lib/windowing/src/wayland/window/root.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const sys = @import("sys");
  3 const windowing = @import("../../root.zig");
  4 const native = @import("wayland");
  5 const wayland = @import("../root.zig");
  6 
  7 const present = wayland.present;
  8 const present_dmabuf = present.dmabuf;
  9 const seat = wayland.seat;
 10 const session = wayland.session;
 11 const toplevel = wayland.toplevel;
 12 const runtime = native.runtime;
 13 
 14 pub const WaylandBackend = struct {
 15     allocator: std.mem.Allocator,
 16     session: session.Session = .{},
 17     toplevel: toplevel.Toplevel = .{},
 18     seat: seat.Seat = .{},
 19     presenter: present.Presenter = .{},
 20     /// Present when the compositor offers dma-buf feedback and explicit synchronization.
 21     dmabuf: ?present.dmabuf.Dmabuf = null,
 22     gamepads: windowing.gamepad.State,
 23     events: windowing.event.Queue,
 24 
 25     pub fn init(self: *WaylandBackend, allocator: std.mem.Allocator, config: windowing.WindowConfig) windowing.CreateError!void {
 26         if (config.width == 0 or config.height == 0 or config.width > std.math.maxInt(i32) or config.height > std.math.maxInt(i32)) {
 27             return error.WindowCreationFailed;
 28         }
 29         const clipboard_capacity = windowing.clipboard.Capacity.derive(config.clipboard) catch
 30             return error.InvalidCapacity;
 31         const event_capacity = windowing.event.Capacity.derive(config.events) catch
 32             return error.InvalidCapacity;
 33         const input_capacity = windowing.input.Capacity.derive(config.input) catch
 34             return error.InvalidCapacity;
 35         const gamepad_capacity = windowing.gamepad.Capacity.derive(config.gamepads) catch
 36             return error.InvalidCapacity;
 37         _ = windowing.WaylandComposeCapacity.derive(config.wayland_compose) catch
 38             return error.InvalidCapacity;
 39         _ = windowing.WaylandComposeScratchCapacity.derive(
 40             config.wayland_compose_scratch,
 41         ) catch return error.InvalidCapacity;
 42         const session_capacity = session.Capacity.derive(
 43             config.wayland_output_count,
 44         ) catch return error.InvalidCapacity;
 45         const runtime_capacity = runtime.Capacity.derive(
 46             config.wayland_runtime,
 47         ) catch return error.InvalidCapacity;
 48         const presentation_capacity = wayland.presentation.Capacity.derive(
 49             config.presentation,
 50         ) catch return error.InvalidCapacity;
 51         var presenter = present.Presenter.init(
 52             allocator,
 53             presentation_capacity.frames,
 54         ) catch return error.OutOfMemory;
 55         var presenter_owned = true;
 56         errdefer if (presenter_owned) presenter.deinit();
 57         var gamepads = windowing.gamepad.State.init(allocator, gamepad_capacity) catch
 58             return error.OutOfMemory;
 59         const events = windowing.event.Queue.init(allocator, event_capacity) catch {
 60             gamepads.deinit();
 61             return error.OutOfMemory;
 62         };
 63         self.* = .{
 64             .allocator = allocator,
 65             .presenter = presenter,
 66             .gamepads = gamepads,
 67             .events = events,
 68         };
 69         presenter_owned = false;
 70         errdefer self.deinit();
 71 
 72         self.session.open(allocator, session_capacity, runtime_capacity) catch |failure| return switch (failure) {
 73             error.OutOfMemory => error.OutOfMemory,
 74             error.ConnectionFailed => error.ConnectionFailed,
 75             else => error.UnsupportedPlatform,
 76         };
 77         if (!self.session.hasRequiredGlobals()) return error.WindowCreationFailed;
 78 
 79         self.seat.open(
 80             allocator,
 81             &self.session.client,
 82             &self.session.globals,
 83             input_capacity,
 84             clipboard_capacity,
 85             config.wayland_compose,
 86             config.wayland_compose_scratch,
 87             .{
 88                 .context = self,
 89                 .event = seatEvent,
 90                 .focus_changed = seatFocusChanged,
 91                 .synchronize = synchronizeSeat,
 92             },
 93         ) catch |failure| return switch (failure) {
 94             error.OutOfMemory => error.OutOfMemory,
 95             error.SequenceSymbolCapacityExceeded,
 96             error.TextByteCapacityExceeded,
 97             error.LineByteCapacityExceeded,
 98             error.PathByteCapacityExceeded,
 99             => error.InvalidCapacity,
100             else => error.UnsupportedPlatform,
101         };
102 
103         self.presenter.open(
104             &self.session.client,
105             self.session.takePresentation(),
106             presentation_capacity,
107             .{ .context = self, .flush = flushSession },
108         ) catch return error.OutOfMemory;
109 
110         self.toplevel.open(&self.session.client, &self.session.globals, config, .{
111             .context = self,
112             .output_entered = outputEntered,
113             .extent_changed = extentChanged,
114             .focus_changed = toplevelFocusChanged,
115             .close_requested = closeRequested,
116         }) catch return error.WindowCreationFailed;
117         const target = self.toplevel.target() orelse return error.WindowCreationFailed;
118         self.presenter.updateTarget(target);
119         self.seat.setSurface(target.surface);
120         if (self.session.globals.linux_dmabuf) |dmabuf_id| if (self.session.globals.syncobj_manager) |manager_id| {
121             self.dmabuf = present.dmabuf.Dmabuf.open(allocator, &self.session.client, dmabuf_id, manager_id, target.surface) catch |failure| switch (failure) {
122                 error.OutOfMemory => return error.OutOfMemory,
123                 else => null,
124             };
125         };
126 
127         self.session.activate(.{
128             .context = self,
129             .seat_capabilities = seatCapabilities,
130             .seat_removed = seatRemoved,
131             .output_scale_changed = outputScaleChanged,
132             .callback_done = callbackDone,
133             .routed = routed,
134         });
135         self.session.roundtrip() catch |failure| return switch (failure) {
136             error.OutOfMemory => error.OutOfMemory,
137             else => error.ConnectionFailed,
138         };
139         self.presenter.configure(
140             self.session.globals.shm.?,
141             if (self.session.has_xbgr8888) .xbgr8888 else .xrgb8888,
142         );
143         self.session.roundtrip() catch |failure| return switch (failure) {
144             error.OutOfMemory => error.OutOfMemory,
145             else => error.ConnectionFailed,
146         };
147         if (self.session.callback_failed or self.seat.callback_failed or self.toplevel.callback_failed) {
148             return error.ConnectionFailed;
149         }
150         if (!self.toplevel.configured or !self.toplevel.server_decorated) return error.WindowCreationFailed;
151     }
152 
153     pub fn deinit(self: *WaylandBackend) void {
154         self.gamepads.deinit();
155         if (self.dmabuf) |*explicit| explicit.deinit();
156         self.presenter.deinit();
157         self.seat.deinit();
158         self.toplevel.deinit();
159         self.session.deinit();
160         self.events.deinit();
161         self.* = undefined;
162     }
163 
164     pub fn pollEvents(self: *WaylandBackend) windowing.PollError!windowing.EventIterator {
165         self.seat.input.beginPoll();
166         self.gamepads.poll();
167         self.events.beginPoll();
168         self.session.poll() catch return error.ConnectionLost;
169         if (self.toplevel.callback_failed) return error.ConnectionLost;
170         self.seat.poll() catch return error.ConnectionLost;
171         self.presenter.poll() catch return error.PresentationFailed;
172         return self.events.iterator();
173     }
174 
175     pub fn eventSource(self: *const WaylandBackend) ?windowing.EventSource {
176         const descriptor = self.session.descriptor() orelse return null;
177         return .{
178             .descriptor = descriptor,
179             .writable = self.session.flush_pending,
180         };
181     }
182 
183     pub fn shouldClose(self: *const WaylandBackend) bool {
184         return self.toplevel.should_close;
185     }
186 
187     pub fn getSize(self: *const WaylandBackend) windowing.Size {
188         return self.toplevel.size();
189     }
190 
191     pub fn getFramebufferSize(self: *const WaylandBackend) windowing.Size {
192         return self.toplevel.framebufferSize();
193     }
194 
195     pub fn nativeSurface(self: *WaylandBackend) windowing.Surface {
196         const descriptor = self.session.descriptor() orelse unreachable;
197         const target = self.toplevel.target() orelse unreachable;
198         const factor = target.scale_state.factor();
199         return .{ .wayland = .{
200             .descriptor = descriptor,
201             .surface_id = target.surface,
202             .extent = windowing.surface.extentFromSizes(
203                 target.logical.width,
204                 target.logical.height,
205                 target.framebuffer.width,
206                 target.framebuffer.height,
207                 factor,
208                 factor,
209             ),
210         } };
211     }
212 
213     pub fn presentRgba8(
214         self: *WaylandBackend,
215         rgba8: []const u8,
216         width: u32,
217         height: u32,
218     ) windowing.PresentError!void {
219         const target = self.toplevel.target() orelse return error.Unexpected;
220         return self.presenter.present(target, rgba8, width, height, windowing.PresentRegion.full(width, height));
221     }
222 
223     pub fn presentRgba8Region(
224         self: *WaylandBackend,
225         rgba8: []const u8,
226         width: u32,
227         height: u32,
228         region: windowing.PresentRegion,
229     ) windowing.PresentError!void {
230         const target = self.toplevel.target() orelse return error.Unexpected;
231         return self.presenter.present(target, rgba8, width, height, region);
232     }
233 
234     pub fn getContentScale(self: *const WaylandBackend) windowing.Scale {
235         return self.toplevel.contentScale();
236     }
237 
238     pub fn inputState(self: *const WaylandBackend) *const windowing.input.State {
239         return self.seat.inputState();
240     }
241 
242     pub fn inputStateMut(self: *WaylandBackend) *windowing.input.State {
243         return self.seat.inputStateMut();
244     }
245 
246     pub fn modifiers(self: *const WaylandBackend) windowing.Modifier {
247         return self.seat.modifiers();
248     }
249 
250     pub fn gamepadState(self: *const WaylandBackend) *const windowing.gamepad.State {
251         return &self.gamepads;
252     }
253 
254     pub fn setTitle(self: *WaylandBackend, title: [:0]const u8) void {
255         self.toplevel.setTitle(title);
256     }
257 
258     pub fn setMinSize(self: *WaylandBackend, width: u32, height: u32) void {
259         self.toplevel.setMinSize(width, height);
260     }
261 
262     pub fn setCursor(self: *WaylandBackend, shape: windowing.CursorShape) void {
263         self.seat.setCursor(shape);
264     }
265 
266     pub fn isCursorOnScreen(self: *const WaylandBackend) bool {
267         return self.seat.cursor_on_screen;
268     }
269 
270     pub fn minimize(self: *WaylandBackend) void {
271         self.toplevel.minimize();
272     }
273 
274     pub fn maximize(self: *WaylandBackend) void {
275         self.toplevel.maximize();
276     }
277 
278     pub fn restore(self: *WaylandBackend) void {
279         self.toplevel.restore();
280     }
281 
282     pub fn isMaximized(self: *const WaylandBackend) bool {
283         return self.toplevel.maximized;
284     }
285 
286     pub fn getClipboardTextAlloc(self: *WaylandBackend, allocator: std.mem.Allocator) !?[]u8 {
287         return self.seat.getClipboardTextAlloc(allocator);
288     }
289 
290     pub fn setClipboardText(
291         self: *WaylandBackend,
292         text: []const u8,
293     ) windowing.clipboard.PublishError!void {
294         try self.seat.setClipboardText(text);
295     }
296 
297     pub fn clipboardStatus(self: *const WaylandBackend) windowing.clipboard.Status {
298         return self.seat.clipboardStatus();
299     }
300 
301     pub fn x11StorageStatus(_: *const WaylandBackend) sys.x11.Status {
302         return .{};
303     }
304 
305     pub fn waylandStorageStatus(self: *const WaylandBackend) windowing.WaylandRuntimeStorageStatus {
306         return self.session.storageStatus();
307     }
308 
309     pub fn nextPresentationOutcome(self: *WaylandBackend) ?windowing.presentation.Outcome {
310         return self.presenter.nextPresentationOutcome();
311     }
312 
313     pub fn presentationStatus(self: *const WaylandBackend) windowing.presentation.Status {
314         return self.presenter.presentationStatus();
315     }
316 
317     pub fn presentationStorageStatus(
318         self: *const WaylandBackend,
319     ) windowing.presentation.StorageStatus {
320         const status = self.presenter.presentationStorageStatus();
321         const retired_rejections = status.retired_buffer_capacity_rejection_count;
322         return .{
323             .retired_buffer_capacity_rejection_count = retired_rejections,
324             .frame_capacity_rejection_count = status.frame_capacity_rejection_count,
325             .wayland_output_capacity_rejection_count = self.session.outputCapacityRejectionCount(),
326             .damage_capacity_collapse_count = status.damage_capacity_collapse_count,
327         };
328     }
329 
330     /// The window's dma-buf presentation, when the compositor offers it.
331     pub fn waylandDmabuf(self: *WaylandBackend) ?DmabufSurface {
332         if (self.dmabuf == null) return null;
333         return .{ .backend = self };
334     }
335 
336     pub fn droppedEventCount(self: *const WaylandBackend) u64 {
337         return self.events.droppedEventCount();
338     }
339 
340     fn push(self: *WaylandBackend, event: windowing.Event) void {
341         _ = self.events.push(event);
342     }
343 
344     fn seatEvent(context: *anyopaque, event: windowing.Event) void {
345         const self: *WaylandBackend = @ptrCast(@alignCast(context));
346         self.push(event);
347     }
348 
349     fn seatFocusChanged(context: *anyopaque, focused: bool) void {
350         const self: *WaylandBackend = @ptrCast(@alignCast(context));
351         self.toplevel.setFocused(focused);
352     }
353 
354     fn synchronizeSeat(context: *anyopaque, timeout_ms: u32) anyerror!void {
355         const self: *WaylandBackend = @ptrCast(@alignCast(context));
356         try self.session.synchronize(timeout_ms);
357     }
358 
359     fn seatCapabilities(context: *anyopaque, capabilities: u32) void {
360         const self: *WaylandBackend = @ptrCast(@alignCast(context));
361         self.seat.setGlobals(&self.session.globals) catch {
362             self.seat.callback_failed = true;
363             return;
364         };
365         self.seat.capabilities(capabilities);
366     }
367 
368     fn seatRemoved(context: *anyopaque) void {
369         const self: *WaylandBackend = @ptrCast(@alignCast(context));
370         self.seat.seatRemoved();
371     }
372 
373     fn outputScaleChanged(context: *anyopaque, integer_scale: u32) void {
374         const self: *WaylandBackend = @ptrCast(@alignCast(context));
375         self.toplevel.setIntegerScale(integer_scale);
376     }
377 
378     fn outputEntered(context: *anyopaque, object_id: u32, entered: bool) u32 {
379         const self: *WaylandBackend = @ptrCast(@alignCast(context));
380         return self.session.setOutputEntered(object_id, entered);
381     }
382 
383     fn extentChanged(context: *anyopaque, target: toplevel.Target, notify_resize: bool) void {
384         const self: *WaylandBackend = @ptrCast(@alignCast(context));
385         self.presenter.updateTarget(target);
386         if (notify_resize) self.push(.{ .window_resize = .{
387             .width = target.logical.width,
388             .height = target.logical.height,
389         } });
390     }
391 
392     fn toplevelFocusChanged(context: *anyopaque, focused: bool) void {
393         const self: *WaylandBackend = @ptrCast(@alignCast(context));
394         self.push(.{ .window_focus = .{ .focused = focused } });
395     }
396 
397     fn closeRequested(context: *anyopaque) void {
398         const self: *WaylandBackend = @ptrCast(@alignCast(context));
399         self.push(.window_close);
400     }
401 
402     fn flushSession(context: *anyopaque) void {
403         const self: *WaylandBackend = @ptrCast(@alignCast(context));
404         self.session.flush();
405     }
406 
407     fn callbackDone(context: *anyopaque, callback_id: u32, data: u32) anyerror!void {
408         const self: *WaylandBackend = @ptrCast(@alignCast(context));
409         if (try self.presenter.dispatchCallback(callback_id, data)) return;
410         return error.UnhandledCallback;
411     }
412 
413     fn routed(context: *anyopaque, event: *@import("wayland").runtime.RoutedView) anyerror!void {
414         const self: *WaylandBackend = @ptrCast(@alignCast(context));
415         if (try self.toplevel.dispatch(event)) return;
416         if (try self.seat.dispatch(event)) return;
417         if (self.dmabuf) |*explicit| if (try explicit.dispatch(event)) return;
418         if (try self.presenter.dispatch(event)) return;
419         return error.UnhandledEvent;
420     }
421 };
422 
423 /// Presents buffers another device rendered on a Wayland window, as dma-bufs with explicit
424 /// synchronization. `Window.waylandDmabuf` hands one out when the compositor offers both
425 /// protocols. It stays valid as long as the window.
426 pub const DmabufSurface = struct {
427     backend: *WaylandBackend,
428 
429     fn explicit(self: DmabufSurface) *present_dmabuf.Dmabuf {
430         return &self.backend.dmabuf.?;
431     }
432 
433     /// The `zwp_linux_dmabuf_v1` version the window bound.
434     pub fn version(self: DmabufSurface) u32 {
435         return self.explicit().version();
436     }
437 
438     /// The compositor's latest complete feedback for the window's surface.
439     pub fn feedback(self: DmabufSurface) ?*const present_dmabuf.Feedback {
440         return self.explicit().currentFeedback();
441     }
442 
443     pub fn importBuffer(self: DmabufSurface, desc: present_dmabuf.BufferDesc) present_dmabuf.Error!present_dmabuf.Buffer {
444         return self.explicit().importBuffer(desc);
445     }
446 
447     pub fn releaseBuffer(self: DmabufSurface, buffer: present_dmabuf.Buffer) void {
448         self.explicit().releaseBuffer(buffer);
449     }
450 
451     pub fn importTimeline(self: DmabufSurface, fd: sys.fd.Descriptor) present_dmabuf.Error!present_dmabuf.Timeline {
452         return self.explicit().importTimeline(fd);
453     }
454 
455     pub fn destroyTimeline(self: DmabufSurface, timeline: present_dmabuf.Timeline) void {
456         self.explicit().destroyTimeline(timeline);
457     }
458 
459     /// Whether the compositor has asked for the next frame.
460     pub fn frameReady(self: DmabufSurface) bool {
461         return self.backend.presenter.frameReady();
462     }
463 
464     /// Commits `frame`. See `Presenter.presentDmabuf`.
465     pub fn present(self: DmabufSurface, frame: present_dmabuf.Frame) present_dmabuf.PresentError!?u64 {
466         const target = self.backend.toplevel.target() orelse return error.Unexpected;
467         return self.backend.presenter.presentDmabuf(target, self.explicit(), frame);
468     }
469 };