[client] transport: recover incompatible sessions

Report expected and current protocol versions through the transport
interface and expose session-independent recovery operations.

Offer recovery from the version mismatch popup, publish requests through
the reserved IVSHMEM channel, and select the validated SPICE fallback.
This commit is contained in:
Geoffrey McRae
2026-08-11 23:31:51 +10:00
parent bacf376305
commit 574e8edbae
9 changed files with 1509 additions and 122 deletions

View File

@@ -712,7 +712,20 @@ MsgBoxHandle app_msgBox(const char * caption, const char * fmt, ...)
{
va_list args;
va_start(args, fmt);
MsgBoxHandle handle = overlayMsg_show(caption, NULL, NULL, fmt, args);
MsgBoxHandle handle = overlayMsg_show(
caption, NULL, NULL, NULL, NULL, fmt, args);
va_end(args);
return handle;
}
MsgBoxHandle app_msgBoxWithClose(const char * caption,
MsgBoxCloseCallback callback, void * opaque, const char * fmt, ...)
{
va_list args;
va_start(args, fmt);
MsgBoxHandle handle = overlayMsg_show(
caption, NULL, NULL, callback, opaque, fmt, args);
va_end(args);
return handle;
@@ -723,7 +736,8 @@ MsgBoxHandle app_confirmMsgBox(const char * caption,
{
va_list args;
va_start(args, fmt);
MsgBoxHandle handle = overlayMsg_show(caption, callback, opaque, fmt, args);
MsgBoxHandle handle = overlayMsg_show(
caption, callback, opaque, NULL, NULL, fmt, args);
va_end(args);
return handle;

View File

@@ -86,6 +86,7 @@ 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 LGEvent *e_startup = NULL;
static LGEvent *e_cursorRepaint = NULL;
@@ -1608,7 +1609,7 @@ int main_frameThread(void * unused)
&g_state.videoSource[LG_VIDEO_SOURCE_PRIMARY].ready, false,
memory_order_release);
if (!app_useVideoSource(LG_VIDEO_SOURCE_FALLBACK))
if (!fallbackRequestVideo())
videoSourceShowSplashIfNeeded();
}
@@ -1645,13 +1646,12 @@ static RenderQueueSource renderQueueSource(LG_VideoSource source)
return RENDER_QUEUE_SOURCE_NONE;
}
static void videoSourceBegin(LG_VideoSource source)
static void videoSourceBeginLocked(LG_VideoSource source)
{
struct VideoSourceState * state = &g_state.videoSource[source];
const uint64_t generation =
renderQueue_sourceBegin(renderQueueSource(source));
LG_LOCK(g_state.videoSourceLock);
atomic_store_explicit(&state->ready, false, memory_order_release);
atomic_store_explicit(
&state->transitionSerial, 0, memory_order_relaxed);
@@ -1664,6 +1664,12 @@ static void videoSourceBegin(LG_VideoSource source)
atomic_store_explicit(
&state->generation, generation, memory_order_release);
state->configurePending = false;
}
static void videoSourceBegin(LG_VideoSource source)
{
LG_LOCK(g_state.videoSourceLock);
videoSourceBeginLocked(source);
LG_UNLOCK(g_state.videoSourceLock);
}
@@ -1883,15 +1889,27 @@ static void videoSourceApplied(void * opaque, RenderQueueSource queueSource,
app_refreshVideoSource();
}
static bool swSurfaceEventAdmitted(LG_VideoSource source)
{
return source != LG_VIDEO_SOURCE_FALLBACK ||
lgTransportFallback_admitted(g_state.fallback);
}
static void swSurfaceConfigure(LG_VideoSource source,
unsigned int width, unsigned int height)
{
struct VideoSourceState * state = &g_state.videoSource[source];
videoSourceBegin(source);
LG_LOCK(g_state.videoSourceLock);
if (!swSurfaceEventAdmitted(source))
{
LG_UNLOCK(g_state.videoSourceLock);
return;
}
videoSourceBeginLocked(source);
atomic_store_explicit(&state->width, width, memory_order_relaxed);
atomic_store_explicit(&state->height, height, memory_order_relaxed);
atomic_store_explicit(&state->rotate, LG_ROTATE_0, memory_order_release);
LG_LOCK(g_state.videoSourceLock);
state->configurePending = true;
atomic_store_explicit(&state->ready, true, memory_order_release);
LG_UNLOCK(g_state.videoSourceLock);
@@ -1924,30 +1942,41 @@ static void swSurfaceDestroy(LG_VideoSource source)
static void swSurfaceDrawFill(LG_VideoSource source, int x, int y,
int width, int height, uint32_t color)
{
LG_LOCK(g_state.videoSourceLock);
if (!swSurfaceEventAdmitted(source))
{
LG_UNLOCK(g_state.videoSourceLock);
return;
}
const uint64_t generation = atomic_load_explicit(
&g_state.videoSource[source].generation, memory_order_acquire);
renderQueue_sourceSwSurfaceDrawFill(renderQueueSource(source), generation,
x, y, width, height, color);
LG_UNLOCK(g_state.videoSourceLock);
}
static void swSurfaceDrawBitmap(LG_VideoSource source, bool topDown,
int x, int y, int width, int height, int stride, const void * data)
{
LG_LOCK(g_state.videoSourceLock);
if (!swSurfaceEventAdmitted(source))
{
LG_UNLOCK(g_state.videoSourceLock);
return;
}
const uint64_t generation = atomic_load_explicit(
&g_state.videoSource[source].generation, memory_order_acquire);
renderQueue_sourceSwSurfaceDrawBitmap(renderQueueSource(source), generation,
x, y, width, height, stride, data, topDown);
LG_UNLOCK(g_state.videoSourceLock);
}
static void swSurfacePointer(LG_VideoSource source,
const LG_TransportPointer * pointer)
{
struct VideoSourceState * state = &g_state.videoSource[source];
const uint64_t generation = atomic_load_explicit(
&state->generation, memory_order_acquire);
if (!generation)
return;
LG_RendererCursor type = LG_CURSOR_COLOR;
if (pointer->flags & LG_TRANSPORT_POINTER_SHAPE)
switch (pointer->type)
@@ -1971,8 +2000,15 @@ static void swSurfacePointer(LG_VideoSource source,
const bool drawCursor = source != LG_VIDEO_SOURCE_PRIMARY ||
g_cursor.draw || !lgInput_available();
LG_LOCK(g_state.videoSourceLock);
if (atomic_load_explicit(&state->generation, memory_order_acquire) !=
generation)
if (!swSurfaceEventAdmitted(source))
{
LG_UNLOCK(g_state.videoSourceLock);
return;
}
const uint64_t generation = atomic_load_explicit(
&state->generation, memory_order_acquire);
if (!generation)
{
LG_UNLOCK(g_state.videoSourceLock);
return;
@@ -2036,7 +2072,6 @@ static void swSurfacePointer(LG_VideoSource source,
g_cursor.guest.hy = hy;
g_cursor.guest.valid = valid;
}
LG_UNLOCK(g_state.videoSourceLock);
if (pointer->flags & LG_TRANSPORT_POINTER_SHAPE)
renderQueue_sourceCursorImage(renderQueueSource(source), generation,
@@ -2055,6 +2090,7 @@ static void swSurfacePointer(LG_VideoSource source,
LG_TRANSPORT_POINTER_VISIBLE_VALID | LG_TRANSPORT_POINTER_SHAPE)))
renderQueue_sourceCursorState(renderQueueSource(source), generation,
visible, x, y, hx, hy);
LG_UNLOCK(g_state.videoSourceLock);
if (sourceApplied)
{
@@ -2094,8 +2130,7 @@ static void swSurfaceEventDestroy(void * opaque)
static void swSurfaceEventDrawFill(void * opaque, int x, int y,
int width, int height, uint32_t color)
{
swSurfaceDrawFill(
swSurfaceSource(opaque), x, y, width, height, color);
swSurfaceDrawFill(swSurfaceSource(opaque), x, y, width, height, color);
}
static void swSurfaceEventDrawBitmap(void * opaque, bool topDown,
@@ -2120,6 +2155,18 @@ static const LG_SwSurfaceEventOps swSurfaceEvents =
.pointer = swSurfaceEventPointer,
};
static bool fallbackRequestVideo(void)
{
if (!g_state.fallback)
return false;
lgTransportFallback_requestVideoActive(g_state.fallback, true);
if (!lgTransportFallback_admitted(g_state.fallback))
return false;
return app_useVideoSource(LG_VIDEO_SOURCE_FALLBACK);
}
static void fallbackConnected(void * opaque,
const LG_TransportSession * session)
{
@@ -2131,7 +2178,10 @@ static void fallbackConnected(void * opaque,
&g_state.lgHostConnected, memory_order_acquire))
core_setTitle(session->name);
app_refreshVideoSource();
if (lgTransportFallback_videoRequested(g_state.fallback))
fallbackRequestVideo();
else
app_refreshVideoSource();
}
static void fallbackDisconnected(void * opaque)
@@ -2162,7 +2212,7 @@ static const LG_TransportFallbackEventOps fallbackEvents =
.uuidMismatch = fallbackUUIDMismatch,
};
static bool fallbackStart(void)
static bool fallbackStart(const uint8_t primaryUUID[16])
{
if (!option_get_bool("spice", "enable") ||
strcmp(g_params.transport, "spice") == 0)
@@ -2170,7 +2220,7 @@ static bool fallbackStart(void)
if (lgTransportFallback_start("spice", &swSurfaceEvents,
(void *)(uintptr_t)LG_VIDEO_SOURCE_FALLBACK,
&fallbackEvents, NULL, &g_state.fallback))
&fallbackEvents, NULL, primaryUUID, &g_state.fallback))
return true;
DEBUG_ERROR("Failed to start the SPICE fallback transport");
@@ -2251,12 +2301,522 @@ static bool tryRenderer(const int index, const LG_RendererParams lgrParams,
return true;
}
static void reportBadVersion(void)
static void reportBadVersion(const LG_VersionMismatch * mismatch)
{
DEBUG_BREAK();
DEBUG_ERROR("The host application is not compatible with this client");
DEBUG_ERROR("This is not a Looking Glass error, do not report this");
DEBUG_ERROR("Please install the matching host application for this client");
if (mismatch->valid)
{
DEBUG_ERROR("Incompatible %s version", mismatch->component);
DEBUG_ERROR("Expected version: %u", mismatch->expectedVersion);
DEBUG_ERROR("Current version : %u", mismatch->currentVersion);
}
else
DEBUG_ERROR("The transport is not compatible with this client");
DEBUG_ERROR("Please install matching Looking Glass components");
}
static const int RECOVERY_PENDING = -1;
static const int RECOVERY_NO = 0;
static const int RECOVERY_YES = 1;
struct RecoveryPrompt
{
atomic_int choice;
atomic_uintptr_t handle;
atomic_uintptr_t message;
atomic_bool messageClosed;
uint64_t instance;
uint32_t serial;
uint32_t failedSerial;
LG_RecoveryState reportedState;
bool shown;
bool requested;
bool owned;
bool retryPrompt;
bool retryDeclined;
};
static bool recoveryGetInfo(LG_RecoveryInfo * info)
{
if (!g_state.transport.ops->getRecoveryInfo)
return false;
return g_state.transport.ops->getRecoveryInfo(
g_state.transport.handle, info) == LG_TRANSPORT_OK;
}
static void recoveryConfirm(bool yes, void * opaque)
{
struct RecoveryPrompt * prompt = opaque;
atomic_store_explicit(&prompt->choice,
yes ? RECOVERY_YES : RECOVERY_NO, memory_order_release);
}
static void recoveryClosePrompt(struct RecoveryPrompt * prompt)
{
if (atomic_load_explicit(
&prompt->choice, memory_order_acquire) != RECOVERY_PENDING)
{
atomic_store_explicit(&prompt->handle, 0, memory_order_relaxed);
return;
}
MsgBoxHandle handle = (MsgBoxHandle)atomic_exchange_explicit(
&prompt->handle, 0, memory_order_acq_rel);
app_msgBoxClose(handle);
}
static void recoveryCloseMessage(struct RecoveryPrompt * prompt)
{
MsgBoxHandle handle = (MsgBoxHandle)atomic_exchange_explicit(
&prompt->message, 0, memory_order_acq_rel);
app_msgBoxClose(handle);
}
static void recoveryMessageClosed(MsgBoxHandle handle, void * opaque)
{
struct RecoveryPrompt * prompt = opaque;
atomic_store_explicit(
&prompt->messageClosed, true, memory_order_release);
uintptr_t expected = (uintptr_t)handle;
atomic_compare_exchange_strong_explicit(&prompt->message,
&expected, 0, memory_order_acq_rel, memory_order_acquire);
}
static void recoveryBeginMessage(struct RecoveryPrompt * prompt)
{
recoveryCloseMessage(prompt);
atomic_store_explicit(
&prompt->messageClosed, false, memory_order_relaxed);
}
static void recoveryStoreMessage(struct RecoveryPrompt * prompt,
MsgBoxHandle handle)
{
atomic_store_explicit(
&prompt->message, (uintptr_t)handle, memory_order_release);
if (!atomic_load_explicit(
&prompt->messageClosed, memory_order_acquire))
return;
uintptr_t expected = (uintptr_t)handle;
atomic_compare_exchange_strong_explicit(&prompt->message,
&expected, 0, memory_order_acq_rel, memory_order_acquire);
}
static void recoveryClose(struct RecoveryPrompt * prompt)
{
recoveryClosePrompt(prompt);
recoveryCloseMessage(prompt);
}
static int recoveryExit(struct RecoveryPrompt * prompt, int result)
{
recoveryClose(prompt);
return result;
}
static void retainMessage(MsgBoxHandle * messages, size_t capacity,
int * count, MsgBoxHandle message)
{
if (!message)
return;
if ((size_t)*count >= capacity)
{
app_msgBoxClose(message);
return;
}
messages[(*count)++] = message;
}
static void recoveryShowPrompt(struct RecoveryPrompt * prompt,
const LG_VersionMismatch * mismatch, bool hasFallback)
{
const char * fallback = hasFallback ?
"The client will then switch to its SPICE fallback." :
"SPICE is disabled in this client, so use another SPICE viewer.";
recoveryCloseMessage(prompt);
prompt->shown = true;
prompt->retryPrompt = false;
atomic_store_explicit(
&prompt->choice, RECOVERY_PENDING, memory_order_relaxed);
MsgBoxHandle handle;
if (mismatch->valid)
{
const char * component = mismatch->component[0] ?
mismatch->component : "transport";
handle = app_confirmMsgBox(
"Incompatible Transport Version", recoveryConfirm, prompt,
"Expected %s version: %u\n"
"Current %s version: %u\n"
"\n"
"Enable recovery mode to restore the guest display topology?\n"
"%s",
component, mismatch->expectedVersion,
component, mismatch->currentVersion, fallback);
}
else
{
handle = app_confirmMsgBox(
"Incompatible Transport Version", recoveryConfirm, prompt,
"The transport is not compatible with this client.\n"
"\n"
"Enable recovery mode to restore the guest display topology?\n"
"%s", fallback);
}
atomic_store_explicit(
&prompt->handle, (uintptr_t)handle, memory_order_release);
if (atomic_load_explicit(
&prompt->choice, memory_order_acquire) != RECOVERY_PENDING)
atomic_store_explicit(&prompt->handle, 0, memory_order_relaxed);
}
static const char * recoveryErrorText(LG_RecoveryError error)
{
switch (error)
{
case LG_RECOVERY_ERR_HELPER_UNAVAILABLE:
return "The IDD helper is unavailable";
case LG_RECOVERY_ERR_TOPOLOGY_FAILED:
return "Windows could not restore the saved display topology";
case LG_RECOVERY_ERR_NO_FALLBACK_DISPLAY:
return "No fallback display became active";
case LG_RECOVERY_ERR_UNSUPPORTED:
return "Recovery is not supported by the guest driver";
case LG_RECOVERY_ERR_NONE:
break;
}
return "The guest could not enter recovery mode";
}
static int recoveryTakeChoice(struct RecoveryPrompt * prompt)
{
const int choice = atomic_exchange_explicit(
&prompt->choice, RECOVERY_PENDING, memory_order_acq_rel);
if (choice != RECOVERY_PENDING)
atomic_store_explicit(&prompt->handle, 0, memory_order_relaxed);
return choice;
}
static void recoveryShowRetry(struct RecoveryPrompt * prompt,
const char * reason, uint32_t failedSerial)
{
recoveryClose(prompt);
if (failedSerial)
prompt->failedSerial = failedSerial;
prompt->serial = 0;
prompt->reportedState = LG_RECOVERY_STATE_FAILED;
prompt->shown = true;
prompt->requested = false;
prompt->owned = false;
prompt->retryPrompt = true;
atomic_store_explicit(
&prompt->choice, RECOVERY_PENDING, memory_order_relaxed);
MsgBoxHandle handle = app_confirmMsgBox(
"Recovery Failed", recoveryConfirm, prompt,
"%s.\n\nRetry guest display recovery?", reason);
atomic_store_explicit(
&prompt->handle, (uintptr_t)handle, memory_order_release);
if (atomic_load_explicit(
&prompt->choice, memory_order_acquire) != RECOVERY_PENDING)
atomic_store_explicit(&prompt->handle, 0, memory_order_relaxed);
}
static void recoveryShowPending(struct RecoveryPrompt * prompt,
const LG_VersionMismatch * mismatch)
{
prompt->shown = true;
prompt->reportedState = LG_RECOVERY_STATE_SWITCHING;
prompt->retryPrompt = false;
if (mismatch->valid)
{
const char * component = mismatch->component[0] ?
mismatch->component : "transport";
recoveryBeginMessage(prompt);
recoveryStoreMessage(prompt, app_msgBoxWithClose(
"Incompatible Transport Version",
recoveryMessageClosed, prompt,
"Expected %s version: %u\n"
"Current %s version: %u\n"
"\n"
"Recovery mode is pending. Waiting for the guest driver.",
component, mismatch->expectedVersion,
component, mismatch->currentVersion));
}
else
{
recoveryBeginMessage(prompt);
recoveryStoreMessage(prompt, app_msgBoxWithClose(
"Incompatible Transport Version",
recoveryMessageClosed, prompt,
"The transport is not compatible with this client.\n"
"\n"
"Recovery mode is pending. Waiting for the guest driver."));
}
}
static bool recoverySerialNewer(uint32_t serial, uint32_t reference)
{
const uint32_t difference = serial - reference;
return difference && difference < 0x80000000U;
}
static void recoveryHandleMismatch(struct RecoveryPrompt * prompt,
const LG_VersionMismatch * mismatch)
{
LG_RecoveryInfo info = { 0 };
const bool available = recoveryGetInfo(&info) &&
(info.capabilities & LG_RECOVERY_CAP_DISPLAY);
if (!available)
{
if (prompt->requested && !prompt->retryPrompt &&
!prompt->retryDeclined)
{
recoveryShowRetry(prompt,
"The guest recovery channel is unavailable", 0);
return;
}
if (prompt->shown)
return;
prompt->shown = true;
if (mismatch->valid)
{
const char * component = mismatch->component[0] ?
mismatch->component : "transport";
recoveryBeginMessage(prompt);
recoveryStoreMessage(prompt, app_msgBoxWithClose(
"Incompatible Transport Version",
recoveryMessageClosed, prompt,
"Expected %s version: %u\n"
"Current %s version: %u\n"
"\n"
"This guest driver does not support remote recovery.\n"
"Use a guest console to install matching components.",
component, mismatch->expectedVersion,
component, mismatch->currentVersion));
}
else
{
recoveryBeginMessage(prompt);
recoveryStoreMessage(prompt, app_msgBoxWithClose(
"Incompatible Transport Version",
recoveryMessageClosed, prompt,
"The transport is not compatible with this client.\n"
"\n"
"This guest driver does not support remote recovery.\n"
"Use a guest console to install matching components."));
}
return;
}
if (prompt->instance != info.instance)
{
recoveryClose(prompt);
prompt->instance = info.instance;
prompt->serial = 0;
prompt->failedSerial = 0;
prompt->reportedState = LG_RECOVERY_STATE_UNKNOWN;
prompt->shown = false;
prompt->requested = false;
prompt->owned = false;
prompt->retryPrompt = false;
prompt->retryDeclined = false;
atomic_store_explicit(
&prompt->choice, RECOVERY_PENDING, memory_order_relaxed);
}
if (info.uuidValid)
lgTransportFallback_setPrimaryUUID(g_state.fallback, info.uuid);
else
lgTransportFallback_clearPrimaryUUID(g_state.fallback);
const bool requestNewer = info.requestSerial != 0 &&
(info.ackSerial == 0 ||
recoverySerialNewer(info.requestSerial, info.ackSerial));
const LG_RecoveryRequest globalRequest = requestNewer ?
info.request : info.ackRequest;
const uint32_t globalSerial = requestNewer ?
info.requestSerial : info.ackSerial;
if (prompt->requested && globalSerial &&
globalRequest == LG_RECOVERY_REQ_NORMAL &&
(globalSerial == prompt->serial ||
recoverySerialNewer(globalSerial, prompt->serial)))
{
recoveryClose(prompt);
prompt->serial = 0;
prompt->reportedState = LG_RECOVERY_STATE_UNKNOWN;
prompt->shown = prompt->retryDeclined;
prompt->requested = false;
prompt->owned = false;
prompt->retryPrompt = false;
atomic_store_explicit(
&prompt->choice, RECOVERY_PENDING, memory_order_relaxed);
}
const bool pendingRecovery = requestNewer &&
info.request == LG_RECOVERY_REQ_RECOVERY;
const bool failedRecovery = !requestNewer && info.ackSerial != 0 &&
info.ackRequest == LG_RECOVERY_REQ_RECOVERY &&
info.state == LG_RECOVERY_STATE_FAILED;
const bool statusRecovery = !requestNewer && info.ackSerial != 0 &&
info.ackRequest == LG_RECOVERY_REQ_RECOVERY &&
(info.state == LG_RECOVERY_STATE_SWITCHING ||
info.state == LG_RECOVERY_STATE_ACTIVE);
if (failedRecovery && prompt->requested &&
prompt->failedSerial != info.ackSerial &&
(info.ackSerial == prompt->serial ||
recoverySerialNewer(info.ackSerial, prompt->serial)))
{
if (prompt->owned && info.ackSerial == prompt->serial)
{
recoveryShowRetry(prompt, recoveryErrorText(info.error),
info.ackSerial);
return;
}
recoveryClose(prompt);
prompt->serial = 0;
prompt->failedSerial = info.ackSerial;
prompt->reportedState = LG_RECOVERY_STATE_UNKNOWN;
prompt->shown = prompt->retryDeclined;
prompt->requested = false;
prompt->owned = false;
prompt->retryPrompt = false;
atomic_store_explicit(
&prompt->choice, RECOVERY_PENDING, memory_order_relaxed);
}
if (pendingRecovery)
{
if (!prompt->requested || prompt->serial != info.requestSerial)
{
recoveryClose(prompt);
prompt->requested = true;
prompt->serial = info.requestSerial;
prompt->failedSerial = 0;
prompt->reportedState = LG_RECOVERY_STATE_UNKNOWN;
prompt->shown = false;
prompt->owned = false;
prompt->retryPrompt = false;
atomic_store_explicit(
&prompt->choice, RECOVERY_PENDING, memory_order_relaxed);
}
}
else if (statusRecovery &&
(!prompt->requested || prompt->serial != info.ackSerial))
{
recoveryClose(prompt);
prompt->requested = true;
prompt->serial = info.ackSerial;
prompt->failedSerial = 0;
prompt->reportedState = LG_RECOVERY_STATE_UNKNOWN;
prompt->shown = false;
prompt->owned = false;
prompt->retryPrompt = false;
atomic_store_explicit(
&prompt->choice, RECOVERY_PENDING, memory_order_relaxed);
}
const bool retryChoice = prompt->retryPrompt;
const int choice = recoveryTakeChoice(prompt);
if (choice == RECOVERY_NO)
{
prompt->retryPrompt = false;
if (retryChoice)
prompt->retryDeclined = true;
return;
}
if (choice == RECOVERY_YES)
{
prompt->retryPrompt = false;
prompt->retryDeclined = false;
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_TRANSPORT_UNAVAILABLE;
prompt->requested = status == LG_TRANSPORT_OK;
prompt->owned = prompt->requested;
prompt->reportedState = LG_RECOVERY_STATE_UNKNOWN;
if (!prompt->requested)
{
recoveryShowRetry(prompt,
"The recovery request could not be sent to the guest driver", 0);
return;
}
prompt->failedSerial = 0;
fallbackRequestVideo();
recoveryShowPending(prompt, mismatch);
app_alert(LG_ALERT_INFO, "Guest display recovery requested");
return;
}
const bool matchingAck = prompt->requested && statusRecovery &&
info.ackSerial == prompt->serial;
if (matchingAck && info.state == LG_RECOVERY_STATE_ACTIVE)
{
fallbackRequestVideo();
if (prompt->reportedState != LG_RECOVERY_STATE_ACTIVE)
{
prompt->shown = true;
prompt->reportedState = LG_RECOVERY_STATE_ACTIVE;
if (mismatch->valid)
{
const char * component = mismatch->component[0] ?
mismatch->component : "transport";
recoveryBeginMessage(prompt);
recoveryStoreMessage(prompt, app_msgBoxWithClose(
"Incompatible Transport Version",
recoveryMessageClosed, prompt,
"Expected %s version: %u\n"
"Current %s version: %u\n"
"\n"
"Recovery mode is active. SPICE video is available.",
component, mismatch->expectedVersion,
component, mismatch->currentVersion));
}
else
{
recoveryBeginMessage(prompt);
recoveryStoreMessage(prompt, app_msgBoxWithClose(
"Incompatible Transport Version",
recoveryMessageClosed, prompt,
"The transport is not compatible with this client.\n"
"\n"
"Recovery mode is active. SPICE video is available."));
}
app_alert(LG_ALERT_SUCCESS, "Guest display recovery is active");
}
return;
}
const bool pending = prompt->requested &&
((pendingRecovery &&
info.requestSerial == prompt->serial) ||
(matchingAck && info.state == LG_RECOVERY_STATE_SWITCHING));
if (pending)
{
fallbackRequestVideo();
if (prompt->reportedState != LG_RECOVERY_STATE_SWITCHING)
recoveryShowPending(prompt, mismatch);
return;
}
if (!prompt->shown)
recoveryShowPrompt(prompt, mismatch, g_state.fallback != NULL);
}
static MsgBoxHandle showSpiceInputHelp(void)
@@ -2412,6 +2972,9 @@ static int lg_run(void)
}
DEBUG_INFO("Using Transport: %s", g_state.transport.ops->name);
LG_RecoveryInfo initialRecovery = { 0 };
const bool initialRecoveryValid = recoveryGetInfo(&initialRecovery);
g_state.videoOps =
g_state.transport.ops->getVideoOps(g_state.transport.handle);
if (!g_state.videoOps ||
@@ -2460,7 +3023,9 @@ static int lg_run(void)
g_state.micDefaultState = g_params.micDefaultState;
if (!fallbackStart())
const uint8_t * fallbackUUID = initialRecoveryValid &&
initialRecovery.uuidValid ? initialRecovery.uuid : NULL;
if (!fallbackStart(fallbackUUID))
return -1;
// select and init a renderer
@@ -2621,6 +3186,11 @@ static int lg_run(void)
LG_TransportSession session;
MsgBoxHandle msgs[10];
int msgsCount;
struct RecoveryPrompt recoveryPrompt = { 0 };
atomic_init(&recoveryPrompt.choice, RECOVERY_PENDING);
atomic_init(&recoveryPrompt.handle, 0);
atomic_init(&recoveryPrompt.message, 0);
atomic_init(&recoveryPrompt.messageClosed, false);
restart:
frameTimingReset();
@@ -2636,8 +3206,8 @@ restart:
if (initialFallbackEnable && microtime() > initialFallbackEnable)
{
app_useVideoSource(LG_VIDEO_SOURCE_FALLBACK);
initialFallbackEnable = 0;
if (fallbackRequestVideo())
initialFallbackEnable = 0;
}
struct TransportSessionProbe probe = {
@@ -2650,7 +3220,7 @@ restart:
&probeThread))
{
DEBUG_ERROR("Failed to create transport session probe thread");
return -1;
return recoveryExit(&recoveryPrompt, -1);
}
while (app_getState() == APP_STATE_RUNNING &&
@@ -2663,11 +3233,11 @@ restart:
if (!lgJoinThread(probeThread, NULL))
{
DEBUG_ERROR("Failed to join transport session probe thread");
return -1;
return recoveryExit(&recoveryPrompt, -1);
}
if (app_getState() != APP_STATE_RUNNING)
return -1;
return recoveryExit(&recoveryPrompt, -1);
if (probe.status == LG_TRANSPORT_OK)
{
@@ -2679,14 +3249,10 @@ restart:
if (probe.status == LG_TRANSPORT_INVALID_VERSION)
{
if (waitCount++ == 0)
{
reportBadVersion();
msgs[msgsCount++] = app_msgBox(
"Incompatible Transport Version",
"The selected transport source is not compatible with this client.\n"
"Please install matching versions.");
DEBUG_INFO("Remote transport version: %u", probe.session.remoteVersion);
}
reportBadVersion(&probe.session.versionMismatch);
recoveryHandleMismatch(
&recoveryPrompt, &probe.session.versionMismatch);
g_state.ds->wait(1000);
continue;
@@ -2703,22 +3269,50 @@ restart:
}
if (waitCount == 30 && !g_params.disableWaitingMessage)
{
msgs[msgsCount++] = app_msgBox(
"Transport Source Not Available",
"The selected transport source is not available.\n"
"Continuing to wait...");
msgs[msgsCount++] = showSpiceInputHelp();
const size_t msgCapacity = sizeof(msgs) / sizeof(*msgs);
retainMessage(msgs, msgCapacity, &msgsCount,
app_msgBox("Transport Source Not Available",
"The selected transport source is not available.\n"
"Continuing to wait..."));
retainMessage(msgs, msgCapacity, &msgsCount,
showSpiceInputHelp());
}
g_state.ds->wait(1000);
continue;
}
DEBUG_ERROR("Transport connection failed with status %d", probe.status);
return -1;
return recoveryExit(&recoveryPrompt, -1);
}
if (app_getState() != APP_STATE_RUNNING)
return -1;
return recoveryExit(&recoveryPrompt, -1);
recoveryClose(&recoveryPrompt);
LG_RecoveryInfo recoveryInfo = { 0 };
if (recoveryGetInfo(&recoveryInfo) &&
(recoveryInfo.state == LG_RECOVERY_STATE_ACTIVE ||
recoveryInfo.state == LG_RECOVERY_STATE_SWITCHING ||
recoveryInfo.request == LG_RECOVERY_REQ_RECOVERY ||
recoveryInfo.ackRequest == LG_RECOVERY_REQ_RECOVERY) &&
g_state.transport.ops->requestRecovery)
{
const LG_TransportStatus status =
g_state.transport.ops->requestRecovery(g_state.transport.handle,
LG_RECOVERY_REQ_NORMAL, NULL);
if (status != LG_TRANSPORT_OK)
DEBUG_WARN("Failed to leave recovery mode: %d", status);
}
recoveryPrompt.serial = 0;
recoveryPrompt.failedSerial = 0;
recoveryPrompt.reportedState = LG_RECOVERY_STATE_UNKNOWN;
recoveryPrompt.shown = false;
recoveryPrompt.requested = false;
recoveryPrompt.owned = false;
recoveryPrompt.retryPrompt = false;
recoveryPrompt.retryDeclined = false;
atomic_store_explicit(
&recoveryPrompt.choice, RECOVERY_PENDING, memory_order_relaxed);
waitCount = 100;
for (int i = 0; i < msgsCount; ++i)
@@ -2811,7 +3405,7 @@ restart:
{
videoSourceBegin(LG_VIDEO_SOURCE_PRIMARY);
if (!core_startCursorThread() || !core_startFrameThread())
return -1;
return recoveryExit(&recoveryPrompt, -1);
}
else
{
@@ -2819,7 +3413,7 @@ restart:
g_state.transport.handle, true))
{
DEBUG_ERROR("Failed to activate the primary software surface");
return -1;
return recoveryExit(&recoveryPrompt, -1);
}
app_useVideoSource(LG_VIDEO_SOURCE_PRIMARY);
}
@@ -2852,7 +3446,6 @@ restart:
atomic_store_explicit(
&g_state.lgHostConnected, false, memory_order_release);
g_state.guestUUIDValid = false;
lgTransportFallback_clearPrimaryUUID(g_state.fallback);
lgSignalEvent(e_startup);
lgSignalEvent(g_state.frameEvent);
@@ -2877,7 +3470,7 @@ restart:
goto restart;
}
return 0;
return recoveryExit(&recoveryPrompt, 0);
}
static void lg_shutdown(void)

