From 39f4aa0d61f12ea1b46588fe23da639b293d76b0 Mon Sep 17 00:00:00 2001 From: fujiwarat Date: Fri, 14 Sep 2012 18:48:56 +0900 Subject: [PATCH] Add ibus-xkb and libgnomekbd. --- bindings/vala/Gkbd-3.0.metadata | 1 + bindings/vala/Makefile.am | 20 ++ bindings/vala/Xkl-1.0.metadata | 3 + client/x11/gdk-private.c | 3 +- configure.ac | 60 +++++ data/ibus.schemas.in | 94 +++++++ engine/Makefile.am | 6 + engine/ibus-xkb/Makefile.am | 64 +++++ engine/ibus-xkb/ibus-xkb-main.c | 111 ++++++++ engine/ibus-xkb/xkblib.c | 327 ++++++++++++++++++++++++ engine/ibus-xkb/xkblib.h | 41 +++ engine/main.vala | 86 +++++++ engine/simple.xml.in.in | 531 +-------------------------------------- ibus-1.0.pc.in | 4 + ibus.spec.in | 12 + setup/main.py | 39 ++- src/Makefile.am | 5 + src/ibus.h | 1 + src/ibusxkbxml.c | 466 ++++++++++++++++++++++++++++++++++ src/ibusxkbxml.h | 187 ++++++++++++++ ui/gtk3/Makefile.am | 39 +++ ui/gtk3/gkbdlayout.vala.false | 63 +++++ ui/gtk3/gkbdlayout.vala.true | 108 ++++++++ ui/gtk3/keybindingmanager.vala | 14 +- ui/gtk3/panel.vala | 365 +++++++++++++++++++++++---- ui/gtk3/switcher.vala | 49 ++-- ui/gtk3/xkblayout.vala | 464 ++++++++++++++++++++++++++++++++++ 27 files changed, 2560 insertions(+), 603 deletions(-) create mode 100644 bindings/vala/Gkbd-3.0.metadata create mode 100644 bindings/vala/Xkl-1.0.metadata create mode 100644 engine/ibus-xkb/Makefile.am create mode 100644 engine/ibus-xkb/ibus-xkb-main.c create mode 100644 engine/ibus-xkb/xkblib.c create mode 100644 engine/ibus-xkb/xkblib.h 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 abcc543..ed132e0 100644 --- a/bindings/vala/Makefile.am +++ b/bindings/vala/Makefile.am @@ -27,12 +27,22 @@ dist_vapi_DATA = \ $(NULL) # Don't rebuild vapi every time gir is updated. +if ENABLE_LIBGNOMEKBD vapi_deps = \ $(srcdir)/IBus-1.0.metadata \ $(srcdir)/IBus-1.0-custom.vala \ | \ + $(builddir)/gkbd.vapi \ $(top_srcdir)/src/IBus-@IBUS_API_VERSION@.gir \ $(NULL) +else +vapi_deps = \ + $(srcdir)/IBus-1.0.metadata \ + $(srcdir)/IBus-1.0-custom.vala \ + | \ + $(top_srcdir)/src/IBus-@IBUS_API_VERSION@.gir \ + $(NULL) +endif ibus-@IBUS_API_VERSION@.vapi: $(vapi_deps) $(AM_V_GEN) $(VAPIGEN) --library ibus-@IBUS_API_VERSION@ \ @@ -41,11 +51,21 @@ ibus-@IBUS_API_VERSION@.vapi: $(vapi_deps) $(top_srcdir)/src/IBus-@IBUS_API_VERSION@.gir \ $(srcdir)/IBus-1.0-custom.vala +if ENABLE_LIBGNOMEKBD +$(builddir)/gkbd.vapi: + $(VAPIGEN) --metadatadir . --library gkbd \ + --pkg gtk+-3.0 --pkg glib-2.0 --pkg gmodule-2.0 \ + /usr/share/gir-1.0/Gkbd-3.0.gir + $(NULL) +endif + EXTRA_DIST = \ + Gkbd-3.0.metadata \ IBus-1.0.metadata \ IBus-1.0-custom.vala \ config.vapi \ xi.vapi \ + Xkl-1.0.metadata \ $(NULL) MAINTAINERCLEANFILES = ibus-@IBUS_API_VERSION@.vapi 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/client/x11/gdk-private.c b/client/x11/gdk-private.c index 009a5b0..20689c3 100644 --- a/client/x11/gdk-private.c +++ b/client/x11/gdk-private.c @@ -27,8 +27,7 @@ #include #include -#ifdef HAVE_X11_XKBLIB_H -# define HAVE_XKB +#ifdef HAVE_XKB # include #endif diff --git a/configure.ac b/configure.ac index cc7d0e0..df0d4a7 100644 --- a/configure.ac +++ b/configure.ac @@ -252,6 +252,63 @@ else enable_xim="no (disabled, use --enable-xim to enable)" fi +# --enable-xkb option. +AC_ARG_ENABLE(xkb, + AS_HELP_STRING([--enable-xkb], + [Build xkb]), + [enable_xkb=$enableval], + [enable_xkb=no] +) +AM_CONDITIONAL([ENABLE_XKB], [test x"$enable_xkb" = x"yes"]) +if test x"$enable_xkb" = x"yes"; then + PKG_CHECK_MODULES(X11, [ + x11 + ]) + PKG_CHECK_MODULES(XKB, + [xkbfile],, + [XKB_LIBS="-lxkbfile"] + ) + AC_DEFINE(HAVE_XKB, 1, [define to 1 if you have xkbfile]) + HAVE_IBUS_XKB=true +else + enable_xkb="no (disabled, use --enable-xkb to enable)" + HAVE_IBUS_XKB=false +fi +AC_SUBST(HAVE_IBUS_XKB) + +# --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]) @@ -517,6 +574,7 @@ src/ibusversion.h src/tests/Makefile bus/Makefile engine/Makefile +engine/ibus-xkb/Makefile engine/simple.xml.in util/Makefile util/IMdkit/Makefile @@ -577,5 +635,7 @@ Build options: Panel icon "$IBUS_ICON_KEYBOARD" Enable surrounding-text $enable_surrounding_text Run test cases $enable_tests + Build XKB $enable_xkb + Build libgnomebkd $enable_libgnomekbd ]) diff --git a/data/ibus.schemas.in b/data/ibus.schemas.in index 53ba05c..07169ae 100644 --- a/data/ibus.schemas.in +++ b/data/ibus.schemas.in @@ -26,6 +26,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 @@ -34,6 +80,54 @@ [Control+space,Zenkaku_Hankaku,Alt+Kanji,Alt+grave,Hangul,Alt+Release+Alt_R] Trigger shortcut keys + The shortcut keys for turning input method on or off + + + + /schemas/desktop/ibus/general/hotkey/trigger_accel + /desktop/ibus/general/hotkey/trigger_accel + ibus + list + string + [<Control>space] + + Trigger shortcut keys for gtk_accelerator_parse + The shortcut keys for turning input method on or off + + + + /schemas/desktop/ibus/general/hotkey/trigger_accel_backward + /desktop/ibus/general/hotkey/trigger_accel_backward + ibus + list + string + [<Control><Shift>space] + + Trigger reverse shortcut keys for gtk_accelerator_parse + The reverse shortcut keys for turning input method on or off + + + + /schemas/desktop/ibus/general/hotkey/trigger_ja + /desktop/ibus/general/hotkey/trigger_ja + ibus + list + string + [Zenkaku_Hankaku] + + Trigger shortcut keys for ja gtk_accelerator_parse + The shortcut keys for turning input method on or off + + + + /schemas/desktop/ibus/general/hotkey/trigger_ko + /desktop/ibus/general/hotkey/trigger_ko + ibus + list + string + [Hangul, Alt_R] + + Trigger shortcut keys for ko gtk_accelerator_parse The shortcut keys for turning input method on or off diff --git a/engine/Makefile.am b/engine/Makefile.am index b3b46be..7a806fc 100644 --- a/engine/Makefile.am +++ b/engine/Makefile.am @@ -22,6 +22,12 @@ libibus = $(top_builddir)/src/libibus-@IBUS_API_VERSION@.la +SUBDIRS = + +if ENABLE_XKB +SUBDIRS += ibus-xkb +endif + INCLUDES = \ -I$(top_srcdir)/src \ -I$(top_builddir)/src \ diff --git a/engine/ibus-xkb/Makefile.am b/engine/ibus-xkb/Makefile.am new file mode 100644 index 0000000..65ede37 --- /dev/null +++ b/engine/ibus-xkb/Makefile.am @@ -0,0 +1,64 @@ +# vim:set noet ts=4: +# +# ibus - The Input Bus +# +# Copyright (c) 2012 Takao Fujiwara +# 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 program; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place, Suite 330, +# Boston, MA 02111-1307 USA + +libibus = $(top_builddir)/src/libibus-@IBUS_API_VERSION@.la + +INCLUDES = \ + -I$(top_srcdir)/src \ + -I$(top_builddir)/src \ + $(NULL) + +AM_CFLAGS = \ + $(INCLUDES) \ + -DG_LOG_DOMAIN=\"IBUS\" \ + -DIBUS_DISABLE_DEPRECATED \ + -Wno-unused-variable \ + -Wno-unused-but-set-variable \ + -Wno-unused-function \ + $(NULL) + +AM_LDADD = \ + $(NULL) + +libexec_PROGRAMS = ibus-xkb + +ibus_xkb_SOURCES = \ + ibus-xkb-main.c \ + xkblib.h \ + xkblib.c \ + $(NULL) +ibus_xkb_CFLAGS = \ + @XKB_CFLAGS@ \ + @X11_CFLAGS@ \ + @GLIB2_CFLAGS@ \ + $(NULL) +ibus_xkb_LDADD = \ + @XKB_LIBS@ \ + @X11_LIBS@ \ + @GLIB2_LIBS@ \ + $(libibus) \ + $(NULL) + +$(libibus): + $(MAKE) -C $(top_builddir)/src + +-include $(top_srcdir)/git.mk diff --git a/engine/ibus-xkb/ibus-xkb-main.c b/engine/ibus-xkb/ibus-xkb-main.c new file mode 100644 index 0000000..2529e4d --- /dev/null +++ b/engine/ibus-xkb/ibus-xkb-main.c @@ -0,0 +1,111 @@ +/* -*- 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 +#include +#include + +#ifdef ENABLE_NLS +#include +#endif + +#include "xkblib.h" + +static gboolean get_layout = FALSE; +static gboolean get_group = FALSE; +static gchar *layout = NULL; +static gchar *variant = NULL; +static gchar *option = NULL; +static int group = 0; + +static const GOptionEntry entries[] = +{ + { "get", 'g', 0, G_OPTION_ARG_NONE, &get_layout, N_("Get current xkb layout"), NULL }, + { "layout", 'l', 0, G_OPTION_ARG_STRING, &layout, N_("Set xkb LAYOUT"), N_("LAYOUT") }, + { "variant", 'v', 0, G_OPTION_ARG_STRING, &variant, N_("Set xkb VARIANT"), N_("VARIANT") }, + { "option", 'o', 0, G_OPTION_ARG_STRING, &option, N_("Set xkb OPTION"), N_("OPTION") }, + { "get-group", 'G', 0, G_OPTION_ARG_NONE, &get_group, N_("Get current xkb state"), NULL }, + { NULL }, +}; + +int +main (int argc, char *argv[]) +{ + GOptionContext *context; + GError *error = NULL; + Display *xdisplay; + +#ifdef ENABLE_NLS + setlocale (LC_ALL, ""); + + bindtextdomain (GETTEXT_PACKAGE, GLIB_LOCALE_DIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); +#endif + + context = g_option_context_new ("- ibus daemon"); + + g_option_context_set_translation_domain (context, GETTEXT_PACKAGE); + g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE); + + if (!g_option_context_parse (context, &argc, &argv, &error)) { + g_printerr ("Option parsing failed: %s\n", error->message); + return -1; + } + + xdisplay = XOpenDisplay (NULL); + if (xdisplay == NULL) { + g_warning ("Could not open display"); + return -1; + } + ibus_xkb_init (xdisplay); + + if (layout) { + ibus_xkb_set_layout (layout, variant, option); + } + if (get_layout) { + layout = ibus_xkb_get_current_layout (); + variant = ibus_xkb_get_current_variant (); + option = ibus_xkb_get_current_option (); + g_printf ("layout: %s\n" + "variant: %s\n" + "option: %s\n", + layout ? layout : "", + variant ? variant : "", + option ? option : ""); + g_free (layout); + g_free (variant); + g_free (option); + } + if (get_group) { + group = ibus_xkb_get_current_group (); + g_printf ("group: %d\n", group); + } + + ibus_xkb_finit (); + + return 0; +} diff --git a/engine/ibus-xkb/xkblib.c b/engine/ibus-xkb/xkblib.c new file mode 100644 index 0000000..bb25455 --- /dev/null +++ b/engine/ibus-xkb/xkblib.c @@ -0,0 +1,327 @@ +/* -*- 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 +#include +#include +#include /* for XKBrules.h */ +#include +#include +#include + +#include "xkblib.h" + +#ifndef XKB_RULES_XML_FILE +#define XKB_RULES_XML_FILE "/usr/share/X11/xkb/rules/evdev.xml" +#endif + +static gchar **default_layouts; +static gchar **default_variants; +static gchar **default_options; +static int default_layout_group; + +static Display * +get_xdisplay (Display *xdisplay) +{ + static Display *saved_xdisplay = NULL; + if (xdisplay != NULL) { + saved_xdisplay = xdisplay; + } + return saved_xdisplay; +} + +static void +init_xkb_default_layouts (Display *xdisplay) +{ + XkbStateRec state; + Atom xkb_rules_name, type; + int format; + unsigned long l, nitems, bytes_after; + unsigned char *prop = NULL; + + xkb_rules_name = XInternAtom (xdisplay, "_XKB_RULES_NAMES", TRUE); + if (xkb_rules_name == None) { + g_warning ("Could not get XKB rules atom"); + return; + } + if (XGetWindowProperty (xdisplay, + XDefaultRootWindow (xdisplay), + xkb_rules_name, + 0, 1024, FALSE, XA_STRING, + &type, &format, &nitems, &bytes_after, &prop) != Success) { + g_warning ("Could not get X property"); + return; + } + if (nitems < 3) { + g_warning ("Could not get group layout from X property"); + return; + } + for (l = 0; l < 2; l++) { + prop += strlen ((const char *) prop) + 1; + } + if (prop == NULL || *prop == '\0') { + g_warning ("No layouts form X property"); + return; + } + default_layouts = g_strsplit ((gchar *) prop, ",", -1); + prop += strlen ((const char *) prop) + 1; + default_variants = g_strsplit ((gchar *) prop, ",", -1); + prop += strlen ((const char *) prop) + 1; + default_options = g_strsplit ((gchar *) prop, ",", -1); + + if (XkbGetState (xdisplay, XkbUseCoreKbd, &state) != Success) { + g_warning ("Could not get state"); + return; + } + default_layout_group = state.group; +} + +static Bool +set_xkb_rules (Display *xdisplay, + const char *rules_file, const char *model, + const char *all_layouts, const char *all_variants, + const char *all_options) +{ + gchar *rules_path; + XkbRF_RulesPtr rules; + XkbRF_VarDefsRec rdefs; + XkbComponentNamesRec rnames; + XkbDescPtr xkb; + + rules_path = g_strdup ("./rules/evdev"); + rules = XkbRF_Load (rules_path, "C", TRUE, TRUE); + if (rules == NULL) { + g_return_val_if_fail (XKB_RULES_XML_FILE != NULL, FALSE); + + g_free (rules_path); + if (g_str_has_suffix (XKB_RULES_XML_FILE, ".xml")) { + rules_path = g_strndup (XKB_RULES_XML_FILE, + strlen (XKB_RULES_XML_FILE) - 4); + } else { + rules_path = g_strdup (XKB_RULES_XML_FILE); + } + rules = XkbRF_Load (rules_path, "C", TRUE, TRUE); + } + g_return_val_if_fail (rules != NULL, FALSE); + + memset (&rdefs, 0, sizeof (XkbRF_VarDefsRec)); + memset (&rnames, 0, sizeof (XkbComponentNamesRec)); + rdefs.model = model ? g_strdup (model) : NULL; + rdefs.layout = all_layouts ? g_strdup (all_layouts) : NULL; + rdefs.variant = all_variants ? g_strdup (all_variants) : NULL; + rdefs.options = all_options ? g_strdup (all_options) : NULL; + XkbRF_GetComponents (rules, &rdefs, &rnames); + xkb = XkbGetKeyboardByName (xdisplay, XkbUseCoreKbd, &rnames, + XkbGBN_AllComponentsMask, + XkbGBN_AllComponentsMask & + (~XkbGBN_GeometryMask), True); + if (!xkb) { + g_warning ("Cannot load new keyboard description."); + return FALSE; + } + XkbRF_SetNamesProp (xdisplay, rules_path, &rdefs); + g_free (rules_path); + g_free (rdefs.model); + g_free (rdefs.layout); + g_free (rdefs.variant); + g_free (rdefs.options); + + return TRUE; +} + +static Bool +update_xkb_properties (Display *xdisplay, + const char *rules_file, const char *model, + const char *all_layouts, const char *all_variants, + const char *all_options) +{ + int len; + char *pval; + char *next; + Atom rules_atom; + Window root_window; + + len = (rules_file ? strlen (rules_file) : 0); + len += (model ? strlen (model) : 0); + len += (all_layouts ? strlen (all_layouts) : 0); + len += (all_variants ? strlen (all_variants) : 0); + len += (all_options ? strlen (all_options) : 0); + + if (len < 1) { + return TRUE; + } + len += 5; /* trailing NULs */ + + rules_atom = XInternAtom (xdisplay, _XKB_RF_NAMES_PROP_ATOM, False); + root_window = XDefaultRootWindow (xdisplay); + pval = next = g_new0 (char, len + 1); + if (!pval) { + return TRUE; + } + + if (rules_file) { + strcpy (next, rules_file); + next += strlen (rules_file); + } + *next++ = '\0'; + if (model) { + strcpy (next, model); + next += strlen (model); + } + *next++ = '\0'; + if (all_layouts) { + strcpy (next, all_layouts); + next += strlen (all_layouts); + } + *next++ = '\0'; + if (all_variants) { + strcpy (next, all_variants); + next += strlen (all_variants); + } + *next++ = '\0'; + if (all_options) { + strcpy (next, all_options); + next += strlen (all_options); + } + *next++ = '\0'; + if ((next - pval) != len) { + g_free (pval); + return TRUE; + } + + XChangeProperty (xdisplay, root_window, + rules_atom, XA_STRING, 8, PropModeReplace, + (unsigned char *) pval, len); + XSync(xdisplay, False); + + return TRUE; +} + +void +ibus_xkb_init (Display *xdisplay) +{ + get_xdisplay (xdisplay); + init_xkb_default_layouts (xdisplay); +} + +void +ibus_xkb_finit (void) +{ + g_strfreev (default_layouts); + default_layouts = NULL; + g_strfreev (default_variants); + default_variants = NULL; + g_strfreev (default_options); + default_options = NULL; +} + +gchar * +ibus_xkb_get_current_layout (void) +{ + if (default_layouts == NULL) { + g_warning ("Your system seems not to support XKB."); + return NULL; + } + + return g_strjoinv (",", (gchar **) default_layouts); +} + +gchar * +ibus_xkb_get_current_variant (void) +{ + if (default_variants == NULL) { + return NULL; + } + + return g_strjoinv (",", (gchar **) default_variants); +} + +gchar * +ibus_xkb_get_current_option (void) +{ + if (default_options == NULL) { + return NULL; + } + + return g_strjoinv (",", (gchar **) default_options); +} + +gboolean +ibus_xkb_set_layout (const char *layouts, + const char *variants, + const char *options) +{ + Display *xdisplay; + gboolean retval; + gchar *layouts_line; + + if (default_layouts == NULL) { + g_warning ("Your system seems not to support XKB."); + return FALSE; + } + + if (layouts == NULL || g_strcmp0 (layouts, "default") == 0) { + layouts_line = g_strjoinv (",", (gchar **) default_layouts); + } else { + layouts_line = g_strdup (layouts); + } + + xdisplay = get_xdisplay (NULL); + retval = set_xkb_rules (xdisplay, + "evdev", "evdev", + layouts_line, variants, options); + update_xkb_properties (xdisplay, + "evdev", "evdev", + layouts_line, variants, options); + g_free (layouts_line); + + return retval; +} + +int +ibus_xkb_get_current_group (void) +{ + Display *xdisplay = get_xdisplay (NULL); + XkbStateRec state; + + if (default_layouts == NULL) { + g_warning ("Your system seems not to support XKB."); + return 0; + } + + if (xdisplay == NULL) { + g_warning ("ibus-xkb is not initialized."); + return 0; + } + + if (XkbGetState (xdisplay, XkbUseCoreKbd, &state) != Success) { + g_warning ("Could not get state"); + return 0; + } + + return state.group; +} diff --git a/engine/ibus-xkb/xkblib.h b/engine/ibus-xkb/xkblib.h new file mode 100644 index 0000000..36597c3 --- /dev/null +++ b/engine/ibus-xkb/xkblib.h @@ -0,0 +1,41 @@ +/* -*- 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 __XKBLIB_H_ +#define __XKBLIB_H_ + +#include + +G_BEGIN_DECLS + +void ibus_xkb_init (Display *xdisplay); +void ibus_xkb_finit (void); +gchar *ibus_xkb_get_current_layout (void); +gchar *ibus_xkb_get_current_variant (void); +gchar *ibus_xkb_get_current_option (void); +gboolean ibus_xkb_set_layout (const char *layouts, + const char *variants, + const char *options); +int ibus_xkb_get_current_group (void); + +G_END_DECLS +#endif diff --git a/engine/main.vala b/engine/main.vala index acfa737..afadca0 100644 --- a/engine/main.vala +++ b/engine/main.vala @@ -21,6 +21,85 @@ */ using IBus; +using GLib; + +private void print_xml(string layout, + string layout_desc, + string? variant, + string? variant_desc, + string lang) { + string name = "xkb:%s:%s:%s".printf(layout, variant ?? "", lang); + string keymap = layout; + string desc = layout_desc; + string symbol = lang; + + if (variant != null) { + keymap = "%s(%s)".printf(layout, variant); + } + + if (variant_desc != null) { + desc = variant_desc; + } + + desc = desc.replace("<", "<").replace(">", ">"); + + if (lang.length > 2) { + symbol = lang[0:2]; + } + + string engine = " + + %s + %s + GPL + Peng Huang <shawn.p.huang@gmail.com> + %s + %s + %s + ibus-keyboard + %s + %d + ".printf(name, lang, keymap, desc, desc, symbol, 99); + print (engine); +} + +private void print_component() { + IBus.XKBConfigRegistry registry = new IBus.XKBConfigRegistry(); + GLib.List layouts = registry.layout_list_get_layouts(); + GLib.List variants; + GLib.List langs; + string layout_desc; + const string header = ""; + const string footer = " +"; + + print (header); + for (unowned GLib.List l = layouts; l != null; l = l.next) { + variants = registry.layout_list_get_variants(l.data); + langs = registry.layout_lang_get_langs(l.data); + layout_desc = registry.layout_desc_get_desc(l.data); + for (unowned GLib.List lg = langs; lg != null; lg = lg.next) { + print_xml(l.data, layout_desc, null, null, lg.data); + } + for (unowned GLib.List v = variants; v != null; v = v.next) { + var l_v = "%s(%s)".printf(l.data, v.data); + unowned GLib.List l_v_langs = null; + GLib.List _l_v_langs = registry.layout_lang_get_langs(l_v); + l_v_langs = _l_v_langs; + if (l_v_langs == null) { + l_v_langs = langs; + } + for (unowned GLib.List lg = l_v_langs; lg != null; lg = lg.next) { + print_xml(l.data, + layout_desc, + v.data, + registry.variant_desc_get_desc(l_v), + lg.data); + } + } + } + print (footer); +} class DummyEngine : IBus.EngineSimple { } @@ -28,6 +107,13 @@ class DummyEngine : IBus.EngineSimple { public int main(string[] args) { IBus.init(); + if (args.length >= 2) { + if (args[1] == "--xml" || args[1] == "-x") { + print_component(); + return 0; + } + } + IBus.Bus bus = new IBus.Bus(); if (!bus.is_connected()) { warning("ibus-daemon does not exist."); diff --git a/engine/simple.xml.in.in b/engine/simple.xml.in.in index 25db578..7de949a 100644 --- a/engine/simple.xml.in.in +++ b/engine/simple.xml.in.in @@ -7,534 +7,5 @@ GPL http://code.google.com/p/ibus ibus - - - xkb:us::eng - eng - GPL - Peng Huang <shawn.p.huang@gmail.com> - us - English (US) - English (US) - ibus-keyboard - 99 - - - xkb:us:intl:eng - eng - GPL - Peng Huang <shawn.p.huang@gmail.com> - us(intl) - English (US, international with dead keys) - English (US, international with dead keys) - ibus-keyboard - 99 - - - xkb:us:colemak:eng - eng - GPL - Peng Huang <shawn.p.huang@gmail.com> - us(colemak) - English (Colemak) - English (Colemak) - ibus-keyboard - 99 - - - xkb:us:dvorak:eng - eng - GPL - Peng Huang <shawn.p.huang@gmail.com> - us(dvorak) - English (Dvorak) - English (Dvorak) - ibus-keyboard - 99 - - - xkb:us:altgr-intl:eng - eng - GPL - Peng Huang <shawn.p.huang@gmail.com> - us(altgr-intl) - English (international AltGr dead keys) - English (international AltGr dead keys) - ibus-keyboard - 99 - - - xkb:us:altgr-intl:eng - eng - GPL - Peng Huang <shawn.p.huang@gmail.com> - us(altgr-intl) - English (international AltGr dead keys) - English (international AltGr dead keys) - ibus-keyboard - 99 - - - xkb:be::ger - ger - GPL - Peng Huang <shawn.p.huang@gmail.com> - be - Belgian - Belgian - ibus-keyboard - 99 - - - xkb:be::nld - nld - GPL - Peng Huang <shawn.p.huang@gmail.com> - be - Belgian - Belgian - ibus-keyboard - 99 - - - xkb:be::fra - fra - GPL - Peng Huang <shawn.p.huang@gmail.com> - be - Belgian - Belgian - ibus-keyboard - 99 - - - xkb:br::por - por - GPL - Peng Huang <shawn.p.huang@gmail.com> - br - Portuguese (Brazil) - Portuguese (Brazil) - ibus-keyboard - 99 - - - xkb:bg::bul - bul - GPL - Peng Huang <shawn.p.huang@gmail.com> - bg - Bulgarian - Bulgarian - ibus-keyboard - 99 - - - xkb:bg:phonetic:bul - bul - GPL - Peng Huang <shawn.p.huang@gmail.com> - bg(phonetic) - Bulgarian (traditional phonetic) - Bulgarian (traditional phonetic) - ibus-keyboard - 99 - - - xkb:ca::fra - fra - GPL - Peng Huang <shawn.p.huang@gmail.com> - ca - French (Canada) - French (Canada) - ibus-keyboard - 99 - - - xkb:ca:eng:eng - eng - GPL - Peng Huang <shawn.p.huang@gmail.com> - ca(eng) - English (Canada) - English (Canada) - ibus-keyboard - 99 - - - xkb:hr::scr - scr - GPL - Peng Huang <shawn.p.huang@gmail.com> - hr - Croatian - Croatian - ibus-keyboard - 99 - - - xkb:cz::cze - cze - GPL - Peng Huang <shawn.p.huang@gmail.com> - cz - Czech - Czech - ibus-keyboard - 99 - - - xkb:dk::dan - dan - GPL - Peng Huang <shawn.p.huang@gmail.com> - dk - Danish - Danish - ibus-keyboard - 99 - - - xkb:ee::est - est - GPL - Peng Huang <shawn.p.huang@gmail.com> - ee - Estonian - Estonian - ibus-keyboard - 99 - - - xkb:fi::fin - fin - GPL - Peng Huang <shawn.p.huang@gmail.com> - fi - Finnish - Finnish - ibus-keyboard - 99 - - - xkb:fr::fra - fra - GPL - Peng Huang <shawn.p.huang@gmail.com> - fr - French - French - ibus-keyboard - 99 - - - xkb:de::ger - ger - GPL - Peng Huang <shawn.p.huang@gmail.com> - de - German - German - ibus-keyboard - 99 - - - xkb:de:neo:ger - ger - GPL - Peng Huang <shawn.p.huang@gmail.com> - de(neo) - German (Neo 2) - German (Neo 2) - ibus-keyboard - 99 - - - xkb:gr::gre - gre - GPL - Peng Huang <shawn.p.huang@gmail.com> - gr - Greek - Greek - ibus-keyboard - 99 - - - xkb:hu::hun - hun - GPL - Peng Huang <shawn.p.huang@gmail.com> - hu - Hungarian - Hungarian - ibus-keyboard - 99 - - - xkb:il::heb - heb - GPL - Peng Huang <shawn.p.huang@gmail.com> - il - Hebrew - Hebrew - ibus-keyboard - 99 - - - xkb:it::ita - ita - GPL - Peng Huang <shawn.p.huang@gmail.com> - it - Italian - Italian - ibus-keyboard - 99 - - - xkb:jp::jpn - jpn - GPL - Peng Huang <shawn.p.huang@gmail.com> - jp - Japanese - Japanese - ibus-keyboard - 99 - - - xkb:latam::spa - spa - GPL - Peng Huang <shawn.p.huang@gmail.com> - latam - Spanish (Latin American) - Spanish (Latin American) - ibus-keyboard - 99 - - - xkb:lt::lit - lit - GPL - Peng Huang <shawn.p.huang@gmail.com> - lt - Lithuanian - Lithuanian - ibus-keyboard - 99 - - - xkb:lv:apostrophe:lav - lav - GPL - Peng Huang <shawn.p.huang@gmail.com> - lv(apostrophe) - Latvian (apostrophe variant) - Latvian (apostrophe variant) - ibus-keyboard - 99 - - - xkb:pl::pol - pol - GPL - Peng Huang <shawn.p.huang@gmail.com> - pl - Polish - Polish - ibus-keyboard - 99 - - - xkb:pt::por - por - GPL - Peng Huang <shawn.p.huang@gmail.com> - pt - Portuguese - Portuguese - ibus-keyboard - 99 - - - xkb:ro::rum - rum - GPL - Peng Huang <shawn.p.huang@gmail.com> - ro - Romanian - Romanian - ibus-keyboard - 99 - - - xkb:ru::rus - rus - GPL - Peng Huang <shawn.p.huang@gmail.com> - ru - Russian - Russian - ibus-keyboard - 99 - - - xkb:ru:phonetic:rus - rus - GPL - Peng Huang <shawn.p.huang@gmail.com> - ru(phonetic) - Russian (phonetic) - Russian (phonetic) - ibus-keyboard - 99 - - - xkb:rs::srp - srp - GPL - Peng Huang <shawn.p.huang@gmail.com> - rs - Serbian - Serbian - ibus-keyboard - 99 - - - xkb:si::slv - slv - GPL - Peng Huang <shawn.p.huang@gmail.com> - si - Slovenian - Slovenian - ibus-keyboard - 99 - - - xkb:sk::slo - slo - GPL - Peng Huang <shawn.p.huang@gmail.com> - sk - Slovak - Slovak - ibus-keyboard - 99 - - - xkb:es::spa - spa - GPL - Peng Huang <shawn.p.huang@gmail.com> - es - Spanish - Spanish - ibus-keyboard - 99 - - - xkb:es:cat:cat - cat - GPL - Peng Huang <shawn.p.huang@gmail.com> - es(cat) - Catalan (Spain, with middle-dot L) - Catalan (Spain, with middle-dot L) - ibus-keyboard - 99 - - - xkb:se::swe - swe - GPL - Peng Huang <shawn.p.huang@gmail.com> - se - Swedish - Swedish - ibus-keyboard - 99 - - - xkb:ch::ger - ger - GPL - Peng Huang <shawn.p.huang@gmail.com> - ch - German (Switzerland) - German (Switzerland) - ibus-keyboard - 99 - - - xkb:ch:fr:fra - fra - GPL - Peng Huang <shawn.p.huang@gmail.com> - ch(fr) - French (Switzerland) - French (Switzerland) - ibus-keyboard - 99 - - - xkb:tr::tur - tur - GPL - Peng Huang <shawn.p.huang@gmail.com> - tr - Turkish - Turkish - ibus-keyboard - 99 - - - xkb:ua::ukr - ukr - GPL - Peng Huang <shawn.p.huang@gmail.com> - ua - Ukrainian - Ukrainian - ibus-keyboard - 99 - - - xkb:gb:extd:eng - eng - GPL - Peng Huang <shawn.p.huang@gmail.com> - gb(extd) - English (UK, extended WinKeys) - English (UK, extended WinKeys) - ibus-keyboard - 99 - - - xkb:gb:dvorak:eng - eng - GPL - Peng Huang <shawn.p.huang@gmail.com> - gb(dvorak) - English (UK, Dvorak) - English (UK, Dvorak) - ibus-keyboard - 99 - - - xkb:kr:kr104:kor - kor - GPL - Peng Huang <shawn.p.huang@gmail.com> - kr(kr104) - Korean (101/104 key compatible) - Korean (101/104 key compatible) - ibus-keyboard - 99 - - + diff --git a/ibus-1.0.pc.in b/ibus-1.0.pc.in index 9f593ab..66b902a 100644 --- a/ibus-1.0.pc.in +++ b/ibus-1.0.pc.in @@ -1,9 +1,13 @@ prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ +libexecdir=@libexecdir@ includedir=@includedir@ datadir=@datadir@ pkgdatadir=@datadir@/ibus +have_ibus_xkb=@HAVE_IBUS_XKB@ +ibus_xkb=${libexecdir}/ibus-xkb +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/setup/main.py b/setup/main.py index 300f11b..141cdc2 100644 --- a/setup/main.py +++ b/setup/main.py @@ -71,6 +71,14 @@ class Setup(object): self.__builder.set_translation_domain(DOMAINNAME) self.__builder.add_from_file(gtk_builder_file); self.__bus = None + + # In the latest pygobject3 3.3.4 or later, g_variant_dup_strv + # returns the allocated strv but in the previous release, + # it returned the tuple of (strv, length) + self.__tuple_for_variant_strv = False + if type(GLib.Variant.new_strv([]).dup_strv()) == tuple: + self.__tuple_for_variant_strv = True + self.__init_bus() self.__init_ui() @@ -183,14 +191,22 @@ class Setup(object): # init engine page self.__engines = self.__bus.list_engines() + value = self.__config.get_value("general", "load_xkb_layouts") + load_layouts = [] + if value != None: + load_layouts = self.__variant_dup_strv(value) + engines = [] + for engine in self.__engines: + if not engine.get_name().startswith('xkb:'): + engines.append(engine) + elif engine.get_layout() in load_layouts: + engines.append(engine) + self.__combobox = self.__builder.get_object("combobox_engines") - self.__combobox.set_engines(self.__engines) + self.__combobox.set_engines(engines) - tmp_dict = {} - for e in self.__engines: - tmp_dict[e.get_name()] = e engine_names = values.get("preload_engines", []) - engines = [tmp_dict[name] for name in engine_names if name in tmp_dict] + engines = self.__get_engine_descs_from_names(engine_names) self.__treeview = self.__builder.get_object("treeview_engines") self.__treeview.set_engines(engines) @@ -239,6 +255,12 @@ class Setup(object): self.__init_panel() self.__init_general() + def __variant_dup_strv(self, variant): + if self.__tuple_for_variant_strv: + return variant.dup_strv()[0] + else: + return variant.dup_strv() + def __combobox_notify_active_engine_cb(self, combobox, property): engine = self.__combobox.get_active_engine() button = self.__builder.get_object("button_engine_add") @@ -263,6 +285,13 @@ class Setup(object): args.append(path.basename(setup_path)) return args + def __get_engine_descs_from_names(self, engine_names): + tmp_dict = {} + for e in self.__engines: + tmp_dict[e.get_name()] = e + engines = [tmp_dict[name] for name in engine_names if name in tmp_dict] + return engines + def __treeview_notify_cb(self, treeview, prop): if prop.name not in ("active-engine", "engines"): return diff --git a/src/Makefile.am b/src/Makefile.am index df4ada3..e249ada 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -194,6 +194,11 @@ typelibs_DATA = $(INTROSPECTION_GIRS:.gir=.typelib) CLEANFILES += $(dist_gir_DATA) $(typelibs_DATA) endif +if ENABLE_XKB +ibus_sources += ibusxkbxml.c +ibus_headers += ibusxkbxml.h +endif + # 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..cbd746c 100644 --- a/ui/gtk3/Makefile.am +++ b/ui/gtk3/Makefile.am @@ -47,6 +47,9 @@ USE_SYMBOL_ICON = FALSE # force include config.h before gi18n.h. AM_CPPFLAGS = -include $(CONFIG_HEADER) +HAVE_IBUS_XKB_C = $(strip $(subst false, FALSE, $(subst true, TRUE, $(HAVE_IBUS_XKB)))) +HAVE_IBUS_GKBD_C = $(strip $(subst false, FALSE, $(subst true, TRUE, $(HAVE_IBUS_GKBD)))) + AM_CFLAGS = \ @GLIB2_CFLAGS@ \ @GIO2_CFLAGS@ \ @@ -58,6 +61,10 @@ AM_CFLAGS = \ -DBINDIR=\"$(bindir)\" \ -DIBUS_DISABLE_DEPRECATED \ -DSWITCHER_USE_SYMBOL_ICON=$(USE_SYMBOL_ICON) \ + -DHAVE_IBUS_XKB=$(HAVE_IBUS_XKB_C) \ + -DHAVE_IBUS_GKBD=$(HAVE_IBUS_GKBD_C) \ + -DIBUS_XKB_COMMAND=\"$(libexecdir)/ibus-xkb\" \ + -DXKB_LAYOUTS_MAX_LENGTH=4 \ -Wno-unused-variable \ -Wno-unused-but-set-variable \ -Wno-unused-function \ @@ -91,6 +98,7 @@ ibus_ui_gtk3_SOURCES = \ application.vala \ candidatearea.vala \ candidatepanel.vala \ + gkbdlayout.vala \ handle.vala \ iconwidget.vala \ keybindingmanager.vala \ @@ -99,17 +107,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/keybindingmanager.vala b/ui/gtk3/keybindingmanager.vala index 5ff0c65..bfe560c 100644 --- a/ui/gtk3/keybindingmanager.vala +++ b/ui/gtk3/keybindingmanager.vala @@ -41,15 +41,18 @@ public class KeybindingManager : GLib.Object { private class Keybinding { public Keybinding(uint keysym, Gdk.ModifierType modifiers, - KeybindingHandlerFunc handler) { + KeybindingHandlerFunc handler, + bool reverse) { this.keysym = keysym; this.modifiers = modifiers; this.handler = handler; + this.reverse = reverse; } public uint keysym { get; set; } public Gdk.ModifierType modifiers { get; set; } public unowned KeybindingHandlerFunc handler { get; set; } + public bool reverse { get; set; } } /** @@ -57,7 +60,7 @@ public class KeybindingManager : GLib.Object { * * @param event passing on gdk event */ - public delegate void KeybindingHandlerFunc(Gdk.Event event); + public delegate void KeybindingHandlerFunc(Gdk.Event event, bool reverse); private KeybindingManager() { @@ -73,7 +76,8 @@ public class KeybindingManager : GLib.Object { */ public bool bind(uint keysym, Gdk.ModifierType modifiers, - KeybindingHandlerFunc handler) { + KeybindingHandlerFunc handler, + bool reverse) { unowned X.Display display = Gdk.x11_get_default_xdisplay(); int keycode = display.keysym_to_keycode(keysym); @@ -84,7 +88,7 @@ public class KeybindingManager : GLib.Object { grab_keycode (Gdk.Display.get_default(), keysym, modifiers); // store binding - Keybinding binding = new Keybinding(keysym, modifiers, handler); + Keybinding binding = new Keybinding(keysym, modifiers, handler, reverse); m_bindings.append(binding); return true; @@ -198,7 +202,7 @@ public class KeybindingManager : GLib.Object { if (event.key.keyval != binding.keysym || modifiers != binding.modifiers) continue; - binding.handler(event); + binding.handler(event, binding.reverse); return; } } diff --git a/ui/gtk3/panel.vala b/ui/gtk3/panel.vala index 40079ec..8e6f756 100644 --- a/ui/gtk3/panel.vala +++ b/ui/gtk3/panel.vala @@ -21,6 +21,20 @@ */ class Panel : IBus.PanelService { + private class Keybinding { + public Keybinding(uint keysym, + Gdk.ModifierType modifiers, + bool reverse) { + this.keysym = keysym; + this.modifiers = modifiers; + this.reverse = reverse; + } + + public uint keysym { get; set; } + public Gdk.ModifierType modifiers { get; set; } + public bool reverse { get; set; } + } + private IBus.Bus m_bus; private IBus.Config m_config; private Gtk.StatusIcon m_status_icon; @@ -33,10 +47,17 @@ class Panel : IBus.PanelService { private GLib.Pid m_setup_pid = 0; private Gtk.AboutDialog m_about_dialog; private Gtk.CssProvider m_css_provider; + 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 const string ACCELERATOR_SWITCH_IME_BACKWARD = "space"; - private uint m_switch_keysym = 0; - private Gdk.ModifierType m_switch_modifiers = 0; + private GLib.List m_keybindings = new GLib.List(); public Panel(IBus.Bus bus) { GLib.assert(bus.is_connected()); @@ -59,7 +80,6 @@ class Panel : IBus.PanelService { m_candidate_panel.page_down.connect((w) => this.page_down()); m_switcher = new Switcher(); - bind_switch_shortcut(); m_property_manager = new PropertyManager(); m_property_manager.property_activate.connect((k, s) => { @@ -71,64 +91,135 @@ class Panel : IBus.PanelService { ~Panel() { unbind_switch_shortcut(); - } - private void bind_switch_shortcut() { - var keybinding_manager = KeybindingManager.get_instance(); + if (HAVE_IBUS_GKBD && m_gkbdlayout != null) { + m_gkbdlayout.changed.disconnect(gkbdlayout_changed_cb); + m_gkbdlayout.stop_listen(); + m_gkbdlayout = null; + } + + m_xkblayout = null; + } - var accelerator = ACCELERATOR_SWITCH_IME_FOREWARD; + private void keybinding_manager_bind(KeybindingManager keybinding_manager, + string? accelerator, + bool reverse) { + uint switch_keysym = 0; + Gdk.ModifierType switch_modifiers = 0; Gtk.accelerator_parse(accelerator, - out m_switch_keysym, out m_switch_modifiers); + out switch_keysym, out switch_modifiers); - // Map virtual modifiers to (i.e.Mod2, Mod3, ...) + // Map virtual modifiers to (i.e. Mod2, Mod3, ...) const Gdk.ModifierType VIRTUAL_MODIFIERS = ( Gdk.ModifierType.SUPER_MASK | Gdk.ModifierType.HYPER_MASK | Gdk.ModifierType.META_MASK); - if ((m_switch_modifiers & VIRTUAL_MODIFIERS) != 0) { + if ((switch_modifiers & VIRTUAL_MODIFIERS) != 0) { // workaround a bug in gdk vapi vala > 0.18 // https://bugzilla.gnome.org/show_bug.cgi?id=677559 #if VALA_0_18 Gdk.Keymap.get_default().map_virtual_modifiers( - ref m_switch_modifiers); + ref switch_modifiers); #else - if ((m_switch_modifiers & Gdk.ModifierType.SUPER_MASK) != 0) - m_switch_modifiers |= Gdk.ModifierType.MOD4_MASK; - if ((m_switch_modifiers & Gdk.ModifierType.HYPER_MASK) != 0) - m_switch_modifiers |= Gdk.ModifierType.MOD4_MASK; + if ((switch_modifiers & Gdk.ModifierType.SUPER_MASK) != 0) + switch_modifiers |= Gdk.ModifierType.MOD4_MASK; + if ((switch_modifiers & Gdk.ModifierType.HYPER_MASK) != 0) + switch_modifiers |= Gdk.ModifierType.MOD4_MASK; #endif - m_switch_modifiers &= ~VIRTUAL_MODIFIERS; + switch_modifiers &= ~VIRTUAL_MODIFIERS; } - if (m_switch_keysym == 0 && m_switch_modifiers == 0) { + if (switch_keysym == 0 && switch_modifiers == 0) { warning("Parse accelerator '%s' failed!", accelerator); return; } - keybinding_manager.bind(m_switch_keysym, m_switch_modifiers, - (e) => handle_engine_switch(e, false)); + Keybinding keybinding = new Keybinding(switch_keysym, + switch_modifiers, + reverse); + m_keybindings.append(keybinding); - // accelerator already has Shift mask - if ((m_switch_modifiers & Gdk.ModifierType.SHIFT_MASK) != 0) - return; + keybinding_manager.bind(switch_keysym, switch_modifiers, + (e, _reverse) => handle_engine_switch(e, _reverse), + reverse); + } + + // ToDo: Customize the input method with ibus-setup + private void bind_switch_shortcut() { + string locale = GLib.Intl.setlocale(GLib.LocaleCategory.ALL, + null); + if (locale == null) { + locale = "C"; + } + + string[] ACCELERATOR_IME_HOTKEYS = {}; + ACCELERATOR_IME_HOTKEYS += ACCELERATOR_SWITCH_IME_FOREWARD; + + if (m_config != null) { + GLib.Variant variant = m_config.get_value("general/hotkey", + "trigger_accel"); + if (variant != null) { + ACCELERATOR_IME_HOTKEYS = {}; + for (int i = 0; i < variant.n_children(); i++) { + ACCELERATOR_IME_HOTKEYS += variant.get_child_value(i).dup_string(); + } + } + } + + if (ACCELERATOR_IME_HOTKEYS.length == 1 && + ACCELERATOR_IME_HOTKEYS[0] == ACCELERATOR_SWITCH_IME_FOREWARD) { + // FIXME: When us keyboard is used, Zenkaku_Hankaku does not work. + /* + if (locale[0:2] == "ja") { + ACCELERATOR_IME_HOTKEYS += "Zenkaku_Hankaku"; + } + */ + if (locale[0:2] == "ko") { + ACCELERATOR_IME_HOTKEYS += "Hangul"; + ACCELERATOR_IME_HOTKEYS += "Alt_R"; + } + } + + var keybinding_manager = KeybindingManager.get_instance(); + + foreach (var accelerator in ACCELERATOR_IME_HOTKEYS) { + keybinding_manager_bind(keybinding_manager, accelerator, false); + } + + ACCELERATOR_IME_HOTKEYS = {}; + ACCELERATOR_IME_HOTKEYS += ACCELERATOR_SWITCH_IME_BACKWARD; - keybinding_manager.bind(m_switch_keysym, - m_switch_modifiers | Gdk.ModifierType.SHIFT_MASK, - (e) => handle_engine_switch(e, true)); + if (m_config != null) { + GLib.Variant variant = m_config.get_value("general/hotkey", + "trigger_accel_backward"); + if (variant != null) { + ACCELERATOR_IME_HOTKEYS = {}; + for (int i = 0; i < variant.n_children(); i++) { + ACCELERATOR_IME_HOTKEYS += variant.get_child_value(i).dup_string(); + } + } + } + + foreach (var accelerator in ACCELERATOR_IME_HOTKEYS) { + keybinding_manager_bind(keybinding_manager, accelerator, true); + } } private void unbind_switch_shortcut() { var keybinding_manager = KeybindingManager.get_instance(); - if (m_switch_keysym == 0 && m_switch_modifiers == 0) - return; + unowned GLib.List keybindings = m_keybindings; + + while (keybindings != null) { + Keybinding keybinding = keybindings.data; - keybinding_manager.unbind(m_switch_keysym, m_switch_modifiers); - keybinding_manager.unbind(m_switch_keysym, - m_switch_modifiers | Gdk.ModifierType.SHIFT_MASK); + keybinding_manager.unbind(keybinding.keysym, + keybinding.modifiers); - m_switch_keysym = 0; - m_switch_modifiers = 0; + // Need to get keybindings.next before GList.remove is called. + keybindings = keybindings.next; + m_keybindings.remove(keybinding); + } } private void set_custom_font() { @@ -196,12 +287,16 @@ class Panel : IBus.PanelService { } m_config = config; + bind_switch_shortcut(); if (m_config != null) { m_config.value_changed.connect(config_value_changed_cb); m_config.watch("general", "preload_engines"); m_config.watch("general", "engines_order"); + m_config.watch("general/hotkey", "trigger_accel"); + m_config.watch("general/hotkey", "trigger_accel_backward"); 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")); } else { @@ -211,6 +306,192 @@ 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); + + if (HAVE_IBUS_GKBD) { + 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(string layout) { + int[] retval = m_xkblayout.set_layout(layout); + 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(string layout) { + /* 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; + } + + 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(string layout) { + if (layout == "default" || layout == null) { + return; + } + + if (m_xkblayout == null) { + init_engines_order(); + } + + if (HAVE_IBUS_GKBD) { + if (set_gkbd_layout(layout)) { + return; + } + set_xkb_group_layout(layout); + return; + } + + m_xkblayout.set_layout(layout); + } + private void switch_engine(int i, bool force = false) { GLib.assert(i >= 0 && i < m_engines.length); @@ -225,15 +506,7 @@ class Panel : IBus.PanelService { return; } // set xkb layout - string cmdline = "setxkbmap %s".printf(engine.get_layout()); - try { - if (!GLib.Process.spawn_command_line_sync(cmdline)) { - warning("Switch xkb layout to %s failed.", - engine.get_layout()); - } - } catch (GLib.SpawnError e) { - warning("Execute setxkbmap failed: %s", e.message); - } + set_layout(engine.get_layout()); } private void config_value_changed_cb(IBus.Config config, @@ -245,6 +518,13 @@ class Panel : IBus.PanelService { return; } + if (section == "general/hotkey" && + name.length >= 13 && name[0:13] == "trigger_accel") { + unbind_switch_shortcut(); + bind_switch_shortcut(); + return; + } + if (section == "panel" && (name == "custom_font" || name == "use_custom_font")) { set_custom_font(); @@ -264,8 +544,7 @@ class Panel : IBus.PanelService { event, primary_modifiers); if (pressed) { int i = revert ? m_engines.length - 1 : 1; - i = m_switcher.run(m_switch_keysym, m_switch_modifiers, event, - m_engines, i); + i = m_switcher.run(m_keybindings, event, m_engines, i); if (i < 0) { debug("switch cancelled"); } else { diff --git a/ui/gtk3/switcher.vala b/ui/gtk3/switcher.vala index 7b731c0..2fffcc3 100644 --- a/ui/gtk3/switcher.vala +++ b/ui/gtk3/switcher.vala @@ -63,13 +63,26 @@ class Switcher : Gtk.Window { public string longname { get; set; } } + private class Keybinding { + public Keybinding(uint keysym, + Gdk.ModifierType modifiers, + bool reverse) { + this.keysym = keysym; + this.modifiers = modifiers; + this.reverse = reverse; + } + + public uint keysym { get; set; } + public Gdk.ModifierType modifiers { get; set; } + public bool reverse { get; set; } + } + private Gtk.Box m_box; private Gtk.Label m_label; private IBusEngineButton[] m_buttons = {}; private IBus.EngineDesc[] m_engines; private uint m_selected_engine; - private uint m_keyval; - private uint m_modifiers; + private unowned GLib.List m_keybindings; private Gdk.ModifierType m_primary_modifier; private GLib.MainLoop m_loop; private int m_result; @@ -105,19 +118,17 @@ class Switcher : Gtk.Window { grab_focus(); } - public int run(uint keyval, - uint state, + public int run(GLib.List keybindings, Gdk.Event event, IBus.EngineDesc[] engines, int index) { assert (m_loop == null); assert (index < engines.length); - m_keyval = keyval; - m_modifiers = state; + m_keybindings = (GLib.List) keybindings; m_primary_modifier = KeybindingManager.get_primary_modifier( - state & KeybindingManager.MODIFIER_FILTER); + event.key.state & KeybindingManager.MODIFIER_FILTER); update_engines(engines); m_result = 0; @@ -302,23 +313,25 @@ class Switcher : Gtk.Window { public override bool key_press_event(Gdk.EventKey e) { bool retval = true; Gdk.EventKey *pe = &e; + uint modifiers = KeybindingManager.MODIFIER_FILTER & pe->state; - do { - uint modifiers = KeybindingManager.MODIFIER_FILTER & pe->state; + for (unowned GLib.List keybindings = m_keybindings; + keybindings != null; + keybindings = keybindings.next) { + Keybinding keybinding = keybindings.data; - if ((modifiers != m_modifiers) && - (modifiers != (m_modifiers | Gdk.ModifierType.SHIFT_MASK))) { - break; - } - - if (pe->keyval == m_keyval) { - if (modifiers == m_modifiers) + if (pe->keyval == keybinding.keysym && + modifiers == (uint) keybinding.modifiers) { + if (!keybinding.reverse) { next_engine(); - else // modififers == m_modifiers | SHIFT_MASK + } else { previous_engine(); - break; + } + return true; } + } + do { switch (pe->keyval) { case 0x08fb: /* leftarrow */ case 0xff51: /* Left */ diff --git a/ui/gtk3/xkblayout.vala b/ui/gtk3/xkblayout.vala new file mode 100644 index 0000000..33e9d9d --- /dev/null +++ b/ui/gtk3/xkblayout.vala @@ -0,0 +1,464 @@ +/* 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 bool HAVE_IBUS_XKB; +public extern const int XKB_LAYOUTS_MAX_LENGTH; +public extern const string IBUS_XKB_COMMAND; + +class XKBLayout +{ + string m_xkb_command = IBUS_XKB_COMMAND; + 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) { + if (!HAVE_IBUS_XKB) { + m_xkb_command = "setxkbmap"; + } + + 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]; + } + } + 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() { + if (HAVE_IBUS_XKB) { + return get_output_from_cmdline("--get", "layout: "); + } else { + return get_output_from_cmdline("-query", "layout: "); + } + } + + public string get_variant() { + if (HAVE_IBUS_XKB) { + return get_output_from_cmdline("--get", "variant: "); + } else { + return get_output_from_cmdline("-query", "variant: "); + } + } + + public string get_option() { + if (HAVE_IBUS_XKB) { + return get_output_from_cmdline("--get", "option: "); + } else { + return get_output_from_cmdline("-query", "options: "); + } + } + + /* + public string get_group() { + return get_output_from_cmdline("--get-group", "group: "); + } + */ + + public int[] set_layout(string _layout="default", + string _variant="default", + string _option="default") { + 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" && + _option == "default") { + return {-1, 0}; + } + // const gchar to gchar + string layout = _layout; + string variant = _variant; + string option = _option; + + if (variant == "default") { + string[] array = get_variant_from_layout(layout); + layout = array[0]; + variant = array[1]; + } + + if (option == "default") { + string[] array = get_option_from_layout(layout); + layout = array[0]; + option = array[1]; + } + + 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 (HAVE_IBUS_GKBD) { + 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; + if (HAVE_IBUS_XKB) { + args += "--layout"; + args += layout; + if (variant != null) { + args += "--variant"; + args += variant; + } + if (option != null) { + args += "--option"; + args += option; + } + } else { + args += layout; + if (variant != null) { + args += variant; + } + if (option != null) { + 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("IBUS_ERROR: %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.7.10.4