mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 07:01:30 +00:00
[client] recovery: add force keybind
Some checks failed
build / client (Debug, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client (Debug, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client (Debug, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client (Debug, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / client (Release, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client (Release, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client (Release, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client (Release, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / module (push) Has been cancelled
build / host-linux (push) Has been cancelled
build / host-windows-cross (push) Has been cancelled
build / host-windows-native (push) Has been cancelled
build / idd (push) Has been cancelled
build / obs (clang) (push) Has been cancelled
build / obs (gcc) (push) Has been cancelled
build / docs (push) Has been cancelled
Some checks failed
build / client (Debug, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client (Debug, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client (Debug, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client (Debug, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / client (Release, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client (Release, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client (Release, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client (Release, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / module (push) Has been cancelled
build / host-linux (push) Has been cancelled
build / host-windows-cross (push) Has been cancelled
build / host-windows-native (push) Has been cancelled
build / idd (push) Has been cancelled
build / obs (clang) (push) Has been cancelled
build / obs (gcc) (push) Has been cancelled
build / docs (push) Has been cancelled
Add a configured escape key plus Shift+R command that requests display recovery from the LGMP transport and switches video to SPICE. Track the request on the main thread, report its asynchronous result, and return its producer instance with the request serial for exact correlation. Preserve normal escape+R rotation behavior, including custom Shift escape keys.
This commit is contained in:
@@ -485,7 +485,7 @@ typedef struct LG_TransportOps
|
||||
LG_TransportStatus (*getRecoveryInfo)(LG_Transport * transport,
|
||||
LG_RecoveryInfo * info);
|
||||
LG_TransportStatus (*requestRecovery)(LG_Transport * transport,
|
||||
LG_RecoveryRequest request, uint32_t * serial);
|
||||
LG_RecoveryRequest request, uint64_t * instance, uint32_t * serial);
|
||||
|
||||
LG_TransportStatus (*sendControl)(LG_Transport * transport,
|
||||
const LG_TransportControl * control, LG_TransportControlToken * token);
|
||||
|
||||
@@ -47,6 +47,12 @@
|
||||
|
||||
#define SHADER_MOUSE_VALID (UINT32_C(1) << 31)
|
||||
|
||||
enum
|
||||
{
|
||||
KEY_MODIFIER_LEFT_SHIFT = 0x1,
|
||||
KEY_MODIFIER_RIGHT_SHIFT = 0x2,
|
||||
};
|
||||
|
||||
#define MTRACE(fmt, ...) \
|
||||
do \
|
||||
{ \
|
||||
@@ -273,6 +279,9 @@ void app_handleFocusEvent(bool focused)
|
||||
|
||||
if (!focused)
|
||||
{
|
||||
atomic_store_explicit(
|
||||
&g_state.keyModifiers, 0, memory_order_release);
|
||||
g_state.escapeActive = false;
|
||||
core_setGrabQuiet(false);
|
||||
core_setCursorInView(false);
|
||||
}
|
||||
@@ -289,8 +298,6 @@ void app_handleFocusEvent(bool focused)
|
||||
if (g_params.releaseKeysOnFocusLoss)
|
||||
lgInput_releaseKeys();
|
||||
|
||||
g_state.escapeActive = false;
|
||||
|
||||
if (!g_params.showCursorDot)
|
||||
g_state.ds->setPointer(LG_POINTER_NONE);
|
||||
|
||||
@@ -418,10 +425,20 @@ void app_handleWheelMotion(double motion)
|
||||
|
||||
void app_handleKeyPressInternal(int sc)
|
||||
{
|
||||
if (sc == KEY_LEFTSHIFT)
|
||||
atomic_fetch_or_explicit(&g_state.keyModifiers,
|
||||
KEY_MODIFIER_LEFT_SHIFT, memory_order_acq_rel);
|
||||
else if (sc == KEY_RIGHTSHIFT)
|
||||
atomic_fetch_or_explicit(&g_state.keyModifiers,
|
||||
KEY_MODIFIER_RIGHT_SHIFT, memory_order_acq_rel);
|
||||
|
||||
if (!app_isOverlayMode() || !g_state.io->WantCaptureKeyboard)
|
||||
{
|
||||
if (sc == g_params.escapeKey && !g_state.escapeActive)
|
||||
{
|
||||
memset(g_state.escapeKeys, 0, sizeof(g_state.escapeKeys));
|
||||
memset(g_state.escapeShiftKeys, 0,
|
||||
sizeof(g_state.escapeShiftKeys));
|
||||
g_state.escapeActive = true;
|
||||
g_state.escapeTime = microtime();
|
||||
g_state.escapeAction = -1;
|
||||
@@ -431,6 +448,7 @@ void app_handleKeyPressInternal(int sc)
|
||||
|
||||
if (g_state.escapeActive)
|
||||
{
|
||||
const bool repeat = g_state.escapeKeys[sc];
|
||||
g_state.escapeAction = sc;
|
||||
g_state.escapeKeys[sc] = true;
|
||||
KeybindHandle handle;
|
||||
@@ -438,7 +456,24 @@ void app_handleKeyPressInternal(int sc)
|
||||
{
|
||||
if (handle->sc == sc)
|
||||
{
|
||||
handle->callback(sc, handle->opaque);
|
||||
unsigned modifiers = atomic_load_explicit(
|
||||
&g_state.keyModifiers, memory_order_acquire);
|
||||
if (g_params.escapeKey == KEY_LEFTSHIFT)
|
||||
modifiers &= ~(unsigned)KEY_MODIFIER_LEFT_SHIFT;
|
||||
else if (g_params.escapeKey == KEY_RIGHTSHIFT)
|
||||
modifiers &= ~(unsigned)KEY_MODIFIER_RIGHT_SHIFT;
|
||||
const bool shift = modifiers != 0;
|
||||
if (!repeat)
|
||||
g_state.escapeShiftKeys[sc] =
|
||||
shift && handle->shiftCallback;
|
||||
|
||||
if (g_state.escapeShiftKeys[sc])
|
||||
{
|
||||
if (!repeat)
|
||||
handle->shiftCallback(sc, handle->shiftOpaque);
|
||||
}
|
||||
else
|
||||
handle->callback(sc, handle->opaque);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -472,6 +507,13 @@ void app_handleKeyPressInternal(int sc)
|
||||
|
||||
void app_handleKeyReleaseInternal(int sc)
|
||||
{
|
||||
if (sc == KEY_LEFTSHIFT)
|
||||
atomic_fetch_and_explicit(&g_state.keyModifiers,
|
||||
~(unsigned)KEY_MODIFIER_LEFT_SHIFT, memory_order_acq_rel);
|
||||
else if (sc == KEY_RIGHTSHIFT)
|
||||
atomic_fetch_and_explicit(&g_state.keyModifiers,
|
||||
~(unsigned)KEY_MODIFIER_RIGHT_SHIFT, memory_order_acq_rel);
|
||||
|
||||
if (g_state.escapeActive && sc == g_params.escapeKey)
|
||||
{
|
||||
if (g_state.escapeAction == -1)
|
||||
@@ -486,7 +528,8 @@ void app_handleKeyReleaseInternal(int sc)
|
||||
|
||||
if (g_state.escapeKeys[sc])
|
||||
{
|
||||
g_state.escapeKeys[sc] = false;
|
||||
g_state.escapeKeys[sc] = false;
|
||||
g_state.escapeShiftKeys[sc] = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -814,10 +857,13 @@ KeybindHandle app_registerKeybind(int sc, KeybindFn callback, void * opaque,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
handle->sc = sc;
|
||||
handle->callback = callback;
|
||||
handle->description = description;
|
||||
handle->opaque = opaque;
|
||||
handle->sc = sc;
|
||||
handle->callback = callback;
|
||||
handle->shiftCallback = NULL;
|
||||
handle->description = description;
|
||||
handle->shiftDescription = NULL;
|
||||
handle->opaque = opaque;
|
||||
handle->shiftOpaque = NULL;
|
||||
|
||||
ll_push(g_state.bindings, handle);
|
||||
return handle;
|
||||
|
||||
@@ -50,6 +50,11 @@ static void bind_rotate(int sc, void * opaque)
|
||||
core_updatePositionInfo();
|
||||
}
|
||||
|
||||
static void bind_forceRecovery(int sc, void * opaque)
|
||||
{
|
||||
atomic_store_explicit(&g_state.forceRecovery, true, memory_order_release);
|
||||
}
|
||||
|
||||
static void bind_input(int sc, void * opaque)
|
||||
{
|
||||
g_state.ignoreInput = !g_state.ignoreInput;
|
||||
@@ -150,8 +155,13 @@ void keybind_commonRegister(void)
|
||||
"Full screen toggle");
|
||||
app_registerKeybind(KEY_V, bind_video , NULL,
|
||||
"Video stream toggle");
|
||||
app_registerKeybind(KEY_R, bind_rotate , NULL,
|
||||
KeybindHandle rotate = app_registerKeybind(KEY_R, bind_rotate, NULL,
|
||||
"Rotate the output clockwise by 90° increments");
|
||||
if (rotate)
|
||||
{
|
||||
rotate->shiftCallback = bind_forceRecovery;
|
||||
rotate->shiftDescription = "Force guest display recovery";
|
||||
}
|
||||
app_registerKeybind(KEY_Q, bind_quit , NULL,
|
||||
"Quit");
|
||||
app_registerKeybind(KEY_O, bind_toggleOverlay, NULL,
|
||||
|
||||
@@ -2803,6 +2803,13 @@ struct RecoveryPrompt
|
||||
bool retryDeclined;
|
||||
};
|
||||
|
||||
struct ForcedRecovery
|
||||
{
|
||||
uint64_t instance;
|
||||
uint32_t serial;
|
||||
bool pending;
|
||||
};
|
||||
|
||||
static bool recoveryGetInfo(LG_RecoveryInfo * info)
|
||||
{
|
||||
if (!g_state.transport.ops->getRecoveryInfo)
|
||||
@@ -3043,6 +3050,152 @@ static bool recoverySerialNewer(uint32_t serial, uint32_t reference)
|
||||
return difference && difference < 0x80000000U;
|
||||
}
|
||||
|
||||
static bool recoveryRequestNewer(const LG_RecoveryInfo * info)
|
||||
{
|
||||
return info->requestSerial != 0 &&
|
||||
(info->ackSerial == 0 ||
|
||||
recoverySerialNewer(info->requestSerial, info->ackSerial));
|
||||
}
|
||||
|
||||
static void recoveryHandleForced(struct ForcedRecovery * recovery)
|
||||
{
|
||||
const bool triggered = atomic_exchange_explicit(
|
||||
&g_state.forceRecovery, false, memory_order_acq_rel);
|
||||
if (!triggered && !recovery->pending)
|
||||
return;
|
||||
|
||||
LG_RecoveryInfo info = { 0 };
|
||||
if (!recoveryGetInfo(&info) ||
|
||||
!(info.capabilities & LG_RECOVERY_CAP_DISPLAY))
|
||||
{
|
||||
if (triggered)
|
||||
app_alert(LG_ALERT_WARNING,
|
||||
"Guest display recovery is unavailable");
|
||||
return;
|
||||
}
|
||||
|
||||
if (info.uuidValid)
|
||||
lgTransportFallback_setPrimaryUUID(g_state.fallback, info.uuid);
|
||||
else
|
||||
lgTransportFallback_clearPrimaryUUID(g_state.fallback);
|
||||
|
||||
const bool requestNewer = recoveryRequestNewer(&info);
|
||||
const LG_RecoveryRequest currentRequest = requestNewer ?
|
||||
info.request : info.ackRequest;
|
||||
const uint32_t currentSerial = requestNewer ?
|
||||
info.requestSerial : info.ackSerial;
|
||||
|
||||
if (recovery->pending)
|
||||
{
|
||||
const bool instanceChanged = recovery->instance != info.instance;
|
||||
const bool currentRelevant = currentSerial &&
|
||||
(instanceChanged || currentSerial == recovery->serial ||
|
||||
recoverySerialNewer(currentSerial, recovery->serial));
|
||||
if (!currentRelevant && instanceChanged)
|
||||
{
|
||||
recovery->pending = false;
|
||||
app_alert(LG_ALERT_WARNING,
|
||||
"Guest display recovery was interrupted");
|
||||
if (!triggered)
|
||||
return;
|
||||
}
|
||||
else if (currentRelevant &&
|
||||
currentRequest == LG_RECOVERY_REQ_RECOVERY)
|
||||
{
|
||||
recovery->instance = info.instance;
|
||||
recovery->serial = currentSerial;
|
||||
if (!requestNewer && info.state == LG_RECOVERY_STATE_ACTIVE)
|
||||
{
|
||||
recovery->pending = false;
|
||||
fallbackRequestVideo();
|
||||
app_alert(LG_ALERT_SUCCESS,
|
||||
"Guest display recovery is active");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!requestNewer && info.state == LG_RECOVERY_STATE_FAILED)
|
||||
{
|
||||
recovery->pending = false;
|
||||
app_alert(LG_ALERT_WARNING, "Recovery failed: %s",
|
||||
recoveryErrorText(info.error));
|
||||
if (!triggered)
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
fallbackRequestVideo();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (currentRelevant)
|
||||
{
|
||||
recovery->pending = false;
|
||||
app_alert(LG_ALERT_WARNING,
|
||||
"Guest display recovery was superseded");
|
||||
if (!triggered)
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
fallbackRequestVideo();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!triggered)
|
||||
return;
|
||||
|
||||
const bool pendingRecovery = requestNewer &&
|
||||
info.request == LG_RECOVERY_REQ_RECOVERY;
|
||||
const bool switchingRecovery = !requestNewer && info.ackSerial &&
|
||||
info.ackRequest == LG_RECOVERY_REQ_RECOVERY &&
|
||||
info.state == LG_RECOVERY_STATE_SWITCHING;
|
||||
const bool activeRecovery = !requestNewer && info.ackSerial &&
|
||||
info.ackRequest == LG_RECOVERY_REQ_RECOVERY &&
|
||||
info.state == LG_RECOVERY_STATE_ACTIVE;
|
||||
if (activeRecovery)
|
||||
{
|
||||
fallbackRequestVideo();
|
||||
app_alert(LG_ALERT_SUCCESS,
|
||||
"Guest display recovery is already active");
|
||||
return;
|
||||
}
|
||||
|
||||
if (pendingRecovery || switchingRecovery)
|
||||
{
|
||||
recovery->instance = info.instance;
|
||||
recovery->serial = currentSerial;
|
||||
recovery->pending = true;
|
||||
fallbackRequestVideo();
|
||||
app_alert(LG_ALERT_INFO,
|
||||
"Guest display recovery is already pending");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!g_state.transport.ops->requestRecovery)
|
||||
{
|
||||
app_alert(LG_ALERT_WARNING,
|
||||
"Guest display recovery is unavailable");
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t serial = 0;
|
||||
const LG_TransportStatus status =
|
||||
g_state.transport.ops->requestRecovery(g_state.transport.handle,
|
||||
LG_RECOVERY_REQ_RECOVERY, &recovery->instance, &serial);
|
||||
if (status != LG_TRANSPORT_OK)
|
||||
{
|
||||
app_alert(LG_ALERT_WARNING,
|
||||
"Guest display recovery request failed (%d)", status);
|
||||
return;
|
||||
}
|
||||
|
||||
recovery->serial = serial;
|
||||
recovery->pending = true;
|
||||
fallbackRequestVideo();
|
||||
app_alert(LG_ALERT_INFO, "Guest display recovery requested");
|
||||
}
|
||||
|
||||
static void recoveryHandleMismatch(struct RecoveryPrompt * prompt,
|
||||
const LG_VersionMismatch * mismatch)
|
||||
{
|
||||
@@ -3114,9 +3267,7 @@ static void recoveryHandleMismatch(struct RecoveryPrompt * prompt,
|
||||
else
|
||||
lgTransportFallback_clearPrimaryUUID(g_state.fallback);
|
||||
|
||||
const bool requestNewer = info.requestSerial != 0 &&
|
||||
(info.ackSerial == 0 ||
|
||||
recoverySerialNewer(info.requestSerial, info.ackSerial));
|
||||
const bool requestNewer = recoveryRequestNewer(&info);
|
||||
const LG_RecoveryRequest globalRequest = requestNewer ?
|
||||
info.request : info.ackRequest;
|
||||
const uint32_t globalSerial = requestNewer ?
|
||||
@@ -3218,7 +3369,8 @@ static void recoveryHandleMismatch(struct RecoveryPrompt * prompt,
|
||||
const LG_TransportStatus status =
|
||||
g_state.transport.ops->requestRecovery ?
|
||||
g_state.transport.ops->requestRecovery(g_state.transport.handle,
|
||||
LG_RECOVERY_REQ_RECOVERY, &prompt->serial) :
|
||||
LG_RECOVERY_REQ_RECOVERY, &prompt->instance,
|
||||
&prompt->serial) :
|
||||
LG_TRANSPORT_UNAVAILABLE;
|
||||
prompt->requested = status == LG_TRANSPORT_OK;
|
||||
prompt->owned = prompt->requested;
|
||||
@@ -3346,6 +3498,8 @@ static int lg_run(void)
|
||||
frameTimingInit();
|
||||
lgInput_init();
|
||||
lgAudio_init();
|
||||
atomic_init(&g_state.forceRecovery, false);
|
||||
atomic_init(&g_state.keyModifiers, 0);
|
||||
if (!clipboard_init())
|
||||
return -1;
|
||||
|
||||
@@ -3688,6 +3842,7 @@ static int lg_run(void)
|
||||
MsgBoxHandle msgs[10];
|
||||
int msgsCount;
|
||||
struct RecoveryPrompt recoveryPrompt = { 0 };
|
||||
struct ForcedRecovery forcedRecovery = { 0 };
|
||||
atomic_init(&recoveryPrompt.choice, RECOVERY_PENDING);
|
||||
atomic_init(&recoveryPrompt.handle, 0);
|
||||
atomic_init(&recoveryPrompt.message, 0);
|
||||
@@ -3704,6 +3859,7 @@ restart:
|
||||
while(app_getState() == APP_STATE_RUNNING)
|
||||
{
|
||||
fallbackHandleEvents();
|
||||
recoveryHandleForced(&forcedRecovery);
|
||||
|
||||
if (initialFallbackEnable && microtime() > initialFallbackEnable)
|
||||
{
|
||||
@@ -3795,7 +3951,7 @@ restart:
|
||||
|
||||
recoveryClose(&recoveryPrompt);
|
||||
LG_RecoveryInfo recoveryInfo = { 0 };
|
||||
if (recoveryGetInfo(&recoveryInfo) &&
|
||||
if (!forcedRecovery.pending && recoveryGetInfo(&recoveryInfo) &&
|
||||
(recoveryInfo.state == LG_RECOVERY_STATE_ACTIVE ||
|
||||
recoveryInfo.state == LG_RECOVERY_STATE_SWITCHING ||
|
||||
recoveryInfo.request == LG_RECOVERY_REQ_RECOVERY ||
|
||||
@@ -3804,7 +3960,7 @@ restart:
|
||||
{
|
||||
const LG_TransportStatus status =
|
||||
g_state.transport.ops->requestRecovery(g_state.transport.handle,
|
||||
LG_RECOVERY_REQ_NORMAL, NULL);
|
||||
LG_RECOVERY_REQ_NORMAL, NULL, NULL);
|
||||
if (status != LG_TRANSPORT_OK)
|
||||
DEBUG_WARN("Failed to leave recovery mode: %d", status);
|
||||
}
|
||||
@@ -3931,6 +4087,7 @@ restart:
|
||||
while(likely(app_getState() == APP_STATE_RUNNING))
|
||||
{
|
||||
fallbackHandleEvents();
|
||||
recoveryHandleForced(&forcedRecovery);
|
||||
if (unlikely(!g_state.transport.ops->sessionValid(
|
||||
g_state.transport.handle)))
|
||||
{
|
||||
|
||||
@@ -116,6 +116,7 @@ struct AppState
|
||||
|
||||
atomic_bool fallbackEndpointMismatch;
|
||||
atomic_uint transportLost;
|
||||
atomic_bool forceRecovery;
|
||||
|
||||
uint8_t guestUUID[16];
|
||||
bool guestUUIDValid;
|
||||
@@ -130,6 +131,8 @@ struct AppState
|
||||
uint64_t escapeTime;
|
||||
int escapeAction;
|
||||
bool escapeKeys[KEY_MAX];
|
||||
bool escapeShiftKeys[KEY_MAX];
|
||||
atomic_uint keyModifiers;
|
||||
bool escapeHelp;
|
||||
struct ll * bindings;
|
||||
bool haveSrcSize;
|
||||
@@ -265,8 +268,11 @@ struct KeybindHandle
|
||||
{
|
||||
int sc;
|
||||
KeybindFn callback;
|
||||
KeybindFn shiftCallback;
|
||||
const char * description;
|
||||
const char * shiftDescription;
|
||||
void * opaque;
|
||||
void * shiftOpaque;
|
||||
};
|
||||
|
||||
enum WarpState
|
||||
|
||||
@@ -82,6 +82,14 @@ static int help_render(void * udata, bool interactive, struct Rect * windowRects
|
||||
igText("%s+%s", escapeName, keyName);
|
||||
igTableNextColumn();
|
||||
igTextUnformatted(handle->description, NULL);
|
||||
|
||||
if (handle->shiftCallback && handle->shiftDescription)
|
||||
{
|
||||
igTableNextColumn();
|
||||
igText("%s+Shift+%s", escapeName, keyName);
|
||||
igTableNextColumn();
|
||||
igTextUnformatted(handle->shiftDescription, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
igEndTable();
|
||||
|
||||
@@ -1420,7 +1420,7 @@ static LG_TransportStatus lgmp_getRecoveryInfo(LG_Transport * this,
|
||||
}
|
||||
|
||||
static LG_TransportStatus lgmp_requestRecovery(LG_Transport * this,
|
||||
LG_RecoveryRequest request, uint32_t * serial)
|
||||
LG_RecoveryRequest request, uint64_t * instance, uint32_t * serial)
|
||||
{
|
||||
uint32_t wireRequest;
|
||||
switch (request)
|
||||
@@ -1505,6 +1505,8 @@ static LG_TransportStatus lgmp_requestRecovery(LG_Transport * this,
|
||||
}
|
||||
if (serial)
|
||||
*serial = ticket;
|
||||
if (instance)
|
||||
*instance = header.session;
|
||||
|
||||
LG_UNLOCK(this->recoveryLock);
|
||||
return LG_TRANSPORT_OK;
|
||||
|
||||
Reference in New Issue
Block a user