View File

@@ -33,11 +33,13 @@
struct Msg
{
char * caption;
char * message;
StringList lines;
char * caption;
char * message;
StringList lines;
MsgBoxConfirmCallback confirm;
void * opaque;
void * confirmOpaque;
MsgBoxCloseCallback close;
void * closeOpaque;
};
struct MsgState
@@ -62,6 +64,9 @@ static bool msg_init(void ** udata, const void * params)
static void freeMsg(struct Msg * msg)
{
if (msg->close)
msg->close((MsgBoxHandle)msg, msg->closeOpaque);
free(msg->caption);
free(msg->message);
stringlist_free(&msg->lines);
@@ -144,14 +149,14 @@ static int msg_render(void * udata, bool interactive, struct Rect * windowRects,
if (igButton("Yes", textSize))
{
destroy = true;
msg->confirm(true, msg->opaque);
msg->confirm(true, msg->confirmOpaque);
}
igSameLine(0.0f, -1.0f);
if (igButton("No", textSize))
{
destroy = true;
msg->confirm(false, msg->opaque);
msg->confirm(false, msg->confirmOpaque);
}
}
else
@@ -202,7 +207,9 @@ bool overlayMsg_modal(void)
}
MsgBoxHandle overlayMsg_show(
const char * caption, MsgBoxConfirmCallback confirm, void * opaque,
const char * caption,
MsgBoxConfirmCallback confirm, void * confirmOpaque,
MsgBoxCloseCallback close, void * closeOpaque,
const char * fmt, va_list args)
{
struct Msg * msg = calloc(1, sizeof(*msg));
@@ -228,8 +235,6 @@ MsgBoxHandle overlayMsg_show(
return NULL;
}
msg->confirm = confirm;
msg->opaque = opaque;
if (valloc_sprintf(&msg->message, fmt ? fmt : "", args) < 0)
{
DEBUG_ERROR("failed to format message");
@@ -256,6 +261,10 @@ MsgBoxHandle overlayMsg_show(
}
}
msg->confirm = confirm;
msg->confirmOpaque = confirmOpaque;
msg->close = close;
msg->closeOpaque = closeOpaque;
ll_push(l_msg.messages, msg);
app_invalidateOverlay(false);

View File

@@ -29,7 +29,9 @@
bool overlayMsg_modal(void);
MsgBoxHandle overlayMsg_show(
const char * caption, MsgBoxConfirmCallback confirm, void * opaque,
const char * caption,
MsgBoxConfirmCallback confirm, void * confirmOpaque,
MsgBoxCloseCallback close, void * closeOpaque,
const char * fmt, va_list args);
void overlayMsg_close(MsgBoxHandle handle);

View File

@@ -50,9 +50,12 @@ struct LG_TransportFallback
LGThread * thread;
LGEvent * wakeEvent;
LG_RWLock lock;
LG_Lock providerLock;
LG_Lock eventLock;
atomic_bool stop;
atomic_bool ready;
atomic_bool admitted;
LG_TransportInstance transport;
LG_TransportSession session;
@@ -62,6 +65,7 @@ struct LG_TransportFallback
bool connected;
bool providersPublished;
bool connectedReported;
bool disconnectedReported;
bool closing;
bool videoRequested;
bool videoActive;
@@ -89,6 +93,11 @@ static bool uuidMismatchLocked(const LG_TransportFallback * fallback)
sizeof(fallback->primaryUUID)) != 0;
}
static bool uuidAdmittedLocked(const LG_TransportFallback * fallback)
{
return !uuidMismatchLocked(fallback);
}
static bool recordMismatchLocked(LG_TransportFallback * fallback,
uint8_t primary[16], uint8_t remote[16])
{
@@ -119,10 +128,50 @@ static bool sessionLive(const LG_TransportFallback * fallback)
fallback->transport.ops->sessionValid(fallback->transport.handle);
}
static void notifyConnected(LG_TransportFallback * fallback)
{
LG_LOCK(fallback->eventLock);
LG_LOCK_EXCLUSIVE(fallback->lock);
const bool report =
atomic_load_explicit(&fallback->ready, memory_order_acquire) &&
atomic_load_explicit(&fallback->admitted, memory_order_acquire) &&
uuidAdmittedLocked(fallback) && !fallback->closing &&
!fallback->disconnectedReported;
fallback->connectedReported = report;
const LG_TransportSession session = fallback->session;
LG_UNLOCK_EXCLUSIVE(fallback->lock);
if (report && fallback->eventOps.connected)
fallback->eventOps.connected(fallback->eventOpaque, &session);
LG_UNLOCK(fallback->eventLock);
}
static void notifyDisconnected(
LG_TransportFallback * fallback, bool requested)
{
if (!requested)
return;
LG_LOCK(fallback->eventLock);
LG_LOCK_EXCLUSIVE(fallback->lock);
const bool report = !fallback->disconnectedReported;
if (report)
fallback->disconnectedReported = true;
LG_UNLOCK_EXCLUSIVE(fallback->lock);
if (report && fallback->eventOps.disconnected)
fallback->eventOps.disconnected(fallback->eventOpaque);
LG_UNLOCK(fallback->eventLock);
}
static void unpublishProviders(LG_TransportFallback * fallback, bool live)
{
LG_LOCK(fallback->providerLock);
if (!fallback->providersPublished)
{
LG_UNLOCK(fallback->providerLock);
return;
}
if (live)
{
@@ -137,12 +186,14 @@ static void unpublishProviders(LG_TransportFallback * fallback, bool live)
lgClipboard_dropFallback();
}
fallback->providersPublished = false;
LG_UNLOCK(fallback->providerLock);
}
static void closeVideoAdmission(LG_TransportFallback * fallback)
{
LG_LOCK_EXCLUSIVE(fallback->lock);
atomic_store_explicit(&fallback->ready, false, memory_order_release);
atomic_store_explicit(&fallback->admitted, false, memory_order_release);
fallback->closing = true;
LG_UNLOCK_EXCLUSIVE(fallback->lock);
}
@@ -184,8 +235,20 @@ static bool cleanupConnection(LG_TransportFallback * fallback,
return reportDisconnected;
}
static void publishProviders(LG_TransportFallback * fallback)
static bool publishProviders(LG_TransportFallback * fallback)
{
LG_LOCK(fallback->providerLock);
LG_LOCK_SHARED(fallback->lock);
const bool admitted = atomic_load_explicit(
&fallback->admitted, memory_order_acquire) &&
uuidAdmittedLocked(fallback) && !fallback->closing;
if (!admitted)
{
LG_UNLOCK_SHARED(fallback->lock);
LG_UNLOCK(fallback->providerLock);
return false;
}
void * inputOpaque = NULL;
const LG_InputOps * inputOps = fallback->transport.ops->getInputOps ?
fallback->transport.ops->getInputOps(
@@ -205,9 +268,12 @@ static void publishProviders(LG_TransportFallback * fallback)
fallback->transport.handle, &clipboardOpaque) : NULL;
lgClipboard_setFallback(clipboardOps, clipboardOpaque);
fallback->providersPublished = true;
LG_UNLOCK_SHARED(fallback->lock);
LG_UNLOCK(fallback->providerLock);
return true;
}
static bool publishConnection(LG_TransportFallback * fallback,
static bool admitConnection(LG_TransportFallback * fallback,
bool * reportMismatch, uint8_t primaryUUID[16],
uint8_t fallbackUUID[16])
{
@@ -218,11 +284,24 @@ static bool publishConnection(LG_TransportFallback * fallback,
fallback, primaryUUID, fallbackUUID);
const bool stop = atomic_load_explicit(
&fallback->stop, memory_order_acquire);
const bool admitted = !reject && !stop && !fallback->closing;
if (admitted)
atomic_store_explicit(
&fallback->admitted, true, memory_order_release);
LG_UNLOCK_EXCLUSIVE(fallback->lock);
if (reject || stop)
return admitted;
}
static bool publishConnection(LG_TransportFallback * fallback,
bool * reportMismatch, uint8_t primaryUUID[16],
uint8_t fallbackUUID[16])
{
if (!admitConnection(fallback, reportMismatch,
primaryUUID, fallbackUUID))
return false;
publishProviders(fallback);
if (!publishProviders(fallback))
return false;
for (;;)
{
@@ -231,12 +310,14 @@ static bool publishConnection(LG_TransportFallback * fallback,
if (reject)
*reportMismatch = recordMismatchLocked(
fallback, primaryUUID, fallbackUUID);
const bool admitted = uuidAdmittedLocked(fallback);
const bool stop = atomic_load_explicit(
&fallback->stop, memory_order_acquire);
const bool closing = fallback->closing;
const bool requested = fallback->videoRequested;
LG_UNLOCK_EXCLUSIVE(fallback->lock);
if (reject || stop)
if (reject || !admitted || stop || closing)
return false;
if (requested == fallback->videoActive)
break;
@@ -252,15 +333,18 @@ static bool publishConnection(LG_TransportFallback * fallback,
if (finalReject)
*reportMismatch = recordMismatchLocked(
fallback, primaryUUID, fallbackUUID);
const bool finalAdmitted = uuidAdmittedLocked(fallback);
const bool finalStop = atomic_load_explicit(
&fallback->stop, memory_order_acquire);
if (!finalReject && !finalStop)
if (!finalReject && finalAdmitted && !finalStop && !fallback->closing)
{
fallback->mismatchReported = false;
atomic_store_explicit(&fallback->ready, true, memory_order_release);
}
const bool published = !finalReject && finalAdmitted && !finalStop &&
!fallback->closing;
LG_UNLOCK_EXCLUSIVE(fallback->lock);
return !finalReject && !finalStop;
return published;
}
static bool connectFallback(LG_TransportFallback * fallback)
@@ -315,8 +399,9 @@ static bool connectFallback(LG_TransportFallback * fallback)
}
LG_LOCK_EXCLUSIVE(fallback->lock);
fallback->connected = true;
fallback->session = session;
fallback->connected = true;
fallback->session = session;
fallback->disconnectedReported = false;
++fallback->connectionSerial;
LG_UNLOCK_EXCLUSIVE(fallback->lock);
@@ -333,13 +418,7 @@ static bool connectFallback(LG_TransportFallback * fallback)
return false;
}
LG_LOCK_EXCLUSIVE(fallback->lock);
fallback->connectedReported = true;
const LG_TransportSession reportedSession = fallback->session;
LG_UNLOCK_EXCLUSIVE(fallback->lock);
if (fallback->eventOps.connected)
fallback->eventOps.connected(
fallback->eventOpaque, &reportedSession);
notifyConnected(fallback);
while (!atomic_load_explicit(&fallback->stop, memory_order_acquire))
{
@@ -351,8 +430,10 @@ static bool connectFallback(LG_TransportFallback * fallback)
if (reject)
reportMismatch = recordMismatchLocked(
fallback, primaryUUID, fallbackUUID);
const bool admitted = uuidAdmittedLocked(fallback);
const bool closing = fallback->closing;
LG_UNLOCK_EXCLUSIVE(fallback->lock);
if (reject)
if (reject || !admitted || closing)
break;
if (!sessionLive(fallback))
break;
@@ -365,8 +446,7 @@ static bool connectFallback(LG_TransportFallback * fallback)
if (reportMismatch && fallback->eventOps.uuidMismatch)
fallback->eventOps.uuidMismatch(
fallback->eventOpaque, primaryUUID, fallbackUUID);
if (reportDisconnect && fallback->eventOps.disconnected)
fallback->eventOps.disconnected(fallback->eventOpaque);
notifyDisconnected(fallback, reportDisconnect);
return true;
}
@@ -388,14 +468,14 @@ static int fallbackThread(void * opaque)
}
const bool reportDisconnect = cleanupConnection(fallback, false);
if (reportDisconnect && fallback->eventOps.disconnected)
fallback->eventOps.disconnected(fallback->eventOpaque);
notifyDisconnected(fallback, reportDisconnect);
return 0;
}
bool lgTransportFallback_start(const char * transportName,
const LG_SwSurfaceEventOps * surfaceEvents, void * surfaceOpaque,
const LG_TransportFallbackEventOps * eventOps, void * eventOpaque,
const uint8_t primaryUUID[16],
LG_TransportFallback ** result)
{
if (!result)
@@ -411,8 +491,11 @@ bool lgTransportFallback_start(const char * transportName,
return false;
LG_RWLOCK_INIT(fallback->lock);
LG_LOCK_INIT(fallback->providerLock);
LG_LOCK_INIT(fallback->eventLock);
atomic_init(&fallback->stop, false);
atomic_init(&fallback->ready, false);
atomic_init(&fallback->admitted, false);
fallback->transportName = strdup(transportName);
if (!fallback->transportName)
@@ -423,6 +506,12 @@ bool lgTransportFallback_start(const char * transportName,
if (eventOps)
fallback->eventOps = *eventOps;
fallback->eventOpaque = eventOpaque;
if (primaryUUID)
{
memcpy(fallback->primaryUUID, primaryUUID,
sizeof(fallback->primaryUUID));
fallback->primaryUUIDValid = true;
}
fallback->wakeEvent = lgCreateEvent(true, 0);
if (!fallback->wakeEvent)
@@ -439,6 +528,8 @@ fail:
*result = NULL;
if (fallback->wakeEvent)
lgFreeEvent(fallback->wakeEvent);
LG_LOCK_FREE(fallback->eventLock);
LG_LOCK_FREE(fallback->providerLock);
LG_RWLOCK_FREE(fallback->lock);
free(fallback->transportName);
free(fallback);
@@ -465,6 +556,8 @@ void lgTransportFallback_stop(LG_TransportFallback ** fallbackPtr)
}
lgFreeEvent(fallback->wakeEvent);
LG_LOCK_FREE(fallback->eventLock);
LG_LOCK_FREE(fallback->providerLock);
LG_RWLOCK_FREE(fallback->lock);
free(fallback->transportName);
free(fallback);
@@ -476,6 +569,24 @@ bool lgTransportFallback_ready(const LG_TransportFallback * fallback)
&fallback->ready, memory_order_acquire);
}
bool lgTransportFallback_admitted(const LG_TransportFallback * fallback)
{
return fallback && atomic_load_explicit(
&fallback->admitted, memory_order_acquire);
}
bool lgTransportFallback_videoRequested(
LG_TransportFallback * fallback)
{
if (!fallback)
return false;
LG_LOCK_SHARED(fallback->lock);
const bool requested = fallback->videoRequested;
LG_UNLOCK_SHARED(fallback->lock);
return requested;
}
static bool applyVideoRequest(LG_TransportFallback * fallback)
{
for (;;)
@@ -533,12 +644,30 @@ void lgTransportFallback_requestVideoActive(
lgSignalEvent(fallback->wakeEvent);
}
static bool revokeAdmissionLocked(LG_TransportFallback * fallback)
{
atomic_store_explicit(&fallback->ready, false, memory_order_release);
atomic_store_explicit(&fallback->admitted, false, memory_order_release);
if (!fallback->connected)
return false;
fallback->connectedReported = false;
fallback->closing = true;
return true;
}
void lgTransportFallback_setPrimaryUUID(
LG_TransportFallback * fallback, const uint8_t uuid[16])
{
if (!fallback || !uuid)
return;
bool revoked = false;
bool notifyDisconnect = false;
bool reportMismatch = false;
uint8_t primaryUUID[16];
uint8_t fallbackUUID[16];
LG_LOCK_EXCLUSIVE(fallback->lock);
const bool changed = !fallback->primaryUUIDValid ||
memcmp(fallback->primaryUUID, uuid,
@@ -547,11 +676,24 @@ void lgTransportFallback_setPrimaryUUID(
{
memcpy(fallback->primaryUUID, uuid, sizeof(fallback->primaryUUID));
fallback->primaryUUIDValid = true;
if (uuidMismatchLocked(fallback))
{
revoked = true;
notifyDisconnect = revokeAdmissionLocked(fallback);
reportMismatch = recordMismatchLocked(
fallback, primaryUUID, fallbackUUID);
}
}
LG_UNLOCK_EXCLUSIVE(fallback->lock);
if (changed)
lgSignalEvent(fallback->wakeEvent);
if (revoked)
unpublishProviders(fallback, false);
notifyDisconnected(fallback, notifyDisconnect);
if (reportMismatch && fallback->eventOps.uuidMismatch)
fallback->eventOps.uuidMismatch(
fallback->eventOpaque, primaryUUID, fallbackUUID);
}
void lgTransportFallback_clearPrimaryUUID(
@@ -562,7 +704,11 @@ void lgTransportFallback_clearPrimaryUUID(
LG_LOCK_EXCLUSIVE(fallback->lock);
const bool changed = fallback->primaryUUIDValid;
fallback->primaryUUIDValid = false;
if (changed)
{
fallback->primaryUUIDValid = false;
fallback->mismatchReported = false;
}
LG_UNLOCK_EXCLUSIVE(fallback->lock);
if (changed)

View File

@@ -41,10 +41,14 @@ LG_TransportFallbackEventOps;
bool lgTransportFallback_start(const char * transportName,
const LG_SwSurfaceEventOps * surfaceEvents, void * surfaceOpaque,
const LG_TransportFallbackEventOps * eventOps, void * eventOpaque,
const uint8_t primaryUUID[16],
LG_TransportFallback ** result);
void lgTransportFallback_stop(LG_TransportFallback ** fallback);
bool lgTransportFallback_ready(const LG_TransportFallback * fallback);
bool lgTransportFallback_admitted(const LG_TransportFallback * fallback);
bool lgTransportFallback_videoRequested(
LG_TransportFallback * fallback);
/* The requested state is retained across reconnects. */
void lgTransportFallback_requestVideoActive(
LG_TransportFallback * fallback, bool active);