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