mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 07:01:30 +00:00
[client] transport: manage persistent fallback sessions
This commit is contained in:
@@ -187,6 +187,7 @@ set(SOURCES
|
||||
src/render_queue.c
|
||||
src/evdev.c
|
||||
src/transport.c
|
||||
src/transport_fallback.c
|
||||
src/input.c
|
||||
|
||||
src/overlay/splash.c
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
typedef struct LG_Transport LG_Transport;
|
||||
typedef struct LG_RendererInterop LG_RendererInterop;
|
||||
|
||||
typedef bool (*LG_TransportCancelledFn)(void * opaque);
|
||||
|
||||
typedef enum LG_TransportStatus
|
||||
{
|
||||
LG_TRANSPORT_OK,
|
||||
@@ -320,6 +322,11 @@ typedef struct LG_TransportOps
|
||||
|
||||
LG_TransportStatus (*connect)(LG_Transport * transport,
|
||||
LG_TransportSession * session);
|
||||
/* Optional cancellable form used by transports which may wait while
|
||||
* establishing a session. */
|
||||
LG_TransportStatus (*connectCancellable)(LG_Transport * transport,
|
||||
LG_TransportSession * session, LG_TransportCancelledFn cancelled,
|
||||
void * opaque);
|
||||
void (*disconnect)(LG_Transport * transport);
|
||||
bool (*sessionValid)(LG_Transport * transport);
|
||||
/* Queried after create. The returned operations remain valid until the
|
||||
|
||||
@@ -1151,16 +1151,14 @@ void app_stopVideo(bool stop)
|
||||
|
||||
bool app_useSpiceDisplay(bool enable)
|
||||
{
|
||||
if (!g_params.useSpice || !g_state.fallbackVideoOps ||
|
||||
g_state.fallbackVideoOps->type != LG_VIDEO_TYPE_SW_SURFACE ||
|
||||
!g_state.fallbackVideoOps->swSurface)
|
||||
if (!g_params.useSpice || !g_state.fallback)
|
||||
return false;
|
||||
|
||||
atomic_store_explicit(&g_state.fallbackDisplayRequested, enable,
|
||||
memory_order_release);
|
||||
|
||||
// if the fallback is not yet ready, retain the requested state
|
||||
if (!atomic_load_explicit(&g_state.fallbackReady, memory_order_acquire))
|
||||
if (!lgTransportFallback_ready(g_state.fallback))
|
||||
return false;
|
||||
|
||||
bool active = atomic_load_explicit(&g_state.fallbackDisplayActive,
|
||||
@@ -1189,8 +1187,7 @@ bool app_useSpiceDisplay(bool enable)
|
||||
if (active == enable)
|
||||
goto done;
|
||||
|
||||
if (!g_state.fallbackVideoOps->swSurface->setActive(
|
||||
g_state.fallbackTransport.handle, enable))
|
||||
if (!lgTransportFallback_setVideoActive(g_state.fallback, enable))
|
||||
goto fail;
|
||||
|
||||
renderQueue_swSurfaceShow(enable);
|
||||
@@ -1215,6 +1212,7 @@ done:
|
||||
fail:
|
||||
DEBUG_ERROR("Failed to %s the SPICE display",
|
||||
enable ? "enable" : "disable");
|
||||
lgTransportFallback_setVideoActive(g_state.fallback, active);
|
||||
atomic_store_explicit(&g_state.fallbackDisplayRequested, active,
|
||||
memory_order_release);
|
||||
goto done;
|
||||
|
||||
@@ -1508,8 +1508,6 @@ static void fallbackSurfaceDestroy(void * opaque)
|
||||
{
|
||||
(void)opaque;
|
||||
g_state.fallbackSurfaceValid = false;
|
||||
atomic_store_explicit(&g_state.fallbackDisplayRequested, false,
|
||||
memory_order_release);
|
||||
if (atomic_exchange_explicit(&g_state.fallbackDisplayActive, false,
|
||||
memory_order_acq_rel))
|
||||
{
|
||||
@@ -1542,7 +1540,7 @@ static void fallbackSurfacePointer(void * opaque,
|
||||
if (pointer->flags & LG_TRANSPORT_POINTER_SHAPE)
|
||||
{
|
||||
LG_RendererCursor type;
|
||||
switch(pointer->type)
|
||||
switch (pointer->type)
|
||||
{
|
||||
case CURSOR_TYPE_COLOR:
|
||||
type = LG_CURSOR_COLOR;
|
||||
@@ -1585,137 +1583,73 @@ static const LG_SwSurfaceEventOps fallbackSurfaceEvents =
|
||||
.pointer = fallbackSurfacePointer,
|
||||
};
|
||||
|
||||
static void fallbackDisconnect(void)
|
||||
static void fallbackConnected(void * opaque,
|
||||
const LG_TransportSession * session)
|
||||
{
|
||||
if (!g_state.fallbackTransport.ops)
|
||||
return;
|
||||
(void)opaque;
|
||||
if (session->name[0] && !atomic_load_explicit(
|
||||
&g_state.lgHostConnected, memory_order_acquire))
|
||||
core_setTitle(session->name);
|
||||
|
||||
const bool connected = g_state.fallbackTransport.handle &&
|
||||
g_state.fallbackTransport.ops->sessionValid(
|
||||
g_state.fallbackTransport.handle);
|
||||
|
||||
atomic_store_explicit(
|
||||
&g_state.fallbackReady, false, memory_order_release);
|
||||
|
||||
if (connected)
|
||||
{
|
||||
lgInput_setFallback(NULL, NULL);
|
||||
lgAudio_setFallback(NULL, NULL);
|
||||
lgClipboard_setFallback(NULL, NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
lgInput_dropFallback();
|
||||
lgAudio_dropFallback();
|
||||
lgClipboard_dropFallback();
|
||||
}
|
||||
|
||||
if (g_state.fallbackVideoOps &&
|
||||
g_state.fallbackVideoOps->type == LG_VIDEO_TYPE_SW_SURFACE &&
|
||||
g_state.fallbackVideoOps->swSurface)
|
||||
{
|
||||
if (connected)
|
||||
g_state.fallbackVideoOps->swSurface->setActive(
|
||||
g_state.fallbackTransport.handle, false);
|
||||
g_state.fallbackVideoOps->swSurface->detach(
|
||||
g_state.fallbackTransport.handle);
|
||||
}
|
||||
|
||||
if (atomic_exchange_explicit(&g_state.fallbackDisplayActive, false,
|
||||
memory_order_acq_rel))
|
||||
{
|
||||
renderQueue_swSurfaceShow(false);
|
||||
lgInput_useTransport(true);
|
||||
overlayStatus_set(LG_USER_STATUS_SPICE, false);
|
||||
}
|
||||
|
||||
g_state.fallbackTransport.ops->disconnect(
|
||||
g_state.fallbackTransport.handle);
|
||||
lgTransport_destroy(&g_state.fallbackTransport);
|
||||
g_state.fallbackVideoOps = NULL;
|
||||
g_state.fallbackSurfaceValid = false;
|
||||
if (atomic_load_explicit(&g_state.fallbackDisplayRequested,
|
||||
memory_order_acquire))
|
||||
app_useSpiceDisplay(true);
|
||||
}
|
||||
|
||||
static bool fallbackConnect(void)
|
||||
static void fallbackDisconnected(void * opaque)
|
||||
{
|
||||
fallbackSurfaceDestroy(opaque);
|
||||
if (!atomic_load_explicit(
|
||||
&g_state.lgHostConnected, memory_order_acquire))
|
||||
overlaySplash_show(true);
|
||||
}
|
||||
|
||||
static void fallbackUUIDMismatch(void * opaque, const uint8_t primary[16],
|
||||
const uint8_t fallback[16])
|
||||
{
|
||||
(void)opaque;
|
||||
(void)primary;
|
||||
(void)fallback;
|
||||
atomic_store_explicit(
|
||||
&g_state.fallbackUUIDMismatch, true, memory_order_release);
|
||||
app_invalidateWindow(false);
|
||||
}
|
||||
|
||||
static const LG_TransportFallbackEventOps fallbackEvents =
|
||||
{
|
||||
.connected = fallbackConnected,
|
||||
.disconnected = fallbackDisconnected,
|
||||
.uuidMismatch = fallbackUUIDMismatch,
|
||||
};
|
||||
|
||||
static bool fallbackStart(void)
|
||||
{
|
||||
if (!g_params.useSpice || strcmp(g_params.transport, "spice") == 0)
|
||||
return true;
|
||||
|
||||
if (!lgTransport_create("spice", &g_state.fallbackTransport))
|
||||
{
|
||||
DEBUG_ERROR("Failed to create the SPICE fallback transport");
|
||||
return false;
|
||||
}
|
||||
if (lgTransportFallback_start("spice", &fallbackSurfaceEvents, NULL,
|
||||
&fallbackEvents, NULL, &g_state.fallback))
|
||||
return true;
|
||||
|
||||
g_state.fallbackVideoOps =
|
||||
g_state.fallbackTransport.ops->getVideoOps(
|
||||
g_state.fallbackTransport.handle);
|
||||
if (!g_state.fallbackVideoOps ||
|
||||
g_state.fallbackVideoOps->type != LG_VIDEO_TYPE_SW_SURFACE ||
|
||||
!g_state.fallbackVideoOps->swSurface ||
|
||||
!g_state.fallbackVideoOps->swSurface->attach(
|
||||
g_state.fallbackTransport.handle, &fallbackSurfaceEvents, NULL))
|
||||
{
|
||||
DEBUG_ERROR("SPICE does not provide a software surface");
|
||||
fallbackDisconnect();
|
||||
return false;
|
||||
}
|
||||
|
||||
const LG_TransportStatus status =
|
||||
g_state.fallbackTransport.ops->connect(
|
||||
g_state.fallbackTransport.handle, &g_state.fallbackSession);
|
||||
if (status != LG_TRANSPORT_OK)
|
||||
{
|
||||
DEBUG_ERROR("Failed to connect the SPICE fallback transport: %d", status);
|
||||
fallbackDisconnect();
|
||||
return false;
|
||||
}
|
||||
|
||||
void * inputOpaque = NULL;
|
||||
const LG_InputOps * inputOps =
|
||||
g_state.fallbackTransport.ops->getInputOps ?
|
||||
g_state.fallbackTransport.ops->getInputOps(
|
||||
g_state.fallbackTransport.handle, &inputOpaque) : NULL;
|
||||
lgInput_setFallback(inputOps, inputOpaque);
|
||||
|
||||
void * audioOpaque = NULL;
|
||||
const LG_AudioOps * audioOps =
|
||||
g_state.fallbackTransport.ops->getAudioOps ?
|
||||
g_state.fallbackTransport.ops->getAudioOps(
|
||||
g_state.fallbackTransport.handle, &audioOpaque) : NULL;
|
||||
lgAudio_setFallback(audioOps, audioOpaque);
|
||||
|
||||
void * clipboardOpaque = NULL;
|
||||
const LG_ClipboardOps * clipboardOps =
|
||||
g_state.fallbackTransport.ops->getClipboardOps ?
|
||||
g_state.fallbackTransport.ops->getClipboardOps(
|
||||
g_state.fallbackTransport.handle, &clipboardOpaque) : NULL;
|
||||
lgClipboard_setFallback(clipboardOps, clipboardOpaque);
|
||||
|
||||
atomic_store_explicit(
|
||||
&g_state.fallbackReady, true, memory_order_release);
|
||||
if (g_state.fallbackSession.name[0])
|
||||
core_setTitle(g_state.fallbackSession.name);
|
||||
if (inputOps)
|
||||
keybind_inputRegister();
|
||||
return true;
|
||||
DEBUG_ERROR("Failed to start the SPICE fallback transport");
|
||||
return false;
|
||||
}
|
||||
|
||||
static void checkUUID(void)
|
||||
static void fallbackStop(void)
|
||||
{
|
||||
if (!atomic_load_explicit(&g_state.fallbackReady, memory_order_acquire) ||
|
||||
!g_state.fallbackSession.uuidValid || !g_state.guestUUIDValid)
|
||||
return;
|
||||
lgTransportFallback_stop(&g_state.fallback);
|
||||
}
|
||||
|
||||
if (memcmp(g_state.fallbackSession.uuid, g_state.guestUUID,
|
||||
sizeof(g_state.guestUUID)) == 0)
|
||||
static void fallbackHandleEvents(void)
|
||||
{
|
||||
if (!atomic_exchange_explicit(
|
||||
&g_state.fallbackUUIDMismatch, false, memory_order_acq_rel))
|
||||
return;
|
||||
|
||||
app_msgBox(
|
||||
"SPICE Configuration Error",
|
||||
"You have connected SPICE to the wrong guest.\n"
|
||||
"SPICE fallback services will not function until this is corrected.");
|
||||
fallbackDisconnect();
|
||||
}
|
||||
|
||||
void intHandler(int sig)
|
||||
@@ -1965,7 +1899,7 @@ static int lg_run(void)
|
||||
|
||||
g_state.micDefaultState = g_params.micDefaultState;
|
||||
|
||||
if (!fallbackConnect())
|
||||
if (!fallbackStart())
|
||||
return -1;
|
||||
|
||||
// select and init a renderer
|
||||
@@ -2072,6 +2006,7 @@ static int lg_run(void)
|
||||
}
|
||||
|
||||
keybind_commonRegister();
|
||||
keybind_inputRegister();
|
||||
|
||||
if (g_state.jitRender)
|
||||
DEBUG_INFO("Using JIT render mode");
|
||||
@@ -2127,15 +2062,7 @@ restart:
|
||||
|
||||
while(app_getState() == APP_STATE_RUNNING)
|
||||
{
|
||||
if (atomic_load_explicit(
|
||||
&g_state.fallbackReady, memory_order_acquire) &&
|
||||
!g_state.fallbackTransport.ops->sessionValid(
|
||||
g_state.fallbackTransport.handle))
|
||||
{
|
||||
DEBUG_ERROR("SPICE fallback transport disconnected");
|
||||
app_setState(APP_STATE_SHUTDOWN);
|
||||
break;
|
||||
}
|
||||
fallbackHandleEvents();
|
||||
|
||||
if (initialSpiceEnable && microtime() > initialSpiceEnable)
|
||||
{
|
||||
@@ -2158,7 +2085,10 @@ restart:
|
||||
|
||||
while (app_getState() == APP_STATE_RUNNING &&
|
||||
!atomic_load_explicit(&probe.done, memory_order_acquire))
|
||||
{
|
||||
g_state.ds->wait(100);
|
||||
fallbackHandleEvents();
|
||||
}
|
||||
|
||||
if (!lgJoinThread(probeThread, NULL))
|
||||
{
|
||||
@@ -2274,7 +2204,13 @@ restart:
|
||||
g_state.guestOS = session.os;
|
||||
g_state.guestUUIDValid = session.uuidValid;
|
||||
if (session.uuidValid)
|
||||
{
|
||||
memcpy(g_state.guestUUID, session.uuid, sizeof(g_state.guestUUID));
|
||||
lgTransportFallback_setPrimaryUUID(
|
||||
g_state.fallback, g_state.guestUUID);
|
||||
}
|
||||
else
|
||||
lgTransportFallback_clearPrimaryUUID(g_state.fallback);
|
||||
g_state.transportFeatures = session.features;
|
||||
frameScheduler_start(session.features);
|
||||
|
||||
@@ -2297,9 +2233,6 @@ restart:
|
||||
g_state.transport.handle, &clipboardOpaque) : NULL;
|
||||
lgClipboard_setTransport(clipboardOps, clipboardOpaque);
|
||||
|
||||
if (inputOps || lgInput_available())
|
||||
keybind_inputRegister();
|
||||
checkUUID();
|
||||
DEBUG_INFO("Starting session");
|
||||
atomic_store_explicit(
|
||||
&g_state.lgHostConnected, true, memory_order_release);
|
||||
@@ -2311,15 +2244,7 @@ restart:
|
||||
|
||||
while(likely(app_getState() == APP_STATE_RUNNING))
|
||||
{
|
||||
if (atomic_load_explicit(
|
||||
&g_state.fallbackReady, memory_order_acquire) &&
|
||||
!g_state.fallbackTransport.ops->sessionValid(
|
||||
g_state.fallbackTransport.handle))
|
||||
{
|
||||
DEBUG_ERROR("SPICE fallback transport disconnected");
|
||||
app_setState(APP_STATE_SHUTDOWN);
|
||||
break;
|
||||
}
|
||||
fallbackHandleEvents();
|
||||
|
||||
if (unlikely(!g_state.transport.ops->sessionValid(
|
||||
g_state.transport.handle)))
|
||||
@@ -2342,6 +2267,11 @@ restart:
|
||||
|
||||
if (app_getState() == APP_STATE_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);
|
||||
|
||||
@@ -2368,8 +2298,6 @@ static void lg_shutdown(void)
|
||||
if (e_cursorRepaint)
|
||||
lgSignalEvent(e_cursorRepaint);
|
||||
|
||||
fallbackDisconnect();
|
||||
|
||||
if (t_render)
|
||||
{
|
||||
if (g_state.jitRender && g_state.ds->stopWaitFrame)
|
||||
@@ -2379,6 +2307,8 @@ static void lg_shutdown(void)
|
||||
lgJoinThread(t_render, NULL);
|
||||
}
|
||||
|
||||
fallbackStop();
|
||||
|
||||
if (t_cursorRepaint)
|
||||
{
|
||||
lgJoinThread(t_cursorRepaint, NULL);
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
|
||||
#include "cimgui.h"
|
||||
#include "interface/transport.h"
|
||||
#include "transport_fallback.h"
|
||||
|
||||
enum RunState
|
||||
{
|
||||
@@ -78,10 +79,10 @@ struct AppState
|
||||
bool dsInitialized;
|
||||
bool jitRender;
|
||||
|
||||
atomic_bool fallbackReady;
|
||||
atomic_bool fallbackDisplayRequested;
|
||||
atomic_bool fallbackDisplayActive;
|
||||
atomic_bool fallbackDisplayTransition;
|
||||
atomic_bool fallbackUUIDMismatch;
|
||||
bool fallbackSurfaceValid;
|
||||
|
||||
uint8_t guestUUID[16];
|
||||
@@ -123,9 +124,7 @@ struct AppState
|
||||
const LG_VideoOps * videoOps;
|
||||
LG_TransportFeatureFlags transportFeatures;
|
||||
|
||||
LG_TransportInstance fallbackTransport;
|
||||
LG_TransportSession fallbackSession;
|
||||
const LG_VideoOps * fallbackVideoOps;
|
||||
LG_TransportFallback * fallback;
|
||||
|
||||
LGThread * cursorThread;
|
||||
LGThread * frameThread;
|
||||
|
||||
582
client/src/transport_fallback.c
Normal file
582
client/src/transport_fallback.c
Normal file
@@ -0,0 +1,582 @@
|
||||
/**
|
||||
* 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 "transport_fallback.h"
|
||||
|
||||
#include "audio.h"
|
||||
#include "clipboard.h"
|
||||
#include "input.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/event.h"
|
||||
#include "common/locking.h"
|
||||
#include "common/thread.h"
|
||||
#include "common/util.h"
|
||||
|
||||
#include <stdatomic.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define RETRY_INITIAL_MS 250U
|
||||
#define RETRY_MAX_MS 5000U
|
||||
#define SESSION_POLL_MS 100U
|
||||
|
||||
struct LG_TransportFallback
|
||||
{
|
||||
char * transportName;
|
||||
|
||||
LG_SwSurfaceEventOps surfaceEvents;
|
||||
void * surfaceOpaque;
|
||||
LG_TransportFallbackEventOps eventOps;
|
||||
void * eventOpaque;
|
||||
|
||||
LGThread * thread;
|
||||
LGEvent * wakeEvent;
|
||||
LGEvent * videoIdleEvent;
|
||||
LG_RWLock lock;
|
||||
|
||||
atomic_bool stop;
|
||||
atomic_bool ready;
|
||||
|
||||
LG_TransportInstance transport;
|
||||
LG_TransportSession session;
|
||||
const LG_VideoOps * videoOps;
|
||||
uint64_t connectionSerial;
|
||||
bool attached;
|
||||
bool connected;
|
||||
bool providersPublished;
|
||||
bool connectedReported;
|
||||
bool closing;
|
||||
bool videoRequested;
|
||||
bool videoActive;
|
||||
bool videoBusy;
|
||||
|
||||
bool primaryUUIDValid;
|
||||
uint8_t primaryUUID[16];
|
||||
|
||||
bool mismatchReported;
|
||||
uint8_t mismatchPrimary[16];
|
||||
uint8_t mismatchFallback[16];
|
||||
};
|
||||
|
||||
static bool connectCancelled(void * opaque)
|
||||
{
|
||||
const LG_TransportFallback * fallback = opaque;
|
||||
return atomic_load_explicit(&fallback->stop, memory_order_acquire);
|
||||
}
|
||||
|
||||
static bool uuidMismatchLocked(const LG_TransportFallback * fallback)
|
||||
{
|
||||
return fallback->primaryUUIDValid && fallback->session.uuidValid &&
|
||||
memcmp(fallback->primaryUUID, fallback->session.uuid,
|
||||
sizeof(fallback->primaryUUID)) != 0;
|
||||
}
|
||||
|
||||
static bool recordMismatchLocked(LG_TransportFallback * fallback,
|
||||
uint8_t primary[16], uint8_t remote[16])
|
||||
{
|
||||
if (!uuidMismatchLocked(fallback))
|
||||
return false;
|
||||
|
||||
const bool duplicate = fallback->mismatchReported &&
|
||||
memcmp(fallback->mismatchPrimary, fallback->primaryUUID,
|
||||
sizeof(fallback->mismatchPrimary)) == 0 &&
|
||||
memcmp(fallback->mismatchFallback, fallback->session.uuid,
|
||||
sizeof(fallback->mismatchFallback)) == 0;
|
||||
if (duplicate)
|
||||
return false;
|
||||
|
||||
memcpy(fallback->mismatchPrimary, fallback->primaryUUID,
|
||||
sizeof(fallback->mismatchPrimary));
|
||||
memcpy(fallback->mismatchFallback, fallback->session.uuid,
|
||||
sizeof(fallback->mismatchFallback));
|
||||
memcpy(primary, fallback->primaryUUID, sizeof(fallback->primaryUUID));
|
||||
memcpy(remote, fallback->session.uuid, sizeof(fallback->session.uuid));
|
||||
fallback->mismatchReported = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool sessionLive(const LG_TransportFallback * fallback)
|
||||
{
|
||||
return fallback->connected && fallback->transport.ops &&
|
||||
fallback->transport.ops->sessionValid(fallback->transport.handle);
|
||||
}
|
||||
|
||||
static void unpublishProviders(LG_TransportFallback * fallback, bool live)
|
||||
{
|
||||
if (!fallback->providersPublished)
|
||||
return;
|
||||
|
||||
if (live)
|
||||
{
|
||||
lgInput_setFallback(NULL, NULL);
|
||||
lgAudio_setFallback(NULL, NULL);
|
||||
lgClipboard_setFallback(NULL, NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
lgInput_dropFallback();
|
||||
lgAudio_dropFallback();
|
||||
lgClipboard_dropFallback();
|
||||
}
|
||||
fallback->providersPublished = false;
|
||||
}
|
||||
|
||||
static void waitForVideoIdle(LG_TransportFallback * fallback)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
LG_LOCK_EXCLUSIVE(fallback->lock);
|
||||
atomic_store_explicit(&fallback->ready, false, memory_order_release);
|
||||
fallback->closing = true;
|
||||
const bool busy = fallback->videoBusy;
|
||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||
|
||||
if (!busy)
|
||||
return;
|
||||
lgWaitEvent(fallback->videoIdleEvent, TIMEOUT_INFINITE);
|
||||
}
|
||||
}
|
||||
|
||||
static bool cleanupConnection(LG_TransportFallback * fallback,
|
||||
bool knownDead)
|
||||
{
|
||||
waitForVideoIdle(fallback);
|
||||
|
||||
LG_LOCK_EXCLUSIVE(fallback->lock);
|
||||
const bool reportDisconnected = fallback->connectedReported;
|
||||
fallback->connectedReported = false;
|
||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||
|
||||
const bool live = !knownDead && sessionLive(fallback);
|
||||
unpublishProviders(fallback, live);
|
||||
|
||||
if (fallback->attached)
|
||||
{
|
||||
if (live && fallback->videoActive)
|
||||
fallback->videoOps->swSurface->setActive(
|
||||
fallback->transport.handle, false);
|
||||
fallback->videoActive = false;
|
||||
fallback->videoOps->swSurface->detach(fallback->transport.handle);
|
||||
fallback->attached = false;
|
||||
}
|
||||
|
||||
if (fallback->transport.ops)
|
||||
fallback->transport.ops->disconnect(fallback->transport.handle);
|
||||
lgTransport_destroy(&fallback->transport);
|
||||
|
||||
LG_LOCK_EXCLUSIVE(fallback->lock);
|
||||
fallback->session = (LG_TransportSession) { 0 };
|
||||
fallback->videoOps = NULL;
|
||||
fallback->connected = false;
|
||||
fallback->closing = false;
|
||||
++fallback->connectionSerial;
|
||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||
return reportDisconnected;
|
||||
}
|
||||
|
||||
static void publishProviders(LG_TransportFallback * fallback)
|
||||
{
|
||||
void * inputOpaque = NULL;
|
||||
const LG_InputOps * inputOps = fallback->transport.ops->getInputOps ?
|
||||
fallback->transport.ops->getInputOps(
|
||||
fallback->transport.handle, &inputOpaque) : NULL;
|
||||
lgInput_setFallback(inputOps, inputOpaque);
|
||||
|
||||
void * audioOpaque = NULL;
|
||||
const LG_AudioOps * audioOps = fallback->transport.ops->getAudioOps ?
|
||||
fallback->transport.ops->getAudioOps(
|
||||
fallback->transport.handle, &audioOpaque) : NULL;
|
||||
lgAudio_setFallback(audioOps, audioOpaque);
|
||||
|
||||
void * clipboardOpaque = NULL;
|
||||
const LG_ClipboardOps * clipboardOps =
|
||||
fallback->transport.ops->getClipboardOps ?
|
||||
fallback->transport.ops->getClipboardOps(
|
||||
fallback->transport.handle, &clipboardOpaque) : NULL;
|
||||
lgClipboard_setFallback(clipboardOps, clipboardOpaque);
|
||||
fallback->providersPublished = true;
|
||||
}
|
||||
|
||||
static bool publishConnection(LG_TransportFallback * fallback,
|
||||
bool * reportMismatch, uint8_t primaryUUID[16],
|
||||
uint8_t fallbackUUID[16])
|
||||
{
|
||||
LG_LOCK_EXCLUSIVE(fallback->lock);
|
||||
const bool reject = uuidMismatchLocked(fallback);
|
||||
if (reject)
|
||||
*reportMismatch = recordMismatchLocked(
|
||||
fallback, primaryUUID, fallbackUUID);
|
||||
const bool stop = atomic_load_explicit(
|
||||
&fallback->stop, memory_order_acquire);
|
||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||
if (reject || stop)
|
||||
return false;
|
||||
|
||||
publishProviders(fallback);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
LG_LOCK_EXCLUSIVE(fallback->lock);
|
||||
const bool reject = uuidMismatchLocked(fallback);
|
||||
if (reject)
|
||||
*reportMismatch = recordMismatchLocked(
|
||||
fallback, primaryUUID, fallbackUUID);
|
||||
const bool stop = atomic_load_explicit(
|
||||
&fallback->stop, memory_order_acquire);
|
||||
const bool requested = fallback->videoRequested;
|
||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||
|
||||
if (reject || stop)
|
||||
return false;
|
||||
if (requested == fallback->videoActive)
|
||||
break;
|
||||
|
||||
if (!fallback->videoOps->swSurface->setActive(
|
||||
fallback->transport.handle, requested))
|
||||
break;
|
||||
fallback->videoActive = requested;
|
||||
}
|
||||
|
||||
LG_LOCK_EXCLUSIVE(fallback->lock);
|
||||
const bool finalReject = uuidMismatchLocked(fallback);
|
||||
if (finalReject)
|
||||
*reportMismatch = recordMismatchLocked(
|
||||
fallback, primaryUUID, fallbackUUID);
|
||||
const bool finalStop = atomic_load_explicit(
|
||||
&fallback->stop, memory_order_acquire);
|
||||
if (!finalReject && !finalStop)
|
||||
{
|
||||
fallback->mismatchReported = false;
|
||||
atomic_store_explicit(&fallback->ready, true, memory_order_release);
|
||||
}
|
||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||
return !finalReject && !finalStop;
|
||||
}
|
||||
|
||||
static bool connectFallback(LG_TransportFallback * fallback)
|
||||
{
|
||||
LG_TransportInstance transport = { 0 };
|
||||
if (!lgTransport_create(fallback->transportName, &transport))
|
||||
{
|
||||
DEBUG_ERROR("Failed to create fallback transport %s",
|
||||
fallback->transportName);
|
||||
return false;
|
||||
}
|
||||
|
||||
const LG_VideoOps * videoOps = transport.ops->getVideoOps ?
|
||||
transport.ops->getVideoOps(transport.handle) : NULL;
|
||||
if (!videoOps || videoOps->type != LG_VIDEO_TYPE_SW_SURFACE ||
|
||||
!videoOps->swSurface || !videoOps->swSurface->attach ||
|
||||
!videoOps->swSurface->detach || !videoOps->swSurface->setActive)
|
||||
{
|
||||
DEBUG_ERROR("Fallback transport %s does not provide a software surface",
|
||||
fallback->transportName);
|
||||
lgTransport_destroy(&transport);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!videoOps->swSurface->attach(transport.handle,
|
||||
&fallback->surfaceEvents, fallback->surfaceOpaque))
|
||||
{
|
||||
DEBUG_ERROR("Failed to attach fallback software surface");
|
||||
lgTransport_destroy(&transport);
|
||||
return false;
|
||||
}
|
||||
|
||||
LG_LOCK_EXCLUSIVE(fallback->lock);
|
||||
fallback->transport = transport;
|
||||
fallback->videoOps = videoOps;
|
||||
fallback->attached = true;
|
||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||
|
||||
LG_TransportSession session = { 0 };
|
||||
const LG_TransportStatus status = transport.ops->connectCancellable ?
|
||||
transport.ops->connectCancellable(transport.handle, &session,
|
||||
connectCancelled, fallback) :
|
||||
transport.ops->connect(transport.handle, &session);
|
||||
|
||||
if (status != LG_TRANSPORT_OK)
|
||||
{
|
||||
if (!atomic_load_explicit(&fallback->stop, memory_order_acquire))
|
||||
DEBUG_ERROR("Fallback transport %s failed to connect: %d",
|
||||
fallback->transportName, status);
|
||||
cleanupConnection(fallback, false);
|
||||
return false;
|
||||
}
|
||||
|
||||
LG_LOCK_EXCLUSIVE(fallback->lock);
|
||||
fallback->connected = true;
|
||||
fallback->session = session;
|
||||
++fallback->connectionSerial;
|
||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||
|
||||
bool reportMismatch = false;
|
||||
uint8_t primaryUUID[16];
|
||||
uint8_t fallbackUUID[16];
|
||||
if (!publishConnection(fallback, &reportMismatch,
|
||||
primaryUUID, fallbackUUID))
|
||||
{
|
||||
cleanupConnection(fallback, false);
|
||||
if (reportMismatch && fallback->eventOps.uuidMismatch)
|
||||
fallback->eventOps.uuidMismatch(
|
||||
fallback->eventOpaque, primaryUUID, fallbackUUID);
|
||||
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);
|
||||
|
||||
while (!atomic_load_explicit(&fallback->stop, memory_order_acquire))
|
||||
{
|
||||
lgWaitEvent(fallback->wakeEvent, SESSION_POLL_MS);
|
||||
|
||||
LG_LOCK_EXCLUSIVE(fallback->lock);
|
||||
const bool reject = uuidMismatchLocked(fallback);
|
||||
if (reject)
|
||||
reportMismatch = recordMismatchLocked(
|
||||
fallback, primaryUUID, fallbackUUID);
|
||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||
if (reject)
|
||||
break;
|
||||
if (!sessionLive(fallback))
|
||||
break;
|
||||
}
|
||||
|
||||
const bool knownDead = !atomic_load_explicit(
|
||||
&fallback->stop, memory_order_acquire) && !sessionLive(fallback);
|
||||
const bool reportDisconnect = cleanupConnection(fallback, knownDead);
|
||||
|
||||
if (reportMismatch && fallback->eventOps.uuidMismatch)
|
||||
fallback->eventOps.uuidMismatch(
|
||||
fallback->eventOpaque, primaryUUID, fallbackUUID);
|
||||
if (reportDisconnect && fallback->eventOps.disconnected)
|
||||
fallback->eventOps.disconnected(fallback->eventOpaque);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int fallbackThread(void * opaque)
|
||||
{
|
||||
LG_TransportFallback * fallback = opaque;
|
||||
unsigned int retry = RETRY_INITIAL_MS;
|
||||
|
||||
while (!atomic_load_explicit(&fallback->stop, memory_order_acquire))
|
||||
{
|
||||
if (connectFallback(fallback))
|
||||
retry = RETRY_INITIAL_MS;
|
||||
|
||||
if (atomic_load_explicit(&fallback->stop, memory_order_acquire))
|
||||
break;
|
||||
|
||||
lgWaitEvent(fallback->wakeEvent, retry);
|
||||
retry = min(retry * 2U, RETRY_MAX_MS);
|
||||
}
|
||||
|
||||
const bool reportDisconnect = cleanupConnection(fallback, false);
|
||||
if (reportDisconnect && fallback->eventOps.disconnected)
|
||||
fallback->eventOps.disconnected(fallback->eventOpaque);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool lgTransportFallback_start(const char * transportName,
|
||||
const LG_SwSurfaceEventOps * surfaceEvents, void * surfaceOpaque,
|
||||
const LG_TransportFallbackEventOps * eventOps, void * eventOpaque,
|
||||
LG_TransportFallback ** result)
|
||||
{
|
||||
if (!result)
|
||||
return false;
|
||||
|
||||
*result = NULL;
|
||||
if (!transportName || !*transportName || !surfaceEvents ||
|
||||
!lgTransport_isValid(transportName))
|
||||
return false;
|
||||
|
||||
LG_TransportFallback * fallback = calloc(1, sizeof(*fallback));
|
||||
if (!fallback)
|
||||
return false;
|
||||
|
||||
LG_RWLOCK_INIT(fallback->lock);
|
||||
atomic_init(&fallback->stop, false);
|
||||
atomic_init(&fallback->ready, false);
|
||||
|
||||
fallback->transportName = strdup(transportName);
|
||||
if (!fallback->transportName)
|
||||
goto fail;
|
||||
|
||||
fallback->surfaceEvents = *surfaceEvents;
|
||||
fallback->surfaceOpaque = surfaceOpaque;
|
||||
if (eventOps)
|
||||
fallback->eventOps = *eventOps;
|
||||
fallback->eventOpaque = eventOpaque;
|
||||
|
||||
fallback->wakeEvent = lgCreateEvent(true, 0);
|
||||
if (!fallback->wakeEvent)
|
||||
goto fail;
|
||||
|
||||
fallback->videoIdleEvent = lgCreateEvent(true, 0);
|
||||
if (!fallback->videoIdleEvent)
|
||||
goto fail;
|
||||
|
||||
*result = fallback;
|
||||
if (!lgCreateThread("transportFallback", fallbackThread,
|
||||
fallback, &fallback->thread))
|
||||
goto fail;
|
||||
|
||||
return true;
|
||||
|
||||
fail:
|
||||
*result = NULL;
|
||||
if (fallback->videoIdleEvent)
|
||||
lgFreeEvent(fallback->videoIdleEvent);
|
||||
if (fallback->wakeEvent)
|
||||
lgFreeEvent(fallback->wakeEvent);
|
||||
LG_RWLOCK_FREE(fallback->lock);
|
||||
free(fallback->transportName);
|
||||
free(fallback);
|
||||
return false;
|
||||
}
|
||||
|
||||
void lgTransportFallback_stop(LG_TransportFallback ** fallbackPtr)
|
||||
{
|
||||
if (!fallbackPtr || !*fallbackPtr)
|
||||
return;
|
||||
|
||||
LG_TransportFallback * fallback = *fallbackPtr;
|
||||
*fallbackPtr = NULL;
|
||||
|
||||
atomic_store_explicit(&fallback->stop, true, memory_order_release);
|
||||
lgSignalEvent(fallback->wakeEvent);
|
||||
|
||||
LGThread * thread = fallback->thread;
|
||||
fallback->thread = NULL;
|
||||
if (!lgJoinThread(thread, NULL))
|
||||
{
|
||||
DEBUG_ERROR("Failed to stop fallback transport worker; preserving state");
|
||||
return;
|
||||
}
|
||||
|
||||
lgFreeEvent(fallback->videoIdleEvent);
|
||||
lgFreeEvent(fallback->wakeEvent);
|
||||
LG_RWLOCK_FREE(fallback->lock);
|
||||
free(fallback->transportName);
|
||||
free(fallback);
|
||||
}
|
||||
|
||||
bool lgTransportFallback_ready(const LG_TransportFallback * fallback)
|
||||
{
|
||||
return fallback && atomic_load_explicit(
|
||||
&fallback->ready, memory_order_acquire);
|
||||
}
|
||||
|
||||
bool lgTransportFallback_setVideoActive(
|
||||
LG_TransportFallback * fallback, bool active)
|
||||
{
|
||||
if (!fallback)
|
||||
return false;
|
||||
|
||||
uint64_t connectionSerial;
|
||||
LG_Transport * transport;
|
||||
const LG_SwSurfaceOps * surfaceOps;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
LG_LOCK_EXCLUSIVE(fallback->lock);
|
||||
fallback->videoRequested = active;
|
||||
if (!atomic_load_explicit(&fallback->ready, memory_order_acquire) ||
|
||||
fallback->closing)
|
||||
{
|
||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fallback->videoActive == active)
|
||||
{
|
||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!fallback->videoBusy)
|
||||
{
|
||||
fallback->videoBusy = true;
|
||||
connectionSerial = fallback->connectionSerial;
|
||||
transport = fallback->transport.handle;
|
||||
surfaceOps = fallback->videoOps->swSurface;
|
||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||
break;
|
||||
}
|
||||
|
||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||
lgWaitEvent(fallback->videoIdleEvent, TIMEOUT_INFINITE);
|
||||
}
|
||||
|
||||
lgResetEvent(fallback->videoIdleEvent);
|
||||
const bool result = surfaceOps->setActive(transport, active);
|
||||
|
||||
LG_LOCK_EXCLUSIVE(fallback->lock);
|
||||
const bool current = fallback->connectionSerial == connectionSerial;
|
||||
if (current && result)
|
||||
fallback->videoActive = active;
|
||||
const bool accepted = current && !fallback->closing && result;
|
||||
fallback->videoBusy = false;
|
||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||
lgSignalEvent(fallback->videoIdleEvent);
|
||||
return accepted;
|
||||
}
|
||||
|
||||
void lgTransportFallback_setPrimaryUUID(
|
||||
LG_TransportFallback * fallback, const uint8_t uuid[16])
|
||||
{
|
||||
if (!fallback || !uuid)
|
||||
return;
|
||||
|
||||
LG_LOCK_EXCLUSIVE(fallback->lock);
|
||||
const bool changed = !fallback->primaryUUIDValid ||
|
||||
memcmp(fallback->primaryUUID, uuid,
|
||||
sizeof(fallback->primaryUUID)) != 0;
|
||||
if (changed)
|
||||
{
|
||||
memcpy(fallback->primaryUUID, uuid, sizeof(fallback->primaryUUID));
|
||||
fallback->primaryUUIDValid = true;
|
||||
}
|
||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||
|
||||
if (changed)
|
||||
lgSignalEvent(fallback->wakeEvent);
|
||||
}
|
||||
|
||||
void lgTransportFallback_clearPrimaryUUID(
|
||||
LG_TransportFallback * fallback)
|
||||
{
|
||||
if (!fallback)
|
||||
return;
|
||||
|
||||
LG_LOCK_EXCLUSIVE(fallback->lock);
|
||||
const bool changed = fallback->primaryUUIDValid;
|
||||
fallback->primaryUUIDValid = false;
|
||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||
|
||||
if (changed)
|
||||
lgSignalEvent(fallback->wakeEvent);
|
||||
}
|
||||
57
client/src/transport_fallback.h
Normal file
57
client/src/transport_fallback.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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_TRANSPORT_FALLBACK_
|
||||
#define _H_LG_CLIENT_TRANSPORT_FALLBACK_
|
||||
|
||||
#include "interface/transport.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct LG_TransportFallback LG_TransportFallback;
|
||||
|
||||
typedef struct LG_TransportFallbackEventOps
|
||||
{
|
||||
/* The session is valid only for the duration of this callback. */
|
||||
void (*connected)(void * opaque, const LG_TransportSession * session);
|
||||
void (*disconnected)(void * opaque);
|
||||
void (*uuidMismatch)(void * opaque, const uint8_t primary[16],
|
||||
const uint8_t fallback[16]);
|
||||
}
|
||||
LG_TransportFallbackEventOps;
|
||||
|
||||
bool lgTransportFallback_start(const char * transportName,
|
||||
const LG_SwSurfaceEventOps * surfaceEvents, void * surfaceOpaque,
|
||||
const LG_TransportFallbackEventOps * eventOps, void * eventOpaque,
|
||||
LG_TransportFallback ** result);
|
||||
void lgTransportFallback_stop(LG_TransportFallback ** fallback);
|
||||
|
||||
bool lgTransportFallback_ready(const LG_TransportFallback * fallback);
|
||||
/* The requested state is retained across reconnects. */
|
||||
bool lgTransportFallback_setVideoActive(
|
||||
LG_TransportFallback * fallback, bool active);
|
||||
|
||||
void lgTransportFallback_setPrimaryUUID(
|
||||
LG_TransportFallback * fallback, const uint8_t uuid[16]);
|
||||
void lgTransportFallback_clearPrimaryUUID(
|
||||
LG_TransportFallback * fallback);
|
||||
|
||||
#endif
|
||||
@@ -157,8 +157,9 @@ static void spiceDestroy(LG_Transport ** transport)
|
||||
*transport = NULL;
|
||||
}
|
||||
|
||||
static LG_TransportStatus spiceConnect(LG_Transport * transport,
|
||||
LG_TransportSession * session)
|
||||
static LG_TransportStatus spiceConnectCancellable(LG_Transport * transport,
|
||||
LG_TransportSession * session, LG_TransportCancelledFn cancelled,
|
||||
void * opaque)
|
||||
{
|
||||
if (transport->thread)
|
||||
return LG_TRANSPORT_ERROR;
|
||||
@@ -174,7 +175,24 @@ static LG_TransportStatus spiceConnect(LG_Transport * transport,
|
||||
"spiceProcess", spiceSession_thread, transport, &transport->thread))
|
||||
return LG_TRANSPORT_ERROR;
|
||||
|
||||
lgWaitEvent(transport->connectEvent, TIMEOUT_INFINITE);
|
||||
bool cancel = false;
|
||||
while (!lgWaitEvent(transport->connectEvent, 100))
|
||||
if (cancelled && cancelled(opaque))
|
||||
{
|
||||
cancel = true;
|
||||
atomic_store_explicit(&transport->stop, true, memory_order_release);
|
||||
lgWaitEvent(transport->connectEvent, TIMEOUT_INFINITE);
|
||||
break;
|
||||
}
|
||||
|
||||
if (cancel || (cancelled && cancelled(opaque)))
|
||||
{
|
||||
atomic_store_explicit(&transport->stop, true, memory_order_release);
|
||||
lgJoinThread(transport->thread, NULL);
|
||||
transport->thread = NULL;
|
||||
return LG_TRANSPORT_DISCONNECTED;
|
||||
}
|
||||
|
||||
if (transport->connectStatus != LG_TRANSPORT_OK)
|
||||
{
|
||||
lgJoinThread(transport->thread, NULL);
|
||||
@@ -186,6 +204,12 @@ static LG_TransportStatus spiceConnect(LG_Transport * transport,
|
||||
return LG_TRANSPORT_OK;
|
||||
}
|
||||
|
||||
static LG_TransportStatus spiceConnect(LG_Transport * transport,
|
||||
LG_TransportSession * session)
|
||||
{
|
||||
return spiceConnectCancellable(transport, session, NULL, NULL);
|
||||
}
|
||||
|
||||
static void spiceDisconnect(LG_Transport * transport)
|
||||
{
|
||||
if (!transport->thread)
|
||||
@@ -261,17 +285,18 @@ static LG_TransportStatus spiceControlStatus(LG_Transport * transport,
|
||||
|
||||
const LG_TransportOps LGT_SPICE =
|
||||
{
|
||||
.name = "spice",
|
||||
.setup = spiceSetup,
|
||||
.create = spiceCreate,
|
||||
.destroy = spiceDestroy,
|
||||
.connect = spiceConnect,
|
||||
.disconnect = spiceDisconnect,
|
||||
.sessionValid = spiceSessionValid,
|
||||
.getVideoOps = spiceGetVideoOps,
|
||||
.getInputOps = spiceGetInputOps,
|
||||
.getAudioOps = spiceGetAudioOps,
|
||||
.getClipboardOps = spiceGetClipboardOps,
|
||||
.sendControl = spiceSendControl,
|
||||
.controlStatus = spiceControlStatus,
|
||||
.name = "spice",
|
||||
.setup = spiceSetup,
|
||||
.create = spiceCreate,
|
||||
.destroy = spiceDestroy,
|
||||
.connect = spiceConnect,
|
||||
.connectCancellable = spiceConnectCancellable,
|
||||
.disconnect = spiceDisconnect,
|
||||
.sessionValid = spiceSessionValid,
|
||||
.getVideoOps = spiceGetVideoOps,
|
||||
.getInputOps = spiceGetInputOps,
|
||||
.getAudioOps = spiceGetAudioOps,
|
||||
.getClipboardOps = spiceGetClipboardOps,
|
||||
.sendControl = spiceSendControl,
|
||||
.controlStatus = spiceControlStatus,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user