[openi18n-im:01502] patch to support frontend hotkey configuration

Federic Zhang <[email protected]> Tue, 09 Aug 2005 19:14:04 +0800
Newsgroups gmane.comp.internationalization.input-methods
Message-ID <[email protected]>
attached is the patch that can display hotkey info and enable user to
change trigger keys.
for example, user can change conversion keys as 'Ctrl+s'. the change
would take effect on all clients.
and original Ctrl+space wouldn't work.

the implementation still replies on x atom, _IIIM_SWITCHER_SET_HOTKEY.
Upon focus in, client check whether 
property info exists in the atom for gimlet window, if not, get the
default hotkey information from
server side and update the atom with it to notify gimlet, if the
property content is changed, client
modify the hotkey and trigger on/off keys within IIIMCF_handle. At
gimlet side, it will displays all 
hotkey configuration and if user changes the trigger keys, update the
atom.

Please comment. As it wouldn't affect other components, i will commit it
in this week.

-federic

> Hi,
> 
> hotkey/triggerkey is critical for normal user to use input method.
> server will send the hotkey
> info to client and several ways exist to configure hotkey in server
> side:
> 
> -  -conversionkeys argument
> -  hardcoded super key 'Ctrl+Spa
the implementation still replies on x atom, _IIIM_SWITCHER_SET_HOTKEY.
Upon focus in, client check whether 
property info exists in the atom for gimlet window, if not, get the
default hotkey information from
server side and update the atom with it to notify gimlet, if the
property content is changed, client
modify the hotkey and trigger on/off keys within IIIMCF_handle. At
gimlet side, it will displays all 
hotkey configuration and if user changes the trigger keys, update the
atom.

-federicce', 'Ctrl+Alt+Space', 'Ctrl+Shift+Space', 'Ctrl+Shift+<',
>     'Ctrl+Shift+>', 'Kanji' and 'Alt+Kanji'
> -  if_GetHotkeyInfo by LE
> -  /etc/iiim/le.xml.conf by iiimf-le-tools
> 
> Those mechanism is lovely to us, but to novice user, how many hotkeys
> on earth they know?
> Are those Hotkeys published well to let those user know well,
> ironically some European users
> ever don't know to use the well known Ctrl+Space.
> 
> >From usability perspective, FrontEnd Hotkey configuration is
> absolutely needed for them, 
> the majority of user. The first step is to display those info on one
> configuration window, 
> it is easily achievable, we can send it via X Atom to gimlet and
> launch one window within 
> gimlet to show. Next step is  FrontEnd configuration, user can specify
> what specific hotkey
> are used instead with the same configuration window, client framework
> gets it whenever 
> one client gets focus and merge into phk_profile in IIIMCF handle
> after iiimcf_register_hotkeys() 
> is called. 
> 
> To implement it, new API in iiimcf should be exported to IIIMGCF or
> IIIMXCF to do hotkey 
> configuration change,  it's unnecessary to send the new hotkey
> configuration to server if 
> the original hotkey id is kept intact.
> 
> Send your comment, otherwise it will be implemented soon!
> 
> -federic
iiim-config-hotkey.diff (text/x-patch, 35.3 KB)
Index: include/iiimcf.h
===================================================================
--- include/iiimcf.h	(revision 2787)
+++ include/iiimcf.h	(working copy)
@@ -196,6 +196,7 @@
     int hotkey_id;
     int state_flag;
     int action_flag;
+    char *hotkey_label; /* identify which specific hotkey it is */
     int nkeys; 
     IIIMCF_keyevent *keys;
 };
Index: include/ChangeLog
===================================================================
--- include/ChangeLog	(revision 2787)
+++ include/ChangeLog	(working copy)
@@ -1,3 +1,7 @@
+2005-08-08  Federic Zhang <[email protected]>
+
+	* iiimcf.h: add hotkey_label into IIIMCF_hotkey
+	
 2005-07-15  AShiZaWa KaZuNoRi  <[email protected]>
 
 	* iiimp/iiimp-data.h: updated for file operation
Index: gnome-im-switcher/ChangeLog
===================================================================
--- gnome-im-switcher/ChangeLog	(revision 2787)
+++ gnome-im-switcher/ChangeLog	(working copy)
@@ -1,3 +1,33 @@
+2005-08-09  Federic Zhang <[email protected]>
+
+	support the hotkey configuration at client side
+	
+	* src/gnome-im-switcher.h:
+	  - new edit_hotkey_dialog, set_hotkey_atom and hotkey in GimletWindow
+	  - new TRIGGER_KEY_LABEL
+	* src/gnome-im-switcher.schemas.in:
+	  - new schema, /schemas/apps/gimlet/preference/conversion_keys, to store
+	    customized conversion keys (a.k.a trigger keys)
+	* src/gnome-im-switcher.glade2:
+	  - new 'hotkey-dialog' dialog for hotkey configuration
+	* src/quick-access-menu.c
+	  (edit_hotkey_destroyed_callback): new
+	  (hotkey_cb): new. Call gimlet_hotkey_dialog_new to create the 'hotkey-dialog'
+	  (quick_lang_menu_init): add the 'Configure hotkey ...' menuitem
+	* src/language.c	  
+	  (gconf_set_conversion_keys): new
+	  (gconf_get_conversion_keys): new
+	  (hotkey_cancel_callback): new
+	  (get_trigger_from_hotkey): new
+	  (hotkey_changed_callback): new
+	  (gimlet_hotkey_dialog_new): new
+	* src/iiim-interface.c:
+	  (property_notify_switcher_window): call process_hotkey () when hotkey atom property changes.
+	  (gimlet_iiim_selection_set): add the hotkey atom, "_IIIM_SWITCHER_SET_HOTKEY"
+	  (process_hotkey): new
+	  (change_hotkey_with_trigger): new
+	  (gimlet_iiim_hotkey_set): new
+	
 2005-07-27  Federic Zhang <[email protected]>
 
 	* src/quick-access-menu.c (quick_lang_menu_init):
