Skip to content

Commit 38ba099

Browse files
committed
ui/gtk3: Implement virtual Wayland input-method with XIM
There were three issues. The first issue was D-Bus signals weren't sent to the IBus panel for the key release events of the shortcut key of the IME Switcher popup and updating preedit and lookup table when XIM or GTK2 applications are used, which don't support Wayland. Seems the problem is caused by adding IBusWaylandSource in ibus/client/wayland to use the duplicated event handling against glib2 and it's fixed to delete it. The second issue was how to implement Wayland with XIM and GTK2. I tried to handle the Wayland input-method activation/deactivation with creating another Wayland compositor and text-input protocols. But seems it needs to implement a custom wl_surface with the drawing input contexts and it's complicated for me at the moment while GNOME/mutter handles it with WL_SURFACE_ID environment but I cannot find ways for other Wayland desktop environments. Now XMODIFIERS=@im=ibus is used for X11 applications and GTK_IM_MODULE=ibus is for GTK2 applications and handles the key events in the IBus CandidatePanel UI and implements ibus_panel_service_forward_process_key_event(). The third issue was when non-supported Wayland applications likes XIM and GTK2 are used, the IBus CandidatePanel popup takes the focus as the normal application window in Wayland since the Wayland panel protocol requires the Wayland input-method activation. And if the popup is shown, it takes the focus and the active applications loose the input focus and the preedit text is cleared and it causes to close the CandidatePanel popup. I.e. the focus continues to be swapped between the application and the popup. Now ibus-daemon handles not to send focus-out events for the non-supported Wayland applications and IBus panel checks ibus-focus-out signal from ibus/client/wayland module if the current application supports the Wayland protocol and IBus engines handle a virtual input context to continue to update the preedit. BUG=#2562 BUG=#2617
1 parent 4251257 commit 38ba099

File tree

3 files changed

+226
-47
lines changed

3 files changed

+226
-47
lines changed

ui/gtk3/application.vala

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* ibus - The Input Bus
44
*
55
* Copyright(c) 2011 Peng Huang <[email protected]>
6-
* Copyright(c) 2017-2024 Takao Fujiwara <[email protected]>
6+
* Copyright(c) 2017-2025 Takao Fujiwara <[email protected]>
77
*
88
* This library is free software; you can redistribute it and/or
99
* modify it under the terms of the GNU Lesser General Public
@@ -38,6 +38,8 @@ class Application {
3838
private static bool m_enable_wayland_im;
3939
#if USE_GDK_WAYLAND
4040
private static ulong m_realize_surface_id;
41+
private static ulong m_ibus_focus_in_id;
42+
private static ulong m_ibus_focus_out_id;
4143
private static string m_user;
4244
private static IBus.WaylandIM m_wayland_im;
4345
private static bool m_exec_daemon;
@@ -101,6 +103,10 @@ class Application {
101103
#if USE_GDK_WAYLAND
102104
m_realize_surface_id = m_panel.realize_surface.connect(
103105
(w, s) => this.set_wayland_surface(s));
106+
m_ibus_focus_in_id = m_wayland_im.ibus_focus_in.connect(
107+
(w, o) => m_panel.set_wayland_object_path(o));
108+
m_ibus_focus_out_id = m_wayland_im.ibus_focus_out.connect(
109+
(w, o) => m_panel.set_wayland_object_path(null));
104110
#endif
105111
m_panel.load_settings();
106112
}
@@ -126,6 +132,14 @@ class Application {
126132
GLib.SignalHandler.disconnect(m_panel, m_realize_surface_id);
127133
m_realize_surface_id = 0;
128134
}
135+
if (m_ibus_focus_in_id != 0) {
136+
GLib.SignalHandler.disconnect(m_wayland_im, m_ibus_focus_in_id);
137+
m_ibus_focus_in_id = 0;
138+
}
139+
if (m_ibus_focus_out_id != 0) {
140+
GLib.SignalHandler.disconnect(m_wayland_im, m_ibus_focus_out_id);
141+
m_ibus_focus_out_id = 0;
142+
}
129143
#endif
130144
m_panel = null;
131145
}

