mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 15:11:31 +00:00
[client] spice: move protocol into transport
This commit is contained in:
404
client/transports/SPICE/session.c
Normal file
404
client/transports/SPICE/session.c
Normal file
@@ -0,0 +1,404 @@
|
||||
/**
|
||||
* 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 "spice.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/time.h"
|
||||
|
||||
#include <purespice.h>
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define USB_REDIR_DISCONNECT_TIMEOUT_US UINT64_C(500000)
|
||||
|
||||
static _Atomic(LG_Transport *) l_callbackTarget;
|
||||
|
||||
static LG_Transport * callbackTarget(void)
|
||||
{
|
||||
return atomic_load_explicit(&l_callbackTarget, memory_order_acquire);
|
||||
}
|
||||
|
||||
static void ready(void)
|
||||
{
|
||||
LG_Transport * transport = callbackTarget();
|
||||
if (!transport)
|
||||
return;
|
||||
|
||||
snprintf(transport->session.version,
|
||||
sizeof(transport->session.version), "SPICE");
|
||||
transport->session.os = LG_TRANSPORT_OS_OTHER;
|
||||
|
||||
purespice_mouseMode(true);
|
||||
|
||||
PSServerInfo info;
|
||||
if (purespice_getServerInfo(&info))
|
||||
{
|
||||
if (info.name)
|
||||
snprintf(transport->session.name,
|
||||
sizeof(transport->session.name), "%s", info.name);
|
||||
|
||||
for (unsigned int i = 0; i < sizeof(info.uuid); ++i)
|
||||
if (info.uuid[i])
|
||||
{
|
||||
transport->session.uuidValid = true;
|
||||
memcpy(transport->session.uuid, info.uuid, sizeof(info.uuid));
|
||||
break;
|
||||
}
|
||||
|
||||
purespice_freeServerInfo(&info);
|
||||
}
|
||||
else
|
||||
DEBUG_WARN("Failed to obtain SPICE server information");
|
||||
|
||||
if (transport->input)
|
||||
spiceInput_setAvailable(transport->input, true);
|
||||
#if ENABLE_AUDIO
|
||||
if (transport->audio && !transport->usbAudioEnabled)
|
||||
spiceAudio_setAvailable(transport->audio, true);
|
||||
#endif
|
||||
|
||||
transport->connectStatus = LG_TRANSPORT_OK;
|
||||
atomic_store_explicit(
|
||||
&transport->sessionValid, true, memory_order_release);
|
||||
lgSignalEvent(transport->connectEvent);
|
||||
}
|
||||
|
||||
static void surfaceCreate(unsigned int surfaceId, PSSurfaceFormat format,
|
||||
unsigned int width, unsigned int height)
|
||||
{
|
||||
LG_Transport * transport = callbackTarget();
|
||||
if (transport)
|
||||
spiceSurface_create(
|
||||
transport->surface, surfaceId, format, width, height);
|
||||
}
|
||||
|
||||
static void surfaceDestroy(unsigned int surfaceId)
|
||||
{
|
||||
LG_Transport * transport = callbackTarget();
|
||||
if (transport)
|
||||
spiceSurface_destroy(transport->surface, surfaceId);
|
||||
}
|
||||
|
||||
static void drawFill(unsigned int surfaceId, int x, int y,
|
||||
int width, int height, uint32_t color)
|
||||
{
|
||||
LG_Transport * transport = callbackTarget();
|
||||
if (transport)
|
||||
spiceSurface_drawFill(
|
||||
transport->surface, surfaceId, x, y, width, height, color);
|
||||
}
|
||||
|
||||
static void drawBitmap(unsigned int surfaceId, PSBitmapFormat format,
|
||||
bool topDown, int x, int y, int width, int height, int stride, void * data)
|
||||
{
|
||||
LG_Transport * transport = callbackTarget();
|
||||
if (transport)
|
||||
spiceSurface_drawBitmap(transport->surface, surfaceId, format,
|
||||
topDown, x, y, width, height, stride, data);
|
||||
}
|
||||
|
||||
static void setRGBAImage(int width, int height, int hx, int hy,
|
||||
const void * data)
|
||||
{
|
||||
LG_Transport * transport = callbackTarget();
|
||||
if (transport)
|
||||
spiceSurface_setRGBAImage(
|
||||
transport->surface, width, height, hx, hy, data);
|
||||
}
|
||||
|
||||
static void setMonoImage(int width, int height, int hx, int hy,
|
||||
const void * xorMask, const void * andMask)
|
||||
{
|
||||
LG_Transport * transport = callbackTarget();
|
||||
if (transport)
|
||||
spiceSurface_setMonoImage(transport->surface,
|
||||
width, height, hx, hy, xorMask, andMask);
|
||||
}
|
||||
|
||||
static void setColorImage(int width, int height, int hx, int hy,
|
||||
const void * data, const void * maskData)
|
||||
{
|
||||
LG_Transport * transport = callbackTarget();
|
||||
if (transport)
|
||||
spiceSurface_setColorImage(transport->surface,
|
||||
width, height, hx, hy, data, maskData);
|
||||
}
|
||||
|
||||
static void setPointerState(bool visible, int x, int y)
|
||||
{
|
||||
LG_Transport * transport = callbackTarget();
|
||||
if (transport)
|
||||
spiceSurface_setPointerState(transport->surface, visible, x, y);
|
||||
}
|
||||
|
||||
#if ENABLE_AUDIO
|
||||
static void playbackStart(int channels, int sampleRate,
|
||||
PSAudioFormat format, uint32_t time)
|
||||
{
|
||||
LG_Transport * transport = callbackTarget();
|
||||
if (transport && transport->audio)
|
||||
spiceAudio_playbackStart(
|
||||
transport->audio, channels, sampleRate, format, time);
|
||||
}
|
||||
|
||||
static void playbackVolume(int channels, const uint16_t volume[])
|
||||
{
|
||||
LG_Transport * transport = callbackTarget();
|
||||
if (transport && transport->audio)
|
||||
spiceAudio_playbackVolume(transport->audio, channels, volume);
|
||||
}
|
||||
|
||||
static void playbackMute(bool mute)
|
||||
{
|
||||
LG_Transport * transport = callbackTarget();
|
||||
if (transport && transport->audio)
|
||||
spiceAudio_playbackMute(transport->audio, mute);
|
||||
}
|
||||
|
||||
static void playbackStop(void)
|
||||
{
|
||||
LG_Transport * transport = callbackTarget();
|
||||
if (transport && transport->audio)
|
||||
spiceAudio_playbackStop(transport->audio);
|
||||
}
|
||||
|
||||
static void playbackData(uint8_t * data, size_t size, uint32_t time)
|
||||
{
|
||||
LG_Transport * transport = callbackTarget();
|
||||
if (transport && transport->audio)
|
||||
spiceAudio_playbackData(transport->audio, data, size, time);
|
||||
}
|
||||
|
||||
static void recordStart(int channels, int sampleRate, PSAudioFormat format)
|
||||
{
|
||||
LG_Transport * transport = callbackTarget();
|
||||
if (transport && transport->audio)
|
||||
spiceAudio_recordStart(
|
||||
transport->audio, channels, sampleRate, format);
|
||||
}
|
||||
|
||||
static void recordVolume(int channels, const uint16_t volume[])
|
||||
{
|
||||
LG_Transport * transport = callbackTarget();
|
||||
if (transport && transport->audio)
|
||||
spiceAudio_recordVolume(transport->audio, channels, volume);
|
||||
}
|
||||
|
||||
static void recordMute(bool mute)
|
||||
{
|
||||
LG_Transport * transport = callbackTarget();
|
||||
if (transport && transport->audio)
|
||||
spiceAudio_recordMute(transport->audio, mute);
|
||||
}
|
||||
|
||||
static void recordStop(void)
|
||||
{
|
||||
LG_Transport * transport = callbackTarget();
|
||||
if (transport && transport->audio)
|
||||
spiceAudio_recordStop(transport->audio);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void disconnectProviders(LG_Transport * transport)
|
||||
{
|
||||
atomic_store_explicit(
|
||||
&transport->sessionValid, false, memory_order_release);
|
||||
|
||||
if (transport->input)
|
||||
spiceInput_setAvailable(transport->input, false);
|
||||
#if ENABLE_AUDIO
|
||||
if (transport->audio)
|
||||
spiceAudio_setAvailable(transport->audio, false);
|
||||
#endif
|
||||
if (transport->clipboard)
|
||||
spiceClipboard_setAvailable(transport->clipboard, false);
|
||||
spiceSurface_sessionStopped(transport->surface);
|
||||
}
|
||||
|
||||
int spiceSession_thread(void * opaque)
|
||||
{
|
||||
LG_Transport * transport = opaque;
|
||||
LG_Transport * expected = NULL;
|
||||
if (!atomic_compare_exchange_strong_explicit(&l_callbackTarget,
|
||||
&expected, transport, memory_order_acq_rel, memory_order_acquire))
|
||||
{
|
||||
DEBUG_ERROR("A SPICE session is already active");
|
||||
lgSignalEvent(transport->connectEvent);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (transport->clipboard)
|
||||
spiceClipboard_setCallbackTarget(transport->clipboard);
|
||||
|
||||
const PSConfig config =
|
||||
{
|
||||
.host = transport->host,
|
||||
.port = transport->port,
|
||||
.password = "",
|
||||
.ready = ready,
|
||||
.inputs =
|
||||
{
|
||||
.enable = transport->inputEnabled,
|
||||
.autoConnect = true,
|
||||
},
|
||||
.clipboard =
|
||||
{
|
||||
.enable = transport->clipboardEnabled,
|
||||
.notice = spiceClipboard_notice,
|
||||
.data = spiceClipboard_data,
|
||||
.release = spiceClipboard_release,
|
||||
.request = spiceClipboard_request,
|
||||
.status = spiceClipboard_status,
|
||||
},
|
||||
.display =
|
||||
{
|
||||
.enable = true,
|
||||
.autoConnect = false,
|
||||
.surfaceCreate = surfaceCreate,
|
||||
.surfaceDestroy = surfaceDestroy,
|
||||
.drawFill = drawFill,
|
||||
.drawBitmap = drawBitmap,
|
||||
},
|
||||
.cursor =
|
||||
{
|
||||
.enable = true,
|
||||
.autoConnect = false,
|
||||
.setRGBAImage = setRGBAImage,
|
||||
.setMonoImage = setMonoImage,
|
||||
.setColorImage = setColorImage,
|
||||
.setState = setPointerState,
|
||||
},
|
||||
#if ENABLE_AUDIO
|
||||
.playback =
|
||||
{
|
||||
.enable = transport->playbackEnabled &&
|
||||
!transport->usbAudioEnabled,
|
||||
.autoConnect = true,
|
||||
.start = playbackStart,
|
||||
.volume = playbackVolume,
|
||||
.mute = playbackMute,
|
||||
.stop = playbackStop,
|
||||
.data = playbackData,
|
||||
},
|
||||
.record =
|
||||
{
|
||||
.enable = transport->recordEnabled &&
|
||||
!transport->usbAudioEnabled,
|
||||
.autoConnect = true,
|
||||
.start = recordStart,
|
||||
.volume = recordVolume,
|
||||
.mute = recordMute,
|
||||
.stop = recordStop,
|
||||
},
|
||||
#endif
|
||||
#if ENABLE_USB_AUDIO
|
||||
.usbRedir =
|
||||
{
|
||||
.enable = transport->usbAudio != NULL,
|
||||
.autoConnect = false,
|
||||
.opaque = transport->usbRedir,
|
||||
.state = lgUsbRedir_state,
|
||||
.data = lgUsbRedir_data,
|
||||
},
|
||||
#endif
|
||||
};
|
||||
|
||||
PSStatus status = PS_STATUS_SHUTDOWN;
|
||||
if (!purespice_connect(&config))
|
||||
{
|
||||
DEBUG_ERROR("Failed to connect to SPICE server");
|
||||
goto done;
|
||||
}
|
||||
|
||||
transport->connected = true;
|
||||
status = PS_STATUS_RUN;
|
||||
int processTimeout = 100;
|
||||
|
||||
while (!atomic_load_explicit(&transport->stop, memory_order_acquire))
|
||||
{
|
||||
#if ENABLE_USB_AUDIO
|
||||
if (transport->usbRedir && !lgUsbRedir_process(transport->usbRedir))
|
||||
DEBUG_WARN("Failed to process USB audio redirection");
|
||||
if (transport->usbAudio)
|
||||
{
|
||||
const uint64_t delay = lgaUsb_processDelayNs(transport->usbAudio);
|
||||
if (delay == UINT64_MAX)
|
||||
processTimeout = 10;
|
||||
else
|
||||
{
|
||||
const uint64_t timeout = delay / UINT64_C(1000000) +
|
||||
(delay % UINT64_C(1000000) != 0);
|
||||
processTimeout = (int)min(timeout, UINT64_C(10));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
status = purespice_process(processTimeout);
|
||||
if (status != PS_STATUS_RUN)
|
||||
{
|
||||
if (status != PS_STATUS_SHUTDOWN)
|
||||
DEBUG_ERROR("Failed to process SPICE messages");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
disconnectProviders(transport);
|
||||
|
||||
#if ENABLE_USB_AUDIO
|
||||
if (status == PS_STATUS_RUN && transport->usbRedir)
|
||||
{
|
||||
if (!lgUsbRedir_process(transport->usbRedir))
|
||||
DEBUG_WARN("Failed to disconnect USB audio device");
|
||||
else
|
||||
{
|
||||
const uint64_t deadline =
|
||||
microtime() + USB_REDIR_DISCONNECT_TIMEOUT_US;
|
||||
while (lgUsbRedir_disconnectPending(transport->usbRedir) &&
|
||||
microtime() < deadline)
|
||||
{
|
||||
status = purespice_process(10);
|
||||
if (status != PS_STATUS_RUN)
|
||||
break;
|
||||
}
|
||||
|
||||
if (status == PS_STATUS_RUN &&
|
||||
lgUsbRedir_disconnectPending(transport->usbRedir))
|
||||
DEBUG_WARN("Timed out disconnecting USB audio device");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
purespice_disconnect();
|
||||
transport->connected = false;
|
||||
|
||||
done:
|
||||
disconnectProviders(transport);
|
||||
if (transport->clipboard)
|
||||
spiceClipboard_setCallbackTarget(NULL);
|
||||
|
||||
expected = transport;
|
||||
atomic_compare_exchange_strong_explicit(&l_callbackTarget,
|
||||
&expected, NULL, memory_order_acq_rel, memory_order_acquire);
|
||||
lgSignalEvent(transport->connectEvent);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user