Index: gnome-im-switcher/src/gnome-im-switcher.h
===================================================================
--- gnome-im-switcher/src/gnome-im-switcher.h	(revision 2787)
+++ gnome-im-switcher/src/gnome-im-switcher.h	(working copy)
@@ -24,6 +24,8 @@
 #include <panel-applet.h>
 #include "status-placement.h"
 
+#define TRIGGER_KEY_LABEL "TRIGGER KEYS"
+
 typedef struct _QuickAccessMenu QuickAccessMenu;
 
 struct _QuickAccessMenu 
@@ -60,6 +62,7 @@
 
   /* language selection dialog */
   GtkWidget *edit_languages_dialog;
+  GtkWidget *edit_hotkey_dialog;
 
   /* preference*/
   GtkWidget *edit_preferences_dialog;
@@ -80,6 +83,7 @@
   GdkAtom  set_input_language_list_atom;
   GdkAtom  set_language_engine_list_atom;
   GdkAtom  set_conversion_mode_atom;
+  GdkAtom  set_hotkey_atom;
 
   /* input method settings */
   gboolean im_enabled;
@@ -95,6 +99,8 @@
   gboolean conversion_mode;
 
   gchar *le_list;
+
+  gchar *hotkey;
 };
 
 #endif /* __GNOME_IM_SWITCHER_H */
Index: gnome-im-switcher/src/gnome-im-switcher.schemas.in
===================================================================
--- gnome-im-switcher/src/gnome-im-switcher.schemas.in	(revision 2787)
+++ gnome-im-switcher/src/gnome-im-switcher.schemas.in	(working copy)
@@ -43,6 +43,20 @@
       </locale>
       </schema>
 
+      <schema>
+        <key>/schemas/apps/gimlet/preference/conversion_keys</key>
+        <applyto>/apps/gimlet/preference/conversion_keys</applyto>
+        <owner>gnome-im-switcher-applet</owner>
+        <type>list</type>
+        <list_type>string</list_type>
+        <default>[]</default>
+        <locale name="C">
+          <short>conversion keys</short>
+          <long>typical hotkey which is used to turn on/off input method function.
+          </long>
+        </locale>
+      </schema>
+
   </schemalist>
 
 </gconfschemafile>
Index: gnome-im-switcher/src/gnome-im-switcher.glade2
===================================================================
--- gnome-im-switcher/src/gnome-im-switcher.glade2	(revision 2787)
+++ gnome-im-switcher/src/gnome-im-switcher.glade2	(working copy)
@@ -578,4 +578,107 @@
   </child>
 </widget>
 
+<widget class="GtkDialog" id="hotkey-dialog">
+  <property name="visible">True</property>
+  <property name="title" translatable="yes">IIIM Hotkey Configuration Window</property>
+  <property name="type">GTK_WINDOW_TOPLEVEL</property>
+  <property name="window_position">GTK_WIN_POS_NONE</property>
+  <property name="modal">False</property>
+  <property name="resizable">True</property>
+  <property name="destroy_with_parent">False</property>
+  <property name="decorated">True</property>
+  <property name="skip_taskbar_hint">False</property>
+  <property name="skip_pager_hint">False</property>
+  <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+  <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+  <property name="has_separator">True</property>
+
+  <child internal-child="vbox">
+    <widget class="GtkVBox" id="dialog-vbox5">
+      <property name="visible">True</property>
+      <property name="homogeneous">False</property>
+      <property name="spacing">0</property>
+
+      <child internal-child="action_area">
+	<widget class="GtkHButtonBox" id="dialog-action_area5">
+	  <property name="visible">True</property>
+	  <property name="layout_style">GTK_BUTTONBOX_END</property>
+
+	  <child>
+	    <widget class="GtkButton" id="hotkey-cancelbutton">
+	      <property name="visible">True</property>
+	      <property name="can_default">True</property>
+	      <property name="can_focus">True</property>
+	      <property name="label">gtk-cancel</property>
+	      <property name="use_stock">True</property>
+	      <property name="relief">GTK_RELIEF_NORMAL</property>
+	      <property name="focus_on_click">True</property>
+	      <property name="response_id">-6</property>
+	    </widget>
+	  </child>
+
+	  <child>
+	    <widget class="GtkButton" id="hotkey-okbutton">
+	      <property name="visible">True</property>
+	      <property name="can_focus">True</property>
+	      <property name="label">gtk-ok</property>
+	      <property name="use_stock">True</property>
+	      <property name="relief">GTK_RELIEF_NORMAL</property>
+	      <property name="focus_on_click">True</property>
+	    </widget>
+	  </child>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">False</property>
+	  <property name="fill">True</property>
+	  <property name="pack_type">GTK_PACK_END</property>
+	</packing>
+      </child>
+
+      <child>
+	<widget class="GtkVBox" id="hotkey-vbox">
+	  <property name="visible">True</property>
+	  <property name="homogeneous">False</property>
+	  <property name="spacing">0</property>
+
+	  <child>
+	    <widget class="GtkLabel" id="hotkey-label">
+	      <property name="visible">True</property>
+	      <property name="label" translatable="yes">IIIM hotkey list</property>
+	      <property name="use_underline">True</property>
+	      <property name="use_markup">False</property>
+	      <property name="justify">GTK_JUSTIFY_LEFT</property>
+	      <property name="wrap">False</property>
+	      <property name="selectable">False</property>
+	      <property name="xalign">0</property>
+	      <property name="yalign">0.5</property>
+	      <property name="xpad">0</property>
+	      <property name="ypad">0</property>
+	    </widget>
+	    <packing>
+	      <property name="padding">0</property>
+	      <property name="expand">False</property>
+	      <property name="fill">False</property>
+	    </packing>
+	  </child>
+
+	  <child>
+	    <placeholder/>
+	  </child>
+
+	  <child>
+	    <placeholder/>
+	  </child>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">True</property>
+	  <property name="fill">True</property>
+	</packing>
+      </child>
+    </widget>
+  </child>
+</widget>
+
 </glade-interface>
Index: gnome-im-switcher/src/quick-access-menu.c
===================================================================
--- gnome-im-switcher/src/quick-access-menu.c	(revision 2787)
+++ gnome-im-switcher/src/quick-access-menu.c	(working copy)
@@ -151,6 +151,53 @@
 }
 
 static void
