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

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const sys = @import("sys");
  3 const native = @import("wayland");
  4 const windowing = @import("../../root.zig");
  5 const wayland = @import("../root.zig");
  6 
  7 pub const clipboard = @import("clipboard.zig");
  8 pub const keyboard = @import("keyboard.zig");
  9 pub const pointer = @import("pointer.zig");
 10 
 11 const core = native.protocol.core;
 12 const desktop = native.protocol.desktop;
 13 const runtime = native.runtime;
 14 const session = wayland.session;
 15 const support = @import("support.zig");
 16 
 17 pub const Callbacks = struct {
 18     context: *anyopaque,
 19     event: *const fn (context: *anyopaque, value: windowing.Event) void,
 20     focus_changed: *const fn (context: *anyopaque, focused: bool) void,
 21     synchronize: *const fn (context: *anyopaque, timeout_ms: u32) anyerror!void,
 22 };
 23 
 24 pub const Seat = struct {
 25     allocator: std.mem.Allocator = undefined,
 26     client: ?*runtime.Client = null,
 27     callbacks: ?Callbacks = null,
 28     seat_id: ?u32 = null,
 29     data_device_manager: ?u32 = null,
 30     cursor_shape_manager: ?u32 = null,
 31     cursor_device: ?u32 = null,
 32     keyboard_id: ?u32 = null,
 33     keyboard_version: u32 = 0,
 34     keyboard_active: bool = false,
 35     pointer_state: ?pointer.Pointer = null,
 36     keyboard_state: ?keyboard.Keyboard = null,
 37     clipboard_state: ?clipboard.Clipboard = null,
 38     surface: ?u32 = null,
 39     input: windowing.input.State = undefined,
 40     cursor_shape: windowing.CursorShape = .default,
 41     cursor_on_screen: bool = false,
 42     last_input_serial: ?u32 = null,
 43     callback_failed: bool = false,
 44 
 45     pub fn open(
 46         self: *Seat,
 47         allocator: std.mem.Allocator,
 48         client: *runtime.Client,
 49         globals: *const session.Globals,
 50         input_capacity: windowing.input.Capacity,
 51         clipboard_capacity: windowing.clipboard.Capacity,
 52         compose_limits: keyboard.ComposeLimits,
 53         compose_scratch_limits: keyboard.ComposeScratchLimits,
 54         callbacks: Callbacks,
 55     ) !void {
 56         const input_state = try windowing.input.State.init(allocator, input_capacity);
 57         self.* = .{
 58             .allocator = allocator,
 59             .client = client,
 60             .callbacks = callbacks,
 61             .input = input_state,
 62         };
 63         errdefer self.deinit();
 64         self.pointer_state = pointer.Pointer.init(self, .{
 65             .entered = pointerEntered,
 66             .left = pointerLeft,
 67             .moved = pointerMoved,
 68             .button = pointerButton,
 69             .axis = pointerAxis,
 70         });
 71         self.keyboard_state = try keyboard.Keyboard.init(allocator, .{
 72             .context = self,
 73             .focus = keyboardFocus,
 74             .key = keyboardKey,
 75             .text = keyboardText,
 76         }, .{
 77             .compose_limits = compose_limits,
 78             .compose_scratch_limits = compose_scratch_limits,
 79         });
 80         self.clipboard_state = try clipboard.Clipboard.init(allocator, client, clipboard_capacity);
 81         try self.setGlobals(globals);
 82     }
 83 
 84     pub fn deinit(self: *Seat) void {
 85         const client = self.client orelse {
 86             self.* = .{};
 87             return;
 88         };
 89         if (self.clipboard_state) |*state| state.deinit();
 90         self.clipboard_state = null;
 91         self.destroyCursorDevice();
 92         if (self.pointer_state) |*state| state.deinit(client);
 93         self.pointer_state = null;
 94         self.destroyKeyboard(true);
 95         if (self.keyboard_state) |*state| state.deinit();
 96         self.keyboard_state = null;
 97         self.input.deinit();
 98         self.* = .{};
 99     }
100 
101     pub fn dispatch(self: *Seat, event: *runtime.RoutedView) !bool {
102         if (self.pointer_state) |*state| {
103             if (event.object_id == state.object_id) {
104                 try state.dispatch(event);
105                 return true;
106             }
107         }
108         if (event.object_id == self.keyboard_id) {
109             if (self.keyboard_active) {
110                 const state = if (self.keyboard_state) |*value| value else return error.KeyboardUnavailable;
111                 try state.dispatch(event);
112             }
113             return true;
114         }
115         if (self.clipboard_state) |*state| {
116             if (try state.dispatch(event)) return true;
117         }
118         return false;
119     }
120 
121     pub fn setGlobals(self: *Seat, globals: *const session.Globals) !void {
122         self.data_device_manager = globals.data_device_manager;
123         self.cursor_shape_manager = globals.cursor_shape_manager;
124         if (self.seat_id == globals.seat) return;
125         self.seatRemoved();
126         self.seat_id = globals.seat;
127         const manager = self.data_device_manager orelse return;
128         const seat = self.seat_id orelse return;
129         const state = if (self.clipboard_state) |*value| value else return;
130         try state.bindSeat(manager, seat);
131     }
132 
133     pub fn setSurface(self: *Seat, surface: ?u32) void {
134         self.surface = surface;
135         if (self.keyboard_state) |*state| state.setSurface(surface);
136     }
137 
138     pub fn capabilities(self: *Seat, value: u32) void {
139         self.updatePointerCapability((value & 1) != 0);
140         self.updateKeyboardCapability((value & 2) != 0);
141     }
142 
143     pub fn seatRemoved(self: *Seat) void {
144         if (self.clipboard_state) |*state| state.unbindSeat();
145         self.updatePointerCapability(false);
146         self.updateKeyboardCapability(false);
147         self.last_input_serial = null;
148         self.seat_id = null;
149     }
150 
151     pub fn poll(self: *Seat) !void {
152         if (self.callback_failed) return error.CallbackFailed;
153         if (self.keyboard_state) |*state| {
154             if (state.takeFailure() != null) return error.KeyboardFailed;
155             const milliseconds = sys.time.milliTimestamp();
156             const now: u32 = if (milliseconds <= 0) 0 else @truncate(@as(u64, @intCast(milliseconds)));
157             _ = try state.dispatchRepeat(now);
158         }
159     }
160 
161     pub fn inputState(self: *const Seat) *const windowing.input.State {
162         return &self.input;
163     }
164 
165     pub fn inputStateMut(self: *Seat) *windowing.input.State {
166         return &self.input;
167     }
168 
169     pub fn modifiers(self: *const Seat) windowing.Modifier {
170         if (self.keyboard_state) |*state| return state.modifiers();
171         return self.input.modifiers();
172     }
173 
174     pub fn setCursor(self: *Seat, shape: windowing.CursorShape) void {
175         self.cursor_shape = shape;
176         self.applyCursorShape();
177     }
178 
179     pub fn getClipboardTextAlloc(self: *Seat, allocator: std.mem.Allocator) !?[]u8 {
180         const state = if (self.clipboard_state) |*value| value else return null;
181         return state.getTextAlloc(allocator, .{
182             .context = self,
183             .run = synchronizeClipboard,
184         });
185     }
186 
187     pub fn setClipboardText(
188         self: *Seat,
189         text: []const u8,
190     ) windowing.clipboard.PublishError!void {
191         const serial = self.last_input_serial orelse return error.Unavailable;
192         const state = if (self.clipboard_state) |*value| value else return error.Unavailable;
193         try state.setText(text, serial);
194     }
195 
196     pub fn clipboardStatus(self: *const Seat) windowing.clipboard.Status {
197         const state = if (self.clipboard_state) |*value| value else return .{};
198         return state.status();
199     }
200 
201     fn updatePointerCapability(self: *Seat, available: bool) void {
202         const client = self.client orelse return;
203         const state = if (self.pointer_state) |*value| value else return;
204         if (!available) {
205             self.destroyCursorDevice();
206             state.deactivate(client);
207             self.cursor_on_screen = false;
208             self.releaseMouseButtons();
209             return;
210         }
211         if (state.object_id != null) {
212             state.active = true;
213             self.ensureCursorDevice();
214             return;
215         }
216         const seat = self.seat_id orelse {
217             self.callback_failed = true;
218             return;
219         };
220         var created: [1]u32 = undefined;
221         client.request(
222             seat,
223             core.wl_seat.requests.get_pointer.opcode,
224             &.{.{ .new_id = .fixed }},
225             &created,
226         ) catch {
227             self.callback_failed = true;
228             return;
229         };
230         const version = (client.object(created[0]) orelse {
231             self.callback_failed = true;
232             return;
233         }).version;
234         state.attach(created[0], version);
235         self.ensureCursorDevice();
236     }
237 
238     fn updateKeyboardCapability(self: *Seat, available: bool) void {
239         const client = self.client orelse return;
240         if (!available) {
241             self.destroyKeyboard(false);
242             return;
243         }
244         if (self.keyboard_id != null) {
245             self.keyboard_active = true;
246             return;
247         }
248         const seat = self.seat_id orelse {
249             self.callback_failed = true;
250             return;
251         };
252         var created: [1]u32 = undefined;
253         client.request(
254             seat,
255             core.wl_seat.requests.get_keyboard.opcode,
256             &.{.{ .new_id = .fixed }},
257             &created,
258         ) catch {
259             self.callback_failed = true;
260             return;
261         };
262         const version = (client.object(created[0]) orelse {
263             self.callback_failed = true;
264             return;
265         }).version;
266         self.keyboard_id = created[0];
267         self.keyboard_version = version;
268         self.keyboard_active = true;
269     }
270 
271     fn ensureCursorDevice(self: *Seat) void {
272         if (self.cursor_device != null) return;
273         const client = self.client orelse return;
274         const manager = self.cursor_shape_manager orelse return;
275         const state = if (self.pointer_state) |*value| value else return;
276         const pointer_id = state.object_id orelse return;
277         var created: [1]u32 = undefined;
278         client.request(
279             manager,
280             desktop.wp_cursor_shape_manager_v1.requests.get_pointer.opcode,
281             &.{ .{ .new_id = .fixed }, .{ .object = pointer_id } },
282             &created,
283         ) catch {
284             self.callback_failed = true;
285             return;
286         };
287         self.cursor_device = created[0];
288     }
289 
290     fn applyCursorShape(self: *Seat) void {
291         const client = self.client orelse return;
292         const device = self.cursor_device orelse return;
293         const state = if (self.pointer_state) |*value| value else return;
294         const serial = state.cursorSerial() orelse return;
295         client.request(
296             device,
297             desktop.wp_cursor_shape_device_v1.requests.set_shape.opcode,
298             &.{ .{ .uint = serial }, .{ .uint = cursorShapeValue(self.cursor_shape) } },
299             &.{},
300         ) catch {
301             self.callback_failed = true;
302         };
303     }
304 
305     fn destroyCursorDevice(self: *Seat) void {
306         const client = self.client orelse return;
307         const device = self.cursor_device orelse return;
308         client.request(
309             device,
310             desktop.wp_cursor_shape_device_v1.requests.destroy.opcode,
311             &.{},
312             &.{},
313         ) catch {};
314         self.cursor_device = null;
315     }
316 
317     fn destroyKeyboard(self: *Seat, final: bool) void {
318         if (self.keyboard_state) |*state| state.clearFocus();
319         const client = self.client orelse return;
320         const object_id = self.keyboard_id orelse return;
321         if (self.keyboard_version >= core.wl_keyboard.requests.release.since) {
322             client.request(object_id, core.wl_keyboard.requests.release.opcode, &.{}, &.{}) catch {};
323             self.keyboard_id = null;
324             self.keyboard_version = 0;
325         } else if (final) {
326             self.keyboard_id = null;
327             self.keyboard_version = 0;
328         }
329         self.keyboard_active = false;
330     }
331 
332     fn ownsSurface(self: *const Seat, candidate: ?u32) bool {
333         const expected = self.surface orelse return false;
334         const actual = candidate orelse return false;
335         return expected == actual;
336     }
337 
338     fn push(self: *Seat, event: windowing.Event) void {
339         if (self.callbacks) |callbacks| callbacks.event(callbacks.context, event);
340     }
341 
342     fn pointerEntered(context: *anyopaque, event: pointer.EnterEvent) void {
343         const self: *Seat = @ptrCast(@alignCast(context));
344         if (!self.ownsSurface(event.surface)) return;
345         self.cursor_on_screen = true;
346         self.input.setMousePosition(coordinate(event.position.x), coordinate(event.position.y));
347         self.applyCursorShape();
348     }
349 
350     fn pointerLeft(context: *anyopaque, event: pointer.LeaveEvent) void {
351         const self: *Seat = @ptrCast(@alignCast(context));
352         if (!self.ownsSurface(event.surface)) return;
353         self.cursor_on_screen = false;
354     }
355 
356     fn pointerMoved(context: *anyopaque, event: pointer.MotionEvent) void {
357         const self: *Seat = @ptrCast(@alignCast(context));
358         if (!self.ownsSurface(event.surface)) return;
359         const x = coordinate(event.position.x);
360         const y = coordinate(event.position.y);
361         self.input.moveMouse(x, y);
362         self.push(.{ .mouse_move = .{ .x = x, .y = y } });
363     }
364 
365     fn pointerButton(context: *anyopaque, event: pointer.ButtonEvent) void {
366         const self: *Seat = @ptrCast(@alignCast(context));
367         if (!self.ownsSurface(event.surface)) return;
368         self.last_input_serial = event.serial;
369         const x = coordinate(event.position.x);
370         const y = coordinate(event.position.y);
371         switch (event.state) {
372             .pressed => {
373                 self.input.pressMouse(event.button);
374                 self.push(.{ .mouse_press = .{
375                     .button = event.button,
376                     .x = x,
377                     .y = y,
378                     .modifiers = self.modifiers(),
379                 } });
380             },
381             .released => {
382                 self.input.releaseMouse(event.button);
383                 self.push(.{ .mouse_release = .{
384                     .button = event.button,
385                     .x = x,
386                     .y = y,
387                     .modifiers = self.modifiers(),
388                 } });
389             },
390         }
391     }
392 
393     fn pointerAxis(context: *anyopaque, event: pointer.AxisEvent) void {
394         const self: *Seat = @ptrCast(@alignCast(context));
395         if (!self.ownsSurface(event.surface)) return;
396         const delta_x: f32 = @floatCast(event.horizontal.delta);
397         const delta_y: f32 = @floatCast(event.vertical.delta);
398         if (delta_x == 0 and delta_y == 0) return;
399         self.input.addMouseWheel(delta_x, delta_y);
400         self.push(.{ .mouse_wheel = .{
401             .x = coordinate(event.position.x),
402             .y = coordinate(event.position.y),
403             .delta_x = delta_x,
404             .delta_y = delta_y,
405             .modifiers = self.modifiers(),
406         } });
407     }
408 
409     fn keyboardFocus(context: *anyopaque, focused: bool) void {
410         const self: *Seat = @ptrCast(@alignCast(context));
411         if (self.callbacks) |callbacks| callbacks.focus_changed(callbacks.context, focused);
412     }
413 
414     fn keyboardKey(context: *anyopaque, action: keyboard.KeyAction) void {
415         const self: *Seat = @ptrCast(@alignCast(context));
416         if (action.pressed) {
417             self.input.pressKey(action.key);
418             if (action.emit_event) self.push(.{ .key_press = .{
419                 .key = action.key,
420                 .modifiers = action.modifiers,
421                 .repeated = action.repeated,
422             } });
423         } else {
424             self.input.releaseKey(action.key);
425             if (action.emit_event) self.push(.{ .key_release = .{
426                 .key = action.key,
427                 .modifiers = action.modifiers,
428             } });
429         }
430     }
431 
432     fn keyboardText(context: *anyopaque, bytes: []const u8, active_modifiers: windowing.Modifier) void {
433         const self: *Seat = @ptrCast(@alignCast(context));
434         if (!self.input.pushTextInput(bytes)) return;
435         if (windowing.Event.textInput(bytes, active_modifiers)) |event| self.push(event);
436     }
437 
438     fn synchronizeClipboard(context: ?*anyopaque) anyerror!void {
439         const self: *Seat = @ptrCast(@alignCast(context orelse return error.ConnectionLost));
440         const callbacks = self.callbacks orelse return error.ConnectionLost;
441         try callbacks.synchronize(callbacks.context, clipboard.receive_timeout_ms);
442     }
443 
444     fn releaseMouseButtons(self: *Seat) void {
445         inline for (
446             @typeInfo(windowing.MouseButton).@"enum".field_names,
447             @typeInfo(windowing.MouseButton).@"enum".field_values,
448         ) |field_name, field_name_value| {
449             const field = .{ .name = field_name, .value = field_name_value };
450             const button: windowing.MouseButton = @fromBackingInt(@intCast(field.value));
451             if (self.input.isMouseButtonDown(button)) self.input.releaseMouse(button);
452         }
453     }
454 };
455 
456 const coordinate = support.coordinate;
457 const cursorShapeValue = support.cursorShapeValue;