3
* Copyright 2002-2003 Red Hat Inc., Durham, North Carolina.
7
* Permission is hereby granted, free of charge, to any person obtaining
8
* a copy of this software and associated documentation files (the
9
* "Software"), to deal in the Software without restriction, including
10
* without limitation on the rights to use, copy, modify, merge,
11
* publish, distribute, sublicense, and/or sell copies of the Software,
12
* and to permit persons to whom the Software is furnished to do so,
13
* subject to the following conditions:
15
* The above copyright notice and this permission notice (including the
16
* next paragraph) shall be included in all copies or substantial
17
* portions of the Software.
19
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22
* NON-INFRINGEMENT. IN NO EVENT SHALL RED HAT AND/OR THEIR SUPPLIERS
23
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
24
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
25
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31
* Rickard E. (Rik) Faith <faith@redhat.com>
36
* Provide support and helper functions for enqueing events received by
37
* the low-level input drivers. */
39
#ifdef HAVE_DMX_CONFIG_H
40
#include <dmx-config.h>
43
#define DMX_EVENTS_DEBUG 0
45
#include "dmxinputinit.h"
46
#include "dmxevents.h"
48
#include "dmxcommon.h"
49
#include "dmxcursor.h"
50
#include "dmxmotion.h"
55
#include <X11/keysym.h>
58
#include "mipointer.h"
64
static int dmxGlobalX, dmxGlobalY; /* Global cursor position */
65
static int dmxGlobalInvalid; /* Flag indicating dmxCoreMotion
66
* should move the mouse anyway. */
69
#define DMXDBG0(f) dmxLog(dmxDebug,f)
70
#define DMXDBG1(f,a) dmxLog(dmxDebug,f,a)
71
#define DMXDBG2(f,a,b) dmxLog(dmxDebug,f,a,b)
72
#define DMXDBG3(f,a,b,c) dmxLog(dmxDebug,f,a,b,c)
73
#define DMXDBG4(f,a,b,c,d) dmxLog(dmxDebug,f,a,b,c,d)
74
#define DMXDBG5(f,a,b,c,d,e) dmxLog(dmxDebug,f,a,b,c,d,e)
75
#define DMXDBG6(f,a,b,c,d,e,g) dmxLog(dmxDebug,f,a,b,c,d,e,g)
76
#define DMXDBG7(f,a,b,c,d,e,g,h) dmxLog(dmxDebug,f,a,b,c,d,e,g,h)
80
#define DMXDBG2(f,a,b)
81
#define DMXDBG3(f,a,b,c)
82
#define DMXDBG4(f,a,b,c,d)
83
#define DMXDBG5(f,a,b,c,d,e)
84
#define DMXDBG6(f,a,b,c,d,e,g)
85
#define DMXDBG7(f,a,b,c,d,e,g,h)
88
static int dmxApplyFunctions(DMXInputInfo *dmxInput, DMXFunctionType f)
93
for (i = 0; i < dmxInput->numDevs; i+= dmxInput->devs[i]->binding)
94
if (dmxInput->devs[i]->functions)
95
rc += dmxInput->devs[i]->functions(dmxInput->devs[i]->private, f);
99
static int dmxCheckFunctionKeys(DMXLocalInputInfoPtr dmxLocal,
103
DMXInputInfo *dmxInput = &dmxInputs[dmxLocal->inputIdx];
104
unsigned short state = 0;
106
if (dmxLocal->sendsCore)
107
state = dmxLocalCoreKeyboard->pDevice->key->state;
108
else if (dmxLocal->pDevice->key)
109
state = dmxLocal->pDevice->key->state;
111
DMXDBG3("dmxCheckFunctionKeys: keySym=0x%04x %s state=0x%04x\n",
112
keySym, type == KeyPress ? "press" : "release", state);
114
if ((state & (ControlMask|Mod1Mask)) != (ControlMask|Mod1Mask)) return 0;
118
if (type == KeyPress)
119
dmxApplyFunctions(dmxInput, DMX_FUNCTION_GRAB);
122
if (type == KeyPress)
123
dmxApplyFunctions(dmxInput, DMX_FUNCTION_FINE);
126
if (type == KeyPress && dmxLocal->sendsCore)
127
if (dmxApplyFunctions(dmxInput, DMX_FUNCTION_TERMINATE)) {
128
dmxLog(dmxInfo, "User request for termination\n");
129
dispatchException |= DE_TERMINATE;
138
static void dmxEnqueueExtEvent(DMXLocalInputInfoPtr dmxLocal, xEvent *e,
142
deviceKeyButtonPointer *xev = (deviceKeyButtonPointer *)xE;
143
deviceValuator *xv = (deviceValuator *)xev+1;
144
DeviceIntPtr pDevice = dmxLocal->pDevice;
145
DMXInputInfo *dmxInput = &dmxInputs[dmxLocal->inputIdx];
146
int type = e->u.u.type;
148
switch (e->u.u.type) {
149
case KeyPress: type = DeviceKeyPress; break;
150
case KeyRelease: type = DeviceKeyRelease; break;
151
case ButtonPress: type = DeviceButtonPress; break;
152
case ButtonRelease: type = DeviceButtonRelease; break;
155
"dmxEnqueueExtEvent: MotionNotify not allowed here\n");
158
if (e->u.u.type == ProximityIn || e->u.u.type == ProximityOut) break;
159
dmxLogInput(dmxInput,
160
"dmxEnqueueExtEvent: Unhandled %s event (%d)\n",
161
e->u.u.type >= LASTEvent ? "extension" : "non-extension",
167
xev->detail = e->u.u.detail;
168
xev->deviceid = pDevice->id | MORE_EVENTS;
169
xev->time = e->u.keyButtonPointer.time;
171
xv->type = DeviceValuator;
172
xv->deviceid = pDevice->id;
173
xv->num_valuators = 0;
174
xv->first_valuator = 0;
176
if (block) dmxSigioBlock();
178
if (block) dmxSigioUnblock();
182
DMXScreenInfo *dmxFindFirstScreen(int x, int y)
186
for (i = 0; i < dmxNumScreens; i++) {
187
DMXScreenInfo *dmxScreen = &dmxScreens[i];
188
if (dmxOnScreen(x, y, dmxScreen)) return dmxScreen;
193
void dmxCoreMotion(int x, int y, int delta, DMXBlockType block)
195
DMXScreenInfo *dmxScreen;
196
DMXInputInfo *dmxInput;
202
if (!dmxGlobalInvalid && dmxGlobalX == x && dmxGlobalY == y) return;
204
DMXDBG5("dmxCoreMotion(%d,%d,%d) dmxGlobalX=%d dmxGlobalY=%d\n",
205
x, y, delta, dmxGlobalX, dmxGlobalY);
207
dmxGlobalInvalid = 0;
211
if (dmxGlobalX < 0) dmxGlobalX = 0;
212
if (dmxGlobalY < 0) dmxGlobalY = 0;
213
if (dmxGlobalX >= dmxGlobalWidth) dmxGlobalX = dmxGlobalWidth + delta -1;
214
if (dmxGlobalY >= dmxGlobalHeight) dmxGlobalY = dmxGlobalHeight + delta -1;
216
if ((dmxScreen = dmxFindFirstScreen(dmxGlobalX, dmxGlobalY))) {
217
localX = dmxGlobalX - dmxScreen->rootXOrigin;
218
localY = dmxGlobalY - dmxScreen->rootYOrigin;
219
if ((pScreen = miPointerCurrentScreen())
220
&& pScreen->myNum == dmxScreen->index) {
221
/* Screen is old screen */
222
if (block) dmxSigioBlock();
223
miPointerAbsoluteCursor(localX, localY, GetTimeInMillis());
224
if (block) dmxSigioUnblock();
227
DMXDBG4(" New screen: old=%d new=%d localX=%d localY=%d\n",
228
pScreen->myNum, dmxScreen->index, localX, localY);
229
if (block) dmxSigioBlock();
230
dmxeqProcessInputEvents();
231
miPointerSetNewScreen(dmxScreen->index, localX, localY);
232
miPointerAbsoluteCursor(localX, localY, GetTimeInMillis());
233
if (block) dmxSigioUnblock();
235
miPointerPosition(&localX, &localY);
237
if ((pScreen = miPointerCurrentScreen())) {
238
dmxGlobalX = localX + dmxScreens[pScreen->myNum].rootXOrigin;
239
dmxGlobalY = localY + dmxScreens[pScreen->myNum].rootYOrigin;
240
DMXDBG6(" Moved to dmxGlobalX=%d dmxGlobalY=%d"
241
" on screen index=%d/%d localX=%d localY=%d\n",
242
dmxGlobalX, dmxGlobalY,
243
dmxScreen ? dmxScreen->index : -1, pScreen->myNum,
247
/* Send updates down to all core input
249
for (i = 0, dmxInput = &dmxInputs[0]; i < dmxNumInputs; i++, dmxInput++) {
252
for (j = 0; j < dmxInput->numDevs; j += dmxInput->devs[j]->binding)
253
if (!dmxInput->detached
254
&& dmxInput->devs[j]->sendsCore
255
&& dmxInput->devs[j]->update_position)
256
dmxInput->devs[j]->update_position(dmxInput->devs[j]->private,
257
dmxGlobalX, dmxGlobalY);
259
if (!dmxScreen) ProcessInputEvents();
263
#define DMX_MAX_AXES 32 /* Max axes reported by this routine */
264
static void dmxExtMotion(DMXLocalInputInfoPtr dmxLocal,
265
int *v, int firstAxis, int axesCount,
266
DMXMotionType type, DMXBlockType block)
268
DeviceIntPtr pDevice = dmxLocal->pDevice;
269
xEvent xE[2 * DMX_MAX_AXES/6];
270
deviceKeyButtonPointer *xev = (deviceKeyButtonPointer *)xE;
271
deviceValuator *xv = (deviceValuator *)xev+1;
277
memset(xE, 0, sizeof(xE));
279
if (axesCount > DMX_MAX_AXES) axesCount = DMX_MAX_AXES;
281
if (!pDevice->valuator->mode && axesCount == 2) {
282
/* The dmx console is a relative mode
283
* device that sometimes reports
284
* absolute motion. It only has two
286
if (type == DMX_RELATIVE) {
289
dmxLocal->lastX += thisX;
290
dmxLocal->lastY += thisY;
291
if (dmxLocal->update_position)
292
dmxLocal->update_position(dmxLocal->private,
293
dmxLocal->lastX, dmxLocal->lastY);
294
} else { /* Convert to relative */
295
if (dmxLocal->lastX || dmxLocal->lastY) {
296
thisX = v[0] - dmxLocal->lastX;
297
thisY = v[1] - dmxLocal->lastY;
299
dmxLocal->lastX = v[0];
300
dmxLocal->lastY = v[1];
306
if (axesCount <= 6) {
307
/* Optimize for the common case when
308
* only 1 or 2 axes change. */
309
xev->time = GetTimeInMillis();
310
xev->type = DeviceMotionNotify;
312
xev->deviceid = pDevice->id | MORE_EVENTS;
314
xv->type = DeviceValuator;
315
xv->deviceid = pDevice->id;
316
xv->num_valuators = axesCount;
317
xv->first_valuator = firstAxis;
318
switch (xv->num_valuators) {
319
case 6: xv->valuator5 = v[5];
320
case 5: xv->valuator4 = v[4];
321
case 4: xv->valuator3 = v[3];
322
case 3: xv->valuator2 = v[2];
323
case 2: xv->valuator1 = v[1];
324
case 1: xv->valuator0 = v[0];
328
for (i = 0, count = 0; i < axesCount; i += 6) {
329
xev->time = GetTimeInMillis();
330
xev->type = DeviceMotionNotify;
332
xev->deviceid = pDevice->id | MORE_EVENTS;
335
xv->type = DeviceValuator;
336
xv->deviceid = pDevice->id;
337
xv->num_valuators = (i+6 >= axesCount ? axesCount - i : 6);
338
xv->first_valuator = firstAxis + i;
339
switch (xv->num_valuators) {
340
case 6: xv->valuator5 = v[i+5];
341
case 5: xv->valuator4 = v[i+4];
342
case 4: xv->valuator3 = v[i+3];
343
case 3: xv->valuator2 = v[i+2];
344
case 2: xv->valuator1 = v[i+1];
345
case 1: xv->valuator0 = v[i+0];
352
if (block) dmxSigioBlock();
353
dmxPointerPutMotionEvent(pDevice, firstAxis, axesCount, v, xev->time);
355
if (block) dmxSigioUnblock();
358
static int dmxTranslateAndEnqueueExtEvent(DMXLocalInputInfoPtr dmxLocal,
359
XEvent *e, DMXBlockType block)
362
deviceKeyButtonPointer *xev = (deviceKeyButtonPointer *)xE;
363
deviceValuator *xv = (deviceValuator *)xev+1;
366
XDeviceKeyEvent *ke = (XDeviceKeyEvent *)e;
367
XDeviceMotionEvent *me = (XDeviceMotionEvent *)e;
369
if (!e) return -1; /* No extended event passed, cannot handle */
371
if ((XID)dmxLocal->deviceId != ke->deviceid) {
372
/* Search for the correct dmxLocal,
373
* since backend and console events are
374
* picked up for the first device on
377
DMXInputInfo *dmxInput = &dmxInputs[dmxLocal->inputIdx];
378
for (i = 0; i < dmxInput->numDevs; i++) {
379
dmxLocal = dmxInput->devs[i];
380
if ((XID)dmxLocal->deviceId == ke->deviceid) break;
384
if ((XID)dmxLocal->deviceId != ke->deviceid
385
|| (type = dmxMapLookup(dmxLocal, e->type)) < 0)
386
return -1; /* No mapping, so this event is unhandled */
389
case XI_DeviceValuator: event = DeviceValuator; break;
390
case XI_DeviceKeyPress: event = DeviceKeyPress; break;
391
case XI_DeviceKeyRelease: event = DeviceKeyRelease; break;
392
case XI_DeviceButtonPress: event = DeviceButtonPress; break;
393
case XI_DeviceButtonRelease: event = DeviceButtonRelease; break;
394
case XI_DeviceMotionNotify: event = DeviceMotionNotify; break;
395
case XI_DeviceFocusIn: event = DeviceFocusIn; break;
396
case XI_DeviceFocusOut: event = DeviceFocusOut; break;
397
case XI_ProximityIn: event = ProximityIn; break;
398
case XI_ProximityOut: event = ProximityOut; break;
399
case XI_DeviceStateNotify: event = DeviceStateNotify; break;
400
case XI_DeviceMappingNotify: event = DeviceMappingNotify; break;
401
case XI_ChangeDeviceNotify: event = ChangeDeviceNotify; break;
402
case XI_DeviceKeystateNotify: event = DeviceStateNotify; break;
403
case XI_DeviceButtonstateNotify: event = DeviceStateNotify; break;
407
case XI_DeviceKeyPress:
408
case XI_DeviceKeyRelease:
409
case XI_DeviceButtonPress:
410
case XI_DeviceButtonRelease:
412
case XI_ProximityOut:
414
xev->detail = ke->keycode; /* same as ->button */
415
xev->deviceid = dmxLocal->pDevice->id | MORE_EVENTS;
416
xev->time = GetTimeInMillis();
418
xv->type = DeviceValuator;
419
xv->deviceid = dmxLocal->pDevice->id;
420
xv->num_valuators = ke->axes_count;
421
xv->first_valuator = ke->first_axis;
422
xv->valuator0 = ke->axis_data[0];
423
xv->valuator1 = ke->axis_data[1];
424
xv->valuator2 = ke->axis_data[2];
425
xv->valuator3 = ke->axis_data[3];
426
xv->valuator4 = ke->axis_data[4];
427
xv->valuator5 = ke->axis_data[5];
429
if (block) dmxSigioBlock();
431
if (block) dmxSigioUnblock();
434
case XI_DeviceMotionNotify:
435
dmxExtMotion(dmxLocal, me->axis_data, me->first_axis, me->axes_count,
436
DMX_ABSOLUTE, block);
438
case XI_DeviceFocusIn:
439
case XI_DeviceFocusOut:
440
case XI_DeviceStateNotify:
441
case XI_DeviceMappingNotify:
442
case XI_ChangeDeviceNotify:
443
case XI_DeviceKeystateNotify:
444
case XI_DeviceButtonstateNotify:
445
/* These are ignored, since DMX will
446
* generate its own events of these
447
* types, as necessary.
449
* Perhaps ChangeDeviceNotify should
450
* generate an error, because it is
453
case XI_DeviceValuator:
456
"XInput extension event (remote=%d -> zero-based=%d)"
457
" not supported yet\n", e->type, type);
464
static int dmxGetButtonMapping(DMXLocalInputInfoPtr dmxLocal, int button)
466
ButtonClassPtr b = dmxLocal->pDevice->button;
468
if (button > b->numButtons) { /* This shouldn't happen. */
469
dmxLog(dmxWarning, "Button %d pressed, but only %d buttons?!?\n",
470
button, b->numButtons);
473
return b->map[button];
476
/** Return DMX's notion of the pointer position in the global coordinate
478
void dmxGetGlobalPosition(int *x, int *y)
484
/** Invalidate the global position for #dmxCoreMotion. */
485
void dmxInvalidateGlobalPosition(void)
487
dmxGlobalInvalid = 1;
490
/** Enqueue a motion event for \a pDev. The \a v vector has length \a
491
* axesCount, and contains values for each of the axes, starting at \a
494
* The \a type of the motion may be \a DMX_RELATIVE, \a DMX_ABSOLUTE, or
495
* \a DMX_ABSOLUTE_CONFINED (in the latter case, the pointer will not be
496
* allowed to move outside the global boundaires).
498
* If \a block is set to \a DMX_BLOCK, then the SIGIO handler will be
499
* blocked around calls to #dmxeqEnqueue(). */
500
void dmxMotion(DevicePtr pDev, int *v, int firstAxes, int axesCount,
501
DMXMotionType type, DMXBlockType block)
506
if (!dmxLocal->sendsCore) {
507
dmxExtMotion(dmxLocal, v, firstAxes, axesCount, type, block);
511
if (axesCount == 2) switch (type) {
512
case DMX_RELATIVE: dmxCoreMotion(dmxGlobalX - v[0],
515
case DMX_ABSOLUTE: dmxCoreMotion(v[0], v[1], 0, block); break;
516
case DMX_ABSOLUTE_CONFINED: dmxCoreMotion(v[0], v[1], -1, block); break;
520
static KeySym dmxKeyCodeToKeySym(DMXLocalInputInfoPtr dmxLocal,
523
KeySymsPtr pKeySyms = NULL;
525
if (!dmxLocal || !dmxLocal->pDevice || !dmxLocal->pDevice->key)
527
pKeySyms = &dmxLocal->pDevice->key->curKeySyms;
528
if (!pKeySyms) return NoSymbol;
530
if (keyCode > pKeySyms->minKeyCode && keyCode <= pKeySyms->maxKeyCode) {
531
DMXDBG2("dmxKeyCodeToKeySym: Translated keyCode=%d to keySym=0x%04x\n",
533
pKeySyms->map[(keyCode - pKeySyms->minKeyCode)
534
* pKeySyms->mapWidth]);
536
return pKeySyms->map[(keyCode - pKeySyms->minKeyCode)
537
* pKeySyms->mapWidth];
542
static KeyCode dmxKeySymToKeyCode(DMXLocalInputInfoPtr dmxLocal, KeySym keySym,
545
KeySymsPtr pKeySyms = &dmxLocal->pDevice->key->curKeySyms;
548
/* Optimize for similar maps */
549
if (tryFirst >= pKeySyms->minKeyCode
550
&& tryFirst <= pKeySyms->maxKeyCode
551
&& pKeySyms->map[(tryFirst - pKeySyms->minKeyCode)
552
* pKeySyms->mapWidth] == keySym)
555
for (i = pKeySyms->minKeyCode; i <= pKeySyms->maxKeyCode; i++) {
556
if (pKeySyms->map[(i - pKeySyms->minKeyCode)
557
* pKeySyms->mapWidth] == keySym) {
558
DMXDBG3("dmxKeySymToKeyCode: Translated keySym=0x%04x to"
559
" keyCode=%d (reverses to core keySym=0x%04x)\n",
560
keySym, i, dmxKeyCodeToKeySym(dmxLocalCoreKeyboard,i));
567
static int dmxFixup(DevicePtr pDev, int detail, KeySym keySym)
572
if (!dmxLocal->pDevice->key) {
573
dmxLog(dmxWarning, "dmxFixup: not a keyboard device (%s)\n",
574
dmxLocal->pDevice->name);
577
if (!keySym) keySym = dmxKeyCodeToKeySym(dmxLocal, detail);
578
if (keySym == NoSymbol) return detail;
579
keyCode = dmxKeySymToKeyCode(dmxLocalCoreKeyboard, keySym, detail);
581
return keyCode ? keyCode : detail;
584
/** Enqueue a non-motion event from the \a pDev device with the
585
* specified \a type and \a detail. If the event is a KeyPress or
586
* KeyRelease event, then the \a keySym is also specified.
588
* If \a block is set to \a DMX_BLOCK, then the SIGIO handler will be
589
* blocked around calls to #dmxeqEnqueue(). */
591
void dmxEnqueue(DevicePtr pDev, int type, int detail, KeySym keySym,
592
XEvent *e, DMXBlockType block)
597
DMXDBG2("dmxEnqueue: Enqueuing type=%d detail=0x%0x\n", type, detail);
602
if (!keySym) keySym = dmxKeyCodeToKeySym(dmxLocal, detail);
603
if (dmxCheckFunctionKeys(dmxLocal, type, keySym))
605
if (dmxLocal->sendsCore && dmxLocal != dmxLocalCoreKeyboard)
606
xE.u.u.detail = dmxFixup(pDev, detail, keySym);
610
detail = dmxGetButtonMapping(dmxLocal, detail);
613
/* All MotionNotify events should be sent via dmxCoreMotion and
614
* dmxExtMotion -- no input driver should build motion events by
616
dmxLog(dmxError, "dmxEnqueueXEvent: MotionNotify not allowed here\n");
618
/* Always ignore these events */
622
case MappingNotify: /* This is sent because we change the
623
* modifier map on the backend/console
624
* input device so that we have complete
625
* control of the input device LEDs. */
629
if (type == ProximityIn || type == ProximityOut) {
630
if (dmxLocal->sendsCore) return; /* Not a core event */
634
if (type >= LASTEvent) {
636
if (dmxTranslateAndEnqueueExtEvent(dmxLocal, e, block))
638
dmxLogInput(dmxInput, "Unhandled extension event: %d\n", type);
640
dmxLogInput(dmxInput, "Unhandled event: %d (%s)\n",
641
type, dmxEventName(type));
646
memset(&xE, 0, sizeof(xE));
648
xE.u.u.detail = detail;
649
xE.u.keyButtonPointer.time = GetTimeInMillis();
652
if (!dmxLocal->sendsCore) dmxEnqueueExtEvent(dmxLocal, &xE, block);
658
/** A pointer to this routine is passed to low-level input drivers so
659
* that all special keychecking is unified to this file. This function
660
* returns 0 if no special keys have been pressed. If the user has
661
* requested termination of the DMX server, -1 is returned. If the user
662
* has requested a switch to a VT, then the (1-based) number of that VT
664
int dmxCheckSpecialKeys(DevicePtr pDev, KeySym keySym)
668
unsigned short state = 0;
670
if (dmxLocal->sendsCore)
671
state = dmxLocalCoreKeyboard->pDevice->key->state;
672
else if (dmxLocal->pDevice->key)
673
state = dmxLocal->pDevice->key->state;
675
if (!dmxLocal->sendsCore) return 0; /* Only for core devices */
677
DMXDBG2("dmxCheckSpecialKeys: keySym=0x%04x state=0x%04x\n", keySym,state);
679
if ((state & (ControlMask|Mod1Mask)) != (ControlMask|Mod1Mask)) return 0;
692
vt = keySym - XK_F1 + 1;
697
vt = keySym - XK_F11 + 11;
700
case XK_q: /* To avoid confusion */
704
dmxLog(dmxInfo, "User request for termination\n");
705
dispatchException |= DE_TERMINATE;
706
return -1; /* Terminate */
710
dmxLog(dmxInfo, "Request to switch to VT %d\n", vt);
711
dmxInput->vt_switch_pending = vt;
715
return 0; /* Do nothing */