+edit_hotkey_destroyed_callback (GtkWidget *dialog,
+				GimletWindow *gimlet)
+{
+  gimlet->edit_hotkey_dialog = NULL;
+}
+
+static void
+hotkey_cb (GtkWidget *menuitem, GimletWindow *gimlet)
+{
+  GtkWindow *transient_parent;
+  GtkWindow *old_transient_parent;
+
+  transient_parent = GTK_WINDOW (gimlet->applet);
+  if (gimlet->edit_hotkey_dialog == NULL)
+    {
+      old_transient_parent = NULL;
+      /* passing in transient_parent here purely for the
+       * glade error dialog
+       */
+      gimlet->edit_hotkey_dialog =
+	gimlet_hotkey_dialog_new (transient_parent, gimlet);
+      if (gimlet->edit_hotkey_dialog == NULL)
+	return; /* glade file missing */
+
+      g_signal_connect (G_OBJECT (gimlet->edit_hotkey_dialog),
+			"destroy",
+			G_CALLBACK (edit_hotkey_destroyed_callback),
+			gimlet);
+    }
+  else
+    {
+      old_transient_parent = gtk_window_get_transient_for (
+				   GTK_WINDOW (gimlet->edit_hotkey_dialog));
+    }
+
+  if (old_transient_parent != transient_parent)
+    {
+      gtk_window_set_transient_for (GTK_WINDOW (gimlet->edit_hotkey_dialog),
+				    transient_parent);
+      gtk_widget_hide (gimlet->edit_hotkey_dialog);
+    }
+
+  gtk_widget_show_all (gimlet->edit_hotkey_dialog);
+  gtk_window_present (GTK_WINDOW (gimlet->edit_hotkey_dialog));
+}
+
+static void
 turn_off_cb (GtkWidget *menuitem, GimletWindow *gimlet)
 {
   gimlet_iiim_conversion_mode_set (gimlet, "off");
@@ -376,6 +423,20 @@
   g_signal_connect (menuitem, "activate",
 		    G_CALLBACK (update_langmenu_cb), gimlet);
 
+// #ifdef ENABLE_HOTKEY_CONFIGURATION
+#if 1
+  /* add separator */
+  menuitem = gtk_separator_menu_item_new ();
+  gtk_widget_show (menuitem);
+  gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
+
+  menuitem = gtk_menu_item_new_with_mnemonic (_("Configure hotkey..."));
+  gtk_widget_show (menuitem);
+  gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
+  g_signal_connect (menuitem, "activate",
+		    G_CALLBACK (hotkey_cb), gimlet);
+#endif
+
   gimlet->quick_access_menu->widget = menu;
   gimlet->quick_access_menu->active_languages = active_languages;
   
Index: gnome-im-switcher/src/language.c
===================================================================
--- gnome-im-switcher/src/language.c	(revision 2787)
+++ gnome-im-switcher/src/language.c	(working copy)
@@ -947,3 +947,232 @@
   g_free (language->iiim_lang_name);
   g_free (language);
 }
