diff --git a/client/include/evdev.h b/client/include/evdev.h index fa5ab2a3..d4073554 100644 --- a/client/include/evdev.h +++ b/client/include/evdev.h @@ -36,22 +36,31 @@ bool evdev_start(void); void evdev_stop(void); /** - * restore the display server hooks and free the device state; only - * call once display server callbacks have stopped + * free the device state; only call once display server callbacks have stopped */ void evdev_free(void); /** - * grab the keyboard for exclusive access + * update the keyboard and pointer capture requirements */ -void evdev_grabKeyboard(void); +void evdev_setGrab(bool keyboard, bool pointer); /** - * ungrab the keyboard + * return the event fd used to dispatch captured input */ -void evdev_ungrabKeyboard(void); +int evdev_getEventFD(void); /** - * returns true if input should only be processed by evdev + * dispatch captured input on the display server's event thread + */ +void evdev_dispatch(void * opaque); + +/** + * return true if keyboard input should only be processed by evdev */ bool evdev_isExclusive(void); + +/** + * return true if pointer input should only be processed by evdev + */ +bool evdev_isPointerExclusive(void); diff --git a/client/include/input.h b/client/include/input.h index 830029a7..13a891ae 100644 --- a/client/include/input.h +++ b/client/include/input.h @@ -25,6 +25,7 @@ typedef void (*LGInputKeyboardLEDsFn)(void * opaque, bool valid, uint8_t leds); +typedef void (*LGInputAvailabilityFn)(void * opaque, bool available); void lgInput_init(void); void lgInput_free(void); @@ -43,11 +44,20 @@ bool lgInput_supports(LG_InputSupport support); * call back into the input layer. */ void lgInput_setKeyboardLEDsListener( LGInputKeyboardLEDsFn callback, void * opaque); +/* The listener is called synchronously with the current input availability + * and later from transport threads whenever it changes. The callback must + * not call back into the input layer. */ +void lgInput_setAvailabilityListener( + LGInputAvailabilityFn callback, void * opaque); bool lgInput_keyDown(int key); bool lgInput_keyUp(int key); bool lgInput_keyboardLEDs(bool numLock, bool capsLock, bool scrollLock); void lgInput_releaseKeys(void); +void lgInput_releaseButtons(void); +/* Release all tracked keyboard and mouse button state under one active-input + * transaction. */ +void lgInput_releaseAll(void); bool lgInput_mouseMotion(int32_t x, int32_t y); bool lgInput_mousePosition(uint32_t x, uint32_t y, uint32_t width, diff --git a/client/src/app.c b/client/src/app.c index 4c20610d..5d9610ad 100644 --- a/client/src/app.c +++ b/client/src/app.c @@ -174,6 +174,9 @@ bool app_isOverlayMode(void) void app_updateCursorPos(double x, double y) { + if (evdev_isPointerExclusive()) + return; + const bool overlay = app_isOverlayMode(); if (overlay) { @@ -375,7 +378,7 @@ static int mapInputToImGuiButton(uint32_t button) return -1; } -void app_handleButtonPress(int button) +void app_handleButtonPressInternal(int button) { g_cursor.buttons |= (1U << button); app_updateMouseButtons(); @@ -399,7 +402,13 @@ void app_handleButtonPress(int button) DEBUG_ERROR("app_handleButtonPress: failed to send message"); } -void app_handleButtonRelease(int button) +void app_handleButtonPress(int button) +{ + if (!evdev_isPointerExclusive()) + app_handleButtonPressInternal(button); +} + +void app_handleButtonReleaseInternal(int button) { g_cursor.buttons &= ~(1U << button); app_updateMouseButtons(); @@ -422,8 +431,17 @@ void app_handleButtonRelease(int button) DEBUG_ERROR("app_handleButtonRelease: failed to send message"); } +void app_handleButtonRelease(int button) +{ + if (!evdev_isPointerExclusive()) + app_handleButtonReleaseInternal(button); +} + void app_handleWheelMotion(double motion) { + if (evdev_isPointerExclusive()) + return; + if (app_isOverlayMode()) g_state.io->MouseWheel -= motion; } @@ -570,11 +588,15 @@ void app_handleKeyRelease(int sc) void app_handleKeyboardTyped(const char * typed) { + if (evdev_isExclusive()) + return; + app_registerImGuiText(typed); ImGuiIO_AddInputCharactersUTF8(g_state.io, typed); } -void app_handleKeyboardModifiers(bool ctrl, bool shift, bool alt, bool super) +void app_handleKeyboardModifiersInternal( + bool ctrl, bool shift, bool alt, bool super) { g_state.modCtrl = ctrl; g_state.modShift = shift; @@ -582,8 +604,57 @@ void app_handleKeyboardModifiers(bool ctrl, bool shift, bool alt, bool super) g_state.modSuper = super; } +void app_handleKeyboardModifiers(bool ctrl, bool shift, bool alt, bool super) +{ + if (!evdev_isExclusive()) + app_handleKeyboardModifiersInternal(ctrl, shift, alt, super); +} + +void app_handleInputResetInternal(bool keyboard, bool pointer) +{ + if (pointer) + { + g_cursor.buttons = 0; + app_updateMouseButtons(); + g_state.io->MouseDown[ImGuiMouseButton_Left ] = false; + g_state.io->MouseDown[ImGuiMouseButton_Right ] = false; + g_state.io->MouseDown[ImGuiMouseButton_Middle] = false; + } + + if (keyboard) + { + for(int key = 0; key < KEY_MAX; ++key) + if (linux_to_imgui[key]) + ImGuiIO_AddKeyEvent(g_state.io, linux_to_imgui[key], false); + + g_state.modCtrl = false; + g_state.modShift = false; + g_state.modAlt = false; + g_state.modSuper = false; + g_state.escapeActive = false; + g_state.escapeTime = 0; + g_state.escapeAction = -1; + memset(g_state.escapeKeys, 0, sizeof(g_state.escapeKeys)); + memset(g_state.escapeShiftKeys, 0, + sizeof(g_state.escapeShiftKeys)); + atomic_store_explicit( + &g_state.keyModifiers, 0, memory_order_release); + } + + if (keyboard && pointer) + lgInput_releaseAll(); + else if (keyboard) + lgInput_releaseKeys(); + else if (pointer) + lgInput_releaseButtons(); + app_invalidateWindow(false); +} + void app_handleKeyboardLEDs(bool numLock, bool capsLock, bool scrollLock) { + if (evdev_isExclusive()) + return; + if (!core_inputEnabled()) return; @@ -594,6 +665,9 @@ void app_handleKeyboardLEDs(bool numLock, bool capsLock, bool scrollLock) void app_handleMouseRelative(double normx, double normy, double rawx, double rawy) { + if (evdev_isPointerExclusive()) + return; + MTRACE("rel delta=%.3f,%.3f raw=%.3f,%.3f inWin=%d inView=%d " "grab=%d", normx, normy, rawx, rawy, g_cursor.inWindow, g_cursor.inView, g_cursor.grab); diff --git a/client/src/app_internal.h b/client/src/app_internal.h index 56592562..d7144153 100644 --- a/client/src/app_internal.h +++ b/client/src/app_internal.h @@ -25,5 +25,10 @@ void app_handleKeyPressInternal(int scancode); void app_handleKeyReleaseInternal(int scancode); +void app_handleButtonPressInternal(int button); +void app_handleButtonReleaseInternal(int button); +void app_handleKeyboardModifiersInternal( + bool ctrl, bool shift, bool alt, bool super); +void app_handleInputResetInternal(bool keyboard, bool pointer); #endif diff --git a/client/src/core.c b/client/src/core.c index 2537a862..7e21cab1 100644 --- a/client/src/core.c +++ b/client/src/core.c @@ -25,6 +25,7 @@ #include "kb.h" #include "message.h" #include "input.h" +#include "evdev.h" #include "common/time.h" #include "common/debug.h" @@ -140,6 +141,8 @@ bool core_inputEnabled(void) void core_updateKeyboardGrab(void) { const bool inputEnabled = core_inputEnabled(); + const bool localActive = g_state.focused && !app_isOverlayMode() && + !g_state.ignoreInput; /* Explicit capture is local window state and must survive an input provider * becoming unavailable or changing during startup. */ const bool capture = g_cursor.grab && g_params.grabKeyboard && @@ -150,6 +153,7 @@ void core_updateKeyboardGrab(void) const bool active = g_state.focused && !app_isOverlayMode() && (capture || (inputEnabled && (view || automatic))); + evdev_setGrab(active, localActive && g_cursor.grab); if (active) g_state.ds->grabKeyboard(); else diff --git a/client/src/evdev.c b/client/src/evdev.c index aa16243d..8b498f5a 100644 --- a/client/src/evdev.c +++ b/client/src/evdev.c @@ -19,56 +19,201 @@ */ #include "evdev.h" -#include -#include -#include + #include +#include +#include #include +#include +#include +#include #include #include +#include +#include +#include #include "app_internal.h" #include "core.h" -#include "main.h" +#include "input.h" #include "common/debug.h" +#include "common/locking.h" #include "common/option.h" #include "common/stringlist.h" #include "common/thread.h" +#include "common/time.h" + +#define EVDEV_BITS_SIZE(count) (((count) + 7) / 8) +#define EVDEV_QUEUE_SIZE 2048 +#define EVDEV_RETRY_INITIAL_US UINT64_C(250000) +#define EVDEV_RETRY_MAX_US UINT64_C(5000000) +#define EVDEV_GRAB_RETRY_US UINT64_C(1000000) +#define EVDEV_READ_BATCHES 16 +#define EVDEV_EVENT_DRAINS 16 +#define EVDEV_MOTION_LIMIT (INT_MAX / 2) +#define EVDEV_WHEEL_LIMIT 64 +#define EVDEV_GUEST_LEDS_VALID UINT8_C(0x80) + +enum EvdevGrabReason +{ + EVDEV_GRAB_KEYBOARD = 1 << 0, + EVDEV_GRAB_POINTER = 1 << 1, +}; + +#define EVDEV_GRAB_MASK (EVDEV_GRAB_KEYBOARD | EVDEV_GRAB_POINTER) +#define EVDEV_GENERATION_BITS 31 +#define EVDEV_GENERATION_MASK ((UINT64_C(1) << EVDEV_GENERATION_BITS) - 1) +#define EVDEV_KEYBOARD_GEN_SHIFT 2 +#define EVDEV_POINTER_GEN_SHIFT \ + (EVDEV_KEYBOARD_GEN_SHIFT + EVDEV_GENERATION_BITS) + +enum EvdevOutputType +{ + EVDEV_OUTPUT_RESET, + EVDEV_OUTPUT_KEY, + EVDEV_OUTPUT_BUTTON, + EVDEV_OUTPUT_MOTION, + EVDEV_OUTPUT_WHEEL, + EVDEV_OUTPUT_BARRIER, +}; typedef struct { - char * path; - int fd; - bool grabbed; + uint64_t serial; + uint64_t desiredState; + uint64_t keyboardTopology; + uint64_t pointerTopology; + unsigned int lanes; + unsigned int activate; + unsigned int deactivate; + unsigned int handoff; +} +EvdevBarrier; + +typedef struct +{ + enum EvdevOutputType type; + uint64_t generation; + union + { + struct + { + unsigned int code; + bool down; + } + key; + + struct + { + unsigned int button; + bool down; + } + button; + + struct + { + int x; + int y; + } + motion; + + struct + { + int vertical; + int horizontal; + } + wheel; + + EvdevBarrier barrier; + }; +} +EvdevOutput; + +typedef struct +{ + char * path; + int fd; + bool writable; + bool keyboard; + bool pointer; + bool absolutePointer; + uint8_t ledMask; + bool hasWheelHiRes; + bool hasHWheelHiRes; + bool grabbed; + bool dropping; + bool openErrorReported; + bool combinedWarned; + bool hostLEDsValid; + bool ledOverride; + bool ledDirty; + uint8_t hostLEDs; + uint8_t observedLEDs; + uint8_t appliedLEDs; + uint8_t keys[EVDEV_BITS_SIZE(KEY_CNT)]; + uint8_t forwarded[EVDEV_BITS_SIZE(KEY_CNT)]; + int mouseX; + int mouseY; + int wheelHiRes; + int hwheelHiRes; + uint64_t retryAt; + uint64_t retryDelay; + uint64_t grabRetryAt; + uint64_t keyboardGeneration; + uint64_t pointerGeneration; } EvdevDevice; struct EvdevState { - char * deviceList; - EvdevDevice * devices; - int deviceCount; - bool exclusive; - int keys[KEY_MAX]; + char * deviceList; + EvdevDevice * devices; + int deviceCount; + bool exclusive; + unsigned int keys[KEY_CNT]; + unsigned int forwarded[KEY_CNT]; - void (*dsGrabKeyboard)(void); - void (*dsUngrabKeyboard)(void); + int epoll; + int commandEvent; + int outputEvent; + LGThread * thread; + uint64_t observedState; + uint64_t nextBarrier; + EvdevBarrier waitingBarrier; + unsigned int needsBarrierLanes; + unsigned int readyDeactivateLanes; + atomic_uint_fast64_t desiredState; + atomic_uint_fast64_t barrierAck; + atomic_uint_fast64_t keyboardTopology; + atomic_uint_fast64_t pointerTopology; + atomic_bool keyboardActive; + atomic_bool pointerActive; + atomic_bool stop; + atomic_bool availabilityDirty; + atomic_uchar guestLEDState; + bool ledsListenerRegistered; + bool availabilityListenerRegistered; + unsigned int forwardingLanes; - int epoll; - LGThread * thread; - bool grabbed; + LG_Lock outputLock; + bool outputLockInitialized; + EvdevOutput output[EVDEV_QUEUE_SIZE]; + unsigned int outputHead; + unsigned int outputCount; + unsigned int outputOverflows; - enum - { - PENDING_NONE, - PENDING_GRAB, - PENDING_UNGRAB - } - pending; + bool dispatchKeys[KEY_MAX]; + uint32_t dispatchButtons; + bool horizontalWheelWarned; }; -static struct EvdevState state = { .epoll = -1 }; +static struct EvdevState state = +{ + .epoll = -1, + .commandEvent = -1, + .outputEvent = -1, +}; static struct Option options[] = { @@ -90,248 +235,1973 @@ static struct Option options[] = {0} }; +static bool evdev_bit(const uint8_t * bits, unsigned int bit) +{ + return bits[bit / 8] & (UINT8_C(1) << (bit % 8)); +} + +static void evdev_setBit(uint8_t * bits, unsigned int bit, bool value) +{ + const uint8_t mask = UINT8_C(1) << (bit % 8); + if (value) + bits[bit / 8] |= mask; + else + bits[bit / 8] &= ~mask; +} + +static int evdev_addLimited(int value, int delta, int limit) +{ + const int64_t result = (int64_t)value + delta; + if (result > limit) + return limit; + if (result < -limit) + return -limit; + return result; +} + +static uint64_t evdev_generation(uint64_t desiredState, + unsigned int lane) +{ + const unsigned int shift = lane == EVDEV_GRAB_KEYBOARD ? + EVDEV_KEYBOARD_GEN_SHIFT : EVDEV_POINTER_GEN_SHIFT; + return (desiredState >> shift) & EVDEV_GENERATION_MASK; +} + +static uint64_t evdev_updateDesired(uint64_t current, + unsigned int reasons, unsigned int lanes) +{ + uint64_t next = (current & ~EVDEV_GRAB_MASK) | reasons; + for(unsigned int lane = EVDEV_GRAB_KEYBOARD; + lane <= EVDEV_GRAB_POINTER; lane <<= 1) + { + if (!(lanes & lane)) + continue; + + const unsigned int shift = lane == EVDEV_GRAB_KEYBOARD ? + EVDEV_KEYBOARD_GEN_SHIFT : EVDEV_POINTER_GEN_SHIFT; + const uint64_t generation = + (evdev_generation(current, lane) + 1) & EVDEV_GENERATION_MASK; + next &= ~(EVDEV_GENERATION_MASK << shift); + next |= generation << shift; + } + + return next; +} + +static bool evdev_laneStateMatches(uint64_t current, uint64_t expected, + unsigned int lanes) +{ + for(unsigned int lane = EVDEV_GRAB_KEYBOARD; + lane <= EVDEV_GRAB_POINTER; lane <<= 1) + if ((lanes & lane) && + (((current ^ expected) & lane) || + evdev_generation(current, lane) != + evdev_generation(expected, lane))) + return false; + + return true; +} + +static unsigned int evdev_barrierStateLanes( + const EvdevBarrier * barrier) +{ + return barrier->lanes | barrier->activate | + barrier->deactivate | barrier->handoff; +} + +static bool evdev_barrierMatches( + const EvdevBarrier * barrier, uint64_t desiredState) +{ + const unsigned int lanes = evdev_barrierStateLanes(barrier); + if (!evdev_laneStateMatches( + desiredState, barrier->desiredState, lanes)) + return false; + + if ((lanes & EVDEV_GRAB_KEYBOARD) && + atomic_load_explicit(&state.keyboardTopology, + memory_order_acquire) != barrier->keyboardTopology) + return false; + if ((lanes & EVDEV_GRAB_POINTER) && + atomic_load_explicit(&state.pointerTopology, + memory_order_acquire) != barrier->pointerTopology) + return false; + + return true; +} + +static uint64_t evdev_nextBarrier(void) +{ + uint64_t serial = ++state.nextBarrier; + if (!serial) + serial = ++state.nextBarrier; + return serial; +} + +static void evdev_signal(int fd, const char * name); +static unsigned int evdev_deviceLanes(const EvdevDevice * device); +static unsigned int evdev_deviceForwardingLanes( + const EvdevDevice * device); + +static uint8_t evdev_convertLEDs(const uint8_t * leds) +{ + uint8_t result = 0; + if (evdev_bit(leds, LED_NUML)) + result |= LG_KEYBOARD_LED_NUM_LOCK; + if (evdev_bit(leds, LED_CAPSL)) + result |= LG_KEYBOARD_LED_CAPS_LOCK; + if (evdev_bit(leds, LED_SCROLLL)) + result |= LG_KEYBOARD_LED_SCROLL_LOCK; + if (evdev_bit(leds, LED_COMPOSE)) + result |= LG_KEYBOARD_LED_COMPOSE; + if (evdev_bit(leds, LED_KANA)) + result |= LG_KEYBOARD_LED_KANA; + return result; +} + +static bool evdev_readLEDs(EvdevDevice * device, uint8_t * result) +{ + uint8_t leds[EVDEV_BITS_SIZE(LED_CNT)] = {0}; + if (ioctl(device->fd, EVIOCGLED(sizeof(leds)), leds) < 0) + { + DEBUG_WARN("Failed to query keyboard LEDs for %s: %s", + device->path, strerror(errno)); + return false; + } + + *result = evdev_convertLEDs(leds); + return true; +} + +static bool evdev_writeLEDs(EvdevDevice * device, uint8_t leds) +{ + struct input_event events[6] = {0}; + int count = 0; + if (device->ledMask & LG_KEYBOARD_LED_NUM_LOCK) + events[count++] = (struct input_event) + { + .type = EV_LED, + .code = LED_NUML, + .value = !!(leds & LG_KEYBOARD_LED_NUM_LOCK), + }; + if (device->ledMask & LG_KEYBOARD_LED_CAPS_LOCK) + events[count++] = (struct input_event) + { + .type = EV_LED, + .code = LED_CAPSL, + .value = !!(leds & LG_KEYBOARD_LED_CAPS_LOCK), + }; + if (device->ledMask & LG_KEYBOARD_LED_SCROLL_LOCK) + events[count++] = (struct input_event) + { + .type = EV_LED, + .code = LED_SCROLLL, + .value = !!(leds & LG_KEYBOARD_LED_SCROLL_LOCK), + }; + if (device->ledMask & LG_KEYBOARD_LED_COMPOSE) + events[count++] = (struct input_event) + { + .type = EV_LED, + .code = LED_COMPOSE, + .value = !!(leds & LG_KEYBOARD_LED_COMPOSE), + }; + if (device->ledMask & LG_KEYBOARD_LED_KANA) + events[count++] = (struct input_event) + { + .type = EV_LED, + .code = LED_KANA, + .value = !!(leds & LG_KEYBOARD_LED_KANA), + }; + events[count++] = (struct input_event) + { + .type = EV_SYN, + .code = SYN_REPORT, + }; + + const size_t expected = count * sizeof(*events); + const ssize_t size = write(device->fd, events, expected); + if (size == (ssize_t)expected) + return true; + + DEBUG_WARN("Failed to set keyboard LEDs for %s: %s", device->path, + size < 0 ? strerror(errno) : "incomplete write"); + return false; +} + +static void evdev_restoreLEDs(EvdevDevice * device) +{ + if (!device->ledOverride) + return; + + if (device->hostLEDsValid && device->writable && + !evdev_writeLEDs(device, device->hostLEDs)) + return; + + if (device->hostLEDsValid) + device->observedLEDs = device->hostLEDs; + device->ledOverride = false; + device->ledDirty = false; + device->appliedLEDs = UINT8_MAX; +} + +static bool evdev_anyLEDOverride(const EvdevDevice * except) +{ + for(int i = 0; i < state.deviceCount; ++i) + { + const EvdevDevice * device = &state.devices[i]; + if (device != except && device->ledOverride) + return true; + } + + return false; +} + +static void evdev_updateLEDs(EvdevDevice * device) +{ + const uint8_t guestState = atomic_load_explicit( + &state.guestLEDState, memory_order_acquire); + const bool valid = guestState & EVDEV_GUEST_LEDS_VALID; + const uint8_t leds = guestState & ~EVDEV_GUEST_LEDS_VALID; + if (!device->grabbed || !device->keyboard || !device->ledMask || + !device->writable || !valid) + { + evdev_restoreLEDs(device); + + if (!device->grabbed && device->keyboard && device->ledMask && + !device->hostLEDsValid && !evdev_anyLEDOverride(device) && + evdev_readLEDs(device, &device->hostLEDs)) + { + device->observedLEDs = device->hostLEDs; + device->hostLEDsValid = true; + } + return; + } + + if (!device->ledOverride) + { + /* The host state is tracked while the device is ungrabbed. Do not query + * it after another event node may already have applied the guest state. */ + if (!device->hostLEDsValid) + return; + + device->ledOverride = true; + device->ledDirty = false; + device->appliedLEDs = UINT8_MAX; + } + + if ((device->appliedLEDs != leds || device->ledDirty) && + evdev_writeLEDs(device, leds)) + { + device->appliedLEDs = leds; + device->ledDirty = false; + } +} + +static void evdev_keyboardLEDs(void * opaque, bool valid, uint8_t leds) +{ + (void)opaque; + leds &= LG_KEYBOARD_LED_NUM_LOCK | + LG_KEYBOARD_LED_CAPS_LOCK | + LG_KEYBOARD_LED_SCROLL_LOCK | + LG_KEYBOARD_LED_COMPOSE | + LG_KEYBOARD_LED_KANA; + atomic_store_explicit(&state.guestLEDState, + leds | (valid ? EVDEV_GUEST_LEDS_VALID : 0), memory_order_release); + evdev_signal(state.commandEvent, "command"); +} + +static void evdev_availabilityChanged( + void * opaque, bool available) +{ + (void)opaque; + (void)available; + atomic_store_explicit(&state.availabilityDirty, true, + memory_order_release); + evdev_signal(state.outputEvent, "output"); +} + +static void evdev_signal(int fd, const char * name) +{ + if (fd < 0) + return; + + const uint64_t value = 1; + if (write(fd, &value, sizeof(value)) < 0 && + errno != EAGAIN && errno != EBADF) + DEBUG_WARN("Failed to signal the evdev %s event: %s", + name, strerror(errno)); +} + +static void evdev_drainEvent(int fd) +{ + uint64_t value; + for(unsigned int i = 0; i < EVDEV_EVENT_DRAINS; ++i) + { + const ssize_t size = read(fd, &value, sizeof(value)); + if (size == (ssize_t)sizeof(value)) + return; + if (size >= 0 || errno != EINTR) + return; + } +} + void evdev_earlyInit(void) { option_register(options); } -static bool evdev_grabDevice(EvdevDevice * device) +static bool evdev_isMouseButton(unsigned int code) { - if (device->grabbed) - return true; + return code >= BTN_MOUSE && code <= BTN_TASK; +} - if (ioctl(device->fd, EVIOCGRAB, (void *)1) < 0) +static unsigned int evdev_codeLane(unsigned int code) +{ + return evdev_isMouseButton(code) ? + EVDEV_GRAB_POINTER : EVDEV_GRAB_KEYBOARD; +} + +static void evdev_clearForwarded(void) +{ + memset(state.forwarded, 0, sizeof(state.forwarded)); + for(int i = 0; i < state.deviceCount; ++i) + memset(state.devices[i].forwarded, 0, + sizeof(state.devices[i].forwarded)); +} + +static bool evdev_queueOutput(const EvdevOutput * output) +{ + bool overflow = false; + + LG_LOCK(state.outputLock); + if (state.outputCount) { - DEBUG_ERROR("EVIOCGRAB=1 failed: %s", strerror(errno)); + const unsigned int lastIndex = + (state.outputHead + state.outputCount - 1) % EVDEV_QUEUE_SIZE; + EvdevOutput * last = &state.output[lastIndex]; + + if (last->type == output->type && + last->generation == output->generation && + output->type == EVDEV_OUTPUT_MOTION) + { + last->motion.x = evdev_addLimited(last->motion.x, + output->motion.x, EVDEV_MOTION_LIMIT); + last->motion.y = evdev_addLimited(last->motion.y, + output->motion.y, EVDEV_MOTION_LIMIT); + LG_UNLOCK(state.outputLock); + evdev_signal(state.outputEvent, "output"); + return true; + } + + if (last->type == output->type && + last->generation == output->generation && + output->type == EVDEV_OUTPUT_WHEEL) + { + last->wheel.vertical = evdev_addLimited(last->wheel.vertical, + output->wheel.vertical, EVDEV_WHEEL_LIMIT); + last->wheel.horizontal = evdev_addLimited(last->wheel.horizontal, + output->wheel.horizontal, EVDEV_WHEEL_LIMIT); + LG_UNLOCK(state.outputLock); + evdev_signal(state.outputEvent, "output"); + return true; + } + } + + if (state.outputCount == EVDEV_QUEUE_SIZE) + { + state.outputHead = 0; + if (state.waitingBarrier.serial) + { + const uint64_t serial = evdev_nextBarrier(); + state.waitingBarrier.serial = serial; + state.outputCount = 2; + state.output[0] = (EvdevOutput) + { + .type = EVDEV_OUTPUT_RESET, + .barrier = { .lanes = EVDEV_GRAB_MASK }, + }; + state.output[1] = (EvdevOutput) + { + .type = EVDEV_OUTPUT_BARRIER, + .barrier = state.waitingBarrier, + }; + } + else + { + state.outputCount = 1; + state.output[0].type = EVDEV_OUTPUT_RESET; + state.output[0].barrier.lanes = EVDEV_GRAB_MASK; + } + ++state.outputOverflows; + overflow = true; + } + else + { + const unsigned int index = + (state.outputHead + state.outputCount) % EVDEV_QUEUE_SIZE; + state.output[index] = *output; + ++state.outputCount; + } + LG_UNLOCK(state.outputLock); + + if (overflow) + { + evdev_clearForwarded(); + DEBUG_WARN("evdev output queue overflowed; input state was reset"); + } + + evdev_signal(state.outputEvent, "output"); + return !overflow; +} + +static void evdev_queueKey( + const EvdevDevice * device, unsigned int code, bool down) +{ + if (evdev_isMouseButton(code)) + { + static const unsigned int map[] = {1, 3, 2, 6, 7, 8, 9, 10}; + const EvdevOutput output = + { + .type = EVDEV_OUTPUT_BUTTON, + .generation = device->pointerGeneration, + .button = + { + .button = map[code - BTN_MOUSE], + .down = down, + }, + }; + evdev_queueOutput(&output); + return; + } + + /* The application key-state arrays intentionally exclude KEY_MAX. */ + if (code == KEY_RESERVED || code >= BTN_MISC || code >= KEY_MAX) + return; + + const EvdevOutput output = + { + .type = EVDEV_OUTPUT_KEY, + .generation = device->keyboardGeneration, + .key = + { + .code = code, + .down = down, + }, + }; + evdev_queueOutput(&output); +} + +static void evdev_cancelOutput(void) +{ + state.waitingBarrier = (EvdevBarrier){0}; + state.needsBarrierLanes = 0; + state.readyDeactivateLanes = 0; + + LG_LOCK(state.outputLock); + state.outputHead = 0; + state.outputCount = 1; + state.output[0] = (EvdevOutput) + { + .type = EVDEV_OUTPUT_RESET, + .barrier = { .lanes = EVDEV_GRAB_MASK }, + }; + LG_UNLOCK(state.outputLock); + + evdev_clearForwarded(); + evdev_signal(state.outputEvent, "output"); +} + +static void evdev_updateKey(EvdevDevice * device, unsigned int code, + bool down, bool forward) +{ + if (code >= KEY_CNT) + return; + + const bool wasDown = evdev_bit(device->keys, code); + if (wasDown != down) + { + evdev_setBit(device->keys, code, down); + if (down) + ++state.keys[code]; + else if (state.keys[code]) + --state.keys[code]; + } + + const bool wasForwarded = evdev_bit(device->forwarded, code); + if (down && forward) + { + if (wasForwarded) + return; + + evdev_setBit(device->forwarded, code, true); + if (++state.forwarded[code] == 1) + evdev_queueKey(device, code, true); + return; + } + + if (!wasForwarded) + return; + + evdev_setBit(device->forwarded, code, false); + if (state.forwarded[code] && --state.forwarded[code] == 0) + evdev_queueKey(device, code, false); +} + +static bool evdev_syncKeys( + EvdevDevice * device, unsigned int forwardLanes) +{ + uint8_t keys[EVDEV_BITS_SIZE(KEY_CNT)] = {0}; + if (ioctl(device->fd, EVIOCGKEY(sizeof(keys)), keys) < 0) + { + if (errno != ENODEV) + DEBUG_WARN("Failed to query key state for %s: %s", + device->path, strerror(errno)); return false; } - DEBUG_INFO("Grabbed %s", device->path); - device->grabbed = true; + for(unsigned int code = 0; code < KEY_CNT; ++code) + evdev_updateKey(device, code, evdev_bit(keys, code), + forwardLanes & evdev_codeLane(code)); + return true; } -static bool evdev_openDevice(EvdevDevice * device, bool quiet) +static void evdev_resetTransient(EvdevDevice * device) { - device->fd = open(device->path, O_RDWR); + device->mouseX = 0; + device->mouseY = 0; + device->wheelHiRes = 0; + device->hwheelHiRes = 0; + device->dropping = false; +} + +static void evdev_releaseState(EvdevDevice * device, bool forward) +{ + for(unsigned int code = 0; code < KEY_CNT; ++code) + { + if (evdev_bit(device->forwarded, code)) + { + evdev_setBit(device->forwarded, code, false); + if (state.forwarded[code] && --state.forwarded[code] == 0 && forward) + evdev_queueKey(device, code, false); + } + + if (evdev_bit(device->keys, code)) + { + evdev_setBit(device->keys, code, false); + if (state.keys[code]) + --state.keys[code]; + } + } + + evdev_resetTransient(device); +} + +static bool evdev_lanePublished(unsigned int lane) +{ + if (atomic_load_explicit(&state.stop, memory_order_acquire)) + return false; + + const atomic_bool * active = lane == EVDEV_GRAB_KEYBOARD ? + &state.keyboardActive : &state.pointerActive; + return atomic_load_explicit(active, memory_order_acquire); +} + +static void evdev_publishActive(unsigned int lanes, bool active) +{ + if (lanes & EVDEV_GRAB_KEYBOARD) + atomic_store_explicit(&state.keyboardActive, active, + memory_order_release); + if (lanes & EVDEV_GRAB_POINTER) + atomic_store_explicit(&state.pointerActive, active, + memory_order_release); +} + +static void evdev_topologyChanged(unsigned int lanes) +{ + if (!lanes) + return; + + if (lanes & EVDEV_GRAB_KEYBOARD) + atomic_fetch_add_explicit( + &state.keyboardTopology, 1, memory_order_acq_rel); + if (lanes & EVDEV_GRAB_POINTER) + atomic_fetch_add_explicit( + &state.pointerTopology, 1, memory_order_acq_rel); + state.readyDeactivateLanes &= ~lanes; + state.needsBarrierLanes |= lanes; +} + +static void evdev_closeDevice(EvdevDevice * device, bool removed) +{ + if (device->fd < 0) + return; + + if (removed) + DEBUG_WARN("Device was removed: %s", device->path); + + const unsigned int lanes = evdev_deviceLanes(device); + evdev_releaseState(device, true); + evdev_restoreLEDs(device); + epoll_ctl(state.epoll, EPOLL_CTL_DEL, device->fd, NULL); + close(device->fd); + + device->fd = -1; + device->writable = false; + device->keyboard = false; + device->pointer = false; + device->absolutePointer = false; + device->ledMask = 0; + device->grabbed = false; + device->hostLEDsValid = false; + device->ledOverride = false; + device->ledDirty = false; + device->observedLEDs = 0; + device->appliedLEDs = UINT8_MAX; + device->grabRetryAt = 0; + device->retryAt = microtime() + EVDEV_RETRY_INITIAL_US; + device->retryDelay = EVDEV_RETRY_INITIAL_US; + evdev_topologyChanged(lanes); +} + +static void evdev_scheduleRetry(EvdevDevice * device, uint64_t now) +{ + device->retryAt = now + device->retryDelay; + device->retryDelay *= 2; + if (device->retryDelay > EVDEV_RETRY_MAX_US) + device->retryDelay = EVDEV_RETRY_MAX_US; +} + +static bool evdev_queryCapabilities(EvdevDevice * device) +{ + uint8_t events[EVDEV_BITS_SIZE(EV_CNT)] = {0}; + uint8_t keys [EVDEV_BITS_SIZE(KEY_CNT)] = {0}; + uint8_t rel [EVDEV_BITS_SIZE(REL_CNT)] = {0}; + uint8_t abs [EVDEV_BITS_SIZE(ABS_CNT)] = {0}; + uint8_t leds [EVDEV_BITS_SIZE(LED_CNT)] = {0}; + + if (ioctl(device->fd, EVIOCGBIT(0, sizeof(events)), events) < 0) + { + DEBUG_ERROR("Failed to query capabilities for %s: %s", + device->path, strerror(errno)); + return false; + } + + if (evdev_bit(events, EV_KEY) && + ioctl(device->fd, EVIOCGBIT(EV_KEY, sizeof(keys)), keys) < 0) + { + DEBUG_ERROR("Failed to query keys for %s: %s", + device->path, strerror(errno)); + return false; + } + + if (evdev_bit(events, EV_REL) && + ioctl(device->fd, EVIOCGBIT(EV_REL, sizeof(rel)), rel) < 0) + { + DEBUG_ERROR("Failed to query relative axes for %s: %s", + device->path, strerror(errno)); + return false; + } + + if (evdev_bit(events, EV_ABS) && + ioctl(device->fd, EVIOCGBIT(EV_ABS, sizeof(abs)), abs) < 0) + { + DEBUG_ERROR("Failed to query absolute axes for %s: %s", + device->path, strerror(errno)); + return false; + } + + if (evdev_bit(events, EV_LED) && + ioctl(device->fd, EVIOCGBIT(EV_LED, sizeof(leds)), leds) < 0) + { + DEBUG_ERROR("Failed to query LEDs for %s: %s", + device->path, strerror(errno)); + return false; + } + + device->keyboard = false; + for(unsigned int code = KEY_ESC; code < BTN_MISC; ++code) + if (evdev_bit(keys, code)) + { + device->keyboard = true; + break; + } + + device->hasWheelHiRes = evdev_bit(rel, REL_WHEEL_HI_RES); + device->hasHWheelHiRes = evdev_bit(rel, REL_HWHEEL_HI_RES); + const bool relativePointer = + evdev_bit(rel, REL_X) || evdev_bit(rel, REL_Y); + const bool wheelPointer = + evdev_bit(rel, REL_WHEEL) || + evdev_bit(rel, REL_HWHEEL) || + device->hasWheelHiRes || + device->hasHWheelHiRes; + device->absolutePointer = + evdev_bit(abs, ABS_X) || evdev_bit(abs, ABS_Y); + device->pointer = relativePointer || wheelPointer; + for(unsigned int code = BTN_MOUSE; code <= BTN_TASK; ++code) + device->pointer = device->pointer || evdev_bit(keys, code); + if (device->absolutePointer && !relativePointer) + { + device->pointer = false; + DEBUG_WARN("Absolute evdev pointer will be handled by the display " + "server: %s", device->path); + } + + device->ledMask = 0; + if (evdev_bit(leds, LED_NUML)) + device->ledMask |= LG_KEYBOARD_LED_NUM_LOCK; + if (evdev_bit(leds, LED_CAPSL)) + device->ledMask |= LG_KEYBOARD_LED_CAPS_LOCK; + if (evdev_bit(leds, LED_SCROLLL)) + device->ledMask |= LG_KEYBOARD_LED_SCROLL_LOCK; + if (evdev_bit(leds, LED_COMPOSE)) + device->ledMask |= LG_KEYBOARD_LED_COMPOSE; + if (evdev_bit(leds, LED_KANA)) + device->ledMask |= LG_KEYBOARD_LED_KANA; + + const char * type = device->keyboard && device->pointer ? + "keyboard, pointer" : + device->keyboard && device->absolutePointer ? + "keyboard, absolute pointer" : device->keyboard ? "keyboard" : + device->pointer ? "pointer" : device->absolutePointer ? + "absolute pointer" : "other"; + DEBUG_INFO("Opened %s (%s)", device->path, type); + return true; +} + +static bool evdev_openDevice(EvdevDevice * device, uint64_t now) +{ + if (device->fd >= 0 || now < device->retryAt) + return device->fd >= 0; + + device->fd = open(device->path, O_RDWR | O_NONBLOCK | O_CLOEXEC); + device->writable = device->fd >= 0; if (device->fd < 0) { - if (errno != ENOENT || !quiet) - DEBUG_ERROR("Unable to open %s (%s)", device->path, strerror(errno)); - goto err; + device->fd = open(device->path, + O_RDONLY | O_NONBLOCK | O_CLOEXEC); + device->writable = false; } + if (device->fd < 0) + { + if (!device->openErrorReported) + { + DEBUG_ERROR("Unable to open %s (%s)", device->path, strerror(errno)); + device->openErrorReported = true; + } + + evdev_scheduleRetry(device, now); + return false; + } + + if (!evdev_queryCapabilities(device)) + goto err; + struct epoll_event event = { - .events = EPOLLIN, + .events = EPOLLIN | EPOLLERR | EPOLLHUP, .data.ptr = device }; if (epoll_ctl(state.epoll, EPOLL_CTL_ADD, device->fd, &event) != 0) { - DEBUG_ERROR("Failed to add fd to epoll"); + DEBUG_ERROR("Failed to add %s to epoll: %s", + device->path, strerror(errno)); goto err; } - DEBUG_INFO("Opened: %s", device->path); - - if (state.grabbed) - evdev_grabDevice(device); + device->openErrorReported = false; + device->retryAt = 0; + device->retryDelay = EVDEV_RETRY_INITIAL_US; + if (!evdev_syncKeys(device, 0)) + goto errEpoll; + if (device->ledMask && !evdev_anyLEDOverride(device)) + { + device->hostLEDsValid = evdev_readLEDs(device, &device->hostLEDs); + if (device->hostLEDsValid) + device->observedLEDs = device->hostLEDs; + } + else + device->hostLEDsValid = false; + evdev_topologyChanged(evdev_deviceLanes(device)); return true; +errEpoll: + epoll_ctl(state.epoll, EPOLL_CTL_DEL, device->fd, NULL); err: close(device->fd); - device->fd = 0; + device->fd = -1; + device->writable = false; + evdev_scheduleRetry(device, now); return false; } +static bool evdev_neutral(const EvdevDevice * device) +{ + for(unsigned int code = 0; code < KEY_CNT; ++code) + if (evdev_bit(device->keys, code)) + return false; + + return true; +} + +static bool evdev_shouldGrab(const EvdevDevice * device, + unsigned int reasons) +{ + const bool combined = device->keyboard && + (device->pointer || device->absolutePointer); + if (combined && (reasons & EVDEV_GRAB_MASK) != EVDEV_GRAB_MASK) + return false; + + if ((reasons & EVDEV_GRAB_POINTER) && device->pointer) + return true; + + if ((reasons & EVDEV_GRAB_KEYBOARD) && device->keyboard) + return !device->absolutePointer; + + return false; +} + +static unsigned int evdev_deviceLanes(const EvdevDevice * device) +{ + return + (device->keyboard ? EVDEV_GRAB_KEYBOARD : 0) | + ((device->pointer || device->absolutePointer) ? + EVDEV_GRAB_POINTER : 0); +} + +static void evdev_setDeviceGenerations(EvdevDevice * device, + uint64_t desiredState, unsigned int lanes) +{ + const unsigned int deviceLanes = evdev_deviceLanes(device); + if ((lanes & deviceLanes) & EVDEV_GRAB_KEYBOARD) + device->keyboardGeneration = + evdev_generation(desiredState, EVDEV_GRAB_KEYBOARD); + if ((lanes & deviceLanes) & EVDEV_GRAB_POINTER) + device->pointerGeneration = + evdev_generation(desiredState, EVDEV_GRAB_POINTER); +} + +static unsigned int evdev_changedLanes(uint64_t oldState, + uint64_t newState) +{ + unsigned int lanes = 0; + for(unsigned int lane = EVDEV_GRAB_KEYBOARD; + lane <= EVDEV_GRAB_POINTER; lane <<= 1) + if (!evdev_laneStateMatches(oldState, newState, lane)) + lanes |= lane; + return lanes; +} + +static unsigned int evdev_publishedLanes(void) +{ + return + (evdev_lanePublished(EVDEV_GRAB_KEYBOARD) ? + EVDEV_GRAB_KEYBOARD : 0) | + (evdev_lanePublished(EVDEV_GRAB_POINTER) ? + EVDEV_GRAB_POINTER : 0); +} + +static unsigned int evdev_targetActiveLanes(unsigned int reasons) +{ + bool keyboardSeen = false; + bool keyboardActive = true; + bool pointerSeen = false; + bool pointerActive = true; + for(int i = 0; i < state.deviceCount; ++i) + { + const EvdevDevice * device = &state.devices[i]; + if (device->fd < 0) + continue; + + const bool active = device->grabbed && + evdev_shouldGrab(device, reasons); + if (device->keyboard) + { + keyboardSeen = true; + keyboardActive = keyboardActive && active; + } + if (device->pointer || device->absolutePointer) + { + pointerSeen = true; + pointerActive = pointerActive && active; + } + } + + return + (keyboardSeen && keyboardActive && (reasons & EVDEV_GRAB_KEYBOARD) ? + EVDEV_GRAB_KEYBOARD : 0) | + (pointerSeen && pointerActive && (reasons & EVDEV_GRAB_POINTER) ? + EVDEV_GRAB_POINTER : 0); +} + +static unsigned int evdev_coupleLanes( + unsigned int lanes, unsigned int reasons) +{ + unsigned int previous; + do + { + previous = lanes; + for(int i = 0; i < state.deviceCount; ++i) + { + const EvdevDevice * device = &state.devices[i]; + if (device->fd < 0 || !evdev_shouldGrab(device, reasons)) + continue; + + const unsigned int deviceLanes = + evdev_deviceLanes(device) & reasons; + if (deviceLanes & ~lanes) + lanes &= ~deviceLanes; + } + } + while(lanes != previous); + + return lanes; +} + +static unsigned int evdev_acquirableLanes( + unsigned int reasons, uint64_t now) +{ + bool keyboardSeen = false; + bool keyboardReady = true; + bool pointerSeen = false; + bool pointerReady = true; + for(int i = 0; i < state.deviceCount; ++i) + { + const EvdevDevice * device = &state.devices[i]; + if (device->fd < 0) + continue; + + const bool ready = evdev_shouldGrab(device, reasons) && + evdev_neutral(device) && + (device->grabbed || now >= device->grabRetryAt); + if (device->keyboard) + { + keyboardSeen = true; + keyboardReady = keyboardReady && ready; + } + if (device->pointer || device->absolutePointer) + { + pointerSeen = true; + pointerReady = pointerReady && ready; + } + } + + const unsigned int lanes = + (keyboardSeen && keyboardReady && (reasons & EVDEV_GRAB_KEYBOARD) ? + EVDEV_GRAB_KEYBOARD : 0) | + (pointerSeen && pointerReady && (reasons & EVDEV_GRAB_POINTER) ? + EVDEV_GRAB_POINTER : 0); + return evdev_coupleLanes(lanes, reasons); +} + +static bool evdev_shouldOwn(const EvdevDevice * device, + unsigned int reasons, unsigned int effective) +{ + if (!evdev_shouldGrab(device, reasons)) + return false; + + return !(evdev_deviceLanes(device) & reasons & ~effective); +} + +static unsigned int evdev_deviceForwardingLanes( + const EvdevDevice * device) +{ + const uint64_t desiredState = atomic_load_explicit( + &state.desiredState, memory_order_acquire); + if (!device->grabbed || !evdev_shouldGrab(device, + desiredState & EVDEV_GRAB_MASK)) + return 0; + + return evdev_deviceLanes(device) & state.forwardingLanes; +} + +static bool evdev_lanesNeutral(unsigned int lanes, + unsigned int reasons) +{ + for(int i = 0; i < state.deviceCount; ++i) + { + const EvdevDevice * device = &state.devices[i]; + if (device->fd >= 0 && evdev_shouldGrab(device, reasons) && + (evdev_deviceLanes(device) & lanes) && !evdev_neutral(device)) + return false; + } + + return true; +} + +static bool evdev_relinquishNeutral(unsigned int lane, + unsigned int reasons, unsigned int effective) +{ + for(int i = 0; i < state.deviceCount; ++i) + { + const EvdevDevice * device = &state.devices[i]; + if (device->fd >= 0 && device->grabbed && + !evdev_shouldOwn(device, reasons, effective) && + (evdev_deviceLanes(device) & lane) && !evdev_neutral(device)) + return false; + } + + return true; +} + +static bool evdev_queueBarrier(uint64_t desiredState, + unsigned int lanes, unsigned int activate, unsigned int deactivate, + unsigned int handoff) +{ + const EvdevBarrier barrier = + { + .serial = evdev_nextBarrier(), + .desiredState = desiredState, + .keyboardTopology = atomic_load_explicit( + &state.keyboardTopology, memory_order_acquire), + .pointerTopology = atomic_load_explicit( + &state.pointerTopology, memory_order_acquire), + .lanes = lanes, + .activate = activate, + .deactivate = deactivate, + .handoff = handoff, + }; + const EvdevOutput output = + { + .type = EVDEV_OUTPUT_BARRIER, + .barrier = barrier, + }; + if (!evdev_queueOutput(&output)) + return false; + + state.waitingBarrier = barrier; + return true; +} + +static bool evdev_setDeviceGrab( + EvdevDevice * device, bool grab, uint64_t now) +{ + if (device->fd < 0) + return false; + + if (device->grabbed == grab) + { + device->grabRetryAt = 0; + return true; + } + + if (now < device->grabRetryAt) + return device->grabbed == grab; + + if (!grab) + evdev_restoreLEDs(device); + + if (ioctl(device->fd, EVIOCGRAB, grab ? (void *)1 : (void *)0) < 0) + { + if (errno == ENODEV) + { + evdev_closeDevice(device, true); + return false; + } + + DEBUG_ERROR("Failed to %s %s: %s", grab ? "grab" : "ungrab", + device->path, strerror(errno)); + device->grabRetryAt = now + EVDEV_GRAB_RETRY_US; + return false; + } + + DEBUG_INFO("%s %s", grab ? "Grabbed" : "Ungrabbed", device->path); + device->grabbed = grab; + device->grabRetryAt = 0; + evdev_resetTransient(device); + evdev_updateLEDs(device); + return true; +} + +static void evdev_applyGrab(uint64_t now) +{ + uint64_t desiredState = atomic_load_explicit( + &state.desiredState, memory_order_acquire); + unsigned int reasons = desiredState & EVDEV_GRAB_MASK; + + if ((reasons & EVDEV_GRAB_MASK) && + (reasons & EVDEV_GRAB_MASK) != EVDEV_GRAB_MASK) + for(int i = 0; i < state.deviceCount; ++i) + { + EvdevDevice * device = &state.devices[i]; + if (device->fd >= 0 && device->keyboard && + (device->pointer || device->absolutePointer) && + !device->combinedWarned) + { + DEBUG_WARN("Leaving combined keyboard/pointer device ungrabbed " + "during partial capture: %s", device->path); + device->combinedWarned = true; + } + } + + for(int i = 0; i < state.deviceCount; ++i) + if (state.devices[i].fd >= 0) + evdev_updateLEDs(&state.devices[i]); + + if (state.waitingBarrier.serial) + { + if (atomic_load_explicit(&state.barrierAck, memory_order_acquire) != + state.waitingBarrier.serial) + return; + + desiredState = atomic_load_explicit( + &state.desiredState, memory_order_acquire); + reasons = desiredState & EVDEV_GRAB_MASK; + const unsigned int barrierLanes = + evdev_barrierStateLanes(&state.waitingBarrier); + if (evdev_barrierMatches(&state.waitingBarrier, desiredState)) + state.readyDeactivateLanes |= state.waitingBarrier.handoff; + else + { + state.readyDeactivateLanes &= ~barrierLanes; + state.needsBarrierLanes |= state.waitingBarrier.lanes; + } + state.waitingBarrier = (EvdevBarrier){0}; + } + + if (desiredState != state.observedState) + { + const unsigned int changed = + evdev_changedLanes(state.observedState, desiredState); + for(int i = 0; i < state.deviceCount; ++i) + { + EvdevDevice * device = &state.devices[i]; + if (device->fd >= 0 && + !evdev_syncKeys(device, + evdev_deviceForwardingLanes(device))) + { + evdev_closeDevice(device, errno == ENODEV); + continue; + } + + if (device->grabbed && evdev_shouldGrab(device, reasons)) + evdev_setDeviceGenerations(device, desiredState, + changed & reasons); + } + + state.readyDeactivateLanes &= ~changed; + state.needsBarrierLanes |= changed; + state.observedState = desiredState; + } + + for(int i = 0; i < state.deviceCount; ++i) + { + EvdevDevice * device = &state.devices[i]; + const unsigned int lanes = evdev_deviceLanes(device) & reasons; + if (device->fd >= 0 && + (!device->grabbed || !evdev_shouldGrab(device, reasons) || + (lanes & ~state.forwardingLanes)) && + !evdev_syncKeys(device, evdev_deviceForwardingLanes(device))) + evdev_closeDevice(device, errno == ENODEV); + } + + const unsigned int alreadyActive = evdev_coupleLanes( + evdev_targetActiveLanes(reasons), reasons); + const unsigned int acquire = alreadyActive | + evdev_acquirableLanes(reasons, now); + for(int i = 0; i < state.deviceCount; ++i) + { + EvdevDevice * device = &state.devices[i]; + if (device->fd < 0) + continue; + + const bool grab = evdev_shouldGrab(device, reasons); + const unsigned int lanes = evdev_deviceLanes(device) & reasons; + if (!grab || device->grabbed || (lanes & ~acquire)) + continue; + + if (evdev_setDeviceGrab(device, true, now) && device->grabbed) + { + const unsigned int deviceLanes = evdev_deviceLanes(device); + evdev_setDeviceGenerations(device, desiredState, deviceLanes); + state.readyDeactivateLanes &= ~deviceLanes; + state.needsBarrierLanes |= deviceLanes; + } + } + + const unsigned int effective = evdev_coupleLanes( + evdev_targetActiveLanes(reasons), reasons); + state.forwardingLanes = effective; + for(int i = 0; i < state.deviceCount; ++i) + { + EvdevDevice * device = &state.devices[i]; + if (device->fd >= 0 && device->grabbed && + !evdev_shouldOwn(device, reasons, effective) && + !evdev_syncKeys(device, 0)) + evdev_closeDevice(device, errno == ENODEV); + } + + bool toUngrab = false; + unsigned int ungrabLanes = 0; + for(int i = 0; i < state.deviceCount; ++i) + { + EvdevDevice * device = &state.devices[i]; + if (device->fd < 0) + continue; + + const bool grab = evdev_shouldGrab(device, reasons); + const bool own = evdev_shouldOwn(device, reasons, effective); + if (!grab && !device->grabbed) + device->grabRetryAt = 0; + if (own && device->grabbed) + device->grabRetryAt = 0; + if (!own && device->grabbed) + { + toUngrab = true; + ungrabLanes |= evdev_deviceLanes(device); + } + } + + const unsigned int published = evdev_publishedLanes(); + const unsigned int pendingHandoff = + ungrabLanes & ~state.readyDeactivateLanes; + unsigned int reset = 0; + unsigned int activate = 0; + unsigned int deactivate = published & ~effective; + unsigned int handoff = 0; + unsigned int satisfiedNeeds = 0; + for(unsigned int lane = EVDEV_GRAB_KEYBOARD; + lane <= EVDEV_GRAB_POINTER; lane <<= 1) + { + const bool desired = reasons & lane; + const bool active = effective & lane; + const bool current = published & lane; + const bool needed = state.needsBarrierLanes & lane; + + if (!desired) + { + if (needed || current || (pendingHandoff & lane)) + reset |= lane; + } + else if (active) + { + if ((needed || !current) && evdev_lanesNeutral(lane, reasons)) + reset |= lane; + } + else if (current) + reset |= lane; + + if (reset & lane) + { + satisfiedNeeds |= lane; + if (active) + activate |= lane; + else + deactivate |= lane; + } + + if (!(pendingHandoff & lane)) + continue; + + if ((!desired && (reset & lane)) || + evdev_relinquishNeutral(lane, reasons, effective)) + { + handoff |= lane; + deactivate |= lane; + if (active) + activate |= lane; + } + } + + if (reset || activate || deactivate || handoff) + { + if (!evdev_queueBarrier(desiredState, reset, + activate, deactivate, handoff)) + return; + + state.needsBarrierLanes &= ~satisfiedNeeds; + return; + } + + if (toUngrab) + { + if (ungrabLanes & ~state.readyDeactivateLanes) + return; + + for(int i = 0; i < state.deviceCount; ++i) + { + const EvdevDevice * device = &state.devices[i]; + if (device->fd >= 0 && device->grabbed && + !evdev_shouldOwn(device, reasons, effective) && + !evdev_neutral(device)) + return; + } + + for(int i = 0; i < state.deviceCount; ++i) + { + EvdevDevice * device = &state.devices[i]; + if (device->fd >= 0 && device->grabbed && + !evdev_shouldOwn(device, reasons, effective)) + evdev_setDeviceGrab(device, false, now); + } + } + + unsigned int remaining = 0; + for(int i = 0; i < state.deviceCount; ++i) + { + const EvdevDevice * device = &state.devices[i]; + if (device->fd >= 0 && device->grabbed && + !evdev_shouldOwn(device, reasons, effective)) + remaining |= evdev_deviceLanes(device); + } + state.readyDeactivateLanes &= remaining; +} + +static void evdev_flushMotion(EvdevDevice * device) +{ + const int x = device->mouseX; + const int y = device->mouseY; + device->mouseX = 0; + device->mouseY = 0; + + if (!(evdev_deviceForwardingLanes(device) & EVDEV_GRAB_POINTER) || + (!x && !y)) + return; + + const EvdevOutput output = + { + .type = EVDEV_OUTPUT_MOTION, + .generation = device->pointerGeneration, + .motion = { .x = x, .y = y }, + }; + evdev_queueOutput(&output); +} + +static void evdev_queueWheel( + EvdevDevice * device, int vertical, int horizontal) +{ + if (vertical > EVDEV_WHEEL_LIMIT) + vertical = EVDEV_WHEEL_LIMIT; + else if (vertical < -EVDEV_WHEEL_LIMIT) + vertical = -EVDEV_WHEEL_LIMIT; + if (horizontal > EVDEV_WHEEL_LIMIT) + horizontal = EVDEV_WHEEL_LIMIT; + else if (horizontal < -EVDEV_WHEEL_LIMIT) + horizontal = -EVDEV_WHEEL_LIMIT; + + if (!vertical && !horizontal) + return; + + const EvdevOutput output = + { + .type = EVDEV_OUTPUT_WHEEL, + .generation = device->pointerGeneration, + .wheel = + { + .vertical = vertical, + .horizontal = horizontal, + }, + }; + evdev_queueOutput(&output); +} + +static void evdev_handleRelative(EvdevDevice * device, + const struct input_event * event) +{ + if (!(evdev_deviceForwardingLanes(device) & EVDEV_GRAB_POINTER)) + return; + + switch(event->code) + { + case REL_X: + device->mouseX = evdev_addLimited(device->mouseX, + event->value, EVDEV_MOTION_LIMIT); + break; + + case REL_Y: + device->mouseY = evdev_addLimited(device->mouseY, + event->value, EVDEV_MOTION_LIMIT); + break; + + case REL_WHEEL: + if (!device->hasWheelHiRes) + { + evdev_flushMotion(device); + evdev_queueWheel(device, event->value, 0); + } + break; + + case REL_WHEEL_HI_RES: + { + evdev_flushMotion(device); + const int64_t total = (int64_t)device->wheelHiRes + event->value; + const int64_t steps64 = total / 120; + const int steps = steps64 > INT_MAX ? INT_MAX : + steps64 < INT_MIN ? INT_MIN : (int)steps64; + device->wheelHiRes = (int)(total % 120); + evdev_queueWheel(device, steps, 0); + break; + } + + case REL_HWHEEL: + if (!device->hasHWheelHiRes) + { + evdev_flushMotion(device); + evdev_queueWheel(device, 0, event->value); + } + break; + + case REL_HWHEEL_HI_RES: + { + evdev_flushMotion(device); + const int64_t total = (int64_t)device->hwheelHiRes + event->value; + const int64_t steps64 = total / 120; + const int steps = steps64 > INT_MAX ? INT_MAX : + steps64 < INT_MIN ? INT_MIN : (int)steps64; + device->hwheelHiRes = (int)(total % 120); + evdev_queueWheel(device, 0, steps); + break; + } + } +} + +static void evdev_handleKey(EvdevDevice * device, + const struct input_event * event) +{ + if (event->code >= KEY_CNT || event->value == 2) + return; + + if (evdev_isMouseButton(event->code)) + evdev_flushMotion(device); + + if (event->value == 0 || event->value == 1) + evdev_updateKey(device, event->code, event->value == 1, + evdev_deviceForwardingLanes(device) & + evdev_codeLane(event->code)); +} + +static uint8_t evdev_ledForCode(unsigned int code) +{ + switch(code) + { + case LED_NUML: + return LG_KEYBOARD_LED_NUM_LOCK; + case LED_CAPSL: + return LG_KEYBOARD_LED_CAPS_LOCK; + case LED_SCROLLL: + return LG_KEYBOARD_LED_SCROLL_LOCK; + case LED_COMPOSE: + return LG_KEYBOARD_LED_COMPOSE; + case LED_KANA: + return LG_KEYBOARD_LED_KANA; + } + + return 0; +} + +static void evdev_handleLED(EvdevDevice * device, + const struct input_event * event) +{ + const uint8_t led = evdev_ledForCode(event->code); + if (!led) + return; + + if (event->value) + device->observedLEDs |= led; + else + device->observedLEDs &= ~led; + + if (device->ledOverride) + { + const bool observed = device->observedLEDs & led; + const bool applied = device->appliedLEDs & led; + if (observed != applied) + { + if (observed) + device->hostLEDs |= led; + else + device->hostLEDs &= ~led; + device->hostLEDsValid = true; + device->ledDirty = true; + } + return; + } + + device->hostLEDs = device->observedLEDs; + device->hostLEDsValid = true; +} + +static void evdev_handleEvents(EvdevDevice * device, + const struct input_event * events, int count) +{ + for(int i = 0; i < count; ++i) + { + const struct input_event * event = &events[i]; + if (device->dropping) + { + if (event->type == EV_SYN && event->code == SYN_REPORT) + { + device->dropping = false; + if (!evdev_syncKeys(device, + evdev_deviceForwardingLanes(device))) + { + evdev_closeDevice(device, errno == ENODEV); + return; + } + if (device->ledMask && !device->ledOverride) + { + device->hostLEDsValid = + evdev_readLEDs(device, &device->hostLEDs); + if (device->hostLEDsValid) + device->observedLEDs = device->hostLEDs; + } + } + continue; + } + + switch(event->type) + { + case EV_SYN: + if (event->code == SYN_DROPPED) + { + device->mouseX = 0; + device->mouseY = 0; + device->wheelHiRes = 0; + device->hwheelHiRes = 0; + device->dropping = true; + } + else if (event->code == SYN_REPORT) + { + evdev_flushMotion(device); + if (device->ledDirty) + evdev_updateLEDs(device); + } + break; + + case EV_KEY: + evdev_handleKey(device, event); + break; + + case EV_REL: + evdev_handleRelative(device, event); + break; + + case EV_LED: + evdev_handleLED(device, event); + break; + } + } +} + +static int evdev_waitTimeout(uint64_t now) +{ + uint64_t next = now + UINT64_C(1000000); + for(int i = 0; i < state.deviceCount; ++i) + { + const EvdevDevice * device = &state.devices[i]; + if (!state.waitingBarrier.serial && + device->fd < 0 && device->retryAt < next) + next = device->retryAt; + if (device->grabRetryAt > now && device->grabRetryAt < next) + next = device->grabRetryAt; + } + + if (next <= now) + return 0; + + const uint64_t delay = next - now; + return delay >= UINT64_C(1000000) ? 1000 : (delay + 999) / 1000; +} + +static void evdev_invalidateDesired(void) +{ + uint_fast64_t current = atomic_load_explicit( + &state.desiredState, memory_order_acquire); + for(;;) + { + const uint_fast64_t next = + evdev_updateDesired(current, 0, EVDEV_GRAB_MASK); + if (atomic_compare_exchange_weak_explicit(&state.desiredState, + ¤t, next, memory_order_acq_rel, memory_order_acquire)) + return; + } +} + +static void evdev_shutdownDevices(void) +{ + evdev_invalidateDesired(); + atomic_fetch_add_explicit( + &state.keyboardTopology, 1, memory_order_acq_rel); + atomic_fetch_add_explicit( + &state.pointerTopology, 1, memory_order_acq_rel); + + for(int i = 0; i < state.deviceCount; ++i) + { + EvdevDevice * device = &state.devices[i]; + if (device->fd < 0) + continue; + + evdev_releaseState(device, false); + evdev_restoreLEDs(device); + if (device->grabbed && + ioctl(device->fd, EVIOCGRAB, (void *)0) < 0 && errno != ENODEV) + DEBUG_WARN("Failed to release %s during shutdown: %s", + device->path, strerror(errno)); + else if (device->grabbed) + DEBUG_INFO("Ungrabbed %s", device->path); + + epoll_ctl(state.epoll, EPOLL_CTL_DEL, device->fd, NULL); + close(device->fd); + device->fd = -1; + device->grabbed = false; + } + + atomic_store_explicit(&state.keyboardActive, false, + memory_order_release); + atomic_store_explicit(&state.pointerActive, false, + memory_order_release); + + evdev_cancelOutput(); +} + static int evdev_thread(void * opaque) { - struct epoll_event * events = alloca(sizeof(*events) * state.deviceCount); - struct input_event msgs[256]; - - DEBUG_INFO("evdev_thread Started"); - while(app_isRunning()) + (void)opaque; + struct epoll_event * events = calloc(state.deviceCount + 1, + sizeof(*events)); + if (!events) { - int openDevices = 0; - for(int i = 0; i < state.deviceCount; ++i) - { - EvdevDevice * dev = &state.devices[i]; - if (dev->fd <= 0) - { - if (evdev_openDevice(dev, true)) - ++openDevices; - } - else - ++openDevices; - } + DEBUG_ERROR("Failed to allocate evdev events"); + return -1; + } - if (openDevices == 0) + struct input_event messages[256]; + DEBUG_INFO("evdev thread started"); + + while(!atomic_load_explicit(&state.stop, memory_order_acquire)) + { + uint64_t now = microtime(); + if (!state.waitingBarrier.serial) + for(int i = 0; i < state.deviceCount; ++i) + evdev_openDevice(&state.devices[i], now); + + evdev_applyGrab(now); + const int waiting = epoll_wait(state.epoll, events, + state.deviceCount + 1, evdev_waitTimeout(now)); + if (waiting < 0) { - usleep(1000); + if (errno != EINTR) + DEBUG_WARN("evdev epoll wait failed: %s", strerror(errno)); continue; } - int waiting = epoll_wait(state.epoll, events, state.deviceCount, 100); for(int i = 0; i < waiting; ++i) { - EvdevDevice * device = (EvdevDevice *)events[i].data.ptr; - int n = read(device->fd, msgs, sizeof(msgs)); - if (n < 0) + if (atomic_load_explicit(&state.stop, memory_order_acquire)) + break; + + if (!events[i].data.ptr) { - if (errno == ENODEV) - { - DEBUG_WARN("Device was removed: %s", device->path); - epoll_ctl(state.epoll, EPOLL_CTL_DEL, device->fd, NULL); - close(device->fd); - device->fd = 0; - device->grabbed = false; - continue; - } - - DEBUG_WARN("Failed to read evdev event: %s (%s)", - device->path, strerror(errno)); - + evdev_drainEvent(state.commandEvent); continue; } - if (n % sizeof(*msgs) != 0) - DEBUG_WARN("Incomplete evdev read: %s", device->path); + EvdevDevice * device = events[i].data.ptr; + if (device->fd < 0) + continue; - bool grabbed = state.grabbed; - - int count = n / sizeof(*msgs); - struct input_event *ev = msgs; - int mouseX = 0, mouseY = 0; - - for(int i = 0; i < count; ++i, ++ev) + if (events[i].events & (EPOLLERR | EPOLLHUP)) { - switch(ev->type) - { - case EV_KEY: - { - bool isMouseBtn = - ev->code >= BTN_MOUSE && ev->code <= BTN_TASK; - static const int mouseBtnMap[] = {1, 3, 2, 6, 7, 8, 9, 10}; - - if (ev->value == 1) - { - ++state.keys[ev->code]; - - if (grabbed && state.keys[ev->code] == 1) - { - if (isMouseBtn) - app_handleButtonPress(mouseBtnMap[ev->code - BTN_MOUSE]); - else - app_handleKeyPressInternal(ev->code); - } - } - else if (ev->value == 0 && --state.keys[ev->code] <= 0) - { - state.keys[ev->code] = 0; - - if (state.pending == PENDING_GRAB) - { - state.pending = PENDING_NONE; - evdev_grabKeyboard(); - } - else if (state.pending == PENDING_UNGRAB) - { - state.pending = PENDING_NONE; - evdev_ungrabKeyboard(); - } - - if (grabbed) - { - if (isMouseBtn) - app_handleButtonRelease(mouseBtnMap[ev->code - BTN_MOUSE]); - else - app_handleKeyReleaseInternal(ev->code); - } - } - break; - } - - case EV_REL: - if (!grabbed) - continue; - - switch(ev->code) - { - case REL_X: - mouseX += ev->value; - break; - - case REL_Y: - mouseY += ev->value; - break; - - case REL_WHEEL: - { - int btn; - if (ev->value > 0) - btn = 4; - else - btn = 5; - - app_handleButtonPress (btn); - app_handleButtonRelease(btn); - break; - } - } - break; - } + evdev_closeDevice(device, true); + continue; } - if (mouseX != 0 || mouseY != 0) - core_handleMouseGrabbed(mouseX, mouseY); + unsigned int batches = 0; + while(device->fd >= 0 && batches < EVDEV_READ_BATCHES && + !atomic_load_explicit(&state.stop, memory_order_acquire)) + { + const ssize_t size = read(device->fd, messages, sizeof(messages)); + if (size > 0) + { + ++batches; + if (size % sizeof(*messages)) + DEBUG_WARN("Incomplete evdev read: %s", device->path); + evdev_handleEvents(device, messages, size / sizeof(*messages)); + continue; + } + + if (size < 0 && errno == EINTR) + continue; + if (size < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) + break; + + if (size < 0 && errno != ENODEV) + DEBUG_WARN("Failed to read evdev event from %s: %s", + device->path, strerror(errno)); + evdev_closeDevice(device, true); + } } } - DEBUG_INFO("evdev_thread Stopped"); + + evdev_shutdownDevices(); + free(events); + DEBUG_INFO("evdev thread stopped"); return 0; } +static bool evdev_popOutput(EvdevOutput * output) +{ + LG_LOCK(state.outputLock); + if (!state.outputCount) + { + LG_UNLOCK(state.outputLock); + return false; + } + + *output = state.output[state.outputHead]; + state.outputHead = (state.outputHead + 1) % EVDEV_QUEUE_SIZE; + --state.outputCount; + LG_UNLOCK(state.outputLock); + return true; +} + +static void evdev_dispatchModifiers(void) +{ + app_handleKeyboardModifiersInternal( + state.dispatchKeys[KEY_LEFTCTRL] || + state.dispatchKeys[KEY_RIGHTCTRL], + state.dispatchKeys[KEY_LEFTSHIFT] || + state.dispatchKeys[KEY_RIGHTSHIFT], + state.dispatchKeys[KEY_LEFTALT] || + state.dispatchKeys[KEY_RIGHTALT], + state.dispatchKeys[KEY_LEFTMETA] || + state.dispatchKeys[KEY_RIGHTMETA]); +} + +static void evdev_dispatchReset(unsigned int lanes) +{ + const bool keyboard = lanes & EVDEV_GRAB_KEYBOARD; + const bool pointer = lanes & EVDEV_GRAB_POINTER; + + if (keyboard) + memset(state.dispatchKeys, 0, sizeof(state.dispatchKeys)); + if (pointer) + state.dispatchButtons = 0; + + app_handleInputResetInternal(keyboard, pointer); +} + +static bool evdev_outputCurrent( + const EvdevOutput * output, unsigned int lane) +{ + if (atomic_load_explicit(&state.stop, memory_order_acquire)) + return false; + + const uint_fast64_t desiredState = atomic_load_explicit( + &state.desiredState, memory_order_acquire); + return evdev_lanePublished(lane) && (desiredState & lane) && + output->generation == evdev_generation(desiredState, lane); +} + +int evdev_getEventFD(void) +{ + return state.outputEvent; +} + +void evdev_dispatch(void * opaque) +{ + (void)opaque; + if (state.outputEvent < 0) + return; + + evdev_drainEvent(state.outputEvent); + + if (!atomic_load_explicit(&state.stop, memory_order_acquire) && + atomic_exchange_explicit(&state.availabilityDirty, false, + memory_order_acq_rel)) + core_updateKeyboardGrab(); + + EvdevOutput output; + while(evdev_popOutput(&output)) + { + switch(output.type) + { + case EVDEV_OUTPUT_RESET: + evdev_dispatchReset(output.barrier.lanes); + break; + + case EVDEV_OUTPUT_KEY: + if (output.key.code >= KEY_MAX || + state.dispatchKeys[output.key.code] == output.key.down || + (output.key.down && !evdev_outputCurrent( + &output, EVDEV_GRAB_KEYBOARD))) + break; + state.dispatchKeys[output.key.code] = output.key.down; + if (output.key.down) + app_handleKeyPressInternal(output.key.code); + else + app_handleKeyReleaseInternal(output.key.code); + evdev_dispatchModifiers(); + break; + + case EVDEV_OUTPUT_BUTTON: + { + if (output.button.button >= 32) + break; + const uint32_t mask = UINT32_C(1) << output.button.button; + const bool down = state.dispatchButtons & mask; + if (down == output.button.down || + (output.button.down && !evdev_outputCurrent( + &output, EVDEV_GRAB_POINTER))) + break; + if (output.button.down) + { + state.dispatchButtons |= mask; + app_handleButtonPressInternal(output.button.button); + } + else + { + state.dispatchButtons &= ~mask; + app_handleButtonReleaseInternal(output.button.button); + } + break; + } + + case EVDEV_OUTPUT_MOTION: + if (evdev_outputCurrent(&output, EVDEV_GRAB_POINTER)) + core_handleMouseGrabbed(output.motion.x, output.motion.y); + break; + + case EVDEV_OUTPUT_WHEEL: + { + if (!evdev_outputCurrent(&output, EVDEV_GRAB_POINTER)) + break; + + const int button = output.wheel.vertical > 0 ? 4 : 5; + const int steps = output.wheel.vertical < 0 ? + -output.wheel.vertical : output.wheel.vertical; + for(int i = 0; i < steps; ++i) + { + app_handleButtonPressInternal(button); + app_handleButtonReleaseInternal(button); + } + + if (output.wheel.horizontal && !state.horizontalWheelWarned) + { + DEBUG_WARN("Horizontal evdev wheel input is unsupported by the " + "active input protocol"); + state.horizontalWheelWarned = true; + } + break; + } + + case EVDEV_OUTPUT_BARRIER: + { + const uint64_t desiredState = atomic_load_explicit( + &state.desiredState, memory_order_acquire); + if (!atomic_load_explicit(&state.stop, memory_order_acquire) && + evdev_barrierMatches(&output.barrier, desiredState)) + { + evdev_dispatchReset(output.barrier.lanes); + evdev_publishActive(output.barrier.deactivate, false); + evdev_publishActive(output.barrier.activate, true); + const uint64_t currentState = atomic_load_explicit( + &state.desiredState, memory_order_acquire); + if (output.barrier.activate && + !evdev_barrierMatches(&output.barrier, currentState)) + evdev_publishActive(output.barrier.activate, false); + } + atomic_store_explicit(&state.barrierAck, + output.barrier.serial, memory_order_release); + evdev_signal(state.commandEvent, "command"); + break; + } + } + } +} + bool evdev_start(void) { const char * deviceList = option_get_string("input", "evdev"); if (!deviceList) return false; + LG_LOCK_INIT(state.outputLock); + state.outputLockInitialized = true; + state.deviceList = strdup(deviceList); - StringList sl = stringlist_new(false); + if (!state.deviceList) + return false; + + StringList list = stringlist_new(false); + if (!list) + return false; char * token = strtok(state.deviceList, ","); - while(token != NULL) + while(token) { - stringlist_push(sl, token); + stringlist_push(list, token); token = strtok(NULL, ","); } - state.deviceCount = stringlist_count(sl); - state.devices = calloc(state.deviceCount, sizeof(*state.devices)); - for(int i = 0; i < state.deviceCount; ++i) - state.devices[i].path = stringlist_at(sl, i); - stringlist_free(&sl); - - // nothing to do if there are no configured devices - if (state.deviceCount == 0) - return false; - - state.exclusive = option_get("input", "evdevExclusive"); - - state.epoll = epoll_create1(0); - if (state.epoll < 0) + state.deviceCount = stringlist_count(list); + if (!state.deviceCount) { - DEBUG_ERROR("Failed to create epoll (%s)", strerror(errno)); + stringlist_free(&list); + return false; + } + + state.devices = calloc(state.deviceCount, sizeof(*state.devices)); + if (!state.devices) + { + stringlist_free(&list); return false; } for(int i = 0; i < state.deviceCount; ++i) { - EvdevDevice * device = &state.devices[i]; - if (!evdev_openDevice(device, false)) - return false; + state.devices[i].path = stringlist_at(list, i); + state.devices[i].fd = -1; + state.devices[i].retryDelay = EVDEV_RETRY_INITIAL_US; + } + stringlist_free(&list); + + state.exclusive = option_get_bool("input", "evdevExclusive"); + atomic_init(&state.desiredState, 0); + atomic_init(&state.barrierAck, 0); + atomic_init(&state.keyboardTopology, 0); + atomic_init(&state.pointerTopology, 0); + atomic_init(&state.keyboardActive, false); + atomic_init(&state.pointerActive, false); + atomic_init(&state.stop, false); + atomic_init(&state.availabilityDirty, false); + atomic_init(&state.guestLEDState, 0); + + state.epoll = epoll_create1(EPOLL_CLOEXEC); + if (state.epoll < 0) + { + DEBUG_ERROR("Failed to create evdev epoll: %s", strerror(errno)); + return false; + } + + state.commandEvent = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); + if (state.commandEvent < 0) + { + DEBUG_ERROR("Failed to create evdev command event: %s", strerror(errno)); + return false; + } + + state.outputEvent = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); + if (state.outputEvent < 0) + { + DEBUG_ERROR("Failed to create evdev output event: %s", strerror(errno)); + return false; + } + + struct epoll_event commandEvent = + { + .events = EPOLLIN, + .data.ptr = NULL + }; + if (epoll_ctl(state.epoll, EPOLL_CTL_ADD, + state.commandEvent, &commandEvent) != 0) + { + DEBUG_ERROR("Failed to add the evdev command event to epoll: %s", + strerror(errno)); + return false; } if (!lgCreateThread("Evdev", evdev_thread, NULL, &state.thread)) @@ -340,43 +2210,52 @@ bool evdev_start(void) return false; } - //hook the display server's grab methods - state.dsGrabKeyboard = g_state.ds->grabKeyboard; - state.dsUngrabKeyboard = g_state.ds->ungrabKeyboard; - g_state.ds->grabKeyboard = &evdev_grabKeyboard; - g_state.ds->ungrabKeyboard = &evdev_ungrabKeyboard; - + lgInput_setKeyboardLEDsListener(evdev_keyboardLEDs, NULL); + state.ledsListenerRegistered = true; + lgInput_setAvailabilityListener(evdev_availabilityChanged, NULL); + state.availabilityListenerRegistered = true; return true; } void evdev_stop(void) { - if (state.thread) + if (state.availabilityListenerRegistered) { - lgJoinThread(state.thread, NULL); - state.thread = NULL; + lgInput_setAvailabilityListener(NULL, NULL); + state.availabilityListenerRegistered = false; + atomic_store_explicit(&state.availabilityDirty, false, + memory_order_release); } + + if (state.ledsListenerRegistered) + { + lgInput_setKeyboardLEDsListener(NULL, NULL); + state.ledsListenerRegistered = false; + } + + if (!state.thread) + return; + + atomic_store_explicit(&state.stop, true, memory_order_release); + evdev_signal(state.commandEvent, "command"); + lgJoinThread(state.thread, NULL); + state.thread = NULL; } void evdev_free(void) { evdev_stop(); - // restore the display server grab methods - if (state.dsGrabKeyboard) + if (state.outputEvent >= 0) { - g_state.ds->grabKeyboard = state.dsGrabKeyboard; - g_state.ds->ungrabKeyboard = state.dsUngrabKeyboard; - state.dsGrabKeyboard = NULL; - state.dsUngrabKeyboard = NULL; + close(state.outputEvent); + state.outputEvent = -1; } - state.grabbed = false; - // the thread and grab callbacks reference the device state - if (state.deviceList) + if (state.commandEvent >= 0) { - free(state.deviceList); - state.deviceList = NULL; + close(state.commandEvent); + state.commandEvent = -1; } if (state.epoll >= 0) @@ -385,82 +2264,56 @@ void evdev_free(void) state.epoll = -1; } - if (state.devices) + if (state.outputLockInitialized) { - for(int i = 0; i < state.deviceCount; ++i) - { - EvdevDevice * device = &state.devices[i]; - if (device->fd > 0) - close(device->fd); - } - free(state.devices); - state.devices = NULL; - state.deviceCount = 0; + LG_LOCK(state.outputLock); + state.outputHead = 0; + state.outputCount = 0; + LG_UNLOCK(state.outputLock); + LG_LOCK_FREE(state.outputLock); + state.outputLockInitialized = false; } + + free(state.devices); + state.devices = NULL; + state.deviceCount = 0; + + free(state.deviceList); + state.deviceList = NULL; } -void evdev_grabKeyboard(void) +void evdev_setGrab(bool keyboard, bool pointer) { - state.pending = PENDING_NONE; - if (state.grabbed) + if (atomic_load_explicit(&state.stop, memory_order_acquire)) return; - // we must be in a neutral state - for(int i = 0; i < KEY_MAX; ++i) - if (state.keys[i] > 0) + const unsigned int reasons = + (keyboard ? EVDEV_GRAB_KEYBOARD : 0) | + (pointer ? EVDEV_GRAB_POINTER : 0); + + uint_fast64_t current = atomic_load_explicit( + &state.desiredState, memory_order_acquire); + while((current & EVDEV_GRAB_MASK) != reasons) + { + const unsigned int changed = + (current ^ reasons) & EVDEV_GRAB_MASK; + const uint_fast64_t next = + evdev_updateDesired(current, reasons, changed); + if (atomic_compare_exchange_weak_explicit(&state.desiredState, + ¤t, next, memory_order_acq_rel, memory_order_acquire)) { - state.pending = PENDING_GRAB; + evdev_signal(state.commandEvent, "command"); return; } - -// state.dsGrabKeyboard(); - - for(int i = 0; i < state.deviceCount; ++i) - { - EvdevDevice * device = &state.devices[i]; - if (device->fd > 0) - evdev_grabDevice(device); } - - state.grabbed = true; -} - -void evdev_ungrabKeyboard(void) -{ - state.pending = PENDING_NONE; - if (!state.grabbed) - return; - - // we must be in a neutral state - for(int i = 0; i < KEY_MAX; ++i) - if (state.keys[i] > 0) - { - state.pending = PENDING_UNGRAB; - return; - } - - for(int i = 0; i < state.deviceCount; ++i) - { - EvdevDevice * device = &state.devices[i]; - if (device->fd <= 0 || !device->grabbed) - continue; - - if (ioctl(device->fd, EVIOCGRAB, (void *)0) < 0) - { - DEBUG_ERROR("EVIOCGRAB=0 failed: %s", strerror(errno)); - continue; - } - - DEBUG_INFO("Ungrabbed %s", device->path); - device->grabbed = false; - } - -// state.dsUngrabKeyboard(); - - state.grabbed = false; } bool evdev_isExclusive(void) { - return state.exclusive && state.grabbed && !app_isOverlayMode(); + return state.exclusive && evdev_lanePublished(EVDEV_GRAB_KEYBOARD); +} + +bool evdev_isPointerExclusive(void) +{ + return state.exclusive && evdev_lanePublished(EVDEV_GRAB_POINTER); } diff --git a/client/src/input.c b/client/src/input.c index ccdf0355..0e707785 100644 --- a/client/src/input.c +++ b/client/src/input.c @@ -42,15 +42,19 @@ static struct LG_Lock bindingLock; LG_RWLock activeLock; LG_Lock keyboardLEDsDispatch; + LG_Lock availabilityDispatch; uint32_t nextBindingEpoch; LGInputKeyboardLEDsFn keyboardLEDsCallback; void * keyboardLEDsOpaque; + LGInputAvailabilityFn availabilityCallback; + void * availabilityOpaque; struct InputBinding fallback; struct InputBinding transport; struct InputBinding active; bool useTransport; + bool notifiedAvailable; _Atomic(bool) keys[KEY_MAX]; _Atomic(uint32_t) buttons; @@ -114,11 +118,11 @@ static void dispatchKeyboardLEDs(void) LG_LOCK(l_input.keyboardLEDsDispatch); LG_LOCK_SHARED(l_input.activeLock); LGInputKeyboardLEDsFn callback = l_input.keyboardLEDsCallback; - void * opaque = l_input.keyboardLEDsOpaque; - const bool valid = l_input.active.ops && + void * opaque = l_input.keyboardLEDsOpaque; + const bool valid = l_input.active.ops && l_input.active.available && l_input.active.keyboardLEDsValid; - const uint8_t leds = l_input.active.keyboardLEDs; + const uint8_t leds = l_input.active.keyboardLEDs; LG_UNLOCK_SHARED(l_input.activeLock); if (callback) @@ -126,6 +130,30 @@ static void dispatchKeyboardLEDs(void) LG_UNLOCK(l_input.keyboardLEDsDispatch); } +static bool updateAvailabilityNL(void) +{ + const bool available = l_input.active.ops != NULL; + if (l_input.notifiedAvailable == available) + return false; + + l_input.notifiedAvailable = available; + return true; +} + +static void dispatchAvailability(void) +{ + LG_LOCK(l_input.availabilityDispatch); + LG_LOCK_SHARED(l_input.activeLock); + LGInputAvailabilityFn callback = l_input.availabilityCallback; + void * opaque = l_input.availabilityOpaque; + const bool available = l_input.active.ops != NULL; + LG_UNLOCK_SHARED(l_input.activeLock); + + if (callback) + callback(opaque, available); + LG_UNLOCK(l_input.availabilityDispatch); +} + static void releaseKeysNL(void) { for (int key = 0; key < KEY_MAX; ++key) @@ -234,9 +262,12 @@ static void fallbackStatusChanged(void * opaque, if (l_input.fallback.ops && l_input.fallback.epoch == (uint32_t)(uintptr_t)opaque) keyboardLEDsChanged = updateStatusNL(&l_input.fallback, status); + const bool availabilityChanged = updateAvailabilityNL(); LG_UNLOCK_EXCLUSIVE(l_input.activeLock); if (keyboardLEDsChanged) dispatchKeyboardLEDs(); + if (availabilityChanged) + dispatchAvailability(); } static void transportStatusChanged(void * opaque, @@ -250,17 +281,21 @@ static void transportStatusChanged(void * opaque, if (l_input.transport.ops && l_input.transport.epoch == (uint32_t)(uintptr_t)opaque) keyboardLEDsChanged = updateStatusNL(&l_input.transport, status); + const bool availabilityChanged = updateAvailabilityNL(); LG_UNLOCK_EXCLUSIVE(l_input.activeLock); if (keyboardLEDsChanged) dispatchKeyboardLEDs(); + if (availabilityChanged) + dispatchAvailability(); } void lgInput_init(void) { - l_input.fallback = (struct InputBinding) { 0 }; - l_input.transport = (struct InputBinding) { 0 }; - l_input.active = (struct InputBinding) { 0 }; - l_input.useTransport = true; + l_input.fallback = (struct InputBinding) { 0 }; + l_input.transport = (struct InputBinding) { 0 }; + l_input.active = (struct InputBinding) { 0 }; + l_input.useTransport = true; + l_input.notifiedAvailable = false; for (int key = 0; key < KEY_MAX; ++key) atomic_init(&l_input.keys[key], false); @@ -269,6 +304,7 @@ void lgInput_init(void) LG_LOCK_INIT(l_input.bindingLock); LG_RWLOCK_INIT(l_input.activeLock); LG_LOCK_INIT(l_input.keyboardLEDsDispatch); + LG_LOCK_INIT(l_input.availabilityDispatch); } void lgInput_free(void) @@ -278,16 +314,20 @@ void lgInput_free(void) LG_LOCK(l_input.bindingLock); LG_LOCK(l_input.keyboardLEDsDispatch); + LG_LOCK(l_input.availabilityDispatch); LG_LOCK_EXCLUSIVE(l_input.activeLock); resetActiveNL(); - fallback = l_input.fallback; - transport = l_input.transport; - l_input.active = (struct InputBinding) { 0 }; - l_input.fallback = (struct InputBinding) { 0 }; - l_input.transport = (struct InputBinding) { 0 }; - l_input.keyboardLEDsCallback = NULL; - l_input.keyboardLEDsOpaque = NULL; + fallback = l_input.fallback; + transport = l_input.transport; + l_input.active = (struct InputBinding) { 0 }; + l_input.fallback = (struct InputBinding) { 0 }; + l_input.transport = (struct InputBinding) { 0 }; + l_input.keyboardLEDsCallback = NULL; + l_input.keyboardLEDsOpaque = NULL; + l_input.availabilityCallback = NULL; + l_input.availabilityOpaque = NULL; LG_UNLOCK_EXCLUSIVE(l_input.activeLock); + LG_UNLOCK(l_input.availabilityDispatch); LG_UNLOCK(l_input.keyboardLEDsDispatch); if (fallback.ops && fallback.ops->setStatusListener) @@ -297,6 +337,7 @@ void lgInput_free(void) LG_UNLOCK(l_input.bindingLock); LG_RWLOCK_FREE(l_input.activeLock); + LG_LOCK_FREE(l_input.availabilityDispatch); LG_LOCK_FREE(l_input.keyboardLEDsDispatch); LG_LOCK_FREE(l_input.bindingLock); } @@ -325,10 +366,13 @@ static void setBinding(struct InputBinding * target, LG_LOCK_EXCLUSIVE(l_input.activeLock); *target = next; const bool keyboardLEDsChanged = updateActiveNL(false); + const bool availabilityChanged = updateAvailabilityNL(); LG_UNLOCK_EXCLUSIVE(l_input.activeLock); if (keyboardLEDsChanged) dispatchKeyboardLEDs(); + if (availabilityChanged) + dispatchAvailability(); if (next.ops && next.ops->setStatusListener) next.ops->setStatusListener(next.opaque, statusFn, @@ -343,9 +387,12 @@ static void dropBinding(struct InputBinding * target) const bool wasActive = bindingEqual(&l_input.active, target); *target = (struct InputBinding) { 0 }; const bool keyboardLEDsChanged = updateActiveNL(wasActive); + const bool availabilityChanged = updateAvailabilityNL(); LG_UNLOCK_EXCLUSIVE(l_input.activeLock); if (keyboardLEDsChanged) dispatchKeyboardLEDs(); + if (availabilityChanged) + dispatchAvailability(); LG_UNLOCK(l_input.bindingLock); } @@ -374,9 +421,12 @@ void lgInput_useTransport(bool enable) LG_LOCK_EXCLUSIVE(l_input.activeLock); l_input.useTransport = enable; const bool keyboardLEDsChanged = updateActiveNL(false); + const bool availabilityChanged = updateAvailabilityNL(); LG_UNLOCK_EXCLUSIVE(l_input.activeLock); if (keyboardLEDsChanged) dispatchKeyboardLEDs(); + if (availabilityChanged) + dispatchAvailability(); } bool lgInput_available(void) @@ -423,6 +473,21 @@ void lgInput_setKeyboardLEDsListener( LG_UNLOCK(l_input.keyboardLEDsDispatch); } +void lgInput_setAvailabilityListener( + LGInputAvailabilityFn callback, void * opaque) +{ + LG_LOCK(l_input.availabilityDispatch); + LG_LOCK_SHARED(l_input.activeLock); + l_input.availabilityCallback = callback; + l_input.availabilityOpaque = opaque; + const bool available = l_input.active.ops != NULL; + LG_UNLOCK_SHARED(l_input.activeLock); + + if (callback) + callback(opaque, available); + LG_UNLOCK(l_input.availabilityDispatch); +} + bool lgInput_keyDown(int key) { if (key < 0 || key >= KEY_MAX) @@ -483,6 +548,21 @@ void lgInput_releaseKeys(void) LG_UNLOCK_EXCLUSIVE(l_input.activeLock); } +void lgInput_releaseButtons(void) +{ + LG_LOCK_EXCLUSIVE(l_input.activeLock); + releaseButtonsNL(); + LG_UNLOCK_EXCLUSIVE(l_input.activeLock); +} + +void lgInput_releaseAll(void) +{ + LG_LOCK_EXCLUSIVE(l_input.activeLock); + releaseKeysNL(); + releaseButtonsNL(); + LG_UNLOCK_EXCLUSIVE(l_input.activeLock); +} + bool lgInput_mouseMotion(int32_t x, int32_t y) { LG_LOCK_SHARED(l_input.activeLock); diff --git a/client/src/main.c b/client/src/main.c index 97af823d..8c58ada0 100644 --- a/client/src/main.c +++ b/client/src/main.c @@ -3603,7 +3603,7 @@ static int lg_run(void) } if (evdev_start()) - DEBUG_INFO("Using evdev for keyboard capture"); + DEBUG_INFO("Using evdev for input capture"); // override the SIGINIT handler so that we can tell the difference between // SIGINT and the user sending a close event, such as ALT+F4 @@ -3739,7 +3739,13 @@ static int lg_run(void) .largeCursorDot = g_params.largeCursorDot, .allowNoInput = strcmp(g_params.transport, "test") == 0, .opengl = needsOpenGL, - .jitRender = g_params.jitRender + .jitRender = g_params.jitRender, + .eventSource = + { + .fd = evdev_getEventFD(), + .callback = evdev_dispatch, + .opaque = NULL, + }, }; g_state.dsInitialized = g_state.ds->init(params); diff --git a/client/tests/app_keybind_test.c b/client/tests/app_keybind_test.c index 6893896c..79ba0a64 100644 --- a/client/tests/app_keybind_test.c +++ b/client/tests/app_keybind_test.c @@ -163,6 +163,10 @@ bool core_inputEnabled(void) return t.input; } +void core_updateKeyboardGrab(void) +{ +} + void core_setGrab(bool enable) { ++t.grabN; diff --git a/client/tests/keybind_test.c b/client/tests/keybind_test.c index f661cef1..20d8b053 100644 --- a/client/tests/keybind_test.c +++ b/client/tests/keybind_test.c @@ -224,6 +224,10 @@ void core_updatePositionInfo(void) ++t.posN; } +void core_updateKeyboardGrab(void) +{ +} + void core_setCursorInView(bool enable) { ++t.cursorN; diff --git a/client/tests/mouse_test.c b/client/tests/mouse_test.c index f17d846f..e3b938c2 100644 --- a/client/tests/mouse_test.c +++ b/client/tests/mouse_test.c @@ -282,6 +282,12 @@ void app_mouseTrace(const char * file, unsigned int line, (void)format; } +void evdev_setGrab(bool keyboard, bool pointer) +{ + (void)keyboard; + (void)pointer; +} + static bool inputSupports(void * opaque, LG_InputSupport support) { return support == LG_INPUT_SUPPORT_MOUSE_ABSOLUTE && diff --git a/doc/input.rst b/doc/input.rst index 2b2c2e6c..6525854b 100644 --- a/doc/input.rst +++ b/doc/input.rst @@ -95,7 +95,10 @@ Evdev capture ------------- Advanced users may list Linux evdev devices in ``input:evdev``. They become -active whenever Looking Glass grabs the keyboard, including capture mode and -automatic keyboard capture. The client user must be allowed to read those -devices. Keep ``input:evdevExclusive=yes`` unless duplicate input from the -window system is specifically required. +active when Looking Glass captures their keyboard or relative-pointer role. +Automatic capture takes keyboard-only devices without taking mouse input from +the window system, while full capture takes both roles. Guest keyboard lock +LEDs are mirrored while a writable keyboard is captured. The client user must +be allowed to read the configured devices and write them for LED mirroring. +Keep ``input:evdevExclusive=yes`` unless input from other window-system +devices is also required.