/* * Copyright (C) 2010 Daiki Ueno * Copyright (C) 2010 Red Hat, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifdef HAVE_CONFIG_H #include "config.h" #endif /* HAVE_CONFIG_H */ #if HAVE_CLUTTER_GTK #include #if NEED_SWAP_EVENT_WORKAROUND #include #endif #endif #include #include #include #include #include #include #include #include #include #include #include #if HAVE_CLUTTER_GTK #include "eek/eek-clutter.h" #endif #include "eek/eek-gtk.h" #include "eek/eek-xkl.h" #define CSW 640 #define CSH 480 #if HAVE_CLUTTER_GTK #define USE_CLUTTER TRUE #else #define USE_CLUTTER FALSE #endif #ifdef EEKBOARD_ENABLE_DEBUG #define EEKBOARD_NOTE(x,a...) g_message (G_STRLOC ": " x, ##a); #else #define EEKBOARD_NOTE(x,a...) #endif #define LICENSE \ "This program is free software: you can redistribute it and/or modify " \ "it under the terms of the GNU General Public License as published by " \ "the Free Software Foundation, either version 3 of the License, or " \ "(at your option) any later version." \ "\n\n" \ "This program 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 General Public License for more details." \ "\n\n" \ "You should have received a copy of the GNU General Public License " \ "along with this program. If not, see . " \ struct _Eekboard { gboolean use_clutter; gboolean need_swap_event_workaround; gboolean accessibility_enabled; Display *display; FakeKey *fakekey; GConfClient *gconfc; GtkWidget *widget, *window; gint width, height; XklEngine *engine; XklConfigRegistry *registry; GtkUIManager *ui_manager; guint countries_merge_id; GtkActionGroup *countries_action_group; guint languages_merge_id; GtkActionGroup *languages_action_group; guint models_merge_id; GtkActionGroup *models_action_group; guint layouts_merge_id; GtkActionGroup *layouts_action_group; guint options_merge_id; GtkActionGroup *options_action_group; EekKeyboard *keyboard; EekLayout *layout; /* FIXME: eek_keyboard_get_layout() */ guint modifiers; }; typedef struct _Eekboard Eekboard; struct _SetConfigCallbackData { Eekboard *eekboard; XklConfigRec *config; }; typedef struct _SetConfigCallbackData SetConfigCallbackData; struct _CreateMenuCallbackData { Eekboard *eekboard; GtkActionGroup *action_group; guint merge_id; }; typedef struct _CreateMenuCallbackData CreateMenuCallbackData; struct _LayoutVariant { XklConfigItem *layout; XklConfigItem *variant; }; typedef struct _LayoutVariant LayoutVariant; static void on_countries_menu (GtkAction *action, GtkWidget *widget); static void on_languages_menu (GtkAction *action, GtkWidget *widget); static void on_models_menu (GtkAction *action, GtkWidget *window); static void on_layouts_menu (GtkAction *action, GtkWidget *window); static void on_options_menu (GtkAction *action, GtkWidget *window); static void on_about (GtkAction *action, GtkWidget *window); static void on_quit (GtkAction * action, GtkWidget *window); static void on_monitor_key_event_toggled (GtkToggleAction *action, GtkWidget *window); static void eekboard_free (Eekboard *eekboard); static GtkWidget *create_widget (Eekboard *eekboard, gint initial_width, gint initial_height); static GtkWidget *create_widget_gtk (Eekboard *eekboard, gint initial_width, gint initial_height); #if !HAVE_CLUTTER_GTK #define create_widget create_widget_gtk #endif static const char ui_description[] = "" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " ""; #define COUNTRIES_UI_PATH "/MainMenu/KeyboardMenu/Country/CountriesPH" #define LANGUAGES_UI_PATH "/MainMenu/KeyboardMenu/Language/LanguagesPH" #define MODELS_UI_PATH "/MainMenu/KeyboardMenu/Model/ModelsPH" #define LAYOUTS_UI_PATH "/MainMenu/KeyboardMenu/Layout/LayoutsPH" #define OPTIONS_UI_PATH "/MainMenu/KeyboardMenu/Option/OptionsPH" static const GtkActionEntry action_entry[] = { {"FileMenu", NULL, N_("_File")}, {"KeyboardMenu", NULL, N_("_Keyboard")}, {"HelpMenu", NULL, N_("_Help")}, {"Quit", GTK_STOCK_QUIT, NULL, NULL, NULL, G_CALLBACK (on_quit)}, {"Country", NULL, N_("Country"), NULL, NULL, G_CALLBACK(on_countries_menu)}, {"Language", NULL, N_("Language"), NULL, NULL, G_CALLBACK(on_languages_menu)}, {"Model", NULL, N_("Model"), NULL, NULL, G_CALLBACK(on_models_menu)}, {"Layout", NULL, N_("Layout"), NULL, NULL, G_CALLBACK(on_layouts_menu)}, {"Option", NULL, N_("Option"), NULL, NULL, G_CALLBACK(on_options_menu)}, {"About", GTK_STOCK_ABOUT, NULL, NULL, NULL, G_CALLBACK (on_about)} }; static const GtkToggleActionEntry toggle_action_entry[] = { {"MonitorKeyEvent", NULL, N_("Monitor Key Typing"), NULL, NULL, G_CALLBACK(on_monitor_key_event_toggled), FALSE} }; static gchar *opt_model = NULL; static gchar *opt_layouts = NULL; static gchar *opt_options = NULL; static gboolean opt_list_models = FALSE; static gboolean opt_list_layouts = FALSE; static gboolean opt_list_options = FALSE; static gboolean opt_version = FALSE; static const GOptionEntry options[] = { {"model", 'M', 0, G_OPTION_ARG_STRING, &opt_model, N_("Keyboard model to display")}, {"layouts", 'L', 0, G_OPTION_ARG_STRING, &opt_layouts, N_("Keyboard layouts to display, separated with commas")}, {"options", 'O', 0, G_OPTION_ARG_STRING, &opt_options, N_("Keyboard layout options to display, separated with commas")}, {"list-models", '\0', 0, G_OPTION_ARG_NONE, &opt_list_models, N_("List keyboard models")}, {"list-layouts", '\0', 0, G_OPTION_ARG_NONE, &opt_list_layouts, N_("List all available keyboard layouts and variants")}, {"list-options", 'O', 0, G_OPTION_ARG_NONE, &opt_list_options, N_("List all available keyboard layout options")}, {"version", 'v', 0, G_OPTION_ARG_NONE, &opt_version, N_("Display version")}, {NULL} }; static void on_about (GtkAction * action, GtkWidget *window) { const gchar *authors[] = { "Daiki Ueno", NULL }; gtk_show_about_dialog (GTK_WINDOW (window), "program-name", PACKAGE, "version", VERSION, "copyright", "Copyright \xc2\xa9 2010 Daiki Ueno\n" "Copyright \xc2\xa9 2010 Red Hat, Inc.", "license", LICENSE, "comments", _("A virtual keyboard for GNOME"), "authors", authors, "website", "http://github.com/ueno/eek/", "website-label", _("Eekboard web site"), "wrap-license", TRUE, NULL); } static void on_quit (GtkAction * action, GtkWidget *window) { Eekboard *eekboard = g_object_get_data (G_OBJECT(window), "eekboard"); fakekey_release (eekboard->fakekey); eekboard_free (eekboard); gtk_main_quit (); } static SPIBoolean a11y_focus_listener (const AccessibleEvent *event, void *user_data) { Eekboard *eekboard = user_data; Accessible *acc = event->source; AccessibleStateSet *state_set = Accessible_getStateSet (acc); if (AccessibleStateSet_contains (state_set, SPI_STATE_EDITABLE) || Accessible_getRole (acc) == SPI_ROLE_TERMINAL) { gtk_widget_show (eekboard->window); } else { gtk_widget_hide (eekboard->window); } return FALSE; } static SPIBoolean a11y_keystroke_listener (const AccessibleKeystroke *stroke, void *user_data) { Eekboard *eekboard = user_data; EekKey *key; //g_return_val_if_fail (stroke->modifiers == SPI_KEYMASK_UNMODIFIED, FALSE); key = eek_keyboard_find_key_by_keycode (eekboard->keyboard, stroke->keycode); g_return_val_if_fail (key, FALSE); if (stroke->type == SPI_KEY_PRESSED) g_signal_emit_by_name (key, "pressed"); else g_signal_emit_by_name (key, "released"); return TRUE; } static AccessibleEventListener* focusListener; static AccessibleEventListener* keystrokeListener; static void on_monitor_key_event_toggled (GtkToggleAction *action, GtkWidget *window) { Eekboard *eekboard = g_object_get_data (G_OBJECT(window), "eekboard"); if (!keystrokeListener) { keystrokeListener = SPI_createAccessibleKeystrokeListener (a11y_keystroke_listener, eekboard); } if (gtk_toggle_action_get_active (action)) { if (!SPI_registerAccessibleKeystrokeListener (keystrokeListener, SPI_KEYSET_ALL_KEYS, 0, SPI_KEY_PRESSED | SPI_KEY_RELEASED, SPI_KEYLISTENER_NOSYNC)) g_warning ("failed to register keystroke listener"); } else if (!SPI_deregisterAccessibleKeystrokeListener (keystrokeListener, 0)) g_warning ("failed to deregister keystroke listener"); } static void on_key_pressed (EekKeyboard *keyboard, EekKey *key, gpointer user_data) { Eekboard *eekboard = user_data; guint keysym; keysym = eek_key_get_keysym (key); EEKBOARD_NOTE("%s %X", eek_keysym_to_string (keysym), eekboard->modifiers); if (keysym == XK_Shift_L || keysym == XK_Shift_R) { gint group, level; eekboard->modifiers ^= ShiftMask; eek_keyboard_get_keysym_index (keyboard, &group, &level); eek_keyboard_set_keysym_index (keyboard, group, eekboard->modifiers & ShiftMask ? 1 : 0); } else if (keysym == XK_Control_L || keysym == XK_Control_R) { eekboard->modifiers ^= ControlMask; } else if (keysym == XK_Alt_L || keysym == XK_Alt_R) { eekboard->modifiers ^= Mod1Mask; } else fakekey_press_keysym (eekboard->fakekey, keysym, eekboard->modifiers); } static void on_key_released (EekKeyboard *keyboard, EekKey *key, gpointer user_data) { Eekboard *eekboard = user_data; fakekey_release (eekboard->fakekey); } static void on_config_activate (GtkAction *action, gpointer user_data) { SetConfigCallbackData *data = user_data; eek_xkl_layout_set_config (EEK_XKL_LAYOUT(data->eekboard->layout), data->config); } static void on_option_toggled (GtkToggleAction *action, gpointer user_data) { SetConfigCallbackData *data = user_data; if (gtk_toggle_action_get_active (action)) eek_xkl_layout_enable_option (EEK_XKL_LAYOUT(data->eekboard->layout), data->config->options[0]); else eek_xkl_layout_disable_option (EEK_XKL_LAYOUT(data->eekboard->layout), data->config->options[0]); } static void on_changed (EekLayout *layout, gpointer user_data) { Eekboard *eekboard = user_data; GtkWidget *vbox, *widget; GtkAllocation allocation; gtk_widget_get_allocation (GTK_WIDGET (eekboard->widget), &allocation); vbox = gtk_widget_get_parent (eekboard->widget); /* gtk_widget_destroy() seems not usable for GtkClutterEmbed */ gtk_container_remove (GTK_CONTAINER(vbox), eekboard->widget); g_object_unref (eekboard->keyboard); widget = create_widget (eekboard, allocation.width, allocation.height); gtk_container_add (GTK_CONTAINER(vbox), widget); gtk_widget_show_all (vbox); } static void collect_layout_variant (XklConfigRegistry *registry, const XklConfigItem *layout, const XklConfigItem *variant, gpointer user_data) { GSList **r_lv_list = user_data; LayoutVariant *lv; lv = g_slice_new0 (LayoutVariant); lv->layout = g_slice_dup (XklConfigItem, layout); if (variant) lv->variant = g_slice_dup (XklConfigItem, variant); *r_lv_list = g_slist_prepend (*r_lv_list, lv); } static void add_layout_variant_actions (CreateMenuCallbackData *data, const gchar *name, const gchar *path, GSList *lv_list) { gchar variant_action_name[128]; SetConfigCallbackData *config; GSList *head; for (head = lv_list; head; head = head->next) { LayoutVariant *lv = head->data; GtkAction *action; gchar description[XKL_MAX_CI_DESC_LENGTH * 2 + 4]; if (lv->variant) { g_snprintf (variant_action_name, sizeof (variant_action_name), "SetLayoutVariant %s %s %s", name, lv->layout->name, lv->variant->name); g_snprintf (description, sizeof (description), "%s (%s)", lv->layout->description, lv->variant->description); } else { g_snprintf (variant_action_name, sizeof (variant_action_name), "SetLayout %s %s", name, lv->layout->name); g_strlcpy (description, lv->layout->description, sizeof (description)); } action = gtk_action_new (variant_action_name, description, NULL, NULL); config = g_slice_new (SetConfigCallbackData); config->eekboard = data->eekboard; config->config = xkl_config_rec_new (); config->config->layouts = g_new0 (char *, 2); config->config->layouts[0] = g_strdup (lv->layout->name); config->config->layouts[1] = NULL; if (lv->variant) { config->config->variants = g_new0 (char *, 2); config->config->variants[0] = g_strdup (lv->variant->name); config->config->variants[1] = NULL; } g_signal_connect (action, "activate", G_CALLBACK (on_config_activate), config); gtk_action_group_add_action (data->action_group, action); g_object_unref (action); gtk_ui_manager_add_ui (data->eekboard->ui_manager, data->merge_id, path, variant_action_name, variant_action_name, GTK_UI_MANAGER_MENUITEM, FALSE); g_slice_free (XklConfigItem, lv->layout); if (lv->variant) g_slice_free (XklConfigItem, lv->variant); g_slice_free (LayoutVariant, lv); } } static void country_callback (XklConfigRegistry *registry, const XklConfigItem *item, gpointer user_data) { CreateMenuCallbackData *data = user_data; GtkAction *action; GSList *lv_list = NULL; char country_action_name[128]; char country_action_path[128]; g_snprintf (country_action_name, sizeof (country_action_name), "Country %s", item->name); action = gtk_action_new (country_action_name, item->description, NULL, NULL); gtk_action_group_add_action (data->action_group, action); g_object_unref (action); gtk_ui_manager_add_ui (data->eekboard->ui_manager, data->merge_id, COUNTRIES_UI_PATH, country_action_name, country_action_name, GTK_UI_MANAGER_MENU, FALSE); g_snprintf (country_action_path, sizeof (country_action_path), COUNTRIES_UI_PATH "/%s", country_action_name); xkl_config_registry_foreach_country_variant (data->eekboard->registry, item->name, collect_layout_variant, &lv_list); add_layout_variant_actions (data, item->name, country_action_path, lv_list); g_slist_free (lv_list); } static guint create_countries_menu (Eekboard *eekboard) { CreateMenuCallbackData data; data.eekboard = eekboard; data.merge_id = gtk_ui_manager_new_merge_id (eekboard->ui_manager); data.action_group = eekboard->countries_action_group; xkl_config_registry_foreach_country (eekboard->registry, country_callback, &data); return data.merge_id; } static void on_countries_menu (GtkAction *action, GtkWidget *window) { Eekboard *eekboard = g_object_get_data (G_OBJECT(window), "eekboard"); if (eekboard->countries_merge_id == 0) eekboard->countries_merge_id = create_countries_menu (eekboard); } static void language_callback (XklConfigRegistry *registry, const XklConfigItem *item, gpointer user_data) { CreateMenuCallbackData *data = user_data; GtkAction *action; GSList *lv_list = NULL; char language_action_name[128]; char language_action_path[128]; g_snprintf (language_action_name, sizeof (language_action_name), "Language %s", item->name); action = gtk_action_new (language_action_name, item->description, NULL, NULL); gtk_action_group_add_action (data->action_group, action); g_object_unref (action); gtk_ui_manager_add_ui (data->eekboard->ui_manager, data->merge_id, LANGUAGES_UI_PATH, language_action_name, language_action_name, GTK_UI_MANAGER_MENU, FALSE); g_snprintf (language_action_path, sizeof (language_action_path), LANGUAGES_UI_PATH "/%s", language_action_name); xkl_config_registry_foreach_language_variant (data->eekboard->registry, item->name, collect_layout_variant, &lv_list); add_layout_variant_actions (data, item->name, language_action_path, lv_list); g_slist_free (lv_list); } static guint create_languages_menu (Eekboard *eekboard) { CreateMenuCallbackData data; data.eekboard = eekboard; data.merge_id = gtk_ui_manager_new_merge_id (eekboard->ui_manager); data.action_group = eekboard->languages_action_group; xkl_config_registry_foreach_language (eekboard->registry, language_callback, &data); return data.merge_id; } static void on_languages_menu (GtkAction *action, GtkWidget *window) { Eekboard *eekboard = g_object_get_data (G_OBJECT(window), "eekboard"); if (eekboard->languages_merge_id == 0) eekboard->languages_merge_id = create_languages_menu (eekboard); } static void model_callback (XklConfigRegistry *registry, const XklConfigItem *item, gpointer user_data) { CreateMenuCallbackData *data = user_data; GtkAction *action; char model_action_name[128]; SetConfigCallbackData *config; g_snprintf (model_action_name, sizeof (model_action_name), "Model %s", item->name); action = gtk_action_new (model_action_name, item->description, NULL, NULL); gtk_action_group_add_action (data->action_group, action); config = g_slice_new (SetConfigCallbackData); config->eekboard = data->eekboard; config->config = xkl_config_rec_new (); config->config->model = g_strdup (item->name); g_signal_connect (action, "activate", G_CALLBACK (on_config_activate), config); g_object_unref (action); gtk_ui_manager_add_ui (data->eekboard->ui_manager, data->merge_id, MODELS_UI_PATH, model_action_name, model_action_name, GTK_UI_MANAGER_MENUITEM, FALSE); } static guint create_models_menu (Eekboard *eekboard) { CreateMenuCallbackData data; data.eekboard = eekboard; data.merge_id = gtk_ui_manager_new_merge_id (eekboard->ui_manager); data.action_group = eekboard->models_action_group; xkl_config_registry_foreach_model (eekboard->registry, model_callback, &data); return data.merge_id; } static void on_models_menu (GtkAction *action, GtkWidget *window) { Eekboard *eekboard = g_object_get_data (G_OBJECT(window), "eekboard"); if (eekboard->models_merge_id == 0) eekboard->models_merge_id = create_models_menu (eekboard); } static void collect_variant (XklConfigRegistry *registry, const XklConfigItem *variant, gpointer user_data) { GSList **r_variants = user_data; XklConfigItem *item; item = g_slice_dup (XklConfigItem, variant); *r_variants = g_slist_prepend (*r_variants, item); } static void layout_callback (XklConfigRegistry *registry, const XklConfigItem *item, gpointer user_data) { CreateMenuCallbackData *data = user_data; GtkAction *action; GSList *variants = NULL; char layout_action_name[128], variant_action_name[128]; SetConfigCallbackData *config; g_snprintf (layout_action_name, sizeof (layout_action_name), "SetLayout %s", item->name); action = gtk_action_new (layout_action_name, item->description, NULL, NULL); gtk_action_group_add_action (data->action_group, action); xkl_config_registry_foreach_layout_variant (registry, item->name, collect_variant, &variants); if (!variants) { config = g_slice_new (SetConfigCallbackData); config->eekboard = data->eekboard; config->config = xkl_config_rec_new (); config->config->layouts = g_new0 (char *, 2); config->config->layouts[0] = g_strdup (item->name); config->config->layouts[1] = NULL; /* Reset the existing variant setting. */ config->config->variants = g_new0 (char *, 1); config->config->variants[0] = NULL; g_signal_connect (action, "activate", G_CALLBACK (on_config_activate), config); g_object_unref (action); gtk_ui_manager_add_ui (data->eekboard->ui_manager, data->merge_id, LAYOUTS_UI_PATH, layout_action_name, layout_action_name, GTK_UI_MANAGER_MENUITEM, FALSE); } else { char layout_path[128]; GSList *head; g_object_unref (action); gtk_ui_manager_add_ui (data->eekboard->ui_manager, data->merge_id, LAYOUTS_UI_PATH, layout_action_name, layout_action_name, GTK_UI_MANAGER_MENU, FALSE); g_snprintf (layout_path, sizeof (layout_path), LAYOUTS_UI_PATH "/%s", layout_action_name); for (head = variants; head; head = head->next) { XklConfigItem *_item = head->data; g_snprintf (variant_action_name, sizeof (variant_action_name), "SetLayoutVariant %s %s", item->name, _item->name); action = gtk_action_new (variant_action_name, _item->description, NULL, NULL); config = g_slice_new (SetConfigCallbackData); config->eekboard = data->eekboard; config->config = xkl_config_rec_new (); config->config->layouts = g_new0 (char *, 2); config->config->layouts[0] = g_strdup (item->name); config->config->layouts[1] = NULL; config->config->variants = g_new0 (char *, 2); config->config->variants[0] = g_strdup (_item->name); config->config->variants[1] = NULL; g_signal_connect (action, "activate", G_CALLBACK (on_config_activate), config); gtk_action_group_add_action (data->action_group, action); g_object_unref (action); gtk_ui_manager_add_ui (data->eekboard->ui_manager, data->merge_id, layout_path, variant_action_name, variant_action_name, GTK_UI_MANAGER_MENUITEM, FALSE); g_slice_free (XklConfigItem, _item); } g_slist_free (variants); } } static guint create_layouts_menu (Eekboard *eekboard) { CreateMenuCallbackData data; data.eekboard = eekboard; data.merge_id = gtk_ui_manager_new_merge_id (eekboard->ui_manager); data.action_group = eekboard->layouts_action_group; xkl_config_registry_foreach_layout (eekboard->registry, layout_callback, &data); return data.merge_id; } static void on_layouts_menu (GtkAction *action, GtkWidget *window) { Eekboard *eekboard = g_object_get_data (G_OBJECT(window), "eekboard"); if (eekboard->layouts_merge_id == 0) eekboard->layouts_merge_id = create_layouts_menu (eekboard); } static void collect_option (XklConfigRegistry *registry, const XklConfigItem *option, gpointer user_data) { GSList **r_options = user_data; XklConfigItem *item; item = g_slice_dup (XklConfigItem, option); *r_options = g_slist_prepend (*r_options, item); } static void option_group_callback (XklConfigRegistry *registry, const XklConfigItem *item, gpointer user_data) { CreateMenuCallbackData *data = user_data; GtkAction *action; GSList *options = NULL, *head; gchar option_group_action_name[128], option_action_name[128]; gchar option_group_path[128]; SetConfigCallbackData *config; g_snprintf (option_group_action_name, sizeof (option_group_action_name), "OptionGroup %s", item->name); action = gtk_action_new (option_group_action_name, item->description, NULL, NULL); gtk_action_group_add_action (data->action_group, action); g_object_unref (action); xkl_config_registry_foreach_option (registry, item->name, collect_option, &options); g_return_if_fail (options); gtk_ui_manager_add_ui (data->eekboard->ui_manager, data->merge_id, OPTIONS_UI_PATH, option_group_action_name, option_group_action_name, GTK_UI_MANAGER_MENU, FALSE); g_snprintf (option_group_path, sizeof (option_group_path), OPTIONS_UI_PATH "/%s", option_group_action_name); for (head = options; head; head = head->next) { XklConfigItem *_item = head->data; GtkToggleAction *toggle; g_snprintf (option_action_name, sizeof (option_action_name), "SetOption %s", _item->name); toggle = gtk_toggle_action_new (option_action_name, _item->description, NULL, NULL); config = g_slice_new (SetConfigCallbackData); config->eekboard = data->eekboard; config->config = xkl_config_rec_new (); config->config->options = g_new0 (char *, 2); config->config->options[0] = g_strdup (_item->name); config->config->options[1] = NULL; g_signal_connect (toggle, "toggled", G_CALLBACK (on_option_toggled), config); gtk_action_group_add_action (data->action_group, GTK_ACTION(toggle)); g_object_unref (toggle); gtk_ui_manager_add_ui (data->eekboard->ui_manager, data->merge_id, option_group_path, option_action_name, option_action_name, GTK_UI_MANAGER_MENUITEM, FALSE); g_slice_free (XklConfigItem, _item); } g_slist_free (options); } static guint create_options_menu (Eekboard *eekboard) { CreateMenuCallbackData data; data.eekboard = eekboard; data.merge_id = gtk_ui_manager_new_merge_id (eekboard->ui_manager); data.action_group = eekboard->options_action_group; xkl_config_registry_foreach_option_group (eekboard->registry, option_group_callback, &data); return data.merge_id; } static void on_options_menu (GtkAction *action, GtkWidget *window) { Eekboard *eekboard = g_object_get_data (G_OBJECT(window), "eekboard"); if (eekboard->options_merge_id == 0) eekboard->options_merge_id = create_options_menu (eekboard); } static void create_menus (Eekboard *eekboard, GtkWidget *window) { GtkActionGroup *action_group; action_group = gtk_action_group_new ("MenuActions"); gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE); gtk_action_group_add_actions (action_group, action_entry, G_N_ELEMENTS (action_entry), window); gtk_action_group_add_toggle_actions (action_group, toggle_action_entry, G_N_ELEMENTS (toggle_action_entry), window); gtk_ui_manager_insert_action_group (eekboard->ui_manager, action_group, 0); gtk_ui_manager_add_ui_from_string (eekboard->ui_manager, ui_description, -1, NULL); eekboard->countries_action_group = gtk_action_group_new ("Countries"); gtk_ui_manager_insert_action_group (eekboard->ui_manager, eekboard->countries_action_group, -1); eekboard->languages_action_group = gtk_action_group_new ("Languages"); gtk_ui_manager_insert_action_group (eekboard->ui_manager, eekboard->languages_action_group, -1); eekboard->models_action_group = gtk_action_group_new ("Models"); gtk_ui_manager_insert_action_group (eekboard->ui_manager, eekboard->models_action_group, -1); eekboard->layouts_action_group = gtk_action_group_new ("Layouts"); gtk_ui_manager_insert_action_group (eekboard->ui_manager, eekboard->layouts_action_group, -1); eekboard->options_action_group = gtk_action_group_new ("Options"); gtk_ui_manager_insert_action_group (eekboard->ui_manager, eekboard->options_action_group, -1); } static GtkWidget * create_widget_gtk (Eekboard *eekboard, gint initial_width, gint initial_height) { EekBounds bounds; bounds.x = bounds.y = 0; bounds.width = initial_width; bounds.height = initial_height; eekboard->keyboard = eek_gtk_keyboard_new (); eek_keyboard_set_layout (eekboard->keyboard, eekboard->layout); eek_element_set_bounds (EEK_ELEMENT(eekboard->keyboard), &bounds); g_signal_connect (eekboard->keyboard, "key-pressed", G_CALLBACK(on_key_pressed), eekboard); g_signal_connect (eekboard->keyboard, "key-released", G_CALLBACK(on_key_released), eekboard); eekboard->widget = eek_gtk_keyboard_get_widget (EEK_GTK_KEYBOARD (eekboard->keyboard)); eek_element_get_bounds (EEK_ELEMENT(eekboard->keyboard), &bounds); eekboard->width = bounds.width; eekboard->height = bounds.height; return eekboard->widget; } #if HAVE_CLUTTER_GTK #if NEED_SWAP_EVENT_WORKAROUND static GdkFilterReturn gtk_clutter_filter_func (GdkXEvent *native_event, GdkEvent *event, gpointer user_data) { XEvent *xevent = native_event; clutter_x11_handle_event (xevent); return GDK_FILTER_CONTINUE; } static void on_gtk_clutter_embed_realize (GtkWidget *widget, gpointer user_data) { gdk_window_add_filter (NULL, gtk_clutter_filter_func, widget); } #endif static GtkWidget * create_widget_clutter (Eekboard *eekboard, gint initial_width, gint initial_height) { ClutterActor *stage, *actor; ClutterColor stage_color = { 0xff, 0xff, 0xff, 0xff }; EekBounds bounds; bounds.x = bounds.y = 0; bounds.width = initial_width; bounds.height = initial_height; eekboard->keyboard = eek_clutter_keyboard_new (); eek_keyboard_set_layout (eekboard->keyboard, eekboard->layout); eek_element_set_bounds (EEK_ELEMENT(eekboard->keyboard), &bounds); g_signal_connect (eekboard->keyboard, "key-pressed", G_CALLBACK(on_key_pressed), eekboard); g_signal_connect (eekboard->keyboard, "key-released", G_CALLBACK(on_key_released), eekboard); eekboard->widget = gtk_clutter_embed_new (); #if NEED_SWAP_EVENT_WORKAROUND if (eekboard->need_swap_event_workaround) g_signal_connect (eekboard->widget, "realize", G_CALLBACK(on_gtk_clutter_embed_realize), NULL); #endif stage = gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED(eekboard->widget)); clutter_stage_set_color (CLUTTER_STAGE(stage), &stage_color); clutter_stage_set_user_resizable (CLUTTER_STAGE (stage), TRUE); actor = eek_clutter_keyboard_get_actor (EEK_CLUTTER_KEYBOARD(eekboard->keyboard)); clutter_container_add_actor (CLUTTER_CONTAINER(stage), actor); eek_element_get_bounds (EEK_ELEMENT(eekboard->keyboard), &bounds); clutter_actor_set_size (stage, bounds.width, bounds.height); eekboard->width = bounds.width; eekboard->height = bounds.height; return eekboard->widget; } static GtkWidget * create_widget (Eekboard *eekboard, gint initial_width, gint initial_height) { if (eekboard->use_clutter) return create_widget_clutter (eekboard, initial_width, initial_height); else return create_widget_gtk (eekboard, initial_width, initial_height); } #endif Eekboard * eekboard_new (gboolean use_clutter, gboolean need_swap_event_workaround, gboolean accessibility_enabled) { Eekboard *eekboard; eekboard = g_slice_new0 (Eekboard); eekboard->use_clutter = use_clutter; eekboard->need_swap_event_workaround = need_swap_event_workaround; eekboard->accessibility_enabled = accessibility_enabled; eekboard->display = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()); if (!eekboard->display) { g_slice_free (Eekboard, eekboard); g_warning ("Can't open display"); return NULL; } eekboard->fakekey = fakekey_init (eekboard->display); if (!eekboard->fakekey) { g_slice_free (Eekboard, eekboard); g_warning ("Can't init fakekey"); return NULL; } eekboard->layout = eek_xkl_layout_new (); if (!eekboard->layout) { g_slice_free (Eekboard, eekboard); g_warning ("Can't create layout"); return NULL; } if (opt_model) eek_xkl_layout_set_model (EEK_XKL_LAYOUT(eekboard->layout), opt_model); if (opt_layouts) { gchar **layouts, **variants; gint i; layouts = g_strsplit (opt_layouts, ",", -1); variants = g_strdupv (layouts); for (i = 0; layouts[i]; i++) { gchar *layout = layouts[i], *variant = variants[i], *variant_start, *variant_end; variant_start = strchr (layout, '('); variant_end = strrchr (layout, ')'); if (variant_start && variant_end) { *variant_start++ = '\0'; g_strlcpy (variant, variant_start, variant_end - variant_start + 1); } else *variant = '\0'; } eek_xkl_layout_set_layouts (EEK_XKL_LAYOUT(eekboard->layout), layouts); g_strfreev (layouts); g_strfreev (variants); } if (opt_options) { gchar **options; options = g_strsplit (opt_options, ",", -1); eek_xkl_layout_set_options (EEK_XKL_LAYOUT(eekboard->layout), options); g_strfreev (options); } g_signal_connect (eekboard->layout, "changed", G_CALLBACK(on_changed), eekboard); eekboard->ui_manager = gtk_ui_manager_new (); eekboard->engine = xkl_engine_get_instance (eekboard->display); eekboard->registry = xkl_config_registry_get_instance (eekboard->engine); xkl_config_registry_load (eekboard->registry, FALSE); return eekboard; } static void eekboard_free (Eekboard *eekboard) { if (eekboard->layout) g_object_unref (eekboard->layout); #if 0 if (eekboard->keyboard) g_object_unref (eekboard->keyboard); #endif if (eekboard->registry) g_object_unref (eekboard->registry); if (eekboard->engine) g_object_unref (eekboard->engine); g_slice_free (Eekboard, eekboard); } static void print_layout (XklConfigRegistry *registry, const XklConfigItem *item, gpointer user_data) { GSList *variants = NULL; xkl_config_registry_foreach_layout_variant (registry, item->name, collect_variant, &variants); if (!variants) printf ("%s: %s\n", item->name, item->description); else { GSList *head; for (head = variants; head; head = head->next) { XklConfigItem *_item = head->data; printf ("%s(%s): %s %s\n", item->name, _item->name, item->description, _item->description); g_slice_free (XklConfigItem, _item); } g_slist_free (variants); } } static void print_item (XklConfigRegistry *registry, const XklConfigItem *item, gpointer user_data) { printf ("%s: %s\n", item->name, item->description); } static void print_option_group (XklConfigRegistry *registry, const XklConfigItem *item, gpointer user_data) { xkl_config_registry_foreach_option (registry, item->name, print_item, NULL); } static void on_notify_never_show (NotifyNotification *notification, char *action, gpointer user_data) { Eekboard *eekboard = user_data; GError *error; gconf_client_set_bool (eekboard->gconfc, "/apps/eekboard/inhibit-startup-notify", TRUE, &error); } int main (int argc, char *argv[]) { const gchar *env; gboolean use_clutter = USE_CLUTTER; gboolean need_swap_event_workaround = FALSE; gboolean accessibility_enabled = FALSE; Eekboard *eekboard; GtkWidget *widget, *vbox, *menubar, *window; GOptionContext *context; GConfClient *gconfc; GError *error; context = g_option_context_new ("eekboard"); g_option_context_add_main_entries (context, options, NULL); g_option_context_parse (context, &argc, &argv, NULL); g_option_context_free (context); if (opt_version) { g_print ("eekboard %s\n", VERSION); exit (0); } #ifdef ENABLE_NLS bindtextdomain (GETTEXT_PACKAGE, EEKBOARD_LOCALEDIR); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); #endif error = NULL; gconfc = gconf_client_get_default (); if (gconf_client_get_bool (gconfc, "/desktop/gnome/interface/accessibility", &error) || gconf_client_get_bool (gconfc, "/desktop/gnome/interface/accessibility2", &error)) { if (SPI_init () == 0) accessibility_enabled = TRUE; else g_warning("AT-SPI initialization failed"); } env = g_getenv ("EEKBOARD_DISABLE_CLUTTER"); if (env && g_strcmp0 (env, "1") == 0) use_clutter = FALSE; #if HAVE_CLUTTER_GTK if (use_clutter && gtk_clutter_init (&argc, &argv) != CLUTTER_INIT_SUCCESS) { g_warning ("Can't init Clutter-Gtk...fallback to GTK"); use_clutter = FALSE; } #ifdef NEED_SWAP_EVENT_WORKAROUND if (use_clutter && clutter_feature_available (CLUTTER_FEATURE_SWAP_EVENTS)) { g_warning ("Enabling GLX_INTEL_swap_event workaround for Clutter-Gtk"); need_swap_event_workaround = TRUE; } #endif #endif if (!use_clutter && !gtk_init_check (&argc, &argv)) { g_warning ("Can't init GTK"); exit (1); } eekboard = eekboard_new (use_clutter, need_swap_event_workaround, accessibility_enabled); if (opt_list_models) { xkl_config_registry_foreach_model (eekboard->registry, print_item, NULL); eekboard_free (eekboard); exit (0); } if (opt_list_layouts) { xkl_config_registry_foreach_layout (eekboard->registry, print_layout, NULL); eekboard_free (eekboard); exit (0); } if (opt_list_options) { xkl_config_registry_foreach_option_group (eekboard->registry, print_option_group, NULL); eekboard_free (eekboard); exit (0); } window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_widget_set_can_focus (window, FALSE); g_object_set (G_OBJECT(window), "accept_focus", FALSE, NULL); gtk_window_set_title (GTK_WINDOW(window), "Keyboard"); g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (gtk_main_quit), NULL); vbox = gtk_vbox_new (FALSE, 0); g_object_set_data (G_OBJECT(window), "eekboard", eekboard); widget = create_widget (eekboard, CSW, CSH); create_menus (eekboard, window); menubar = gtk_ui_manager_get_widget (eekboard->ui_manager, "/MainMenu"); gtk_box_pack_start (GTK_BOX (vbox), menubar, FALSE, FALSE, 0); gtk_container_add (GTK_CONTAINER(vbox), widget); gtk_container_add (GTK_CONTAINER(window), vbox); gtk_widget_set_size_request (widget, eekboard->width, eekboard->height); gtk_widget_show_all (window); gtk_widget_set_size_request (widget, -1, -1); notify_init ("eekboard"); eekboard->window = window; eekboard->gconfc = gconfc; if (eekboard->accessibility_enabled) { NotifyNotification *notification; error = NULL; if (!gconf_client_get_bool (eekboard->gconfc, "/apps/eekboard/inhibit-startup-notify", &error)) { notification = notify_notification_new ("eekboard started in background", "As GNOME accessibility support enabled, " "eekboard is starting without a window.\n" "To make eekboard show up, click on some window with " "an editable widget.", NULL, NULL); notify_notification_add_action (notification, "dont-ask", "Don't show up", NOTIFY_ACTION_CALLBACK(on_notify_never_show), eekboard, NULL); error = NULL; notify_notification_show (notification, &error); } gtk_widget_hide (window); focusListener = SPI_createAccessibleEventListener (a11y_focus_listener, eekboard); SPI_registerGlobalEventListener (focusListener, "object:state-changed:focused"); } gtk_main (); return 0; }