[client] input: abstract guest input backends

Introduce LG_InputOps and route keyboard and mouse input through the
active video transport when it provides an input backend.

Keep SPICE as the fallback and force it while the SPICE display is
active. Add atomic shared/exclusive locking for safe backend changes.
This commit is contained in:
Geoffrey McRae
2026-08-08 21:23:32 +10:00
parent 04fd31219c
commit f495e5b7ba
17 changed files with 711 additions and 105 deletions

View File

@@ -167,6 +167,8 @@ set(SOURCES
src/render_queue.c src/render_queue.c
src/evdev.c src/evdev.c
src/transport.c src/transport.c
src/input.c
src/input_spice.c
src/overlay/splash.c src/overlay/splash.c
src/overlay/alert.c src/overlay/alert.c

50
client/include/input.h Normal file
View File

@@ -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

View File

@@ -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 <stdbool.h>
#include <stdint.h>
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

View File

@@ -27,6 +27,7 @@
#include "common/framebuffer.h" #include "common/framebuffer.h"
#include "common/types.h" #include "common/types.h"
#include "interface/input.h"
#define LG_TRANSPORT_MAX_DAMAGE_RECTS LG_MAX_FRAME_DAMAGE_RECTS #define LG_TRANSPORT_MAX_DAMAGE_RECTS LG_MAX_FRAME_DAMAGE_RECTS
@@ -244,6 +245,10 @@ typedef struct LG_TransportOps
void (*disconnect)(LG_Transport * transport); void (*disconnect)(LG_Transport * transport);
bool (*sessionValid)(LG_Transport * transport); bool (*sessionValid)(LG_Transport * transport);
bool (*supportsDMA)(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, bool (*attachRenderer)(LG_Transport * transport,
const LG_RendererInterop * interop); const LG_RendererInterop * interop);
void (*detachRenderer)(LG_Transport * transport); void (*detachRenderer)(LG_Transport * transport);

View File

@@ -26,6 +26,7 @@
#include "clipboard.h" #include "clipboard.h"
#include "render_queue.h" #include "render_queue.h"
#include "evdev.h" #include "evdev.h"
#include "input.h"
#include "kb.h" #include "kb.h"
@@ -236,9 +237,7 @@ void app_handleFocusEvent(bool focused)
core_setCursorInView(false); core_setCursorInView(false);
if (g_params.releaseKeysOnFocusLoss) if (g_params.releaseKeysOnFocusLoss)
for (int key = 0; key < KEY_MAX; key++) lgInput_releaseKeys();
if (atomic_load_explicit(&g_state.keyDown[key], memory_order_relaxed))
app_handleKeyReleaseInternal(key);
g_state.escapeActive = false; g_state.escapeActive = false;
@@ -472,7 +471,7 @@ void app_handleButtonPress(int button)
if (!core_inputEnabled() || !g_cursor.inView || !g_cursor.viewReq) if (!core_inputEnabled() || !g_cursor.inView || !g_cursor.viewReq)
return; return;
if (!purespice_mousePress(button)) if (!lgInput_mousePress(button))
DEBUG_ERROR("app_handleButtonPress: failed to send message"); DEBUG_ERROR("app_handleButtonPress: failed to send message");
} }
@@ -495,7 +494,7 @@ void app_handleButtonRelease(int button)
if (!core_inputEnabled()) if (!core_inputEnabled())
return; return;
if (!purespice_mouseRelease(button)) if (!lgInput_mouseRelease(button))
DEBUG_ERROR("app_handleButtonRelease: failed to send message"); 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)) if (g_params.ignoreWindowsKeys && (sc == KEY_LEFTMETA || sc == KEY_RIGHTMETA))
return; return;
if (!atomic_load_explicit(&g_state.keyDown[sc], memory_order_relaxed)) if (!lgInput_keyDown(sc))
{ DEBUG_ERROR("app_handleKeyPress: failed to send message");
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;
}
}
} }
void app_handleKeyReleaseInternal(int sc) void app_handleKeyReleaseInternal(int sc)
@@ -573,7 +560,7 @@ void app_handleKeyReleaseInternal(int sc)
{ {
if (g_state.escapeAction == -1) if (g_state.escapeAction == -1)
{ {
if (!g_state.escapeHelp && g_params.useSpiceInput && if (!g_state.escapeHelp && lgInput_available() &&
!app_isOverlayMode()) !app_isOverlayMode())
core_setGrab(!g_cursor.grab); core_setGrab(!g_cursor.grab);
} }
@@ -592,24 +579,11 @@ void app_handleKeyReleaseInternal(int sc)
if (!core_inputEnabled()) if (!core_inputEnabled())
return; 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)) if (g_params.ignoreWindowsKeys && (sc == KEY_LEFTMETA || sc == KEY_RIGHTMETA))
return; return;
uint32_t ps2 = linux_to_ps2[sc]; if (!lgInput_keyUp(sc))
if (!ps2)
return;
if (purespice_keyUp(ps2))
atomic_store_explicit(&g_state.keyDown[sc], false, memory_order_relaxed);
else
{
DEBUG_ERROR("app_handleKeyRelease: failed to send message"); DEBUG_ERROR("app_handleKeyRelease: failed to send message");
return;
}
} }
void app_handleKeyPress(int sc) void app_handleKeyPress(int sc)
@@ -643,12 +617,7 @@ void app_handleKeyboardLEDs(bool numLock, bool capsLock, bool scrollLock)
if (!core_inputEnabled()) if (!core_inputEnabled())
return; return;
uint32_t modifiers = if (!lgInput_keyboardLEDs(numLock, capsLock, scrollLock))
(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))
DEBUG_ERROR("app_handleKeyboardLEDs: failed to send message"); DEBUG_ERROR("app_handleKeyboardLEDs: failed to send message");
} }
@@ -716,7 +685,7 @@ void app_handleMouseBasic(void)
g_cursor.projected.x += x; g_cursor.projected.x += x;
g_cursor.projected.y += y; g_cursor.projected.y += y;
if (!purespice_mouseMotion(x, y)) if (!lgInput_mouseMotion(x, y))
DEBUG_ERROR("failed to send mouse motion message"); DEBUG_ERROR("failed to send mouse motion message");
} }
@@ -1395,6 +1364,7 @@ bool app_useSpiceDisplay(bool enable)
active = enable; active = enable;
atomic_store_explicit(&g_state.spiceDisplayActive, active, atomic_store_explicit(&g_state.spiceDisplayActive, active,
memory_order_release); memory_order_release);
lgInput_useTransport(!active);
overlayStatus_set(LG_USER_STATUS_SPICE, enable); overlayStatus_set(LG_USER_STATUS_SPICE, enable);
done: done:

View File

@@ -419,7 +419,7 @@ static struct Option options[] =
{ {
.module = "input", .module = "input",
.name = "captureOnly", .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, .type = OPTION_TYPE_BOOL,
.value.x_bool = false .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.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.minimizeOnFocusLoss = option_get_bool("win", "minimizeOnFocusLoss");
g_params.setGuestRes = option_get_bool("win", "setGuestRes" ); 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.clipboardToVM = false;
g_params.clipboardToLocal = 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"); g_params.audioDebug = option_get_bool("audio", "debug");

View File

@@ -24,6 +24,7 @@
#include "util.h" #include "util.h"
#include "kb.h" #include "kb.h"
#include "message.h" #include "message.h"
#include "input.h"
#include "common/time.h" #include "common/time.h"
#include "common/debug.h" #include "common/debug.h"
@@ -132,7 +133,7 @@ static bool moveExit(double ex, double ey)
bool core_inputEnabled(void) 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); ((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) if (x == 0 && y == 0)
return; return;
if (!purespice_mouseMotion(x, y)) if (!lgInput_mouseMotion(x, y))
DEBUG_ERROR("failed to send mouse motion message"); 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 // 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 // to do this because windows doesn't enable a cursor at all until it has
// been moved for the first time. // been moved for the first time.
if (!purespice_mouseMotion(1, 1)) if (!lgInput_mouseMotion(1, 1))
DEBUG_ERROR("failed to send mouse motion message"); 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"); DEBUG_ERROR("failed to send mouse motion message");
} }
return; return;
@@ -985,7 +986,7 @@ fallback:
x, y, g_cursor.guest.x, g_cursor.guest.y, didExit, testExit, x, y, g_cursor.guest.x, g_cursor.guest.y, didExit, testExit,
g_cursor.warpState); g_cursor.warpState);
if (!purespice_mouseMotion(x, y)) if (!lgInput_mouseMotion(x, y))
DEBUG_ERROR("failed to send mouse motion message"); DEBUG_ERROR("failed to send mouse motion message");
} }

