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