+
+void
+gconf_set_conversion_keys (GtkWidget *gimlet_applet,
+			   GSList *strings)
+{
+  gchar *key;
+
+  key = panel_applet_gconf_get_full_key (PANEL_APPLET (gimlet_applet),
+					 (const gchar*)"conversion_keys");
+  gconf_client_set_list (default_client, key,
+			 GCONF_VALUE_STRING,
+			 strings,
+			 NULL);
+  g_free (key);
+}
+
+char *
+gconf_get_conversion_keys (GimletWindow *gimlet)
+{
+  gchar *key;
+  GSList *strings = NULL;
+  int i, n;
+  gchar *conversion_keys = NULL;
+  char s[512], *p = s;
+
+  key = panel_applet_gconf_get_full_key (PANEL_APPLET (gimlet->applet),
+					 "conversion_keys");
+  if (default_client == NULL)
+    {
+      GError *err = NULL;
+
+      default_client = gconf_client_get_default ();
+      g_object_ref (G_OBJECT (default_client));
+    }
+
+  strings = gconf_client_get_list (default_client,
+				   key,
+				   GCONF_VALUE_STRING, NULL);
+
+  
+  n = g_list_length (strings);
+
+  memset (s, 0, 512);
+
+  for (i=0; i < n; ++i)
+    {
+      char *k = g_list_nth_data (strings, i);
+      char key_delimiter = ',';
+
+      strcpy (p, k);
+      p += strlen (k);
+
+      if (i < n -1)
+	*p++ = key_delimiter;
+
+      g_free (k);
+    }
+
+  g_free (key);
+  g_list_free (strings);
+
+  if (*s) conversion_keys = g_strdup (s);
+
+  return conversion_keys;
+}
+
+static void
+hotkey_cancel_callback (GtkWidget *button,
+			 GimletWindow *gimlet)
+{
+  gtk_widget_destroy (GTK_WINDOW (gimlet->edit_hotkey_dialog));
+}
+
+char *
+get_trigger_from_hotkey (char *s)
+{
+  char *label_delimiter = ":";
+  char **hotkeys = g_strsplit (s, label_delimiter, -1);
+  int num_hotkeys = 0;
+  int i = 0;
+  char *trigger = NULL;
+
+  // num_hotkeys = g_strv_length (hotkeys) / 2;
+  while (hotkeys [num_hotkeys]) ++num_hotkeys;
+  num_hotkeys /= 2;
+
+  for (i = 0; i < num_hotkeys; ++i)
+    {
+      if (!strcmp (hotkeys[i * 2], TRIGGER_KEY_LABEL))
+	{	  
+	  trigger = strdup (hotkeys[i * 2 + 1]);
+	  break;
+	}
+    }
+
+  g_strfreev (hotkeys);
+
+  return trigger;
+}
+
+static void
+hotkey_changed_callback (GtkWidget *button,
+			 GimletWindow *gimlet)
+{
+  GSList *strings = NULL;
+  GtkEntry *trigger_entry = (GtkEntry *)g_object_get_data (G_OBJECT (button),
+					   "iiim-hotkey-trigger");
+  char *trigger; 
+  char **keys;
+  char *key_delimiter = ",";
+  char *old_trigger;
+  int num_keys, i;
+  
+  trigger = strdup (gtk_entry_get_text (trigger_entry));
+
+  /* return if no change is made */
+  
+  old_trigger = get_trigger_from_hotkey (gimlet->hotkey);
+  if (!strcasecmp (trigger, old_trigger))
+    goto END_HOTKEY_CHANGED_CALLBACK;
+
+  /*
+   * FIXME one warning dialog should be popuped if the trigger input is corrupted.
+   */
+
+  gimlet_iiim_hotkey_set (gimlet, trigger);
+
+  keys = g_strsplit (trigger, key_delimiter, -1);
+
+  //  num_keys = g_strv_length (keys);
+  num_keys = 0;
+  while (keys[num_keys]) ++num_keys;
+
+  for (i = 0; i < num_keys; ++i)
+    strings = g_list_insert (strings, keys[i], -1);
+
+  gconf_set_conversion_keys (gimlet->applet, strings);
+
+  g_list_free (strings);
+  g_strfreev (keys);
+
+ END_HOTKEY_CHANGED_CALLBACK:
+  gtk_widget_destroy (GTK_WINDOW (gimlet->edit_hotkey_dialog));
+
+  g_free (old_trigger);
+  g_free (trigger);
+}
+
+GtkWidget *
+gimlet_hotkey_dialog_new (GtkWidget *transient_parent,
+			  GimletWindow *gimlet)
+{
+  GladeXML *xml;
+  GtkWidget *dialog = NULL;
+  GtkWidget *vbox, *table;
+  GtkWidget *trigger_entry, *w;
+  int num_hotkeys = 0;
+  gchar *label_delimiter = ":";
+  gchar **hotkeys;
+  int i;
+
+  if (!gimlet->hotkey) return NULL;
+
+  hotkeys = g_strsplit (gimlet->hotkey, label_delimiter, -1);
+
+  //num_hotkeys = g_strv_length (hotkeys)/2;
+  while (hotkeys[num_hotkeys]) ++num_hotkeys;
+  num_hotkeys /= 2;
+
+  xml = gimlet_util_load_glade_file (GIMLET_GLADE_FILE,
+				     "hotkey-dialog",
+				     transient_parent);
+
+  if (xml == NULL) goto END_HOTKEY_DIALOG;
+
+  dialog = glade_xml_get_widget (xml, "hotkey-dialog");
+
+  gimlet_util_set_unique_role (GTK_WINDOW (dialog), "gimlet-hotkeys");
+
+  /* dynamically create the table */
+  vbox = glade_xml_get_widget (xml, "hotkey-vbox");
+  table = gtk_table_new (num_hotkeys, 2, FALSE);
+  gtk_table_set_row_spacings (GTK_TABLE (table), 4);
+  gtk_table_set_col_spacings (GTK_TABLE (table), 4);
+  gtk_box_pack_start (GTK_BOX (vbox), table, TRUE, TRUE, 0);
+
+  for (i = 0; i < num_hotkeys; ++i)
+    {
+      w = gtk_label_new (hotkeys [i * 2]);
+      gtk_table_attach_defaults (GTK_TABLE (table),
+				 w,
+				 0, 1, i, i + 1);
+
+      w = gtk_entry_new ();
+      gtk_entry_set_text (w, hotkeys[i * 2 + 1]);
+      gtk_table_attach_defaults (GTK_TABLE (table),
+				 w,
+				 1, 2, i, i + 1);
+      if (!strcasecmp (hotkeys[i * 2], TRIGGER_KEY_LABEL))
+	trigger_entry = w;
+      else
+	gtk_widget_set_sensitive (w, FALSE);
+    }
+
+  w = glade_xml_get_widget (xml, "hotkey-okbutton");
+  g_object_set_data (G_OBJECT (w), "iiim-hotkey-trigger",
+		     trigger_entry);
+  g_object_set_data (G_OBJECT (w), "iiim-hotkey-dialog",
+		     dialog);
+
+  g_signal_connect (G_OBJECT (w), "clicked",
+		    G_CALLBACK (hotkey_changed_callback),
+		    gimlet);
+
+  w = glade_xml_get_widget (xml, "hotkey-cancelbutton");
+  g_signal_connect (G_OBJECT (w), "clicked",
+		    G_CALLBACK (hotkey_cancel_callback),
+		    gimlet);
+
+  /* FIXME one 'reset' button should be added here in order 
+   * to reset the trigger key
+   */
+
+  g_object_unref (G_OBJECT (xml));
+
+ END_HOTKEY_DIALOG:
+  g_strfreev (hotkeys);
+  return dialog;
+}
Index: gnome-im-switcher/src/iiim-interface.c
===================================================================
--- gnome-im-switcher/src/iiim-interface.c	(revision 2789)
+++ gnome-im-switcher/src/iiim-interface.c	(working copy)
@@ -215,6 +215,8 @@
 	process_conversion_mode (gimlet, data, length);
       else if (type == gimlet->set_language_engine_list_atom)
 	process_language_engine_list (gimlet, data, length);
+      else if (type == gimlet->set_hotkey_atom)
+	process_hotkey (gimlet, data, length);
 
       g_free (data);
     }
@@ -273,6 +275,9 @@
   atom = gdk_atom_intern ("_IIIM_SWITCHER_LANGUAGE_ENGINE_LIST", FALSE);
   gimlet->set_language_engine_list_atom = atom;
 
+  atom = gdk_atom_intern ("_IIIM_SWITCHER_SET_HOTKEY", FALSE);
+  gimlet->set_hotkey_atom = atom;
+
   gtk_widget_add_events (gimlet->invisible,
 			 GDK_PROPERTY_CHANGE_MASK | GDK_STRUCTURE_MASK);
 
@@ -315,3 +320,81 @@
 		       (guchar *)conversion_mode,
 		       strlen (conversion_mode) + 1); /* including last NULL */
 }
