1697 lines
58 KiB
C
1697 lines
58 KiB
C
/*
|
|
* Copyright (C) 2010-2011 Daiki Ueno <ueno@unixuser.org>
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif /* HAVE_CONFIG_H */
|
|
|
|
#include <glib/gi18n.h>
|
|
#include <gdk/gdkx.h>
|
|
#include <gtk/gtk.h>
|
|
#include <gconf/gconf-client.h>
|
|
#include <libxklavier/xklavier.h>
|
|
#include <fakekey/fakekey.h>
|
|
#include <cspi/spi.h>
|
|
#include <libnotify/notify.h>
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
|
|
#include "eek/eek-gtk.h"
|
|
#include "eek/eek-xkl.h"
|
|
|
|
#if HAVE_CLUTTER_GTK
|
|
#include <clutter-gtk/clutter-gtk.h>
|
|
#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 <http://www.gnu.org/licenses/>. " \
|
|
|
|
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[] =
|
|
"<ui>"
|
|
" <menubar name='MainMenu'>"
|
|
" <menu action='FileMenu'>"
|
|
" <menuitem action='Quit'/>"
|
|
" </menu>"
|
|
" <menu action='KeyboardMenu'>"
|
|
" <menu action='Country'>"
|
|
" <placeholder name='CountriesPH'/>"
|
|
" </menu>"
|
|
" <menu action='Language'>"
|
|
" <placeholder name='LanguagesPH'/>"
|
|
" </menu>"
|
|
" <separator/>"
|
|
" <menu action='Model'>"
|
|
" <placeholder name='ModelsPH'/>"
|
|
" </menu>"
|
|
" <menu action='Layout'>"
|
|
" <placeholder name='LayoutsPH'/>"
|
|
" </menu>"
|
|
" <menu action='Option'>"
|
|
" <placeholder name='OptionsPH'/>"
|
|
" </menu>"
|
|
" </menu>"
|
|
" <menu action='HelpMenu'>"
|
|
" <menuitem action='About'/>"
|
|
" </menu>"
|
|
" </menubar>"
|
|
"</ui>";
|
|
|
|
#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;
|
|
}
|