FreeRDP/client/Wayland/wlf_cliprdr.c
2024-09-20 18:49:38 +02:00

1017 lines
26 KiB
C

/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Wayland Clipboard Redirection
*
* Copyright 2018 Armin Novak <armin.novak@thincast.com>
* Copyright 2018 Thincast Technologies GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <freerdp/config.h>
#include <stdlib.h>
#include <winpr/crt.h>
#include <winpr/image.h>
#include <winpr/stream.h>
#include <winpr/clipboard.h>
#include <freerdp/log.h>
#include <freerdp/client/cliprdr.h>
#include <freerdp/channels/channels.h>
#include <freerdp/channels/cliprdr.h>
#include <freerdp/client/client_cliprdr_file.h>
#include "wlf_cliprdr.h"
#define TAG CLIENT_TAG("wayland.cliprdr")
#define mime_text_plain "text/plain"
#define mime_text_utf8 mime_text_plain ";charset=utf-8"
static const char* mime_text[] = { mime_text_plain, mime_text_utf8, "UTF8_STRING",
"COMPOUND_TEXT", "TEXT", "STRING" };
static const char mime_png[] = "image/png";
static const char mime_webp[] = "image/webp";
static const char mime_jpg[] = "image/jpeg";
static const char mime_tiff[] = "image/tiff";
static const char mime_uri_list[] = "text/uri-list";
static const char mime_html[] = "text/html";
#define BMP_MIME_LIST "image/bmp", "image/x-bmp", "image/x-MS-bmp", "image/x-win-bitmap"
static const char* mime_bitmap[] = { BMP_MIME_LIST };
static const char* mime_image[] = { mime_png, mime_webp, mime_jpg, mime_tiff, BMP_MIME_LIST };
static const char mime_gnome_copied_files[] = "x-special/gnome-copied-files";
static const char mime_mate_copied_files[] = "x-special/mate-copied-files";
static const char type_FileGroupDescriptorW[] = "FileGroupDescriptorW";
static const char type_HtmlFormat[] = "HTML Format";
typedef struct
{
FILE* responseFile;
UINT32 responseFormat;
char* responseMime;
} wlf_request;
typedef struct
{
const FILE* responseFile;
UINT32 responseFormat;
const char* responseMime;
} wlf_const_request;
struct wlf_clipboard
{
wlfContext* wfc;
rdpChannels* channels;
CliprdrClientContext* context;
wLog* log;
UwacSeat* seat;
wClipboard* system;
size_t numClientFormats;
CLIPRDR_FORMAT* clientFormats;
size_t numServerFormats;
CLIPRDR_FORMAT* serverFormats;
BOOL sync;
CRITICAL_SECTION lock;
CliprdrFileContext* file;
wQueue* request_queue;
};
static void wlf_request_free(void* rq)
{
wlf_request* request = rq;
if (request)
{
free(request->responseMime);
if (request->responseFile)
(void)fclose(request->responseFile);
}
free(request);
}
static wlf_request* wlf_request_new(void)
{
return calloc(1, sizeof(wlf_request));
}
static void* wlf_request_clone(const void* oth)
{
const wlf_request* other = (const wlf_request*)oth;
wlf_request* copy = wlf_request_new();
if (!copy)
return NULL;
*copy = *other;
if (other->responseMime)
{
copy->responseMime = _strdup(other->responseMime);
if (!copy->responseMime)
goto fail;
}
return copy;
fail:
wlf_request_free(copy);
return NULL;
}
static BOOL wlf_mime_is_file(const char* mime)
{
if (strncmp(mime_uri_list, mime, sizeof(mime_uri_list)) == 0)
return TRUE;
if (strncmp(mime_gnome_copied_files, mime, sizeof(mime_gnome_copied_files)) == 0)
return TRUE;
if (strncmp(mime_mate_copied_files, mime, sizeof(mime_mate_copied_files)) == 0)
return TRUE;
return FALSE;
}
static BOOL wlf_mime_is_text(const char* mime)
{
for (size_t x = 0; x < ARRAYSIZE(mime_text); x++)
{
if (strcmp(mime, mime_text[x]) == 0)
return TRUE;
}
return FALSE;
}
static BOOL wlf_mime_is_image(const char* mime)
{
for (size_t x = 0; x < ARRAYSIZE(mime_image); x++)
{
if (strcmp(mime, mime_image[x]) == 0)
return TRUE;
}
return FALSE;
}
static BOOL wlf_mime_is_html(const char* mime)
{
if (strcmp(mime, mime_html) == 0)
return TRUE;
return FALSE;
}
static void wlf_cliprdr_free_server_formats(wfClipboard* clipboard)
{
if (clipboard && clipboard->serverFormats)
{
for (size_t j = 0; j < clipboard->numServerFormats; j++)
{
CLIPRDR_FORMAT* format = &clipboard->serverFormats[j];
free(format->formatName);
}
free(clipboard->serverFormats);
clipboard->serverFormats = NULL;
clipboard->numServerFormats = 0;
}
if (clipboard)
UwacClipboardOfferDestroy(clipboard->seat);
}
static void wlf_cliprdr_free_client_formats(wfClipboard* clipboard)
{
if (clipboard && clipboard->numClientFormats)
{
for (size_t j = 0; j < clipboard->numClientFormats; j++)
{
CLIPRDR_FORMAT* format = &clipboard->clientFormats[j];
free(format->formatName);
}
free(clipboard->clientFormats);
clipboard->clientFormats = NULL;
clipboard->numClientFormats = 0;
}
if (clipboard)
UwacClipboardOfferDestroy(clipboard->seat);
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT wlf_cliprdr_send_client_format_list(wfClipboard* clipboard)
{
WINPR_ASSERT(clipboard);
const CLIPRDR_FORMAT_LIST formatList = { .common.msgFlags = 0,
.numFormats = (UINT32)clipboard->numClientFormats,
.formats = clipboard->clientFormats,
.common.msgType = CB_FORMAT_LIST };
cliprdr_file_context_clear(clipboard->file);
WLog_VRB(TAG, "-------------- client format list [%" PRIu32 "] ------------------",
formatList.numFormats);
for (UINT32 x = 0; x < formatList.numFormats; x++)
{
const CLIPRDR_FORMAT* format = &formatList.formats[x];
WLog_VRB(TAG, "client announces %" PRIu32 " [%s][%s]", format->formatId,
ClipboardGetFormatIdString(format->formatId), format->formatName);
}
WINPR_ASSERT(clipboard->context);
WINPR_ASSERT(clipboard->context->ClientFormatList);
return clipboard->context->ClientFormatList(clipboard->context, &formatList);
}
static void wfl_cliprdr_add_client_format_id(wfClipboard* clipboard, UINT32 formatId)
{
CLIPRDR_FORMAT* format = NULL;
const char* name = ClipboardGetFormatName(clipboard->system, formatId);
for (size_t x = 0; x < clipboard->numClientFormats; x++)
{
format = &clipboard->clientFormats[x];
if (format->formatId == formatId)
return;
}
format = realloc(clipboard->clientFormats,
(clipboard->numClientFormats + 1) * sizeof(CLIPRDR_FORMAT));
if (!format)
return;
clipboard->clientFormats = format;
format = &clipboard->clientFormats[clipboard->numClientFormats++];
format->formatId = formatId;
format->formatName = NULL;
if (name && (formatId >= CF_MAX))
format->formatName = _strdup(name);
}
static BOOL wlf_cliprdr_add_client_format(wfClipboard* clipboard, const char* mime)
{
WINPR_ASSERT(mime);
ClipboardLock(clipboard->system);
if (wlf_mime_is_html(mime))
{
UINT32 formatId = ClipboardGetFormatId(clipboard->system, type_HtmlFormat);
wfl_cliprdr_add_client_format_id(clipboard, formatId);
}
else if (wlf_mime_is_text(mime))
{
wfl_cliprdr_add_client_format_id(clipboard, CF_TEXT);
wfl_cliprdr_add_client_format_id(clipboard, CF_OEMTEXT);
wfl_cliprdr_add_client_format_id(clipboard, CF_UNICODETEXT);
}
else if (wlf_mime_is_image(mime))
{
for (size_t x = 0; x < ARRAYSIZE(mime_image); x++)
{
const char* mime_bmp = mime_image[x];
UINT32 formatId = ClipboardGetFormatId(clipboard->system, mime_bmp);
if (formatId != 0)
wfl_cliprdr_add_client_format_id(clipboard, formatId);
}
wfl_cliprdr_add_client_format_id(clipboard, CF_DIB);
wfl_cliprdr_add_client_format_id(clipboard, CF_TIFF);
}
else if (wlf_mime_is_file(mime))
{
const UINT32 fileFormatId =
ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW);
wfl_cliprdr_add_client_format_id(clipboard, fileFormatId);
}
ClipboardUnlock(clipboard->system);
if (wlf_cliprdr_send_client_format_list(clipboard) != CHANNEL_RC_OK)
return FALSE;
return TRUE;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT wlf_cliprdr_send_data_request(wfClipboard* clipboard, const wlf_const_request* rq)
{
WINPR_ASSERT(rq);
CLIPRDR_FORMAT_DATA_REQUEST request = { .requestedFormatId = rq->responseFormat };
if (!Queue_Enqueue(clipboard->request_queue, rq))
return ERROR_INTERNAL_ERROR;
WINPR_ASSERT(clipboard);
WINPR_ASSERT(clipboard->context);
WINPR_ASSERT(clipboard->context->ClientFormatDataRequest);
return clipboard->context->ClientFormatDataRequest(clipboard->context, &request);
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT wlf_cliprdr_send_data_response(wfClipboard* clipboard, const BYTE* data, size_t size)
{
CLIPRDR_FORMAT_DATA_RESPONSE response = { 0 };
if (size > UINT32_MAX)
return ERROR_INVALID_PARAMETER;
response.common.msgFlags = (data) ? CB_RESPONSE_OK : CB_RESPONSE_FAIL;
response.common.dataLen = (UINT32)size;
response.requestedFormatData = data;
WINPR_ASSERT(clipboard);
WINPR_ASSERT(clipboard->context);
WINPR_ASSERT(clipboard->context->ClientFormatDataResponse);
return clipboard->context->ClientFormatDataResponse(clipboard->context, &response);
}
BOOL wlf_cliprdr_handle_event(wfClipboard* clipboard, const UwacClipboardEvent* event)
{
if (!clipboard || !event)
return FALSE;
if (!clipboard->context)
return TRUE;
switch (event->type)
{
case UWAC_EVENT_CLIPBOARD_AVAILABLE:
clipboard->seat = event->seat;
return TRUE;
case UWAC_EVENT_CLIPBOARD_OFFER:
WLog_Print(clipboard->log, WLOG_DEBUG, "client announces mime %s", event->mime);
return wlf_cliprdr_add_client_format(clipboard, event->mime);
case UWAC_EVENT_CLIPBOARD_SELECT:
WLog_Print(clipboard->log, WLOG_DEBUG, "client announces new data");
wlf_cliprdr_free_client_formats(clipboard);
return TRUE;
default:
return FALSE;
}
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT wlf_cliprdr_send_client_capabilities(wfClipboard* clipboard)
{
WINPR_ASSERT(clipboard);
CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet = {
.capabilitySetType = CB_CAPSTYPE_GENERAL,
.capabilitySetLength = 12,
.version = CB_CAPS_VERSION_2,
.generalFlags =
CB_USE_LONG_FORMAT_NAMES | cliprdr_file_context_current_flags(clipboard->file)
};
CLIPRDR_CAPABILITIES capabilities = { .cCapabilitiesSets = 1,
.capabilitySets =
(CLIPRDR_CAPABILITY_SET*)&(generalCapabilitySet) };
WINPR_ASSERT(clipboard);
WINPR_ASSERT(clipboard->context);
WINPR_ASSERT(clipboard->context->ClientCapabilities);
return clipboard->context->ClientCapabilities(clipboard->context, &capabilities);
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT wlf_cliprdr_send_client_format_list_response(wfClipboard* clipboard, BOOL status)
{
const CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse = {
.common.msgType = CB_FORMAT_LIST_RESPONSE,
.common.msgFlags = status ? CB_RESPONSE_OK : CB_RESPONSE_FAIL,
.common.dataLen = 0
};
WINPR_ASSERT(clipboard);
WINPR_ASSERT(clipboard->context);
WINPR_ASSERT(clipboard->context->ClientFormatListResponse);
return clipboard->context->ClientFormatListResponse(clipboard->context, &formatListResponse);
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT wlf_cliprdr_monitor_ready(CliprdrClientContext* context,
const CLIPRDR_MONITOR_READY* monitorReady)
{
UINT ret = 0;
WINPR_UNUSED(monitorReady);
WINPR_ASSERT(context);
WINPR_ASSERT(monitorReady);
wfClipboard* clipboard = cliprdr_file_context_get_context(context->custom);
WINPR_ASSERT(clipboard);
if ((ret = wlf_cliprdr_send_client_capabilities(clipboard)) != CHANNEL_RC_OK)
return ret;
if ((ret = wlf_cliprdr_send_client_format_list(clipboard)) != CHANNEL_RC_OK)
return ret;
clipboard->sync = TRUE;
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT wlf_cliprdr_server_capabilities(CliprdrClientContext* context,
const CLIPRDR_CAPABILITIES* capabilities)
{
WINPR_ASSERT(context);
WINPR_ASSERT(capabilities);
const BYTE* capsPtr = (const BYTE*)capabilities->capabilitySets;
WINPR_ASSERT(capsPtr);
wfClipboard* clipboard = cliprdr_file_context_get_context(context->custom);
WINPR_ASSERT(clipboard);
if (!cliprdr_file_context_remote_set_flags(clipboard->file, 0))
return ERROR_INTERNAL_ERROR;
for (UINT32 i = 0; i < capabilities->cCapabilitiesSets; i++)
{
const CLIPRDR_CAPABILITY_SET* caps = (const CLIPRDR_CAPABILITY_SET*)capsPtr;
if (caps->capabilitySetType == CB_CAPSTYPE_GENERAL)
{
const CLIPRDR_GENERAL_CAPABILITY_SET* generalCaps =
(const CLIPRDR_GENERAL_CAPABILITY_SET*)caps;
if (!cliprdr_file_context_remote_set_flags(clipboard->file, generalCaps->generalFlags))
return ERROR_INTERNAL_ERROR;
}
capsPtr += caps->capabilitySetLength;
}
return CHANNEL_RC_OK;
}
static UINT32 wlf_get_server_format_id(const wfClipboard* clipboard, const char* name)
{
WINPR_ASSERT(clipboard);
WINPR_ASSERT(name);
for (UINT32 x = 0; x < clipboard->numServerFormats; x++)
{
const CLIPRDR_FORMAT* format = &clipboard->serverFormats[x];
if (!format->formatName)
continue;
if (strcmp(name, format->formatName) == 0)
return format->formatId;
}
return 0;
}
static const char* wlf_get_server_format_name(const wfClipboard* clipboard, UINT32 formatId)
{
WINPR_ASSERT(clipboard);
for (UINT32 x = 0; x < clipboard->numServerFormats; x++)
{
const CLIPRDR_FORMAT* format = &clipboard->serverFormats[x];
if (format->formatId == formatId)
return format->formatName;
}
return NULL;
}
static void wlf_cliprdr_transfer_data(UwacSeat* seat, void* context, const char* mime, int fd)
{
wfClipboard* clipboard = (wfClipboard*)context;
WINPR_UNUSED(seat);
EnterCriticalSection(&clipboard->lock);
wlf_const_request request = { 0 };
if (wlf_mime_is_html(mime))
{
request.responseMime = mime_html;
request.responseFormat = wlf_get_server_format_id(clipboard, type_HtmlFormat);
}
else if (wlf_mime_is_file(mime))
{
request.responseMime = mime;
request.responseFormat = wlf_get_server_format_id(clipboard, type_FileGroupDescriptorW);
}
else if (wlf_mime_is_text(mime))
{
request.responseMime = mime_text_plain;
request.responseFormat = CF_UNICODETEXT;
}
else if (wlf_mime_is_image(mime))
{
request.responseMime = mime;
if (strcmp(mime, mime_tiff) == 0)
request.responseFormat = CF_TIFF;
else
request.responseFormat = CF_DIB;
}
if (request.responseMime != NULL)
{
request.responseFile = fdopen(fd, "w");
if (request.responseFile)
wlf_cliprdr_send_data_request(clipboard, &request);
else
WLog_Print(clipboard->log, WLOG_ERROR,
"failed to open clipboard file descriptor for MIME %s",
request.responseMime);
}
LeaveCriticalSection(&clipboard->lock);
}
static void wlf_cliprdr_cancel_data(UwacSeat* seat, void* context)
{
wfClipboard* clipboard = (wfClipboard*)context;
WINPR_UNUSED(seat);
WINPR_ASSERT(clipboard);
cliprdr_file_context_clear(clipboard->file);
}
/**
* Called when the clipboard changes server side.
*
* Clear the local clipboard offer and replace it with a new one
* that announces the formats we get listed here.
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT wlf_cliprdr_server_format_list(CliprdrClientContext* context,
const CLIPRDR_FORMAT_LIST* formatList)
{
BOOL html = FALSE;
BOOL text = FALSE;
BOOL image = FALSE;
BOOL file = FALSE;
if (!context || !context->custom)
return ERROR_INVALID_PARAMETER;
wfClipboard* clipboard = cliprdr_file_context_get_context(context->custom);
WINPR_ASSERT(clipboard);
wlf_cliprdr_free_server_formats(clipboard);
cliprdr_file_context_clear(clipboard->file);
if (!(clipboard->serverFormats =
(CLIPRDR_FORMAT*)calloc(formatList->numFormats, sizeof(CLIPRDR_FORMAT))))
{
WLog_Print(clipboard->log, WLOG_ERROR,
"failed to allocate %" PRIuz " CLIPRDR_FORMAT structs",
clipboard->numServerFormats);
return CHANNEL_RC_NO_MEMORY;
}
clipboard->numServerFormats = formatList->numFormats;
if (!clipboard->seat)
{
WLog_Print(clipboard->log, WLOG_ERROR,
"clipboard->seat=NULL, check your client implementation");
return ERROR_INTERNAL_ERROR;
}
for (UINT32 i = 0; i < formatList->numFormats; i++)
{
const CLIPRDR_FORMAT* format = &formatList->formats[i];
CLIPRDR_FORMAT* srvFormat = &clipboard->serverFormats[i];
srvFormat->formatId = format->formatId;
if (format->formatName)
{
srvFormat->formatName = _strdup(format->formatName);
if (!srvFormat->formatName)
{
wlf_cliprdr_free_server_formats(clipboard);
return CHANNEL_RC_NO_MEMORY;
}
}
if (format->formatName)
{
if (strcmp(format->formatName, type_HtmlFormat) == 0)
{
text = TRUE;
html = TRUE;
}
else if (strcmp(format->formatName, type_FileGroupDescriptorW) == 0)
{
file = TRUE;
text = TRUE;
}
}
else
{
switch (format->formatId)
{
case CF_TEXT:
case CF_OEMTEXT:
case CF_UNICODETEXT:
text = TRUE;
break;
case CF_DIB:
image = TRUE;
break;
default:
break;
}
}
}
if (html)
{
UwacClipboardOfferCreate(clipboard->seat, mime_html);
}
if (file && cliprdr_file_context_has_local_support(clipboard->file))
{
UwacClipboardOfferCreate(clipboard->seat, mime_uri_list);
UwacClipboardOfferCreate(clipboard->seat, mime_gnome_copied_files);
UwacClipboardOfferCreate(clipboard->seat, mime_mate_copied_files);
}
if (text)
{
for (size_t x = 0; x < ARRAYSIZE(mime_text); x++)
UwacClipboardOfferCreate(clipboard->seat, mime_text[x]);
}
if (image)
{
for (size_t x = 0; x < ARRAYSIZE(mime_image); x++)
UwacClipboardOfferCreate(clipboard->seat, mime_image[x]);
}
UwacClipboardOfferAnnounce(clipboard->seat, clipboard, wlf_cliprdr_transfer_data,
wlf_cliprdr_cancel_data);
return wlf_cliprdr_send_client_format_list_response(clipboard, TRUE);
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT
wlf_cliprdr_server_format_list_response(CliprdrClientContext* context,
const CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse)
{
WINPR_ASSERT(context);
WINPR_ASSERT(formatListResponse);
if (formatListResponse->common.msgFlags & CB_RESPONSE_FAIL)
WLog_WARN(TAG, "format list update failed");
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT
wlf_cliprdr_server_format_data_request(CliprdrClientContext* context,
const CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest)
{
UINT rc = CHANNEL_RC_OK;
char* data = NULL;
size_t size = 0;
const char* mime = NULL;
UINT32 formatId = 0;
UINT32 localFormatId = 0;
wfClipboard* clipboard = 0;
UINT32 dsize = 0;
BYTE* ddata = NULL;
WINPR_ASSERT(context);
WINPR_ASSERT(formatDataRequest);
localFormatId = formatId = formatDataRequest->requestedFormatId;
clipboard = cliprdr_file_context_get_context(context->custom);
WINPR_ASSERT(clipboard);
ClipboardLock(clipboard->system);
const UINT32 fileFormatId = ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW);
const UINT32 htmlFormatId = ClipboardGetFormatId(clipboard->system, type_HtmlFormat);
switch (formatId)
{
case CF_TEXT:
case CF_OEMTEXT:
case CF_UNICODETEXT:
localFormatId = ClipboardGetFormatId(clipboard->system, mime_text_plain);
mime = mime_text_utf8;
break;
case CF_DIB:
case CF_DIBV5:
mime = mime_bitmap[0];
break;
case CF_TIFF:
mime = mime_tiff;
break;
default:
if (formatId == fileFormatId)
{
localFormatId = ClipboardGetFormatId(clipboard->system, mime_uri_list);
mime = mime_uri_list;
}
else if (formatId == htmlFormatId)
{
localFormatId = ClipboardGetFormatId(clipboard->system, mime_html);
mime = mime_html;
}
else
goto fail;
break;
}
data = UwacClipboardDataGet(clipboard->seat, mime, &size);
if (!data)
goto fail;
if (fileFormatId == formatId)
{
if (!cliprdr_file_context_update_client_data(clipboard->file, data, size))
goto fail;
}
const BOOL res = ClipboardSetData(clipboard->system, localFormatId, data, size);
free(data);
UINT32 len = 0;
data = NULL;
if (res)
data = ClipboardGetData(clipboard->system, formatId, &len);
if (!res || !data)
goto fail;
if (fileFormatId == formatId)
{
const UINT32 flags = cliprdr_file_context_remote_get_flags(clipboard->file);
const UINT32 error = cliprdr_serialize_file_list_ex(
flags, (const FILEDESCRIPTORW*)data, len / sizeof(FILEDESCRIPTORW), &ddata, &dsize);
if (error)
goto fail;
}
fail:
ClipboardUnlock(clipboard->system);
rc = wlf_cliprdr_send_data_response(clipboard, ddata, dsize);
free(data);
return rc;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT
wlf_cliprdr_server_format_data_response(CliprdrClientContext* context,
const CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse)
{
UINT rc = ERROR_INTERNAL_ERROR;
WINPR_ASSERT(context);
WINPR_ASSERT(formatDataResponse);
const UINT32 size = formatDataResponse->common.dataLen;
const BYTE* data = formatDataResponse->requestedFormatData;
wfClipboard* clipboard = cliprdr_file_context_get_context(context->custom);
WINPR_ASSERT(clipboard);
wlf_request* request = Queue_Dequeue(clipboard->request_queue);
if (!request)
goto fail;
rc = CHANNEL_RC_OK;
if (formatDataResponse->common.msgFlags & CB_RESPONSE_FAIL)
{
WLog_WARN(TAG, "clipboard data request for format %" PRIu32 " [%s], mime %s failed",
request->responseFormat, ClipboardGetFormatIdString(request->responseFormat),
request->responseMime);
goto fail;
}
rc = ERROR_INTERNAL_ERROR;
ClipboardLock(clipboard->system);
EnterCriticalSection(&clipboard->lock);
BYTE* cdata = NULL;
UINT32 srcFormatId = 0;
UINT32 dstFormatId = 0;
switch (request->responseFormat)
{
case CF_TEXT:
case CF_OEMTEXT:
case CF_UNICODETEXT:
srcFormatId = request->responseFormat;
dstFormatId = ClipboardGetFormatId(clipboard->system, request->responseMime);
break;
case CF_DIB:
case CF_DIBV5:
srcFormatId = request->responseFormat;
dstFormatId = ClipboardGetFormatId(clipboard->system, request->responseMime);
break;
default:
{
const char* name = wlf_get_server_format_name(clipboard, request->responseFormat);
if (name)
{
if (strcmp(type_FileGroupDescriptorW, name) == 0)
{
srcFormatId =
ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW);
dstFormatId = ClipboardGetFormatId(clipboard->system, request->responseMime);
if (!cliprdr_file_context_update_server_data(clipboard->file, clipboard->system,
data, size))
goto unlock;
}
else if (strcmp(type_HtmlFormat, name) == 0)
{
srcFormatId = ClipboardGetFormatId(clipboard->system, type_HtmlFormat);
dstFormatId = ClipboardGetFormatId(clipboard->system, request->responseMime);
}
}
}
break;
}
UINT32 len = 0;
const BOOL sres = ClipboardSetData(clipboard->system, srcFormatId, data, size);
if (sres)
cdata = ClipboardGetData(clipboard->system, dstFormatId, &len);
if (!sres || !cdata)
goto unlock;
if (request->responseFile)
{
const size_t res = fwrite(cdata, 1, len, request->responseFile);
if (res == len)
rc = CHANNEL_RC_OK;
}
else
rc = CHANNEL_RC_OK;
unlock:
free(cdata);
ClipboardUnlock(clipboard->system);
LeaveCriticalSection(&clipboard->lock);
fail:
wlf_request_free(request);
return rc;
}
wfClipboard* wlf_clipboard_new(wlfContext* wfc)
{
rdpChannels* channels = NULL;
wfClipboard* clipboard = NULL;
WINPR_ASSERT(wfc);
clipboard = (wfClipboard*)calloc(1, sizeof(wfClipboard));
if (!clipboard)
goto fail;
InitializeCriticalSection(&clipboard->lock);
clipboard->wfc = wfc;
channels = wfc->common.context.channels;
clipboard->log = WLog_Get(TAG);
clipboard->channels = channels;
clipboard->system = ClipboardCreate();
if (!clipboard->system)
goto fail;
clipboard->file = cliprdr_file_context_new(clipboard);
if (!clipboard->file)
goto fail;
if (!cliprdr_file_context_set_locally_available(clipboard->file, TRUE))
goto fail;
clipboard->request_queue = Queue_New(TRUE, -1, -1);
if (!clipboard->request_queue)
goto fail;
wObject* obj = Queue_Object(clipboard->request_queue);
WINPR_ASSERT(obj);
obj->fnObjectFree = wlf_request_free;
obj->fnObjectNew = wlf_request_clone;
return clipboard;
fail:
wlf_clipboard_free(clipboard);
return NULL;
}
void wlf_clipboard_free(wfClipboard* clipboard)
{
if (!clipboard)
return;
cliprdr_file_context_free(clipboard->file);
wlf_cliprdr_free_server_formats(clipboard);
wlf_cliprdr_free_client_formats(clipboard);
ClipboardDestroy(clipboard->system);
EnterCriticalSection(&clipboard->lock);
Queue_Free(clipboard->request_queue);
LeaveCriticalSection(&clipboard->lock);
DeleteCriticalSection(&clipboard->lock);
free(clipboard);
}
BOOL wlf_cliprdr_init(wfClipboard* clipboard, CliprdrClientContext* cliprdr)
{
WINPR_ASSERT(clipboard);
WINPR_ASSERT(cliprdr);
clipboard->context = cliprdr;
cliprdr->MonitorReady = wlf_cliprdr_monitor_ready;
cliprdr->ServerCapabilities = wlf_cliprdr_server_capabilities;
cliprdr->ServerFormatList = wlf_cliprdr_server_format_list;
cliprdr->ServerFormatListResponse = wlf_cliprdr_server_format_list_response;
cliprdr->ServerFormatDataRequest = wlf_cliprdr_server_format_data_request;
cliprdr->ServerFormatDataResponse = wlf_cliprdr_server_format_data_response;
return cliprdr_file_context_init(clipboard->file, cliprdr);
}
BOOL wlf_cliprdr_uninit(wfClipboard* clipboard, CliprdrClientContext* cliprdr)
{
WINPR_ASSERT(clipboard);
if (!cliprdr_file_context_uninit(clipboard->file, cliprdr))
return FALSE;
if (cliprdr)
cliprdr->custom = NULL;
return TRUE;
}