summaryrefslogtreecommitdiffstats
path: root/client/Wayland
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--client/Wayland/CMakeLists.txt62
-rw-r--r--client/Wayland/wlf_channels.c79
-rw-r--r--client/Wayland/wlf_channels.h37
-rw-r--r--client/Wayland/wlf_cliprdr.c1009
-rw-r--r--client/Wayland/wlf_cliprdr.h36
-rw-r--r--client/Wayland/wlf_disp.c455
-rw-r--r--client/Wayland/wlf_disp.h38
-rw-r--r--client/Wayland/wlf_input.c458
-rw-r--r--client/Wayland/wlf_input.h45
-rw-r--r--client/Wayland/wlf_pointer.c167
-rw-r--r--client/Wayland/wlf_pointer.h28
-rw-r--r--client/Wayland/wlfreerdp.1.in38
-rw-r--r--client/Wayland/wlfreerdp.c807
-rw-r--r--client/Wayland/wlfreerdp.h59
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 */