summaryrefslogtreecommitdiffstats
path: root/plugins/rdp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 17:06:32 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 17:06:32 +0000
commit2dad5357405ad33cfa792f04b3ab62a5d188841e (patch)
treeb8f8893942060fe3cfb04ac374cda96fdfc8f453 /plugins/rdp
parentInitial commit. (diff)
downloadremmina-upstream/1.4.34+dfsg.tar.xz
remmina-upstream/1.4.34+dfsg.zip
Adding upstream version 1.4.34+dfsg.upstream/1.4.34+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'plugins/rdp')
-rw-r--r--plugins/rdp/CMakeLists.txt88
-rw-r--r--plugins/rdp/rdp_channels.c96
-rw-r--r--plugins/rdp/rdp_channels.h62
-rw-r--r--plugins/rdp/rdp_cliprdr.c1034
-rw-r--r--plugins/rdp/rdp_cliprdr.h51
-rw-r--r--plugins/rdp/rdp_event.c1494
-rw-r--r--plugins/rdp/rdp_event.h58
-rw-r--r--plugins/rdp/rdp_file.c299
-rw-r--r--plugins/rdp/rdp_file.h48
-rw-r--r--plugins/rdp/rdp_graphics.c163
-rw-r--r--plugins/rdp/rdp_graphics.h40
-rw-r--r--plugins/rdp/rdp_monitor.c231
-rw-r--r--plugins/rdp/rdp_monitor.h45
-rw-r--r--plugins/rdp/rdp_plugin.c3363
-rw-r--r--plugins/rdp/rdp_plugin.h400
-rw-r--r--plugins/rdp/rdp_settings.c759
-rw-r--r--plugins/rdp/rdp_settings.h49
-rw-r--r--plugins/rdp/scalable/emblems/org.remmina.Remmina-rdp-ssh-symbolic.svg89
-rw-r--r--plugins/rdp/scalable/emblems/org.remmina.Remmina-rdp-symbolic.svg112
19 files changed, 8481 insertions, 0 deletions
diff --git a/plugins/rdp/CMakeLists.txt b/plugins/rdp/CMakeLists.txt
new file mode 100644
index 0000000..da4798b
--- /dev/null
+++ b/plugins/rdp/CMakeLists.txt
@@ -0,0 +1,88 @@
+# remmina-plugin-rdp - The GTK+ Remote Desktop Client
+#
+# Copyright (C) 2011 Marc-Andre Moreau
+# Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+# Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+#
+# In addition, as a special exception, the copyright holders give
+# permission to link the code of portions of this program with the
+# OpenSSL library under certain conditions as described in each
+# individual source file, and distribute linked combinations
+# including the two.
+# You must obey the GNU General Public License in all respects
+# for all of the code used other than OpenSSL. If you modify
+# file(s) with this exception, you may extend this exception to your
+# version of the file(s), but you are not obligated to do so. If you
+# do not wish to do so, delete this exception statement from your
+# version. If you delete this exception statement from all source
+# files in the program, then also delete it here.
+
+
+set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
+find_package(Threads REQUIRED)
+
+find_suggested_package(Cups)
+
+set(REMMINA_PLUGIN_RDP_SRCS
+ rdp_plugin.c
+ rdp_plugin.h
+ rdp_event.c
+ rdp_event.h
+ rdp_file.c
+ rdp_file.h
+ rdp_settings.c
+ rdp_settings.h
+ rdp_graphics.c
+ rdp_graphics.h
+ rdp_cliprdr.c
+ rdp_cliprdr.h
+ rdp_monitor.c
+ rdp_monitor.h
+ rdp_channels.c
+ rdp_channels.h
+ )
+
+add_definitions(-DFREERDP_REQUIRED_MAJOR=${FREERDP_REQUIRED_MAJOR})
+add_definitions(-DFREERDP_REQUIRED_MINOR=${FREERDP_REQUIRED_MINOR})
+add_definitions(-DFREERDP_REQUIRED_REVISION=${FREERDP_REQUIRED_REVISION})
+
+add_library(remmina-plugin-rdp MODULE ${REMMINA_PLUGIN_RDP_SRCS})
+set_target_properties(remmina-plugin-rdp PROPERTIES PREFIX "")
+set_target_properties(remmina-plugin-rdp PROPERTIES NO_SONAME 1)
+
+include_directories(${REMMINA_COMMON_INCLUDE_DIRS} ${FreeRDP-Client_INCLUDE_DIR} ${FreeRDP_INCLUDE_DIR} ${WinPR_INCLUDE_DIR})
+target_link_libraries(remmina-plugin-rdp
+ ${REMMINA_COMMON_LIBRARIES} freerdp-client)
+
+if(CUPS_FOUND)
+ add_definitions(-DHAVE_CUPS)
+ include_directories(${CUPS_INCLUDE_DIR})
+ target_link_libraries(remmina-plugin-rdp
+ ${REMMINA_COMMON_LIBRARIES} freerdp-client ${CUPS_LIBRARIES})
+endif()
+
+install(TARGETS remmina-plugin-rdp DESTINATION ${REMMINA_PLUGINDIR})
+
+install(FILES
+ scalable/emblems/org.remmina.Remmina-rdp-ssh-symbolic.svg
+ scalable/emblems/org.remmina.Remmina-rdp-symbolic.svg
+ DESTINATION ${APPICONSCALE_EMBLEMS_DIR})
+
+if(WITH_ICON_CACHE)
+ gtk_update_icon_cache("${REMMINA_DATADIR}/icons/hicolor")
+endif()
diff --git a/plugins/rdp/rdp_channels.c b/plugins/rdp/rdp_channels.c
new file mode 100644
index 0000000..fabb25f
--- /dev/null
+++ b/plugins/rdp/rdp_channels.c
@@ -0,0 +1,96 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2012-2012 Jean-Louis Dupond
+ * Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL. * If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. * If you
+ * do not wish to do so, delete this exception statement from your
+ * version. * If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ *
+ */
+
+#include "rdp_plugin.h"
+#include "rdp_cliprdr.h"
+#include "rdp_channels.h"
+#include "rdp_event.h"
+
+#include <freerdp/freerdp.h>
+#include <freerdp/channels/channels.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/gdi/gfx.h>
+
+void remmina_rdp_OnChannelConnectedEventHandler(void *context, CONST_ARG ChannelConnectedEventArgs *e)
+{
+ TRACE_CALL(__func__);
+
+ rfContext* rfi = (rfContext*)context;
+
+ if (g_strcmp0(e->name, RDPEI_DVC_CHANNEL_NAME) == 0) {
+ g_print("Unimplemented: channel %s connected but we can’t use it\n", e->name);
+ // xfc->rdpei = (RdpeiClientContext*) e->pInterface;
+ }else if (g_strcmp0(e->name, TSMF_DVC_CHANNEL_NAME) == 0) {
+ g_print("Unimplemented: channel %s connected but we can’t use it\n", e->name);
+ // xf_tsmf_init(xfc, (TsmfClientContext*) e->pInterface);
+ }else if (g_strcmp0(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0) {
+ if (freerdp_settings_get_bool(rfi->clientContext.context.settings, FreeRDP_SoftwareGdi)) {
+ rfi->rdpgfxchan = TRUE;
+ gdi_graphics_pipeline_init(rfi->clientContext.context.gdi, (RdpgfxClientContext*) e->pInterface);
+ }
+ else
+ g_print("Unimplemented: channel %s connected but libfreerdp is in HardwareGdi mode\n", e->name);
+ }else if (g_strcmp0(e->name, RAIL_SVC_CHANNEL_NAME) == 0) {
+ g_print("Unimplemented: channel %s connected but we can’t use it\n", e->name);
+ // xf_rail_init(xfc, (RailClientContext*) e->pInterface);
+ }else if (g_strcmp0(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0) {
+ remmina_rdp_cliprdr_init( rfi, (CliprdrClientContext*)e->pInterface);
+ }else if (g_strcmp0(e->name, ENCOMSP_SVC_CHANNEL_NAME) == 0) {
+ g_print("Unimplemented: channel %s connected but we can’t use it\n", e->name);
+ // xf_encomsp_init(xfc, (EncomspClientContext*) e->pInterface);
+ }else if (g_strcmp0(e->name, DISP_DVC_CHANNEL_NAME) == 0) {
+ // "disp" channel connected, save its context pointer
+ rfi->dispcontext = (DispClientContext*)e->pInterface;
+ // Notify rcw to unlock dynres capability
+ remmina_plugin_service->protocol_plugin_unlock_dynres(rfi->protocol_widget);
+ // Send monitor layout message here to ask for resize of remote desktop now
+ if (rfi->scale == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES) {
+ remmina_rdp_event_send_delayed_monitor_layout(rfi->protocol_widget);
+ }
+ }
+ REMMINA_PLUGIN_DEBUG("Channel %s has been opened", e->name);
+}
+
+void remmina_rdp_OnChannelDisconnectedEventHandler(void *context, CONST_ARG ChannelDisconnectedEventArgs *e) {
+ TRACE_CALL(__func__);
+ rfContext* rfi = (rfContext*)context;
+
+ if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0) {
+ if (freerdp_settings_get_bool(rfi->clientContext.context.settings, FreeRDP_SoftwareGdi))
+ gdi_graphics_pipeline_uninit(rfi->clientContext.context.gdi, (RdpgfxClientContext*) e->pInterface);
+ }
+ REMMINA_PLUGIN_DEBUG("Channel %s has been closed", e->name);
+
+}
diff --git a/plugins/rdp/rdp_channels.h b/plugins/rdp/rdp_channels.h
new file mode 100644
index 0000000..f614610
--- /dev/null
+++ b/plugins/rdp/rdp_channels.h
@@ -0,0 +1,62 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2012-2012 Jean-Louis Dupond
+ * Copyright (C) 2017-2023 Antenore Gatta, Giovanni Panozzo
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL. * If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. * If you
+ * do not wish to do so, delete this exception statement from your
+ * version. * If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ *
+ */
+
+
+#pragma once
+
+#include <glib.h>
+#include <freerdp/version.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/client/channels.h>
+#include <freerdp/client/rdpei.h>
+#include <freerdp/client/tsmf.h>
+#include <freerdp/client/rail.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/client/rdpgfx.h>
+#include <freerdp/client/encomsp.h>
+
+G_BEGIN_DECLS
+
+#if FREERDP_VERSION_MAJOR >= 3
+#define CONST_ARG const
+#else
+#define CONST_ARG
+#endif
+
+void remmina_rdp_OnChannelConnectedEventHandler(void *context, CONST_ARG ChannelConnectedEventArgs *e);
+void remmina_rdp_OnChannelDisconnectedEventHandler(void *context, CONST_ARG ChannelDisconnectedEventArgs *e);
+
+
+G_END_DECLS
diff --git a/plugins/rdp/rdp_cliprdr.c b/plugins/rdp/rdp_cliprdr.c
new file mode 100644
index 0000000..d42aa35
--- /dev/null
+++ b/plugins/rdp/rdp_cliprdr.c
@@ -0,0 +1,1034 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2012-2012 Jean-Louis Dupond
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL. * If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. * If you
+ * do not wish to do so, delete this exception statement from your
+ * version. * If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ *
+ */
+
+#include "rdp_plugin.h"
+#include "rdp_cliprdr.h"
+#include "rdp_event.h"
+
+#include <freerdp/freerdp.h>
+#include <freerdp/channels/channels.h>
+#include <freerdp/client/cliprdr.h>
+#include <sys/time.h>
+
+#define CLIPBOARD_TRANSFER_WAIT_TIME 6
+
+#define CB_FORMAT_HTML 0xD010
+#define CB_FORMAT_PNG 0xD011
+#define CB_FORMAT_JPEG 0xD012
+#define CB_FORMAT_GIF 0xD013
+#define CB_FORMAT_TEXTURILIST 0xD014
+
+UINT32 remmina_rdp_cliprdr_get_format_from_gdkatom(GdkAtom atom)
+{
+ TRACE_CALL(__func__);
+ UINT32 rc;
+ gchar *name = gdk_atom_name(atom);
+ rc = 0;
+ if (g_strcmp0("UTF8_STRING", name) == 0 || g_strcmp0("text/plain;charset=utf-8", name) == 0)
+ rc = CF_UNICODETEXT;
+ if (g_strcmp0("TEXT", name) == 0 || g_strcmp0("text/plain", name) == 0)
+ rc = CF_TEXT;
+ if (g_strcmp0("text/html", name) == 0)
+ rc = CB_FORMAT_HTML;
+ if (g_strcmp0("image/png", name) == 0)
+ rc = CB_FORMAT_PNG;
+ if (g_strcmp0("image/jpeg", name) == 0)
+ rc = CB_FORMAT_JPEG;
+ if (g_strcmp0("image/bmp", name) == 0)
+ rc = CF_DIB;
+ if (g_strcmp0("text/uri-list", name) == 0)
+ rc = CB_FORMAT_TEXTURILIST;
+ g_free(name);
+ return rc;
+}
+
+/* Never used? */
+void remmina_rdp_cliprdr_get_target_types(UINT32 **formats, UINT16 *size, GdkAtom *types, int count)
+{
+ TRACE_CALL(__func__);
+ int i;
+ *size = 1;
+ *formats = (UINT32 *)malloc(sizeof(UINT32) * (count + 1));
+
+ *formats[0] = 0;
+ for (i = 0; i < count; i++) {
+ UINT32 format = remmina_rdp_cliprdr_get_format_from_gdkatom(types[i]);
+ if (format != 0) {
+ (*formats)[*size] = format;
+ (*size)++;
+ }
+ }
+
+ *formats = realloc(*formats, sizeof(UINT32) * (*size));
+}
+
+static UINT8 *lf2crlf(UINT8 *data, int *size)
+{
+ TRACE_CALL(__func__);
+ UINT8 c;
+ UINT8 *outbuf;
+ UINT8 *out;
+ UINT8 *in_end;
+ UINT8 *in;
+ int out_size;
+
+ out_size = (*size) * 2 + 1;
+ outbuf = (UINT8 *)malloc(out_size);
+ out = outbuf;
+ in = data;
+ in_end = data + (*size);
+
+ while (in < in_end) {
+ c = *in++;
+ if (c == '\n') {
+ *out++ = '\r';
+ *out++ = '\n';
+ } else {
+ *out++ = c;
+ }
+ }
+
+ *out++ = 0;
+ *size = out - outbuf;
+
+ return outbuf;
+}
+
+static void crlf2lf(UINT8 *data, size_t *size)
+{
+ TRACE_CALL(__func__);
+ UINT8 c;
+ UINT8 *out;
+ UINT8 *in;
+ UINT8 *in_end;
+
+ out = data;
+ in = data;
+ in_end = data + (*size);
+
+ while (in < in_end) {
+ c = *in++;
+ if (c != '\r')
+ *out++ = c;
+ }
+
+ *size = out - data;
+}
+
+/* Never used? */
+int remmina_rdp_cliprdr_server_file_contents_request(CliprdrClientContext *context, CLIPRDR_FILE_CONTENTS_REQUEST *fileContentsRequest)
+{
+ TRACE_CALL(__func__);
+ return -1;
+}
+
+/* Never used? */
+int remmina_rdp_cliprdr_server_file_contents_response(CliprdrClientContext *context, CLIPRDR_FILE_CONTENTS_RESPONSE *fileContentsResponse)
+{
+ TRACE_CALL(__func__);
+ return 1;
+}
+
+void remmina_rdp_cliprdr_send_client_format_list(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginRdpUiObject *ui;
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+ rfClipboard *clipboard;
+ CLIPRDR_FORMAT_LIST *pFormatList;
+ RemminaPluginRdpEvent rdp_event = { 0 };
+
+ if (!rfi || !rfi->connected || rfi->is_reconnecting)
+ return;
+
+ clipboard = &(rfi->clipboard);
+
+ ui = g_new0(RemminaPluginRdpUiObject, 1);
+ ui->type = REMMINA_RDP_UI_CLIPBOARD;
+ ui->clipboard.clipboard = clipboard;
+ ui->clipboard.type = REMMINA_RDP_UI_CLIPBOARD_FORMATLIST;
+ pFormatList = remmina_rdp_event_queue_ui_sync_retptr(gp, ui);
+
+ rdp_event.type = REMMINA_RDP_EVENT_TYPE_CLIPBOARD_SEND_CLIENT_FORMAT_LIST;
+ rdp_event.clipboard_formatlist.pFormatList = pFormatList;
+ remmina_rdp_event_event_push(gp, &rdp_event);
+}
+
+static void remmina_rdp_cliprdr_send_client_capabilities(rfClipboard *clipboard)
+{
+ TRACE_CALL(__func__);
+ CLIPRDR_CAPABILITIES capabilities;
+ CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet;
+
+ capabilities.cCapabilitiesSets = 1;
+ capabilities.capabilitySets = (CLIPRDR_CAPABILITY_SET *)&(generalCapabilitySet);
+
+ generalCapabilitySet.capabilitySetType = CB_CAPSTYPE_GENERAL;
+ generalCapabilitySet.capabilitySetLength = 12;
+
+ generalCapabilitySet.version = CB_CAPS_VERSION_2;
+ generalCapabilitySet.generalFlags = CB_USE_LONG_FORMAT_NAMES;
+
+ clipboard->context->ClientCapabilities(clipboard->context, &capabilities);
+}
+
+static UINT remmina_rdp_cliprdr_monitor_ready(CliprdrClientContext *context, const CLIPRDR_MONITOR_READY *monitorReady)
+{
+ TRACE_CALL(__func__);
+ rfClipboard *clipboard = (rfClipboard *)context->custom;
+ RemminaProtocolWidget *gp;
+
+ remmina_rdp_cliprdr_send_client_capabilities(clipboard);
+ gp = clipboard->rfi->protocol_widget;
+ remmina_rdp_cliprdr_send_client_format_list(gp);
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT remmina_rdp_cliprdr_server_capabilities(CliprdrClientContext *context, const CLIPRDR_CAPABILITIES *capabilities)
+{
+ TRACE_CALL(__func__);
+ return CHANNEL_RC_OK;
+}
+
+
+void remmina_rdp_cliprdr_cached_clipboard_free(rfClipboard *clipboard)
+{
+ TRACE_CALL(__func__);
+ guint fmt;
+
+ pthread_mutex_lock(&clipboard->srv_data_mutex);
+ if (clipboard->srv_data != NULL) {
+ fmt = clipboard->format;
+ if (fmt == CB_FORMAT_PNG || fmt == CF_DIB || fmt == CF_DIBV5 || fmt == CB_FORMAT_JPEG) {
+ g_object_unref(clipboard->srv_data);
+ } else {
+ free(clipboard->srv_data);
+ }
+ clipboard->srv_data = NULL;
+ }
+ pthread_mutex_unlock(&clipboard->srv_data_mutex);
+}
+
+
+static UINT remmina_rdp_cliprdr_server_format_list(CliprdrClientContext *context, const CLIPRDR_FORMAT_LIST *formatList)
+{
+ TRACE_CALL(__func__);
+
+ /* Called when a user do a "Copy" on the server: we collect all formats
+ * the server send us and then setup the local clipboard with the appropriate
+ * targets to request server data */
+
+ RemminaPluginRdpUiObject *ui;
+ RemminaProtocolWidget *gp;
+ rfClipboard *clipboard;
+ CLIPRDR_FORMAT *format;
+ CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse;
+ UINT rc;
+
+ int has_dib_level = 0;
+
+ int i;
+
+ clipboard = (rfClipboard *)context->custom;
+ gp = clipboard->rfi->protocol_widget;
+
+ REMMINA_PLUGIN_DEBUG("gp=%p: Received a new ServerFormatList from server clipboard. Remmina version = %s",
+ gp, VERSION);
+
+ GtkTargetList *list = gtk_target_list_new(NULL, 0);
+
+ if (clipboard->srv_clip_data_wait == SCDW_BUSY_WAIT) {
+ REMMINA_PLUGIN_DEBUG("gp=%p: we already have a FormatDataRequest in progress to the server, aborting", gp);
+ remmina_rdp_clipboard_abort_client_format_data_request(clipboard->rfi);
+ }
+
+ remmina_rdp_cliprdr_cached_clipboard_free(clipboard);
+ clipboard->server_html_format_id = 0;
+
+ REMMINA_PLUGIN_DEBUG("gp=%p: format list from the server:", gp);
+ for (i = 0; i < formatList->numFormats; i++) {
+ format = &formatList->formats[i];
+ const char *serverFormatName = format->formatName;
+ gchar *gtkFormatName = NULL;
+ if (format->formatId == CF_UNICODETEXT) {
+ serverFormatName = "CF_UNICODETEXT";
+ gtkFormatName = "text/plain;charset=utf-8";
+ GdkAtom atom = gdk_atom_intern(gtkFormatName, TRUE);
+ gtk_target_list_add(list, atom, 0, CF_UNICODETEXT);
+ /* Add also the older UTF8_STRING format for older applications */
+ atom = gdk_atom_intern("UTF8_STRING", TRUE);
+ gtk_target_list_add(list, atom, 0, CF_UNICODETEXT);
+ } else if (format->formatId == CF_TEXT) {
+ serverFormatName = "CF_TEXT";
+ gtkFormatName = "text/plain";
+ GdkAtom atom = gdk_atom_intern(gtkFormatName, TRUE);
+ gtk_target_list_add(list, atom, 0, CF_TEXT);
+ /* Add also the older TEXT format for older applications */
+ atom = gdk_atom_intern("TEXT", TRUE);
+ gtk_target_list_add(list, atom, 0, CF_TEXT);
+ } else if (format->formatId == CF_DIB) {
+ serverFormatName = "CF_DIB";
+ if (has_dib_level < 1)
+ has_dib_level = 1;
+ } else if (format->formatId == CF_DIBV5) {
+ serverFormatName = "CF_DIBV5";
+ if (has_dib_level < 5)
+ has_dib_level = 5;
+ } else if (format->formatId == CB_FORMAT_JPEG) {
+ serverFormatName = "CB_FORMAT_JPEG";
+ gtkFormatName = "image/jpeg";
+ GdkAtom atom = gdk_atom_intern(gtkFormatName, TRUE);
+ gtk_target_list_add(list, atom, 0, CB_FORMAT_JPEG);
+ } else if (format->formatId == CB_FORMAT_PNG) {
+ serverFormatName = "CB_FORMAT_PNG";
+ gtkFormatName = "image/png";
+ GdkAtom atom = gdk_atom_intern(gtkFormatName, TRUE);
+ gtk_target_list_add(list, atom, 0, CB_FORMAT_PNG);
+ } else if (format->formatId == CB_FORMAT_HTML) {
+ serverFormatName = "CB_FORMAT_HTML";
+ gtkFormatName = "text/html";
+ GdkAtom atom = gdk_atom_intern(gtkFormatName, TRUE);
+ gtk_target_list_add(list, atom, 0, CB_FORMAT_HTML);
+ } else if (format->formatId == CB_FORMAT_TEXTURILIST) {
+ serverFormatName = "CB_FORMAT_TEXTURILIST";
+ gtkFormatName = "text/uri-list";
+ GdkAtom atom = gdk_atom_intern(gtkFormatName, TRUE);
+ gtk_target_list_add(list, atom, 0, CB_FORMAT_TEXTURILIST);
+ } else if (format->formatId == CF_LOCALE) {
+ serverFormatName = "CF_LOCALE";
+ } else if (format->formatId == CF_METAFILEPICT) {
+ serverFormatName = "CF_METAFILEPICT";
+ } else if (serverFormatName != NULL && strcmp(serverFormatName, "HTML Format") == 0) {
+ gtkFormatName = "text/html";
+ GdkAtom atom = gdk_atom_intern(gtkFormatName, TRUE);
+ gtk_target_list_add(list, atom, 0, format->formatId);
+ clipboard->server_html_format_id = format->formatId;
+ }
+ REMMINA_PLUGIN_DEBUG("the server has clipboard format %d: %s -> GTK %s", format->formatId, serverFormatName, gtkFormatName);
+ }
+
+ /* Keep only one DIB format, if present */
+ if (has_dib_level) {
+ GdkAtom atom = gdk_atom_intern("image/bmp", TRUE);
+ if (has_dib_level == 5)
+ gtk_target_list_add(list, atom, 0, CF_DIBV5);
+ else
+ gtk_target_list_add(list, atom, 0, CF_DIB);
+ }
+
+
+ REMMINA_PLUGIN_DEBUG("gp=%p: sending ClientFormatListResponse to server", gp);
+#if FREERDP_VERSION_MAJOR >= 3
+ formatListResponse.common.msgType = CB_FORMAT_LIST_RESPONSE;
+ formatListResponse.common.msgFlags = CB_RESPONSE_OK; // Can be CB_RESPONSE_FAIL in case of error
+ formatListResponse.common.dataLen = 0;
+#else
+ formatListResponse.msgType = CB_FORMAT_LIST_RESPONSE;
+ formatListResponse.msgFlags = CB_RESPONSE_OK; // Can be CB_RESPONSE_FAIL in case of error
+ formatListResponse.dataLen = 0;
+#endif
+ rc = clipboard->context->ClientFormatListResponse(clipboard->context, &formatListResponse);
+ /* Schedule GTK event to tell GTK to change the local clipboard calling gtk_clipboard_set_with_owner
+ * via REMMINA_RDP_UI_CLIPBOARD_SET_DATA
+ * GTK will immediately fire an "owner-change" event, that we should ignore.
+ * And if we are putting text on the clipboard, mutter or other clipboard
+ * managers may try immediately request a clipboard transfer. */
+
+ /* Ensure we have at least one target into list, or Gtk will not change owner
+ when setting clipboard owner later.
+ We put it directly in the clipboard cache (clipboard->srv_data),
+ so remmina will never ask it to the server via ClientFormatDataRequest */
+ gint n_targets;
+ GtkTargetEntry *target_table = gtk_target_table_new_from_list(list, &n_targets);
+ if (target_table)
+ gtk_target_table_free(target_table, n_targets);
+ if (n_targets == 0) {
+ REMMINA_PLUGIN_DEBUG("gp=%p adding a dummy text target (empty text) for local clipboard, because we have no interesting targets from the server. Putting it in the local clipboard cache.");
+ GdkAtom atom = gdk_atom_intern("text/plain;charset=utf-8", TRUE);
+ gtk_target_list_add(list, atom, 0, CF_UNICODETEXT);
+ pthread_mutex_lock(&clipboard->srv_data_mutex);
+ clipboard->srv_data = malloc(1);
+ ((char *)(clipboard->srv_data))[0] = 0;
+ pthread_mutex_unlock(&clipboard->srv_data_mutex);
+ clipboard->format = CF_UNICODETEXT;
+ }
+
+
+ ui = g_new0(RemminaPluginRdpUiObject, 1);
+ ui->type = REMMINA_RDP_UI_CLIPBOARD;
+ ui->clipboard.clipboard = clipboard;
+ ui->clipboard.type = REMMINA_RDP_UI_CLIPBOARD_SET_DATA;
+ ui->clipboard.targetlist = list;
+ remmina_rdp_event_queue_ui_async(gp, ui);
+
+ REMMINA_PLUGIN_DEBUG("gp=%p: processing of ServerFormatList ended, returning rc=%u to libfreerdp", gp, rc);
+ return rc;
+}
+
+static UINT remmina_rdp_cliprdr_server_format_list_response(CliprdrClientContext *context, const CLIPRDR_FORMAT_LIST_RESPONSE *formatListResponse)
+{
+ TRACE_CALL(__func__);
+ return CHANNEL_RC_OK;
+}
+
+static UINT remmina_rdp_cliprdr_server_format_data_request(CliprdrClientContext *context, const CLIPRDR_FORMAT_DATA_REQUEST *formatDataRequest)
+{
+ TRACE_CALL(__func__);
+
+ RemminaPluginRdpUiObject *ui;
+ RemminaProtocolWidget *gp;
+ rfClipboard *clipboard;
+
+ clipboard = (rfClipboard *)context->custom;
+ gp = clipboard->rfi->protocol_widget;
+
+ ui = g_new0(RemminaPluginRdpUiObject, 1);
+ ui->type = REMMINA_RDP_UI_CLIPBOARD;
+ ui->clipboard.clipboard = clipboard;
+ ui->clipboard.type = REMMINA_RDP_UI_CLIPBOARD_GET_DATA;
+ ui->clipboard.format = formatDataRequest->requestedFormatId;
+ remmina_rdp_event_queue_ui_sync_retint(gp, ui);
+
+ return CHANNEL_RC_OK;
+}
+
+int timeval_diff(struct timeval *start, struct timeval *end)
+{
+ /* Returns the time elapsed from start to end, in ms */
+ int64_t ms_end = (end->tv_sec * 1000) + (end->tv_usec / 1000);
+ int64_t ms_start = (start->tv_sec * 1000) + (start->tv_usec / 1000);
+
+ return (int)(ms_end - ms_start);
+}
+
+
+static UINT remmina_rdp_cliprdr_server_format_data_response(CliprdrClientContext *context, const CLIPRDR_FORMAT_DATA_RESPONSE *formatDataResponse)
+{
+ TRACE_CALL(__func__);
+
+ const UINT8 *data;
+ size_t size;
+ rfContext *rfi;
+ RemminaProtocolWidget *gp;
+ rfClipboard *clipboard;
+ gpointer output = NULL;
+ struct timeval now;
+ int mstrans;
+
+ clipboard = (rfClipboard *)context->custom;
+ gp = clipboard->rfi->protocol_widget;
+ rfi = GET_PLUGIN_DATA(gp);
+
+ data = formatDataResponse->requestedFormatData;
+#if FREERDP_VERSION_MAJOR >= 3
+ size = formatDataResponse->common.dataLen;
+#else
+ size = formatDataResponse->dataLen;
+#endif
+
+ REMMINA_PLUGIN_DEBUG("gp=%p server FormatDataResponse received: clipboard data arrived form server.", gp);
+ gettimeofday(&now, NULL);
+ remmina_rdp_cliprdr_cached_clipboard_free(clipboard);
+
+ /* Calculate stats */
+ mstrans = timeval_diff(&(clipboard->clientformatdatarequest_tv), &now);
+ REMMINA_PLUGIN_DEBUG("gp=%p %zu bytes transferred from server in %d ms. Speed is %d bytes/sec",
+ gp, (size_t)size, mstrans, mstrans != 0 ? (int)((int64_t)size * 1000 / mstrans) : 0);
+
+ if (size > 0) {
+ switch (rfi->clipboard.format) {
+ case CF_UNICODETEXT:
+ {
+ output =
+ g_utf16_to_utf8((const WCHAR *)data, size / sizeof(WCHAR), NULL, NULL, NULL);
+ if (output) {
+ size = strlen(output) + 1;
+ crlf2lf(output, &size);
+ }
+ break;
+ }
+
+ case CF_TEXT:
+ case CB_FORMAT_HTML:
+ {
+ output = (gpointer)calloc(1, size + 1);
+ if (output) {
+ memcpy(output, data, size);
+ crlf2lf(output, &size);
+ }
+ break;
+ }
+
+ case CF_DIBV5:
+ case CF_DIB:
+ {
+ wStream *s;
+ UINT32 offset;
+ GError *perr;
+ BITMAPINFOHEADER *pbi;
+ BITMAPV5HEADER *pbi5;
+
+ pbi = (BITMAPINFOHEADER *)data;
+
+ // offset calculation inspired by http://downloads.poolelan.com/MSDN/MSDNLibrary6/Disk1/Samples/VC/OS/WindowsXP/GetImage/BitmapUtil.cpp
+ offset = 14 + pbi->biSize;
+ if (pbi->biClrUsed != 0)
+ offset += sizeof(RGBQUAD) * pbi->biClrUsed;
+ else if (pbi->biBitCount <= 8)
+ offset += sizeof(RGBQUAD) * (1 << pbi->biBitCount);
+ if (pbi->biSize == sizeof(BITMAPINFOHEADER)) {
+ if (pbi->biCompression == 3) // BI_BITFIELDS is 3
+ offset += 12;
+ } else if (pbi->biSize >= sizeof(BITMAPV5HEADER)) {
+ pbi5 = (BITMAPV5HEADER *)pbi;
+ if (pbi5->bV5ProfileData <= offset)
+ offset += pbi5->bV5ProfileSize;
+ }
+ s = Stream_New(NULL, 14 + size);
+ Stream_Write_UINT8(s, 'B');
+ Stream_Write_UINT8(s, 'M');
+ Stream_Write_UINT32(s, 14 + size);
+ Stream_Write_UINT32(s, 0);
+ Stream_Write_UINT32(s, offset);
+ Stream_Write(s, data, size);
+
+ data = Stream_Buffer(s);
+ size = Stream_Length(s);
+
+ GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
+ perr = NULL;
+ if (!gdk_pixbuf_loader_write(loader, data, size, &perr)) {
+ Stream_Free(s, TRUE);
+ g_warning("[RDP] rdp_cliprdr: gdk_pixbuf_loader_write() returned error %s\n", perr->message);
+ } else {
+ if (!gdk_pixbuf_loader_close(loader, &perr)) {
+ g_warning("[RDP] rdp_cliprdr: gdk_pixbuf_loader_close() returned error %s\n", perr->message);
+ perr = NULL;
+ }
+ Stream_Free(s, TRUE);
+ output = g_object_ref(gdk_pixbuf_loader_get_pixbuf(loader));
+ }
+ g_object_unref(loader);
+ break;
+ }
+
+ case CB_FORMAT_PNG:
+ case CB_FORMAT_JPEG:
+ {
+ GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
+ gdk_pixbuf_loader_write(loader, data, size, NULL);
+ output = g_object_ref(gdk_pixbuf_loader_get_pixbuf(loader));
+ gdk_pixbuf_loader_close(loader, NULL);
+ g_object_unref(loader);
+ break;
+ }
+ default:
+ {
+ if (rfi->clipboard.format == clipboard->server_html_format_id) {
+ /* Converting from Microsoft HTML Clipboard Format to pure text/html
+ * https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format */
+ int p = 0, lstart = 0;
+ size_t osize;
+ char c;
+ /* Find the 1st starting with '<' */
+ while(p < size && (c = data[p]) != 0) {
+ if (c == '<' && p == lstart) break;
+ if (c == '\n') lstart = p + 1;
+ p++;
+ }
+ if (p < size) {
+ osize = size - lstart;
+ output = (gpointer)calloc(1, osize + 1);
+ if (output) {
+ memcpy(output, data + lstart, osize);
+ ((char *)output)[osize] = 0;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ pthread_mutex_lock(&clipboard->srv_data_mutex);
+ clipboard->srv_data = output;
+ pthread_mutex_unlock(&clipboard->srv_data_mutex);
+
+ if (output != NULL)
+ REMMINA_PLUGIN_DEBUG("gp=%p: clipboard local cache data has been loaded", gp);
+ else
+ REMMINA_PLUGIN_DEBUG("gp=%p: data from server is not valid (size=%zu format=%d), cannot load into cache", gp, size, rfi->clipboard.format);
+
+
+ REMMINA_PLUGIN_DEBUG("gp=%p: signalling main GTK thread that we have some clipboard data.", gp);
+
+ pthread_mutex_lock(&clipboard->transfer_clip_mutex);
+ pthread_cond_signal(&clipboard->transfer_clip_cond);
+ if (clipboard->srv_clip_data_wait == SCDW_BUSY_WAIT) {
+ REMMINA_PLUGIN_DEBUG("gp=%p: clipboard transfer from server completed.", gp);
+ } else {
+ // Clipboard data arrived from server when we are not busy waiting on main loop
+ // Unfortunately, we must discard it
+ REMMINA_PLUGIN_DEBUG("gp=%p: clipboard transfer from server completed, but no local application is requesting it. Data is on local cache now, try to paste later.", gp);
+ }
+ clipboard->srv_clip_data_wait = SCDW_NONE;
+ pthread_mutex_unlock(&clipboard->transfer_clip_mutex);
+
+ return CHANNEL_RC_OK;
+}
+
+
+void remmina_rdp_cliprdr_request_data(GtkClipboard *gtkClipboard, GtkSelectionData *selection_data, guint info, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+
+ /* Called by GTK when someone presses "Paste" on the client side.
+ * We ask to the server the data we need */
+
+ CLIPRDR_FORMAT_DATA_REQUEST *pFormatDataRequest;
+ rfClipboard *clipboard;
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+ RemminaPluginRdpEvent rdp_event = { 0 };
+ struct timespec to;
+ struct timeval tv;
+ int rc;
+ time_t tlimit, tlimit1s, tnow, tstart;
+
+ REMMINA_PLUGIN_DEBUG("gp=%p: A local application has requested remote clipboard data for remote format id %d", gp, info);
+
+ clipboard = &(rfi->clipboard);
+ if (clipboard->srv_clip_data_wait != SCDW_NONE) {
+ g_message("[RDP] Cannot paste now, I’m already transferring clipboard data from server. Try again later\n");
+ return;
+ }
+
+ if (clipboard->format != info || clipboard->srv_data == NULL) {
+ /* We do not have a local cached clipoard, so we have to start a remote request */
+ remmina_rdp_cliprdr_cached_clipboard_free(clipboard);
+
+ clipboard->format = info;
+
+ pthread_mutex_lock(&clipboard->transfer_clip_mutex);
+
+ pFormatDataRequest = (CLIPRDR_FORMAT_DATA_REQUEST *)malloc(sizeof(CLIPRDR_FORMAT_DATA_REQUEST));
+ ZeroMemory(pFormatDataRequest, sizeof(CLIPRDR_FORMAT_DATA_REQUEST));
+ pFormatDataRequest->requestedFormatId = clipboard->format;
+
+ clipboard->srv_clip_data_wait = SCDW_BUSY_WAIT; // Annotate that we are waiting for ServerFormatDataResponse
+
+ REMMINA_PLUGIN_DEBUG("gp=%p Requesting clipboard data with format %d from the server via ServerFormatDataRequest", gp, clipboard->format);
+ rdp_event.type = REMMINA_RDP_EVENT_TYPE_CLIPBOARD_SEND_CLIENT_FORMAT_DATA_REQUEST;
+ rdp_event.clipboard_formatdatarequest.pFormatDataRequest = pFormatDataRequest;
+ remmina_rdp_event_event_push(gp, &rdp_event);
+
+ /* Busy wait clipboard data for CLIPBOARD_TRANSFER_WAIT_TIME seconds.
+ * In the meanwhile allow GTK event loop to proceed */
+
+ tstart = time(NULL);
+ tlimit = tstart + CLIPBOARD_TRANSFER_WAIT_TIME;
+ rc = 100000;
+ tlimit1s = tstart + 1;
+ while ((tnow = time(NULL)) < tlimit && rc != 0 && clipboard->srv_clip_data_wait == SCDW_BUSY_WAIT) {
+
+ if (tnow >= tlimit1s) {
+ REMMINA_PLUGIN_DEBUG("gp=%p, clipboard data is still not here after %u seconds", gp, (unsigned)(tnow - tstart));
+ tlimit1s = time(NULL) + 1;
+ }
+
+ gettimeofday(&tv, NULL);
+ to.tv_sec = tv.tv_sec;
+ to.tv_nsec = tv.tv_usec * 1000 + 5000000; // wait for data for 5ms
+ if (to.tv_nsec >= 1000000000) {
+ to.tv_nsec -= 1000000000;
+ to.tv_sec++;
+ }
+ rc = pthread_cond_timedwait(&clipboard->transfer_clip_cond, &clipboard->transfer_clip_mutex, &to);
+ if (rc == 0)
+ break;
+
+ gtk_main_iteration_do(FALSE);
+ }
+
+ if (rc != 0) {
+ /* Timeout, just log it and hope that data will arrive later */
+ if (clipboard->srv_clip_data_wait == SCDW_ABORTING) {
+ g_warning("[RDP] gp=%p Clipboard data wait aborted.",gp);
+ } else {
+ if (rc == ETIMEDOUT)
+ g_warning("[RDP] gp=%p Clipboard data from the server is not available in %d seconds. No data will be available to user.",
+ gp, CLIPBOARD_TRANSFER_WAIT_TIME);
+ else
+ g_warning("[RDP] gp=%p internal error: pthread_cond_timedwait() returned %d\n", gp, rc);
+ }
+ }
+ pthread_mutex_unlock(&clipboard->transfer_clip_mutex);
+
+ }
+
+ pthread_mutex_lock(&clipboard->srv_data_mutex);
+ if (clipboard->srv_data != NULL) {
+ REMMINA_PLUGIN_DEBUG("gp=%p pasting data to local application", gp);
+ /* We have data in cache, just paste it */
+ if (info == CB_FORMAT_PNG || info == CF_DIB || info == CF_DIBV5 || info == CB_FORMAT_JPEG) {
+ gtk_selection_data_set_pixbuf(selection_data, clipboard->srv_data);
+ } else if (info == CB_FORMAT_HTML || info == clipboard->server_html_format_id) {
+ REMMINA_PLUGIN_DEBUG("gp=%p returning %zu bytes of HTML in clipboard to requesting application", gp, strlen(clipboard->srv_data));
+ GdkAtom atom = gdk_atom_intern("text/html", TRUE);
+ gtk_selection_data_set(selection_data, atom, 8, clipboard->srv_data, strlen(clipboard->srv_data));
+ } else {
+ REMMINA_PLUGIN_DEBUG("gp=%p returning %zu bytes of text in clipboard to requesting application", gp, strlen(clipboard->srv_data));
+ gtk_selection_data_set_text(selection_data, clipboard->srv_data, -1);
+ }
+ clipboard->srv_clip_data_wait = SCDW_NONE;
+ } else {
+ REMMINA_PLUGIN_DEBUG("gp=%p cannot paste data to local application because ->srv_data is NULL", gp);
+ }
+ pthread_mutex_unlock(&clipboard->srv_data_mutex);
+
+}
+
+void remmina_rdp_cliprdr_empty_clipboard(GtkClipboard *gtkClipboard, rfClipboard *clipboard)
+{
+ TRACE_CALL(__func__);
+ /* No need to do anything here */
+}
+
+CLIPRDR_FORMAT_LIST *remmina_rdp_cliprdr_get_client_format_list(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+
+ GtkClipboard *gtkClipboard;
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+ GdkAtom *targets;
+ gboolean result = 0;
+ gint loccount, srvcount;
+ gint formatId, i;
+ CLIPRDR_FORMAT *formats;
+ struct retp_t {
+ CLIPRDR_FORMAT_LIST pFormatList;
+ CLIPRDR_FORMAT formats[];
+ } *retp;
+
+ formats = NULL;
+ retp = NULL;
+ loccount = 0;
+
+ gtkClipboard = gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD);
+ if (gtkClipboard)
+ result = gtk_clipboard_wait_for_targets(gtkClipboard, &targets, &loccount);
+ REMMINA_PLUGIN_DEBUG("gp=%p sending to server the following local clipboard content formats", gp);
+ if (result && loccount > 0) {
+ formats = (CLIPRDR_FORMAT *)malloc(loccount * sizeof(CLIPRDR_FORMAT));
+ srvcount = 0;
+ for (i = 0; i < loccount; i++) {
+ formatId = remmina_rdp_cliprdr_get_format_from_gdkatom(targets[i]);
+ if (formatId != 0) {
+ gchar *name = gdk_atom_name(targets[i]);
+ REMMINA_PLUGIN_DEBUG(" local clipboard format %s will be sent to remote as %d", name, formatId);
+ g_free(name);
+ formats[srvcount].formatId = formatId;
+ formats[srvcount].formatName = NULL;
+ srvcount++;
+ }
+ }
+ if (srvcount > 0) {
+ retp = (struct retp_t *)malloc(sizeof(struct retp_t) + sizeof(CLIPRDR_FORMAT) * srvcount);
+ retp->pFormatList.formats = retp->formats;
+ retp->pFormatList.numFormats = srvcount;
+ memcpy(retp->formats, formats, sizeof(CLIPRDR_FORMAT) * srvcount);
+ } else {
+ retp = (struct retp_t *)malloc(sizeof(struct retp_t));
+ retp->pFormatList.formats = NULL;
+ retp->pFormatList.numFormats = 0;
+ }
+ free(formats);
+ } else {
+ retp = (struct retp_t *)malloc(sizeof(struct retp_t) + sizeof(CLIPRDR_FORMAT));
+ retp->pFormatList.formats = NULL;
+ retp->pFormatList.numFormats = 0;
+ }
+
+ if (result)
+ g_free(targets);
+
+#if FREERDP_VERSION_MAJOR >= 3
+ retp->pFormatList.common.msgType = CB_FORMAT_LIST;
+ retp->pFormatList.common.msgFlags = 0;
+#else
+ retp->pFormatList.msgType = CB_FORMAT_LIST;
+ retp->pFormatList.msgFlags = 0;
+#endif
+
+ return (CLIPRDR_FORMAT_LIST *)retp;
+}
+
+static void remmina_rdp_cliprdr_mt_get_format_list(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
+{
+ TRACE_CALL(__func__);
+ ui->retptr = (void *)remmina_rdp_cliprdr_get_client_format_list(gp);
+}
+
+void remmina_rdp_cliprdr_get_clipboard_data(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
+{
+ TRACE_CALL(__func__);
+ GtkClipboard *gtkClipboard;
+ UINT8 *inbuf = NULL;
+ UINT8 *outbuf = NULL;
+#if FREERDP_VERSION_MAJOR >= 3
+ WCHAR *outbuf_wchar = NULL;
+#endif
+ GdkPixbuf *image = NULL;
+ size_t size = 0;
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+ RemminaPluginRdpEvent rdp_event = { 0 };
+
+ gtkClipboard = gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD);
+ if (gtkClipboard) {
+ switch (ui->clipboard.format) {
+ case CF_TEXT:
+ case CF_UNICODETEXT:
+ case CB_FORMAT_HTML:
+ {
+ inbuf = (UINT8 *)gtk_clipboard_wait_for_text(gtkClipboard);
+ break;
+ }
+
+ case CB_FORMAT_PNG:
+ case CB_FORMAT_JPEG:
+ case CF_DIB:
+ case CF_DIBV5:
+ {
+ image = gtk_clipboard_wait_for_image(gtkClipboard);
+ break;
+ }
+ }
+ }
+
+ /* No data received, send nothing */
+ if (inbuf != NULL || image != NULL) {
+ switch (ui->clipboard.format) {
+ case CF_TEXT:
+ case CB_FORMAT_HTML:
+ {
+ size = strlen((char *)inbuf);
+ outbuf = lf2crlf(inbuf, (int *) &size);
+ break;
+ }
+ case CF_UNICODETEXT:
+ {
+ size = strlen((const char *)inbuf);
+ inbuf = lf2crlf(inbuf, (int *) &size);
+#if FREERDP_VERSION_MAJOR >= 3
+ size_t len = 0;
+ outbuf_wchar = ConvertUtf8NToWCharAlloc((const char *)inbuf, (size_t)size, &len);
+ size = (len + 1) * sizeof(WCHAR);
+#else
+ const int rc = (ConvertToUnicode(CP_UTF8, 0, (CHAR *)inbuf, -1, (WCHAR **)&outbuf, 0)) * sizeof(WCHAR);
+ size = 0;
+ if (rc >= 0) {
+ size = (size_t)rc;
+ }
+#endif
+ g_free(inbuf);
+ break;
+ }
+ case CB_FORMAT_PNG:
+ {
+ gchar *data;
+ gsize buffersize;
+ gdk_pixbuf_save_to_buffer(image, &data, &buffersize, "png", NULL, NULL);
+ outbuf = (UINT8 *)malloc(buffersize);
+ memcpy(outbuf, data, buffersize);
+ size = buffersize;
+ g_object_unref(image);
+ break;
+ }
+ case CB_FORMAT_JPEG:
+ {
+ gchar *data;
+ gsize buffersize;
+ gdk_pixbuf_save_to_buffer(image, &data, &buffersize, "jpeg", NULL, NULL);
+ outbuf = (UINT8 *)malloc(buffersize);
+ memcpy(outbuf, data, buffersize);
+ size = buffersize;
+ g_object_unref(image);
+ break;
+ }
+ case CF_DIB:
+ case CF_DIBV5:
+ {
+ gchar *data;
+ gsize buffersize;
+ gdk_pixbuf_save_to_buffer(image, &data, &buffersize, "bmp", NULL, NULL);
+ size = buffersize - 14;
+ outbuf = (UINT8 *)malloc(size);
+ memcpy(outbuf, data + 14, size);
+ g_object_unref(image);
+ break;
+ }
+ }
+ }
+
+ rdp_event.type = REMMINA_RDP_EVENT_TYPE_CLIPBOARD_SEND_CLIENT_FORMAT_DATA_RESPONSE;
+ rdp_event.clipboard_formatdataresponse.size = (int)MIN(size, INT32_MAX);
+
+#if FREERDP_VERSION_MAJOR >= 3
+ // For unicode, use the wchar buffer
+ if (outbuf == NULL && outbuf_wchar != NULL) {
+ rdp_event.clipboard_formatdataresponse.data = (unsigned char *)outbuf_wchar;
+ }
+ else {
+ rdp_event.clipboard_formatdataresponse.data = (unsigned char *)outbuf;
+ }
+#else
+ rdp_event.clipboard_formatdataresponse.data = (unsigned char *)outbuf;
+#endif
+
+ remmina_rdp_event_event_push(gp, &rdp_event);
+}
+
+void remmina_rdp_cliprdr_set_clipboard_data(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
+{
+ TRACE_CALL(__func__);
+ GtkClipboard *gtkClipboard;
+ gint n_targets;
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+
+ gtkClipboard = gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD);
+ if (gtkClipboard) {
+ GtkTargetEntry *targets = gtk_target_table_new_from_list(ui->clipboard.targetlist, &n_targets);
+ if (!targets) {
+ /* If no targets exists, this is an internal error, because
+ * remmina_rdp_cliprdr_server_format_list() must have produced
+ * at least one target before calling REMMINA_RDP_UI_CLIPBOARD_SET_DATA */
+ g_warning("[RDP] internal error: no targets to insert into the local clipboard");
+ }
+
+ REMMINA_PLUGIN_DEBUG("setting clipboard with owner to me: %p", gp);
+ gtk_clipboard_set_with_owner(gtkClipboard, targets, n_targets,
+ (GtkClipboardGetFunc)remmina_rdp_cliprdr_request_data,
+ (GtkClipboardClearFunc)remmina_rdp_cliprdr_empty_clipboard, G_OBJECT(gp));
+ gtk_target_table_free(targets, n_targets);
+ }
+}
+
+void remmina_rdp_cliprdr_detach_owner(RemminaProtocolWidget *gp)
+{
+ /* When closing a rdp connection, we should check if gp is a clipboard owner.
+ * If it’s an owner, detach it from the clipboard */
+ TRACE_CALL(__func__);
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+ GtkClipboard *gtkClipboard;
+
+ if (!rfi || !rfi->drawing_area) return;
+
+ gtkClipboard = gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD);
+ if (gtkClipboard && gtk_clipboard_get_owner(gtkClipboard) == (GObject *)gp)
+ gtk_clipboard_clear(gtkClipboard);
+
+}
+
+void remmina_rdp_event_process_clipboard(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
+{
+ TRACE_CALL(__func__);
+
+ switch (ui->clipboard.type) {
+ case REMMINA_RDP_UI_CLIPBOARD_FORMATLIST:
+ remmina_rdp_cliprdr_mt_get_format_list(gp, ui);
+ break;
+
+ case REMMINA_RDP_UI_CLIPBOARD_GET_DATA:
+ remmina_rdp_cliprdr_get_clipboard_data(gp, ui);
+ break;
+
+ case REMMINA_RDP_UI_CLIPBOARD_SET_DATA:
+ remmina_rdp_cliprdr_set_clipboard_data(gp, ui);
+ break;
+
+ }
+}
+
+void remmina_rdp_clipboard_init(rfContext *rfi)
+{
+ TRACE_CALL(__func__);
+ // Future: initialize rfi->clipboard
+}
+void remmina_rdp_clipboard_free(rfContext *rfi)
+{
+ TRACE_CALL(__func__);
+
+ remmina_rdp_cliprdr_cached_clipboard_free(&(rfi->clipboard));
+
+}
+
+void remmina_rdp_clipboard_abort_client_format_data_request(rfContext *rfi)
+{
+ TRACE_CALL(__func__);
+ if (rfi && rfi->clipboard.srv_clip_data_wait == SCDW_BUSY_WAIT) {
+ REMMINA_PLUGIN_DEBUG("requesting clipboard data transfer from server to be ignored and busywait loop to exit");
+ /* Allow clipboard transfer from server to terminate */
+ rfi->clipboard.srv_clip_data_wait = SCDW_ABORTING;
+ usleep(100000);
+ }
+}
+
+void remmina_rdp_cliprdr_init(rfContext *rfi, CliprdrClientContext *cliprdr)
+{
+ TRACE_CALL(__func__);
+
+ rfClipboard *clipboard;
+ clipboard = &(rfi->clipboard);
+
+ rfi->clipboard.rfi = rfi;
+ cliprdr->custom = (void *)clipboard;
+
+ clipboard->context = cliprdr;
+ pthread_mutex_init(&clipboard->transfer_clip_mutex, NULL);
+ pthread_cond_init(&clipboard->transfer_clip_cond, NULL);
+ clipboard->srv_clip_data_wait = SCDW_NONE;
+
+ pthread_mutex_init(&clipboard->srv_data_mutex, NULL);
+
+ cliprdr->MonitorReady = remmina_rdp_cliprdr_monitor_ready;
+ cliprdr->ServerCapabilities = remmina_rdp_cliprdr_server_capabilities;
+ cliprdr->ServerFormatList = remmina_rdp_cliprdr_server_format_list;
+ cliprdr->ServerFormatListResponse = remmina_rdp_cliprdr_server_format_list_response;
+ cliprdr->ServerFormatDataRequest = remmina_rdp_cliprdr_server_format_data_request;
+ cliprdr->ServerFormatDataResponse = remmina_rdp_cliprdr_server_format_data_response;
+
+// cliprdr->ServerFileContentsRequest = remmina_rdp_cliprdr_server_file_contents_request;
+// cliprdr->ServerFileContentsResponse = remmina_rdp_cliprdr_server_file_contents_response;
+}
diff --git a/plugins/rdp/rdp_cliprdr.h b/plugins/rdp/rdp_cliprdr.h
new file mode 100644
index 0000000..56b58a8
--- /dev/null
+++ b/plugins/rdp/rdp_cliprdr.h
@@ -0,0 +1,51 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010-2011 Vic Lee
+ * Copyright (C) 2012-2012 Jean-Louis Dupond
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL. * If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. * If you
+ * do not wish to do so, delete this exception statement from your
+ * version. * If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ *
+ */
+
+#pragma once
+
+
+#include <freerdp/freerdp.h>
+#include "rdp_plugin.h"
+
+void remmina_rdp_clipboard_init(rfContext *rfi);
+void remmina_rdp_clipboard_free(rfContext *rfi);
+void remmina_rdp_cliprdr_init(rfContext *rfi, CliprdrClientContext *cliprdr);
+void remmina_rdp_channel_cliprdr_process(RemminaProtocolWidget *gp, wMessage *event);
+void remmina_rdp_event_process_clipboard(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui);
+CLIPRDR_FORMAT_LIST *remmina_rdp_cliprdr_get_client_format_list(RemminaProtocolWidget *gp);
+void remmina_rdp_cliprdr_detach_owner(RemminaProtocolWidget *gp);
+void remmina_rdp_clipboard_abort_client_format_data_request(rfContext *rfi);
diff --git a/plugins/rdp/rdp_event.c b/plugins/rdp/rdp_event.c
new file mode 100644
index 0000000..19b349c
--- /dev/null
+++ b/plugins/rdp/rdp_event.c
@@ -0,0 +1,1494 @@
+/*
+ * Remmina - The GTK Remote Desktop Client
+ * Copyright (C) 2010 Jay Sorg
+ * Copyright (C) 2010-2011 Vic Lee
+ * Copyright (C) 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * Copyright (C) 2016-2022 Antenore Gatta, Giovanni Panozzo
+ * Copyright (C) 2022-2023 Antenore Gatta, Giovanni Panozzo, Hiroyuki Tanaka
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL. * If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. * If you
+ * do not wish to do so, delete this exception statement from your
+ * version. * If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ *
+ */
+
+#include "rdp_cliprdr.h"
+#include "rdp_event.h"
+#include "rdp_monitor.h"
+#include "rdp_settings.h"
+#include <gdk/gdkkeysyms.h>
+#ifdef GDK_WINDOWING_X11
+#include <cairo/cairo-xlib.h>
+#else
+#include <cairo/cairo.h>
+#endif
+#include <freerdp/locale/keyboard.h>
+
+gboolean remmina_rdp_event_on_map(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+ rdpGdi *gdi;
+
+ if (rfi == NULL)
+ return false;
+
+ RemminaFile *remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ int do_suppress = !remmina_plugin_service->file_get_int(remminafile, "no-suppress", FALSE);
+
+ if (do_suppress) {
+ gdi = ((rdpContext *)rfi)->gdi;
+
+ REMMINA_PLUGIN_DEBUG("Map event received, disabling TS_SUPPRESS_OUTPUT_PDU ");
+ gdi_send_suppress_output(gdi, FALSE);
+ }
+
+ return FALSE;
+}
+
+gboolean remmina_rdp_event_on_unmap(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+ rdpGdi *gdi;
+
+ if (rfi == NULL)
+ return false;
+
+ GtkWidget *toplevel = gtk_widget_get_toplevel(GTK_WIDGET(gp));
+ GdkWindow *window = gtk_widget_get_window(toplevel);
+
+ if (gdk_window_get_fullscreen_mode(window) == GDK_FULLSCREEN_ON_ALL_MONITORS) {
+ REMMINA_PLUGIN_DEBUG("Unmap event received, but cannot enable TS_SUPPRESS_OUTPUT_PDU when in fullscreen");
+ return FALSE;
+ }
+
+ RemminaFile *remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ int do_suppress = !remmina_plugin_service->file_get_int(remminafile, "no-suppress", FALSE);
+
+ if (do_suppress) {
+ gdi = ((rdpContext *)rfi)->gdi;
+
+ REMMINA_PLUGIN_DEBUG("Unmap event received, enabling TS_SUPPRESS_OUTPUT_PDU ");
+ gdi_send_suppress_output(gdi, TRUE);
+ }
+
+ return FALSE;
+}
+
+static gboolean remmina_rdp_event_on_focus_in(GtkWidget *widget, GdkEventKey *event, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+ rdpInput *input;
+ GdkModifierType state;
+
+#if GTK_CHECK_VERSION(3, 20, 0)
+ GdkSeat *seat;
+#else
+ GdkDeviceManager *manager;
+#endif
+ GdkDevice *keyboard = NULL;
+
+ const gchar *wname = gtk_widget_get_name(gtk_widget_get_toplevel(widget));
+ REMMINA_PLUGIN_DEBUG("Top level name is: %s", wname);
+
+ if (!rfi || !rfi->connected || rfi->is_reconnecting)
+ return FALSE;
+
+ input = rfi->clientContext.context.input;
+ UINT32 toggle_keys_state = 0;
+
+#if GTK_CHECK_VERSION(3, 20, 0)
+ seat = gdk_display_get_default_seat(gdk_display_get_default());
+ keyboard = gdk_seat_get_pointer(seat);
+#else
+ manager = gdk_display_get_device_manager(gdk_display_get_default());
+ keyboard = gdk_device_manager_get_client_pointer(manager);
+#endif
+ gdk_window_get_device_position(gdk_get_default_root_window(), keyboard, NULL, NULL, &state);
+
+ if (state & GDK_LOCK_MASK)
+ toggle_keys_state |= KBD_SYNC_CAPS_LOCK;
+ if (state & GDK_MOD2_MASK)
+ toggle_keys_state |= KBD_SYNC_NUM_LOCK;
+ if (state & GDK_MOD5_MASK)
+ toggle_keys_state |= KBD_SYNC_SCROLL_LOCK;
+
+ input->SynchronizeEvent(input, toggle_keys_state);
+ input->KeyboardEvent(input, KBD_FLAGS_RELEASE, 0x0F);
+
+ return FALSE;
+}
+
+void remmina_rdp_event_event_push(RemminaProtocolWidget *gp, const RemminaPluginRdpEvent *e)
+{
+ TRACE_CALL(__func__);
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+ RemminaPluginRdpEvent *event;
+
+ /* Called by the main GTK thread to send an event to the libfreerdp thread */
+
+ if (!rfi || !rfi->connected || rfi->is_reconnecting)
+ return;
+
+ if (rfi->event_queue) {
+#if GLIB_CHECK_VERSION(2,67,3)
+ event = g_memdup2(e, sizeof(RemminaPluginRdpEvent));
+#else
+ event = g_memdup(e, sizeof(RemminaPluginRdpEvent));
+#endif
+ g_async_queue_push(rfi->event_queue, event);
+
+ if (write(rfi->event_pipe[1], "\0", 1)) {
+ }
+ }
+}
+
+static void remmina_rdp_event_release_all_keys(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+ RemminaPluginRdpEvent rdp_event = { 0 };
+ int i;
+
+ /* Send all release key events for previously pressed keys */
+ for (i = 0; i < rfi->pressed_keys->len; i++) {
+ rdp_event = g_array_index(rfi->pressed_keys, RemminaPluginRdpEvent, i);
+ if ((rdp_event.type == REMMINA_RDP_EVENT_TYPE_SCANCODE ||
+ rdp_event.type == REMMINA_RDP_EVENT_TYPE_SCANCODE_UNICODE) &&
+ rdp_event.key_event.up == false) {
+ rdp_event.key_event.up = true;
+ remmina_rdp_event_event_push(gp, &rdp_event);
+ }
+ }
+
+ g_array_set_size(rfi->pressed_keys, 0);
+}
+
+static void remmina_rdp_event_release_key(RemminaProtocolWidget *gp, RemminaPluginRdpEvent rdp_event)
+{
+ TRACE_CALL(__func__);
+ gint i;
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+ RemminaPluginRdpEvent rdp_event_2 = { 0 };
+
+ rdp_event_2.type = REMMINA_RDP_EVENT_TYPE_SCANCODE;
+
+ if ((rdp_event.type == REMMINA_RDP_EVENT_TYPE_SCANCODE ||
+ rdp_event.type == REMMINA_RDP_EVENT_TYPE_SCANCODE_UNICODE) &&
+ rdp_event.key_event.up) {
+ /* Unregister the keycode only */
+ for (i = 0; i < rfi->pressed_keys->len; i++) {
+ rdp_event_2 = g_array_index(rfi->pressed_keys, RemminaPluginRdpEvent, i);
+
+ if (rdp_event_2.key_event.key_code == rdp_event.key_event.key_code &&
+ rdp_event_2.key_event.unicode_code == rdp_event.key_event.unicode_code &&
+ rdp_event_2.key_event.extended == rdp_event.key_event.extended &&
+ rdp_event_2.key_event.extended1 == rdp_event.key_event.extended1) {
+ g_array_remove_index_fast(rfi->pressed_keys, i);
+ break;
+ }
+ }
+ }
+}
+
+static void keypress_list_add(RemminaProtocolWidget *gp, RemminaPluginRdpEvent rdp_event)
+{
+ TRACE_CALL(__func__);
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+
+ if (!rdp_event.key_event.key_code)
+ return;
+
+ if (rdp_event.key_event.up)
+ remmina_rdp_event_release_key(gp, rdp_event);
+ else
+ g_array_append_val(rfi->pressed_keys, rdp_event);
+}
+
+
+static void remmina_rdp_event_scale_area(RemminaProtocolWidget *gp, gint *x, gint *y, gint *w, gint *h)
+{
+ TRACE_CALL(__func__);
+ gint width, height;
+ gint sx, sy, sw, sh;
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+
+ if (!rfi || !rfi->connected || rfi->is_reconnecting || !rfi->surface)
+ return;
+
+ width = remmina_plugin_service->protocol_plugin_get_width(gp);
+ height = remmina_plugin_service->protocol_plugin_get_height(gp);
+
+ if ((width == 0) || (height == 0))
+ return;
+
+ if ((rfi->scale_width == width) && (rfi->scale_height == height)) {
+ /* Same size, just copy the pixels */
+ *x = MIN(MAX(0, *x), width - 1);
+ *y = MIN(MAX(0, *y), height - 1);
+ *w = MIN(width - *x, *w);
+ *h = MIN(height - *y, *h);
+ return;
+ }
+
+ /* We have to extend the scaled region one scaled pixel, to avoid gaps */
+
+ sx = MIN(MAX(0, (*x) * rfi->scale_width / width
+ - rfi->scale_width / width - 2), rfi->scale_width - 1);
+
+ sy = MIN(MAX(0, (*y) * rfi->scale_height / height
+ - rfi->scale_height / height - 2), rfi->scale_height - 1);
+
+ sw = MIN(rfi->scale_width - sx, (*w) * rfi->scale_width / width
+ + rfi->scale_width / width + 4);
+
+ sh = MIN(rfi->scale_height - sy, (*h) * rfi->scale_height / height
+ + rfi->scale_height / height + 4);
+
+ *x = sx;
+ *y = sy;
+ *w = sw;
+ *h = sh;
+}
+
+void remmina_rdp_event_update_regions(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
+{
+ TRACE_CALL(__func__);
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+ gint x, y, w, h, i;
+
+ for (i = 0; i < ui->reg.ninvalid; i++) {
+ x = ui->reg.ureg[i].x;
+ y = ui->reg.ureg[i].y;
+ w = ui->reg.ureg[i].w;
+ h = ui->reg.ureg[i].h;
+
+ if (rfi->scale == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED)
+ remmina_rdp_event_scale_area(gp, &x, &y, &w, &h);
+
+ gtk_widget_queue_draw_area(rfi->drawing_area, x, y, w, h);
+ }
+ g_free(ui->reg.ureg);
+}
+
+void remmina_rdp_event_update_rect(RemminaProtocolWidget *gp, gint x, gint y, gint w, gint h)
+{
+ TRACE_CALL(__func__);
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+
+ if (rfi->scale == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED)
+ remmina_rdp_event_scale_area(gp, &x, &y, &w, &h);
+
+ gtk_widget_queue_draw_area(rfi->drawing_area, x, y, w, h);
+}
+
+static void remmina_rdp_event_update_scale_factor(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ GtkAllocation a;
+ gint rdwidth, rdheight;
+ gint gpwidth, gpheight;
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+
+ gtk_widget_get_allocation(GTK_WIDGET(gp), &a);
+ gpwidth = a.width;
+ gpheight = a.height;
+
+ if (rfi->scale == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED) {
+ if ((gpwidth > 1) && (gpheight > 1)) {
+ rdwidth = remmina_plugin_service->protocol_plugin_get_width(gp);
+ rdheight = remmina_plugin_service->protocol_plugin_get_height(gp);
+
+ rfi->scale_width = gpwidth;
+ rfi->scale_height = gpheight;
+
+ rfi->scale_x = (gdouble)rfi->scale_width / (gdouble)rdwidth;
+ rfi->scale_y = (gdouble)rfi->scale_height / (gdouble)rdheight;
+ }
+ } else {
+ rfi->scale_width = 0;
+ rfi->scale_height = 0;
+ rfi->scale_x = 0;
+ rfi->scale_y = 0;
+ }
+}
+
+static gboolean remmina_rdp_event_on_draw(GtkWidget *widget, cairo_t *context, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+ guint width, height;
+ gchar *msg;
+ cairo_text_extents_t extents;
+
+ if (!rfi || !rfi->connected)
+ return FALSE;
+
+
+ if (rfi->is_reconnecting) {
+ /* FreeRDP is reconnecting, just show a message to the user */
+
+ width = gtk_widget_get_allocated_width(widget);
+ height = gtk_widget_get_allocated_height(widget);
+
+ /* Draw text */
+ msg = g_strdup_printf(_("Reconnection attempt %d of %d…"),
+ rfi->reconnect_nattempt, rfi->reconnect_maxattempts);
+
+ cairo_select_font_face(context, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
+ cairo_set_font_size(context, 24);
+ cairo_set_source_rgb(context, 0.9, 0.9, 0.9);
+ cairo_text_extents(context, msg, &extents);
+ cairo_move_to(context, (width - (extents.width + extents.x_bearing)) / 2, (height - (extents.height + extents.y_bearing)) / 2);
+ cairo_show_text(context, msg);
+ g_free(msg);
+ } else {
+ /* Standard drawing: We copy the surface from RDP */
+
+ if (!rfi->surface)
+ return FALSE;
+
+ if (rfi->scale == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED)
+ cairo_scale(context, rfi->scale_x, rfi->scale_y);
+
+ cairo_surface_flush(rfi->surface);
+ cairo_set_source_surface(context, rfi->surface, 0, 0);
+ cairo_surface_mark_dirty(rfi->surface);
+
+ cairo_set_operator(context, CAIRO_OPERATOR_SOURCE); // Ignore alpha channel from FreeRDP
+ cairo_paint(context);
+ }
+
+ return TRUE;
+}
+
+static gboolean remmina_rdp_event_delayed_monitor_layout(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+
+ RemminaPluginRdpEvent rdp_event = { 0 };
+ GtkAllocation a;
+ gint desktopOrientation, desktopScaleFactor, deviceScaleFactor;
+
+ RemminaFile *remminafile;
+
+ if (!rfi || !rfi->connected || rfi->is_reconnecting)
+ return FALSE;
+
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ if (rfi->scale != REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES)
+ return FALSE;
+
+ rfi->delayed_monitor_layout_handler = 0;
+ gint gpwidth, gpheight, prevwidth, prevheight;
+
+ gchar *monitorids = NULL;
+ guint32 maxwidth = 0;
+ guint32 maxheight = 0;
+
+ remmina_rdp_monitor_get(rfi, &monitorids, &maxwidth, &maxheight);
+
+ REMMINA_PLUGIN_DEBUG("Sending preconfigured monitor layout");
+ if (rfi->dispcontext && rfi->dispcontext->SendMonitorLayout) {
+ remmina_rdp_settings_get_orientation_scale_prefs(&desktopOrientation, &desktopScaleFactor, &deviceScaleFactor);
+ gtk_widget_get_allocation(GTK_WIDGET(gp), &a);
+ gpwidth = a.width;
+ gpheight = a.height;
+ prevwidth = remmina_plugin_service->protocol_plugin_get_width(gp);
+ prevheight = remmina_plugin_service->protocol_plugin_get_height(gp);
+
+ if ((gpwidth != prevwidth || gpheight != prevheight) && gpwidth >= 200 && gpheight >= 200) {
+ if (rfi->rdpgfxchan) {
+ /* Workaround for FreeRDP issue #5417 */
+ if (gpwidth < AVC_MIN_DESKTOP_WIDTH)
+ gpwidth = AVC_MIN_DESKTOP_WIDTH;
+ if (gpheight < AVC_MIN_DESKTOP_HEIGHT)
+ gpheight = AVC_MIN_DESKTOP_HEIGHT;
+ }
+ rdp_event.type = REMMINA_RDP_EVENT_TYPE_SEND_MONITOR_LAYOUT;
+ if (remmina_plugin_service->file_get_int(remminafile, "multimon", FALSE)) {
+ const rdpMonitor *base = freerdp_settings_get_pointer(rfi->clientContext.context.settings, FreeRDP_MonitorDefArray);
+ for (gint i = 0; i < freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_MonitorCount); ++i) {
+ const rdpMonitor *current = &base[i];
+ REMMINA_PLUGIN_DEBUG("Sending display layout n° %d", i);
+ rdp_event.monitor_layout.Flags = current->is_primary;
+ REMMINA_PLUGIN_DEBUG("EVNT MON LAYOUT - Flags: %i", rdp_event.monitor_layout.Flags);
+ rdp_event.monitor_layout.Left = current->x;
+ REMMINA_PLUGIN_DEBUG("EVNT MON LAYOUT - Left: %i", rdp_event.monitor_layout.Left);
+ rdp_event.monitor_layout.Top = current->y;
+ REMMINA_PLUGIN_DEBUG("EVNT MON LAYOUT - Top: %i", rdp_event.monitor_layout.Top);
+ rdp_event.monitor_layout.width = current->width;
+ REMMINA_PLUGIN_DEBUG("EVNT MON LAYOUT - width: %i", rdp_event.monitor_layout.width);
+ rdp_event.monitor_layout.height = current->height;
+ REMMINA_PLUGIN_DEBUG("EVNT MON LAYOUT - height: %i", rdp_event.monitor_layout.height);
+ rdp_event.monitor_layout.physicalWidth = current->attributes.physicalWidth;
+ REMMINA_PLUGIN_DEBUG("EVNT MON LAYOUT - physicalWidth: %i", rdp_event.monitor_layout.physicalWidth);
+ rdp_event.monitor_layout.physicalHeight = current->attributes.physicalHeight;
+ REMMINA_PLUGIN_DEBUG("EVNT MON LAYOUT - PhysicalHeight: %i", rdp_event.monitor_layout.physicalHeight);
+ if (current->attributes.orientation)
+ rdp_event.monitor_layout.desktopOrientation = current->attributes.orientation;
+ else
+ rdp_event.monitor_layout.desktopOrientation = rdp_event.monitor_layout.desktopOrientation;
+ REMMINA_PLUGIN_DEBUG("EVNT MON LAYOUT - desktopOrientation: %i", rdp_event.monitor_layout.desktopOrientation);
+ rdp_event.monitor_layout.desktopScaleFactor = rdp_event.monitor_layout.desktopScaleFactor;
+ REMMINA_PLUGIN_DEBUG("EVNT MON LAYOUT - ScaleFactorflag: %i", rdp_event.monitor_layout.desktopScaleFactor);
+ rdp_event.monitor_layout.deviceScaleFactor = rdp_event.monitor_layout.deviceScaleFactor;
+ }
+ remmina_rdp_event_event_push(gp, &rdp_event);
+ } else {
+ rdp_event.monitor_layout.width = gpwidth;
+ rdp_event.monitor_layout.height = gpheight;
+ rdp_event.monitor_layout.desktopOrientation = desktopOrientation;
+ rdp_event.monitor_layout.desktopScaleFactor = desktopScaleFactor;
+ rdp_event.monitor_layout.deviceScaleFactor = deviceScaleFactor;
+ remmina_rdp_event_event_push(gp, &rdp_event);
+ }
+ }
+ }
+
+ g_free(monitorids);
+
+ return FALSE;
+}
+
+void remmina_rdp_event_send_delayed_monitor_layout(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+
+ if (!rfi || !rfi->connected || rfi->is_reconnecting)
+ return;
+ if (rfi->delayed_monitor_layout_handler) {
+ g_source_remove(rfi->delayed_monitor_layout_handler);
+ rfi->delayed_monitor_layout_handler = 0;
+ }
+ if (rfi->scale == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES)
+ rfi->delayed_monitor_layout_handler = g_timeout_add(500, (GSourceFunc)remmina_rdp_event_delayed_monitor_layout, gp);
+}
+
+static gboolean remmina_rdp_event_on_configure(GtkWidget *widget, GdkEventConfigure *event, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ /* Called when gp changes its size or position */
+
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+
+ if (!rfi || !rfi->connected || rfi->is_reconnecting)
+ return FALSE;
+
+ remmina_rdp_event_update_scale_factor(gp);
+
+ /* If the scaler is not active, schedule a delayed remote resolution change */
+ remmina_rdp_event_send_delayed_monitor_layout(gp);
+
+
+ return FALSE;
+}
+
+static void remmina_rdp_event_translate_pos(RemminaProtocolWidget *gp, int ix, int iy, UINT16 *ox, UINT16 *oy)
+{
+ TRACE_CALL(__func__);
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+
+ /*
+ * Translate a position from local window coordinates (ix,iy) to
+ * RDP coordinates and put result on (*ox,*uy)
+ * */
+
+ if (!rfi || !rfi->connected || rfi->is_reconnecting)
+ return;
+
+ if ((rfi->scale == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED) && (rfi->scale_width >= 1) && (rfi->scale_height >= 1)) {
+ *ox = (UINT16)(ix * remmina_plugin_service->protocol_plugin_get_width(gp) / rfi->scale_width);
+ *oy = (UINT16)(iy * remmina_plugin_service->protocol_plugin_get_height(gp) / rfi->scale_height);
+ } else {
+ *ox = (UINT16)ix;
+ *oy = (UINT16)iy;
+ }
+}
+
+static void remmina_rdp_event_reverse_translate_pos_reverse(RemminaProtocolWidget *gp, int ix, int iy, int *ox, int *oy)
+{
+ TRACE_CALL(__func__);
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+
+ /*
+ * Translate a position from RDP coordinates (ix,iy) to
+ * local window coordinates and put result on (*ox,*uy)
+ * */
+
+ if (!rfi || !rfi->connected || rfi->is_reconnecting)
+ return;
+
+ if ((rfi->scale == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED) && (rfi->scale_width >= 1) && (rfi->scale_height >= 1)) {
+ *ox = (ix * rfi->scale_width) / remmina_plugin_service->protocol_plugin_get_width(gp);
+ *oy = (iy * rfi->scale_height) / remmina_plugin_service->protocol_plugin_get_height(gp);
+ } else {
+ *ox = ix;
+ *oy = iy;
+ }
+}
+
+void remmina_rdp_mouse_jitter(RemminaProtocolWidget *gp){
+ TRACE_CALL(__func__);
+ RemminaPluginRdpEvent rdp_event = { 0 };
+ RemminaFile *remminafile;
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ if (remmina_plugin_service->file_get_int(remminafile, "viewonly", FALSE))
+ return;
+
+ rdp_event.type = REMMINA_RDP_EVENT_TYPE_MOUSE;
+ rdp_event.mouse_event.flags = PTR_FLAGS_MOVE;
+ rdp_event.mouse_event.extended = FALSE;
+ rdp_event.mouse_event.x = rfi->last_x;
+ rdp_event.mouse_event.y = rfi->last_y;
+ remmina_rdp_event_event_push(gp, &rdp_event);
+}
+
+static gboolean remmina_rdp_event_on_motion(GtkWidget *widget, GdkEventMotion *event, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginRdpEvent rdp_event = { 0 };
+ RemminaFile *remminafile;
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ if (remmina_plugin_service->file_get_int(remminafile, "viewonly", FALSE))
+ return FALSE;
+
+ rdp_event.type = REMMINA_RDP_EVENT_TYPE_MOUSE;
+ rdp_event.mouse_event.flags = PTR_FLAGS_MOVE;
+ rdp_event.mouse_event.extended = FALSE;
+
+ remmina_rdp_event_translate_pos(gp, event->x, event->y, &rdp_event.mouse_event.x, &rdp_event.mouse_event.y);
+ if (rfi != NULL){
+ rfi->last_x = rdp_event.mouse_event.x;
+ rfi->last_y = rdp_event.mouse_event.y;
+ }
+
+ remmina_rdp_event_event_push(gp, &rdp_event);
+
+ return TRUE;
+}
+
+static gboolean remmina_rdp_event_on_button(GtkWidget *widget, GdkEventButton *event, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ gint flag;
+ gboolean extended = FALSE;
+ RemminaPluginRdpEvent rdp_event = { 0 };
+ gint primary, secondary;
+
+ RemminaFile *remminafile;
+
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ if (remmina_plugin_service->file_get_int(remminafile, "viewonly", FALSE))
+ return FALSE;
+
+ /* We bypass 2button-press and 3button-press events */
+ if ((event->type != GDK_BUTTON_PRESS) && (event->type != GDK_BUTTON_RELEASE))
+ return TRUE;
+
+ flag = 0;
+
+ if (remmina_plugin_service->file_get_int(remminafile, "left-handed", FALSE)) {
+ primary = PTR_FLAGS_BUTTON2;
+ secondary = PTR_FLAGS_BUTTON1;
+ } else {
+ primary = PTR_FLAGS_BUTTON1;
+ secondary = PTR_FLAGS_BUTTON2;
+ }
+
+ switch (event->button) {
+ case 1:
+ flag |= primary;
+ break;
+ case 2:
+ flag |= PTR_FLAGS_BUTTON3;
+ break;
+ case 3:
+ flag |= secondary;
+ break;
+ case 8: /* back */
+ case 97: /* Xming */
+ extended = TRUE;
+ flag |= PTR_XFLAGS_BUTTON1;
+ break;
+ case 9: /* forward */
+ case 112: /* Xming */
+ extended = TRUE;
+ flag |= PTR_XFLAGS_BUTTON2;
+ break;
+ default:
+ return FALSE;
+ }
+
+ if (event->type == GDK_BUTTON_PRESS) {
+ if (extended)
+ flag |= PTR_XFLAGS_DOWN;
+ else
+ flag |= PTR_FLAGS_DOWN;
+ }
+
+ rdp_event.type = REMMINA_RDP_EVENT_TYPE_MOUSE;
+ remmina_rdp_event_translate_pos(gp, event->x, event->y, &rdp_event.mouse_event.x, &rdp_event.mouse_event.y);
+
+ if (flag != 0) {
+ rdp_event.mouse_event.flags = flag;
+ rdp_event.mouse_event.extended = extended;
+ remmina_rdp_event_event_push(gp, &rdp_event);
+ }
+
+ return TRUE;
+}
+
+static gboolean remmina_rdp_event_on_scroll(GtkWidget *widget, GdkEventScroll *event, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ gint flag;
+ RemminaPluginRdpEvent rdp_event = { 0 };
+ float windows_delta;
+ RemminaFile *remminafile;
+
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ if (remmina_plugin_service->file_get_int(remminafile, "viewonly", FALSE))
+ return FALSE;
+
+ flag = 0;
+ rdp_event.type = REMMINA_RDP_EVENT_TYPE_MOUSE;
+
+ /* See [MS-RDPBCGR] TS_POINTER_EVENT and WM_MOUSEWHEEL message */
+
+ switch (event->direction) {
+ case GDK_SCROLL_UP:
+ flag = PTR_FLAGS_WHEEL | 0x0078; // 120 is one scroll unit defined in WM_MOUSEWHEEL
+ break;
+
+ case GDK_SCROLL_DOWN:
+ flag = PTR_FLAGS_WHEEL | 0x0188; // -120 (one scroll unit) in 9 bits two's complement
+ break;
+
+#if GTK_CHECK_VERSION(3, 4, 0)
+ case GDK_SCROLL_SMOOTH:
+
+ if (event->delta_y == 0.0)
+ return FALSE;
+
+ windows_delta = event->delta_y * -120;
+
+ if (windows_delta > 255)
+ windows_delta = 255;
+ if (windows_delta < -256)
+ windows_delta = -256;
+
+ flag = PTR_FLAGS_WHEEL | ((short)windows_delta & WheelRotationMask);
+
+ break;
+#endif
+
+ default:
+ return FALSE;
+ }
+
+ rdp_event.mouse_event.flags = flag;
+ rdp_event.mouse_event.extended = FALSE;
+ remmina_rdp_event_translate_pos(gp, event->x, event->y, &rdp_event.mouse_event.x, &rdp_event.mouse_event.y);
+ remmina_rdp_event_event_push(gp, &rdp_event);
+
+ return TRUE;
+}
+
+static void remmina_rdp_event_init_keymap(rfContext *rfi, const gchar *strmap)
+{
+ long int v1, v2;
+ const char *s;
+ char *endptr;
+ RemminaPluginRdpKeymapEntry ke;
+
+ if (strmap == NULL || strmap[0] == 0) {
+ rfi->keymap = NULL;
+ return;
+ }
+ s = strmap;
+ rfi->keymap = g_array_new(FALSE, TRUE, sizeof(RemminaPluginRdpKeymapEntry));
+ while (1) {
+ v1 = strtol(s, &endptr, 10);
+ if (endptr == s) break;
+ s = endptr;
+ if (*s != ':') break;
+ s++;
+ v2 = strtol(s, &endptr, 10);
+ if (endptr == s) break;
+ s = endptr;
+ ke.orig_keycode = v1 & 0x7fffffff;
+ ke.translated_keycode = v2 & 0x7fffffff;
+ g_array_append_val(rfi->keymap, ke);
+ if (*s != ',') break;
+ s++;
+ }
+ if (rfi->keymap->len == 0) {
+ g_array_unref(rfi->keymap);
+ rfi->keymap = NULL;
+ }
+}
+
+static gboolean remmina_rdp_event_on_key(GtkWidget *widget, GdkEventKey *event, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ guint32 unicode_keyval;
+ guint16 hardware_keycode;
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+ RemminaPluginRdpEvent rdp_event;
+ RemminaPluginRdpKeymapEntry *kep;
+ RemminaFile *remminafile;
+ DWORD scancode = 0;
+ int ik;
+
+ if (!rfi || !rfi->connected || rfi->is_reconnecting)
+ return FALSE;
+
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ if (remmina_plugin_service->file_get_int(remminafile, "viewonly", FALSE))
+ return FALSE;
+
+#ifdef ENABLE_GTK_INSPECTOR_KEY
+ /* GTK inspector key is propagated up. Disabled by default.
+ * enable it by defining ENABLE_GTK_INSPECTOR_KEY */
+ if ((event->state & GDK_CONTROL_MASK) != 0 && (event->keyval == GDK_KEY_I || event->keyval == GDK_KEY_D))
+ return FALSE;
+
+#endif
+
+ rdp_event.type = REMMINA_RDP_EVENT_TYPE_SCANCODE;
+ rdp_event.key_event.up = (event->type == GDK_KEY_PRESS ? false : true);
+ rdp_event.key_event.extended = false;
+ rdp_event.key_event.extended1 = false;
+
+ switch (event->keyval) {
+ case GDK_KEY_Pause:
+ /*
+ * See https://msdn.microsoft.com/en-us/library/cc240584.aspx
+ * 2.2.8.1.1.3.1.1.1 Keyboard Event (TS_KEYBOARD_EVENT)
+ * for pause key management
+ */
+ rdp_event.key_event.key_code = 0x1D;
+ rdp_event.key_event.up = false;
+ rdp_event.key_event.extended1 = TRUE;
+ remmina_rdp_event_event_push(gp, &rdp_event);
+ rdp_event.key_event.key_code = 0x45;
+ rdp_event.key_event.up = false;
+ rdp_event.key_event.extended1 = FALSE;
+ remmina_rdp_event_event_push(gp, &rdp_event);
+ rdp_event.key_event.key_code = 0x1D;
+ rdp_event.key_event.up = true;
+ rdp_event.key_event.extended1 = TRUE;
+ remmina_rdp_event_event_push(gp, &rdp_event);
+ rdp_event.key_event.key_code = 0x45;
+ rdp_event.key_event.up = true;
+ rdp_event.key_event.extended1 = FALSE;
+ remmina_rdp_event_event_push(gp, &rdp_event);
+ break;
+
+ default:
+ if (!rfi->use_client_keymap) {
+ hardware_keycode = event->hardware_keycode;
+ if (rfi->keymap) {
+ for (ik = 0; ik < rfi->keymap->len; ik++) {
+ kep = &g_array_index(rfi->keymap, RemminaPluginRdpKeymapEntry, ik);
+ if (hardware_keycode == kep->orig_keycode) {
+ hardware_keycode = kep->translated_keycode;
+ break;
+ }
+ }
+ }
+ scancode = freerdp_keyboard_get_rdp_scancode_from_x11_keycode(hardware_keycode);
+ if (scancode) {
+ rdp_event.key_event.key_code = scancode & 0xFF;
+ rdp_event.key_event.extended = scancode & 0x100;
+ rdp_event.key_event.extended1 = FALSE;
+ remmina_rdp_event_event_push(gp, &rdp_event);
+ keypress_list_add(gp, rdp_event);
+ }
+ } else {
+ unicode_keyval = gdk_keyval_to_unicode(event->keyval);
+ /* Decide when whe should send a keycode or a Unicode character.
+ * - All non char keys (Shift, Alt, Super) should be sent as keycode
+ * - Space should be sent as keycode (see issue #1364)
+ * - All special keys (F1-F10, numeric pad, Home/End/Arrows/PgUp/PgDn/Insert/Delete) keycode
+ * - All key pressed while Ctrl or Alt or Super is down are not decoded by gdk_keyval_to_unicode(), so send it as keycode
+ * - All keycodes not translatable to unicode chars, as keycode
+ * - The rest as Unicode char
+ */
+ if (event->keyval >= 0xfe00 || // Arrows, Shift, Alt, Fn, num keypad…
+ event->hardware_keycode == 0x41 || // Spacebar
+ unicode_keyval == 0 || // Impossible to translate
+ (event->state & (GDK_MOD1_MASK | GDK_CONTROL_MASK | GDK_SUPER_MASK)) != 0 // A modifier not recognized by gdk_keyval_to_unicode()
+ ) {
+ scancode = freerdp_keyboard_get_rdp_scancode_from_x11_keycode(event->hardware_keycode);
+ rdp_event.key_event.key_code = scancode & 0xFF;
+ rdp_event.key_event.extended = scancode & 0x100;
+ rdp_event.key_event.extended1 = FALSE;
+ if (rdp_event.key_event.key_code) {
+ remmina_rdp_event_event_push(gp, &rdp_event);
+ keypress_list_add(gp, rdp_event);
+ }
+ } else {
+ rdp_event.type = REMMINA_RDP_EVENT_TYPE_SCANCODE_UNICODE;
+ rdp_event.key_event.unicode_code = unicode_keyval;
+ rdp_event.key_event.extended = false;
+ rdp_event.key_event.extended1 = FALSE;
+ remmina_rdp_event_event_push(gp, &rdp_event);
+ keypress_list_add(gp, rdp_event);
+ }
+ }
+ break;
+ }
+
+ return TRUE;
+}
+
+gboolean remmina_rdp_event_on_clipboard(GtkClipboard *gtkClipboard, GdkEvent *event, RemminaProtocolWidget *gp)
+{
+ /* Signal handler for GTK clipboard owner-change */
+ TRACE_CALL(__func__);
+ RemminaPluginRdpEvent rdp_event = { 0 };
+ CLIPRDR_FORMAT_LIST *pFormatList;
+ GObject *new_owner;
+
+ /* Usually "owner-change" is fired when a user presses "COPY" on the client
+ * OR when this plugin calls gtk_clipboard_set_with_owner()
+ * after receiving a RDP server format list in remmina_rdp_cliprdr_server_format_list()
+ * In the latter case, we must ignore owner change */
+
+ REMMINA_PLUGIN_DEBUG("gp=%p: owner-change event received", gp);
+
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+
+ if (rfi)
+ remmina_rdp_clipboard_abort_client_format_data_request(rfi);
+
+ new_owner = gtk_clipboard_get_owner(gtkClipboard);
+ if (new_owner != (GObject *)gp) {
+ /* To do: avoid this when the new owner is another remmina protocol widget of
+ * the same remmina application */
+ REMMINA_PLUGIN_DEBUG("gp=%p owner-change: new owner is different than me: new=%p me=%p",
+ gp, new_owner, gp);
+
+ REMMINA_PLUGIN_DEBUG("gp=%p owner-change: new owner is not me: Sending local clipboard format list to server.",
+ gp, new_owner, gp);
+ pFormatList = remmina_rdp_cliprdr_get_client_format_list(gp);
+ rdp_event.type = REMMINA_RDP_EVENT_TYPE_CLIPBOARD_SEND_CLIENT_FORMAT_LIST;
+ rdp_event.clipboard_formatlist.pFormatList = pFormatList;
+ remmina_rdp_event_event_push(gp, &rdp_event);
+ } else {
+ REMMINA_PLUGIN_DEBUG(" ... but I'm the owner!");
+ }
+ return TRUE;
+}
+
+void remmina_rdp_event_init(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ gchar *s;
+ gint flags;
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+ GtkClipboard *clipboard;
+ RemminaFile *remminafile;
+
+ gboolean disable_smooth_scrolling = FALSE;
+
+ if (!rfi) return;
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ /* we get first the global preferences */
+ s = remmina_plugin_service->pref_get_value("rdp_disable_smooth_scrolling");
+ disable_smooth_scrolling = (s && s[0] == '1' ? TRUE : FALSE);
+ g_free(s), s = NULL;
+ /* Otherwise we use the connection profile specific setting */
+ disable_smooth_scrolling = remmina_plugin_service->file_get_int(remminafile, "disable-smooth-scrolling", disable_smooth_scrolling);
+
+ REMMINA_PLUGIN_DEBUG("Disable smooth scrolling is set to %d", disable_smooth_scrolling);
+
+ rfi->drawing_area = gtk_drawing_area_new();
+ gtk_widget_show(rfi->drawing_area);
+ gtk_container_add(GTK_CONTAINER(gp), rfi->drawing_area);
+
+ gtk_widget_add_events(rfi->drawing_area, GDK_POINTER_MOTION_MASK
+ | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
+ | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK
+ | GDK_SCROLL_MASK | GDK_FOCUS_CHANGE_MASK);
+
+ if (!disable_smooth_scrolling) {
+ REMMINA_PLUGIN_DEBUG("Adding GDK_SMOOTH_SCROLL_MASK");
+ gtk_widget_add_events(rfi->drawing_area, GDK_SMOOTH_SCROLL_MASK);
+ }
+
+ gtk_widget_set_can_focus(rfi->drawing_area, TRUE);
+
+ remmina_plugin_service->protocol_plugin_register_hostkey(gp, rfi->drawing_area);
+
+ s = remmina_plugin_service->pref_get_value("rdp_use_client_keymap");
+ rfi->use_client_keymap = (s && s[0] == '1' ? TRUE : FALSE);
+ g_free(s), s = NULL;
+
+ /* Read special keymap from profile file, if exists */
+ remmina_rdp_event_init_keymap(rfi, remmina_plugin_service->pref_get_value("rdp_map_keycode"));
+
+ if (rfi->use_client_keymap && rfi->keymap)
+ fprintf(stderr, "RDP profile error: you cannot define both rdp_map_hardware_keycode and have 'Use client keyboard mapping' enabled\n");
+
+ g_signal_connect(G_OBJECT(rfi->drawing_area), "draw",
+ G_CALLBACK(remmina_rdp_event_on_draw), gp);
+ g_signal_connect(G_OBJECT(rfi->drawing_area), "configure-event",
+ G_CALLBACK(remmina_rdp_event_on_configure), gp);
+ g_signal_connect(G_OBJECT(rfi->drawing_area), "motion-notify-event",
+ G_CALLBACK(remmina_rdp_event_on_motion), gp);
+ g_signal_connect(G_OBJECT(rfi->drawing_area), "button-press-event",
+ G_CALLBACK(remmina_rdp_event_on_button), gp);
+ g_signal_connect(G_OBJECT(rfi->drawing_area), "button-release-event",
+ G_CALLBACK(remmina_rdp_event_on_button), gp);
+ g_signal_connect(G_OBJECT(rfi->drawing_area), "scroll-event",
+ G_CALLBACK(remmina_rdp_event_on_scroll), gp);
+ g_signal_connect(G_OBJECT(rfi->drawing_area), "key-press-event",
+ G_CALLBACK(remmina_rdp_event_on_key), gp);
+ g_signal_connect(G_OBJECT(rfi->drawing_area), "key-release-event",
+ G_CALLBACK(remmina_rdp_event_on_key), gp);
+ g_signal_connect(G_OBJECT(rfi->drawing_area), "focus-in-event",
+ G_CALLBACK(remmina_rdp_event_on_focus_in), gp);
+ /** Fixme: This comment
+ * needed for TS_SUPPRESS_OUTPUT_PDU
+ * But it works only when we stay in the same window mode, if we switch to
+ * fullscreen, for instance, the object refernce is lost, so we loose these
+ * events.
+ */
+ //g_signal_connect(G_OBJECT(gtk_widget_get_toplevel(rfi->drawing_area)), "map-event",
+ // G_CALLBACK(remmina_rdp_event_on_map), gp);
+ //g_signal_connect(G_OBJECT(gtk_widget_get_toplevel(rfi->drawing_area)), "unmap-event",
+ // G_CALLBACK(remmina_rdp_event_on_unmap), gp);
+
+ if (!remmina_plugin_service->file_get_int(remminafile, "disableclipboard", FALSE)) {
+ clipboard = gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD);
+ rfi->clipboard.clipboard_handler = g_signal_connect(clipboard, "owner-change", G_CALLBACK(remmina_rdp_event_on_clipboard), gp);
+ }
+
+ rfi->pressed_keys = g_array_new(FALSE, TRUE, sizeof(RemminaPluginRdpEvent));
+ rfi->event_queue = g_async_queue_new_full(g_free);
+ rfi->ui_queue = g_async_queue_new();
+ pthread_mutex_init(&rfi->ui_queue_mutex, NULL);
+
+ if (pipe(rfi->event_pipe)) {
+ g_print("Error creating pipes.\n");
+ rfi->event_pipe[0] = -1;
+ rfi->event_pipe[1] = -1;
+ rfi->event_handle = NULL;
+ } else {
+ flags = fcntl(rfi->event_pipe[0], F_GETFL, 0);
+ fcntl(rfi->event_pipe[0], F_SETFL, flags | O_NONBLOCK);
+ rfi->event_handle = CreateFileDescriptorEvent(NULL, FALSE, FALSE, rfi->event_pipe[0], WINPR_FD_READ);
+ if (!rfi->event_handle)
+ g_print("CreateFileDescriptorEvent() failed\n");
+ }
+
+ rfi->object_table = g_hash_table_new_full(NULL, NULL, NULL, g_free);
+
+ rfi->display = gdk_display_get_default();
+
+#if GTK_CHECK_VERSION(3, 22, 0)
+ GdkVisual *visual = gdk_screen_get_system_visual(
+ gdk_display_get_default_screen(rfi->display));
+ rfi->bpp = gdk_visual_get_depth(visual);
+#else
+ rfi->bpp = gdk_visual_get_best_depth();
+#endif
+}
+
+void remmina_rdp_event_free_event(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *obj)
+{
+ TRACE_CALL(__func__);
+
+ switch (obj->type) {
+ case REMMINA_RDP_UI_NOCODEC:
+ free(obj->nocodec.bitmap);
+ break;
+
+ default:
+ break;
+ }
+
+ g_free(obj);
+}
+
+void remmina_rdp_event_uninit(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+ RemminaPluginRdpUiObject *ui;
+
+ if (!rfi) return;
+
+ /* unregister the clipboard monitor */
+ if (rfi->clipboard.clipboard_handler) {
+ g_signal_handler_disconnect(G_OBJECT(gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD)), rfi->clipboard.clipboard_handler);
+ rfi->clipboard.clipboard_handler = 0;
+ }
+ if (rfi->delayed_monitor_layout_handler) {
+ g_source_remove(rfi->delayed_monitor_layout_handler);
+ rfi->delayed_monitor_layout_handler = 0;
+ }
+ if (rfi->ui_handler) {
+ g_source_remove(rfi->ui_handler);
+ rfi->ui_handler = 0;
+ }
+ while ((ui = (RemminaPluginRdpUiObject *)g_async_queue_try_pop(rfi->ui_queue)) != NULL)
+ remmina_rdp_event_free_event(gp, ui);
+ if (rfi->surface) {
+ cairo_surface_mark_dirty(rfi->surface);
+ cairo_surface_destroy(rfi->surface);
+ rfi->surface = NULL;
+ }
+
+ g_hash_table_destroy(rfi->object_table);
+
+ g_array_free(rfi->pressed_keys, TRUE);
+ if (rfi->keymap) {
+ g_array_free(rfi->keymap, TRUE);
+ rfi->keymap = NULL;
+ }
+ g_async_queue_unref(rfi->event_queue);
+ rfi->event_queue = NULL;
+ g_async_queue_unref(rfi->ui_queue);
+ rfi->ui_queue = NULL;
+ pthread_mutex_destroy(&rfi->ui_queue_mutex);
+
+ if (rfi->event_handle) {
+ CloseHandle(rfi->event_handle);
+ rfi->event_handle = NULL;
+ }
+
+ close(rfi->event_pipe[0]);
+ close(rfi->event_pipe[1]);
+}
+
+static void remmina_rdp_event_create_cairo_surface(rfContext *rfi)
+{
+ int stride;
+ rdpGdi *gdi;
+
+ if (!rfi)
+ return;
+
+ gdi = ((rdpContext *)rfi)->gdi;
+
+ if (!gdi)
+ return;
+
+ if (rfi->surface) {
+ cairo_surface_mark_dirty(rfi->surface);
+ cairo_surface_destroy(rfi->surface);
+ rfi->surface = NULL;
+ }
+ stride = cairo_format_stride_for_width(rfi->cairo_format, gdi->width);
+ rfi->surface = cairo_image_surface_create_for_data((unsigned char *)gdi->primary_buffer, rfi->cairo_format, gdi->width, gdi->height, stride);
+ cairo_surface_flush(rfi->surface);
+}
+
+void remmina_rdp_event_update_scale(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ gint width, height;
+ rdpGdi *gdi;
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+
+ width = remmina_plugin_service->protocol_plugin_get_width(gp);
+ height = remmina_plugin_service->protocol_plugin_get_height(gp);
+
+ gdi = ((rdpContext *)rfi)->gdi;
+
+ rfi->scale = remmina_plugin_service->remmina_protocol_widget_get_current_scale_mode(gp);
+
+ /* See if we also must rellocate rfi->surface with different width and height,
+ * this usually happens after a DesktopResize RDP event*/
+
+ if (rfi->surface && (cairo_image_surface_get_width(rfi->surface) != gdi->width ||
+ cairo_image_surface_get_height(rfi->surface) != gdi->height)) {
+ /* Destroys and recreate rfi->surface with new width and height */
+ cairo_surface_mark_dirty(rfi->surface);
+ cairo_surface_destroy(rfi->surface);
+ rfi->surface = NULL;
+ remmina_rdp_event_create_cairo_surface(rfi);
+ } else if (rfi->surface == NULL) {
+ remmina_rdp_event_create_cairo_surface(rfi);
+ }
+
+ /* Send gdi->width and gdi->height obtained from remote server to gp plugin,
+ * so they will be saved when closing connection */
+ if (width != gdi->width)
+ remmina_plugin_service->protocol_plugin_set_width(gp, gdi->width);
+ if (height != gdi->height)
+ remmina_plugin_service->protocol_plugin_set_height(gp, gdi->height);
+
+ remmina_rdp_event_update_scale_factor(gp);
+
+ if (rfi->scale == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED || rfi->scale == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES)
+ /* In scaled mode and autores mode, drawing_area will get its dimensions from its parent */
+ gtk_widget_set_size_request(rfi->drawing_area, -1, -1);
+ else
+ /* In non scaled mode, the plugins forces dimensions of drawing area */
+ gtk_widget_set_size_request(rfi->drawing_area, width, height);
+ remmina_plugin_service->protocol_plugin_update_align(gp);
+}
+
+static void remmina_rdp_event_connected(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
+{
+ TRACE_CALL(__func__);
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+ rdpGdi *gdi;
+
+ gdi = ((rdpContext *)rfi)->gdi;
+
+ gtk_widget_realize(rfi->drawing_area);
+
+ remmina_rdp_event_create_cairo_surface(rfi);
+ gtk_widget_queue_draw_area(rfi->drawing_area, 0, 0, gdi->width, gdi->height);
+
+ remmina_rdp_event_update_scale(gp);
+
+ remmina_plugin_service->protocol_plugin_signal_connection_opened(gp);
+ const gchar *host = freerdp_settings_get_string (rfi->clientContext.context.settings, FreeRDP_ServerHostname);
+ // TRANSLATORS: the placeholder may be either an IP/FQDN or a server hostname
+ REMMINA_PLUGIN_AUDIT(_("Connected to %s via RDP"), host);
+}
+
+static void remmina_rdp_event_reconnect_progress(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
+{
+ TRACE_CALL(__func__);
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+
+ gdk_window_invalidate_rect(gtk_widget_get_window(rfi->drawing_area), NULL, TRUE);
+}
+
+static BOOL remmina_rdp_event_create_cursor(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
+{
+ TRACE_CALL(__func__);
+ GdkPixbuf *pixbuf;
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+ rdpPointer *pointer = (rdpPointer *)ui->cursor.pointer;
+ cairo_surface_t *surface;
+ UINT8 *data = malloc(pointer->width * pointer->height * 4);
+
+ if (!freerdp_image_copy_from_pointer_data(
+ (BYTE *)data, PIXEL_FORMAT_BGRA32,
+ pointer->width * 4, 0, 0, pointer->width, pointer->height,
+ pointer->xorMaskData, pointer->lengthXorMask,
+ pointer->andMaskData, pointer->lengthAndMask,
+ pointer->xorBpp, &(ui->cursor.context->gdi->palette))) {
+ free(data);
+ return FALSE;
+ }
+
+ surface = cairo_image_surface_create_for_data(data, CAIRO_FORMAT_ARGB32, pointer->width, pointer->height, cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, pointer->width));
+ cairo_surface_flush(surface);
+ pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, pointer->width, pointer->height);
+ cairo_surface_mark_dirty(surface);
+ cairo_surface_destroy(surface);
+ free(data);
+ ((rfPointer *)ui->cursor.pointer)->cursor = gdk_cursor_new_from_pixbuf(rfi->display, pixbuf, pointer->xPos, pointer->yPos);
+ g_object_unref(pixbuf);
+
+ return TRUE;
+}
+
+static void remmina_rdp_event_free_cursor(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
+{
+ TRACE_CALL(__func__);
+ g_object_unref(ui->cursor.pointer->cursor);
+ ui->cursor.pointer->cursor = NULL;
+}
+
+static BOOL remmina_rdp_event_set_pointer_position(RemminaProtocolWidget *gp, gint x, gint y)
+{
+ TRACE_CALL(__func__);
+ GdkWindow *w, *nw;
+ gint nx, ny, wx, wy;
+
+#if GTK_CHECK_VERSION(3, 20, 0)
+ GdkSeat *seat;
+#else
+ GdkDeviceManager *manager;
+#endif
+ GdkDevice *dev;
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+
+ if (rfi == NULL)
+ return FALSE;
+
+ w = gtk_widget_get_window(rfi->drawing_area);
+#if GTK_CHECK_VERSION(3, 20, 0)
+ seat = gdk_display_get_default_seat(gdk_display_get_default());
+ dev = gdk_seat_get_pointer(seat);
+#else
+ manager = gdk_display_get_device_manager(gdk_display_get_default());
+ dev = gdk_device_manager_get_client_pointer(manager);
+#endif
+
+ nw = gdk_device_get_window_at_position(dev, NULL, NULL);
+
+ if (nw == w) {
+ nx = 0;
+ ny = 0;
+ remmina_rdp_event_reverse_translate_pos_reverse(gp, x, y, &nx, &ny);
+ gdk_window_get_root_coords(w, nx, ny, &wx, &wy);
+ gdk_device_warp(dev, gdk_window_get_screen(w), wx, wy);
+ }
+ return TRUE;
+}
+
+static void remmina_rdp_event_cursor(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
+{
+ TRACE_CALL(__func__);
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+
+ switch (ui->cursor.type) {
+ case REMMINA_RDP_POINTER_NEW:
+ ui->retval = remmina_rdp_event_create_cursor(gp, ui) ? 1 : 0;
+ break;
+
+ case REMMINA_RDP_POINTER_FREE:
+ remmina_rdp_event_free_cursor(gp, ui);
+ break;
+
+ case REMMINA_RDP_POINTER_SET:
+ gdk_window_set_cursor(gtk_widget_get_window(rfi->drawing_area), ui->cursor.pointer->cursor);
+ ui->retval = 1;
+ break;
+
+ case REMMINA_RDP_POINTER_SETPOS:
+ ui->retval = remmina_rdp_event_set_pointer_position(gp, ui->pos.x, ui->pos.y) ? 1 : 0;
+ break;
+
+ case REMMINA_RDP_POINTER_NULL:
+ gdk_window_set_cursor(gtk_widget_get_window(rfi->drawing_area),
+ gdk_cursor_new_for_display(gdk_display_get_default(),
+ GDK_BLANK_CURSOR));
+ ui->retval = 1;
+ break;
+
+ case REMMINA_RDP_POINTER_DEFAULT:
+ gdk_window_set_cursor(gtk_widget_get_window(rfi->drawing_area), NULL);
+ ui->retval = 1;
+ break;
+ }
+}
+
+static void remmina_rdp_ui_event_update_scale(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
+{
+ TRACE_CALL(__func__);
+ remmina_rdp_event_update_scale(gp);
+}
+
+void remmina_rdp_event_unfocus(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+
+ if (!rfi || !rfi->connected || rfi->is_reconnecting)
+ return;
+ remmina_rdp_event_release_all_keys(gp);
+}
+
+static void remmina_rdp_ui_event_destroy_cairo_surface(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
+{
+ TRACE_CALL(__func__);
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+
+ cairo_surface_mark_dirty(rfi->surface);
+ cairo_surface_destroy(rfi->surface);
+ rfi->surface = NULL;
+}
+
+static void remmina_rdp_event_process_event(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
+{
+ TRACE_CALL(__func__);
+ switch (ui->event.type) {
+ case REMMINA_RDP_UI_EVENT_UPDATE_SCALE:
+ remmina_rdp_ui_event_update_scale(gp, ui);
+ break;
+ case REMMINA_RDP_UI_EVENT_DESTROY_CAIRO_SURFACE:
+ remmina_rdp_ui_event_destroy_cairo_surface(gp, ui);
+ break;
+ }
+}
+
+static void remmina_rdp_event_process_ui_event(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
+{
+ TRACE_CALL(__func__);
+ switch (ui->type) {
+ case REMMINA_RDP_UI_UPDATE_REGIONS:
+ remmina_rdp_event_update_regions(gp, ui);
+ break;
+
+ case REMMINA_RDP_UI_CONNECTED:
+ remmina_rdp_event_connected(gp, ui);
+ break;
+
+ case REMMINA_RDP_UI_RECONNECT_PROGRESS:
+ remmina_rdp_event_reconnect_progress(gp, ui);
+ break;
+
+ case REMMINA_RDP_UI_CURSOR:
+ remmina_rdp_event_cursor(gp, ui);
+ break;
+
+ case REMMINA_RDP_UI_CLIPBOARD:
+ remmina_rdp_event_process_clipboard(gp, ui);
+ break;
+
+ case REMMINA_RDP_UI_EVENT:
+ remmina_rdp_event_process_event(gp, ui);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static gboolean remmina_rdp_event_process_ui_queue(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+ RemminaPluginRdpUiObject *ui;
+
+ pthread_mutex_lock(&rfi->ui_queue_mutex);
+ ui = (RemminaPluginRdpUiObject *)g_async_queue_try_pop(rfi->ui_queue);
+ if (ui) {
+ pthread_mutex_lock(&ui->sync_wait_mutex);
+ if (!rfi->thread_cancelled)
+ remmina_rdp_event_process_ui_event(gp, ui);
+ // Should we signal the caller thread to unlock ?
+ if (ui->sync) {
+ ui->complete = TRUE;
+ pthread_cond_signal(&ui->sync_wait_cond);
+ pthread_mutex_unlock(&ui->sync_wait_mutex);
+ } else {
+ remmina_rdp_event_free_event(gp, ui);
+ }
+
+ pthread_mutex_unlock(&rfi->ui_queue_mutex);
+ return TRUE;
+ } else {
+ rfi->ui_handler = 0;
+ pthread_mutex_unlock(&rfi->ui_queue_mutex);
+ return FALSE;
+ }
+}
+
+static void remmina_rdp_event_queue_ui(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
+{
+ TRACE_CALL(__func__);
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+ gboolean ui_sync_save;
+ int oldcanceltype;
+
+ if (!rfi || rfi->thread_cancelled)
+ return;
+
+ if (remmina_plugin_service->is_main_thread()) {
+ remmina_rdp_event_process_ui_event(gp, ui);
+ return;
+ }
+
+ pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldcanceltype);
+
+ pthread_mutex_lock(&rfi->ui_queue_mutex);
+
+ ui_sync_save = ui->sync;
+ ui->complete = FALSE;
+
+ if (ui_sync_save) {
+ pthread_mutex_init(&ui->sync_wait_mutex, NULL);
+ pthread_cond_init(&ui->sync_wait_cond, NULL);
+ }
+
+ ui->complete = FALSE;
+
+ g_async_queue_push(rfi->ui_queue, ui);
+
+ if (!rfi->ui_handler)
+ rfi->ui_handler = IDLE_ADD((GSourceFunc)remmina_rdp_event_process_ui_queue, gp);
+
+ if (ui_sync_save) {
+ /* Wait for main thread function completion before returning */
+ pthread_mutex_lock(&ui->sync_wait_mutex);
+ pthread_mutex_unlock(&rfi->ui_queue_mutex);
+ while (!ui->complete)
+ pthread_cond_wait(&ui->sync_wait_cond, &ui->sync_wait_mutex);
+ pthread_cond_destroy(&ui->sync_wait_cond);
+ pthread_mutex_destroy(&ui->sync_wait_mutex);
+ } else {
+ pthread_mutex_unlock(&rfi->ui_queue_mutex);
+ }
+ pthread_setcanceltype(oldcanceltype, NULL);
+}
+
+void remmina_rdp_event_queue_ui_async(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
+{
+ ui->sync = FALSE;
+ remmina_rdp_event_queue_ui(gp, ui);
+}
+
+int remmina_rdp_event_queue_ui_sync_retint(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
+{
+ TRACE_CALL(__func__);
+ int retval;
+
+ ui->sync = TRUE;
+ remmina_rdp_event_queue_ui(gp, ui);
+ retval = ui->retval;
+ remmina_rdp_event_free_event(gp, ui);
+ return retval;
+}
+
+void *remmina_rdp_event_queue_ui_sync_retptr(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
+{
+ TRACE_CALL(__func__);
+ void *rp;
+
+ ui->sync = TRUE;
+ remmina_rdp_event_queue_ui(gp, ui);
+ rp = ui->retptr;
+ remmina_rdp_event_free_event(gp, ui);
+ return rp;
+}
diff --git a/plugins/rdp/rdp_event.h b/plugins/rdp/rdp_event.h
new file mode 100644
index 0000000..8a7f08a
--- /dev/null
+++ b/plugins/rdp/rdp_event.h
@@ -0,0 +1,58 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010-2011 Vic Lee
+ * Copyright (C) 2017-2023 Antenore Gatta, Giovanni Panozzo
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL. * If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. * If you
+ * do not wish to do so, delete this exception statement from your
+ * version. * If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ *
+ */
+
+#pragma once
+
+#include <glib.h>
+#include "common/remmina_plugin.h"
+#include "rdp_plugin.h"
+
+G_BEGIN_DECLS
+
+
+void remmina_rdp_event_init(RemminaProtocolWidget *gp);
+void remmina_rdp_event_uninit(RemminaProtocolWidget *gp);
+void remmina_rdp_event_update_scale(RemminaProtocolWidget *gp);
+void remmina_rdp_event_unfocus(RemminaProtocolWidget *gp);
+void remmina_rdp_event_send_delayed_monitor_layout(RemminaProtocolWidget *gp);
+void remmina_rdp_event_update_rect(RemminaProtocolWidget *gp, gint x, gint y, gint w, gint h);
+void remmina_rdp_event_queue_ui_async(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui);
+int remmina_rdp_event_queue_ui_sync_retint(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui);
+void *remmina_rdp_event_queue_ui_sync_retptr(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui);
+gboolean remmina_rdp_event_on_map(RemminaProtocolWidget *gp);
+gboolean remmina_rdp_event_on_unmap(RemminaProtocolWidget *gp);
+void remmina_rdp_mouse_jitter(RemminaProtocolWidget *gp);
+
+G_END_DECLS
diff --git a/plugins/rdp/rdp_file.c b/plugins/rdp/rdp_file.c
new file mode 100644
index 0000000..fc161e8
--- /dev/null
+++ b/plugins/rdp/rdp_file.c
@@ -0,0 +1,299 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010-2011 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL. * If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. * If you
+ * do not wish to do so, delete this exception statement from your
+ * version. * If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ *
+ */
+
+#include "remmina/plugin.h"
+#include "rdp_plugin.h"
+#include "rdp_file.h"
+
+gboolean remmina_rdp_file_import_test(RemminaFilePlugin *plugin, const gchar *from_file)
+{
+ TRACE_CALL(__func__);
+ gchar *ext;
+
+ ext = strrchr(from_file, '.');
+
+ if (!ext)
+ return FALSE;
+
+ ext++;
+
+ if (g_strcmp0(ext, "RDP") == 0)
+ return TRUE;
+
+ if (g_strcmp0(ext, "rdp") == 0)
+ return TRUE;
+
+ return FALSE;
+}
+
+static void remmina_rdp_file_import_field(RemminaFile *remminafile, const gchar *key, const gchar *value)
+{
+ TRACE_CALL(__func__);
+ if (g_strcmp0(key, "desktopwidth") == 0) {
+ remmina_plugin_service->file_set_string(remminafile, "resolution_width", value);
+ } else if (g_strcmp0(key, "desktopheight") == 0) {
+ remmina_plugin_service->file_set_string(remminafile, "resolution_height", value);
+ } else if (g_strcmp0(key, "session bpp") == 0) {
+ remmina_plugin_service->file_set_string(remminafile, "colordepth", value);
+ } else if (g_strcmp0(key, "keyboardhook") == 0) {
+ remmina_plugin_service->file_set_int(remminafile, "keyboard_grab", (atoi(value) == 1));
+ } else if (g_strcmp0(key, "full address") == 0) {
+ remmina_plugin_service->file_set_string(remminafile, "server", value);
+ } else if (g_strcmp0(key, "audiomode") == 0) {
+ switch (atoi(value)) {
+ case 0:
+ remmina_plugin_service->file_set_string(remminafile, "sound", "local");
+ break;
+ case 1:
+ remmina_plugin_service->file_set_string(remminafile, "sound", "remote");
+ break;
+ }
+ } else if (g_strcmp0(key, "redirectprinters") == 0) {
+ remmina_plugin_service->file_set_int(remminafile, "shareprinter", (atoi(value) == 1));
+ } else if (g_strcmp0(key, "redirectsmartcard") == 0) {
+ remmina_plugin_service->file_set_int(remminafile, "sharesmartcard", (atoi(value) == 1));
+ } else if (g_strcmp0(key, "redirectclipboard") == 0) {
+ remmina_plugin_service->file_set_int(remminafile, "disableclipboard", (atoi(value) != 1));
+ } else if (g_strcmp0(key, "alternate shell") == 0) {
+ remmina_plugin_service->file_set_string(remminafile, "exec", value);
+ } else if (g_strcmp0(key, "shell working directory") == 0) {
+ remmina_plugin_service->file_set_string(remminafile, "execpath", value);
+ } else if (g_strcmp0(key, "loadbalanceinfo") == 0) {
+ remmina_plugin_service->file_set_string(remminafile, "loadbalanceinfo", value);
+ } else if (g_strcmp0(key, "gatewayhostname") == 0) {
+ remmina_plugin_service->file_set_string(remminafile, "gateway_server", value);
+ } else if (g_strcmp0(key, "gatewayusagemethod") == 0) {
+ remmina_plugin_service->file_set_int(remminafile, "gateway_usage", (atoi(value) == TSC_PROXY_MODE_DETECT));
+ } else if (g_strcmp0(key, "gatewayaccesstoken") == 0) {
+ remmina_plugin_service->file_set_string(remminafile, "gatewayaccesstoken", value);
+ } else if (g_strcmp0(key, "authentication level") == 0) {
+ remmina_plugin_service->file_set_int(remminafile, "authentication level", atoi(value));
+ }
+ /* tsclient fields, import only */
+ else if (g_strcmp0(key, "client hostname") == 0) {
+ remmina_plugin_service->file_set_string(remminafile, "clientname", value);
+ } else if (g_strcmp0(key, "domain") == 0) {
+ remmina_plugin_service->file_set_string(remminafile, "domain", value);
+ } else if (g_strcmp0(key, "username") == 0) {
+ remmina_plugin_service->file_set_string(remminafile, "username", value);
+ } else if (g_strcmp0(key, "password") == 0) {
+ remmina_plugin_service->file_set_string(remminafile, "password", value);
+ }
+}
+
+static RemminaFile *remmina_rdp_file_import_channel(GIOChannel *channel)
+{
+ TRACE_CALL(__func__);
+ gchar *p;
+ const gchar *enc;
+ gchar *line = NULL;
+ GError *error = NULL;
+ gsize bytes_read = 0;
+ RemminaFile *remminafile;
+ guchar magic[2] = { 0 };
+
+ if (g_io_channel_set_encoding(channel, NULL, &error) != G_IO_STATUS_NORMAL) {
+ g_print("g_io_channel_set_encoding: %s\n", error->message);
+ return NULL;
+ }
+
+ /* Try to detect the UTF-16 encoding */
+ if (g_io_channel_read_chars(channel, (gchar *)magic, 2, &bytes_read, &error) != G_IO_STATUS_NORMAL) {
+ g_print("g_io_channel_read_chars: %s\n", error->message);
+ return NULL;
+ }
+
+ if (magic[0] == 0xFF && magic[1] == 0xFE) {
+ enc = "UTF-16LE";
+ } else if (magic[0] == 0xFE && magic[1] == 0xFF) {
+ enc = "UTF-16BE";
+ } else {
+ enc = "UTF-8";
+ if (g_io_channel_seek_position(channel, 0, G_SEEK_SET, &error) != G_IO_STATUS_NORMAL) {
+ g_print("g_io_channel_seek: failed\n");
+ return NULL;
+ }
+ }
+
+ if (g_io_channel_set_encoding(channel, enc, &error) != G_IO_STATUS_NORMAL) {
+ g_print("g_io_channel_set_encoding: %s\n", error->message);
+ return NULL;
+ }
+
+ remminafile = remmina_plugin_service->file_new();
+
+ while (g_io_channel_read_line(channel, &line, NULL, &bytes_read, &error) == G_IO_STATUS_NORMAL) {
+ if (line == NULL)
+ break;
+
+ line[bytes_read] = '\0';
+ p = strchr(line, ':');
+
+ if (p) {
+ *p++ = '\0';
+ p = strchr(p, ':');
+
+ if (p) {
+ p++;
+ remmina_rdp_file_import_field(remminafile, line, p);
+ }
+ }
+
+ g_free(line);
+ }
+
+ remmina_plugin_service->file_set_string(remminafile, "name",
+ remmina_plugin_service->file_get_string(remminafile, "server"));
+ remmina_plugin_service->file_set_string(remminafile, "protocol", "RDP");
+
+ return remminafile;
+}
+
+RemminaFile *remmina_rdp_file_import(RemminaFilePlugin *plugin,const gchar *from_file)
+{
+ TRACE_CALL(__func__);
+ GIOChannel *channel;
+ GError *error = NULL;
+ RemminaFile *remminafile;
+
+ channel = g_io_channel_new_file(from_file, "r", &error);
+
+ if (channel == NULL) {
+ g_print("Failed to import %s: %s\n", from_file, error->message);
+ return NULL;
+ }
+
+ remminafile = remmina_rdp_file_import_channel(channel);
+ g_io_channel_shutdown(channel, TRUE, &error);
+
+ return remminafile;
+}
+
+gboolean remmina_rdp_file_export_test(RemminaFilePlugin *plugin, RemminaFile *remminafile)
+{
+ TRACE_CALL(__func__);
+ if (g_strcmp0(remmina_plugin_service->file_get_string(remminafile, "protocol"), "RDP") == 0)
+ return TRUE;
+
+ return FALSE;
+}
+
+gboolean remmina_rdp_file_export_channel(RemminaFile *remminafile, FILE *fp)
+{
+ TRACE_CALL(__func__);
+ const gchar *cs;
+ int w, h;
+
+ fprintf(fp, "screen mode id:i:2\r\n");
+ w = remmina_plugin_service->file_get_int(remminafile, "resolution_width", -1);
+ h = remmina_plugin_service->file_get_int(remminafile, "resolution_height", -1);
+ if (w > 0 && h > 0) {
+ fprintf(fp, "desktopwidth:i:%d\r\n", w);
+ fprintf(fp, "desktopheight:i:%d\r\n", h);
+ }
+
+ fprintf(fp, "session bpp:i:%i\r\n", remmina_plugin_service->file_get_int(remminafile, "colordepth", 8));
+ //fprintf(fp, "winposstr:s:0,1,123,34,931,661\r\n");
+ fprintf(fp, "compression:i:1\r\n");
+ fprintf(fp, "keyboardhook:i:2\r\n");
+ fprintf(fp, "displayconnectionbar:i:1\r\n");
+ fprintf(fp, "disable wallpaper:i:1\r\n");
+ fprintf(fp, "disable full window drag:i:1\r\n");
+ fprintf(fp, "allow desktop composition:i:0\r\n");
+ fprintf(fp, "allow font smoothing:i:0\r\n");
+ fprintf(fp, "disable menu anims:i:1\r\n");
+ fprintf(fp, "disable themes:i:0\r\n");
+ fprintf(fp, "disable cursor setting:i:0\r\n");
+ fprintf(fp, "bitmapcachepersistenable:i:1\r\n");
+ cs = remmina_plugin_service->file_get_string(remminafile, "server");
+ fprintf(fp, "full address:s:%s\r\n", cs ? cs : "");
+ if (g_strcmp0(remmina_plugin_service->file_get_string(remminafile, "sound"), "local") == 0)
+ fprintf(fp, "audiomode:i:0\r\n");
+ else if (g_strcmp0(remmina_plugin_service->file_get_string(remminafile, "sound"), "remote") == 0)
+ fprintf(fp, "audiomode:i:1\r\n");
+ else
+ fprintf(fp, "audiomode:i:2\r\n");
+ if (g_strcmp0(remmina_plugin_service->file_get_string(remminafile, "microphone"), "") == 0)
+ fprintf(fp, "audiocapturemode:i:0\r\n");
+ else if (g_strcmp0(remmina_plugin_service->file_get_string(remminafile, "microphone"), "0") == 0)
+ fprintf(fp, "audiocapturemode:i:1\r\n");
+ else
+ fprintf(fp, "audiocapturemode:i:1\r\n");
+ fprintf(fp, "redirectprinters:i:%i\r\n", remmina_plugin_service->file_get_int(remminafile, "shareprinter", FALSE) ? 1 : 0);
+ fprintf(fp, "redirectsmartcard:i:%i\r\n", remmina_plugin_service->file_get_int(remminafile, "sharesmartcard", FALSE) ? 1 : 0);
+ fprintf(fp, "redirectcomports:i:0\r\n");
+ fprintf(fp, "redirectsmartcards:i:0\r\n");
+ fprintf(fp, "redirectclipboard:i:1\r\n");
+ fprintf(fp, "redirectposdevices:i:0\r\n");
+ fprintf(fp, "autoreconnection enabled:i:1\r\n");
+ fprintf(fp, "authentication level:i:0\r\n");
+ fprintf(fp, "prompt for credentials:i:1\r\n");
+ fprintf(fp, "negotiate security layer:i:1\r\n");
+ fprintf(fp, "remoteapplicationmode:i:0\r\n");
+ cs = remmina_plugin_service->file_get_string(remminafile, "exec");
+ fprintf(fp, "alternate shell:s:%s\r\n", cs ? cs : "");
+ cs = remmina_plugin_service->file_get_string(remminafile, "execpath");
+ fprintf(fp, "shell working directory:s:%s\r\n", cs ? cs : "");
+ cs = remmina_plugin_service->file_get_string(remminafile, "gateway_server");
+ fprintf(fp, "gatewayhostname:s:%s\r\n", cs ? cs : "");
+ fprintf(fp, "gatewayusagemethod:i:4\r\n");
+ fprintf(fp, "gatewaycredentialssource:i:4\r\n");
+ fprintf(fp, "gatewayprofileusagemethod:i:0\r\n");
+ fprintf(fp, "precommand:s:\r\n");
+ fprintf(fp, "promptcredentialonce:i:1\r\n");
+ fprintf(fp, "drivestoredirect:s:\r\n");
+
+ return TRUE;
+}
+
+gboolean remmina_rdp_file_export(RemminaFilePlugin *plugin, RemminaFile *remminafile, const gchar *to_file)
+{
+ TRACE_CALL(__func__);
+ FILE *fp;
+ gboolean ret;
+
+ fp = g_fopen(to_file, "w+");
+
+ if (fp == NULL) {
+ g_print("Failed to export %s\n", to_file);
+ return FALSE;
+ }
+
+ ret = remmina_rdp_file_export_channel(remminafile, fp);
+ fclose(fp);
+
+ return ret;
+}
diff --git a/plugins/rdp/rdp_file.h b/plugins/rdp/rdp_file.h
new file mode 100644
index 0000000..0196293
--- /dev/null
+++ b/plugins/rdp/rdp_file.h
@@ -0,0 +1,48 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010-2011 Vic Lee
+ * Copyright (C) 2017-2023 Antenore Gatta, Giovanni Panozzo
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL. * If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. * If you
+ * do not wish to do so, delete this exception statement from your
+ * version. * If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ *
+ */
+
+#pragma once
+
+#include <glib.h>
+#include "common/remmina_plugin.h"
+
+G_BEGIN_DECLS
+
+gboolean remmina_rdp_file_import_test(RemminaFilePlugin *plugin, const gchar *from_file);
+RemminaFile *remmina_rdp_file_import(RemminaFilePlugin *plugin, const gchar *from_file);
+gboolean remmina_rdp_file_export_test(RemminaFilePlugin *plugin, RemminaFile *remminafile);
+gboolean remmina_rdp_file_export(RemminaFilePlugin *plugin, RemminaFile *remminafile, const gchar *to_file);
+
+G_END_DECLS
diff --git a/plugins/rdp/rdp_graphics.c b/plugins/rdp/rdp_graphics.c
new file mode 100644
index 0000000..b97f30e
--- /dev/null
+++ b/plugins/rdp/rdp_graphics.c
@@ -0,0 +1,163 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010 Jay Sorg
+ * Copyright (C) 2010-2011 Vic Lee
+ * Copyright (C) 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL. * If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. * If you
+ * do not wish to do so, delete this exception statement from your
+ * version. * If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ *
+ */
+
+#include "rdp_plugin.h"
+#include "rdp_event.h"
+#include "rdp_graphics.h"
+
+#include <freerdp/codec/color.h>
+#include <freerdp/codec/bitmap.h>
+#include <winpr/memory.h>
+
+//#define RF_BITMAP
+//#define RF_GLYPH
+
+/* Pointer Class */
+#if FREERDP_VERSION_MAJOR < 3
+#define CONST_ARG const
+#else
+#define CONST_ARG
+#endif
+
+static BOOL rf_Pointer_New(rdpContext* context, rdpPointer* pointer)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginRdpUiObject* ui;
+ rfContext* rfi = (rfContext*)context;
+
+ if (pointer->xorMaskData != 0) {
+ ui = g_new0(RemminaPluginRdpUiObject, 1);
+ ui->type = REMMINA_RDP_UI_CURSOR;
+ ui->cursor.context = context;
+ ui->cursor.pointer = (rfPointer*)pointer;
+ ui->cursor.type = REMMINA_RDP_POINTER_NEW;
+ return remmina_rdp_event_queue_ui_sync_retint(rfi->protocol_widget, ui) ? TRUE : FALSE;
+ }
+ return FALSE;
+}
+
+static void rf_Pointer_Free(rdpContext* context, rdpPointer* pointer)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginRdpUiObject* ui;
+ rfContext* rfi = (rfContext*)context;
+
+ if (G_IS_OBJECT(((rfPointer*)pointer)->cursor))
+ {
+ ui = g_new0(RemminaPluginRdpUiObject, 1);
+ ui->type = REMMINA_RDP_UI_CURSOR;
+ ui->cursor.context = context;
+ ui->cursor.pointer = (rfPointer*)pointer;
+ ui->cursor.type = REMMINA_RDP_POINTER_FREE;
+ remmina_rdp_event_queue_ui_sync_retint(rfi->protocol_widget, ui);
+ }
+}
+
+static BOOL rf_Pointer_Set(rdpContext* context, CONST_ARG rdpPointer* pointer)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginRdpUiObject* ui;
+ rfContext* rfi = (rfContext*)context;
+
+ ui = g_new0(RemminaPluginRdpUiObject, 1);
+ ui->type = REMMINA_RDP_UI_CURSOR;
+ ui->cursor.pointer = (rfPointer*)pointer;
+ ui->cursor.type = REMMINA_RDP_POINTER_SET;
+
+ return remmina_rdp_event_queue_ui_sync_retint(rfi->protocol_widget, ui) ? TRUE : FALSE;
+
+}
+
+static BOOL rf_Pointer_SetNull(rdpContext* context)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginRdpUiObject* ui;
+ rfContext* rfi = (rfContext*)context;
+
+ ui = g_new0(RemminaPluginRdpUiObject, 1);
+ ui->type = REMMINA_RDP_UI_CURSOR;
+ ui->cursor.type = REMMINA_RDP_POINTER_NULL;
+
+ return remmina_rdp_event_queue_ui_sync_retint(rfi->protocol_widget, ui) ? TRUE : FALSE;
+}
+
+static BOOL rf_Pointer_SetDefault(rdpContext* context)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginRdpUiObject* ui;
+ rfContext* rfi = (rfContext*)context;
+
+ ui = g_new0(RemminaPluginRdpUiObject, 1);
+ ui->type = REMMINA_RDP_UI_CURSOR;
+ ui->cursor.type = REMMINA_RDP_POINTER_DEFAULT;
+
+ return remmina_rdp_event_queue_ui_sync_retint(rfi->protocol_widget, ui) ? TRUE : FALSE;
+}
+
+static BOOL rf_Pointer_SetPosition(rdpContext* context, UINT32 x, UINT32 y)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginRdpUiObject* ui;
+ rfContext* rfi = (rfContext*)context;
+ ui = g_new0(RemminaPluginRdpUiObject, 1);
+ ui->type = REMMINA_RDP_UI_CURSOR;
+ ui->cursor.type = REMMINA_RDP_POINTER_SETPOS;
+ ui->pos.x = x;
+ ui->pos.y = y;
+
+ return remmina_rdp_event_queue_ui_sync_retint(rfi->protocol_widget, ui) ? TRUE : FALSE;
+}
+
+/* Graphics Module */
+
+void rf_register_graphics(rdpGraphics* graphics)
+{
+ TRACE_CALL(__func__);
+ rdpPointer pointer={0};
+
+ pointer.size = sizeof(rfPointer);
+
+ pointer.New = rf_Pointer_New;
+ pointer.Free = rf_Pointer_Free;
+ pointer.Set = rf_Pointer_Set;
+ pointer.SetNull = rf_Pointer_SetNull;
+ pointer.SetDefault = rf_Pointer_SetDefault;
+ pointer.SetPosition = rf_Pointer_SetPosition;
+
+ graphics_register_pointer(graphics, &pointer);
+}
diff --git a/plugins/rdp/rdp_graphics.h b/plugins/rdp/rdp_graphics.h
new file mode 100644
index 0000000..1c290f9
--- /dev/null
+++ b/plugins/rdp/rdp_graphics.h
@@ -0,0 +1,40 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010-2011 Vic Lee
+ * Copyright (C) 2017-2023 Antenore Gatta, Giovanni Panozzo
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL. * If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. * If you
+ * do not wish to do so, delete this exception statement from your
+ * version. * If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ *
+ */
+
+#pragma once
+
+#include "rdp_plugin.h"
+
+void rf_register_graphics(rdpGraphics *graphics);
diff --git a/plugins/rdp/rdp_monitor.c b/plugins/rdp/rdp_monitor.c
new file mode 100644
index 0000000..4aaf413
--- /dev/null
+++ b/plugins/rdp/rdp_monitor.c
@@ -0,0 +1,231 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2016-2020 Antenore Gatta, Giovanni Panozzo
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL. * If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. * If you
+ * do not wish to do so, delete this exception statement from your
+ * version. * If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ *
+ */
+
+#include "rdp_plugin.h"
+#include "rdp_monitor.h"
+
+/** @ToDo Utility functions should be moved somewhere else */
+gint remmina_rdp_utils_strpos(const gchar *haystack, const gchar *needle)
+{
+ TRACE_CALL(__func__);
+ const gchar *sub;
+
+ if (!*needle)
+ return -1;
+
+ sub = strstr(haystack, needle);
+ if (!sub)
+ return -1;
+
+ return sub - haystack;
+}
+
+/* https://github.com/adlocode/xfwm4/blob/1d21be9ffc0fa1cea91905a07d1446c5227745f4/common/xfwm-common.c */
+
+
+/**
+ * Set the MonitorIDs, the maxwidth and maxheight
+ *
+ * - number of monitors
+ * - Geometry of each
+ * - Choosen by the user
+ * - Primary monitor
+ * - Otherwise use current monitor as the origin
+ *
+ * The origin must be 0,0
+ */
+void remmina_rdp_monitor_get (rfContext *rfi, gchar **monitorids, guint32 *maxwidth, guint32 *maxheight)
+{
+ TRACE_CALL(__func__);
+
+ GdkDisplay *display;
+ GdkMonitor *monitor;
+ gboolean has_custom_monitors = FALSE;
+
+ gboolean primary_found = FALSE;
+
+ gint n_monitors;
+ gint scale;
+ gint index = 0;
+ gint count = 0;
+
+ static gchar buffer[256];
+ gint buffer_offset = 0;
+
+ GdkRectangle geometry = { 0, 0, 0, 0 };
+ GdkRectangle tempgeom = { 0, 0, 0, 0 };
+ GdkRectangle destgeom = { 0, 0, 0, 0 };
+ rdpSettings* settings;
+ if (!rfi || !rfi->clientContext.context.settings)
+ return;
+
+ settings = rfi->clientContext.context.settings;
+
+ *maxwidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ *maxheight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+
+ display = gdk_display_get_default ();
+ n_monitors = gdk_display_get_n_monitors(display);
+
+ /* Get monitor at windows curently in use */
+ //w = gtk_widget_get_window(rfi->drawing_area);
+
+ //current_monitor = gdk_display_get_monitor_at_window (display, w);
+
+ /* we got monitorids as options */
+ if (*monitorids)
+ has_custom_monitors = TRUE;
+
+ rdpMonitor* base = (rdpMonitor *)freerdp_settings_get_pointer(settings, FreeRDP_MonitorDefArray);
+ for (gint i = 0; i < n_monitors; ++i) {
+ rdpMonitor* current;
+ if (has_custom_monitors) {
+ REMMINA_PLUGIN_DEBUG("We have custom monitors");
+ gchar itoc[11];
+ snprintf(itoc, sizeof(itoc), "%d", i);
+ if (remmina_rdp_utils_strpos(*monitorids, itoc) < 0 ) {
+ REMMINA_PLUGIN_DEBUG("Monitor n %d it's out of the provided list", i);
+ index += 1;
+ continue;
+ }
+ }
+
+ monitor = gdk_display_get_monitor(display, i);
+ if (monitor == NULL) {
+ REMMINA_PLUGIN_DEBUG("Monitor n %d does not exist or is not active", i);
+ index +=1;
+ continue;
+ }
+
+ monitor = gdk_display_get_monitor(display, index);
+ current = &base[index];
+ REMMINA_PLUGIN_DEBUG("Monitor n %d", index);
+ /* If the desktop env in use doesn't have the working area concept
+ * gdk_monitor_get_workarea will return the monitor geometry*/
+ //gdk_monitor_get_workarea (monitor, &geometry);
+ gdk_monitor_get_geometry (monitor, &geometry);
+ current->x = geometry.x;
+ REMMINA_PLUGIN_DEBUG("Monitor n %d x: %d", index, geometry.x);
+ current->y = geometry.y;
+ REMMINA_PLUGIN_DEBUG("Monitor n %d y: %d", index, geometry.y);
+ /* geometry contain the application geometry, to obtain the real one
+ * we must multiply by the scale factor */
+ scale = gdk_monitor_get_scale_factor (monitor);
+ REMMINA_PLUGIN_DEBUG("Monitor n %d scale: %d", index, scale);
+ geometry.x *= scale;
+ geometry.y *= scale;
+ geometry.width *= scale;
+ geometry.height *= scale;
+ REMMINA_PLUGIN_DEBUG("Monitor n %d width: %d", index, geometry.width);
+ REMMINA_PLUGIN_DEBUG("Monitor n %d height: %d", index, geometry.height);
+ current->width = geometry.width;
+ current->height = geometry.height;
+ current->attributes.physicalHeight = gdk_monitor_get_height_mm (monitor);
+ REMMINA_PLUGIN_DEBUG("Monitor n %d physical height: %d", i, current->attributes.physicalHeight);
+ current->attributes.physicalWidth = gdk_monitor_get_width_mm (monitor);
+ REMMINA_PLUGIN_DEBUG("Monitor n %d physical width: %d", i, current->attributes.physicalWidth);
+ current->orig_screen = index;
+ if (!primary_found) {
+ freerdp_settings_set_uint32(settings, FreeRDP_MonitorLocalShiftX, current->x);
+ freerdp_settings_set_uint32(settings, FreeRDP_MonitorLocalShiftY, current->y);
+ }
+ if (gdk_monitor_is_primary(monitor)) {
+ REMMINA_PLUGIN_DEBUG ("Primary monitor found with id: %d", index);
+ current->is_primary = TRUE;
+ primary_found = TRUE;
+ if (current->x != 0 || current->y != 0)
+ {
+ REMMINA_PLUGIN_DEBUG ("Primary monitor not at 0,0 coordinates: %d", index);
+ freerdp_settings_set_uint32(settings, FreeRDP_MonitorLocalShiftX, current->x);
+ freerdp_settings_set_uint32(settings, FreeRDP_MonitorLocalShiftY, current->y);
+ }
+ } else {
+ if (!primary_found && current->x == 0 &&
+ current->y == 0)
+ {
+ REMMINA_PLUGIN_DEBUG ("Monitor %d has 0,0 coordinates", index);
+ current->is_primary = TRUE;
+ freerdp_settings_set_uint32(settings, FreeRDP_MonitorLocalShiftX, current->x);
+ freerdp_settings_set_uint32(settings, FreeRDP_MonitorLocalShiftY, current->y);
+ primary_found = TRUE;
+ REMMINA_PLUGIN_DEBUG ("Primary monitor set to id: %d", index);
+ }
+ }
+ REMMINA_PLUGIN_DEBUG ("Local X Shift: %d", freerdp_settings_get_uint32(settings, FreeRDP_MonitorLocalShiftX));
+ REMMINA_PLUGIN_DEBUG ("Local Y Shift: %d", freerdp_settings_get_uint32(settings, FreeRDP_MonitorLocalShiftY));
+ //current->x =
+ //current->x - freerdp_settings_get_uint32(settings, FreeRDP_MonitorLocalShiftX);
+ //REMMINA_PLUGIN_DEBUG("Monitor n %d calculated x: %d", index, current->x);
+ //current->y =
+ //current->y - freerdp_settings_get_uint32(settings, FreeRDP_MonitorLocalShiftY);
+ //REMMINA_PLUGIN_DEBUG("Monitor n %d calculated y: %d", index, current->y);
+
+ if (buffer_offset == 0)
+ buffer_offset = g_sprintf(buffer + buffer_offset, "%d", i);
+ else
+ buffer_offset = g_sprintf(buffer + buffer_offset, ",%d", i);
+ REMMINA_PLUGIN_DEBUG("Monitor IDs buffer: %s", buffer);
+ gdk_rectangle_union(&tempgeom, &geometry, &destgeom);
+ memcpy(&tempgeom, &destgeom, sizeof tempgeom);
+ count++;
+ index++;
+
+ }
+ freerdp_settings_set_uint32(settings, FreeRDP_MonitorCount, index);
+ /* Subtract monitor shift from monitor variables for server-side use.
+ * We maintain monitor shift value as Window requires the primary monitor to have a
+ * coordinate of 0,0 In some X configurations, no monitor may have a coordinate of 0,0. This
+ * can also be happen if the user requests specific monitors from the command-line as well.
+ * So, we make sure to translate our primary monitor's upper-left corner to 0,0 on the
+ * server.
+ */
+ for (gint i = 0; i < freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount); i++)
+ {
+ rdpMonitor* current = &base[i];
+ current->x =
+ current->x - freerdp_settings_get_uint32(settings, FreeRDP_MonitorLocalShiftX);
+ REMMINA_PLUGIN_DEBUG("Monitor n %d calculated x: %d", i, current->x);
+ current->y =
+ current->y - freerdp_settings_get_uint32(settings, FreeRDP_MonitorLocalShiftY);
+ REMMINA_PLUGIN_DEBUG("Monitor n %d calculated y: %d", i, current->y);
+ }
+
+ REMMINA_PLUGIN_DEBUG("%d monitors on %d have been configured", freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount), count);
+ *maxwidth = destgeom.width;
+ *maxheight = destgeom.height;
+ REMMINA_PLUGIN_DEBUG("maxw and maxh: %ux%u", *maxwidth, *maxheight);
+ if (n_monitors > 1)
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_SupportMonitorLayoutPdu, TRUE);
+ *monitorids = g_strdup(buffer);
+}
diff --git a/plugins/rdp/rdp_monitor.h b/plugins/rdp/rdp_monitor.h
new file mode 100644
index 0000000..1c8f08d
--- /dev/null
+++ b/plugins/rdp/rdp_monitor.h
@@ -0,0 +1,45 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2016-2020 Antenore Gatta, Giovanni Panozzo
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL. * If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. * If you
+ * do not wish to do so, delete this exception statement from your
+ * version. * If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ *
+ */
+
+#pragma once
+
+
+#include <freerdp/freerdp.h>
+#include "rdp_plugin.h"
+
+G_BEGIN_DECLS
+
+void remmina_rdp_monitor_get (rfContext *rfi, gchar **monitorids, guint32 *maxwidth, guint32 *maxheight);
+
+G_END_DECLS
diff --git a/plugins/rdp/rdp_plugin.c b/plugins/rdp/rdp_plugin.c
new file mode 100644
index 0000000..68ea505
--- /dev/null
+++ b/plugins/rdp/rdp_plugin.c
@@ -0,0 +1,3363 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010-2011 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * Copyright (C) 2016-2022 Antenore Gatta, Giovanni Panozzo
+ * Copyright (C) 2022-2023 Antenore Gatta, Giovanni Panozzo, Hiroyuki Tanaka
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL. * If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. * If you
+ * do not wish to do so, delete this exception statement from your
+ * version. * If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ *
+ */
+
+
+#define _GNU_SOURCE
+
+#include "remmina/plugin.h"
+#include "rdp_plugin.h"
+#include "rdp_event.h"
+#include "rdp_graphics.h"
+#include "rdp_file.h"
+#include "rdp_settings.h"
+#include "rdp_cliprdr.h"
+#include "rdp_monitor.h"
+#include "rdp_channels.h"
+
+#include <errno.h>
+#include <pthread.h>
+#include <time.h>
+#include <sys/time.h>
+#ifdef GDK_WINDOWING_X11
+#include <cairo/cairo-xlib.h>
+#else
+#include <cairo/cairo.h>
+#endif
+#include <ctype.h>
+#include <freerdp/addin.h>
+#include <freerdp/assistance.h>
+#if FREERDP_VERSION_MAJOR >= 3
+#include <freerdp/channels/rdp2tcp.h>
+#else
+#define RDP2TCP_DVC_CHANNEL_NAME "rdp2tcp"
+#endif
+#include <freerdp/client/channels.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/client/cmdline.h>
+#include <freerdp/constants.h>
+#include <freerdp/error.h>
+#include <freerdp/event.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/settings.h>
+#include <winpr/cmdline.h>
+#include <winpr/memory.h>
+
+#ifdef HAVE_CUPS
+#include <cups/cups.h>
+#endif
+
+#include <unistd.h>
+#include <string.h>
+
+#ifdef GDK_WINDOWING_X11
+#include <X11/Xlib.h>
+#include <X11/XKBlib.h>
+#include <gdk/gdkx.h>
+#elif defined(GDK_WINDOWING_WAYLAND)
+#include <gdk/gdkwayland.h>
+#endif
+
+#if defined(__FreeBSD__)
+#include <pthread_np.h>
+#endif
+
+#include <freerdp/locale/keyboard.h>
+
+#define REMMINA_RDP_FEATURE_TOOL_REFRESH 1
+#define REMMINA_RDP_FEATURE_SCALE 2
+#define REMMINA_RDP_FEATURE_UNFOCUS 3
+#define REMMINA_RDP_FEATURE_TOOL_SENDCTRLALTDEL 4
+#define REMMINA_RDP_FEATURE_DYNRESUPDATE 5
+#define REMMINA_RDP_FEATURE_MULTIMON 6
+#define REMMINA_RDP_FEATURE_VIEWONLY 7
+
+#define REMMINA_CONNECTION_TYPE_NONE 0
+
+#if FREERDP_VERSION_MAJOR >= 3
+ #define CLPARAM const char
+#else
+ #define CLPARAM char
+#endif
+
+#if FREERDP_VERSION_MAJOR < 3
+static HANDLE freerdp_abort_event(rdpContext* context) {
+ WINPR_ASSERT(context);
+ return context->abortEvent;
+}
+
+static BOOL freerdp_settings_set_pointer_len(rdpSettings* settings, size_t id, const void* data, size_t len)
+{
+ switch(id) {
+ case FreeRDP_LoadBalanceInfo:
+ free(settings->LoadBalanceInfo);
+ settings->LoadBalanceInfo = _strdup(data);
+ settings->LoadBalanceInfoLength = len;
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+static void freerdp_abort_connect_context(rdpContext* context) {
+ WINPR_ASSERT(context);
+ freerdp_abort_connect(context->instance);
+}
+#endif
+
+RemminaPluginService *remmina_plugin_service = NULL;
+
+static BOOL gfx_h264_available = FALSE;
+// keep track of last interaction time for keep alive
+static time_t last_time;
+
+/* Compatibility: these functions have been introduced with https://github.com/FreeRDP/FreeRDP/commit/8c5d96784d
+ * and are missing on older FreeRDP, so we add them here.
+ * They should be removed from here after all distributed versions of FreeRDP (libwinpr) will have
+ * CommandLineParseCommaSeparatedValuesEx() onboard.
+ *
+ * (C) Copyright goes to the FreeRDP authors.
+ */
+static CLPARAM **remmina_rdp_CommandLineParseCommaSeparatedValuesEx(const char *name, const char *list, size_t *count)
+{
+ TRACE_CALL(__func__);
+#if FREERDP_CHECK_VERSION(2, 0, 0)
+ return (CLPARAM **)CommandLineParseCommaSeparatedValuesEx(name, list, count);
+#else
+ char **p;
+ char *str;
+ size_t nArgs;
+ size_t index;
+ size_t nCommas;
+ size_t prefix, len;
+
+ nCommas = 0;
+
+ if (count == NULL)
+ return NULL;
+
+ *count = 0;
+
+ if (!list) {
+ if (name) {
+ size_t len = strlen(name);
+ p = (char **)calloc(2UL + len, sizeof(char *));
+
+ if (p) {
+ char *dst = (char *)&p[1];
+ p[0] = dst;
+ sprintf_s(dst, len + 1, "%s", name);
+ *count = 1;
+ return p;
+ }
+ }
+
+ return NULL;
+ }
+
+ {
+ const char *it = list;
+
+ while ((it = strchr(it, ',')) != NULL) {
+ it++;
+ nCommas++;
+ }
+ }
+
+ nArgs = nCommas + 1;
+
+ if (name)
+ nArgs++;
+
+ prefix = (nArgs + 1UL) * sizeof(char *);
+ len = strlen(list);
+ p = (char **)calloc(len + prefix + 1, sizeof(char *));
+
+ if (!p)
+ return NULL;
+
+ str = &((char *)p)[prefix];
+ memcpy(str, list, len);
+
+ if (name)
+ p[0] = (char *)name;
+
+ for (index = name ? 1 : 0; index < nArgs; index++) {
+ char *comma = strchr(str, ',');
+ p[index] = str;
+
+ if (comma) {
+ str = comma + 1;
+ *comma = '\0';
+ }
+ }
+
+ *count = nArgs;
+ return p;
+#endif
+}
+
+static CLPARAM **remmina_rdp_CommandLineParseCommaSeparatedValues(const char *list, size_t *count)
+{
+ TRACE_CALL(__func__);
+ return remmina_rdp_CommandLineParseCommaSeparatedValuesEx(NULL, list, count);
+}
+
+/*
+ * End of CommandLineParseCommaSeparatedValuesEx() compatibility and copyright
+ */
+static BOOL rf_process_event_queue(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ UINT16 flags;
+ rdpInput *input;
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+ RemminaPluginRdpEvent *event;
+ DISPLAY_CONTROL_MONITOR_LAYOUT *dcml;
+ CLIPRDR_FORMAT_DATA_RESPONSE response = { 0 };
+ RemminaFile *remminafile;
+
+ if (rfi->event_queue == NULL)
+ return true;
+
+ input = rfi->clientContext.context.input;
+
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ while ((event = (RemminaPluginRdpEvent *)g_async_queue_try_pop(rfi->event_queue)) != NULL) {
+ time(&last_time); //update last user interaction time
+ switch (event->type) {
+ case REMMINA_RDP_EVENT_TYPE_SCANCODE:
+
+ if (event->key_event.extended1){
+ flags = KBD_FLAGS_EXTENDED1;
+ }
+ else{
+ flags = event->key_event.extended ? KBD_FLAGS_EXTENDED : 0;
+ }
+ flags |= event->key_event.up ? KBD_FLAGS_RELEASE : KBD_FLAGS_DOWN;
+ input->KeyboardEvent(input, flags, event->key_event.key_code);
+ break;
+
+ case REMMINA_RDP_EVENT_TYPE_SCANCODE_UNICODE:
+ /*
+ * TS_UNICODE_KEYBOARD_EVENT RDP message, see https://msdn.microsoft.com/en-us/library/cc240585.aspx
+ */
+ flags = event->key_event.up ? KBD_FLAGS_RELEASE : KBD_FLAGS_DOWN;
+ input->UnicodeKeyboardEvent(input, flags, event->key_event.unicode_code);
+ break;
+
+ case REMMINA_RDP_EVENT_TYPE_MOUSE:
+ if (event->mouse_event.extended)
+ input->ExtendedMouseEvent(input, event->mouse_event.flags,
+ event->mouse_event.x, event->mouse_event.y);
+ else
+ input->MouseEvent(input, event->mouse_event.flags,
+ event->mouse_event.x, event->mouse_event.y);
+ break;
+
+ case REMMINA_RDP_EVENT_TYPE_CLIPBOARD_SEND_CLIENT_FORMAT_LIST:
+ rfi->clipboard.context->ClientFormatList(rfi->clipboard.context, event->clipboard_formatlist.pFormatList);
+ free(event->clipboard_formatlist.pFormatList);
+ break;
+
+ case REMMINA_RDP_EVENT_TYPE_CLIPBOARD_SEND_CLIENT_FORMAT_DATA_RESPONSE:
+ {
+ UINT32 msgFlags = (event->clipboard_formatdataresponse.data) ? CB_RESPONSE_OK : CB_RESPONSE_FAIL;
+#if FREERDP_VERSION_MAJOR >= 3
+ response.common.msgFlags = msgFlags;
+ response.common.dataLen = event->clipboard_formatdataresponse.size;
+#else
+ response.msgFlags = msgFlags;
+ response.dataLen = event->clipboard_formatdataresponse.size;
+#endif
+ response.requestedFormatData = event->clipboard_formatdataresponse.data;
+ rfi->clipboard.context->ClientFormatDataResponse(rfi->clipboard.context, &response);
+ }
+ break;
+
+ case REMMINA_RDP_EVENT_TYPE_CLIPBOARD_SEND_CLIENT_FORMAT_DATA_REQUEST:
+ REMMINA_PLUGIN_DEBUG("Sending client FormatDataRequest to server");
+ gettimeofday(&(rfi->clipboard.clientformatdatarequest_tv), NULL);
+ rfi->clipboard.context->ClientFormatDataRequest(rfi->clipboard.context, event->clipboard_formatdatarequest.pFormatDataRequest);
+ free(event->clipboard_formatdatarequest.pFormatDataRequest);
+ break;
+
+ case REMMINA_RDP_EVENT_TYPE_SEND_MONITOR_LAYOUT:
+ if (remmina_plugin_service->file_get_int(remminafile, "multimon", FALSE)) {
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_UseMultimon, TRUE);
+ /* TODO Add an option for this */
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_ForceMultimon, TRUE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_Fullscreen, TRUE);
+ /* got some crashes with g_malloc0, to be investigated */
+ dcml = calloc(freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_MonitorCount), sizeof(DISPLAY_CONTROL_MONITOR_LAYOUT));
+ REMMINA_PLUGIN_DEBUG("REMMINA_RDP_EVENT_TYPE_SEND_MONITOR_LAYOUT:");
+ if (!dcml)
+ break;
+
+ const rdpMonitor *base = freerdp_settings_get_pointer(rfi->clientContext.context.settings, FreeRDP_MonitorDefArray);
+ for (gint i = 0; i < freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_MonitorCount); ++i) {
+ const rdpMonitor *current = &base[i];
+ REMMINA_PLUGIN_DEBUG("Sending display layout for monitor n° %d", i);
+ dcml[i].Flags = (current->is_primary ? DISPLAY_CONTROL_MONITOR_PRIMARY : 0);
+ REMMINA_PLUGIN_DEBUG("Monitor %d is primary: %d", i, dcml[i].Flags);
+ dcml[i].Left = current->x;
+ REMMINA_PLUGIN_DEBUG("Monitor %d x: %d", i, dcml[i].Left);
+ dcml[i].Top = current->y;
+ REMMINA_PLUGIN_DEBUG("Monitor %d y: %d", i, dcml[i].Top);
+ dcml[i].Width = current->width;
+ REMMINA_PLUGIN_DEBUG("Monitor %d width: %d", i, dcml[i].Width);
+ dcml[i].Height = current->height;
+ REMMINA_PLUGIN_DEBUG("Monitor %d height: %d", i, dcml[i].Height);
+ dcml[i].PhysicalWidth = current->attributes.physicalWidth;
+ REMMINA_PLUGIN_DEBUG("Monitor %d physical width: %d", i, dcml[i].PhysicalWidth);
+ dcml[i].PhysicalHeight = current->attributes.physicalHeight;
+ REMMINA_PLUGIN_DEBUG("Monitor %d physical height: %d", i, dcml[i].PhysicalHeight);
+ if (current->attributes.orientation)
+ dcml[i].Orientation = current->attributes.orientation;
+ else
+ dcml[i].Orientation = event->monitor_layout.desktopOrientation;
+ REMMINA_PLUGIN_DEBUG("Monitor %d orientation: %d", i, dcml[i].Orientation);
+ dcml[i].DesktopScaleFactor = event->monitor_layout.desktopScaleFactor;
+ dcml[i].DeviceScaleFactor = event->monitor_layout.deviceScaleFactor;
+ }
+ rfi->dispcontext->SendMonitorLayout(rfi->dispcontext, freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_MonitorCount), dcml);
+ g_free(dcml);
+ } else {
+ dcml = g_malloc0(sizeof(DISPLAY_CONTROL_MONITOR_LAYOUT));
+ if (dcml) {
+ dcml->Flags = DISPLAY_CONTROL_MONITOR_PRIMARY;
+ dcml->Width = event->monitor_layout.width;
+ dcml->Height = event->monitor_layout.height;
+ dcml->Orientation = event->monitor_layout.desktopOrientation;
+ dcml->DesktopScaleFactor = event->monitor_layout.desktopScaleFactor;
+ dcml->DeviceScaleFactor = event->monitor_layout.deviceScaleFactor;
+ rfi->dispcontext->SendMonitorLayout(rfi->dispcontext, 1, dcml);
+ g_free(dcml); \
+ }
+ }
+ break;
+ case REMMINA_RDP_EVENT_DISCONNECT:
+ /* Disconnect requested via GUI (i.e: tab destroy/close) */
+ freerdp_abort_connect_context(&rfi->clientContext.context);
+ break;
+ }
+
+ g_free(event);
+ }
+
+ return true;
+}
+
+static gboolean remmina_rdp_tunnel_init(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+
+ /* Opens the optional SSH tunnel if needed.
+ * Used also when reopening the same tunnel for a FreeRDP reconnect.
+ * Returns TRUE if all OK, and setups correct rfi->Settings values
+ * with connection and certificate parameters */
+
+ gchar *hostport;
+ gchar *s;
+ gchar *host;
+ gchar *cert_host;
+ gint cert_port;
+ gint port;
+
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+
+ REMMINA_PLUGIN_DEBUG("Tunnel init");
+ hostport = remmina_plugin_service->protocol_plugin_start_direct_tunnel(gp, 3389, FALSE);
+ if (hostport == NULL)
+ return FALSE;
+
+ remmina_plugin_service->get_server_port(hostport, 3389, &host, &port);
+
+ if (host[0] == 0)
+ return FALSE;
+
+ REMMINA_PLUGIN_DEBUG("protocol_plugin_start_direct_tunnel() returned %s", hostport);
+
+ cert_host = host;
+ cert_port = port;
+
+ if (!rfi->is_reconnecting) {
+ /* settings->CertificateName and settings->ServerHostname is created
+ * only on 1st connect, not on reconnections */
+
+ freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname, host);
+
+ if (cert_port == 3389) {
+ freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_CertificateName, cert_host);
+ } else {
+ s = g_strdup_printf("%s:%d", cert_host, cert_port);
+ freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_CertificateName, s);
+ g_free(s);
+ }
+ }
+
+ REMMINA_PLUGIN_DEBUG("Tunnel has been optionally initialized. Now connecting to %s:%d", host, port);
+
+ if (cert_host != host) g_free(cert_host);
+ g_free(host);
+ g_free(hostport);
+
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_ServerPort, port);
+
+ return TRUE;
+}
+
+static BOOL rf_auto_reconnect(rfContext *rfi)
+{
+ TRACE_CALL(__func__);
+ rdpSettings *settings = rfi->clientContext.context.settings;
+ RemminaPluginRdpUiObject *ui;
+ time_t treconn;
+ gchar *cval;
+ gint maxattempts;
+
+ RemminaProtocolWidget *gp = rfi->protocol_widget;
+ RemminaFile *remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ rfi->is_reconnecting = TRUE;
+ rfi->stop_reconnecting_requested = FALSE;
+
+ /* Get the value set in FreeRDP_AutoReconnectMaxRetries (20) */
+ maxattempts = freerdp_settings_get_uint32(settings, FreeRDP_AutoReconnectMaxRetries);
+ REMMINA_PLUGIN_DEBUG("maxattempts from default: %d", maxattempts);
+ /* Get the value from the global preferences if any */
+ if ((cval = remmina_plugin_service->pref_get_value("rdp_reconnect_attempts")) != NULL)
+ maxattempts = atoi(cval);
+ REMMINA_PLUGIN_DEBUG("maxattempts from general preferences: %d", maxattempts);
+ /* Get the value from the profile if any, otherwise uses the value of maxattempts */
+ maxattempts = remmina_plugin_service->file_get_int(remminafile, "rdp_reconnect_attempts", maxattempts);
+ REMMINA_PLUGIN_DEBUG("maxattempts from general plugin: %d", maxattempts);
+ /* If maxattemps is <= 0, we get the value from FreeRDP_AutoReconnectMaxRetries (20) */
+ if (maxattempts <= 0)
+ maxattempts = freerdp_settings_get_uint32(settings, FreeRDP_AutoReconnectMaxRetries);
+ freerdp_settings_set_uint32(settings, FreeRDP_AutoReconnectMaxRetries, maxattempts);
+ REMMINA_PLUGIN_DEBUG("maxattempts set to: %d", maxattempts);
+
+ rfi->reconnect_maxattempts = maxattempts;
+ rfi->reconnect_nattempt = 0;
+
+ /* Only auto reconnect on network disconnects. */
+ switch (freerdp_error_info(rfi->clientContext.context.instance)) {
+ case ERRINFO_GRAPHICS_SUBSYSTEM_FAILED:
+ /* Disconnected by server hitting a bug or resource limit */
+ break;
+ case ERRINFO_SUCCESS:
+ /* A network disconnect was detected */
+ break;
+ default:
+ rfi->is_reconnecting = FALSE;
+ return FALSE;
+ }
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_AutoReconnectionEnabled)) {
+ /* No auto-reconnect - just quit */
+ rfi->is_reconnecting = FALSE;
+ return FALSE;
+ }
+
+ /* A network disconnect was detected and we should try to reconnect */
+ REMMINA_PLUGIN_DEBUG("[%s] network disconnection detected, initiating reconnection attempt",
+ freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname));
+
+ ui = g_new0(RemminaPluginRdpUiObject, 1);
+ ui->type = REMMINA_RDP_UI_RECONNECT_PROGRESS;
+ remmina_rdp_event_queue_ui_async(rfi->protocol_widget, ui);
+
+ /* Sleep half a second to allow:
+ * - processing of the UI event we just pushed on the queue
+ * - better network conditions
+ * Remember: We hare on a thread, so the main gui won’t lock */
+ usleep(500000);
+
+ /* Perform an auto-reconnect. */
+ while (TRUE) {
+ /* Quit retrying if max retries has been exceeded */
+ if (rfi->reconnect_nattempt++ >= rfi->reconnect_maxattempts) {
+ REMMINA_PLUGIN_DEBUG("[%s] maximum number of reconnection attempts exceeded.",
+ freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname));
+ break;
+ }
+
+ if (rfi->stop_reconnecting_requested) {
+ REMMINA_PLUGIN_DEBUG("[%s] reconnect request loop interrupted by user.",
+ freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname));
+ break;
+ }
+
+ /* Attempt the next reconnect */
+ REMMINA_PLUGIN_DEBUG("[%s] reconnection, attempt #%d of %d",
+ freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname), rfi->reconnect_nattempt, rfi->reconnect_maxattempts);
+
+ ui = g_new0(RemminaPluginRdpUiObject, 1);
+ ui->type = REMMINA_RDP_UI_RECONNECT_PROGRESS;
+ remmina_rdp_event_queue_ui_async(rfi->protocol_widget, ui);
+
+ treconn = time(NULL);
+
+ /* Reconnect the SSH tunnel, if needed */
+ if (!remmina_rdp_tunnel_init(rfi->protocol_widget)) {
+ REMMINA_PLUGIN_DEBUG("[%s] unable to recreate tunnel with remmina_rdp_tunnel_init.",
+ freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname));
+ } else {
+ if (freerdp_reconnect(rfi->clientContext.context.instance)) {
+ /* Reconnection is successful */
+ REMMINA_PLUGIN_DEBUG("[%s] reconnected.", freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname));
+ rfi->is_reconnecting = FALSE;
+ return TRUE;
+ }
+ }
+
+ /* Wait until 5 secs have elapsed from last reconnect attempt, while checking for rfi->stop_reconnecting_requested */
+ while (time(NULL) - treconn < 5) {
+ if (rfi->stop_reconnecting_requested)
+ break;
+ usleep(200000); // 200ms sleep
+ }
+ }
+
+ rfi->is_reconnecting = FALSE;
+ return FALSE;
+}
+
+static BOOL rf_begin_paint(rdpContext *context)
+{
+ TRACE_CALL(__func__);
+ rdpGdi *gdi;
+
+ if (!context)
+ return FALSE;
+
+ gdi = context->gdi;
+ if (!gdi || !gdi->primary || !gdi->primary->hdc || !gdi->primary->hdc->hwnd)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL rf_end_paint(rdpContext *context)
+{
+ TRACE_CALL(__func__);
+ rdpGdi *gdi;
+ rfContext *rfi;
+ RemminaPluginRdpUiObject *ui;
+ int i, ninvalid;
+ region *reg;
+ HGDI_RGN cinvalid;
+
+ gdi = context->gdi;
+ rfi = (rfContext *)context;
+
+ if (gdi == NULL || gdi->primary == NULL || gdi->primary->hdc == NULL || gdi->primary->hdc->hwnd == NULL)
+ return TRUE;
+
+ if (gdi->primary->hdc->hwnd->invalid->null)
+ return TRUE;
+
+ if (gdi->primary->hdc->hwnd->ninvalid < 1)
+ return TRUE;
+
+ ninvalid = gdi->primary->hdc->hwnd->ninvalid;
+ cinvalid = gdi->primary->hdc->hwnd->cinvalid;
+ reg = (region *)g_malloc(sizeof(region) * ninvalid);
+ for (i = 0; i < ninvalid; i++) {
+ reg[i].x = cinvalid[i].x;
+ reg[i].y = cinvalid[i].y;
+ reg[i].w = cinvalid[i].w;
+ reg[i].h = cinvalid[i].h;
+ }
+
+ ui = g_new0(RemminaPluginRdpUiObject, 1);
+ ui->type = REMMINA_RDP_UI_UPDATE_REGIONS;
+ ui->reg.ninvalid = ninvalid;
+ ui->reg.ureg = reg;
+
+ remmina_rdp_event_queue_ui_async(rfi->protocol_widget, ui);
+
+
+ gdi->primary->hdc->hwnd->invalid->null = TRUE;
+ gdi->primary->hdc->hwnd->ninvalid = 0;
+
+
+ return TRUE;
+}
+
+static BOOL rf_desktop_resize(rdpContext *context)
+{
+ TRACE_CALL(__func__);
+ rfContext *rfi;
+ RemminaProtocolWidget *gp;
+ RemminaPluginRdpUiObject *ui;
+ UINT32 w, h;
+
+ rfi = (rfContext *)context;
+ gp = rfi->protocol_widget;
+
+ w = freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopWidth);
+ h = freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopHeight);
+ remmina_plugin_service->protocol_plugin_set_width(gp, w);
+ remmina_plugin_service->protocol_plugin_set_height(gp, h);
+
+ ui = g_new0(RemminaPluginRdpUiObject, 1);
+ ui->type = REMMINA_RDP_UI_EVENT;
+ ui->event.type = REMMINA_RDP_UI_EVENT_DESTROY_CAIRO_SURFACE;
+ remmina_rdp_event_queue_ui_sync_retint(gp, ui);
+
+ /* Tell libfreerdp to change its internal GDI bitmap width and heigt,
+ * this will also destroy gdi->primary_buffer, making our rfi->surface invalid */
+ gdi_resize(((rdpContext *)rfi)->gdi, w, h);
+
+ /* Call to remmina_rdp_event_update_scale(gp) on the main UI thread,
+ * this will recreate rfi->surface from gdi->primary_buffer */
+
+ ui = g_new0(RemminaPluginRdpUiObject, 1);
+ ui->type = REMMINA_RDP_UI_EVENT;
+ ui->event.type = REMMINA_RDP_UI_EVENT_UPDATE_SCALE;
+ remmina_rdp_event_queue_ui_sync_retint(gp, ui);
+
+ remmina_plugin_service->protocol_plugin_desktop_resize(gp);
+
+ return TRUE;
+}
+
+static BOOL rf_play_sound(rdpContext *context, const PLAY_SOUND_UPDATE *play_sound)
+{
+ TRACE_CALL(__func__);
+ rfContext *rfi;
+ RemminaProtocolWidget *gp;
+ GdkDisplay *disp;
+
+ rfi = (rfContext *)context;
+ gp = rfi->protocol_widget;
+
+ disp = gtk_widget_get_display(GTK_WIDGET(gp));
+ gdk_display_beep(disp);
+
+ return TRUE;
+}
+
+static BOOL rf_keyboard_set_indicators(rdpContext *context, UINT16 led_flags)
+{
+ TRACE_CALL(__func__);
+ rfContext *rfi;
+ RemminaProtocolWidget *gp;
+ GdkDisplay *disp;
+
+ rfi = (rfContext *)context;
+ gp = rfi->protocol_widget;
+ disp = gtk_widget_get_display(GTK_WIDGET(gp));
+
+#ifdef GDK_WINDOWING_X11
+ if (GDK_IS_X11_DISPLAY(disp)) {
+ /* TODO: We are not on the main thread. Will X.Org complain? */
+ Display *x11_display;
+ x11_display = gdk_x11_display_get_xdisplay(disp);
+ XkbLockModifiers(x11_display, XkbUseCoreKbd,
+ LockMask | Mod2Mask,
+ ((led_flags & KBD_SYNC_CAPS_LOCK) ? LockMask : 0) |
+ ((led_flags & KBD_SYNC_NUM_LOCK) ? Mod2Mask : 0)
+ );
+
+ /* TODO: Add support to KANA_LOCK and SCROLL_LOCK */
+ }
+#endif
+
+ return TRUE;
+}
+
+static BOOL rf_keyboard_set_ime_status(rdpContext *context, UINT16 imeId, UINT32 imeState,
+ UINT32 imeConvMode)
+{
+ TRACE_CALL(__func__);
+ if (!context)
+ return FALSE;
+
+ /* Unimplemented, we ignore it */
+
+ return TRUE;
+}
+
+static BOOL remmina_rdp_pre_connect(freerdp *instance)
+{
+ TRACE_CALL(__func__);
+ rdpChannels *channels;
+ rdpSettings *settings;
+ rdpContext *context = instance->context;
+
+ settings = context->settings;
+ channels = context->channels;
+ freerdp_settings_set_uint32(settings, FreeRDP_OsMajorType, OSMAJORTYPE_UNIX);
+ freerdp_settings_set_uint32(settings, FreeRDP_OsMinorType, OSMINORTYPE_UNSPECIFIED);
+ freerdp_settings_set_bool(settings, FreeRDP_BitmapCacheEnabled, TRUE);
+ freerdp_settings_set_uint32(settings, FreeRDP_OffscreenSupportLevel, 1);
+
+ PubSub_SubscribeChannelConnected(instance->context->pubSub,
+ remmina_rdp_OnChannelConnectedEventHandler);
+ PubSub_SubscribeChannelDisconnected(instance->context->pubSub,
+ remmina_rdp_OnChannelDisconnectedEventHandler);
+
+ if (!freerdp_client_load_addins(channels, settings))
+ return FALSE;
+
+ return true;
+}
+
+static BOOL remmina_rdp_post_connect(freerdp *instance)
+{
+ TRACE_CALL(__func__);
+ rfContext *rfi;
+ RemminaProtocolWidget *gp;
+ RemminaPluginRdpUiObject *ui;
+ UINT32 freerdp_local_color_format;
+
+ rfi = (rfContext *)instance->context;
+ gp = rfi->protocol_widget;
+ rfi->postconnect_error = REMMINA_POSTCONNECT_ERROR_OK;
+
+ rfi->attempt_interactive_authentication = FALSE; // We authenticated!
+
+ rfi->srcBpp = freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth);
+
+ if (freerdp_settings_get_bool(rfi->clientContext.context.settings, FreeRDP_RemoteFxCodec) == FALSE)
+ rfi->sw_gdi = TRUE;
+
+ rf_register_graphics(instance->context->graphics);
+
+ REMMINA_PLUGIN_DEBUG("bpp: %d", rfi->bpp);
+ switch (rfi->bpp) {
+ case 24:
+ REMMINA_PLUGIN_DEBUG("CAIRO_FORMAT_RGB24");
+ freerdp_local_color_format = PIXEL_FORMAT_BGRX32;
+ rfi->cairo_format = CAIRO_FORMAT_RGB24;
+ break;
+ case 32:
+ /** Do not use alpha as it's not used with the desktop
+ * CAIRO_FORMAT_ARGB32
+ * See https://gitlab.com/Remmina/Remmina/-/issues/2456
+ */
+ REMMINA_PLUGIN_DEBUG("CAIRO_FORMAT_RGB24");
+ freerdp_local_color_format = PIXEL_FORMAT_BGRA32;
+ rfi->cairo_format = CAIRO_FORMAT_RGB24;
+ break;
+ default:
+ REMMINA_PLUGIN_DEBUG("CAIRO_FORMAT_RGB16_565");
+ freerdp_local_color_format = PIXEL_FORMAT_RGB16;
+ rfi->cairo_format = CAIRO_FORMAT_RGB16_565;
+ break;
+ }
+
+ if (!gdi_init(instance, freerdp_local_color_format)) {
+ rfi->postconnect_error = REMMINA_POSTCONNECT_ERROR_GDI_INIT;
+ return FALSE;
+ }
+
+ if (instance->context->codecs->h264 == NULL && freerdp_settings_get_bool(rfi->clientContext.context.settings, FreeRDP_GfxH264)) {
+ gdi_free(instance);
+ rfi->postconnect_error = REMMINA_POSTCONNECT_ERROR_NO_H264;
+ return FALSE;
+ }
+
+ // pointer_cache_register_callbacks(instance->update);
+ rdpUpdate *update = instance->context->update;
+ update->BeginPaint = rf_begin_paint;
+ update->EndPaint = rf_end_paint;
+ update->DesktopResize = rf_desktop_resize;
+
+ update->PlaySound = rf_play_sound;
+ update->SetKeyboardIndicators = rf_keyboard_set_indicators;
+ update->SetKeyboardImeStatus = rf_keyboard_set_ime_status;
+
+ remmina_rdp_clipboard_init(rfi);
+ rfi->connected = true;
+
+ ui = g_new0(RemminaPluginRdpUiObject, 1);
+ ui->type = REMMINA_RDP_UI_CONNECTED;
+ remmina_rdp_event_queue_ui_async(gp, ui);
+
+ return TRUE;
+}
+
+#if !defined(FREERDP_VERSION_MAJOR) || (FREERDP_VERSION_MAJOR < 3)
+static BOOL remmina_rdp_authenticate(freerdp *instance, char **username, char **password, char **domain)
+{
+ TRACE_CALL(__func__);
+ gchar *s_username, *s_password, *s_domain;
+ gint ret;
+ rfContext *rfi;
+ RemminaProtocolWidget *gp;
+ gboolean save;
+ gboolean disablepasswordstoring;
+ RemminaFile *remminafile;
+
+ rfi = (rfContext *)instance->context;
+ gp = rfi->protocol_widget;
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ disablepasswordstoring = remmina_plugin_service->file_get_int(remminafile, "disablepasswordstoring", FALSE);
+
+ ret = remmina_plugin_service->protocol_plugin_init_auth(gp,
+ (disablepasswordstoring ? 0 : REMMINA_MESSAGE_PANEL_FLAG_SAVEPASSWORD) | REMMINA_MESSAGE_PANEL_FLAG_USERNAME | REMMINA_MESSAGE_PANEL_FLAG_DOMAIN,
+ _("Enter RDP authentication credentials"),
+ remmina_plugin_service->file_get_string(remminafile, "username"),
+ remmina_plugin_service->file_get_string(remminafile, "password"),
+ remmina_plugin_service->file_get_string(remminafile, "domain"),
+ NULL);
+ if (ret == GTK_RESPONSE_OK) {
+ s_username = remmina_plugin_service->protocol_plugin_init_get_username(gp);
+ if (s_username) freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_Username, s_username);
+
+ s_password = remmina_plugin_service->protocol_plugin_init_get_password(gp);
+ if (s_password) freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_Password, s_password);
+
+ s_domain = remmina_plugin_service->protocol_plugin_init_get_domain(gp);
+ if (s_domain) freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_Domain, s_domain);
+
+ remmina_plugin_service->file_set_string(remminafile, "username", s_username);
+ remmina_plugin_service->file_set_string(remminafile, "domain", s_domain);
+
+ save = remmina_plugin_service->protocol_plugin_init_get_savepassword(gp);
+ if (save) {
+ // User has requested to save credentials. We put the password
+ // into remminafile->settings. It will be saved later, on successful connection, by
+ // rcw.c
+ remmina_plugin_service->file_set_string(remminafile, "password", s_password);
+ } else {
+ remmina_plugin_service->file_set_string(remminafile, "password", NULL);
+ }
+
+
+ if (s_username) g_free(s_username);
+ if (s_password) g_free(s_password);
+ if (s_domain) g_free(s_domain);
+
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL remmina_rdp_gw_authenticate(freerdp *instance, char **username, char **password, char **domain)
+{
+ TRACE_CALL(__func__);
+ gchar *s_username, *s_password, *s_domain;
+ gint ret;
+ rfContext *rfi;
+ RemminaProtocolWidget *gp;
+ gboolean save;
+ gboolean disablepasswordstoring;
+ gboolean basecredforgw;
+ RemminaFile *remminafile;
+
+ rfi = (rfContext *)instance->context;
+ gp = rfi->protocol_widget;
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ if (!remmina_plugin_service->file_get_string(remminafile, "gateway_server"))
+ return false;
+ disablepasswordstoring = remmina_plugin_service->file_get_int(remminafile, "disablepasswordstoring", FALSE);
+ basecredforgw = remmina_plugin_service->file_get_int(remminafile, "base-cred-for-gw", FALSE);
+
+ if (basecredforgw) {
+ ret = remmina_plugin_service->protocol_plugin_init_auth(gp,
+ (disablepasswordstoring ? 0 : REMMINA_MESSAGE_PANEL_FLAG_SAVEPASSWORD) | REMMINA_MESSAGE_PANEL_FLAG_USERNAME | REMMINA_MESSAGE_PANEL_FLAG_DOMAIN,
+ _("Enter RDP authentication credentials"),
+ remmina_plugin_service->file_get_string(remminafile, "username"),
+ remmina_plugin_service->file_get_string(remminafile, "password"),
+ remmina_plugin_service->file_get_string(remminafile, "domain"),
+ NULL);
+ } else {
+ ret = remmina_plugin_service->protocol_plugin_init_auth(gp,
+ (disablepasswordstoring ? 0 : REMMINA_MESSAGE_PANEL_FLAG_SAVEPASSWORD) | REMMINA_MESSAGE_PANEL_FLAG_USERNAME | REMMINA_MESSAGE_PANEL_FLAG_DOMAIN,
+ _("Enter RDP gateway authentication credentials"),
+ remmina_plugin_service->file_get_string(remminafile, "gateway_username"),
+ remmina_plugin_service->file_get_string(remminafile, "gateway_password"),
+ remmina_plugin_service->file_get_string(remminafile, "gateway_domain"),
+ NULL);
+ }
+
+
+ if (ret == GTK_RESPONSE_OK) {
+ s_username = remmina_plugin_service->protocol_plugin_init_get_username(gp);
+ if (s_username) freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_GatewayUsername, s_username);
+
+ s_password = remmina_plugin_service->protocol_plugin_init_get_password(gp);
+ if (s_password) freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_GatewayPassword, s_password);
+
+ s_domain = remmina_plugin_service->protocol_plugin_init_get_domain(gp);
+ if (s_domain) freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_GatewayDomain, s_domain);
+
+ save = remmina_plugin_service->protocol_plugin_init_get_savepassword(gp);
+
+ if (basecredforgw) {
+ remmina_plugin_service->file_set_string(remminafile, "username", s_username);
+ remmina_plugin_service->file_set_string(remminafile, "domain", s_domain);
+ if (save)
+ remmina_plugin_service->file_set_string(remminafile, "password", s_password);
+ else
+ remmina_plugin_service->file_set_string(remminafile, "password", NULL);
+ } else {
+ remmina_plugin_service->file_set_string(remminafile, "gateway_username", s_username);
+ remmina_plugin_service->file_set_string(remminafile, "gateway_domain", s_domain);
+ if (save)
+ remmina_plugin_service->file_set_string(remminafile, "gateway_password", s_password);
+ else
+ remmina_plugin_service->file_set_string(remminafile, "gateway_password", NULL);
+ }
+
+ if (s_username) g_free(s_username);
+ if (s_password) g_free(s_password);
+ if (s_domain) g_free(s_domain);
+
+ return true;
+ } else {
+ return false;
+ }
+
+ return true;
+}
+#else
+static BOOL remmina_rdp_authenticate_ex(freerdp* instance, char** username, char** password,
+ char** domain, rdp_auth_reason reason)
+{
+ TRACE_CALL(__func__);
+ gchar *s_username = NULL, *s_password = NULL, *s_domain = NULL;
+ const gchar* key_user = NULL;
+ const gchar* key_domain = NULL;
+ const gchar* key_password = NULL;
+ const gchar* key_title = NULL;
+ gint ret;
+ rfContext *rfi;
+ RemminaProtocolWidget *gp;
+ gboolean save;
+ gboolean disablepasswordstoring;
+ RemminaFile *remminafile;
+ RemminaMessagePanelFlags flags = REMMINA_MESSAGE_PANEL_FLAG_SAVEPASSWORD | REMMINA_MESSAGE_PANEL_FLAG_USERNAME | REMMINA_MESSAGE_PANEL_FLAG_DOMAIN;
+
+ rfi = (rfContext *)instance->context;
+ gp = rfi->protocol_widget;
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ disablepasswordstoring = remmina_plugin_service->file_get_int(remminafile, "disablepasswordstoring", FALSE);
+
+ FreeRDP_Settings_Keys_String cfg_key_user = FreeRDP_STRING_UNUSED;
+ FreeRDP_Settings_Keys_String cfg_key_domain = FreeRDP_STRING_UNUSED;
+ FreeRDP_Settings_Keys_String cfg_key_password = FreeRDP_STRING_UNUSED;
+ switch(reason) {
+ case AUTH_NLA:
+ case AUTH_TLS:
+ case AUTH_RDP:
+ key_title = _("Enter RDP authentication credentials");
+ key_user = "username";
+ key_domain = "domain";
+ key_password = "password";
+ cfg_key_user = FreeRDP_Username;
+ cfg_key_domain = FreeRDP_Domain;
+ cfg_key_password = FreeRDP_Password;
+ break;
+ case GW_AUTH_HTTP:
+ case GW_AUTH_RDG:
+ case GW_AUTH_RPC:
+ key_title = _("Enter RDP gateway authentication credentials");
+ key_user = "gateway_username";
+ key_domain = "gateway_domain";
+ key_password = "gateway_password";
+ cfg_key_user = FreeRDP_GatewayUsername;
+ cfg_key_domain = FreeRDP_GatewayDomain;
+ cfg_key_password = FreeRDP_GatewayPassword;
+ break;
+ case AUTH_SMARTCARD_PIN:
+ key_title = _("Enter RDP SmartCard PIN");
+ key_password = "smartcard_pin";
+ flags = 0;
+ break;
+ default:
+ // TODO: Display an error dialog informing the user that the remote requires some mechanism FreeRDP or Remmina currently do not support
+ g_fprintf(stderr, "[authentication] unsupported type %d, access denied", reason);
+ return FALSE;
+ }
+
+ if (!disablepasswordstoring)
+ flags |= REMMINA_MESSAGE_PANEL_FLAG_SAVEPASSWORD;
+
+ ret = remmina_plugin_service->protocol_plugin_init_auth(gp, flags,
+ key_title,
+ remmina_plugin_service->file_get_string(remminafile, key_user),
+ remmina_plugin_service->file_get_string(remminafile, key_password),
+ remmina_plugin_service->file_get_string(remminafile, disablepasswordstoring ? NULL : key_domain),
+ NULL);
+ if (ret == GTK_RESPONSE_OK) {
+ if (cfg_key_user != FreeRDP_STRING_UNUSED)
+ {
+ s_username = remmina_plugin_service->protocol_plugin_init_get_username(gp);
+ if (s_username)
+ freerdp_settings_set_string(rfi->clientContext.context.settings, cfg_key_user, s_username);
+ remmina_plugin_service->file_set_string(remminafile, key_user, s_username);
+ }
+
+ if (cfg_key_password != FreeRDP_STRING_UNUSED)
+ {
+ s_password = remmina_plugin_service->protocol_plugin_init_get_password(gp);
+ if (s_password)
+ freerdp_settings_set_string(rfi->clientContext.context.settings, cfg_key_password, s_password);
+ }
+
+ if (cfg_key_domain != FreeRDP_STRING_UNUSED) {
+ s_domain = remmina_plugin_service->protocol_plugin_init_get_domain(gp);
+ if (s_domain)
+ freerdp_settings_set_string(rfi->clientContext.context.settings, cfg_key_domain, s_domain);
+ remmina_plugin_service->file_set_string(remminafile, key_domain, s_domain);
+ }
+
+ save = remmina_plugin_service->protocol_plugin_init_get_savepassword(gp);
+ if (save) {
+ // User has requested to save credentials. We put the password
+ // into remminafile->settings. It will be saved later, on successful connection, by
+ // rcw.c
+ remmina_plugin_service->file_set_string(remminafile, key_password, s_password);
+ } else {
+ remmina_plugin_service->file_set_string(remminafile, key_password, NULL);
+ }
+
+
+ if (s_username) g_free(s_username);
+ if (s_password) g_free(s_password);
+ if (s_domain) g_free(s_domain);
+
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL remmina_rdp_choose_smartcard(freerdp* instance, SmartcardCertInfo** cert_list, DWORD count,
+ DWORD* choice, BOOL gateway)
+{
+ // TODO: Display a simple list of smartcards/certs to choose from.
+ // See client_cli_choose_smartcard for a sample
+ return client_cli_choose_smartcard(instance, cert_list, count, choice, gateway);
+}
+
+static BOOL remmina_rdp_get_access_token(freerdp* instance, AccessTokenType tokenType, char** token,
+ size_t count, ...)
+{
+ // TODO: Open a (currently hard coded) URL, authenticate in a webview/browser, return the access token.
+ // See client_cli_get_access_token or sdl_webview_get_access_token for implementations
+ return client_cli_get_access_token(instance, tokenType, token, count);
+}
+
+static BOOL remmina_rdp_present_gateway_message(freerdp* instance, UINT32 type, BOOL isDisplayMandatory,
+ BOOL isConsentMandatory, size_t length,
+ const WCHAR* message)
+{
+ // TODO: Present a message to the user, usually terms of service or similar
+ // See client_cli_present_gateway_message or sdl_present_gateway_message
+ return client_cli_present_gateway_message(instance, type, isDisplayMandatory, isConsentMandatory, length, message);
+}
+
+static int remmina_rdp_logon_error_info(freerdp* instance, UINT32 data, UINT32 type)
+{
+ // TODO: Display the reason for connection termination
+ // See client_cli_logon_error_info or sdl_logon_error_info
+ return client_cli_logon_error_info(instance, data, type);
+}
+
+static SSIZE_T remmina_rdp_retry_dialog(freerdp* instance, const char* what, size_t current,
+ void* userarg)
+{
+ // TODO:
+ // See client_common_retry_dialog or
+ return client_common_retry_dialog(instance, what, current, userarg);
+}
+
+static void remmina_rdp_post_final_disconnect(freerdp* instance)
+{
+ // Clean up resources allocated in PreConnect
+}
+#endif
+
+static DWORD remmina_rdp_verify_certificate_ex(freerdp *instance, const char *host, UINT16 port,
+ const char *common_name, const char *subject,
+ const char *issuer, const char *fingerprint, DWORD flags)
+{
+ TRACE_CALL(__func__);
+ gint status;
+ rfContext *rfi;
+ RemminaProtocolWidget *gp;
+
+ rfi = (rfContext *)instance->context;
+ gp = rfi->protocol_widget;
+
+ status = remmina_plugin_service->protocol_plugin_init_certificate(gp, subject, issuer, fingerprint);
+
+ if (status == GTK_RESPONSE_OK)
+ return 1;
+
+ return 0;
+}
+
+static DWORD
+remmina_rdp_verify_certificate(freerdp *instance, const char *common_name, const char *subject, const char *issuer, const char *fingerprint, BOOL host_mismatch) __attribute__ ((unused));
+static DWORD
+remmina_rdp_verify_certificate(freerdp *instance, const char *common_name, const char *subject,
+ const char *issuer, const char *fingerprint, BOOL host_mismatch)
+{
+ TRACE_CALL(__func__);
+ gint status;
+ rfContext *rfi;
+ RemminaProtocolWidget *gp;
+
+ rfi = (rfContext *)instance->context;
+ gp = rfi->protocol_widget;
+
+ status = remmina_plugin_service->protocol_plugin_init_certificate(gp, subject, issuer, fingerprint);
+
+ if (status == GTK_RESPONSE_OK)
+ return 1;
+
+ return 0;
+}
+
+static DWORD remmina_rdp_verify_changed_certificate_ex(freerdp *instance, const char *host, UINT16 port,
+ const char *common_name, const char *subject,
+ const char *issuer, const char *fingerprint,
+ const char *old_subject, const char *old_issuer,
+ const char *old_fingerprint, DWORD flags)
+{
+ TRACE_CALL(__func__);
+ gint status;
+ rfContext *rfi;
+ RemminaProtocolWidget *gp;
+
+ rfi = (rfContext *)instance->context;
+ gp = rfi->protocol_widget;
+
+ status = remmina_plugin_service->protocol_plugin_changed_certificate(gp, subject, issuer, fingerprint, old_fingerprint);
+
+ if (status == GTK_RESPONSE_OK)
+ return 1;
+
+ return 0;
+}
+
+static void remmina_rdp_post_disconnect(freerdp *instance)
+{
+ TRACE_CALL(__func__);
+
+ if (!instance || !instance->context)
+ return;
+
+ PubSub_UnsubscribeChannelConnected(instance->context->pubSub,
+ remmina_rdp_OnChannelConnectedEventHandler);
+ PubSub_UnsubscribeChannelDisconnected(instance->context->pubSub,
+ remmina_rdp_OnChannelDisconnectedEventHandler);
+
+ /* The remaining cleanup will be continued on main thread by complete_cleanup_on_main_thread() */
+
+ // With FreeRDP3 only resources allocated in PostConnect and later are cleaned up here.
+}
+
+static void remmina_rdp_main_loop(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ DWORD status;
+ gchar buf[100];
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ time_t cur_time, time_diff;
+
+ int jitter_time = remmina_plugin_service->file_get_int(remminafile, "rdp_mouse_jitter", 0);
+ time(&last_time);
+#if FREERDP_VERSION_MAJOR >= 3
+ while (!freerdp_shall_disconnect_context(&rfi->clientContext.context)) {
+#else
+ while (!freerdp_shall_disconnect(rfi->clientContext.context.instance)) {
+#endif
+ // move mouse if we've been idle and option is selected
+ time(&cur_time);
+ time_diff = cur_time - last_time;
+ if (jitter_time > 0 && time_diff > jitter_time){
+ last_time = cur_time;
+ remmina_rdp_mouse_jitter(gp);
+ }
+
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS] = {0};
+ DWORD nCount = freerdp_get_event_handles(&rfi->clientContext.context, &handles[0], ARRAYSIZE(handles));
+ if (rfi->event_handle)
+ handles[nCount++] = rfi->event_handle;
+
+ handles[nCount++] = freerdp_abort_event(&rfi->clientContext.context);
+
+ if (nCount == 0) {
+ fprintf(stderr, "freerdp_get_event_handles failed\n");
+ break;
+ }
+
+ status = WaitForMultipleObjects(nCount, handles, FALSE, 100);
+
+ if (status == WAIT_FAILED) {
+ fprintf(stderr, "WaitForMultipleObjects failed with %lu\n", (unsigned long)status);
+ break;
+ }
+
+ if (rfi->event_handle && WaitForSingleObject(rfi->event_handle, 0) == WAIT_OBJECT_0) {
+ if (!rf_process_event_queue(gp)) {
+ fprintf(stderr, "Could not process local keyboard/mouse event queue\n");
+ break;
+ }
+ if (read(rfi->event_pipe[0], buf, sizeof(buf))) {
+ }
+ }
+
+ /* Check if a processed event called freerdp_abort_connect() and exit if true */
+ if (WaitForSingleObject(freerdp_abort_event(&rfi->clientContext.context), 0) == WAIT_OBJECT_0)
+ /* Session disconnected by local user action */
+ break;
+
+ if (!freerdp_check_event_handles(&rfi->clientContext.context)) {
+ if (rf_auto_reconnect(rfi)) {
+ /* Reset the possible reason/error which made us doing many reconnection reattempts and continue */
+ remmina_plugin_service->protocol_plugin_set_error(gp, NULL);
+ continue;
+ }
+ if (freerdp_get_last_error(&rfi->clientContext.context) == FREERDP_ERROR_SUCCESS)
+ fprintf(stderr, "Could not check FreeRDP file descriptor\n");
+ break;
+ }
+ }
+ const gchar *host = freerdp_settings_get_string (rfi->clientContext.context.settings, FreeRDP_ServerHostname);
+ // TRANSLATORS: the placeholder may be either an IP/FQDN or a server hostname
+ REMMINA_PLUGIN_AUDIT(_("Disconnected from %s via RDP"), host);
+ freerdp_disconnect(rfi->clientContext.context.instance);
+ REMMINA_PLUGIN_DEBUG("RDP client disconnected");
+}
+
+static int remmina_rdp_load_static_channel_addin(rdpChannels *channels, rdpSettings *settings, char *name, void *data)
+{
+ TRACE_CALL(__func__);
+ PVIRTUALCHANNELENTRY entry = NULL;
+ PVIRTUALCHANNELENTRYEX entryEx = NULL;
+
+ entryEx = (PVIRTUALCHANNELENTRYEX)(void *)freerdp_load_channel_addin_entry(
+ name, NULL, NULL, FREERDP_ADDIN_CHANNEL_STATIC | FREERDP_ADDIN_CHANNEL_ENTRYEX);
+
+ if (!entryEx)
+ entry = freerdp_load_channel_addin_entry(name, NULL, NULL, FREERDP_ADDIN_CHANNEL_STATIC);
+
+ if (entryEx) {
+ if (freerdp_channels_client_load_ex(channels, settings, entryEx, data) == 0) {
+ fprintf(stderr, "loading channel %s\n", name);
+ return TRUE;
+ }
+ } else if (entry) {
+ if (freerdp_channels_client_load(channels, settings, entry, data) == 0) {
+ fprintf(stderr, "loading channel %s\n", name);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static gchar *remmina_rdp_find_prdriver(char *smap, char *prn)
+{
+ char c, *p, *dr;
+ int matching;
+ size_t sz;
+
+ enum { S_WAITPR,
+ S_INPRINTER,
+ S_WAITCOLON,
+ S_WAITDRIVER,
+ S_INDRIVER,
+ S_WAITSEMICOLON } state = S_WAITPR;
+
+ matching = 0;
+ while ((c = *smap++) != 0) {
+ switch (state) {
+ case S_WAITPR:
+ if (c != '\"') return NULL;
+ state = S_INPRINTER;
+ p = prn;
+ matching = 1;
+ break;
+ case S_INPRINTER:
+ if (matching && c == *p && *p != 0) {
+ p++;
+ } else if (c == '\"') {
+ if (*p != 0)
+ matching = 0;
+ state = S_WAITCOLON;
+ } else {
+ matching = 0;
+ }
+ break;
+ case S_WAITCOLON:
+ if (c != ':')
+ return NULL;
+ state = S_WAITDRIVER;
+ break;
+ case S_WAITDRIVER:
+ if (c != '\"')
+ return NULL;
+ state = S_INDRIVER;
+ dr = smap;
+ break;
+ case S_INDRIVER:
+ if (c == '\"') {
+ if (matching)
+ goto found;
+ else
+ state = S_WAITSEMICOLON;
+ }
+ break;
+ case S_WAITSEMICOLON:
+ if (c != ';')
+ return NULL;
+ state = S_WAITPR;
+ break;
+ }
+ }
+ return NULL;
+
+found:
+ sz = smap - dr;
+ p = (char *)malloc(sz);
+ memcpy(p, dr, sz);
+ p[sz - 1] = 0;
+ return p;
+}
+
+#ifdef HAVE_CUPS
+/**
+ * Callback function used by cupsEnumDests
+ * - For each enumerated local printer tries to set the Printer Name and Driver.
+ * @return 1 if there are other printers to scan or 0 when it's done.
+ */
+static int remmina_rdp_set_printers(void *user_data, unsigned flags, cups_dest_t *dest)
+{
+ rfContext *rfi = (rfContext *)user_data;
+ RemminaProtocolWidget *gp = rfi->protocol_widget;
+
+ /** @warning printer-make-and-model is not always the same as on the Windows,
+ * therefore it fails finding to right one and it fails to add
+ * the printer.
+ *
+ * We pass NULL and we do not check for errors. The following code is
+ * how it is supposed to work. @todo Ask CUPS mailing list for help.
+ *
+ * @code
+ * const char *model = cupsGetOption("printer-make-and-model",
+ * dest->num_options,
+ * dest->options);
+ * @endcode
+ */
+
+ RemminaFile *remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ const gchar *s = remmina_plugin_service->file_get_string(remminafile, "printer_overrides");
+
+ RDPDR_PRINTER *printer;
+ printer = (RDPDR_PRINTER *)calloc(1, sizeof(RDPDR_PRINTER));
+
+#if FREERDP_VERSION_MAJOR >= 3
+ RDPDR_DEVICE *pdev;
+ pdev = &(printer->device);
+#else
+ RDPDR_PRINTER *pdev;
+ pdev = printer;
+#endif
+
+ pdev->Type = RDPDR_DTYP_PRINT;
+ REMMINA_PLUGIN_DEBUG("Printer Type: %d", pdev->Type);
+
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_RedirectPrinters, TRUE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_DeviceRedirection, TRUE);
+
+ REMMINA_PLUGIN_DEBUG("Destination: %s", dest->name);
+ if (!(pdev->Name = _strdup(dest->name))) {
+ free(printer);
+ return 1;
+ }
+
+ REMMINA_PLUGIN_DEBUG("Printer Name: %s", pdev->Name);
+
+ if (s) {
+ gchar *d = remmina_rdp_find_prdriver(strdup(s), pdev->Name);
+ if (d) {
+ printer->DriverName = strdup(d);
+ REMMINA_PLUGIN_DEBUG("Printer DriverName set to: %s", printer->DriverName);
+ g_free(d);
+ } else {
+ /**
+ * When remmina_rdp_find_prdriver doesn't return a DriverName
+ * it means that we don't want to share that printer
+ *
+ */
+ free(pdev->Name);
+ free(printer);
+ return 1;
+ }
+ } else {
+ /* We set to a default driver*/
+ printer->DriverName = _strdup("MS Publisher Imagesetter");
+ }
+
+ REMMINA_PLUGIN_DEBUG("Printer Driver: %s", printer->DriverName);
+ if (!freerdp_device_collection_add(rfi->clientContext.context.settings, (RDPDR_DEVICE *)printer)) {
+ free(printer->DriverName);
+ free(pdev->Name);
+ free(printer);
+ return 1;
+ }
+
+ return 1;
+}
+#endif /* HAVE_CUPS */
+
+/* Send Ctrl+Alt+Del keystrokes to the plugin drawing_area widget */
+static void remmina_rdp_send_ctrlaltdel(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ guint keys[] = { GDK_KEY_Control_L, GDK_KEY_Alt_L, GDK_KEY_Delete };
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+
+ remmina_plugin_service->protocol_plugin_send_keys_signals(rfi->drawing_area,
+ keys, G_N_ELEMENTS(keys), GDK_KEY_PRESS | GDK_KEY_RELEASE);
+}
+
+static gboolean remmina_rdp_set_connection_type(rdpSettings *settings, guint32 type)
+{
+ freerdp_settings_set_uint32(settings, FreeRDP_ConnectionType, type);
+
+ if (type == CONNECTION_TYPE_MODEM) {
+ freerdp_settings_set_bool(settings, FreeRDP_DisableWallpaper, TRUE);
+ freerdp_settings_set_bool(settings, FreeRDP_AllowFontSmoothing, FALSE);
+ freerdp_settings_set_bool(settings, FreeRDP_AllowDesktopComposition, FALSE);
+ freerdp_settings_set_bool(settings, FreeRDP_DisableFullWindowDrag, TRUE);
+ freerdp_settings_set_bool(settings, FreeRDP_DisableMenuAnims, TRUE);
+ freerdp_settings_set_bool(settings, FreeRDP_DisableThemes, TRUE);
+ } else if (type == CONNECTION_TYPE_BROADBAND_LOW) {
+ freerdp_settings_set_bool(settings, FreeRDP_DisableWallpaper, TRUE);
+ freerdp_settings_set_bool(settings, FreeRDP_AllowFontSmoothing, FALSE);
+ freerdp_settings_set_bool(settings, FreeRDP_AllowDesktopComposition, FALSE);
+ freerdp_settings_set_bool(settings, FreeRDP_DisableFullWindowDrag, TRUE);
+ freerdp_settings_set_bool(settings, FreeRDP_DisableMenuAnims, TRUE);
+ freerdp_settings_set_bool(settings, FreeRDP_DisableThemes, FALSE);
+ } else if (type == CONNECTION_TYPE_SATELLITE) {
+ freerdp_settings_set_bool(settings, FreeRDP_DisableWallpaper, TRUE);
+ freerdp_settings_set_bool(settings, FreeRDP_AllowFontSmoothing, FALSE);
+ freerdp_settings_set_bool(settings, FreeRDP_AllowDesktopComposition, TRUE);
+ freerdp_settings_set_bool(settings, FreeRDP_DisableFullWindowDrag, TRUE);
+ freerdp_settings_set_bool(settings, FreeRDP_DisableMenuAnims, TRUE);
+ freerdp_settings_set_bool(settings, FreeRDP_DisableThemes, FALSE);
+ } else if (type == CONNECTION_TYPE_BROADBAND_HIGH) {
+ freerdp_settings_set_bool(settings, FreeRDP_DisableWallpaper, TRUE);
+ freerdp_settings_set_bool(settings, FreeRDP_AllowFontSmoothing, FALSE);
+ freerdp_settings_set_bool(settings, FreeRDP_AllowDesktopComposition, TRUE);
+ freerdp_settings_set_bool(settings, FreeRDP_DisableFullWindowDrag, TRUE);
+ freerdp_settings_set_bool(settings, FreeRDP_DisableMenuAnims, TRUE);
+ freerdp_settings_set_bool(settings, FreeRDP_DisableThemes, FALSE);
+ } else if (type == CONNECTION_TYPE_WAN) {
+ freerdp_settings_set_bool(settings, FreeRDP_DisableWallpaper, FALSE);
+ freerdp_settings_set_bool(settings, FreeRDP_AllowFontSmoothing, TRUE);
+ freerdp_settings_set_bool(settings, FreeRDP_AllowDesktopComposition, TRUE);
+ freerdp_settings_set_bool(settings, FreeRDP_DisableFullWindowDrag, FALSE);
+ freerdp_settings_set_bool(settings, FreeRDP_DisableMenuAnims, FALSE);
+ freerdp_settings_set_bool(settings, FreeRDP_DisableThemes, FALSE);
+ } else if (type == CONNECTION_TYPE_LAN) {
+ freerdp_settings_set_bool(settings, FreeRDP_DisableWallpaper, FALSE);
+ freerdp_settings_set_bool(settings, FreeRDP_AllowFontSmoothing, TRUE);
+ freerdp_settings_set_bool(settings, FreeRDP_AllowDesktopComposition, TRUE);
+ freerdp_settings_set_bool(settings, FreeRDP_DisableFullWindowDrag, FALSE);
+ freerdp_settings_set_bool(settings, FreeRDP_DisableMenuAnims, FALSE);
+ freerdp_settings_set_bool(settings, FreeRDP_DisableThemes, FALSE);
+ } else if (type == CONNECTION_TYPE_AUTODETECT) {
+ freerdp_settings_set_bool(settings, FreeRDP_DisableWallpaper, FALSE);
+ freerdp_settings_set_bool(settings, FreeRDP_AllowFontSmoothing, TRUE);
+ freerdp_settings_set_bool(settings, FreeRDP_AllowDesktopComposition, TRUE);
+ freerdp_settings_set_bool(settings, FreeRDP_DisableFullWindowDrag, FALSE);
+ freerdp_settings_set_bool(settings, FreeRDP_DisableMenuAnims, FALSE);
+ freerdp_settings_set_bool(settings, FreeRDP_DisableThemes, FALSE);
+ freerdp_settings_set_bool(settings, FreeRDP_NetworkAutoDetect, TRUE);
+
+ /* Automatically activate GFX and RFX codec support */
+#ifdef WITH_GFX_H264
+ freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444, gfx_h264_available);
+ freerdp_settings_set_bool(settings, FreeRDP_GfxH264, gfx_h264_available);
+#endif
+ freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, TRUE);
+ freerdp_settings_set_bool(settings, FreeRDP_SupportGraphicsPipeline, TRUE);
+ } else if (type == REMMINA_CONNECTION_TYPE_NONE) {
+ return FALSE;
+ } else {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+#ifdef GDK_WINDOWING_X11
+#if FREERDP_CHECK_VERSION(2, 3, 0)
+static gchar *remmina_get_rdp_kbd_remap(const gchar *keymap)
+{
+ TRACE_CALL(__func__);
+ guint *table;
+ gchar keys[20];
+ gchar *rdp_kbd_remap = NULL;
+ gint i;
+ Display *display;
+
+ table = remmina_plugin_service->pref_keymap_get_table(keymap);
+ if (!table)
+ return rdp_kbd_remap;
+ rdp_kbd_remap = g_malloc0(512);
+ display = XOpenDisplay(0);
+ for (i = 0; table[i] > 0; i += 2) {
+ g_snprintf(keys, sizeof(keys), "0x%02x=0x%02x", freerdp_keyboard_get_rdp_scancode_from_x11_keycode(XKeysymToKeycode(display, table[i])),
+ freerdp_keyboard_get_rdp_scancode_from_x11_keycode(XKeysymToKeycode(display, table[i + 1])));
+ if (i > 0)
+ g_strlcat(rdp_kbd_remap, ",", 512);
+ g_strlcat(rdp_kbd_remap, keys, 512);
+ }
+ XCloseDisplay(display);
+
+ return rdp_kbd_remap;
+}
+#endif
+#endif
+
+static gboolean remmina_rdp_main(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ const gchar *s;
+ gchar *sm;
+ gchar *value;
+ const gchar *cs;
+ RemminaFile *remminafile;
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+ rdpChannels *channels;
+ gchar *gateway_host;
+ gint gateway_port;
+ gchar *datapath = NULL;
+ gboolean status = TRUE;
+#ifdef GDK_WINDOWING_X11
+ gchar *rdp_kbd_remap;
+#endif
+ gint i;
+
+ gint desktopOrientation, desktopScaleFactor, deviceScaleFactor;
+
+ channels = rfi->clientContext.context.channels;
+
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ datapath = g_build_path("/",
+ remmina_plugin_service->file_get_user_datadir(),
+ "RDP",
+ NULL);
+ REMMINA_PLUGIN_DEBUG("RDP data path is %s", datapath);
+
+ if ((datapath != NULL) && (datapath[0] != '\0'))
+ if (access(datapath, W_OK) == 0)
+ freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_ConfigPath, datapath);
+ g_free(datapath);
+
+ if (remmina_plugin_service->file_get_int(remminafile, "assistance_mode", 0)){
+ rdpAssistanceFile* file = freerdp_assistance_file_new();
+ if (!file){
+ REMMINA_PLUGIN_DEBUG("Could not allocate assistance file structure");
+ return FALSE;
+ }
+
+ if (remmina_plugin_service->file_get_string(remminafile, "assistance_file") == NULL ||
+ remmina_plugin_service->file_get_string(remminafile, "assistance_pass") == NULL ){
+
+ REMMINA_PLUGIN_DEBUG("Assistance file and password are not set while assistance mode is on");
+ return FALSE;
+ }
+
+ status = freerdp_assistance_parse_file(file,
+ remmina_plugin_service->file_get_string(remminafile, "assistance_file"),
+ remmina_plugin_service->file_get_string(remminafile, "assistance_pass"));
+
+ if (status < 0){
+ REMMINA_PLUGIN_DEBUG("Could not parse assistance file");
+ return FALSE;
+ }
+
+
+ if (!freerdp_assistance_populate_settings_from_assistance_file(file, rfi->clientContext.context.settings)){
+ REMMINA_PLUGIN_DEBUG("Could not populate settings from assistance file");
+ return FALSE;
+ }
+ }
+
+
+#if defined(PROXY_TYPE_IGNORE)
+ if (!remmina_plugin_service->file_get_int(remminafile, "useproxyenv", FALSE) ? TRUE : FALSE) {
+ REMMINA_PLUGIN_DEBUG("Not using system proxy settings");
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_ProxyType, PROXY_TYPE_IGNORE);
+ }
+#endif
+
+ if (!remmina_rdp_tunnel_init(gp))
+ return FALSE;
+
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_AutoReconnectionEnabled, (remmina_plugin_service->file_get_int(remminafile, "disableautoreconnect", FALSE) ? FALSE : TRUE));
+ /* Disable RDP auto reconnection when SSH tunnel is enabled */
+ if (remmina_plugin_service->file_get_int(remminafile, "ssh_tunnel_enabled", FALSE))
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_AutoReconnectionEnabled, FALSE);
+
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth, remmina_plugin_service->file_get_int(remminafile, "colordepth", 99));
+
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_SoftwareGdi, TRUE);
+ REMMINA_PLUGIN_DEBUG("gfx_h264_available: %d", gfx_h264_available);
+
+ /* Avoid using H.264 modes if they are not available on libfreerdp */
+ if (!gfx_h264_available && (freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth) == 65 || freerdp_settings_get_bool(rfi->clientContext.context.settings, FreeRDP_ColorDepth == 66)))
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth, 64); // Fallback to GFX RFX
+
+ if (freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth) == 0) {
+ /* RFX (Win7)*/
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_RemoteFxCodec, TRUE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_SupportGraphicsPipeline, FALSE);
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth, 32);
+ } else if (freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth) == 63) {
+ /* /gfx (RFX Progressive) (Win8) */
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth, 32);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_SupportGraphicsPipeline, TRUE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GfxH264, FALSE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GfxAVC444, FALSE);
+ } else if (freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth) == 64) {
+ /* /gfx:rfx (Win8) */
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth, 32);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_RemoteFxCodec, TRUE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_SupportGraphicsPipeline, TRUE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GfxH264, FALSE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GfxAVC444, FALSE);
+ } else if (freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth) == 65) {
+ /* /gfx:avc420 (Win8.1) */
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth, 32);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_SupportGraphicsPipeline, TRUE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GfxH264, gfx_h264_available);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GfxAVC444, FALSE);
+ } else if (freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth) == 66) {
+ /* /gfx:avc444 (Win10) */
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth, 32);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_SupportGraphicsPipeline, TRUE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GfxH264, gfx_h264_available);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GfxAVC444, gfx_h264_available);
+ } else if (freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth) == 99) {
+ /* Automatic (Let the server choose its best format) */
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth, 32);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_RemoteFxCodec, TRUE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_SupportGraphicsPipeline, TRUE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GfxH264, gfx_h264_available);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GfxAVC444, gfx_h264_available);
+ }
+
+ if (freerdp_settings_get_bool(rfi->clientContext.context.settings, FreeRDP_RemoteFxCodec) ||
+ freerdp_settings_get_bool(rfi->clientContext.context.settings, FreeRDP_NSCodec) ||
+ freerdp_settings_get_bool(rfi->clientContext.context.settings, FreeRDP_SupportGraphicsPipeline)) {
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_FastPathOutput, TRUE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_FrameMarkerCommandEnabled, TRUE);
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth, 32);
+ rfi->bpp = 32;
+ }
+
+ gint w = remmina_plugin_service->get_profile_remote_width(gp);
+ gint h = remmina_plugin_service->get_profile_remote_height(gp);
+ /* multiple of 4 */
+ w = (w + 3) & ~0x3;
+ h = (h + 3) & ~0x3;
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopWidth, w);
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopHeight, h);
+ REMMINA_PLUGIN_DEBUG("Resolution set by the user: %dx%d", w, h);
+
+ /* Workaround for FreeRDP issue #5417: in GFX AVC modes we can't go under
+ * AVC_MIN_DESKTOP_WIDTH x AVC_MIN_DESKTOP_HEIGHT */
+ if (freerdp_settings_get_bool(rfi->clientContext.context.settings, FreeRDP_SupportGraphicsPipeline) &&
+ freerdp_settings_get_bool(rfi->clientContext.context.settings, FreeRDP_GfxH264)) {
+ if (freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopWidth) <
+ AVC_MIN_DESKTOP_WIDTH)
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopWidth,
+ AVC_MIN_DESKTOP_WIDTH);
+ if (freerdp_settings_get_uint32(rfi->clientContext.context.settings,
+ FreeRDP_DesktopHeight) <
+ AVC_MIN_DESKTOP_HEIGHT)
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopHeight,
+ AVC_MIN_DESKTOP_HEIGHT);
+ }
+
+ /* Workaround for FreeRDP issue #5119. This will make our horizontal resolution
+ * an even value, but it will add a vertical black 1 pixel line on the
+ * right of the desktop */
+ if ((freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopWidth) & 1) != 0) {
+ UINT32 tmp = freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopWidth);
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopWidth, tmp - 1);
+ }
+
+ remmina_plugin_service->protocol_plugin_set_width(gp, freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopWidth));
+ remmina_plugin_service->protocol_plugin_set_height(gp, freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopHeight));
+
+ w = freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopWidth);
+ h = freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopHeight);
+ REMMINA_PLUGIN_DEBUG("Resolution set after workarounds: %dx%d", w, h);
+
+
+ if (remmina_plugin_service->file_get_string(remminafile, "username"))
+ freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_Username, remmina_plugin_service->file_get_string(remminafile, "username"));
+
+ if (remmina_plugin_service->file_get_string(remminafile, "domain"))
+ freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_Domain, remmina_plugin_service->file_get_string(remminafile, "domain"));
+
+ s = remmina_plugin_service->file_get_string(remminafile, "password");
+ if (s) freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_Password, s);
+
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_AutoLogonEnabled, TRUE);
+
+ /**
+ * Proxy support
+ * Proxy settings are hidden at the moment as an advanced feature
+ */
+ gchar *proxy_type = g_strdup(remmina_plugin_service->file_get_string(remminafile, "proxy_type"));
+ gchar *proxy_username = g_strdup(remmina_plugin_service->file_get_string(remminafile, "proxy_username"));
+ gchar *proxy_password = g_strdup(remmina_plugin_service->file_get_string(remminafile, "proxy_password"));
+ gchar *proxy_hostname = g_strdup(remmina_plugin_service->file_get_string(remminafile, "proxy_hostname"));
+ gint proxy_port = remmina_plugin_service->file_get_int(remminafile, "proxy_port", 80);
+ REMMINA_PLUGIN_DEBUG("proxy_type: %s", proxy_type);
+ REMMINA_PLUGIN_DEBUG("proxy_username: %s", proxy_username);
+ REMMINA_PLUGIN_DEBUG("proxy_password: %s", proxy_password);
+ REMMINA_PLUGIN_DEBUG("proxy_hostname: %s", proxy_hostname);
+ REMMINA_PLUGIN_DEBUG("proxy_port: %d", proxy_port);
+ if (proxy_type && proxy_hostname) {
+ if (g_strcmp0(proxy_type, "no_proxy") == 0)
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_ProxyType, PROXY_TYPE_IGNORE);
+ else if (g_strcmp0(proxy_type, "http") == 0)
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_ProxyType, PROXY_TYPE_HTTP);
+ else if (g_strcmp0(proxy_type, "socks5") == 0)
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_ProxyType, PROXY_TYPE_SOCKS);
+ else
+ g_warning("Invalid proxy protocol, at the moment only no_proxy, HTTP and SOCKS5 are supported");
+ REMMINA_PLUGIN_DEBUG("ProxyType set to: %" PRIu32, freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_ProxyType));
+ freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_ProxyHostname, proxy_hostname);
+ if (proxy_username)
+ freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_ProxyUsername, proxy_username);
+ if (proxy_password)
+ freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_ProxyPassword, proxy_password);
+ if (proxy_port)
+ freerdp_settings_set_uint16(rfi->clientContext.context.settings, FreeRDP_ProxyPort, proxy_port);
+ }
+ g_free(proxy_hostname);
+ g_free(proxy_username);
+ g_free(proxy_password);
+
+ if (remmina_plugin_service->file_get_int(remminafile, "base-cred-for-gw", FALSE)) {
+ // Reset gateway credentials
+ remmina_plugin_service->file_set_string(remminafile, "gateway_username", NULL);
+ remmina_plugin_service->file_set_string(remminafile, "gateway_domain", NULL);
+ remmina_plugin_service->file_set_string(remminafile, "gateway_password", NULL);
+ }
+
+ /* Remote Desktop Gateway server address */
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GatewayEnabled, FALSE);
+ s = remmina_plugin_service->file_get_string(remminafile, "gateway_server");
+ if (s) {
+ cs = remmina_plugin_service->file_get_string(remminafile, "gwtransp");
+#if FREERDP_CHECK_VERSION(2, 3, 1)
+ if (remmina_plugin_service->file_get_int(remminafile, "websockets", FALSE))
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GatewayHttpUseWebsockets, TRUE);
+ else
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GatewayHttpUseWebsockets, FALSE);
+#endif
+ if (g_strcmp0(cs, "http") == 0) {
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GatewayRpcTransport, FALSE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GatewayHttpTransport, TRUE);
+ } else if (g_strcmp0(cs, "rpc") == 0) {
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GatewayRpcTransport, TRUE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GatewayHttpTransport, FALSE);
+ } else if (g_strcmp0(cs, "auto") == 0) {
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GatewayRpcTransport, TRUE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GatewayHttpTransport, TRUE);
+ }
+ remmina_plugin_service->get_server_port(s, 443, &gateway_host, &gateway_port);
+ freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_GatewayHostname, gateway_host);
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_GatewayPort, gateway_port);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GatewayEnabled, TRUE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GatewayUseSameCredentials, TRUE);
+ }
+ /* Remote Desktop Gateway domain */
+ if (remmina_plugin_service->file_get_string(remminafile, "gateway_domain")) {
+ freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_GatewayDomain, remmina_plugin_service->file_get_string(remminafile, "gateway_domain"));
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GatewayUseSameCredentials, FALSE);
+ }
+ /* Remote Desktop Gateway username */
+ if (remmina_plugin_service->file_get_string(remminafile, "gateway_username")) {
+ freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_GatewayUsername, remmina_plugin_service->file_get_string(remminafile, "gateway_username"));
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GatewayUseSameCredentials, FALSE);
+ }
+ /* Remote Desktop Gateway password */
+ s = remmina_plugin_service->file_get_string(remminafile, "gateway_password");
+ if (s) {
+ freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_GatewayPassword, s);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GatewayUseSameCredentials, FALSE);
+ }
+ /* If no different credentials were provided for the Remote Desktop Gateway
+ * use the same authentication credentials for the host */
+ if (freerdp_settings_get_bool(rfi->clientContext.context.settings, FreeRDP_GatewayEnabled) && freerdp_settings_get_bool(rfi->clientContext.context.settings, FreeRDP_GatewayUseSameCredentials)) {
+ freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_GatewayDomain, freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_Domain));
+ freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_GatewayUsername, freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_Username));
+ freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_GatewayPassword, freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_Password));
+ }
+ /* Remote Desktop Gateway usage */
+ if (freerdp_settings_get_bool(rfi->clientContext.context.settings, FreeRDP_GatewayEnabled))
+ freerdp_set_gateway_usage_method(rfi->clientContext.context.settings,
+ remmina_plugin_service->file_get_int(remminafile, "gateway_usage", FALSE) ? TSC_PROXY_MODE_DETECT : TSC_PROXY_MODE_DIRECT);
+
+ freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_GatewayAccessToken,
+ remmina_plugin_service->file_get_string(remminafile, "gatewayaccesstoken"));
+
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_AuthenticationLevel, remmina_plugin_service->file_get_int(
+ remminafile, "authentication level", freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_AuthenticationLevel)));
+
+ /* Certificate ignore */
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_IgnoreCertificate, remmina_plugin_service->file_get_int(remminafile, "cert_ignore", 0));
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_OldLicenseBehaviour, remmina_plugin_service->file_get_int(remminafile, "old-license", 0));
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_AllowUnanouncedOrdersFromServer, remmina_plugin_service->file_get_int(remminafile, "relax-order-checks", 0));
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_GlyphSupportLevel, (remmina_plugin_service->file_get_int(remminafile, "glyph-cache", 0) ? GLYPH_SUPPORT_FULL : GLYPH_SUPPORT_NONE));
+
+ if ((cs = remmina_plugin_service->file_get_string(remminafile, "clientname")))
+ freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_ClientHostname, cs);
+ else
+ freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_ClientHostname, g_get_host_name());
+
+ /* Client Build number is optional, if not specified defaults to 0, allow for comments to appear after number */
+ if ((cs = remmina_plugin_service->file_get_string(remminafile, "clientbuild"))) {
+ if (*cs) {
+ UINT32 val = strtoul(cs, NULL, 0);
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_ClientBuild, val);
+ }
+ }
+
+
+ if (remmina_plugin_service->file_get_string(remminafile, "loadbalanceinfo")) {
+ const gchar *tmp = strdup(remmina_plugin_service->file_get_string(remminafile, "loadbalanceinfo"));
+ freerdp_settings_set_pointer_len(rfi->clientContext.context.settings, FreeRDP_LoadBalanceInfo, tmp, strlen(tmp) + 1);
+ }
+
+ if (remmina_plugin_service->file_get_string(remminafile, "exec"))
+ freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_AlternateShell, remmina_plugin_service->file_get_string(remminafile, "exec"));
+
+ if (remmina_plugin_service->file_get_string(remminafile, "execpath"))
+ freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_ShellWorkingDirectory, remmina_plugin_service->file_get_string(remminafile, "execpath"));
+
+ sm = g_strdup_printf("rdp_quality_%i", remmina_plugin_service->file_get_int(remminafile, "quality", DEFAULT_QUALITY_0));
+ value = remmina_plugin_service->pref_get_value(sm);
+ g_free(sm);
+
+ if (value && value[0]) {
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_PerformanceFlags, strtoul(value, NULL, 16));
+ } else {
+ switch (remmina_plugin_service->file_get_int(remminafile, "quality", DEFAULT_QUALITY_0)) {
+ case 9:
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_PerformanceFlags, DEFAULT_QUALITY_9);
+ break;
+
+ case 2:
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_PerformanceFlags, DEFAULT_QUALITY_2);
+ break;
+
+ case 1:
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_PerformanceFlags, DEFAULT_QUALITY_1);
+ break;
+
+ case 0:
+ default:
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_PerformanceFlags, DEFAULT_QUALITY_0);
+ break;
+ }
+ }
+ g_free(value);
+
+ if ((cs = remmina_plugin_service->file_get_string(remminafile, "network"))) {
+ guint32 type = 0;
+
+ if (g_strcmp0(cs, "modem") == 0)
+ type = CONNECTION_TYPE_MODEM;
+ else if (g_strcmp0(cs, "broadband") == 0)
+ type = CONNECTION_TYPE_BROADBAND_HIGH;
+ else if (g_strcmp0(cs, "broadband-low") == 0)
+ type = CONNECTION_TYPE_BROADBAND_LOW;
+ else if (g_strcmp0(cs, "broadband-high") == 0)
+ type = CONNECTION_TYPE_BROADBAND_HIGH;
+ else if (g_strcmp0(cs, "wan") == 0)
+ type = CONNECTION_TYPE_WAN;
+ else if (g_strcmp0(cs, "lan") == 0)
+ type = CONNECTION_TYPE_LAN;
+ else if ((g_strcmp0(cs, "autodetect") == 0))
+ type = CONNECTION_TYPE_AUTODETECT;
+ else if ((g_strcmp0(cs, "none") == 0))
+ type = REMMINA_CONNECTION_TYPE_NONE;
+ else
+ type = REMMINA_CONNECTION_TYPE_NONE;
+
+ if (!remmina_rdp_set_connection_type(rfi->clientContext.context.settings, type))
+ REMMINA_PLUGIN_DEBUG("Network settings not set");
+ }
+
+ /* PerformanceFlags bitmask need also to be splitted into BOOL variables
+ * like freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_DisableWallpaper, freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_AllowFontSmoothing…
+ * or freerdp_get_param_bool() function will return the wrong value
+ */
+ freerdp_performance_flags_split(rfi->clientContext.context.settings);
+
+#ifdef GDK_WINDOWING_X11
+#if FREERDP_CHECK_VERSION(2, 3, 0)
+ rdp_kbd_remap = remmina_get_rdp_kbd_remap(remmina_plugin_service->file_get_string(remminafile, "keymap"));
+ if (rdp_kbd_remap != NULL) {
+ freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_KeyboardRemappingList, rdp_kbd_remap);
+ REMMINA_PLUGIN_DEBUG(
+ "rdp_keyboard_remapping_list: %s",
+ freerdp_settings_get_string(rfi->clientContext.context.settings,
+ FreeRDP_KeyboardRemappingList));
+ g_free(rdp_kbd_remap);
+ }
+ else {
+ freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_KeyboardRemappingList, remmina_plugin_service->pref_get_value("rdp_kbd_remap"));
+ REMMINA_PLUGIN_DEBUG(
+ "rdp_keyboard_remapping_list: %s",
+ freerdp_settings_get_string(rfi->clientContext.context.settings,
+ FreeRDP_KeyboardRemappingList));
+ }
+#endif
+#endif
+
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_KeyboardLayout, remmina_rdp_settings_get_keyboard_layout());
+
+ if (remmina_plugin_service->file_get_int(remminafile, "console", FALSE))
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_ConsoleSession, TRUE);
+
+ if (remmina_plugin_service->file_get_int(remminafile, "restricted-admin", FALSE)) {
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_ConsoleSession, TRUE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_RestrictedAdminModeRequired, TRUE);
+ }
+
+ if (remmina_plugin_service->file_get_string(remminafile, "pth")) {
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_ConsoleSession, TRUE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_RestrictedAdminModeRequired, TRUE);
+ freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_PasswordHash, remmina_plugin_service->file_get_string(remminafile, "pth"));
+ remmina_plugin_service->file_set_int(remminafile, "restricted-admin", TRUE);
+ }
+
+ cs = remmina_plugin_service->file_get_string(remminafile, "security");
+ if (g_strcmp0(cs, "rdp") == 0) {
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_RdpSecurity, TRUE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_TlsSecurity, FALSE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_NlaSecurity, FALSE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_ExtSecurity, FALSE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_UseRdpSecurityLayer, TRUE);
+ } else if (g_strcmp0(cs, "tls") == 0) {
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_RdpSecurity, FALSE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_TlsSecurity, TRUE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_NlaSecurity, FALSE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_ExtSecurity, FALSE);
+ } else if (g_strcmp0(cs, "nla") == 0) {
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_RdpSecurity, FALSE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_TlsSecurity, FALSE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_NlaSecurity, TRUE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_ExtSecurity, FALSE);
+ } else if (g_strcmp0(cs, "ext") == 0) {
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_RdpSecurity, FALSE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_TlsSecurity, FALSE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_NlaSecurity, FALSE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_ExtSecurity, TRUE);
+ } else {
+ /* This is "-nego" switch of xfreerdp */
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_NegotiateSecurityLayer, TRUE);
+ }
+
+ cs = remmina_plugin_service->file_get_string(remminafile, "tls-seclevel");
+ if (cs && g_strcmp0(cs,"")!=0) {
+ i = atoi(cs);
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_TlsSecLevel, i);
+ }
+
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_CompressionEnabled, TRUE);
+ if (remmina_plugin_service->file_get_int(remminafile, "disable_fastpath", FALSE)) {
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_FastPathInput, FALSE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_FastPathOutput, FALSE);
+ } else {
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_FastPathInput, TRUE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_FastPathOutput, TRUE);
+ }
+
+ /* Orientation and scaling settings */
+ remmina_rdp_settings_get_orientation_scale_prefs(&desktopOrientation, &desktopScaleFactor, &deviceScaleFactor);
+
+ freerdp_settings_set_uint16(rfi->clientContext.context.settings, FreeRDP_DesktopOrientation, desktopOrientation);
+ if (desktopScaleFactor != 0 && deviceScaleFactor != 0) {
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopScaleFactor, desktopScaleFactor);
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_DeviceScaleFactor, deviceScaleFactor);
+ }
+
+ /* Try to enable "Display Control Virtual Channel Extension", needed to
+ * dynamically resize remote desktop. This will automatically open
+ * the "disp" dynamic channel, if available */
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_SupportDisplayControl, TRUE);
+
+ if (freerdp_settings_get_bool(rfi->clientContext.context.settings, FreeRDP_SupportDisplayControl)) {
+ CLPARAM *d[1];
+ int dcount;
+
+ dcount = 1;
+ d[0] = "disp";
+ freerdp_client_add_dynamic_channel(rfi->clientContext.context.settings, dcount, d);
+ }
+
+ if (freerdp_settings_get_bool(rfi->clientContext.context.settings, FreeRDP_SupportGraphicsPipeline)) {
+ CLPARAM *d[1];
+
+ int dcount;
+
+ dcount = 1;
+ d[0] = "rdpgfx";
+ freerdp_client_add_dynamic_channel(rfi->clientContext.context.settings, dcount, d);
+ }
+
+ /* Sound settings */
+ cs = remmina_plugin_service->file_get_string(remminafile, "sound");
+ if (g_strcmp0(cs, "remote") == 0) {
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_RemoteConsoleAudio, TRUE);
+ } else if ((cs != NULL && cs[0] != '\0') && g_str_has_prefix(cs, "local")) {
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_AudioPlayback, TRUE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_AudioCapture, TRUE);
+ } else {
+ /* Disable sound */
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_AudioPlayback, FALSE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_RemoteConsoleAudio, FALSE);
+ }
+
+ cs = remmina_plugin_service->file_get_string(remminafile, "microphone");
+ if (cs != NULL && cs[0] != '\0') {
+ if (g_strcmp0(cs, "0") == 0) {
+ REMMINA_PLUGIN_DEBUG("“microphone” was set to 0, setting to \"\"");
+ remmina_plugin_service->file_set_string(remminafile, "microphone", "");
+ } else {
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_AudioCapture, TRUE);
+ REMMINA_PLUGIN_DEBUG("“microphone” set to “%s”", cs);
+ CLPARAM **p;
+ size_t count;
+
+ p = remmina_rdp_CommandLineParseCommaSeparatedValuesEx("audin", g_strdup(cs), &count);
+
+ freerdp_client_add_dynamic_channel(rfi->clientContext.context.settings, count, p);
+ g_free(p);
+ }
+ }
+
+ cs = remmina_plugin_service->file_get_string(remminafile, "audio-output");
+ if (cs != NULL && cs[0] != '\0') {
+ REMMINA_PLUGIN_DEBUG("audio output set to %s", cs);
+ CLPARAM **p;
+ size_t count;
+
+ p = remmina_rdp_CommandLineParseCommaSeparatedValuesEx("rdpsnd", g_strdup(cs), &count);
+ status = freerdp_client_add_static_channel(rfi->clientContext.context.settings, count, p);
+ if (status)
+ status = freerdp_client_add_dynamic_channel(rfi->clientContext.context.settings, count, p);
+ g_free(p);
+ }
+
+
+ cs = remmina_plugin_service->file_get_string(remminafile, "freerdp_log_level");
+ if (cs != NULL && cs[0] != '\0')
+ REMMINA_PLUGIN_DEBUG("Log level set to to %s", cs);
+ else
+ cs = g_strdup("INFO");
+ wLog *root = WLog_GetRoot();
+ WLog_SetStringLogLevel(root, cs);
+
+ cs = remmina_plugin_service->file_get_string(remminafile, "freerdp_log_filters");
+ if (cs != NULL && cs[0] != '\0') {
+ REMMINA_PLUGIN_DEBUG("Log filters set to to %s", cs);
+ WLog_AddStringLogFilters(cs);
+ } else {
+ WLog_AddStringLogFilters(NULL);
+ }
+
+
+ cs = remmina_plugin_service->file_get_string(remminafile, "usb");
+ if (cs != NULL && cs[0] != '\0') {
+ CLPARAM **p;
+ size_t count;
+ p = remmina_rdp_CommandLineParseCommaSeparatedValuesEx("urbdrc", g_strdup(cs), &count);
+ freerdp_client_add_dynamic_channel(rfi->clientContext.context.settings, count, p);
+ g_free(p);
+ }
+
+ cs = remmina_plugin_service->file_get_string(remminafile, "vc");
+ if (cs != NULL && cs[0] != '\0') {
+ CLPARAM **p;
+ size_t count;
+ p = remmina_rdp_CommandLineParseCommaSeparatedValues(g_strdup(cs), &count);
+ freerdp_client_add_static_channel(rfi->clientContext.context.settings, count, p);
+ g_free(p);
+ }
+
+ cs = remmina_plugin_service->file_get_string(remminafile, "dvc");
+ if (cs != NULL && cs[0] != '\0') {
+ CLPARAM **p;
+ size_t count;
+ p = remmina_rdp_CommandLineParseCommaSeparatedValues(g_strdup(cs), &count);
+ freerdp_client_add_dynamic_channel(rfi->clientContext.context.settings, count, p);
+ g_free(p);
+ }
+
+ cs = remmina_plugin_service->file_get_string(remminafile, RDP2TCP_DVC_CHANNEL_NAME);
+ if (cs != NULL && cs[0] != '\0') {
+ freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_RDP2TCPArgs, cs);
+
+#if FREERDP_VERSION_MAJOR >= 3
+ char* args = freerdp_settings_get_string_writable(rfi->clientContext.context.settings, FreeRDP_RDP2TCPArgs);
+#else
+ char* args = freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_RDP2TCPArgs);
+#endif
+ REMMINA_PLUGIN_DEBUG(RDP2TCP_DVC_CHANNEL_NAME " set to %s",args);
+ remmina_rdp_load_static_channel_addin(channels, rfi->clientContext.context.settings, RDP2TCP_DVC_CHANNEL_NAME, args);
+ }
+
+ int vermaj, vermin, verrev;
+ freerdp_get_version(&vermaj, &vermin, &verrev);
+
+#if FREERDP_CHECK_VERSION(2, 1, 0)
+ cs = remmina_plugin_service->file_get_string(remminafile, "timeout");
+ if (cs != NULL && cs[0] != '\0') {
+ const gchar *endptr = NULL;
+ guint64 val = g_ascii_strtoull(cs, (gchar **)&endptr, 10);
+ if (val > 600000 || val <= 0)
+ val = 600000;
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_TcpAckTimeout, (UINT32)val);
+ }
+#endif
+
+ if (remmina_plugin_service->file_get_int(remminafile, "preferipv6", FALSE) ? TRUE : FALSE)
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_PreferIPv6OverIPv4, TRUE);
+
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_RedirectClipboard, remmina_plugin_service->file_get_int(remminafile, "disableclipboard", FALSE) ? FALSE : TRUE);
+
+ cs = remmina_plugin_service->file_get_string(remminafile, "sharefolder");
+ if (cs != NULL && cs[0] != '\0') {
+ gchar *ccs = g_strdup(cs);
+ REMMINA_PLUGIN_DEBUG("[Deprecated->migrating] - Old sharefolder %s to \"drive \"", ccs);
+ if (!remmina_plugin_service->file_get_string(remminafile, "drive")) {
+ remmina_plugin_service->file_set_string(remminafile, "drive", g_strdup(ccs));
+ remmina_plugin_service->file_set_string(remminafile, "sharefolder", NULL);
+ REMMINA_PLUGIN_DEBUG("[Deprecated->migrated] - drive set to %s", g_strdup(ccs));
+ }
+ g_free(ccs);
+ //CLPARAM **p;
+ //size_t count;
+ //p = remmina_rdp_CommandLineParseCommaSeparatedValuesEx("drive", g_strdup(cs), &count);
+ //status = freerdp_client_add_device_channel(rfi->clientContext.context.settings, count, p);
+ //g_free(p);
+ }
+ cs = remmina_plugin_service->file_get_string(remminafile, "drive");
+ if (cs != NULL && cs[0] != '\0') {
+ REMMINA_PLUGIN_DEBUG("Redirect directory set to %s", cs);
+ CLPARAM **p;
+ size_t count;
+
+ gchar **folders = g_strsplit(cs, ";", -1);
+ for (i = 0; folders[i] != NULL; i++) {
+ REMMINA_PLUGIN_DEBUG("Parsing folder %s", folders[i]);
+ p = remmina_rdp_CommandLineParseCommaSeparatedValuesEx("drive", g_strdup(folders[i]), &count);
+ status = freerdp_client_add_device_channel(rfi->clientContext.context.settings, count, p);
+ g_free(p);
+ }
+ g_strfreev(folders);
+ }
+
+ if (remmina_plugin_service->file_get_int(remminafile, "shareprinter", FALSE)) {
+#ifdef HAVE_CUPS
+ REMMINA_PLUGIN_DEBUG("Sharing printers");
+ const gchar *po = remmina_plugin_service->file_get_string(remminafile, "printer_overrides");
+ if (po && po[0] != 0) {
+ /* Fallback to remmina code to override print drivers */
+ if (cupsEnumDests(CUPS_DEST_FLAGS_NONE, 1000, NULL, 0, 0, remmina_rdp_set_printers, rfi))
+ REMMINA_PLUGIN_DEBUG("All printers have been shared");
+ else
+ REMMINA_PLUGIN_DEBUG("Cannot share printers, are there any available?");
+ } else {
+ /* Use libfreerdp code to map all printers */
+ CLPARAM *d[1];
+ int dcount;
+ dcount = 1;
+ d[0] = "printer";
+ freerdp_client_add_device_channel(rfi->clientContext.context.settings, dcount, d);
+ }
+#endif /* HAVE_CUPS */
+ }
+
+ if (remmina_plugin_service->file_get_int(remminafile, "span", FALSE)) {
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_SpanMonitors, TRUE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_UseMultimon, TRUE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_Fullscreen, TRUE);
+ remmina_plugin_service->file_set_int(remminafile, "multimon", 1);
+ }
+
+ if (remmina_plugin_service->file_get_int(remminafile, "multimon", FALSE)) {
+ guint32 maxwidth = 0;
+ guint32 maxheight = 0;
+ gchar *monitorids;
+ guint32 i;
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_UseMultimon, TRUE);
+ /* TODO Add an option for this */
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_ForceMultimon, TRUE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_Fullscreen, TRUE);
+
+ gchar *monitorids_string = g_strdup(remmina_plugin_service->file_get_string(remminafile, "monitorids"));
+ /* Otherwise we get all the attached monitors
+ * monitorids may contains desktop orientation values.
+ * But before we check if there are orientation attributes
+ */
+ if (monitorids_string != NULL && monitorids_string[0] != '\0') {
+ if (g_strstr_len(monitorids_string, -1, ",") != NULL) {
+ if (g_strstr_len(monitorids_string, -1, ":") != NULL) {
+ rdpMonitor *base = (rdpMonitor *)freerdp_settings_get_pointer(rfi->clientContext.context.settings, FreeRDP_MonitorDefArray);
+ /* We have an ID and an orientation degree */
+ gchar **temp_items;
+ gchar **rot_items;
+ temp_items = g_strsplit(monitorids_string, ",", -1);
+ for (i = 0; i < g_strv_length(temp_items); i++) {
+ rot_items = g_strsplit(temp_items[i], ":", -1);
+ rdpMonitor *current = &base[atoi(rot_items[0])];
+ if (i == 0)
+ monitorids = g_strdup(rot_items[0]);
+ else
+ monitorids = g_strdup_printf("%s,%s", monitorids, rot_items[0]);
+ current->attributes.orientation = atoi(rot_items[1]);
+ REMMINA_PLUGIN_DEBUG("Monitor n %d orientation: %d", i, current->attributes.orientation);
+ }
+ } else {
+ monitorids = g_strdup(monitorids_string);
+ }
+ } else {
+ monitorids = g_strdup(monitorids_string);
+ }
+ } else {
+ monitorids = g_strdup(monitorids_string);
+ }
+ remmina_rdp_monitor_get(rfi, &monitorids, &maxwidth, &maxheight);
+ if (monitorids != NULL && monitorids[0] != '\0') {
+ UINT32 *base = (UINT32 *)freerdp_settings_get_pointer(rfi->clientContext.context.settings, FreeRDP_MonitorIds);
+ gchar **items;
+ items = g_strsplit(monitorids, ",", -1);
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_NumMonitorIds, g_strv_length(items));
+ REMMINA_PLUGIN_DEBUG("NumMonitorIds: %d", freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_NumMonitorIds));
+ for (i = 0; i < g_strv_length(items); i++) {
+ if (base != NULL){
+ UINT32 *current = &base[i];
+ *current = atoi(items[i]);
+ REMMINA_PLUGIN_DEBUG("Added monitor with ID %" PRIu32, *current);
+ }
+ }
+ g_free(monitorids);
+ g_strfreev(items);
+ }
+ if (maxwidth && maxheight) {
+ REMMINA_PLUGIN_DEBUG("Setting DesktopWidth and DesktopHeight to: %dx%d", maxwidth, maxheight);
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopWidth, maxwidth);
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopHeight, maxheight);
+ REMMINA_PLUGIN_DEBUG("DesktopWidth and DesktopHeight set to: %dx%d", freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopWidth), freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopHeight));
+ } else {
+ REMMINA_PLUGIN_DEBUG("Cannot set Desktop Size, we are using the previously set values: %dx%d", freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopWidth), freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopHeight));
+ }
+ remmina_plugin_service->protocol_plugin_set_width(gp, freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopWidth));
+ remmina_plugin_service->protocol_plugin_set_height(gp, freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopHeight));
+ }
+
+ const gchar *sn = remmina_plugin_service->file_get_string(remminafile, "smartcardname");
+ if (remmina_plugin_service->file_get_int(remminafile, "sharesmartcard", FALSE) ||
+ (sn != NULL && sn[0] != '\0')) {
+ RDPDR_SMARTCARD *smartcard;
+ smartcard = (RDPDR_SMARTCARD *)calloc(1, sizeof(RDPDR_SMARTCARD));
+
+#if FREERDP_VERSION_MAJOR >= 3
+ RDPDR_DEVICE *sdev;
+ sdev = &(smartcard->device);
+#else
+ RDPDR_SMARTCARD *sdev;
+ sdev = smartcard;
+#endif
+
+ sdev->Type = RDPDR_DTYP_SMARTCARD;
+
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_DeviceRedirection, TRUE);
+
+ if (sn != NULL && sn[0] != '\0')
+ sdev->Name = _strdup(sn);
+
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_RedirectSmartCards, TRUE);
+
+ freerdp_device_collection_add(rfi->clientContext.context.settings, (RDPDR_DEVICE *)smartcard);
+ }
+
+ if (remmina_plugin_service->file_get_int(remminafile, "passwordispin", FALSE))
+ /* Option works only combined with Username and Domain, because FreeRDP
+ * doesn’t know anything about info on smart card */
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_PasswordIsSmartcardPin, TRUE);
+
+ /* /serial[:<name>[,<path>[,<driver>[,permissive]]]] */
+ if (remmina_plugin_service->file_get_int(remminafile, "shareserial", FALSE)) {
+ RDPDR_SERIAL *serial;
+ serial = (RDPDR_SERIAL *)calloc(1, sizeof(RDPDR_SERIAL));
+
+#if FREERDP_VERSION_MAJOR >= 3
+ RDPDR_DEVICE *sdev;
+ sdev = &(serial->device);
+#else
+ RDPDR_SERIAL *sdev;
+ sdev = serial;
+#endif
+
+ sdev->Type = RDPDR_DTYP_SERIAL;
+
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_DeviceRedirection, TRUE);
+
+ const gchar *sn = remmina_plugin_service->file_get_string(remminafile, "serialname");
+ if (sn != NULL && sn[0] != '\0')
+ sdev->Name = _strdup(sn);
+
+ const gchar *sd = remmina_plugin_service->file_get_string(remminafile, "serialdriver");
+ if (sd != NULL && sd[0] != '\0')
+ serial->Driver = _strdup(sd);
+
+ const gchar *sp = remmina_plugin_service->file_get_string(remminafile, "serialpath");
+ if (sp != NULL && sp[0] != '\0')
+ serial->Path = _strdup(sp);
+
+ if (remmina_plugin_service->file_get_int(remminafile, "serialpermissive", FALSE))
+ serial->Permissive = _strdup("permissive");
+
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_RedirectSerialPorts, TRUE);
+
+ freerdp_device_collection_add(rfi->clientContext.context.settings, (RDPDR_DEVICE *)serial);
+ }
+
+ if (remmina_plugin_service->file_get_int(remminafile, "shareparallel", FALSE)) {
+ RDPDR_PARALLEL *parallel;
+ parallel = (RDPDR_PARALLEL *)calloc(1, sizeof(RDPDR_PARALLEL));
+
+#if FREERDP_VERSION_MAJOR >= 3
+ RDPDR_DEVICE *pdev;
+ pdev = &(parallel->device);
+#else
+ RDPDR_PARALLEL *pdev;
+ pdev = parallel;
+#endif
+
+ pdev->Type = RDPDR_DTYP_PARALLEL;
+
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_DeviceRedirection, TRUE);
+
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_RedirectParallelPorts, TRUE);
+
+ const gchar *pn = remmina_plugin_service->file_get_string(remminafile, "parallelname");
+ if (pn != NULL && pn[0] != '\0')
+ pdev->Name = _strdup(pn);
+ const gchar *dp = remmina_plugin_service->file_get_string(remminafile, "parallelpath");
+ if (dp != NULL && dp[0] != '\0')
+ parallel->Path = _strdup(dp);
+
+ freerdp_device_collection_add(rfi->clientContext.context.settings, (RDPDR_DEVICE *)parallel);
+ }
+
+ /**
+ * multitransport enables RDP8 UDP support
+ */
+ if (remmina_plugin_service->file_get_int(remminafile, "multitransport", FALSE)) {
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_DeviceRedirection, TRUE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_SupportMultitransport, TRUE);
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_MultitransportFlags,
+ (TRANSPORT_TYPE_UDP_FECR | TRANSPORT_TYPE_UDP_FECL | TRANSPORT_TYPE_UDP_PREFERRED));
+ } else {
+ freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_MultitransportFlags, 0);
+ }
+
+ /* If needed, force interactive authentication by deleting all authentication fields,
+ * forcing libfreerdp to call our callbacks for authentication.
+ * This usually happens from a second attempt of connection, never on the 1st one. */
+ if (rfi->attempt_interactive_authentication) {
+ freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_Username, NULL);
+ freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_Password, NULL);
+ freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_Domain, NULL);
+
+ freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_GatewayDomain, NULL);
+ freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_GatewayUsername, NULL);
+ freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_GatewayPassword, NULL);
+
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GatewayUseSameCredentials, FALSE);
+ }
+
+ gboolean orphaned;
+
+ if (!freerdp_connect(rfi->clientContext.context.instance)) {
+ orphaned = (GET_PLUGIN_DATA(rfi->protocol_widget) == NULL);
+ if (!orphaned) {
+ UINT32 e;
+
+ e = freerdp_get_last_error(&rfi->clientContext.context);
+
+ switch (e) {
+ case FREERDP_ERROR_AUTHENTICATION_FAILED:
+ case STATUS_LOGON_FAILURE: // wrong return code from FreeRDP introduced at the end of July 2016? (fixed with b86c0ba)
+#ifdef FREERDP_ERROR_CONNECT_LOGON_FAILURE
+ case FREERDP_ERROR_CONNECT_LOGON_FAILURE:
+#endif
+ /* Logon failure, will retry with interactive authentication */
+ rfi->attempt_interactive_authentication = TRUE;
+ break;
+ case STATUS_ACCOUNT_LOCKED_OUT:
+#ifdef FREERDP_ERROR_CONNECT_ACCOUNT_LOCKED_OUT
+ case FREERDP_ERROR_CONNECT_ACCOUNT_LOCKED_OUT:
+#endif
+ remmina_plugin_service->protocol_plugin_set_error(gp, _("Could not access the RDP server “%s”.\nAccount locked out."),
+ freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname));
+ break;
+ case STATUS_ACCOUNT_EXPIRED:
+#ifdef FREERDP_ERROR_CONNECT_ACCOUNT_EXPIRED
+ case FREERDP_ERROR_CONNECT_ACCOUNT_EXPIRED:
+#endif
+ remmina_plugin_service->protocol_plugin_set_error(gp, _("Could not access the RDP server “%s”.\nAccount expired."),
+ freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname));
+ break;
+ case STATUS_PASSWORD_EXPIRED:
+#ifdef FREERDP_ERROR_CONNECT_PASSWORD_EXPIRED
+ case FREERDP_ERROR_CONNECT_PASSWORD_EXPIRED:
+#endif
+ remmina_plugin_service->protocol_plugin_set_error(gp, _("Could not access the RDP server “%s”.\nPassword expired."),
+ freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname));
+ break;
+ case STATUS_ACCOUNT_DISABLED:
+#ifdef FREERDP_ERROR_CONNECT_ACCOUNT_DISABLED
+ case FREERDP_ERROR_CONNECT_ACCOUNT_DISABLED:
+#endif
+ remmina_plugin_service->protocol_plugin_set_error(gp, _("Could not access the RDP server “%s”.\nAccount disabled."),
+ freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname));
+ break;
+#ifdef FREERDP_ERROR_SERVER_INSUFFICIENT_PRIVILEGES
+ /* https://msdn.microsoft.com/en-us/library/ee392247.aspx */
+ case FREERDP_ERROR_SERVER_INSUFFICIENT_PRIVILEGES:
+ remmina_plugin_service->protocol_plugin_set_error(gp, _("Could not access the RDP server “%s”.\nInsufficient user privileges."),
+ freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname));
+ break;
+#endif
+ case STATUS_ACCOUNT_RESTRICTION:
+#ifdef FREERDP_ERROR_CONNECT_ACCOUNT_RESTRICTION
+ case FREERDP_ERROR_CONNECT_ACCOUNT_RESTRICTION:
+#endif
+ remmina_plugin_service->protocol_plugin_set_error(gp, _("Could not access the RDP server “%s”.\nAccount restricted."),
+ freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname));
+ break;
+
+ case STATUS_PASSWORD_MUST_CHANGE:
+#ifdef FREERDP_ERROR_CONNECT_PASSWORD_MUST_CHANGE
+ case FREERDP_ERROR_CONNECT_PASSWORD_MUST_CHANGE:
+#endif
+ remmina_plugin_service->protocol_plugin_set_error(gp, _("Could not access the RDP server “%s”.\nChange user password before connecting."),
+ freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname));
+ break;
+
+ case FREERDP_ERROR_CONNECT_FAILED:
+ remmina_plugin_service->protocol_plugin_set_error(gp, _("Lost connection to the RDP server “%s”."), freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname));
+ break;
+ case FREERDP_ERROR_DNS_NAME_NOT_FOUND:
+ remmina_plugin_service->protocol_plugin_set_error(gp, _("Could not find the address for the RDP server “%s”."), freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname));
+ break;
+ case FREERDP_ERROR_TLS_CONNECT_FAILED:
+ remmina_plugin_service->protocol_plugin_set_error(gp,
+ _("Could not connect to the RDP server “%s” via TLS. See the DEBUG traces from a terminal for more information."), freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname));
+ break;
+ case FREERDP_ERROR_SECURITY_NEGO_CONNECT_FAILED:
+ // TRANSLATORS: the placeholder may be either an IP/FQDN or a server hostname
+ remmina_plugin_service->protocol_plugin_set_error(gp, _("Unable to establish a connection to the RDP server “%s”. Check “Security protocol negotiation”."), freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname));
+ break;
+#ifdef FREERDP_ERROR_POST_CONNECT_FAILED
+ case FREERDP_ERROR_POST_CONNECT_FAILED:
+ /* remmina_rdp_post_connect() returned FALSE to libfreerdp. We saved the error on rfi->postconnect_error */
+ switch (rfi->postconnect_error) {
+ case REMMINA_POSTCONNECT_ERROR_OK:
+ /* We should never come here */
+ remmina_plugin_service->protocol_plugin_set_error(gp, _("Cannot connect to the RDP server “%s”."), freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname));
+ break;
+ case REMMINA_POSTCONNECT_ERROR_GDI_INIT:
+ remmina_plugin_service->protocol_plugin_set_error(gp, _("Could not start libfreerdp-gdi."));
+ break;
+ case REMMINA_POSTCONNECT_ERROR_NO_H264:
+ remmina_plugin_service->protocol_plugin_set_error(gp, _("You requested a H.264 GFX mode for the server “%s”, but your libfreerdp does not support H.264. Please use a non-AVC colour depth setting."), freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname));
+ break;
+ }
+ break;
+#endif
+#ifdef FREERDP_ERROR_SERVER_DENIED_CONNECTION
+ case FREERDP_ERROR_SERVER_DENIED_CONNECTION:
+ remmina_plugin_service->protocol_plugin_set_error(gp, _("The “%s” server refused the connection."), freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname));
+ break;
+#endif
+ case 0x800759DB:
+ // E_PROXY_NAP_ACCESSDENIED https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tsgu/84cd92e4-592c-4219-95d8-18021ac654b0
+ remmina_plugin_service->protocol_plugin_set_error(gp, _("The Remote Desktop Gateway “%s” denied the user “%s\\%s” access due to policy."),
+ freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_GatewayHostname), freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_GatewayDomain), freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_GatewayUsername));
+ break;
+
+ case FREERDP_ERROR_CONNECT_NO_OR_MISSING_CREDENTIALS:
+ rfi->user_cancelled = TRUE;
+ break;
+
+ default:
+ g_printf("libfreerdp returned code is %08X\n", e);
+ remmina_plugin_service->protocol_plugin_set_error(gp, _("Cannot connect to the “%s” RDP server."), freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname));
+ break;
+ }
+ }
+
+ return FALSE;
+ }
+
+ if (GET_PLUGIN_DATA(rfi->protocol_widget) == NULL) orphaned = true; else orphaned = false;
+ if (!orphaned && freerdp_get_last_error(&rfi->clientContext.context) == FREERDP_ERROR_SUCCESS && !rfi->user_cancelled)
+ remmina_rdp_main_loop(gp);
+
+ return TRUE;
+}
+
+static void rfi_uninit(rfContext *rfi)
+{
+ freerdp *instance;
+
+ instance = rfi->clientContext.context.instance;
+
+ if (rfi->remmina_plugin_thread) {
+ rfi->thread_cancelled = TRUE; // Avoid all rf_queue function to run
+ pthread_cancel(rfi->remmina_plugin_thread);
+ if (rfi->remmina_plugin_thread)
+ pthread_join(rfi->remmina_plugin_thread, NULL);
+ }
+
+ if (instance) {
+ if (rfi->connected) {
+ freerdp_abort_connect_context(&rfi->clientContext.context);
+ rfi->connected = false;
+ }
+ }
+
+ if (instance) {
+ RDP_CLIENT_ENTRY_POINTS *pEntryPoints = instance->pClientEntryPoints;
+ if (pEntryPoints)
+ IFCALL(pEntryPoints->GlobalUninit);
+ free(instance->pClientEntryPoints);
+ freerdp_context_free(instance); /* context is rfContext* rfi */
+ freerdp_free(instance); /* This implicitly frees instance->context and rfi is no longer valid */
+ }
+}
+
+static gboolean complete_cleanup_on_main_thread(gpointer data)
+{
+ TRACE_CALL(__func__);
+
+ gboolean orphaned;
+ rfContext *rfi = (rfContext *)data;
+ RemminaProtocolWidget *gp;
+
+ remmina_rdp_clipboard_free(rfi);
+
+ gdi_free(rfi->clientContext.context.instance);
+
+ gp = rfi->protocol_widget;
+ if (GET_PLUGIN_DATA(gp) == NULL) orphaned = true; else orphaned = false;
+
+ remmina_rdp_cliprdr_detach_owner(gp);
+ if (!orphaned) remmina_rdp_event_uninit(gp);
+
+ if (!orphaned) g_object_steal_data(G_OBJECT(gp), "plugin-data");
+
+ rfi_uninit(rfi);
+
+ /* Notify the RemminaProtocolWidget that we closed our connection, and the GUI interface
+ * can be removed */
+ if (!orphaned)
+ remmina_plugin_service->protocol_plugin_signal_connection_closed(gp);
+
+ return G_SOURCE_REMOVE;
+}
+
+static gpointer remmina_rdp_main_thread(gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp;
+ rfContext *rfi;
+
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+ CANCEL_ASYNC
+
+ gp = (RemminaProtocolWidget *)data;
+
+ rfi = GET_PLUGIN_DATA(gp);
+
+ rfi->attempt_interactive_authentication = FALSE;
+ do
+ remmina_rdp_main(gp);
+ while (!remmina_plugin_service->protocol_plugin_has_error(gp) && rfi->attempt_interactive_authentication == TRUE && !rfi->user_cancelled);
+
+ rfi->remmina_plugin_thread = 0;
+
+ /* cleanup */
+ g_idle_add(complete_cleanup_on_main_thread, (gpointer)rfi);
+
+
+ return NULL;
+}
+
+static void remmina_rdp_init(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ freerdp *instance;
+ rfContext *rfi;
+
+ instance = freerdp_new();
+ instance->PreConnect = remmina_rdp_pre_connect;
+ instance->PostConnect = remmina_rdp_post_connect;
+ instance->PostDisconnect = remmina_rdp_post_disconnect;
+ //instance->VerifyCertificate = remmina_rdp_verify_certificate;
+ instance->VerifyCertificateEx = remmina_rdp_verify_certificate_ex;
+ //instance->VerifyChangedCertificate = remmina_rdp_verify_changed_certificate;
+ instance->VerifyChangedCertificateEx = remmina_rdp_verify_changed_certificate_ex;
+#if FREERDP_VERSION_MAJOR >= 3
+ instance->AuthenticateEx = remmina_rdp_authenticate_ex;
+ instance->ChooseSmartcard = remmina_rdp_choose_smartcard;
+ instance->GetAccessToken = remmina_rdp_get_access_token;
+ instance->LoadChannels = freerdp_client_load_channels;
+ instance->PresentGatewayMessage = remmina_rdp_present_gateway_message;
+ instance->LogonErrorInfo = remmina_rdp_logon_error_info;
+ instance->RetryDialog = remmina_rdp_retry_dialog;
+ instance->PostFinalDisconnect = remmina_rdp_post_final_disconnect;
+#else
+ instance->Authenticate = remmina_rdp_authenticate;
+ instance->GatewayAuthenticate = remmina_rdp_gw_authenticate;
+#endif
+
+ instance->ContextSize = sizeof(rfContext);
+ freerdp_context_new(instance);
+ rfi = (rfContext *)instance->context;
+
+ g_object_set_data_full(G_OBJECT(gp), "plugin-data", rfi, free);
+
+ rfi->protocol_widget = gp;
+ rfi->clientContext.context.settings = instance->context->settings;
+ rfi->connected = false;
+ rfi->is_reconnecting = false;
+ rfi->stop_reconnecting_requested = false;
+ rfi->user_cancelled = FALSE;
+ rfi->last_x = 0;
+ rfi->last_y = 0;
+
+ freerdp_register_addin_provider(freerdp_channels_load_static_addin_entry, 0);
+
+ remmina_rdp_event_init(gp);
+}
+
+static gboolean remmina_rdp_open_connection(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile;
+ const gchar *profile_name, *p;
+ gchar thname[16], c;
+ gint nthname = 0;
+
+ rfi->scale = remmina_plugin_service->remmina_protocol_widget_get_current_scale_mode(gp);
+
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+
+ if (pthread_create(&rfi->remmina_plugin_thread, NULL, remmina_rdp_main_thread, gp)) {
+ remmina_plugin_service->protocol_plugin_set_error(gp, "%s",
+ "Could not start pthread.");
+
+ rfi->remmina_plugin_thread = 0;
+
+ return FALSE;
+ }
+
+ /* Generate a thread name to be used with pthread_setname_np() for debugging */
+ profile_name = remmina_plugin_service->file_get_string(remminafile, "name");
+ p = profile_name;
+ strcpy(thname, "RemmRDP:");
+ if (p) {
+ nthname = strlen(thname);
+ while ((c = *p) != 0 && nthname < sizeof(thname) - 1) {
+ if (isalnum(c))
+ thname[nthname++] = c;
+ p++;
+ }
+ } else {
+ strcat(thname, "<NONAM>");
+ nthname = strlen(thname);
+ }
+ thname[nthname] = 0;
+#if defined(__linux__)
+ pthread_setname_np(rfi->remmina_plugin_thread, thname);
+#elif defined(__FreeBSD__)
+ pthread_set_name_np(rfi->remmina_plugin_thread, thname);
+#endif
+
+ return TRUE;
+}
+
+static gboolean remmina_rdp_close_connection(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+
+ REMMINA_PLUGIN_DEBUG("Requesting to close the connection");
+ RemminaPluginRdpEvent rdp_event = { 0 };
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+
+ if (!remmina_plugin_service->is_main_thread())
+ g_warning("WARNING: %s called on a subthread, which may not work or crash Remmina.", __func__);
+
+ if (rfi && !rfi->connected) {
+ /* libfreerdp is attempting to connect, we cannot interrupt our main thread
+ * in the connect phase.
+ * So we remove "plugin-data" from gp, so our rfi remains "orphan"
+ */
+ remmina_rdp_event_uninit(gp);
+ g_object_steal_data(G_OBJECT(gp), "plugin-data");
+ remmina_plugin_service->protocol_plugin_signal_connection_closed(gp);
+ return FALSE;
+ }
+
+
+ if (rfi && rfi->clipboard.srv_clip_data_wait == SCDW_BUSY_WAIT) {
+ REMMINA_PLUGIN_DEBUG("[RDP] requesting clipboard transfer to abort");
+ /* Allow clipboard transfer from server to terminate */
+ rfi->clipboard.srv_clip_data_wait = SCDW_ABORTING;
+ usleep(100000);
+ }
+
+ if (rfi->is_reconnecting) {
+ /* Special case: window closed when attempting to reconnect */
+ rfi->stop_reconnecting_requested = TRUE;
+ return FALSE;
+ }
+
+ rdp_event.type = REMMINA_RDP_EVENT_DISCONNECT;
+ remmina_rdp_event_event_push(gp, &rdp_event);
+
+ return FALSE;
+}
+
+static gboolean remmina_rdp_query_feature(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
+{
+ TRACE_CALL(__func__);
+ return TRUE;
+}
+
+static void remmina_rdp_call_feature(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
+{
+ TRACE_CALL(__func__);
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+
+ switch (feature->id) {
+ case REMMINA_RDP_FEATURE_UNFOCUS:
+ remmina_rdp_event_unfocus(gp);
+ break;
+
+ case REMMINA_RDP_FEATURE_SCALE:
+ if (rfi) {
+ rfi->scale = remmina_plugin_service->remmina_protocol_widget_get_current_scale_mode(gp);
+ remmina_rdp_event_update_scale(gp);
+ } else {
+ REMMINA_PLUGIN_DEBUG("Remmina RDP plugin warning: Null value for rfi by REMMINA_RDP_FEATURE_SCALE");
+ }
+ break;
+
+ case REMMINA_RDP_FEATURE_MULTIMON:
+ if (rfi) {
+ RemminaFile *remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ if (remmina_plugin_service->file_get_int(remminafile, "multimon", FALSE)) {
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_UseMultimon, TRUE);
+ /* TODO Add an option for this */
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_ForceMultimon, TRUE);
+ freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_Fullscreen, TRUE);
+ remmina_rdp_event_send_delayed_monitor_layout(gp);
+ }
+ } else {
+ REMMINA_PLUGIN_DEBUG("Remmina RDP plugin warning: Null value for rfi by REMMINA_RDP_FEATURE_MULTIMON");
+ }
+ break;
+
+ case REMMINA_RDP_FEATURE_DYNRESUPDATE:
+ break;
+
+ case REMMINA_RDP_FEATURE_TOOL_REFRESH:
+ if (rfi)
+ gtk_widget_queue_draw_area(rfi->drawing_area, 0, 0,
+ remmina_plugin_service->protocol_plugin_get_width(gp),
+ remmina_plugin_service->protocol_plugin_get_height(gp));
+ else
+ REMMINA_PLUGIN_DEBUG("Remmina RDP plugin warning: Null value for rfi by REMMINA_RDP_FEATURE_TOOL_REFRESH");
+ break;
+
+ case REMMINA_RDP_FEATURE_TOOL_SENDCTRLALTDEL:
+ remmina_rdp_send_ctrlaltdel(gp);
+ break;
+
+ default:
+ break;
+ }
+}
+
+/* Send a keystroke to the plugin window */
+static void remmina_rdp_keystroke(RemminaProtocolWidget *gp, const guint keystrokes[], const gint keylen)
+{
+ TRACE_CALL(__func__);
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+
+ remmina_plugin_service->protocol_plugin_send_keys_signals(rfi->drawing_area,
+ keystrokes, keylen, GDK_KEY_PRESS | GDK_KEY_RELEASE);
+ return;
+}
+
+static gboolean remmina_rdp_get_screenshot(RemminaProtocolWidget *gp, RemminaPluginScreenshotData *rpsd)
+{
+ rfContext *rfi = GET_PLUGIN_DATA(gp);
+ rdpGdi *gdi;
+ size_t szmem;
+
+ UINT32 bytesPerPixel;
+ UINT32 bitsPerPixel;
+
+ if (!rfi)
+ return FALSE;
+
+ gdi = ((rdpContext *)rfi)->gdi;
+
+#if FREERDP_VERSION_MAJOR >= 3
+ bytesPerPixel = FreeRDPGetBytesPerPixel(gdi->hdc->format);
+ bitsPerPixel = FreeRDPGetBitsPerPixel(gdi->hdc->format);
+#else
+ bytesPerPixel = GetBytesPerPixel(gdi->hdc->format);
+ bitsPerPixel = GetBitsPerPixel(gdi->hdc->format);
+#endif
+
+ /** @todo we should lock FreeRDP subthread to update rfi->primary_buffer, rfi->gdi and w/h,
+ * from here to memcpy, but… how ? */
+
+ szmem = gdi->width * gdi->height * bytesPerPixel;
+
+ REMMINA_PLUGIN_DEBUG("allocating %zu bytes for a full screenshot", szmem);
+ rpsd->buffer = malloc(szmem);
+ if (!rpsd->buffer) {
+ REMMINA_PLUGIN_DEBUG("could not set aside %zu bytes for a full screenshot", szmem);
+ return FALSE;
+ }
+ rpsd->width = gdi->width;
+ rpsd->height = gdi->height;
+ rpsd->bitsPerPixel = bitsPerPixel;
+ rpsd->bytesPerPixel = bytesPerPixel;
+
+ memcpy(rpsd->buffer, gdi->primary_buffer, szmem);
+
+ /* Returning TRUE instruct also the caller to deallocate rpsd->buffer */
+ return TRUE;
+}
+
+/* Array of key/value pairs for colour depths */
+static gpointer colordepth_list[] =
+{
+ /* 1st one is the default in a new install */
+ "99", N_("Automatic (32 bpp) (Server chooses its best format)"),
+ "66", N_("GFX AVC444 (32 bpp)"),
+ "65", N_("GFX AVC420 (32 bpp)"),
+ "64", N_("GFX RFX (32 bpp)"),
+ "63", N_("GFX RFX Progressive (32 bpp)"),
+ "0", N_("RemoteFX (32 bpp)"),
+ "32", N_("True colour (32 bpp)"),
+ "24", N_("True colour (24 bpp)"),
+ "16", N_("High colour (16 bpp)"),
+ "15", N_("High colour (15 bpp)"),
+ "8", N_("256 colours (8 bpp)"),
+ NULL
+};
+
+/* Array of key/value pairs for the FreeRDP logging level */
+static gpointer log_level[] =
+{
+ "INFO", "INFO",
+ "FATAL", "FATAL",
+ "ERROR", "ERROR",
+ "WARN", "WARN",
+ "DEBUG", "DEBUG",
+ "TRACE", "TRACE",
+ "OFF", "OFF",
+ NULL
+};
+
+
+/* Array of key/value pairs for quality selection */
+static gpointer quality_list[] =
+{
+ "0", N_("Poor (fastest)"),
+ "1", N_("Medium"),
+ "2", N_("Good"),
+ "9", N_("Best (slowest)"),
+ NULL
+};
+
+/* Array of key/value pairs for quality selection */
+static gpointer network_list[] =
+{
+ "none", N_("None"),
+ "autodetect", N_("Auto-detect"),
+ "modem", N_("Modem"),
+ "broadband-low", N_("Low performance broadband"),
+ "satellite", N_("Satellite"),
+ "broadband-high", N_("High performance broadband"),
+ "wan", N_("WAN"),
+ "lan", N_("LAN"),
+ NULL
+};
+
+/* Array of key/value pairs for sound options */
+static gpointer sound_list[] =
+{
+ "off", N_("Off"),
+ "local", N_("Local"),
+ "remote", N_("Remote"),
+ NULL
+};
+
+/* Array of key/value pairs for security */
+static gpointer security_list[] =
+{
+ "", N_("Automatic negotiation"),
+ "nla", N_("NLA protocol security"),
+ "tls", N_("TLS protocol security"),
+ "rdp", N_("RDP protocol security"),
+ "ext", N_("NLA extended protocol security"),
+ NULL
+};
+
+/* Array of key/value pairs for mouse movement */
+static gpointer mouse_jitter_list[] =
+{
+ "No", N_("No"),
+ "60", N_("Every 1 min"),
+ "180", N_("Every 3 min"),
+ "300", N_("Every 5 min"),
+ "600", N_("Every 10 min"),
+ NULL
+};
+
+static gpointer gwtransp_list[] =
+{
+ "http", "HTTP",
+ "rpc", "RPC",
+ "auto", "Auto",
+ NULL
+};
+
+static gpointer tls_seclevel[] =
+{
+ "", N_("Default"),
+ "0", N_("0 — Windows 7 compatible"),
+ "1", N_("1"),
+ "2", N_("2"),
+ "3", N_("3"),
+ "4", N_("4"),
+ "5", N_("5"),
+ NULL
+};
+
+static gchar clientbuild_list[] =
+ N_("2600 (Windows XP), 7601 (Windows Vista/7), 9600 (Windows 8 and newer)");
+
+static gchar clientbuild_tooltip[] =
+ N_("Used i.a. by terminal services in a smart card channel to distinguish client capabilities:\n"
+ " • < 4034: Windows XP base smart card functions\n"
+ " • 4034-7064: Windows Vista/7: SCardReadCache(),\n"
+ " SCardWriteCache(), SCardGetTransmitCount()\n"
+ " • >= 7065: Windows 8 and newer: SCardGetReaderIcon(),\n"
+ " SCardGetDeviceTypeId()");
+
+static gchar microphone_tooltip[] =
+ N_("Options for redirection of audio input:\n"
+ " • [sys:<sys>,][dev:<dev>,][format:<format>,][rate:<rate>,]\n"
+ " [channel:<channel>] Audio input (microphone)\n"
+ " • sys:pulse\n"
+ " • format:1\n"
+ " • sys:oss,dev:1,format:1\n"
+ " • sys:alsa");
+
+static gchar audio_tooltip[] =
+ N_("Options for redirection of audio output:\n"
+ " • [sys:<sys>,][dev:<dev>,][format:<format>,][rate:<rate>,]\n"
+ " [channel:<channel>] Audio output\n"
+ " • sys:pulse\n"
+ " • format:1\n"
+ " • sys:oss,dev:1,format:1\n"
+ " • sys:alsa");
+
+
+static gchar usb_tooltip[] =
+ N_("Options for redirection of USB device:\n"
+ " • [dbg,][id:<vid>:<pid>#…,][addr:<bus>:<addr>#…,][auto]\n"
+ " • auto\n"
+ " • id:054c:0268#4669:6e6b,addr:04:0c");
+
+static gchar timeout_tooltip[] =
+ N_("Advanced setting for high latency links:\n"
+ "Adjusts the connection timeout. Use if your connection times out.\n"
+ "The highest possible value is 600000 ms (10 minutes).\n");
+
+static gchar network_tooltip[] =
+ N_("Performance optimisations based on the network connection type:\n"
+ "Using auto-detection is advised.\n"
+ "If “Auto-detect” fails, choose the most appropriate option in the list.\n");
+
+static gchar monitorids_tooltip[] =
+ N_("Comma-separated list of monitor IDs and desktop orientations:\n"
+ " • [<id>:<orientation-in-degrees>,]\n"
+ " • 0,1,2,3\n"
+ " • 0:270,1:90\n"
+ "Orientations are specified in degrees, valid values are:\n"
+ " • 0 (landscape)\n"
+ " • 90 (portrait)\n"
+ " • 180 (landscape flipped)\n"
+ " • 270 (portrait flipped)\n"
+ "\n");
+
+static gchar drive_tooltip[] =
+ N_("Redirect directory <path> as named share <name>.\n"
+ " • <name>,<fullpath>[;<name>,<fullpath>[;…]]\n"
+ " • MyHome,/home/remminer\n"
+ " • /home/remminer\n"
+ " • MyHome,/home/remminer;SomePath,/path/to/somepath\n"
+ "Hotplug support is enabled with:\n"
+ " • hotplug,*\n"
+ "\n");
+
+/* Array of RemminaProtocolSetting for basic settings.
+ * Each item is composed by:
+ * a) RemminaProtocolSettingType for setting type
+ * b) Setting name
+ * c) Setting description
+ * d) Compact disposition
+ * e) Values for REMMINA_PROTOCOL_SETTING_TYPE_SELECT or REMMINA_PROTOCOL_SETTING_TYPE_COMBO
+ * f) Setting tooltip
+ * g) Validation data pointer, will be passed to the validation callback method.
+ * h) Validation callback method (Can be NULL. Every entry will be valid then.)
+ * use following prototype:
+ * gboolean mysetting_validator_method(gpointer key, gpointer value,
+ * gpointer validator_data);
+ * gpointer key is a gchar* containing the setting's name,
+ * gpointer value contains the value which should be validated,
+ * gpointer validator_data contains your passed data.
+ */
+static const RemminaProtocolSetting remmina_rdp_basic_settings[] =
+{
+ { REMMINA_PROTOCOL_SETTING_TYPE_SERVER, "server", NULL, FALSE, NULL, NULL, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "username", N_("Username"), FALSE, NULL, NULL, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_PASSWORD, "password", N_("Password"), FALSE, NULL, NULL, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "domain", N_("Domain"), FALSE, NULL, NULL, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "drive", N_("Share folder"), FALSE, NULL, drive_tooltip, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "restricted-admin", N_("Restricted admin mode"), FALSE, NULL, NULL, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "pth", N_("Password hash"), FALSE, NULL, N_("Restricted admin mode password hash"), NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "left-handed", N_("Left-handed mouse support"), TRUE, NULL, N_("Swap left and right mouse buttons for left-handed mouse support"), NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disable-smooth-scrolling", N_("Disable smooth scrolling"), TRUE, NULL, NULL, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "multimon", N_("Enable multi monitor"), TRUE, NULL, NULL, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "span", N_("Span screen over multiple monitors"), TRUE, NULL, NULL, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "monitorids", N_("List monitor IDs"), FALSE, NULL, monitorids_tooltip, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_RESOLUTION, "resolution", NULL, FALSE, NULL, NULL, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "colordepth", N_("Colour depth"), FALSE, colordepth_list, NULL, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "network", N_("Network connection type"), FALSE, network_list, network_tooltip, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_KEYMAP, "keymap", NULL, FALSE, NULL, NULL, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_END, NULL, NULL, FALSE, NULL, NULL, NULL, NULL }
+};
+
+/* Array of RemminaProtocolSetting for advanced settings.
+ * Each item is composed by:
+ * a) RemminaProtocolSettingType for setting type
+ * b) Setting name
+ * c) Setting description
+ * d) Compact disposition
+ * e) Values for REMMINA_PROTOCOL_SETTING_TYPE_SELECT or REMMINA_PROTOCOL_SETTING_TYPE_COMBO
+ * f) Setting Tooltip
+ */
+static const RemminaProtocolSetting remmina_rdp_advanced_settings[] =
+{
+ { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "quality", N_("Quality"), FALSE, quality_list, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "security", N_("Security protocol negotiation"), FALSE, security_list, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "gwtransp", N_("Gateway transport type"), FALSE, gwtransp_list, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "tls-seclevel", N_("TLS Security Level"), FALSE, tls_seclevel, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "freerdp_log_level", N_("FreeRDP log level"), FALSE, log_level, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "freerdp_log_filters", N_("FreeRDP log filters"), FALSE, NULL, N_("tag:level[,tag:level[,…]]") },
+ { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "sound", N_("Audio output mode"), FALSE, sound_list, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "audio-output", N_("Redirect local audio output"), TRUE, NULL, audio_tooltip },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "microphone", N_("Redirect local microphone"), TRUE, NULL, microphone_tooltip },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "timeout", N_("Connection timeout in ms"), TRUE, NULL, timeout_tooltip },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "gateway_server", N_("Remote Desktop Gateway server"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "gateway_username", N_("Remote Desktop Gateway username"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_PASSWORD, "gateway_password", N_("Remote Desktop Gateway password"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "gateway_domain", N_("Remote Desktop Gateway domain"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "clientname", N_("Client name"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_COMBO, "clientbuild", N_("Client build"), FALSE, clientbuild_list, clientbuild_tooltip },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "exec", N_("Start-up program"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "execpath", N_("Start-up path"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "loadbalanceinfo", N_("Load balance info"), FALSE, NULL, NULL },
+ // TRANSLATORS: Do not use typographic quotation marks, these must stay as "double quote", also know as “Typewriter ("programmer's") quote, ambidextrous.”
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "printer_overrides", N_("Override printer drivers"), FALSE, NULL, N_("\"Samsung_CLX-3300_Series\":\"Samsung CLX-3300 Series PS\";\"Canon MF410\":\"Canon MF410 Series UFR II\"") },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "usb", N_("USB device redirection"), TRUE, NULL, usb_tooltip },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "serialname", N_("Local serial name"), FALSE, NULL, N_("COM1, COM2, etc.") },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "serialdriver", N_("Local serial driver"), FALSE, NULL, N_("Serial") },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "serialpath", N_("Local serial path"), FALSE, NULL, N_("/dev/ttyS0, /dev/ttyS1, etc.") },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "parallelname", N_("Local parallel name"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "parallelpath", N_("Local parallel device"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "smartcardname", N_("Name of smart card"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "dvc", N_("Dynamic virtual channel"), FALSE, NULL, N_("<channel>[,<options>]") },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "vc", N_("Static virtual channel"), FALSE, NULL, N_("<channel>[,<options>]") },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "rdp2tcp", N_("TCP redirection"), FALSE, NULL, N_("/PATH/TO/rdp2tcp") },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "rdp_reconnect_attempts", N_("Reconnect attempts number"), FALSE, NULL, N_("The maximum number of reconnect attempts upon an RDP disconnect (default: 20)") },
+ { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "rdp_mouse_jitter", N_("Move mouse when connection is idle"), FALSE, mouse_jitter_list, NULL },
+
+ { REMMINA_PROTOCOL_SETTING_TYPE_ASSISTANCE, "assistance_mode", N_("Attempt to connect in assistance mode"), TRUE, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "preferipv6", N_("Prefer IPv6 AAAA record over IPv4 A record"), TRUE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "shareprinter", N_("Share printers"), TRUE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "shareserial", N_("Share serial ports"), TRUE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "serialpermissive", N_("(SELinux) permissive mode for serial ports"), TRUE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "shareparallel", N_("Share parallel ports"), TRUE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "sharesmartcard", N_("Share a smart card"), TRUE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableclipboard", N_("Turn off clipboard sync"), TRUE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "cert_ignore", N_("Ignore certificate"), TRUE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "old-license", N_("Use the old license workflow"), TRUE, NULL, N_("It disables CAL and hwId is set to 0") },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disablepasswordstoring", N_("Forget passwords after use"), TRUE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "console", N_("Attach to console (2003/2003 R2)"), TRUE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disable_fastpath", N_("Turn off fast-path"), TRUE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "gateway_usage", N_("Server detection using Remote Desktop Gateway"), TRUE, NULL, NULL },
+#if defined(PROXY_TYPE_IGNORE)
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "useproxyenv", N_("Use system proxy settings"), TRUE, NULL, NULL },
+#endif
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableautoreconnect", N_("Turn off automatic reconnection"), TRUE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "relax-order-checks", N_("Relax order checks"), TRUE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "glyph-cache", N_("Glyph cache"), TRUE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "multitransport", N_("Enable multitransport protocol (UDP)"), TRUE, NULL, N_("Using the UDP protocol may improve performance") },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "base-cred-for-gw", N_("Use base credentials for gateway too"), TRUE, NULL, NULL },
+#if FREERDP_CHECK_VERSION(2, 3, 1)
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "websockets", N_("Enable Gateway websockets support"), TRUE, NULL, NULL },
+#endif
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "no-suppress", N_("Update framebuffer even when not visible"), TRUE, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_END, NULL, NULL, FALSE, NULL, NULL }
+};
+
+/* Array for available features.
+ * The last element of the array must be REMMINA_PROTOCOL_FEATURE_TYPE_END. */
+static const RemminaProtocolFeature remmina_rdp_features[] =
+{
+ { REMMINA_PROTOCOL_FEATURE_TYPE_PREF, REMMINA_RDP_FEATURE_VIEWONLY, GINT_TO_POINTER(REMMINA_PROTOCOL_FEATURE_PREF_CHECK), "viewonly",
+ N_("View only") },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_RDP_FEATURE_TOOL_REFRESH, N_("Refresh"), NULL, NULL },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_SCALE, REMMINA_RDP_FEATURE_SCALE, NULL, NULL, NULL },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_DYNRESUPDATE, REMMINA_RDP_FEATURE_DYNRESUPDATE, NULL, NULL, NULL },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_MULTIMON, REMMINA_RDP_FEATURE_MULTIMON, NULL, NULL, NULL },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_RDP_FEATURE_TOOL_SENDCTRLALTDEL, N_("Send Ctrl+Alt+Delete"), NULL, NULL },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_UNFOCUS, REMMINA_RDP_FEATURE_UNFOCUS, NULL, NULL, NULL },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_END, 0, NULL, NULL, NULL }
+};
+
+/* This will be filled with version info string */
+static char remmina_plugin_rdp_version[256];
+
+/* Protocol plugin definition and features */
+static RemminaProtocolPlugin remmina_rdp =
+{
+ REMMINA_PLUGIN_TYPE_PROTOCOL, // Type
+ "RDP", // Name
+ N_("RDP - Remote Desktop Protocol"), // Description
+ GETTEXT_PACKAGE, // Translation domain
+ remmina_plugin_rdp_version, // Version number
+ "org.remmina.Remmina-rdp-symbolic", // Icon for normal connection
+ "org.remmina.Remmina-rdp-ssh-symbolic", // Icon for SSH connection
+ remmina_rdp_basic_settings, // Array for basic settings
+ remmina_rdp_advanced_settings, // Array for advanced settings
+ REMMINA_PROTOCOL_SSH_SETTING_TUNNEL, // SSH settings type
+ remmina_rdp_features, // Array for available features
+ remmina_rdp_init, // Plugin initialization
+ remmina_rdp_open_connection, // Plugin open connection
+ remmina_rdp_close_connection, // Plugin close connection
+ remmina_rdp_query_feature, // Query for available features
+ remmina_rdp_call_feature, // Call a feature
+ remmina_rdp_keystroke, // Send a keystroke
+ remmina_rdp_get_screenshot, // Screenshot
+ remmina_rdp_event_on_map, // RCW map event
+ remmina_rdp_event_on_unmap // RCW unmap event
+};
+
+/* File plugin definition and features */
+static RemminaFilePlugin remmina_rdpf =
+{
+ REMMINA_PLUGIN_TYPE_FILE, // Type
+ "RDPF", // Name
+ N_("RDP - RDP File Handler"), // Description
+ GETTEXT_PACKAGE, // Translation domain
+ remmina_plugin_rdp_version, // Version number
+ remmina_rdp_file_import_test, // Test import function
+ remmina_rdp_file_import, // Import function
+ remmina_rdp_file_export_test, // Test export function
+ remmina_rdp_file_export, // Export function
+ NULL
+};
+
+/* Preferences plugin definition and features */
+static RemminaPrefPlugin remmina_rdps =
+{
+ REMMINA_PLUGIN_TYPE_PREF, // Type
+ "RDPS", // Name
+ N_("RDP - Preferences"), // Description
+ GETTEXT_PACKAGE, // Translation domain
+ remmina_plugin_rdp_version, // Version number
+ "RDP", // Label
+ remmina_rdp_settings_new // Preferences body function
+};
+
+static char *buildconfig_strstr(const char *bc, const char *option)
+{
+ TRACE_CALL(__func__);
+
+ char *p, *n;
+
+ p = strcasestr(bc, option);
+ if (p == NULL)
+ return NULL;
+
+ if (p > bc && *(p - 1) > ' ')
+ return NULL;
+
+ n = p + strlen(option);
+ if (*n > ' ')
+ return NULL;
+
+ return p;
+}
+
+G_MODULE_EXPORT gboolean remmina_plugin_entry(RemminaPluginService *service)
+{
+ int vermaj, vermin, verrev;
+
+ TRACE_CALL(__func__);
+ remmina_plugin_service = service;
+
+ /* Check that we are linked to the correct version of libfreerdp */
+
+ freerdp_get_version(&vermaj, &vermin, &verrev);
+ if ((vermaj < FREERDP_REQUIRED_MAJOR) ||
+ ((vermaj == FREERDP_REQUIRED_MAJOR) && ((vermin < FREERDP_REQUIRED_MINOR) ||
+ ((vermin == FREERDP_REQUIRED_MINOR) && (verrev < FREERDP_REQUIRED_REVISION))))) {
+ g_printf("Upgrade your FreeRDP library version from %d.%d.%d to at least libfreerdp %d.%d.%d "
+ "to run the RDP plugin.\n",
+ vermaj, vermin, verrev,
+ FREERDP_REQUIRED_MAJOR, FREERDP_REQUIRED_MINOR, FREERDP_REQUIRED_REVISION);
+ return FALSE;
+ }
+
+ bindtextdomain(GETTEXT_PACKAGE, REMMINA_RUNTIME_LOCALEDIR);
+ bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
+
+ if (!service->register_plugin((RemminaPlugin *)&remmina_rdp))
+ return FALSE;
+
+ remmina_rdpf.export_hints = _("Export connection in Windows .rdp file format");
+
+ if (!service->register_plugin((RemminaPlugin *)&remmina_rdpf))
+ return FALSE;
+
+ if (!service->register_plugin((RemminaPlugin *)&remmina_rdps))
+ return FALSE;
+
+ if (buildconfig_strstr(freerdp_get_build_config(), "WITH_GFX_H264=ON")) {
+ gfx_h264_available = TRUE;
+ REMMINA_PLUGIN_DEBUG("gfx_h264_available: %d", gfx_h264_available);
+ } else {
+ gfx_h264_available = FALSE;
+ REMMINA_PLUGIN_DEBUG("gfx_h264_available: %d", gfx_h264_available);
+ /* Remove values 65 and 66 from colordepth_list array by shifting it */
+ gpointer *src, *dst;
+ dst = src = colordepth_list;
+ while (*src) {
+ if (strcmp(*src, "66") != 0 && strcmp(*src, "65") != 0) {
+ if (dst != src) {
+ *dst = *src;
+ *(dst + 1) = *(src + 1);
+ }
+ dst += 2;
+ }
+ src += 2;
+ }
+ *dst = NULL;
+ }
+
+ snprintf(remmina_plugin_rdp_version, sizeof(remmina_plugin_rdp_version),
+ "RDP plugin: %s (Git %s), Compiled with libfreerdp %s (%s), Running with libfreerdp %s (rev %s), H.264 %s",
+ VERSION, REMMINA_GIT_REVISION,
+#if FREERDP_VERSION_MAJOR >= 3
+ FREERDP_VERSION_FULL, FREERDP_GIT_REVISION,
+#else
+ FREERDP_VERSION_FULL, GIT_REVISION,
+#endif
+ freerdp_get_version_string(),
+ freerdp_get_build_revision(),
+ gfx_h264_available ? "Yes" : "No"
+ );
+
+ remmina_rdp_settings_init();
+
+ return TRUE;
+}
diff --git a/plugins/rdp/rdp_plugin.h b/plugins/rdp/rdp_plugin.h
new file mode 100644
index 0000000..a0f0cc7
--- /dev/null
+++ b/plugins/rdp/rdp_plugin.h
@@ -0,0 +1,400 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010-2011 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL. * If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. * If you
+ * do not wish to do so, delete this exception statement from your
+ * version. * If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ *
+ */
+
+#pragma once
+
+#include "common/remmina_plugin.h"
+#include <freerdp/freerdp.h>
+#include <freerdp/version.h>
+#include <freerdp/channels/channels.h>
+#include <freerdp/codec/color.h>
+#include <freerdp/codec/rfx.h>
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/gdi/dc.h>
+#include <freerdp/gdi/region.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/client/disp.h>
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#elif defined(GDK_WINDOWING_WAYLAND)
+#include <gdk/gdkwayland.h>
+#endif
+
+#include <winpr/clipboard.h>
+
+/**
+ * FREERDP_CHECK_VERSION:
+ * @major: major version (e.g. 2 for version 2.1.0)
+ * @minor: minor version (e.g. 0 for version 2.1.0)
+ * @release: release version (e.g. 0 for version 2.1.0)
+ *
+ * Returns %TRUE if the version of the FRERDDP header files
+ * is the same as or newer than the passed-in version.
+ *
+ * Returns: %TRUE if FREERDP headers are new enough
+ */
+#define FREERDP_CHECK_VERSION(major,minor,revision) \
+ (FREERDP_VERSION_MAJOR > (major) || \
+ (FREERDP_VERSION_MAJOR == (major) && FREERDP_VERSION_MINOR > (minor)) || \
+ (FREERDP_VERSION_MAJOR == (major) && FREERDP_VERSION_MINOR == (minor) && \
+ FREERDP_VERSION_REVISION >= (revision)))
+
+/**
+ * Constants to workaround FreeRDP issue #5417 (min resolution in AVC mode)
+ * Must be 4 aligned (multiple of 4).
+ * We should instead check destRect and be sure the condition is met
+ * See https://gitlab.com/Remmina/Remmina/-/issues/2507 for a deep discussion
+ */
+#define AVC_MIN_DESKTOP_WIDTH 644
+#define AVC_MIN_DESKTOP_HEIGHT 480
+
+typedef struct rf_context rfContext;
+
+#define GET_PLUGIN_DATA(gp) (rfContext *)g_object_get_data(G_OBJECT(gp), "plugin-data")
+
+/**
+ * Performance Flags, from FreeRDP source
+ *
+ * PERF_FLAG_NONE 0x00000000
+ * PERF_DISABLE_WALLPAPER 0x00000001
+ * PERF_DISABLE_FULLWINDOWDRAG 0x00000002
+ * PERF_DISABLE_MENUANIMATIONS 0x00000004
+ * PERF_DISABLE_THEMING 0x00000008
+ * PERF_DISABLE_CURSOR_SHADOW 0x00000020
+ * PERF_DISABLE_CURSORSETTINGS 0x00000040
+ * PERF_ENABLE_FONT_SMOOTHING 0x00000080
+ * PERF_ENABLE_DESKTOP_COMPOSITION 0x00000100
+ */
+/* Poor (default) - all disabled */
+#define DEFAULT_QUALITY_0 0x6f
+/* Medium - only THEMING, CURSOR_SHADOW and CURSORSETTINGS enabled, all other disabled */
+#define DEFAULT_QUALITY_1 0x07
+/* Good - WALLPAPER, FONT_SMOOTHING, DESKTOP_COMPOSITION disabled, all other enabled */
+#define DEFAULT_QUALITY_2 0x01
+/* Best - DESKTOP_COMPOSITION disabled, all other enabled */
+#define DEFAULT_QUALITY_9 0x80
+
+extern RemminaPluginService *remmina_plugin_service;
+
+#define REMMINA_PLUGIN_INFO(fmt, ...) \
+ remmina_plugin_service->_remmina_info(__func__, fmt, ##__VA_ARGS__)
+
+#define REMMINA_PLUGIN_MESSAGE(fmt, ...) \
+ remmina_plugin_service->_remmina_message(__func, fmt, ##__VA_ARGS__)
+
+#define REMMINA_PLUGIN_DEBUG(fmt, ...) \
+ remmina_plugin_service->_remmina_debug(__func__, fmt, ##__VA_ARGS__)
+
+#define REMMINA_PLUGIN_WARNING(fmt, ...) \
+ remmina_plugin_service->_remmina_warning(__func__, fmt, ##__VA_ARGS__)
+
+/* This will intentionally crash Remmina */
+#define REMMINA_PLUGIN_ERROR(fmt, ...) \
+ remmina_plugin_service->_remmina_error(__func__, fmt, ##__VA_ARGS__)
+
+#define REMMINA_PLUGIN_CRITICAL(fmt, ...) \
+ remmina_plugin_service->_remmina_critical(__func__, fmt, ##__VA_ARGS__)
+#define REMMINA_PLUGIN_AUDIT(fmt, ...) \
+ remmina_plugin_service->_remmina_audit(__func__, fmt, ##__VA_ARGS__)
+
+struct rf_clipboard {
+ rfContext * rfi;
+ CliprdrClientContext * context;
+ wClipboard * system;
+ int requestedFormatId;
+
+ UINT32 format;
+ gulong clipboard_handler;
+
+ pthread_mutex_t transfer_clip_mutex;
+ pthread_cond_t transfer_clip_cond;
+ enum { SCDW_NONE, SCDW_BUSY_WAIT, SCDW_ABORTING } srv_clip_data_wait;
+ gpointer srv_data;
+ pthread_mutex_t srv_data_mutex;
+
+ UINT32 server_html_format_id;
+
+ /* Stats for clipboard download */
+ struct timeval clientformatdatarequest_tv;
+};
+typedef struct rf_clipboard rfClipboard;
+
+
+struct rf_pointer {
+ rdpPointer pointer;
+ GdkCursor * cursor;
+};
+typedef struct rf_pointer rfPointer;
+
+#ifdef RF_BITMAP
+struct rf_bitmap {
+ rdpBitmap bitmap;
+ Pixmap pixmap;
+ cairo_surface_t * surface;
+};
+typedef struct rf_bitmap rfBitmap;
+#endif
+
+#ifdef RF_GLYPH
+struct rf_glyph {
+ rdpGlyph glyph;
+ Pixmap pixmap;
+};
+typedef struct rf_glyph rfGlyph;
+#endif
+
+typedef enum {
+ REMMINA_RDP_EVENT_TYPE_SCANCODE,
+ REMMINA_RDP_EVENT_TYPE_SCANCODE_UNICODE,
+ REMMINA_RDP_EVENT_TYPE_MOUSE,
+ REMMINA_RDP_EVENT_TYPE_CLIPBOARD_SEND_CLIENT_FORMAT_LIST,
+ REMMINA_RDP_EVENT_TYPE_CLIPBOARD_SEND_CLIENT_FORMAT_DATA_RESPONSE,
+ REMMINA_RDP_EVENT_TYPE_CLIPBOARD_SEND_CLIENT_FORMAT_DATA_REQUEST,
+ REMMINA_RDP_EVENT_TYPE_SEND_MONITOR_LAYOUT,
+ REMMINA_RDP_EVENT_DISCONNECT
+} RemminaPluginRdpEventType;
+
+struct remmina_plugin_rdp_event {
+ RemminaPluginRdpEventType type;
+ union {
+ struct {
+ BOOL up;
+ BOOL extended;
+ UINT8 key_code;
+ UINT32 unicode_code;
+ BOOL extended1;
+ } key_event;
+ struct {
+ UINT16 flags;
+ UINT16 x;
+ UINT16 y;
+ BOOL extended;
+ } mouse_event;
+ struct {
+ CLIPRDR_FORMAT_LIST *pFormatList;
+ } clipboard_formatlist;
+ struct {
+ BYTE * data;
+ int size;
+ } clipboard_formatdataresponse;
+ struct {
+ CLIPRDR_FORMAT_DATA_REQUEST *pFormatDataRequest;
+ } clipboard_formatdatarequest;
+ struct {
+ gint Flags;
+ gint Left;
+ gint Top;
+ gint width;
+ gint height;
+ gint desktopOrientation;
+ gint desktopScaleFactor;
+ gint deviceScaleFactor;
+ gint physicalWidth;
+ gint physicalHeight;
+ } monitor_layout;
+ };
+};
+typedef struct remmina_plugin_rdp_event RemminaPluginRdpEvent;
+
+typedef enum {
+ REMMINA_RDP_UI_UPDATE_REGIONS = 0,
+ REMMINA_RDP_UI_CONNECTED,
+ REMMINA_RDP_UI_RECONNECT_PROGRESS,
+ REMMINA_RDP_UI_CURSOR,
+ REMMINA_RDP_UI_NOCODEC,
+ REMMINA_RDP_UI_CLIPBOARD,
+ REMMINA_RDP_UI_EVENT
+} RemminaPluginRdpUiType;
+
+typedef enum {
+ REMMINA_RDP_UI_CLIPBOARD_FORMATLIST,
+ REMMINA_RDP_UI_CLIPBOARD_GET_DATA,
+ REMMINA_RDP_UI_CLIPBOARD_SET_DATA
+} RemminaPluginRdpUiClipboardType;
+
+typedef enum {
+ REMMINA_RDP_POINTER_NEW,
+ REMMINA_RDP_POINTER_FREE,
+ REMMINA_RDP_POINTER_SET,
+ REMMINA_RDP_POINTER_NULL,
+ REMMINA_RDP_POINTER_DEFAULT,
+ REMMINA_RDP_POINTER_SETPOS
+} RemminaPluginRdpUiPointerType;
+
+typedef enum {
+ REMMINA_RDP_UI_EVENT_UPDATE_SCALE,
+ REMMINA_RDP_UI_EVENT_DESTROY_CAIRO_SURFACE
+} RemminaPluginRdpUiEeventType;
+
+typedef struct {
+ gint x, y, w, h;
+} region;
+
+struct remmina_plugin_rdp_ui_object {
+ RemminaPluginRdpUiType type;
+ gboolean sync;
+ gboolean complete;
+ pthread_mutex_t sync_wait_mutex;
+ pthread_cond_t sync_wait_cond;
+ union {
+ struct {
+ region *ureg;
+ gint ninvalid;
+ } reg;
+ struct {
+ rdpContext * context;
+ rfPointer * pointer;
+ RemminaPluginRdpUiPointerType type;
+ } cursor;
+ struct {
+ gint left;
+ gint top;
+ RFX_MESSAGE * message;
+ } rfx;
+ struct {
+ gint left;
+ gint top;
+ gint width;
+ gint height;
+ UINT8 * bitmap;
+ } nocodec;
+ struct {
+ RemminaPluginRdpUiClipboardType type;
+ GtkTargetList * targetlist;
+ UINT32 format;
+ rfClipboard * clipboard;
+ gpointer data;
+ } clipboard;
+ struct {
+ RemminaPluginRdpUiEeventType type;
+ } event;
+ struct {
+ gint x;
+ gint y;
+ } pos;
+ };
+ /* We can also return values here, usually integers*/
+ int retval;
+ /* Some functions also may return a pointer. */
+ void * retptr;
+};
+
+typedef struct remmina_plugin_rdp_keymap_entry {
+ unsigned orig_keycode;
+ unsigned translated_keycode;
+} RemminaPluginRdpKeymapEntry;
+
+struct rf_context {
+ rdpClientContext clientContext;
+
+ RemminaProtocolWidget *protocol_widget;
+
+ /* main */
+ pthread_t remmina_plugin_thread;
+ RemminaScaleMode scale;
+ gboolean user_cancelled;
+ gboolean thread_cancelled;
+
+ CliprdrClientContext * cliprdr;
+ DispClientContext * dispcontext;
+
+ RDP_PLUGIN_DATA rdpdr_data[5];
+ RDP_PLUGIN_DATA drdynvc_data[5];
+ gchar rdpsnd_options[20];
+
+ gboolean rdpgfxchan;
+
+ gboolean connected;
+ gboolean is_reconnecting;
+ gboolean stop_reconnecting_requested;
+ /* orphaned: rf_context has still one or more libfreerdp thread active,
+ * but no longer maintained by an open RemminaProtocolWidget/tab.
+ * When the orphaned thread terminates, we must cleanup rf_context.
+ */
+ gboolean orphaned;
+ int reconnect_maxattempts;
+ int reconnect_nattempt;
+
+ gboolean sw_gdi;
+ GtkWidget * drawing_area;
+ gint scale_width;
+ gint scale_height;
+ gdouble scale_x;
+ gdouble scale_y;
+ guint delayed_monitor_layout_handler;
+ gboolean use_client_keymap;
+
+ gint srcBpp;
+ GdkDisplay * display;
+ GdkVisual * visual;
+ cairo_surface_t * surface;
+ cairo_format_t cairo_format;
+ gint bpp;
+ gint scanline_pad;
+ gint * colormap;
+
+ guint object_id_seq;
+ GHashTable * object_table;
+
+ GAsyncQueue * ui_queue;
+ pthread_mutex_t ui_queue_mutex;
+ guint ui_handler;
+
+ GArray * pressed_keys;
+ GAsyncQueue * event_queue;
+ gint event_pipe[2];
+ HANDLE event_handle;
+ UINT16 last_x;
+ UINT16 last_y;
+
+ rfClipboard clipboard;
+
+ GArray * keymap; /* Array of RemminaPluginRdpKeymapEntry */
+
+ gboolean attempt_interactive_authentication;
+
+ enum { REMMINA_POSTCONNECT_ERROR_OK = 0, REMMINA_POSTCONNECT_ERROR_GDI_INIT = 1, REMMINA_POSTCONNECT_ERROR_NO_H264 } postconnect_error;
+};
+
+typedef struct remmina_plugin_rdp_ui_object RemminaPluginRdpUiObject;
+
+void rf_init(RemminaProtocolWidget *gp);
+void rf_uninit(RemminaProtocolWidget *gp);
+void rf_get_fds(RemminaProtocolWidget *gp, void **rfds, int *rcount);
+BOOL rf_check_fds(RemminaProtocolWidget *gp);
+void rf_object_free(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *obj);
+
+void remmina_rdp_event_event_push(RemminaProtocolWidget *gp, const RemminaPluginRdpEvent *e);
diff --git a/plugins/rdp/rdp_settings.c b/plugins/rdp/rdp_settings.c
new file mode 100644
index 0000000..7a3d4aa
--- /dev/null
+++ b/plugins/rdp/rdp_settings.c
@@ -0,0 +1,759 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010-2011 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL. * If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. * If you
+ * do not wish to do so, delete this exception statement from your
+ * version. * If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ *
+ */
+
+#include "rdp_settings.h"
+#include <freerdp/locale/keyboard.h>
+
+static guint keyboard_layout = 0;
+static guint rdp_keyboard_layout = 0;
+static gchar *rdp_keyboard_remapping_list = NULL;
+
+static void remmina_rdp_settings_kbd_init(void)
+{
+ TRACE_CALL(__func__);
+#if FREERDP_CHECK_VERSION(2, 3, 0)
+ rdp_keyboard_remapping_list = g_strdup(
+ remmina_plugin_service->pref_get_value("rdp_kbd_remap"));
+ REMMINA_PLUGIN_DEBUG("rdp_keyboard_remapping_list: %s", rdp_keyboard_remapping_list);
+ keyboard_layout = freerdp_keyboard_init_ex(rdp_keyboard_layout, rdp_keyboard_remapping_list);
+#else
+ keyboard_layout = freerdp_keyboard_init(rdp_keyboard_layout);
+#endif
+}
+
+void remmina_rdp_settings_init(void)
+{
+ TRACE_CALL(__func__);
+ gchar *value;
+
+ value = remmina_plugin_service->pref_get_value("rdp_keyboard_layout");
+
+ if (value && value[0])
+ rdp_keyboard_layout = strtoul(value, NULL, 16);
+
+ g_free(value);
+
+ remmina_rdp_settings_kbd_init();
+}
+
+guint remmina_rdp_settings_get_keyboard_layout(void)
+{
+ TRACE_CALL(__func__);
+ return keyboard_layout;
+}
+
+#define REMMINA_TYPE_PLUGIN_RDPSET_GRID (remmina_rdp_settings_grid_get_type())
+#define REMMINA_RDPSET_GRID(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), REMMINA_TYPE_PLUGIN_RDPSET_GRID, RemminaPluginRdpsetGrid))
+#define REMMINA_RDPSET_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), REMMINA_TYPE_PLUGIN_RDPSET_GRID, RemminaPluginRdpsetGridClass))
+#define REMMINA_IS_PLUGIN_RDPSET_GRID(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), REMMINA_TYPE_PLUGIN_RDPSET_GRID))
+#define REMMINA_IS_PLUGIN_RDPSET_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), REMMINA_TYPE_PLUGIN_RDPSET_GRID))
+#define REMMINA_RDPSET_GRID_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), REMMINA_TYPE_PLUGIN_RDPSET_GRID, RemminaPluginRdpsetGridClass))
+
+typedef struct _RemminaPluginRdpsetGrid {
+ GtkGrid grid;
+
+ GtkWidget * keyboard_layout_label;
+ GtkWidget * keyboard_layout_combo;
+ GtkListStore * keyboard_layout_store;
+
+ GtkWidget * quality_combo;
+ GtkListStore * quality_store;
+ GtkWidget * wallpaper_check;
+ GtkWidget * windowdrag_check;
+ GtkWidget * menuanimation_check;
+ GtkWidget * theme_check;
+ GtkWidget * cursorshadow_check;
+ GtkWidget * cursorblinking_check;
+ GtkWidget * fontsmoothing_check;
+ GtkWidget * composition_check;
+ GtkWidget * use_client_keymap_check;
+ GtkWidget * disable_smooth_scrolling_check;
+ GtkWidget * reconnect_attempts;
+ GtkWidget * kbd_remap;
+
+ /* FreeRDP /scale-desktop: Scaling of desktop app */
+ GtkWidget * desktop_scale_factor_spin;
+ /* FreeRDP /scale-device: Scaling of appstore app */
+ GtkListStore * device_scale_factor_store;
+ GtkWidget * device_scale_factor_combo;
+ /* FreeRDP /orientation: Orientation of display */
+ GtkListStore * desktop_orientation_store;
+ GtkWidget * desktop_orientation_combo;
+
+ guint quality_values[10];
+} RemminaPluginRdpsetGrid;
+
+typedef struct _RemminaPluginRdpsetGridClass {
+ GtkGridClass parent_class;
+} RemminaPluginRdpsetGridClass;
+
+GType remmina_rdp_settings_grid_get_type(void) G_GNUC_CONST;
+
+G_DEFINE_TYPE(RemminaPluginRdpsetGrid, remmina_rdp_settings_grid, GTK_TYPE_GRID)
+
+static void remmina_rdp_settings_grid_class_init(RemminaPluginRdpsetGridClass *klass)
+{
+ TRACE_CALL(__func__);
+}
+
+static void remmina_rdp_settings_grid_destroy(GtkWidget *widget, gpointer data)
+{
+ TRACE_CALL(__func__);
+ gchar *s;
+ guint new_layout;
+ GtkTreeIter iter;
+ RemminaPluginRdpsetGrid *grid;
+ gint val;
+
+ grid = REMMINA_RDPSET_GRID(widget);
+
+ if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(grid->keyboard_layout_combo), &iter)) {
+ gtk_tree_model_get(GTK_TREE_MODEL(grid->keyboard_layout_store), &iter, 0, &new_layout, -1);
+
+ if (new_layout != rdp_keyboard_layout) {
+ rdp_keyboard_layout = new_layout;
+ s = g_strdup_printf("%X", new_layout);
+ remmina_plugin_service->pref_set_value("rdp_keyboard_layout", s);
+ g_free(s);
+
+ remmina_rdp_settings_kbd_init();
+ }
+ }
+
+ remmina_plugin_service->pref_set_value("rdp_use_client_keymap",
+ gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(grid->use_client_keymap_check)) ? "1" : "0");
+
+ remmina_plugin_service->pref_set_value("rdp_disable_smooth_scrolling",
+ gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(grid->disable_smooth_scrolling_check)) ? "1" : "0");
+
+ remmina_plugin_service->pref_set_value("rdp_reconnect_attempts",
+ gtk_entry_get_text(GTK_ENTRY(grid->reconnect_attempts)));
+
+ remmina_plugin_service->pref_set_value("rdp_kbd_remap",
+ gtk_entry_get_text(GTK_ENTRY(grid->kbd_remap)));
+
+ s = g_strdup_printf("%X", grid->quality_values[0]);
+ remmina_plugin_service->pref_set_value("rdp_quality_0", s);
+ g_free(s);
+
+ s = g_strdup_printf("%X", grid->quality_values[1]);
+ remmina_plugin_service->pref_set_value("rdp_quality_1", s);
+ g_free(s);
+
+ s = g_strdup_printf("%X", grid->quality_values[2]);
+ remmina_plugin_service->pref_set_value("rdp_quality_2", s);
+ g_free(s);
+
+ s = g_strdup_printf("%X", grid->quality_values[9]);
+ remmina_plugin_service->pref_set_value("rdp_quality_9", s);
+ g_free(s);
+
+ if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(grid->device_scale_factor_combo), &iter))
+ gtk_tree_model_get(GTK_TREE_MODEL(grid->device_scale_factor_store), &iter, 0, &val, -1);
+ else
+ val = 0;
+ s = g_strdup_printf("%d", val);
+ remmina_plugin_service->pref_set_value("rdp_deviceScaleFactor", s);
+ g_free(s);
+
+ val = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(grid->desktop_scale_factor_spin));
+ s = g_strdup_printf("%d", val);
+ remmina_plugin_service->pref_set_value("rdp_desktopScaleFactor", s);
+ g_free(s);
+
+ if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(grid->desktop_orientation_combo), &iter))
+ gtk_tree_model_get(GTK_TREE_MODEL(grid->desktop_orientation_store), &iter, 0, &val, -1);
+ else
+ val = 0;
+ s = g_strdup_printf("%d", val);
+ remmina_plugin_service->pref_set_value("rdp_desktopOrientation", s);
+ g_free(s);
+}
+
+static void remmina_rdp_settings_grid_load_layout(RemminaPluginRdpsetGrid *grid)
+{
+ TRACE_CALL(__func__);
+ gchar *s;
+ GtkTreeIter iter;
+ RDP_KEYBOARD_LAYOUT *layouts;
+
+ gtk_list_store_append(grid->keyboard_layout_store, &iter);
+ gtk_list_store_set(grid->keyboard_layout_store, &iter, 0, 0, 1, _("<Auto-detect>"), -1);
+
+ if (rdp_keyboard_layout == 0)
+ gtk_combo_box_set_active(GTK_COMBO_BOX(grid->keyboard_layout_combo), 0);
+
+ gtk_label_set_text(GTK_LABEL(grid->keyboard_layout_label), "-");
+
+#if FREERDP_VERSION_MAJOR >= 3
+ size_t layout_count = 0;
+ layouts = freerdp_keyboard_get_layouts(RDP_KEYBOARD_LAYOUT_TYPE_STANDARD | RDP_KEYBOARD_LAYOUT_TYPE_VARIANT, &layout_count);
+
+ for (DWORD i = 0; i < layout_count; i++) {
+ s = g_strdup_printf("%08X - %s", layouts[i].code, layouts[i].name);
+ gtk_list_store_append(grid->keyboard_layout_store, &iter);
+ gtk_list_store_set(grid->keyboard_layout_store, &iter, 0, layouts[i].code, 1, s, -1);
+
+ if (rdp_keyboard_layout == layouts[i].code)
+ gtk_combo_box_set_active(GTK_COMBO_BOX(grid->keyboard_layout_combo), i + 1);
+
+ if (keyboard_layout == layouts[i].code)
+ gtk_label_set_text(GTK_LABEL(grid->keyboard_layout_label), s);
+
+ g_free(s);
+ }
+
+ freerdp_keyboard_layouts_free(layouts, layout_count);
+#else
+ layouts = freerdp_keyboard_get_layouts(RDP_KEYBOARD_LAYOUT_TYPE_STANDARD | RDP_KEYBOARD_LAYOUT_TYPE_VARIANT);
+
+ for (DWORD i = 0; layouts[i].code; i++) {
+ s = g_strdup_printf("%08X - %s", layouts[i].code, layouts[i].name);
+ gtk_list_store_append(grid->keyboard_layout_store, &iter);
+ gtk_list_store_set(grid->keyboard_layout_store, &iter, 0, layouts[i].code, 1, s, -1);
+
+ if (rdp_keyboard_layout == layouts[i].code)
+ gtk_combo_box_set_active(GTK_COMBO_BOX(grid->keyboard_layout_combo), i + 1);
+
+ if (keyboard_layout == layouts[i].code)
+ gtk_label_set_text(GTK_LABEL(grid->keyboard_layout_label), s);
+
+ g_free(s);
+ }
+
+ freerdp_keyboard_layouts_free(layouts);
+#endif
+}
+
+static void remmina_rdp_settings_grid_load_devicescalefactor_combo(RemminaPluginRdpsetGrid *grid)
+{
+ TRACE_CALL(__func__);
+ GtkTreeIter iter;
+
+ gtk_list_store_append(grid->device_scale_factor_store, &iter);
+ gtk_list_store_set(grid->device_scale_factor_store, &iter, 0, 0, 1, _("<Not set>"), -1);
+ gtk_list_store_append(grid->device_scale_factor_store, &iter);
+ gtk_list_store_set(grid->device_scale_factor_store, &iter, 0, 100, 1, "100%", -1);
+ gtk_list_store_append(grid->device_scale_factor_store, &iter);
+ gtk_list_store_set(grid->device_scale_factor_store, &iter, 0, 140, 1, "140%", -1);
+ gtk_list_store_append(grid->device_scale_factor_store, &iter);
+ gtk_list_store_set(grid->device_scale_factor_store, &iter, 0, 180, 1, "180%", -1);
+}
+
+static void remmina_rdp_settings_grid_load_desktoporientation_combo(RemminaPluginRdpsetGrid *grid)
+{
+ TRACE_CALL(__func__);
+ GtkTreeIter iter;
+
+ gtk_list_store_append(grid->desktop_orientation_store, &iter);
+ gtk_list_store_set(grid->desktop_orientation_store, &iter, 0, 0, 1, "0°", -1);
+ gtk_list_store_append(grid->desktop_orientation_store, &iter);
+ gtk_list_store_set(grid->desktop_orientation_store, &iter, 0, 90, 1, "90°", -1);
+ gtk_list_store_append(grid->desktop_orientation_store, &iter);
+ gtk_list_store_set(grid->desktop_orientation_store, &iter, 0, 180, 1, "180°", -1);
+ gtk_list_store_append(grid->desktop_orientation_store, &iter);
+ gtk_list_store_set(grid->desktop_orientation_store, &iter, 0, 270, 1, "270°", -1);
+}
+
+static void remmina_rdp_settings_grid_load_quality(RemminaPluginRdpsetGrid *grid)
+{
+ TRACE_CALL(__func__);
+ gchar *value;
+ GtkTreeIter iter;
+
+ gtk_list_store_append(grid->quality_store, &iter);
+ gtk_list_store_set(grid->quality_store, &iter, 0, -1, 1, _("<Choose a quality level to edit…>"), -1);
+ gtk_list_store_append(grid->quality_store, &iter);
+ gtk_list_store_set(grid->quality_store, &iter, 0, 0, 1, _("Poor (fastest)"), -1);
+ gtk_list_store_append(grid->quality_store, &iter);
+ gtk_list_store_set(grid->quality_store, &iter, 0, 1, 1, _("Medium"), -1);
+ gtk_list_store_append(grid->quality_store, &iter);
+ gtk_list_store_set(grid->quality_store, &iter, 0, 2, 1, _("Good"), -1);
+ gtk_list_store_append(grid->quality_store, &iter);
+ gtk_list_store_set(grid->quality_store, &iter, 0, 9, 1, _("Best (slowest)"), -1);
+
+ memset(grid->quality_values, 0, sizeof(grid->quality_values));
+
+ value = remmina_plugin_service->pref_get_value("rdp_quality_0");
+ grid->quality_values[0] = (value && value[0] ? strtoul(value, NULL, 16) : DEFAULT_QUALITY_0);
+ g_free(value);
+
+ value = remmina_plugin_service->pref_get_value("rdp_quality_1");
+ grid->quality_values[1] = (value && value[0] ? strtoul(value, NULL, 16) : DEFAULT_QUALITY_1);
+ g_free(value);
+
+ value = remmina_plugin_service->pref_get_value("rdp_quality_2");
+ grid->quality_values[2] = (value && value[0] ? strtoul(value, NULL, 16) : DEFAULT_QUALITY_2);
+ g_free(value);
+
+ value = remmina_plugin_service->pref_get_value("rdp_quality_9");
+ grid->quality_values[9] = (value && value[0] ? strtoul(value, NULL, 16) : DEFAULT_QUALITY_9);
+ g_free(value);
+}
+
+static void remmina_rdp_settings_appscale_on_changed(GtkComboBox *widget, RemminaPluginRdpsetGrid *grid)
+{
+ TRACE_CALL(__func__);
+ GtkTreeIter iter;
+ guint i = 0;
+
+ if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(grid->device_scale_factor_combo), &iter))
+ gtk_tree_model_get(GTK_TREE_MODEL(grid->device_scale_factor_store), &iter, 0, &i, -1);
+ if (i == 0) {
+ gtk_widget_set_sensitive(GTK_WIDGET(grid->desktop_scale_factor_spin), FALSE);
+ gtk_spin_button_set_range(GTK_SPIN_BUTTON(grid->desktop_scale_factor_spin), 0, 0);
+ gtk_spin_button_set_value(GTK_SPIN_BUTTON(grid->desktop_scale_factor_spin), 0);
+ } else {
+ gtk_widget_set_sensitive(GTK_WIDGET(grid->desktop_scale_factor_spin), TRUE);
+ gtk_spin_button_set_range(GTK_SPIN_BUTTON(grid->desktop_scale_factor_spin), 100, 500);
+ // gtk_spin_button_set_value(GTK_SPIN_BUTTON(grid->desktop_scale_factor_spin), i);
+ }
+}
+
+static void remmina_rdp_settings_quality_on_changed(GtkComboBox *widget, RemminaPluginRdpsetGrid *grid)
+{
+ TRACE_CALL(__func__);
+ guint v;
+ guint i = 0;
+ GtkTreeIter iter;
+ gboolean sensitive;
+
+ if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(grid->quality_combo), &iter)) {
+ gtk_tree_model_get(GTK_TREE_MODEL(grid->quality_store), &iter, 0, &i, -1);
+ sensitive = (i != -1);
+
+ if (sensitive)
+ v = grid->quality_values[i];
+ else
+ v = 0x3f; /* All checkboxes disabled */
+
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(grid->wallpaper_check), (v & 1) == 0);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(grid->windowdrag_check), (v & 2) == 0);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(grid->menuanimation_check), (v & 4) == 0);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(grid->theme_check), (v & 8) == 0);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(grid->cursorshadow_check), (v & 0x20) == 0);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(grid->cursorblinking_check), (v & 0x40) == 0);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(grid->fontsmoothing_check), (v & 0x80) != 0);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(grid->composition_check), (v & 0x100) != 0);
+
+
+ gtk_widget_set_sensitive(GTK_WIDGET(grid->wallpaper_check), sensitive);
+ gtk_widget_set_sensitive(GTK_WIDGET(grid->windowdrag_check), sensitive);
+ gtk_widget_set_sensitive(GTK_WIDGET(grid->menuanimation_check), sensitive);
+ gtk_widget_set_sensitive(GTK_WIDGET(grid->theme_check), sensitive);
+ gtk_widget_set_sensitive(GTK_WIDGET(grid->cursorshadow_check), sensitive);
+ gtk_widget_set_sensitive(GTK_WIDGET(grid->cursorblinking_check), sensitive);
+ gtk_widget_set_sensitive(GTK_WIDGET(grid->fontsmoothing_check), sensitive);
+ gtk_widget_set_sensitive(GTK_WIDGET(grid->composition_check), sensitive);
+ }
+}
+
+static void remmina_rdp_settings_quality_option_on_toggled(GtkToggleButton *togglebutton, RemminaPluginRdpsetGrid *grid)
+{
+ TRACE_CALL(__func__);
+ guint v;
+ guint i = 0;
+ GtkTreeIter iter;
+
+ if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(grid->quality_combo), &iter)) {
+ gtk_tree_model_get(GTK_TREE_MODEL(grid->quality_store), &iter, 0, &i, -1);
+ if (i != -1) {
+ v = 0;
+ v |= (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(grid->wallpaper_check)) ? 0 : 1);
+ v |= (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(grid->windowdrag_check)) ? 0 : 2);
+ v |= (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(grid->menuanimation_check)) ? 0 : 4);
+ v |= (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(grid->theme_check)) ? 0 : 8);
+ v |= (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(grid->cursorshadow_check)) ? 0 : 0x20);
+ v |= (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(grid->cursorblinking_check)) ? 0 : 0x40);
+ v |= (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(grid->fontsmoothing_check)) ? 0x80 : 0);
+ v |= (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(grid->composition_check)) ? 0x100 : 0);
+ grid->quality_values[i] = v;
+ }
+ }
+}
+
+static void remmina_rdp_settings_set_combo_active_item(GtkComboBox *combo, int itemval)
+{
+ GtkTreeIter iter;
+ int i;
+ GtkTreeModel *m;
+ gboolean valid;
+
+ m = gtk_combo_box_get_model(combo);
+ if (!m)
+ return;
+
+ valid = gtk_tree_model_get_iter_first(m, &iter);
+ while (valid) {
+ gtk_tree_model_get(m, &iter, 0, &i, -1);
+ if (i == itemval)
+ gtk_combo_box_set_active_iter(combo, &iter);
+ valid = gtk_tree_model_iter_next(m, &iter);
+ }
+}
+
+static void remmina_rdp_settings_grid_init(RemminaPluginRdpsetGrid *grid)
+{
+ TRACE_CALL(__func__);
+ gchar *s;
+ GtkWidget *widget;
+ GtkCellRenderer *renderer;
+ int desktopOrientation, desktopScaleFactor, deviceScaleFactor;
+
+ /* Create the grid */
+ g_signal_connect(G_OBJECT(grid), "destroy", G_CALLBACK(remmina_rdp_settings_grid_destroy), NULL);
+ gtk_grid_set_row_homogeneous(GTK_GRID(grid), FALSE);
+ gtk_grid_set_column_homogeneous(GTK_GRID(grid), FALSE);
+ gtk_container_set_border_width(GTK_CONTAINER(grid), 8);
+ gtk_grid_set_row_spacing(GTK_GRID(grid), 4);
+ gtk_grid_set_column_spacing(GTK_GRID(grid), 4);
+
+ /* Create the content */
+ widget = gtk_label_new(_("Keyboard layout"));
+ gtk_widget_show(widget);
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_set_margin_end(GTK_WIDGET(widget), 6);
+ gtk_widget_set_margin_start(GTK_WIDGET(widget), 18);
+ gtk_widget_set_margin_top(GTK_WIDGET(widget), 18);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, 0, 1, 1);
+
+ grid->keyboard_layout_store = gtk_list_store_new(2, G_TYPE_UINT, G_TYPE_STRING);
+ widget = gtk_combo_box_new_with_model(GTK_TREE_MODEL(grid->keyboard_layout_store));
+ gtk_widget_show(widget);
+ gtk_widget_set_margin_start(GTK_WIDGET(widget), 6);
+ gtk_grid_attach(GTK_GRID(grid), widget, 1, 0, 2, 1);
+
+ renderer = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(widget), renderer, TRUE);
+ gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(widget), renderer, "text", 1);
+ grid->keyboard_layout_combo = widget;
+
+ widget = gtk_label_new("-");
+ gtk_widget_show(widget);
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_set_margin_start(GTK_WIDGET(widget), 6);
+ gtk_grid_attach(GTK_GRID(grid), widget, 1, 1, 2, 1);
+ grid->keyboard_layout_label = widget;
+
+ remmina_rdp_settings_grid_load_layout(grid);
+
+ widget = gtk_check_button_new_with_label(_("Use client keyboard mapping"));
+ gtk_widget_show(widget);
+ gtk_widget_set_margin_start(GTK_WIDGET(widget), 6);
+ gtk_grid_attach(GTK_GRID(grid), widget, 1, 2, 1, 1);
+ grid->use_client_keymap_check = widget;
+
+ s = remmina_plugin_service->pref_get_value("rdp_use_client_keymap");
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget),
+ s && s[0] == '1' ? TRUE : FALSE);
+ g_free(s);
+
+ widget = gtk_label_new(_("Keyboard scancode remapping"));
+ gtk_widget_show(widget);
+ gtk_widget_set_margin_start(GTK_WIDGET(widget), 6);
+ gtk_grid_attach(GTK_GRID(grid), widget, 1, 3, 1, 1);
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ widget = gtk_entry_new();
+ gtk_widget_show(widget);
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_END);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_grid_attach(GTK_GRID(grid), widget, 2, 3, 1, 1);
+ gtk_entry_set_width_chars(GTK_ENTRY(widget), 32);
+#if FREERDP_CHECK_VERSION(2, 3, 0)
+ /* This is the default, but we set it to make things crystal clear */
+ gtk_widget_set_sensitive (widget, TRUE);
+ gtk_widget_set_tooltip_text(widget, _("List of key=value,… pairs to remap scancodes. E.g. 0x56=0x29,0x29=0x56"));
+#else
+ gtk_widget_set_sensitive (widget, FALSE);
+ gtk_widget_set_tooltip_text(widget, _("FreeRDP > 2.3.0 is required to map scancodes"));
+#endif
+ s = remmina_plugin_service->pref_get_value("rdp_kbd_remap");
+ if (s && s[0])
+ gtk_entry_set_text(GTK_ENTRY(widget), s);
+ g_free(s);
+ grid->kbd_remap = widget;
+
+ widget = gtk_label_new(_("Quality settings"));
+ gtk_widget_show(widget);
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_set_margin_end(GTK_WIDGET(widget), 6);
+ gtk_widget_set_margin_start(GTK_WIDGET(widget), 18);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, 4, 1, 1);
+
+ grid->quality_store = gtk_list_store_new(2, G_TYPE_UINT, G_TYPE_STRING);
+ widget = gtk_combo_box_new_with_model(GTK_TREE_MODEL(grid->quality_store));
+ gtk_widget_show(widget);
+ gtk_widget_set_margin_start(GTK_WIDGET(widget), 6);
+ gtk_grid_attach(GTK_GRID(grid), widget, 1, 4, 2, 1);
+
+ renderer = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(widget), renderer, TRUE);
+ gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(widget), renderer, "text", 1);
+ g_signal_connect(G_OBJECT(widget), "changed",
+ G_CALLBACK(remmina_rdp_settings_quality_on_changed), grid);
+ grid->quality_combo = widget;
+
+ remmina_rdp_settings_grid_load_quality(grid);
+
+ widget = gtk_check_button_new_with_label(_("Wallpaper"));
+ gtk_widget_show(widget);
+ gtk_widget_set_margin_start(GTK_WIDGET(widget), 6);
+ gtk_grid_attach(GTK_GRID(grid), widget, 1, 5, 1, 1);
+ g_signal_connect(G_OBJECT(widget), "toggled",
+ G_CALLBACK(remmina_rdp_settings_quality_option_on_toggled), grid);
+ grid->wallpaper_check = widget;
+
+ widget = gtk_check_button_new_with_label(_("Window drag"));
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 2, 5, 1, 1);
+ g_signal_connect(G_OBJECT(widget), "toggled",
+ G_CALLBACK(remmina_rdp_settings_quality_option_on_toggled), grid);
+ grid->windowdrag_check = widget;
+
+ widget = gtk_check_button_new_with_label(_("Menu animation"));
+ gtk_widget_show(widget);
+ gtk_widget_set_margin_start(GTK_WIDGET(widget), 6);
+ gtk_grid_attach(GTK_GRID(grid), widget, 1, 6, 1, 1);
+ g_signal_connect(G_OBJECT(widget), "toggled",
+ G_CALLBACK(remmina_rdp_settings_quality_option_on_toggled), grid);
+ grid->menuanimation_check = widget;
+
+ widget = gtk_check_button_new_with_label(_("Theme"));
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 2, 6, 1, 1);
+ g_signal_connect(G_OBJECT(widget), "toggled",
+ G_CALLBACK(remmina_rdp_settings_quality_option_on_toggled), grid);
+ grid->theme_check = widget;
+
+ widget = gtk_check_button_new_with_label(_("Cursor shadow"));
+ gtk_widget_show(widget);
+ gtk_widget_set_margin_start(GTK_WIDGET(widget), 6);
+ gtk_grid_attach(GTK_GRID(grid), widget, 1, 7, 1, 1);
+ g_signal_connect(G_OBJECT(widget), "toggled",
+ G_CALLBACK(remmina_rdp_settings_quality_option_on_toggled), grid);
+ grid->cursorshadow_check = widget;
+
+ widget = gtk_check_button_new_with_label(_("Cursor blinking"));
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 2, 7, 1, 1);
+ g_signal_connect(G_OBJECT(widget), "toggled",
+ G_CALLBACK(remmina_rdp_settings_quality_option_on_toggled), grid);
+ grid->cursorblinking_check = widget;
+
+ widget = gtk_check_button_new_with_label(_("Font smoothing"));
+ gtk_widget_show(widget);
+ gtk_widget_set_margin_start(GTK_WIDGET(widget), 6);
+ gtk_grid_attach(GTK_GRID(grid), widget, 1, 8, 1, 1);
+ g_signal_connect(G_OBJECT(widget), "toggled",
+ G_CALLBACK(remmina_rdp_settings_quality_option_on_toggled), grid);
+ grid->fontsmoothing_check = widget;
+
+ widget = gtk_check_button_new_with_label(_("Composition"));
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 2, 8, 1, 1);
+ g_signal_connect(G_OBJECT(widget), "toggled",
+ G_CALLBACK(remmina_rdp_settings_quality_option_on_toggled), grid);
+ grid->composition_check = widget;
+
+ gtk_combo_box_set_active(GTK_COMBO_BOX(grid->quality_combo), 0);
+
+
+ widget = gtk_label_new(_("Remote scale factor"));
+ gtk_widget_show(widget);
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_set_margin_end(GTK_WIDGET(widget), 6);
+ gtk_widget_set_margin_start(GTK_WIDGET(widget), 18);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, 9, 1, 1);
+
+ grid->device_scale_factor_store = gtk_list_store_new(2, G_TYPE_INT, G_TYPE_STRING);
+ grid->desktop_orientation_store = gtk_list_store_new(2, G_TYPE_INT, G_TYPE_STRING);
+
+ remmina_rdp_settings_get_orientation_scale_prefs(&desktopOrientation, &desktopScaleFactor, &deviceScaleFactor);
+ remmina_rdp_settings_grid_load_devicescalefactor_combo(grid);
+ remmina_rdp_settings_grid_load_desktoporientation_combo(grid);
+
+ widget = gtk_label_new(_("Desktop scale factor %"));
+ gtk_widget_show(widget);
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_set_margin_start(GTK_WIDGET(widget), 6);
+ gtk_grid_attach(GTK_GRID(grid), widget, 1, 9, 1, 1);
+
+ widget = gtk_spin_button_new_with_range(0, 10000, 1);
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 2, 9, 1, 1);
+ grid->desktop_scale_factor_spin = widget;
+
+ widget = gtk_label_new(_("Device scale factor %"));
+ gtk_widget_show(widget);
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_set_margin_start(GTK_WIDGET(widget), 6);
+ gtk_grid_attach(GTK_GRID(grid), widget, 1, 10, 1, 1);
+
+ widget = gtk_combo_box_new_with_model(GTK_TREE_MODEL(grid->device_scale_factor_store));
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 2, 10, 1, 1);
+
+ renderer = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(widget), renderer, TRUE);
+ gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(widget), renderer, "text", 1);
+ grid->device_scale_factor_combo = widget;
+
+ remmina_rdp_settings_set_combo_active_item(GTK_COMBO_BOX(grid->device_scale_factor_combo), deviceScaleFactor);
+ gtk_spin_button_set_value(GTK_SPIN_BUTTON(grid->desktop_scale_factor_spin), (gdouble)desktopScaleFactor);
+
+ g_signal_connect(G_OBJECT(widget), "changed",
+ G_CALLBACK(remmina_rdp_settings_appscale_on_changed), grid);
+ remmina_rdp_settings_appscale_on_changed(GTK_COMBO_BOX(grid->device_scale_factor_combo), grid);
+
+ widget = gtk_label_new(_("Desktop orientation"));
+ gtk_widget_show(widget);
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_set_margin_end(GTK_WIDGET(widget), 6);
+ gtk_widget_set_margin_start(GTK_WIDGET(widget), 18);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, 11, 1, 1);
+
+ widget = gtk_combo_box_new_with_model(GTK_TREE_MODEL(grid->desktop_orientation_store));
+ gtk_widget_show(widget);
+ gtk_widget_set_margin_start(GTK_WIDGET(widget), 6);
+ gtk_grid_attach(GTK_GRID(grid), widget, 1, 11, 2, 1);
+
+ renderer = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(widget), renderer, TRUE);
+ gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(widget), renderer, "text", 1);
+ grid->desktop_orientation_combo = widget;
+
+ remmina_rdp_settings_set_combo_active_item(GTK_COMBO_BOX(grid->desktop_orientation_combo), desktopOrientation);
+
+ widget = gtk_label_new(_("Input device settings"));
+ gtk_widget_show(widget);
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_set_margin_end(GTK_WIDGET(widget), 6);
+ gtk_widget_set_margin_start(GTK_WIDGET(widget), 18);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, 12, 1, 1);
+
+ widget = gtk_check_button_new_with_label(_("Disable smooth scrolling"));
+ gtk_widget_show(widget);
+ gtk_widget_set_margin_start(GTK_WIDGET(widget), 6);
+ gtk_grid_attach(GTK_GRID(grid), widget, 1, 12, 2, 1);
+ grid->disable_smooth_scrolling_check = widget;
+
+ s = remmina_plugin_service->pref_get_value("rdp_disable_smooth_scrolling");
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget),
+ s && s[0] == '1' ? TRUE : FALSE);
+ g_free(s);
+
+ widget = gtk_label_new(_("General settings"));
+ gtk_widget_show(widget);
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_set_margin_end(GTK_WIDGET(widget), 6);
+ gtk_widget_set_margin_start(GTK_WIDGET(widget), 18);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, 13, 1, 1);
+ widget = gtk_label_new(_("Reconnect attempts number"));
+ gtk_widget_show(widget);
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_set_margin_start(GTK_WIDGET(widget), 6);
+ gtk_grid_attach(GTK_GRID(grid), widget, 1, 13, 1, 1);
+ widget = gtk_entry_new();
+ gtk_widget_show(widget);
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_END);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_grid_attach(GTK_GRID(grid), widget, 2, 13, 1, 1);
+ gtk_entry_set_input_purpose(GTK_ENTRY(widget), GTK_INPUT_PURPOSE_NUMBER);
+ gtk_entry_set_input_hints(GTK_ENTRY(widget), GTK_INPUT_HINT_NONE);
+ gtk_widget_set_tooltip_text(widget, _("The maximum number of reconnect attempts upon an RDP disconnect (default: 20)"));
+ s = remmina_plugin_service->pref_get_value("rdp_reconnect_attempts");
+ if (s && s[0])
+ gtk_entry_set_text(GTK_ENTRY(widget), s);
+ g_free(s);
+ grid->reconnect_attempts = widget;
+
+}
+
+GtkWidget *remmina_rdp_settings_new(RemminaPrefPlugin* plugin)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *widget;
+
+ widget = GTK_WIDGET(g_object_new(REMMINA_TYPE_PLUGIN_RDPSET_GRID, NULL));
+ gtk_widget_show(widget);
+
+ return widget;
+}
+
+void remmina_rdp_settings_get_orientation_scale_prefs(int *desktopOrientation, int *desktopScaleFactor, int *deviceScaleFactor)
+{
+ TRACE_CALL(__func__);
+
+ /* See https://msdn.microsoft.com/en-us/library/cc240510.aspx */
+
+ int orientation, dpsf, desf;
+ gchar *s;
+
+ *desktopOrientation = *desktopScaleFactor = *deviceScaleFactor = 0;
+
+ s = remmina_plugin_service->pref_get_value("rdp_desktopOrientation");
+ orientation = s ? atoi(s) : 0;
+ g_free(s);
+ if (orientation != 90 && orientation != 180 && orientation != 270)
+ orientation = 0;
+ *desktopOrientation = orientation;
+
+ s = remmina_plugin_service->pref_get_value("rdp_desktopScaleFactor");
+ dpsf = s ? atoi(s) : 0;
+ g_free(s);
+ if (dpsf < 100 || dpsf > 500)
+ return;
+
+ s = remmina_plugin_service->pref_get_value("rdp_deviceScaleFactor");
+ desf = s ? atoi(s) : 0;
+ g_free(s);
+ if (desf != 100 && desf != 140 && desf != 180)
+ return;
+
+ *desktopScaleFactor = dpsf;
+ *deviceScaleFactor = desf;
+}
diff --git a/plugins/rdp/rdp_settings.h b/plugins/rdp/rdp_settings.h
new file mode 100644
index 0000000..e3b9bc7
--- /dev/null
+++ b/plugins/rdp/rdp_settings.h
@@ -0,0 +1,49 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010-2011 Vic Lee
+ * Copyright (C) 2017-2023 Antenore Gatta, Giovanni Panozzo
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL. * If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. * If you
+ * do not wish to do so, delete this exception statement from your
+ * version. * If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ *
+ */
+
+#pragma once
+
+#include <glib.h>
+#include "rdp_plugin.h"
+
+G_BEGIN_DECLS
+
+void remmina_rdp_settings_init(void);
+guint remmina_rdp_settings_get_keyboard_layout(void);
+GtkWidget *remmina_rdp_settings_new(RemminaPrefPlugin* plugin);
+
+void remmina_rdp_settings_get_orientation_scale_prefs(int *desktopOrientation, int *desktopScaleFactor, int *deviceScaleFactor);
+
+G_END_DECLS
diff --git a/plugins/rdp/scalable/emblems/org.remmina.Remmina-rdp-ssh-symbolic.svg b/plugins/rdp/scalable/emblems/org.remmina.Remmina-rdp-ssh-symbolic.svg
new file mode 100644
index 0000000..6a345cf
--- /dev/null
+++ b/plugins/rdp/scalable/emblems/org.remmina.Remmina-rdp-ssh-symbolic.svg
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="100"
+ height="100"
+ viewBox="0 0 26.458334 26.458332"
+ version="1.1"
+ id="svg8"
+ inkscape:version="0.92.2 2405546, 2018-03-11"
+ sodipodi:docname="remmina-rdp-ssh-symbolic.svg">
+ <defs
+ id="defs2" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="4"
+ inkscape:cx="25.558198"
+ inkscape:cy="61.982189"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ inkscape:document-rotation="0"
+ showgrid="false"
+ borderlayer="true"
+ inkscape:showpageshadow="true"
+ units="px"
+ inkscape:pagecheckerboard="false"
+ showguides="true"
+ inkscape:window-width="1600"
+ inkscape:window-height="879"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ objecttolerance="10"
+ guidetolerance="10"
+ inkscape:snap-tangential="true"
+ inkscape:snap-perpendicular="true"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0">
+ <inkscape:grid
+ type="xygrid"
+ id="grid10"
+ dotted="false"
+ originx="-179.27136"
+ originy="-128.69822" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata5">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ <cc:license
+ rdf:resource="" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-179.27135,-141.84345)">
+ <g
+ id="g817"
+ transform="matrix(0.93878631,0,0,0.93878631,13.549948,10.22929)">
+ <path
+ style="isolation:isolate;fill:#000000;stroke-width:0.93878627"
+ d="M 49.884766 0.27539062 C 34.886727 0.27539062 22.888672 12.506314 22.888672 27.273438 L 22.888672 32.117188 L 9.7363281 32.117188 L 9.7363281 99.724609 L 31.220703 99.724609 L 90.263672 99.724609 L 90.263672 32.117188 L 76.880859 32.117188 L 76.880859 27.273438 C 76.880859 12.275364 64.651854 0.27539062 49.884766 0.27539062 z M 49.654297 14.582031 C 56.806908 14.582031 62.34375 20.350818 62.34375 27.273438 L 62.34375 32.117188 L 36.732422 32.117188 L 36.732422 27.273438 L 36.962891 27.273438 C 36.962891 20.119868 42.732636 14.582031 49.654297 14.582031 z M 50 39.673828 C 51.038296 39.673828 52.075804 40.069448 52.867188 40.861328 L 56.373047 44.367188 L 59.878906 47.871094 C 61.462631 49.453896 61.462631 52.024619 59.878906 53.607422 L 56.373047 57.113281 L 52.867188 60.619141 C 51.28442 62.201943 48.715615 62.201943 47.132812 60.619141 L 43.625 57.113281 L 40.121094 53.607422 C 38.538291 52.024619 38.538291 49.453896 40.121094 47.871094 L 43.625 44.367188 L 47.132812 40.861328 C 47.924214 40.069448 48.961704 39.673828 50 39.673828 z M 32.560547 55.105469 C 33.593916 55.105469 34.626422 55.499469 35.414062 56.287109 L 38.933594 59.806641 L 42.451172 63.324219 C 44.02741 64.900457 44.02741 67.458918 42.451172 69.035156 L 38.933594 72.552734 L 35.414062 76.072266 C 33.838782 77.647546 31.280359 77.647546 29.705078 76.072266 L 26.185547 72.552734 L 22.666016 69.035156 C 21.090735 67.458918 21.090735 64.900457 22.666016 63.324219 L 26.185547 59.806641 L 29.705078 56.287109 C 30.492718 55.499469 31.527178 55.105469 32.560547 55.105469 z M 67.710938 55.105469 C 68.744306 55.105469 69.776813 55.499469 70.564453 56.287109 L 74.083984 59.806641 L 77.603516 63.324219 C 79.179719 64.900457 79.179719 67.458918 77.603516 69.035156 L 74.083984 72.552734 L 70.564453 76.072266 C 68.989173 77.647546 66.430749 77.647546 64.855469 76.072266 L 61.335938 72.552734 L 57.816406 69.035156 C 56.241126 67.458918 56.241126 64.900457 57.816406 63.324219 L 61.335938 59.806641 L 64.855469 56.287109 C 65.643109 55.499469 66.677569 55.105469 67.710938 55.105469 z M 50.261719 70.865234 C 51.295087 70.865234 52.329547 71.259235 53.117188 72.046875 L 56.636719 75.566406 L 60.154297 79.083984 C 61.729577 80.660223 61.729577 83.218683 60.154297 84.794922 L 56.636719 88.3125 L 53.117188 91.832031 C 51.541907 93.407312 48.983484 93.407312 47.408203 91.832031 L 43.888672 88.3125 L 40.369141 84.794922 C 38.79386 83.218683 38.79386 80.660223 40.369141 79.083984 L 43.888672 75.566406 L 47.408203 72.046875 C 48.195843 71.259235 49.22835 70.865234 50.261719 70.865234 z "
+ transform="matrix(0.28183552,0,0,0.28183552,176.52729,140.19608)"
+ id="path79" />
+ </g>
+ </g>
+</svg>
diff --git a/plugins/rdp/scalable/emblems/org.remmina.Remmina-rdp-symbolic.svg b/plugins/rdp/scalable/emblems/org.remmina.Remmina-rdp-symbolic.svg
new file mode 100644
index 0000000..af2b970
--- /dev/null
+++ b/plugins/rdp/scalable/emblems/org.remmina.Remmina-rdp-symbolic.svg
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="100"
+ height="100"
+ viewBox="0 0 26.458334 26.458333"
+ version="1.1"
+ id="svg8"
+ inkscape:version="0.92.2 2405546, 2018-03-11"
+ sodipodi:docname="remmina-rdp-symbolic.svg">
+ <defs
+ id="defs2" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="2.0986883"
+ inkscape:cx="-49.823663"
+ inkscape:cy="43.808883"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ inkscape:document-rotation="0"
+ showgrid="false"
+ borderlayer="true"
+ inkscape:showpageshadow="true"
+ units="px"
+ inkscape:pagecheckerboard="false"
+ showguides="true"
+ inkscape:window-width="1600"
+ inkscape:window-height="879"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ objecttolerance="10"
+ guidetolerance="10"
+ inkscape:snap-tangential="true"
+ inkscape:snap-perpendicular="true"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0">
+ <inkscape:grid
+ type="xygrid"
+ id="grid10"
+ dotted="false"
+ originx="-190.61239"
+ originy="-78.157086" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata5">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ <cc:license
+ rdf:resource="" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-190.61238,-192.38458)">
+ <g
+ transform="matrix(0.26458333,0,0,0.26458333,153.78079,124.26165)"
+ style="isolation:isolate"
+ id="RDP Icons">
+ <g
+ id="Group" />
+ <g
+ id="g50">
+ <path
+ style="fill:#000000"
+ d="M 50 0.41992188 C 22.636004 0.41992188 0.41992185 22.636004 0.41992188 50 C 0.41992188 77.363996 22.636004 99.580078 50 99.580078 C 77.363996 99.580078 99.580078 77.363996 99.580078 50 C 99.580078 22.636004 77.363996 0.41992185 50 0.41992188 z M 50.339844 15.697266 C 51.446721 15.697266 52.552988 16.117441 53.396484 16.960938 L 58.603516 22.167969 L 63.808594 27.373047 C 65.496606 29.061022 65.496606 31.800306 63.808594 33.488281 L 58.603516 38.695312 L 53.396484 43.900391 C 51.709492 45.587421 48.971216 45.587421 47.283203 43.900391 L 42.078125 38.695312 L 36.871094 33.488281 C 35.183119 31.800306 35.183119 29.061022 36.871094 27.373047 L 42.078125 22.167969 L 47.283203 16.960938 C 48.127209 16.117441 49.232967 15.697266 50.339844 15.697266 z M 27.388672 34.402344 C 28.495544 34.402344 29.603259 34.824473 30.447266 35.667969 L 35.652344 40.873047 L 40.857422 46.080078 C 42.545397 47.76707 42.545397 50.507303 40.857422 52.195312 L 35.652344 57.400391 L 30.447266 62.607422 C 28.759253 64.294414 26.019061 64.294414 24.332031 62.607422 L 19.126953 57.400391 L 13.919922 52.195312 C 12.231909 50.507303 12.231909 47.76707 13.919922 46.080078 L 19.126953 40.873047 L 24.332031 35.667969 C 25.175546 34.824473 26.281799 34.402344 27.388672 34.402344 z M 72.611328 34.402344 C 73.718201 34.402344 74.824473 34.824473 75.667969 35.667969 L 80.875 40.873047 L 86.080078 46.080078 C 87.767108 47.76707 87.767108 50.507301 86.080078 52.195312 L 80.875 57.400391 L 75.667969 62.607422 C 73.980977 64.294414 71.240709 64.294414 69.552734 62.607422 L 64.347656 57.400391 L 59.140625 52.195312 C 57.453633 50.507301 57.453633 47.76707 59.140625 46.080078 L 64.347656 40.873047 L 69.552734 35.667969 C 70.396722 34.824473 71.504456 34.402344 72.611328 34.402344 z M 50.339844 54.833984 C 51.446721 54.833984 52.552988 55.255603 53.396484 56.099609 L 58.603516 61.304688 L 63.808594 66.511719 C 65.496606 68.198711 65.496606 70.938008 63.808594 72.625 L 58.603516 77.832031 L 53.396484 83.037109 C 51.709492 84.725122 48.971216 84.725122 47.283203 83.037109 L 42.078125 77.832031 L 36.871094 72.625 C 35.183119 70.938008 35.183119 68.198711 36.871094 66.511719 L 42.078125 61.304688 L 47.283203 56.099609 C 48.127209 55.255603 49.232967 54.833984 50.339844 54.833984 z "
+ transform="translate(139.20601,257.47249)"
+ id="path48" />
+ </g>
+ <g
+ id="g62">
+ <g
+ id="g52" />
+ <g
+ id="g54" />
+ <g
+ id="g56" />
+ <g
+ id="g60">
+ <g
+ id="g58" />
+ </g>
+ </g>
+ </g>
+ <g
+ id="g831"
+ transform="translate(0.20117925,-0.3392995)" />
+ </g>
+</svg>