dmxbackend.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/** \file
36 * These routines support taking input from devices on the backend
37 * (output) displays.  \see dmxcommon.c. */
38
39#ifdef HAVE_DMX_CONFIG_H
40#include <dmx-config.h>
41#endif
42
43#define DMX_BACKEND_DEBUG 0
44
45#include "dmxinputinit.h"
46#include "dmxbackend.h"
47#include "dmxcommon.h"
48#include "dmxconsole.h"
49#include "dmxcursor.h"
50#include "dmxprop.h"
51#include "dmxsync.h"
52#include "dmxcb.h"              /* For dmxGlobalWidth and dmxGlobalHeight */
53#include "dmxevents.h"          /* For dmxGetGlobalPosition */
54#include "ChkNotMaskEv.h"
55
56#include "inputstr.h"
57#include "input.h"
58#include <X11/keysym.h>
59#include "mipointer.h"
60#include "scrnintstr.h"
61#include "windowstr.h"
62
63/* Private area for backend devices. */
64typedef struct _myPrivate {
65    DMX_COMMON_PRIVATE;
66    int                     myScreen;
67    DMXScreenInfo           *grabbedScreen;
68
69    int                     lastX, lastY;
70    int                     centerX, centerY;
71    int                     relative;
72    int                     newscreen;
73    int                     initialized;
74    DevicePtr               mou, kbd;
75    int                     entered;
76    int                     offX, offY;
77} myPrivate;
78
79#if DMX_BACKEND_DEBUG
80#define DMXDBG0(f)                   dmxLog(dmxDebug,f)
81#define DMXDBG1(f,a)                 dmxLog(dmxDebug,f,a)
82#define DMXDBG2(f,a,b)               dmxLog(dmxDebug,f,a,b)
83#define DMXDBG3(f,a,b,c)             dmxLog(dmxDebug,f,a,b,c)
84#define DMXDBG4(f,a,b,c,d)           dmxLog(dmxDebug,f,a,b,c,d)
85#define DMXDBG5(f,a,b,c,d,e)         dmxLog(dmxDebug,f,a,b,c,d,e)
86#define DMXDBG6(f,a,b,c,d,e,g)       dmxLog(dmxDebug,f,a,b,c,d,e,g)
87#define DMXDBG7(f,a,b,c,d,e,g,h)     dmxLog(dmxDebug,f,a,b,c,d,e,g,h)
88#define DMXDBG8(f,a,b,c,d,e,g,h,i)   dmxLog(dmxDebug,f,a,b,c,d,e,g,h,i)
89#define DMXDBG9(f,a,b,c,d,e,g,h,i,j) dmxLog(dmxDebug,f,a,b,c,d,e,g,h,i,j)
90#else
91#define DMXDBG0(f)
92#define DMXDBG1(f,a)
93#define DMXDBG2(f,a,b)
94#define DMXDBG3(f,a,b,c)
95#define DMXDBG4(f,a,b,c,d)
96#define DMXDBG5(f,a,b,c,d,e)
97#define DMXDBG6(f,a,b,c,d,e,g)
98#define DMXDBG7(f,a,b,c,d,e,g,h)
99#define DMXDBG8(f,a,b,c,d,e,g,h,i)
100#define DMXDBG9(f,a,b,c,d,e,g,h,i,j)
101#endif
102
103/** Create and return a private data structure. */
104pointer dmxBackendCreatePrivate(DeviceIntPtr pDevice)
105{
106    GETDMXLOCALFROMPDEVICE;
107    myPrivate *priv = calloc(1, sizeof(*priv));
108    priv->dmxLocal  = dmxLocal;
109    return priv;
110}
111
112/** Destroy the private data structure.  No checking is performed to
113 * verify that the structure was actually created by
114 * #dmxBackendCreatePrivate. */
115void dmxBackendDestroyPrivate(pointer private)
116{
117    free(private);
118}
119
120static void *dmxBackendTestScreen(DMXScreenInfo *dmxScreen, void *closure)
121{
122    long target = (long)closure;
123
124    if (dmxScreen->index == target) return dmxScreen;
125    return NULL;
126}
127
128/* Return non-zero if screen and priv->myScreen are on the same physical
129 * backend display (1 if they are the same screen, 2 if they are
130 * different screens).  Since this is a common operation, the results
131 * are cached.  The cache is invalidated if \a priv is NULL (this should
132 * be done with each server generation and reconfiguration). */
133static int dmxBackendSameDisplay(myPrivate *priv, long screen)
134{
135    static myPrivate *oldpriv  = NULL;
136    static int       oldscreen = -1;
137    static int       retcode   = 0;
138
139    if (priv == oldpriv && screen == oldscreen) return retcode;
140    if (!priv) {                /* Invalidate cache */
141        oldpriv   = NULL;
142        oldscreen = -1;
143        retcode   = 0;
144        return 0;
145    }
146
147    if (screen == priv->myScreen)                     retcode = 1;
148    else if (screen < 0 || screen >= dmxNumScreens)   retcode = 0;
149    else if (dmxPropertyIterate(priv->be,
150                                dmxBackendTestScreen,
151                                (void *)screen))      retcode = 2;
152    else                                              retcode = 0;
153
154    oldpriv   = priv;
155    oldscreen = screen;
156    return retcode;
157}
158
159static void *dmxBackendTestEvents(DMXScreenInfo *dmxScreen, void *closure)
160{
161    XEvent *X = (XEvent *)closure;
162
163    if (XCheckNotMaskEvent(dmxScreen->beDisplay, ExposureMask, X))
164        return dmxScreen;
165    return NULL;
166}
167
168static void *dmxBackendTestMotionEvent(DMXScreenInfo *dmxScreen, void *closure)
169{
170    XEvent *X = (XEvent *)closure;
171
172    if (XCheckTypedEvent(dmxScreen->beDisplay, MotionNotify, X))
173        return dmxScreen;
174    return NULL;
175}
176
177static DMXScreenInfo *dmxBackendGetEvent(myPrivate *priv, XEvent *X)
178{
179    DMXScreenInfo *dmxScreen;
180
181    if ((dmxScreen = dmxPropertyIterate(priv->be, dmxBackendTestEvents, X)))
182        return dmxScreen;
183    return NULL;
184}
185
186static DMXScreenInfo *dmxBackendPendingMotionEvent(myPrivate *priv, int save)
187{
188    DMXScreenInfo *dmxScreen;
189    XEvent        N;
190
191    if ((dmxScreen = dmxPropertyIterate(priv->be,
192                                        dmxBackendTestMotionEvent, &N))) {
193        if (save) XPutBackEvent(dmxScreen->beDisplay, &N);
194        return dmxScreen;
195    }
196    return NULL;
197}
198
199static void *dmxBackendTestWindow(DMXScreenInfo *dmxScreen, void *closure)
200{
201    Window win = (Window)(long)closure;
202    if (dmxScreen->scrnWin == win) return dmxScreen;
203    return NULL;
204}
205
206static DMXScreenInfo *dmxBackendFindWindow(myPrivate *priv, Window win)
207{
208    return dmxPropertyIterate(priv->be, dmxBackendTestWindow,
209                              (void *)(long)win);
210}
211
212/* If the cursor is over a set of overlapping screens and one of those
213 * screens takes backend input, then we want that particular screen to
214 * be current, not one of the other ones. */
215static int dmxBackendFindOverlapping(myPrivate *priv, int screen, int x, int y)
216{
217    DMXScreenInfo *start = &dmxScreens[screen];
218    DMXScreenInfo *pt;
219
220    if (!start->over) return screen;
221
222    for (pt = start->over; /* condition at end of loop */; pt = pt->over) {
223        if (pt->index == priv->myScreen
224            && dmxOnScreen(x, y, &dmxScreens[pt->index])) return pt->index;
225        if (pt == start) break;
226    }
227    return screen;
228}
229
230/* Return non-zero if \a x and \a y are off \a screen. */
231static int dmxBackendOffscreen(int screen, int x, int y)
232{
233    DMXScreenInfo *dmxScreen = &dmxScreens[screen];
234
235    return (!dmxOnScreen(x, y, dmxScreen));
236}
237
238/** This routine is called from #dmxCoreMotion for each motion
239 * event. \a x and \a y are global coordinants. */
240void dmxBackendUpdatePosition(pointer private, int x, int y)
241{
242    GETPRIVFROMPRIVATE;
243    int           screen      = miPointerGetScreen(inputInfo.pointer)->myNum;
244    DMXScreenInfo *dmxScreen  = &dmxScreens[priv->myScreen];
245    int           oldRelative = priv->relative;
246    int           topscreen   = dmxBackendFindOverlapping(priv, screen, x, y);
247    int           same        = dmxBackendSameDisplay(priv, topscreen);
248    int           offscreen   = dmxBackendOffscreen(priv->myScreen, x, y);
249    int           offthis     = dmxBackendOffscreen(screen, x, y);
250
251    DMXDBG9("dmxBackendUpdatePosition(%d,%d) my=%d mi=%d rel=%d"
252            " topscreen=%d same=%d offscreen=%d offthis=%d\n",
253            x, y, priv->myScreen, screen, priv->relative,
254            topscreen, same, offscreen, offthis);
255
256    if (offscreen) {
257        /* If the cursor is off the input screen, it should be moving
258         * relative unless it is visible on a screen of the same display
259         * (i.e., one that shares the mouse). */
260        if (same == 2 && !offthis) {
261            if (priv->relative) {
262                DMXDBG0("   Off screen, but not absolute\n");
263                priv->relative = 0;
264            }
265        } else {
266            if (!priv->relative) {
267                DMXDBG0("   Off screen, but not relative\n");
268                priv->relative = 1;
269            }
270        }
271    } else {
272        if (topscreen != screen) {
273            DMXDBG2("   Using screen %d instead of %d (from mi)\n",
274                    topscreen, screen);
275        }
276        if (same) {
277            if (priv->relative) {
278                DMXDBG0("   On screen, but not absolute\n");
279                priv->relative = 0;
280            }
281        } else {
282            if (!priv->relative) {
283                DMXDBG0("   Not on screen, but not relative\n");
284                priv->relative = 1;
285            }
286        }
287    }
288
289    if (oldRelative != priv->relative) {
290        DMXDBG2("   Do switch, relative=%d same=%d\n",
291                priv->relative, same);
292        /* Discard all pre-switch events */
293        dmxSync(dmxScreen, TRUE);
294        while (dmxBackendPendingMotionEvent(priv, FALSE));
295
296        if (dmxInput->console && offscreen) {
297            /* Our special case is a console window and a backend window
298             * share a display.  In this case, the cursor is either on
299             * the backend window (taking absolute input), or not (in
300             * which case the cursor needs to be in the console
301             * window). */
302            if (priv->grabbedScreen) {
303                DMXDBG2("   *** force ungrab on %s, display=%p\n",
304                        priv->grabbedScreen->name,
305                        priv->grabbedScreen->beDisplay);
306                XUngrabPointer(priv->grabbedScreen->beDisplay, CurrentTime);
307                dmxSync(priv->grabbedScreen, TRUE);
308                priv->grabbedScreen = NULL;
309            }
310            DMXDBG0("   Capturing console\n");
311            dmxConsoleCapture(dmxInput);
312        } else {
313            priv->newscreen = 1;
314            if (priv->relative && !dmxInput->console) {
315                DMXDBG5("   Hide cursor; warp from %d,%d to %d,%d on %d\n",
316                        priv->lastX, priv->lastY, priv->centerX, priv->centerY,
317                        priv->myScreen);
318                dmxConsoleUncapture(dmxInput);
319                dmxHideCursor(dmxScreen);
320                priv->lastX   = priv->centerX;
321                priv->lastY   = priv->centerY;
322                XWarpPointer(priv->display, None, priv->window,
323                             0, 0, 0, 0, priv->lastX, priv->lastY);
324                dmxSync(dmxScreen, TRUE);
325            } else {
326                DMXDBG0("   Check cursor\n");
327                dmxCheckCursor();
328            }
329        }
330    }
331}
332
333/** Get events from the X queue on the backend servers and put the
334 * events into the DMX event queue. */
335void dmxBackendCollectEvents(DevicePtr pDev,
336                             dmxMotionProcPtr motion,
337                             dmxEnqueueProcPtr enqueue,
338                             dmxCheckSpecialProcPtr checkspecial,
339                             DMXBlockType block)
340{
341    GETPRIVFROMPDEV;
342    GETDMXINPUTFROMPRIV;
343    XEvent               X;
344    DMXScreenInfo        *dmxScreen;
345    int                  left        = 0;
346    int                  entered     = priv->entered;
347    int                  ignoreLeave = 0;
348    int                  v[2];
349    int                  retcode;
350
351    while ((dmxScreen = dmxBackendGetEvent(priv, &X))) {
352	switch (X.type) {
353        case EnterNotify:
354            dmxCommonSaveState(priv);
355            if (entered++)
356                continue;
357            priv->entered = 1;
358            ignoreLeave   = 1;
359            DMXDBG5("dmxBackendCollectEvents: Enter %lu %d,%d; GRAB %s %p\n",
360                    X.xcrossing.root, X.xcrossing.x, X.xcrossing.y,
361                    dmxScreen->name, dmxScreen->beDisplay);
362            XRaiseWindow(dmxScreen->beDisplay, dmxScreen->scrnWin);
363            priv->grabbedScreen = dmxScreen;
364            if ((retcode = XGrabPointer(dmxScreen->beDisplay,
365					dmxScreen->scrnWin,
366                                        True, 0, GrabModeAsync,
367                                        GrabModeAsync, None, None,
368                                        CurrentTime))) {
369                dmxLog(dmxError,
370                       "XGrabPointer failed during backend enter (%d)\n",
371                       retcode);
372            }
373            break;
374        case LeaveNotify:
375            if (ignoreLeave) {
376                ignoreLeave = 0;
377                continue;
378            }
379            dmxCommonRestoreState(priv);
380            if (left++)
381                continue;
382            DMXDBG7("dmxBackendCollectEvents: Leave %lu %d,%d %d %d %s %s\n",
383                    X.xcrossing.root, X.xcrossing.x, X.xcrossing.y,
384                    X.xcrossing.detail, X.xcrossing.focus,
385                    priv->grabbedScreen ? "UNGRAB" : "",
386                    dmxScreen->name);
387            if (priv->grabbedScreen) {
388                XUngrabPointer(priv->grabbedScreen->beDisplay, CurrentTime);
389                dmxSync(priv->grabbedScreen, TRUE);
390                priv->grabbedScreen = NULL;
391            }
392            break;
393	case MotionNotify:
394            DMXDBG9("dmxBackendCollectEvents: MotionNotify %d/%d (mi %d)"
395                    " newscreen=%d: %d %d (e=%d; last=%d,%d)\n",
396                    dmxScreen->index, priv->myScreen,
397                    miPointerCurrentScreen()->myNum,
398                    priv->newscreen,
399                    X.xmotion.x, X.xmotion.y,
400                    entered, priv->lastX, priv->lastY);
401            if (dmxBackendPendingMotionEvent(priv, TRUE))
402                continue;
403            if (!(dmxScreen = dmxBackendFindWindow(priv, X.xmotion.window)))
404                dmxLog(dmxFatal,
405                       "   Event on non-existant window %lu\n",
406                       X.xmotion.window);
407            if (!priv->relative || dmxInput->console) {
408                int newX = X.xmotion.x - dmxScreen->rootX;
409                int newY = X.xmotion.y - dmxScreen->rootY;
410
411                if (!priv->newscreen) {
412                    int width  = dmxScreen->rootWidth;
413                    int height = dmxScreen->rootHeight;
414                    if (!newX)              newX = -1;
415                    if (newX == width - 1)  newX = width;
416                    if (!newY)              newY = -1;
417                    if (newY == height - 1) newY = height;
418                }
419                priv->newscreen = 0;
420                v[0] = dmxScreen->rootXOrigin + newX;
421                v[1] = dmxScreen->rootYOrigin + newY;
422                DMXDBG8("   Absolute move: %d,%d (r=%dx%d+%d+%d s=%dx%d)\n",
423                        v[0], v[1],
424                        priv->be->rootWidth, priv->be->rootHeight,
425                        priv->be->rootX, priv->be->rootY,
426                        priv->be->scrnWidth, priv->be->scrnHeight);
427                motion(priv->mou, v, 0, 2, DMX_ABSOLUTE, block);
428                priv->entered = 0;
429            } else {
430                int newX = priv->lastX - X.xmotion.x;
431                int newY = priv->lastY - X.xmotion.y;
432                priv->lastX = X.xmotion.x;
433                priv->lastY = X.xmotion.y;
434                v[0]        = newX;
435                v[1]        = newY;
436                DMXDBG2("   Relative move: %d, %d\n", v[0], v[1]);
437                motion(priv->mou, v, 0, 2, DMX_RELATIVE, block);
438            }
439            if (entered && priv->relative) {
440                DMXDBG4("   **** Relative %d %d instead of absolute %d %d\n",
441                        v[0], v[1],
442                        (dmxScreen->rootXOrigin + X.xmotion.x
443                         - dmxScreen->rootX),
444                        (dmxScreen->rootYOrigin + X.xmotion.y
445                         - dmxScreen->rootY));
446            }
447	    break;
448
449        case KeyPress:
450        case KeyRelease:
451            enqueue(priv->kbd, X.type, X.xkey.keycode, 0, NULL, block);
452            break;
453        case ButtonPress:
454        case ButtonRelease:
455            /* fall-through */
456	default:
457                                /* Pass the whole event here, because
458                                 * this may be an extension event. */
459            enqueue(priv->mou, X.type, X.xbutton.button, 0, &X, block);
460	    break;
461	}
462    }
463}
464
465/** Called after input events are processed from the DMX queue.  No
466 * event processing actually takes place here, but this is a convenient
467 * place to update the pointer. */
468void dmxBackendProcessInput(pointer private)
469{
470    GETPRIVFROMPRIVATE;
471
472    DMXDBG6("dmxBackendProcessInput: myScreen=%d relative=%d"
473            " last=%d,%d center=%d,%d\n",
474            priv->myScreen, priv->relative,
475            priv->lastX, priv->lastY,
476            priv->centerX, priv->centerY);
477
478    if (priv->relative
479        && !dmxInput->console
480        && (priv->lastX != priv->centerX || priv->lastY != priv->centerY)) {
481        DMXDBG4("   warping pointer from last=%d,%d to center=%d,%d\n",
482                priv->lastX, priv->lastY, priv->centerX, priv->centerY);
483	priv->lastX   = priv->centerX;
484	priv->lastY   = priv->centerY;
485        XWarpPointer(priv->display, None, priv->window,
486                     0, 0, 0, 0, priv->lastX, priv->lastY);
487        dmxSync(&dmxScreens[priv->myScreen], TRUE);
488    }
489}
490
491static void dmxBackendComputeCenter(myPrivate *priv)
492{
493    int centerX;
494    int centerY;
495
496    centerX       = priv->be->rootWidth / 2 + priv->be->rootX;
497    centerY       = priv->be->rootHeight / 2 + priv->be->rootY;
498
499    if (centerX > priv->be->rootWidth)  centerX = priv->be->rootWidth  - 1;
500    if (centerY > priv->be->rootHeight) centerY = priv->be->rootHeight - 1;
501    if (centerX < 1)                    centerX = 1;
502    if (centerY < 1)                    centerY = 1;
503
504    priv->centerX = centerX;
505    priv->centerY = centerY;
506}
507
508static DMXScreenInfo *dmxBackendInitPrivate(DevicePtr pDev)
509{
510    GETPRIVFROMPDEV;
511    DMXInputInfo      *dmxInput = &dmxInputs[dmxLocal->inputIdx];
512    DMXScreenInfo     *dmxScreen;
513    int               i;
514
515    /* Fill in myPrivate */
516    for (i = 0,dmxScreen = &dmxScreens[0]; i<dmxNumScreens; i++,dmxScreen++) {
517        if (dmxPropertySameDisplay(dmxScreen, dmxInput->name)) {
518            priv->display  = dmxScreen->beDisplay;
519            priv->window   = dmxScreen->scrnWin;
520            priv->be       = dmxScreen;
521            break;
522        }
523    }
524
525    if (i >= dmxNumScreens)
526        dmxLog(dmxFatal,
527               "%s is not an existing backend display - cannot initialize\n",
528               dmxInput->name);
529
530    return dmxScreen;
531}
532
533/** Re-initialized the backend device described by \a pDev (after a
534 * reconfig). */
535void dmxBackendLateReInit(DevicePtr pDev)
536{
537    GETPRIVFROMPDEV;
538    int               x, y;
539
540    DMXDBG1("dmxBackendLateReInit miPointerCurrentScreen() = %p\n",
541            miPointerCurrentScreen());
542
543    dmxBackendSameDisplay(NULL, 0); /* Invalidate cache */
544    dmxBackendInitPrivate(pDev);
545    dmxBackendComputeCenter(priv);
546    dmxGetGlobalPosition(&x, &y);
547    dmxInvalidateGlobalPosition(); /* To force event processing */
548    dmxBackendUpdatePosition(priv, x, y);
549}
550
551/** Initialized the backend device described by \a pDev. */
552void dmxBackendInit(DevicePtr pDev)
553{
554    GETPRIVFROMPDEV;
555    DMXScreenInfo     *dmxScreen;
556
557    dmxBackendSameDisplay(NULL, 0); /* Invalidate cache */
558
559    if (dmxLocal->type == DMX_LOCAL_MOUSE)    priv->mou = pDev;
560    if (dmxLocal->type == DMX_LOCAL_KEYBOARD) priv->kbd = pDev;
561    if (priv->initialized++) return; /* Only do once for mouse/keyboard pair */
562
563    dmxScreen = dmxBackendInitPrivate(pDev);
564
565    /* Finish initialization using computed values or constants. */
566    dmxBackendComputeCenter(priv);
567    priv->eventMask          = (EnterWindowMask|LeaveWindowMask);
568    priv->myScreen           = dmxScreen->index;
569    priv->lastX              = priv->centerX;
570    priv->lastY              = priv->centerY;
571    priv->relative           = 0;
572    priv->newscreen          = 0;
573}
574
575/** Get information about the backend pointer (for initialization). */
576void dmxBackendMouGetInfo(DevicePtr pDev, DMXLocalInitInfoPtr info)
577{
578    const DMXScreenInfo *dmxScreen = dmxBackendInitPrivate(pDev);
579
580    info->buttonClass      = 1;
581    dmxCommonMouGetMap(pDev, info->map, &info->numButtons);
582    info->valuatorClass    = 1;
583    info->numRelAxes       = 2;
584    info->minval[0]        = 0;
585    info->minval[1]        = 0;
586    info->maxval[0]        = dmxScreen->beWidth;
587    info->maxval[1]        = dmxScreen->beHeight;
588    info->res[0]           = 1;
589    info->minres[0]        = 0;
590    info->maxres[0]        = 1;
591    info->ptrFeedbackClass = 1;
592}
593
594/** Get information about the backend keyboard (for initialization). */
595void dmxBackendKbdGetInfo(DevicePtr pDev, DMXLocalInitInfoPtr info)
596{
597    dmxCommonKbdGetInfo(pDev, info);
598    info->keyboard         = 1;
599    info->keyClass         = 1;
600    dmxCommonKbdGetMap(pDev, &info->keySyms, info->modMap);
601    info->freemap          = 1;
602    info->focusClass       = 1;
603    info->kbdFeedbackClass = 1;
604}
605
606/** Process #DMXFunctionType functions.  The only function handled here
607 * is to acknowledge a pending server shutdown. */
608int dmxBackendFunctions(pointer private, DMXFunctionType function)
609{
610    switch (function) {
611    case DMX_FUNCTION_TERMINATE:
612        return 1;
613    default:
614        return 0;
615    }
616}
617