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