mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-09 16:51:31 +00:00
[client] clipboard: add transport provider interface
Some checks are pending
build / client (Debug, map[cc:clang cxx:clang++], libdecor) (push) Waiting to run
build / client (Debug, map[cc:clang cxx:clang++], xdg-shell) (push) Waiting to run
build / client (Debug, map[cc:gcc cxx:g++], libdecor) (push) Waiting to run
build / client (Debug, map[cc:gcc cxx:g++], xdg-shell) (push) Waiting to run
build / client (Release, map[cc:clang cxx:clang++], libdecor) (push) Waiting to run
build / client (Release, map[cc:clang cxx:clang++], xdg-shell) (push) Waiting to run
build / client (Release, map[cc:gcc cxx:g++], libdecor) (push) Waiting to run
build / client (Release, map[cc:gcc cxx:g++], xdg-shell) (push) Waiting to run
build / module (push) Waiting to run
build / host-linux (push) Waiting to run
build / host-windows-cross (push) Waiting to run
build / host-windows-native (push) Waiting to run
build / idd (push) Waiting to run
build / obs (clang) (push) Waiting to run
build / obs (gcc) (push) Waiting to run
build / docs (push) Waiting to run
Some checks are pending
build / client (Debug, map[cc:clang cxx:clang++], libdecor) (push) Waiting to run
build / client (Debug, map[cc:clang cxx:clang++], xdg-shell) (push) Waiting to run
build / client (Debug, map[cc:gcc cxx:g++], libdecor) (push) Waiting to run
build / client (Debug, map[cc:gcc cxx:g++], xdg-shell) (push) Waiting to run
build / client (Release, map[cc:clang cxx:clang++], libdecor) (push) Waiting to run
build / client (Release, map[cc:clang cxx:clang++], xdg-shell) (push) Waiting to run
build / client (Release, map[cc:gcc cxx:g++], libdecor) (push) Waiting to run
build / client (Release, map[cc:gcc cxx:g++], xdg-shell) (push) Waiting to run
build / module (push) Waiting to run
build / host-linux (push) Waiting to run
build / host-windows-cross (push) Waiting to run
build / host-windows-native (push) Waiting to run
build / idd (push) Waiting to run
build / obs (clang) (push) Waiting to run
build / obs (gcc) (push) Waiting to run
build / docs (push) Waiting to run
Route clipboard exchange through a provider interface. Connected transports take priority while SPICE remains the fallback. Track asynchronous X11 and Wayland transfers by request, preserving clipboard ownership across provider changes. Use PureSpice clipboard status notifications for capability and lifecycle updates.
This commit is contained in:
@@ -158,6 +158,7 @@ set(SOURCES
|
||||
src/font.c
|
||||
src/util.c
|
||||
src/clipboard.c
|
||||
src/clipboard_spice.c
|
||||
src/kb.c
|
||||
src/kb_hid.c
|
||||
src/gl_dynprocs.c
|
||||
|
||||
@@ -191,6 +191,44 @@ static bool hasImageMimetype(char ** mimetypes)
|
||||
return false;
|
||||
}
|
||||
|
||||
/* wlCb.lock must be held. */
|
||||
static struct ClipboardRead * clipboardReadTakeCurrentNL(void)
|
||||
{
|
||||
struct ClipboardRead * data = wlCb.currentRead;
|
||||
if (!data)
|
||||
return NULL;
|
||||
|
||||
wlCb.currentRead = NULL;
|
||||
return data;
|
||||
}
|
||||
|
||||
static void clipboardReadRetire(struct ClipboardRead * data)
|
||||
{
|
||||
if (data)
|
||||
waylandPollUnregister(data->fd);
|
||||
}
|
||||
|
||||
static bool clipboardReadClaim(struct ClipboardRead * data)
|
||||
{
|
||||
LG_LOCK(wlCb.lock);
|
||||
const bool current = wlCb.currentRead == data;
|
||||
if (current)
|
||||
clipboardReadTakeCurrentNL();
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
|
||||
if (current)
|
||||
clipboardReadRetire(data);
|
||||
return current;
|
||||
}
|
||||
|
||||
static void clipboardReadCleanup(void * opaque)
|
||||
{
|
||||
struct ClipboardRead * data = opaque;
|
||||
close(data->fd);
|
||||
free(data->buf);
|
||||
free(data);
|
||||
}
|
||||
|
||||
// Destination client handlers.
|
||||
|
||||
static void dataOfferHandleOffer(void * opaque, struct wl_data_offer * offer,
|
||||
@@ -276,21 +314,36 @@ static void dataDeviceHandleSelection(void * opaque,
|
||||
return;
|
||||
}
|
||||
|
||||
wlCb.offer = offer;
|
||||
|
||||
for (enum LG_ClipboardData i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
free(wlCb.mimetypes[i]);
|
||||
memcpy(wlCb.mimetypes, extra->mimetypes, sizeof(wlCb.mimetypes));
|
||||
|
||||
wl_data_offer_set_user_data(offer, NULL);
|
||||
free(extra);
|
||||
|
||||
int idx = 0;
|
||||
enum LG_ClipboardData types[LG_CLIPBOARD_DATA_NONE];
|
||||
for (enum LG_ClipboardData i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
if (wlCb.mimetypes[i])
|
||||
if (extra->mimetypes[i])
|
||||
types[idx++] = i;
|
||||
|
||||
char * oldMimetypes[LG_CLIPBOARD_DATA_NONE];
|
||||
LG_LOCK(wlCb.lock);
|
||||
struct ClipboardRead * read = clipboardReadTakeCurrentNL();
|
||||
struct wl_data_offer * oldOffer = wlCb.offer;
|
||||
memcpy(oldMimetypes, wlCb.mimetypes, sizeof(oldMimetypes));
|
||||
wlCb.offer = offer;
|
||||
memcpy(wlCb.mimetypes, extra->mimetypes, sizeof(wlCb.mimetypes));
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
|
||||
wl_data_offer_set_user_data(offer, NULL);
|
||||
memset(extra->mimetypes, 0, sizeof(extra->mimetypes));
|
||||
free(extra);
|
||||
|
||||
if (read)
|
||||
{
|
||||
const LG_ClipboardRequest request = read->request;
|
||||
clipboardReadRetire(read);
|
||||
app_clipboardAbort(request);
|
||||
}
|
||||
if (oldOffer && oldOffer != offer)
|
||||
wl_data_offer_destroy(oldOffer);
|
||||
for (enum LG_ClipboardData i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
free(oldMimetypes[i]);
|
||||
|
||||
app_clipboardNotifyTypes(types, idx);
|
||||
}
|
||||
|
||||
@@ -340,8 +393,6 @@ static const struct wl_data_device_listener dataDeviceListener = {
|
||||
|
||||
bool waylandCBInit(void)
|
||||
{
|
||||
memset(&wlCb, 0, sizeof(wlCb));
|
||||
|
||||
if (!wlWm.seat)
|
||||
{
|
||||
DEBUG_WARN("Clipboard unavailable without wl_seat");
|
||||
@@ -370,37 +421,37 @@ bool waylandCBInit(void)
|
||||
return true;
|
||||
}
|
||||
|
||||
static void clipboardReadCancel(struct ClipboardRead * data)
|
||||
{
|
||||
waylandPollUnregister(data->fd);
|
||||
close(data->fd);
|
||||
free(data->buf);
|
||||
free(data);
|
||||
wlCb.currentRead = NULL;
|
||||
}
|
||||
|
||||
static void clipboardReadCallback(uint32_t events, void * opaque)
|
||||
{
|
||||
struct ClipboardRead * data = opaque;
|
||||
LG_LOCK(wlCb.lock);
|
||||
const bool active = wlCb.currentRead == data;
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
if (!active)
|
||||
return;
|
||||
|
||||
if (events & EPOLLERR)
|
||||
{
|
||||
clipboardReadCancel(data);
|
||||
if (clipboardReadClaim(data))
|
||||
app_clipboardAbort(data->request);
|
||||
return;
|
||||
}
|
||||
|
||||
ssize_t result = read(data->fd, data->buf + data->numRead, data->size - data->numRead);
|
||||
ssize_t result = read(data->fd,
|
||||
data->buf + data->numRead, data->size - data->numRead);
|
||||
if (result < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to read from clipboard: %s", strerror(errno));
|
||||
clipboardReadCancel(data);
|
||||
if (clipboardReadClaim(data))
|
||||
app_clipboardAbort(data->request);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
app_clipboardNotifySize(data->type, data->numRead);
|
||||
app_clipboardData(data->type, data->buf, data->numRead);
|
||||
clipboardReadCancel(data);
|
||||
if (clipboardReadClaim(data))
|
||||
app_clipboardData(
|
||||
data->request, data->type, data->buf, data->numRead);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -409,9 +460,11 @@ static void clipboardReadCallback(uint32_t events, void * opaque)
|
||||
{
|
||||
data->size *= 2;
|
||||
void * nbuf = realloc(data->buf, data->size);
|
||||
if (!nbuf) {
|
||||
if (!nbuf)
|
||||
{
|
||||
DEBUG_ERROR("Failed to realloc clipboard buffer: %s", strerror(errno));
|
||||
clipboardReadCancel(data);
|
||||
if (clipboardReadClaim(data))
|
||||
app_clipboardAbort(data->request);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -421,70 +474,103 @@ static void clipboardReadCallback(uint32_t events, void * opaque)
|
||||
|
||||
void waylandCBInvalidate(void)
|
||||
{
|
||||
if (wlCb.currentRead)
|
||||
clipboardReadCancel(wlCb.currentRead);
|
||||
char * mimetypes[LG_CLIPBOARD_DATA_NONE];
|
||||
LG_LOCK(wlCb.lock);
|
||||
struct ClipboardRead * read = clipboardReadTakeCurrentNL();
|
||||
struct wl_data_offer * offer = wlCb.offer;
|
||||
wlCb.offer = NULL;
|
||||
memcpy(mimetypes, wlCb.mimetypes, sizeof(mimetypes));
|
||||
memset(wlCb.mimetypes, 0, sizeof(wlCb.mimetypes));
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
|
||||
if (read)
|
||||
{
|
||||
const LG_ClipboardRequest request = read->request;
|
||||
clipboardReadRetire(read);
|
||||
app_clipboardAbort(request);
|
||||
}
|
||||
|
||||
app_clipboardRelease();
|
||||
|
||||
if (wlCb.offer)
|
||||
wl_data_offer_destroy(wlCb.offer);
|
||||
wlCb.offer = NULL;
|
||||
if (offer)
|
||||
wl_data_offer_destroy(offer);
|
||||
for (enum LG_ClipboardData i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
free(mimetypes[i]);
|
||||
}
|
||||
|
||||
void waylandCBRequest(LG_ClipboardData type)
|
||||
void waylandCBRequest(LG_ClipboardRequest request, LG_ClipboardData type)
|
||||
{
|
||||
if (!wlCb.offer || !wlCb.mimetypes[type])
|
||||
{
|
||||
app_clipboardRelease();
|
||||
if (request == LG_CLIPBOARD_REQUEST_INVALID ||
|
||||
type < LG_CLIPBOARD_DATA_TEXT || type >= LG_CLIPBOARD_DATA_NONE)
|
||||
return;
|
||||
}
|
||||
|
||||
if (wlCb.currentRead)
|
||||
clipboardReadCancel(wlCb.currentRead);
|
||||
|
||||
int fds[2];
|
||||
if (pipe(fds) < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to get a clipboard pipe: %s", strerror(errno));
|
||||
abort();
|
||||
app_clipboardAbort(request);
|
||||
return;
|
||||
}
|
||||
|
||||
wl_data_offer_receive(wlCb.offer, wlCb.mimetypes[type], fds[1]);
|
||||
close(fds[1]);
|
||||
|
||||
struct ClipboardRead * data = malloc(sizeof(*data));
|
||||
if (!data)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate memory to read clipboard");
|
||||
close(fds[0]);
|
||||
close(fds[1]);
|
||||
app_clipboardAbort(request);
|
||||
return;
|
||||
}
|
||||
|
||||
data->fd = fds[0];
|
||||
data->size = 4096;
|
||||
data->numRead = 0;
|
||||
data->buf = malloc(data->size);
|
||||
data->offer = wlCb.offer;
|
||||
data->type = type;
|
||||
data->fd = fds[0];
|
||||
data->size = 4096;
|
||||
data->numRead = 0;
|
||||
data->buf = malloc(data->size);
|
||||
data->request = request;
|
||||
data->type = type;
|
||||
|
||||
if (!data->buf)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate memory to receive clipboard data");
|
||||
close(data->fd);
|
||||
close(fds[1]);
|
||||
free(data);
|
||||
app_clipboardAbort(request);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!waylandPollRegister(data->fd, clipboardReadCallback, data, EPOLLIN))
|
||||
LG_LOCK(wlCb.lock);
|
||||
struct ClipboardRead * old = clipboardReadTakeCurrentNL();
|
||||
const bool available = wlCb.offer && wlCb.mimetypes[type];
|
||||
if (available)
|
||||
{
|
||||
DEBUG_ERROR("Failed to register clipboard read into epoll: %s", strerror(errno));
|
||||
close(data->fd);
|
||||
free(data->buf);
|
||||
free(data);
|
||||
wl_data_offer_receive(
|
||||
wlCb.offer, wlCb.mimetypes[type], fds[1]);
|
||||
}
|
||||
close(fds[1]);
|
||||
|
||||
const bool registered = available &&
|
||||
waylandPollRegisterWithCleanup(data->fd,
|
||||
clipboardReadCallback, data, clipboardReadCleanup, EPOLLIN);
|
||||
if (registered)
|
||||
wlCb.currentRead = data;
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
|
||||
if (old)
|
||||
{
|
||||
const LG_ClipboardRequest oldRequest = old->request;
|
||||
clipboardReadRetire(old);
|
||||
app_clipboardAbort(oldRequest);
|
||||
}
|
||||
if (!registered)
|
||||
{
|
||||
if (available)
|
||||
DEBUG_ERROR("Failed to register clipboard read into epoll: %s",
|
||||
strerror(errno));
|
||||
clipboardReadCleanup(data);
|
||||
app_clipboardAbort(request);
|
||||
return;
|
||||
}
|
||||
|
||||
wlCb.currentRead = data;
|
||||
}
|
||||
|
||||
struct ClipboardWrite
|
||||
@@ -494,6 +580,14 @@ struct ClipboardWrite
|
||||
struct CountedBuffer * buffer;
|
||||
};
|
||||
|
||||
static void clipboardWriteCleanup(void * opaque)
|
||||
{
|
||||
struct ClipboardWrite * data = opaque;
|
||||
close(data->fd);
|
||||
countedBufferRelease(&data->buffer);
|
||||
free(data);
|
||||
}
|
||||
|
||||
static void clipboardWriteCallback(uint32_t events, void * opaque)
|
||||
{
|
||||
struct ClipboardWrite * data = opaque;
|
||||
@@ -514,9 +608,6 @@ static void clipboardWriteCallback(uint32_t events, void * opaque)
|
||||
|
||||
error:
|
||||
waylandPollUnregister(data->fd);
|
||||
close(data->fd);
|
||||
countedBufferRelease(&data->buffer);
|
||||
free(data);
|
||||
}
|
||||
|
||||
static void dataSourceHandleTarget(void * data, struct wl_data_source * source,
|
||||
@@ -543,8 +634,12 @@ static void dataSourceHandleSend(void * data, struct wl_data_source * source,
|
||||
data->pos = 0;
|
||||
data->buffer = transfer->data;
|
||||
countedBufferAddRef(transfer->data);
|
||||
waylandPollRegister(fd, clipboardWriteCallback, data, EPOLLOUT);
|
||||
return;
|
||||
if (waylandPollRegisterWithCleanup(fd,
|
||||
clipboardWriteCallback, data, clipboardWriteCleanup, EPOLLOUT))
|
||||
return;
|
||||
|
||||
countedBufferRelease(&data->buffer);
|
||||
free(data);
|
||||
}
|
||||
|
||||
error:
|
||||
@@ -567,7 +662,7 @@ static const struct wl_data_source_listener dataSourceListener = {
|
||||
};
|
||||
|
||||
static void waylandCBReplyFn(void * opaque, LG_ClipboardData type,
|
||||
uint8_t * data, uint32_t size)
|
||||
const uint8_t * data, uint32_t size)
|
||||
{
|
||||
if (type == LG_CLIPBOARD_DATA_NONE)
|
||||
return;
|
||||
@@ -587,7 +682,8 @@ static void waylandCBReplyFn(void * opaque, LG_ClipboardData type,
|
||||
free(transfer);
|
||||
return;
|
||||
}
|
||||
memcpy(transfer->data->data, data, size);
|
||||
if (size)
|
||||
memcpy(transfer->data->data, data, size);
|
||||
|
||||
struct wl_data_source * source =
|
||||
wl_data_device_manager_create_data_source(wlWm.dataDeviceManager);
|
||||
@@ -602,13 +698,38 @@ static void waylandCBReplyFn(void * opaque, LG_ClipboardData type,
|
||||
|
||||
void waylandCBNotice(LG_ClipboardData type)
|
||||
{
|
||||
wlCb.haveRequest = true;
|
||||
wlCb.type = type;
|
||||
if (!app_clipboardRequest(waylandCBReplyFn, NULL))
|
||||
DEBUG_ERROR("Failed to request SPICE clipboard data");
|
||||
if (!app_clipboardRequest(type, waylandCBReplyFn, NULL))
|
||||
DEBUG_ERROR("Failed to request remote clipboard data");
|
||||
}
|
||||
|
||||
void waylandCBRelease(void)
|
||||
{
|
||||
wlCb.haveRequest = false;
|
||||
}
|
||||
|
||||
void waylandCBFree(void)
|
||||
{
|
||||
char * mimetypes[LG_CLIPBOARD_DATA_NONE];
|
||||
LG_LOCK(wlCb.lock);
|
||||
struct ClipboardRead * read = clipboardReadTakeCurrentNL();
|
||||
struct wl_data_offer * offer = wlCb.offer;
|
||||
struct wl_data_offer * dndOffer = wlCb.dndOffer;
|
||||
struct wl_data_device * dataDevice = wlCb.dataDevice;
|
||||
wlCb.offer = NULL;
|
||||
wlCb.dndOffer = NULL;
|
||||
wlCb.dataDevice = NULL;
|
||||
memcpy(mimetypes, wlCb.mimetypes, sizeof(mimetypes));
|
||||
memset(wlCb.mimetypes, 0, sizeof(wlCb.mimetypes));
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
|
||||
clipboardReadRetire(read);
|
||||
if (offer)
|
||||
wl_data_offer_destroy(offer);
|
||||
if (dndOffer && dndOffer != offer)
|
||||
wl_data_offer_destroy(dndOffer);
|
||||
if (dataDevice)
|
||||
wl_data_device_release(dataDevice);
|
||||
for (enum LG_ClipboardData i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
free(mimetypes[i]);
|
||||
|
||||
LG_LOCK_FREE(wlCb.lock);
|
||||
}
|
||||
|
||||
@@ -263,7 +263,7 @@ void libdecor_pollWait(struct wl_display * display, int epollFd,
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
struct WaylandPoll * poll = events[i].data.ptr;
|
||||
if (!poll->removed)
|
||||
if (!atomic_load_explicit(&poll->removed, memory_order_acquire))
|
||||
poll->callback(events[i].events, poll->opaque);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,7 +300,7 @@ void xdg_pollWait(struct wl_display * display, int epollFd,
|
||||
bool sawDisplay = false;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
struct WaylandPoll * poll = events[i].data.ptr;
|
||||
if (!poll->removed)
|
||||
if (!atomic_load_explicit(&poll->removed, memory_order_acquire))
|
||||
poll->callback(events[i].events, poll->opaque);
|
||||
if (poll->fd == state.displayFd)
|
||||
sawDisplay = true;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#include <errno.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <unistd.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "common/debug.h"
|
||||
@@ -47,84 +48,150 @@ bool waylandPollInit(void)
|
||||
return wlWm.desktop->pollInit(wlWm.display);
|
||||
}
|
||||
|
||||
void waylandWait(unsigned int time)
|
||||
static void waylandPollDestroyNode(struct WaylandPoll * node)
|
||||
{
|
||||
wlWm.desktop->pollWait(wlWm.display, wlWm.epollFd, time);
|
||||
INTERLOCKED_SECTION(wlWm.pollFreeLock,
|
||||
{
|
||||
struct WaylandPoll * node;
|
||||
struct WaylandPoll * temp;
|
||||
wl_list_for_each_safe(node, temp, &wlWm.pollFree, link)
|
||||
{
|
||||
wl_list_remove(&node->link);
|
||||
free(node);
|
||||
}
|
||||
});
|
||||
if (node->cleanup)
|
||||
node->cleanup(node->opaque);
|
||||
free(node);
|
||||
}
|
||||
|
||||
static void waylandPollRemoveNode(struct WaylandPoll * node)
|
||||
static void waylandPollDestroyList(struct wl_list * list)
|
||||
{
|
||||
INTERLOCKED_SECTION(wlWm.pollLock,
|
||||
struct WaylandPoll * node;
|
||||
struct WaylandPoll * temp;
|
||||
wl_list_for_each_safe(node, temp, list, link)
|
||||
{
|
||||
wl_list_remove(&node->link);
|
||||
});
|
||||
waylandPollDestroyNode(node);
|
||||
}
|
||||
}
|
||||
|
||||
bool waylandPollRegister(int fd, WaylandPollCallback callback, void * opaque, uint32_t events)
|
||||
static void waylandPollTakeList(struct wl_list * source,
|
||||
struct wl_list * destination)
|
||||
{
|
||||
struct WaylandPoll * node;
|
||||
struct WaylandPoll * temp;
|
||||
wl_list_for_each_safe(node, temp, source, link)
|
||||
{
|
||||
wl_list_remove(&node->link);
|
||||
wl_list_insert(destination, &node->link);
|
||||
}
|
||||
}
|
||||
|
||||
void waylandWait(unsigned int time)
|
||||
{
|
||||
INTERLOCKED_SECTION(wlWm.pollFreeLock,
|
||||
{
|
||||
++wlWm.pollWaiters;
|
||||
});
|
||||
|
||||
wlWm.desktop->pollWait(wlWm.display, wlWm.epollFd, time);
|
||||
|
||||
struct wl_list retired;
|
||||
wl_list_init(&retired);
|
||||
INTERLOCKED_SECTION(wlWm.pollFreeLock,
|
||||
{
|
||||
if (--wlWm.pollWaiters == 0)
|
||||
waylandPollTakeList(&wlWm.pollFree, &retired);
|
||||
});
|
||||
waylandPollDestroyList(&retired);
|
||||
}
|
||||
|
||||
void waylandPollFree(void)
|
||||
{
|
||||
if (wlWm.epollFd >= 0)
|
||||
{
|
||||
close(wlWm.epollFd);
|
||||
wlWm.epollFd = -1;
|
||||
}
|
||||
|
||||
struct wl_list active;
|
||||
struct wl_list retired;
|
||||
wl_list_init(&active);
|
||||
wl_list_init(&retired);
|
||||
INTERLOCKED_SECTION(wlWm.pollLock,
|
||||
{
|
||||
waylandPollTakeList(&wlWm.poll, &active);
|
||||
});
|
||||
|
||||
INTERLOCKED_SECTION(wlWm.pollFreeLock,
|
||||
{
|
||||
waylandPollTakeList(&wlWm.pollFree, &retired);
|
||||
});
|
||||
waylandPollDestroyList(&active);
|
||||
waylandPollDestroyList(&retired);
|
||||
|
||||
LG_LOCK_FREE(wlWm.pollFreeLock);
|
||||
LG_LOCK_FREE(wlWm.pollLock);
|
||||
}
|
||||
|
||||
bool waylandPollRegisterWithCleanup(int fd, WaylandPollCallback callback,
|
||||
void * opaque, WaylandPollCleanup cleanup, uint32_t events)
|
||||
{
|
||||
struct WaylandPoll * node = malloc(sizeof(*node));
|
||||
if (!node)
|
||||
return false;
|
||||
|
||||
node->fd = fd;
|
||||
node->removed = false;
|
||||
node->callback = callback;
|
||||
node->cleanup = cleanup;
|
||||
node->opaque = opaque;
|
||||
atomic_init(&node->removed, false);
|
||||
|
||||
INTERLOCKED_SECTION(wlWm.pollLock,
|
||||
{
|
||||
wl_list_insert(&wlWm.poll, &node->link);
|
||||
});
|
||||
|
||||
LG_LOCK(wlWm.pollLock);
|
||||
if (epoll_ctl(wlWm.epollFd, EPOLL_CTL_ADD, fd, &(struct epoll_event) {
|
||||
.events = events,
|
||||
.data = (epoll_data_t) { .ptr = node },
|
||||
}) < 0)
|
||||
{
|
||||
waylandPollRemoveNode(node);
|
||||
LG_UNLOCK(wlWm.pollLock);
|
||||
free(node);
|
||||
return false;
|
||||
}
|
||||
wl_list_insert(&wlWm.poll, &node->link);
|
||||
LG_UNLOCK(wlWm.pollLock);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool waylandPollRegister(int fd, WaylandPollCallback callback,
|
||||
void * opaque, uint32_t events)
|
||||
{
|
||||
return waylandPollRegisterWithCleanup(
|
||||
fd, callback, opaque, NULL, events);
|
||||
}
|
||||
|
||||
bool waylandPollUnregister(int fd)
|
||||
{
|
||||
struct WaylandPoll * node = NULL;
|
||||
INTERLOCKED_SECTION(wlWm.pollLock,
|
||||
LG_LOCK(wlWm.pollLock);
|
||||
struct WaylandPoll * current;
|
||||
wl_list_for_each(current, &wlWm.poll, link)
|
||||
{
|
||||
wl_list_for_each(node, &wlWm.poll, link)
|
||||
if (current->fd == fd)
|
||||
{
|
||||
if (node->fd == fd)
|
||||
break;
|
||||
node = current;
|
||||
atomic_store_explicit(
|
||||
&node->removed, true, memory_order_release);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!node)
|
||||
{
|
||||
LG_UNLOCK(wlWm.pollLock);
|
||||
DEBUG_ERROR("Attempt to unregister a fd that was not registered: %d", fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
node->removed = true;
|
||||
if (epoll_ctl(wlWm.epollFd, EPOLL_CTL_DEL, fd, NULL) < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to unregistered from epoll: %s", strerror(errno));
|
||||
LG_UNLOCK(wlWm.pollLock);
|
||||
DEBUG_ERROR("Failed to unregister from epoll: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
waylandPollRemoveNode(node);
|
||||
wl_list_remove(&node->link);
|
||||
LG_UNLOCK(wlWm.pollLock);
|
||||
|
||||
INTERLOCKED_SECTION(wlWm.pollFreeLock,
|
||||
{
|
||||
|
||||
@@ -108,9 +108,11 @@ static bool getCompositor(char * dst, size_t size)
|
||||
static bool waylandInit(const LG_DSInitParams params)
|
||||
{
|
||||
memset(&wlWm, 0, sizeof(wlWm));
|
||||
memset(&wlCb, 0, sizeof(wlCb));
|
||||
LG_LOCK_INIT(wlWm.surfaceLock);
|
||||
LG_LOCK_INIT(wlWm.pendingHDRLock);
|
||||
LG_LOCK_INIT(wlWm.hdrLock);
|
||||
LG_LOCK_INIT(wlCb.lock);
|
||||
wlWm.desktop = WL_Desktops[0];
|
||||
atomic_init(&wlWm.lockActive, false);
|
||||
atomic_init(&wlWm.cmFeaturesDone, false);
|
||||
@@ -207,6 +209,7 @@ static void waylandShutdown(void)
|
||||
|
||||
static void waylandFree(void)
|
||||
{
|
||||
waylandCBFree();
|
||||
waylandIdleFree();
|
||||
waylandPresentationFree();
|
||||
waylandWindowFree();
|
||||
@@ -215,6 +218,7 @@ static void waylandFree(void)
|
||||
waylandColorMgmtFree();
|
||||
waylandRegistryFree();
|
||||
waylandCursorFree();
|
||||
waylandPollFree();
|
||||
wl_display_disconnect(wlWm.display);
|
||||
LG_LOCK_FREE(wlWm.hdrLock);
|
||||
LG_LOCK_FREE(wlWm.pendingHDRLock);
|
||||
|
||||
@@ -59,14 +59,16 @@
|
||||
#include "motion.h"
|
||||
|
||||
typedef void (*WaylandPollCallback)(uint32_t events, void * opaque);
|
||||
typedef void (*WaylandPollCleanup)(void * opaque);
|
||||
|
||||
struct WaylandPoll
|
||||
{
|
||||
int fd;
|
||||
bool removed;
|
||||
int fd;
|
||||
atomic_bool removed;
|
||||
WaylandPollCallback callback;
|
||||
void * opaque;
|
||||
struct wl_list link;
|
||||
WaylandPollCleanup cleanup;
|
||||
void * opaque;
|
||||
struct wl_list link;
|
||||
};
|
||||
|
||||
struct OutputColorDescription;
|
||||
@@ -273,40 +275,39 @@ struct WaylandDSState
|
||||
|
||||
struct wl_list poll; // WaylandPoll::link
|
||||
struct wl_list pollFree; // WaylandPoll::link
|
||||
LG_Lock pollLock;
|
||||
LG_Lock pollFreeLock;
|
||||
int epollFd;
|
||||
int displayFd;
|
||||
LG_Lock pollLock;
|
||||
LG_Lock pollFreeLock;
|
||||
unsigned int pollWaiters;
|
||||
int epollFd;
|
||||
int displayFd;
|
||||
};
|
||||
|
||||
struct WCBTransfer
|
||||
{
|
||||
struct CountedBuffer * data;
|
||||
const char ** mimetypes;
|
||||
const char ** mimetypes;
|
||||
};
|
||||
|
||||
struct ClipboardRead
|
||||
{
|
||||
int fd;
|
||||
size_t size;
|
||||
size_t numRead;
|
||||
uint8_t * buf;
|
||||
enum LG_ClipboardData type;
|
||||
struct wl_data_offer * offer;
|
||||
int fd;
|
||||
size_t size;
|
||||
size_t numRead;
|
||||
uint8_t * buf;
|
||||
LG_ClipboardRequest request;
|
||||
LG_ClipboardData type;
|
||||
};
|
||||
|
||||
struct WCBState
|
||||
{
|
||||
struct wl_data_device * dataDevice;
|
||||
char lgMimetype[64];
|
||||
char lgMimetype[64];
|
||||
|
||||
char * mimetypes[LG_CLIPBOARD_DATA_NONE];
|
||||
char * mimetypes[LG_CLIPBOARD_DATA_NONE];
|
||||
struct wl_data_offer * offer;
|
||||
struct wl_data_offer * dndOffer;
|
||||
|
||||
bool haveRequest;
|
||||
LG_ClipboardData type;
|
||||
|
||||
LG_Lock lock;
|
||||
struct ClipboardRead * currentRead;
|
||||
};
|
||||
|
||||
@@ -320,7 +321,8 @@ void waylandActivationRequestActivation(void);
|
||||
|
||||
// clipboard module
|
||||
bool waylandCBInit(void);
|
||||
void waylandCBRequest(LG_ClipboardData type);
|
||||
void waylandCBFree(void);
|
||||
void waylandCBRequest(LG_ClipboardRequest request, LG_ClipboardData type);
|
||||
void waylandCBNotice(LG_ClipboardData type);
|
||||
void waylandCBRelease(void);
|
||||
void waylandCBInvalidate(void);
|
||||
@@ -407,8 +409,11 @@ void waylandOutputUpdateHDRWhiteLevel(void);
|
||||
|
||||
// poll module
|
||||
bool waylandPollInit(void);
|
||||
void waylandPollFree(void);
|
||||
void waylandWait(unsigned int time);
|
||||
bool waylandPollRegister(int fd, WaylandPollCallback callback, void * opaque, uint32_t events);
|
||||
bool waylandPollRegisterWithCleanup(int fd, WaylandPollCallback callback,
|
||||
void * opaque, WaylandPollCleanup cleanup, uint32_t events);
|
||||
bool waylandPollUnregister(int fd);
|
||||
|
||||
// presentation module
|
||||
|
||||
@@ -31,16 +31,29 @@
|
||||
#include "app.h"
|
||||
#include "common/array.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/locking.h"
|
||||
|
||||
struct X11ClipboardRead
|
||||
{
|
||||
Window window;
|
||||
LG_ClipboardRequest request;
|
||||
LG_ClipboardData type;
|
||||
bool incremental;
|
||||
uint8_t * buffer;
|
||||
size_t size;
|
||||
size_t capacity;
|
||||
};
|
||||
|
||||
struct X11ClipboardState
|
||||
{
|
||||
LG_Lock lock;
|
||||
Atom aCurSelection;
|
||||
Atom aTypes[LG_CLIPBOARD_DATA_NONE];
|
||||
Window targetsWindow;
|
||||
LG_ClipboardData type;
|
||||
bool haveRequest;
|
||||
|
||||
bool incrStart;
|
||||
unsigned int lowerBound;
|
||||
struct X11ClipboardRead read;
|
||||
};
|
||||
|
||||
static const char * atomTypes[] =
|
||||
@@ -60,6 +73,8 @@ static void x11CBSelectionClear(const XSelectionClearEvent e);
|
||||
static void x11CBSelectionIncr(const XPropertyEvent e);
|
||||
static void x11CBSelectionNotify(const XSelectionEvent e);
|
||||
static void x11CBXFixesSelectionNotify(const XFixesSelectionNotifyEvent e);
|
||||
static LG_ClipboardRequest cancelReadNL(void);
|
||||
static void clearTargetsNL(void);
|
||||
|
||||
bool x11CBEventThread(const XEvent * xe)
|
||||
{
|
||||
@@ -83,9 +98,6 @@ bool x11CBEventThread(const XEvent * xe)
|
||||
|
||||
if (xe->xproperty.atom == x11atoms.SEL_DATA)
|
||||
{
|
||||
if (x11cb.lowerBound == 0)
|
||||
return true;
|
||||
|
||||
x11CBSelectionIncr(xe->xproperty);
|
||||
return true;
|
||||
}
|
||||
@@ -106,6 +118,7 @@ bool x11CBEventThread(const XEvent * xe)
|
||||
|
||||
bool x11CBInit(void)
|
||||
{
|
||||
LG_LOCK_INIT(x11cb.lock);
|
||||
x11cb.aCurSelection = BadValue;
|
||||
for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
{
|
||||
@@ -131,7 +144,7 @@ bool x11CBInit(void)
|
||||
}
|
||||
|
||||
static void x11CBReplyFn(void * opaque, LG_ClipboardData type,
|
||||
uint8_t * data, uint32_t size)
|
||||
const uint8_t * data, uint32_t size)
|
||||
{
|
||||
XEvent *s = (XEvent *)opaque;
|
||||
|
||||
@@ -169,7 +182,12 @@ static void x11CBSelectionRequest(const XSelectionRequestEvent e)
|
||||
s->xselection.property = e.property;
|
||||
s->xselection.time = e.time;
|
||||
|
||||
if (!x11cb.haveRequest)
|
||||
LG_LOCK(x11cb.lock);
|
||||
const bool haveRequest = x11cb.haveRequest;
|
||||
const LG_ClipboardData requestType = x11cb.type;
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
|
||||
if (!haveRequest)
|
||||
goto nodata;
|
||||
|
||||
// target list requested
|
||||
@@ -177,7 +195,7 @@ static void x11CBSelectionRequest(const XSelectionRequestEvent e)
|
||||
{
|
||||
Atom targets[2];
|
||||
targets[0] = x11atoms.TARGETS;
|
||||
targets[1] = x11cb.aTypes[x11cb.type];
|
||||
targets[1] = x11cb.aTypes[requestType];
|
||||
|
||||
XChangeProperty(
|
||||
e.display,
|
||||
@@ -195,10 +213,10 @@ static void x11CBSelectionRequest(const XSelectionRequestEvent e)
|
||||
|
||||
// look to see if we can satisfy the data type
|
||||
for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
if (x11cb.aTypes[i] == e.target && x11cb.type == i)
|
||||
if (x11cb.aTypes[i] == e.target && requestType == i)
|
||||
{
|
||||
// request the data
|
||||
if (app_clipboardRequest(x11CBReplyFn, s))
|
||||
if (app_clipboardRequest(requestType, x11CBReplyFn, s))
|
||||
return;
|
||||
goto nodata;
|
||||
}
|
||||
@@ -215,20 +233,53 @@ send:
|
||||
|
||||
static void x11CBSelectionClear(const XSelectionClearEvent e)
|
||||
{
|
||||
if (e.selection != x11atoms.CLIPBOARD)
|
||||
(void)e;
|
||||
}
|
||||
|
||||
/* x11cb.lock must be held. */
|
||||
static struct X11ClipboardRead clearReadNL(void)
|
||||
{
|
||||
const struct X11ClipboardRead read = x11cb.read;
|
||||
x11cb.read = (struct X11ClipboardRead) { 0 };
|
||||
if (read.window)
|
||||
XDestroyWindow(x11.display, read.window);
|
||||
return read;
|
||||
}
|
||||
|
||||
/* x11cb.lock must be held. */
|
||||
static LG_ClipboardRequest cancelReadNL(void)
|
||||
{
|
||||
const struct X11ClipboardRead read = clearReadNL();
|
||||
free(read.buffer);
|
||||
return read.request;
|
||||
}
|
||||
|
||||
/* x11cb.lock must be held. */
|
||||
static void clearTargetsNL(void)
|
||||
{
|
||||
if (!x11cb.targetsWindow)
|
||||
return;
|
||||
|
||||
x11cb.aCurSelection = BadValue;
|
||||
app_clipboardRelease();
|
||||
return;
|
||||
XDestroyWindow(x11.display, x11cb.targetsWindow);
|
||||
x11cb.targetsWindow = 0;
|
||||
}
|
||||
|
||||
static void x11CBSelectionIncr(const XPropertyEvent e)
|
||||
{
|
||||
Atom type;
|
||||
int format;
|
||||
unsigned long itemCount, after;
|
||||
unsigned char *data;
|
||||
unsigned long itemCount;
|
||||
unsigned long after;
|
||||
unsigned char * data = NULL;
|
||||
|
||||
LG_LOCK(x11cb.lock);
|
||||
if (!x11cb.read.window || !x11cb.read.incremental ||
|
||||
e.window != x11cb.read.window ||
|
||||
e.atom != x11atoms.SEL_DATA)
|
||||
{
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
return;
|
||||
}
|
||||
|
||||
if (XGetWindowProperty(
|
||||
e.display,
|
||||
@@ -236,49 +287,7 @@ static void x11CBSelectionIncr(const XPropertyEvent e)
|
||||
e.atom,
|
||||
0, ~0L, // start and length
|
||||
True, // delete the property
|
||||
x11atoms.INCR,
|
||||
&type,
|
||||
&format,
|
||||
&itemCount,
|
||||
&after,
|
||||
&data) != Success)
|
||||
{
|
||||
DEBUG_INFO("GetProp Failed");
|
||||
app_clipboardNotifySize(LG_CLIPBOARD_DATA_NONE, 0);
|
||||
goto out;
|
||||
}
|
||||
|
||||
LG_ClipboardData dataType;
|
||||
for(dataType = 0; dataType < LG_CLIPBOARD_DATA_NONE; ++dataType)
|
||||
if (x11cb.aTypes[dataType] == type)
|
||||
break;
|
||||
|
||||
if (dataType == LG_CLIPBOARD_DATA_NONE)
|
||||
{
|
||||
DEBUG_WARN("clipboard data (%s) not in a supported format",
|
||||
XGetAtomName(x11.display, type));
|
||||
|
||||
x11cb.lowerBound = 0;
|
||||
app_clipboardNotifySize(LG_CLIPBOARD_DATA_NONE, 0);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (x11cb.incrStart)
|
||||
{
|
||||
app_clipboardNotifySize(dataType, x11cb.lowerBound);
|
||||
x11cb.incrStart = false;
|
||||
}
|
||||
|
||||
XFree(data);
|
||||
data = NULL;
|
||||
|
||||
if (XGetWindowProperty(
|
||||
e.display,
|
||||
e.window,
|
||||
e.atom,
|
||||
0, ~0L, // start and length
|
||||
True, // delete the property
|
||||
type,
|
||||
AnyPropertyType,
|
||||
&type,
|
||||
&format,
|
||||
&itemCount,
|
||||
@@ -286,49 +295,233 @@ static void x11CBSelectionIncr(const XPropertyEvent e)
|
||||
&data) != Success)
|
||||
{
|
||||
DEBUG_ERROR("XGetWindowProperty Failed");
|
||||
app_clipboardNotifySize(LG_CLIPBOARD_DATA_NONE, 0);
|
||||
goto out;
|
||||
const struct X11ClipboardRead read = clearReadNL();
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
free(read.buffer);
|
||||
app_clipboardAbort(read.request);
|
||||
return;
|
||||
}
|
||||
|
||||
app_clipboardData(dataType, data, itemCount);
|
||||
x11cb.lowerBound -= itemCount;
|
||||
LG_ClipboardData dataType;
|
||||
for(dataType = 0; dataType < LG_CLIPBOARD_DATA_NONE; ++dataType)
|
||||
if (x11cb.aTypes[dataType] == type)
|
||||
break;
|
||||
|
||||
out:
|
||||
if ((itemCount && !data) || dataType == LG_CLIPBOARD_DATA_NONE ||
|
||||
dataType != x11cb.read.type || format != 8)
|
||||
{
|
||||
DEBUG_WARN("Invalid incremental clipboard data");
|
||||
const struct X11ClipboardRead read = clearReadNL();
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
if (data)
|
||||
XFree(data);
|
||||
free(read.buffer);
|
||||
app_clipboardAbort(read.request);
|
||||
return;
|
||||
}
|
||||
|
||||
if (itemCount == 0)
|
||||
{
|
||||
const struct X11ClipboardRead read = clearReadNL();
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
if (data)
|
||||
XFree(data);
|
||||
app_clipboardData(
|
||||
read.request, read.type, read.buffer, read.size);
|
||||
free(read.buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
if (itemCount > SIZE_MAX - x11cb.read.size)
|
||||
{
|
||||
const struct X11ClipboardRead read = clearReadNL();
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
XFree(data);
|
||||
free(read.buffer);
|
||||
app_clipboardAbort(read.request);
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t required = x11cb.read.size + itemCount;
|
||||
if (required > x11cb.read.capacity)
|
||||
{
|
||||
size_t capacity = x11cb.read.capacity;
|
||||
while (capacity < required)
|
||||
{
|
||||
if (capacity > SIZE_MAX / 2)
|
||||
{
|
||||
capacity = required;
|
||||
break;
|
||||
}
|
||||
capacity *= 2;
|
||||
}
|
||||
|
||||
uint8_t * buffer = realloc(x11cb.read.buffer, capacity);
|
||||
if (!buffer)
|
||||
{
|
||||
const struct X11ClipboardRead read = clearReadNL();
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
XFree(data);
|
||||
free(read.buffer);
|
||||
app_clipboardAbort(read.request);
|
||||
return;
|
||||
}
|
||||
x11cb.read.buffer = buffer;
|
||||
x11cb.read.capacity = capacity;
|
||||
}
|
||||
|
||||
memcpy(x11cb.read.buffer + x11cb.read.size, data, itemCount);
|
||||
x11cb.read.size += itemCount;
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
if (data)
|
||||
XFree(data);
|
||||
}
|
||||
|
||||
static void x11CBXFixesSelectionNotify(const XFixesSelectionNotifyEvent e)
|
||||
{
|
||||
// check if the selection is valid and it isn't ourself
|
||||
if (e.selection != x11atoms.CLIPBOARD ||
|
||||
e.owner == x11.window || e.owner == 0)
|
||||
if (e.selection != x11atoms.CLIPBOARD || e.owner == x11.window)
|
||||
return;
|
||||
|
||||
LG_LOCK(x11cb.lock);
|
||||
XGrabServer(x11.display);
|
||||
if (XGetSelectionOwner(x11.display, x11atoms.CLIPBOARD) != e.owner)
|
||||
{
|
||||
XUngrabServer(x11.display);
|
||||
XFlush(x11.display);
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
return;
|
||||
}
|
||||
|
||||
const LG_ClipboardRequest oldRequest = x11cb.read.window ?
|
||||
cancelReadNL() : LG_CLIPBOARD_REQUEST_INVALID;
|
||||
clearTargetsNL();
|
||||
|
||||
if (e.owner == 0)
|
||||
{
|
||||
x11cb.aCurSelection = BadValue;
|
||||
XUngrabServer(x11.display);
|
||||
XFlush(x11.display);
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
if (oldRequest != LG_CLIPBOARD_REQUEST_INVALID)
|
||||
app_clipboardAbort(oldRequest);
|
||||
app_clipboardRelease();
|
||||
return;
|
||||
}
|
||||
|
||||
// remember which selection we are working with
|
||||
x11cb.aCurSelection = e.selection;
|
||||
x11cb.targetsWindow = XCreateSimpleWindow(
|
||||
x11.display, x11.window, 0, 0, 1, 1, 0, 0, 0);
|
||||
if (!x11cb.targetsWindow)
|
||||
{
|
||||
x11cb.aCurSelection = BadValue;
|
||||
XUngrabServer(x11.display);
|
||||
XFlush(x11.display);
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
if (oldRequest != LG_CLIPBOARD_REQUEST_INVALID)
|
||||
app_clipboardAbort(oldRequest);
|
||||
app_clipboardRelease();
|
||||
return;
|
||||
}
|
||||
|
||||
XConvertSelection(
|
||||
x11.display,
|
||||
e.selection,
|
||||
x11atoms.TARGETS,
|
||||
x11atoms.TARGETS,
|
||||
x11.window,
|
||||
x11cb.targetsWindow,
|
||||
CurrentTime);
|
||||
XUngrabServer(x11.display);
|
||||
XFlush(x11.display);
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
|
||||
return;
|
||||
if (oldRequest != LG_CLIPBOARD_REQUEST_INVALID)
|
||||
app_clipboardAbort(oldRequest);
|
||||
}
|
||||
|
||||
static void x11CBSelectionNotify(const XSelectionEvent e)
|
||||
{
|
||||
if (e.property == None)
|
||||
return;
|
||||
|
||||
Atom type;
|
||||
int format;
|
||||
unsigned long itemCount, after;
|
||||
unsigned char *data;
|
||||
unsigned long itemCount;
|
||||
unsigned long after;
|
||||
unsigned char * data = NULL;
|
||||
|
||||
LG_LOCK(x11cb.lock);
|
||||
const bool targetReply = x11cb.targetsWindow &&
|
||||
e.requestor == x11cb.targetsWindow && e.target == x11atoms.TARGETS;
|
||||
if (targetReply)
|
||||
{
|
||||
if (e.property == None)
|
||||
{
|
||||
clearTargetsNL();
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
app_clipboardRelease();
|
||||
return;
|
||||
}
|
||||
|
||||
if (XGetWindowProperty(
|
||||
e.display,
|
||||
e.requestor,
|
||||
e.property,
|
||||
0, ~0L, // start and length
|
||||
True, // delete the property
|
||||
AnyPropertyType,
|
||||
&type,
|
||||
&format,
|
||||
&itemCount,
|
||||
&after,
|
||||
&data) != Success)
|
||||
{
|
||||
clearTargetsNL();
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
app_clipboardRelease();
|
||||
return;
|
||||
}
|
||||
clearTargetsNL();
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
|
||||
// the format is 32-bit and we must have data
|
||||
// this is technically incorrect however as it's
|
||||
// an array of padded 64-bit values
|
||||
if (!data || format != 32)
|
||||
{
|
||||
app_clipboardRelease();
|
||||
goto out;
|
||||
}
|
||||
|
||||
int typeCount = 0;
|
||||
LG_ClipboardData types[LG_CLIPBOARD_DATA_NONE];
|
||||
|
||||
// see if we support any of the targets listed
|
||||
const Atom * targets = (const Atom *)data;
|
||||
for(int n = 0; n < LG_CLIPBOARD_DATA_NONE; ++n)
|
||||
for(unsigned long i = 0; i < itemCount; ++i)
|
||||
if (x11cb.aTypes[n] == targets[i])
|
||||
{
|
||||
types[typeCount++] = n;
|
||||
break;
|
||||
}
|
||||
|
||||
app_clipboardNotifyTypes(types, typeCount);
|
||||
goto out;
|
||||
}
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
|
||||
LG_LOCK(x11cb.lock);
|
||||
if (!x11cb.read.window || e.requestor != x11cb.read.window)
|
||||
{
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.property == None)
|
||||
{
|
||||
const LG_ClipboardRequest request = cancelReadNL();
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
app_clipboardAbort(request);
|
||||
return;
|
||||
}
|
||||
|
||||
if (XGetWindowProperty(
|
||||
e.display,
|
||||
@@ -343,60 +536,65 @@ static void x11CBSelectionNotify(const XSelectionEvent e)
|
||||
&after,
|
||||
&data) != Success)
|
||||
{
|
||||
app_clipboardNotifySize(LG_CLIPBOARD_DATA_NONE, 0);
|
||||
goto out;
|
||||
const LG_ClipboardRequest request = cancelReadNL();
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
app_clipboardAbort(request);
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == x11atoms.INCR)
|
||||
{
|
||||
x11cb.incrStart = true;
|
||||
x11cb.lowerBound = *(unsigned int *)data;
|
||||
goto out;
|
||||
}
|
||||
|
||||
// the target list
|
||||
if (e.property == x11atoms.TARGETS)
|
||||
{
|
||||
// the format is 32-bit and we must have data
|
||||
// this is technically incorrect however as it's
|
||||
// an array of padded 64-bit values
|
||||
if (!data || format != 32)
|
||||
goto out;
|
||||
|
||||
int typeCount = 0;
|
||||
LG_ClipboardData types[itemCount];
|
||||
|
||||
// see if we support any of the targets listed
|
||||
const uint64_t * targets = (const uint64_t *)data;
|
||||
for(unsigned long i = 0; i < itemCount; ++i)
|
||||
if (!data || format != 32 || itemCount < 1)
|
||||
{
|
||||
for(int n = 0; n < LG_CLIPBOARD_DATA_NONE; ++n)
|
||||
if (x11cb.aTypes[n] == targets[i])
|
||||
types[typeCount++] = n;
|
||||
const LG_ClipboardRequest request = cancelReadNL();
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
if (data)
|
||||
XFree(data);
|
||||
app_clipboardAbort(request);
|
||||
return;
|
||||
}
|
||||
|
||||
app_clipboardNotifyTypes(types, typeCount);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (e.property == x11atoms.SEL_DATA)
|
||||
{
|
||||
LG_ClipboardData dataType;
|
||||
for(dataType = 0; dataType < LG_CLIPBOARD_DATA_NONE; ++dataType)
|
||||
if (x11cb.aTypes[dataType] == type)
|
||||
break;
|
||||
|
||||
if (dataType == LG_CLIPBOARD_DATA_NONE)
|
||||
size_t capacity = *(const unsigned long *)data;
|
||||
if (capacity < 4096)
|
||||
capacity = 4096;
|
||||
if (capacity > 1048576)
|
||||
capacity = 1048576;
|
||||
x11cb.read.buffer = malloc(capacity);
|
||||
if (!x11cb.read.buffer)
|
||||
{
|
||||
DEBUG_WARN("clipboard data (%s) not in a supported format",
|
||||
XGetAtomName(x11.display, type));
|
||||
goto out;
|
||||
const LG_ClipboardRequest request = cancelReadNL();
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
XFree(data);
|
||||
app_clipboardAbort(request);
|
||||
return;
|
||||
}
|
||||
x11cb.read.capacity = capacity;
|
||||
x11cb.read.incremental = true;
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
XFree(data);
|
||||
return;
|
||||
}
|
||||
|
||||
app_clipboardData(dataType, data, itemCount);
|
||||
LG_ClipboardData dataType;
|
||||
for(dataType = 0; dataType < LG_CLIPBOARD_DATA_NONE; ++dataType)
|
||||
if (x11cb.aTypes[dataType] == type)
|
||||
break;
|
||||
|
||||
const LG_ClipboardRequest request = x11cb.read.request;
|
||||
const bool valid = dataType != LG_CLIPBOARD_DATA_NONE &&
|
||||
dataType == x11cb.read.type && format == 8;
|
||||
cancelReadNL();
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
|
||||
if (!valid)
|
||||
{
|
||||
DEBUG_WARN("Invalid clipboard data");
|
||||
app_clipboardAbort(request);
|
||||
goto out;
|
||||
}
|
||||
|
||||
app_clipboardData(request, dataType, data, itemCount);
|
||||
|
||||
out:
|
||||
if (data)
|
||||
XFree(data);
|
||||
@@ -404,29 +602,79 @@ out:
|
||||
|
||||
void x11CBNotice(LG_ClipboardData type)
|
||||
{
|
||||
x11cb.haveRequest = true;
|
||||
x11cb.type = type;
|
||||
LG_LOCK(x11cb.lock);
|
||||
const LG_ClipboardRequest oldRequest = x11cb.read.window ?
|
||||
cancelReadNL() : LG_CLIPBOARD_REQUEST_INVALID;
|
||||
clearTargetsNL();
|
||||
x11cb.aCurSelection = BadValue;
|
||||
x11cb.haveRequest = true;
|
||||
x11cb.type = type;
|
||||
XSetSelectionOwner(x11.display, x11atoms.CLIPBOARD, x11.window, CurrentTime);
|
||||
XFlush(x11.display);
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
if (oldRequest != LG_CLIPBOARD_REQUEST_INVALID)
|
||||
app_clipboardAbort(oldRequest);
|
||||
}
|
||||
|
||||
void x11CBRelease(void)
|
||||
{
|
||||
LG_LOCK(x11cb.lock);
|
||||
x11cb.haveRequest = false;
|
||||
XSetSelectionOwner(x11.display, x11atoms.CLIPBOARD, None, CurrentTime);
|
||||
XGrabServer(x11.display);
|
||||
if (XGetSelectionOwner(x11.display, x11atoms.CLIPBOARD) == x11.window)
|
||||
XSetSelectionOwner(
|
||||
x11.display, x11atoms.CLIPBOARD, None, CurrentTime);
|
||||
XUngrabServer(x11.display);
|
||||
XFlush(x11.display);
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
}
|
||||
|
||||
void x11CBRequest(LG_ClipboardData type)
|
||||
void x11CBRequest(LG_ClipboardRequest request, LG_ClipboardData type)
|
||||
{
|
||||
if (x11cb.aCurSelection == BadValue)
|
||||
if (request == LG_CLIPBOARD_REQUEST_INVALID ||
|
||||
type < LG_CLIPBOARD_DATA_TEXT || type >= LG_CLIPBOARD_DATA_NONE)
|
||||
return;
|
||||
|
||||
LG_LOCK(x11cb.lock);
|
||||
const LG_ClipboardRequest oldRequest = x11cb.read.window ?
|
||||
cancelReadNL() : LG_CLIPBOARD_REQUEST_INVALID;
|
||||
if (x11cb.aCurSelection == BadValue)
|
||||
{
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
if (oldRequest != LG_CLIPBOARD_REQUEST_INVALID)
|
||||
app_clipboardAbort(oldRequest);
|
||||
app_clipboardAbort(request);
|
||||
return;
|
||||
}
|
||||
|
||||
const Window window = XCreateSimpleWindow(
|
||||
x11.display, x11.window, 0, 0, 1, 1, 0, 0, 0);
|
||||
if (!window)
|
||||
{
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
if (oldRequest != LG_CLIPBOARD_REQUEST_INVALID)
|
||||
app_clipboardAbort(oldRequest);
|
||||
app_clipboardAbort(request);
|
||||
return;
|
||||
}
|
||||
|
||||
XSelectInput(x11.display, window, PropertyChangeMask);
|
||||
x11cb.read = (struct X11ClipboardRead)
|
||||
{
|
||||
.window = window,
|
||||
.request = request,
|
||||
.type = type,
|
||||
};
|
||||
XConvertSelection(
|
||||
x11.display,
|
||||
x11cb.aCurSelection,
|
||||
x11cb.aTypes[type],
|
||||
x11atoms.SEL_DATA,
|
||||
x11.window,
|
||||
window,
|
||||
CurrentTime);
|
||||
XFlush(x11.display);
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
|
||||
if (oldRequest != LG_CLIPBOARD_REQUEST_INVALID)
|
||||
app_clipboardAbort(oldRequest);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,6 @@ bool x11CBEventThread(const XEvent * xe);
|
||||
bool x11CBInit(void);
|
||||
void x11CBNotice(LG_ClipboardData type);
|
||||
void x11CBRelease(void);
|
||||
void x11CBRequest(LG_ClipboardData type);
|
||||
void x11CBRequest(LG_ClipboardRequest request, LG_ClipboardData type);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -165,9 +165,11 @@ void app_overlayConfigRegisterTab(const char * title,
|
||||
|
||||
void app_clipboardRelease(void);
|
||||
void app_clipboardNotifyTypes(const LG_ClipboardData types[], int count);
|
||||
void app_clipboardNotifySize(const LG_ClipboardData type, size_t size);
|
||||
void app_clipboardData(const LG_ClipboardData type, uint8_t * data, size_t size);
|
||||
bool app_clipboardRequest(const LG_ClipboardReplyFn replyFn, void * opaque);
|
||||
void app_clipboardData(LG_ClipboardRequest request,
|
||||
LG_ClipboardData type, const void * data, size_t size);
|
||||
void app_clipboardAbort(LG_ClipboardRequest request);
|
||||
bool app_clipboardRequest(LG_ClipboardData type,
|
||||
LG_ClipboardReplyFn replyFn, void * opaque);
|
||||
|
||||
/**
|
||||
* Show an alert on screen
|
||||
|
||||
108
client/include/interface/clipboard.h
Normal file
108
client/include/interface/clipboard.h
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* 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_CLIPBOARD_INTERFACE_
|
||||
#define _H_LG_CLIENT_CLIPBOARD_INTERFACE_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum LG_ClipboardData
|
||||
{
|
||||
LG_CLIPBOARD_DATA_TEXT = 0,
|
||||
LG_CLIPBOARD_DATA_PNG,
|
||||
LG_CLIPBOARD_DATA_BMP,
|
||||
LG_CLIPBOARD_DATA_TIFF,
|
||||
LG_CLIPBOARD_DATA_JPEG,
|
||||
|
||||
LG_CLIPBOARD_DATA_NONE
|
||||
}
|
||||
LG_ClipboardData;
|
||||
|
||||
typedef void (*LG_ClipboardReplyFn)(void * opaque,
|
||||
LG_ClipboardData type, const uint8_t * data, uint32_t size);
|
||||
|
||||
typedef uint64_t LG_ClipboardRequest;
|
||||
|
||||
#define LG_CLIPBOARD_REQUEST_INVALID UINT64_C(0)
|
||||
|
||||
typedef struct LG_ClipboardStatus
|
||||
{
|
||||
bool available;
|
||||
uint32_t generation;
|
||||
}
|
||||
LG_ClipboardStatus;
|
||||
|
||||
typedef void (*LG_ClipboardStatusFn)(void * opaque,
|
||||
const LG_ClipboardStatus * status);
|
||||
|
||||
typedef struct LG_ClipboardEventOps
|
||||
{
|
||||
/* The type list and data are borrowed for the duration of the callback.
|
||||
* Event callbacks are always invoked after releasing backend locks. */
|
||||
void (*notice)(void * opaque, const LG_ClipboardData types[], size_t count);
|
||||
void (*data)(void * opaque, LG_ClipboardRequest request,
|
||||
LG_ClipboardData type, const void * data, size_t size);
|
||||
void (*release)(void * opaque);
|
||||
bool (*request)(void * opaque, LG_ClipboardRequest request,
|
||||
LG_ClipboardData type);
|
||||
}
|
||||
LG_ClipboardEventOps;
|
||||
|
||||
typedef struct LG_ClipboardOps
|
||||
{
|
||||
const char * name;
|
||||
|
||||
/* Operations must fail safely if the remote endpoint disappears. */
|
||||
|
||||
/* Registration must synchronously report the current status after releasing
|
||||
* any backend locks. Status callbacks must be serialized and generations
|
||||
* must advance whenever availability changes. Passing NULL unregisters the
|
||||
* listener and synchronously quiesces its callbacks. Status callbacks must
|
||||
* not be delivered from another operation or event callback. */
|
||||
void (*setStatusListener)(void * opaque, LG_ClipboardStatusFn callback,
|
||||
void * callbackOpaque);
|
||||
|
||||
/* Attach begins serialized event delivery and may synchronously replay the
|
||||
* current remote notice. Detach synchronously quiesces event callbacks.
|
||||
* Other operations must not deliver event callbacks synchronously. */
|
||||
bool (*attach)(void * opaque, const LG_ClipboardEventOps * events,
|
||||
void * eventOpaque);
|
||||
void (*detach)(void * opaque);
|
||||
|
||||
bool (*release)(void * opaque);
|
||||
/* All outbound arrays and data are borrowed only until the call returns. */
|
||||
bool (*notifyTypes)(void * opaque, const LG_ClipboardData types[],
|
||||
size_t count);
|
||||
/* Data is a complete response to a remote request. NONE with no payload
|
||||
* reports that the request could not be completed. The provider must finish
|
||||
* any transport framing before returning. */
|
||||
bool (*data)(void * opaque, LG_ClipboardRequest request,
|
||||
LG_ClipboardData type, const void * data, size_t size);
|
||||
/* A successful request produces exactly one matching data event unless the
|
||||
* provider is detached, becomes unavailable, or publishes a newer notice
|
||||
* or release first. */
|
||||
bool (*request)(void * opaque, LG_ClipboardRequest request,
|
||||
LG_ClipboardData type);
|
||||
}
|
||||
LG_ClipboardOps;
|
||||
|
||||
#endif
|
||||
@@ -26,18 +26,7 @@
|
||||
#include <EGL/egl.h>
|
||||
#include "common/types.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
typedef enum LG_ClipboardData
|
||||
{
|
||||
LG_CLIPBOARD_DATA_TEXT = 0,
|
||||
LG_CLIPBOARD_DATA_PNG,
|
||||
LG_CLIPBOARD_DATA_BMP,
|
||||
LG_CLIPBOARD_DATA_TIFF,
|
||||
LG_CLIPBOARD_DATA_JPEG,
|
||||
|
||||
LG_CLIPBOARD_DATA_NONE // enum max, not a data type
|
||||
}
|
||||
LG_ClipboardData;
|
||||
#include "interface/clipboard.h"
|
||||
|
||||
typedef enum LG_DSProperty
|
||||
{
|
||||
@@ -136,9 +125,6 @@ typedef struct LG_DSInitParams
|
||||
}
|
||||
LG_DSInitParams;
|
||||
|
||||
typedef void (* LG_ClipboardReplyFn)(void * opaque, const LG_ClipboardData type,
|
||||
uint8_t * data, uint32_t size);
|
||||
|
||||
typedef struct LG_DSGLContext
|
||||
* LG_DSGLContext;
|
||||
|
||||
@@ -262,7 +248,7 @@ struct LG_DisplayServerOps
|
||||
bool (*cbInit)(void);
|
||||
void (*cbNotice)(LG_ClipboardData type);
|
||||
void (*cbRelease)(void);
|
||||
void (*cbRequest)(LG_ClipboardData type);
|
||||
void (*cbRequest)(LG_ClipboardRequest request, LG_ClipboardData type);
|
||||
};
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "common/framebuffer.h"
|
||||
#include "common/types.h"
|
||||
#include "interface/audio.h"
|
||||
#include "interface/clipboard.h"
|
||||
#include "interface/input.h"
|
||||
|
||||
#define LG_TRANSPORT_MAX_DAMAGE_RECTS LG_MAX_FRAME_DAMAGE_RECTS
|
||||
@@ -255,6 +256,11 @@ typedef struct LG_TransportOps
|
||||
* valid until disconnect; NULL indicates that this session has no audio. */
|
||||
const LG_AudioOps *(*getAudioOps)(LG_Transport * transport,
|
||||
void ** opaque);
|
||||
/* Queried after connect. The returned operations and opaque value remain
|
||||
* valid until disconnect; NULL indicates that this session has no
|
||||
* clipboard. */
|
||||
const LG_ClipboardOps *(*getClipboardOps)(LG_Transport * transport,
|
||||
void ** opaque);
|
||||
bool (*attachRenderer)(LG_Transport * transport,
|
||||
const LG_RendererInterop * interop);
|
||||
void (*detachRenderer)(LG_Transport * transport);
|
||||
|
||||
139
client/src/app.c
139
client/src/app.c
@@ -299,148 +299,31 @@ void app_handleEnterEvent(bool entered)
|
||||
|
||||
void app_clipboardRelease(void)
|
||||
{
|
||||
if (!g_params.clipboardToVM)
|
||||
return;
|
||||
|
||||
purespice_clipboardRelease();
|
||||
lgClipboard_release();
|
||||
}
|
||||
|
||||
void app_clipboardNotifyTypes(const LG_ClipboardData types[], int count)
|
||||
{
|
||||
if (!g_params.clipboardToVM)
|
||||
if (count < 0)
|
||||
return;
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
purespice_clipboardRelease();
|
||||
return;
|
||||
}
|
||||
|
||||
PSDataType conv[count];
|
||||
for(int i = 0; i < count; ++i)
|
||||
conv[i] = cb_lgTypeToSpiceType(types[i]);
|
||||
|
||||
purespice_clipboardGrab(conv, count);
|
||||
lgClipboard_notifyTypes(types, (size_t)count);
|
||||
}
|
||||
|
||||
void app_clipboardNotifySize(const LG_ClipboardData type, size_t size)
|
||||
void app_clipboardData(LG_ClipboardRequest request,
|
||||
const LG_ClipboardData type, const void * data, size_t size)
|
||||
{
|
||||
if (!g_params.clipboardToVM)
|
||||
return;
|
||||
|
||||
if (type == LG_CLIPBOARD_DATA_NONE)
|
||||
{
|
||||
purespice_clipboardRelease();
|
||||
return;
|
||||
}
|
||||
|
||||
const PSDataType spiceType = cb_lgTypeToSpiceType(type);
|
||||
if (spiceType == SPICE_DATA_NONE)
|
||||
return;
|
||||
|
||||
if (!purespice_clipboardDataStart(spiceType, size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to start a %zu-byte SPICE clipboard transfer", size);
|
||||
return;
|
||||
}
|
||||
|
||||
g_state.cbWriteType = spiceType;
|
||||
g_state.cbChunked = true;
|
||||
g_state.cbXfer = size;
|
||||
lgClipboard_data(request, type, data, size);
|
||||
}
|
||||
|
||||
void app_clipboardData(const LG_ClipboardData type, uint8_t * data, size_t size)
|
||||
void app_clipboardAbort(LG_ClipboardRequest request)
|
||||
{
|
||||
if (!g_params.clipboardToVM)
|
||||
return;
|
||||
|
||||
const PSDataType spiceType = cb_lgTypeToSpiceType(type);
|
||||
if (spiceType == SPICE_DATA_NONE)
|
||||
return;
|
||||
|
||||
if (size && !data)
|
||||
{
|
||||
DEBUG_ERROR("SPICE clipboard data is NULL");
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_state.cbChunked)
|
||||
{
|
||||
if (spiceType != g_state.cbWriteType)
|
||||
{
|
||||
DEBUG_ERROR("SPICE clipboard transfer type changed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (size > g_state.cbXfer)
|
||||
{
|
||||
DEBUG_ERROR("SPICE clipboard chunk exceeds the remaining transfer size");
|
||||
return;
|
||||
}
|
||||
|
||||
if (size && !purespice_clipboardData(spiceType, data, size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to send SPICE clipboard data");
|
||||
return;
|
||||
}
|
||||
|
||||
g_state.cbXfer -= size;
|
||||
if (g_state.cbXfer == 0)
|
||||
{
|
||||
g_state.cbWriteType = SPICE_DATA_NONE;
|
||||
g_state.cbChunked = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!purespice_clipboardDataStart(spiceType, size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to start a %zu-byte SPICE clipboard transfer", size);
|
||||
return;
|
||||
}
|
||||
|
||||
if (size && !purespice_clipboardData(spiceType, data, size))
|
||||
DEBUG_ERROR("Failed to send SPICE clipboard data");
|
||||
lgClipboard_abort(request);
|
||||
}
|
||||
|
||||
bool app_clipboardRequest(const LG_ClipboardReplyFn replyFn, void * opaque)
|
||||
bool app_clipboardRequest(LG_ClipboardData type,
|
||||
LG_ClipboardReplyFn replyFn, void * opaque)
|
||||
{
|
||||
if (!g_params.clipboardToLocal || !replyFn || !g_state.cbRequestList)
|
||||
return false;
|
||||
|
||||
const PSDataType type = atomic_load_explicit(
|
||||
&g_state.cbRemoteType, memory_order_acquire);
|
||||
struct CBRequest * cbr = malloc(sizeof(*cbr));
|
||||
if (!cbr)
|
||||
{
|
||||
DEBUG_ERROR("out of memory");
|
||||
return false;
|
||||
}
|
||||
|
||||
cbr->type = type;
|
||||
cbr->replyFn = replyFn;
|
||||
cbr->opaque = opaque;
|
||||
|
||||
if (!ll_push(g_state.cbRequestList, cbr))
|
||||
{
|
||||
free(cbr);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (purespice_clipboardRequest(type))
|
||||
return true;
|
||||
|
||||
/*
|
||||
* Queue the callback before sending so a fast response cannot arrive before
|
||||
* its request is visible. If another thread has already consumed it, the
|
||||
* callback owns both cbr and opaque and the request completed successfully
|
||||
* from the caller's perspective.
|
||||
*/
|
||||
if (!ll_removeData(g_state.cbRequestList, cbr))
|
||||
return true;
|
||||
|
||||
free(cbr);
|
||||
return false;
|
||||
return lgClipboard_request(type, replyFn, opaque);
|
||||
}
|
||||
|
||||
static int mapSpiceToImGuiButton(uint32_t button)
|
||||
|
||||
@@ -19,113 +19,885 @@
|
||||
*/
|
||||
|
||||
#include "clipboard.h"
|
||||
|
||||
#include "main.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/ll.h"
|
||||
#include "common/locking.h"
|
||||
|
||||
LG_ClipboardData cb_spiceTypeToLGType(const PSDataType type)
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct ClipboardBinding
|
||||
{
|
||||
switch(type)
|
||||
const LG_ClipboardOps * ops;
|
||||
void * opaque;
|
||||
bool available;
|
||||
uint32_t generation;
|
||||
}
|
||||
ClipboardBinding;
|
||||
|
||||
typedef struct ClipboardRequest
|
||||
{
|
||||
LG_ClipboardRequest request;
|
||||
ClipboardBinding binding;
|
||||
uint32_t remoteGeneration;
|
||||
LG_ClipboardData type;
|
||||
LG_ClipboardReplyFn replyFn;
|
||||
void * opaque;
|
||||
}
|
||||
ClipboardRequest;
|
||||
|
||||
static struct
|
||||
{
|
||||
LG_Lock registrationLock;
|
||||
LG_Lock providerLock;
|
||||
LG_RWLock activeLock;
|
||||
LG_Lock stateLock;
|
||||
LG_Lock requestLock;
|
||||
LG_Lock writeLock;
|
||||
LG_Lock callbackLock;
|
||||
|
||||
ClipboardBinding fallback;
|
||||
ClipboardBinding transport;
|
||||
ClipboardBinding active;
|
||||
|
||||
bool localAvailable;
|
||||
LG_ClipboardData localTypes[LG_CLIPBOARD_DATA_NONE];
|
||||
size_t localTypeCount;
|
||||
|
||||
bool remoteNotice;
|
||||
LG_ClipboardData remoteType;
|
||||
uint32_t remoteGeneration;
|
||||
|
||||
bool remoteRequest;
|
||||
ClipboardBinding remoteRequestBinding;
|
||||
LG_ClipboardRequest remoteRequestId;
|
||||
LG_ClipboardRequest remoteTransferId;
|
||||
LG_ClipboardData remoteRequestType;
|
||||
|
||||
LG_ClipboardRequest requestSerial;
|
||||
LG_ClipboardRequest transferSerial;
|
||||
struct ll * requests;
|
||||
}
|
||||
clipboard;
|
||||
|
||||
static bool validType(LG_ClipboardData type)
|
||||
{
|
||||
return type >= LG_CLIPBOARD_DATA_TEXT &&
|
||||
type < LG_CLIPBOARD_DATA_NONE;
|
||||
}
|
||||
|
||||
static uint32_t nextGeneration(uint32_t generation)
|
||||
{
|
||||
if (++generation == 0)
|
||||
++generation;
|
||||
return generation;
|
||||
}
|
||||
|
||||
static LG_ClipboardRequest nextRequestNL(void)
|
||||
{
|
||||
if (++clipboard.requestSerial == LG_CLIPBOARD_REQUEST_INVALID)
|
||||
++clipboard.requestSerial;
|
||||
return clipboard.requestSerial;
|
||||
}
|
||||
|
||||
static LG_ClipboardRequest nextTransferNL(void)
|
||||
{
|
||||
if (++clipboard.transferSerial == LG_CLIPBOARD_REQUEST_INVALID)
|
||||
++clipboard.transferSerial;
|
||||
return clipboard.transferSerial;
|
||||
}
|
||||
|
||||
static bool bindingEqual(const ClipboardBinding * a,
|
||||
const ClipboardBinding * b)
|
||||
{
|
||||
return a->ops == b->ops &&
|
||||
a->opaque == b->opaque &&
|
||||
a->generation == b->generation;
|
||||
}
|
||||
|
||||
static bool bindingActiveNL(const ClipboardBinding * binding)
|
||||
{
|
||||
return binding->ops && bindingEqual(binding, &clipboard.active);
|
||||
}
|
||||
|
||||
static bool localHasTypeNL(LG_ClipboardData type)
|
||||
{
|
||||
for (size_t i = 0; i < clipboard.localTypeCount; ++i)
|
||||
if (clipboard.localTypes[i] == type)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void clearRemoteRequestNL(void)
|
||||
{
|
||||
clipboard.remoteRequest = false;
|
||||
clipboard.remoteRequestBinding = (ClipboardBinding) { 0 };
|
||||
clipboard.remoteRequestId = LG_CLIPBOARD_REQUEST_INVALID;
|
||||
clipboard.remoteTransferId = LG_CLIPBOARD_REQUEST_INVALID;
|
||||
clipboard.remoteRequestType = LG_CLIPBOARD_DATA_NONE;
|
||||
}
|
||||
|
||||
static ClipboardRequest * takeCancelableRequest(
|
||||
const ClipboardBinding * binding, uint32_t generation, bool all)
|
||||
{
|
||||
if (!clipboard.requests)
|
||||
return NULL;
|
||||
|
||||
ClipboardRequest * request;
|
||||
ll_lock(clipboard.requests);
|
||||
ll_forEachNL(clipboard.requests, item, request)
|
||||
{
|
||||
case SPICE_DATA_TEXT: return LG_CLIPBOARD_DATA_TEXT; break;
|
||||
case SPICE_DATA_PNG : return LG_CLIPBOARD_DATA_PNG ; break;
|
||||
case SPICE_DATA_BMP : return LG_CLIPBOARD_DATA_BMP ; break;
|
||||
case SPICE_DATA_TIFF: return LG_CLIPBOARD_DATA_TIFF; break;
|
||||
case SPICE_DATA_JPEG: return LG_CLIPBOARD_DATA_JPEG; break;
|
||||
default:
|
||||
DEBUG_ERROR("invalid spice data type");
|
||||
return LG_CLIPBOARD_DATA_NONE;
|
||||
if (!all && bindingEqual(&request->binding, binding) &&
|
||||
request->remoteGeneration == generation)
|
||||
continue;
|
||||
|
||||
ll_removeNL(clipboard.requests, item);
|
||||
free(item);
|
||||
ll_unlock(clipboard.requests);
|
||||
return request;
|
||||
}
|
||||
ll_unlock(clipboard.requests);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static ClipboardRequest * takeRequest(LG_ClipboardRequest id)
|
||||
{
|
||||
if (!clipboard.requests)
|
||||
return NULL;
|
||||
|
||||
ClipboardRequest * request;
|
||||
ll_lock(clipboard.requests);
|
||||
ll_forEachNL(clipboard.requests, item, request)
|
||||
{
|
||||
if (request->request != id)
|
||||
continue;
|
||||
|
||||
ll_removeNL(clipboard.requests, item);
|
||||
free(item);
|
||||
ll_unlock(clipboard.requests);
|
||||
return request;
|
||||
}
|
||||
ll_unlock(clipboard.requests);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* callbackLock must be held while completing canceled requests. */
|
||||
static void cancelRequestsNL(const ClipboardBinding * binding,
|
||||
uint32_t generation, bool all)
|
||||
{
|
||||
ClipboardRequest * request;
|
||||
while ((request = takeCancelableRequest(binding, generation, all)))
|
||||
{
|
||||
request->replyFn(
|
||||
request->opaque, LG_CLIPBOARD_DATA_NONE, NULL, 0);
|
||||
free(request);
|
||||
}
|
||||
}
|
||||
|
||||
PSDataType cb_lgTypeToSpiceType(const LG_ClipboardData type)
|
||||
static void resetRemote(void)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case LG_CLIPBOARD_DATA_TEXT: return SPICE_DATA_TEXT; break;
|
||||
case LG_CLIPBOARD_DATA_PNG : return SPICE_DATA_PNG ; break;
|
||||
case LG_CLIPBOARD_DATA_BMP : return SPICE_DATA_BMP ; break;
|
||||
case LG_CLIPBOARD_DATA_TIFF: return SPICE_DATA_TIFF; break;
|
||||
case LG_CLIPBOARD_DATA_JPEG: return SPICE_DATA_JPEG; break;
|
||||
default:
|
||||
DEBUG_ERROR("invalid clipboard data type");
|
||||
return SPICE_DATA_NONE;
|
||||
}
|
||||
}
|
||||
bool release;
|
||||
|
||||
void cb_spiceNotice(const PSDataType type)
|
||||
{
|
||||
if (!g_params.clipboardToLocal)
|
||||
return;
|
||||
LG_LOCK(clipboard.requestLock);
|
||||
LG_LOCK(clipboard.stateLock);
|
||||
release = clipboard.remoteNotice && clipboard.localAvailable &&
|
||||
g_params.clipboardToLocal;
|
||||
clipboard.remoteNotice = false;
|
||||
clipboard.remoteType = LG_CLIPBOARD_DATA_NONE;
|
||||
clipboard.remoteGeneration = nextGeneration(
|
||||
clipboard.remoteGeneration);
|
||||
clearRemoteRequestNL();
|
||||
LG_UNLOCK(clipboard.stateLock);
|
||||
LG_UNLOCK(clipboard.requestLock);
|
||||
|
||||
if (!g_state.cbAvailable)
|
||||
return;
|
||||
|
||||
atomic_store_explicit(
|
||||
&g_state.cbRemoteType, type, memory_order_release);
|
||||
g_state.ds->cbNotice(cb_spiceTypeToLGType(type));
|
||||
}
|
||||
|
||||
void cb_spiceData(const PSDataType type, uint8_t * buffer, uint32_t size)
|
||||
{
|
||||
if (!g_params.clipboardToLocal)
|
||||
return;
|
||||
|
||||
struct CBRequest * cbr;
|
||||
if (!ll_shift(g_state.cbRequestList, (void **)&cbr))
|
||||
{
|
||||
DEBUG_ERROR("Received unsolicited SPICE clipboard data");
|
||||
return;
|
||||
}
|
||||
|
||||
if (type != cbr->type || (size && !buffer))
|
||||
{
|
||||
DEBUG_ERROR("Invalid SPICE clipboard response");
|
||||
cbr->replyFn(cbr->opaque, LG_CLIPBOARD_DATA_NONE, NULL, 0);
|
||||
free(cbr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == SPICE_DATA_TEXT)
|
||||
{
|
||||
// dos2unix
|
||||
uint8_t * p = buffer;
|
||||
uint32_t newSize = size;
|
||||
for(uint32_t i = 0; i < size; ++i)
|
||||
{
|
||||
uint8_t c = buffer[i];
|
||||
if (c == '\r')
|
||||
{
|
||||
--newSize;
|
||||
continue;
|
||||
}
|
||||
*p++ = c;
|
||||
}
|
||||
size = newSize;
|
||||
}
|
||||
|
||||
cbr->replyFn(cbr->opaque, cb_spiceTypeToLGType(type), buffer, size);
|
||||
free(cbr);
|
||||
}
|
||||
|
||||
void cb_spiceRelease(void)
|
||||
{
|
||||
if (!g_params.clipboardToLocal)
|
||||
return;
|
||||
|
||||
atomic_store_explicit(
|
||||
&g_state.cbRemoteType, SPICE_DATA_NONE, memory_order_release);
|
||||
|
||||
if (g_state.cbAvailable)
|
||||
LG_LOCK(clipboard.callbackLock);
|
||||
cancelRequestsNL(NULL, 0, true);
|
||||
LG_LOCK(clipboard.stateLock);
|
||||
release = release && clipboard.localAvailable &&
|
||||
!clipboard.remoteNotice;
|
||||
LG_UNLOCK(clipboard.stateLock);
|
||||
if (release)
|
||||
g_state.ds->cbRelease();
|
||||
LG_UNLOCK(clipboard.callbackLock);
|
||||
}
|
||||
|
||||
void cb_spiceRequest(const PSDataType type)
|
||||
static bool validOps(const LG_ClipboardOps * ops)
|
||||
{
|
||||
return ops && ops->name && ops->attach && ops->detach &&
|
||||
ops->release && ops->notifyTypes && ops->data && ops->request;
|
||||
}
|
||||
|
||||
static ClipboardBinding makeBinding(
|
||||
const LG_ClipboardOps * ops, void * opaque)
|
||||
{
|
||||
return (ClipboardBinding)
|
||||
{
|
||||
.ops = ops,
|
||||
.opaque = opaque,
|
||||
.available = ops && !ops->setStatusListener,
|
||||
.generation = 0,
|
||||
};
|
||||
}
|
||||
|
||||
static ClipboardBinding * nextBindingSlotNL(void)
|
||||
{
|
||||
if (clipboard.transport.available)
|
||||
return &clipboard.transport;
|
||||
if (clipboard.fallback.available)
|
||||
return &clipboard.fallback;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void publishLocal(const ClipboardBinding * binding)
|
||||
{
|
||||
LG_LOCK(clipboard.writeLock);
|
||||
LG_ClipboardData types[LG_CLIPBOARD_DATA_NONE];
|
||||
size_t count;
|
||||
|
||||
LG_LOCK(clipboard.stateLock);
|
||||
count = g_params.clipboardToVM ? clipboard.localTypeCount : 0;
|
||||
memcpy(types, clipboard.localTypes, count * sizeof(*types));
|
||||
LG_UNLOCK(clipboard.stateLock);
|
||||
|
||||
const bool result = count ?
|
||||
binding->ops->notifyTypes(binding->opaque, types, count) :
|
||||
binding->ops->release(binding->opaque);
|
||||
if (!result)
|
||||
DEBUG_WARN("Failed to publish the local clipboard to %s",
|
||||
binding->ops->name);
|
||||
LG_UNLOCK(clipboard.writeLock);
|
||||
}
|
||||
|
||||
static void eventNotice(void * opaque,
|
||||
const LG_ClipboardData types[], size_t count)
|
||||
{
|
||||
ClipboardBinding * binding = opaque;
|
||||
if (!types || count == 0)
|
||||
return;
|
||||
|
||||
LG_ClipboardData type = LG_CLIPBOARD_DATA_NONE;
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
if (validType(types[i]))
|
||||
{
|
||||
type = types[i];
|
||||
break;
|
||||
}
|
||||
if (type == LG_CLIPBOARD_DATA_NONE)
|
||||
return;
|
||||
|
||||
LG_LOCK_SHARED(clipboard.activeLock);
|
||||
if (!bindingActiveNL(binding))
|
||||
{
|
||||
LG_UNLOCK_SHARED(clipboard.activeLock);
|
||||
return;
|
||||
}
|
||||
|
||||
const ClipboardBinding current = *binding;
|
||||
LG_UNLOCK_SHARED(clipboard.activeLock);
|
||||
bool notice;
|
||||
uint32_t generation;
|
||||
|
||||
LG_LOCK(clipboard.requestLock);
|
||||
LG_LOCK(clipboard.stateLock);
|
||||
clipboard.remoteNotice = true;
|
||||
clipboard.remoteType = type;
|
||||
clipboard.remoteGeneration = nextGeneration(
|
||||
clipboard.remoteGeneration);
|
||||
generation = clipboard.remoteGeneration;
|
||||
clearRemoteRequestNL();
|
||||
notice = clipboard.localAvailable && g_params.clipboardToLocal;
|
||||
LG_UNLOCK(clipboard.stateLock);
|
||||
LG_UNLOCK(clipboard.requestLock);
|
||||
|
||||
LG_LOCK(clipboard.callbackLock);
|
||||
cancelRequestsNL(¤t, generation, false);
|
||||
LG_LOCK(clipboard.stateLock);
|
||||
notice = notice && clipboard.localAvailable &&
|
||||
clipboard.remoteNotice &&
|
||||
clipboard.remoteGeneration == generation;
|
||||
LG_UNLOCK(clipboard.stateLock);
|
||||
LG_LOCK_SHARED(clipboard.activeLock);
|
||||
const bool active = bindingActiveNL(¤t);
|
||||
LG_UNLOCK_SHARED(clipboard.activeLock);
|
||||
if (notice && active)
|
||||
g_state.ds->cbNotice(type);
|
||||
LG_UNLOCK(clipboard.callbackLock);
|
||||
}
|
||||
|
||||
static void eventData(void * opaque, LG_ClipboardRequest id,
|
||||
LG_ClipboardData type, const void * data, size_t size)
|
||||
{
|
||||
ClipboardBinding * binding = opaque;
|
||||
if (id == LG_CLIPBOARD_REQUEST_INVALID)
|
||||
return;
|
||||
|
||||
LG_LOCK_SHARED(clipboard.activeLock);
|
||||
if (!bindingActiveNL(binding))
|
||||
{
|
||||
LG_UNLOCK_SHARED(clipboard.activeLock);
|
||||
return;
|
||||
}
|
||||
|
||||
const ClipboardBinding current = *binding;
|
||||
LG_UNLOCK_SHARED(clipboard.activeLock);
|
||||
LG_LOCK(clipboard.callbackLock);
|
||||
ClipboardRequest * request = takeRequest(id);
|
||||
if (!request)
|
||||
{
|
||||
LG_UNLOCK(clipboard.callbackLock);
|
||||
DEBUG_WARN("Ignoring stale clipboard data from %s",
|
||||
current.ops->name);
|
||||
return;
|
||||
}
|
||||
|
||||
LG_LOCK(clipboard.stateLock);
|
||||
const bool deliver = clipboard.localAvailable &&
|
||||
g_params.clipboardToLocal && clipboard.remoteNotice &&
|
||||
request->remoteGeneration == clipboard.remoteGeneration;
|
||||
LG_UNLOCK(clipboard.stateLock);
|
||||
|
||||
LG_LOCK_SHARED(clipboard.activeLock);
|
||||
const bool active = bindingActiveNL(¤t);
|
||||
LG_UNLOCK_SHARED(clipboard.activeLock);
|
||||
if (!deliver || !active || !bindingEqual(&request->binding, ¤t) ||
|
||||
type != request->type || !validType(type) ||
|
||||
size > UINT32_MAX || (size && !data))
|
||||
{
|
||||
DEBUG_ERROR("Invalid clipboard response from %s", current.ops->name);
|
||||
request->replyFn(
|
||||
request->opaque, LG_CLIPBOARD_DATA_NONE, NULL, 0);
|
||||
}
|
||||
else
|
||||
request->replyFn(
|
||||
request->opaque, type, data, (uint32_t)size);
|
||||
|
||||
LG_UNLOCK(clipboard.callbackLock);
|
||||
free(request);
|
||||
}
|
||||
|
||||
static void eventRelease(void * opaque)
|
||||
{
|
||||
ClipboardBinding * binding = opaque;
|
||||
|
||||
LG_LOCK_SHARED(clipboard.activeLock);
|
||||
if (!bindingActiveNL(binding))
|
||||
{
|
||||
LG_UNLOCK_SHARED(clipboard.activeLock);
|
||||
return;
|
||||
}
|
||||
LG_UNLOCK_SHARED(clipboard.activeLock);
|
||||
|
||||
bool release;
|
||||
LG_LOCK(clipboard.requestLock);
|
||||
LG_LOCK(clipboard.stateLock);
|
||||
release = clipboard.remoteNotice && clipboard.localAvailable &&
|
||||
g_params.clipboardToLocal;
|
||||
clipboard.remoteNotice = false;
|
||||
clipboard.remoteType = LG_CLIPBOARD_DATA_NONE;
|
||||
clipboard.remoteGeneration = nextGeneration(
|
||||
clipboard.remoteGeneration);
|
||||
LG_UNLOCK(clipboard.stateLock);
|
||||
LG_UNLOCK(clipboard.requestLock);
|
||||
|
||||
LG_LOCK(clipboard.callbackLock);
|
||||
cancelRequestsNL(NULL, 0, true);
|
||||
LG_LOCK(clipboard.stateLock);
|
||||
release = release && clipboard.localAvailable &&
|
||||
!clipboard.remoteNotice;
|
||||
LG_UNLOCK(clipboard.stateLock);
|
||||
if (release)
|
||||
g_state.ds->cbRelease();
|
||||
LG_UNLOCK(clipboard.callbackLock);
|
||||
}
|
||||
|
||||
static bool eventRequest(void * opaque, LG_ClipboardRequest id,
|
||||
LG_ClipboardData type)
|
||||
{
|
||||
ClipboardBinding * binding = opaque;
|
||||
if (id == LG_CLIPBOARD_REQUEST_INVALID || !validType(type))
|
||||
return false;
|
||||
|
||||
LG_LOCK_SHARED(clipboard.activeLock);
|
||||
if (!bindingActiveNL(binding))
|
||||
{
|
||||
LG_UNLOCK_SHARED(clipboard.activeLock);
|
||||
return false;
|
||||
}
|
||||
|
||||
const ClipboardBinding current = *binding;
|
||||
const char * name = current.ops->name;
|
||||
LG_UNLOCK_SHARED(clipboard.activeLock);
|
||||
LG_ClipboardRequest transfer = LG_CLIPBOARD_REQUEST_INVALID;
|
||||
LG_LOCK(clipboard.stateLock);
|
||||
const bool request = clipboard.localAvailable &&
|
||||
g_params.clipboardToVM && localHasTypeNL(type) &&
|
||||
!clipboard.remoteRequest;
|
||||
if (request)
|
||||
{
|
||||
clipboard.remoteRequest = true;
|
||||
clipboard.remoteRequestBinding = current;
|
||||
clipboard.remoteRequestId = id;
|
||||
clipboard.remoteTransferId = nextTransferNL();
|
||||
clipboard.remoteRequestType = type;
|
||||
transfer = clipboard.remoteTransferId;
|
||||
}
|
||||
LG_UNLOCK(clipboard.stateLock);
|
||||
|
||||
LG_LOCK(clipboard.callbackLock);
|
||||
LG_LOCK(clipboard.stateLock);
|
||||
const bool deliver = request && clipboard.localAvailable &&
|
||||
clipboard.remoteRequest &&
|
||||
clipboard.remoteRequestId == id &&
|
||||
clipboard.remoteTransferId == transfer;
|
||||
LG_UNLOCK(clipboard.stateLock);
|
||||
LG_LOCK_SHARED(clipboard.activeLock);
|
||||
const bool active = bindingActiveNL(¤t);
|
||||
LG_UNLOCK_SHARED(clipboard.activeLock);
|
||||
if (deliver && active)
|
||||
g_state.ds->cbRequest(transfer, type);
|
||||
else
|
||||
{
|
||||
LG_LOCK(clipboard.stateLock);
|
||||
if (clipboard.remoteRequest &&
|
||||
clipboard.remoteRequestId == id &&
|
||||
clipboard.remoteTransferId == transfer)
|
||||
clearRemoteRequestNL();
|
||||
LG_UNLOCK(clipboard.stateLock);
|
||||
DEBUG_WARN("Ignoring invalid clipboard request from %s",
|
||||
name);
|
||||
}
|
||||
LG_UNLOCK(clipboard.callbackLock);
|
||||
return deliver && active;
|
||||
}
|
||||
|
||||
static const LG_ClipboardEventOps eventOps =
|
||||
{
|
||||
.notice = eventNotice,
|
||||
.data = eventData,
|
||||
.release = eventRelease,
|
||||
.request = eventRequest,
|
||||
};
|
||||
|
||||
/* providerLock must be held. dropActive suppresses remote release when the
|
||||
* endpoint has already disappeared; detach must always quiesce local events. */
|
||||
static void updateActive(bool dropActive)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
LG_LOCK_EXCLUSIVE(clipboard.activeLock);
|
||||
ClipboardBinding * slot = nextBindingSlotNL();
|
||||
ClipboardBinding next = slot ? *slot : (ClipboardBinding) { 0 };
|
||||
const ClipboardBinding old = clipboard.active;
|
||||
if (bindingEqual(&old, &next))
|
||||
{
|
||||
clipboard.active = next;
|
||||
LG_UNLOCK_EXCLUSIVE(clipboard.activeLock);
|
||||
return;
|
||||
}
|
||||
clipboard.active = (ClipboardBinding) { 0 };
|
||||
LG_UNLOCK_EXCLUSIVE(clipboard.activeLock);
|
||||
|
||||
if (old.ops)
|
||||
{
|
||||
if (!dropActive && !old.ops->release(old.opaque))
|
||||
DEBUG_WARN("Failed to release Clipboard provider: %s",
|
||||
old.ops->name);
|
||||
old.ops->detach(old.opaque);
|
||||
}
|
||||
dropActive = false;
|
||||
resetRemote();
|
||||
|
||||
LG_LOCK_EXCLUSIVE(clipboard.activeLock);
|
||||
slot = nextBindingSlotNL();
|
||||
if (!slot)
|
||||
{
|
||||
LG_UNLOCK_EXCLUSIVE(clipboard.activeLock);
|
||||
DEBUG_INFO("Clipboard is unavailable");
|
||||
return;
|
||||
}
|
||||
next = *slot;
|
||||
clipboard.active = next;
|
||||
LG_UNLOCK_EXCLUSIVE(clipboard.activeLock);
|
||||
|
||||
if (next.ops->attach(next.opaque, &eventOps, slot))
|
||||
{
|
||||
publishLocal(&next);
|
||||
DEBUG_INFO("Using Clipboard: %s", next.ops->name);
|
||||
return;
|
||||
}
|
||||
|
||||
next.ops->detach(next.opaque);
|
||||
resetRemote();
|
||||
|
||||
LG_LOCK_EXCLUSIVE(clipboard.activeLock);
|
||||
if (bindingEqual(&clipboard.active, &next))
|
||||
clipboard.active = (ClipboardBinding) { 0 };
|
||||
if (bindingEqual(slot, &next))
|
||||
slot->available = false;
|
||||
LG_UNLOCK_EXCLUSIVE(clipboard.activeLock);
|
||||
|
||||
DEBUG_WARN("Failed to attach Clipboard provider: %s", next.ops->name);
|
||||
}
|
||||
}
|
||||
|
||||
static void statusChanged(ClipboardBinding * binding, void * opaque,
|
||||
const LG_ClipboardStatus * status)
|
||||
{
|
||||
if (!status)
|
||||
return;
|
||||
|
||||
LG_LOCK(clipboard.providerLock);
|
||||
LG_LOCK_EXCLUSIVE(clipboard.activeLock);
|
||||
const bool current = binding->ops && binding->opaque == opaque;
|
||||
const bool wasActive = current && bindingActiveNL(binding);
|
||||
const bool changed = current &&
|
||||
(binding->available != status->available ||
|
||||
binding->generation != status->generation);
|
||||
if (changed)
|
||||
{
|
||||
binding->available = status->available;
|
||||
binding->generation = status->generation;
|
||||
}
|
||||
LG_UNLOCK_EXCLUSIVE(clipboard.activeLock);
|
||||
if (changed)
|
||||
updateActive(wasActive && !status->available);
|
||||
LG_UNLOCK(clipboard.providerLock);
|
||||
}
|
||||
|
||||
static void fallbackStatusChanged(void * opaque,
|
||||
const LG_ClipboardStatus * status)
|
||||
{
|
||||
statusChanged(&clipboard.fallback, opaque, status);
|
||||
}
|
||||
|
||||
static void transportStatusChanged(void * opaque,
|
||||
const LG_ClipboardStatus * status)
|
||||
{
|
||||
statusChanged(&clipboard.transport, opaque, status);
|
||||
}
|
||||
|
||||
static void setBinding(ClipboardBinding * target,
|
||||
const LG_ClipboardOps * ops, void * opaque,
|
||||
LG_ClipboardStatusFn statusFn)
|
||||
{
|
||||
if (ops && !validOps(ops))
|
||||
{
|
||||
DEBUG_ERROR("Invalid clipboard operations");
|
||||
ops = NULL;
|
||||
opaque = NULL;
|
||||
}
|
||||
|
||||
LG_LOCK(clipboard.registrationLock);
|
||||
LG_LOCK(clipboard.providerLock);
|
||||
const ClipboardBinding old = *target;
|
||||
LG_UNLOCK(clipboard.providerLock);
|
||||
|
||||
if (old.ops && old.ops->setStatusListener)
|
||||
old.ops->setStatusListener(old.opaque, NULL, NULL);
|
||||
|
||||
const ClipboardBinding next = makeBinding(ops, opaque);
|
||||
LG_LOCK(clipboard.providerLock);
|
||||
LG_LOCK_EXCLUSIVE(clipboard.activeLock);
|
||||
*target = next;
|
||||
LG_UNLOCK_EXCLUSIVE(clipboard.activeLock);
|
||||
updateActive(false);
|
||||
LG_UNLOCK(clipboard.providerLock);
|
||||
|
||||
if (next.ops && next.ops->setStatusListener)
|
||||
next.ops->setStatusListener(next.opaque, statusFn, next.opaque);
|
||||
LG_UNLOCK(clipboard.registrationLock);
|
||||
}
|
||||
|
||||
void lgClipboard_init(void)
|
||||
{
|
||||
memset(&clipboard, 0, sizeof(clipboard));
|
||||
LG_LOCK_INIT(clipboard.registrationLock);
|
||||
LG_LOCK_INIT(clipboard.providerLock);
|
||||
LG_RWLOCK_INIT(clipboard.activeLock);
|
||||
LG_LOCK_INIT(clipboard.stateLock);
|
||||
LG_LOCK_INIT(clipboard.requestLock);
|
||||
LG_LOCK_INIT(clipboard.writeLock);
|
||||
LG_LOCK_INIT(clipboard.callbackLock);
|
||||
clipboard.remoteType = LG_CLIPBOARD_DATA_NONE;
|
||||
clipboard.remoteRequestType = LG_CLIPBOARD_DATA_NONE;
|
||||
clipboard.requests = ll_new();
|
||||
}
|
||||
|
||||
void lgClipboard_free(void)
|
||||
{
|
||||
lgClipboard_setLocalAvailable(false);
|
||||
lgClipboard_setTransport(NULL, NULL);
|
||||
lgClipboard_setFallback(NULL, NULL);
|
||||
LG_LOCK(clipboard.callbackLock);
|
||||
cancelRequestsNL(NULL, 0, true);
|
||||
LG_UNLOCK(clipboard.callbackLock);
|
||||
if (clipboard.requests)
|
||||
{
|
||||
ll_free(clipboard.requests);
|
||||
clipboard.requests = NULL;
|
||||
}
|
||||
|
||||
LG_LOCK_FREE(clipboard.writeLock);
|
||||
LG_LOCK_FREE(clipboard.requestLock);
|
||||
LG_LOCK_FREE(clipboard.stateLock);
|
||||
LG_LOCK_FREE(clipboard.callbackLock);
|
||||
LG_RWLOCK_FREE(clipboard.activeLock);
|
||||
LG_LOCK_FREE(clipboard.providerLock);
|
||||
LG_LOCK_FREE(clipboard.registrationLock);
|
||||
}
|
||||
|
||||
void lgClipboard_setLocalAvailable(bool available)
|
||||
{
|
||||
bool notice;
|
||||
bool release;
|
||||
LG_ClipboardData type;
|
||||
|
||||
LG_LOCK(clipboard.callbackLock);
|
||||
LG_LOCK(clipboard.requestLock);
|
||||
LG_LOCK(clipboard.stateLock);
|
||||
release = !available && clipboard.localAvailable &&
|
||||
clipboard.remoteNotice && g_params.clipboardToLocal;
|
||||
clipboard.localAvailable = available;
|
||||
notice = available && clipboard.remoteNotice &&
|
||||
g_params.clipboardToLocal;
|
||||
type = clipboard.remoteType;
|
||||
if (!available)
|
||||
clearRemoteRequestNL();
|
||||
LG_UNLOCK(clipboard.stateLock);
|
||||
LG_UNLOCK(clipboard.requestLock);
|
||||
|
||||
if (notice)
|
||||
g_state.ds->cbNotice(type);
|
||||
if (!available)
|
||||
{
|
||||
cancelRequestsNL(NULL, 0, true);
|
||||
if (release)
|
||||
g_state.ds->cbRelease();
|
||||
}
|
||||
LG_UNLOCK(clipboard.callbackLock);
|
||||
}
|
||||
|
||||
void lgClipboard_setFallback(const LG_ClipboardOps * ops, void * opaque)
|
||||
{
|
||||
setBinding(&clipboard.fallback,
|
||||
ops, opaque, fallbackStatusChanged);
|
||||
}
|
||||
|
||||
void lgClipboard_setTransport(const LG_ClipboardOps * ops, void * opaque)
|
||||
{
|
||||
setBinding(&clipboard.transport,
|
||||
ops, opaque, transportStatusChanged);
|
||||
}
|
||||
|
||||
void lgClipboard_dropTransport(void)
|
||||
{
|
||||
LG_LOCK(clipboard.registrationLock);
|
||||
LG_LOCK(clipboard.providerLock);
|
||||
const ClipboardBinding old = clipboard.transport;
|
||||
LG_UNLOCK(clipboard.providerLock);
|
||||
|
||||
if (old.ops && old.ops->setStatusListener)
|
||||
old.ops->setStatusListener(old.opaque, NULL, NULL);
|
||||
|
||||
LG_LOCK(clipboard.providerLock);
|
||||
LG_LOCK_EXCLUSIVE(clipboard.activeLock);
|
||||
const bool wasActive = bindingActiveNL(&clipboard.transport);
|
||||
clipboard.transport = (ClipboardBinding) { 0 };
|
||||
LG_UNLOCK_EXCLUSIVE(clipboard.activeLock);
|
||||
updateActive(wasActive);
|
||||
LG_UNLOCK(clipboard.providerLock);
|
||||
LG_UNLOCK(clipboard.registrationLock);
|
||||
}
|
||||
|
||||
void lgClipboard_release(void)
|
||||
{
|
||||
if (!g_params.clipboardToVM)
|
||||
return;
|
||||
|
||||
if (g_state.cbAvailable)
|
||||
g_state.ds->cbRequest(cb_spiceTypeToLGType(type));
|
||||
LG_LOCK(clipboard.writeLock);
|
||||
LG_LOCK(clipboard.stateLock);
|
||||
clipboard.localTypeCount = 0;
|
||||
clearRemoteRequestNL();
|
||||
LG_UNLOCK(clipboard.stateLock);
|
||||
|
||||
LG_LOCK_SHARED(clipboard.activeLock);
|
||||
if (clipboard.active.ops &&
|
||||
!clipboard.active.ops->release(clipboard.active.opaque))
|
||||
DEBUG_WARN("Failed to release the remote clipboard");
|
||||
LG_UNLOCK_SHARED(clipboard.activeLock);
|
||||
LG_UNLOCK(clipboard.writeLock);
|
||||
}
|
||||
|
||||
void lgClipboard_notifyTypes(
|
||||
const LG_ClipboardData types[], size_t count)
|
||||
{
|
||||
if (!g_params.clipboardToVM)
|
||||
return;
|
||||
if (count == 0)
|
||||
{
|
||||
lgClipboard_release();
|
||||
return;
|
||||
}
|
||||
if (!types || count > LG_CLIPBOARD_DATA_NONE)
|
||||
return;
|
||||
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
if (!validType(types[i]))
|
||||
return;
|
||||
|
||||
LG_LOCK(clipboard.writeLock);
|
||||
LG_LOCK(clipboard.stateLock);
|
||||
memcpy(clipboard.localTypes, types, count * sizeof(*types));
|
||||
clipboard.localTypeCount = count;
|
||||
clearRemoteRequestNL();
|
||||
LG_UNLOCK(clipboard.stateLock);
|
||||
|
||||
LG_LOCK_SHARED(clipboard.activeLock);
|
||||
if (clipboard.active.ops &&
|
||||
!clipboard.active.ops->notifyTypes(
|
||||
clipboard.active.opaque, types, count))
|
||||
DEBUG_WARN("Failed to publish the local clipboard types");
|
||||
LG_UNLOCK_SHARED(clipboard.activeLock);
|
||||
LG_UNLOCK(clipboard.writeLock);
|
||||
}
|
||||
|
||||
void lgClipboard_data(LG_ClipboardRequest transfer,
|
||||
LG_ClipboardData type, const void * data, size_t size)
|
||||
{
|
||||
if (!g_params.clipboardToVM ||
|
||||
transfer == LG_CLIPBOARD_REQUEST_INVALID)
|
||||
return;
|
||||
|
||||
LG_LOCK(clipboard.writeLock);
|
||||
LG_LOCK_SHARED(clipboard.activeLock);
|
||||
LG_LOCK(clipboard.stateLock);
|
||||
|
||||
const bool matches = clipboard.remoteRequest &&
|
||||
clipboard.remoteTransferId == transfer;
|
||||
if (!matches)
|
||||
{
|
||||
LG_UNLOCK(clipboard.stateLock);
|
||||
LG_UNLOCK_SHARED(clipboard.activeLock);
|
||||
LG_UNLOCK(clipboard.writeLock);
|
||||
return;
|
||||
}
|
||||
|
||||
const LG_ClipboardRequest request = clipboard.remoteRequestId;
|
||||
const bool provider = clipboard.active.ops &&
|
||||
bindingEqual(&clipboard.remoteRequestBinding, &clipboard.active);
|
||||
const bool valid = provider && clipboard.remoteRequestType == type &&
|
||||
validType(type) && (!size || data);
|
||||
clearRemoteRequestNL();
|
||||
LG_UNLOCK(clipboard.stateLock);
|
||||
|
||||
const bool result = provider && clipboard.active.ops->data(
|
||||
clipboard.active.opaque, request,
|
||||
valid ? type : LG_CLIPBOARD_DATA_NONE,
|
||||
valid ? data : NULL, valid ? size : 0);
|
||||
LG_UNLOCK_SHARED(clipboard.activeLock);
|
||||
LG_UNLOCK(clipboard.writeLock);
|
||||
|
||||
if (!valid)
|
||||
DEBUG_WARN("Ignoring unexpected local clipboard data");
|
||||
else if (!result)
|
||||
DEBUG_WARN("Failed to send remote clipboard data");
|
||||
}
|
||||
|
||||
void lgClipboard_abort(LG_ClipboardRequest transfer)
|
||||
{
|
||||
if (!g_params.clipboardToVM ||
|
||||
transfer == LG_CLIPBOARD_REQUEST_INVALID)
|
||||
return;
|
||||
|
||||
LG_LOCK(clipboard.writeLock);
|
||||
LG_LOCK_SHARED(clipboard.activeLock);
|
||||
LG_LOCK(clipboard.stateLock);
|
||||
const bool matches = clipboard.remoteRequest &&
|
||||
clipboard.remoteTransferId == transfer;
|
||||
const bool valid = matches && clipboard.active.ops &&
|
||||
bindingEqual(&clipboard.remoteRequestBinding, &clipboard.active);
|
||||
const LG_ClipboardRequest request = clipboard.remoteRequestId;
|
||||
if (matches)
|
||||
clearRemoteRequestNL();
|
||||
LG_UNLOCK(clipboard.stateLock);
|
||||
|
||||
const bool result = !valid || clipboard.active.ops->data(
|
||||
clipboard.active.opaque, request,
|
||||
LG_CLIPBOARD_DATA_NONE, NULL, 0);
|
||||
LG_UNLOCK_SHARED(clipboard.activeLock);
|
||||
LG_UNLOCK(clipboard.writeLock);
|
||||
|
||||
if (!result)
|
||||
DEBUG_WARN("Failed to abort remote clipboard data");
|
||||
}
|
||||
|
||||
bool lgClipboard_request(LG_ClipboardData type,
|
||||
LG_ClipboardReplyFn replyFn, void * opaque)
|
||||
{
|
||||
if (!g_params.clipboardToLocal || !validType(type) ||
|
||||
!replyFn || !clipboard.requests)
|
||||
return false;
|
||||
|
||||
ClipboardRequest * request = malloc(sizeof(*request));
|
||||
if (!request)
|
||||
{
|
||||
DEBUG_ERROR("Out of memory");
|
||||
return false;
|
||||
}
|
||||
|
||||
LG_LOCK_SHARED(clipboard.activeLock);
|
||||
if (!clipboard.active.ops)
|
||||
{
|
||||
LG_UNLOCK_SHARED(clipboard.activeLock);
|
||||
free(request);
|
||||
return false;
|
||||
}
|
||||
|
||||
LG_LOCK(clipboard.requestLock);
|
||||
LG_LOCK(clipboard.stateLock);
|
||||
const bool available = clipboard.localAvailable &&
|
||||
clipboard.remoteNotice && clipboard.remoteType == type;
|
||||
const uint32_t generation = clipboard.remoteGeneration;
|
||||
LG_ClipboardRequest id = LG_CLIPBOARD_REQUEST_INVALID;
|
||||
if (available)
|
||||
{
|
||||
id = nextRequestNL();
|
||||
*request = (ClipboardRequest)
|
||||
{
|
||||
.request = id,
|
||||
.binding = clipboard.active,
|
||||
.remoteGeneration = generation,
|
||||
.type = type,
|
||||
.replyFn = replyFn,
|
||||
.opaque = opaque,
|
||||
};
|
||||
}
|
||||
LG_UNLOCK(clipboard.stateLock);
|
||||
|
||||
const bool queued = available &&
|
||||
ll_push(clipboard.requests, request);
|
||||
bool result = queued;
|
||||
if (queued)
|
||||
result = clipboard.active.ops->request(clipboard.active.opaque,
|
||||
request->request, type);
|
||||
LG_UNLOCK(clipboard.requestLock);
|
||||
LG_UNLOCK_SHARED(clipboard.activeLock);
|
||||
|
||||
if (result)
|
||||
return true;
|
||||
if (queued)
|
||||
{
|
||||
ClipboardRequest * failed = takeRequest(id);
|
||||
if (!failed)
|
||||
return true;
|
||||
free(failed);
|
||||
return false;
|
||||
}
|
||||
|
||||
free(request);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -18,13 +18,26 @@
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <purespice.h>
|
||||
#include "interface/displayserver.h"
|
||||
#ifndef _H_LG_CLIENT_CLIPBOARD_
|
||||
#define _H_LG_CLIENT_CLIPBOARD_
|
||||
|
||||
LG_ClipboardData cb_spiceTypeToLGType(const PSDataType type);
|
||||
PSDataType cb_lgTypeToSpiceType(const LG_ClipboardData type);
|
||||
#include "interface/clipboard.h"
|
||||
|
||||
void cb_spiceNotice(const PSDataType type);
|
||||
void cb_spiceData(const PSDataType type, uint8_t * buffer, uint32_t size);
|
||||
void cb_spiceRelease(void);
|
||||
void cb_spiceRequest(const PSDataType type);
|
||||
void lgClipboard_init(void);
|
||||
void lgClipboard_free(void);
|
||||
void lgClipboard_setLocalAvailable(bool available);
|
||||
|
||||
void lgClipboard_setFallback(const LG_ClipboardOps * ops, void * opaque);
|
||||
void lgClipboard_setTransport(const LG_ClipboardOps * ops, void * opaque);
|
||||
void lgClipboard_dropTransport(void);
|
||||
|
||||
void lgClipboard_release(void);
|
||||
void lgClipboard_notifyTypes(
|
||||
const LG_ClipboardData types[], size_t count);
|
||||
void lgClipboard_data(LG_ClipboardRequest request,
|
||||
LG_ClipboardData type, const void * data, size_t size);
|
||||
void lgClipboard_abort(LG_ClipboardRequest request);
|
||||
bool lgClipboard_request(LG_ClipboardData type,
|
||||
LG_ClipboardReplyFn replyFn, void * opaque);
|
||||
|
||||
#endif
|
||||
|
||||
491
client/src/clipboard_spice.c
Normal file
491
client/src/clipboard_spice.c
Normal file
@@ -0,0 +1,491 @@
|
||||
/**
|
||||
* 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 "clipboard_spice.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/locking.h"
|
||||
|
||||
#include <stdatomic.h>
|
||||
|
||||
typedef struct ClipboardEventTarget
|
||||
{
|
||||
const LG_ClipboardEventOps * events;
|
||||
void * opaque;
|
||||
}
|
||||
ClipboardEventTarget;
|
||||
|
||||
typedef struct PendingRequest
|
||||
{
|
||||
bool pending;
|
||||
LG_ClipboardRequest request;
|
||||
LG_ClipboardData type;
|
||||
}
|
||||
PendingRequest;
|
||||
|
||||
static struct
|
||||
{
|
||||
LG_Lock stateLock;
|
||||
LG_Lock eventDispatch;
|
||||
LG_Lock statusDispatch;
|
||||
|
||||
bool available;
|
||||
uint32_t statusGeneration;
|
||||
LG_ClipboardStatusFn statusCallback;
|
||||
void * statusOpaque;
|
||||
|
||||
const LG_ClipboardEventOps * events;
|
||||
void * eventOpaque;
|
||||
|
||||
bool remoteNotice;
|
||||
LG_ClipboardData remoteType;
|
||||
PendingRequest read;
|
||||
PendingRequest write;
|
||||
|
||||
LG_ClipboardRequest requestSerial;
|
||||
}
|
||||
l_spice =
|
||||
{
|
||||
.stateLock = ATOMIC_FLAG_INIT,
|
||||
.eventDispatch = ATOMIC_FLAG_INIT,
|
||||
.statusDispatch = ATOMIC_FLAG_INIT,
|
||||
.remoteType = LG_CLIPBOARD_DATA_NONE,
|
||||
};
|
||||
|
||||
static uint32_t nextGeneration(uint32_t generation)
|
||||
{
|
||||
if (++generation == 0)
|
||||
++generation;
|
||||
return generation;
|
||||
}
|
||||
|
||||
static LG_ClipboardRequest nextRequestNL(void)
|
||||
{
|
||||
if (++l_spice.requestSerial == LG_CLIPBOARD_REQUEST_INVALID)
|
||||
++l_spice.requestSerial;
|
||||
return l_spice.requestSerial;
|
||||
}
|
||||
|
||||
static bool spiceType(PSDataType source, LG_ClipboardData * type)
|
||||
{
|
||||
switch (source)
|
||||
{
|
||||
case SPICE_DATA_TEXT : *type = LG_CLIPBOARD_DATA_TEXT; return true;
|
||||
case SPICE_DATA_PNG : *type = LG_CLIPBOARD_DATA_PNG ; return true;
|
||||
case SPICE_DATA_BMP : *type = LG_CLIPBOARD_DATA_BMP ; return true;
|
||||
case SPICE_DATA_TIFF : *type = LG_CLIPBOARD_DATA_TIFF; return true;
|
||||
case SPICE_DATA_JPEG : *type = LG_CLIPBOARD_DATA_JPEG; return true;
|
||||
case SPICE_DATA_NONE : break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool lgType(LG_ClipboardData source, PSDataType * type)
|
||||
{
|
||||
switch (source)
|
||||
{
|
||||
case LG_CLIPBOARD_DATA_TEXT : *type = SPICE_DATA_TEXT; return true;
|
||||
case LG_CLIPBOARD_DATA_PNG : *type = SPICE_DATA_PNG ; return true;
|
||||
case LG_CLIPBOARD_DATA_BMP : *type = SPICE_DATA_BMP ; return true;
|
||||
case LG_CLIPBOARD_DATA_TIFF : *type = SPICE_DATA_TIFF; return true;
|
||||
case LG_CLIPBOARD_DATA_JPEG : *type = SPICE_DATA_JPEG; return true;
|
||||
case LG_CLIPBOARD_DATA_NONE : *type = SPICE_DATA_NONE; return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void spiceSetStatusListener(void * opaque,
|
||||
LG_ClipboardStatusFn callback, void * callbackOpaque)
|
||||
{
|
||||
(void)opaque;
|
||||
LG_LOCK(l_spice.statusDispatch);
|
||||
|
||||
LG_LOCK(l_spice.stateLock);
|
||||
l_spice.statusCallback = callback;
|
||||
l_spice.statusOpaque = callbackOpaque;
|
||||
const LG_ClipboardStatus status =
|
||||
{
|
||||
.available = l_spice.available,
|
||||
.generation = l_spice.statusGeneration,
|
||||
};
|
||||
LG_UNLOCK(l_spice.stateLock);
|
||||
|
||||
if (callback)
|
||||
callback(callbackOpaque, &status);
|
||||
LG_UNLOCK(l_spice.statusDispatch);
|
||||
}
|
||||
|
||||
static bool spiceAttach(void * opaque,
|
||||
const LG_ClipboardEventOps * events, void * eventOpaque)
|
||||
{
|
||||
(void)opaque;
|
||||
if (!events)
|
||||
return false;
|
||||
|
||||
LG_LOCK(l_spice.eventDispatch);
|
||||
LG_LOCK(l_spice.stateLock);
|
||||
if (!l_spice.available)
|
||||
{
|
||||
LG_UNLOCK(l_spice.stateLock);
|
||||
LG_UNLOCK(l_spice.eventDispatch);
|
||||
return false;
|
||||
}
|
||||
|
||||
l_spice.events = events;
|
||||
l_spice.eventOpaque = eventOpaque;
|
||||
const bool notice = l_spice.remoteNotice;
|
||||
const LG_ClipboardData type = l_spice.remoteType;
|
||||
LG_UNLOCK(l_spice.stateLock);
|
||||
|
||||
if (notice && events->notice)
|
||||
events->notice(eventOpaque, &type, 1);
|
||||
LG_UNLOCK(l_spice.eventDispatch);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void spiceDetach(void * opaque)
|
||||
{
|
||||
(void)opaque;
|
||||
LG_LOCK(l_spice.eventDispatch);
|
||||
LG_LOCK(l_spice.stateLock);
|
||||
const bool failWrite = l_spice.available && l_spice.write.pending;
|
||||
l_spice.events = NULL;
|
||||
l_spice.eventOpaque = NULL;
|
||||
l_spice.read = (PendingRequest) { 0 };
|
||||
l_spice.write = (PendingRequest) { 0 };
|
||||
LG_UNLOCK(l_spice.stateLock);
|
||||
if (failWrite)
|
||||
purespice_clipboardDataStart(SPICE_DATA_NONE, 0);
|
||||
LG_UNLOCK(l_spice.eventDispatch);
|
||||
}
|
||||
|
||||
static bool spiceRelease(void * opaque)
|
||||
{
|
||||
(void)opaque;
|
||||
LG_LOCK(l_spice.stateLock);
|
||||
const bool available = l_spice.available;
|
||||
const bool failWrite = l_spice.write.pending;
|
||||
l_spice.write = (PendingRequest) { 0 };
|
||||
LG_UNLOCK(l_spice.stateLock);
|
||||
|
||||
if (!available)
|
||||
return false;
|
||||
|
||||
const bool failed = !failWrite ||
|
||||
purespice_clipboardDataStart(SPICE_DATA_NONE, 0);
|
||||
return purespice_clipboardRelease() && failed;
|
||||
}
|
||||
|
||||
static bool spiceNotifyTypes(void * opaque,
|
||||
const LG_ClipboardData types[], size_t count)
|
||||
{
|
||||
(void)opaque;
|
||||
if (count == 0)
|
||||
return spiceRelease(NULL);
|
||||
if (!types || count > LG_CLIPBOARD_DATA_NONE)
|
||||
return false;
|
||||
|
||||
PSDataType converted[LG_CLIPBOARD_DATA_NONE];
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
if (types[i] == LG_CLIPBOARD_DATA_NONE ||
|
||||
!lgType(types[i], &converted[i]))
|
||||
return false;
|
||||
|
||||
LG_LOCK(l_spice.stateLock);
|
||||
const bool available = l_spice.available;
|
||||
const bool failWrite = l_spice.write.pending;
|
||||
l_spice.write = (PendingRequest) { 0 };
|
||||
LG_UNLOCK(l_spice.stateLock);
|
||||
|
||||
if (!available)
|
||||
return false;
|
||||
|
||||
const bool failed = !failWrite ||
|
||||
purespice_clipboardDataStart(SPICE_DATA_NONE, 0);
|
||||
return purespice_clipboardGrab(converted, (int)count) && failed;
|
||||
}
|
||||
|
||||
static bool spiceData(void * opaque, LG_ClipboardRequest request,
|
||||
LG_ClipboardData type, const void * data, size_t size)
|
||||
{
|
||||
(void)opaque;
|
||||
PSDataType converted;
|
||||
if (request == LG_CLIPBOARD_REQUEST_INVALID || !lgType(type, &converted) ||
|
||||
(type == LG_CLIPBOARD_DATA_NONE && size != 0) ||
|
||||
(size && !data))
|
||||
return false;
|
||||
|
||||
LG_LOCK(l_spice.stateLock);
|
||||
const bool valid = l_spice.available && l_spice.write.pending &&
|
||||
l_spice.write.request == request &&
|
||||
(type == LG_CLIPBOARD_DATA_NONE || l_spice.write.type == type);
|
||||
if (valid)
|
||||
l_spice.write = (PendingRequest) { 0 };
|
||||
LG_UNLOCK(l_spice.stateLock);
|
||||
|
||||
if (!valid || !purespice_clipboardDataStart(converted, size))
|
||||
return false;
|
||||
|
||||
return !size || purespice_clipboardData(
|
||||
converted, (uint8_t *)data, size);
|
||||
}
|
||||
|
||||
static bool spiceRequest(void * opaque, LG_ClipboardRequest request,
|
||||
LG_ClipboardData type)
|
||||
{
|
||||
(void)opaque;
|
||||
PSDataType converted;
|
||||
if (request == LG_CLIPBOARD_REQUEST_INVALID ||
|
||||
type == LG_CLIPBOARD_DATA_NONE || !lgType(type, &converted))
|
||||
return false;
|
||||
|
||||
LG_LOCK(l_spice.stateLock);
|
||||
const bool valid = l_spice.available && l_spice.events &&
|
||||
!l_spice.read.pending;
|
||||
if (valid)
|
||||
l_spice.read = (PendingRequest)
|
||||
{
|
||||
.pending = true,
|
||||
.request = request,
|
||||
.type = type,
|
||||
};
|
||||
LG_UNLOCK(l_spice.stateLock);
|
||||
|
||||
if (!valid)
|
||||
return false;
|
||||
|
||||
if (purespice_clipboardRequest(converted))
|
||||
return true;
|
||||
|
||||
LG_LOCK(l_spice.stateLock);
|
||||
if (!l_spice.read.pending || l_spice.read.request != request)
|
||||
{
|
||||
LG_UNLOCK(l_spice.stateLock);
|
||||
return true;
|
||||
}
|
||||
l_spice.read = (PendingRequest) { 0 };
|
||||
LG_UNLOCK(l_spice.stateLock);
|
||||
return false;
|
||||
}
|
||||
|
||||
const LG_ClipboardOps LGC_Spice =
|
||||
{
|
||||
.name = "SPICE",
|
||||
.setStatusListener = spiceSetStatusListener,
|
||||
.attach = spiceAttach,
|
||||
.detach = spiceDetach,
|
||||
.release = spiceRelease,
|
||||
.notifyTypes = spiceNotifyTypes,
|
||||
.data = spiceData,
|
||||
.request = spiceRequest,
|
||||
};
|
||||
|
||||
void lgcSpice_setAvailable(bool available)
|
||||
{
|
||||
LG_LOCK(l_spice.statusDispatch);
|
||||
LG_LOCK(l_spice.eventDispatch);
|
||||
LG_LOCK(l_spice.stateLock);
|
||||
|
||||
const bool changed = l_spice.available != available;
|
||||
if (changed)
|
||||
{
|
||||
l_spice.available = available;
|
||||
l_spice.statusGeneration = nextGeneration(
|
||||
l_spice.statusGeneration);
|
||||
}
|
||||
if (!available)
|
||||
{
|
||||
l_spice.remoteNotice = false;
|
||||
l_spice.remoteType = LG_CLIPBOARD_DATA_NONE;
|
||||
l_spice.read = (PendingRequest) { 0 };
|
||||
l_spice.write = (PendingRequest) { 0 };
|
||||
}
|
||||
|
||||
const LG_ClipboardStatusFn callback = l_spice.statusCallback;
|
||||
void * callbackOpaque = l_spice.statusOpaque;
|
||||
const LG_ClipboardStatus status =
|
||||
{
|
||||
.available = available,
|
||||
.generation = l_spice.statusGeneration,
|
||||
};
|
||||
LG_UNLOCK(l_spice.stateLock);
|
||||
LG_UNLOCK(l_spice.eventDispatch);
|
||||
|
||||
if (changed && callback)
|
||||
callback(callbackOpaque, &status);
|
||||
LG_UNLOCK(l_spice.statusDispatch);
|
||||
}
|
||||
|
||||
void lgcSpice_notice(PSDataType source)
|
||||
{
|
||||
LG_ClipboardData type;
|
||||
if (!spiceType(source, &type))
|
||||
{
|
||||
if (source != SPICE_DATA_NONE)
|
||||
DEBUG_ERROR("Invalid SPICE clipboard notice type: %d", source);
|
||||
lgcSpice_release();
|
||||
return;
|
||||
}
|
||||
|
||||
LG_LOCK(l_spice.eventDispatch);
|
||||
LG_LOCK(l_spice.stateLock);
|
||||
const bool failWrite = l_spice.write.pending;
|
||||
l_spice.remoteNotice = true;
|
||||
l_spice.remoteType = type;
|
||||
l_spice.read = (PendingRequest) { 0 };
|
||||
l_spice.write = (PendingRequest) { 0 };
|
||||
const ClipboardEventTarget target =
|
||||
{
|
||||
.events = l_spice.available ? l_spice.events : NULL,
|
||||
.opaque = l_spice.eventOpaque,
|
||||
};
|
||||
LG_UNLOCK(l_spice.stateLock);
|
||||
|
||||
if (failWrite)
|
||||
purespice_clipboardDataStart(SPICE_DATA_NONE, 0);
|
||||
if (target.events && target.events->notice)
|
||||
target.events->notice(target.opaque, &type, 1);
|
||||
LG_UNLOCK(l_spice.eventDispatch);
|
||||
}
|
||||
|
||||
void lgcSpice_data(PSDataType source, uint8_t * buffer, uint32_t size)
|
||||
{
|
||||
LG_ClipboardData type = LG_CLIPBOARD_DATA_NONE;
|
||||
const bool converted = spiceType(source, &type);
|
||||
const bool validData = source == SPICE_DATA_NONE ||
|
||||
(converted && (!size || buffer));
|
||||
|
||||
LG_LOCK(l_spice.eventDispatch);
|
||||
LG_LOCK(l_spice.stateLock);
|
||||
if (!l_spice.read.pending)
|
||||
{
|
||||
LG_UNLOCK(l_spice.stateLock);
|
||||
LG_UNLOCK(l_spice.eventDispatch);
|
||||
DEBUG_WARN("Ignoring unsolicited SPICE clipboard data");
|
||||
return;
|
||||
}
|
||||
|
||||
const PendingRequest request = l_spice.read;
|
||||
l_spice.read = (PendingRequest) { 0 };
|
||||
const ClipboardEventTarget target =
|
||||
{
|
||||
.events = l_spice.available ? l_spice.events : NULL,
|
||||
.opaque = l_spice.eventOpaque,
|
||||
};
|
||||
LG_UNLOCK(l_spice.stateLock);
|
||||
|
||||
if (target.events && target.events->data)
|
||||
{
|
||||
if (!validData || (source != SPICE_DATA_NONE && type != request.type))
|
||||
{
|
||||
DEBUG_ERROR("Invalid SPICE clipboard response");
|
||||
target.events->data(target.opaque, request.request,
|
||||
LG_CLIPBOARD_DATA_NONE, NULL, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (type == LG_CLIPBOARD_DATA_TEXT && size)
|
||||
{
|
||||
uint8_t * output = buffer;
|
||||
for (uint32_t i = 0; i < size; ++i)
|
||||
if (buffer[i] != '\r')
|
||||
*output++ = buffer[i];
|
||||
size = (uint32_t)(output - buffer);
|
||||
}
|
||||
|
||||
target.events->data(target.opaque, request.request,
|
||||
type, buffer, size);
|
||||
}
|
||||
}
|
||||
LG_UNLOCK(l_spice.eventDispatch);
|
||||
}
|
||||
|
||||
void lgcSpice_release(void)
|
||||
{
|
||||
LG_LOCK(l_spice.eventDispatch);
|
||||
LG_LOCK(l_spice.stateLock);
|
||||
l_spice.remoteNotice = false;
|
||||
l_spice.remoteType = LG_CLIPBOARD_DATA_NONE;
|
||||
l_spice.read = (PendingRequest) { 0 };
|
||||
const ClipboardEventTarget target =
|
||||
{
|
||||
.events = l_spice.available ? l_spice.events : NULL,
|
||||
.opaque = l_spice.eventOpaque,
|
||||
};
|
||||
LG_UNLOCK(l_spice.stateLock);
|
||||
|
||||
if (target.events && target.events->release)
|
||||
target.events->release(target.opaque);
|
||||
LG_UNLOCK(l_spice.eventDispatch);
|
||||
}
|
||||
|
||||
void lgcSpice_request(PSDataType source)
|
||||
{
|
||||
LG_ClipboardData type;
|
||||
if (!spiceType(source, &type))
|
||||
{
|
||||
DEBUG_ERROR("Invalid SPICE clipboard request type: %d", source);
|
||||
purespice_clipboardDataStart(SPICE_DATA_NONE, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
LG_LOCK(l_spice.eventDispatch);
|
||||
LG_LOCK(l_spice.stateLock);
|
||||
const bool busy = l_spice.write.pending;
|
||||
const ClipboardEventTarget target =
|
||||
{
|
||||
.events = l_spice.available && !busy ?
|
||||
l_spice.events : NULL,
|
||||
.opaque = l_spice.eventOpaque,
|
||||
};
|
||||
LG_ClipboardRequest request = LG_CLIPBOARD_REQUEST_INVALID;
|
||||
if (target.events)
|
||||
{
|
||||
request = nextRequestNL();
|
||||
l_spice.write = (PendingRequest)
|
||||
{
|
||||
.pending = true,
|
||||
.request = request,
|
||||
.type = type,
|
||||
};
|
||||
}
|
||||
LG_UNLOCK(l_spice.stateLock);
|
||||
|
||||
const bool accepted = target.events && target.events->request &&
|
||||
target.events->request(target.opaque, request, type);
|
||||
if (!accepted)
|
||||
{
|
||||
bool fail = !busy && request == LG_CLIPBOARD_REQUEST_INVALID;
|
||||
LG_LOCK(l_spice.stateLock);
|
||||
if (l_spice.write.pending && l_spice.write.request == request)
|
||||
{
|
||||
l_spice.write = (PendingRequest) { 0 };
|
||||
fail = true;
|
||||
}
|
||||
LG_UNLOCK(l_spice.stateLock);
|
||||
if (fail)
|
||||
purespice_clipboardDataStart(SPICE_DATA_NONE, 0);
|
||||
else if (busy)
|
||||
DEBUG_WARN("Ignoring overlapping SPICE clipboard request");
|
||||
}
|
||||
LG_UNLOCK(l_spice.eventDispatch);
|
||||
}
|
||||
37
client/src/clipboard_spice.h
Normal file
37
client/src/clipboard_spice.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 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_CLIPBOARD_SPICE_
|
||||
#define _H_LG_CLIENT_CLIPBOARD_SPICE_
|
||||
|
||||
#include "interface/clipboard.h"
|
||||
|
||||
#include <purespice.h>
|
||||
|
||||
extern const LG_ClipboardOps LGC_Spice;
|
||||
|
||||
void lgcSpice_setAvailable(bool available);
|
||||
|
||||
void lgcSpice_notice(PSDataType type);
|
||||
void lgcSpice_data(PSDataType type, uint8_t * buffer, uint32_t size);
|
||||
void lgcSpice_release(void);
|
||||
void lgcSpice_request(PSDataType type);
|
||||
|
||||
#endif
|
||||
@@ -755,26 +755,19 @@ bool config_load(int argc, char * argv[])
|
||||
g_params.minimizeOnFocusLoss = option_get_bool("win", "minimizeOnFocusLoss");
|
||||
g_params.setGuestRes = option_get_bool("win", "setGuestRes" );
|
||||
|
||||
g_params.clipboardToVM = option_get_bool("spice", "clipboardToVM" );
|
||||
g_params.clipboardToLocal = option_get_bool("spice", "clipboardToLocal");
|
||||
|
||||
if ((g_params.useSpice = option_get_bool("spice", "enable")))
|
||||
{
|
||||
g_params.spiceHost = option_get_string("spice", "host");
|
||||
g_params.spicePort = option_get_int ("spice", "port");
|
||||
|
||||
g_params.useSpiceInput = option_get_bool("spice", "input" );
|
||||
g_params.useSpiceClipboard = option_get_bool("spice", "clipboard");
|
||||
g_params.useSpiceClipboard =
|
||||
option_get_bool("spice", "clipboard") &&
|
||||
(g_params.clipboardToVM || g_params.clipboardToLocal);
|
||||
g_params.useSpiceAudio = option_get_bool("spice", "audio" );
|
||||
|
||||
if (g_params.useSpiceClipboard)
|
||||
{
|
||||
g_params.clipboardToVM = option_get_bool("spice", "clipboardToVM" );
|
||||
g_params.clipboardToLocal = option_get_bool("spice", "clipboardToLocal");
|
||||
g_params.useSpiceClipboard = g_params.clipboardToVM || g_params.clipboardToLocal;
|
||||
}
|
||||
else
|
||||
{
|
||||
g_params.clipboardToVM = false;
|
||||
g_params.clipboardToLocal = false;
|
||||
}
|
||||
}
|
||||
|
||||
g_params.audioDebug = option_get_bool("audio", "debug");
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
#endif
|
||||
#include "keybind.h"
|
||||
#include "clipboard.h"
|
||||
#include "clipboard_spice.h"
|
||||
#include "kb.h"
|
||||
#include "egl_dynprocs.h"
|
||||
#include "gl_dynprocs.h"
|
||||
@@ -108,11 +109,7 @@ l_cursorRepaint;
|
||||
|
||||
_Atomic(enum RunState) p_appState = APP_STATE_RUNNING;
|
||||
|
||||
struct AppState g_state =
|
||||
{
|
||||
.cbRemoteType = SPICE_DATA_NONE,
|
||||
.cbWriteType = SPICE_DATA_NONE,
|
||||
};
|
||||
struct AppState g_state = { 0 };
|
||||
struct CursorState g_cursor;
|
||||
|
||||
// this structure is initialized in config.c
|
||||
@@ -1031,11 +1028,13 @@ static int renderThread(void * unused)
|
||||
{
|
||||
lgInput_setTransport(NULL, NULL);
|
||||
lgAudio_setTransport(NULL, NULL);
|
||||
lgClipboard_setTransport(NULL, NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
lgInput_dropTransport();
|
||||
lgAudio_dropTransport();
|
||||
lgClipboard_dropTransport();
|
||||
}
|
||||
|
||||
core_stopCursorThread();
|
||||
@@ -1087,6 +1086,7 @@ int main_cursorThread(void * unused)
|
||||
{
|
||||
lgInput_dropTransport();
|
||||
lgAudio_dropTransport();
|
||||
lgClipboard_dropTransport();
|
||||
}
|
||||
app_setState(status == LG_TRANSPORT_DISCONNECTED ?
|
||||
APP_STATE_RESTART : APP_STATE_SHUTDOWN);
|
||||
@@ -1229,6 +1229,7 @@ int main_frameThread(void * unused)
|
||||
{
|
||||
lgInput_dropTransport();
|
||||
lgAudio_dropTransport();
|
||||
lgClipboard_dropTransport();
|
||||
app_setState(APP_STATE_RESTART);
|
||||
}
|
||||
else if (status == LG_TRANSPORT_END)
|
||||
@@ -1507,6 +1508,8 @@ static void checkUUID(void)
|
||||
|
||||
g_params.useSpiceInput = false;
|
||||
lgInput_setFallback(NULL, NULL);
|
||||
lgcSpice_setAvailable(false);
|
||||
lgClipboard_setFallback(NULL, NULL);
|
||||
atomic_store_explicit(&g_state.spiceClose, true, memory_order_release);
|
||||
purespice_disconnect();
|
||||
}
|
||||
@@ -1554,6 +1557,10 @@ void spiceReady(void)
|
||||
else
|
||||
DEBUG_WARN("Failed to obtain SPICE server information");
|
||||
|
||||
if (g_params.useSpiceClipboard &&
|
||||
!atomic_load_explicit(&g_state.spiceClose, memory_order_acquire))
|
||||
lgClipboard_setFallback(&LGC_Spice, NULL);
|
||||
|
||||
if (g_params.useSpiceInput)
|
||||
keybind_inputRegister();
|
||||
|
||||
@@ -1721,10 +1728,11 @@ int spiceThread(void * arg)
|
||||
.clipboard =
|
||||
{
|
||||
.enable = g_params.useSpiceClipboard,
|
||||
.notice = cb_spiceNotice,
|
||||
.data = cb_spiceData,
|
||||
.release = cb_spiceRelease,
|
||||
.request = cb_spiceRequest
|
||||
.notice = lgcSpice_notice,
|
||||
.data = lgcSpice_data,
|
||||
.release = lgcSpice_release,
|
||||
.request = lgcSpice_request,
|
||||
.status = lgcSpice_setAvailable,
|
||||
},
|
||||
.display =
|
||||
{
|
||||
@@ -1784,10 +1792,13 @@ int spiceThread(void * arg)
|
||||
DEBUG_ERROR("failed to process spice messages");
|
||||
goto end;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
lgInput_setFallback(NULL, NULL);
|
||||
lgAudio_setFallback(NULL, NULL);
|
||||
lgcSpice_setAvailable(false);
|
||||
lgClipboard_setFallback(NULL, NULL);
|
||||
#if ENABLE_AUDIO
|
||||
lgaSpice_setAvailable(false);
|
||||
#endif
|
||||
@@ -1795,8 +1806,10 @@ int spiceThread(void * arg)
|
||||
|
||||
end:
|
||||
|
||||
lgcSpice_setAvailable(false);
|
||||
lgInput_setFallback(NULL, NULL);
|
||||
lgAudio_setFallback(NULL, NULL);
|
||||
lgClipboard_setFallback(NULL, NULL);
|
||||
#if ENABLE_AUDIO
|
||||
lgaSpice_setAvailable(false);
|
||||
#endif
|
||||
@@ -1911,6 +1924,7 @@ static int lg_run(void)
|
||||
frameTimingInit();
|
||||
lgInput_init();
|
||||
lgAudio_init();
|
||||
lgClipboard_init();
|
||||
|
||||
#ifdef ENABLE_TESTS
|
||||
memset(&l_testCapture, 0, sizeof(l_testCapture));
|
||||
@@ -2214,9 +2228,9 @@ static int lg_run(void)
|
||||
}
|
||||
|
||||
g_state.ds->startup();
|
||||
g_state.cbAvailable = g_state.ds->cbInit && g_state.ds->cbInit();
|
||||
if (g_state.cbAvailable)
|
||||
g_state.cbRequestList = ll_new();
|
||||
const bool clipboardAvailable =
|
||||
g_state.ds->cbInit && g_state.ds->cbInit();
|
||||
lgClipboard_setLocalAvailable(clipboardAvailable);
|
||||
|
||||
if (g_params.captureOnStart)
|
||||
core_setGrab(true);
|
||||
@@ -2386,6 +2400,13 @@ restart:
|
||||
g_state.transportOps->getAudioOps(g_state.transport, &audioOpaque) : NULL;
|
||||
lgAudio_setTransport(audioOps, audioOpaque);
|
||||
|
||||
void * clipboardOpaque = NULL;
|
||||
const LG_ClipboardOps * clipboardOps =
|
||||
g_state.transportOps->getClipboardOps ?
|
||||
g_state.transportOps->getClipboardOps(
|
||||
g_state.transport, &clipboardOpaque) : NULL;
|
||||
lgClipboard_setTransport(clipboardOps, clipboardOpaque);
|
||||
|
||||
if (inputOps || lgInput_available())
|
||||
keybind_inputRegister();
|
||||
checkUUID();
|
||||
@@ -2404,6 +2425,7 @@ restart:
|
||||
{
|
||||
lgInput_dropTransport();
|
||||
lgAudio_dropTransport();
|
||||
lgClipboard_dropTransport();
|
||||
atomic_store_explicit(
|
||||
&g_state.lgHostConnected, false, memory_order_release);
|
||||
DEBUG_INFO("Waiting for the host to restart...");
|
||||
@@ -2426,6 +2448,7 @@ restart:
|
||||
core_stopCursorThread();
|
||||
lgInput_dropTransport();
|
||||
lgAudio_dropTransport();
|
||||
lgClipboard_dropTransport();
|
||||
g_state.transportOps->disconnect(g_state.transport);
|
||||
|
||||
app_setState(APP_STATE_RUNNING);
|
||||
@@ -2474,14 +2497,21 @@ static void lg_shutdown(void)
|
||||
lgInput_dropTransport();
|
||||
if (g_state.transport &&
|
||||
g_state.transportOps->sessionValid(g_state.transport))
|
||||
{
|
||||
lgAudio_setTransport(NULL, NULL);
|
||||
lgClipboard_setTransport(NULL, NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
lgAudio_dropTransport();
|
||||
lgClipboard_dropTransport();
|
||||
}
|
||||
g_state.transportOps->destroy(&g_state.transport);
|
||||
}
|
||||
|
||||
lgInput_free();
|
||||
lgAudio_free();
|
||||
lgClipboard_setLocalAvailable(false);
|
||||
|
||||
if (g_state.frameEvent)
|
||||
{
|
||||
@@ -2503,12 +2533,7 @@ static void lg_shutdown(void)
|
||||
|
||||
if (g_state.ds)
|
||||
g_state.ds->shutdown();
|
||||
|
||||
if (g_state.cbRequestList)
|
||||
{
|
||||
ll_free(g_state.cbRequestList);
|
||||
g_state.cbRequestList = NULL;
|
||||
}
|
||||
lgClipboard_free();
|
||||
|
||||
app_releaseAllKeybinds();
|
||||
ll_free(g_state.bindings);
|
||||
|
||||
@@ -123,13 +123,6 @@ struct AppState
|
||||
LG_Lock lgrLock;
|
||||
bool useDMA;
|
||||
|
||||
bool cbAvailable;
|
||||
_Atomic(PSDataType) cbRemoteType;
|
||||
PSDataType cbWriteType;
|
||||
bool cbChunked;
|
||||
size_t cbXfer;
|
||||
struct ll * cbRequestList;
|
||||
|
||||
LG_Transport * transport;
|
||||
const LG_TransportOps * transportOps;
|
||||
LG_TransportFeatureFlags transportFeatures;
|
||||
@@ -243,13 +236,6 @@ struct AppParams
|
||||
bool audioSyncVolume;
|
||||
};
|
||||
|
||||
struct CBRequest
|
||||
{
|
||||
PSDataType type;
|
||||
LG_ClipboardReplyFn replyFn;
|
||||
void * opaque;
|
||||
};
|
||||
|
||||
struct KeybindHandle
|
||||
{
|
||||
int sc;
|
||||
|
||||
Submodule repos/PureSpice updated: 4f0f5b5b9f...b66cdfa4d4
Reference in New Issue
Block a user