/* * Copyright 2002-2003 Red Hat Inc., Durham, North Carolina. * * All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation on the rights to use, copy, modify, merge, * publish, distribute, sublicense, and/or sell copies of the Software, * and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NON-INFRINGEMENT. IN NO EVENT SHALL RED HAT AND/OR THEIR SUPPLIERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ /* * Authors: * Rickard E. (Rik) Faith * */ /** \file * Provide support and helper functions for enqueuing events received by * the low-level input drivers. */ #ifdef HAVE_DMX_CONFIG_H #include #endif #define DMX_EVENTS_DEBUG 0 #include "dmxinputinit.h" #include "dmxevents.h" #include "dmxcb.h" #include "dmxcommon.h" #include "dmxcursor.h" #include "dmxmotion.h" #include "dmxmap.h" #include #include "opaque.h" #include "inputstr.h" #include "inpututils.h" #include "mipointer.h" #include "mi.h" #include "exglobals.h" #include "xkbsrv.h" #include "XIstubs.h" static int dmxGlobalX, dmxGlobalY; /* Global cursor position */ static int dmxGlobalInvalid; /* Flag indicating dmxCoreMotion * should move the mouse anyway. */ #if DMX_EVENTS_DEBUG #define DMXDBG0(f) dmxLog(dmxDebug,f) #define DMXDBG1(f,a) dmxLog(dmxDebug,f,a) #define DMXDBG2(f,a,b) dmxLog(dmxDebug,f,a,b) #define DMXDBG3(f,a,b,c) dmxLog(dmxDebug,f,a,b,c) #define DMXDBG4(f,a,b,c,d) dmxLog(dmxDebug,f,a,b,c,d) #define DMXDBG5(f,a,b,c,d,e) dmxLog(dmxDebug,f,a,b,c,d,e) #define DMXDBG6(f,a,b,c,d,e,g) dmxLog(dmxDebug,f,a,b,c,d,e,g) #define DMXDBG7(f,a,b,c,d,e,g,h) dmxLog(dmxDebug,f,a,b,c,d,e,g,h) #else #define DMXDBG0(f) #define DMXDBG1(f,a) #define DMXDBG2(f,a,b) #define DMXDBG3(f,a,b,c) #define DMXDBG4(f,a,b,c,d) #define DMXDBG5(f,a,b,c,d,e) #define DMXDBG6(f,a,b,c,d,e,g) #define DMXDBG7(f,a,b,c,d,e,g,h) #endif static int dmxApplyFunctions(DMXInputInfo * dmxInput, DMXFunctionType f) { int i; int rc = 0; for (i = 0; i < dmxInput->numDevs; i += dmxInput->devs[i]->binding) if (dmxInput->devs[i]->functions) rc += dmxInput->devs[i]->functions(dmxInput->devs[i]->private, f); return rc; } static int dmxCheckFunctionKeys(DMXLocalInputInfoPtr dmxLocal, int type, KeySym keySym) { DMXInputInfo *dmxInput = &dmxInputs[dmxLocal->inputIdx]; #if 1 /* hack to detect ctrl-alt-q, etc */ static int ctrl = 0, alt = 0; /* keep track of ctrl/alt key status */ if (type == KeyPress && keySym == 0xffe3) { ctrl = 1; } else if (type == KeyRelease && keySym == 0xffe3) { ctrl = 0; } else if (type == KeyPress && keySym == 0xffe9) { alt = 1; } else if (type == KeyRelease && keySym == 0xffe9) { alt = 0; } if (!ctrl || !alt) return 0; #else unsigned short state = 0; if (dmxLocal->sendsCore) state = dmxLocalCoreKeyboard->pDevice->key->state; else if (dmxLocal->pDevice->key) state = dmxLocal->pDevice->key->state; DMXDBG3("dmxCheckFunctionKeys: keySym=0x%04x %s state=0x%04x\n", keySym, type == KeyPress ? "press" : "release", state); if ((state & (ControlMask | Mod1Mask)) != (ControlMask | Mod1Mask)) return 0; #endif switch (keySym) { case XK_g: if (type == KeyPress) dmxApplyFunctions(dmxInput, DMX_FUNCTION_GRAB); return 1; case XK_f: if (type == KeyPress) dmxApplyFunctions(dmxInput, DMX_FUNCTION_FINE); return 1; case XK_q: if (type == KeyPress && dmxLocal->sendsCore) if (dmxApplyFunctions(dmxInput, DMX_FUNCTION_TERMINATE)) { dmxLog(dmxInfo, "User request for termination\n"); dispatchException |= DE_TERMINATE; } return 1; } return 0; } DMXScreenInfo * dmxFindFirstScreen(int x, int y) { int i; for (i = 0; i < dmxNumScreens; i++) { DMXScreenInfo *dmxScreen = &dmxScreens[i]; if (dmxOnScreen(x, y, dmxScreen)) return dmxScreen; } return NULL; } /** * Enqueue a motion event. */ static void enqueueMotion(DevicePtr pDev, int x, int y) { GETDMXLOCALFROMPDEV; DeviceIntPtr p = dmxLocal->pDevice; int valuators[3]; int detail = 0; /* XXX should this be mask of pressed buttons? */ ValuatorMask mask; valuators[0] = x; valuators[1] = y; valuator_mask_set_range(&mask, 0, 2, valuators); QueuePointerEvents(p, MotionNotify, detail, POINTER_ABSOLUTE | POINTER_SCREEN, &mask); return; } void dmxCoreMotion(DevicePtr pDev, int x, int y, int delta, DMXBlockType block) { DMXScreenInfo *dmxScreen; DMXInputInfo *dmxInput; ScreenPtr pScreen; int localX; int localY; int i; if (!dmxGlobalInvalid && dmxGlobalX == x && dmxGlobalY == y) return; DMXDBG5("dmxCoreMotion(%d,%d,%d) dmxGlobalX=%d dmxGlobalY=%d\n", x, y, delta, dmxGlobalX, dmxGlobalY); dmxGlobalInvalid = 0; dmxGlobalX = x; dmxGlobalY = y; if (dmxGlobalX < 0) dmxGlobalX = 0; if (dmxGlobalY < 0) dmxGlobalY = 0; if (dmxGlobalX >= dmxGlobalWidth) dmxGlobalX = dmxGlobalWidth + delta - 1; if (dmxGlobalY >= dmxGlobalHeight) dmxGlobalY = dmxGlobalHeight + delta - 1; if ((dmxScreen = dmxFindFirstScreen(dmxGlobalX, dmxGlobalY))) { localX = dmxGlobalX - dmxScreen->rootXOrigin; localY = dmxGlobalY - dmxScreen->rootYOrigin; if ((pScreen = miPointerGetScreen(inputInfo.pointer)) && pScreen->myNum == dmxScreen->index) { /* Screen is old screen */ if (block) input_lock(); if (pDev) enqueueMotion(pDev, localX, localY); if (block) input_unlock(); } else { /* Screen is new */ DMXDBG4(" New screen: old=%d new=%d localX=%d localY=%d\n", pScreen->myNum, dmxScreen->index, localX, localY); if (block) input_lock(); mieqProcessInputEvents(); miPointerSetScreen(inputInfo.pointer, dmxScreen->index, localX, localY); if (pDev) enqueueMotion(pDev, localX, localY); if (block) input_unlock(); } #if 00 miPointerGetPosition(inputInfo.pointer, &localX, &localY); if ((pScreen = miPointerGetScreen(inputInfo.pointer))) { dmxGlobalX = localX + dmxScreens[pScreen->myNum].rootXOrigin; dmxGlobalY = localY + dmxScreens[pScreen->myNum].rootYOrigin; ErrorF("Global is now %d, %d %d, %d\n", dmxGlobalX, dmxGlobalY, localX, localY); DMXDBG6(" Moved to dmxGlobalX=%d dmxGlobalY=%d" " on screen index=%d/%d localX=%d localY=%d\n", dmxGlobalX, dmxGlobalY, dmxScreen ? dmxScreen->index : -1, pScreen->myNum, localX, localY); } #endif } /* Send updates down to all core input * drivers */ for (i = 0, dmxInput = &dmxInputs[0]; i < dmxNumInputs; i++, dmxInput++) { int j; for (j = 0; j < dmxInput->numDevs; j += dmxInput->devs[j]->binding) if (!dmxInput->detached && dmxInput->devs[j]->sendsCore && dmxInput->devs[j]->update_position) dmxInput->devs[j]->update_position(dmxInput->devs[j]->private, dmxGlobalX, dmxGlobalY); } if (!dmxScreen) ProcessInputEvents(); } #define DMX_MAX_AXES 32 /* Max axes reported by this routine */ static void dmxExtMotion(DMXLocalInputInfoPtr dmxLocal, int *v, int firstAxis, int axesCount, DMXMotionType type, DMXBlockType block) { DeviceIntPtr pDevice = dmxLocal->pDevice; xEvent xE[2 * DMX_MAX_AXES / 6]; deviceKeyButtonPointer *xev = (deviceKeyButtonPointer *) xE; deviceValuator *xv = (deviceValuator *) xev + 1; int thisX = 0; int thisY = 0; int count; ValuatorMask mask; memset(xE, 0, sizeof(xE)); if (axesCount > DMX_MAX_AXES) axesCount = DMX_MAX_AXES; if ((valuator_get_mode(pDevice, 0) == Relative) && axesCount == 2) { /* The dmx console is a relative mode * device that sometimes reports * absolute motion. It only has two * axes. */ if (type == DMX_RELATIVE) { thisX = -v[0]; thisY = -v[1]; dmxLocal->lastX += thisX; dmxLocal->lastY += thisY; if (dmxLocal->update_position) dmxLocal->update_position(dmxLocal->private, dmxLocal->lastX, dmxLocal->lastY); } else { /* Convert to relative */ if (dmxLocal->lastX || dmxLocal->lastY) { thisX = v[0] - dmxLocal->lastX; thisY = v[1] - dmxLocal->lastY; } dmxLocal->lastX = v[0]; dmxLocal->lastY = v[1]; } v[0] = thisX; v[1] = thisY; } if (axesCount <= 6) { /* Optimize for the common case when * only 1 or 2 axes change. */ xev->time = GetTimeInMillis(); xev->type = DeviceMotionNotify; xev->detail = 0; xev->deviceid = pDevice->id | MORE_EVENTS; xv->type = DeviceValuator; xv->deviceid = pDevice->id; xv->num_valuators = axesCount; xv->first_valuator = firstAxis; switch (xv->num_valuators) { case 6: xv->valuator5 = v[5]; case 5: xv->valuator4 = v[4]; case 4: xv->valuator3 = v[3]; case 3: xv->valuator2 = v[2]; case 2: xv->valuator1 = v[1]; case 1: xv->valuator0 = v[0]; } count = 2; } else { int i; for (i = 0, count = 0; i < axesCount; i += 6) { xev->time = GetTimeInMillis(); xev->type = DeviceMotionNotify; xev->detail = 0; xev->deviceid = pDevice->id | MORE_EVENTS; xev += 2; xv->type = DeviceValuator; xv->deviceid = pDevice->id; xv->num_valuators = (i + 6 >= axesCount ? axesCount - i : 6); xv->first_valuator = firstAxis + i; switch (xv->num_valuators) { case 6: xv->valuator5 = v[i + 5]; case 5: xv->valuator4 = v[i + 4]; case 4: xv->valuator3 = v[i + 3]; case 3: xv->valuator2 = v[i + 2]; case 2: xv->valuator1 = v[i + 1]; case 1: xv->valuator0 = v[i + 0]; } xv += 2; count += 2; } } if (block) input_lock(); valuator_mask_set_range(&mask, firstAxis, axesCount, v); QueuePointerEvents(pDevice, MotionNotify, 0, POINTER_ABSOLUTE, &mask); if (block) input_unlock(); } static int dmxTranslateAndEnqueueExtEvent(DMXLocalInputInfoPtr dmxLocal, XEvent * e, DMXBlockType block) { int type; int event = -1; XDeviceKeyEvent *ke = (XDeviceKeyEvent *) e; XDeviceMotionEvent *me = (XDeviceMotionEvent *) e; DeviceIntPtr pDevice = dmxLocal->pDevice; int valuators[MAX_VALUATORS]; ValuatorMask mask; if (!e) return -1; /* No extended event passed, cannot handle */ if ((XID) dmxLocal->deviceId != ke->deviceid) { /* Search for the correct dmxLocal, * since backend and console events are * picked up for the first device on * that X server. */ int i; DMXInputInfo *dmxInput = &dmxInputs[dmxLocal->inputIdx]; for (i = 0; i < dmxInput->numDevs; i++) { dmxLocal = dmxInput->devs[i]; if ((XID) dmxLocal->deviceId == ke->deviceid) break; } } if ((XID) dmxLocal->deviceId != ke->deviceid || (type = dmxMapLookup(dmxLocal, e->type)) < 0) return -1; /* No mapping, so this event is unhandled */ switch (type) { case XI_DeviceValuator: event = DeviceValuator; break; case XI_DeviceKeyPress: event = KeyPress; break; case XI_DeviceKeyRelease: event = KeyRelease; break; case XI_DeviceButtonPress: event = ButtonPress; break; case XI_DeviceButtonRelease: event = ButtonRelease; break; case XI_DeviceMotionNotify: event = MotionNotify; break; case XI_DeviceFocusIn: event = DeviceFocusIn; break; case XI_DeviceFocusOut: event = DeviceFocusOut; break; case XI_ProximityIn: event = ProximityIn; break; case XI_ProximityOut: event = ProximityOut; break; case XI_DeviceStateNotify: event = DeviceStateNotify; break; case XI_DeviceMappingNotify: event = DeviceMappingNotify; break; case XI_ChangeDeviceNotify: event = ChangeDeviceNotify; break; case XI_DeviceKeystateNotify: event = DeviceStateNotify; break; case XI_DeviceButtonstateNotify: event = DeviceStateNotify; break; } #define EXTRACT_VALUATORS(ke, valuators) \ valuators[0] = ke->axis_data[0]; \ valuators[1] = ke->axis_data[1]; \ valuators[2] = ke->axis_data[2]; \ valuators[3] = ke->axis_data[3]; \ valuators[4] = ke->axis_data[4]; \ valuators[5] = ke->axis_data[5]; \ switch (type) { case XI_DeviceKeyPress: case XI_DeviceKeyRelease: if (block) input_lock(); QueueKeyboardEvents(pDevice, event, ke->keycode); if (block) input_unlock(); break; case XI_DeviceButtonPress: case XI_DeviceButtonRelease: EXTRACT_VALUATORS(ke, valuators); valuator_mask_set_range(&mask, ke->first_axis, ke->axes_count, valuators); if (block) input_lock(); QueuePointerEvents(pDevice, event, ke->keycode, POINTER_ABSOLUTE, &mask); if (block) input_unlock(); break; case XI_ProximityIn: case XI_ProximityOut: EXTRACT_VALUATORS(ke, valuators); valuator_mask_set_range(&mask, ke->first_axis, ke->axes_count, valuators); if (block) input_lock(); QueueProximityEvents(pDevice, event, &mask); if (block) input_unlock(); break; break; case XI_DeviceMotionNotify: dmxExtMotion(dmxLocal, me->axis_data, me->first_axis, me->axes_count, DMX_ABSOLUTE, block); break; case XI_DeviceFocusIn: case XI_DeviceFocusOut: case XI_DeviceStateNotify: case XI_DeviceMappingNotify: case XI_ChangeDeviceNotify: case XI_DeviceKeystateNotify: case XI_DeviceButtonstateNotify: /* These are ignored, since DMX will * generate its own events of these * types, as necessary. * Perhaps ChangeDeviceNotify should * generate an error, because it is * unexpected? */ break; case XI_DeviceValuator: default: dmxLog(dmxWarning, "XInput extension event (remote=%d -> zero-based=%d)" " not supported yet\n", e->type, type); return -1; } return 0; } static int dmxGetButtonMapping(DMXLocalInputInfoPtr dmxLocal, int button) { ButtonClassPtr b = dmxLocal->pDevice->button; if (button > b->numButtons) { /* This shouldn't happen. */ dmxLog(dmxWarning, "Button %d pressed, but only %d buttons?!?\n", button, b->numButtons); return button; } return b->map[button]; } /** Return DMX's notion of the pointer position in the global coordinate * space. */ void dmxGetGlobalPosition(int *x, int *y) { *x = dmxGlobalX; *y = dmxGlobalY; } /** Invalidate the global position for #dmxCoreMotion. */ void dmxInvalidateGlobalPosition(void) { dmxGlobalInvalid = 1; } /** Enqueue a motion event for \a pDev. The \a v vector has length \a * axesCount, and contains values for each of the axes, starting at \a * firstAxes. * * The \a type of the motion may be \a DMX_RELATIVE, \a DMX_ABSOLUTE, or * \a DMX_ABSOLUTE_CONFINED (in the latter case, the pointer will not be * allowed to move outside the global boundaires). * * If \a block is set to \a DMX_BLOCK, then the input thread will be * blocked around calls to \a enqueueMotion(). */ void dmxMotion(DevicePtr pDev, int *v, int firstAxes, int axesCount, DMXMotionType type, DMXBlockType block) { GETDMXLOCALFROMPDEV; if (!dmxLocal->sendsCore) { dmxExtMotion(dmxLocal, v, firstAxes, axesCount, type, block); return; } if (axesCount == 2) { switch (type) { case DMX_RELATIVE: dmxCoreMotion(pDev, dmxGlobalX - v[0], dmxGlobalY - v[1], 0, block); break; case DMX_ABSOLUTE: dmxCoreMotion(pDev, v[0], v[1], 0, block); break; case DMX_ABSOLUTE_CONFINED: dmxCoreMotion(pDev, v[0], v[1], -1, block); break; } } } static KeySym dmxKeyCodeToKeySym(DMXLocalInputInfoPtr dmxLocal, KeyCode keyCode) { KeySym keysym = NoSymbol; int effectiveGroup; XkbSrvInfoPtr xkbi; if (!dmxLocal || !dmxLocal->pDevice || !dmxLocal->pDevice->key) goto out; xkbi = dmxLocal->pDevice->key->xkbInfo; effectiveGroup = XkbGetEffectiveGroup(xkbi, &xkbi->state, keyCode); if (effectiveGroup == -1) goto out; keysym = XkbKeySym(xkbi->desc, keyCode, effectiveGroup); DMXDBG2("dmxKeyCodeToKeySym: Translated keyCode=%d to keySym=0x%04x\n", keyCode, keysym); out: return keysym; } static KeyCode dmxKeySymToKeyCode(DMXLocalInputInfoPtr dmxLocal, KeySym keySym, int tryFirst) { /* FIXME: this is quite ineffective, converting to a core map first and * then extracting the info from there. It'd be better to run the actual * xkb map */ XkbSrvInfoPtr xkbi = dmxLocal->pDevice->key->xkbInfo; KeySymsPtr pKeySyms = XkbGetCoreMap(dmxLocal->pDevice); int i; /* Optimize for similar maps */ if (XkbKeycodeInRange(xkbi->desc, tryFirst) && pKeySyms->map[(tryFirst - xkbi->desc->min_key_code) * pKeySyms->mapWidth] == keySym) return tryFirst; for (i = pKeySyms->minKeyCode; i <= pKeySyms->maxKeyCode; i++) { if (pKeySyms->map[(i - pKeySyms->minKeyCode) * pKeySyms->mapWidth] == keySym) { DMXDBG3("dmxKeySymToKeyCode: Translated keySym=0x%04x to" " keyCode=%d (reverses to core keySym=0x%04x)\n", keySym, i, dmxKeyCodeToKeySym(dmxLocalCoreKeyboard, i)); return i; } } return 0; } static int dmxFixup(DevicePtr pDev, int detail, KeySym keySym) { GETDMXLOCALFROMPDEV; int keyCode; if (!dmxLocal->pDevice->key) { dmxLog(dmxWarning, "dmxFixup: not a keyboard device (%s)\n", dmxLocal->pDevice->name); return NoSymbol; } if (!keySym) keySym = dmxKeyCodeToKeySym(dmxLocal, detail); if (keySym == NoSymbol) return detail; keyCode = dmxKeySymToKeyCode(dmxLocalCoreKeyboard, keySym, detail); return keyCode ? keyCode : detail; } /** Enqueue an event from the \a pDev device with the * specified \a type and \a detail. If the event is a KeyPress or * KeyRelease event, then the \a keySym is also specified. * * FIXME: make the code do what the comment says, or remove this comment. * If \a block is set to \a DMX_BLOCK, then the input thread will be * blocked around calls to dmxeqEnqueue(). */ void dmxEnqueue(DevicePtr pDev, int type, int detail, KeySym keySym, XEvent * e, DMXBlockType block) { GETDMXINPUTFROMPDEV; DeviceIntPtr p = dmxLocal->pDevice; int valuators[3]; ValuatorMask mask; DMXDBG2("dmxEnqueue: Enqueuing type=%d detail=0x%0x\n", type, detail); switch (type) { case KeyPress: case KeyRelease: if (!keySym) keySym = dmxKeyCodeToKeySym(dmxLocal, detail); if (dmxCheckFunctionKeys(dmxLocal, type, keySym)) return; if (dmxLocal->sendsCore && dmxLocal != dmxLocalCoreKeyboard) detail = dmxFixup(pDev, detail, keySym); /*ErrorF("KEY %d sym %d\n", detail, (int) keySym); */ QueueKeyboardEvents(p, type, detail); return; case ButtonPress: case ButtonRelease: detail = dmxGetButtonMapping(dmxLocal, detail); valuator_mask_zero(&mask); QueuePointerEvents(p, type, detail, 0, &mask); return; case MotionNotify: valuators[0] = e->xmotion.x; valuators[1] = e->xmotion.y; valuators[2] = e->xmotion.state; /* FIXME: WTF?? */ valuator_mask_set_range(&mask, 0, 3, valuators); QueuePointerEvents(p, type, detail, POINTER_ABSOLUTE | POINTER_SCREEN, &mask); return; case EnterNotify: case LeaveNotify: case KeymapNotify: case MappingNotify: /* This is sent because we change the * modifier map on the backend/console * input device so that we have complete * control of the input device LEDs. */ return; default: if (type == ProximityIn || type == ProximityOut) { if (dmxLocal->sendsCore) return; /* Not a core event */ break; } if (type >= LASTEvent) { if (dmxTranslateAndEnqueueExtEvent(dmxLocal, e, block)) dmxLogInput(dmxInput, "Unhandled extension event: %d\n", type); } else { dmxLogInput(dmxInput, "Unhandled event: %d (%s)\n", type, dmxEventName(type)); } return; } } /** A pointer to this routine is passed to low-level input drivers so * that all special keychecking is unified to this file. This function * returns 0 if no special keys have been pressed. If the user has * requested termination of the DMX server, -1 is returned. If the user * has requested a switch to a VT, then the (1-based) number of that VT * is returned. */ int dmxCheckSpecialKeys(DevicePtr pDev, KeySym keySym) { GETDMXINPUTFROMPDEV; int vt = 0; unsigned short state = 0; if (dmxLocal->sendsCore) state = XkbStateFieldFromRec(&dmxLocalCoreKeyboard->pDevice->key->xkbInfo-> state); else if (dmxLocal->pDevice->key) state = XkbStateFieldFromRec(&dmxLocal->pDevice->key->xkbInfo->state); if (!dmxLocal->sendsCore) return 0; /* Only for core devices */ DMXDBG2("dmxCheckSpecialKeys: keySym=0x%04x state=0x%04x\n", keySym, state); if ((state & (ControlMask | Mod1Mask)) != (ControlMask | Mod1Mask)) return 0; switch (keySym) { case XK_F1: case XK_F2: case XK_F3: case XK_F4: case XK_F5: case XK_F6: case XK_F7: case XK_F8: case XK_F9: case XK_F10: vt = keySym - XK_F1 + 1; break; case XK_F11: case XK_F12: vt = keySym - XK_F11 + 11; break; case XK_q: /* To avoid confusion */ case XK_BackSpace: case XK_Delete: case XK_KP_Delete: dmxLog(dmxInfo, "User request for termination\n"); dispatchException |= DE_TERMINATE; return -1; /* Terminate */ } if (vt) { dmxLog(dmxInfo, "Request to switch to VT %d\n", vt); dmxInput->vt_switch_pending = vt; return vt; } return 0; /* Do nothing */ }