Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:rkwasny
vino
vino-196556-i18n.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File vino-196556-i18n.patch of Package vino
--- configure.in +++ configure.in @@ -175,6 +175,14 @@ ]) AC_SUBST(XTEST_LIBS) +# +# Check for XKB extension +# +AC_CHECK_HEADER(X11/XKBlib.h, [ + AC_CHECK_LIB(X11, XkbQueryExtension, + [AC_DEFINE(HAVE_XKB, 1, [Defined if the XKB X extension is present])] + ,, $X_LIBS) + ]) dnl dnl From libvncserver --- server/vino-input.c +++ server/vino-input.c @@ -31,6 +31,106 @@ * x0rfbserver, the original native X vnc server (Jens Wagner) */ +/* + * Theory of keyboard operation + * + * The remote VNC client sends us a series of key press and release + * events identified as X keysyms. (Even Windows clients use X + * keysyms; that's just how the protocol works.) We then use our + * knowledge of the keyboard layout to translate the keysyms into + * keycodes, and use XTEST to send events for the appropriate + * keycodes. + * + * + * Keyboard layouts + * + * The XKEYBOARD extension describes the keyboard as having up to 4 + * "groups" (layouts), and each key on the keyboard has some number of + * "levels" corresponding to combinations of modifier keys. Different + * keys may have different numbers of levels, with different meanings. + * The "Alt-Gr" key on the keyboard is bound to the keysym + * ISO_Level3_Shift, which is a modifer that switches to a level + * called "Level3" on some keys. + * + * The core X protocol (on non-XKB servers) has a simpler model, in + * which there are normally 2 groups, and each group has 2 levels + * (unshifted and shifted). The "Alt-Gr" key is bound to the keysym + * Mode_switch, which is a modifier key that switches to the second + * group when it is held down. + * + * Our model is a simplification of the XKB model that also fits in + * well with the core model; there are between 1 and 4 groups + * (keyboard layouts), and each group has exactly 4 levels (plain, + * Shift or NumLock, Alt-Gr, and Shift+Alt-Gr). + * + * + * Details of key event processing + * + * When a modifier keypress is received, vino sends a KeyPress to the + * server, and when the corresponding keyrelease event is received, it + * sends a KeyRelease to the server. But for non-modifier keys, it + * always sends both a KeyPress and a KeyRelease together when it + * receives just the keypress event. (This is because we assume that + * the client side will handle "key repeat", and if we left the key + * pressed down on the server as well, we'd get double repeating.) + * + * Shift and Alt-Gr are treated slightly different from the other + * modifier keys; they are considered to be just implementation + * details of how you type a particular keysym. Eg, if you type + * Control+Shift+? (on a US keyboard), you're typing Control because + * you want the Control key to be pressed, but you're only typing + * Shift because it's required to get a "?" rather than a "/". + * Different keyboard layouts require Shift and Alt-Gr to be used for + * different keysyms, so if the client and server keyboards are not of + * the same type, vino will sometimes need to send fake press and + * release events for Shift and Alt-Gr in order to be able to type the + * keys the client intended to type. (On keys that have no shifted + * state, like the arrow and function keys, Shift and Alt-Gr behave + * as ordinary modifiers, the same way Control and Alt do.) + * + * Two further bits of special behavior are needed for NumLock; first, + * the second key level, which is normally "Shift", actually + * corresponds to "NumLock" on keypad keys. Second, since the server's + * NumLock key could plausibly be locked in either state, we need to + * take its state into account when typing keypad keys. If the server + * supports XKB, we use the XkbStateNotify event to track NumLock + * state changes. If the server doesn't support XKB, we have to + * explicitly query the modifier state any time the client types a + * keypad key. + * + * On servers that support XKB, we also have to worry about multiple + * keyboard layouts. We need to keep track of what keysyms are + * available in what layouts, and what layout (or "group") the server + * is currently using. When a keysym arrives, we look for a way to + * type that keysym in the current group, but if it's only available + * in a different group, we have to change groups temporarily to type + * it (in the same way we have to temporarily change modifiers + * sometimes). Doing this programmatically is a little bit tricky; XKB + * provides XkbLatchGroup(), but if the layout doesn't use + * "XkbWrapIntoRange" semantics, then that can only switch to + * higher-numbered groups. Alternatively, we could find the + * group-switching keys on the keyboard and use them the way we use + * the Shift and Alt-Gr keys, but there are several different ways + * that group switching could be set up, and we'd need to support all + * of them. Anyway, all of the layouts in the xkeyboard-config package + * use XkbWrapIntoRange, so XkbLatchGroup() works for us in that case, + * so we just require that, and don't support multiple groups if the + * layout uses XkbRedirectIntoRange or XkbClampIntoRange. + * + * Two additional problems show up with Windows-based clients; first, + * Windows considers Alt-Gr to be equivalent to Control_L + Alt_R, so + * we have to translate that. Second, Windows clients never send + * events for dead keys. Instead, if you type a dead acute accent key + * followed by "e", it sends a single keypress event for XK_eacute. + * This works fine when the current keyboard layout has a key for that + * precomposed keysym, but if it doesn't, we need to decompose it back + * into multiple keypresses. There is no guarantee that the + * decompositions we use will actually work, but since compositions + * happen in the library, not the server, there is no way to find out + * 100% reliably what compositions are available to the window + * currently being typed into, so this is the best we can do. + */ + #include <config.h> #include "vino-input.h" @@ -42,105 +142,600 @@ #ifdef HAVE_XTEST #include <X11/extensions/XTest.h> #endif +#ifdef HAVE_XKB +#include <X11/XKBlib.h> +#endif #include "vino-util.h" -/* See <X11/keysymdef.h> - "Latin 1: Byte 3 = 0" - */ -#define VINO_IS_LATIN1_KEYSYM(k) ((k) != NoSymbol && ((k) & 0x0f00) == 0) +#define VINO_IS_MODIFIER_KEYSYM(k) (((k) >= XK_Shift_L && (k) <= XK_Hyper_R) || \ + (k) == XK_Num_Lock || \ + (k) == XK_Mode_switch || \ + (k) == XK_ISO_Level3_Shift) + +#define VINO_IS_KEYPAD_KEYSYM(k) ((k) >= XK_KP_Space && (k) <= XK_KP_Equal) typedef enum { VINO_LEFT_SHIFT = 1 << 0, VINO_RIGHT_SHIFT = 1 << 1, - VINO_ALT_GR = 1 << 2 + VINO_LEFT_CONTROL = 1 << 2, + VINO_RIGHT_ALT = 1 << 3, + VINO_ALT_GR = 1 << 4, + VINO_NUM_LOCK = 1 << 5 } VinoModifierState; -#define VINO_LEFT_OR_RIGHT_SHIFT (VINO_LEFT_SHIFT | VINO_RIGHT_SHIFT) +typedef enum +{ + VINO_LEVEL_PLAIN = 0, + VINO_LEVEL_SHIFT = 1, + VINO_LEVEL_ALTGR = 2, + VINO_LEVEL_SHIFT_ALTGR = 3, + VINO_LEVEL_NUM_LOCK = 4 +} VinoLevel; + +#define VINO_NUM_LEVELS 4 /* NumLock is special and doesn't count */ +#define VINO_NUM_GROUPS 4 + +#define VINO_LEVEL(state) ((((state) & (VINO_LEFT_SHIFT | VINO_RIGHT_SHIFT)) ? VINO_LEVEL_SHIFT : 0) | \ + (((state) & VINO_ALT_GR) ? VINO_LEVEL_ALTGR : 0) | \ + (((state) & VINO_NUM_LOCK) ? VINO_LEVEL_NUM_LOCK : 0)) + +#define VINO_LEVEL_IS_SHIFT(level) (level & VINO_LEVEL_SHIFT) +#define VINO_LEVEL_IS_ALTGR(level) (level & VINO_LEVEL_ALTGR) +#define VINO_LEVEL_IS_NUM_LOCK(level) (level & VINO_LEVEL_NUM_LOCK) typedef struct { - guint8 button_mask; + KeyCode keycode; + guint level; + gboolean keypad; +} VinoKeybinding; +typedef struct +{ + guint8 button_mask; VinoModifierState modifier_state; - guint8 modifiers [0x100]; - KeyCode keycodes [0x100]; +#ifdef HAVE_XKB + int current_group; +#endif + + GHashTable *keybindings; + GHashTable *decompositions; KeyCode left_shift_keycode; KeyCode right_shift_keycode; + KeyCode left_control_keycode; KeyCode alt_gr_keycode; + KeyCode num_lock_keycode; + + KeySym alt_gr_keysym; + guint num_lock_mod; + +#ifdef HAVE_XKB + int xkb_num_groups; + int xkb_event_type; +#endif guint initialized : 1; guint xtest_supported : 1; + guint xkb_supported : 1; } VinoInputData; /* Data is per-display, but we only handle a single display. */ static VinoInputData global_input_data = { 0, }; -/* Set up a keysym -> keycode + modifier mapping. - * - * RFB transmits the KeySym for a keypress, but we may only inject - * keycodes using XTest. Thus, we must ensure that the modifier - * state is such that the keycode we inject maps to the KeySym - * we received from the client. - */ #ifdef HAVE_XTEST + +static struct { + guint32 composed; + guint32 decomposed[3]; +} decompositions[] = { + { XK_Cabovedot, { XK_dead_abovedot, 'C', 0 } }, + { XK_Eabovedot, { XK_dead_abovedot, 'E', 0 } }, + { XK_Gabovedot, { XK_dead_abovedot, 'G', 0 } }, + { XK_Iabovedot, { XK_dead_abovedot, 'I', 0 } }, + { XK_Zabovedot, { XK_dead_abovedot, 'Z', 0 } }, + { XK_cabovedot, { XK_dead_abovedot, 'c', 0 } }, + { XK_eabovedot, { XK_dead_abovedot, 'e', 0 } }, + { XK_gabovedot, { XK_dead_abovedot, 'g', 0 } }, + { XK_idotless, { XK_dead_abovedot, 'i', 0 } }, + { XK_zabovedot, { XK_dead_abovedot, 'z', 0 } }, + { XK_abovedot, { XK_dead_abovedot, XK_dead_abovedot, 0 } }, + + { XK_Aring, { XK_dead_abovering, 'A', 0 } }, + { XK_Uring, { XK_dead_abovering, 'U', 0 } }, + { XK_aring, { XK_dead_abovering, 'a', 0 } }, + { XK_uring, { XK_dead_abovering, 'u', 0 } }, + { XK_degree, { XK_dead_abovering, XK_dead_abovering, 0 } }, + + { XK_Aacute, { XK_dead_acute, 'A', 0 } }, + { XK_Cacute, { XK_dead_acute, 'C', 0 } }, + { XK_Eacute, { XK_dead_acute, 'E', 0 } }, + { XK_Iacute, { XK_dead_acute, 'I', 0 } }, + { XK_Lacute, { XK_dead_acute, 'L', 0 } }, + { XK_Nacute, { XK_dead_acute, 'N', 0 } }, + { XK_Oacute, { XK_dead_acute, 'O', 0 } }, + { XK_Racute, { XK_dead_acute, 'R', 0 } }, + { XK_Sacute, { XK_dead_acute, 'S', 0 } }, + { XK_Uacute, { XK_dead_acute, 'U', 0 } }, + { XK_Yacute, { XK_dead_acute, 'Y', 0 } }, + { XK_Zacute, { XK_dead_acute, 'Z', 0 } }, + { XK_aacute, { XK_dead_acute, 'a', 0 } }, + { XK_cacute, { XK_dead_acute, 'c', 0 } }, + { XK_eacute, { XK_dead_acute, 'e', 0 } }, + { XK_iacute, { XK_dead_acute, 'i', 0 } }, + { XK_lacute, { XK_dead_acute, 'l', 0 } }, + { XK_nacute, { XK_dead_acute, 'n', 0 } }, + { XK_oacute, { XK_dead_acute, 'o', 0 } }, + { XK_racute, { XK_dead_acute, 'r', 0 } }, + { XK_sacute, { XK_dead_acute, 's', 0 } }, + { XK_uacute, { XK_dead_acute, 'u', 0 } }, + { XK_yacute, { XK_dead_acute, 'y', 0 } }, + { XK_zacute, { XK_dead_acute, 'z', 0 } }, + { XK_acute, { XK_dead_acute, XK_dead_acute, 0 } }, + + { XK_Abreve, { XK_dead_breve, 'A', 0 } }, + { XK_Gbreve, { XK_dead_breve, 'G', 0 } }, + { XK_Ubreve, { XK_dead_breve, 'U', 0 } }, + { XK_abreve, { XK_dead_breve, 'a', 0 } }, + { XK_gbreve, { XK_dead_breve, 'g', 0 } }, + { XK_ubreve, { XK_dead_breve, 'u', 0 } }, + { XK_breve, { XK_dead_breve, XK_dead_breve, 0 } }, + + { XK_Ccaron, { XK_dead_caron, 'C', 0 } }, + { XK_Dcaron, { XK_dead_caron, 'D', 0 } }, + { XK_Ecaron, { XK_dead_caron, 'E', 0 } }, + { XK_Lcaron, { XK_dead_caron, 'L', 0 } }, + { XK_Ncaron, { XK_dead_caron, 'N', 0 } }, + { XK_Rcaron, { XK_dead_caron, 'R', 0 } }, + { XK_Scaron, { XK_dead_caron, 'S', 0 } }, + { XK_Tcaron, { XK_dead_caron, 'T', 0 } }, + { XK_Zcaron, { XK_dead_caron, 'Z', 0 } }, + { XK_ccaron, { XK_dead_caron, 'c', 0 } }, + { XK_dcaron, { XK_dead_caron, 'd', 0 } }, + { XK_ecaron, { XK_dead_caron, 'e', 0 } }, + { XK_lcaron, { XK_dead_caron, 'l', 0 } }, + { XK_ncaron, { XK_dead_caron, 'n', 0 } }, + { XK_rcaron, { XK_dead_caron, 'r', 0 } }, + { XK_scaron, { XK_dead_caron, 's', 0 } }, + { XK_tcaron, { XK_dead_caron, 't', 0 } }, + { XK_zcaron, { XK_dead_caron, 'z', 0 } }, + { XK_caron, { XK_dead_caron, XK_dead_caron, 0 } }, + + { XK_Ccedilla, { XK_dead_cedilla, 'C', 0 } }, + { XK_Gcedilla, { XK_dead_cedilla, 'G', 0 } }, + { XK_Kcedilla, { XK_dead_cedilla, 'K', 0 } }, + { XK_Lcedilla, { XK_dead_cedilla, 'L', 0 } }, + { XK_Ncedilla, { XK_dead_cedilla, 'N', 0 } }, + { XK_Rcedilla, { XK_dead_cedilla, 'R', 0 } }, + { XK_Scedilla, { XK_dead_cedilla, 'S', 0 } }, + { XK_Tcedilla, { XK_dead_cedilla, 'T', 0 } }, + { XK_ccedilla, { XK_dead_cedilla, 'c', 0 } }, + { XK_gcedilla, { XK_dead_cedilla, 'g', 0 } }, + { XK_kcedilla, { XK_dead_cedilla, 'k', 0 } }, + { XK_lcedilla, { XK_dead_cedilla, 'l', 0 } }, + { XK_ncedilla, { XK_dead_cedilla, 'n', 0 } }, + { XK_rcedilla, { XK_dead_cedilla, 'r', 0 } }, + { XK_scedilla, { XK_dead_cedilla, 's', 0 } }, + { XK_tcedilla, { XK_dead_cedilla, 't', 0 } }, + { XK_cedilla, { XK_dead_cedilla, XK_dead_cedilla, 0 } }, + + { XK_Acircumflex, { XK_dead_circumflex, 'A', 0 } }, + { XK_Ccircumflex, { XK_dead_circumflex, 'C', 0 } }, + { XK_Ecircumflex, { XK_dead_circumflex, 'E', 0 } }, + { XK_Gcircumflex, { XK_dead_circumflex, 'G', 0 } }, + { XK_Hcircumflex, { XK_dead_circumflex, 'H', 0 } }, + { XK_Icircumflex, { XK_dead_circumflex, 'I', 0 } }, + { XK_Jcircumflex, { XK_dead_circumflex, 'J', 0 } }, + { XK_Ocircumflex, { XK_dead_circumflex, 'O', 0 } }, + { XK_Scircumflex, { XK_dead_circumflex, 'S', 0 } }, + { XK_Ucircumflex, { XK_dead_circumflex, 'U', 0 } }, + { XK_acircumflex, { XK_dead_circumflex, 'a', 0 } }, + { XK_ccircumflex, { XK_dead_circumflex, 'c', 0 } }, + { XK_ecircumflex, { XK_dead_circumflex, 'e', 0 } }, + { XK_gcircumflex, { XK_dead_circumflex, 'g', 0 } }, + { XK_hcircumflex, { XK_dead_circumflex, 'h', 0 } }, + { XK_icircumflex, { XK_dead_circumflex, 'i', 0 } }, + { XK_jcircumflex, { XK_dead_circumflex, 'j', 0 } }, + { XK_ocircumflex, { XK_dead_circumflex, 'o', 0 } }, + { XK_scircumflex, { XK_dead_circumflex, 's', 0 } }, + { XK_ucircumflex, { XK_dead_circumflex, 'u', 0 } }, + { XK_asciicircum, { XK_dead_circumflex, XK_dead_circumflex, 0 } }, + + { XK_Adiaeresis, { XK_dead_diaeresis, 'A', 0 } }, + { XK_Ediaeresis, { XK_dead_diaeresis, 'E', 0 } }, + { XK_Idiaeresis, { XK_dead_diaeresis, 'I', 0 } }, + { XK_Odiaeresis, { XK_dead_diaeresis, 'O', 0 } }, + { XK_Udiaeresis, { XK_dead_diaeresis, 'U', 0 } }, + { XK_adiaeresis, { XK_dead_diaeresis, 'a', 0 } }, + { XK_ediaeresis, { XK_dead_diaeresis, 'e', 0 } }, + { XK_idiaeresis, { XK_dead_diaeresis, 'i', 0 } }, + { XK_odiaeresis, { XK_dead_diaeresis, 'o', 0 } }, + { XK_udiaeresis, { XK_dead_diaeresis, 'u', 0 } }, + { XK_ydiaeresis, { XK_dead_diaeresis, 'y', 0 } }, + { XK_diaeresis, { XK_dead_diaeresis, XK_dead_diaeresis, 0 } }, + + { XK_Odoubleacute, { XK_dead_doubleacute, 'O', 0 } }, + { XK_Udoubleacute, { XK_dead_doubleacute, 'U', 0 } }, + { XK_odoubleacute, { XK_dead_doubleacute, 'o', 0 } }, + { XK_udoubleacute, { XK_dead_doubleacute, 'u', 0 } }, + { XK_doubleacute, { XK_dead_doubleacute, XK_dead_doubleacute, 0 } }, + + { XK_Agrave, { XK_dead_grave, 'A', 0 } }, + { XK_Egrave, { XK_dead_grave, 'E', 0 } }, + { XK_Igrave, { XK_dead_grave, 'I', 0 } }, + { XK_Ograve, { XK_dead_grave, 'O', 0 } }, + { XK_Ugrave, { XK_dead_grave, 'U', 0 } }, + { XK_agrave, { XK_dead_grave, 'a', 0 } }, + { XK_egrave, { XK_dead_grave, 'e', 0 } }, + { XK_igrave, { XK_dead_grave, 'i', 0 } }, + { XK_ograve, { XK_dead_grave, 'o', 0 } }, + { XK_ugrave, { XK_dead_grave, 'u', 0 } }, + { XK_grave, { XK_dead_grave, XK_dead_grave, 0 } }, + + { XK_Amacron, { XK_dead_macron, 'A', 0 } }, + { XK_Emacron, { XK_dead_macron, 'E', 0 } }, + { XK_Imacron, { XK_dead_macron, 'I', 0 } }, + { XK_Omacron, { XK_dead_macron, 'O', 0 } }, + { XK_Umacron, { XK_dead_macron, 'U', 0 } }, + { XK_amacron, { XK_dead_macron, 'a', 0 } }, + { XK_emacron, { XK_dead_macron, 'e', 0 } }, + { XK_imacron, { XK_dead_macron, 'i', 0 } }, + { XK_omacron, { XK_dead_macron, 'o', 0 } }, + { XK_umacron, { XK_dead_macron, 'u', 0 } }, + { XK_macron, { XK_dead_macron, XK_dead_macron, 0 } }, + + { XK_Aogonek, { XK_dead_ogonek, 'A', 0 } }, + { XK_Eogonek, { XK_dead_ogonek, 'E', 0 } }, + { XK_Iogonek, { XK_dead_ogonek, 'I', 0 } }, + { XK_Uogonek, { XK_dead_ogonek, 'U', 0 } }, + { XK_aogonek, { XK_dead_ogonek, 'a', 0 } }, + { XK_eogonek, { XK_dead_ogonek, 'e', 0 } }, + { XK_iogonek, { XK_dead_ogonek, 'i', 0 } }, + { XK_uogonek, { XK_dead_ogonek, 'u', 0 } }, + { XK_ogonek, { XK_dead_ogonek, XK_dead_ogonek, 0 } }, + + { XK_Atilde, { XK_dead_tilde, 'A', 0 } }, + { XK_Itilde, { XK_dead_tilde, 'I', 0 } }, + { XK_Ntilde, { XK_dead_tilde, 'N', 0 } }, + { XK_Otilde, { XK_dead_tilde, 'O', 0 } }, + { XK_Utilde, { XK_dead_tilde, 'U', 0 } }, + { XK_atilde, { XK_dead_tilde, 'a', 0 } }, + { XK_itilde, { XK_dead_tilde, 'i', 0 } }, + { XK_ntilde, { XK_dead_tilde, 'n', 0 } }, + { XK_otilde, { XK_dead_tilde, 'o', 0 } }, + { XK_utilde, { XK_dead_tilde, 'u', 0 } }, + { XK_asciitilde, { XK_dead_tilde, XK_dead_tilde, 0 } }, + + { XK_Greek_ALPHAaccent, { XK_dead_acute, XK_Greek_ALPHA, 0 } }, + { XK_Greek_EPSILONaccent, { XK_dead_acute, XK_Greek_EPSILON, 0 } }, + { XK_Greek_ETAaccent, { XK_dead_acute, XK_Greek_ETA, 0 } }, + { XK_Greek_IOTAaccent, { XK_dead_acute, XK_Greek_IOTA, 0 } }, + { XK_Greek_OMICRONaccent, { XK_dead_acute, XK_Greek_OMICRON, 0 } }, + { XK_Greek_UPSILONaccent, { XK_dead_acute, XK_Greek_UPSILON, 0 } }, + { XK_Greek_OMEGAaccent, { XK_dead_acute, XK_Greek_OMEGA, 0 } }, + { XK_Greek_alphaaccent, { XK_dead_acute, XK_Greek_alpha, 0 } }, + { XK_Greek_epsilonaccent, { XK_dead_acute, XK_Greek_epsilon, 0 } }, + { XK_Greek_etaaccent, { XK_dead_acute, XK_Greek_eta, 0 } }, + { XK_Greek_iotaaccent, { XK_dead_acute, XK_Greek_iota, 0 } }, + { XK_Greek_omicronaccent, { XK_dead_acute, XK_Greek_omicron, 0 } }, + { XK_Greek_upsilonaccent, { XK_dead_acute, XK_Greek_upsilon, 0 } }, + { XK_Greek_omegaaccent, { XK_dead_acute, XK_Greek_omega, 0 } }, + + { XK_Greek_IOTAdieresis, { XK_dead_diaeresis, XK_Greek_IOTA, 0 } }, + { XK_Greek_UPSILONdieresis, { XK_dead_diaeresis, XK_Greek_UPSILON, 0 } }, + { XK_Greek_iotadieresis, { XK_dead_diaeresis, XK_Greek_iota, 0 } }, + { XK_Greek_upsilondieresis, { XK_dead_diaeresis, XK_Greek_upsilon, 0 } }, + + { XK_Greek_iotaaccentdieresis, { XK_dead_acute, XK_dead_diaeresis, XK_Greek_iota } }, + { XK_Greek_upsilonaccentdieresis, { XK_dead_acute, XK_dead_diaeresis, XK_Greek_upsilon } } +}; +static const int num_decompositions = G_N_ELEMENTS (decompositions); + +static void vino_input_initialize_keycodes (Display *xdisplay); + static void -vino_input_initialize_keycodes (GdkDisplay *display) +vino_input_initialize_keycodes_core (Display *xdisplay) { - Display *xdisplay; int min_keycodes, max_keycodes; int keysyms_per_keycode; - KeySym *keymap; - int keycode; + KeySym sym; + int keycode, level, i; + XModifierKeymap *modmap; - xdisplay = GDK_DISPLAY_XDISPLAY (display); - - memset (global_input_data.keycodes, 0, sizeof (global_input_data.keycodes)); - memset (global_input_data.modifiers, -1, sizeof (global_input_data.modifiers)); + global_input_data.alt_gr_keysym = XK_Mode_switch; XDisplayKeycodes (xdisplay, &min_keycodes, &max_keycodes); + XGetKeyboardMapping (xdisplay, min_keycodes, 0, &keysyms_per_keycode); + + /* We iterate by level first, then by keycode, to ensure that we + * find an unshifted match for each keysym, if possible. + */ + for (level = 0; level < MAX (keysyms_per_keycode, VINO_NUM_LEVELS); level++) + { + for (keycode = min_keycodes; keycode < max_keycodes; keycode++) + { + VinoKeybinding *binding; + gboolean unmodifiable = FALSE; - g_assert (min_keycodes >= 8); - g_assert (max_keycodes <= 255); + sym = XKeycodeToKeysym (xdisplay, keycode, level); + if (sym == NoSymbol) + continue; + + if (g_hash_table_lookup (global_input_data.keybindings, + GUINT_TO_POINTER (sym))) + continue; + + binding = g_new (VinoKeybinding, VINO_NUM_GROUPS); + g_hash_table_insert (global_input_data.keybindings, + GUINT_TO_POINTER (sym), binding); + + /* There's only one group in plain X, so use "binding[0]". */ + binding[0].keycode = keycode; + binding[0].keypad = VINO_IS_KEYPAD_KEYSYM (sym); + + /* Check if this is a key like XK_uparrow that doesn't change + * when you press Shift/Alt-Gr. + */ + if (level == VINO_LEVEL_PLAIN) + { + KeySym shiftsym, altgrsym; - keymap = XGetKeyboardMapping (xdisplay, - min_keycodes, - max_keycodes - min_keycodes + 1, - &keysyms_per_keycode); + shiftsym = XKeycodeToKeysym (xdisplay, keycode, VINO_LEVEL_SHIFT); + altgrsym = (keysyms_per_keycode <= VINO_LEVEL_ALTGR) ? NoSymbol : + XKeycodeToKeysym (xdisplay, keycode, VINO_LEVEL_ALTGR); - g_assert (keymap != NULL); + unmodifiable = (shiftsym == NoSymbol) && (altgrsym == NoSymbol); + } - dprintf (INPUT, "Initializing keysym to keycode/modifier mapping\n"); + if (unmodifiable) + binding[0].level = -1; + else if (binding[0].keypad && level == VINO_LEVEL_SHIFT) + binding[0].level = VINO_LEVEL_NUM_LOCK; + else + binding[0].level = level; - for (keycode = min_keycodes; keycode < max_keycodes; keycode++) + dprintf (INPUT, "\t0x%.2x (%s) -> key %d level %d\n", (guint)sym, + XKeysymToString (sym), keycode, level); + } + } + + /* Find the modifier mask corresponding to NumLock */ + keycode = XKeysymToKeycode (xdisplay, XK_Num_Lock); + modmap = XGetModifierMapping (xdisplay); + for (i = 0; i < 8 * modmap->max_keypermod; i++) { - int keycode_index = (keycode - min_keycodes) * keysyms_per_keycode; - guint8 modifier; + if (modmap->modifiermap[i] == keycode) + { + global_input_data.num_lock_mod = 1 << (i / modmap->max_keypermod); + break; + } + } + XFreeModifiermap (modmap); +} - for (modifier = 0; modifier < keysyms_per_keycode; modifier++) +#ifdef HAVE_XKB +static void +vino_input_initialize_keycodes_xkb (Display *xdisplay) +{ + XkbDescPtr xkb; + XkbStateRec state; + int levelmap[XkbMaxKeyTypes][VINO_NUM_LEVELS], kc, kt, level, group; + int LevelThreeMask; + VinoKeybinding *binding; + KeySym sym; + + global_input_data.alt_gr_keysym = XK_ISO_Level3_Shift; + + xkb = XkbGetMap (xdisplay, XkbAllClientInfoMask, XkbUseCoreKbd); + g_assert (xkb != NULL); + + LevelThreeMask = XkbKeysymToModifiers (xdisplay, XK_ISO_Level3_Shift); + + /* XKB's levels don't map to VinoLevel, and in fact, may be + * different for different types of keys (eg, the second level on + * the function keys is Ctrl+Alt, not Shift). So we create + * "levelmap" to map VinoLevel values to keytype-specific levels. + */ + for (kt = 0; kt < xkb->map->num_types; kt++) + { + XkbKeyTypePtr type; + XkbModsPtr mods; + int ktl; + + levelmap[kt][VINO_LEVEL_PLAIN] = 0; + levelmap[kt][VINO_LEVEL_SHIFT] = -1; + levelmap[kt][VINO_LEVEL_ALTGR] = -1; + levelmap[kt][VINO_LEVEL_SHIFT_ALTGR] = -1; + + type = &xkb->map->types[kt]; + for (ktl = 0; ktl < type->map_count; ktl++) { - guint32 keysym = keymap [keycode_index + modifier]; + if (!type->map[ktl].active) + continue; + + mods = &type->map[ktl].mods; + if (mods->mask == ShiftMask) + levelmap[kt][VINO_LEVEL_SHIFT] = type->map[ktl].level; + else if (mods->mask == LevelThreeMask) + levelmap[kt][VINO_LEVEL_ALTGR] = type->map[ktl].level; + else if (mods->mask == (ShiftMask | LevelThreeMask)) + levelmap[kt][VINO_LEVEL_SHIFT_ALTGR] = type->map[ktl].level; + } + + if (levelmap[kt][VINO_LEVEL_SHIFT] == -1) + levelmap[kt][VINO_LEVEL_SHIFT] = levelmap[kt][VINO_LEVEL_PLAIN]; + if (levelmap[kt][VINO_LEVEL_ALTGR] == -1) + levelmap[kt][VINO_LEVEL_ALTGR] = levelmap[kt][VINO_LEVEL_PLAIN]; + if (levelmap[kt][VINO_LEVEL_SHIFT_ALTGR] == -1) + levelmap[kt][VINO_LEVEL_SHIFT_ALTGR] = levelmap[kt][VINO_LEVEL_SHIFT]; + } - if (VINO_IS_LATIN1_KEYSYM (keysym) && - XKeysymToKeycode (xdisplay, keysym) == keycode) + /* Now map out the keysyms. As in the core case, we iterate levels + * before keycodes, so our first match for a keysym in each group + * will be unshifted if possible. + */ + for (group = 0; group < VINO_NUM_GROUPS; group++) + { + for (level = 0; level < VINO_NUM_LEVELS; level++) + { + for (kc = xkb->min_key_code; kc <= xkb->max_key_code; kc++) { - if (global_input_data.keycodes [keysym] != 0) + int ngroups, kgroup; + + ngroups = XkbKeyNumGroups (xkb, kc); + if (ngroups == 0) continue; - global_input_data.keycodes [keysym] = keycode; - global_input_data.modifiers [keysym] = modifier; + /* If the key has fewer groups than the keyboard as a + * whole does, figure out which of the key's groups + * ("kgroup") will end up being used when the keyboard + * as a whole is using group "group". + */ + if (group >= ngroups) + { + if (xkb->map->key_sym_map[kc].group_info & XkbRedirectIntoRange) + kgroup = 0; + else if (xkb->map->key_sym_map[kc].group_info & XkbClampIntoRange) + kgroup = ngroups - 1; + else + kgroup = group % ngroups; + } + else + kgroup = group; + + kt = xkb->map->key_sym_map[kc].kt_index[kgroup]; + sym = XkbKeySymEntry (xkb, kc, levelmap[kt][level], kgroup); + if (!sym) + continue; + + binding = g_hash_table_lookup (global_input_data.keybindings, + GUINT_TO_POINTER (sym)); + if (!binding) + { + binding = g_new0 (VinoKeybinding, VINO_NUM_GROUPS); + g_hash_table_insert (global_input_data.keybindings, + GUINT_TO_POINTER (sym), binding); + } + + if (!binding[group].keycode) + { + binding[group].keycode = kc; + binding[group].keypad = (kt == XkbKeypadIndex); + if (xkb->map->types[kt].num_levels == 1) + binding[group].level = -1; + else if (binding[group].keypad && level == VINO_LEVEL_SHIFT) + binding[group].level = VINO_LEVEL_NUM_LOCK; + else + binding[group].level = level; + } + + if (kgroup == group) + { + dprintf (INPUT, "\t0x%.2x (%s) -> key %d group %d level %d\n", + (guint)sym, XKeysymToString (sym), kc, group, level); + } + } + } + } + + /* Figure out how switching between groups works; we only support + * XkbWrapIntoRange. (In theory, xkb->ctrls->group_info should equal + * XkbWrapIntoRange (ie, 0) when that's the case, but in practice + * there seems to be junk in the lower 4 bits, so we test for the + * absence of the other two flags instead.) + */ + if (XkbGetControls (xdisplay, XkbGroupsWrapMask, xkb) == Success) + { + if (!(xkb->ctrls->groups_wrap & (XkbClampIntoRange | XkbRedirectIntoRange))) + { + dprintf (INPUT, "%d groups configured\n", xkb->ctrls->num_groups); + global_input_data.xkb_num_groups = xkb->ctrls->num_groups; + } + else if (xkb->ctrls->num_groups > 1) + { + dprintf (INPUT, "groups_wrap is not WrapIntoRange, can't use\n"); + global_input_data.xkb_num_groups = -1; + } + } + + /* Find NumLock modifier mask and get initial NumLock state */ + global_input_data.num_lock_mod = XkbKeysymToModifiers (xdisplay, XK_Num_Lock); + if (XkbGetState (xdisplay, XkbUseCoreKbd, &state) == Success) + { + if (state.locked_mods & global_input_data.num_lock_mod) + global_input_data.modifier_state |= VINO_NUM_LOCK; + } + + XkbFreeKeyboard (xkb, 0, True); +} + +static GdkFilterReturn +xkb_event_filter (GdkXEvent *xevent, GdkEvent *event, gpointer user_data) +{ + VinoInputData *data = user_data; + XkbStateNotifyEvent *state; - dprintf (INPUT, "\t0x%.2x -> %d %d\n", keysym, keycode, modifier); + if (((XEvent *)xevent)->type == data->xkb_event_type) + { + switch (((XkbAnyEvent *)xevent)->xkb_type) + { + case XkbStateNotify: + state = (XkbStateNotifyEvent *)xevent; + + if (state->changed & XkbGroupStateMask) + { + data->current_group = state->group; + dprintf (INPUT, "current_group -> %d\n", data->current_group); } + if (state->changed & XkbModifierLockMask) + { + if (state->locked_mods & data->num_lock_mod) + data->modifier_state |= VINO_NUM_LOCK; + else + data->modifier_state &= ~VINO_NUM_LOCK; + dprintf (INPUT, "locked_mods -> %u, modifier_state -> %u\n", + state->locked_mods, data->modifier_state); + } + break; + + case XkbMapNotify: + XkbRefreshKeyboardMapping ((XkbMapNotifyEvent *)xevent); + /* fall through */ + + case XkbControlsNotify: + vino_input_initialize_keycodes (((XkbAnyEvent *)xevent)->display); + break; } } - XFree (keymap); + return GDK_FILTER_CONTINUE; +} +#endif + +static void +vino_input_initialize_keycodes (Display *xdisplay) +{ + dprintf (INPUT, "Initializing keysym to keycode/modifier mapping\n"); + + if (global_input_data.keybindings) + g_hash_table_destroy (global_input_data.keybindings); + global_input_data.keybindings = + g_hash_table_new_full (NULL, NULL, NULL, g_free); + +#ifdef HAVE_XKB + if (global_input_data.xkb_supported) + vino_input_initialize_keycodes_xkb (xdisplay); +#endif + if (!global_input_data.xkb_supported) + vino_input_initialize_keycodes_core (xdisplay); global_input_data.left_shift_keycode = XKeysymToKeycode (xdisplay, XK_Shift_L); global_input_data.right_shift_keycode = XKeysymToKeycode (xdisplay, XK_Shift_R); - global_input_data.alt_gr_keycode = XKeysymToKeycode (xdisplay, XK_Mode_switch); + global_input_data.left_control_keycode = XKeysymToKeycode (xdisplay, XK_Control_L); + global_input_data.num_lock_keycode = XKeysymToKeycode (xdisplay, XK_Num_Lock); + global_input_data.alt_gr_keycode = XKeysymToKeycode (xdisplay, global_input_data.alt_gr_keysym); } #endif /* HAVE_XTEST */ @@ -150,6 +745,7 @@ #ifdef HAVE_XTEST Display *xdisplay; int ignore, *i = &ignore; + int d; g_assert (global_input_data.initialized != TRUE); @@ -162,14 +758,49 @@ global_input_data.xtest_supported = TRUE; } - vino_input_initialize_keycodes (display); +#ifdef HAVE_XKB + if (XkbQueryExtension (xdisplay, NULL, &global_input_data.xkb_event_type, + NULL, NULL, NULL)) + { + XkbStateRec state; + + dprintf (INPUT, "Using XKB\n"); + + XkbGetState (xdisplay, XkbUseCoreKbd, &state); + global_input_data.current_group = state.group; + + XkbSelectEventDetails (xdisplay, XkbUseCoreKbd, XkbStateNotify, + XkbGroupStateMask | XkbModifierLockMask, + XkbGroupStateMask | XkbModifierLockMask); + XkbSelectEventDetails (xdisplay, XkbUseCoreKbd, XkbMapNotify, + XkbAllClientInfoMask, XkbAllClientInfoMask); + XkbSelectEventDetails (xdisplay, XkbUseCoreKbd, XkbControlsNotify, + XkbGroupsWrapMask, XkbGroupsWrapMask); + + gdk_window_add_filter (NULL, xkb_event_filter, &global_input_data); + + global_input_data.xkb_supported = TRUE; + } + else +#endif + global_input_data.xkb_supported = FALSE; + + global_input_data.decompositions = g_hash_table_new (NULL, NULL); + for (d = 0; d < num_decompositions; d++) + { + g_hash_table_insert (global_input_data.decompositions, + GUINT_TO_POINTER (decompositions[d].composed), + decompositions[d].decomposed); + } + + vino_input_initialize_keycodes (xdisplay); global_input_data.initialized = TRUE; return global_input_data.xtest_supported; #else return global_input_data.xtest_supported = FALSE; -#endif /* HAVE_XSHM */ +#endif /* HAVE_XTEST */ } void @@ -227,20 +858,46 @@ } } +/* If @key_press is %TRUE, adjusts the modifier state on the keyboard + * such that it's possible to type @binding. If @key_press is %FALSE, + * undoes that. + */ static void -vino_input_fake_modifier (GdkScreen *screen, +vino_input_fake_modifiers (Display *xdisplay, VinoInputData *input_data, - guint8 modifier, + VinoKeybinding *binding, gboolean key_press) { - Display *xdisplay; VinoModifierState modifier_state = input_data->modifier_state; + int cur_level; - xdisplay = GDK_DISPLAY_XDISPLAY (gdk_screen_get_display (screen)); + if (binding->level == -1) + { + /* The keysym is unaffected by modifier keys, so we should leave + * the modifiers in their current state. + */ + return; + } + + if (binding->keypad && !input_data->xkb_supported) + { + Window root, child; + int rx, ry, wx, wy; + unsigned int mask; + + XQueryPointer (xdisplay, RootWindow (xdisplay, DefaultScreen (xdisplay)), + &root, &child, &rx, &ry, &wx, &wy, &mask); + if (mask & input_data->num_lock_mod) + modifier_state |= VINO_NUM_LOCK; + else + modifier_state &= ~VINO_NUM_LOCK; + } - if ((modifier_state & VINO_LEFT_OR_RIGHT_SHIFT) && modifier != 1) + cur_level = VINO_LEVEL (modifier_state); + + if (VINO_LEVEL_IS_SHIFT (cur_level) && !VINO_LEVEL_IS_SHIFT (binding->level)) { - dprintf (INPUT, "Shift is down, but we don't want it to be\n"); + dprintf (INPUT, "Faking Shift %s\n", key_press ? "release" : "press"); if (modifier_state & VINO_LEFT_SHIFT) XTestFakeKeyEvent (xdisplay, @@ -255,9 +912,9 @@ CurrentTime); } - if (!(modifier_state & VINO_LEFT_OR_RIGHT_SHIFT) && modifier == 1) + if (!VINO_LEVEL_IS_SHIFT (cur_level) && VINO_LEVEL_IS_SHIFT (binding->level)) { - dprintf (INPUT, "Shift isn't down, but we want it to be\n"); + dprintf (INPUT, "Faking Shift %s\n", key_press ? "press" : "release"); XTestFakeKeyEvent (xdisplay, input_data->left_shift_keycode, @@ -265,25 +922,105 @@ CurrentTime); } - if ((modifier_state & VINO_ALT_GR) && modifier != 2) + if (VINO_LEVEL_IS_ALTGR (cur_level) != VINO_LEVEL_IS_ALTGR (binding->level)) { - dprintf (INPUT, "Alt is down, but we don't want it to be\n"); + gboolean want_pressed = VINO_LEVEL_IS_ALTGR (binding->level) ? key_press : !key_press; + + dprintf (INPUT, "Faking Alt-Gr %s\n", want_pressed ? "press" : "release"); XTestFakeKeyEvent (xdisplay, input_data->alt_gr_keycode, - !key_press, + want_pressed, CurrentTime); } - - if (!(modifier_state & VINO_ALT_GR) && modifier == 2) + + if (VINO_LEVEL_IS_NUM_LOCK (cur_level) != VINO_LEVEL_IS_NUM_LOCK (binding->level)) { - dprintf (INPUT, "Alt isn't down, but we want it to be\n"); + dprintf (INPUT, "Faking NumLock press/release\n"); XTestFakeKeyEvent (xdisplay, - input_data->alt_gr_keycode, - key_press, + input_data->num_lock_keycode, + True, CurrentTime); + XTestFakeKeyEvent (xdisplay, + input_data->num_lock_keycode, + False, + CurrentTime); + } +} + +static gboolean +vino_input_fake_keypress (Display *xdisplay, guint32 keysym) +{ + VinoKeybinding *binding = g_hash_table_lookup (global_input_data.keybindings, + GUINT_TO_POINTER (keysym)); + + if (binding) + { + int group; + +#ifdef HAVE_XKB + if (global_input_data.xkb_supported) + { + if (binding[global_input_data.current_group].keycode) + group = global_input_data.current_group; + else if (global_input_data.xkb_num_groups > 1) + { + for (group = 0; group < VINO_NUM_GROUPS; group ++) + { + if (binding[group].keycode) + { + dprintf (INPUT, "Latching group to %d\n", group); + XkbLatchGroup (xdisplay, XkbUseCoreKbd, + (group - global_input_data.current_group) % + global_input_data.xkb_num_groups); + break; + } + } + if (group == VINO_NUM_GROUPS) + return FALSE; + } + else + return FALSE; + } + else +#endif + group = 0; + + vino_input_fake_modifiers (xdisplay, &global_input_data, + &binding[group], TRUE); + + dprintf (INPUT, "Injecting keysym 0x%.2x (%s) press/release (via keycode %d, level %d)\n", + keysym, XKeysymToString (keysym), binding[group].keycode, binding[group].level); + + XTestFakeKeyEvent (xdisplay, binding[group].keycode, TRUE, CurrentTime); + XTestFakeKeyEvent (xdisplay, binding[group].keycode, FALSE, CurrentTime); + + vino_input_fake_modifiers (xdisplay, &global_input_data, + &binding[group], FALSE); + + return TRUE; + } + else + { + guint32 *decomposition = + g_hash_table_lookup (global_input_data.decompositions, + GUINT_TO_POINTER (keysym)); + int i; + + if (decomposition) + { + for (i = 0; i < 3 && decomposition[i]; i++) + { + if (!vino_input_fake_keypress (xdisplay, decomposition[i])) + return FALSE; + } + + return TRUE; + } } + + return FALSE; } #endif /* HAVE_XTEST */ @@ -295,12 +1032,15 @@ #ifdef HAVE_XTEST Display *xdisplay; + dprintf (INPUT, "Got key %s for %s\n", key_press ? "press" : "release", + XKeysymToString (keysym)); + /* * We inject a key press/release pair for all key presses * and ignore key releases. The exception is modifiers. */ - if (!key_press && !(keysym >= XK_Shift_L && keysym <= XK_Hyper_R)) + if (!key_press && !VINO_IS_MODIFIER_KEYSYM (keysym)) return; xdisplay = GDK_DISPLAY_XDISPLAY (gdk_screen_get_display (screen)); @@ -314,44 +1054,52 @@ keysym, key_press); vino_input_update_modifier_state (&global_input_data, - VINO_ALT_GR, XK_Mode_switch, + VINO_LEFT_CONTROL, XK_Control_L, + keysym, key_press); + + vino_input_update_modifier_state (&global_input_data, + VINO_ALT_GR, global_input_data.alt_gr_keysym, keysym, key_press); - if (VINO_IS_LATIN1_KEYSYM (keysym)) + if (!VINO_IS_MODIFIER_KEYSYM (keysym)) { - KeyCode keycode = global_input_data.keycodes [keysym]; - guint8 modifier = global_input_data.modifiers [keysym]; + vino_input_fake_keypress (xdisplay, keysym); + } + else if (key_press && keysym == XK_Alt_R && + (global_input_data.modifier_state & VINO_LEFT_CONTROL)) + { + /* Windows translates Alt-Gr to XK_Ctrl_L + XK_Alt_R */ - if (keycode != NoSymbol) - { - g_assert (key_press != FALSE); + dprintf (INPUT, "Translating Ctrl+Alt press into Alt-Gr\n"); - vino_input_fake_modifier (screen, &global_input_data, modifier, TRUE); + XTestFakeKeyEvent (xdisplay, global_input_data.left_control_keycode, + FALSE, CurrentTime); + XTestFakeKeyEvent (xdisplay, global_input_data.alt_gr_keycode, + TRUE, CurrentTime); - dprintf (INPUT, "Injecting keysym 0x%.2x %s (keycode %d, modifier %d)\n", - keysym, key_press ? "press" : "release", keycode, modifier); + global_input_data.modifier_state |= (VINO_RIGHT_ALT | VINO_ALT_GR); + } + else if (!key_press && keysym == XK_Control_L && + (global_input_data.modifier_state & VINO_RIGHT_ALT)) + { + dprintf (INPUT, "Translating Ctrl+Alt release into Alt-Gr\n"); - XTestFakeKeyEvent (xdisplay, keycode, TRUE, CurrentTime); - XTestFakeKeyEvent (xdisplay, keycode, FALSE, CurrentTime); + XTestFakeKeyEvent (xdisplay, global_input_data.alt_gr_keycode, + FALSE, CurrentTime); - vino_input_fake_modifier (screen, &global_input_data, modifier, FALSE); - } + global_input_data.modifier_state &= ~(VINO_RIGHT_ALT | VINO_ALT_GR); } - else if (keysym != XK_Caps_Lock) + else if (keysym != XK_Caps_Lock && keysym != XK_Num_Lock) { KeyCode keycode; if ((keycode = XKeysymToKeycode (xdisplay, keysym)) != NoSymbol) { - dprintf (INPUT, "Injecting keysym 0x%.2x %s (keycode %d)\n", - keysym, key_press ? "press" : "release", keycode); + dprintf (INPUT, "Injecting keysym 0x%.2x (%s) %s (keycode %d)\n", + keysym, XKeysymToString (keysym), + key_press ? "press" : "release", keycode); XTestFakeKeyEvent (xdisplay, keycode, key_press, CurrentTime); - - if (key_press && !(keysym >= XK_Shift_L && keysym <= XK_Hyper_R)) - { - XTestFakeKeyEvent (xdisplay, keycode, FALSE, CurrentTime); - } } }
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor