diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 57d4c792..7d64458c 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -167,6 +167,8 @@ set(SOURCES src/render_queue.c src/evdev.c src/transport.c + src/input.c + src/input_spice.c src/overlay/splash.c src/overlay/alert.c diff --git a/client/include/input.h b/client/include/input.h new file mode 100644 index 00000000..3c05adf5 --- /dev/null +++ b/client/include/input.h @@ -0,0 +1,50 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _H_LG_CLIENT_INPUT_ +#define _H_LG_CLIENT_INPUT_ + +#include "interface/input.h" + +extern const LG_InputOps LGI_Spice; + +void lgInput_init(void); +void lgInput_free(void); + +void lgInput_setFallback(const LG_InputOps * ops, void * opaque); +void lgInput_setTransport(const LG_InputOps * ops, void * opaque); +void lgInput_dropTransport(void); +void lgInput_useTransport(bool enable); + +bool lgInput_available(void); +bool lgInput_supports(LG_InputSupport support); + +bool lgInput_keyDown(int key); +bool lgInput_keyUp(int key); +bool lgInput_keyboardLEDs(bool numLock, bool capsLock, bool scrollLock); +void lgInput_releaseKeys(void); + +bool lgInput_mouseMotion(int32_t x, int32_t y); +bool lgInput_mousePosition(uint32_t x, uint32_t y, uint32_t width, + uint32_t height); +bool lgInput_mousePress(unsigned int button); +bool lgInput_mouseRelease(unsigned int button); + +#endif diff --git a/client/include/interface/input.h b/client/include/interface/input.h new file mode 100644 index 00000000..95090ae4 --- /dev/null +++ b/client/include/interface/input.h @@ -0,0 +1,60 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _H_LG_CLIENT_INPUT_INTERFACE_ +#define _H_LG_CLIENT_INPUT_INTERFACE_ + +#include +#include + +typedef enum LG_InputSupport +{ + LG_INPUT_SUPPORT_MOUSE_ABSOLUTE, +} +LG_InputSupport; + +typedef struct LG_InputOps +{ + const char * name; + + /* Operations must fail safely if a remote endpoint disappears. */ + bool (*supports)(void * opaque, LG_InputSupport support); + + /* Keyboard codes use the Linux input-event KEY_* values. */ + bool (*keyDown)(void * opaque, int key); + bool (*keyUp)(void * opaque, int key); + bool (*keyboardLEDs)(void * opaque, bool numLock, bool capsLock, + bool scrollLock); + + bool (*mouseMotion)(void * opaque, int32_t x, int32_t y); + /* Position is in guest pixels and must be within the supplied dimensions. */ + bool (*mousePosition)(void * opaque, uint32_t x, uint32_t y, + uint32_t width, uint32_t height); + /* Button order: left, middle, right, wheel up/down, side, extra. */ + bool (*mousePress)(void * opaque, unsigned int button); + bool (*mouseRelease)(void * opaque, unsigned int button); + + /* Restore all devices owned by this implementation to a neutral state. + * This must safely tolerate the remote input endpoint becoming unavailable. */ + void (*reset)(void * opaque); +} +LG_InputOps; + +#endif diff --git a/client/include/interface/transport.h b/client/include/interface/transport.h index d90b12b5..8d65e5a6 100644 --- a/client/include/interface/transport.h +++ b/client/include/interface/transport.h @@ -27,6 +27,7 @@ #include "common/framebuffer.h" #include "common/types.h" +#include "interface/input.h" #define LG_TRANSPORT_MAX_DAMAGE_RECTS LG_MAX_FRAME_DAMAGE_RECTS @@ -244,6 +245,10 @@ typedef struct LG_TransportOps void (*disconnect)(LG_Transport * transport); bool (*sessionValid)(LG_Transport * transport); bool (*supportsDMA)(LG_Transport * transport); + /* Queried after connect. The returned operations and opaque value remain + * valid until disconnect; NULL indicates that this session has no input. */ + const LG_InputOps *(*getInputOps)(LG_Transport * transport, + void ** opaque); bool (*attachRenderer)(LG_Transport * transport, const LG_RendererInterop * interop); void (*detachRenderer)(LG_Transport * transport); diff --git a/client/src/app.c b/client/src/app.c index 181c5ce8..aff6d6a3 100644 --- a/client/src/app.c +++ b/client/src/app.c @@ -26,6 +26,7 @@ #include "clipboard.h" #include "render_queue.h" #include "evdev.h" +#include "input.h" #include "kb.h" @@ -236,9 +237,7 @@ void app_handleFocusEvent(bool focused) core_setCursorInView(false); if (g_params.releaseKeysOnFocusLoss) - for (int key = 0; key < KEY_MAX; key++) - if (atomic_load_explicit(&g_state.keyDown[key], memory_order_relaxed)) - app_handleKeyReleaseInternal(key); + lgInput_releaseKeys(); g_state.escapeActive = false; @@ -472,7 +471,7 @@ void app_handleButtonPress(int button) if (!core_inputEnabled() || !g_cursor.inView || !g_cursor.viewReq) return; - if (!purespice_mousePress(button)) + if (!lgInput_mousePress(button)) DEBUG_ERROR("app_handleButtonPress: failed to send message"); } @@ -495,7 +494,7 @@ void app_handleButtonRelease(int button) if (!core_inputEnabled()) return; - if (!purespice_mouseRelease(button)) + if (!lgInput_mouseRelease(button)) DEBUG_ERROR("app_handleButtonRelease: failed to send message"); } @@ -551,20 +550,8 @@ void app_handleKeyPressInternal(int sc) if (g_params.ignoreWindowsKeys && (sc == KEY_LEFTMETA || sc == KEY_RIGHTMETA)) return; - if (!atomic_load_explicit(&g_state.keyDown[sc], memory_order_relaxed)) - { - uint32_t ps2 = linux_to_ps2[sc]; - if (!ps2) - return; - - if (purespice_keyDown(ps2)) - atomic_store_explicit(&g_state.keyDown[sc], true, memory_order_relaxed); - else - { - DEBUG_ERROR("app_handleKeyPress: failed to send message"); - return; - } - } + if (!lgInput_keyDown(sc)) + DEBUG_ERROR("app_handleKeyPress: failed to send message"); } void app_handleKeyReleaseInternal(int sc) @@ -573,7 +560,7 @@ void app_handleKeyReleaseInternal(int sc) { if (g_state.escapeAction == -1) { - if (!g_state.escapeHelp && g_params.useSpiceInput && + if (!g_state.escapeHelp && lgInput_available() && !app_isOverlayMode()) core_setGrab(!g_cursor.grab); } @@ -592,24 +579,11 @@ void app_handleKeyReleaseInternal(int sc) if (!core_inputEnabled()) return; - // avoid sending key up events when we didn't send a down - if (!atomic_load_explicit(&g_state.keyDown[sc], memory_order_relaxed)) - return; - if (g_params.ignoreWindowsKeys && (sc == KEY_LEFTMETA || sc == KEY_RIGHTMETA)) return; - uint32_t ps2 = linux_to_ps2[sc]; - if (!ps2) - return; - - if (purespice_keyUp(ps2)) - atomic_store_explicit(&g_state.keyDown[sc], false, memory_order_relaxed); - else - { + if (!lgInput_keyUp(sc)) DEBUG_ERROR("app_handleKeyRelease: failed to send message"); - return; - } } void app_handleKeyPress(int sc) @@ -643,12 +617,7 @@ void app_handleKeyboardLEDs(bool numLock, bool capsLock, bool scrollLock) if (!core_inputEnabled()) return; - uint32_t modifiers = - (scrollLock ? 1 /* SPICE_SCROLL_LOCK_MODIFIER */ : 0) | - (numLock ? 2 /* SPICE_NUM_LOCK_MODIFIER */ : 0) | - (capsLock ? 4 /* SPICE_CAPS_LOCK_MODIFIER */ : 0); - - if (!purespice_keyModifiers(modifiers)) + if (!lgInput_keyboardLEDs(numLock, capsLock, scrollLock)) DEBUG_ERROR("app_handleKeyboardLEDs: failed to send message"); } @@ -716,7 +685,7 @@ void app_handleMouseBasic(void) g_cursor.projected.x += x; g_cursor.projected.y += y; - if (!purespice_mouseMotion(x, y)) + if (!lgInput_mouseMotion(x, y)) DEBUG_ERROR("failed to send mouse motion message"); } @@ -1395,6 +1364,7 @@ bool app_useSpiceDisplay(bool enable) active = enable; atomic_store_explicit(&g_state.spiceDisplayActive, active, memory_order_release); + lgInput_useTransport(!active); overlayStatus_set(LG_USER_STATUS_SPICE, enable); done: diff --git a/client/src/config.c b/client/src/config.c index c702a507..43f24a8e 100644 --- a/client/src/config.c +++ b/client/src/config.c @@ -419,7 +419,7 @@ static struct Option options[] = { .module = "input", .name = "captureOnly", - .description = "Only enable input via SPICE if in capture mode", + .description = "Only enable guest input while in capture mode", .type = OPTION_TYPE_BOOL, .value.x_bool = false }, @@ -746,6 +746,12 @@ bool config_load(int argc, char * argv[]) g_params.helpMenuDelayUs = option_get_int("input", "helpMenuDelay") * (uint64_t) 1000; + g_params.scaleMouseInput = option_get_bool("spice", "scaleCursor"); + g_params.captureOnStart = option_get_bool("spice", "captureOnStart"); + g_params.alwaysShowCursor = option_get_bool("spice", "alwaysShowCursor"); + g_params.showCursorDot = option_get_bool("spice", "showCursorDot"); + g_params.largeCursorDot = option_get_bool("spice", "largeCursorDot"); + g_params.minimizeOnFocusLoss = option_get_bool("win", "minimizeOnFocusLoss"); g_params.setGuestRes = option_get_bool("win", "setGuestRes" ); @@ -769,12 +775,6 @@ bool config_load(int argc, char * argv[]) g_params.clipboardToVM = false; g_params.clipboardToLocal = false; } - - g_params.scaleMouseInput = option_get_bool("spice", "scaleCursor"); - g_params.captureOnStart = option_get_bool("spice", "captureOnStart"); - g_params.alwaysShowCursor = option_get_bool("spice", "alwaysShowCursor"); - g_params.showCursorDot = option_get_bool("spice", "showCursorDot"); - g_params.largeCursorDot = option_get_bool("spice", "largeCursorDot"); } g_params.audioDebug = option_get_bool("audio", "debug"); diff --git a/client/src/core.c b/client/src/core.c index 637d2be8..0ba73775 100644 --- a/client/src/core.c +++ b/client/src/core.c @@ -24,6 +24,7 @@ #include "util.h" #include "kb.h" #include "message.h" +#include "input.h" #include "common/time.h" #include "common/debug.h" @@ -132,7 +133,7 @@ static bool moveExit(double ex, double ey) bool core_inputEnabled(void) { - return g_params.useSpiceInput && !g_state.ignoreInput && + return lgInput_available() && !g_state.ignoreInput && ((g_cursor.grab && g_params.captureInputOnly) || !g_params.captureInputOnly); } @@ -719,7 +720,7 @@ void core_handleMouseGrabbed(double ex, double ey) if (x == 0 && y == 0) return; - if (!purespice_mouseMotion(x, y)) + if (!lgInput_mouseMotion(x, y)) DEBUG_ERROR("failed to send mouse motion message"); } @@ -737,9 +738,9 @@ void core_handleMouseNormal(double ex, double ey) // wiggle the mouse when the guest has not provided any information, we need // to do this because windows doesn't enable a cursor at all until it has // been moved for the first time. - if (!purespice_mouseMotion(1, 1)) + if (!lgInput_mouseMotion(1, 1)) DEBUG_ERROR("failed to send mouse motion message"); - if (!purespice_mouseMotion(-1, -1)) + if (!lgInput_mouseMotion(-1, -1)) DEBUG_ERROR("failed to send mouse motion message"); } return; @@ -985,7 +986,7 @@ fallback: x, y, g_cursor.guest.x, g_cursor.guest.y, didExit, testExit, g_cursor.warpState); - if (!purespice_mouseMotion(x, y)) + if (!lgInput_mouseMotion(x, y)) DEBUG_ERROR("failed to send mouse motion message"); } diff --git a/client/src/input.c b/client/src/input.c new file mode 100644 index 00000000..bba6f824 --- /dev/null +++ b/client/src/input.c @@ -0,0 +1,362 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "input.h" + +#include "common/debug.h" +#include "common/locking.h" + +#include +#include + +struct InputBinding +{ + const LG_InputOps * ops; + void * opaque; + bool mouseAbsolute; +}; + +static struct +{ + LG_RWLock activeLock; + + struct InputBinding fallback; + struct InputBinding transport; + struct InputBinding active; + bool useTransport; + + _Atomic(bool) keys[KEY_MAX]; + _Atomic(uint32_t) buttons; +} +l_input; + +static bool validOps(const LG_InputOps * ops) +{ + return + ops && + ops->name && + ops->supports && + ops->keyDown && + ops->keyUp && + ops->mouseMotion && + ops->mousePress && + ops->mouseRelease; +} + +static struct InputBinding makeBinding(const LG_InputOps * ops, + void * opaque) +{ + return (struct InputBinding) + { + .ops = ops, + .opaque = opaque, + .mouseAbsolute = ops && ops->mousePosition && + ops->supports(opaque, LG_INPUT_SUPPORT_MOUSE_ABSOLUTE), + }; +} + +static void releaseKeysNL(void) +{ + for (int key = 0; key < KEY_MAX; ++key) + if (atomic_exchange_explicit(&l_input.keys[key], false, + memory_order_relaxed) && l_input.active.ops) + l_input.active.ops->keyUp(l_input.active.opaque, key); +} + +static void releaseButtonsNL(void) +{ + const uint32_t buttons = atomic_exchange_explicit( + &l_input.buttons, 0, memory_order_relaxed); + + if (!l_input.active.ops) + return; + + for (unsigned int button = 1; button < 32; ++button) + if (buttons & (UINT32_C(1) << button)) + l_input.active.ops->mouseRelease(l_input.active.opaque, button); +} + +static void resetActiveNL(void) +{ + releaseKeysNL(); + releaseButtonsNL(); + if (l_input.active.ops && l_input.active.ops->reset) + l_input.active.ops->reset(l_input.active.opaque); +} + +static void clearStateNL(void) +{ + for (int key = 0; key < KEY_MAX; ++key) + atomic_store_explicit(&l_input.keys[key], false, memory_order_relaxed); + + atomic_store_explicit(&l_input.buttons, 0, memory_order_relaxed); +} + +static void updateActiveNL(void) +{ + const struct InputBinding next = + l_input.useTransport && l_input.transport.ops ? + l_input.transport : l_input.fallback; + + if (next.ops == l_input.active.ops && + next.opaque == l_input.active.opaque) + return; + + resetActiveNL(); + l_input.active = next; + + if (l_input.active.ops) + { + if (l_input.active.ops->reset) + l_input.active.ops->reset(l_input.active.opaque); + DEBUG_INFO("Using Input: %s", l_input.active.ops->name); + } + else + DEBUG_INFO("Input is unavailable"); +} + +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; + + for (int key = 0; key < KEY_MAX; ++key) + atomic_init(&l_input.keys[key], false); + atomic_init(&l_input.buttons, 0); + + LG_RWLOCK_INIT(l_input.activeLock); +} + +void lgInput_free(void) +{ + LG_LOCK_EXCLUSIVE(l_input.activeLock); + resetActiveNL(); + l_input.active = (struct InputBinding) { 0 }; + l_input.fallback = (struct InputBinding) { 0 }; + l_input.transport = (struct InputBinding) { 0 }; + LG_UNLOCK_EXCLUSIVE(l_input.activeLock); + LG_RWLOCK_FREE(l_input.activeLock); +} + +void lgInput_setFallback(const LG_InputOps * ops, void * opaque) +{ + if (ops && !validOps(ops)) + { + DEBUG_ERROR("Invalid fallback input operations"); + ops = NULL; + opaque = NULL; + } + + LG_LOCK_EXCLUSIVE(l_input.activeLock); + l_input.fallback = makeBinding(ops, opaque); + updateActiveNL(); + LG_UNLOCK_EXCLUSIVE(l_input.activeLock); +} + +void lgInput_setTransport(const LG_InputOps * ops, void * opaque) +{ + if (ops && !validOps(ops)) + { + DEBUG_ERROR("Invalid transport input operations"); + ops = NULL; + opaque = NULL; + } + + LG_LOCK_EXCLUSIVE(l_input.activeLock); + l_input.transport = makeBinding(ops, opaque); + updateActiveNL(); + LG_UNLOCK_EXCLUSIVE(l_input.activeLock); +} + +void lgInput_dropTransport(void) +{ + LG_LOCK_EXCLUSIVE(l_input.activeLock); + if (l_input.useTransport && l_input.active.ops == l_input.transport.ops && + l_input.active.opaque == l_input.transport.opaque) + { + l_input.active = (struct InputBinding) { 0 }; + clearStateNL(); + } + + l_input.transport = (struct InputBinding) { 0 }; + updateActiveNL(); + LG_UNLOCK_EXCLUSIVE(l_input.activeLock); +} + +void lgInput_useTransport(bool enable) +{ + LG_LOCK_EXCLUSIVE(l_input.activeLock); + l_input.useTransport = enable; + updateActiveNL(); + LG_UNLOCK_EXCLUSIVE(l_input.activeLock); +} + +bool lgInput_available(void) +{ + LG_LOCK_SHARED(l_input.activeLock); + const bool result = l_input.active.ops != NULL; + LG_UNLOCK_SHARED(l_input.activeLock); + return result; +} + +bool lgInput_supports(LG_InputSupport support) +{ + LG_LOCK_SHARED(l_input.activeLock); + bool result; + switch (support) + { + case LG_INPUT_SUPPORT_MOUSE_ABSOLUTE: + result = l_input.active.mouseAbsolute; + break; + + default: + result = false; + break; + } + LG_UNLOCK_SHARED(l_input.activeLock); + return result; +} + +bool lgInput_keyDown(int key) +{ + if (key < 0 || key >= KEY_MAX) + return false; + + LG_LOCK_SHARED(l_input.activeLock); + if (atomic_exchange_explicit( + &l_input.keys[key], true, memory_order_relaxed)) + { + LG_UNLOCK_SHARED(l_input.activeLock); + return true; + } + + const bool result = l_input.active.ops && + l_input.active.ops->keyDown(l_input.active.opaque, key); + if (!result) + atomic_store_explicit(&l_input.keys[key], false, memory_order_relaxed); + LG_UNLOCK_SHARED(l_input.activeLock); + return result; +} + +bool lgInput_keyUp(int key) +{ + if (key < 0 || key >= KEY_MAX) + return false; + + LG_LOCK_SHARED(l_input.activeLock); + if (!atomic_exchange_explicit( + &l_input.keys[key], false, memory_order_relaxed)) + { + LG_UNLOCK_SHARED(l_input.activeLock); + return true; + } + + const bool result = l_input.active.ops && + l_input.active.ops->keyUp(l_input.active.opaque, key); + if (!result) + atomic_store_explicit(&l_input.keys[key], true, memory_order_relaxed); + LG_UNLOCK_SHARED(l_input.activeLock); + return result; +} + +bool lgInput_keyboardLEDs(bool numLock, bool capsLock, bool scrollLock) +{ + LG_LOCK_SHARED(l_input.activeLock); + const bool result = l_input.active.ops && + (!l_input.active.ops->keyboardLEDs || + l_input.active.ops->keyboardLEDs(l_input.active.opaque, + numLock, capsLock, scrollLock)); + LG_UNLOCK_SHARED(l_input.activeLock); + return result; +} + +void lgInput_releaseKeys(void) +{ + LG_LOCK_EXCLUSIVE(l_input.activeLock); + releaseKeysNL(); + LG_UNLOCK_EXCLUSIVE(l_input.activeLock); +} + +bool lgInput_mouseMotion(int32_t x, int32_t y) +{ + LG_LOCK_SHARED(l_input.activeLock); + const bool result = l_input.active.ops && + l_input.active.ops->mouseMotion(l_input.active.opaque, x, y); + LG_UNLOCK_SHARED(l_input.activeLock); + return result; +} + +bool lgInput_mousePosition(uint32_t x, uint32_t y, uint32_t width, + uint32_t height) +{ + LG_LOCK_SHARED(l_input.activeLock); + const bool result = l_input.active.mouseAbsolute && + l_input.active.ops->mousePosition(l_input.active.opaque, + x, y, width, height); + LG_UNLOCK_SHARED(l_input.activeLock); + return result; +} + +bool lgInput_mousePress(unsigned int button) +{ + if (button == 0 || button >= 32) + return false; + + const uint32_t mask = UINT32_C(1) << button; + LG_LOCK_SHARED(l_input.activeLock); + if (atomic_fetch_or_explicit( + &l_input.buttons, mask, memory_order_relaxed) & mask) + { + LG_UNLOCK_SHARED(l_input.activeLock); + return true; + } + + const bool result = l_input.active.ops && + l_input.active.ops->mousePress(l_input.active.opaque, button); + if (!result) + atomic_fetch_and_explicit( + &l_input.buttons, ~mask, memory_order_relaxed); + LG_UNLOCK_SHARED(l_input.activeLock); + return result; +} + +bool lgInput_mouseRelease(unsigned int button) +{ + if (button == 0 || button >= 32) + return false; + + const uint32_t mask = UINT32_C(1) << button; + LG_LOCK_SHARED(l_input.activeLock); + if (!(atomic_fetch_and_explicit( + &l_input.buttons, ~mask, memory_order_relaxed) & mask)) + { + LG_UNLOCK_SHARED(l_input.activeLock); + return true; + } + + const bool result = l_input.active.ops && + l_input.active.ops->mouseRelease(l_input.active.opaque, button); + if (!result) + atomic_fetch_or_explicit(&l_input.buttons, mask, memory_order_relaxed); + LG_UNLOCK_SHARED(l_input.activeLock); + return result; +} diff --git a/client/src/input_spice.c b/client/src/input_spice.c new file mode 100644 index 00000000..efaec2c9 --- /dev/null +++ b/client/src/input_spice.c @@ -0,0 +1,82 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "input.h" +#include "kb.h" + +#include +#include + +static bool spiceSupports(void * opaque, LG_InputSupport support) +{ + return false; +} + +static bool spiceKeyDown(void * opaque, int key) +{ + const uint32_t ps2 = linux_to_ps2[key]; + return !ps2 || purespice_keyDown(ps2); +} + +static bool spiceKeyUp(void * opaque, int key) +{ + const uint32_t ps2 = linux_to_ps2[key]; + return !ps2 || purespice_keyUp(ps2); +} + +static bool spiceKeyboardLEDs(void * opaque, bool numLock, bool capsLock, + bool scrollLock) +{ + const uint32_t modifiers = + (scrollLock ? 1 /* SPICE_SCROLL_LOCK_MODIFIER */ : 0) | + (numLock ? 2 /* SPICE_NUM_LOCK_MODIFIER */ : 0) | + (capsLock ? 4 /* SPICE_CAPS_LOCK_MODIFIER */ : 0); + + return purespice_keyModifiers(modifiers); +} + +static bool spiceMouseMotion(void * opaque, int32_t x, int32_t y) +{ + return purespice_mouseMotion(x, y); +} + +static bool spiceMousePress(void * opaque, unsigned int button) +{ + return purespice_mousePress(button); +} + +static bool spiceMouseRelease(void * opaque, unsigned int button) +{ + return purespice_mouseRelease(button); +} + +const LG_InputOps LGI_Spice = +{ + .name = "SPICE", + .supports = spiceSupports, + .keyDown = spiceKeyDown, + .keyUp = spiceKeyUp, + .keyboardLEDs = spiceKeyboardLEDs, + .mouseMotion = spiceMouseMotion, + .mousePosition = NULL, + .mousePress = spiceMousePress, + .mouseRelease = spiceMouseRelease, + .reset = NULL, +}; diff --git a/client/src/keybind.c b/client/src/keybind.c index e949a76c..e28bb30e 100644 --- a/client/src/keybind.c +++ b/client/src/keybind.c @@ -24,10 +24,10 @@ #include "app.h" #include "audio.h" #include "core.h" +#include "input.h" #include "kb.h" #include "message.h" -#include #include static void bind_fullscreen(int sc, void * opaque) @@ -96,23 +96,19 @@ static void bind_mouseSens(int sc, void * opaque) static void bind_ctrlAltFn(int sc, void * opaque) { - const uint32_t ctrl = linux_to_ps2[KEY_LEFTCTRL]; - const uint32_t alt = linux_to_ps2[KEY_LEFTALT ]; - const uint32_t fn = linux_to_ps2[sc]; - purespice_keyDown(ctrl); - purespice_keyDown(alt ); - purespice_keyDown(fn ); + lgInput_keyDown(KEY_LEFTCTRL); + lgInput_keyDown(KEY_LEFTALT); + lgInput_keyDown(sc); - purespice_keyUp(ctrl); - purespice_keyUp(alt ); - purespice_keyUp(fn ); + lgInput_keyUp(KEY_LEFTCTRL); + lgInput_keyUp(KEY_LEFTALT); + lgInput_keyUp(sc); } static void bind_passthrough(int sc, void * opaque) { - sc = linux_to_ps2[sc]; - purespice_keyDown(sc); - purespice_keyUp (sc); + lgInput_keyDown(sc); + lgInput_keyUp (sc); } static void bind_toggleOverlay(int sc, void * opaque) @@ -122,8 +118,9 @@ static void bind_toggleOverlay(int sc, void * opaque) static void bind_toggleKey(int sc, void * opaque) { - purespice_keyDown((uintptr_t) opaque); - purespice_keyUp((uintptr_t) opaque); + const int key = (uintptr_t) opaque; + lgInput_keyDown(key); + lgInput_keyUp(key); } static void bind_setGuestRes(int sc, void * opaque) @@ -183,25 +180,25 @@ static void bind_toggleMicDefault(int sc, void * opaque) } #endif -void keybind_spiceRegister(void) +void keybind_inputRegister(void) { - /* register the common keybinds for spice */ + /* register the common input keybinds */ static bool firstTime = true; if (firstTime) { app_registerKeybind(KEY_I, bind_input, NULL, - "Spice keyboard & mouse toggle"); + "Keyboard & mouse toggle"); app_registerKeybind(KEY_INSERT, bind_mouseSens, (void *) true, "Increase mouse sensitivity in capture mode"); app_registerKeybind(KEY_DELETE, bind_mouseSens, (void *) false, "Decrease mouse sensitivity in capture mode"); - app_registerKeybind(KEY_UP, bind_toggleKey, (void *) PS2_VOLUME_UP, + app_registerKeybind(KEY_UP, bind_toggleKey, (void *) KEY_VOLUMEUP, "Send volume up to the guest"); - app_registerKeybind(KEY_DOWN, bind_toggleKey, (void *) PS2_VOLUME_DOWN, + app_registerKeybind(KEY_DOWN, bind_toggleKey, (void *) KEY_VOLUMEDOWN, "Send volume down to the guest"); - app_registerKeybind(KEY_M, bind_toggleKey, (void *) PS2_MUTE, + app_registerKeybind(KEY_M, bind_toggleKey, (void *) KEY_MUTE, "Send mute to the guest"); app_registerKeybind(KEY_LEFTMETA, bind_passthrough, NULL, diff --git a/client/src/keybind.h b/client/src/keybind.h index 2fe50e89..473a14c5 100644 --- a/client/src/keybind.h +++ b/client/src/keybind.h @@ -22,6 +22,6 @@ #define _H_LG_KEYBIND_ void keybind_commonRegister(void); -void keybind_spiceRegister(void); +void keybind_inputRegister(void); #endif diff --git a/client/src/main.c b/client/src/main.c index 2e30390b..95980278 100644 --- a/client/src/main.c +++ b/client/src/main.c @@ -69,6 +69,7 @@ #include "render_queue.h" #include "evdev.h" #include "frame_scheduler.h" +#include "input.h" #ifdef ENABLE_TESTS #include "interface/test_capture.h" @@ -150,8 +151,8 @@ static void lgInit(void) g_cursor.surfaceExit = false; g_cursor.guest.valid = false; - // if spice is not in use, hide the local cursor - if ((!g_params.useSpiceInput && g_params.hideMouse) || !g_params.showCursorDot) + // if guest input is not in use, hide the local cursor + if ((!lgInput_available() && g_params.hideMouse) || !g_params.showCursorDot) g_state.ds->setPointer(LG_POINTER_NONE); else g_state.ds->setPointer(LG_POINTER_SQUARE); @@ -1022,6 +1023,12 @@ static int renderThread(void * unused) lgTimerDestroy(tickTimer); lgTimerDestroy(fpsTimer); + if (g_state.transport && + g_state.transportOps->sessionValid(g_state.transport)) + lgInput_setTransport(NULL, NULL); + else + lgInput_dropTransport(); + core_stopCursorThread(); core_stopFrameThread(); @@ -1058,7 +1065,7 @@ int main_cursorThread(void * unused) g_cursor.redraw = false; RENDERER(onMouseEvent, g_cursor.guest.visible && - (g_cursor.draw || !g_params.useSpiceInput), + (g_cursor.draw || !lgInput_available()), g_cursor.guest.x, g_cursor.guest.y, g_cursor.guest.hx, g_cursor.guest.hy); if (!g_state.stopVideo) @@ -1067,6 +1074,8 @@ int main_cursorThread(void * unused) continue; } + if (status == LG_TRANSPORT_DISCONNECTED) + lgInput_dropTransport(); app_setState(status == LG_TRANSPORT_DISCONNECTED ? APP_STATE_RESTART : APP_STATE_SHUTDOWN); if (status != LG_TRANSPORT_DISCONNECTED) @@ -1075,7 +1084,7 @@ int main_cursorThread(void * unused) } const bool wasRendered = g_cursor.guest.visible && - (g_cursor.draw || !g_params.useSpiceInput); + (g_cursor.draw || !lgInput_available()); bool hotspotChanged = false; if (pointer.flags & LG_TRANSPORT_POINTER_VISIBLE_VALID) @@ -1156,12 +1165,12 @@ int main_cursorThread(void * unused) app_updateMouseState(); g_cursor.redraw = false; RENDERER(onMouseEvent, - g_cursor.guest.visible && (g_cursor.draw || !g_params.useSpiceInput), + g_cursor.guest.visible && (g_cursor.draw || !lgInput_available()), g_cursor.guest.x, g_cursor.guest.y, g_cursor.guest.hx, g_cursor.guest.hy); const bool isRendered = g_cursor.guest.visible && - (g_cursor.draw || !g_params.useSpiceInput); + (g_cursor.draw || !lgInput_available()); const bool contentChanged = (pointer.flags & (LG_TRANSPORT_POINTER_SHAPE | LG_TRANSPORT_POINTER_COLOR_TRANSFORM)) || whiteLevelChanged; @@ -1211,7 +1220,10 @@ int main_frameThread(void * unused) continue; } if (status == LG_TRANSPORT_DISCONNECTED) + { + lgInput_dropTransport(); app_setState(APP_STATE_RESTART); + } else if (status == LG_TRANSPORT_END) app_setState(APP_STATE_SHUTDOWN); else @@ -1484,9 +1496,10 @@ static void checkUUID(void) app_msgBox( "SPICE Configuration Error", "You have connected SPICE to the wrong guest.\n" - "Input will not function until this is corrected."); + "SPICE input will not function until this is corrected."); g_params.useSpiceInput = false; + lgInput_setFallback(NULL, NULL); atomic_store_explicit(&g_state.spiceClose, true, memory_order_release); purespice_disconnect(); } @@ -1494,6 +1507,9 @@ static void checkUUID(void) void spiceReady(void) { atomic_store_explicit(&g_state.spiceReady, true, memory_order_release); + if (g_params.useSpiceInput) + lgInput_setFallback(&LGI_Spice, NULL); + if (atomic_load_explicit(&g_state.spiceDisplayRequested, memory_order_acquire)) app_useSpiceDisplay(true); @@ -1525,7 +1541,7 @@ void spiceReady(void) DEBUG_WARN("Failed to obtain SPICE server information"); if (g_params.useSpiceInput) - keybind_spiceRegister(); + keybind_inputRegister(); lgSignalEvent(e_spice); } @@ -1759,27 +1775,12 @@ int spiceThread(void * arg) } } - // send key up events for any pressed keys - if (g_params.useSpiceInput) - { - for(int scancode = 0; scancode < KEY_MAX; ++scancode) - if (atomic_load_explicit( - &g_state.keyDown[scancode], memory_order_relaxed)) - { - const uint32_t ps2 = linux_to_ps2[scancode]; - if (ps2 && purespice_keyUp(ps2)) - atomic_store_explicit( - &g_state.keyDown[scancode], false, memory_order_relaxed); - else - DEBUG_ERROR("Failed to release key %d during SPICE shutdown", - scancode); - } - } - + lgInput_setFallback(NULL, NULL); purespice_disconnect(); end: + lgInput_setFallback(NULL, NULL); audio_free(); // if the connection was disconnected intentionally we don't want to shutdown @@ -1890,6 +1891,7 @@ static int lg_run(void) { LG_LOCK_INIT(l_cursorRepaint.lock); frameTimingInit(); + lgInput_init(); #ifdef ENABLE_TESTS memset(&l_testCapture, 0, sizeof(l_testCapture)); @@ -2355,8 +2357,13 @@ restart: g_state.transportFeatures = session.features; frameScheduler_start(session.features); - if (g_state.spiceReady && g_params.useSpiceInput) - keybind_spiceRegister(); + void * inputOpaque = NULL; + const LG_InputOps * inputOps = g_state.transportOps->getInputOps ? + g_state.transportOps->getInputOps(g_state.transport, &inputOpaque) : NULL; + lgInput_setTransport(inputOps, inputOpaque); + + if (lgInput_available()) + keybind_inputRegister(); checkUUID(); DEBUG_INFO("Starting session"); atomic_store_explicit( @@ -2371,6 +2378,7 @@ restart: { if (unlikely(!g_state.transportOps->sessionValid(g_state.transport))) { + lgInput_dropTransport(); atomic_store_explicit( &g_state.lgHostConnected, false, memory_order_release); DEBUG_INFO("Waiting for the host to restart..."); @@ -2391,6 +2399,7 @@ restart: core_stopFrameThread(); core_stopCursorThread(); + lgInput_dropTransport(); g_state.transportOps->disconnect(g_state.transport); app_setState(APP_STATE_RUNNING); @@ -2435,7 +2444,12 @@ static void lg_shutdown(void) LG_LOCK_FREE(l_cursorRepaint.lock); if (g_state.transportOps) + { + lgInput_dropTransport(); g_state.transportOps->destroy(&g_state.transport); + } + + lgInput_free(); if (g_state.frameEvent) { diff --git a/client/src/main.h b/client/src/main.h index 32b95c7d..b56811d3 100644 --- a/client/src/main.h +++ b/client/src/main.h @@ -99,8 +99,6 @@ struct AppState int escapeAction; bool escapeHelp; struct ll * bindings; - atomic_bool keyDown[KEY_MAX]; - bool haveSrcSize; struct Point windowPos; int windowW, windowH; diff --git a/client/tests/mouse_test.c b/client/tests/mouse_test.c index c7d3e088..9b3764fe 100644 --- a/client/tests/mouse_test.c +++ b/client/tests/mouse_test.c @@ -241,7 +241,12 @@ void app_mouseTrace(const char * file, unsigned int line, (void)format; } -bool purespice_mouseMotion(int32_t x, int32_t y) +bool lgInput_available(void) +{ + return g_params.useSpiceInput; +} + +bool lgInput_mouseMotion(int32_t x, int32_t y) { push(EV_MOTION, x, y, false); return true; diff --git a/client/transports/LGMP/lgmp.c b/client/transports/LGMP/lgmp.c index 458aa5cd..a24e7b3b 100644 --- a/client/transports/LGMP/lgmp.c +++ b/client/transports/LGMP/lgmp.c @@ -1327,6 +1327,13 @@ static LG_TransportStatus lgmp_controlStatus(LG_Transport * this, LG_TRANSPORT_OK : LG_TRANSPORT_UNAVAILABLE; } +static const LG_InputOps * lgmp_getInputOps(LG_Transport * this, + void ** opaque) +{ + *opaque = NULL; + return NULL; +} + const LG_TransportOps LGT_LGMP = { .name = "lgmp", @@ -1337,6 +1344,7 @@ const LG_TransportOps LGT_LGMP = .disconnect = lgmp_disconnect, .sessionValid = lgmp_sessionValid, .supportsDMA = lgmp_supportsDMA, + .getInputOps = lgmp_getInputOps, .attachRenderer = lgmp_attachRenderer, .detachRenderer = lgmp_detachRenderer, .nextFrame = lgmp_nextFrame, diff --git a/client/transports/Test/test.c b/client/transports/Test/test.c index 4858cb68..4cf8a1e1 100644 --- a/client/transports/Test/test.c +++ b/client/transports/Test/test.c @@ -727,6 +727,13 @@ static LG_TransportStatus test_controlStatus(LG_Transport * this, LG_TRANSPORT_DISCONNECTED; } +static const LG_InputOps * test_getInputOps(LG_Transport * this, + void ** opaque) +{ + *opaque = NULL; + return NULL; +} + const LG_TransportOps LGT_Test = { .name = "test", @@ -737,6 +744,7 @@ const LG_TransportOps LGT_Test = .disconnect = test_disconnect, .sessionValid = test_sessionValid, .supportsDMA = test_supportsDMA, + .getInputOps = test_getInputOps, .attachRenderer = test_attachRenderer, .detachRenderer = test_detachRenderer, .nextFrame = test_nextFrame, diff --git a/common/include/common/locking.h b/common/include/common/locking.h index e2274870..a75b1977 100644 --- a/common/include/common/locking.h +++ b/common/include/common/locking.h @@ -34,6 +34,50 @@ typedef atomic_flag LG_Lock; atomic_flag_clear_explicit(&(x), memory_order_release); #define LG_LOCK_FREE(x) +typedef struct LG_RWLock +{ + atomic_uint readers; + atomic_uint writers; + atomic_flag writer; +} +LG_RWLock; + +#define LG_RWLOCK_INIT(x) do { \ + atomic_init(&(x).readers, 0); \ + atomic_init(&(x).writers, 0); \ + atomic_flag_clear(&(x).writer); \ +} while (0) + +#define LG_LOCK_SHARED(x) do { \ + for (;;) \ + { \ + while (atomic_load_explicit( \ + &(x).writers, memory_order_seq_cst)) { ; } \ + atomic_fetch_add_explicit(&(x).readers, 1, memory_order_seq_cst); \ + if (!atomic_load_explicit(&(x).writers, memory_order_seq_cst)) \ + break; \ + atomic_fetch_sub_explicit(&(x).readers, 1, memory_order_seq_cst); \ + } \ +} while (0) + +#define LG_UNLOCK_SHARED(x) \ + atomic_fetch_sub_explicit(&(x).readers, 1, memory_order_seq_cst) + +#define LG_LOCK_EXCLUSIVE(x) do { \ + atomic_fetch_add_explicit(&(x).writers, 1, memory_order_seq_cst); \ + while (atomic_flag_test_and_set_explicit( \ + &(x).writer, memory_order_seq_cst)) { ; } \ + while (atomic_load_explicit( \ + &(x).readers, memory_order_seq_cst)) { ; } \ +} while (0) + +#define LG_UNLOCK_EXCLUSIVE(x) do { \ + atomic_flag_clear_explicit(&(x).writer, memory_order_seq_cst); \ + atomic_fetch_sub_explicit(&(x).writers, 1, memory_order_seq_cst); \ +} while (0) + +#define LG_RWLOCK_FREE(x) + #define INTERLOCKED_INC(x) atomic_fetch_add((x), 1) #define INTERLOCKED_DEC(x) atomic_fetch_sub((x), 1)