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 };