river: Implement input_method and text_input
This commit is contained in:
		| @ -28,10 +28,12 @@ const util = @import("util.zig"); | ||||
|  | ||||
| const InputConfig = @import("InputConfig.zig"); | ||||
| const InputDevice = @import("InputDevice.zig"); | ||||
| const InputRelay = @import("InputRelay.zig"); | ||||
| const Keyboard = @import("Keyboard.zig"); | ||||
| const PointerConstraint = @import("PointerConstraint.zig"); | ||||
| const Seat = @import("Seat.zig"); | ||||
| const Switch = @import("Switch.zig"); | ||||
| const TextInput = @import("TextInput.zig"); | ||||
|  | ||||
| const default_seat_name = "default"; | ||||
|  | ||||
| @ -44,6 +46,8 @@ relative_pointer_manager: *wlr.RelativePointerManagerV1, | ||||
| virtual_pointer_manager: *wlr.VirtualPointerManagerV1, | ||||
| virtual_keyboard_manager: *wlr.VirtualKeyboardManagerV1, | ||||
| pointer_constraints: *wlr.PointerConstraintsV1, | ||||
| input_method_manager: *wlr.InputMethodManagerV2, | ||||
| text_input_manager: *wlr.TextInputManagerV3, | ||||
|  | ||||
| configs: std.ArrayList(InputConfig), | ||||
| devices: wl.list.Head(InputDevice, .link), | ||||
| @ -57,6 +61,10 @@ new_virtual_keyboard: wl.Listener(*wlr.VirtualKeyboardV1) = | ||||
|     wl.Listener(*wlr.VirtualKeyboardV1).init(handleNewVirtualKeyboard), | ||||
| new_constraint: wl.Listener(*wlr.PointerConstraintV1) = | ||||
|     wl.Listener(*wlr.PointerConstraintV1).init(handleNewConstraint), | ||||
| new_input_method: wl.Listener(*wlr.InputMethodV2) = | ||||
|     wl.Listener(*wlr.InputMethodV2).init(handleNewInputMethod), | ||||
| new_text_input: wl.Listener(*wlr.TextInputV3) = | ||||
|     wl.Listener(*wlr.TextInputV3).init(handleNewTextInput), | ||||
|  | ||||
| pub fn init(self: *Self) !void { | ||||
|     const seat_node = try util.gpa.create(std.TailQueue(Seat).Node); | ||||
| @ -69,6 +77,8 @@ pub fn init(self: *Self) !void { | ||||
|         .virtual_pointer_manager = try wlr.VirtualPointerManagerV1.create(server.wl_server), | ||||
|         .virtual_keyboard_manager = try wlr.VirtualKeyboardManagerV1.create(server.wl_server), | ||||
|         .pointer_constraints = try wlr.PointerConstraintsV1.create(server.wl_server), | ||||
|         .input_method_manager = try wlr.InputMethodManagerV2.create(server.wl_server), | ||||
|         .text_input_manager = try wlr.TextInputManagerV3.create(server.wl_server), | ||||
|         .configs = std.ArrayList(InputConfig).init(util.gpa), | ||||
|  | ||||
|         .devices = undefined, | ||||
| @ -84,6 +94,8 @@ pub fn init(self: *Self) !void { | ||||
|     self.virtual_pointer_manager.events.new_virtual_pointer.add(&self.new_virtual_pointer); | ||||
|     self.virtual_keyboard_manager.events.new_virtual_keyboard.add(&self.new_virtual_keyboard); | ||||
|     self.pointer_constraints.events.new_constraint.add(&self.new_constraint); | ||||
|     self.input_method_manager.events.input_method.add(&self.new_input_method); | ||||
|     self.text_input_manager.events.text_input.add(&self.new_text_input); | ||||
| } | ||||
|  | ||||
| pub fn deinit(self: *Self) void { | ||||
| @ -93,6 +105,8 @@ pub fn deinit(self: *Self) void { | ||||
|     self.new_virtual_pointer.link.remove(); | ||||
|     self.new_virtual_keyboard.link.remove(); | ||||
|     self.new_constraint.link.remove(); | ||||
|     self.new_input_method.link.remove(); | ||||
|     self.new_text_input.link.remove(); | ||||
|  | ||||
|     while (self.seats.pop()) |seat_node| { | ||||
|         seat_node.data.deinit(); | ||||
| @ -158,3 +172,51 @@ fn handleNewConstraint( | ||||
|         wlr_constraint.resource.postNoMemory(); | ||||
|     }; | ||||
| } | ||||
|  | ||||
| fn handleNewInputMethod( | ||||
|     _: *wl.Listener(*wlr.InputMethodV2), | ||||
|     input_method: *wlr.InputMethodV2, | ||||
| ) void { | ||||
|     //const self = @fieldParentPtr(Self, "new_input_method", listener); | ||||
|     const seat = @as(*Seat, @ptrFromInt(input_method.seat.data)); | ||||
|     const relay = &seat.relay; | ||||
|  | ||||
|     // Only one input_method can be bound to a seat. | ||||
|     if (relay.input_method != null) { | ||||
|         log.debug("attempted to connect second input method to a seat", .{}); | ||||
|         input_method.sendUnavailable(); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     relay.input_method = input_method; | ||||
|  | ||||
|     input_method.events.commit.add(&relay.input_method_commit); | ||||
|     input_method.events.grab_keyboard.add(&relay.grab_keyboard); | ||||
|     input_method.events.destroy.add(&relay.input_method_destroy); | ||||
|  | ||||
|     log.debug("new input method on seat {s}", .{relay.seat.wlr_seat.name}); | ||||
|  | ||||
|     const text_input = relay.getFocusableTextInput() orelse return; | ||||
|     if (text_input.pending_focused_surface) |surface| { | ||||
|         text_input.wlr_text_input.sendEnter(surface); | ||||
|         text_input.setPendingFocusedSurface(null); | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn handleNewTextInput( | ||||
|     _: *wl.Listener(*wlr.TextInputV3), | ||||
|     wlr_text_input: *wlr.TextInputV3, | ||||
| ) void { | ||||
|     //const self = @fieldParentPtr(Self, "new_text_input", listener); | ||||
|     const seat = @as(*Seat, @ptrFromInt(wlr_text_input.seat.data)); | ||||
|     const relay = &seat.relay; | ||||
|  | ||||
|     const text_input_node = util.gpa.create(std.TailQueue(TextInput).Node) catch { | ||||
|         wlr_text_input.resource.postNoMemory(); | ||||
|         return; | ||||
|     }; | ||||
|     text_input_node.data.init(relay, wlr_text_input); | ||||
|     relay.text_inputs.append(text_input_node); | ||||
|  | ||||
|     log.debug("new text input on seat {s}", .{relay.seat.wlr_seat.name}); | ||||
| } | ||||
|  | ||||
							
								
								
									
										218
									
								
								river/InputRelay.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										218
									
								
								river/InputRelay.zig
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,218 @@ | ||||
| // This file is part of river, a dynamic tiling wayland compositor. | ||||
| // | ||||
| // Copyright 2021 The River Developers | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
|  | ||||
| const Self = @This(); | ||||
|  | ||||
| const std = @import("std"); | ||||
| const assert = std.debug.assert; | ||||
| const mem = std.mem; | ||||
| const wlr = @import("wlroots"); | ||||
| const wl = @import("wayland").server.wl; | ||||
|  | ||||
| const util = @import("util.zig"); | ||||
|  | ||||
| const TextInput = @import("TextInput.zig"); | ||||
| const Seat = @import("Seat.zig"); | ||||
|  | ||||
| const log = std.log.scoped(.input_relay); | ||||
|  | ||||
| /// The Relay structure manages the communication between text_inputs | ||||
| /// and input_method on a given seat. | ||||
| seat: *Seat, | ||||
|  | ||||
| /// List of all TextInput bound to the relay. | ||||
| /// Multiple wlr_text_input interfaces can be bound to a relay, | ||||
| /// but only one at a time can receive events. | ||||
| text_inputs: std.TailQueue(TextInput) = .{}, | ||||
|  | ||||
| input_method: ?*wlr.InputMethodV2 = null, | ||||
|  | ||||
| input_method_commit: wl.Listener(*wlr.InputMethodV2) = | ||||
|     wl.Listener(*wlr.InputMethodV2).init(handleInputMethodCommit), | ||||
| grab_keyboard: wl.Listener(*wlr.InputMethodV2.KeyboardGrab) = | ||||
|     wl.Listener(*wlr.InputMethodV2.KeyboardGrab).init(handleInputMethodGrabKeyboard), | ||||
| input_method_destroy: wl.Listener(*wlr.InputMethodV2) = | ||||
|     wl.Listener(*wlr.InputMethodV2).init(handleInputMethodDestroy), | ||||
|  | ||||
| grab_keyboard_destroy: wl.Listener(*wlr.InputMethodV2.KeyboardGrab) = | ||||
|     wl.Listener(*wlr.InputMethodV2.KeyboardGrab).init(handleInputMethodGrabKeyboardDestroy), | ||||
|  | ||||
| pub fn init(self: *Self, seat: *Seat) void { | ||||
|     self.* = .{ .seat = seat }; | ||||
| } | ||||
|  | ||||
| fn handleInputMethodCommit( | ||||
|     listener: *wl.Listener(*wlr.InputMethodV2), | ||||
|     input_method: *wlr.InputMethodV2, | ||||
| ) void { | ||||
|     const self = @fieldParentPtr(Self, "input_method_commit", listener); | ||||
|     const text_input = self.getFocusedTextInput() orelse return; | ||||
|  | ||||
|     assert(input_method == self.input_method); | ||||
|  | ||||
|     if (input_method.current.preedit.text) |preedit_text| { | ||||
|         text_input.wlr_text_input.sendPreeditString( | ||||
|             preedit_text, | ||||
|             input_method.current.preedit.cursor_begin, | ||||
|             input_method.current.preedit.cursor_end, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     if (input_method.current.commit_text) |commit_text| { | ||||
|         text_input.wlr_text_input.sendCommitString(commit_text); | ||||
|     } | ||||
|  | ||||
|     if (input_method.current.delete.before_length != 0 or | ||||
|         input_method.current.delete.after_length != 0) | ||||
|     { | ||||
|         text_input.wlr_text_input.sendDeleteSurroundingText( | ||||
|             input_method.current.delete.before_length, | ||||
|             input_method.current.delete.after_length, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     text_input.wlr_text_input.sendDone(); | ||||
| } | ||||
|  | ||||
| fn handleInputMethodDestroy( | ||||
|     listener: *wl.Listener(*wlr.InputMethodV2), | ||||
|     input_method: *wlr.InputMethodV2, | ||||
| ) void { | ||||
|     const self = @fieldParentPtr(Self, "input_method_destroy", listener); | ||||
|  | ||||
|     assert(input_method == self.input_method); | ||||
|     self.input_method = null; | ||||
|  | ||||
|     const text_input = self.getFocusedTextInput() orelse return; | ||||
|     if (text_input.wlr_text_input.focused_surface) |surface| { | ||||
|         text_input.setPendingFocusedSurface(surface); | ||||
|     } | ||||
|     text_input.wlr_text_input.sendLeave(); | ||||
| } | ||||
|  | ||||
| fn handleInputMethodGrabKeyboard( | ||||
|     listener: *wl.Listener(*wlr.InputMethodV2.KeyboardGrab), | ||||
|     keyboard_grab: *wlr.InputMethodV2.KeyboardGrab, | ||||
| ) void { | ||||
|     const self = @fieldParentPtr(Self, "grab_keyboard", listener); | ||||
|  | ||||
|     const active_keyboard = self.seat.wlr_seat.getKeyboard() orelse return; | ||||
|     keyboard_grab.setKeyboard(active_keyboard); | ||||
|     // sway says 'send modifier state to grab' but doesn't seem to do this send_modifiers | ||||
|     keyboard_grab.sendModifiers(&active_keyboard.modifiers); | ||||
|  | ||||
|     keyboard_grab.events.destroy.add(&self.grab_keyboard_destroy); | ||||
| } | ||||
|  | ||||
| fn handleInputMethodGrabKeyboardDestroy( | ||||
|     listener: *wl.Listener(*wlr.InputMethodV2.KeyboardGrab), | ||||
|     keyboard_grab: *wlr.InputMethodV2.KeyboardGrab, | ||||
| ) void { | ||||
|     const self = @fieldParentPtr(Self, "grab_keyboard_destroy", listener); | ||||
|     self.grab_keyboard_destroy.link.remove(); | ||||
|  | ||||
|     if (keyboard_grab.keyboard) |keyboard| { | ||||
|         keyboard_grab.input_method.seat.keyboardNotifyModifiers(&keyboard.modifiers); | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub fn getFocusableTextInput(self: *Self) ?*TextInput { | ||||
|     var it = self.text_inputs.first; | ||||
|     return while (it) |node| : (it = node.next) { | ||||
|         const text_input = &node.data; | ||||
|         if (text_input.pending_focused_surface != null) break text_input; | ||||
|     } else null; | ||||
| } | ||||
|  | ||||
| pub fn getFocusedTextInput(self: *Self) ?*TextInput { | ||||
|     var it = self.text_inputs.first; | ||||
|     return while (it) |node| : (it = node.next) { | ||||
|         const text_input = &node.data; | ||||
|         if (text_input.wlr_text_input.focused_surface != null) break text_input; | ||||
|     } else null; | ||||
| } | ||||
|  | ||||
| pub fn disableTextInput(self: *Self, text_input: *TextInput) void { | ||||
|     if (self.input_method) |im| { | ||||
|         im.sendDeactivate(); | ||||
|     } else { | ||||
|         log.debug("disabling text input but input method is gone", .{}); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     self.sendInputMethodState(text_input.wlr_text_input); | ||||
| } | ||||
|  | ||||
| pub fn sendInputMethodState(self: *Self, wlr_text_input: *wlr.TextInputV3) void { | ||||
|     const input_method = self.input_method orelse return; | ||||
|  | ||||
|     // TODO: only send each of those if they were modified | ||||
|  | ||||
|     if (wlr_text_input.active_features.surrounding_text) { | ||||
|         if (wlr_text_input.current.surrounding.text) |text| { | ||||
|             input_method.sendSurroundingText( | ||||
|                 text, | ||||
|                 wlr_text_input.current.surrounding.cursor, | ||||
|                 wlr_text_input.current.surrounding.anchor, | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     input_method.sendTextChangeCause(wlr_text_input.current.text_change_cause); | ||||
|  | ||||
|     if (wlr_text_input.active_features.content_type) { | ||||
|         input_method.sendContentType( | ||||
|             wlr_text_input.current.content_type.hint, | ||||
|             wlr_text_input.current.content_type.purpose, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     input_method.sendDone(); | ||||
|     // TODO: pass intent, display popup size | ||||
| } | ||||
|  | ||||
| /// Update the current focused surface. Surface must belong to the same seat. | ||||
| pub fn setSurfaceFocus(self: *Self, wlr_surface: ?*wlr.Surface) void { | ||||
|     var it = self.text_inputs.first; | ||||
|     while (it) |node| : (it = node.next) { | ||||
|         const text_input = &node.data; | ||||
|         if (text_input.pending_focused_surface) |surface| { | ||||
|             assert(text_input.wlr_text_input.focused_surface == null); | ||||
|             if (wlr_surface != surface) { | ||||
|                 text_input.setPendingFocusedSurface(null); | ||||
|             } | ||||
|         } else if (text_input.wlr_text_input.focused_surface) |surface| { | ||||
|             assert(text_input.pending_focused_surface == null); | ||||
|             if (wlr_surface != surface) { | ||||
|                 text_input.relay.disableTextInput(text_input); | ||||
|                 text_input.wlr_text_input.sendLeave(); | ||||
|             } else { | ||||
|                 log.debug("IM relay setSurfaceFocus already focused", .{}); | ||||
|                 continue; | ||||
|             } | ||||
|         } | ||||
|         if (wlr_surface) |surface| { | ||||
|             if (text_input.wlr_text_input.resource.getClient() == surface.resource.getClient()) { | ||||
|                 if (self.input_method != null) { | ||||
|                     text_input.wlr_text_input.sendEnter(surface); | ||||
|                 } else { | ||||
|                     text_input.setPendingFocusedSurface(surface); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -116,7 +116,21 @@ fn handleKey(listener: *wl.Listener(*wlr.Keyboard.event.Key), event: *wlr.Keyboa | ||||
|         assert(handled); | ||||
|     } | ||||
|  | ||||
|     const eaten = if (released) self.eaten_keycodes.remove(event.keycode) else mapped; | ||||
|     // Handle IM grab | ||||
|     const keyboard_grab = self.getInputMethodGrab(); | ||||
|     const grabbed = !mapped and (keyboard_grab != null); | ||||
|     if (grabbed) ungrab: { | ||||
|         if (!released) { | ||||
|             self.eaten_keycodes.add(event.keycode); | ||||
|         } else if (!self.eaten_keycodes.present(event.keycode)) { | ||||
|             break :ungrab; | ||||
|         } | ||||
|  | ||||
|         keyboard_grab.?.setKeyboard(keyboard_grab.?.keyboard); | ||||
|         keyboard_grab.?.sendKey(event.time_msec, event.keycode, event.state); | ||||
|     } | ||||
|  | ||||
|     const eaten = if (released) self.eaten_keycodes.remove(event.keycode) else (mapped or grabbed); | ||||
|  | ||||
|     if (!eaten) { | ||||
|         // If key was not handled, we pass it along to the client. | ||||
| @ -137,8 +151,13 @@ fn handleModifiers(listener: *wl.Listener(*wlr.Keyboard), _: *wlr.Keyboard) void | ||||
|     // If the keyboard is in a group, this event will be handled by the group's Keyboard instance. | ||||
|     if (wlr_keyboard.group != null) return; | ||||
|  | ||||
|     self.device.seat.wlr_seat.setKeyboard(self.device.wlr_device.toKeyboard()); | ||||
|     self.device.seat.wlr_seat.keyboardNotifyModifiers(&wlr_keyboard.modifiers); | ||||
|     if (self.getInputMethodGrab()) |keyboard_grab| { | ||||
|         keyboard_grab.setKeyboard(keyboard_grab.keyboard); | ||||
|         keyboard_grab.sendModifiers(&wlr_keyboard.modifiers); | ||||
|     } else { | ||||
|         self.device.seat.wlr_seat.setKeyboard(self.device.wlr_device.toKeyboard()); | ||||
|         self.device.seat.wlr_seat.keyboardNotifyModifiers(&wlr_keyboard.modifiers); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Handle any builtin, harcoded compsitor mappings such as VT switching. | ||||
| @ -158,3 +177,17 @@ fn handleBuiltinMapping(keysym: xkb.Keysym) bool { | ||||
|         else => return false, | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Returns null if the keyboard is not grabbed by an input method, | ||||
| /// or if event is from virtual keyboard of the same client as grab. | ||||
| /// TODO: see https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/2322 | ||||
| fn getInputMethodGrab(self: Self) ?*wlr.InputMethodV2.KeyboardGrab { | ||||
|     const input_method = self.device.seat.relay.input_method; | ||||
|     const virtual_keyboard = self.device.wlr_device.getVirtualKeyboard(); | ||||
|     if (input_method == null or input_method.?.keyboard_grab == null or | ||||
|         (virtual_keyboard != null and | ||||
|         virtual_keyboard.?.resource.getClient() == input_method.?.keyboard_grab.?.resource.getClient())) | ||||
|     { | ||||
|         return null; | ||||
|     } else return input_method.?.keyboard_grab; | ||||
| } | ||||
|  | ||||
| @ -40,6 +40,11 @@ pub fn add(self: *Self, new: u32) void { | ||||
|     self.len += 1; | ||||
| } | ||||
|  | ||||
| pub fn present(self: *Self, value: u32) bool { | ||||
|     for (self.items[0..self.len]) |item| if (value == item) return true; | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| pub fn remove(self: *Self, old: u32) bool { | ||||
|     for (self.items[0..self.len], 0..) |item, idx| if (old == item) { | ||||
|         self.len -= 1; | ||||
|  | ||||
| @ -31,6 +31,7 @@ const Cursor = @import("Cursor.zig"); | ||||
| const DragIcon = @import("DragIcon.zig"); | ||||
| const InputDevice = @import("InputDevice.zig"); | ||||
| const InputManager = @import("InputManager.zig"); | ||||
| const InputRelay = @import("InputRelay.zig"); | ||||
| const Keyboard = @import("Keyboard.zig"); | ||||
| const KeyboardGroup = @import("KeyboardGroup.zig"); | ||||
| const KeycodeSet = @import("KeycodeSet.zig"); | ||||
| @ -88,6 +89,9 @@ drag: enum { | ||||
|     touch, | ||||
| } = .none, | ||||
|  | ||||
| /// Relay for communication between text_input and input_method. | ||||
| relay: InputRelay = undefined, | ||||
|  | ||||
| request_set_selection: wl.Listener(*wlr.Seat.event.RequestSetSelection) = | ||||
|     wl.Listener(*wlr.Seat.event.RequestSetSelection).init(handleRequestSetSelection), | ||||
| request_start_drag: wl.Listener(*wlr.Seat.event.RequestStartDrag) = | ||||
| @ -110,6 +114,7 @@ pub fn init(self: *Self, name: [*:0]const u8) !void { | ||||
|     self.wlr_seat.data = @intFromPtr(self); | ||||
|  | ||||
|     try self.cursor.init(self); | ||||
|     self.relay.init(self); | ||||
|  | ||||
|     self.wlr_seat.events.request_set_selection.add(&self.request_set_selection); | ||||
|     self.wlr_seat.events.request_start_drag.add(&self.request_start_drag); | ||||
| @ -260,6 +265,7 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void { | ||||
|     } | ||||
|  | ||||
|     self.keyboardEnterOrLeave(target_surface); | ||||
|     self.relay.setSurfaceFocus(target_surface); | ||||
|  | ||||
|     if (target_surface) |surface| { | ||||
|         const pointer_constraints = server.input_manager.pointer_constraints; | ||||
|  | ||||
							
								
								
									
										129
									
								
								river/TextInput.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								river/TextInput.zig
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,129 @@ | ||||
| // This file is part of river, a dynamic tiling wayland compositor. | ||||
| // | ||||
| // Copyright 2021 The River Developers | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
|  | ||||
| const Self = @This(); | ||||
|  | ||||
| const std = @import("std"); | ||||
| const assert = std.debug.assert; | ||||
| const wlr = @import("wlroots"); | ||||
| const wl = @import("wayland").server.wl; | ||||
|  | ||||
| const util = @import("util.zig"); | ||||
|  | ||||
| const InputRelay = @import("InputRelay.zig"); | ||||
| const Seat = @import("Seat.zig"); | ||||
|  | ||||
| const log = std.log.scoped(.text_input); | ||||
|  | ||||
| relay: *InputRelay, | ||||
| wlr_text_input: *wlr.TextInputV3, | ||||
|  | ||||
| /// Surface stored for when text-input can't receive an enter event immediately | ||||
| /// after getting focus. Cleared once text-input receive the enter event. | ||||
| pending_focused_surface: ?*wlr.Surface = null, | ||||
|  | ||||
| enable: wl.Listener(*wlr.TextInputV3) = | ||||
|     wl.Listener(*wlr.TextInputV3).init(handleEnable), | ||||
| commit: wl.Listener(*wlr.TextInputV3) = | ||||
|     wl.Listener(*wlr.TextInputV3).init(handleCommit), | ||||
| disable: wl.Listener(*wlr.TextInputV3) = | ||||
|     wl.Listener(*wlr.TextInputV3).init(handleDisable), | ||||
| destroy: wl.Listener(*wlr.TextInputV3) = | ||||
|     wl.Listener(*wlr.TextInputV3).init(handleDestroy), | ||||
|  | ||||
| pending_focused_surface_destroy: wl.Listener(*wlr.Surface) = | ||||
|     wl.Listener(*wlr.Surface).init(handlePendingFocusedSurfaceDestroy), | ||||
|  | ||||
| pub fn init(self: *Self, relay: *InputRelay, wlr_text_input: *wlr.TextInputV3) void { | ||||
|     self.* = .{ | ||||
|         .relay = relay, | ||||
|         .wlr_text_input = wlr_text_input, | ||||
|     }; | ||||
|  | ||||
|     wlr_text_input.events.enable.add(&self.enable); | ||||
|     wlr_text_input.events.commit.add(&self.commit); | ||||
|     wlr_text_input.events.disable.add(&self.disable); | ||||
|     wlr_text_input.events.destroy.add(&self.destroy); | ||||
| } | ||||
|  | ||||
| fn handleEnable(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) void { | ||||
|     const self = @fieldParentPtr(Self, "enable", listener); | ||||
|  | ||||
|     if (self.relay.input_method) |im| { | ||||
|         im.sendActivate(); | ||||
|     } else { | ||||
|         log.debug("enabling text input but input method is gone", .{}); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     self.relay.sendInputMethodState(self.wlr_text_input); | ||||
| } | ||||
|  | ||||
| fn handleCommit(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) void { | ||||
|     const self = @fieldParentPtr(Self, "commit", listener); | ||||
|     if (!self.wlr_text_input.current_enabled) { | ||||
|         log.debug("inactive text input tried to commit an update", .{}); | ||||
|         return; | ||||
|     } | ||||
|     log.debug("text input committed update", .{}); | ||||
|     if (self.relay.input_method == null) { | ||||
|         log.debug("committed text input but input method is gone", .{}); | ||||
|         return; | ||||
|     } | ||||
|     self.relay.sendInputMethodState(self.wlr_text_input); | ||||
| } | ||||
|  | ||||
| fn handleDisable(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) void { | ||||
|     const self = @fieldParentPtr(Self, "disable", listener); | ||||
|     if (self.wlr_text_input.focused_surface == null) { | ||||
|         log.debug("disabling text input, but no longer focused", .{}); | ||||
|         return; | ||||
|     } | ||||
|     self.relay.disableTextInput(self); | ||||
| } | ||||
|  | ||||
| fn handleDestroy(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) void { | ||||
|     const self = @fieldParentPtr(Self, "destroy", listener); | ||||
|     const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); | ||||
|  | ||||
|     if (self.wlr_text_input.current_enabled) self.relay.disableTextInput(self); | ||||
|  | ||||
|     node.data.setPendingFocusedSurface(null); | ||||
|  | ||||
|     self.enable.link.remove(); | ||||
|     self.commit.link.remove(); | ||||
|     self.disable.link.remove(); | ||||
|     self.destroy.link.remove(); | ||||
|  | ||||
|     self.relay.text_inputs.remove(node); | ||||
|     util.gpa.destroy(node); | ||||
| } | ||||
|  | ||||
| fn handlePendingFocusedSurfaceDestroy(listener: *wl.Listener(*wlr.Surface), surface: *wlr.Surface) void { | ||||
|     const self = @fieldParentPtr(Self, "pending_focused_surface_destroy", listener); | ||||
|     assert(self.pending_focused_surface == surface); | ||||
|     self.pending_focused_surface = null; | ||||
|     self.pending_focused_surface_destroy.link.remove(); | ||||
| } | ||||
|  | ||||
| pub fn setPendingFocusedSurface(self: *Self, wlr_surface: ?*wlr.Surface) void { | ||||
|     if (self.pending_focused_surface != null) self.pending_focused_surface_destroy.link.remove(); | ||||
|     self.pending_focused_surface = wlr_surface; | ||||
|     if (self.pending_focused_surface) |surface| { | ||||
|         surface.events.destroy.add(&self.pending_focused_surface_destroy); | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user