diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 17:06:32 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 17:06:32 +0000 |
commit | 2dad5357405ad33cfa792f04b3ab62a5d188841e (patch) | |
tree | b8f8893942060fe3cfb04ac374cda96fdfc8f453 /plugins/rdp | |
parent | Initial commit. (diff) | |
download | remmina-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.txt | 88 | ||||
-rw-r--r-- | plugins/rdp/rdp_channels.c | 96 | ||||
-rw-r--r-- | plugins/rdp/rdp_channels.h | 62 | ||||
-rw-r--r-- | plugins/rdp/rdp_cliprdr.c | 1034 | ||||
-rw-r--r-- | plugins/rdp/rdp_cliprdr.h | 51 | ||||
-rw-r--r-- | plugins/rdp/rdp_event.c | 1494 | ||||
-rw-r--r-- | plugins/rdp/rdp_event.h | 58 | ||||
-rw-r--r-- | plugins/rdp/rdp_file.c | 299 | ||||
-rw-r--r-- | plugins/rdp/rdp_file.h | 48 | ||||
-rw-r--r-- | plugins/rdp/rdp_graphics.c | 163 | ||||
-rw-r--r-- | plugins/rdp/rdp_graphics.h | 40 | ||||
-rw-r--r-- | plugins/rdp/rdp_monitor.c | 231 | ||||
-rw-r--r-- | plugins/rdp/rdp_monitor.h | 45 | ||||
-rw-r--r-- | plugins/rdp/rdp_plugin.c | 3363 | ||||
-rw-r--r-- | plugins/rdp/rdp_plugin.h | 400 | ||||
-rw-r--r-- | plugins/rdp/rdp_settings.c | 759 | ||||
-rw-r--r-- | plugins/rdp/rdp_settings.h | 49 | ||||
-rw-r--r-- | plugins/rdp/scalable/emblems/org.remmina.Remmina-rdp-ssh-symbolic.svg | 89 | ||||
-rw-r--r-- | plugins/rdp/scalable/emblems/org.remmina.Remmina-rdp-symbolic.svg | 112 |
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> |