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:
parent
49defcfb7a
commit
134a6bcfb5
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 (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) |input_method| {
|
||||||
if (self.input_method) |im| {
|
input_method.sendDeactivate();
|
||||||
im.sendDeactivate();
|
input_method.sendDone();
|
||||||
} else {
|
|
||||||
log.debug("disabling text input but input method is gone", .{});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.sendInputMethodState(text_input.wlr_text_input);
|
self.text_input = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sendInputMethodState(self: *Self, wlr_text_input: *wlr.TextInputV3) void {
|
pub fn sendInputMethodState(self: *Self) void {
|
||||||
const input_method = self.input_method orelse return;
|
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
|
// TODO Send these events only if something changed.
|
||||||
// after activation, all supported features must be sent
|
// On activation all events must be sent for all active features.
|
||||||
|
|
||||||
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;
|
||||||
|
while (it) |node| : (it = node.next) {
|
||||||
|
const text_input = &node.data;
|
||||||
|
|
||||||
var it = self.text_inputs.first;
|
if (text_input.wlr_text_input.focused_surface) |surface| {
|
||||||
while (it) |node| : (it = node.next) {
|
// This function should not be called unless focus changes
|
||||||
const text_input = &node.data;
|
assert(surface != new_focus);
|
||||||
if (text_input.wlr_text_input.focused_surface) |surface| {
|
|
||||||
if (wlr_surface != surface) {
|
|
||||||
text_input.relay.disableTextInput(text_input);
|
|
||||||
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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();
|
||||||
|
Loading…
Reference in New Issue
Block a user