diff --git a/river/InputManager.zig b/river/InputManager.zig
index 1e4df90..4ce937a 100644
--- a/river/InputManager.zig
+++ b/river/InputManager.zig
@@ -173,48 +173,18 @@ fn handleNewConstraint(
};
}
-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;
+fn handleNewInputMethod(_: *wl.Listener(*wlr.InputMethodV2), input_method: *wlr.InputMethodV2) void {
+ const seat: *Seat = @ptrFromInt(input_method.seat.data);
- // 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;
- }
+ log.debug("new input method on seat {s}", .{seat.wlr_seat.name});
- 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});
-
- if (seat.focused.surface()) |surface| {
- relay.focus(surface);
- }
+ seat.relay.newInputMethod(input_method);
}
-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 {
+fn handleNewTextInput(_: *wl.Listener(*wlr.TextInputV3), wlr_text_input: *wlr.TextInputV3) void {
+ TextInput.create(wlr_text_input) catch {
+ log.err("out of memory", .{});
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});
}
diff --git a/river/InputRelay.zig b/river/InputRelay.zig
index 99d4f34..d70d447 100644
--- a/river/InputRelay.zig
+++ b/river/InputRelay.zig
@@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
-const Self = @This();
+const InputRelay = @This();
const std = @import("std");
const assert = std.debug.assert;
@@ -30,17 +30,18 @@ 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) = .{},
+/// List of all text input objects for the seat.
+/// Multiple text input objects may be created per seat, even multiple from the same client.
+/// However, only one text input per seat may be enabled at a time.
+text_inputs: wl.list.Head(TextInput, .link),
+/// The input method currently in use for this seat.
+/// Only one input method per seat may be used at a time and if one is
+/// already in use new input methods are ignored.
+/// If this is null, no text input enter events will be sent.
input_method: ?*wlr.InputMethodV2 = null,
/// The currently enabled text input for the currently focused surface.
+/// Always null if there is no input method.
text_input: ?*TextInput = null,
input_method_commit: wl.Listener(*wlr.InputMethodV2) =
@@ -53,19 +54,44 @@ input_method_destroy: wl.Listener(*wlr.InputMethodV2) =
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 };
+pub fn init(relay: *InputRelay) void {
+ relay.* = .{ .text_inputs = undefined };
+
+ relay.text_inputs.init();
+}
+
+pub fn newInputMethod(relay: *InputRelay, input_method: *wlr.InputMethodV2) void {
+ const seat = @fieldParentPtr(Seat, "relay", relay);
+
+ log.debug("new input method on seat {s}", .{seat.wlr_seat.name});
+
+ // Only one input_method can be bound to a seat.
+ if (relay.input_method != null) {
+ log.info("seat {s} already has an input method", .{seat.wlr_seat.name});
+ 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);
+
+ if (seat.focused.surface()) |surface| {
+ relay.focus(surface);
+ }
}
fn handleInputMethodCommit(
listener: *wl.Listener(*wlr.InputMethodV2),
input_method: *wlr.InputMethodV2,
) void {
- const self = @fieldParentPtr(Self, "input_method_commit", listener);
- assert(input_method == self.input_method);
+ const relay = @fieldParentPtr(InputRelay, "input_method_commit", listener);
+ assert(input_method == relay.input_method);
if (!input_method.client_active) return;
- const text_input = self.text_input orelse return;
+ const text_input = relay.text_input orelse return;
if (input_method.current.preedit.text) |preedit_text| {
text_input.wlr_text_input.sendPreeditString(
@@ -95,56 +121,59 @@ 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);
+ const relay = @fieldParentPtr(InputRelay, "input_method_destroy", listener);
+ assert(input_method == relay.input_method);
- self.input_method_commit.link.remove();
- self.grab_keyboard.link.remove();
- self.input_method_destroy.link.remove();
+ relay.input_method_commit.link.remove();
+ relay.grab_keyboard.link.remove();
+ relay.input_method_destroy.link.remove();
- self.input_method = null;
+ relay.input_method = null;
- self.focus(null);
+ relay.focus(null);
+
+ assert(relay.text_input == null);
}
fn handleInputMethodGrabKeyboard(
listener: *wl.Listener(*wlr.InputMethodV2.KeyboardGrab),
keyboard_grab: *wlr.InputMethodV2.KeyboardGrab,
) void {
- const self = @fieldParentPtr(Self, "grab_keyboard", listener);
+ const relay = @fieldParentPtr(InputRelay, "grab_keyboard", listener);
+ const seat = @fieldParentPtr(Seat, "relay", relay);
- const active_keyboard = self.seat.wlr_seat.getKeyboard();
+ const active_keyboard = seat.wlr_seat.getKeyboard();
keyboard_grab.setKeyboard(active_keyboard);
- keyboard_grab.events.destroy.add(&self.grab_keyboard_destroy);
+ keyboard_grab.events.destroy.add(&relay.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();
+ const relay = @fieldParentPtr(InputRelay, "grab_keyboard_destroy", listener);
+ relay.grab_keyboard_destroy.link.remove();
if (keyboard_grab.keyboard) |keyboard| {
keyboard_grab.input_method.seat.keyboardNotifyModifiers(&keyboard.modifiers);
}
}
-pub fn disableTextInput(self: *Self) void {
- assert(self.text_input != null);
+pub fn disableTextInput(relay: *InputRelay) void {
+ assert(relay.text_input != null);
- if (self.input_method) |input_method| {
+ if (relay.input_method) |input_method| {
input_method.sendDeactivate();
input_method.sendDone();
}
- self.text_input = null;
+ relay.text_input = null;
}
-pub fn sendInputMethodState(self: *Self) void {
- const input_method = self.input_method.?;
- const wlr_text_input = self.text_input.?.wlr_text_input;
+pub fn sendInputMethodState(relay: *InputRelay) void {
+ const input_method = relay.input_method.?;
+ const wlr_text_input = relay.text_input.?.wlr_text_input;
// TODO Send these events only if something changed.
// On activation all events must be sent for all active features.
@@ -171,13 +200,11 @@ pub fn sendInputMethodState(self: *Self) void {
input_method.sendDone();
}
-pub fn focus(self: *Self, new_focus: ?*wlr.Surface) void {
+pub fn focus(relay: *InputRelay, new_focus: ?*wlr.Surface) void {
// Send leave events
{
- var it = self.text_inputs.first;
- while (it) |node| : (it = node.next) {
- const text_input = &node.data;
-
+ var it = relay.text_inputs.iterator(.forward);
+ while (it.next()) |text_input| {
if (text_input.wlr_text_input.focused_surface) |surface| {
// This function should not be called unless focus changes
assert(surface != new_focus);
@@ -187,19 +214,17 @@ pub fn focus(self: *Self, new_focus: ?*wlr.Surface) void {
}
// Clear currently enabled text input
- if (self.text_input != null) {
- self.disableTextInput();
+ if (relay.text_input != null) {
+ relay.disableTextInput();
}
// Send enter events if we have an input method.
// No text input for the new surface should be enabled yet as the client
// should wait until it receives an enter event.
if (new_focus) |surface| {
- if (self.input_method != null) {
- var it = self.text_inputs.first;
- while (it) |node| : (it = node.next) {
- const text_input = &node.data;
-
+ if (relay.input_method != null) {
+ var it = relay.text_inputs.iterator(.forward);
+ while (it.next()) |text_input| {
if (text_input.wlr_text_input.resource.getClient() == surface.resource.getClient()) {
text_input.wlr_text_input.sendEnter(surface);
}
diff --git a/river/Seat.zig b/river/Seat.zig
index 6058dd1..dc28b2f 100644
--- a/river/Seat.zig
+++ b/river/Seat.zig
@@ -68,7 +68,9 @@ pub const FocusTarget = union(enum) {
wlr_seat: *wlr.Seat,
/// Multiple mice are handled by the same Cursor
-cursor: Cursor = undefined,
+cursor: Cursor,
+/// Input Method handling
+relay: InputRelay,
/// ID of the current keymap mode
mode_id: u32 = 0,
@@ -99,9 +101,6 @@ 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) =
@@ -119,12 +118,14 @@ pub fn init(self: *Self, name: [*:0]const u8) !void {
self.* = .{
// This will be automatically destroyed when the display is destroyed
.wlr_seat = try wlr.Seat.create(server.wl_server, name),
+ .cursor = undefined,
+ .relay = undefined,
.mapping_repeat_timer = mapping_repeat_timer,
};
self.wlr_seat.data = @intFromPtr(self);
try self.cursor.init(self);
- self.relay.init(self);
+ self.relay.init();
self.wlr_seat.events.request_set_selection.add(&self.request_set_selection);
self.wlr_seat.events.request_start_drag.add(&self.request_start_drag);
diff --git a/river/TextInput.zig b/river/TextInput.zig
index 3375500..56661b2 100644
--- a/river/TextInput.zig
+++ b/river/TextInput.zig
@@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
-const Self = @This();
+const TextInput = @This();
const std = @import("std");
const assert = std.debug.assert;
@@ -29,7 +29,8 @@ const Seat = @import("Seat.zig");
const log = std.log.scoped(.text_input);
-relay: *InputRelay,
+link: wl.list.Link,
+
wlr_text_input: *wlr.TextInputV3,
enable: wl.Listener(*wlr.TextInputV3) =
@@ -41,68 +42,79 @@ disable: wl.Listener(*wlr.TextInputV3) =
destroy: wl.Listener(*wlr.TextInputV3) =
wl.Listener(*wlr.TextInputV3).init(handleDestroy),
-pub fn init(self: *Self, relay: *InputRelay, wlr_text_input: *wlr.TextInputV3) void {
- self.* = .{
- .relay = relay,
+pub fn create(wlr_text_input: *wlr.TextInputV3) !void {
+ const seat: *Seat = @ptrFromInt(wlr_text_input.seat.data);
+
+ const text_input = try util.gpa.create(TextInput);
+
+ log.debug("new text input on seat {s}", .{seat.wlr_seat.name});
+
+ text_input.* = .{
+ .link = undefined,
.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);
+ seat.relay.text_inputs.append(text_input);
+
+ wlr_text_input.events.enable.add(&text_input.enable);
+ wlr_text_input.events.commit.add(&text_input.commit);
+ wlr_text_input.events.disable.add(&text_input.disable);
+ wlr_text_input.events.destroy.add(&text_input.destroy);
}
fn handleEnable(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) void {
- const self = @fieldParentPtr(Self, "enable", listener);
+ const text_input = @fieldParentPtr(TextInput, "enable", listener);
+ const seat: *Seat = @ptrFromInt(text_input.wlr_text_input.seat.data);
- if (self.relay.text_input != null) {
+ if (seat.relay.text_input != null) {
log.err("client requested to enable more than one text input on a single seat, ignoring request", .{});
return;
}
- self.relay.text_input = self;
+ seat.relay.text_input = text_input;
- if (self.relay.input_method) |input_method| {
+ if (seat.relay.input_method) |input_method| {
input_method.sendActivate();
- self.relay.sendInputMethodState();
+ seat.relay.sendInputMethodState();
}
}
fn handleCommit(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) void {
- const self = @fieldParentPtr(Self, "commit", listener);
+ const text_input = @fieldParentPtr(TextInput, "commit", listener);
+ const seat: *Seat = @ptrFromInt(text_input.wlr_text_input.seat.data);
- if (self.relay.text_input != self) {
+ if (seat.relay.text_input != text_input) {
log.err("inactive text input tried to commit an update, client bug?", .{});
return;
}
- if (self.relay.input_method != null) {
- self.relay.sendInputMethodState();
+ if (seat.relay.input_method != null) {
+ seat.relay.sendInputMethodState();
}
}
fn handleDisable(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) void {
- const self = @fieldParentPtr(Self, "disable", listener);
+ const text_input = @fieldParentPtr(TextInput, "disable", listener);
+ const seat: *Seat = @ptrFromInt(text_input.wlr_text_input.seat.data);
- if (self.relay.text_input == self) {
- self.relay.disableTextInput();
+ if (seat.relay.text_input == text_input) {
+ seat.relay.disableTextInput();
}
}
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);
+ const text_input = @fieldParentPtr(TextInput, "destroy", listener);
+ const seat: *Seat = @ptrFromInt(text_input.wlr_text_input.seat.data);
- if (self.relay.text_input == self) {
- self.relay.disableTextInput();
+ if (seat.relay.text_input == text_input) {
+ seat.relay.disableTextInput();
}
- self.enable.link.remove();
- self.commit.link.remove();
- self.disable.link.remove();
- self.destroy.link.remove();
+ text_input.enable.link.remove();
+ text_input.commit.link.remove();
+ text_input.disable.link.remove();
+ text_input.destroy.link.remove();
- self.relay.text_inputs.remove(node);
- util.gpa.destroy(node);
+ text_input.link.remove();
+ util.gpa.destroy(text_input);
}