/* * Copyright 2001-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: * David H. Dawes <dawes@xfree86.org> * Kevin E. Martin <kem@redhat.com> * Rickard E. (Rik) Faith <faith@redhat.com> */ /** \file * These routines support taking input from devices on the backend * (output) displays. \see dmxcommon.c. */ #ifdef HAVE_DMX_CONFIG_H #include <dmx-config.h> #endif #define DMX_BACKEND_DEBUG 0 #include "dmxinputinit.h" #include "dmxbackend.h" #include "dmxcommon.h" #include "dmxconsole.h" #include "dmxcursor.h" #include "dmxprop.h" #include "dmxsync.h" #include "dmxcb.h" /* For dmxGlobalWidth and dmxGlobalHeight */ #include "dmxevents.h" /* For dmxGetGlobalPosition */ #include "ChkNotMaskEv.h" #include "inputstr.h" #include "input.h" #include <X11/keysym.h> #include "mipointer.h" #include "scrnintstr.h" #include "windowstr.h" /* Private area for backend devices. */ typedef struct _myPrivate { DMX_COMMON_PRIVATE; int myScreen; DMXScreenInfo *grabbedScreen; int lastX, lastY; int centerX, centerY; int relative; int newscreen; int initialized; DevicePtr mou, kbd; int entered; int offX, offY; } myPrivate; #if DMX_BACKEND_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) #define DMXDBG8(f,a,b,c,d,e,g,h,i) dmxLog(dmxDebug,f,a,b,c,d,e,g,h,i) #define DMXDBG9(f,a,b,c,d,e,g,h,i,j) dmxLog(dmxDebug,f,a,b,c,d,e,g,h,i,j) #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) #define DMXDBG8(f,a,b,c,d,e,g,h,i) #define DMXDBG9(f,a,b,c,d,e,g,h,i,j) #endif /** Create and return a private data structure. */ pointer dmxBackendCreatePrivate(DeviceIntPtr pDevice) { GETDMXLOCALFROMPDEVICE; myPrivate *priv = calloc(1, sizeof(*priv)); priv->dmxLocal = dmxLocal; return priv; } /** Destroy the private data structure. No checking is performed to * verify that the structure was actually created by * #dmxBackendCreatePrivate. */ void dmxBackendDestroyPrivate(pointer private) { free(private); } static void *dmxBackendTestScreen(DMXScreenInfo *dmxScreen, void *closure) { long target = (long)closure; if (dmxScreen->index == target) return dmxScreen; return NULL; } /* Return non-zero if screen and priv->myScreen are on the same physical * backend display (1 if they are the same screen, 2 if they are * different screens). Since this is a common operation, the results * are cached. The cache is invalidated if \a priv is NULL (this should * be done with each server generation and reconfiguration). */ static int dmxBackendSameDisplay(myPrivate *priv, long screen) { static myPrivate *oldpriv = NULL; static int oldscreen = -1; static int retcode = 0; if (priv == oldpriv && screen == oldscreen) return retcode; if (!priv) { /* Invalidate cache */ oldpriv = NULL; oldscreen = -1; retcode = 0; return 0; } if (screen == priv->myScreen) retcode = 1; else if (screen < 0 || screen >= dmxNumScreens) retcode = 0; else if (dmxPropertyIterate(priv->be, dmxBackendTestScreen, (void *)screen)) retcode = 2; else retcode = 0; oldpriv = priv; oldscreen = screen; return retcode; } static void *dmxBackendTestEvents(DMXScreenInfo *dmxScreen, void *closure) { XEvent *X = (XEvent *)closure; if (XCheckNotMaskEvent(dmxScreen->beDisplay, ExposureMask, X)) return dmxScreen; return NULL; } static void *dmxBackendTestMotionEvent(DMXScreenInfo *dmxScreen, void *closure) { XEvent *X = (XEvent *)closure; if (XCheckTypedEvent(dmxScreen->beDisplay, MotionNotify, X)) return dmxScreen; return NULL; } static DMXScreenInfo *dmxBackendGetEvent(myPrivate *priv, XEvent *X) { DMXScreenInfo *dmxScreen; if ((dmxScreen = dmxPropertyIterate(priv->be, dmxBackendTestEvents, X))) return dmxScreen; return NULL; } static DMXScreenInfo *dmxBackendPendingMotionEvent(myPrivate *priv, int save) { DMXScreenInfo *dmxScreen; XEvent N; if ((dmxScreen = dmxPropertyIterate(priv->be, dmxBackendTestMotionEvent, &N))) { if (save) XPutBackEvent(dmxScreen->beDisplay, &N); return dmxScreen; } return NULL; } static void *dmxBackendTestWindow(DMXScreenInfo *dmxScreen, void *closure) { Window win = (Window)(long)closure; if (dmxScreen->scrnWin == win) return dmxScreen; return NULL; } static DMXScreenInfo *dmxBackendFindWindow(myPrivate *priv, Window win) { return dmxPropertyIterate(priv->be, dmxBackendTestWindow, (void *)(long)win); } /* If the cursor is over a set of overlapping screens and one of those * screens takes backend input, then we want that particular screen to * be current, not one of the other ones. */ static int dmxBackendFindOverlapping(myPrivate *priv, int screen, int x, int y) { DMXScreenInfo *start = &dmxScreens[screen]; DMXScreenInfo *pt; if (!start->over) return screen; for (pt = start->over; /* condition at end of loop */; pt = pt->over) { if (pt->index == priv->myScreen && dmxOnScreen(x, y, &dmxScreens[pt->index])) return pt->index; if (pt == start) break; } return screen; } /* Return non-zero if \a x and \a y are off \a screen. */ static int dmxBackendOffscreen(int screen, int x, int y) { DMXScreenInfo *dmxScreen = &dmxScreens[screen]; return (!dmxOnScreen(x, y, dmxScreen)); } /** This routine is called from #dmxCoreMotion for each motion * event. \a x and \a y are global coordinants. */ void dmxBackendUpdatePosition(pointer private, int x, int y) { GETPRIVFROMPRIVATE; int screen = miPointerGetScreen(inputInfo.pointer)->myNum; DMXScreenInfo *dmxScreen = &dmxScreens[priv->myScreen]; int oldRelative = priv->relative; int topscreen = dmxBackendFindOverlapping(priv, screen, x, y); int same = dmxBackendSameDisplay(priv, topscreen); int offscreen = dmxBackendOffscreen(priv->myScreen, x, y); int offthis = dmxBackendOffscreen(screen, x, y); DMXDBG9("dmxBackendUpdatePosition(%d,%d) my=%d mi=%d rel=%d" " topscreen=%d same=%d offscreen=%d offthis=%d\n", x, y, priv->myScreen, screen, priv->relative, topscreen, same, offscreen, offthis); if (offscreen) { /* If the cursor is off the input screen, it should be moving * relative unless it is visible on a screen of the same display * (i.e., one that shares the mouse). */ if (same == 2 && !offthis) { if (priv->relative) { DMXDBG0(" Off screen, but not absolute\n"); priv->relative = 0; } } else { if (!priv->relative) { DMXDBG0(" Off screen, but not relative\n"); priv->relative = 1; } } } else { if (topscreen != screen) { DMXDBG2(" Using screen %d instead of %d (from mi)\n", topscreen, screen); } if (same) { if (priv->relative) { DMXDBG0(" On screen, but not absolute\n"); priv->relative = 0; } } else { if (!priv->relative) { DMXDBG0(" Not on screen, but not relative\n"); priv->relative = 1; } } } if (oldRelative != priv->relative) { DMXDBG2(" Do switch, relative=%d same=%d\n", priv->relative, same); /* Discard all pre-switch events */ dmxSync(dmxScreen, TRUE); while (dmxBackendPendingMotionEvent(priv, FALSE)); if (dmxInput->console && offscreen) { /* Our special case is a console window and a backend window * share a display. In this case, the cursor is either on * the backend window (taking absolute input), or not (in * which case the cursor needs to be in the console * window). */ if (priv->grabbedScreen) { DMXDBG2(" *** force ungrab on %s, display=%p\n", priv->grabbedScreen->name, priv->grabbedScreen->beDisplay); XUngrabPointer(priv->grabbedScreen->beDisplay, CurrentTime); dmxSync(priv->grabbedScreen, TRUE); priv->grabbedScreen = NULL; } DMXDBG0(" Capturing console\n"); dmxConsoleCapture(dmxInput); } else { priv->newscreen = 1; if (priv->relative && !dmxInput->console) { DMXDBG5(" Hide cursor; warp from %d,%d to %d,%d on %d\n", priv->lastX, priv->lastY, priv->centerX, priv->centerY, priv->myScreen); dmxConsoleUncapture(dmxInput); dmxHideCursor(dmxScreen); priv->lastX = priv->centerX; priv->lastY = priv->centerY; XWarpPointer(priv->display, None, priv->window, 0, 0, 0, 0, priv->lastX, priv->lastY); dmxSync(dmxScreen, TRUE); } else { DMXDBG0(" Check cursor\n"); dmxCheckCursor(); } } } } /** Get events from the X queue on the backend servers and put the * events into the DMX event queue. */ void dmxBackendCollectEvents(DevicePtr pDev, dmxMotionProcPtr motion, dmxEnqueueProcPtr enqueue, dmxCheckSpecialProcPtr checkspecial, DMXBlockType block) { GETPRIVFROMPDEV; GETDMXINPUTFROMPRIV; XEvent X; DMXScreenInfo *dmxScreen; int left = 0; int entered = priv->entered; int ignoreLeave = 0; int v[2]; int retcode; while ((dmxScreen = dmxBackendGetEvent(priv, &X))) { switch (X.type) { case EnterNotify: dmxCommonSaveState(priv); if (entered++) continue; priv->entered = 1; ignoreLeave = 1; DMXDBG5("dmxBackendCollectEvents: Enter %lu %d,%d; GRAB %s %p\n", X.xcrossing.root, X.xcrossing.x, X.xcrossing.y, dmxScreen->name, dmxScreen->beDisplay); XRaiseWindow(dmxScreen->beDisplay, dmxScreen->scrnWin); priv->grabbedScreen = dmxScreen; if ((retcode = XGrabPointer(dmxScreen->beDisplay, dmxScreen->scrnWin, True, 0, GrabModeAsync, GrabModeAsync, None, None, CurrentTime))) { dmxLog(dmxError, "XGrabPointer failed during backend enter (%d)\n", retcode); } break; case LeaveNotify: if (ignoreLeave) { ignoreLeave = 0; continue; } dmxCommonRestoreState(priv); if (left++) continue; DMXDBG7("dmxBackendCollectEvents: Leave %lu %d,%d %d %d %s %s\n", X.xcrossing.root, X.xcrossing.x, X.xcrossing.y, X.xcrossing.detail, X.xcrossing.focus, priv->grabbedScreen ? "UNGRAB" : "", dmxScreen->name); if (priv->grabbedScreen) { XUngrabPointer(priv->grabbedScreen->beDisplay, CurrentTime); dmxSync(priv->grabbedScreen, TRUE); priv->grabbedScreen = NULL; } break; case MotionNotify: DMXDBG9("dmxBackendCollectEvents: MotionNotify %d/%d (mi %d)" " newscreen=%d: %d %d (e=%d; last=%d,%d)\n", dmxScreen->index, priv->myScreen, miPointerCurrentScreen()->myNum, priv->newscreen, X.xmotion.x, X.xmotion.y, entered, priv->lastX, priv->lastY); if (dmxBackendPendingMotionEvent(priv, TRUE)) continue; if (!(dmxScreen = dmxBackendFindWindow(priv, X.xmotion.window))) dmxLog(dmxFatal, " Event on non-existant window %lu\n", X.xmotion.window); if (!priv->relative || dmxInput->console) { int newX = X.xmotion.x - dmxScreen->rootX; int newY = X.xmotion.y - dmxScreen->rootY; if (!priv->newscreen) { int width = dmxScreen->rootWidth; int height = dmxScreen->rootHeight; if (!newX) newX = -1; if (newX == width - 1) newX = width; if (!newY) newY = -1; if (newY == height - 1) newY = height; } priv->newscreen = 0; v[0] = dmxScreen->rootXOrigin + newX; v[1] = dmxScreen->rootYOrigin + newY; DMXDBG8(" Absolute move: %d,%d (r=%dx%d+%d+%d s=%dx%d)\n", v[0], v[1], priv->be->rootWidth, priv->be->rootHeight, priv->be->rootX, priv->be->rootY, priv->be->scrnWidth, priv->be->scrnHeight); motion(priv->mou, v, 0, 2, DMX_ABSOLUTE, block); priv->entered = 0; } else { int newX = priv->lastX - X.xmotion.x; int newY = priv->lastY - X.xmotion.y; priv->lastX = X.xmotion.x; priv->lastY = X.xmotion.y; v[0] = newX; v[1] = newY; DMXDBG2(" Relative move: %d, %d\n", v[0], v[1]); motion(priv->mou, v, 0, 2, DMX_RELATIVE, block); } if (entered && priv->relative) { DMXDBG4(" **** Relative %d %d instead of absolute %d %d\n", v[0], v[1], (dmxScreen->rootXOrigin + X.xmotion.x - dmxScreen->rootX), (dmxScreen->rootYOrigin + X.xmotion.y - dmxScreen->rootY)); } break; case KeyPress: case KeyRelease: enqueue(priv->kbd, X.type, X.xkey.keycode, 0, NULL, block); break; case ButtonPress: case ButtonRelease: /* fall-through */ default: /* Pass the whole event here, because * this may be an extension event. */ enqueue(priv->mou, X.type, X.xbutton.button, 0, &X, block); break; } } } /** Called after input events are processed from the DMX queue. No * event processing actually takes place here, but this is a convenient * place to update the pointer. */ void dmxBackendProcessInput(pointer private) { GETPRIVFROMPRIVATE; DMXDBG6("dmxBackendProcessInput: myScreen=%d relative=%d" " last=%d,%d center=%d,%d\n", priv->myScreen, priv->relative, priv->lastX, priv->lastY, priv->centerX, priv->centerY); if (priv->relative && !dmxInput->console && (priv->lastX != priv->centerX || priv->lastY != priv->centerY)) { DMXDBG4(" warping pointer from last=%d,%d to center=%d,%d\n", priv->lastX, priv->lastY, priv->centerX, priv->centerY); priv->lastX = priv->centerX; priv->lastY = priv->centerY; XWarpPointer(priv->display, None, priv->window, 0, 0, 0, 0, priv->lastX, priv->lastY); dmxSync(&dmxScreens[priv->myScreen], TRUE); } } static void dmxBackendComputeCenter(myPrivate *priv) { int centerX; int centerY; centerX = priv->be->rootWidth / 2 + priv->be->rootX; centerY = priv->be->rootHeight / 2 + priv->be->rootY; if (centerX > priv->be->rootWidth) centerX = priv->be->rootWidth - 1; if (centerY > priv->be->rootHeight) centerY = priv->be->rootHeight - 1; if (centerX < 1) centerX = 1; if (centerY < 1) centerY = 1; priv->centerX = centerX; priv->centerY = centerY; } static DMXScreenInfo *dmxBackendInitPrivate(DevicePtr pDev) { GETPRIVFROMPDEV; DMXInputInfo *dmxInput = &dmxInputs[dmxLocal->inputIdx]; DMXScreenInfo *dmxScreen; int i; /* Fill in myPrivate */ for (i = 0,dmxScreen = &dmxScreens[0]; i<dmxNumScreens; i++,dmxScreen++) { if (dmxPropertySameDisplay(dmxScreen, dmxInput->name)) { priv->display = dmxScreen->beDisplay; priv->window = dmxScreen->scrnWin; priv->be = dmxScreen; break; } } if (i >= dmxNumScreens) dmxLog(dmxFatal, "%s is not an existing backend display - cannot initialize\n", dmxInput->name); return dmxScreen; } /** Re-initialized the backend device described by \a pDev (after a * reconfig). */ void dmxBackendLateReInit(DevicePtr pDev) { GETPRIVFROMPDEV; int x, y; DMXDBG1("dmxBackendLateReInit miPointerCurrentScreen() = %p\n", miPointerCurrentScreen()); dmxBackendSameDisplay(NULL, 0); /* Invalidate cache */ dmxBackendInitPrivate(pDev); dmxBackendComputeCenter(priv); dmxGetGlobalPosition(&x, &y); dmxInvalidateGlobalPosition(); /* To force event processing */ dmxBackendUpdatePosition(priv, x, y); } /** Initialized the backend device described by \a pDev. */ void dmxBackendInit(DevicePtr pDev) { GETPRIVFROMPDEV; DMXScreenInfo *dmxScreen; dmxBackendSameDisplay(NULL, 0); /* Invalidate cache */ if (dmxLocal->type == DMX_LOCAL_MOUSE) priv->mou = pDev; if (dmxLocal->type == DMX_LOCAL_KEYBOARD) priv->kbd = pDev; if (priv->initialized++) return; /* Only do once for mouse/keyboard pair */ dmxScreen = dmxBackendInitPrivate(pDev); /* Finish initialization using computed values or constants. */ dmxBackendComputeCenter(priv); priv->eventMask = (EnterWindowMask|LeaveWindowMask); priv->myScreen = dmxScreen->index; priv->lastX = priv->centerX; priv->lastY = priv->centerY; priv->relative = 0; priv->newscreen = 0; } /** Get information about the backend pointer (for initialization). */ void dmxBackendMouGetInfo(DevicePtr pDev, DMXLocalInitInfoPtr info) { const DMXScreenInfo *dmxScreen = dmxBackendInitPrivate(pDev); info->buttonClass = 1; dmxCommonMouGetMap(pDev, info->map, &info->numButtons); info->valuatorClass = 1; info->numRelAxes = 2; info->minval[0] = 0; info->minval[1] = 0; info->maxval[0] = dmxScreen->beWidth; info->maxval[1] = dmxScreen->beHeight; info->res[0] = 1; info->minres[0] = 0; info->maxres[0] = 1; info->ptrFeedbackClass = 1; } /** Get information about the backend keyboard (for initialization). */ void dmxBackendKbdGetInfo(DevicePtr pDev, DMXLocalInitInfoPtr info) { dmxCommonKbdGetInfo(pDev, info); info->keyboard = 1; info->keyClass = 1; dmxCommonKbdGetMap(pDev, &info->keySyms, info->modMap); info->freemap = 1; info->focusClass = 1; info->kbdFeedbackClass = 1; } /** Process #DMXFunctionType functions. The only function handled here * is to acknowledge a pending server shutdown. */ int dmxBackendFunctions(pointer private, DMXFunctionType function) { switch (function) { case DMX_FUNCTION_TERMINATE: return 1; default: return 0; } }