+
+static void
+process_hotkey (GimletWindow *gimlet,
+                gpointer data, int length)
+{
+  char *trigger, *old_trigger;
+  char *hotkey = g_strndup (data, length);
+
+  /*
+   * FIXME why process_hotkey would be invoked when put focus on the TRIGGER entry
+   * of the hotkey configuration window?
+   */
+
+  /*
+   * FIXME override hotkey with the trigger value from gconf.
+   */
+  old_trigger = gconf_get_conversion_keys (gimlet);
+  trigger = get_trigger_from_hotkey (hotkey);
+  if (old_trigger && strcasecmp (old_trigger, trigger) != 0)
+    {
+#if 0
+      char *tmp_hotkey = change_hotkey_with_trigger (hotkey, old_trigger);
+
+      g_free (hotkey);
+      hotkey = tmp_hotkey;
+#endif
+    }
+
+  g_free (gimlet->hotkey);
+  gimlet->hotkey = g_strdup (hotkey);
+
+  g_free (old_trigger);
+  g_free (trigger);
+  g_free (hotkey);
+}
+
+static char *
+change_hotkey_with_trigger (char *s, char *trigger)
+{
+  char *label_delimiter = ":";
+  char **hotkeys = g_strsplit (s, label_delimiter, -1);
+  int num_hotkeys = 0;
+  int i;
+
+  // num_hotkeys = g_strv_length (hotkeys)/2;
+  while (hotkeys[num_hotkeys]) ++num_hotkeys;
+  num_hotkeys /= 2;
+
+  for (i = 0; i < num_hotkeys; ++i)
+    {
+      if (!strcmp (hotkeys[i * 2], TRIGGER_KEY_LABEL))
+        {
+          g_free (hotkeys[i * 2 + 1]);
+          hotkeys[i * 2 + 1] = strdup (trigger);
+        }
+    }
+
+  return g_strjoinv (label_delimiter, hotkeys);
+}
+
+void
+gimlet_iiim_hotkey_set (GimletWindow *gimlet,
+                        const gchar *trigger)
+{
+  char *hotkey;
+
+  g_return_if_fail (trigger != NULL);
+
+  hotkey = change_hotkey_with_trigger (gimlet->hotkey, trigger);
+
+  gdk_property_change (gimlet->invisible->window,
+                       gimlet->set_hotkey_atom,
+                       gimlet->set_hotkey_atom,
+                       8,
+                       GDK_PROP_MODE_REPLACE,
+                       (guchar *)hotkey,
+                       strlen (hotkey) + 1); /* including last NULL */
+}
Index: lib/iiimcf/iiimcf.c
===================================================================
--- lib/iiimcf/iiimcf.c	(revision 2787)
+++ lib/iiimcf/iiimcf.c	(working copy)
@@ -302,10 +302,20 @@
         ph->phk_profile[n_hkp].phot_keys = hk;
 
 	for(i=0; i<n_hotkeys; i++) {
+	    IIIMP_string *label = hklist->hotkey[i].label;
+	    char *pc;
+	    IIIMP_card16 *pu;
+
 	    hk[i].hotkey_id = hklist->hotkey[i].hotkeyctrl.hotkey_id;
 	    hk[i].state_flag = hklist->hotkey[i].hotkeyctrl.state_flag;
 	    hk[i].action_flag = hklist->hotkey[i].hotkeyctrl.action_flag;
 
+	    /* copy label from IIIMP_string* to char* */
+	    hk[i].hotkey_label = (char *)malloc (label->len + 1);
+	    memset (hk[i].hotkey_label, 0, label->len + 1);
+	    for (j = 0, pu = label->ptr, pc = hk[i].hotkey_label; j < label->len; ++j)
+	      *pc++ = (*pu++ & 0x7f);
+
 	    n = hklist->hotkey[i].hotkeylist->count;
 	    if (n > 0) {
 		pkev = (IIIMCF_keyevent*) malloc(sizeof(IIIMCF_keyevent) * n);
Index: lib/iiimcf/trigger.c
===================================================================
--- lib/iiimcf/trigger.c	(revision 2787)
+++ lib/iiimcf/trigger.c	(working copy)
@@ -268,6 +268,26 @@
     return IIIMF_STATUS_SUCCESS;
 }
 
+/*
+ * get the hotkey list of current hotkey profile.
+ */
+IIIMF_status
+iiimcf_get_hotkeys(
+   IIIMCF_context context,
+   int *pnum_hotkey,
+   const IIIMCF_hotkey **photkey
+)
+{
+  IIIMCF_handle_rec *hc = (IIIMCF_context_rec *)context->ph;
+
+  if (pnum_hotkey && hc->num_of_hkprofiles)
+    *pnum_hotkey = hc->phk_profile[hc->curr_profile_id].num_hotkeys;
+  if (photkey && hc->num_of_hkprofiles)
+    *photkey = hc->phk_profile[hc->curr_profile_id].phot_keys;
+
+  return IIIMF_STATUS_SUCCESS;
+}
+
 /* Local Variables: */
 /* c-file-style: "iiim-project" */
 /* End: */
Index: lib/iiimcf/ChangeLog
===================================================================
--- lib/iiimcf/ChangeLog	(revision 2787)
+++ lib/iiimcf/ChangeLog	(working copy)
@@ -1,3 +1,15 @@
+2005-08-08  Federic Zhang <[email protected]>
+
+	export new iiimcf_get_hotkeys API to various client frameworks which 
+	want to get or configure hotkey.
+	
+	* iiimcf.c (iiimcf_register_hotkeys): 
+	  copy the hotkey label from HOTKEY_LIST to the IIIMCF_hotkey struct.
+	* trigger.c (iiimcf_get_hotkeys): New. 
+	  get the hotkey list of current hotkey profile
+	* libiiimcf.sym: 
+	  add iiimcf_get_hotkeys as global symbol
+	
 2005-07-08  AShiZaWa KaZuNoRi  <[email protected]>
 
 	* aux.c (iiimcf_forward_aux_getvalues()): fix memory leak.
Index: lib/iiimcf/libiiimcf.sym
===================================================================
--- lib/iiimcf/libiiimcf.sym	(revision 2787)
+++ lib/iiimcf/libiiimcf.sym	(working copy)
@@ -37,6 +37,7 @@
 iiimcf_get_supported_input_methods
 iiimcf_get_object_descriptor_list
 iiimcf_get_trigger_keys
+iiimcf_get_hotkeys
 iiimcf_get_current_conversion_mode
 iiimcf_get_preedit_text
 iiimcf_get_status_text
Index: iiimgcf/ChangeLog
===================================================================
--- iiimgcf/ChangeLog	(revision 2787)
+++ iiimgcf/ChangeLog	(working copy)
@@ -1,3 +1,28 @@
+2005-08-09  Federic Zhang <[email protected]>
+
+	support the hotkey configuration at client side
+	
+	* gtkimcontextiiim.h:
+	  new TRIGGER_KEY_LABEL
+	* IIIMGdkEventKey.h:
+	  move IIIM_MODIFIERS enum here
+	* IIIMGdkEventKey.c (g2icode):
+	  make it global
+	* gtkimcontextiiim.c
+	  (im_context_iiim_focus_in): get hotkey property for gimlet window,
+	  if failed, get hotkey info within IIIMCF_handle and change the atom
+	  property to notify gimlet. if hotkey propery gets changed, update
+	  the both hotkey of current profile and trigger on/off keys in IIIMCF_handle.
+	* imswitcher.c:
+	  add set_hotkey_atom to _SwitcherInfo struct
+	  (im_info_switcher_new): create the "_IIIM_SWITCHER_SET_HOTKEY" atom
+	  (im_context_switcher_set_hotkey): new
+	  (im_context_switcher_get_hotkey): new
+	  (change_hotkey_with_type): new
+	  (convert_triggerkey_to_IIIMCF_keyevent): new
+	  (im_context_switcher_change_triggerkey): new
+	  (im_context_switcher_get_hotkey_with_atom): new
+	
 2005-07-26  Akira TAGOH  <[email protected]>
 
 	* gtkimcontextiiim.c (im_context_iiim_commit_cb): new function to emit
Index: iiimgcf/imswitcher.c
===================================================================
--- iiimgcf/imswitcher.c	(revision 2787)
+++ iiimgcf/imswitcher.c	(working copy)
@@ -28,6 +28,7 @@
 #include "iiimcf.h"
 #include "gtkimcontextiiim.h"
 #include "imswitcher.h"
+#include "IIIMGdkEventKey.h"
 
 #include <gtk/gtkinvisible.h>
 #include <gdk/gdkproperty.h>
@@ -50,6 +51,7 @@
   GdkAtom  set_input_language_list_atom;
   GdkAtom  set_language_engine_list_atom;
   GdkAtom  set_conversion_mode_atom;
+  GdkAtom  set_hotkey_atom;
 
   /*
     When switcher is NULL while switcher_x_window isn't and works, it is due
@@ -150,6 +152,9 @@
   sw_info->set_conversion_mode_atom = 
     gdk_atom_intern ("_IIIM_SWITCHER_SET_CONVERSION_MODE", FALSE);
 
+  sw_info->set_hotkey_atom =
+    gdk_atom_intern ("_IIIM_SWITCHER_SET_HOTKEY", FALSE);
+
   return TRUE;
 }
 
@@ -279,7 +284,46 @@
     }
 }
 
+/* change the hotkey property for the gimlet window */
 void
+im_context_switcher_set_hotkey (GtkIMContextIIIM *context_iiim,
+                                char *hotkey)
+{
+  GtkIIIMInfo *info = context_iiim->iiim_info;
+  SwitcherInfo *sw_info = im_info_get_switcher_info (info);
+
+  if (sw_info == NULL && hotkey)
+    return;
+
+  if (sw_info->switcher)
+    {
+      gdk_property_change (sw_info->switcher,
+                           sw_info->set_hotkey_atom,
+                           sw_info->set_hotkey_atom,
+                           8,
+                           GDK_PROP_MODE_REPLACE,
+                           (unsigned char*)hotkey,
+                           strlen (hotkey));
+    }
+  else if (sw_info->switcher_x_window)
+    {
+      GdkScreen *screen = im_info_get_screen (info);
+      GdkDisplay *display = gdk_screen_get_display (screen);
+      Atom x_atom = gdk_x11_atom_to_xatom_for_display (display,
+                               sw_info->set_hotkey_atom);
+
+      XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
+                       sw_info->switcher_x_window,
+                       x_atom,
+                       x_atom,
+                       8,
+                       PropModeReplace,
+                       (guchar *)hotkey,
+                       strlen (hotkey));
+    }
+}
+
+void
 im_context_switcher_set_input_language (GtkIMContextIIIM *context_iiim,
 					gchar *input_lang)
 {
@@ -576,3 +620,222 @@
   context_iiim->switcher_context = NULL;
   return;
 }
