initial commit
[profile/ivi/xorg-x11-server.git] / hw / dmx / input / dmxbackend.c
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. */
64 typedef 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. */
104 pointer 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. */
115 void dmxBackendDestroyPrivate(pointer private)
116 {
117     free(private);
118 }
119
120 static 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). */
133 static 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
159 static 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
168 static 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
177 static 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
186 static 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
199 static 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
206 static 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. */
215 static 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. */
231 static 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. */
240 void 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. */
335 void 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. */
468 void 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
491 static 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
508 static 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). */
535 void 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. */
552 void 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). */
576 void 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). */
595 void 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. */
608 int dmxBackendFunctions(pointer private, DMXFunctionType function)
609 {
610     switch (function) {
611     case DMX_FUNCTION_TERMINATE:
612         return 1;
613     default:
614         return 0;
615     }
616 }