You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3193 lines
74 KiB
3193 lines
74 KiB
/* -- 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);
|
|
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);
|
|
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);
|
|
}
|
|
|
|
/*
|
|
* 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();
|
|
X_LOCK;
|
|
/* read state and skip restore if equal (e.g. no clients) */
|
|
if (global_auto_repeat == save_auto_repeat) {
|
|
X_UNLOCK;
|
|
return;
|
|
}
|
|
|
|
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 new[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++) {
|
|
new[i] = NoSymbol;
|
|
}
|
|
if (add_keysyms == 2) {
|
|
new[0] = keysym; /* XXX remove me */
|
|
} else {
|
|
for(i=0; i < syms_per_keycode; i++) {
|
|
new[i] = keysym;
|
|
if (i >= 7) break;
|
|
}
|
|
}
|
|
|
|
XChangeKeyboardMapping(dpy, kc, syms_per_keycode,
|
|
new, 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, new[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++) {
|
|
new[i] = NoSymbol;
|
|
}
|
|
|
|
XChangeKeyboardMapping(dpy, kc, syms_per_keycode, new, 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) {
|
|
if (!sloppy_keys) {
|
|
return 0;
|
|
}
|
|
|
|
RAWFB_RET(0)
|
|
#if NO_X11
|
|
if (!key || !down || !keysym || !new) {}
|
|
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 = 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_gt_4 >= 8) {
|
|
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");
|
|
}
|
|
|
|
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");
|
|
}
|
|
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");
|
|
}
|
|
}
|
|
|
|
/* 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 > 1) {
|
|
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 > 1) {
|
|
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 > 1) {
|
|
fprintf(stderr, "grp_max=%d lvl_max=%d\n", grp_max, lvl_max);
|
|
}
|
|
}
|
|
|
|
static int score_hint[0x100][0x100];
|
|
/*
|
|
* 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;
|
|
|
|
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;
|
|
}
|
|
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]] = 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, sbest = -1, lbest, l;
|
|
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 any one that is down */
|
|
if (Kc_f == -1 && anydown) {
|
|
int l;
|
|
int best = -1, lbest;
|
|
/*
|
|
* 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).
|
|
*/
|
|
/* we could probably break ties based on lowest XKeycodeToKeysym index */
|
|
for (l=0; l < found; l++) {
|
|
int key = (int) kc_f[l];
|
|
int j, jmatch = -1;
|
|
|
|
if (keycode_state[key]) {
|
|
continue;
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
|
|
/* 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, 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 acheive 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) {
|
|
|
|
RAWFB_RET_VOID
|
|
|
|
if (use_modifier_tweak) {
|
|
initialize_modtweak();
|
|
}
|
|
if (remap_file != NULL) {
|
|
initialize_remap(remap_file);
|
|
}
|
|
|
|
initialize_pointer_map(pointer_remap);
|
|
|
|
clear_modifiers(1);
|
|
if (clear_mods == 1) {
|
|
clear_modifiers(0);
|
|
}
|
|
}
|
|
|
|
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) {
|
|
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++;
|
|
}
|
|
}
|
|
|
|
/* for -pipeinput mode */
|
|
static void pipe_keyboard(rfbBool down, rfbKeySym keysym, rfbClientPtr client) {
|
|
int can_input = 0, uid = 0;
|
|
allowed_input_t input;
|
|
char *name;
|
|
ClientData *cd = (ClientData *) client->clientData;
|
|
|
|
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;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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);
|
|
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;
|
|
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;
|
|
|
|
got_user_input++;
|
|
got_keyboard_input++;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (view_only) {
|
|
return;
|
|
}
|
|
get_allowed_input(client, &input);
|
|
if (! input.keystroke) {
|
|
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;
|
|
|
|
got_user_input++;
|
|
got_keyboard_input++;
|
|
|
|
RAWFB_RET_VOID
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
if (! down) {
|
|
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.
|
|
*/
|
|
mask = 1<<(button-1);
|
|
do_button_mask_change(mask, button); /* down */
|
|
mask = 0;
|
|
do_button_mask_change(mask, button); /* up */
|
|
XFlush_wr(dpy);
|
|
X_UNLOCK;
|
|
return;
|
|
}
|
|
|
|
if (use_modifier_tweak) {
|
|
modifier_tweak_keyboard(down, keysym, client);
|
|
X_LOCK;
|
|
XFlush_wr(dpy);
|
|
X_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;
|
|
}
|
|
|
|
|