+
+gchar *
+im_context_switcher_get_hotkey (GtkIMContextIIIM *context_iiim)
+{
+  int num_hotkey = 0, i;
+  IIIMCF_hotkey *hotkeys;
+  char *hotkey_info;
+  char info[512], *p = info;
+
+  memset (info, 0, 512);
+
+  iiimcf_get_hotkeys (context_iiim->context, &num_hotkey, &hotkeys);
+
+  if (num_hotkey == 0) return NULL;
+
+  for (i = 0; i < num_hotkey; ++i)
+    {
+      char label_delimiter = ':';
+      char key_delimiter = ',';
+      int k;
+
+      strcpy (p, hotkeys[i].hotkey_label);
+      p += strlen (hotkeys[i].hotkey_label);
+      *p++ = label_delimiter;
+
+      for (k = 0; k < hotkeys[i].nkeys; ++k)
+	{
+	  IIIMCF_keyevent *key = hotkeys[i].keys + k;
+	  GdkEventKey event;
+	  char *keyname;
+
+	  if (k) *p++ = key_delimiter;
+
+	  if (key->modifier & IIIMF_CONTROL_MODIFIER)
+	    {
+	      strcpy (p, "Ctrl+");
+	      p += strlen ("Ctrl+");
+	    }
+	  if (key->modifier & IIIMF_SHIFT_MODIFIER)
+	    {
+	      strcpy (p, "Shift+");
+	      p += strlen ("Shift+");
+	    }
+	  if (key->modifier & IIIMF_ALT_MODIFIER)
+	    {
+	      strcpy (p, "Alt+");
+	      p += strlen ("Alt+");
+	    }
+
+	  convert_IIIMCF_keyevent_to_GdkEventKey (key, &event);
+	  keyname = gdk_keyval_name (event.keyval);
+
+	  if (keyname)
+	    {
+	      strcpy (p, keyname);
+	      p += strlen (keyname);
+	    }
+	}
+
+      if (i < num_hotkey - 1) *p++ = label_delimiter;
+    }
+
+  hotkey_info = strdup (info);
+  return hotkey_info;
+}
+
+static void
+change_hotkey_with_type (GtkIMContextIIIM *context_iiim,
+			 char *type, int num_keys,
+			 IIIMCF_keyevent *keys)
+{
+  int num_hotkey = 0, i;
+  IIIMCF_hotkey *hotkeys = NULL;
+  IIIMCF_handle *handle = im_info_get_handle (context_iiim->iiim_info);
+
+  iiimcf_get_hotkeys (context_iiim->context, &num_hotkey, &hotkeys);
+
+  if (num_hotkey == 0) return;
+
+  for (i = 0; i < num_hotkey; ++i)
+    {
+      if (!strcasecmp (type, hotkeys[i].hotkey_label))
+	{
+	  if (hotkeys[i].nkeys < num_keys)
+	    {
+	      g_free (hotkeys[i].keys);
+	      hotkeys[i].keys = g_new0 (IIIMCF_keyevent, num_keys);
+	    }
+
+	  hotkeys[i].nkeys = num_keys;
+	  memcpy (hotkeys[i].keys, keys, sizeof (IIIMCF_keyevent) * num_keys);
+	}
+    }
+
+  /* change the onkeys and offkeys if the target type is TRIGGER_KEY_LABEL */
+  if ( handle && !strcasecmp (type, TRIGGER_KEY_LABEL))
+    {
+      int num_on_keys = 0, num_off_keys = 0;
+      static int initial_num_on_keys = 0, initial_num_off_keys = 0;
+      IIIMCF_keyevent *onkeys, *offkeys;
+
+      iiimcf_get_trigger_keys (handle, &num_on_keys, &onkeys,
+			       &num_off_keys, &offkeys);
+
+      if (!initial_num_on_keys && !initial_num_off_keys) 
+	{
+	  initial_num_on_keys = num_on_keys;
+	  initial_num_off_keys = num_off_keys;
+	}
+      
+      num_keys = initial_num_on_keys < num_keys ? initial_num_on_keys : num_keys;
+      memcpy (onkeys, keys, sizeof (IIIMCF_keyevent) * num_keys);
+      memset (onkeys + num_keys, 0, sizeof (IIIMCF_keyevent) * (initial_num_on_keys - num_keys));
+
+      num_keys = initial_num_off_keys < num_keys ? initial_num_off_keys : num_keys;
+      memcpy (offkeys, keys, sizeof (IIIMCF_keyevent) * num_keys);
+      memset (offkeys + num_keys, 0, sizeof (IIIMCF_keyevent) * (initial_num_off_keys - num_keys));
+    }
+}
+
+/* 
+ * The triggerkey information is set by gimlet with set_hotkey_atom, it is gimlet's responsibility
+ * to validate its content before it change the atom's content.
+ * Usually the format of hotkey would take "Ctrl+Shift+a,Kanji,Shift+Kanji".
+ */
+static void
+convert_triggerkey_to_IIIMCF_keyevent (gchar *triggerkey,
+				       int *num_keys, 
+				       IIIMCF_keyevent **keys)
+{
+  char *key_delimiter = ",";
+  char **hotkeys = g_strsplit (triggerkey, key_delimiter, -1);
+  int i;
+
+  if (hotkeys == NULL) return;
+
+  // *num_keys = g_strv_length (hotkeys);
+  *num_keys = 0;
+  while (hotkeys[*num_keys]) ++ *num_keys;
+
+  *keys = g_new0 (IIIMCF_keyevent, *num_keys);
+
+  for (i=0; i < *num_keys; ++i)
+    {
+      char *key_separator = "+";
+      gchar **k, **keys_text = g_strsplit (hotkeys[i], key_separator, -1);
+      GdkEvent event;
+
+      k = keys_text;
+      for (; *k; ++k)
+	{
+	  if (!strcasecmp (*k, "Ctrl"))
+	    (*keys)[i].modifier |= IIIMF_CONTROL_MODIFIER;
+	  else if (!strcasecmp (*k, "Shift"))
+	    (*keys)[i].modifier |= IIIMF_SHIFT_MODIFIER;
+	  else if (!strcasecmp (*k, "Alt"))
+	    (*keys)[i].modifier |= IIIMF_ALT_MODIFIER;
+	  else
+	    {
+	      guint keyval = gdk_keyval_from_name (*k);
+	      gint keycode = g2icode (keyval);
+
+	      if (keycode < 0)
+		{
+		  (*keys)[i].keychar = 0;
+		  (*keys)[i].keycode = -keycode;
+		}
+	      else
+		{
+		  (*keys)[i].keychar = gdk_keyval_to_unicode (keyval);
+		  (*keys)[i].keycode = keycode;
+		}
+	    }
+	}
+
+      g_strfreev (keys_text);
+    }
+
+  g_strfreev (hotkeys);
+}
+
+void
+im_context_switcher_change_triggerkey (GtkIMContextIIIM *context_iiim,
+				       gchar *triggerkey)
+{
+  int num_keys;
+  IIIMCF_keyevent *keys;
+
+  convert_triggerkey_to_IIIMCF_keyevent (triggerkey, &num_keys, &keys);
+  
+  change_hotkey_with_type (context_iiim, TRIGGER_KEY_LABEL, num_keys, keys);
+}
+
+char *
+im_context_switcher_get_hotkey_with_atom (GtkIMContextIIIM *context_iiim)
+{
+  SwitcherInfo *info = im_info_get_switcher_info (context_iiim->iiim_info);
+  GdkScreen *screen;
+  GdkDisplay *display;
+  Atom x_atom;
+  int format;
+  unsigned long length, nitem;
+  unsigned char *data = NULL;
+  Atom type;
+
+  if (!info || !info->switcher_x_window) return NULL;
+
+  screen = im_info_get_screen (context_iiim->iiim_info);
+  display = gdk_screen_get_display (screen);
+  x_atom = gdk_x11_atom_to_xatom_for_display (display, info->set_hotkey_atom);
+
+  XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display),
+		      info->switcher_x_window,
+		      x_atom,
+		      0, INT_MAX, False, x_atom,
+		      &type, &format, &nitem, &length, &data);
+
+  return data;
+}
Index: iiimgcf/gtkimcontextiiim.c
===================================================================
--- iiimgcf/gtkimcontextiiim.c	(revision 2787)
+++ iiimgcf/gtkimcontextiiim.c	(working copy)
@@ -1874,7 +1874,8 @@
     return FALSE;
 
  commit_this_event:
