| From: Chung-yih Wang <cywang@chromium.org> |
| Date: Thu, 15 Nov 2012 16:17:12 +0800 |
| Subject: [PATCH] x11-drivers/xf86-input-evdev: Add SYN_DROPPED handling |
| |
| If an evdev client cannot consume evdev events in its queue fast enough, the |
| evdev kernel driver will enqueue a SYN_DROPPED event and clear the queue |
| once the client's queue is full. The result is that the X driver will be out |
| of sync with respect to the kernel driver state. The patch tries to handle the |
| SYN_DROPPED event by retrieving the kernel driver's state. Retrieving this |
| state is inherently non-atomic, since it requires a sequence of ioctls. We use |
| a simple before and after time stamping approach to deal with the race |
| condition between partially syncing state and any potentially stale events that |
| arrive during synchronization. |
| --- |
| src/evdev.c | 379 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- |
| src/evdev.h | 17 +++ |
| 2 files changed, 387 insertions(+), 9 deletions(-) |
| |
| diff --git a/src/evdev.c b/src/evdev.c |
| index 41043dc..6cc7f2e 100644 |
| --- a/src/evdev.c |
| +++ b/src/evdev.c |
| @@ -127,11 +127,27 @@ static void EvdevSetCalibration(InputInfoPtr pInfo, int num_calibration, int cal |
| static int EvdevOpenDevice(InputInfoPtr pInfo); |
| static void EvdevCloseDevice(InputInfoPtr pInfo); |
| |
| +static int EvdevInjectEvent(InputInfoPtr pInfo, uint16_t type, |
| + uint16_t code, int32_t value); |
| static void EvdevInitAxesLabels(EvdevPtr pEvdev, int mode, int natoms, Atom *atoms); |
| static void EvdevInitButtonLabels(EvdevPtr pEvdev, int natoms, Atom *atoms); |
| static void EvdevInitProperty(DeviceIntPtr dev); |
| static int EvdevSetProperty(DeviceIntPtr dev, Atom atom, |
| XIPropertyValuePtr val, BOOL checkonly); |
| +static void EvdevSyncState(InputInfoPtr pInfo); |
| +static void EvdevGetKernelTime(struct timeval *current_time, |
| + BOOL use_monotonic); |
| +static int EvdevKeyStateSync(InputInfoPtr pInfo); |
| +static int EvdevAbsAxesSync(InputInfoPtr pInfo); |
| +static int EvdevAbsMtSlotSync(InputInfoPtr pInfo); |
| +static int EvdevInjectAbsMtAxisChangeEvent(InputInfoPtr pInfo, int slot_index, |
| + uint16_t code, int32_t value); |
| +static int EvdevCheckAbsMtAxesChange(InputInfoPtr pInfo, MTSlotInfoPtr slots, |
| + int *count_after_synreport); |
| +static int EvdevGetAllSlotVals(InputInfoPtr pInfo, MTSlotInfoPtr slots); |
| +static int EvdevAbsMtStateSync(InputInfoPtr pInfo, int *count_after_synreport); |
| +static int EvdevAbsStateSync(InputInfoPtr pInfo, int *count_after_synreport); |
| + |
| static Atom prop_product_id; |
| static Atom prop_invert; |
| static Atom prop_calibration; |
| @@ -205,6 +221,11 @@ static inline void EvdevSetBit(unsigned long *array, int bit) |
| array[bit / LONG_BITS] |= (1LL << (bit % LONG_BITS)); |
| } |
| |
| +static inline void EvdevClearBit(unsigned long *array, int bit) |
| +{ |
| + array[bit / LONG_BITS] &= ~(1LL << (bit % LONG_BITS)); |
| +} |
| + |
| static int |
| EvdevGetMajorMinor(InputInfoPtr pInfo) |
| { |
| @@ -660,6 +681,11 @@ EvdevProcessButtonEvent(InputInfoPtr pInfo, struct input_event *ev) |
| /* Get the signed value, earlier kernels had this as unsigned */ |
| value = ev->value; |
| |
| + if (ev->value) |
| + EvdevSetBit(pEvdev->key_state_bitmask, ev->code); |
| + else |
| + EvdevClearBit(pEvdev->key_state_bitmask, ev->code); |
| + |
| /* Handle drag lock */ |
| if (EvdevDragLockFilterEvent(pInfo, button, value)) |
| return; |
| @@ -758,7 +784,9 @@ num_slots(EvdevPtr pEvdev) |
| { |
| int value = pEvdev->absinfo[ABS_MT_SLOT].maximum - |
| pEvdev->absinfo[ABS_MT_SLOT].minimum + 1; |
| - |
| + /* Disable MT reporting if the minimum of slot id is less than 0 */ |
| + if (pEvdev->absinfo[ABS_MT_SLOT].minimum < 0) |
| + return 0; |
| /* If we don't know how many slots there are, assume at least 10 */ |
| return value > 1 ? value : 10; |
| } |
| @@ -767,7 +795,8 @@ static int |
| last_mt_vals_slot(EvdevPtr pEvdev) |
| { |
| int value = pEvdev->cur_slot - pEvdev->absinfo[ABS_MT_SLOT].minimum; |
| - |
| + if (pEvdev->absinfo[ABS_MT_SLOT].minimum < 0) |
| + return -1; |
| return value < num_slots(pEvdev) ? value : -1; |
| } |
| |
| @@ -787,6 +816,8 @@ EvdevProcessTouchEvent(InputInfoPtr pInfo, struct input_event *ev) |
| if (pEvdev->slot_state == SLOTSTATE_EMPTY) |
| pEvdev->slot_state = SLOTSTATE_UPDATE; |
| if (ev->code == ABS_MT_TRACKING_ID) { |
| + if (slot_index >= 0) |
| + pEvdev->cached_tid[slot_index] = ev->value; |
| if (ev->value >= 0) { |
| pEvdev->slot_state = SLOTSTATE_OPEN; |
| |
| @@ -1000,7 +1031,7 @@ static void EvdevPostQueuedEvents(InputInfoPtr pInfo, int num_v, int first_v, |
| * Take the synchronization input event and process it accordingly; the motion |
| * notify events are sent first, then any button/key press/release events. |
| */ |
| -static void |
| +static BOOL |
| EvdevProcessSyncEvent(InputInfoPtr pInfo, struct input_event *ev) |
| { |
| int i; |
| @@ -1008,6 +1039,11 @@ EvdevProcessSyncEvent(InputInfoPtr pInfo, struct input_event *ev) |
| int v[MAX_VALUATORS] = {}; |
| EvdevPtr pEvdev = pInfo->private; |
| |
| + if (ev->code == SYN_DROPPED) { |
| + xf86IDrvMsg(pInfo, X_INFO, "+++ SYN_DROPPED +++\n"); |
| + return TRUE; |
| + } |
| + |
| EvdevProcessProximityState(pInfo); |
| |
| EvdevProcessValuators(pInfo); |
| @@ -1035,16 +1071,20 @@ EvdevProcessSyncEvent(InputInfoPtr pInfo, struct input_event *ev) |
| pEvdev->abs_queued = 0; |
| pEvdev->rel_queued = 0; |
| pEvdev->prox_queued = 0; |
| - |
| + return FALSE; |
| } |
| |
| /** |
| * Process the events from the device; nothing is actually posted to the server |
| - * until an EV_SYN event is received. |
| + * until an EV_SYN event is received. As the SYN_DROPPED event indicates that the |
| + * state of evdev driver will be out of sync with the event queue, additional |
| + * handling is required for processing the SYN_DROPPED event. The function returns |
| + * TRUE if a SYN_DROPPED event is received, FALSE otherwise. |
| */ |
| -static void |
| +static BOOL |
| EvdevProcessEvent(InputInfoPtr pInfo, struct input_event *ev) |
| { |
| + BOOL syn_dropped = FALSE; |
| switch (ev->type) { |
| case EV_REL: |
| EvdevProcessRelativeMotionEvent(pInfo, ev); |
| @@ -1056,9 +1096,10 @@ EvdevProcessEvent(InputInfoPtr pInfo, struct input_event *ev) |
| EvdevProcessKeyEvent(pInfo, ev); |
| break; |
| case EV_SYN: |
| - EvdevProcessSyncEvent(pInfo, ev); |
| + syn_dropped = EvdevProcessSyncEvent(pInfo, ev); |
| break; |
| } |
| + return syn_dropped; |
| } |
| |
| #undef ABS_X_VALUE |
| @@ -1089,6 +1130,308 @@ EvdevFreeMasks(EvdevPtr pEvdev) |
| #endif |
| } |
| |
| +static void |
| +EvdevGetKernelTime(struct timeval *current_time, BOOL use_monotonic) { |
| + struct timespec now; |
| + clockid_t clockid = (use_monotonic) ? CLOCK_MONOTONIC : CLOCK_REALTIME; |
| + |
| + clock_gettime(clockid, &now); |
| + current_time->tv_sec = now.tv_sec; |
| + current_time->tv_usec = now.tv_nsec / 1000; |
| +} |
| + |
| +static int |
| +EvdevInjectEvent(InputInfoPtr pInfo, uint16_t type, uint16_t code, |
| + int32_t value) { |
| + EvdevPtr pEvdev = pInfo->private; |
| + struct input_event ev; |
| + |
| + ev.type = type; |
| + ev.code = code; |
| + ev.value = value; |
| + EvdevGetKernelTime(&ev.time, pEvdev->is_monotonic); |
| + /* Inject the event by processing it */ |
| + EvdevProcessEvent(pInfo, &ev); |
| + return 1; |
| +} |
| + |
| +static int |
| +EvdevKeyStateSync(InputInfoPtr pInfo) { |
| + EvdevPtr pEvdev = pInfo->private; |
| + unsigned long key_state_bitmask[NLONGS(KEY_CNT)]; |
| + int i, ev_count = 0; |
| + int len = sizeof(key_state_bitmask); |
| + |
| + if (ioctl(pInfo->fd, EVIOCGKEY(len), key_state_bitmask) < 0) { |
| + xf86IDrvMsg(pInfo, X_ERROR, |
| + "ioctl EVIOCGKEY failed: %s\n", strerror(errno)); |
| + return !Success; |
| + } |
| + for (i = 0; i < KEY_CNT; i++) { |
| + int orig_value, current_value; |
| + if (!EvdevBitIsSet(pEvdev->key_bitmask, i)) |
| + continue; |
| + orig_value = EvdevBitIsSet(pEvdev->key_state_bitmask, i); |
| + current_value = EvdevBitIsSet(key_state_bitmask, i); |
| + if (current_value == orig_value) |
| + continue; |
| + ev_count += EvdevInjectEvent(pInfo, EV_KEY, i, current_value); |
| + } |
| + return ev_count; |
| +} |
| + |
| +static int |
| +EvdevAbsAxesSync(InputInfoPtr pInfo) { |
| + EvdevPtr device = pInfo->private; |
| + struct input_absinfo absinfo; |
| + int i, ev_count = 0; |
| + |
| + /* Sync all ABS_ axes excluding ABS_MT_ axes */ |
| + for (i = ABS_X; i < ABS_MAX; i++) { |
| + if (i >= ABS_MT_SLOT && i <= _ABS_MT_LAST) |
| + continue; |
| + if (!EvdevBitIsSet(device->abs_bitmask, i)) |
| + continue; |
| + if (ioctl(pInfo->fd, EVIOCGABS(i), &absinfo) < 0) { |
| + xf86IDrvMsg(pInfo, X_ERROR, "ioctl EVIOCGABS(%zu) failed: %s\n", |
| + i, strerror(errno)); |
| + } else if (absinfo.value != device->absinfo[i].value) { |
| + ev_count += EvdevInjectEvent(pInfo, EV_ABS, i, absinfo.value); |
| + } |
| + } |
| + return ev_count; |
| +} |
| + |
| +static int |
| +EvdevAbsMtSlotSync(InputInfoPtr pInfo) { |
| + EvdevPtr device = pInfo->private; |
| + struct input_absinfo absinfo; |
| + int ev_count = 0; |
| + |
| + if (ioctl(pInfo->fd, EVIOCGABS(ABS_MT_SLOT), &absinfo) < 0) { |
| + xf86IDrvMsg(pInfo, X_ERROR, "ioctl EVIOCGABS(ABS_MT_SLOT) failed: %s\n", |
| + strerror(errno)); |
| + return 0; |
| + } |
| + if (device->cur_slot != absinfo.value) |
| + ev_count = EvdevInjectEvent(pInfo, EV_ABS, ABS_MT_SLOT, absinfo.value); |
| + return ev_count; |
| +} |
| + |
| +static int |
| +EvdevInjectAbsMtAxisChangeEvent(InputInfoPtr pInfo, int slot_index, |
| + uint16_t code, int32_t value) { |
| + EvdevPtr device = pInfo->private; |
| + int ev_count = 0; |
| + |
| + if (device->cur_slot != slot_index) |
| + ev_count += EvdevInjectEvent(pInfo, EV_ABS, ABS_MT_SLOT, slot_index); |
| + ev_count += EvdevInjectEvent(pInfo, EV_ABS, code, value); |
| + return ev_count; |
| +} |
| + |
| +static int |
| +EvdevCheckAbsMtAxesChange(InputInfoPtr pInfo, MTSlotInfoPtr slots, |
| + int *count_after_synreport) |
| +{ |
| + EvdevPtr device = pInfo->private; |
| + int i, j, ev_count = 0; |
| + int total_ev_count = 0; |
| + |
| + /* |
| + * There will be five conditions of a slot change after SYN_DROPPED: |
| + * a. Finger leaving, i.e., tracking id changes from a non-negative |
| + * number to -1. |
| + * b. Finger arriving, i.e., tracking id changes from -1 to a |
| + * non-negative number. |
| + * c. Finger changing, i.e., original finger leaving and new finger |
| + * arriving, tracking id changes from a non-negative number to |
| + * another one. |
| + * d. Same finger, but axes change, i.e., no tracking id changes, but some |
| + * axes values have changed. |
| + * e. Fingers arrive and leave: tracking ID was -1, and is still -1, but |
| + * some axes values have changed. |
| + * f. nothing changed |
| + * |
| + * To have X server seamless of SYN_DROPPED event, additional event |
| + * injections will be required except for conditions e and f: |
| + * |
| + * Finger leaving (a): all axes of the slot should be updated first, then |
| + * followed with tracking id change (-1). |
| + * |
| + * Finger arriving (b): new tracking id should be injected first, followed |
| + * with all axes updates. |
| + * |
| + * Finger changing (c): first, inject finger leaving with tracking id -1, |
| + * followed with new tracking id event, then update all axes data. |
| + * |
| + * Same finger, but axes change (d): all axes updates should be injected |
| + * |
| + */ |
| + |
| + for (i = 0; i < num_slots(device); i++) { |
| + int curr_tid = slots[ABS_MT_TRACKING_ID - _ABS_MT_FIRST].values[i]; |
| + int orig_tid = device->cached_tid[i]; |
| + |
| + /* For conditions b and c, inject the tracking id change events first */ |
| + if (orig_tid != curr_tid && curr_tid != -1) { |
| + /* For (c), inject the leaving event for original finger */ |
| + if (orig_tid != -1) { |
| + ev_count += EvdevInjectAbsMtAxisChangeEvent(pInfo, |
| + i, |
| + ABS_MT_TRACKING_ID, |
| + -1); |
| + ev_count += EvdevInjectEvent(pInfo, EV_SYN, SYN_REPORT, 0); |
| + /* Reset the count_after_synreport after SYN_REPORT event */ |
| + total_ev_count += ev_count; |
| + *count_after_synreport = ev_count = 0; |
| + } |
| + /* For (b) and (c), set the new tid before updating axes */ |
| + ev_count += EvdevInjectAbsMtAxisChangeEvent(pInfo, |
| + i, |
| + ABS_MT_TRACKING_ID, |
| + curr_tid); |
| + } |
| + |
| + |
| + for (j = _ABS_MT_FIRST; j <= _ABS_MT_LAST; j++) { |
| + int axis = j - _ABS_MT_FIRST; |
| + int map, orig_value, curr_value; |
| + if ((j == ABS_MT_TRACKING_ID) || |
| + ((map = device->axis_map[j]) == -1)) |
| + continue; |
| + if (!EvdevBitIsSet(device->abs_bitmask, j)) |
| + continue; |
| + |
| + orig_value = valuator_mask_get(device->last_mt_vals[i], map); |
| + curr_value = slots[axis].values[i]; |
| + |
| + if (orig_value == curr_value) |
| + continue; |
| + |
| + /* For condition e, internal axes values should be updated */ |
| + if (orig_tid == -1 && curr_tid == -1) { |
| + valuator_mask_set(device->last_mt_vals[i], map, curr_value); |
| + continue; |
| + } |
| + |
| + /* In addition to condition d, all axes updates will be injected */ |
| + ev_count += EvdevInjectAbsMtAxisChangeEvent(pInfo, |
| + i, |
| + j, |
| + curr_value); |
| + } |
| + |
| + /* For condition a, inject finger leaving event */ |
| + if (orig_tid != -1 && curr_tid == -1) { |
| + ev_count += EvdevInjectAbsMtAxisChangeEvent(pInfo, |
| + i, |
| + ABS_MT_TRACKING_ID, |
| + -1); |
| + } |
| + } |
| + /* Update current slot index if it is different from cur_slot value */ |
| + ev_count += EvdevAbsMtSlotSync(pInfo); |
| + *count_after_synreport += ev_count; |
| + |
| + return total_ev_count + ev_count; |
| +} |
| + |
| +static int |
| +EvdevGetAllSlotVals(InputInfoPtr pInfo, MTSlotInfoPtr slots) |
| +{ |
| + EvdevPtr device = pInfo->private; |
| + int i; |
| + |
| + /* Retrieve current ABS_MT_ axes for all slots */ |
| + for (i = _ABS_MT_FIRST; i <= _ABS_MT_LAST; i++) { |
| + MTSlotInfoPtr req = &slots[i - _ABS_MT_FIRST]; |
| + if (!EvdevBitIsSet(device->abs_bitmask, i)) |
| + continue; |
| + req->code = i; |
| + if (ioctl(pInfo->fd, EVIOCGMTSLOTS((sizeof(*req))), req) < 0) { |
| + xf86IDrvMsg(pInfo, X_ERROR, |
| + "ioctl EVIOCGMTSLOTS(req.code=%d) failed: %s\n", |
| + req->code, strerror(errno)); |
| + return !Success; |
| + } |
| + } |
| + |
| + return Success; |
| +} |
| + |
| +static int |
| +EvdevAbsMtStateSync(InputInfoPtr pInfo, int *count_after_synreport) { |
| + MTSlotInfo slots[_ABS_MT_CNT]; |
| + int ev_count = 0; |
| + |
| + /* Get all current slots axes, then check if there is any update required */ |
| + if (EvdevGetAllSlotVals(pInfo, slots) == Success) { |
| + ev_count = EvdevCheckAbsMtAxesChange(pInfo, slots, |
| + count_after_synreport); |
| + } |
| + |
| + return ev_count; |
| +} |
| + |
| +static int |
| +EvdevAbsStateSync(InputInfoPtr pInfo, int *count_after_synreport) { |
| + EvdevPtr device = pInfo->private; |
| + int ev_count; |
| + |
| + /* Sync all ABS_ axes */ |
| + ev_count = EvdevAbsAxesSync(pInfo); |
| + *count_after_synreport += ev_count; |
| + |
| + /* Sync ABS_MT_ axes for all slots if exists */ |
| + if (device->num_mt_vals) |
| + ev_count += EvdevAbsMtStateSync(pInfo, count_after_synreport); |
| + |
| + return ev_count; |
| +} |
| + |
| +/** |
| + * Synchronize the current state with kernel evdev driver. |
| + */ |
| +static void |
| +EvdevSyncState(InputInfoPtr pInfo) |
| +{ |
| + int ev_count = 0; |
| + int ev_count_after_synreport = 0; |
| + EvdevPtr device = pInfo->private; |
| + |
| + EvdevGetKernelTime(&device->before_sync_time, device->is_monotonic); |
| + |
| + ev_count = EvdevKeyStateSync(pInfo); |
| + ev_count_after_synreport += ev_count; |
| + |
| + /* |
| + * TODO: sync all led, switch and sound states as well. We probably need |
| + * to post events out actively if the new states are different from the |
| + * cached ones. |
| + */ |
| + |
| + /* sync abs and abs_mt value/limits */ |
| + ev_count += EvdevAbsStateSync(pInfo, &ev_count_after_synreport); |
| + |
| + /* |
| + * Push SYN_REPORT event out if there is any event injected |
| + * during the state synchronization. |
| + */ |
| + if (ev_count_after_synreport) |
| + ev_count += EvdevInjectEvent(pInfo, EV_SYN, SYN_REPORT, 0); |
| + |
| + EvdevGetKernelTime(&device->after_sync_time, device->is_monotonic); |
| + |
| + xf86IDrvMsg(pInfo, X_INFO, |
| + "Sync_State: before %ld.%ld after %ld.%ld injected events=%d\n", |
| + device->before_sync_time.tv_sec, |
| + device->before_sync_time.tv_usec, |
| + device->after_sync_time.tv_sec, |
| + device->after_sync_time.tv_usec, |
| + ev_count); |
| +} |
| + |
| /* just a magic number to reduce the number of reads */ |
| #define NUM_EVENTS 16 |
| |
| @@ -1097,6 +1440,7 @@ EvdevReadInput(InputInfoPtr pInfo) |
| { |
| struct input_event ev[NUM_EVENTS]; |
| int i, len = sizeof(ev); |
| + BOOL sync_evdev_state = FALSE; |
| |
| while (len == sizeof(ev)) |
| { |
| @@ -1131,9 +1475,23 @@ EvdevReadInput(InputInfoPtr pInfo) |
| break; |
| } |
| |
| - for (i = 0; i < len/sizeof(ev[0]); i++) |
| - EvdevProcessEvent(pInfo, &ev[i]); |
| + for (i = 0; i < len/sizeof(ev[0]); i++) { |
| + if (sync_evdev_state) |
| + break; |
| + if (timercmp(&ev[i].time, &pEvdev->before_sync_time, <)) { |
| + /* Ignore events before last sync time */ |
| + continue; |
| + } else if (timercmp(&ev[i].time, &pEvdev->after_sync_time, >)) { |
| + /* Event_Process returns TRUE if SYN_DROPPED detected */ |
| + sync_evdev_state = EvdevProcessEvent(pInfo, &ev[i]); |
| + } else { |
| + /* If the event occurred during sync, then sync again */ |
| + sync_evdev_state = TRUE; |
| + } |
| + } |
| } |
| + if (sync_evdev_state) |
| + EvdevSyncState(pInfo); |
| } |
| |
| static void |
| @@ -1325,6 +1683,7 @@ EvdevAddAbsValuatorClass(DeviceIntPtr device) |
| } |
| |
| for (i = 0; i < num_slots(pEvdev); i++) { |
| + pEvdev->cached_tid[i] = -1; |
| pEvdev->last_mt_vals[i] = valuator_mask_new(num_mt_axes_total); |
| if (!pEvdev->last_mt_vals[i]) { |
| xf86IDrvMsg(pInfo, X_ERROR, |
| @@ -1850,6 +2209,8 @@ EvdevOn(DeviceIntPtr device) |
| Evdev3BEmuOn(pInfo); |
| pEvdev->flags |= EVDEV_INITIALIZED; |
| device->public.on = TRUE; |
| + pEvdev->slot_state = SLOTSTATE_EMPTY; |
| + EvdevSyncState(pInfo); |
| |
| return Success; |
| } |
| diff --git a/src/evdev.h b/src/evdev.h |
| index 913561e..fae7c72 100644 |
| --- a/src/evdev.h |
| +++ b/src/evdev.h |
| @@ -97,6 +97,12 @@ |
| /* Number of longs needed to hold the given number of bits */ |
| #define NLONGS(x) (((x) + LONG_BITS - 1) / LONG_BITS) |
| |
| +#define _ABS_MT_FIRST ABS_MT_TOUCH_MAJOR |
| +#define _ABS_MT_LAST ABS_MT_DISTANCE |
| +#define _ABS_MT_CNT (_ABS_MT_LAST - _ABS_MT_FIRST + 1) |
| + |
| +#define MAX_SLOT_COUNT 64 |
| + |
| /* Function key mode */ |
| enum fkeymode { |
| FKEYMODE_UNKNOWN = 0, |
| @@ -250,8 +256,19 @@ typedef struct { |
| EventQueueRec queue[EVDEV_MAXQUEUE]; |
| |
| enum fkeymode fkeymode; |
| + |
| + /* Sync timestamps */ |
| + unsigned long key_state_bitmask[NLONGS(KEY_CNT)]; |
| + struct timeval before_sync_time; |
| + struct timeval after_sync_time; |
| + int32_t cached_tid[MAX_SLOT_COUNT]; |
| } EvdevRec, *EvdevPtr; |
| |
| +typedef struct { |
| + uint32_t code; |
| + int32_t values[MAX_SLOT_COUNT]; |
| +} MTSlotInfo, *MTSlotInfoPtr; |
| + |
| /* Event posting functions */ |
| void EvdevQueueKbdEvent(InputInfoPtr pInfo, struct input_event *ev, int value); |
| void EvdevQueueButtonEvent(InputInfoPtr pInfo, int button, int value); |
| -- |
| 1.8.4.1 |
| |