Files
LookingGlass/client/src/clipboard.c
2026-08-11 15:06:45 +10:00

907 lines
25 KiB
C

/**
* 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.h"
#include "main.h"
#include "common/debug.h"
#include "common/ll.h"
#include "common/locking.h"
#include <limits.h>
#include <stdlib.h>
#include <string.h>
typedef struct ClipboardBinding
{
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)
{
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);
}
}
static void resetRemote(void)
{
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);
clearRemoteRequestNL();
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 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(&current, 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(&current);
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(&current);
LG_UNLOCK_SHARED(clipboard.activeLock);
if (!deliver || !active || !bindingEqual(&request->binding, &current) ||
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(&current);
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 all calls into an endpoint
* which has already disappeared. */
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 && !dropActive)
{
if (!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);
}
static void dropBinding(ClipboardBinding * target)
{
LG_LOCK(clipboard.registrationLock);
LG_LOCK(clipboard.providerLock);
LG_LOCK_EXCLUSIVE(clipboard.activeLock);
const bool wasActive = bindingActiveNL(target);
*target = (ClipboardBinding) { 0 };
LG_UNLOCK_EXCLUSIVE(clipboard.activeLock);
updateActive(wasActive);
LG_UNLOCK(clipboard.providerLock);
LG_UNLOCK(clipboard.registrationLock);
}
void lgClipboard_dropFallback(void)
{
dropBinding(&clipboard.fallback);
}
void lgClipboard_setTransport(const LG_ClipboardOps * ops, void * opaque)
{
setBinding(&clipboard.transport,
ops, opaque, transportStatusChanged);
}
void lgClipboard_dropTransport(void)
{
dropBinding(&clipboard.transport);
}
void lgClipboard_release(void)
{
if (!g_params.clipboardToVM)
return;
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;
}