ui/gtk3/candidatepanel.vala

Lines changed: 110 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* ibus - The Input Bus
44
*
55
* Copyright(c) 2011-2015 Peng Huang <[email protected]>
6-
* Copyright(c) 2015-2024 Takao Fujiwara <[email protected]>
6+
* Copyright(c) 2015-2025 Takao Fujiwara <[email protected]>
77
*
88
* This library is free software; you can redistribute it and/or
99
* modify it under the terms of the GNU Lesser General Public
@@ -26,9 +26,6 @@ public class CandidatePanel : Gtk.Box{
2626
private bool m_vertical_writing;
2727
private Gtk.Window m_toplevel;
2828
private Gtk.Box m_vbox;
29-
#if USE_GDK_WAYLAND
30-
private bool m_hide_after_show;
31-
#endif
3229

3330
private Gtk.Label m_preedit_label;
3431
private Gtk.Label m_aux_label;
@@ -38,6 +35,15 @@ public class CandidatePanel : Gtk.Box{
3835
private Gdk.Rectangle m_cursor_location;
3936

4037
private Pango.Attribute m_language_attribute;
38+
private uint m_update_id;
39+
40+
#if USE_GDK_WAYLAND
41+
private bool m_no_wayland_panel;
42+
private bool m_hide_after_show;
43+
private uint m_set_preedit_text_id;
44+
private uint m_set_auxiliary_text_id;
45+
private uint m_set_lookup_table_id;
46+
#endif
4147

4248
public signal void cursor_up();
4349
public signal void cursor_down();
@@ -47,17 +53,23 @@ public class CandidatePanel : Gtk.Box{
4753
uint button,
4854
uint state);
4955
#if USE_GDK_WAYLAND
56+
public signal void forward_process_key_event(uint keyval,
57+
uint keycode,
58+
uint modifiers);
5059
public signal void realize_surface(void *surface);
5160
#endif
5261

53-
public CandidatePanel() {
62+
public CandidatePanel(bool no_wayland_panel) {
5463
// Call base class constructor
5564
GLib.Object(
5665
name : "IBusCandidate",
5766
orientation: Gtk.Orientation.HORIZONTAL,
5867
visible: true
5968
);
6069

70+
#if USE_GDK_WAYLAND
71+
m_no_wayland_panel = no_wayland_panel;
72+
#endif
6173
m_toplevel = new Gtk.Window(Gtk.WindowType.POPUP);
6274
m_toplevel.add_events(Gdk.EventMask.BUTTON_PRESS_MASK);
6375
m_toplevel.button_press_event.connect((w, e) => {
@@ -84,6 +96,29 @@ public class CandidatePanel : Gtk.Box{
8496
});
8597
}
8698
#endif
99+
m_toplevel.key_press_event.connect(
100+
(widget, event) => {
101+
#if USE_GDK_WAYLAND
102+
uint32 keyval = event.keyval;
103+
uint32 keycode = event.hardware_keycode;
104+
uint32 state = event.state;
105+
forward_process_key_event(keyval, keycode, state);
106+
#endif
107+
return false;
108+
}
109+
);
110+
m_toplevel.key_release_event.connect(
111+
(widget, event) => {
112+
#if USE_GDK_WAYLAND
113+
uint32 keyval = event.keyval;
114+
uint32 keycode = event.hardware_keycode;
115+
uint32 state = event.state;
116+
state |= IBus.ModifierType.RELEASE_MASK;
117+
forward_process_key_event(keyval, keycode, state);
118+
#endif
119+
return false;
120+
}
121+
);
87122

88123
Handle handle = new Handle();
89124
handle.set_visible(true);
@@ -161,6 +196,24 @@ public class CandidatePanel : Gtk.Box{
161196
}
162197

163198
public void set_preedit_text(IBus.Text? text, uint cursor) {
199+
#if USE_GDK_WAYLAND
200+
if (m_set_preedit_text_id > 0)
201+
GLib.Source.remove(m_set_preedit_text_id);
202+
// FIXME: Too many PreeditText D-Bus signal happens in Wayland.
203+
m_set_preedit_text_id =
204+
Timeout.add(100,
205+
() => {
206+
//warning("test set_preedit_text_real");
207+
m_set_preedit_text_id = 0;
208+
set_preedit_text_real(text, cursor);
209+
return Source.REMOVE;
210+
});
211+
#else
212+
set_preedit_text_real(text, cursor);
213+
#endif
214+
}
215+
216+
public void set_preedit_text_real(IBus.Text? text, uint cursor) {
164217
if (text != null) {
165218
var str = text.get_text();
166219

@@ -180,6 +233,23 @@ public class CandidatePanel : Gtk.Box{
180233
}
181234

182235
public void set_auxiliary_text(IBus.Text? text) {
236+
#if USE_GDK_WAYLAND
237+
if (m_set_auxiliary_text_id > 0)
238+
GLib.Source.remove(m_set_auxiliary_text_id);
239+
// FIXME: Too many PreeditText D-Bus signal happens in Wayland.
240+
m_set_auxiliary_text_id =
241+
Timeout.add(100,
242+
() => {
243+
m_set_auxiliary_text_id = 0;
244+
set_auxiliary_text_real(text);
245+
return Source.REMOVE;
246+
});
247+
#else
248+
set_auxiliary_text_real(text);
249+
#endif
250+
}
251+
252+
public void set_auxiliary_text_real(IBus.Text? text) {
183253
if (text != null) {
184254
m_aux_label.set_text(text.get_text());
185255
Pango.AttrList attrs = get_pango_attr_list_from_ibus_text(text);
@@ -194,6 +264,22 @@ public class CandidatePanel : Gtk.Box{
194264
}
195265

196266
public void set_lookup_table(IBus.LookupTable? table) {
267+
#if USE_GDK_WAYLAND
268+
if (m_set_lookup_table_id > 0)
269+
GLib.Source.remove(m_set_lookup_table_id);
270+
// FIXME: Too many PreeditText D-Bus signal happens in Wayland.
271+
m_set_lookup_table_id = Timeout.add(100,
272+
() => {
273+
m_set_lookup_table_id = 0;
274+
set_lookup_table_real(table);
275+
return Source.REMOVE;
276+
});
277+
#else
278+
set_lookup_table_real(table);
279+
#endif
280+
}
281+
282+
public void set_lookup_table_real(IBus.LookupTable? table) {
197283
IBus.Text[] candidates = {};
198284
uint cursor_in_page = 0;
199285
bool show_cursor = true;
@@ -221,7 +307,9 @@ public class CandidatePanel : Gtk.Box{
221307
orientation = (IBus.Orientation)table.get_orientation();
222308
}
223309

224-
m_candidate_area.set_candidates(candidates, cursor_in_page, show_cursor);
310+
m_candidate_area.set_candidates(candidates,
311+
cursor_in_page,
312+
show_cursor);
225313
set_labels(labels);
226314

227315
if (table != null) {
@@ -244,6 +332,18 @@ public class CandidatePanel : Gtk.Box{
244332
}
245333

246334
private void update() {
335+
if (m_update_id > 0)
336+
GLib.Source.remove(m_update_id);
337+
// set_lookup_table() and set_preedit_text() happens sequentially.
338+
m_update_id = Timeout.add(100,
339+
() => {
340+
m_update_id = 0;
341+
update_real();
342+
return Source.REMOVE;
343+
});
344+
}
345+
346+
private void update_real() {
247347
/* Do not call gtk_window_resize() in
248348
* GtkWidgetClass->get_preferred_width()
249349
* because the following warning is shown in GTK 3.20:
@@ -449,6 +549,10 @@ public class CandidatePanel : Gtk.Box{
449549

450550
#if USE_GDK_WAYLAND
451551
private void realize_window(bool initial) {
552+
// The custom surface can be used when the Wayland input-method
553+
// is activated.
554+
if (m_no_wayland_panel)
555+
return;
452556
var window = m_toplevel.get_window();
453557
if (!window.ensure_native()) {
454558
warning("No native window.");

0 commit comments

Comments
 (0)