diff --git a/client/include/app.h b/client/include/app.h index 9fbfc18d..844421f2 100644 --- a/client/include/app.h +++ b/client/include/app.h @@ -30,6 +30,13 @@ #include "interface/displayserver.h" #include "interface/overlay.h" +enum RunState +{ + APP_STATE_RUNNING, + APP_STATE_RESTART, + APP_STATE_SHUTDOWN +}; + typedef enum LG_MsgAlert { LG_ALERT_INFO , @@ -53,6 +60,9 @@ typedef struct LG_MouseState LG_MouseState; bool app_isRunning(void); +enum RunState app_getState(void); +void app_setState(enum RunState state); +bool app_transitionState(enum RunState from, enum RunState to); bool app_inputEnabled(void); bool app_isCaptureMode(void); bool app_isCaptureOnlyMode(void); diff --git a/client/src/app.c b/client/src/app.c index 9ac55a7d..5c076a26 100644 --- a/client/src/app.c +++ b/client/src/app.c @@ -57,6 +57,37 @@ } \ while (0) +extern _Atomic(enum RunState) p_appState; + +enum RunState app_getState(void) +{ + return atomic_load_explicit(&p_appState, memory_order_acquire); +} + +void app_setState(enum RunState state) +{ + if (state == APP_STATE_SHUTDOWN) + { + atomic_store_explicit(&p_appState, state, memory_order_release); + return; + } + + enum RunState current = atomic_load_explicit( + &p_appState, memory_order_acquire); + while (current != APP_STATE_SHUTDOWN && + !atomic_compare_exchange_weak_explicit(&p_appState, ¤t, state, + memory_order_acq_rel, memory_order_acquire)); +} + +bool app_transitionState(enum RunState from, enum RunState to) +{ + if (from == APP_STATE_SHUTDOWN) + return false; + + return atomic_compare_exchange_strong_explicit( + &p_appState, &from, to, memory_order_acq_rel, memory_order_acquire); +} + bool app_isRunning(void) { const enum RunState state = app_getState(); diff --git a/client/src/main.c b/client/src/main.c index e3748b8d..586101ba 100644 --- a/client/src/main.c +++ b/client/src/main.c @@ -81,12 +81,18 @@ _Static_assert((int)LG_CAPTURE_RGBA32F == (int)LG_TEST_CAPTURE_RGBA32F, "capture format mismatch"); #endif +#define TRANSPORT_LOST_PRIMARY (1U << 0) +#define TRANSPORT_LOST_FALLBACK (1U << 1) +#define TRANSPORT_LOST_ALL \ + (TRANSPORT_LOST_PRIMARY | TRANSPORT_LOST_FALLBACK) + // forwards static int renderThread(void * unused); static RenderQueueSource renderQueueSource(LG_VideoSource source); static bool videoSourceInvalidate(LG_VideoSource source); static void videoSourceShowSplashIfNeeded(void); static bool fallbackRequestVideo(void); +static void primaryLost(void); static LGEvent *e_startup = NULL; static LGEvent *e_cursorRepaint = NULL; @@ -1148,11 +1154,13 @@ int main_cursorThread(void * unused) lgInput_dropTransport(); lgAudio_dropTransport(); lgClipboard_dropTransport(); + primaryLost(); } - app_setState(status == LG_TRANSPORT_DISCONNECTED ? - APP_STATE_RESTART : APP_STATE_SHUTDOWN); - if (status != LG_TRANSPORT_DISCONNECTED) + else + { DEBUG_ERROR("Pointer transport failed with status %d", status); + app_setState(APP_STATE_SHUTDOWN); + } break; } @@ -1334,7 +1342,7 @@ int main_frameThread(void * unused) lgInput_dropTransport(); lgAudio_dropTransport(); lgClipboard_dropTransport(); - app_setState(APP_STATE_RESTART); + primaryLost(); } else if (status == LG_TRANSPORT_END) app_setState(APP_STATE_SHUTDOWN); @@ -2196,6 +2204,8 @@ static void fallbackConnected(void * opaque, const LG_TransportSession * session) { (void)opaque; + atomic_fetch_and_explicit(&g_state.transportLost, + ~TRANSPORT_LOST_FALLBACK, memory_order_acq_rel); if (app_getState() == APP_STATE_SHUTDOWN) return; @@ -2219,6 +2229,19 @@ static void fallbackDisconnected(void * opaque) videoSourceShowSplashIfNeeded(); } +static void fallbackLost(void * opaque) +{ + (void)opaque; + const unsigned int lost = atomic_fetch_or_explicit(&g_state.transportLost, + TRANSPORT_LOST_FALLBACK, memory_order_acq_rel) | + TRANSPORT_LOST_FALLBACK; + if ((lost & TRANSPORT_LOST_ALL) == TRANSPORT_LOST_ALL) + { + DEBUG_INFO("Primary and fallback transport sessions disconnected"); + app_setState(APP_STATE_SHUTDOWN); + } +} + static void fallbackUUIDMismatch(void * opaque, const uint8_t primary[16], const uint8_t fallback[16]) { @@ -2233,10 +2256,40 @@ static void fallbackUUIDMismatch(void * opaque, const uint8_t primary[16], static const LG_TransportFallbackEventOps fallbackEvents = { .connected = fallbackConnected, + .lost = fallbackLost, .disconnected = fallbackDisconnected, .uuidMismatch = fallbackUUIDMismatch, }; +static void primaryLost(void) +{ + atomic_store_explicit( + &g_state.lgHostConnected, false, memory_order_release); + unsigned int lost = atomic_load_explicit( + &g_state.transportLost, memory_order_acquire); + for (;;) + { + unsigned int next = lost | TRANSPORT_LOST_PRIMARY; + if (lgTransportFallback_ready(g_state.fallback)) + next &= ~TRANSPORT_LOST_FALLBACK; + if (atomic_compare_exchange_weak_explicit(&g_state.transportLost, + &lost, next, memory_order_acq_rel, memory_order_acquire)) + { + lost = next; + break; + } + } + if ((lost & TRANSPORT_LOST_ALL) == TRANSPORT_LOST_ALL) + { + DEBUG_INFO("Primary and fallback transport sessions disconnected"); + app_setState(APP_STATE_SHUTDOWN); + return; + } + + DEBUG_INFO("Waiting for the host to restart..."); + app_setState(APP_STATE_RESTART); +} + static bool fallbackStart(const uint8_t primaryUUID[16]) { if (!option_get_bool("spice", "enable") || @@ -3268,6 +3321,8 @@ restart: if (probe.status == LG_TRANSPORT_OK) { session = probe.session; + atomic_fetch_and_explicit(&g_state.transportLost, + ~TRANSPORT_LOST_PRIMARY, memory_order_acq_rel); initialFallbackEnable = 0; break; } @@ -3454,10 +3509,7 @@ restart: lgInput_dropTransport(); lgAudio_dropTransport(); lgClipboard_dropTransport(); - atomic_store_explicit( - &g_state.lgHostConnected, false, memory_order_release); - DEBUG_INFO("Waiting for the host to restart..."); - app_setState(APP_STATE_RESTART); + primaryLost(); break; } lgMessage_process(); @@ -3491,9 +3543,11 @@ restart: lgClipboard_dropTransport(); g_state.transport.ops->disconnect(g_state.transport.handle); - app_setState(APP_STATE_RUNNING); - lgInit(); - goto restart; + if (app_transitionState(APP_STATE_RESTART, APP_STATE_RUNNING)) + { + lgInit(); + goto restart; + } } return recoveryExit(&recoveryPrompt, 0); diff --git a/client/src/main.h b/client/src/main.h index 9eac12e8..0a9f08fe 100644 --- a/client/src/main.h +++ b/client/src/main.h @@ -34,16 +34,10 @@ #include "common/ll.h" #include "cimgui.h" +#include "app.h" #include "interface/transport.h" #include "transport_fallback.h" -enum RunState -{ - APP_STATE_RUNNING, - APP_STATE_RESTART, - APP_STATE_SHUTDOWN -}; - enum MicDefaultState { MIC_DEFAULT_PROMPT, MIC_DEFAULT_ALLOW, @@ -112,6 +106,7 @@ struct AppState bool videoGeometryDirty; atomic_bool fallbackUUIDMismatch; + atomic_uint transportLost; uint8_t guestUUID[16]; bool guestUUIDValid; @@ -359,18 +354,6 @@ extern struct AppState g_state; extern struct CursorState g_cursor; extern struct AppParams g_params; -static inline enum RunState app_getState(void) -{ - extern _Atomic(enum RunState) p_appState; - return atomic_load_explicit(&p_appState, memory_order_acquire); -} - -static inline void app_setState(enum RunState state) -{ - extern _Atomic(enum RunState) p_appState; - atomic_store_explicit(&p_appState, state, memory_order_release); -} - int main_cursorThread(void * unused); int main_frameThread(void * unused); diff --git a/client/src/transport_fallback.c b/client/src/transport_fallback.c index 7e1b4b3d..3873f46a 100644 --- a/client/src/transport_fallback.c +++ b/client/src/transport_fallback.c @@ -164,6 +164,16 @@ static void notifyDisconnected( LG_UNLOCK(fallback->eventLock); } +static void notifyLost(LG_TransportFallback * fallback, bool requested) +{ + if (!requested || !fallback->eventOps.lost) + return; + + LG_LOCK(fallback->eventLock); + fallback->eventOps.lost(fallback->eventOpaque); + LG_UNLOCK(fallback->eventLock); +} + static void unpublishProviders(LG_TransportFallback * fallback, bool live) { LG_LOCK(fallback->providerLock); @@ -420,9 +430,12 @@ static bool connectFallback(LG_TransportFallback * fallback) notifyConnected(fallback); + bool lost = false; while (!atomic_load_explicit(&fallback->stop, memory_order_acquire)) { lgWaitEvent(fallback->wakeEvent, SESSION_POLL_MS); + if (atomic_load_explicit(&fallback->stop, memory_order_acquire)) + break; applyVideoRequest(fallback); LG_LOCK_EXCLUSIVE(fallback->lock); @@ -436,16 +449,21 @@ static bool connectFallback(LG_TransportFallback * fallback) if (reject || !admitted || closing) break; if (!sessionLive(fallback)) + { + lost = !atomic_load_explicit( + &fallback->stop, memory_order_acquire); break; + } } - const bool knownDead = !atomic_load_explicit( - &fallback->stop, memory_order_acquire) && !sessionLive(fallback); - const bool reportDisconnect = cleanupConnection(fallback, knownDead); + const bool reportDisconnect = cleanupConnection(fallback, lost); if (reportMismatch && fallback->eventOps.uuidMismatch) fallback->eventOps.uuidMismatch( fallback->eventOpaque, primaryUUID, fallbackUUID); + const bool reportLost = lost && reportDisconnect && !atomic_load_explicit( + &fallback->stop, memory_order_acquire); + notifyLost(fallback, reportLost); notifyDisconnected(fallback, reportDisconnect); return true; } diff --git a/client/src/transport_fallback.h b/client/src/transport_fallback.h index e1bfa118..51180760 100644 --- a/client/src/transport_fallback.h +++ b/client/src/transport_fallback.h @@ -32,6 +32,8 @@ typedef struct LG_TransportFallbackEventOps { /* The session is valid only for the duration of this callback. */ void (*connected)(void * opaque, const LG_TransportSession * session); + /* Called only when an admitted session is unexpectedly lost. */ + void (*lost)(void * opaque); void (*disconnected)(void * opaque); void (*uuidMismatch)(void * opaque, const uint8_t primary[16], const uint8_t fallback[16]); diff --git a/client/tests/keybind_test.c b/client/tests/keybind_test.c index 60e18197..f661cef1 100644 --- a/client/tests/keybind_test.c +++ b/client/tests/keybind_test.c @@ -81,6 +81,16 @@ struct CursorState g_cursor; struct AppParams g_params; _Atomic(enum RunState) p_appState; +enum RunState app_getState(void) +{ + return atomic_load(&p_appState); +} + +void app_setState(enum RunState state) +{ + atomic_store(&p_appState, state); +} + static const int fKeys[] = { KEY_F1 , KEY_F2 , KEY_F3 , KEY_F4,