From ca2e8e4bc2fc16bf48fa9b669c055cd1f6cd664d Mon Sep 17 00:00:00 2001 From: Armin Novak Date: Wed, 19 Dec 2018 16:18:13 +0100 Subject: [PATCH] Implemented wayland clipboard. --- client/Wayland/CMakeLists.txt | 2 + client/Wayland/wlf_channels.c | 3 + client/Wayland/wlf_cliprdr.c | 866 ++++++++++++++++++++++++++++++++++ client/Wayland/wlf_cliprdr.h | 36 ++ client/Wayland/wlfreerdp.c | 15 + client/Wayland/wlfreerdp.h | 2 + 6 files changed, 924 insertions(+) create mode 100644 client/Wayland/wlf_cliprdr.c create mode 100644 client/Wayland/wlf_cliprdr.h diff --git a/client/Wayland/CMakeLists.txt b/client/Wayland/CMakeLists.txt index 8324f69e2..d98d2d4ab 100644 --- a/client/Wayland/CMakeLists.txt +++ b/client/Wayland/CMakeLists.txt @@ -29,6 +29,8 @@ set(${MODULE_PREFIX}_SRCS wlf_disp.h wlf_input.c wlf_input.h + wlf_cliprdr.c + wlf_cliprdr.h wlf_channels.c wlf_channels.h ) diff --git a/client/Wayland/wlf_channels.c b/client/Wayland/wlf_channels.c index 8f491f7a3..7783eb7c4 100644 --- a/client/Wayland/wlf_channels.c +++ b/client/Wayland/wlf_channels.c @@ -24,6 +24,7 @@ #include #include "wlf_channels.h" +#include "wlf_cliprdr.h" #include "wlf_disp.h" #include "wlfreerdp.h" @@ -81,6 +82,7 @@ void wlf_OnChannelConnectedEventHandler(void* context, } else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0) { + wlf_cliprdr_init(wlf->clipboard, (CliprdrClientContext*)e->pInterface); } else if (strcmp(e->name, ENCOMSP_SVC_CHANNEL_NAME) == 0) { @@ -116,6 +118,7 @@ void wlf_OnChannelDisconnectedEventHandler(void* context, } else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0) { + wlf_cliprdr_uninit(wlf->clipboard, (CliprdrClientContext*)e->pInterface); } else if (strcmp(e->name, ENCOMSP_SVC_CHANNEL_NAME) == 0) { diff --git a/client/Wayland/wlf_cliprdr.c b/client/Wayland/wlf_cliprdr.c new file mode 100644 index 000000000..acce16efd --- /dev/null +++ b/client/Wayland/wlf_cliprdr.c @@ -0,0 +1,866 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Clipboard Redirection + * + * Copyright 2018 Armin Novak + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "wlf_cliprdr.h" + +#define MAX_CLIPBOARD_FORMATS 255 + +static const char* mime_text[] = +{ + "text/plain", + "text/plain;charset=utf-8", + "UTF8_STRING", + "COMPOUND_TEXT", + "TEXT", + "STRING" +}; + +static const char* mime_image[] = +{ + "image/png", + "image/bmp", + "image/x-bmp", + "image/x-MS-bmp", + "image/x-icon", + "image/x-ico", + "image/x-win-bitmap", + "image/vmd.microsoft.icon", + "application/ico", + "image/ico", + "image/icon", + "image/jpeg", + "image/tiff" +}; + +static const char* mime_html[] = +{ + "text/html" +}; + +struct wlf_clipboard +{ + wlfContext* wfc; + rdpChannels* channels; + CliprdrClientContext* context; + wLog* log; + + UwacSeat* seat; + wClipboard* system; + wClipboardDelegate* delegate; + + size_t numClientFormats; + CLIPRDR_FORMAT* clientFormats; + + size_t numServerFormats; + CLIPRDR_FORMAT* serverFormats; + + BOOL sync; + + /* File clipping */ + BOOL streams_supported; + BOOL file_formats_registered; + + /* Server response stuff */ + FILE* responseFile; + UINT32 responseFormat; + const char* responseMime; +}; + +static BOOL wlf_mime_is_text(const char* mime) +{ + size_t x; + for (x=0; xserverFormats) + { + size_t j; + + for (j = 0; j < clipboard->numServerFormats; j++) + { + CLIPRDR_FORMAT* format = &clipboard->serverFormats[j]; + free(format->formatName); + } + + free(clipboard->serverFormats); + clipboard->serverFormats = NULL; + clipboard->numServerFormats = 0; + } + + UwacClipboardOfferDestroy(clipboard->seat); +} + +static void wlf_cliprdr_free_client_formats(wfClipboard* clipboard) +{ + if (clipboard && clipboard->numClientFormats) + { + size_t j; + + for (j = 0; j < clipboard->numClientFormats; j++) + { + CLIPRDR_FORMAT* format = &clipboard->clientFormats[j]; + free(format->formatName); + } + + free(clipboard->clientFormats); + clipboard->clientFormats = NULL; + clipboard->numClientFormats = 0; + } + + UwacClipboardOfferDestroy(clipboard->seat); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wlf_cliprdr_send_client_format_list(wfClipboard* clipboard) +{ + CLIPRDR_FORMAT_LIST formatList = { 0 }; + + formatList.msgFlags = CB_RESPONSE_OK; + formatList.numFormats = (UINT32)clipboard->numClientFormats; + formatList.formats = clipboard->clientFormats; + return clipboard->context->ClientFormatList(clipboard->context, &formatList); +} + +static void wfl_cliprdr_add_client_format_id(wfClipboard* clipboard, UINT32 formatId) +{ + size_t x; + CLIPRDR_FORMAT* format; + const char* name = ClipboardGetFormatName(clipboard->system, formatId); + + for (x=0; xnumClientFormats; 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 void wlf_cliprdr_add_client_format(wfClipboard* clipboard, const char* mime) +{ + if (wlf_mime_is_html(mime)) + { + UINT32 formatId = ClipboardGetFormatId(clipboard->system, "HTML Format"); + 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)) + { + UINT32 formatId = ClipboardGetFormatId(clipboard->system, "image/bmp"); + wfl_cliprdr_add_client_format_id(clipboard, formatId); + wfl_cliprdr_add_client_format_id(clipboard, CF_DIB); + } + wlf_cliprdr_send_client_format_list(clipboard); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wlf_cliprdr_send_data_request(wfClipboard* clipboard, + UINT32 formatId) +{ + CLIPRDR_FORMAT_DATA_REQUEST request; + ZeroMemory(&request, sizeof(CLIPRDR_FORMAT_DATA_REQUEST)); + request.requestedFormatId = formatId; + 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; + ZeroMemory(&response, sizeof(CLIPRDR_FORMAT_DATA_RESPONSE)); + response.msgFlags = (data) ? CB_RESPONSE_OK : CB_RESPONSE_FAIL; + response.dataLen = size; + response.requestedFormatData = data; + return clipboard->context->ClientFormatDataResponse(clipboard->context, + &response); +} + +BOOL wlf_cliprdr_handle_event(wfClipboard* clipboard, const UwacClipboardEvent* event) +{ + if (!clipboard || !event) + return FALSE; + + if (!clipboard) + return FALSE; + + switch (event->type) + { + case UWAC_EVENT_CLIPBOARD_AVAILABLE: + clipboard->seat = event->seat; + return TRUE; + + case UWAC_EVENT_CLIPBOARD_OFFER: + WLog_Print(clipboard->log, WLOG_INFO, "client announces mime %s", event->mime); + wlf_cliprdr_add_client_format(clipboard, event->mime); + return TRUE; + + 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) +{ + CLIPRDR_CAPABILITIES capabilities; + CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet; + capabilities.cCapabilitiesSets = 1; + capabilities.capabilitySets = (CLIPRDR_CAPABILITY_SET*) & + (generalCapabilitySet); + generalCapabilitySet.capabilitySetType = CB_CAPSTYPE_GENERAL; + generalCapabilitySet.capabilitySetLength = 12; + generalCapabilitySet.version = CB_CAPS_VERSION_2; + generalCapabilitySet.generalFlags = CB_USE_LONG_FORMAT_NAMES; + + if (clipboard->streams_supported && clipboard->file_formats_registered) + generalCapabilitySet.generalFlags |= + CB_STREAM_FILECLIP_ENABLED | CB_FILECLIP_NO_FILE_PATHS; + + 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) +{ + CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse; + formatListResponse.msgType = CB_FORMAT_LIST_RESPONSE; + formatListResponse.msgFlags = status ? CB_RESPONSE_OK : CB_RESPONSE_FAIL; + formatListResponse.dataLen = 0; + 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) +{ + wfClipboard* clipboard = (wfClipboard*) context->custom; + UINT ret; + + 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) +{ + UINT32 i; + const BYTE* capsPtr = (const BYTE*) capabilities->capabilitySets; + wfClipboard* clipboard = (wfClipboard*) context->custom; + clipboard->streams_supported = FALSE; + + for (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 (generalCaps->generalFlags & CB_STREAM_FILECLIP_ENABLED) + { + clipboard->streams_supported = TRUE; + } + } + + capsPtr += caps->capabilitySetLength; + } + + return CHANNEL_RC_OK; +} + +static void wlf_cliprdr_transfer_data(UwacSeat* seat, void* context, const char* mime, int fd) +{ + wfClipboard* clipboard = (wfClipboard*)context; + size_t x; + WINPR_UNUSED(seat); + clipboard->responseMime = NULL; + + for (x = 0; x < ARRAYSIZE(mime_html); x++) + { + const char* mime_cur = mime_html[x]; + + if (strcmp(mime_cur, mime) == 0) + { + clipboard->responseMime = mime_cur; + clipboard->responseFormat = ClipboardGetFormatId(clipboard->system, "HTML Format"); + break; + } + } + + for (x = 0; x < ARRAYSIZE(mime_text); x++) + { + const char* mime_cur = mime_text[x]; + + if (strcmp(mime_cur, mime) == 0) + { + clipboard->responseMime = mime_cur; + clipboard->responseFormat = CF_UNICODETEXT; + break; + } + } + + for (x = 0; x < ARRAYSIZE(mime_image); x++) + { + const char* mime_cur = mime_image[x]; + + if (strcmp(mime_cur, mime) == 0) + { + clipboard->responseMime = mime_cur; + clipboard->responseFormat = CF_DIB; + break; + } + } + + if (clipboard->responseMime != NULL) + { + clipboard->responseFile = fdopen(fd, "w"); + + if (clipboard->responseFile) + wlf_cliprdr_send_data_request(clipboard, clipboard->responseFormat); + } +} + +static void wlf_cliprdr_cancel_data(UwacSeat* seat, void* context) +{ + WINPR_UNUSED(seat); + WINPR_UNUSED(context); +} + +/** + * 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) +{ + UINT32 i; + wfClipboard* clipboard; + BOOL html = FALSE; + BOOL text = FALSE; + BOOL image = FALSE; + + if (!context || !context->custom) + return ERROR_INVALID_PARAMETER; + + clipboard = (wfClipboard*) context->custom; + wlf_cliprdr_free_server_formats(clipboard); + + 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) + return ERROR_INTERNAL_ERROR; + + for (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, "HTML Format") == 0) + { + text = TRUE; + html = 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) + { + size_t x; + + for (x = 0; x < ARRAYSIZE(mime_html); x++) + UwacClipboardOfferCreate(clipboard->seat, mime_html[x]); + } + + if (text) + { + size_t x; + + for (x = 0; x < ARRAYSIZE(mime_text); x++) + UwacClipboardOfferCreate(clipboard->seat, mime_text[x]); + } + + if (image) + { + size_t x; + + for (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) +{ + //wfClipboard* clipboard = (wfClipboard*) context->custom; + 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; + char* data; + LPWSTR cdata; + size_t size; + const char* mime; + UINT32 formatId = formatDataRequest->requestedFormatId; + wfClipboard* clipboard = (wfClipboard*) context->custom; + + switch(formatId) + { + case CF_TEXT: + case CF_OEMTEXT: + case CF_UNICODETEXT: + mime = "text/plain;charset=utf-8"; + break; + case CF_DIB: + case CF_DIBV5: + mime = "image/bmp"; + break; + default: + if (formatId == ClipboardGetFormatId(clipboard->system, "HTML Format")) + mime = "text/html"; + else if (formatId == ClipboardGetFormatId(clipboard->system, "image/bmp")) + mime = "image/bmp"; + else + mime = ClipboardGetFormatName(clipboard->system, formatId); + break; + } + + data = UwacClipboardDataGet(clipboard->seat, mime, &size); + if (!data) + return ERROR_INTERNAL_ERROR; + + switch (formatId) + { + case CF_UNICODETEXT: + size = ConvertToUnicode(CP_UTF8, 0, data, size, &cdata, 0); + free(data); + data = cdata; + size *= sizeof(WCHAR); + break; + + default: + // TODO: Image conversions + break; + } + + rc = wlf_cliprdr_send_data_response(clipboard, data, size); + 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; + BOOL freedata = FALSE; + UINT32 size = formatDataResponse->dataLen; + LPSTR data = formatDataResponse->requestedFormatData; + const WCHAR* wdata = (WCHAR*)formatDataResponse->requestedFormatData; + wfClipboard* clipboard = (wfClipboard*) context->custom; + + switch (clipboard->responseFormat) + { + case CF_UNICODETEXT: + size = ConvertFromUnicode(CP_UTF8, 0, wdata, size / sizeof(WCHAR), &data, 0, NULL, NULL); + freedata = TRUE; + break; + + default: + // TODO: Image conversions + break; + } + + fwrite(data, 1, size, clipboard->responseFile); + fclose(clipboard->responseFile); + rc = CHANNEL_RC_OK; +fail: + + if (freedata) + free(data); + + return rc; +} + +static UINT wlf_cliprdr_server_file_size_request(wfClipboard* clipboard, + const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) +{ + wClipboardFileSizeRequest request; + ZeroMemory(&request, sizeof(request)); + request.streamId = fileContentsRequest->streamId; + request.listIndex = fileContentsRequest->listIndex; + + if (fileContentsRequest->cbRequested != sizeof(UINT64)) + { + WLog_Print(clipboard->log, WLOG_WARN, "unexpected FILECONTENTS_SIZE request: %"PRIu32" bytes", + fileContentsRequest->cbRequested); + } + + return clipboard->delegate->ClientRequestFileSize(clipboard->delegate, &request); +} + +static UINT wlf_cliprdr_server_file_range_request(wfClipboard* clipboard, + const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) +{ + wClipboardFileRangeRequest request; + ZeroMemory(&request, sizeof(request)); + request.streamId = fileContentsRequest->streamId; + request.listIndex = fileContentsRequest->listIndex; + request.nPositionLow = fileContentsRequest->nPositionLow; + request.nPositionHigh = fileContentsRequest->nPositionHigh; + request.cbRequested = fileContentsRequest->cbRequested; + return clipboard->delegate->ClientRequestFileRange(clipboard->delegate, &request); +} + +static UINT wlf_cliprdr_send_file_contents_failure(CliprdrClientContext* context, + const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) +{ + CLIPRDR_FILE_CONTENTS_RESPONSE response; + ZeroMemory(&response, sizeof(response)); + response.msgFlags = CB_RESPONSE_FAIL; + response.streamId = fileContentsRequest->streamId; + response.dwFlags = fileContentsRequest->dwFlags; + return context->ClientFileContentsResponse(context, &response); +} + +static UINT wlf_cliprdr_server_file_contents_request(CliprdrClientContext* context, + const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) +{ + UINT error = NO_ERROR; + wfClipboard* clipboard = context->custom; + + /* + * MS-RDPECLIP 2.2.5.3 File Contents Request PDU (CLIPRDR_FILECONTENTS_REQUEST): + * The FILECONTENTS_SIZE and FILECONTENTS_RANGE flags MUST NOT be set at the same time. + */ + if ((fileContentsRequest->dwFlags & (FILECONTENTS_SIZE | FILECONTENTS_RANGE)) == + (FILECONTENTS_SIZE | FILECONTENTS_RANGE)) + { + WLog_Print(clipboard->log, WLOG_ERROR, "invalid CLIPRDR_FILECONTENTS_REQUEST.dwFlags"); + return wlf_cliprdr_send_file_contents_failure(context, fileContentsRequest); + } + + if (fileContentsRequest->dwFlags & FILECONTENTS_SIZE) + error = wlf_cliprdr_server_file_size_request(clipboard, fileContentsRequest); + + if (fileContentsRequest->dwFlags & FILECONTENTS_RANGE) + error = wlf_cliprdr_server_file_range_request(clipboard, fileContentsRequest); + + if (error) + { + WLog_Print(clipboard->log, WLOG_ERROR, "failed to handle CLIPRDR_FILECONTENTS_REQUEST: 0x%08X", + error); + return wlf_cliprdr_send_file_contents_failure(context, fileContentsRequest); + } + + return CHANNEL_RC_OK; +} + +static UINT wlf_cliprdr_clipboard_file_size_success(wClipboardDelegate* delegate, + const wClipboardFileSizeRequest* request, UINT64 fileSize) +{ + CLIPRDR_FILE_CONTENTS_RESPONSE response; + wfClipboard* clipboard = delegate->custom; + ZeroMemory(&response, sizeof(response)); + response.msgFlags = CB_RESPONSE_OK; + response.streamId = request->streamId; + response.dwFlags = FILECONTENTS_SIZE; + response.cbRequested = sizeof(UINT64); + response.requestedData = (BYTE*) &fileSize; + return clipboard->context->ClientFileContentsResponse(clipboard->context, &response); +} + +static UINT wlf_cliprdr_clipboard_file_size_failure(wClipboardDelegate* delegate, + const wClipboardFileSizeRequest* request, UINT errorCode) +{ + CLIPRDR_FILE_CONTENTS_RESPONSE response; + wfClipboard* clipboard = delegate->custom; + ZeroMemory(&response, sizeof(response)); + response.msgFlags = CB_RESPONSE_FAIL; + response.streamId = request->streamId; + response.dwFlags = FILECONTENTS_SIZE; + return clipboard->context->ClientFileContentsResponse(clipboard->context, &response); +} + +static UINT wlf_cliprdr_clipboard_file_range_success(wClipboardDelegate* delegate, + const wClipboardFileRangeRequest* request, const BYTE* data, UINT32 size) +{ + CLIPRDR_FILE_CONTENTS_RESPONSE response; + wfClipboard* clipboard = delegate->custom; + ZeroMemory(&response, sizeof(response)); + response.msgFlags = CB_RESPONSE_OK; + response.streamId = request->streamId; + response.dwFlags = FILECONTENTS_RANGE; + response.cbRequested = size; + response.requestedData = (BYTE*) data; + return clipboard->context->ClientFileContentsResponse(clipboard->context, &response); +} + +static UINT wlf_cliprdr_clipboard_file_range_failure(wClipboardDelegate* delegate, + const wClipboardFileRangeRequest* request, UINT errorCode) +{ + CLIPRDR_FILE_CONTENTS_RESPONSE response; + wfClipboard* clipboard = delegate->custom; + ZeroMemory(&response, sizeof(response)); + response.msgFlags = CB_RESPONSE_FAIL; + response.streamId = request->streamId; + response.dwFlags = FILECONTENTS_RANGE; + return clipboard->context->ClientFileContentsResponse(clipboard->context, &response); +} + +wfClipboard* wlf_clipboard_new(wlfContext* wfc) +{ + rdpChannels* channels; + wfClipboard* clipboard; + + if (!(clipboard = (wfClipboard*) calloc(1, sizeof(wfClipboard)))) + { + WLog_Print(clipboard->log, WLOG_ERROR, "failed to allocate wfClipboard data"); + return NULL; + } + + clipboard->wfc = wfc; + channels = wfc->context.channels; + clipboard->log = WLog_Get(TAG); + clipboard->channels = channels; + clipboard->system = ClipboardCreate(); + + clipboard->delegate = ClipboardGetDelegate(clipboard->system); + clipboard->delegate->custom = clipboard; + clipboard->delegate->ClipboardFileSizeSuccess = wlf_cliprdr_clipboard_file_size_success; + clipboard->delegate->ClipboardFileSizeFailure = wlf_cliprdr_clipboard_file_size_failure; + clipboard->delegate->ClipboardFileRangeSuccess = wlf_cliprdr_clipboard_file_range_success; + clipboard->delegate->ClipboardFileRangeFailure = wlf_cliprdr_clipboard_file_range_failure; + return clipboard; +error: + wlf_clipboard_free(clipboard); + return NULL; +} + +void wlf_clipboard_free(wfClipboard* clipboard) +{ + if (!clipboard) + return; + + wlf_cliprdr_free_server_formats(clipboard); + wlf_cliprdr_free_client_formats(clipboard); + ClipboardDestroy(clipboard->system); + free(clipboard); +} + +BOOL wlf_cliprdr_init(wfClipboard* clipboard, CliprdrClientContext* cliprdr) +{ + if (!cliprdr || !clipboard) + return FALSE; + + clipboard->context = cliprdr; + cliprdr->custom = (void*) clipboard; + 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; + cliprdr->ServerFileContentsRequest = wlf_cliprdr_server_file_contents_request; + return TRUE; +} + +BOOL wlf_cliprdr_uninit(wfClipboard* clipboard, CliprdrClientContext* cliprdr) +{ + if (cliprdr) + cliprdr->custom = NULL; + + if (clipboard) + clipboard->context = NULL; + + return TRUE; +} diff --git a/client/Wayland/wlf_cliprdr.h b/client/Wayland/wlf_cliprdr.h new file mode 100644 index 000000000..a1131400e --- /dev/null +++ b/client/Wayland/wlf_cliprdr.h @@ -0,0 +1,36 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Clipboard Redirection + * + * Copyright 2018 Armin Novak + * 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. + */ + +#ifndef FREERDP_CLIENT_WAYLAND_CLIPRDR_H +#define FREERDP_CLIENT_WAYLAND_CLIPRDR_H + +#include "wlfreerdp.h" + +#include + +wfClipboard* wlf_clipboard_new(wlfContext* wlc); +void wlf_clipboard_free(wfClipboard* clipboard); + +BOOL wlf_cliprdr_init(wfClipboard* clipboard, CliprdrClientContext* cliprdr); +BOOL wlf_cliprdr_uninit(wfClipboard* clipboard, CliprdrClientContext* cliprdr); + +BOOL wlf_cliprdr_handle_event(wfClipboard* clipboard, const UwacClipboardEvent* event); + +#endif /* FREERDP_CLIENT_WAYLAND_CLIPRDR_H */ diff --git a/client/Wayland/wlfreerdp.c b/client/Wayland/wlfreerdp.c index 30a55d4c4..28d0b5e23 100644 --- a/client/Wayland/wlfreerdp.c +++ b/client/Wayland/wlfreerdp.c @@ -36,6 +36,7 @@ #include "wlfreerdp.h" #include "wlf_input.h" +#include "wlf_cliprdr.h" #include "wlf_disp.h" #include "wlf_channels.h" @@ -232,6 +233,11 @@ static BOOL wl_post_connect(freerdp* instance) if (!(context->disp = wlf_disp_new(context))) return FALSE; + context->clipboard = wlf_clipboard_new(context); + + if (!context->clipboard) + return FALSE; + return wl_update_buffer(context, 0, 0, gdi->width, gdi->height); } @@ -247,6 +253,7 @@ static void wl_post_disconnect(freerdp* instance) context = (wlfContext*) instance->context; gdi_free(instance); + wlf_clipboard_free(context->clipboard); wlf_disp_free(context->disp); if (context->window) @@ -329,6 +336,14 @@ static BOOL handle_uwac_events(freerdp* instance, UwacDisplay* display) break; + case UWAC_EVENT_CLIPBOARD_AVAILABLE: + case UWAC_EVENT_CLIPBOARD_OFFER: + case UWAC_EVENT_CLIPBOARD_SELECT: + if (!wlf_cliprdr_handle_event(context->clipboard, &event.clipboard)) + return FALSE; + + break; + default: break; } diff --git a/client/Wayland/wlfreerdp.h b/client/Wayland/wlfreerdp.h index e146c9139..b39915687 100644 --- a/client/Wayland/wlfreerdp.h +++ b/client/Wayland/wlfreerdp.h @@ -31,6 +31,7 @@ #define TAG CLIENT_TAG("wayland") typedef struct wlf_context wlfContext; +typedef struct wlf_clipboard wfClipboard; typedef struct _wlfDispContext wlfDispContext; struct wlf_context @@ -49,6 +50,7 @@ struct wlf_context RdpeiClientContext* rdpei; RdpgfxClientContext* gfx; EncomspClientContext* encomsp; + wfClipboard* clipboard; wlfDispContext* disp; wLog* log; };