/**************************************************************************/ /* */ /* Copyright (c) 2001, 2011 NoMachine, http://www.nomachine.com/. */ /* */ /* NXAGENT, NX protocol compression and NX extensions to this software */ /* are copyright of NoMachine. Redistribution and use of the present */ /* software is allowed according to terms specified in the file LICENSE */ /* which comes in the source distribution. */ /* */ /* Check http://www.nomachine.com/licensing.html for applicability. */ /* */ /* NX and NoMachine are trademarks of Medialogic S.p.A. */ /* */ /* All rights reserved. */ /* */ /**************************************************************************/ #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 "Keystroke.h" #include "Drawable.h" #include "Init.h" /* extern int nxagentX2go */ #include #include #include extern Bool nxagentWMIsRunning; extern Bool nxagentIpaq; extern char *nxagentKeystrokeFile; Bool nxagentKeystrokeFileParsed = False; #ifdef NX_DEBUG_INPUT int nxagentDebugInputDevices = 0; unsigned long nxagentLastInputDevicesDumpTime = 0; extern void nxagentDeactivateInputDevicesGrabs(); #endif /* * Set here the required log level. */ #define PANIC #define WARNING #undef TEST #undef DEBUG #undef DUMP /* 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", "minimize", "left", "up", "right", "down", "resize", "defer", "ignore", "force_synchronization", "debug_tree", "regions_on_screen", "test_input", "deactivate_input_devices_grab", "fullscreen", "viewport_move_left", "viewport_move_up", "viewport_move_right", "viewport_move_down", NULL, }; struct nxagentSpecialKeystrokeMap default_map[] = { /* stroke, modifierMask, modifierAltMeta, keysym */ {KEYSTROKE_DEBUG_TREE, ControlMask, 1, XK_q}, {KEYSTROKE_DEBUG_TREE, ControlMask, 1, XK_Q}, {KEYSTROKE_CLOSE_SESSION, ControlMask, 1, XK_t}, {KEYSTROKE_CLOSE_SESSION, ControlMask, 1, XK_T}, {KEYSTROKE_SWITCH_ALL_SCREENS, ControlMask, 1, XK_f}, {KEYSTROKE_SWITCH_ALL_SCREENS, ControlMask, 1, XK_F}, {KEYSTROKE_MINIMIZE, ControlMask, 1, XK_m}, {KEYSTROKE_MINIMIZE, ControlMask, 1, XK_M}, {KEYSTROKE_LEFT, ControlMask, 1, XK_Left}, {KEYSTROKE_LEFT, ControlMask, 1, XK_KP_Left}, {KEYSTROKE_UP, ControlMask, 1, XK_Up}, {KEYSTROKE_UP, ControlMask, 1, XK_KP_Up}, {KEYSTROKE_RIGHT, ControlMask, 1, XK_Right}, {KEYSTROKE_RIGHT, ControlMask, 1, XK_KP_Right}, {KEYSTROKE_DOWN, ControlMask, 1, XK_Down}, {KEYSTROKE_DOWN, ControlMask, 1, XK_KP_Down}, {KEYSTROKE_RESIZE, ControlMask, 1, XK_r}, {KEYSTROKE_RESIZE, ControlMask, 1, XK_R}, {KEYSTROKE_DEFER, ControlMask, 1, XK_e}, {KEYSTROKE_DEFER, ControlMask, 1, XK_E}, {KEYSTROKE_IGNORE, ControlMask, 1, XK_BackSpace}, {KEYSTROKE_IGNORE, 0, 0, XK_Terminate_Server}, {KEYSTROKE_FORCE_SYNCHRONIZATION, ControlMask, 1, XK_j}, {KEYSTROKE_FORCE_SYNCHRONIZATION, ControlMask, 1, XK_J}, {KEYSTROKE_REGIONS_ON_SCREEN, ControlMask, 1, XK_a}, {KEYSTROKE_REGIONS_ON_SCREEN, ControlMask, 1, XK_A}, {KEYSTROKE_TEST_INPUT, ControlMask, 1, XK_x}, {KEYSTROKE_TEST_INPUT, ControlMask, 1, XK_X}, {KEYSTROKE_DEACTIVATE_INPUT_DEVICES_GRAB, ControlMask, 1, XK_y}, {KEYSTROKE_DEACTIVATE_INPUT_DEVICES_GRAB, ControlMask, 1, XK_Y}, {KEYSTROKE_FULLSCREEN, ControlMask | ShiftMask, 1, XK_f}, {KEYSTROKE_FULLSCREEN, ControlMask | ShiftMask, 1, XK_F}, {KEYSTROKE_VIEWPORT_MOVE_LEFT, ControlMask | ShiftMask, 1, XK_Left}, {KEYSTROKE_VIEWPORT_MOVE_LEFT, ControlMask | ShiftMask, 1, XK_KP_Left}, {KEYSTROKE_VIEWPORT_MOVE_UP, ControlMask | ShiftMask, 1, XK_Up}, {KEYSTROKE_VIEWPORT_MOVE_UP, ControlMask | ShiftMask, 1, XK_KP_Up}, {KEYSTROKE_VIEWPORT_MOVE_RIGHT, ControlMask | ShiftMask, 1, XK_Right}, {KEYSTROKE_VIEWPORT_MOVE_RIGHT, ControlMask | ShiftMask, 1, XK_KP_Right}, {KEYSTROKE_VIEWPORT_MOVE_DOWN, ControlMask | ShiftMask, 1, XK_Down}, {KEYSTROKE_VIEWPORT_MOVE_DOWN, ControlMask | ShiftMask, 1, XK_KP_Down}, {KEYSTROKE_END_MARKER, 0, 0, 0}, }; struct nxagentSpecialKeystrokeMap *map = default_map; static int 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 */ int ret = 1; if (compare_alt_meta) { if (! (state & nxagentAltMetaMask)) { ret = 0; } mask &= ~nxagentAltMetaMask; } /* all modifiers except meta/alt have to match exactly, extra bits are evil */ if ((mask & state) != mask) { ret = 0; } return ret; } static int read_binding_from_xmlnode(xmlNode *node, struct nxagentSpecialKeystrokeMap *ret) { int successful = 0; struct nxagentSpecialKeystrokeMap new = {0, 0, 0, 0}; xmlAttr *attr; for (attr = node->properties; attr; attr = attr->next) { /* ignore attributes without data (which should never happen anyways) */ if (attr->children->content == NULL) { char *aname = (attr->name)?((char *)attr->name):"unknown"; fprintf(stderr, "attribute %s with NULL value", aname); continue; } if (strcmp((char *)attr->name, "action") == 0) { int i; for (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 */ new.stroke = i; break; } } continue; } else if (strcmp((char *)attr->name, "key") == 0) { new.keysym = XStringToKeysym((char *)attr->children->content); /* NoSymbol is usually 0, but could there be weird implementations? */ if (new.keysym == NoSymbol) { new.keysym = 0; } continue; } /* 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) { new.modifierMask |= Mod1Mask; } else if (strcmp((char *)attr->name, "Mod2") == 0) { new.modifierMask |= Mod2Mask; } else if (strcmp((char *)attr->name, "Mod3") == 0) { new.modifierMask |= Mod3Mask; } else if (strcmp((char *)attr->name, "Mod4") == 0) { new.modifierMask |= Mod4Mask; } else if (strcmp((char *)attr->name, "Control") == 0) { new.modifierMask |= ControlMask; } else if (strcmp((char *)attr->name, "Shift") == 0) { new.modifierMask |= ShiftMask; } else if (strcmp((char *)attr->name, "Lock") == 0) { new.modifierMask |= LockMask; } else if (strcmp((char *)attr->name, "AltMeta") == 0) { new.modifierAltMeta = 1; } } if (new.stroke != 0 && new.keysym != 0) { /* keysym and stroke are required, everything else is optional */ successful = 1; memcpy(ret, &new, sizeof(struct nxagentSpecialKeystrokeMap)); } return successful; } /* * 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 */ static void parse_keystroke_file(void) { char *filename = NULL; char *homefile = "/.nx/config/keystrokes.cfg"; char *etcfile = "/etc/nxagent/keystrokes.cfg"; if (nxagentX2go) { homefile = "/.x2go/config/keystrokes.cfg"; etcfile = "/etc/x2go/keystrokes.cfg"; } if (nxagentKeystrokeFile != NULL && access(nxagentKeystrokeFile, R_OK) == 0) { filename = strdup(nxagentKeystrokeFile); if (filename == NULL) { fprintf(stderr, "malloc failed"); exit(EXIT_FAILURE); } } else if ((filename = getenv("NXAGENT_KEYSTROKEFILE")) != NULL && access(filename, R_OK) == 0) { filename = strdup(filename); if (filename == NULL) { fprintf(stderr, "malloc failed"); exit(EXIT_FAILURE); } } else { char *homedir = getenv("HOME"); filename = NULL; if (homedir != NULL) { homedir = strdup(homedir); if (homedir == NULL) { fprintf(stderr, "malloc failed"); exit(EXIT_FAILURE); } filename = calloc(1, strlen(homefile) + strlen(homedir) + 1); if (filename == NULL) { fprintf(stderr, "malloc failed"); exit(EXIT_FAILURE); } strcpy(filename, homedir); strcpy(filename + strlen(homedir), homefile); if (homedir) { free(homedir); } } if (access(filename, R_OK) == 0) { /* empty */ } else if (access(etcfile, R_OK) == 0) { if (filename) free(filename); filename = strdup(etcfile); if (filename == NULL) { fprintf(stderr, "malloc failed"); exit(EXIT_FAILURE); } } else { if (filename) free(filename); filename = NULL; } } /* now we know which file to read, if any */ if (filename) { xmlDoc *doc = NULL; xmlNode *root = NULL; LIBXML_TEST_VERSION doc = xmlReadFile(filename, NULL, 0); if (doc != NULL) { xmlNode *cur = NULL; root = xmlDocGetRootElement(doc); for (cur = root; cur; cur = cur->next) { if (cur->type == XML_ELEMENT_NODE && strcmp((char *)cur->name, "keystrokes") == 0) { xmlNode *bindings = NULL; 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++; } } map = calloc((num + 1), sizeof(struct nxagentSpecialKeystrokeMap)); if (map == NULL) { fprintf(stderr, "malloc failed"); exit(EXIT_FAILURE); } for (bindings = cur->children; bindings; bindings = bindings->next) { if (bindings->type == XML_ELEMENT_NODE && strcmp((char *)bindings->name, "keystroke") == 0) { int res = 0; res = read_binding_from_xmlnode(bindings, &(map[idx])); if (res) idx++; } } map[idx].stroke = KEYSTROKE_END_MARKER; } } xmlFreeDoc(doc); xmlCleanupParser(); } else { #ifdef DEBUG fprintf("XML parsing for %s failed\n", filename); #endif } free(filename); } } static enum nxagentSpecialKeystroke find_keystroke(XKeyEvent *X) { KeySym keysym = XKeycodeToKeysym(nxagentDisplay, X->keycode, 0); struct nxagentSpecialKeystrokeMap *cur = map; if (! nxagentKeystrokeFileParsed) { parse_keystroke_file(); nxagentKeystrokeFileParsed = True; } enum nxagentSpecialKeystroke ret = KEYSTROKE_NOTHING; while (cur->stroke != KEYSTROKE_END_MARKER) { if (cur->keysym == keysym && modifier_matches(cur->modifierMask, cur->modifierAltMeta, X->state)) { return cur->stroke; } cur++; } return ret; } int nxagentCheckSpecialKeystroke(XKeyEvent *X, enum HandleEventResult *result) { KeySym sym; int index = 0; enum nxagentSpecialKeystroke stroke = find_keystroke(X); *result = doNothing; /* * I don't know how much hard work is doing this operation. * Do we need a cache ? */ sym = XKeycodeToKeysym(nxagentDisplay, X -> keycode, index); if (sym == XK_VoidSymbol || sym == NoSymbol) { return 0; } #ifdef TEST fprintf(stderr, "nxagentCheckSpecialKeystroke: got code %x - state %x - sym %lx\n", X -> keycode, X -> state, sym); #endif /* * Check special keys. */ /* * FIXME: We should use the keysym instead that the keycode * here. */ if (X -> keycode == 130 && nxagentIpaq) { *result = doStartKbd; return 1; } switch (stroke) { case KEYSTROKE_DEBUG_TREE: #ifdef DEBUG_TREE *result = doDebugTree; #endif break; case KEYSTROKE_CLOSE_SESSION: *result = doCloseSession; break; case KEYSTROKE_SWITCH_ALL_SCREENS: if (nxagentOption(Rootless) == False) { *result = doSwitchAllScreens; } break; case KEYSTROKE_MINIMIZE: if (nxagentOption(Rootless) == False) { *result = doMinimize; } break; case KEYSTROKE_LEFT: if (nxagentOption(Rootless) == False && nxagentOption(DesktopResize) == False) { *result = doViewportLeft; } break; case KEYSTROKE_UP: if (nxagentOption(Rootless) == False && nxagentOption(DesktopResize) == False) { *result = doViewportUp; } break; case KEYSTROKE_RIGHT: if (nxagentOption(Rootless) == False && nxagentOption(DesktopResize) == False) { *result = doViewportRight; } break; case KEYSTROKE_DOWN: if (nxagentOption(Rootless) == False && nxagentOption(DesktopResize) == False) { *result = doViewportDown; } break; case KEYSTROKE_RESIZE: if (nxagentOption(Rootless) == False) { *result = doSwitchResizeMode; } 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 1; break; case KEYSTROKE_FORCE_SYNCHRONIZATION: nxagentForceSynchronization = 1; break; case KEYSTROKE_REGIONS_ON_SCREEN: #ifdef DUMP nxagentRegionsOnScreen(); #endif break; case KEYSTROKE_TEST_INPUT: /* * Used to test the input devices state. */ #ifdef NX_DEBUG_INPUT if (X -> type == KeyPress) { if (nxagentDebugInputDevices == 0) { fprintf(stderr, "Info: Turning input devices debug ON.\n"); nxagentDebugInputDevices = 1; } else { fprintf(stderr, "Info: Turning input devices debug OFF.\n"); nxagentDebugInputDevices = 0; nxagentLastInputDevicesDumpTime = 0; } } return 1; #endif break; case KEYSTROKE_DEACTIVATE_INPUT_DEVICES_GRAB: #ifdef NX_DEBUG_INPUT if (X->type == KeyPress) { nxagentDeactivateInputDevicesGrab(); } return 1; #endif break; case KEYSTROKE_FULLSCREEN: if (nxagentOption(Rootless) == 0) { *result = doSwitchFullscreen; } break; case KEYSTROKE_VIEWPORT_MOVE_LEFT: if (nxagentOption(Rootless) == 0 && nxagentOption(DesktopResize) == 0) { *result = doViewportMoveLeft; } break; case KEYSTROKE_VIEWPORT_MOVE_UP: if (nxagentOption(Rootless) == 0 && nxagentOption(DesktopResize) == 0) { *result = doViewportMoveUp; } break; case KEYSTROKE_VIEWPORT_MOVE_RIGHT: if (nxagentOption(Rootless) == 0 && nxagentOption(DesktopResize) == 0) { *result = doViewportMoveRight; } break; case KEYSTROKE_VIEWPORT_MOVE_DOWN: if (nxagentOption(Rootless) == 0 && nxagentOption(DesktopResize) == 0) { *result = doViewportMoveDown; } 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) ? 0 : 1; }