Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/Remmina/Remmina.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--plugins/CMakeLists.txt88
-rw-r--r--plugins/common/remmina_plugin.h101
-rw-r--r--plugins/exec/16x16/emblems/remmina-tool.pngbin0 -> 721 bytes
-rw-r--r--plugins/exec/22x22/emblems/remmina-tool.pngbin0 -> 1204 bytes
-rw-r--r--plugins/exec/CMakeLists.txt56
-rw-r--r--plugins/exec/exec_plugin.c303
-rw-r--r--plugins/exec/exec_plugin_config.h43
-rw-r--r--plugins/nx/16x16/emblems/remmina-nx.pngbin0 -> 426 bytes
-rw-r--r--plugins/nx/22x22/emblems/remmina-nx.pngbin0 -> 788 bytes
-rw-r--r--plugins/nx/CMakeLists.txt65
-rw-r--r--plugins/nx/nx_plugin.c801
-rw-r--r--plugins/nx/nx_plugin.h81
-rw-r--r--plugins/nx/nx_session.c1021
-rw-r--r--plugins/nx/nx_session.h107
-rw-r--r--plugins/nx/nx_session_manager.c244
-rw-r--r--plugins/nx/nx_session_manager.h43
-rw-r--r--plugins/rdp/16x16/emblems/remmina-rdp-ssh.pngbin0 -> 803 bytes
-rw-r--r--plugins/rdp/16x16/emblems/remmina-rdp.pngbin0 -> 747 bytes
-rw-r--r--plugins/rdp/22x22/emblems/remmina-rdp-ssh.pngbin0 -> 1273 bytes
-rw-r--r--plugins/rdp/22x22/emblems/remmina-rdp.pngbin0 -> 1181 bytes
-rw-r--r--plugins/rdp/CMakeLists.txt89
-rw-r--r--plugins/rdp/rdp_channels.c94
-rw-r--r--plugins/rdp/rdp_channels.h55
-rw-r--r--plugins/rdp/rdp_cliprdr.c821
-rw-r--r--plugins/rdp/rdp_cliprdr.h50
-rw-r--r--plugins/rdp/rdp_event.c1182
-rw-r--r--plugins/rdp/rdp_event.h52
-rw-r--r--plugins/rdp/rdp_file.c307
-rw-r--r--plugins/rdp/rdp_file.h46
-rw-r--r--plugins/rdp/rdp_graphics.c435
-rw-r--r--plugins/rdp/rdp_graphics.h41
-rw-r--r--plugins/rdp/rdp_plugin.c1553
-rw-r--r--plugins/rdp/rdp_plugin.h309
-rw-r--r--plugins/rdp/rdp_settings.c640
-rw-r--r--plugins/rdp/rdp_settings.h47
-rw-r--r--plugins/secret/CMakeLists.txt52
-rw-r--r--plugins/secret/src/glibsecret_plugin.c180
-rw-r--r--plugins/secret/src/glibsecret_plugin.h42
-rw-r--r--plugins/spice/16x16/emblems/remmina-spice.pngbin0 -> 405 bytes
-rw-r--r--plugins/spice/22x22/emblems/remmina-spice.pngbin0 -> 1003 bytes
-rw-r--r--plugins/spice/CMakeLists.txt49
-rw-r--r--plugins/spice/spice_plugin.c493
-rw-r--r--plugins/spice/spice_plugin.h70
-rw-r--r--plugins/spice/spice_plugin_file_transfer.c244
-rw-r--r--plugins/spice/spice_plugin_usb.c100
-rw-r--r--plugins/telepathy/CMakeLists.txt65
-rw-r--r--plugins/telepathy/Remmina.client8
-rw-r--r--plugins/telepathy/org.freedesktop.Telepathy.Client.Remmina.service.in3
-rw-r--r--plugins/telepathy/telepathy_channel_handler.c374
-rw-r--r--plugins/telepathy/telepathy_channel_handler.h44
-rw-r--r--plugins/telepathy/telepathy_handler.c127
-rw-r--r--plugins/telepathy/telepathy_handler.h58
-rw-r--r--plugins/telepathy/telepathy_plugin.c77
-rw-r--r--plugins/tool_hello_world/16x16/emblems/remmina-tool.pngbin0 -> 721 bytes
-rw-r--r--plugins/tool_hello_world/22x22/emblems/remmina-tool.pngbin0 -> 1204 bytes
-rw-r--r--plugins/tool_hello_world/CMakeLists.txt57
-rw-r--r--plugins/tool_hello_world/plugin.c123
-rw-r--r--plugins/tool_hello_world/plugin_config.h43
-rw-r--r--plugins/vnc/16x16/emblems/remmina-vnc-ssh.pngbin0 -> 692 bytes
-rw-r--r--plugins/vnc/16x16/emblems/remmina-vnc.pngbin0 -> 528 bytes
-rw-r--r--plugins/vnc/22x22/emblems/remmina-vnc-ssh.pngbin0 -> 1086 bytes
-rw-r--r--plugins/vnc/22x22/emblems/remmina-vnc.pngbin0 -> 883 bytes
-rw-r--r--plugins/vnc/CMakeLists.txt54
-rw-r--r--plugins/vnc/vnc_plugin.c1981
-rw-r--r--plugins/xdmcp/16x16/emblems/remmina-xdmcp-ssh.pngbin0 -> 878 bytes
-rw-r--r--plugins/xdmcp/16x16/emblems/remmina-xdmcp.pngbin0 -> 817 bytes
-rw-r--r--plugins/xdmcp/22x22/emblems/remmina-xdmcp-ssh.pngbin0 -> 1345 bytes
-rw-r--r--plugins/xdmcp/22x22/emblems/remmina-xdmcp.pngbin0 -> 1293 bytes
-rw-r--r--plugins/xdmcp/CMakeLists.txt53
-rw-r--r--plugins/xdmcp/xdmcp_plugin.c426
70 files changed, 13397 insertions, 0 deletions
diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt
new file mode 100644
index 000000000..804cb3e99
--- /dev/null
+++ b/plugins/CMakeLists.txt
@@ -0,0 +1,88 @@
+# Remmina - 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.
+
+# Needed FreeRDP version to build freerdp plugin
+set(FREERDP_REQUIRED_MAJOR 2)
+set(FREERDP_REQUIRED_MINOR 0)
+set(FREERDP_REQUIRED_REVISION 0)
+set(FREERDP_REQUIRED_VERSIONSTRING
+ ${FREERDP_REQUIRED_MAJOR}.${FREERDP_REQUIRED_MINOR}.${FREERDP_REQUIRED_REVISION})
+
+include_directories(${CMAKE_SOURCE_DIR}/plugins)
+
+set(APPICON16_EMBLEMS_DIR "${REMMINA_DATADIR}/icons/hicolor/16x16/emblems")
+set(APPICON22_EMBLEMS_DIR "${REMMINA_DATADIR}/icons/hicolor/22x22/emblems")
+
+set(REMMINA_COMMON_INCLUDE_DIRS ${GTK_INCLUDE_DIRS})
+set(REMMINA_COMMON_LIBRARIES ${GTK_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
+
+find_suggested_package(LIBSSH)
+if(LIBSSH_FOUND)
+ add_definitions(-DHAVE_LIBSSH)
+ include_directories(${SSH_INCLUDE_DIRS})
+ target_link_libraries(remmina ${SSH_LIBRARIES})
+endif()
+
+find_required_package(XKBFILE)
+
+if(LIBSSH_FOUND AND XKBFILE_FOUND)
+ add_subdirectory(nx)
+endif()
+
+add_subdirectory(xdmcp)
+
+find_suggested_package(FREERDP)
+if(FREERDP_FOUND)
+ add_subdirectory(rdp)
+endif()
+
+find_suggested_package(TELEPATHY)
+if(TELEPATHY_FOUND)
+ add_subdirectory(telepathy)
+endif()
+
+find_suggested_package(LIBVNCSERVER)
+if(LIBVNCSERVER_FOUND)
+ add_subdirectory(vnc)
+endif()
+
+find_suggested_package(SPICE)
+if(SPICE_FOUND)
+ add_subdirectory(spice)
+endif()
+
+if(WITH_EXAMPLES)
+ add_subdirectory(tool_hello_world)
+endif()
+add_subdirectory(exec)
diff --git a/plugins/common/remmina_plugin.h b/plugins/common/remmina_plugin.h
new file mode 100644
index 000000000..e4148dcde
--- /dev/null
+++ b/plugins/common/remmina_plugin.h
@@ -0,0 +1,101 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010 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 "config.h"
+#include <gtk/gtk.h>
+#include <glib/gi18n-lib.h>
+#include <glib/gstdio.h>
+#include <remmina/plugin.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <pthread.h>
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#include "remmina/remmina_trace_calls.h"
+
+typedef void (*PThreadCleanupFunc)(void*);
+
+
+#define IDLE_ADD gdk_threads_add_idle
+#define TIMEOUT_ADD gdk_threads_add_timeout
+#define CANCEL_ASYNC pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); pthread_testcancel();
+#define CANCEL_DEFER pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
+
+#define THREADS_ENTER _Pragma("GCC error \"THREADS_ENTER has been deprecated in Remmina 1.2\"")
+#define THREADS_LEAVE _Pragma("GCC error \"THREADS_LEAVE has been deprecated in Remmina 1.2\"")
+
+#define MAX_X_DISPLAY_NUMBER 99
+#define X_UNIX_SOCKET "/tmp/.X11-unix/X%d"
+
+#define INCLUDE_GET_AVAILABLE_XDISPLAY static gint \
+ remmina_get_available_xdisplay(void) \
+ { \
+ gint i; \
+ gint display = 0; \
+ gchar fn[200]; \
+ for (i = 1; i < MAX_X_DISPLAY_NUMBER; i++) \
+ { \
+ g_snprintf(fn, sizeof(fn), X_UNIX_SOCKET, i); \
+ if (!g_file_test(fn, G_FILE_TEST_EXISTS)) \
+ { \
+ display = i; \
+ break; \
+ } \
+ } \
+ return display; \
+ }
+
+
diff --git a/plugins/exec/16x16/emblems/remmina-tool.png b/plugins/exec/16x16/emblems/remmina-tool.png
new file mode 100644
index 000000000..96bc6828a
--- /dev/null
+++ b/plugins/exec/16x16/emblems/remmina-tool.png
Binary files differ
diff --git a/plugins/exec/22x22/emblems/remmina-tool.png b/plugins/exec/22x22/emblems/remmina-tool.png
new file mode 100644
index 000000000..a28ca75ad
--- /dev/null
+++ b/plugins/exec/22x22/emblems/remmina-tool.png
Binary files differ
diff --git a/plugins/exec/CMakeLists.txt b/plugins/exec/CMakeLists.txt
new file mode 100644
index 000000000..d98d7d529
--- /dev/null
+++ b/plugins/exec/CMakeLists.txt
@@ -0,0 +1,56 @@
+# remmina-plugin-tool_hello_world - The GTK+ Remote Desktop Client
+#
+# 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(REMMINA_PLUGIN_EXEC_SRCS
+ exec_plugin_config.h
+ exec_plugin.c
+)
+
+add_library(remmina-plugin-exec MODULE ${REMMINA_PLUGIN_EXEC_SRCS})
+set_target_properties(remmina-plugin-exec PROPERTIES PREFIX "")
+set_target_properties(remmina-plugin-exec PROPERTIES NO_SONAME 1)
+
+include_directories(${REMMINA_COMMON_INCLUDE_DIRS} ${GTK_INCLUDE_DIRS})
+target_link_libraries(remmina-plugin-exec ${REMMINA_COMMON_LIBRARIES})
+
+install(TARGETS remmina-plugin-exec DESTINATION ${REMMINA_PLUGINDIR})
+
+install(FILES
+ 16x16/emblems/remmina-tool.png
+ 16x16/emblems/remmina-tool.png
+ DESTINATION ${APPICON16_EMBLEMS_DIR})
+install(FILES
+ 22x22/emblems/remmina-tool.png
+ 22x22/emblems/remmina-tool.png
+ DESTINATION ${APPICON22_EMBLEMS_DIR})
diff --git a/plugins/exec/exec_plugin.c b/plugins/exec/exec_plugin.c
new file mode 100644
index 000000000..a48a1dd89
--- /dev/null
+++ b/plugins/exec/exec_plugin.c
@@ -0,0 +1,303 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2017 Antenore Gatta, Giovanni Panozzo
+ *
+ * Initially based on the plugin "Remmina Plugin EXEC", created and written by
+ * Fabio Castelli (Muflone) <muflone@vbsimple.net>.
+ *
+ * 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 "exec_plugin_config.h"
+
+#include "common/remmina_plugin.h"
+
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+#include <glib.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#define GET_PLUGIN_DATA(gp) (RemminaPluginExecData*)g_object_get_data(G_OBJECT(gp), "plugin-data")
+
+typedef struct _RemminaPluginExecData {
+ GtkWidget *log_view;
+ GtkTextBuffer *log_buffer;
+ GtkTextBuffer *err;
+ GtkWidget *sw;
+} RemminaPluginExecData;
+
+static RemminaPluginService *remmina_plugin_service = NULL;
+
+ static void
+cb_child_watch( GPid pid, gint status)
+{
+ /* Close pid */
+ g_spawn_close_pid( pid );
+}
+
+ static gboolean
+cb_out_watch (GIOChannel *channel, GIOCondition cond, RemminaProtocolWidget *gp)
+{
+ gchar *string;
+ gsize size;
+
+ RemminaPluginExecData *gpdata = GET_PLUGIN_DATA(gp);
+
+ if( cond == G_IO_HUP )
+ {
+ g_io_channel_unref( channel );
+ return FALSE;
+ }
+
+ g_io_channel_read_line( channel, &string, &size, NULL, NULL );
+ gtk_text_buffer_insert_at_cursor( gpdata->log_buffer, string, -1 );
+ g_free( string );
+
+ return TRUE;
+}
+
+ static gboolean
+cb_err_watch (GIOChannel *channel, GIOCondition cond, RemminaProtocolWidget *gp)
+{
+ gchar *string;
+ gsize size;
+
+ RemminaPluginExecData *gpdata = GET_PLUGIN_DATA(gp);
+
+ if( cond == G_IO_HUP )
+ {
+ g_io_channel_unref( channel );
+ return FALSE;
+ }
+
+ g_io_channel_read_line( channel, &string, &size, NULL, NULL );
+ gtk_text_buffer_insert_at_cursor( gpdata->err, string, -1 );
+ g_free( string );
+
+ return TRUE;
+}
+
+static void remmina_plugin_exec_init(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginExecData *gpdata;
+
+ remmina_plugin_service->log_printf("[%s] Plugin init\n", PLUGIN_NAME);
+
+ gpdata = g_new0(RemminaPluginExecData, 1);
+ g_object_set_data_full(G_OBJECT(gp), "plugin-data", gpdata, g_free);
+
+ gpdata->log_view = gtk_text_view_new();
+ gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(gpdata->log_view), GTK_WRAP_CHAR);
+ gtk_text_view_set_editable(GTK_TEXT_VIEW(gpdata->log_view), FALSE);
+ gtk_text_view_set_left_margin (GTK_TEXT_VIEW (gpdata->log_view), 20);
+ gtk_text_view_set_right_margin (GTK_TEXT_VIEW (gpdata->log_view), 20);
+ gpdata->log_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (gpdata->log_view));
+ gpdata->sw = gtk_scrolled_window_new (NULL, NULL);
+ gtk_widget_set_size_request (gpdata->sw, 640, 480);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (gpdata->sw),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_container_add(GTK_CONTAINER(gp), gpdata->sw);
+ gtk_container_add(GTK_CONTAINER(gpdata->sw), gpdata->log_view);
+ gtk_text_buffer_set_text (gpdata->log_buffer, "Remmina Exec Plugin Logger", -1);
+
+ gtk_widget_show_all(gpdata->sw);
+}
+
+static gboolean remmina_plugin_exec_run(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaFile* remminafile;
+ const gchar *cmd;
+ gchar *stdout_buffer;
+ gchar *stderr_buffer;
+ char **argv;
+ GError *error = NULL;
+ GPid child_pid;
+ gint child_stdout, child_stderr;
+ GtkDialog *dialog;
+ GIOChannel *out_ch, *err_ch;
+
+ remmina_plugin_service->log_printf("[%s] Plugin run\n", PLUGIN_NAME);
+ RemminaPluginExecData *gpdata = GET_PLUGIN_DATA(gp);
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ cmd = remmina_plugin_service->file_get_string(remminafile, "execcommand");
+ if (!cmd) {
+ gtk_text_buffer_set_text (gpdata->log_buffer,
+ _("You did not set any command to be executed"), -1);
+ remmina_plugin_service->protocol_plugin_emit_signal(gp, "connect");
+ return TRUE;
+ }
+
+ g_shell_parse_argv(cmd, NULL, &argv, &error);
+ if (error) {
+ gtk_text_buffer_set_text (gpdata->log_buffer, error->message, -1);
+ remmina_plugin_service->protocol_plugin_emit_signal(gp, "connect");
+ return TRUE;
+ g_error_free(error);
+ }
+
+ if (remmina_plugin_service->file_get_int(remminafile, "runasync", FALSE)) {
+ remmina_plugin_service->log_printf("[%s] Run Async\n", PLUGIN_NAME);
+ g_spawn_async_with_pipes( NULL,
+ argv,
+ NULL,
+ G_SPAWN_DO_NOT_REAP_CHILD |
+ G_SPAWN_SEARCH_PATH_FROM_ENVP |
+ G_SPAWN_SEARCH_PATH,
+ NULL,
+ NULL,
+ &child_pid,
+ NULL,
+ &child_stdout,
+ &child_stderr,
+ &error);
+ if (error != NULL) {
+ gtk_text_buffer_set_text (gpdata->log_buffer, error->message, -1);
+ g_error_free(error);
+ remmina_plugin_service->protocol_plugin_emit_signal(gp, "connect");
+ return TRUE;
+ }
+ g_child_watch_add(child_pid, (GChildWatchFunc)cb_child_watch, gp );
+
+ /* Create channels that will be used to read data from pipes. */
+ out_ch = g_io_channel_unix_new(child_stdout);
+ err_ch = g_io_channel_unix_new(child_stderr);
+ /* Add watches to channels */
+ g_io_add_watch(out_ch, G_IO_IN | G_IO_HUP, (GIOFunc)cb_out_watch, gp );
+ g_io_add_watch(err_ch, G_IO_IN | G_IO_HUP, (GIOFunc)cb_err_watch, gp );
+
+ }else {
+ dialog = GTK_DIALOG(gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL,
+ GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
+ _("WARNING! Executing a command synchronously, may hung Remmina.\rDo you really want to continue?")));
+ gint result = gtk_dialog_run (GTK_DIALOG (dialog));
+
+ switch (result)
+ {
+ case GTK_RESPONSE_YES:
+ break;
+ default:
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+ return FALSE;
+ break;
+ }
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+ remmina_plugin_service->log_printf("[%s] Run Sync\n", PLUGIN_NAME);
+ g_spawn_sync (NULL, // CWD or NULL
+ argv,
+ NULL, // ENVP or NULL
+ G_SPAWN_SEARCH_PATH |
+ G_SPAWN_SEARCH_PATH_FROM_ENVP,
+ NULL,
+ NULL,
+ &stdout_buffer, // STDOUT
+ &stderr_buffer, // STDERR
+ NULL, // Exit status
+ &error);
+ }
+ if (!error) {
+ remmina_plugin_service->log_printf("[%s] Command executed\n", PLUGIN_NAME);
+ gtk_text_buffer_set_text (gpdata->log_buffer, stdout_buffer, -1);
+ }else {
+ g_warning("Command %s exited with error: %s\n", cmd, error->message);
+ gtk_text_buffer_set_text (gpdata->log_buffer, error->message, -1);
+ g_error_free(error);
+ }
+
+ g_strfreev(argv);
+
+ remmina_plugin_service->protocol_plugin_emit_signal(gp, "connect");
+ return TRUE;
+}
+
+static gboolean remmina_plugin_exec_close(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ remmina_plugin_service->log_printf("[%s] Plugin close\n", PLUGIN_NAME);
+ remmina_plugin_service->protocol_plugin_emit_signal(gp, "disconnect");
+ return FALSE;
+}
+
+/* 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_plugin_exec_basic_settings[] =
+{
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "execcommand", N_("Command"), FALSE, NULL, NULL},
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "runasync", N_("Asynchrounous execution"), FALSE, NULL, NULL},
+ { REMMINA_PROTOCOL_SETTING_TYPE_END, NULL, NULL, FALSE, NULL, NULL }
+};
+
+/* Protocol plugin definition and features */
+static RemminaProtocolPlugin remmina_plugin = {
+ REMMINA_PLUGIN_TYPE_PROTOCOL, // Type
+ PLUGIN_NAME, // Name
+ PLUGIN_DESCRIPTION, // Description
+ GETTEXT_PACKAGE, // Translation domain
+ PLUGIN_VERSION, // Version number
+ PLUGIN_APPICON, // Icon for normal connection
+ PLUGIN_APPICON, // Icon for SSH connection
+ remmina_plugin_exec_basic_settings, // Array for basic settings
+ NULL, // Array for advanced settings
+ REMMINA_PROTOCOL_SSH_SETTING_NONE, // SSH settings type
+ NULL, // Array for available features
+ remmina_plugin_exec_init, // Plugin initialization
+ remmina_plugin_exec_run, // Plugin open connection
+ remmina_plugin_exec_close, // Plugin close connection
+ NULL, // Query for available features
+ NULL, // Call a feature
+ NULL, // Send a keystroke */
+};
+
+G_MODULE_EXPORT gboolean remmina_plugin_entry(RemminaPluginService *service)
+{
+ TRACE_CALL(__func__);
+ remmina_plugin_service = service;
+
+ bindtextdomain(GETTEXT_PACKAGE, REMMINA_RUNTIME_LOCALEDIR);
+ bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
+
+ if (!service->register_plugin((RemminaPlugin*)&remmina_plugin)) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
diff --git a/plugins/exec/exec_plugin_config.h b/plugins/exec/exec_plugin_config.h
new file mode 100644
index 000000000..1a20bd8b8
--- /dev/null
+++ b/plugins/exec/exec_plugin_config.h
@@ -0,0 +1,43 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2017 Antenore Gatta
+ *
+ * Initially based on the plugin "Remmina Plugin EXEC", created and written by
+ * Fabio Castelli (Muflone) <muflone@vbsimple.net>.
+ *
+ * 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
+
+#define PLUGIN_NAME "EXEC"
+#define PLUGIN_DESCRIPTION "Execute a command"
+#define PLUGIN_VERSION "1.0"
+#define PLUGIN_APPICON "remmina-tool"
diff --git a/plugins/nx/16x16/emblems/remmina-nx.png b/plugins/nx/16x16/emblems/remmina-nx.png
new file mode 100644
index 000000000..9d1b952bb
--- /dev/null
+++ b/plugins/nx/16x16/emblems/remmina-nx.png
Binary files differ
diff --git a/plugins/nx/22x22/emblems/remmina-nx.png b/plugins/nx/22x22/emblems/remmina-nx.png
new file mode 100644
index 000000000..13766daa1
--- /dev/null
+++ b/plugins/nx/22x22/emblems/remmina-nx.png
Binary files differ
diff --git a/plugins/nx/CMakeLists.txt b/plugins/nx/CMakeLists.txt
new file mode 100644
index 000000000..c383fbfad
--- /dev/null
+++ b/plugins/nx/CMakeLists.txt
@@ -0,0 +1,65 @@
+# remmina-plugin-nx - The GTK+ Remote Desktop Client
+#
+# Copyright (C) 2011 Marc-Andre Moreau
+# Copyright (C) 2014-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(REMMINA_PLUGIN_NX_SRCS
+ nx_session.c
+ nx_session.h
+ nx_session_manager.c
+ nx_session_manager.h
+ nx_plugin.c
+ nx_plugin.h
+ )
+
+add_library(remmina-plugin-nx ${REMMINA_PLUGIN_NX_SRCS})
+set_target_properties(remmina-plugin-nx PROPERTIES PREFIX "")
+set_target_properties(remmina-plugin-nx PROPERTIES NO_SONAME 1)
+
+find_package(X11)
+
+include_directories(${REMMINA_COMMON_INCLUDE_DIRS}
+ ${XKBFILE_INCLUDE_DIRS} ${LIBSSH_INCLUDE_DIRS})
+target_link_libraries(remmina-plugin-nx
+ ${REMMINA_COMMON_LIBRARIES}
+ ${XKBFILE_LIBRARIES}
+ ${LIBSSH_LIBRARIES}
+ ${X11_X11_LIB})
+
+install(TARGETS remmina-plugin-nx DESTINATION ${REMMINA_PLUGINDIR})
+
+install(FILES
+ 16x16/emblems/remmina-nx.png
+ DESTINATION ${APPICON16_EMBLEMS_DIR})
+install(FILES
+ 22x22/emblems/remmina-nx.png
+ DESTINATION ${APPICON22_EMBLEMS_DIR})
diff --git a/plugins/nx/nx_plugin.c b/plugins/nx/nx_plugin.c
new file mode 100644
index 000000000..e1f87cd85
--- /dev/null
+++ b/plugins/nx/nx_plugin.c
@@ -0,0 +1,801 @@
+/*
+ * 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 <errno.h>
+#include <pthread.h>
+#include "common/remmina_plugin.h"
+#if GTK_VERSION == 3
+# include <gtk/gtkx.h>
+#endif
+#include <time.h>
+#define LIBSSH_STATIC 1
+#include <libssh/libssh.h>
+#include <X11/Xlib.h>
+#include <X11/XKBlib.h>
+#include <X11/extensions/XKBrules.h>
+#include "nx_plugin.h"
+#include "nx_session_manager.h"
+
+#define REMMINA_PLUGIN_NX_FEATURE_TOOL_SENDCTRLALTDEL 1
+
+/* Forward declaration */
+static RemminaProtocolPlugin remmina_plugin_nx;
+
+RemminaPluginService *remmina_plugin_nx_service = NULL;
+
+static gchar *remmina_kbtype = "pc102/us";
+
+/* When more than one NX sessions is connecting in progress, we need this mutex and array
+ * to prevent them from stealing the same window id.
+ */
+static pthread_mutex_t remmina_nx_init_mutex;
+static GArray *remmina_nx_window_id_array;
+
+/* --------- Support for execution on main thread of GTK functions -------------- */
+
+struct onMainThread_cb_data {
+ enum { FUNC_GTK_SOCKET_ADD_ID } func;
+
+ GtkSocket* sk;
+ Window w;
+
+ /* Mutex for thread synchronization */
+ pthread_mutex_t mu;
+ /* Flag to catch cancellations */
+ gboolean cancelled;
+};
+
+static gboolean onMainThread_cb(struct onMainThread_cb_data *d)
+{
+ TRACE_CALL(__func__);
+ if ( !d->cancelled ) {
+ switch ( d->func ) {
+ case FUNC_GTK_SOCKET_ADD_ID:
+ gtk_socket_add_id( d->sk, d->w );
+ break;
+ }
+ pthread_mutex_unlock( &d->mu );
+ } else {
+ /* thread has been cancelled, so we must free d memory here */
+ g_free( d );
+ }
+ return G_SOURCE_REMOVE;
+}
+
+
+static void onMainThread_cleanup_handler(gpointer data)
+{
+ TRACE_CALL(__func__);
+ struct onMainThread_cb_data *d = data;
+ d->cancelled = TRUE;
+}
+
+
+static void onMainThread_schedule_callback_and_wait( struct onMainThread_cb_data *d )
+{
+ TRACE_CALL(__func__);
+ d->cancelled = FALSE;
+ pthread_cleanup_push( onMainThread_cleanup_handler, d );
+ pthread_mutex_init( &d->mu, NULL );
+ pthread_mutex_lock( &d->mu );
+ gdk_threads_add_idle( (GSourceFunc)onMainThread_cb, (gpointer)d );
+
+ pthread_mutex_lock( &d->mu );
+
+ pthread_cleanup_pop(0);
+ pthread_mutex_unlock( &d->mu );
+ pthread_mutex_destroy( &d->mu );
+}
+
+static void onMainThread_gtk_socket_add_id( GtkSocket* sk, Window w)
+{
+ TRACE_CALL(__func__);
+
+ struct onMainThread_cb_data *d;
+
+ d = (struct onMainThread_cb_data *)g_malloc( sizeof(struct onMainThread_cb_data) );
+ d->func = FUNC_GTK_SOCKET_ADD_ID;
+ d->sk = sk;
+ d->w = w;
+
+ onMainThread_schedule_callback_and_wait( d );
+ g_free(d);
+
+}
+
+
+/* --------------------------------------- */
+
+
+static gboolean remmina_plugin_nx_try_window_id(Window window_id)
+{
+ TRACE_CALL(__func__);
+ gint i;
+ gboolean found = FALSE;
+
+ pthread_mutex_lock(&remmina_nx_init_mutex);
+ for (i = 0; i < remmina_nx_window_id_array->len; i++) {
+ if (g_array_index(remmina_nx_window_id_array, Window, i) == window_id) {
+ found = TRUE;
+ break;
+ }
+ }
+ if (!found) {
+ g_array_append_val(remmina_nx_window_id_array, window_id);
+ }
+ pthread_mutex_unlock(&remmina_nx_init_mutex);
+
+ return (!found);
+}
+
+static void remmina_plugin_nx_remove_window_id(Window window_id)
+{
+ TRACE_CALL(__func__);
+ gint i;
+ gboolean found = FALSE;
+
+ pthread_mutex_lock(&remmina_nx_init_mutex);
+ for (i = 0; i < remmina_nx_window_id_array->len; i++) {
+ if (g_array_index(remmina_nx_window_id_array, Window, i) == window_id) {
+ found = TRUE;
+ break;
+ }
+ }
+ if (found) {
+ g_array_remove_index_fast(remmina_nx_window_id_array, i);
+ }
+ pthread_mutex_unlock(&remmina_nx_init_mutex);
+}
+
+static void remmina_plugin_nx_on_plug_added(GtkSocket *socket, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ remmina_plugin_nx_service->protocol_plugin_emit_signal(gp, "connect");
+}
+
+static void remmina_plugin_nx_on_plug_removed(GtkSocket *socket, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ remmina_plugin_nx_service->protocol_plugin_close_connection(gp);
+}
+
+gboolean remmina_plugin_nx_ssh_auth_callback(gchar **passphrase, gpointer userdata)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp = (RemminaProtocolWidget*)userdata;
+ gint ret;
+
+ /* SSH passwords must not be saved */
+ ret = remmina_plugin_nx_service->protocol_plugin_init_authpwd(gp, REMMINA_AUTHPWD_TYPE_SSH_PRIVKEY, FALSE);
+
+ if (ret != GTK_RESPONSE_OK)
+ return FALSE;
+ *passphrase = remmina_plugin_nx_service->protocol_plugin_init_get_password(gp);
+
+ return TRUE;
+}
+
+static void remmina_plugin_nx_on_proxy_exit(GPid pid, gint status, gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp = (RemminaProtocolWidget*)data;
+
+ remmina_plugin_nx_service->protocol_plugin_close_connection(gp);
+}
+
+static int remmina_plugin_nx_dummy_handler(Display *dsp, XErrorEvent *err)
+{
+ TRACE_CALL(__func__);
+ return 0;
+}
+
+static gboolean remmina_plugin_nx_start_create_notify(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginNxData *gpdata = GET_PLUGIN_DATA(gp);
+
+ gpdata->display = XOpenDisplay(gdk_display_get_name(gdk_display_get_default()));
+ if (gpdata->display == NULL)
+ return FALSE;
+
+ gpdata->orig_handler = XSetErrorHandler(remmina_plugin_nx_dummy_handler);
+
+ XSelectInput(gpdata->display, XDefaultRootWindow(gpdata->display), SubstructureNotifyMask);
+
+ return TRUE;
+}
+
+static gboolean remmina_plugin_nx_monitor_create_notify(RemminaProtocolWidget *gp, const gchar *cmd)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginNxData *gpdata;
+ Atom atom;
+ XEvent xev;
+ Window w;
+ Atom type;
+ int format;
+ unsigned long nitems, rest;
+ unsigned char *data = NULL;
+ struct timespec ts;
+
+ CANCEL_DEFER
+
+ gpdata = GET_PLUGIN_DATA(gp);
+ atom = XInternAtom(gpdata->display, "WM_COMMAND", True);
+ if (atom == None)
+ return FALSE;
+
+ ts.tv_sec = 0;
+ ts.tv_nsec = 200000000;
+
+ while (1) {
+ pthread_testcancel();
+ while (!XPending(gpdata->display)) {
+ nanosleep(&ts, NULL);
+ continue;
+ }
+ XNextEvent(gpdata->display, &xev);
+ if (xev.type != CreateNotify)
+ continue;
+ w = xev.xcreatewindow.window;
+ if (XGetWindowProperty(gpdata->display, w, atom, 0, 255, False, AnyPropertyType, &type, &format, &nitems, &rest,
+ &data) != Success)
+ continue;
+ if (data && strstr((char*)data, cmd) && remmina_plugin_nx_try_window_id(w)) {
+ gpdata->window_id = w;
+ XFree(data);
+ break;
+ }
+ if (data)
+ XFree(data);
+ }
+
+ XSetErrorHandler(gpdata->orig_handler);
+ XCloseDisplay(gpdata->display);
+ gpdata->display = NULL;
+
+ CANCEL_ASYNC
+ return TRUE;
+}
+
+static gint remmina_plugin_nx_wait_signal(RemminaPluginNxData *gpdata)
+{
+ TRACE_CALL(__func__);
+ fd_set set;
+ guchar dummy = 0;
+
+ FD_ZERO(&set);
+ FD_SET(gpdata->event_pipe[0], &set);
+ select(gpdata->event_pipe[0] + 1, &set, NULL, NULL, NULL);
+ if (read(gpdata->event_pipe[0], &dummy, 1)) {
+ }
+ return (gint)dummy;
+}
+
+static gboolean remmina_plugin_nx_start_session(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginNxData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile;
+ RemminaNXSession *nx;
+ const gchar *type, *app;
+ gchar *s1, *s2;
+ gint port;
+ gint ret;
+ gboolean is_empty_list;
+ gint event_type = 0;
+ const gchar *cs;
+ gint i;
+ gboolean disablepasswordstoring;
+
+ remminafile = remmina_plugin_nx_service->protocol_plugin_get_file(gp);
+ nx = gpdata->nx;
+
+ /* Connect */
+
+ remmina_nx_session_set_encryption(nx,
+ remmina_plugin_nx_service->file_get_int(remminafile, "disableencryption", FALSE) ? 0 : 1);
+ remmina_nx_session_set_localport(nx, remmina_plugin_nx_service->pref_get_sshtunnel_port());
+ remmina_nx_session_set_log_callback(nx, remmina_plugin_nx_service->log_printf);
+
+ s2 = remmina_plugin_nx_service->protocol_plugin_start_direct_tunnel(gp, 22, FALSE);
+ if (s2 == NULL) {
+ return FALSE;
+ }
+ remmina_plugin_nx_service->get_server_port(s2, 22, &s1, &port);
+ g_free(s2);
+
+ if (!remmina_nx_session_open(nx, s1, port, remmina_plugin_nx_service->file_get_string(remminafile, "nx_privatekey"),
+ remmina_plugin_nx_ssh_auth_callback, gp)) {
+ g_free(s1);
+ return FALSE;
+ }
+ g_free(s1);
+
+ /* Login */
+
+ s1 = g_strdup(remmina_plugin_nx_service->file_get_string(remminafile, "username"));
+ s2 = g_strdup(remmina_plugin_nx_service->file_get_string(remminafile, "password"));
+
+ if (s1 && s2) {
+ ret = remmina_nx_session_login(nx, s1, s2);
+ }else {
+ g_free(s1);
+ g_free(s2);
+
+ disablepasswordstoring = remmina_plugin_nx_service->file_get_int(remminafile, "disablepasswordstoring", FALSE);
+ ret = remmina_plugin_nx_service->protocol_plugin_init_authuserpwd(gp, FALSE, !disablepasswordstoring);
+
+ if (ret != GTK_RESPONSE_OK)
+ return FALSE;
+
+ s1 = remmina_plugin_nx_service->protocol_plugin_init_get_username(gp);
+ s2 = remmina_plugin_nx_service->protocol_plugin_init_get_password(gp);
+ ret = remmina_nx_session_login(nx, s1, s2);
+ }
+ g_free(s1);
+ g_free(s2);
+
+ if (!ret)
+ return FALSE;
+
+ remmina_plugin_nx_service->protocol_plugin_init_save_cred(gp);
+
+ /* Prepare the session type and application */
+ cs = remmina_plugin_nx_service->file_get_string(remminafile, "exec");
+ if (!cs || g_strcmp0(cs, "GNOME") == 0) {
+ type = "unix-gnome";
+ app = NULL;
+ }else
+ if (g_strcmp0(cs, "KDE") == 0) {
+ type = "unix-kde";
+ app = NULL;
+ }else
+ if (g_strcmp0(cs, "Xfce") == 0) {
+ /* NX does not know Xfce. So we simply launch the Xfce startup program. */
+ type = "unix-application";
+ app = "startxfce4";
+ }else
+ if (g_strcmp0(cs, "Shadow") == 0) {
+ type = "shadow";
+ app = NULL;
+ }else {
+ type = "unix-application";
+ app = cs;
+ }
+
+ /* List sessions */
+
+ gpdata->attach_session = (g_strcmp0(type, "shadow") == 0);
+ while (1) {
+ remmina_nx_session_add_parameter(nx, "type", type);
+ if (!gpdata->attach_session) {
+ remmina_nx_session_add_parameter(nx, "user",
+ remmina_plugin_nx_service->file_get_string(remminafile, "username"));
+ remmina_nx_session_add_parameter(nx, "status", "suspended,running");
+ }
+
+ if (!remmina_nx_session_list(nx)) {
+ return FALSE;
+ }
+
+ is_empty_list = !remmina_nx_session_iter_first(nx, &gpdata->iter);
+ if (is_empty_list && !gpdata->manager_started && !gpdata->attach_session) {
+ event_type = REMMINA_NX_EVENT_START;
+ }else {
+ remmina_nx_session_manager_start(gp);
+ event_type = remmina_plugin_nx_wait_signal(gpdata);
+ if (event_type == REMMINA_NX_EVENT_CANCEL) {
+ return FALSE;
+ }
+ if (event_type == REMMINA_NX_EVENT_TERMINATE) {
+ if (!is_empty_list) {
+ s1 = remmina_nx_session_iter_get(nx, &gpdata->iter, REMMINA_NX_SESSION_COLUMN_ID);
+ remmina_nx_session_add_parameter(nx, "sessionid", s1);
+ g_free(s1);
+ if (!remmina_nx_session_terminate(nx)) {
+ remmina_nx_session_manager_start(gp);
+ remmina_plugin_nx_wait_signal(gpdata);
+ }
+ }
+ continue;
+ }
+ }
+
+ break;
+ }
+
+ /* Start, Restore or Attach, based on the setting and existing session */
+ remmina_nx_session_add_parameter(nx, "type", type);
+ i = remmina_plugin_nx_service->file_get_int(remminafile, "quality", 0);
+ remmina_nx_session_add_parameter(nx, "link", i > 2 ? "lan" : i == 2 ? "adsl" : i == 1 ? "isdn" : "modem");
+ remmina_nx_session_add_parameter(nx, "geometry", "%ix%i",
+ remmina_plugin_nx_service->get_profile_remote_width(gp),
+ remmina_plugin_nx_service->get_profile_remote_height(gp));
+ remmina_nx_session_add_parameter(nx, "keyboard", remmina_kbtype);
+ remmina_nx_session_add_parameter(nx, "client", "linux");
+ remmina_nx_session_add_parameter(nx, "media", "0");
+ remmina_nx_session_add_parameter(nx, "clipboard",
+ remmina_plugin_nx_service->file_get_int(remminafile, "disableclipboard", FALSE) ? "none" : "both");
+
+ switch (event_type) {
+
+ case REMMINA_NX_EVENT_START:
+ if (app)
+ remmina_nx_session_add_parameter(nx, "application", app);
+
+ remmina_nx_session_add_parameter(nx, "session",
+ remmina_plugin_nx_service->file_get_string(remminafile, "name"));
+ remmina_nx_session_add_parameter(nx, "screeninfo", "%ix%ix24+render",
+ remmina_plugin_nx_service->file_get_int(remminafile, "resolution_width", 0),
+ remmina_plugin_nx_service->file_get_int(remminafile, "resolution_height", 0));
+
+ if (!remmina_nx_session_start(nx))
+ return FALSE;
+ break;
+
+ case REMMINA_NX_EVENT_ATTACH:
+ s1 = remmina_nx_session_iter_get(nx, &gpdata->iter, REMMINA_NX_SESSION_COLUMN_ID);
+ remmina_nx_session_add_parameter(nx, "id", s1);
+ g_free(s1);
+
+ s1 = remmina_nx_session_iter_get(nx, &gpdata->iter, REMMINA_NX_SESSION_COLUMN_DISPLAY);
+ remmina_nx_session_add_parameter(nx, "display", s1);
+ g_free(s1);
+
+ if (!remmina_nx_session_attach(nx))
+ return FALSE;
+ break;
+
+ case REMMINA_NX_EVENT_RESTORE:
+ s1 = remmina_nx_session_iter_get(nx, &gpdata->iter, REMMINA_NX_SESSION_COLUMN_ID);
+ remmina_nx_session_add_parameter(nx, "id", s1);
+ g_free(s1);
+
+ remmina_nx_session_add_parameter(nx, "session",
+ remmina_plugin_nx_service->file_get_string(remminafile, "name"));
+
+ if (!remmina_nx_session_restore(nx))
+ return FALSE;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ if (!remmina_nx_session_tunnel_open(nx))
+ return FALSE;
+
+ if (!remmina_plugin_nx_start_create_notify(gp))
+ return FALSE;
+
+ /* nxproxy */
+ if (!remmina_nx_session_invoke_proxy(nx, -1, remmina_plugin_nx_on_proxy_exit, gp))
+ return FALSE;
+
+ /* get the window id of the remote nxagent */
+ if (!remmina_plugin_nx_monitor_create_notify(gp, "nxagent"))
+ return FALSE;
+
+ /* embed it */
+ onMainThread_gtk_socket_add_id(GTK_SOCKET(gpdata->socket), gpdata->window_id);
+
+
+ return TRUE;
+}
+
+static gboolean remmina_plugin_nx_main(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginNxData *gpdata = GET_PLUGIN_DATA(gp);
+ gboolean ret;
+ const gchar *err;
+
+ gpdata->nx = remmina_nx_session_new();
+ ret = remmina_plugin_nx_start_session(gp);
+ if (!ret) {
+ err = remmina_nx_session_get_error(gpdata->nx);
+ if (err) {
+ remmina_plugin_nx_service->protocol_plugin_set_error(gp, "%s", err);
+ }
+ }
+
+ gpdata->thread = 0;
+ return ret;
+}
+
+static gpointer remmina_plugin_nx_main_thread(gpointer data)
+{
+ TRACE_CALL(__func__);
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+
+ CANCEL_ASYNC
+ if (!remmina_plugin_nx_main((RemminaProtocolWidget*)data)) {
+ IDLE_ADD((GSourceFunc)remmina_plugin_nx_service->protocol_plugin_close_connection, data);
+ }
+ return NULL;
+}
+
+static void remmina_plugin_nx_init(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginNxData *gpdata;
+ gint flags;
+
+ gpdata = g_new0(RemminaPluginNxData, 1);
+ g_object_set_data_full(G_OBJECT(gp), "plugin-data", gpdata, g_free);
+
+ gpdata->socket = gtk_socket_new();
+ remmina_plugin_nx_service->protocol_plugin_register_hostkey(gp, gpdata->socket);
+ gtk_widget_show(gpdata->socket);
+ g_signal_connect(G_OBJECT(gpdata->socket), "plug-added", G_CALLBACK(remmina_plugin_nx_on_plug_added), gp);
+ g_signal_connect(G_OBJECT(gpdata->socket), "plug-removed", G_CALLBACK(remmina_plugin_nx_on_plug_removed), gp);
+ gtk_container_add(GTK_CONTAINER(gp), gpdata->socket);
+
+ if (pipe(gpdata->event_pipe)) {
+ g_print("Error creating pipes.\n");
+ gpdata->event_pipe[0] = -1;
+ gpdata->event_pipe[1] = -1;
+ }else {
+ flags = fcntl(gpdata->event_pipe[0], F_GETFL, 0);
+ fcntl(gpdata->event_pipe[0], F_SETFL, flags | O_NONBLOCK);
+ }
+}
+
+static gboolean remmina_plugin_nx_open_connection(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginNxData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile;
+ gint width, height;
+
+ if (!remmina_plugin_nx_service->gtksocket_available()) {
+ remmina_plugin_nx_service->protocol_plugin_set_error(gp,
+ _("Protocol %s is unavailable because GtkSocket only works under Xorg"),
+ remmina_plugin_nx.name);
+ return FALSE;
+ }
+
+ remminafile = remmina_plugin_nx_service->protocol_plugin_get_file(gp);
+
+ width = remmina_plugin_nx_service->get_profile_remote_width(gp);
+ height = remmina_plugin_nx_service->get_profile_remote_height(gp);
+
+ remmina_plugin_nx_service->protocol_plugin_set_width(gp, width);
+ remmina_plugin_nx_service->protocol_plugin_set_height(gp, height);
+ gtk_widget_set_size_request(GTK_WIDGET(gp), width, height);
+
+ gpdata->socket_id = gtk_socket_get_id(GTK_SOCKET(gpdata->socket));
+
+ if (pthread_create(&gpdata->thread, NULL, remmina_plugin_nx_main_thread, gp)) {
+ remmina_plugin_nx_service->protocol_plugin_set_error(gp,
+ "Failed to initialize pthread. Falling back to non-thread mode...");
+ gpdata->thread = 0;
+ return FALSE;
+ }else {
+ return TRUE;
+ }
+}
+
+static gboolean remmina_plugin_nx_close_connection(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginNxData *gpdata = GET_PLUGIN_DATA(gp);
+
+ if (gpdata->thread) {
+ pthread_cancel(gpdata->thread);
+ if (gpdata->thread)
+ pthread_join(gpdata->thread, NULL);
+ }
+ if (gpdata->session_manager_start_handler) {
+ g_source_remove(gpdata->session_manager_start_handler);
+ gpdata->session_manager_start_handler = 0;
+ }
+
+ if (gpdata->window_id) {
+ remmina_plugin_nx_remove_window_id(gpdata->window_id);
+ }
+
+ if (gpdata->nx) {
+ remmina_nx_session_free(gpdata->nx);
+ gpdata->nx = NULL;
+ }
+
+ if (gpdata->display) {
+ XSetErrorHandler(gpdata->orig_handler);
+ XCloseDisplay(gpdata->display);
+ gpdata->display = NULL;
+ }
+ close(gpdata->event_pipe[0]);
+ close(gpdata->event_pipe[1]);
+
+ remmina_plugin_nx_service->protocol_plugin_emit_signal(gp, "disconnect");
+
+ return FALSE;
+}
+
+/* Send CTRL+ALT+DEL keys keystrokes to the plugin socket widget */
+static void remmina_plugin_nx_send_ctrlaltdel(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ guint keys[] = { GDK_KEY_Control_L, GDK_KEY_Alt_L, GDK_KEY_Delete };
+ RemminaPluginNxData *gpdata = GET_PLUGIN_DATA(gp);
+
+ remmina_plugin_nx_service->protocol_plugin_send_keys_signals(gpdata->socket,
+ keys, G_N_ELEMENTS(keys), GDK_KEY_PRESS | GDK_KEY_RELEASE);
+}
+
+static gboolean remmina_plugin_nx_query_feature(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
+{
+ TRACE_CALL(__func__);
+ return TRUE;
+}
+
+static void remmina_plugin_nx_call_feature(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
+{
+ TRACE_CALL(__func__);
+ switch (feature->id) {
+ case REMMINA_PLUGIN_NX_FEATURE_TOOL_SENDCTRLALTDEL:
+ remmina_plugin_nx_send_ctrlaltdel(gp);
+ break;
+ default:
+ break;
+ }
+}
+
+/* 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 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_plugin_nx_basic_settings[] =
+{
+ { REMMINA_PROTOCOL_SETTING_TYPE_SERVER, "server", NULL, FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_FILE, "nx_privatekey", N_("Identity file"), 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_RESOLUTION, "resolution", NULL, FALSE, GINT_TO_POINTER(1), NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "quality", N_("Quality"), FALSE, quality_list, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_COMBO, "exec", N_("Startup program"), FALSE, "GNOME,KDE,Xfce,Shadow", 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_plugin_nx_advanced_settings[] =
+{
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableclipboard", N_("Disable clipboard sync"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableencryption", N_("Disable encryption"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "showcursor", N_("Use local cursor"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disablepasswordstoring", N_("Disable password storing"), 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_plugin_nx_features[] =
+{
+ { REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_PLUGIN_NX_FEATURE_TOOL_SENDCTRLALTDEL, N_("Send Ctrl+Alt+Delete"), NULL, NULL },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_END, 0, NULL, NULL, NULL }
+};
+
+/* Protocol plugin definition and features */
+static RemminaProtocolPlugin remmina_plugin_nx =
+{
+ REMMINA_PLUGIN_TYPE_PROTOCOL, // Type
+ "NX", // Name
+ N_("NX - NX Technology"), // Description
+ GETTEXT_PACKAGE, // Translation domain
+ VERSION, // Version number
+ "remmina-nx", // Icon for normal connection
+ "remmina-nx", // Icon for SSH connection
+ remmina_plugin_nx_basic_settings, // Array for basic settings
+ remmina_plugin_nx_advanced_settings, // Array for advanced settings
+ REMMINA_PROTOCOL_SSH_SETTING_TUNNEL, // SSH settings type
+ remmina_plugin_nx_features, // Array for available features
+ remmina_plugin_nx_init, // Plugin initialization
+ remmina_plugin_nx_open_connection, // Plugin open connection
+ remmina_plugin_nx_close_connection, // Plugin close connection
+ remmina_plugin_nx_query_feature, // Query for available features
+ remmina_plugin_nx_call_feature, // Call a feature
+ NULL, // Send a keystroke
+ NULL // Screenshot
+};
+
+G_MODULE_EXPORT gboolean
+remmina_plugin_entry(RemminaPluginService *service)
+{
+ TRACE_CALL(__func__);
+ Display *dpy;
+ XkbRF_VarDefsRec vd;
+ gchar *s;
+
+ remmina_plugin_nx_service = service;
+
+ bindtextdomain(GETTEXT_PACKAGE, REMMINA_RUNTIME_LOCALEDIR);
+ bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
+
+ if ((dpy = XkbOpenDisplay(NULL, NULL, NULL, NULL, NULL, NULL)) != NULL) {
+ if (XkbRF_GetNamesProp(dpy, NULL, &vd)) {
+ remmina_kbtype = g_strdup_printf("%s/%s", vd.model, vd.layout);
+ if (vd.layout)
+ XFree(vd.layout);
+ if (vd.model)
+ XFree(vd.model);
+ if (vd.variant)
+ XFree(vd.variant);
+ if (vd.options)
+ XFree(vd.options);
+ s = strchr(remmina_kbtype, ',');
+ if (s)
+ *s = '\0';
+ /* g_print("NX: detected keyboard type %s\n", remmina_kbtype); */
+ }
+ XCloseDisplay(dpy);
+ }
+
+ if (!service->register_plugin((RemminaPlugin*)&remmina_plugin_nx)) {
+ return FALSE;
+ }
+
+ ssh_init();
+ pthread_mutex_init(&remmina_nx_init_mutex, NULL);
+ remmina_nx_window_id_array = g_array_new(FALSE, TRUE, sizeof(Window));
+
+ return TRUE;
+}
+
diff --git a/plugins/nx/nx_plugin.h b/plugins/nx/nx_plugin.h
new file mode 100644
index 000000000..9a9e60223
--- /dev/null
+++ b/plugins/nx/nx_plugin.h
@@ -0,0 +1,81 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010 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
+
+#define GET_PLUGIN_DATA(gp) (RemminaPluginNxData*)g_object_get_data(G_OBJECT(gp), "plugin-data");
+
+G_BEGIN_DECLS
+
+#include "nx_session.h"
+
+typedef enum {
+ REMMINA_NX_EVENT_CANCEL,
+ REMMINA_NX_EVENT_START,
+ REMMINA_NX_EVENT_RESTORE,
+ REMMINA_NX_EVENT_ATTACH,
+ REMMINA_NX_EVENT_TERMINATE
+} RemminaNXEventType;
+
+typedef struct _RemminaPluginNxData {
+ GtkWidget *socket;
+ gint socket_id;
+
+ pthread_t thread;
+
+ RemminaNXSession *nx;
+
+ Display *display;
+ Window window_id;
+ int (*orig_handler)(Display *, XErrorEvent *);
+
+ /* Session Manager data */
+ gboolean manager_started;
+ GtkWidget *manager_dialog;
+ gboolean manager_selected;
+
+ /* Communication between the NX thread and the session manager */
+ gint event_pipe[2];
+ guint session_manager_start_handler;
+ gboolean attach_session;
+ GtkTreeIter iter;
+ gint default_response;
+} RemminaPluginNxData;
+
+extern RemminaPluginService *remmina_plugin_nx_service;
+
+G_END_DECLS
+
diff --git a/plugins/nx/nx_session.c b/plugins/nx/nx_session.c
new file mode 100644
index 000000000..fbf8b26a0
--- /dev/null
+++ b/plugins/nx/nx_session.c
@@ -0,0 +1,1021 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010 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 <errno.h>
+#include <pthread.h>
+#include "common/remmina_plugin.h"
+#include <glib/gstdio.h>
+#define LIBSSH_STATIC 1
+#include <libssh/libssh.h>
+#include "nx_session.h"
+
+/* Some missing stuff in libssh */
+#define REMMINA_SSH_TYPE_DSS 1
+#define REMMINA_SSH_TYPE_RSA 2
+
+static gboolean remmina_get_keytype(const gchar *private_key_file, gint *keytype, gboolean *encrypted)
+{
+ TRACE_CALL(__func__);
+ FILE *fp;
+ gchar buf1[100], buf2[100];
+
+ if ((fp = g_fopen(private_key_file, "r")) == NULL) {
+ return FALSE;
+ }
+ if (!fgets(buf1, sizeof(buf1), fp) || !fgets(buf2, sizeof(buf2), fp)) {
+ fclose(fp);
+ return FALSE;
+ }
+ fclose(fp);
+
+ if (strstr(buf1, "BEGIN RSA"))
+ *keytype = REMMINA_SSH_TYPE_RSA;
+ else if (strstr(buf1, "BEGIN DSA"))
+ *keytype = REMMINA_SSH_TYPE_DSS;
+ else
+ return FALSE;
+
+ *encrypted = (strstr(buf2, "ENCRYPTED") ? TRUE : FALSE);
+
+ return TRUE;
+}
+
+/*****/
+
+static const gchar nx_default_private_key[] = "-----BEGIN DSA PRIVATE KEY-----\n"
+ "MIIBuwIBAAKBgQCXv9AzQXjxvXWC1qu3CdEqskX9YomTfyG865gb4D02ZwWuRU/9\n"
+ "C3I9/bEWLdaWgJYXIcFJsMCIkmWjjeSZyTmeoypI1iLifTHUxn3b7WNWi8AzKcVF\n"
+ "aBsBGiljsop9NiD1mEpA0G+nHHrhvTXz7pUvYrsrXcdMyM6rxqn77nbbnwIVALCi\n"
+ "xFdHZADw5KAVZI7r6QatEkqLAoGBAI4L1TQGFkq5xQ/nIIciW8setAAIyrcWdK/z\n"
+ "5/ZPeELdq70KDJxoLf81NL/8uIc4PoNyTRJjtT3R4f8Az1TsZWeh2+ReCEJxDWgG\n"
+ "fbk2YhRqoQTtXPFsI4qvzBWct42WonWqyyb1bPBHk+JmXFscJu5yFQ+JUVNsENpY\n"
+ "+Gkz3HqTAoGANlgcCuA4wrC+3Cic9CFkqiwO/Rn1vk8dvGuEQqFJ6f6LVfPfRTfa\n"
+ "QU7TGVLk2CzY4dasrwxJ1f6FsT8DHTNGnxELPKRuLstGrFY/PR7KeafeFZDf+fJ3\n"
+ "mbX5nxrld3wi5titTnX+8s4IKv29HJguPvOK/SI7cjzA+SqNfD7qEo8CFDIm1xRf\n"
+ "8xAPsSKs6yZ6j1FNklfu\n"
+ "-----END DSA PRIVATE KEY-----\n";
+
+static const gchar nx_hello_server_msg[] = "hello nxserver - version ";
+
+struct _RemminaNXSession {
+ /* Common SSH members */
+ ssh_session session;
+ ssh_channel channel;
+ gchar *server;
+ gchar *error;
+ RemminaNXLogCallback log_callback;
+
+ /* Tunnel related members */
+ pthread_t thread;
+ gboolean running;
+ gint server_sock;
+
+ /* NX related members */
+ GHashTable *session_parameters;
+
+ GString *response;
+ gint response_pos;
+ gint status;
+ gint encryption;
+ gint localport;
+
+ gchar *version;
+ gchar *session_id;
+ gint session_display;
+ gchar *proxy_cookie;
+
+ gboolean allow_start;
+ GtkListStore *session_list;
+ gint session_list_state;
+
+ GPid proxy_pid;
+ guint proxy_watch_source;
+};
+
+RemminaNXSession*
+remmina_nx_session_new(void)
+{
+ TRACE_CALL(__func__);
+ RemminaNXSession *nx;
+
+ nx = g_new0(RemminaNXSession, 1);
+
+ nx->session_parameters = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+ nx->response = g_string_new(NULL);
+ nx->status = -1;
+ nx->encryption = 1;
+ nx->server_sock = -1;
+
+ return nx;
+}
+
+void remmina_nx_session_free(RemminaNXSession *nx)
+{
+ TRACE_CALL(__func__);
+ pthread_t thread;
+
+ if (nx->proxy_watch_source) {
+ g_source_remove(nx->proxy_watch_source);
+ nx->proxy_watch_source = 0;
+ }
+ if (nx->proxy_pid) {
+ kill(nx->proxy_pid, SIGTERM);
+ g_spawn_close_pid(nx->proxy_pid);
+ nx->proxy_pid = 0;
+ }
+ thread = nx->thread;
+ if (thread) {
+ nx->running = FALSE;
+ pthread_cancel(thread);
+ pthread_join(thread, NULL);
+ nx->thread = 0;
+ }
+ if (nx->channel) {
+ ssh_channel_close(nx->channel);
+ ssh_channel_free(nx->channel);
+ }
+ if (nx->server_sock >= 0) {
+ close(nx->server_sock);
+ nx->server_sock = -1;
+ }
+
+ g_free(nx->server);
+ g_free(nx->error);
+ g_hash_table_destroy(nx->session_parameters);
+ g_string_free(nx->response, TRUE);
+ g_free(nx->version);
+ g_free(nx->session_id);
+ g_free(nx->proxy_cookie);
+
+ if (nx->session_list) {
+ g_object_unref(nx->session_list);
+ nx->session_list = NULL;
+ }
+ if (nx->session) {
+ ssh_free(nx->session);
+ nx->session = NULL;
+ }
+ g_free(nx);
+}
+
+static void remmina_nx_session_set_error(RemminaNXSession *nx, const gchar *fmt)
+{
+ TRACE_CALL(__func__);
+ const gchar *err;
+
+ if (nx->error)
+ g_free(nx->error);
+ err = ssh_get_error(nx->session);
+ nx->error = g_strdup_printf(fmt, err);
+}
+
+static void remmina_nx_session_set_application_error(RemminaNXSession *nx, const gchar *fmt, ...)
+{
+ TRACE_CALL(__func__);
+ va_list args;
+
+ if (nx->error) g_free(nx->error);
+ va_start(args, fmt);
+ nx->error = g_strdup_vprintf(fmt, args);
+ va_end(args);
+}
+
+gboolean remmina_nx_session_has_error(RemminaNXSession *nx)
+{
+ TRACE_CALL(__func__);
+ return (nx->error != NULL);
+}
+
+const gchar*
+remmina_nx_session_get_error(RemminaNXSession *nx)
+{
+ TRACE_CALL(__func__);
+ return nx->error;
+}
+
+void remmina_nx_session_clear_error(RemminaNXSession *nx)
+{
+ TRACE_CALL(__func__);
+ if (nx->error) {
+ g_free(nx->error);
+ nx->error = NULL;
+ }
+}
+
+void remmina_nx_session_set_encryption(RemminaNXSession *nx, gint encryption)
+{
+ TRACE_CALL(__func__);
+ nx->encryption = encryption;
+}
+
+void remmina_nx_session_set_localport(RemminaNXSession *nx, gint localport)
+{
+ TRACE_CALL(__func__);
+ nx->localport = localport;
+}
+
+void remmina_nx_session_set_log_callback(RemminaNXSession *nx, RemminaNXLogCallback log_callback)
+{
+ TRACE_CALL(__func__);
+ nx->log_callback = log_callback;
+}
+
+static gboolean remmina_nx_session_get_response(RemminaNXSession *nx)
+{
+ TRACE_CALL(__func__);
+ struct timeval timeout;
+ ssh_channel ch[2];
+ ssh_buffer buffer;
+ gint len;
+ gint is_stderr;
+
+ timeout.tv_sec = 60;
+ timeout.tv_usec = 0;
+ ch[0] = nx->channel;
+ ch[1] = NULL;
+ ssh_channel_select(ch, NULL, NULL, &timeout);
+
+ is_stderr = 0;
+ while (is_stderr <= 1) {
+ len = ssh_channel_poll(nx->channel, is_stderr);
+ if (len == SSH_ERROR) {
+ remmina_nx_session_set_error(nx, "Error reading channel: %s");
+ return FALSE;
+ }
+ if (len > 0)
+ break;
+ is_stderr++;
+ }
+ if (is_stderr > 1)
+ return FALSE;
+
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ buffer = buffer_new();
+ len = channel_read_buffer(nx->channel, buffer, len, is_stderr);
+ G_GNUC_END_IGNORE_DEPRECATIONS
+ if (len <= 0) {
+ remmina_nx_session_set_application_error(nx, "Channel closed.");
+ return FALSE;
+ }
+ if (len > 0) {
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ g_string_append_len(nx->response, (const gchar*)buffer_get(buffer), len);
+ G_GNUC_END_IGNORE_DEPRECATIONS
+ }
+
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ buffer_free(buffer);
+ G_GNUC_END_IGNORE_DEPRECATIONS
+ return TRUE;
+}
+
+static void remmina_nx_session_parse_session_list_line(RemminaNXSession *nx, const gchar *line)
+{
+ TRACE_CALL(__func__);
+ gchar *p1, *p2;
+ gchar *val;
+ gint i;
+ GtkTreeIter iter;
+
+ p1 = (char*)line;
+ while (*p1 == ' ')
+ p1++;
+ if (*p1 == '\0')
+ return;
+
+ gtk_list_store_append(nx->session_list, &iter);
+
+ p1 = (char*)line;
+ for (i = 0; i < 7; i++) {
+ p2 = strchr(p1, ' ');
+ if (!p2)
+ return;
+ val = g_strndup(p1, (gint)(p2 - p1));
+ switch (i) {
+ case 0:
+ gtk_list_store_set(nx->session_list, &iter, REMMINA_NX_SESSION_COLUMN_DISPLAY, val, -1);
+ break;
+ case 1:
+ gtk_list_store_set(nx->session_list, &iter, REMMINA_NX_SESSION_COLUMN_TYPE, val, -1);
+ break;
+ case 2:
+ gtk_list_store_set(nx->session_list, &iter, REMMINA_NX_SESSION_COLUMN_ID, val, -1);
+ break;
+ case 6:
+ gtk_list_store_set(nx->session_list, &iter, REMMINA_NX_SESSION_COLUMN_STATUS, val, -1);
+ break;
+ default:
+ break;
+ }
+ g_free(val);
+
+ while (*p2 == ' ')
+ p2++;
+ p1 = p2;
+ }
+ /* The last name column might contains space so it's not in the above loop. We simply rtrim it here. */
+ i = strlen(p1);
+ if (i < 1)
+ return;
+ p2 = p1 + i - 1;
+ while (*p2 == ' ' && p2 > p1)
+ p2--;
+ val = g_strndup(p1, (gint)(p2 - p1 + 1));
+ gtk_list_store_set(nx->session_list, &iter, REMMINA_NX_SESSION_COLUMN_NAME, val, -1);
+ g_free(val);
+}
+
+static gint remmina_nx_session_parse_line(RemminaNXSession *nx, const gchar *line, gchar **valueptr)
+{
+ TRACE_CALL(__func__);
+ gchar *s;
+ gchar *ptr;
+ gint status;
+
+ *valueptr = NULL;
+
+ /* Get the server version from the initial line */
+ if (!nx->version) {
+ s = g_ascii_strdown(line, -1);
+ ptr = strstr(s, nx_hello_server_msg);
+ if (!ptr) {
+ /* Try to use a default version */
+ nx->version = g_strdup("3.3.0");
+ } else {
+ nx->version = g_strdup(ptr + strlen(nx_hello_server_msg));
+ ptr = strchr(nx->version, ' ');
+ if (ptr)
+ *ptr = '\0';
+ /* NoMachine NX append a dash+subversion. Need to be removed. */
+ ptr = strchr(nx->version, '-');
+ if (ptr)
+ *ptr = '\0';
+ }
+ g_free(s);
+ return nx->status;
+ }
+
+ if (sscanf(line, "NX> %i ", &status) < 1) {
+ if (nx->session_list_state && nx->session_list) {
+ if (nx->session_list_state == 1 && strncmp(line, "----", 4) == 0) {
+ nx->session_list_state = 2;
+ } else if (nx->session_list_state == 2) {
+ remmina_nx_session_parse_session_list_line(nx, line);
+ }
+ return -1;
+ }
+ return nx->status;
+ }
+
+ nx->session_list_state = 0;
+ nx->status = status;
+ ptr = strchr(line, ':');
+ if (!ptr)
+ return status;
+ *valueptr = ptr + 2;
+ return status;
+}
+
+static gchar*
+remmina_nx_session_get_line(RemminaNXSession *nx)
+{
+ TRACE_CALL(__func__);
+ gchar *line;
+ gchar *pos, *ptr;
+ gint len;
+ gint l;
+
+ if (nx->response_pos >= nx->response->len)
+ return NULL;
+
+ pos = nx->response->str + nx->response_pos;
+ if ((ptr = strchr(pos, '\n')) == NULL)
+ return NULL;
+
+ len = ((gint)(ptr - pos)) + 1;
+ line = g_strndup(pos, len - 1);
+
+ l = strlen(line);
+ if (l > 0 && line[l - 1] == '\r') {
+ line[l - 1] = '\0';
+ }
+
+ nx->response_pos += len;
+
+ return line;
+}
+
+static gint remmina_nx_session_parse_response(RemminaNXSession *nx)
+{
+ TRACE_CALL(__func__);
+ gchar *line;
+ gchar *pos, *p;
+ gint status = -1;
+
+ if (nx->response_pos >= nx->response->len)
+ return -1;
+
+ while ((line = remmina_nx_session_get_line(nx)) != NULL) {
+ if (nx->log_callback)
+ nx->log_callback("[NX] %s\n", line);
+
+ status = remmina_nx_session_parse_line(nx, line, &p);
+ if (status == 500) {
+ /* 500: Last operation failed. Should be ignored. */
+ } else if (status >= 400 && status <= 599) {
+ remmina_nx_session_set_application_error(nx, "%s", line);
+ } else {
+ switch (status) {
+ case 127: /* Session list */
+ nx->session_list_state = 1;
+ break;
+ case 148: /* Server capacity not reached for user xxx */
+ nx->session_list_state = 0;
+ nx->allow_start = TRUE;
+ break;
+ case 700:
+ nx->session_id = g_strdup(p);
+ break;
+ case 705:
+ nx->session_display = atoi(p);
+ break;
+ case 701:
+ nx->proxy_cookie = g_strdup(p);
+ break;
+ }
+ }
+ g_free(line);
+
+ nx->status = status;
+ }
+
+ pos = nx->response->str + nx->response_pos;
+ if (sscanf(pos, "NX> %i ", &status) < 1) {
+ status = nx->status;
+ } else {
+ if (nx->log_callback)
+ nx->log_callback("[NX] %s\n", pos);
+ nx->response_pos += 8;
+ }
+ nx->status = -1;
+ return status;
+}
+
+static gint remmina_nx_session_expect_status2(RemminaNXSession *nx, gint status, gint status2)
+{
+ TRACE_CALL(__func__);
+ gint response;
+
+ while ((response = remmina_nx_session_parse_response(nx)) != status && response != status2) {
+ if (response == 999)
+ break;
+ if (!remmina_nx_session_get_response(nx))
+ return -1;
+ }
+ nx->session_list_state = 0;
+ if (remmina_nx_session_has_error(nx))
+ return -1;
+ return response;
+}
+
+static gboolean remmina_nx_session_expect_status(RemminaNXSession *nx, gint status)
+{
+ TRACE_CALL(__func__);
+ return (remmina_nx_session_expect_status2(nx, status, 0) == status);
+}
+
+static void remmina_nx_session_send_command(RemminaNXSession *nx, const gchar *cmdfmt, ...)
+{
+ TRACE_CALL(__func__);
+ va_list args;
+ gchar *cmd;
+
+ va_start(args, cmdfmt);
+ cmd = g_strdup_vprintf(cmdfmt, args);
+ ssh_channel_write(nx->channel, cmd, strlen(cmd));
+ g_free(cmd);
+
+ ssh_set_fd_towrite(nx->session);
+ ssh_channel_write(nx->channel, "\n", 1);
+ va_end(args);
+}
+
+gboolean remmina_nx_session_open(RemminaNXSession *nx, const gchar *server, guint port, const gchar *private_key_file,
+ RemminaNXPassphraseCallback passphrase_func, gpointer userdata)
+{
+ TRACE_CALL(__func__);
+ gint ret;
+ ssh_key priv_key;
+ gint keytype;
+ gboolean encrypted;
+ gchar *passphrase = NULL;
+
+ nx->session = ssh_new();
+ ssh_options_set(nx->session, SSH_OPTIONS_HOST, server);
+ ssh_options_set(nx->session, SSH_OPTIONS_PORT, &port);
+ ssh_options_set(nx->session, SSH_OPTIONS_USER, "nx");
+
+ if (private_key_file && private_key_file[0]) {
+ if (!remmina_get_keytype(private_key_file, &keytype, &encrypted)) {
+ remmina_nx_session_set_application_error(nx, "Invalid private key file.");
+ return FALSE;
+ }
+ if (encrypted && !passphrase_func(&passphrase, userdata)) {
+ return FALSE;
+ }
+ if ( ssh_pki_import_privkey_file(private_key_file, (passphrase ? passphrase : ""), NULL, NULL, &priv_key) != SSH_OK ) {
+ remmina_nx_session_set_application_error(nx, "Error importing private key from file.");
+ g_free(passphrase);
+ return FALSE;
+ }
+ g_free(passphrase);
+ } else {
+ /* Use NoMachine's default nx private key */
+ if ( ssh_pki_import_privkey_base64(nx_default_private_key, NULL, NULL, NULL, &priv_key) != SSH_OK ) {
+ remmina_nx_session_set_application_error(nx, "Failed to import NX default private key.");
+ return FALSE;
+ }
+ }
+
+ if (ssh_connect(nx->session)) {
+ ssh_key_free(priv_key);
+ remmina_nx_session_set_error(nx, "Failed to startup SSH session: %s");
+ return FALSE;
+ }
+
+ ret = ssh_userauth_publickey(nx->session, NULL, priv_key);
+
+ ssh_key_free(priv_key);
+
+ if (ret != SSH_AUTH_SUCCESS) {
+ remmina_nx_session_set_error(nx, "NX SSH authentication failed: %s");
+ return FALSE;
+ }
+
+ if ((nx->channel = ssh_channel_new(nx->session)) == NULL || ssh_channel_open_session(nx->channel) != SSH_OK) {
+ return FALSE;
+ }
+
+ if (ssh_channel_request_shell(nx->channel) != SSH_OK) {
+ return FALSE;
+ }
+
+ /* NX server starts the session with an initial 105 status */
+ if (!remmina_nx_session_expect_status(nx, 105))
+ return FALSE;
+
+ /* Say hello to the NX server */
+ remmina_nx_session_send_command(nx, "HELLO NXCLIENT - Version %s", nx->version);
+ if (!remmina_nx_session_expect_status(nx, 105))
+ return FALSE;
+
+ /* Set the NX session environment */
+ remmina_nx_session_send_command(nx, "SET SHELL_MODE SHELL");
+ if (!remmina_nx_session_expect_status(nx, 105))
+ return FALSE;
+ remmina_nx_session_send_command(nx, "SET AUTH_MODE PASSWORD");
+ if (!remmina_nx_session_expect_status(nx, 105))
+ return FALSE;
+
+ nx->server = g_strdup(server);
+
+ return TRUE;
+}
+
+gboolean remmina_nx_session_login(RemminaNXSession *nx, const gchar *username, const gchar *password)
+{
+ TRACE_CALL(__func__);
+ gint response;
+
+ /* Login to the NX server */
+ remmina_nx_session_send_command(nx, "login");
+ if (!remmina_nx_session_expect_status(nx, 101))
+ return FALSE;
+ remmina_nx_session_send_command(nx, username);
+ /* NoMachine Testdrive does not prompt for password, in which case 105 response is received without 102 */
+ response = remmina_nx_session_expect_status2(nx, 102, 105);
+ if (response == 102) {
+ remmina_nx_session_send_command(nx, password);
+ if (!remmina_nx_session_expect_status(nx, 105))
+ return FALSE;
+ } else if (response != 105) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void remmina_nx_session_add_parameter(RemminaNXSession *nx, const gchar *name, const gchar *valuefmt, ...)
+{
+ TRACE_CALL(__func__);
+ va_list args;
+ gchar *value;
+
+ va_start(args, valuefmt);
+ value = g_strdup_vprintf(valuefmt, args);
+ g_hash_table_insert(nx->session_parameters, g_strdup(name), value);
+ va_end(args);
+}
+
+static gboolean remmina_nx_session_send_session_command(RemminaNXSession *nx, const gchar *cmd_type, gint response)
+{
+ TRACE_CALL(__func__);
+ GString *cmd;
+ GHashTableIter iter;
+ gchar *key, *value;
+
+ cmd = g_string_new(cmd_type);
+ g_hash_table_iter_init(&iter, nx->session_parameters);
+ while (g_hash_table_iter_next(&iter, (gpointer*)&key, (gpointer*)&value)) {
+ g_string_append_printf(cmd, " --%s=\"%s\"", key, value);
+ }
+
+ remmina_nx_session_send_command(nx, cmd->str);
+ g_string_free(cmd, TRUE);
+
+ g_hash_table_remove_all(nx->session_parameters);
+
+ return remmina_nx_session_expect_status(nx, response);
+}
+
+gboolean remmina_nx_session_list(RemminaNXSession *nx)
+{
+ TRACE_CALL(__func__);
+ gboolean ret;
+
+ if (nx->session_list == NULL) {
+ nx->session_list = gtk_list_store_new(REMMINA_NX_SESSION_N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
+ G_TYPE_STRING, G_TYPE_STRING);
+ } else {
+ gtk_list_store_clear(nx->session_list);
+ }
+ ret = remmina_nx_session_send_session_command(nx, "listsession", 105);
+
+ return ret;
+}
+
+void remmina_nx_session_set_tree_view(RemminaNXSession *nx, GtkTreeView *tree)
+{
+ TRACE_CALL(__func__);
+ gtk_tree_view_set_model(tree, GTK_TREE_MODEL(nx->session_list));
+}
+
+gboolean remmina_nx_session_iter_first(RemminaNXSession *nx, GtkTreeIter *iter)
+{
+ TRACE_CALL(__func__);
+ if (!nx->session_list)
+ return FALSE;
+ return gtk_tree_model_get_iter_first(GTK_TREE_MODEL(nx->session_list), iter);
+}
+
+gboolean remmina_nx_session_iter_next(RemminaNXSession *nx, GtkTreeIter *iter)
+{
+ TRACE_CALL(__func__);
+ if (!nx->session_list)
+ return FALSE;
+ return gtk_tree_model_iter_next(GTK_TREE_MODEL(nx->session_list), iter);
+}
+
+gchar*
+remmina_nx_session_iter_get(RemminaNXSession *nx, GtkTreeIter *iter, gint column)
+{
+ TRACE_CALL(__func__);
+ gchar *val;
+
+ gtk_tree_model_get(GTK_TREE_MODEL(nx->session_list), iter, column, &val, -1);
+ return val;
+}
+
+void remmina_nx_session_iter_set(RemminaNXSession *nx, GtkTreeIter *iter, gint column, const gchar *data)
+{
+ TRACE_CALL(__func__);
+ gtk_list_store_set(nx->session_list, iter, column, data, -1);
+}
+
+gboolean remmina_nx_session_allow_start(RemminaNXSession *nx)
+{
+ TRACE_CALL(__func__);
+ return nx->allow_start;
+}
+
+static void remmina_nx_session_add_common_parameters(RemminaNXSession *nx)
+{
+ TRACE_CALL(__func__);
+ gchar *value;
+
+ /* Add fixed session parameters for startsession */
+ remmina_nx_session_add_parameter(nx, "cache", "16M");
+ remmina_nx_session_add_parameter(nx, "images", "64M");
+ remmina_nx_session_add_parameter(nx, "render", "1");
+ remmina_nx_session_add_parameter(nx, "backingstore", "1");
+ remmina_nx_session_add_parameter(nx, "agent_server", "");
+ remmina_nx_session_add_parameter(nx, "agent_user", "");
+ remmina_nx_session_add_parameter(nx, "agent_password", "");
+
+ value = g_strdup_printf("%i", nx->encryption);
+ remmina_nx_session_add_parameter(nx, "encryption", value);
+ g_free(value);
+}
+
+gboolean remmina_nx_session_start(RemminaNXSession *nx)
+{
+ TRACE_CALL(__func__);
+ remmina_nx_session_add_common_parameters(nx);
+ return remmina_nx_session_send_session_command(nx, "startsession", 105);
+}
+
+gboolean remmina_nx_session_attach(RemminaNXSession *nx)
+{
+ TRACE_CALL(__func__);
+ remmina_nx_session_add_common_parameters(nx);
+ return remmina_nx_session_send_session_command(nx, "attachsession", 105);
+}
+
+gboolean remmina_nx_session_restore(RemminaNXSession *nx)
+{
+ TRACE_CALL(__func__);
+ remmina_nx_session_add_common_parameters(nx);
+ return remmina_nx_session_send_session_command(nx, "restoresession", 105);
+}
+
+gboolean remmina_nx_session_terminate(RemminaNXSession *nx)
+{
+ TRACE_CALL(__func__);
+ return remmina_nx_session_send_session_command(nx, "terminate", 105);
+}
+
+static gpointer remmina_nx_session_tunnel_main_thread(gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaNXSession *nx = (RemminaNXSession*)data;
+ gchar *ptr;
+ ssize_t len = 0, lenw = 0;
+ fd_set set;
+ struct timeval timeout;
+ ssh_channel channels[2];
+ ssh_channel channels_out[2];
+ gint sock;
+ gint ret;
+ gchar buffer[10240];
+ gchar socketbuffer[10240];
+ gchar *socketbuffer_ptr = NULL;
+ gint socketbuffer_len = 0;
+
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+ pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
+
+ /* Accept a local connection */
+ sock = accept(nx->server_sock, NULL, NULL);
+ if (sock < 0) {
+ remmina_nx_session_set_application_error(nx, "Failed to accept local socket");
+ nx->thread = 0;
+ return NULL;
+ }
+ close(nx->server_sock);
+ nx->server_sock = -1;
+
+ channels[0] = nx->channel;
+ channels[1] = NULL;
+
+ /* Start the tunnel data transmittion */
+ while (nx->running) {
+ timeout.tv_sec = 1;
+ timeout.tv_usec = 0;
+
+ FD_ZERO(&set);
+ FD_SET(sock, &set);
+
+ ret = ssh_select(channels, channels_out, sock + 1, &set, &timeout);
+ if (!nx->running)
+ break;
+ if (ret == SSH_EINTR)
+ continue;
+ if (ret == -1)
+ break;
+
+ if (FD_ISSET(sock, &set)) {
+ len = read(sock, buffer, sizeof(buffer));
+ if (len == 0)
+ nx->running = FALSE;
+ else if (len > 0) {
+ for (ptr = buffer, lenw = 0; len > 0; len -= lenw, ptr += lenw) {
+ ssh_set_fd_towrite(nx->session);
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ lenw = channel_write(channels[0], (char*)ptr, len);
+ G_GNUC_END_IGNORE_DEPRECATIONS
+ if (lenw <= 0) {
+ nx->running = FALSE;
+ break;
+ }
+ }
+ }
+ }
+
+ if (!nx->running)
+ break;
+
+ if (channels_out[0] && socketbuffer_len <= 0) {
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ len = channel_read_nonblocking(channels_out[0], socketbuffer, sizeof(socketbuffer), 0);
+ G_GNUC_END_IGNORE_DEPRECATIONS
+ if (len == SSH_ERROR || len == SSH_EOF) {
+ nx->running = FALSE;
+ break;
+ } else if (len > 0) {
+ socketbuffer_ptr = socketbuffer;
+ socketbuffer_len = len;
+ } else {
+ /* Clean up the stderr buffer in case FreeNX send something there */
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ len = channel_read_nonblocking(channels_out[0], buffer, sizeof(buffer), 1);
+ if (len == SSH_ERROR || len == SSH_EOF) {
+ nx->running = FALSE;
+ break;
+ }
+ G_GNUC_END_IGNORE_DEPRECATIONS
+ }
+ }
+
+ if (nx->running && socketbuffer_len > 0) {
+ for (lenw = 0; socketbuffer_len > 0; socketbuffer_len -= lenw, socketbuffer_ptr += lenw) {
+ lenw = write(sock, socketbuffer_ptr, socketbuffer_len);
+ if (lenw == -1 && errno == EAGAIN && nx->running) {
+ /* Sometimes we cannot write to a socket (always EAGAIN), probably because it's internal
+ * buffer is full. We need read the pending bytes from the socket first. so here we simply
+ * break, leave the buffer there, and continue with other data */
+ break;
+ }
+ if (lenw <= 0) {
+ nx->running = FALSE;
+ break;
+ }
+ }
+ }
+ }
+
+ nx->thread = 0;
+
+ return NULL;
+}
+
+gboolean remmina_nx_session_tunnel_open(RemminaNXSession *nx)
+{
+ TRACE_CALL(__func__);
+ gint port;
+ gint sock;
+ gint sockopt = 1;
+ struct sockaddr_in sin;
+
+ if (!nx->encryption)
+ return TRUE;
+
+ remmina_nx_session_send_command(nx, "bye");
+ if (!remmina_nx_session_expect_status(nx, 999)) {
+ /* Shoud not happen, just in case */
+ remmina_nx_session_set_application_error(nx, "Server won't say bye to us?");
+ return FALSE;
+ }
+
+ port = (nx->localport ? nx->localport : nx->session_display) + 4000;
+
+ /* Create the server socket that listens on the local port */
+ sock = socket(AF_INET, SOCK_STREAM, 0);
+ if (sock < 0) {
+ remmina_nx_session_set_application_error(nx, "Failed to create socket.");
+ return FALSE;
+ }
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &sockopt, sizeof(sockopt));
+
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(port);
+ sin.sin_addr.s_addr = inet_addr("127.0.0.1");
+
+ if (bind(sock, (struct sockaddr *)&sin, sizeof(sin))) {
+ remmina_nx_session_set_application_error(nx, "Failed to bind on local port.");
+ close(sock);
+ return FALSE;
+ }
+
+ if (listen(sock, 1)) {
+ remmina_nx_session_set_application_error(nx, "Failed to listen on local port.");
+ close(sock);
+ return FALSE;
+ }
+
+ nx->server_sock = sock;
+ nx->running = TRUE;
+
+ if (pthread_create(&nx->thread, NULL, remmina_nx_session_tunnel_main_thread, nx)) {
+ remmina_nx_session_set_application_error(nx, "Failed to initialize pthread.");
+ nx->thread = 0;
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static gchar*
+remmina_nx_session_get_proxy_option(RemminaNXSession *nx)
+{
+ TRACE_CALL(__func__);
+ if (nx->encryption) {
+ return g_strdup_printf("nx,session=%s,cookie=%s,id=%s,shmem=1,shpix=1,connect=127.0.0.1:%i",
+ (gchar*)g_hash_table_lookup(nx->session_parameters, "session"), nx->proxy_cookie,
+ nx->session_id, (nx->localport ? nx->localport : nx->session_display));
+ } else {
+ return g_strdup_printf("nx,session=%s,cookie=%s,id=%s,shmem=1,shpix=1,connect=%s:%i",
+ (gchar*)g_hash_table_lookup(nx->session_parameters, "session"), nx->proxy_cookie,
+ nx->session_id, nx->server, nx->session_display);
+ }
+}
+
+gboolean remmina_nx_session_invoke_proxy(RemminaNXSession *nx, gint display, GChildWatchFunc exit_func, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ gchar *argv[50];
+ gint argc;
+ GError *error = NULL;
+ gboolean ret;
+ gchar **envp;
+ gchar *s;
+ gint i;
+
+ /* Copy all current environment variable, but change DISPLAY. Assume we should always have DISPLAY... */
+ if (display >= 0) {
+ envp = g_listenv();
+ for (i = 0; envp[i]; i++) {
+ if (g_strcmp0(envp[i], "DISPLAY") == 0) {
+ s = g_strdup_printf("DISPLAY=:%i", display);
+ } else {
+ s = g_strdup_printf("%s=%s", envp[i], g_getenv(envp[i]));
+ }
+ g_free(envp[i]);
+ envp[i] = s;
+ }
+ } else {
+ envp = NULL;
+ }
+
+ argc = 0;
+ argv[argc++] = g_strdup("nxproxy");
+ argv[argc++] = g_strdup("-S");
+ argv[argc++] = remmina_nx_session_get_proxy_option(nx);
+ argv[argc++] = NULL;
+
+ ret = g_spawn_async(NULL, argv, envp, G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &nx->proxy_pid,
+ &error);
+ g_strfreev(envp);
+ for (i = 0; i < argc; i++)
+ g_free(argv[i]);
+
+ if (!ret) {
+ remmina_nx_session_set_application_error(nx, "%s", error->message);
+ return FALSE;
+ }
+
+ if (exit_func) {
+ nx->proxy_watch_source = g_child_watch_add(nx->proxy_pid, exit_func, user_data);
+ }
+
+ return TRUE;
+}
+
+void remmina_nx_session_bye(RemminaNXSession *nx)
+{
+ TRACE_CALL(__func__);
+ remmina_nx_session_send_command(nx, "bye");
+ remmina_nx_session_get_response(nx);
+}
+
diff --git a/plugins/nx/nx_session.h b/plugins/nx/nx_session.h
new file mode 100644
index 000000000..0d9d3b9e5
--- /dev/null
+++ b/plugins/nx/nx_session.h
@@ -0,0 +1,107 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010 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
+
+enum {
+ REMMINA_NX_SESSION_COLUMN_DISPLAY,
+ REMMINA_NX_SESSION_COLUMN_TYPE,
+ REMMINA_NX_SESSION_COLUMN_ID,
+ REMMINA_NX_SESSION_COLUMN_STATUS,
+ REMMINA_NX_SESSION_COLUMN_NAME,
+ REMMINA_NX_SESSION_N_COLUMNS
+};
+
+typedef struct _RemminaNXSession RemminaNXSession;
+
+typedef gboolean (*RemminaNXPassphraseCallback)(gchar **passphrase, gpointer userdata);
+typedef void (*RemminaNXLogCallback)(const gchar *fmt, ...);
+
+RemminaNXSession* remmina_nx_session_new(void);
+
+void remmina_nx_session_free(RemminaNXSession *nx);
+
+gboolean remmina_nx_session_has_error(RemminaNXSession *nx);
+
+const gchar* remmina_nx_session_get_error(RemminaNXSession *nx);
+
+void remmina_nx_session_clear_error(RemminaNXSession *nx);
+
+void remmina_nx_session_set_encryption(RemminaNXSession *nx, gint encryption);
+
+void remmina_nx_session_set_localport(RemminaNXSession *nx, gint localport);
+
+void remmina_nx_session_set_log_callback(RemminaNXSession *nx, RemminaNXLogCallback log_callback);
+
+gboolean remmina_nx_session_open(RemminaNXSession *nx, const gchar *server, guint port, const gchar *private_key_file,
+ RemminaNXPassphraseCallback passphrase_func, gpointer userdata);
+
+gboolean remmina_nx_session_login(RemminaNXSession *nx, const gchar *username, const gchar *password);
+
+void remmina_nx_session_add_parameter(RemminaNXSession *nx, const gchar *name, const gchar *valuefmt, ...);
+
+gboolean remmina_nx_session_list(RemminaNXSession *nx);
+
+void remmina_nx_session_set_tree_view(RemminaNXSession *nx, GtkTreeView *tree);
+
+gboolean remmina_nx_session_iter_first(RemminaNXSession *nx, GtkTreeIter *iter);
+
+gboolean remmina_nx_session_iter_next(RemminaNXSession *nx, GtkTreeIter *iter);
+
+gchar* remmina_nx_session_iter_get(RemminaNXSession *nx, GtkTreeIter *iter, gint column);
+
+void remmina_nx_session_iter_set(RemminaNXSession *nx, GtkTreeIter *iter, gint column, const gchar *data);
+
+gboolean remmina_nx_session_allow_start(RemminaNXSession *nx);
+
+gboolean remmina_nx_session_start(RemminaNXSession *nx);
+
+gboolean remmina_nx_session_attach(RemminaNXSession *nx);
+
+gboolean remmina_nx_session_restore(RemminaNXSession *nx);
+
+gboolean remmina_nx_session_terminate(RemminaNXSession *nx);
+
+gboolean remmina_nx_session_tunnel_open(RemminaNXSession *nx);
+
+gboolean remmina_nx_session_invoke_proxy(RemminaNXSession *nx, gint display, GChildWatchFunc exit_func, gpointer user_data);
+
+void remmina_nx_session_bye(RemminaNXSession *nx);
+
+G_END_DECLS
+
+
diff --git a/plugins/nx/nx_session_manager.c b/plugins/nx/nx_session_manager.c
new file mode 100644
index 000000000..d6b1e0296
--- /dev/null
+++ b/plugins/nx/nx_session_manager.c
@@ -0,0 +1,244 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010 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 <X11/Xlib.h>
+#include "nx_plugin.h"
+#include "nx_session_manager.h"
+
+static void remmina_nx_session_manager_set_sensitive(RemminaProtocolWidget *gp, gboolean sensitive)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginNxData *gpdata = GET_PLUGIN_DATA(gp);
+
+ if (gpdata->attach_session) {
+ gtk_dialog_set_response_sensitive(GTK_DIALOG(gpdata->manager_dialog), REMMINA_NX_EVENT_TERMINATE, sensitive);
+ gtk_dialog_set_response_sensitive(GTK_DIALOG(gpdata->manager_dialog), REMMINA_NX_EVENT_ATTACH, sensitive);
+ }else {
+ gtk_dialog_set_response_sensitive(GTK_DIALOG(gpdata->manager_dialog), REMMINA_NX_EVENT_TERMINATE, sensitive);
+ gtk_dialog_set_response_sensitive(GTK_DIALOG(gpdata->manager_dialog), REMMINA_NX_EVENT_RESTORE, sensitive);
+ }
+}
+
+static gboolean remmina_nx_session_manager_selection_func(GtkTreeSelection *selection, GtkTreeModel *model, GtkTreePath *path,
+ gboolean path_currently_selected, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp = (RemminaProtocolWidget*)user_data;
+ RemminaPluginNxData *gpdata = GET_PLUGIN_DATA(gp);
+
+ gpdata->manager_selected = FALSE;
+ if (path_currently_selected) {
+ remmina_nx_session_manager_set_sensitive(gp, FALSE);
+ return TRUE;
+ }
+
+ if (!gtk_tree_model_get_iter(model, &gpdata->iter, path))
+ return TRUE;
+ gpdata->manager_selected = TRUE;
+ remmina_nx_session_manager_set_sensitive(gp, TRUE);
+ return TRUE;
+}
+
+static void remmina_nx_session_manager_send_signal(RemminaPluginNxData *gpdata, gint event_type)
+{
+ TRACE_CALL(__func__);
+ guchar dummy = (guchar)event_type;
+ /* Signal the NX thread to resume execution */
+ if (write(gpdata->event_pipe[1], &dummy, 1)) {
+ }
+}
+
+static void remmina_nx_session_manager_on_response(GtkWidget *dialog, gint response_id, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginNxData *gpdata = GET_PLUGIN_DATA(gp);
+ gint event_type;
+
+ remmina_nx_session_manager_set_sensitive(gp, FALSE);
+ if (response_id <= 0) {
+ event_type = REMMINA_NX_EVENT_CANCEL;
+ }else {
+ event_type = response_id;
+ }
+ if (response_id == REMMINA_NX_EVENT_TERMINATE && gpdata->manager_selected) {
+ remmina_nx_session_iter_set(gpdata->nx, &gpdata->iter, REMMINA_NX_SESSION_COLUMN_STATUS, _("Terminating"));
+ }
+ if (response_id != REMMINA_NX_EVENT_TERMINATE) {
+ gtk_widget_destroy(dialog);
+ gpdata->manager_dialog = NULL;
+ }
+ if (response_id != REMMINA_NX_EVENT_TERMINATE && response_id != REMMINA_NX_EVENT_CANCEL) {
+ remmina_plugin_nx_service->protocol_plugin_init_show(gp);
+ }
+ remmina_nx_session_manager_send_signal(gpdata, event_type);
+}
+
+/* Handle double click on a row in the NX Session manager
+ * Automatically close the dialog using the default response id */
+void remmina_nx_session_manager_on_row_activated(GtkTreeView *tree, GtkTreePath *path, GtkTreeViewColumn *column, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginNxData *gpdata = GET_PLUGIN_DATA(gp);
+ remmina_plugin_nx_service->log_printf("[NX] Default response_id %d\n",
+ gpdata->default_response);
+
+ if (gpdata->default_response >= 0) {
+ gtk_dialog_response(GTK_DIALOG(gpdata->manager_dialog), gpdata->default_response);
+ }
+}
+
+static gboolean remmina_nx_session_manager_main(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginNxData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile;
+ GtkWidget *dialog;
+ gchar *s;
+ GtkWidget *scrolledwindow;
+ GtkWidget *tree;
+ GtkCellRenderer *renderer;
+ GtkTreeViewColumn *column;
+
+ remminafile = remmina_plugin_nx_service->protocol_plugin_get_file(gp);
+
+ gpdata->default_response = -1;
+ if (!gpdata->manager_started) {
+ remmina_plugin_nx_service->protocol_plugin_init_hide(gp);
+
+ dialog = gtk_dialog_new();
+ s = g_strdup_printf(_("NX Sessions on %s"), remmina_plugin_nx_service->file_get_string(remminafile, "server"));
+ gtk_window_set_title(GTK_WINDOW(dialog), s);
+ g_free(s);
+ if (gpdata->attach_session) {
+ gtk_dialog_add_button(GTK_DIALOG(dialog), _("Attach"), REMMINA_NX_EVENT_ATTACH);
+ /* Set default response id for attach */
+ gpdata->default_response = REMMINA_NX_EVENT_ATTACH;
+ }else {
+ gtk_dialog_add_button(GTK_DIALOG(dialog), _("Restore"), REMMINA_NX_EVENT_RESTORE);
+ gtk_dialog_add_button(GTK_DIALOG(dialog), _("Start"), REMMINA_NX_EVENT_START);
+ /* Set default response id for restore */
+ gpdata->default_response = REMMINA_NX_EVENT_RESTORE;
+ }
+ gtk_dialog_add_button(GTK_DIALOG(dialog), _("_Cancel"), REMMINA_NX_EVENT_CANCEL);
+
+ gtk_dialog_add_button(GTK_DIALOG(dialog), _("Terminate"), REMMINA_NX_EVENT_TERMINATE);
+
+ gtk_window_set_default_size(GTK_WINDOW(dialog), 640, 300);
+ gpdata->manager_dialog = dialog;
+
+ scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
+ gtk_widget_show(scrolledwindow);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwindow), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+ gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), scrolledwindow, TRUE, TRUE, 0);
+
+ tree = gtk_tree_view_new();
+ gtk_container_add(GTK_CONTAINER(scrolledwindow), tree);
+ gtk_widget_show(tree);
+ remmina_nx_session_set_tree_view(gpdata->nx, GTK_TREE_VIEW(tree));
+ /* Handle double click on the row */
+ g_signal_connect(G_OBJECT(tree), "row-activated",
+ G_CALLBACK(remmina_nx_session_manager_on_row_activated), gp);
+
+ renderer = gtk_cell_renderer_text_new();
+ column = gtk_tree_view_column_new_with_attributes("#", renderer, "text", REMMINA_NX_SESSION_COLUMN_ID, NULL);
+ gtk_tree_view_column_set_resizable(column, TRUE);
+ gtk_tree_view_column_set_sort_column_id(column, REMMINA_NX_SESSION_COLUMN_ID);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
+
+ renderer = gtk_cell_renderer_text_new();
+ column = gtk_tree_view_column_new_with_attributes(_("Type"), renderer, "text", REMMINA_NX_SESSION_COLUMN_TYPE,
+ NULL);
+ gtk_tree_view_column_set_resizable(column, TRUE);
+ gtk_tree_view_column_set_sort_column_id(column, REMMINA_NX_SESSION_COLUMN_TYPE);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
+
+ renderer = gtk_cell_renderer_text_new();
+ column = gtk_tree_view_column_new_with_attributes(_("Display"), renderer, "text",
+ REMMINA_NX_SESSION_COLUMN_DISPLAY, NULL);
+ gtk_tree_view_column_set_resizable(column, TRUE);
+ gtk_tree_view_column_set_sort_column_id(column, REMMINA_NX_SESSION_COLUMN_DISPLAY);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
+
+ renderer = gtk_cell_renderer_text_new();
+ column = gtk_tree_view_column_new_with_attributes(_("Status"), renderer, "text",
+ REMMINA_NX_SESSION_COLUMN_STATUS, NULL);
+ gtk_tree_view_column_set_resizable(column, TRUE);
+ gtk_tree_view_column_set_sort_column_id(column, REMMINA_NX_SESSION_COLUMN_STATUS);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
+
+ renderer = gtk_cell_renderer_text_new();
+ column = gtk_tree_view_column_new_with_attributes(_("Name"), renderer, "text", REMMINA_NX_SESSION_COLUMN_NAME,
+ NULL);
+ gtk_tree_view_column_set_resizable(column, TRUE);
+ gtk_tree_view_column_set_sort_column_id(column, REMMINA_NX_SESSION_COLUMN_NAME);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
+
+ gtk_tree_selection_set_select_function(gtk_tree_view_get_selection(GTK_TREE_VIEW(tree)),
+ remmina_nx_session_manager_selection_func, gp, NULL);
+
+ g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(remmina_nx_session_manager_on_response), gp);
+ gpdata->manager_started = TRUE;
+ }
+ gpdata->manager_selected = FALSE;
+ if (gpdata->manager_dialog) {
+ remmina_nx_session_manager_set_sensitive(gp, FALSE);
+ gtk_widget_show(gpdata->manager_dialog);
+ }
+ if (remmina_nx_session_has_error(gpdata->nx)) {
+ dialog = gtk_message_dialog_new((gpdata->manager_dialog ? GTK_WINDOW(gpdata->manager_dialog) : NULL),
+ GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s",
+ remmina_nx_session_get_error(gpdata->nx));
+ remmina_nx_session_clear_error(gpdata->nx);
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ remmina_nx_session_manager_send_signal(gpdata, 0);
+ }
+
+ gpdata->session_manager_start_handler = 0;
+ return FALSE;
+}
+
+void remmina_nx_session_manager_start(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginNxData *gpdata = GET_PLUGIN_DATA(gp);
+
+ if (gpdata->session_manager_start_handler == 0) {
+ gpdata->session_manager_start_handler = IDLE_ADD((GSourceFunc)remmina_nx_session_manager_main, gp);
+ }
+}
+
diff --git a/plugins/nx/nx_session_manager.h b/plugins/nx/nx_session_manager.h
new file mode 100644
index 000000000..b0db0c26f
--- /dev/null
+++ b/plugins/nx/nx_session_manager.h
@@ -0,0 +1,43 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010 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_nx_session_manager_start(RemminaProtocolWidget *gp);
+
+G_END_DECLS
+
diff --git a/plugins/rdp/16x16/emblems/remmina-rdp-ssh.png b/plugins/rdp/16x16/emblems/remmina-rdp-ssh.png
new file mode 100644
index 000000000..9defcd6f6
--- /dev/null
+++ b/plugins/rdp/16x16/emblems/remmina-rdp-ssh.png
Binary files differ
diff --git a/plugins/rdp/16x16/emblems/remmina-rdp.png b/plugins/rdp/16x16/emblems/remmina-rdp.png
new file mode 100644
index 000000000..2d7724312
--- /dev/null
+++ b/plugins/rdp/16x16/emblems/remmina-rdp.png
Binary files differ
diff --git a/plugins/rdp/22x22/emblems/remmina-rdp-ssh.png b/plugins/rdp/22x22/emblems/remmina-rdp-ssh.png
new file mode 100644
index 000000000..e977bb3b7
--- /dev/null
+++ b/plugins/rdp/22x22/emblems/remmina-rdp-ssh.png
Binary files differ
diff --git a/plugins/rdp/22x22/emblems/remmina-rdp.png b/plugins/rdp/22x22/emblems/remmina-rdp.png
new file mode 100644
index 000000000..feb093c09
--- /dev/null
+++ b/plugins/rdp/22x22/emblems/remmina-rdp.png
Binary files differ
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
+
diff --git a/plugins/secret/CMakeLists.txt b/plugins/secret/CMakeLists.txt
new file mode 100644
index 000000000..b7ff1aed3
--- /dev/null
+++ b/plugins/secret/CMakeLists.txt
@@ -0,0 +1,52 @@
+# remmina-plugin-secret - The GTK+ Remote Desktop Client
+#
+# Copyright (C) 2011 Marc-Andre Moreau
+# Copyright (C) 2014-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.
+
+
+find_suggested_package(Libsecret)
+if(LIBSECRET_FOUND)
+ set(REMMINA_PLUGIN_SECRET_SRCS
+ src/glibsecret_plugin.c
+ )
+
+ add_library(remmina-plugin-secret MODULE ${REMMINA_PLUGIN_SECRET_SRCS})
+ set_target_properties(remmina-plugin-secret PROPERTIES PREFIX "")
+ set_target_properties(remmina-plugin-secret PROPERTIES NO_SONAME 1)
+
+ include_directories(${GTK_INCLUDE_DIRS})
+ target_link_libraries(remmina-plugin-secret ${GTK_LIBRARY_DIRS})
+
+ include_directories(SYSTEM ${LIBSECRET_INCLUDE_DIRS})
+ target_link_libraries(remmina-plugin-secret ${LIBSECRET_LIBRARIES} ${GLIB2_LIBRARY})
+
+ install(TARGETS remmina-plugin-secret DESTINATION ${REMMINA_PLUGINDIR})
+endif()
diff --git a/plugins/secret/src/glibsecret_plugin.c b/plugins/secret/src/glibsecret_plugin.c
new file mode 100644
index 000000000..c28ead897
--- /dev/null
+++ b/plugins/secret/src/glibsecret_plugin.c
@@ -0,0 +1,180 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2011 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, 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 "config.h"
+#include "glibsecret_plugin.h"
+#include <gtk/gtk.h>
+#include <glib.h>
+#include <libsecret/secret.h>
+#include <remmina/plugin.h>
+
+static RemminaPluginService *remmina_plugin_service = NULL;
+
+static SecretSchema remmina_file_secret_schema =
+{ "org.remmina.Password", SECRET_SCHEMA_NONE,
+ {
+ { "filename", SECRET_SCHEMA_ATTRIBUTE_STRING },
+ { "key", SECRET_SCHEMA_ATTRIBUTE_STRING },
+ { NULL, 0 }
+ } };
+
+
+#ifdef LIBSECRET_VERSION_0_18
+static SecretService* secretservice;
+static SecretCollection* defaultcollection;
+#endif
+
+static void remmina_plugin_glibsecret_unlock_secret_service()
+{
+ TRACE_CALL(__func__);
+
+#ifdef LIBSECRET_VERSION_0_18
+
+ GError *error = NULL;
+ GList *objects, *ul;
+ gchar* lbl;
+
+ if (secretservice && defaultcollection) {
+ if (secret_collection_get_locked(defaultcollection)) {
+ lbl = secret_collection_get_label(defaultcollection);
+ remmina_plugin_service->log_printf("[glibsecret] requesting unlock of the default '%s' collection\n", lbl);
+ objects = g_list_append(NULL, defaultcollection);
+ secret_service_unlock_sync(secretservice, objects, NULL, &ul, &error);
+ g_list_free(objects);
+ g_list_free(ul);
+ }
+ }
+#endif
+ return;
+}
+
+void remmina_plugin_glibsecret_store_password(RemminaFile *remminafile, const gchar *key, const gchar *password)
+{
+ TRACE_CALL(__func__);
+ GError *r = NULL;
+ const gchar *path;
+ gchar *s;
+
+ remmina_plugin_glibsecret_unlock_secret_service();
+
+ path = remmina_plugin_service->file_get_path(remminafile);
+ s = g_strdup_printf("Remmina: %s - %s", remmina_plugin_service->file_get_string(remminafile, "name"), key);
+ secret_password_store_sync(&remmina_file_secret_schema, SECRET_COLLECTION_DEFAULT, s, password,
+ NULL, &r, "filename", path, "key", key, NULL);
+ g_free(s);
+ if (r == NULL) {
+ remmina_plugin_service->log_printf("[glibsecret] password saved for file %s\n", path);
+ }else {
+ remmina_plugin_service->log_printf("[glibsecret] password cannot be saved for file %s\n", path);
+ g_error_free(r);
+ }
+}
+
+gchar*
+remmina_plugin_glibsecret_get_password(RemminaFile *remminafile, const gchar *key)
+{
+ TRACE_CALL(__func__);
+ GError *r = NULL;
+ const gchar *path;
+ gchar *password;
+ gchar *p;
+
+ remmina_plugin_glibsecret_unlock_secret_service();
+
+ path = remmina_plugin_service->file_get_path(remminafile);
+ password = secret_password_lookup_sync(&remmina_file_secret_schema, NULL, &r, "filename", path, "key", key, NULL);
+ if (r == NULL) {
+ // remmina_plugin_service->log_printf("[glibsecret] found password for file %s\n", path);
+ p = g_strdup(password);
+ secret_password_free(password);
+ return p;
+ }else {
+ remmina_plugin_service->log_printf("[glibsecret] password cannot be found for file %s\n", path);
+ return NULL;
+ }
+}
+
+void remmina_plugin_glibsecret_delete_password(RemminaFile *remminafile, const gchar *key)
+{
+ TRACE_CALL(__func__);
+ GError *r = NULL;
+ const gchar *path;
+
+ remmina_plugin_glibsecret_unlock_secret_service();
+
+ path = remmina_plugin_service->file_get_path(remminafile);
+ secret_password_clear_sync(&remmina_file_secret_schema, NULL, &r, "filename", path, "key", key, NULL);
+ if (r == NULL) {
+ remmina_plugin_service->log_printf("[glibsecret] password deleted for file %s\n", path);
+ }else {
+ remmina_plugin_service->log_printf("[glibsecret] password cannot be deleted for file %s\n", path);
+ }
+}
+
+static RemminaSecretPlugin remmina_plugin_glibsecret =
+{ REMMINA_PLUGIN_TYPE_SECRET, "glibsecret", "GNOME libsecret", NULL, VERSION,
+
+ TRUE, remmina_plugin_glibsecret_store_password, remmina_plugin_glibsecret_get_password, remmina_plugin_glibsecret_delete_password };
+
+G_MODULE_EXPORT gboolean
+remmina_plugin_entry(RemminaPluginService *service)
+{
+ TRACE_CALL(__func__);
+
+ remmina_plugin_service = service;
+
+ if (!service->register_plugin((RemminaPlugin*)&remmina_plugin_glibsecret)) {
+ return FALSE;
+ }
+
+#ifdef LIBSECRET_VERSION_0_18
+ GError *error;
+ error = NULL;
+ secretservice = secret_service_get_sync(SECRET_SERVICE_LOAD_COLLECTIONS, NULL, &error);
+ if (error) {
+ remmina_plugin_service->log_printf("[glibsecret] unable to get secret service: %s\n", error->message);
+ return FALSE;
+ }
+
+ defaultcollection = secret_collection_for_alias_sync(secretservice, SECRET_COLLECTION_DEFAULT, SECRET_COLLECTION_NONE, NULL, &error);
+ if (error) {
+ remmina_plugin_service->log_printf("[glibsecret] unable to get secret service default collection: %s\n", error->message);
+ return FALSE;
+ }
+#endif
+
+ return TRUE;
+}
+
diff --git a/plugins/secret/src/glibsecret_plugin.h b/plugins/secret/src/glibsecret_plugin.h
new file mode 100644
index 000000000..f28c97cf1
--- /dev/null
+++ b/plugins/secret/src/glibsecret_plugin.h
@@ -0,0 +1,42 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2015-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.
+ *
+ */
+
+#ifndef __BACKEND_LIBSECRET_H__
+#define __BACKEND_LIBSECRET_H__
+
+#include <libsecret/secret.h>
+#include <glib.h>
+
+
+#endif // __BACKEND_LIBSECRET_H__
diff --git a/plugins/spice/16x16/emblems/remmina-spice.png b/plugins/spice/16x16/emblems/remmina-spice.png
new file mode 100644
index 000000000..433610cd3
--- /dev/null
+++ b/plugins/spice/16x16/emblems/remmina-spice.png
Binary files differ
diff --git a/plugins/spice/22x22/emblems/remmina-spice.png b/plugins/spice/22x22/emblems/remmina-spice.png
new file mode 100644
index 000000000..64d5061db
--- /dev/null
+++ b/plugins/spice/22x22/emblems/remmina-spice.png
Binary files differ
diff --git a/plugins/spice/CMakeLists.txt b/plugins/spice/CMakeLists.txt
new file mode 100644
index 000000000..f8fd8fe3b
--- /dev/null
+++ b/plugins/spice/CMakeLists.txt
@@ -0,0 +1,49 @@
+# Remmina - The GTK+ Remote Desktop Client
+#
+# Copyright (C) 2016-2017 Denis Ollier
+#
+# 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(REMMINA_PLUGIN_SPICE_SRCS
+ spice_plugin.c
+ spice_plugin_file_transfer.c
+ spice_plugin_usb.c
+ )
+
+add_library(remmina-plugin-spice ${REMMINA_PLUGIN_SPICE_SRCS})
+set_target_properties(remmina-plugin-spice PROPERTIES PREFIX "")
+set_target_properties(remmina-plugin-spice PROPERTIES NO_SONAME 1)
+
+include_directories(${REMMINA_COMMON_INCLUDE_DIRS} ${SPICE_INCLUDE_DIRS})
+target_link_libraries(remmina-plugin-spice ${REMMINA_COMMON_LIBRARIES} ${SPICE_LIBRARIES})
+
+install(TARGETS remmina-plugin-spice DESTINATION ${REMMINA_PLUGINDIR})
+
+install(FILES 16x16/emblems/remmina-spice.png DESTINATION ${APPICON16_EMBLEMS_DIR})
+install(FILES 22x22/emblems/remmina-spice.png DESTINATION ${APPICON22_EMBLEMS_DIR})
diff --git a/plugins/spice/spice_plugin.c b/plugins/spice/spice_plugin.c
new file mode 100644
index 000000000..b05149b3e
--- /dev/null
+++ b/plugins/spice/spice_plugin.c
@@ -0,0 +1,493 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2016-2017 Denis Ollier
+ *
+ * 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 "spice_plugin.h"
+
+#define XSPICE_DEFAULT_PORT 5900
+
+enum {
+ REMMINA_PLUGIN_SPICE_FEATURE_PREF_VIEWONLY = 1,
+ REMMINA_PLUGIN_SPICE_FEATURE_PREF_RESIZEGUEST,
+ REMMINA_PLUGIN_SPICE_FEATURE_PREF_DISABLECLIPBOARD,
+ REMMINA_PLUGIN_SPICE_FEATURE_TOOL_SENDCTRLALTDEL,
+ REMMINA_PLUGIN_SPICE_FEATURE_TOOL_USBREDIR,
+ REMMINA_PLUGIN_SPICE_FEATURE_SCALE
+};
+
+static RemminaPluginService *remmina_plugin_service = NULL;
+
+static void remmina_plugin_spice_channel_new_cb(SpiceSession *, SpiceChannel *, RemminaProtocolWidget *);
+static void remmina_plugin_spice_main_channel_event_cb(SpiceChannel *, SpiceChannelEvent, RemminaProtocolWidget *);
+static void remmina_plugin_spice_display_ready_cb(GObject *, GParamSpec *, RemminaProtocolWidget *);
+static void remmina_plugin_spice_update_scale(RemminaProtocolWidget *);
+
+void remmina_plugin_spice_select_usb_devices(RemminaProtocolWidget *);
+#ifdef SPICE_GTK_CHECK_VERSION
+# if SPICE_GTK_CHECK_VERSION(0, 31, 0)
+void remmina_plugin_spice_file_transfer_new_cb(SpiceMainChannel *, SpiceFileTransferTask *, RemminaProtocolWidget *);
+# endif /* SPICE_GTK_CHECK_VERSION(0, 31, 0) */
+#endif /* SPICE_GTK_CHECK_VERSION */
+
+static void remmina_plugin_spice_init(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+
+ RemminaPluginSpiceData *gpdata;
+ RemminaFile *remminafile;
+
+ gpdata = g_new0(RemminaPluginSpiceData, 1);
+ g_object_set_data_full(G_OBJECT(gp), "plugin-data", gpdata, g_free);
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ gpdata->session = spice_session_new();
+ g_signal_connect(gpdata->session,
+ "channel-new",
+ G_CALLBACK(remmina_plugin_spice_channel_new_cb),
+ gp);
+
+ g_object_set(gpdata->session,
+ "password", g_strdup(remmina_plugin_service->file_get_string(remminafile, "password")),
+ "read-only", remmina_plugin_service->file_get_int(remminafile, "viewonly", FALSE),
+ "enable-audio", remmina_plugin_service->file_get_int(remminafile, "enableaudio", FALSE),
+ "enable-smartcard", remmina_plugin_service->file_get_int(remminafile, "sharesmartcard", FALSE),
+ "shared-dir", remmina_plugin_service->file_get_string(remminafile, "sharefolder"),
+ NULL);
+
+ gpdata->gtk_session = spice_gtk_session_get(gpdata->session);
+ g_object_set(gpdata->gtk_session,
+ "auto-clipboard",
+ !remmina_plugin_service->file_get_int(remminafile, "disableclipboard", FALSE),
+ NULL);
+}
+
+static gboolean remmina_plugin_spice_open_connection(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+
+ gint port;
+ const gchar *cacert;
+ gchar *host;
+ RemminaPluginSpiceData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ remmina_plugin_service->get_server_port(remmina_plugin_service->file_get_string(remminafile, "server"),
+ XSPICE_DEFAULT_PORT,
+ &host,
+ &port);
+
+ g_object_set(gpdata->session, "host", host, NULL);
+ g_free(host);
+
+ /* Unencrypted connection */
+ if (!remmina_plugin_service->file_get_int(remminafile, "usetls", FALSE)) {
+ g_object_set(gpdata->session, "port", g_strdup_printf("%i", port), NULL);
+ }
+ /* TLS encrypted connection */
+ else{
+ g_object_set(gpdata->session, "tls_port", g_strdup_printf("%i", port), NULL);
+
+ /* Server CA certificate */
+ cacert = remmina_plugin_service->file_get_string(remminafile, "cacert");
+ if (cacert) {
+ g_object_set(gpdata->session, "ca-file", cacert, NULL);
+ }
+ }
+
+ spice_session_connect(gpdata->session);
+
+ /*
+ * FIXME: Add a waiting loop until the g_signal "channel-event" occurs.
+ * If the event is SPICE_CHANNEL_OPENED, TRUE should be returned,
+ * otherwise FALSE should be returned.
+ */
+ return TRUE;
+}
+
+static gboolean remmina_plugin_spice_close_connection(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginSpiceData *gpdata = GET_PLUGIN_DATA(gp);
+
+ if (gpdata->main_channel) {
+ g_signal_handlers_disconnect_by_func(gpdata->main_channel,
+ G_CALLBACK(remmina_plugin_spice_main_channel_event_cb),
+ gp);
+ }
+
+ if (gpdata->session) {
+ spice_session_disconnect(gpdata->session);
+ g_object_unref(gpdata->session);
+ gpdata->session = NULL;
+ remmina_plugin_service->protocol_plugin_emit_signal(gp, "disconnect");
+ }
+
+#ifdef SPICE_GTK_CHECK_VERSION
+# if SPICE_GTK_CHECK_VERSION(0, 31, 0)
+ if (gpdata->file_transfers) {
+ g_hash_table_unref(gpdata->file_transfers);
+ }
+# endif /* SPICE_GTK_CHECK_VERSION(0, 31, 0) */
+#endif /* SPICE_GTK_CHECK_VERSION */
+
+ return FALSE;
+}
+
+static void remmina_plugin_spice_channel_new_cb(SpiceSession *session, SpiceChannel *channel, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+
+ gint id;
+ RemminaPluginSpiceData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ g_object_get(channel, "channel-id", &id, NULL);
+
+ if (SPICE_IS_MAIN_CHANNEL(channel)) {
+ gpdata->main_channel = SPICE_MAIN_CHANNEL(channel);
+ g_signal_connect(channel,
+ "channel-event",
+ G_CALLBACK(remmina_plugin_spice_main_channel_event_cb),
+ gp);
+#ifdef SPICE_GTK_CHECK_VERSION
+# if SPICE_GTK_CHECK_VERSION(0, 31, 0)
+ g_signal_connect(channel,
+ "new-file-transfer",
+ G_CALLBACK(remmina_plugin_spice_file_transfer_new_cb),
+ gp);
+# endif /* SPICE_GTK_CHECK_VERSION(0, 31, 0) */
+#endif /* SPICE_GTK_CHECK_VERSION */
+ }
+
+ if (SPICE_IS_DISPLAY_CHANNEL(channel)) {
+ gpdata->display_channel = SPICE_DISPLAY_CHANNEL(channel);
+ gpdata->display = spice_display_new(gpdata->session, id);
+ g_signal_connect(gpdata->display,
+ "notify::ready",
+ G_CALLBACK(remmina_plugin_spice_display_ready_cb),
+ gp);
+ remmina_plugin_spice_display_ready_cb(G_OBJECT(gpdata->display), NULL, gp);
+ }
+
+ if (SPICE_IS_PLAYBACK_CHANNEL(channel)) {
+ if (remmina_plugin_service->file_get_int(remminafile, "enableaudio", FALSE)) {
+ gpdata->audio = spice_audio_get(gpdata->session, NULL);
+ }
+ }
+
+ if (SPICE_IS_WEBDAV_CHANNEL(channel)) {
+ if (remmina_plugin_service->file_get_string(remminafile, "sharefolder")) {
+ spice_channel_connect(channel);
+ }
+ }
+}
+
+static gboolean remmina_plugin_spice_ask_auth(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+
+ gint ret;
+ gboolean disablepasswordstoring;
+
+ RemminaPluginSpiceData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *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_authpwd(gp, REMMINA_AUTHPWD_TYPE_PROTOCOL, !disablepasswordstoring);
+
+ if (ret == GTK_RESPONSE_OK) {
+ g_object_set(gpdata->session,
+ "password",
+ remmina_plugin_service->protocol_plugin_init_get_password(gp),
+ NULL);
+
+ return TRUE;
+ }else {
+ return FALSE;
+ }
+}
+
+static void remmina_plugin_spice_main_channel_event_cb(SpiceChannel *channel, SpiceChannelEvent event, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+
+ gchar *server;
+ gint port;
+ RemminaFile *remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ switch (event) {
+ case SPICE_CHANNEL_CLOSED:
+ remmina_plugin_service->get_server_port(remmina_plugin_service->file_get_string(remminafile, "server"),
+ XSPICE_DEFAULT_PORT,
+ &server,
+ &port);
+ remmina_plugin_service->protocol_plugin_set_error(gp, _("Disconnected from SPICE server %s."), server);
+ g_free(server);
+ remmina_plugin_spice_close_connection(gp);
+ break;
+ case SPICE_CHANNEL_OPENED:
+ break;
+ case SPICE_CHANNEL_ERROR_AUTH:
+ if (remmina_plugin_spice_ask_auth(gp)) {
+ remmina_plugin_spice_open_connection(gp);
+ }else{
+ remmina_plugin_service->protocol_plugin_set_error(gp, _("Invalid password."));
+ remmina_plugin_spice_close_connection(gp);
+ }
+ break;
+ case SPICE_CHANNEL_ERROR_TLS:
+ remmina_plugin_service->protocol_plugin_set_error(gp, _("TLS connection error."));
+ remmina_plugin_spice_close_connection(gp);
+ break;
+ case SPICE_CHANNEL_ERROR_IO:
+ case SPICE_CHANNEL_ERROR_LINK:
+ case SPICE_CHANNEL_ERROR_CONNECT:
+ remmina_plugin_service->protocol_plugin_set_error(gp, _("Connection to SPICE server failed."));
+ remmina_plugin_spice_close_connection(gp);
+ break;
+ default:
+ break;
+ }
+}
+
+static void remmina_plugin_spice_display_ready_cb(GObject *display, GParamSpec *param_spec, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+
+ gboolean ready;
+ RemminaFile *remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ g_object_get(display, "ready", &ready, NULL);
+
+ if (ready) {
+ g_signal_handlers_disconnect_by_func(display,
+ G_CALLBACK(remmina_plugin_spice_display_ready_cb),
+ gp);
+
+ g_object_set(display,
+ "scaling", (remmina_plugin_service->remmina_protocol_widget_get_current_scale_mode(gp) != REMMINA_PROTOCOL_WIDGET_SCALE_MODE_NONE),
+ "resize-guest", remmina_plugin_service->file_get_int(remminafile, "resizeguest", FALSE),
+ NULL);
+ gtk_container_add(GTK_CONTAINER(gp), GTK_WIDGET(display));
+ gtk_widget_show(GTK_WIDGET(display));
+
+ remmina_plugin_service->protocol_plugin_register_hostkey(gp, GTK_WIDGET(display));
+ remmina_plugin_service->protocol_plugin_emit_signal(gp, "connect");
+ }
+}
+
+/* Send a keystroke to the plugin window */
+static void remmina_plugin_spice_keystroke(RemminaProtocolWidget *gp, const guint keystrokes[], const gint keylen)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginSpiceData *gpdata = GET_PLUGIN_DATA(gp);
+
+ if (gpdata->display) {
+ spice_display_send_keys(gpdata->display,
+ keystrokes,
+ keylen,
+ SPICE_DISPLAY_KEY_EVENT_CLICK);
+ }
+}
+
+/* Send CTRL+ALT+DEL keys keystrokes to the plugin socket widget */
+static void remmina_plugin_spice_send_ctrlaltdel(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+
+ guint keys[] = { GDK_KEY_Control_L, GDK_KEY_Alt_L, GDK_KEY_Delete };
+
+ remmina_plugin_spice_keystroke(gp, keys, G_N_ELEMENTS(keys));
+}
+
+static void remmina_plugin_spice_update_scale(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+
+ gint scale, width, height;
+ RemminaPluginSpiceData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ scale = remmina_plugin_service->file_get_int(remminafile, "scale", FALSE);
+ g_object_set(gpdata->display, "scaling", scale, NULL);
+
+ if (scale) {
+ /* In scaled mode, the SpiceDisplay will get its dimensions from its parent */
+ gtk_widget_set_size_request(GTK_WIDGET(gpdata->display), -1, -1 );
+ }else {
+ /* In non scaled mode, the plugins forces dimensions of the SpiceDisplay */
+ g_object_get(gpdata->display_channel,
+ "width", &width,
+ "height", &height,
+ NULL);
+ gtk_widget_set_size_request(GTK_WIDGET(gpdata->display), width, height);
+ }
+}
+
+static gboolean remmina_plugin_spice_query_feature(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
+{
+ TRACE_CALL(__func__);
+
+ return TRUE;
+}
+
+static void remmina_plugin_spice_call_feature(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
+{
+ TRACE_CALL(__func__);
+
+ RemminaPluginSpiceData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ switch (feature->id) {
+ case REMMINA_PLUGIN_SPICE_FEATURE_PREF_VIEWONLY:
+ g_object_set(gpdata->session,
+ "read-only",
+ remmina_plugin_service->file_get_int(remminafile, "viewonly", FALSE),
+ NULL);
+ break;
+ case REMMINA_PLUGIN_SPICE_FEATURE_PREF_RESIZEGUEST:
+ g_object_set(gpdata->display,
+ "resize-guest",
+ remmina_plugin_service->file_get_int(remminafile, "resizeguest", TRUE),
+ NULL);
+ break;
+ case REMMINA_PLUGIN_SPICE_FEATURE_PREF_DISABLECLIPBOARD:
+ g_object_set(gpdata->gtk_session,
+ "auto-clipboard",
+ !remmina_plugin_service->file_get_int(remminafile, "disableclipboard", FALSE),
+ NULL);
+ break;
+ case REMMINA_PLUGIN_SPICE_FEATURE_SCALE:
+ remmina_plugin_spice_update_scale(gp);
+ break;
+ case REMMINA_PLUGIN_SPICE_FEATURE_TOOL_SENDCTRLALTDEL:
+ remmina_plugin_spice_send_ctrlaltdel(gp);
+ break;
+ case REMMINA_PLUGIN_SPICE_FEATURE_TOOL_USBREDIR:
+ remmina_plugin_spice_select_usb_devices(gp);
+ break;
+ default:
+ break;
+ }
+}
+
+/* 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_plugin_spice_basic_settings[] =
+{
+ { REMMINA_PROTOCOL_SETTING_TYPE_SERVER, "server", NULL, FALSE, NULL, NULL},
+ { REMMINA_PROTOCOL_SETTING_TYPE_PASSWORD, "password", N_("User password"), FALSE, NULL, NULL},
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "usetls", N_("Use TLS encryption"), FALSE, NULL, NULL},
+ { REMMINA_PROTOCOL_SETTING_TYPE_FILE, "cacert", N_("Server CA certificate"), FALSE, NULL, NULL},
+ { REMMINA_PROTOCOL_SETTING_TYPE_FOLDER, "sharefolder", N_("Share folder"), 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_plugin_spice_advanced_settings[] =
+{
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableclipboard", N_("Disable clipboard sync"), TRUE, NULL, NULL},
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disablepasswordstoring", N_("Disable password storing"), TRUE, NULL, NULL},
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "enableaudio", N_("Enable audio channel"), TRUE, NULL, NULL},
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "resizeguest", N_("Resize guest to match window size"), TRUE, NULL, NULL},
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "sharesmartcard", N_("Share smartcard"), TRUE, NULL, NULL},
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "viewonly", N_("View only"), TRUE, NULL, NULL},
+ { REMMINA_PROTOCOL_SETTING_TYPE_END, NULL, NULL, TRUE, NULL, NULL}
+};
+
+/* Array for available features.
+ * The last element of the array must be REMMINA_PROTOCOL_FEATURE_TYPE_END. */
+static const RemminaProtocolFeature remmina_plugin_spice_features[] =
+{
+ { REMMINA_PROTOCOL_FEATURE_TYPE_PREF, REMMINA_PLUGIN_SPICE_FEATURE_PREF_VIEWONLY, GINT_TO_POINTER(REMMINA_PROTOCOL_FEATURE_PREF_CHECK), "viewonly",
+ N_("View only") },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_PREF, REMMINA_PLUGIN_SPICE_FEATURE_PREF_RESIZEGUEST, GINT_TO_POINTER(REMMINA_PROTOCOL_FEATURE_PREF_CHECK), "resizeguest", N_("Resize guest to match window size")},
+ { REMMINA_PROTOCOL_FEATURE_TYPE_PREF, REMMINA_PLUGIN_SPICE_FEATURE_PREF_DISABLECLIPBOARD, GINT_TO_POINTER(REMMINA_PROTOCOL_FEATURE_PREF_CHECK), "disableclipboard", N_("Disable clipboard sync")},
+ { REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_PLUGIN_SPICE_FEATURE_TOOL_SENDCTRLALTDEL, N_("Send Ctrl+Alt+Delete"), NULL, NULL},
+ { REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_PLUGIN_SPICE_FEATURE_TOOL_USBREDIR, N_("Select USB devices for redirection"), NULL, NULL},
+ { REMMINA_PROTOCOL_FEATURE_TYPE_SCALE, REMMINA_PLUGIN_SPICE_FEATURE_SCALE, NULL, NULL, NULL},
+ { REMMINA_PROTOCOL_FEATURE_TYPE_END, 0, NULL, NULL, NULL}
+};
+
+static RemminaProtocolPlugin remmina_plugin_spice =
+{
+ REMMINA_PLUGIN_TYPE_PROTOCOL, // Type
+ "SPICE", // Name
+ N_("SPICE - Simple Protocol for Independent Computing Environments"), // Description
+ GETTEXT_PACKAGE, // Translation domain
+ VERSION, // Version number
+ "remmina-spice", // Icon for normal connection
+ "remmina-spice", // Icon for SSH connection
+ remmina_plugin_spice_basic_settings, // Array for basic settings
+ remmina_plugin_spice_advanced_settings, // Array for advanced settings
+ REMMINA_PROTOCOL_SSH_SETTING_NONE, // SSH settings type
+ remmina_plugin_spice_features, // Array for available features
+ remmina_plugin_spice_init, // Plugin initialization
+ remmina_plugin_spice_open_connection, // Plugin open connection
+ remmina_plugin_spice_close_connection, // Plugin close connection
+ remmina_plugin_spice_query_feature, // Query for available features
+ remmina_plugin_spice_call_feature, // Call a feature
+ remmina_plugin_spice_keystroke, // Send a keystroke
+ NULL // Screenshot
+};
+
+G_MODULE_EXPORT gboolean
+remmina_plugin_entry(RemminaPluginService *service)
+{
+ TRACE_CALL(__func__);
+ remmina_plugin_service = service;
+
+ bindtextdomain(GETTEXT_PACKAGE, REMMINA_RUNTIME_LOCALEDIR);
+ bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
+
+ if (!service->register_plugin((RemminaPlugin*)&remmina_plugin_spice)) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
diff --git a/plugins/spice/spice_plugin.h b/plugins/spice/spice_plugin.h
new file mode 100644
index 000000000..f63b2707e
--- /dev/null
+++ b/plugins/spice/spice_plugin.h
@@ -0,0 +1,70 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2016-2017 Denis Ollier
+ *
+ * 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 <spice-client.h>
+#ifdef SPICE_GTK_CHECK_VERSION
+# if SPICE_GTK_CHECK_VERSION(0, 31, 0)
+# include <spice-client-gtk.h>
+# else
+# include <spice-widget.h>
+# include <usb-device-widget.h>
+# endif
+#else
+# include <spice-widget.h>
+# include <usb-device-widget.h>
+#endif
+
+#define GET_PLUGIN_DATA(gp) (RemminaPluginSpiceData*)g_object_get_data(G_OBJECT(gp), "plugin-data")
+
+typedef struct _RemminaPluginSpiceData {
+ SpiceAudio *audio;
+ SpiceDisplay *display;
+ SpiceDisplayChannel *display_channel;
+ SpiceGtkSession *gtk_session;
+ SpiceMainChannel *main_channel;
+ SpiceSession *session;
+
+#ifdef SPICE_GTK_CHECK_VERSION
+# if SPICE_GTK_CHECK_VERSION(0, 31, 0)
+ /* key: SpiceFileTransferTask, value: RemminaPluginSpiceXferWidgets */
+ GHashTable *file_transfers;
+ GtkWidget *file_transfer_dialog;
+# endif /* SPICE_GTK_CHECK_VERSION(0, 31, 0) */
+#endif /* SPICE_GTK_CHECK_VERSION */
+} RemminaPluginSpiceData;
+
+
diff --git a/plugins/spice/spice_plugin_file_transfer.c b/plugins/spice/spice_plugin_file_transfer.c
new file mode 100644
index 000000000..d3eeac6b0
--- /dev/null
+++ b/plugins/spice/spice_plugin_file_transfer.c
@@ -0,0 +1,244 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2016-2017 Denis Ollier
+ *
+ * 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 "spice_plugin.h"
+
+#ifdef SPICE_GTK_CHECK_VERSION
+# if SPICE_GTK_CHECK_VERSION(0, 31, 0)
+
+static void remmina_plugin_spice_file_transfer_cancel_cb(GtkButton *, SpiceFileTransferTask *);
+static void remmina_plugin_spice_file_transfer_dialog_response_cb(GtkDialog *, gint, RemminaProtocolWidget *);
+static void remmina_plugin_spice_file_transfer_finished_cb(SpiceFileTransferTask *, GError *, RemminaProtocolWidget *);
+static void remmina_plugin_spice_file_transfer_progress_cb(GObject *, GParamSpec *, RemminaProtocolWidget *);
+
+typedef struct _RemminaPluginSpiceXferWidgets {
+ GtkWidget *vbox;
+ GtkWidget *hbox;
+ GtkWidget *progress;
+ GtkWidget *label;
+ GtkWidget *cancel;
+} RemminaPluginSpiceXferWidgets;
+
+static RemminaPluginSpiceXferWidgets * remmina_plugin_spice_xfer_widgets_new(SpiceFileTransferTask *);
+static void remmina_plugin_spice_xfer_widgets_free(RemminaPluginSpiceXferWidgets *widgets);
+
+void remmina_plugin_spice_file_transfer_new_cb(SpiceMainChannel *main_channel, SpiceFileTransferTask *task, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+
+ GtkWidget *dialog_content;
+ RemminaPluginSpiceXferWidgets *widgets;
+ RemminaPluginSpiceData *gpdata = GET_PLUGIN_DATA(gp);
+
+ g_signal_connect(task,
+ "finished",
+ G_CALLBACK(remmina_plugin_spice_file_transfer_finished_cb),
+ gp);
+
+ if (!gpdata->file_transfers) {
+ gpdata->file_transfers = g_hash_table_new_full(g_direct_hash,
+ g_direct_equal,
+ g_object_unref,
+ (GDestroyNotify)remmina_plugin_spice_xfer_widgets_free);
+ }
+
+ if (!gpdata->file_transfer_dialog) {
+ /*
+ * FIXME: Use the RemminaConnectionWindow as transient parent widget
+ * (and add the GTK_DIALOG_DESTROY_WITH_PARENT flag) if it becomes
+ * accessible from the Remmina plugin API.
+ */
+ gpdata->file_transfer_dialog = gtk_dialog_new_with_buttons(_("File Transfers"),
+ NULL, 0,
+ _("_Cancel"),
+ GTK_RESPONSE_CANCEL,
+ NULL);
+ dialog_content = gtk_dialog_get_content_area(GTK_DIALOG(gpdata->file_transfer_dialog));
+ gtk_widget_set_size_request(dialog_content, 400, -1);
+ gtk_window_set_resizable(GTK_WINDOW(gpdata->file_transfer_dialog), FALSE);
+ g_signal_connect(gpdata->file_transfer_dialog,
+ "response",
+ G_CALLBACK(remmina_plugin_spice_file_transfer_dialog_response_cb),
+ gp);
+ }
+
+ widgets = remmina_plugin_spice_xfer_widgets_new(task);
+ g_hash_table_insert(gpdata->file_transfers, g_object_ref(task), widgets);
+
+ gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(gpdata->file_transfer_dialog))),
+ widgets->vbox,
+ TRUE, TRUE, 6);
+
+ g_signal_connect(task,
+ "notify::progress",
+ G_CALLBACK(remmina_plugin_spice_file_transfer_progress_cb),
+ gp);
+
+ gtk_widget_show(gpdata->file_transfer_dialog);
+}
+
+static RemminaPluginSpiceXferWidgets * remmina_plugin_spice_xfer_widgets_new(SpiceFileTransferTask *task)
+{
+ TRACE_CALL(__func__);
+
+ gchar *filename;
+ RemminaPluginSpiceXferWidgets *widgets = g_new0(RemminaPluginSpiceXferWidgets, 1);
+
+ widgets->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+ widgets->hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
+
+ filename = spice_file_transfer_task_get_filename(task);
+ widgets->label = gtk_label_new(filename);
+ gtk_widget_set_halign(widgets->label, GTK_ALIGN_START);
+ gtk_widget_set_valign(widgets->label, GTK_ALIGN_BASELINE);
+
+ widgets->progress = gtk_progress_bar_new();
+ gtk_widget_set_hexpand(widgets->progress, TRUE);
+ gtk_widget_set_valign(widgets->progress, GTK_ALIGN_CENTER);
+
+ widgets->cancel = gtk_button_new_from_icon_name("gtk-cancel", GTK_ICON_SIZE_SMALL_TOOLBAR);
+ g_signal_connect(widgets->cancel,
+ "clicked",
+ G_CALLBACK(remmina_plugin_spice_file_transfer_cancel_cb),
+ task);
+ gtk_widget_set_hexpand(widgets->cancel, FALSE);
+ gtk_widget_set_valign(widgets->cancel, GTK_ALIGN_CENTER);
+
+ gtk_box_pack_start(GTK_BOX(widgets->hbox), widgets->progress,
+ TRUE, TRUE, 0);
+ gtk_box_pack_start(GTK_BOX(widgets->hbox), widgets->cancel,
+ FALSE, TRUE, 0);
+
+ gtk_box_pack_start(GTK_BOX(widgets->vbox), widgets->label,
+ TRUE, TRUE, 0);
+ gtk_box_pack_start(GTK_BOX(widgets->vbox), widgets->hbox,
+ TRUE, TRUE, 0);
+
+ gtk_widget_show_all(widgets->vbox);
+
+ g_free(filename);
+
+ return widgets;
+}
+
+static void remmina_plugin_spice_xfer_widgets_free(RemminaPluginSpiceXferWidgets *widgets)
+{
+ TRACE_CALL(__func__);
+
+ /* Child widgets will be destroyed automatically */
+ gtk_widget_destroy(widgets->vbox);
+ g_free(widgets);
+}
+
+static void remmina_plugin_spice_file_transfer_cancel_cb(GtkButton *button, SpiceFileTransferTask *task)
+{
+ TRACE_CALL(__func__);
+
+ spice_file_transfer_task_cancel(task);
+}
+
+
+static void remmina_plugin_spice_file_transfer_dialog_response_cb(GtkDialog *dialog, gint response, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+
+ GHashTableIter iter;
+ gpointer key, value;
+ SpiceFileTransferTask *task;
+ RemminaPluginSpiceData *gpdata = GET_PLUGIN_DATA(gp);
+
+ if (response == GTK_RESPONSE_CANCEL) {
+ g_hash_table_iter_init(&iter, gpdata->file_transfers);
+ while (g_hash_table_iter_next(&iter, &key, &value)) {
+ task = key;
+ spice_file_transfer_task_cancel(task);
+ }
+ }
+}
+
+static void remmina_plugin_spice_file_transfer_progress_cb(GObject *task, GParamSpec *param_spec, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+
+ RemminaPluginSpiceXferWidgets *widgets;
+ RemminaPluginSpiceData *gpdata = GET_PLUGIN_DATA(gp);
+
+ widgets = g_hash_table_lookup(gpdata->file_transfers, task);
+ if (widgets) {
+ gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(widgets->progress),
+ spice_file_transfer_task_get_progress(SPICE_FILE_TRANSFER_TASK(task)));
+ }
+}
+
+static void remmina_plugin_spice_file_transfer_finished_cb(SpiceFileTransferTask *task, GError *error, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+
+ gchar *filename, *notification_message;
+ GNotification *notification;
+ RemminaPluginSpiceData *gpdata = GET_PLUGIN_DATA(gp);
+
+ /*
+ * Send a desktop notification to inform about the outcome of
+ * the file transfer.
+ */
+ filename = spice_file_transfer_task_get_filename(task);
+
+ if (error) {
+ notification = g_notification_new(_("Transfer error"));
+ notification_message = g_strdup_printf(_("%s: %s"),
+ filename, error->message);
+ }else {
+ notification = g_notification_new(_("Transfer completed"));
+ notification_message = g_strdup_printf(_("File %s transferred successfully"),
+ filename);
+ }
+
+ g_notification_set_body(notification, notification_message);
+ g_application_send_notification(g_application_get_default(),
+ "remmina-plugin-spice-file-transfer-finished",
+ notification);
+
+ g_hash_table_remove(gpdata->file_transfers, task);
+
+ if (!g_hash_table_size(gpdata->file_transfers)) {
+ gtk_widget_hide(gpdata->file_transfer_dialog);
+ }
+
+ g_free(filename);
+ g_free(notification_message);
+ g_object_unref(notification);
+}
+# endif /* SPICE_GTK_CHECK_VERSION(0, 31, 0) */
+#endif /* SPICE_GTK_CHECK_VERSION */
diff --git a/plugins/spice/spice_plugin_usb.c b/plugins/spice/spice_plugin_usb.c
new file mode 100644
index 000000000..88edb3c9f
--- /dev/null
+++ b/plugins/spice/spice_plugin_usb.c
@@ -0,0 +1,100 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2016-2017 Denis Ollier
+ *
+ * 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 "spice_plugin.h"
+
+static void remmina_plugin_spice_usb_connect_failed_cb(GObject *, SpiceUsbDevice *, GError *, RemminaProtocolWidget *);
+
+void remmina_plugin_spice_select_usb_devices(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+
+ GtkWidget *dialog, *usb_device_widget;
+ RemminaPluginSpiceData *gpdata = GET_PLUGIN_DATA(gp);
+
+ /*
+ * FIXME: Use the RemminaConnectionWindow as transient parent widget
+ * (and add the GTK_DIALOG_DESTROY_WITH_PARENT flag) if it becomes
+ * accessible from the Remmina plugin API.
+ */
+ dialog = gtk_dialog_new_with_buttons(_("Select USB devices for redirection"),
+ NULL,
+ GTK_DIALOG_MODAL,
+ _("_Close"),
+ GTK_RESPONSE_ACCEPT,
+ NULL);
+ gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
+
+ usb_device_widget = spice_usb_device_widget_new(gpdata->session, NULL);
+ g_signal_connect(usb_device_widget,
+ "connect-failed",
+ G_CALLBACK(remmina_plugin_spice_usb_connect_failed_cb),
+ gp);
+
+ gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))),
+ usb_device_widget,
+ TRUE,
+ TRUE,
+ 0);
+ gtk_widget_show_all(dialog);
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+}
+
+static void remmina_plugin_spice_usb_connect_failed_cb(GObject *object, SpiceUsbDevice *usb_device, GError *error, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+
+ GtkWidget *dialog;
+
+ if (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANCELLED) {
+ return;
+ }
+
+ /*
+ * FIXME: Use the RemminaConnectionWindow as transient parent widget
+ * (and add the GTK_DIALOG_DESTROY_WITH_PARENT flag) if it becomes
+ * accessible from the Remmina plugin API.
+ */
+ dialog = gtk_message_dialog_new(NULL,
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ _("USB redirection error"));
+ gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
+ "%s",
+ error->message);
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+}
diff --git a/plugins/telepathy/CMakeLists.txt b/plugins/telepathy/CMakeLists.txt
new file mode 100644
index 000000000..afd4a2665
--- /dev/null
+++ b/plugins/telepathy/CMakeLists.txt
@@ -0,0 +1,65 @@
+# remmina-plugin-telepathy - The GTK+ Remote Desktop Client
+#
+# Copyright (C) 2011 Marc-Andre Moreau
+# Copyright (C) 2014-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(REMMINA_PLUGIN_TELEPATHY_SRCS
+ telepathy_plugin.c
+ telepathy_handler.c
+ telepathy_channel_handler.c
+ )
+
+add_library(remmina-plugin-telepathy MODULE ${REMMINA_PLUGIN_TELEPATHY_SRCS})
+set_target_properties(remmina-plugin-telepathy PROPERTIES PREFIX "")
+set_target_properties(remmina-plugin-telepathy PROPERTIES NO_SONAME 1)
+
+find_required_package(GTK3)
+
+include_directories(${REMMINA_COMMON_INCLUDE_DIRS} ${TELEPATHY_INCLUDE_DIRS})
+target_link_libraries(remmina-plugin-telepathy ${REMMINA_COMMON_LIBRARIES}
+ ${TELEPATHY_LIBRARIES})
+
+install(TARGETS remmina-plugin-telepathy DESTINATION ${REMMINA_PLUGINDIR})
+
+# Telepathy client file
+install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/Remmina.client
+ DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/telepathy/clients)
+
+# DBus activation file
+if(NOT REMMINA_BINARY_PATH)
+ set(REMMINA_BINARY_PATH ${CMAKE_INSTALL_FULL_BINDIR}/remmina)
+endif()
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/org.freedesktop.Telepathy.Client.Remmina.service.in
+ ${CMAKE_CURRENT_BINARY_DIR}/org.freedesktop.Telepathy.Client.Remmina.service @ONLY)
+
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.freedesktop.Telepathy.Client.Remmina.service
+ DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/dbus-1/services)
diff --git a/plugins/telepathy/Remmina.client b/plugins/telepathy/Remmina.client
new file mode 100644
index 000000000..e5baf3707
--- /dev/null
+++ b/plugins/telepathy/Remmina.client
@@ -0,0 +1,8 @@
+[org.freedesktop.Telepathy.Client]
+Interfaces=org.freedesktop.Telepathy.Client.Handler;org.freedesktop.Telepathy.Client.Interface.Requests
+
+[org.freedesktop.Telepathy.Client.Handler.HandlerChannelFilter 0]
+org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.StreamTube
+org.freedesktop.Telepathy.Channel.TargetHandleType u=1
+org.freedesktop.Telepathy.Channel.Requested b=false
+org.freedesktop.Telepathy.Channel.Type.StreamTube.Service s=rfb
diff --git a/plugins/telepathy/org.freedesktop.Telepathy.Client.Remmina.service.in b/plugins/telepathy/org.freedesktop.Telepathy.Client.Remmina.service.in
new file mode 100644
index 000000000..95c0cf775
--- /dev/null
+++ b/plugins/telepathy/org.freedesktop.Telepathy.Client.Remmina.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.freedesktop.Telepathy.Client.Remmina
+Exec=@REMMINA_BINARY_PATH@ -x telepathy
diff --git a/plugins/telepathy/telepathy_channel_handler.c b/plugins/telepathy/telepathy_channel_handler.c
new file mode 100644
index 000000000..f746beafc
--- /dev/null
+++ b/plugins/telepathy/telepathy_channel_handler.c
@@ -0,0 +1,374 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010 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 <telepathy-glib/account.h>
+#include <telepathy-glib/channel.h>
+#include <telepathy-glib/contact.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/defs.h>
+#include <telepathy-glib/handle.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/svc-client.h>
+#include <telepathy-glib/util.h>
+#include "telepathy_channel_handler.h"
+
+extern RemminaPluginService *remmina_plugin_telepathy_service;
+
+typedef struct _RemminaTpChannelHandler {
+ gchar *connection_path;
+ gchar *channel_path;
+ GHashTable *channel_properties;
+ DBusGMethodInvocation *context;
+
+ GtkWidget *proto_widget;
+ guint disconnect_handler;
+
+ TpDBusDaemon *bus;
+ TpAccount *account;
+ TpConnection *connection;
+ TpChannel *channel;
+
+ gchar *alias;
+ gchar *host;
+ guint port;
+ gchar *protocol;
+} RemminaTpChannelHandler;
+
+static void remmina_tp_channel_handler_free(RemminaTpChannelHandler *chandler)
+{
+ TRACE_CALL(__func__);
+ if (chandler->disconnect_handler) {
+ g_signal_handler_disconnect(chandler->proto_widget, chandler->disconnect_handler);
+ chandler->disconnect_handler = 0;
+ }
+ g_free(chandler->connection_path);
+ g_free(chandler->channel_path);
+ g_hash_table_destroy(chandler->channel_properties);
+ if (chandler->bus) {
+ g_object_unref(chandler->bus);
+ }
+ if (chandler->account) {
+ g_object_unref(chandler->account);
+ }
+ if (chandler->connection) {
+ g_object_unref(chandler->connection);
+ }
+ if (chandler->channel) {
+ g_object_unref(chandler->channel);
+ }
+ if (chandler->alias) {
+ g_free(chandler->alias);
+ }
+ if (chandler->host) {
+ g_free(chandler->host);
+ }
+ if (chandler->protocol) {
+ g_free(chandler->protocol);
+ }
+ g_free(chandler);
+}
+
+static void remmina_tp_channel_handler_channel_closed(TpChannel *channel, gpointer user_data, GObject *self)
+{
+ TRACE_CALL(__func__);
+ RemminaTpChannelHandler *chandler = (RemminaTpChannelHandler*)user_data;
+
+ g_print("%s: %s\n", __func__, chandler->channel_path);
+ remmina_tp_channel_handler_free(chandler);
+}
+
+static void remmina_tp_channel_handler_on_disconnect(GtkWidget *widget, RemminaTpChannelHandler *chandler)
+{
+ TRACE_CALL(__func__);
+ g_print("%s: %s\n", __func__, chandler->channel_path);
+ g_signal_handler_disconnect(widget, chandler->disconnect_handler);
+ chandler->disconnect_handler = 0;
+ tp_cli_channel_call_close(chandler->channel, -1, NULL, NULL, NULL, NULL);
+}
+
+static void remmina_tp_channel_handler_connect(RemminaTpChannelHandler *chandler)
+{
+ TRACE_CALL(__func__);
+ RemminaFile *remminafile;
+ gchar *s;
+
+ remminafile = remmina_plugin_telepathy_service->file_new();
+ remmina_plugin_telepathy_service->file_set_string(remminafile, "name", chandler->alias);
+ remmina_plugin_telepathy_service->file_set_string(remminafile, "protocol", chandler->protocol);
+ s = g_strdup_printf("[%s]:%i", chandler->host, chandler->port);
+ remmina_plugin_telepathy_service->file_set_string(remminafile, "server", s);
+ g_free(s);
+ remmina_plugin_telepathy_service->file_set_int(remminafile, "colordepth", 8);
+
+ g_free(chandler->alias);
+ chandler->alias = NULL;
+ g_free(chandler->protocol);
+ chandler->protocol = NULL;
+
+ chandler->proto_widget = remmina_plugin_telepathy_service->open_connection(remminafile,
+ G_CALLBACK(remmina_tp_channel_handler_on_disconnect), chandler, &chandler->disconnect_handler);
+}
+
+static void remmina_tp_channel_handler_get_service(TpProxy *channel, const GValue *service, const GError *error,
+ gpointer user_data, GObject *weak_object)
+{
+ TRACE_CALL(__func__);
+ RemminaTpChannelHandler *chandler = (RemminaTpChannelHandler*)user_data;
+ const gchar *svc;
+
+ if (error != NULL) {
+ g_print("%s: %s", __func__, error->message);
+ remmina_tp_channel_handler_free(chandler);
+ return;
+ }
+ svc = g_value_get_string(service);
+ g_print("%s: %s %s:%u\n", __func__, svc, chandler->host, chandler->port);
+
+ if (g_strcmp0(svc, "rfb") == 0) {
+ chandler->protocol = g_strdup("VNC");
+ }else {
+ chandler->protocol = g_ascii_strup(svc, -1);
+ }
+ remmina_tp_channel_handler_connect(chandler);
+}
+
+static void remmina_tp_channel_handler_accept(TpChannel *channel, const GValue *address, const GError *error,
+ gpointer user_data, GObject *weak_object)
+{
+ TRACE_CALL(__func__);
+ RemminaTpChannelHandler *chandler = (RemminaTpChannelHandler*)user_data;
+
+ if (error != NULL) {
+ g_print("%s: %s", __func__, error->message);
+ remmina_tp_channel_handler_free(chandler);
+ return;
+ }
+
+ dbus_g_type_struct_get(address, 0, &chandler->host, 1, &chandler->port, G_MAXUINT);
+
+ tp_cli_dbus_properties_call_get(channel, -1, TP_IFACE_CHANNEL_TYPE_STREAM_TUBE, "Service",
+ remmina_tp_channel_handler_get_service, chandler, NULL, NULL);
+}
+
+static void remmina_tp_channel_handler_on_response(GtkDialog *dialog, gint response_id, RemminaTpChannelHandler *chandler)
+{
+ TRACE_CALL(__func__);
+ GValue noop =
+ { 0 };
+ GError *error;
+
+ if (response_id == GTK_RESPONSE_YES) {
+ g_value_init(&noop, G_TYPE_INT);
+ tp_cli_channel_type_stream_tube_call_accept(chandler->channel, -1, TP_SOCKET_ADDRESS_TYPE_IPV4,
+ TP_SOCKET_ACCESS_CONTROL_LOCALHOST, &noop, remmina_tp_channel_handler_accept, chandler, NULL,
+ NULL);
+ g_value_unset(&noop);
+ tp_svc_client_handler_return_from_handle_channels(chandler->context);
+ }else {
+ error = g_error_new(TP_ERRORS, TP_ERROR_NOT_AVAILABLE, "Channel rejected by user.");
+ dbus_g_method_return_error(chandler->context, error);
+ g_error_free(error);
+ remmina_tp_channel_handler_free(chandler);
+ }
+}
+
+static void remmina_tp_channel_handler_get_contacts(TpConnection *connection, guint n_contacts, TpContact * const *contacts,
+ guint n_failed, const TpHandle *failed, const GError *error, gpointer user_data, GObject *weak_object)
+{
+ TRACE_CALL(__func__);
+ RemminaTpChannelHandler *chandler = (RemminaTpChannelHandler*)user_data;
+ TpContact *contact;
+ gchar *token;
+ gchar *cm;
+ gchar *protocol;
+ gchar *filename;
+ GdkPixbuf *pixbuf;
+ GtkWidget *image;
+ GtkWidget *dialog;
+
+ if (error != NULL) {
+ g_print("%s: %s", __func__, error->message);
+ remmina_tp_channel_handler_free(chandler);
+ return;
+ }
+ if (n_contacts <= 0) {
+ g_print("%s: no contacts\n", __func__);
+ remmina_tp_channel_handler_free(chandler);
+ return;
+ }
+ contact = contacts[0];
+ chandler->alias = g_strdup(tp_contact_get_alias(contact));
+
+ dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
+ _("%s wants to share his/her desktop.\nDo you accept the invitation?"), chandler->alias);
+ g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(remmina_tp_channel_handler_on_response), chandler);
+ g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(gtk_widget_destroy), NULL);
+ gtk_window_set_title(GTK_WINDOW(dialog), _("Desktop sharing invitation"));
+ remmina_plugin_telepathy_service->ui_register(dialog);
+ gtk_widget_show(dialog);
+
+ token = (gchar*)tp_contact_get_avatar_token(contact);
+ if (token == NULL) {
+ return;
+ }
+ if (!tp_connection_parse_object_path(chandler->connection, &protocol, &cm)) {
+ g_print("tp_connection_parse_object_path: failed\n");
+ return;
+ }
+ token = tp_escape_as_identifier(token);
+ filename = g_build_filename(g_get_user_cache_dir(), "telepathy", "avatars", cm, protocol, token, NULL);
+ g_free(cm);
+ g_free(protocol);
+ g_free(token);
+ if (g_file_test(filename, G_FILE_TEST_EXISTS)) {
+ pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
+ if (pixbuf) {
+ image = gtk_image_new_from_pixbuf(pixbuf);
+ gtk_widget_show(image);
+ g_object_unref(pixbuf);
+ gtk_message_dialog_set_image(GTK_MESSAGE_DIALOG(dialog), image);
+ }
+ }
+ g_free(filename);
+}
+
+static void remmina_tp_channel_handler_channel_ready(TpChannel *channel, const GError *channel_error, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ RemminaTpChannelHandler *chandler = (RemminaTpChannelHandler*)user_data;
+ TpHandle handle;
+ GError *error = NULL;
+ TpContactFeature features[] =
+ { TP_CONTACT_FEATURE_ALIAS, TP_CONTACT_FEATURE_AVATAR_TOKEN };
+
+ if (channel_error != NULL) {
+ g_print("%s: %s\n", __func__, channel_error->message);
+ remmina_tp_channel_handler_free(chandler);
+ return;
+ }
+
+ if (tp_cli_channel_connect_to_closed(channel, remmina_tp_channel_handler_channel_closed, chandler, NULL, NULL, &error)
+ == NULL) {
+ g_print("tp_cli_channel_connect_to_closed: %s\n", channel_error->message);
+ remmina_tp_channel_handler_free(chandler);
+ return;
+ }
+ g_print("%s: %s\n", __func__, chandler->channel_path);
+
+ handle = tp_channel_get_handle(channel, NULL);
+ tp_connection_get_contacts_by_handle(chandler->connection, 1, &handle, G_N_ELEMENTS(features), features,
+ remmina_tp_channel_handler_get_contacts, chandler, NULL, NULL);
+}
+
+static void remmina_tp_channel_handler_connection_ready(TpConnection *connection, const GError *connection_error,
+ gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ RemminaTpChannelHandler *chandler = (RemminaTpChannelHandler*)user_data;
+ GError *error = NULL;
+
+ if (connection_error != NULL) {
+ g_print("%s: %s\n", __func__, connection_error->message);
+ remmina_tp_channel_handler_free(chandler);
+ return;
+ }
+
+ chandler->channel = tp_channel_new_from_properties(connection, chandler->channel_path, chandler->channel_properties,
+ &error);
+ if (chandler->channel == NULL) {
+ g_print("tp_channel_new_from_properties: %s\n", error->message);
+ remmina_tp_channel_handler_free(chandler);
+ return;
+ }
+ tp_channel_call_when_ready(chandler->channel, remmina_tp_channel_handler_channel_ready, chandler);
+}
+
+static void remmina_tp_channel_handler_account_ready(GObject *account, GAsyncResult *res, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ RemminaTpChannelHandler *chandler = (RemminaTpChannelHandler*)user_data;
+ GError *error = NULL;
+
+ if (!tp_account_prepare_finish(TP_ACCOUNT(account), res, &error)) {
+ g_print("tp_account_prepare_finish: %s\n", error->message);
+ remmina_tp_channel_handler_free(chandler);
+ return;
+ }
+
+ chandler->connection = tp_connection_new(chandler->bus, NULL, chandler->connection_path, &error);
+ if (chandler->connection == NULL) {
+ g_print("tp_connection_new: %s\n", error->message);
+ remmina_tp_channel_handler_free(chandler);
+ return;
+ }
+ tp_connection_call_when_ready(chandler->connection, remmina_tp_channel_handler_connection_ready, chandler);
+}
+
+void remmina_tp_channel_handler_new(const gchar *account_path, const gchar *connection_path, const gchar *channel_path,
+ GHashTable *channel_properties, DBusGMethodInvocation *context)
+{
+ TRACE_CALL(__func__);
+ TpDBusDaemon *bus;
+ TpAccount *account;
+ GError *error = NULL;
+ RemminaTpChannelHandler *chandler;
+
+ bus = tp_dbus_daemon_dup(&error);
+ if (bus == NULL) {
+ g_print("tp_dbus_daemon_dup: %s", error->message);
+ return;
+ }
+ account = tp_account_new(bus, account_path, &error);
+ if (account == NULL) {
+ g_object_unref(bus);
+ g_print("tp_account_new: %s", error->message);
+ return;
+ }
+
+ chandler = g_new0(RemminaTpChannelHandler, 1);
+ chandler->bus = bus;
+ chandler->account = account;
+ chandler->connection_path = g_strdup(connection_path);
+ chandler->channel_path = g_strdup(channel_path);
+ chandler->channel_properties = tp_asv_new(NULL, NULL);
+ tp_g_hash_table_update(chandler->channel_properties, channel_properties, (GBoxedCopyFunc)g_strdup,
+ (GBoxedCopyFunc)tp_g_value_slice_dup);
+ chandler->context = context;
+
+ tp_account_prepare_async(account, NULL, remmina_tp_channel_handler_account_ready, chandler);
+}
+
diff --git a/plugins/telepathy/telepathy_channel_handler.h b/plugins/telepathy/telepathy_channel_handler.h
new file mode 100644
index 000000000..6e202a93c
--- /dev/null
+++ b/plugins/telepathy/telepathy_channel_handler.h
@@ -0,0 +1,44 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010 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_tp_channel_handler_new(const gchar *account_path, const gchar *connection_path, const gchar *channel_path,
+ GHashTable *channel_properties, DBusGMethodInvocation *context);
+
+G_END_DECLS
diff --git a/plugins/telepathy/telepathy_handler.c b/plugins/telepathy/telepathy_handler.c
new file mode 100644
index 000000000..bc1ad80a2
--- /dev/null
+++ b/plugins/telepathy/telepathy_handler.c
@@ -0,0 +1,127 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010 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 <telepathy-glib/dbus.h>
+#include <telepathy-glib/defs.h>
+#include <telepathy-glib/svc-client.h>
+#include "telepathy_channel_handler.h"
+#include "telepathy_handler.h"
+
+extern RemminaPluginService *remmina_plugin_telepathy_service;
+
+#define REMMINA_TP_BUS_NAME TP_CLIENT_BUS_NAME_BASE "Remmina"
+#define REMMINA_TP_OBJECT_PATH TP_CLIENT_OBJECT_PATH_BASE "Remmina"
+
+static void remmina_tp_handler_iface_init(gpointer g_iface, gpointer iface_data);
+
+G_DEFINE_TYPE_WITH_CODE(RemminaTpHandler, remmina_tp_handler, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE(TP_TYPE_SVC_CLIENT, NULL);
+ G_IMPLEMENT_INTERFACE( TP_TYPE_SVC_CLIENT_HANDLER, remmina_tp_handler_iface_init);
+ );
+
+static void remmina_tp_handler_class_init(RemminaTpHandlerClass *klass)
+{
+ TRACE_CALL(__func__);
+}
+
+static void remmina_tp_handler_init(RemminaTpHandler *handler)
+{
+ TRACE_CALL(__func__);
+}
+
+static void remmina_tp_handler_handle_channels(TpSvcClientHandler *handler, const char *account_path,
+ const char *connection_path, const GPtrArray *channels, const GPtrArray *requests_satisfied,
+ guint64 user_action_time, GHashTable *handler_info, DBusGMethodInvocation *context)
+{
+ TRACE_CALL(__func__);
+ gint i;
+ GValueArray *array;
+
+ for (i = 0; i < channels->len; i++) {
+ array = g_ptr_array_index(channels, i);
+ remmina_tp_channel_handler_new(account_path, connection_path,
+ (const gchar*)g_value_get_boxed(g_value_array_get_nth(array, 0)),
+ (GHashTable*)g_value_get_boxed(g_value_array_get_nth(array, 1)), context);
+ }
+}
+
+static void remmina_tp_handler_iface_init(gpointer g_iface, gpointer iface_data)
+{
+ TRACE_CALL(__func__);
+ TpSvcClientHandlerClass *klass = (TpSvcClientHandlerClass*)g_iface;
+
+#define IMPLEMENT(x) tp_svc_client_handler_implement_ ## x(klass, remmina_tp_handler_ ## x)
+ IMPLEMENT(handle_channels);
+#undef IMPLEMENT
+}
+
+static gboolean remmina_tp_handler_register(RemminaTpHandler *handler)
+{
+ TRACE_CALL(__func__);
+ TpDBusDaemon *bus;
+ GError *error = NULL;
+
+ bus = tp_dbus_daemon_dup(&error);
+ if (bus == NULL) {
+ g_print("tp_dbus_daemon_dup: %s", error->message);
+ return FALSE;
+ }
+ if (!tp_dbus_daemon_request_name(bus, REMMINA_TP_BUS_NAME, FALSE, &error)) {
+ g_object_unref(bus);
+ g_print("tp_dbus_daemon_request_name: %s", error->message);
+ return FALSE;
+ }
+ dbus_g_connection_register_g_object(
+ tp_proxy_get_dbus_connection(TP_PROXY(bus)),
+ REMMINA_TP_OBJECT_PATH, G_OBJECT(handler));
+ g_object_unref(bus);
+ g_print("%s: bus_name " REMMINA_TP_BUS_NAME
+ " object_path " REMMINA_TP_OBJECT_PATH "\n", __func__);
+ return TRUE;
+}
+
+RemminaTpHandler*
+remmina_tp_handler_new(void)
+{
+ TRACE_CALL(__func__);
+ RemminaTpHandler *handler;
+
+ handler = REMMINA_TP_HANDLER(g_object_new(REMMINA_TYPE_TP_HANDLER, NULL));
+ remmina_tp_handler_register(handler);
+ return handler;
+}
+
diff --git a/plugins/telepathy/telepathy_handler.h b/plugins/telepathy/telepathy_handler.h
new file mode 100644
index 000000000..9116345f3
--- /dev/null
+++ b/plugins/telepathy/telepathy_handler.h
@@ -0,0 +1,58 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010 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
+
+#define REMMINA_TYPE_TP_HANDLER (remmina_tp_handler_get_type())
+#define REMMINA_TP_HANDLER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), REMMINA_TYPE_TP_HANDLER, RemminaTpHandler))
+#define REMMINA_TP_HANDLER_CLASS(obj) (G_TYPE_CHECK_CLASS_CAST((obj), REMMINA_TYPE_TP_HANDLER, RemminaTpHandlerClass))
+#define REMMINA_IS_TP_HANDLER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), REMMINA_TYPE_TP_HANDLER))
+#define REMMINA_IS_TP_HANDLER_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((obj), REMMINA_TYPE_TP_HANDLER))
+#define REMMINA_TP_HANDLER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), REMMINA_TYPE_TP_HANDLER, RemminaTpHandlerClass))
+
+typedef struct _RemminaTpHandler {
+ GObject parent;
+} RemminaTpHandler;
+
+typedef struct _RemminaTpHandlerClass {
+ GObjectClass parent_class;
+} RemminaTpHandlerClass;
+
+RemminaTpHandler* remmina_tp_handler_new(void);
+
+G_END_DECLS
+
diff --git a/plugins/telepathy/telepathy_plugin.c b/plugins/telepathy/telepathy_plugin.c
new file mode 100644
index 000000000..d85549904
--- /dev/null
+++ b/plugins/telepathy/telepathy_plugin.c
@@ -0,0 +1,77 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010 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 "telepathy_handler.h"
+
+RemminaPluginService *remmina_plugin_telepathy_service = NULL;
+
+static RemminaTpHandler *remmina_tp_handler = NULL;
+
+void remmina_plugin_telepathy_entry(void)
+{
+ TRACE_CALL(__func__);
+ if (remmina_tp_handler == NULL) {
+ remmina_tp_handler = remmina_tp_handler_new();
+ }
+}
+
+/* Entry plugin definition and features */
+static RemminaEntryPlugin remmina_plugin_telepathy =
+{
+ REMMINA_PLUGIN_TYPE_ENTRY, // Type
+ "telepathy", // Name
+ N_("Telepathy - Desktop Sharing"), // Description
+ GETTEXT_PACKAGE, // Translation domain
+ VERSION, // Version number
+ remmina_plugin_telepathy_entry // Plugin entry function
+};
+
+G_MODULE_EXPORT gboolean
+remmina_plugin_entry(RemminaPluginService *service)
+{
+ TRACE_CALL(__func__);
+ remmina_plugin_telepathy_service = service;
+
+ bindtextdomain(GETTEXT_PACKAGE, REMMINA_RUNTIME_LOCALEDIR);
+ bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
+
+ if (!service->register_plugin((RemminaPlugin*)&remmina_plugin_telepathy)) {
+ return FALSE;
+ }
+ return TRUE;
+}
+
diff --git a/plugins/tool_hello_world/16x16/emblems/remmina-tool.png b/plugins/tool_hello_world/16x16/emblems/remmina-tool.png
new file mode 100644
index 000000000..96bc6828a
--- /dev/null
+++ b/plugins/tool_hello_world/16x16/emblems/remmina-tool.png
Binary files differ
diff --git a/plugins/tool_hello_world/22x22/emblems/remmina-tool.png b/plugins/tool_hello_world/22x22/emblems/remmina-tool.png
new file mode 100644
index 000000000..a28ca75ad
--- /dev/null
+++ b/plugins/tool_hello_world/22x22/emblems/remmina-tool.png
Binary files differ
diff --git a/plugins/tool_hello_world/CMakeLists.txt b/plugins/tool_hello_world/CMakeLists.txt
new file mode 100644
index 000000000..5fa226d5d
--- /dev/null
+++ b/plugins/tool_hello_world/CMakeLists.txt
@@ -0,0 +1,57 @@
+# remmina-plugin-tool_hello_world - The GTK+ Remote Desktop Client
+#
+# 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(REMMINA_PLUGIN_SRCS
+ plugin_config.h
+ plugin.c
+)
+
+add_library(remmina-plugin-tool_hello_world MODULE ${REMMINA_PLUGIN_SRCS})
+set_target_properties(remmina-plugin-tool_hello_world PROPERTIES PREFIX "")
+set_target_properties(remmina-plugin-tool_hello_world PROPERTIES NO_SONAME 1)
+
+include_directories(${REMMINA_COMMON_INCLUDE_DIRS})
+include_directories(${GTK_INCLUDE_DIRS})
+include_directories(${CMAKE_SOURCE_DIR}/plugins)
+
+install(TARGETS remmina-plugin-tool_hello_world DESTINATION ${REMMINA_PLUGINDIR})
+
+install(FILES
+ 16x16/emblems/remmina-tool.png
+ 16x16/emblems/remmina-tool.png
+ DESTINATION ${APPICON16_EMBLEMS_DIR})
+install(FILES
+ 22x22/emblems/remmina-tool.png
+ 22x22/emblems/remmina-tool.png
+ DESTINATION ${APPICON22_EMBLEMS_DIR})
diff --git a/plugins/tool_hello_world/plugin.c b/plugins/tool_hello_world/plugin.c
new file mode 100644
index 000000000..895efebf0
--- /dev/null
+++ b/plugins/tool_hello_world/plugin.c
@@ -0,0 +1,123 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010 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 "plugin_config.h"
+
+#include "common/remmina_plugin.h"
+
+#if GTK_VERSION == 3
+# include <gtk/gtkx.h>
+# include <gdk/gdkx.h>
+#endif
+
+static RemminaPluginService *remmina_plugin_service = NULL;
+
+static void remmina_plugin_tool_init(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ remmina_plugin_service->log_printf("[%s] Plugin init\n", PLUGIN_NAME);
+}
+
+static gboolean remmina_plugin_tool_open_connection(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ remmina_plugin_service->log_printf("[%s] Plugin open connection\n", PLUGIN_NAME);
+
+ GtkDialog *dialog;
+ dialog = GTK_DIALOG(gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL | GTK_DIALOG_USE_HEADER_BAR,
+ GTK_MESSAGE_INFO, GTK_BUTTONS_OK, PLUGIN_DESCRIPTION));
+ gtk_dialog_run(dialog);
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+ return FALSE;
+}
+
+static gboolean remmina_plugin_tool_close_connection(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ remmina_plugin_service->log_printf("[%s] Plugin close connection\n", PLUGIN_NAME);
+ remmina_plugin_service->protocol_plugin_emit_signal(gp, "disconnect");
+ return FALSE;
+}
+
+/* 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_plugin_tool_basic_settings[] =
+{
+ { REMMINA_PROTOCOL_SETTING_TYPE_END, NULL, NULL, FALSE, NULL, NULL }
+};
+
+/* Protocol plugin definition and features */
+static RemminaProtocolPlugin remmina_plugin = {
+ REMMINA_PLUGIN_TYPE_PROTOCOL, // Type
+ PLUGIN_NAME, // Name
+ PLUGIN_DESCRIPTION, // Description
+ GETTEXT_PACKAGE, // Translation domain
+ PLUGIN_VERSION, // Version number
+ PLUGIN_APPICON, // Icon for normal connection
+ PLUGIN_APPICON, // Icon for SSH connection
+ remmina_plugin_tool_basic_settings, // Array for basic settings
+ NULL, // Array for advanced settings
+ REMMINA_PROTOCOL_SSH_SETTING_NONE, // SSH settings type
+ NULL, // Array for available features
+ remmina_plugin_tool_init, // Plugin initialization
+ remmina_plugin_tool_open_connection, // Plugin open connection
+ remmina_plugin_tool_close_connection, // Plugin close connection
+ NULL, // Query for available features
+ NULL, // Call a feature
+ NULL, // Send a keystroke */
+};
+
+G_MODULE_EXPORT gboolean remmina_plugin_entry(RemminaPluginService *service)
+{
+ TRACE_CALL(__func__);
+ remmina_plugin_service = service;
+
+ bindtextdomain(GETTEXT_PACKAGE, REMMINA_RUNTIME_LOCALEDIR);
+ bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
+
+ if (!service->register_plugin((RemminaPlugin*)&remmina_plugin)) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
diff --git a/plugins/tool_hello_world/plugin_config.h b/plugins/tool_hello_world/plugin_config.h
new file mode 100644
index 000000000..154c25192
--- /dev/null
+++ b/plugins/tool_hello_world/plugin_config.h
@@ -0,0 +1,43 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 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.
+ *
+ */
+
+#ifndef __PLUGIN_CONFIG_H
+#define __PLUGIN_CONFIG_H
+
+#define PLUGIN_NAME "HELLO"
+#define PLUGIN_DESCRIPTION "Hello World!"
+#define PLUGIN_VERSION "1.0"
+#define PLUGIN_APPICON "remmina-tool"
+#endif
diff --git a/plugins/vnc/16x16/emblems/remmina-vnc-ssh.png b/plugins/vnc/16x16/emblems/remmina-vnc-ssh.png
new file mode 100644
index 000000000..8b3363714
--- /dev/null
+++ b/plugins/vnc/16x16/emblems/remmina-vnc-ssh.png
Binary files differ
diff --git a/plugins/vnc/16x16/emblems/remmina-vnc.png b/plugins/vnc/16x16/emblems/remmina-vnc.png
new file mode 100644
index 000000000..b06083010
--- /dev/null
+++ b/plugins/vnc/16x16/emblems/remmina-vnc.png
Binary files differ
diff --git a/plugins/vnc/22x22/emblems/remmina-vnc-ssh.png b/plugins/vnc/22x22/emblems/remmina-vnc-ssh.png
new file mode 100644
index 000000000..644d7aa81
--- /dev/null
+++ b/plugins/vnc/22x22/emblems/remmina-vnc-ssh.png
Binary files differ
diff --git a/plugins/vnc/22x22/emblems/remmina-vnc.png b/plugins/vnc/22x22/emblems/remmina-vnc.png
new file mode 100644
index 000000000..ab1cf2964
--- /dev/null
+++ b/plugins/vnc/22x22/emblems/remmina-vnc.png
Binary files differ
diff --git a/plugins/vnc/CMakeLists.txt b/plugins/vnc/CMakeLists.txt
new file mode 100644
index 000000000..a74178111
--- /dev/null
+++ b/plugins/vnc/CMakeLists.txt
@@ -0,0 +1,54 @@
+# remmina-plugin-vnc - The GTK+ Remote Desktop Client
+#
+# Copyright (C) 2011 Marc-Andre Moreau
+# Copyright (C) 2014-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(LIBVNCSERVER_INCLUDE_DIRS)
+set(LIBVNCSERVER_LIBRARIES vncclient)
+
+set(REMMINA_PLUGIN_VNC_SRCS vnc_plugin.c)
+
+add_library(remmina-plugin-vnc MODULE ${REMMINA_PLUGIN_VNC_SRCS})
+set_target_properties(remmina-plugin-vnc PROPERTIES PREFIX "")
+set_target_properties(remmina-plugin-vnc PROPERTIES NO_SONAME 1)
+
+include_directories(${REMMINA_COMMON_INCLUDE_DIRS} ${LIBVNCSERVER_INCLUDE_DIRS})
+target_link_libraries(remmina-plugin-vnc ${REMMINA_COMMON_LIBRARIES} ${LIBVNCSERVER_LIBRARIES})
+
+install(TARGETS remmina-plugin-vnc DESTINATION ${REMMINA_PLUGINDIR})
+
+install(FILES
+ 16x16/emblems/remmina-vnc-ssh.png
+ 16x16/emblems/remmina-vnc.png DESTINATION ${APPICON16_EMBLEMS_DIR})
+install(FILES
+ 22x22/emblems/remmina-vnc-ssh.png
+ 22x22/emblems/remmina-vnc.png DESTINATION ${APPICON22_EMBLEMS_DIR})
diff --git a/plugins/vnc/vnc_plugin.c b/plugins/vnc/vnc_plugin.c
new file mode 100644
index 000000000..1c5accdc5
--- /dev/null
+++ b/plugins/vnc/vnc_plugin.c
@@ -0,0 +1,1981 @@
+/*
+ * 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"
+
+#define REMMINA_PLUGIN_VNC_FEATURE_PREF_QUALITY 1
+#define REMMINA_PLUGIN_VNC_FEATURE_PREF_VIEWONLY 2
+#define REMMINA_PLUGIN_VNC_FEATURE_PREF_DISABLESERVERINPUT 3
+#define REMMINA_PLUGIN_VNC_FEATURE_TOOL_REFRESH 4
+#define REMMINA_PLUGIN_VNC_FEATURE_TOOL_CHAT 5
+#define REMMINA_PLUGIN_VNC_FEATURE_SCALE 6
+#define REMMINA_PLUGIN_VNC_FEATURE_UNFOCUS 7
+#define REMMINA_PLUGIN_VNC_FEATURE_TOOL_SENDCTRLALTDEL 8
+
+#define GET_PLUGIN_DATA(gp) (RemminaPluginVncData*)g_object_get_data(G_OBJECT(gp), "plugin-data")
+
+typedef struct _RemminaPluginVncData {
+ /* Whether the user requests to connect/disconnect */
+ gboolean connected;
+ /* Whether the vnc process is running */
+ gboolean running;
+ /* Whether the initialzation calls the authentication process */
+ gboolean auth_called;
+ /* Whether it is the first attempt for authentication. Only first attempt will try to use cached credentials */
+ gboolean auth_first;
+
+ GtkWidget *drawing_area;
+ guchar *vnc_buffer;
+ cairo_surface_t *rgb_buffer;
+
+ gint queuedraw_x, queuedraw_y, queuedraw_w, queuedraw_h;
+ guint queuedraw_handler;
+
+ gulong clipboard_handler;
+ GTimeVal clipboard_timer;
+
+ cairo_surface_t *queuecursor_surface;
+ gint queuecursor_x, queuecursor_y;
+ guint queuecursor_handler;
+
+ gpointer client;
+ gint listen_sock;
+
+ gint button_mask;
+
+ GPtrArray *pressed_keys;
+
+ pthread_mutex_t vnc_event_queue_mutex;
+ GQueue *vnc_event_queue;
+ gint vnc_event_pipe[2];
+
+ pthread_t thread;
+ pthread_mutex_t buffer_mutex;
+
+} RemminaPluginVncData;
+
+static RemminaPluginService *remmina_plugin_service = NULL;
+
+static int dot_cursor_x_hot = 2;
+static int dot_cursor_y_hot = 2;
+static const gchar * dot_cursor_xpm[] =
+{ "5 5 3 1", " c None", ". c #000000", "+ c #FFFFFF", " ... ", ".+++.", ".+ +.", ".+++.", " ... " };
+
+
+#define LOCK_BUFFER(t) if (t) { CANCEL_DEFER } pthread_mutex_lock(&gpdata->buffer_mutex);
+#define UNLOCK_BUFFER(t) pthread_mutex_unlock(&gpdata->buffer_mutex); if (t) { CANCEL_ASYNC }
+
+enum {
+ REMMINA_PLUGIN_VNC_EVENT_KEY,
+ REMMINA_PLUGIN_VNC_EVENT_POINTER,
+ REMMINA_PLUGIN_VNC_EVENT_CUTTEXT,
+ REMMINA_PLUGIN_VNC_EVENT_CHAT_OPEN,
+ REMMINA_PLUGIN_VNC_EVENT_CHAT_SEND,
+ REMMINA_PLUGIN_VNC_EVENT_CHAT_CLOSE
+};
+
+typedef struct _RemminaPluginVncEvent {
+ gint event_type;
+ union {
+ struct {
+ guint keyval;
+ gboolean pressed;
+ } key;
+ struct {
+ gint x;
+ gint y;
+ gint button_mask;
+ } pointer;
+ struct {
+ gchar *text;
+ } text;
+ } event_data;
+} RemminaPluginVncEvent;
+
+typedef struct _RemminaPluginVncCoordinates {
+ gint x, y;
+} RemminaPluginVncCoordinates;
+
+
+/* --------- Support for execution on main thread of GUI functions -------------- */
+static void remmina_plugin_vnc_update_scale(RemminaProtocolWidget *gp, gboolean scale);
+
+struct onMainThread_cb_data {
+
+ enum { FUNC_UPDATE_SCALE } func;
+ GtkWidget *widget;
+ gint x, y, width, height;
+ RemminaProtocolWidget* gp;
+ gboolean scale;
+
+ /* Mutex for thread synchronization */
+ pthread_mutex_t mu;
+ /* Flag to catch cancellations */
+ gboolean cancelled;
+};
+
+static gboolean onMainThread_cb(struct onMainThread_cb_data *d)
+{
+ TRACE_CALL(__func__);
+ if ( !d->cancelled ) {
+ switch ( d->func ) {
+ case FUNC_UPDATE_SCALE:
+ remmina_plugin_vnc_update_scale(d->gp, d->scale);
+ break;
+ }
+ pthread_mutex_unlock( &d->mu );
+ } else {
+ /* thread has been cancelled, so we must free d memory here */
+ g_free( d );
+ }
+ return G_SOURCE_REMOVE;
+}
+
+
+static void onMainThread_cleanup_handler( gpointer data )
+{
+ TRACE_CALL(__func__);
+ struct onMainThread_cb_data *d = data;
+ d->cancelled = TRUE;
+}
+
+
+static void onMainThread_schedule_callback_and_wait( struct onMainThread_cb_data *d )
+{
+ TRACE_CALL(__func__);
+ d->cancelled = FALSE;
+ pthread_cleanup_push( onMainThread_cleanup_handler, d );
+ pthread_mutex_init( &d->mu, NULL );
+ pthread_mutex_lock( &d->mu );
+ gdk_threads_add_idle( (GSourceFunc)onMainThread_cb, (gpointer)d );
+
+ pthread_mutex_lock( &d->mu );
+
+ pthread_cleanup_pop(0);
+ pthread_mutex_unlock( &d->mu );
+ pthread_mutex_destroy( &d->mu );
+}
+
+static void remmina_plugin_vnc_event_push(RemminaProtocolWidget *gp, gint event_type, gpointer p1, gpointer p2, gpointer p3)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaPluginVncEvent *event;
+
+ event = g_new(RemminaPluginVncEvent, 1);
+ event->event_type = event_type;
+ switch (event_type) {
+ case REMMINA_PLUGIN_VNC_EVENT_KEY:
+ event->event_data.key.keyval = GPOINTER_TO_UINT(p1);
+ event->event_data.key.pressed = GPOINTER_TO_INT(p2);
+ break;
+ case REMMINA_PLUGIN_VNC_EVENT_POINTER:
+ event->event_data.pointer.x = GPOINTER_TO_INT(p1);
+ event->event_data.pointer.y = GPOINTER_TO_INT(p2);
+ event->event_data.pointer.button_mask = GPOINTER_TO_INT(p3);
+ break;
+ case REMMINA_PLUGIN_VNC_EVENT_CUTTEXT:
+ case REMMINA_PLUGIN_VNC_EVENT_CHAT_SEND:
+ event->event_data.text.text = g_strdup((char*)p1);
+ break;
+ default:
+ break;
+ }
+
+ pthread_mutex_lock(&gpdata->vnc_event_queue_mutex);
+ g_queue_push_tail(gpdata->vnc_event_queue, event);
+ pthread_mutex_unlock(&gpdata->vnc_event_queue_mutex);
+
+ if (write(gpdata->vnc_event_pipe[1], "\0", 1)) {
+ /* Ignore */
+ }
+}
+
+static void remmina_plugin_vnc_event_free(RemminaPluginVncEvent *event)
+{
+ TRACE_CALL(__func__);
+ switch (event->event_type) {
+ case REMMINA_PLUGIN_VNC_EVENT_CUTTEXT:
+ case REMMINA_PLUGIN_VNC_EVENT_CHAT_SEND:
+ g_free(event->event_data.text.text);
+ break;
+ default:
+ break;
+ }
+ g_free(event);
+}
+
+static void remmina_plugin_vnc_event_free_all(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaPluginVncEvent *event;
+
+ /* This is called from main thread after plugin thread has
+ been closed, so no queue locking is nessesary here */
+ while ((event = g_queue_pop_head(gpdata->vnc_event_queue)) != NULL) {
+ remmina_plugin_vnc_event_free(event);
+ }
+}
+
+static void remmina_plugin_vnc_scale_area(RemminaProtocolWidget *gp, gint *x, gint *y, gint *w, gint *h)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ GtkAllocation widget_allocation;
+ gint width, height;
+ gint sx, sy, sw, sh;
+
+ if (gpdata->rgb_buffer == NULL)
+ return;
+
+ width = remmina_plugin_service->protocol_plugin_get_width(gp);
+ height = remmina_plugin_service->protocol_plugin_get_height(gp);
+
+ gtk_widget_get_allocation(GTK_WIDGET(gp), &widget_allocation);
+
+ if (widget_allocation.width == width && widget_allocation.height == height)
+ return; /* Same size, no scaling */
+
+ /* We have to extend the scaled region 2 scaled pixels, to avoid gaps */
+ sx = MIN(MAX(0, (*x) * widget_allocation.width / width - widget_allocation.width / width - 2), widget_allocation.width - 1);
+ sy = MIN(MAX(0, (*y) * widget_allocation.height / height - widget_allocation.height / height - 2), widget_allocation.height - 1);
+ sw = MIN(widget_allocation.width - sx, (*w) * widget_allocation.width / width + widget_allocation.width / width + 4);
+ sh = MIN(widget_allocation.height - sy, (*h) * widget_allocation.height / height + widget_allocation.height / height + 4);
+
+ *x = sx;
+ *y = sy;
+ *w = sw;
+ *h = sh;
+}
+
+static void remmina_plugin_vnc_update_scale(RemminaProtocolWidget *gp, gboolean scale)
+{
+ TRACE_CALL(__func__);
+ /* This function can be called from a non main thread */
+
+ RemminaPluginVncData *gpdata;
+ gint width, height;
+
+ if ( !remmina_plugin_service->is_main_thread() ) {
+ struct onMainThread_cb_data *d;
+ d = (struct onMainThread_cb_data *)g_malloc( sizeof(struct onMainThread_cb_data) );
+ d->func = FUNC_UPDATE_SCALE;
+ d->gp = gp;
+ d->scale = scale;
+ onMainThread_schedule_callback_and_wait( d );
+ g_free(d);
+ return;
+ }
+
+ gpdata = GET_PLUGIN_DATA(gp);
+
+ width = remmina_plugin_service->protocol_plugin_get_width(gp);
+ height = remmina_plugin_service->protocol_plugin_get_height(gp);
+ if (scale) {
+ /* In scaled mode, drawing_area will get its dimensions from its parent */
+ gtk_widget_set_size_request(GTK_WIDGET(gpdata->drawing_area), -1, -1 );
+ }else {
+ /* In non scaled mode, the plugins forces dimensions of drawing area */
+ gtk_widget_set_size_request(GTK_WIDGET(gpdata->drawing_area), width, height);
+ }
+
+ remmina_plugin_service->protocol_plugin_emit_signal(gp, "update-align");
+}
+
+gboolean remmina_plugin_vnc_setcursor(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ GdkCursor *cur;
+
+ LOCK_BUFFER(FALSE);
+ gpdata->queuecursor_handler = 0;
+
+ if (gpdata->queuecursor_surface) {
+ cur = gdk_cursor_new_from_surface(gdk_display_get_default(), gpdata->queuecursor_surface, gpdata->queuecursor_x,
+ gpdata->queuecursor_y);
+ gdk_window_set_cursor(gtk_widget_get_window(gpdata->drawing_area), cur);
+ g_object_unref(cur);
+ cairo_surface_destroy(gpdata->queuecursor_surface);
+ gpdata->queuecursor_surface = NULL;
+ }else {
+ gdk_window_set_cursor(gtk_widget_get_window(gpdata->drawing_area), NULL);
+ }
+ UNLOCK_BUFFER(FALSE);
+
+ return FALSE;
+}
+
+static void remmina_plugin_vnc_queuecursor(RemminaProtocolWidget *gp, cairo_surface_t *surface, gint x, gint y)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+
+ if (gpdata->queuecursor_surface) {
+ cairo_surface_destroy(gpdata->queuecursor_surface);
+ }
+ gpdata->queuecursor_surface = surface;
+ gpdata->queuecursor_x = x;
+ gpdata->queuecursor_y = y;
+ if (!gpdata->queuecursor_handler) {
+ gpdata->queuecursor_handler = IDLE_ADD((GSourceFunc)remmina_plugin_vnc_setcursor, gp);
+ }
+}
+
+typedef struct _RemminaKeyVal {
+ guint keyval;
+ guint16 keycode;
+} RemminaKeyVal;
+
+/***************************** LibVNCClient related codes *********************************/
+#include <rfb/rfbclient.h>
+
+static const uint32_t remmina_plugin_vnc_no_encrypt_auth_types[] =
+{ rfbNoAuth, rfbVncAuth, rfbMSLogon, 0 };
+
+static RemminaPluginVncEvent *remmina_plugin_vnc_event_queue_pop_head(RemminaPluginVncData *gpdata)
+{
+ RemminaPluginVncEvent *event;
+
+ CANCEL_DEFER;
+ pthread_mutex_lock(&gpdata->vnc_event_queue_mutex);
+
+ event = g_queue_pop_head(gpdata->vnc_event_queue);
+
+ pthread_mutex_unlock(&gpdata->vnc_event_queue_mutex);
+ CANCEL_ASYNC;
+
+ return event;
+}
+
+static void remmina_plugin_vnc_process_vnc_event(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncEvent *event;
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ rfbClient *cl;
+ gchar buf[100];
+
+ cl = (rfbClient*)gpdata->client;
+ while ((event = remmina_plugin_vnc_event_queue_pop_head(gpdata)) != NULL) {
+ if (cl) {
+ switch (event->event_type) {
+ case REMMINA_PLUGIN_VNC_EVENT_KEY:
+ SendKeyEvent(cl, event->event_data.key.keyval, event->event_data.key.pressed);
+ break;
+ case REMMINA_PLUGIN_VNC_EVENT_POINTER:
+ SendPointerEvent(cl, event->event_data.pointer.x, event->event_data.pointer.y,
+ event->event_data.pointer.button_mask);
+ break;
+ case REMMINA_PLUGIN_VNC_EVENT_CUTTEXT:
+ SendClientCutText(cl, event->event_data.text.text, strlen(event->event_data.text.text));
+ break;
+ case REMMINA_PLUGIN_VNC_EVENT_CHAT_OPEN:
+ TextChatOpen(cl);
+ break;
+ case REMMINA_PLUGIN_VNC_EVENT_CHAT_SEND:
+ TextChatSend(cl, event->event_data.text.text);
+ break;
+ case REMMINA_PLUGIN_VNC_EVENT_CHAT_CLOSE:
+ TextChatClose(cl);
+ TextChatFinish(cl);
+ break;
+ }
+ }
+ remmina_plugin_vnc_event_free(event);
+ }
+ if (read(gpdata->vnc_event_pipe[0], buf, sizeof(buf))) {
+ /* Ignore */
+ }
+}
+
+typedef struct _RemminaPluginVncCuttextParam {
+ RemminaProtocolWidget *gp;
+ gchar *text;
+ gint textlen;
+} RemminaPluginVncCuttextParam;
+
+static void remmina_plugin_vnc_update_quality(rfbClient *cl, gint quality)
+{
+ TRACE_CALL(__func__);
+ switch (quality) {
+ case 9:
+ cl->appData.useBGR233 = 0;
+ cl->appData.encodingsString = "copyrect hextile raw";
+ cl->appData.compressLevel = 0;
+ cl->appData.qualityLevel = 9;
+ break;
+ case 2:
+ cl->appData.useBGR233 = 0;
+ cl->appData.encodingsString = "tight zrle ultra copyrect hextile zlib corre rre raw";
+ cl->appData.compressLevel = 3;
+ cl->appData.qualityLevel = 7;
+ break;
+ case 1:
+ cl->appData.useBGR233 = 0;
+ cl->appData.encodingsString = "tight zrle ultra copyrect hextile zlib corre rre raw";
+ cl->appData.compressLevel = 5;
+ cl->appData.qualityLevel = 5;
+ break;
+ case 0:
+ default:
+ cl->appData.useBGR233 = 1;
+ cl->appData.encodingsString = "tight zrle ultra copyrect hextile zlib corre rre raw";
+ cl->appData.compressLevel = 9;
+ cl->appData.qualityLevel = 0;
+ break;
+ }
+}
+
+static void remmina_plugin_vnc_update_colordepth(rfbClient *cl, gint colordepth)
+{
+ TRACE_CALL(__func__);
+ cl->format.depth = colordepth;
+ cl->format.bigEndian = 0;
+ cl->appData.requestedDepth = colordepth;
+
+ switch (colordepth) {
+ case 8:
+ cl->format.depth = 8;
+ cl->format.bitsPerPixel = 8;
+ cl->format.blueMax = 3;
+ cl->format.blueShift = 6;
+ cl->format.greenMax = 7;
+ cl->format.greenShift = 3;
+ cl->format.redMax = 7;
+ cl->format.redShift = 0;
+ break;
+ case 16:
+ cl->format.depth = 16;
+ cl->format.bitsPerPixel = 16;
+ cl->format.blueMax = 0x1f;
+ cl->format.blueShift = 0;
+ cl->format.greenMax = 0x3f;
+ cl->format.greenShift = 5;
+ cl->format.redMax = 0x1f;
+ cl->format.redShift = 11;
+ break;
+ case 15:
+ cl->format.depth = 15;
+ cl->format.bitsPerPixel = 16;
+ cl->format.blueMax = 0x1f;
+ cl->format.blueShift = 0;
+ cl->format.greenMax = 0x1f;
+ cl->format.greenShift = 5;
+ cl->format.redMax = 0x1f;
+ cl->format.redShift = 10;
+ break;
+ case 24:
+ case 32:
+ default:
+ cl->format.depth = 24;
+ cl->format.bitsPerPixel = 32;
+ cl->format.blueShift = 0;
+ cl->format.redShift = 16;
+ cl->format.greenShift = 8;
+ cl->format.blueMax = 0xff;
+ cl->format.redMax = 0xff;
+ cl->format.greenMax = 0xff;
+ break;
+ }
+}
+
+static rfbBool remmina_plugin_vnc_rfb_allocfb(rfbClient *cl)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp = rfbClientGetClientData(cl, NULL);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ gint width, height, depth, size;
+ gboolean scale;
+ cairo_surface_t *new_surface, *old_surface;
+
+ width = cl->width;
+ height = cl->height;
+ depth = cl->format.bitsPerPixel;
+ size = width * height * (depth / 8);
+
+ new_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
+ if (cairo_surface_status(new_surface) != CAIRO_STATUS_SUCCESS)
+ return FALSE;
+ old_surface = gpdata->rgb_buffer;
+
+ LOCK_BUFFER(TRUE);
+
+ remmina_plugin_service->protocol_plugin_set_width(gp, width);
+ remmina_plugin_service->protocol_plugin_set_height(gp, height);
+
+ gpdata->rgb_buffer = new_surface;
+
+ if (gpdata->vnc_buffer)
+ g_free(gpdata->vnc_buffer);
+ gpdata->vnc_buffer = (guchar*)g_malloc(size);
+ cl->frameBuffer = gpdata->vnc_buffer;
+
+ UNLOCK_BUFFER(TRUE);
+
+ if (old_surface)
+ cairo_surface_destroy(old_surface);
+
+ scale = (remmina_plugin_service->remmina_protocol_widget_get_current_scale_mode(gp) != REMMINA_PROTOCOL_WIDGET_SCALE_MODE_NONE);
+ remmina_plugin_vnc_update_scale(gp, scale);
+
+ /* Notify window of change so that scroll border can be hidden or shown if needed */
+ remmina_plugin_service->protocol_plugin_emit_signal(gp, "desktop-resize");
+
+ /* Refresh the client's updateRect - bug in xvncclient */
+ cl->updateRect.w = width;
+ cl->updateRect.h = height;
+
+ return TRUE;
+}
+
+static gint remmina_plugin_vnc_bits(gint n)
+{
+ TRACE_CALL(__func__);
+ gint b = 0;
+ while (n) {
+ b++;
+ n >>= 1;
+ }
+ return b ? b : 1;
+}
+
+static gboolean remmina_plugin_vnc_queue_draw_area_real(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ gint x, y, w, h;
+
+ if (GTK_IS_WIDGET(gp) && gpdata->connected) {
+ LOCK_BUFFER(FALSE);
+ x = gpdata->queuedraw_x;
+ y = gpdata->queuedraw_y;
+ w = gpdata->queuedraw_w;
+ h = gpdata->queuedraw_h;
+ gpdata->queuedraw_handler = 0;
+ UNLOCK_BUFFER(FALSE);
+
+ gtk_widget_queue_draw_area(GTK_WIDGET(gp), x, y, w, h);
+ }
+ return FALSE;
+}
+
+static void remmina_plugin_vnc_queue_draw_area(RemminaProtocolWidget *gp, gint x, gint y, gint w, gint h)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ gint nx2, ny2, ox2, oy2;
+
+ LOCK_BUFFER(TRUE);
+ if (gpdata->queuedraw_handler) {
+ nx2 = x + w;
+ ny2 = y + h;
+ ox2 = gpdata->queuedraw_x + gpdata->queuedraw_w;
+ oy2 = gpdata->queuedraw_y + gpdata->queuedraw_h;
+ gpdata->queuedraw_x = MIN(gpdata->queuedraw_x, x);
+ gpdata->queuedraw_y = MIN(gpdata->queuedraw_y, y);
+ gpdata->queuedraw_w = MAX(ox2, nx2) - gpdata->queuedraw_x;
+ gpdata->queuedraw_h = MAX(oy2, ny2) - gpdata->queuedraw_y;
+ }else {
+ gpdata->queuedraw_x = x;
+ gpdata->queuedraw_y = y;
+ gpdata->queuedraw_w = w;
+ gpdata->queuedraw_h = h;
+ gpdata->queuedraw_handler = IDLE_ADD((GSourceFunc)remmina_plugin_vnc_queue_draw_area_real, gp);
+ }
+ UNLOCK_BUFFER(TRUE);
+}
+
+static void remmina_plugin_vnc_rfb_fill_buffer(rfbClient* cl, guchar *dest, gint dest_rowstride, guchar *src,
+ gint src_rowstride, guchar *mask, gint w, gint h)
+{
+ TRACE_CALL(__func__);
+ guchar *srcptr;
+ gint bytesPerPixel;
+ guint32 src_pixel;
+ gint ix, iy;
+ gint i;
+ guchar c;
+ gint rs, gs, bs, rm, gm, bm, rl, gl, bl, rr, gr, br;
+ gint r;
+ guint32 *destptr;
+ union {
+ struct {
+ guchar a, r, g, b;
+ } colors;
+ guint32 argb;
+ } dst_pixel;
+
+ bytesPerPixel = cl->format.bitsPerPixel / 8;
+ switch (cl->format.bitsPerPixel) {
+ case 32:
+ /* The following codes fill in the Alpha channel swap red/green value */
+ for (iy = 0; iy < h; iy++) {
+ destptr = (guint32*)(dest + iy * dest_rowstride);
+ srcptr = src + iy * src_rowstride;
+ for (ix = 0; ix < w; ix++) {
+ if (!mask || *mask++) {
+ dst_pixel.colors.a = 0xff;
+ dst_pixel.colors.r = *(srcptr + 2);
+ dst_pixel.colors.g = *(srcptr + 1);
+ dst_pixel.colors.b = *srcptr;
+ *destptr++ = ntohl(dst_pixel.argb);
+ }else{
+ *destptr++ = 0;
+ }
+ srcptr += 4;
+ }
+ }
+ break;
+ default:
+ rm = cl->format.redMax;
+ gm = cl->format.greenMax;
+ bm = cl->format.blueMax;
+ rr = remmina_plugin_vnc_bits(rm);
+ gr = remmina_plugin_vnc_bits(gm);
+ br = remmina_plugin_vnc_bits(bm);
+ rl = 8 - rr;
+ gl = 8 - gr;
+ bl = 8 - br;
+ rs = cl->format.redShift;
+ gs = cl->format.greenShift;
+ bs = cl->format.blueShift;
+ for (iy = 0; iy < h; iy++) {
+ destptr = (guint32*)(dest + iy * dest_rowstride);
+ srcptr = src + iy * src_rowstride;
+ for (ix = 0; ix < w; ix++) {
+ src_pixel = 0;
+ for (i = 0; i < bytesPerPixel; i++)
+ src_pixel += (*srcptr++) << (8 * i);
+
+ if (!mask || *mask++) {
+ dst_pixel.colors.a = 0xff;
+ c = (guchar)((src_pixel >> rs) & rm) << rl;
+ for (r = rr; r < 8; r *= 2)
+ c |= c >> r;
+ dst_pixel.colors.r = c;
+ c = (guchar)((src_pixel >> gs) & gm) << gl;
+ for (r = gr; r < 8; r *= 2)
+ c |= c >> r;
+ dst_pixel.colors.g = c;
+ c = (guchar)((src_pixel >> bs) & bm) << bl;
+ for (r = br; r < 8; r *= 2)
+ c |= c >> r;
+ dst_pixel.colors.b = c;
+ *destptr++ = ntohl(dst_pixel.argb);
+ }else{
+ *destptr++ = 0;
+ }
+ }
+ }
+ break;
+ }
+}
+
+static void remmina_plugin_vnc_rfb_updatefb(rfbClient* cl, int x, int y, int w, int h)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp = rfbClientGetClientData(cl, NULL);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ gint bytesPerPixel;
+ gint rowstride;
+ gint width;
+
+ LOCK_BUFFER(TRUE);
+
+ if (w >= 1 || h >= 1) {
+ width = remmina_plugin_service->protocol_plugin_get_width(gp);
+ bytesPerPixel = cl->format.bitsPerPixel / 8;
+ rowstride = cairo_image_surface_get_stride(gpdata->rgb_buffer);
+ cairo_surface_flush(gpdata->rgb_buffer);
+ remmina_plugin_vnc_rfb_fill_buffer(cl, cairo_image_surface_get_data(gpdata->rgb_buffer) + y * rowstride + x * 4,
+ rowstride, gpdata->vnc_buffer + ((y * width + x) * bytesPerPixel), width * bytesPerPixel, NULL,
+ w, h);
+ cairo_surface_mark_dirty(gpdata->rgb_buffer);
+ }
+
+ if ((remmina_plugin_service->remmina_protocol_widget_get_current_scale_mode(gp) != REMMINA_PROTOCOL_WIDGET_SCALE_MODE_NONE)) {
+ remmina_plugin_vnc_scale_area(gp, &x, &y, &w, &h);
+ }
+
+ UNLOCK_BUFFER(TRUE);
+
+ remmina_plugin_vnc_queue_draw_area(gp, x, y, w, h);
+}
+
+static gboolean remmina_plugin_vnc_queue_cuttext(RemminaPluginVncCuttextParam *param)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp = param->gp;
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ GTimeVal t;
+ glong diff;
+ const char *cur_charset;
+ gchar *text;
+ gsize br, bw;
+
+
+ if (GTK_IS_WIDGET(gp) && gpdata->connected) {
+ g_get_current_time(&t);
+ diff = (t.tv_sec - gpdata->clipboard_timer.tv_sec) * 10
+ + (t.tv_usec - gpdata->clipboard_timer.tv_usec) / 100000;
+ if (diff >= 10) {
+ gpdata->clipboard_timer = t;
+ /* Convert text from VNC latin-1 to current GTK charset (usually UTF-8) */
+ g_get_charset(&cur_charset);
+ text = g_convert_with_fallback(param->text, -1, cur_charset, "ISO-8859-1", "?", &br, &bw, NULL);
+ gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), text, bw);
+ g_free(text);
+ }
+ }
+ g_free(param->text);
+ g_free(param);
+ return FALSE;
+}
+
+static void remmina_plugin_vnc_rfb_cuttext(rfbClient* cl, const char *text, int textlen)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncCuttextParam *param;
+
+ param = g_new(RemminaPluginVncCuttextParam, 1);
+ param->gp = (RemminaProtocolWidget*)rfbClientGetClientData(cl, NULL);
+ param->text = g_malloc(textlen);
+ memcpy(param->text, text, textlen);
+ param->textlen = textlen;
+ IDLE_ADD((GSourceFunc)remmina_plugin_vnc_queue_cuttext, param);
+}
+
+static char*
+remmina_plugin_vnc_rfb_password(rfbClient *cl)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp = rfbClientGetClientData(cl, NULL);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile;
+ gint ret;
+ gchar *pwd = NULL;
+ gboolean disablepasswordstoring;
+
+ gpdata->auth_called = TRUE;
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ if (gpdata->auth_first) {
+ pwd = g_strdup(remmina_plugin_service->file_get_string(remminafile, "password"));
+ }
+ if (!pwd) {
+ disablepasswordstoring = remmina_plugin_service->file_get_int(remminafile, "disablepasswordstoring", FALSE);
+ ret = remmina_plugin_service->protocol_plugin_init_authpwd(gp, REMMINA_AUTHPWD_TYPE_PROTOCOL, !disablepasswordstoring);
+
+ if (ret == GTK_RESPONSE_OK) {
+ pwd = remmina_plugin_service->protocol_plugin_init_get_password(gp);
+ }else {
+ gpdata->connected = FALSE;
+ }
+ }
+ return pwd;
+}
+
+static rfbCredential*
+remmina_plugin_vnc_rfb_credential(rfbClient *cl, int credentialType)
+{
+ TRACE_CALL(__func__);
+ rfbCredential *cred;
+ RemminaProtocolWidget *gp = rfbClientGetClientData(cl, NULL);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile;
+ gint ret;
+ gchar *s1, *s2;
+ gboolean disablepasswordstoring;
+
+ gpdata->auth_called = TRUE;
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ cred = g_new0(rfbCredential, 1);
+
+ switch (credentialType) {
+
+ case rfbCredentialTypeUser:
+
+ s1 = g_strdup(remmina_plugin_service->file_get_string(remminafile, "username"));
+
+ s2 = g_strdup(remmina_plugin_service->file_get_string(remminafile, "password"));
+
+ if (gpdata->auth_first && s1 && s2) {
+ cred->userCredential.username = s1;
+ cred->userCredential.password = s2;
+ }else {
+ g_free(s1);
+ g_free(s2);
+
+ disablepasswordstoring = remmina_plugin_service->file_get_int(remminafile, "disablepasswordstoring", FALSE);
+ ret = remmina_plugin_service->protocol_plugin_init_authuserpwd(gp, FALSE, !disablepasswordstoring);
+
+
+ if (ret == GTK_RESPONSE_OK) {
+ cred->userCredential.username = remmina_plugin_service->protocol_plugin_init_get_username(gp);
+ cred->userCredential.password = remmina_plugin_service->protocol_plugin_init_get_password(gp);
+ }else {
+ g_free(cred);
+ cred = NULL;
+ gpdata->connected = FALSE;
+ }
+ }
+ break;
+
+ case rfbCredentialTypeX509:
+ if (gpdata->auth_first &&
+ remmina_plugin_service->file_get_string(remminafile, "cacert")) {
+ cred->x509Credential.x509CACertFile = g_strdup(remmina_plugin_service->file_get_string(remminafile, "cacert"));
+ cred->x509Credential.x509CACrlFile = g_strdup(remmina_plugin_service->file_get_string(remminafile, "cacrl"));
+ cred->x509Credential.x509ClientCertFile = g_strdup(remmina_plugin_service->file_get_string(remminafile, "clientcert"));
+ cred->x509Credential.x509ClientKeyFile = g_strdup(remmina_plugin_service->file_get_string(remminafile, "clientkey"));
+ }else {
+
+ ret = remmina_plugin_service->protocol_plugin_init_authx509(gp);
+
+ if (ret == GTK_RESPONSE_OK) {
+ cred->x509Credential.x509CACertFile = remmina_plugin_service->protocol_plugin_init_get_cacert(gp);
+ cred->x509Credential.x509CACrlFile = remmina_plugin_service->protocol_plugin_init_get_cacrl(gp);
+ cred->x509Credential.x509ClientCertFile = remmina_plugin_service->protocol_plugin_init_get_clientcert(gp);
+ cred->x509Credential.x509ClientKeyFile = remmina_plugin_service->protocol_plugin_init_get_clientkey(gp);
+ }else {
+ g_free(cred);
+ cred = NULL;
+ gpdata->connected = FALSE;
+ }
+ }
+ break;
+
+ default:
+ g_free(cred);
+ cred = NULL;
+ break;
+ }
+ return cred;
+}
+
+static void remmina_plugin_vnc_rfb_cursor_shape(rfbClient *cl, int xhot, int yhot, int width, int height, int bytesPerPixel)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp = rfbClientGetClientData(cl, NULL);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ int stride;
+ guchar *data;
+ cairo_surface_t *surface;
+
+ if (!gtk_widget_get_window(GTK_WIDGET(gp)))
+ return;
+
+ if (width && height) {
+ stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
+ data = g_malloc(stride * height);
+ remmina_plugin_vnc_rfb_fill_buffer(cl, data, stride, cl->rcSource,
+ width * cl->format.bitsPerPixel / 8, cl->rcMask, width, height);
+ surface = cairo_image_surface_create_for_data(data, CAIRO_FORMAT_ARGB32, width, height, stride);
+ if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) {
+ g_free(data);
+ return;
+ }
+ if (cairo_surface_set_user_data(surface, NULL, NULL, g_free) != CAIRO_STATUS_SUCCESS) {
+ g_free(data);
+ return;
+ }
+
+ LOCK_BUFFER(TRUE);
+ remmina_plugin_vnc_queuecursor(gp, surface, xhot, yhot);
+ UNLOCK_BUFFER(TRUE);
+ }
+}
+
+static void remmina_plugin_vnc_rfb_bell(rfbClient *cl)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp;
+ GdkWindow *window;
+
+ gp = (RemminaProtocolWidget*)(rfbClientGetClientData(cl, NULL));
+ window = gtk_widget_get_window(GTK_WIDGET(gp));
+
+ if (window)
+ gdk_window_beep(window);
+}
+
+/* Translate known VNC messages. It's for intltool only, not for gcc */
+#ifdef __DO_NOT_COMPILE_ME__
+N_("Unable to connect to VNC server")
+N_("Couldn't convert '%s' to host address")
+N_("VNC connection failed: %s")
+N_("Your connection has been rejected.")
+#endif
+/** @todo We only store the last message at this moment. */
+#define MAX_ERROR_LENGTH 1000
+static gchar vnc_error[MAX_ERROR_LENGTH + 1];
+static gboolean vnc_encryption_disable_requested;
+
+static void remmina_plugin_vnc_rfb_output(const char *format, ...)
+{
+ TRACE_CALL(__func__);
+ va_list args;
+ va_start(args, format);
+ gchar *f, *p, *ff;
+
+ /* eliminate the last \n */
+ f = g_strdup(format);
+ if (f[strlen(f) - 1] == '\n') f[strlen(f) - 1] = '\0';
+
+ if (g_strcmp0(f, "VNC connection failed: %s") == 0) {
+ p = va_arg(args, gchar*);
+ g_snprintf(vnc_error, MAX_ERROR_LENGTH, _(f), _(p));
+ }else if (g_strcmp0(f, "Unknown authentication scheme from VNC server: %s") == 0) {
+ p = va_arg(args, gchar*);
+ if (vnc_encryption_disable_requested) {
+ ff = g_strconcat(_("Unknown authentication scheme from VNC server: %s"),
+ ". ",
+ _("Please retry after enabling encryption on this profile."),
+ NULL);
+ g_snprintf(vnc_error, MAX_ERROR_LENGTH, ff, p);
+ g_free(ff);
+ }else
+ g_snprintf(vnc_error, MAX_ERROR_LENGTH, _(f), p);
+ }else {
+ g_vsnprintf(vnc_error, MAX_ERROR_LENGTH, _(f), args);
+ }
+ g_free(f);
+ va_end(args);
+
+ remmina_plugin_service->log_printf("[VNC]%s\n", vnc_error);
+}
+
+static void remmina_plugin_vnc_chat_on_send(RemminaProtocolWidget *gp, const gchar *text)
+{
+ TRACE_CALL(__func__);
+ gchar *ptr;
+
+ /* Need to add a line-feed for UltraVNC */
+ ptr = g_strdup_printf("%s\n", text);
+ remmina_plugin_vnc_event_push(gp, REMMINA_PLUGIN_VNC_EVENT_CHAT_SEND, ptr, NULL, NULL);
+ g_free(ptr);
+}
+
+static void remmina_plugin_vnc_chat_on_destroy(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ remmina_plugin_vnc_event_push(gp, REMMINA_PLUGIN_VNC_EVENT_CHAT_CLOSE, NULL, NULL, NULL);
+}
+
+/* Send CTRL+ALT+DEL keys keystrokes to the plugin drawing_area widget */
+static void remmina_plugin_vnc_send_ctrlaltdel(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ guint keys[] = { GDK_KEY_Control_L, GDK_KEY_Alt_L, GDK_KEY_Delete };
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+
+ remmina_plugin_service->protocol_plugin_send_keys_signals(gpdata->drawing_area,
+ keys, G_N_ELEMENTS(keys), GDK_KEY_PRESS | GDK_KEY_RELEASE);
+}
+
+static gboolean remmina_plugin_vnc_close_chat(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ remmina_plugin_service->protocol_plugin_chat_close(gp);
+ return FALSE;
+}
+
+static gboolean remmina_plugin_vnc_open_chat(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ rfbClient *cl;
+
+ cl = (rfbClient*)gpdata->client;
+
+ remmina_plugin_service->protocol_plugin_chat_open(gp, cl->desktopName, remmina_plugin_vnc_chat_on_send,
+ remmina_plugin_vnc_chat_on_destroy);
+ remmina_plugin_vnc_event_push(gp, REMMINA_PLUGIN_VNC_EVENT_CHAT_OPEN, NULL, NULL, NULL);
+ return FALSE;
+}
+
+static void remmina_plugin_vnc_rfb_chat(rfbClient* cl, int value, char *text)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp;
+
+ gp = (RemminaProtocolWidget*)(rfbClientGetClientData(cl, NULL));
+ switch (value) {
+ case rfbTextChatOpen:
+ IDLE_ADD((GSourceFunc)remmina_plugin_vnc_open_chat, gp);
+ break;
+ case rfbTextChatClose:
+ /* Do nothing... but wait for the next rfbTextChatFinished signal */
+ break;
+ case rfbTextChatFinished:
+ IDLE_ADD((GSourceFunc)remmina_plugin_vnc_close_chat, gp);
+ break;
+ default:
+ /* value is the text length */
+ remmina_plugin_service->protocol_plugin_chat_receive(gp, text);
+ break;
+ }
+}
+
+static gboolean remmina_plugin_vnc_incoming_connection(RemminaProtocolWidget *gp, rfbClient *cl)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ fd_set fds;
+
+ gpdata->listen_sock = ListenAtTcpPort(cl->listenPort);
+ if (gpdata->listen_sock < 0)
+ return FALSE;
+
+ remmina_plugin_service->protocol_plugin_init_show_listen(gp, cl->listenPort);
+
+ remmina_plugin_service->protocol_plugin_start_reverse_tunnel(gp, cl->listenPort);
+
+ FD_ZERO(&fds);
+ FD_SET(gpdata->listen_sock, &fds);
+ select(gpdata->listen_sock + 1, &fds, NULL, NULL, NULL);
+
+ if (!FD_ISSET(gpdata->listen_sock, &fds)) {
+ close(gpdata->listen_sock);
+ gpdata->listen_sock = -1;
+ return FALSE;
+ }
+
+ cl->sock = AcceptTcpConnection(gpdata->listen_sock);
+ close(gpdata->listen_sock);
+ gpdata->listen_sock = -1;
+ if (cl->sock < 0 || !SetNonBlocking(cl->sock)) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean remmina_plugin_vnc_main_loop(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ gint ret;
+ gint i;
+ rfbClient *cl;
+ fd_set fds;
+ struct timeval timeout;
+
+ if (!gpdata->connected) {
+ gpdata->running = FALSE;
+ return FALSE;
+ }
+
+ cl = (rfbClient*)gpdata->client;
+
+ timeout.tv_sec = 10;
+ timeout.tv_usec = 0;
+ FD_ZERO(&fds);
+ FD_SET(cl->sock, &fds);
+ FD_SET(gpdata->vnc_event_pipe[0], &fds);
+ ret = select(MAX(cl->sock, gpdata->vnc_event_pipe[0]) + 1, &fds, NULL, NULL, &timeout);
+
+ /* Sometimes it returns <0 when opening a modal dialog in other window. Absolutely weird */
+ /* So we continue looping anyway */
+ if (ret <= 0)
+ return TRUE;
+
+ if (FD_ISSET(gpdata->vnc_event_pipe[0], &fds)) {
+ remmina_plugin_vnc_process_vnc_event(gp);
+ }
+ if (FD_ISSET(cl->sock, &fds)) {
+ i = WaitForMessage(cl, 500);
+ if (i < 0)
+ return TRUE;
+ if (!HandleRFBServerMessage(cl)) {
+ gpdata->running = FALSE;
+ if (gpdata->connected && !remmina_plugin_service->protocol_plugin_is_closed(gp)) {
+ IDLE_ADD((GSourceFunc)remmina_plugin_service->protocol_plugin_close_connection, gp);
+ }
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean remmina_plugin_vnc_main(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile;
+ rfbClient *cl = NULL;
+ gchar *host;
+ gchar *s = NULL;
+
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ gpdata->running = TRUE;
+
+ rfbClientLog = remmina_plugin_vnc_rfb_output;
+ rfbClientErr = remmina_plugin_vnc_rfb_output;
+
+ while (gpdata->connected) {
+ gpdata->auth_called = FALSE;
+
+ host = remmina_plugin_service->protocol_plugin_start_direct_tunnel(gp, 5900, TRUE);
+
+ if (host == NULL) {
+ gpdata->connected = FALSE;
+ break;
+ }
+
+ cl = rfbGetClient(8, 3, 4);
+ cl->MallocFrameBuffer = remmina_plugin_vnc_rfb_allocfb;
+ cl->canHandleNewFBSize = TRUE;
+ cl->GetPassword = remmina_plugin_vnc_rfb_password;
+ cl->GetCredential = remmina_plugin_vnc_rfb_credential;
+ cl->GotFrameBufferUpdate = remmina_plugin_vnc_rfb_updatefb;
+ cl->GotXCutText = (
+ remmina_plugin_service->file_get_int(remminafile, "disableclipboard", FALSE) ?
+ NULL : remmina_plugin_vnc_rfb_cuttext);
+ cl->GotCursorShape = remmina_plugin_vnc_rfb_cursor_shape;
+ cl->Bell = remmina_plugin_vnc_rfb_bell;
+ cl->HandleTextChat = remmina_plugin_vnc_rfb_chat;
+ rfbClientSetClientData(cl, NULL, gp);
+
+ if (host[0] == '\0') {
+ cl->serverHost = strdup(host);
+ cl->listenSpecified = TRUE;
+ if (remmina_plugin_service->file_get_int(remminafile, "ssh_enabled", FALSE)) {
+ /* When we use reverse tunnel, the local port does not really matter.
+ * Hardcode a default port just in case the remote port is customized
+ * to a privilege port then we will have problem listening. */
+ cl->listenPort = 5500;
+ }else {
+ cl->listenPort = remmina_plugin_service->file_get_int(remminafile, "listenport", 5500);
+ }
+
+ remmina_plugin_vnc_incoming_connection(gp, cl);
+ }else {
+ remmina_plugin_service->get_server_port(host, 5900, &s, &cl->serverPort);
+ cl->serverHost = strdup(s);
+ g_free(s);
+
+ /* Support short-form (:0, :1) */
+ if (cl->serverPort < 100)
+ cl->serverPort += 5900;
+ }
+ g_free(host);
+ host = NULL;
+
+ if (remmina_plugin_service->file_get_string(remminafile, "proxy")) {
+ cl->destHost = cl->serverHost;
+ cl->destPort = cl->serverPort;
+ remmina_plugin_service->get_server_port(remmina_plugin_service->file_get_string(remminafile, "proxy"), 5900,
+ &s, &cl->serverPort);
+ cl->serverHost = strdup(s);
+ g_free(s);
+ }
+
+ cl->appData.useRemoteCursor = (
+ remmina_plugin_service->file_get_int(remminafile, "showcursor", FALSE) ? FALSE : TRUE);
+
+ remmina_plugin_vnc_update_quality(cl, remmina_plugin_service->file_get_int(remminafile, "quality", 0));
+ remmina_plugin_vnc_update_colordepth(cl, remmina_plugin_service->file_get_int(remminafile, "colordepth", 15));
+ SetFormatAndEncodings(cl);
+
+ if (remmina_plugin_service->file_get_int(remminafile, "disableencryption", FALSE)) {
+ vnc_encryption_disable_requested = TRUE;
+ SetClientAuthSchemes(cl, remmina_plugin_vnc_no_encrypt_auth_types, -1);
+ }else {
+ vnc_encryption_disable_requested = FALSE;
+ }
+
+ if (rfbInitClient(cl, NULL, NULL))
+ break;
+
+ /* If the authentication is not called, it has to be a fatel error and must quit */
+ if (!gpdata->auth_called) {
+ gpdata->connected = FALSE;
+ break;
+ }
+
+ /* vnc4server reports "already in use" after authentication. Workaround here */
+ if (strstr(vnc_error, "The server is already in use")) {
+ gpdata->connected = FALSE;
+ gpdata->auth_called = FALSE;
+ break;
+ }
+
+ /* Otherwise, it's a password error. Try to clear saved password if any */
+ remmina_plugin_service->file_set_string(remminafile, "password", NULL);
+
+ if (!gpdata->connected)
+ break;
+
+ remmina_plugin_service->protocol_plugin_init_show_retry(gp);
+
+ /* It's safer to sleep a while before reconnect */
+ sleep(2);
+
+ gpdata->auth_first = FALSE;
+ }
+
+ if (!gpdata->connected) {
+ if (cl && !gpdata->auth_called && !(remmina_plugin_service->protocol_plugin_has_error(gp))) {
+ remmina_plugin_service->protocol_plugin_set_error(gp, "%s", vnc_error);
+ }
+ gpdata->running = FALSE;
+
+ IDLE_ADD((GSourceFunc)remmina_plugin_service->protocol_plugin_close_connection, gp);
+
+ return FALSE;
+ }
+
+ remmina_plugin_service->protocol_plugin_init_save_cred(gp);
+
+ gpdata->client = cl;
+
+ remmina_plugin_service->protocol_plugin_emit_signal(gp, "connect");
+
+ if (remmina_plugin_service->file_get_int(remminafile, "disableserverinput", FALSE)) {
+ PermitServerInput(cl, 1);
+ }
+
+ if (gpdata->thread) {
+ while (remmina_plugin_vnc_main_loop(gp)) {
+ }
+ gpdata->running = FALSE;
+ }else {
+ IDLE_ADD((GSourceFunc)remmina_plugin_vnc_main_loop, gp);
+ }
+
+ return FALSE;
+}
+
+
+static gpointer
+remmina_plugin_vnc_main_thread(gpointer data)
+{
+ TRACE_CALL(__func__);
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+
+ CANCEL_ASYNC
+ remmina_plugin_vnc_main((RemminaProtocolWidget*)data);
+ return NULL;
+}
+
+
+static RemminaPluginVncCoordinates remmina_plugin_vnc_scale_coordinates(GtkWidget *widget, RemminaProtocolWidget *gp, gint x, gint y)
+{
+ GtkAllocation widget_allocation;
+ RemminaPluginVncCoordinates result;
+
+ if ((remmina_plugin_service->remmina_protocol_widget_get_current_scale_mode(gp) != REMMINA_PROTOCOL_WIDGET_SCALE_MODE_NONE)) {
+ gtk_widget_get_allocation(widget, &widget_allocation);
+ result.x = x * remmina_plugin_service->protocol_plugin_get_width(gp) / widget_allocation.width;
+ result.y = y * remmina_plugin_service->protocol_plugin_get_height(gp) / widget_allocation.height;
+ }else {
+ result.x = x;
+ result.y = y;
+ }
+
+ return result;
+}
+
+static gboolean remmina_plugin_vnc_on_motion(GtkWidget *widget, GdkEventMotion *event, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile;
+ RemminaPluginVncCoordinates coordinates;
+
+ if (!gpdata->connected || !gpdata->client)
+ return FALSE;
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ if (remmina_plugin_service->file_get_int(remminafile, "viewonly", FALSE))
+ return FALSE;
+
+ coordinates = remmina_plugin_vnc_scale_coordinates(widget, gp, event->x, event->y);
+ remmina_plugin_vnc_event_push(gp, REMMINA_PLUGIN_VNC_EVENT_POINTER, GINT_TO_POINTER(coordinates.x), GINT_TO_POINTER(coordinates.y),
+ GINT_TO_POINTER(gpdata->button_mask));
+ return TRUE;
+}
+
+static gboolean remmina_plugin_vnc_on_button(GtkWidget *widget, GdkEventButton *event, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile;
+ RemminaPluginVncCoordinates coordinates;
+ gint mask;
+
+ if (!gpdata->connected || !gpdata->client)
+ return FALSE;
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ if (remmina_plugin_service->file_get_int(remminafile, "viewonly", FALSE))
+ return FALSE;
+
+ /* We only accept 3 buttons */
+ if (event->button < 1 || event->button > 3)
+ return FALSE;
+ /* We bypass 2button-press and 3button-press events */
+ if (event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE)
+ return TRUE;
+
+ mask = (1 << (event->button - 1));
+ gpdata->button_mask = (event->type == GDK_BUTTON_PRESS ? (gpdata->button_mask | mask) :
+ (gpdata->button_mask & (0xff - mask)));
+
+ coordinates = remmina_plugin_vnc_scale_coordinates(widget, gp, event->x, event->y);
+ remmina_plugin_vnc_event_push(gp, REMMINA_PLUGIN_VNC_EVENT_POINTER, GINT_TO_POINTER(coordinates.x), GINT_TO_POINTER(coordinates.y),
+ GINT_TO_POINTER(gpdata->button_mask));
+ return TRUE;
+}
+
+static gboolean remmina_plugin_vnc_on_scroll(GtkWidget *widget, GdkEventScroll *event, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile;
+ RemminaPluginVncCoordinates coordinates;
+ gint mask;
+
+ if (!gpdata->connected || !gpdata->client)
+ return FALSE;
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ if (remmina_plugin_service->file_get_int(remminafile, "viewonly", FALSE))
+ return FALSE;
+
+ switch (event->direction) {
+ case GDK_SCROLL_UP:
+ mask = (1 << 3);
+ break;
+ case GDK_SCROLL_DOWN:
+ mask = (1 << 4);
+ break;
+ case GDK_SCROLL_LEFT:
+ mask = (1 << 5);
+ break;
+ case GDK_SCROLL_RIGHT:
+ mask = (1 << 6);
+ break;
+#ifdef GDK_SCROLL_SMOOTH
+ case GDK_SCROLL_SMOOTH:
+ if (event->delta_y < 0)
+ mask = (1 << 3);
+ if (event->delta_y > 0)
+ mask = (1 << 4);
+ if (event->delta_x < 0)
+ mask = (1 << 5);
+ if (event->delta_x > 0)
+ mask = (1 << 6);
+ if (!mask)
+ return FALSE;
+ break;
+#endif
+ default:
+ return FALSE;
+ }
+
+ coordinates = remmina_plugin_vnc_scale_coordinates(widget, gp, event->x, event->y);
+ remmina_plugin_vnc_event_push(gp, REMMINA_PLUGIN_VNC_EVENT_POINTER, GINT_TO_POINTER(coordinates.x), GINT_TO_POINTER(coordinates.y),
+ GINT_TO_POINTER(mask | gpdata->button_mask));
+ remmina_plugin_vnc_event_push(gp, REMMINA_PLUGIN_VNC_EVENT_POINTER, GINT_TO_POINTER(coordinates.x), GINT_TO_POINTER(coordinates.y),
+ GINT_TO_POINTER(gpdata->button_mask));
+
+ return TRUE;
+}
+
+static void remmina_plugin_vnc_release_key(RemminaProtocolWidget *gp, guint16 keycode)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaKeyVal *k;
+ gint i;
+
+ if (keycode == 0) {
+ /* Send all release key events for previously pressed keys */
+ for (i = 0; i < gpdata->pressed_keys->len; i++) {
+ k = g_ptr_array_index(gpdata->pressed_keys, i);
+ remmina_plugin_vnc_event_push(gp, REMMINA_PLUGIN_VNC_EVENT_KEY, GUINT_TO_POINTER(k->keyval),
+ GINT_TO_POINTER(FALSE), NULL);
+ g_free(k);
+ }
+ g_ptr_array_set_size(gpdata->pressed_keys, 0);
+ }else {
+ /* Unregister the keycode only */
+ for (i = 0; i < gpdata->pressed_keys->len; i++) {
+ k = g_ptr_array_index(gpdata->pressed_keys, i);
+ if (k->keycode == keycode) {
+ g_free(k);
+ g_ptr_array_remove_index_fast(gpdata->pressed_keys, i);
+ break;
+ }
+ }
+ }
+}
+
+static gboolean remmina_plugin_vnc_on_key(GtkWidget *widget, GdkEventKey *event, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile;
+ RemminaKeyVal *k;
+ guint event_keyval;
+ guint keyval;
+ int i;
+
+ if (!gpdata->connected || !gpdata->client)
+ return FALSE;
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ if (remmina_plugin_service->file_get_int(remminafile, "viewonly", FALSE))
+ return FALSE;
+
+ /* When sending key release, try first to find out a previously sent keyval
+ to workaround bugs like https://bugs.freedesktop.org/show_bug.cgi?id=7430 */
+
+ event_keyval = event->keyval;
+ if ( event->type == GDK_KEY_RELEASE ) {
+ for (i = 0; i < gpdata->pressed_keys->len; i++) {
+ k = g_ptr_array_index(gpdata->pressed_keys, i);
+ if ( k->keycode == event->hardware_keycode ) {
+ event_keyval = k->keyval;
+ break;
+ }
+ }
+ }
+
+ keyval = remmina_plugin_service->pref_keymap_get_keyval(remmina_plugin_service->file_get_string(remminafile, "keymap"),
+ event_keyval);
+
+ remmina_plugin_vnc_event_push(gp, REMMINA_PLUGIN_VNC_EVENT_KEY, GUINT_TO_POINTER(keyval),
+ GINT_TO_POINTER(event->type == GDK_KEY_PRESS ? TRUE : FALSE), NULL);
+
+ /* Register/unregister the pressed key */
+ if (event->type == GDK_KEY_PRESS) {
+ k = g_new(RemminaKeyVal, 1);
+ k->keyval = keyval;
+ k->keycode = event->hardware_keycode;
+ g_ptr_array_add(gpdata->pressed_keys, k);
+ }else {
+ remmina_plugin_vnc_release_key(gp, event->hardware_keycode);
+ }
+ return TRUE;
+}
+
+static void remmina_plugin_vnc_on_cuttext_request(GtkClipboard *clipboard, const gchar *text, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ GTimeVal t;
+ glong diff;
+ gsize br, bw;
+ gchar *latin1_text;
+ const char *cur_charset;
+
+ if (text) {
+ /* A timer (1 second) to avoid clipboard "loopback": text cut out from VNC won't paste back into VNC */
+ g_get_current_time(&t);
+ diff = (t.tv_sec - gpdata->clipboard_timer.tv_sec) * 10
+ + (t.tv_usec - gpdata->clipboard_timer.tv_usec) / 100000;
+ if (diff < 10)
+ return;
+
+ gpdata->clipboard_timer = t;
+ /* Convert text from current charset to latin-1 before sending to remote server.
+ * See RFC6143 7.5.6 */
+ g_get_charset(&cur_charset);
+ latin1_text = g_convert_with_fallback(text, -1, "ISO-8859-1", cur_charset, "?", &br, &bw, NULL);
+ remmina_plugin_vnc_event_push(gp, REMMINA_PLUGIN_VNC_EVENT_CUTTEXT, (gpointer)latin1_text, NULL, NULL);
+ g_free(latin1_text);
+ }
+}
+
+static void remmina_plugin_vnc_on_cuttext(GtkClipboard *clipboard, GdkEvent *event, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile;
+
+ if (!gpdata->connected || !gpdata->client)
+ return;
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ if (remmina_plugin_service->file_get_int(remminafile, "viewonly", FALSE))
+ return;
+
+ gtk_clipboard_request_text(clipboard, (GtkClipboardTextReceivedFunc)remmina_plugin_vnc_on_cuttext_request, gp);
+}
+
+static void remmina_plugin_vnc_on_realize(RemminaProtocolWidget *gp, gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaFile *remminafile;
+ GdkCursor *cursor;
+ GdkPixbuf *pixbuf;
+
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ if (remmina_plugin_service->file_get_int(remminafile, "showcursor", FALSE)) {
+ /* Hide local cursor (show a small dot instead) */
+ pixbuf = gdk_pixbuf_new_from_xpm_data(dot_cursor_xpm);
+ cursor = gdk_cursor_new_from_pixbuf(gdk_display_get_default(), pixbuf, dot_cursor_x_hot, dot_cursor_y_hot);
+ g_object_unref(pixbuf);
+ gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(gp)), cursor);
+ g_object_unref(cursor);
+ }
+}
+
+/******************************************************************************************/
+
+static gboolean remmina_plugin_vnc_open_connection(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile;
+
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ gpdata->connected = TRUE;
+
+ remmina_plugin_service->protocol_plugin_register_hostkey(gp, gpdata->drawing_area);
+
+ g_signal_connect(G_OBJECT(gp), "realize", G_CALLBACK(remmina_plugin_vnc_on_realize), NULL);
+ g_signal_connect(G_OBJECT(gpdata->drawing_area), "motion-notify-event", G_CALLBACK(remmina_plugin_vnc_on_motion), gp);
+ g_signal_connect(G_OBJECT(gpdata->drawing_area), "button-press-event", G_CALLBACK(remmina_plugin_vnc_on_button), gp);
+ g_signal_connect(G_OBJECT(gpdata->drawing_area), "button-release-event", G_CALLBACK(remmina_plugin_vnc_on_button), gp);
+ g_signal_connect(G_OBJECT(gpdata->drawing_area), "scroll-event", G_CALLBACK(remmina_plugin_vnc_on_scroll), gp);
+ g_signal_connect(G_OBJECT(gpdata->drawing_area), "key-press-event", G_CALLBACK(remmina_plugin_vnc_on_key), gp);
+ g_signal_connect(G_OBJECT(gpdata->drawing_area), "key-release-event", G_CALLBACK(remmina_plugin_vnc_on_key), gp);
+
+ if (!remmina_plugin_service->file_get_int(remminafile, "disableclipboard", FALSE)) {
+ gpdata->clipboard_handler = g_signal_connect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD)),
+ "owner-change", G_CALLBACK(remmina_plugin_vnc_on_cuttext), gp);
+ }
+
+
+ if (pthread_create(&gpdata->thread, NULL, remmina_plugin_vnc_main_thread, gp)) {
+ /* I don't think this will ever happen... */
+ g_print("Failed to initialize pthread. Falling back to non-thread mode...\n");
+ g_timeout_add(0, (GSourceFunc)remmina_plugin_vnc_main, gp);
+ gpdata->thread = 0;
+ }
+
+ return TRUE;
+}
+
+static gboolean remmina_plugin_vnc_close_connection_timeout(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+
+ /* wait until the running attribute is set to false by the VNC thread */
+ if (gpdata->running)
+ return TRUE;
+
+ /* unregister the clipboard monitor */
+ if (gpdata->clipboard_handler) {
+ g_signal_handler_disconnect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD)), gpdata->clipboard_handler);
+ gpdata->clipboard_handler = 0;
+ }
+
+ if (gpdata->queuecursor_handler) {
+ g_source_remove(gpdata->queuecursor_handler);
+ gpdata->queuecursor_handler = 0;
+ }
+ if (gpdata->queuecursor_surface) {
+ cairo_surface_destroy(gpdata->queuecursor_surface);
+ gpdata->queuecursor_surface = NULL;
+ }
+
+ if (gpdata->queuedraw_handler) {
+ g_source_remove(gpdata->queuedraw_handler);
+ gpdata->queuedraw_handler = 0;
+ }
+ if (gpdata->listen_sock >= 0) {
+ close(gpdata->listen_sock);
+ }
+ if (gpdata->client) {
+ rfbClientCleanup((rfbClient*)gpdata->client);
+ gpdata->client = NULL;
+ }
+ if (gpdata->rgb_buffer) {
+ cairo_surface_destroy(gpdata->rgb_buffer);
+ gpdata->rgb_buffer = NULL;
+ }
+ if (gpdata->vnc_buffer) {
+ g_free(gpdata->vnc_buffer);
+ gpdata->vnc_buffer = NULL;
+ }
+ g_ptr_array_free(gpdata->pressed_keys, TRUE);
+ remmina_plugin_vnc_event_free_all(gp);
+ g_queue_free(gpdata->vnc_event_queue);
+ pthread_mutex_destroy(&gpdata->vnc_event_queue_mutex);
+ close(gpdata->vnc_event_pipe[0]);
+ close(gpdata->vnc_event_pipe[1]);
+
+
+ pthread_mutex_destroy(&gpdata->buffer_mutex);
+
+
+ remmina_plugin_service->protocol_plugin_emit_signal(gp, "disconnect");
+
+ return FALSE;
+}
+
+static gboolean remmina_plugin_vnc_close_connection(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+
+ gpdata->connected = FALSE;
+
+ if (gpdata->thread) {
+ pthread_cancel(gpdata->thread);
+ if (gpdata->thread) pthread_join(gpdata->thread, NULL);
+ gpdata->running = FALSE;
+ remmina_plugin_vnc_close_connection_timeout(gp);
+ }else {
+ g_timeout_add(200, (GSourceFunc)remmina_plugin_vnc_close_connection_timeout, gp);
+ }
+
+ return FALSE;
+}
+
+static gboolean remmina_plugin_vnc_query_feature(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+
+ switch (feature->id) {
+ case REMMINA_PLUGIN_VNC_FEATURE_PREF_DISABLESERVERINPUT:
+ return (SupportsClient2Server((rfbClient*)(gpdata->client), rfbSetServerInput) ? TRUE : FALSE);
+ case REMMINA_PLUGIN_VNC_FEATURE_TOOL_CHAT:
+ return (SupportsClient2Server((rfbClient*)(gpdata->client), rfbTextChat) ? TRUE : FALSE);
+ default:
+ return TRUE;
+ }
+}
+
+static void remmina_plugin_vnc_call_feature(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile;
+
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ switch (feature->id) {
+ case REMMINA_PLUGIN_VNC_FEATURE_PREF_QUALITY:
+ remmina_plugin_vnc_update_quality((rfbClient*)(gpdata->client),
+ remmina_plugin_service->file_get_int(remminafile, "quality", 0));
+ remmina_plugin_vnc_update_colordepth((rfbClient*)(gpdata->client),
+ remmina_plugin_service->file_get_int(remminafile, "colordepth", 15));
+ SetFormatAndEncodings((rfbClient*)(gpdata->client));
+ break;
+ case REMMINA_PLUGIN_VNC_FEATURE_PREF_VIEWONLY:
+ break;
+ case REMMINA_PLUGIN_VNC_FEATURE_PREF_DISABLESERVERINPUT:
+ PermitServerInput((rfbClient*)(gpdata->client),
+ remmina_plugin_service->file_get_int(remminafile, "disableserverinput", FALSE) ? 1 : 0);
+ break;
+ case REMMINA_PLUGIN_VNC_FEATURE_UNFOCUS:
+ remmina_plugin_vnc_release_key(gp, 0);
+ break;
+ case REMMINA_PLUGIN_VNC_FEATURE_SCALE:
+ remmina_plugin_vnc_update_scale(gp, remmina_plugin_service->file_get_int(remminafile, "scale", FALSE));
+ break;
+ case REMMINA_PLUGIN_VNC_FEATURE_TOOL_REFRESH:
+ SendFramebufferUpdateRequest((rfbClient*)(gpdata->client), 0, 0,
+ remmina_plugin_service->protocol_plugin_get_width(gp),
+ remmina_plugin_service->protocol_plugin_get_height(gp), FALSE);
+ break;
+ case REMMINA_PLUGIN_VNC_FEATURE_TOOL_CHAT:
+ remmina_plugin_vnc_open_chat(gp);
+ break;
+ case REMMINA_PLUGIN_VNC_FEATURE_TOOL_SENDCTRLALTDEL:
+ remmina_plugin_vnc_send_ctrlaltdel(gp);
+ break;
+ default:
+ break;
+ }
+}
+
+/* Send a keystroke to the plugin window */
+static void remmina_plugin_vnc_keystroke(RemminaProtocolWidget *gp, const guint keystrokes[], const gint keylen)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ remmina_plugin_service->protocol_plugin_send_keys_signals(gpdata->drawing_area,
+ keystrokes, keylen, GDK_KEY_PRESS | GDK_KEY_RELEASE);
+ return;
+}
+
+static gboolean remmina_plugin_vnc_on_draw(GtkWidget *widget, cairo_t *context, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata = GET_PLUGIN_DATA(gp);
+ cairo_surface_t *surface;
+ gint width, height;
+ GtkAllocation widget_allocation;
+
+ LOCK_BUFFER(FALSE);
+
+ surface = gpdata->rgb_buffer;
+ if (!surface) {
+ UNLOCK_BUFFER(FALSE);
+ return FALSE;
+ }
+
+ width = remmina_plugin_service->protocol_plugin_get_width(gp);
+ height = remmina_plugin_service->protocol_plugin_get_height(gp);
+
+ if ((remmina_plugin_service->remmina_protocol_widget_get_current_scale_mode(gp) != REMMINA_PROTOCOL_WIDGET_SCALE_MODE_NONE)) {
+ gtk_widget_get_allocation(widget, &widget_allocation);
+ cairo_scale(context,
+ (double)widget_allocation.width / width,
+ (double)widget_allocation.height / height);
+ }
+
+ cairo_rectangle(context, 0, 0, width, height);
+ cairo_set_source_surface(context, surface, 0, 0);
+ cairo_fill(context);
+
+ UNLOCK_BUFFER(FALSE);
+ return TRUE;
+}
+
+static void remmina_plugin_vnc_init(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginVncData *gpdata;
+ gint flags;
+
+ gpdata = g_new0(RemminaPluginVncData, 1);
+ g_object_set_data_full(G_OBJECT(gp), "plugin-data", gpdata, g_free);
+
+ gpdata->drawing_area = gtk_drawing_area_new();
+ gtk_widget_show(gpdata->drawing_area);
+ gtk_container_add(GTK_CONTAINER(gp), gpdata->drawing_area);
+
+ gtk_widget_add_events(
+ gpdata->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);
+ gtk_widget_set_can_focus(gpdata->drawing_area, TRUE);
+
+
+ g_signal_connect(G_OBJECT(gpdata->drawing_area), "draw", G_CALLBACK(remmina_plugin_vnc_on_draw), gp);
+
+ gpdata->auth_first = TRUE;
+ g_get_current_time(&gpdata->clipboard_timer);
+ gpdata->listen_sock = -1;
+ gpdata->pressed_keys = g_ptr_array_new();
+ gpdata->vnc_event_queue = g_queue_new();
+ pthread_mutex_init(&gpdata->vnc_event_queue_mutex, NULL);
+ if (pipe(gpdata->vnc_event_pipe)) {
+ g_print("Error creating pipes.\n");
+ gpdata->vnc_event_pipe[0] = 0;
+ gpdata->vnc_event_pipe[1] = 0;
+ }
+ flags = fcntl(gpdata->vnc_event_pipe[0], F_GETFL, 0);
+ fcntl(gpdata->vnc_event_pipe[0], F_SETFL, flags | O_NONBLOCK);
+
+ pthread_mutex_init(&gpdata->buffer_mutex, NULL);
+
+}
+
+/* Array of key/value pairs for color depths */
+static gpointer colordepth_list[] =
+{
+ "8", N_("256 colors (8 bpp)"),
+ "15", N_("High color (15 bpp)"),
+ "16", N_("High color (16 bpp)"),
+ "24", N_("True color (24 bpp)"),
+ "32", N_("True color (32 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 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_plugin_vnc_basic_settings[] =
+{
+ { REMMINA_PROTOCOL_SETTING_TYPE_SERVER, "server", NULL, FALSE, "_rfb._tcp", NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "proxy", N_("Repeater"), 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_SELECT, "colordepth", N_("Color depth"), FALSE, colordepth_list, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "quality", N_("Quality"), FALSE, quality_list, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_KEYMAP, NULL, NULL, FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_END, NULL, NULL, FALSE, NULL, 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_plugin_vnci_basic_settings[] =
+{
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "listenport", N_("Listen on port"), 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_SELECT, "colordepth", N_("Color depth"), FALSE, colordepth_list, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "quality", N_("Quality"), FALSE, quality_list, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_KEYMAP, NULL, NULL, 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_plugin_vnc_advanced_settings[] =
+{
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "showcursor", N_("Show remote cursor"), TRUE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "viewonly", N_("View only"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableclipboard", N_("Disable clipboard sync"), TRUE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableencryption", N_("Disable encryption"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableserverinput", N_("Disable server input"), TRUE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disablepasswordstoring", N_("Disable password storing"), 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_plugin_vnc_features[] =
+{
+ { REMMINA_PROTOCOL_FEATURE_TYPE_PREF, REMMINA_PLUGIN_VNC_FEATURE_PREF_QUALITY, GINT_TO_POINTER(REMMINA_PROTOCOL_FEATURE_PREF_RADIO), "quality",
+ quality_list },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_PREF, REMMINA_PLUGIN_VNC_FEATURE_PREF_VIEWONLY, GINT_TO_POINTER(REMMINA_PROTOCOL_FEATURE_PREF_CHECK), "viewonly",
+ N_("View only") },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_PREF, REMMINA_PLUGIN_VNC_FEATURE_PREF_DISABLESERVERINPUT, GINT_TO_POINTER(REMMINA_PROTOCOL_FEATURE_PREF_CHECK), "disableserverinput", N_("Disable server input") },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_PLUGIN_VNC_FEATURE_TOOL_REFRESH, N_("Refresh"), NULL, NULL },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_PLUGIN_VNC_FEATURE_TOOL_CHAT, N_("Open Chat..."), "face-smile", NULL },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_PLUGIN_VNC_FEATURE_TOOL_SENDCTRLALTDEL, N_("Send Ctrl+Alt+Delete"), NULL, NULL },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_SCALE, REMMINA_PLUGIN_VNC_FEATURE_SCALE, NULL, NULL, NULL },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_UNFOCUS, REMMINA_PLUGIN_VNC_FEATURE_UNFOCUS, NULL, NULL, NULL },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_END, 0, NULL, NULL, NULL }
+};
+
+/* Protocol plugin definition and features */
+static RemminaProtocolPlugin remmina_plugin_vnc =
+{
+ REMMINA_PLUGIN_TYPE_PROTOCOL, // Type
+ "VNC", // Name
+ N_("VNC - Virtual Network Computing"), // Description
+ GETTEXT_PACKAGE, // Translation domain
+ VERSION, // Version number
+ "remmina-vnc", // Icon for normal connection
+ "remmina-vnc-ssh", // Icon for SSH connection
+ remmina_plugin_vnc_basic_settings, // Array for basic settings
+ remmina_plugin_vnc_advanced_settings, // Array for advanced settings
+ REMMINA_PROTOCOL_SSH_SETTING_TUNNEL, // SSH settings type
+ remmina_plugin_vnc_features, // Array for available features
+ remmina_plugin_vnc_init, // Plugin initialization
+ remmina_plugin_vnc_open_connection, // Plugin open connection
+ remmina_plugin_vnc_close_connection, // Plugin close connection
+ remmina_plugin_vnc_query_feature, // Query for available features
+ remmina_plugin_vnc_call_feature, // Call a feature
+ remmina_plugin_vnc_keystroke // Send a keystroke
+};
+
+/* Protocol plugin definition and features */
+static RemminaProtocolPlugin remmina_plugin_vnci =
+{
+ REMMINA_PLUGIN_TYPE_PROTOCOL, // Type
+ "VNCI", // Name
+ N_("VNC - Incoming Connection"), // Description
+ GETTEXT_PACKAGE, // Translation domain
+ VERSION, // Version number
+ "remmina-vnc", // Icon for normal connection
+ "remmina-vnc-ssh", // Icon for SSH connection
+ remmina_plugin_vnci_basic_settings, // Array for basic settings
+ remmina_plugin_vnc_advanced_settings, // Array for advanced settings
+ REMMINA_PROTOCOL_SSH_SETTING_REVERSE_TUNNEL, // SSH settings type
+ remmina_plugin_vnc_features, // Array for available features
+ remmina_plugin_vnc_init, // Plugin initialization
+ remmina_plugin_vnc_open_connection, // Plugin open connection
+ remmina_plugin_vnc_close_connection, // Plugin close connection
+ remmina_plugin_vnc_query_feature, // Query for available features
+ remmina_plugin_vnc_call_feature, // Call a feature
+ remmina_plugin_vnc_keystroke, // Send a keystroke
+ NULL // No screenshot support available
+};
+
+G_MODULE_EXPORT gboolean
+remmina_plugin_entry(RemminaPluginService *service)
+{
+ TRACE_CALL(__func__);
+ remmina_plugin_service = service;
+
+ bindtextdomain(GETTEXT_PACKAGE, REMMINA_RUNTIME_LOCALEDIR);
+ bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
+
+ if (!service->register_plugin((RemminaPlugin*)&remmina_plugin_vnc)) {
+ return FALSE;
+ }
+
+ if (!service->register_plugin((RemminaPlugin*)&remmina_plugin_vnci)) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
diff --git a/plugins/xdmcp/16x16/emblems/remmina-xdmcp-ssh.png b/plugins/xdmcp/16x16/emblems/remmina-xdmcp-ssh.png
new file mode 100644
index 000000000..5493ba3bc
--- /dev/null
+++ b/plugins/xdmcp/16x16/emblems/remmina-xdmcp-ssh.png
Binary files differ
diff --git a/plugins/xdmcp/16x16/emblems/remmina-xdmcp.png b/plugins/xdmcp/16x16/emblems/remmina-xdmcp.png
new file mode 100644
index 000000000..2367a4e6e
--- /dev/null
+++ b/plugins/xdmcp/16x16/emblems/remmina-xdmcp.png
Binary files differ
diff --git a/plugins/xdmcp/22x22/emblems/remmina-xdmcp-ssh.png b/plugins/xdmcp/22x22/emblems/remmina-xdmcp-ssh.png
new file mode 100644
index 000000000..f69563357
--- /dev/null
+++ b/plugins/xdmcp/22x22/emblems/remmina-xdmcp-ssh.png
Binary files differ
diff --git a/plugins/xdmcp/22x22/emblems/remmina-xdmcp.png b/plugins/xdmcp/22x22/emblems/remmina-xdmcp.png
new file mode 100644
index 000000000..83ffc6edb
--- /dev/null
+++ b/plugins/xdmcp/22x22/emblems/remmina-xdmcp.png
Binary files differ
diff --git a/plugins/xdmcp/CMakeLists.txt b/plugins/xdmcp/CMakeLists.txt
new file mode 100644
index 000000000..159c58a55
--- /dev/null
+++ b/plugins/xdmcp/CMakeLists.txt
@@ -0,0 +1,53 @@
+# remmina-plugin-xdmcp - The GTK+ Remote Desktop Client
+#
+# Copyright (C) 2011 Marc-Andre Moreau
+# Copyright (C) 2014-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(REMMINA_PLUGIN_XDMCP_SRCS xdmcp_plugin.c)
+
+add_library(remmina-plugin-xdmcp MODULE ${REMMINA_PLUGIN_XDMCP_SRCS})
+set_target_properties(remmina-plugin-xdmcp PROPERTIES PREFIX "")
+set_target_properties(remmina-plugin-xdmcp PROPERTIES NO_SONAME 1)
+
+include_directories(${REMMINA_COMMON_INCLUDE_DIRS})
+target_link_libraries(remmina-plugin-xdmcp ${REMMINA_COMMON_LIBRARIES})
+
+install(TARGETS remmina-plugin-xdmcp DESTINATION ${REMMINA_PLUGINDIR})
+
+install(FILES
+ 16x16/emblems/remmina-xdmcp-ssh.png
+ 16x16/emblems/remmina-xdmcp.png
+ DESTINATION ${APPICON16_EMBLEMS_DIR})
+install(FILES
+ 22x22/emblems/remmina-xdmcp-ssh.png
+ 22x22/emblems/remmina-xdmcp.png
+ DESTINATION ${APPICON22_EMBLEMS_DIR})
diff --git a/plugins/xdmcp/xdmcp_plugin.c b/plugins/xdmcp/xdmcp_plugin.c
new file mode 100644
index 000000000..97de902a5
--- /dev/null
+++ b/plugins/xdmcp/xdmcp_plugin.c
@@ -0,0 +1,426 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010 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"
+#if GTK_VERSION == 3
+# include <gtk/gtkx.h>
+#endif
+
+INCLUDE_GET_AVAILABLE_XDISPLAY
+
+#define REMMINA_PLUGIN_XDMCP_FEATURE_TOOL_SENDCTRLALTDEL 1
+
+#define GET_PLUGIN_DATA(gp) (RemminaPluginXdmcpData*)g_object_get_data(G_OBJECT(gp), "plugin-data");
+
+/* Forward declaration */
+static RemminaProtocolPlugin remmina_plugin_xdmcp;
+
+typedef struct _RemminaPluginXdmcpData {
+ GtkWidget *socket;
+ gint socket_id;
+ GPid pid;
+ gint output_fd;
+ gint error_fd;
+ gint display;
+ gboolean ready;
+
+ pthread_t thread;
+
+} RemminaPluginXdmcpData;
+
+static RemminaPluginService *remmina_plugin_service = NULL;
+
+
+static void remmina_plugin_xdmcp_on_plug_added(GtkSocket *socket, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginXdmcpData *gpdata = GET_PLUGIN_DATA(gp);
+
+ remmina_plugin_service->protocol_plugin_emit_signal(gp, "connect");
+ gpdata->ready = TRUE;
+}
+
+static void remmina_plugin_xdmcp_on_plug_removed(GtkSocket *socket, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ remmina_plugin_service->protocol_plugin_close_connection(gp);
+}
+
+static gboolean remmina_plugin_xdmcp_start_xephyr(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginXdmcpData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile;
+ gchar *argv[50];
+ gint argc;
+ gchar *host;
+ gint i;
+ GError *error = NULL;
+ gboolean ret;
+
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ gpdata->display = remmina_get_available_xdisplay();
+ if (gpdata->display == 0) {
+ remmina_plugin_service->protocol_plugin_set_error(gp, _("Run out of available local X display number."));
+ return FALSE;
+ }
+
+ argc = 0;
+ argv[argc++] = g_strdup("Xephyr");
+
+ argv[argc++] = g_strdup_printf(":%i", gpdata->display);
+
+ argv[argc++] = g_strdup("-parent");
+ argv[argc++] = g_strdup_printf("%i", gpdata->socket_id);
+
+ /* All Xephyr version between 1.5.0 and 1.6.4 will break when -screen argument is specified with -parent.
+ * It's not possible to support color depth if you have those Xephyr version. Please see this bug
+ * http://bugs.freedesktop.org/show_bug.cgi?id=24144
+ * As a workaround, a "Default" color depth will not add the -screen argument.
+ */
+ i = remmina_plugin_service->file_get_int(remminafile, "colordepth", 8);
+ if (i >= 8) {
+ argv[argc++] = g_strdup("-screen");
+ argv[argc++] = g_strdup_printf("%ix%ix%i",
+ remmina_plugin_service->get_profile_remote_width(gp),
+ remmina_plugin_service->get_profile_remote_height(gp), i);
+ }
+
+ if (i == 2) {
+ argv[argc++] = g_strdup("-grayscale");
+ }
+
+ if (remmina_plugin_service->file_get_int(remminafile, "showcursor", FALSE)) {
+ argv[argc++] = g_strdup("-host-cursor");
+ }
+ if (remmina_plugin_service->file_get_int(remminafile, "once", FALSE)) {
+ argv[argc++] = g_strdup("-once");
+ }
+ /* Listen on protocol TCP */
+ if (remmina_plugin_service->file_get_int(remminafile, "listen_on_tcp", FALSE)) {
+ argv[argc++] = g_strdup("-listen");
+ argv[argc++] = g_strdup("tcp");
+ }
+
+ if (!remmina_plugin_service->file_get_int(remminafile, "ssh_enabled", FALSE)) {
+ remmina_plugin_service->get_server_port(remmina_plugin_service->file_get_string(remminafile, "server"), 0,
+ &host, &i);
+
+ argv[argc++] = g_strdup("-query");
+ argv[argc++] = host;
+
+ if (i) {
+ argv[argc++] = g_strdup("-port");
+ argv[argc++] = g_strdup_printf("%i", i);
+ }
+ }else {
+ /* When the connection is through an SSH tunnel, it connects back to local unix socket,
+ * so for security we can disable tcp listening */
+ argv[argc++] = g_strdup("-nolisten");
+ argv[argc++] = g_strdup("tcp");
+
+ /* FIXME: It's better to get the magic cookie back from xqproxy, then call xauth,
+ * instead of disable access control */
+ argv[argc++] = g_strdup("-ac");
+ }
+
+ argv[argc++] = NULL;
+
+ ret = g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, &gpdata->pid, &error);
+ for (i = 0; i < argc; i++)
+ g_free(argv[i]);
+
+ if (!ret) {
+ remmina_plugin_service->protocol_plugin_set_error(gp, "%s", error->message);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean remmina_plugin_xdmcp_tunnel_init_callback(RemminaProtocolWidget *gp, gint remotedisplay, const gchar *server,
+ gint port)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginXdmcpData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile;
+
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ if (!remmina_plugin_xdmcp_start_xephyr(gp))
+ return FALSE;
+ while (!gpdata->ready)
+ sleep(1);
+
+ remmina_plugin_service->protocol_plugin_set_display(gp, gpdata->display);
+
+ if (remmina_plugin_service->file_get_string(remminafile, "exec")) {
+ return remmina_plugin_service->protocol_plugin_ssh_exec(gp, FALSE, "DISPLAY=localhost:%i.0 %s", remotedisplay,
+ remmina_plugin_service->file_get_string(remminafile, "exec"));
+ }else {
+ return remmina_plugin_service->protocol_plugin_ssh_exec(gp, TRUE,
+ "xqproxy -display %i -host %s -port %i -query -manage", remotedisplay, server, port);
+ }
+}
+
+static gboolean remmina_plugin_xdmcp_main(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginXdmcpData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile;
+
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ if (remmina_plugin_service->file_get_int(remminafile, "ssh_enabled", FALSE)) {
+ if (!remmina_plugin_service->protocol_plugin_start_xport_tunnel(gp, remmina_plugin_xdmcp_tunnel_init_callback)) {
+ gpdata->thread = 0;
+ return FALSE;
+ }
+ }else {
+ if (!remmina_plugin_xdmcp_start_xephyr(gp)) {
+ gpdata->thread = 0;
+ return FALSE;
+ }
+ }
+
+ gpdata->thread = 0;
+ return TRUE;
+}
+
+static gpointer
+remmina_plugin_xdmcp_main_thread(gpointer data)
+{
+ TRACE_CALL(__func__);
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+
+ CANCEL_ASYNC
+ if (!remmina_plugin_xdmcp_main((RemminaProtocolWidget*)data)) {
+ IDLE_ADD((GSourceFunc)remmina_plugin_service->protocol_plugin_close_connection, data);
+ }
+ return NULL;
+}
+
+static void remmina_plugin_xdmcp_init(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginXdmcpData *gpdata;
+
+ gpdata = g_new0(RemminaPluginXdmcpData, 1);
+ g_object_set_data_full(G_OBJECT(gp), "plugin-data", gpdata, g_free);
+
+ gpdata->socket = gtk_socket_new();
+ remmina_plugin_service->protocol_plugin_register_hostkey(gp, gpdata->socket);
+ gtk_widget_show(gpdata->socket);
+ g_signal_connect(G_OBJECT(gpdata->socket), "plug-added", G_CALLBACK(remmina_plugin_xdmcp_on_plug_added), gp);
+ g_signal_connect(G_OBJECT(gpdata->socket), "plug-removed", G_CALLBACK(remmina_plugin_xdmcp_on_plug_removed), gp);
+ gtk_container_add(GTK_CONTAINER(gp), gpdata->socket);
+
+}
+
+
+
+static gboolean remmina_plugin_xdmcp_open_connection(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginXdmcpData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile;
+ gint width, height;
+
+ if (!remmina_plugin_service->gtksocket_available()) {
+ remmina_plugin_service->protocol_plugin_set_error(gp,
+ _("Protocol %s is unavailable because GtkSocket only works under Xorg"),
+ remmina_plugin_xdmcp.name);
+ return FALSE;
+ }
+
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ width = remmina_plugin_service->get_profile_remote_width(gp);
+ height = remmina_plugin_service->get_profile_remote_height(gp);
+ remmina_plugin_service->protocol_plugin_set_width(gp, width);
+ remmina_plugin_service->protocol_plugin_set_height(gp, height);
+ gtk_widget_set_size_request(GTK_WIDGET(gp), width, height);
+ gpdata->socket_id = gtk_socket_get_id(GTK_SOCKET(gpdata->socket));
+
+
+ if (remmina_plugin_service->file_get_int(remminafile, "ssh_enabled", FALSE)) {
+ if (pthread_create(&gpdata->thread, NULL, remmina_plugin_xdmcp_main_thread, gp)) {
+ remmina_plugin_service->protocol_plugin_set_error(gp,
+ "Failed to initialize pthread. Falling back to non-thread mode...");
+ gpdata->thread = 0;
+ return FALSE;
+ }else {
+ return TRUE;
+ }
+ }else {
+ return remmina_plugin_xdmcp_main(gp);
+ }
+
+}
+
+static gboolean remmina_plugin_xdmcp_close_connection(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginXdmcpData *gpdata = GET_PLUGIN_DATA(gp);
+
+ if (gpdata->thread) {
+ pthread_cancel(gpdata->thread);
+ if (gpdata->thread) pthread_join(gpdata->thread, NULL);
+ }
+
+ if (gpdata->pid) {
+ kill(gpdata->pid, SIGTERM);
+ g_spawn_close_pid(gpdata->pid);
+ gpdata->pid = 0;
+ }
+
+ remmina_plugin_service->protocol_plugin_emit_signal(gp, "disconnect");
+
+ return FALSE;
+}
+
+/* Send CTRL+ALT+DEL keys keystrokes to the plugin socket widget */
+static void remmina_plugin_xdmcp_send_ctrlaltdel(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ guint keys[] = { GDK_KEY_Control_L, GDK_KEY_Alt_L, GDK_KEY_Delete };
+ RemminaPluginXdmcpData *gpdata = GET_PLUGIN_DATA(gp);
+
+ remmina_plugin_service->protocol_plugin_send_keys_signals(gpdata->socket,
+ keys, G_N_ELEMENTS(keys), GDK_KEY_PRESS | GDK_KEY_RELEASE);
+}
+
+static gboolean remmina_plugin_xdmcp_query_feature(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
+{
+ TRACE_CALL(__func__);
+ return TRUE;
+}
+
+static void remmina_plugin_xdmcp_call_feature(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
+{
+ TRACE_CALL(__func__);
+ RemminaFile *remminafile;
+
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ switch (feature->id) {
+ case REMMINA_PLUGIN_XDMCP_FEATURE_TOOL_SENDCTRLALTDEL:
+ remmina_plugin_xdmcp_send_ctrlaltdel(gp);
+ break;
+ default:
+ break;
+ }
+}
+
+/* Array of key/value pairs for color depths */
+static gpointer colordepth_list[] =
+{
+ "0", N_("Default"),
+ "2", N_("Grayscale"),
+ "8", N_("256 colors"),
+ "16", N_("High color (16 bit)"),
+ "24", N_("True color (24 bit)"),
+ 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_plugin_xdmcp_basic_settings[] =
+{
+ { REMMINA_PROTOCOL_SETTING_TYPE_SERVER, NULL, NULL, FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_RESOLUTION, NULL, NULL, FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "colordepth", N_("Color depth"), FALSE, colordepth_list, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "exec", N_("Startup program"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "showcursor", N_("Use local cursor"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "once", N_("Disconnect after one session"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "listen_on_tcp", N_("Listening connection on protocol TCP"), 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_plugin_xdmcp_features[] =
+{
+ { REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_PLUGIN_XDMCP_FEATURE_TOOL_SENDCTRLALTDEL, N_("Send Ctrl+Alt+Delete"), NULL, NULL },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_END, 0, NULL, NULL, NULL }
+};
+
+/* Protocol plugin definition and features */
+static RemminaProtocolPlugin remmina_plugin_xdmcp =
+{
+ REMMINA_PLUGIN_TYPE_PROTOCOL, // Type
+ "XDMCP", // Name
+ N_("XDMCP - X Remote Session"), // Description
+ GETTEXT_PACKAGE, // Translation domain
+ VERSION, // Version number
+ "remmina-xdmcp", // Icon for normal connection
+ "remmina-xdmcp-ssh", // Icon for SSH connection
+ remmina_plugin_xdmcp_basic_settings, // Array for basic settings
+ NULL, // Array for advanced settings
+ REMMINA_PROTOCOL_SSH_SETTING_TUNNEL, // SSH settings type
+ remmina_plugin_xdmcp_features, // Array for available features
+ remmina_plugin_xdmcp_init, // Plugin initialization
+ remmina_plugin_xdmcp_open_connection, // Plugin open connection
+ remmina_plugin_xdmcp_close_connection, // Plugin close connection
+ remmina_plugin_xdmcp_query_feature, // Query for available features
+ remmina_plugin_xdmcp_call_feature, // Call a feature
+ NULL, // Send a keystroke
+ NULL // Screenshot
+};
+
+G_MODULE_EXPORT gboolean
+remmina_plugin_entry(RemminaPluginService *service)
+{
+ TRACE_CALL(__func__);
+ remmina_plugin_service = service;
+
+ bindtextdomain(GETTEXT_PACKAGE, REMMINA_RUNTIME_LOCALEDIR);
+ bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
+
+ if (!service->register_plugin((RemminaPlugin*)&remmina_plugin_xdmcp)) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+