// Copyright status: this file is in the public domain. /* * Quotes here are from the protocol documet. The R4 protocol document * is slightly self-contradictory (in its description of how keysyms * are selected within groups); the quotes below are from the R7.7 * document, which has been fixed in that respect. * * A list of KEYSYMs is associated with each KEYCODE. The list is * intended to convey the set of symbols on the corresponding key. * If the list (ignoring trailing NoSymbol entries) is a single * KEYSYM "K", then the list is treated as if it were the list "K * NoSymbol K NoSymbol". If the list (ignoring trailing NoSymbol * entries) is a pair of KEYSYMs "K1 K2", then the list is treated * as if it were the list "K1 K2 K1 K2". If the list (ignoring * trailing NoSymbol entries) is a triple of KEYSYMs "K1 K2 K3", * then the list is treated as if it were the list " K1 K2 K3 * NoSymbol". When an explicit "void" element is desired in the * list, the value VoidSymbol can be used. * * The first four elements of the list are split into two groups * of KEYSYMs. Group 1 contains the first and second KEYSYMs, * Group 2 contains the third and fourth KEYSYMs. Within each * group, if the second element of the group is NoSymbol, then the * group should be treated as if the second element were the same * as the first element, except when the first element is an * alphabetic KEYSYM "K" for which both lowercase and uppercase * forms are defined. In that case, the group should be treated * as if the first element were the lowercase form of "K" and the * second element were the uppercase form of "K". * * The standard rules for obtaining a KEYSYM from a KeyPress event * make use of only the Group 1 and Group 2 KEYSYMs; no * interpretation of other KEYSYMs in the list is defined. The * modifier state determines which group to use. Switching * between groups is controlled by the KEYSYM named MODE SWITCH, * by attaching that KEYSYM to some KEYCODE and attaching that * KEYCODE to any one of the modifiers Mod1 through Mod5. This * modifier is called the "group modifier". For any KEYCODE, * Group 1 is used when the group modifier is off, and Group 2 is * used when the group modifier is on. * * The Lock modifier is interpreted as CapsLock when the KEYSYM * named CAPS LOCK is attached to some KEYCODE and that KEYCODE is * attached to the Lock modifier. The Lock modifier is * interpreted as ShiftLock when the KEYSYM named SHIFT LOCK is * attached to some KEYCODE and that KEYCODE is attached to the * Lock modifier. If the Lock modifier could be interpreted as * both CapsLock and ShiftLock, the CapsLock interpretation is * used. * * The operation of "keypad" keys is controlled by the KEYSYM * named NUM LOCK, by attaching that KEYSYM to some KEYCODE and * attaching that KEYCODE to any one of the modifiers Mod1 through * Mod5. This modifier is called the "numlock modifier". The * standard KEYSYMs with the prefix KEYPAD in their name are * called "keypad" KEYSYMs; these are KEYSYMS with numeric value * in the hexadecimal range #xFF80 to #xFFBD inclusive. In * addition, vendor-specific KEYSYMS in the hexadecimal range * #x11000000 to #x1100FFFF are also keypad KEYSYMs. * * Within a group, the choice of KEYSYM is determined by applying * the first rule that is satisfied from the following list: * * · The numlock modifier is on and the second KEYSYM is a keypad * KEYSYM. In this case, if the Shift modifier is on, or if the * Lock modifier is on and is interpreted as ShiftLock, then the * first KEYSYM is used; otherwise, the second KEYSYM is used. * * · The Shift and Lock modifiers are both off. In this case, the * first KEYSYM is used. * * · The Shift modifier is off, and the Lock modifier is on and is * interpreted as CapsLock. In this case, the first KEYSYM is * used, but if that KEYSYM is lowercase alphabetic, then the * corresponding uppercase KEYSYM is used instead. * * · The Shift modifier is on, and the Lock modifier is on and is * interpreted as CapsLock. In this case, the second KEYSYM is * used, but if that KEYSYM is lowercase alphabetic, then the * corresponding uppercase KEYSYM is used instead. * * · The Shift modifier is on, or the Lock modifier is on and is * interpreted as ShiftLock, or both. In this case, the second * KEYSYM is used. * * We convert the actual returned list to a 4-keysyms-per-keycode * effective list based on the above rules. This logic is partly here * and partly in case_fixup. Much of the interpretation logic is in * lx_kbmap_keysym(). We (with help from case_fixup) compute and save * data to make lx_kbmap_keysym() simpler, such as p->symflags. * * We ignore the R7.7 protocol document blather about Unicode. We * don't have any sane way to handle Unicode in any case. (We could * provide one, but no clients have any use for it, nor is that likely * to change. It would also mean yet more large tables; lx__cmap is * already uncomfortably big, and that's in a compact form we have to * search instead of just indexing into it.) */ #include #include "lx.h" #include "internal.h" static int kbmap_px = -1; typedef struct kmpriv KMPRIV; struct kmpriv { LX_CONN *xc; int kc0; int kc1; int nkc; LX_KEYSYM *syms; unsigned char symflags[256][2]; // See the comment on kbmap_gotmap(). #define SF_KS1_LOWER 0x01 #define SF_KS2_LOWER 0x02 #define SF_KS2_UPPER 0x04 #define SF_KS2_KEYPAD 0x08 int kperm; LX_KEYCODE *mods; unsigned int group_mask; unsigned int num_lock_mask; unsigned int lock_is_caps; int querying; int qkpk; LX_KEYSYM *qsv; int qkpm; LX_KEYCODE *qmap; LX_OP *setup_op; } ; static void rqmapping(KMPRIV *); // forward static int find_modifier_keysym(KMPRIV *p, int mod, LX_KEYSYM sym) { int mws; int kc; LX_KEYSYM *ksp; int spk; for (mws=p->kperm-1;mws>=0;mws--) { kc = p->mods[(mod*p->kperm)+mws]; if (! kc) continue; if ((kc < p->kc0) || (kc > p->kc1)) { lx__protoerr(p->xc,"modifier mapping contains invalid keycode %d",kc); return(-1); } ksp = &p->syms[(kc-p->kc0)*4]; for (spk=4-1;spk>=0;spk--) { if (ksp[spk] == sym) return(1); } } return(0); } // Find a keysym in the case-mapping table. static int casemap_find(LX_KEYSYM ks) { int l; int m; int h; l = -1; h = lx__n_casemap; while (h-l > 1) { m = (h + l) >> 1; if (ks < lx__cmap[m][0]) h = m; else l = m; } return( ((l < 0) || (ks != lx__cmap[l][0])) ? -1 : l ); } // See kbmap_gotmap()'s comment. static unsigned char case_fixup(LX_KEYSYM *ksp) { int m; unsigned char f; if (ksp[1] != LX_KEYSYM_NoSymbol) { f = 0; m = casemap_find(ksp[0]); if ((m >= 0) && (ksp[0] == lx__cmap[m][1])) f |= SF_KS1_LOWER; m = casemap_find(ksp[1]); if ((m >= 0) && (ksp[0] == lx__cmap[m][1])) f |= SF_KS2_LOWER; if ((m >= 0) && (ksp[0] == lx__cmap[m][2])) f |= SF_KS2_UPPER; } else { if (ksp[0] == LX_KEYSYM_NoSymbol) { f = 0; } else { m = casemap_find(ksp[0]); if (m < 0) { ksp[1] = ksp[0]; f = 0; } else { ksp[0] = lx__cmap[m][1]; ksp[1] = lx__cmap[m][2]; f = SF_KS1_LOWER | SF_KS2_UPPER; } } } if ( ((ksp[1] >= 0x0000ff80) && (ksp[1] <= 0x0000ffbd)) || ((ksp[1] >= 0x11000000) && (ksp[1] <= 0x1100ffff)) ) { f |= SF_KS2_KEYPAD; } return(f); } static void kbmap_gotmap(void *pv) { KMPRIV *p; int s; int i; int j; int n; static const unsigned int masks[] = { [3] = LX_EVS_Mod1, [4] = LX_EVS_Mod2, [5] = LX_EVS_Mod3, [6] = LX_EVS_Mod4, [7] = LX_EVS_Mod5 }; p = pv; free(p->syms); p->syms = malloc(p->nkc*4*sizeof(LX_KEYSYM)); switch (p->qkpk) { case 1: for (i=p->nkc-1,j=p->nkc*4;i>=0;i--) { p->syms[--j] = LX_KEYSYM_NoSymbol; p->syms[--j] = LX_KEYSYM_NoSymbol; p->syms[--j] = LX_KEYSYM_NoSymbol; p->syms[--j] = p->qsv[i]; } break; case 2: for (i=(p->nkc-1)*2,j=p->nkc*4;i>=0;i-=2) { p->syms[--j] = LX_KEYSYM_NoSymbol; p->syms[--j] = LX_KEYSYM_NoSymbol; p->syms[--j] = p->qsv[i+1]; p->syms[--j] = p->qsv[i]; } break; case 3: for (i=(p->nkc-1)*3,j=p->nkc*4;i>=0;i-=3) { p->syms[--j] = LX_KEYSYM_NoSymbol; p->syms[--j] = p->qsv[i+2]; p->syms[--j] = p->qsv[i+1]; p->syms[--j] = p->qsv[i]; } break; default: if (p->qkpk < 1) { for (i=(p->nkc*4)-1;i>=0;i--) p->syms[i] = LX_KEYSYM_NoSymbol; } else { for (i=(p->nkc-1)*p->qkpk,j=p->nkc*4;i>=0;i-=p->qkpk) { p->syms[--j] = p->qsv[i+3]; p->syms[--j] = p->qsv[i+2]; p->syms[--j] = p->qsv[i+1]; p->syms[--j] = p->qsv[i]; } } break; } free(p->qsv); for (i=(p->nkc*4)-2,j=(p->nkc*2)-1;i>=0;i-=2,j--) p->symflags[j>>1][j&1] = case_fixup(&p->syms[i]); for (i=p->nkc-1,j=p->nkc*4;i>=0;i--) { for (n=0;(n<4)&&(p->syms[j-1-n]==LX_KEYSYM_NoSymbol);n++) ; switch (n) { case 0: case 1: case 4: break; case 2: p->syms[j-1] = p->syms[j-3]; p->syms[j-2] = p->syms[j-4]; break; case 3: p->syms[j-2] = p->syms[j-4]; break; default: lx_abort(); return; break; } } free(p->mods); p->kperm = p->qkpm; p->mods = p->qmap; p->group_mask = 0; p->num_lock_mask = 0; for <"s"> (s=7;s>=3;s--) { i = find_modifier_keysym(p,s,LX_KEYSYM_Mode_switch); if (i < 0) return; if (i) p->group_mask |= masks[s]; i = find_modifier_keysym(p,s,LX_KEYSYM_Num_Lock); if (i < 0) return; if (i) p->num_lock_mask |= masks[s]; } i = find_modifier_keysym(p,1,LX_KEYSYM_Caps_Lock); if (i < 0) return; p->lock_is_caps = i; switch (p->querying) { case 1: p->querying = 0; if (p->setup_op) { LX_OP *op; op = p->setup_op; p->setup_op = 0; lx__op_finished(op); } break; case 2: p->querying = 0; rqmapping(p); break; default: lx_abort(); p->querying = 0; return; break; } } static void rqmapping(KMPRIV *p) { switch (p->querying) { case 0: p->querying = 1; break; case 1: p->querying = 2; break; case 2: return; break; default: lx_abort(); p->querying = 0; return; break; } p->qmap = malloc(2048); lx_op_drop(lx_GetKeyboardMapping(p->xc,p->kc0,p->kc1+1-p->kc0,&p->qkpk,&p->qsv)); lx_op_callback(lx_GetModifierMapping(p->xc,&p->qkpm,p->qmap),&kbmap_gotmap,p,0); } LX_OP *lx_kbmap_setup(LX_CONN *xc) { KMPRIV *p; if (xc->flags & XCF_FAIL) return(0); if (kbmap_px < 0) kbmap_px = lx_new_conn_private(); p = malloc(sizeof(KMPRIV)); lx_set_private(xc,kbmap_px,p); p->xc = xc; p->kc0 = lx_min_keycode(p->xc); p->kc1 = lx_max_keycode(p->xc); p->nkc = p->kc1 + 1 - p->kc0; p->syms = 0; p->kperm = 0; p->mods = 0; p->querying = 0; rqmapping(p); p->setup_op = lx__internal_op(xc); return(p->setup_op); } void lx_kbmap_update(LX_CONN *xc, const LX_EVENT_MappingNotify *e) { KMPRIV *p; if (xc->flags & XCF_FAIL) return; if (e->request == LX_MAPPINGREQUEST_Pointer) return; p = lx_get_private(xc,kbmap_px); if (! p) return; if (p->setup_op) return; rqmapping(p); } /* * Because of all the massaging done above, by kbmap_gotmap, this is * fairly simple. */ LX_KEYSYM lx_kbmap_keysym(LX_CONN *xc, unsigned char kc, unsigned int st) { KMPRIV *p; LX_KEYSYM *ksp; unsigned char f; int m; p = lx_get_private(xc,kbmap_px); if (! p) return(LX_KEYSYM_NoSymbol); if ((kc < p->kc0) || (kc > p->kc1)) return(LX_KEYSYM_NoSymbol); ksp = p->syms + (4 * (kc - p->kc0)); if (st & p->group_mask) { ksp += 2; f = p->symflags[kc-p->kc0][1]; } else { f = p->symflags[kc-p->kc0][0]; } // "Rule"s here are from the file header comment. // First rule. if ( (st & p->num_lock_mask) && (f & SF_KS2_KEYPAD) ) { return( ((st & LX_EVS_Shift) || ((st & LX_EVS_Lock) && !p->lock_is_caps)) ? ksp[0] : ksp[1] ); } // Second rule. if (! (st & (LX_EVS_Shift|LX_EVS_Lock))) return(ksp[0]); // Third rule. if ( !(st & LX_EVS_Shift) && (st & LX_EVS_Lock) && p->lock_is_caps ) { if (f & SF_KS1_LOWER) { m = casemap_find(ksp[0]); return((m<0)?ksp[0]:lx__cmap[m][2]); } } // Fourth rule. if ( ((st & (LX_EVS_Shift|LX_EVS_Lock)) == (LX_EVS_Shift|LX_EVS_Lock)) && p->lock_is_caps ) { if (f & SF_KS1_LOWER) { m = casemap_find(ksp[1]); return((m<0)?ksp[1]:lx__cmap[m][2]); } } // Fifth rule. if ((st & LX_EVS_Shift) || ((st & LX_EVS_Lock) && !p->lock_is_caps)) { return(ksp[1]); } // I think the above covers everything. In case not.... return(LX_KEYSYM_NoSymbol); } /* * This is awkward. We return an octet string (see the file header * comment paragraph about Unicode), but we have no way to return its * character set - and, as the file header comment says of Unicode, * clients aren't prepared to do anything with it anyway. * * So we do just Latin-1 until and unless we have use for a variant * that returns strings of Unicode codepoints instead of strings of * octets. * * XXX Do we want to do any kind of compose processing? */ int lx_kbmap_string(LX_CONN *xc, unsigned char kc, unsigned int st, unsigned char *str, LX_KEYSYM *ksp) { LX_KEYSYM ks; unsigned char c; ks = lx_kbmap_keysym(xc,kc,st); if (ksp) *ksp = ks; switch (ks) { case LX_KEYSYM_BackSpace: c = 8; break; case LX_KEYSYM_Tab: c = 9; break; case LX_KEYSYM_Linefeed: c = 10; break; case LX_KEYSYM_Return: c = 13; break; case LX_KEYSYM_Escape: c = 27; break; case LX_KEYSYM_Delete: c = 127; break; case LX_KEYSYM_KP_Space: c = 32; break; case LX_KEYSYM_KP_Tab: c = 9; break; case LX_KEYSYM_KP_Enter: c = 13; break; case LX_KEYSYM_KP_Delete: c = 127; break; case LX_KEYSYM_KP_Equal: c = 61; break; case LX_KEYSYM_KP_Multiply: c = 42; break; case LX_KEYSYM_KP_Add: c = 43; break; case LX_KEYSYM_KP_Separator: c = 44; break; case LX_KEYSYM_KP_Subtract: c = 45; break; case LX_KEYSYM_KP_Decimal: c = 46; break; case LX_KEYSYM_KP_Divide: c = 47; break; case LX_KEYSYM_KP_0: c = 48; break; case LX_KEYSYM_KP_1: c = 49; break; case LX_KEYSYM_KP_2: c = 50; break; case LX_KEYSYM_KP_3: c = 51; break; case LX_KEYSYM_KP_4: c = 52; break; case LX_KEYSYM_KP_5: c = 53; break; case LX_KEYSYM_KP_6: c = 54; break; case LX_KEYSYM_KP_7: c = 55; break; case LX_KEYSYM_KP_8: c = 56; break; case LX_KEYSYM_KP_9: c = 57; break; case LX_KEYSYM_space: case LX_KEYSYM_exclam: case LX_KEYSYM_quotedbl: case LX_KEYSYM_numbersign: case LX_KEYSYM_dollar: case LX_KEYSYM_percent: case LX_KEYSYM_ampersand: case LX_KEYSYM_apostrophe: case LX_KEYSYM_parenleft: case LX_KEYSYM_parenright: case LX_KEYSYM_asterisk: case LX_KEYSYM_plus: case LX_KEYSYM_comma: case LX_KEYSYM_minus: case LX_KEYSYM_period: case LX_KEYSYM_slash: case LX_KEYSYM_0: case LX_KEYSYM_1: case LX_KEYSYM_2: case LX_KEYSYM_3: case LX_KEYSYM_4: case LX_KEYSYM_5: case LX_KEYSYM_6: case LX_KEYSYM_7: case LX_KEYSYM_8: case LX_KEYSYM_9: case LX_KEYSYM_colon: case LX_KEYSYM_semicolon: case LX_KEYSYM_less: case LX_KEYSYM_equal: case LX_KEYSYM_greater: case LX_KEYSYM_question: case LX_KEYSYM_at: case LX_KEYSYM_A: case LX_KEYSYM_B: case LX_KEYSYM_C: case LX_KEYSYM_D: case LX_KEYSYM_E: case LX_KEYSYM_F: case LX_KEYSYM_G: case LX_KEYSYM_H: case LX_KEYSYM_I: case LX_KEYSYM_J: case LX_KEYSYM_K: case LX_KEYSYM_L: case LX_KEYSYM_M: case LX_KEYSYM_N: case LX_KEYSYM_O: case LX_KEYSYM_P: case LX_KEYSYM_Q: case LX_KEYSYM_R: case LX_KEYSYM_S: case LX_KEYSYM_T: case LX_KEYSYM_U: case LX_KEYSYM_V: case LX_KEYSYM_W: case LX_KEYSYM_X: case LX_KEYSYM_Y: case LX_KEYSYM_Z: case LX_KEYSYM_bracketleft: case LX_KEYSYM_backslash: case LX_KEYSYM_bracketright: case LX_KEYSYM_asciicircum: case LX_KEYSYM_underscore: case LX_KEYSYM_grave: case LX_KEYSYM_a: case LX_KEYSYM_b: case LX_KEYSYM_c: case LX_KEYSYM_d: case LX_KEYSYM_e: case LX_KEYSYM_f: case LX_KEYSYM_g: case LX_KEYSYM_h: case LX_KEYSYM_i: case LX_KEYSYM_j: case LX_KEYSYM_k: case LX_KEYSYM_l: case LX_KEYSYM_m: case LX_KEYSYM_n: case LX_KEYSYM_o: case LX_KEYSYM_p: case LX_KEYSYM_q: case LX_KEYSYM_r: case LX_KEYSYM_s: case LX_KEYSYM_t: case LX_KEYSYM_u: case LX_KEYSYM_v: case LX_KEYSYM_w: case LX_KEYSYM_x: case LX_KEYSYM_y: case LX_KEYSYM_z: case LX_KEYSYM_braceleft: case LX_KEYSYM_bar: case LX_KEYSYM_braceright: case LX_KEYSYM_asciitilde: case LX_KEYSYM_nobreakspace: case LX_KEYSYM_exclamdown: case LX_KEYSYM_cent: case LX_KEYSYM_sterling: case LX_KEYSYM_currency: case LX_KEYSYM_yen: case LX_KEYSYM_brokenbar: case LX_KEYSYM_section: case LX_KEYSYM_diaeresis: case LX_KEYSYM_copyright: case LX_KEYSYM_ordfeminine: case LX_KEYSYM_guillemotleft: case LX_KEYSYM_notsign: case LX_KEYSYM_hyphen: case LX_KEYSYM_registered: case LX_KEYSYM_macron: case LX_KEYSYM_degree: case LX_KEYSYM_plusminus: case LX_KEYSYM_twosuperior: case LX_KEYSYM_threesuperior: case LX_KEYSYM_acute: case LX_KEYSYM_mu: case LX_KEYSYM_paragraph: case LX_KEYSYM_periodcentered: case LX_KEYSYM_cedilla: case LX_KEYSYM_onesuperior: case LX_KEYSYM_masculine: case LX_KEYSYM_guillemotright: case LX_KEYSYM_onequarter: case LX_KEYSYM_onehalf: case LX_KEYSYM_threequarters: case LX_KEYSYM_questiondown: case LX_KEYSYM_Agrave: case LX_KEYSYM_Aacute: case LX_KEYSYM_Acircumflex: case LX_KEYSYM_Atilde: case LX_KEYSYM_Adiaeresis: case LX_KEYSYM_Aring: case LX_KEYSYM_AE: case LX_KEYSYM_Ccedilla: case LX_KEYSYM_Egrave: case LX_KEYSYM_Eacute: case LX_KEYSYM_Ecircumflex: case LX_KEYSYM_Ediaeresis: case LX_KEYSYM_Igrave: case LX_KEYSYM_Iacute: case LX_KEYSYM_Icircumflex: case LX_KEYSYM_Idiaeresis: case LX_KEYSYM_Eth: case LX_KEYSYM_Ntilde: case LX_KEYSYM_Ograve: case LX_KEYSYM_Oacute: case LX_KEYSYM_Ocircumflex: case LX_KEYSYM_Otilde: case LX_KEYSYM_Odiaeresis: case LX_KEYSYM_multiply: case LX_KEYSYM_Ooblique: case LX_KEYSYM_Ugrave: case LX_KEYSYM_Uacute: case LX_KEYSYM_Ucircumflex: case LX_KEYSYM_Udiaeresis: case LX_KEYSYM_Yacute: case LX_KEYSYM_Thorn: case LX_KEYSYM_ssharp: case LX_KEYSYM_agrave: case LX_KEYSYM_aacute: case LX_KEYSYM_acircumflex: case LX_KEYSYM_atilde: case LX_KEYSYM_adiaeresis: case LX_KEYSYM_aring: case LX_KEYSYM_ae: case LX_KEYSYM_ccedilla: case LX_KEYSYM_egrave: case LX_KEYSYM_eacute: case LX_KEYSYM_ecircumflex: case LX_KEYSYM_ediaeresis: case LX_KEYSYM_igrave: case LX_KEYSYM_iacute: case LX_KEYSYM_icircumflex: case LX_KEYSYM_idiaeresis: case LX_KEYSYM_eth: case LX_KEYSYM_ntilde: case LX_KEYSYM_ograve: case LX_KEYSYM_oacute: case LX_KEYSYM_ocircumflex: case LX_KEYSYM_otilde: case LX_KEYSYM_odiaeresis: case LX_KEYSYM_division: case LX_KEYSYM_oslash: case LX_KEYSYM_ugrave: case LX_KEYSYM_uacute: case LX_KEYSYM_ucircumflex: case LX_KEYSYM_udiaeresis: case LX_KEYSYM_yacute: case LX_KEYSYM_thorn: case LX_KEYSYM_ydiaeresis: c = ks & 0xff; break; default: return(0); break; } if (st & LX_EVS_Control) c &= 0x9f; str[0] = c; return(1); }