362
client/src/input.c Normal file
View File

@@ -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 <linux/input.h>
#include <stdatomic.h>
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;
}

82
client/src/input_spice.c Normal file
View File

@@ -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 <purespice.h>
#include <stddef.h>
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,
};

View File

@@ -24,10 +24,10 @@
#include "app.h" #include "app.h"
#include "audio.h" #include "audio.h"
#include "core.h" #include "core.h"
#include "input.h"
#include "kb.h" #include "kb.h"
#include "message.h" #include "message.h"
#include <purespice.h>
#include <stdio.h> #include <stdio.h>
static void bind_fullscreen(int sc, void * opaque) 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) static void bind_ctrlAltFn(int sc, void * opaque)
{ {
const uint32_t ctrl = linux_to_ps2[KEY_LEFTCTRL]; lgInput_keyDown(KEY_LEFTCTRL);
const uint32_t alt = linux_to_ps2[KEY_LEFTALT ]; lgInput_keyDown(KEY_LEFTALT);
const uint32_t fn = linux_to_ps2[sc]; lgInput_keyDown(sc);
purespice_keyDown(ctrl);
purespice_keyDown(alt );
purespice_keyDown(fn );
purespice_keyUp(ctrl); lgInput_keyUp(KEY_LEFTCTRL);
purespice_keyUp(alt ); lgInput_keyUp(KEY_LEFTALT);
purespice_keyUp(fn ); lgInput_keyUp(sc);
} }
static void bind_passthrough(int sc, void * opaque) static void bind_passthrough(int sc, void * opaque)
{ {
sc = linux_to_ps2[sc]; lgInput_keyDown(sc);
purespice_keyDown(sc); lgInput_keyUp (sc);
purespice_keyUp (sc);
} }
static void bind_toggleOverlay(int sc, void * opaque) 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) static void bind_toggleKey(int sc, void * opaque)
{ {
purespice_keyDown((uintptr_t) opaque); const int key = (uintptr_t) opaque;
purespice_keyUp((uintptr_t) opaque); lgInput_keyDown(key);
lgInput_keyUp(key);
} }
static void bind_setGuestRes(int sc, void * opaque) static void bind_setGuestRes(int sc, void * opaque)
@@ -183,25 +180,25 @@ static void bind_toggleMicDefault(int sc, void * opaque)
} }
#endif #endif
void keybind_spiceRegister(void) void keybind_inputRegister(void)
{ {
/* register the common keybinds for spice */ /* register the common input keybinds */
static bool firstTime = true; static bool firstTime = true;
if (firstTime) if (firstTime)
{ {
app_registerKeybind(KEY_I, bind_input, NULL, app_registerKeybind(KEY_I, bind_input, NULL,
"Spice keyboard & mouse toggle"); "Keyboard & mouse toggle");
app_registerKeybind(KEY_INSERT, bind_mouseSens, (void *) true, app_registerKeybind(KEY_INSERT, bind_mouseSens, (void *) true,
"Increase mouse sensitivity in capture mode"); "Increase mouse sensitivity in capture mode");
app_registerKeybind(KEY_DELETE, bind_mouseSens, (void *) false, app_registerKeybind(KEY_DELETE, bind_mouseSens, (void *) false,
"Decrease mouse sensitivity in capture mode"); "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"); "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"); "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"); "Send mute to the guest");
app_registerKeybind(KEY_LEFTMETA, bind_passthrough, NULL, app_registerKeybind(KEY_LEFTMETA, bind_passthrough, NULL,

View File

@@ -22,6 +22,6 @@
#define _H_LG_KEYBIND_ #define _H_LG_KEYBIND_
void keybind_commonRegister(void); void keybind_commonRegister(void);
void keybind_spiceRegister(void); void keybind_inputRegister(void);
#endif #endif

View File

@@ -69,6 +69,7 @@
#include "render_queue.h" #include "render_queue.h"
#include "evdev.h" #include "evdev.h"
#include "frame_scheduler.h" #include "frame_scheduler.h"
#include "input.h"
#ifdef ENABLE_TESTS #ifdef ENABLE_TESTS
#include "interface/test_capture.h" #include "interface/test_capture.h"
@@ -150,8 +151,8 @@ static void lgInit(void)
g_cursor.surfaceExit = false; g_cursor.surfaceExit = false;
g_cursor.guest.valid = false; g_cursor.guest.valid = false;
// if spice is not in use, hide the local cursor // if guest input is not in use, hide the local cursor
if ((!g_params.useSpiceInput && g_params.hideMouse) || !g_params.showCursorDot) if ((!lgInput_available() && g_params.hideMouse) || !g_params.showCursorDot)
g_state.ds->setPointer(LG_POINTER_NONE); g_state.ds->setPointer(LG_POINTER_NONE);
else else
g_state.ds->setPointer(LG_POINTER_SQUARE); g_state.ds->setPointer(LG_POINTER_SQUARE);
@@ -1022,6 +1023,12 @@ static int renderThread(void * unused)
lgTimerDestroy(tickTimer); lgTimerDestroy(tickTimer);
lgTimerDestroy(fpsTimer); lgTimerDestroy(fpsTimer);
if (g_state.transport &&
g_state.transportOps->sessionValid(g_state.transport))
lgInput_setTransport(NULL, NULL);
else
lgInput_dropTransport();
core_stopCursorThread(); core_stopCursorThread();
core_stopFrameThread(); core_stopFrameThread();
@@ -1058,7 +1065,7 @@ int main_cursorThread(void * unused)
g_cursor.redraw = false; g_cursor.redraw = false;
RENDERER(onMouseEvent, RENDERER(onMouseEvent,
g_cursor.guest.visible && 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.x, g_cursor.guest.y,
g_cursor.guest.hx, g_cursor.guest.hy); g_cursor.guest.hx, g_cursor.guest.hy);
if (!g_state.stopVideo) if (!g_state.stopVideo)
@@ -1067,6 +1074,8 @@ int main_cursorThread(void * unused)
continue; continue;
} }
if (status == LG_TRANSPORT_DISCONNECTED)
lgInput_dropTransport();
app_setState(status == LG_TRANSPORT_DISCONNECTED ? app_setState(status == LG_TRANSPORT_DISCONNECTED ?
APP_STATE_RESTART : APP_STATE_SHUTDOWN); APP_STATE_RESTART : APP_STATE_SHUTDOWN);
if (status != LG_TRANSPORT_DISCONNECTED) if (status != LG_TRANSPORT_DISCONNECTED)
@@ -1075,7 +1084,7 @@ int main_cursorThread(void * unused)
} }
const bool wasRendered = g_cursor.guest.visible && const bool wasRendered = g_cursor.guest.visible &&
(g_cursor.draw || !g_params.useSpiceInput); (g_cursor.draw || !lgInput_available());
bool hotspotChanged = false; bool hotspotChanged = false;
if (pointer.flags & LG_TRANSPORT_POINTER_VISIBLE_VALID) if (pointer.flags & LG_TRANSPORT_POINTER_VISIBLE_VALID)
@@ -1156,12 +1165,12 @@ int main_cursorThread(void * unused)
app_updateMouseState(); app_updateMouseState();
g_cursor.redraw = false; g_cursor.redraw = false;
RENDERER(onMouseEvent, 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.x, g_cursor.guest.y,
g_cursor.guest.hx, g_cursor.guest.hy); g_cursor.guest.hx, g_cursor.guest.hy);
const bool isRendered = g_cursor.guest.visible && const bool isRendered = g_cursor.guest.visible &&
(g_cursor.draw || !g_params.useSpiceInput); (g_cursor.draw || !lgInput_available());
const bool contentChanged = const bool contentChanged =
(pointer.flags & (LG_TRANSPORT_POINTER_SHAPE | (pointer.flags & (LG_TRANSPORT_POINTER_SHAPE |
LG_TRANSPORT_POINTER_COLOR_TRANSFORM)) || whiteLevelChanged; LG_TRANSPORT_POINTER_COLOR_TRANSFORM)) || whiteLevelChanged;
@@ -1211,7 +1220,10 @@ int main_frameThread(void * unused)
continue; continue;
} }
if (status == LG_TRANSPORT_DISCONNECTED) if (status == LG_TRANSPORT_DISCONNECTED)
{
lgInput_dropTransport();
app_setState(APP_STATE_RESTART); app_setState(APP_STATE_RESTART);
}
else if (status == LG_TRANSPORT_END) else if (status == LG_TRANSPORT_END)
app_setState(APP_STATE_SHUTDOWN); app_setState(APP_STATE_SHUTDOWN);
else else
@@ -1484,9 +1496,10 @@ static void checkUUID(void)
app_msgBox( app_msgBox(
"SPICE Configuration Error", "SPICE Configuration Error",
"You have connected SPICE to the wrong guest.\n" "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; g_params.useSpiceInput = false;
lgInput_setFallback(NULL, NULL);
atomic_store_explicit(&g_state.spiceClose, true, memory_order_release); atomic_store_explicit(&g_state.spiceClose, true, memory_order_release);
purespice_disconnect(); purespice_disconnect();
} }
@@ -1494,6 +1507,9 @@ static void checkUUID(void)
void spiceReady(void) void spiceReady(void)
{ {
atomic_store_explicit(&g_state.spiceReady, true, memory_order_release); 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, if (atomic_load_explicit(&g_state.spiceDisplayRequested,
memory_order_acquire)) memory_order_acquire))
app_useSpiceDisplay(true); app_useSpiceDisplay(true);
@@ -1525,7 +1541,7 @@ void spiceReady(void)
DEBUG_WARN("Failed to obtain SPICE server information"); DEBUG_WARN("Failed to obtain SPICE server information");
if (g_params.useSpiceInput) if (g_params.useSpiceInput)
keybind_spiceRegister(); keybind_inputRegister();
lgSignalEvent(e_spice); lgSignalEvent(e_spice);
} }
@@ -1759,27 +1775,12 @@ int spiceThread(void * arg)
} }
} }
// send key up events for any pressed keys lgInput_setFallback(NULL, NULL);
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);
}
}
purespice_disconnect(); purespice_disconnect();
end: end:
lgInput_setFallback(NULL, NULL);
audio_free(); audio_free();
// if the connection was disconnected intentionally we don't want to shutdown // 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); LG_LOCK_INIT(l_cursorRepaint.lock);
frameTimingInit(); frameTimingInit();
lgInput_init();
#ifdef ENABLE_TESTS #ifdef ENABLE_TESTS
memset(&l_testCapture, 0, sizeof(l_testCapture)); memset(&l_testCapture, 0, sizeof(l_testCapture));
@@ -2355,8 +2357,13 @@ restart:
g_state.transportFeatures = session.features; g_state.transportFeatures = session.features;
frameScheduler_start(session.features); frameScheduler_start(session.features);
if (g_state.spiceReady && g_params.useSpiceInput) void * inputOpaque = NULL;
keybind_spiceRegister(); 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(); checkUUID();
DEBUG_INFO("Starting session"); DEBUG_INFO("Starting session");
atomic_store_explicit( atomic_store_explicit(
@@ -2371,6 +2378,7 @@ restart:
{ {
if (unlikely(!g_state.transportOps->sessionValid(g_state.transport))) if (unlikely(!g_state.transportOps->sessionValid(g_state.transport)))
{ {
lgInput_dropTransport();
atomic_store_explicit( atomic_store_explicit(
&g_state.lgHostConnected, false, memory_order_release); &g_state.lgHostConnected, false, memory_order_release);
DEBUG_INFO("Waiting for the host to restart..."); DEBUG_INFO("Waiting for the host to restart...");
@@ -2391,6 +2399,7 @@ restart:
core_stopFrameThread(); core_stopFrameThread();
core_stopCursorThread(); core_stopCursorThread();
lgInput_dropTransport();
g_state.transportOps->disconnect(g_state.transport); g_state.transportOps->disconnect(g_state.transport);
app_setState(APP_STATE_RUNNING); app_setState(APP_STATE_RUNNING);
@@ -2435,7 +2444,12 @@ static void lg_shutdown(void)
LG_LOCK_FREE(l_cursorRepaint.lock); LG_LOCK_FREE(l_cursorRepaint.lock);
if (g_state.transportOps) if (g_state.transportOps)
{
lgInput_dropTransport();
g_state.transportOps->destroy(&g_state.transport); g_state.transportOps->destroy(&g_state.transport);
}
lgInput_free();
if (g_state.frameEvent) if (g_state.frameEvent)
{ {

View File

@@ -99,8 +99,6 @@ struct AppState
int escapeAction; int escapeAction;
bool escapeHelp; bool escapeHelp;
struct ll * bindings; struct ll * bindings;
atomic_bool keyDown[KEY_MAX];
bool haveSrcSize; bool haveSrcSize;
struct Point windowPos; struct Point windowPos;
int windowW, windowH; int windowW, windowH;

View File

@@ -241,7 +241,12 @@ void app_mouseTrace(const char * file, unsigned int line,
(void)format; (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); push(EV_MOTION, x, y, false);
return true; return true;

View File

@@ -1327,6 +1327,13 @@ static LG_TransportStatus lgmp_controlStatus(LG_Transport * this,
LG_TRANSPORT_OK : LG_TRANSPORT_UNAVAILABLE; 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 = const LG_TransportOps LGT_LGMP =
{ {
.name = "lgmp", .name = "lgmp",
@@ -1337,6 +1344,7 @@ const LG_TransportOps LGT_LGMP =
.disconnect = lgmp_disconnect, .disconnect = lgmp_disconnect,
.sessionValid = lgmp_sessionValid, .sessionValid = lgmp_sessionValid,
.supportsDMA = lgmp_supportsDMA, .supportsDMA = lgmp_supportsDMA,
.getInputOps = lgmp_getInputOps,
.attachRenderer = lgmp_attachRenderer, .attachRenderer = lgmp_attachRenderer,
.detachRenderer = lgmp_detachRenderer, .detachRenderer = lgmp_detachRenderer,
.nextFrame = lgmp_nextFrame, .nextFrame = lgmp_nextFrame,

View File

@@ -727,6 +727,13 @@ static LG_TransportStatus test_controlStatus(LG_Transport * this,
LG_TRANSPORT_DISCONNECTED; LG_TRANSPORT_DISCONNECTED;
} }
static const LG_InputOps * test_getInputOps(LG_Transport * this,
void ** opaque)
{
*opaque = NULL;
return NULL;
}
const LG_TransportOps LGT_Test = const LG_TransportOps LGT_Test =
{ {
.name = "test", .name = "test",
@@ -737,6 +744,7 @@ const LG_TransportOps LGT_Test =
.disconnect = test_disconnect, .disconnect = test_disconnect,
.sessionValid = test_sessionValid, .sessionValid = test_sessionValid,
.supportsDMA = test_supportsDMA, .supportsDMA = test_supportsDMA,
.getInputOps = test_getInputOps,
.attachRenderer = test_attachRenderer, .attachRenderer = test_attachRenderer,
.detachRenderer = test_detachRenderer, .detachRenderer = test_detachRenderer,
.nextFrame = test_nextFrame, .nextFrame = test_nextFrame,

View File

@@ -34,6 +34,50 @@ typedef atomic_flag LG_Lock;
atomic_flag_clear_explicit(&(x), memory_order_release); atomic_flag_clear_explicit(&(x), memory_order_release);
#define LG_LOCK_FREE(x) #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_INC(x) atomic_fetch_add((x), 1)
#define INTERLOCKED_DEC(x) atomic_fetch_sub((x), 1) #define INTERLOCKED_DEC(x) atomic_fetch_sub((x), 1)