diff options
Diffstat (limited to 'nx-X11/programs/Xserver/hw/nxagent/Keystroke.c')
-rw-r--r-- | nx-X11/programs/Xserver/hw/nxagent/Keystroke.c | 662 |
1 files changed, 662 insertions, 0 deletions
diff --git a/nx-X11/programs/Xserver/hw/nxagent/Keystroke.c b/nx-X11/programs/Xserver/hw/nxagent/Keystroke.c new file mode 100644 index 000000000..1e3ebca8f --- /dev/null +++ b/nx-X11/programs/Xserver/hw/nxagent/Keystroke.c @@ -0,0 +1,662 @@ +/**************************************************************************/ +/* */ +/* Copyright (c) 2001, 2011 NoMachine (http://www.nomachine.com) */ +/* Copyright (c) 2008-2014 Oleksandr Shneyder <o.shneyder@phoca-gmbh.de> */ +/* Copyright (c) 2011-2016 Mike Gabriel <mike.gabriel@das-netzwerkteam.de>*/ +/* Copyright (c) 2014-2016 Mihai Moldovan <ionic@ionic.de> */ +/* Copyright (c) 2014-2016 Ulrich Sibiller <uli42@gmx.de> */ +/* Copyright (c) 2015-2016 Qindel Group (http://www.qindel.com) */ +/* */ +/* NXAGENT, NX protocol compression and NX extensions to this software */ +/* are copyright of the aforementioned persons and companies. */ +/* */ +/* Redistribution and use of the present software is allowed according */ +/* to terms specified in the file LICENSE which comes in the source */ +/* distribution. */ +/* */ +/* All rights reserved. */ +/* */ +/* NOTE: This software has received contributions from various other */ +/* contributors, only the core maintainers and supporters are listed as */ +/* copyright holders. Please contact us, if you feel you should be listed */ +/* as copyright holder, as well. */ +/* */ +/**************************************************************************/ + +#include "X.h" +#include "keysym.h" + +#include "screenint.h" +#include "scrnintstr.h" + +#include "Agent.h" +#include "Display.h" +#include "Events.h" +#include "Options.h" +#include "Keyboard.h" +#include "Drawable.h" +#include "Init.h" /* extern int nxagentX2go */ +#include "Utils.h" + +#include <unistd.h> + +#include <libxml/parser.h> +#include <libxml/tree.h> + +extern Bool nxagentWMIsRunning; +extern Bool nxagentIpaq; +extern char *nxagentKeystrokeFile; + +#ifdef NX_DEBUG_INPUT +int nxagentDebugInputDevices = False; +unsigned long nxagentLastInputDevicesDumpTime = 0; +extern void nxagentDeactivateInputDevicesGrabs(); +#endif + +/* + * Set here the required log level. + */ + +#define PANIC +#define WARNING +#undef TEST +#undef DEBUG +#undef DUMP + +/* must be included _after_ DUMP */ +#include "Keystroke.h" + + +/* this table is used to parse actions given on the command line or in the + * config file, therefore indices have to match the enum in Keystroke.h */ +char * nxagentSpecialKeystrokeNames[] = { + "end_marker", + "close_session", + "switch_all_screens", + "fullscreen", + "minimize", + "defer", + "ignore", + "force_synchronization", + +#ifdef DEBUG_TREE + "debug_tree", +#endif +#ifdef DUMP + "regions_on_screen", +#endif +#ifdef NX_DEBUG_INPUT + "test_input", + "deactivate_input_devices_grab", +#endif + "resize", + "viewport_move_left", + "viewport_move_up", + "viewport_move_right", + "viewport_move_down", + "viewport_scroll_left", + "viewport_scroll_up", + "viewport_scroll_right", + "viewport_scroll_down", + + "reread_keystrokes", + NULL, +}; + +struct nxagentSpecialKeystrokeMap default_map[] = { + /* stroke, modifierMask, modifierAltMeta, keysym */ +#ifdef DEBUG_TREE + {KEYSTROKE_DEBUG_TREE, ControlMask, True, XK_q}, +#endif + {KEYSTROKE_CLOSE_SESSION, ControlMask, True, XK_t}, + {KEYSTROKE_SWITCH_ALL_SCREENS, ControlMask, True, XK_f}, + {KEYSTROKE_FULLSCREEN, ControlMask | ShiftMask, True, XK_f}, + {KEYSTROKE_MINIMIZE, ControlMask, True, XK_m}, + {KEYSTROKE_DEFER, ControlMask, True, XK_e}, + {KEYSTROKE_IGNORE, ControlMask, True, XK_BackSpace}, + {KEYSTROKE_IGNORE, 0, False, XK_Terminate_Server}, + {KEYSTROKE_FORCE_SYNCHRONIZATION, ControlMask, True, XK_j}, +#ifdef DUMP + {KEYSTROKE_REGIONS_ON_SCREEN, ControlMask, True, XK_a}, +#endif +#ifdef NX_DEBUG_INPUT + {KEYSTROKE_TEST_INPUT, ControlMask, True, XK_x}, + {KEYSTROKE_DEACTIVATE_INPUT_DEVICES_GRAB, ControlMask, True, XK_y}, +#endif + {KEYSTROKE_RESIZE, ControlMask, True, XK_r}, + {KEYSTROKE_VIEWPORT_MOVE_LEFT, ControlMask | ShiftMask, True, XK_Left}, + {KEYSTROKE_VIEWPORT_MOVE_LEFT, ControlMask | ShiftMask, True, XK_KP_Left}, + {KEYSTROKE_VIEWPORT_MOVE_UP, ControlMask | ShiftMask, True, XK_Up}, + {KEYSTROKE_VIEWPORT_MOVE_UP, ControlMask | ShiftMask, True, XK_KP_Up}, + {KEYSTROKE_VIEWPORT_MOVE_RIGHT, ControlMask | ShiftMask, True, XK_Right}, + {KEYSTROKE_VIEWPORT_MOVE_RIGHT, ControlMask | ShiftMask, True, XK_KP_Right}, + {KEYSTROKE_VIEWPORT_MOVE_DOWN, ControlMask | ShiftMask, True, XK_Down}, + {KEYSTROKE_VIEWPORT_MOVE_DOWN, ControlMask | ShiftMask, True, XK_KP_Down}, + {KEYSTROKE_VIEWPORT_SCROLL_LEFT, ControlMask, True, XK_Left}, + {KEYSTROKE_VIEWPORT_SCROLL_LEFT, ControlMask, True, XK_KP_Left}, + {KEYSTROKE_VIEWPORT_SCROLL_UP, ControlMask, True, XK_Up}, + {KEYSTROKE_VIEWPORT_SCROLL_UP, ControlMask, True, XK_KP_Up}, + {KEYSTROKE_VIEWPORT_SCROLL_RIGHT, ControlMask, True, XK_Right}, + {KEYSTROKE_VIEWPORT_SCROLL_RIGHT, ControlMask, True, XK_KP_Right}, + {KEYSTROKE_VIEWPORT_SCROLL_DOWN, ControlMask, True, XK_Down}, + {KEYSTROKE_VIEWPORT_SCROLL_DOWN, ControlMask, True, XK_KP_Down}, + {KEYSTROKE_REREAD_KEYSTROKES, ControlMask, True, XK_k}, + {KEYSTROKE_END_MARKER, 0, False, NoSymbol}, +}; +struct nxagentSpecialKeystrokeMap *map = default_map; + +static Bool modifier_matches(unsigned int mask, int compare_alt_meta, unsigned int state) +{ + /* nxagentAltMetaMask needs special handling + * it seems to me its an and-ed mask of all possible meta and alt keys + * somehow... + * + * otherwise this function would be just a simple bitop + */ + Bool ret = True; + + if (compare_alt_meta) { + if (! (state & nxagentAltMetaMask)) { + ret = False; + } + + mask &= ~nxagentAltMetaMask; + state &= ~nxagentAltMetaMask; + } + + /* ignore CapsLock and/or Numlock if the keystroke does not + explicitly require them */ + + if ( !(mask & nxagentCapsMask) ) + state &= ~nxagentCapsMask; + + if ( !(mask & nxagentNumlockMask) ) + state &= ~nxagentNumlockMask; + + /* all modifiers except meta/alt have to match exactly, extra bits are evil */ + if (mask != state) { + ret = False; + } + + return ret; +} + +static Bool read_binding_from_xmlnode(xmlNode *node, struct nxagentSpecialKeystrokeMap *ret) +{ + /* init the struct to have proper values in case not all attributes are found */ + struct nxagentSpecialKeystrokeMap newkm = { + .stroke = KEYSTROKE_NOTHING, + .modifierMask = 0, + .modifierAltMeta = False, + .keysym = NoSymbol + }; + + for (xmlAttr *attr = node->properties; attr; attr = attr->next) + { + /* ignore attributes without data (which should never happen anyways) */ + if (attr->children->content == NULL) + { + #ifdef DEBUG + char *aname = (attr->name)?((char *)attr->name):"unknown"; + fprintf(stderr, "attribute %s with NULL value", aname); + #endif + continue; + } + + if (strcmp((char *)attr->name, "action") == 0) + { + newkm.stroke = KEYSTROKE_NOTHING; + for (int i = 0; nxagentSpecialKeystrokeNames[i] != NULL; i++) + { + if (strcmp(nxagentSpecialKeystrokeNames[i], (char *)attr->children->content) == 0) + { + /* this relies on the values of enum nxagentSpecialKeystroke and the + * indices of nxagentSpecialKeystrokeNames being in sync */ + newkm.stroke = i; + break; + } + } + if (newkm.stroke == KEYSTROKE_NOTHING) + fprintf(stderr, "Info: ignoring unknown keystroke action '%s'.\n", (char *)attr->children->content); + continue; + } + else if (strcmp((char *)attr->name, "key") == 0) + { + newkm.keysym = XStringToKeysym((char *)attr->children->content); + continue; + } + else + { + /* ignore attributes with value="0" or "false", everything else is interpreted as true */ + if (strcmp((char *)attr->children->content, "0") == 0 || strcmp((char *)attr->children->content, "false") == 0) + continue; + + if (strcmp((char *)attr->name, "Mod1") == 0) { newkm.modifierMask |= Mod1Mask; } + else if (strcmp((char *)attr->name, "Mod2") == 0) { newkm.modifierMask |= Mod2Mask; } + else if (strcmp((char *)attr->name, "Mod3") == 0) { newkm.modifierMask |= Mod3Mask; } + else if (strcmp((char *)attr->name, "Mod4") == 0) { newkm.modifierMask |= Mod4Mask; } + else if (strcmp((char *)attr->name, "Mod5") == 0) { newkm.modifierMask |= Mod5Mask; } + else if (strcmp((char *)attr->name, "Control") == 0) { newkm.modifierMask |= ControlMask; } + else if (strcmp((char *)attr->name, "Shift") == 0) { newkm.modifierMask |= ShiftMask; } + else if (strcmp((char *)attr->name, "Lock") == 0) { newkm.modifierMask |= LockMask; } + else if (strcmp((char *)attr->name, "AltMeta") == 0) { newkm.modifierAltMeta = True; } + } + } + + if (newkm.stroke != KEYSTROKE_NOTHING && newkm.keysym != NoSymbol) + { + /* keysym and stroke are required, everything else is optional */ + memcpy(ret, &newkm, sizeof(struct nxagentSpecialKeystrokeMap)); + return True; + } + else + return False; +} + +/* + * searches a keystroke xml file + * + * search order: + * - '-keystrokefile' commandline parameter + * - $NXAGENT_KEYSTROKEFILE environment variable + * - $HOME/.nx/config/keystrokes.cfg + * - /etc/nxagent/keystrokes.cfg + * - hardcoded traditional NX default settings + * If run in x2go flavour different filenames and varnames are used. + */ +void nxagentInitKeystrokes(Bool force) +{ + char *filename = NULL; + + char *homefile; + char *etcfile; + char *envvar; + + /* used for tracking if the config file parsing has already been + done (regardless of the result) */ + static Bool done = False; + + if (force) { + if (map != default_map) + { + free(map); + map = default_map; + } + fprintf(stderr, "Info: re-reading keystrokes configuration\n"); + } + else + { + if (done) + return; + } + + done = True; + + if (nxagentX2go) { + homefile = "/.x2go/config/keystrokes.cfg"; + etcfile = "/etc/x2go/keystrokes.cfg"; + envvar = "X2GO_KEYSTROKEFILE"; + } else { + homefile = "/.nx/config/keystrokes.cfg"; + etcfile = "/etc/nxagent/keystrokes.cfg"; + envvar = "NXAGENT_KEYSTROKEFILE"; + } + + if (nxagentKeystrokeFile && access(nxagentKeystrokeFile, R_OK) == 0) + { + if (!(filename = strdup(nxagentKeystrokeFile))) + { + fprintf(stderr, "malloc failed"); + exit(EXIT_FAILURE); + } + } + else if (nxagentKeystrokeFile) + { + fprintf(stderr, "Warning: Cannot read keystroke file '%s'.\n", nxagentKeystrokeFile); + if ((filename = getenv(envvar)) && access(filename, R_OK) == 0) + { + if (!(filename = strdup(filename))) + { + fprintf(stderr, "malloc failed"); + exit(EXIT_FAILURE); + } + } + else + { + char *homedir = getenv("HOME"); + filename = NULL; + if (homedir) + { + if (!(filename = calloc(1, strlen(homefile) + strlen(homedir) + 1))) + { + fprintf(stderr, "malloc failed"); + exit(EXIT_FAILURE); + } + strcpy(filename, homedir); + strcpy(filename + strlen(homedir), homefile); + } + + if (access(filename, R_OK) == 0) + { + /* empty */ + } + else if (access(etcfile, R_OK) == 0) + { + free(filename); + if (!(filename = strdup(etcfile))) + { + fprintf(stderr, "malloc failed"); + exit(EXIT_FAILURE); + } + } + else + { + free(filename); + filename = NULL; + } + } + } + + /* now we know which file to read, if any */ + if (filename) + { + LIBXML_TEST_VERSION + xmlDoc *doc = xmlReadFile(filename, NULL, 0); + if (doc) + { + fprintf(stderr, "Info: using keystrokes file '%s'\n", filename); + for (xmlNode *cur = xmlDocGetRootElement(doc); cur; cur = cur->next) + { + if (cur->type == XML_ELEMENT_NODE && strcmp((char *)cur->name, "keystrokes") == 0) + { + xmlNode *bindings; + int num = 0; + int idx = 0; + + for (bindings = cur->children; bindings; bindings = bindings->next) + { + if (bindings->type == XML_ELEMENT_NODE && strcmp((char *)bindings->name, "keystroke") == 0) + { + num++; + } + } + #ifdef DEBUG + fprintf(stderr, "%s: found %d keystrokes in %s\n", __func__, num, filename); + #endif + if (!(map = calloc(num+1, sizeof(struct nxagentSpecialKeystrokeMap)))) + { + fprintf(stderr, "calloc failed"); + exit(EXIT_FAILURE); + } + + for (bindings = cur->children; bindings; bindings = bindings->next) + { + map[idx].stroke = KEYSTROKE_NOTHING; + if (bindings->type == XML_ELEMENT_NODE && + strcmp((char *)bindings->name, "keystroke") == 0 && + read_binding_from_xmlnode(bindings, &(map[idx]))) + { + Bool store = True; + for (int j = 0; j < idx; j++) + { + if (map[j].stroke != KEYSTROKE_NOTHING && + map[idx].keysym != NoSymbol && + map[j].keysym == map[idx].keysym && + map[j].modifierMask == map[idx].modifierMask && + map[j].modifierAltMeta == map[idx].modifierAltMeta) + { + fprintf(stderr, "Warning: ignoring keystroke '%s' (already in use by '%s')\n", + nxagentSpecialKeystrokeNames[map[idx].stroke], + nxagentSpecialKeystrokeNames[map[j].stroke]); + store = False; + break; + } + } + + if (store) + idx++; + else + map[idx].stroke = KEYSTROKE_NOTHING; + } + } + #ifdef DEBUG + fprintf(stderr, "%s: read %d keystrokes", __func__, idx); + #endif + + map[idx].stroke = KEYSTROKE_END_MARKER; + } + } + + xmlFreeDoc(doc); + xmlCleanupParser(); + } + else + { + fprintf(stderr, "Warning: could not read/parse keystrokes file '%s'\n", filename); + } + free(filename); + filename = NULL; + } + + if (map == default_map) + { + fprintf(stderr, "Info: Using builtin keystrokes.\n"); + } + + nxagentDumpKeystrokes(); +} + +void nxagentDumpKeystrokes(void) +{ + int maxlen = 0; + for (int i = 0; nxagentSpecialKeystrokeNames[i]; i++) + maxlen = MAX(maxlen, strlen(nxagentSpecialKeystrokeNames[i])); + + fprintf(stderr, "Current known keystrokes:\n"); + + for (struct nxagentSpecialKeystrokeMap *cur = map; cur->stroke != KEYSTROKE_END_MARKER; cur++) { + unsigned int mask = cur->modifierMask; + fprintf(stderr, " %-*s ", maxlen, nxagentSpecialKeystrokeNames[cur->stroke]); + if (mask & ControlMask) {fprintf(stderr, "Ctrl+"); mask &= ~ControlMask;} + if (mask & ShiftMask) {fprintf(stderr, "Shift+"); mask &= ~ShiftMask;} + + /* these are only here for better readable modifier + names. Normally they are covered by the Mod<n> and Lock lines + below */ + if (cur->modifierAltMeta) {fprintf(stderr, "Alt+"); mask &= ~(cur->modifierAltMeta);} + if (mask & nxagentCapsMask) {fprintf(stderr, "CapsLock+"); mask &= ~nxagentCapsMask;} + if (mask & nxagentNumlockMask) {fprintf(stderr, "NumLock+"); mask &= ~nxagentNumlockMask;} + + if (mask & Mod1Mask) {fprintf(stderr, "Mod1+"); mask &= ~Mod1Mask;} + if (mask & Mod2Mask) {fprintf(stderr, "Mod2+"); mask &= ~Mod2Mask;} + if (mask & Mod3Mask) {fprintf(stderr, "Mod3+"); mask &= ~Mod3Mask;} + if (mask & Mod4Mask) {fprintf(stderr, "Mod4+"); mask &= ~Mod4Mask;} + if (mask & Mod5Mask) {fprintf(stderr, "Mod5+"); mask &= ~Mod5Mask;} + if (mask & LockMask) {fprintf(stderr, "Lock+"); mask &= ~LockMask;} + fprintf(stderr, "%s\n", XKeysymToString(cur->keysym)); + } +} + +static enum nxagentSpecialKeystroke find_keystroke(XKeyEvent *X) +{ + enum nxagentSpecialKeystroke ret = KEYSTROKE_NOTHING; + + KeySym keysym = XKeycodeToKeysym(nxagentDisplay, X->keycode, 0); + + + #ifdef DEBUG + fprintf(stderr, "%s: got keysym '%c' (%d)\n", __func__, keysym, keysym); + #endif + for (struct nxagentSpecialKeystrokeMap *cur = map; cur->stroke != KEYSTROKE_END_MARKER; cur++) { + #ifdef DEBUG + fprintf(stderr, "%s: checking keysym '%c' (%d)\n", __func__, cur->keysym, cur->keysym); + #endif + if (cur->keysym == keysym && modifier_matches(cur->modifierMask, cur->modifierAltMeta, X->state)) { + #ifdef DEBUG + fprintf(stderr, "%s: match including modifiers for keysym '%c' (%d), stroke %d (%s)\n", __func__, cur->keysym, cur->keysym, cur->stroke, nxagentSpecialKeystrokeNames[cur->stroke]); + #endif + return cur->stroke; + } + } + + return ret; +} + +Bool nxagentCheckSpecialKeystroke(XKeyEvent *X, enum HandleEventResult *result) +{ + enum nxagentSpecialKeystroke stroke = find_keystroke(X); + *result = doNothing; + + #ifdef TEST + if (stroke != KEYSTROKE_NOTHING && stroke != KEYSTROKE_END_MARKER) + fprintf(stderr, "nxagentCheckSpecialKeystroke: got code %x - state %x - stroke %d (%s)\n", + X -> keycode, X -> state, stroke, nxagentSpecialKeystrokeNames[stroke]); + else + fprintf(stderr, "nxagentCheckSpecialKeystroke: got code %x - state %x - stroke %d (unused)\n", + X -> keycode, X -> state, stroke); + #endif + + if (stroke == KEYSTROKE_NOTHING) + return False; + + /* + * Check special keys. + */ + + /* + * FIXME: We should use the keysym instead that the keycode + * here. + */ + + if (X -> keycode == 130 && nxagentIpaq) + { + *result = doStartKbd; + + return True; + } + + switch (stroke) { +#ifdef DEBUG_TREE + case KEYSTROKE_DEBUG_TREE: + *result = doDebugTree; + break; +#endif + case KEYSTROKE_CLOSE_SESSION: + *result = doCloseSession; + break; + case KEYSTROKE_SWITCH_ALL_SCREENS: + if (!nxagentOption(Rootless)) { + *result = doSwitchAllScreens; + } + break; + case KEYSTROKE_MINIMIZE: + if (!nxagentOption(Rootless)) { + *result = doMinimize; + } + break; + case KEYSTROKE_DEFER: + *result = doSwitchDeferMode; + break; + case KEYSTROKE_IGNORE: + /* this is used e.g. to ignore C-A-Backspace aka XK_Terminate_Server */ + return True; + break; + case KEYSTROKE_FORCE_SYNCHRONIZATION: + nxagentForceSynchronization = 1; + break; +#ifdef DUMP + case KEYSTROKE_REGIONS_ON_SCREEN: + nxagentRegionsOnScreen(); + break; +#endif +#ifdef NX_DEBUG_INPUT + case KEYSTROKE_TEST_INPUT: + /* + * Used to test the input devices state. + */ + if (X -> type == KeyPress) { + if (!nxagentDebugInputDevices) { + fprintf(stderr, "Info: Turning input devices debug ON.\n"); + nxagentDebugInputDevices = True; + } else { + fprintf(stderr, "Info: Turning input devices debug OFF.\n"); + nxagentDebugInputDevices = False; + nxagentLastInputDevicesDumpTime = 0; + } + } + return True; + break; + case KEYSTROKE_DEACTIVATE_INPUT_DEVICES_GRAB: + if (X->type == KeyPress) { + nxagentDeactivateInputDevicesGrab(); + } + return True; + break; +#endif + case KEYSTROKE_FULLSCREEN: + if (!nxagentOption(Rootless)) { + *result = doSwitchFullscreen; + } + break; + case KEYSTROKE_RESIZE: + if (!nxagentOption(Rootless)) { + *result = doSwitchResizeMode; + } + break; + case KEYSTROKE_VIEWPORT_MOVE_LEFT: + if (!nxagentOption(Rootless) && !nxagentOption(DesktopResize)) { + *result = doViewportMoveLeft; + } + break; + case KEYSTROKE_VIEWPORT_MOVE_UP: + if (!nxagentOption(Rootless) && !nxagentOption(DesktopResize)) { + *result = doViewportMoveUp; + } + break; + case KEYSTROKE_VIEWPORT_MOVE_RIGHT: + if (!nxagentOption(Rootless) && !nxagentOption(DesktopResize)) { + *result = doViewportMoveRight; + } + break; + case KEYSTROKE_VIEWPORT_MOVE_DOWN: + if (!nxagentOption(Rootless) && !nxagentOption(DesktopResize)) { + *result = doViewportMoveDown; + } + break; + case KEYSTROKE_VIEWPORT_SCROLL_LEFT: + if (!nxagentOption(Rootless) && !nxagentOption(DesktopResize)) { + *result = doViewportLeft; + } + break; + case KEYSTROKE_VIEWPORT_SCROLL_UP: + if (!nxagentOption(Rootless) && !nxagentOption(DesktopResize)) { + *result = doViewportUp; + } + break; + case KEYSTROKE_VIEWPORT_SCROLL_RIGHT: + if (!nxagentOption(Rootless) && !nxagentOption(DesktopResize)) { + *result = doViewportRight; + } + break; + case KEYSTROKE_VIEWPORT_SCROLL_DOWN: + if (!nxagentOption(Rootless) && !nxagentOption(DesktopResize)) { + *result = doViewportDown; + } + break; + case KEYSTROKE_REREAD_KEYSTROKES: + /* two reasons to check on KeyRelease: + - this code is called for KeyPress and KeyRelease, so we + would read the keystroke file twice + - if the keystroke file changes settings for this key this + might lead to unexpected behaviour + */ + if (X->type == KeyRelease) + nxagentInitKeystrokes(True); + break; + case KEYSTROKE_NOTHING: /* do nothing. difference to KEYSTROKE_IGNORE is the return value */ + case KEYSTROKE_END_MARKER: /* just to make gcc STFU */ + case KEYSTROKE_MAX: + break; + } + return (*result == doNothing); +} |