mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-11-13 15:27:21 +00:00
[client] windows: implement file clipboard support
This commit is contained in:
@@ -27,6 +27,7 @@ target_link_libraries(displayserver_Windows
|
|||||||
lg_common
|
lg_common
|
||||||
user32
|
user32
|
||||||
gdi32
|
gdi32
|
||||||
|
ole32
|
||||||
)
|
)
|
||||||
|
|
||||||
if(ENABLE_EGL)
|
if(ENABLE_EGL)
|
||||||
|
|||||||
@@ -29,9 +29,14 @@
|
|||||||
#include "common/debug.h"
|
#include "common/debug.h"
|
||||||
#include "common/windebug.h"
|
#include "common/windebug.h"
|
||||||
#include "core/clipboard.h"
|
#include "core/clipboard.h"
|
||||||
|
#include "core/clipboard_files.h"
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
|
#include "platform/windows/clipboard_files.h"
|
||||||
|
|
||||||
#define WINDOWS_CB_OPEN_ATTEMPTS 8
|
#define WINDOWS_CB_OPEN_ATTEMPTS 8
|
||||||
|
#define WINDOWS_CB_RETRY_INTERVAL_MS 50
|
||||||
|
#define WINDOWS_CB_RETRY_TIMEOUT_MS 5000
|
||||||
|
#define WINDOWS_CB_RETRY_TIMER_ID ((UINT_PTR)0x4C47)
|
||||||
|
|
||||||
enum WindowsClipboardMessage
|
enum WindowsClipboardMessage
|
||||||
{
|
{
|
||||||
@@ -47,6 +52,13 @@ enum WindowsClipboardOperationType
|
|||||||
WINDOWS_CB_OPERATION_DATA,
|
WINDOWS_CB_OPERATION_DATA,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum WindowsClipboardCaptureResult
|
||||||
|
{
|
||||||
|
WINDOWS_CB_CAPTURE_RETRY,
|
||||||
|
WINDOWS_CB_CAPTURE_TYPES,
|
||||||
|
WINDOWS_CB_CAPTURE_FILES,
|
||||||
|
};
|
||||||
|
|
||||||
struct WindowsClipboardOperation
|
struct WindowsClipboardOperation
|
||||||
{
|
{
|
||||||
struct WindowsClipboardOperation * next;
|
struct WindowsClipboardOperation * next;
|
||||||
@@ -79,15 +91,23 @@ struct WindowsClipboardState
|
|||||||
UINT pngFormat;
|
UINT pngFormat;
|
||||||
UINT jfifFormat;
|
UINT jfifFormat;
|
||||||
UINT jpegFormat;
|
UINT jpegFormat;
|
||||||
|
IDataObject * remoteFiles;
|
||||||
LG_ClipboardRequest canceledRequest;
|
LG_ClipboardRequest canceledRequest;
|
||||||
|
ULONGLONG retryDeadline;
|
||||||
|
DWORD retrySequence;
|
||||||
bool listener;
|
bool listener;
|
||||||
bool accepting;
|
bool accepting;
|
||||||
|
bool oleInitialized;
|
||||||
|
bool applyingRemote;
|
||||||
|
bool pendingUpdate;
|
||||||
};
|
};
|
||||||
|
|
||||||
static SRWLOCK windowsCBQueueLock = SRWLOCK_INIT;
|
static SRWLOCK windowsCBQueueLock = SRWLOCK_INIT;
|
||||||
static struct WindowsClipboardState windowsCB;
|
static struct WindowsClipboardState windowsCB;
|
||||||
static _Atomic(uint64_t) windowsCBGeneration;
|
static _Atomic(uint64_t) windowsCBGeneration;
|
||||||
|
|
||||||
|
static void updateLocalClipboard(void);
|
||||||
|
|
||||||
static uint16_t readLE16(const uint8_t * data)
|
static uint16_t readLE16(const uint8_t * data)
|
||||||
{
|
{
|
||||||
return (uint16_t)data[0] |
|
return (uint16_t)data[0] |
|
||||||
@@ -557,6 +577,8 @@ static bool localTypeAvailable(LG_ClipboardData type)
|
|||||||
IsClipboardFormatAvailable(windowsCB.jpegFormat));
|
IsClipboardFormatAvailable(windowsCB.jpegFormat));
|
||||||
|
|
||||||
case LG_CLIPBOARD_DATA_FILES:
|
case LG_CLIPBOARD_DATA_FILES:
|
||||||
|
return IsClipboardFormatAvailable(CF_HDROP);
|
||||||
|
|
||||||
case LG_CLIPBOARD_DATA_NONE:
|
case LG_CLIPBOARD_DATA_NONE:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -649,36 +671,294 @@ static bool readLocalClipboard(LG_ClipboardData type,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void releaseRemoteFilesReference(void)
|
||||||
|
{
|
||||||
|
if (!windowsCB.remoteFiles)
|
||||||
|
return;
|
||||||
|
|
||||||
|
windowsCB.remoteFiles->lpVtbl->Release(windowsCB.remoteFiles);
|
||||||
|
windowsCB.remoteFiles = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool remoteFilesAreCurrent(void)
|
||||||
|
{
|
||||||
|
return windowsCB.oleInitialized && windowsCB.remoteFiles &&
|
||||||
|
OleIsCurrentClipboard(windowsCB.remoteFiles) == S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static HRESULT setOleClipboard(IDataObject * object)
|
||||||
|
{
|
||||||
|
HRESULT status = E_FAIL;
|
||||||
|
DWORD delay = 1;
|
||||||
|
// OleSetClipboard can synchronously dispatch a clipboard notification.
|
||||||
|
windowsCB.applyingRemote = true;
|
||||||
|
for (unsigned int attempt = 0;
|
||||||
|
attempt < WINDOWS_CB_OPEN_ATTEMPTS; ++attempt)
|
||||||
|
{
|
||||||
|
status = OleSetClipboard(object);
|
||||||
|
if (SUCCEEDED(status))
|
||||||
|
break;
|
||||||
|
if (attempt + 1U < WINDOWS_CB_OPEN_ATTEMPTS)
|
||||||
|
{
|
||||||
|
Sleep(delay);
|
||||||
|
delay *= 2U;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
windowsCB.applyingRemote = false;
|
||||||
|
if (windowsCB.pendingUpdate && windowsCB.accepting && windowsCB.window)
|
||||||
|
{
|
||||||
|
windowsCB.pendingUpdate = false;
|
||||||
|
if (!PostMessageW(windowsCB.window, WM_CLIPBOARDUPDATE, 0, 0))
|
||||||
|
DEBUG_WINERROR("Failed to replay a Windows clipboard update",
|
||||||
|
GetLastError());
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool clearRemoteFilesClipboard(void)
|
||||||
|
{
|
||||||
|
if (!windowsCB.remoteFiles)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (remoteFilesAreCurrent())
|
||||||
|
{
|
||||||
|
const HRESULT status = setOleClipboard(NULL);
|
||||||
|
if (FAILED(status))
|
||||||
|
{
|
||||||
|
DEBUG_WINERROR("Failed to clear the Windows file clipboard", status);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
releaseRemoteFilesReference();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WindowsClipboardCapture
|
||||||
|
{
|
||||||
|
LG_ClipboardData types[LG_CLIPBOARD_DATA_NONE];
|
||||||
|
size_t count;
|
||||||
|
uint32_t mask;
|
||||||
|
uint8_t * fileData;
|
||||||
|
size_t fileSize;
|
||||||
|
HWND owner;
|
||||||
|
DWORD sequence;
|
||||||
|
};
|
||||||
|
|
||||||
|
static enum WindowsClipboardCaptureResult captureLocalClipboard(
|
||||||
|
HWND owner, DWORD sequence, struct WindowsClipboardCapture * capture)
|
||||||
|
{
|
||||||
|
memset(capture, 0, sizeof(*capture));
|
||||||
|
// Explorer can still own the clipboard when WM_CLIPBOARDUPDATE arrives.
|
||||||
|
// Acquire it before probing formats so that generation is retried instead
|
||||||
|
// of being recorded as an empty clipboard.
|
||||||
|
if (!openClipboard())
|
||||||
|
return WINDOWS_CB_CAPTURE_RETRY;
|
||||||
|
|
||||||
|
if (owner != GetClipboardOwner() ||
|
||||||
|
sequence != GetClipboardSequenceNumber())
|
||||||
|
{
|
||||||
|
CloseClipboard();
|
||||||
|
return WINDOWS_CB_CAPTURE_RETRY;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool files = localTypeAvailable(LG_CLIPBOARD_DATA_FILES);
|
||||||
|
bool copied = false;
|
||||||
|
if (files)
|
||||||
|
{
|
||||||
|
HANDLE handle = GetClipboardData(CF_HDROP);
|
||||||
|
copied = handle && copyGlobalData(handle,
|
||||||
|
&capture->fileData, &capture->fileSize);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (LG_ClipboardData type = LG_CLIPBOARD_DATA_TEXT;
|
||||||
|
type < LG_CLIPBOARD_DATA_FILES; ++type)
|
||||||
|
{
|
||||||
|
if (!localTypeAvailable(type))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
capture->types[capture->count++] = type;
|
||||||
|
capture->mask |= UINT32_C(1) << type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
capture->owner = GetClipboardOwner();
|
||||||
|
capture->sequence = GetClipboardSequenceNumber();
|
||||||
|
const bool sequenceChanged = capture->sequence != sequence;
|
||||||
|
const BOOL closed = CloseClipboard();
|
||||||
|
const DWORD closeError = closed ? ERROR_SUCCESS : GetLastError();
|
||||||
|
const HWND currentOwner = GetClipboardOwner();
|
||||||
|
const DWORD currentSequence = GetClipboardSequenceNumber();
|
||||||
|
const bool stable = closed && capture->owner == currentOwner &&
|
||||||
|
capture->sequence == currentSequence && capture->owner == owner &&
|
||||||
|
(!sequenceChanged || (files && owner));
|
||||||
|
if (!stable || (files && !copied))
|
||||||
|
{
|
||||||
|
if (!closed)
|
||||||
|
DEBUG_WINERROR("Failed to close the Windows clipboard", closeError);
|
||||||
|
free(capture->fileData);
|
||||||
|
capture->fileData = NULL;
|
||||||
|
return WINDOWS_CB_CAPTURE_RETRY;
|
||||||
|
}
|
||||||
|
|
||||||
|
return files ? WINDOWS_CB_CAPTURE_FILES : WINDOWS_CB_CAPTURE_TYPES;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clearLocalRetry(void)
|
||||||
|
{
|
||||||
|
if (windowsCB.window && windowsCB.retryDeadline)
|
||||||
|
KillTimer(windowsCB.window, WINDOWS_CB_RETRY_TIMER_ID);
|
||||||
|
windowsCB.retryDeadline = 0;
|
||||||
|
windowsCB.retrySequence = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void expireLocalRetry(DWORD sequence)
|
||||||
|
{
|
||||||
|
clearLocalRetry();
|
||||||
|
if (!windowsCB.accepting || remoteFilesAreCurrent())
|
||||||
|
return;
|
||||||
|
if (sequence != GetClipboardSequenceNumber())
|
||||||
|
{
|
||||||
|
PostMessageW(windowsCB.window, WM_CLIPBOARDUPDATE, 0, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
windowsCB.localOwner = GetClipboardOwner();
|
||||||
|
windowsCB.localSequence = sequence;
|
||||||
|
windowsCB.localTypes = 0;
|
||||||
|
clipboardFiles_clearLocal();
|
||||||
|
clipboard_release();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void deferLocalClipboard(DWORD sequence)
|
||||||
|
{
|
||||||
|
const ULONGLONG now = GetTickCount64();
|
||||||
|
if (!windowsCB.retryDeadline || windowsCB.retrySequence != sequence)
|
||||||
|
{
|
||||||
|
clearLocalRetry();
|
||||||
|
windowsCB.retrySequence = sequence;
|
||||||
|
windowsCB.retryDeadline = now + WINDOWS_CB_RETRY_TIMEOUT_MS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (now < windowsCB.retryDeadline &&
|
||||||
|
SetTimer(windowsCB.window, WINDOWS_CB_RETRY_TIMER_ID,
|
||||||
|
WINDOWS_CB_RETRY_INTERVAL_MS, NULL))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (now >= windowsCB.retryDeadline)
|
||||||
|
DEBUG_WARN("Timed out waiting for the Windows clipboard");
|
||||||
|
else
|
||||||
|
DEBUG_WINERROR("Failed to schedule a Windows clipboard retry",
|
||||||
|
GetLastError());
|
||||||
|
expireLocalRetry(sequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void retryLocalClipboard(void)
|
||||||
|
{
|
||||||
|
if (windowsCB.window)
|
||||||
|
KillTimer(windowsCB.window, WINDOWS_CB_RETRY_TIMER_ID);
|
||||||
|
if (!windowsCB.retryDeadline)
|
||||||
|
return;
|
||||||
|
if (!windowsCB.accepting || remoteFilesAreCurrent())
|
||||||
|
{
|
||||||
|
clearLocalRetry();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DWORD sequence = GetClipboardSequenceNumber();
|
||||||
|
if (sequence != windowsCB.retrySequence)
|
||||||
|
{
|
||||||
|
clearLocalRetry();
|
||||||
|
PostMessageW(windowsCB.window, WM_CLIPBOARDUPDATE, 0, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (GetTickCount64() >= windowsCB.retryDeadline)
|
||||||
|
{
|
||||||
|
DEBUG_WARN("Timed out waiting for the Windows clipboard");
|
||||||
|
expireLocalRetry(sequence);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLocalClipboard();
|
||||||
|
}
|
||||||
|
|
||||||
static void updateLocalClipboard(void)
|
static void updateLocalClipboard(void)
|
||||||
{
|
{
|
||||||
|
if (windowsCB.applyingRemote)
|
||||||
|
{
|
||||||
|
windowsCB.pendingUpdate = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const HWND owner = GetClipboardOwner();
|
const HWND owner = GetClipboardOwner();
|
||||||
const DWORD sequence = GetClipboardSequenceNumber();
|
const DWORD sequence = GetClipboardSequenceNumber();
|
||||||
|
if (remoteFilesAreCurrent())
|
||||||
|
{
|
||||||
|
clearLocalRetry();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
releaseRemoteFilesReference();
|
||||||
if (owner == windowsCB.window)
|
if (owner == windowsCB.window)
|
||||||
return;
|
return;
|
||||||
if (owner == windowsCB.localOwner &&
|
if (owner == windowsCB.localOwner &&
|
||||||
sequence == windowsCB.localSequence)
|
sequence == windowsCB.localSequence)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
LG_ClipboardData types[LG_CLIPBOARD_DATA_NONE];
|
if (!windowsCB.retryDeadline || windowsCB.retrySequence != sequence)
|
||||||
size_t count = 0;
|
atomic_fetch_add_explicit(
|
||||||
uint32_t mask = 0;
|
&windowsCBGeneration, 1, memory_order_acq_rel);
|
||||||
for (LG_ClipboardData type = LG_CLIPBOARD_DATA_TEXT;
|
|
||||||
type < LG_CLIPBOARD_DATA_FILES; ++type)
|
|
||||||
{
|
|
||||||
if (!localTypeAvailable(type))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
types[count++] = type;
|
struct WindowsClipboardCapture capture;
|
||||||
mask |= UINT32_C(1) << type;
|
const enum WindowsClipboardCaptureResult result =
|
||||||
|
captureLocalClipboard(owner, sequence, &capture);
|
||||||
|
if (result == WINDOWS_CB_CAPTURE_RETRY)
|
||||||
|
{
|
||||||
|
if (owner != GetClipboardOwner() ||
|
||||||
|
sequence != GetClipboardSequenceNumber())
|
||||||
|
{
|
||||||
|
clearLocalRetry();
|
||||||
|
PostMessageW(windowsCB.window, WM_CLIPBOARDUPDATE, 0, 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
deferLocalClipboard(sequence);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
windowsCB.localOwner = owner;
|
if (result == WINDOWS_CB_CAPTURE_FILES)
|
||||||
windowsCB.localSequence = sequence;
|
{
|
||||||
windowsCB.localTypes = mask;
|
HGLOBAL duplicate = makeGlobalData(capture.fileData, capture.fileSize);
|
||||||
atomic_fetch_add_explicit(
|
free(capture.fileData);
|
||||||
&windowsCBGeneration, 1, memory_order_acq_rel);
|
if (!duplicate)
|
||||||
if (count)
|
{
|
||||||
clipboard_notifyTypes(types, count);
|
deferLocalClipboard(capture.sequence);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool published = windowsClipboardFiles_setLocalDrop(
|
||||||
|
(HDROP)duplicate);
|
||||||
|
GlobalFree(duplicate);
|
||||||
|
if (!published)
|
||||||
|
{
|
||||||
|
DEBUG_WARN("Failed to capture the Windows file clipboard");
|
||||||
|
deferLocalClipboard(capture.sequence);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearLocalRetry();
|
||||||
|
windowsCB.localOwner = capture.owner;
|
||||||
|
windowsCB.localSequence = capture.sequence;
|
||||||
|
windowsCB.localTypes = UINT32_C(1) << LG_CLIPBOARD_DATA_FILES;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearLocalRetry();
|
||||||
|
windowsCB.localOwner = capture.owner;
|
||||||
|
windowsCB.localSequence = capture.sequence;
|
||||||
|
windowsCB.localTypes = capture.mask;
|
||||||
|
clipboardFiles_clearLocal();
|
||||||
|
if (capture.count)
|
||||||
|
clipboard_notifyTypes(capture.types, capture.count);
|
||||||
else
|
else
|
||||||
clipboard_release();
|
clipboard_release();
|
||||||
}
|
}
|
||||||
@@ -827,7 +1107,8 @@ static void handleRemoteData(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (operation->generation != atomic_load_explicit(
|
if (operation->generation != atomic_load_explicit(
|
||||||
&windowsCBGeneration, memory_order_acquire) || !openClipboard())
|
&windowsCBGeneration, memory_order_acquire) ||
|
||||||
|
!clearRemoteFilesClipboard() || !openClipboard())
|
||||||
goto fail;
|
goto fail;
|
||||||
if (operation->generation != atomic_load_explicit(
|
if (operation->generation != atomic_load_explicit(
|
||||||
&windowsCBGeneration, memory_order_acquire) || !EmptyClipboard())
|
&windowsCBGeneration, memory_order_acquire) || !EmptyClipboard())
|
||||||
@@ -835,7 +1116,6 @@ static void handleRemoteData(
|
|||||||
CloseClipboard();
|
CloseClipboard();
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool published = false;
|
bool published = false;
|
||||||
for (size_t i = 0; i < count; ++i)
|
for (size_t i = 0; i < count; ++i)
|
||||||
{
|
{
|
||||||
@@ -853,6 +1133,7 @@ static void handleRemoteData(
|
|||||||
GetLastError());
|
GetLastError());
|
||||||
}
|
}
|
||||||
CloseClipboard();
|
CloseClipboard();
|
||||||
|
clipboardFiles_clearLocal();
|
||||||
windowsCB.localOwner = GetClipboardOwner();
|
windowsCB.localOwner = GetClipboardOwner();
|
||||||
windowsCB.localSequence = GetClipboardSequenceNumber();
|
windowsCB.localSequence = GetClipboardSequenceNumber();
|
||||||
windowsCB.localTypes = 0;
|
windowsCB.localTypes = 0;
|
||||||
@@ -865,12 +1146,96 @@ fail:
|
|||||||
GlobalFree(handles[i]);
|
GlobalFree(handles[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void handleRemoteFiles(
|
||||||
|
const struct WindowsClipboardOperation * operation)
|
||||||
|
{
|
||||||
|
if (!windowsCB.oleInitialized)
|
||||||
|
{
|
||||||
|
DEBUG_WARN("OLE is unavailable; cannot publish guest clipboard files");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint64_t presentation =
|
||||||
|
clipboardFiles_remotePresentationAcquire();
|
||||||
|
if (!presentation)
|
||||||
|
{
|
||||||
|
DEBUG_WARN("Failed to acquire the guest clipboard file offer");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IDataObject * object = NULL;
|
||||||
|
if (!windowsClipboardFiles_createRemoteDataObject(
|
||||||
|
presentation, &object) || !object)
|
||||||
|
{
|
||||||
|
clipboardFiles_remotePresentationRelease(presentation);
|
||||||
|
DEBUG_WARN("Failed to create the guest clipboard file object");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation->generation != atomic_load_explicit(
|
||||||
|
&windowsCBGeneration, memory_order_acquire))
|
||||||
|
{
|
||||||
|
object->lpVtbl->Release(object);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const HRESULT status = setOleClipboard(object);
|
||||||
|
if (FAILED(status))
|
||||||
|
{
|
||||||
|
DEBUG_WINERROR("Failed to publish guest clipboard files", status);
|
||||||
|
object->lpVtbl->Release(object);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation->generation != atomic_load_explicit(
|
||||||
|
&windowsCBGeneration, memory_order_acquire))
|
||||||
|
{
|
||||||
|
const HRESULT clearStatus = OleIsCurrentClipboard(object) == S_OK ?
|
||||||
|
setOleClipboard(NULL) : S_OK;
|
||||||
|
releaseRemoteFilesReference();
|
||||||
|
if (FAILED(clearStatus) && OleIsCurrentClipboard(object) == S_OK)
|
||||||
|
{
|
||||||
|
DEBUG_WINERROR("Failed to withdraw stale guest clipboard files",
|
||||||
|
clearStatus);
|
||||||
|
windowsCB.remoteFiles = object;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
object->lpVtbl->Release(object);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
releaseRemoteFilesReference();
|
||||||
|
windowsCB.remoteFiles = object;
|
||||||
|
windowsCB.localOwner = GetClipboardOwner();
|
||||||
|
windowsCB.localSequence = GetClipboardSequenceNumber();
|
||||||
|
windowsCB.localTypes = 0;
|
||||||
|
clipboardFiles_clearLocal();
|
||||||
|
clipboardFiles_remotePresentationDelivered(presentation);
|
||||||
|
}
|
||||||
|
|
||||||
static void handleRemoteRelease(
|
static void handleRemoteRelease(
|
||||||
const struct WindowsClipboardOperation * operation)
|
const struct WindowsClipboardOperation * operation)
|
||||||
{
|
{
|
||||||
if (operation->generation != atomic_load_explicit(
|
if (operation->generation != atomic_load_explicit(
|
||||||
&windowsCBGeneration, memory_order_acquire) ||
|
&windowsCBGeneration, memory_order_acquire))
|
||||||
GetClipboardOwner() != windowsCB.window || !openClipboard())
|
return;
|
||||||
|
|
||||||
|
if (windowsCB.remoteFiles)
|
||||||
|
{
|
||||||
|
const bool current = remoteFilesAreCurrent();
|
||||||
|
if (!clearRemoteFilesClipboard())
|
||||||
|
return;
|
||||||
|
if (current)
|
||||||
|
{
|
||||||
|
windowsCB.localOwner = GetClipboardOwner();
|
||||||
|
windowsCB.localSequence = GetClipboardSequenceNumber();
|
||||||
|
windowsCB.localTypes = 0;
|
||||||
|
clipboardFiles_clearLocal();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GetClipboardOwner() != windowsCB.window || !openClipboard())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
bool released = false;
|
bool released = false;
|
||||||
@@ -884,6 +1249,7 @@ static void handleRemoteRelease(
|
|||||||
windowsCB.localOwner = GetClipboardOwner();
|
windowsCB.localOwner = GetClipboardOwner();
|
||||||
windowsCB.localSequence = GetClipboardSequenceNumber();
|
windowsCB.localSequence = GetClipboardSequenceNumber();
|
||||||
windowsCB.localTypes = 0;
|
windowsCB.localTypes = 0;
|
||||||
|
clipboardFiles_clearLocal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -997,7 +1363,7 @@ static void handleRemoteNotice(
|
|||||||
return;
|
return;
|
||||||
if (operation->dataType == LG_CLIPBOARD_DATA_FILES)
|
if (operation->dataType == LG_CLIPBOARD_DATA_FILES)
|
||||||
{
|
{
|
||||||
handleRemoteRelease(operation);
|
handleRemoteFiles(operation);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (operation->dataType >= LG_CLIPBOARD_DATA_NONE)
|
if (operation->dataType >= LG_CLIPBOARD_DATA_NONE)
|
||||||
@@ -1052,6 +1418,13 @@ static bool eventInit(HWND window)
|
|||||||
if (!window)
|
if (!window)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
const HRESULT oleStatus = OleInitialize(NULL);
|
||||||
|
if (SUCCEEDED(oleStatus))
|
||||||
|
windowsCB.oleInitialized = true;
|
||||||
|
else
|
||||||
|
DEBUG_WINERROR("Failed to initialize OLE for clipboard files",
|
||||||
|
oleStatus);
|
||||||
|
|
||||||
windowsCB.pngFormat = RegisterClipboardFormatW(L"PNG");
|
windowsCB.pngFormat = RegisterClipboardFormatW(L"PNG");
|
||||||
windowsCB.jfifFormat = RegisterClipboardFormatW(L"JFIF");
|
windowsCB.jfifFormat = RegisterClipboardFormatW(L"JFIF");
|
||||||
windowsCB.jpegFormat = RegisterClipboardFormatW(L"JPEG");
|
windowsCB.jpegFormat = RegisterClipboardFormatW(L"JPEG");
|
||||||
@@ -1060,6 +1433,11 @@ static bool eventInit(HWND window)
|
|||||||
{
|
{
|
||||||
DEBUG_WINERROR("Failed to register Windows clipboard formats",
|
DEBUG_WINERROR("Failed to register Windows clipboard formats",
|
||||||
GetLastError());
|
GetLastError());
|
||||||
|
if (windowsCB.oleInitialized)
|
||||||
|
{
|
||||||
|
OleUninitialize();
|
||||||
|
windowsCB.oleInitialized = false;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1067,6 +1445,11 @@ static bool eventInit(HWND window)
|
|||||||
{
|
{
|
||||||
DEBUG_WINERROR("Failed to register the Windows clipboard listener",
|
DEBUG_WINERROR("Failed to register the Windows clipboard listener",
|
||||||
GetLastError());
|
GetLastError());
|
||||||
|
if (windowsCB.oleInitialized)
|
||||||
|
{
|
||||||
|
OleUninitialize();
|
||||||
|
windowsCB.oleInitialized = false;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1083,7 +1466,6 @@ static bool eventInit(HWND window)
|
|||||||
bool windowsCBHandleMessage(HWND window, UINT message,
|
bool windowsCBHandleMessage(HWND window, UINT message,
|
||||||
WPARAM wParam, LPARAM lParam, LRESULT * result)
|
WPARAM wParam, LPARAM lParam, LRESULT * result)
|
||||||
{
|
{
|
||||||
(void)wParam;
|
|
||||||
(void)lParam;
|
(void)lParam;
|
||||||
|
|
||||||
switch (message)
|
switch (message)
|
||||||
@@ -1102,6 +1484,13 @@ bool windowsCBHandleMessage(HWND window, UINT message,
|
|||||||
updateLocalClipboard();
|
updateLocalClipboard();
|
||||||
*result = 0;
|
*result = 0;
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
case WM_TIMER:
|
||||||
|
if ((UINT_PTR)wParam != WINDOWS_CB_RETRY_TIMER_ID)
|
||||||
|
return false;
|
||||||
|
retryLocalClipboard();
|
||||||
|
*result = 0;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -1113,6 +1502,7 @@ void windowsCBEventShutdown(void)
|
|||||||
bool listener;
|
bool listener;
|
||||||
struct WindowsClipboardOperation * operations;
|
struct WindowsClipboardOperation * operations;
|
||||||
|
|
||||||
|
clearLocalRetry();
|
||||||
AcquireSRWLockExclusive(&windowsCBQueueLock);
|
AcquireSRWLockExclusive(&windowsCBQueueLock);
|
||||||
window = windowsCB.window;
|
window = windowsCB.window;
|
||||||
listener = windowsCB.listener;
|
listener = windowsCB.listener;
|
||||||
@@ -1129,6 +1519,13 @@ void windowsCBEventShutdown(void)
|
|||||||
if (listener && window && !RemoveClipboardFormatListener(window))
|
if (listener && window && !RemoveClipboardFormatListener(window))
|
||||||
DEBUG_WINERROR("Failed to remove the Windows clipboard listener",
|
DEBUG_WINERROR("Failed to remove the Windows clipboard listener",
|
||||||
GetLastError());
|
GetLastError());
|
||||||
|
if (!clearRemoteFilesClipboard())
|
||||||
|
releaseRemoteFilesReference();
|
||||||
|
if (windowsCB.oleInitialized)
|
||||||
|
{
|
||||||
|
OleUninitialize();
|
||||||
|
windowsCB.oleInitialized = false;
|
||||||
|
}
|
||||||
freeOperations(operations);
|
freeOperations(operations);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ bool clipboard_requestCancel(LG_ClipboardRequest request,
|
|||||||
bool clipboard_request(LG_ClipboardData type,
|
bool clipboard_request(LG_ClipboardData type,
|
||||||
LG_ClipboardReplyFn replyFn, void * opaque);
|
LG_ClipboardReplyFn replyFn, void * opaque);
|
||||||
|
|
||||||
/* File-dataset transport entry points used by clipboard_files.c. */
|
/* File-dataset transport entry points used by platform clipboard backends. */
|
||||||
bool clipboard_fileAcquire(uint64_t dataset, uint64_t acquisition);
|
bool clipboard_fileAcquire(uint64_t dataset, uint64_t acquisition);
|
||||||
bool clipboard_fileAcquired(uint64_t dataset, uint64_t acquisition,
|
bool clipboard_fileAcquired(uint64_t dataset, uint64_t acquisition,
|
||||||
LG_ClipboardFileError error);
|
LG_ClipboardFileError error);
|
||||||
|
|||||||
@@ -23,6 +23,10 @@
|
|||||||
|
|
||||||
#include "interface/clipboard.h"
|
#include "interface/clipboard.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
bool clipboardFiles_init(void);
|
bool clipboardFiles_init(void);
|
||||||
void clipboardFiles_free(void);
|
void clipboardFiles_free(void);
|
||||||
|
|
||||||
@@ -83,4 +87,8 @@ bool clipboardFiles_testLocalRequest(
|
|||||||
const LG_ClipboardFileRequest * request);
|
const LG_ClipboardFileRequest * request);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
43
client/include/platform/windows/clipboard_files.h
Normal file
43
client/include/platform/windows/clipboard_files.h
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* 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_PLATFORM_WINDOWS_CLIPBOARD_FILES_
|
||||||
|
#define _H_LG_PLATFORM_WINDOWS_CLIPBOARD_FILES_
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <ole2.h>
|
||||||
|
#include <shellapi.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool windowsClipboardFiles_setLocalDrop(HDROP drop);
|
||||||
|
bool windowsClipboardFiles_createRemoteDataObject(uint64_t presentation,
|
||||||
|
IDataObject ** dataObject);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
88
client/include/platform/windows/clipboard_files_win32.h
Normal file
88
client/include/platform/windows/clipboard_files_win32.h
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
/**
|
||||||
|
* 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_PLATFORM_WINDOWS_CLIPBOARD_FILES_WIN32_
|
||||||
|
#define _H_LG_PLATFORM_WINDOWS_CLIPBOARD_FILES_WIN32_
|
||||||
|
|
||||||
|
#include "interface/clipboard.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <ole2.h>
|
||||||
|
#include <shellapi.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct WindowsClipboardLocalFiles WindowsClipboardLocalFiles;
|
||||||
|
typedef struct WindowsClipboardRemoteManifest WindowsClipboardRemoteManifest;
|
||||||
|
|
||||||
|
typedef struct WindowsClipboardRemoteProviderOps
|
||||||
|
{
|
||||||
|
HRESULT (*read )(void * opaque, uint64_t dataset, uint64_t acquisition,
|
||||||
|
uint64_t node, uint64_t offset, void * data, ULONG length,
|
||||||
|
ULONG * read);
|
||||||
|
void (*release)(void * opaque, uint64_t dataset, uint64_t acquisition);
|
||||||
|
}
|
||||||
|
WindowsClipboardRemoteProviderOps;
|
||||||
|
|
||||||
|
/* Capture copies the selection metadata. List and read return malloc-owned
|
||||||
|
* buffers which the caller releases with free(). */
|
||||||
|
WindowsClipboardLocalFiles * windowsClipboardLocalFilesCapture(HDROP drop,
|
||||||
|
LG_ClipboardFileError * error);
|
||||||
|
void windowsClipboardLocalFilesRetain(WindowsClipboardLocalFiles * files);
|
||||||
|
void windowsClipboardLocalFilesRelease(WindowsClipboardLocalFiles * files);
|
||||||
|
bool windowsClipboardLocalFilesList(WindowsClipboardLocalFiles * files,
|
||||||
|
uint64_t node, void ** data, size_t * size,
|
||||||
|
LG_ClipboardFileError * error);
|
||||||
|
bool windowsClipboardLocalFilesRead(WindowsClipboardLocalFiles * files,
|
||||||
|
uint64_t node, uint64_t offset, uint32_t length, void ** data,
|
||||||
|
size_t * size, LG_ClipboardFileError * error);
|
||||||
|
|
||||||
|
WindowsClipboardRemoteManifest * windowsClipboardRemoteManifestCreate(void);
|
||||||
|
void windowsClipboardRemoteManifestFree(
|
||||||
|
WindowsClipboardRemoteManifest * manifest);
|
||||||
|
/* Call NextDirectory before each LIST request, then append the complete
|
||||||
|
* response for the returned node. The first returned node is the wire root. */
|
||||||
|
bool windowsClipboardRemoteManifestAppendList(
|
||||||
|
WindowsClipboardRemoteManifest * manifest, uint64_t parent,
|
||||||
|
const void * data, size_t size, LG_ClipboardFileError * error);
|
||||||
|
bool windowsClipboardRemoteManifestNextDirectory(
|
||||||
|
WindowsClipboardRemoteManifest * manifest, uint64_t * node);
|
||||||
|
bool windowsClipboardRemoteManifestHasEntries(
|
||||||
|
const WindowsClipboardRemoteManifest * manifest);
|
||||||
|
|
||||||
|
/* The data object owns one provider lease after a successful return. The
|
||||||
|
* release callback runs once after both the object and all of its streams are
|
||||||
|
* released. A failed call does not invoke it. */
|
||||||
|
HRESULT windowsClipboardRemoteCreateDataObject(
|
||||||
|
WindowsClipboardRemoteManifest * manifest, uint64_t dataset,
|
||||||
|
uint64_t acquisition, const WindowsClipboardRemoteProviderOps * ops,
|
||||||
|
void * opaque, IDataObject ** object);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -3,6 +3,7 @@ project(client_platform_Windows LANGUAGES C CXX)
|
|||||||
|
|
||||||
add_library(client_platform_Windows STATIC
|
add_library(client_platform_Windows STATIC
|
||||||
clipboard_files.c
|
clipboard_files.c
|
||||||
|
clipboard_files_win32.c
|
||||||
config.c
|
config.c
|
||||||
font.cpp
|
font.cpp
|
||||||
input.c
|
input.c
|
||||||
@@ -16,6 +17,7 @@ target_link_libraries(client_platform_Windows
|
|||||||
ole32
|
ole32
|
||||||
shell32
|
shell32
|
||||||
shlwapi
|
shlwapi
|
||||||
|
user32
|
||||||
uuid
|
uuid
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
2570
client/platform/Windows/clipboard_files_win32.c
Normal file
2570
client/platform/Windows/clipboard_files_win32.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -40,17 +40,6 @@ set_tests_properties(event-source-tests PROPERTIES
|
|||||||
TIMEOUT 10
|
TIMEOUT 10
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(windows-clipboard-files-tests
|
|
||||||
clipboard_files_windows_test.c
|
|
||||||
../platform/Windows/clipboard_files.c
|
|
||||||
)
|
|
||||||
add_test(NAME windows-clipboard-files-tests
|
|
||||||
COMMAND windows-clipboard-files-tests
|
|
||||||
)
|
|
||||||
set_tests_properties(windows-clipboard-files-tests PROPERTIES
|
|
||||||
TIMEOUT 10
|
|
||||||
)
|
|
||||||
|
|
||||||
if(LG_CLIENT_PLATFORM_LINUX)
|
if(LG_CLIENT_PLATFORM_LINUX)
|
||||||
add_executable(config-platform-tests
|
add_executable(config-platform-tests
|
||||||
config_platform_test.c
|
config_platform_test.c
|
||||||
|
|||||||
@@ -1,103 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 "core/clipboard_files.h"
|
|
||||||
#include "test.h"
|
|
||||||
|
|
||||||
static struct
|
|
||||||
{
|
|
||||||
uint64_t dataset;
|
|
||||||
uint64_t transfer;
|
|
||||||
LG_ClipboardFileError error;
|
|
||||||
unsigned int acquired;
|
|
||||||
unsigned int cancelled;
|
|
||||||
}
|
|
||||||
trace;
|
|
||||||
|
|
||||||
bool clipboard_fileAcquired(uint64_t dataset, uint64_t acquisition,
|
|
||||||
LG_ClipboardFileError error)
|
|
||||||
{
|
|
||||||
trace.dataset = dataset;
|
|
||||||
trace.transfer = acquisition;
|
|
||||||
trace.error = error;
|
|
||||||
++trace.acquired;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool clipboard_fileCancel(uint64_t dataset, uint64_t request,
|
|
||||||
LG_ClipboardFileError reason)
|
|
||||||
{
|
|
||||||
trace.dataset = dataset;
|
|
||||||
trace.transfer = request;
|
|
||||||
trace.error = reason;
|
|
||||||
++trace.cancelled;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
const LG_ClipboardFileRequest request =
|
|
||||||
{
|
|
||||||
.dataset = UINT64_C(0x1234),
|
|
||||||
.request = UINT64_C(0x5678),
|
|
||||||
.node = 1,
|
|
||||||
.operation = LG_CLIPBOARD_FILE_READ,
|
|
||||||
};
|
|
||||||
|
|
||||||
CHECK(clipboardFiles_init());
|
|
||||||
CHECK(!clipboardFiles_remoteOffer(request.dataset));
|
|
||||||
CHECK(!clipboardFiles_remoteReady(request.dataset));
|
|
||||||
|
|
||||||
char * data = (char *)(uintptr_t)1;
|
|
||||||
size_t size = 1;
|
|
||||||
CHECK(!clipboardFiles_getRemote("text/uri-list", &data, &size));
|
|
||||||
CHECK(!data);
|
|
||||||
CHECK(size == 0);
|
|
||||||
|
|
||||||
data = (char *)(uintptr_t)1;
|
|
||||||
size = 1;
|
|
||||||
CHECK(!clipboardFiles_getRemotePresentation(
|
|
||||||
1, "text/uri-list", &data, &size));
|
|
||||||
CHECK(!data);
|
|
||||||
CHECK(size == 0);
|
|
||||||
|
|
||||||
clipboardFiles_localAcquire(request.dataset, request.request);
|
|
||||||
CHECK(trace.acquired == 1);
|
|
||||||
CHECK(trace.dataset == request.dataset);
|
|
||||||
CHECK(trace.transfer == request.request);
|
|
||||||
CHECK(trace.error == LG_CLIPBOARD_FILE_ERROR_NOT_SUPPORTED);
|
|
||||||
|
|
||||||
clipboardFiles_localRequest(&request);
|
|
||||||
CHECK(trace.cancelled == 1);
|
|
||||||
CHECK(trace.dataset == request.dataset);
|
|
||||||
CHECK(trace.transfer == request.request);
|
|
||||||
CHECK(trace.error == LG_CLIPBOARD_FILE_ERROR_NOT_SUPPORTED);
|
|
||||||
|
|
||||||
clipboardFiles_localRequest(NULL);
|
|
||||||
CHECK(trace.cancelled == 1);
|
|
||||||
CHECK(clipboardFiles_remoteDataBegin(&request, 0) ==
|
|
||||||
LG_CLIPBOARD_RESULT_FAILED);
|
|
||||||
CHECK(clipboardFiles_remoteDataChunk(&request, 0, NULL, 0) ==
|
|
||||||
LG_CLIPBOARD_RESULT_FAILED);
|
|
||||||
CHECK(clipboardFiles_remoteDataEnd(&request, 0) ==
|
|
||||||
LG_CLIPBOARD_RESULT_FAILED);
|
|
||||||
clipboardFiles_free();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user