-  if (IS_DEAD_KEY(event->keyval)) {
+  //  if (IS_DEAD_KEY(event->keyval)) {
+  if (0) {
     /* don't send the dead key with the commit event! */
     was_dead_key = TRUE;
     return gtk_im_context_filter_keypress(context_iiim->slave, event);
@@ -2045,6 +2046,57 @@
       gtk_widget_show (cand_win);
     }
 
+//#ifdef ENABLE_HOTKEY_CONFIGURATION
+#if 1
+ {
+   /*
+    * server sends the hotkey when client's content is constructed and client
+    * will try once to get the hotkey info with iiimcf_register_hotkeys by sending
+    * HOTKEY_NOTIFY message, usually it will happen at the first time when client
+    * sends set ic focus message to server in im_context_iiim_focus_in. The
+    * hotkey wouldn't be changed at the client's life cycle.
+    *
+    * To communicate with gimlet, current implemetation is as followings:
+    *    Once client gets focus, it checks whether hotkey info exists with _IIIM_SWITCHER_SET_HOTKEY 
+    *    atom, if it does exits, check further whether the trigger key gets changed with
+    *    the atom by gimlet, if yes, change the trigger key.
+    *    otherwise, get the hotkey info from server and notify this hotkey info
+    *    to gimlet via the atom.
+    * 
+    * FIXME - the better way is each client can be notified asynchronously with the
+    *    atom change for gimlet window.
+    */
+
+   char *hotkey = im_context_switcher_get_hotkey_with_atom (context_iiim);
+
+   if (!hotkey)
+     {
+       hotkey = im_context_switcher_get_hotkey (context_iiim);
+
+       if (hotkey)
+	 im_context_switcher_set_hotkey (context_iiim, hotkey);
+     }
+   else
+     {
+       /* get the trigger key */
+       char *label_delimiter = ":";
+       char **keys = g_strsplit (hotkey, label_delimiter, -1);
+       int i = 0;
+       while (keys[i] && strcasecmp (keys[i], TRIGGER_KEY_LABEL) != 0) ++i;
+
+       /* 
+	* change the trigger key if it is changed.
+	* FIXME - how can we tell it is changed?
+	*/
+       im_context_switcher_change_triggerkey (context_iiim, keys[i+1]);
+
+       g_strfreev (keys);
+     }
+
+   if (hotkey) g_free (hotkey);
+ }
+#endif
+
   if (!has_focus)
     {
       if (im_info_switcher_active (context_iiim->iiim_info))
Index: iiimgcf/IIIMGdkEventKey.c
===================================================================
--- iiimgcf/IIIMGdkEventKey.c	(revision 2787)
+++ iiimgcf/IIIMGdkEventKey.c	(working copy)
@@ -137,15 +137,6 @@
 };
 #endif /* USE_KANA_TABLE */
 
-enum IIIMF_MODIFIERS
-{
-  IIIMF_SHIFT_MODIFIER = 1,
-  IIIMF_CONTROL_MODIFIER = 2,
-  IIIMF_META_MODIFIER = 4,
-  IIIMF_ALT_MODIFIER = 8,
-  IIIMF_ALT_GRAPH_MODIFIER = 32
-};
-
 #if	USE_KANA_TABLE
 static void
 set_keymap_table(GdkDisplay *display)
@@ -324,7 +315,7 @@
 }
 #endif /* USE_KANA_TABLE */
 
