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;