lib/windowing/src/wayland/session/root.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const sys = @import("sys");
3 const native = @import("wayland");
4
5 const core = native.protocol.core;
6 const desktop = native.protocol.desktop;
7 const runtime = native.runtime;
8
9 pub const output = @import("output.zig");
10
11 const global = @import("global.zig");
12 const dmabuf = @import("../present/dmabuf.zig");
13 const storage = @import("storage.zig");
14
15 pub const CapacityError = storage.CapacityError;
16 pub const Capacity = storage.Capacity;
17 const classify = global.classify;
18 const OutputStorage = storage.OutputStorage;
19
20 pub const Callbacks = struct {
21 context: *anyopaque,
22 seat_capabilities: *const fn (context: *anyopaque, capabilities: u32) void,
23 seat_removed: *const fn (context: *anyopaque) void,
24 output_scale_changed: *const fn (context: *anyopaque, scale: u32) void,
25 callback_done: *const fn (context: *anyopaque, callback_id: u32, data: u32) anyerror!void,
26 routed: *const fn (context: *anyopaque, event: *runtime.RoutedView) anyerror!void,
27 };
28
29 pub const Globals = struct {
30 compositor: ?u32 = null,
31 shm: ?u32 = null,
32 seat: ?u32 = null,
33 data_device_manager: ?u32 = null,
34 xdg_wm_base: ?u32 = null,
35 viewporter: ?u32 = null,
36 fractional_scale_manager: ?u32 = null,
37 decoration_manager: ?u32 = null,
38 cursor_shape_manager: ?u32 = null,
39 presentation: ?u32 = null,
40 linux_dmabuf: ?u32 = null,
41 syncobj_manager: ?u32 = null,
42 seat_name: ?u32 = null,
43 };
44
45 pub const Session = struct {
46 client: runtime.Client = undefined,
47 client_open: bool = false,
48 registry_id: ?u32 = null,
49 globals: Globals = .{},
50 outputs: OutputStorage = .{},
51 callbacks: ?Callbacks = null,
52 seat_capabilities: u32 = 0,
53 has_xbgr8888: bool = false,
54 callback_failed: bool = false,
55 flush_pending: bool = false,
56
57 pub fn open(
58 self: *Session,
59 allocator: std.mem.Allocator,
60 capacity: Capacity,
61 client_capacity: runtime.Capacity,
62 ) !void {
63 self.* = .{ .outputs = try OutputStorage.init(allocator, capacity) };
64 errdefer self.deinit();
65
66 self.client = try runtime.Client.connect(allocator, client_capacity);
67 self.client_open = true;
68 self.registry_id = try self.client.getRegistry();
69 try self.roundtrip();
70 }
71
72 pub fn deinit(self: *Session) void {
73 self.outputs.deinit();
74 if (self.client_open) self.client.deinit();
75 self.* = .{};
76 }
77
78 pub fn activate(self: *Session, callbacks: Callbacks) void {
79 self.callbacks = callbacks;
80 callbacks.seat_capabilities(callbacks.context, self.seat_capabilities);
81 callbacks.output_scale_changed(callbacks.context, self.enteredScale());
82 }
83
84 pub fn roundtrip(self: *Session) !void {
85 const callback_id = try self.client.sync();
86 try self.flushBlocking(-1);
87 while (true) {
88 switch (try self.client.step()) {
89 .event => |value| {
90 var event = value;
91 switch (event) {
92 .callback_done => |done| {
93 if (done.callback_id == callback_id) return;
94 try self.dispatchCallback(done.callback_id, done.data);
95 },
96 else => try self.dispatchEvent(&event),
97 }
98 },
99 .pending => {
100 if (!try sys.net.pollReadable(self.client.descriptor(), -1)) {
101 return error.ConnectionFailed;
102 }
103 },
104 .closed => return error.ConnectionFailed,
105 }
106 }
107 }
108
109 pub fn poll(self: *Session) !void {
110 try self.drain();
111 self.flush();
112 if (try sys.net.pollReadable(self.client.descriptor(), 0)) try self.drain();
113 self.flush();
114 if (self.callback_failed) return error.ConnectionLost;
115 }
116
117 pub fn flush(self: *Session) void {
118 const status = self.client.flush() catch {
119 self.callback_failed = true;
120 return;
121 };
122 self.flush_pending = status == .pending;
123 }
124
125 pub fn synchronize(self: *Session, timeout_ms: u32) !void {
126 try self.flushBlocking(@intCast(@min(timeout_ms, std.math.maxInt(i32))));
127 }
128
129 pub fn descriptor(self: *const Session) ?sys.fd.Descriptor {
130 if (!self.client_open) return null;
131 return self.client.descriptor();
132 }
133
134 pub fn storageStatus(self: *const Session) runtime.StorageStatus {
135 if (!self.client_open) return .{};
136 return self.client.storageStatus();
137 }
138
139 pub fn takePresentation(self: *Session) ?u32 {
140 const id = self.globals.presentation;
141 self.globals.presentation = null;
142 return id;
143 }
144
145 pub fn hasRequiredGlobals(self: *const Session) bool {
146 const compositor_id = self.globals.compositor orelse return false;
147 const compositor = self.client.object(compositor_id) orelse return false;
148 return compositor.version >= 4 and
149 self.globals.shm != null and
150 self.globals.seat != null and
151 self.globals.xdg_wm_base != null and
152 self.globals.decoration_manager != null and
153 self.globals.cursor_shape_manager != null;
154 }
155
156 pub fn setOutputEntered(self: *Session, object_id: u32, entered: bool) u32 {
157 for (self.outputs.retainedMut()) |*value| {
158 if (value.id != object_id) continue;
159 if (value.setEntered(entered)) self.notifyOutputScale();
160 break;
161 }
162 return self.enteredScale();
163 }
164
165 pub fn enteredScale(self: *const Session) u32 {
166 var maximum: u32 = 1;
167 for (self.outputs.retained()) |value| {
168 if (value.entered) maximum = @max(maximum, value.integer_scale);
169 }
170 return maximum;
171 }
172
173 pub fn outputCapacityRejectionCount(self: *const Session) u64 {
174 return self.outputs.capacity_rejection_count;
175 }
176
177 fn drain(self: *Session) !void {
178 while (true) {
179 switch (try self.client.step()) {
180 .event => |value| {
181 var event = value;
182 try self.dispatchEvent(&event);
183 },
184 .pending => return,
185 .closed => return error.ConnectionLost,
186 }
187 }
188 }
189
190 fn dispatchEvent(self: *Session, event: *runtime.EventView) !void {
191 switch (event.*) {
192 .fatal => return error.ConnectionLost,
193 .delete_id => {},
194 .callback_done => |done| try self.dispatchCallback(done.callback_id, done.data),
195 .global => |item| try self.registryGlobal(item.name, item.interface, item.version),
196 .global_remove => |item| self.registryRemove(item.name),
197 .routed => |*routed| try self.dispatchRouted(routed),
198 }
199 }
200
201 fn dispatchCallback(self: *Session, callback_id: u32, data: u32) !void {
202 const callbacks = self.callbacks orelse return;
203 try callbacks.callback_done(callbacks.context, callback_id, data);
204 }
205
206 fn dispatchRouted(self: *Session, event: *runtime.RoutedView) !void {
207 const name = event.interface.name;
208 if (std.mem.eql(u8, name, "wl_shm")) return self.dispatchShm(event);
209 if (std.mem.eql(u8, name, "wl_seat")) return self.dispatchSeat(event);
210 if (std.mem.eql(u8, name, "wl_output")) return self.dispatchOutput(event);
211 if (std.mem.eql(u8, name, "xdg_wm_base")) return self.dispatchWmBase(event);
212 const callbacks = self.callbacks orelse return error.UnhandledEvent;
213 try callbacks.routed(callbacks.context, event);
214 }
215
216 fn dispatchShm(self: *Session, event: *runtime.RoutedView) !void {
217 if (event.metadata.opcode != core.wl_shm.events.format.opcode) return error.InvalidEvent;
218 var decoder = try event.borrowedDecoder();
219 const format = try decoder.unsigned();
220 try decoder.finish();
221 if (format == 0x3432_4258) self.has_xbgr8888 = true;
222 }
223
224 fn dispatchSeat(self: *Session, event: *runtime.RoutedView) !void {
225 if (event.object_id != self.globals.seat) return;
226 switch (event.metadata.opcode) {
227 core.wl_seat.events.capabilities.opcode => {
228 var decoder = try event.borrowedDecoder();
229 self.seat_capabilities = try decoder.unsigned();
230 try decoder.finish();
231 if (self.callbacks) |callbacks| {
232 callbacks.seat_capabilities(callbacks.context, self.seat_capabilities);
233 }
234 },
235 core.wl_seat.events.name.opcode => {},
236 else => return error.InvalidEvent,
237 }
238 }
239
240 fn dispatchOutput(self: *Session, event: *runtime.RoutedView) !void {
241 for (self.outputs.retainedMut()) |*value| {
242 if (value.id != event.object_id) continue;
243 if (try value.dispatch(event)) self.notifyOutputScale();
244 return;
245 }
246 }
247
248 fn dispatchWmBase(self: *Session, event: *runtime.RoutedView) !void {
249 if (event.metadata.opcode != desktop.xdg_wm_base.events.ping.opcode) {
250 return error.InvalidEvent;
251 }
252 var decoder = try event.borrowedDecoder();
253 const serial = try decoder.unsigned();
254 try decoder.finish();
255 try self.client.request(
256 event.object_id,
257 desktop.xdg_wm_base.requests.pong.opcode,
258 &.{.{ .uint = serial }},
259 &.{},
260 );
261 }
262
263 fn registryGlobal(self: *Session, name: u32, interface_name: []const u8, version: u32) !void {
264 const registry_id = self.registry_id orelse return error.ConnectionFailed;
265 switch (classify(interface_name)) {
266 .compositor => try self.bindUnique(&self.globals.compositor, registry_id, name, version, &core.wl_compositor.metadata, 6),
267 .shm => try self.bindUnique(&self.globals.shm, registry_id, name, version, &core.wl_shm.metadata, 1),
268 .seat => {
269 if (self.globals.seat != null) return;
270 try self.bindUnique(&self.globals.seat, registry_id, name, version, &core.wl_seat.metadata, 9);
271 if (self.globals.seat != null) self.globals.seat_name = name;
272 },
273 .output => try self.bindOutput(registry_id, name, version),
274 .data_device_manager => try self.bindUnique(&self.globals.data_device_manager, registry_id, name, version, &core.wl_data_device_manager.metadata, 3),
275 .xdg_wm_base => try self.bindUnique(&self.globals.xdg_wm_base, registry_id, name, version, &desktop.xdg_wm_base.metadata, 7),
276 .viewporter => try self.bindUnique(&self.globals.viewporter, registry_id, name, version, &desktop.wp_viewporter.metadata, 1),
277 .fractional_scale_manager => try self.bindUnique(&self.globals.fractional_scale_manager, registry_id, name, version, &desktop.wp_fractional_scale_manager_v1.metadata, 1),
278 .decoration_manager => try self.bindUnique(&self.globals.decoration_manager, registry_id, name, version, &desktop.zxdg_decoration_manager_v1.metadata, 1),
279 .cursor_shape_manager => try self.bindUnique(&self.globals.cursor_shape_manager, registry_id, name, version, &desktop.wp_cursor_shape_manager_v1.metadata, 2),
280 .presentation => try self.bindUnique(&self.globals.presentation, registry_id, name, version, &desktop.wp_presentation.metadata, 2),
281 .linux_dmabuf => if (version >= dmabuf.minimum_dmabuf_version) {
282 try self.bindUnique(&self.globals.linux_dmabuf, registry_id, name, version, &desktop.zwp_linux_dmabuf_v1.metadata, dmabuf.maximum_dmabuf_version);
283 },
284 .syncobj_manager => try self.bindUnique(&self.globals.syncobj_manager, registry_id, name, version, &desktop.wp_linux_drm_syncobj_manager_v1.metadata, 1),
285 .unknown => {},
286 }
287 }
288
289 fn registryRemove(self: *Session, name: u32) void {
290 if (self.globals.seat_name == name) {
291 if (self.callbacks) |callbacks| callbacks.seat_removed(callbacks.context);
292 self.destroyVersioned(&self.globals.seat, core.wl_seat.requests.release.opcode, 5);
293 self.globals.seat_name = null;
294 self.seat_capabilities = 0;
295 return;
296 }
297 if (self.outputs.removeGlobal(name)) |value| {
298 self.destroyVersionedId(value.id, core.wl_output.requests.release.opcode, 3);
299 self.notifyOutputScale();
300 return;
301 }
302 }
303
304 fn bindUnique(
305 self: *Session,
306 slot: *?u32,
307 registry_id: u32,
308 name: u32,
309 advertised_version: u32,
310 interface: *const native.protocol.schema.Interface,
311 supported_version: u32,
312 ) !void {
313 if (slot.* != null) return;
314 slot.* = try self.client.bind(
315 registry_id,
316 name,
317 interface,
318 @min(advertised_version, supported_version),
319 );
320 }
321
322 fn bindOutput(self: *Session, registry_id: u32, name: u32, advertised_version: u32) !void {
323 const index = self.outputs.reserve() orelse return;
324 const id = try self.client.bind(
325 registry_id,
326 name,
327 &core.wl_output.metadata,
328 @min(advertised_version, 4),
329 );
330 self.outputs.commit(index, .{
331 .global_name = name,
332 .id = id,
333 });
334 }
335
336 fn notifyOutputScale(self: *Session) void {
337 if (self.callbacks) |callbacks| {
338 callbacks.output_scale_changed(callbacks.context, self.enteredScale());
339 }
340 }
341
342 fn destroyVersioned(self: *Session, slot: *?u32, opcode: u16, since: u32) void {
343 const id = slot.* orelse return;
344 self.destroyVersionedId(id, opcode, since);
345 slot.* = null;
346 }
347
348 fn destroyVersionedId(self: *Session, id: u32, opcode: u16, since: u32) void {
349 const object = self.client.object(id) orelse return;
350 if (object.version < since) return;
351 self.client.request(id, opcode, &.{}, &.{}) catch {
352 self.callback_failed = true;
353 };
354 }
355
356 fn flushBlocking(self: *Session, timeout_ms: i32) !void {
357 while (true) {
358 const status = try self.client.flush();
359 self.flush_pending = status == .pending;
360 if (!self.flush_pending) return;
361 if (!try sys.net.pollWritable(self.client.descriptor(), timeout_ms)) {
362 return error.ConnectionLost;
363 }
364 }
365 }
366 };