TextInput: handle multiple text inputs correctly

The protocol states that we must send enter and leave to all text input
objects if the client has created multiple.

Only one text input is allowed to be activated by the client per seat
however.
This commit is contained in:
Isaac Freund 2023-12-31 14:44:38 -06:00
parent 49defcfb7a
commit 134a6bcfb5
No known key found for this signature in database
GPG Key ID: 86DED400DDFD7A11
4 changed files with 63 additions and 63 deletions

View File

@ -197,7 +197,7 @@ fn handleNewInputMethod(
log.debug("new input method on seat {s}", .{relay.seat.wlr_seat.name}); log.debug("new input method on seat {s}", .{relay.seat.wlr_seat.name});
if (seat.focused.surface()) |surface| { if (seat.focused.surface()) |surface| {
relay.setSurfaceFocus(surface); relay.focus(surface);
} }
} }

View File

@ -40,6 +40,8 @@ seat: *Seat,
text_inputs: std.TailQueue(TextInput) = .{}, text_inputs: std.TailQueue(TextInput) = .{},
input_method: ?*wlr.InputMethodV2 = null, input_method: ?*wlr.InputMethodV2 = null,
/// The currently enabled text input for the currently focused surface.
text_input: ?*TextInput = null,
input_method_commit: wl.Listener(*wlr.InputMethodV2) = input_method_commit: wl.Listener(*wlr.InputMethodV2) =
wl.Listener(*wlr.InputMethodV2).init(handleInputMethodCommit), wl.Listener(*wlr.InputMethodV2).init(handleInputMethodCommit),
@ -63,7 +65,7 @@ fn handleInputMethodCommit(
assert(input_method == self.input_method); assert(input_method == self.input_method);
if (!input_method.client_active) return; if (!input_method.client_active) return;
const text_input = self.getFocusedTextInput() orelse return; const text_input = self.text_input orelse return;
if (input_method.current.preedit.text) |preedit_text| { if (input_method.current.preedit.text) |preedit_text| {
text_input.wlr_text_input.sendPreeditString( text_input.wlr_text_input.sendPreeditString(
@ -102,9 +104,7 @@ fn handleInputMethodDestroy(
self.input_method = null; self.input_method = null;
if (self.getFocusedTextInput()) |text_input| { self.focus(null);
text_input.wlr_text_input.sendLeave();
}
} }
fn handleInputMethodGrabKeyboard( fn handleInputMethodGrabKeyboard(
@ -131,30 +131,23 @@ fn handleInputMethodGrabKeyboardDestroy(
} }
} }
pub fn getFocusedTextInput(self: *Self) ?*TextInput { pub fn disableTextInput(self: *Self) void {
var it = self.text_inputs.first; assert(self.text_input != null);
return while (it) |node| : (it = node.next) {
const text_input = &node.data; if (self.input_method) |input_method| {
if (text_input.wlr_text_input.focused_surface != null) break text_input; input_method.sendDeactivate();
} else null; input_method.sendDone();
} }
pub fn disableTextInput(self: *Self, text_input: *TextInput) void { self.text_input = null;
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) void {
} const input_method = self.input_method.?;
const wlr_text_input = self.text_input.?.wlr_text_input;
pub fn sendInputMethodState(self: *Self, wlr_text_input: *wlr.TextInputV3) void { // TODO Send these events only if something changed.
const input_method = self.input_method orelse return; // On activation all events must be sent for all active features.
// TODO: only send each of those if they were modified
// after activation, all supported features must be sent
if (wlr_text_input.active_features.surrounding_text) { if (wlr_text_input.active_features.surrounding_text) {
if (wlr_text_input.current.surrounding.text) |text| { if (wlr_text_input.current.surrounding.text) |text| {
@ -178,33 +171,39 @@ pub fn sendInputMethodState(self: *Self, wlr_text_input: *wlr.TextInputV3) void
input_method.sendDone(); input_method.sendDone();
} }
pub fn setSurfaceFocus(self: *Self, wlr_surface: ?*wlr.Surface) void { pub fn focus(self: *Self, new_focus: ?*wlr.Surface) void {
var new_text_input: ?*TextInput = null; // Send leave events
{
var it = self.text_inputs.first; var it = self.text_inputs.first;
while (it) |node| : (it = node.next) { while (it) |node| : (it = node.next) {
const text_input = &node.data; const text_input = &node.data;
if (text_input.wlr_text_input.focused_surface) |surface| { if (text_input.wlr_text_input.focused_surface) |surface| {
if (wlr_surface != surface) { // This function should not be called unless focus changes
text_input.relay.disableTextInput(text_input); assert(surface != new_focus);
text_input.wlr_text_input.sendLeave(); text_input.wlr_text_input.sendLeave();
} else {
log.debug("input relay setSurfaceFocus already focused", .{});
continue;
}
}
if (wlr_surface) |surface| {
if (text_input.wlr_text_input.resource.getClient() == surface.resource.getClient()) {
assert(new_text_input == null);
new_text_input = text_input;
} }
} }
} }
if (new_text_input) |text_input| { // Clear currently enabled text input
if (self.text_input != null) {
self.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) { if (self.input_method != null) {
text_input.wlr_text_input.sendEnter(wlr_surface.?); var it = self.text_inputs.first;
while (it) |node| : (it = node.next) {
const text_input = &node.data;
if (text_input.wlr_text_input.resource.getClient() == surface.resource.getClient()) {
text_input.wlr_text_input.sendEnter(surface);
}
}
} }
} }
} }

View File

@ -268,7 +268,7 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void {
} }
self.keyboardEnterOrLeave(target_surface); self.keyboardEnterOrLeave(target_surface);
self.relay.setSurfaceFocus(target_surface); self.relay.focus(target_surface);
if (target_surface) |surface| { if (target_surface) |surface| {
const pointer_constraints = server.input_manager.pointer_constraints; const pointer_constraints = server.input_manager.pointer_constraints;

View File

@ -56,46 +56,47 @@ pub fn init(self: *Self, relay: *InputRelay, wlr_text_input: *wlr.TextInputV3) v
fn handleEnable(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) void { fn handleEnable(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) void {
const self = @fieldParentPtr(Self, "enable", listener); const self = @fieldParentPtr(Self, "enable", listener);
if (self.relay.input_method) |im| { if (self.relay.text_input != null) {
im.sendActivate(); log.err("client requested to enable more than one text input on a single seat, ignoring request", .{});
} else {
log.debug("enabling text input but input method is gone", .{});
return; return;
} }
// must send surrounding_text if supported self.relay.text_input = self;
// must send content_type if supported
self.relay.sendInputMethodState(self.wlr_text_input); if (self.relay.input_method) |input_method| {
input_method.sendActivate();
self.relay.sendInputMethodState();
}
} }
fn handleCommit(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) void { fn handleCommit(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) void {
const self = @fieldParentPtr(Self, "commit", listener); const self = @fieldParentPtr(Self, "commit", listener);
if (!self.wlr_text_input.current_enabled) {
log.debug("inactive text input tried to commit an update", .{}); if (self.relay.text_input != self) {
log.err("inactive text input tried to commit an update, client bug?", .{});
return; return;
} }
log.debug("text input committed update", .{});
if (self.relay.input_method == null) { if (self.relay.input_method != null) {
log.debug("committed text input but input method is gone", .{}); self.relay.sendInputMethodState();
return;
} }
self.relay.sendInputMethodState(self.wlr_text_input);
} }
fn handleDisable(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) void { fn handleDisable(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) void {
const self = @fieldParentPtr(Self, "disable", listener); const self = @fieldParentPtr(Self, "disable", listener);
if (self.wlr_text_input.focused_surface == null) {
log.debug("disabling text input, but no longer focused", .{}); if (self.relay.text_input == self) {
return; self.relay.disableTextInput();
} }
self.relay.disableTextInput(self);
} }
fn handleDestroy(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) void { fn handleDestroy(listener: *wl.Listener(*wlr.TextInputV3), _: *wlr.TextInputV3) void {
const self = @fieldParentPtr(Self, "destroy", listener); const self = @fieldParentPtr(Self, "destroy", listener);
const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self);
if (self.wlr_text_input.current_enabled) self.relay.disableTextInput(self); if (self.relay.text_input == self) {
self.relay.disableTextInput();
}
self.enable.link.remove(); self.enable.link.remove();
self.commit.link.remove(); self.commit.link.remove();