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});
if (seat.focused.surface()) |surface| {
relay.setSurfaceFocus(surface);
relay.focus(surface);
}
}

View File

@ -40,6 +40,8 @@ seat: *Seat,
text_inputs: std.TailQueue(TextInput) = .{},
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) =
wl.Listener(*wlr.InputMethodV2).init(handleInputMethodCommit),
@ -63,7 +65,7 @@ fn handleInputMethodCommit(
assert(input_method == self.input_method);
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| {
text_input.wlr_text_input.sendPreeditString(
@ -102,9 +104,7 @@ fn handleInputMethodDestroy(
self.input_method = null;
if (self.getFocusedTextInput()) |text_input| {
text_input.wlr_text_input.sendLeave();
}
self.focus(null);
}
fn handleInputMethodGrabKeyboard(
@ -131,30 +131,23 @@ fn handleInputMethodGrabKeyboardDestroy(
}
}
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) void {
assert(self.text_input != 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;
if (self.input_method) |input_method| {
input_method.sendDeactivate();
input_method.sendDone();
}
self.sendInputMethodState(text_input.wlr_text_input);
self.text_input = null;
}
pub fn sendInputMethodState(self: *Self, wlr_text_input: *wlr.TextInputV3) void {
const input_method = self.input_method orelse return;
pub fn sendInputMethodState(self: *Self) void {
const input_method = self.input_method.?;
const wlr_text_input = self.text_input.?.wlr_text_input;
// TODO: only send each of those if they were modified
// after activation, all supported features must be sent
// TODO Send these events only if something changed.
// On activation all events must be sent for all active features.
if (wlr_text_input.active_features.surrounding_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();
}
pub fn setSurfaceFocus(self: *Self, wlr_surface: ?*wlr.Surface) void {
var new_text_input: ?*TextInput = null;
pub fn focus(self: *Self, 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;
if (text_input.wlr_text_input.focused_surface) |surface| {
if (wlr_surface != surface) {
text_input.relay.disableTextInput(text_input);
// This function should not be called unless focus changes
assert(surface != new_focus);
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) {
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.relay.setSurfaceFocus(target_surface);
self.relay.focus(target_surface);
if (target_surface) |surface| {
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 {
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", .{});
if (self.relay.text_input != null) {
log.err("client requested to enable more than one text input on a single seat, ignoring request", .{});
return;
}
// must send surrounding_text if supported
// must send content_type if supported
self.relay.sendInputMethodState(self.wlr_text_input);
self.relay.text_input = self;
if (self.relay.input_method) |input_method| {
input_method.sendActivate();
self.relay.sendInputMethodState();
}
}
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", .{});
if (self.relay.text_input != self) {
log.err("inactive text input tried to commit an update, client bug?", .{});
return;
}
log.debug("text input committed update", .{});
if (self.relay.input_method == null) {
log.debug("committed text input but input method is gone", .{});
return;
if (self.relay.input_method != null) {
self.relay.sendInputMethodState();
}
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;
if (self.relay.text_input == self) {
self.relay.disableTextInput();
}
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);
if (self.relay.text_input == self) {
self.relay.disableTextInput();
}
self.enable.link.remove();
self.commit.link.remove();