/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* vim:set et sts=4: */ /* IBus - The Input Bus * Copyright (C) 2008-2010 Peng Huang * Copyright (C) 2018 Takao Fujiwara * Copyright (C) 2008-2018 Red Hat, Inc. * * 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 */ #include "ibushotkey.h" #include "ibusmarshalers.h" #include "ibuskeysyms.h" #include "ibusinternal.h" #include "ibusshare.h" #define IBUS_HOTKEY_PROFILE_GET_PRIVATE(o) \ (G_TYPE_INSTANCE_GET_PRIVATE ((o), IBUS_TYPE_HOTKEY_PROFILE, IBusHotkeyProfilePrivate)) enum { TRIGGER, LAST_SIGNAL, }; typedef struct _IBusHotkey IBusHotkey; typedef struct _IBusHotkeyEvent IBusHotkeyEvent; typedef struct _IBusHotkeyProfilePrivate IBusHotkeyProfilePrivate; struct _IBusHotkey { guint keyval; guint modifiers; }; struct _IBusHotkeyEvent { GQuark event; GList *hotkeys; }; struct _IBusHotkeyProfilePrivate { GTree *hotkeys; GArray *events; guint mask; }; /* functions prototype */ static IBusHotkey *ibus_hotkey_new (guint keyval, guint modifiers); static IBusHotkey *ibus_hotkey_copy (const IBusHotkey *src); static void ibus_hotkey_free (IBusHotkey *hotkey); static void ibus_hotkey_profile_class_init (IBusHotkeyProfileClass *class); static void ibus_hotkey_profile_init (IBusHotkeyProfile *profile); static void ibus_hotkey_profile_destroy (IBusHotkeyProfile *profile); static gboolean ibus_hotkey_profile_serialize (IBusHotkeyProfile *profile, GVariantBuilder *builder); static gint ibus_hotkey_profile_deserialize(IBusHotkeyProfile *profile, GVariant *variant); static gboolean ibus_hotkey_profile_copy (IBusHotkeyProfile *dest, const IBusHotkeyProfile*src); static void ibus_hotkey_profile_trigger (IBusHotkeyProfile *profile, GQuark event, gpointer user_data); // Normalize modifiers by setting necessary modifier bits according to keyval. static guint normalize_modifiers (guint keyval, guint modifiers); static gboolean is_modifier (guint keyval); static IBusSerializableClass *parent_class = NULL; static guint profile_signals[LAST_SIGNAL] = { 0 }; GType ibus_hotkey_get_type (void) { static GType type = 0; if (type == 0) { type = g_boxed_type_register_static ("IBusHotkey", (GBoxedCopyFunc) ibus_hotkey_copy, (GBoxedFreeFunc) ibus_hotkey_free); } return type; } static IBusHotkey * ibus_hotkey_new (guint keyval, guint modifiers) { IBusHotkey *hotkey = g_slice_new (IBusHotkey); hotkey->keyval = keyval; hotkey->modifiers = modifiers; return hotkey; } static void ibus_hotkey_free (IBusHotkey *hotkey) { g_slice_free (IBusHotkey, hotkey); } static IBusHotkey * ibus_hotkey_copy (const IBusHotkey *src) { return ibus_hotkey_new (src->keyval, src->modifiers); } static gint ibus_hotkey_cmp_with_data (IBusHotkey *hotkey1, IBusHotkey *hotkey2, gpointer user_data) { gint retval; if (hotkey1 == hotkey2) return 0; retval = hotkey1->keyval - hotkey2->keyval; if (retval == 0) retval = hotkey1->modifiers - hotkey2->modifiers; return retval; } GType ibus_hotkey_profile_get_type (void) { static GType type = 0; static const GTypeInfo type_info = { sizeof (IBusHotkeyProfileClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) ibus_hotkey_profile_class_init, NULL, /* class finialize */ NULL, /* class data */ sizeof (IBusHotkeyProfile), 0, (GInstanceInitFunc) ibus_hotkey_profile_init, }; if (type == 0) { type = g_type_register_static (IBUS_TYPE_SERIALIZABLE, "IBusHotkeyProfile", &type_info, 0); } return type; } static void ibus_hotkey_profile_class_init (IBusHotkeyProfileClass *class) { IBusObjectClass *object_class = IBUS_OBJECT_CLASS (class); IBusSerializableClass *serializable_class = IBUS_SERIALIZABLE_CLASS (class); parent_class = (IBusSerializableClass *) g_type_class_peek_parent (class); g_type_class_add_private (class, sizeof (IBusHotkeyProfilePrivate)); object_class->destroy = (IBusObjectDestroyFunc) ibus_hotkey_profile_destroy; serializable_class->serialize = (IBusSerializableSerializeFunc) ibus_hotkey_profile_serialize; serializable_class->deserialize = (IBusSerializableDeserializeFunc) ibus_hotkey_profile_deserialize; serializable_class->copy = (IBusSerializableCopyFunc) ibus_hotkey_profile_copy; class->trigger = ibus_hotkey_profile_trigger; /* install signals */ /** * IBusHotkeyProfile::trigger: * @profile: An IBusHotkeyProfile. * @event: An event in GQuark. * @user_data: User data for callback. * * Emitted when a hotkey is pressed and the hotkey is in profile. * Implement the member function trigger() in extended class to receive this signal. * * The last parameter, user_data is not actually a valid parameter. It is displayed because of GtkDoc bug. */ profile_signals[TRIGGER] = g_signal_new (I_("trigger"), G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, G_STRUCT_OFFSET (IBusHotkeyProfileClass, trigger), NULL, NULL, _ibus_marshal_VOID__UINT_POINTER, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_POINTER); } static void ibus_hotkey_profile_init (IBusHotkeyProfile *profile) { IBusHotkeyProfilePrivate *priv; priv = IBUS_HOTKEY_PROFILE_GET_PRIVATE (profile); priv->hotkeys = g_tree_new_full ((GCompareDataFunc) ibus_hotkey_cmp_with_data, NULL, (GDestroyNotify) ibus_hotkey_free, NULL); priv->events = g_array_new (TRUE, TRUE, sizeof (IBusHotkeyEvent)); priv->mask = IBUS_SHIFT_MASK | IBUS_CONTROL_MASK | IBUS_MOD1_MASK | IBUS_SUPER_MASK | IBUS_HYPER_MASK | IBUS_RELEASE_MASK; } static void ibus_hotkey_profile_destroy (IBusHotkeyProfile *profile) { IBusHotkeyProfilePrivate *priv; priv = IBUS_HOTKEY_PROFILE_GET_PRIVATE (profile); /* free events */ if (priv->events) { IBusHotkeyEvent *p; gint i; p = (IBusHotkeyEvent *)g_array_free (priv->events, FALSE); priv->events = NULL; for (i = 0; p[i].event != 0; i++) { g_list_free (p[i].hotkeys); } g_free (p); } if (priv->hotkeys) { g_tree_destroy (priv->hotkeys); priv->hotkeys = NULL; } IBUS_OBJECT_CLASS (parent_class)->destroy ((IBusObject *)profile); } static gboolean ibus_hotkey_profile_serialize (IBusHotkeyProfile *profile, GVariantBuilder *builder) { gboolean retval; retval = parent_class->serialize ((IBusSerializable *) profile, builder); g_return_val_if_fail (retval, FALSE); return TRUE; } static gint ibus_hotkey_profile_deserialize (IBusHotkeyProfile *profile, GVariant *variant) { gint retval; retval = parent_class->deserialize ((IBusSerializable *) profile, variant); g_return_val_if_fail (retval, 0); return retval; } static gboolean ibus_hotkey_profile_copy (IBusHotkeyProfile *dest, const IBusHotkeyProfile *src) { gboolean retval; retval = parent_class->copy ((IBusSerializable *)dest, (IBusSerializable *)src); g_return_val_if_fail (retval, FALSE); g_return_val_if_fail (IBUS_IS_HOTKEY_PROFILE (dest), FALSE); g_return_val_if_fail (IBUS_IS_HOTKEY_PROFILE (src), FALSE); return TRUE; } IBusHotkeyProfile * ibus_hotkey_profile_new (void) { IBusHotkeyProfile *profile = g_object_new (IBUS_TYPE_HOTKEY_PROFILE, NULL); return profile; } static void ibus_hotkey_profile_trigger (IBusHotkeyProfile *profile, GQuark event, gpointer user_data) { // g_debug ("%s is triggerred", g_quark_to_string (event)); } static guint normalize_modifiers (guint keyval, guint modifiers) { switch(keyval) { case IBUS_KEY_Control_L: case IBUS_KEY_Control_R: return modifiers | IBUS_CONTROL_MASK; case IBUS_KEY_Shift_L: case IBUS_KEY_Shift_R: return modifiers | IBUS_SHIFT_MASK; case IBUS_KEY_Alt_L: case IBUS_KEY_Alt_R: // Chrome OS does not have Meta key. Instead, shift+alt generates Meta keyval. case IBUS_KEY_Meta_L: case IBUS_KEY_Meta_R: return modifiers | IBUS_MOD1_MASK; case IBUS_KEY_Super_L: case IBUS_KEY_Super_R: return modifiers | IBUS_SUPER_MASK; case IBUS_KEY_Hyper_L: case IBUS_KEY_Hyper_R: return modifiers | IBUS_HYPER_MASK; default: return modifiers; } } static gboolean is_modifier (guint keyval) { switch(keyval) { case IBUS_KEY_Control_L: case IBUS_KEY_Control_R: case IBUS_KEY_Shift_L: case IBUS_KEY_Shift_R: case IBUS_KEY_Alt_L: case IBUS_KEY_Alt_R: case IBUS_KEY_Meta_L: case IBUS_KEY_Meta_R: case IBUS_KEY_Super_L: case IBUS_KEY_Super_R: case IBUS_KEY_Hyper_L: case IBUS_KEY_Hyper_R: return TRUE; default: return FALSE; } } gboolean ibus_hotkey_profile_add_hotkey (IBusHotkeyProfile *profile, guint keyval, guint modifiers, GQuark event) { IBusHotkeyProfilePrivate *priv; priv = IBUS_HOTKEY_PROFILE_GET_PRIVATE (profile); IBusHotkey *hotkey = ibus_hotkey_new (keyval, normalize_modifiers (keyval, modifiers & priv->mask)); /* has the same hotkey in profile */ if (g_tree_lookup (priv->hotkeys, hotkey) != NULL) { ibus_hotkey_free (hotkey); g_return_val_if_reached (FALSE); } g_tree_insert (priv->hotkeys, (gpointer) hotkey, GUINT_TO_POINTER (event)); IBusHotkeyEvent *p = NULL; gint i; for ( i = 0; i < priv->events->len; i++) { p = &g_array_index (priv->events, IBusHotkeyEvent, i); if (p->event == event) break; } if (i >= priv->events->len) { g_array_set_size (priv->events, i + 1); p = &g_array_index (priv->events, IBusHotkeyEvent, i); p->event = event; } p->hotkeys = g_list_append (p->hotkeys, hotkey); return TRUE; } gboolean ibus_hotkey_profile_add_hotkey_from_string (IBusHotkeyProfile *profile, const gchar *str, GQuark event) { guint keyval; guint modifiers; if (ibus_key_event_from_string (str, &keyval, &modifiers) == FALSE) { return FALSE; } return ibus_hotkey_profile_add_hotkey (profile, keyval, modifiers, event); } gboolean ibus_hotkey_profile_remove_hotkey (IBusHotkeyProfile *profile, guint keyval, guint modifiers) { IBusHotkeyProfilePrivate *priv; priv = IBUS_HOTKEY_PROFILE_GET_PRIVATE (profile); modifiers = normalize_modifiers (keyval, modifiers & priv->mask); IBusHotkey hotkey = { .keyval = keyval, .modifiers = modifiers }; IBusHotkey *p1; GQuark event; gboolean retval; retval = g_tree_lookup_extended (priv->hotkeys, &hotkey, (gpointer)&p1, (gpointer)&event); if (!retval) return FALSE; gint i; IBusHotkeyEvent *p2 = NULL; for ( i = 0; i < priv->events->len; i++) { p2 = &g_array_index (priv->events, IBusHotkeyEvent, i); if (p2->event == event) break; } g_assert (p2 && p2->event == event); p2->hotkeys = g_list_remove (p2->hotkeys, p1); if (p2->hotkeys == NULL) { g_array_remove_index_fast (priv->events, i); } g_tree_remove (priv->hotkeys, p1); return TRUE; } gboolean ibus_hotkey_profile_remove_hotkey_by_event (IBusHotkeyProfile *profile, GQuark event) { IBusHotkeyProfilePrivate *priv; priv = IBUS_HOTKEY_PROFILE_GET_PRIVATE (profile); gint i; IBusHotkeyEvent *p = NULL; for ( i = 0; i < priv->events->len; i++) { p = &g_array_index (priv->events, IBusHotkeyEvent, i); if (p->event == event) break; } if (p == NULL || p->event != event) return FALSE; GList *list; for (list = p->hotkeys; list != NULL; list = list->next) { g_tree_remove (priv->hotkeys, (IBusHotkey *)list->data); } g_list_free (p->hotkeys); g_array_remove_index_fast (priv->events, i); return TRUE; } GQuark ibus_hotkey_profile_filter_key_event (IBusHotkeyProfile *profile, guint keyval, guint modifiers, guint prev_keyval, guint prev_modifiers, gpointer user_data) { IBusHotkeyProfilePrivate *priv; priv = IBUS_HOTKEY_PROFILE_GET_PRIVATE (profile); modifiers = normalize_modifiers (keyval, modifiers & priv->mask); prev_modifiers = normalize_modifiers (prev_keyval, prev_modifiers & priv->mask); IBusHotkey hotkey = { .keyval = keyval, .modifiers = modifiers, }; if (modifiers & IBUS_RELEASE_MASK) { /* previous key event must be a press key event */ if (prev_modifiers & IBUS_RELEASE_MASK) return 0; /* modifiers should be same except the release bit */ if (modifiers != (prev_modifiers | IBUS_RELEASE_MASK)) return 0; /* If it is release key event, * we need check if keyval is equal to the prev keyval. * If keyval is not equal to the prev keyval, * but both keyvals are modifier keys, * we will still search it in hotkeys. * It is for matching some key sequences like: * Shift Down, Alt Down, Shift Up => Shift+Alt+Release - Shift hotkey **/ if ((keyval != prev_keyval) && (is_modifier (keyval) == FALSE || is_modifier (prev_keyval) == FALSE)) return 0; } GQuark event = (GQuark) GPOINTER_TO_UINT (g_tree_lookup (priv->hotkeys, &hotkey)); if (event != 0) { g_signal_emit (profile, profile_signals[TRIGGER], event, user_data); } return event; } GQuark ibus_hotkey_profile_lookup_hotkey (IBusHotkeyProfile *profile, guint keyval, guint modifiers) { IBusHotkeyProfilePrivate *priv; priv = IBUS_HOTKEY_PROFILE_GET_PRIVATE (profile); modifiers = normalize_modifiers (keyval, modifiers & priv->mask); IBusHotkey hotkey = { .keyval = keyval, .modifiers = modifiers, }; return (GQuark) GPOINTER_TO_UINT (g_tree_lookup (priv->hotkeys, &hotkey)); }