diff options
author | Antenore Gatta <antenore@simbiosi.org> | 2018-03-24 00:19:39 +0300 |
---|---|---|
committer | Antenore Gatta <antenore@simbiosi.org> | 2018-03-24 00:19:39 +0300 |
commit | 9bdd19746a0d62fc76e906d561f59f1855c1a54f (patch) | |
tree | bd50983a12e2461780c36d63110f2eefacd25be8 /plugins/rdp | |
parent | da29a0661b5734c48490f567a8464fff37acbc5a (diff) |
Refactoring - Moving all plugins inside the plugins directory
Diffstat (limited to 'plugins/rdp')
-rw-r--r-- | plugins/rdp/16x16/emblems/remmina-rdp-ssh.png | bin | 0 -> 803 bytes | |||
-rw-r--r-- | plugins/rdp/16x16/emblems/remmina-rdp.png | bin | 0 -> 747 bytes | |||
-rw-r--r-- | plugins/rdp/22x22/emblems/remmina-rdp-ssh.png | bin | 0 -> 1273 bytes | |||
-rw-r--r-- | plugins/rdp/22x22/emblems/remmina-rdp.png | bin | 0 -> 1181 bytes | |||
-rw-r--r-- | plugins/rdp/CMakeLists.txt | 89 | ||||
-rw-r--r-- | plugins/rdp/rdp_channels.c | 94 | ||||
-rw-r--r-- | plugins/rdp/rdp_channels.h | 55 | ||||
-rw-r--r-- | plugins/rdp/rdp_cliprdr.c | 821 | ||||
-rw-r--r-- | plugins/rdp/rdp_cliprdr.h | 50 | ||||
-rw-r--r-- | plugins/rdp/rdp_event.c | 1182 | ||||
-rw-r--r-- | plugins/rdp/rdp_event.h | 52 | ||||
-rw-r--r-- | plugins/rdp/rdp_file.c | 307 | ||||
-rw-r--r-- | plugins/rdp/rdp_file.h | 46 | ||||
-rw-r--r-- | plugins/rdp/rdp_graphics.c | 435 | ||||
-rw-r--r-- | plugins/rdp/rdp_graphics.h | 41 | ||||
-rw-r--r-- | plugins/rdp/rdp_plugin.c | 1553 | ||||
-rw-r--r-- | plugins/rdp/rdp_plugin.h | 309 | ||||
-rw-r--r-- | plugins/rdp/rdp_settings.c | 640 | ||||
-rw-r--r-- | plugins/rdp/rdp_settings.h | 47 |
19 files changed, 5721 insertions, 0 deletions
diff --git a/plugins/rdp/16x16/emblems/remmina-rdp-ssh.png b/plugins/rdp/16x16/emblems/remmina-rdp-ssh.png Binary files differnew file mode 100644 index 000000000..9defcd6f6 --- /dev/null +++ b/plugins/rdp/16x16/emblems/remmina-rdp-ssh.png diff --git a/plugins/rdp/16x16/emblems/remmina-rdp.png b/plugins/rdp/16x16/emblems/remmina-rdp.png Binary files differnew file mode 100644 index 000000000..2d7724312 --- /dev/null +++ b/plugins/rdp/16x16/emblems/remmina-rdp.png diff --git a/plugins/rdp/22x22/emblems/remmina-rdp-ssh.png b/plugins/rdp/22x22/emblems/remmina-rdp-ssh.png Binary files differnew file mode 100644 index 000000000..e977bb3b7 --- /dev/null +++ b/plugins/rdp/22x22/emblems/remmina-rdp-ssh.png diff --git a/plugins/rdp/22x22/emblems/remmina-rdp.png b/plugins/rdp/22x22/emblems/remmina-rdp.png Binary files differnew file mode 100644 index 000000000..feb093c09 --- /dev/null +++ b/plugins/rdp/22x22/emblems/remmina-rdp.png diff --git a/plugins/rdp/CMakeLists.txt b/plugins/rdp/CMakeLists.txt new file mode 100644 index 000000000..c03054444 --- /dev/null +++ b/plugins/rdp/CMakeLists.txt @@ -0,0 +1,89 @@ +# 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-2017 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_package(X11) + +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_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}) + +if(WITH_EXAMPLES) + message(STATUS "Enabling examples and test plugins.") + add_definitions(-DWITH_EXAMPLES) +endif() + +option(WITH_GFX_H264 "Enable support for H264 modes in GFX" ON) +if (WITH_GFX_H264) + add_definitions(-DWITH_GFX_H264) +endif() + + +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_INCLUDE_DIRS} ${X11_INCLUDE_DIR}) +target_link_libraries(remmina-plugin-rdp + ${REMMINA_COMMON_LIBRARIES} ${FREERDP_LIBRARIES} ${X11_LIBRARIES}) + +install(TARGETS remmina-plugin-rdp DESTINATION ${REMMINA_PLUGINDIR}) + +install(FILES + 16x16/emblems/remmina-rdp-ssh.png + 16x16/emblems/remmina-rdp.png + DESTINATION ${APPICON16_EMBLEMS_DIR}) +install(FILES + 22x22/emblems/remmina-rdp-ssh.png + 22x22/emblems/remmina-rdp.png + DESTINATION ${APPICON22_EMBLEMS_DIR}) diff --git a/plugins/rdp/rdp_channels.c b/plugins/rdp/rdp_channels.c new file mode 100644 index 000000000..eb8d3723f --- /dev/null +++ b/plugins/rdp/rdp_channels.c @@ -0,0 +1,94 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2012-2012 Jean-Louis Dupond + * Copyright (C) 2016-2018 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(rdpContext* context, 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 (rfi->settings->SoftwareGdi) + gdi_graphics_pipeline_init(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 remmina_connection_window to unlock dynres capability + remmina_plugin_service->protocol_plugin_emit_signal(rfi->protocol_widget, "unlock-dynres"); + // 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_service->log_printf("Channel %s has been opened\n", e->name); +} + +void remmina_rdp_OnChannelDisconnectedEventHandler(rdpContext* context, ChannelConnectedEventArgs* e) +{ + TRACE_CALL(__func__); + rfContext* rfi = (rfContext*)context; + + if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0) { + if (rfi->settings->SoftwareGdi) + gdi_graphics_pipeline_uninit(context->gdi, (RdpgfxClientContext*) e->pInterface); + } + remmina_plugin_service->log_printf("Channel %s has been closed\n", e->name); + +} diff --git a/plugins/rdp/rdp_channels.h b/plugins/rdp/rdp_channels.h new file mode 100644 index 000000000..193706ed9 --- /dev/null +++ b/plugins/rdp/rdp_channels.h @@ -0,0 +1,55 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2012-2012 Jean-Louis Dupond + * Copyright (C) 2017 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 <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 + +void remmina_rdp_OnChannelConnectedEventHandler(rdpContext* context, ChannelConnectedEventArgs* e); +void remmina_rdp_OnChannelDisconnectedEventHandler(rdpContext* context, ChannelConnectedEventArgs* e); + + +G_END_DECLS + diff --git a/plugins/rdp/rdp_cliprdr.c b/plugins/rdp/rdp_cliprdr.c new file mode 100644 index 000000000..b564bd6fd --- /dev/null +++ b/plugins/rdp/rdp_cliprdr.c @@ -0,0 +1,821 @@ +/* + * 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-2017 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 2 + +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; + } + g_free(name); + return rc; +} + +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; +} + +int remmina_rdp_cliprdr_server_file_contents_request(CliprdrClientContext* context, CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) +{ + TRACE_CALL(__func__); + return -1; +} +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, 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, CLIPRDR_CAPABILITIES* capabilities) +{ + TRACE_CALL(__func__); + return CHANNEL_RC_OK; +} + + +static UINT remmina_rdp_cliprdr_server_format_list(CliprdrClientContext* context, 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 appropiate + * functions to request server data */ + + RemminaPluginRdpUiObject* ui; + RemminaProtocolWidget* gp; + rfClipboard* clipboard; + CLIPRDR_FORMAT* format; + CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse; + + int i; + + clipboard = (rfClipboard*)context->custom; + gp = clipboard->rfi->protocol_widget; + GtkTargetList* list = gtk_target_list_new(NULL, 0); + + for (i = 0; i < formatList->numFormats; i++) { + format = &formatList->formats[i]; + if (format->formatId == CF_UNICODETEXT) { + GdkAtom atom = gdk_atom_intern("UTF8_STRING", TRUE); + gtk_target_list_add(list, atom, 0, CF_UNICODETEXT); + }else if (format->formatId == CF_TEXT) { + GdkAtom atom = gdk_atom_intern("TEXT", TRUE); + gtk_target_list_add(list, atom, 0, CF_TEXT); + }else if (format->formatId == CF_DIB) { + GdkAtom atom = gdk_atom_intern("image/bmp", TRUE); + gtk_target_list_add(list, atom, 0, CF_DIB); + }else if (format->formatId == CF_DIBV5) { + GdkAtom atom = gdk_atom_intern("image/bmp", TRUE); + gtk_target_list_add(list, atom, 0, CF_DIBV5); + }else if (format->formatId == CB_FORMAT_JPEG) { + GdkAtom atom = gdk_atom_intern("image/jpeg", TRUE); + gtk_target_list_add(list, atom, 0, CB_FORMAT_JPEG); + }else if (format->formatId == CB_FORMAT_PNG) { + GdkAtom atom = gdk_atom_intern("image/png", TRUE); + gtk_target_list_add(list, atom, 0, CB_FORMAT_PNG); + }else if (format->formatId == CB_FORMAT_HTML) { + GdkAtom atom = gdk_atom_intern("text/html", TRUE); + gtk_target_list_add(list, atom, 0, CB_FORMAT_HTML); + } + } + + /* Now we tell GTK to change the local keyboard 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 */ + + 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_sync_retint(gp, ui); + + /* Send FormatListResponse to server */ + + formatListResponse.msgType = CB_FORMAT_LIST_RESPONSE; + formatListResponse.msgFlags = CB_RESPONSE_OK; // Can be CB_RESPONSE_FAIL in case of error + formatListResponse.dataLen = 0; + + return clipboard->context->ClientFormatListResponse(clipboard->context, &formatListResponse); + +} + +static UINT remmina_rdp_cliprdr_server_format_list_response(CliprdrClientContext* context, CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse) +{ + TRACE_CALL(__func__); + return CHANNEL_RC_OK; +} + + +static UINT remmina_rdp_cliprdr_server_format_data_request(CliprdrClientContext* context, 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; +} + +static UINT remmina_rdp_cliprdr_server_format_data_response(CliprdrClientContext* context, CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse) +{ + TRACE_CALL(__func__); + + UINT8* data; + size_t size; + rfContext* rfi; + RemminaProtocolWidget* gp; + rfClipboard* clipboard; + GdkPixbufLoader *pixbuf; + gpointer output = NULL; + RemminaPluginRdpUiObject *ui; + + clipboard = (rfClipboard*)context->custom; + gp = clipboard->rfi->protocol_widget; + rfi = GET_PLUGIN_DATA(gp); + + data = formatDataResponse->requestedFormatData; + size = formatDataResponse->dataLen; + + // formatDataResponse->requestedFormatData is allocated + // by freerdp and freed after returning from this callback function. + // So we must make a copy if we need to preserve it + + if (size > 0) { + switch (rfi->clipboard.format) { + case CF_UNICODETEXT: + { + size = ConvertFromUnicode(CP_UTF8, 0, (WCHAR*)data, size / 2, (CHAR**)&output, 0, NULL, NULL); + 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); + + pixbuf = gdk_pixbuf_loader_new(); + perr = NULL; + if ( !gdk_pixbuf_loader_write(pixbuf, data, size, &perr) ) { + remmina_plugin_service->log_printf("[RDP] rdp_cliprdr: gdk_pixbuf_loader_write() returned error %s\n", perr->message); + }else { + if ( !gdk_pixbuf_loader_close(pixbuf, &perr) ) { + remmina_plugin_service->log_printf("[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(pixbuf)); + } + g_object_unref(pixbuf); + break; + } + + case CB_FORMAT_PNG: + case CB_FORMAT_JPEG: + { + pixbuf = gdk_pixbuf_loader_new(); + gdk_pixbuf_loader_write(pixbuf, data, size, NULL); + output = g_object_ref(gdk_pixbuf_loader_get_pixbuf(pixbuf)); + gdk_pixbuf_loader_close(pixbuf, NULL); + g_object_unref(pixbuf); + break; + } + } + } + + pthread_mutex_lock(&clipboard->transfer_clip_mutex); + pthread_cond_signal(&clipboard->transfer_clip_cond); + if ( clipboard->srv_clip_data_wait == SCDW_BUSY_WAIT ) { + clipboard->srv_data = output; + }else { + // Clipboard data arrived from server when we are not busywaiting. + // Just put it on the local clipboard + + ui = g_new0(RemminaPluginRdpUiObject, 1); + ui->type = REMMINA_RDP_UI_CLIPBOARD; + ui->clipboard.clipboard = clipboard; + ui->clipboard.type = REMMINA_RDP_UI_CLIPBOARD_SET_CONTENT; + ui->clipboard.data = output; + ui->clipboard.format = clipboard->format; + remmina_rdp_event_queue_ui_sync_retint(gp, ui); + + 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 press "Paste" on the client side. + * We ask to the server the data we need */ + + GdkAtom target; + 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; + + clipboard = &(rfi->clipboard); + if ( clipboard->srv_clip_data_wait != SCDW_NONE ) { + remmina_plugin_service->log_printf("[RDP] Cannot paste now, I'm transferring clipboard data from server. Try again later\n"); + return; + } + + target = gtk_selection_data_get_target(selection_data); + // clipboard->format = remmina_rdp_cliprdr_get_format_from_gdkatom(target); + clipboard->format = info; + + /* Request Clipboard content from the server, the request is async */ + + 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; + + 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 clibpoard data for CLIPBOARD_TRANSFER_WAIT_TIME seconds */ + gettimeofday(&tv, NULL); + to.tv_sec = tv.tv_sec + CLIPBOARD_TRANSFER_WAIT_TIME; + to.tv_nsec = tv.tv_usec * 1000; + rc = pthread_cond_timedwait(&clipboard->transfer_clip_cond, &clipboard->transfer_clip_mutex, &to); + + if ( rc == 0 ) { + /* Data has arrived without timeout */ + if (clipboard->srv_data != NULL) { + 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); + g_object_unref(clipboard->srv_data); + }else { + gtk_selection_data_set_text(selection_data, clipboard->srv_data, -1); + free(clipboard->srv_data); + } + } + clipboard->srv_clip_data_wait = SCDW_NONE; + } else { + clipboard->srv_clip_data_wait = SCDW_ASYNCWAIT; + if ( rc == ETIMEDOUT ) { + remmina_plugin_service->log_printf("[RDP] Clipboard data has not been transferred from the server in %d seconds. Try to paste later.\n", + CLIPBOARD_TRANSFER_WAIT_TIME); + }else { + remmina_plugin_service->log_printf("[RDP] internal error: pthread_cond_timedwait() returned %d\n", rc); + clipboard->srv_clip_data_wait = SCDW_NONE; + } + } + pthread_mutex_unlock(&clipboard->transfer_clip_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; + rfClipboard* clipboard; + 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; + + clipboard = &(rfi->clipboard); + formats = NULL; + + retp = NULL; + + gtkClipboard = gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD); + if (gtkClipboard) { + result = gtk_clipboard_wait_for_targets(gtkClipboard, &targets, &loccount); + } + + 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 ) { + 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); + + retp->pFormatList.msgFlags = CB_RESPONSE_OK; + + 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; + rfClipboard* clipboard; + UINT8* inbuf = NULL; + UINT8* outbuf = NULL; + GdkPixbuf *image = NULL; + int size = 0; + rfContext* rfi = GET_PLUGIN_DATA(gp); + RemminaPluginRdpEvent rdp_event = { 0 }; + CLIPRDR_FORMAT_DATA_RESPONSE* pFormatDataResponse; + + clipboard = ui->clipboard.clipboard; + 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, &size); + break; + } + case CF_UNICODETEXT: + { + size = strlen((char*)inbuf); + inbuf = lf2crlf(inbuf, &size); + size = (ConvertToUnicode(CP_UTF8, 0, (CHAR*)inbuf, -1, (WCHAR**)&outbuf, 0) ) * sizeof(WCHAR); + 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; + } + } + } + + pFormatDataResponse = (CLIPRDR_FORMAT_DATA_RESPONSE*)malloc(sizeof(CLIPRDR_FORMAT_DATA_RESPONSE)); + if (!pFormatDataResponse) { + if (outbuf) free(outbuf); + return; + } + + ZeroMemory(pFormatDataResponse, sizeof(CLIPRDR_FORMAT_DATA_RESPONSE)); + rdp_event.type = REMMINA_RDP_EVENT_TYPE_CLIPBOARD_SEND_CLIENT_FORMAT_DATA_RESPONSE; + rdp_event.clipboard_formatdataresponse.pFormatDataResponse = pFormatDataResponse; + pFormatDataResponse->msgFlags = CB_RESPONSE_OK; + pFormatDataResponse->dataLen = size; + pFormatDataResponse->requestedFormatData = outbuf; + remmina_rdp_event_event_push(gp, &rdp_event); + +} + +void remmina_rdp_cliprdr_set_clipboard_content(RemminaProtocolWidget* gp, RemminaPluginRdpUiObject* ui) +{ + TRACE_CALL(__func__); + GtkClipboard* gtkClipboard; + rfContext* rfi = GET_PLUGIN_DATA(gp); + + gtkClipboard = gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD); + if (ui->clipboard.format == CB_FORMAT_PNG || ui->clipboard.format == CF_DIB || ui->clipboard.format == CF_DIBV5 || ui->clipboard.format == CB_FORMAT_JPEG) { + gtk_clipboard_set_image( gtkClipboard, ui->clipboard.data ); + g_object_unref(ui->clipboard.data); + }else { + gtk_clipboard_set_text( gtkClipboard, ui->clipboard.data, -1 ); + free(ui->clipboard.data); + } + +} + +void remmina_rdp_cliprdr_set_clipboard_data(RemminaProtocolWidget* gp, RemminaPluginRdpUiObject* ui) +{ + TRACE_CALL(__func__); + GtkClipboard* gtkClipboard; + GtkTargetEntry* targets; + gint n_targets; + rfContext* rfi = GET_PLUGIN_DATA(gp); + rfClipboard* clipboard; + + clipboard = ui->clipboard.clipboard; + targets = gtk_target_table_new_from_list(ui->clipboard.targetlist, &n_targets); + gtkClipboard = gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD); + if (gtkClipboard && targets) { + 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; + + 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; + + case REMMINA_RDP_UI_CLIPBOARD_SET_CONTENT: + remmina_rdp_cliprdr_set_clipboard_content(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__); + // Future: deinitialize rfi->clipboard +} + + +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; + + 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 000000000..d40bff98c --- /dev/null +++ b/plugins/rdp/rdp_cliprdr.h @@ -0,0 +1,50 @@ +/* + * 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-2017 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* rfc, 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); diff --git a/plugins/rdp/rdp_event.c b/plugins/rdp/rdp_event.c new file mode 100644 index 000000000..48fe41c8e --- /dev/null +++ b/plugins/rdp/rdp_event.c @@ -0,0 +1,1182 @@ +/* + * 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-2017 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_cliprdr.h" +#include "rdp_settings.h" +#include <gdk/gdkkeysyms.h> +#include <cairo/cairo-xlib.h> +#include <freerdp/locale/keyboard.h> +#include <execinfo.h> + +static void 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; + + if (!rfi || !rfi->connected || rfi->is_reconnecting) + return; + + input = rfi->instance->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); +} + +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) { + event = g_memdup(e, sizeof(RemminaPluginRdpEvent)); + 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) { + 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_region(RemminaProtocolWidget* gp, RemminaPluginRdpUiObject* ui) +{ + TRACE_CALL(__func__); + rfContext* rfi = GET_PLUGIN_DATA(gp); + gint x, y, w, h; + + x = ui->region.x; + y = ui->region.y; + w = ui->region.width; + h = ui->region.height; + + 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); +} + +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; + RemminaFile* remminafile; + rfContext* rfi = GET_PLUGIN_DATA(gp); + + remminafile = remmina_plugin_service->protocol_plugin_get_file(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 in progress. 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; + + GtkAllocation a; + gtk_widget_get_allocation(GTK_WIDGET(gp), &a); + + if (rfi->scale == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED) + cairo_scale(context, rfi->scale_x, rfi->scale_y); + + cairo_set_source_surface(context, rfi->surface, 0, 0); + + 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; + RemminaFile* remminafile; + gint desktopOrientation, desktopScaleFactor, deviceScaleFactor; + + if (!rfi || !rfi->connected || rfi->is_reconnecting) + return FALSE; + + if (rfi->scale != REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES) + return FALSE; + + rfi->delayed_monitor_layout_handler = 0; + gint gpwidth, gpheight, prevwidth, prevheight; + + 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); + remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); + + if ((gpwidth != prevwidth || gpheight != prevheight) && + gpwidth >= 200 && gpwidth < 8192 && + gpheight >= 200 && gpheight < 8192 + ) { + rdp_event.type = REMMINA_RDP_EVENT_TYPE_SEND_MONITOR_LAYOUT; + 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); + remmina_plugin_service->file_set_int(remminafile, "dynamic_resolution_width", gpwidth); + remmina_plugin_service->file_set_int(remminafile, "dynamic_resolution_height", gpheight); + } + } + + 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; + } +} + +static gboolean remmina_rdp_event_on_motion(GtkWidget* widget, GdkEventMotion* event, RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + RemminaPluginRdpEvent rdp_event = { 0 }; + + 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); + 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 }; + + /* We bypass 2button-press and 3button-press events */ + if ((event->type != GDK_BUTTON_PRESS) && (event->type != GDK_BUTTON_RELEASE)) + return TRUE; + + flag = 0; + + switch (event->button) { + case 1: + flag |= PTR_FLAGS_BUTTON1; + break; + case 2: + flag |= PTR_FLAGS_BUTTON3; + break; + case 3: + flag |= PTR_FLAGS_BUTTON2; + 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 }; + + flag = 0; + rdp_event.type = REMMINA_RDP_EVENT_TYPE_MOUSE; + + switch (event->direction) { + case GDK_SCROLL_UP: + flag = PTR_FLAGS_WHEEL | 0x0078; + break; + + case GDK_SCROLL_DOWN: + flag = PTR_FLAGS_WHEEL | PTR_FLAGS_WHEEL_NEGATIVE | 0x0088; + break; + +#ifdef GDK_SCROLL_SMOOTH + case GDK_SCROLL_SMOOTH: + if (event->delta_y < 0) + flag = PTR_FLAGS_WHEEL | 0x0078; + if (event->delta_y > 0) + flag = PTR_FLAGS_WHEEL | PTR_FLAGS_WHEEL_NEGATIVE | 0x0088; + if (!flag) + return FALSE; + 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 gboolean remmina_rdp_event_on_key(GtkWidget* widget, GdkEventKey* event, RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + GdkDisplay* display; + guint32 unicode_keyval; + rfContext* rfi = GET_PLUGIN_DATA(gp); + RemminaPluginRdpEvent rdp_event; + DWORD scancode = 0; + + if (!rfi || !rfi->connected || rfi->is_reconnecting) + 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; + + 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; + remmina_rdp_event_event_push(gp, &rdp_event); + rdp_event.key_event.key_code = 0x45; + rdp_event.key_event.up = False; + remmina_rdp_event_event_push(gp, &rdp_event); + rdp_event.key_event.key_code = 0x1D; + rdp_event.key_event.up = True; + remmina_rdp_event_event_push(gp, &rdp_event); + rdp_event.key_event.key_code = 0x45; + rdp_event.key_event.up = True; + remmina_rdp_event_event_push(gp, &rdp_event); + break; + + default: + if (!rfi->use_client_keymap) { + 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; + if (rdp_event.key_event.key_code) { + remmina_rdp_event_event_push(gp, &rdp_event); + keypress_list_add(gp, rdp_event); + } + } else { + display = gtk_widget_get_display(widget); + 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, win) 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/ins/del) keycode + * - All key pressed while CTRL or ALT or WIN 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 || // space bar + 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; + 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; + 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; + + /* Usually "owner-change" is fired when a user pres "COPY" on the client + * OR when this plugin calls gtk_clipboard_set_with_owner() + * after receivina a RDP server format list in remmina_rdp_cliprdr_server_format_list() + * In the latter case, we must ignore owner change */ + + if (gtk_clipboard_get_owner(gtkClipboard) != (GObject*)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); + } + return TRUE; +} + +void remmina_rdp_event_init(RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + gchar* s; + gint flags; + rfContext* rfi = GET_PLUGIN_DATA(gp); + GtkClipboard* clipboard; + + if (!rfi) return; + + 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); + 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); + + 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); + + RemminaFile* remminafile = remmina_plugin_service->protocol_plugin_get_file(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(); + rfi->bpp = gdk_visual_get_best_depth(); +} + + + +void remmina_rdp_event_free_event(RemminaProtocolWidget* gp, RemminaPluginRdpUiObject* obj) +{ + TRACE_CALL(__func__); + rfContext* rfi = GET_PLUGIN_DATA(gp); + + switch (obj->type) { + case REMMINA_RDP_UI_RFX: + rfx_message_free(rfi->rfx_context, obj->rfx.message); + break; + + 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_destroy(rfi->surface); + rfi->surface = NULL; + } + + g_hash_table_destroy(rfi->object_table); + + g_array_free(rfi->pressed_keys, TRUE); + 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; + + gdi = ((rdpContext *)rfi)->gdi; + if (!rfi || !gdi) + return; + + if (rfi->surface) { + cairo_surface_destroy(rfi->surface); + rfi->surface = NULL; + } + stride = cairo_format_stride_for_width(rfi->cairo_format, rfi->width); + rfi->surface = cairo_image_surface_create_for_data((unsigned char*)gdi->primary_buffer, rfi->cairo_format, rfi->width, rfi->height, stride); +} + +void remmina_rdp_event_update_scale(RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + gint width, height; + RemminaFile* remminafile; + rdpGdi* gdi; + rfContext* rfi = GET_PLUGIN_DATA(gp); + + remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); + width = remmina_plugin_service->protocol_plugin_get_width(gp); + height = remmina_plugin_service->protocol_plugin_get_height(gp); + + 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 && (width != cairo_image_surface_get_width(rfi->surface) || + height != cairo_image_surface_get_height(rfi->surface) )) { + /* Destroys and recreate rfi->surface with new width and height, + * calls gdi_resize and save new gdi->primary buffer pointer */ + if (rfi->surface) { + cairo_surface_destroy(rfi->surface); + rfi->surface = NULL; + } + rfi->width = width; + rfi->height = height; + gdi = ((rdpContext*)rfi)->gdi; + gdi_resize(gdi, width, height); + rfi->primary_buffer = gdi->primary_buffer; + remmina_rdp_event_create_cairo_surface(rfi); + } + + 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_emit_signal(gp, "update-align"); +} + +static void remmina_rdp_event_connected(RemminaProtocolWidget* gp, RemminaPluginRdpUiObject* ui) +{ + TRACE_CALL(__func__); + rfContext* rfi = GET_PLUGIN_DATA(gp); + + remmina_plugin_service->protocol_plugin_emit_signal(gp, "connect"); + gtk_widget_realize(rfi->drawing_area); + + remmina_rdp_event_create_cairo_surface(rfi); + gtk_widget_queue_draw_area(rfi->drawing_area, 0, 0, rfi->width, rfi->height); + + remmina_rdp_event_update_scale(gp); +} + +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)) < 0) { + 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)); + pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, pointer->width, pointer->height); + 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__); + remmina_rdp_event_release_all_keys(gp); +} + +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; + } +} + +static void remmina_rdp_event_process_ui_event(RemminaProtocolWidget* gp, RemminaPluginRdpUiObject* ui) +{ + TRACE_CALL(__func__); + switch (ui->type) { + case REMMINA_RDP_UI_UPDATE_REGION: + remmina_rdp_event_update_region(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->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 000000000..f671ef380 --- /dev/null +++ b/plugins/rdp/rdp_event.h @@ -0,0 +1,52 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2010-2011 Vic Lee + * Copyright (C) 2017 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 + +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); + +G_END_DECLS + diff --git a/plugins/rdp/rdp_file.c b/plugins/rdp/rdp_file.c new file mode 100644 index 000000000..8f85d4934 --- /dev/null +++ b/plugins/rdp/rdp_file.c @@ -0,0 +1,307 @@ +/* + * 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-2017 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 "common/remmina_plugin.h" +#include "rdp_plugin.h" +#include "rdp_file.h" + +gboolean remmina_rdp_file_import_test(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, "microphone") == 0) { + remmina_plugin_service->file_set_int(remminafile, "microphone", (atoi(value) == 1)); + } 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_string(remminafile, "gatewayusagemethod", value); + }else if (g_strcmp0(key, "gatewaycredentialssource") == 0) { + remmina_plugin_service->file_set_string(remminafile, "gatewaycredentialssource", value); + }else if (g_strcmp0(key, "gatewayprofileusagemethod") == 0) { + remmina_plugin_service->file_set_string(remminafile, "gatewayprofileusagemethod", 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(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(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"); + fprintf(fp, "microphone:i:%i\r\n", remmina_plugin_service->file_get_int(remminafile, "microphone", FALSE) ? 1 : 0); + 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 : ""); + fprintf(fp, "gatewayhostname:s:\r\n"); + 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(RemminaFile* remminafile, const gchar* to_file) +{ + TRACE_CALL(__func__); + FILE* fp; + gchar* p; + gboolean ret; + + p = strrchr(to_file, '.'); + + if (p && (g_strcmp0(p + 1, "rdp") == 0 || g_strcmp0(p + 1, "RDP") == 0)) { + p = g_strdup(to_file); + }else { + p = g_strdup_printf("%s.rdp", to_file); + } + + fp = g_fopen(p, "w+"); + + if (fp == NULL) { + g_print("Failed to export %s\n", p); + g_free(p); + return FALSE; + } + + g_free(p); + 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 000000000..9ca04f024 --- /dev/null +++ b/plugins/rdp/rdp_file.h @@ -0,0 +1,46 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2010-2011 Vic Lee + * Copyright (C) 2017 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 + +G_BEGIN_DECLS + +gboolean remmina_rdp_file_import_test(const gchar* from_file); +RemminaFile* remmina_rdp_file_import(const gchar* from_file); +gboolean remmina_rdp_file_export_test(RemminaFile* remminafile); +gboolean remmina_rdp_file_export(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 000000000..06404a611 --- /dev/null +++ b/plugins/rdp/rdp_graphics.c @@ -0,0 +1,435 @@ +/* + * 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-2017 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 + +/* Bitmap Class */ + +BOOL rf_Bitmap_New(rdpContext* context, rdpBitmap* bitmap) +{ + TRACE_CALL(__func__); +#ifdef RF_BITMAP + UINT8* data; + Pixmap pixmap; + XImage* image; + rfContext* rfi = (rfContext*)context; + + XSetFunction(rfi->display, rfi->gc, GXcopy); + pixmap = XCreatePixmap(rfi->display, rfi->drawable, bitmap->width, bitmap->height, rfi->depth); + + if (bitmap->data != NULL) { + data = freerdp_image_convert(bitmap->data, NULL, + bitmap->width, bitmap->height, rfi->srcBpp, rfi->bpp, rfi->clrconv); + + if (bitmap->ephemeral != TRUE) { + image = XCreateImage(rfi->display, rfi->visual, rfi->depth, + ZPixmap, 0, (char*)data, bitmap->width, bitmap->height, rfi->scanline_pad, 0); + + XPutImage(rfi->display, pixmap, rfi->gc, image, 0, 0, 0, 0, bitmap->width, bitmap->height); + XFree(image); + + if (data != bitmap->data) && (data != NULL) + free(data); + }else { + if (data != bitmap->data) && (data != NULL) + free(bitmap->data); + + bitmap->data = data; + } + } + + ((rfBitmap*)bitmap)->pixmap = pixmap; +#endif + return TRUE; +} + +void rf_Bitmap_Free(rdpContext* context, rdpBitmap* bitmap) +{ + TRACE_CALL(__func__); +#ifdef RF_BITMAP + rfContext* rfi = (rfContext*)context; + + printf("rf_Bitmap_Free\n"); + + if (((rfBitmap*)bitmap)->pixmap != 0) + XFreePixmap(rfi->display, ((rfBitmap*)bitmap)->pixmap); +#endif +} + +BOOL rf_Bitmap_Paint(rdpContext* context, rdpBitmap* bitmap) +{ + TRACE_CALL(__func__); +#ifdef RF_BITMAP + XImage* image; + int width, height; + rfContext* rfi = (rfContext*)context; + + printf("rf_Bitmap_Paint\n"); + + width = bitmap->right - bitmap->left + 1; + height = bitmap->bottom - bitmap->top + 1; + + XSetFunction(rfi->display, rfi->gc, GXcopy); + + image = XCreateImage(rfi->display, rfi->visual, rfi->depth, + ZPixmap, 0, (char*)bitmap->data, bitmap->width, bitmap->height, rfi->scanline_pad, 0); + + XPutImage(rfi->display, rfi->primary, rfi->gc, + image, 0, 0, bitmap->left, bitmap->top, width, height); + + XFree(image); + + //XCopyArea(rfi->display, rfi->primary, rfi->drawable, rfi->gc, + // bitmap->left, bitmap->top, width, height, bitmap->left, bitmap->top); + + //gdi_InvalidateRegion(rfi->hdc, bitmap->left, bitmap->top, width, height); +#endif + return FALSE; +} + +BOOL rf_Bitmap_Decompress(rdpContext* context, rdpBitmap* bitmap, + const BYTE* data, UINT32 width, UINT32 height, UINT32 bpp, UINT32 length, BOOL compressed, UINT32 codec_id) +{ + TRACE_CALL(__func__); +#ifdef RF_BITMAP + UINT16 size; + + printf("rf_Bitmap_Decompress\n"); + + size = width * height * (bpp + 7) / 8; + + if (bitmap->data == NULL) + bitmap->data = (UINT8*)xmalloc(size); + else + bitmap->data = (UINT8*)xrealloc(bitmap->data, size); + + if (compressed) { + BOOL status; + + status = bitmap_decompress(data, bitmap->data, width, height, length, bpp, bpp); + + if (status != TRUE) { + printf("Bitmap Decompression Failed\n"); + } + }else { + freerdp_image_flip(data, bitmap->data, width, height, bpp); + } + + bitmap->compressed = FALSE; + bitmap->length = size; + bitmap->bpp = bpp; +#endif + return TRUE; +} + +BOOL rf_Bitmap_SetSurface(rdpContext* context, rdpBitmap* bitmap, BOOL primary) +{ + TRACE_CALL(__func__); +#ifdef RF_BITMAP + rfContext* rfi = (rfContext*)context; + + if (primary) + rfi->drawing = rfi->primary; + else + rfi->drawing = ((rfBitmap*)bitmap)->pixmap; +#endif + return TRUE; +} + +/* Pointer Class */ + +BOOL rf_Pointer_New(rdpContext* context, rdpPointer* pointer) +{ + TRACE_CALL(__func__); + RemminaPluginRdpUiObject* ui; + rfContext* rfi = (rfContext*)context; + + if ((pointer->andMaskData != 0) && (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; +} + +void rf_Pointer_Free(rdpContext* context, rdpPointer* pointer) +{ + TRACE_CALL(__func__); + RemminaPluginRdpUiObject* ui; + rfContext* rfi = (rfContext*)context; + +#if GTK_VERSION == 2 + if (((rfPointer*)pointer)->cursor != NULL) +#else + if (G_IS_OBJECT(((rfPointer*)pointer)->cursor)) +#endif + { + 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); + } +} + +BOOL rf_Pointer_Set(rdpContext* context, const 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; + +} + +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; +} + +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; +} + +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; +} + +/* Glyph Class */ + +BOOL rf_Glyph_New(rdpContext* context, const rdpGlyph* glyph) +{ + TRACE_CALL(__func__); +#ifdef RF_GLYPH + int scanline; + XImage* image; + rfContext* rfi; + rfGlyph* rf_glyph; + + rf_glyph = (rfGlyph*)glyph; + rfi = (rfContext*)context; + + scanline = (glyph->cx + 7) / 8; + + rf_glyph->pixmap = XCreatePixmap(rfi->display, rfi->drawing, glyph->cx, glyph->cy, 1); + + image = XCreateImage(rfi->display, rfi->visual, 1, + ZPixmap, 0, (char*)glyph->aj, glyph->cx, glyph->cy, 8, scanline); + + image->byte_order = MSBFirst; + image->bitmap_bit_order = MSBFirst; + + XInitImage(image); + XPutImage(rfi->display, rf_glyph->pixmap, rfi->gc_mono, image, 0, 0, 0, 0, glyph->cx, glyph->cy); + XFree(image); +#endif + return TRUE; +} + +void rf_Glyph_Free(rdpContext* context, rdpGlyph* glyph) +{ + TRACE_CALL(__func__); +#ifdef RF_GLYPH + rfContext* rfi = (rfContext*)context; + + if (((rfGlyph*)glyph)->pixmap != 0) + XFreePixmap(rfi->display, ((rfGlyph*)glyph)->pixmap); +#endif +} + +static BOOL rf_Glyph_Draw(rdpContext* context, const rdpGlyph* glyph, UINT32 x, + UINT32 y, UINT32 w, UINT32 h, UINT32 sx, UINT32 sy, + BOOL fOpRedundant) +{ + TRACE_CALL(__func__); +#ifdef RF_GLYPH + rfGlyph* rf_glyph; + rfContext* rfi = (rfContext*)context; + + rf_glyph = (rfGlyph*)glyph; + + XSetStipple(rfi->display, rfi->gc, rf_glyph->pixmap); + XSetTSOrigin(rfi->display, rfi->gc, x, y); + XFillRectangle(rfi->display, rfi->drawing, rfi->gc, x, y, glyph->cx, glyph->cy); + XSetStipple(rfi->display, rfi->gc, rfi->bitmap_mono); +#endif + return TRUE; +} + +static BOOL rf_Glyph_BeginDraw(rdpContext* context, UINT32 x, UINT32 y, + UINT32 width, UINT32 height, UINT32 bgcolor, + UINT32 fgcolor, BOOL fOpRedundant) +{ + TRACE_CALL(__func__); +#ifdef RF_GLYPH + rfContext* rfi = (rfContext*)context; + + bgcolor = (rfi->clrconv->invert) ? + freerdp_color_convert_var_bgr(bgcolor, rfi->srcBpp, 32, rfi->clrconv) : + freerdp_color_convert_var_rgb(bgcolor, rfi->srcBpp, 32, rfi->clrconv); + + fgcolor = (rfi->clrconv->invert) ? + freerdp_color_convert_var_bgr(fgcolor, rfi->srcBpp, 32, rfi->clrconv) : + freerdp_color_convert_var_rgb(fgcolor, rfi->srcBpp, 32, rfi->clrconv); + + XSetFunction(rfi->display, rfi->gc, GXcopy); + XSetFillStyle(rfi->display, rfi->gc, FillSolid); + XSetForeground(rfi->display, rfi->gc, fgcolor); + XFillRectangle(rfi->display, rfi->drawing, rfi->gc, x, y, width, height); + + XSetForeground(rfi->display, rfi->gc, bgcolor); + XSetBackground(rfi->display, rfi->gc, fgcolor); + XSetFillStyle(rfi->display, rfi->gc, FillStippled); +#endif + return TRUE; +} + +static BOOL rf_Glyph_EndDraw(rdpContext* context, UINT32 x, UINT32 y, + UINT32 width, UINT32 height, + UINT32 bgcolor, UINT32 fgcolor) +{ + TRACE_CALL(__func__); +#ifdef RF_GLYPH + rfContext* rfi = (rfContext*)context; + + if (rfi->drawing == rfi->primary) { + //XCopyArea(rfi->display, rfi->primary, rfi->drawable, rfi->gc, x, y, width, height, x, y); + //gdi_InvalidateRegion(rfi->hdc, x, y, width, height); + } +#endif + return TRUE; +} + +/* Graphics Module */ + +void rf_register_graphics(rdpGraphics* graphics) +{ + TRACE_CALL(__func__); + rdpBitmap* bitmap; + rdpPointer* pointer; + rdpGlyph* glyph; + + bitmap = (rdpBitmap*)malloc(sizeof(rdpBitmap)); + ZeroMemory(bitmap, sizeof(rdpBitmap)); + bitmap->size = sizeof(rfBitmap); + + bitmap->New = rf_Bitmap_New; + bitmap->Free = rf_Bitmap_Free; + bitmap->Paint = rf_Bitmap_Paint; + bitmap->Decompress = rf_Bitmap_Decompress; + bitmap->SetSurface = rf_Bitmap_SetSurface; + + graphics_register_bitmap(graphics, bitmap); + free(bitmap); + + pointer = (rdpPointer*)malloc(sizeof(rdpPointer)); + ZeroMemory(pointer, sizeof(rdpPointer)); + + 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); + + free(pointer); + + glyph = (rdpGlyph*)malloc(sizeof(rdpGlyph)); + ZeroMemory(glyph, sizeof(rdpGlyph)); + + glyph->size = sizeof(rfGlyph); + + glyph->New = rf_Glyph_New; + glyph->Free = rf_Glyph_Free; + glyph->Draw = rf_Glyph_Draw; + glyph->BeginDraw = rf_Glyph_BeginDraw; + glyph->EndDraw = rf_Glyph_EndDraw; + + graphics_register_glyph(graphics, glyph); + + free(glyph); +} diff --git a/plugins/rdp/rdp_graphics.h b/plugins/rdp/rdp_graphics.h new file mode 100644 index 000000000..1476a3114 --- /dev/null +++ b/plugins/rdp/rdp_graphics.h @@ -0,0 +1,41 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2010-2011 Vic Lee + * Copyright (C) 2017 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_plugin.c b/plugins/rdp/rdp_plugin.c new file mode 100644 index 000000000..9a8bf92fe --- /dev/null +++ b/plugins/rdp/rdp_plugin.c @@ -0,0 +1,1553 @@ +/* + * 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-2017 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 "rdp_file.h" +#include "rdp_settings.h" +#include "rdp_cliprdr.h" +#include "rdp_channels.h" + +#include <errno.h> +#include <pthread.h> +#include <time.h> +#include <cairo/cairo-xlib.h> +#include <freerdp/freerdp.h> +#include <freerdp/constants.h> +#include <freerdp/client/cliprdr.h> +#include <freerdp/client/channels.h> +#include <freerdp/client/cmdline.h> +#include <freerdp/error.h> +#include <winpr/memory.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 + +/* Some string settings of freerdp are preallocated buffers of N bytes */ +#define FREERDP_CLIENTHOSTNAME_LEN 32 + +RemminaPluginService* remmina_plugin_service = NULL; +static char remmina_rdp_plugin_default_drive_name[] = "RemminaDisk"; + +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; + + if (rfi->event_queue == NULL) + return True; + + input = rfi->instance->input; + + while ((event = (RemminaPluginRdpEvent*)g_async_queue_try_pop(rfi->event_queue)) != NULL) { + switch (event->type) { + case REMMINA_RDP_EVENT_TYPE_SCANCODE: + 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: + rfi->clipboard.context->ClientFormatDataResponse(rfi->clipboard.context, event->clipboard_formatdataresponse.pFormatDataResponse); + if (event->clipboard_formatdataresponse.pFormatDataResponse->requestedFormatData) + free(event->clipboard_formatdataresponse.pFormatDataResponse->requestedFormatData); + free(event->clipboard_formatdataresponse.pFormatDataResponse); + break; + + case REMMINA_RDP_EVENT_TYPE_CLIPBOARD_SEND_CLIENT_FORMAT_DATA_REQUEST: + 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: + 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; + } + + 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; + const gchar *cert_hostport; + + rfContext* rfi = GET_PLUGIN_DATA(gp); + RemminaFile* remminafile; + + remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); + + 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); + + cert_host = host; + cert_port = port; + + if (remmina_plugin_service->file_get_int(remminafile, "ssh_enabled", FALSE)) { + cert_hostport = remmina_plugin_service->file_get_string(remminafile, "server"); + if ( cert_hostport ) { + remmina_plugin_service->get_server_port(cert_hostport, 3389, &cert_host, &cert_port); + } + + } + + if (!rfi->is_reconnecting) { + /* settings->CertificateName and settings->ServerHostname is created + * only on 1st connect, not on reconnections */ + + rfi->settings->ServerHostname = strdup(host); + + if (cert_port == 3389) { + rfi->settings->CertificateName = strdup(cert_host); + }else { + s = g_strdup_printf("%s:%d", cert_host, cert_port); + rfi->settings->CertificateName = strdup(s); + g_free(s); + } + } + + if (cert_host != host) g_free(cert_host); + g_free(host); + g_free(hostport); + + rfi->settings->ServerPort = port; + + return TRUE; +} + +BOOL rf_auto_reconnect(rfContext* rfi) +{ + TRACE_CALL(__func__); + rdpSettings* settings = rfi->instance->settings; + RemminaPluginRdpUiObject* ui; + time_t treconn; + + rfi->is_reconnecting = TRUE; + rfi->reconnect_maxattempts = settings->AutoReconnectMaxRetries; + rfi->reconnect_nattempt = 0; + + /* Only auto reconnect on network disconnects. */ + if (freerdp_error_info(rfi->instance) != 0) { + rfi->is_reconnecting = FALSE; + return FALSE; + } + + if (!settings->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_service->log_printf("[RDP][%s] network disconnection detected, initiating reconnection attempt\n", + rfi->settings->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_service->log_printf("[RDP][%s] maximum number of reconnection attempts exceeded.\n", + rfi->settings->ServerHostname); + break; + } + + /* Attempt the next reconnect */ + remmina_plugin_service->log_printf("[RDP][%s] attempting reconnection, attempt #%d of %d\n", + rfi->settings->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_service->log_printf("[RDP][%s] unable to recreate tunnel with remmina_rdp_tunnel_init.\n", + rfi->settings->ServerHostname); + } else { + if (freerdp_reconnect(rfi->instance)) { + /* Reconnection is successful */ + remmina_plugin_service->log_printf("[RDP][%s] reconnection successful.\n", + rfi->settings->ServerHostname); + rfi->is_reconnecting = FALSE; + return TRUE; + } + } + + /* Wait until 5 secs have elapsed from last reconnect attempt */ + while (time(NULL) - treconn < 5) + sleep(1); + } + + rfi->is_reconnecting = FALSE; + return FALSE; +} + +BOOL rf_begin_paint(rdpContext* context) +{ + TRACE_CALL(__func__); + rdpGdi* gdi; + HGDI_WND hwnd; + + if (!context) + return FALSE; + + gdi = context->gdi; + if (!gdi || !gdi->primary || !gdi->primary->hdc || !gdi->primary->hdc->hwnd) + return FALSE; + + hwnd = gdi->primary->hdc->hwnd; + if (!hwnd->ninvalid) + return FALSE; + + hwnd->invalid->null = 1; + hwnd->ninvalid = 0; + return TRUE; +} + +BOOL rf_end_paint(rdpContext* context) +{ + TRACE_CALL(__func__); + INT32 x, y; + UINT32 w, h; + rdpGdi* gdi; + rfContext* rfi; + RemminaProtocolWidget* gp; + RemminaPluginRdpUiObject* ui; + + gdi = context->gdi; + rfi = (rfContext*)context; + gp = rfi->protocol_widget; + + if (gdi->primary->hdc->hwnd->invalid->null) + return FALSE; + + x = gdi->primary->hdc->hwnd->invalid->x; + y = gdi->primary->hdc->hwnd->invalid->y; + w = gdi->primary->hdc->hwnd->invalid->w; + h = gdi->primary->hdc->hwnd->invalid->h; + + ui = g_new0(RemminaPluginRdpUiObject, 1); + ui->type = REMMINA_RDP_UI_UPDATE_REGION; + ui->region.x = x; + ui->region.y = y; + ui->region.width = w; + ui->region.height = h; + + remmina_rdp_event_queue_ui_async(rfi->protocol_widget, ui); + + return TRUE; +} + +static BOOL rf_desktop_resize(rdpContext* context) +{ + TRACE_CALL(__func__); + rfContext* rfi; + RemminaProtocolWidget* gp; + RemminaPluginRdpUiObject* ui; + + rfi = (rfContext*)context; + gp = rfi->protocol_widget; + + remmina_plugin_service->protocol_plugin_set_width(gp, rfi->settings->DesktopWidth); + remmina_plugin_service->protocol_plugin_set_height(gp, rfi->settings->DesktopHeight); + + /* Call to remmina_rdp_event_update_scale(gp) on the main UI thread */ + + 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_emit_signal(gp, "desktop-resize"); + + return TRUE; +} + +static BOOL remmina_rdp_pre_connect(freerdp* instance) +{ + TRACE_CALL(__func__); + rfContext* rfi; + ALIGN64 rdpSettings* settings; + RemminaProtocolWidget* gp; + + rfi = (rfContext*)instance->context; + settings = instance->settings; + gp = rfi->protocol_widget; + + settings->OsMajorType = OSMAJORTYPE_UNIX; + settings->OsMinorType = OSMINORTYPE_UNSPECIFIED; + ZeroMemory(settings->OrderSupport, 32); + + settings->BitmapCacheEnabled = True; + settings->OffscreenSupportLevel = True; + + + settings->OrderSupport[NEG_DSTBLT_INDEX] = True; + settings->OrderSupport[NEG_PATBLT_INDEX] = True; + settings->OrderSupport[NEG_SCRBLT_INDEX] = True; + settings->OrderSupport[NEG_OPAQUE_RECT_INDEX] = True; + settings->OrderSupport[NEG_DRAWNINEGRID_INDEX] = False; + settings->OrderSupport[NEG_MULTIDSTBLT_INDEX] = False; + settings->OrderSupport[NEG_MULTIPATBLT_INDEX] = False; + settings->OrderSupport[NEG_MULTISCRBLT_INDEX] = False; + settings->OrderSupport[NEG_MULTIOPAQUERECT_INDEX] = True; + settings->OrderSupport[NEG_MULTI_DRAWNINEGRID_INDEX] = False; + settings->OrderSupport[NEG_LINETO_INDEX] = True; + settings->OrderSupport[NEG_POLYLINE_INDEX] = True; + settings->OrderSupport[NEG_MEMBLT_INDEX] = settings->BitmapCacheEnabled; + settings->OrderSupport[NEG_MEM3BLT_INDEX] = settings->BitmapCacheEnabled; + settings->OrderSupport[NEG_MEMBLT_V2_INDEX] = settings->BitmapCacheEnabled; + settings->OrderSupport[NEG_MEM3BLT_V2_INDEX] = settings->BitmapCacheEnabled; + settings->OrderSupport[NEG_SAVEBITMAP_INDEX] = False; + settings->OrderSupport[NEG_GLYPH_INDEX_INDEX] = True; + settings->OrderSupport[NEG_FAST_INDEX_INDEX] = True; + settings->OrderSupport[NEG_FAST_GLYPH_INDEX] = True; + settings->OrderSupport[NEG_POLYGON_SC_INDEX] = False; + settings->OrderSupport[NEG_POLYGON_CB_INDEX] = False; + settings->OrderSupport[NEG_ELLIPSE_SC_INDEX] = False; + settings->OrderSupport[NEG_ELLIPSE_CB_INDEX] = False; + + if (settings->RemoteFxCodec == True) { + settings->FrameAcknowledge = False; + settings->LargePointerFlag = True; + settings->PerformanceFlags = PERF_FLAG_NONE; + + rfi->rfx_context = rfx_context_new(FALSE); + } + + PubSub_SubscribeChannelConnected(instance->context->pubSub, + (pChannelConnectedEventHandler)remmina_rdp_OnChannelConnectedEventHandler); + PubSub_SubscribeChannelDisconnected(instance->context->pubSub, + (pChannelDisconnectedEventHandler)remmina_rdp_OnChannelDisconnectedEventHandler); + + freerdp_client_load_addins(instance->context->channels, instance->settings); + + return True; +} + +static UINT32 rf_get_local_color_format(rfContext* rfi, BOOL aligned) +{ + UINT32 DstFormat; + BOOL invert = aligned; + + if (!rfi) + return 0; + + if (rfi->bpp == 32) + DstFormat = (!invert) ? PIXEL_FORMAT_RGBA32 : PIXEL_FORMAT_BGRA32; + else if (rfi->bpp == 24) { + if (aligned) + DstFormat = (!invert) ? PIXEL_FORMAT_RGBX32 : PIXEL_FORMAT_BGRX32; + else + DstFormat = (!invert) ? PIXEL_FORMAT_RGB24 : PIXEL_FORMAT_BGR24; + }else if (rfi->bpp == 16) + DstFormat = (!invert) ? PIXEL_FORMAT_RGB16 : PIXEL_FORMAT_BGR16; + else if (rfi->bpp == 15) + DstFormat = (!invert) ? PIXEL_FORMAT_RGB16 : PIXEL_FORMAT_BGR16; + else + DstFormat = (!invert) ? PIXEL_FORMAT_RGBX32 : PIXEL_FORMAT_BGRX32; + + return DstFormat; +} + + +static BOOL remmina_rdp_post_connect(freerdp* instance) +{ + TRACE_CALL(__func__); + rfContext* rfi; + RemminaProtocolWidget* gp; + RemminaPluginRdpUiObject* ui; + rdpGdi* gdi; + int hdcBytesPerPixel, hdcBitsPerPixel; + + rfi = (rfContext*)instance->context; + gp = rfi->protocol_widget; + rfi->postconnect_error = REMMINA_POSTCONNECT_ERROR_OK; + + rfi->width = rfi->settings->DesktopWidth; + rfi->height = rfi->settings->DesktopHeight; + rfi->srcBpp = rfi->settings->ColorDepth; + + if (rfi->settings->RemoteFxCodec == FALSE) + rfi->sw_gdi = TRUE; + + rf_register_graphics(instance->context->graphics); + + if (rfi->bpp == 32) { + hdcBytesPerPixel = 4; + hdcBitsPerPixel = 32; + rfi->cairo_format = CAIRO_FORMAT_ARGB32; + }else if (rfi->bpp == 24) { + hdcBytesPerPixel = 4; + hdcBitsPerPixel = 32; + rfi->cairo_format = CAIRO_FORMAT_RGB24; + }else { + hdcBytesPerPixel = 2; + hdcBitsPerPixel = 16; + rfi->cairo_format = CAIRO_FORMAT_RGB16_565; + } + + if (!gdi_init(instance, rf_get_local_color_format(rfi, TRUE))) { + rfi->postconnect_error = REMMINA_POSTCONNECT_ERROR_GDI_INIT; + return FALSE; + } + + if (instance->context->codecs->h264 == NULL && rfi->settings->GfxH264) { + gdi_free(instance); + rfi->postconnect_error = REMMINA_POSTCONNECT_ERROR_NO_H264; + return FALSE; + } + + gdi = instance->context->gdi; + rfi->primary_buffer = gdi->primary_buffer; + + pointer_cache_register_callbacks(instance->update); + + instance->update->BeginPaint = rf_begin_paint; + instance->update->EndPaint = rf_end_paint; + instance->update->DesktopResize = rf_desktop_resize; + + 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; +} + +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_authuserpwd(gp, TRUE, !disablepasswordstoring); + + if (ret == GTK_RESPONSE_OK) { + s_username = remmina_plugin_service->protocol_plugin_init_get_username(gp); + if (s_username) rfi->settings->Username = strdup(s_username); + + s_password = remmina_plugin_service->protocol_plugin_init_get_password(gp); + if (s_password) rfi->settings->Password = strdup(s_password); + + s_domain = remmina_plugin_service->protocol_plugin_init_get_domain(gp); + if (s_domain) rfi->settings->Domain = strdup(s_domain); + + save = remmina_plugin_service->protocol_plugin_init_get_savepassword(gp); + if (save) { + // User has requested to save credentials. We put all the new cretentials + // into remminafile->settings. They will be saved later, on successful connection, by + // remmina_connection_window.c + + remmina_plugin_service->file_set_string( remminafile, "username", s_username ); + remmina_plugin_service->file_set_string( remminafile, "password", s_password ); + remmina_plugin_service->file_set_string( remminafile, "domain", s_domain ); + + } + + if ( s_username ) g_free( s_username ); + if ( s_password ) g_free( s_password ); + if ( s_domain ) g_free( s_domain ); + + return True; + }else { + rfi->user_cancelled = TRUE; + return False; + } + + return True; +} + +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(freerdp* instance, + const char* common_name, const char* subject, const char* issuer, + const char* new_fingerprint, const char* old_subject, const char* old_issuer, const char* old_fingerprint) +{ + 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, new_fingerprint, old_fingerprint); + + if (status == GTK_RESPONSE_OK) + return 1; + + return 0; +} + +static void remmina_rdp_main_loop(RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + DWORD nCount; + DWORD status; + HANDLE handles[64]; + gchar buf[100]; + rfContext* rfi = GET_PLUGIN_DATA(gp); + + while (!freerdp_shall_disconnect(rfi->instance)) { + nCount = freerdp_get_event_handles(rfi->instance->context, &handles[0], 64); + if (rfi->event_handle) { + handles[nCount++] = rfi->event_handle; + } + + 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, "Failed to process local kb/mouse event queue\n"); + break; + } + if (read(rfi->event_pipe[0], buf, sizeof(buf))) { + } + } + + if (!freerdp_check_event_handles(rfi->instance->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; + } + fprintf(stderr, "Failed to check FreeRDP event handles\n"); + break; + } + } +} + +int remmina_rdp_load_static_channel_addin(rdpChannels* channels, rdpSettings* settings, char* name, void* data) +{ + TRACE_CALL(__func__); + void* entry; + + entry = freerdp_load_channel_addin_entry(name, NULL, NULL, FREERDP_ADDIN_CHANNEL_STATIC); + if (entry) { + if (freerdp_channels_client_load(channels, settings, entry, data) == 0) { + fprintf(stderr, "loading channel %s\n", name); + return 0; + } + } + + return -1; +} + +/* Send CTRL+ALT+DEL keys 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_main(RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + const gchar* s; + gchar *sm; + gchar* value; + gint rdpsnd_rate; + gint rdpsnd_channel; + char *rdpsnd_params[3]; + int rdpsnd_nparams; + char rdpsnd_param1[16]; + char rdpsnd_param2[16]; + const gchar* cs; + RemminaFile* remminafile; + rfContext* rfi = GET_PLUGIN_DATA(gp); + gchar *gateway_host; + gint gateway_port; + gint desktopOrientation, desktopScaleFactor, deviceScaleFactor; + gint dynresw, dynresh; + + remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); + + if (!remmina_rdp_tunnel_init(gp)) + return FALSE; + + rfi->settings->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_enabled", FALSE)) { + rfi->settings->AutoReconnectionEnabled = FALSE; + } + + rfi->settings->ColorDepth = remmina_plugin_service->file_get_int(remminafile, "colordepth", 66); + + rfi->settings->SoftwareGdi = TRUE; + + if (rfi->settings->ColorDepth == 0) { + /* RFX (Win7)*/ + rfi->settings->RemoteFxCodec = TRUE; + rfi->settings->SupportGraphicsPipeline = FALSE; + rfi->settings->ColorDepth = 32; + } else if (rfi->settings->ColorDepth == 64) { + /* /gfx:rfx (Win8) */ + rfi->settings->ColorDepth = 32; + rfi->settings->SupportGraphicsPipeline = TRUE; + rfi->settings->GfxH264 = FALSE; + rfi->settings->GfxAVC444 = FALSE; + } else if (rfi->settings->ColorDepth == 65) { + /* /gfx:avc420 (Win8.1) */ + rfi->settings->ColorDepth = 32; + rfi->settings->SupportGraphicsPipeline = TRUE; +#ifdef WITH_GFX_H264 + rfi->settings->GfxH264 = TRUE; + rfi->settings->GfxAVC444 = FALSE; +#endif + } else if (rfi->settings->ColorDepth >= 66) { + /* /gfx:avc444 (Win10) */ + rfi->settings->ColorDepth = 32; + rfi->settings->SupportGraphicsPipeline = TRUE; +#ifdef WITH_GFX_H264 + rfi->settings->GfxH264 = TRUE; + rfi->settings->GfxAVC444 = TRUE; +#endif + } + + rfi->settings->DesktopWidth = remmina_plugin_service->get_profile_remote_width(gp); + rfi->settings->DesktopHeight = remmina_plugin_service->get_profile_remote_height(gp); + dynresw = remmina_plugin_service->file_get_int(remminafile, "dynamic_resolution_width", 0); + dynresh = remmina_plugin_service->file_get_int(remminafile, "dynamic_resolution_height", 0); + + if (rfi->scale == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES && dynresh != 0 && dynresw != 0) { + rfi->settings->DesktopWidth = dynresw; + rfi->settings->DesktopHeight = dynresh; + } + remmina_plugin_service->protocol_plugin_set_width(gp, rfi->settings->DesktopWidth); + remmina_plugin_service->protocol_plugin_set_height(gp, rfi->settings->DesktopHeight); + + if (remmina_plugin_service->file_get_string(remminafile, "username")) + rfi->settings->Username = strdup(remmina_plugin_service->file_get_string(remminafile, "username")); + + if (remmina_plugin_service->file_get_string(remminafile, "domain")) + rfi->settings->Domain = strdup(remmina_plugin_service->file_get_string(remminafile, "domain")); + + s = remmina_plugin_service->file_get_string(remminafile, "password"); + + if (s) { + rfi->settings->Password = strdup(s); + rfi->settings->AutoLogonEnabled = 1; + } + /* Remote Desktop Gateway server address */ + rfi->settings->GatewayEnabled = FALSE; + s = remmina_plugin_service->file_get_string(remminafile, "gateway_server"); + if (s) { + remmina_plugin_service->get_server_port(s, 443, &gateway_host, &gateway_port); + rfi->settings->GatewayHostname = gateway_host; + rfi->settings->GatewayPort = gateway_port; + rfi->settings->GatewayEnabled = TRUE; + rfi->settings->GatewayUseSameCredentials = TRUE; + } + /* Remote Desktop Gateway domain */ + if (remmina_plugin_service->file_get_string(remminafile, "gateway_domain")) { + rfi->settings->GatewayDomain = strdup(remmina_plugin_service->file_get_string(remminafile, "gateway_domain")); + rfi->settings->GatewayUseSameCredentials = FALSE; + } + /* Remote Desktop Gateway username */ + if (remmina_plugin_service->file_get_string(remminafile, "gateway_username")) { + rfi->settings->GatewayUsername = strdup(remmina_plugin_service->file_get_string(remminafile, "gateway_username")); + rfi->settings->GatewayUseSameCredentials = FALSE; + } + /* Remote Desktop Gateway password */ + s = remmina_plugin_service->file_get_string(remminafile, "gateway_password"); + if (s) { + rfi->settings->GatewayPassword = strdup(s); + rfi->settings->GatewayUseSameCredentials = FALSE; + } + /* If no different credentials were provided for the Remote Desktop Gateway + * use the same authentication credentials for the host */ + if (rfi->settings->GatewayEnabled && rfi->settings->GatewayUseSameCredentials) { + g_free(rfi->settings->GatewayDomain); + rfi->settings->GatewayDomain = g_strdup(rfi->settings->Domain); + g_free(rfi->settings->GatewayUsername); + rfi->settings->GatewayUsername = g_strdup(rfi->settings->Username); + g_free(rfi->settings->GatewayPassword); + rfi->settings->GatewayPassword = g_strdup(rfi->settings->Password); + } + /* Remote Desktop Gateway usage */ + if (rfi->settings->GatewayEnabled) + freerdp_set_gateway_usage_method(rfi->settings, + remmina_plugin_service->file_get_int(remminafile, "gateway_usage", FALSE) ? TSC_PROXY_MODE_DETECT : TSC_PROXY_MODE_DIRECT); + /* Certificate ignore */ + rfi->settings->IgnoreCertificate = remmina_plugin_service->file_get_int(remminafile, "cert_ignore", 0); + + /* ClientHostname is internally preallocated to 32 bytes by libfreerdp */ + if ((cs = remmina_plugin_service->file_get_string(remminafile, "clientname"))) { + strncpy(rfi->settings->ClientHostname, cs, FREERDP_CLIENTHOSTNAME_LEN - 1); + }else { + strncpy(rfi->settings->ClientHostname, g_get_host_name(), FREERDP_CLIENTHOSTNAME_LEN - 1); + } + rfi->settings->ClientHostname[FREERDP_CLIENTHOSTNAME_LEN - 1] = 0; + + if (remmina_plugin_service->file_get_string(remminafile, "loadbalanceinfo")) { + rfi->settings->LoadBalanceInfo = (BYTE*)strdup(remmina_plugin_service->file_get_string(remminafile, "loadbalanceinfo")); + rfi->settings->LoadBalanceInfoLength = (UINT32)strlen((char*)rfi->settings->LoadBalanceInfo); + } + + if (remmina_plugin_service->file_get_string(remminafile, "exec")) { + rfi->settings->AlternateShell = strdup(remmina_plugin_service->file_get_string(remminafile, "exec")); + } + + if (remmina_plugin_service->file_get_string(remminafile, "execpath")) { + rfi->settings->ShellWorkingDirectory = strdup(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]) { + rfi->settings->PerformanceFlags = strtoul(value, NULL, 16); + }else { + switch (remmina_plugin_service->file_get_int(remminafile, "quality", DEFAULT_QUALITY_0)) { + case 9: + rfi->settings->PerformanceFlags = DEFAULT_QUALITY_9; + break; + + case 2: + rfi->settings->PerformanceFlags = DEFAULT_QUALITY_2; + break; + + case 1: + rfi->settings->PerformanceFlags = DEFAULT_QUALITY_1; + break; + + case 0: + default: + rfi->settings->PerformanceFlags = DEFAULT_QUALITY_0; + break; + } + } + g_free(value); + + /* PerformanceFlags bitmask need also to be splitted into BOOL variables + * like rfi->settings->DisableWallpaper, rfi->settings->AllowFontSmoothing... + * or freerdp_get_param_bool() function will return the wrong value + */ + freerdp_performance_flags_split(rfi->settings); + + rfi->settings->KeyboardLayout = remmina_rdp_settings_get_keyboard_layout(); + + if (remmina_plugin_service->file_get_int(remminafile, "console", FALSE)) { + rfi->settings->ConsoleSession = True; + } + + cs = remmina_plugin_service->file_get_string(remminafile, "security"); + + + + if (g_strcmp0(cs, "rdp") == 0) { + rfi->settings->RdpSecurity = True; + rfi->settings->TlsSecurity = False; + rfi->settings->NlaSecurity = False; + rfi->settings->ExtSecurity = False; + rfi->settings->UseRdpSecurityLayer = True; + }else if (g_strcmp0(cs, "tls") == 0) { + rfi->settings->RdpSecurity = False; + rfi->settings->TlsSecurity = True; + rfi->settings->NlaSecurity = False; + rfi->settings->ExtSecurity = False; + }else if (g_strcmp0(cs, "nla") == 0) { + rfi->settings->RdpSecurity = False; + rfi->settings->TlsSecurity = False; + rfi->settings->NlaSecurity = True; + rfi->settings->ExtSecurity = False; + } + + /* This is "-nego" switch of xfreerdp */ + rfi->settings->NegotiateSecurityLayer = True; + + rfi->settings->CompressionEnabled = True; + rfi->settings->FastPathInput = True; + rfi->settings->FastPathOutput = True; + + /* Orientation and scaling settings */ + remmina_rdp_settings_get_orientation_scale_prefs(&desktopOrientation, &desktopScaleFactor, &deviceScaleFactor); + + rfi->settings->DesktopOrientation = desktopOrientation; + if (desktopScaleFactor != 0 && deviceScaleFactor != 0) { + rfi->settings->DesktopScaleFactor = desktopScaleFactor; + rfi->settings->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 */ + rfi->settings->SupportDisplayControl = TRUE; + + cs = remmina_plugin_service->file_get_string(remminafile, "sound"); + + if (g_strcmp0(cs, "remote") == 0) { + rfi->settings->RemoteConsoleAudio = 1; + }else if (g_str_has_prefix(cs, "local")) { + + rdpsnd_nparams = 0; + rdpsnd_params[rdpsnd_nparams++] = "rdpsnd"; + + cs = strchr(cs, ','); + if (cs) { + rdpsnd_rate = atoi(cs + 1); + if (rdpsnd_rate > 1000 && rdpsnd_rate < 150000) { + snprintf( rdpsnd_param1, sizeof(rdpsnd_param1), "rate:%d", rdpsnd_rate ); + rdpsnd_params[rdpsnd_nparams++] = rdpsnd_param1; + cs = strchr(cs + 1, ','); + if (cs) { + rdpsnd_channel = atoi(cs + 1); + if (rdpsnd_channel >= 1 && rdpsnd_channel <= 2) { + snprintf( rdpsnd_param2, sizeof(rdpsnd_param2), "channel:%d", rdpsnd_channel ); + rdpsnd_params[rdpsnd_nparams++] = rdpsnd_param2; + } + } + } + } + + freerdp_client_add_static_channel(rfi->settings, rdpsnd_nparams, (char**)rdpsnd_params); + + } + + if ( remmina_plugin_service->file_get_int(remminafile, "microphone", FALSE) ? TRUE : FALSE ) { + char* p[1]; + int count; + + count = 1; + p[0] = "audin"; + + freerdp_client_add_dynamic_channel(rfi->settings, count, p); + } + + rfi->settings->RedirectClipboard = ( remmina_plugin_service->file_get_int(remminafile, "disableclipboard", FALSE) ? FALSE : TRUE ); + + cs = remmina_plugin_service->file_get_string(remminafile, "sharefolder"); + + if (cs && cs[0] == '/') { + RDPDR_DRIVE* drive; + gsize sz; + + drive = (RDPDR_DRIVE*)malloc(sizeof(RDPDR_DRIVE)); + ZeroMemory(drive, sizeof(RDPDR_DRIVE)); + + s = strrchr( cs, '/' ); + if ( s == NULL || s[1] == 0 ) + s = remmina_rdp_plugin_default_drive_name; + else + s++; + sm = g_convert_with_fallback(s, -1, "ascii", "utf-8", "_", NULL, &sz, NULL); + + drive->Type = RDPDR_DTYP_FILESYSTEM; + drive->Name = _strdup(sm); + drive->Path = _strdup(cs); + g_free(sm); + + freerdp_device_collection_add(rfi->settings, (RDPDR_DEVICE*)drive); + rfi->settings->DeviceRedirection = TRUE; + } + + if (remmina_plugin_service->file_get_int(remminafile, "shareprinter", FALSE)) { + RDPDR_PRINTER* printer; + printer = (RDPDR_PRINTER*)malloc(sizeof(RDPDR_PRINTER)); + ZeroMemory(printer, sizeof(RDPDR_PRINTER)); + + printer->Type = RDPDR_DTYP_PRINT; + + rfi->settings->DeviceRedirection = TRUE; + rfi->settings->RedirectPrinters = TRUE; + + freerdp_device_collection_add(rfi->settings, (RDPDR_DEVICE*)printer); + } + + if (remmina_plugin_service->file_get_int(remminafile, "sharesmartcard", FALSE)) { + RDPDR_SMARTCARD* smartcard; + smartcard = (RDPDR_SMARTCARD*)malloc(sizeof(RDPDR_SMARTCARD)); + ZeroMemory(smartcard, sizeof(RDPDR_SMARTCARD)); + + smartcard->Type = RDPDR_DTYP_SMARTCARD; + + smartcard->Name = _strdup("scard"); + + rfi->settings->DeviceRedirection = TRUE; + rfi->settings->RedirectSmartCards = TRUE; + + freerdp_device_collection_add(rfi->settings, (RDPDR_DEVICE*)smartcard); + } + + if (!freerdp_connect(rfi->instance)) { + if (!rfi->user_cancelled) { + UINT32 e; + + e = freerdp_get_last_error(rfi->instance->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 + remmina_plugin_service->protocol_plugin_set_error(gp, _("Authentication to RDP server %s failed.\nCheck username, password and domain."), + rfi->settings->ServerHostname ); + // Invalidate the saved password, so the user will be re-asked at next logon + remmina_plugin_service->file_unsave_password(remminafile); + 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, _("Access to RDP server %s failed.\nAccount is locked out."), + rfi->settings->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, _("Access to RDP server %s failed.\nAccount is expired."), + rfi->settings->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, _("Access to RDP server %s failed.\nPassword expired."), + rfi->settings->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, _("Access to RDP server %s failed.\nAccount is disabled."), + rfi->settings->ServerHostname ); + break; + 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, _("Access to RDP server %s failed.\nAccount has restrictions."), + rfi->settings->ServerHostname ); + break; + case FREERDP_ERROR_CONNECT_FAILED: + remmina_plugin_service->protocol_plugin_set_error(gp, _("Connection to RDP server %s failed."), rfi->settings->ServerHostname ); + break; + case FREERDP_ERROR_DNS_NAME_NOT_FOUND: + remmina_plugin_service->protocol_plugin_set_error(gp, _("Unable to find the address of RDP server %s."), rfi->settings->ServerHostname ); + break; + case FREERDP_ERROR_TLS_CONNECT_FAILED: + remmina_plugin_service->protocol_plugin_set_error(gp, + _("Error connecting to RDP server %s. TLS connection failed. Check that client and server support a common TLS version."), rfi->settings->ServerHostname ); + break; + case FREERDP_ERROR_SECURITY_NEGO_CONNECT_FAILED: + remmina_plugin_service->protocol_plugin_set_error(gp, _("Unable to establish a connection to RDP server %s."), rfi->settings->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, _("Unable to connect to RDP server %s."), rfi->settings->ServerHostname ); + break; + case REMMINA_POSTCONNECT_ERROR_GDI_INIT: + remmina_plugin_service->protocol_plugin_set_error(gp, _("Unable to initialize libfreerdp gdi") ); + break; + case REMMINA_POSTCONNECT_ERROR_NO_H264: + remmina_plugin_service->protocol_plugin_set_error(gp, _("You requested an H264 GFX mode for server %s, but your libfreerdp does not support H264. Please check Color Depth settings."), rfi->settings->ServerHostname); + break; + } + break; +#endif + default: + g_printf("%08X %08X\n", e, (unsigned)ERRCONNECT_POST_CONNECT_FAILED); + remmina_plugin_service->protocol_plugin_set_error(gp, _("Unable to connect to RDP server %s"), rfi->settings->ServerHostname); + break; + } + + } + + return FALSE; + } + + remmina_rdp_main_loop(gp); + + return TRUE; +} + +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); + remmina_rdp_main(gp); + rfi->thread = 0; + + + /* Signal main thread that we closed the connection. But wait 200ms, because we may + * have outstaiding events to process in the meanwhile */ + g_timeout_add(200, ((GSourceFunc)remmina_plugin_service->protocol_plugin_close_connection), gp); + + 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->Authenticate = remmina_rdp_authenticate; + instance->VerifyCertificate = remmina_rdp_verify_certificate; + instance->VerifyChangedCertificate = remmina_rdp_verify_changed_certificate; + + 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->instance = instance; + rfi->settings = instance->settings; + rfi->connected = False; + rfi->is_reconnecting = False; + + 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); + + rfi->scale = remmina_plugin_service->remmina_protocol_widget_get_current_scale_mode(gp); + + if (pthread_create(&rfi->thread, NULL, remmina_rdp_main_thread, gp)) { + remmina_plugin_service->protocol_plugin_set_error(gp, "%s", + "Failed to initialize pthread. Falling back to non-thread mode..."); + + rfi->thread = 0; + + return FALSE; + } + + return TRUE; +} + +static gboolean remmina_rdp_close_connection(RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + rfContext* rfi = GET_PLUGIN_DATA(gp); + freerdp* instance; + + if (!remmina_plugin_service->is_main_thread()) { + g_printf("WARNING: %s called on a subthread, may not work or crash remmina.\n", __func__); + } + + /* Immediately deatch GTK clipboard from this connection */ + remmina_rdp_cliprdr_detach_owner(gp); + + if (freerdp_get_last_error(rfi->instance->context) == 0x10005) { + remmina_plugin_service->protocol_plugin_set_error(gp, "Another user connected to the server (%s), forcing the disconnection of the current connection.", rfi->settings->ServerHostname); + } + instance = rfi->instance; + if (rfi->thread) { + rfi->thread_cancelled = TRUE; // Avoid all rf_queue function to run + pthread_cancel(rfi->thread); + + if (rfi->thread) + pthread_join(rfi->thread, NULL); + + } + + if (instance) { + if ( rfi->connected ) { + freerdp_disconnect(instance); + rfi->connected = False; + } + } + + if (rfi->hdc) { + gdi_DeleteDC(rfi->hdc); + rfi->hdc = NULL; + } + + remmina_rdp_clipboard_free(rfi); + if (rfi->rfx_context) { + rfx_context_free(rfi->rfx_context); + rfi->rfx_context = NULL; + } + + if (instance) { + gdi_free(instance); + cache_free(instance->context->cache); + instance->context->cache = NULL; + } + + /* Destroy event queue. Pending async events will be discarded. Should we flush it ? */ + remmina_rdp_event_uninit(gp); + + if (instance) { + freerdp_context_free(instance); /* context is rfContext* rfi */ + freerdp_free(instance); /* This implicitly frees instance->context and rfi is no longer valid */ + } + + /* Remove instance->context from gp object data to avoid double free */ + g_object_steal_data(G_OBJECT(gp), "plugin-data"); + + /* Now let remmina to complete its disconnection tasks */ + remmina_plugin_service->protocol_plugin_emit_signal(gp, "disconnect"); + + 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__); + RemminaFile* remminafile; + rfContext* rfi = GET_PLUGIN_DATA(gp); + + remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); + + switch (feature->id) { + case REMMINA_RDP_FEATURE_UNFOCUS: + remmina_rdp_event_unfocus(gp); + break; + + case REMMINA_RDP_FEATURE_SCALE: + rfi->scale = remmina_plugin_service->remmina_protocol_widget_get_current_scale_mode(gp); + remmina_rdp_event_update_scale(gp); + break; + + case REMMINA_RDP_FEATURE_DYNRESUPDATE: + break; + + case REMMINA_RDP_FEATURE_TOOL_REFRESH: + 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)); + 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; + + bytesPerPixel = GetBytesPerPixel(gdi->hdc->format); + bitsPerPixel = GetBitsPerPixel(gdi->hdc->format); + + /** @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_service->log_printf("[RDP] allocating %zu bytes for a full screenshot\n", szmem); + rpsd->buffer = malloc(szmem); + if (!rpsd->buffer) { + remmina_plugin_service->log_printf("[RDP] unable to allocate %zu bytes for a full screenshot\n", 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 color depths */ +static gpointer colordepth_list[] = +{ + /* 1st one is the default in a new install */ +#ifdef WITH_GFX_H264 + "66", N_("GFX AVC444 (32 bpp)"), + "65", N_("GFX AVC420 (32 bpp)"), +#endif + "64", N_("GFX RFX (32 bpp)"), + "0", N_("RemoteFX (32 bpp)"), + "32", N_("True color (32 bpp)"), + "24", N_("True color (24 bpp)"), + "16", N_("High color (16 bpp)"), + "15", N_("High color (15 bpp)"), + "8", N_("256 colors (8 bpp)"), + 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 sound options */ +static gpointer sound_list[] = +{ + "off", N_("Off"), + "local", N_("Local"), + "local,11025,1", N_("Local - low quality"), + "local,22050,2", N_("Local - medium quality"), + "local,44100,2", N_("Local - high quality"), + "remote", N_("Remote"), + NULL +}; + +/* Array of key/value pairs for security */ +static gpointer security_list[] = +{ + "", N_("Negotiate"), + "nla", "NLA", + "tls", "TLS", + "rdp", "RDP", + NULL +}; + +/* 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) Unused pointer + */ +static const RemminaProtocolSetting remmina_rdp_basic_settings[] = +{ + { REMMINA_PROTOCOL_SETTING_TYPE_SERVER, "server", NULL, FALSE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "username", N_("User name"), FALSE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_PASSWORD, "password", N_("User password"), FALSE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "domain", N_("Domain"), FALSE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_RESOLUTION, "resolution", NULL, FALSE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "colordepth", N_("Color depth"), FALSE, colordepth_list, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_FOLDER, "sharefolder", N_("Share folder"), FALSE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableautoreconnect", N_("Disable automatic reconnection"), FALSE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_END, NULL, NULL, FALSE, 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) Unused pointer + */ +static const RemminaProtocolSetting remmina_rdp_advanced_settings[] = +{ + { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "quality", N_("Quality"), FALSE, quality_list, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "sound", N_("Sound"), FALSE, sound_list, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "security", N_("Security"), FALSE, security_list, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "gateway_server", N_("RD Gateway server"), FALSE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "gateway_username", N_("RD Gateway username"), FALSE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_PASSWORD, "gateway_password", N_("RD Gateway password"), FALSE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "gateway_domain", N_("RD Gateway domain"), FALSE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "clientname", N_("Client name"), FALSE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "exec", N_("Startup program"), FALSE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "execpath", N_("Startup path"), FALSE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "loadbalanceinfo", N_("Load Balance Info"), FALSE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "cert_ignore", N_("Ignore certificate"), TRUE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "microphone", N_("Redirect local microphone"), TRUE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "sharesmartcard", N_("Share smartcard"), TRUE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "shareprinter", N_("Share local printers"), TRUE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disablepasswordstoring", N_("Disable password storing"), TRUE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableclipboard", N_("Disable clipboard sync"), TRUE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "console", N_("Attach to console (2003/2003 R2)"), FALSE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "gateway_usage", N_("Server detection using RD Gateway"), FALSE, NULL, 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_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_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} +}; + +/* 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 + "remmina-rdp", // Icon for normal connection + "remmina-rdp-ssh", // 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 +}; + +/* 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 +}; + +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("Unable to load RDP plugin due to bad freerdp library version. Required " + "libfreerdp version is at least %d.%d.%d but we found libfreerdp version %d.%d.%d\n", + FREERDP_REQUIRED_MAJOR, FREERDP_REQUIRED_MINOR, FREERDP_REQUIRED_REVISION, + vermaj, vermin, verrev ); + 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; + + 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 000000000..9a43ee6aa --- /dev/null +++ b/plugins/rdp/rdp_plugin.h @@ -0,0 +1,309 @@ +/* + * 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-2017 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> +#include <gdk/gdkx.h> + +#include <winpr/clipboard.h> + +typedef struct rf_context rfContext; + +#define GET_PLUGIN_DATA(gp) (rfContext*)g_object_get_data(G_OBJECT(gp), "plugin-data") + +#define DEFAULT_QUALITY_0 0x6f +#define DEFAULT_QUALITY_1 0x07 +#define DEFAULT_QUALITY_2 0x01 +#define DEFAULT_QUALITY_9 0x80 + +#define REMMINA_PLUGIN_RDP_VERSION "RDP Plugin: " VERSION " (git " REMMINA_GIT_REVISION \ + "), FreeRDP lib: " FREERDP_VERSION_FULL " (git " GIT_REVISION ")" + +extern RemminaPluginService* remmina_plugin_service; + +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_ASYNCWAIT } srv_clip_data_wait; + gpointer srv_data; + +}; +typedef struct rf_clipboard rfClipboard; + + +struct rf_pointer { + rdpPointer pointer; + GdkCursor* cursor; +}; +typedef struct rf_pointer rfPointer; + +struct rf_bitmap { + rdpBitmap bitmap; + Pixmap pixmap; + cairo_surface_t* surface; +}; +typedef struct rf_bitmap rfBitmap; + +struct rf_glyph { + rdpGlyph glyph; + Pixmap pixmap; +}; +typedef struct rf_glyph rfGlyph; + + +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 +} RemminaPluginRdpEventType; + +struct remmina_plugin_rdp_event { + RemminaPluginRdpEventType type; + union { + struct { + BOOL up; + BOOL extended; + UINT8 key_code; + UINT32 unicode_code; + } key_event; + struct { + UINT16 flags; + UINT16 x; + UINT16 y; + BOOL extended; + } mouse_event; + struct { + CLIPRDR_FORMAT_LIST* pFormatList; + } clipboard_formatlist; + struct { + CLIPRDR_FORMAT_DATA_RESPONSE* pFormatDataResponse; + } clipboard_formatdataresponse; + struct { + CLIPRDR_FORMAT_DATA_REQUEST* pFormatDataRequest; + } clipboard_formatdatarequest; + struct { + gint width; + gint height; + gint desktopOrientation; + gint desktopScaleFactor; + gint deviceScaleFactor; + } monitor_layout; + }; +}; +typedef struct remmina_plugin_rdp_event RemminaPluginRdpEvent; + +typedef enum { + REMMINA_RDP_UI_UPDATE_REGION = 0, + REMMINA_RDP_UI_CONNECTED, + REMMINA_RDP_UI_RECONNECT_PROGRESS, + REMMINA_RDP_UI_CURSOR, + REMMINA_RDP_UI_RFX, + 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, + REMMINA_RDP_UI_CLIPBOARD_SET_CONTENT +} 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 +} RemminaPluginRdpUiEeventType; + +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 { + gint x; + gint y; + gint width; + gint height; + } region; + 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; +}; + +struct rf_context { + rdpContext _p; + + RemminaProtocolWidget* protocol_widget; + + /* main */ + rdpSettings* settings; + freerdp* instance; + + pthread_t 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]; + + RFX_CONTEXT* rfx_context; + + gboolean connected; + gboolean is_reconnecting; + 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; + + HGDI_DC hdc; + gint srcBpp; + GdkDisplay* display; + GdkVisual* visual; + cairo_surface_t* surface; + cairo_format_t cairo_format; + gint bpp; + gint width; + gint height; + gint scanline_pad; + gint* colormap; + UINT8* primary_buffer; + + 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; + + rfClipboard clipboard; + + 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 000000000..592feae07 --- /dev/null +++ b/plugins/rdp/rdp_settings.c @@ -0,0 +1,640 @@ +/* + * 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-2017 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_settings.h" +#include <freerdp/locale/keyboard.h> + +static guint keyboard_layout = 0; +static guint rdp_keyboard_layout = 0; + +static void remmina_rdp_settings_kbd_init(void) +{ + TRACE_CALL(__func__); + keyboard_layout = freerdp_keyboard_init(rdp_keyboard_layout); +} + +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; + + /* 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"); + + 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__); + gint i; + 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), "-"); + + layouts = freerdp_keyboard_get_layouts(RDP_KEYBOARD_LAYOUT_TYPE_STANDARD | RDP_KEYBOARD_LAYOUT_TYPE_VARIANT); + + for (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); + } + + free(layouts); +} + + +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_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_grid_attach(GTK_GRID(grid), widget, 1, 0, 4, 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_grid_attach(GTK_GRID(grid), widget, 1, 1, 4, 2); + 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_grid_attach(GTK_GRID(grid), widget, 1, 3, 3, 3); + 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(_("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_grid_attach(GTK_GRID(grid), widget, 0, 6, 1, 4); + + 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_grid_attach(GTK_GRID(grid), widget, 1, 6, 4, 4); + + 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_grid_attach(GTK_GRID(grid), widget, 1, 10, 2, 5); + 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, 3, 10, 3, 5); + 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_grid_attach(GTK_GRID(grid), widget, 1, 13, 2, 6); + 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, 3, 13, 3, 6); + 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_grid_attach(GTK_GRID(grid), widget, 1, 16, 2, 7); + 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, 3, 16, 3, 7); + 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_grid_attach(GTK_GRID(grid), widget, 1, 19, 2, 8); + 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, 3, 19, 3, 8); + 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_grid_attach(GTK_GRID(grid), widget, 0, 27, 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_grid_attach(GTK_GRID(grid), widget, 1, 27, 1, 1); + + widget = gtk_spin_button_new_with_range(0, 10000, 1); + gtk_widget_show(widget); + gtk_grid_attach(GTK_GRID(grid), widget, 2, 27, 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_grid_attach(GTK_GRID(grid), widget, 1, 28, 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, 28, 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_grid_attach(GTK_GRID(grid), widget, 0, 29, 1, 1); + + widget = gtk_combo_box_new_with_model(GTK_TREE_MODEL(grid->desktop_orientation_store)); + gtk_widget_show(widget); + gtk_grid_attach(GTK_GRID(grid), widget, 1, 29, 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->desktop_orientation_combo = widget; + + remmina_rdp_settings_set_combo_active_item(GTK_COMBO_BOX(grid->desktop_orientation_combo), desktopOrientation); + +} + +GtkWidget* remmina_rdp_settings_new(void) +{ + 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 000000000..5dca220af --- /dev/null +++ b/plugins/rdp/rdp_settings.h @@ -0,0 +1,47 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2010-2011 Vic Lee + * Copyright (C) 2017 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 + +G_BEGIN_DECLS + +void remmina_rdp_settings_init(void); +guint remmina_rdp_settings_get_keyboard_layout(void); +GtkWidget* remmina_rdp_settings_new(void); + +void remmina_rdp_settings_get_orientation_scale_prefs(int *desktopOrientation, int *desktopScaleFactor, int *deviceScaleFactor); + +G_END_DECLS + |