| /* |
| Copyright (C) 2002-2010 Karl J. Runge <[email protected]> |
| All rights reserved. |
| |
| This file is part of x11vnc. |
| |
| x11vnc is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2 of the License, or (at |
| your option) any later version. |
| |
| x11vnc 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 General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with x11vnc; if not, write to the Free Software |
| Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA |
| or see <http://www.gnu.org/licenses/>. |
| |
| In addition, as a special exception, Karl J. Runge |
| gives permission to link the code of its release of x11vnc with the |
| OpenSSL project's "OpenSSL" library (or with modified versions of it |
| that use the same license as the "OpenSSL" library), and distribute |
| the linked executables. You must obey the GNU General Public License |
| in all respects for all of the code used other than "OpenSSL". If you |
| modify this file, you may extend this exception to your version of the |
| file, but you are not obligated to do so. If you do not wish to do |
| so, delete this exception statement from your version. |
| */ |
| |
| /* -- keyboard.c -- */ |
| |
| #include "x11vnc.h" |
| #include "xwrappers.h" |
| #include "xrecord.h" |
| #include "xinerama.h" |
| #include "pointer.h" |
| #include "userinput.h" |
| #include "win_utils.h" |
| #include "rates.h" |
| #include "cleanup.h" |
| #include "allowed_input_t.h" |
| #include "unixpw.h" |
| #include "v4l.h" |
| #include "linuxfb.h" |
| #include "uinput.h" |
| #include "macosx.h" |
| #include "screen.h" |
| |
| void get_keystate(int *keystate); |
| void clear_modifiers(int init); |
| int track_mod_state(rfbKeySym keysym, rfbBool down, rfbBool set); |
| void clear_keys(void); |
| void clear_locks(void); |
| int get_autorepeat_state(void); |
| int get_initial_autorepeat_state(void); |
| void autorepeat(int restore, int bequiet); |
| void check_add_keysyms(void); |
| int add_keysym(KeySym keysym); |
| void delete_added_keycodes(int bequiet); |
| void initialize_remap(char *infile); |
| int sloppy_key_check(int key, rfbBool down, rfbKeySym keysym, int *new_kc); |
| void switch_to_xkb_if_better(void); |
| char *short_kmbcf(char *str); |
| void initialize_allowed_input(void); |
| void initialize_modtweak(void); |
| void initialize_keyboard_and_pointer(void); |
| void get_allowed_input(rfbClientPtr client, allowed_input_t *input); |
| double typing_rate(double time_window, int *repeating); |
| int skip_cr_when_scaling(char *mode); |
| void keyboard(rfbBool down, rfbKeySym keysym, rfbClientPtr client); |
| |
| |
| static void delete_keycode(KeyCode kc, int bequiet); |
| static int count_added_keycodes(void); |
| static void add_remap(char *line); |
| static void add_dead_keysyms(char *str); |
| static void initialize_xkb_modtweak(void); |
| static void xkb_tweak_keyboard(rfbBool down, rfbKeySym keysym, |
| rfbClientPtr client); |
| static void tweak_mod(signed char mod, rfbBool down); |
| static void modifier_tweak_keyboard(rfbBool down, rfbKeySym keysym, |
| rfbClientPtr client); |
| static void pipe_keyboard(rfbBool down, rfbKeySym keysym, rfbClientPtr client); |
| |
| |
| /* |
| * Routine to retreive current state keyboard. 1 means down, 0 up. |
| */ |
| void get_keystate(int *keystate) { |
| #if NO_X11 |
| RAWFB_RET_VOID |
| if (!keystate) {} |
| return; |
| #else |
| int i, k; |
| char keys[32]; |
| |
| RAWFB_RET_VOID |
| |
| /* n.b. caller decides to X_LOCK or not. */ |
| XQueryKeymap(dpy, keys); |
| for (i=0; i<32; i++) { |
| char c = keys[i]; |
| |
| for (k=0; k < 8; k++) { |
| if (c & 0x1) { |
| keystate[8*i + k] = 1; |
| } else { |
| keystate[8*i + k] = 0; |
| } |
| c = c >> 1; |
| } |
| } |
| #endif /* NO_X11 */ |
| } |
| |
| /* |
| * Try to KeyRelease any non-Lock modifiers that are down. |
| */ |
| void clear_modifiers(int init) { |
| #if NO_X11 |
| RAWFB_RET_VOID |
| if (!init) {} |
| return; |
| #else |
| static KeyCode keycodes[256]; |
| static KeySym keysyms[256]; |
| static char *keystrs[256]; |
| static int kcount = 0, first = 1; |
| int keystate[256]; |
| int i, j, minkey, maxkey, syms_per_keycode; |
| KeySym *keymap; |
| KeySym keysym; |
| KeyCode keycode; |
| |
| RAWFB_RET_VOID |
| |
| /* n.b. caller decides to X_LOCK or not. */ |
| if (first) { |
| /* |
| * we store results in static arrays, to aid interrupted |
| * case, but modifiers could have changed during session... |
| */ |
| XDisplayKeycodes(dpy, &minkey, &maxkey); |
| |
| keymap = XGetKeyboardMapping(dpy, minkey, (maxkey - minkey + 1), |
| &syms_per_keycode); |
| |
| for (i = minkey; i <= maxkey; i++) { |
| for (j = 0; j < syms_per_keycode; j++) { |
| char *str; |
| keysym = keymap[ (i - minkey) * syms_per_keycode + j ]; |
| if (keysym == NoSymbol || ! ismodkey(keysym)) { |
| continue; |
| } |
| keycode = XKeysymToKeycode(dpy, keysym); |
| if (keycode == NoSymbol) { |
| continue; |
| } |
| keycodes[kcount] = keycode; |
| keysyms[kcount] = keysym; |
| str = XKeysymToString(keysym); |
| if (! str) str = "null"; |
| keystrs[kcount] = strdup(str); |
| kcount++; |
| } |
| } |
| XFree_wr((void *) keymap); |
| first = 0; |
| } |
| if (init) { |
| return; |
| } |
| |
| get_keystate(keystate); |
| for (i=0; i < kcount; i++) { |
| keysym = keysyms[i]; |
| keycode = keycodes[i]; |
| |
| if (! keystate[(int) keycode]) { |
| continue; |
| } |
| |
| if (clear_mods) { |
| rfbLog("clear_modifiers: up: %-10s (0x%x) " |
| "keycode=0x%x\n", keystrs[i], keysym, keycode); |
| } |
| XTestFakeKeyEvent_wr(dpy, keycode, False, CurrentTime); |
| } |
| XFlush_wr(dpy); |
| #endif /* NO_X11 */ |
| } |
| |
| static KeySym simple_mods[] = { |
| XK_Shift_L, XK_Shift_R, |
| XK_Control_L, XK_Control_R, |
| XK_Meta_L, XK_Meta_R, |
| XK_Alt_L, XK_Alt_R, |
| XK_Super_L, XK_Super_R, |
| XK_Hyper_L, XK_Hyper_R, |
| XK_Mode_switch, |
| NoSymbol |
| }; |
| #define NSIMPLE_MODS 13 |
| |
| int track_mod_state(rfbKeySym keysym, rfbBool down, rfbBool set) { |
| KeySym sym = (KeySym) keysym; |
| static rfbBool isdown[NSIMPLE_MODS]; |
| static int first = 1; |
| int i, cnt = 0; |
| |
| /* |
| * simple tracking method for the modifier state without |
| * contacting the Xserver. Ignores, of course what keys are |
| * pressed on the physical display. |
| * |
| * This is unrelated to our mod_tweak and xkb stuff. |
| * Just a simple thing for wireframe/scroll heuristics, |
| * sloppy keys etc. |
| */ |
| |
| if (first) { |
| for (i=0; i<NSIMPLE_MODS; i++) { |
| isdown[i] = FALSE; |
| } |
| first = 0; |
| } |
| |
| if (sym != NoSymbol) { |
| for (i=0; i<NSIMPLE_MODS; i++) { |
| if (sym == simple_mods[i]) { |
| if (set) { |
| isdown[i] = down; |
| return 1; |
| } else { |
| if (isdown[i]) { |
| return 1; |
| } else { |
| return 0; |
| } |
| } |
| break; |
| } |
| } |
| /* not a modifier */ |
| if (set) { |
| return 0; |
| } else { |
| return -1; |
| } |
| } |
| |
| /* called with NoSymbol: return number currently pressed: */ |
| for (i=0; i<NSIMPLE_MODS; i++) { |
| if (isdown[i]) { |
| cnt++; |
| } |
| } |
| return cnt; |
| } |
| |
| /* |
| * Attempt to set all keys to Up position. Can mess up typing at the |
| * physical keyboard so use with caution. |
| */ |
| void clear_keys(void) { |
| int k, keystate[256]; |
| |
| RAWFB_RET_VOID |
| |
| /* n.b. caller decides to X_LOCK or not. */ |
| get_keystate(keystate); |
| for (k=0; k<256; k++) { |
| if (keystate[k]) { |
| KeyCode keycode = (KeyCode) k; |
| rfbLog("clear_keys: keycode=%d\n", keycode); |
| XTestFakeKeyEvent_wr(dpy, keycode, False, CurrentTime); |
| } |
| } |
| XFlush_wr(dpy); |
| } |
| |
| |
| void clear_locks(void) { |
| #if NO_X11 |
| RAWFB_RET_VOID |
| return; |
| #else |
| XModifierKeymap *map; |
| int i, j, k = 0; |
| unsigned int state = 0; |
| |
| RAWFB_RET_VOID |
| |
| /* n.b. caller decides to X_LOCK or not. */ |
| #if LIBVNCSERVER_HAVE_XKEYBOARD |
| if (xkb_present) { |
| XkbStateRec kbstate; |
| XkbGetState(dpy, XkbUseCoreKbd, &kbstate); |
| rfbLog("locked: 0x%x\n", kbstate.locked_mods); |
| rfbLog("latched: 0x%x\n", kbstate.latched_mods); |
| rfbLog("compat: 0x%x\n", kbstate.compat_state); |
| state = kbstate.locked_mods; |
| if (! state) { |
| state = kbstate.compat_state; |
| } |
| } else |
| #endif |
| { |
| state = mask_state(); |
| /* this may contain non-locks too... */ |
| rfbLog("state: 0x%x\n", state); |
| } |
| if (! state) { |
| return; |
| } |
| map = XGetModifierMapping(dpy); |
| if (! map) { |
| return; |
| } |
| for (i = 0; i < 8; i++) { |
| int did = 0; |
| for (j = 0; j < map->max_keypermod; j++) { |
| if (! did && state & (0x1 << i)) { |
| if (map->modifiermap[k]) { |
| KeyCode key = map->modifiermap[k]; |
| KeySym ks = XKeycodeToKeysym(dpy, key, 0); |
| char *nm = XKeysymToString(ks); |
| rfbLog("toggling: %03d / %03d -- %s\n", key, ks, nm ? nm : "BadKey"); |
| did = 1; |
| XTestFakeKeyEvent_wr(dpy, key, True, CurrentTime); |
| usleep(10*1000); |
| XTestFakeKeyEvent_wr(dpy, key, False, CurrentTime); |
| XFlush_wr(dpy); |
| } |
| } |
| k++; |
| } |
| } |
| XFreeModifiermap(map); |
| XFlush_wr(dpy); |
| rfbLog("state: 0x%x\n", mask_state()); |
| #endif |
| } |
| |
| /* |
| * Kludge for -norepeat option: we turn off keystroke autorepeat in |
| * the X server when clients are connected. This may annoy people at |
| * the physical display. We do this because 'key down' and 'key up' |
| * user input events may be separated by 100s of ms due to screen fb |
| * processing or link latency, thereby inducing the X server to apply |
| * autorepeat when it should not. Since the *client* is likely doing |
| * keystroke autorepeating as well, it kind of makes sense to shut it |
| * off if no one is at the physical display... |
| */ |
| static int save_auto_repeat = -1; |
| |
| int get_autorepeat_state(void) { |
| #if NO_X11 |
| RAWFB_RET(0) |
| return 0; |
| #else |
| XKeyboardState kstate; |
| |
| RAWFB_RET(0) |
| |
| X_LOCK; |
| XGetKeyboardControl(dpy, &kstate); |
| X_UNLOCK; |
| return kstate.global_auto_repeat; |
| #endif /* NO_X11 */ |
| } |
| |
| int get_initial_autorepeat_state(void) { |
| if (save_auto_repeat < 0) { |
| save_auto_repeat = get_autorepeat_state(); |
| } |
| return save_auto_repeat; |
| } |
| |
| void autorepeat(int restore, int bequiet) { |
| #if NO_X11 |
| RAWFB_RET_VOID |
| if (!restore || !bequiet) {} |
| return; |
| #else |
| int global_auto_repeat; |
| XKeyboardControl kctrl; |
| |
| RAWFB_RET_VOID |
| |
| if (restore) { |
| if (save_auto_repeat < 0) { |
| return; /* nothing to restore */ |
| } |
| global_auto_repeat = get_autorepeat_state(); |
| /* read state and skip restore if equal (e.g. no clients) */ |
| if (global_auto_repeat == save_auto_repeat) { |
| return; |
| } |
| |
| X_LOCK; |
| kctrl.auto_repeat_mode = save_auto_repeat; |
| XChangeKeyboardControl(dpy, KBAutoRepeatMode, &kctrl); |
| XFlush_wr(dpy); |
| X_UNLOCK; |
| |
| if (! bequiet && ! quiet) { |
| rfbLog("Restored X server key autorepeat to: %d\n", |
| save_auto_repeat); |
| } |
| } else { |
| global_auto_repeat = get_autorepeat_state(); |
| if (save_auto_repeat < 0) { |
| /* |
| * we only remember the state at startup |
| * to avoid confusing ourselves later on. |
| */ |
| save_auto_repeat = global_auto_repeat; |
| } |
| |
| X_LOCK; |
| kctrl.auto_repeat_mode = AutoRepeatModeOff; |
| XChangeKeyboardControl(dpy, KBAutoRepeatMode, &kctrl); |
| XFlush_wr(dpy); |
| X_UNLOCK; |
| |
| if (! bequiet && ! quiet) { |
| rfbLog("Disabled X server key autorepeat.\n"); |
| if (no_repeat_countdown >= 0) { |
| rfbLog(" to force back on run: 'xset r on' (%d " |
| "times)\n", no_repeat_countdown+1); |
| } |
| } |
| } |
| #endif /* NO_X11 */ |
| } |
| |
| /* |
| * We periodically delete any keysyms we have added, this is to |
| * lessen our effect on the X server state if we are terminated abruptly |
| * and cannot clear them and also to clear out any strange little used |
| * ones that would just fill up the keymapping. |
| */ |
| void check_add_keysyms(void) { |
| static time_t last_check = 0; |
| int clear_freq = 300, quiet = 1, count; |
| time_t now = time(NULL); |
| |
| if (unixpw_in_progress) return; |
| |
| if (now > last_check + clear_freq) { |
| count = count_added_keycodes(); |
| /* |
| * only really delete if they have not typed recently |
| * and we have added 8 or more. |
| */ |
| if (now > last_keyboard_input + 5 && count >= 8) { |
| X_LOCK; |
| delete_added_keycodes(quiet); |
| X_UNLOCK; |
| } |
| last_check = now; |
| } |
| } |
| |
| static KeySym added_keysyms[0x100]; |
| |
| /* these are just for rfbLog messages: */ |
| static KeySym alltime_added_keysyms[1024]; |
| static int alltime_len = 1024; |
| static int alltime_num = 0; |
| |
| int add_keysym(KeySym keysym) { |
| static int first = 1; |
| int n; |
| #if NO_X11 |
| if (first) { |
| for (n=0; n < 0x100; n++) { |
| added_keysyms[n] = NoSymbol; |
| } |
| first = 0; |
| } |
| RAWFB_RET(0) |
| if (!keysym) {} |
| return 0; |
| #else |
| int minkey, maxkey, syms_per_keycode; |
| int kc, ret = 0; |
| KeySym *keymap; |
| |
| if (first) { |
| for (n=0; n < 0x100; n++) { |
| added_keysyms[n] = NoSymbol; |
| } |
| first = 0; |
| } |
| |
| RAWFB_RET(0) |
| |
| if (keysym == NoSymbol) { |
| return 0; |
| } |
| /* there can be a race before MappingNotify */ |
| for (n=0; n < 0x100; n++) { |
| if (added_keysyms[n] == keysym) { |
| return n; |
| } |
| } |
| |
| XDisplayKeycodes(dpy, &minkey, &maxkey); |
| keymap = XGetKeyboardMapping(dpy, minkey, (maxkey - minkey + 1), |
| &syms_per_keycode); |
| |
| for (kc = minkey+1; kc <= maxkey; kc++) { |
| int i, j, didmsg = 0, is_empty = 1; |
| char *str; |
| KeySym newks[8]; |
| |
| for (n=0; n < syms_per_keycode; n++) { |
| if (keymap[ (kc-minkey) * syms_per_keycode + n] |
| != NoSymbol) { |
| is_empty = 0; |
| break; |
| } |
| } |
| if (! is_empty) { |
| continue; |
| } |
| |
| for (i=0; i<8; i++) { |
| newks[i] = NoSymbol; |
| } |
| if (add_keysyms == 2) { |
| newks[0] = keysym; /* XXX remove me */ |
| } else { |
| for(i=0; i < syms_per_keycode; i++) { |
| newks[i] = keysym; |
| if (i >= 7) break; |
| } |
| } |
| |
| XChangeKeyboardMapping(dpy, kc, syms_per_keycode, |
| newks, 1); |
| |
| if (alltime_num >= alltime_len) { |
| didmsg = 1; /* something weird */ |
| } else { |
| for (j=0; j<alltime_num; j++) { |
| if (alltime_added_keysyms[j] == keysym) { |
| didmsg = 1; |
| break; |
| } |
| } |
| } |
| if (! didmsg) { |
| str = XKeysymToString(keysym); |
| rfbLog("added missing keysym to X display: %03d " |
| "0x%x \"%s\"\n", kc, keysym, str ? str : "null"); |
| |
| if (alltime_num < alltime_len) { |
| alltime_added_keysyms[alltime_num++] = keysym; |
| } |
| } |
| |
| XFlush_wr(dpy); |
| added_keysyms[kc] = keysym; |
| ret = kc; |
| break; |
| } |
| XFree_wr(keymap); |
| return ret; |
| #endif /* NO_X11 */ |
| } |
| |
| static void delete_keycode(KeyCode kc, int bequiet) { |
| #if NO_X11 |
| RAWFB_RET_VOID |
| if (!kc || !bequiet) {} |
| return; |
| #else |
| int minkey, maxkey, syms_per_keycode, i; |
| KeySym *keymap; |
| KeySym ksym, newks[8]; |
| char *str; |
| |
| RAWFB_RET_VOID |
| |
| XDisplayKeycodes(dpy, &minkey, &maxkey); |
| keymap = XGetKeyboardMapping(dpy, minkey, (maxkey - minkey + 1), |
| &syms_per_keycode); |
| |
| for (i=0; i<8; i++) { |
| newks[i] = NoSymbol; |
| } |
| |
| XChangeKeyboardMapping(dpy, kc, syms_per_keycode, newks, 1); |
| |
| if (! bequiet && ! quiet) { |
| ksym = XKeycodeToKeysym(dpy, kc, 0); |
| str = XKeysymToString(ksym); |
| rfbLog("deleted keycode from X display: %03d 0x%x \"%s\"\n", |
| kc, ksym, str ? str : "null"); |
| } |
| |
| XFree_wr(keymap); |
| XFlush_wr(dpy); |
| #endif /* NO_X11 */ |
| } |
| |
| static int count_added_keycodes(void) { |
| int kc, count = 0; |
| for (kc = 0; kc < 0x100; kc++) { |
| if (added_keysyms[kc] != NoSymbol) { |
| count++; |
| } |
| } |
| return count; |
| } |
| |
| void delete_added_keycodes(int bequiet) { |
| int kc; |
| for (kc = 0; kc < 0x100; kc++) { |
| if (added_keysyms[kc] != NoSymbol) { |
| delete_keycode(kc, bequiet); |
| added_keysyms[kc] = NoSymbol; |
| } |
| } |
| } |
| |
| /* |
| * The following is for an experimental -remap option to allow the user |
| * to remap keystrokes. It is currently confusing wrt modifiers... |
| */ |
| typedef struct keyremap { |
| KeySym before; |
| KeySym after; |
| int isbutton; |
| struct keyremap *next; |
| } keyremap_t; |
| |
| static keyremap_t *keyremaps = NULL; |
| |
| static void add_remap(char *line) { |
| char str1[256], str2[256]; |
| KeySym ksym1, ksym2; |
| int isbtn = 0; |
| unsigned int i; |
| static keyremap_t *current = NULL; |
| keyremap_t *remap; |
| |
| if (sscanf(line, "%s %s", str1, str2) != 2) { |
| rfbLogEnable(1); |
| rfbLog("remap: invalid line: %s\n", line); |
| clean_up_exit(1); |
| } |
| if (sscanf(str1, "0x%x", &i) == 1) { |
| ksym1 = (KeySym) i; |
| } else { |
| ksym1 = XStringToKeysym(str1); |
| } |
| if (sscanf(str2, "0x%x", &i) == 1) { |
| ksym2 = (KeySym) i; |
| } else { |
| ksym2 = XStringToKeysym(str2); |
| } |
| if (ksym2 == NoSymbol) { |
| if (sscanf(str2, "Button%u", &i) == 1) { |
| ksym2 = (KeySym) i; |
| isbtn = 1; |
| } |
| } |
| if (ksym1 == NoSymbol || ksym2 == NoSymbol) { |
| if (strcasecmp(str2, "NoSymbol") && strcasecmp(str2, "None")) { |
| rfbLog("warning: skipping invalid remap line: %s", line); |
| return; |
| } |
| } |
| remap = (keyremap_t *) malloc((size_t) sizeof(keyremap_t)); |
| remap->before = ksym1; |
| remap->after = ksym2; |
| remap->isbutton = isbtn; |
| remap->next = NULL; |
| |
| rfbLog("remapping: (%s, 0x%x) -> (%s, 0x%x) isbtn=%d\n", str1, |
| ksym1, str2, ksym2, isbtn); |
| |
| if (keyremaps == NULL) { |
| keyremaps = remap; |
| } else { |
| current->next = remap; |
| } |
| current = remap; |
| } |
| |
| static void add_dead_keysyms(char *str) { |
| char *p, *q; |
| int i; |
| char *list[] = { |
| "g grave dead_grave", |
| "a acute dead_acute", |
| "c asciicircum dead_circumflex", |
| "t asciitilde dead_tilde", |
| "m macron dead_macron", |
| "b breve dead_breve", |
| "D abovedot dead_abovedot", |
| "d diaeresis dead_diaeresis", |
| "o degree dead_abovering", |
| "A doubleacute dead_doubleacute", |
| "r caron dead_caron", |
| "e cedilla dead_cedilla", |
| /* "x XXX-ogonek dead_ogonek", */ |
| /* "x XXX-belowdot dead_belowdot", */ |
| /* "x XXX-hook dead_hook", */ |
| /* "x XXX-horn dead_horn", */ |
| NULL |
| }; |
| |
| p = str; |
| |
| while (*p != '\0') { |
| if (isspace((unsigned char) (*p))) { |
| *p = '\0'; |
| } |
| p++; |
| } |
| |
| if (!strcmp(str, "DEAD")) { |
| for (i = 0; list[i] != NULL; i++) { |
| p = list[i] + 2; |
| add_remap(p); |
| } |
| } else if (!strcmp(str, "DEAD=missing")) { |
| for (i = 0; list[i] != NULL; i++) { |
| KeySym ksym, ksym2; |
| int inmap = 0; |
| |
| p = strdup(list[i] + 2); |
| q = strchr(p, ' '); |
| if (q == NULL) { |
| free(p); |
| continue; |
| } |
| *q = '\0'; |
| ksym = XStringToKeysym(p); |
| *q = ' '; |
| if (ksym == NoSymbol) { |
| free(p); |
| continue; |
| } |
| if (XKeysymToKeycode(dpy, ksym)) { |
| inmap = 1; |
| } |
| #if LIBVNCSERVER_HAVE_XKEYBOARD |
| if (! inmap && xkb_present && dpy) { |
| int kc, grp, lvl; |
| for (kc = 0; kc < 0x100; kc++) { |
| for (grp = 0; grp < 4; grp++) { |
| for (lvl = 0; lvl < 8; lvl++) { |
| ksym2 = XkbKeycodeToKeysym(dpy, |
| kc, grp, lvl); |
| if (ksym2 == NoSymbol) { |
| continue; |
| } |
| if (ksym2 == ksym) { |
| inmap = 1; |
| break; |
| } |
| } |
| } |
| } |
| } |
| #else |
| if ((ksym2 = 0)) {} |
| #endif |
| if (! inmap) { |
| add_remap(p); |
| } |
| free(p); |
| } |
| } else if ((p = strchr(str, '=')) != NULL) { |
| while (*p != '\0') { |
| for (i = 0; list[i] != NULL; i++) { |
| q = list[i]; |
| if (*p == *q) { |
| q += 2; |
| add_remap(q); |
| break; |
| } |
| } |
| p++; |
| } |
| } |
| } |
| |
| /* |
| * process the -remap string (file or mapping string) |
| */ |
| void initialize_remap(char *infile) { |
| FILE *in; |
| char *p, *q, line[256]; |
| |
| if (keyremaps != NULL) { |
| /* free last remapping */ |
| keyremap_t *next_remap, *curr_remap = keyremaps; |
| while (curr_remap != NULL) { |
| next_remap = curr_remap->next; |
| free(curr_remap); |
| curr_remap = next_remap; |
| } |
| keyremaps = NULL; |
| } |
| if (infile == NULL || *infile == '\0') { |
| /* just unset remapping */ |
| return; |
| } |
| |
| in = fopen(infile, "r"); |
| if (in == NULL) { |
| /* assume cmd line key1-key2,key3-key4 */ |
| if (strstr(infile, "DEAD") == infile) { |
| ; |
| } else if (!strchr(infile, '-')) { |
| rfbLogEnable(1); |
| rfbLog("remap: cannot open: %s\n", infile); |
| rfbLogPerror("fopen"); |
| clean_up_exit(1); |
| } |
| if ((in = tmpfile()) == NULL) { |
| rfbLogEnable(1); |
| rfbLog("remap: cannot open tmpfile for %s\n", infile); |
| rfbLogPerror("tmpfile"); |
| clean_up_exit(1); |
| } |
| |
| /* copy in the string to file format */ |
| p = infile; |
| while (*p) { |
| if (*p == '-') { |
| fprintf(in, " "); |
| } else if (*p == ',' || *p == ' ' || *p == '\t') { |
| fprintf(in, "\n"); |
| } else { |
| fprintf(in, "%c", *p); |
| } |
| p++; |
| } |
| fprintf(in, "\n"); |
| fflush(in); |
| rewind(in); |
| } |
| |
| while (fgets(line, 256, in) != NULL) { |
| p = lblanks(line); |
| if (*p == '\0') { |
| continue; |
| } |
| if (strchr(line, '#')) { |
| continue; |
| } |
| |
| if (strstr(p, "DEAD") == p) { |
| add_dead_keysyms(p); |
| continue; |
| } |
| if ((q = strchr(line, '-')) != NULL) { |
| /* allow Keysym1-Keysym2 notation */ |
| *q = ' '; |
| } |
| add_remap(p); |
| } |
| fclose(in); |
| } |
| |
| /* |
| * preliminary support for using the Xkb (XKEYBOARD) extension for handling |
| * user input. inelegant, slow, and incomplete currently... but initial |
| * tests show it is useful for some setups. |
| */ |
| typedef struct keychar { |
| KeyCode code; |
| int group; |
| int level; |
| } keychar_t; |
| |
| /* max number of key groups and shift levels we consider */ |
| #define GRP 4 |
| #define LVL 8 |
| static int lvl_max, grp_max, kc_min, kc_max; |
| static KeySym xkbkeysyms[0x100][GRP][LVL]; |
| static unsigned int xkbstate[0x100][GRP][LVL]; |
| static unsigned int xkbignore[0x100][GRP][LVL]; |
| static unsigned int xkbmodifiers[0x100][GRP][LVL]; |
| static int multi_key[0x100], mode_switch[0x100], skipkeycode[0x100]; |
| static int shift_keys[0x100]; |
| |
| /* |
| * for trying to order the keycodes to avoid problems, note the |
| * *first* keycode bound to it. kc_vec will be a permutation |
| * of 1...256 to get them in the preferred order. |
| */ |
| static int kc_vec[0x100]; |
| static int kc1_shift, kc1_control, kc1_caplock, kc1_alt; |
| static int kc1_meta, kc1_numlock, kc1_super, kc1_hyper; |
| static int kc1_mode_switch, kc1_iso_level3_shift, kc1_multi_key; |
| |
| int sloppy_key_check(int key, rfbBool down, rfbKeySym keysym, int *new_kc) { |
| if (!sloppy_keys) { |
| return 0; |
| } |
| |
| RAWFB_RET(0) |
| #if NO_X11 |
| if (!key || !down || !keysym || !new_kc) {} |
| return 0; |
| #else |
| |
| if (!down && !keycode_state[key] && !IsModifierKey(keysym)) { |
| int i, cnt = 0, downkey = -1; |
| int nmods_down = track_mod_state(NoSymbol, FALSE, FALSE); |
| int mods_down[256]; |
| |
| if (nmods_down) { |
| /* tracking to skip down modifier keycodes. */ |
| for(i=0; i<256; i++) { |
| mods_down[i] = 0; |
| } |
| i = 0; |
| while (simple_mods[i] != NoSymbol) { |
| KeySym ksym = simple_mods[i]; |
| KeyCode k = XKeysymToKeycode(dpy, ksym); |
| if (k != NoSymbol && keycode_state[(int) k]) { |
| mods_down[(int) k] = 1; |
| } |
| |
| i++; |
| } |
| } |
| /* |
| * the keycode is already up... look for a single one |
| * (non modifier) that is down |
| */ |
| for (i=0; i<256; i++) { |
| if (keycode_state[i]) { |
| if (nmods_down && mods_down[i]) { |
| continue; |
| } |
| cnt++; |
| downkey = i; |
| } |
| } |
| if (cnt == 1) { |
| if (debug_keyboard) { |
| fprintf(stderr, " sloppy_keys: %d/0x%x " |
| "-> %d/0x%x (nmods: %d)\n", (int) key, |
| (int) key, downkey, downkey, nmods_down); |
| } |
| *new_kc = downkey; |
| return 1; |
| } |
| } |
| return 0; |
| #endif /* NO_X11 */ |
| } |
| |
| #if !LIBVNCSERVER_HAVE_XKEYBOARD || SKIP_XKB |
| |
| /* empty functions for no xkb */ |
| static void initialize_xkb_modtweak(void) {} |
| static void xkb_tweak_keyboard(rfbBool down, rfbKeySym keysym, |
| rfbClientPtr client) { |
| if (!client || !down || !keysym) {} /* unused vars warning: */ |
| } |
| void switch_to_xkb_if_better(void) {} |
| |
| #else |
| |
| void switch_to_xkb_if_better(void) { |
| KeySym keysym, *keymap; |
| int miss_noxkb[256], miss_xkb[256], missing_noxkb = 0, missing_xkb = 0; |
| int i, j, k, n, minkey, maxkey, syms_per_keycode; |
| int syms_gt_4 = 0; |
| int kc, grp, lvl; |
| |
| /* non-alphanumeric on us keyboard */ |
| KeySym must_have[] = { |
| XK_exclam, |
| XK_at, |
| XK_numbersign, |
| XK_dollar, |
| XK_percent, |
| /* XK_asciicircum, */ |
| XK_ampersand, |
| XK_asterisk, |
| XK_parenleft, |
| XK_parenright, |
| XK_underscore, |
| XK_plus, |
| XK_minus, |
| XK_equal, |
| XK_bracketleft, |
| XK_bracketright, |
| XK_braceleft, |
| XK_braceright, |
| XK_bar, |
| XK_backslash, |
| XK_semicolon, |
| /* XK_apostrophe, */ |
| XK_colon, |
| XK_quotedbl, |
| XK_comma, |
| XK_period, |
| XK_less, |
| XK_greater, |
| XK_slash, |
| XK_question, |
| /* XK_asciitilde, */ |
| /* XK_grave, */ |
| NoSymbol |
| }; |
| |
| if (! use_modifier_tweak || got_noxkb) { |
| return; |
| } |
| if (use_xkb_modtweak) { |
| /* already using it */ |
| return; |
| } |
| RAWFB_RET_VOID |
| |
| XDisplayKeycodes(dpy, &minkey, &maxkey); |
| |
| keymap = XGetKeyboardMapping(dpy, minkey, (maxkey - minkey + 1), |
| &syms_per_keycode); |
| |
| /* handle alphabetic char with only one keysym (no upper + lower) */ |
| for (i = minkey; i <= maxkey; i++) { |
| KeySym lower, upper; |
| /* 2nd one */ |
| keysym = keymap[(i - minkey) * syms_per_keycode + 1]; |
| if (keysym != NoSymbol) { |
| continue; |
| } |
| /* 1st one */ |
| keysym = keymap[(i - minkey) * syms_per_keycode + 0]; |
| if (keysym == NoSymbol) { |
| continue; |
| } |
| XConvertCase(keysym, &lower, &upper); |
| if (lower != upper) { |
| keymap[(i - minkey) * syms_per_keycode + 0] = lower; |
| keymap[(i - minkey) * syms_per_keycode + 1] = upper; |
| } |
| } |
| |
| k = 0; |
| while (must_have[k] != NoSymbol) { |
| int gotit = 0; |
| KeySym must = must_have[k]; |
| for (i = minkey; i <= maxkey; i++) { |
| for (j = 0; j < syms_per_keycode; j++) { |
| keysym = keymap[(i-minkey) * syms_per_keycode + j]; |
| if (j >= 4) { |
| if (k == 0 && keysym != NoSymbol) { |
| /* for k=0 count the high keysyms */ |
| syms_gt_4++; |
| if (debug_keyboard > 1) { |
| char *str = XKeysymToString(keysym); |
| fprintf(stderr, "- high keysym mapping" |
| ": at %3d j=%d " |
| "'%s'\n", i, j, str ? str : "null"); |
| } |
| } |
| continue; |
| } |
| if (keysym == must) { |
| if (debug_keyboard > 1) { |
| char *str = XKeysymToString(must); |
| fprintf(stderr, "- at %3d j=%d found " |
| "'%s'\n", i, j, str ? str : "null"); |
| } |
| /* n.b. do not break, see syms_gt_4 above. */ |
| gotit = 1; |
| } |
| } |
| } |
| if (! gotit) { |
| if (debug_keyboard > 1) { |
| char *str = XKeysymToString(must); |
| KeyCode kc = XKeysymToKeycode(dpy, must); |
| fprintf(stderr, "- did not find 0x%lx '%s'\t" |
| "Ks2Kc: %d\n", must, str ? str:"null", kc); |
| if (kc != None) { |
| int j2; |
| for(j2=0; j2<syms_per_keycode; j2++) { |
| keysym = keymap[(kc-minkey) * |
| syms_per_keycode + j2]; |
| fprintf(stderr, " %d=0x%lx", |
| j2, keysym); |
| } |
| fprintf(stderr, "\n"); |
| } |
| } |
| missing_noxkb++; |
| miss_noxkb[k] = 1; |
| } else { |
| miss_noxkb[k] = 0; |
| } |
| k++; |
| } |
| n = k; |
| |
| XFree_wr(keymap); |
| if (missing_noxkb == 0 && syms_per_keycode > 4 && syms_gt_4 >= 0) { |
| /* we used to have syms_gt_4 >= 8, now always on. */ |
| if (! raw_fb_str) { |
| rfbLog("\n"); |
| rfbLog("XKEYBOARD: number of keysyms per keycode %d is greater\n", syms_per_keycode); |
| rfbLog(" than 4 and %d keysyms are mapped above 4.\n", syms_gt_4); |
| rfbLog(" Automatically switching to -xkb mode.\n"); |
| rfbLog(" If this makes the key mapping worse you can\n"); |
| rfbLog(" disable it with the \"-noxkb\" option.\n"); |
| rfbLog(" Also, remember \"-remap DEAD\" for accenting characters.\n"); |
| rfbLog("\n"); |
| } |
| |
| use_xkb_modtweak = 1; |
| return; |
| |
| } else if (missing_noxkb == 0) { |
| if (! raw_fb_str) { |
| rfbLog("\n"); |
| rfbLog("XKEYBOARD: all %d \"must have\" keysyms accounted for.\n", n); |
| rfbLog(" Not automatically switching to -xkb mode.\n"); |
| rfbLog(" If some keys still cannot be typed, try using -xkb.\n"); |
| rfbLog(" Also, remember \"-remap DEAD\" for accenting characters.\n"); |
| rfbLog("\n"); |
| } |
| return; |
| } |
| |
| for (k=0; k<n; k++) { |
| miss_xkb[k] = 1; |
| } |
| |
| for (kc = 0; kc < 0x100; kc++) { |
| for (grp = 0; grp < GRP; grp++) { |
| for (lvl = 0; lvl < LVL; lvl++) { |
| /* look up the Keysym, if any */ |
| keysym = XkbKeycodeToKeysym(dpy, kc, grp, lvl); |
| if (keysym == NoSymbol) { |
| continue; |
| } |
| for (k=0; k<n; k++) { |
| if (keysym == must_have[k]) { |
| miss_xkb[k] = 0; |
| } |
| } |
| } |
| } |
| } |
| |
| for (k=0; k<n; k++) { |
| if (miss_xkb[k]) { |
| missing_xkb++; |
| } |
| } |
| |
| rfbLog("\n"); |
| if (missing_xkb < missing_noxkb) { |
| rfbLog("XKEYBOARD:\n"); |
| rfbLog("Switching to -xkb mode to recover these keysyms:\n"); |
| } else { |
| rfbLog("XKEYBOARD: \"must have\" keysyms better accounted" |
| " for\n"); |
| rfbLog("under -noxkb mode: not switching to -xkb mode:\n"); |
| } |
| |
| rfbLog(" xkb noxkb Keysym (\"X\" means present)\n"); |
| rfbLog(" --- ----- -----------------------------\n"); |
| for (k=0; k<n; k++) { |
| char *xx, *xn, *name; |
| |
| keysym = must_have[k]; |
| if (keysym == NoSymbol) { |
| continue; |
| } |
| if (!miss_xkb[k] && !miss_noxkb[k]) { |
| continue; |
| } |
| if (miss_xkb[k]) { |
| xx = " "; |
| } else { |
| xx = " X "; |
| } |
| if (miss_noxkb[k]) { |
| xn = " "; |
| } else { |
| xn = " X "; |
| } |
| name = XKeysymToString(keysym); |
| rfbLog(" %s %s 0x%lx %s\n", xx, xn, keysym, |
| name ? name : "null"); |
| } |
| rfbLog("\n"); |
| |
| if (missing_xkb < missing_noxkb) { |
| rfbLog(" If this makes the key mapping worse you can\n"); |
| rfbLog(" disable it with the \"-noxkb\" option.\n"); |
| rfbLog("\n"); |
| |
| use_xkb_modtweak = 1; |
| |
| } else { |
| rfbLog(" If some keys still cannot be typed, try using" |
| " -xkb.\n"); |
| rfbLog(" Also, remember \"-remap DEAD\" for accenting" |
| " characters.\n"); |
| } |
| rfbLog("\n"); |
| } |
| |
| /* sets up all the keymapping info via Xkb API */ |
| |
| static void initialize_xkb_modtweak(void) { |
| KeySym ks; |
| int kc, grp, lvl, k; |
| unsigned int state; |
| |
| /* |
| * Here is a guide: |
| |
| Workarounds arrays: |
| |
| multi_key[] indicates which keycodes have Multi_key (Compose) |
| bound to them. |
| mode_switch[] indicates which keycodes have Mode_switch (AltGr) |
| bound to them. |
| shift_keys[] indicates which keycodes have Shift bound to them. |
| skipkeycode[] indicates which keycodes are to be skipped |
| for any lookups from -skip_keycodes option. |
| |
| Groups and Levels, here is an example: |
| |
| ^ -------- |
| | L2 | A AE | |
| shift | | |
| level L1 | a ae | |
| -------- |
| G1 G2 |
| |
| group -> |
| |
| Traditionally this it all a key could do. L1 vs. L2 selected via Shift |
| and G1 vs. G2 selected via Mode_switch. Up to 4 Keysyms could be bound |
| to a key. See initialize_modtweak() for an example of using that type |
| of keymap from XGetKeyboardMapping(). |
| |
| Xkb gives us up to 4 groups and 63 shift levels per key, with the |
| situation being potentially different for each key. This is complicated, |
| and I don't claim to understand it all, but in the following we just think |
| of ranging over the group and level indices as covering all of the cases. |
| This gives us an accurate view of the keymap. The main tricky part |
| is mapping between group+level and modifier state. |
| |
| On current linux/XFree86 setups (Xkb is enabled by default) the |
| information from XGetKeyboardMapping() (evidently the compat map) |
| is incomplete and inaccurate, so we are really forced to use the |
| Xkb API. |
| |
| xkbkeysyms[] For a (keycode,group,level) holds the KeySym (0 for none) |
| xkbstate[] For a (keycode,group,level) holds the corresponding |
| modifier state needed to get that KeySym |
| xkbignore[] For a (keycode,group,level) which modifiers can be |
| ignored (the 0 bits can be ignored). |
| xkbmodifiers[] For the KeySym bound to this (keycode,group,level) store |
| the modifier mask. |
| * |
| */ |
| |
| RAWFB_RET_VOID |
| |
| /* initialize all the arrays: */ |
| for (kc = 0; kc < 0x100; kc++) { |
| multi_key[kc] = 0; |
| mode_switch[kc] = 0; |
| skipkeycode[kc] = 0; |
| shift_keys[kc] = 0; |
| |
| for (grp = 0; grp < GRP; grp++) { |
| for (lvl = 0; lvl < LVL; lvl++) { |
| xkbkeysyms[kc][grp][lvl] = NoSymbol; |
| xkbmodifiers[kc][grp][lvl] = -1; |
| xkbstate[kc][grp][lvl] = -1; |
| } |
| } |
| } |
| |
| /* |
| * the array is 256*LVL*GRP, but we can make the searched region |
| * smaller by computing the actual ranges. |
| */ |
| lvl_max = 0; |
| grp_max = 0; |
| kc_max = 0; |
| kc_min = 0x100; |
| |
| /* first keycode for a modifier type (multi_key too) */ |
| kc1_shift = -1; |
| kc1_control = -1; |
| kc1_caplock = -1; |
| kc1_alt = -1; |
| kc1_meta = -1; |
| kc1_numlock = -1; |
| kc1_super = -1; |
| kc1_hyper = -1; |
| kc1_mode_switch = -1; |
| kc1_iso_level3_shift = -1; |
| kc1_multi_key = -1; |
| |
| /* |
| * loop over all possible (keycode, group, level) triples |
| * and record what we find for it: |
| */ |
| if (debug_keyboard) { |
| rfbLog("initialize_xkb_modtweak: XKB keycode -> keysyms " |
| "mapping info:\n"); |
| } |
| for (kc = 0; kc < 0x100; kc++) { |
| for (grp = 0; grp < GRP; grp++) { |
| for (lvl = 0; lvl < LVL; lvl++) { |
| unsigned int ms, mods; |
| int state_save = -1, mods_save = -1; |
| KeySym ks2; |
| |
| /* look up the Keysym, if any */ |
| ks = XkbKeycodeToKeysym(dpy, kc, grp, lvl); |
| xkbkeysyms[kc][grp][lvl] = ks; |
| |
| /* if no Keysym, on to next */ |
| if (ks == NoSymbol) { |
| continue; |
| } |
| /* |
| * for various workarounds, note where these special |
| * keys are bound to. |
| */ |
| if (ks == XK_Multi_key) { |
| multi_key[kc] = lvl+1; |
| } |
| if (ks == XK_Mode_switch) { |
| mode_switch[kc] = lvl+1; |
| } |
| if (ks == XK_Shift_L || ks == XK_Shift_R) { |
| shift_keys[kc] = lvl+1; |
| } |
| |
| if (ks == XK_Shift_L || ks == XK_Shift_R) { |
| if (kc1_shift == -1) { |
| kc1_shift = kc; |
| } |
| } |
| if (ks == XK_Control_L || ks == XK_Control_R) { |
| if (kc1_control == -1) { |
| kc1_control = kc; |
| } |
| } |
| if (ks == XK_Caps_Lock || ks == XK_Caps_Lock) { |
| if (kc1_caplock == -1) { |
| kc1_caplock = kc; |
| } |
| } |
| if (ks == XK_Alt_L || ks == XK_Alt_R) { |
| if (kc1_alt == -1) { |
| kc1_alt = kc; |
| } |
| } |
| if (ks == XK_Meta_L || ks == XK_Meta_R) { |
| if (kc1_meta == -1) { |
| kc1_meta = kc; |
| } |
| } |
| if (ks == XK_Num_Lock) { |
| if (kc1_numlock == -1) { |
| kc1_numlock = kc; |
| } |
| } |
| if (ks == XK_Super_L || ks == XK_Super_R) { |
| if (kc1_super == -1) { |
| kc1_super = kc; |
| } |
| } |
| if (ks == XK_Hyper_L || ks == XK_Hyper_R) { |
| if (kc1_hyper == -1) { |
| kc1_hyper = kc; |
| } |
| } |
| if (ks == XK_Mode_switch) { |
| if (kc1_mode_switch == -1) { |
| kc1_mode_switch = kc; |
| } |
| } |
| if (ks == XK_ISO_Level3_Shift) { |
| if (kc1_iso_level3_shift == -1) { |
| kc1_iso_level3_shift = kc; |
| } |
| } |
| if (ks == XK_Multi_key) { /* not a modifier.. */ |
| if (kc1_multi_key == -1) { |
| kc1_multi_key = kc; |
| } |
| } |
| |
| /* |
| * record maximum extent for group/level indices |
| * and keycode range: |
| */ |
| if (grp > grp_max) { |
| grp_max = grp; |
| } |
| if (lvl > lvl_max) { |
| lvl_max = lvl; |
| } |
| if (kc > kc_max) { |
| kc_max = kc; |
| } |
| if (kc < kc_min) { |
| kc_min = kc; |
| } |
| |
| /* |
| * lookup on *keysym* (i.e. not kc, grp, lvl) |
| * and get the modifier mask. this is 0 for |
| * most keysyms, only non zero for modifiers. |
| */ |
| ms = XkbKeysymToModifiers(dpy, ks); |
| xkbmodifiers[kc][grp][lvl] = ms; |
| |
| /* |
| * Amusing heuristic (may have bugs). There are |
| * 8 modifier bits, so 256 possible modifier |
| * states. We loop over all of them for this |
| * keycode (simulating Key "events") and ask |
| * XkbLookupKeySym to tell us the Keysym. Once it |
| * matches the Keysym we have for this (keycode, |
| * group, level), gotten via XkbKeycodeToKeysym() |
| * above, we then (hopefully...) know that state |
| * of modifiers needed to generate this keysym. |
| * |
| * Yes... keep your fingers crossed. |
| * |
| * Note that many of the 256 states give the |
| * Keysym, we just need one, and we take the |
| * first one found. |
| */ |
| state = 0; |
| while(state < 256) { |
| if (XkbLookupKeySym(dpy, kc, state, &mods, |
| &ks2)) { |
| |
| /* save these for workaround below */ |
| if (state_save == -1) { |
| state_save = state; |
| mods_save = mods; |
| } |
| if (ks2 == ks) { |
| /* |
| * zero the irrelevant bits |
| * by anding with mods. |
| */ |
| xkbstate[kc][grp][lvl] |
| = state & mods; |
| /* |
| * also remember the irrelevant |
| * bits since it is handy. |
| */ |
| xkbignore[kc][grp][lvl] = mods; |
| |
| break; |
| } |
| } |
| state++; |
| } |
| if (xkbstate[kc][grp][lvl] == (unsigned int) -1 |
| && grp == 1) { |
| /* |
| * Hack on Solaris 9 for Mode_switch |
| * for Group2 characters. We force the |
| * Mode_switch modifier bit on. |
| * XXX Need to figure out better what is |
| * happening here. Is compat on somehow?? |
| */ |
| unsigned int ms2; |
| ms2 = XkbKeysymToModifiers(dpy, XK_Mode_switch); |
| |
| xkbstate[kc][grp][lvl] |
| = (state_save & mods_save) | ms2; |
| |
| xkbignore[kc][grp][lvl] = mods_save | ms2; |
| } |
| |
| if (debug_keyboard) { |
| char *str; |
| fprintf(stderr, " %03d G%d L%d mod=%s ", |
| kc, grp+1, lvl+1, bitprint(ms, 8)); |
| fprintf(stderr, "state=%s ", |
| bitprint(xkbstate[kc][grp][lvl], 8)); |
| fprintf(stderr, "ignore=%s ", |
| bitprint(xkbignore[kc][grp][lvl], 8)); |
| str = XKeysymToString(ks); |
| fprintf(stderr, " ks=0x%08lx \"%s\"\n", |
| ks, str ? str : "null"); |
| } |
| } |
| } |
| } |
| |
| /* |
| * kc_vec will be used in some places to find modifiers, etc |
| * we apply some permutations to it as workarounds. |
| */ |
| for (kc = 0; kc < 0x100; kc++) { |
| kc_vec[kc] = kc; |
| } |
| |
| if (kc1_mode_switch != -1 && kc1_iso_level3_shift != -1) { |
| if (kc1_mode_switch < kc1_iso_level3_shift) { |
| /* we prefer XK_ISO_Level3_Shift: */ |
| kc_vec[kc1_mode_switch] = kc1_iso_level3_shift; |
| kc_vec[kc1_iso_level3_shift] = kc1_mode_switch; |
| } |
| } |
| /* any more? need to watch for undoing the above. */ |
| |
| /* |
| * process the user supplied -skip_keycodes string. |
| * This is presumably a list if "ghost" keycodes, the X server |
| * thinks they exist, but they do not. ghosts can lead to |
| * ambiguities in the reverse map: Keysym -> KeyCode + Modstate, |
| * so if we can ignore them so much the better. Presumably the |
| * user can never generate them from the physical keyboard. |
| * There may be other reasons to deaden some keys. |
| */ |
| if (skip_keycodes != NULL) { |
| char *p, *str = strdup(skip_keycodes); |
| p = strtok(str, ", \t\n\r"); |
| while (p) { |
| k = 1; |
| if (sscanf(p, "%d", &k) != 1 || k < 0 || k >= 0x100) { |
| rfbLogEnable(1); |
| rfbLog("invalid skip_keycodes: %s %s\n", |
| skip_keycodes, p); |
| clean_up_exit(1); |
| } |
| skipkeycode[k] = 1; |
| p = strtok(NULL, ", \t\n\r"); |
| } |
| free(str); |
| } |
| if (debug_keyboard) { |
| fprintf(stderr, "grp_max=%d lvl_max=%d\n", grp_max, lvl_max); |
| } |
| } |
| |
| static short **score_hint = NULL; |
| /* |
| * Called on user keyboard input. Try to solve the reverse mapping |
| * problem: KeySym (from VNC client) => KeyCode(s) to press to generate |
| * it. The one-to-many KeySym => KeyCode mapping makes it difficult, as |
| * does working out what changes to the modifier keypresses are needed. |
| */ |
| static void xkb_tweak_keyboard(rfbBool down, rfbKeySym keysym, |
| rfbClientPtr client) { |
| |
| int kc, grp, lvl, i, kci; |
| int kc_f[0x100], grp_f[0x100], lvl_f[0x100], state_f[0x100], found; |
| int ignore_f[0x100]; |
| unsigned int state = 0; |
| |
| |
| /* these are used for finding modifiers, etc */ |
| XkbStateRec kbstate; |
| int got_kbstate = 0; |
| int Kc_f, Grp_f = 0, Lvl_f = 0; |
| # define KLAST 10 |
| static int Kc_last_down[KLAST]; |
| static KeySym Ks_last_down[KLAST]; |
| static int klast = 0, khints = 1, anydown = 1; |
| static int cnt = 0; |
| |
| if (!client || !down || !keysym) {} /* unused vars warning: */ |
| |
| RAWFB_RET_VOID |
| |
| X_LOCK; |
| |
| if (klast == 0) { |
| int i, j; |
| for (i=0; i<KLAST; i++) { |
| Kc_last_down[i] = -1; |
| Ks_last_down[i] = NoSymbol; |
| } |
| if (getenv("NOKEYHINTS")) { |
| khints = 0; |
| } |
| if (getenv("NOANYDOWN")) { |
| anydown = 0; |
| } |
| if (getenv("KEYSDOWN")) { |
| klast = atoi(getenv("KEYSDOWN")); |
| if (klast < 1) klast = 1; |
| if (klast > KLAST) klast = KLAST; |
| } else { |
| klast = 3; |
| } |
| if (khints && score_hint == NULL) { |
| score_hint = (short **) malloc(0x100 * sizeof(short *)); |
| for (i=0; i<0x100; i++) { |
| score_hint[i] = (short *) malloc(0x100 * sizeof(short)); |
| } |
| |
| for (i=0; i<0x100; i++) { |
| for (j=0; j<0x100; j++) { |
| score_hint[i][j] = -1; |
| } |
| } |
| } |
| } |
| cnt++; |
| if (cnt % 100 && khints && score_hint != NULL) { |
| int i, j; |
| for (i=0; i<0x100; i++) { |
| for (j=0; j<0x100; j++) { |
| score_hint[i][j] = -1; |
| } |
| } |
| } |
| |
| if (debug_keyboard) { |
| char *str = XKeysymToString(keysym); |
| |
| if (debug_keyboard > 1) { |
| rfbLog("----------start-xkb_tweak_keyboard (%s) " |
| "--------\n", down ? "DOWN" : "UP"); |
| } |
| |
| rfbLog("xkb_tweak_keyboard: %s keysym=0x%x \"%s\"\n", |
| down ? "down" : "up", (int) keysym, str ? str : "null"); |
| } |
| |
| /* |
| * set everything to not-yet-found. |
| * these "found" arrays (*_f) let us dynamically consider the |
| * one-to-many Keysym -> Keycode issue. we set the size at 256, |
| * but of course only very few will be found. |
| */ |
| for (i = 0; i < 0x100; i++) { |
| kc_f[i] = -1; |
| grp_f[i] = -1; |
| lvl_f[i] = -1; |
| state_f[i] = -1; |
| ignore_f[i] = -1; |
| } |
| found = 0; |
| |
| /* |
| * loop over all (keycode, group, level) triples looking for |
| * matching keysyms. Amazingly this isn't slow (but maybe if |
| * you type really fast...). Hash lookup into a linked list of |
| * (keycode,grp,lvl) triples would be the way to improve this |
| * in the future if needed. |
| */ |
| for (kc = kc_min; kc <= kc_max; kc++) { |
| for (grp = 0; grp < grp_max+1; grp++) { |
| for (lvl = 0; lvl < lvl_max+1; lvl++) { |
| if (keysym != xkbkeysyms[kc][grp][lvl]) { |
| continue; |
| } |
| /* got a keysym match */ |
| state = xkbstate[kc][grp][lvl]; |
| |
| if (debug_keyboard > 1) { |
| char *s1, *s2; |
| s1 = XKeysymToString(XKeycodeToKeysym(dpy, |
| kc, 0)); |
| if (! s1) s1 = "null"; |
| s2 = XKeysymToString(keysym); |
| if (! s2) s2 = "null"; |
| fprintf(stderr, " got match kc=%03d=0x%02x G%d" |
| " L%d ks=0x%x \"%s\" (basesym: \"%s\")\n", |
| kc, kc, grp+1, lvl+1, keysym, s2, s1); |
| fprintf(stderr, " need state: %s\n", |
| bitprint(state, 8)); |
| fprintf(stderr, " ignorable : %s\n", |
| bitprint(xkbignore[kc][grp][lvl], 8)); |
| } |
| |
| /* save it if state is OK and not told to skip */ |
| if (state == (unsigned int) -1) { |
| continue; |
| } |
| if (skipkeycode[kc] && debug_keyboard) { |
| fprintf(stderr, " xxx skipping keycode: %d " |
| "G%d/L%d\n", kc, grp+1, lvl+1); |
| } |
| if (skipkeycode[kc]) { |
| continue; |
| } |
| if (found > 0 && kc == kc_f[found-1]) { |
| /* ignore repeats for same keycode */ |
| continue; |
| } |
| kc_f[found] = kc; |
| grp_f[found] = grp; |
| lvl_f[found] = lvl; |
| state_f[found] = state; |
| ignore_f[found] = xkbignore[kc][grp][lvl]; |
| found++; |
| } |
| } |
| } |
| |
| #define PKBSTATE \ |
| fprintf(stderr, " --- current mod state:\n"); \ |
| fprintf(stderr, " mods : %s\n", bitprint(kbstate.mods, 8)); \ |
| fprintf(stderr, " base_mods : %s\n", bitprint(kbstate.base_mods, 8)); \ |
| fprintf(stderr, " latch_mods: %s\n", bitprint(kbstate.latched_mods, 8)); \ |
| fprintf(stderr, " lock_mods : %s\n", bitprint(kbstate.locked_mods, 8)); \ |
| fprintf(stderr, " compat : %s\n", bitprint(kbstate.compat_state, 8)); |
| |
| /* |
| * Now get the current state of the keyboard from the X server. |
| * This seems to be the safest way to go as opposed to our |
| * keeping track of the modifier state on our own. Again, |
| * this is fortunately not too slow. |
| */ |
| |
| if (debug_keyboard > 1) { |
| /* get state early for debug output */ |
| XkbGetState(dpy, XkbUseCoreKbd, &kbstate); |
| got_kbstate = 1; |
| PKBSTATE |
| } |
| |
| if (!found && add_keysyms && keysym && ! IsModifierKey(keysym)) { |
| int new_kc = add_keysym(keysym); |
| if (new_kc != 0) { |
| found = 1; |
| kc_f[0] = new_kc; |
| grp_f[0] = 0; |
| lvl_f[0] = 0; |
| state_f[0] = 0; |
| } |
| } |
| |
| if (!found && debug_keyboard) { |
| char *str = XKeysymToString(keysym); |
| fprintf(stderr, " *** NO key found for: 0x%x %s " |
| "*keystroke ignored*\n", keysym, str ? str : "null"); |
| } |
| if (!found) { |
| X_UNLOCK; |
| return; |
| } |
| |
| /* |
| * we try to optimize here if found > 1 |
| * e.g. minimize lvl or grp, or other things to give |
| * "safest" scenario to simulate the keystrokes. |
| */ |
| |
| if (found > 1) { |
| if (down) { |
| int l, score[0x100]; |
| int best = 0, best_score = -1; |
| /* need to break the tie... */ |
| if (! got_kbstate) { |
| XkbGetState(dpy, XkbUseCoreKbd, &kbstate); |
| got_kbstate = 1; |
| } |
| if (khints && keysym < 0x100) { |
| int ks = (int) keysym, j; |
| for (j=0; j< 0x100; j++) { |
| score_hint[ks][j] = -1; |
| } |
| } |
| for (l=0; l < found; l++) { |
| int myscore = 0, b = 0x1, i; |
| int curr, curr_state = kbstate.mods; |
| int need, need_state = state_f[l]; |
| int ignore_state = ignore_f[l]; |
| |
| /* see how many modifiers need to be changed */ |
| for (i=0; i<8; i++) { |
| curr = b & curr_state; |
| need = b & need_state; |
| if (! (b & ignore_state)) { |
| ; |
| } else if (curr == need) { |
| ; |
| } else { |
| myscore++; |
| } |
| b = b << 1; |
| } |
| myscore *= 100; |
| |
| /* throw in some minimization of lvl too: */ |
| myscore += 2*lvl_f[l] + grp_f[l]; |
| |
| /* |
| * XXX since we now internally track |
| * keycode_state[], we could throw that into |
| * the score as well. I.e. if it is already |
| * down, it is pointless to think we can |
| * press it down further! E.g. |
| * myscore += 1000 * keycode_state[kc_f[l]]; |
| * Also could watch multiple modifier |
| * problem, e.g. Shift+key -> Alt |
| * keycode = 125 on my keyboard. |
| */ |
| |
| score[l] = myscore; |
| if (debug_keyboard > 1) { |
| fprintf(stderr, " *** score for " |
| "keycode %03d: %4d\n", |
| kc_f[l], myscore); |
| } |
| if (khints && keysym < 0x100 && kc_f[l] < 0x100) { |
| score_hint[(int) keysym][kc_f[l]] = (short) score[l]; |
| } |
| } |
| for (l=0; l < found; l++) { |
| int myscore = score[l]; |
| if (best_score == -1 || myscore < best_score) { |
| best = l; |
| best_score = myscore; |
| } |
| } |
| Kc_f = kc_f[best]; |
| Grp_f = grp_f[best]; |
| Lvl_f = lvl_f[best]; |
| state = state_f[best]; |
| |
| } else { |
| /* up */ |
| int i, Kc_loc = -1; |
| Kc_f = -1; |
| |
| /* first try the scores we remembered when the key went down: */ |
| if (khints && keysym < 0x100) { |
| /* low keysyms, ascii, only */ |
| int ks = (int) keysym; |
| int ok = 1, lbest = 0, l; |
| short sbest = -1; |
| for (l=0; l < found; l++) { |
| if (kc_f[l] < 0x100) { |
| int key = (int) kc_f[l]; |
| if (! keycode_state[key]) { |
| continue; |
| } |
| if (score_hint[ks][key] < 0) { |
| ok = 0; |
| break; |
| } |
| if (sbest < 0 || score_hint[ks][key] < sbest) { |
| sbest = score_hint[ks][key]; |
| lbest = l; |
| } |
| } else { |
| ok = 0; |
| break; |
| } |
| } |
| if (ok && sbest != -1) { |
| Kc_f = kc_f[lbest]; |
| } |
| if (debug_keyboard && Kc_f != -1) { |
| fprintf(stderr, " UP: found via score_hint, s/l=%d/%d\n", |
| sbest, lbest); |
| } |
| } |
| |
| /* next look at our list of recently pressed down keys */ |
| if (Kc_f == -1) { |
| for (i=klast-1; i>=0; i--) { |
| /* |
| * some people type really fast and leave |
| * lots of keys down before releasing |
| * them. this gives problem on weird |
| * qwerty+dvorak keymappings where each |
| * alpha character is on TWO keys. |
| */ |
| if (keysym == Ks_last_down[i]) { |
| int l; |
| for (l=0; l < found; l++) { |
| if (Kc_last_down[i] == kc_f[l]) { |
| int key = (int) kc_f[l]; |
| if (keycode_state[key]) { |
| Kc_f = Kc_last_down[i]; |
| Kc_loc = i; |
| break; |
| } |
| } |
| } |
| } |
| if (Kc_f != -1) { |
| break; |
| } |
| } |
| if (debug_keyboard && Kc_f != -1) { |
| fprintf(stderr, " UP: found via klast, i=%d\n", Kc_loc); |
| } |
| } |
| |
| /* next just check for "best" one that is down */ |
| if (Kc_f == -1 && anydown) { |
| int l; |
| int best = -1, lbest = 0; |
| /* |
| * If it is already down, that is |
| * a great hint. Use it. |
| * |
| * note: keycode_state is internal and |
| * ignores someone pressing keys on the |
| * physical display (but is updated |
| * periodically to clean out stale info). |
| */ |
| for (l=0; l < found; l++) { |
| int key = (int) kc_f[l]; |
| int j, jmatch = -1; |
| |
| if (! keycode_state[key]) { |
| continue; |
| } |
| /* break ties based on lowest XKeycodeToKeysym index */ |
| for (j=0; j<8; j++) { |
| KeySym ks = XKeycodeToKeysym(dpy, kc_f[l], j); |
| if (ks != NoSymbol && ks == keysym) { |
| jmatch = j; |
| break; |
| } |
| } |
| if (jmatch == -1) { |
| continue; |
| } |
| if (best == -1 || jmatch < best) { |
| best = jmatch; |
| lbest = l; |
| } |
| } |
| if (best != -1) { |
| Kc_f = kc_f[lbest]; |
| } |
| if (debug_keyboard && Kc_f != -1) { |
| fprintf(stderr, " UP: found via downlist, l=%d\n", lbest); |
| } |
| } |
| |
| /* next, use the first one found that is down */ |
| if (Kc_f == -1) { |
| int l; |
| for (l=0; l < found; l++) { |
| int key = (int) kc_f[l]; |
| if (keycode_state[key]) { |
| Kc_f = kc_f[l]; |
| break; |
| } |
| } |
| if (debug_keyboard && Kc_f != -1) { |
| fprintf(stderr, " UP: set to first one down, kc_f[%d]!!\n", l); |
| } |
| } |
| |
| /* last, use the first one found */ |
| if (Kc_f == -1) { |
| /* hope for the best... XXX check mods */ |
| Kc_f = kc_f[0]; |
| if (debug_keyboard && Kc_f != -1) { |
| fprintf(stderr, " UP: set to first one at all, kc_f[0]!!\n"); |
| } |
| } |
| } |
| } else { |
| Kc_f = kc_f[0]; |
| Grp_f = grp_f[0]; |
| Lvl_f = lvl_f[0]; |
| state = state_f[0]; |
| } |
| |
| if (debug_keyboard && found > 1) { |
| int l; |
| char *str; |
| fprintf(stderr, " *** found more than one keycode: "); |
| for (l = 0; l < found; l++) { |
| fprintf(stderr, "%03d ", kc_f[l]); |
| } |
| for (l = 0; l < found; l++) { |
| str = XKeysymToString(XKeycodeToKeysym(dpy,kc_f[l],0)); |
| fprintf(stderr, " \"%s\"", str ? str : "null"); |
| } |
| fprintf(stderr, ", picked this one: %03d (last down: %03d)\n", |
| Kc_f, Kc_last_down[0]); |
| } |
| |
| if (sloppy_keys) { |
| int new_kc; |
| if (sloppy_key_check(Kc_f, down, keysym, &new_kc)) { |
| Kc_f = new_kc; |
| } |
| } |
| |
| if (down) { |
| /* |
| * need to set up the mods for tweaking and other workarounds |
| */ |
| int needmods[8], sentmods[8], Ilist[8], keystate[256]; |
| int involves_multi_key, shift_is_down; |
| int i, j, b, curr, need; |
| unsigned int ms; |
| KeySym ks; |
| Bool dn; |
| |
| /* remember these to aid the subsequent up case: */ |
| for (i=KLAST-1; i >= 1; i--) { |
| Ks_last_down[i] = Ks_last_down[i-1]; |
| Kc_last_down[i] = Kc_last_down[i-1]; |
| } |
| Ks_last_down[0] = keysym; |
| Kc_last_down[0] = Kc_f; |
| |
| if (! got_kbstate) { |
| /* get the current modifier state if we haven't yet */ |
| XkbGetState(dpy, XkbUseCoreKbd, &kbstate); |
| got_kbstate = 1; |
| } |
| |
| /* |
| * needmods[] whether or not that modifier bit needs |
| * something done to it. |
| * < 0 means no, |
| * 0 means needs to go up. |
| * 1 means needs to go down. |
| * |
| * -1, -2, -3 are used for debugging info to indicate |
| * why nothing needs to be done with the modifier, see below. |
| * |
| * sentmods[] is the corresponding keycode to use |
| * to achieve the needmods[] requirement for the bit. |
| */ |
| |
| for (i=0; i<8; i++) { |
| needmods[i] = -1; |
| sentmods[i] = 0; |
| } |
| |
| /* |
| * Loop over the 8 modifier bits and check if the current |
| * setting is what we need it to be or whether it should |
| * be changed (by us sending some keycode event) |
| * |
| * If nothing needs to be done to it record why: |
| * -1 the modifier bit is ignored. |
| * -2 the modifier bit is ignored, but is correct anyway. |
| * -3 the modifier bit is correct. |
| */ |
| |
| b = 0x1; |
| for (i=0; i<8; i++) { |
| curr = b & kbstate.mods; |
| need = b & state; |
| |
| if (! (b & xkbignore[Kc_f][Grp_f][Lvl_f])) { |
| /* irrelevant modifier bit */ |
| needmods[i] = -1; |
| if (curr == need) needmods[i] = -2; |
| } else if (curr == need) { |
| /* already correct */ |
| needmods[i] = -3; |
| } else if (! curr && need) { |
| /* need it down */ |
| needmods[i] = 1; |
| } else if (curr && ! need) { |
| /* need it up */ |
| needmods[i] = 0; |
| } |
| |
| b = b << 1; |
| } |
| |
| /* |
| * Again we dynamically probe the X server for information, |
| * this time for the state of all the keycodes. Useful |
| * info, and evidently is not too slow... |
| */ |
| get_keystate(keystate); |
| |
| /* |
| * We try to determine if Shift is down (since that can |
| * screw up ISO_Level3_Shift manipulations). |
| */ |
| shift_is_down = 0; |
| |
| for (kc = kc_min; kc <= kc_max; kc++) { |
| if (skipkeycode[kc] && debug_keyboard) { |
| fprintf(stderr, " xxx skipping keycode: " |
| "%d\n", kc); |
| } |
| if (skipkeycode[kc]) { |
| continue; |
| } |
| if (shift_keys[kc] && keystate[kc]) { |
| shift_is_down = kc; |
| break; |
| } |
| } |
| |
| /* |
| * Now loop over the modifier bits and try to deduce the |
| * keycode presses/release require to match the desired |
| * state. |
| */ |
| for (i=0; i<8; i++) { |
| if (needmods[i] < 0 && debug_keyboard > 1) { |
| int k = -needmods[i] - 1; |
| char *words[] = {"ignorable", |
| "bitset+ignorable", "bitset"}; |
| fprintf(stderr, " +++ needmods: mod=%d is " |
| "OK (%s)\n", i, words[k]); |
| } |
| if (needmods[i] < 0) { |
| continue; |
| } |
| |
| b = 1 << i; |
| |
| if (debug_keyboard > 1) { |
| fprintf(stderr, " +++ needmods: mod=%d %s " |
| "need it to be: %d %s\n", i, bitprint(b, 8), |
| needmods[i], needmods[i] ? "down" : "up"); |
| } |
| |
| /* |
| * Again, an inefficient loop, this time just |
| * looking for modifiers... |
| * |
| * note the use of kc_vec to prefer XK_ISO_Level3_Shift |
| * over XK_Mode_switch. |
| */ |
| for (kci = kc_min; kci <= kc_max; kci++) { |
| for (grp = 0; grp < grp_max+1; grp++) { |
| for (lvl = 0; lvl < lvl_max+1; lvl++) { |
| int skip = 1, dbmsg = 0; |
| |
| kc = kc_vec[kci]; |
| |
| ms = xkbmodifiers[kc][grp][lvl]; |
| if (! ms || ms != (unsigned int) b) { |
| continue; |
| } |
| |
| if (skipkeycode[kc] && debug_keyboard) { |
| fprintf(stderr, " xxx skipping keycode:" |
| " %d G%d/L%d\n", kc, grp+1, lvl+1); |
| } |
| if (skipkeycode[kc]) { |
| continue; |
| } |
| |
| ks = xkbkeysyms[kc][grp][lvl]; |
| if (! ks) { |
| continue; |
| } |
| |
| if (ks == XK_Shift_L) { |
| skip = 0; |
| } else if (ks == XK_Shift_R) { |
| skip = 0; |
| } else if (ks == XK_Mode_switch) { |
| skip = 0; |
| } else if (ks == XK_ISO_Level3_Shift) { |
| skip = 0; |
| } |
| |
| if (watch_capslock && kbstate.locked_mods & LockMask) { |
| if (keysym >= 'A' && keysym <= 'Z') { |
| if (ks == XK_Shift_L || ks == XK_Shift_R) { |
| if (debug_keyboard > 1) { |
| fprintf(stderr, " A-Z caplock skip Shift\n"); |
| } |
| skip = 1; |
| } else if (ks == XK_Caps_Lock) { |
| if (debug_keyboard > 1) { |
| fprintf(stderr, " A-Z caplock noskip CapsLock\n"); |
| } |
| skip = 0; |
| } |
| } |
| } |
| /* |
| * Alt, Meta, Control, Super, |
| * Hyper, Num, Caps are skipped. |
| * |
| * XXX need more work on Locks, |
| * and non-standard modifiers. |
| * (e.g. XF86_Next_VMode using |
| * Ctrl+Alt) |
| */ |
| if (debug_keyboard > 1) { |
| char *str = XKeysymToString(ks); |
| int kt = keystate[kc]; |
| fprintf(stderr, " === for mod=%s " |
| "found kc=%03d/G%d/L%d it is %d " |
| "%s skip=%d (%s)\n", bitprint(b,8), |
| kc, grp+1, lvl+1, kt, kt ? |
| "down" : "up ", skip, str ? |
| str : "null"); |
| } |
| |
| if (! skip && needmods[i] != |
| keystate[kc] && sentmods[i] == 0) { |
| sentmods[i] = kc; |
| dbmsg = 1; |
| } |
| |
| if (debug_keyboard > 1 && dbmsg) { |
| int nm = needmods[i]; |
| fprintf(stderr, " >>> we choose " |
| "kc=%03d=0x%02x to change it to: " |
| "%d %s\n", kc, kc, nm, nm ? |
| "down" : "up"); |
| } |
| |
| } |
| } |
| } |
| } |
| for (i=0; i<8; i++) { |
| /* |
| * reverse order is useful for tweaking |
| * ISO_Level3_Shift before Shift, but assumes they |
| * are in that order (i.e. Shift is first bit). |
| */ |
| int reverse = 1; |
| if (reverse) { |
| Ilist[i] = 7 - i; |
| } else { |
| Ilist[i] = i; |
| } |
| } |
| |
| /* |
| * check to see if Multi_key is bound to one of the Mods |
| * we have to tweak |
| */ |
| involves_multi_key = 0; |
| for (j=0; j<8; j++) { |
| i = Ilist[j]; |
| if (sentmods[i] == 0) continue; |
| dn = (Bool) needmods[i]; |
| if (!dn) continue; |
| if (multi_key[sentmods[i]]) { |
| involves_multi_key = i+1; |
| } |
| } |
| |
| if (involves_multi_key && shift_is_down && needmods[0] < 0) { |
| /* |
| * Workaround for Multi_key and shift. |
| * Assumes Shift is bit 1 (needmods[0]) |
| */ |
| if (debug_keyboard) { |
| fprintf(stderr, " ^^^ trying to avoid " |
| "inadvertent Multi_key from Shift " |
| "(doing %03d up now)\n", shift_is_down); |
| } |
| XTestFakeKeyEvent_wr(dpy, shift_is_down, False, |
| CurrentTime); |
| } else { |
| involves_multi_key = 0; |
| } |
| |
| for (j=0; j<8; j++) { |
| /* do the Mod ups */ |
| i = Ilist[j]; |
| if (sentmods[i] == 0) continue; |
| dn = (Bool) needmods[i]; |
| if (dn) continue; |
| XTestFakeKeyEvent_wr(dpy, sentmods[i], dn, CurrentTime); |
| } |
| for (j=0; j<8; j++) { |
| /* next, do the Mod downs */ |
| i = Ilist[j]; |
| if (sentmods[i] == 0) continue; |
| dn = (Bool) needmods[i]; |
| if (!dn) continue; |
| XTestFakeKeyEvent_wr(dpy, sentmods[i], dn, CurrentTime); |
| } |
| |
| if (involves_multi_key) { |
| /* |
| * Reverse workaround for Multi_key and shift. |
| */ |
| if (debug_keyboard) { |
| fprintf(stderr, " vvv trying to avoid " |
| "inadvertent Multi_key from Shift " |
| "(doing %03d down now)\n", shift_is_down); |
| } |
| XTestFakeKeyEvent_wr(dpy, shift_is_down, True, |
| CurrentTime); |
| } |
| |
| /* |
| * With the above modifier work done, send the actual keycode: |
| */ |
| XTestFakeKeyEvent_wr(dpy, Kc_f, (Bool) down, CurrentTime); |
| |
| /* |
| * Now undo the modifier work: |
| */ |
| for (j=7; j>=0; j--) { |
| /* reverse Mod downs we did */ |
| i = Ilist[j]; |
| if (sentmods[i] == 0) continue; |
| dn = (Bool) needmods[i]; |
| if (!dn) continue; |
| XTestFakeKeyEvent_wr(dpy, sentmods[i], !dn, |
| CurrentTime); |
| } |
| for (j=7; j>=0; j--) { |
| /* finally reverse the Mod ups we did */ |
| i = Ilist[j]; |
| if (sentmods[i] == 0) continue; |
| dn = (Bool) needmods[i]; |
| if (dn) continue; |
| XTestFakeKeyEvent_wr(dpy, sentmods[i], !dn, |
| CurrentTime); |
| } |
| |
| } else { /* for up case, hopefully just need to pop it up: */ |
| |
| XTestFakeKeyEvent_wr(dpy, Kc_f, (Bool) down, CurrentTime); |
| } |
| X_UNLOCK; |
| } |
| #endif |
| |
| /* |
| * For tweaking modifiers wrt the Alt-Graph key, etc. |
| */ |
| #define LEFTSHIFT 1 |
| #define RIGHTSHIFT 2 |
| #define ALTGR 4 |
| static char mod_state = 0; |
| |
| static char modifiers[0x100]; |
| static KeyCode keycodes[0x100]; |
| static KeyCode left_shift_code, right_shift_code, altgr_code, iso_level3_code; |
| |
| /* workaround for X11R5, Latin 1 only */ |
| #ifndef XConvertCase |
| #define XConvertCase(sym, lower, upper) \ |
| *(lower) = sym; \ |
| *(upper) = sym; \ |
| if (sym >> 8 == 0) { \ |
| if ((sym >= XK_A) && (sym <= XK_Z)) \ |
| *(lower) += (XK_a - XK_A); \ |
| else if ((sym >= XK_a) && (sym <= XK_z)) \ |
| *(upper) -= (XK_a - XK_A); \ |
| else if ((sym >= XK_Agrave) && (sym <= XK_Odiaeresis)) \ |
| *(lower) += (XK_agrave - XK_Agrave); \ |
| else if ((sym >= XK_agrave) && (sym <= XK_odiaeresis)) \ |
| *(upper) -= (XK_agrave - XK_Agrave); \ |
| else if ((sym >= XK_Ooblique) && (sym <= XK_Thorn)) \ |
| *(lower) += (XK_oslash - XK_Ooblique); \ |
| else if ((sym >= XK_oslash) && (sym <= XK_thorn)) \ |
| *(upper) -= (XK_oslash - XK_Ooblique); \ |
| } |
| #endif |
| |
| char *short_kmbcf(char *str) { |
| int i, saw_k = 0, saw_m = 0, saw_b = 0, saw_c = 0, saw_f = 0, n = 10; |
| char *p, tmp[10]; |
| |
| for (i=0; i<n; i++) { |
| tmp[i] = '\0'; |
| } |
| |
| p = str; |
| i = 0; |
| while (*p) { |
| if ((*p == 'K' || *p == 'k') && !saw_k) { |
| tmp[i++] = 'K'; |
| saw_k = 1; |
| } else if ((*p == 'M' || *p == 'm') && !saw_m) { |
| tmp[i++] = 'M'; |
| saw_m = 1; |
| } else if ((*p == 'B' || *p == 'b') && !saw_b) { |
| tmp[i++] = 'B'; |
| saw_b = 1; |
| } else if ((*p == 'C' || *p == 'c') && !saw_c) { |
| tmp[i++] = 'C'; |
| saw_c = 1; |
| } else if ((*p == 'F' || *p == 'f') && !saw_f) { |
| tmp[i++] = 'F'; |
| saw_f = 1; |
| } |
| p++; |
| } |
| return(strdup(tmp)); |
| } |
| |
| void initialize_allowed_input(void) { |
| char *str; |
| |
| if (allowed_input_normal) { |
| free(allowed_input_normal); |
| allowed_input_normal = NULL; |
| } |
| if (allowed_input_view_only) { |
| free(allowed_input_view_only); |
| allowed_input_view_only = NULL; |
| } |
| |
| if (! allowed_input_str) { |
| allowed_input_normal = strdup("KMBCF"); |
| allowed_input_view_only = strdup(""); |
| } else { |
| char *p, *str = strdup(allowed_input_str); |
| p = strchr(str, ','); |
| if (p) { |
| allowed_input_view_only = strdup(p+1); |
| *p = '\0'; |
| allowed_input_normal = strdup(str); |
| } else { |
| allowed_input_normal = strdup(str); |
| allowed_input_view_only = strdup(""); |
| } |
| free(str); |
| } |
| |
| /* shorten them */ |
| str = short_kmbcf(allowed_input_normal); |
| free(allowed_input_normal); |
| allowed_input_normal = str; |
| |
| str = short_kmbcf(allowed_input_view_only); |
| free(allowed_input_view_only); |
| allowed_input_view_only = str; |
| |
| if (screen) { |
| rfbClientIteratorPtr iter; |
| rfbClientPtr cl; |
| |
| iter = rfbGetClientIterator(screen); |
| while( (cl = rfbClientIteratorNext(iter)) ) { |
| ClientData *cd = (ClientData *) cl->clientData; |
| |
| if (! cd) { |
| continue; |
| } |
| #if 0 |
| rfbLog("cd: %p\n", cd); |
| rfbLog("cd->input: %s\n", cd->input); |
| rfbLog("cd->login_viewonly: %d\n", cd->login_viewonly); |
| rfbLog("allowed_input_view_only: %s\n", allowed_input_view_only); |
| #endif |
| |
| if (cd->input[0] == '=') { |
| ; /* custom setting */ |
| } else if (cd->login_viewonly) { |
| if (*allowed_input_view_only != '\0') { |
| cl->viewOnly = FALSE; |
| cd->input[0] = '\0'; |
| strncpy(cd->input, |
| allowed_input_view_only, CILEN); |
| } else { |
| cl->viewOnly = TRUE; |
| } |
| } else { |
| if (allowed_input_normal) { |
| cd->input[0] = '\0'; |
| strncpy(cd->input, |
| allowed_input_normal, CILEN); |
| } |
| } |
| } |
| rfbReleaseClientIterator(iter); |
| } |
| } |
| |
| void initialize_modtweak(void) { |
| #if NO_X11 |
| RAWFB_RET_VOID |
| return; |
| #else |
| KeySym keysym, *keymap; |
| int i, j, minkey, maxkey, syms_per_keycode; |
| int use_lowest_index = 0; |
| |
| if (use_xkb_modtweak) { |
| initialize_xkb_modtweak(); |
| return; |
| } |
| memset(modifiers, -1, sizeof(modifiers)); |
| for (i=0; i<0x100; i++) { |
| keycodes[i] = NoSymbol; |
| } |
| |
| RAWFB_RET_VOID |
| |
| if (getenv("MODTWEAK_LOWEST")) { |
| use_lowest_index = 1; |
| } |
| |
| X_LOCK; |
| XDisplayKeycodes(dpy, &minkey, &maxkey); |
| |
| keymap = XGetKeyboardMapping(dpy, minkey, (maxkey - minkey + 1), |
| &syms_per_keycode); |
| |
| /* handle alphabetic char with only one keysym (no upper + lower) */ |
| for (i = minkey; i <= maxkey; i++) { |
| KeySym lower, upper; |
| /* 2nd one */ |
| keysym = keymap[(i - minkey) * syms_per_keycode + 1]; |
| if (keysym != NoSymbol) { |
| continue; |
| } |
| /* 1st one */ |
| keysym = keymap[(i - minkey) * syms_per_keycode + 0]; |
| if (keysym == NoSymbol) { |
| continue; |
| } |
| XConvertCase(keysym, &lower, &upper); |
| if (lower != upper) { |
| keymap[(i - minkey) * syms_per_keycode + 0] = lower; |
| keymap[(i - minkey) * syms_per_keycode + 1] = upper; |
| } |
| } |
| for (i = minkey; i <= maxkey; i++) { |
| if (debug_keyboard) { |
| if (i == minkey) { |
| rfbLog("initialize_modtweak: keycode -> " |
| "keysyms mapping info:\n"); |
| } |
| fprintf(stderr, " %03d ", i); |
| } |
| for (j = 0; j < syms_per_keycode; j++) { |
| if (debug_keyboard) { |
| char *sym; |
| #if 0 |
| sym =XKeysymToString(XKeycodeToKeysym(dpy,i,j)); |
| #else |
| keysym = keymap[(i-minkey)*syms_per_keycode+j]; |
| sym = XKeysymToString(keysym); |
| #endif |
| fprintf(stderr, "%-18s ", sym ? sym : "null"); |
| if (j == syms_per_keycode - 1) { |
| fprintf(stderr, "\n"); |
| } |
| } |
| if (j >= 4) { |
| /* |
| * Something wacky in the keymapping. |
| * Ignore these non Shift/AltGr chords |
| * for now... n.b. we try to automatically |
| * switch to -xkb for this case. |
| */ |
| continue; |
| } |
| keysym = keymap[ (i - minkey) * syms_per_keycode + j ]; |
| if ( keysym >= ' ' && keysym < 0x100 |
| && i == XKeysymToKeycode(dpy, keysym) ) { |
| if (use_lowest_index && keycodes[keysym] != NoSymbol) { |
| continue; |
| } |
| keycodes[keysym] = i; |
| modifiers[keysym] = j; |
| } |
| } |
| } |
| |
| left_shift_code = XKeysymToKeycode(dpy, XK_Shift_L); |
| right_shift_code = XKeysymToKeycode(dpy, XK_Shift_R); |
| altgr_code = XKeysymToKeycode(dpy, XK_Mode_switch); |
| iso_level3_code = NoSymbol; |
| #ifdef XK_ISO_Level3_Shift |
| iso_level3_code = XKeysymToKeycode(dpy, XK_ISO_Level3_Shift); |
| #endif |
| |
| XFree_wr ((void *) keymap); |
| |
| X_UNLOCK; |
| #endif /* NO_X11 */ |
| } |
| |
| /* |
| * does the actual tweak: |
| */ |
| static void tweak_mod(signed char mod, rfbBool down) { |
| rfbBool is_shift = mod_state & (LEFTSHIFT|RIGHTSHIFT); |
| Bool dn = (Bool) down; |
| KeyCode altgr = altgr_code; |
| |
| RAWFB_RET_VOID |
| |
| if (mod < 0) { |
| if (debug_keyboard) { |
| rfbLog("tweak_mod: Skip: down=%d index=%d\n", down, |
| (int) mod); |
| } |
| return; |
| } |
| if (debug_keyboard) { |
| rfbLog("tweak_mod: Start: down=%d index=%d mod_state=0x%x" |
| " is_shift=%d\n", down, (int) mod, (int) mod_state, |
| is_shift); |
| } |
| |
| if (use_iso_level3 && iso_level3_code) { |
| altgr = iso_level3_code; |
| } |
| |
| X_LOCK; |
| if (is_shift && mod != 1) { |
| if (mod_state & LEFTSHIFT) { |
| XTestFakeKeyEvent_wr(dpy, left_shift_code, !dn, CurrentTime); |
| } |
| if (mod_state & RIGHTSHIFT) { |
| XTestFakeKeyEvent_wr(dpy, right_shift_code, !dn, CurrentTime); |
| } |
| } |
| if ( ! is_shift && mod == 1 ) { |
| XTestFakeKeyEvent_wr(dpy, left_shift_code, dn, CurrentTime); |
| } |
| if ( altgr && (mod_state & ALTGR) && mod != 2 ) { |
| XTestFakeKeyEvent_wr(dpy, altgr, !dn, CurrentTime); |
| } |
| if ( altgr && ! (mod_state & ALTGR) && mod == 2 ) { |
| XTestFakeKeyEvent_wr(dpy, altgr, dn, CurrentTime); |
| } |
| X_UNLOCK; |
| |
| if (debug_keyboard) { |
| rfbLog("tweak_mod: Finish: down=%d index=%d mod_state=0x%x" |
| " is_shift=%d\n", down, (int) mod, (int) mod_state, |
| is_shift); |
| } |
| } |
| |
| /* |
| * tweak the modifier under -modtweak |
| */ |
| static void modifier_tweak_keyboard(rfbBool down, rfbKeySym keysym, |
| rfbClientPtr client) { |
| #if NO_X11 |
| RAWFB_RET_VOID |
| if (!down || !keysym || !client) {} |
| return; |
| #else |
| KeyCode k; |
| int tweak = 0; |
| |
| RAWFB_RET_VOID |
| |
| if (use_xkb_modtweak) { |
| xkb_tweak_keyboard(down, keysym, client); |
| return; |
| } |
| if (debug_keyboard) { |
| rfbLog("modifier_tweak_keyboard: %s keysym=0x%x\n", |
| down ? "down" : "up", (int) keysym); |
| } |
| |
| #define ADJUSTMOD(sym, state) \ |
| if (keysym == sym) { \ |
| if (down) { \ |
| mod_state |= state; \ |
| } else { \ |
| mod_state &= ~state; \ |
| } \ |
| } |
| |
| ADJUSTMOD(XK_Shift_L, LEFTSHIFT) |
| ADJUSTMOD(XK_Shift_R, RIGHTSHIFT) |
| ADJUSTMOD(XK_Mode_switch, ALTGR) |
| |
| if ( down && keysym >= ' ' && keysym < 0x100 ) { |
| unsigned int state = 0; |
| tweak = 1; |
| if (watch_capslock && keysym >= 'A' && keysym <= 'Z') { |
| X_LOCK; |
| state = mask_state(); |
| X_UNLOCK; |
| } |
| if (state & LockMask) { |
| /* capslock set for A-Z, so no tweak */ |
| X_LOCK; |
| k = XKeysymToKeycode(dpy, (KeySym) keysym); |
| X_UNLOCK; |
| tweak = 0; |
| } else { |
| tweak_mod(modifiers[keysym], True); |
| k = keycodes[keysym]; |
| } |
| } else { |
| X_LOCK; |
| k = XKeysymToKeycode(dpy, (KeySym) keysym); |
| X_UNLOCK; |
| } |
| if (k == NoSymbol && add_keysyms && ! IsModifierKey(keysym)) { |
| int new_kc = add_keysym(keysym); |
| if (new_kc) { |
| k = new_kc; |
| } |
| } |
| |
| if (sloppy_keys) { |
| int new_kc; |
| if (sloppy_key_check((int) k, down, keysym, &new_kc)) { |
| k = (KeyCode) new_kc; |
| } |
| } |
| |
| if (debug_keyboard) { |
| char *str = XKeysymToString(keysym); |
| rfbLog("modifier_tweak_keyboard: KeySym 0x%x \"%s\" -> " |
| "KeyCode 0x%x%s\n", (int) keysym, str ? str : "null", |
| (int) k, k ? "" : " *ignored*"); |
| } |
| if ( k != NoSymbol ) { |
| X_LOCK; |
| XTestFakeKeyEvent_wr(dpy, k, (Bool) down, CurrentTime); |
| X_UNLOCK; |
| } |
| |
| if ( tweak ) { |
| tweak_mod(modifiers[keysym], False); |
| } |
| #endif /* NO_X11 */ |
| } |
| |
| void initialize_keyboard_and_pointer(void) { |
| |
| #ifdef MACOSX |
| if (macosx_console) { |
| initialize_remap(remap_file); |
| initialize_pointer_map(pointer_remap); |
| } |
| #endif |
| |
| RAWFB_RET_VOID |
| |
| if (use_modifier_tweak) { |
| initialize_modtweak(); |
| } |
| |
| initialize_remap(remap_file); |
| initialize_pointer_map(pointer_remap); |
| |
| X_LOCK; |
| clear_modifiers(1); |
| if (clear_mods == 1) { |
| clear_modifiers(0); |
| } |
| if (clear_mods == 3) { |
| clear_locks(); |
| } |
| X_UNLOCK; |
| } |
| |
| void get_allowed_input(rfbClientPtr client, allowed_input_t *input) { |
| ClientData *cd; |
| char *str; |
| |
| input->keystroke = 0; |
| input->motion = 0; |
| input->button = 0; |
| input->clipboard = 0; |
| input->files = 0; |
| |
| if (! client) { |
| input->keystroke = 1; |
| input->motion = 1; |
| input->button = 1; |
| input->clipboard = 1; |
| input->files = 1; |
| return; |
| } |
| |
| cd = (ClientData *) client->clientData; |
| |
| if (! cd) { |
| return; |
| } |
| |
| if (cd->input[0] != '-') { |
| str = cd->input; |
| } else if (client->viewOnly) { |
| if (allowed_input_view_only) { |
| str = allowed_input_view_only; |
| } else { |
| str = ""; |
| } |
| } else { |
| if (allowed_input_normal) { |
| str = allowed_input_normal; |
| } else { |
| str = "KMBCF"; |
| } |
| } |
| if (0) fprintf(stderr, "GAI: %s - %s\n", str, cd->input); |
| |
| while (*str) { |
| if (*str == 'K') { |
| input->keystroke = 1; |
| } else if (*str == 'M') { |
| input->motion = 1; |
| } else if (*str == 'B') { |
| input->button = 1; |
| } else if (*str == 'C') { |
| input->clipboard = 1; |
| } else if (*str == 'F') { |
| input->files = 1; |
| } |
| str++; |
| } |
| } |
| |
| static void apply_remap(rfbKeySym *keysym, int *isbutton) { |
| if (keyremaps) { |
| keyremap_t *remap = keyremaps; |
| while (remap != NULL) { |
| if (remap->before == *keysym) { |
| *keysym = remap->after; |
| *isbutton = remap->isbutton; |
| if (debug_keyboard) { |
| char *str1, *str2; |
| X_LOCK; |
| str1 = XKeysymToString(remap->before); |
| str2 = XKeysymToString(remap->after); |
| rfbLog("keyboard(): remapping keysym: " |
| "0x%x \"%s\" -> 0x%x \"%s\"\n", |
| (int) remap->before, |
| str1 ? str1 : "null", |
| (int) remap->after, |
| remap->isbutton ? "button" : |
| str2 ? str2 : "null"); |
| X_UNLOCK; |
| } |
| break; |
| } |
| remap = remap->next; |
| } |
| } |
| } |
| |
| /* for -pipeinput mode */ |
| static void pipe_keyboard(rfbBool down, rfbKeySym keysym, rfbClientPtr client) { |
| int can_input = 0, uid = 0, isbutton = 0; |
| allowed_input_t input; |
| char *name; |
| ClientData *cd = (ClientData *) client->clientData; |
| |
| apply_remap(&keysym, &isbutton); |
| |
| if (isbutton) { |
| int mask, button = (int) keysym; |
| int x = cursor_x, y = cursor_y; |
| char *b, bstr[32]; |
| |
| if (!down) { |
| return; |
| } |
| if (debug_keyboard) { |
| rfbLog("keyboard(): remapping keystroke to button %d" |
| " click\n", button); |
| } |
| dtime0(&last_key_to_button_remap_time); |
| |
| /* |
| * This in principle can be a little dicey... i.e. even |
| * remap the button click to keystroke sequences! |
| * Usually just will simulate the button click. |
| */ |
| |
| /* loop over possible multiclicks: Button123 */ |
| sprintf(bstr, "%d", button); |
| b = bstr; |
| while (*b != '\0') { |
| char t[2]; |
| int butt; |
| t[0] = *b; |
| t[1] = '\0'; |
| if (sscanf(t, "%d", &butt) == 1) { |
| mask = 1<<(butt-1); |
| pointer_event(mask, x, y, client); |
| mask = 0; |
| pointer_event(mask, x, y, client); |
| } |
| b++; |
| } |
| return; |
| } |
| |
| if (pipeinput_int == PIPEINPUT_VID) { |
| v4l_key_command(down, keysym, client); |
| } else if (pipeinput_int == PIPEINPUT_CONSOLE) { |
| console_key_command(down, keysym, client); |
| } else if (pipeinput_int == PIPEINPUT_UINPUT) { |
| uinput_key_command(down, keysym, client); |
| } else if (pipeinput_int == PIPEINPUT_MACOSX) { |
| macosx_key_command(down, keysym, client); |
| } else if (pipeinput_int == PIPEINPUT_VNC) { |
| vnc_reflect_send_key((uint32_t) keysym, down); |
| } |
| if (pipeinput_fh == NULL) { |
| return; |
| } |
| |
| if (! view_only) { |
| get_allowed_input(client, &input); |
| if (input.keystroke) { |
| can_input = 1; /* XXX distinguish later */ |
| } |
| } |
| if (cd) { |
| uid = cd->uid; |
| } |
| if (! can_input) { |
| uid = -uid; |
| } |
| |
| X_LOCK; |
| name = XKeysymToString(keysym); |
| X_UNLOCK; |
| |
| fprintf(pipeinput_fh, "Keysym %d %d %u %s %s\n", uid, down, |
| keysym, name ? name : "null", down ? "KeyPress" : "KeyRelease"); |
| |
| fflush(pipeinput_fh); |
| check_pipeinput(); |
| } |
| |
| typedef struct keyevent { |
| rfbKeySym sym; |
| rfbBool down; |
| double time; |
| } keyevent_t; |
| |
| #define KEY_HIST 256 |
| static int key_history_idx = -1; |
| static keyevent_t key_history[KEY_HIST]; |
| |
| double typing_rate(double time_window, int *repeating) { |
| double dt = 1.0, now = dnow(); |
| KeySym key = NoSymbol; |
| int i, idx, cnt = 0, repeat_keys = 0; |
| |
| if (key_history_idx == -1) { |
| if (repeating) { |
| *repeating = 0; |
| } |
| return 0.0; |
| } |
| if (time_window > 0.0) { |
| dt = time_window; |
| } |
| for (i=0; i<KEY_HIST; i++) { |
| idx = key_history_idx - i; |
| if (idx < 0) { |
| idx += KEY_HIST; |
| } |
| if (! key_history[idx].down) { |
| continue; |
| } |
| if (now > key_history[idx].time + dt) { |
| break; |
| } |
| cnt++; |
| if (key == NoSymbol) { |
| key = key_history[idx].sym; |
| repeat_keys = 1; |
| } else if (key == key_history[idx].sym) { |
| repeat_keys++; |
| } |
| } |
| |
| if (repeating) { |
| if (repeat_keys >= 2) { |
| *repeating = repeat_keys; |
| } else { |
| *repeating = 0; |
| } |
| } |
| |
| /* |
| * n.b. keyrate could seem very high with libvncserver buffering them |
| * so avoid using small dt. |
| */ |
| return ((double) cnt)/dt; |
| } |
| |
| int skip_cr_when_scaling(char *mode) { |
| int got = 0; |
| |
| if (!scaling) { |
| return 0; |
| } |
| |
| if (scaling_copyrect != scaling_copyrect0) { |
| /* user override via -scale: */ |
| if (! scaling_copyrect) { |
| return 1; |
| } else { |
| return 0; |
| } |
| } |
| if (*mode == 's') { |
| got = got_scrollcopyrect; |
| } else if (*mode == 'w') { |
| got = got_wirecopyrect; |
| } |
| if (scaling_copyrect || got) { |
| int lat, rate; |
| int link = link_rate(&lat, &rate); |
| if (link == LR_DIALUP) { |
| return 1; |
| } else if (rate < 25) { |
| /* the fill-in of the repair may be too slow */ |
| return 1; |
| } else { |
| return 0; |
| } |
| } else { |
| return 1; |
| } |
| } |
| |
| /* |
| * key event handler. See the above functions for contortions for |
| * running under -modtweak. |
| */ |
| static rfbClientPtr last_keyboard_client = NULL; |
| |
| void keyboard(rfbBool down, rfbKeySym keysym, rfbClientPtr client) { |
| KeyCode k; |
| int idx, isbutton = 0; |
| allowed_input_t input; |
| time_t now = time(NULL); |
| double tnow; |
| static int skipped_last_down; |
| static rfbBool last_down; |
| static rfbKeySym last_keysym = NoSymbol; |
| static rfbKeySym max_keyrepeat_last_keysym = NoSymbol; |
| static double max_keyrepeat_last_time = 0.0; |
| static double max_keyrepeat_always = -1.0; |
| |
| if (threads_drop_input) { |
| return; |
| } |
| |
| dtime0(&tnow); |
| got_keyboard_calls++; |
| |
| if (debug_keyboard) { |
| char *str; |
| X_LOCK; |
| str = XKeysymToString((KeySym) keysym); |
| X_UNLOCK; |
| rfbLog("# keyboard(%s, 0x%x \"%s\") uip=%d %.4f\n", |
| down ? "down":"up", (int) keysym, str ? str : "null", |
| unixpw_in_progress, tnow - x11vnc_start); |
| } |
| |
| if (keysym <= 0) { |
| rfbLog("keyboard: skipping 0x0 keysym\n"); |
| return; |
| } |
| |
| if (unixpw_in_progress) { |
| if (unixpw_denied) { |
| rfbLog("keyboard: ignoring keystroke 0x%x in " |
| "unixpw_denied=1 state\n", (int) keysym); |
| return; |
| } |
| if (client != unixpw_client) { |
| rfbLog("keyboard: skipping other client in unixpw\n"); |
| return; |
| } |
| |
| unixpw_keystroke(down, keysym, 0); |
| |
| return; |
| } |
| |
| if (skip_duplicate_key_events) { |
| if (keysym == last_keysym && down == last_down) { |
| if (debug_keyboard) { |
| rfbLog("skipping dup key event: %d 0x%x\n", |
| down, keysym); |
| } |
| return; |
| } |
| } |
| |
| if (skip_lockkeys) { |
| /* we don't handle XK_ISO*_Lock or XK_Kana_Lock ... */ |
| if (keysym == XK_Scroll_Lock || keysym == XK_Num_Lock || |
| keysym == XK_Caps_Lock || keysym == XK_Shift_Lock) { |
| if (debug_keyboard) { |
| rfbLog("skipping lock key event: %d 0x%x\n", |
| down, keysym); |
| } |
| return; |
| } else if (keysym >= XK_KP_0 && keysym <= XK_KP_9) { |
| /* ugh this is probably what they meant... assume NumLock. */ |
| if (debug_keyboard) { |
| rfbLog("changed KP digit to regular digit: %d 0x%x\n", |
| down, keysym); |
| } |
| keysym = (keysym - XK_KP_0) + XK_0; |
| } else if (keysym == XK_KP_Decimal) { |
| if (debug_keyboard) { |
| rfbLog("changed XK_KP_Decimal to XK_period: %d 0x%x\n", |
| down, keysym); |
| } |
| keysym = XK_period; |
| } |
| } |
| |
| INPUT_LOCK; |
| |
| last_down = down; |
| last_keysym = keysym; |
| last_keyboard_time = tnow; |
| |
| last_rfb_down = down; |
| last_rfb_keysym = keysym; |
| last_rfb_keytime = tnow; |
| last_rfb_key_accepted = FALSE; |
| |
| if (key_history_idx == -1) { |
| for (idx=0; idx<KEY_HIST; idx++) { |
| key_history[idx].sym = NoSymbol; |
| key_history[idx].down = FALSE; |
| key_history[idx].time = 0.0; |
| } |
| } |
| idx = ++key_history_idx; |
| if (key_history_idx >= KEY_HIST) { |
| key_history_idx = 0; |
| idx = 0; |
| } |
| key_history[idx].sym = keysym; |
| key_history[idx].down = down; |
| key_history[idx].time = tnow; |
| |
| if (down && (keysym == XK_Alt_L || keysym == XK_Super_L)) { |
| int i, k, run = 0, ups = 0; |
| double delay = 1.0; |
| KeySym ks; |
| for (i=0; i<16; i++) { |
| k = idx - i; |
| if (k < 0) k += KEY_HIST; |
| if (!key_history[k].down) { |
| ups++; |
| continue; |
| } |
| ks = key_history[k].sym; |
| if (key_history[k].time < tnow - delay) { |
| break; |
| } else if (ks == keysym && ks == XK_Alt_L) { |
| run++; |
| } else if (ks == keysym && ks == XK_Super_L) { |
| run++; |
| } else { |
| break; |
| } |
| } |
| if (ups < 2) { |
| ; |
| } else if (run == 3 && keysym == XK_Alt_L) { |
| rfbLog("3*Alt_L, calling: refresh_screen(0)\n"); |
| refresh_screen(0); |
| } else if (run == 4 && keysym == XK_Alt_L) { |
| rfbLog("4*Alt_L, setting: do_copy_screen\n"); |
| do_copy_screen = 1; |
| } else if (run == 5 && keysym == XK_Alt_L) { |
| ; |
| } else if (run == 3 && keysym == XK_Super_L) { |
| rfbLog("3*Super_L, calling: set_xdamage_mark()\n"); |
| set_xdamage_mark(0, 0, dpy_x, dpy_y); |
| } else if (run == 4 && keysym == XK_Super_L) { |
| rfbLog("4*Super_L, calling: check_xrecord_reset()\n"); |
| check_xrecord_reset(1); |
| } else if (run == 5 && keysym == XK_Super_L) { |
| rfbLog("5*Super_L, calling: push_black_screen(0)\n"); |
| push_black_screen(0); |
| } |
| } |
| |
| #ifdef MAX_KEYREPEAT |
| if (max_keyrepeat_always < 0.0) { |
| if (getenv("MAX_KEYREPEAT")) { |
| max_keyrepeat_always = atof(getenv("MAX_KEYREPEAT")); |
| } else { |
| max_keyrepeat_always = 0.0; |
| } |
| } |
| if (max_keyrepeat_always > 0.0) { |
| max_keyrepeat_time = max_keyrepeat_always; |
| } |
| #else |
| if (0) {max_keyrepeat_always=0;} |
| #endif |
| if (!down && skipped_last_down) { |
| int db = debug_scroll; |
| if (keysym == max_keyrepeat_last_keysym) { |
| skipped_last_down = 0; |
| if (db) rfbLog("--- scroll keyrate skipping 0x%lx %s " |
| "%.4f %.4f\n", keysym, down ? "down":"up ", |
| tnow - x11vnc_start, tnow - max_keyrepeat_last_time); |
| INPUT_UNLOCK; |
| return; |
| } |
| } |
| if (down && max_keyrepeat_time > 0.0) { |
| int skip = 0; |
| int db = debug_scroll; |
| |
| if (max_keyrepeat_last_keysym != NoSymbol && |
| max_keyrepeat_last_keysym != keysym) { |
| ; |
| } else { |
| if (tnow < max_keyrepeat_last_time+max_keyrepeat_time) { |
| skip = 1; |
| } |
| } |
| max_keyrepeat_time = 0.0; |
| if (skip) { |
| if (db) rfbLog("--- scroll keyrate skipping 0x%lx %s " |
| "%.4f %.4f\n", keysym, down ? "down":"up ", |
| tnow - x11vnc_start, tnow - max_keyrepeat_last_time); |
| max_keyrepeat_last_keysym = keysym; |
| skipped_last_down = 1; |
| INPUT_UNLOCK; |
| return; |
| } else { |
| if (db) rfbLog("--- scroll keyrate KEEPING 0x%lx %s " |
| "%.4f %.4f\n", keysym, down ? "down":"up ", |
| tnow - x11vnc_start, tnow - max_keyrepeat_last_time); |
| } |
| } |
| max_keyrepeat_last_keysym = keysym; |
| max_keyrepeat_last_time = tnow; |
| skipped_last_down = 0; |
| last_rfb_key_accepted = TRUE; |
| |
| if (pipeinput_fh != NULL || pipeinput_int) { |
| pipe_keyboard(down, keysym, client); /* MACOSX here. */ |
| if (! pipeinput_tee) { |
| if (! view_only || raw_fb) { /* raw_fb hack */ |
| last_keyboard_client = client; |
| last_event = last_input = now; |
| last_keyboard_input = now; |
| |
| last_keysym = keysym; |
| |
| last_rfb_down = down; |
| last_rfb_keysym = keysym; |
| last_rfb_keytime = tnow; |
| last_rfb_key_injected = dnow(); |
| |
| got_user_input++; |
| got_keyboard_input++; |
| } |
| INPUT_UNLOCK; |
| return; |
| } |
| } |
| |
| if (view_only) { |
| INPUT_UNLOCK; |
| return; |
| } |
| get_allowed_input(client, &input); |
| if (! input.keystroke) { |
| INPUT_UNLOCK; |
| return; |
| } |
| |
| track_mod_state(keysym, down, TRUE); /* ignores remaps */ |
| |
| last_keyboard_client = client; |
| last_event = last_input = now; |
| last_keyboard_input = now; |
| |
| last_keysym = keysym; |
| |
| last_rfb_down = down; |
| last_rfb_keysym = keysym; |
| last_rfb_keytime = tnow; |
| last_rfb_key_injected = dnow(); |
| |
| got_user_input++; |
| got_keyboard_input++; |
| |
| RAWFB_RET_VOID |
| |
| |
| apply_remap(&keysym, &isbutton); |
| |
| if (use_xrecord && ! xrecording && down) { |
| |
| if (!strcmp(scroll_copyrect, "never")) { |
| ; |
| } else if (!strcmp(scroll_copyrect, "mouse")) { |
| ; |
| } else if (skip_cr_when_scaling("scroll")) { |
| ; |
| } else if (! xrecord_skip_keysym(keysym)) { |
| snapshot_stack_list(0, 0.25); |
| xrecord_watch(1, SCR_KEY); |
| xrecord_set_by_keys = 1; |
| xrecord_keysym = keysym; |
| } else { |
| if (debug_scroll) { |
| char *str = XKeysymToString(keysym); |
| rfbLog("xrecord_skip_keysym: %s\n", |
| str ? str : "NoSymbol"); |
| } |
| } |
| } |
| |
| if (isbutton) { |
| int mask, button = (int) keysym; |
| char *b, bstr[32]; |
| |
| if (! down) { |
| INPUT_UNLOCK; |
| return; /* nothing to send */ |
| } |
| if (debug_keyboard) { |
| rfbLog("keyboard(): remapping keystroke to button %d" |
| " click\n", button); |
| } |
| dtime0(&last_key_to_button_remap_time); |
| |
| X_LOCK; |
| /* |
| * This in principle can be a little dicey... i.e. even |
| * remap the button click to keystroke sequences! |
| * Usually just will simulate the button click. |
| */ |
| |
| /* loop over possible multiclicks: Button123 */ |
| sprintf(bstr, "%d", button); |
| b = bstr; |
| while (*b != '\0') { |
| char t[2]; |
| int butt; |
| t[0] = *b; |
| t[1] = '\0'; |
| if (sscanf(t, "%d", &butt) == 1) { |
| mask = 1<<(butt-1); |
| do_button_mask_change(mask, butt); /* down */ |
| mask = 0; |
| do_button_mask_change(mask, butt); /* up */ |
| } |
| b++; |
| } |
| XFlush_wr(dpy); |
| X_UNLOCK; |
| INPUT_UNLOCK; |
| return; |
| } |
| |
| if (use_modifier_tweak) { |
| modifier_tweak_keyboard(down, keysym, client); |
| X_LOCK; |
| XFlush_wr(dpy); |
| X_UNLOCK; |
| INPUT_UNLOCK; |
| return; |
| } |
| |
| X_LOCK; |
| |
| k = XKeysymToKeycode(dpy, (KeySym) keysym); |
| |
| if (k == NoSymbol && add_keysyms && ! IsModifierKey(keysym)) { |
| int new_kc = add_keysym(keysym); |
| if (new_kc) { |
| k = new_kc; |
| } |
| } |
| if (debug_keyboard) { |
| char *str = XKeysymToString(keysym); |
| rfbLog("keyboard(): KeySym 0x%x \"%s\" -> KeyCode 0x%x%s\n", |
| (int) keysym, str ? str : "null", (int) k, |
| k ? "" : " *ignored*"); |
| } |
| |
| if ( k != NoSymbol ) { |
| XTestFakeKeyEvent_wr(dpy, k, (Bool) down, CurrentTime); |
| XFlush_wr(dpy); |
| } |
| |
| X_UNLOCK; |
| INPUT_UNLOCK; |
| } |
| |
| |