diff options
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 Binary files differnew file mode 100644 index 000000000..96bc6828a --- /dev/null +++ b/plugins/exec/16x16/emblems/remmina-tool.png diff --git a/plugins/exec/22x22/emblems/remmina-tool.png b/plugins/exec/22x22/emblems/remmina-tool.png Binary files differnew file mode 100644 index 000000000..a28ca75ad --- /dev/null +++ b/plugins/exec/22x22/emblems/remmina-tool.png 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 Binary files differnew file mode 100644 index 000000000..9d1b952bb --- /dev/null +++ b/plugins/nx/16x16/emblems/remmina-nx.png diff --git a/plugins/nx/22x22/emblems/remmina-nx.png b/plugins/nx/22x22/emblems/remmina-nx.png Binary files differnew file mode 100644 index 000000000..13766daa1 --- /dev/null +++ b/plugins/nx/22x22/emblems/remmina-nx.png 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 Binary files differnew file mode 100644 index 000000000..9defcd6f6 --- /dev/null +++ b/plugins/rdp/16x16/emblems/remmina-rdp-ssh.png diff --git a/plugins/rdp/16x16/emblems/remmina-rdp.png b/plugins/rdp/16x16/emblems/remmina-rdp.png Binary files differnew file mode 100644 index 000000000..2d7724312 --- /dev/null +++ b/plugins/rdp/16x16/emblems/remmina-rdp.png diff --git a/plugins/rdp/22x22/emblems/remmina-rdp-ssh.png b/plugins/rdp/22x22/emblems/remmina-rdp-ssh.png Binary files differnew file mode 100644 index 000000000..e977bb3b7 --- /dev/null +++ b/plugins/rdp/22x22/emblems/remmina-rdp-ssh.png diff --git a/plugins/rdp/22x22/emblems/remmina-rdp.png b/plugins/rdp/22x22/emblems/remmina-rdp.png Binary files differnew file mode 100644 index 000000000..feb093c09 --- /dev/null +++ b/plugins/rdp/22x22/emblems/remmina-rdp.png diff --git a/plugins/rdp/CMakeLists.txt b/plugins/rdp/CMakeLists.txt new file mode 100644 index 000000000..c03054444 --- /dev/null +++ b/plugins/rdp/CMakeLists.txt @@ -0,0 +1,89 @@ +# remmina-plugin-rdp - The GTK+ Remote Desktop Client +# +# Copyright (C) 2011 Marc-Andre Moreau +# Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo +# Copyright (C) 2016-2017 Antenore Gatta, Giovanni Panozzo +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the +# OpenSSL library under certain conditions as described in each +# individual source file, and distribute linked combinations +# including the two. +# You must obey the GNU General Public License in all respects +# for all of the code used other than OpenSSL. If you modify +# file(s) with this exception, you may extend this exception to your +# version of the file(s), but you are not obligated to do so. If you +# do not wish to do so, delete this exception statement from your +# version. If you delete this exception statement from all source +# files in the program, then also delete it here. + + +set(CMAKE_THREAD_PREFER_PTHREAD TRUE) +find_package(Threads REQUIRED) +find_package(X11) + +set(REMMINA_PLUGIN_RDP_SRCS + rdp_plugin.c + rdp_plugin.h + rdp_event.c + rdp_event.h + rdp_file.c + rdp_file.h + rdp_settings.c + rdp_settings.h + rdp_graphics.c + rdp_graphics.h + rdp_cliprdr.c + rdp_cliprdr.h + rdp_channels.c + rdp_channels.h + ) + +add_definitions(-DFREERDP_REQUIRED_MAJOR=${FREERDP_REQUIRED_MAJOR}) +add_definitions(-DFREERDP_REQUIRED_MINOR=${FREERDP_REQUIRED_MINOR}) +add_definitions(-DFREERDP_REQUIRED_REVISION=${FREERDP_REQUIRED_REVISION}) + +if(WITH_EXAMPLES) + message(STATUS "Enabling examples and test plugins.") + add_definitions(-DWITH_EXAMPLES) +endif() + +option(WITH_GFX_H264 "Enable support for H264 modes in GFX" ON) +if (WITH_GFX_H264) + add_definitions(-DWITH_GFX_H264) +endif() + + +add_library(remmina-plugin-rdp MODULE ${REMMINA_PLUGIN_RDP_SRCS}) +set_target_properties(remmina-plugin-rdp PROPERTIES PREFIX "") +set_target_properties(remmina-plugin-rdp PROPERTIES NO_SONAME 1) + +include_directories(${REMMINA_COMMON_INCLUDE_DIRS} ${FREERDP_INCLUDE_DIRS} ${X11_INCLUDE_DIR}) +target_link_libraries(remmina-plugin-rdp + ${REMMINA_COMMON_LIBRARIES} ${FREERDP_LIBRARIES} ${X11_LIBRARIES}) + +install(TARGETS remmina-plugin-rdp DESTINATION ${REMMINA_PLUGINDIR}) + +install(FILES + 16x16/emblems/remmina-rdp-ssh.png + 16x16/emblems/remmina-rdp.png + DESTINATION ${APPICON16_EMBLEMS_DIR}) +install(FILES + 22x22/emblems/remmina-rdp-ssh.png + 22x22/emblems/remmina-rdp.png + DESTINATION ${APPICON22_EMBLEMS_DIR}) diff --git a/plugins/rdp/rdp_channels.c b/plugins/rdp/rdp_channels.c new file mode 100644 index 000000000..eb8d3723f --- /dev/null +++ b/plugins/rdp/rdp_channels.c @@ -0,0 +1,94 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2012-2012 Jean-Louis Dupond + * Copyright (C) 2016-2018 Antenore Gatta, Giovanni Panozzo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + * + */ + +#include "rdp_plugin.h" +#include "rdp_cliprdr.h" +#include "rdp_channels.h" +#include "rdp_event.h" + +#include <freerdp/freerdp.h> +#include <freerdp/channels/channels.h> +#include <freerdp/client/cliprdr.h> +#include <freerdp/gdi/gfx.h> + +void remmina_rdp_OnChannelConnectedEventHandler(rdpContext* context, ChannelConnectedEventArgs* e) +{ + TRACE_CALL(__func__); + + rfContext* rfi = (rfContext*)context; + + if (g_strcmp0(e->name, RDPEI_DVC_CHANNEL_NAME) == 0) { + g_print("Unimplemented: channel %s connected but we can't use it\n", e->name); + // xfc->rdpei = (RdpeiClientContext*) e->pInterface; + }else if (g_strcmp0(e->name, TSMF_DVC_CHANNEL_NAME) == 0) { + g_print("Unimplemented: channel %s connected but we can't use it\n", e->name); + // xf_tsmf_init(xfc, (TsmfClientContext*) e->pInterface); + }else if (g_strcmp0(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0) { + if (rfi->settings->SoftwareGdi) + gdi_graphics_pipeline_init(context->gdi, (RdpgfxClientContext*) e->pInterface); + else + g_print("Unimplemented: channel %s connected but libfreerdp is in HardwareGdi mode\n", e->name); + }else if (g_strcmp0(e->name, RAIL_SVC_CHANNEL_NAME) == 0) { + g_print("Unimplemented: channel %s connected but we can't use it\n", e->name); + // xf_rail_init(xfc, (RailClientContext*) e->pInterface); + }else if (g_strcmp0(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0) { + remmina_rdp_cliprdr_init( rfi, (CliprdrClientContext*)e->pInterface); + }else if (g_strcmp0(e->name, ENCOMSP_SVC_CHANNEL_NAME) == 0) { + g_print("Unimplemented: channel %s connected but we can't use it\n", e->name); + // xf_encomsp_init(xfc, (EncomspClientContext*) e->pInterface); + }else if (g_strcmp0(e->name, DISP_DVC_CHANNEL_NAME) == 0) { + // "disp" channel connected, save its context pointer + rfi->dispcontext = (DispClientContext*)e->pInterface; + // Notify remmina_connection_window to unlock dynres capability + remmina_plugin_service->protocol_plugin_emit_signal(rfi->protocol_widget, "unlock-dynres"); + // Send monitor layout message here to ask for resize of remote desktop now + if (rfi->scale == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES) { + remmina_rdp_event_send_delayed_monitor_layout(rfi->protocol_widget); + } + }remmina_plugin_service->log_printf("Channel %s has been opened\n", e->name); +} + +void remmina_rdp_OnChannelDisconnectedEventHandler(rdpContext* context, ChannelConnectedEventArgs* e) +{ + TRACE_CALL(__func__); + rfContext* rfi = (rfContext*)context; + + if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0) { + if (rfi->settings->SoftwareGdi) + gdi_graphics_pipeline_uninit(context->gdi, (RdpgfxClientContext*) e->pInterface); + } + remmina_plugin_service->log_printf("Channel %s has been closed\n", e->name); + +} diff --git a/plugins/rdp/rdp_channels.h b/plugins/rdp/rdp_channels.h new file mode 100644 index 000000000..193706ed9 --- /dev/null +++ b/plugins/rdp/rdp_channels.h @@ -0,0 +1,55 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2012-2012 Jean-Louis Dupond + * Copyright (C) 2017 Antenore Gatta, Giovanni Panozzo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + * + */ + + +#pragma once + +#include <freerdp/freerdp.h> +#include <freerdp/client/channels.h> +#include <freerdp/client/rdpei.h> +#include <freerdp/client/tsmf.h> +#include <freerdp/client/rail.h> +#include <freerdp/client/cliprdr.h> +#include <freerdp/client/rdpgfx.h> +#include <freerdp/client/encomsp.h> + +G_BEGIN_DECLS + +void remmina_rdp_OnChannelConnectedEventHandler(rdpContext* context, ChannelConnectedEventArgs* e); +void remmina_rdp_OnChannelDisconnectedEventHandler(rdpContext* context, ChannelConnectedEventArgs* e); + + +G_END_DECLS + diff --git a/plugins/rdp/rdp_cliprdr.c b/plugins/rdp/rdp_cliprdr.c new file mode 100644 index 000000000..b564bd6fd --- /dev/null +++ b/plugins/rdp/rdp_cliprdr.c @@ -0,0 +1,821 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2012-2012 Jean-Louis Dupond + * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo + * Copyright (C) 2016-2017 Antenore Gatta, Giovanni Panozzo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + * + */ + +#include "rdp_plugin.h" +#include "rdp_cliprdr.h" +#include "rdp_event.h" + +#include <freerdp/freerdp.h> +#include <freerdp/channels/channels.h> +#include <freerdp/client/cliprdr.h> +#include <sys/time.h> + +#define CLIPBOARD_TRANSFER_WAIT_TIME 2 + +UINT32 remmina_rdp_cliprdr_get_format_from_gdkatom(GdkAtom atom) +{ + TRACE_CALL(__func__); + UINT32 rc; + gchar* name = gdk_atom_name(atom); + rc = 0; + if (g_strcmp0("UTF8_STRING", name) == 0 || g_strcmp0("text/plain;charset=utf-8", name) == 0) { + rc = CF_UNICODETEXT; + } + if (g_strcmp0("TEXT", name) == 0 || g_strcmp0("text/plain", name) == 0) { + rc = CF_TEXT; + } + if (g_strcmp0("text/html", name) == 0) { + rc = CB_FORMAT_HTML; + } + if (g_strcmp0("image/png", name) == 0) { + rc = CB_FORMAT_PNG; + } + if (g_strcmp0("image/jpeg", name) == 0) { + rc = CB_FORMAT_JPEG; + } + if (g_strcmp0("image/bmp", name) == 0) { + rc = CF_DIB; + } + g_free(name); + return rc; +} + +void remmina_rdp_cliprdr_get_target_types(UINT32** formats, UINT16* size, GdkAtom* types, int count) +{ + TRACE_CALL(__func__); + int i; + *size = 1; + *formats = (UINT32*)malloc(sizeof(UINT32) * (count + 1)); + + *formats[0] = 0; + for (i = 0; i < count; i++) { + UINT32 format = remmina_rdp_cliprdr_get_format_from_gdkatom(types[i]); + if (format != 0) { + (*formats)[*size] = format; + (*size)++; + } + } + + *formats = realloc(*formats, sizeof(UINT32) * (*size)); +} + +static UINT8* lf2crlf(UINT8* data, int* size) +{ + TRACE_CALL(__func__); + UINT8 c; + UINT8* outbuf; + UINT8* out; + UINT8* in_end; + UINT8* in; + int out_size; + + out_size = (*size) * 2 + 1; + outbuf = (UINT8*)malloc(out_size); + out = outbuf; + in = data; + in_end = data + (*size); + + while (in < in_end) { + c = *in++; + if (c == '\n') { + *out++ = '\r'; + *out++ = '\n'; + }else { + *out++ = c; + } + } + + *out++ = 0; + *size = out - outbuf; + + return outbuf; +} + +static void crlf2lf(UINT8* data, size_t* size) +{ + TRACE_CALL(__func__); + UINT8 c; + UINT8* out; + UINT8* in; + UINT8* in_end; + + out = data; + in = data; + in_end = data + (*size); + + while (in < in_end) { + c = *in++; + if (c != '\r') + *out++ = c; + } + + *size = out - data; +} + +int remmina_rdp_cliprdr_server_file_contents_request(CliprdrClientContext* context, CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) +{ + TRACE_CALL(__func__); + return -1; +} +int remmina_rdp_cliprdr_server_file_contents_response(CliprdrClientContext* context, CLIPRDR_FILE_CONTENTS_RESPONSE* fileContentsResponse) +{ + TRACE_CALL(__func__); + return 1; +} + +void remmina_rdp_cliprdr_send_client_format_list(RemminaProtocolWidget *gp) +{ + TRACE_CALL(__func__); + RemminaPluginRdpUiObject* ui; + rfContext* rfi = GET_PLUGIN_DATA(gp); + rfClipboard* clipboard; + CLIPRDR_FORMAT_LIST *pFormatList; + RemminaPluginRdpEvent rdp_event = { 0 }; + + if (!rfi || !rfi->connected || rfi->is_reconnecting) + return; + + clipboard = &(rfi->clipboard); + + ui = g_new0(RemminaPluginRdpUiObject, 1); + ui->type = REMMINA_RDP_UI_CLIPBOARD; + ui->clipboard.clipboard = clipboard; + ui->clipboard.type = REMMINA_RDP_UI_CLIPBOARD_FORMATLIST; + pFormatList = remmina_rdp_event_queue_ui_sync_retptr(gp, ui); + + rdp_event.type = REMMINA_RDP_EVENT_TYPE_CLIPBOARD_SEND_CLIENT_FORMAT_LIST; + rdp_event.clipboard_formatlist.pFormatList = pFormatList; + remmina_rdp_event_event_push(gp, &rdp_event); + +} + +static void remmina_rdp_cliprdr_send_client_capabilities(rfClipboard* clipboard) +{ + TRACE_CALL(__func__); + CLIPRDR_CAPABILITIES capabilities; + CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet; + + capabilities.cCapabilitiesSets = 1; + capabilities.capabilitySets = (CLIPRDR_CAPABILITY_SET*)&(generalCapabilitySet); + + generalCapabilitySet.capabilitySetType = CB_CAPSTYPE_GENERAL; + generalCapabilitySet.capabilitySetLength = 12; + + generalCapabilitySet.version = CB_CAPS_VERSION_2; + generalCapabilitySet.generalFlags = CB_USE_LONG_FORMAT_NAMES; + + clipboard->context->ClientCapabilities(clipboard->context, &capabilities); +} + + +static UINT remmina_rdp_cliprdr_monitor_ready(CliprdrClientContext* context, CLIPRDR_MONITOR_READY* monitorReady) +{ + TRACE_CALL(__func__); + rfClipboard* clipboard = (rfClipboard*)context->custom; + RemminaProtocolWidget* gp; + + remmina_rdp_cliprdr_send_client_capabilities(clipboard); + gp = clipboard->rfi->protocol_widget; + remmina_rdp_cliprdr_send_client_format_list(gp); + + return CHANNEL_RC_OK; +} + +static UINT remmina_rdp_cliprdr_server_capabilities(CliprdrClientContext* context, CLIPRDR_CAPABILITIES* capabilities) +{ + TRACE_CALL(__func__); + return CHANNEL_RC_OK; +} + + +static UINT remmina_rdp_cliprdr_server_format_list(CliprdrClientContext* context, CLIPRDR_FORMAT_LIST* formatList) +{ + TRACE_CALL(__func__); + + /* Called when a user do a "Copy" on the server: we collect all formats + * the server send us and then setup the local clipboard with the appropiate + * functions to request server data */ + + RemminaPluginRdpUiObject* ui; + RemminaProtocolWidget* gp; + rfClipboard* clipboard; + CLIPRDR_FORMAT* format; + CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse; + + int i; + + clipboard = (rfClipboard*)context->custom; + gp = clipboard->rfi->protocol_widget; + GtkTargetList* list = gtk_target_list_new(NULL, 0); + + for (i = 0; i < formatList->numFormats; i++) { + format = &formatList->formats[i]; + if (format->formatId == CF_UNICODETEXT) { + GdkAtom atom = gdk_atom_intern("UTF8_STRING", TRUE); + gtk_target_list_add(list, atom, 0, CF_UNICODETEXT); + }else if (format->formatId == CF_TEXT) { + GdkAtom atom = gdk_atom_intern("TEXT", TRUE); + gtk_target_list_add(list, atom, 0, CF_TEXT); + }else if (format->formatId == CF_DIB) { + GdkAtom atom = gdk_atom_intern("image/bmp", TRUE); + gtk_target_list_add(list, atom, 0, CF_DIB); + }else if (format->formatId == CF_DIBV5) { + GdkAtom atom = gdk_atom_intern("image/bmp", TRUE); + gtk_target_list_add(list, atom, 0, CF_DIBV5); + }else if (format->formatId == CB_FORMAT_JPEG) { + GdkAtom atom = gdk_atom_intern("image/jpeg", TRUE); + gtk_target_list_add(list, atom, 0, CB_FORMAT_JPEG); + }else if (format->formatId == CB_FORMAT_PNG) { + GdkAtom atom = gdk_atom_intern("image/png", TRUE); + gtk_target_list_add(list, atom, 0, CB_FORMAT_PNG); + }else if (format->formatId == CB_FORMAT_HTML) { + GdkAtom atom = gdk_atom_intern("text/html", TRUE); + gtk_target_list_add(list, atom, 0, CB_FORMAT_HTML); + } + } + + /* Now we tell GTK to change the local keyboard calling gtk_clipboard_set_with_owner + * via REMMINA_RDP_UI_CLIPBOARD_SET_DATA + * GTK will immediately fire an "owner-change" event, that we should ignore */ + + ui = g_new0(RemminaPluginRdpUiObject, 1); + ui->type = REMMINA_RDP_UI_CLIPBOARD; + ui->clipboard.clipboard = clipboard; + ui->clipboard.type = REMMINA_RDP_UI_CLIPBOARD_SET_DATA; + ui->clipboard.targetlist = list; + remmina_rdp_event_queue_ui_sync_retint(gp, ui); + + /* Send FormatListResponse to server */ + + formatListResponse.msgType = CB_FORMAT_LIST_RESPONSE; + formatListResponse.msgFlags = CB_RESPONSE_OK; // Can be CB_RESPONSE_FAIL in case of error + formatListResponse.dataLen = 0; + + return clipboard->context->ClientFormatListResponse(clipboard->context, &formatListResponse); + +} + +static UINT remmina_rdp_cliprdr_server_format_list_response(CliprdrClientContext* context, CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse) +{ + TRACE_CALL(__func__); + return CHANNEL_RC_OK; +} + + +static UINT remmina_rdp_cliprdr_server_format_data_request(CliprdrClientContext* context, CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest) +{ + TRACE_CALL(__func__); + + RemminaPluginRdpUiObject* ui; + RemminaProtocolWidget* gp; + rfClipboard* clipboard; + + clipboard = (rfClipboard*)context->custom; + gp = clipboard->rfi->protocol_widget; + + ui = g_new0(RemminaPluginRdpUiObject, 1); + ui->type = REMMINA_RDP_UI_CLIPBOARD; + ui->clipboard.clipboard = clipboard; + ui->clipboard.type = REMMINA_RDP_UI_CLIPBOARD_GET_DATA; + ui->clipboard.format = formatDataRequest->requestedFormatId; + remmina_rdp_event_queue_ui_sync_retint(gp, ui); + + return CHANNEL_RC_OK; +} + +static UINT remmina_rdp_cliprdr_server_format_data_response(CliprdrClientContext* context, CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse) +{ + TRACE_CALL(__func__); + + UINT8* data; + size_t size; + rfContext* rfi; + RemminaProtocolWidget* gp; + rfClipboard* clipboard; + GdkPixbufLoader *pixbuf; + gpointer output = NULL; + RemminaPluginRdpUiObject *ui; + + clipboard = (rfClipboard*)context->custom; + gp = clipboard->rfi->protocol_widget; + rfi = GET_PLUGIN_DATA(gp); + + data = formatDataResponse->requestedFormatData; + size = formatDataResponse->dataLen; + + // formatDataResponse->requestedFormatData is allocated + // by freerdp and freed after returning from this callback function. + // So we must make a copy if we need to preserve it + + if (size > 0) { + switch (rfi->clipboard.format) { + case CF_UNICODETEXT: + { + size = ConvertFromUnicode(CP_UTF8, 0, (WCHAR*)data, size / 2, (CHAR**)&output, 0, NULL, NULL); + crlf2lf(output, &size); + break; + } + + case CF_TEXT: + case CB_FORMAT_HTML: + { + output = (gpointer)calloc(1, size + 1); + if (output) { + memcpy(output, data, size); + crlf2lf(output, &size); + } + break; + } + + case CF_DIBV5: + case CF_DIB: + { + wStream* s; + UINT32 offset; + GError *perr; + BITMAPINFOHEADER* pbi; + BITMAPV5HEADER* pbi5; + + pbi = (BITMAPINFOHEADER*)data; + + // offset calculation inspired by http://downloads.poolelan.com/MSDN/MSDNLibrary6/Disk1/Samples/VC/OS/WindowsXP/GetImage/BitmapUtil.cpp + offset = 14 + pbi->biSize; + if (pbi->biClrUsed != 0) + offset += sizeof(RGBQUAD) * pbi->biClrUsed; + else if (pbi->biBitCount <= 8) + offset += sizeof(RGBQUAD) * (1 << pbi->biBitCount); + if (pbi->biSize == sizeof(BITMAPINFOHEADER)) { + if (pbi->biCompression == 3) // BI_BITFIELDS is 3 + offset += 12; + } else if (pbi->biSize >= sizeof(BITMAPV5HEADER)) { + pbi5 = (BITMAPV5HEADER*)pbi; + if (pbi5->bV5ProfileData <= offset) + offset += pbi5->bV5ProfileSize; + } + s = Stream_New(NULL, 14 + size); + Stream_Write_UINT8(s, 'B'); + Stream_Write_UINT8(s, 'M'); + Stream_Write_UINT32(s, 14 + size); + Stream_Write_UINT32(s, 0); + Stream_Write_UINT32(s, offset); + Stream_Write(s, data, size); + + data = Stream_Buffer(s); + size = Stream_Length(s); + + pixbuf = gdk_pixbuf_loader_new(); + perr = NULL; + if ( !gdk_pixbuf_loader_write(pixbuf, data, size, &perr) ) { + remmina_plugin_service->log_printf("[RDP] rdp_cliprdr: gdk_pixbuf_loader_write() returned error %s\n", perr->message); + }else { + if ( !gdk_pixbuf_loader_close(pixbuf, &perr) ) { + remmina_plugin_service->log_printf("[RDP] rdp_cliprdr: gdk_pixbuf_loader_close() returned error %s\n", perr->message); + perr = NULL; + } + Stream_Free(s, TRUE); + output = g_object_ref(gdk_pixbuf_loader_get_pixbuf(pixbuf)); + } + g_object_unref(pixbuf); + break; + } + + case CB_FORMAT_PNG: + case CB_FORMAT_JPEG: + { + pixbuf = gdk_pixbuf_loader_new(); + gdk_pixbuf_loader_write(pixbuf, data, size, NULL); + output = g_object_ref(gdk_pixbuf_loader_get_pixbuf(pixbuf)); + gdk_pixbuf_loader_close(pixbuf, NULL); + g_object_unref(pixbuf); + break; + } + } + } + + pthread_mutex_lock(&clipboard->transfer_clip_mutex); + pthread_cond_signal(&clipboard->transfer_clip_cond); + if ( clipboard->srv_clip_data_wait == SCDW_BUSY_WAIT ) { + clipboard->srv_data = output; + }else { + // Clipboard data arrived from server when we are not busywaiting. + // Just put it on the local clipboard + + ui = g_new0(RemminaPluginRdpUiObject, 1); + ui->type = REMMINA_RDP_UI_CLIPBOARD; + ui->clipboard.clipboard = clipboard; + ui->clipboard.type = REMMINA_RDP_UI_CLIPBOARD_SET_CONTENT; + ui->clipboard.data = output; + ui->clipboard.format = clipboard->format; + remmina_rdp_event_queue_ui_sync_retint(gp, ui); + + clipboard->srv_clip_data_wait = SCDW_NONE; + + } + pthread_mutex_unlock(&clipboard->transfer_clip_mutex); + + return CHANNEL_RC_OK; +} + +void remmina_rdp_cliprdr_request_data(GtkClipboard *gtkClipboard, GtkSelectionData *selection_data, guint info, RemminaProtocolWidget* gp ) +{ + TRACE_CALL(__func__); + + /* Called by GTK when someone press "Paste" on the client side. + * We ask to the server the data we need */ + + GdkAtom target; + CLIPRDR_FORMAT_DATA_REQUEST* pFormatDataRequest; + rfClipboard* clipboard; + rfContext* rfi = GET_PLUGIN_DATA(gp); + RemminaPluginRdpEvent rdp_event = { 0 }; + struct timespec to; + struct timeval tv; + int rc; + + clipboard = &(rfi->clipboard); + if ( clipboard->srv_clip_data_wait != SCDW_NONE ) { + remmina_plugin_service->log_printf("[RDP] Cannot paste now, I'm transferring clipboard data from server. Try again later\n"); + return; + } + + target = gtk_selection_data_get_target(selection_data); + // clipboard->format = remmina_rdp_cliprdr_get_format_from_gdkatom(target); + clipboard->format = info; + + /* Request Clipboard content from the server, the request is async */ + + pthread_mutex_lock(&clipboard->transfer_clip_mutex); + + pFormatDataRequest = (CLIPRDR_FORMAT_DATA_REQUEST*)malloc(sizeof(CLIPRDR_FORMAT_DATA_REQUEST)); + ZeroMemory(pFormatDataRequest, sizeof(CLIPRDR_FORMAT_DATA_REQUEST)); + pFormatDataRequest->requestedFormatId = clipboard->format; + clipboard->srv_clip_data_wait = SCDW_BUSY_WAIT; + + rdp_event.type = REMMINA_RDP_EVENT_TYPE_CLIPBOARD_SEND_CLIENT_FORMAT_DATA_REQUEST; + rdp_event.clipboard_formatdatarequest.pFormatDataRequest = pFormatDataRequest; + remmina_rdp_event_event_push(gp, &rdp_event); + + + /* Busy wait clibpoard data for CLIPBOARD_TRANSFER_WAIT_TIME seconds */ + gettimeofday(&tv, NULL); + to.tv_sec = tv.tv_sec + CLIPBOARD_TRANSFER_WAIT_TIME; + to.tv_nsec = tv.tv_usec * 1000; + rc = pthread_cond_timedwait(&clipboard->transfer_clip_cond, &clipboard->transfer_clip_mutex, &to); + + if ( rc == 0 ) { + /* Data has arrived without timeout */ + if (clipboard->srv_data != NULL) { + if (info == CB_FORMAT_PNG || info == CF_DIB || info == CF_DIBV5 || info == CB_FORMAT_JPEG) { + gtk_selection_data_set_pixbuf(selection_data, clipboard->srv_data); + g_object_unref(clipboard->srv_data); + }else { + gtk_selection_data_set_text(selection_data, clipboard->srv_data, -1); + free(clipboard->srv_data); + } + } + clipboard->srv_clip_data_wait = SCDW_NONE; + } else { + clipboard->srv_clip_data_wait = SCDW_ASYNCWAIT; + if ( rc == ETIMEDOUT ) { + remmina_plugin_service->log_printf("[RDP] Clipboard data has not been transferred from the server in %d seconds. Try to paste later.\n", + CLIPBOARD_TRANSFER_WAIT_TIME); + }else { + remmina_plugin_service->log_printf("[RDP] internal error: pthread_cond_timedwait() returned %d\n", rc); + clipboard->srv_clip_data_wait = SCDW_NONE; + } + } + pthread_mutex_unlock(&clipboard->transfer_clip_mutex); + +} + +void remmina_rdp_cliprdr_empty_clipboard(GtkClipboard *gtkClipboard, rfClipboard *clipboard) +{ + TRACE_CALL(__func__); + /* No need to do anything here */ +} + +CLIPRDR_FORMAT_LIST *remmina_rdp_cliprdr_get_client_format_list(RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + + GtkClipboard* gtkClipboard; + rfClipboard* clipboard; + rfContext* rfi = GET_PLUGIN_DATA(gp); + GdkAtom* targets; + gboolean result = 0; + gint loccount, srvcount; + gint formatId, i; + CLIPRDR_FORMAT *formats; + struct retp_t { + CLIPRDR_FORMAT_LIST pFormatList; + CLIPRDR_FORMAT formats[]; + } *retp; + + clipboard = &(rfi->clipboard); + formats = NULL; + + retp = NULL; + + gtkClipboard = gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD); + if (gtkClipboard) { + result = gtk_clipboard_wait_for_targets(gtkClipboard, &targets, &loccount); + } + + if (result && loccount > 0) { + formats = (CLIPRDR_FORMAT*)malloc(loccount * sizeof(CLIPRDR_FORMAT)); + srvcount = 0; + for (i = 0; i < loccount; i++) { + formatId = remmina_rdp_cliprdr_get_format_from_gdkatom(targets[i]); + if ( formatId != 0 ) { + formats[srvcount].formatId = formatId; + formats[srvcount].formatName = NULL; + srvcount++; + } + } + if (srvcount > 0) { + retp = (struct retp_t *)malloc(sizeof(struct retp_t) + sizeof(CLIPRDR_FORMAT) * srvcount); + retp->pFormatList.formats = retp->formats; + retp->pFormatList.numFormats = srvcount; + memcpy(retp->formats, formats, sizeof(CLIPRDR_FORMAT) * srvcount); + } else { + retp = (struct retp_t *)malloc(sizeof(struct retp_t)); + retp->pFormatList.formats = NULL; + retp->pFormatList.numFormats = 0; + } + free(formats); + } else { + retp = (struct retp_t *)malloc(sizeof(struct retp_t) + sizeof(CLIPRDR_FORMAT)); + retp->pFormatList.formats = NULL; + retp->pFormatList.numFormats = 0; + } + + if (result) + g_free(targets); + + retp->pFormatList.msgFlags = CB_RESPONSE_OK; + + return (CLIPRDR_FORMAT_LIST*)retp; +} + +static void remmina_rdp_cliprdr_mt_get_format_list(RemminaProtocolWidget* gp, RemminaPluginRdpUiObject* ui) +{ + TRACE_CALL(__func__); + ui->retptr = (void*)remmina_rdp_cliprdr_get_client_format_list(gp); +} + + +void remmina_rdp_cliprdr_get_clipboard_data(RemminaProtocolWidget* gp, RemminaPluginRdpUiObject* ui) +{ + TRACE_CALL(__func__); + GtkClipboard* gtkClipboard; + rfClipboard* clipboard; + UINT8* inbuf = NULL; + UINT8* outbuf = NULL; + GdkPixbuf *image = NULL; + int size = 0; + rfContext* rfi = GET_PLUGIN_DATA(gp); + RemminaPluginRdpEvent rdp_event = { 0 }; + CLIPRDR_FORMAT_DATA_RESPONSE* pFormatDataResponse; + + clipboard = ui->clipboard.clipboard; + gtkClipboard = gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD); + if (gtkClipboard) { + switch (ui->clipboard.format) { + case CF_TEXT: + case CF_UNICODETEXT: + case CB_FORMAT_HTML: + { + inbuf = (UINT8*)gtk_clipboard_wait_for_text(gtkClipboard); + break; + } + + case CB_FORMAT_PNG: + case CB_FORMAT_JPEG: + case CF_DIB: + case CF_DIBV5: + { + image = gtk_clipboard_wait_for_image(gtkClipboard); + break; + } + } + } + + /* No data received, send nothing */ + if (inbuf != NULL || image != NULL) { + switch (ui->clipboard.format) { + case CF_TEXT: + case CB_FORMAT_HTML: + { + size = strlen((char*)inbuf); + outbuf = lf2crlf(inbuf, &size); + break; + } + case CF_UNICODETEXT: + { + size = strlen((char*)inbuf); + inbuf = lf2crlf(inbuf, &size); + size = (ConvertToUnicode(CP_UTF8, 0, (CHAR*)inbuf, -1, (WCHAR**)&outbuf, 0) ) * sizeof(WCHAR); + g_free(inbuf); + break; + } + case CB_FORMAT_PNG: + { + gchar* data; + gsize buffersize; + gdk_pixbuf_save_to_buffer(image, &data, &buffersize, "png", NULL, NULL); + outbuf = (UINT8*)malloc(buffersize); + memcpy(outbuf, data, buffersize); + size = buffersize; + g_object_unref(image); + break; + } + case CB_FORMAT_JPEG: + { + gchar* data; + gsize buffersize; + gdk_pixbuf_save_to_buffer(image, &data, &buffersize, "jpeg", NULL, NULL); + outbuf = (UINT8*)malloc(buffersize); + memcpy(outbuf, data, buffersize); + size = buffersize; + g_object_unref(image); + break; + } + case CF_DIB: + case CF_DIBV5: + { + gchar* data; + gsize buffersize; + gdk_pixbuf_save_to_buffer(image, &data, &buffersize, "bmp", NULL, NULL); + size = buffersize - 14; + outbuf = (UINT8*)malloc(size); + memcpy(outbuf, data + 14, size); + g_object_unref(image); + break; + } + } + } + + pFormatDataResponse = (CLIPRDR_FORMAT_DATA_RESPONSE*)malloc(sizeof(CLIPRDR_FORMAT_DATA_RESPONSE)); + if (!pFormatDataResponse) { + if (outbuf) free(outbuf); + return; + } + + ZeroMemory(pFormatDataResponse, sizeof(CLIPRDR_FORMAT_DATA_RESPONSE)); + rdp_event.type = REMMINA_RDP_EVENT_TYPE_CLIPBOARD_SEND_CLIENT_FORMAT_DATA_RESPONSE; + rdp_event.clipboard_formatdataresponse.pFormatDataResponse = pFormatDataResponse; + pFormatDataResponse->msgFlags = CB_RESPONSE_OK; + pFormatDataResponse->dataLen = size; + pFormatDataResponse->requestedFormatData = outbuf; + remmina_rdp_event_event_push(gp, &rdp_event); + +} + +void remmina_rdp_cliprdr_set_clipboard_content(RemminaProtocolWidget* gp, RemminaPluginRdpUiObject* ui) +{ + TRACE_CALL(__func__); + GtkClipboard* gtkClipboard; + rfContext* rfi = GET_PLUGIN_DATA(gp); + + gtkClipboard = gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD); + if (ui->clipboard.format == CB_FORMAT_PNG || ui->clipboard.format == CF_DIB || ui->clipboard.format == CF_DIBV5 || ui->clipboard.format == CB_FORMAT_JPEG) { + gtk_clipboard_set_image( gtkClipboard, ui->clipboard.data ); + g_object_unref(ui->clipboard.data); + }else { + gtk_clipboard_set_text( gtkClipboard, ui->clipboard.data, -1 ); + free(ui->clipboard.data); + } + +} + +void remmina_rdp_cliprdr_set_clipboard_data(RemminaProtocolWidget* gp, RemminaPluginRdpUiObject* ui) +{ + TRACE_CALL(__func__); + GtkClipboard* gtkClipboard; + GtkTargetEntry* targets; + gint n_targets; + rfContext* rfi = GET_PLUGIN_DATA(gp); + rfClipboard* clipboard; + + clipboard = ui->clipboard.clipboard; + targets = gtk_target_table_new_from_list(ui->clipboard.targetlist, &n_targets); + gtkClipboard = gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD); + if (gtkClipboard && targets) { + gtk_clipboard_set_with_owner(gtkClipboard, targets, n_targets, + (GtkClipboardGetFunc)remmina_rdp_cliprdr_request_data, + (GtkClipboardClearFunc)remmina_rdp_cliprdr_empty_clipboard, G_OBJECT(gp)); + gtk_target_table_free(targets, n_targets); + } +} + +void remmina_rdp_cliprdr_detach_owner(RemminaProtocolWidget* gp) +{ + /* When closing a rdp connection, we should check if gp is a clipboard owner. + * If it's an owner, detach it from the clipboard */ + TRACE_CALL(__func__); + rfContext* rfi = GET_PLUGIN_DATA(gp); + GtkClipboard* gtkClipboard; + + gtkClipboard = gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD); + if (gtkClipboard && gtk_clipboard_get_owner(gtkClipboard) == (GObject*)gp) { + gtk_clipboard_clear(gtkClipboard); + } + +} + +void remmina_rdp_event_process_clipboard(RemminaProtocolWidget* gp, RemminaPluginRdpUiObject* ui) +{ + TRACE_CALL(__func__); + + switch (ui->clipboard.type) { + + case REMMINA_RDP_UI_CLIPBOARD_FORMATLIST: + remmina_rdp_cliprdr_mt_get_format_list(gp, ui); + break; + + case REMMINA_RDP_UI_CLIPBOARD_GET_DATA: + remmina_rdp_cliprdr_get_clipboard_data(gp, ui); + break; + + case REMMINA_RDP_UI_CLIPBOARD_SET_DATA: + remmina_rdp_cliprdr_set_clipboard_data(gp, ui); + break; + + case REMMINA_RDP_UI_CLIPBOARD_SET_CONTENT: + remmina_rdp_cliprdr_set_clipboard_content(gp, ui); + break; + } +} + +void remmina_rdp_clipboard_init(rfContext *rfi) +{ + TRACE_CALL(__func__); + // Future: initialize rfi->clipboard +} +void remmina_rdp_clipboard_free(rfContext *rfi) +{ + TRACE_CALL(__func__); + // Future: deinitialize rfi->clipboard +} + + +void remmina_rdp_cliprdr_init(rfContext* rfi, CliprdrClientContext* cliprdr) +{ + TRACE_CALL(__func__); + + rfClipboard* clipboard; + clipboard = &(rfi->clipboard); + + rfi->clipboard.rfi = rfi; + cliprdr->custom = (void*)clipboard; + + clipboard->context = cliprdr; + pthread_mutex_init(&clipboard->transfer_clip_mutex, NULL); + pthread_cond_init(&clipboard->transfer_clip_cond, NULL); + clipboard->srv_clip_data_wait = SCDW_NONE; + + cliprdr->MonitorReady = remmina_rdp_cliprdr_monitor_ready; + cliprdr->ServerCapabilities = remmina_rdp_cliprdr_server_capabilities; + cliprdr->ServerFormatList = remmina_rdp_cliprdr_server_format_list; + cliprdr->ServerFormatListResponse = remmina_rdp_cliprdr_server_format_list_response; + cliprdr->ServerFormatDataRequest = remmina_rdp_cliprdr_server_format_data_request; + cliprdr->ServerFormatDataResponse = remmina_rdp_cliprdr_server_format_data_response; + +// cliprdr->ServerFileContentsRequest = remmina_rdp_cliprdr_server_file_contents_request; +// cliprdr->ServerFileContentsResponse = remmina_rdp_cliprdr_server_file_contents_response; + +} + diff --git a/plugins/rdp/rdp_cliprdr.h b/plugins/rdp/rdp_cliprdr.h new file mode 100644 index 000000000..d40bff98c --- /dev/null +++ b/plugins/rdp/rdp_cliprdr.h @@ -0,0 +1,50 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2010-2011 Vic Lee + * Copyright (C) 2012-2012 Jean-Louis Dupond + * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo + * Copyright (C) 2016-2017 Antenore Gatta, Giovanni Panozzo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + * + */ + +#pragma once + + +#include <freerdp/freerdp.h> +#include "rdp_plugin.h" + +void remmina_rdp_clipboard_init(rfContext* rfi); +void remmina_rdp_clipboard_free(rfContext* rfi); +void remmina_rdp_cliprdr_init(rfContext* rfc, CliprdrClientContext* cliprdr); +void remmina_rdp_channel_cliprdr_process(RemminaProtocolWidget* gp, wMessage* event); +void remmina_rdp_event_process_clipboard(RemminaProtocolWidget* gp, RemminaPluginRdpUiObject* ui); +CLIPRDR_FORMAT_LIST *remmina_rdp_cliprdr_get_client_format_list(RemminaProtocolWidget* gp); +void remmina_rdp_cliprdr_detach_owner(RemminaProtocolWidget* gp); diff --git a/plugins/rdp/rdp_event.c b/plugins/rdp/rdp_event.c new file mode 100644 index 000000000..48fe41c8e --- /dev/null +++ b/plugins/rdp/rdp_event.c @@ -0,0 +1,1182 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2010 Jay Sorg + * Copyright (C) 2010-2011 Vic Lee + * Copyright (C) 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo + * Copyright (C) 2016-2017 Antenore Gatta, Giovanni Panozzo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + * + */ + +#include "rdp_plugin.h" +#include "rdp_event.h" +#include "rdp_cliprdr.h" +#include "rdp_settings.h" +#include <gdk/gdkkeysyms.h> +#include <cairo/cairo-xlib.h> +#include <freerdp/locale/keyboard.h> +#include <execinfo.h> + +static void remmina_rdp_event_on_focus_in(GtkWidget* widget, GdkEventKey* event, RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + rfContext* rfi = GET_PLUGIN_DATA(gp); + rdpInput* input; + GdkModifierType state; +#if GTK_CHECK_VERSION(3, 20, 0) + GdkSeat *seat; +#else + GdkDeviceManager *manager; +#endif + GdkDevice *keyboard = NULL; + + if (!rfi || !rfi->connected || rfi->is_reconnecting) + return; + + input = rfi->instance->input; + UINT32 toggle_keys_state = 0; + +#if GTK_CHECK_VERSION(3, 20, 0) + seat = gdk_display_get_default_seat(gdk_display_get_default()); + keyboard = gdk_seat_get_pointer(seat); +#else + manager = gdk_display_get_device_manager(gdk_display_get_default()); + keyboard = gdk_device_manager_get_client_pointer(manager); +#endif + gdk_window_get_device_position(gdk_get_default_root_window(), keyboard, NULL, NULL, &state); + + if (state & GDK_LOCK_MASK) { + toggle_keys_state |= KBD_SYNC_CAPS_LOCK; + } + if (state & GDK_MOD2_MASK) { + toggle_keys_state |= KBD_SYNC_NUM_LOCK; + } + if (state & GDK_MOD5_MASK) { + toggle_keys_state |= KBD_SYNC_SCROLL_LOCK; + } + + input->SynchronizeEvent(input, toggle_keys_state); + input->KeyboardEvent(input, KBD_FLAGS_RELEASE, 0x0F); +} + +void remmina_rdp_event_event_push(RemminaProtocolWidget* gp, const RemminaPluginRdpEvent* e) +{ + TRACE_CALL(__func__); + rfContext* rfi = GET_PLUGIN_DATA(gp); + RemminaPluginRdpEvent* event; + + /* Called by the main GTK thread to send an event to the libfreerdp thread */ + + if (!rfi || !rfi->connected || rfi->is_reconnecting) + return; + + if (rfi->event_queue) { + event = g_memdup(e, sizeof(RemminaPluginRdpEvent)); + g_async_queue_push(rfi->event_queue, event); + + if (write(rfi->event_pipe[1], "\0", 1)) { + } + } +} + +static void remmina_rdp_event_release_all_keys(RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + rfContext* rfi = GET_PLUGIN_DATA(gp); + RemminaPluginRdpEvent rdp_event = { 0 }; + int i; + + /* Send all release key events for previously pressed keys */ + for (i = 0; i < rfi->pressed_keys->len; i++) { + rdp_event = g_array_index(rfi->pressed_keys, RemminaPluginRdpEvent, i); + if ((rdp_event.type == REMMINA_RDP_EVENT_TYPE_SCANCODE || + rdp_event.type == REMMINA_RDP_EVENT_TYPE_SCANCODE_UNICODE) && + rdp_event.key_event.up == False) { + rdp_event.key_event.up = True; + remmina_rdp_event_event_push(gp, &rdp_event); + } + } + + g_array_set_size(rfi->pressed_keys, 0); +} + +static void remmina_rdp_event_release_key(RemminaProtocolWidget* gp, RemminaPluginRdpEvent rdp_event) +{ + TRACE_CALL(__func__); + gint i; + rfContext* rfi = GET_PLUGIN_DATA(gp); + RemminaPluginRdpEvent rdp_event_2 = { 0 }; + + rdp_event_2.type = REMMINA_RDP_EVENT_TYPE_SCANCODE; + + if ((rdp_event.type == REMMINA_RDP_EVENT_TYPE_SCANCODE || + rdp_event.type == REMMINA_RDP_EVENT_TYPE_SCANCODE_UNICODE) && + rdp_event.key_event.up ) { + /* Unregister the keycode only */ + for (i = 0; i < rfi->pressed_keys->len; i++) { + rdp_event_2 = g_array_index(rfi->pressed_keys, RemminaPluginRdpEvent, i); + + if (rdp_event_2.key_event.key_code == rdp_event.key_event.key_code && + rdp_event_2.key_event.unicode_code == rdp_event.key_event.unicode_code && + rdp_event_2.key_event.extended == rdp_event.key_event.extended) { + g_array_remove_index_fast(rfi->pressed_keys, i); + break; + } + } + } +} + +static void keypress_list_add(RemminaProtocolWidget *gp, RemminaPluginRdpEvent rdp_event) +{ + TRACE_CALL(__func__); + rfContext* rfi = GET_PLUGIN_DATA(gp); + if (!rdp_event.key_event.key_code) + return; + + if (rdp_event.key_event.up) { + remmina_rdp_event_release_key(gp, rdp_event); + } else { + g_array_append_val(rfi->pressed_keys, rdp_event); + } + +} + + +static void remmina_rdp_event_scale_area(RemminaProtocolWidget* gp, gint* x, gint* y, gint* w, gint* h) +{ + TRACE_CALL(__func__); + gint width, height; + gint sx, sy, sw, sh; + rfContext* rfi = GET_PLUGIN_DATA(gp); + + if (!rfi || !rfi->connected || rfi->is_reconnecting || !rfi->surface) + return; + + width = remmina_plugin_service->protocol_plugin_get_width(gp); + height = remmina_plugin_service->protocol_plugin_get_height(gp); + + if ((width == 0) || (height == 0)) + return; + + if ((rfi->scale_width == width) && (rfi->scale_height == height)) { + /* Same size, just copy the pixels */ + *x = MIN(MAX(0, *x), width - 1); + *y = MIN(MAX(0, *y), height - 1); + *w = MIN(width - *x, *w); + *h = MIN(height - *y, *h); + return; + } + + /* We have to extend the scaled region one scaled pixel, to avoid gaps */ + + sx = MIN(MAX(0, (*x) * rfi->scale_width / width + - rfi->scale_width / width - 2), rfi->scale_width - 1); + + sy = MIN(MAX(0, (*y) * rfi->scale_height / height + - rfi->scale_height / height - 2), rfi->scale_height - 1); + + sw = MIN(rfi->scale_width - sx, (*w) * rfi->scale_width / width + + rfi->scale_width / width + 4); + + sh = MIN(rfi->scale_height - sy, (*h) * rfi->scale_height / height + + rfi->scale_height / height + 4); + + *x = sx; + *y = sy; + *w = sw; + *h = sh; +} + +void remmina_rdp_event_update_region(RemminaProtocolWidget* gp, RemminaPluginRdpUiObject* ui) +{ + TRACE_CALL(__func__); + rfContext* rfi = GET_PLUGIN_DATA(gp); + gint x, y, w, h; + + x = ui->region.x; + y = ui->region.y; + w = ui->region.width; + h = ui->region.height; + + if (rfi->scale == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED) + remmina_rdp_event_scale_area(gp, &x, &y, &w, &h); + + gtk_widget_queue_draw_area(rfi->drawing_area, x, y, w, h); +} + +void remmina_rdp_event_update_rect(RemminaProtocolWidget* gp, gint x, gint y, gint w, gint h) +{ + TRACE_CALL(__func__); + rfContext* rfi = GET_PLUGIN_DATA(gp); + + if (rfi->scale == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED) + remmina_rdp_event_scale_area(gp, &x, &y, &w, &h); + + gtk_widget_queue_draw_area(rfi->drawing_area, x, y, w, h); +} + +static void remmina_rdp_event_update_scale_factor(RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + GtkAllocation a; + gint rdwidth, rdheight; + gint gpwidth, gpheight; + RemminaFile* remminafile; + rfContext* rfi = GET_PLUGIN_DATA(gp); + + remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); + + gtk_widget_get_allocation(GTK_WIDGET(gp), &a); + gpwidth = a.width; + gpheight = a.height; + + if (rfi->scale == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED) { + if ((gpwidth > 1) && (gpheight > 1)) { + rdwidth = remmina_plugin_service->protocol_plugin_get_width(gp); + rdheight = remmina_plugin_service->protocol_plugin_get_height(gp); + + rfi->scale_width = gpwidth; + rfi->scale_height = gpheight; + + rfi->scale_x = (gdouble)rfi->scale_width / (gdouble)rdwidth; + rfi->scale_y = (gdouble)rfi->scale_height / (gdouble)rdheight; + } + }else { + rfi->scale_width = 0; + rfi->scale_height = 0; + rfi->scale_x = 0; + rfi->scale_y = 0; + } + +} + +static gboolean remmina_rdp_event_on_draw(GtkWidget* widget, cairo_t* context, RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + rfContext* rfi = GET_PLUGIN_DATA(gp); + guint width, height; + gchar *msg; + cairo_text_extents_t extents; + + if (!rfi || !rfi->connected) + return FALSE; + + + if (rfi->is_reconnecting) { + /* freerdp is reconnecting, just show a message to the user */ + + width = gtk_widget_get_allocated_width(widget); + height = gtk_widget_get_allocated_height(widget); + + /* Draw text */ + msg = g_strdup_printf(_("Reconnection in progress. Attempt %d of %d..."), + rfi->reconnect_nattempt, rfi->reconnect_maxattempts); + + cairo_select_font_face(context, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); + cairo_set_font_size(context, 24); + cairo_set_source_rgb(context, 0.9, 0.9, 0.9); + cairo_text_extents(context, msg, &extents); + cairo_move_to(context, (width - (extents.width + extents.x_bearing)) / 2, (height - (extents.height + extents.y_bearing)) / 2); + cairo_show_text(context, msg); + g_free(msg); + + }else { + /* Standard drawing: we copy the surface from RDP */ + + if (!rfi->surface) + return FALSE; + + GtkAllocation a; + gtk_widget_get_allocation(GTK_WIDGET(gp), &a); + + if (rfi->scale == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED) + cairo_scale(context, rfi->scale_x, rfi->scale_y); + + cairo_set_source_surface(context, rfi->surface, 0, 0); + + cairo_set_operator(context, CAIRO_OPERATOR_SOURCE); // Ignore alpha channel from FreeRDP + cairo_paint(context); + } + + return TRUE; +} + +static gboolean remmina_rdp_event_delayed_monitor_layout(RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + rfContext* rfi = GET_PLUGIN_DATA(gp); + RemminaPluginRdpEvent rdp_event = { 0 }; + GtkAllocation a; + RemminaFile* remminafile; + gint desktopOrientation, desktopScaleFactor, deviceScaleFactor; + + if (!rfi || !rfi->connected || rfi->is_reconnecting) + return FALSE; + + if (rfi->scale != REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES) + return FALSE; + + rfi->delayed_monitor_layout_handler = 0; + gint gpwidth, gpheight, prevwidth, prevheight; + + if (rfi->dispcontext && rfi->dispcontext->SendMonitorLayout) { + remmina_rdp_settings_get_orientation_scale_prefs(&desktopOrientation, &desktopScaleFactor, &deviceScaleFactor); + gtk_widget_get_allocation(GTK_WIDGET(gp), &a); + gpwidth = a.width; + gpheight = a.height; + prevwidth = remmina_plugin_service->protocol_plugin_get_width(gp); + prevheight = remmina_plugin_service->protocol_plugin_get_height(gp); + remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); + + if ((gpwidth != prevwidth || gpheight != prevheight) && + gpwidth >= 200 && gpwidth < 8192 && + gpheight >= 200 && gpheight < 8192 + ) { + rdp_event.type = REMMINA_RDP_EVENT_TYPE_SEND_MONITOR_LAYOUT; + rdp_event.monitor_layout.width = gpwidth; + rdp_event.monitor_layout.height = gpheight; + rdp_event.monitor_layout.desktopOrientation = desktopOrientation; + rdp_event.monitor_layout.desktopScaleFactor = desktopScaleFactor; + rdp_event.monitor_layout.deviceScaleFactor = deviceScaleFactor; + remmina_rdp_event_event_push(gp, &rdp_event); + remmina_plugin_service->file_set_int(remminafile, "dynamic_resolution_width", gpwidth); + remmina_plugin_service->file_set_int(remminafile, "dynamic_resolution_height", gpheight); + } + } + + return FALSE; +} + +void remmina_rdp_event_send_delayed_monitor_layout(RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + rfContext* rfi = GET_PLUGIN_DATA(gp); + if (!rfi || !rfi->connected || rfi->is_reconnecting) + return; + if (rfi->delayed_monitor_layout_handler) { + g_source_remove(rfi->delayed_monitor_layout_handler); + rfi->delayed_monitor_layout_handler = 0; + } + if (rfi->scale == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES) { + rfi->delayed_monitor_layout_handler = g_timeout_add(500, (GSourceFunc)remmina_rdp_event_delayed_monitor_layout, gp); + } + +} + +static gboolean remmina_rdp_event_on_configure(GtkWidget* widget, GdkEventConfigure* event, RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + /* Called when gp changes its size or position */ + + rfContext* rfi = GET_PLUGIN_DATA(gp); + if (!rfi || !rfi->connected || rfi->is_reconnecting) + return FALSE; + + remmina_rdp_event_update_scale_factor(gp); + + /* If the scaler is not active, schedule a delayed remote resolution change */ + remmina_rdp_event_send_delayed_monitor_layout(gp); + + + return FALSE; +} + +static void remmina_rdp_event_translate_pos(RemminaProtocolWidget* gp, int ix, int iy, UINT16* ox, UINT16* oy) +{ + TRACE_CALL(__func__); + rfContext* rfi = GET_PLUGIN_DATA(gp); + + /* + * Translate a position from local window coordinates (ix,iy) to + * RDP coordinates and put result on (*ox,*uy) + * */ + + if (!rfi || !rfi->connected || rfi->is_reconnecting) + return; + + if ((rfi->scale == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED) && (rfi->scale_width >= 1) && (rfi->scale_height >= 1)) { + *ox = (UINT16)(ix * remmina_plugin_service->protocol_plugin_get_width(gp) / rfi->scale_width); + *oy = (UINT16)(iy * remmina_plugin_service->protocol_plugin_get_height(gp) / rfi->scale_height); + }else { + *ox = (UINT16)ix; + *oy = (UINT16)iy; + } +} + +static void remmina_rdp_event_reverse_translate_pos_reverse(RemminaProtocolWidget* gp, int ix, int iy, int* ox, int* oy) +{ + TRACE_CALL(__func__); + rfContext* rfi = GET_PLUGIN_DATA(gp); + + /* + * Translate a position from RDP coordinates (ix,iy) to + * local window coordinates and put result on (*ox,*uy) + * */ + + if (!rfi || !rfi->connected || rfi->is_reconnecting) + return; + + if ((rfi->scale == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED) && (rfi->scale_width >= 1) && (rfi->scale_height >= 1)) { + *ox = (ix * rfi->scale_width) / remmina_plugin_service->protocol_plugin_get_width(gp); + *oy = (iy * rfi->scale_height) / remmina_plugin_service->protocol_plugin_get_height(gp); + }else { + *ox = ix; + *oy = iy; + } +} + +static gboolean remmina_rdp_event_on_motion(GtkWidget* widget, GdkEventMotion* event, RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + RemminaPluginRdpEvent rdp_event = { 0 }; + + rdp_event.type = REMMINA_RDP_EVENT_TYPE_MOUSE; + rdp_event.mouse_event.flags = PTR_FLAGS_MOVE; + rdp_event.mouse_event.extended = FALSE; + + remmina_rdp_event_translate_pos(gp, event->x, event->y, &rdp_event.mouse_event.x, &rdp_event.mouse_event.y); + remmina_rdp_event_event_push(gp, &rdp_event); + + return TRUE; +} + +static gboolean remmina_rdp_event_on_button(GtkWidget* widget, GdkEventButton* event, RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + gint flag; + gboolean extended = FALSE; + RemminaPluginRdpEvent rdp_event = { 0 }; + + /* We bypass 2button-press and 3button-press events */ + if ((event->type != GDK_BUTTON_PRESS) && (event->type != GDK_BUTTON_RELEASE)) + return TRUE; + + flag = 0; + + switch (event->button) { + case 1: + flag |= PTR_FLAGS_BUTTON1; + break; + case 2: + flag |= PTR_FLAGS_BUTTON3; + break; + case 3: + flag |= PTR_FLAGS_BUTTON2; + break; + case 8: /* back */ + case 97: /* Xming */ + extended = TRUE; + flag |= PTR_XFLAGS_BUTTON1; + break; + case 9: /* forward */ + case 112: /* Xming */ + extended = TRUE; + flag |= PTR_XFLAGS_BUTTON2; + break; + default: + return FALSE; + } + + if (event->type == GDK_BUTTON_PRESS) { + if (extended) + flag |= PTR_XFLAGS_DOWN; + else + flag |= PTR_FLAGS_DOWN; + } + + rdp_event.type = REMMINA_RDP_EVENT_TYPE_MOUSE; + remmina_rdp_event_translate_pos(gp, event->x, event->y, &rdp_event.mouse_event.x, &rdp_event.mouse_event.y); + + if (flag != 0) { + rdp_event.mouse_event.flags = flag; + rdp_event.mouse_event.extended = extended; + remmina_rdp_event_event_push(gp, &rdp_event); + } + + return TRUE; +} + +static gboolean remmina_rdp_event_on_scroll(GtkWidget* widget, GdkEventScroll* event, RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + gint flag; + RemminaPluginRdpEvent rdp_event = { 0 }; + + flag = 0; + rdp_event.type = REMMINA_RDP_EVENT_TYPE_MOUSE; + + switch (event->direction) { + case GDK_SCROLL_UP: + flag = PTR_FLAGS_WHEEL | 0x0078; + break; + + case GDK_SCROLL_DOWN: + flag = PTR_FLAGS_WHEEL | PTR_FLAGS_WHEEL_NEGATIVE | 0x0088; + break; + +#ifdef GDK_SCROLL_SMOOTH + case GDK_SCROLL_SMOOTH: + if (event->delta_y < 0) + flag = PTR_FLAGS_WHEEL | 0x0078; + if (event->delta_y > 0) + flag = PTR_FLAGS_WHEEL | PTR_FLAGS_WHEEL_NEGATIVE | 0x0088; + if (!flag) + return FALSE; + break; +#endif + + default: + return FALSE; + } + + rdp_event.mouse_event.flags = flag; + rdp_event.mouse_event.extended = FALSE; + remmina_rdp_event_translate_pos(gp, event->x, event->y, &rdp_event.mouse_event.x, &rdp_event.mouse_event.y); + remmina_rdp_event_event_push(gp, &rdp_event); + + return TRUE; +} + +static gboolean remmina_rdp_event_on_key(GtkWidget* widget, GdkEventKey* event, RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + GdkDisplay* display; + guint32 unicode_keyval; + rfContext* rfi = GET_PLUGIN_DATA(gp); + RemminaPluginRdpEvent rdp_event; + DWORD scancode = 0; + + if (!rfi || !rfi->connected || rfi->is_reconnecting) + return FALSE; + +#ifdef ENABLE_GTK_INSPECTOR_KEY + /* GTK inspector key is propagated up. Disabled by default. + * enable it by defining ENABLE_GTK_INSPECTOR_KEY */ + if ( ( event->state & GDK_CONTROL_MASK ) != 0 && ( event->keyval == GDK_KEY_I || event->keyval == GDK_KEY_D ) ) { + return FALSE; + } +#endif + + rdp_event.type = REMMINA_RDP_EVENT_TYPE_SCANCODE; + rdp_event.key_event.up = (event->type == GDK_KEY_PRESS ? False : True); + rdp_event.key_event.extended = False; + + switch (event->keyval) { + case GDK_KEY_Pause: + /* + * See https://msdn.microsoft.com/en-us/library/cc240584.aspx + * 2.2.8.1.1.3.1.1.1 Keyboard Event (TS_KEYBOARD_EVENT) + * for pause key management + */ + rdp_event.key_event.key_code = 0x1D; + rdp_event.key_event.up = False; + remmina_rdp_event_event_push(gp, &rdp_event); + rdp_event.key_event.key_code = 0x45; + rdp_event.key_event.up = False; + remmina_rdp_event_event_push(gp, &rdp_event); + rdp_event.key_event.key_code = 0x1D; + rdp_event.key_event.up = True; + remmina_rdp_event_event_push(gp, &rdp_event); + rdp_event.key_event.key_code = 0x45; + rdp_event.key_event.up = True; + remmina_rdp_event_event_push(gp, &rdp_event); + break; + + default: + if (!rfi->use_client_keymap) { + scancode = freerdp_keyboard_get_rdp_scancode_from_x11_keycode(event->hardware_keycode); + rdp_event.key_event.key_code = scancode & 0xFF; + rdp_event.key_event.extended = scancode & 0x100; + if (rdp_event.key_event.key_code) { + remmina_rdp_event_event_push(gp, &rdp_event); + keypress_list_add(gp, rdp_event); + } + } else { + display = gtk_widget_get_display(widget); + unicode_keyval = gdk_keyval_to_unicode(event->keyval); + /* Decide when whe should send a keycode or a unicode character. + * - All non char keys (shift, alt, win) should be sent as keycode + * - Space should be sent as keycode (see issue #1364) + * - All special keys (F1-F10, numeric pad, home/end/arrows/pgup/pgdn/ins/del) keycode + * - All key pressed while CTRL or ALT or WIN is down are not decoded by gdk_keyval_to_unicode(), so send it as keycode + * - All keycodes not translatable to unicode chars, as keycode + * - The rest as unicode char + */ + if (event->keyval >= 0xfe00 || // arrows, shift, alt, Fn, num keypad... + event->hardware_keycode == 0x41 || // space bar + unicode_keyval == 0 || // impossible to translate + (event->state & (GDK_MOD1_MASK | GDK_CONTROL_MASK | GDK_SUPER_MASK)) != 0 // a modifier not recognized by gdk_keyval_to_unicode() + ) { + scancode = freerdp_keyboard_get_rdp_scancode_from_x11_keycode(event->hardware_keycode); + rdp_event.key_event.key_code = scancode & 0xFF; + rdp_event.key_event.extended = scancode & 0x100; + if (rdp_event.key_event.key_code) { + remmina_rdp_event_event_push(gp, &rdp_event); + keypress_list_add(gp, rdp_event); + } + } else { + rdp_event.type = REMMINA_RDP_EVENT_TYPE_SCANCODE_UNICODE; + rdp_event.key_event.unicode_code = unicode_keyval; + rdp_event.key_event.extended = False; + remmina_rdp_event_event_push(gp, &rdp_event); + keypress_list_add(gp, rdp_event); + } + } + break; + } + + return TRUE; +} + +gboolean remmina_rdp_event_on_clipboard(GtkClipboard *gtkClipboard, GdkEvent *event, RemminaProtocolWidget *gp) +{ + /* Signal handler for GTK clipboard owner-change */ + TRACE_CALL(__func__); + RemminaPluginRdpEvent rdp_event = { 0 }; + CLIPRDR_FORMAT_LIST* pFormatList; + + /* Usually "owner-change" is fired when a user pres "COPY" on the client + * OR when this plugin calls gtk_clipboard_set_with_owner() + * after receivina a RDP server format list in remmina_rdp_cliprdr_server_format_list() + * In the latter case, we must ignore owner change */ + + if (gtk_clipboard_get_owner(gtkClipboard) != (GObject*)gp) { + pFormatList = remmina_rdp_cliprdr_get_client_format_list(gp); + rdp_event.type = REMMINA_RDP_EVENT_TYPE_CLIPBOARD_SEND_CLIENT_FORMAT_LIST; + rdp_event.clipboard_formatlist.pFormatList = pFormatList; + remmina_rdp_event_event_push(gp, &rdp_event); + } + return TRUE; +} + +void remmina_rdp_event_init(RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + gchar* s; + gint flags; + rfContext* rfi = GET_PLUGIN_DATA(gp); + GtkClipboard* clipboard; + + if (!rfi) return; + + rfi->drawing_area = gtk_drawing_area_new(); + gtk_widget_show(rfi->drawing_area); + gtk_container_add(GTK_CONTAINER(gp), rfi->drawing_area); + + gtk_widget_add_events(rfi->drawing_area, GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK + | GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_SCROLL_MASK | GDK_FOCUS_CHANGE_MASK); + gtk_widget_set_can_focus(rfi->drawing_area, TRUE); + + remmina_plugin_service->protocol_plugin_register_hostkey(gp, rfi->drawing_area); + + s = remmina_plugin_service->pref_get_value("rdp_use_client_keymap"); + rfi->use_client_keymap = (s && s[0] == '1' ? TRUE : FALSE); + g_free(s); + + g_signal_connect(G_OBJECT(rfi->drawing_area), "draw", + G_CALLBACK(remmina_rdp_event_on_draw), gp); + g_signal_connect(G_OBJECT(rfi->drawing_area), "configure-event", + G_CALLBACK(remmina_rdp_event_on_configure), gp); + g_signal_connect(G_OBJECT(rfi->drawing_area), "motion-notify-event", + G_CALLBACK(remmina_rdp_event_on_motion), gp); + g_signal_connect(G_OBJECT(rfi->drawing_area), "button-press-event", + G_CALLBACK(remmina_rdp_event_on_button), gp); + g_signal_connect(G_OBJECT(rfi->drawing_area), "button-release-event", + G_CALLBACK(remmina_rdp_event_on_button), gp); + g_signal_connect(G_OBJECT(rfi->drawing_area), "scroll-event", + G_CALLBACK(remmina_rdp_event_on_scroll), gp); + g_signal_connect(G_OBJECT(rfi->drawing_area), "key-press-event", + G_CALLBACK(remmina_rdp_event_on_key), gp); + g_signal_connect(G_OBJECT(rfi->drawing_area), "key-release-event", + G_CALLBACK(remmina_rdp_event_on_key), gp); + g_signal_connect(G_OBJECT(rfi->drawing_area), "focus-in-event", + G_CALLBACK(remmina_rdp_event_on_focus_in), gp); + + RemminaFile* remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); + if (!remmina_plugin_service->file_get_int(remminafile, "disableclipboard", FALSE)) { + clipboard = gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD); + rfi->clipboard.clipboard_handler = g_signal_connect(clipboard, "owner-change", G_CALLBACK(remmina_rdp_event_on_clipboard), gp); + } + + rfi->pressed_keys = g_array_new(FALSE, TRUE, sizeof(RemminaPluginRdpEvent)); + rfi->event_queue = g_async_queue_new_full(g_free); + rfi->ui_queue = g_async_queue_new(); + pthread_mutex_init(&rfi->ui_queue_mutex, NULL); + + if (pipe(rfi->event_pipe)) { + g_print("Error creating pipes.\n"); + rfi->event_pipe[0] = -1; + rfi->event_pipe[1] = -1; + rfi->event_handle = NULL; + }else { + flags = fcntl(rfi->event_pipe[0], F_GETFL, 0); + fcntl(rfi->event_pipe[0], F_SETFL, flags | O_NONBLOCK); + rfi->event_handle = CreateFileDescriptorEvent(NULL, FALSE, FALSE, rfi->event_pipe[0], WINPR_FD_READ); + if (!rfi->event_handle) { + g_print("CreateFileDescriptorEvent() failed\n"); + } + } + + rfi->object_table = g_hash_table_new_full(NULL, NULL, NULL, g_free); + + rfi->display = gdk_display_get_default(); + rfi->bpp = gdk_visual_get_best_depth(); +} + + + +void remmina_rdp_event_free_event(RemminaProtocolWidget* gp, RemminaPluginRdpUiObject* obj) +{ + TRACE_CALL(__func__); + rfContext* rfi = GET_PLUGIN_DATA(gp); + + switch (obj->type) { + case REMMINA_RDP_UI_RFX: + rfx_message_free(rfi->rfx_context, obj->rfx.message); + break; + + case REMMINA_RDP_UI_NOCODEC: + free(obj->nocodec.bitmap); + break; + + default: + break; + } + + g_free(obj); +} + + +void remmina_rdp_event_uninit(RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + rfContext* rfi = GET_PLUGIN_DATA(gp); + RemminaPluginRdpUiObject* ui; + + if (!rfi) return; + + /* unregister the clipboard monitor */ + if (rfi->clipboard.clipboard_handler) { + g_signal_handler_disconnect(G_OBJECT(gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD)), rfi->clipboard.clipboard_handler); + rfi->clipboard.clipboard_handler = 0; + } + if (rfi->delayed_monitor_layout_handler) { + g_source_remove(rfi->delayed_monitor_layout_handler); + rfi->delayed_monitor_layout_handler = 0; + } + if (rfi->ui_handler) { + g_source_remove(rfi->ui_handler); + rfi->ui_handler = 0; + } + while ((ui = (RemminaPluginRdpUiObject*)g_async_queue_try_pop(rfi->ui_queue)) != NULL) { + remmina_rdp_event_free_event(gp, ui); + } + if (rfi->surface) { + cairo_surface_destroy(rfi->surface); + rfi->surface = NULL; + } + + g_hash_table_destroy(rfi->object_table); + + g_array_free(rfi->pressed_keys, TRUE); + g_async_queue_unref(rfi->event_queue); + rfi->event_queue = NULL; + g_async_queue_unref(rfi->ui_queue); + rfi->ui_queue = NULL; + pthread_mutex_destroy(&rfi->ui_queue_mutex); + + if (rfi->event_handle) { + CloseHandle(rfi->event_handle); + rfi->event_handle = NULL; + } + + close(rfi->event_pipe[0]); + close(rfi->event_pipe[1]); +} + +static void remmina_rdp_event_create_cairo_surface(rfContext* rfi) +{ + int stride; + rdpGdi* gdi; + + gdi = ((rdpContext *)rfi)->gdi; + if (!rfi || !gdi) + return; + + if (rfi->surface) { + cairo_surface_destroy(rfi->surface); + rfi->surface = NULL; + } + stride = cairo_format_stride_for_width(rfi->cairo_format, rfi->width); + rfi->surface = cairo_image_surface_create_for_data((unsigned char*)gdi->primary_buffer, rfi->cairo_format, rfi->width, rfi->height, stride); +} + +void remmina_rdp_event_update_scale(RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + gint width, height; + RemminaFile* remminafile; + rdpGdi* gdi; + rfContext* rfi = GET_PLUGIN_DATA(gp); + + remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); + width = remmina_plugin_service->protocol_plugin_get_width(gp); + height = remmina_plugin_service->protocol_plugin_get_height(gp); + + rfi->scale = remmina_plugin_service->remmina_protocol_widget_get_current_scale_mode(gp); + + /* See if we also must rellocate rfi->surface with different width and height, + * this usually happens after a DesktopResize RDP event*/ + if ( rfi->surface && (width != cairo_image_surface_get_width(rfi->surface) || + height != cairo_image_surface_get_height(rfi->surface) )) { + /* Destroys and recreate rfi->surface with new width and height, + * calls gdi_resize and save new gdi->primary buffer pointer */ + if (rfi->surface) { + cairo_surface_destroy(rfi->surface); + rfi->surface = NULL; + } + rfi->width = width; + rfi->height = height; + gdi = ((rdpContext*)rfi)->gdi; + gdi_resize(gdi, width, height); + rfi->primary_buffer = gdi->primary_buffer; + remmina_rdp_event_create_cairo_surface(rfi); + } + + remmina_rdp_event_update_scale_factor(gp); + + if (rfi->scale == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED || rfi->scale == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES) { + /* In scaled mode and autores mode, drawing_area will get its dimensions from its parent */ + gtk_widget_set_size_request(rfi->drawing_area, -1, -1 ); + }else { + /* In non scaled mode, the plugins forces dimensions of drawing area */ + gtk_widget_set_size_request(rfi->drawing_area, width, height); + } + remmina_plugin_service->protocol_plugin_emit_signal(gp, "update-align"); +} + +static void remmina_rdp_event_connected(RemminaProtocolWidget* gp, RemminaPluginRdpUiObject* ui) +{ + TRACE_CALL(__func__); + rfContext* rfi = GET_PLUGIN_DATA(gp); + + remmina_plugin_service->protocol_plugin_emit_signal(gp, "connect"); + gtk_widget_realize(rfi->drawing_area); + + remmina_rdp_event_create_cairo_surface(rfi); + gtk_widget_queue_draw_area(rfi->drawing_area, 0, 0, rfi->width, rfi->height); + + remmina_rdp_event_update_scale(gp); +} + +static void remmina_rdp_event_reconnect_progress(RemminaProtocolWidget* gp, RemminaPluginRdpUiObject* ui) +{ + TRACE_CALL(__func__); + rfContext* rfi = GET_PLUGIN_DATA(gp); + gdk_window_invalidate_rect(gtk_widget_get_window(rfi->drawing_area), NULL, TRUE); +} + +static BOOL remmina_rdp_event_create_cursor(RemminaProtocolWidget* gp, RemminaPluginRdpUiObject* ui) +{ + TRACE_CALL(__func__); + GdkPixbuf* pixbuf; + rfContext* rfi = GET_PLUGIN_DATA(gp); + rdpPointer* pointer = (rdpPointer*)ui->cursor.pointer; + cairo_surface_t* surface; + UINT8* data = malloc(pointer->width * pointer->height * 4); + + if (freerdp_image_copy_from_pointer_data( + (BYTE*)data, PIXEL_FORMAT_BGRA32, + pointer->width * 4, 0, 0, pointer->width, pointer->height, + pointer->xorMaskData, pointer->lengthXorMask, + pointer->andMaskData, pointer->lengthAndMask, + pointer->xorBpp, &(ui->cursor.context->gdi->palette)) < 0) { + free(data); + return FALSE; + } + + surface = cairo_image_surface_create_for_data(data, CAIRO_FORMAT_ARGB32, pointer->width, pointer->height, cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, pointer->width)); + pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, pointer->width, pointer->height); + cairo_surface_destroy(surface); + free(data); + ((rfPointer*)ui->cursor.pointer)->cursor = gdk_cursor_new_from_pixbuf(rfi->display, pixbuf, pointer->xPos, pointer->yPos); + g_object_unref(pixbuf); + + return TRUE; +} + +static void remmina_rdp_event_free_cursor(RemminaProtocolWidget* gp, RemminaPluginRdpUiObject* ui) +{ + TRACE_CALL(__func__); + g_object_unref(ui->cursor.pointer->cursor); + ui->cursor.pointer->cursor = NULL; +} + +static BOOL remmina_rdp_event_set_pointer_position(RemminaProtocolWidget *gp, gint x, gint y) +{ + TRACE_CALL(__func__); + GdkWindow *w, *nw; + gint nx, ny, wx, wy; +#if GTK_CHECK_VERSION(3, 20, 0) + GdkSeat *seat; +#else + GdkDeviceManager *manager; +#endif + GdkDevice *dev; + rfContext* rfi = GET_PLUGIN_DATA(gp); + + if (rfi == NULL) + return FALSE; + + w = gtk_widget_get_window(rfi->drawing_area); +#if GTK_CHECK_VERSION(3, 20, 0) + seat = gdk_display_get_default_seat(gdk_display_get_default()); + dev = gdk_seat_get_pointer(seat); +#else + manager = gdk_display_get_device_manager(gdk_display_get_default()); + dev = gdk_device_manager_get_client_pointer(manager); +#endif + + nw = gdk_device_get_window_at_position(dev, NULL, NULL); + + if (nw == w) { + nx = 0; + ny = 0; + remmina_rdp_event_reverse_translate_pos_reverse(gp, x, y, &nx, &ny); + gdk_window_get_root_coords(w, nx, ny, &wx, &wy); + gdk_device_warp(dev, gdk_window_get_screen(w), wx, wy); + } + return TRUE; +} + +static void remmina_rdp_event_cursor(RemminaProtocolWidget* gp, RemminaPluginRdpUiObject* ui) +{ + TRACE_CALL(__func__); + rfContext* rfi = GET_PLUGIN_DATA(gp); + + switch (ui->cursor.type) { + case REMMINA_RDP_POINTER_NEW: + ui->retval = remmina_rdp_event_create_cursor(gp, ui) ? 1 : 0; + break; + + case REMMINA_RDP_POINTER_FREE: + remmina_rdp_event_free_cursor(gp, ui); + break; + + case REMMINA_RDP_POINTER_SET: + gdk_window_set_cursor(gtk_widget_get_window(rfi->drawing_area), ui->cursor.pointer->cursor); + ui->retval = 1; + break; + + case REMMINA_RDP_POINTER_SETPOS: + ui->retval = remmina_rdp_event_set_pointer_position(gp, ui->pos.x, ui->pos.y) ? 1 : 0; + break; + + case REMMINA_RDP_POINTER_NULL: + gdk_window_set_cursor(gtk_widget_get_window(rfi->drawing_area), + gdk_cursor_new_for_display(gdk_display_get_default(), + GDK_BLANK_CURSOR)); + ui->retval = 1; + break; + + case REMMINA_RDP_POINTER_DEFAULT: + gdk_window_set_cursor(gtk_widget_get_window(rfi->drawing_area), NULL); + ui->retval = 1; + break; + } +} + +static void remmina_rdp_ui_event_update_scale(RemminaProtocolWidget* gp, RemminaPluginRdpUiObject* ui) +{ + TRACE_CALL(__func__); + remmina_rdp_event_update_scale(gp); +} + +void remmina_rdp_event_unfocus(RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + remmina_rdp_event_release_all_keys(gp); +} + +static void remmina_rdp_event_process_event(RemminaProtocolWidget* gp, RemminaPluginRdpUiObject* ui) +{ + TRACE_CALL(__func__); + switch (ui->event.type) { + case REMMINA_RDP_UI_EVENT_UPDATE_SCALE: + remmina_rdp_ui_event_update_scale(gp, ui); + break; + } +} + +static void remmina_rdp_event_process_ui_event(RemminaProtocolWidget* gp, RemminaPluginRdpUiObject* ui) +{ + TRACE_CALL(__func__); + switch (ui->type) { + case REMMINA_RDP_UI_UPDATE_REGION: + remmina_rdp_event_update_region(gp, ui); + break; + + case REMMINA_RDP_UI_CONNECTED: + remmina_rdp_event_connected(gp, ui); + break; + + case REMMINA_RDP_UI_RECONNECT_PROGRESS: + remmina_rdp_event_reconnect_progress(gp, ui); + break; + + case REMMINA_RDP_UI_CURSOR: + remmina_rdp_event_cursor(gp, ui); + break; + + case REMMINA_RDP_UI_CLIPBOARD: + remmina_rdp_event_process_clipboard(gp, ui); + break; + + case REMMINA_RDP_UI_EVENT: + remmina_rdp_event_process_event(gp, ui); + break; + + default: + break; + } +} + +static gboolean remmina_rdp_event_process_ui_queue(RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + + rfContext* rfi = GET_PLUGIN_DATA(gp); + RemminaPluginRdpUiObject* ui; + + pthread_mutex_lock(&rfi->ui_queue_mutex); + ui = (RemminaPluginRdpUiObject*)g_async_queue_try_pop(rfi->ui_queue); + if (ui) { + pthread_mutex_lock(&ui->sync_wait_mutex); + if (!rfi->thread_cancelled) { + remmina_rdp_event_process_ui_event(gp, ui); + } + // Should we signal the caller thread to unlock ? + if (ui->sync) { + ui->complete = TRUE; + pthread_cond_signal(&ui->sync_wait_cond); + pthread_mutex_unlock(&ui->sync_wait_mutex); + + } else { + remmina_rdp_event_free_event(gp, ui); + } + + pthread_mutex_unlock(&rfi->ui_queue_mutex); + return TRUE; + }else { + rfi->ui_handler = 0; + pthread_mutex_unlock(&rfi->ui_queue_mutex); + return FALSE; + } +} + +static void remmina_rdp_event_queue_ui(RemminaProtocolWidget* gp, RemminaPluginRdpUiObject* ui) +{ + TRACE_CALL(__func__); + rfContext* rfi = GET_PLUGIN_DATA(gp); + gboolean ui_sync_save; + int oldcanceltype; + + if (rfi->thread_cancelled) { + return; + } + + if (remmina_plugin_service->is_main_thread()) { + remmina_rdp_event_process_ui_event(gp, ui); + return; + } + + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldcanceltype); + + pthread_mutex_lock(&rfi->ui_queue_mutex); + + ui_sync_save = ui->sync; + ui->complete = FALSE; + + if (ui_sync_save) { + pthread_mutex_init(&ui->sync_wait_mutex, NULL); + pthread_cond_init(&ui->sync_wait_cond, NULL); + } + + ui->complete = FALSE; + + g_async_queue_push(rfi->ui_queue, ui); + + if (!rfi->ui_handler) { + rfi->ui_handler = IDLE_ADD((GSourceFunc)remmina_rdp_event_process_ui_queue, gp); + } + + if (ui_sync_save) { + /* Wait for main thread function completion before returning */ + pthread_mutex_lock(&ui->sync_wait_mutex); + pthread_mutex_unlock(&rfi->ui_queue_mutex); + while (!ui->complete) { + pthread_cond_wait(&ui->sync_wait_cond, &ui->sync_wait_mutex); + } + pthread_cond_destroy(&ui->sync_wait_cond); + pthread_mutex_destroy(&ui->sync_wait_mutex); + } else { + pthread_mutex_unlock(&rfi->ui_queue_mutex); + } + pthread_setcanceltype(oldcanceltype, NULL); +} + +void remmina_rdp_event_queue_ui_async(RemminaProtocolWidget* gp, RemminaPluginRdpUiObject* ui) +{ + ui->sync = FALSE; + remmina_rdp_event_queue_ui(gp, ui); +} + +int remmina_rdp_event_queue_ui_sync_retint(RemminaProtocolWidget* gp, RemminaPluginRdpUiObject* ui) +{ + TRACE_CALL(__func__); + int retval; + ui->sync = TRUE; + remmina_rdp_event_queue_ui(gp, ui); + retval = ui->retval; + remmina_rdp_event_free_event(gp, ui); + return retval; +} + +void *remmina_rdp_event_queue_ui_sync_retptr(RemminaProtocolWidget* gp, RemminaPluginRdpUiObject* ui) +{ + TRACE_CALL(__func__); + void *rp; + ui->sync = TRUE; + remmina_rdp_event_queue_ui(gp, ui); + rp = ui->retptr; + remmina_rdp_event_free_event(gp, ui); + return rp; +} diff --git a/plugins/rdp/rdp_event.h b/plugins/rdp/rdp_event.h new file mode 100644 index 000000000..f671ef380 --- /dev/null +++ b/plugins/rdp/rdp_event.h @@ -0,0 +1,52 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2010-2011 Vic Lee + * Copyright (C) 2017 Antenore Gatta, Giovanni Panozzo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + * + */ + +#pragma once + +G_BEGIN_DECLS + + +void remmina_rdp_event_init(RemminaProtocolWidget* gp); +void remmina_rdp_event_uninit(RemminaProtocolWidget* gp); +void remmina_rdp_event_update_scale(RemminaProtocolWidget* gp); +void remmina_rdp_event_unfocus(RemminaProtocolWidget* gp); +void remmina_rdp_event_send_delayed_monitor_layout(RemminaProtocolWidget* gp); +void remmina_rdp_event_update_rect(RemminaProtocolWidget* gp, gint x, gint y, gint w, gint h); +void remmina_rdp_event_queue_ui_async(RemminaProtocolWidget* gp, RemminaPluginRdpUiObject* ui); +int remmina_rdp_event_queue_ui_sync_retint(RemminaProtocolWidget* gp, RemminaPluginRdpUiObject* ui); +void *remmina_rdp_event_queue_ui_sync_retptr(RemminaProtocolWidget* gp, RemminaPluginRdpUiObject* ui); + +G_END_DECLS + diff --git a/plugins/rdp/rdp_file.c b/plugins/rdp/rdp_file.c new file mode 100644 index 000000000..8f85d4934 --- /dev/null +++ b/plugins/rdp/rdp_file.c @@ -0,0 +1,307 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2010-2011 Vic Lee + * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo + * Copyright (C) 2016-2017 Antenore Gatta, Giovanni Panozzo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + * + */ + +#include "common/remmina_plugin.h" +#include "rdp_plugin.h" +#include "rdp_file.h" + +gboolean remmina_rdp_file_import_test(const gchar* from_file) +{ + TRACE_CALL(__func__); + gchar* ext; + + ext = strrchr(from_file, '.'); + + if (!ext) + return FALSE; + + ext++; + + if (g_strcmp0(ext, "RDP") == 0) + return TRUE; + + if (g_strcmp0(ext, "rdp") == 0) + return TRUE; + + return FALSE; +} + +static void remmina_rdp_file_import_field(RemminaFile* remminafile, const gchar* key, const gchar* value) +{ + TRACE_CALL(__func__); + if (g_strcmp0(key, "desktopwidth") == 0) { + remmina_plugin_service->file_set_string(remminafile, "resolution_width", value); + }else if (g_strcmp0(key, "desktopheight") == 0) { + remmina_plugin_service->file_set_string(remminafile, "resolution_height", value); + }else if (g_strcmp0(key, "session bpp") == 0) { + remmina_plugin_service->file_set_string(remminafile, "colordepth", value); + }else if (g_strcmp0(key, "keyboardhook") == 0) { + remmina_plugin_service->file_set_int(remminafile, "keyboard_grab", (atoi(value) == 1)); + }else if (g_strcmp0(key, "full address") == 0) { + remmina_plugin_service->file_set_string(remminafile, "server", value); + }else if (g_strcmp0(key, "audiomode") == 0) { + switch (atoi(value)) { + case 0: + remmina_plugin_service->file_set_string(remminafile, "sound", "local"); + break; + case 1: + remmina_plugin_service->file_set_string(remminafile, "sound", "remote"); + break; + } + }else if (g_strcmp0(key, "microphone") == 0) { + remmina_plugin_service->file_set_int(remminafile, "microphone", (atoi(value) == 1)); + } else if (g_strcmp0(key, "redirectprinters") == 0) { + remmina_plugin_service->file_set_int(remminafile, "shareprinter", (atoi(value) == 1)); + }else if (g_strcmp0(key, "redirectsmartcard") == 0) { + remmina_plugin_service->file_set_int(remminafile, "sharesmartcard", (atoi(value) == 1)); + }else if (g_strcmp0(key, "redirectclipboard") == 0) { + remmina_plugin_service->file_set_int(remminafile, "disableclipboard", (atoi(value) != 1)); + }else if (g_strcmp0(key, "alternate shell") == 0) { + remmina_plugin_service->file_set_string(remminafile, "exec", value); + }else if (g_strcmp0(key, "shell working directory") == 0) { + remmina_plugin_service->file_set_string(remminafile, "execpath", value); + }else if (g_strcmp0(key, "loadbalanceinfo") == 0) { + remmina_plugin_service->file_set_string(remminafile, "loadbalanceinfo", value); + }else if (g_strcmp0(key, "gatewayhostname") == 0) { + remmina_plugin_service->file_set_string(remminafile, "gateway_server", value); + }else if (g_strcmp0(key, "gatewayusagemethod") == 0) { + remmina_plugin_service->file_set_string(remminafile, "gatewayusagemethod", value); + }else if (g_strcmp0(key, "gatewaycredentialssource") == 0) { + remmina_plugin_service->file_set_string(remminafile, "gatewaycredentialssource", value); + }else if (g_strcmp0(key, "gatewayprofileusagemethod") == 0) { + remmina_plugin_service->file_set_string(remminafile, "gatewayprofileusagemethod", value); + } + /* tsclient fields, import only */ + else if (g_strcmp0(key, "client hostname") == 0) { + remmina_plugin_service->file_set_string(remminafile, "clientname", value); + }else if (g_strcmp0(key, "domain") == 0) { + remmina_plugin_service->file_set_string(remminafile, "domain", value); + }else if (g_strcmp0(key, "username") == 0) { + remmina_plugin_service->file_set_string(remminafile, "username", value); + }else if (g_strcmp0(key, "password") == 0) { + remmina_plugin_service->file_set_string(remminafile, "password", value); + } +} + +static RemminaFile* remmina_rdp_file_import_channel(GIOChannel* channel) +{ + TRACE_CALL(__func__); + gchar* p; + const gchar* enc; + gchar* line = NULL; + GError* error = NULL; + gsize bytes_read = 0; + RemminaFile* remminafile; + guchar magic[2] = { 0 }; + + if (g_io_channel_set_encoding(channel, NULL, &error) != G_IO_STATUS_NORMAL) { + g_print("g_io_channel_set_encoding: %s\n", error->message); + return NULL; + } + + /* Try to detect the UTF-16 encoding */ + if (g_io_channel_read_chars(channel, (gchar*)magic, 2, &bytes_read, &error) != G_IO_STATUS_NORMAL) { + g_print("g_io_channel_read_chars: %s\n", error->message); + return NULL; + } + + if (magic[0] == 0xFF && magic[1] == 0xFE) { + enc = "UTF-16LE"; + }else if (magic[0] == 0xFE && magic[1] == 0xFF) { + enc = "UTF-16BE"; + }else { + enc = "UTF-8"; + if (g_io_channel_seek_position(channel, 0, G_SEEK_SET, &error) != G_IO_STATUS_NORMAL) { + g_print("g_io_channel_seek: failed\n"); + return NULL; + } + } + + if (g_io_channel_set_encoding(channel, enc, &error) != G_IO_STATUS_NORMAL) { + g_print("g_io_channel_set_encoding: %s\n", error->message); + return NULL; + } + + remminafile = remmina_plugin_service->file_new(); + + while (g_io_channel_read_line(channel, &line, NULL, &bytes_read, &error) == G_IO_STATUS_NORMAL) { + if (line == NULL) + break; + + line[bytes_read] = '\0'; + p = strchr(line, ':'); + + if (p) { + *p++ = '\0'; + p = strchr(p, ':'); + + if (p) { + p++; + remmina_rdp_file_import_field(remminafile, line, p); + } + } + + g_free(line); + } + + remmina_plugin_service->file_set_string(remminafile, "name", + remmina_plugin_service->file_get_string(remminafile, "server")); + remmina_plugin_service->file_set_string(remminafile, "protocol", "RDP"); + + return remminafile; +} + +RemminaFile* remmina_rdp_file_import(const gchar* from_file) +{ + TRACE_CALL(__func__); + GIOChannel* channel; + GError* error = NULL; + RemminaFile* remminafile; + + channel = g_io_channel_new_file(from_file, "r", &error); + + if (channel == NULL) { + g_print("Failed to import %s: %s\n", from_file, error->message); + return NULL; + } + + remminafile = remmina_rdp_file_import_channel(channel); + g_io_channel_shutdown(channel, TRUE, &error); + + return remminafile; +} + +gboolean remmina_rdp_file_export_test(RemminaFile* remminafile) +{ + TRACE_CALL(__func__); + if (g_strcmp0(remmina_plugin_service->file_get_string(remminafile, "protocol"), "RDP") == 0) + return TRUE; + + return FALSE; +} + +gboolean remmina_rdp_file_export_channel(RemminaFile* remminafile, FILE* fp) +{ + TRACE_CALL(__func__); + const gchar* cs; + int w, h; + + fprintf(fp, "screen mode id:i:2\r\n"); + w = remmina_plugin_service->file_get_int(remminafile, "resolution_width", -1); + h = remmina_plugin_service->file_get_int(remminafile, "resolution_height", -1); + if ( w > 0 && h > 0 ) { + fprintf(fp, "desktopwidth:i:%d\r\n", w); + fprintf(fp, "desktopheight:i:%d\r\n", h); + } + + fprintf(fp, "session bpp:i:%i\r\n", remmina_plugin_service->file_get_int(remminafile, "colordepth", 8)); + //fprintf(fp, "winposstr:s:0,1,123,34,931,661\r\n"); + fprintf(fp, "compression:i:1\r\n"); + fprintf(fp, "keyboardhook:i:2\r\n"); + fprintf(fp, "displayconnectionbar:i:1\r\n"); + fprintf(fp, "disable wallpaper:i:1\r\n"); + fprintf(fp, "disable full window drag:i:1\r\n"); + fprintf(fp, "allow desktop composition:i:0\r\n"); + fprintf(fp, "allow font smoothing:i:0\r\n"); + fprintf(fp, "disable menu anims:i:1\r\n"); + fprintf(fp, "disable themes:i:0\r\n"); + fprintf(fp, "disable cursor setting:i:0\r\n"); + fprintf(fp, "bitmapcachepersistenable:i:1\r\n"); + cs = remmina_plugin_service->file_get_string(remminafile, "server"); + fprintf(fp, "full address:s:%s\r\n", cs ? cs : "" ); + if (g_strcmp0(remmina_plugin_service->file_get_string(remminafile, "sound"), "local") == 0) + fprintf(fp, "audiomode:i:0\r\n"); + else if (g_strcmp0(remmina_plugin_service->file_get_string(remminafile, "sound"), "remote") == 0) + fprintf(fp, "audiomode:i:1\r\n"); + else + fprintf(fp, "audiomode:i:2\r\n"); + fprintf(fp, "microphone:i:%i\r\n", remmina_plugin_service->file_get_int(remminafile, "microphone", FALSE) ? 1 : 0); + fprintf(fp, "redirectprinters:i:%i\r\n", remmina_plugin_service->file_get_int(remminafile, "shareprinter", FALSE) ? 1 : 0); + fprintf(fp, "redirectsmartcard:i:%i\r\n", remmina_plugin_service->file_get_int(remminafile, "sharesmartcard", FALSE) ? 1 : 0); + fprintf(fp, "redirectcomports:i:0\r\n"); + fprintf(fp, "redirectsmartcards:i:0\r\n"); + fprintf(fp, "redirectclipboard:i:1\r\n"); + fprintf(fp, "redirectposdevices:i:0\r\n"); + fprintf(fp, "autoreconnection enabled:i:1\r\n"); + fprintf(fp, "authentication level:i:0\r\n"); + fprintf(fp, "prompt for credentials:i:1\r\n"); + fprintf(fp, "negotiate security layer:i:1\r\n"); + fprintf(fp, "remoteapplicationmode:i:0\r\n"); + cs = remmina_plugin_service->file_get_string(remminafile, "exec"); + fprintf(fp, "alternate shell:s:%s\r\n", cs ? cs : ""); + cs = remmina_plugin_service->file_get_string(remminafile, "execpath"); + fprintf(fp, "shell working directory:s:%s\r\n", cs ? cs : ""); + fprintf(fp, "gatewayhostname:s:\r\n"); + fprintf(fp, "gatewayusagemethod:i:4\r\n"); + fprintf(fp, "gatewaycredentialssource:i:4\r\n"); + fprintf(fp, "gatewayprofileusagemethod:i:0\r\n"); + fprintf(fp, "precommand:s:\r\n"); + fprintf(fp, "promptcredentialonce:i:1\r\n"); + fprintf(fp, "drivestoredirect:s:\r\n"); + + return TRUE; +} + +gboolean remmina_rdp_file_export(RemminaFile* remminafile, const gchar* to_file) +{ + TRACE_CALL(__func__); + FILE* fp; + gchar* p; + gboolean ret; + + p = strrchr(to_file, '.'); + + if (p && (g_strcmp0(p + 1, "rdp") == 0 || g_strcmp0(p + 1, "RDP") == 0)) { + p = g_strdup(to_file); + }else { + p = g_strdup_printf("%s.rdp", to_file); + } + + fp = g_fopen(p, "w+"); + + if (fp == NULL) { + g_print("Failed to export %s\n", p); + g_free(p); + return FALSE; + } + + g_free(p); + ret = remmina_rdp_file_export_channel(remminafile, fp); + fclose(fp); + + return ret; +} + diff --git a/plugins/rdp/rdp_file.h b/plugins/rdp/rdp_file.h new file mode 100644 index 000000000..9ca04f024 --- /dev/null +++ b/plugins/rdp/rdp_file.h @@ -0,0 +1,46 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2010-2011 Vic Lee + * Copyright (C) 2017 Antenore Gatta, Giovanni Panozzo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + * + */ + +#pragma once + +G_BEGIN_DECLS + +gboolean remmina_rdp_file_import_test(const gchar* from_file); +RemminaFile* remmina_rdp_file_import(const gchar* from_file); +gboolean remmina_rdp_file_export_test(RemminaFile* remminafile); +gboolean remmina_rdp_file_export(RemminaFile* remminafile, const gchar* to_file); + +G_END_DECLS + diff --git a/plugins/rdp/rdp_graphics.c b/plugins/rdp/rdp_graphics.c new file mode 100644 index 000000000..06404a611 --- /dev/null +++ b/plugins/rdp/rdp_graphics.c @@ -0,0 +1,435 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2010 Jay Sorg + * Copyright (C) 2010-2011 Vic Lee + * Copyright (C) 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo + * Copyright (C) 2016-2017 Antenore Gatta, Giovanni Panozzo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + * + */ + +#include "rdp_plugin.h" +#include "rdp_event.h" +#include "rdp_graphics.h" + +#include <freerdp/codec/color.h> +#include <freerdp/codec/bitmap.h> +#include <winpr/memory.h> + +//#define RF_BITMAP +//#define RF_GLYPH + +/* Bitmap Class */ + +BOOL rf_Bitmap_New(rdpContext* context, rdpBitmap* bitmap) +{ + TRACE_CALL(__func__); +#ifdef RF_BITMAP + UINT8* data; + Pixmap pixmap; + XImage* image; + rfContext* rfi = (rfContext*)context; + + XSetFunction(rfi->display, rfi->gc, GXcopy); + pixmap = XCreatePixmap(rfi->display, rfi->drawable, bitmap->width, bitmap->height, rfi->depth); + + if (bitmap->data != NULL) { + data = freerdp_image_convert(bitmap->data, NULL, + bitmap->width, bitmap->height, rfi->srcBpp, rfi->bpp, rfi->clrconv); + + if (bitmap->ephemeral != TRUE) { + image = XCreateImage(rfi->display, rfi->visual, rfi->depth, + ZPixmap, 0, (char*)data, bitmap->width, bitmap->height, rfi->scanline_pad, 0); + + XPutImage(rfi->display, pixmap, rfi->gc, image, 0, 0, 0, 0, bitmap->width, bitmap->height); + XFree(image); + + if (data != bitmap->data) && (data != NULL) + free(data); + }else { + if (data != bitmap->data) && (data != NULL) + free(bitmap->data); + + bitmap->data = data; + } + } + + ((rfBitmap*)bitmap)->pixmap = pixmap; +#endif + return TRUE; +} + +void rf_Bitmap_Free(rdpContext* context, rdpBitmap* bitmap) +{ + TRACE_CALL(__func__); +#ifdef RF_BITMAP + rfContext* rfi = (rfContext*)context; + + printf("rf_Bitmap_Free\n"); + + if (((rfBitmap*)bitmap)->pixmap != 0) + XFreePixmap(rfi->display, ((rfBitmap*)bitmap)->pixmap); +#endif +} + +BOOL rf_Bitmap_Paint(rdpContext* context, rdpBitmap* bitmap) +{ + TRACE_CALL(__func__); +#ifdef RF_BITMAP + XImage* image; + int width, height; + rfContext* rfi = (rfContext*)context; + + printf("rf_Bitmap_Paint\n"); + + width = bitmap->right - bitmap->left + 1; + height = bitmap->bottom - bitmap->top + 1; + + XSetFunction(rfi->display, rfi->gc, GXcopy); + + image = XCreateImage(rfi->display, rfi->visual, rfi->depth, + ZPixmap, 0, (char*)bitmap->data, bitmap->width, bitmap->height, rfi->scanline_pad, 0); + + XPutImage(rfi->display, rfi->primary, rfi->gc, + image, 0, 0, bitmap->left, bitmap->top, width, height); + + XFree(image); + + //XCopyArea(rfi->display, rfi->primary, rfi->drawable, rfi->gc, + // bitmap->left, bitmap->top, width, height, bitmap->left, bitmap->top); + + //gdi_InvalidateRegion(rfi->hdc, bitmap->left, bitmap->top, width, height); +#endif + return FALSE; +} + +BOOL rf_Bitmap_Decompress(rdpContext* context, rdpBitmap* bitmap, + const BYTE* data, UINT32 width, UINT32 height, UINT32 bpp, UINT32 length, BOOL compressed, UINT32 codec_id) +{ + TRACE_CALL(__func__); +#ifdef RF_BITMAP + UINT16 size; + + printf("rf_Bitmap_Decompress\n"); + + size = width * height * (bpp + 7) / 8; + + if (bitmap->data == NULL) + bitmap->data = (UINT8*)xmalloc(size); + else + bitmap->data = (UINT8*)xrealloc(bitmap->data, size); + + if (compressed) { + BOOL status; + + status = bitmap_decompress(data, bitmap->data, width, height, length, bpp, bpp); + + if (status != TRUE) { + printf("Bitmap Decompression Failed\n"); + } + }else { + freerdp_image_flip(data, bitmap->data, width, height, bpp); + } + + bitmap->compressed = FALSE; + bitmap->length = size; + bitmap->bpp = bpp; +#endif + return TRUE; +} + +BOOL rf_Bitmap_SetSurface(rdpContext* context, rdpBitmap* bitmap, BOOL primary) +{ + TRACE_CALL(__func__); +#ifdef RF_BITMAP + rfContext* rfi = (rfContext*)context; + + if (primary) + rfi->drawing = rfi->primary; + else + rfi->drawing = ((rfBitmap*)bitmap)->pixmap; +#endif + return TRUE; +} + +/* Pointer Class */ + +BOOL rf_Pointer_New(rdpContext* context, rdpPointer* pointer) +{ + TRACE_CALL(__func__); + RemminaPluginRdpUiObject* ui; + rfContext* rfi = (rfContext*)context; + + if ((pointer->andMaskData != 0) && (pointer->xorMaskData != 0)) { + ui = g_new0(RemminaPluginRdpUiObject, 1); + ui->type = REMMINA_RDP_UI_CURSOR; + ui->cursor.context = context; + ui->cursor.pointer = (rfPointer*)pointer; + ui->cursor.type = REMMINA_RDP_POINTER_NEW; + return remmina_rdp_event_queue_ui_sync_retint(rfi->protocol_widget, ui) ? TRUE : FALSE; + } + return FALSE; +} + +void rf_Pointer_Free(rdpContext* context, rdpPointer* pointer) +{ + TRACE_CALL(__func__); + RemminaPluginRdpUiObject* ui; + rfContext* rfi = (rfContext*)context; + +#if GTK_VERSION == 2 + if (((rfPointer*)pointer)->cursor != NULL) +#else + if (G_IS_OBJECT(((rfPointer*)pointer)->cursor)) +#endif + { + ui = g_new0(RemminaPluginRdpUiObject, 1); + ui->type = REMMINA_RDP_UI_CURSOR; + ui->cursor.context = context; + ui->cursor.pointer = (rfPointer*)pointer; + ui->cursor.type = REMMINA_RDP_POINTER_FREE; + remmina_rdp_event_queue_ui_sync_retint(rfi->protocol_widget, ui); + } +} + +BOOL rf_Pointer_Set(rdpContext* context, const rdpPointer* pointer) +{ + TRACE_CALL(__func__); + RemminaPluginRdpUiObject* ui; + rfContext* rfi = (rfContext*)context; + + ui = g_new0(RemminaPluginRdpUiObject, 1); + ui->type = REMMINA_RDP_UI_CURSOR; + ui->cursor.pointer = (rfPointer*)pointer; + ui->cursor.type = REMMINA_RDP_POINTER_SET; + + return remmina_rdp_event_queue_ui_sync_retint(rfi->protocol_widget, ui) ? TRUE : FALSE; + +} + +BOOL rf_Pointer_SetNull(rdpContext* context) +{ + TRACE_CALL(__func__); + RemminaPluginRdpUiObject* ui; + rfContext* rfi = (rfContext*)context; + + ui = g_new0(RemminaPluginRdpUiObject, 1); + ui->type = REMMINA_RDP_UI_CURSOR; + ui->cursor.type = REMMINA_RDP_POINTER_NULL; + + return remmina_rdp_event_queue_ui_sync_retint(rfi->protocol_widget, ui) ? TRUE : FALSE; +} + +BOOL rf_Pointer_SetDefault(rdpContext* context) +{ + TRACE_CALL(__func__); + RemminaPluginRdpUiObject* ui; + rfContext* rfi = (rfContext*)context; + + ui = g_new0(RemminaPluginRdpUiObject, 1); + ui->type = REMMINA_RDP_UI_CURSOR; + ui->cursor.type = REMMINA_RDP_POINTER_DEFAULT; + + return remmina_rdp_event_queue_ui_sync_retint(rfi->protocol_widget, ui) ? TRUE : FALSE; +} + +BOOL rf_Pointer_SetPosition(rdpContext* context, UINT32 x, UINT32 y) +{ + TRACE_CALL(__func__); + RemminaPluginRdpUiObject* ui; + rfContext* rfi = (rfContext*)context; + ui = g_new0(RemminaPluginRdpUiObject, 1); + ui->type = REMMINA_RDP_UI_CURSOR; + ui->cursor.type = REMMINA_RDP_POINTER_SETPOS; + ui->pos.x = x; + ui->pos.y = y; + + return remmina_rdp_event_queue_ui_sync_retint(rfi->protocol_widget, ui) ? TRUE : FALSE; +} + +/* Glyph Class */ + +BOOL rf_Glyph_New(rdpContext* context, const rdpGlyph* glyph) +{ + TRACE_CALL(__func__); +#ifdef RF_GLYPH + int scanline; + XImage* image; + rfContext* rfi; + rfGlyph* rf_glyph; + + rf_glyph = (rfGlyph*)glyph; + rfi = (rfContext*)context; + + scanline = (glyph->cx + 7) / 8; + + rf_glyph->pixmap = XCreatePixmap(rfi->display, rfi->drawing, glyph->cx, glyph->cy, 1); + + image = XCreateImage(rfi->display, rfi->visual, 1, + ZPixmap, 0, (char*)glyph->aj, glyph->cx, glyph->cy, 8, scanline); + + image->byte_order = MSBFirst; + image->bitmap_bit_order = MSBFirst; + + XInitImage(image); + XPutImage(rfi->display, rf_glyph->pixmap, rfi->gc_mono, image, 0, 0, 0, 0, glyph->cx, glyph->cy); + XFree(image); +#endif + return TRUE; +} + +void rf_Glyph_Free(rdpContext* context, rdpGlyph* glyph) +{ + TRACE_CALL(__func__); +#ifdef RF_GLYPH + rfContext* rfi = (rfContext*)context; + + if (((rfGlyph*)glyph)->pixmap != 0) + XFreePixmap(rfi->display, ((rfGlyph*)glyph)->pixmap); +#endif +} + +static BOOL rf_Glyph_Draw(rdpContext* context, const rdpGlyph* glyph, UINT32 x, + UINT32 y, UINT32 w, UINT32 h, UINT32 sx, UINT32 sy, + BOOL fOpRedundant) +{ + TRACE_CALL(__func__); +#ifdef RF_GLYPH + rfGlyph* rf_glyph; + rfContext* rfi = (rfContext*)context; + + rf_glyph = (rfGlyph*)glyph; + + XSetStipple(rfi->display, rfi->gc, rf_glyph->pixmap); + XSetTSOrigin(rfi->display, rfi->gc, x, y); + XFillRectangle(rfi->display, rfi->drawing, rfi->gc, x, y, glyph->cx, glyph->cy); + XSetStipple(rfi->display, rfi->gc, rfi->bitmap_mono); +#endif + return TRUE; +} + +static BOOL rf_Glyph_BeginDraw(rdpContext* context, UINT32 x, UINT32 y, + UINT32 width, UINT32 height, UINT32 bgcolor, + UINT32 fgcolor, BOOL fOpRedundant) +{ + TRACE_CALL(__func__); +#ifdef RF_GLYPH + rfContext* rfi = (rfContext*)context; + + bgcolor = (rfi->clrconv->invert) ? + freerdp_color_convert_var_bgr(bgcolor, rfi->srcBpp, 32, rfi->clrconv) : + freerdp_color_convert_var_rgb(bgcolor, rfi->srcBpp, 32, rfi->clrconv); + + fgcolor = (rfi->clrconv->invert) ? + freerdp_color_convert_var_bgr(fgcolor, rfi->srcBpp, 32, rfi->clrconv) : + freerdp_color_convert_var_rgb(fgcolor, rfi->srcBpp, 32, rfi->clrconv); + + XSetFunction(rfi->display, rfi->gc, GXcopy); + XSetFillStyle(rfi->display, rfi->gc, FillSolid); + XSetForeground(rfi->display, rfi->gc, fgcolor); + XFillRectangle(rfi->display, rfi->drawing, rfi->gc, x, y, width, height); + + XSetForeground(rfi->display, rfi->gc, bgcolor); + XSetBackground(rfi->display, rfi->gc, fgcolor); + XSetFillStyle(rfi->display, rfi->gc, FillStippled); +#endif + return TRUE; +} + +static BOOL rf_Glyph_EndDraw(rdpContext* context, UINT32 x, UINT32 y, + UINT32 width, UINT32 height, + UINT32 bgcolor, UINT32 fgcolor) +{ + TRACE_CALL(__func__); +#ifdef RF_GLYPH + rfContext* rfi = (rfContext*)context; + + if (rfi->drawing == rfi->primary) { + //XCopyArea(rfi->display, rfi->primary, rfi->drawable, rfi->gc, x, y, width, height, x, y); + //gdi_InvalidateRegion(rfi->hdc, x, y, width, height); + } +#endif + return TRUE; +} + +/* Graphics Module */ + +void rf_register_graphics(rdpGraphics* graphics) +{ + TRACE_CALL(__func__); + rdpBitmap* bitmap; + rdpPointer* pointer; + rdpGlyph* glyph; + + bitmap = (rdpBitmap*)malloc(sizeof(rdpBitmap)); + ZeroMemory(bitmap, sizeof(rdpBitmap)); + bitmap->size = sizeof(rfBitmap); + + bitmap->New = rf_Bitmap_New; + bitmap->Free = rf_Bitmap_Free; + bitmap->Paint = rf_Bitmap_Paint; + bitmap->Decompress = rf_Bitmap_Decompress; + bitmap->SetSurface = rf_Bitmap_SetSurface; + + graphics_register_bitmap(graphics, bitmap); + free(bitmap); + + pointer = (rdpPointer*)malloc(sizeof(rdpPointer)); + ZeroMemory(pointer, sizeof(rdpPointer)); + + pointer->size = sizeof(rfPointer); + + pointer->New = rf_Pointer_New; + pointer->Free = rf_Pointer_Free; + pointer->Set = rf_Pointer_Set; + pointer->SetNull = rf_Pointer_SetNull; + pointer->SetDefault = rf_Pointer_SetDefault; + pointer->SetPosition = rf_Pointer_SetPosition; + + graphics_register_pointer(graphics, pointer); + + free(pointer); + + glyph = (rdpGlyph*)malloc(sizeof(rdpGlyph)); + ZeroMemory(glyph, sizeof(rdpGlyph)); + + glyph->size = sizeof(rfGlyph); + + glyph->New = rf_Glyph_New; + glyph->Free = rf_Glyph_Free; + glyph->Draw = rf_Glyph_Draw; + glyph->BeginDraw = rf_Glyph_BeginDraw; + glyph->EndDraw = rf_Glyph_EndDraw; + + graphics_register_glyph(graphics, glyph); + + free(glyph); +} diff --git a/plugins/rdp/rdp_graphics.h b/plugins/rdp/rdp_graphics.h new file mode 100644 index 000000000..1476a3114 --- /dev/null +++ b/plugins/rdp/rdp_graphics.h @@ -0,0 +1,41 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2010-2011 Vic Lee + * Copyright (C) 2017 Antenore Gatta, Giovanni Panozzo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + * + */ + +#pragma once + +#include "rdp_plugin.h" + +void rf_register_graphics(rdpGraphics* graphics); + diff --git a/plugins/rdp/rdp_plugin.c b/plugins/rdp/rdp_plugin.c new file mode 100644 index 000000000..9a8bf92fe --- /dev/null +++ b/plugins/rdp/rdp_plugin.c @@ -0,0 +1,1553 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2010-2011 Vic Lee + * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo + * Copyright (C) 2016-2017 Antenore Gatta, Giovanni Panozzo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + * + */ + +#include "rdp_plugin.h" +#include "rdp_event.h" +#include "rdp_graphics.h" +#include "rdp_file.h" +#include "rdp_settings.h" +#include "rdp_cliprdr.h" +#include "rdp_channels.h" + +#include <errno.h> +#include <pthread.h> +#include <time.h> +#include <cairo/cairo-xlib.h> +#include <freerdp/freerdp.h> +#include <freerdp/constants.h> +#include <freerdp/client/cliprdr.h> +#include <freerdp/client/channels.h> +#include <freerdp/client/cmdline.h> +#include <freerdp/error.h> +#include <winpr/memory.h> + +#define REMMINA_RDP_FEATURE_TOOL_REFRESH 1 +#define REMMINA_RDP_FEATURE_SCALE 2 +#define REMMINA_RDP_FEATURE_UNFOCUS 3 +#define REMMINA_RDP_FEATURE_TOOL_SENDCTRLALTDEL 4 +#define REMMINA_RDP_FEATURE_DYNRESUPDATE 5 + +/* Some string settings of freerdp are preallocated buffers of N bytes */ +#define FREERDP_CLIENTHOSTNAME_LEN 32 + +RemminaPluginService* remmina_plugin_service = NULL; +static char remmina_rdp_plugin_default_drive_name[] = "RemminaDisk"; + +static BOOL rf_process_event_queue(RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + UINT16 flags; + rdpInput* input; + rfContext* rfi = GET_PLUGIN_DATA(gp); + RemminaPluginRdpEvent* event; + DISPLAY_CONTROL_MONITOR_LAYOUT* dcml; + + if (rfi->event_queue == NULL) + return True; + + input = rfi->instance->input; + + while ((event = (RemminaPluginRdpEvent*)g_async_queue_try_pop(rfi->event_queue)) != NULL) { + switch (event->type) { + case REMMINA_RDP_EVENT_TYPE_SCANCODE: + flags = event->key_event.extended ? KBD_FLAGS_EXTENDED : 0; + flags |= event->key_event.up ? KBD_FLAGS_RELEASE : KBD_FLAGS_DOWN; + input->KeyboardEvent(input, flags, event->key_event.key_code); + break; + + case REMMINA_RDP_EVENT_TYPE_SCANCODE_UNICODE: + /* + * TS_UNICODE_KEYBOARD_EVENT RDP message, see https://msdn.microsoft.com/en-us/library/cc240585.aspx + */ + flags = event->key_event.up ? KBD_FLAGS_RELEASE : KBD_FLAGS_DOWN; + input->UnicodeKeyboardEvent(input, flags, event->key_event.unicode_code); + break; + + case REMMINA_RDP_EVENT_TYPE_MOUSE: + if (event->mouse_event.extended) + input->ExtendedMouseEvent(input, event->mouse_event.flags, + event->mouse_event.x, event->mouse_event.y); + else + input->MouseEvent(input, event->mouse_event.flags, + event->mouse_event.x, event->mouse_event.y); + break; + + case REMMINA_RDP_EVENT_TYPE_CLIPBOARD_SEND_CLIENT_FORMAT_LIST: + rfi->clipboard.context->ClientFormatList(rfi->clipboard.context, event->clipboard_formatlist.pFormatList); + free(event->clipboard_formatlist.pFormatList); + break; + + case REMMINA_RDP_EVENT_TYPE_CLIPBOARD_SEND_CLIENT_FORMAT_DATA_RESPONSE: + rfi->clipboard.context->ClientFormatDataResponse(rfi->clipboard.context, event->clipboard_formatdataresponse.pFormatDataResponse); + if (event->clipboard_formatdataresponse.pFormatDataResponse->requestedFormatData) + free(event->clipboard_formatdataresponse.pFormatDataResponse->requestedFormatData); + free(event->clipboard_formatdataresponse.pFormatDataResponse); + break; + + case REMMINA_RDP_EVENT_TYPE_CLIPBOARD_SEND_CLIENT_FORMAT_DATA_REQUEST: + rfi->clipboard.context->ClientFormatDataRequest(rfi->clipboard.context, event->clipboard_formatdatarequest.pFormatDataRequest); + free(event->clipboard_formatdatarequest.pFormatDataRequest); + break; + + case REMMINA_RDP_EVENT_TYPE_SEND_MONITOR_LAYOUT: + dcml = g_malloc0(sizeof(DISPLAY_CONTROL_MONITOR_LAYOUT)); + if (dcml) { + dcml->Flags = DISPLAY_CONTROL_MONITOR_PRIMARY; + dcml->Width = event->monitor_layout.width; + dcml->Height = event->monitor_layout.height; + dcml->Orientation = event->monitor_layout.desktopOrientation; + dcml->DesktopScaleFactor = event->monitor_layout.desktopScaleFactor; + dcml->DeviceScaleFactor = event->monitor_layout.deviceScaleFactor; + rfi->dispcontext->SendMonitorLayout(rfi->dispcontext, 1, dcml); + g_free(dcml); + } + break; + } + + g_free(event); + } + + return True; +} + +static gboolean remmina_rdp_tunnel_init(RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + + /* Opens the optional SSH tunnel if needed. + * Used also when reopening the same tunnel for a freerdp reconnect. + * Returns TRUE if all OK, and setups correct rfi->Settings values + * with connection and certificate parameters */ + + gchar* hostport; + gchar* s; + gchar* host; + gchar *cert_host; + gint cert_port; + gint port; + const gchar *cert_hostport; + + rfContext* rfi = GET_PLUGIN_DATA(gp); + RemminaFile* remminafile; + + remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); + + hostport = remmina_plugin_service->protocol_plugin_start_direct_tunnel(gp, 3389, FALSE); + if (hostport == NULL) { + return FALSE; + } + + remmina_plugin_service->get_server_port(hostport, 3389, &host, &port); + + cert_host = host; + cert_port = port; + + if (remmina_plugin_service->file_get_int(remminafile, "ssh_enabled", FALSE)) { + cert_hostport = remmina_plugin_service->file_get_string(remminafile, "server"); + if ( cert_hostport ) { + remmina_plugin_service->get_server_port(cert_hostport, 3389, &cert_host, &cert_port); + } + + } + + if (!rfi->is_reconnecting) { + /* settings->CertificateName and settings->ServerHostname is created + * only on 1st connect, not on reconnections */ + + rfi->settings->ServerHostname = strdup(host); + + if (cert_port == 3389) { + rfi->settings->CertificateName = strdup(cert_host); + }else { + s = g_strdup_printf("%s:%d", cert_host, cert_port); + rfi->settings->CertificateName = strdup(s); + g_free(s); + } + } + + if (cert_host != host) g_free(cert_host); + g_free(host); + g_free(hostport); + + rfi->settings->ServerPort = port; + + return TRUE; +} + +BOOL rf_auto_reconnect(rfContext* rfi) +{ + TRACE_CALL(__func__); + rdpSettings* settings = rfi->instance->settings; + RemminaPluginRdpUiObject* ui; + time_t treconn; + + rfi->is_reconnecting = TRUE; + rfi->reconnect_maxattempts = settings->AutoReconnectMaxRetries; + rfi->reconnect_nattempt = 0; + + /* Only auto reconnect on network disconnects. */ + if (freerdp_error_info(rfi->instance) != 0) { + rfi->is_reconnecting = FALSE; + return FALSE; + } + + if (!settings->AutoReconnectionEnabled) { + /* No auto-reconnect - just quit */ + rfi->is_reconnecting = FALSE; + return FALSE; + } + + /* A network disconnect was detected and we should try to reconnect */ + remmina_plugin_service->log_printf("[RDP][%s] network disconnection detected, initiating reconnection attempt\n", + rfi->settings->ServerHostname); + + ui = g_new0(RemminaPluginRdpUiObject, 1); + ui->type = REMMINA_RDP_UI_RECONNECT_PROGRESS; + remmina_rdp_event_queue_ui_async(rfi->protocol_widget, ui); + + /* Sleep half a second to allow: + * - processing of the ui event we just pushed on the queue + * - better network conditions + * Remember: we hare on a thread, so the main gui won't lock */ + + usleep(500000); + + /* Perform an auto-reconnect. */ + while (TRUE) { + /* Quit retrying if max retries has been exceeded */ + if (rfi->reconnect_nattempt++ >= rfi->reconnect_maxattempts) { + remmina_plugin_service->log_printf("[RDP][%s] maximum number of reconnection attempts exceeded.\n", + rfi->settings->ServerHostname); + break; + } + + /* Attempt the next reconnect */ + remmina_plugin_service->log_printf("[RDP][%s] attempting reconnection, attempt #%d of %d\n", + rfi->settings->ServerHostname, rfi->reconnect_nattempt, rfi->reconnect_maxattempts); + + ui = g_new0(RemminaPluginRdpUiObject, 1); + ui->type = REMMINA_RDP_UI_RECONNECT_PROGRESS; + remmina_rdp_event_queue_ui_async(rfi->protocol_widget, ui); + + treconn = time(NULL); + + /* Reconnect the SSH tunnel, if needed */ + if (!remmina_rdp_tunnel_init(rfi->protocol_widget)) { + remmina_plugin_service->log_printf("[RDP][%s] unable to recreate tunnel with remmina_rdp_tunnel_init.\n", + rfi->settings->ServerHostname); + } else { + if (freerdp_reconnect(rfi->instance)) { + /* Reconnection is successful */ + remmina_plugin_service->log_printf("[RDP][%s] reconnection successful.\n", + rfi->settings->ServerHostname); + rfi->is_reconnecting = FALSE; + return TRUE; + } + } + + /* Wait until 5 secs have elapsed from last reconnect attempt */ + while (time(NULL) - treconn < 5) + sleep(1); + } + + rfi->is_reconnecting = FALSE; + return FALSE; +} + +BOOL rf_begin_paint(rdpContext* context) +{ + TRACE_CALL(__func__); + rdpGdi* gdi; + HGDI_WND hwnd; + + if (!context) + return FALSE; + + gdi = context->gdi; + if (!gdi || !gdi->primary || !gdi->primary->hdc || !gdi->primary->hdc->hwnd) + return FALSE; + + hwnd = gdi->primary->hdc->hwnd; + if (!hwnd->ninvalid) + return FALSE; + + hwnd->invalid->null = 1; + hwnd->ninvalid = 0; + return TRUE; +} + +BOOL rf_end_paint(rdpContext* context) +{ + TRACE_CALL(__func__); + INT32 x, y; + UINT32 w, h; + rdpGdi* gdi; + rfContext* rfi; + RemminaProtocolWidget* gp; + RemminaPluginRdpUiObject* ui; + + gdi = context->gdi; + rfi = (rfContext*)context; + gp = rfi->protocol_widget; + + if (gdi->primary->hdc->hwnd->invalid->null) + return FALSE; + + x = gdi->primary->hdc->hwnd->invalid->x; + y = gdi->primary->hdc->hwnd->invalid->y; + w = gdi->primary->hdc->hwnd->invalid->w; + h = gdi->primary->hdc->hwnd->invalid->h; + + ui = g_new0(RemminaPluginRdpUiObject, 1); + ui->type = REMMINA_RDP_UI_UPDATE_REGION; + ui->region.x = x; + ui->region.y = y; + ui->region.width = w; + ui->region.height = h; + + remmina_rdp_event_queue_ui_async(rfi->protocol_widget, ui); + + return TRUE; +} + +static BOOL rf_desktop_resize(rdpContext* context) +{ + TRACE_CALL(__func__); + rfContext* rfi; + RemminaProtocolWidget* gp; + RemminaPluginRdpUiObject* ui; + + rfi = (rfContext*)context; + gp = rfi->protocol_widget; + + remmina_plugin_service->protocol_plugin_set_width(gp, rfi->settings->DesktopWidth); + remmina_plugin_service->protocol_plugin_set_height(gp, rfi->settings->DesktopHeight); + + /* Call to remmina_rdp_event_update_scale(gp) on the main UI thread */ + + ui = g_new0(RemminaPluginRdpUiObject, 1); + ui->type = REMMINA_RDP_UI_EVENT; + ui->event.type = REMMINA_RDP_UI_EVENT_UPDATE_SCALE; + remmina_rdp_event_queue_ui_sync_retint(gp, ui); + + remmina_plugin_service->protocol_plugin_emit_signal(gp, "desktop-resize"); + + return TRUE; +} + +static BOOL remmina_rdp_pre_connect(freerdp* instance) +{ + TRACE_CALL(__func__); + rfContext* rfi; + ALIGN64 rdpSettings* settings; + RemminaProtocolWidget* gp; + + rfi = (rfContext*)instance->context; + settings = instance->settings; + gp = rfi->protocol_widget; + + settings->OsMajorType = OSMAJORTYPE_UNIX; + settings->OsMinorType = OSMINORTYPE_UNSPECIFIED; + ZeroMemory(settings->OrderSupport, 32); + + settings->BitmapCacheEnabled = True; + settings->OffscreenSupportLevel = True; + + + settings->OrderSupport[NEG_DSTBLT_INDEX] = True; + settings->OrderSupport[NEG_PATBLT_INDEX] = True; + settings->OrderSupport[NEG_SCRBLT_INDEX] = True; + settings->OrderSupport[NEG_OPAQUE_RECT_INDEX] = True; + settings->OrderSupport[NEG_DRAWNINEGRID_INDEX] = False; + settings->OrderSupport[NEG_MULTIDSTBLT_INDEX] = False; + settings->OrderSupport[NEG_MULTIPATBLT_INDEX] = False; + settings->OrderSupport[NEG_MULTISCRBLT_INDEX] = False; + settings->OrderSupport[NEG_MULTIOPAQUERECT_INDEX] = True; + settings->OrderSupport[NEG_MULTI_DRAWNINEGRID_INDEX] = False; + settings->OrderSupport[NEG_LINETO_INDEX] = True; + settings->OrderSupport[NEG_POLYLINE_INDEX] = True; + settings->OrderSupport[NEG_MEMBLT_INDEX] = settings->BitmapCacheEnabled; + settings->OrderSupport[NEG_MEM3BLT_INDEX] = settings->BitmapCacheEnabled; + settings->OrderSupport[NEG_MEMBLT_V2_INDEX] = settings->BitmapCacheEnabled; + settings->OrderSupport[NEG_MEM3BLT_V2_INDEX] = settings->BitmapCacheEnabled; + settings->OrderSupport[NEG_SAVEBITMAP_INDEX] = False; + settings->OrderSupport[NEG_GLYPH_INDEX_INDEX] = True; + settings->OrderSupport[NEG_FAST_INDEX_INDEX] = True; + settings->OrderSupport[NEG_FAST_GLYPH_INDEX] = True; + settings->OrderSupport[NEG_POLYGON_SC_INDEX] = False; + settings->OrderSupport[NEG_POLYGON_CB_INDEX] = False; + settings->OrderSupport[NEG_ELLIPSE_SC_INDEX] = False; + settings->OrderSupport[NEG_ELLIPSE_CB_INDEX] = False; + + if (settings->RemoteFxCodec == True) { + settings->FrameAcknowledge = False; + settings->LargePointerFlag = True; + settings->PerformanceFlags = PERF_FLAG_NONE; + + rfi->rfx_context = rfx_context_new(FALSE); + } + + PubSub_SubscribeChannelConnected(instance->context->pubSub, + (pChannelConnectedEventHandler)remmina_rdp_OnChannelConnectedEventHandler); + PubSub_SubscribeChannelDisconnected(instance->context->pubSub, + (pChannelDisconnectedEventHandler)remmina_rdp_OnChannelDisconnectedEventHandler); + + freerdp_client_load_addins(instance->context->channels, instance->settings); + + return True; +} + +static UINT32 rf_get_local_color_format(rfContext* rfi, BOOL aligned) +{ + UINT32 DstFormat; + BOOL invert = aligned; + + if (!rfi) + return 0; + + if (rfi->bpp == 32) + DstFormat = (!invert) ? PIXEL_FORMAT_RGBA32 : PIXEL_FORMAT_BGRA32; + else if (rfi->bpp == 24) { + if (aligned) + DstFormat = (!invert) ? PIXEL_FORMAT_RGBX32 : PIXEL_FORMAT_BGRX32; + else + DstFormat = (!invert) ? PIXEL_FORMAT_RGB24 : PIXEL_FORMAT_BGR24; + }else if (rfi->bpp == 16) + DstFormat = (!invert) ? PIXEL_FORMAT_RGB16 : PIXEL_FORMAT_BGR16; + else if (rfi->bpp == 15) + DstFormat = (!invert) ? PIXEL_FORMAT_RGB16 : PIXEL_FORMAT_BGR16; + else + DstFormat = (!invert) ? PIXEL_FORMAT_RGBX32 : PIXEL_FORMAT_BGRX32; + + return DstFormat; +} + + +static BOOL remmina_rdp_post_connect(freerdp* instance) +{ + TRACE_CALL(__func__); + rfContext* rfi; + RemminaProtocolWidget* gp; + RemminaPluginRdpUiObject* ui; + rdpGdi* gdi; + int hdcBytesPerPixel, hdcBitsPerPixel; + + rfi = (rfContext*)instance->context; + gp = rfi->protocol_widget; + rfi->postconnect_error = REMMINA_POSTCONNECT_ERROR_OK; + + rfi->width = rfi->settings->DesktopWidth; + rfi->height = rfi->settings->DesktopHeight; + rfi->srcBpp = rfi->settings->ColorDepth; + + if (rfi->settings->RemoteFxCodec == FALSE) + rfi->sw_gdi = TRUE; + + rf_register_graphics(instance->context->graphics); + + if (rfi->bpp == 32) { + hdcBytesPerPixel = 4; + hdcBitsPerPixel = 32; + rfi->cairo_format = CAIRO_FORMAT_ARGB32; + }else if (rfi->bpp == 24) { + hdcBytesPerPixel = 4; + hdcBitsPerPixel = 32; + rfi->cairo_format = CAIRO_FORMAT_RGB24; + }else { + hdcBytesPerPixel = 2; + hdcBitsPerPixel = 16; + rfi->cairo_format = CAIRO_FORMAT_RGB16_565; + } + + if (!gdi_init(instance, rf_get_local_color_format(rfi, TRUE))) { + rfi->postconnect_error = REMMINA_POSTCONNECT_ERROR_GDI_INIT; + return FALSE; + } + + if (instance->context->codecs->h264 == NULL && rfi->settings->GfxH264) { + gdi_free(instance); + rfi->postconnect_error = REMMINA_POSTCONNECT_ERROR_NO_H264; + return FALSE; + } + + gdi = instance->context->gdi; + rfi->primary_buffer = gdi->primary_buffer; + + pointer_cache_register_callbacks(instance->update); + + instance->update->BeginPaint = rf_begin_paint; + instance->update->EndPaint = rf_end_paint; + instance->update->DesktopResize = rf_desktop_resize; + + remmina_rdp_clipboard_init(rfi); + rfi->connected = True; + + ui = g_new0(RemminaPluginRdpUiObject, 1); + ui->type = REMMINA_RDP_UI_CONNECTED; + remmina_rdp_event_queue_ui_async(gp, ui); + + return TRUE; +} + +static BOOL remmina_rdp_authenticate(freerdp* instance, char** username, char** password, char** domain) +{ + TRACE_CALL(__func__); + gchar *s_username, *s_password, *s_domain; + gint ret; + rfContext* rfi; + RemminaProtocolWidget* gp; + gboolean save; + gboolean disablepasswordstoring; + RemminaFile* remminafile; + + rfi = (rfContext*)instance->context; + gp = rfi->protocol_widget; + remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); + disablepasswordstoring = remmina_plugin_service->file_get_int(remminafile, "disablepasswordstoring", FALSE); + ret = remmina_plugin_service->protocol_plugin_init_authuserpwd(gp, TRUE, !disablepasswordstoring); + + if (ret == GTK_RESPONSE_OK) { + s_username = remmina_plugin_service->protocol_plugin_init_get_username(gp); + if (s_username) rfi->settings->Username = strdup(s_username); + + s_password = remmina_plugin_service->protocol_plugin_init_get_password(gp); + if (s_password) rfi->settings->Password = strdup(s_password); + + s_domain = remmina_plugin_service->protocol_plugin_init_get_domain(gp); + if (s_domain) rfi->settings->Domain = strdup(s_domain); + + save = remmina_plugin_service->protocol_plugin_init_get_savepassword(gp); + if (save) { + // User has requested to save credentials. We put all the new cretentials + // into remminafile->settings. They will be saved later, on successful connection, by + // remmina_connection_window.c + + remmina_plugin_service->file_set_string( remminafile, "username", s_username ); + remmina_plugin_service->file_set_string( remminafile, "password", s_password ); + remmina_plugin_service->file_set_string( remminafile, "domain", s_domain ); + + } + + if ( s_username ) g_free( s_username ); + if ( s_password ) g_free( s_password ); + if ( s_domain ) g_free( s_domain ); + + return True; + }else { + rfi->user_cancelled = TRUE; + return False; + } + + return True; +} + +static DWORD remmina_rdp_verify_certificate(freerdp* instance, const char *common_name, const char* subject, + const char* issuer, const char* fingerprint, BOOL host_mismatch) +{ + TRACE_CALL(__func__); + gint status; + rfContext* rfi; + RemminaProtocolWidget* gp; + + rfi = (rfContext*)instance->context; + gp = rfi->protocol_widget; + + status = remmina_plugin_service->protocol_plugin_init_certificate(gp, subject, issuer, fingerprint); + + if (status == GTK_RESPONSE_OK) + return 1; + + return 0; +} +static DWORD remmina_rdp_verify_changed_certificate(freerdp* instance, + const char* common_name, const char* subject, const char* issuer, + const char* new_fingerprint, const char* old_subject, const char* old_issuer, const char* old_fingerprint) +{ + TRACE_CALL(__func__); + gint status; + rfContext* rfi; + RemminaProtocolWidget* gp; + + rfi = (rfContext*)instance->context; + gp = rfi->protocol_widget; + + status = remmina_plugin_service->protocol_plugin_changed_certificate(gp, subject, issuer, new_fingerprint, old_fingerprint); + + if (status == GTK_RESPONSE_OK) + return 1; + + return 0; +} + +static void remmina_rdp_main_loop(RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + DWORD nCount; + DWORD status; + HANDLE handles[64]; + gchar buf[100]; + rfContext* rfi = GET_PLUGIN_DATA(gp); + + while (!freerdp_shall_disconnect(rfi->instance)) { + nCount = freerdp_get_event_handles(rfi->instance->context, &handles[0], 64); + if (rfi->event_handle) { + handles[nCount++] = rfi->event_handle; + } + + if (nCount == 0) { + fprintf(stderr, "freerdp_get_event_handles failed\n"); + break; + } + + status = WaitForMultipleObjects(nCount, handles, FALSE, 100); + + if (status == WAIT_FAILED) { + fprintf(stderr, "WaitForMultipleObjects failed with %lu\n", (unsigned long)status); + break; + } + + if (rfi->event_handle && WaitForSingleObject(rfi->event_handle, 0) == WAIT_OBJECT_0) { + if (!rf_process_event_queue(gp)) { + fprintf(stderr, "Failed to process local kb/mouse event queue\n"); + break; + } + if (read(rfi->event_pipe[0], buf, sizeof(buf))) { + } + } + + if (!freerdp_check_event_handles(rfi->instance->context)) { + if (rf_auto_reconnect(rfi)) { + /* Reset the possible reason/error which made us doing many reconnection reattempts and continue */ + remmina_plugin_service->protocol_plugin_set_error(gp, NULL); + continue; + } + fprintf(stderr, "Failed to check FreeRDP event handles\n"); + break; + } + } +} + +int remmina_rdp_load_static_channel_addin(rdpChannels* channels, rdpSettings* settings, char* name, void* data) +{ + TRACE_CALL(__func__); + void* entry; + + entry = freerdp_load_channel_addin_entry(name, NULL, NULL, FREERDP_ADDIN_CHANNEL_STATIC); + if (entry) { + if (freerdp_channels_client_load(channels, settings, entry, data) == 0) { + fprintf(stderr, "loading channel %s\n", name); + return 0; + } + } + + return -1; +} + +/* Send CTRL+ALT+DEL keys keystrokes to the plugin drawing_area widget */ +static void remmina_rdp_send_ctrlaltdel(RemminaProtocolWidget *gp) +{ + TRACE_CALL(__func__); + guint keys[] = { GDK_KEY_Control_L, GDK_KEY_Alt_L, GDK_KEY_Delete }; + rfContext* rfi = GET_PLUGIN_DATA(gp); + + remmina_plugin_service->protocol_plugin_send_keys_signals(rfi->drawing_area, + keys, G_N_ELEMENTS(keys), GDK_KEY_PRESS | GDK_KEY_RELEASE); +} + +static gboolean remmina_rdp_main(RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + const gchar* s; + gchar *sm; + gchar* value; + gint rdpsnd_rate; + gint rdpsnd_channel; + char *rdpsnd_params[3]; + int rdpsnd_nparams; + char rdpsnd_param1[16]; + char rdpsnd_param2[16]; + const gchar* cs; + RemminaFile* remminafile; + rfContext* rfi = GET_PLUGIN_DATA(gp); + gchar *gateway_host; + gint gateway_port; + gint desktopOrientation, desktopScaleFactor, deviceScaleFactor; + gint dynresw, dynresh; + + remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); + + if (!remmina_rdp_tunnel_init(gp)) + return FALSE; + + rfi->settings->AutoReconnectionEnabled = ( remmina_plugin_service->file_get_int(remminafile, "disableautoreconnect", FALSE) ? FALSE : TRUE ); + /* Disable RDP auto reconnection when ssh tunnel is enabled */ + if (remmina_plugin_service->file_get_int(remminafile, "ssh_enabled", FALSE)) { + rfi->settings->AutoReconnectionEnabled = FALSE; + } + + rfi->settings->ColorDepth = remmina_plugin_service->file_get_int(remminafile, "colordepth", 66); + + rfi->settings->SoftwareGdi = TRUE; + + if (rfi->settings->ColorDepth == 0) { + /* RFX (Win7)*/ + rfi->settings->RemoteFxCodec = TRUE; + rfi->settings->SupportGraphicsPipeline = FALSE; + rfi->settings->ColorDepth = 32; + } else if (rfi->settings->ColorDepth == 64) { + /* /gfx:rfx (Win8) */ + rfi->settings->ColorDepth = 32; + rfi->settings->SupportGraphicsPipeline = TRUE; + rfi->settings->GfxH264 = FALSE; + rfi->settings->GfxAVC444 = FALSE; + } else if (rfi->settings->ColorDepth == 65) { + /* /gfx:avc420 (Win8.1) */ + rfi->settings->ColorDepth = 32; + rfi->settings->SupportGraphicsPipeline = TRUE; +#ifdef WITH_GFX_H264 + rfi->settings->GfxH264 = TRUE; + rfi->settings->GfxAVC444 = FALSE; +#endif + } else if (rfi->settings->ColorDepth >= 66) { + /* /gfx:avc444 (Win10) */ + rfi->settings->ColorDepth = 32; + rfi->settings->SupportGraphicsPipeline = TRUE; +#ifdef WITH_GFX_H264 + rfi->settings->GfxH264 = TRUE; + rfi->settings->GfxAVC444 = TRUE; +#endif + } + + rfi->settings->DesktopWidth = remmina_plugin_service->get_profile_remote_width(gp); + rfi->settings->DesktopHeight = remmina_plugin_service->get_profile_remote_height(gp); + dynresw = remmina_plugin_service->file_get_int(remminafile, "dynamic_resolution_width", 0); + dynresh = remmina_plugin_service->file_get_int(remminafile, "dynamic_resolution_height", 0); + + if (rfi->scale == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES && dynresh != 0 && dynresw != 0) { + rfi->settings->DesktopWidth = dynresw; + rfi->settings->DesktopHeight = dynresh; + } + remmina_plugin_service->protocol_plugin_set_width(gp, rfi->settings->DesktopWidth); + remmina_plugin_service->protocol_plugin_set_height(gp, rfi->settings->DesktopHeight); + + if (remmina_plugin_service->file_get_string(remminafile, "username")) + rfi->settings->Username = strdup(remmina_plugin_service->file_get_string(remminafile, "username")); + + if (remmina_plugin_service->file_get_string(remminafile, "domain")) + rfi->settings->Domain = strdup(remmina_plugin_service->file_get_string(remminafile, "domain")); + + s = remmina_plugin_service->file_get_string(remminafile, "password"); + + if (s) { + rfi->settings->Password = strdup(s); + rfi->settings->AutoLogonEnabled = 1; + } + /* Remote Desktop Gateway server address */ + rfi->settings->GatewayEnabled = FALSE; + s = remmina_plugin_service->file_get_string(remminafile, "gateway_server"); + if (s) { + remmina_plugin_service->get_server_port(s, 443, &gateway_host, &gateway_port); + rfi->settings->GatewayHostname = gateway_host; + rfi->settings->GatewayPort = gateway_port; + rfi->settings->GatewayEnabled = TRUE; + rfi->settings->GatewayUseSameCredentials = TRUE; + } + /* Remote Desktop Gateway domain */ + if (remmina_plugin_service->file_get_string(remminafile, "gateway_domain")) { + rfi->settings->GatewayDomain = strdup(remmina_plugin_service->file_get_string(remminafile, "gateway_domain")); + rfi->settings->GatewayUseSameCredentials = FALSE; + } + /* Remote Desktop Gateway username */ + if (remmina_plugin_service->file_get_string(remminafile, "gateway_username")) { + rfi->settings->GatewayUsername = strdup(remmina_plugin_service->file_get_string(remminafile, "gateway_username")); + rfi->settings->GatewayUseSameCredentials = FALSE; + } + /* Remote Desktop Gateway password */ + s = remmina_plugin_service->file_get_string(remminafile, "gateway_password"); + if (s) { + rfi->settings->GatewayPassword = strdup(s); + rfi->settings->GatewayUseSameCredentials = FALSE; + } + /* If no different credentials were provided for the Remote Desktop Gateway + * use the same authentication credentials for the host */ + if (rfi->settings->GatewayEnabled && rfi->settings->GatewayUseSameCredentials) { + g_free(rfi->settings->GatewayDomain); + rfi->settings->GatewayDomain = g_strdup(rfi->settings->Domain); + g_free(rfi->settings->GatewayUsername); + rfi->settings->GatewayUsername = g_strdup(rfi->settings->Username); + g_free(rfi->settings->GatewayPassword); + rfi->settings->GatewayPassword = g_strdup(rfi->settings->Password); + } + /* Remote Desktop Gateway usage */ + if (rfi->settings->GatewayEnabled) + freerdp_set_gateway_usage_method(rfi->settings, + remmina_plugin_service->file_get_int(remminafile, "gateway_usage", FALSE) ? TSC_PROXY_MODE_DETECT : TSC_PROXY_MODE_DIRECT); + /* Certificate ignore */ + rfi->settings->IgnoreCertificate = remmina_plugin_service->file_get_int(remminafile, "cert_ignore", 0); + + /* ClientHostname is internally preallocated to 32 bytes by libfreerdp */ + if ((cs = remmina_plugin_service->file_get_string(remminafile, "clientname"))) { + strncpy(rfi->settings->ClientHostname, cs, FREERDP_CLIENTHOSTNAME_LEN - 1); + }else { + strncpy(rfi->settings->ClientHostname, g_get_host_name(), FREERDP_CLIENTHOSTNAME_LEN - 1); + } + rfi->settings->ClientHostname[FREERDP_CLIENTHOSTNAME_LEN - 1] = 0; + + if (remmina_plugin_service->file_get_string(remminafile, "loadbalanceinfo")) { + rfi->settings->LoadBalanceInfo = (BYTE*)strdup(remmina_plugin_service->file_get_string(remminafile, "loadbalanceinfo")); + rfi->settings->LoadBalanceInfoLength = (UINT32)strlen((char*)rfi->settings->LoadBalanceInfo); + } + + if (remmina_plugin_service->file_get_string(remminafile, "exec")) { + rfi->settings->AlternateShell = strdup(remmina_plugin_service->file_get_string(remminafile, "exec")); + } + + if (remmina_plugin_service->file_get_string(remminafile, "execpath")) { + rfi->settings->ShellWorkingDirectory = strdup(remmina_plugin_service->file_get_string(remminafile, "execpath")); + } + + sm = g_strdup_printf("rdp_quality_%i", remmina_plugin_service->file_get_int(remminafile, "quality", DEFAULT_QUALITY_0)); + value = remmina_plugin_service->pref_get_value(sm); + g_free(sm); + + if (value && value[0]) { + rfi->settings->PerformanceFlags = strtoul(value, NULL, 16); + }else { + switch (remmina_plugin_service->file_get_int(remminafile, "quality", DEFAULT_QUALITY_0)) { + case 9: + rfi->settings->PerformanceFlags = DEFAULT_QUALITY_9; + break; + + case 2: + rfi->settings->PerformanceFlags = DEFAULT_QUALITY_2; + break; + + case 1: + rfi->settings->PerformanceFlags = DEFAULT_QUALITY_1; + break; + + case 0: + default: + rfi->settings->PerformanceFlags = DEFAULT_QUALITY_0; + break; + } + } + g_free(value); + + /* PerformanceFlags bitmask need also to be splitted into BOOL variables + * like rfi->settings->DisableWallpaper, rfi->settings->AllowFontSmoothing... + * or freerdp_get_param_bool() function will return the wrong value + */ + freerdp_performance_flags_split(rfi->settings); + + rfi->settings->KeyboardLayout = remmina_rdp_settings_get_keyboard_layout(); + + if (remmina_plugin_service->file_get_int(remminafile, "console", FALSE)) { + rfi->settings->ConsoleSession = True; + } + + cs = remmina_plugin_service->file_get_string(remminafile, "security"); + + + + if (g_strcmp0(cs, "rdp") == 0) { + rfi->settings->RdpSecurity = True; + rfi->settings->TlsSecurity = False; + rfi->settings->NlaSecurity = False; + rfi->settings->ExtSecurity = False; + rfi->settings->UseRdpSecurityLayer = True; + }else if (g_strcmp0(cs, "tls") == 0) { + rfi->settings->RdpSecurity = False; + rfi->settings->TlsSecurity = True; + rfi->settings->NlaSecurity = False; + rfi->settings->ExtSecurity = False; + }else if (g_strcmp0(cs, "nla") == 0) { + rfi->settings->RdpSecurity = False; + rfi->settings->TlsSecurity = False; + rfi->settings->NlaSecurity = True; + rfi->settings->ExtSecurity = False; + } + + /* This is "-nego" switch of xfreerdp */ + rfi->settings->NegotiateSecurityLayer = True; + + rfi->settings->CompressionEnabled = True; + rfi->settings->FastPathInput = True; + rfi->settings->FastPathOutput = True; + + /* Orientation and scaling settings */ + remmina_rdp_settings_get_orientation_scale_prefs(&desktopOrientation, &desktopScaleFactor, &deviceScaleFactor); + + rfi->settings->DesktopOrientation = desktopOrientation; + if (desktopScaleFactor != 0 && deviceScaleFactor != 0) { + rfi->settings->DesktopScaleFactor = desktopScaleFactor; + rfi->settings->DeviceScaleFactor = deviceScaleFactor; + } + + /* Try to enable "Display Control Virtual Channel Extension", needed to + * dynamically resize remote desktop. This will automatically open + * the "disp" dynamic channel, if available */ + rfi->settings->SupportDisplayControl = TRUE; + + cs = remmina_plugin_service->file_get_string(remminafile, "sound"); + + if (g_strcmp0(cs, "remote") == 0) { + rfi->settings->RemoteConsoleAudio = 1; + }else if (g_str_has_prefix(cs, "local")) { + + rdpsnd_nparams = 0; + rdpsnd_params[rdpsnd_nparams++] = "rdpsnd"; + + cs = strchr(cs, ','); + if (cs) { + rdpsnd_rate = atoi(cs + 1); + if (rdpsnd_rate > 1000 && rdpsnd_rate < 150000) { + snprintf( rdpsnd_param1, sizeof(rdpsnd_param1), "rate:%d", rdpsnd_rate ); + rdpsnd_params[rdpsnd_nparams++] = rdpsnd_param1; + cs = strchr(cs + 1, ','); + if (cs) { + rdpsnd_channel = atoi(cs + 1); + if (rdpsnd_channel >= 1 && rdpsnd_channel <= 2) { + snprintf( rdpsnd_param2, sizeof(rdpsnd_param2), "channel:%d", rdpsnd_channel ); + rdpsnd_params[rdpsnd_nparams++] = rdpsnd_param2; + } + } + } + } + + freerdp_client_add_static_channel(rfi->settings, rdpsnd_nparams, (char**)rdpsnd_params); + + } + + if ( remmina_plugin_service->file_get_int(remminafile, "microphone", FALSE) ? TRUE : FALSE ) { + char* p[1]; + int count; + + count = 1; + p[0] = "audin"; + + freerdp_client_add_dynamic_channel(rfi->settings, count, p); + } + + rfi->settings->RedirectClipboard = ( remmina_plugin_service->file_get_int(remminafile, "disableclipboard", FALSE) ? FALSE : TRUE ); + + cs = remmina_plugin_service->file_get_string(remminafile, "sharefolder"); + + if (cs && cs[0] == '/') { + RDPDR_DRIVE* drive; + gsize sz; + + drive = (RDPDR_DRIVE*)malloc(sizeof(RDPDR_DRIVE)); + ZeroMemory(drive, sizeof(RDPDR_DRIVE)); + + s = strrchr( cs, '/' ); + if ( s == NULL || s[1] == 0 ) + s = remmina_rdp_plugin_default_drive_name; + else + s++; + sm = g_convert_with_fallback(s, -1, "ascii", "utf-8", "_", NULL, &sz, NULL); + + drive->Type = RDPDR_DTYP_FILESYSTEM; + drive->Name = _strdup(sm); + drive->Path = _strdup(cs); + g_free(sm); + + freerdp_device_collection_add(rfi->settings, (RDPDR_DEVICE*)drive); + rfi->settings->DeviceRedirection = TRUE; + } + + if (remmina_plugin_service->file_get_int(remminafile, "shareprinter", FALSE)) { + RDPDR_PRINTER* printer; + printer = (RDPDR_PRINTER*)malloc(sizeof(RDPDR_PRINTER)); + ZeroMemory(printer, sizeof(RDPDR_PRINTER)); + + printer->Type = RDPDR_DTYP_PRINT; + + rfi->settings->DeviceRedirection = TRUE; + rfi->settings->RedirectPrinters = TRUE; + + freerdp_device_collection_add(rfi->settings, (RDPDR_DEVICE*)printer); + } + + if (remmina_plugin_service->file_get_int(remminafile, "sharesmartcard", FALSE)) { + RDPDR_SMARTCARD* smartcard; + smartcard = (RDPDR_SMARTCARD*)malloc(sizeof(RDPDR_SMARTCARD)); + ZeroMemory(smartcard, sizeof(RDPDR_SMARTCARD)); + + smartcard->Type = RDPDR_DTYP_SMARTCARD; + + smartcard->Name = _strdup("scard"); + + rfi->settings->DeviceRedirection = TRUE; + rfi->settings->RedirectSmartCards = TRUE; + + freerdp_device_collection_add(rfi->settings, (RDPDR_DEVICE*)smartcard); + } + + if (!freerdp_connect(rfi->instance)) { + if (!rfi->user_cancelled) { + UINT32 e; + + e = freerdp_get_last_error(rfi->instance->context); + + switch (e) { + case FREERDP_ERROR_AUTHENTICATION_FAILED: + case STATUS_LOGON_FAILURE: // wrong return code from FreeRDP introduced at the end of July 2016 ? (fixed with b86c0ba) +#ifdef FREERDP_ERROR_CONNECT_LOGON_FAILURE + case FREERDP_ERROR_CONNECT_LOGON_FAILURE: +#endif + remmina_plugin_service->protocol_plugin_set_error(gp, _("Authentication to RDP server %s failed.\nCheck username, password and domain."), + rfi->settings->ServerHostname ); + // Invalidate the saved password, so the user will be re-asked at next logon + remmina_plugin_service->file_unsave_password(remminafile); + break; + case STATUS_ACCOUNT_LOCKED_OUT: +#ifdef FREERDP_ERROR_CONNECT_ACCOUNT_LOCKED_OUT + case FREERDP_ERROR_CONNECT_ACCOUNT_LOCKED_OUT: +#endif + remmina_plugin_service->protocol_plugin_set_error(gp, _("Access to RDP server %s failed.\nAccount is locked out."), + rfi->settings->ServerHostname ); + break; + case STATUS_ACCOUNT_EXPIRED: +#ifdef FREERDP_ERROR_CONNECT_ACCOUNT_EXPIRED + case FREERDP_ERROR_CONNECT_ACCOUNT_EXPIRED: +#endif + remmina_plugin_service->protocol_plugin_set_error(gp, _("Access to RDP server %s failed.\nAccount is expired."), + rfi->settings->ServerHostname ); + break; + case STATUS_PASSWORD_EXPIRED: +#ifdef FREERDP_ERROR_CONNECT_PASSWORD_EXPIRED + case FREERDP_ERROR_CONNECT_PASSWORD_EXPIRED: +#endif + remmina_plugin_service->protocol_plugin_set_error(gp, _("Access to RDP server %s failed.\nPassword expired."), + rfi->settings->ServerHostname ); + break; + case STATUS_ACCOUNT_DISABLED: +#ifdef FREERDP_ERROR_CONNECT_ACCOUNT_DISABLED + case FREERDP_ERROR_CONNECT_ACCOUNT_DISABLED: +#endif + remmina_plugin_service->protocol_plugin_set_error(gp, _("Access to RDP server %s failed.\nAccount is disabled."), + rfi->settings->ServerHostname ); + break; + case STATUS_ACCOUNT_RESTRICTION: +#ifdef FREERDP_ERROR_CONNECT_ACCOUNT_RESTRICTION + case FREERDP_ERROR_CONNECT_ACCOUNT_RESTRICTION: +#endif + remmina_plugin_service->protocol_plugin_set_error(gp, _("Access to RDP server %s failed.\nAccount has restrictions."), + rfi->settings->ServerHostname ); + break; + case FREERDP_ERROR_CONNECT_FAILED: + remmina_plugin_service->protocol_plugin_set_error(gp, _("Connection to RDP server %s failed."), rfi->settings->ServerHostname ); + break; + case FREERDP_ERROR_DNS_NAME_NOT_FOUND: + remmina_plugin_service->protocol_plugin_set_error(gp, _("Unable to find the address of RDP server %s."), rfi->settings->ServerHostname ); + break; + case FREERDP_ERROR_TLS_CONNECT_FAILED: + remmina_plugin_service->protocol_plugin_set_error(gp, + _("Error connecting to RDP server %s. TLS connection failed. Check that client and server support a common TLS version."), rfi->settings->ServerHostname ); + break; + case FREERDP_ERROR_SECURITY_NEGO_CONNECT_FAILED: + remmina_plugin_service->protocol_plugin_set_error(gp, _("Unable to establish a connection to RDP server %s."), rfi->settings->ServerHostname ); + break; +#ifdef FREERDP_ERROR_POST_CONNECT_FAILED + case FREERDP_ERROR_POST_CONNECT_FAILED: + /* remmina_rdp_post_connect() returned FALSE to libfreerdp. We saved the error on rfi->postconnect_error */ + switch(rfi->postconnect_error) { + case REMMINA_POSTCONNECT_ERROR_OK: + /* We should never come here */ + remmina_plugin_service->protocol_plugin_set_error(gp, _("Unable to connect to RDP server %s."), rfi->settings->ServerHostname ); + break; + case REMMINA_POSTCONNECT_ERROR_GDI_INIT: + remmina_plugin_service->protocol_plugin_set_error(gp, _("Unable to initialize libfreerdp gdi") ); + break; + case REMMINA_POSTCONNECT_ERROR_NO_H264: + remmina_plugin_service->protocol_plugin_set_error(gp, _("You requested an H264 GFX mode for server %s, but your libfreerdp does not support H264. Please check Color Depth settings."), rfi->settings->ServerHostname); + break; + } + break; +#endif + default: + g_printf("%08X %08X\n", e, (unsigned)ERRCONNECT_POST_CONNECT_FAILED); + remmina_plugin_service->protocol_plugin_set_error(gp, _("Unable to connect to RDP server %s"), rfi->settings->ServerHostname); + break; + } + + } + + return FALSE; + } + + remmina_rdp_main_loop(gp); + + return TRUE; +} + +static gpointer remmina_rdp_main_thread(gpointer data) +{ + TRACE_CALL(__func__); + RemminaProtocolWidget* gp; + rfContext* rfi; + + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); + CANCEL_ASYNC + + gp = (RemminaProtocolWidget*)data; + rfi = GET_PLUGIN_DATA(gp); + remmina_rdp_main(gp); + rfi->thread = 0; + + + /* Signal main thread that we closed the connection. But wait 200ms, because we may + * have outstaiding events to process in the meanwhile */ + g_timeout_add(200, ((GSourceFunc)remmina_plugin_service->protocol_plugin_close_connection), gp); + + return NULL; +} + +static void remmina_rdp_init(RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + freerdp* instance; + rfContext* rfi; + + instance = freerdp_new(); + instance->PreConnect = remmina_rdp_pre_connect; + instance->PostConnect = remmina_rdp_post_connect; + instance->Authenticate = remmina_rdp_authenticate; + instance->VerifyCertificate = remmina_rdp_verify_certificate; + instance->VerifyChangedCertificate = remmina_rdp_verify_changed_certificate; + + instance->ContextSize = sizeof(rfContext); + freerdp_context_new(instance); + rfi = (rfContext*)instance->context; + + g_object_set_data_full(G_OBJECT(gp), "plugin-data", rfi, free); + + rfi->protocol_widget = gp; + rfi->instance = instance; + rfi->settings = instance->settings; + rfi->connected = False; + rfi->is_reconnecting = False; + + freerdp_register_addin_provider(freerdp_channels_load_static_addin_entry, 0); + + remmina_rdp_event_init(gp); +} + +static gboolean remmina_rdp_open_connection(RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + rfContext* rfi = GET_PLUGIN_DATA(gp); + + rfi->scale = remmina_plugin_service->remmina_protocol_widget_get_current_scale_mode(gp); + + if (pthread_create(&rfi->thread, NULL, remmina_rdp_main_thread, gp)) { + remmina_plugin_service->protocol_plugin_set_error(gp, "%s", + "Failed to initialize pthread. Falling back to non-thread mode..."); + + rfi->thread = 0; + + return FALSE; + } + + return TRUE; +} + +static gboolean remmina_rdp_close_connection(RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + rfContext* rfi = GET_PLUGIN_DATA(gp); + freerdp* instance; + + if (!remmina_plugin_service->is_main_thread()) { + g_printf("WARNING: %s called on a subthread, may not work or crash remmina.\n", __func__); + } + + /* Immediately deatch GTK clipboard from this connection */ + remmina_rdp_cliprdr_detach_owner(gp); + + if (freerdp_get_last_error(rfi->instance->context) == 0x10005) { + remmina_plugin_service->protocol_plugin_set_error(gp, "Another user connected to the server (%s), forcing the disconnection of the current connection.", rfi->settings->ServerHostname); + } + instance = rfi->instance; + if (rfi->thread) { + rfi->thread_cancelled = TRUE; // Avoid all rf_queue function to run + pthread_cancel(rfi->thread); + + if (rfi->thread) + pthread_join(rfi->thread, NULL); + + } + + if (instance) { + if ( rfi->connected ) { + freerdp_disconnect(instance); + rfi->connected = False; + } + } + + if (rfi->hdc) { + gdi_DeleteDC(rfi->hdc); + rfi->hdc = NULL; + } + + remmina_rdp_clipboard_free(rfi); + if (rfi->rfx_context) { + rfx_context_free(rfi->rfx_context); + rfi->rfx_context = NULL; + } + + if (instance) { + gdi_free(instance); + cache_free(instance->context->cache); + instance->context->cache = NULL; + } + + /* Destroy event queue. Pending async events will be discarded. Should we flush it ? */ + remmina_rdp_event_uninit(gp); + + if (instance) { + freerdp_context_free(instance); /* context is rfContext* rfi */ + freerdp_free(instance); /* This implicitly frees instance->context and rfi is no longer valid */ + } + + /* Remove instance->context from gp object data to avoid double free */ + g_object_steal_data(G_OBJECT(gp), "plugin-data"); + + /* Now let remmina to complete its disconnection tasks */ + remmina_plugin_service->protocol_plugin_emit_signal(gp, "disconnect"); + + return FALSE; +} + +static gboolean remmina_rdp_query_feature(RemminaProtocolWidget* gp, const RemminaProtocolFeature* feature) +{ + TRACE_CALL(__func__); + return TRUE; +} + +static void remmina_rdp_call_feature(RemminaProtocolWidget* gp, const RemminaProtocolFeature* feature) +{ + TRACE_CALL(__func__); + RemminaFile* remminafile; + rfContext* rfi = GET_PLUGIN_DATA(gp); + + remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); + + switch (feature->id) { + case REMMINA_RDP_FEATURE_UNFOCUS: + remmina_rdp_event_unfocus(gp); + break; + + case REMMINA_RDP_FEATURE_SCALE: + rfi->scale = remmina_plugin_service->remmina_protocol_widget_get_current_scale_mode(gp); + remmina_rdp_event_update_scale(gp); + break; + + case REMMINA_RDP_FEATURE_DYNRESUPDATE: + break; + + case REMMINA_RDP_FEATURE_TOOL_REFRESH: + gtk_widget_queue_draw_area(rfi->drawing_area, 0, 0, + remmina_plugin_service->protocol_plugin_get_width(gp), + remmina_plugin_service->protocol_plugin_get_height(gp)); + break; + + case REMMINA_RDP_FEATURE_TOOL_SENDCTRLALTDEL: + remmina_rdp_send_ctrlaltdel(gp); + break; + + default: + break; + } +} + +/* Send a keystroke to the plugin window */ +static void remmina_rdp_keystroke(RemminaProtocolWidget *gp, const guint keystrokes[], const gint keylen) +{ + TRACE_CALL(__func__); + rfContext* rfi = GET_PLUGIN_DATA(gp); + remmina_plugin_service->protocol_plugin_send_keys_signals(rfi->drawing_area, + keystrokes, keylen, GDK_KEY_PRESS | GDK_KEY_RELEASE); + return; +} + +static gboolean remmina_rdp_get_screenshot(RemminaProtocolWidget *gp, RemminaPluginScreenshotData *rpsd) +{ + rfContext* rfi = GET_PLUGIN_DATA(gp); + rdpGdi* gdi; + size_t szmem; + + UINT32 bytesPerPixel; + UINT32 bitsPerPixel; + + if (!rfi) + return FALSE; + + gdi = ((rdpContext*)rfi)->gdi; + + bytesPerPixel = GetBytesPerPixel(gdi->hdc->format); + bitsPerPixel = GetBitsPerPixel(gdi->hdc->format); + + /** @todo we should lock freerdp subthread to update rfi->primary_buffer, rfi->gdi and w/h, + * from here to memcpy, but... how ? */ + + szmem = gdi->width * gdi->height * bytesPerPixel; + + remmina_plugin_service->log_printf("[RDP] allocating %zu bytes for a full screenshot\n", szmem); + rpsd->buffer = malloc(szmem); + if (!rpsd->buffer) { + remmina_plugin_service->log_printf("[RDP] unable to allocate %zu bytes for a full screenshot\n", szmem); + return FALSE; + } + rpsd->width = gdi->width; + rpsd->height = gdi->height; + rpsd->bitsPerPixel = bitsPerPixel; + rpsd->bytesPerPixel = bytesPerPixel; + + memcpy(rpsd->buffer, gdi->primary_buffer, szmem); + + /* Returning TRUE instruct also the caller to deallocate rpsd->buffer */ + return TRUE; + +} + +/* Array of key/value pairs for color depths */ +static gpointer colordepth_list[] = +{ + /* 1st one is the default in a new install */ +#ifdef WITH_GFX_H264 + "66", N_("GFX AVC444 (32 bpp)"), + "65", N_("GFX AVC420 (32 bpp)"), +#endif + "64", N_("GFX RFX (32 bpp)"), + "0", N_("RemoteFX (32 bpp)"), + "32", N_("True color (32 bpp)"), + "24", N_("True color (24 bpp)"), + "16", N_("High color (16 bpp)"), + "15", N_("High color (15 bpp)"), + "8", N_("256 colors (8 bpp)"), + NULL +}; + +/* Array of key/value pairs for quality selection */ +static gpointer quality_list[] = +{ + "0", N_("Poor (fastest)"), + "1", N_("Medium"), + "2", N_("Good"), + "9", N_("Best (slowest)"), + NULL +}; + +/* Array of key/value pairs for sound options */ +static gpointer sound_list[] = +{ + "off", N_("Off"), + "local", N_("Local"), + "local,11025,1", N_("Local - low quality"), + "local,22050,2", N_("Local - medium quality"), + "local,44100,2", N_("Local - high quality"), + "remote", N_("Remote"), + NULL +}; + +/* Array of key/value pairs for security */ +static gpointer security_list[] = +{ + "", N_("Negotiate"), + "nla", "NLA", + "tls", "TLS", + "rdp", "RDP", + NULL +}; + +/* Array of RemminaProtocolSetting for basic settings. + * Each item is composed by: + * a) RemminaProtocolSettingType for setting type + * b) Setting name + * c) Setting description + * d) Compact disposition + * e) Values for REMMINA_PROTOCOL_SETTING_TYPE_SELECT or REMMINA_PROTOCOL_SETTING_TYPE_COMBO + * f) Unused pointer + */ +static const RemminaProtocolSetting remmina_rdp_basic_settings[] = +{ + { REMMINA_PROTOCOL_SETTING_TYPE_SERVER, "server", NULL, FALSE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "username", N_("User name"), FALSE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_PASSWORD, "password", N_("User password"), FALSE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "domain", N_("Domain"), FALSE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_RESOLUTION, "resolution", NULL, FALSE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "colordepth", N_("Color depth"), FALSE, colordepth_list, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_FOLDER, "sharefolder", N_("Share folder"), FALSE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableautoreconnect", N_("Disable automatic reconnection"), FALSE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_END, NULL, NULL, FALSE, NULL, NULL} +}; + +/* Array of RemminaProtocolSetting for advanced settings. + * Each item is composed by: + * a) RemminaProtocolSettingType for setting type + * b) Setting name + * c) Setting description + * d) Compact disposition + * e) Values for REMMINA_PROTOCOL_SETTING_TYPE_SELECT or REMMINA_PROTOCOL_SETTING_TYPE_COMBO + * f) Unused pointer + */ +static const RemminaProtocolSetting remmina_rdp_advanced_settings[] = +{ + { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "quality", N_("Quality"), FALSE, quality_list, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "sound", N_("Sound"), FALSE, sound_list, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "security", N_("Security"), FALSE, security_list, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "gateway_server", N_("RD Gateway server"), FALSE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "gateway_username", N_("RD Gateway username"), FALSE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_PASSWORD, "gateway_password", N_("RD Gateway password"), FALSE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "gateway_domain", N_("RD Gateway domain"), FALSE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "clientname", N_("Client name"), FALSE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "exec", N_("Startup program"), FALSE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "execpath", N_("Startup path"), FALSE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "loadbalanceinfo", N_("Load Balance Info"), FALSE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "cert_ignore", N_("Ignore certificate"), TRUE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "microphone", N_("Redirect local microphone"), TRUE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "sharesmartcard", N_("Share smartcard"), TRUE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "shareprinter", N_("Share local printers"), TRUE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disablepasswordstoring", N_("Disable password storing"), TRUE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableclipboard", N_("Disable clipboard sync"), TRUE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "console", N_("Attach to console (2003/2003 R2)"), FALSE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "gateway_usage", N_("Server detection using RD Gateway"), FALSE, NULL, NULL}, + { REMMINA_PROTOCOL_SETTING_TYPE_END, NULL, NULL, FALSE, NULL, NULL} +}; + +/* Array for available features. + * The last element of the array must be REMMINA_PROTOCOL_FEATURE_TYPE_END. */ +static const RemminaProtocolFeature remmina_rdp_features[] = +{ + { REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_RDP_FEATURE_TOOL_REFRESH, N_("Refresh"), NULL, NULL}, + { REMMINA_PROTOCOL_FEATURE_TYPE_SCALE, REMMINA_RDP_FEATURE_SCALE, NULL, NULL, NULL}, + { REMMINA_PROTOCOL_FEATURE_TYPE_DYNRESUPDATE, REMMINA_RDP_FEATURE_DYNRESUPDATE, NULL, NULL, NULL}, + { REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_RDP_FEATURE_TOOL_SENDCTRLALTDEL, N_("Send Ctrl+Alt+Delete"), NULL, NULL}, + { REMMINA_PROTOCOL_FEATURE_TYPE_UNFOCUS, REMMINA_RDP_FEATURE_UNFOCUS, NULL, NULL, NULL}, + { REMMINA_PROTOCOL_FEATURE_TYPE_END, 0, NULL, NULL, NULL} +}; + +/* Protocol plugin definition and features */ +static RemminaProtocolPlugin remmina_rdp = +{ + REMMINA_PLUGIN_TYPE_PROTOCOL, // Type + "RDP", // Name + N_("RDP - Remote Desktop Protocol"), // Description + GETTEXT_PACKAGE, // Translation domain + REMMINA_PLUGIN_RDP_VERSION, // Version number + "remmina-rdp", // Icon for normal connection + "remmina-rdp-ssh", // Icon for SSH connection + remmina_rdp_basic_settings, // Array for basic settings + remmina_rdp_advanced_settings, // Array for advanced settings + REMMINA_PROTOCOL_SSH_SETTING_TUNNEL, // SSH settings type + remmina_rdp_features, // Array for available features + remmina_rdp_init, // Plugin initialization + remmina_rdp_open_connection, // Plugin open connection + remmina_rdp_close_connection, // Plugin close connection + remmina_rdp_query_feature, // Query for available features + remmina_rdp_call_feature, // Call a feature + remmina_rdp_keystroke, // Send a keystroke + remmina_rdp_get_screenshot // Screenshot +}; + +/* File plugin definition and features */ +static RemminaFilePlugin remmina_rdpf = +{ + REMMINA_PLUGIN_TYPE_FILE, // Type + "RDPF", // Name + N_("RDP - RDP File Handler"), // Description + GETTEXT_PACKAGE, // Translation domain + REMMINA_PLUGIN_RDP_VERSION, // Version number + remmina_rdp_file_import_test, // Test import function + remmina_rdp_file_import, // Import function + remmina_rdp_file_export_test, // Test export function + remmina_rdp_file_export, // Export function + NULL +}; + +/* Preferences plugin definition and features */ +static RemminaPrefPlugin remmina_rdps = +{ + REMMINA_PLUGIN_TYPE_PREF, // Type + "RDPS", // Name + N_("RDP - Preferences"), // Description + GETTEXT_PACKAGE, // Translation domain + REMMINA_PLUGIN_RDP_VERSION, // Version number + "RDP", // Label + remmina_rdp_settings_new // Preferences body function +}; + +G_MODULE_EXPORT gboolean remmina_plugin_entry(RemminaPluginService* service) +{ + int vermaj, vermin, verrev; + + TRACE_CALL(__func__); + remmina_plugin_service = service; + + /* Check that we are linked to the correct version of libfreerdp */ + + freerdp_get_version(&vermaj, &vermin, &verrev); + if (vermaj < FREERDP_REQUIRED_MAJOR || + (vermaj == FREERDP_REQUIRED_MAJOR && ( vermin < FREERDP_REQUIRED_MINOR || + (vermin == FREERDP_REQUIRED_MINOR && verrev < FREERDP_REQUIRED_REVISION) ) ) ) { + g_printf("Unable to load RDP plugin due to bad freerdp library version. Required " + "libfreerdp version is at least %d.%d.%d but we found libfreerdp version %d.%d.%d\n", + FREERDP_REQUIRED_MAJOR, FREERDP_REQUIRED_MINOR, FREERDP_REQUIRED_REVISION, + vermaj, vermin, verrev ); + return FALSE; + } + + bindtextdomain(GETTEXT_PACKAGE, REMMINA_RUNTIME_LOCALEDIR); + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); + + if (!service->register_plugin((RemminaPlugin*)&remmina_rdp)) + return FALSE; + + remmina_rdpf.export_hints = _("Export connection in Windows .rdp file format"); + + if (!service->register_plugin((RemminaPlugin*)&remmina_rdpf)) + return FALSE; + + if (!service->register_plugin((RemminaPlugin*)&remmina_rdps)) + return FALSE; + + remmina_rdp_settings_init(); + + return TRUE; +} + diff --git a/plugins/rdp/rdp_plugin.h b/plugins/rdp/rdp_plugin.h new file mode 100644 index 000000000..9a43ee6aa --- /dev/null +++ b/plugins/rdp/rdp_plugin.h @@ -0,0 +1,309 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2010-2011 Vic Lee + * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo + * Copyright (C) 2016-2017 Antenore Gatta, Giovanni Panozzo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + * + */ + +#pragma once + +#include "common/remmina_plugin.h" +#include <freerdp/freerdp.h> +#include <freerdp/version.h> +#include <freerdp/channels/channels.h> +#include <freerdp/codec/color.h> +#include <freerdp/codec/rfx.h> +#include <freerdp/gdi/gdi.h> +#include <freerdp/gdi/dc.h> +#include <freerdp/gdi/region.h> +#include <freerdp/client/cliprdr.h> +#include <freerdp/client/disp.h> +#include <gdk/gdkx.h> + +#include <winpr/clipboard.h> + +typedef struct rf_context rfContext; + +#define GET_PLUGIN_DATA(gp) (rfContext*)g_object_get_data(G_OBJECT(gp), "plugin-data") + +#define DEFAULT_QUALITY_0 0x6f +#define DEFAULT_QUALITY_1 0x07 +#define DEFAULT_QUALITY_2 0x01 +#define DEFAULT_QUALITY_9 0x80 + +#define REMMINA_PLUGIN_RDP_VERSION "RDP Plugin: " VERSION " (git " REMMINA_GIT_REVISION \ + "), FreeRDP lib: " FREERDP_VERSION_FULL " (git " GIT_REVISION ")" + +extern RemminaPluginService* remmina_plugin_service; + +struct rf_clipboard { + rfContext* rfi; + CliprdrClientContext* context; + wClipboard* system; + int requestedFormatId; + + UINT32 format; + gulong clipboard_handler; + + pthread_mutex_t transfer_clip_mutex; + pthread_cond_t transfer_clip_cond; + enum { SCDW_NONE, SCDW_BUSY_WAIT, SCDW_ASYNCWAIT } srv_clip_data_wait; + gpointer srv_data; + +}; +typedef struct rf_clipboard rfClipboard; + + +struct rf_pointer { + rdpPointer pointer; + GdkCursor* cursor; +}; +typedef struct rf_pointer rfPointer; + +struct rf_bitmap { + rdpBitmap bitmap; + Pixmap pixmap; + cairo_surface_t* surface; +}; +typedef struct rf_bitmap rfBitmap; + +struct rf_glyph { + rdpGlyph glyph; + Pixmap pixmap; +}; +typedef struct rf_glyph rfGlyph; + + +typedef enum { + REMMINA_RDP_EVENT_TYPE_SCANCODE, + REMMINA_RDP_EVENT_TYPE_SCANCODE_UNICODE, + REMMINA_RDP_EVENT_TYPE_MOUSE, + REMMINA_RDP_EVENT_TYPE_CLIPBOARD_SEND_CLIENT_FORMAT_LIST, + REMMINA_RDP_EVENT_TYPE_CLIPBOARD_SEND_CLIENT_FORMAT_DATA_RESPONSE, + REMMINA_RDP_EVENT_TYPE_CLIPBOARD_SEND_CLIENT_FORMAT_DATA_REQUEST, + REMMINA_RDP_EVENT_TYPE_SEND_MONITOR_LAYOUT +} RemminaPluginRdpEventType; + +struct remmina_plugin_rdp_event { + RemminaPluginRdpEventType type; + union { + struct { + BOOL up; + BOOL extended; + UINT8 key_code; + UINT32 unicode_code; + } key_event; + struct { + UINT16 flags; + UINT16 x; + UINT16 y; + BOOL extended; + } mouse_event; + struct { + CLIPRDR_FORMAT_LIST* pFormatList; + } clipboard_formatlist; + struct { + CLIPRDR_FORMAT_DATA_RESPONSE* pFormatDataResponse; + } clipboard_formatdataresponse; + struct { + CLIPRDR_FORMAT_DATA_REQUEST* pFormatDataRequest; + } clipboard_formatdatarequest; + struct { + gint width; + gint height; + gint desktopOrientation; + gint desktopScaleFactor; + gint deviceScaleFactor; + } monitor_layout; + }; +}; +typedef struct remmina_plugin_rdp_event RemminaPluginRdpEvent; + +typedef enum { + REMMINA_RDP_UI_UPDATE_REGION = 0, + REMMINA_RDP_UI_CONNECTED, + REMMINA_RDP_UI_RECONNECT_PROGRESS, + REMMINA_RDP_UI_CURSOR, + REMMINA_RDP_UI_RFX, + REMMINA_RDP_UI_NOCODEC, + REMMINA_RDP_UI_CLIPBOARD, + REMMINA_RDP_UI_EVENT +} RemminaPluginRdpUiType; + +typedef enum { + REMMINA_RDP_UI_CLIPBOARD_FORMATLIST, + REMMINA_RDP_UI_CLIPBOARD_GET_DATA, + REMMINA_RDP_UI_CLIPBOARD_SET_DATA, + REMMINA_RDP_UI_CLIPBOARD_SET_CONTENT +} RemminaPluginRdpUiClipboardType; + +typedef enum { + REMMINA_RDP_POINTER_NEW, + REMMINA_RDP_POINTER_FREE, + REMMINA_RDP_POINTER_SET, + REMMINA_RDP_POINTER_NULL, + REMMINA_RDP_POINTER_DEFAULT, + REMMINA_RDP_POINTER_SETPOS +} RemminaPluginRdpUiPointerType; + +typedef enum { + REMMINA_RDP_UI_EVENT_UPDATE_SCALE +} RemminaPluginRdpUiEeventType; + +struct remmina_plugin_rdp_ui_object { + RemminaPluginRdpUiType type; + gboolean sync; + gboolean complete; + pthread_mutex_t sync_wait_mutex; + pthread_cond_t sync_wait_cond; + union { + struct { + gint x; + gint y; + gint width; + gint height; + } region; + struct { + rdpContext* context; + rfPointer* pointer; + RemminaPluginRdpUiPointerType type; + } cursor; + struct { + gint left; + gint top; + RFX_MESSAGE* message; + } rfx; + struct { + gint left; + gint top; + gint width; + gint height; + UINT8* bitmap; + } nocodec; + struct { + RemminaPluginRdpUiClipboardType type; + GtkTargetList* targetlist; + UINT32 format; + rfClipboard* clipboard; + gpointer data; + } clipboard; + struct { + RemminaPluginRdpUiEeventType type; + } event; + struct { + gint x; + gint y; + } pos; + }; + /* We can also return values here, usually integers*/ + int retval; + /* Some functions also may return a pointer. */ + void *retptr; +}; + +struct rf_context { + rdpContext _p; + + RemminaProtocolWidget* protocol_widget; + + /* main */ + rdpSettings* settings; + freerdp* instance; + + pthread_t thread; + RemminaScaleMode scale; + gboolean user_cancelled; + gboolean thread_cancelled; + + CliprdrClientContext* cliprdr; + DispClientContext* dispcontext; + + RDP_PLUGIN_DATA rdpdr_data[5]; + RDP_PLUGIN_DATA drdynvc_data[5]; + gchar rdpsnd_options[20]; + + RFX_CONTEXT* rfx_context; + + gboolean connected; + gboolean is_reconnecting; + int reconnect_maxattempts; + int reconnect_nattempt; + + gboolean sw_gdi; + GtkWidget* drawing_area; + gint scale_width; + gint scale_height; + gdouble scale_x; + gdouble scale_y; + guint delayed_monitor_layout_handler; + gboolean use_client_keymap; + + HGDI_DC hdc; + gint srcBpp; + GdkDisplay* display; + GdkVisual* visual; + cairo_surface_t* surface; + cairo_format_t cairo_format; + gint bpp; + gint width; + gint height; + gint scanline_pad; + gint* colormap; + UINT8* primary_buffer; + + guint object_id_seq; + GHashTable* object_table; + + GAsyncQueue* ui_queue; + pthread_mutex_t ui_queue_mutex; + guint ui_handler; + + GArray* pressed_keys; + GAsyncQueue* event_queue; + gint event_pipe[2]; + HANDLE event_handle; + + rfClipboard clipboard; + + enum { REMMINA_POSTCONNECT_ERROR_OK = 0, REMMINA_POSTCONNECT_ERROR_GDI_INIT = 1, REMMINA_POSTCONNECT_ERROR_NO_H264 } postconnect_error; +}; + +typedef struct remmina_plugin_rdp_ui_object RemminaPluginRdpUiObject; + +void rf_init(RemminaProtocolWidget* gp); +void rf_uninit(RemminaProtocolWidget* gp); +void rf_get_fds(RemminaProtocolWidget* gp, void** rfds, int* rcount); +BOOL rf_check_fds(RemminaProtocolWidget* gp); +void rf_object_free(RemminaProtocolWidget* gp, RemminaPluginRdpUiObject* obj); + +void remmina_rdp_event_event_push(RemminaProtocolWidget* gp, const RemminaPluginRdpEvent* e); + diff --git a/plugins/rdp/rdp_settings.c b/plugins/rdp/rdp_settings.c new file mode 100644 index 000000000..592feae07 --- /dev/null +++ b/plugins/rdp/rdp_settings.c @@ -0,0 +1,640 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2010-2011 Vic Lee + * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo + * Copyright (C) 2016-2017 Antenore Gatta, Giovanni Panozzo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + * + */ + +#include "rdp_plugin.h" +#include "rdp_settings.h" +#include <freerdp/locale/keyboard.h> + +static guint keyboard_layout = 0; +static guint rdp_keyboard_layout = 0; + +static void remmina_rdp_settings_kbd_init(void) +{ + TRACE_CALL(__func__); + keyboard_layout = freerdp_keyboard_init(rdp_keyboard_layout); +} + +void remmina_rdp_settings_init(void) +{ + TRACE_CALL(__func__); + gchar* value; + + value = remmina_plugin_service->pref_get_value("rdp_keyboard_layout"); + + if (value && value[0]) + rdp_keyboard_layout = strtoul(value, NULL, 16); + + g_free(value); + + remmina_rdp_settings_kbd_init(); +} + +guint remmina_rdp_settings_get_keyboard_layout(void) +{ + TRACE_CALL(__func__); + return keyboard_layout; +} + +#define REMMINA_TYPE_PLUGIN_RDPSET_GRID (remmina_rdp_settings_grid_get_type()) +#define REMMINA_RDPSET_GRID(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), REMMINA_TYPE_PLUGIN_RDPSET_GRID, RemminaPluginRdpsetGrid)) +#define REMMINA_RDPSET_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), REMMINA_TYPE_PLUGIN_RDPSET_GRID, RemminaPluginRdpsetGridClass)) +#define REMMINA_IS_PLUGIN_RDPSET_GRID(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), REMMINA_TYPE_PLUGIN_RDPSET_GRID)) +#define REMMINA_IS_PLUGIN_RDPSET_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), REMMINA_TYPE_PLUGIN_RDPSET_GRID)) +#define REMMINA_RDPSET_GRID_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), REMMINA_TYPE_PLUGIN_RDPSET_GRID, RemminaPluginRdpsetGridClass)) + +typedef struct _RemminaPluginRdpsetGrid { + GtkGrid grid; + + GtkWidget* keyboard_layout_label; + GtkWidget* keyboard_layout_combo; + GtkListStore* keyboard_layout_store; + + GtkWidget* quality_combo; + GtkListStore* quality_store; + GtkWidget* wallpaper_check; + GtkWidget* windowdrag_check; + GtkWidget* menuanimation_check; + GtkWidget* theme_check; + GtkWidget* cursorshadow_check; + GtkWidget* cursorblinking_check; + GtkWidget* fontsmoothing_check; + GtkWidget* composition_check; + GtkWidget* use_client_keymap_check; + + /* FreeRDP /scale-desktop: Scaling of desktop app */ + GtkWidget* desktop_scale_factor_spin; + /* FreeRDP /scale-device: Scaling of appstore app */ + GtkListStore* device_scale_factor_store; + GtkWidget* device_scale_factor_combo; + /* FreeRDP /orientation: Orientation of display */ + GtkListStore* desktop_orientation_store; + GtkWidget* desktop_orientation_combo; + + guint quality_values[10]; +} RemminaPluginRdpsetGrid; + +typedef struct _RemminaPluginRdpsetGridClass { + GtkGridClass parent_class; +} RemminaPluginRdpsetGridClass; + +GType remmina_rdp_settings_grid_get_type(void) G_GNUC_CONST; + +G_DEFINE_TYPE(RemminaPluginRdpsetGrid, remmina_rdp_settings_grid, GTK_TYPE_GRID) + +static void remmina_rdp_settings_grid_class_init(RemminaPluginRdpsetGridClass* klass) +{ + TRACE_CALL(__func__); +} + +static void remmina_rdp_settings_grid_destroy(GtkWidget* widget, gpointer data) +{ + TRACE_CALL(__func__); + gchar* s; + guint new_layout; + GtkTreeIter iter; + RemminaPluginRdpsetGrid* grid; + gint val; + + grid = REMMINA_RDPSET_GRID(widget); + + if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(grid->keyboard_layout_combo), &iter)) { + gtk_tree_model_get(GTK_TREE_MODEL(grid->keyboard_layout_store), &iter, 0, &new_layout, -1); + + if (new_layout != rdp_keyboard_layout) { + rdp_keyboard_layout = new_layout; + s = g_strdup_printf("%X", new_layout); + remmina_plugin_service->pref_set_value("rdp_keyboard_layout", s); + g_free(s); + + remmina_rdp_settings_kbd_init(); + } + } + + remmina_plugin_service->pref_set_value("rdp_use_client_keymap", + gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(grid->use_client_keymap_check)) ? "1" : "0"); + + s = g_strdup_printf("%X", grid->quality_values[0]); + remmina_plugin_service->pref_set_value("rdp_quality_0", s); + g_free(s); + + s = g_strdup_printf("%X", grid->quality_values[1]); + remmina_plugin_service->pref_set_value("rdp_quality_1", s); + g_free(s); + + s = g_strdup_printf("%X", grid->quality_values[2]); + remmina_plugin_service->pref_set_value("rdp_quality_2", s); + g_free(s); + + s = g_strdup_printf("%X", grid->quality_values[9]); + remmina_plugin_service->pref_set_value("rdp_quality_9", s); + g_free(s); + + if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(grid->device_scale_factor_combo), &iter)) { + gtk_tree_model_get(GTK_TREE_MODEL(grid->device_scale_factor_store), &iter, 0, &val, -1); + } else { + val = 0; + } + s = g_strdup_printf("%d", val); + remmina_plugin_service->pref_set_value("rdp_deviceScaleFactor", s); + g_free(s); + + val = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(grid->desktop_scale_factor_spin)); + s = g_strdup_printf("%d", val); + remmina_plugin_service->pref_set_value("rdp_desktopScaleFactor", s); + g_free(s); + + if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(grid->desktop_orientation_combo), &iter)) { + gtk_tree_model_get(GTK_TREE_MODEL(grid->desktop_orientation_store), &iter, 0, &val, -1); + } else { + val = 0; + } + s = g_strdup_printf("%d", val); + remmina_plugin_service->pref_set_value("rdp_desktopOrientation", s); + g_free(s); + +} + +static void remmina_rdp_settings_grid_load_layout(RemminaPluginRdpsetGrid* grid) +{ + TRACE_CALL(__func__); + gint i; + gchar* s; + GtkTreeIter iter; + RDP_KEYBOARD_LAYOUT* layouts; + + gtk_list_store_append(grid->keyboard_layout_store, &iter); + gtk_list_store_set(grid->keyboard_layout_store, &iter, 0, 0, 1, _("<Auto detect>"), -1); + + if (rdp_keyboard_layout == 0) + gtk_combo_box_set_active(GTK_COMBO_BOX(grid->keyboard_layout_combo), 0); + + gtk_label_set_text(GTK_LABEL(grid->keyboard_layout_label), "-"); + + layouts = freerdp_keyboard_get_layouts(RDP_KEYBOARD_LAYOUT_TYPE_STANDARD | RDP_KEYBOARD_LAYOUT_TYPE_VARIANT); + + for (i = 0; layouts[i].code; i++) { + s = g_strdup_printf("%08X - %s", layouts[i].code, layouts[i].name); + gtk_list_store_append(grid->keyboard_layout_store, &iter); + gtk_list_store_set(grid->keyboard_layout_store, &iter, 0, layouts[i].code, 1, s, -1); + + if (rdp_keyboard_layout == layouts[i].code) + gtk_combo_box_set_active(GTK_COMBO_BOX(grid->keyboard_layout_combo), i + 1); + + if (keyboard_layout == layouts[i].code) + gtk_label_set_text(GTK_LABEL(grid->keyboard_layout_label), s); + + g_free(s); + } + + free(layouts); +} + + +static void remmina_rdp_settings_grid_load_devicescalefactor_combo(RemminaPluginRdpsetGrid* grid) +{ + TRACE_CALL(__func__); + GtkTreeIter iter; + + gtk_list_store_append(grid->device_scale_factor_store, &iter); + gtk_list_store_set(grid->device_scale_factor_store, &iter, 0, 0, 1, _("<Not set>"), -1); + gtk_list_store_append(grid->device_scale_factor_store, &iter); + gtk_list_store_set(grid->device_scale_factor_store, &iter, 0, 100, 1, "100%", -1); + gtk_list_store_append(grid->device_scale_factor_store, &iter); + gtk_list_store_set(grid->device_scale_factor_store, &iter, 0, 140, 1, "140%", -1); + gtk_list_store_append(grid->device_scale_factor_store, &iter); + gtk_list_store_set(grid->device_scale_factor_store, &iter, 0, 180, 1, "180%", -1); + +} + +static void remmina_rdp_settings_grid_load_desktoporientation_combo(RemminaPluginRdpsetGrid* grid) +{ + TRACE_CALL(__func__); + GtkTreeIter iter; + + gtk_list_store_append(grid->desktop_orientation_store, &iter); + gtk_list_store_set(grid->desktop_orientation_store, &iter, 0, 0, 1, "0°", -1); + gtk_list_store_append(grid->desktop_orientation_store, &iter); + gtk_list_store_set(grid->desktop_orientation_store, &iter, 0, 90, 1, "90°", -1); + gtk_list_store_append(grid->desktop_orientation_store, &iter); + gtk_list_store_set(grid->desktop_orientation_store, &iter, 0, 180, 1, "180°", -1); + gtk_list_store_append(grid->desktop_orientation_store, &iter); + gtk_list_store_set(grid->desktop_orientation_store, &iter, 0, 270, 1, "270°", -1); + +} + + +static void remmina_rdp_settings_grid_load_quality(RemminaPluginRdpsetGrid* grid) +{ + TRACE_CALL(__func__); + gchar* value; + GtkTreeIter iter; + + gtk_list_store_append(grid->quality_store, &iter); + gtk_list_store_set(grid->quality_store, &iter, 0, -1, 1, _("<Choose a quality level to edit...>"), -1); + gtk_list_store_append(grid->quality_store, &iter); + gtk_list_store_set(grid->quality_store, &iter, 0, 0, 1, _("Poor (fastest)"), -1); + gtk_list_store_append(grid->quality_store, &iter); + gtk_list_store_set(grid->quality_store, &iter, 0, 1, 1, _("Medium"), -1); + gtk_list_store_append(grid->quality_store, &iter); + gtk_list_store_set(grid->quality_store, &iter, 0, 2, 1, _("Good"), -1); + gtk_list_store_append(grid->quality_store, &iter); + gtk_list_store_set(grid->quality_store, &iter, 0, 9, 1, _("Best (slowest)"), -1); + + memset(grid->quality_values, 0, sizeof(grid->quality_values)); + + value = remmina_plugin_service->pref_get_value("rdp_quality_0"); + grid->quality_values[0] = (value && value[0] ? strtoul(value, NULL, 16) : DEFAULT_QUALITY_0); + g_free(value); + + value = remmina_plugin_service->pref_get_value("rdp_quality_1"); + grid->quality_values[1] = (value && value[0] ? strtoul(value, NULL, 16) : DEFAULT_QUALITY_1); + g_free(value); + + value = remmina_plugin_service->pref_get_value("rdp_quality_2"); + grid->quality_values[2] = (value && value[0] ? strtoul(value, NULL, 16) : DEFAULT_QUALITY_2); + g_free(value); + + value = remmina_plugin_service->pref_get_value("rdp_quality_9"); + grid->quality_values[9] = (value && value[0] ? strtoul(value, NULL, 16) : DEFAULT_QUALITY_9); + g_free(value); +} + +static void remmina_rdp_settings_appscale_on_changed(GtkComboBox *widget, RemminaPluginRdpsetGrid *grid) +{ + TRACE_CALL(__func__); + GtkTreeIter iter; + guint i = 0; + + if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(grid->device_scale_factor_combo), &iter)) { + gtk_tree_model_get(GTK_TREE_MODEL(grid->device_scale_factor_store), &iter, 0, &i, -1); + } + if (i == 0) { + gtk_widget_set_sensitive(GTK_WIDGET(grid->desktop_scale_factor_spin), FALSE); + gtk_spin_button_set_range(GTK_SPIN_BUTTON(grid->desktop_scale_factor_spin), 0, 0); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(grid->desktop_scale_factor_spin), 0); + }else { + gtk_widget_set_sensitive(GTK_WIDGET(grid->desktop_scale_factor_spin), TRUE); + gtk_spin_button_set_range(GTK_SPIN_BUTTON(grid->desktop_scale_factor_spin), 100, 500); + // gtk_spin_button_set_value(GTK_SPIN_BUTTON(grid->desktop_scale_factor_spin), i); + } +} + +static void remmina_rdp_settings_quality_on_changed(GtkComboBox *widget, RemminaPluginRdpsetGrid *grid) +{ + TRACE_CALL(__func__); + guint v; + guint i = 0; + GtkTreeIter iter; + gboolean sensitive; + + if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(grid->quality_combo), &iter)) { + gtk_tree_model_get(GTK_TREE_MODEL(grid->quality_store), &iter, 0, &i, -1); + sensitive = ( i != -1 ); + + if (sensitive) + v = grid->quality_values[i]; + else + v = 0x3f; /* All checkboxes disabled */ + + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(grid->wallpaper_check), (v & 1) == 0); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(grid->windowdrag_check), (v & 2) == 0); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(grid->menuanimation_check), (v & 4) == 0); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(grid->theme_check), (v & 8) == 0); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(grid->cursorshadow_check), (v & 0x20) == 0); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(grid->cursorblinking_check), (v & 0x40) == 0); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(grid->fontsmoothing_check), (v & 0x80) != 0); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(grid->composition_check), (v & 0x100) != 0); + + + gtk_widget_set_sensitive(GTK_WIDGET(grid->wallpaper_check), sensitive); + gtk_widget_set_sensitive(GTK_WIDGET(grid->windowdrag_check), sensitive); + gtk_widget_set_sensitive(GTK_WIDGET(grid->menuanimation_check), sensitive); + gtk_widget_set_sensitive(GTK_WIDGET(grid->theme_check), sensitive); + gtk_widget_set_sensitive(GTK_WIDGET(grid->cursorshadow_check), sensitive); + gtk_widget_set_sensitive(GTK_WIDGET(grid->cursorblinking_check), sensitive); + gtk_widget_set_sensitive(GTK_WIDGET(grid->fontsmoothing_check), sensitive); + gtk_widget_set_sensitive(GTK_WIDGET(grid->composition_check), sensitive); + } +} + +static void remmina_rdp_settings_quality_option_on_toggled(GtkToggleButton* togglebutton, RemminaPluginRdpsetGrid* grid) +{ + TRACE_CALL(__func__); + guint v; + guint i = 0; + GtkTreeIter iter; + + if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(grid->quality_combo), &iter)) { + gtk_tree_model_get(GTK_TREE_MODEL(grid->quality_store), &iter, 0, &i, -1); + if (i != -1) { + v = 0; + v |= (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(grid->wallpaper_check)) ? 0 : 1); + v |= (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(grid->windowdrag_check)) ? 0 : 2); + v |= (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(grid->menuanimation_check)) ? 0 : 4); + v |= (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(grid->theme_check)) ? 0 : 8); + v |= (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(grid->cursorshadow_check)) ? 0 : 0x20); + v |= (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(grid->cursorblinking_check)) ? 0 : 0x40); + v |= (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(grid->fontsmoothing_check)) ? 0x80 : 0); + v |= (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(grid->composition_check)) ? 0x100 : 0); + grid->quality_values[i] = v; + } + } +} + +static void remmina_rdp_settings_set_combo_active_item(GtkComboBox* combo, int itemval) +{ + GtkTreeIter iter; + int i; + GtkTreeModel *m; + gboolean valid; + + m = gtk_combo_box_get_model(combo); + if (!m) { + return; + } + + valid = gtk_tree_model_get_iter_first(m, &iter); + while (valid) { + gtk_tree_model_get(m, &iter, 0, &i, -1); + if (i == itemval) { + gtk_combo_box_set_active_iter(combo, &iter); + } + valid = gtk_tree_model_iter_next(m, &iter); + } + +} + +static void remmina_rdp_settings_grid_init(RemminaPluginRdpsetGrid *grid) +{ + TRACE_CALL(__func__); + gchar* s; + GtkWidget* widget; + GtkCellRenderer* renderer; + int desktopOrientation, desktopScaleFactor, deviceScaleFactor; + + /* Create the grid */ + g_signal_connect(G_OBJECT(grid), "destroy", G_CALLBACK(remmina_rdp_settings_grid_destroy), NULL); + gtk_grid_set_row_homogeneous(GTK_GRID(grid), FALSE); + gtk_grid_set_column_homogeneous(GTK_GRID(grid), FALSE); + gtk_container_set_border_width(GTK_CONTAINER(grid), 8); + gtk_grid_set_row_spacing(GTK_GRID(grid), 4); + gtk_grid_set_column_spacing(GTK_GRID(grid), 4); + + /* Create the content */ + widget = gtk_label_new(_("Keyboard layout")); + gtk_widget_show(widget); + gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START); + gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER); + gtk_grid_attach(GTK_GRID(grid), widget, 0, 0, 1, 1); + + grid->keyboard_layout_store = gtk_list_store_new(2, G_TYPE_UINT, G_TYPE_STRING); + widget = gtk_combo_box_new_with_model(GTK_TREE_MODEL(grid->keyboard_layout_store)); + gtk_widget_show(widget); + gtk_grid_attach(GTK_GRID(grid), widget, 1, 0, 4, 1); + + renderer = gtk_cell_renderer_text_new(); + gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(widget), renderer, TRUE); + gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(widget), renderer, "text", 1); + grid->keyboard_layout_combo = widget; + + widget = gtk_label_new("-"); + gtk_widget_show(widget); + gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START); + gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER); + gtk_grid_attach(GTK_GRID(grid), widget, 1, 1, 4, 2); + grid->keyboard_layout_label = widget; + + remmina_rdp_settings_grid_load_layout(grid); + + widget = gtk_check_button_new_with_label(_("Use client keyboard mapping")); + gtk_widget_show(widget); + gtk_grid_attach(GTK_GRID(grid), widget, 1, 3, 3, 3); + grid->use_client_keymap_check = widget; + + s = remmina_plugin_service->pref_get_value("rdp_use_client_keymap"); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), + s && s[0] == '1' ? TRUE : FALSE); + g_free(s); + + widget = gtk_label_new(_("Quality settings")); + gtk_widget_show(widget); + gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START); + gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER); + gtk_grid_attach(GTK_GRID(grid), widget, 0, 6, 1, 4); + + grid->quality_store = gtk_list_store_new(2, G_TYPE_UINT, G_TYPE_STRING); + widget = gtk_combo_box_new_with_model(GTK_TREE_MODEL(grid->quality_store)); + gtk_widget_show(widget); + gtk_grid_attach(GTK_GRID(grid), widget, 1, 6, 4, 4); + + renderer = gtk_cell_renderer_text_new(); + gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(widget), renderer, TRUE); + gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(widget), renderer, "text", 1); + g_signal_connect(G_OBJECT(widget), "changed", + G_CALLBACK(remmina_rdp_settings_quality_on_changed), grid); + grid->quality_combo = widget; + + remmina_rdp_settings_grid_load_quality(grid); + + widget = gtk_check_button_new_with_label(_("Wallpaper")); + gtk_widget_show(widget); + gtk_grid_attach(GTK_GRID(grid), widget, 1, 10, 2, 5); + g_signal_connect(G_OBJECT(widget), "toggled", + G_CALLBACK(remmina_rdp_settings_quality_option_on_toggled), grid); + grid->wallpaper_check = widget; + + widget = gtk_check_button_new_with_label(_("Window drag")); + gtk_widget_show(widget); + gtk_grid_attach(GTK_GRID(grid), widget, 3, 10, 3, 5); + g_signal_connect(G_OBJECT(widget), "toggled", + G_CALLBACK(remmina_rdp_settings_quality_option_on_toggled), grid); + grid->windowdrag_check = widget; + + widget = gtk_check_button_new_with_label(_("Menu animation")); + gtk_widget_show(widget); + gtk_grid_attach(GTK_GRID(grid), widget, 1, 13, 2, 6); + g_signal_connect(G_OBJECT(widget), "toggled", + G_CALLBACK(remmina_rdp_settings_quality_option_on_toggled), grid); + grid->menuanimation_check = widget; + + widget = gtk_check_button_new_with_label(_("Theme")); + gtk_widget_show(widget); + gtk_grid_attach(GTK_GRID(grid), widget, 3, 13, 3, 6); + g_signal_connect(G_OBJECT(widget), "toggled", + G_CALLBACK(remmina_rdp_settings_quality_option_on_toggled), grid); + grid->theme_check = widget; + + widget = gtk_check_button_new_with_label(_("Cursor shadow")); + gtk_widget_show(widget); + gtk_grid_attach(GTK_GRID(grid), widget, 1, 16, 2, 7); + g_signal_connect(G_OBJECT(widget), "toggled", + G_CALLBACK(remmina_rdp_settings_quality_option_on_toggled), grid); + grid->cursorshadow_check = widget; + + widget = gtk_check_button_new_with_label(_("Cursor blinking")); + gtk_widget_show(widget); + gtk_grid_attach(GTK_GRID(grid), widget, 3, 16, 3, 7); + g_signal_connect(G_OBJECT(widget), "toggled", + G_CALLBACK(remmina_rdp_settings_quality_option_on_toggled), grid); + grid->cursorblinking_check = widget; + + widget = gtk_check_button_new_with_label(_("Font smoothing")); + gtk_widget_show(widget); + gtk_grid_attach(GTK_GRID(grid), widget, 1, 19, 2, 8); + g_signal_connect(G_OBJECT(widget), "toggled", + G_CALLBACK(remmina_rdp_settings_quality_option_on_toggled), grid); + grid->fontsmoothing_check = widget; + + widget = gtk_check_button_new_with_label(_("Composition")); + gtk_widget_show(widget); + gtk_grid_attach(GTK_GRID(grid), widget, 3, 19, 3, 8); + g_signal_connect(G_OBJECT(widget), "toggled", + G_CALLBACK(remmina_rdp_settings_quality_option_on_toggled), grid); + grid->composition_check = widget; + + gtk_combo_box_set_active(GTK_COMBO_BOX(grid->quality_combo), 0); + + + widget = gtk_label_new(_("Remote scale factor")); + gtk_widget_show(widget); + gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START); + gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER); + gtk_grid_attach(GTK_GRID(grid), widget, 0, 27, 1, 1); + + grid->device_scale_factor_store = gtk_list_store_new(2, G_TYPE_INT, G_TYPE_STRING); + grid->desktop_orientation_store = gtk_list_store_new(2, G_TYPE_INT, G_TYPE_STRING); + + remmina_rdp_settings_get_orientation_scale_prefs(&desktopOrientation, &desktopScaleFactor, &deviceScaleFactor); + remmina_rdp_settings_grid_load_devicescalefactor_combo(grid); + remmina_rdp_settings_grid_load_desktoporientation_combo(grid); + + widget = gtk_label_new(_("Desktop scale factor %")); + gtk_widget_show(widget); + gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START); + gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER); + gtk_grid_attach(GTK_GRID(grid), widget, 1, 27, 1, 1); + + widget = gtk_spin_button_new_with_range(0, 10000, 1); + gtk_widget_show(widget); + gtk_grid_attach(GTK_GRID(grid), widget, 2, 27, 1, 1); + grid->desktop_scale_factor_spin = widget; + + widget = gtk_label_new(_("Device scale factor %")); + gtk_widget_show(widget); + gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START); + gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER); + gtk_grid_attach(GTK_GRID(grid), widget, 1, 28, 1, 1); + + widget = gtk_combo_box_new_with_model(GTK_TREE_MODEL(grid->device_scale_factor_store)); + gtk_widget_show(widget); + gtk_grid_attach(GTK_GRID(grid), widget, 2, 28, 1, 1); + + renderer = gtk_cell_renderer_text_new(); + gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(widget), renderer, TRUE); + gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(widget), renderer, "text", 1); + grid->device_scale_factor_combo = widget; + + remmina_rdp_settings_set_combo_active_item(GTK_COMBO_BOX(grid->device_scale_factor_combo), deviceScaleFactor); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(grid->desktop_scale_factor_spin), (gdouble)desktopScaleFactor); + + g_signal_connect(G_OBJECT(widget), "changed", + G_CALLBACK(remmina_rdp_settings_appscale_on_changed), grid); + remmina_rdp_settings_appscale_on_changed(GTK_COMBO_BOX(grid->device_scale_factor_combo), grid); + + widget = gtk_label_new(_("Desktop orientation")); + gtk_widget_show(widget); + gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START); + gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER); + gtk_grid_attach(GTK_GRID(grid), widget, 0, 29, 1, 1); + + widget = gtk_combo_box_new_with_model(GTK_TREE_MODEL(grid->desktop_orientation_store)); + gtk_widget_show(widget); + gtk_grid_attach(GTK_GRID(grid), widget, 1, 29, 1, 1); + + renderer = gtk_cell_renderer_text_new(); + gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(widget), renderer, TRUE); + gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(widget), renderer, "text", 1); + grid->desktop_orientation_combo = widget; + + remmina_rdp_settings_set_combo_active_item(GTK_COMBO_BOX(grid->desktop_orientation_combo), desktopOrientation); + +} + +GtkWidget* remmina_rdp_settings_new(void) +{ + TRACE_CALL(__func__); + GtkWidget* widget; + + widget = GTK_WIDGET(g_object_new(REMMINA_TYPE_PLUGIN_RDPSET_GRID, NULL)); + gtk_widget_show(widget); + + return widget; +} + +void remmina_rdp_settings_get_orientation_scale_prefs(int *desktopOrientation, int *desktopScaleFactor, int *deviceScaleFactor) +{ + TRACE_CALL(__func__); + + /* See https://msdn.microsoft.com/en-us/library/cc240510.aspx */ + + int orientation, dpsf, desf; + gchar* s; + + *desktopOrientation = *desktopScaleFactor = *deviceScaleFactor = 0; + + s = remmina_plugin_service->pref_get_value("rdp_desktopOrientation"); + orientation = s ? atoi(s) : 0; + g_free(s); + if (orientation != 90 && orientation != 180 && orientation != 270) + orientation = 0; + *desktopOrientation = orientation; + + s = remmina_plugin_service->pref_get_value("rdp_desktopScaleFactor"); + dpsf = s ? atoi(s) : 0; + g_free(s); + if (dpsf < 100 || dpsf > 500) + return; + + s = remmina_plugin_service->pref_get_value("rdp_deviceScaleFactor"); + desf = s ? atoi(s) : 0; + g_free(s); + if (desf != 100 && desf != 140 && desf != 180) + return; + + *desktopScaleFactor = dpsf; + *deviceScaleFactor = desf; + +} diff --git a/plugins/rdp/rdp_settings.h b/plugins/rdp/rdp_settings.h new file mode 100644 index 000000000..5dca220af --- /dev/null +++ b/plugins/rdp/rdp_settings.h @@ -0,0 +1,47 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2010-2011 Vic Lee + * Copyright (C) 2017 Antenore Gatta, Giovanni Panozzo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + * + */ + +#pragma once + +G_BEGIN_DECLS + +void remmina_rdp_settings_init(void); +guint remmina_rdp_settings_get_keyboard_layout(void); +GtkWidget* remmina_rdp_settings_new(void); + +void remmina_rdp_settings_get_orientation_scale_prefs(int *desktopOrientation, int *desktopScaleFactor, int *deviceScaleFactor); + +G_END_DECLS + 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 Binary files differnew file mode 100644 index 000000000..433610cd3 --- /dev/null +++ b/plugins/spice/16x16/emblems/remmina-spice.png diff --git a/plugins/spice/22x22/emblems/remmina-spice.png b/plugins/spice/22x22/emblems/remmina-spice.png Binary files differnew file mode 100644 index 000000000..64d5061db --- /dev/null +++ b/plugins/spice/22x22/emblems/remmina-spice.png 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 Binary files differnew file mode 100644 index 000000000..96bc6828a --- /dev/null +++ b/plugins/tool_hello_world/16x16/emblems/remmina-tool.png diff --git a/plugins/tool_hello_world/22x22/emblems/remmina-tool.png b/plugins/tool_hello_world/22x22/emblems/remmina-tool.png Binary files differnew file mode 100644 index 000000000..a28ca75ad --- /dev/null +++ b/plugins/tool_hello_world/22x22/emblems/remmina-tool.png 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 Binary files differnew file mode 100644 index 000000000..8b3363714 --- /dev/null +++ b/plugins/vnc/16x16/emblems/remmina-vnc-ssh.png diff --git a/plugins/vnc/16x16/emblems/remmina-vnc.png b/plugins/vnc/16x16/emblems/remmina-vnc.png Binary files differnew file mode 100644 index 000000000..b06083010 --- /dev/null +++ b/plugins/vnc/16x16/emblems/remmina-vnc.png diff --git a/plugins/vnc/22x22/emblems/remmina-vnc-ssh.png b/plugins/vnc/22x22/emblems/remmina-vnc-ssh.png Binary files differnew file mode 100644 index 000000000..644d7aa81 --- /dev/null +++ b/plugins/vnc/22x22/emblems/remmina-vnc-ssh.png diff --git a/plugins/vnc/22x22/emblems/remmina-vnc.png b/plugins/vnc/22x22/emblems/remmina-vnc.png Binary files differnew file mode 100644 index 000000000..ab1cf2964 --- /dev/null +++ b/plugins/vnc/22x22/emblems/remmina-vnc.png 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 Binary files differnew file mode 100644 index 000000000..5493ba3bc --- /dev/null +++ b/plugins/xdmcp/16x16/emblems/remmina-xdmcp-ssh.png diff --git a/plugins/xdmcp/16x16/emblems/remmina-xdmcp.png b/plugins/xdmcp/16x16/emblems/remmina-xdmcp.png Binary files differnew file mode 100644 index 000000000..2367a4e6e --- /dev/null +++ b/plugins/xdmcp/16x16/emblems/remmina-xdmcp.png diff --git a/plugins/xdmcp/22x22/emblems/remmina-xdmcp-ssh.png b/plugins/xdmcp/22x22/emblems/remmina-xdmcp-ssh.png Binary files differnew file mode 100644 index 000000000..f69563357 --- /dev/null +++ b/plugins/xdmcp/22x22/emblems/remmina-xdmcp-ssh.png diff --git a/plugins/xdmcp/22x22/emblems/remmina-xdmcp.png b/plugins/xdmcp/22x22/emblems/remmina-xdmcp.png Binary files differnew file mode 100644 index 000000000..83ffc6edb --- /dev/null +++ b/plugins/xdmcp/22x22/emblems/remmina-xdmcp.png 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; +} + |