/* vim:set et sts=4 sw=4: * * ibus - The Input Bus * * Copyright(c) 2018 Peng Huang * Copyright(c) 2018 Takao Fujwiara * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA */ class Preedit : Gtk.Window { private Gtk.Label m_extension_preedit_text; private Gtk.Label m_extension_preedit_emoji; private IBus.Text? m_engine_preedit_text; private bool m_engine_preedit_text_show; private uint m_engine_preedit_cursor_pos; private string m_prefix = "@"; private bool m_is_shown = true; public Preedit() { GLib.Object( name : "IBusPreedit", type: Gtk.WindowType.POPUP ); m_extension_preedit_text = new Gtk.Label(""); m_extension_preedit_emoji = new Gtk.Label(""); } public new void hide() { reset(); base.hide(); m_is_shown = false; } public bool is_shown() { return m_is_shown; } public void reset() { set_emoji(""); set_text(""); resize(1, 1); m_is_shown = true; } public void append_text(string text) { if (text.length == 0) return; string total = m_extension_preedit_text.get_text(); total += text; m_extension_preedit_text.set_text(total); } public string get_text() { return m_extension_preedit_text.get_text(); } public void set_text(string text) { m_extension_preedit_text.set_text(text); } public string get_emoji() { return m_extension_preedit_emoji.get_text(); } public void set_emoji(string text) { m_extension_preedit_emoji.set_text(text); } public bool backspace() { string total = m_extension_preedit_emoji.get_text(); if (total.length > 0) { m_extension_preedit_emoji.set_text(""); resize(1, 1); return false; } total = m_extension_preedit_text.get_text(); int char_count = total.char_count(); if (char_count == 0) return true; total = total[0:total.index_of_nth_char(char_count - 1)]; resize(1, 1); m_extension_preedit_text.set_text(total); if (total.length == 0) resize(1, 1); return true; } private string get_extension_text () { string extension_text = m_extension_preedit_emoji.get_text(); if (extension_text.length == 0) extension_text = m_extension_preedit_text.get_text(); return m_prefix + extension_text; } private void set_preedit_color(IBus.Text text, uint start_index, uint end_index) { text.append_attribute(IBus.AttrType.UNDERLINE, IBus.AttrUnderline.SINGLE, start_index, (int)end_index); } public IBus.Text get_engine_preedit_text() { string extension_text = get_extension_text(); uint char_count = extension_text.char_count(); IBus.Text retval; if (m_engine_preedit_text == null || !m_engine_preedit_text_show) { retval = new IBus.Text.from_string(extension_text); set_preedit_color(retval, 0, char_count); return retval; } retval = new IBus.Text.from_string( extension_text + m_engine_preedit_text.get_text()); set_preedit_color(retval, 0, char_count); unowned IBus.AttrList attrs = m_engine_preedit_text.get_attributes(); if (attrs == null) return retval; int i = 0; while (true) { IBus.Attribute attr = attrs.get(i++); if (attr == null) break; long start_index = attr.start_index; long end_index = attr.end_index; if (start_index < 0) start_index = 0; if (end_index < 0) end_index = m_engine_preedit_text.get_length(); retval.append_attribute(attr.type, attr.value, char_count + (uint)start_index, (int)char_count + (int)end_index); } return retval; } public void set_engine_preedit_text(IBus.Text? text) { m_engine_preedit_text = text; } public void show_engine_preedit_text() { m_engine_preedit_text_show = true; } public void hide_engine_preedit_text() { m_engine_preedit_text_show = false; } public uint get_engine_preedit_cursor_pos() { return get_extension_text().char_count() + m_engine_preedit_cursor_pos; } public void set_engine_preedit_cursor_pos(uint cursor_pos) { m_engine_preedit_cursor_pos = cursor_pos; } public IBus.Text get_commit_text() { string extension_text = m_extension_preedit_emoji.get_text(); if (extension_text.length == 0) extension_text = m_extension_preedit_text.get_text(); return new IBus.Text.from_string(extension_text); } public void set_extension_name(string extension_name) { if (extension_name.length == 0) m_prefix = "@"; else m_prefix = extension_name[0:1]; } } class PanelBinding : IBus.PanelService { private bool m_is_wayland; private bool m_wayland_lookup_table_is_visible; private IBus.Bus m_bus; private Gtk.Application m_application; private GLib.Settings m_settings_panel = null; private GLib.Settings m_settings_emoji = null; private string m_current_context_path = ""; private string m_real_current_context_path = ""; private IBusEmojier? m_emojier; private uint m_emojier_set_emoji_lang_id; private uint m_emojier_focus_commit_text_id; private string[] m_emojier_favorites = {}; private Gtk.CssProvider m_css_provider; private const uint PRELOAD_ENGINES_DELAY_TIME = 30000; private bool m_load_emoji_at_startup; private bool m_loaded_emoji = false; private bool m_load_unicode_at_startup; private bool m_loaded_unicode = false; private bool m_enable_extension; private string m_extension_name = ""; private Preedit m_preedit; private IBus.ProcessKeyEventData m_key_event_data = IBus.ProcessKeyEventData(); public PanelBinding(IBus.Bus bus, Gtk.Application application) { GLib.assert(bus.is_connected()); // Chain up base class constructor GLib.Object(connection : bus.get_connection(), object_path : IBus.PATH_PANEL_EXTENSION_EMOJI); Type instance_type = Gdk.Display.get_default().get_type(); Type wayland_type = typeof(GdkWayland.Display); m_is_wayland = instance_type.is_a(wayland_type); m_bus = bus; m_application = application; init_settings(); m_preedit = new Preedit(); } private void init_settings() { m_settings_panel = new GLib.Settings("org.freedesktop.ibus.panel"); m_settings_emoji = new GLib.Settings("org.freedesktop.ibus.panel.emoji"); m_settings_panel.changed["custom-font"].connect((key) => { BindingCommon.set_custom_font(m_settings_panel, m_settings_emoji, ref m_css_provider); }); m_settings_panel.changed["use-custom-font"].connect((key) => { BindingCommon.set_custom_font(m_settings_panel, m_settings_emoji, ref m_css_provider); }); m_settings_emoji.changed["unicode-hotkey"].connect((key) => { set_emoji_hotkey(); }); m_settings_emoji.changed["font"].connect((key) => { BindingCommon.set_custom_font(m_settings_panel, m_settings_emoji, ref m_css_provider); }); m_settings_emoji.changed["hotkey"].connect((key) => { set_emoji_hotkey(); }); m_settings_emoji.changed["favorites"].connect((key) => { set_emoji_favorites(); }); m_settings_emoji.changed["favorite-annotations"].connect((key) => { set_emoji_favorites(); }); m_settings_emoji.changed["lang"].connect((key) => { set_emoji_lang(); }); m_settings_emoji.changed["has-partial-match"].connect((key) => { set_emoji_partial_match(); }); m_settings_emoji.changed["partial-match-length"].connect((key) => { set_emoji_partial_match(); }); m_settings_emoji.changed["partial-match-condition"].connect((key) => { set_emoji_partial_match(); }); m_settings_emoji.changed["load-emoji-at-startup"].connect((key) => { set_load_emoji_at_startup(); }); m_settings_emoji.changed["load-unicode-at-startup"].connect((key) => { set_load_unicode_at_startup(); }); } // Returning unowned IBus.KeyEventData causes NULL with gcc optimization // and use m_key_event_data. private void parse_accelerator(string accelerator) { m_key_event_data = {}; uint keysym = 0; IBus.ModifierType modifiers = 0; IBus.accelerator_parse(accelerator, out keysym, out modifiers); if (keysym == 0U && modifiers == 0) { warning("Failed to parse shortcut key '%s'".printf(accelerator)); return; } if ((modifiers & IBus.ModifierType.SUPER_MASK) != 0) { modifiers ^= IBus.ModifierType.SUPER_MASK; modifiers |= IBus.ModifierType.MOD4_MASK; } m_key_event_data.keyval = keysym; m_key_event_data.state = modifiers; } private void set_emoji_hotkey() { IBus.ProcessKeyEventData[] emoji_keys = {}; IBus.ProcessKeyEventData key; string[] accelerators = m_settings_emoji.get_strv("hotkey"); foreach (var accelerator in accelerators) { parse_accelerator(accelerator); emoji_keys += m_key_event_data; } /* Since {} is not allocated, parse_accelerator() should be unowned. */ key = {}; emoji_keys += key; IBus.ProcessKeyEventData[] unicode_keys = {}; accelerators = m_settings_emoji.get_strv("unicode-hotkey"); foreach (var accelerator in accelerators) { parse_accelerator(accelerator); unicode_keys += m_key_event_data; } key = {}; unicode_keys += key; panel_extension_register_keys("emoji", emoji_keys, "unicode", unicode_keys); } private void set_emoji_favorites() { m_emojier_favorites = m_settings_emoji.get_strv("favorites"); IBusEmojier.set_favorites( m_emojier_favorites, m_settings_emoji.get_strv("favorite-annotations")); } private void set_emoji_lang() { if (m_emojier_set_emoji_lang_id > 0) { GLib.Source.remove(m_emojier_set_emoji_lang_id); m_emojier_set_emoji_lang_id = 0; } m_emojier_set_emoji_lang_id = GLib.Idle.add(() => { IBusEmojier.set_annotation_lang( m_settings_emoji.get_string("lang")); m_emojier_set_emoji_lang_id = 0; m_loaded_emoji = true; if (m_load_unicode_at_startup && !m_loaded_unicode) { IBusEmojier.load_unicode_dict(); m_loaded_unicode = true; } return false; }); } private void set_emoji_partial_match() { IBusEmojier.set_partial_match( m_settings_emoji.get_boolean("has-partial-match")); IBusEmojier.set_partial_match_length( m_settings_emoji.get_int("partial-match-length")); IBusEmojier.set_partial_match_condition( m_settings_emoji.get_int("partial-match-condition")); } private void set_load_emoji_at_startup() { m_load_emoji_at_startup = m_settings_emoji.get_boolean("load-emoji-at-startup"); } private void set_load_unicode_at_startup() { m_load_unicode_at_startup = m_settings_emoji.get_boolean("load-unicode-at-startup"); } public void load_settings() { set_emoji_hotkey(); set_load_emoji_at_startup(); set_load_unicode_at_startup(); BindingCommon.set_custom_font(m_settings_panel, m_settings_emoji, ref m_css_provider); set_emoji_favorites(); if (m_load_emoji_at_startup && !m_loaded_emoji) set_emoji_lang(); set_emoji_partial_match(); } /** * disconnect_signals: * * Call this API before m_panel = null so that the ref_count becomes 0 */ public void disconnect_signals() { if (m_emojier_set_emoji_lang_id > 0) { GLib.Source.remove(m_emojier_set_emoji_lang_id); m_emojier_set_emoji_lang_id = 0; } if (m_emojier != null) { m_application.remove_window(m_emojier); m_emojier = null; } m_application = null; } private void commit_text_update_favorites(IBus.Text text, bool disable_extension) { commit_text(text); // If disable_extension is false, the extension event is already // sent before the focus-in is received. if (disable_extension) { IBus.ExtensionEvent event = new IBus.ExtensionEvent( "name", m_extension_name, "is-enabled", false, "is-extension", true); panel_extension(event); } string committed_string = text.text; string preedit_string = m_preedit.get_text(); m_preedit.hide(); if (preedit_string == committed_string) return; bool has_favorite = false; foreach (unowned string favorite in m_emojier_favorites) { if (favorite == committed_string) { has_favorite = true; break; } } if (!has_favorite) { m_emojier_favorites += committed_string; m_settings_emoji.set_strv("favorites", m_emojier_favorites); } } private bool emojier_focus_commit_real() { if (m_emojier == null) return true; string selected_string = m_emojier.get_selected_string(); string prev_context_path = m_emojier.get_input_context_path(); if (selected_string != null && prev_context_path != "" && prev_context_path == m_current_context_path) { IBus.Text text = new IBus.Text.from_string(selected_string); commit_text_update_favorites(text, false); m_emojier.reset(); return true; } return false; } private void emojier_focus_commit() { if (m_emojier == null) return; string selected_string = m_emojier.get_selected_string(); string prev_context_path = m_emojier.get_input_context_path(); if (selected_string == null && prev_context_path != "") { var context = GLib.MainContext.default(); if (m_emojier_focus_commit_text_id > 0 && context.find_source_by_id(m_emojier_focus_commit_text_id) != null) { GLib.Source.remove(m_emojier_focus_commit_text_id); } m_emojier_focus_commit_text_id = GLib.Timeout.add(100, () => { // focus_in is comming before switcher returns emojier_focus_commit_real(); m_emojier_focus_commit_text_id = -1; return false; }); } else { if (emojier_focus_commit_real()) { var context = GLib.MainContext.default(); if (m_emojier_focus_commit_text_id > 0 && context.find_source_by_id(m_emojier_focus_commit_text_id) != null) { GLib.Source.remove(m_emojier_focus_commit_text_id); } m_emojier_focus_commit_text_id = -1; } } } private bool key_press_escape() { if (is_emoji_lookup_table()) { bool show_candidate = m_emojier.key_press_escape(); convert_preedit_text(); return show_candidate; } if (m_preedit.get_emoji() != "") { m_preedit.set_emoji(""); string annotation = m_preedit.get_text(); m_emojier.set_annotation(annotation); return false; } m_enable_extension = false; hide_emoji_lookup_table(); m_preedit.hide(); IBus.ExtensionEvent event = new IBus.ExtensionEvent( "name", m_extension_name, "is-enabled", false, "is-extension", true); panel_extension(event); return false; } private bool key_press_keyval(uint keyval) { unichar ch = IBus.keyval_to_unicode(keyval); if (ch.iscntrl()) return false; string str = ch.to_string(); m_preedit.append_text(str); string annotation = m_preedit.get_text(); m_emojier.set_annotation(annotation); m_preedit.set_emoji(""); return true; } private bool key_press_enter() { if (m_extension_name != "unicode" && is_emoji_lookup_table()) { // Check if variats exist if (m_emojier.key_press_enter(false)) { convert_preedit_text(); return true; } } IBus.Text text = m_preedit.get_commit_text(); commit_text_update_favorites(text, true); return false; } private void convert_preedit_text() { if (m_emojier.get_number_of_candidates() > 0) m_preedit.set_emoji(m_emojier.get_current_candidate()); else m_preedit.set_emoji(""); } private bool key_press_space() { bool show_candidate = false; if (m_preedit.get_emoji() != "") { m_emojier.key_press_cursor_horizontal(Gdk.Key.Right, 0); show_candidate = true; } else { string annotation = m_preedit.get_text(); if (annotation.length == 0) { show_candidate = true; if (is_emoji_lookup_table()) m_emojier.key_press_cursor_horizontal(Gdk.Key.Right, 0); } else { m_emojier.set_annotation(annotation); } } convert_preedit_text(); return show_candidate; } private bool key_press_cursor_horizontal(uint keyval, uint modifiers) { if (is_emoji_lookup_table()) { m_emojier.key_press_cursor_horizontal(keyval, modifiers); convert_preedit_text(); return true; } return false; } private bool key_press_cursor_vertical(uint keyval, uint modifiers) { if (is_emoji_lookup_table()) { m_emojier.key_press_cursor_vertical(keyval, modifiers); convert_preedit_text(); return true; } return false; } private bool key_press_cursor_home_end(uint keyval, uint modifiers) { if (is_emoji_lookup_table()) { m_emojier.key_press_cursor_home_end(keyval, modifiers); convert_preedit_text(); return true; } return false; } private bool key_press_control_keyval(uint keyval, uint modifiers) { bool show_candidate = false; switch(keyval) { case Gdk.Key.f: show_candidate = key_press_cursor_horizontal(Gdk.Key.Right, modifiers); break; case Gdk.Key.b: show_candidate = key_press_cursor_horizontal(Gdk.Key.Left, modifiers); break; case Gdk.Key.n: case Gdk.Key.N: show_candidate = key_press_cursor_vertical(Gdk.Key.Down, modifiers); break; case Gdk.Key.p: case Gdk.Key.P: show_candidate = key_press_cursor_vertical(Gdk.Key.Up, modifiers); break; case Gdk.Key.h: show_candidate = key_press_cursor_home_end(Gdk.Key.Home, modifiers); break; case Gdk.Key.e: show_candidate = key_press_cursor_home_end(Gdk.Key.End, modifiers); break; case Gdk.Key.u: m_preedit.reset(); m_emojier.set_annotation(""); hide_emoji_lookup_table(); break; case Gdk.Key.C: case Gdk.Key.c: if ((modifiers & Gdk.ModifierType.SHIFT_MASK) != 0) { if (!m_is_wayland && m_emojier != null && m_emojier.get_number_of_candidates() > 0) { var text = m_emojier.get_current_candidate(); Gtk.Clipboard clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD); clipboard.set_text(text, -1); clipboard.store(); } show_candidate = is_emoji_lookup_table(); } break; default: show_candidate = is_emoji_lookup_table(); break; } return show_candidate; } private void hide_wayland_lookup_table() { m_wayland_lookup_table_is_visible = false; var text = new IBus.Text.from_string(""); update_auxiliary_text_received(text, false); update_lookup_table_received( new IBus.LookupTable(1, 0, false, true), false); } private void show_wayland_lookup_table(IBus.Text text) { m_wayland_lookup_table_is_visible = true; var table = m_emojier.get_one_dimension_lookup_table(); uint ncandidates = table.get_number_of_candidates(); update_auxiliary_text_received( text, ncandidates > 0 ? true : false); update_lookup_table_received( table, ncandidates > 0 ? true : false); } private void hide_emoji_lookup_table() { if (m_emojier == null) return; if (m_wayland_lookup_table_is_visible) hide_wayland_lookup_table(); else m_emojier.hide(); } private void show_emoji_lookup_table() { /* Emojier category_list is shown in both Xorg and Wayland * because the annotation information is useful but the Wayland lookup * window is alway one dimension. So the category_list is shown * when the user annotation is null. */ if (m_is_wayland && m_preedit.get_text() != "") { var text = m_emojier.get_title_text(); show_wayland_lookup_table(text); } else { // POPUP window takes the focus in Wayland. if (m_is_wayland) m_emojier.set_input_context_path(m_real_current_context_path); m_emojier.show_all(); } } private bool is_emoji_lookup_table() { if (m_is_wayland) return m_wayland_lookup_table_is_visible; else return m_emojier.get_visible(); } private void show_preedit_and_candidate(bool show_candidate) { uint cursor_pos = 0; if (!show_candidate) cursor_pos = m_preedit.get_engine_preedit_cursor_pos(); update_preedit_text_received( m_preedit.get_engine_preedit_text(), cursor_pos, true); if (!show_candidate) { hide_emoji_lookup_table(); return; } if (m_emojier == null) return; /* Wayland gives the focus on Emojir which is a GTK popup window * and move the focus fom the current input context to Emojier. * This forwards the lookup table to gnome-shell's lookup table * but it enables one dimension lookup table only. */ show_emoji_lookup_table(); } public override void focus_in(string input_context_path) { m_current_context_path = input_context_path; /* 'fake' input context is named as * '/org/freedesktop/IBus/InputContext_1' and always send in * focus-out events by ibus-daemon for the global engine mode. * Now ibus-daemon assumes to always use the global engine. * But this event should not be used for modal dialogs * such as Switcher. */ if (!input_context_path.has_suffix("InputContext_1")) { m_real_current_context_path = m_current_context_path; if (m_is_wayland) this.emojier_focus_commit(); } } public override void focus_out(string input_context_path) { m_current_context_path = ""; } public override void panel_extension_received(IBus.ExtensionEvent event) { m_extension_name = event.get_name(); if (m_extension_name != "emoji" && m_extension_name != "unicode") { string format = "The name %s is not implemented in PanelExtension"; warning (format.printf(m_extension_name)); m_extension_name = ""; return; } m_enable_extension = event.is_enabled; if (!m_enable_extension) { hide_emoji_lookup_table(); return; } if (!m_loaded_emoji) set_emoji_lang(); if (!m_loaded_unicode && m_loaded_emoji) { IBusEmojier.load_unicode_dict(); m_loaded_unicode = true; } if (m_emojier == null) { m_emojier = new IBusEmojier(); // For title handling in gnome-shell m_application.add_window(m_emojier); m_emojier.candidate_clicked.connect((i, b, s) => { candidate_clicked_lookup_table_real(i, b, s, true); }); m_emojier.commit_text.connect((s) => { if (!m_is_wayland) return; // Currently emojier has a focus but the text input focus // does not and commit the text later. IBus.ExtensionEvent close_event = new IBus.ExtensionEvent( "name", m_extension_name, "is-enabled", false, "is-extension", true); panel_extension(close_event); }); } m_emojier.reset(); m_emojier.set_annotation(""); m_preedit.set_extension_name(m_extension_name); m_preedit.reset(); update_preedit_text_received( m_preedit.get_engine_preedit_text(), m_preedit.get_engine_preedit_cursor_pos(), true); string params = event.get_params(); if (params == "category-list") { key_press_space(); show_preedit_and_candidate(true); } } public override void set_cursor_location(int x, int y, int width, int height) { if (m_emojier != null) m_emojier.set_cursor_location(x, y, width, height); } public override void update_preedit_text(IBus.Text text, uint cursor_pos, bool visible) { m_preedit.set_engine_preedit_text(text); if (visible) m_preedit.show_engine_preedit_text(); else m_preedit.hide_engine_preedit_text(); m_preedit.set_engine_preedit_cursor_pos(cursor_pos); update_preedit_text_received(m_preedit.get_engine_preedit_text(), m_preedit.get_engine_preedit_cursor_pos(), visible); } public override void show_preedit_text() { m_preedit.show_engine_preedit_text(); show_preedit_and_candidate(false); } public override void hide_preedit_text() { m_preedit.hide_engine_preedit_text(); show_preedit_and_candidate(false); } public override bool process_key_event(uint keyval, uint keycode, uint state) { if ((state & IBus.ModifierType.RELEASE_MASK) != 0) return false; uint modifiers = state; bool show_candidate = false; switch(keyval) { case Gdk.Key.Escape: show_candidate = key_press_escape(); if (!m_preedit.is_shown()) return true; break; case Gdk.Key.Return: case Gdk.Key.KP_Enter: if (m_extension_name == "unicode") key_press_space(); show_candidate = key_press_enter(); if (!m_preedit.is_shown()) { hide_emoji_lookup_table(); return true; } break; case Gdk.Key.BackSpace: m_preedit.backspace(); string annotation = m_preedit.get_text(); if (annotation == "" && m_extension_name == "unicode") { key_press_escape(); return true; } m_emojier.set_annotation(annotation); break; case Gdk.Key.space: case Gdk.Key.KP_Space: if ((modifiers & Gdk.ModifierType.SHIFT_MASK) != 0) { if (!key_press_keyval(keyval)) return true; show_candidate = is_emoji_lookup_table(); break; } show_candidate = key_press_space(); if (m_extension_name == "unicode") { key_press_enter(); return true; } break; case Gdk.Key.Right: case Gdk.Key.KP_Right: /* one dimension in Wayland, two dimensions in X11 */ if (m_is_wayland) { show_candidate = key_press_cursor_vertical(Gdk.Key.Down, modifiers); } else { show_candidate = key_press_cursor_horizontal(Gdk.Key.Right, modifiers); } break; case Gdk.Key.Left: case Gdk.Key.KP_Left: if (m_is_wayland) { show_candidate = key_press_cursor_vertical(Gdk.Key.Up, modifiers); } else { show_candidate = key_press_cursor_horizontal(Gdk.Key.Left, modifiers); } break; case Gdk.Key.Down: case Gdk.Key.KP_Down: if (m_is_wayland) { show_candidate = key_press_cursor_horizontal(Gdk.Key.Right, modifiers); } else { show_candidate = key_press_cursor_vertical(Gdk.Key.Down, modifiers); } break; case Gdk.Key.Up: case Gdk.Key.KP_Up: if (m_is_wayland) { show_candidate = key_press_cursor_horizontal(Gdk.Key.Left, modifiers); } else { show_candidate = key_press_cursor_vertical(Gdk.Key.Up, modifiers); } break; case Gdk.Key.Page_Down: case Gdk.Key.KP_Page_Down: if (m_is_wayland) { show_candidate = key_press_cursor_vertical(Gdk.Key.Down, modifiers); } else { show_candidate = key_press_cursor_vertical(Gdk.Key.Page_Down, modifiers); } break; case Gdk.Key.Page_Up: case Gdk.Key.KP_Page_Up: if (m_is_wayland) { show_candidate = key_press_cursor_vertical(Gdk.Key.Up, modifiers); } else { show_candidate = key_press_cursor_vertical(Gdk.Key.Page_Up, modifiers); } break; case Gdk.Key.Home: case Gdk.Key.KP_Home: show_candidate = key_press_cursor_home_end(Gdk.Key.Home, modifiers); break; case Gdk.Key.End: case Gdk.Key.KP_End: show_candidate = key_press_cursor_home_end(Gdk.Key.End, modifiers); break; default: if ((modifiers & Gdk.ModifierType.CONTROL_MASK) != 0) { show_candidate = key_press_control_keyval(keyval, modifiers); break; } if (!key_press_keyval(keyval)) return true; show_candidate = is_emoji_lookup_table(); break; } show_preedit_and_candidate(show_candidate); return true; } public override void commit_text_received(IBus.Text text) { unowned string? str = text.text; if (str == null) return; /* Do not call convert_preedit_text() because it depends on * each IME whether process_key_event() receives Shift-space or not. */ m_preedit.append_text(str); m_preedit.set_emoji(""); string annotation = m_preedit.get_text(); m_emojier.set_annotation(annotation); show_preedit_and_candidate(false); } public override void page_up_lookup_table() { bool show_candidate = key_press_cursor_vertical(Gdk.Key.Up, 0); show_preedit_and_candidate(show_candidate); } public override void page_down_lookup_table() { bool show_candidate = key_press_cursor_vertical(Gdk.Key.Down, 0); show_preedit_and_candidate(show_candidate); } public override void cursor_up_lookup_table() { bool show_candidate = key_press_cursor_horizontal(Gdk.Key.Left, 0); show_preedit_and_candidate(show_candidate); } public override void cursor_down_lookup_table() { bool show_candidate = key_press_cursor_horizontal(Gdk.Key.Right, 0); show_preedit_and_candidate(show_candidate); } private void candidate_clicked_lookup_table_real(uint index, uint button, uint state, bool is_emojier) { if (button == IBusEmojier.BUTTON_CLOSE_BUTTON) { m_enable_extension = false; hide_emoji_lookup_table(); m_preedit.hide(); IBus.ExtensionEvent event = new IBus.ExtensionEvent( "name", m_extension_name, "is-enabled", false, "is-extension", true); panel_extension(event); return; } if (m_emojier == null) return; bool show_candidate = false; uint ncandidates = m_emojier.get_number_of_candidates(); if (ncandidates > 0 && ncandidates >= index) { m_emojier.set_cursor_pos(index); bool need_commit_signal = m_is_wayland && is_emojier; show_candidate = m_emojier.has_variants(index, need_commit_signal); if (!m_is_wayland) m_preedit.set_emoji(m_emojier.get_current_candidate()); } else { return; } if (!show_candidate) { IBus.Text text = m_preedit.get_commit_text(); hide_emoji_lookup_table(); if (!is_emojier || !m_is_wayland) commit_text_update_favorites(text, true); return; } show_preedit_and_candidate(show_candidate); } public override void candidate_clicked_lookup_table(uint index, uint button, uint state) { candidate_clicked_lookup_table_real(index, button, state, false); } }