From 6772bc0fc82074cd00d56c0e9795d163b489281e Mon Sep 17 00:00:00 2001 From: fujiwarat Date: Fri, 11 Jan 2013 22:55:17 +0900 Subject: [PATCH] Add ibus-xkb and libgnomekbd. --- bindings/vala/Gkbd-3.0.metadata | 1 + bindings/vala/Makefile.am | 15 ++ bindings/vala/Xkl-1.0.metadata | 3 + configure.ac | 40 ++++ data/ibus.schemas.in | 59 +++++ ibus-1.0.pc.in | 1 + ibus.spec.in | 12 ++ src/Makefile.am | 3 + src/ibus.h | 1 + src/ibusxkbxml.c | 466 ++++++++++++++++++++++++++++++++++++++++ src/ibusxkbxml.h | 187 ++++++++++++++++ ui/gtk3/Makefile.am | 36 ++++ ui/gtk3/gkbdlayout.vala.false | 63 ++++++ ui/gtk3/gkbdlayout.vala.true | 108 ++++++++++ ui/gtk3/panel.vala | 216 ++++++++++++++++++- ui/gtk3/xkblayout.vala | 431 +++++++++++++++++++++++++++++++++++++ 16 files changed, 1641 insertions(+), 1 deletion(-) create mode 100644 bindings/vala/Gkbd-3.0.metadata create mode 100644 bindings/vala/Xkl-1.0.metadata create mode 100644 src/ibusxkbxml.c create mode 100644 src/ibusxkbxml.h create mode 100644 ui/gtk3/gkbdlayout.vala.false create mode 100644 ui/gtk3/gkbdlayout.vala.true create mode 100644 ui/gtk3/xkblayout.vala diff --git a/bindings/vala/Gkbd-3.0.metadata b/bindings/vala/Gkbd-3.0.metadata new file mode 100644 index 0000000..661e6fd --- /dev/null +++ b/bindings/vala/Gkbd-3.0.metadata @@ -0,0 +1 @@ +Configuration cheader_filename="libgnomekbd/gkbd-configuration.h" diff --git a/bindings/vala/Makefile.am b/bindings/vala/Makefile.am index aba3454..bf3074a 100644 --- a/bindings/vala/Makefile.am +++ b/bindings/vala/Makefile.am @@ -28,6 +28,17 @@ vapi_deps = \ $(top_builddir)/src/IBus-1.0.gir \ $(NULL) +if ENABLE_LIBGNOMEKBD +vapi_deps += $(builddir)/gkbd.vapi + +$(builddir)/gkbd.vapi: + $(AM_V_GEN) $(VAPIGEN) --library gkbd \ + --metadatadir $(srcdir) \ + --pkg gtk+-3.0 --pkg glib-2.0 --pkg gmodule-2.0 \ + /usr/share/gir-1.0/Gkbd-3.0.gir + $(NULL) +endif + ibus-1.0.vapi: $(vapi_deps) VAPIGEN_VAPIS = ibus-1.0.vapi @@ -43,11 +54,15 @@ vapidir = $(datadir)/vala/vapi vapi_DATA = $(VAPIGEN_VAPIS) $(VAPIGEN_VAPIS:.vapi=.deps) EXTRA_DIST = \ + Gkbd-3.0.metadata \ IBus-1.0.metadata \ IBus-1.0-custom.vala \ ibus-1.0.deps \ config.vapi \ xi.vapi \ + Xkl-1.0.metadata \ $(NULL) +CLEANFILES = gkbd.vapi + -include $(top_srcdir)/git.mk diff --git a/bindings/vala/Xkl-1.0.metadata b/bindings/vala/Xkl-1.0.metadata new file mode 100644 index 0000000..4961d0c --- /dev/null +++ b/bindings/vala/Xkl-1.0.metadata @@ -0,0 +1,3 @@ +Xkl cheader_filename="libxklavier/xklavier.h" +Engine + .filter_events.evt ref type="X.Event" diff --git a/configure.ac b/configure.ac index b328073..61d1464 100644 --- a/configure.ac +++ b/configure.ac @@ -237,6 +237,45 @@ else enable_xim="no (disabled, use --enable-xim to enable)" fi +# Option for XKB command. +PKG_CHECK_MODULES(XKB, + [xkbfile],, + [XKB_LIBS="-lxkbfile"] +) + +# --enable-libgnomekbd option. +AC_ARG_ENABLE(libgnomekbd, + AS_HELP_STRING([--enable-libgnomekbd], + [Use libgnomekbd to handle the keymaps]), + [enable_libgnomekbd=$enableval], + [enable_libgnomekbd=no] +) +AM_CONDITIONAL([ENABLE_LIBGNOMEKBD], [test x"$enable_libgnomekbd" = x"yes"]) +if test x"$enable_libgnomekbd" = x"yes"; then + # check for libgnomekbd + PKG_CHECK_MODULES(LIBGNOMEKBDUI, [ + libgnomekbdui + ]) + PKG_CHECK_MODULES(ATK, [ + atk + ]) + HAVE_IBUS_GKBD=true +else + enable_libgnomekbd="no (disabled, use --enable-libgnomekbd to enable)" + HAVE_IBUS_GKBD=false +fi +AC_SUBST(HAVE_IBUS_GKBD) + +# Define XKB rules file +AC_ARG_WITH(xkb-rules-xml, + AS_HELP_STRING([--with-xkb-rules-xml[=$DIR/evdev.xml]], + [Set evdev.xml file path (default: /usr/share/X11/xkb/rules/evdev.xml)]), + XKB_RULES_XML_FILE=$with_xkb_rules_xml, + XKB_RULES_XML_FILE="/usr/share/X11/xkb/rules/evdev.xml" +) +AC_DEFINE_UNQUOTED(XKB_RULES_XML_FILE, "$XKB_RULES_XML_FILE", + [Define file path of evdev.xml]) + # GObject introspection GOBJECT_INTROSPECTION_CHECK([0.6.8]) @@ -579,6 +618,7 @@ Build options: No snooper regexes "$NO_SNOOPER_APPS" Panel icon "$IBUS_ICON_KEYBOARD" Enable surrounding-text $enable_surrounding_text + Build libgnomebkd $enable_libgnomekbd Run test cases $enable_tests ]) diff --git a/data/ibus.schemas.in b/data/ibus.schemas.in index dbb6da8..70bf9ca 100644 --- a/data/ibus.schemas.in +++ b/data/ibus.schemas.in @@ -42,6 +42,52 @@ + /schemas/desktop/ibus/general/use_xmodmap + /desktop/ibus/general/use_xmodmap + ibus + bool + true + + Use xmodmap + Run xmodmap if .xmodmap/.Xmodmap exists. + + + + /schemas/desktop/ibus/general/xkb_latin_layouts + /desktop/ibus/general/xkb_latin_layouts + ibus + list + string + [ara,bg,cz,dev,gr,gur,in,jp(kana),mal,mkd,ru,ua] + + Latin layout which have no ASCII + us layout is appended to the latin layouts. variant is not needed. + + + + /schemas/desktop/ibus/general/load_xkb_layouts + /desktop/ibus/general/load_xkb_layouts + ibus + list + string + [us,us(chr),us(dvorak),ad,al,am,ara,az,ba,bd,be,bg,br,bt,by, +de,dk,ca,ch,cn(tib),cz,ee,epo,es,et,fi,fo,fr, +gb,ge,ge(dsb),ge(ru),ge(os),gh,gh(akan),gh(ewe),gh(fula),gh(ga),gh(hausa), +gn,gr,hu,hr,ie,ie(CloGaelach),il, +in, +in(tel),in(bolnagri),iq,iq(ku),ir,ir(ku),is,it,jp, +kg,kh,kz,la,latam,lk,lk(tam_unicode),lt,lv,ma,ma(tifinagh),mal,mao, +me,mk,mm,mt,mv,ng,ng(hausa),ng,ng(igbo),ng(yoruba),nl,no,no(smi),np, +pk,pl,pl(csb),pt,ro,rs,ru,ru(cv),ru(kom),ru(sah),ru(tt),ru(xal), +se,si,sk,sy,sy(ku),th,tj,tr,ua,uz,vn +] + + XKB layout list which is shown on ibus-setup + XKB layout list which is shown on ibus-setup. + The format is "layout" or "layout(variant)". + + + /schemas/desktop/ibus/general/hotkey/trigger /desktop/ibus/general/hotkey/trigger ibus @@ -66,6 +112,19 @@ + /schemas/desktop/ibus/general/hotkey/triggers-no-modifiers + /desktop/ibus/general/hotkey/triggers-no-modifiers + ibus + list + string + [] + + Trigger shortcut keys without modifier keys + Trigger shortcut keys without modifier keys. + The list is used by ibus-gjs. + + + /schemas/desktop/ibus/general/hotkey/enable_unconditional /desktop/ibus/general/hotkey/enable_unconditional ibus diff --git a/ibus-1.0.pc.in b/ibus-1.0.pc.in index 9f593ab..c93a0ed 100644 --- a/ibus-1.0.pc.in +++ b/ibus-1.0.pc.in @@ -4,6 +4,7 @@ libdir=@libdir@ includedir=@includedir@ datadir=@datadir@ pkgdatadir=@datadir@/ibus +have_ibus_gkbd=@HAVE_IBUS_GKBD@ Name: IBus Description: IBus Library diff --git a/ibus.spec.in b/ibus.spec.in index 58cac38..4b6f869 100644 --- a/ibus.spec.in +++ b/ibus.spec.in @@ -4,6 +4,7 @@ # Build flags %define build_python_library 0 +%define build_xkb 0 %define glib_ver %([ -a %{_libdir}/pkgconfig/glib-2.0.pc ] && pkg-config --modversion glib-2.0 | cut -d. -f 1,2 || echo -n "999") %define gconf2_version 2.12.0 @@ -38,6 +39,10 @@ BuildRequires: GConf2-devel BuildRequires: pygobject2-devel BuildRequires: intltool BuildRequires: iso-codes-devel +%if %{build_xkb} +BuildRequires: libxkbfile-devel +BuildRequires: libgnomekbd-devel +%endif Requires: %{name}-libs = %{version}-%{release} Requires: %{name}-gtk2 = %{version}-%{release} @@ -51,6 +56,9 @@ Requires: im-chooser >= %{im_chooser_version} Requires: GConf2 >= %{gconf2_version} Requires: notify-python Requires: librsvg2 +%if %{build_xkb} +Requires: libgnomekbd +%endif Requires(post): desktop-file-utils Requires(postun): desktop-file-utils @@ -132,6 +140,10 @@ OPTIONS="$OPTIONS --enable-python-library" OPTIONS="$OPTIONS --disable-python-library" %endif +%if %{build_xkb} +OPTIONS="$OPTIONS --enable-xkb --enable-libgnomekbd" +%endif + %configure $OPTIONS # make -C po update-gmo diff --git a/src/Makefile.am b/src/Makefile.am index 7ee5df8..8fa954e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -197,6 +197,9 @@ typelibs_DATA = $(INTROSPECTION_GIRS:.gir=.typelib) CLEANFILES += $(dist_gir_DATA) $(typelibs_DATA) endif +ibus_sources += ibusxkbxml.c +ibus_headers += ibusxkbxml.h + # gen enum types ibusenumtypes.h: $(ibus_headers) ibusenumtypes.h.template $(AM_V_GEN) ( top_builddir=`cd $(top_builddir) && pwd`; \ diff --git a/src/ibus.h b/src/ibus.h index ef811a4..f82a162 100644 --- a/src/ibus.h +++ b/src/ibus.h @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include diff --git a/src/ibusxkbxml.c b/src/ibusxkbxml.c new file mode 100644 index 0000000..4792664 --- /dev/null +++ b/src/ibusxkbxml.c @@ -0,0 +1,466 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* vim:set et sts=4: */ +/* bus - The Input Bus + * Copyright (C) 2012 Takao Fujiwara + * Copyright (C) 2012 Peng Huang + * Copyright (C) 2012 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "ibus.h" +#include "ibusxkbxml.h" + +#ifndef XKB_RULES_XML_FILE +#define XKB_RULES_XML_FILE "/usr/share/X11/xkb/rules/evdev.xml" +#endif + +#define IBUS_XKB_CONFIG_REGISTRY_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((o), IBUS_TYPE_XKB_CONFIG_REGISTRY, IBusXKBConfigRegistryPrivate)) + +typedef struct _IBusXKBConfigRegistryPrivate IBusXKBConfigRegistryPrivate; + +struct _IBusXKBConfigRegistryPrivate { + GHashTable *layout_list; + GHashTable *layout_lang; + GHashTable *layout_desc; + GHashTable *variant_desc; +}; + + +/* functions prototype */ +static void ibus_xkb_config_registry_destroy + (IBusXKBConfigRegistry *xkb_config); + +G_DEFINE_TYPE (IBusXKBConfigRegistry, ibus_xkb_config_registry, IBUS_TYPE_OBJECT) + +static void +parse_xkb_xml_languagelist_node (IBusXKBConfigRegistryPrivate *priv, + XMLNode *parent_node, + const gchar *layout_name) +{ + XMLNode *node = parent_node; + XMLNode *sub_node; + GList *p; + GList *lang_list = NULL; + + g_assert (node != NULL); + g_assert (layout_name != NULL); + for (p = node->sub_nodes; p; p = p->next) { + sub_node = (XMLNode *) p->data; + if (g_strcmp0 (sub_node->name, "iso639Id") == 0) { + lang_list = g_list_append (lang_list, + (gpointer) g_strdup (sub_node->text)); + continue; + } + } + if (lang_list == NULL) { + /* some nodes have no lang */ + return; + } + if (g_hash_table_lookup (priv->layout_lang, layout_name) != NULL) { + g_warning ("duplicated name %s exists", layout_name); + return; + } + g_hash_table_insert (priv->layout_lang, + (gpointer) g_strdup (layout_name), + (gpointer) lang_list); +} + +static const gchar * +parse_xkb_xml_configitem_node (IBusXKBConfigRegistryPrivate *priv, + XMLNode *parent_node) +{ + XMLNode *node = parent_node; + XMLNode *sub_node; + GList *p; + gchar *name = NULL; + gchar *description = NULL; + + g_assert (node != NULL); + for (p = node->sub_nodes; p; p = p->next) { + sub_node = (XMLNode *) p->data; + if (g_strcmp0 (sub_node->name, "name") == 0) { + name = sub_node->text; + continue; + } + if (g_strcmp0 (sub_node->name, "description") == 0) { + description = sub_node->text; + continue; + } + if (g_strcmp0 (sub_node->name, "languageList") == 0) { + if (name == NULL) { + g_warning ("layout name is NULL in node %s", node->name); + continue; + } + parse_xkb_xml_languagelist_node (priv, sub_node, name); + continue; + } + } + if (name == NULL) { + g_warning ("No name in layout node"); + return NULL; + } + if (g_hash_table_lookup (priv->layout_desc, name) != NULL) { + g_warning ("duplicated name %s exists", name); + return name; + } + g_hash_table_insert (priv->layout_desc, + (gpointer) g_strdup (name), + (gpointer) g_strdup (description)); + + return name; +} + +static const gchar * +parse_xkb_xml_variant_configitem_node (IBusXKBConfigRegistryPrivate *priv, + XMLNode *parent_node, + const gchar *layout_name) +{ + XMLNode *node = parent_node; + XMLNode *sub_node; + GList *p; + gchar *name = NULL; + gchar *description = NULL; + gchar *variant_lang_name = NULL; + + g_assert (node != NULL); + g_assert (layout_name != NULL); + for (p = node->sub_nodes; p; p = p->next) { + sub_node = (XMLNode *) p->data; + if (g_strcmp0 (sub_node->name, "name") == 0) { + name = sub_node->text; + continue; + } + if (g_strcmp0 (sub_node->name, "description") == 0) { + description = sub_node->text; + continue; + } + if (g_strcmp0 (sub_node->name, "languageList") == 0) { + if (name == NULL) { + g_warning ("layout name is NULL in node %s", node->name); + continue; + } + variant_lang_name = g_strdup_printf ("%s(%s)", layout_name, name); + parse_xkb_xml_languagelist_node (priv, sub_node, variant_lang_name); + g_free (variant_lang_name); + continue; + } + } + if (name == NULL) { + g_warning ("No name in layout node"); + return NULL; + } + if (g_hash_table_lookup (priv->variant_desc, name) != NULL) { + /* This is an expected case. */ + return name; + } + variant_lang_name = g_strdup_printf ("%s(%s)", layout_name, name); + g_hash_table_insert (priv->variant_desc, + (gpointer) variant_lang_name, + (gpointer) g_strdup (description)); + return name; +} + +static const gchar * +parse_xkb_xml_variant_node (IBusXKBConfigRegistryPrivate *priv, + XMLNode *parent_node, + const gchar *layout_name) +{ + XMLNode *node = parent_node; + XMLNode *sub_node; + GList *p; + const gchar *variant_name = NULL; + + g_assert (node != NULL); + g_assert (layout_name != NULL); + for (p = node->sub_nodes; p; p = p->next) { + sub_node = (XMLNode *) p->data; + if (g_strcmp0 (sub_node->name, "configItem") == 0) { + variant_name = parse_xkb_xml_variant_configitem_node (priv, sub_node, layout_name); + continue; + } + } + return variant_name; +} + +static GList * +parse_xkb_xml_variantlist_node (IBusXKBConfigRegistryPrivate *priv, + XMLNode *parent_node, + const gchar *layout_name, + GList *variant_list) +{ + XMLNode *node = parent_node; + XMLNode *sub_node; + GList *p; + const gchar *variant_name = NULL; + + g_assert (node != NULL); + g_assert (layout_name != NULL); + for (p = node->sub_nodes; p; p = p->next) { + sub_node = (XMLNode *) p->data; + if (g_strcmp0 (sub_node->name, "variant") == 0) { + variant_name = parse_xkb_xml_variant_node (priv, sub_node, layout_name); + if (variant_name != NULL) { + variant_list = g_list_append (variant_list, + (gpointer) g_strdup (variant_name)); + } + continue; + } + } + return variant_list; +} + +static void +parse_xkb_xml_layout_node (IBusXKBConfigRegistryPrivate *priv, + XMLNode *parent_node) +{ + XMLNode *node = parent_node; + XMLNode *sub_node; + GList *p; + const gchar *name = NULL; + GList *variant_list = NULL; + + g_assert (node != NULL); + for (p = node->sub_nodes; p; p = p->next) { + sub_node = (XMLNode *) p->data; + if (g_strcmp0 (sub_node->name, "configItem") == 0) { + name = parse_xkb_xml_configitem_node (priv, sub_node); + continue; + } + if (g_strcmp0 (sub_node->name, "variantList") == 0) { + if (name == NULL) { + g_warning ("layout name is NULL in node %s", node->name); + continue; + } + variant_list = parse_xkb_xml_variantlist_node (priv, sub_node, + name, + variant_list); + continue; + } + } + if (g_hash_table_lookup (priv->layout_list, name) != NULL) { + g_warning ("duplicated name %s exists", name); + return; + } + g_hash_table_insert (priv->layout_list, + (gpointer) g_strdup (name), + (gpointer) variant_list); +} + +static void +parse_xkb_xml_top_node (IBusXKBConfigRegistryPrivate *priv, + XMLNode *parent_node) +{ + XMLNode *node = parent_node; + XMLNode *sub_node; + GList *p; + + g_assert (priv != NULL); + g_assert (node != NULL); + + if (g_strcmp0 (node->name, "xkbConfigRegistry") != 0) { + g_warning ("node has no xkbConfigRegistry name"); + return; + } + for (p = node->sub_nodes; p; p = p->next) { + sub_node = (XMLNode *) p->data; + if (g_strcmp0 (sub_node->name, "layoutList") == 0) { + break; + } + } + if (p == NULL) { + g_warning ("xkbConfigRegistry node has no layoutList node"); + return; + } + node = sub_node; + for (p = node->sub_nodes; p; p = p->next) { + sub_node = (XMLNode *) p->data; + if (g_strcmp0 (sub_node->name, "layout") == 0) { + parse_xkb_xml_layout_node (priv, sub_node); + continue; + } + } +} + +static void +free_lang_list (GList *list) +{ + GList *l = list; + while (l) { + g_free (l->data); + l->data = NULL; + l = l->next; + } + g_list_free (list); +} + +static void +parse_xkb_config_registry_file (IBusXKBConfigRegistryPrivate *priv, + const gchar *file) +{ + XMLNode *node; + + g_assert (file != NULL); + + priv->layout_list = g_hash_table_new_full (g_str_hash, + (GEqualFunc) g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) free_lang_list); + priv->layout_desc = g_hash_table_new_full (g_str_hash, + (GEqualFunc) g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_free); + priv->layout_lang = g_hash_table_new_full (g_str_hash, + (GEqualFunc) g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) free_lang_list); + priv->variant_desc = g_hash_table_new_full (g_str_hash, + (GEqualFunc) g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_free); + node = ibus_xml_parse_file (file); + parse_xkb_xml_top_node (priv, node); + ibus_xml_free (node); +} + +static void +ibus_xkb_config_registry_init (IBusXKBConfigRegistry *xkb_config) +{ + IBusXKBConfigRegistryPrivate *priv; + const gchar *file = XKB_RULES_XML_FILE; + + priv = IBUS_XKB_CONFIG_REGISTRY_GET_PRIVATE (xkb_config); + parse_xkb_config_registry_file (priv, file); +} + +static void +ibus_xkb_config_registry_destroy (IBusXKBConfigRegistry *xkb_config) +{ + IBusXKBConfigRegistryPrivate *priv; + + g_return_if_fail (xkb_config != NULL); + + priv = IBUS_XKB_CONFIG_REGISTRY_GET_PRIVATE (xkb_config); + + g_hash_table_destroy (priv->layout_list); + priv->layout_list = NULL; + g_hash_table_destroy (priv->layout_lang); + priv->layout_lang= NULL; + g_hash_table_destroy (priv->layout_desc); + priv->layout_desc= NULL; + g_hash_table_destroy (priv->variant_desc); + priv->variant_desc = NULL; + + IBUS_OBJECT_CLASS(ibus_xkb_config_registry_parent_class)->destroy (IBUS_OBJECT (xkb_config)); +} + +static void +ibus_xkb_config_registry_class_init (IBusXKBConfigRegistryClass *klass) +{ + IBusObjectClass *ibus_object_class = IBUS_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (IBusXKBConfigRegistryPrivate)); + + ibus_object_class->destroy = (IBusObjectDestroyFunc) ibus_xkb_config_registry_destroy; +} + +IBusXKBConfigRegistry * +ibus_xkb_config_registry_new (void) +{ + IBusXKBConfigRegistry *xkb_config; + + xkb_config = IBUS_XKB_CONFIG_REGISTRY (g_object_new (IBUS_TYPE_XKB_CONFIG_REGISTRY, NULL)); + return xkb_config; +} + +#define TABLE_FUNC(field_name) const GHashTable * \ +ibus_xkb_config_registry_get_##field_name (IBusXKBConfigRegistry *xkb_config) \ +{ \ + IBusXKBConfigRegistryPrivate *priv; \ + \ + g_return_val_if_fail (xkb_config != NULL, NULL); \ + priv = IBUS_XKB_CONFIG_REGISTRY_GET_PRIVATE (xkb_config); \ + return priv->field_name; \ +} + +TABLE_FUNC (layout_list) +TABLE_FUNC (layout_lang) +TABLE_FUNC (layout_desc) +TABLE_FUNC (variant_desc) + +#undef TABLE_FUNC + +GList * +ibus_xkb_config_registry_layout_list_get_layouts (IBusXKBConfigRegistry *xkb_config) +{ + GHashTable *table; + GList *list = NULL; + + table = (GHashTable *) + ibus_xkb_config_registry_get_layout_list (xkb_config); + list = (GList *) g_hash_table_get_keys (table); + return list; +} + +/* vala could use GLib.List for the returned pointer and + * the declaration calls g_list_foreach (retval, g_free, NULL). + * When I think about GLib.List v.s. GLib.List, probably + * I think GLib.List is better for the function and set + * g_strdup() here. I do not know about GJS implementation. + */ +#define TABLE_LOOKUP_LIST_FUNC(field_name, value) GList * \ +ibus_xkb_config_registry_##field_name##_get_##value (IBusXKBConfigRegistry *xkb_config, const gchar *key) \ +{ \ + GHashTable *table; \ + GList *list = NULL; \ + GList *retval= NULL; \ + GList *p = NULL; \ + \ + table = (GHashTable *) \ + ibus_xkb_config_registry_get_##field_name (xkb_config); \ + list = (GList *) g_hash_table_lookup (table, key); \ + retval = g_list_copy (list); \ + for (p = retval; p; p = p->next) { \ + p->data = g_strdup (p->data); \ + } \ + return retval; \ +} + +#define TABLE_LOOKUP_STRING_FUNC(field_name, value) gchar * \ +ibus_xkb_config_registry_##field_name##_get_##value (IBusXKBConfigRegistry *xkb_config, const gchar *key) \ +{ \ + GHashTable *table; \ + const gchar *desc = NULL; \ + \ + table = (GHashTable *) \ + ibus_xkb_config_registry_get_##field_name (xkb_config); \ + desc = (const gchar *) g_hash_table_lookup (table, key); \ + return g_strdup (desc); \ +} + +TABLE_LOOKUP_LIST_FUNC (layout_list, variants) +TABLE_LOOKUP_LIST_FUNC (layout_lang, langs) +TABLE_LOOKUP_STRING_FUNC (layout_desc, desc) +TABLE_LOOKUP_STRING_FUNC (variant_desc, desc) + +#undef TABLE_LOOKUP_LIST_FUNC +#undef TABLE_LOOKUP_STRING_FUNC diff --git a/src/ibusxkbxml.h b/src/ibusxkbxml.h new file mode 100644 index 0000000..6f5b7bd --- /dev/null +++ b/src/ibusxkbxml.h @@ -0,0 +1,187 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* vim:set et sts=4: */ +/* bus - The Input Bus + * Copyright (C) 2012 Takao Fujiwara + * Copyright (C) 2012 Peng Huang + * Copyright (C) 2012 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifndef __IBUS_XKBXML_H_ +#define __IBUS_XKBXML_H_ + +#if !defined (__IBUS_H_INSIDE__) && !defined (IBUS_COMPILATION) +#error "Only can be included directly" +#endif + +#include "ibus.h" + +/* + * Type macros. + */ +/* define IBusXKBConfigRegistry macros */ +#define IBUS_TYPE_XKB_CONFIG_REGISTRY \ + (ibus_xkb_config_registry_get_type ()) +#define IBUS_XKB_CONFIG_REGISTRY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), IBUS_TYPE_XKB_CONFIG_REGISTRY, IBusXKBConfigRegistry)) +#define IBUS_XKB_CONFIG_REGISTRY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), IBUS_TYPE_XKB_CONFIG_REGISTRY, IBusXKBConfigRegistryClass)) +#define IBUS_IS_XKB_CONFIG_REGISTRY(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IBUS_TYPE_XKB_CONFIG_REGISTRY)) +#define IBUS_IS_XKB_CONFIG_REGISTRY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), IBUS_TYPE_XKB_CONFIG_REGISTRY)) +#define IBUS_XKB_CONFIG_REGISTRY_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), IBUS_TYPE_XKB_CONFIG_REGISTRY, IBusXKBConfigRegistryClass)) + +G_BEGIN_DECLS + +typedef struct _IBusXKBConfigRegistry IBusXKBConfigRegistry; +typedef struct _IBusXKBConfigRegistryClass IBusXKBConfigRegistryClass; + +struct _IBusXKBConfigRegistry { + IBusObject parent; +}; + +struct _IBusXKBConfigRegistryClass { + IBusObjectClass parent; + /* signals */ + /*< private >*/ + /* padding */ + gpointer pdummy[8]; +}; + + +GType ibus_xkb_config_registry_get_type + (void); + +/** + * ibus_xkb_config_registry_new: + * @returns: A newly allocated IBusXKBConfigRegistry + * + * New an IBusXKBConfigRegistry. + */ +IBusXKBConfigRegistry * + ibus_xkb_config_registry_new + (void); + +/** + * ibus_xkb_config_registry_get_layout_list: (skip) + * @xkb_config: An IBusXKBConfigRegistry. + * @returns: A const GHashTable + * + * a const GHashTable + */ +const GHashTable * + ibus_xkb_config_registry_get_layout_list + (IBusXKBConfigRegistry *xkb_config); + +/** + * ibus_xkb_config_registry_get_layout_lang: (skip) + * @xkb_config: An IBusXKBConfigRegistry. + * @returns: A const GHashTable + * + * a const GHashTable + */ +const GHashTable * + ibus_xkb_config_registry_get_layout_lang + (IBusXKBConfigRegistry *xkb_config); + +/** + * ibus_xkb_config_registry_get_layout_desc: (skip) + * @xkb_config: An IBusXKBConfigRegistry. + * @returns: A const GHashTable + * + * a const GHashTable + */ +const GHashTable * + ibus_xkb_config_registry_get_layout_desc + (IBusXKBConfigRegistry *xkb_config); + +/** + * ibus_xkb_config_registry_get_variant_desc: (skip) + * @xkb_config: An IBusXKBConfigRegistry. + * @returns: A const GHashTable + * + * a const GHashTable + */ +const GHashTable * + ibus_xkb_config_registry_get_variant_desc + (IBusXKBConfigRegistry *xkb_config); + +/** + * ibus_xkb_config_registry_layout_list_get_layouts: + * @xkb_config: An IBusXKBConfigRegistry. + * @returns: (transfer container) (element-type utf8): A GList of layouts + * + * a GList of layouts + */ +GList * + ibus_xkb_config_registry_layout_list_get_layouts + (IBusXKBConfigRegistry *xkb_config); + +/** + * ibus_xkb_config_registry_layout_list_get_variants: + * @xkb_config: An IBusXKBConfigRegistry. + * @layout: A layout. + * @returns: (transfer container) (element-type utf8): A GList + * + * a GList + */ +GList * + ibus_xkb_config_registry_layout_list_get_variants + (IBusXKBConfigRegistry *xkb_config, + const gchar *layout); + +/** + * ibus_xkb_config_registry_layout_lang_get_langs: + * @xkb_config: An IBusXKBConfigRegistry. + * @layout: A layout. + * @returns: (transfer container) (element-type utf8): A GList + * + * a GList + */ +GList * + ibus_xkb_config_registry_layout_lang_get_langs + (IBusXKBConfigRegistry *xkb_config, + const gchar *layout); + +/** + * ibus_xkb_config_registry_layout_desc_get_desc: + * @xkb_config: An IBusXKBConfigRegistry. + * @layout: A layout. + * @returns: A layout description + * + * a layout description + */ +gchar * + ibus_xkb_config_registry_layout_desc_get_desc + (IBusXKBConfigRegistry *xkb_config, + const gchar *layout); + +/** + * ibus_xkb_config_registry_variant_desc_get_desc: + * @xkb_config: An IBusXKBConfigRegistry. + * @variant: A variant. + * @returns: A variant description + * + * a variant description + */ +gchar * + ibus_xkb_config_registry_variant_desc_get_desc + (IBusXKBConfigRegistry *xkb_config, + const gchar *variant); +G_END_DECLS +#endif diff --git a/ui/gtk3/Makefile.am b/ui/gtk3/Makefile.am index 5473027..b7e663c 100644 --- a/ui/gtk3/Makefile.am +++ b/ui/gtk3/Makefile.am @@ -47,6 +47,8 @@ USE_SYMBOL_ICON = FALSE # force include config.h before gi18n.h. AM_CPPFLAGS = -include $(CONFIG_HEADER) +HAVE_IBUS_GKBD_C = $(strip $(subst false, FALSE, $(subst true, TRUE, $(HAVE_IBUS_GKBD)))) + AM_CFLAGS = \ @GLIB2_CFLAGS@ \ @GIO2_CFLAGS@ \ @@ -58,6 +60,8 @@ AM_CFLAGS = \ -DBINDIR=\"$(bindir)\" \ -DIBUS_DISABLE_DEPRECATED \ -DSWITCHER_USE_SYMBOL_ICON=$(USE_SYMBOL_ICON) \ + -DHAVE_IBUS_GKBD=$(HAVE_IBUS_GKBD_C) \ + -DXKB_LAYOUTS_MAX_LENGTH=4 \ -Wno-unused-variable \ -Wno-unused-but-set-variable \ -Wno-unused-function \ @@ -91,6 +95,7 @@ ibus_ui_gtk3_SOURCES = \ application.vala \ candidatearea.vala \ candidatepanel.vala \ + gkbdlayout.vala \ handle.vala \ iconwidget.vala \ keybindingmanager.vala \ @@ -99,17 +104,48 @@ ibus_ui_gtk3_SOURCES = \ property.vala \ separator.vala \ switcher.vala \ + xkblayout.vala \ $(NULL) ibus_ui_gtk3_LDADD = \ $(AM_LDADD) \ $(NULL) +if ENABLE_LIBGNOMEKBD +AM_CFLAGS += \ + @LIBGNOMEKBDUI_CFLAGS@ \ + @ATK_CFLAGS@ \ + $(NULL) + +AM_LDADD += \ + @LIBGNOMEKBDUI_LIBS@ \ + @ATK_LIBS@ \ + $(NULL) + +AM_VALAFLAGS += \ + --vapidir=. \ + --metadatadir=$(top_srcdir)/bindings/vala \ + --pkg=glib-2.0 \ + --pkg=gmodule-2.0 \ + --pkg=gkbd \ + --pkg=Xkl-1.0 \ + $(NULL) + +$(srcdir)/gkbdlayout.vala: $(top_builddir)/bindings/vala/gkbd.vapi + @cp $(srcdir)/gkbdlayout.vala.true $(srcdir)/gkbdlayout.vala +else +$(srcdir)/gkbdlayout.vala: + @cp $(srcdir)/gkbdlayout.vala.false $(srcdir)/gkbdlayout.vala +endif + CLEANFILES = \ + gkbdlayout.vala \ gtkpanel.xml \ $(NULL) EXTRA_DIST = \ + gkbdlayout.vala.false \ + gkbdlayout.vala.true \ gtkpanel.xml.in.in \ $(NULL) diff --git a/ui/gtk3/gkbdlayout.vala.false b/ui/gtk3/gkbdlayout.vala.false new file mode 100644 index 0000000..a387de9 --- /dev/null +++ b/ui/gtk3/gkbdlayout.vala.false @@ -0,0 +1,63 @@ +/* vim:set et sts=4 sw=4: + * + * ibus - The Input Bus + * + * Copyright 2012 Red Hat, Inc. + * Copyright(c) 2012 Peng Huang + * Copyright(c) 2012 Takao Fujiwara + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or(at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + */ + +public class GkbdLayout +{ + public signal void changed(); + public signal void group_changed (int object); + + public GkbdLayout() { + } + + public string[] get_layouts() { + return new string[0]; + } + + public string[] get_group_names() { + return new string[0]; + } + + public void lock_group(int id) { + } + + public void start_listen() { + } + + public void stop_listen() { + } + + /* + public static int main(string[] args) { + GkbdLayout ibus_layouts = new GkbdLayout(); + + string[] layouts = ibus_layouts.get_layouts(); + string[] names = ibus_layouts.get_group_names(); + for (int i = 0; layouts != null && i < layouts.length; i++) { + stdout.printf("%s %s\n", layouts[i], names[i]); + } + + return 0; + } + */ +} diff --git a/ui/gtk3/gkbdlayout.vala.true b/ui/gtk3/gkbdlayout.vala.true new file mode 100644 index 0000000..2b78c69 --- /dev/null +++ b/ui/gtk3/gkbdlayout.vala.true @@ -0,0 +1,108 @@ +/* vim:set et sts=4 sw=4: + * + * ibus - The Input Bus + * + * Copyright 2012 Red Hat, Inc. + * Copyright(c) 2012 Peng Huang + * Copyright(c) 2012 Takao Fujiwara + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or(at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + */ + +public class GkbdLayout +{ + public signal void changed(); + public signal void group_changed (int object); + + private Gkbd.Configuration m_config = null; + + public GkbdLayout() { + m_config = Gkbd.Configuration.get(); + if (m_config != null) { + m_config.changed.connect(config_changed_cb); + m_config.group_changed.connect(config_group_changed_cb); + } + } + + ~GkbdLayout() { + if (m_config != null) { + m_config.changed.disconnect(config_changed_cb); + m_config.group_changed.disconnect(config_group_changed_cb); + /* gkbd_configuration_get reuses the object and do not + * destroy m_config here. */ + m_config.ref(); + m_config = null; + } + } + + private void config_changed_cb() { + changed(); + } + + private void config_group_changed_cb(int object) { + group_changed(object); + } + + public string[] get_layouts() { + if (m_config == null) { + return new string[0]; + } + return m_config.get_short_group_names(); + } + + public string[] get_group_names() { + if (m_config == null) { + return new string[0]; + } + return m_config.get_group_names(); + } + + public void lock_group(int id) { + if (m_config == null) { + return; + } + m_config.lock_group(id); + } + + public void start_listen() { + if (m_config == null) { + return; + } + m_config.start_listen(); + } + + public void stop_listen() { + if (m_config == null) { + return; + } + m_config.stop_listen(); + } + + /* + public static int main(string[] args) { + Gtk.init(ref args); + GkbdLayout ibus_layouts = new GkbdLayout(); + + string[] layouts = ibus_layouts.get_layouts(); + string[] names = ibus_layouts.get_group_names(); + for (int i = 0; layouts != null && i < layouts.length; i++) { + stdout.printf("%s %s\n", layouts[i], names[i]); + } + + return 0; + } + */ +} diff --git a/ui/gtk3/panel.vala b/ui/gtk3/panel.vala index 24e6b2e..55b63ce 100644 --- a/ui/gtk3/panel.vala +++ b/ui/gtk3/panel.vala @@ -48,6 +48,13 @@ class Panel : IBus.PanelService { private Gtk.AboutDialog m_about_dialog; private Gtk.CssProvider m_css_provider; private int m_switcher_delay_time = 400; + private GkbdLayout m_gkbdlayout = null; + private XKBLayout m_xkblayout = null; + private string[] m_layouts = {}; + private string[] m_variants = {}; + private int m_fallback_lock_id = -1; + private bool m_changed_xkb_option = false; + private GLib.Timer m_changed_layout_timer; private const string ACCELERATOR_SWITCH_IME_FOREWARD = "space"; private GLib.List m_keybindings = new GLib.List(); @@ -90,6 +97,14 @@ class Panel : IBus.PanelService { ~Panel() { unbind_switch_shortcut(); + + if (HAVE_IBUS_GKBD && m_gkbdlayout != null) { + m_gkbdlayout.changed.disconnect(gkbdlayout_changed_cb); + m_gkbdlayout.stop_listen(); + m_gkbdlayout = null; + } + + m_xkblayout = null; } private void keybinding_manager_bind(KeybindingManager keybinding_manager, @@ -281,6 +296,7 @@ class Panel : IBus.PanelService { m_config.watch("general/hotkey", "triggers"); m_config.watch("panel", "custom_font"); m_config.watch("panel", "use_custom_font"); + init_engines_order(); update_engines(m_config.get_value("general", "preload_engines"), m_config.get_value("general", "engines_order")); unbind_switch_shortcut(); @@ -293,6 +309,204 @@ class Panel : IBus.PanelService { set_custom_font(); } + private void gkbdlayout_changed_cb() { + /* The callback is called four times after set_layout is called + * so check the elapsed and take the first signal only. */ + double elapsed = m_changed_layout_timer.elapsed(); + if (elapsed < 1.0 && elapsed > 0.0) { + return; + } + + if (m_fallback_lock_id != -1) { + /* Call lock_group only when set_layout is called. */ + m_gkbdlayout.lock_group(m_fallback_lock_id); + m_fallback_lock_id = -1; + } else { + /* Reset default layout when gnome-control-center is called. */ + m_xkblayout.reset_layout(); + } + + update_xkb_engines(); + m_changed_layout_timer.reset(); + } + + private void init_gkbd() { + m_gkbdlayout = new GkbdLayout(); + m_gkbdlayout.changed.connect(gkbdlayout_changed_cb); + + /* Probably we cannot support both keyboard and ibus indicators + * How can I get the engine from keymap of group_id? + * e.g. 'en' could be owned by xkb:en and pinyin engines. */ + //m_gkbdlayout.group_changed.connect((object) => {}); + + m_changed_layout_timer = new GLib.Timer(); + m_changed_layout_timer.start(); + m_gkbdlayout.start_listen(); + } + + private void init_engines_order() { + if (m_config == null) { + return; + } + + m_xkblayout = new XKBLayout(m_config); + string session = Environment.get_variable("DESKTOP_SESSION"); + + if (HAVE_IBUS_GKBD && + session != null && session.length >= 5 && + session[0:5] == "gnome") { + init_gkbd(); + } + + update_xkb_engines(); + } + + private void update_xkb_engines() { + string var_layout = m_xkblayout.get_layout(); + string var_variant = m_xkblayout.get_variant(); + if (var_layout == "") { + return; + } + + m_layouts = var_layout.split(","); + m_variants = var_variant.split(","); + + IBus.XKBConfigRegistry registry = new IBus.XKBConfigRegistry(); + string[] var_xkb_engine_names = {}; + for (int i = 0; i < m_layouts.length; i++) { + string name = m_layouts[i]; + string lang = null; + + if (i < m_variants.length && m_variants[i] != "") { + name = "%s:%s".printf(name, m_variants[i]); + string layout = "%s(%s)".printf(name, m_variants[i]); + GLib.List langs = + registry.layout_lang_get_langs(layout); + if (langs.length() != 0) { + lang = langs.data; + } + } else { + name = "%s:".printf(name); + } + + if (lang == null) { + GLib.List langs = + registry.layout_lang_get_langs(m_layouts[i]); + if (langs.length() != 0) { + lang = langs.data; + } + } + + var_xkb_engine_names += "%s:%s:%s".printf("xkb", name, lang); + } + + GLib.Variant var_engines = + m_config.get_value("general", "preload_engines"); + string[] engine_names = {}; + bool updated_engine_names = false; + + if (var_engines != null) { + engine_names = var_engines.dup_strv(); + } + + foreach (string name in var_xkb_engine_names) { + if (name in engine_names) + continue; + updated_engine_names = true; + engine_names += name; + } + + if (updated_engine_names) { + m_config.set_value("general", + "preload_engines", + new GLib.Variant.strv(engine_names)); + } + + GLib.Variant var_order = + m_config.get_value("general", "engines_order"); + string[] order_names = {}; + bool updated_order_names = false; + + if (var_order != null) { + order_names = var_order.dup_strv(); + } + + foreach (var name in var_xkb_engine_names) { + if (name in order_names) + continue; + order_names += name; + updated_order_names = true; + } + + if (updated_order_names) { + m_config.set_value("general", + "engines_order", + new GLib.Variant.strv(order_names)); + } + } + + private void set_xkb_group_layout(IBus.EngineDesc engine) { + int[] retval = m_xkblayout.set_layout(engine, true); + if (retval[0] >= 0) { + /* If an XKB keymap is added into the XKB group, + * this._gkbdlayout.lock_group will be called after + * 'group-changed' signal is received. */ + m_fallback_lock_id = retval[0]; + m_changed_xkb_option = (retval[1] != 0) ? true : false; + } + } + + private bool set_gkbd_layout(IBus.EngineDesc engine) { + string layout = engine.get_layout(); + string variant = engine.get_layout_variant(); + + /* If a previous ibus engine changed XKB options, need to set the + * default XKB option. */ + if (m_changed_xkb_option == true) { + m_changed_xkb_option = false; + return false; + } + + if (variant != "" && variant != "default") { + layout = "%s(%s)".printf(layout, variant); + } + + int gkbd_len = m_gkbdlayout.get_group_names().length; + for (int i = 0; i < m_layouts.length && i < gkbd_len; i++) { + string sys_layout = m_layouts[i]; + if (i < m_variants.length && m_variants[i] != "") { + sys_layout = "%s(%s)".printf(sys_layout, m_variants[i]); + } + if (sys_layout == layout) { + m_gkbdlayout.lock_group(i); + return true; + } + } + return false; + } + + private void set_layout(IBus.EngineDesc engine) { + string layout = engine.get_layout(); + + if (layout == "" || layout == null) { + return; + } + + if (m_xkblayout == null) { + init_engines_order(); + } + + if (HAVE_IBUS_GKBD && m_gkbdlayout != null) { + if (set_gkbd_layout(engine)) { + return; + } + set_xkb_group_layout(engine); + return; + } + + m_xkblayout.set_layout(engine); + } + private void exec_setxkbmap(IBus.EngineDesc engine) { string layout = engine.get_layout(); string variant = engine.get_layout_variant(); @@ -352,7 +566,7 @@ class Panel : IBus.PanelService { return; } // set xkb layout - exec_setxkbmap(engine); + set_layout(engine); } private void config_value_changed_cb(IBus.Config config, diff --git a/ui/gtk3/xkblayout.vala b/ui/gtk3/xkblayout.vala new file mode 100644 index 0000000..165a905 --- /dev/null +++ b/ui/gtk3/xkblayout.vala @@ -0,0 +1,431 @@ +/* vim:set et sts=4 sw=4: + * + * ibus - The Input Bus + * + * Copyright 2012 Red Hat, Inc. + * Copyright(c) 2012 Peng Huang + * Copyright(c) 2012 Takao Fujiwara + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or(at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + */ + +public extern const bool HAVE_IBUS_GKBD; +public extern const int XKB_LAYOUTS_MAX_LENGTH; + +class XKBLayout +{ + string m_xkb_command = "setxkbmap"; + IBus.Config m_config = null; + string[] m_xkb_latin_layouts = {}; + GLib.Pid m_xkb_pid = -1; + GLib.Pid m_xmodmap_pid = -1; + string m_xmodmap_command = "xmodmap"; + bool m_use_xmodmap = true; + string[] m_xmodmap_known_files = {".xmodmap", ".xmodmaprc", + ".Xmodmap", ".Xmodmaprc"}; + string m_default_layout = ""; + string m_default_variant = ""; + string m_default_option = ""; + + public XKBLayout(IBus.Config? config) { + m_config = config; + + if (config != null) { + var value = config.get_value("general", "xkb_latin_layouts"); + for (int i = 0; value != null && i < value.n_children(); i++) { + m_xkb_latin_layouts += + value.get_child_value(i).dup_string(); + } + if (m_use_xmodmap) { + m_use_xmodmap = config.get_value("general", "use_xmodmap").get_boolean(); + } + } + } + + private string get_output_from_cmdline(string arg, string element) { + string[] exec_command = {}; + exec_command += m_xkb_command; + exec_command += arg; + string standard_output = null; + string standard_error = null; + int exit_status = 0; + string retval = ""; + try { + GLib.Process.spawn_sync(null, + exec_command, + null, + GLib.SpawnFlags.SEARCH_PATH, + null, + out standard_output, + out standard_error, + out exit_status); + } catch (GLib.SpawnError err) { + stderr.printf("IBUS_ERROR: %s\n", err.message); + } + if (exit_status != 0) { + stderr.printf("IBUS_ERROR: %s\n", standard_error ?? ""); + } + if (standard_output == null) { + return ""; + } + foreach (string line in standard_output.split("\n")) { + if (element.length <= line.length && + line[0:element.length] == element) { + retval = line[element.length:line.length]; + if (retval == null) { + retval = ""; + } else { + retval = retval.strip(); + } + } + } + return retval; + } + + private void set_layout_cb(GLib.Pid pid, int status) { + if (m_xkb_pid != pid) { + stderr.printf("IBUS_ERROR: set_layout_cb has another pid\n"); + return; + } + GLib.Process.close_pid(m_xkb_pid); + m_xkb_pid = -1; + set_xmodmap(); + } + + private void set_xmodmap_cb(GLib.Pid pid, int status) { + if (m_xmodmap_pid != pid) { + stderr.printf("IBUS_ERROR: set_xmodmap_cb has another pid\n"); + return; + } + GLib.Process.close_pid(m_xmodmap_pid); + m_xmodmap_pid = -1; + } + + private string get_fullpath(string command) { + string envpath = GLib.Environment.get_variable("PATH"); + foreach (string dir in envpath.split(":")) { + string filepath = GLib.Path.build_filename(dir, command); + if (GLib.FileUtils.test(filepath, GLib.FileTest.EXISTS)) { + return filepath; + } + } + return ""; + } + + private string[] get_xkb_group_layout (string layout, + string variant, + int layouts_max_length) { + int group_id = 0; + int i = 0; + string[] layouts = m_default_layout.split(","); + string[] variants = m_default_variant.split(","); + string group_layouts = ""; + string group_variants = ""; + bool has_variant = false; + bool include_keymap = false; + + for (i = 0; i < layouts.length; i++) { + if (i >= layouts_max_length - 1) { + break; + } + + if (i == 0) { + group_layouts = layouts[i]; + } else { + group_layouts = "%s,%s".printf(group_layouts, layouts[i]); + } + + if (i >= variants.length) { + if (i == 0) { + group_variants = ""; + } else { + group_variants += ","; + } + if (layout == layouts[i] && variant == "") { + include_keymap = true; + group_id = i; + } + continue; + } + if (layout == layouts[i] && variant == variants[i]) { + include_keymap = true; + group_id = i; + } + + if (variants[i] != "") { + has_variant = true; + } + + if (i == 0) { + group_variants = variants[i]; + } else { + group_variants = "%s,%s".printf(group_variants, variants[i]); + } + } + + if (variant != "") { + has_variant = true; + } + + if (!include_keymap) { + group_layouts = "%s,%s".printf(group_layouts, layout); + group_variants = "%s,%s".printf(group_variants, variant); + group_id = i; + } + + if (!has_variant) { + group_variants = null; + } + + return {group_layouts, group_variants, group_id.to_string()}; + } + + public string[] get_variant_from_layout(string layout) { + int left_bracket = layout.index_of("("); + int right_bracket = layout.index_of(")"); + if (left_bracket >= 0 && right_bracket > left_bracket) { + return {layout[0:left_bracket] + + layout[right_bracket + 1:layout.length], + layout[left_bracket + 1:right_bracket]}; + } + return {layout, "default"}; + } + + public string[] get_option_from_layout(string layout) { + int left_bracket = layout.index_of("["); + int right_bracket = layout.index_of("]"); + if (left_bracket >= 0 && right_bracket > left_bracket) { + return {layout[0:left_bracket] + + layout[right_bracket + 1:layout.length], + layout[left_bracket + 1:right_bracket]}; + } + return {layout, "default"}; + } + + public string get_layout() { + return get_output_from_cmdline("-query", "layout: "); + } + + public string get_variant() { + return get_output_from_cmdline("-query", "variant: "); + } + + public string get_option() { + return get_output_from_cmdline("-query", "options: "); + } + + /* + public string get_group() { + return get_output_from_cmdline("--get-group", "group: "); + } + */ + + public int[] set_layout(IBus.EngineDesc engine, + bool use_group_layout=false) { + string layout = engine.get_layout(); + string variant = engine.get_layout_variant(); + string option = engine.get_layout_option(); + + assert (layout != null); + + int xkb_group_id = 0; + int changed_option = 0; + + if (m_xkb_pid != -1) { + return {-1, 0}; + } + + if (layout == "default" && + (variant == "default" || variant == "") && + (option == "default" || option == "")) { + return {-1, 0}; + } + + bool need_us_layout = false; + foreach (string latin_layout in m_xkb_latin_layouts) { + if (layout == latin_layout && variant != "eng") { + need_us_layout = true; + break; + } + if (variant != null && + "%s(%s)".printf(layout, variant) == latin_layout) { + need_us_layout = true; + break; + } + } + + int layouts_max_length = XKB_LAYOUTS_MAX_LENGTH; + if (need_us_layout) { + layouts_max_length--; + } + + if (m_default_layout == "") { + m_default_layout = get_layout(); + } + if (m_default_variant == "") { + m_default_variant = get_variant(); + } + if (m_default_option == "") { + m_default_option = get_option(); + } + + if (layout == "default") { + layout = m_default_layout; + variant = m_default_variant; + } else { + if (use_group_layout) { + if (variant == "default") { + variant = ""; + } + string[] retval = get_xkb_group_layout (layout, variant, + layouts_max_length); + layout = retval[0]; + variant = retval[1]; + xkb_group_id = int.parse(retval[2]); + } + } + + if (layout == "") { + warning("Could not get the correct layout"); + return {-1, 0}; + } + + if (variant == "default" || variant == "") { + variant = null; + } + + if (option == "default" || option == "") { + option = m_default_option; + } else { + if (!(option in m_default_option.split(","))) { + option = "%s,%s".printf(m_default_option, option); + changed_option = 1; + } else { + option = m_default_option; + } + } + + if (option == "") { + option = null; + } + + if (need_us_layout) { + layout += ",us"; + if (variant != null) { + variant += ","; + } + } + + string[] args = {}; + args += m_xkb_command; + args += "-layout"; + args += layout; + if (variant != null) { + args += "-variant"; + args += variant; + } + if (option != null) { + /* TODO: Need to get the session XKB options */ + args += "-option"; + args += "-option"; + args += option; + } + + GLib.Pid child_pid; + try { + GLib.Process.spawn_async(null, + args, + null, + GLib.SpawnFlags.DO_NOT_REAP_CHILD | + GLib.SpawnFlags.SEARCH_PATH, + null, + out child_pid); + } catch (GLib.SpawnError err) { + stderr.printf("Execute setxkbmap failed: %s\n", err.message); + return {-1, 0}; + } + m_xkb_pid = child_pid; + GLib.ChildWatch.add(m_xkb_pid, set_layout_cb); + + return {xkb_group_id, changed_option}; + } + + public void set_xmodmap() { + if (!m_use_xmodmap) { + return; + } + + if (m_xmodmap_pid != -1) { + return; + } + + string xmodmap_cmdpath = get_fullpath(m_xmodmap_command); + if (xmodmap_cmdpath == "") { + xmodmap_cmdpath = m_xmodmap_command; + } + string homedir = GLib.Environment.get_home_dir(); + foreach (string xmodmap_file in m_xmodmap_known_files) { + string xmodmap_filepath = GLib.Path.build_filename(homedir, xmodmap_file); + if (!GLib.FileUtils.test(xmodmap_filepath, GLib.FileTest.EXISTS)) { + continue; + } + string[] args = {xmodmap_cmdpath, xmodmap_filepath}; + + GLib.Pid child_pid; + try { + GLib.Process.spawn_async(null, + args, + null, + GLib.SpawnFlags.DO_NOT_REAP_CHILD | + GLib.SpawnFlags.SEARCH_PATH, + null, + out child_pid); + } catch (GLib.SpawnError err) { + stderr.printf("IBUS_ERROR: %s\n", err.message); + return; + } + m_xmodmap_pid = child_pid; + GLib.ChildWatch.add(m_xmodmap_pid, set_xmodmap_cb); + + break; + } + } + + public void reset_layout() { + m_default_layout = get_layout(); + m_default_variant = get_variant(); + m_default_option = get_option(); + } + + /* + public static int main(string[] args) { + IBus.Bus bus = new IBus.Bus(); + IBus.Config config = bus.get_config(); + XKBLayout xkblayout = new XKBLayout(config); + stdout.printf ("layout: %s\n", xkblayout.get_layout()); + stdout.printf ("variant: %s\n", xkblayout.get_variant()); + stdout.printf ("option: %s\n", xkblayout.get_option()); + xkblayout.set_layout("jp"); + if (config != null) { + IBus.main(); + } else { + Gtk.init (ref args); + Gtk.main(); + } + return 0; + } + */ +} -- 1.8.0