dmxconsole.c revision 706f2543
1/*
2 * Copyright 2001-2003 Red Hat Inc., Durham, North Carolina.
3 *
4 * All Rights Reserved.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining
7 * a copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation on the rights to use, copy, modify, merge,
10 * publish, distribute, sublicense, and/or sell copies of the Software,
11 * and to permit persons to whom the Software is furnished to do so,
12 * subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice (including the
15 * next paragraph) shall be included in all copies or substantial
16 * portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 * NON-INFRINGEMENT.  IN NO EVENT SHALL RED HAT AND/OR THEIR SUPPLIERS
22 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
23 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
24 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25 * SOFTWARE.
26 */
27
28/*
29 * Authors:
30 *   David H. Dawes <dawes@xfree86.org>
31 *   Kevin E. Martin <kem@redhat.com>
32 *   Rickard E. (Rik) Faith <faith@redhat.com>
33 *
34 */
35
36/** \file
37 *
38 * This file implements the console input devices.
39 */
40
41#ifdef HAVE_DMX_CONFIG_H
42#include <dmx-config.h>
43#endif
44
45#define DMX_CONSOLE_DEBUG 0
46#define DMX_WINDOW_DEBUG  0
47
48#include "dmxinputinit.h"
49#include "dmxevents.h"
50#include "dmxconsole.h"
51#include "dmxcommon.h"
52#include "dmxscrinit.h"
53#include "dmxcb.h"
54#include "dmxsync.h"
55
56#include "inputstr.h"
57#include "input.h"
58#include "mipointer.h"
59#include "windowstr.h"
60
61#define CONSOLE_NUM 3
62#define CONSOLE_DEN 4
63#define DMX_CONSOLE_NAME "DMX Console"
64#define DMX_RES_NAME     "Xdmx"
65#define DMX_RES_CLASS    "XDmx"
66#define CONSOLE_BG_COLOR "gray75"
67#define CONSOLE_FG_COLOR "black"
68#define CONSOLE_SCREEN_BG_COLOR "white"
69#define CONSOLE_SCREEN_FG_COLOR "black"
70#define CONSOLE_SCREEN_DET_COLOR "gray75"
71#define CONSOLE_SCREEN_CUR_COLOR "red"
72
73#if DMX_CONSOLE_DEBUG
74#define DMXDBG0(f)               dmxLog(dmxDebug,f)
75#define DMXDBG1(f,a)             dmxLog(dmxDebug,f,a)
76#define DMXDBG2(f,a,b)           dmxLog(dmxDebug,f,a,b)
77#define DMXDBG3(f,a,b,c)         dmxLog(dmxDebug,f,a,b,c)
78#define DMXDBG4(f,a,b,c,d)       dmxLog(dmxDebug,f,a,b,c,d)
79#define DMXDBG5(f,a,b,c,d,e)     dmxLog(dmxDebug,f,a,b,c,d,e)
80#define DMXDBG6(f,a,b,c,d,e,g)   dmxLog(dmxDebug,f,a,b,c,d,e,g)
81#define DMXDBG7(f,a,b,c,d,e,g,h) dmxLog(dmxDebug,f,a,b,c,d,e,g,h)
82#else
83#define DMXDBG0(f)
84#define DMXDBG1(f,a)
85#define DMXDBG2(f,a,b)
86#define DMXDBG3(f,a,b,c)
87#define DMXDBG4(f,a,b,c,d)
88#define DMXDBG5(f,a,b,c,d,e)
89#define DMXDBG6(f,a,b,c,d,e,g)
90#define DMXDBG7(f,a,b,c,d,e,g,h)
91#endif
92
93/* Private area for consoles. */
94typedef struct _myPrivate {
95    DMX_COMMON_PRIVATE;
96    int                     lastX;
97    int                     lastY;
98    int                     globalX;
99    int                     globalY;
100    int                     curX;
101    int                     curY;
102    int                     width;
103    int                     height;
104    int                     consWidth;
105    int                     consHeight;
106    double                  xScale;
107    double                  yScale;
108    XlibGC                  gc, gcDet, gcRev, gcCur;
109    int                     grabbed, fine, captured;
110    Cursor                  cursorNormal, cursorGrabbed, cursorEmpty;
111    Pixmap                  pixmap;
112
113    CloseScreenProcPtr      CloseScreen;
114    struct _myPrivate       *next; /* for closing multiple consoles */
115    int                     initialized;
116    DevicePtr               mou, kbd;
117} myPrivate;
118
119static int scalex(myPrivate *priv, int x)
120{
121    return (int)((x * priv->xScale) + .5);
122}
123
124static int scaley(myPrivate *priv, int y)
125{
126    return (int)((y * priv->yScale) + .5);
127}
128
129static int unscalex(myPrivate *priv, int x)
130{
131    return (int)((x / priv->xScale) + .5);
132}
133
134static int unscaley(myPrivate *priv, int y)
135{
136    return (int)((y / priv->yScale) + .5);
137}
138
139/** Create the private area for \a pDevice. */
140pointer dmxConsoleCreatePrivate(DeviceIntPtr pDevice)
141{
142    GETDMXLOCALFROMPDEVICE;
143    myPrivate *priv = calloc(1, sizeof(*priv));
144    priv->dmxLocal  = dmxLocal;
145    return priv;
146}
147
148/** If \a private is non-NULL, free its associated memory. */
149void dmxConsoleDestroyPrivate(pointer private)
150{
151    free(private);
152}
153
154static void dmxConsoleDrawFineCursor(myPrivate *priv, XRectangle *rect)
155{
156    int size  = 6;
157    int x, y;
158
159    XDrawLine(priv->display, priv->pixmap, priv->gcCur,
160              x = scalex(priv, priv->globalX) - size,
161              scaley(priv, priv->globalY),
162              scalex(priv, priv->globalX) + size,
163              scaley(priv, priv->globalY));
164    XDrawLine(priv->display, priv->pixmap, priv->gcCur,
165              scalex(priv, priv->globalX),
166              y = scaley(priv, priv->globalY) - size,
167              scalex(priv, priv->globalX),
168              scaley(priv, priv->globalY) + size);
169    if (priv->grabbed) {
170        XDrawLine(priv->display, priv->pixmap, priv->gcCur,
171                  scalex(priv, priv->globalX) - (int)(size / 1.4),
172                  scaley(priv, priv->globalY) - (int)(size / 1.4),
173                  scalex(priv, priv->globalX) + (int)(size / 1.4),
174                  scaley(priv, priv->globalY) + (int)(size / 1.4));
175        XDrawLine(priv->display, priv->pixmap, priv->gcCur,
176                  scalex(priv, priv->globalX) - (int)(size / 1.4),
177                  scaley(priv, priv->globalY) + (int)(size / 1.4),
178                  scalex(priv, priv->globalX) + (int)(size / 1.4),
179                  scaley(priv, priv->globalY) - (int)(size / 1.4));
180    }
181    if (rect) {
182        rect->x      = x;
183        rect->y      = y;
184        rect->width  = 2 * size;
185        rect->height = 2 * size;
186    }
187}
188
189static void dmxConsoleDrawWindows(pointer private)
190{
191    GETONLYPRIVFROMPRIVATE;
192    Display    *dpy   = priv->display;
193    int        i;
194    Region     whole, used, avail;
195    XRectangle rect;
196
197    whole       = XCreateRegion();
198    used        = XCreateRegion();
199    avail       = XCreateRegion();
200    rect.x      = 0;
201    rect.y      = 0;
202    rect.width  = priv->consWidth;
203    rect.height = priv->consHeight;
204    XUnionRectWithRegion(&rect, whole, whole);
205
206    for (i = 0; i < dmxNumScreens; i++) {
207        ScreenPtr     pScreen     = screenInfo.screens[i];
208        WindowPtr     pRoot       = pScreen->root;
209        WindowPtr     pChild;
210
211#if DMX_WINDOW_DEBUG
212        dmxLog(dmxDebug, "%lu %p %p %p 2\n",
213               pRoot->drawable.id,
214               pRoot->parent, pRoot->firstChild, pRoot->lastChild);
215#endif
216
217        for (pChild = pRoot->firstChild; pChild; pChild = pChild->nextSib) {
218            if (pChild->mapped
219                && pChild->realized) {
220#if DMX_WINDOW_DEBUG
221                dmxLog(dmxDebug, "  %p %d,%d %dx%d %d %d  %d RECTS\n",
222                       pChild,
223                       pChild->drawable.x,
224                       pChild->drawable.y,
225                       pChild->drawable.width,
226                       pChild->drawable.height,
227                       pChild->visibility,
228                       pChild->overrideRedirect,
229                       RegionNumRects(&pChild->clipList));
230#endif
231                rect.x      = scalex(priv, pChild->drawable.x + pScreen->x);
232                rect.y      = scaley(priv, pChild->drawable.y + pScreen->y);
233                rect.width  = scalex(priv, pChild->drawable.width);
234                rect.height = scaley(priv, pChild->drawable.height);
235                XDrawRectangle(dpy, priv->pixmap, priv->gc,
236                               rect.x, rect.y, rect.width, rect.height);
237                XUnionRectWithRegion(&rect, used, used);
238                XSubtractRegion(whole, used, avail);
239                XSetRegion(dpy, priv->gc, avail);
240            }
241        }
242#ifdef PANORAMIX
243        if (!noPanoramiXExtension) break; /* Screen 0 valid with Xinerama */
244#endif
245    }
246    XDestroyRegion(avail);
247    XDestroyRegion(used);
248    XDestroyRegion(whole);
249    XSetClipMask(dpy, priv->gc, None);
250}
251
252static void dmxConsoleDraw(myPrivate *priv, int updateCursor, int update)
253{
254    GETDMXINPUTFROMPRIV;
255    Display       *dpy     = priv->display;
256    int           i;
257
258    XFillRectangle(dpy, priv->pixmap, priv->gc, 0, 0,
259                   priv->consWidth, priv->consHeight);
260
261    for (i = 0; i < dmxNumScreens; i++) {
262        DMXScreenInfo *dmxScreen = &dmxScreens[i];
263	XFillRectangle(dpy, priv->pixmap,
264                       dmxScreen->beDisplay ? priv->gcRev : priv->gcDet,
265                       scalex(priv, screenInfo.screens[i]->x),
266                       scaley(priv, screenInfo.screens[i]->y),
267                       scalex(priv, screenInfo.screens[i]->width),
268                       scaley(priv, screenInfo.screens[i]->height));
269    }
270    for (i = 0; i < dmxNumScreens; i++) {
271        XDrawRectangle(dpy, priv->pixmap, priv->gc,
272                       scalex(priv, screenInfo.screens[i]->x),
273                       scaley(priv, screenInfo.screens[i]->y),
274                       scalex(priv, screenInfo.screens[i]->width),
275                       scaley(priv, screenInfo.screens[i]->height));
276    }
277    if (dmxInput->windows)          dmxConsoleDrawWindows(priv);
278    if (priv->fine && updateCursor) dmxConsoleDrawFineCursor(priv, 0);
279    if (update) {
280        XCopyArea(priv->display, priv->pixmap, priv->window, priv->gc,
281                  0, 0, priv->consWidth, priv->consHeight, 0, 0);
282        XSync(priv->display, False);          /* Not a backend display */
283    }
284}
285
286static void dmxConsoleClearCursor(myPrivate *priv, int x, int y,
287                                  XRectangle *rect)
288{
289    int        cw = 14, ch = 14;    /* Clear width and height */
290
291    rect->x      = scalex(priv, x) - cw/2;
292    rect->y      = scaley(priv, y) - ch/2;
293    rect->width  = cw;
294    rect->height = ch;
295    XSetClipRectangles(priv->display, priv->gc, 0, 0, rect, 1, Unsorted);
296    XSetClipRectangles(priv->display, priv->gcDet, 0, 0, rect, 1, Unsorted);
297    XSetClipRectangles(priv->display, priv->gcRev, 0, 0, rect, 1, Unsorted);
298    dmxConsoleDraw(priv, 0, 0);
299    XSetClipMask(priv->display, priv->gc, None);
300    XSetClipMask(priv->display, priv->gcDet, None);
301    XSetClipMask(priv->display, priv->gcRev, None);
302}
303
304
305static void dmxConsoleUpdateFineCursor(myPrivate *priv)
306{
307    int        leave = 0;
308    XRectangle rects[2];
309
310    dmxConsoleClearCursor(priv, priv->globalX, priv->globalY, &rects[0]);
311    if (priv->dmxLocal->sendsCore) {
312        dmxGetGlobalPosition(&priv->globalX, &priv->globalY);
313    } else {
314        priv->globalX = priv->dmxLocal->lastX;
315        priv->globalY = priv->dmxLocal->lastY;
316    }
317
318    priv->lastX   = scalex(priv, priv->width / 2);
319    priv->lastY   = scaley(priv, priv->height / 2);
320
321                                /* Compute new warp position, which may be
322                                   outside the window */
323    if (priv->globalX < 1 || priv->globalX >= priv->width) {
324        if (priv->globalX < 1) priv->lastX = 0;
325        else                   priv->lastX = scalex(priv, priv->width);
326        priv->lastY = scaley(priv, priv->globalY);
327        ++leave;
328    }
329    if (priv->globalY < 1 || priv->globalY >= priv->height) {
330        if (priv->globalY < 1) priv->lastY = 0;
331        else                   priv->lastY = scaley(priv, priv->height);
332        priv->lastX = scalex(priv, priv->globalX);
333        ++leave;
334    }
335
336                                /* Draw pseudo cursor in window */
337    dmxConsoleDrawFineCursor(priv, &rects[1]);
338
339    XSetClipRectangles(priv->display, priv->gc, 0, 0, rects, 2, Unsorted);
340    XCopyArea(priv->display, priv->pixmap, priv->window, priv->gc,
341              0, 0, priv->consWidth, priv->consHeight, 0, 0);
342    XSetClipMask(priv->display, priv->gc, None);
343
344    DMXDBG2("dmxConsoleUpdateFineCursor: WARP %d %d\n",
345            priv->lastX, priv->lastY);
346    XWarpPointer(priv->display, priv->window, priv->window,
347                 0, 0, 0, 0, priv->lastX, priv->lastY);
348    XSync(priv->display, False); /* Not a backend display */
349
350    if (leave) {
351        XEvent X;
352        while (XCheckMaskEvent(priv->display, PointerMotionMask, &X)) {
353            if (X.type == MotionNotify) {
354                if (X.xmotion.x != priv->lastX || X.xmotion.y != priv->lastY) {
355                    DMXDBG4("Ignoring motion to %d %d after leave frm %d %d\n",
356                            X.xmotion.x, X.xmotion.y,
357                            priv->lastX, priv->lastY);
358                }
359            } else {
360                dmxLog(dmxInfo, "Ignoring event (%d): %s ****************\n",
361                       X.type, dmxEventName(X.type));
362            }
363        }
364    }
365    DMXDBG6("dmxConsoleUpdateFineCursor: Warp %d %d on %d %d [%d %d]\n",
366            priv->lastX, priv->lastY,
367            scalex(priv, priv->width),
368            scaley(priv, priv->height),
369            priv->globalX, priv->globalY);
370}
371
372/** Whenever the window layout (size, position, stacking order) might be
373 * changed, this routine is called with the \a pWindow that changed and
374 * the \a type of change.  This routine is called in a conservative
375 * fashion: the actual layout of the windows of the screen might not
376 * have had any human-visible changes. */
377void dmxConsoleUpdateInfo(pointer private, DMXUpdateType type,
378                          WindowPtr pWindow)
379{
380    GETONLYPRIVFROMPRIVATE;
381    dmxConsoleDraw(priv, 1, 1);
382}
383
384static void dmxConsoleMoveAbsolute(myPrivate *priv, int x, int y,
385                                   DevicePtr pDev, dmxMotionProcPtr motion,
386                                   DMXBlockType block)
387{
388    int tmpX, tmpY, v[2];
389
390    tmpX = unscalex(priv, x);
391    tmpY = unscalex(priv, y);
392    DMXDBG6("dmxConsoleMoveAbsolute(,%d,%d) %d %d =? %d %d\n",
393            x, y, tmpX, tmpY, priv->curX, priv->curY);
394    if (tmpX == priv->curX && tmpY == priv->curY) return;
395    v[0] = unscalex(priv, x);
396    v[1] = unscaley(priv, y);
397    motion(pDev, v, 0, 2, DMX_ABSOLUTE_CONFINED, block);
398    /* dmxConsoleUpdatePosition gets called here by dmxCoreMotion */
399}
400
401static void dmxConsoleMoveRelative(myPrivate *priv, int x, int y,
402                                   DevicePtr pDev, dmxMotionProcPtr motion,
403                                   DMXBlockType block)
404{
405    int v[2];
406    /* Ignore the event generated from * warping back to middle */
407    if (x == priv->lastX && y == priv->lastY) return;
408    v[0] = priv->lastX - x;
409    v[1] = priv->lastY - y;
410    motion(pDev, v, 0, 2, DMX_RELATIVE, block);
411    /* dmxConsoleUpdatePosition gets called here by dmxCoreMotion */
412}
413
414/** This routine gets called from #dmxCoreMotion for each motion.  This
415 * allows the console's notion of the cursor postion to change when
416 * another input device actually caused the change. */
417void dmxConsoleUpdatePosition(pointer private, int x, int y)
418{
419    GETONLYPRIVFROMPRIVATE;
420    int                  tmpX, tmpY;
421    Display              *dpy          = priv->display;
422    static unsigned long dmxGeneration = 0;
423
424
425    tmpX = scalex(priv, x);
426    tmpY = scaley(priv, y);
427    DMXDBG6("dmxConsoleUpdatePosition(,%d,%d) new=%d,%d dims=%d,%d\n",
428            x, y, tmpX, tmpY, priv->consWidth, priv->consHeight);
429
430    if (priv->fine) dmxConsoleUpdateFineCursor(priv);
431    if (tmpX != priv->curX || tmpY != priv->curY) {
432        if (tmpX < 0)                 tmpX = 0;
433        if (tmpY < 0)                 tmpY = 0;
434        if (tmpX >= priv->consWidth)  tmpX = priv->consWidth  - 1;
435        if (tmpY >= priv->consHeight) tmpY = priv->consHeight - 1;
436        priv->curX = tmpX;
437        priv->curY = tmpY;
438        if (!priv->fine) {
439            DMXDBG2("   WARP B %d %d\n", priv->curX, priv->curY);
440            XWarpPointer(dpy, priv->window,
441                         priv->window, 0, 0, 0, 0, tmpX, tmpY);
442            XSync(dpy, False); /* Not a backend display */
443        }
444    }
445
446    if (dmxGeneration != serverGeneration) {
447        dmxGeneration = serverGeneration;
448        dmxConsoleDraw(priv, 1, 1);
449    }
450}
451
452/** Collect all pending events from the console's display.  Plase these
453 * events on the server event queue using the \a motion and \a enqueue
454 * routines.  The \a checkspecial routine is used to check for special
455 * keys that need handling.  \a block tells if signals should be blocked
456 * when updating the event queue. */
457void dmxConsoleCollectEvents(DevicePtr pDev,
458                             dmxMotionProcPtr motion,
459                             dmxEnqueueProcPtr enqueue,
460                             dmxCheckSpecialProcPtr checkspecial,
461                             DMXBlockType block)
462{
463    GETPRIVFROMPDEV;
464    GETDMXINPUTFROMPRIV;
465    Display              *dpy        = priv->display;
466    Window               win         = priv->window;
467    int                  width       = priv->width;
468    int                  height      = priv->height;
469    XEvent               X, N;
470    XSetWindowAttributes attribs;
471    static int           rInitialized = 0;
472    static Region        r;
473    XRectangle           rect;
474    static int           raising = 0, raiseX, raiseY; /* FIXME */
475
476    while (XPending(dpy)) {
477        XNextEvent(dpy, &X);
478	switch(X.type) {
479        case VisibilityNotify:
480            break;
481	case Expose:
482            DMXDBG5("dmxConsoleCollectEvents: Expose #%d %d %d %d %d\n",
483                    X.xexpose.count,
484                    X.xexpose.x, X.xexpose.y,
485                    X.xexpose.width, X.xexpose.height);
486            if (!rInitialized++) r = XCreateRegion();
487            rect.x      = X.xexpose.x;
488            rect.y      = X.xexpose.y;
489            rect.width  = X.xexpose.width;
490            rect.height = X.xexpose.height;
491            XUnionRectWithRegion(&rect, r, r);
492	    if (X.xexpose.count == 0) {
493                XSetRegion(dpy, priv->gc, r);
494                XSetRegion(dpy, priv->gcDet, r);
495                XSetRegion(dpy, priv->gcRev, r);
496                dmxConsoleDraw(priv, 1, 1);
497                XSetClipMask(dpy, priv->gc, None);
498                XSetClipMask(dpy, priv->gcDet, None);
499                XSetClipMask(dpy, priv->gcRev, None);
500                XDestroyRegion(r);
501                rInitialized = 0;
502            }
503	    break;
504	case ResizeRequest:
505            DMXDBG2("dmxConsoleCollectEvents: Resize %d %d\n",
506                    X.xresizerequest.width, X.xresizerequest.height);
507            priv->consWidth           = X.xresizerequest.width;
508            priv->consHeight          = X.xresizerequest.height;
509	    priv->xScale              = (double)priv->consWidth  / width;
510	    priv->yScale              = (double)priv->consHeight / height;
511	    attribs.override_redirect = True;
512	    XChangeWindowAttributes(dpy, win, CWOverrideRedirect, &attribs);
513	    XResizeWindow(dpy, win, priv->consWidth, priv->consHeight);
514            XFreePixmap(dpy, priv->pixmap);
515            priv->pixmap = XCreatePixmap(dpy,
516                                         RootWindow(dpy, DefaultScreen(dpy)),
517                                         priv->consWidth,
518                                         priv->consHeight,
519                                         DefaultDepth(dpy,DefaultScreen(dpy)));
520	    dmxConsoleDraw(priv, 1, 1);
521	    attribs.override_redirect = False;
522	    XChangeWindowAttributes(dpy, win, CWOverrideRedirect, &attribs);
523	    break;
524        case LeaveNotify:
525            DMXDBG4("dmxConsoleCollectEvents: Leave @ %d,%d; r=%d f=%d\n",
526                    X.xcrossing.x, X.xcrossing.y, raising, priv->fine);
527            if (!priv->captured) dmxCommonRestoreState(priv);
528            else {
529                dmxConsoleUncapture(dmxInput);
530                dmxCommonRestoreState(priv);
531            }
532            break;
533        case EnterNotify:
534            DMXDBG6("dmxConsoleCollectEvents: Enter %d,%d r=%d f=%d (%d,%d)\n",
535                    X.xcrossing.x, X.xcrossing.y, raising, priv->fine,
536                    priv->curX, priv->curY);
537            dmxCommonSaveState(priv);
538            if (raising) {
539                raising = 0;
540                dmxConsoleMoveAbsolute(priv, raiseX, raiseY,
541                                       priv->mou, motion, block);
542            } else {
543                if (priv->fine) {
544                    /* The raise will generate an event near the center,
545                     * which is not where the cursor should be.  So we
546                     * save the real position, do the raise, and move
547                     * the cursor here again after the raise generates
548                     * the event. */
549                    raising = 1;
550                    raiseX = X.xcrossing.x;
551                    raiseY = X.xcrossing.y;
552                    XRaiseWindow(dpy, priv->window);
553                }
554                XSync(dpy, False); /* Not a backend display */
555                if (!X.xcrossing.x && !X.xcrossing.y)
556                    dmxConsoleMoveAbsolute(priv, priv->curX, priv->curY,
557                                           priv->mou, motion, block);
558            }
559            break;
560	case MotionNotify:
561            if (priv->curX == X.xmotion.x && priv->curY == X.xmotion.y)
562                continue;
563            if (XPending(dpy)) { /* do motion compression */
564                XPeekEvent(dpy, &N);
565                if (N.type == MotionNotify) continue;
566            }
567            DMXDBG2("dmxConsoleCollectEvents: Motion %d %d\n",
568                    X.xmotion.x, X.xmotion.y);
569            if (raising) {
570                raising = 0;
571                dmxConsoleMoveAbsolute(priv, raiseX, raiseY,
572                                       priv->mou, motion, block);
573            } else {
574                if (priv->fine)
575                    dmxConsoleMoveRelative(priv, X.xmotion.x, X.xmotion.y,
576                                           priv->mou, motion, block);
577                else
578                    dmxConsoleMoveAbsolute(priv, X.xmotion.x, X.xmotion.y,
579                                           priv->mou, motion, block);
580            }
581	    break;
582        case KeyPress:
583        case KeyRelease:
584            enqueue(priv->kbd, X.type, X.xkey.keycode, 0, NULL, block);
585            break;
586        default:
587                                /* Pass the whole event here, because
588                                 * this may be an extension event. */
589            enqueue(priv->mou, X.type, X.xbutton.button, 0, &X, block);
590	    break;
591	}
592    }
593}
594
595static void dmxCloseConsole(myPrivate *priv)
596{
597    GETDMXINPUTFROMPRIV;
598    dmxCommonRestoreState(priv);
599    if (priv->display) {
600        XFreeGC(priv->display, priv->gc);
601        XFreeGC(priv->display, priv->gcDet);
602        XFreeGC(priv->display, priv->gcRev);
603        XFreeGC(priv->display, priv->gcCur);
604        if (!dmxInput->console) XCloseDisplay(priv->display);
605    }
606    priv->display = NULL;
607}
608
609static Bool dmxCloseConsoleScreen(int idx, ScreenPtr pScreen)
610{
611    myPrivate *priv, *last;
612
613    for (last = priv = (myPrivate *)dixLookupPrivate(&pScreen->devPrivates,
614						     dmxScreenPrivateKey);
615         priv;
616         priv = priv->next) dmxCloseConsole(last = priv);
617
618    DMX_UNWRAP(CloseScreen, last, pScreen);
619    return pScreen->CloseScreen(idx, pScreen);
620}
621
622static Cursor dmxConsoleCreateEmptyCursor(myPrivate *priv)
623{
624    char    noCursorData[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
625    Pixmap  pixmap;
626    Cursor  cursor;
627    XColor  color, tmpColor;
628    Display *dpy = priv->display;
629
630                                 /* Create empty cursor for window */
631    pixmap = XCreateBitmapFromData(priv->display, priv->window,
632                                   noCursorData, 8, 8);
633    if (!XAllocNamedColor(dpy, DefaultColormap(dpy, DefaultScreen(dpy)),
634                          "black",
635                          &color,
636                          &tmpColor))
637        dmxLog(dmxFatal, "Cannot allocate color for cursor\n");
638    cursor = XCreatePixmapCursor(dpy, pixmap, pixmap, &color, &color, 0, 0);
639    XFreePixmap(dpy, pixmap);
640    return cursor;
641}
642
643static void dmxConsoleComputeWidthHeight(myPrivate *priv,
644                                         int *width, int *height,
645                                         double *xScale, double *yScale,
646                                         int *consWidth, int *consHeight)
647{
648    int     screen;
649    Display *dpy = priv->display;
650
651    *width      = 0;
652    *height     = 0;
653    *xScale     = 1.0;
654    *yScale     = 1.0;
655
656    screen      = DefaultScreen(dpy);
657    *consWidth  = DisplayWidth(dpy, screen)  * CONSOLE_NUM / CONSOLE_DEN;
658    *consHeight = DisplayHeight(dpy, screen) * CONSOLE_NUM / CONSOLE_DEN;
659
660    if (*consWidth  < 1) *consWidth  = 1;
661    if (*consHeight < 1) *consHeight = 1;
662
663#if 1
664                                /* Always keep the console size similar
665                                 * to the global bounding box. */
666    *width  = dmxGlobalWidth;
667    *height = dmxGlobalHeight;
668#else
669                                /* Make the console window as big as
670                                 * possible by computing the visible
671                                 * bounding box. */
672    for (i = 0; i < dmxNumScreens; i++) {
673	if (screenInfo.screens[i]->x+screenInfo.screens[i]->width > *width)
674	    *width = screenInfo.screens[i]->x+screenInfo.screens[i]->width;
675
676	if (screenInfo.screens[i]->y+screenInfo.screens[i]->height > *height)
677	    *height = screenInfo.screens[i]->y+screenInfo.screens[i]->height;
678    }
679#endif
680
681    if ((double)*consWidth / *width < (double)*consHeight / *height)
682	*xScale = *yScale = (double)*consWidth / *width;
683    else
684	*xScale = *yScale = (double)*consHeight / *height;
685
686    *consWidth  = scalex(priv, *width);
687    *consHeight = scaley(priv, *height);
688    if (*consWidth  < 1) *consWidth  = 1;
689    if (*consHeight < 1) *consHeight = 1;
690}
691
692/** Re-initialized the console device described by \a pDev (after a
693 * reconfig). */
694void dmxConsoleReInit(DevicePtr pDev)
695{
696    GETPRIVFROMPDEV;
697    Display *dpy;
698
699    if (!priv || !priv->initialized) return;
700    dpy = priv->display;
701
702    dmxConsoleComputeWidthHeight(priv,
703                                 &priv->width, &priv->height,
704                                 &priv->xScale, &priv->yScale,
705                                 &priv->consWidth, &priv->consHeight);
706    XResizeWindow(dpy, priv->window, priv->consWidth, priv->consHeight);
707    XFreePixmap(dpy, priv->pixmap);
708    priv->pixmap = XCreatePixmap(dpy,
709                                 RootWindow(dpy, DefaultScreen(dpy)),
710                                 priv->consWidth,
711                                 priv->consHeight,
712                                 DefaultDepth(dpy,DefaultScreen(dpy)));
713    dmxConsoleDraw(priv, 1, 1);
714}
715
716/** Initialized the console device described by \a pDev. */
717void dmxConsoleInit(DevicePtr pDev)
718{
719    GETPRIVFROMPDEV;
720    DMXInputInfo         *dmxInput = &dmxInputs[dmxLocal->inputIdx];
721    int                  screen;
722    unsigned long        mask;
723    XSetWindowAttributes attribs;
724    Display              *dpy;
725    Window               win;
726    XGCValues            gcvals;
727    XColor               color;
728    XClassHint           class_hints;
729    unsigned long        tmp;
730
731    if (dmxLocal->type == DMX_LOCAL_MOUSE)    priv->mou = pDev;
732    if (dmxLocal->type == DMX_LOCAL_KEYBOARD) priv->kbd = pDev;
733    if (priv->initialized++) return; /* Only do once for mouse/keyboard pair */
734
735    if (!(dpy = priv->display = XOpenDisplay(dmxInput->name)))
736        dmxLog(dmxFatal,
737               "dmxOpenConsole: cannot open console display %s\n",
738               dmxInput->name);
739
740    /* Set up defaults */
741    dmxConsoleComputeWidthHeight(priv,
742                                 &priv->width, &priv->height,
743                                 &priv->xScale, &priv->yScale,
744                                 &priv->consWidth, &priv->consHeight);
745
746    /* Private initialization using computed values or constants. */
747    screen                   = DefaultScreen(dpy);
748    priv->initPointerX       = scalex(priv, priv->width / 2);
749    priv->initPointerY       = scaley(priv, priv->height / 2);
750    priv->eventMask          = (ButtonPressMask
751                                | ButtonReleaseMask
752                                | PointerMotionMask
753                                | EnterWindowMask
754                                | LeaveWindowMask
755                                | KeyPressMask
756                                | KeyReleaseMask
757                                | ExposureMask
758                                | ResizeRedirectMask);
759
760    mask = CWBackPixel | CWEventMask | CWColormap | CWOverrideRedirect;
761    attribs.colormap = DefaultColormap(dpy, screen);
762    if (XParseColor(dpy, attribs.colormap, CONSOLE_BG_COLOR, &color)
763        && XAllocColor(dpy, attribs.colormap, &color)) {
764	attribs.background_pixel = color.pixel;
765    } else
766        attribs.background_pixel = WhitePixel(dpy, screen);
767
768    attribs.event_mask        = priv->eventMask;
769    attribs.override_redirect = False;
770
771    win = priv->window = XCreateWindow(dpy,
772                                       RootWindow(dpy, screen),
773                                       0, 0, priv->consWidth, priv->consHeight,
774                                       0,
775                                       DefaultDepth(dpy, screen),
776                                       InputOutput,
777                                       DefaultVisual(dpy, screen),
778                                       mask, &attribs);
779    priv->pixmap = XCreatePixmap(dpy, RootWindow(dpy, screen),
780                                 priv->consWidth, priv->consHeight,
781                                 DefaultDepth(dpy, screen));
782
783                                /* Set up properties */
784    XStoreName(dpy, win, DMX_CONSOLE_NAME);
785    class_hints.res_name  = DMX_RES_NAME;
786    class_hints.res_class = DMX_RES_CLASS;
787    XSetClassHint(dpy, win, &class_hints);
788
789
790                                /* Map the window */
791    XMapWindow(dpy, win);
792
793                                /* Create cursors */
794    priv->cursorNormal  = XCreateFontCursor(dpy, XC_circle);
795    priv->cursorGrabbed = XCreateFontCursor(dpy, XC_spider);
796    priv->cursorEmpty   = dmxConsoleCreateEmptyCursor(priv);
797    XDefineCursor(dpy, priv->window, priv->cursorNormal);
798
799                                /* Create GC */
800    mask = (GCFunction | GCPlaneMask | GCClipMask | GCForeground |
801	    GCBackground | GCLineWidth | GCLineStyle | GCCapStyle |
802	    GCFillStyle | GCGraphicsExposures);
803    gcvals.function = GXcopy;
804    gcvals.plane_mask = AllPlanes;
805    gcvals.clip_mask = None;
806    if (XParseColor(dpy, attribs.colormap, CONSOLE_SCREEN_FG_COLOR, &color)
807        && XAllocColor(dpy, attribs.colormap, &color)) {
808	gcvals.foreground = color.pixel;
809    } else
810	gcvals.foreground = BlackPixel(dpy, screen);
811    if (XParseColor(dpy, attribs.colormap, CONSOLE_SCREEN_BG_COLOR, &color)
812        && XAllocColor(dpy, attribs.colormap, &color)) {
813	gcvals.background = color.pixel;
814    } else
815	gcvals.background = WhitePixel(dpy, screen);
816    gcvals.line_width         = 0;
817    gcvals.line_style         = LineSolid;
818    gcvals.cap_style          = CapNotLast;
819    gcvals.fill_style         = FillSolid;
820    gcvals.graphics_exposures = False;
821
822    priv->gc = XCreateGC(dpy, win, mask, &gcvals);
823
824    tmp               = gcvals.foreground;
825    if (XParseColor(dpy, attribs.colormap, CONSOLE_SCREEN_DET_COLOR, &color)
826        && XAllocColor(dpy, attribs.colormap, &color)) {
827        gcvals.foreground = color.pixel;
828    } else
829        gcvals.foreground = BlackPixel(dpy, screen);
830    priv->gcDet = XCreateGC(dpy, win, mask, &gcvals);
831    gcvals.foreground = tmp;
832
833    tmp               = gcvals.background;
834    gcvals.background = gcvals.foreground;
835    gcvals.foreground = tmp;
836    priv->gcRev = XCreateGC(dpy, win, mask, &gcvals);
837
838    gcvals.background = gcvals.foreground;
839    if (XParseColor(dpy, attribs.colormap, CONSOLE_SCREEN_CUR_COLOR, &color)
840        && XAllocColor(dpy, attribs.colormap, &color)) {
841        gcvals.foreground = color.pixel;
842    } else
843        gcvals.foreground = BlackPixel(dpy, screen);
844    priv->gcCur = XCreateGC(dpy, win, mask, &gcvals);
845
846    dmxConsoleDraw(priv, 1, 1);
847
848    if (dixLookupPrivate(&screenInfo.screens[0]->devPrivates,
849			 dmxScreenPrivateKey))
850        priv->next = dixLookupPrivate(&screenInfo.screens[0]->devPrivates,
851				      dmxScreenPrivateKey);
852    else
853        DMX_WRAP(CloseScreen, dmxCloseConsoleScreen,
854                 priv, screenInfo.screens[0]);
855    dixSetPrivate(&screenInfo.screens[0]->devPrivates, dmxScreenPrivateKey,
856		  priv);
857}
858
859/** Fill in the \a info structure for the specified \a pDev.  Only used
860 * for pointers. */
861void dmxConsoleMouGetInfo(DevicePtr pDev, DMXLocalInitInfoPtr info)
862{
863    GETPRIVFROMPDEV;
864
865    info->buttonClass      = 1;
866    dmxCommonMouGetMap(pDev, info->map, &info->numButtons);
867    info->valuatorClass    = 1;
868    info->numRelAxes       = 2;
869    info->minval[0] = 0;
870    info->minval[1] = 0;
871    /* max possible console window size: */
872    info->maxval[0] = DisplayWidth(priv->display, DefaultScreen(priv->display));
873    info->maxval[1] = DisplayHeight(priv->display, DefaultScreen(priv->display));
874    info->res[0]           = 1;
875    info->minres[0]        = 0;
876    info->maxres[0]        = 1;
877    info->ptrFeedbackClass = 1;
878}
879
880/** Fill in the \a info structure for the specified \a pDev.  Only used
881 * for keyboard. */
882void dmxConsoleKbdGetInfo(DevicePtr pDev, DMXLocalInitInfoPtr info)
883{
884    dmxCommonKbdGetInfo(pDev, info);
885    info->keyboard         = 1;
886    info->keyClass         = 1;
887    dmxCommonKbdGetMap(pDev, &info->keySyms, info->modMap);
888    info->freemap          = 1;
889    info->focusClass       = 1;
890    info->kbdFeedbackClass = 1;
891}
892
893/** Handle special console-only keys. */
894int dmxConsoleFunctions(pointer private, DMXFunctionType function)
895{
896    GETONLYPRIVFROMPRIVATE;
897    XRectangle rect;
898    Display    *dpy = priv->display;
899
900    switch (function) {
901    case DMX_FUNCTION_FINE:
902        if (priv->fine) {
903            priv->fine = 0;
904            dmxConsoleClearCursor(priv, priv->globalX, priv->globalY, &rect);
905            XSetClipRectangles(dpy, priv->gc, 0, 0, &rect, 1, Unsorted);
906            XCopyArea(dpy, priv->pixmap, priv->window, priv->gc,
907                      0, 0, priv->consWidth, priv->consHeight, 0, 0);
908            XSetClipMask(dpy, priv->gc, None);
909
910            XDefineCursor(dpy, priv->window,
911                          priv->grabbed
912                          ? priv->cursorGrabbed
913                          : priv->cursorNormal);
914            XWarpPointer(dpy, priv->window, priv->window,
915                         0, 0, 0, 0,
916                         scalex(priv, priv->globalX),
917                         scaley(priv, priv->globalY));
918            XSync(dpy, False); /* Not a backend display */
919        } else {
920            priv->fine = 1;
921            XRaiseWindow(dpy, priv->window);
922            XDefineCursor(dpy, priv->window, priv->cursorEmpty);
923            dmxConsoleUpdateFineCursor(priv);
924        }
925        return 1;
926    case DMX_FUNCTION_GRAB:
927        if (priv->grabbed) {
928            XUngrabKeyboard(dpy, CurrentTime);
929            XUngrabPointer(dpy, CurrentTime);
930            XDefineCursor(dpy, priv->window,
931                          priv->fine
932                          ? priv->cursorEmpty
933                          : priv->cursorNormal);
934        } else {
935            if (XGrabPointer(dpy, priv->window, True,
936                             0, GrabModeAsync, GrabModeAsync, priv->window,
937                             None, CurrentTime)) {
938                dmxLog(dmxError, "XGrabPointer failed\n");
939                return 0;
940            }
941            if (XGrabKeyboard(dpy, priv->window, True,
942                              GrabModeAsync, GrabModeAsync, CurrentTime)) {
943                dmxLog(dmxError, "XGrabKeyboard failed\n");
944                XUngrabPointer(dpy, CurrentTime);
945                return 0;
946            }
947            XDefineCursor(dpy, priv->window,
948                          priv->fine
949                          ? priv->cursorEmpty
950                          : priv->cursorGrabbed);
951        }
952        priv->grabbed = !priv->grabbed;
953        if (priv->fine) dmxConsoleUpdateFineCursor(priv);
954        return 1;
955    case DMX_FUNCTION_TERMINATE:
956        return 1;
957    default:
958        return 0;
959    }
960}
961
962static void dmxDump(void)
963{
964    int          i, j;
965    DMXInputInfo *dmxInput;
966    XEvent       X;
967
968    for (i = 0, dmxInput = &dmxInputs[0]; i < dmxNumInputs; i++, dmxInput++) {
969        for (j = 0; j < dmxInput->numDevs; j++) {
970            DMXLocalInputInfoPtr dmxLocal = dmxInput->devs[j];
971            myPrivate            *priv    = dmxLocal->private;
972            while (priv
973                   && priv->display
974                   && XCheckTypedEvent(priv->display, MotionNotify, &X)) {
975                DMXDBG4("dmxDump: %s/%d threw event away %d %s\n",
976                        dmxInput->name, j, X.type, dmxEventName(X.type));
977            }
978        }
979    }
980}
981
982/** This routine is used to warp the pointer into the console window
983 * from anywhere on the screen.  It is used when backend and console
984 * input are both being taken from the same X display. */
985void dmxConsoleCapture(DMXInputInfo *dmxInput)
986{
987    int     i;
988    XEvent  X;
989
990    DMXDBG0("dmxConsoleCapture\n");
991    dmxSync(NULL, TRUE);
992    for (i = 0; i < dmxInput->numDevs; i++) {
993        DMXLocalInputInfoPtr dmxLocal = dmxInput->devs[i];
994        myPrivate            *priv    = dmxLocal->private;
995        if (dmxLocal->extType != DMX_LOCAL_TYPE_CONSOLE) continue;
996        if (dmxLocal->type    != DMX_LOCAL_MOUSE)        continue;
997        if (priv->captured)                              continue;
998        priv->captured = 2;     /* Ungrab only after proximal events. */
999        XRaiseWindow(priv->display, priv->window);
1000        XSync(priv->display, False); /* Not a backend display */
1001        while (XCheckTypedEvent(priv->display, MotionNotify, &X)) {
1002            DMXDBG3("   Ignoring motion to %d %d after capture on %s\n",
1003                    X.xmotion.x, X.xmotion.y, dmxInput->name);
1004        }
1005        XWarpPointer(priv->display, None,
1006                     priv->window, 0, 0, 0, 0, priv->curX, priv->curY);
1007        XSync(priv->display, False); /* Not a backend display */
1008        dmxDump();
1009        if (priv->fine) dmxConsoleUpdateFineCursor(priv);
1010    }
1011}
1012
1013/** Undo the capture that was done by #dmxConsoleCapture. */
1014void dmxConsoleUncapture(DMXInputInfo *dmxInput)
1015{
1016    int i;
1017
1018    DMXDBG0("dmxConsoleUncapture\n");
1019    dmxSync(NULL, TRUE);
1020    for (i = 0; i < dmxInput->numDevs; i++) {
1021        DMXLocalInputInfoPtr dmxLocal = dmxInput->devs[i];
1022        myPrivate            *priv    = dmxLocal->private;
1023        if (dmxLocal->extType != DMX_LOCAL_TYPE_CONSOLE) continue;
1024        if (dmxLocal->type    != DMX_LOCAL_MOUSE)        continue;
1025        if (!priv->captured)                             continue;
1026        priv->captured = 0;
1027        XSync(priv->display, False); /* Not a backend display */
1028    }
1029}
1030