diff options
Diffstat (limited to '')
-rw-r--r-- | client/Wayland/CMakeLists.txt | 62 | ||||
-rw-r--r-- | client/Wayland/wlf_channels.c | 79 | ||||
-rw-r--r-- | client/Wayland/wlf_channels.h | 37 | ||||
-rw-r--r-- | client/Wayland/wlf_cliprdr.c | 1009 | ||||
-rw-r--r-- | client/Wayland/wlf_cliprdr.h | 36 | ||||
-rw-r--r-- | client/Wayland/wlf_disp.c | 455 | ||||
-rw-r--r-- | client/Wayland/wlf_disp.h | 38 | ||||
-rw-r--r-- | client/Wayland/wlf_input.c | 458 | ||||
-rw-r--r-- | client/Wayland/wlf_input.h | 45 | ||||
-rw-r--r-- | client/Wayland/wlf_pointer.c | 167 | ||||
-rw-r--r-- | client/Wayland/wlf_pointer.h | 28 | ||||
-rw-r--r-- | client/Wayland/wlfreerdp.1.in | 38 | ||||
-rw-r--r-- | client/Wayland/wlfreerdp.c | 807 | ||||
-rw-r--r-- | client/Wayland/wlfreerdp.h | 59 |
14 files changed, 3318 insertions, 0 deletions
diff --git a/client/Wayland/CMakeLists.txt b/client/Wayland/CMakeLists.txt new file mode 100644 index 0000000..7076ff1 --- /dev/null +++ b/client/Wayland/CMakeLists.txt @@ -0,0 +1,62 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP Wayland Client cmake build script +# +# Copyright 2014 Manuel Bachmann <tarnyko@tarnyko.net> +# Copyright 2015 David Fort <contact@hardening-consulting.com> +# +# 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. + +set(MODULE_NAME "wlfreerdp") +set(MODULE_PREFIX "FREERDP_CLIENT_WAYLAND") + +include_directories(${WAYLAND_INCLUDE_DIR}) + +set(${MODULE_PREFIX}_SRCS + wlfreerdp.c + wlfreerdp.h + wlf_disp.c + wlf_disp.h + wlf_pointer.c + wlf_pointer.h + wlf_input.c + wlf_input.h + wlf_cliprdr.c + wlf_cliprdr.h + wlf_channels.c + wlf_channels.h + ) + +if (FREERDP_UNIFIED_BUILD) + include_directories(${PROJECT_SOURCE_DIR}/uwac/include) + include_directories(${PROJECT_BINARY_DIR}/uwac/include) +else() + find_package(uwac 0 REQUIRED) + include_directories(${UWAC_INCLUDE_DIR}) +endif() + +list (APPEND ${MODULE_PREFIX}_LIBS freerdp-client freerdp uwac) + +add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS}) + +set(MANPAGE_NAME ${MODULE_NAME}) +if (WITH_BINARY_VERSIONING) + set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "${MODULE_NAME}${FREERDP_API_VERSION}") + set(MANPAGE_NAME ${MODULE_NAME}${FREERDP_API_VERSION}) +endif() +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) + +install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT client) + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Client/Wayland") +configure_file(wlfreerdp.1.in ${CMAKE_CURRENT_BINARY_DIR}/${MANPAGE_NAME}.1) +install_freerdp_man(${CMAKE_CURRENT_BINARY_DIR}/${MANPAGE_NAME}.1 1) diff --git a/client/Wayland/wlf_channels.c b/client/Wayland/wlf_channels.c new file mode 100644 index 0000000..3a11407 --- /dev/null +++ b/client/Wayland/wlf_channels.c @@ -0,0 +1,79 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Client Channels + * + * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * 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 <freerdp/gdi/gfx.h> + +#include <freerdp/gdi/video.h> + +#include "wlf_channels.h" +#include "wlf_cliprdr.h" +#include "wlf_disp.h" +#include "wlfreerdp.h" + +void wlf_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e) +{ + wlfContext* wlf = (wlfContext*)context; + + WINPR_ASSERT(wlf); + WINPR_ASSERT(e); + + if (FALSE) + { + } + else if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0) + { + } + else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0) + { + wlf_cliprdr_init(wlf->clipboard, (CliprdrClientContext*)e->pInterface); + } + else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0) + { + wlf_disp_init(wlf->disp, (DispClientContext*)e->pInterface); + } + else + freerdp_client_OnChannelConnectedEventHandler(context, e); +} + +void wlf_OnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e) +{ + wlfContext* wlf = (wlfContext*)context; + + WINPR_ASSERT(wlf); + WINPR_ASSERT(e); + + if (FALSE) + { + } + else if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0) + { + } + else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0) + { + wlf_cliprdr_uninit(wlf->clipboard, (CliprdrClientContext*)e->pInterface); + } + else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0) + { + wlf_disp_uninit(wlf->disp, (DispClientContext*)e->pInterface); + } + else + freerdp_client_OnChannelDisconnectedEventHandler(context, e); +} diff --git a/client/Wayland/wlf_channels.h b/client/Wayland/wlf_channels.h new file mode 100644 index 0000000..e876031 --- /dev/null +++ b/client/Wayland/wlf_channels.h @@ -0,0 +1,37 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Client Channels + * + * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * 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_CHANNELS_H +#define FREERDP_CLIENT_WAYLAND_CHANNELS_H + +#include <freerdp/freerdp.h> +#include <freerdp/client/channels.h> +#include <freerdp/client/rdpei.h> +#include <freerdp/client/rail.h> +#include <freerdp/client/cliprdr.h> +#include <freerdp/client/rdpgfx.h> +#include <freerdp/client/encomsp.h> + +int wlf_on_channel_connected(freerdp* instance, const char* name, void* pInterface); +int wlf_on_channel_disconnected(freerdp* instance, const char* name, void* pInterface); + +void wlf_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e); +void wlf_OnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e); + +#endif /* FREERDP_CLIENT_WAYLAND_CHANNELS_H */ diff --git a/client/Wayland/wlf_cliprdr.c b/client/Wayland/wlf_cliprdr.c new file mode 100644 index 0000000..dc189d5 --- /dev/null +++ b/client/Wayland/wlf_cliprdr.c @@ -0,0 +1,1009 @@ +/** + * 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 MAX_CLIPBOARD_FORMATS 255 + +#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; + +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) + 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 = CB_RESPONSE_OK, + .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_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_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; + BYTE* 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); + + 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) + data = ClipboardGetData(clipboard->system, dstFormatId, &len); + + if (!sres || !data) + goto unlock; + + if (request->responseFile) + { + const size_t res = fwrite(data, 1, len, request->responseFile); + if (res == len) + rc = CHANNEL_RC_OK; + } + else + rc = CHANNEL_RC_OK; + +unlock: + 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; +} diff --git a/client/Wayland/wlf_cliprdr.h b/client/Wayland/wlf_cliprdr.h new file mode 100644 index 0000000..a113140 --- /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 <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. + */ + +#ifndef FREERDP_CLIENT_WAYLAND_CLIPRDR_H +#define FREERDP_CLIENT_WAYLAND_CLIPRDR_H + +#include "wlfreerdp.h" + +#include <freerdp/client/cliprdr.h> + +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/wlf_disp.c b/client/Wayland/wlf_disp.c new file mode 100644 index 0000000..0d87675 --- /dev/null +++ b/client/Wayland/wlf_disp.c @@ -0,0 +1,455 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Display Control Channel + * + * 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 <winpr/sysinfo.h> + +#include "wlf_disp.h" + +#define TAG CLIENT_TAG("wayland.disp") + +#define RESIZE_MIN_DELAY 200 /* minimum delay in ms between two resizes */ + +struct s_wlfDispContext +{ + wlfContext* wlc; + DispClientContext* disp; + BOOL haveXRandr; + int eventBase, errorBase; + int lastSentWidth, lastSentHeight; + UINT64 lastSentDate; + int targetWidth, targetHeight; + BOOL activated; + BOOL waitingResize; + BOOL fullscreen; + UINT16 lastSentDesktopOrientation; + UINT32 lastSentDesktopScaleFactor; + UINT32 lastSentDeviceScaleFactor; +}; + +static UINT wlf_disp_sendLayout(DispClientContext* disp, const rdpMonitor* monitors, + size_t nmonitors); + +static BOOL wlf_disp_settings_changed(wlfDispContext* wlfDisp) +{ + rdpSettings* settings = NULL; + + WINPR_ASSERT(wlfDisp); + WINPR_ASSERT(wlfDisp->wlc); + + settings = wlfDisp->wlc->common.context.settings; + WINPR_ASSERT(settings); + + if (wlfDisp->lastSentWidth != wlfDisp->targetWidth) + return TRUE; + + if (wlfDisp->lastSentHeight != wlfDisp->targetHeight) + return TRUE; + + if (wlfDisp->lastSentDesktopOrientation != + freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation)) + return TRUE; + + if (wlfDisp->lastSentDesktopScaleFactor != + freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor)) + return TRUE; + + if (wlfDisp->lastSentDeviceScaleFactor != + freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor)) + return TRUE; + + if (wlfDisp->fullscreen != wlfDisp->wlc->fullscreen) + return TRUE; + + return FALSE; +} + +static BOOL wlf_update_last_sent(wlfDispContext* wlfDisp) +{ + rdpSettings* settings = NULL; + + WINPR_ASSERT(wlfDisp); + WINPR_ASSERT(wlfDisp->wlc); + + settings = wlfDisp->wlc->common.context.settings; + WINPR_ASSERT(settings); + + wlfDisp->lastSentWidth = wlfDisp->targetWidth; + wlfDisp->lastSentHeight = wlfDisp->targetHeight; + wlfDisp->lastSentDesktopOrientation = + freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation); + wlfDisp->lastSentDesktopScaleFactor = + freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor); + wlfDisp->lastSentDeviceScaleFactor = + freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor); + wlfDisp->fullscreen = wlfDisp->wlc->fullscreen; + return TRUE; +} + +static BOOL wlf_disp_sendResize(wlfDispContext* wlfDisp) +{ + DISPLAY_CONTROL_MONITOR_LAYOUT layout; + wlfContext* wlc = NULL; + rdpSettings* settings = NULL; + + if (!wlfDisp || !wlfDisp->wlc) + return FALSE; + + wlc = wlfDisp->wlc; + settings = wlc->common.context.settings; + + if (!settings) + return FALSE; + + if (!wlfDisp->activated || !wlfDisp->disp) + return TRUE; + + if (GetTickCount64() - wlfDisp->lastSentDate < RESIZE_MIN_DELAY) + return TRUE; + + wlfDisp->lastSentDate = GetTickCount64(); + + if (!wlf_disp_settings_changed(wlfDisp)) + return TRUE; + + /* TODO: Multimonitor support for wayland + if (wlc->fullscreen && (freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount > 0)) + { + if (wlf_disp_sendLayout(wlfDisp->disp, setings->MonitorDefArray, + freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount) != + CHANNEL_RC_OK) return FALSE; + } + else + */ + { + wlfDisp->waitingResize = TRUE; + layout.Flags = DISPLAY_CONTROL_MONITOR_PRIMARY; + layout.Top = layout.Left = 0; + layout.Width = wlfDisp->targetWidth; + layout.Height = wlfDisp->targetHeight; + layout.Orientation = freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation); + layout.DesktopScaleFactor = + freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor); + layout.DeviceScaleFactor = freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor); + layout.PhysicalWidth = wlfDisp->targetWidth; + layout.PhysicalHeight = wlfDisp->targetHeight; + + if (IFCALLRESULT(CHANNEL_RC_OK, wlfDisp->disp->SendMonitorLayout, wlfDisp->disp, 1, + &layout) != CHANNEL_RC_OK) + return FALSE; + } + return wlf_update_last_sent(wlfDisp); +} + +static BOOL wlf_disp_set_window_resizable(wlfDispContext* wlfDisp) +{ +#if 0 // TODO +#endif + return TRUE; +} + +static BOOL wlf_disp_check_context(void* context, wlfContext** ppwlc, wlfDispContext** ppwlfDisp, + rdpSettings** ppSettings) +{ + wlfContext* wlc = NULL; + + if (!context) + return FALSE; + + wlc = (wlfContext*)context; + + if (!(wlc->disp)) + return FALSE; + + if (!wlc->common.context.settings) + return FALSE; + + *ppwlc = wlc; + *ppwlfDisp = wlc->disp; + *ppSettings = wlc->common.context.settings; + return TRUE; +} + +static void wlf_disp_OnActivated(void* context, const ActivatedEventArgs* e) +{ + wlfContext* wlc = NULL; + wlfDispContext* wlfDisp = NULL; + rdpSettings* settings = NULL; + + if (!wlf_disp_check_context(context, &wlc, &wlfDisp, &settings)) + return; + + wlfDisp->waitingResize = FALSE; + + if (wlfDisp->activated && !freerdp_settings_get_bool(settings, FreeRDP_Fullscreen)) + { + wlf_disp_set_window_resizable(wlfDisp); + + if (e->firstActivation) + return; + + wlf_disp_sendResize(wlfDisp); + } +} + +static void wlf_disp_OnGraphicsReset(void* context, const GraphicsResetEventArgs* e) +{ + wlfContext* wlc = NULL; + wlfDispContext* wlfDisp = NULL; + rdpSettings* settings = NULL; + + WINPR_UNUSED(e); + if (!wlf_disp_check_context(context, &wlc, &wlfDisp, &settings)) + return; + + wlfDisp->waitingResize = FALSE; + + if (wlfDisp->activated && !freerdp_settings_get_bool(settings, FreeRDP_Fullscreen)) + { + wlf_disp_set_window_resizable(wlfDisp); + wlf_disp_sendResize(wlfDisp); + } +} + +static void wlf_disp_OnTimer(void* context, const TimerEventArgs* e) +{ + wlfContext* wlc = NULL; + wlfDispContext* wlfDisp = NULL; + rdpSettings* settings = NULL; + + WINPR_UNUSED(e); + if (!wlf_disp_check_context(context, &wlc, &wlfDisp, &settings)) + return; + + if (!wlfDisp->activated || freerdp_settings_get_bool(settings, FreeRDP_Fullscreen)) + return; + + wlf_disp_sendResize(wlfDisp); +} + +wlfDispContext* wlf_disp_new(wlfContext* wlc) +{ + wlfDispContext* ret = NULL; + wPubSub* pubSub = NULL; + rdpSettings* settings = NULL; + + if (!wlc || !wlc->common.context.settings || !wlc->common.context.pubSub) + return NULL; + + settings = wlc->common.context.settings; + pubSub = wlc->common.context.pubSub; + ret = calloc(1, sizeof(wlfDispContext)); + + if (!ret) + return NULL; + + ret->wlc = wlc; + ret->lastSentWidth = ret->targetWidth = + freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + ret->lastSentHeight = ret->targetHeight = + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + PubSub_SubscribeActivated(pubSub, wlf_disp_OnActivated); + PubSub_SubscribeGraphicsReset(pubSub, wlf_disp_OnGraphicsReset); + PubSub_SubscribeTimer(pubSub, wlf_disp_OnTimer); + return ret; +} + +void wlf_disp_free(wlfDispContext* disp) +{ + if (!disp) + return; + + if (disp->wlc) + { + wPubSub* pubSub = disp->wlc->common.context.pubSub; + PubSub_UnsubscribeActivated(pubSub, wlf_disp_OnActivated); + PubSub_UnsubscribeGraphicsReset(pubSub, wlf_disp_OnGraphicsReset); + PubSub_UnsubscribeTimer(pubSub, wlf_disp_OnTimer); + } + + free(disp); +} + +UINT wlf_disp_sendLayout(DispClientContext* disp, const rdpMonitor* monitors, size_t nmonitors) +{ + UINT ret = CHANNEL_RC_OK; + DISPLAY_CONTROL_MONITOR_LAYOUT* layouts = NULL; + wlfDispContext* wlfDisp = NULL; + rdpSettings* settings = NULL; + + WINPR_ASSERT(disp); + WINPR_ASSERT(monitors); + WINPR_ASSERT(nmonitors > 0); + + wlfDisp = (wlfDispContext*)disp->custom; + WINPR_ASSERT(wlfDisp); + WINPR_ASSERT(wlfDisp->wlc); + + settings = wlfDisp->wlc->common.context.settings; + WINPR_ASSERT(settings); + + layouts = calloc(nmonitors, sizeof(DISPLAY_CONTROL_MONITOR_LAYOUT)); + + if (!layouts) + return CHANNEL_RC_NO_MEMORY; + + for (size_t i = 0; i < nmonitors; i++) + { + const rdpMonitor* monitor = &monitors[i]; + DISPLAY_CONTROL_MONITOR_LAYOUT* layout = &layouts[i]; + + layout->Flags = (monitor->is_primary ? DISPLAY_CONTROL_MONITOR_PRIMARY : 0); + layout->Left = monitor->x; + layout->Top = monitor->y; + layout->Width = monitor->width; + layout->Height = monitor->height; + layout->Orientation = ORIENTATION_LANDSCAPE; + layout->PhysicalWidth = monitor->attributes.physicalWidth; + layout->PhysicalHeight = monitor->attributes.physicalHeight; + + switch (monitor->attributes.orientation) + { + case 90: + layout->Orientation = ORIENTATION_PORTRAIT; + break; + + case 180: + layout->Orientation = ORIENTATION_LANDSCAPE_FLIPPED; + break; + + case 270: + layout->Orientation = ORIENTATION_PORTRAIT_FLIPPED; + break; + + case 0: + default: + /* MS-RDPEDISP - 2.2.2.2.1: + * Orientation (4 bytes): A 32-bit unsigned integer that specifies the + * orientation of the monitor in degrees. Valid values are 0, 90, 180 + * or 270 + * + * So we default to ORIENTATION_LANDSCAPE + */ + layout->Orientation = ORIENTATION_LANDSCAPE; + break; + } + + layout->DesktopScaleFactor = + freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor); + layout->DeviceScaleFactor = + freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor); + } + + ret = IFCALLRESULT(CHANNEL_RC_OK, disp->SendMonitorLayout, disp, nmonitors, layouts); + free(layouts); + return ret; +} + +BOOL wlf_disp_handle_configure(wlfDispContext* disp, int32_t width, int32_t height) +{ + if (!disp) + return FALSE; + + disp->targetWidth = width; + disp->targetHeight = height; + return wlf_disp_sendResize(disp); +} + +static UINT wlf_DisplayControlCaps(DispClientContext* disp, UINT32 maxNumMonitors, + UINT32 maxMonitorAreaFactorA, UINT32 maxMonitorAreaFactorB) +{ + /* we're called only if dynamic resolution update is activated */ + wlfDispContext* wlfDisp = NULL; + rdpSettings* settings = NULL; + + WINPR_ASSERT(disp); + + wlfDisp = (wlfDispContext*)disp->custom; + WINPR_ASSERT(wlfDisp); + WINPR_ASSERT(wlfDisp->wlc); + + settings = wlfDisp->wlc->common.context.settings; + WINPR_ASSERT(settings); + + WLog_DBG(TAG, + "DisplayControlCapsPdu: MaxNumMonitors: %" PRIu32 " MaxMonitorAreaFactorA: %" PRIu32 + " MaxMonitorAreaFactorB: %" PRIu32 "", + maxNumMonitors, maxMonitorAreaFactorA, maxMonitorAreaFactorB); + wlfDisp->activated = TRUE; + + if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen)) + return CHANNEL_RC_OK; + + WLog_DBG(TAG, "DisplayControlCapsPdu: setting the window as resizable"); + return wlf_disp_set_window_resizable(wlfDisp) ? CHANNEL_RC_OK : CHANNEL_RC_NO_MEMORY; +} + +BOOL wlf_disp_init(wlfDispContext* wlfDisp, DispClientContext* disp) +{ + rdpSettings* settings = NULL; + + if (!wlfDisp || !wlfDisp->wlc || !disp) + return FALSE; + + settings = wlfDisp->wlc->common.context.settings; + + if (!settings) + return FALSE; + + wlfDisp->disp = disp; + disp->custom = (void*)wlfDisp; + + if (freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate)) + { + disp->DisplayControlCaps = wlf_DisplayControlCaps; + } + + return TRUE; +} + +BOOL wlf_disp_uninit(wlfDispContext* wlfDisp, DispClientContext* disp) +{ + if (!wlfDisp || !disp) + return FALSE; + + wlfDisp->disp = NULL; + return TRUE; +} + +int wlf_list_monitors(wlfContext* wlc) +{ + uint32_t nmonitors = UwacDisplayGetNbOutputs(wlc->display); + + for (uint32_t i = 0; i < nmonitors; i++) + { + const UwacOutput* monitor = UwacDisplayGetOutput(wlc->display, i); + UwacSize resolution; + UwacPosition pos; + + if (!monitor) + continue; + UwacOutputGetPosition(monitor, &pos); + UwacOutputGetResolution(monitor, &resolution); + + printf(" %s [%d] %dx%d\t+%d+%d\n", (i == 0) ? "*" : " ", i, resolution.width, + resolution.height, pos.x, pos.y); + } + + return 0; +} diff --git a/client/Wayland/wlf_disp.h b/client/Wayland/wlf_disp.h new file mode 100644 index 0000000..36fa27c --- /dev/null +++ b/client/Wayland/wlf_disp.h @@ -0,0 +1,38 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Display Control Channel + * + * 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. + */ +#ifndef FREERDP_CLIENT_WAYLAND_DISP_H +#define FREERDP_CLIENT_WAYLAND_DISP_H + +#include <freerdp/types.h> +#include <freerdp/client/disp.h> + +#include "wlfreerdp.h" + +FREERDP_API BOOL wlf_disp_init(wlfDispContext* xfDisp, DispClientContext* disp); +FREERDP_API BOOL wlf_disp_uninit(wlfDispContext* xfDisp, DispClientContext* disp); + +wlfDispContext* wlf_disp_new(wlfContext* wlc); +void wlf_disp_free(wlfDispContext* disp); +BOOL wlf_disp_handle_configure(wlfDispContext* disp, int32_t width, int32_t height); +void wlf_disp_resized(wlfDispContext* disp); + +int wlf_list_monitors(wlfContext* wlc); + +#endif /* FREERDP_CLIENT_WAYLAND_DISP_H */ diff --git a/client/Wayland/wlf_input.c b/client/Wayland/wlf_input.c new file mode 100644 index 0000000..60603db --- /dev/null +++ b/client/Wayland/wlf_input.c @@ -0,0 +1,458 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Input + * + * Copyright 2014 Manuel Bachmann <tarnyko@tarnyko.net> + * Copyright 2015 David Fort <contact@hardening-consulting.com> + * + * 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 <stdlib.h> +#include <float.h> + +#include <linux/input.h> + +#include <winpr/assert.h> + +#include <freerdp/config.h> +#include <freerdp/locale/keyboard.h> +#if defined(CHANNEL_RDPEI_CLIENT) +#include <freerdp/client/rdpei.h> +#endif +#include <uwac/uwac.h> + +#include "wlfreerdp.h" +#include "wlf_input.h" + +#define TAG CLIENT_TAG("wayland.input") + +static BOOL scale_signed_coordinates(rdpContext* context, int32_t* x, int32_t* y, + BOOL fromLocalToRDP) +{ + BOOL rc = 0; + UINT32 ux = 0; + UINT32 uy = 0; + WINPR_ASSERT(context); + WINPR_ASSERT(x); + WINPR_ASSERT(y); + WINPR_ASSERT(*x >= 0); + WINPR_ASSERT(*y >= 0); + + ux = (UINT32)*x; + uy = (UINT32)*y; + rc = wlf_scale_coordinates(context, &ux, &uy, fromLocalToRDP); + WINPR_ASSERT(ux < INT32_MAX); + WINPR_ASSERT(uy < INT32_MAX); + *x = (int32_t)ux; + *y = (int32_t)uy; + return rc; +} + +BOOL wlf_handle_pointer_enter(freerdp* instance, const UwacPointerEnterLeaveEvent* ev) +{ + uint32_t x = 0; + uint32_t y = 0; + rdpClientContext* cctx = NULL; + + if (!instance || !ev) + return FALSE; + + x = ev->x; + y = ev->y; + + if (!wlf_scale_coordinates(instance->context, &x, &y, TRUE)) + return FALSE; + + WINPR_ASSERT(x <= UINT16_MAX); + WINPR_ASSERT(y <= UINT16_MAX); + cctx = (rdpClientContext*)instance->context; + return freerdp_client_send_button_event(cctx, FALSE, PTR_FLAGS_MOVE, x, y); +} + +BOOL wlf_handle_pointer_motion(freerdp* instance, const UwacPointerMotionEvent* ev) +{ + uint32_t x = 0; + uint32_t y = 0; + rdpClientContext* cctx = NULL; + + if (!instance || !ev) + return FALSE; + + cctx = (rdpClientContext*)instance->context; + WINPR_ASSERT(cctx); + + x = ev->x; + y = ev->y; + + if (!wlf_scale_coordinates(instance->context, &x, &y, TRUE)) + return FALSE; + + WINPR_ASSERT(x <= UINT16_MAX); + WINPR_ASSERT(y <= UINT16_MAX); + return freerdp_client_send_button_event(cctx, FALSE, PTR_FLAGS_MOVE, x, y); +} + +BOOL wlf_handle_pointer_buttons(freerdp* instance, const UwacPointerButtonEvent* ev) +{ + rdpClientContext* cctx = NULL; + UINT16 flags = 0; + UINT16 xflags = 0; + uint32_t x = 0; + uint32_t y = 0; + + if (!instance || !ev) + return FALSE; + + cctx = (rdpClientContext*)instance->context; + WINPR_ASSERT(cctx); + + x = ev->x; + y = ev->y; + + if (!wlf_scale_coordinates(instance->context, &x, &y, TRUE)) + return FALSE; + + if (ev->state == WL_POINTER_BUTTON_STATE_PRESSED) + { + flags |= PTR_FLAGS_DOWN; + xflags |= PTR_XFLAGS_DOWN; + } + + switch (ev->button) + { + case BTN_LEFT: + flags |= PTR_FLAGS_BUTTON1; + break; + + case BTN_RIGHT: + flags |= PTR_FLAGS_BUTTON2; + break; + + case BTN_MIDDLE: + flags |= PTR_FLAGS_BUTTON3; + break; + + case BTN_SIDE: + xflags |= PTR_XFLAGS_BUTTON1; + break; + + case BTN_EXTRA: + xflags |= PTR_XFLAGS_BUTTON2; + break; + + default: + return TRUE; + } + + WINPR_ASSERT(x <= UINT16_MAX); + WINPR_ASSERT(y <= UINT16_MAX); + + if ((flags & ~PTR_FLAGS_DOWN) != 0) + return freerdp_client_send_button_event(cctx, FALSE, flags, x, y); + + if ((xflags & ~PTR_XFLAGS_DOWN) != 0) + return freerdp_client_send_extended_button_event(cctx, FALSE, xflags, x, y); + + return FALSE; +} + +BOOL wlf_handle_pointer_axis(freerdp* instance, const UwacPointerAxisEvent* ev) +{ + wlfContext* context = NULL; + if (!instance || !instance->context || !ev) + return FALSE; + + context = (wlfContext*)instance->context; + return ArrayList_Append(context->events, ev); +} + +BOOL wlf_handle_pointer_axis_discrete(freerdp* instance, const UwacPointerAxisEvent* ev) +{ + wlfContext* context = NULL; + if (!instance || !instance->context || !ev) + return FALSE; + + context = (wlfContext*)instance->context; + return ArrayList_Append(context->events, ev); +} + +static BOOL wlf_handle_wheel(freerdp* instance, uint32_t x, uint32_t y, uint32_t axis, + int32_t value) +{ + rdpClientContext* cctx = NULL; + UINT16 flags = 0; + int32_t direction = 0; + uint32_t avalue = (uint32_t)abs(value); + + WINPR_ASSERT(instance); + + cctx = (rdpClientContext*)instance->context; + WINPR_ASSERT(cctx); + + if (!wlf_scale_coordinates(instance->context, &x, &y, TRUE)) + return FALSE; + + direction = value; + switch (axis) + { + case WL_POINTER_AXIS_VERTICAL_SCROLL: + flags |= PTR_FLAGS_WHEEL; + if (direction > 0) + flags |= PTR_FLAGS_WHEEL_NEGATIVE; + break; + + case WL_POINTER_AXIS_HORIZONTAL_SCROLL: + flags |= PTR_FLAGS_HWHEEL; + if (direction < 0) + flags |= PTR_FLAGS_WHEEL_NEGATIVE; + break; + + default: + return FALSE; + } + + /* Wheel rotation steps: + * + * positive: 0 ... 0xFF -> slow ... fast + * negative: 0 ... 0xFF -> fast ... slow + */ + + while (avalue > 0) + { + const UINT16 cval = (avalue > 0xFF) ? 0xFF : (UINT16)avalue; + UINT16 cflags = flags | cval; + /* Convert negative values to 9bit twos complement */ + if (flags & PTR_FLAGS_WHEEL_NEGATIVE) + cflags = (flags & 0xFF00) | (0x100 - cval); + if (!freerdp_client_send_wheel_event(cctx, cflags)) + return FALSE; + + avalue -= cval; + } + return TRUE; +} + +BOOL wlf_handle_pointer_frame(freerdp* instance, const UwacPointerFrameEvent* ev) +{ + BOOL success = TRUE; + BOOL handle = FALSE; + wlfContext* context = NULL; + enum wl_pointer_axis_source source = WL_POINTER_AXIS_SOURCE_CONTINUOUS; + + if (!instance || !ev || !instance->context) + return FALSE; + + context = (wlfContext*)instance->context; + + for (size_t x = 0; x < ArrayList_Count(context->events); x++) + { + UwacEvent* cev = ArrayList_GetItem(context->events, x); + if (!cev) + continue; + if (cev->type == UWAC_EVENT_POINTER_SOURCE) + { + handle = TRUE; + source = cev->mouse_source.axis_source; + } + } + + /* We need source events to determine how to interpret the data */ + if (handle) + { + for (size_t x = 0; x < ArrayList_Count(context->events); x++) + { + UwacEvent* cev = ArrayList_GetItem(context->events, x); + if (!cev) + continue; + + switch (source) + { + /* If we have a mouse wheel, just use discrete data */ + case WL_POINTER_AXIS_SOURCE_WHEEL: +#if defined(WL_POINTER_AXIS_SOURCE_WHEEL_TILT_SINCE_VERSION) + case WL_POINTER_AXIS_SOURCE_WHEEL_TILT: +#endif + if (cev->type == UWAC_EVENT_POINTER_AXIS_DISCRETE) + { + /* Get the number of steps, multiply by default step width of 120 */ + int32_t val = cev->mouse_axis.value * 0x78; + /* No wheel event received, success! */ + if (!wlf_handle_wheel(instance, cev->mouse_axis.x, cev->mouse_axis.y, + cev->mouse_axis.axis, val)) + success = FALSE; + } + break; + /* If we have a touch pad we get actual data, scale */ + case WL_POINTER_AXIS_SOURCE_FINGER: + case WL_POINTER_AXIS_SOURCE_CONTINUOUS: + if (cev->type == UWAC_EVENT_POINTER_AXIS) + { + double dval = wl_fixed_to_double(cev->mouse_axis.value); + int32_t val = (int32_t)(dval * 0x78 / 10.0); + if (!wlf_handle_wheel(instance, cev->mouse_axis.x, cev->mouse_axis.y, + cev->mouse_axis.axis, val)) + success = FALSE; + } + break; + default: + break; + } + } + } + ArrayList_Clear(context->events); + return success; +} + +BOOL wlf_handle_pointer_source(freerdp* instance, const UwacPointerSourceEvent* ev) +{ + wlfContext* context = NULL; + if (!instance || !instance->context || !ev) + return FALSE; + + context = (wlfContext*)instance->context; + return ArrayList_Append(context->events, ev); +} + +BOOL wlf_handle_key(freerdp* instance, const UwacKeyEvent* ev) +{ + rdpInput* input = NULL; + DWORD rdp_scancode = 0; + + if (!instance || !ev) + return FALSE; + + WINPR_ASSERT(instance->context); + if (freerdp_settings_get_bool(instance->context->settings, FreeRDP_GrabKeyboard) && + ev->raw_key == KEY_RIGHTCTRL) + wlf_handle_ungrab_key(instance, ev); + + input = instance->context->input; + rdp_scancode = freerdp_keyboard_get_rdp_scancode_from_x11_keycode(ev->raw_key + 8); + + if (rdp_scancode == RDP_SCANCODE_UNKNOWN) + return TRUE; + + return freerdp_input_send_keyboard_event_ex(input, ev->pressed, ev->repeated, rdp_scancode); +} + +BOOL wlf_handle_ungrab_key(freerdp* instance, const UwacKeyEvent* ev) +{ + wlfContext* context = NULL; + if (!instance || !instance->context || !ev) + return FALSE; + + context = (wlfContext*)instance->context; + + return UwacSeatInhibitShortcuts(context->seat, false) == UWAC_SUCCESS; +} + +BOOL wlf_keyboard_enter(freerdp* instance, const UwacKeyboardEnterLeaveEvent* ev) +{ + if (!instance || !ev) + return FALSE; + + ((wlfContext*)instance->context)->focusing = TRUE; + return TRUE; +} + +BOOL wlf_keyboard_modifiers(freerdp* instance, const UwacKeyboardModifiersEvent* ev) +{ + rdpInput* input = NULL; + UINT16 syncFlags = 0; + wlfContext* wlf = NULL; + + if (!instance || !ev) + return FALSE; + + wlf = (wlfContext*)instance->context; + WINPR_ASSERT(wlf); + + input = instance->context->input; + WINPR_ASSERT(input); + + syncFlags = 0; + + if (ev->modifiers & UWAC_MOD_CAPS_MASK) + syncFlags |= KBD_SYNC_CAPS_LOCK; + if (ev->modifiers & UWAC_MOD_NUM_MASK) + syncFlags |= KBD_SYNC_NUM_LOCK; + + if (!wlf->focusing) + return TRUE; + + ((wlfContext*)instance->context)->focusing = FALSE; + + return freerdp_input_send_focus_in_event(input, syncFlags) && + freerdp_client_send_button_event(&wlf->common, FALSE, PTR_FLAGS_MOVE, 0, 0); +} + +BOOL wlf_handle_touch_up(freerdp* instance, const UwacTouchUp* ev) +{ + int32_t x = 0; + int32_t y = 0; + + WINPR_ASSERT(instance); + WINPR_ASSERT(ev); + + wlfContext* wlf = (wlfContext*)instance->context; + WINPR_ASSERT(wlf); + + x = ev->x; + y = ev->y; + + if (!scale_signed_coordinates(instance->context, &x, &y, TRUE)) + return FALSE; + + return freerdp_client_handle_touch(&wlf->common, FREERDP_TOUCH_UP, ev->id, 0, x, y); +} + +BOOL wlf_handle_touch_down(freerdp* instance, const UwacTouchDown* ev) +{ + int32_t x = 0; + int32_t y = 0; + + WINPR_ASSERT(instance); + WINPR_ASSERT(ev); + + wlfContext* wlf = (wlfContext*)instance->context; + WINPR_ASSERT(wlf); + + x = ev->x; + y = ev->y; + + if (!scale_signed_coordinates(instance->context, &x, &y, TRUE)) + return FALSE; + + return freerdp_client_handle_touch(&wlf->common, FREERDP_TOUCH_DOWN, ev->id, 0, x, y); +} + +BOOL wlf_handle_touch_motion(freerdp* instance, const UwacTouchMotion* ev) +{ + int32_t x = 0; + int32_t y = 0; + + WINPR_ASSERT(instance); + WINPR_ASSERT(ev); + + wlfContext* wlf = (wlfContext*)instance->context; + WINPR_ASSERT(wlf); + + x = ev->x; + y = ev->y; + + if (!scale_signed_coordinates(instance->context, &x, &y, TRUE)) + return FALSE; + + return freerdp_client_handle_touch(&wlf->common, FREERDP_TOUCH_MOTION, 0, ev->id, x, y); +} diff --git a/client/Wayland/wlf_input.h b/client/Wayland/wlf_input.h new file mode 100644 index 0000000..0d4f5ef --- /dev/null +++ b/client/Wayland/wlf_input.h @@ -0,0 +1,45 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Input + * + * Copyright 2014 Manuel Bachmann <tarnyko@tarnyko.net> + * Copyright 2015 David Fort <contact@hardening-consulting.com> + * + * 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_INPUT_H +#define FREERDP_CLIENT_WAYLAND_INPUT_H + +#include <freerdp/freerdp.h> +#include <freerdp/gdi/gdi.h> +#include <freerdp/gdi/gfx.h> +#include <uwac/uwac.h> + +BOOL wlf_handle_pointer_enter(freerdp* instance, const UwacPointerEnterLeaveEvent* ev); +BOOL wlf_handle_pointer_motion(freerdp* instance, const UwacPointerMotionEvent* ev); +BOOL wlf_handle_pointer_buttons(freerdp* instance, const UwacPointerButtonEvent* ev); +BOOL wlf_handle_pointer_axis(freerdp* instance, const UwacPointerAxisEvent* ev); +BOOL wlf_handle_pointer_axis_discrete(freerdp* instance, const UwacPointerAxisEvent* ev); +BOOL wlf_handle_pointer_frame(freerdp* instance, const UwacPointerFrameEvent* ev); +BOOL wlf_handle_pointer_source(freerdp* instance, const UwacPointerSourceEvent* ev); +BOOL wlf_handle_touch_up(freerdp* instance, const UwacTouchUp* ev); +BOOL wlf_handle_touch_down(freerdp* instance, const UwacTouchDown* ev); +BOOL wlf_handle_touch_motion(freerdp* instance, const UwacTouchMotion* ev); + +BOOL wlf_handle_key(freerdp* instance, const UwacKeyEvent* ev); +BOOL wlf_handle_ungrab_key(freerdp* instance, const UwacKeyEvent* ev); +BOOL wlf_keyboard_enter(freerdp* instance, const UwacKeyboardEnterLeaveEvent* ev); +BOOL wlf_keyboard_modifiers(freerdp* instance, const UwacKeyboardModifiersEvent* ev); + +#endif /* FREERDP_CLIENT_WAYLAND_INPUT_H */ diff --git a/client/Wayland/wlf_pointer.c b/client/Wayland/wlf_pointer.c new file mode 100644 index 0000000..6fba40b --- /dev/null +++ b/client/Wayland/wlf_pointer.c @@ -0,0 +1,167 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Mouse Pointer + * + * Copyright 2019 Armin Novak <armin.novak@thincast.com> + * Copyright 2019 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 "wlf_pointer.h" +#include "wlfreerdp.h" + +#define TAG CLIENT_TAG("wayland.pointer") + +typedef struct +{ + rdpPointer pointer; + size_t size; + void* data; +} wlfPointer; + +static BOOL wlf_Pointer_New(rdpContext* context, rdpPointer* pointer) +{ + wlfPointer* ptr = (wlfPointer*)pointer; + + if (!ptr) + return FALSE; + + ptr->size = pointer->width * pointer->height * 4ULL; + ptr->data = winpr_aligned_malloc(ptr->size, 16); + + if (!ptr->data) + return FALSE; + + if (!freerdp_image_copy_from_pointer_data( + ptr->data, PIXEL_FORMAT_BGRA32, 0, 0, 0, pointer->width, pointer->height, + pointer->xorMaskData, pointer->lengthXorMask, pointer->andMaskData, + pointer->lengthAndMask, pointer->xorBpp, &context->gdi->palette)) + { + winpr_aligned_free(ptr->data); + return FALSE; + } + + return TRUE; +} + +static void wlf_Pointer_Free(rdpContext* context, rdpPointer* pointer) +{ + wlfPointer* ptr = (wlfPointer*)pointer; + WINPR_UNUSED(context); + + if (ptr) + winpr_aligned_free(ptr->data); +} + +static BOOL wlf_Pointer_Set(rdpContext* context, rdpPointer* pointer) +{ + wlfContext* wlf = (wlfContext*)context; + wlfPointer* ptr = (wlfPointer*)pointer; + void* data = NULL; + UINT32 w = 0; + UINT32 h = 0; + UINT32 x = 0; + UINT32 y = 0; + size_t size = 0; + UwacReturnCode rc = UWAC_ERROR_INTERNAL; + BOOL res = FALSE; + RECTANGLE_16 area; + + if (!wlf || !wlf->seat) + return FALSE; + + x = pointer->xPos; + y = pointer->yPos; + w = pointer->width; + h = pointer->height; + + if (!wlf_scale_coordinates(context, &x, &y, FALSE) || + !wlf_scale_coordinates(context, &w, &h, FALSE)) + return FALSE; + + size = w * h * 4ULL; + data = malloc(size); + + if (!data) + return FALSE; + + area.top = 0; + area.left = 0; + area.right = (UINT16)pointer->width; + area.bottom = (UINT16)pointer->height; + + if (!wlf_copy_image(ptr->data, pointer->width * 4, pointer->width, pointer->height, data, w * 4, + w, h, &area, + freerdp_settings_get_bool(context->settings, FreeRDP_SmartSizing))) + goto fail; + + rc = UwacSeatSetMouseCursor(wlf->seat, data, size, w, h, x, y); + + if (rc == UWAC_SUCCESS) + res = TRUE; + +fail: + free(data); + return res; +} + +static BOOL wlf_Pointer_SetNull(rdpContext* context) +{ + wlfContext* wlf = (wlfContext*)context; + + if (!wlf || !wlf->seat) + return FALSE; + + if (UwacSeatSetMouseCursor(wlf->seat, NULL, 0, 0, 0, 0, 0) != UWAC_SUCCESS) + return FALSE; + + return TRUE; +} + +static BOOL wlf_Pointer_SetDefault(rdpContext* context) +{ + wlfContext* wlf = (wlfContext*)context; + + if (!wlf || !wlf->seat) + return FALSE; + + if (UwacSeatSetMouseCursor(wlf->seat, NULL, 1, 0, 0, 0, 0) != UWAC_SUCCESS) + return FALSE; + + return TRUE; +} + +static BOOL wlf_Pointer_SetPosition(rdpContext* context, UINT32 x, UINT32 y) +{ + // TODO + WLog_WARN(TAG, "not implemented"); + return TRUE; +} + +BOOL wlf_register_pointer(rdpGraphics* graphics) +{ + rdpPointer pointer = { 0 }; + + pointer.size = sizeof(wlfPointer); + pointer.New = wlf_Pointer_New; + pointer.Free = wlf_Pointer_Free; + pointer.Set = wlf_Pointer_Set; + pointer.SetNull = wlf_Pointer_SetNull; + pointer.SetDefault = wlf_Pointer_SetDefault; + pointer.SetPosition = wlf_Pointer_SetPosition; + graphics_register_pointer(graphics, &pointer); + return TRUE; +} diff --git a/client/Wayland/wlf_pointer.h b/client/Wayland/wlf_pointer.h new file mode 100644 index 0000000..8ae82e1 --- /dev/null +++ b/client/Wayland/wlf_pointer.h @@ -0,0 +1,28 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Mouse Pointer + * + * Copyright 2019 Armin Novak <armin.novak@thincast.com> + * Copyright 2019 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_POINTER_H +#define FREERDP_CLIENT_WAYLAND_POINTER_H + +#include <freerdp/graphics.h> + +BOOL wlf_register_pointer(rdpGraphics* graphics); + +#endif /* FREERDP_CLIENT_WAYLAND_POINTER_H */ diff --git a/client/Wayland/wlfreerdp.1.in b/client/Wayland/wlfreerdp.1.in new file mode 100644 index 0000000..ee40412 --- /dev/null +++ b/client/Wayland/wlfreerdp.1.in @@ -0,0 +1,38 @@ +.de URL +\\$2 \(laURL: \\$1 \(ra\\$3 +.. +.if \n[.g] .mso www.tmac +.TH @MANPAGE_NAME@ 1 2017-01-12 "@FREERDP_VERSION_FULL@" "FreeRDP" +.SH NAME +@MANPAGE_NAME@ \- FreeRDP wayland client +.SH SYNOPSIS +.B @MANPAGE_NAME@ +[file] +[\fIdefault_client_options\fP] +[\fB/v\fP:<server>[:port]] +[\fB/version\fP] +[\fB/help\fP] +.SH DESCRIPTION +.B @MANPAGE_NAME@ +is a wayland Remote Desktop Protocol (RDP) client which is part of the FreeRDP project. A RDP server is built-in to many editions of Windows.. Alternative servers included ogon, gnome-remote-desktop, xrdp and VRDP (VirtualBox). +.SH OPTIONS +The wayland client also supports a lot of the \fIdefault client options\fP which are not described here. For details on those see the xfreerdp(1) man page. +.IP \fB/v:\fP\fI<server>[:port]\fP +The server hostname or IP, and optionally the port, to connect to. +.IP /version +Print the version and exit. +.IP /help +Print the help and exit. +.SH EXIT STATUS +.TP +.B 0 +Successful program execution. +.TP +.B not 0 +On failure. + +.SH SEE ALSO +xfreerdp(1) wlog(7) + +.SH AUTHOR +FreeRDP <team@freerdp.com> diff --git a/client/Wayland/wlfreerdp.c b/client/Wayland/wlfreerdp.c new file mode 100644 index 0000000..037c999 --- /dev/null +++ b/client/Wayland/wlfreerdp.c @@ -0,0 +1,807 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Client + * + * Copyright 2014 Manuel Bachmann <tarnyko@tarnyko.net> + * Copyright 2016 Thincast Technologies GmbH + * Copyright 2016 Armin Novak <armin.novak@thincast.com> + * + * 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 <math.h> +#include <stdio.h> +#include <errno.h> +#include <locale.h> +#include <float.h> + +#include <winpr/sysinfo.h> + +#include <freerdp/client/cmdline.h> +#include <freerdp/channels/channels.h> +#include <freerdp/gdi/gdi.h> +#include <freerdp/client.h> +#include <freerdp/utils/signal.h> +#include <freerdp/locale/keyboard.h> + +#include <linux/input.h> + +#include <uwac/uwac.h> + +#include "wlfreerdp.h" +#include "wlf_input.h" +#include "wlf_cliprdr.h" +#include "wlf_disp.h" +#include "wlf_channels.h" +#include "wlf_pointer.h" + +#define TAG CLIENT_TAG("wayland") + +static BOOL wl_update_buffer(wlfContext* context_w, INT32 ix, INT32 iy, INT32 iw, INT32 ih) +{ + BOOL res = FALSE; + rdpGdi* gdi = NULL; + char* data = NULL; + UINT32 x = 0; + UINT32 y = 0; + UINT32 w = 0; + UINT32 h = 0; + UwacSize geometry; + size_t stride = 0; + UwacReturnCode rc = UWAC_ERROR_INTERNAL; + RECTANGLE_16 area; + + if (!context_w) + return FALSE; + + if ((ix < 0) || (iy < 0) || (iw < 0) || (ih < 0)) + return FALSE; + + EnterCriticalSection(&context_w->critical); + x = (UINT32)ix; + y = (UINT32)iy; + w = (UINT32)iw; + h = (UINT32)ih; + rc = UwacWindowGetDrawingBufferGeometry(context_w->window, &geometry, &stride); + data = UwacWindowGetDrawingBuffer(context_w->window); + + if (!data || (rc != UWAC_SUCCESS)) + goto fail; + + gdi = context_w->common.context.gdi; + + if (!gdi) + goto fail; + + /* Ignore output if the surface size does not match. */ + if (((INT64)x > geometry.width) || ((INT64)y > geometry.height)) + { + res = TRUE; + goto fail; + } + + area.left = x; + area.top = y; + area.right = x + w; + area.bottom = y + h; + + if (!wlf_copy_image( + gdi->primary_buffer, gdi->stride, gdi->width, gdi->height, data, stride, geometry.width, + geometry.height, &area, + freerdp_settings_get_bool(context_w->common.context.settings, FreeRDP_SmartSizing))) + goto fail; + + if (!wlf_scale_coordinates(&context_w->common.context, &x, &y, FALSE)) + goto fail; + + if (!wlf_scale_coordinates(&context_w->common.context, &w, &h, FALSE)) + goto fail; + + if (UwacWindowAddDamage(context_w->window, x, y, w, h) != UWAC_SUCCESS) + goto fail; + + if (UwacWindowSubmitBuffer(context_w->window, false) != UWAC_SUCCESS) + goto fail; + + res = TRUE; +fail: + LeaveCriticalSection(&context_w->critical); + return res; +} + +static BOOL wl_end_paint(rdpContext* context) +{ + rdpGdi* gdi = NULL; + wlfContext* context_w = NULL; + INT32 x = 0; + INT32 y = 0; + INT32 w = 0; + INT32 h = 0; + + if (!context || !context->gdi || !context->gdi->primary) + return FALSE; + + gdi = context->gdi; + + if (gdi->primary->hdc->hwnd->invalid->null) + return TRUE; + + x = gdi->primary->hdc->hwnd->invalid->x; + y = gdi->primary->hdc->hwnd->invalid->y; + w = gdi->primary->hdc->hwnd->invalid->w; + h = gdi->primary->hdc->hwnd->invalid->h; + context_w = (wlfContext*)context; + if (!wl_update_buffer(context_w, x, y, w, h)) + { + return FALSE; + } + + gdi->primary->hdc->hwnd->invalid->null = TRUE; + gdi->primary->hdc->hwnd->ninvalid = 0; + return TRUE; +} + +static BOOL wl_refresh_display(wlfContext* context) +{ + rdpGdi* gdi = NULL; + + if (!context || !context->common.context.gdi) + return FALSE; + + gdi = context->common.context.gdi; + return wl_update_buffer(context, 0, 0, gdi->width, gdi->height); +} + +static BOOL wl_resize_display(rdpContext* context) +{ + wlfContext* wlc = (wlfContext*)context; + rdpGdi* gdi = context->gdi; + rdpSettings* settings = context->settings; + + if (!gdi_resize(gdi, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight))) + return FALSE; + + return wl_refresh_display(wlc); +} + +static BOOL wl_pre_connect(freerdp* instance) +{ + rdpSettings* settings = NULL; + wlfContext* context = NULL; + const UwacOutput* output = NULL; + UwacSize resolution; + + if (!instance) + return FALSE; + + context = (wlfContext*)instance->context; + WINPR_ASSERT(context); + + settings = instance->context->settings; + WINPR_ASSERT(settings); + + if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMajorType, OSMAJORTYPE_UNIX)) + return FALSE; + if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMinorType, OSMINORTYPE_NATIVE_WAYLAND)) + return FALSE; + PubSub_SubscribeChannelConnected(instance->context->pubSub, wlf_OnChannelConnectedEventHandler); + PubSub_SubscribeChannelDisconnected(instance->context->pubSub, + wlf_OnChannelDisconnectedEventHandler); + + if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen)) + { + // Use the resolution of the first display output + output = UwacDisplayGetOutput(context->display, 0); + + if ((output != NULL) && (UwacOutputGetResolution(output, &resolution) == UWAC_SUCCESS)) + { + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, + (UINT32)resolution.width)) + return FALSE; + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, + (UINT32)resolution.height)) + return FALSE; + } + else + { + WLog_WARN(TAG, "Failed to get output resolution! Check your display settings"); + } + } + + return TRUE; +} + +static BOOL wl_post_connect(freerdp* instance) +{ + rdpGdi* gdi = NULL; + UwacWindow* window = NULL; + wlfContext* context = NULL; + rdpSettings* settings = NULL; + char* title = "FreeRDP"; + char* app_id = "wlfreerdp"; + UINT32 w = 0; + UINT32 h = 0; + + if (!instance || !instance->context) + return FALSE; + + context = (wlfContext*)instance->context; + settings = instance->context->settings; + + const char* wtitle = freerdp_settings_get_string(settings, FreeRDP_WindowTitle); + if (wtitle) + title = wtitle; + + if (!gdi_init(instance, PIXEL_FORMAT_BGRA32)) + return FALSE; + + gdi = instance->context->gdi; + + if (!gdi || (gdi->width < 0) || (gdi->height < 0)) + return FALSE; + + if (!wlf_register_pointer(instance->context->graphics)) + return FALSE; + + w = (UINT32)gdi->width; + h = (UINT32)gdi->height; + + if (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) && !context->fullscreen) + { + const UINT32 sw = freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingWidth); + if (sw > 0) + w = sw; + + const UINT32 sh = freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingHeight); + if (sh > 0) + h = sh; + } + + context->window = window = UwacCreateWindowShm(context->display, w, h, WL_SHM_FORMAT_XRGB8888); + + if (!window) + return FALSE; + + UwacWindowSetFullscreenState( + window, NULL, freerdp_settings_get_bool(instance->context->settings, FreeRDP_Fullscreen)); + UwacWindowSetTitle(window, title); + UwacWindowSetAppId(window, app_id); + UwacWindowSetOpaqueRegion(context->window, 0, 0, w, h); + instance->context->update->EndPaint = wl_end_paint; + instance->context->update->DesktopResize = wl_resize_display; + UINT32 KeyboardLayout = + freerdp_settings_get_uint32(instance->context->settings, FreeRDP_KeyboardLayout); + const char* KeyboardRemappingList = + freerdp_settings_get_string(instance->context->settings, FreeRDP_KeyboardRemappingList); + + freerdp_keyboard_init_ex(KeyboardLayout, KeyboardRemappingList); + + if (!(context->disp = wlf_disp_new(context))) + return FALSE; + + context->clipboard = wlf_clipboard_new(context); + + if (!context->clipboard) + return FALSE; + + return wl_refresh_display(context); +} + +static void wl_post_disconnect(freerdp* instance) +{ + wlfContext* context = NULL; + + if (!instance) + return; + + if (!instance->context) + return; + + context = (wlfContext*)instance->context; + gdi_free(instance); + wlf_clipboard_free(context->clipboard); + wlf_disp_free(context->disp); + + if (context->window) + UwacDestroyWindow(&context->window); +} + +static BOOL handle_uwac_events(freerdp* instance, UwacDisplay* display) +{ + UwacEvent event; + wlfContext* context = NULL; + + if (UwacDisplayDispatch(display, 1) < 0) + return FALSE; + + context = (wlfContext*)instance->context; + + while (UwacHasEvent(display)) + { + if (UwacNextEvent(display, &event) != UWAC_SUCCESS) + return FALSE; + + /*printf("UWAC event type %d\n", event.type);*/ + switch (event.type) + { + case UWAC_EVENT_NEW_SEAT: + context->seat = event.seat_new.seat; + break; + + case UWAC_EVENT_REMOVED_SEAT: + context->seat = NULL; + break; + + case UWAC_EVENT_FRAME_DONE: + { + EnterCriticalSection(&context->critical); + UwacReturnCode r = UwacWindowSubmitBuffer(context->window, false); + LeaveCriticalSection(&context->critical); + if (r != UWAC_SUCCESS) + return FALSE; + } + break; + + case UWAC_EVENT_POINTER_ENTER: + if (!wlf_handle_pointer_enter(instance, &event.mouse_enter_leave)) + return FALSE; + + break; + + case UWAC_EVENT_POINTER_MOTION: + if (!wlf_handle_pointer_motion(instance, &event.mouse_motion)) + return FALSE; + + break; + + case UWAC_EVENT_POINTER_BUTTONS: + if (!wlf_handle_pointer_buttons(instance, &event.mouse_button)) + return FALSE; + + break; + + case UWAC_EVENT_POINTER_AXIS: + if (!wlf_handle_pointer_axis(instance, &event.mouse_axis)) + return FALSE; + break; + + case UWAC_EVENT_POINTER_AXIS_DISCRETE: + if (!wlf_handle_pointer_axis_discrete(instance, &event.mouse_axis)) + return FALSE; + break; + + case UWAC_EVENT_POINTER_FRAME: + if (!wlf_handle_pointer_frame(instance, &event.mouse_frame)) + return FALSE; + break; + case UWAC_EVENT_POINTER_SOURCE: + if (!wlf_handle_pointer_source(instance, &event.mouse_source)) + return FALSE; + break; + + case UWAC_EVENT_KEY: + if (!wlf_handle_key(instance, &event.key)) + return FALSE; + + break; + + case UWAC_EVENT_TOUCH_UP: + if (!wlf_handle_touch_up(instance, &event.touchUp)) + return FALSE; + + break; + + case UWAC_EVENT_TOUCH_DOWN: + if (!wlf_handle_touch_down(instance, &event.touchDown)) + return FALSE; + + break; + + case UWAC_EVENT_TOUCH_MOTION: + if (!wlf_handle_touch_motion(instance, &event.touchMotion)) + return FALSE; + + break; + + case UWAC_EVENT_KEYBOARD_ENTER: + if (freerdp_settings_get_bool(instance->context->settings, FreeRDP_GrabKeyboard)) + UwacSeatInhibitShortcuts(event.keyboard_enter_leave.seat, true); + + if (!wlf_keyboard_enter(instance, &event.keyboard_enter_leave)) + return FALSE; + + break; + + case UWAC_EVENT_KEYBOARD_MODIFIERS: + if (!wlf_keyboard_modifiers(instance, &event.keyboard_modifiers)) + return FALSE; + + break; + + case UWAC_EVENT_CONFIGURE: + if (!wlf_disp_handle_configure(context->disp, event.configure.width, + event.configure.height)) + return FALSE; + + if (!wl_refresh_display(context)) + return FALSE; + + 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; + + case UWAC_EVENT_CLOSE: + context->closed = TRUE; + + break; + + default: + break; + } + } + + return TRUE; +} + +static BOOL handle_window_events(freerdp* instance) +{ + if (!instance) + return FALSE; + + return TRUE; +} + +static int wlfreerdp_run(freerdp* instance) +{ + wlfContext* context = NULL; + HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 }; + DWORD status = WAIT_ABANDONED; + HANDLE timer = NULL; + LARGE_INTEGER due; + + TimerEventArgs timerEvent; + EventArgsInit(&timerEvent, "xfreerdp"); + + if (!instance) + return -1; + + context = (wlfContext*)instance->context; + + if (!context) + return -1; + + if (!freerdp_connect(instance)) + { + WLog_Print(context->log, WLOG_ERROR, "Failed to connect"); + return -1; + } + + timer = CreateWaitableTimerA(NULL, FALSE, "mainloop-periodic-timer"); + + if (!timer) + { + WLog_ERR(TAG, "failed to create timer"); + goto disconnect; + } + + due.QuadPart = 0; + + if (!SetWaitableTimer(timer, &due, 20, NULL, NULL, FALSE)) + { + goto disconnect; + } + + while (!freerdp_shall_disconnect_context(instance->context)) + { + DWORD count = 0; + handles[count++] = timer; + handles[count++] = context->displayHandle; + count += freerdp_get_event_handles(instance->context, &handles[count], + ARRAYSIZE(handles) - count); + + if (count <= 2) + { + WLog_Print(context->log, WLOG_ERROR, "Failed to get FreeRDP file descriptor"); + break; + } + + status = WaitForMultipleObjects(count, handles, FALSE, INFINITE); + + if (WAIT_FAILED == status) + { + WLog_Print(context->log, WLOG_ERROR, "WaitForMultipleObjects failed"); + break; + } + + if (!handle_uwac_events(instance, context->display)) + { + WLog_Print(context->log, WLOG_ERROR, "error handling UWAC events"); + break; + } + + if (context->closed) + { + WLog_Print(context->log, WLOG_INFO, "Closed from Wayland"); + break; + } + + if (freerdp_check_event_handles(instance->context) != TRUE) + { + if (client_auto_reconnect_ex(instance, handle_window_events)) + continue; + else + { + /* + * Indicate an unsuccessful connection attempt if reconnect + * did not succeed and no other error was specified. + */ + if (freerdp_error_info(instance) == 0) + status = 42; + } + + if (freerdp_get_last_error(instance->context) == FREERDP_ERROR_SUCCESS) + WLog_Print(context->log, WLOG_ERROR, "Failed to check FreeRDP file descriptor"); + + break; + } + + if ((status != WAIT_TIMEOUT) && (status == WAIT_OBJECT_0)) + { + timerEvent.now = GetTickCount64(); + PubSub_OnTimer(context->common.context.pubSub, context, &timerEvent); + } + } + +disconnect: + if (timer) + CloseHandle(timer); + freerdp_disconnect(instance); + return status; +} + +static BOOL wlf_client_global_init(void) +{ + setlocale(LC_ALL, ""); + + if (freerdp_handle_signals() != 0) + return FALSE; + + return TRUE; +} + +static void wlf_client_global_uninit(void) +{ +} + +static int wlf_logon_error_info(freerdp* instance, UINT32 data, UINT32 type) +{ + wlfContext* wlf = NULL; + const char* str_data = freerdp_get_logon_error_info_data(data); + const char* str_type = freerdp_get_logon_error_info_type(type); + + if (!instance || !instance->context) + return -1; + + wlf = (wlfContext*)instance->context; + WLog_Print(wlf->log, WLOG_INFO, "Logon Error Info %s [%s]", str_data, str_type); + return 1; +} + +static void wlf_client_free(freerdp* instance, rdpContext* context) +{ + wlfContext* wlf = (wlfContext*)instance->context; + + if (!context) + return; + + if (wlf->display) + UwacCloseDisplay(&wlf->display); + + if (wlf->displayHandle) + CloseHandle(wlf->displayHandle); + ArrayList_Free(wlf->events); + DeleteCriticalSection(&wlf->critical); +} + +static void* uwac_event_clone(const void* val) +{ + UwacEvent* copy = NULL; + const UwacEvent* ev = (const UwacEvent*)val; + + copy = calloc(1, sizeof(UwacEvent)); + if (!copy) + return NULL; + *copy = *ev; + return copy; +} + +static BOOL wlf_client_new(freerdp* instance, rdpContext* context) +{ + wObject* obj = NULL; + UwacReturnCode status = UWAC_ERROR_INTERNAL; + wlfContext* wfl = (wlfContext*)context; + + if (!instance || !context) + return FALSE; + + instance->PreConnect = wl_pre_connect; + instance->PostConnect = wl_post_connect; + instance->PostDisconnect = wl_post_disconnect; + instance->LogonErrorInfo = wlf_logon_error_info; + wfl->log = WLog_Get(TAG); + wfl->display = UwacOpenDisplay(NULL, &status); + + if (!wfl->display || (status != UWAC_SUCCESS) || !wfl->log) + return FALSE; + + wfl->displayHandle = CreateFileDescriptorEvent(NULL, FALSE, FALSE, + UwacDisplayGetFd(wfl->display), WINPR_FD_READ); + + if (!wfl->displayHandle) + return FALSE; + + wfl->events = ArrayList_New(FALSE); + if (!wfl->events) + return FALSE; + + obj = ArrayList_Object(wfl->events); + obj->fnObjectNew = uwac_event_clone; + obj->fnObjectFree = free; + + InitializeCriticalSection(&wfl->critical); + + return TRUE; +} + +static int wfl_client_start(rdpContext* context) +{ + WINPR_UNUSED(context); + return 0; +} + +static int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints) +{ + WINPR_ASSERT(pEntryPoints); + ZeroMemory(pEntryPoints, sizeof(RDP_CLIENT_ENTRY_POINTS)); + pEntryPoints->Version = RDP_CLIENT_INTERFACE_VERSION; + pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1); + pEntryPoints->GlobalInit = wlf_client_global_init; + pEntryPoints->GlobalUninit = wlf_client_global_uninit; + pEntryPoints->ContextSize = sizeof(wlfContext); + pEntryPoints->ClientNew = wlf_client_new; + pEntryPoints->ClientFree = wlf_client_free; + pEntryPoints->ClientStart = wfl_client_start; + pEntryPoints->ClientStop = freerdp_client_common_stop; + return 0; +} + +int main(int argc, char* argv[]) +{ + int rc = -1; + int status = 0; + RDP_CLIENT_ENTRY_POINTS clientEntryPoints; + rdpContext* context = NULL; + rdpSettings* settings = NULL; + wlfContext* wlc = NULL; + + freerdp_client_warn_deprecated(argc, argv); + + RdpClientEntry(&clientEntryPoints); + context = freerdp_client_context_new(&clientEntryPoints); + if (!context) + goto fail; + wlc = (wlfContext*)context; + settings = context->settings; + + status = freerdp_client_settings_parse_command_line(settings, argc, argv, FALSE); + if (status) + { + rc = freerdp_client_settings_command_line_status_print(settings, status, argc, argv); + + if (freerdp_settings_get_bool(settings, FreeRDP_ListMonitors)) + wlf_list_monitors(wlc); + + goto fail; + } + + if (freerdp_client_start(context) != 0) + goto fail; + + rc = wlfreerdp_run(context->instance); + + if (freerdp_client_stop(context) != 0) + rc = -1; + +fail: + freerdp_client_context_free(context); + return rc; +} + +BOOL wlf_copy_image(const void* src, size_t srcStride, size_t srcWidth, size_t srcHeight, void* dst, + size_t dstStride, size_t dstWidth, size_t dstHeight, const RECTANGLE_16* area, + BOOL scale) +{ + BOOL rc = FALSE; + + if (!src || !dst || !area) + return FALSE; + + if (scale) + { + return freerdp_image_scale(dst, PIXEL_FORMAT_BGRA32, dstStride, 0, 0, dstWidth, dstHeight, + src, PIXEL_FORMAT_BGRA32, srcStride, 0, 0, srcWidth, srcHeight); + } + else + { + const size_t baseSrcOffset = area->top * srcStride + area->left * 4; + const size_t baseDstOffset = area->top * dstStride + area->left * 4; + const size_t width = MIN((size_t)area->right - area->left, dstWidth - area->left); + const size_t height = MIN((size_t)area->bottom - area->top, dstHeight - area->top); + const BYTE* psrc = (const BYTE*)src; + BYTE* pdst = (BYTE*)dst; + + for (size_t i = 0; i < height; i++) + { + const size_t srcOffset = i * srcStride + baseSrcOffset; + const size_t dstOffset = i * dstStride + baseDstOffset; + memcpy(&pdst[dstOffset], &psrc[srcOffset], width * 4); + } + + rc = TRUE; + } + + return rc; +} + +BOOL wlf_scale_coordinates(rdpContext* context, UINT32* px, UINT32* py, BOOL fromLocalToRDP) +{ + wlfContext* wlf = (wlfContext*)context; + rdpGdi* gdi = NULL; + UwacSize geometry; + double sx = NAN; + double sy = NAN; + + if (!context || !px || !py || !context->gdi) + return FALSE; + + if (!freerdp_settings_get_bool(context->settings, FreeRDP_SmartSizing)) + return TRUE; + + gdi = context->gdi; + + if (UwacWindowGetDrawingBufferGeometry(wlf->window, &geometry, NULL) != UWAC_SUCCESS) + return FALSE; + + sx = geometry.width / (double)gdi->width; + sy = geometry.height / (double)gdi->height; + + if (!fromLocalToRDP) + { + *px *= sx; + *py *= sy; + } + else + { + *px /= sx; + *py /= sy; + } + + return TRUE; +} diff --git a/client/Wayland/wlfreerdp.h b/client/Wayland/wlfreerdp.h new file mode 100644 index 0000000..e1a4650 --- /dev/null +++ b/client/Wayland/wlfreerdp.h @@ -0,0 +1,59 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Client + * + * Copyright 2014 Manuel Bachmann <tarnyko@tarnyko.net> + * + * 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_FREERDP_H +#define FREERDP_CLIENT_WAYLAND_FREERDP_H + +#include <freerdp/client/rdpei.h> +#include <freerdp/gdi/gfx.h> +#include <freerdp/freerdp.h> +#include <freerdp/log.h> +#include <winpr/wtypes.h> +#include <uwac/uwac.h> + +typedef struct wlf_clipboard wfClipboard; +typedef struct s_wlfDispContext wlfDispContext; + +typedef struct +{ + rdpClientContext common; + + UwacDisplay* display; + HANDLE displayHandle; + UwacWindow* window; + UwacSeat* seat; + + BOOL fullscreen; + BOOL closed; + BOOL focusing; + + /* Channels */ + wfClipboard* clipboard; + wlfDispContext* disp; + wLog* log; + CRITICAL_SECTION critical; + wArrayList* events; +} wlfContext; + +BOOL wlf_scale_coordinates(rdpContext* context, UINT32* px, UINT32* py, BOOL fromLocalToRDP); +BOOL wlf_copy_image(const void* src, size_t srcStride, size_t srcWidth, size_t srcHeight, void* dst, + size_t dstStride, size_t dstWidth, size_t dstHeight, const RECTANGLE_16* area, + BOOL scale); + +#endif /* FREERDP_CLIENT_WAYLAND_FREERDP_H */ |