-static int
+int
 g2icode (guint kv)
 {
   switch (kv)
Index: iiimgcf/IIIMGdkEventKey.h
===================================================================
--- iiimgcf/IIIMGdkEventKey.h	(revision 2787)
+++ iiimgcf/IIIMGdkEventKey.h	(working copy)
@@ -35,6 +35,15 @@
 #include <iiimcf.h>
 #include <gdk/gdk.h>
 
+enum IIIMF_MODIFIERS
+{
+  IIIMF_SHIFT_MODIFIER = 1,
+  IIIMF_CONTROL_MODIFIER = 2,
+  IIIMF_META_MODIFIER = 4,
+  IIIMF_ALT_MODIFIER = 8,
+  IIIMF_ALT_GRAPH_MODIFIER = 32
+};
+
 extern IIIMF_status
 convert_GdkEventKey_to_IIIMCF_keyevent (GdkEventKey * e,
 					IIIMCF_keyevent * pkev);
Index: iiimgcf/gtkimcontextiiim.h
===================================================================
--- iiimgcf/gtkimcontextiiim.h	(revision 2787)
+++ iiimgcf/gtkimcontextiiim.h	(working copy)
@@ -103,6 +103,8 @@
   GtkIMContextClass parent_class;
 };
 
+#define TRIGGER_KEY_LABEL "TRIGGER KEYS"
+
 void im_context_iiim_register_type (GTypeModule *type_module);
 GtkIMContext *im_context_iiim_new (void);
Screenshot-Gnome-im-switcher-applet-1.png (image/png, 21.3 KB) - not displayed