keycode handling
- From: Havoc Pennington <hp redhat com>
- To: gtk-devel-list gnome org
- Subject: keycode handling
- Date: 02 Dec 2000 14:50:45 -0500
Hi,
Attached patch adds the keycode to GdkEventKey and some functions to
deal with the mapping from keycode to keyval and back. The main
question here is whether the interface is implementable on other
platforms; input from people working on GTK ports would be good.
Here is the API introduced:
/* GdkKeyInfo is a hardware key that can be mapped to a keyval */
struct _GdkKeyInfo
{
guint keycode;
gint group;
gint level;
};
gboolean gdk_keyval_get_keys (guint keyval,
GdkKeyInfo **infos,
gint *n_infos);
guint gdk_key_info_to_keyval (const GdkKeyInfo *info);
gboolean gdk_keycode_translate (guint hardware_keycode,
GdkModifierType state,
gint group,
guint *keyval,
gint *effective_group,
gint *level,
GdkModifierType *unused_modifiers);
void gdk_keycode_get_map (guint hardware_keycode,
GdkKeyInfo **infos,
guint **keyvals,
gint *n_entries);
Some explanation of what this all means:
- a keycode (scancode in Windows) is a code that refers to a physical
keyboard key.
- a keyval (keysym in X) is a "logical" key name, such as GDK_Enter,
GDK_a, GDK_space, etc.
- a level is a keyboard state that affects which symbol on a key
will be used. for example, if you press shift on a US keyboard,
the level goes from 0 (default unmodified) to 1 (use top symbol
on the key, or convert case). Some keyboards have another level
in addition to shift/unshift. Also, if shift lock is down, the
default level becomes 1 and the shift key changes it to 0 (undoes
the lock)
- a group is just another "dimension" of keyboard state; some
keyboards have English and Hebrew modes for example. You would
normally switch groups by pressing a special key similar to shift
lock. In English mode, the keys would generate US characters, in
Hebrew mode they'd generate Hewbrew characters
- a keymap translates from a keycode/group/level triplet into
a keyval. Each keycode has a list of keyvals indexed by
group/level.
- keys have a logical "type" which determines how the keyboard
state maps to a group/level to be used for indexing the keymap.
For example, some keys are affected by shift/shiftlock and
the interaction of those two; others are affected by the
current keyboard group; others (such as Enter) are not affected
by any of the keyboard modes.
gdk_keyval_get_keys() finds all keycode/group/level triplets that
generate a given keyval.
gdk_key_info_to_keyval() looks up the keyval for a keycode/group/level
triplet.
gdk_keycode_get_map() gets the keymap for a given keycode (list of
group/level<->keyval pairs).
gdk_keycode_translate() uses the logical key type to derive an
effective group/level from the keyboard state (e.g. the enter key
may be reverted to group/level 0, shift/shiftlock interact to
determine a level). It then looks up the keyval using the effective
group/level (i.e. in effect calls gdk_key_info_to_keyval()).
Conceptually, gdk_keycode_translate() is the origin of the keyval
field in GdkEventKey, though in practice we aren't doing it that way.
I imagine it would make sense to implement it that way in the win32
port though, to avoid cut-and-paste.
The patch adds two fields to GdkEventKey, one is "hardware_keycode"
(long name is to keep people from making keycode/keyval typos), the
other is "group". The group is the keyboard state, not the effective
group.
This patch also contains the reason I needed to add these features,
which is that in some cases you want to know that "shift-7" has been
pressed, rather than "shift-&", specifically I added a
Shift-Control-hexdigit Unicode input method. Probably we should
change GTK accelerators to work this way as well.
Havoc
Index: ChangeLog
===================================================================
RCS file: /cvs/gnome/gtk+/ChangeLog,v
retrieving revision 1.1577
diff -u -u -r1.1577 ChangeLog
--- ChangeLog 2000/12/02 07:51:34 1.1577
+++ ChangeLog 2000/12/02 19:31:07
@@ -1,4 +1,33 @@
-2000-11-30 Havoc Pennington <hp pobox com>
+2000-12-02 Havoc Pennington <hp pobox com>
+
+ * acconfig.h, configure.in: add checks and command line options
+ for XKB
+
+ * gdk/gdk.h (gdk_keyval_get_keys) (gdk_key_info_to_keyval)
+ (gdk_keycode_translate) (gdk_keycode_get_map):
+ Functions to deal with the mapping from keycodes to keyvals
+
+ * gdk/x11/gdkkeys-x11.c: Implement the above functions
+
+ * gdk/x11/gdkevents-x11.c (gdk_event_translate): Put the keycode
+ and group in the key event
+
+ * gdk/gdkevents.h (struct _GdkEventKey): Add a hardware_keycode
+ field with the low-level hardware key code, and a keyboard_group
+ field with the keyboard group
+
+ * gdk/gdktypes.h: Add GdkKeyInfo struct
+
+ * gdk/x11/gdkprivate-x11.h: include config.h for HAVE_XKB,
+ and declare a couple globals used for keymap handling
+
+ * gtk/gtkimcontextsimple.c: Implement ISO 14755 input method,
+ hold down Shift-Control and type a hex number to get a Unicode
+ character corresponding to the hex number
+ (gtk_im_context_simple_get_preedit_string): Fix cursor position
+ (return bytes not chars)
+
+2000-12-01 Havoc Pennington <hp redhat com>
* gtk/gtktextdisplay.c (gtk_text_layout_draw): don't create
dangling pointers to the appearance attributes from the
Index: configure.in
===================================================================
RCS file: /cvs/gnome/gtk+/configure.in,v
retrieving revision 1.180
diff -u -u -r1.180 configure.in
--- configure.in 2000/11/14 23:07:31 1.180
+++ configure.in 2000/12/02 19:31:07
@@ -112,6 +112,8 @@
, enable_xim="yes")
AC_ARG_ENABLE(xim_inst, [ --disable-xim-inst does not use xim instantiate callback],
, enable_xim_inst="maybe")
+AC_ARG_ENABLE(xkb, [ --enable-xkb support XKB [default=maybe]],
+ , enable_xkb="maybe")
AC_ARG_ENABLE(rebuilds, [ --disable-rebuilds disable all source autogeneration rules],,enable_rebuilds=yes)
AC_ARG_WITH(locale, [ --with-locale=LOCALE locale name you want to use ])
@@ -423,6 +425,20 @@
if test "x$enable_xim_inst" = "xyes"; then
AC_DEFINE(USE_X11R6_XIM)
fi
+ fi
+
+ # Check for XKB support.
+
+ if test "x$enable_xkb" = "xyes"; then
+ AC_MSG_WARN(XKB support explicitly enabled)
+ AC_DEFINE(HAVE_XKB)
+ elif test "x$enable_xkb" = "xmaybe"; then
+ AC_CHECK_LIB(X11, XkbQueryExtension,
+ AC_DEFINE(HAVE_XKB),
+ ,
+ $x_libs)
+ else
+ AC_MSG_WARN(XKB support explicitly disabled)
fi
x_cflags="$X_CFLAGS"
Index: acconfig.h
===================================================================
RCS file: /cvs/gnome/gtk+/acconfig.h,v
retrieving revision 1.18
diff -u -u -r1.18 acconfig.h
--- acconfig.h 2000/07/14 20:08:00 1.18
+++ acconfig.h 2000/12/02 19:31:07
@@ -43,6 +43,8 @@
/* Define to use X11R6 additions to XIM */
#undef USE_X11R6_XIM
+/* Define to use XKB extension */
+#undef HAVE_XKB
#undef XINPUT_NONE
#undef XINPUT_GXI
Index: gdk/gdk.h
===================================================================
RCS file: /cvs/gnome/gtk+/gdk/gdk.h,v
retrieving revision 1.80
diff -u -u -r1.80 gdk.h
--- gdk/gdk.h 2000/11/13 19:47:29 1.80
+++ gdk/gdk.h 2000/12/02 19:31:07
@@ -149,6 +149,22 @@
guint32 gdk_keyval_to_unicode (guint keyval) G_GNUC_CONST;
guint gdk_unicode_to_keyval (guint32 wc) G_GNUC_CONST;
+gboolean gdk_keyval_get_keys (guint keyval,
+ GdkKeyInfo **infos,
+ gint *n_infos);
+guint gdk_key_info_to_keyval (const GdkKeyInfo *info);
+gboolean gdk_keycode_translate (guint hardware_keycode,
+ GdkModifierType state,
+ gint group,
+ guint *keyval,
+ gint *effective_group,
+ gint *level,
+ GdkModifierType *unused_modifiers);
+void gdk_keycode_get_map (guint hardware_keycode,
+ GdkKeyInfo **infos,
+ guint **keyvals,
+ gint *n_entries);
+
/* Threading
*/
Index: gdk/gdkevents.h
===================================================================
RCS file: /cvs/gnome/gtk+/gdk/gdkevents.h,v
retrieving revision 1.8
diff -u -u -r1.8 gdkevents.h
--- gdk/gdkevents.h 2000/11/17 21:59:03 1.8
+++ gdk/gdkevents.h 2000/12/02 19:31:07
@@ -279,6 +279,8 @@
guint keyval;
gint length;
gchar *string;
+ guint16 hardware_keycode;
+ guint8 group;
};
struct _GdkEventCrossing
Index: gdk/gdktypes.h
===================================================================
RCS file: /cvs/gnome/gtk+/gdk/gdktypes.h,v
retrieving revision 1.57
diff -u -u -r1.57 gdktypes.h
--- gdk/gdktypes.h 2000/11/22 10:07:32 1.57
+++ gdk/gdktypes.h 2000/12/02 19:31:07
@@ -64,6 +64,7 @@
/* Type definitions for the basic structures.
*/
+typedef struct _GdkKeyInfo GdkKeyInfo;
typedef struct _GdkPoint GdkPoint;
typedef struct _GdkRectangle GdkRectangle;
typedef struct _GdkSegment GdkSegment;
@@ -162,6 +163,14 @@
GdkInputCondition condition);
typedef void (*GdkDestroyNotify) (gpointer data);
+
+/* GdkKeyInfo is a hardware key that can be mapped to a keyval */
+struct _GdkKeyInfo
+{
+ guint keycode;
+ gint group;
+ gint level;
+};
struct _GdkPoint
{
Index: gdk/x11/Makefile.am
===================================================================
RCS file: /cvs/gnome/gtk+/gdk/x11/Makefile.am,v
retrieving revision 1.15
diff -u -u -r1.15 Makefile.am
--- gdk/x11/Makefile.am 2000/07/14 20:08:07 1.15
+++ gdk/x11/Makefile.am 2000/12/02 19:31:07
@@ -55,6 +55,7 @@
gdkim-x11.c \
gdkimage-x11.c \
gdkinput.c \
+ gdkkeys-x11.c \
gdkmain-x11.c \
gdkpango-x11.c \
gdkpixmap-x11.c \
Index: gdk/x11/gdkevents-x11.c
===================================================================
RCS file: /cvs/gnome/gtk+/gdk/x11/gdkevents-x11.c,v
retrieving revision 1.34
diff -u -u -r1.34 gdkevents-x11.c
--- gdk/x11/gdkevents-x11.c 2000/10/04 16:51:42 1.34
+++ gdk/x11/gdkevents-x11.c 2000/12/02 19:31:08
@@ -40,6 +40,10 @@
#include "gdkinputprivate.h"
+#ifdef HAVE_XKB
+#include <X11/XKBlib.h>
+#endif
+
typedef struct _GdkIOClosure GdkIOClosure;
typedef struct _GdkEventPrivate GdkEventPrivate;
@@ -430,6 +434,7 @@
&keysym, &compose);
#endif
event->key.keyval = keysym;
+ event->key.hardware_keycode = xevent->xkey.keycode;
if (charcount > 0 && buf[charcount-1] == '\0')
charcount --;
@@ -448,6 +453,9 @@
charcount, buf);
}
#endif /* G_ENABLE_DEBUG */
+
+ /* bits 13 and 14 in the "state" field are the keyboard group */
+#define KEYBOARD_GROUP_MASK ((1 << 13) | (1 << 14))
event->key.type = GDK_KEY_PRESS;
event->key.window = window;
@@ -455,6 +463,8 @@
event->key.state = (GdkModifierType) xevent->xkey.state;
event->key.string = g_strdup (buf);
event->key.length = charcount;
+
+ event->key.group = xevent->xkey.state & KEYBOARD_GROUP_MASK;
break;
@@ -1161,8 +1171,16 @@
/* Let XLib know that there is a new keyboard mapping.
*/
XRefreshKeyboardMapping (&xevent->xmapping);
+ ++_gdk_keymap_serial;
return_val = FALSE;
break;
+
+#ifdef HAVE_XKB
+ case XkbMapNotify:
+ ++_gdk_keymap_serial;
+ return_val = FALSE;
+ break;
+#endif
default:
/* something else - (e.g., a Xinput event) */
Index: gdk/x11/gdkkeys-x11.c
===================================================================
RCS file: gdkkeys-x11.c
diff -N gdkkeys-x11.c
--- /dev/null Tue May 5 16:32:27 1998
+++ gdkkeys-x11.c Sat Dec 2 14:31:08 2000
@@ -0,0 +1,698 @@
+/* GDK - The GIMP Drawing Kit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
+ * file for a list of people on the GTK+ Team. See the ChangeLog
+ * files for a list of changes. These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+#include <errno.h>
+
+#include "gdk.h"
+
+#include "gdkprivate-x11.h"
+#include "gdkinternals.h"
+#include "gdkkeysyms.h"
+
+#include "config.h"
+
+guint _gdk_keymap_serial = 0;
+
+#ifdef HAVE_XKB
+#include <X11/XKBlib.h>
+
+gboolean _gdk_use_xkb = FALSE;
+static XkbDescPtr xkb_desc = NULL;
+
+static XkbDescPtr
+get_xkb (void)
+{
+ static guint current_serial = 0;
+
+ if (xkb_desc == NULL)
+ {
+ xkb_desc = XkbGetMap (gdk_display, XkbKeySymsMask, XkbUseCoreKbd);
+ if (xkb_desc == NULL)
+ g_error ("Failed to get keymap");
+ }
+ else if (current_serial != _gdk_keymap_serial)
+ {
+ XkbGetUpdatedMap (gdk_display, XkbKeySymsMask, xkb_desc);
+ }
+
+ current_serial = _gdk_keymap_serial;
+
+ return xkb_desc;
+}
+#endif /* HAVE_XKB */
+
+static KeySym* keymap = NULL;
+static gint keysyms_per_keycode = 0;
+static XModifierKeymap* mod_keymap = NULL;
+static gint min_keycode = 0;
+static gint max_keycode = 0;
+static GdkModifierType group_switch_mask = 0;
+
+static void
+update_keymaps (void)
+{
+ static guint current_serial = 0;
+
+#ifdef HAVE_XKB
+ g_assert (!_gdk_use_xkb);
+#endif
+
+ if (keymap == NULL ||
+ current_serial != _gdk_keymap_serial)
+ {
+ gint i;
+ gint map_size;
+
+ if (min_keycode == 0)
+ XDisplayKeycodes (gdk_display, &min_keycode, &max_keycode);
+
+ if (keymap)
+ XFree (keymap);
+
+ if (mod_keymap)
+ XFreeModifiermap (mod_keymap);
+
+ keymap = XGetKeyboardMapping (gdk_display, min_keycode,
+ max_keycode - min_keycode,
+ &keysyms_per_keycode);
+
+ mod_keymap = XGetModifierMapping (gdk_display);
+
+
+ group_switch_mask = 0;
+
+ /* there are 8 modifiers, and the first 3 are shift, shift lock,
+ * and control
+ */
+ map_size = 8 * mod_keymap->max_keypermod;
+ i = 3 * mod_keymap->max_keypermod;
+ while (i < map_size)
+ {
+ /* get the key code at this point in the map,
+ * see if its keysym is GDK_Mode_switch, if so
+ * we have the mode key
+ */
+ gint keycode = mod_keymap->modifiermap[i];
+
+ if (keycode >= min_keycode &&
+ keycode <= max_keycode)
+ {
+ gint j = 0;
+ KeySym *syms = keymap + (keycode - min_keycode) * keysyms_per_keycode;
+ while (j < keysyms_per_keycode)
+ {
+ if (syms[j] == GDK_Mode_switch)
+ {
+ /* This modifier swaps groups */
+
+ /* GDK_MOD1_MASK is 1 << 3 for example, i.e. the
+ * fourth modifier, i / keyspermod is the modifier
+ * index
+ */
+
+ group_switch_mask |= (1 << ( i / mod_keymap->max_keypermod));
+ break;
+ }
+
+ ++j;
+ }
+ }
+
+ ++i;
+ }
+ }
+}
+
+static const KeySym*
+get_keymap (void)
+{
+ update_keymaps ();
+
+ return keymap;
+}
+
+/**
+ * gdk_keyval_get_keys:
+ * @keyval: a keyval, such as %GDK_a, %GDK_Up, %GDK_Return, etc.
+ * @infos: return location for an array of #GdkKeyInfo
+ * @n_infos: return location for number of elements in returned array
+ *
+ * Obtains a list of keycode/group/level combinations that will
+ * generate @keyval. Groups and levels are two kinds of keyboard mode;
+ * in general, the level determines whether the top or bottom symbol
+ * on a key is used, and the group determines whether the left or
+ * right symbol is used. On US keyboards, the shift key changes the
+ * keyboard level, and there are no groups. A group switch key might
+ * convert a keyboard between Hebrew to English modes, for example.
+ * #GdkEventKey contains a %group field that indicates the active
+ * keyboard group. The level is computed from the modifier mask.
+ * gdk_key_info_to_keyval() and gdk_keycode_translate() are the
+ * reverse operations. The returned array should be freed
+ * with g_free().
+ *
+ * Return value: %TRUE if keys were found and returned
+ **/
+gboolean
+gdk_keyval_get_keys (guint keyval,
+ GdkKeyInfo **infos,
+ gint *n_infos)
+{
+ GArray *retval;
+
+ g_return_val_if_fail (infos != NULL, FALSE);
+ g_return_val_if_fail (n_infos != NULL, FALSE);
+ g_return_val_if_fail (keyval != 0, FALSE);
+
+ retval = g_array_new (FALSE, FALSE, sizeof (GdkKeyInfo));
+
+#ifdef HAVE_XKB
+ if (_gdk_use_xkb)
+ {
+ /* See sec 15.3.4 in XKB docs */
+
+ XkbDescRec *xkb = get_xkb ();
+ gint keycode;
+
+ keycode = min_keycode;
+
+ while (keycode <= max_keycode)
+ {
+ gint max_shift_levels = XkbKeyGroupsWidth (xkb, keycode); /* "key width" */
+ gint group = 0;
+ gint level = 0;
+ gint total_syms = XkbKeyNumSyms (xkb, keycode);
+ gint i = 0;
+ KeySym *entry;
+
+ /* entry is an array with all syms for group 0, all
+ * syms for group 1, etc. and for each group the
+ * shift level syms are in order
+ */
+ entry = XkbKeySymsPtr (xkb, keycode);
+
+ while (i < total_syms)
+ {
+ /* check out our cool loop invariant */
+ g_assert (i == (group * max_shift_levels + level));
+
+ if (entry[i] == keyval)
+ {
+ /* Found a match */
+ GdkKeyInfo info;
+
+ info.keycode = keycode;
+ info.group = group;
+ info.level = level;
+
+ g_array_append_val (retval, info);
+
+ g_assert (XkbKeySymEntry (xkb, keycode, level, group) == keyval);
+ }
+
+ ++level;
+
+ if (level == max_shift_levels)
+ {
+ level = 0;
+ ++group;
+ }
+
+ ++i;
+ }
+
+ ++keycode;
+ }
+ }
+ else
+#endif
+ {
+ const KeySym *map = get_keymap ();
+ gint keycode;
+
+ keycode = min_keycode;
+ while (keycode < max_keycode)
+ {
+ const KeySym *syms = map + (keycode - min_keycode) * keysyms_per_keycode;
+ gint i = 0;
+
+ while (i < keysyms_per_keycode)
+ {
+ if (syms[i] == keyval)
+ {
+ /* found a match */
+ GdkKeyInfo info;
+
+ info.keycode = keycode;
+
+ /* The "classic" non-XKB keymap has 2 levels per group */
+ info.group = i / 2;
+ info.level = i % 2;
+
+ g_array_append_val (retval, info);
+ }
+
+ ++i;
+ }
+
+ ++keycode;
+ }
+ }
+
+ if (retval->len > 0)
+ {
+ *infos = (GdkKeyInfo*) retval->data;
+ *n_infos = retval->len;
+ }
+ else
+ {
+ *infos = NULL;
+ *n_infos = 0;
+ }
+
+ g_array_free (retval, retval->len > 0 ? FALSE : TRUE);
+
+ return *n_infos > 0;
+}
+
+/**
+ * gdk_keycode_get_map:
+ * @hardware_keycode: a keycode
+ * @infos: return location for array of #GdkKeyInfo, or NULL
+ * @keyvals: return location for array of keyvals, or NULL
+ * @n_entries: length of @infos and @keyvals
+ *
+ * Returns the keyvals bound to @hardware_keycode.
+ * The Nth #GdkKeyInfo in @infos is bound to the Nth
+ * keyval in @keyvals. Free the returned arrays with g_free().
+ *
+ **/
+void
+gdk_keycode_get_map (guint hardware_keycode,
+ GdkKeyInfo **infos,
+ guint **keyvals,
+ gint *n_entries)
+{
+ GArray *info_array;
+ GArray *keyval_array;
+
+ g_return_if_fail (n_entries != NULL);
+
+ if (infos)
+ info_array = g_array_new (FALSE, FALSE, sizeof (GdkKeyInfo));
+ else
+ info_array = NULL;
+
+ if (keyvals)
+ keyval_array = g_array_new (FALSE, FALSE, sizeof (guint));
+ else
+ keyval_array = NULL;
+
+#ifdef HAVE_XKB
+ if (_gdk_use_xkb)
+ {
+ /* See sec 15.3.4 in XKB docs */
+
+ XkbDescRec *xkb = get_xkb ();
+ gint max_shift_levels;
+ gint group = 0;
+ gint level = 0;
+ gint total_syms;
+ gint i = 0;
+ KeySym *entry;
+
+ max_shift_levels = XkbKeyGroupsWidth (xkb, hardware_keycode); /* "key width" */
+ total_syms = XkbKeyNumSyms (xkb, hardware_keycode);
+
+ /* entry is an array with all syms for group 0, all
+ * syms for group 1, etc. and for each group the
+ * shift level syms are in order
+ */
+ entry = XkbKeySymsPtr (xkb, hardware_keycode);
+
+ while (i < total_syms)
+ {
+ /* check out our cool loop invariant */
+ g_assert (i == (group * max_shift_levels + level));
+
+ if (info_array)
+ {
+ GdkKeyInfo info;
+
+ info.keycode = hardware_keycode;
+ info.group = group;
+ info.level = level;
+
+ g_array_append_val (info_array, info);
+ }
+
+ if (keyval_array)
+ g_array_append_val (keyval_array, entry[i]);
+
+ ++level;
+
+ if (level == max_shift_levels)
+ {
+ level = 0;
+ ++group;
+ }
+
+ ++i;
+ }
+ }
+ else
+#endif
+ {
+ const KeySym *map = get_keymap ();
+ const KeySym *syms;
+ gint i = 0;
+
+ syms = map + (hardware_keycode - min_keycode) * keysyms_per_keycode;
+
+ while (i < keysyms_per_keycode)
+ {
+ if (info_array)
+ {
+ GdkKeyInfo info;
+
+ info.keycode = hardware_keycode;
+
+ /* The "classic" non-XKB keymap has 2 levels per group */
+ info.group = i / 2;
+ info.level = i % 2;
+
+ g_array_append_val (info_array, info);
+ }
+
+ if (keyval_array)
+ g_array_append_val (keyval_array, syms[i]);
+
+ ++i;
+ }
+ }
+
+ if ((info_array && info_array->len > 0) ||
+ (keyval_array && keyval_array->len > 0))
+ {
+ if (infos)
+ *infos = (GdkKeyInfo*) info_array->data;
+
+ if (keyvals)
+ *keyvals = (guint*) keyval_array->data;
+
+ if (info_array)
+ *n_entries = info_array->len;
+ else
+ *n_entries = keyval_array->len;
+ }
+ else
+ {
+ if (infos)
+ *infos = NULL;
+
+ if (keyvals)
+ *keyvals = NULL;
+
+ *n_entries = 0;
+ }
+
+ if (info_array)
+ g_array_free (info_array, info_array->len > 0 ? FALSE : TRUE);
+ if (keyval_array)
+ g_array_free (keyval_array, keyval_array->len > 0 ? FALSE : TRUE);
+}
+
+
+/**
+ * gdk_key_info_to_keyval:
+ * @info: a #GdkKeyInfo with keycode, group, and level initialized
+ *
+ * Looks up the keyval mapped to a keycode/group/level triplet.
+ * If no keyval is bound to @info, returns 0.
+ *
+ * Return value: a keyval, or 0 if none was mapped to the given @info
+ **/
+guint
+gdk_key_info_to_keyval (const GdkKeyInfo *info)
+{
+ g_return_val_if_fail (info != NULL, 0);
+ g_return_val_if_fail (info->group < 4, FALSE);
+
+#ifdef HAVE_XKB
+ if (_gdk_use_xkb)
+ {
+ XkbDescRec *xkb = get_xkb ();
+
+ return XkbKeySymEntry (xkb, info->keycode, info->level, info->group);
+ }
+ else
+#endif
+ {
+ update_keymaps ();
+
+ return XKeycodeToKeysym (gdk_display, info->keycode,
+ info->group * keysyms_per_keycode + info->level);
+ }
+}
+
+#ifdef HAVE_XKB
+/* This is copied straight from XFree86 Xlib, because I needed to
+ * add the group and level return. It's unchanged for ease of
+ * diff against the Xlib sources; don't reformat it.
+ */
+static Bool
+MyEnhancedXkbTranslateKeyCode(register XkbDescPtr xkb,
+ KeyCode key,
+ register unsigned int mods,
+ unsigned int * mods_rtrn,
+ KeySym * keysym_rtrn,
+ unsigned int * group_rtrn,
+ unsigned int * level_rtrn)
+{
+ XkbKeyTypeRec *type;
+ int col,nKeyGroups;
+ unsigned preserve,effectiveGroup;
+ KeySym *syms;
+
+ if (mods_rtrn!=NULL)
+ *mods_rtrn = 0;
+
+ nKeyGroups= XkbKeyNumGroups(xkb,key);
+ if ((!XkbKeycodeInRange(xkb,key))||(nKeyGroups==0)) {
+ if (keysym_rtrn!=NULL)
+ *keysym_rtrn = NoSymbol;
+ return False;
+ }
+
+ syms = XkbKeySymsPtr(xkb,key);
+
+ /* find the offset of the effective group */
+ col = 0;
+ effectiveGroup= XkbGroupForCoreState(mods);
+ if ( effectiveGroup>=nKeyGroups ) {
+ unsigned groupInfo= XkbKeyGroupInfo(xkb,key);
+ switch (XkbOutOfRangeGroupAction(groupInfo)) {
+ default:
+ effectiveGroup %= nKeyGroups;
+ break;
+ case XkbClampIntoRange:
+ effectiveGroup = nKeyGroups-1;
+ break;
+ case XkbRedirectIntoRange:
+ effectiveGroup = XkbOutOfRangeGroupNumber(groupInfo);
+ if (effectiveGroup>=nKeyGroups)
+ effectiveGroup= 0;
+ break;
+ }
+ }
+ col= effectiveGroup*XkbKeyGroupsWidth(xkb,key);
+ type = XkbKeyKeyType(xkb,key,effectiveGroup);
+
+ preserve= 0;
+ if (type->map) { /* find the column (shift level) within the group */
+ register int i;
+ register XkbKTMapEntryPtr entry;
+ for (i=0,entry=type->map;i<type->map_count;i++,entry++) {
+ if ((entry->active)&&((mods&type->mods.mask)==entry->mods.mask)) {
+ col+= entry->level;
+ if (type->preserve)
+ preserve= type->preserve[i].mask;
+
+ /* ---- Begin stuff GDK adds to the original Xlib version ---- */
+
+ if (level_rtrn)
+ *level_rtrn = entry->level;
+
+ /* ---- End stuff GDK adds to the original Xlib version ---- */
+
+ break;
+ }
+ }
+ }
+
+ if (keysym_rtrn!=NULL)
+ *keysym_rtrn= syms[col];
+ if (mods_rtrn) {
+ *mods_rtrn= type->mods.mask&(~preserve);
+
+ /* ---- Begin stuff GDK comments out of the original Xlib version ---- */
+ /* This is commented out because xkb_info is a private struct */
+
+#if 0
+ /* The Motif VTS doesn't get the help callback called if help
+ * is bound to Shift+<whatever>, and it appears as though it
+ * is XkbTranslateKeyCode that is causing the problem. The
+ * core X version of XTranslateKey always OR's in ShiftMask
+ * and LockMask for mods_rtrn, so this "fix" keeps this behavior
+ * and solves the VTS problem.
+ */
+ if ((xkb->dpy)&&(xkb->dpy->xkb_info)&&
+ (xkb->dpy->xkb_info->xlib_ctrls&XkbLC_AlwaysConsumeShiftAndLock)) { *mods_rtrn|= (ShiftMask|LockMask);
+ }
+#endif
+
+ /* ---- End stuff GDK comments out of the original Xlib version ---- */
+ }
+
+ /* ---- Begin stuff GDK adds to the original Xlib version ---- */
+
+ if (group_rtrn)
+ *group_rtrn = effectiveGroup;
+
+ /* ---- End stuff GDK adds to the original Xlib version ---- */
+
+ return (syms[col]!=NoSymbol);
+}
+#endif /* HAVE_XKB */
+
+/**
+ * gdk_keycode_translate:
+ * @hardware_keycode: a keycode
+ * @state: a modifier state
+ * @group: active keyboard group
+ * @keyval: return location for keyval
+ * @effective_group: return location for effective group
+ * @level: return location for level
+ * @unused_modifiers: return location for modifiers that didn't affect the group or level
+ *
+ *
+ * Translates the contents of a #GdkEventKey into a keyval, effective
+ * group, and level. Modifiers that didn't affect the translation and
+ * are thus available for application use are returned in
+ * @unused_modifiers. See gdk_keyval_get_keys() for an explanation of
+ * groups and levels. The @effective_group is the group that was
+ * actually used for the translation; some keys such as Enter are not
+ * affected by the active keyboard group. The @level is derived from
+ * @state. For convenience, #GdkEventKey already contains the translated
+ * keyval, so this function isn't as useful as you might think.
+ *
+ * Return value: %TRUE if there was a keyval bound to the keycode/state/group
+ **/
+gboolean
+gdk_keycode_translate (guint hardware_keycode,
+ GdkModifierType state,
+ gint group,
+ guint *keyval,
+ gint *effective_group,
+ gint *level,
+ GdkModifierType *unused_modifiers)
+{
+ KeySym tmp_keyval = NoSymbol;
+
+ g_return_val_if_fail (group < 4, FALSE);
+
+ if (keyval)
+ *keyval = NoSymbol;
+ if (effective_group)
+ *effective_group = 0;
+ if (level)
+ *level = 0;
+ if (unused_modifiers)
+ *unused_modifiers = state;
+
+#ifdef HAVE_XKB
+ if (_gdk_use_xkb)
+ {
+ XkbDescRec *xkb = get_xkb ();
+
+ /* replace bits 13 and 14 with the provided group */
+ state &= ~(1 << 13 | 1 << 14);
+ state |= group << 13;
+
+ MyEnhancedXkbTranslateKeyCode (xkb,
+ hardware_keycode,
+ state,
+ unused_modifiers,
+ &tmp_keyval,
+ effective_group,
+ level);
+
+ if (keyval)
+ *keyval = tmp_keyval;
+ }
+ else
+#endif
+ {
+ gint shift_level;
+
+ update_keymaps ();
+
+ if ((state & GDK_SHIFT_MASK) &&
+ (state & GDK_LOCK_MASK))
+ shift_level = 0; /* shift disables shift lock */
+ else if ((state & GDK_SHIFT_MASK) ||
+ (state & GDK_LOCK_MASK))
+ shift_level = 1;
+ else
+ shift_level = 0;
+
+ tmp_keyval = XKeycodeToKeysym (gdk_display, hardware_keycode,
+ group * keysyms_per_keycode + shift_level);
+
+ if (keyval)
+ *keyval = tmp_keyval;
+
+ if (unused_modifiers)
+ {
+ *unused_modifiers = state;
+ *unused_modifiers &= ~(GDK_SHIFT_MASK | GDK_LOCK_MASK | group_switch_mask);
+ }
+
+ if (effective_group)
+ *effective_group = (state & group_switch_mask) ? 1 : 0;
+
+ if (level)
+ *level = shift_level;
+ }
+
+ return tmp_keyval != NoSymbol;
+}
+
Index: gdk/x11/gdkmain-x11.c
===================================================================
RCS file: /cvs/gnome/gtk+/gdk/x11/gdkmain-x11.c,v
retrieving revision 1.121
diff -u -u -r1.121 gdkmain-x11.c
--- gdk/x11/gdkmain-x11.c 2000/10/04 16:51:42 1.121
+++ gdk/x11/gdkmain-x11.c 2000/12/02 19:31:08
@@ -45,11 +45,16 @@
#include <X11/Xutil.h>
#include <X11/cursorfont.h>
+#ifdef HAVE_XKB
+#include <X11/XKBlib.h>
+#endif
+
#include "gdk.h"
#include "gdkprivate-x11.h"
#include "gdkinternals.h"
#include "gdkinputprivate.h"
+
#include <pango/pangox.h>
typedef struct _GdkPredicate GdkPredicate;
@@ -196,6 +201,28 @@
XGetKeyboardControl (gdk_display, &keyboard_state);
autorepeat = keyboard_state.global_auto_repeat;
+
+#ifdef HAVE_XKB
+ {
+ gint xkb_major = XkbMajorVersion;
+ gint xkb_minor = XkbMinorVersion;
+ if (XkbLibraryVersion (&xkb_major, &xkb_minor))
+ {
+ xkb_major = XkbMajorVersion;
+ xkb_minor = XkbMinorVersion;
+ if (XkbQueryExtension (gdk_display, NULL, NULL, NULL,
+ &xkb_major, &xkb_minor))
+ {
+ _gdk_use_xkb = TRUE;
+
+ XkbSelectEvents (gdk_display,
+ XkbUseCoreKbd,
+ XkbMapNotifyMask,
+ XkbMapNotifyMask);
+ }
+ }
+ }
+#endif
return TRUE;
}
Index: gdk/x11/gdkprivate-x11.h
===================================================================
RCS file: /cvs/gnome/gtk+/gdk/x11/gdkprivate-x11.h,v
retrieving revision 1.6
diff -u -u -r1.6 gdkprivate-x11.h
--- gdk/x11/gdkprivate-x11.h 2000/10/24 00:15:13 1.6
+++ gdk/x11/gdkprivate-x11.h 2000/12/02 19:31:08
@@ -34,6 +34,8 @@
#include <gdk/gdkprivate.h>
#include "gdkx.h"
+#include <config.h>
+
void gdk_xid_table_insert (XID *xid,
gpointer data);
void gdk_xid_table_remove (XID xid);
@@ -101,5 +103,10 @@
extern GdkWindow *gdk_xim_window; /* currently using Window */
#endif /* USE_XIM */
+#ifdef HAVE_XKB
+/* Used to detect not-up-to-date XKB keymap */
+extern guint _gdk_keymap_serial;
+extern gboolean _gdk_use_xkb;
+#endif
#endif /* __GDK_PRIVATE_X11_H__ */
Index: gtk/gtkimcontextsimple.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkimcontextsimple.c,v
retrieving revision 1.9
diff -u -u -r1.9 gtkimcontextsimple.c
--- gtk/gtkimcontextsimple.c 2000/12/01 04:07:38 1.9
+++ gtk/gtkimcontextsimple.c 2000/12/02 19:31:08
@@ -794,7 +794,7 @@
static void
gtk_im_context_simple_init (GtkIMContextSimple *im_context_simple)
-{
+{
}
static void
@@ -823,6 +823,8 @@
len = g_unichar_to_utf8 (ch, buf);
buf[len] = '\0';
+ context_simple->in_hex_sequence = FALSE;
+
if (context_simple->tentative_match)
{
context_simple->tentative_match = 0;
@@ -916,46 +918,84 @@
return FALSE;
}
+/* In addition to the table-driven sequences, we allow Unicode hex
+ * codes entered with Ctrl-Shift held down as specified in ISO
+ * 14755. 14755 actually allows a different set of modifiers to be
+ * used at our discretion, but for now using Ctrl-Shift as in their
+ * examples. While holding Ctrl-Shift, pressing space commits the
+ * character, and pressing a non-hex-digit is an error.
+ */
+
+#define ISO_14755_MOD_MASK (GDK_CONTROL_MASK | GDK_SHIFT_MASK)
+
static gboolean
-gtk_im_context_simple_filter_keypress (GtkIMContext *context,
- GdkEventKey *event)
+check_hex (GtkIMContextSimple *context_simple,
+ gint n_compose)
{
- GtkIMContextSimple *context_simple = GTK_IM_CONTEXT_SIMPLE (context);
- GSList *tmp_list;
+ /* See if this is a hex sequence, return TRUE if so */
+ gint i;
+ GString *str;
+ gulong n;
+ gchar *nptr = NULL;
- gunichar ch;
- int n_compose = 0;
- int i;
+ str = g_string_new (NULL);
+
+ i = 0;
+ while (i < n_compose)
+ {
+ gunichar ch;
+ gchar buf[7];
+
+ ch = gdk_keyval_to_unicode (context_simple->compose_buffer[i]);
+
+ if (ch == 0)
+ return FALSE;
- /* Ignore modifier key presses, and any presses with modifiers set
- */
- for (i=0; i < G_N_ELEMENTS (gtk_compose_ignore); i++)
- if (event->keyval == gtk_compose_ignore[i])
- return FALSE;
+ if (!g_unichar_isxdigit (ch))
+ return FALSE;
- if (event->state &
- (gtk_accelerator_get_default_mod_mask () & ~GDK_SHIFT_MASK))
- return FALSE;
-
- /* Then, check for compose sequences
- */
- while (context_simple->compose_buffer[n_compose] != 0)
- n_compose++;
+ buf[g_unichar_to_utf8 (ch, buf)] = '\0';
- context_simple->compose_buffer[n_compose++] = event->keyval;
- context_simple->compose_buffer[n_compose] = 0;
+ g_string_append (str, buf);
+
+ ++i;
+ }
+
+ n = strtoul (str->str, &nptr, 16);
- tmp_list = context_simple->tables;
- while (tmp_list)
+ /* if strtoul fails it probably means non-latin digits were used;
+ * we should in principle handle that, but we probably don't.
+ */
+ if (str->str == nptr)
{
- if (check_table (context_simple, tmp_list->data, n_compose))
- return TRUE;
- tmp_list = tmp_list->next;
+ g_string_free (str, TRUE);
+ return FALSE;
}
+ else
+ g_string_free (str, TRUE);
- if (check_table (context_simple, >k_compose_table, n_compose))
- return TRUE;
+ if (n > 0xFFFF)
+ return FALSE; /* too many digits */
+
+ context_simple->tentative_match = n;
+ context_simple->tentative_match_len = n_compose;
+ gtk_signal_emit_by_name (GTK_OBJECT (context_simple),
+ "preedit-changed");
+
+ return TRUE;
+}
+
+static gboolean
+no_sequence_matches (GtkIMContextSimple *context_simple,
+ gint n_compose,
+ GdkEventKey *event)
+{
+ GtkIMContext *context;
+ gunichar ch;
+
+ context = GTK_IM_CONTEXT (context_simple);
+
/* No compose sequences found, check first if we have a partial
* match pending.
*/
@@ -997,6 +1037,139 @@
}
}
+static gboolean
+is_hex_keyval (guint keyval)
+{
+ gunichar ch = gdk_keyval_to_unicode (keyval);
+
+ return g_unichar_isxdigit (ch);
+}
+
+static guint
+canonical_hex_keyval (GdkEventKey *event)
+{
+ GdkKeyInfo info;
+ guint keyval;
+ guint *keyvals = NULL;
+ gint n_vals = 0;
+ gint i;
+
+ /* See if the keyval is already a hex digit */
+ if (is_hex_keyval (event->keyval))
+ return event->keyval;
+
+ /* See if this key would have generated a hex keyval in
+ * any other state, and return that hex keyval if so
+ */
+ gdk_keycode_get_map (event->hardware_keycode, NULL,
+ &keyvals, &n_vals);
+
+ keyval = 0;
+ i = 0;
+ while (i < n_vals)
+ {
+ if (is_hex_keyval (keyvals[i]))
+ {
+ keyval = keyvals[i];
+ break;
+ }
+
+ ++i;
+ }
+
+ g_free (keyvals);
+
+ if (keyval)
+ return keyval;
+ else
+ /* just return the keyval unchanged, we couldn't figure
+ * out a way to make it a hex digit
+ */
+ return event->keyval;
+}
+
+static gboolean
+gtk_im_context_simple_filter_keypress (GtkIMContext *context,
+ GdkEventKey *event)
+{
+ GtkIMContextSimple *context_simple = GTK_IM_CONTEXT_SIMPLE (context);
+ GSList *tmp_list;
+ int n_compose = 0;
+ int i;
+
+ /* FIXME? 14755 says you have to commit the char on release of the shift/control
+ * keys. But instead we wait for the user to type another char, or to lose focus.
+ */
+
+ /* Ignore modifier key presses
+ */
+ for (i=0; i < G_N_ELEMENTS (gtk_compose_ignore); i++)
+ if (event->keyval == gtk_compose_ignore[i])
+ return FALSE;
+
+ while (context_simple->compose_buffer[n_compose] != 0)
+ n_compose++;
+
+ /* First key in sequence; decide if it's a 14755 hex sequence */
+ if (n_compose == 0)
+ context_simple->in_hex_sequence =
+ ((event->state & (ISO_14755_MOD_MASK)) == ISO_14755_MOD_MASK);
+
+ /* If we are already in a non-hex sequence, or
+ * the 14755 modifiers are not held down, filter all
+ * key events with accelerator modifiers held down.
+ */
+ if (!context_simple->in_hex_sequence ||
+ ((event->state & (ISO_14755_MOD_MASK)) != ISO_14755_MOD_MASK))
+ {
+ if (event->state &
+ (gtk_accelerator_get_default_mod_mask () & ~GDK_SHIFT_MASK))
+ return FALSE;
+ }
+
+ /* Then, check for compose sequences
+ */
+ if (context_simple->in_hex_sequence)
+ context_simple->compose_buffer[n_compose++] = canonical_hex_keyval (event);
+ else
+ context_simple->compose_buffer[n_compose++] = event->keyval;
+
+ context_simple->compose_buffer[n_compose] = 0;
+
+ if (context_simple->in_hex_sequence)
+ {
+ /* If the modifiers are still held down, consider the sequence again */
+ if ((event->state & (ISO_14755_MOD_MASK)) == ISO_14755_MOD_MASK)
+ {
+ /* space ends the sequence, and we eat the space */
+ if (n_compose > 1 && event->keyval == GDK_space)
+ {
+ gtk_im_context_simple_commit_char (context, context_simple->tentative_match);
+ context_simple->compose_buffer[0] = 0;
+ return TRUE;
+ }
+ else if (check_hex (context_simple, n_compose))
+ return TRUE;
+ }
+ }
+ else
+ {
+ tmp_list = context_simple->tables;
+ while (tmp_list)
+ {
+ if (check_table (context_simple, tmp_list->data, n_compose))
+ return TRUE;
+ tmp_list = tmp_list->next;
+ }
+
+ if (check_table (context_simple, >k_compose_table, n_compose))
+ return TRUE;
+ }
+
+ /* The current compose_buffer doesn't match anything */
+ return no_sequence_matches (context_simple, n_compose, event);
+}
+
static void
gtk_im_context_simple_reset (GtkIMContext *context)
{
@@ -1006,6 +1179,8 @@
if (context_simple->tentative_match)
gtk_im_context_simple_commit_char (context, context_simple->tentative_match);
+
+ context_simple->in_hex_sequence = FALSE;
}
static void
@@ -1014,32 +1189,51 @@
PangoAttrList **attrs,
gint *cursor_pos)
{
- char outbuf[7];
+ char outbuf[25]; /* up to 4 hex digits */
int len = 0;
-
+
GtkIMContextSimple *context_simple = GTK_IM_CONTEXT_SIMPLE (context);
if (context_simple->tentative_match)
- len = g_unichar_to_utf8 (context_simple->tentative_match, outbuf);
-
+ {
+ if (context_simple->in_hex_sequence)
+ {
+ int hexchars = 0;
+
+ while (context_simple->compose_buffer[hexchars] != 0)
+ {
+ len += g_unichar_to_utf8 (gdk_keyval_to_unicode (context_simple->compose_buffer[hexchars]),
+ outbuf + len);
+ ++hexchars;
+ }
+ }
+ else
+ {
+ len = g_unichar_to_utf8 (context_simple->tentative_match, outbuf);
+ }
+
+ g_assert (len <= 25);
+ outbuf[len] = '\0';
+ }
+
if (str)
- *str = g_strndup (outbuf, len);
+ *str = g_strdup (outbuf);
if (attrs)
{
*attrs = pango_attr_list_new();
-
+
if (len)
{
PangoAttribute *attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
attr->start_index = 0;
- attr->end_index = len;
+ attr->end_index = len;
pango_attr_list_insert (*attrs, attr);
}
}
if (cursor_pos)
- *cursor_pos = context_simple->tentative_match ? 1 : 0;
+ *cursor_pos = (context_simple->tentative_match ? len : 0);
}
/**
Index: gtk/gtkimcontextsimple.h
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkimcontextsimple.h,v
retrieving revision 1.5
diff -u -u -r1.5 gtkimcontextsimple.h
--- gtk/gtkimcontextsimple.h 2000/11/12 03:43:22 1.5
+++ gtk/gtkimcontextsimple.h 2000/12/02 19:31:08
@@ -49,6 +49,8 @@
guint compose_buffer[GTK_MAX_COMPOSE_LEN + 1];
gunichar tentative_match;
gint tentative_match_len;
+
+ guint in_hex_sequence : 1;
};
struct _GtkIMContextSimpleClass
Index: gtk/gtktextiter.h
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtktextiter.h,v
retrieving revision 1.16
diff -u -u -r1.16 gtktextiter.h
--- gtk/gtktextiter.h 2000/12/02 07:51:37 1.16
+++ gtk/gtktextiter.h 2000/12/02 19:31:08
@@ -84,12 +84,13 @@
*/
gunichar gtk_text_iter_get_char (const GtkTextIter *iter);
-/* includes the 0xFFFD char for pixmaps/widgets, so char offsets
- into the returned string map properly into buffer char offsets */
+/* includes the 0xFFFC char for pixmaps/widgets, so char offsets
+ * into the returned string map properly into buffer char offsets
+ */
gchar *gtk_text_iter_get_slice (const GtkTextIter *start,
const GtkTextIter *end);
-/* includes only text, no 0xFFFD */
+/* includes only text, no 0xFFFC */
gchar *gtk_text_iter_get_text (const GtkTextIter *start,
const GtkTextIter *end);
/* exclude invisible chars */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]