/* * Copyright (C) 2010-2011 Daiki Ueno * Copyright (C) 2010-2011 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 */ #include #include #include #include #include #include #include #include #include #include #include #include "eek/eek-gtk.h" #include "eek/eek-xkl.h" #if HAVE_CLUTTER_GTK #include #include "eek/eek-clutter.h" #endif #define CSW 640 #define CSH 480 #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 _Config { gchar *name; XklConfigRec *rec; }; typedef struct _Config Config; struct _Eekboard { gboolean accessibility_enabled; Config **config; gint active_config; Display *display; FakeKey *fakekey; GConfClient *gconfc; Accessible *acc; GtkWidget *widget, *window, *combo; XklEngine *engine; XklConfigRegistry *registry; GtkUIManager *ui_manager; gulong on_key_pressed_id, on_key_released_id; #if HAVE_CLUTTER_GTK ClutterActor *actor; #endif 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; }; 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_from_menu (GtkAction * action, GtkWidget *window); static void eekboard_free (Eekboard *eekboard); static void create_widget (Eekboard *eekboard); static void update_widget (Eekboard *eekboard); 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_from_menu)}, {"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 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 gboolean opt_popup = FALSE; static gchar *opt_config = NULL; static gboolean opt_fullscreen = FALSE; static gchar *opt_xml = NULL; 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")}, {"popup", 'p', 0, G_OPTION_ARG_NONE, &opt_popup, N_("Start as a popup window")}, {"fullscreen", 'f', 0, G_OPTION_ARG_NONE, &opt_fullscreen, N_("Start in fullscreen mode")}, {"config", 'c', 0, G_OPTION_ARG_STRING, &opt_config, N_("Specify configuration file")}, {"xml", '\0', 0, G_OPTION_ARG_STRING, &opt_xml, N_("Dump the keyboard in XML")}, {"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_destroy (gpointer user_data) { gtk_main_quit (); } static void on_quit_from_menu (GtkAction * action, GtkWidget *window) { gtk_main_quit (); } static void make_fullscreen (Eekboard *eekboard) { AccessibleComponent *component = Accessible_getComponent (eekboard->acc); long int x, y, width, height; GdkScreen *screen; gint monitor; GdkRectangle rect; screen = gdk_screen_get_default (); if (eekboard->acc) { AccessibleComponent_getExtents (component, &x, &y, &width, &height, SPI_COORD_TYPE_SCREEN); monitor = gdk_screen_get_monitor_at_point (screen, x, y); } else { GdkWindow *root; root = gtk_widget_get_root_window (GTK_WIDGET(eekboard->widget)); monitor = gdk_screen_get_monitor_at_window (screen, root); } gdk_screen_get_monitor_geometry (screen, monitor, &rect); gtk_window_move (GTK_WINDOW(eekboard->window), rect.x, rect.y + rect.height / 2); gtk_window_resize (GTK_WINDOW(eekboard->window), rect.width, rect.height / 2); } static void make_popup (Eekboard *eekboard) { AccessibleComponent *component = Accessible_getComponent (eekboard->acc); long int x, y, width, height; AccessibleComponent_getExtents (component, &x, &y, &width, &height, SPI_COORD_TYPE_SCREEN); gtk_window_move (GTK_WINDOW(eekboard->window), x, y + height); } static void eekboard_show (Eekboard *eekboard) { gdouble transparency; GError *error; gtk_widget_show (eekboard->window); if (opt_fullscreen) make_fullscreen (eekboard); else if (opt_popup) make_popup (eekboard); error = NULL; transparency = gconf_client_get_float (eekboard->gconfc, "/apps/eekboard/transparency", &error); gtk_window_set_opacity (GTK_WINDOW(eekboard->window), 1.0 - transparency); } 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); AccessibleRole role = Accessible_getRole (acc); /* Ignore focus on eekboard itself since eekboard itself has GTK+ widgets. */ if (gtk_widget_has_focus (eekboard->window)) return FALSE; /* The logic is borrowed from Caribou. */ if (AccessibleStateSet_contains (state_set, SPI_STATE_EDITABLE) || role == SPI_ROLE_TERMINAL) { switch (role) { case SPI_ROLE_TEXT: case SPI_ROLE_PARAGRAPH: case SPI_ROLE_PASSWORD_TEXT: case SPI_ROLE_TERMINAL: if (strncmp (event->type, "focus", 5) == 0 || event->detail1 == 1) { eekboard->acc = acc; eekboard_show (eekboard); } else if (event->detail1 == 0 && acc == eekboard->acc) { eekboard->acc = NULL; gtk_widget_hide (eekboard->window); } case SPI_ROLE_ENTRY: if (strncmp (event->type, "focus", 5) == 0 || event->detail1 == 1) { eekboard->acc = acc; eekboard_show (eekboard); } else if (event->detail1 == 0 && acc == eekboard->acc) { eekboard->acc = NULL; gtk_widget_hide (eekboard->window); } default: ; } } return FALSE; } static SPIBoolean a11y_keystroke_listener (const AccessibleKeystroke *stroke, void *user_data) { Eekboard *eekboard = user_data; EekKey *key; EekSymbol *symbol; guint ignored_keysyms[] = {XK_Shift_L, XK_Shift_R, XK_ISO_Level3_Shift, XK_Control_L, XK_Control_R, XK_Alt_L, XK_Alt_R}; gint i; key = eek_keyboard_find_key_by_keycode (eekboard->keyboard, stroke->keycode); if (!key) return FALSE; /* XXX: Ignore modifier keys since there is no way to receive SPI_KEY_RELEASED event for them. */ symbol = eek_key_get_symbol_with_fallback (key, -1, 0); if (!symbol) return FALSE; for (i = 0; i < G_N_ELEMENTS(ignored_keysyms); i++) if (EEK_IS_KEYSYM(symbol) && eek_keysym_get_xkeysym (EEK_KEYSYM(symbol)) == ignored_keysyms[i]) break; if (i != G_N_ELEMENTS(ignored_keysyms)) return FALSE; if (stroke->type == SPI_KEY_PRESSED) { g_signal_handler_block (eekboard->keyboard, eekboard->on_key_pressed_id); g_signal_emit_by_name (key, "pressed"); g_signal_handler_unblock (eekboard->keyboard, eekboard->on_key_pressed_id); } else { g_signal_handler_block (eekboard->keyboard, eekboard->on_key_released_id); g_signal_emit_by_name (key, "released"); g_signal_handler_unblock (eekboard->keyboard, eekboard->on_key_released_id); } return TRUE; } static AccessibleEventListener* focusListener; static AccessibleEventListener* keystrokeListener; static void on_key_pressed (EekKeyboard *keyboard, EekKey *key, gpointer user_data) { Eekboard *eekboard = user_data; EekSymbol *symbol; symbol = eek_key_get_symbol_with_fallback (key, -1, 0); if (!symbol) return; EEKBOARD_NOTE("%s %X", eek_symbol_get_name (symbol), eek_keyboard_get_modifiers (keyboard)); if (!eek_symbol_is_modifier (symbol) && EEK_IS_KEYSYM(symbol)) fakekey_press_keysym (eekboard->fakekey, eek_keysym_get_xkeysym (EEK_KEYSYM(symbol)), eek_keyboard_get_modifiers (keyboard)); } 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; EekLayout *layout = eek_keyboard_get_layout (data->eekboard->keyboard); if (eek_xkl_layout_set_config (EEK_XKL_LAYOUT(layout), data->config)) update_widget (data->eekboard); } static void on_option_toggled (GtkToggleAction *action, gpointer user_data) { SetConfigCallbackData *data = user_data; EekLayout *layout = eek_keyboard_get_layout (data->eekboard->keyboard); if (gtk_toggle_action_get_active (action)) { if (eek_xkl_layout_enable_option (EEK_XKL_LAYOUT(layout), data->config->options[0])) update_widget (data->eekboard); } else { if (eek_xkl_layout_disable_option (EEK_XKL_LAYOUT(layout), data->config->options[0])) update_widget (data->eekboard); } } static void update_widget (Eekboard *eekboard) { GtkWidget *vbox; GtkAllocation allocation; EekLayout *layout; EekKeyboard *keyboard; 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); layout = eek_keyboard_get_layout (eekboard->keyboard); keyboard = eek_keyboard_new (layout, allocation.width, allocation.height); eek_keyboard_set_modifier_behavior (keyboard, EEK_MODIFIER_BEHAVIOR_LATCH); g_object_unref (eekboard->keyboard); eekboard->keyboard = keyboard; create_widget (eekboard); gtk_container_add (GTK_CONTAINER(vbox), eekboard->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_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); g_object_unref (action_group); 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); } #if HAVE_CLUTTER_GTK static void on_allocation_changed (ClutterActor *stage, ClutterActorBox *box, ClutterAllocationFlags flags, gpointer user_data) { Eekboard *eekboard = user_data; EekBounds bounds; gfloat scale; eek_element_get_bounds (EEK_ELEMENT(eekboard->keyboard), &bounds); scale = MIN((box->x2 - box->x1) / bounds.width, (box->y2 - box->y1) / bounds.height); clutter_actor_set_size (eekboard->actor, bounds.width * scale, bounds.height * scale); } #endif static void create_widget (Eekboard *eekboard) { #if HAVE_CLUTTER_GTK ClutterActor *stage; ClutterColor stage_color = { 0xff, 0xff, 0xff, 0xff }; #endif EekBounds bounds; eekboard->on_key_pressed_id = g_signal_connect (eekboard->keyboard, "key-pressed", G_CALLBACK(on_key_pressed), eekboard); eekboard->on_key_released_id = g_signal_connect (eekboard->keyboard, "key-released", G_CALLBACK(on_key_released), eekboard); eek_element_get_bounds (EEK_ELEMENT(eekboard->keyboard), &bounds); #if HAVE_CLUTTER_GTK eekboard->widget = gtk_clutter_embed_new (); stage = gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED(eekboard->widget)); eekboard->actor = eek_clutter_keyboard_new (eekboard->keyboard); clutter_container_add_actor (CLUTTER_CONTAINER(stage), eekboard->actor); clutter_stage_set_color (CLUTTER_STAGE(stage), &stage_color); clutter_stage_set_user_resizable (CLUTTER_STAGE(stage), TRUE); clutter_stage_set_minimum_size (CLUTTER_STAGE(stage), bounds.width / 3, bounds.height / 3); g_signal_connect (stage, "allocation-changed", G_CALLBACK(on_allocation_changed), eekboard); #else eekboard->widget = eek_gtk_keyboard_new (eekboard->keyboard); #endif gtk_widget_set_size_request (eekboard->widget, bounds.width, bounds.height); } static void parse_layouts (XklConfigRec *rec, const gchar *_layouts) { gchar **layouts, **variants; gint i; layouts = g_strsplit (_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'; } rec->layouts = layouts; rec->variants = variants; } static GdkFilterReturn filter_xkl_event (GdkXEvent * xev, GdkEvent * event, gpointer user_data) { XEvent *xevent = (XEvent *) xev; Eekboard *eekboard = user_data; xkl_engine_filter_events (eekboard->engine, xevent); return GDK_FILTER_CONTINUE; } static void on_xkl_config_changed (XklEngine *xklengine, gpointer user_data) { } static void on_xkl_state_changed (XklEngine *xklengine, XklEngineStateChange type, gint value, gboolean restore, gpointer user_data) { } Eekboard * eekboard_new (gboolean accessibility_enabled) { Eekboard *eekboard; EekLayout *layout; eekboard = g_slice_new0 (Eekboard); eekboard->accessibility_enabled = accessibility_enabled; eekboard->active_config = -1; 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; } if (opt_xml) { GFile *file; GFileInputStream *input; GError *error; file = g_file_new_for_path (opt_xml); error = NULL; input = g_file_read (file, NULL, &error); layout = eek_xml_layout_new (G_INPUT_STREAM(input)); g_object_unref (input); } else layout = eek_xkl_layout_new (); if (!layout) { g_slice_free (Eekboard, eekboard); g_warning ("Can't create layout"); return NULL; } if (!opt_xml) { if (opt_model) eek_xkl_layout_set_model (EEK_XKL_LAYOUT(layout), opt_model); if (opt_layouts) { XklConfigRec *rec = xkl_config_rec_new (); parse_layouts (rec, opt_layouts); eek_xkl_layout_set_layouts (EEK_XKL_LAYOUT(layout), rec->layouts); eek_xkl_layout_set_variants (EEK_XKL_LAYOUT(layout), rec->variants); g_object_unref (rec); } if (opt_options) { gchar **options; options = g_strsplit (opt_options, ",", -1); eek_xkl_layout_set_options (EEK_XKL_LAYOUT(layout), options); g_strfreev (options); } eekboard->engine = xkl_engine_get_instance (eekboard->display); eekboard->registry = xkl_config_registry_get_instance (eekboard->engine); xkl_config_registry_load (eekboard->registry, FALSE); g_signal_connect (eekboard->engine, "X-config-changed", G_CALLBACK(on_xkl_config_changed), eekboard); g_signal_connect (eekboard->engine, "X-state-changed", G_CALLBACK(on_xkl_state_changed), eekboard); xkl_engine_start_listen (eekboard->engine, XKLL_TRACK_KEYBOARD_STATE); } eekboard->keyboard = eek_keyboard_new (layout, CSW, CSH); eek_keyboard_set_modifier_behavior (eekboard->keyboard, EEK_MODIFIER_BEHAVIOR_LATCH); eekboard->ui_manager = gtk_ui_manager_new (); if (eekboard->engine) { gdk_window_add_filter (NULL, (GdkFilterFunc) filter_xkl_event, eekboard); gdk_window_add_filter (gdk_get_default_root_window (), (GdkFilterFunc) filter_xkl_event, eekboard); } return eekboard; } static void eekboard_free (Eekboard *eekboard) { if (eekboard->keyboard) g_object_unref (eekboard->keyboard); if (eekboard->registry) g_object_unref (eekboard->registry); if (eekboard->engine) g_object_unref (eekboard->engine); if (eekboard->gconfc) g_object_unref (eekboard->gconfc); if (eekboard->ui_manager) g_object_unref (eekboard->ui_manager); if (eekboard->countries_action_group) g_object_unref (eekboard->countries_action_group); if (eekboard->languages_action_group) g_object_unref (eekboard->languages_action_group); if (eekboard->models_action_group) g_object_unref (eekboard->models_action_group); if (eekboard->layouts_action_group) g_object_unref (eekboard->layouts_action_group); if (eekboard->options_action_group) g_object_unref (eekboard->options_action_group); 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 = NULL; gconf_client_set_bool (eekboard->gconfc, "/apps/eekboard/inhibit-startup-notify", TRUE, &error); } static void on_layout_changed (GtkComboBox *combo, gpointer user_data) { Eekboard *eekboard = user_data; GtkTreeModel *model; GtkTreeIter iter; gint active; model = gtk_combo_box_get_model (combo); gtk_combo_box_get_active_iter (combo, &iter); gtk_tree_model_get (model, &iter, 1, &active, -1); if (eekboard->active_config != active) { XklConfigRec *config, *config_base = eekboard->config[active]->rec; EekLayout *layout; layout = eek_keyboard_get_layout (eekboard->keyboard); config = xkl_config_rec_new (); if (config_base->model) config->model = g_strdup (config_base->model); else config->model = eek_xkl_layout_get_model (EEK_XKL_LAYOUT(layout)); if (config_base->layouts) config->layouts = g_strdupv (config_base->layouts); else config->layouts = eek_xkl_layout_get_layouts (EEK_XKL_LAYOUT(layout)); if (config_base->variants) config->variants = g_strdupv (config_base->variants); else config->variants = eek_xkl_layout_get_variants (EEK_XKL_LAYOUT(layout)); if (config_base->options) config->options = g_strdupv (config_base->options); else config->options = eek_xkl_layout_get_options (EEK_XKL_LAYOUT(layout)); eek_xkl_layout_set_config (EEK_XKL_LAYOUT(layout), config); g_object_unref (config); eekboard->active_config = active; } } struct _ConfigContext { GSList *list; GString *text; }; typedef struct _ConfigContext ConfigContext; static void config_parser_start_element (GMarkupParseContext *pcontext, const gchar *element_name, const gchar **attribute_names, const gchar **attribute_values, gpointer user_data, GError **error) { ConfigContext *context = user_data; if (g_strcmp0 (element_name, "config") == 0) { Config *config = g_slice_new0 (Config); config->rec = xkl_config_rec_new (); context->list = g_slist_prepend (context->list, config); } else context->text->len = 0; } static void config_parser_end_element (GMarkupParseContext *pcontext, const gchar *element_name, gpointer user_data, GError **error) { ConfigContext *context = user_data; Config *config = context->list->data; gchar *text; if (g_strcmp0 (element_name, "config") == 0 && !config->name) { if (error) *error = g_error_new (G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "\"name\" is missing"); return; } if (!context->text) return; text = g_strndup (context->text->str, context->text->len); if (g_strcmp0 (element_name, "name") == 0) config->name = text; else if (g_strcmp0 (element_name, "model") == 0) config->rec->model = text; else if (g_strcmp0 (element_name, "layouts") == 0) parse_layouts (config->rec, text); else if (g_strcmp0 (element_name, "options") == 0) config->rec->options = g_strsplit (text, ",", -1); } static void config_parser_text (GMarkupParseContext *pcontext, const gchar *text, gsize text_len, gpointer user_data, GError **error) { ConfigContext *context = user_data; context->text = g_string_append_len (context->text, text, text_len); } GMarkupParser config_parser = { .start_element = config_parser_start_element, .end_element = config_parser_end_element, .text = config_parser_text }; int main (int argc, char *argv[]) { gboolean accessibility_enabled = FALSE; Eekboard *eekboard; GtkWidget *vbox, *menubar, *window, *combo = NULL; GOptionContext *context; GConfClient *gconfc; GError *error; g_log_set_always_fatal (G_LOG_LEVEL_CRITICAL); 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"); } #if HAVE_CLUTTER_GTK if (gtk_clutter_init (&argc, &argv) != CLUTTER_INIT_SUCCESS) { g_warning ("Can't init GTK with Clutter"); exit (1); } #else if (!gtk_init_check (&argc, &argv)) { g_warning ("Can't init GTK"); exit (1); } #endif eekboard = eekboard_new (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); } if (opt_config) { ConfigContext context; GMarkupParseContext *pcontext; GFile *file; GError *error; GFileInputStream *stream; gchar buf[BUFSIZ]; GSList *head; gint i; file = g_file_new_for_path (opt_config); error = NULL; stream = g_file_read (file, NULL, &error); if (!stream) { eekboard_free (eekboard); g_print ("Can't read configuration file: %s\n", opt_config); exit (1); } context.list = NULL; context.text = g_string_sized_new (BUFSIZ); pcontext = g_markup_parse_context_new (&config_parser, 0, &context, NULL); while (1) { gssize len; error = NULL; len = g_input_stream_read (G_INPUT_STREAM(stream), buf, sizeof buf, NULL, &error); if (len <= 0) break; error = NULL; if (!g_markup_parse_context_parse (pcontext, buf, len, &error)) break; } g_object_unref (stream); error = NULL; g_markup_parse_context_end_parse (pcontext, &error); g_markup_parse_context_free (pcontext); g_string_free (context.text, TRUE); g_object_unref (file); if (context.list) { eekboard->config = g_slice_alloc0 ((g_slist_length (context.list) + 1) * sizeof (*eekboard->config)); for (i = 0, head = context.list; head; head = head->next) eekboard->config[i++] = head->data; } } window = gtk_window_new (opt_popup ? GTK_WINDOW_POPUP : 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 (on_destroy), eekboard); vbox = gtk_vbox_new (FALSE, 0); g_object_set_data (G_OBJECT(window), "eekboard", eekboard); create_widget (eekboard); if (!opt_popup) { 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); } if (eekboard->config) { GtkListStore *store; GtkTreeIter iter; GtkCellRenderer *renderer; int i; store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_INT); for (i = 0; eekboard->config[i]; i++) { gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, 0, eekboard->config[i]->name, 1, i, -1); } combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL(store)); renderer = gtk_cell_renderer_text_new (); gtk_cell_layout_pack_start (GTK_CELL_LAYOUT(combo), renderer, TRUE); gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT(combo), renderer, "text", 0, NULL); gtk_box_pack_end (GTK_BOX (vbox), combo, FALSE, FALSE, 0); g_signal_connect (combo, "changed", G_CALLBACK(on_layout_changed), eekboard); } gtk_container_add (GTK_CONTAINER(vbox), eekboard->widget); gtk_container_add (GTK_CONTAINER(window), vbox); gtk_widget_show_all (window); gtk_widget_set_size_request (eekboard->widget, -1, -1); notify_init ("eekboard"); eekboard->window = window; eekboard->gconfc = gconfc; if (eekboard->accessibility_enabled) { if (opt_popup) { 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.", "keyboard" #if NEED_LIBNOTIFY_ATTACH_WORKAROUND , NULL #endif ); 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 ((AccessibleEventListenerCB)a11y_focus_listener, eekboard); SPI_registerGlobalEventListener (focusListener, "object:state-changed:focused"); SPI_registerGlobalEventListener (focusListener, "focus:"); } /* monitor key events */ if (!keystrokeListener) { keystrokeListener = SPI_createAccessibleKeystrokeListener (a11y_keystroke_listener, eekboard); } 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"); } if (combo) gtk_combo_box_set_active (GTK_COMBO_BOX(combo), 0); gtk_main (); /* release the currently held key */ if (eekboard->fakekey) fakekey_release (eekboard->fakekey); eekboard_free (eekboard); return 0; }