river: Implement input_method and text_input
This commit is contained in:
parent
3aba3abbcd
commit
2abab1e9c7
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user