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

gitlab.com/Remmina/Remmina.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAntenore Gatta <antenore@simbiosi.org>2018-05-03 15:38:44 +0300
committerAntenore Gatta <antenore@simbiosi.org>2018-05-03 15:38:44 +0300
commitd77997ca5c06c1dd971cf85383e81bd6584be746 (patch)
tree78a08a34126a30a25655ca748b9e2197e918102b /src
parentbcf9990358f0ac9423734421be45c38fdccdf1f0 (diff)
Renamed remmina in src and moved source files
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt208
-rw-r--r--src/external_tools/CMakeLists.txt44
-rwxr-xr-xsrc/external_tools/functions.sh17
-rwxr-xr-xsrc/external_tools/launcher.sh20
-rwxr-xr-xsrc/external_tools/remmina_filezilla_sftp.sh7
-rwxr-xr-xsrc/external_tools/remmina_filezilla_sftp_pki.sh7
-rwxr-xr-xsrc/external_tools/remmina_nslookup.sh8
-rwxr-xr-xsrc/external_tools/remmina_ping.sh8
-rwxr-xr-xsrc/external_tools/remmina_traceroute.sh8
-rw-r--r--src/include/remmina/plugin.h231
-rw-r--r--src/include/remmina/remmina_trace_calls.h54
-rw-r--r--src/include/remmina/types.h125
-rw-r--r--src/remmina.196
-rw-r--r--src/remmina.c292
-rw-r--r--src/remmina_about.c64
-rw-r--r--src/remmina_about.h45
-rw-r--r--src/remmina_applet_menu.c271
-rw-r--r--src/remmina_applet_menu.h79
-rw-r--r--src/remmina_applet_menu_item.c177
-rw-r--r--src/remmina_applet_menu_item.h75
-rw-r--r--src/remmina_avahi.c301
-rw-r--r--src/remmina_avahi.h56
-rw-r--r--src/remmina_chat_window.c256
-rw-r--r--src/remmina_chat_window.h68
-rw-r--r--src/remmina_connection_window.c3836
-rw-r--r--src/remmina_connection_window.h82
-rw-r--r--src/remmina_crypt.c181
-rw-r--r--src/remmina_crypt.h45
-rw-r--r--src/remmina_exec.c243
-rw-r--r--src/remmina_exec.h66
-rw-r--r--src/remmina_ext_exec.c141
-rw-r--r--src/remmina_ext_exec.h50
-rw-r--r--src/remmina_external_tools.c154
-rw-r--r--src/remmina_external_tools.h49
-rw-r--r--src/remmina_file.c681
-rw-r--r--src/remmina_file.h96
-rw-r--r--src/remmina_file_editor.c1521
-rw-r--r--src/remmina_file_editor.h74
-rw-r--r--src/remmina_file_manager.c339
-rw-r--r--src/remmina_file_manager.h62
-rw-r--r--src/remmina_ftp_client.c1249
-rw-r--r--src/remmina_ftp_client.h150
-rw-r--r--src/remmina_icon.c536
-rw-r--r--src/remmina_icon.h49
-rw-r--r--src/remmina_init_dialog.c830
-rw-r--r--src/remmina_init_dialog.h102
-rw-r--r--src/remmina_key_chooser.c133
-rw-r--r--src/remmina_key_chooser.h64
-rw-r--r--src/remmina_log.c206
-rw-r--r--src/remmina_log.h47
-rw-r--r--src/remmina_main.c1272
-rw-r--r--src/remmina_main.h122
-rw-r--r--src/remmina_marshals.c161
-rw-r--r--src/remmina_marshals.h19
-rw-r--r--src/remmina_marshals.list6
-rw-r--r--src/remmina_masterthread_exec.c160
-rw-r--r--src/remmina_masterthread_exec.h159
-rw-r--r--src/remmina_mpchange.c451
-rw-r--r--src/remmina_mpchange.h46
-rw-r--r--src/remmina_plugin_manager.c437
-rw-r--r--src/remmina_plugin_manager.h61
-rw-r--r--src/remmina_pref.c1039
-rw-r--r--src/remmina_pref.h235
-rw-r--r--src/remmina_pref_dialog.c625
-rw-r--r--src/remmina_pref_dialog.h147
-rw-r--r--src/remmina_protocol_widget.c1315
-rw-r--r--src/remmina_protocol_widget.h161
-rw-r--r--src/remmina_public.c701
-rw-r--r--src/remmina_public.h118
-rw-r--r--src/remmina_scrolled_viewport.c203
-rw-r--r--src/remmina_scrolled_viewport.h74
-rw-r--r--src/remmina_sftp_client.c1000
-rw-r--r--src/remmina_sftp_client.h82
-rw-r--r--src/remmina_sftp_plugin.c403
-rw-r--r--src/remmina_sftp_plugin.h45
-rw-r--r--src/remmina_ssh.c1732
-rw-r--r--src/remmina_ssh.h268
-rw-r--r--src/remmina_ssh_plugin.c1064
-rw-r--r--src/remmina_ssh_plugin.h58
-rw-r--r--src/remmina_stats.c777
-rw-r--r--src/remmina_stats.h45
-rw-r--r--src/remmina_stats_sender.c341
-rw-r--r--src/remmina_stats_sender.h47
-rw-r--r--src/remmina_string_array.c175
-rw-r--r--src/remmina_string_array.h57
-rw-r--r--src/remmina_string_list.c313
-rw-r--r--src/remmina_string_list.h83
-rw-r--r--src/remmina_sysinfo.c153
-rw-r--r--src/remmina_sysinfo.h47
-rw-r--r--src/remmina_utils.c429
-rw-r--r--src/remmina_utils.h56
-rw-r--r--src/remmina_widget_pool.c144
-rw-r--r--src/remmina_widget_pool.h51
93 files changed, 28385 insertions, 0 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 000000000..20eeeb4d8
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,208 @@
+# src/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-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.
+
+
+cmake_minimum_required(VERSION 2.8)
+
+list(APPEND REMMINA_SRCS
+ "remmina_about.c"
+ "remmina_about.h"
+ "remmina_applet_menu.c"
+ "remmina_applet_menu.h"
+ "remmina_applet_menu_item.c"
+ "remmina_applet_menu_item.h"
+ "remmina_avahi.c"
+ "remmina_avahi.h"
+ "remmina.c"
+ "remmina_chat_window.c"
+ "remmina_chat_window.h"
+ "remmina_crypt.c"
+ "remmina_crypt.h"
+ "remmina_exec.c"
+ "remmina_exec.h"
+ "remmina_file.c"
+ "remmina_file_editor.c"
+ "remmina_file_editor.h"
+ "remmina_file.h"
+ "remmina_file_manager.c"
+ "remmina_file_manager.h"
+ "remmina_ftp_client.c"
+ "remmina_ftp_client.h"
+ "remmina_icon.c"
+ "remmina_icon.h"
+ "remmina_init_dialog.c"
+ "remmina_init_dialog.h"
+ "remmina_key_chooser.c"
+ "remmina_key_chooser.h"
+ "remmina_log.c"
+ "remmina_log.h"
+ "remmina_main.c"
+ "remmina_main.h"
+ "remmina_marshals.c"
+ "remmina_marshals.h"
+ "remmina_marshals.list"
+ "remmina_masterthread_exec.c"
+ "remmina_masterthread_exec.h"
+ "remmina_plugin_manager.c"
+ "remmina_plugin_manager.h"
+ "remmina_ext_exec.c"
+ "remmina_ext_exec.h"
+ "remmina_pref.c"
+ "remmina_pref_dialog.c"
+ "remmina_pref_dialog.h"
+ "remmina_pref.h"
+ "remmina_protocol_widget.c"
+ "remmina_protocol_widget.h"
+ "remmina_public.c"
+ "remmina_public.h"
+ "remmina_scrolled_viewport.c"
+ "remmina_scrolled_viewport.h"
+ "remmina_sftp_client.c"
+ "remmina_sftp_client.h"
+ "remmina_sftp_plugin.c"
+ "remmina_sftp_plugin.h"
+ "remmina_ssh.c"
+ "remmina_ssh.h"
+ "remmina_ssh_plugin.c"
+ "remmina_ssh_plugin.h"
+ "remmina_string_array.c"
+ "remmina_string_array.h"
+ "remmina_string_list.c"
+ "remmina_string_list.h"
+ "remmina_utils.c"
+ "remmina_utils.h"
+ "remmina_widget_pool.c"
+ "remmina_widget_pool.h"
+ "remmina_external_tools.c"
+ "remmina_external_tools.h"
+ "remmina_sysinfo.h"
+ "remmina_sysinfo.c"
+ "remmina_connection_window.c"
+ "remmina_connection_window.h"
+ "remmina_mpchange.c"
+ "remmina_mpchange.h"
+ "remmina_stats.c"
+ "remmina_stats.h"
+ "remmina_stats_sender.c"
+ "remmina_stats_sender.h"
+ )
+
+add_executable(remmina ${REMMINA_SRCS})
+include_directories(${GTK_INCLUDE_DIRS})
+target_link_libraries(remmina ${GTK_LIBRARIES})
+
+if(WITH_MANPAGES)
+ install(FILES remmina.1 DESTINATION ${CMAKE_INSTALL_FULL_MANDIR}/man1)
+endif()
+
+find_package(X11)
+include_directories(${X11_INCLUDE_DIR})
+target_link_libraries(remmina ${X11_LIBRARIES})
+
+target_link_libraries(remmina ${CMAKE_THREAD_LIBS_INIT})
+
+find_suggested_package(LIBSSH)
+if(LIBSSH_FOUND)
+ add_definitions(-DHAVE_LIBSSH)
+ include_directories(${LIBSSH_INCLUDE_DIRS})
+ target_link_libraries(remmina ${LIBSSH_LIBRARIES})
+endif()
+
+if(GCRYPT_FOUND)
+ include_directories(${GCRYPT_INCLUDE_DIRS})
+ target_link_libraries(remmina ${GCRYPT_LIBRARIES})
+endif()
+
+if(AVAHI_FOUND)
+ include_directories(${AVAHI_INCLUDE_DIRS})
+ target_link_libraries(remmina ${AVAHI_LIBRARIES})
+endif()
+
+if(OPENSSL_FOUND)
+ include_directories(${OPENSSL_INCLUDE_DIRS})
+ target_link_libraries(remmina ${OPENSSL_LIBRARIES})
+endif()
+
+option(WITH_VTE "Build with support for VTE" ON)
+if(GTK3_FOUND AND WITH_VTE)
+ set(_VTE_VERSION_NUMS 2.91 2.90)
+ foreach(__VTE_VERSION ${_VTE_VERSION_NUMS})
+ set(_VTE_VERSION_NUM ${__VTE_VERSION})
+ find_package(VTE)
+ if(VTE_FOUND)
+ break()
+ endif()
+ message(STATUS "VTE ${__VTE_VERSION} not found")
+ endforeach()
+elseif(WITH_VTE)
+ set(_VTE_VERSION_NUM)
+ find_package(VTE)
+endif()
+
+if(VTE_FOUND)
+ add_definitions(-DHAVE_LIBVTE)
+ include_directories(${VTE_INCLUDE_DIRS})
+ target_link_libraries(remmina ${VTE_LIBRARIES})
+endif()
+
+if(GTK3_FOUND)
+ find_suggested_package(APPINDICATOR)
+ if(APPINDICATOR_FOUND)
+ add_definitions(-DHAVE_LIBAPPINDICATOR)
+ include_directories(${APPINDICATOR_INCLUDE_DIRS})
+ target_link_libraries(remmina ${APPINDICATOR_LIBRARIES})
+ endif()
+ find_required_package(JSONGLIB)
+ if(JSONGLIB_FOUND)
+ include_directories(${JSONGLIB_INCLUDE_DIRS})
+ target_link_libraries(remmina ${JSONGLIB_LIBRARIES})
+ else()
+ message(FATAL_ERROR "json-glib library not found")
+ endif()
+ find_required_package(LIBSOUP24)
+ if(LIBSOUP24_FOUND)
+ include_directories(${LIBSOUP24_INCLUDE_DIRS})
+ target_link_libraries(remmina ${LIBSOUP24_LIBRARIES})
+ else()
+ message(FATAL_ERROR "libsoup 2.4 library not found")
+ endif()
+endif()
+
+add_subdirectory(external_tools)
+
+install(TARGETS remmina DESTINATION ${CMAKE_INSTALL_BINDIR})
+install(DIRECTORY include/remmina/
+ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/remmina
+ FILES_MATCHING PATTERN "*.h")
+
diff --git a/src/external_tools/CMakeLists.txt b/src/external_tools/CMakeLists.txt
new file mode 100644
index 000000000..f41f5d68d
--- /dev/null
+++ b/src/external_tools/CMakeLists.txt
@@ -0,0 +1,44 @@
+# desktop/remmina - The GTK+ Remote Desktop Client
+#
+# Copyright (C) 2011 Marc-Andre Moreau
+# Copyright (C) 2014-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.
+
+INSTALL(PROGRAMS
+ launcher.sh
+ functions.sh
+ remmina_filezilla_sftp.sh
+ remmina_filezilla_sftp_pki.sh
+ remmina_nslookup.sh
+ remmina_ping.sh
+ remmina_traceroute.sh
+ DESTINATION ${REMMINA_EXTERNAL_TOOLS_DIR})
+
+
diff --git a/src/external_tools/functions.sh b/src/external_tools/functions.sh
new file mode 100755
index 000000000..f35914564
--- /dev/null
+++ b/src/external_tools/functions.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+# Waits for user key press.
+pause ()
+{
+ echo "Hit a key to continue ..."
+ OLDCONFIG=`stty -g`
+ stty -icanon -echo min 1 time 0
+ dd count=1 2>/dev/null
+ stty $OLDCONFIG
+}
+
+# set terminal title for gnome-terminal and many others
+settitle() {
+ echo -ne "\033]0;${remmina_term_title}\007"
+}
+
diff --git a/src/external_tools/launcher.sh b/src/external_tools/launcher.sh
new file mode 100755
index 000000000..161e9830b
--- /dev/null
+++ b/src/external_tools/launcher.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+####################
+# Main Script
+####################
+#gnome-terminal -e $(dirname $0)/$1
+
+if [ -x "/usr/bin/x-terminal-emulator" ];
+then
+ TERMNAME="/usr/bin/x-terminal-emulator"
+else
+ TERMNAME="gnome-terminal"
+fi
+$TERMNAME -e "$1" &
+
+#if [ "$2" = "1" ]
+#then
+# echo "Hit a key to continue ..."
+# Pause
+#fi
diff --git a/src/external_tools/remmina_filezilla_sftp.sh b/src/external_tools/remmina_filezilla_sftp.sh
new file mode 100755
index 000000000..3ac46f809
--- /dev/null
+++ b/src/external_tools/remmina_filezilla_sftp.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+. $(dirname $0)/functions.sh
+settitle
+
+filezilla sftp://$ssh_username:$password@$server
+
diff --git a/src/external_tools/remmina_filezilla_sftp_pki.sh b/src/external_tools/remmina_filezilla_sftp_pki.sh
new file mode 100755
index 000000000..5b1ac507a
--- /dev/null
+++ b/src/external_tools/remmina_filezilla_sftp_pki.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+. $(dirname $0)/functions.sh
+settitle
+
+filezilla sftp://$ssh_username@$server
+
diff --git a/src/external_tools/remmina_nslookup.sh b/src/external_tools/remmina_nslookup.sh
new file mode 100755
index 000000000..9f4895338
--- /dev/null
+++ b/src/external_tools/remmina_nslookup.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+. $(dirname $0)/functions.sh
+settitle
+
+nslookup $server
+
+pause
diff --git a/src/external_tools/remmina_ping.sh b/src/external_tools/remmina_ping.sh
new file mode 100755
index 000000000..f69fe5ef2
--- /dev/null
+++ b/src/external_tools/remmina_ping.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+. $(dirname $0)/functions.sh
+settitle
+
+ping -c3 $server
+
+pause
diff --git a/src/external_tools/remmina_traceroute.sh b/src/external_tools/remmina_traceroute.sh
new file mode 100755
index 000000000..3b5d8cc5f
--- /dev/null
+++ b/src/external_tools/remmina_traceroute.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+. $(dirname $0)/functions.sh
+settitle
+
+traceroute $server
+
+pause
diff --git a/src/include/remmina/plugin.h b/src/include/remmina/plugin.h
new file mode 100644
index 000000000..b8889959a
--- /dev/null
+++ b/src/include/remmina/plugin.h
@@ -0,0 +1,231 @@
+/*
+ * 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-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.
+ *
+ */
+
+#pragma once
+
+#include <remmina/types.h>
+#include "remmina/remmina_trace_calls.h"
+
+G_BEGIN_DECLS
+
+typedef enum {
+ REMMINA_PLUGIN_TYPE_PROTOCOL = 0,
+ REMMINA_PLUGIN_TYPE_ENTRY = 1,
+ REMMINA_PLUGIN_TYPE_FILE = 2,
+ REMMINA_PLUGIN_TYPE_TOOL = 3,
+ REMMINA_PLUGIN_TYPE_PREF = 4,
+ REMMINA_PLUGIN_TYPE_SECRET = 5
+} RemminaPluginType;
+
+typedef struct _RemminaPlugin {
+ RemminaPluginType type;
+ const gchar *name;
+ const gchar *description;
+ const gchar *domain;
+ const gchar *version;
+} RemminaPlugin;
+
+typedef struct _RemminaProtocolPlugin {
+ RemminaPluginType type;
+ const gchar *name;
+ const gchar *description;
+ const gchar *domain;
+ const gchar *version;
+
+ const gchar *icon_name;
+ const gchar *icon_name_ssh;
+ const RemminaProtocolSetting *basic_settings;
+ const RemminaProtocolSetting *advanced_settings;
+ RemminaProtocolSSHSetting ssh_setting;
+ const RemminaProtocolFeature *features;
+
+ void (* init)(RemminaProtocolWidget *gp);
+ gboolean (* open_connection)(RemminaProtocolWidget *gp);
+ gboolean (* close_connection)(RemminaProtocolWidget *gp);
+ gboolean (* query_feature)(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature);
+ void (* call_feature)(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature);
+ void (* send_keystrokes)(RemminaProtocolWidget *gp, const guint keystrokes[], const gint keylen);
+ gboolean (* get_plugin_screenshot)(RemminaProtocolWidget *gp, RemminaPluginScreenshotData *rpsd);
+} RemminaProtocolPlugin;
+
+typedef struct _RemminaEntryPlugin {
+ RemminaPluginType type;
+ const gchar *name;
+ const gchar *description;
+ const gchar *domain;
+ const gchar *version;
+
+ void (* entry_func)(void);
+} RemminaEntryPlugin;
+
+typedef struct _RemminaFilePlugin {
+ RemminaPluginType type;
+ const gchar *name;
+ const gchar *description;
+ const gchar *domain;
+ const gchar *version;
+
+ gboolean (* import_test_func)(const gchar *from_file);
+ RemminaFile* (*import_func)(const gchar * from_file);
+ gboolean (* export_test_func)(RemminaFile *file);
+ gboolean (* export_func)(RemminaFile *file, const gchar *to_file);
+ const gchar *export_hints;
+} RemminaFilePlugin;
+
+typedef struct _RemminaToolPlugin {
+ RemminaPluginType type;
+ const gchar *name;
+ const gchar *description;
+ const gchar *domain;
+ const gchar *version;
+
+ void (* exec_func)(void);
+} RemminaToolPlugin;
+
+typedef struct _RemminaPrefPlugin {
+ RemminaPluginType type;
+ const gchar *name;
+ const gchar *description;
+ const gchar *domain;
+ const gchar *version;
+
+ const gchar *pref_label;
+ GtkWidget* (*get_pref_body)(void);
+} RemminaPrefPlugin;
+
+typedef struct _RemminaSecretPlugin {
+ RemminaPluginType type;
+ const gchar *name;
+ const gchar *description;
+ const gchar *domain;
+ const gchar *version;
+
+ gboolean trusted;
+ void (* store_password)(RemminaFile *remminafile, const gchar *key, const gchar *password);
+ gchar* (*get_password)(RemminaFile * remminafile, const gchar * key);
+ void (* delete_password)(RemminaFile *remminafile, const gchar *key);
+ gboolean (* is_service_available)(void);
+} RemminaSecretPlugin;
+
+/* Plugin Service is a struct containing a list of function pointers,
+ * which is passed from Remmina main program to the plugin module
+ * through the plugin entry function remmina_plugin_entry() */
+typedef struct _RemminaPluginService {
+ gboolean (* register_plugin)(RemminaPlugin *plugin);
+
+ gint (* protocol_plugin_get_width)(RemminaProtocolWidget *gp);
+ void (* protocol_plugin_set_width)(RemminaProtocolWidget *gp, gint width);
+ gint (* protocol_plugin_get_height)(RemminaProtocolWidget *gp);
+ void (* protocol_plugin_set_height)(RemminaProtocolWidget *gp, gint height);
+ RemminaScaleMode (* remmina_protocol_widget_get_current_scale_mode)(RemminaProtocolWidget *gp);
+ gboolean (* protocol_plugin_get_expand)(RemminaProtocolWidget *gp);
+ void (* protocol_plugin_set_expand)(RemminaProtocolWidget *gp, gboolean expand);
+ gboolean (* protocol_plugin_has_error)(RemminaProtocolWidget *gp);
+ void (* protocol_plugin_set_error)(RemminaProtocolWidget *gp, const gchar *fmt, ...);
+ gboolean (* protocol_plugin_is_closed)(RemminaProtocolWidget *gp);
+ RemminaFile* (*protocol_plugin_get_file)(RemminaProtocolWidget * gp);
+ void (* protocol_plugin_emit_signal)(RemminaProtocolWidget *gp, const gchar *signal_name);
+ void (* protocol_plugin_register_hostkey)(RemminaProtocolWidget *gp, GtkWidget *widget);
+ gchar* (*protocol_plugin_start_direct_tunnel)(RemminaProtocolWidget * gp, gint default_port, gboolean port_plus);
+ gboolean (* protocol_plugin_start_reverse_tunnel)(RemminaProtocolWidget *gp, gint local_port);
+ gboolean (* protocol_plugin_start_xport_tunnel)(RemminaProtocolWidget *gp, RemminaXPortTunnelInitFunc init_func);
+ void (* protocol_plugin_set_display)(RemminaProtocolWidget *gp, gint display);
+ gboolean (* protocol_plugin_close_connection)(RemminaProtocolWidget *gp);
+ gint (* protocol_plugin_init_authpwd)(RemminaProtocolWidget *gp, RemminaAuthpwdType authpwd_type, gboolean allow_password_saving);
+ gint (* protocol_plugin_init_authuserpwd)(RemminaProtocolWidget *gp, gboolean want_domain, gboolean allow_password_saving);
+ gint (* protocol_plugin_init_certificate)(RemminaProtocolWidget *gp, const gchar* subject, const gchar* issuer, const gchar* fingerprint);
+ gint (* protocol_plugin_changed_certificate)(RemminaProtocolWidget *gp, const gchar* subject, const gchar* issuer, const gchar* new_fingerprint, const gchar* old_fingerprint);
+ gchar* (*protocol_plugin_init_get_username)(RemminaProtocolWidget * gp);
+ gchar* (*protocol_plugin_init_get_password)(RemminaProtocolWidget * gp);
+ gchar* (*protocol_plugin_init_get_domain)(RemminaProtocolWidget * gp);
+ gboolean (* protocol_plugin_init_get_savepassword)(RemminaProtocolWidget *gp);
+ gint (* protocol_plugin_init_authx509)(RemminaProtocolWidget *gp);
+ gchar* (*protocol_plugin_init_get_cacert)(RemminaProtocolWidget * gp);
+ gchar* (*protocol_plugin_init_get_cacrl)(RemminaProtocolWidget * gp);
+ gchar* (*protocol_plugin_init_get_clientcert)(RemminaProtocolWidget * gp);
+ gchar* (*protocol_plugin_init_get_clientkey)(RemminaProtocolWidget * gp);
+ void (* protocol_plugin_init_save_cred)(RemminaProtocolWidget *gp);
+ void (* protocol_plugin_init_show_listen)(RemminaProtocolWidget *gp, gint port);
+ void (* protocol_plugin_init_show_retry)(RemminaProtocolWidget *gp);
+ void (* protocol_plugin_init_show)(RemminaProtocolWidget *gp);
+ void (* protocol_plugin_init_hide)(RemminaProtocolWidget *gp);
+ gboolean (* protocol_plugin_ssh_exec)(RemminaProtocolWidget *gp, gboolean wait, const gchar *fmt, ...);
+ void (* protocol_plugin_chat_open)(RemminaProtocolWidget *gp, const gchar *name,
+ void (*on_send)(RemminaProtocolWidget *gp, const gchar *text),
+ void (*on_destroy)(RemminaProtocolWidget *gp));
+ void (* protocol_plugin_chat_close)(RemminaProtocolWidget *gp);
+ void (* protocol_plugin_chat_receive)(RemminaProtocolWidget *gp, const gchar *text);
+ void (* protocol_plugin_send_keys_signals)(GtkWidget *widget, const guint *keyvals, int length, GdkEventType action);
+
+ gchar* (*file_get_user_datadir)(void);
+
+ RemminaFile* (*file_new)(void);
+ const gchar* (*file_get_path)(RemminaFile * remminafile);
+ void (* file_set_string)(RemminaFile *remminafile, const gchar *setting, const gchar *value);
+ const gchar* (*file_get_string)(RemminaFile * remminafile, const gchar * setting);
+ gchar* (*file_get_secret)(RemminaFile * remminafile, const gchar * setting);
+ void (* file_set_int)(RemminaFile *remminafile, const gchar *setting, gint value);
+ gint (* file_get_int)(RemminaFile *remminafile, const gchar *setting, gint default_value);
+ void (* file_unsave_password)(RemminaFile *remminafile);
+
+ void (* pref_set_value)(const gchar *key, const gchar *value);
+ gchar* (*pref_get_value)(const gchar * key);
+ gint (* pref_get_scale_quality)(void);
+ gint (* pref_get_sshtunnel_port)(void);
+ gint (* pref_get_ssh_loglevel)(void);
+ gboolean (* pref_get_ssh_parseconfig)(void);
+ guint (* pref_keymap_get_keyval)(const gchar *keymap, guint keyval);
+
+ void (* log_print)(const gchar *text);
+ void (* log_printf)(const gchar *fmt, ...);
+
+ void (* ui_register)(GtkWidget *widget);
+
+ GtkWidget* (*open_connection)(RemminaFile * remminafile, GCallback disconnect_cb, gpointer data, guint * handler);
+ void (* get_server_port)(const gchar *server, gint defaultport, gchar **host, gint *port);
+ gboolean (* is_main_thread)(void);
+ gboolean (* gtksocket_available)(void);
+ gint (* get_profile_remote_width)(RemminaProtocolWidget *gp);
+ gint (* get_profile_remote_height)(RemminaProtocolWidget *gp);
+
+} RemminaPluginService;
+
+/* "Prototype" of the plugin entry function */
+typedef gboolean (*RemminaPluginEntryFunc) (RemminaPluginService *service);
+
+G_END_DECLS
+
+
diff --git a/src/include/remmina/remmina_trace_calls.h b/src/include/remmina/remmina_trace_calls.h
new file mode 100644
index 000000000..bb670888a
--- /dev/null
+++ b/src/include/remmina/remmina_trace_calls.h
@@ -0,0 +1,54 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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.
+ *
+ */
+
+#pragma once
+
+#ifdef WITH_TRACE_CALLS
+
+#include <gtk/gtk.h>
+
+#define TRACE_CALL(text) \
+ { \
+ GDateTime *datetime = g_date_time_new_now_local(); \
+ gchar *sfmtdate = g_date_time_format(datetime, "%x %X"); \
+ g_print("%s Trace calls: %s\n", sfmtdate, text); \
+ g_free(sfmtdate); \
+ g_date_time_unref(datetime); \
+ }
+
+#else
+#define TRACE_CALL(text)
+#endif /* _WITH_TRACE_CALLS_ */
+
diff --git a/src/include/remmina/types.h b/src/include/remmina/types.h
new file mode 100644
index 000000000..2b194b38b
--- /dev/null
+++ b/src/include/remmina/types.h
@@ -0,0 +1,125 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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.
+ *
+ */
+
+#pragma once
+
+G_BEGIN_DECLS
+
+typedef struct _RemminaFile RemminaFile;
+
+typedef enum {
+ REMMINA_PROTOCOL_FEATURE_TYPE_END,
+ REMMINA_PROTOCOL_FEATURE_TYPE_PREF,
+ REMMINA_PROTOCOL_FEATURE_TYPE_TOOL,
+ REMMINA_PROTOCOL_FEATURE_TYPE_UNFOCUS,
+ REMMINA_PROTOCOL_FEATURE_TYPE_SCALE,
+ REMMINA_PROTOCOL_FEATURE_TYPE_DYNRESUPDATE
+} RemminaProtocolFeatureType;
+
+#define REMMINA_PROTOCOL_FEATURE_PREF_RADIO 1
+#define REMMINA_PROTOCOL_FEATURE_PREF_CHECK 2
+
+typedef struct _RemminaProtocolFeature {
+ RemminaProtocolFeatureType type;
+ gint id;
+ gpointer opt1;
+ gpointer opt2;
+ gpointer opt3;
+} RemminaProtocolFeature;
+
+typedef struct _RemminaPluginScreenshotData {
+ unsigned char* buffer;
+ int bitsPerPixel;
+ int bytesPerPixel;
+ int width;
+ int height;
+} RemminaPluginScreenshotData;
+
+
+typedef struct _RemminaProtocolWidgetClass RemminaProtocolWidgetClass;
+typedef struct _RemminaProtocolWidget RemminaProtocolWidget;
+typedef gpointer RemminaTunnelInitFunc;
+typedef gboolean (*RemminaXPortTunnelInitFunc) (RemminaProtocolWidget *gp,
+ gint remotedisplay, const gchar *server, gint port);
+
+typedef enum {
+ REMMINA_PROTOCOL_SETTING_TYPE_END,
+
+ REMMINA_PROTOCOL_SETTING_TYPE_SERVER,
+ REMMINA_PROTOCOL_SETTING_TYPE_PASSWORD,
+ REMMINA_PROTOCOL_SETTING_TYPE_RESOLUTION,
+ REMMINA_PROTOCOL_SETTING_TYPE_KEYMAP,
+
+ REMMINA_PROTOCOL_SETTING_TYPE_TEXT,
+ REMMINA_PROTOCOL_SETTING_TYPE_SELECT,
+ REMMINA_PROTOCOL_SETTING_TYPE_COMBO,
+ REMMINA_PROTOCOL_SETTING_TYPE_CHECK,
+ REMMINA_PROTOCOL_SETTING_TYPE_FILE,
+ REMMINA_PROTOCOL_SETTING_TYPE_FOLDER
+} RemminaProtocolSettingType;
+
+typedef struct _RemminaProtocolSetting {
+ RemminaProtocolSettingType type;
+ const gchar *name;
+ const gchar *label;
+ gboolean compact;
+ const gpointer opt1;
+ const gpointer opt2;
+} RemminaProtocolSetting;
+
+typedef enum {
+ REMMINA_PROTOCOL_SSH_SETTING_NONE,
+ REMMINA_PROTOCOL_SSH_SETTING_TUNNEL,
+ REMMINA_PROTOCOL_SSH_SETTING_SSH,
+ REMMINA_PROTOCOL_SSH_SETTING_REVERSE_TUNNEL,
+ REMMINA_PROTOCOL_SSH_SETTING_SFTP
+} RemminaProtocolSSHSetting;
+
+typedef enum {
+ REMMINA_AUTHPWD_TYPE_PROTOCOL,
+ REMMINA_AUTHPWD_TYPE_SSH_PWD,
+ REMMINA_AUTHPWD_TYPE_SSH_PRIVKEY
+} RemminaAuthpwdType;
+
+typedef enum {
+ REMMINA_PROTOCOL_WIDGET_SCALE_MODE_NONE = 0,
+ REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED = 1,
+ REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES = 2
+} RemminaScaleMode;
+
+G_END_DECLS
+
+
diff --git a/src/remmina.1 b/src/remmina.1
new file mode 100644
index 000000000..803a9e34a
--- /dev/null
+++ b/src/remmina.1
@@ -0,0 +1,96 @@
+.Dd 2016-10-04
+.Dt REMMINA 1
+.Sh NAME
+.Nm remmina
+.Nd Remmina the GTK+ Remote Desktop Client
+.Sh SYNOPSIS
+.Nm
+.Op Fl a|i|n|q|v
+.Op Fl c Ar FILE.remmina
+.Op Fl e Ar FILE.remmina
+.Op Fl p Ar PAGENR
+.Op Fl s Ar SERVER
+.Op Fl t Ar PROTOCOL
+.Op Fl x Ar PLUGIN
+.Op Fl -display Ar DISPLAY
+.Sh DESCRIPTION
+Remmina is a remote desktop client written in GTK+, aiming to be useful for system
+administrators and travellers, who need to work with lots of remote computers
+in front of either large monitors or tiny netbooks. Remmina supports multiple
+network protocols in an integrated and consistent user interface.
+Currently RDP, VNC, SPICE, NX, XDMCP and SSH are supported.
+
+Remmina is released in separated source packages:
+
+ "remmina", the main GTK+ application
+ "remmina-plugins", a set of plugins
+
+Remmina is free and open-source software, released under GNU GPL license.
+.Sh FILES
+.Tp
+\(Do\(lCXDG_CONFIG_DIRS\(rC/remmina.pref or \(Do\(lCXDG_CONFIG_HOME\(rC/remmina/remmina.pref :
+.Lp
+Remmina configuration files.
+.Lp
+At the first Remmina execution the system wide Remmina configuration files,
+will be copied in the \(Do\(lCXDG_CONFIG_HOME\(rC
+.Lp
+.Tp
+\(Do\(lCXDG_DATA_DIRS\(rC/FILE.remmina or \(Do\(lCXDG_DATA_HOME\(rC/remmina/FILE.remmina :
+.Lp
+Remmina profiles, the file name is autogenerated, but you can create manually your
+own files with the \fBFILE\fR name you prefer.
+.Sh OPTIONS
+.Bl -tag -width Ds
+.It Fl h, -help
+Show help options
+.It Fl a
+Show about dialog
+.Tp
+.It Fl c, -connect\fR=\fIFILE\fR
+Connect to a .remmina file
+.Tp
+.It Fl e, -edit\fR=\fIFILE\fR
+Edit a .remmina file
+.Tp
+.It Fl n, -new\fR
+Create a new connection profile
+.Tp
+.It Fl p, -pref\fR=\fIPAGENR\fR
+Show preferences dialog page
+.Tp
+.It Fl x, -plugin\fR=\fIPLUGIN\fR
+Execute the plugin
+.Tp
+.It Fl q, -quit\fR
+Quit the application
+.Tp
+.It Fl s, -server\fR=\fISERVER\fR
+Use default server name (for \fB\-\-new\fR)
+.Tp
+.It Fl t, -protocol\fR=\fIPROTOCOL\fR
+Use default protocol (for \fB\-\-new\fR)
+.Tp
+.It Fl i, -icon\fR
+Start as tray icon
+.Tp
+.It Fl v, -version\fR
+Show the application's version
+.Tp
+.It Fl \-display\fR=\fIDISPLAY\fR
+X display to use
+.Sh SEE ALSO
+.Sh AUTHORS
+Antenore Gatta <antenore at simbiosi dot org> and Giovanni Panozzo <giovanni at panozzo dot it>
+.Lp
+See the THANKS file for a more detailed list.
+.Lp
+Remmina was initially written by Vic Lee <llyzs@163.com>
+.Lp
+This manual page was written by Antenore Gatta <antenore at simbiosi dot org>.
+.Lp
+.Sh COPYRIGHT
+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, or (at your option) any
+later version.
diff --git a/src/remmina.c b/src/remmina.c
new file mode 100644
index 000000000..a7399bbf2
--- /dev/null
+++ b/src/remmina.c
@@ -0,0 +1,292 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2010 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 <gdk/gdkx.h>
+#include <gio/gio.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <stdlib.h>
+
+#include "config.h"
+#include "remmina_exec.h"
+#include "remmina_file_manager.h"
+#include "remmina_icon.h"
+#include "remmina_main.h"
+#include "remmina_masterthread_exec.h"
+#include "remmina_plugin_manager.h"
+#include "remmina_pref.h"
+#include "remmina_public.h"
+#include "remmina_sftp_plugin.h"
+#include "remmina_ssh_plugin.h"
+#include "remmina_widget_pool.h"
+#include "remmina/remmina_trace_calls.h"
+#include "remmina_stats_sender.h"
+
+
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#include <pthread.h>
+#ifdef HAVE_LIBGCRYPT
+#include <gcrypt.h>
+# if GCRYPT_VERSION_NUMBER < 0x010600
+GCRY_THREAD_OPTION_PTHREAD_IMPL;
+#endif /* !GCRYPT_VERSION_NUMBER */
+#endif /* HAVE_LIBGCRYPT */
+
+#ifdef HAVE_LIBGCRYPT
+# if GCRYPT_VERSION_NUMBER < 0x010600
+static int gcrypt_thread_initialized = 0;
+#endif /* !GCRYPT_VERSION_NUMBER */
+#endif /* HAVE_LIBGCRYPT */
+
+static GOptionEntry remmina_options[] =
+{
+ { "about", 'a', 0, G_OPTION_ARG_NONE, NULL, N_("Show about dialog"), NULL },
+ { "connect", 'c', 0, G_OPTION_ARG_FILENAME, NULL, N_("Connect to a .remmina file"), "FILE" },
+ { G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_FILENAME_ARRAY, NULL, N_("Connect to a .remmina file"), "FILE" },
+ { "edit", 'e', 0, G_OPTION_ARG_FILENAME, NULL, N_("Edit a .remmina file"), "FILE" },
+ { "help", '?', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, NULL, NULL, NULL },
+ { "new", 'n', 0, G_OPTION_ARG_NONE, NULL, N_("Create a new connection profile"), NULL },
+ { "pref", 'p', 0, G_OPTION_ARG_STRING, NULL, N_("Show preferences dialog page"), "PAGENR" },
+ { "plugin", 'x', 0, G_OPTION_ARG_STRING, NULL, N_("Execute the plugin"), "PLUGIN" },
+ { "quit", 'q', 0, G_OPTION_ARG_NONE, NULL, N_("Quit the application"), NULL },
+ { "server", 's', 0, G_OPTION_ARG_STRING, NULL, N_("Use default server name (for --new)"), "SERVER" },
+ { "protocol", 't', 0, G_OPTION_ARG_STRING, NULL, N_("Use default protocol (for --new)"), "PROTOCOL" },
+ { "icon", 'i', 0, G_OPTION_ARG_NONE, NULL, N_("Start as tray icon"), NULL },
+ { "version", 'v', 0, G_OPTION_ARG_NONE, NULL, N_("Show the application's version"), NULL },
+ { "full-version", 'V', 0, G_OPTION_ARG_NONE, NULL, N_("Show the application's version, including the pulgin versions"), NULL },
+ { NULL }
+};
+
+#ifdef WITH_LIBGCRYPT
+static int
+_gpg_error_to_errno(gcry_error_t e)
+{
+ /* be lazy right now */
+ if (e == GPG_ERR_NO_ERROR)
+ return (0);
+ else
+ return (EINVAL);
+}
+#endif /* !WITH_LIBGCRYPT */
+
+static gint remmina_on_command_line(GApplication *app, GApplicationCommandLine *cmdline)
+{
+ TRACE_CALL(__func__);
+
+ gint status = 0;
+ gboolean executed = FALSE;
+ GVariantDict *opts;
+ gchar *str;
+ const gchar **remaining_args;
+ gchar *protocol;
+ gchar *server;
+
+ opts = g_application_command_line_get_options_dict(cmdline);
+
+ if (g_variant_dict_lookup_value(opts, "quit", NULL)) {
+ remmina_exec_command(REMMINA_COMMAND_EXIT, NULL);
+ executed = TRUE;
+ status = 1;
+ }
+
+ if (g_variant_dict_lookup_value(opts, "about", NULL)) {
+ remmina_exec_command(REMMINA_COMMAND_ABOUT, NULL);
+ executed = TRUE;
+ }
+
+ /** @todo This should be a G_OPTION_ARG_FILENAME_ARRAY (^aay) so that
+ * we can implement multi profile connection:
+ * https://github.com/FreeRDP/Remmina/issues/915
+ */
+ if (g_variant_dict_lookup(opts, "connect", "^ay", &str)) {
+ remmina_exec_command(REMMINA_COMMAND_CONNECT, g_strdup(str));
+ g_free(str);
+ executed = TRUE;
+ }
+
+ if (g_variant_dict_lookup(opts, G_OPTION_REMAINING, "^a&ay", &remaining_args)) {
+ remmina_exec_command(REMMINA_COMMAND_CONNECT, remaining_args[0]);
+ g_free(remaining_args);
+ executed = TRUE;
+ }
+
+ if (g_variant_dict_lookup(opts, "edit", "^ay", &str)) {
+ remmina_exec_command(REMMINA_COMMAND_EDIT, str);
+ g_free(str);
+ executed = TRUE;
+ }
+
+ if (g_variant_dict_lookup_value(opts, "new", NULL)) {
+ if (!g_variant_dict_lookup(opts, "protocol", "&s", &protocol))
+ protocol = NULL;
+
+ if (g_variant_dict_lookup(opts, "server", "&s", &server)) {
+ str = g_strdup_printf("%s,%s", protocol, server);
+ }else {
+ str = g_strdup(protocol);
+ }
+
+ remmina_exec_command(REMMINA_COMMAND_NEW, str);
+ g_free(str);
+ executed = TRUE;
+ }
+
+ if (g_variant_dict_lookup(opts, "pref", "&s", &str)) {
+ remmina_exec_command(REMMINA_COMMAND_PREF, str);
+ executed = TRUE;
+ }
+
+ if (g_variant_dict_lookup(opts, "plugin", "&s", &str)) {
+ remmina_exec_command(REMMINA_COMMAND_PLUGIN, str);
+ executed = TRUE;
+ }
+
+ if (g_variant_dict_lookup_value(opts, "icon", NULL)) {
+ remmina_exec_command(REMMINA_COMMAND_NONE, NULL);
+ executed = TRUE;
+ }
+
+ if (g_variant_dict_lookup_value(opts, "version", NULL)) {
+ remmina_exec_command(REMMINA_COMMAND_VERSION, NULL);
+ executed = TRUE;
+ }
+
+ if (g_variant_dict_lookup_value(opts, "full-version", NULL)) {
+ remmina_exec_command(REMMINA_COMMAND_FULL_VERSION, NULL);
+ executed = TRUE;
+ }
+
+ if (!executed) {
+ remmina_exec_command(REMMINA_COMMAND_MAIN, NULL);
+ }
+
+ return status;
+}
+
+static void remmina_on_startup(GApplication *app)
+{
+ TRACE_CALL(__func__);
+
+ RemminaSecretPlugin *secret_plugin;
+
+ remmina_file_manager_init();
+ remmina_pref_init();
+ remmina_plugin_manager_init();
+ remmina_widget_pool_init();
+ remmina_sftp_plugin_register();
+ remmina_ssh_plugin_register();
+ remmina_icon_init();
+
+ g_set_application_name("Remmina");
+ gtk_window_set_default_icon_name("remmina");
+
+ gtk_icon_theme_append_search_path(gtk_icon_theme_get_default(),
+ REMMINA_RUNTIME_DATADIR G_DIR_SEPARATOR_S "icons");
+ g_application_hold(app);
+
+ remmina_stats_sender_schedule();
+
+ /* Check for secret plugin and service initialization and show some warnings on the console if
+ * there is something missing */
+ secret_plugin = remmina_plugin_manager_get_secret_plugin();
+ if (!secret_plugin) {
+ g_print("WARNING: Remmina is running without a secret plugin. Passwords will be saved in a less secure way.\n");
+ } else {
+ if (!secret_plugin->is_service_available()) {
+ g_print("WARNING: Remmina is running with a secret plugin, but it cannot connect to a secret service.\n");
+ }
+ }
+
+}
+
+static gint remmina_on_local_cmdline(GApplication *app, GVariantDict *options, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+
+ int status = -1;
+
+ /* Here you handle any command line options that you want to be executed
+ * from command line, one time, and than exit */
+
+ return status;
+}
+
+int main(int argc, char* argv[])
+{
+ TRACE_CALL(__func__);
+ GtkApplication *app;
+ const gchar *app_id;
+ int status;
+
+ gdk_set_allowed_backends("x11,broadway,quartz,mir");
+
+ remmina_masterthread_exec_save_main_thread_id();
+
+ bindtextdomain(GETTEXT_PACKAGE, REMMINA_RUNTIME_LOCALEDIR);
+ bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
+ textdomain(GETTEXT_PACKAGE);
+
+#ifdef HAVE_LIBGCRYPT
+# if GCRYPT_VERSION_NUMBER < 0x010600
+ gcry_error_t e;
+ if (!gcrypt_thread_initialized) {
+ if ((e = gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread)) != GPG_ERR_NO_ERROR) {
+ return (-1);
+ }
+ gcrypt_thread_initialized++;
+ }
+#endif /* !GCRYPT_VERSION_NUMBER */
+ gcry_check_version(NULL);
+ gcry_control(GCRYCTL_DISABLE_SECMEM, 0);
+ gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
+#endif /* !HAVE_LIBGCRYPT */
+
+ app_id = g_application_id_is_valid(UNIQUE_APPNAME) ? UNIQUE_APPNAME : NULL;
+ app = gtk_application_new(app_id, G_APPLICATION_HANDLES_COMMAND_LINE);
+ g_signal_connect(app, "startup", G_CALLBACK(remmina_on_startup), NULL);
+ g_signal_connect(app, "command-line", G_CALLBACK(remmina_on_command_line), NULL);
+ g_signal_connect(app, "handle-local-options", G_CALLBACK(remmina_on_local_cmdline), NULL);
+
+ g_application_add_main_option_entries(G_APPLICATION(app), remmina_options);
+
+ g_application_set_inactivity_timeout(G_APPLICATION(app), 10000);
+ status = g_application_run(G_APPLICATION(app), argc, argv);
+ g_object_unref(app);
+
+ return status;
+}
diff --git a/src/remmina_about.c b/src/remmina_about.c
new file mode 100644
index 000000000..ef2ad637f
--- /dev/null
+++ b/src/remmina_about.c
@@ -0,0 +1,64 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2011 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include "remmina_about.h"
+#include "remmina_public.h"
+#include "remmina/remmina_trace_calls.h"
+
+/* Show the about dialog from the file ui/remmina_about.glade */
+void remmina_about_open(GtkWindow *parent)
+{
+ TRACE_CALL(__func__);
+
+ GtkBuilder *builder = remmina_public_gtk_builder_new_from_file("remmina_about.glade");
+ GtkDialog *dialog = GTK_DIALOG(gtk_builder_get_object(builder, "dialog_remmina_about"));
+
+ gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(dialog), VERSION " (git " REMMINA_GIT_REVISION ")");
+ gtk_about_dialog_set_translator_credits(GTK_ABOUT_DIALOG(dialog), _("translator-credits"));
+
+ if (parent) {
+ gtk_window_set_transient_for(GTK_WINDOW(dialog), parent);
+ gtk_window_set_destroy_with_parent(GTK_WINDOW(dialog), TRUE);
+ }
+
+ g_signal_connect(dialog, "response", G_CALLBACK(gtk_widget_destroy), NULL);
+ gtk_window_present(GTK_WINDOW(dialog));
+
+ g_object_unref(G_OBJECT(builder));
+}
diff --git a/src/remmina_about.h b/src/remmina_about.h
new file mode 100644
index 000000000..62a728b04
--- /dev/null
+++ b/src/remmina_about.h
@@ -0,0 +1,45 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2011 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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.
+ *
+ */
+
+#pragma once
+
+G_BEGIN_DECLS
+
+void remmina_about_open(GtkWindow *parent);
+
+G_END_DECLS
+
+
diff --git a/src/remmina_applet_menu.c b/src/remmina_applet_menu.c
new file mode 100644
index 000000000..a34f8b24b
--- /dev/null
+++ b/src/remmina_applet_menu.c
@@ -0,0 +1,271 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 "config.h"
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <string.h>
+
+#include "remmina_public.h"
+#include "remmina_applet_menu_item.h"
+#include "remmina_applet_menu.h"
+#include "remmina_file_manager.h"
+#include "remmina/remmina_trace_calls.h"
+
+G_DEFINE_TYPE( RemminaAppletMenu, remmina_applet_menu, GTK_TYPE_MENU)
+
+struct _RemminaAppletMenuPriv {
+ gboolean hide_count;
+};
+
+enum {
+ LAUNCH_ITEM_SIGNAL, EDIT_ITEM_SIGNAL, LAST_SIGNAL
+};
+
+static guint remmina_applet_menu_signals[LAST_SIGNAL] =
+{ 0 };
+
+static void remmina_applet_menu_destroy(RemminaAppletMenu *menu, gpointer data)
+{
+ TRACE_CALL(__func__);
+ g_free(menu->priv);
+}
+
+static void remmina_applet_menu_class_init(RemminaAppletMenuClass *klass)
+{
+ TRACE_CALL(__func__);
+ remmina_applet_menu_signals[LAUNCH_ITEM_SIGNAL] = g_signal_new("launch-item", G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaAppletMenuClass, launch_item), NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, G_TYPE_OBJECT);
+ remmina_applet_menu_signals[EDIT_ITEM_SIGNAL] = g_signal_new("edit-item", G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaAppletMenuClass, edit_item), NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, G_TYPE_OBJECT);
+}
+
+static void remmina_applet_menu_init(RemminaAppletMenu *menu)
+{
+ TRACE_CALL(__func__);
+ menu->priv = g_new0(RemminaAppletMenuPriv, 1);
+
+ g_signal_connect(G_OBJECT(menu), "destroy", G_CALLBACK(remmina_applet_menu_destroy), NULL);
+}
+
+static void remmina_applet_menu_on_item_activate(RemminaAppletMenuItem *menuitem, RemminaAppletMenu *menu)
+{
+ TRACE_CALL(__func__);
+ g_signal_emit(G_OBJECT(menu), remmina_applet_menu_signals[LAUNCH_ITEM_SIGNAL], 0, menuitem);
+}
+
+static GtkWidget*
+remmina_applet_menu_add_group(GtkWidget *menu, const gchar *group, gint position, RemminaAppletMenuItem *menuitem,
+ GtkWidget **groupmenuitem)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *widget;
+ GtkWidget *submenu;
+
+ widget = gtk_menu_item_new_with_label(group);
+ gtk_widget_show(widget);
+
+ g_object_set_data_full(G_OBJECT(widget), "group", g_strdup(group), g_free);
+ g_object_set_data(G_OBJECT(widget), "count", GINT_TO_POINTER(0));
+ if (groupmenuitem) {
+ *groupmenuitem = widget;
+ }
+ if (position < 0) {
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), widget);
+ }else {
+ gtk_menu_shell_insert(GTK_MENU_SHELL(menu), widget, position);
+ }
+
+ submenu = gtk_menu_new();
+ gtk_widget_show(submenu);
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(widget), submenu);
+
+ return submenu;
+}
+
+static void remmina_applet_menu_increase_group_count(GtkWidget *widget)
+{
+ TRACE_CALL(__func__);
+ gint cnt;
+ gchar *s;
+
+ cnt = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "count")) + 1;
+ g_object_set_data(G_OBJECT(widget), "count", GINT_TO_POINTER(cnt));
+ s = g_strdup_printf("%s (%i)", (const gchar*)g_object_get_data(G_OBJECT(widget), "group"), cnt);
+ gtk_menu_item_set_label(GTK_MENU_ITEM(widget), s);
+ g_free(s);
+}
+
+void remmina_applet_menu_register_item(RemminaAppletMenu *menu, RemminaAppletMenuItem *menuitem)
+{
+ TRACE_CALL(__func__);
+ g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(remmina_applet_menu_on_item_activate), menu);
+}
+
+void remmina_applet_menu_add_item(RemminaAppletMenu *menu, RemminaAppletMenuItem *menuitem)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *submenu;
+ GtkWidget *groupmenuitem;
+ GtkMenuItem *submenuitem;
+ gchar *s, *p1, *p2, *mstr;
+ GList *childs, *child;
+ gint position;
+
+ submenu = GTK_WIDGET(menu);
+ s = g_strdup(menuitem->group);
+ p1 = s;
+ p2 = p1 ? strchr(p1, '/') : NULL;
+ if (p2)
+ *p2++ = '\0';
+ while (p1 && p1[0]) {
+ groupmenuitem = NULL;
+ childs = gtk_container_get_children(GTK_CONTAINER(submenu));
+ position = -1;
+ for (child = g_list_first(childs); child; child = g_list_next(child)) {
+ if (!GTK_IS_MENU_ITEM(child->data))
+ continue;
+ position++;
+ submenuitem = GTK_MENU_ITEM(child->data);
+ if (gtk_menu_item_get_submenu(submenuitem)) {
+ mstr = (gchar*)g_object_get_data(G_OBJECT(submenuitem), "group");
+ if (g_strcmp0(p1, mstr) == 0) {
+ /* Found existing group menu */
+ submenu = gtk_menu_item_get_submenu(submenuitem);
+ groupmenuitem = GTK_WIDGET(submenuitem);
+ break;
+ }else {
+ /* Redo comparison ignoring case and respecting international
+ * collation, to set menu sort order */
+ if (strcoll(p1, mstr) < 0) {
+ submenu = remmina_applet_menu_add_group(submenu, p1, position, menuitem,
+ &groupmenuitem);
+ break;
+ }
+ }
+ }else {
+ submenu = remmina_applet_menu_add_group(submenu, p1, position, menuitem, &groupmenuitem);
+ break;
+ }
+
+ }
+
+ if (!child) {
+ submenu = remmina_applet_menu_add_group(submenu, p1, -1, menuitem, &groupmenuitem);
+ }
+ g_list_free(childs);
+ if (groupmenuitem && !menu->priv->hide_count) {
+ remmina_applet_menu_increase_group_count(groupmenuitem);
+ }
+ p1 = p2;
+ p2 = p1 ? strchr(p1, '/') : NULL;
+ if (p2)
+ *p2++ = '\0';
+ }
+ g_free(s);
+
+ childs = gtk_container_get_children(GTK_CONTAINER(submenu));
+ position = -1;
+ for (child = g_list_first(childs); child; child = g_list_next(child)) {
+ if (!GTK_IS_MENU_ITEM(child->data))
+ continue;
+ position++;
+ submenuitem = GTK_MENU_ITEM(child->data);
+ if (gtk_menu_item_get_submenu(submenuitem))
+ continue;
+ if (!REMMINA_IS_APPLET_MENU_ITEM(submenuitem))
+ continue;
+ if (strcoll(menuitem->name, REMMINA_APPLET_MENU_ITEM(submenuitem)->name) <= 0) {
+ gtk_menu_shell_insert(GTK_MENU_SHELL(submenu), GTK_WIDGET(menuitem), position);
+ break;
+ }
+ }
+ if (!child) {
+ gtk_menu_shell_append(GTK_MENU_SHELL(submenu), GTK_WIDGET(menuitem));
+ }
+ g_list_free(childs);
+ remmina_applet_menu_register_item(menu, menuitem);
+}
+
+GtkWidget*
+remmina_applet_menu_new(void)
+{
+ TRACE_CALL(__func__);
+ RemminaAppletMenu *menu;
+
+ menu = REMMINA_APPLET_MENU(g_object_new(REMMINA_TYPE_APPLET_MENU, NULL));
+
+ return GTK_WIDGET(menu);
+}
+
+void remmina_applet_menu_set_hide_count(RemminaAppletMenu *menu, gboolean hide_count)
+{
+ TRACE_CALL(__func__);
+ menu->priv->hide_count = hide_count;
+}
+
+void remmina_applet_menu_populate(RemminaAppletMenu *menu)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *menuitem;
+ gchar filename[MAX_PATH_LEN];
+ GDir *dir;
+ gchar *remmina_data_dir;
+ const gchar *name;
+
+ remmina_data_dir = remmina_file_get_datadir();
+ dir = g_dir_open(remmina_data_dir, 0, NULL);
+ if (dir != NULL) {
+ /* Iterate all remote desktop profiles */
+ while ((name = g_dir_read_name(dir)) != NULL) {
+ if (!g_str_has_suffix(name, ".remmina"))
+ continue;
+ g_snprintf(filename, sizeof(filename), "%s/%s", remmina_data_dir, name);
+
+ menuitem = remmina_applet_menu_item_new(REMMINA_APPLET_MENU_ITEM_FILE, filename);
+ if (menuitem != NULL) {
+ remmina_applet_menu_add_item(menu, REMMINA_APPLET_MENU_ITEM(menuitem));
+ gtk_widget_show(menuitem);
+ }
+ }
+ g_dir_close(dir);
+ }
+ g_free(remmina_data_dir);
+}
+
diff --git a/src/remmina_applet_menu.h b/src/remmina_applet_menu.h
new file mode 100644
index 000000000..b31a1ad92
--- /dev/null
+++ b/src/remmina_applet_menu.h
@@ -0,0 +1,79 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010 Vic Lee
+ * Copyright (C) 2017-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.
+ *
+ */
+
+#pragma once
+
+G_BEGIN_DECLS
+
+#define REMMINA_TYPE_APPLET_MENU (remmina_applet_menu_get_type())
+#define REMMINA_APPLET_MENU(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), REMMINA_TYPE_APPLET_MENU, RemminaAppletMenu))
+#define REMMINA_APPLET_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), REMMINA_TYPE_APPLET_MENU, RemminaAppletMenuClass))
+#define REMMINA_IS_APPLET_MENU(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), REMMINA_TYPE_APPLET_MENU))
+#define REMMINA_IS_APPLET_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), REMMINA_TYPE_APPLET_MENU))
+#define REMMINA_APPLET_MENU_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), REMMINA_TYPE_APPLET_MENU, RemminaAppletMenuClass))
+
+typedef enum {
+ REMMINA_APPLET_MENU_NEW_CONNECTION_NONE,
+ REMMINA_APPLET_MENU_NEW_CONNECTION_TOP,
+ REMMINA_APPLET_MENU_NEW_CONNECTION_BOTTOM
+} RemminaAppletMenuNewConnectionType;
+
+typedef struct _RemminaAppletMenuPriv RemminaAppletMenuPriv;
+
+typedef struct _RemminaAppletMenu {
+ GtkMenu menu;
+
+ RemminaAppletMenuPriv* priv;
+} RemminaAppletMenu;
+
+typedef struct _RemminaAppletMenuClass {
+ GtkMenuClass parent_class;
+
+ void (*launch_item)(RemminaAppletMenu* menu);
+ void (*edit_item)(RemminaAppletMenu* menu);
+} RemminaAppletMenuClass;
+
+GType remmina_applet_menu_get_type(void)
+G_GNUC_CONST;
+
+void remmina_applet_menu_register_item(RemminaAppletMenu* menu, RemminaAppletMenuItem* menuitem);
+void remmina_applet_menu_add_item(RemminaAppletMenu* menu, RemminaAppletMenuItem* menuitem);
+GtkWidget* remmina_applet_menu_new(void);
+void remmina_applet_menu_set_hide_count(RemminaAppletMenu* menu, gboolean hide_count);
+void remmina_applet_menu_populate(RemminaAppletMenu* menu);
+
+G_END_DECLS
+
+
diff --git a/src/remmina_applet_menu_item.c b/src/remmina_applet_menu_item.c
new file mode 100644
index 000000000..f3bd58667
--- /dev/null
+++ b/src/remmina_applet_menu_item.c
@@ -0,0 +1,177 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2010 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <glib/gprintf.h>
+#include <string.h>
+#include <stdarg.h>
+#include "remmina_applet_menu_item.h"
+#include "remmina/remmina_trace_calls.h"
+
+G_DEFINE_TYPE( RemminaAppletMenuItem, remmina_applet_menu_item, GTK_TYPE_MENU_ITEM)
+
+#define IS_EMPTY(s) ((!s) || (s[0] == 0))
+
+static void remmina_applet_menu_item_destroy(RemminaAppletMenuItem* item, gpointer data)
+{
+ TRACE_CALL(__func__);
+ g_free(item->filename);
+ g_free(item->name);
+ g_free(item->group);
+ g_free(item->protocol);
+ g_free(item->server);
+}
+
+static void remmina_applet_menu_item_class_init(RemminaAppletMenuItemClass* klass)
+{
+ TRACE_CALL(__func__);
+}
+
+static void remmina_applet_menu_item_init(RemminaAppletMenuItem* item)
+{
+ TRACE_CALL(__func__);
+ item->filename = NULL;
+ item->name = NULL;
+ item->group = NULL;
+ item->protocol = NULL;
+ item->server = NULL;
+ item->ssh_enabled = FALSE;
+ g_signal_connect(G_OBJECT(item), "destroy", G_CALLBACK(remmina_applet_menu_item_destroy), NULL);
+}
+
+GtkWidget* remmina_applet_menu_item_new(RemminaAppletMenuItemType item_type, ...)
+{
+ TRACE_CALL(__func__);
+ va_list ap;
+ RemminaAppletMenuItem* item;
+ GKeyFile* gkeyfile;
+ GtkWidget* widget;
+
+ va_start(ap, item_type);
+
+ item = REMMINA_APPLET_MENU_ITEM(g_object_new(REMMINA_TYPE_APPLET_MENU_ITEM, NULL));
+
+ item->item_type = item_type;
+
+ switch (item_type) {
+ case REMMINA_APPLET_MENU_ITEM_FILE:
+ item->filename = g_strdup(va_arg(ap, const gchar*));
+
+ /* Load the file */
+ gkeyfile = g_key_file_new();
+
+ if (!g_key_file_load_from_file(gkeyfile, item->filename, G_KEY_FILE_NONE, NULL)) {
+ g_key_file_free(gkeyfile);
+ va_end(ap);
+ return NULL;
+ }
+
+ item->name = g_key_file_get_string(gkeyfile, "remmina", "name", NULL);
+ item->group = g_key_file_get_string(gkeyfile, "remmina", "group", NULL);
+ item->protocol = g_key_file_get_string(gkeyfile, "remmina", "protocol", NULL);
+ item->server = g_key_file_get_string(gkeyfile, "remmina", "server", NULL);
+ item->ssh_enabled = g_key_file_get_boolean(gkeyfile, "remmina", "ssh_enabled", NULL);
+
+ g_key_file_free(gkeyfile);
+
+ if (item->name == NULL) {
+ g_printf("WARNING: missing name= line in file %s. Skipping.\n", item->filename);
+ va_end(ap);
+ return NULL;
+ }
+
+ break;
+
+ case REMMINA_APPLET_MENU_ITEM_DISCOVERED:
+ item->name = g_strdup(va_arg(ap, const gchar *));
+ item->group = g_strdup(_("Discovered"));
+ item->protocol = g_strdup("VNC");
+ break;
+
+ case REMMINA_APPLET_MENU_ITEM_NEW:
+ item->name = g_strdup(_("New Connection"));
+ break;
+ }
+
+ va_end(ap);
+
+ /* Create the label */
+ widget = gtk_label_new(item->name);
+ gtk_widget_show(widget);
+ gtk_widget_set_valign(widget, GTK_ALIGN_START);
+ gtk_widget_set_halign(widget, GTK_ALIGN_START);
+ gtk_container_add(GTK_CONTAINER(item), widget);
+
+ if (item->server) {
+ gtk_widget_set_tooltip_text(GTK_WIDGET(item), item->server);
+ }
+
+ return GTK_WIDGET(item);
+}
+
+gint remmina_applet_menu_item_compare(gconstpointer a, gconstpointer b, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ gint cmp;
+ RemminaAppletMenuItem* itema;
+ RemminaAppletMenuItem* itemb;
+
+ /* Passed in parameters are pointers to pointers */
+ itema = REMMINA_APPLET_MENU_ITEM(*((void**)a));
+ itemb = REMMINA_APPLET_MENU_ITEM(*((void**)b));
+
+ /* Put ungrouped items to the last */
+ if (IS_EMPTY(itema->group) && !IS_EMPTY(itemb->group))
+ return 1;
+ if (!IS_EMPTY(itema->group) && IS_EMPTY(itemb->group))
+ return -1;
+
+ /* Put discovered items the last group */
+ if (itema->item_type == REMMINA_APPLET_MENU_ITEM_DISCOVERED && itemb->item_type != REMMINA_APPLET_MENU_ITEM_DISCOVERED)
+ return 1;
+ if (itema->item_type != REMMINA_APPLET_MENU_ITEM_DISCOVERED && itemb->item_type == REMMINA_APPLET_MENU_ITEM_DISCOVERED)
+ return -1;
+
+ if (itema->item_type != REMMINA_APPLET_MENU_ITEM_DISCOVERED && !IS_EMPTY(itema->group)) {
+ cmp = g_strcmp0(itema->group, itemb->group);
+
+ if (cmp != 0)
+ return cmp;
+ }
+
+ return g_strcmp0(itema->name, itemb->name);
+}
diff --git a/src/remmina_applet_menu_item.h b/src/remmina_applet_menu_item.h
new file mode 100644
index 000000000..11a918e6f
--- /dev/null
+++ b/src/remmina_applet_menu_item.h
@@ -0,0 +1,75 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2010 Vic Lee
+ * Copyright (C) 2017-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.
+ *
+ */
+
+#pragma once
+
+G_BEGIN_DECLS
+
+#define REMMINA_TYPE_APPLET_MENU_ITEM (remmina_applet_menu_item_get_type())
+#define REMMINA_APPLET_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), REMMINA_TYPE_APPLET_MENU_ITEM, RemminaAppletMenuItem))
+#define REMMINA_APPLET_MENU_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), REMMINA_TYPE_APPLET_MENU_ITEM, RemminaAppletMenuItemClass))
+#define REMMINA_IS_APPLET_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), REMMINA_TYPE_APPLET_MENU_ITEM))
+#define REMMINA_IS_APPLET_MENU_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), REMMINA_TYPE_APPLET_MENU_ITEM))
+#define REMMINA_APPLET_MENU_ITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), REMMINA_TYPE_APPLET_MENU_ITEM, RemminaAppletMenuItemClass))
+
+typedef enum {
+ REMMINA_APPLET_MENU_ITEM_FILE, REMMINA_APPLET_MENU_ITEM_NEW, REMMINA_APPLET_MENU_ITEM_DISCOVERED
+} RemminaAppletMenuItemType;
+
+typedef struct _RemminaAppletMenuItem {
+ GtkImageMenuItem image_menu_item;
+
+ RemminaAppletMenuItemType item_type;
+ gchar* filename;
+ gchar* name;
+ gchar* group;
+ gchar* protocol;
+ gchar* server;
+ gboolean ssh_enabled;
+} RemminaAppletMenuItem;
+
+typedef struct _RemminaAppletMenuItemClass {
+ GtkImageMenuItemClass parent_class;
+} RemminaAppletMenuItemClass;
+
+GType remmina_applet_menu_item_get_type(void)
+G_GNUC_CONST;
+
+GtkWidget* remmina_applet_menu_item_new(RemminaAppletMenuItemType item_type, ...);
+gint remmina_applet_menu_item_compare(gconstpointer a, gconstpointer b, gpointer user_data);
+
+G_END_DECLS
+
+
diff --git a/src/remmina_avahi.c b/src/remmina_avahi.c
new file mode 100644
index 000000000..a373cfd39
--- /dev/null
+++ b/src/remmina_avahi.c
@@ -0,0 +1,301 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2010 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 <gtk/gtk.h>
+#include "config.h"
+#include "remmina_avahi.h"
+#include "remmina/remmina_trace_calls.h"
+
+#ifdef HAVE_LIBAVAHI_CLIENT
+
+#include <avahi-client/client.h>
+#include <avahi-client/lookup.h>
+#include <avahi-common/simple-watch.h>
+#include <avahi-common/malloc.h>
+#include <avahi-common/error.h>
+
+struct _RemminaAvahiPriv {
+ AvahiSimplePoll* simple_poll;
+ AvahiClient* client;
+ AvahiServiceBrowser* sb;
+ guint iterate_handler;
+ gboolean has_event;
+};
+
+static void
+remmina_avahi_resolve_callback(
+ AvahiServiceResolver* r,
+ AVAHI_GCC_UNUSED AvahiIfIndex interface,
+ AVAHI_GCC_UNUSED AvahiProtocol protocol,
+ AvahiResolverEvent event,
+ const char* name,
+ const char* type,
+ const char* domain,
+ const char* host_name,
+ const AvahiAddress* address,
+ uint16_t port,
+ AvahiStringList* txt,
+ AvahiLookupResultFlags flags,
+ AVAHI_GCC_UNUSED void* userdata)
+{
+ TRACE_CALL(__func__);
+ gchar* key;
+ gchar* value;
+ RemminaAvahi* ga = (RemminaAvahi*)userdata;
+
+ assert(r);
+
+ ga->priv->has_event = TRUE;
+
+ switch (event) {
+ case AVAHI_RESOLVER_FAILURE:
+ g_print("(remmina-applet avahi-resolver) Failed to resolve service '%s' of type '%s' in domain '%s': %s\n",
+ name, type, domain, avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r))));
+ break;
+
+ case AVAHI_RESOLVER_FOUND:
+ key = g_strdup_printf("%s,%s,%s", name, type, domain);
+ if (g_hash_table_lookup(ga->discovered_services, key)) {
+ g_free(key);
+ break;
+ }
+ value = g_strdup_printf("[%s]:%i", host_name, port);
+ g_hash_table_insert(ga->discovered_services, key, value);
+ /* key and value will be freed with g_free when the has table is freed */
+
+ g_print("(remmina-applet avahi-resolver) Added service '%s'\n", value);
+
+ break;
+ }
+
+ avahi_service_resolver_free(r);
+}
+
+static void
+remmina_avahi_browse_callback(
+ AvahiServiceBrowser* b,
+ AvahiIfIndex interface,
+ AvahiProtocol protocol,
+ AvahiBrowserEvent event,
+ const char* name,
+ const char* type,
+ const char* domain,
+ AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
+ void* userdata)
+{
+ TRACE_CALL(__func__);
+ gchar* key;
+ RemminaAvahi* ga = (RemminaAvahi*)userdata;
+
+ assert(b);
+
+ ga->priv->has_event = TRUE;
+
+ switch (event) {
+ case AVAHI_BROWSER_FAILURE:
+ g_print("(remmina-applet avahi-browser) %s\n",
+ avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b))));
+ return;
+
+ case AVAHI_BROWSER_NEW:
+ key = g_strdup_printf("%s,%s,%s", name, type, domain);
+ if (g_hash_table_lookup(ga->discovered_services, key)) {
+ g_free(key);
+ break;
+ }
+ g_free(key);
+
+ g_print("(remmina-applet avahi-browser) Found service '%s' of type '%s' in domain '%s'\n", name, type, domain);
+
+ if (!(avahi_service_resolver_new(ga->priv->client, interface, protocol, name, type, domain,
+ AVAHI_PROTO_UNSPEC, 0, remmina_avahi_resolve_callback, ga))) {
+ g_print("(remmina-applet avahi-browser) Failed to resolve service '%s': %s\n",
+ name, avahi_strerror(avahi_client_errno(ga->priv->client)));
+ }
+ break;
+
+ case AVAHI_BROWSER_REMOVE:
+ g_print("(remmina-applet avahi-browser) Removed service '%s' of type '%s' in domain '%s'\n", name, type, domain);
+ key = g_strdup_printf("%s,%s,%s", name, type, domain);
+ g_hash_table_remove(ga->discovered_services, key);
+ g_free(key);
+ break;
+
+ case AVAHI_BROWSER_ALL_FOR_NOW:
+ case AVAHI_BROWSER_CACHE_EXHAUSTED:
+ break;
+ }
+}
+
+static void remmina_avahi_client_callback(AvahiClient* c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata)
+{
+ TRACE_CALL(__func__);
+ RemminaAvahi* ga = (RemminaAvahi*)userdata;
+
+ ga->priv->has_event = TRUE;
+
+ if (state == AVAHI_CLIENT_FAILURE) {
+ g_print("(remmina-applet avahi) Server connection failure: %s\n", avahi_strerror(avahi_client_errno(c)));
+ }
+}
+
+static gboolean remmina_avahi_iterate(RemminaAvahi* ga)
+{
+ TRACE_CALL(__func__);
+ while (TRUE) {
+ /* Call the iteration until no further events */
+ ga->priv->has_event = FALSE;
+ avahi_simple_poll_iterate(ga->priv->simple_poll, 0);
+ if (!ga->priv->has_event)
+ break;
+ }
+
+ return TRUE;
+}
+
+RemminaAvahi* remmina_avahi_new(void)
+{
+ TRACE_CALL(__func__);
+ RemminaAvahi* ga;
+
+ ga = g_new(RemminaAvahi, 1);
+ ga->discovered_services = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+ ga->started = FALSE;
+ ga->priv = g_new(RemminaAvahiPriv, 1);
+ ga->priv->simple_poll = NULL;
+ ga->priv->client = NULL;
+ ga->priv->sb = NULL;
+ ga->priv->iterate_handler = 0;
+ ga->priv->has_event = FALSE;
+
+ return ga;
+}
+
+void remmina_avahi_start(RemminaAvahi* ga)
+{
+ TRACE_CALL(__func__);
+ int error;
+
+ if (ga->started)
+ return;
+
+ ga->started = TRUE;
+
+ ga->priv->simple_poll = avahi_simple_poll_new();
+ if (!ga->priv->simple_poll) {
+ g_print("Failed to create simple poll object.\n");
+ return;
+ }
+
+ ga->priv->client = avahi_client_new(avahi_simple_poll_get(ga->priv->simple_poll), 0, remmina_avahi_client_callback, ga,
+ &error);
+ if (!ga->priv->client) {
+ g_print("Failed to create client: %s\n", avahi_strerror(error));
+ return;
+ }
+
+ /** @todo Customize the default domain here */
+ ga->priv->sb = avahi_service_browser_new(ga->priv->client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, "_rfb._tcp", NULL, 0,
+ remmina_avahi_browse_callback, ga);
+ if (!ga->priv->sb) {
+ g_print("Failed to create service browser: %s\n", avahi_strerror(avahi_client_errno(ga->priv->client)));
+ return;
+ }
+
+ ga->priv->iterate_handler = g_timeout_add(5000, (GSourceFunc)remmina_avahi_iterate, ga);
+}
+
+void remmina_avahi_stop(RemminaAvahi* ga)
+{
+ TRACE_CALL(__func__);
+ g_hash_table_remove_all(ga->discovered_services);
+ if (ga->priv->iterate_handler) {
+ g_source_remove(ga->priv->iterate_handler);
+ ga->priv->iterate_handler = 0;
+ }
+ if (ga->priv->sb) {
+ avahi_service_browser_free(ga->priv->sb);
+ ga->priv->sb = NULL;
+ }
+ if (ga->priv->client) {
+ avahi_client_free(ga->priv->client);
+ ga->priv->client = NULL;
+ }
+ if (ga->priv->simple_poll) {
+ avahi_simple_poll_free(ga->priv->simple_poll);
+ ga->priv->simple_poll = NULL;
+ }
+ ga->started = FALSE;
+}
+
+void remmina_avahi_free(RemminaAvahi* ga)
+{
+ TRACE_CALL(__func__);
+ if (ga == NULL)
+ return;
+
+ remmina_avahi_stop(ga);
+
+ g_free(ga->priv);
+ g_hash_table_destroy(ga->discovered_services);
+ g_free(ga);
+}
+
+#else
+
+RemminaAvahi* remmina_avahi_new(void)
+{
+ TRACE_CALL(__func__);
+ return NULL;
+}
+
+void remmina_avahi_start(RemminaAvahi* ga)
+{
+ TRACE_CALL(__func__);
+}
+
+void remmina_avahi_stop(RemminaAvahi* ga)
+{
+ TRACE_CALL(__func__);
+}
+
+void remmina_avahi_free(RemminaAvahi* ga)
+{
+ TRACE_CALL(__func__);
+}
+
+#endif
+
diff --git a/src/remmina_avahi.h b/src/remmina_avahi.h
new file mode 100644
index 000000000..d0988004a
--- /dev/null
+++ b/src/remmina_avahi.h
@@ -0,0 +1,56 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2010 Vic Lee
+ * Copyright (C) 2017-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.
+ *
+ */
+
+#pragma once
+
+G_BEGIN_DECLS
+
+typedef struct _RemminaAvahiPriv RemminaAvahiPriv;
+
+typedef struct _RemminaAvahi {
+ GHashTable *discovered_services;
+ gboolean started;
+
+ RemminaAvahiPriv *priv;
+} RemminaAvahi;
+
+RemminaAvahi* remmina_avahi_new(void);
+void remmina_avahi_start(RemminaAvahi* ga);
+void remmina_avahi_stop(RemminaAvahi* ga);
+void remmina_avahi_free(RemminaAvahi* ga);
+
+G_END_DECLS
+
+
diff --git a/src/remmina_chat_window.c b/src/remmina_chat_window.c
new file mode 100644
index 000000000..212929603
--- /dev/null
+++ b/src/remmina_chat_window.c
@@ -0,0 +1,256 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009 - Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
+#include "remmina_chat_window.h"
+#include "remmina/remmina_trace_calls.h"
+
+G_DEFINE_TYPE( RemminaChatWindow, remmina_chat_window, GTK_TYPE_WINDOW)
+
+enum {
+ SEND_SIGNAL,
+ LAST_SIGNAL
+};
+
+static guint remmina_chat_window_signals[LAST_SIGNAL] = { 0 };
+
+static void remmina_chat_window_class_init(RemminaChatWindowClass* klass)
+{
+ TRACE_CALL(__func__);
+ remmina_chat_window_signals[SEND_SIGNAL] = g_signal_new("send", G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaChatWindowClass, send), NULL, NULL,
+ g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING);
+}
+
+static void remmina_chat_window_init(RemminaChatWindow* window)
+{
+ TRACE_CALL(__func__);
+ window->history_text = NULL;
+ window->send_text = NULL;
+}
+
+static void remmina_chat_window_clear_send_text(GtkWidget* widget, RemminaChatWindow* window)
+{
+ TRACE_CALL(__func__);
+ GtkTextBuffer* buffer;
+
+ buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(window->send_text));
+ gtk_text_buffer_set_text(buffer, "", -1);
+ gtk_widget_grab_focus(window->send_text);
+}
+
+static gboolean remmina_chat_window_scroll_proc(RemminaChatWindow* window)
+{
+ TRACE_CALL(__func__);
+ GtkTextBuffer* buffer;
+ GtkTextIter iter;
+
+ buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(window->history_text));
+ gtk_text_buffer_get_end_iter(buffer, &iter);
+ gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(window->history_text), &iter, 0.0, FALSE, 0.0, 0.0);
+
+ return FALSE;
+}
+
+static void remmina_chat_window_append_text(RemminaChatWindow* window, const gchar* name, const gchar* tagname,
+ const gchar* text)
+{
+ TRACE_CALL(__func__);
+ GtkTextBuffer* buffer;
+ GtkTextIter iter;
+ gchar* ptr;
+
+ buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(window->history_text));
+
+ if (name) {
+ ptr = g_strdup_printf("(%s) ", name);
+ gtk_text_buffer_get_end_iter(buffer, &iter);
+ if (tagname) {
+ gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, ptr, -1, tagname, NULL);
+ }else {
+ gtk_text_buffer_insert(buffer, &iter, ptr, -1);
+ }
+ g_free(ptr);
+ }
+
+ if (text && text[0] != 0) {
+ gtk_text_buffer_get_end_iter(buffer, &iter);
+ if (text[strlen(text) - 1] == '\n') {
+ gtk_text_buffer_insert(buffer, &iter, text, -1);
+ }else {
+ ptr = g_strdup_printf("%s\n", text);
+ gtk_text_buffer_insert(buffer, &iter, ptr, -1);
+ g_free(ptr);
+ }
+ }
+
+ /* Use g_idle_add to make the scroll happen after the text has been actually updated */
+ g_idle_add((GSourceFunc)remmina_chat_window_scroll_proc, window);
+}
+
+static void remmina_chat_window_send(GtkWidget* widget, RemminaChatWindow* window)
+{
+ TRACE_CALL(__func__);
+ GtkTextBuffer* buffer;
+ GtkTextIter start, end;
+ gchar* text;
+
+ buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(window->send_text));
+ gtk_text_buffer_get_bounds(buffer, &start, &end);
+ text = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
+
+ if (!text || text[0] == '\0')
+ return;
+
+ g_signal_emit_by_name(G_OBJECT(window), "send", text);
+
+ remmina_chat_window_append_text(window, g_get_user_name(), "sender-foreground", text);
+
+ g_free(text);
+
+ remmina_chat_window_clear_send_text(widget, window);
+}
+
+static gboolean remmina_chat_window_send_text_on_key(GtkWidget* widget, GdkEventKey* event, RemminaChatWindow* window)
+{
+ TRACE_CALL(__func__);
+ if (event->keyval == GDK_KEY_Return) {
+ remmina_chat_window_send(widget, window);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+GtkWidget*
+remmina_chat_window_new(GtkWindow* parent, const gchar* chat_with)
+{
+ TRACE_CALL(__func__);
+ RemminaChatWindow* window;
+ gchar buf[100];
+ GtkWidget* grid;
+ GtkWidget* scrolledwindow;
+ GtkWidget* widget;
+ GtkWidget* image;
+ GtkTextBuffer* buffer;
+
+ window = REMMINA_CHAT_WINDOW(g_object_new(REMMINA_TYPE_CHAT_WINDOW, NULL));
+
+ if (parent) {
+ gtk_window_set_transient_for(GTK_WINDOW(window), parent);
+ }
+
+ /* Title */
+ g_snprintf(buf, sizeof(buf), _("Chat with %s"), chat_with);
+ gtk_window_set_title(GTK_WINDOW(window), buf);
+ gtk_window_set_default_size(GTK_WINDOW(window), 450, 300);
+
+ /* Main container */
+ grid = gtk_grid_new();
+ gtk_widget_show(grid);
+ gtk_grid_set_row_spacing(GTK_GRID(grid), 4);
+ gtk_grid_set_column_spacing(GTK_GRID(grid), 4);
+ gtk_container_set_border_width(GTK_CONTAINER(grid), 8);
+ gtk_container_add(GTK_CONTAINER(window), grid);
+
+ /* Chat history */
+ scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
+ gtk_widget_show(scrolledwindow);
+ gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(scrolledwindow), 100);
+ gtk_widget_set_hexpand(GTK_WIDGET(scrolledwindow), TRUE);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwindow), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
+ gtk_grid_attach(GTK_GRID(grid), scrolledwindow, 0, 0, 3, 1);
+
+ widget = gtk_text_view_new();
+ gtk_widget_show(widget);
+ gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(widget), GTK_WRAP_WORD_CHAR);
+ gtk_text_view_set_editable(GTK_TEXT_VIEW(widget), FALSE);
+ gtk_container_add(GTK_CONTAINER(scrolledwindow), widget);
+
+ buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
+ gtk_text_buffer_create_tag(buffer, "sender-foreground", "foreground", "blue", NULL);
+ gtk_text_buffer_create_tag(buffer, "receiver-foreground", "foreground", "red", NULL);
+
+ window->history_text = widget;
+
+ /* Chat message to be sent */
+ scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
+ gtk_widget_show(scrolledwindow);
+ gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(scrolledwindow), 100);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwindow), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
+ gtk_widget_set_hexpand(GTK_WIDGET(scrolledwindow), TRUE);
+ gtk_grid_attach(GTK_GRID(grid), scrolledwindow, 0, 1, 3, 1);
+
+ widget = gtk_text_view_new();
+ gtk_widget_show(widget);
+ gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(widget), GTK_WRAP_WORD_CHAR);
+ gtk_container_add(GTK_CONTAINER(scrolledwindow), widget);
+ g_signal_connect(G_OBJECT(widget), "key-press-event", G_CALLBACK(remmina_chat_window_send_text_on_key), window);
+
+ window->send_text = widget;
+
+ /* Send button */
+ image = gtk_image_new_from_icon_name("document-send", GTK_ICON_SIZE_BUTTON);
+ gtk_widget_show(image);
+
+ widget = gtk_button_new_with_mnemonic(_("_Send"));
+ gtk_widget_show(widget);
+ gtk_button_set_image(GTK_BUTTON(widget), image);
+ gtk_grid_attach(GTK_GRID(grid), widget, 2, 2, 1, 1);
+ g_signal_connect(G_OBJECT(widget), "clicked", G_CALLBACK(remmina_chat_window_send), window);
+
+ /* Clear button */
+ image = gtk_image_new_from_icon_name("edit-clear", GTK_ICON_SIZE_BUTTON);
+ gtk_widget_show(image);
+
+ widget = gtk_button_new_with_mnemonic(_("_Clear"));
+ gtk_widget_show(widget);
+ gtk_button_set_image(GTK_BUTTON(widget), image);
+ gtk_grid_attach(GTK_GRID(grid), widget, 1, 2, 1, 1);
+ g_signal_connect(G_OBJECT(widget), "clicked", G_CALLBACK(remmina_chat_window_clear_send_text), window);
+
+ gtk_widget_grab_focus(window->send_text);
+
+ return GTK_WIDGET(window);
+}
+
+void remmina_chat_window_receive(RemminaChatWindow* window, const gchar* name, const gchar* text)
+{
+ TRACE_CALL(__func__);
+ remmina_chat_window_append_text(window, name, "receiver-foreground", text);
+}
+
diff --git a/src/remmina_chat_window.h b/src/remmina_chat_window.h
new file mode 100644
index 000000000..461b330a5
--- /dev/null
+++ b/src/remmina_chat_window.h
@@ -0,0 +1,68 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009 - Vic Lee
+ * Copyright (C) 2017-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.
+ *
+ */
+
+#pragma once
+
+G_BEGIN_DECLS
+
+#define REMMINA_TYPE_CHAT_WINDOW (remmina_chat_window_get_type())
+#define REMMINA_CHAT_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), REMMINA_TYPE_CHAT_WINDOW, RemminaChatWindow))
+#define REMMINA_CHAT_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), REMMINA_TYPE_CHAT_WINDOW, RemminaChatWindowClass))
+#define REMMINA_IS_CHAT_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), REMMINA_TYPE_CHAT_WINDOW))
+#define REMMINA_IS_CHAT_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), REMMINA_TYPE_CHAT_WINDOW))
+#define REMMINA_CHAT_WINDOW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), REMMINA_TYPE_CHAT_WINDOW, RemminaChatWindowClass))
+
+typedef struct _RemminaChatWindow {
+ GtkWindow window;
+
+ GtkWidget *history_text;
+ GtkWidget *send_text;
+} RemminaChatWindow;
+
+typedef struct _RemminaChatWindowClass {
+ GtkWindowClass parent_class;
+
+ void (*send)(RemminaChatWindow *window);
+} RemminaChatWindowClass;
+
+GType remmina_chat_window_get_type(void)
+G_GNUC_CONST;
+
+GtkWidget* remmina_chat_window_new(GtkWindow *parent, const gchar *chat_with);
+void remmina_chat_window_receive(RemminaChatWindow *window, const gchar *name, const gchar *text);
+
+G_END_DECLS
+
+
diff --git a/src/remmina_connection_window.c b/src/remmina_connection_window.c
new file mode 100644
index 000000000..40da08cdd
--- /dev/null
+++ b/src/remmina_connection_window.c
@@ -0,0 +1,3836 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2011 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 "config.h"
+
+#include <cairo/cairo-xlib.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <stdlib.h>
+
+#include "remmina_connection_window.h"
+#include "remmina_file.h"
+#include "remmina_file_manager.h"
+#include "remmina_init_dialog.h"
+#include "remmina_ext_exec.h"
+#include "remmina_plugin_manager.h"
+#include "remmina_pref.h"
+#include "remmina_protocol_widget.h"
+#include "remmina_public.h"
+#include "remmina_scrolled_viewport.h"
+#include "remmina_widget_pool.h"
+#include "remmina_log.h"
+#include "remmina/remmina_trace_calls.h"
+
+#define DEBUG_KB_GRABBING 0
+#include "remmina_exec.h"
+
+gchar *remmina_pref_file;
+RemminaPref remmina_pref;
+
+G_DEFINE_TYPE( RemminaConnectionWindow, remmina_connection_window, GTK_TYPE_WINDOW)
+
+#define MOTION_TIME 100
+
+/* default timeout used to hide the floating toolbar wen switching profile */
+#define TB_HIDE_TIME_TIME 1000
+
+#define FLOATING_TOOLBAR_WIDGET (GTK_CHECK_VERSION(3, 10, 0))
+
+typedef struct _RemminaConnectionHolder RemminaConnectionHolder;
+
+
+struct _RemminaConnectionWindowPriv {
+ RemminaConnectionHolder* cnnhld;
+
+ GtkWidget* notebook;
+
+ guint switch_page_handler;
+
+#if FLOATING_TOOLBAR_WIDGET
+ GtkWidget* floating_toolbar_widget;
+ GtkWidget* overlay;
+ GtkWidget* revealer;
+ GtkWidget* overlay_ftb_overlay;
+#else
+ GtkWidget* floating_toolbar_window;
+ gboolean floating_toolbar_motion_show;
+ gboolean floating_toolbar_motion_visible;
+#endif
+
+ GtkWidget* floating_toolbar_label;
+ gdouble floating_toolbar_opacity;
+ /* To avoid strange event-loop */
+ guint floating_toolbar_motion_handler;
+ /* Other event sources to remove when deleting the object */
+ guint ftb_hide_eventsource;
+ /* Timer to hide the toolbar */
+ guint hidetb_timer;
+
+ /* Timer to save new window state and wxh */
+ guint savestate_eventsourceid;
+
+
+ GtkWidget* toolbar;
+ GtkWidget* grid;
+
+ /* Toolitems that need to be handled */
+ GtkToolItem* toolitem_autofit;
+ GtkToolItem* toolitem_fullscreen;
+ GtkToolItem* toolitem_switch_page;
+ GtkToolItem* toolitem_dynres;
+ GtkToolItem* toolitem_scale;
+ GtkToolItem* toolitem_grab;
+ GtkToolItem* toolitem_preferences;
+ GtkToolItem* toolitem_tools;
+ GtkToolItem* toolitem_screenshot;
+ GtkWidget* fullscreen_option_button;
+ GtkWidget* fullscreen_scaler_button;
+ GtkWidget* scaler_option_button;
+
+ GtkWidget* pin_button;
+ gboolean pin_down;
+
+ gboolean sticky;
+
+ gint view_mode;
+
+ gboolean kbcaptured;
+ gboolean mouse_pointer_entered;
+
+ RemminaConnectionWindowOnDeleteConfirmMode on_delete_confirm_mode;
+
+};
+
+typedef struct _RemminaConnectionObject {
+ RemminaConnectionHolder* cnnhld;
+
+ RemminaFile* remmina_file;
+
+ /* A dummy window which will be realized as a container during initialize, before reparent to the real window */
+ GtkWidget* window;
+
+ /* Containers for RemminaProtocolWidget: RemminaProtocolWidget->aspectframe->viewport->scrolledcontainer->...->window */
+ GtkWidget* proto;
+ GtkWidget* aspectframe;
+ GtkWidget* viewport;
+
+ /* Scrolled containers */
+ GtkWidget* scrolled_container;
+
+ gboolean plugin_can_scale;
+
+ gboolean connected;
+ gboolean dynres_unlocked;
+
+ /* The time of the GTK event which called remmina_connection_window_open_from_file_full().
+ * Needed to make gtk_window_present_with_time() work under wayland */
+ guint32 open_from_file_event_time;
+
+} RemminaConnectionObject;
+
+struct _RemminaConnectionHolder {
+ RemminaConnectionWindow* cnnwin;
+ gint fullscreen_view_mode;
+
+ gboolean hostkey_activated;
+ gboolean hostkey_used;
+
+};
+
+enum {
+ TOOLBARPLACE_SIGNAL,
+ LAST_SIGNAL
+};
+
+static guint remmina_connection_window_signals[LAST_SIGNAL] =
+{ 0 };
+
+#define DECLARE_CNNOBJ \
+ if (!cnnhld || !cnnhld->cnnwin || gtk_notebook_get_current_page(GTK_NOTEBOOK(cnnhld->cnnwin->priv->notebook)) < 0) return; \
+ RemminaConnectionObject* cnnobj = (RemminaConnectionObject*)g_object_get_data( \
+ G_OBJECT(gtk_notebook_get_nth_page(GTK_NOTEBOOK(cnnhld->cnnwin->priv->notebook), \
+ gtk_notebook_get_current_page(GTK_NOTEBOOK(cnnhld->cnnwin->priv->notebook)))), "cnnobj");
+
+#define DECLARE_CNNOBJ_WITH_RETURN(r) \
+ if (!cnnhld || !cnnhld->cnnwin || gtk_notebook_get_current_page(GTK_NOTEBOOK(cnnhld->cnnwin->priv->notebook)) < 0) return r; \
+ RemminaConnectionObject* cnnobj = (RemminaConnectionObject*)g_object_get_data( \
+ G_OBJECT(gtk_notebook_get_nth_page(GTK_NOTEBOOK(cnnhld->cnnwin->priv->notebook), \
+ gtk_notebook_get_current_page(GTK_NOTEBOOK(cnnhld->cnnwin->priv->notebook)))), "cnnobj");
+
+static void remmina_connection_holder_create_scrolled(RemminaConnectionHolder* cnnhld, RemminaConnectionObject* cnnobj);
+static void remmina_connection_holder_create_fullscreen(RemminaConnectionHolder* cnnhld, RemminaConnectionObject* cnnobj,
+ gint view_mode);
+static gboolean remmina_connection_window_hostkey_func(RemminaProtocolWidget* gp, guint keyval, gboolean release,
+ RemminaConnectionHolder* cnnhld);
+
+static void remmina_connection_holder_grab_focus(GtkNotebook *notebook);
+static GtkWidget* remmina_connection_holder_create_toolbar(RemminaConnectionHolder* cnnhld, gint mode);
+static void remmina_connection_holder_place_toolbar(GtkToolbar *toolbar, GtkGrid *grid, GtkWidget *sibling, int toolbar_placement);
+
+#if FLOATING_TOOLBAR_WIDGET
+static void remmina_connection_window_ftb_drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer user_data);
+#endif
+
+
+static const GtkTargetEntry dnd_targets_ftb[] =
+{
+ {
+ (char*)"text/x-remmina-ftb",
+ GTK_TARGET_SAME_APP | GTK_TARGET_OTHER_WIDGET,
+ 0
+ },
+};
+
+static const GtkTargetEntry dnd_targets_tb[] =
+{
+ {
+ (char*)"text/x-remmina-tb",
+ GTK_TARGET_SAME_APP,
+ 0
+ },
+};
+
+static void remmina_connection_window_class_init(RemminaConnectionWindowClass* klass)
+{
+ TRACE_CALL(__func__);
+ GtkCssProvider *provider;
+
+ provider = gtk_css_provider_new();
+
+ /* It's important to remove padding, border and shadow from GtkViewport or
+ * we will never know its internal area size, because GtkViweport::viewport_get_view_allocation,
+ * which returns the internal size of the GtkViewport, is private and we cannot access it */
+
+#if GTK_CHECK_VERSION(3, 14, 0)
+ gtk_css_provider_load_from_data(provider,
+ "#remmina-cw-viewport, #remmina-cw-aspectframe {\n"
+ " padding:0;\n"
+ " border:0;\n"
+ " background-color: black;\n"
+ "}\n"
+ "GtkDrawingArea {\n"
+ " background-color: black;\n"
+ "}\n"
+ "GtkToolbar {\n"
+ " -GtkWidget-window-dragging: 0;\n"
+ "}\n"
+ "#remmina-connection-window-fullscreen {\n"
+ " background-color: black;\n"
+ "}\n"
+ "#remmina-small-button {\n"
+ " outline-offset: 0;\n"
+ " outline-width: 0;\n"
+ " padding: 0;\n"
+ " border: 0;\n"
+ "}\n"
+ "#remmina-pin-button {\n"
+ " outline-offset: 0;\n"
+ " outline-width: 0;\n"
+ " padding: 2px;\n"
+ " border: 0;\n"
+ "}\n"
+ "#remmina-scrolled-container {\n"
+ " background-color: black;\n"
+ "}\n"
+ "#remmina-scrolled-container.undershoot {\n"
+ " background: none\n"
+ "}\n"
+ "#ftbbox-upper {\n"
+ " border-style: none solid solid solid;\n"
+ " border-width: 1px;\n"
+ " border-radius: 4px;\n"
+ " border-color: #808080;\n"
+ " padding: 0px;\n"
+ " background-color: #f0f0f0;\n"
+ "}\n"
+ "#ftbbox-lower {\n"
+ " border-style: solid solid none solid;\n"
+ " border-width: 1px;\n"
+ " border-radius: 4px;\n"
+ " border-color: #808080;\n"
+ " padding: 0px;\n"
+ " background-color: #f0f0f0;\n"
+ "}\n"
+ "#ftb-handle {\n"
+ " background-color: #f0f0f0;\n"
+ "}\n"
+
+ , -1, NULL);
+
+#else
+ gtk_css_provider_load_from_data(provider,
+ "#remmina-cw-viewport, #remmina-cw-aspectframe {\n"
+ " padding:0;\n"
+ " border:0;\n"
+ " background-color: black;\n"
+ "}\n"
+ "GtkDrawingArea {\n"
+ " background-color: black;\n"
+ "}\n"
+ "GtkToolbar {\n"
+ " -GtkWidget-window-dragging: 0;\n"
+ "}\n"
+ "#remmina-connection-window-fullscreen {\n"
+ " background-color: black;\n"
+ "}\n"
+ "#remmina-small-button {\n"
+ " -GtkWidget-focus-padding: 0;\n"
+ " -GtkWidget-focus-line-width: 0;\n"
+ " padding: 0;\n"
+ " border: 0;\n"
+ "}\n"
+ "#remmina-pin-button {\n"
+ " -GtkWidget-focus-padding: 0;\n"
+ " -GtkWidget-focus-line-width: 0;\n"
+ " padding: 2px;\n"
+ " border: 0;\n"
+ "}\n"
+ "#remmina-scrolled-container {\n"
+ " background-color: black;\n"
+ "}\n"
+ "#remmina-scrolled-container.undershoot {\n"
+ " background: none\n"
+ "}\n"
+ "#ftbbox-upper {\n"
+ " border-style: none solid solid solid;\n"
+ " border-width: 1px;\n"
+ " border-radius: 4px;\n"
+ " border-color: #808080;\n"
+ " padding: 0px;\n"
+ " background-color: #f0f0f0;\n"
+ "}\n"
+ "#ftbbox-lower {\n"
+ " border-style: solid solid none solid;\n"
+ " border-width: 1px;\n"
+ " border-radius: 4px;\n"
+ " border-color: #808080;\n"
+ " padding: 0px;\n"
+ " background-color: #f0f0f0;\n"
+ "}\n"
+ "#ftb-handle {\n"
+ " background-color: #f0f0f0;\n"
+ "}\n"
+
+ , -1, NULL);
+#endif
+
+ gtk_style_context_add_provider_for_screen(gdk_screen_get_default(),
+ GTK_STYLE_PROVIDER(provider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+
+ g_object_unref(provider);
+
+ /* Define a signal used to notify all remmina_connection_windows of toolbar move */
+ remmina_connection_window_signals[TOOLBARPLACE_SIGNAL] = g_signal_new("toolbar-place", G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaConnectionWindowClass, toolbar_place), NULL, NULL,
+ g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
+
+}
+
+static RemminaScaleMode get_current_allowed_scale_mode(RemminaConnectionObject* cnnobj, gboolean *dynres_avail, gboolean *scale_avail)
+{
+ RemminaScaleMode scalemode;
+ gboolean plugin_has_dynres, plugin_can_scale;
+
+ scalemode = remmina_protocol_widget_get_current_scale_mode(REMMINA_PROTOCOL_WIDGET(cnnobj->proto));
+
+ plugin_has_dynres = remmina_protocol_widget_query_feature_by_type(REMMINA_PROTOCOL_WIDGET(cnnobj->proto),
+ REMMINA_PROTOCOL_FEATURE_TYPE_SCALE);
+
+ plugin_can_scale = remmina_protocol_widget_query_feature_by_type(REMMINA_PROTOCOL_WIDGET(cnnobj->proto),
+ REMMINA_PROTOCOL_FEATURE_TYPE_SCALE);
+
+ /* forbid scalemode REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES when not possible */
+ if ((!plugin_has_dynres) && scalemode == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES)
+ scalemode = REMMINA_PROTOCOL_WIDGET_SCALE_MODE_NONE;
+
+ /* forbid scalemode REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED when not possible */
+ if (!plugin_can_scale && scalemode == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED)
+ scalemode = REMMINA_PROTOCOL_WIDGET_SCALE_MODE_NONE;
+
+ if (scale_avail)
+ *scale_avail = plugin_can_scale;
+ if (dynres_avail)
+ *dynres_avail = (plugin_has_dynres && cnnobj->dynres_unlocked);
+
+ return scalemode;
+
+}
+
+static void remmina_connection_holder_disconnect_current_page(RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ DECLARE_CNNOBJ
+
+ /* Disconnects the connection which is currently in view in the notebook */
+
+ remmina_protocol_widget_close_connection(REMMINA_PROTOCOL_WIDGET(cnnobj->proto));
+}
+
+static void remmina_connection_holder_keyboard_ungrab(RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ GdkDisplay *display;
+#if GTK_CHECK_VERSION(3, 20, 0)
+ GdkSeat *seat;
+#else
+ GdkDeviceManager *manager;
+#endif
+ GdkDevice *keyboard = NULL;
+
+ display = gtk_widget_get_display(GTK_WIDGET(cnnhld->cnnwin));
+#if GTK_CHECK_VERSION(3, 20, 0)
+ seat = gdk_display_get_default_seat(display);
+ keyboard = gdk_seat_get_pointer(seat);
+#else
+ manager = gdk_display_get_device_manager(display);
+ keyboard = gdk_device_manager_get_client_pointer(manager);
+#endif
+
+
+ if (!cnnhld->cnnwin->priv->kbcaptured) {
+ return;
+ }
+
+ if (keyboard != NULL) {
+ if ( gdk_device_get_source(keyboard) != GDK_SOURCE_KEYBOARD ) {
+ keyboard = gdk_device_get_associated_device(keyboard);
+ }
+#if DEBUG_KB_GRABBING
+ printf("DEBUG_KB_GRABBING: --- ungrabbing\n");
+#endif
+
+#if GTK_CHECK_VERSION(3, 20, 0)
+ gdk_seat_ungrab(seat);
+#else
+ gdk_device_ungrab(keyboard, GDK_CURRENT_TIME);
+#endif
+ cnnhld->cnnwin->priv->kbcaptured = FALSE;
+
+ }
+}
+
+static void remmina_connection_holder_keyboard_grab(RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ DECLARE_CNNOBJ
+ GdkDisplay *display;
+#if GTK_CHECK_VERSION(3, 20, 0)
+ GdkSeat *seat;
+#else
+ GdkDeviceManager *manager;
+#endif
+ GdkDevice *keyboard = NULL;
+
+ if (cnnhld->cnnwin->priv->kbcaptured || !cnnhld->cnnwin->priv->mouse_pointer_entered) {
+ return;
+ }
+
+ display = gtk_widget_get_display(GTK_WIDGET(cnnhld->cnnwin));
+#if GTK_CHECK_VERSION(3, 20, 0)
+ seat = gdk_display_get_default_seat(display);
+ keyboard = gdk_seat_get_pointer(seat);
+#else
+ manager = gdk_display_get_device_manager(display);
+ keyboard = gdk_device_manager_get_client_pointer(manager);
+#endif
+
+ if (keyboard != NULL) {
+
+ if ( gdk_device_get_source(keyboard) != GDK_SOURCE_KEYBOARD) {
+ keyboard = gdk_device_get_associated_device( keyboard );
+ }
+
+ if (remmina_file_get_int(cnnobj->remmina_file, "keyboard_grab", FALSE)) {
+#if DEBUG_KB_GRABBING
+ printf("DEBUG_KB_GRABBING: +++ grabbing\n");
+#endif
+#if GTK_CHECK_VERSION(3, 20, 0)
+ if (gdk_seat_grab(seat, gtk_widget_get_window(GTK_WIDGET(cnnhld->cnnwin)),
+ GDK_SEAT_CAPABILITY_KEYBOARD, FALSE, NULL, NULL, NULL, NULL) == GDK_GRAB_SUCCESS)
+#else
+ if (gdk_device_grab(keyboard, gtk_widget_get_window(GTK_WIDGET(cnnhld->cnnwin)), GDK_OWNERSHIP_WINDOW,
+ TRUE, GDK_KEY_PRESS | GDK_KEY_RELEASE, NULL, GDK_CURRENT_TIME) == GDK_GRAB_SUCCESS)
+#endif
+ {
+ cnnhld->cnnwin->priv->kbcaptured = TRUE;
+ }
+ }else {
+ remmina_connection_holder_keyboard_ungrab(cnnhld);
+ }
+ }
+}
+
+static void remmina_connection_window_close_all_connections(RemminaConnectionWindow* cnnwin)
+{
+ RemminaConnectionWindowPriv* priv = cnnwin->priv;
+ GtkNotebook* notebook = GTK_NOTEBOOK(priv->notebook);
+ GtkWidget* w;
+ RemminaConnectionObject* cnnobj;
+ gint i, n;
+
+ if (GTK_IS_WIDGET(notebook)) {
+ n = gtk_notebook_get_n_pages(notebook);
+ for (i = n - 1; i >= 0; i--) {
+ w = gtk_notebook_get_nth_page(notebook, i);
+ cnnobj = (RemminaConnectionObject*)g_object_get_data(G_OBJECT(w), "cnnobj");
+ /* Do close the connection on this tab */
+ remmina_protocol_widget_close_connection(REMMINA_PROTOCOL_WIDGET(cnnobj->proto));
+ }
+ }
+
+}
+
+gboolean remmina_connection_window_delete(RemminaConnectionWindow* cnnwin)
+{
+ TRACE_CALL(__func__);
+ RemminaConnectionWindowPriv* priv = cnnwin->priv;
+ RemminaConnectionHolder *cnnhld = cnnwin->priv->cnnhld;
+ GtkNotebook* notebook = GTK_NOTEBOOK(priv->notebook);
+ GtkWidget* dialog;
+ gint i, n;
+
+ if (!REMMINA_IS_CONNECTION_WINDOW(cnnwin))
+ return TRUE;
+
+ if (cnnwin->priv->on_delete_confirm_mode != REMMINA_CONNECTION_WINDOW_ONDELETE_NOCONFIRM) {
+ n = gtk_notebook_get_n_pages(notebook);
+ if (n > 1) {
+ dialog = gtk_message_dialog_new(GTK_WINDOW(cnnwin), GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_YES_NO,
+ _("There are %i active connections in the current window. Are you sure to close?"), n);
+ i = gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ if (i != GTK_RESPONSE_YES)
+ return FALSE;
+ }
+ }
+ remmina_connection_window_close_all_connections(cnnwin);
+
+ /* After remmina_connection_window_close_all_connections() call, cnnwin
+ * has been already destroyed during a last page of notebook removal.
+ * So we must rely on cnnhld */
+ if (cnnhld->cnnwin != NULL && GTK_IS_WIDGET(cnnhld->cnnwin))
+ gtk_widget_destroy(GTK_WIDGET(cnnhld->cnnwin));
+ cnnhld->cnnwin = NULL;
+
+ return TRUE;
+}
+
+static gboolean remmina_connection_window_delete_event(GtkWidget* widget, GdkEvent* event, gpointer data)
+{
+ TRACE_CALL(__func__);
+ remmina_connection_window_delete(REMMINA_CONNECTION_WINDOW(widget));
+ return TRUE;
+}
+
+static void remmina_connection_window_destroy(GtkWidget* widget, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ RemminaConnectionWindowPriv* priv = REMMINA_CONNECTION_WINDOW(widget)->priv;
+
+ if (priv->kbcaptured)
+ remmina_connection_holder_keyboard_ungrab(cnnhld);
+
+ if (priv->floating_toolbar_motion_handler) {
+ g_source_remove(priv->floating_toolbar_motion_handler);
+ priv->floating_toolbar_motion_handler = 0;
+ }
+ if (priv->ftb_hide_eventsource) {
+ g_source_remove(priv->ftb_hide_eventsource);
+ priv->ftb_hide_eventsource = 0;
+ }
+ if (priv->savestate_eventsourceid) {
+ g_source_remove(priv->savestate_eventsourceid);
+ priv->savestate_eventsourceid = 0;
+ }
+
+#if FLOATING_TOOLBAR_WIDGET
+ /* There is no need to destroy priv->floating_toolbar_widget,
+ * because it's our child and will be destroyed automatically */
+#else
+ if (priv->floating_toolbar_window != NULL) {
+ gtk_widget_destroy(priv->floating_toolbar_window);
+ priv->floating_toolbar_window = NULL;
+ }
+#endif
+ /* Timer used to hide the toolbar */
+ if (priv->hidetb_timer) {
+ g_source_remove(priv->hidetb_timer);
+ priv->hidetb_timer = 0;
+ }
+ if (priv->switch_page_handler) {
+ g_source_remove(priv->switch_page_handler);
+ priv->switch_page_handler = 0;
+ }
+ g_free(priv);
+
+ if (GTK_WIDGET(cnnhld->cnnwin) == widget) {
+ cnnhld->cnnwin->priv = NULL;
+ cnnhld->cnnwin = NULL;
+ }
+
+}
+
+gboolean remmina_connection_window_notify_widget_toolbar_placement(GtkWidget *widget, gpointer data)
+{
+ TRACE_CALL(__func__);
+ GType rcwtype;
+ rcwtype = remmina_connection_window_get_type();
+ if (G_TYPE_CHECK_INSTANCE_TYPE(widget, rcwtype)) {
+ g_signal_emit_by_name(G_OBJECT(widget), "toolbar-place");
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static gboolean remmina_connection_window_tb_drag_failed(GtkWidget *widget, GdkDragContext *context,
+ GtkDragResult result, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ RemminaConnectionHolder* cnnhld;
+ RemminaConnectionWindowPriv* priv;
+
+ cnnhld = (RemminaConnectionHolder*)user_data;
+ priv = cnnhld->cnnwin->priv;
+
+ if (priv->toolbar)
+ gtk_widget_show(GTK_WIDGET(priv->toolbar));
+
+ return TRUE;
+}
+
+static gboolean remmina_connection_window_tb_drag_drop(GtkWidget *widget, GdkDragContext *context,
+ gint x, gint y, guint time, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ GtkAllocation wa;
+ gint new_toolbar_placement;
+ RemminaConnectionHolder* cnnhld;
+ RemminaConnectionWindowPriv* priv;
+
+ cnnhld = (RemminaConnectionHolder*)user_data;
+ priv = cnnhld->cnnwin->priv;
+
+ gtk_widget_get_allocation(widget, &wa);
+
+ if (wa.width * y >= wa.height * x) {
+ if (wa.width * y > wa.height * ( wa.width - x) )
+ new_toolbar_placement = TOOLBAR_PLACEMENT_BOTTOM;
+ else
+ new_toolbar_placement = TOOLBAR_PLACEMENT_LEFT;
+ }else {
+ if (wa.width * y > wa.height * ( wa.width - x) )
+ new_toolbar_placement = TOOLBAR_PLACEMENT_RIGHT;
+ else
+ new_toolbar_placement = TOOLBAR_PLACEMENT_TOP;
+ }
+
+ gtk_drag_finish(context, TRUE, TRUE, time);
+
+ if (new_toolbar_placement != remmina_pref.toolbar_placement) {
+ /* Save new position */
+ remmina_pref.toolbar_placement = new_toolbar_placement;
+ remmina_pref_save();
+
+ /* Signal all windows that the toolbar must be moved */
+ remmina_widget_pool_foreach(remmina_connection_window_notify_widget_toolbar_placement, NULL);
+
+ }
+ if (priv->toolbar)
+ gtk_widget_show(GTK_WIDGET(priv->toolbar));
+
+ return TRUE;
+
+}
+
+static void remmina_connection_window_tb_drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+
+ cairo_surface_t *surface;
+ cairo_t *cr;
+ GtkAllocation wa;
+ double dashes[] = { 10 };
+
+ gtk_widget_get_allocation(widget, &wa);
+
+ surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 16, 16);
+ cr = cairo_create(surface);
+ cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
+ cairo_set_line_width(cr, 4);
+ cairo_set_dash(cr, dashes, 1, 0 );
+ cairo_rectangle(cr, 0, 0, 16, 16);
+ cairo_stroke(cr);
+ cairo_destroy(cr);
+
+ gtk_widget_hide(widget);
+
+ gtk_drag_set_icon_surface(context, surface);
+
+}
+
+static void remmina_connection_holder_update_toolbar_opacity(RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ DECLARE_CNNOBJ
+ RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
+
+ priv->floating_toolbar_opacity = (1.0 - TOOLBAR_OPACITY_MIN) / ((gdouble)TOOLBAR_OPACITY_LEVEL)
+ * ((gdouble)(TOOLBAR_OPACITY_LEVEL - remmina_file_get_int(cnnobj->remmina_file, "toolbar_opacity", 0)))
+ + TOOLBAR_OPACITY_MIN;
+#if FLOATING_TOOLBAR_WIDGET
+ if (priv->floating_toolbar_widget) {
+ gtk_widget_set_opacity(GTK_WIDGET(priv->overlay_ftb_overlay), priv->floating_toolbar_opacity);
+ }
+#else
+ if (priv->floating_toolbar_window) {
+#if GTK_CHECK_VERSION(3, 8, 0)
+ gtk_widget_set_opacity(GTK_WIDGET(priv->floating_toolbar_window), priv->floating_toolbar_opacity);
+#else
+ gtk_window_set_opacity(GTK_WINDOW(priv->floating_toolbar_window), priv->floating_toolbar_opacity);
+#endif
+ }
+#endif
+}
+
+#if !FLOATING_TOOLBAR_WIDGET
+static gboolean remmina_connection_holder_floating_toolbar_motion(RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+
+
+ RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
+ GtkRequisition req;
+ gint x, y, t, cnnwin_x, cnnwin_y;
+
+ if (priv->floating_toolbar_window == NULL) {
+ priv->floating_toolbar_motion_handler = 0;
+ return FALSE;
+ }
+
+ gtk_widget_get_preferred_size(priv->floating_toolbar_window, &req, NULL);
+
+ gtk_window_get_position(GTK_WINDOW(priv->floating_toolbar_window), &x, &y);
+ gtk_window_get_position(GTK_WINDOW(cnnhld->cnnwin), &cnnwin_x, &cnnwin_y );
+ x -= cnnwin_x;
+ y -= cnnwin_y;
+
+ if (priv->floating_toolbar_motion_show || priv->floating_toolbar_motion_visible) {
+ if (priv->floating_toolbar_motion_show)
+ y += 2;
+ else
+ y -= 2;
+ t = (priv->pin_down ? 18 : 2) - req.height;
+ if (y > 0)
+ y = 0;
+ if (y < t)
+ y = t;
+
+ gtk_window_move(GTK_WINDOW(priv->floating_toolbar_window), x + cnnwin_x, y + cnnwin_y);
+ if (remmina_pref.fullscreen_toolbar_visibility == FLOATING_TOOLBAR_VISIBILITY_INVISIBLE && !priv->pin_down) {
+#if GTK_CHECK_VERSION(3, 8, 0)
+ gtk_widget_set_opacity(GTK_WIDGET(priv->floating_toolbar_window),
+ (gdouble)(y - t) / (gdouble)(-t) * priv->floating_toolbar_opacity);
+#else
+ gtk_window_set_opacity(GTK_WINDOW(priv->floating_toolbar_window),
+ (gdouble)(y - t) / (gdouble)(-t) * priv->floating_toolbar_opacity);
+#endif
+ }
+ if ((priv->floating_toolbar_motion_show && y >= 0) || (!priv->floating_toolbar_motion_show && y <= t)) {
+ priv->floating_toolbar_motion_handler = 0;
+ return FALSE;
+ }
+ }else {
+ gtk_window_move(GTK_WINDOW(priv->floating_toolbar_window), x + cnnwin_x, -20 - req.height + cnnwin_y);
+ priv->floating_toolbar_motion_handler = 0;
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void remmina_connection_holder_floating_toolbar_update(RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
+
+ if (priv->floating_toolbar_motion_show || priv->floating_toolbar_motion_visible) {
+ if (priv->floating_toolbar_motion_handler)
+ g_source_remove(priv->floating_toolbar_motion_handler);
+ priv->floating_toolbar_motion_handler = g_idle_add(
+ (GSourceFunc)remmina_connection_holder_floating_toolbar_motion, cnnhld);
+ }else {
+ if (priv->floating_toolbar_motion_handler == 0) {
+ priv->floating_toolbar_motion_handler = g_timeout_add(MOTION_TIME,
+ (GSourceFunc)remmina_connection_holder_floating_toolbar_motion, cnnhld);
+ }
+ }
+}
+#endif /* !FLOATING_TOOLBAR_WIDGET */
+
+#if FLOATING_TOOLBAR_WIDGET
+static gboolean remmina_connection_holder_floating_toolbar_make_invisible(gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaConnectionWindowPriv* priv = (RemminaConnectionWindowPriv*)data;
+ gtk_widget_set_opacity(GTK_WIDGET(priv->overlay_ftb_overlay), 0.0);
+ priv->ftb_hide_eventsource = 0;
+ return FALSE;
+}
+#endif
+
+static void remmina_connection_holder_floating_toolbar_show(RemminaConnectionHolder* cnnhld, gboolean show)
+{
+ TRACE_CALL(__func__);
+ RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
+
+#if FLOATING_TOOLBAR_WIDGET
+ if (priv->floating_toolbar_widget == NULL)
+ return;
+
+ if (show || priv->pin_down) {
+ /* Make the FTB no longer transparent, in case we have an hidden toolbar */
+ remmina_connection_holder_update_toolbar_opacity(cnnhld);
+ /* Remove outstanding hide events, if not yet active */
+ if (priv->ftb_hide_eventsource) {
+ g_source_remove(priv->ftb_hide_eventsource);
+ priv->ftb_hide_eventsource = 0;
+ }
+ }else {
+ /* If we are hiding and the toolbar must be made invisible, schedule
+ * a later toolbar hide */
+ if (remmina_pref.fullscreen_toolbar_visibility == FLOATING_TOOLBAR_VISIBILITY_INVISIBLE) {
+ if (priv->ftb_hide_eventsource == 0)
+ priv->ftb_hide_eventsource = g_timeout_add(1000, remmina_connection_holder_floating_toolbar_make_invisible, priv);
+ }
+ }
+
+ gtk_revealer_set_reveal_child(GTK_REVEALER(priv->revealer), show || priv->pin_down);
+#else
+
+ if (priv->floating_toolbar_window == NULL)
+ return;
+
+ priv->floating_toolbar_motion_show = show;
+
+ remmina_connection_holder_floating_toolbar_update(cnnhld);
+#endif
+}
+
+static void remmina_connection_holder_floating_toolbar_visible(RemminaConnectionHolder* cnnhld, gboolean visible)
+{
+ TRACE_CALL(__func__);
+#if !FLOATING_TOOLBAR_WIDGET
+ RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
+
+ if (priv->floating_toolbar_window == NULL)
+ return;
+
+ priv->floating_toolbar_motion_visible = visible;
+
+ remmina_connection_holder_floating_toolbar_update(cnnhld);
+#endif
+}
+
+static void remmina_connection_holder_get_desktop_size(RemminaConnectionHolder* cnnhld, gint* width, gint* height)
+{
+ TRACE_CALL(__func__);
+ DECLARE_CNNOBJ
+ RemminaProtocolWidget* gp = REMMINA_PROTOCOL_WIDGET(cnnobj->proto);
+
+
+ *width = remmina_protocol_widget_get_width(gp);
+ *height = remmina_protocol_widget_get_height(gp);
+}
+
+static void remmina_connection_object_set_scrolled_policy(RemminaConnectionObject* cnnobj, GtkScrolledWindow* scrolled_window)
+{
+ TRACE_CALL(__func__);
+ RemminaScaleMode scalemode;
+ scalemode = get_current_allowed_scale_mode(cnnobj, NULL, NULL);
+ gtk_scrolled_window_set_policy(scrolled_window,
+ scalemode == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED ? GTK_POLICY_NEVER : GTK_POLICY_AUTOMATIC,
+ scalemode == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED ? GTK_POLICY_NEVER : GTK_POLICY_AUTOMATIC);
+}
+
+static gboolean remmina_connection_holder_toolbar_autofit_restore(RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ DECLARE_CNNOBJ_WITH_RETURN(FALSE)
+ RemminaConnectionWindowPriv * priv = cnnhld->cnnwin->priv;
+ gint dwidth, dheight;
+ GtkAllocation nba, ca, ta;
+
+ if (cnnobj->connected && GTK_IS_SCROLLED_WINDOW(cnnobj->scrolled_container)) {
+ remmina_connection_holder_get_desktop_size(cnnhld, &dwidth, &dheight);
+ gtk_widget_get_allocation(priv->notebook, &nba);
+ gtk_widget_get_allocation(cnnobj->scrolled_container, &ca);
+ gtk_widget_get_allocation(priv->toolbar, &ta);
+ if (remmina_pref.toolbar_placement == TOOLBAR_PLACEMENT_LEFT ||
+ remmina_pref.toolbar_placement == TOOLBAR_PLACEMENT_RIGHT) {
+ gtk_window_resize(GTK_WINDOW(cnnhld->cnnwin), MAX(1, dwidth + ta.width + nba.width - ca.width),
+ MAX(1, dheight + nba.height - ca.height));
+ }else {
+ gtk_window_resize(GTK_WINDOW(cnnhld->cnnwin), MAX(1, dwidth + nba.width - ca.width),
+ MAX(1, dheight + ta.height + nba.height - ca.height));
+ }
+ gtk_container_check_resize(GTK_CONTAINER(cnnhld->cnnwin));
+ }
+ if (GTK_IS_SCROLLED_WINDOW(cnnobj->scrolled_container)) {
+ remmina_connection_object_set_scrolled_policy(cnnobj, GTK_SCROLLED_WINDOW(cnnobj->scrolled_container));
+ }
+ return FALSE;
+}
+
+static void remmina_connection_holder_toolbar_autofit(GtkWidget* widget, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ DECLARE_CNNOBJ
+
+ if (GTK_IS_SCROLLED_WINDOW(cnnobj->scrolled_container)) {
+ if ((gdk_window_get_state(gtk_widget_get_window(GTK_WIDGET(cnnhld->cnnwin))) & GDK_WINDOW_STATE_MAXIMIZED) != 0) {
+ gtk_window_unmaximize(GTK_WINDOW(cnnhld->cnnwin));
+ }
+
+ /* It's tricky to make the toolbars disappear automatically, while keeping scrollable.
+ Please tell me if you know a better way to do this */
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(cnnobj->scrolled_container), GTK_POLICY_NEVER,
+ GTK_POLICY_NEVER);
+
+ /** @todo save returned source id in priv->something and then delete when main object is destroyed */
+ g_timeout_add(200, (GSourceFunc)remmina_connection_holder_toolbar_autofit_restore, cnnhld);
+ }
+
+}
+
+
+static void remmina_connection_holder_check_resize(RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ DECLARE_CNNOBJ
+ gboolean scroll_required = FALSE;
+
+#if GTK_CHECK_VERSION(3, 22, 0)
+ GdkDisplay* display;
+ GdkMonitor* monitor;
+#else
+ GdkScreen* screen;
+ gint monitor;
+#endif
+ GdkRectangle screen_size;
+ gint screen_width, screen_height;
+ gint server_width, server_height;
+ gint bordersz;
+
+ remmina_connection_holder_get_desktop_size(cnnhld, &server_width, &server_height);
+#if GTK_CHECK_VERSION(3, 22, 0)
+ display = gtk_widget_get_display(GTK_WIDGET(cnnhld->cnnwin));
+ monitor = gdk_display_get_monitor_at_window(display, gtk_widget_get_window(GTK_WIDGET(cnnhld->cnnwin)));
+#else
+ screen = gtk_window_get_screen(GTK_WINDOW(cnnhld->cnnwin));
+ monitor = gdk_screen_get_monitor_at_window(screen, gtk_widget_get_window(GTK_WIDGET(cnnhld->cnnwin)));
+#endif
+#if GTK_CHECK_VERSION(3, 22, 0)
+ gdk_monitor_get_workarea(monitor, &screen_size);
+#elif gdk_screen_get_monitor_workarea
+ gdk_screen_get_monitor_workarea(screen, monitor, &screen_size);
+#else
+ gdk_screen_get_monitor_geometry(screen, monitor, &screen_size);
+#endif
+ screen_width = screen_size.width;
+ screen_height = screen_size.height;
+
+ if (!remmina_protocol_widget_get_expand(REMMINA_PROTOCOL_WIDGET(cnnobj->proto))
+ && (server_width <= 0 || server_height <= 0 || screen_width < server_width
+ || screen_height < server_height)) {
+ scroll_required = TRUE;
+ }
+
+ switch (cnnhld->cnnwin->priv->view_mode) {
+ case SCROLLED_FULLSCREEN_MODE:
+ gtk_window_resize(GTK_WINDOW(cnnhld->cnnwin), screen_width, screen_height);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(cnnobj->scrolled_container),
+ (scroll_required ? GTK_POLICY_AUTOMATIC : GTK_POLICY_NEVER),
+ (scroll_required ? GTK_POLICY_AUTOMATIC : GTK_POLICY_NEVER));
+ break;
+
+ case VIEWPORT_FULLSCREEN_MODE:
+ bordersz = scroll_required ? 1 : 0;
+ gtk_window_resize(GTK_WINDOW(cnnhld->cnnwin), screen_width, screen_height);
+ if (REMMINA_IS_SCROLLED_VIEWPORT(cnnobj->scrolled_container)) {
+ /* Put a border around Notebook content (RemminaScrolledViewpord), so we can
+ * move the mouse over the border to scroll */
+ gtk_container_set_border_width(GTK_CONTAINER(cnnobj->scrolled_container), bordersz);
+ }
+
+ break;
+
+ case SCROLLED_WINDOW_MODE:
+ if (remmina_file_get_int(cnnobj->remmina_file, "viewmode", AUTO_MODE) == AUTO_MODE) {
+ gtk_window_set_default_size(GTK_WINDOW(cnnhld->cnnwin),
+ MIN(server_width, screen_width), MIN(server_height, screen_height));
+ if (server_width >= screen_width || server_height >= screen_height) {
+ gtk_window_maximize(GTK_WINDOW(cnnhld->cnnwin));
+ remmina_file_set_int(cnnobj->remmina_file, "window_maximize", TRUE);
+ }else {
+ remmina_connection_holder_toolbar_autofit(NULL, cnnhld);
+ remmina_file_set_int(cnnobj->remmina_file, "window_maximize", FALSE);
+ }
+ }else {
+ if (remmina_file_get_int(cnnobj->remmina_file, "window_maximize", FALSE)) {
+ gtk_window_maximize(GTK_WINDOW(cnnhld->cnnwin));
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void remmina_connection_holder_set_tooltip(GtkWidget* item, const gchar* tip, guint key1, guint key2)
+{
+ TRACE_CALL(__func__);
+ gchar* s1;
+ gchar* s2;
+
+ if (remmina_pref.hostkey && key1) {
+ if (key2) {
+ s1 = g_strdup_printf(" (%s + %s,%s)", gdk_keyval_name(remmina_pref.hostkey),
+ gdk_keyval_name(gdk_keyval_to_upper(key1)), gdk_keyval_name(gdk_keyval_to_upper(key2)));
+ }else if (key1 == remmina_pref.hostkey) {
+ s1 = g_strdup_printf(" (%s)", gdk_keyval_name(remmina_pref.hostkey));
+ }else {
+ s1 = g_strdup_printf(" (%s + %s)", gdk_keyval_name(remmina_pref.hostkey),
+ gdk_keyval_name(gdk_keyval_to_upper(key1)));
+ }
+ }else {
+ s1 = NULL;
+ }
+ s2 = g_strdup_printf("%s%s", tip, s1 ? s1 : "");
+ gtk_widget_set_tooltip_text(item, s2);
+ g_free(s2);
+ g_free(s1);
+}
+
+static void remmina_protocol_widget_update_alignment(RemminaConnectionObject* cnnobj)
+{
+ TRACE_CALL(__func__);
+ RemminaScaleMode scalemode;
+ gboolean scaledexpandedmode;
+ int rdwidth, rdheight;
+ gfloat aratio;
+
+ if (!cnnobj->plugin_can_scale) {
+ /* If we have a plugin that cannot scale,
+ * (i.e. SFTP plugin), then we expand proto */
+ gtk_widget_set_halign(GTK_WIDGET(cnnobj->proto), GTK_ALIGN_FILL);
+ gtk_widget_set_valign(GTK_WIDGET(cnnobj->proto), GTK_ALIGN_FILL);
+ }else {
+ /* Plugin can scale */
+
+ scalemode = get_current_allowed_scale_mode(cnnobj, NULL, NULL);
+ scaledexpandedmode = remmina_protocol_widget_get_expand(REMMINA_PROTOCOL_WIDGET(cnnobj->proto));
+
+ /* Check if we need aspectframe and create/destroy it accordingly */
+ if (scalemode == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED && !scaledexpandedmode) {
+ /* We need an aspectframe as a parent of proto */
+ rdwidth = remmina_protocol_widget_get_width(REMMINA_PROTOCOL_WIDGET(cnnobj->proto));
+ rdheight = remmina_protocol_widget_get_height(REMMINA_PROTOCOL_WIDGET(cnnobj->proto));
+ aratio = (gfloat)rdwidth / (gfloat)rdheight;
+ if (!cnnobj->aspectframe) {
+ /* We need a new aspectframe */
+ cnnobj->aspectframe = gtk_aspect_frame_new(NULL, 0.5, 0.5, aratio, FALSE);
+ gtk_widget_set_name(cnnobj->aspectframe, "remmina-cw-aspectframe");
+ gtk_frame_set_shadow_type(GTK_FRAME(cnnobj->aspectframe), GTK_SHADOW_NONE);
+ g_object_ref(cnnobj->proto);
+ gtk_container_remove(GTK_CONTAINER(cnnobj->viewport), cnnobj->proto);
+ gtk_container_add(GTK_CONTAINER(cnnobj->viewport), cnnobj->aspectframe);
+ gtk_container_add(GTK_CONTAINER(cnnobj->aspectframe), cnnobj->proto);
+ g_object_unref(cnnobj->proto);
+ gtk_widget_show(cnnobj->aspectframe);
+ if (cnnobj->cnnhld != NULL && cnnobj->cnnhld->cnnwin != NULL && cnnobj->cnnhld->cnnwin->priv->notebook != NULL)
+ remmina_connection_holder_grab_focus(GTK_NOTEBOOK(cnnobj->cnnhld->cnnwin->priv->notebook));
+ }else {
+ gtk_aspect_frame_set(GTK_ASPECT_FRAME(cnnobj->aspectframe), 0.5, 0.5, aratio, FALSE);
+ }
+ }else {
+ /* We do not need an aspectframe as a parent of proto */
+ if (cnnobj->aspectframe) {
+ /* We must remove the old aspectframe reparenting proto to viewport */
+ g_object_ref(cnnobj->aspectframe);
+ g_object_ref(cnnobj->proto);
+ gtk_container_remove(GTK_CONTAINER(cnnobj->aspectframe), cnnobj->proto);
+ gtk_container_remove(GTK_CONTAINER(cnnobj->viewport), cnnobj->aspectframe);
+ g_object_unref(cnnobj->aspectframe);
+ cnnobj->aspectframe = NULL;
+ gtk_container_add(GTK_CONTAINER(cnnobj->viewport), cnnobj->proto);
+ g_object_unref(cnnobj->proto);
+ if (cnnobj->cnnhld != NULL && cnnobj->cnnhld->cnnwin != NULL && cnnobj->cnnhld->cnnwin->priv->notebook != NULL)
+ remmina_connection_holder_grab_focus(GTK_NOTEBOOK(cnnobj->cnnhld->cnnwin->priv->notebook));
+ }
+ }
+
+ if (scalemode == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED || scalemode == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES) {
+ /* We have a plugin that can be scaled, and the scale button
+ * has been pressed. Give it the correct WxH maintaining aspect
+ * ratio of remote destkop size */
+ gtk_widget_set_halign(GTK_WIDGET(cnnobj->proto), GTK_ALIGN_FILL);
+ gtk_widget_set_valign(GTK_WIDGET(cnnobj->proto), GTK_ALIGN_FILL);
+ }else {
+ /* Plugin can scale, but no scaling is active. Ensure that we have
+ * aspectframe with a ratio of 1 */
+ gtk_widget_set_halign(GTK_WIDGET(cnnobj->proto), GTK_ALIGN_CENTER);
+ gtk_widget_set_valign(GTK_WIDGET(cnnobj->proto), GTK_ALIGN_CENTER);
+ }
+ }
+}
+
+
+static void remmina_connection_holder_toolbar_fullscreen(GtkWidget* widget, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ if (gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(widget))) {
+ remmina_connection_holder_create_fullscreen(cnnhld, NULL, cnnhld->fullscreen_view_mode);
+ }else {
+ remmina_connection_holder_create_scrolled(cnnhld, NULL);
+ }
+}
+
+static void remmina_connection_holder_viewport_fullscreen_mode(GtkWidget* widget, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
+ return;
+ cnnhld->fullscreen_view_mode = VIEWPORT_FULLSCREEN_MODE;
+ remmina_connection_holder_create_fullscreen(cnnhld, NULL, cnnhld->fullscreen_view_mode);
+}
+
+static void remmina_connection_holder_scrolled_fullscreen_mode(GtkWidget* widget, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
+ return;
+ cnnhld->fullscreen_view_mode = SCROLLED_FULLSCREEN_MODE;
+ remmina_connection_holder_create_fullscreen(cnnhld, NULL, cnnhld->fullscreen_view_mode);
+}
+
+static void remmina_connection_holder_fullscreen_option_popdown(GtkWidget* widget, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
+
+ priv->sticky = FALSE;
+
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->fullscreen_option_button), FALSE);
+ remmina_connection_holder_floating_toolbar_show(cnnhld, FALSE);
+}
+
+static void remmina_connection_holder_toolbar_fullscreen_option(GtkWidget* widget, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
+ GtkWidget* menu;
+ GtkWidget* menuitem;
+ GSList* group;
+
+ if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
+ return;
+
+ priv->sticky = TRUE;
+
+ menu = gtk_menu_new();
+
+ menuitem = gtk_radio_menu_item_new_with_label(NULL, _("Viewport fullscreen mode"));
+ gtk_widget_show(menuitem);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+ group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(menuitem));
+ if (priv->view_mode == VIEWPORT_FULLSCREEN_MODE) {
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
+ }
+ g_signal_connect(G_OBJECT(menuitem), "toggled", G_CALLBACK(remmina_connection_holder_viewport_fullscreen_mode), cnnhld);
+
+ menuitem = gtk_radio_menu_item_new_with_label(group, _("Scrolled fullscreen mode"));
+ gtk_widget_show(menuitem);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+ if (priv->view_mode == SCROLLED_FULLSCREEN_MODE) {
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
+ }
+ g_signal_connect(G_OBJECT(menuitem), "toggled", G_CALLBACK(remmina_connection_holder_scrolled_fullscreen_mode), cnnhld);
+
+ g_signal_connect(G_OBJECT(menu), "deactivate", G_CALLBACK(remmina_connection_holder_fullscreen_option_popdown), cnnhld);
+
+#if GTK_CHECK_VERSION(3, 22, 0)
+ gtk_menu_popup_at_widget(GTK_MENU(menu), widget,
+ GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, NULL);
+#else
+ gtk_menu_popup(GTK_MENU(menu), NULL, NULL, remmina_public_popup_position, priv->toolitem_fullscreen, 0,
+ gtk_get_current_event_time());
+#endif
+}
+
+
+static void remmina_connection_holder_scaler_option_popdown(GtkWidget* widget, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
+ priv->sticky = FALSE;
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->scaler_option_button), FALSE);
+ remmina_connection_holder_floating_toolbar_show(cnnhld, FALSE);
+}
+
+static void remmina_connection_holder_scaler_expand(GtkWidget* widget, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ DECLARE_CNNOBJ
+ if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
+ return;
+ remmina_protocol_widget_set_expand(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), TRUE);
+ remmina_file_set_int(cnnobj->remmina_file, "scaler_expand", TRUE);
+ remmina_protocol_widget_update_alignment(cnnobj);
+}
+static void remmina_connection_holder_scaler_keep_aspect(GtkWidget* widget, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ DECLARE_CNNOBJ
+ if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
+ return;
+ remmina_protocol_widget_set_expand(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), FALSE);
+ remmina_file_set_int(cnnobj->remmina_file, "scaler_expand", FALSE);
+ remmina_protocol_widget_update_alignment(cnnobj);
+}
+
+static void remmina_connection_holder_toolbar_scaler_option(GtkWidget* widget, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ DECLARE_CNNOBJ
+ RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
+ GtkWidget* menu;
+ GtkWidget* menuitem;
+ GSList* group;
+ gboolean scaler_expand;
+
+ if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
+ return;
+
+ scaler_expand = remmina_protocol_widget_get_expand(REMMINA_PROTOCOL_WIDGET(cnnobj->proto));
+
+ priv->sticky = TRUE;
+
+ menu = gtk_menu_new();
+
+ menuitem = gtk_radio_menu_item_new_with_label(NULL, _("Keep aspect ratio when scaled"));
+ gtk_widget_show(menuitem);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+ group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(menuitem));
+ if (!scaler_expand) {
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
+ }
+ g_signal_connect(G_OBJECT(menuitem), "toggled", G_CALLBACK(remmina_connection_holder_scaler_keep_aspect), cnnhld);
+
+ menuitem = gtk_radio_menu_item_new_with_label(group, _("Fill client window when scaled"));
+ gtk_widget_show(menuitem);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+ if (scaler_expand) {
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
+ }
+ g_signal_connect(G_OBJECT(menuitem), "toggled", G_CALLBACK(remmina_connection_holder_scaler_expand), cnnhld);
+
+ g_signal_connect(G_OBJECT(menu), "deactivate", G_CALLBACK(remmina_connection_holder_scaler_option_popdown), cnnhld);
+
+#if GTK_CHECK_VERSION(3, 22, 0)
+ gtk_menu_popup_at_widget(GTK_MENU(menu), widget,
+ GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, NULL);
+#else
+ gtk_menu_popup(GTK_MENU(menu), NULL, NULL, remmina_public_popup_position, priv->toolitem_scale, 0,
+ gtk_get_current_event_time());
+#endif
+}
+
+static void remmina_connection_holder_switch_page_activate(GtkMenuItem* menuitem, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
+ gint page_num;
+
+ page_num = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menuitem), "new-page-num"));
+ gtk_notebook_set_current_page(GTK_NOTEBOOK(priv->notebook), page_num);
+}
+
+static void remmina_connection_holder_toolbar_switch_page_popdown(GtkWidget* widget, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
+
+ priv->sticky = FALSE;
+
+ gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_switch_page), FALSE);
+ remmina_connection_holder_floating_toolbar_show(cnnhld, FALSE);
+}
+
+static void remmina_connection_holder_toolbar_switch_page(GtkWidget* widget, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ RemminaConnectionObject* cnnobj;
+ RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
+ GtkWidget* menu;
+ GtkWidget* menuitem;
+ GtkWidget* image;
+ GtkWidget* page;
+ gint i, n;
+
+ if (!gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(widget)))
+ return;
+
+ priv->sticky = TRUE;
+
+ menu = gtk_menu_new();
+
+ n = gtk_notebook_get_n_pages(GTK_NOTEBOOK(priv->notebook));
+ for (i = 0; i < n; i++) {
+ page = gtk_notebook_get_nth_page(GTK_NOTEBOOK(priv->notebook), i);
+ if (!page)
+ break;
+ cnnobj = (RemminaConnectionObject*)g_object_get_data(G_OBJECT(page), "cnnobj");
+
+ menuitem = gtk_menu_item_new_with_label(remmina_file_get_string(cnnobj->remmina_file, "name"));
+ gtk_widget_show(menuitem);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+
+ image = gtk_image_new_from_icon_name(remmina_file_get_icon_name(cnnobj->remmina_file), GTK_ICON_SIZE_MENU);
+ gtk_widget_show(image);
+
+ g_object_set_data(G_OBJECT(menuitem), "new-page-num", GINT_TO_POINTER(i));
+ g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(remmina_connection_holder_switch_page_activate),
+ cnnhld);
+ if (i == gtk_notebook_get_current_page(GTK_NOTEBOOK(priv->notebook))) {
+ gtk_widget_set_sensitive(menuitem, FALSE);
+ }
+ }
+
+ g_signal_connect(G_OBJECT(menu), "deactivate", G_CALLBACK(remmina_connection_holder_toolbar_switch_page_popdown),
+ cnnhld);
+
+#if GTK_CHECK_VERSION(3, 22, 0)
+ gtk_menu_popup_at_widget(GTK_MENU(menu), widget,
+ GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, NULL);
+#else
+ gtk_menu_popup(GTK_MENU(menu), NULL, NULL, remmina_public_popup_position, widget, 0, gtk_get_current_event_time());
+#endif
+
+}
+
+static void remmina_connection_holder_update_toolbar_autofit_button(RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ DECLARE_CNNOBJ
+ RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
+ GtkToolItem* toolitem;
+ RemminaScaleMode sc;
+
+ toolitem = priv->toolitem_autofit;
+ if (toolitem) {
+ if (priv->view_mode != SCROLLED_WINDOW_MODE) {
+ gtk_widget_set_sensitive(GTK_WIDGET(toolitem), FALSE);
+ }else {
+ sc = get_current_allowed_scale_mode(cnnobj, NULL, NULL);
+ gtk_widget_set_sensitive(GTK_WIDGET(toolitem), sc == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_NONE);
+ }
+ }
+}
+
+static void remmina_connection_holder_change_scalemode(RemminaConnectionHolder* cnnhld, gboolean bdyn, gboolean bscale)
+{
+ RemminaScaleMode scalemode;
+ DECLARE_CNNOBJ
+ RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
+
+ if (bdyn)
+ scalemode = REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES;
+ else if (bscale)
+ scalemode = REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED;
+ else
+ scalemode = REMMINA_PROTOCOL_WIDGET_SCALE_MODE_NONE;
+
+ if (scalemode != REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES) {
+ remmina_file_set_int(cnnobj->remmina_file, "dynamic_resolution_width", 0);
+ remmina_file_set_int(cnnobj->remmina_file, "dynamic_resolution_height", 0);
+ }
+
+ remmina_protocol_widget_set_current_scale_mode(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), scalemode);
+ remmina_file_set_int(cnnobj->remmina_file, "scale", scalemode);
+ gtk_widget_set_sensitive(GTK_WIDGET(priv->scaler_option_button), scalemode == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED);
+ remmina_connection_holder_update_toolbar_autofit_button(cnnhld);
+
+ remmina_protocol_widget_call_feature_by_type(REMMINA_PROTOCOL_WIDGET(cnnobj->proto),
+ REMMINA_PROTOCOL_FEATURE_TYPE_SCALE, 0);
+
+ if (cnnhld->cnnwin->priv->view_mode != SCROLLED_WINDOW_MODE) {
+ remmina_connection_holder_check_resize(cnnhld);
+ }
+ if (GTK_IS_SCROLLED_WINDOW(cnnobj->scrolled_container)) {
+ remmina_connection_object_set_scrolled_policy(cnnobj, GTK_SCROLLED_WINDOW(cnnobj->scrolled_container));
+ }
+
+}
+
+static void remmina_connection_holder_toolbar_dynres(GtkWidget* widget, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ gboolean bdyn, bscale;
+ RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
+
+ bdyn = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(widget));
+ bscale = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_scale));
+
+ if (bdyn && bscale) {
+ gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_scale), FALSE);
+ bscale = FALSE;
+ }
+
+ remmina_connection_holder_change_scalemode(cnnhld, bdyn, bscale);
+}
+
+
+static void remmina_connection_holder_toolbar_scaled_mode(GtkWidget* widget, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ gboolean bdyn, bscale;
+ RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
+
+ bdyn = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_dynres));
+ bscale = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(widget));
+
+ if (bdyn && bscale) {
+ gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_dynres), FALSE);
+ bdyn = FALSE;
+ }
+
+ remmina_connection_holder_change_scalemode(cnnhld, bdyn, bscale);
+}
+
+static void remmina_connection_holder_toolbar_preferences_popdown(GtkWidget* widget, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
+
+ priv->sticky = FALSE;
+
+ gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_preferences), FALSE);
+ remmina_connection_holder_floating_toolbar_show(cnnhld, FALSE);
+}
+
+static void remmina_connection_holder_toolbar_tools_popdown(GtkWidget* widget, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
+
+ priv->sticky = FALSE;
+
+ gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_tools), FALSE);
+ remmina_connection_holder_floating_toolbar_show(cnnhld, FALSE);
+}
+
+static void remmina_connection_holder_call_protocol_feature_radio(GtkMenuItem* menuitem, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ DECLARE_CNNOBJ
+ RemminaProtocolFeature* feature;
+ gpointer value;
+
+ if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem))) {
+ feature = (RemminaProtocolFeature*)g_object_get_data(G_OBJECT(menuitem), "feature-type");
+ value = g_object_get_data(G_OBJECT(menuitem), "feature-value");
+
+ remmina_file_set_string(cnnobj->remmina_file, (const gchar*)feature->opt2, (const gchar*)value);
+ remmina_protocol_widget_call_feature_by_ref(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), feature);
+ }
+}
+
+static void remmina_connection_holder_call_protocol_feature_check(GtkMenuItem* menuitem, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ DECLARE_CNNOBJ
+ RemminaProtocolFeature* feature;
+ gboolean value;
+
+ feature = (RemminaProtocolFeature*)g_object_get_data(G_OBJECT(menuitem), "feature-type");
+ value = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem));
+ remmina_file_set_int(cnnobj->remmina_file, (const gchar*)feature->opt2, value);
+ remmina_protocol_widget_call_feature_by_ref(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), feature);
+}
+
+static void remmina_connection_holder_call_protocol_feature_activate(GtkMenuItem* menuitem, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ DECLARE_CNNOBJ
+ RemminaProtocolFeature* feature;
+
+ feature = (RemminaProtocolFeature*)g_object_get_data(G_OBJECT(menuitem), "feature-type");
+ remmina_protocol_widget_call_feature_by_ref(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), feature);
+}
+
+static void remmina_connection_holder_toolbar_preferences_radio(RemminaConnectionHolder* cnnhld, RemminaFile* remminafile,
+ GtkWidget* menu, const RemminaProtocolFeature* feature, const gchar* domain, gboolean enabled)
+{
+ TRACE_CALL(__func__);
+ GtkWidget* menuitem;
+ GSList* group;
+ gint i;
+ const gchar** list;
+ const gchar* value;
+
+ group = NULL;
+ value = remmina_file_get_string(remminafile, (const gchar*)feature->opt2);
+ list = (const gchar**)feature->opt3;
+ for (i = 0; list[i]; i += 2) {
+ menuitem = gtk_radio_menu_item_new_with_label(group, g_dgettext(domain, list[i + 1]));
+ group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(menuitem));
+ gtk_widget_show(menuitem);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+
+ if (enabled) {
+ g_object_set_data(G_OBJECT(menuitem), "feature-type", (gpointer)feature);
+ g_object_set_data(G_OBJECT(menuitem), "feature-value", (gpointer)list[i]);
+
+ if (value && g_strcmp0(list[i], value) == 0) {
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
+ }
+
+ g_signal_connect(G_OBJECT(menuitem), "toggled",
+ G_CALLBACK(remmina_connection_holder_call_protocol_feature_radio), cnnhld);
+ }else {
+ gtk_widget_set_sensitive(menuitem, FALSE);
+ }
+ }
+}
+
+static void remmina_connection_holder_toolbar_preferences_check(RemminaConnectionHolder* cnnhld, RemminaFile* remminafile,
+ GtkWidget* menu, const RemminaProtocolFeature* feature, const gchar* domain, gboolean enabled)
+{
+ TRACE_CALL(__func__);
+ GtkWidget* menuitem;
+
+ menuitem = gtk_check_menu_item_new_with_label(g_dgettext(domain, (const gchar*)feature->opt3));
+ gtk_widget_show(menuitem);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+
+ if (enabled) {
+ g_object_set_data(G_OBJECT(menuitem), "feature-type", (gpointer)feature);
+
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
+ remmina_file_get_int(remminafile, (const gchar*)feature->opt2, FALSE));
+
+ g_signal_connect(G_OBJECT(menuitem), "toggled",
+ G_CALLBACK(remmina_connection_holder_call_protocol_feature_check), cnnhld);
+ }else {
+ gtk_widget_set_sensitive(menuitem, FALSE);
+ }
+}
+
+static void remmina_connection_holder_toolbar_preferences(GtkWidget* widget, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ DECLARE_CNNOBJ
+ RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
+ const RemminaProtocolFeature* feature;
+ GtkWidget* menu;
+ GtkWidget* menuitem;
+ gboolean separator;
+ const gchar* domain;
+ gboolean enabled;
+
+ if (!gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(widget)))
+ return;
+
+ priv->sticky = TRUE;
+
+ separator = FALSE;
+
+ domain = remmina_protocol_widget_get_domain(REMMINA_PROTOCOL_WIDGET(cnnobj->proto));
+ menu = gtk_menu_new();
+ for (feature = remmina_protocol_widget_get_features(REMMINA_PROTOCOL_WIDGET(cnnobj->proto)); feature && feature->type;
+ feature++) {
+ if (feature->type != REMMINA_PROTOCOL_FEATURE_TYPE_PREF)
+ continue;
+
+ if (separator) {
+ menuitem = gtk_separator_menu_item_new();
+ gtk_widget_show(menuitem);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+ separator = FALSE;
+ }
+ enabled = remmina_protocol_widget_query_feature_by_ref(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), feature);
+ switch (GPOINTER_TO_INT(feature->opt1)) {
+ case REMMINA_PROTOCOL_FEATURE_PREF_RADIO:
+ remmina_connection_holder_toolbar_preferences_radio(cnnhld, cnnobj->remmina_file, menu, feature,
+ domain, enabled);
+ separator = TRUE;
+ break;
+ case REMMINA_PROTOCOL_FEATURE_PREF_CHECK:
+ remmina_connection_holder_toolbar_preferences_check(cnnhld, cnnobj->remmina_file, menu, feature,
+ domain, enabled);
+ break;
+ }
+ }
+
+ g_signal_connect(G_OBJECT(menu), "deactivate", G_CALLBACK(remmina_connection_holder_toolbar_preferences_popdown),
+ cnnhld);
+
+#if GTK_CHECK_VERSION(3, 22, 0)
+ gtk_menu_popup_at_widget(GTK_MENU(menu), widget,
+ GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, NULL);
+#else
+ gtk_menu_popup(GTK_MENU(menu), NULL, NULL, remmina_public_popup_position, widget, 0, gtk_get_current_event_time());
+#endif
+
+}
+
+static void remmina_connection_holder_toolbar_tools(GtkWidget* widget, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ DECLARE_CNNOBJ
+ RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
+ const RemminaProtocolFeature* feature;
+ GtkWidget* menu;
+ GtkWidget* menuitem = NULL;
+ GtkMenu *submenu_keystrokes;
+ const gchar* domain;
+ gboolean enabled;
+ gchar **keystrokes;
+ gchar **keystroke_values;
+ gint i;
+
+ if (!gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(widget)))
+ return;
+
+ priv->sticky = TRUE;
+
+ domain = remmina_protocol_widget_get_domain(REMMINA_PROTOCOL_WIDGET(cnnobj->proto));
+ menu = gtk_menu_new();
+ for (feature = remmina_protocol_widget_get_features(REMMINA_PROTOCOL_WIDGET(cnnobj->proto)); feature && feature->type;
+ feature++) {
+ if (feature->type != REMMINA_PROTOCOL_FEATURE_TYPE_TOOL)
+ continue;
+
+ if (feature->opt1) {
+ menuitem = gtk_menu_item_new_with_label(g_dgettext(domain, (const gchar*)feature->opt1));
+ }
+ if (feature->opt3) {
+ remmina_connection_holder_set_tooltip(menuitem, "", GPOINTER_TO_UINT(feature->opt3), 0);
+ }
+ gtk_widget_show(menuitem);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+
+ enabled = remmina_protocol_widget_query_feature_by_ref(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), feature);
+ if (enabled) {
+ g_object_set_data(G_OBJECT(menuitem), "feature-type", (gpointer)feature);
+
+ g_signal_connect(G_OBJECT(menuitem), "activate",
+ G_CALLBACK(remmina_connection_holder_call_protocol_feature_activate), cnnhld);
+ }else {
+ gtk_widget_set_sensitive(menuitem, FALSE);
+ }
+ }
+
+ g_signal_connect(G_OBJECT(menu), "deactivate", G_CALLBACK(remmina_connection_holder_toolbar_tools_popdown), cnnhld);
+
+ /* If the plugin accepts keystrokes include the keystrokes menu */
+ if (remmina_protocol_widget_plugin_receives_keystrokes(REMMINA_PROTOCOL_WIDGET(cnnobj->proto))) {
+ /* Get the registered keystrokes list */
+ keystrokes = g_strsplit(remmina_pref.keystrokes, STRING_DELIMITOR, -1);
+ if (g_strv_length(keystrokes)) {
+ /* Add a keystrokes submenu */
+ menuitem = gtk_menu_item_new_with_label(_("Keystrokes"));
+ submenu_keystrokes = GTK_MENU(gtk_menu_new());
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), GTK_WIDGET(submenu_keystrokes));
+ gtk_widget_show(menuitem);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+ /* Add each registered keystroke */
+ for (i = 0; i < g_strv_length(keystrokes); i++) {
+ keystroke_values = g_strsplit(keystrokes[i], STRING_DELIMITOR2, -1);
+ if (g_strv_length(keystroke_values) > 1) {
+ /* Add the keystroke if no description was available */
+ menuitem = gtk_menu_item_new_with_label(
+ g_strdup(keystroke_values[strlen(keystroke_values[0]) ? 0 : 1]));
+ g_object_set_data(G_OBJECT(menuitem), "keystrokes", g_strdup(keystroke_values[1]));
+ g_signal_connect_swapped(G_OBJECT(menuitem), "activate",
+ G_CALLBACK(remmina_protocol_widget_send_keystrokes),
+ REMMINA_PROTOCOL_WIDGET(cnnobj->proto));
+ gtk_widget_show(menuitem);
+ gtk_menu_shell_append(GTK_MENU_SHELL(submenu_keystrokes), menuitem);
+ }
+ g_strfreev(keystroke_values);
+ }
+ }
+ g_strfreev(keystrokes);
+ }
+#if GTK_CHECK_VERSION(3, 22, 0)
+ gtk_menu_popup_at_widget(GTK_MENU(menu), widget,
+ GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, NULL);
+#else
+ gtk_menu_popup(GTK_MENU(menu), NULL, NULL, remmina_public_popup_position, widget, 0, gtk_get_current_event_time());
+#endif
+
+}
+
+static void remmina_connection_holder_toolbar_screenshot(GtkWidget* widget, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+
+ GdkPixbuf *screenshot;
+ GdkWindow *active_window;
+ cairo_t *cr;
+ gint width, height;
+ const gchar* remminafile;
+ gchar* pngname;
+ gchar* pngdate;
+ GtkWidget* dialog;
+ RemminaProtocolWidget *gp;
+ RemminaPluginScreenshotData rpsd;
+ cairo_surface_t *srcsurface;
+ cairo_format_t cairo_format;
+ cairo_surface_t *surface;
+ int stride;
+
+ GDateTime *date = g_date_time_new_now_utc();
+
+ // We will take a screenshot of the currently displayed RemminaProtocolWidget.
+ // DECLARE_CNNOBJ already did part of the job for us.
+ DECLARE_CNNOBJ
+ gp = REMMINA_PROTOCOL_WIDGET(cnnobj->proto);
+
+ // Ask the plugin if it can give us a screenshot
+ if (remmina_protocol_widget_plugin_screenshot(gp, &rpsd)) {
+ // Good, we have a screenshot from the plugin !
+
+ remmina_log_printf("Screenshot from plugin: w=%d h=%d bpp=%d bytespp=%d\n",
+ rpsd.width, rpsd.height, rpsd.bitsPerPixel, rpsd.bytesPerPixel);
+
+ width = rpsd.width;
+ height = rpsd.height;
+
+ if (rpsd.bitsPerPixel == 32)
+ cairo_format = CAIRO_FORMAT_ARGB32;
+ else if (rpsd.bitsPerPixel == 24)
+ cairo_format = CAIRO_FORMAT_RGB24;
+ else
+ cairo_format = CAIRO_FORMAT_RGB16_565;
+
+ stride = cairo_format_stride_for_width(cairo_format, width);
+
+ srcsurface = cairo_image_surface_create_for_data(rpsd.buffer, cairo_format, width, height, stride);
+
+ surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height);
+ cr = cairo_create(surface);
+ cairo_set_source_surface(cr, srcsurface, 0, 0);
+ cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
+ cairo_paint(cr);
+ cairo_surface_destroy(srcsurface);
+
+ free(rpsd.buffer);
+
+ } else {
+ // The plugin is not releasing us a screenshot, just try to catch one via GTK
+
+ /* Warn the user if image is distorted */
+ if (cnnobj->plugin_can_scale &&
+ get_current_allowed_scale_mode(cnnobj, NULL, NULL) == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED) {
+ dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
+ _("Warning: screenshot is scaled or distorted. Disable scaling to have better screenshot."));
+ g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(gtk_widget_destroy), NULL);
+ gtk_widget_show(dialog);
+ }
+
+ // Get the screenshot.
+ active_window = gtk_widget_get_window(GTK_WIDGET(gp));
+ // width = gdk_window_get_width(gtk_widget_get_window(GTK_WIDGET(cnnhld->cnnwin)));
+ width = gdk_window_get_width(active_window);
+ // height = gdk_window_get_height(gtk_widget_get_window(GTK_WIDGET(cnnhld->cnnwin)));
+ height = gdk_window_get_height(active_window);
+
+ screenshot = gdk_pixbuf_get_from_window(active_window, 0, 0, width, height);
+ if (screenshot == NULL)
+ g_print("gdk_pixbuf_get_from_window failed\n");
+
+ // Prepare the destination cairo surface.
+ surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height);
+ cr = cairo_create(surface);
+
+ // Copy the source pixbuf to the surface and paint it.
+ gdk_cairo_set_source_pixbuf(cr, screenshot, 0, 0);
+ cairo_paint(cr);
+
+ // Deallocate screenshot pixbuf
+ g_object_unref(screenshot);
+
+ }
+
+ remminafile = remmina_file_get_filename(cnnobj->remmina_file);
+ //imagedir = g_get_user_special_dir(G_USER_DIRECTORY_PICTURES);
+ /** @todo Improve file name (DONE:8743571d) + give the user the option */
+ pngdate = g_strdup_printf("%d-%d-%d-%d:%d:%f",
+ g_date_time_get_year(date),
+ g_date_time_get_month(date),
+ g_date_time_get_day_of_month(date),
+ g_date_time_get_hour(date),
+ g_date_time_get_minute(date),
+ g_date_time_get_seconds(date));
+
+ g_date_time_unref(date);
+ if (remminafile == NULL)
+ remminafile = "remmina_screenshot";
+ pngname = g_strdup_printf("%s/%s-%s.png", remmina_pref.screenshot_path,
+ g_path_get_basename(remminafile), pngdate);
+
+ cairo_surface_write_to_png(surface, pngname);
+
+ /* send a desktop notification */
+ remmina_public_send_notification("remmina-screenshot-is-ready-id", _("Screenshot taken"), pngname);
+
+ //Clean up and return.
+ cairo_destroy(cr);
+ cairo_surface_destroy(surface);
+
+
+}
+
+static void remmina_connection_holder_toolbar_minimize(GtkWidget* widget, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ remmina_connection_holder_floating_toolbar_show(cnnhld, FALSE);
+ gtk_window_iconify(GTK_WINDOW(cnnhld->cnnwin));
+}
+
+static void remmina_connection_holder_toolbar_disconnect(GtkWidget* widget, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ remmina_connection_holder_disconnect_current_page(cnnhld);
+}
+
+static void remmina_connection_holder_toolbar_grab(GtkWidget* widget, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ DECLARE_CNNOBJ
+ gboolean capture;
+
+ capture = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(widget));
+ remmina_file_set_int(cnnobj->remmina_file, "keyboard_grab", capture);
+ if (capture) {
+#if DEBUG_KB_GRABBING
+ printf("DEBUG_KB_GRABBING: Grabbing for button\n");
+#endif
+ remmina_connection_holder_keyboard_grab(cnnhld);
+ }else
+ remmina_connection_holder_keyboard_ungrab(cnnhld);
+}
+
+static GtkWidget*
+remmina_connection_holder_create_toolbar(RemminaConnectionHolder* cnnhld, gint mode)
+{
+ TRACE_CALL(__func__);
+ RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
+ GtkWidget* toolbar;
+ GtkToolItem* toolitem;
+ GtkWidget* widget;
+ GtkWidget* arrow;
+
+ toolbar = gtk_toolbar_new();
+ gtk_widget_show(toolbar);
+ gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_ICONS);
+ gtk_toolbar_set_show_arrow(GTK_TOOLBAR(toolbar), FALSE);
+ if (remmina_pref.small_toolbutton) {
+ gtk_toolbar_set_icon_size(GTK_TOOLBAR(toolbar), GTK_ICON_SIZE_MENU);
+ }
+
+ /* Auto-Fit */
+ toolitem = gtk_tool_button_new(NULL, NULL);
+ gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "remmina-fit-window");
+ remmina_connection_holder_set_tooltip(GTK_WIDGET(toolitem), _("Resize the window to fit in remote resolution"),
+ remmina_pref.shortcutkey_autofit, 0);
+ g_signal_connect(G_OBJECT(toolitem), "clicked", G_CALLBACK(remmina_connection_holder_toolbar_autofit), cnnhld);
+ priv->toolitem_autofit = toolitem;
+ gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
+ gtk_widget_show(GTK_WIDGET(toolitem));
+
+ /* Fullscreen toggle */
+ toolitem = gtk_toggle_tool_button_new();
+ gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "remmina-fullscreen");
+ remmina_connection_holder_set_tooltip(GTK_WIDGET(toolitem), _("Toggle fullscreen mode"),
+ remmina_pref.shortcutkey_fullscreen, 0);
+ gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
+ gtk_widget_show(GTK_WIDGET(toolitem));
+ priv->toolitem_fullscreen = toolitem;
+ gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(toolitem), mode != SCROLLED_WINDOW_MODE);
+ g_signal_connect(G_OBJECT(toolitem), "clicked", G_CALLBACK(remmina_connection_holder_toolbar_fullscreen), cnnhld);
+
+ /* Fullscreen drop-down options */
+ toolitem = gtk_tool_item_new();
+ gtk_widget_show(GTK_WIDGET(toolitem));
+ gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
+ widget = gtk_toggle_button_new();
+ gtk_widget_show(widget);
+ gtk_container_set_border_width(GTK_CONTAINER(widget), 0);
+ gtk_button_set_relief(GTK_BUTTON(widget), GTK_RELIEF_NONE);
+#if GTK_CHECK_VERSION(3, 20, 0)
+ gtk_widget_set_focus_on_click(GTK_WIDGET(widget), FALSE);
+#else
+ gtk_button_set_focus_on_click(GTK_BUTTON(widget), FALSE);
+#endif
+ if (remmina_pref.small_toolbutton) {
+ gtk_widget_set_name(widget, "remmina-small-button");
+ }
+ gtk_container_add(GTK_CONTAINER(toolitem), widget);
+
+#if GTK_CHECK_VERSION(3, 14, 0)
+ arrow = gtk_image_new_from_icon_name("pan-down-symbolic", GTK_ICON_SIZE_BUTTON);
+#else
+ arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
+#endif
+ gtk_widget_show(arrow);
+ gtk_container_add(GTK_CONTAINER(widget), arrow);
+ g_signal_connect(G_OBJECT(widget), "toggled", G_CALLBACK(remmina_connection_holder_toolbar_fullscreen_option), cnnhld);
+ priv->fullscreen_option_button = widget;
+ if (mode == SCROLLED_WINDOW_MODE) {
+ gtk_widget_set_sensitive(GTK_WIDGET(widget), FALSE);
+ }
+
+ /* Switch tabs */
+ toolitem = gtk_toggle_tool_button_new();
+ gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "remmina-switch-page");
+ remmina_connection_holder_set_tooltip(GTK_WIDGET(toolitem), _("Switch tab pages"), remmina_pref.shortcutkey_prevtab,
+ remmina_pref.shortcutkey_nexttab);
+ gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
+ gtk_widget_show(GTK_WIDGET(toolitem));
+ g_signal_connect(G_OBJECT(toolitem), "toggled", G_CALLBACK(remmina_connection_holder_toolbar_switch_page), cnnhld);
+ priv->toolitem_switch_page = toolitem;
+
+ toolitem = gtk_separator_tool_item_new();
+ gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
+ gtk_widget_show(GTK_WIDGET(toolitem));
+
+ /* Dynamic Resolution Update */
+ toolitem = gtk_toggle_tool_button_new();
+ gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "remmina-dynres");
+ remmina_connection_holder_set_tooltip(GTK_WIDGET(toolitem), _("Toggle dynamic resolution update"),
+ remmina_pref.shortcutkey_dynres, 0);
+ gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
+ gtk_widget_show(GTK_WIDGET(toolitem));
+ g_signal_connect(G_OBJECT(toolitem), "toggled", G_CALLBACK(remmina_connection_holder_toolbar_dynres), cnnhld);
+ priv->toolitem_dynres = toolitem;
+
+ /* Scaler button */
+ toolitem = gtk_toggle_tool_button_new();
+ gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "remmina-scale");
+ remmina_connection_holder_set_tooltip(GTK_WIDGET(toolitem), _("Toggle scaled mode"), remmina_pref.shortcutkey_scale, 0);
+ gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
+ gtk_widget_show(GTK_WIDGET(toolitem));
+ g_signal_connect(G_OBJECT(toolitem), "toggled", G_CALLBACK(remmina_connection_holder_toolbar_scaled_mode), cnnhld);
+ priv->toolitem_scale = toolitem;
+
+ /* Scaler aspect ratio dropdown menu */
+ toolitem = gtk_tool_item_new();
+ gtk_widget_show(GTK_WIDGET(toolitem));
+ gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
+ widget = gtk_toggle_button_new();
+ gtk_widget_show(widget);
+ gtk_container_set_border_width(GTK_CONTAINER(widget), 0);
+ gtk_button_set_relief(GTK_BUTTON(widget), GTK_RELIEF_NONE);
+#if GTK_CHECK_VERSION(3, 20, 0)
+ gtk_widget_set_focus_on_click(GTK_WIDGET(widget), FALSE);
+#else
+ gtk_button_set_focus_on_click(GTK_BUTTON(widget), FALSE);
+#endif
+ if (remmina_pref.small_toolbutton) {
+ gtk_widget_set_name(widget, "remmina-small-button");
+ }
+ gtk_container_add(GTK_CONTAINER(toolitem), widget);
+#if GTK_CHECK_VERSION(3, 14, 0)
+ arrow = gtk_image_new_from_icon_name("pan-down-symbolic", GTK_ICON_SIZE_BUTTON);
+#else
+ arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
+#endif
+ gtk_widget_show(arrow);
+ gtk_container_add(GTK_CONTAINER(widget), arrow);
+ g_signal_connect(G_OBJECT(widget), "toggled", G_CALLBACK(remmina_connection_holder_toolbar_scaler_option), cnnhld);
+ priv->scaler_option_button = widget;
+
+ /* Grab keyboard button */
+ toolitem = gtk_toggle_tool_button_new();
+ gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "input-keyboard");
+ remmina_connection_holder_set_tooltip(GTK_WIDGET(toolitem), _("Grab all keyboard events"),
+ remmina_pref.shortcutkey_grab, 0);
+ gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
+ gtk_widget_show(GTK_WIDGET(toolitem));
+ g_signal_connect(G_OBJECT(toolitem), "toggled", G_CALLBACK(remmina_connection_holder_toolbar_grab), cnnhld);
+ priv->toolitem_grab = toolitem;
+
+ toolitem = gtk_toggle_tool_button_new();
+ gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "preferences-system");
+ gtk_tool_item_set_tooltip_text(toolitem, _("Preferences"));
+ gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
+ gtk_widget_show(GTK_WIDGET(toolitem));
+ g_signal_connect(G_OBJECT(toolitem), "toggled", G_CALLBACK(remmina_connection_holder_toolbar_preferences), cnnhld);
+ priv->toolitem_preferences = toolitem;
+
+ toolitem = gtk_toggle_tool_button_new();
+ gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "system-run");
+ gtk_tool_button_set_label(GTK_TOOL_BUTTON(toolitem), _("Tools"));
+ gtk_tool_item_set_tooltip_text(toolitem, _("Tools"));
+ gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
+ gtk_widget_show(GTK_WIDGET(toolitem));
+ g_signal_connect(G_OBJECT(toolitem), "toggled", G_CALLBACK(remmina_connection_holder_toolbar_tools), cnnhld);
+ priv->toolitem_tools = toolitem;
+
+ toolitem = gtk_separator_tool_item_new();
+ gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
+ gtk_widget_show(GTK_WIDGET(toolitem));
+
+ toolitem = gtk_tool_button_new(NULL, "_Screenshot");
+ gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "camera-photo");
+ remmina_connection_holder_set_tooltip(GTK_WIDGET(toolitem), _("Screenshot"), remmina_pref.shortcutkey_screenshot, 0);
+ gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
+ gtk_widget_show(GTK_WIDGET(toolitem));
+ g_signal_connect(G_OBJECT(toolitem), "clicked", G_CALLBACK(remmina_connection_holder_toolbar_screenshot), cnnhld);
+
+ toolitem = gtk_tool_button_new(NULL, "_Bottom");
+ gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "go-bottom");
+ remmina_connection_holder_set_tooltip(GTK_WIDGET(toolitem), _("Minimize window"), remmina_pref.shortcutkey_minimize, 0);
+ gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
+ gtk_widget_show(GTK_WIDGET(toolitem));
+ g_signal_connect(G_OBJECT(toolitem), "clicked", G_CALLBACK(remmina_connection_holder_toolbar_minimize), cnnhld);
+
+ toolitem = gtk_tool_button_new(NULL, "_Disconnect");
+ gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "gtk-disconnect");
+ remmina_connection_holder_set_tooltip(GTK_WIDGET(toolitem), _("Disconnect"), remmina_pref.shortcutkey_disconnect, 0);
+ gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
+ gtk_widget_show(GTK_WIDGET(toolitem));
+ g_signal_connect(G_OBJECT(toolitem), "clicked", G_CALLBACK(remmina_connection_holder_toolbar_disconnect), cnnhld);
+
+ return toolbar;
+}
+
+static void remmina_connection_holder_place_toolbar(GtkToolbar *toolbar, GtkGrid *grid, GtkWidget *sibling, int toolbar_placement)
+{
+ /* Place the toolbar inside the grid and set its orientation */
+
+ if ( toolbar_placement == TOOLBAR_PLACEMENT_LEFT || toolbar_placement == TOOLBAR_PLACEMENT_RIGHT)
+ gtk_orientable_set_orientation(GTK_ORIENTABLE(toolbar), GTK_ORIENTATION_VERTICAL);
+ else
+ gtk_orientable_set_orientation(GTK_ORIENTABLE(toolbar), GTK_ORIENTATION_HORIZONTAL);
+
+
+ switch (toolbar_placement) {
+ case TOOLBAR_PLACEMENT_TOP:
+ gtk_widget_set_hexpand(GTK_WIDGET(toolbar), TRUE);
+ gtk_widget_set_vexpand(GTK_WIDGET(toolbar), FALSE);
+ gtk_grid_attach_next_to(GTK_GRID(grid), GTK_WIDGET(toolbar), sibling, GTK_POS_TOP, 1, 1);
+ break;
+ case TOOLBAR_PLACEMENT_RIGHT:
+ gtk_widget_set_vexpand(GTK_WIDGET(toolbar), TRUE);
+ gtk_widget_set_hexpand(GTK_WIDGET(toolbar), FALSE);
+ gtk_grid_attach_next_to(GTK_GRID(grid), GTK_WIDGET(toolbar), sibling, GTK_POS_RIGHT, 1, 1);
+ break;
+ case TOOLBAR_PLACEMENT_BOTTOM:
+ gtk_widget_set_hexpand(GTK_WIDGET(toolbar), TRUE);
+ gtk_widget_set_vexpand(GTK_WIDGET(toolbar), FALSE);
+ gtk_grid_attach_next_to(GTK_GRID(grid), GTK_WIDGET(toolbar), sibling, GTK_POS_BOTTOM, 1, 1);
+ break;
+ case TOOLBAR_PLACEMENT_LEFT:
+ gtk_widget_set_vexpand(GTK_WIDGET(toolbar), TRUE);
+ gtk_widget_set_hexpand(GTK_WIDGET(toolbar), FALSE);
+ gtk_grid_attach_next_to(GTK_GRID(grid), GTK_WIDGET(toolbar), sibling, GTK_POS_LEFT, 1, 1);
+ break;
+ }
+
+}
+
+static void remmina_connection_holder_update_toolbar(RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ DECLARE_CNNOBJ
+ RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
+ GtkToolItem* toolitem;
+ gboolean bval, dynres_avail, scale_avail;
+ gboolean test_floating_toolbar;
+ RemminaScaleMode scalemode;
+
+ remmina_connection_holder_update_toolbar_autofit_button(cnnhld);
+
+ toolitem = priv->toolitem_switch_page;
+ bval = (gtk_notebook_get_n_pages(GTK_NOTEBOOK(priv->notebook)) > 1);
+ gtk_widget_set_sensitive(GTK_WIDGET(toolitem), bval);
+
+ scalemode = get_current_allowed_scale_mode(cnnobj, &dynres_avail, &scale_avail);
+ gtk_widget_set_sensitive(GTK_WIDGET(priv->toolitem_dynres), dynres_avail);
+ gtk_widget_set_sensitive(GTK_WIDGET(priv->toolitem_scale), scale_avail);
+
+ switch (scalemode) {
+ case REMMINA_PROTOCOL_WIDGET_SCALE_MODE_NONE:
+ gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_dynres), FALSE);
+ gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_scale), FALSE);
+ gtk_widget_set_sensitive(GTK_WIDGET(priv->scaler_option_button), FALSE);
+ break;
+ case REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED:
+ gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_dynres), FALSE);
+ gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_scale), TRUE);
+ gtk_widget_set_sensitive(GTK_WIDGET(priv->scaler_option_button), TRUE);
+ break;
+ case REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES:
+ gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_dynres), TRUE);
+ gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_scale), FALSE);
+ gtk_widget_set_sensitive(GTK_WIDGET(priv->scaler_option_button), FALSE);
+ break;
+ }
+
+ toolitem = priv->toolitem_grab;
+ gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(toolitem),
+ remmina_file_get_int(cnnobj->remmina_file, "keyboard_grab", FALSE));
+
+ toolitem = priv->toolitem_preferences;
+ bval = remmina_protocol_widget_query_feature_by_type(REMMINA_PROTOCOL_WIDGET(cnnobj->proto),
+ REMMINA_PROTOCOL_FEATURE_TYPE_PREF);
+ gtk_widget_set_sensitive(GTK_WIDGET(toolitem), bval);
+
+ toolitem = priv->toolitem_tools;
+ bval = remmina_protocol_widget_query_feature_by_type(REMMINA_PROTOCOL_WIDGET(cnnobj->proto),
+ REMMINA_PROTOCOL_FEATURE_TYPE_TOOL);
+ gtk_widget_set_sensitive(GTK_WIDGET(toolitem), bval);
+
+ gtk_window_set_title(GTK_WINDOW(cnnhld->cnnwin), remmina_file_get_string(cnnobj->remmina_file, "name"));
+
+#if FLOATING_TOOLBAR_WIDGET
+ test_floating_toolbar = (priv->floating_toolbar_widget != NULL);
+#else
+ test_floating_toolbar = (priv->floating_toolbar_window != NULL);
+#endif
+ if (test_floating_toolbar) {
+ gtk_label_set_text(GTK_LABEL(priv->floating_toolbar_label),
+ remmina_file_get_string(cnnobj->remmina_file, "name"));
+ }
+
+}
+
+static void remmina_connection_holder_showhide_toolbar(RemminaConnectionHolder* cnnhld, gboolean resize)
+{
+ TRACE_CALL(__func__);
+ RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
+
+ /* Here we should threat the resize flag, but we don't */
+ if (priv->view_mode == SCROLLED_WINDOW_MODE) {
+ if (remmina_pref.hide_connection_toolbar) {
+ gtk_widget_hide(priv->toolbar);
+ }else {
+ gtk_widget_show(priv->toolbar);
+ }
+ }
+}
+
+static gboolean remmina_connection_holder_floating_toolbar_on_enter(GtkWidget* widget, GdkEventCrossing* event,
+ RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ remmina_connection_holder_floating_toolbar_show(cnnhld, TRUE);
+ return TRUE;
+}
+
+static gboolean remmina_connection_object_enter_protocol_widget(GtkWidget* widget, GdkEventCrossing* event,
+ RemminaConnectionObject* cnnobj)
+{
+ TRACE_CALL(__func__);
+ RemminaConnectionHolder* cnnhld = cnnobj->cnnhld;
+ RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
+ if (!priv->sticky && event->mode == GDK_CROSSING_NORMAL) {
+ remmina_connection_holder_floating_toolbar_show(cnnhld, FALSE);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void remmina_connection_window_focus_in(GtkWidget* widget, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+
+#if !FLOATING_TOOLBAR_WIDGET
+ RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
+
+ if (priv->floating_toolbar_window) {
+ remmina_connection_holder_floating_toolbar_visible(cnnhld, TRUE);
+ }
+#endif
+
+ remmina_connection_holder_keyboard_grab(cnnhld);
+}
+
+static void remmina_connection_window_focus_out(GtkWidget* widget, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ DECLARE_CNNOBJ
+
+#if !FLOATING_TOOLBAR_WIDGET
+ RemminaConnectionWindowPriv * priv = cnnhld->cnnwin->priv;
+#endif
+
+ remmina_connection_holder_keyboard_ungrab(cnnhld);
+ cnnhld->hostkey_activated = FALSE;
+
+#if !FLOATING_TOOLBAR_WIDGET
+ if (!priv->sticky && priv->floating_toolbar_window) {
+ remmina_connection_holder_floating_toolbar_visible(cnnhld, FALSE);
+ }
+#endif
+ if (REMMINA_IS_SCROLLED_VIEWPORT(cnnobj->scrolled_container)) {
+ remmina_scrolled_viewport_remove_motion(REMMINA_SCROLLED_VIEWPORT(cnnobj->scrolled_container));
+ }
+ remmina_protocol_widget_call_feature_by_type(REMMINA_PROTOCOL_WIDGET(cnnobj->proto),
+ REMMINA_PROTOCOL_FEATURE_TYPE_UNFOCUS, 0);
+
+}
+
+static gboolean remmina_connection_window_focus_out_event(GtkWidget* widget, GdkEvent* event, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+#if DEBUG_KB_GRABBING
+ printf("DEBUG_KB_GRABBING: focus out and mouse_pointer_entered is %s\n", cnnhld->cnnwin->priv->mouse_pointer_entered ? "true" : "false");
+#endif
+ remmina_connection_window_focus_out(widget, cnnhld);
+ return FALSE;
+}
+
+static gboolean remmina_connection_window_focus_in_event(GtkWidget* widget, GdkEvent* event, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+#if DEBUG_KB_GRABBING
+ printf("DEBUG_KB_GRABBING: focus in and mouse_pointer_entered is %s\n", cnnhld->cnnwin->priv->mouse_pointer_entered ? "true" : "false");
+#endif
+ remmina_connection_window_focus_in(widget, cnnhld);
+ return FALSE;
+}
+
+static gboolean remmina_connection_window_on_enter(GtkWidget* widget, GdkEventCrossing* event, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ cnnhld->cnnwin->priv->mouse_pointer_entered = TRUE;
+#if DEBUG_KB_GRABBING
+ printf("DEBUG_KB_GRABBING: enter detail=");
+ switch (event->detail) {
+ case GDK_NOTIFY_ANCESTOR: printf("GDK_NOTIFY_ANCESTOR"); break;
+ case GDK_NOTIFY_VIRTUAL: printf("GDK_NOTIFY_VIRTUAL"); break;
+ case GDK_NOTIFY_NONLINEAR: printf("GDK_NOTIFY_NONLINEAR"); break;
+ case GDK_NOTIFY_NONLINEAR_VIRTUAL: printf("GDK_NOTIFY_NONLINEAR_VIRTUAL"); break;
+ case GDK_NOTIFY_UNKNOWN: printf("GDK_NOTIFY_UNKNOWN"); break;
+ case GDK_NOTIFY_INFERIOR: printf("GDK_NOTIFY_INFERIOR"); break;
+ }
+ printf("\n");
+#endif
+ if (gtk_window_is_active(GTK_WINDOW(cnnhld->cnnwin))) {
+ remmina_connection_holder_keyboard_grab(cnnhld);
+ }
+ return FALSE;
+}
+
+
+static gboolean remmina_connection_window_on_leave(GtkWidget* widget, GdkEventCrossing* event, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+#if DEBUG_KB_GRABBING
+ printf("DEBUG_KB_GRABBING: leave detail=");
+ switch (event->detail) {
+ case GDK_NOTIFY_ANCESTOR: printf("GDK_NOTIFY_ANCESTOR"); break;
+ case GDK_NOTIFY_VIRTUAL: printf("GDK_NOTIFY_VIRTUAL"); break;
+ case GDK_NOTIFY_NONLINEAR: printf("GDK_NOTIFY_NONLINEAR"); break;
+ case GDK_NOTIFY_NONLINEAR_VIRTUAL: printf("GDK_NOTIFY_NONLINEAR_VIRTUAL"); break;
+ case GDK_NOTIFY_UNKNOWN: printf("GDK_NOTIFY_UNKNOWN"); break;
+ case GDK_NOTIFY_INFERIOR: printf("GDK_NOTIFY_INFERIOR"); break;
+ }
+ printf(" x=%f y=%f\n", event->x, event->y);
+ printf(" focus=%s\n", event->focus ? "yes" : "no");
+ printf("\n");
+#endif
+ /*
+ * Unity: we leave windows with GDK_NOTIFY_VIRTUAL or GDK_NOTIFY_NONLINEAR_VIRTUAL
+ * Gnome shell: we leave windows with both GDK_NOTIFY_VIRTUAL or GDK_NOTIFY_ANCESTOR
+ * Xfce: we cannot drag this window when grabbed, so we need to ungrab in response to GDK_NOTIFY_NONLINEAR
+ */
+ if (event->detail == GDK_NOTIFY_VIRTUAL || event->detail == GDK_NOTIFY_ANCESTOR ||
+ event->detail == GDK_NOTIFY_NONLINEAR_VIRTUAL || event->detail == GDK_NOTIFY_NONLINEAR) {
+ cnnhld->cnnwin->priv->mouse_pointer_entered = FALSE;
+ remmina_connection_holder_keyboard_ungrab(cnnhld);
+ }
+ return FALSE;
+}
+
+static gboolean
+remmina_connection_holder_floating_toolbar_hide(RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
+ priv->hidetb_timer = 0;
+ remmina_connection_holder_floating_toolbar_show(cnnhld, FALSE);
+ return FALSE;
+}
+
+static gboolean remmina_connection_holder_floating_toolbar_on_scroll(GtkWidget* widget, GdkEventScroll* event,
+ RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ DECLARE_CNNOBJ_WITH_RETURN(FALSE)
+ int opacity;
+
+ opacity = remmina_file_get_int(cnnobj->remmina_file, "toolbar_opacity", 0);
+ switch (event->direction) {
+ case GDK_SCROLL_UP:
+ if (opacity > 0) {
+ remmina_file_set_int(cnnobj->remmina_file, "toolbar_opacity", opacity - 1);
+ remmina_connection_holder_update_toolbar_opacity(cnnhld);
+ return TRUE;
+ }
+ break;
+ case GDK_SCROLL_DOWN:
+ if (opacity < TOOLBAR_OPACITY_LEVEL) {
+ remmina_file_set_int(cnnobj->remmina_file, "toolbar_opacity", opacity + 1);
+ remmina_connection_holder_update_toolbar_opacity(cnnhld);
+ return TRUE;
+ }
+ break;
+#ifdef GDK_SCROLL_SMOOTH
+ case GDK_SCROLL_SMOOTH:
+ if (event->delta_y < 0 && opacity > 0) {
+ remmina_file_set_int(cnnobj->remmina_file, "toolbar_opacity", opacity - 1);
+ remmina_connection_holder_update_toolbar_opacity(cnnhld);
+ return TRUE;
+ }
+ if (event->delta_y > 0 && opacity < TOOLBAR_OPACITY_LEVEL) {
+ remmina_file_set_int(cnnobj->remmina_file, "toolbar_opacity", opacity + 1);
+ remmina_connection_holder_update_toolbar_opacity(cnnhld);
+ return TRUE;
+ }
+ break;
+#endif
+ default:
+ break;
+ }
+ return FALSE;
+}
+
+static gboolean remmina_connection_window_after_configure_scrolled(gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ gint width, height;
+ GdkWindowState s;
+ gint ipg, npages;
+ RemminaConnectionObject* cnnobj;
+ RemminaConnectionHolder* cnnhld;
+
+ cnnhld = (RemminaConnectionHolder*)user_data;
+
+ s = gdk_window_get_state(gtk_widget_get_window(GTK_WIDGET(cnnhld->cnnwin)));
+
+ if (!cnnhld || !cnnhld->cnnwin)
+ return FALSE;
+
+ /* Changed window_maximize, window_width and window_height for all
+ * connections inside the notebook */
+ npages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(cnnhld->cnnwin->priv->notebook));
+ for (ipg = 0; ipg < npages; ipg++) {
+ cnnobj = g_object_get_data(
+ G_OBJECT(gtk_notebook_get_nth_page(GTK_NOTEBOOK(cnnhld->cnnwin->priv->notebook), ipg)),
+ "cnnobj");
+ if (s & GDK_WINDOW_STATE_MAXIMIZED) {
+ remmina_file_set_int(cnnobj->remmina_file, "window_maximize", TRUE);
+ } else {
+ gtk_window_get_size(GTK_WINDOW(cnnhld->cnnwin), &width, &height);
+ remmina_file_set_int(cnnobj->remmina_file, "window_width", width);
+ remmina_file_set_int(cnnobj->remmina_file, "window_height", height);
+ remmina_file_set_int(cnnobj->remmina_file, "window_maximize", FALSE);
+ }
+ }
+ cnnhld->cnnwin->priv->savestate_eventsourceid = 0;
+ return FALSE;
+}
+
+static gboolean remmina_connection_window_on_configure(GtkWidget* widget, GdkEventConfigure* event,
+ RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ DECLARE_CNNOBJ_WITH_RETURN(FALSE)
+#if !FLOATING_TOOLBAR_WIDGET
+ RemminaConnectionWindowPriv * priv = cnnhld->cnnwin->priv;
+ GtkRequisition req;
+ gint y;
+#endif
+
+ if (cnnhld->cnnwin->priv->savestate_eventsourceid) {
+ g_source_remove(cnnhld->cnnwin->priv->savestate_eventsourceid);
+ cnnhld->cnnwin->priv->savestate_eventsourceid = 0;
+ }
+
+ if (cnnhld->cnnwin && gtk_widget_get_window(GTK_WIDGET(cnnhld->cnnwin))
+ && cnnhld->cnnwin->priv->view_mode == SCROLLED_WINDOW_MODE) {
+ /* Under gnome shell we receive this configure_event BEFORE a window
+ * is really unmaximized, so we must read its new state and dimensions
+ * later, not now */
+ cnnhld->cnnwin->priv->savestate_eventsourceid = g_timeout_add(500, remmina_connection_window_after_configure_scrolled, cnnhld);
+ }
+
+#if !FLOATING_TOOLBAR_WIDGET
+ if (priv->floating_toolbar_window) {
+
+ gtk_widget_get_preferred_size(priv->floating_toolbar_window, &req, NULL);
+ gtk_window_get_position(GTK_WINDOW(priv->floating_toolbar_window), NULL, &y);
+ gtk_window_move(GTK_WINDOW(priv->floating_toolbar_window), event->x + MAX(0, (event->width - req.width) / 2), y);
+
+ remmina_connection_holder_floating_toolbar_update(cnnhld);
+ }
+#endif
+
+ if (cnnhld->cnnwin->priv->view_mode != SCROLLED_WINDOW_MODE) {
+ /* Notify window of change so that scroll border can be hidden or shown if needed */
+ remmina_connection_holder_check_resize(cnnobj->cnnhld);
+ }
+ return FALSE;
+}
+
+static void remmina_connection_holder_update_pin(RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ if (cnnhld->cnnwin->priv->pin_down) {
+ gtk_button_set_image(GTK_BUTTON(cnnhld->cnnwin->priv->pin_button),
+ gtk_image_new_from_icon_name("remmina-pin-down", GTK_ICON_SIZE_MENU));
+ }else {
+ gtk_button_set_image(GTK_BUTTON(cnnhld->cnnwin->priv->pin_button),
+ gtk_image_new_from_icon_name("remmina-pin-up", GTK_ICON_SIZE_MENU));
+ }
+}
+
+static void remmina_connection_holder_toolbar_pin(GtkWidget* widget, RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ remmina_pref.toolbar_pin_down = cnnhld->cnnwin->priv->pin_down = !cnnhld->cnnwin->priv->pin_down;
+ remmina_pref_save();
+ remmina_connection_holder_update_pin(cnnhld);
+}
+
+static void remmina_connection_holder_create_floating_toolbar(RemminaConnectionHolder* cnnhld, gint mode)
+{
+ TRACE_CALL(__func__);
+ DECLARE_CNNOBJ
+ RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
+#if FLOATING_TOOLBAR_WIDGET
+ GtkWidget* ftb_widget;
+#else
+ GtkWidget* ftb_popup_window;
+ GtkWidget* eventbox;
+#endif
+ GtkWidget* vbox;
+ GtkWidget* hbox;
+ GtkWidget* label;
+ GtkWidget* pinbutton;
+ GtkWidget* tb;
+
+
+#if FLOATING_TOOLBAR_WIDGET
+ /* A widget to be used for GtkOverlay for GTK >= 3.10 */
+ ftb_widget = gtk_event_box_new();
+#else
+ /* A popup window for GTK < 3.10 */
+ ftb_popup_window = gtk_window_new(GTK_WINDOW_POPUP);
+#endif
+
+ vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+ gtk_widget_show(vbox);
+
+
+#if FLOATING_TOOLBAR_WIDGET
+ gtk_container_add(GTK_CONTAINER(ftb_widget), vbox);
+#else
+ gtk_container_add(GTK_CONTAINER(ftb_popup_window), vbox);
+#endif
+
+ tb = remmina_connection_holder_create_toolbar(cnnhld, mode);
+ hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_widget_show(hbox);
+
+
+ /* The pin button */
+ pinbutton = gtk_button_new();
+ gtk_widget_show(pinbutton);
+ gtk_box_pack_start(GTK_BOX(hbox), pinbutton, FALSE, FALSE, 0);
+ gtk_button_set_relief(GTK_BUTTON(pinbutton), GTK_RELIEF_NONE);
+#if GTK_CHECK_VERSION(3, 20, 0)
+ gtk_widget_set_focus_on_click(GTK_WIDGET(pinbutton), FALSE);
+#else
+ gtk_button_set_focus_on_click(GTK_BUTTON(pinbutton), FALSE);
+#endif
+ gtk_widget_set_name(pinbutton, "remmina-pin-button");
+ g_signal_connect(G_OBJECT(pinbutton), "clicked", G_CALLBACK(remmina_connection_holder_toolbar_pin), cnnhld);
+ priv->pin_button = pinbutton;
+ priv->pin_down = remmina_pref.toolbar_pin_down;
+ remmina_connection_holder_update_pin(cnnhld);
+
+
+ label = gtk_label_new(remmina_file_get_string(cnnobj->remmina_file, "name"));
+ gtk_label_set_max_width_chars(GTK_LABEL(label), 50);
+ gtk_widget_show(label);
+
+#if FLOATING_TOOLBAR_WIDGET
+ gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
+#else
+ /* An event box is required to wrap the label to avoid infinite "leave-enter" event loop */
+ eventbox = gtk_event_box_new();
+ gtk_widget_show(eventbox);
+ gtk_box_pack_start(GTK_BOX(hbox), eventbox, TRUE, TRUE, 0);
+ gtk_container_add(GTK_CONTAINER(eventbox), label);
+#endif
+
+ priv->floating_toolbar_label = label;
+
+
+#if FLOATING_TOOLBAR_WIDGET
+
+ if (remmina_pref.floating_toolbar_placement == FLOATING_TOOLBAR_PLACEMENT_BOTTOM) {
+ gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(vbox), tb, FALSE, FALSE, 0);
+ }else {
+ gtk_box_pack_start(GTK_BOX(vbox), tb, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+ }
+
+ priv->floating_toolbar_widget = ftb_widget;
+ if (cnnobj->connected)
+ gtk_widget_show(ftb_widget);
+
+#else
+
+ gtk_box_pack_start(GTK_BOX(vbox), tb, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+
+ /* The position will be moved in configure event instead during maximizing. Just make it invisible here */
+ gtk_window_move(GTK_WINDOW(ftb_popup_window), 0, 6000);
+ gtk_window_set_accept_focus(GTK_WINDOW(ftb_popup_window), FALSE);
+
+ priv->floating_toolbar_window = ftb_popup_window;
+
+ remmina_connection_holder_update_toolbar_opacity(cnnhld);
+ if (remmina_pref.fullscreen_toolbar_visibility == FLOATING_TOOLBAR_VISIBILITY_INVISIBLE && !priv->pin_down) {
+#if GTK_CHECK_VERSION(3, 8, 0)
+ gtk_widget_set_opacity(GTK_WIDGET(ftb_popup_window), 0.0);
+#else
+ gtk_window_set_opacity(GTK_WINDOW(ftb_popup_window), 0.0);
+#endif
+ }
+
+ if (cnnobj->connected)
+ gtk_widget_show(ftb_popup_window);
+#endif
+}
+
+static void remmina_connection_window_toolbar_place_signal(RemminaConnectionWindow* cnnwin, gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaConnectionWindowPriv* priv;
+
+ priv = cnnwin->priv;
+ /* Detach old toolbar widget and reattach in new position in the grid */
+ if (priv->toolbar && priv->grid) {
+ g_object_ref(priv->toolbar);
+ gtk_container_remove(GTK_CONTAINER(priv->grid), priv->toolbar);
+ remmina_connection_holder_place_toolbar(GTK_TOOLBAR(priv->toolbar), GTK_GRID(priv->grid), priv->notebook, remmina_pref.toolbar_placement);
+ g_object_unref(priv->toolbar);
+ }
+}
+
+
+static void remmina_connection_window_init(RemminaConnectionWindow* cnnwin)
+{
+ TRACE_CALL(__func__);
+ RemminaConnectionWindowPriv* priv;
+
+ priv = g_new0(RemminaConnectionWindowPriv, 1);
+ cnnwin->priv = priv;
+
+ priv->view_mode = AUTO_MODE;
+ priv->floating_toolbar_opacity = 1.0;
+ priv->kbcaptured = FALSE;
+ priv->mouse_pointer_entered = FALSE;
+
+ gtk_container_set_border_width(GTK_CONTAINER(cnnwin), 0);
+
+ remmina_widget_pool_register(GTK_WIDGET(cnnwin));
+
+ g_signal_connect(G_OBJECT(cnnwin), "toolbar-place", G_CALLBACK(remmina_connection_window_toolbar_place_signal), NULL);
+}
+
+static gboolean remmina_connection_window_state_event(GtkWidget* widget, GdkEventWindowState* event, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+
+ if (event->changed_mask & GDK_WINDOW_STATE_FOCUSED) {
+ if (event->new_window_state & GDK_WINDOW_STATE_FOCUSED)
+ remmina_connection_window_focus_in(widget, user_data);
+ else
+ remmina_connection_window_focus_out(widget, user_data);
+ }
+
+#ifdef ENABLE_MINIMIZE_TO_TRAY
+ GdkScreen* screen;
+
+ screen = gdk_screen_get_default();
+ if (remmina_pref.minimize_to_tray && (event->changed_mask & GDK_WINDOW_STATE_ICONIFIED) != 0
+ && (event->new_window_state & GDK_WINDOW_STATE_ICONIFIED) != 0
+ && remmina_public_get_current_workspace(screen)
+ == remmina_public_get_window_workspace(GTK_WINDOW(widget))
+ && gdk_screen_get_number(screen) == gdk_screen_get_number(gtk_widget_get_screen(widget))) {
+ gtk_widget_hide(widget);
+ return TRUE;
+ }
+#endif
+ return FALSE; // moved here because a function should return a value. Should be correct
+}
+
+static GtkWidget*
+remmina_connection_window_new_from_holder(RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ RemminaConnectionWindow* cnnwin;
+
+ cnnwin = REMMINA_CONNECTION_WINDOW(g_object_new(REMMINA_TYPE_CONNECTION_WINDOW, NULL));
+ cnnwin->priv->cnnhld = cnnhld;
+ cnnwin->priv->on_delete_confirm_mode = REMMINA_CONNECTION_WINDOW_ONDELETE_CONFIRM_IF_2_OR_MORE;
+
+ g_signal_connect(G_OBJECT(cnnwin), "delete-event", G_CALLBACK(remmina_connection_window_delete_event), cnnhld);
+ g_signal_connect(G_OBJECT(cnnwin), "destroy", G_CALLBACK(remmina_connection_window_destroy), cnnhld);
+
+ /* focus-in-event and focus-out-event don't work when keyboard is grabbed
+ * via gdk_device_grab. So we listen for window-state-event to detect focus in and focus out */
+ g_signal_connect(G_OBJECT(cnnwin), "window-state-event", G_CALLBACK(remmina_connection_window_state_event), cnnhld);
+
+ g_signal_connect(G_OBJECT(cnnwin), "focus-in-event", G_CALLBACK(remmina_connection_window_focus_in_event), cnnhld);
+ g_signal_connect(G_OBJECT(cnnwin), "focus-out-event", G_CALLBACK(remmina_connection_window_focus_out_event), cnnhld);
+
+ g_signal_connect(G_OBJECT(cnnwin), "enter-notify-event", G_CALLBACK(remmina_connection_window_on_enter), cnnhld);
+ g_signal_connect(G_OBJECT(cnnwin), "leave-notify-event", G_CALLBACK(remmina_connection_window_on_leave), cnnhld);
+
+ g_signal_connect(G_OBJECT(cnnwin), "configure_event", G_CALLBACK(remmina_connection_window_on_configure), cnnhld);
+
+ return GTK_WIDGET(cnnwin);
+}
+
+/* This function will be called for the first connection. A tag is set to the window so that
+ * other connections can determine if whether a new tab should be append to the same window
+ */
+static void remmina_connection_window_update_tag(RemminaConnectionWindow* cnnwin, RemminaConnectionObject* cnnobj)
+{
+ TRACE_CALL(__func__);
+ gchar* tag;
+
+ switch (remmina_pref.tab_mode) {
+ case REMMINA_TAB_BY_GROUP:
+ tag = g_strdup(remmina_file_get_string(cnnobj->remmina_file, "group"));
+ break;
+ case REMMINA_TAB_BY_PROTOCOL:
+ tag = g_strdup(remmina_file_get_string(cnnobj->remmina_file, "protocol"));
+ break;
+ default:
+ tag = NULL;
+ break;
+ }
+ g_object_set_data_full(G_OBJECT(cnnwin), "tag", tag, (GDestroyNotify)g_free);
+}
+
+static void remmina_connection_object_create_scrolled_container(RemminaConnectionObject* cnnobj, gint view_mode)
+{
+ TRACE_CALL(__func__);
+ GtkWidget* container;
+
+ if (view_mode == VIEWPORT_FULLSCREEN_MODE) {
+ container = remmina_scrolled_viewport_new();
+ }else {
+ container = gtk_scrolled_window_new(NULL, NULL);
+ remmina_connection_object_set_scrolled_policy(cnnobj, GTK_SCROLLED_WINDOW(container));
+ gtk_container_set_border_width(GTK_CONTAINER(container), 0);
+ gtk_widget_set_can_focus(container, FALSE);
+ }
+
+ gtk_widget_set_name(container, "remmina-scrolled-container");
+
+ g_object_set_data(G_OBJECT(container), "cnnobj", cnnobj);
+ gtk_widget_show(container);
+ cnnobj->scrolled_container = container;
+
+ g_signal_connect(G_OBJECT(cnnobj->proto), "enter-notify-event", G_CALLBACK(remmina_connection_object_enter_protocol_widget), cnnobj);
+
+}
+
+static void remmina_connection_holder_grab_focus(GtkNotebook *notebook)
+{
+ TRACE_CALL(__func__);
+ RemminaConnectionObject* cnnobj;
+ GtkWidget* child;
+
+ child = gtk_notebook_get_nth_page(notebook, gtk_notebook_get_current_page(notebook));
+ cnnobj = g_object_get_data(G_OBJECT(child), "cnnobj");
+ if (GTK_IS_WIDGET(cnnobj->proto)) {
+ remmina_protocol_widget_grab_focus(REMMINA_PROTOCOL_WIDGET(cnnobj->proto));
+ }
+}
+
+static void remmina_connection_object_on_close_button_clicked(GtkButton* button, RemminaConnectionObject* cnnobj)
+{
+ TRACE_CALL(__func__);
+ if (REMMINA_IS_PROTOCOL_WIDGET(cnnobj->proto)) {
+ remmina_protocol_widget_close_connection(REMMINA_PROTOCOL_WIDGET(cnnobj->proto));
+ }
+}
+
+static GtkWidget* remmina_connection_object_create_tab(RemminaConnectionObject* cnnobj)
+{
+ TRACE_CALL(__func__);
+ GtkWidget* hbox;
+ GtkWidget* widget;
+ GtkWidget* button;
+
+
+ hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
+ gtk_widget_show(hbox);
+
+ widget = gtk_image_new_from_icon_name(remmina_file_get_icon_name(cnnobj->remmina_file), GTK_ICON_SIZE_MENU);
+ gtk_widget_show(widget);
+ gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, FALSE, 0);
+
+ widget = gtk_label_new(remmina_file_get_string(cnnobj->remmina_file, "name"));
+ gtk_widget_set_valign(widget, GTK_ALIGN_CENTER);
+ gtk_widget_set_halign(widget, GTK_ALIGN_CENTER);
+
+ gtk_widget_show(widget);
+ gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, TRUE, 0);
+
+ button = gtk_button_new(); // The "x" to close the tab
+ gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
+#if GTK_CHECK_VERSION(3, 20, 0)
+ gtk_widget_set_focus_on_click(GTK_WIDGET(widget), FALSE);
+#else
+ gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
+#endif
+ gtk_widget_set_name(button, "remmina-small-button");
+ gtk_widget_show(button);
+
+ widget = gtk_image_new_from_icon_name("window-close", GTK_ICON_SIZE_MENU);
+ gtk_widget_show(widget);
+ gtk_container_add(GTK_CONTAINER(button), widget);
+
+ gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
+
+ g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(remmina_connection_object_on_close_button_clicked), cnnobj);
+
+ return hbox;
+}
+
+static gint remmina_connection_object_append_page(RemminaConnectionObject* cnnobj, GtkNotebook* notebook, GtkWidget* tab,
+ gint view_mode)
+{
+ TRACE_CALL(__func__);
+ gint i;
+
+ remmina_connection_object_create_scrolled_container(cnnobj, view_mode);
+ i = gtk_notebook_append_page(notebook, cnnobj->scrolled_container, tab);
+ gtk_notebook_set_tab_reorderable(notebook, cnnobj->scrolled_container, TRUE);
+ gtk_notebook_set_tab_detachable(notebook, cnnobj->scrolled_container, TRUE);
+ /* This trick prevents the tab label from being focused */
+ gtk_widget_set_can_focus(gtk_widget_get_parent(tab), FALSE);
+ return i;
+}
+
+static void remmina_connection_window_initialize_notebook(GtkNotebook* to, GtkNotebook* from, RemminaConnectionObject* cnnobj,
+ gint view_mode)
+{
+ TRACE_CALL(__func__);
+ gint i, n, c;
+ GtkWidget* tab;
+ GtkWidget* widget;
+ RemminaConnectionObject* tc;
+
+ if (cnnobj) {
+ /* Search cnnobj in the "from" notebook */
+ tc = NULL;
+ if (from) {
+ n = gtk_notebook_get_n_pages(from);
+ for (i = 0; i < n; i++) {
+ widget = gtk_notebook_get_nth_page(from, i);
+ tc = (RemminaConnectionObject*)g_object_get_data(G_OBJECT(widget), "cnnobj");
+ if (tc == cnnobj)
+ break;
+ }
+ }
+ if (tc) {
+ /* if cnnobj is already in the "from" notebook, we should be in the drag and drop case.
+ * just... do not move it. GTK will do the move when the create-window signal
+ * of GtkNotebook will return */
+
+ } else {
+ /* cnnobj is not on the "from" notebook. This is a new connection for a newly created window */
+ tab = remmina_connection_object_create_tab(cnnobj);
+ remmina_connection_object_append_page(cnnobj, to, tab, view_mode);
+
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ gtk_widget_reparent(cnnobj->viewport, cnnobj->scrolled_container);
+ G_GNUC_END_IGNORE_DEPRECATIONS
+
+ if (cnnobj->window) {
+ gtk_widget_destroy(cnnobj->window);
+ cnnobj->window = NULL;
+ }
+ }
+ }else {
+ /* cnnobj=null: migrate all existing connections to the new notebook */
+ if (from != NULL && GTK_IS_NOTEBOOK(from)) {
+ c = gtk_notebook_get_current_page(from);
+ n = gtk_notebook_get_n_pages(from);
+ for (i = 0; i < n; i++) {
+ widget = gtk_notebook_get_nth_page(from, i);
+ tc = (RemminaConnectionObject*)g_object_get_data(G_OBJECT(widget), "cnnobj");
+
+ tab = remmina_connection_object_create_tab(tc);
+ remmina_connection_object_append_page(tc, to, tab, view_mode);
+
+ /* Reparent cnnobj->viewport */
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ gtk_widget_reparent(tc->viewport, tc->scrolled_container);
+ G_GNUC_END_IGNORE_DEPRECATIONS
+ }
+ gtk_notebook_set_current_page(to, c);
+ }
+ }
+}
+
+static void remmina_connection_holder_update_notebook(RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ GtkNotebook* notebook;
+ gint n;
+
+ if (!cnnhld->cnnwin)
+ return;
+
+ notebook = GTK_NOTEBOOK(cnnhld->cnnwin->priv->notebook);
+
+ switch (cnnhld->cnnwin->priv->view_mode) {
+ case SCROLLED_WINDOW_MODE:
+ n = gtk_notebook_get_n_pages(notebook);
+ gtk_notebook_set_show_tabs(notebook, remmina_pref.always_show_tab ? TRUE : n > 1);
+ gtk_notebook_set_show_border(notebook, remmina_pref.always_show_tab ? TRUE : n > 1);
+ break;
+ default:
+ gtk_notebook_set_show_tabs(notebook, FALSE);
+ gtk_notebook_set_show_border(notebook, FALSE);
+ break;
+ }
+}
+
+static gboolean remmina_connection_holder_on_switch_page_real(gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaConnectionHolder* cnnhld = (RemminaConnectionHolder*)data;
+ RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
+
+ if (GTK_IS_WIDGET(cnnhld->cnnwin)) {
+ remmina_connection_holder_floating_toolbar_show(cnnhld, TRUE);
+ if (!priv->hidetb_timer)
+ priv->hidetb_timer = g_timeout_add(TB_HIDE_TIME_TIME, (GSourceFunc)
+ remmina_connection_holder_floating_toolbar_hide, cnnhld);
+ remmina_connection_holder_update_toolbar(cnnhld);
+ remmina_connection_holder_grab_focus(GTK_NOTEBOOK(priv->notebook));
+ if (cnnhld->cnnwin->priv->view_mode != SCROLLED_WINDOW_MODE) {
+ remmina_connection_holder_check_resize(cnnhld);
+ }
+
+ }
+ priv->switch_page_handler = 0;
+ return FALSE;
+}
+
+static void remmina_connection_holder_on_switch_page(GtkNotebook* notebook, GtkWidget* page, guint page_num,
+ RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
+
+ if (!priv->switch_page_handler) {
+ priv->switch_page_handler = g_idle_add(remmina_connection_holder_on_switch_page_real, cnnhld);
+ }
+}
+
+static void remmina_connection_holder_on_page_added(GtkNotebook* notebook, GtkWidget* child, guint page_num,
+ RemminaConnectionHolder* cnnhld)
+{
+ if (gtk_notebook_get_n_pages(GTK_NOTEBOOK(cnnhld->cnnwin->priv->notebook)) > 0)
+ remmina_connection_holder_update_notebook(cnnhld);
+}
+
+static void remmina_connection_holder_on_page_removed(GtkNotebook* notebook, GtkWidget* child, guint page_num,
+ RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+
+ if (!cnnhld->cnnwin)
+ return;
+
+ if (gtk_notebook_get_n_pages(GTK_NOTEBOOK(cnnhld->cnnwin->priv->notebook)) <= 0) {
+ gtk_widget_destroy(GTK_WIDGET(cnnhld->cnnwin));
+ cnnhld->cnnwin = NULL;
+ }
+
+}
+
+GtkNotebook*
+remmina_connection_holder_on_notebook_create_window(GtkNotebook* notebook, GtkWidget* page, gint x, gint y, gpointer data)
+{
+ /* This signal callback is called by GTK when a detachable tab is dropped on the root window */
+
+ TRACE_CALL(__func__);
+ RemminaConnectionWindow* srccnnwin;
+ RemminaConnectionWindow* dstcnnwin;
+ RemminaConnectionObject* cnnobj;
+ GdkWindow* window;
+
+#if GTK_CHECK_VERSION(3, 20, 0)
+ GdkSeat *seat;
+#else
+ GdkDeviceManager *manager;
+#endif
+ GdkDevice* device = NULL;
+
+#if GTK_CHECK_VERSION(3, 20, 0)
+ seat = gdk_display_get_default_seat(gdk_display_get_default());
+ device = gdk_seat_get_pointer(seat);
+#else
+ manager = gdk_display_get_device_manager(gdk_display_get_default());
+ device = gdk_device_manager_get_client_pointer(manager);
+#endif
+
+ window = gdk_device_get_window_at_position(device, &x, &y);
+ srccnnwin = REMMINA_CONNECTION_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(notebook)));
+ dstcnnwin = REMMINA_CONNECTION_WINDOW(remmina_widget_pool_find_by_window(REMMINA_TYPE_CONNECTION_WINDOW, window));
+
+ if (srccnnwin == dstcnnwin)
+ return NULL;
+
+ if (gtk_notebook_get_n_pages(GTK_NOTEBOOK(srccnnwin->priv->notebook)) == 1 && !dstcnnwin)
+ return NULL;
+
+ cnnobj = (RemminaConnectionObject*)g_object_get_data(G_OBJECT(page), "cnnobj");
+
+ if (dstcnnwin) {
+ cnnobj->cnnhld = dstcnnwin->priv->cnnhld;
+ }else {
+ cnnobj->cnnhld = g_new0(RemminaConnectionHolder, 1);
+ if (!cnnobj->cnnhld->cnnwin) {
+ /* Create a new scrolled window to accomodate the dropped connection
+ * and move our cnnobj there */
+ cnnobj->cnnhld->cnnwin = srccnnwin;
+ remmina_connection_holder_create_scrolled(cnnobj->cnnhld, cnnobj);
+ }
+ }
+
+ remmina_protocol_widget_set_hostkey_func(REMMINA_PROTOCOL_WIDGET(cnnobj->proto),
+ (RemminaHostkeyFunc)remmina_connection_window_hostkey_func, cnnobj->cnnhld);
+
+ return GTK_NOTEBOOK(cnnobj->cnnhld->cnnwin->priv->notebook);
+}
+
+static GtkWidget*
+remmina_connection_holder_create_notebook(RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ GtkWidget* notebook;
+
+ notebook = gtk_notebook_new();
+
+ gtk_notebook_set_scrollable(GTK_NOTEBOOK(notebook), TRUE);
+ gtk_widget_show(notebook);
+
+ g_signal_connect(G_OBJECT(notebook), "create-window", G_CALLBACK(remmina_connection_holder_on_notebook_create_window),
+ cnnhld);
+ g_signal_connect(G_OBJECT(notebook), "switch-page", G_CALLBACK(remmina_connection_holder_on_switch_page), cnnhld);
+ g_signal_connect(G_OBJECT(notebook), "page-added", G_CALLBACK(remmina_connection_holder_on_page_added), cnnhld);
+ g_signal_connect(G_OBJECT(notebook), "page-removed", G_CALLBACK(remmina_connection_holder_on_page_removed), cnnhld);
+ gtk_widget_set_can_focus(notebook, FALSE);
+
+ return notebook;
+}
+
+/* Create a scrolled window container */
+static void remmina_connection_holder_create_scrolled(RemminaConnectionHolder* cnnhld, RemminaConnectionObject* cnnobj)
+{
+ TRACE_CALL(__func__);
+ GtkWidget* window;
+ GtkWidget* oldwindow;
+ GtkWidget* grid;
+ GtkWidget* toolbar;
+ GtkWidget* notebook;
+ GList *chain;
+ gchar* tag;
+ int newwin_width, newwin_height;
+
+ oldwindow = GTK_WIDGET(cnnhld->cnnwin);
+ window = remmina_connection_window_new_from_holder(cnnhld);
+ gtk_widget_realize(window);
+ cnnhld->cnnwin = REMMINA_CONNECTION_WINDOW(window);
+
+
+ newwin_width = newwin_height = 100;
+ if (cnnobj) {
+ /* If we have a cnnobj as a reference for this window, we can setup its default size here */
+ newwin_width = remmina_file_get_int(cnnobj->remmina_file, "window_width", 640);
+ newwin_height = remmina_file_get_int(cnnobj->remmina_file, "window_height", 480);
+ } else {
+ /* Try to get a temporary RemminaConnectionObject from the old window and get
+ * a remmina_file and width/height */
+ int np;
+ GtkWidget* page;
+ RemminaConnectionObject* oldwindow_currentpage_cnnobj;
+
+ np = gtk_notebook_get_current_page(GTK_NOTEBOOK(REMMINA_CONNECTION_WINDOW(oldwindow)->priv->notebook));
+ page = gtk_notebook_get_nth_page(GTK_NOTEBOOK(REMMINA_CONNECTION_WINDOW(oldwindow)->priv->notebook), np);
+ oldwindow_currentpage_cnnobj = (RemminaConnectionObject*)g_object_get_data(G_OBJECT(page), "cnnobj");
+ newwin_width = remmina_file_get_int(oldwindow_currentpage_cnnobj->remmina_file, "window_width", 640);
+ newwin_height = remmina_file_get_int(oldwindow_currentpage_cnnobj->remmina_file, "window_height", 480);
+ }
+
+ gtk_window_set_default_size(GTK_WINDOW(cnnhld->cnnwin), newwin_width, newwin_height);
+
+ /* Create the toolbar */
+ toolbar = remmina_connection_holder_create_toolbar(cnnhld, SCROLLED_WINDOW_MODE);
+
+ /* Create the notebook */
+ notebook = remmina_connection_holder_create_notebook(cnnhld);
+
+ /* Create the grid container for toolbars+notebook and populate it */
+ grid = gtk_grid_new();
+ gtk_grid_attach(GTK_GRID(grid), notebook, 0, 0, 1, 1);
+
+ gtk_widget_set_hexpand(notebook, TRUE);
+ gtk_widget_set_vexpand(notebook, TRUE);
+
+ remmina_connection_holder_place_toolbar(GTK_TOOLBAR(toolbar), GTK_GRID(grid), notebook, remmina_pref.toolbar_placement);
+
+
+ gtk_container_add(GTK_CONTAINER(window), grid);
+
+ chain = g_list_append(NULL, notebook);
+ gtk_container_set_focus_chain(GTK_CONTAINER(grid), chain);
+ g_list_free(chain);
+
+ /* Add drag capabilities to the toolbar */
+ gtk_drag_source_set(GTK_WIDGET(toolbar), GDK_BUTTON1_MASK,
+ dnd_targets_tb, sizeof dnd_targets_tb / sizeof *dnd_targets_tb, GDK_ACTION_MOVE);
+ g_signal_connect_after(GTK_WIDGET(toolbar), "drag-begin", G_CALLBACK(remmina_connection_window_tb_drag_begin), cnnhld);
+ g_signal_connect(GTK_WIDGET(toolbar), "drag-failed", G_CALLBACK(remmina_connection_window_tb_drag_failed), cnnhld);
+
+ /* Add drop capabilities to the drop/dest target for the toolbar (the notebook) */
+ gtk_drag_dest_set(GTK_WIDGET(notebook), GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT,
+ dnd_targets_tb, sizeof dnd_targets_tb / sizeof *dnd_targets_tb, GDK_ACTION_MOVE);
+ gtk_drag_dest_set_track_motion(GTK_WIDGET(notebook), TRUE);
+ g_signal_connect(GTK_WIDGET(notebook), "drag-drop", G_CALLBACK(remmina_connection_window_tb_drag_drop), cnnhld);
+
+ cnnhld->cnnwin->priv->view_mode = SCROLLED_WINDOW_MODE;
+ cnnhld->cnnwin->priv->toolbar = toolbar;
+ cnnhld->cnnwin->priv->grid = grid;
+ cnnhld->cnnwin->priv->notebook = notebook;
+
+ /* The notebook and all its child must be realized now, or a reparent will
+ * call unrealize() and will destroy a GtkSocket */
+ gtk_widget_show(grid);
+ gtk_widget_show(GTK_WIDGET(cnnhld->cnnwin));
+
+ remmina_connection_window_initialize_notebook(GTK_NOTEBOOK(notebook),
+ (oldwindow ? GTK_NOTEBOOK(REMMINA_CONNECTION_WINDOW(oldwindow)->priv->notebook) : NULL), cnnobj,
+ SCROLLED_WINDOW_MODE);
+
+ if (cnnobj) {
+ if (!oldwindow)
+ remmina_connection_window_update_tag(cnnhld->cnnwin, cnnobj);
+
+ if (remmina_file_get_int(cnnobj->remmina_file, "window_maximize", FALSE)) {
+ gtk_window_maximize(GTK_WINDOW(cnnhld->cnnwin));
+ }
+ }
+
+ if (oldwindow) {
+ tag = g_strdup((gchar*)g_object_get_data(G_OBJECT(oldwindow), "tag"));
+ g_object_set_data_full(G_OBJECT(cnnhld->cnnwin), "tag", tag, (GDestroyNotify)g_free);
+ if (!cnnobj)
+ gtk_widget_destroy(oldwindow);
+ }
+
+ remmina_connection_holder_update_toolbar(cnnhld);
+ remmina_connection_holder_showhide_toolbar(cnnhld, FALSE);
+ remmina_connection_holder_check_resize(cnnhld);
+
+
+}
+
+static gboolean remmina_connection_window_go_fullscreen(GtkWidget *widget, GdkEvent *event, gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaConnectionHolder* cnnhld;
+
+ cnnhld = (RemminaConnectionHolder*)data;
+
+#if GTK_CHECK_VERSION(3, 18, 0)
+ if (remmina_pref.fullscreen_on_auto) {
+ gtk_window_fullscreen_on_monitor(GTK_WINDOW(cnnhld->cnnwin),
+ gdk_screen_get_default(),
+ gdk_screen_get_monitor_at_window
+ (gdk_screen_get_default(), gtk_widget_get_window(GTK_WIDGET(cnnhld->cnnwin))
+ ));
+ } else {
+ remmina_log_print("Fullscreen managed by WM or by the user, as per settings");
+ gtk_window_fullscreen(GTK_WINDOW(cnnhld->cnnwin));
+ }
+#else
+ remmina_log_print("Cannot fullscreen on a specific monitor, feature available from GTK 3.18");
+ gtk_window_fullscreen(GTK_WINDOW(cnnhld->cnnwin));
+#endif
+ return FALSE;
+}
+
+#if FLOATING_TOOLBAR_WIDGET
+
+static void remmina_connection_holder_create_overlay_ftb_overlay(RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+
+ GtkWidget* revealer;
+ RemminaConnectionWindowPriv* priv;
+ priv = cnnhld->cnnwin->priv;
+
+ if (priv->overlay_ftb_overlay != NULL) {
+ gtk_widget_destroy(priv->overlay_ftb_overlay);
+ priv->overlay_ftb_overlay = NULL;
+ priv->revealer = NULL;
+ }
+
+ remmina_connection_holder_create_floating_toolbar(cnnhld, cnnhld->fullscreen_view_mode);
+ remmina_connection_holder_update_toolbar(cnnhld);
+
+ priv->overlay_ftb_overlay = gtk_event_box_new();
+
+ GtkWidget* vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+ gtk_container_set_border_width(GTK_CONTAINER(vbox), 0);
+
+ GtkWidget* handle = gtk_drawing_area_new();
+ gtk_widget_set_size_request(handle, 4, 4);
+ gtk_widget_set_name(handle, "ftb-handle");
+
+ revealer = gtk_revealer_new();
+
+ gtk_widget_set_halign(GTK_WIDGET(priv->overlay_ftb_overlay), GTK_ALIGN_CENTER);
+
+ if (remmina_pref.floating_toolbar_placement == FLOATING_TOOLBAR_PLACEMENT_BOTTOM) {
+ gtk_box_pack_start(GTK_BOX(vbox), handle, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(vbox), revealer, FALSE, FALSE, 0);
+ gtk_revealer_set_transition_type(GTK_REVEALER(revealer), GTK_REVEALER_TRANSITION_TYPE_SLIDE_UP);
+ gtk_widget_set_valign(GTK_WIDGET(priv->overlay_ftb_overlay), GTK_ALIGN_END);
+ }else {
+ gtk_box_pack_start(GTK_BOX(vbox), revealer, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(vbox), handle, FALSE, FALSE, 0);
+ gtk_revealer_set_transition_type(GTK_REVEALER(revealer), GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN);
+ gtk_widget_set_valign(GTK_WIDGET(priv->overlay_ftb_overlay), GTK_ALIGN_START);
+ }
+
+
+ gtk_container_add(GTK_CONTAINER(revealer), priv->floating_toolbar_widget);
+ gtk_widget_set_halign(GTK_WIDGET(revealer), GTK_ALIGN_CENTER);
+ gtk_widget_set_valign(GTK_WIDGET(revealer), GTK_ALIGN_START);
+
+ priv->revealer = revealer;
+
+ GtkWidget *fr;
+ fr = gtk_frame_new(NULL);
+ gtk_container_add(GTK_CONTAINER(priv->overlay_ftb_overlay), fr );
+ gtk_container_add(GTK_CONTAINER(fr), vbox);
+
+ gtk_widget_show(vbox);
+ gtk_widget_show(revealer);
+ gtk_widget_show(handle);
+ gtk_widget_show(priv->overlay_ftb_overlay);
+ gtk_widget_show(fr);
+
+ if (remmina_pref.floating_toolbar_placement == FLOATING_TOOLBAR_PLACEMENT_BOTTOM) {
+ gtk_widget_set_name(fr, "ftbbox-lower");
+ }else {
+ gtk_widget_set_name(fr, "ftbbox-upper");
+ }
+
+ gtk_overlay_add_overlay(GTK_OVERLAY(priv->overlay), priv->overlay_ftb_overlay);
+
+ remmina_connection_holder_floating_toolbar_show(cnnhld, TRUE);
+
+ g_signal_connect(G_OBJECT(priv->overlay_ftb_overlay), "enter-notify-event", G_CALLBACK(remmina_connection_holder_floating_toolbar_on_enter), cnnhld);
+ g_signal_connect(G_OBJECT(priv->overlay_ftb_overlay), "scroll-event", G_CALLBACK(remmina_connection_holder_floating_toolbar_on_scroll), cnnhld);
+ gtk_widget_add_events(GTK_WIDGET(priv->overlay_ftb_overlay), GDK_SCROLL_MASK);
+
+ /* Add drag and drop capabilities to the source */
+ gtk_drag_source_set(GTK_WIDGET(priv->overlay_ftb_overlay), GDK_BUTTON1_MASK,
+ dnd_targets_ftb, sizeof dnd_targets_ftb / sizeof *dnd_targets_ftb, GDK_ACTION_MOVE);
+ g_signal_connect_after(GTK_WIDGET(priv->overlay_ftb_overlay), "drag-begin", G_CALLBACK(remmina_connection_window_ftb_drag_begin), cnnhld);
+}
+
+
+static gboolean remmina_connection_window_ftb_drag_drop(GtkWidget *widget, GdkDragContext *context,
+ gint x, gint y, guint time, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ GtkAllocation wa;
+ gint new_floating_toolbar_placement;
+ RemminaConnectionHolder* cnnhld;
+
+ cnnhld = (RemminaConnectionHolder*)user_data;
+
+ gtk_widget_get_allocation(widget, &wa);
+
+ if (y >= wa.height / 2) {
+ new_floating_toolbar_placement = FLOATING_TOOLBAR_PLACEMENT_BOTTOM;
+ }else {
+ new_floating_toolbar_placement = FLOATING_TOOLBAR_PLACEMENT_TOP;
+ }
+
+ gtk_drag_finish(context, TRUE, TRUE, time);
+
+ if (new_floating_toolbar_placement != remmina_pref.floating_toolbar_placement) {
+ /* Destroy and recreate the FTB */
+ remmina_pref.floating_toolbar_placement = new_floating_toolbar_placement;
+ remmina_pref_save();
+ remmina_connection_holder_create_overlay_ftb_overlay(cnnhld);
+ }
+
+ return TRUE;
+
+}
+
+static void remmina_connection_window_ftb_drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+
+ cairo_surface_t *surface;
+ cairo_t *cr;
+ GtkAllocation wa;
+ double dashes[] = { 10 };
+
+ gtk_widget_get_allocation(widget, &wa);
+
+ surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, wa.width, wa.height);
+ cr = cairo_create(surface);
+ cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
+ cairo_set_line_width(cr, 2);
+ cairo_set_dash(cr, dashes, 1, 0 );
+ cairo_rectangle(cr, 0, 0, wa.width, wa.height);
+ cairo_stroke(cr);
+ cairo_destroy(cr);
+
+ gtk_drag_set_icon_surface(context, surface);
+
+}
+
+
+#endif
+
+static void remmina_connection_holder_create_fullscreen(RemminaConnectionHolder* cnnhld, RemminaConnectionObject* cnnobj,
+ gint view_mode)
+{
+ TRACE_CALL(__func__);
+ GtkWidget* window;
+ GtkWidget* oldwindow;
+ GtkWidget* notebook;
+ RemminaConnectionWindowPriv* priv;
+
+ gchar* tag;
+
+ oldwindow = GTK_WIDGET(cnnhld->cnnwin);
+ window = remmina_connection_window_new_from_holder(cnnhld);
+ gtk_widget_set_name(GTK_WIDGET(window), "remmina-connection-window-fullscreen");
+ gtk_widget_realize(window);
+
+ cnnhld->cnnwin = REMMINA_CONNECTION_WINDOW(window);
+ priv = cnnhld->cnnwin->priv;
+
+ if (!view_mode)
+ view_mode = VIEWPORT_FULLSCREEN_MODE;
+
+ notebook = remmina_connection_holder_create_notebook(cnnhld);
+
+#if FLOATING_TOOLBAR_WIDGET
+ priv->overlay = gtk_overlay_new();
+ gtk_container_add(GTK_CONTAINER(window), priv->overlay);
+ gtk_container_add(GTK_CONTAINER(priv->overlay), notebook);
+ gtk_widget_show(GTK_WIDGET(priv->overlay));
+#else
+ gtk_container_add(GTK_CONTAINER(window), notebook);
+#endif
+
+ priv->notebook = notebook;
+ priv->view_mode = view_mode;
+ cnnhld->fullscreen_view_mode = view_mode;
+
+ remmina_connection_window_initialize_notebook(GTK_NOTEBOOK(notebook),
+ (oldwindow ? GTK_NOTEBOOK(REMMINA_CONNECTION_WINDOW(oldwindow)->priv->notebook) : NULL), cnnobj,
+ view_mode);
+
+ if (cnnobj) {
+ remmina_connection_window_update_tag(cnnhld->cnnwin, cnnobj);
+ }
+ if (oldwindow) {
+ tag = g_strdup((gchar*)g_object_get_data(G_OBJECT(oldwindow), "tag"));
+ g_object_set_data_full(G_OBJECT(cnnhld->cnnwin), "tag", tag, (GDestroyNotify)g_free);
+ gtk_widget_destroy(oldwindow);
+ }
+
+ /* Create the floating toolbar */
+#if FLOATING_TOOLBAR_WIDGET
+ if (remmina_pref.fullscreen_toolbar_visibility != FLOATING_TOOLBAR_VISIBILITY_DISABLE) {
+ remmina_connection_holder_create_overlay_ftb_overlay(cnnhld);
+ /* Add drag and drop capabilities to the drop/dest target for floating toolbar */
+ gtk_drag_dest_set(GTK_WIDGET(priv->overlay), GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT,
+ dnd_targets_ftb, sizeof dnd_targets_ftb / sizeof *dnd_targets_ftb, GDK_ACTION_MOVE);
+ gtk_drag_dest_set_track_motion(GTK_WIDGET(priv->notebook), TRUE);
+ g_signal_connect(GTK_WIDGET(priv->overlay), "drag-drop", G_CALLBACK(remmina_connection_window_ftb_drag_drop), cnnhld);
+ }
+#else
+ if (remmina_pref.fullscreen_toolbar_visibility != FLOATING_TOOLBAR_VISIBILITY_DISABLE) {
+ remmina_connection_holder_create_floating_toolbar(cnnhld, view_mode);
+ remmina_connection_holder_update_toolbar(cnnhld);
+
+ g_signal_connect(G_OBJECT(priv->floating_toolbar_window), "enter-notify-event", G_CALLBACK(remmina_connection_holder_floating_toolbar_on_enter), cnnhld);
+ g_signal_connect(G_OBJECT(priv->floating_toolbar_window), "scroll-event", G_CALLBACK(remmina_connection_holder_floating_toolbar_on_scroll), cnnhld);
+ gtk_widget_add_events(GTK_WIDGET(priv->floating_toolbar_window), GDK_SCROLL_MASK);
+ }
+#endif
+
+ remmina_connection_holder_check_resize(cnnhld);
+
+ gtk_widget_show(window);
+
+ /* Put the window in fullscreen after it is mapped to have it appear on the same monitor */
+ g_signal_connect(G_OBJECT(window), "map-event", G_CALLBACK(remmina_connection_window_go_fullscreen), (gpointer)cnnhld);
+}
+
+static gboolean remmina_connection_window_hostkey_func(RemminaProtocolWidget* gp, guint keyval, gboolean release,
+ RemminaConnectionHolder* cnnhld)
+{
+ TRACE_CALL(__func__);
+ DECLARE_CNNOBJ_WITH_RETURN(FALSE);
+ RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
+ const RemminaProtocolFeature* feature;
+ gint i;
+
+ if (release) {
+ if (remmina_pref.hostkey && keyval == remmina_pref.hostkey) {
+ cnnhld->hostkey_activated = FALSE;
+ if (cnnhld->hostkey_used) {
+ /* hostkey pressed + something else */
+ return TRUE;
+ }
+ }
+ /* If hostkey is released without pressing other keys, we should execute the
+ * shortcut key which is the same as hostkey. Be default, this is grab/ungrab
+ * keyboard */
+ else if (cnnhld->hostkey_activated) {
+ /* Trap all key releases when hostkey is pressed */
+ /* hostkey pressed + something else */
+ return TRUE;
+
+ }else {
+ /* Any key pressed, no hostkey */
+ return FALSE;
+ }
+ }else if (remmina_pref.hostkey && keyval == remmina_pref.hostkey) {
+ /** @todo Add callback for hostname transparent overlay #832 */
+ cnnhld->hostkey_activated = TRUE;
+ cnnhld->hostkey_used = FALSE;
+ return TRUE;
+ }else if (!cnnhld->hostkey_activated) {
+ /* Any key pressed, no hostkey */
+ return FALSE;
+ }
+
+ cnnhld->hostkey_used = TRUE;
+ keyval = gdk_keyval_to_lower(keyval);
+ if (keyval == GDK_KEY_Up || keyval == GDK_KEY_Down
+ || keyval == GDK_KEY_Left || keyval == GDK_KEY_Right) {
+ DECLARE_CNNOBJ_WITH_RETURN(FALSE);
+ GtkAdjustment *adjust;
+ int pos;
+
+ if (cnnobj->connected && GTK_IS_SCROLLED_WINDOW(cnnobj->scrolled_container)) {
+ if (keyval == GDK_KEY_Up || keyval == GDK_KEY_Down)
+ adjust = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(cnnobj->scrolled_container));
+ else
+ adjust = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(cnnobj->scrolled_container));
+
+ if (keyval == GDK_KEY_Up || keyval == GDK_KEY_Left)
+ pos = 0;
+ else
+ pos = gtk_adjustment_get_upper(adjust);
+
+ gtk_adjustment_set_value(adjust, pos);
+ if (keyval == GDK_KEY_Up || keyval == GDK_KEY_Down)
+ gtk_scrolled_window_set_vadjustment(GTK_SCROLLED_WINDOW(cnnobj->scrolled_container), adjust);
+ else
+ gtk_scrolled_window_set_hadjustment(GTK_SCROLLED_WINDOW(cnnobj->scrolled_container), adjust);
+ }else if (REMMINA_IS_SCROLLED_VIEWPORT(cnnobj->scrolled_container)) {
+ RemminaScrolledViewport *gsv;
+ GtkWidget *child;
+ GdkWindow *gsvwin;
+ gint sz;
+ GtkAdjustment *adj;
+ gdouble value;
+
+ if (!GTK_IS_BIN(cnnobj->scrolled_container))
+ return FALSE;
+
+ gsv = REMMINA_SCROLLED_VIEWPORT(cnnobj->scrolled_container);
+ child = gtk_bin_get_child(GTK_BIN(gsv));
+ if (!GTK_IS_VIEWPORT(child))
+ return FALSE;
+
+ gsvwin = gtk_widget_get_window(GTK_WIDGET(gsv));
+ if (!gsv)
+ return FALSE;
+
+ if (keyval == GDK_KEY_Up || keyval == GDK_KEY_Down) {
+ sz = gdk_window_get_height(gsvwin) + 2; // Add 2px of black scroll border
+ adj = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(child));
+ }else {
+ sz = gdk_window_get_width(gsvwin) + 2; // Add 2px of black scroll border
+ adj = gtk_scrollable_get_hadjustment(GTK_SCROLLABLE(child));
+ }
+
+ if (keyval == GDK_KEY_Up || keyval == GDK_KEY_Left) {
+ value = 0;
+ }else {
+ value = gtk_adjustment_get_upper(GTK_ADJUSTMENT(adj)) - (gdouble)sz + 2.0;
+ }
+
+ gtk_adjustment_set_value(GTK_ADJUSTMENT(adj), value);
+ }
+ }
+
+ if (keyval == remmina_pref.shortcutkey_fullscreen) {
+ switch (priv->view_mode) {
+ case SCROLLED_WINDOW_MODE:
+ remmina_connection_holder_create_fullscreen(
+ cnnhld,
+ NULL,
+ cnnhld->fullscreen_view_mode ?
+ cnnhld->fullscreen_view_mode : VIEWPORT_FULLSCREEN_MODE);
+ break;
+ case SCROLLED_FULLSCREEN_MODE:
+ case VIEWPORT_FULLSCREEN_MODE:
+ remmina_connection_holder_create_scrolled(cnnhld, NULL);
+ break;
+ default:
+ break;
+ }
+ }else if (keyval == remmina_pref.shortcutkey_autofit) {
+ if (priv->toolitem_autofit && gtk_widget_is_sensitive(GTK_WIDGET(priv->toolitem_autofit))) {
+ remmina_connection_holder_toolbar_autofit(GTK_WIDGET(gp), cnnhld);
+ }
+ }else if (keyval == remmina_pref.shortcutkey_nexttab) {
+ i = gtk_notebook_get_current_page(GTK_NOTEBOOK(priv->notebook)) + 1;
+ if (i >= gtk_notebook_get_n_pages(GTK_NOTEBOOK(priv->notebook)))
+ i = 0;
+ gtk_notebook_set_current_page(GTK_NOTEBOOK(priv->notebook), i);
+ }else if (keyval == remmina_pref.shortcutkey_prevtab) {
+ i = gtk_notebook_get_current_page(GTK_NOTEBOOK(priv->notebook)) - 1;
+ if (i < 0)
+ i = gtk_notebook_get_n_pages(GTK_NOTEBOOK(priv->notebook)) - 1;
+ gtk_notebook_set_current_page(GTK_NOTEBOOK(priv->notebook), i);
+ }else if (keyval == remmina_pref.shortcutkey_scale) {
+ if (gtk_widget_is_sensitive(GTK_WIDGET(priv->toolitem_scale))) {
+ gtk_toggle_tool_button_set_active(
+ GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_scale),
+ !gtk_toggle_tool_button_get_active(
+ GTK_TOGGLE_TOOL_BUTTON(
+ priv->toolitem_scale)));
+ }
+ }else if (keyval == remmina_pref.shortcutkey_grab) {
+ gtk_toggle_tool_button_set_active(
+ GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_grab),
+ !gtk_toggle_tool_button_get_active(
+ GTK_TOGGLE_TOOL_BUTTON(
+ priv->toolitem_grab)));
+ }else if (keyval == remmina_pref.shortcutkey_minimize) {
+ remmina_connection_holder_toolbar_minimize(GTK_WIDGET(gp),
+ cnnhld);
+ }else if (keyval == remmina_pref.shortcutkey_viewonly) {
+ remmina_file_set_int(cnnobj->remmina_file, "viewonly",
+ ( remmina_file_get_int(cnnobj->remmina_file, "viewonly", 0 )
+ == 0 ) ? 1 : 0 );
+ }else if (keyval == remmina_pref.shortcutkey_screenshot) {
+ remmina_connection_holder_toolbar_screenshot(GTK_WIDGET(gp),
+ cnnhld);
+ }else if (keyval == remmina_pref.shortcutkey_disconnect) {
+ remmina_connection_holder_disconnect_current_page(cnnhld);
+ }else if (keyval == remmina_pref.shortcutkey_toolbar) {
+ if (priv->view_mode == SCROLLED_WINDOW_MODE) {
+ remmina_pref.hide_connection_toolbar =
+ !remmina_pref.hide_connection_toolbar;
+ remmina_connection_holder_showhide_toolbar( cnnhld, TRUE);
+ }
+ }else {
+ for (feature =
+ remmina_protocol_widget_get_features(
+ REMMINA_PROTOCOL_WIDGET(
+ cnnobj->proto));
+ feature && feature->type;
+ feature++) {
+ if (feature->type
+ == REMMINA_PROTOCOL_FEATURE_TYPE_TOOL
+ && GPOINTER_TO_UINT(
+ feature->opt3)
+ == keyval) {
+ remmina_protocol_widget_call_feature_by_ref(
+ REMMINA_PROTOCOL_WIDGET(
+ cnnobj->proto),
+ feature);
+ break;
+ }
+ }
+ }
+ cnnhld->hostkey_activated = FALSE;
+ /* Trap all key presses when hostkey is pressed */
+ return TRUE;
+}
+
+static RemminaConnectionWindow* remmina_connection_window_find(RemminaFile* remminafile)
+{
+ TRACE_CALL(__func__);
+ const gchar* tag;
+
+ switch (remmina_pref.tab_mode) {
+ case REMMINA_TAB_BY_GROUP:
+ tag = remmina_file_get_string(remminafile, "group");
+ break;
+ case REMMINA_TAB_BY_PROTOCOL:
+ tag = remmina_file_get_string(remminafile, "protocol");
+ break;
+ case REMMINA_TAB_ALL:
+ tag = NULL;
+ break;
+ case REMMINA_TAB_NONE:
+ default:
+ return NULL;
+ }
+ return REMMINA_CONNECTION_WINDOW(remmina_widget_pool_find(REMMINA_TYPE_CONNECTION_WINDOW, tag));
+}
+
+static gboolean remmina_connection_object_delayed_window_present(gpointer user_data)
+{
+ RemminaConnectionObject* cnnobj = (RemminaConnectionObject*)user_data;
+ if (cnnobj && cnnobj->cnnhld && cnnobj->cnnhld->cnnwin)
+ gtk_window_present_with_time(GTK_WINDOW(cnnobj->cnnhld->cnnwin), cnnobj->open_from_file_event_time);
+ return FALSE;
+}
+
+static void remmina_connection_object_on_connect(RemminaProtocolWidget* gp, RemminaConnectionObject* cnnobj)
+{
+ TRACE_CALL(__func__);
+ RemminaConnectionWindow* cnnwin;
+ RemminaConnectionHolder* cnnhld;
+ GtkWidget* tab;
+ gint i;
+
+ gchar *last_success;
+ GDateTime *date = g_date_time_new_now_utc();
+
+ /* This signal handler is called by a plugin where it's correctly connected
+ * (and authenticated) */
+
+ if (!cnnobj->cnnhld) {
+ cnnwin = remmina_connection_window_find(cnnobj->remmina_file);
+ if (cnnwin) {
+ cnnhld = cnnwin->priv->cnnhld;
+ }else {
+ cnnhld = g_new0(RemminaConnectionHolder, 1);
+ }
+ cnnobj->cnnhld = cnnhld;
+ }else {
+ cnnhld = cnnobj->cnnhld;
+ }
+
+ cnnobj->connected = TRUE;
+
+ remmina_protocol_widget_set_hostkey_func(REMMINA_PROTOCOL_WIDGET(cnnobj->proto),
+ (RemminaHostkeyFunc)remmina_connection_window_hostkey_func, cnnhld);
+
+ /** Remember recent list for quick connect, and save the current date
+ * in the last_used field.
+ */
+ last_success = g_strdup_printf("%d%02d%02d",
+ g_date_time_get_year(date),
+ g_date_time_get_month(date),
+ g_date_time_get_day_of_month(date));
+ if (remmina_file_get_filename(cnnobj->remmina_file) == NULL) {
+ remmina_pref_add_recent(remmina_file_get_string(cnnobj->remmina_file, "protocol"),
+ remmina_file_get_string(cnnobj->remmina_file, "server"));
+ }
+ remmina_file_set_string (cnnobj->remmina_file, "last_success", last_success);
+
+ /* Save credentials */
+ remmina_file_save(cnnobj->remmina_file);
+
+ if (!cnnhld->cnnwin) {
+ i = remmina_file_get_int(cnnobj->remmina_file, "viewmode", 0);
+ switch (i) {
+ case SCROLLED_FULLSCREEN_MODE:
+ case VIEWPORT_FULLSCREEN_MODE:
+ remmina_connection_holder_create_fullscreen(cnnhld, cnnobj, i);
+ break;
+ case SCROLLED_WINDOW_MODE:
+ default:
+ remmina_connection_holder_create_scrolled(cnnhld, cnnobj);
+ break;
+ }
+ }else {
+ tab = remmina_connection_object_create_tab(cnnobj);
+ i = remmina_connection_object_append_page(cnnobj, GTK_NOTEBOOK(cnnhld->cnnwin->priv->notebook), tab,
+ cnnhld->cnnwin->priv->view_mode);
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ gtk_widget_reparent(cnnobj->viewport, cnnobj->scrolled_container);
+ G_GNUC_END_IGNORE_DEPRECATIONS
+
+ gtk_notebook_set_current_page(GTK_NOTEBOOK(cnnhld->cnnwin->priv->notebook), i);
+
+ /* Present the windows after a delay so GTK can realize and show its objects
+ * before calling gtk_window_present_with_time() */
+ g_timeout_add(200, remmina_connection_object_delayed_window_present, (gpointer)cnnobj);
+
+ }
+
+#if FLOATING_TOOLBAR_WIDGET
+ if (cnnhld->cnnwin->priv->floating_toolbar_widget) {
+ gtk_widget_show(cnnhld->cnnwin->priv->floating_toolbar_widget);
+ }
+#else
+ if (cnnhld->cnnwin->priv->floating_toolbar_window) {
+ gtk_widget_show(cnnhld->cnnwin->priv->floating_toolbar_window);
+ }
+#endif
+
+}
+
+static void cb_autoclose_widget(GtkWidget *widget)
+{
+ gtk_widget_destroy(widget);
+ remmina_application_condexit(REMMINA_CONDEXIT_ONDISCONNECT);
+}
+
+static void remmina_connection_object_on_disconnect(RemminaProtocolWidget* gp, RemminaConnectionObject* cnnobj)
+{
+ TRACE_CALL(__func__);
+ RemminaConnectionHolder* cnnhld = cnnobj->cnnhld;
+ GtkWidget* dialog;
+ GtkWidget* pparent;
+
+
+ /* Detach the protocol widget from the notebook now, or we risk that a
+ * window delete will destroy cnnobj->proto before we complete disconnection.
+ */
+ pparent = gtk_widget_get_parent(cnnobj->proto);
+ if (pparent != NULL) {
+ g_object_ref(cnnobj->proto);
+ gtk_container_remove(GTK_CONTAINER(pparent), cnnobj->proto);
+ }
+
+ cnnobj->connected = FALSE;
+
+ if (cnnhld && remmina_pref.save_view_mode) {
+ if (cnnhld->cnnwin) {
+ remmina_file_set_int(cnnobj->remmina_file, "viewmode", cnnhld->cnnwin->priv->view_mode);
+ }
+ remmina_file_save(cnnobj->remmina_file);
+ }
+
+ if (remmina_protocol_widget_has_error(gp)) {
+ dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+ remmina_protocol_widget_get_error_message(gp), NULL);
+ g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(cb_autoclose_widget), NULL);
+ gtk_widget_show(dialog);
+ remmina_widget_pool_register(dialog);
+ }
+
+ if (cnnhld && cnnhld->cnnwin && cnnobj->scrolled_container) {
+ gtk_notebook_remove_page(
+ GTK_NOTEBOOK(cnnhld->cnnwin->priv->notebook),
+ gtk_notebook_page_num(GTK_NOTEBOOK(cnnhld->cnnwin->priv->notebook),
+ cnnobj->scrolled_container));
+ }
+
+ cnnobj->remmina_file = NULL;
+ g_free(cnnobj);
+
+ remmina_application_condexit(REMMINA_CONDEXIT_ONDISCONNECT);
+}
+
+static void remmina_connection_object_on_desktop_resize(RemminaProtocolWidget* gp, RemminaConnectionObject* cnnobj)
+{
+ TRACE_CALL(__func__);
+ if (cnnobj->cnnhld && cnnobj->cnnhld->cnnwin && cnnobj->cnnhld->cnnwin->priv->view_mode != SCROLLED_WINDOW_MODE) {
+ remmina_connection_holder_check_resize(cnnobj->cnnhld);
+ }
+}
+
+static void remmina_connection_object_on_update_align(RemminaProtocolWidget* gp, RemminaConnectionObject* cnnobj)
+{
+ TRACE_CALL(__func__);
+ remmina_protocol_widget_update_alignment(cnnobj);
+}
+
+static void remmina_connection_object_on_unlock_dynres(RemminaProtocolWidget* gp, RemminaConnectionObject* cnnobj)
+{
+ TRACE_CALL(__func__);
+ cnnobj->dynres_unlocked = TRUE;
+ remmina_connection_holder_update_toolbar(cnnobj->cnnhld);
+}
+
+gboolean remmina_connection_window_open_from_filename(const gchar* filename)
+{
+ TRACE_CALL(__func__);
+ RemminaFile* remminafile;
+ GtkWidget* dialog;
+
+ remminafile = remmina_file_manager_load_file(filename);
+ if (remminafile) {
+ remmina_connection_window_open_from_file(remminafile);
+ return TRUE;
+ }else {
+ dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
+ _("File %s not found."), filename);
+ g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(gtk_widget_destroy), NULL);
+ gtk_widget_show(dialog);
+ remmina_widget_pool_register(dialog);
+ return FALSE;
+ }
+}
+
+void remmina_connection_window_open_from_file(RemminaFile* remminafile)
+{
+ TRACE_CALL(__func__);
+ remmina_connection_window_open_from_file_full(remminafile, NULL, NULL, NULL);
+}
+
+GtkWidget* remmina_connection_window_open_from_file_full(RemminaFile* remminafile, GCallback disconnect_cb, gpointer data, guint* handler)
+{
+ TRACE_CALL(__func__);
+ RemminaConnectionObject* cnnobj;
+ GtkWidget* protocolwidget;
+
+ cnnobj = g_new0(RemminaConnectionObject, 1);
+ cnnobj->remmina_file = remminafile;
+
+ /* Save the time of the event which caused the file open, so we can
+ * use gtk_window_present_with_time() later */
+ cnnobj->open_from_file_event_time = gtk_get_current_event_time();
+
+ /* Create the RemminaProtocolWidget */
+ protocolwidget = cnnobj->proto = remmina_protocol_widget_new();
+
+ /* Set default remote desktop size in the profile, so the plugins can query
+ * protocolwidget and know WxH that the user put on the profile settings */
+ remmina_protocol_widget_update_remote_resolution((RemminaProtocolWidget*)protocolwidget,
+ remmina_file_get_int(remminafile, "resolution_width", -1),
+ remmina_file_get_int(remminafile, "resolution_height", -1)
+ );
+
+ /* Set a name for the widget, for CSS selector */
+ gtk_widget_set_name(GTK_WIDGET(cnnobj->proto), "remmina-protocol-widget");
+
+ gtk_widget_set_halign(GTK_WIDGET(cnnobj->proto), GTK_ALIGN_FILL);
+ gtk_widget_set_valign(GTK_WIDGET(cnnobj->proto), GTK_ALIGN_FILL);
+
+ if (data) {
+ g_object_set_data(G_OBJECT(cnnobj->proto), "user-data", data);
+ }
+
+ gtk_widget_show(cnnobj->proto);
+ g_signal_connect(G_OBJECT(cnnobj->proto), "connect", G_CALLBACK(remmina_connection_object_on_connect), cnnobj);
+ if (disconnect_cb) {
+ *handler = g_signal_connect(G_OBJECT(cnnobj->proto), "disconnect", disconnect_cb, data);
+ }
+ g_signal_connect(G_OBJECT(cnnobj->proto), "disconnect", G_CALLBACK(remmina_connection_object_on_disconnect), cnnobj);
+ g_signal_connect(G_OBJECT(cnnobj->proto), "desktop-resize", G_CALLBACK(remmina_connection_object_on_desktop_resize),
+ cnnobj);
+ g_signal_connect(G_OBJECT(cnnobj->proto), "update-align", G_CALLBACK(remmina_connection_object_on_update_align),
+ cnnobj);
+ g_signal_connect(G_OBJECT(cnnobj->proto), "unlock-dynres", G_CALLBACK(remmina_connection_object_on_unlock_dynres),
+ cnnobj);
+
+ /* Create the viewport to make the RemminaProtocolWidget scrollable */
+ cnnobj->viewport = gtk_viewport_new(NULL, NULL);
+ gtk_widget_set_name(cnnobj->viewport, "remmina-cw-viewport");
+ gtk_widget_show(cnnobj->viewport);
+ gtk_container_set_border_width(GTK_CONTAINER(cnnobj->viewport), 0);
+ gtk_viewport_set_shadow_type(GTK_VIEWPORT(cnnobj->viewport), GTK_SHADOW_NONE);
+
+ /* Determine whether the plugin can scale or not. If the plugin can scale and we do
+ * not want to expand, then we add a GtkAspectFrame to maintain aspect ratio during scaling */
+ cnnobj->plugin_can_scale = remmina_plugin_manager_query_feature_by_type(REMMINA_PLUGIN_TYPE_PROTOCOL,
+ remmina_file_get_string(remminafile, "protocol"),
+ REMMINA_PROTOCOL_FEATURE_TYPE_SCALE);
+
+ cnnobj->aspectframe = NULL;
+ gtk_container_add(GTK_CONTAINER(cnnobj->viewport), cnnobj->proto);
+
+ cnnobj->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_widget_realize(cnnobj->window);
+ gtk_container_add(GTK_CONTAINER(cnnobj->window), cnnobj->viewport);
+
+ if (!remmina_pref.save_view_mode)
+ remmina_file_set_int(cnnobj->remmina_file, "viewmode", remmina_pref.default_mode);
+
+ remmina_protocol_widget_open_connection(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), remminafile);
+
+ return protocolwidget;
+}
+
+void remmina_connection_window_set_delete_confirm_mode(RemminaConnectionWindow* cnnwin, RemminaConnectionWindowOnDeleteConfirmMode mode)
+{
+ cnnwin->priv->on_delete_confirm_mode = mode;
+}
diff --git a/src/remmina_connection_window.h b/src/remmina_connection_window.h
new file mode 100644
index 000000000..945b463e0
--- /dev/null
+++ b/src/remmina_connection_window.h
@@ -0,0 +1,82 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009 - Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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.
+ *
+ */
+
+#pragma once
+
+#include "remmina_file.h"
+
+G_BEGIN_DECLS
+
+#define REMMINA_TYPE_CONNECTION_WINDOW (remmina_connection_window_get_type())
+#define REMMINA_CONNECTION_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), REMMINA_TYPE_CONNECTION_WINDOW, RemminaConnectionWindow))
+#define REMMINA_CONNECTION_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), REMMINA_TYPE_CONNECTION_WINDOW, RemminaConnectionWindowClass))
+#define REMMINA_IS_CONNECTION_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), REMMINA_TYPE_CONNECTION_WINDOW))
+#define REMMINA_IS_CONNECTION_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), REMMINA_TYPE_CONNECTION_WINDOW))
+#define REMMINA_CONNECTION_WINDOW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), REMMINA_TYPE_CONNECTION_WINDOW, RemminaConnectionWindowClass))
+
+typedef struct _RemminaConnectionWindowPriv RemminaConnectionWindowPriv;
+
+typedef struct _RemminaConnectionWindow {
+ GtkWindow window;
+ RemminaConnectionWindowPriv* priv;
+} RemminaConnectionWindow;
+
+typedef struct _RemminaConnectionWindowClass {
+ GtkWindowClass parent_class;
+ void (*toolbar_place)(RemminaConnectionWindow * gp);
+} RemminaConnectionWindowClass;
+
+typedef enum {
+ REMMINA_CONNECTION_WINDOW_ONDELETE_CONFIRM_IF_2_OR_MORE = 0,
+ REMMINA_CONNECTION_WINDOW_ONDELETE_NOCONFIRM = 1
+
+} RemminaConnectionWindowOnDeleteConfirmMode;
+
+GType remmina_connection_window_get_type(void)
+G_GNUC_CONST;
+
+/* Open a new connection window for a .remmina file */
+gboolean remmina_connection_window_open_from_filename(const gchar* filename);
+/* Open a new connection window for a given RemminaFile struct. The struct will be freed after the call */
+void remmina_connection_window_open_from_file(RemminaFile* remminafile);
+gboolean remmina_connection_window_delete(RemminaConnectionWindow* cnnwin);
+void remmina_connection_window_set_delete_confirm_mode(RemminaConnectionWindow* cnnwin, RemminaConnectionWindowOnDeleteConfirmMode mode);
+GtkWidget* remmina_connection_window_open_from_file_full(RemminaFile* remminafile, GCallback disconnect_cb, gpointer data,
+ guint* handler);
+
+G_END_DECLS
+
+
diff --git a/src/remmina_crypt.c b/src/remmina_crypt.c
new file mode 100644
index 000000000..97f8874cb
--- /dev/null
+++ b/src/remmina_crypt.c
@@ -0,0 +1,181 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009 - Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 "config.h"
+#include <glib.h>
+#ifdef HAVE_LIBGCRYPT
+#include <gcrypt.h>
+#endif
+#include "remmina_pref.h"
+#include "remmina_crypt.h"
+#include "remmina/remmina_trace_calls.h"
+
+#ifdef HAVE_LIBGCRYPT
+
+static gboolean remmina_crypt_init(gcry_cipher_hd_t *phd)
+{
+ TRACE_CALL(__func__);
+ guchar* secret;
+ gcry_error_t err;
+ gsize secret_len;
+
+ secret = g_base64_decode(remmina_pref.secret, &secret_len);
+
+ if (secret_len < 32) {
+ g_print("secret corrupted\n");
+ g_free(secret);
+ return FALSE;
+ }
+
+ err = gcry_cipher_open(phd, GCRY_CIPHER_3DES, GCRY_CIPHER_MODE_CBC, 0);
+
+ if (err) {
+ g_print("gcry_cipher_open failure: %s\n", gcry_strerror(err));
+ g_free(secret);
+ return FALSE;
+ }
+
+ err = gcry_cipher_setkey((*phd), secret, 24);
+
+ if (err) {
+ g_print("gcry_cipher_setkey failure: %s\n", gcry_strerror(err));
+ g_free(secret);
+ gcry_cipher_close((*phd));
+ return FALSE;
+ }
+
+ err = gcry_cipher_setiv((*phd), secret + 24, 8);
+
+ if (err) {
+ g_print("gcry_cipher_setiv failure: %s\n", gcry_strerror(err));
+ g_free(secret);
+ gcry_cipher_close((*phd));
+ return FALSE;
+ }
+
+ g_free(secret);
+
+ return TRUE;
+}
+
+gchar* remmina_crypt_encrypt(const gchar *str)
+{
+ TRACE_CALL(__func__);
+ guchar* buf;
+ gint buf_len;
+ gchar* result;
+ gcry_error_t err;
+ gcry_cipher_hd_t hd;
+
+ if (!str || str[0] == '\0')
+ return NULL;
+
+ if (!remmina_crypt_init(&hd))
+ return NULL;
+
+ buf_len = strlen(str);
+ /* Pack to 64bit block size, and make sure it's always 0-terminated */
+ buf_len += 8 - buf_len % 8;
+ buf = (guchar*)g_malloc(buf_len);
+ memset(buf, 0, buf_len);
+ memcpy(buf, str, strlen(str));
+
+ err = gcry_cipher_encrypt(hd, buf, buf_len, NULL, 0);
+
+ if (err) {
+ g_print("gcry_cipher_encrypt failure: %s\n", gcry_strerror(err));
+ g_free(buf);
+ gcry_cipher_close(hd);
+ return NULL;
+ }
+
+ result = g_base64_encode(buf, buf_len);
+
+ g_free(buf);
+ gcry_cipher_close(hd);
+
+ return result;
+}
+
+gchar* remmina_crypt_decrypt(const gchar *str)
+{
+ TRACE_CALL(__func__);
+ guchar* buf;
+ gsize buf_len;
+ gcry_error_t err;
+ gcry_cipher_hd_t hd;
+
+ if (!str || str[0] == '\0')
+ return NULL;
+
+ if (!remmina_crypt_init(&hd))
+ return NULL;
+
+ buf = g_base64_decode(str, &buf_len);
+
+ err = gcry_cipher_decrypt(hd, buf, buf_len, NULL, 0);
+
+ if (err) {
+ g_print("gcry_cipher_decrypt failure: %s\n", gcry_strerror(err));
+ g_free(buf);
+ gcry_cipher_close(hd);
+ return NULL;
+ }
+
+ gcry_cipher_close(hd);
+
+ /* Just in case */
+ buf[buf_len - 1] = '\0';
+
+ return (gchar*)buf;
+}
+
+#else
+
+gchar* remmina_crypt_encrypt(const gchar *str)
+{
+ TRACE_CALL(__func__);
+ return NULL;
+}
+
+gchar* remmina_crypt_decrypt(const gchar *str)
+{
+ TRACE_CALL(__func__);
+ return NULL;
+}
+
+#endif
+
diff --git a/src/remmina_crypt.h b/src/remmina_crypt.h
new file mode 100644
index 000000000..03384cc91
--- /dev/null
+++ b/src/remmina_crypt.h
@@ -0,0 +1,45 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009 - Vic Lee
+ * Copyright (C) 2017-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.
+ *
+ */
+
+#pragma once
+
+G_BEGIN_DECLS
+
+gchar* remmina_crypt_encrypt(const gchar* str);
+gchar* remmina_crypt_decrypt(const gchar* str);
+
+G_END_DECLS
+
+
diff --git a/src/remmina_exec.c b/src/remmina_exec.c
new file mode 100644
index 000000000..59c3be8a2
--- /dev/null
+++ b/src/remmina_exec.c
@@ -0,0 +1,243 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 "config.h"
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <stdlib.h>
+#include "remmina_main.h"
+#include "remmina_widget_pool.h"
+#include "remmina_pref_dialog.h"
+#include "remmina_file.h"
+#include "remmina_pref.h"
+#include "remmina_file_editor.h"
+#include "remmina_connection_window.h"
+#include "remmina_about.h"
+#include "remmina_plugin_manager.h"
+#include "remmina_exec.h"
+#include "remmina_icon.h"
+#include "remmina/remmina_trace_calls.h"
+
+#ifdef SNAP_BUILD
+# define ISSNAP "- SNAP Build -"
+#else
+# define ISSNAP "-"
+#endif
+
+static gboolean cb_closewidget(GtkWidget *widget, gpointer data)
+{
+ TRACE_CALL(__func__);
+ /* The correct way to close a remmina_connection_window is to send
+ * it a "delete-event" signal. Simply destroying it will not close
+ * all network connections */
+ if (REMMINA_IS_CONNECTION_WINDOW(widget))
+ return remmina_connection_window_delete(REMMINA_CONNECTION_WINDOW(widget));
+ return TRUE;
+}
+
+void remmina_exec_exitremmina()
+{
+ TRACE_CALL(__func__);
+
+ /* Save main window state/position */
+ remmina_main_save_before_destroy();
+
+ /* Delete all widgets, main window not included */
+ remmina_widget_pool_foreach(cb_closewidget, NULL);
+
+ /* Remove systray menu */
+ remmina_icon_destroy();
+
+ /* Exit from Remmina */
+ g_application_quit(g_application_get_default());
+}
+
+static gboolean disable_remmina_connection_window_delete_confirm_cb(GtkWidget *widget, gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaConnectionWindow *rcw;
+
+ if (REMMINA_IS_CONNECTION_WINDOW(widget)) {
+ rcw = (RemminaConnectionWindow*)widget;
+ remmina_connection_window_set_delete_confirm_mode(rcw, REMMINA_CONNECTION_WINDOW_ONDELETE_NOCONFIRM);
+ }
+ return TRUE;
+}
+
+void remmina_application_condexit(RemminaCondExitType why)
+{
+ TRACE_CALL(__func__);
+
+ /* Exit remmina only if there are no interesting windows left:
+ * no main window, no systray menu, no connection window.
+ * This function is usually called after a disconnection */
+
+ switch (why) {
+ case REMMINA_CONDEXIT_ONDISCONNECT:
+ // A connection has disconnected, should we exit remmina ?
+ if (remmina_widget_pool_count() < 1 && !remmina_main_get_window() && !remmina_icon_is_available())
+ remmina_exec_exitremmina();
+ break;
+ case REMMINA_CONDEXIT_ONMAINWINDELETE:
+ // Main window has been deleted
+ if (remmina_widget_pool_count() < 1 && !remmina_icon_is_available())
+ remmina_exec_exitremmina();
+ break;
+ case REMMINA_CONDEXIT_ONQUIT:
+ // Quit command has been sent from main window or appindicator/systray menu
+ // quit means QUIT.
+ remmina_widget_pool_foreach(disable_remmina_connection_window_delete_confirm_cb, NULL);
+ remmina_exec_exitremmina();
+ break;
+ }
+}
+
+void remmina_exec_command(RemminaCommandType command, const gchar* data)
+{
+ TRACE_CALL(__func__);
+ gchar* s1;
+ gchar* s2;
+ GtkWidget* widget;
+ GtkWindow* mainwindow;
+ GtkDialog* prefdialog;
+ RemminaEntryPlugin* plugin;
+
+ switch (command) {
+ case REMMINA_COMMAND_MAIN:
+ mainwindow = remmina_main_get_window();
+ if (mainwindow) {
+ gtk_window_present(mainwindow);
+ gtk_window_deiconify(GTK_WINDOW(mainwindow));
+ }else {
+ widget = remmina_main_new();
+ gtk_widget_show(widget);
+ }
+ break;
+
+ case REMMINA_COMMAND_PREF:
+ prefdialog = remmina_pref_dialog_get_dialog();
+ if (prefdialog) {
+ gtk_window_present(GTK_WINDOW(prefdialog));
+ gtk_window_deiconify(GTK_WINDOW(prefdialog));
+ }else {
+ /* Create a new preference dialog */
+ widget = GTK_WIDGET(remmina_pref_dialog_new(atoi(data), NULL));
+ gtk_widget_show(widget);
+ }
+ break;
+
+ case REMMINA_COMMAND_NEW:
+ s1 = (data ? strchr(data, ',') : NULL);
+ if (s1) {
+ s1 = g_strdup(data);
+ s2 = strchr(s1, ',');
+ *s2++ = '\0';
+ widget = remmina_file_editor_new_full(s2, s1);
+ g_free(s1);
+ }else {
+ widget = remmina_file_editor_new_full(NULL, data);
+ }
+ gtk_widget_show(widget);
+ break;
+
+ case REMMINA_COMMAND_CONNECT:
+ /** @todo This should be a G_OPTION_ARG_FILENAME_ARRAY (^aay) so that
+ * we can implement multi profile connection:
+ * https://github.com/FreeRDP/Remmina/issues/915
+ */
+ remmina_connection_window_open_from_filename(data);
+ break;
+
+ case REMMINA_COMMAND_EDIT:
+ widget = remmina_file_editor_new_from_filename(data);
+ if (widget)
+ gtk_widget_show(widget);
+ break;
+
+ case REMMINA_COMMAND_ABOUT:
+ remmina_about_open(NULL);
+ break;
+
+ case REMMINA_COMMAND_VERSION:
+ mainwindow = remmina_main_get_window();
+ if (mainwindow) {
+ remmina_about_open(NULL);
+ }else {
+ g_print("%s %s %s (git %s)\n", g_get_application_name(), ISSNAP, VERSION, REMMINA_GIT_REVISION);
+ /* As we do not use the "handle-local-options" signal, we have to exit Remmina */
+ remmina_exec_command(REMMINA_COMMAND_EXIT, NULL);
+ }
+
+ break;
+
+ case REMMINA_COMMAND_FULL_VERSION:
+ mainwindow = remmina_main_get_window();
+ if (mainwindow) {
+ /* Show th widget with the list of plugins and versions */
+ remmina_plugin_manager_show(mainwindow);
+ }else {
+ g_print("%s %s %s (git %s)\n", g_get_application_name(), ISSNAP, VERSION, REMMINA_GIT_REVISION);
+
+ remmina_plugin_manager_show_stdout();
+ remmina_exec_command(REMMINA_COMMAND_EXIT, NULL);
+ }
+
+ break;
+
+
+ case REMMINA_COMMAND_PLUGIN:
+ plugin = (RemminaEntryPlugin*)remmina_plugin_manager_get_plugin(REMMINA_PLUGIN_TYPE_ENTRY, data);
+ if (plugin) {
+ plugin->entry_func();
+ }else {
+ widget = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+ _("Plugin %s is not registered."), data);
+ g_signal_connect(G_OBJECT(widget), "response", G_CALLBACK(gtk_widget_destroy), NULL);
+ gtk_widget_show(widget);
+ remmina_widget_pool_register(widget);
+ }
+ break;
+
+ case REMMINA_COMMAND_EXIT:
+ remmina_widget_pool_foreach(disable_remmina_connection_window_delete_confirm_cb, NULL);
+ remmina_exec_exitremmina();
+ break;
+
+ default:
+ break;
+ }
+}
+
diff --git a/src/remmina_exec.h b/src/remmina_exec.h
new file mode 100644
index 000000000..675d07a4c
--- /dev/null
+++ b/src/remmina_exec.h
@@ -0,0 +1,66 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010 Vic Lee
+ * Copyright (C) 2017-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.
+ *
+ */
+
+#pragma once
+
+G_BEGIN_DECLS
+
+typedef enum {
+ REMMINA_COMMAND_NONE = 0,
+ REMMINA_COMMAND_MAIN = 1,
+ REMMINA_COMMAND_PREF = 2,
+ REMMINA_COMMAND_NEW = 3,
+ REMMINA_COMMAND_CONNECT = 4,
+ REMMINA_COMMAND_EDIT = 5,
+ REMMINA_COMMAND_ABOUT = 6,
+ REMMINA_COMMAND_VERSION = 7,
+ REMMINA_COMMAND_FULL_VERSION = 8,
+ REMMINA_COMMAND_PLUGIN = 9,
+ REMMINA_COMMAND_EXIT = 10
+} RemminaCommandType;
+
+typedef enum {
+ REMMINA_CONDEXIT_ONDISCONNECT = 0,
+ REMMINA_CONDEXIT_ONQUIT = 1,
+ REMMINA_CONDEXIT_ONMAINWINDELETE = 2
+} RemminaCondExitType;
+
+void remmina_exec_command(RemminaCommandType command, const gchar* data);
+void remmina_exec_exitremmina(void);
+void remmina_application_condexit(RemminaCondExitType why);
+
+G_END_DECLS
+
+
diff --git a/src/remmina_ext_exec.c b/src/remmina_ext_exec.c
new file mode 100644
index 000000000..0c0833d65
--- /dev/null
+++ b/src/remmina_ext_exec.c
@@ -0,0 +1,141 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2010 Vic Lee
+ * Copyright (C) 2014-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 <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <glib.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include "remmina_utils.h"
+#include "remmina_file.h"
+#include "remmina_ext_exec.h"
+#include "remmina_public.h"
+#include "remmina/remmina_trace_calls.h"
+
+#define SPAWN_TIMEOUT 10
+
+#define GET_OBJECT(object_name) gtk_builder_get_object(builder, object_name)
+
+static void wait_for_child(GPid pid, gint script_retval, gpointer data)
+{
+ PCon_Spinner *pcspinner = (PCon_Spinner*)data;
+
+ gtk_spinner_stop(GTK_SPINNER(pcspinner->spinner));
+ gtk_widget_destroy(GTK_WIDGET(pcspinner->dialog));
+ g_spawn_close_pid(pid);
+ /* TODO At the moment background processes will fail to start before the
+ * remmina connection.
+ * Adding a delay here could be a (not good) solution, or we should
+ * monitor each child opened, but it could be quit tricky and messy */
+
+ g_free(pcspinner);
+}
+
+GtkDialog* remmina_ext_exec_new(RemminaFile* remminafile, const char *remmina_ext_exec_type)
+{
+ TRACE_CALL(__func__);
+ GtkBuilder *builder;
+ PCon_Spinner *pcspinner;
+ GError *error = NULL;
+ char **argv;
+ gchar *cmd = NULL;
+ GString *cmd_str;
+ gchar pre[11];
+ gchar post[12];
+ GPid child_pid;
+
+ strcpy(pre, "precommand");
+ strcpy(post, "postcommand");
+
+ if (remmina_ext_exec_type != NULL && (
+ strcmp(remmina_ext_exec_type, pre) |
+ strcmp(remmina_ext_exec_type, post) )) {
+ cmd_str = g_string_new(remmina_file_get_string(remminafile, remmina_ext_exec_type));
+ remmina_utils_string_replace_all(cmd_str, "%h", remmina_file_get_string(remminafile, "server"));
+ remmina_utils_string_replace_all(cmd_str, "%t", remmina_file_get_string(remminafile, "ssh_server"));
+ remmina_utils_string_replace_all(cmd_str, "%u", remmina_file_get_string(remminafile, "username"));
+ remmina_utils_string_replace_all(cmd_str, "%U", remmina_file_get_string(remminafile, "ssh_username"));
+ remmina_utils_string_replace_all(cmd_str, "%p", remmina_file_get_string(remminafile, "name"));
+ remmina_utils_string_replace_all(cmd_str, "%g", remmina_file_get_string(remminafile, "group"));
+ }else{
+ return FALSE;
+ }
+
+ cmd = g_string_free(cmd_str, FALSE);
+ if (*cmd != 0) {
+
+ pcspinner = g_new(PCon_Spinner, 1);
+ builder = remmina_public_gtk_builder_new_from_file("remmina_spinner.glade");
+ pcspinner->dialog = GTK_DIALOG(gtk_builder_get_object(builder, "DialogSpinner"));
+ pcspinner->label_pleasewait = GTK_LABEL(GET_OBJECT("label_pleasewait"));
+ pcspinner->spinner = GTK_WIDGET(GET_OBJECT("spinner"));
+ pcspinner->button_cancel = GTK_BUTTON(GET_OBJECT("button_cancel"));
+ /* Connect signals */
+ gtk_builder_connect_signals(builder, NULL);
+
+ /* Exec a predefined command */
+ g_shell_parse_argv(cmd, NULL, &argv, &error);
+
+ if (error) {
+ g_warning("%s\n", error->message);
+ g_error_free(error);
+ }
+
+ /* Consider using G_SPAWN_SEARCH_PATH_FROM_ENVP (from glib 2.38)*/
+ g_spawn_async( NULL, // cwd
+ argv, // argv
+ NULL, // envp
+ G_SPAWN_SEARCH_PATH |
+ G_SPAWN_SEARCH_PATH_FROM_ENVP |
+ G_SPAWN_DO_NOT_REAP_CHILD, // flags
+ NULL, // child_setup
+ NULL, // child_setup user data
+ &child_pid, // pid location
+ &error); // error
+ if (!error) {
+ gtk_spinner_start(GTK_SPINNER(pcspinner->spinner));
+ g_child_watch_add(child_pid, wait_for_child, (gpointer)pcspinner);
+ gtk_dialog_run(pcspinner->dialog);
+ }else {
+ g_warning("Command %s exited with error: %s\n", cmd, error->message);
+ g_error_free(error);
+ }
+ g_strfreev(argv);
+ return (pcspinner->dialog);
+ }
+ return FALSE;
+}
diff --git a/src/remmina_ext_exec.h b/src/remmina_ext_exec.h
new file mode 100644
index 000000000..3af57f04e
--- /dev/null
+++ b/src/remmina_ext_exec.h
@@ -0,0 +1,50 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2010 Vic Lee
+ * Copyright (C) 2014-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.
+ *
+ */
+
+#pragma once
+
+G_BEGIN_DECLS
+
+typedef struct {
+ GtkDialog *dialog;
+ GtkLabel *label_pleasewait;
+ GtkButton *button_cancel;
+ GtkWidget *spinner;
+} PCon_Spinner;
+
+GtkDialog* remmina_ext_exec_new(RemminaFile* remminafile, const char *remmina_ext_exec_type);
+
+G_END_DECLS
+
diff --git a/src/remmina_external_tools.c b/src/remmina_external_tools.c
new file mode 100644
index 000000000..6ddde475b
--- /dev/null
+++ b/src/remmina_external_tools.c
@@ -0,0 +1,154 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2011 Marc-Andre Moreau
+ *
+ * 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 <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <stdlib.h>
+#include "remmina/types.h"
+#include "remmina_public.h"
+#include "remmina_external_tools.h"
+#include "remmina/remmina_trace_calls.h"
+
+static gboolean remmina_external_tools_launcher(const gchar* filename, const gchar* scriptname, const gchar* shortname);
+
+static void view_popup_menu_onDoSomething(GtkWidget *menuitem, gpointer userdata)
+{
+ TRACE_CALL(__func__);
+ gchar *remminafilename = g_object_get_data(G_OBJECT(menuitem), "remminafilename");
+ gchar *scriptfilename = g_object_get_data(G_OBJECT(menuitem), "scriptfilename");
+ gchar *scriptshortname = g_object_get_data(G_OBJECT(menuitem), "scriptshortname");
+
+ remmina_external_tools_launcher(remminafilename, scriptfilename, scriptshortname);
+}
+
+gboolean remmina_external_tools_from_filename(RemminaMain *remminamain, gchar* remminafilename)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *menu, *menuitem;
+ gchar dirname[MAX_PATH_LEN];
+ gchar filename[MAX_PATH_LEN];
+ GDir* dir;
+ const gchar* name;
+
+ strcpy(dirname, REMMINA_RUNTIME_EXTERNAL_TOOLS_DIR);
+ dir = g_dir_open(dirname, 0, NULL);
+
+ if (dir == NULL)
+ return FALSE;
+
+ menu = gtk_menu_new();
+
+ while ((name = g_dir_read_name(dir)) != NULL) {
+ if (!g_str_has_prefix(name, "remmina_"))
+ continue;
+ g_snprintf(filename, MAX_PATH_LEN, "%s/%s", dirname, name);
+
+ menuitem = gtk_menu_item_new_with_label(name + 8);
+ g_object_set_data_full(G_OBJECT(menuitem), "remminafilename", g_strdup(remminafilename), g_free);
+ g_object_set_data_full(G_OBJECT(menuitem), "scriptfilename", g_strdup(filename), g_free);
+ g_object_set_data_full(G_OBJECT(menuitem), "scriptshortname", g_strdup(name), g_free);
+ g_signal_connect(menuitem, "activate", (GCallback)view_popup_menu_onDoSomething, NULL);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+ }
+ g_dir_close(dir);
+
+ gtk_widget_show_all(menu);
+
+ /* Note: event can be NULL here when called from view_onPopupMenu;
+ * gdk_event_get_time() accepts a NULL argument
+ */
+#if GTK_CHECK_VERSION(3, 22, 0)
+ gtk_menu_popup_at_pointer(GTK_MENU(menu), NULL);
+#else
+ gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 0, 0);
+#endif
+
+ return TRUE;
+}
+
+static gboolean remmina_external_tools_launcher(const gchar* filename, const gchar* scriptname, const gchar* shortname)
+{
+ TRACE_CALL(__func__);
+ RemminaFile *remminafile;
+ const char *env_format = "%s=%s";
+ char *env;
+ size_t envstrlen;
+ gchar launcher[MAX_PATH_LEN];
+
+ g_snprintf(launcher, MAX_PATH_LEN, "%s/launcher.sh", REMMINA_RUNTIME_EXTERNAL_TOOLS_DIR);
+
+ remminafile = remmina_file_load(filename);
+ GHashTableIter iter;
+ const gchar *key, *value;
+ g_hash_table_iter_init(&iter, remminafile->settings);
+ while (g_hash_table_iter_next(&iter, (gpointer*)&key, (gpointer*)&value)) {
+ envstrlen = strlen(key) + strlen(value) + strlen(env_format) + 1;
+ env = (char*)malloc(envstrlen);
+ if (env == NULL) {
+ return -1;
+ }
+
+ int retval = snprintf(env, envstrlen, env_format, key, value);
+ if (retval > 0 && (size_t)retval <= envstrlen) {
+ if (putenv(env) != 0) {
+ /* If putenv fails, we must free the unused space */
+ free(env);
+ }
+ }
+ }
+ /* Adds the window title for the terminal window */
+ const char *term_title_key = "remmina_term_title";
+ const char *term_title_val_prefix = "Remmina external tool";
+ envstrlen = strlen(term_title_key) + strlen(term_title_val_prefix) + strlen(shortname) + 7;
+ env = (char*)malloc(envstrlen);
+ if (env != NULL) {
+ if (snprintf(env, envstrlen, "%s=%s: %s", term_title_key, term_title_val_prefix, shortname) ) {
+ if (putenv(env) != 0) {
+ /* If putenv fails, we must free the unused space */
+ free(env);
+ }
+ }
+ }
+
+ const size_t cmdlen = strlen(launcher) + strlen(scriptname) + 2;
+ gchar *cmd = (gchar*)malloc(cmdlen);
+ g_snprintf(cmd, cmdlen, "%s %s", launcher, scriptname);
+ system(cmd);
+ free(cmd);
+
+ remmina_file_free(remminafile);
+
+ return TRUE;
+}
diff --git a/src/remmina_external_tools.h b/src/remmina_external_tools.h
new file mode 100644
index 000000000..679523357
--- /dev/null
+++ b/src/remmina_external_tools.h
@@ -0,0 +1,49 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009 - Vic Lee
+ * Copyright (C) 2017-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.
+ *
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include "remmina_file.h"
+#include "remmina_main.h"
+
+G_BEGIN_DECLS
+
+/* Open a new connection window for a .remmina file */
+gboolean remmina_external_tools_from_filename(RemminaMain *remminamain, gchar* filename);
+
+G_END_DECLS
+
+
diff --git a/src/remmina_file.c b/src/remmina_file.c
new file mode 100644
index 000000000..0ea26d275
--- /dev/null
+++ b/src/remmina_file.c
@@ -0,0 +1,681 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2011 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 "config.h"
+
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <locale.h>
+#include <langinfo.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <utime.h>
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+
+#include "remmina_public.h"
+#include "remmina_log.h"
+#include "remmina_crypt.h"
+#include "remmina_file_manager.h"
+#include "remmina_plugin_manager.h"
+#include "remmina_pref.h"
+#include "remmina_main.h"
+#include "remmina_masterthread_exec.h"
+#include "remmina/remmina_trace_calls.h"
+
+#define MIN_WINDOW_WIDTH 10
+#define MIN_WINDOW_HEIGHT 10
+
+static struct timespec times[2];
+
+static RemminaFile*
+remmina_file_new_empty(void)
+{
+ TRACE_CALL(__func__);
+ RemminaFile *remminafile;
+
+ remminafile = g_new0(RemminaFile, 1);
+ remminafile->settings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+ /* spsettings contains settings that are loaded from the secure_plugin.
+ * it's used by remmina_file_store_secret_plugin_password() to know
+ * where to change */
+ remminafile->spsettings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+ remminafile->prevent_saving = FALSE;
+ return remminafile;
+}
+
+RemminaFile*
+remmina_file_new(void)
+{
+ TRACE_CALL(__func__);
+ RemminaFile *remminafile;
+
+ /* Try to load from the preference file for default settings first */
+ remminafile = remmina_file_load(remmina_pref_file);
+
+ if (remminafile) {
+ g_free(remminafile->filename);
+ remminafile->filename = NULL;
+ }else {
+ remminafile = remmina_file_new_empty();
+ }
+
+ return remminafile;
+}
+
+void remmina_file_generate_filename(RemminaFile *remminafile)
+{
+ TRACE_CALL(__func__);
+ GTimeVal gtime;
+ GDir *dir;
+
+ g_free(remminafile->filename);
+ g_get_current_time(&gtime);
+
+ dir = g_dir_open(remmina_file_get_datadir(), 0, NULL);
+ if (dir != NULL)
+ remminafile->filename = g_strdup_printf("%s/%li%03li.remmina", remmina_file_get_datadir(), gtime.tv_sec,
+ gtime.tv_usec / 1000);
+ else
+ remminafile->filename = NULL;
+ g_dir_close(dir);
+}
+
+void remmina_file_set_filename(RemminaFile *remminafile, const gchar *filename)
+{
+ TRACE_CALL(__func__);
+ g_free(remminafile->filename);
+ remminafile->filename = g_strdup(filename);
+}
+
+const gchar*
+remmina_file_get_filename(RemminaFile *remminafile)
+{
+ TRACE_CALL(__func__);
+ return remminafile->filename;
+}
+
+RemminaFile*
+remmina_file_copy(const gchar *filename)
+{
+ TRACE_CALL(__func__);
+ RemminaFile *remminafile;
+
+ remminafile = remmina_file_load(filename);
+ remmina_file_generate_filename(remminafile);
+
+ return remminafile;
+}
+
+static const RemminaProtocolSetting* find_protocol_setting(const gchar *name, RemminaProtocolPlugin* protocol_plugin)
+{
+ TRACE_CALL(__func__);
+ const RemminaProtocolSetting* setting_iter;
+
+ if (protocol_plugin == NULL)
+ return NULL;
+
+ setting_iter = protocol_plugin->basic_settings;
+ if (setting_iter) {
+ while (setting_iter->type != REMMINA_PROTOCOL_SETTING_TYPE_END) {
+ if (strcmp(name, remmina_plugin_manager_get_canonical_setting_name(setting_iter)) == 0)
+ return setting_iter;
+ setting_iter++;
+ }
+ }
+
+ setting_iter = protocol_plugin->advanced_settings;
+ if (setting_iter) {
+ while (setting_iter->type != REMMINA_PROTOCOL_SETTING_TYPE_END) {
+ if (strcmp(name, remmina_plugin_manager_get_canonical_setting_name(setting_iter)) == 0)
+ return setting_iter;
+ setting_iter++;
+ }
+ }
+
+ return NULL;
+
+}
+
+static gboolean is_encrypted_setting(const RemminaProtocolSetting* setting)
+{
+ TRACE_CALL(__func__);
+ if (setting != NULL &&
+ (setting->type == REMMINA_PROTOCOL_SETTING_TYPE_PASSWORD) ) {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static gboolean is_encrypted_setting_by_name(const gchar *setting_name, RemminaProtocolPlugin* protocol_plugin)
+{
+ TRACE_CALL(__func__);
+ const RemminaProtocolSetting* setting;
+
+ if (strcmp(setting_name, "ssh_password") == 0) {
+ return TRUE;
+ }
+ if (strcmp(setting_name, "ssh_passphrase") == 0) {
+ return TRUE;
+ }
+
+ setting = find_protocol_setting(setting_name, protocol_plugin);
+ return is_encrypted_setting(setting);
+}
+
+RemminaFile*
+remmina_file_load(const gchar *filename)
+{
+ TRACE_CALL(__func__);
+ GKeyFile *gkeyfile;
+ RemminaFile *remminafile;
+ gchar *proto;
+ gchar **keys;
+ gchar *key;
+ gchar *resolution_str;
+ gint i;
+ gchar *s, *sec;
+ RemminaProtocolPlugin* protocol_plugin;
+ RemminaSecretPlugin *secret_plugin;
+ gboolean secret_service_available;
+ int w, h;
+
+ gkeyfile = g_key_file_new();
+
+ if (!g_key_file_load_from_file(gkeyfile, filename, G_KEY_FILE_NONE, NULL)) {
+ g_key_file_free(gkeyfile);
+ return NULL;
+ }
+
+ if (g_key_file_has_key(gkeyfile, "remmina", "name", NULL)) {
+ remminafile = remmina_file_new_empty();
+
+ protocol_plugin = NULL;
+
+ /* Identify the protocol plugin and get pointers to its RemminaProtocolSetting structs */
+ proto = g_key_file_get_string(gkeyfile, "remmina", "protocol", NULL);
+ if (proto) {
+ protocol_plugin = (RemminaProtocolPlugin*)remmina_plugin_manager_get_plugin(REMMINA_PLUGIN_TYPE_PROTOCOL, proto);
+ g_free(proto);
+ }
+
+ secret_plugin = remmina_plugin_manager_get_secret_plugin();
+ secret_service_available = secret_plugin && secret_plugin->is_service_available();
+
+ remminafile->filename = g_strdup(filename);
+ keys = g_key_file_get_keys(gkeyfile, "remmina", NULL, NULL);
+ if (keys) {
+ for (i = 0; keys[i]; i++) {
+ key = keys[i];
+ if (is_encrypted_setting_by_name(key, protocol_plugin)) {
+ s = g_key_file_get_string(gkeyfile, "remmina", key, NULL);
+ if (g_strcmp0(s, ".") == 0) {
+ if (secret_service_available) {
+ sec = secret_plugin->get_password(remminafile, key);
+ remmina_file_set_string(remminafile, key, sec);
+ /* Annotate in spsettings that this value comes from secret_plugin */
+ g_hash_table_insert(remminafile->spsettings, g_strdup(key), NULL);
+ g_free(sec);
+ }else {
+ remmina_file_set_string(remminafile, key, s);
+ }
+ }else {
+ remmina_file_set_string_ref(remminafile, key, remmina_crypt_decrypt(s));
+ }
+ g_free(s);
+ }else {
+ /* If we find "resolution", then we split it in two */
+ if (strcmp(key, "resolution") == 0) {
+ resolution_str = g_key_file_get_string(gkeyfile, "remmina", key, NULL);
+ if (remmina_public_split_resolution_string(resolution_str, &w, &h)) {
+ remmina_file_set_string_ref(remminafile, "resolution_width", g_strdup_printf("%i", w));
+ remmina_file_set_string_ref(remminafile, "resolution_height", g_strdup_printf("%i", h));
+ } else {
+ remmina_file_set_string_ref(remminafile, "resolution_width", NULL);
+ remmina_file_set_string_ref(remminafile, "resolution_height", NULL);
+ }
+ g_free(resolution_str);
+ }else {
+ remmina_file_set_string_ref(remminafile, key,
+ g_key_file_get_string(gkeyfile, "remmina", key, NULL));
+ }
+ }
+ }
+ g_strfreev(keys);
+ }
+ }else {
+ remminafile = NULL;
+ }
+
+ g_key_file_free(gkeyfile);
+
+ return remminafile;
+}
+
+void remmina_file_set_string(RemminaFile *remminafile, const gchar *setting, const gchar *value)
+{
+ TRACE_CALL(__func__);
+ remmina_file_set_string_ref(remminafile, setting, g_strdup(value));
+}
+
+void remmina_file_set_string_ref(RemminaFile *remminafile, const gchar *setting, gchar *value)
+{
+ TRACE_CALL(__func__);
+ const gchar* message;
+
+ if (value) {
+ /* We refuse to accept to set the "resolution" field */
+ if (strcmp(setting, "resolution") == 0) {
+ message = "WARNING: the \"resolution\" setting in .pref files is deprecated, but some code in remmina or in a plugin is trying to set it.\n";
+ fputs(message, stdout);
+ remmina_main_show_warning_dialog(message);
+ return;
+ }
+ g_hash_table_insert(remminafile->settings, g_strdup(setting), value);
+ }else {
+ g_hash_table_insert(remminafile->settings, g_strdup(setting), g_strdup(""));
+ }
+}
+
+const gchar*
+remmina_file_get_string(RemminaFile *remminafile, const gchar *setting)
+{
+ TRACE_CALL(__func__);
+ gchar *value;
+ const gchar* message;
+
+ /* Returned value is a pointer to the string stored on the hash table,
+ * please do not free it or the hash table will contain invalid pointer */
+ if ( !remmina_masterthread_exec_is_main_thread() ) {
+ /* Allow the execution of this function from a non main thread
+ * (plugins needs it to have user credentials)*/
+ RemminaMTExecData *d;
+ const gchar *retval;
+ d = (RemminaMTExecData*)g_malloc( sizeof(RemminaMTExecData) );
+ d->func = FUNC_FILE_GET_STRING;
+ d->p.file_get_string.remminafile = remminafile;
+ d->p.file_get_string.setting = setting;
+ remmina_masterthread_exec_and_wait(d);
+ retval = d->p.file_get_string.retval;
+ g_free(d);
+ return retval;
+ }
+
+ if (strcmp(setting, "resolution") == 0) {
+ message = "WARNING: the \"resolution\" setting in .pref files is deprecated, but some code in remmina or in a plugin is trying to read it.\n";
+ fputs(message, stdout);
+ remmina_main_show_warning_dialog(message);
+ return NULL;
+ }
+
+ value = (gchar*)g_hash_table_lookup(remminafile->settings, setting);
+ return value && value[0] ? value : NULL;
+}
+
+gchar*
+remmina_file_get_secret(RemminaFile *remminafile, const gchar *setting)
+{
+ TRACE_CALL(__func__);
+
+ /* This function is in the RemminaPluginService table, we cannot remove it
+ * without breaking plugin API */
+ printf("WARNING: remmina_file_get_secret(remminafile,\"%s\") is deprecated and must not be called. Use remmina_file_get_string() and do not deallocate returned memory.\n", setting);
+ return g_strdup(remmina_file_get_string(remminafile, setting));
+}
+
+void remmina_file_set_int(RemminaFile *remminafile, const gchar *setting, gint value)
+{
+ TRACE_CALL(__func__);
+ g_hash_table_insert(remminafile->settings, g_strdup(setting), g_strdup_printf("%i", value));
+}
+
+gint remmina_file_get_int(RemminaFile *remminafile, const gchar *setting, gint default_value)
+{
+ TRACE_CALL(__func__);
+ gchar *value;
+
+ value = g_hash_table_lookup(remminafile->settings, setting);
+ return value == NULL ? default_value : (value[0] == 't' ? TRUE : atoi(value));
+}
+
+static GKeyFile*
+remmina_file_get_keyfile(RemminaFile *remminafile)
+{
+ TRACE_CALL(__func__);
+ GKeyFile *gkeyfile;
+
+ if (remminafile->filename == NULL)
+ return NULL;
+ gkeyfile = g_key_file_new();
+ if (!g_key_file_load_from_file(gkeyfile, remminafile->filename, G_KEY_FILE_NONE, NULL)) {
+ /* it will fail if it's a new file, but shouldn't matter. */
+ }
+ return gkeyfile;
+}
+
+void remmina_file_free(RemminaFile *remminafile)
+{
+ TRACE_CALL(__func__);
+ if (remminafile == NULL)
+ return;
+
+ g_free(remminafile->filename);
+ g_hash_table_destroy(remminafile->settings);
+ g_hash_table_destroy(remminafile->spsettings);
+ g_free(remminafile);
+}
+
+
+void remmina_file_save(RemminaFile *remminafile)
+{
+ TRACE_CALL(__func__);
+ RemminaSecretPlugin *secret_plugin;
+ gboolean secret_service_available;
+ RemminaProtocolPlugin* protocol_plugin;
+ GHashTableIter iter;
+ const gchar *key, *value;
+ gchar *s, *proto, *content;
+ GKeyFile *gkeyfile;
+ gsize length = 0;
+
+ if (remminafile->prevent_saving)
+ return;
+
+ if ((gkeyfile = remmina_file_get_keyfile(remminafile)) == NULL)
+ return;
+
+ /* Identify the protocol plugin and get pointers to its RemminaProtocolSetting structs */
+ proto = (gchar*)g_hash_table_lookup(remminafile->settings, "protocol");
+ if (proto) {
+ protocol_plugin = (RemminaProtocolPlugin*)remmina_plugin_manager_get_plugin(REMMINA_PLUGIN_TYPE_PROTOCOL, proto);
+ } else {
+ printf("Remmina WARNING: saving settings for unknown protocol, because remminafile has non proto key\n");
+ protocol_plugin = NULL;
+ }
+
+ secret_plugin = remmina_plugin_manager_get_secret_plugin();
+ secret_service_available = secret_plugin && secret_plugin->is_service_available();
+
+ g_hash_table_iter_init(&iter, remminafile->settings);
+ while (g_hash_table_iter_next(&iter, (gpointer*)&key, (gpointer*)&value)) {
+ if (is_encrypted_setting_by_name(key, protocol_plugin)) {
+ if (remminafile->filename && g_strcmp0(remminafile->filename, remmina_pref_file)) {
+ if (secret_service_available) {
+ if (value && value[0]) {
+ if (g_strcmp0(value, ".") != 0) {
+ secret_plugin->store_password(remminafile, key, value);
+ }
+ g_key_file_set_string(gkeyfile, "remmina", key, ".");
+ }else {
+ g_key_file_set_string(gkeyfile, "remmina", key, "");
+ secret_plugin->delete_password(remminafile, key);
+ }
+ }else {
+ if (value && value[0]) {
+ s = remmina_crypt_encrypt(value);
+ g_key_file_set_string(gkeyfile, "remmina", key, s);
+ g_free(s);
+ }else {
+ g_key_file_set_string(gkeyfile, "remmina", key, "");
+ }
+ }
+ }
+ }else {
+ g_key_file_set_string(gkeyfile, "remmina", key, value);
+ }
+ }
+
+ /* Avoid storing redundant and deprecated "resolution" field */
+ g_key_file_remove_key(gkeyfile, "remmina", "resolution", NULL);
+
+ /* Store gkeyfile to disk (password are already sent to keyring) */
+ content = g_key_file_to_data(gkeyfile, &length, NULL);
+ g_file_set_contents(remminafile->filename, content, length, NULL);
+
+ g_free(content);
+ g_key_file_free(gkeyfile);
+
+ remmina_main_update_file_datetime(remminafile);
+}
+
+void remmina_file_store_secret_plugin_password(RemminaFile *remminafile, const gchar* key, const gchar* value)
+{
+ TRACE_CALL(__func__);
+
+ /* Only change the password in the keyring. This function
+ * is a shortcut which avoids updating of date/time of .pref file
+ * when possible, and is used by the mpchanger */
+ RemminaSecretPlugin* plugin;
+
+ if (g_hash_table_lookup_extended(remminafile->spsettings, g_strdup(key), NULL, NULL)) {
+ plugin = remmina_plugin_manager_get_secret_plugin();
+ plugin->store_password(remminafile, key, value);
+ } else {
+ remmina_file_set_string(remminafile, key, value);
+ remmina_file_save(remminafile);
+ }
+}
+
+RemminaFile*
+remmina_file_dup(RemminaFile *remminafile)
+{
+ TRACE_CALL(__func__);
+ RemminaFile *dupfile;
+ GHashTableIter iter;
+ const gchar *key, *value;
+
+ dupfile = remmina_file_new_empty();
+ dupfile->filename = g_strdup(remminafile->filename);
+
+ g_hash_table_iter_init(&iter, remminafile->settings);
+ while (g_hash_table_iter_next(&iter, (gpointer*)&key, (gpointer*)&value)) {
+ remmina_file_set_string(dupfile, key, value);
+ }
+
+ return dupfile;
+}
+
+const gchar*
+remmina_file_get_icon_name(RemminaFile *remminafile)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolPlugin *plugin;
+
+ plugin = (RemminaProtocolPlugin*)remmina_plugin_manager_get_plugin(REMMINA_PLUGIN_TYPE_PROTOCOL,
+ remmina_file_get_string(remminafile, "protocol"));
+ if (!plugin)
+ return "remmina";
+
+ return (remmina_file_get_int(remminafile, "ssh_enabled", FALSE) ? plugin->icon_name_ssh : plugin->icon_name);
+}
+
+RemminaFile*
+remmina_file_dup_temp_protocol(RemminaFile *remminafile, const gchar *new_protocol)
+{
+ TRACE_CALL(__func__);
+ RemminaFile *tmp;
+
+ tmp = remmina_file_dup(remminafile);
+ g_free(tmp->filename);
+ tmp->filename = NULL;
+ remmina_file_set_string(tmp, "protocol", new_protocol);
+ return tmp;
+}
+
+void remmina_file_delete(const gchar *filename)
+{
+ TRACE_CALL(__func__);
+ RemminaFile *remminafile;
+
+ remminafile = remmina_file_load(filename);
+ if (remminafile) {
+ remmina_file_unsave_password(remminafile);
+ remmina_file_free(remminafile);
+ }
+ g_unlink(filename);
+}
+
+void remmina_file_unsave_password(RemminaFile *remminafile)
+{
+ /* Delete all saved secrets for this profile */
+
+ TRACE_CALL(__func__);
+ const RemminaProtocolSetting* setting_iter;
+ RemminaProtocolPlugin* protocol_plugin;
+ gchar *proto;
+ protocol_plugin = NULL;
+
+ remmina_file_set_string(remminafile, "password", NULL);
+
+ proto = (gchar*)g_hash_table_lookup(remminafile->settings, "protocol");
+ if (proto) {
+ protocol_plugin = (RemminaProtocolPlugin*)remmina_plugin_manager_get_plugin(REMMINA_PLUGIN_TYPE_PROTOCOL, proto);
+ if (protocol_plugin) {
+ setting_iter = protocol_plugin->basic_settings;
+ if (setting_iter) {
+ while (setting_iter->type != REMMINA_PROTOCOL_SETTING_TYPE_END) {
+ if (is_encrypted_setting(setting_iter)) {
+ remmina_file_set_string(remminafile, remmina_plugin_manager_get_canonical_setting_name(setting_iter), NULL);
+ }
+ setting_iter++;
+ }
+ }
+ setting_iter = protocol_plugin->advanced_settings;
+ if (setting_iter) {
+ while (setting_iter->type != REMMINA_PROTOCOL_SETTING_TYPE_END) {
+ if (is_encrypted_setting(setting_iter)) {
+ remmina_file_set_string(remminafile, remmina_plugin_manager_get_canonical_setting_name(setting_iter), NULL);
+ }
+ setting_iter++;
+ }
+ }
+ remmina_file_save(remminafile);
+ }
+ }
+}
+
+/**
+ * Return the string date of the last time a file has been modified.
+ *
+ * This is used to return the modification date of a file and it's used
+ * to return the modification date and time of a givwn remmina file.
+ * If it fails it will return "26/01/1976 23:30:00", that is just a date to don't
+ * return an empty string (challenge: what was happened that day at that time?).
+ * @return A date string in the form "%d/%m/%Y %H:%M:%S".
+ * @todo This should be moved to remmina_utils.c
+ */
+gchar*
+remmina_file_get_datetime(RemminaFile *remminafile)
+{
+ TRACE_CALL(__func__);
+
+ GFile *file;
+ GFileInfo *info;
+
+ struct timeval tv;
+ struct tm* ptm;
+ char time_string[256];
+
+ guint64 mtime;
+ gchar *modtime_string;
+
+ file = g_file_new_for_path(remminafile->filename);
+
+ info = g_file_query_info(file,
+ G_FILE_ATTRIBUTE_TIME_MODIFIED,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ NULL);
+
+ g_object_unref(file);
+
+ if (info == NULL) {
+ g_print("couldn't get time info\n");
+ return "26/01/1976 23:30:00";
+ }
+
+ mtime = g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
+ tv.tv_sec = mtime;
+
+ ptm = localtime(&tv.tv_sec);
+ strftime(time_string, sizeof(time_string), "%F - %T", ptm);
+
+ modtime_string = g_locale_to_utf8(time_string, -1, NULL, NULL, NULL);
+
+ g_object_unref(info);
+
+ return modtime_string;
+}
+
+/**
+ * Update the atime and mtime of a given filename.
+ * Function used to update the atime and mtime of a given remmina file, partially
+ * taken from suckless sbase
+ * @see https://git.suckless.org/sbase/tree/touch.c
+ * @todo This should be moved to remmina_utils.c
+ */
+void
+remmina_file_touch(RemminaFile *remminafile)
+{
+ TRACE_CALL(__func__);
+ int fd;
+ struct stat st;
+ int r;
+
+ if ((r = stat(remminafile->filename, &st)) < 0) {
+ if (errno != ENOENT)
+ remmina_log_printf("stat %s:", remminafile->filename);
+ } else if (!r) {
+ times[0] = st.st_atim;
+ times[1] = st.st_mtim;
+ if (utimensat(AT_FDCWD, remminafile->filename, times, 0) < 0)
+ remmina_log_printf("utimensat %s:", remminafile->filename);
+ return;
+ }
+
+ if ((fd = open(remminafile->filename, O_CREAT | O_EXCL, 0644)) < 0)
+ remmina_log_printf("open %s:", remminafile->filename);
+ close(fd);
+
+ remmina_file_touch(remminafile);
+}
+
diff --git a/src/remmina_file.h b/src/remmina_file.h
new file mode 100644
index 000000000..e3d8e602b
--- /dev/null
+++ b/src/remmina_file.h
@@ -0,0 +1,96 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2011 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 "remmina/types.h"
+
+#pragma once
+
+G_BEGIN_DECLS
+
+struct _RemminaFile {
+ gchar *filename;
+ GHashTable *settings;
+ GHashTable *spsettings;
+ gboolean prevent_saving;
+};
+
+enum {
+ SSH_AUTH_PASSWORD, SSH_AUTH_PUBLICKEY, SSH_AUTH_AGENT, SSH_AUTH_AUTO_PUBLICKEY, SSH_AUTH_GSSAPI
+};
+
+
+#define TOOLBAR_OPACITY_LEVEL 8
+#define TOOLBAR_OPACITY_MIN 0.2
+
+/* Create a empty .remmina file */
+RemminaFile* remmina_file_new(void);
+RemminaFile* remmina_file_copy(const gchar *filename);
+void remmina_file_generate_filename(RemminaFile *remminafile);
+void remmina_file_set_filename(RemminaFile *remminafile, const gchar *filename);
+const gchar* remmina_file_get_filename(RemminaFile *remminafile);
+/* Load a new .remmina file and return the allocated RemminaFile object */
+RemminaFile* remmina_file_load(const gchar *filename);
+/* Settings get/set functions */
+void remmina_file_set_string(RemminaFile *remminafile, const gchar *setting, const gchar *value);
+void remmina_file_set_string_ref(RemminaFile *remminafile, const gchar *setting, gchar *value);
+const gchar* remmina_file_get_string(RemminaFile *remminafile, const gchar *setting);
+gchar* remmina_file_get_secret(RemminaFile *remminafile, const gchar *setting);
+void remmina_file_set_int(RemminaFile *remminafile, const gchar *setting, gint value);
+gint remmina_file_get_int(RemminaFile *remminafile, const gchar *setting, gint default_value);
+void remmina_file_store_secret_plugin_password(RemminaFile *remminafile, const gchar* key, const gchar* value);
+/* Create or overwrite the .remmina file */
+void remmina_file_save(RemminaFile *remminafile);
+/* Free the RemminaFile object */
+void remmina_file_free(RemminaFile *remminafile);
+/* Duplicate a RemminaFile object */
+RemminaFile* remmina_file_dup(RemminaFile *remminafile);
+/* Get the protocol icon name */
+const gchar* remmina_file_get_icon_name(RemminaFile *remminafile);
+/* Duplicate a temporary RemminaFile and change the protocol */
+RemminaFile* remmina_file_dup_temp_protocol(RemminaFile *remminafile, const gchar *new_protocol);
+/* Delete a .remmina file */
+void remmina_file_delete(const gchar *filename);
+/* Delete a "password" field and save into .remmina file */
+void remmina_file_unsave_password(RemminaFile *remminafile);
+/* Function used to update the atime and mtime of a given remmina file, partially
+ * taken from suckless sbase */
+gchar* remmina_file_get_datetime(RemminaFile *remminafile);
+/* Function used to update the atime and mtime of a given remmina file */
+void remmina_file_touch(RemminaFile *remminafilefile);
+
+G_END_DECLS
+
+
diff --git a/src/remmina_file_editor.c b/src/remmina_file_editor.c
new file mode 100644
index 000000000..0597ad35d
--- /dev/null
+++ b/src/remmina_file_editor.c
@@ -0,0 +1,1521 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2011 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <stdlib.h>
+#include "config.h"
+#ifdef HAVE_LIBAVAHI_UI
+#include <avahi-ui/avahi-ui.h>
+#endif
+#include "remmina_public.h"
+#include "remmina_pref.h"
+#include "remmina_connection_window.h"
+#include "remmina_string_list.h"
+#include "remmina_pref_dialog.h"
+#include "remmina_file.h"
+#include "remmina_file_manager.h"
+#include "remmina_ssh.h"
+#include "remmina_widget_pool.h"
+#include "remmina_plugin_manager.h"
+#include "remmina_icon.h"
+#include "remmina_file_editor.h"
+#include "remmina/remmina_trace_calls.h"
+
+G_DEFINE_TYPE( RemminaFileEditor, remmina_file_editor, GTK_TYPE_DIALOG)
+
+#ifdef HAVE_LIBSSH
+static const gchar * charset_list = "ASCII,BIG5,"
+ "CP437,CP720,CP737,CP775,CP850,CP852,CP855,"
+ "CP857,CP858,CP862,CP866,CP874,CP1125,CP1250,"
+ "CP1251,CP1252,CP1253,CP1254,CP1255,CP1256,"
+ "CP1257,CP1258,"
+ "EUC-JP,EUC-KR,GBK,"
+ "ISO-8859-1,ISO-8859-2,ISO-8859-3,ISO-8859-4,"
+ "ISO-8859-5,ISO-8859-6,ISO-8859-7,ISO-8859-8,"
+ "ISO-8859-9,ISO-8859-10,ISO-8859-11,ISO-8859-12,"
+ "ISO-8859-13,ISO-8859-14,ISO-8859-15,ISO-8859-16,"
+ "KOI8-R,SJIS,UTF-8";
+#endif
+
+static const gchar * server_tips = N_( "<tt><big>"
+ "Supported formats\n"
+ "* server\n"
+ "* server:port\n"
+ "* [server]:port"
+ "</big></tt>");
+
+static const gchar * cmd_tips = N_( "<tt><big>"
+ "* command in PATH args %h\n"
+ "* /path/to/foo -options %h %u\n"
+ "* %h is substituted with the server name\n"
+ "* %t is substituted with the SSH server name\n"
+ "* %u is substituted with the user name\n"
+ "* %U is substituted with the SSH user name\n"
+ "* %p is substituted with Remmina profile name\n"
+ "* %g is substituted with Remmina profile group name\n"
+ "Do not run in background if you want the command to be executed before connecting.\n"
+ "</big></tt>");
+
+#ifdef HAVE_LIBSSH
+static const gchar* server_tips2 = N_( "<tt><big>"
+ "Supported formats\n"
+ "* :port\n"
+ "* server\n"
+ "* server:port\n"
+ "* [server]:port\n"
+ "* username@server:port (SSH protocol only)"
+ "</big></tt>");
+#endif
+
+struct _RemminaFileEditorPriv {
+ RemminaFile* remmina_file;
+ RemminaProtocolPlugin* plugin;
+ const gchar* avahi_service_type;
+
+ GtkWidget* name_entry;
+ GtkWidget* group_combo;
+ GtkWidget* protocol_combo;
+ GtkWidget* precommand_entry;
+ GtkWidget* postcommand_entry;
+ GtkWidget* save_button;
+
+ GtkWidget* config_box;
+ GtkWidget* config_scrollable;
+ GtkWidget* config_viewport;
+ GtkWidget* config_container;
+
+ GtkWidget* server_combo;
+ GtkWidget* resolution_auto_radio;
+ GtkWidget* resolution_custom_radio;
+ GtkWidget* resolution_custom_combo;
+ GtkWidget* keymap_combo;
+
+ GtkWidget* ssh_enabled_check;
+ GtkWidget* ssh_loopback_check;
+ GtkWidget* ssh_server_default_radio;
+ GtkWidget* ssh_server_custom_radio;
+ GtkWidget* ssh_server_entry;
+ GtkWidget* ssh_auth_agent_radio;
+ GtkWidget* ssh_auth_password_radio;
+ GtkWidget* ssh_auth_publickey_radio;
+ GtkWidget* ssh_auth_auto_publickey_radio;
+ GtkWidget* ssh_username_entry;
+ GtkWidget* ssh_privatekey_chooser;
+ GtkWidget* ssh_charset_combo;
+
+ GHashTable* setting_widgets;
+};
+
+static void remmina_file_editor_class_init(RemminaFileEditorClass* klass)
+{
+ TRACE_CALL(__func__);
+}
+
+#ifdef HAVE_LIBAVAHI_UI
+
+static void remmina_file_editor_browse_avahi(GtkWidget* button, RemminaFileEditor* gfe)
+{
+ TRACE_CALL(__func__);
+ GtkWidget* dialog;
+ gchar* host;
+
+ dialog = aui_service_dialog_new(_("Choose a Remote Desktop Server"),
+ GTK_WINDOW(gfe),
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_OK"), GTK_RESPONSE_ACCEPT,
+ NULL);
+
+ gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(gfe));
+ aui_service_dialog_set_resolve_service(AUI_SERVICE_DIALOG(dialog), TRUE);
+ aui_service_dialog_set_resolve_host_name(AUI_SERVICE_DIALOG(dialog), TRUE);
+ aui_service_dialog_set_browse_service_types(AUI_SERVICE_DIALOG(dialog),
+ gfe->priv->avahi_service_type, NULL);
+
+ if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
+ host = g_strdup_printf("[%s]:%i",
+ aui_service_dialog_get_host_name(AUI_SERVICE_DIALOG(dialog)),
+ aui_service_dialog_get_port(AUI_SERVICE_DIALOG(dialog)));
+ }else {
+ host = NULL;
+ }
+ gtk_widget_destroy(dialog);
+
+ if (host) {
+ gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(gfe->priv->server_combo))), host);
+ g_free(host);
+ }
+}
+#endif
+
+static void remmina_file_editor_on_realize(GtkWidget* widget, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ RemminaFileEditor* gfe;
+ GtkWidget* defaultwidget;
+
+ gfe = REMMINA_FILE_EDITOR(widget);
+
+ defaultwidget = gfe->priv->server_combo;
+
+ if (defaultwidget) {
+ if (GTK_IS_EDITABLE(defaultwidget)) {
+ gtk_editable_select_region(GTK_EDITABLE(defaultwidget), 0, -1);
+ }
+ gtk_widget_grab_focus(defaultwidget);
+ }
+}
+
+static void remmina_file_editor_destroy(GtkWidget* widget, gpointer data)
+{
+ TRACE_CALL(__func__);
+ remmina_file_free(REMMINA_FILE_EDITOR(widget)->priv->remmina_file);
+ g_hash_table_destroy(REMMINA_FILE_EDITOR(widget)->priv->setting_widgets);
+ g_free(REMMINA_FILE_EDITOR(widget)->priv);
+}
+
+static void remmina_file_editor_button_on_toggled(GtkToggleButton* togglebutton, GtkWidget* widget)
+{
+ TRACE_CALL(__func__);
+ gtk_widget_set_sensitive(widget, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(togglebutton)));
+}
+
+static void remmina_file_editor_create_notebook_container(RemminaFileEditor* gfe)
+{
+ TRACE_CALL(__func__);
+ /* Create the notebook */
+ gfe->priv->config_container = gtk_notebook_new();
+ gfe->priv->config_viewport = gtk_viewport_new(NULL, NULL);
+ gfe->priv->config_scrollable = gtk_scrolled_window_new(NULL, NULL);
+ gtk_container_set_border_width(GTK_CONTAINER(gfe->priv->config_scrollable), 2);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(gfe->priv->config_scrollable),
+ GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
+ gtk_widget_show(gfe->priv->config_scrollable);
+
+ gtk_container_add(GTK_CONTAINER(gfe->priv->config_viewport), gfe->priv->config_container);
+ gtk_container_set_border_width(GTK_CONTAINER(gfe->priv->config_viewport), 2);
+ gtk_widget_show(gfe->priv->config_viewport);
+ gtk_container_add(GTK_CONTAINER(gfe->priv->config_scrollable), gfe->priv->config_viewport);
+ gtk_container_set_border_width(GTK_CONTAINER(gfe->priv->config_container), 2);
+ gtk_widget_show(gfe->priv->config_container);
+
+ gtk_container_add(GTK_CONTAINER(gfe->priv->config_box), gfe->priv->config_scrollable);
+}
+
+static GtkWidget* remmina_file_editor_create_notebook_tab(RemminaFileEditor* gfe,
+ const gchar* stock_id, const gchar* label, gint rows, gint cols)
+{
+ TRACE_CALL(__func__);
+ GtkWidget* tablabel;
+ GtkWidget* tabbody;
+ GtkWidget* grid;
+ GtkWidget* widget;
+
+ tablabel = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_widget_show(tablabel);
+
+ widget = gtk_image_new_from_icon_name(stock_id, GTK_ICON_SIZE_BUTTON);
+ gtk_box_pack_start(GTK_BOX(tablabel), widget, FALSE, FALSE, 0);
+ gtk_widget_show(widget);
+
+ widget = gtk_label_new(label);
+ gtk_box_pack_start(GTK_BOX(tablabel), widget, FALSE, FALSE, 0);
+ gtk_widget_show(widget);
+
+ tabbody = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+ gtk_widget_show(tabbody);
+ gtk_notebook_append_page(GTK_NOTEBOOK(gfe->priv->config_container), tabbody, tablabel);
+
+ grid = gtk_grid_new();
+ gtk_widget_show(grid);
+ gtk_grid_set_row_spacing(GTK_GRID(grid), 8);
+ gtk_grid_set_column_spacing(GTK_GRID(grid), 8);
+ gtk_container_set_border_width(GTK_CONTAINER(grid), 15);
+ gtk_box_pack_start(GTK_BOX(tabbody), grid, FALSE, FALSE, 0);
+
+ return grid;
+}
+
+#ifdef HAVE_LIBSSH
+
+static void remmina_file_editor_ssh_server_custom_radio_on_toggled(GtkToggleButton* togglebutton, RemminaFileEditor* gfe)
+{
+ TRACE_CALL(__func__);
+ gtk_widget_set_sensitive(gfe->priv->ssh_server_entry,
+ gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gfe->priv->ssh_enabled_check)) &&
+ (gfe->priv->ssh_server_custom_radio == NULL ||
+ gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gfe->priv->ssh_server_custom_radio)))
+ );
+}
+
+static void remmina_file_editor_ssh_auth_publickey_radio_on_toggled(GtkToggleButton* togglebutton, RemminaFileEditor* gfe)
+{
+ TRACE_CALL(__func__);
+ gboolean b;
+ const gchar* s;
+
+ b = ((!gfe->priv->ssh_enabled_check ||
+ gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gfe->priv->ssh_enabled_check))) &&
+ gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gfe->priv->ssh_auth_publickey_radio)));
+ gtk_widget_set_sensitive(gfe->priv->ssh_privatekey_chooser, b);
+
+ if (b && ( s = remmina_file_get_string(gfe->priv->remmina_file, "ssh_privatekey")) ) {
+ gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(gfe->priv->ssh_privatekey_chooser), s);
+ }
+}
+
+static void remmina_file_editor_ssh_enabled_check_on_toggled(GtkToggleButton* togglebutton,
+ RemminaFileEditor* gfe, RemminaProtocolSSHSetting ssh_setting)
+{
+ TRACE_CALL(__func__);
+ RemminaFileEditorPriv* priv = gfe->priv;
+ gboolean enabled = TRUE;
+ gchar* p;
+
+ if (gfe->priv->ssh_enabled_check) {
+ enabled = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gfe->priv->ssh_enabled_check));
+ if (gfe->priv->ssh_loopback_check)
+ gtk_widget_set_sensitive(gfe->priv->ssh_loopback_check, enabled);
+ if (gfe->priv->ssh_server_default_radio)
+ gtk_widget_set_sensitive(gfe->priv->ssh_server_default_radio, enabled);
+ if (gfe->priv->ssh_server_custom_radio)
+ gtk_widget_set_sensitive(gfe->priv->ssh_server_custom_radio, enabled);
+ remmina_file_editor_ssh_server_custom_radio_on_toggled(NULL, gfe);
+ p = remmina_public_combo_get_active_text(GTK_COMBO_BOX(priv->protocol_combo));
+ if (!(g_strcmp0(p, "SFTP") == 0 || g_strcmp0(p, "SSH") == 0)) {
+ gtk_widget_set_sensitive(gfe->priv->ssh_charset_combo, enabled);
+ gtk_widget_set_sensitive(gfe->priv->ssh_username_entry, enabled);
+ gtk_widget_set_sensitive(gfe->priv->ssh_auth_agent_radio, enabled);
+ gtk_widget_set_sensitive(gfe->priv->ssh_auth_password_radio, enabled);
+ gtk_widget_set_sensitive(gfe->priv->ssh_auth_publickey_radio, enabled);
+ gtk_widget_set_sensitive(gfe->priv->ssh_auth_auto_publickey_radio, enabled);
+ }
+ g_free(p);
+ }
+ remmina_file_editor_ssh_auth_publickey_radio_on_toggled(NULL, gfe);
+
+ if (gfe->priv->ssh_username_entry)
+ if (enabled && gtk_entry_get_text(GTK_ENTRY(gfe->priv->ssh_username_entry)) [0] == '\0') {
+ gtk_entry_set_text(GTK_ENTRY(gfe->priv->ssh_username_entry),
+ remmina_file_get_string(priv->remmina_file, "ssh_username"));
+ }
+}
+
+static void remmina_file_editor_create_ssh_privatekey(RemminaFileEditor* gfe, GtkWidget* grid, gint row, gint column)
+{
+ TRACE_CALL(__func__);
+ gchar* s;
+ GtkWidget* widget;
+ GtkWidget* dialog;
+ const gchar* ssh_privatekey;
+ RemminaFileEditorPriv* priv = gfe->priv;
+
+ widget = gtk_radio_button_new_with_label_from_widget(
+ GTK_RADIO_BUTTON(priv->ssh_auth_password_radio), _("Identity file"));
+ g_signal_connect(G_OBJECT(widget), "toggled",
+ G_CALLBACK(remmina_file_editor_ssh_auth_publickey_radio_on_toggled), gfe);
+ priv->ssh_auth_publickey_radio = widget;
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, row + 22, 1, 1);
+
+ dialog = gtk_file_chooser_dialog_new(_("Identity file"), GTK_WINDOW(gfe), GTK_FILE_CHOOSER_ACTION_OPEN,
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_Open"), GTK_RESPONSE_ACCEPT,
+ NULL);
+
+ widget = gtk_file_chooser_button_new_with_dialog(dialog);
+#ifdef SNAP_BUILD
+ s = g_strdup_printf("%s/.ssh", g_getenv("SNAP_USER_COMMON"));
+#else
+ s = g_strdup_printf("%s/.ssh", g_get_home_dir());
+#endif
+ if (g_file_test(s, G_FILE_TEST_IS_DIR)) {
+ gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(widget), s);
+ }
+ g_free(s);
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, column + 1, row + 22, 1, 1);
+ priv->ssh_privatekey_chooser = widget;
+
+ ssh_privatekey = remmina_file_get_string(priv->remmina_file, "ssh_privatekey");
+ if (ssh_privatekey &&
+ g_file_test(ssh_privatekey, G_FILE_TEST_IS_REGULAR | G_FILE_TEST_EXISTS)) {
+ gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(priv->ssh_privatekey_chooser),
+ ssh_privatekey);
+ }else {
+ remmina_file_set_string(priv->remmina_file, "ssh_privatekey", NULL);
+ }
+}
+#endif
+
+static void remmina_file_editor_create_server(RemminaFileEditor* gfe, const RemminaProtocolSetting* setting, GtkWidget* grid,
+ gint row)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolPlugin* plugin = gfe->priv->plugin;
+ GtkWidget* widget;
+#ifdef HAVE_LIBAVAHI_UI
+ GtkWidget* hbox;
+#endif
+ gchar* s;
+
+ widget = gtk_label_new(_("Server"));
+ gtk_widget_show(widget);
+ gtk_widget_set_valign(widget, GTK_ALIGN_START);
+ gtk_widget_set_halign(widget, GTK_ALIGN_START);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, row, 1, row + 1);
+
+ s = remmina_pref_get_recent(plugin->name);
+ widget = remmina_public_create_combo_entry(s, remmina_file_get_string(gfe->priv->remmina_file, "server"), TRUE);
+ gtk_widget_set_hexpand(widget, TRUE);
+ gtk_widget_show(widget);
+ gtk_widget_set_tooltip_markup(widget, _(server_tips));
+ gtk_entry_set_activates_default(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(widget))), TRUE);
+ gfe->priv->server_combo = widget;
+ g_free(s);
+
+#ifdef HAVE_LIBAVAHI_UI
+ if (setting->opt1) {
+ gfe->priv->avahi_service_type = (const gchar*)setting->opt1;
+
+ hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_widget_show(hbox);
+ gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, TRUE, 0);
+
+ widget = gtk_button_new_with_label("...");
+ s = g_strdup_printf(_("Browse the network to find a %s server"), plugin->name);
+ gtk_widget_set_tooltip_text(widget, s);
+ g_free(s);
+ gtk_widget_show(widget);
+ gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, FALSE, 0);
+ g_signal_connect(G_OBJECT(widget), "clicked", G_CALLBACK(remmina_file_editor_browse_avahi), gfe);
+
+ gtk_grid_attach(GTK_GRID(grid), hbox, 1, row, 1, 1);
+ }else
+#endif
+ {
+ gtk_grid_attach(GTK_GRID(grid), widget, 1, row, 1, 1);
+ }
+}
+
+static GtkWidget* remmina_file_editor_create_password(RemminaFileEditor* gfe, GtkWidget* grid,
+ gint row, gint col, const gchar* label, const gchar* value)
+{
+ TRACE_CALL(__func__);
+ GtkWidget* widget;
+
+ widget = gtk_label_new(label);
+ gtk_widget_show(widget);
+#if GTK_CHECK_VERSION(3, 12, 0)
+ gtk_widget_set_margin_end(widget, 40);
+#else
+ gtk_widget_set_margin_right(widget, 40);
+#endif
+ gtk_widget_set_valign(widget, GTK_ALIGN_START);
+ gtk_widget_set_halign(widget, GTK_ALIGN_START);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, row, 1, 1);
+
+ widget = gtk_entry_new();
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 1, row, 1, 1);
+ gtk_entry_set_max_length(GTK_ENTRY(widget), 100);
+ gtk_entry_set_visibility(GTK_ENTRY(widget), FALSE);
+ gtk_widget_set_hexpand(widget, TRUE);
+ gtk_entry_set_activates_default(GTK_ENTRY(widget), TRUE);
+
+ if (value) {
+ gtk_entry_set_text(GTK_ENTRY(widget), value);
+ }
+ return widget;
+}
+
+static void remmina_file_editor_update_resolution(GtkWidget* widget, RemminaFileEditor* gfe)
+{
+ TRACE_CALL(__func__);
+ gchar* res_str;
+ res_str = g_strdup_printf("%dx%d",
+ remmina_file_get_int(gfe->priv->remmina_file, "resolution_width", 0),
+ remmina_file_get_int(gfe->priv->remmina_file, "resolution_height", 0)
+ );
+ remmina_public_load_combo_text_d(gfe->priv->resolution_custom_combo, remmina_pref.resolutions,
+ res_str, NULL);
+ g_free(res_str);
+}
+
+static void remmina_file_editor_browse_resolution(GtkWidget* button, RemminaFileEditor* gfe)
+{
+ TRACE_CALL(__func__);
+
+ GtkDialog *dialog = remmina_string_list_new(FALSE, NULL);
+ remmina_string_list_set_validation_func(remmina_public_resolution_validation_func);
+ remmina_string_list_set_text(remmina_pref.resolutions, TRUE);
+ remmina_string_list_set_titles(_("Resolutions"), _("Configure the available resolutions"));
+ gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(gfe));
+ gtk_dialog_run(dialog);
+ g_free(remmina_pref.resolutions);
+ remmina_pref.resolutions = remmina_string_list_get_text();
+ g_signal_connect(G_OBJECT(dialog), "destroy", G_CALLBACK(remmina_file_editor_update_resolution), gfe);
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+
+}
+
+static void remmina_file_editor_create_resolution(RemminaFileEditor* gfe, const RemminaProtocolSetting* setting,
+ GtkWidget* grid, gint row)
+{
+ TRACE_CALL(__func__);
+ GtkWidget* widget;
+ GtkWidget* hbox;
+ const gchar *resolution_w, *resolution_h;
+ gchar *res_str;
+
+ widget = gtk_label_new(_("Resolution"));
+ gtk_widget_show(widget);
+ gtk_widget_set_valign(widget, GTK_ALIGN_START);
+ gtk_widget_set_halign(widget, GTK_ALIGN_START);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, row, 1, 1);
+
+ widget = gtk_radio_button_new_with_label(NULL, setting->opt1 ? _("Use window size") : _("Use client resolution"));
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 1, row, 1, 1);
+ gfe->priv->resolution_auto_radio = widget;
+
+ hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_widget_show(hbox);
+ gtk_grid_attach(GTK_GRID(grid), hbox, 1, row + 1, 1, 1);
+
+ widget = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(gfe->priv->resolution_auto_radio), _("Custom"));
+ gtk_widget_show(widget);
+ gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, FALSE, 0);
+ gfe->priv->resolution_custom_radio = widget;
+
+ resolution_w = remmina_file_get_string(gfe->priv->remmina_file, "resolution_width");
+ resolution_h = remmina_file_get_string(gfe->priv->remmina_file, "resolution_height");
+
+ if (resolution_w && resolution_h && resolution_w[0] != 0 && resolution_h[0] != 0)
+ res_str = g_strdup_printf("%sx%s", resolution_w, resolution_h);
+ else
+ res_str = NULL;
+ widget = remmina_public_create_combo_text_d(remmina_pref.resolutions, res_str, NULL);
+ gtk_widget_show(widget);
+ gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, TRUE, 0);
+ gfe->priv->resolution_custom_combo = widget;
+
+ widget = gtk_button_new_with_label("...");
+ gtk_widget_show(widget);
+ gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, FALSE, 0);
+ g_signal_connect(G_OBJECT(widget), "clicked", G_CALLBACK(remmina_file_editor_browse_resolution), gfe);
+
+ g_signal_connect(G_OBJECT(gfe->priv->resolution_custom_radio), "toggled",
+ G_CALLBACK(remmina_file_editor_button_on_toggled), gfe->priv->resolution_custom_combo);
+
+ if (res_str) {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gfe->priv->resolution_custom_radio), TRUE);
+ }else {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gfe->priv->resolution_auto_radio), TRUE);
+ gtk_widget_set_sensitive(gfe->priv->resolution_custom_combo, FALSE);
+ }
+
+ g_free(res_str);
+}
+
+static GtkWidget* remmina_file_editor_create_text(RemminaFileEditor* gfe, GtkWidget* grid,
+ gint row, gint col, const gchar* label, const gchar* value)
+{
+ TRACE_CALL(__func__);
+ GtkWidget* widget;
+
+ widget = gtk_label_new(label);
+ gtk_widget_show(widget);
+#if GTK_CHECK_VERSION(3, 12, 0)
+ gtk_widget_set_margin_end(widget, 40);
+#else
+ gtk_widget_set_margin_right(widget, 40);
+#endif
+ gtk_widget_set_valign(widget, GTK_ALIGN_START);
+ gtk_widget_set_halign(widget, GTK_ALIGN_START);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, row, 1, 1);
+
+ widget = gtk_entry_new();
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 1, row, 1, 1);
+ gtk_entry_set_max_length(GTK_ENTRY(widget), 300);
+ gtk_widget_set_hexpand(widget, TRUE);
+
+ if (value)
+ gtk_entry_set_text(GTK_ENTRY(widget), value);
+
+ return widget;
+}
+
+static GtkWidget* remmina_file_editor_create_select(RemminaFileEditor* gfe, GtkWidget* grid,
+ gint row, gint col, const gchar* label, const gpointer* list, const gchar* value)
+{
+ TRACE_CALL(__func__);
+ GtkWidget* widget;
+
+ widget = gtk_label_new(label);
+ gtk_widget_show(widget);
+ gtk_widget_set_valign(widget, GTK_ALIGN_START);
+ gtk_widget_set_halign(widget, GTK_ALIGN_START);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, row, 1, 1);
+
+ widget = remmina_public_create_combo_map(list, value, FALSE, gfe->priv->plugin->domain);
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 1, row, 1, 1);
+
+ return widget;
+}
+
+static GtkWidget* remmina_file_editor_create_combo(RemminaFileEditor* gfe, GtkWidget* grid,
+ gint row, gint col, const gchar* label, const gchar* list, const gchar* value)
+{
+ TRACE_CALL(__func__);
+ GtkWidget* widget;
+
+ widget = gtk_label_new(label);
+ gtk_widget_show(widget);
+ gtk_widget_set_valign(widget, GTK_ALIGN_START);
+ gtk_widget_set_halign(widget, GTK_ALIGN_START);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, row, 1, 1);
+
+ widget = remmina_public_create_combo_entry(list, value, FALSE);
+ gtk_widget_show(widget);
+ gtk_widget_set_hexpand(widget, TRUE);
+ gtk_grid_attach(GTK_GRID(grid), widget, 1, row, 1, 1);
+
+ return widget;
+}
+
+static GtkWidget* remmina_file_editor_create_check(RemminaFileEditor* gfe, GtkWidget* grid,
+ gint row, gint top, const gchar* label, gboolean value)
+{
+ TRACE_CALL(__func__);
+ GtkWidget* widget;
+ widget = gtk_check_button_new_with_label(label);
+ gtk_widget_show(widget);
+ gtk_grid_set_row_spacing(GTK_GRID(grid), 1);
+ gtk_grid_attach(GTK_GRID(grid), widget, top, row, 1, 1);
+
+ if (value)
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), TRUE);
+
+ return widget;
+}
+
+static GtkWidget*
+remmina_file_editor_create_chooser(RemminaFileEditor* gfe, GtkWidget* grid, gint row, gint col, const gchar* label,
+ const gchar* value, gint type)
+{
+ TRACE_CALL(__func__);
+ GtkWidget* check;
+ GtkWidget* widget;
+ GtkWidget* hbox;
+
+ widget = gtk_label_new(label);
+ gtk_widget_show(widget);
+ gtk_widget_set_valign(widget, GTK_ALIGN_START);
+ gtk_widget_set_halign(widget, GTK_ALIGN_START);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, row, 1, 1);
+
+ hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_widget_show(hbox);
+ gtk_grid_attach(GTK_GRID(grid), hbox, 1, row, 1, 1);
+
+ check = gtk_check_button_new();
+ gtk_widget_show(check);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), (value && value[0] == '/'));
+ gtk_box_pack_start(GTK_BOX(hbox), check, FALSE, FALSE, 0);
+
+ widget = gtk_file_chooser_button_new(label, type);
+ gtk_widget_show(widget);
+ if (value) {
+ gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(widget), value);
+ }
+ gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, TRUE, 0);
+
+ g_signal_connect(G_OBJECT(check), "toggled", G_CALLBACK(remmina_file_editor_button_on_toggled), widget);
+ remmina_file_editor_button_on_toggled(GTK_TOGGLE_BUTTON(check), widget);
+
+ return widget;
+}
+
+static void remmina_file_editor_create_settings(RemminaFileEditor* gfe, GtkWidget* grid,
+ const RemminaProtocolSetting* settings)
+{
+ TRACE_CALL(__func__);
+ RemminaFileEditorPriv* priv = gfe->priv;
+ GtkWidget* widget;
+ gint grid_row = 0;
+ gint grid_column = 0;
+ gchar** strarr;
+ gchar* setting_name;
+
+
+ while (settings->type != REMMINA_PROTOCOL_SETTING_TYPE_END) {
+ setting_name = (gchar*)(remmina_plugin_manager_get_canonical_setting_name(settings));
+ switch (settings->type) {
+ case REMMINA_PROTOCOL_SETTING_TYPE_SERVER:
+ remmina_file_editor_create_server(gfe, settings, grid, grid_row);
+ break;
+
+ case REMMINA_PROTOCOL_SETTING_TYPE_PASSWORD:
+ widget = remmina_file_editor_create_password(gfe, grid, grid_row, 0,
+ g_dgettext(priv->plugin->domain, settings->label),
+ remmina_file_get_string(priv->remmina_file, setting_name));
+ g_hash_table_insert(priv->setting_widgets, setting_name, widget);
+ grid_row++;
+ break;
+
+ case REMMINA_PROTOCOL_SETTING_TYPE_RESOLUTION:
+ remmina_file_editor_create_resolution(gfe, settings, grid, grid_row);
+ grid_row++;
+ break;
+
+ case REMMINA_PROTOCOL_SETTING_TYPE_KEYMAP:
+ strarr = remmina_pref_keymap_groups();
+ priv->keymap_combo = remmina_file_editor_create_select(gfe, grid,
+ grid_row + 1, 0,
+ _("Keyboard mapping"), (const gpointer*)strarr,
+ remmina_file_get_string(priv->remmina_file, "keymap"));
+ g_strfreev(strarr);
+ grid_row++;
+ break;
+
+ case REMMINA_PROTOCOL_SETTING_TYPE_TEXT:
+ widget = remmina_file_editor_create_text(gfe, grid, grid_row, 0,
+ g_dgettext(priv->plugin->domain, settings->label),
+ remmina_file_get_string(priv->remmina_file, setting_name));
+ g_hash_table_insert(priv->setting_widgets, setting_name, widget);
+ grid_row++;
+ break;
+
+ case REMMINA_PROTOCOL_SETTING_TYPE_SELECT:
+ widget = remmina_file_editor_create_select(gfe, grid, grid_row, 0,
+ g_dgettext(priv->plugin->domain, settings->label),
+ (const gpointer*)settings->opt1,
+ remmina_file_get_string(priv->remmina_file, setting_name));
+ g_hash_table_insert(priv->setting_widgets, setting_name, widget);
+ break;
+
+ case REMMINA_PROTOCOL_SETTING_TYPE_COMBO:
+ widget = remmina_file_editor_create_combo(gfe, grid, grid_row, 0,
+ g_dgettext(priv->plugin->domain, settings->label),
+ (const gchar*)settings->opt1,
+ remmina_file_get_string(priv->remmina_file, setting_name));
+ g_hash_table_insert(priv->setting_widgets, setting_name, widget);
+ break;
+
+ case REMMINA_PROTOCOL_SETTING_TYPE_CHECK:
+ widget = remmina_file_editor_create_check(gfe, grid, grid_row, grid_column,
+ g_dgettext(priv->plugin->domain, settings->label),
+ remmina_file_get_int(priv->remmina_file, setting_name, FALSE));
+ g_hash_table_insert(priv->setting_widgets, setting_name, widget);
+ break;
+
+ case REMMINA_PROTOCOL_SETTING_TYPE_FILE:
+ widget = remmina_file_editor_create_chooser(gfe, grid, grid_row, 0,
+ g_dgettext(priv->plugin->domain, settings->label),
+ remmina_file_get_string(priv->remmina_file, setting_name),
+ GTK_FILE_CHOOSER_ACTION_OPEN);
+ g_hash_table_insert(priv->setting_widgets, setting_name, widget);
+ break;
+
+ case REMMINA_PROTOCOL_SETTING_TYPE_FOLDER:
+ widget = remmina_file_editor_create_chooser(gfe, grid, grid_row, 0,
+ g_dgettext(priv->plugin->domain, settings->label),
+ remmina_file_get_string(priv->remmina_file, setting_name),
+ GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER);
+ g_hash_table_insert(priv->setting_widgets, setting_name, widget);
+ break;
+
+ default:
+ break;
+ }
+ /* If the setting wants compactness, move to the next column */
+ if (settings->compact) {
+ grid_column++;
+ }
+ /* Add a new settings row and move to the first column
+ * if the setting doesn't want the compactness
+ * or we already have two columns */
+ if (!settings->compact || grid_column > 1) {
+ grid_row++;
+ grid_column = 0;
+ }
+ settings++;
+ }
+
+}
+
+static void remmina_file_editor_create_ssh_tab(RemminaFileEditor* gfe, RemminaProtocolSSHSetting ssh_setting)
+{
+ TRACE_CALL(__func__);
+#ifdef HAVE_LIBSSH
+ RemminaFileEditorPriv* priv = gfe->priv;
+ GtkWidget* grid;
+ GtkWidget* hbox;
+ GtkWidget* widget;
+ const gchar* cs;
+ gchar* s;
+ gchar* p;
+ gint row = 0;
+
+ if (ssh_setting == REMMINA_PROTOCOL_SSH_SETTING_NONE) return;
+
+ /* The SSH tab (implementation) */
+ grid = remmina_file_editor_create_notebook_tab(gfe, NULL,
+ "SSH Tunnel", 9, 3);
+
+ hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_widget_show(hbox);
+ gtk_grid_attach(GTK_GRID(grid), hbox, 0, 0, 3, 1);
+ row++;
+
+ widget = gtk_check_button_new_with_label(_("Enable SSH tunnel"));
+ gtk_widget_show(widget);
+ gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, TRUE, 0);
+ g_signal_connect(G_OBJECT(widget), "toggled",
+ G_CALLBACK(remmina_file_editor_ssh_enabled_check_on_toggled), gfe);
+ priv->ssh_enabled_check = widget;
+
+ widget = gtk_check_button_new_with_label(_("Tunnel via loopback address"));
+ gtk_widget_show(widget);
+ gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, TRUE, 0);
+ priv->ssh_loopback_check = widget;
+
+ row++;
+ /* SSH Server group */
+
+ switch (ssh_setting) {
+ case REMMINA_PROTOCOL_SSH_SETTING_TUNNEL:
+ s = g_strdup_printf(_("Same server at port %i"), DEFAULT_SSH_PORT);
+ widget = gtk_radio_button_new_with_label(NULL, s);
+ g_free(s);
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, row, 3, 1);
+ priv->ssh_server_default_radio = widget;
+ row++;
+
+ widget = gtk_radio_button_new_with_label_from_widget(
+ GTK_RADIO_BUTTON(priv->ssh_server_default_radio), _("Custom"));
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, row, 1, 1);
+ g_signal_connect(G_OBJECT(widget), "toggled",
+ G_CALLBACK(remmina_file_editor_ssh_server_custom_radio_on_toggled), gfe);
+ priv->ssh_server_custom_radio = widget;
+
+ widget = gtk_entry_new();
+ gtk_widget_show(widget);
+ gtk_entry_set_max_length(GTK_ENTRY(widget), 100);
+ gtk_widget_set_tooltip_markup(widget, _(server_tips2));
+ gtk_grid_attach(GTK_GRID(grid), widget, 1, row, 2, 1);
+ priv->ssh_server_entry = widget;
+ row++;
+ break;
+
+ case REMMINA_PROTOCOL_SSH_SETTING_REVERSE_TUNNEL:
+ priv->ssh_server_default_radio = NULL;
+ priv->ssh_server_custom_radio = NULL;
+
+ priv->ssh_server_entry = remmina_file_editor_create_text(gfe, grid, 1, 0,
+ _("Server"), NULL);
+ gtk_widget_set_tooltip_markup(priv->ssh_server_entry, _(server_tips));
+ row++;
+ break;
+ case REMMINA_PROTOCOL_SSH_SETTING_SSH:
+ case REMMINA_PROTOCOL_SSH_SETTING_SFTP:
+ priv->ssh_server_default_radio = NULL;
+ priv->ssh_server_custom_radio = NULL;
+ priv->ssh_server_entry = NULL;
+
+ break;
+
+ default:
+ break;
+ }
+
+ p = remmina_public_combo_get_active_text(GTK_COMBO_BOX(priv->protocol_combo));
+ if (!(g_strcmp0(p, "SFTP") == 0 || g_strcmp0(p, "SSH") == 0)) {
+ priv->ssh_charset_combo = remmina_file_editor_create_combo(gfe, grid, row + 3, 0,
+ _("Character set"), charset_list, remmina_file_get_string(priv->remmina_file, "ssh_charset"));
+ row++;
+ }
+ if (ssh_setting == REMMINA_PROTOCOL_SSH_SETTING_SFTP) {
+ widget = remmina_file_editor_create_text(gfe, grid, row + 8, 1,
+ _("Startup path"), NULL);
+ cs = remmina_file_get_string(priv->remmina_file, "execpath");
+ gtk_entry_set_text(GTK_ENTRY(widget), cs ? cs : "");
+ g_hash_table_insert(priv->setting_widgets, "execpath", widget);
+ row++;
+ }
+
+ /* SSH Authentication frame */
+ if (!(g_strcmp0(p, "SFTP") == 0 || g_strcmp0(p, "SSH") == 0)) {
+ remmina_public_create_group(GTK_GRID(grid), _("SSH Authentication"), row + 8, 6, 1);
+ row++;
+
+ if (ssh_setting == REMMINA_PROTOCOL_SSH_SETTING_TUNNEL ||
+ ssh_setting == REMMINA_PROTOCOL_SSH_SETTING_REVERSE_TUNNEL) {
+ priv->ssh_username_entry =
+ remmina_file_editor_create_text(gfe, grid, row + 10, 0,
+ _("User name"), NULL);
+ row++;
+ }
+ widget = gtk_radio_button_new_with_label(NULL, _("SSH Agent (automatic)"));
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, row + 19, 1, 1);
+ priv->ssh_auth_agent_radio = widget;
+ row++;
+
+ widget = gtk_radio_button_new_with_label_from_widget(
+ GTK_RADIO_BUTTON(priv->ssh_auth_agent_radio), _("Password"));
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, row + 21, 1, 1);
+ priv->ssh_auth_password_radio = widget;
+ row++;
+
+ widget = gtk_radio_button_new_with_label_from_widget(
+ GTK_RADIO_BUTTON(priv->ssh_auth_password_radio), _("Public key (automatic)"));
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, row + 22, 1, 1);
+ priv->ssh_auth_auto_publickey_radio = widget;
+ row++;
+
+ remmina_file_editor_create_ssh_privatekey(gfe, grid, row + 1, 0);
+ }
+ row++;
+
+ /* Set the values */
+ cs = remmina_file_get_string(priv->remmina_file, "ssh_server");
+ if (ssh_setting == REMMINA_PROTOCOL_SSH_SETTING_TUNNEL) {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->ssh_enabled_check),
+ remmina_file_get_int(priv->remmina_file, "ssh_enabled", FALSE));
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->ssh_loopback_check),
+ remmina_file_get_int(priv->remmina_file, "ssh_loopback", FALSE));
+
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cs ?
+ priv->ssh_server_custom_radio : priv->ssh_server_default_radio), TRUE);
+ gtk_entry_set_text(GTK_ENTRY(priv->ssh_server_entry),
+ cs ? cs : "");
+ }else if (ssh_setting == REMMINA_PROTOCOL_SSH_SETTING_REVERSE_TUNNEL) {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->ssh_enabled_check),
+ remmina_file_get_int(priv->remmina_file, "ssh_enabled", FALSE));
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->ssh_loopback_check),
+ remmina_file_get_int(priv->remmina_file, "ssh_loopback", FALSE));
+ gtk_entry_set_text(GTK_ENTRY(priv->ssh_server_entry),
+ cs ? cs : "");
+ }
+
+ if (!(g_strcmp0(p, "SFTP") == 0 || g_strcmp0(p, "SSH") == 0)) {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(
+ remmina_file_get_int(priv->remmina_file, "ssh_auth", 0) == SSH_AUTH_PUBLICKEY ?
+ priv->ssh_auth_publickey_radio :
+ remmina_file_get_int(priv->remmina_file, "ssh_auth", 0) == SSH_AUTH_AUTO_PUBLICKEY ?
+ priv->ssh_auth_auto_publickey_radio :
+ remmina_file_get_int(priv->remmina_file, "ssh_auth", 0) == SSH_AUTH_AGENT ?
+ priv->ssh_auth_agent_radio :
+ priv->ssh_auth_password_radio), TRUE);
+
+ remmina_file_editor_ssh_enabled_check_on_toggled(NULL, gfe, ssh_setting);
+ }
+ g_free(p);
+#endif
+}
+
+static void remmina_file_editor_create_all_settings(RemminaFileEditor* gfe)
+{
+ TRACE_CALL(__func__);
+ RemminaFileEditorPriv* priv = gfe->priv;
+ GtkWidget* grid;
+
+ remmina_file_editor_create_notebook_container(gfe);
+
+ /* The Basic tab */
+ if (priv->plugin->basic_settings) {
+ grid = remmina_file_editor_create_notebook_tab(gfe, NULL, _("Basic"), 20, 2);
+ remmina_file_editor_create_settings(gfe, grid, priv->plugin->basic_settings);
+ }
+
+ /* The Advanced tab */
+ if (priv->plugin->advanced_settings) {
+ grid = remmina_file_editor_create_notebook_tab(gfe, NULL, _("Advanced"), 20, 2);
+ remmina_file_editor_create_settings(gfe, grid, priv->plugin->advanced_settings);
+ }
+
+ /* The SSH tab */
+ remmina_file_editor_create_ssh_tab(gfe, priv->plugin->ssh_setting);
+}
+
+static void remmina_file_editor_protocol_combo_on_changed(GtkComboBox* combo, RemminaFileEditor* gfe)
+{
+ TRACE_CALL(__func__);
+ RemminaFileEditorPriv* priv = gfe->priv;
+ gchar* protocol;
+
+ if (priv->config_container) {
+ gtk_widget_destroy(priv->config_container);
+ priv->config_container = NULL;
+ gtk_widget_destroy(priv->config_viewport);
+ priv->config_viewport = NULL;
+ gtk_widget_destroy(priv->config_scrollable);
+ priv->config_scrollable = NULL;
+ }
+
+ priv->server_combo = NULL;
+ priv->resolution_auto_radio = NULL;
+ priv->resolution_custom_radio = NULL;
+ priv->resolution_custom_combo = NULL;
+ priv->keymap_combo = NULL;
+
+ priv->ssh_enabled_check = NULL;
+ priv->ssh_loopback_check = NULL;
+ priv->ssh_server_default_radio = NULL;
+ priv->ssh_server_custom_radio = NULL;
+ priv->ssh_server_entry = NULL;
+ priv->ssh_username_entry = NULL;
+ priv->ssh_auth_agent_radio = NULL;
+ priv->ssh_auth_password_radio = NULL;
+ priv->ssh_auth_publickey_radio = NULL;
+ priv->ssh_auth_auto_publickey_radio = NULL;
+ priv->ssh_privatekey_chooser = NULL;
+ priv->ssh_charset_combo = NULL;
+
+ g_hash_table_remove_all(priv->setting_widgets);
+
+ protocol = remmina_public_combo_get_active_text(combo);
+ if (protocol) {
+ priv->plugin = (RemminaProtocolPlugin*)remmina_plugin_manager_get_plugin(REMMINA_PLUGIN_TYPE_PROTOCOL,
+ protocol);
+ g_free(protocol);
+ remmina_file_editor_create_all_settings(gfe);
+ }
+}
+
+static void remmina_file_editor_update_ssh(RemminaFileEditor* gfe)
+{
+ TRACE_CALL(__func__);
+ RemminaFileEditorPriv* priv = gfe->priv;
+ gboolean ssh_enabled;
+
+ if (priv->ssh_charset_combo) {
+ remmina_file_set_string_ref(priv->remmina_file, "ssh_charset",
+ remmina_public_combo_get_active_text(GTK_COMBO_BOX(priv->ssh_charset_combo)));
+ }
+
+ ssh_enabled = (priv->ssh_enabled_check ?
+ gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->ssh_enabled_check)) : FALSE);
+ remmina_file_set_int( priv->remmina_file,
+ "ssh_loopback",
+ (priv->ssh_loopback_check ?
+ gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->ssh_loopback_check)) :
+ FALSE));
+ remmina_file_set_int(priv->remmina_file, "ssh_enabled", ssh_enabled);
+ remmina_file_set_string(priv->remmina_file, "ssh_username",
+ (ssh_enabled ? gtk_entry_get_text(GTK_ENTRY(priv->ssh_username_entry)) : NULL));
+ remmina_file_set_string(
+ priv->remmina_file,
+ "ssh_server",
+ (ssh_enabled && priv->ssh_server_entry
+ && (priv->ssh_server_custom_radio == NULL
+ || gtk_toggle_button_get_active(
+ GTK_TOGGLE_BUTTON(priv->ssh_server_custom_radio))) ?
+ gtk_entry_get_text(GTK_ENTRY(priv->ssh_server_entry)) : NULL));
+ remmina_file_set_int(
+ priv->remmina_file,
+ "ssh_auth",
+ (priv->ssh_auth_publickey_radio
+ && gtk_toggle_button_get_active(
+ GTK_TOGGLE_BUTTON(priv->ssh_auth_publickey_radio)) ?
+ SSH_AUTH_PUBLICKEY :
+ priv->ssh_auth_auto_publickey_radio
+ && gtk_toggle_button_get_active(
+ GTK_TOGGLE_BUTTON(priv->ssh_auth_auto_publickey_radio)) ?
+ SSH_AUTH_AUTO_PUBLICKEY :
+ priv->ssh_auth_agent_radio
+ && gtk_toggle_button_get_active(
+ GTK_TOGGLE_BUTTON(priv->ssh_auth_agent_radio)) ?
+ SSH_AUTH_AGENT : SSH_AUTH_PASSWORD));
+ remmina_file_set_string(
+ priv->remmina_file,
+ "ssh_privatekey",
+ (priv->ssh_privatekey_chooser ?
+ gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(priv->ssh_privatekey_chooser)) : NULL));
+}
+
+static void remmina_file_editor_update_settings(RemminaFileEditor* gfe)
+{
+ TRACE_CALL(__func__);
+ RemminaFileEditorPriv* priv = gfe->priv;
+ GHashTableIter iter;
+ gpointer key, value;
+
+ g_hash_table_iter_init(&iter, priv->setting_widgets);
+ while (g_hash_table_iter_next(&iter, &key, &value)) {
+ if (GTK_IS_ENTRY(value)) {
+ remmina_file_set_string(priv->remmina_file, (gchar*)key, gtk_entry_get_text(GTK_ENTRY(value)));
+ }else if (GTK_IS_COMBO_BOX(value)) {
+ remmina_file_set_string_ref(priv->remmina_file, (gchar*)key,
+ remmina_public_combo_get_active_text(GTK_COMBO_BOX(value)));
+ }else if (GTK_IS_FILE_CHOOSER(value)) {
+ remmina_file_set_string(
+ priv->remmina_file,
+ (gchar*)key,
+ gtk_widget_get_sensitive(GTK_WIDGET(value)) ?
+ gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(value)) :
+ NULL);
+ }else if (GTK_IS_TOGGLE_BUTTON(value)) {
+ remmina_file_set_int(priv->remmina_file, (gchar*)key,
+ gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(value)));
+ }
+ }
+}
+
+static void remmina_file_editor_update(RemminaFileEditor* gfe)
+{
+ TRACE_CALL(__func__);
+ gchar *custom_resolution, *res_w, *res_h;
+ int w, h;
+
+ RemminaFileEditorPriv* priv = gfe->priv;
+
+ remmina_file_set_string(priv->remmina_file, "name", gtk_entry_get_text(GTK_ENTRY(priv->name_entry)));
+
+ remmina_file_set_string_ref(priv->remmina_file, "group",
+ (priv->group_combo ? remmina_public_combo_get_active_text(GTK_COMBO_BOX(priv->group_combo)) : NULL));
+
+ remmina_file_set_string_ref(priv->remmina_file, "protocol",
+ remmina_public_combo_get_active_text(GTK_COMBO_BOX(priv->protocol_combo)));
+
+ remmina_file_set_string(priv->remmina_file, "precommand", gtk_entry_get_text(GTK_ENTRY(priv->precommand_entry)));
+ remmina_file_set_string(priv->remmina_file, "postcommand", gtk_entry_get_text(GTK_ENTRY(priv->postcommand_entry)));
+
+ remmina_file_set_string_ref(priv->remmina_file, "server",
+ (priv->server_combo ? remmina_public_combo_get_active_text(GTK_COMBO_BOX(priv->server_combo)) : NULL));
+
+ if (priv->resolution_auto_radio) {
+ if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->resolution_auto_radio))) {
+ /* Resolution is set to auto */
+ res_w = res_h = NULL;
+ }else {
+ /* Resolution is set to a value from the list */
+ custom_resolution = remmina_public_combo_get_active_text(GTK_COMBO_BOX(priv->resolution_custom_combo));
+ if (remmina_public_split_resolution_string(custom_resolution, &w, &h)) {
+ res_w = g_strdup_printf("%i", w);
+ res_h = g_strdup_printf("%i", h);
+ }else {
+ res_w = res_h = NULL;
+ }
+ g_free(custom_resolution);
+ }
+ remmina_file_set_string_ref(priv->remmina_file, "resolution_width", res_w);
+ remmina_file_set_string_ref(priv->remmina_file, "resolution_height", res_h);
+ }
+
+ if (priv->keymap_combo) {
+ remmina_file_set_string_ref(priv->remmina_file, "keymap",
+ remmina_public_combo_get_active_text(GTK_COMBO_BOX(priv->keymap_combo)));
+ }
+
+ remmina_file_editor_update_ssh(gfe);
+ remmina_file_editor_update_settings(gfe);
+}
+
+static void remmina_file_editor_on_default(GtkWidget* button, RemminaFileEditor* gfe)
+{
+ TRACE_CALL(__func__);
+ RemminaFile* gf;
+ GtkWidget* dialog;
+
+ remmina_file_editor_update(gfe);
+
+ gf = remmina_file_dup(gfe->priv->remmina_file);
+
+ remmina_file_set_filename(gf, remmina_pref_file);
+
+ /* Clear properties that should never be default */
+ remmina_file_set_string(gf, "name", NULL);
+ remmina_file_set_string(gf, "server", NULL);
+ remmina_file_set_string(gf, "password", NULL);
+ remmina_file_set_string(gf, "precommand", NULL);
+ remmina_file_set_string(gf, "postcommand", NULL);
+
+ remmina_file_save(gf);
+ remmina_file_free(gf);
+
+ dialog = gtk_message_dialog_new(GTK_WINDOW(gfe), GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, GTK_BUTTONS_OK,
+ _("Default settings saved."));
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+}
+
+static void remmina_file_editor_on_save(GtkWidget* button, RemminaFileEditor* gfe)
+{
+ TRACE_CALL(__func__);
+
+ remmina_file_editor_update(gfe);
+
+ remmina_file_save(gfe->priv->remmina_file);
+ remmina_icon_populate_menu();
+
+ gtk_widget_destroy(GTK_WIDGET(gfe));
+}
+
+static void remmina_file_editor_on_connect(GtkWidget* button, RemminaFileEditor* gfe)
+{
+ TRACE_CALL(__func__);
+ RemminaFile* gf;
+
+ remmina_file_editor_update(gfe);
+
+ gf = remmina_file_dup(gfe->priv->remmina_file);
+ /* Put server into name for Quick Connect */
+ if (remmina_file_get_filename(gf) == NULL) {
+ remmina_file_set_string(gf, "name", remmina_file_get_string(gf, "server"));
+ }
+ gtk_widget_destroy(GTK_WIDGET(gfe));
+ gf->prevent_saving = TRUE;
+ remmina_connection_window_open_from_file(gf);
+}
+
+static void remmina_file_editor_on_save_connect(GtkWidget* button, RemminaFileEditor* gfe)
+{
+ TRACE_CALL(__func__);
+ /** @todo Call remmina_file_editor_on_save */
+ RemminaFile* gf;
+
+ remmina_file_editor_update(gfe);
+
+ remmina_file_save(gfe->priv->remmina_file);
+ remmina_icon_populate_menu();
+
+ gf = remmina_file_dup(gfe->priv->remmina_file);
+ /* Put server into name for Quick Connect */
+ if (remmina_file_get_filename(gf) == NULL) {
+ remmina_file_set_string(gf, "name", remmina_file_get_string(gf, "server"));
+ }
+ gtk_widget_destroy(GTK_WIDGET(gfe));
+ remmina_connection_window_open_from_file(gf);
+}
+
+static void remmina_file_editor_on_cancel(GtkWidget* button, RemminaFileEditor* gfe)
+{
+ TRACE_CALL(__func__);
+ gtk_widget_destroy(GTK_WIDGET(gfe));
+}
+
+static void remmina_file_editor_init(RemminaFileEditor* gfe)
+{
+ TRACE_CALL(__func__);
+ RemminaFileEditorPriv* priv;
+ GtkWidget* widget;
+
+ priv = g_new0(RemminaFileEditorPriv, 1);
+ gfe->priv = priv;
+
+ /* Create the editor dialog */
+ gtk_window_set_title(GTK_WINDOW(gfe), _("Remote Desktop Preference"));
+
+ widget = gtk_dialog_add_button(GTK_DIALOG(gfe), (_("_Cancel")), GTK_RESPONSE_CANCEL);
+ g_signal_connect(G_OBJECT(widget), "clicked", G_CALLBACK(remmina_file_editor_on_cancel), gfe);
+
+ /* Default button */
+ widget = gtk_dialog_add_button(GTK_DIALOG(gfe), (_("Save as Default")), GTK_RESPONSE_OK);
+ g_signal_connect(G_OBJECT(widget), "clicked", G_CALLBACK(remmina_file_editor_on_default), gfe);
+
+ widget = gtk_dialog_add_button(GTK_DIALOG(gfe), (_("_Save")), GTK_RESPONSE_APPLY);
+ g_signal_connect(G_OBJECT(widget), "clicked", G_CALLBACK(remmina_file_editor_on_save), gfe);
+ gtk_widget_set_sensitive(widget, FALSE);
+ priv->save_button = widget;
+
+ widget = gtk_dialog_add_button(GTK_DIALOG(gfe), (_("Connect")), GTK_RESPONSE_ACCEPT);
+ g_signal_connect(G_OBJECT(widget), "clicked", G_CALLBACK(remmina_file_editor_on_connect), gfe);
+
+ widget = gtk_dialog_add_button(GTK_DIALOG(gfe), (_("_Save and Connect")), GTK_RESPONSE_OK);
+ gtk_widget_set_can_default(widget, TRUE);
+ g_signal_connect(G_OBJECT(widget), "clicked", G_CALLBACK(remmina_file_editor_on_save_connect), gfe);
+
+ gtk_dialog_set_default_response(GTK_DIALOG(gfe), GTK_RESPONSE_OK);
+ gtk_window_set_default_size(GTK_WINDOW(gfe), 800, 600);
+
+ g_signal_connect(G_OBJECT(gfe), "destroy", G_CALLBACK(remmina_file_editor_destroy), NULL);
+ g_signal_connect(G_OBJECT(gfe), "realize", G_CALLBACK(remmina_file_editor_on_realize), NULL);
+
+ priv->setting_widgets = g_hash_table_new(g_str_hash, g_str_equal);
+
+ remmina_widget_pool_register(GTK_WIDGET(gfe));
+}
+
+static gboolean remmina_file_editor_iterate_protocol(gchar* protocol, RemminaPlugin* plugin, gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaFileEditor* gfe = REMMINA_FILE_EDITOR(data);
+ GtkListStore* store;
+ GtkTreeIter iter;
+ gboolean first;
+
+ store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(gfe->priv->protocol_combo)));
+
+ first = !gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);
+
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, 0, protocol, 1, g_dgettext(plugin->domain, plugin->description), 2,
+ ((RemminaProtocolPlugin*)plugin)->icon_name, -1);
+
+ if (first || g_strcmp0(protocol, remmina_file_get_string(gfe->priv->remmina_file, "protocol")) == 0) {
+ gtk_combo_box_set_active_iter(GTK_COMBO_BOX(gfe->priv->protocol_combo), &iter);
+ }
+
+ return FALSE;
+}
+
+static void remmina_file_editor_check_profile(RemminaFileEditor* gfe)
+{
+ TRACE_CALL(__func__);
+ RemminaFileEditorPriv* priv;
+
+ priv = gfe->priv;
+ if (remmina_file_get_filename(priv->remmina_file)) {
+ gtk_widget_set_sensitive(priv->group_combo, TRUE);
+ gtk_widget_set_sensitive(priv->save_button, TRUE);
+ }
+}
+
+static void remmina_file_editor_name_on_changed(GtkEditable* editable, RemminaFileEditor* gfe)
+{
+ TRACE_CALL(__func__);
+ RemminaFileEditorPriv* priv;
+
+ priv = gfe->priv;
+ if (remmina_file_get_filename(priv->remmina_file) == NULL) {
+ remmina_file_generate_filename(priv->remmina_file);
+ remmina_file_editor_check_profile(gfe);
+ }
+}
+
+GtkWidget* remmina_file_editor_new_from_file(RemminaFile* remminafile)
+{
+ TRACE_CALL(__func__);
+ RemminaFileEditor* gfe;
+ RemminaFileEditorPriv* priv;
+ GtkWidget* grid;
+ GtkWidget* widget;
+ gchar* groups;
+ gchar* s;
+ const gchar* cs;
+
+ gfe = REMMINA_FILE_EDITOR(g_object_new(REMMINA_TYPE_FILE_EDITOR, NULL));
+ priv = gfe->priv;
+ priv->remmina_file = remminafile;
+
+ if (remmina_file_get_filename(remminafile) == NULL) {
+ gtk_dialog_set_response_sensitive(GTK_DIALOG(gfe), GTK_RESPONSE_APPLY, FALSE);
+ }
+
+ /* Create the Profile group on the top (for name and protocol) */
+ grid = gtk_grid_new();
+ gtk_widget_show(grid);
+ gtk_grid_set_row_spacing(GTK_GRID(grid), 4);
+ gtk_grid_set_column_spacing(GTK_GRID(grid), 8);
+ gtk_grid_set_column_homogeneous(GTK_GRID(grid), TRUE);
+ gtk_container_set_border_width(GTK_CONTAINER(grid), 8);
+ gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(gfe))), grid, FALSE, FALSE, 2);
+
+ remmina_public_create_group(GTK_GRID(grid), _("Profile"), 0, 4, 3);
+
+ /* Profile: Name */
+ widget = gtk_label_new(_("Name"));
+ gtk_widget_show(widget);
+ gtk_widget_set_valign(widget, GTK_ALIGN_START);
+ gtk_widget_set_halign(widget, GTK_ALIGN_START);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, 3, 2, 1);
+ gtk_grid_set_column_spacing(GTK_GRID(grid), 10);
+
+ widget = gtk_entry_new();
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 1, 3, 3, 1);
+ gtk_entry_set_max_length(GTK_ENTRY(widget), 100);
+ priv->name_entry = widget;
+
+ if (remmina_file_get_filename(remminafile) == NULL) {
+ gtk_entry_set_text(GTK_ENTRY(widget), _("Quick Connect"));
+#if GTK_CHECK_VERSION(3, 16, 0)
+ gtk_entry_grab_focus_without_selecting(GTK_ENTRY(widget));
+#endif
+ g_signal_connect(G_OBJECT(widget), "changed", G_CALLBACK(remmina_file_editor_name_on_changed), gfe);
+ }else {
+ cs = remmina_file_get_string(remminafile, "name");
+ gtk_entry_set_text(GTK_ENTRY(widget), cs ? cs : "");
+ }
+
+ /* Profile: Group */
+ widget = gtk_label_new(_("Group"));
+ gtk_widget_show(widget);
+ gtk_widget_set_valign(widget, GTK_ALIGN_START);
+ gtk_widget_set_halign(widget, GTK_ALIGN_START);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, 6, 2, 1);
+
+ groups = remmina_file_manager_get_groups();
+ priv->group_combo = remmina_public_create_combo_entry(groups, remmina_file_get_string(remminafile, "group"), FALSE);
+ g_free(groups);
+ gtk_widget_show(priv->group_combo);
+ gtk_grid_attach(GTK_GRID(grid), priv->group_combo, 1, 6, 3, 1);
+ gtk_widget_set_sensitive(priv->group_combo, FALSE);
+
+ s = g_strdup_printf(_("Use '%s' as subgroup delimiter"), "/");
+ gtk_widget_set_tooltip_text(priv->group_combo, s);
+ g_free(s);
+
+ /* Profile: Protocol */
+ widget = gtk_label_new(_("Protocol"));
+ gtk_widget_show(widget);
+ gtk_widget_set_valign(widget, GTK_ALIGN_START);
+ gtk_widget_set_halign(widget, GTK_ALIGN_START);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, 9, 2, 1);
+
+ widget = remmina_public_create_combo(TRUE);
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 1, 9, 3, 1);
+ priv->protocol_combo = widget;
+ remmina_plugin_manager_for_each_plugin(REMMINA_PLUGIN_TYPE_PROTOCOL, remmina_file_editor_iterate_protocol, gfe);
+ g_signal_connect(G_OBJECT(widget), "changed", G_CALLBACK(remmina_file_editor_protocol_combo_on_changed), gfe);
+
+ /* Prior Connection Command */
+ widget = gtk_label_new(_("Pre Command"));
+ gtk_widget_show(widget);
+ gtk_widget_set_valign(widget, GTK_ALIGN_START);
+ gtk_widget_set_halign(widget, GTK_ALIGN_START);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, 12, 3, 1);
+ gtk_grid_set_column_spacing(GTK_GRID(grid), 10);
+
+ widget = gtk_entry_new();
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 1, 12, 3, 1);
+ gtk_entry_set_max_length(GTK_ENTRY(widget), 200);
+ priv->precommand_entry = widget;
+ cs = remmina_file_get_string(remminafile, "precommand");
+ gtk_entry_set_text(GTK_ENTRY(widget), cs ? cs : "");
+ gtk_entry_set_placeholder_text(GTK_ENTRY(widget), "command %h %u %t %U %p %g --option");
+ gtk_widget_set_tooltip_markup(widget, _(cmd_tips));
+
+ /* POST Connection Command */
+ widget = gtk_label_new(_("Post Command"));
+ gtk_widget_show(widget);
+ gtk_widget_set_valign(widget, GTK_ALIGN_START);
+ gtk_widget_set_halign(widget, GTK_ALIGN_START);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, 15, 3, 1);
+ gtk_grid_set_column_spacing(GTK_GRID(grid), 10);
+
+ widget = gtk_entry_new();
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 1, 15, 3, 1);
+ gtk_entry_set_max_length(GTK_ENTRY(widget), 200);
+ priv->postcommand_entry = widget;
+ cs = remmina_file_get_string(remminafile, "postcommand");
+ gtk_entry_set_text(GTK_ENTRY(widget), cs ? cs : "");
+ gtk_entry_set_placeholder_text(GTK_ENTRY(widget), "/path/to/command -opt1 arg %h %u %t -opt2 %U %p %g");
+ gtk_widget_set_tooltip_markup(widget, _(cmd_tips));
+
+ /* Create the Preference frame */
+ widget = gtk_event_box_new();
+ gtk_widget_show(widget);
+ gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(gfe))), widget, TRUE, TRUE, 2);
+ priv->config_box = widget;
+
+ priv->config_container = NULL;
+ priv->config_scrollable = NULL;
+
+ remmina_file_editor_protocol_combo_on_changed(GTK_COMBO_BOX(priv->protocol_combo), gfe);
+
+ remmina_file_editor_check_profile(gfe);
+
+ return GTK_WIDGET(gfe);
+}
+
+GtkWidget* remmina_file_editor_new(void)
+{
+ TRACE_CALL(__func__);
+ return remmina_file_editor_new_full(NULL, NULL);
+}
+
+GtkWidget* remmina_file_editor_new_full(const gchar* server, const gchar* protocol)
+{
+ TRACE_CALL(__func__);
+ RemminaFile* remminafile;
+
+ remminafile = remmina_file_new();
+ if (server)
+ remmina_file_set_string(remminafile, "server", server);
+ if (protocol)
+ remmina_file_set_string(remminafile, "protocol", protocol);
+
+ return remmina_file_editor_new_from_file(remminafile);
+}
+
+GtkWidget* remmina_file_editor_new_copy(const gchar* filename)
+{
+ TRACE_CALL(__func__);
+ RemminaFile* remminafile;
+ GtkWidget* dialog;
+
+ remminafile = remmina_file_copy(filename);
+ if (remminafile) {
+ return remmina_file_editor_new_from_file(remminafile);
+ }else {
+ dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
+ _("File %s not found."), filename);
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ return NULL;
+ }
+}
+
+GtkWidget* remmina_file_editor_new_from_filename(const gchar* filename)
+{
+ TRACE_CALL(__func__);
+ RemminaFile* remminafile;
+ GtkWidget* dialog;
+
+ remminafile = remmina_file_manager_load_file(filename);
+ if (remminafile) {
+ return remmina_file_editor_new_from_file(remminafile);
+ }else {
+ dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
+ _("File %s not found."), filename);
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ return NULL;
+ }
+}
diff --git a/src/remmina_file_editor.h b/src/remmina_file_editor.h
new file mode 100644
index 000000000..e2e3aa477
--- /dev/null
+++ b/src/remmina_file_editor.h
@@ -0,0 +1,74 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2011 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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.
+ *
+ */
+
+#pragma once
+
+G_BEGIN_DECLS
+
+#define REMMINA_TYPE_FILE_EDITOR (remmina_file_editor_get_type())
+#define REMMINA_FILE_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), REMMINA_TYPE_FILE_EDITOR, RemminaFileEditor))
+#define REMMINA_FILE_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), REMMINA_TYPE_FILE_EDITOR, RemminaFileEditorClass))
+#define REMMINA_IS_FILE_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), REMMINA_TYPE_FILE_EDITOR))
+#define REMMINA_IS_FILE_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), REMMINA_TYPE_FILE_EDITOR))
+#define REMMINA_FILE_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), REMMINA_TYPE_FILE_EDITOR, RemminaFileEditorClass))
+
+typedef struct _RemminaFileEditorPriv RemminaFileEditorPriv;
+
+typedef struct _RemminaFileEditor {
+ GtkDialog dialog;
+
+ RemminaFileEditorPriv* priv;
+} RemminaFileEditor;
+
+typedef struct _RemminaFileEditorClass {
+ GtkDialogClass parent_class;
+} RemminaFileEditorClass;
+
+GType remmina_file_editor_get_type(void)
+G_GNUC_CONST;
+
+/* Base constructor */
+GtkWidget* remmina_file_editor_new_from_file(RemminaFile* remminafile);
+/* Create new file */
+GtkWidget* remmina_file_editor_new(void);
+GtkWidget* remmina_file_editor_new_full(const gchar* server, const gchar* protocol);
+GtkWidget* remmina_file_editor_new_copy(const gchar* filename);
+/* Open existing file */
+GtkWidget* remmina_file_editor_new_from_filename(const gchar* filename);
+
+G_END_DECLS
+
+
diff --git a/src/remmina_file_manager.c b/src/remmina_file_manager.c
new file mode 100644
index 000000000..e699f2b44
--- /dev/null
+++ b/src/remmina_file_manager.c
@@ -0,0 +1,339 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2010 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 "config.h"
+
+#include <gtk/gtk.h>
+#include <string.h>
+
+#include "remmina_public.h"
+#include "remmina_string_array.h"
+#include "remmina_plugin_manager.h"
+#include "remmina_file_manager.h"
+#include "remmina/remmina_trace_calls.h"
+
+static gchar *remminadir;
+static gchar *cachedir;
+
+/* return first found data dir as per XDG specs.
+ * The returned string must be freed by the caller with g_free */
+gchar *remmina_file_get_datadir(void)
+{
+ TRACE_CALL(__func__);
+ gchar *dir = g_strdup_printf(".%s", g_get_prgname());
+ int i;
+ /* Legacy ~/.remmina */
+ remminadir = g_build_path("/", g_get_home_dir(), dir, NULL);
+ g_free(dir);
+ if (g_file_test(remminadir, G_FILE_TEST_IS_DIR))
+ return remminadir;
+ g_free(remminadir), remminadir = NULL;
+ /* ~/.local/share/remmina */
+ remminadir = g_build_path( "/", g_get_user_data_dir(), g_get_prgname(), NULL);
+ if (g_file_test(remminadir, G_FILE_TEST_IS_DIR))
+ return remminadir;
+ g_free(remminadir), remminadir = NULL;
+ /* /usr/local/share/remmina */
+ const gchar * const *dirs = g_get_system_data_dirs();
+ g_free(remminadir), remminadir = NULL;
+ for (i = 0; dirs[i] != NULL; ++i) {
+ remminadir = g_build_path( "/", dirs[i], g_get_prgname(), NULL);
+ if (g_file_test(remminadir, G_FILE_TEST_IS_DIR))
+ return remminadir;
+ g_free(remminadir), remminadir = NULL;
+ }
+ /* The last case we use the home ~/.local/share/remmina */
+ remminadir = g_build_path( "/", g_get_user_data_dir(), g_get_prgname(), NULL);
+ return remminadir;
+}
+
+/** @todo remmina_pref_file_do_copy and remmina_file_manager_do_copy to remmina_files_copy */
+static gboolean remmina_file_manager_do_copy(const char *src_path, const char *dst_path)
+{
+ GFile *src = g_file_new_for_path(src_path), *dst = g_file_new_for_path(dst_path);
+ /* We don't overwrite the target if it exists */
+ const gboolean ok = g_file_copy(src, dst, G_FILE_COPY_NONE, NULL, NULL, NULL, NULL);
+
+ g_object_unref(dst);
+ g_object_unref(src);
+
+ return ok;
+}
+
+void remmina_file_manager_init(void)
+{
+ TRACE_CALL(__func__);
+ GDir *dir;
+ gchar *legacy = g_strdup_printf(".%s", g_get_prgname());
+ const gchar *filename;
+ int i;
+
+ remminadir = g_build_path( "/", g_get_user_data_dir(), g_get_prgname(), NULL);
+ /* Create the XDG_USER_DATA directory */
+ g_mkdir_with_parents(remminadir, 0750);
+ g_free(remminadir), remminadir = NULL;
+ /* Create the XDG_CACHE_HOME directory */
+ cachedir = g_build_path( "/", g_get_user_cache_dir(), g_get_prgname(), NULL);
+ g_mkdir_with_parents(cachedir, 0750);
+ g_free(cachedir), cachedir = NULL;
+ /* Empty legacy ~/.remmina */
+ remminadir = g_build_path("/", g_get_home_dir(), legacy, NULL);
+ if (g_file_test(remminadir, G_FILE_TEST_IS_DIR)) {
+ dir = g_dir_open(remminadir, 0, NULL);
+ while ((filename = g_dir_read_name(dir)) != NULL) {
+ remmina_file_manager_do_copy(
+ g_build_path( "/", remminadir, filename, NULL),
+ g_build_path( "/", g_get_user_data_dir(),
+ g_get_prgname(), filename, NULL));
+ }
+ }
+
+ /* XDG_DATA_DIRS, i.e. /usr/local/share/remmina */
+ const gchar * const *dirs = g_get_system_data_dirs();
+ g_free(remminadir), remminadir = NULL;
+ for (i = 0; dirs[i] != NULL; ++i) {
+ remminadir = g_build_path( "/", dirs[i], g_get_prgname(), NULL);
+ if (g_file_test(remminadir, G_FILE_TEST_IS_DIR)) {
+ dir = g_dir_open(remminadir, 0, NULL);
+ while ((filename = g_dir_read_name(dir)) != NULL) {
+ remmina_file_manager_do_copy(
+ g_build_path( "/", remminadir, filename, NULL),
+ g_build_path( "/", g_get_user_data_dir(),
+ g_get_prgname(), filename, NULL));
+ }
+ }
+ g_free(remminadir), remminadir = NULL;
+ }
+ /* At last we make sure we use XDG_USER_DATA */
+ if (remminadir != NULL)
+ g_free(remminadir), remminadir = NULL;
+}
+
+gint remmina_file_manager_iterate(GFunc func, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ gchar filename[MAX_PATH_LEN];
+ GDir* dir;
+ const gchar* name;
+ RemminaFile* remminafile;
+ gint items_count = 0;
+ gchar* remmina_data_dir;
+
+ remmina_data_dir = remmina_file_get_datadir();
+ dir = g_dir_open(remmina_data_dir, 0, NULL);
+
+ if (dir) {
+ while ((name = g_dir_read_name(dir)) != NULL) {
+ if (!g_str_has_suffix(name, ".remmina"))
+ continue;
+ g_snprintf(filename, MAX_PATH_LEN, "%s/%s",
+ remmina_data_dir, name);
+ remminafile = remmina_file_load(filename);
+ if (remminafile) {
+ (*func)(remminafile, user_data);
+ remmina_file_free(remminafile);
+ items_count++;
+ }
+ }
+ g_dir_close(dir);
+ }
+ g_free(remmina_data_dir);
+ return items_count;
+}
+
+gchar* remmina_file_manager_get_groups(void)
+{
+ TRACE_CALL(__func__);
+ gchar filename[MAX_PATH_LEN];
+ GDir* dir;
+ const gchar* name;
+ RemminaFile* remminafile;
+ RemminaStringArray* array;
+ const gchar* group;
+ gchar* groups;
+ gchar* remmina_data_dir;
+
+ remmina_data_dir = remmina_file_get_datadir();
+ array = remmina_string_array_new();
+
+ dir = g_dir_open(remmina_data_dir, 0, NULL);
+
+ if (dir == NULL)
+ return 0;
+ while ((name = g_dir_read_name(dir)) != NULL) {
+ if (!g_str_has_suffix(name, ".remmina"))
+ continue;
+ g_snprintf(filename, MAX_PATH_LEN, "%s/%s", remmina_data_dir, name);
+ remminafile = remmina_file_load(filename);
+ group = remmina_file_get_string(remminafile, "group");
+ if (group && remmina_string_array_find(array, group) < 0) {
+ remmina_string_array_add(array, group);
+ }
+ remmina_file_free(remminafile);
+ }
+ g_dir_close(dir);
+ remmina_string_array_sort(array);
+ groups = remmina_string_array_to_string(array);
+ remmina_string_array_free(array);
+ g_free(remmina_data_dir);
+ return groups;
+}
+
+static void remmina_file_manager_add_group(GNode* node, const gchar* group)
+{
+ TRACE_CALL(__func__);
+ gint cmp;
+ gchar* p1;
+ gchar* p2;
+ GNode* child;
+ gboolean found;
+ RemminaGroupData* data;
+
+ if (node == NULL)
+ return;
+
+ if (group == NULL || group[0] == '\0')
+ return;
+
+ p1 = g_strdup(group);
+ p2 = strchr(p1, '/');
+
+ if (p2)
+ *p2++ = '\0';
+
+ found = FALSE;
+
+ for (child = g_node_first_child(node); child; child = g_node_next_sibling(child)) {
+ cmp = g_strcmp0(((RemminaGroupData*)child->data)->name, p1);
+
+ if (cmp == 0) {
+ found = TRUE;
+ break;
+ }
+
+ if (cmp > 0)
+ break;
+ }
+
+ if (!found) {
+ data = g_new0(RemminaGroupData, 1);
+ data->name = p1;
+ if (node->data) {
+ data->group = g_strdup_printf("%s/%s", ((RemminaGroupData*)node->data)->group, p1);
+ }else {
+ data->group = g_strdup(p1);
+ }
+ if (child) {
+ child = g_node_insert_data_before(node, child, data);
+ }else {
+ child = g_node_append_data(node, data);
+ }
+ }
+ remmina_file_manager_add_group(child, p2);
+
+ if (found) {
+ g_free(p1);
+ }
+}
+
+GNode* remmina_file_manager_get_group_tree(void)
+{
+ TRACE_CALL(__func__);
+ gchar filename[MAX_PATH_LEN];
+ GDir* dir;
+ const gchar* name;
+ RemminaFile* remminafile;
+ const gchar* group;
+ GNode* root;
+
+ root = g_node_new(NULL);
+
+ dir = g_dir_open(remmina_file_get_datadir(), 0, NULL);
+
+ if (dir == NULL)
+ return root;
+ while ((name = g_dir_read_name(dir)) != NULL) {
+ if (!g_str_has_suffix(name, ".remmina"))
+ continue;
+ g_snprintf(filename, MAX_PATH_LEN, "%s/%s", remmina_file_get_datadir(), name);
+ remminafile = remmina_file_load(filename);
+ group = remmina_file_get_string(remminafile, "group");
+ remmina_file_manager_add_group(root, group);
+ remmina_file_free(remminafile);
+ }
+ g_dir_close(dir);
+ return root;
+}
+
+void remmina_file_manager_free_group_tree(GNode* node)
+{
+ TRACE_CALL(__func__);
+ RemminaGroupData* data;
+ GNode* child;
+
+ if (!node)
+ return;
+ data = (RemminaGroupData*)node->data;
+ if (data) {
+ g_free(data->name);
+ g_free(data->group);
+ g_free(data);
+ node->data = NULL;
+ }
+ for (child = g_node_first_child(node); child; child = g_node_next_sibling(child)) {
+ remmina_file_manager_free_group_tree(child);
+ }
+ g_node_unlink(node);
+}
+
+RemminaFile* remmina_file_manager_load_file(const gchar* filename)
+{
+ TRACE_CALL(__func__);
+ RemminaFile* remminafile = NULL;
+ RemminaFilePlugin* plugin;
+ gchar* p;
+
+ if ((p = strrchr(filename, '.')) != NULL && g_strcmp0(p + 1, "remmina") == 0) {
+ remminafile = remmina_file_load(filename);
+ }else {
+ plugin = remmina_plugin_manager_get_import_file_handler(filename);
+ if (plugin) {
+ remminafile = plugin->import_func(filename);
+ }
+ }
+ return remminafile;
+}
+
diff --git a/src/remmina_file_manager.h b/src/remmina_file_manager.h
new file mode 100644
index 000000000..b53bf40bd
--- /dev/null
+++ b/src/remmina_file_manager.h
@@ -0,0 +1,62 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2010 Vic Lee
+ * Copyright (C) 2017-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.
+ *
+ */
+
+#pragma once
+
+#include "remmina_file.h"
+
+G_BEGIN_DECLS
+
+typedef struct _RemminaGroupData {
+ gchar *name;
+ gchar *group;
+ gchar *datetime;
+} RemminaGroupData;
+
+/* Initialize */
+gchar* remmina_file_get_datadir(void);
+void remmina_file_manager_init(void);
+/* Iterate all .remmina connections in the home directory */
+gint remmina_file_manager_iterate(GFunc func, gpointer user_data);
+/* Get a list of groups */
+gchar* remmina_file_manager_get_groups(void);
+GNode* remmina_file_manager_get_group_tree(void);
+void remmina_file_manager_free_group_tree(GNode *node);
+/* Load or import a file */
+RemminaFile* remmina_file_manager_load_file(const gchar *filename);
+
+G_END_DECLS
+
+
diff --git a/src/remmina_ftp_client.c b/src/remmina_ftp_client.c
new file mode 100644
index 000000000..630bc06eb
--- /dev/null
+++ b/src/remmina_ftp_client.c
@@ -0,0 +1,1249 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2011 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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.
+ *
+ */
+
+#define _FILE_OFFSET_BITS 64
+
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include "config.h"
+#include "remmina_public.h"
+#include "remmina_pref.h"
+#include "remmina_marshals.h"
+#include "remmina_file.h"
+#include "remmina_ftp_client.h"
+#include "remmina_masterthread_exec.h"
+#include "remmina/remmina_trace_calls.h"
+
+/* -------------------- RemminaCellRendererPixbuf ----------------------- */
+/* A tiny cell renderer that extends the default pixbuf cell render to accept activation */
+
+#define REMMINA_TYPE_CELL_RENDERER_PIXBUF \
+ (remmina_cell_renderer_pixbuf_get_type())
+#define REMMINA_CELL_RENDERER_PIXBUF(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), REMMINA_TYPE_CELL_RENDERER_PIXBUF, RemminaCellRendererPixbuf))
+#define REMMINA_CELL_RENDERER_PIXBUF_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), REMMINA_TYPE_CELL_RENDERER_PIXBUF, RemminaCellRendererPixbufClass))
+#define REMMINA_IS_CELL_RENDERER_PIXBUF(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), REMMINA_TYPE_CELL_RENDERER_PIXBUF))
+
+typedef struct _RemminaCellRendererPixbuf {
+ GtkCellRendererPixbuf renderer;
+} RemminaCellRendererPixbuf;
+
+typedef struct _RemminaCellRendererPixbufClass {
+ GtkCellRendererPixbufClass parent_class;
+
+ void (*activate)(RemminaCellRendererPixbuf * renderer);
+} RemminaCellRendererPixbufClass;
+
+GType remmina_cell_renderer_pixbuf_get_type(void)
+G_GNUC_CONST;
+
+G_DEFINE_TYPE(RemminaCellRendererPixbuf, remmina_cell_renderer_pixbuf, GTK_TYPE_CELL_RENDERER_PIXBUF)
+
+static guint remmina_cell_renderer_pixbuf_signals[1] =
+{ 0 };
+
+static gboolean remmina_cell_renderer_pixbuf_activate(GtkCellRenderer *renderer, GdkEvent *event, GtkWidget *widget,
+ const gchar *path, const GdkRectangle *background_area, const GdkRectangle *cell_area,
+ GtkCellRendererState flags)
+{
+ TRACE_CALL(__func__);
+ g_signal_emit(G_OBJECT(renderer), remmina_cell_renderer_pixbuf_signals[0], 0, path);
+ return TRUE;
+}
+
+static void remmina_cell_renderer_pixbuf_class_init(RemminaCellRendererPixbufClass *klass)
+{
+ TRACE_CALL(__func__);
+ GtkCellRendererClass *renderer_class = GTK_CELL_RENDERER_CLASS(klass);
+
+ renderer_class->activate = remmina_cell_renderer_pixbuf_activate;
+
+ remmina_cell_renderer_pixbuf_signals[0] = g_signal_new("activate", G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaCellRendererPixbufClass, activate), NULL,
+ NULL, g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING);
+}
+
+static void remmina_cell_renderer_pixbuf_init(RemminaCellRendererPixbuf *renderer)
+{
+ TRACE_CALL(__func__);
+ g_object_set(G_OBJECT(renderer), "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE, NULL);
+}
+
+static GtkCellRenderer*
+remmina_cell_renderer_pixbuf_new(void)
+{
+ TRACE_CALL(__func__);
+ GtkCellRenderer *renderer;
+
+ renderer = GTK_CELL_RENDERER(g_object_new(REMMINA_TYPE_CELL_RENDERER_PIXBUF, NULL));
+ return renderer;
+}
+
+/* --------------------- RemminaFTPClient ----------------------------*/
+G_DEFINE_TYPE( RemminaFTPClient, remmina_ftp_client, GTK_TYPE_GRID)
+
+#define BUSY_CURSOR \
+ if (GDK_IS_WINDOW(gtk_widget_get_window(GTK_WIDGET(client)))) \
+ { \
+ gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(client)), gdk_cursor_new_for_display(gdk_display_get_default(), GDK_WATCH)); \
+ gdk_display_flush(gdk_display_get_default()); \
+ }
+
+#define NORMAL_CURSOR \
+ if (GDK_IS_WINDOW(gtk_widget_get_window(GTK_WIDGET(client)))) \
+ { \
+ gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(client)), NULL); \
+ }
+
+struct _RemminaFTPClientPriv {
+ GtkWidget *directory_combo;
+ GtkWidget *vpaned;
+
+ GtkTreeModel *file_list_model;
+ GtkTreeModel *file_list_filter;
+ GtkTreeModel *file_list_sort;
+ GtkWidget *file_list_view;
+ gboolean file_list_show_hidden;
+
+ GtkTreeModel *task_list_model;
+ GtkWidget *task_list_view;
+
+ gchar *current_directory;
+ gchar *working_directory;
+
+ GtkWidget *file_action_widgets[10];
+ gboolean sensitive;
+ gboolean overwrite_all;
+};
+
+static gint remmina_ftp_client_taskid = 1;
+
+enum {
+ OPEN_DIR_SIGNAL, NEW_TASK_SIGNAL, CANCEL_TASK_SIGNAL, DELETE_FILE_SIGNAL, LAST_SIGNAL
+};
+
+static guint remmina_ftp_client_signals[LAST_SIGNAL] =
+{ 0 };
+
+static void remmina_ftp_client_class_init(RemminaFTPClientClass *klass)
+{
+ TRACE_CALL(__func__);
+ remmina_ftp_client_signals[OPEN_DIR_SIGNAL] = g_signal_new("open-dir", G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaFTPClientClass, open_dir), NULL, NULL,
+ g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING);
+ remmina_ftp_client_signals[NEW_TASK_SIGNAL] = g_signal_new("new-task", G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaFTPClientClass, new_task), NULL, NULL,
+ g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
+ remmina_ftp_client_signals[CANCEL_TASK_SIGNAL] = g_signal_new("cancel-task", G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaFTPClientClass, cancel_task), NULL, NULL,
+ remmina_marshal_BOOLEAN__INT, G_TYPE_BOOLEAN, 1, G_TYPE_INT);
+ remmina_ftp_client_signals[DELETE_FILE_SIGNAL] = g_signal_new("delete-file", G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaFTPClientClass, delete_file), NULL, NULL,
+ remmina_marshal_BOOLEAN__INT_STRING, G_TYPE_BOOLEAN, 2, G_TYPE_INT, G_TYPE_STRING);
+}
+
+static void remmina_ftp_client_destroy(RemminaFTPClient *client, gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaFTPClientPriv *priv = (RemminaFTPClientPriv*)client->priv;
+ g_free(priv->current_directory);
+ g_free(priv->working_directory);
+ g_free(priv);
+}
+
+static void remmina_ftp_client_cell_data_filetype_pixbuf(GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model,
+ GtkTreeIter *iter, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ gint type;
+
+ /* Same as REMMINA_FTP_TASK_COLUMN_TYPE */
+ gtk_tree_model_get(model, iter, REMMINA_FTP_FILE_COLUMN_TYPE, &type, -1);
+
+ switch (type) {
+ case REMMINA_FTP_FILE_TYPE_DIR:
+ g_object_set(renderer, "stock-id", "folder", NULL);
+ break;
+ default:
+ g_object_set(renderer, "stock-id", "text-x-generic", NULL);
+ break;
+ }
+}
+
+static void remmina_ftp_client_cell_data_progress_pixbuf(GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model,
+ GtkTreeIter *iter, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ gint tasktype, status;
+
+ gtk_tree_model_get(model, iter, REMMINA_FTP_TASK_COLUMN_TASKTYPE, &tasktype, REMMINA_FTP_TASK_COLUMN_STATUS, &status,
+ -1);
+
+ switch (status) {
+ case REMMINA_FTP_TASK_STATUS_WAIT:
+ g_object_set(renderer, "stock-id", "P_ause", NULL);
+ break;
+ case REMMINA_FTP_TASK_STATUS_RUN:
+ g_object_set(renderer, "stock-id",
+ (tasktype == REMMINA_FTP_TASK_TYPE_UPLOAD ? "go-up" : "go-down"), NULL);
+ break;
+ case REMMINA_FTP_TASK_STATUS_FINISH:
+ g_object_set(renderer, "stock-id", "_Yes", NULL);
+ break;
+ case REMMINA_FTP_TASK_STATUS_ERROR:
+ g_object_set(renderer, "stock-id", "_No", NULL);
+ break;
+ }
+}
+
+static gchar*
+remmina_ftp_client_size_to_str(gfloat size)
+{
+ TRACE_CALL(__func__);
+ gchar *str;
+
+ if (size < 1024.0) {
+ str = g_strdup_printf("%i", (gint)size);
+ }else if (size < 1024.0 * 1024.0) {
+ str = g_strdup_printf("%iK", (gint)(size / 1024.0));
+ }else if (size < 1024.0 * 1024.0 * 1024.0) {
+ str = g_strdup_printf("%.1fM", size / 1024.0 / 1024.0);
+ }else {
+ str = g_strdup_printf("%.1fG", size / 1024.0 / 1024.0 / 1024.0);
+ }
+ return str;
+}
+
+static void remmina_ftp_client_cell_data_size(GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model,
+ GtkTreeIter *iter, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ gfloat size;
+ gchar *str;
+
+ gtk_tree_model_get(model, iter, REMMINA_FTP_FILE_COLUMN_SIZE, &size, -1);
+
+ str = remmina_ftp_client_size_to_str(size);
+ g_object_set(renderer, "text", str, NULL);
+ g_object_set(renderer, "xalign", 1.0, NULL);
+ g_free(str);
+}
+
+static void remmina_ftp_client_cell_data_permission(GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model,
+ GtkTreeIter *iter, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ gint permission;
+ gchar buf[11];
+
+ gtk_tree_model_get(model, iter, REMMINA_FTP_FILE_COLUMN_PERMISSION, &permission, -1);
+
+ buf[0] = (permission & 040000 ? 'd' : '-');
+ buf[1] = (permission & 0400 ? 'r' : '-');
+ buf[2] = (permission & 0200 ? 'w' : '-');
+ buf[3] = (permission & 0100 ? 'x' : '-');
+ buf[4] = (permission & 040 ? 'r' : '-');
+ buf[5] = (permission & 020 ? 'w' : '-');
+ buf[6] = (permission & 010 ? 'x' : '-');
+ buf[7] = (permission & 04 ? 'r' : '-');
+ buf[8] = (permission & 02 ? 'w' : '-');
+ buf[9] = (permission & 01 ? 'x' : '-');
+ buf[10] = '\0';
+
+ g_object_set(renderer, "text", buf, NULL);
+}
+
+static void remmina_ftp_client_cell_data_size_progress(GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model,
+ GtkTreeIter *iter, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ gint status;
+ gfloat size, donesize;
+ gchar *strsize, *strdonesize, *str;
+
+ gtk_tree_model_get(model, iter, REMMINA_FTP_TASK_COLUMN_STATUS, &status, REMMINA_FTP_TASK_COLUMN_SIZE, &size,
+ REMMINA_FTP_TASK_COLUMN_DONESIZE, &donesize, -1);
+
+ if (status == REMMINA_FTP_TASK_STATUS_FINISH) {
+ str = remmina_ftp_client_size_to_str(size);
+ }else {
+ strsize = remmina_ftp_client_size_to_str(size);
+ strdonesize = remmina_ftp_client_size_to_str(donesize);
+ str = g_strdup_printf("%s / %s", strdonesize, strsize);
+ g_free(strsize);
+ g_free(strdonesize);
+ }
+
+ g_object_set(renderer, "text", str, NULL);
+ g_object_set(renderer, "xalign", 1.0, NULL);
+ g_free(str);
+}
+
+static void remmina_ftp_client_cell_data_progress(GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model,
+ GtkTreeIter *iter, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ gint status;
+ gfloat size, donesize;
+ gint progress;
+
+ gtk_tree_model_get(model, iter, REMMINA_FTP_TASK_COLUMN_STATUS, &status, REMMINA_FTP_TASK_COLUMN_SIZE, &size,
+ REMMINA_FTP_TASK_COLUMN_DONESIZE, &donesize, -1);
+ if (status == REMMINA_FTP_TASK_STATUS_FINISH) {
+ progress = 100;
+ }else {
+ if (size <= 1) {
+ progress = 0;
+ }else {
+ progress = (gint)(donesize / size * 100);
+ if (progress > 99)
+ progress = 99;
+ }
+ }
+ g_object_set(renderer, "value", progress, NULL);
+}
+
+static void remmina_ftp_client_open_dir(RemminaFTPClient *client, const gchar *dir)
+{
+ TRACE_CALL(__func__);
+ BUSY_CURSOR
+ g_signal_emit(G_OBJECT(client), remmina_ftp_client_signals[OPEN_DIR_SIGNAL], 0, dir);
+ NORMAL_CURSOR
+}
+
+static void remmina_ftp_client_dir_on_activate(GtkWidget *widget, RemminaFTPClient *client)
+{
+ TRACE_CALL(__func__);
+ remmina_ftp_client_open_dir(client, gtk_entry_get_text(GTK_ENTRY(widget)));
+}
+
+static void remmina_ftp_client_dir_on_changed(GtkWidget *widget, RemminaFTPClient *client)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *entry = gtk_bin_get_child(GTK_BIN(widget));
+
+ if (!gtk_widget_is_focus(entry)) {
+ gtk_widget_grab_focus(entry);
+ /* If the text was changed but the entry is not the focus, it should be changed by the drop-down list.
+ Not sure this will always work in the future, but it works right now :) */
+ remmina_ftp_client_open_dir(client, gtk_entry_get_text(GTK_ENTRY(entry)));
+ }
+}
+
+static void remmina_ftp_client_set_file_action_sensitive(RemminaFTPClient *client, gboolean sensitive)
+{
+ TRACE_CALL(__func__);
+ gint i;
+ for (i = 0; client->priv->file_action_widgets[i]; i++) {
+ gtk_widget_set_sensitive(client->priv->file_action_widgets[i], sensitive);
+ }
+ client->priv->sensitive = sensitive;
+}
+
+static void remmina_ftp_client_file_selection_on_changed(GtkTreeSelection *selection, RemminaFTPClient *client)
+{
+ TRACE_CALL(__func__);
+ GList *list;
+
+ list = gtk_tree_selection_get_selected_rows(selection, NULL);
+ remmina_ftp_client_set_file_action_sensitive(client, (list ? TRUE : FALSE));
+ g_list_free(list);
+}
+
+static gchar*
+remmina_ftp_client_get_download_dir(RemminaFTPClient *client)
+{
+ TRACE_CALL(__func__);
+ RemminaFTPClientPriv *priv = (RemminaFTPClientPriv*)client->priv;
+ GtkWidget *dialog;
+ gchar *localdir = NULL;
+
+ dialog = gtk_file_chooser_dialog_new(_("Choose download location"),
+ GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(client))), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
+ "_Cancel", GTK_RESPONSE_CANCEL, "_OK", GTK_RESPONSE_ACCEPT, NULL);
+ if (priv->working_directory) {
+ gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), priv->working_directory);
+ }
+ if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
+ g_free(priv->working_directory);
+ priv->working_directory = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(dialog));
+ localdir = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
+ }
+ gtk_widget_destroy(dialog);
+ return localdir;
+}
+
+static void remmina_ftp_client_download(RemminaFTPClient *client, GtkTreeIter *piter, const gchar *localdir)
+{
+ TRACE_CALL(__func__);
+ RemminaFTPClientPriv *priv = (RemminaFTPClientPriv*)client->priv;
+ GtkListStore *store = GTK_LIST_STORE(priv->task_list_model);
+ GtkTreeIter iter;
+ gint type;
+ gchar *name;
+ gfloat size;
+
+ gtk_tree_model_get(priv->file_list_sort, piter, REMMINA_FTP_FILE_COLUMN_TYPE, &type, REMMINA_FTP_FILE_COLUMN_NAME,
+ &name, REMMINA_FTP_FILE_COLUMN_SIZE, &size, -1);
+
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, REMMINA_FTP_TASK_COLUMN_TYPE, type, REMMINA_FTP_TASK_COLUMN_NAME, name,
+ REMMINA_FTP_TASK_COLUMN_SIZE, size, REMMINA_FTP_TASK_COLUMN_TASKID, remmina_ftp_client_taskid++,
+ REMMINA_FTP_TASK_COLUMN_TASKTYPE, REMMINA_FTP_TASK_TYPE_DOWNLOAD, REMMINA_FTP_TASK_COLUMN_REMOTEDIR,
+ priv->current_directory, REMMINA_FTP_TASK_COLUMN_LOCALDIR, localdir, REMMINA_FTP_TASK_COLUMN_STATUS,
+ REMMINA_FTP_TASK_STATUS_WAIT, REMMINA_FTP_TASK_COLUMN_DONESIZE, 0.0, REMMINA_FTP_TASK_COLUMN_TOOLTIP,
+ NULL, -1);
+
+ g_free(name);
+
+ g_signal_emit(G_OBJECT(client), remmina_ftp_client_signals[NEW_TASK_SIGNAL], 0);
+}
+
+static gboolean remmina_ftp_client_task_list_on_query_tooltip(GtkWidget *widget, gint x, gint y, gboolean keyboard_tip,
+ GtkTooltip *tooltip, RemminaFTPClient *client)
+{
+ TRACE_CALL(__func__);
+ RemminaFTPClientPriv *priv = (RemminaFTPClientPriv*)client->priv;
+ GtkTreeIter iter;
+ GtkTreePath *path = NULL;
+ gchar *tmp;
+
+ if (!gtk_tree_view_get_tooltip_context(GTK_TREE_VIEW(priv->task_list_view), &x, &y, keyboard_tip, NULL, &path, &iter)) {
+ return FALSE;
+ }
+
+ gtk_tree_model_get(priv->task_list_model, &iter, REMMINA_FTP_TASK_COLUMN_TOOLTIP, &tmp, -1);
+ if (!tmp)
+ return FALSE;
+
+ gtk_tooltip_set_text(tooltip, tmp);
+
+ gtk_tree_view_set_tooltip_row(GTK_TREE_VIEW(priv->task_list_view), tooltip, path);
+
+ gtk_tree_path_free(path);
+ g_free(tmp);
+
+ return TRUE;
+}
+
+static void remmina_ftp_client_action_parent(GObject *object, RemminaFTPClient *client)
+{
+ TRACE_CALL(__func__);
+ remmina_ftp_client_open_dir(client, "..");
+}
+
+static void remmina_ftp_client_action_home(GObject *object, RemminaFTPClient *client)
+{
+ TRACE_CALL(__func__);
+ remmina_ftp_client_open_dir(client, NULL);
+}
+
+static void remmina_ftp_client_action_refresh(GObject *object, RemminaFTPClient *client)
+{
+ TRACE_CALL(__func__);
+ remmina_ftp_client_open_dir(client, ".");
+}
+
+static void remmina_ftp_client_action_download(GObject *object, RemminaFTPClient *client)
+{
+ TRACE_CALL(__func__);
+ RemminaFTPClientPriv *priv = (RemminaFTPClientPriv*)client->priv;
+ GtkTreeSelection *selection;
+ gchar *localdir;
+ GList *list, *list_iter;
+ GtkTreeIter iter;
+
+ selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->file_list_view));
+ if (!selection)
+ return;
+ list = gtk_tree_selection_get_selected_rows(selection, NULL);
+ if (!list)
+ return;
+
+ localdir = remmina_ftp_client_get_download_dir(client);
+ if (!localdir) {
+ g_list_free(list);
+ return;
+ }
+
+ list_iter = g_list_first(list);
+ while (list_iter) {
+ gtk_tree_model_get_iter(priv->file_list_sort, &iter, (GtkTreePath*)list_iter->data);
+ remmina_ftp_client_download(client, &iter, localdir);
+ list_iter = g_list_next(list_iter);
+ }
+ g_list_free(list);
+ g_free(localdir);
+}
+
+static void remmina_ftp_client_action_delete(GObject *object, RemminaFTPClient *client)
+{
+ TRACE_CALL(__func__);
+ RemminaFTPClientPriv *priv = (RemminaFTPClientPriv*)client->priv;
+ GtkWidget *dialog;
+ GtkTreeSelection *selection;
+ GList *list, *list_iter;
+ GtkTreeIter iter;
+ gint type;
+ gchar *name;
+ gchar *path;
+ gint response;
+ gboolean ret = TRUE;
+
+ selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->file_list_view));
+ if (!selection)
+ return;
+ list = gtk_tree_selection_get_selected_rows(selection, NULL);
+ if (!list)
+ return;
+
+ dialog = gtk_message_dialog_new(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(client))), GTK_DIALOG_MODAL,
+ GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, _("Are you sure to delete the selected files on server?"));
+ response = gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ if (response != GTK_RESPONSE_YES)
+ return;
+
+ BUSY_CURSOR
+
+ list_iter = g_list_first(list);
+ while (list_iter) {
+ gtk_tree_model_get_iter(priv->file_list_sort, &iter, (GtkTreePath*)list_iter->data);
+
+ gtk_tree_model_get(priv->file_list_sort, &iter, REMMINA_FTP_FILE_COLUMN_TYPE, &type,
+ REMMINA_FTP_FILE_COLUMN_NAME, &name, -1);
+
+ path = remmina_public_combine_path(priv->current_directory, name);
+ g_signal_emit(G_OBJECT(client), remmina_ftp_client_signals[DELETE_FILE_SIGNAL], 0, type, path, &ret);
+ g_free(name);
+ g_free(path);
+ if (!ret)
+ break;
+
+ list_iter = g_list_next(list_iter);
+ }
+ g_list_free(list);
+
+ NORMAL_CURSOR
+
+ if (ret) {
+ remmina_ftp_client_action_refresh(object, client);
+ }
+}
+
+static void remmina_ftp_client_upload_folder_on_toggled(GtkToggleButton *togglebutton, GtkWidget *widget)
+{
+ TRACE_CALL(__func__);
+ gtk_file_chooser_set_action(
+ GTK_FILE_CHOOSER(widget),
+ gtk_toggle_button_get_active(togglebutton) ?
+ GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER : GTK_FILE_CHOOSER_ACTION_OPEN);
+}
+
+static void remmina_ftp_client_action_upload(GObject *object, RemminaFTPClient *client)
+{
+ TRACE_CALL(__func__);
+ RemminaFTPClientPriv *priv = (RemminaFTPClientPriv*)client->priv;
+ GtkListStore *store = GTK_LIST_STORE(priv->task_list_model);
+ GtkTreeIter iter;
+ GtkWidget *dialog;
+ GtkWidget *upload_folder_check;
+ gint type;
+ GSList *files = NULL;
+ GSList *element;
+ gchar *path;
+ gchar *dir, *name;
+ struct stat st;
+
+ dialog = gtk_file_chooser_dialog_new(_("Choose a file to upload"),
+ GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(client))), GTK_FILE_CHOOSER_ACTION_OPEN, "_Cancel",
+ GTK_RESPONSE_CANCEL, "_OK", GTK_RESPONSE_ACCEPT, NULL);
+ gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
+ if (priv->working_directory) {
+ gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), priv->working_directory);
+ }
+ upload_folder_check = gtk_check_button_new_with_label(_("Upload folder"));
+ gtk_widget_show(upload_folder_check);
+ g_signal_connect(G_OBJECT(upload_folder_check), "toggled", G_CALLBACK(remmina_ftp_client_upload_folder_on_toggled),
+ dialog);
+ gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(dialog), upload_folder_check);
+ if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
+ g_free(priv->working_directory);
+ priv->working_directory = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(dialog));
+ files = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
+ }
+ type = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(upload_folder_check)) ?
+ REMMINA_FTP_FILE_TYPE_DIR : REMMINA_FTP_FILE_TYPE_FILE;
+ gtk_widget_destroy(dialog);
+ if (!files)
+ return;
+
+ for (element = files; element; element = element->next) {
+ path = (gchar*)element->data;
+
+ if (g_stat(path, &st) < 0)
+ continue;
+
+ name = g_strrstr(path, "/");
+ if (name) {
+ *name++ = '\0';
+ dir = path;
+ }else {
+ name = path;
+ dir = NULL;
+ }
+
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, REMMINA_FTP_TASK_COLUMN_TYPE, type, REMMINA_FTP_TASK_COLUMN_NAME, name,
+ REMMINA_FTP_TASK_COLUMN_SIZE, (gfloat)st.st_size, REMMINA_FTP_TASK_COLUMN_TASKID,
+ remmina_ftp_client_taskid++, REMMINA_FTP_TASK_COLUMN_TASKTYPE, REMMINA_FTP_TASK_TYPE_UPLOAD,
+ REMMINA_FTP_TASK_COLUMN_REMOTEDIR, priv->current_directory, REMMINA_FTP_TASK_COLUMN_LOCALDIR,
+ dir, REMMINA_FTP_TASK_COLUMN_STATUS, REMMINA_FTP_TASK_STATUS_WAIT,
+ REMMINA_FTP_TASK_COLUMN_DONESIZE, 0.0, REMMINA_FTP_TASK_COLUMN_TOOLTIP, NULL, -1);
+
+ g_free(path);
+ }
+
+ g_slist_free(files);
+
+ g_signal_emit(G_OBJECT(client), remmina_ftp_client_signals[NEW_TASK_SIGNAL], 0);
+}
+
+static void remmina_ftp_client_popup_menu(RemminaFTPClient *client, GdkEventButton *event)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *menu;
+ GtkWidget *menuitem;
+ GtkWidget *image;
+
+ menu = gtk_menu_new();
+
+ menuitem = gtk_menu_item_new_with_label(_("Download"));
+ gtk_widget_show(menuitem);
+ image = gtk_image_new_from_icon_name("document-save", GTK_ICON_SIZE_MENU);
+ gtk_widget_show(image);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+ g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(remmina_ftp_client_action_download), client);
+
+ menuitem = gtk_menu_item_new_with_label(_("Upload"));
+ gtk_widget_show(menuitem);
+ image = gtk_image_new_from_icon_name("document-send", GTK_ICON_SIZE_MENU);
+ gtk_widget_show(image);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+ g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(remmina_ftp_client_action_upload), client);
+
+ menuitem = gtk_menu_item_new_with_mnemonic(_("_Delete"));
+ gtk_widget_show(menuitem);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+ g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(remmina_ftp_client_action_delete), client);
+
+#if GTK_CHECK_VERSION(3, 22, 0)
+ gtk_menu_popup_at_pointer(GTK_MENU(menu), (GdkEvent*)event);
+#else
+ gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, event->time);
+#endif
+}
+
+static gboolean remmina_ftp_client_file_list_on_button_press(GtkWidget *widget, GdkEventButton *event, RemminaFTPClient *client)
+{
+ TRACE_CALL(__func__);
+ RemminaFTPClientPriv *priv = (RemminaFTPClientPriv*)client->priv;
+ GList *list;
+ GtkTreeIter iter;
+ gint type;
+ gchar *name;
+ gchar *localdir;
+
+ if (event->button == 3) {
+ remmina_ftp_client_popup_menu(client, event);
+ }else if (event->button == 1 && event->type == GDK_2BUTTON_PRESS) {
+ list = gtk_tree_selection_get_selected_rows(
+ gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->file_list_view)), NULL);
+ if (list) {
+ gtk_tree_model_get_iter(priv->file_list_sort, &iter, (GtkTreePath*)list->data);
+ gtk_tree_model_get(priv->file_list_sort, &iter, REMMINA_FTP_FILE_COLUMN_TYPE, &type,
+ REMMINA_FTP_FILE_COLUMN_NAME, &name, -1);
+ switch (type) {
+ case REMMINA_FTP_FILE_TYPE_DIR:
+ remmina_ftp_client_open_dir(client, name);
+ break;
+ case REMMINA_FTP_FILE_TYPE_FILE:
+ default:
+ localdir = remmina_ftp_client_get_download_dir(client);
+ if (localdir) {
+ remmina_ftp_client_download(client, &iter, localdir);
+ g_free(localdir);
+ }
+ break;
+ }
+ g_list_free(list);
+ g_free(name);
+ }
+ }
+
+ return FALSE;
+}
+
+static void remmina_ftp_client_task_list_cell_on_activate(GtkCellRenderer *renderer, gchar *path, RemminaFTPClient *client)
+{
+ TRACE_CALL(__func__);
+ RemminaFTPClientPriv *priv = (RemminaFTPClientPriv*)client->priv;
+ GtkTreeIter iter;
+ GtkTreePath *treepath;
+ gint taskid;
+ gboolean ret = FALSE;
+
+ treepath = gtk_tree_path_new_from_string(path);
+ gtk_tree_model_get_iter(priv->task_list_model, &iter, treepath);
+ gtk_tree_path_free(treepath);
+
+ gtk_tree_model_get(priv->task_list_model, &iter, REMMINA_FTP_TASK_COLUMN_TASKID, &taskid, -1);
+
+ g_signal_emit(G_OBJECT(client), remmina_ftp_client_signals[CANCEL_TASK_SIGNAL], 0, taskid, &ret);
+
+ if (ret) {
+ gtk_list_store_remove(GTK_LIST_STORE(priv->task_list_model), &iter);
+ }
+}
+
+static GtkWidget* remmina_ftp_client_create_toolbar(RemminaFTPClient *client)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *box;
+ GtkWidget *button;
+ GtkWidget *image;
+ gint i = 0;
+
+ box = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
+ gtk_widget_show(box);
+ gtk_button_box_set_layout(GTK_BUTTON_BOX(box), GTK_BUTTONBOX_START);
+ gtk_grid_attach(GTK_GRID(client), box, 0, 0, 1, 1);
+
+ button = gtk_button_new_with_label(_("Home"));
+ gtk_widget_show(button);
+ gtk_widget_set_tooltip_text(button, _("Go to home folder"));
+ gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
+ gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 0);
+ g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(remmina_ftp_client_action_home), client);
+
+ button = gtk_button_new_with_label(_("Up"));
+ gtk_widget_show(button);
+ gtk_widget_set_tooltip_text(button, _("Go to parent folder"));
+ gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
+ gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 0);
+ g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(remmina_ftp_client_action_parent), client);
+
+ button = gtk_button_new_with_label(_("Refresh"));
+ gtk_widget_show(button);
+ gtk_widget_set_tooltip_text(button, _("Refresh current folder"));
+ gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
+ gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 0);
+ g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(remmina_ftp_client_action_refresh), client);
+
+ button = gtk_button_new_with_label(_("Download"));
+ gtk_widget_show(button);
+ gtk_widget_set_tooltip_text(button, _("Download from server"));
+ gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
+ image = gtk_image_new_from_icon_name("document-save", GTK_ICON_SIZE_BUTTON);
+ gtk_button_set_image(GTK_BUTTON(button), image);
+ gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 0);
+ g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(remmina_ftp_client_action_download), client);
+
+ client->priv->file_action_widgets[i++] = button;
+
+ button = gtk_button_new_with_label(_("Upload"));
+ gtk_widget_show(button);
+ gtk_widget_set_tooltip_text(button, _("Upload to server"));
+ gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
+ image = gtk_image_new_from_icon_name("document-send", GTK_ICON_SIZE_BUTTON);
+ gtk_button_set_image(GTK_BUTTON(button), image);
+ gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 0);
+ g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(remmina_ftp_client_action_upload), client);
+
+ button = gtk_button_new_with_label(_("Delete"));
+ gtk_widget_show(button);
+ gtk_widget_set_tooltip_text(button, _("Delete files on server"));
+ gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
+ gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 0);
+ g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(remmina_ftp_client_action_delete), client);
+
+ client->priv->file_action_widgets[i++] = button;
+
+ return box;
+}
+
+void remmina_ftp_client_set_show_hidden(RemminaFTPClient *client, gboolean show_hidden)
+{
+ TRACE_CALL(__func__);
+ client->priv->file_list_show_hidden = show_hidden;
+ gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER(client->priv->file_list_filter));
+}
+
+static gboolean remmina_ftp_client_filter_visible_func(GtkTreeModel *model, GtkTreeIter *iter, RemminaFTPClient *client)
+{
+ TRACE_CALL(__func__);
+ gchar *name;
+ gboolean result = TRUE;
+
+ if (client->priv->file_list_show_hidden)
+ return TRUE;
+
+ gtk_tree_model_get(model, iter, REMMINA_FTP_FILE_COLUMN_NAME, &name, -1);
+ if (name && name[0] == '.') {
+ result = FALSE;
+ }
+ g_free(name);
+ return result;
+}
+
+/* Set the overwrite_all status */
+void remmina_ftp_client_set_overwrite_status(RemminaFTPClient *client, gboolean status)
+{
+ TRACE_CALL(__func__);
+ client->priv->overwrite_all = status;
+}
+
+/* Get the overwrite_all status */
+gboolean remmina_ftp_client_get_overwrite_status(RemminaFTPClient *client)
+{
+ TRACE_CALL(__func__);
+ return client->priv->overwrite_all;
+}
+
+static void remmina_ftp_client_init(RemminaFTPClient *client)
+{
+ TRACE_CALL(__func__);
+ RemminaFTPClientPriv *priv;
+ GtkWidget *vpaned;
+ GtkWidget *toolbar;
+ GtkWidget *scrolledwindow;
+ GtkWidget *widget;
+ GtkCellRenderer *renderer;
+ GtkTreeViewColumn *column;
+ GtkWidget *vbox;
+
+ priv = g_new0(RemminaFTPClientPriv, 1);
+ client->priv = priv;
+
+ /* Initialize overwrite status to FALSE */
+ client->priv->overwrite_all = FALSE;
+
+ /* Main container */
+ gtk_widget_set_vexpand(GTK_WIDGET(client), TRUE);
+ gtk_widget_set_hexpand(GTK_WIDGET(client), TRUE);
+
+ /* Toolbar */
+ toolbar = remmina_ftp_client_create_toolbar(client);
+
+ /* The Paned to separate File List and Task List */
+ vpaned = gtk_paned_new(GTK_ORIENTATION_VERTICAL);
+ gtk_widget_set_vexpand(GTK_WIDGET(vpaned), TRUE);
+ gtk_widget_set_hexpand(GTK_WIDGET(vpaned), TRUE);
+ gtk_widget_show(vpaned);
+ gtk_grid_attach_next_to(GTK_GRID(client), vpaned, toolbar, GTK_POS_BOTTOM, 1, 1);
+
+ priv->vpaned = vpaned;
+
+ /* Remote */
+ vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+ gtk_widget_show(vbox);
+ gtk_paned_pack1(GTK_PANED(vpaned), vbox, TRUE, FALSE);
+
+ /* Remote Directory */
+ widget = gtk_combo_box_text_new_with_entry();
+ gtk_widget_show(widget);
+ gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(widget), "/");
+ gtk_box_pack_start(GTK_BOX(vbox), widget, FALSE, TRUE, 0);
+
+ priv->directory_combo = widget;
+
+ /* Remote File List */
+ 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_ALWAYS);
+ gtk_box_pack_start(GTK_BOX(vbox), scrolledwindow, TRUE, TRUE, 0);
+
+ widget = gtk_tree_view_new();
+ gtk_widget_show(widget);
+ gtk_container_add(GTK_CONTAINER(scrolledwindow), widget);
+
+ gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(widget)), GTK_SELECTION_MULTIPLE);
+
+ priv->file_list_view = widget;
+
+ /* Remote File List - Columns */
+ column = gtk_tree_view_column_new();
+ gtk_tree_view_column_set_title(column, _("File Name"));
+ gtk_tree_view_column_set_expand(column, TRUE);
+ gtk_tree_view_column_set_resizable(column, TRUE);
+ gtk_tree_view_column_set_sort_column_id(column, REMMINA_FTP_FILE_COLUMN_NAME_SORT);
+ renderer = gtk_cell_renderer_pixbuf_new();
+ gtk_tree_view_column_pack_start(column, renderer, FALSE);
+ gtk_tree_view_column_set_cell_data_func(column, renderer, remmina_ftp_client_cell_data_filetype_pixbuf, NULL, NULL);
+ renderer = gtk_cell_renderer_text_new();
+ gtk_tree_view_column_pack_start(column, renderer, FALSE);
+ gtk_tree_view_column_add_attribute(column, renderer, "text", REMMINA_FTP_FILE_COLUMN_NAME);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(priv->file_list_view), column);
+
+ renderer = gtk_cell_renderer_text_new();
+ column = gtk_tree_view_column_new_with_attributes(_("Size"), renderer, NULL);
+ gtk_tree_view_column_set_alignment(column, 1.0);
+ gtk_tree_view_column_set_resizable(column, TRUE);
+ gtk_tree_view_column_set_cell_data_func(column, renderer, remmina_ftp_client_cell_data_size, NULL, NULL);
+ gtk_tree_view_column_set_sort_column_id(column, REMMINA_FTP_FILE_COLUMN_SIZE);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(priv->file_list_view), column);
+
+ renderer = gtk_cell_renderer_text_new();
+ column = gtk_tree_view_column_new_with_attributes(_("User"), renderer, "text", REMMINA_FTP_FILE_COLUMN_USER, NULL);
+ gtk_tree_view_column_set_resizable(column, TRUE);
+ gtk_tree_view_column_set_sort_column_id(column, REMMINA_FTP_FILE_COLUMN_USER);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(priv->file_list_view), column);
+
+ renderer = gtk_cell_renderer_text_new();
+ column = gtk_tree_view_column_new_with_attributes(_("Group"), renderer, "text", REMMINA_FTP_FILE_COLUMN_GROUP, NULL);
+ gtk_tree_view_column_set_resizable(column, TRUE);
+ gtk_tree_view_column_set_sort_column_id(column, REMMINA_FTP_FILE_COLUMN_GROUP);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(priv->file_list_view), column);
+
+ renderer = gtk_cell_renderer_text_new();
+ column = gtk_tree_view_column_new_with_attributes(_("Permission"), renderer, "text", REMMINA_FTP_FILE_COLUMN_PERMISSION,
+ NULL);
+ gtk_tree_view_column_set_resizable(column, TRUE);
+ gtk_tree_view_column_set_cell_data_func(column, renderer, remmina_ftp_client_cell_data_permission, NULL, NULL);
+ gtk_tree_view_column_set_sort_column_id(column, REMMINA_FTP_FILE_COLUMN_PERMISSION);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(priv->file_list_view), column);
+
+ /* Remote File List - Model */
+ priv->file_list_model = GTK_TREE_MODEL(
+ gtk_list_store_new(REMMINA_FTP_FILE_N_COLUMNS, G_TYPE_INT, G_TYPE_STRING, G_TYPE_FLOAT, G_TYPE_STRING,
+ G_TYPE_STRING, G_TYPE_INT, G_TYPE_STRING));
+
+ priv->file_list_filter = gtk_tree_model_filter_new(priv->file_list_model, NULL);
+ gtk_tree_model_filter_set_visible_func(GTK_TREE_MODEL_FILTER(priv->file_list_filter),
+ (GtkTreeModelFilterVisibleFunc)remmina_ftp_client_filter_visible_func, client, NULL);
+
+ priv->file_list_sort = gtk_tree_model_sort_new_with_model(priv->file_list_filter);
+ gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(priv->file_list_sort), REMMINA_FTP_FILE_COLUMN_NAME_SORT,
+ GTK_SORT_ASCENDING);
+ gtk_tree_view_set_model(GTK_TREE_VIEW(priv->file_list_view), priv->file_list_sort);
+
+ /* Task List */
+ 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_ALWAYS);
+ gtk_paned_pack2(GTK_PANED(vpaned), scrolledwindow, FALSE, TRUE);
+
+ widget = gtk_tree_view_new();
+ gtk_widget_show(widget);
+ gtk_container_add(GTK_CONTAINER(scrolledwindow), widget);
+ g_object_set(widget, "has-tooltip", TRUE, NULL);
+
+ priv->task_list_view = widget;
+
+ /* Task List - Columns */
+ column = gtk_tree_view_column_new();
+ gtk_tree_view_column_set_title(column, _("File Name"));
+ gtk_tree_view_column_set_resizable(column, TRUE);
+ gtk_tree_view_column_set_expand(column, TRUE);
+ gtk_tree_view_column_set_sort_column_id(column, REMMINA_FTP_TASK_COLUMN_NAME);
+ renderer = gtk_cell_renderer_pixbuf_new();
+ gtk_tree_view_column_pack_start(column, renderer, FALSE);
+ gtk_tree_view_column_set_cell_data_func(column, renderer, remmina_ftp_client_cell_data_progress_pixbuf, NULL, NULL);
+ renderer = gtk_cell_renderer_pixbuf_new();
+ gtk_tree_view_column_pack_start(column, renderer, FALSE);
+ gtk_tree_view_column_set_cell_data_func(column, renderer, remmina_ftp_client_cell_data_filetype_pixbuf, NULL, NULL);
+ renderer = gtk_cell_renderer_text_new();
+ gtk_tree_view_column_pack_start(column, renderer, FALSE);
+ gtk_tree_view_column_add_attribute(column, renderer, "text", REMMINA_FTP_FILE_COLUMN_NAME);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(priv->task_list_view), column);
+
+ renderer = gtk_cell_renderer_text_new();
+ column = gtk_tree_view_column_new_with_attributes(_("Remote"), renderer, "text", REMMINA_FTP_TASK_COLUMN_REMOTEDIR,
+ NULL);
+ gtk_tree_view_column_set_resizable(column, TRUE);
+ gtk_tree_view_column_set_sort_column_id(column, REMMINA_FTP_TASK_COLUMN_REMOTEDIR);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(priv->task_list_view), column);
+
+ renderer = gtk_cell_renderer_text_new();
+ column = gtk_tree_view_column_new_with_attributes(_("Local"), renderer, "text", REMMINA_FTP_TASK_COLUMN_LOCALDIR, NULL);
+ gtk_tree_view_column_set_resizable(column, TRUE);
+ gtk_tree_view_column_set_sort_column_id(column, REMMINA_FTP_TASK_COLUMN_LOCALDIR);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(priv->task_list_view), column);
+
+ renderer = gtk_cell_renderer_text_new();
+ column = gtk_tree_view_column_new_with_attributes(_("Size"), renderer, NULL);
+ gtk_tree_view_column_set_alignment(column, 1.0);
+ gtk_tree_view_column_set_resizable(column, TRUE);
+ gtk_tree_view_column_set_cell_data_func(column, renderer, remmina_ftp_client_cell_data_size_progress, NULL, NULL);
+ gtk_tree_view_column_set_sort_column_id(column, REMMINA_FTP_TASK_COLUMN_SIZE);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(priv->task_list_view), column);
+
+ renderer = gtk_cell_renderer_progress_new();
+ column = gtk_tree_view_column_new_with_attributes(_("Progress"), renderer, NULL);
+ gtk_tree_view_column_set_resizable(column, TRUE);
+ gtk_tree_view_column_set_cell_data_func(column, renderer, remmina_ftp_client_cell_data_progress, NULL, NULL);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(priv->task_list_view), column);
+
+ renderer = remmina_cell_renderer_pixbuf_new();
+ column = gtk_tree_view_column_new_with_attributes(NULL, renderer, NULL);
+ g_object_set(G_OBJECT(renderer), "stock-id", "_Cancel", NULL);
+ gtk_tree_view_column_set_resizable(column, FALSE);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(priv->task_list_view), column);
+
+ g_signal_connect(G_OBJECT(renderer), "activate", G_CALLBACK(remmina_ftp_client_task_list_cell_on_activate), client);
+
+ /* Task List - Model */
+ priv->task_list_model = GTK_TREE_MODEL(
+ gtk_list_store_new(REMMINA_FTP_TASK_N_COLUMNS, G_TYPE_INT, G_TYPE_STRING, G_TYPE_FLOAT, G_TYPE_INT,
+ G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, G_TYPE_FLOAT, G_TYPE_STRING));
+ gtk_tree_view_set_model(GTK_TREE_VIEW(priv->task_list_view), priv->task_list_model);
+
+ /* Setup the internal signals */
+ g_signal_connect(G_OBJECT(client), "destroy", G_CALLBACK(remmina_ftp_client_destroy), NULL);
+ g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(priv->directory_combo))), "activate",
+ G_CALLBACK(remmina_ftp_client_dir_on_activate), client);
+ g_signal_connect(G_OBJECT(priv->directory_combo), "changed", G_CALLBACK(remmina_ftp_client_dir_on_changed), client);
+ g_signal_connect(G_OBJECT(priv->file_list_view), "button-press-event",
+ G_CALLBACK(remmina_ftp_client_file_list_on_button_press), client);
+ g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->file_list_view))), "changed",
+ G_CALLBACK(remmina_ftp_client_file_selection_on_changed), client);
+ g_signal_connect(G_OBJECT(priv->task_list_view), "query-tooltip",
+ G_CALLBACK(remmina_ftp_client_task_list_on_query_tooltip), client);
+}
+
+GtkWidget*
+remmina_ftp_client_new(void)
+{
+ TRACE_CALL(__func__);
+ RemminaFTPClient *client;
+
+ client = REMMINA_FTP_CLIENT(g_object_new(REMMINA_TYPE_FTP_CLIENT, NULL));
+
+ return GTK_WIDGET(client);
+}
+
+void remmina_ftp_client_save_state(RemminaFTPClient *client, RemminaFile *remminafile)
+{
+ TRACE_CALL(__func__);
+ gint pos;
+
+ pos = gtk_paned_get_position(GTK_PANED(client->priv->vpaned));
+ remmina_file_set_int(remminafile, "ftp_vpanedpos", pos);
+}
+
+void remmina_ftp_client_load_state(RemminaFTPClient *client, RemminaFile *remminafile)
+{
+ TRACE_CALL(__func__);
+ gint pos;
+ GtkAllocation a;
+
+ pos = remmina_file_get_int(remminafile, "ftp_vpanedpos", 0);
+ if (pos) {
+ gtk_widget_get_allocation(client->priv->vpaned, &a);
+ if (a.height > 0 && pos > a.height - 60) {
+ pos = a.height - 60;
+ }
+ gtk_paned_set_position(GTK_PANED(client->priv->vpaned), pos);
+ }
+}
+
+void remmina_ftp_client_clear_file_list(RemminaFTPClient *client)
+{
+ TRACE_CALL(__func__);
+ RemminaFTPClientPriv *priv = (RemminaFTPClientPriv*)client->priv;
+
+ gtk_list_store_clear(GTK_LIST_STORE(priv->file_list_model));
+ remmina_ftp_client_set_file_action_sensitive(client, FALSE);
+}
+
+void remmina_ftp_client_add_file(RemminaFTPClient *client, ...)
+{
+ TRACE_CALL(__func__);
+ RemminaFTPClientPriv *priv = (RemminaFTPClientPriv*)client->priv;
+ GtkListStore *store = GTK_LIST_STORE(priv->file_list_model);
+ GtkTreeIter iter;
+ va_list args;
+ gint type;
+ gchar *name;
+ gchar *ptr;
+
+ va_start(args, client);
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set_valist(store, &iter, args);
+ va_end(args);
+
+ gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
+ REMMINA_FTP_FILE_COLUMN_TYPE, &type,
+ REMMINA_FTP_FILE_COLUMN_NAME, &name,
+ -1);
+
+ ptr = g_strdup_printf("%i%s", type, name);
+ gtk_list_store_set(store, &iter, REMMINA_FTP_FILE_COLUMN_NAME_SORT, ptr, -1);
+ g_free(ptr);
+ g_free(name);
+}
+
+void remmina_ftp_client_set_dir(RemminaFTPClient *client, const gchar *dir)
+{
+ TRACE_CALL(__func__);
+ RemminaFTPClientPriv *priv = (RemminaFTPClientPriv*)client->priv;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gboolean ret;
+ gchar *t;
+
+ if (priv->current_directory && g_strcmp0(priv->current_directory, dir) == 0)
+ return;
+
+ model = gtk_combo_box_get_model(GTK_COMBO_BOX(priv->directory_combo));
+ for (ret = gtk_tree_model_get_iter_first(model, &iter); ret; ret = gtk_tree_model_iter_next(model, &iter)) {
+ gtk_tree_model_get(model, &iter, 0, &t, -1);
+ if (g_strcmp0(t, dir) == 0) {
+ gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
+ g_free(t);
+ break;
+ }
+ g_free(t);
+ }
+
+ gtk_combo_box_text_prepend_text(GTK_COMBO_BOX_TEXT(priv->directory_combo), dir);
+ gtk_combo_box_set_active(GTK_COMBO_BOX(priv->directory_combo), 0);
+
+ g_free(priv->current_directory);
+ priv->current_directory = g_strdup(dir);
+}
+
+gchar*
+remmina_ftp_client_get_dir(RemminaFTPClient *client)
+{
+ TRACE_CALL(__func__);
+ RemminaFTPClientPriv *priv = (RemminaFTPClientPriv*)client->priv;
+
+ return g_strdup(priv->current_directory);
+}
+
+RemminaFTPTask*
+remmina_ftp_client_get_waiting_task(RemminaFTPClient *client)
+{
+ TRACE_CALL(__func__);
+ RemminaFTPClientPriv *priv = (RemminaFTPClientPriv*)client->priv;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ RemminaFTPTask task;
+
+ if ( !remmina_masterthread_exec_is_main_thread() ) {
+ /* Allow the execution of this function from a non main thread */
+ RemminaMTExecData *d;
+ RemminaFTPTask* retval;
+ d = (RemminaMTExecData*)g_malloc( sizeof(RemminaMTExecData) );
+ d->func = FUNC_FTP_CLIENT_GET_WAITING_TASK;
+ d->p.ftp_client_get_waiting_task.client = client;
+ remmina_masterthread_exec_and_wait(d);
+ retval = d->p.ftp_client_get_waiting_task.retval;
+ g_free(d);
+ return retval;
+ }
+
+ if (!gtk_tree_model_get_iter_first(priv->task_list_model, &iter))
+ return NULL;
+
+ while (1) {
+ gtk_tree_model_get(priv->task_list_model, &iter, REMMINA_FTP_TASK_COLUMN_TYPE, &task.type,
+ REMMINA_FTP_TASK_COLUMN_NAME, &task.name, REMMINA_FTP_TASK_COLUMN_SIZE, &task.size,
+ REMMINA_FTP_TASK_COLUMN_TASKID, &task.taskid, REMMINA_FTP_TASK_COLUMN_TASKTYPE, &task.tasktype,
+ REMMINA_FTP_TASK_COLUMN_REMOTEDIR, &task.remotedir, REMMINA_FTP_TASK_COLUMN_LOCALDIR,
+ &task.localdir, REMMINA_FTP_TASK_COLUMN_STATUS, &task.status, REMMINA_FTP_TASK_COLUMN_DONESIZE,
+ &task.donesize, REMMINA_FTP_TASK_COLUMN_TOOLTIP, &task.tooltip, -1);
+ if (task.status == REMMINA_FTP_TASK_STATUS_WAIT) {
+ path = gtk_tree_model_get_path(priv->task_list_model, &iter);
+ task.rowref = gtk_tree_row_reference_new(priv->task_list_model, path);
+ gtk_tree_path_free(path);
+ return (RemminaFTPTask*)g_memdup(&task, sizeof(RemminaFTPTask));
+ }
+ if (!gtk_tree_model_iter_next(priv->task_list_model, &iter))
+ break;
+ }
+
+ return NULL;
+}
+
+void remmina_ftp_client_update_task(RemminaFTPClient *client, RemminaFTPTask* task)
+{
+ TRACE_CALL(__func__);
+ RemminaFTPClientPriv *priv = (RemminaFTPClientPriv*)client->priv;
+ GtkListStore *store = GTK_LIST_STORE(priv->task_list_model);
+ GtkTreePath *path;
+ GtkTreeIter iter;
+
+ if ( !remmina_masterthread_exec_is_main_thread() ) {
+ /* Allow the execution of this function from a non main thread */
+ RemminaMTExecData *d;
+ d = (RemminaMTExecData*)g_malloc( sizeof(RemminaMTExecData) );
+ d->func = FUNC_FTP_CLIENT_UPDATE_TASK;
+ d->p.ftp_client_update_task.client = client;
+ d->p.ftp_client_update_task.task = task;
+ remmina_masterthread_exec_and_wait(d);
+ g_free(d);
+ return;
+ }
+
+
+
+ path = gtk_tree_row_reference_get_path(task->rowref);
+ if (path == NULL)
+ return;
+ gtk_tree_model_get_iter(priv->task_list_model, &iter, path);
+ gtk_tree_path_free(path);
+ gtk_list_store_set(store, &iter, REMMINA_FTP_TASK_COLUMN_SIZE, task->size, REMMINA_FTP_TASK_COLUMN_STATUS, task->status,
+ REMMINA_FTP_TASK_COLUMN_DONESIZE, task->donesize, REMMINA_FTP_TASK_COLUMN_TOOLTIP, task->tooltip, -1);
+}
+
+void remmina_ftp_task_free(RemminaFTPTask *task)
+{
+ TRACE_CALL(__func__);
+ if (task) {
+ g_free(task->name);
+ g_free(task->remotedir);
+ g_free(task->localdir);
+ g_free(task->tooltip);
+ g_free(task);
+ }
+}
+
diff --git a/src/remmina_ftp_client.h b/src/remmina_ftp_client.h
new file mode 100644
index 000000000..ead701666
--- /dev/null
+++ b/src/remmina_ftp_client.h
@@ -0,0 +1,150 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2010 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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.
+ *
+ */
+
+#pragma once
+
+G_BEGIN_DECLS
+
+#define REMMINA_TYPE_FTP_CLIENT (remmina_ftp_client_get_type())
+#define REMMINA_FTP_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), REMMINA_TYPE_FTP_CLIENT, RemminaFTPClient))
+#define REMMINA_FTP_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), REMMINA_TYPE_FTP_CLIENT, RemminaFTPClientClass))
+#define REMMINA_IS_FTP_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), REMMINA_TYPE_FTP_CLIENT))
+#define REMMINA_IS_FTP_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), REMMINA_TYPE_FTP_CLIENT))
+#define REMMINA_FTP_CLIENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), REMMINA_TYPE_FTP_CLIENT, RemminaFTPClientClass))
+
+typedef struct _RemminaFTPClientPriv RemminaFTPClientPriv;
+
+typedef struct _RemminaFTPClient {
+ GtkVBox vbox;
+
+ RemminaFTPClientPriv *priv;
+} RemminaFTPClient;
+
+typedef struct _RemminaFTPClientClass {
+ GtkVBoxClass parent_class;
+
+ void (*open_dir)(RemminaFTPClient *client);
+ void (*new_task)(RemminaFTPClient *client);
+ void (*cancel_task)(RemminaFTPClient *client);
+ void (*delete_file)(RemminaFTPClient *client);
+} RemminaFTPClientClass;
+
+GType remmina_ftp_client_get_type(void)
+G_GNUC_CONST;
+
+enum {
+ REMMINA_FTP_FILE_TYPE_DIR, REMMINA_FTP_FILE_TYPE_FILE, REMMINA_FTP_FILE_N_TYPES,
+};
+
+enum {
+ REMMINA_FTP_FILE_COLUMN_TYPE,
+ REMMINA_FTP_FILE_COLUMN_NAME,
+ REMMINA_FTP_FILE_COLUMN_SIZE,
+ REMMINA_FTP_FILE_COLUMN_USER,
+ REMMINA_FTP_FILE_COLUMN_GROUP,
+ REMMINA_FTP_FILE_COLUMN_PERMISSION,
+ REMMINA_FTP_FILE_COLUMN_NAME_SORT, /* Auto populate */
+ REMMINA_FTP_FILE_N_COLUMNS
+};
+
+enum {
+ REMMINA_FTP_TASK_TYPE_DOWNLOAD, REMMINA_FTP_TASK_TYPE_UPLOAD, REMMINA_FTP_TASK_N_TYPES
+};
+
+enum {
+ REMMINA_FTP_TASK_STATUS_WAIT,
+ REMMINA_FTP_TASK_STATUS_RUN,
+ REMMINA_FTP_TASK_STATUS_FINISH,
+ REMMINA_FTP_TASK_STATUS_ERROR,
+ REMMINA_FTP_TASK_N_STATUSES
+};
+
+enum {
+ REMMINA_FTP_TASK_COLUMN_TYPE,
+ REMMINA_FTP_TASK_COLUMN_NAME,
+ REMMINA_FTP_TASK_COLUMN_SIZE,
+ REMMINA_FTP_TASK_COLUMN_TASKID,
+ REMMINA_FTP_TASK_COLUMN_TASKTYPE,
+ REMMINA_FTP_TASK_COLUMN_REMOTEDIR,
+ REMMINA_FTP_TASK_COLUMN_LOCALDIR,
+ REMMINA_FTP_TASK_COLUMN_STATUS,
+ REMMINA_FTP_TASK_COLUMN_DONESIZE,
+ REMMINA_FTP_TASK_COLUMN_TOOLTIP,
+ REMMINA_FTP_TASK_N_COLUMNS
+};
+
+typedef struct _RemminaFTPTask {
+ /* Read-only */
+ gint type;
+ gchar *name;
+ gint taskid;
+ gint tasktype;
+ gchar *remotedir;
+ gchar *localdir;
+ GtkTreeRowReference *rowref;
+ /* Updatable */
+ gfloat size;
+ gint status;
+ gfloat donesize;
+ gchar *tooltip;
+} RemminaFTPTask;
+
+GtkWidget* remmina_ftp_client_new(void);
+
+void remmina_ftp_client_save_state(RemminaFTPClient *client, RemminaFile *remminafile);
+void remmina_ftp_client_load_state(RemminaFTPClient *client, RemminaFile *remminafile);
+
+void remmina_ftp_client_set_show_hidden(RemminaFTPClient *client, gboolean show_hidden);
+void remmina_ftp_client_clear_file_list(RemminaFTPClient *client);
+/* column, value, ..., -1 */
+void remmina_ftp_client_add_file(RemminaFTPClient *client, ...);
+/* Set the current directory. Should be called by opendir signal handler */
+void remmina_ftp_client_set_dir(RemminaFTPClient *client, const gchar *dir);
+/* Get the current directory as newly allocated string */
+gchar* remmina_ftp_client_get_dir(RemminaFTPClient *client);
+/* Get the next waiting task */
+RemminaFTPTask* remmina_ftp_client_get_waiting_task(RemminaFTPClient *client);
+/* Update the task */
+void remmina_ftp_client_update_task(RemminaFTPClient *client, RemminaFTPTask* task);
+/* Free the RemminaFTPTask object */
+void remmina_ftp_task_free(RemminaFTPTask *task);
+/* Get/Set Set overwrite_all status */
+void remmina_ftp_client_set_overwrite_status(RemminaFTPClient *client, gboolean status);
+gboolean remmina_ftp_client_get_overwrite_status(RemminaFTPClient *client);
+
+G_END_DECLS
+
+
diff --git a/src/remmina_icon.c b/src/remmina_icon.c
new file mode 100644
index 000000000..87d18bde5
--- /dev/null
+++ b/src/remmina_icon.c
@@ -0,0 +1,536 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 "config.h"
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include "remmina_widget_pool.h"
+#include "remmina_pref.h"
+#include "remmina_exec.h"
+#include "remmina_avahi.h"
+#include "remmina_applet_menu_item.h"
+#include "remmina_applet_menu.h"
+#include "remmina_connection_window.h"
+#include "remmina_icon.h"
+#include "remmina/remmina_trace_calls.h"
+#include "remmina_sysinfo.h"
+
+#ifdef HAVE_LIBAPPINDICATOR
+#include <libappindicator/app-indicator.h>
+#endif
+
+typedef struct _RemminaIcon {
+#ifdef HAVE_LIBAPPINDICATOR
+ AppIndicator *icon;
+#else
+ GtkStatusIcon *icon;
+#endif
+ RemminaAvahi *avahi;
+ guint32 popup_time;
+ gchar *autostart_file;
+ gchar *gsversion; // GnomeShell version string, or null if not available
+} RemminaIcon;
+
+static RemminaIcon remmina_icon =
+{ 0 };
+
+void remmina_icon_destroy(void)
+{
+ TRACE_CALL(__func__);
+ if (remmina_icon.icon) {
+#ifdef HAVE_LIBAPPINDICATOR
+ app_indicator_set_status(remmina_icon.icon, APP_INDICATOR_STATUS_PASSIVE);
+#else
+ gtk_status_icon_set_visible(remmina_icon.icon, FALSE);
+#endif
+ remmina_icon.icon = NULL;
+ }
+ if (remmina_icon.avahi) {
+ remmina_avahi_free(remmina_icon.avahi);
+ remmina_icon.avahi = NULL;
+ }
+ if (remmina_icon.autostart_file) {
+ g_free(remmina_icon.autostart_file);
+ remmina_icon.autostart_file = NULL;
+ }
+ if (remmina_icon.gsversion) {
+ g_free(remmina_icon.gsversion);
+ remmina_icon.gsversion = NULL;
+ }
+}
+
+static void remmina_icon_main(void)
+{
+ TRACE_CALL(__func__);
+ remmina_exec_command(REMMINA_COMMAND_MAIN, NULL);
+}
+
+static void remmina_icon_preferences(void)
+{
+ TRACE_CALL(__func__);
+ remmina_exec_command(REMMINA_COMMAND_PREF, "2");
+}
+
+static void remmina_icon_about(void)
+{
+ TRACE_CALL(__func__);
+ remmina_exec_command(REMMINA_COMMAND_ABOUT, NULL);
+}
+
+static void remmina_icon_enable_avahi(GtkCheckMenuItem *checkmenuitem, gpointer data)
+{
+ TRACE_CALL(__func__);
+ if (!remmina_icon.avahi)
+ return;
+
+ if (gtk_check_menu_item_get_active(checkmenuitem)) {
+ remmina_pref.applet_enable_avahi = TRUE;
+ if (!remmina_icon.avahi->started)
+ remmina_avahi_start(remmina_icon.avahi);
+ }else {
+ remmina_pref.applet_enable_avahi = FALSE;
+ remmina_avahi_stop(remmina_icon.avahi);
+ }
+ remmina_pref_save();
+}
+
+static void remmina_icon_populate_additional_menu_item(GtkWidget *menu)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *menuitem;
+
+ menuitem = gtk_menu_item_new_with_label(_("Open Main Window"));
+ gtk_widget_show(menuitem);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+ g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(remmina_icon_main), NULL);
+
+ menuitem = gtk_menu_item_new_with_mnemonic(_("_Preferences"));
+ gtk_widget_show(menuitem);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+ g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(remmina_icon_preferences), NULL);
+
+ menuitem = gtk_menu_item_new_with_mnemonic(_("_About"));
+ gtk_widget_show(menuitem);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+ g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(remmina_icon_about), NULL);
+
+ menuitem = gtk_separator_menu_item_new();
+ gtk_widget_show(menuitem);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+
+#ifdef HAVE_LIBAVAHI_CLIENT
+ menuitem = gtk_check_menu_item_new_with_label(_("Enable Service Discovery"));
+ if (remmina_pref.applet_enable_avahi) {
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
+ }
+ gtk_widget_show(menuitem);
+ g_signal_connect(G_OBJECT(menuitem), "toggled", G_CALLBACK(remmina_icon_enable_avahi), NULL);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+
+ menuitem = gtk_separator_menu_item_new();
+ gtk_widget_show(menuitem);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+#endif
+
+ menuitem = gtk_menu_item_new_with_mnemonic(_("_Quit"));
+ gtk_widget_show(menuitem);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+ g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(remmina_exec_exitremmina), NULL);
+}
+
+static void remmina_icon_on_launch_item(RemminaAppletMenu *menu, RemminaAppletMenuItem *menuitem, gpointer data)
+{
+ TRACE_CALL(__func__);
+ gchar *s;
+
+ switch (menuitem->item_type) {
+ case REMMINA_APPLET_MENU_ITEM_NEW:
+ remmina_exec_command(REMMINA_COMMAND_NEW, NULL);
+ break;
+ case REMMINA_APPLET_MENU_ITEM_FILE:
+ remmina_exec_command(REMMINA_COMMAND_CONNECT, menuitem->filename);
+ break;
+ case REMMINA_APPLET_MENU_ITEM_DISCOVERED:
+ s = g_strdup_printf("%s,%s", menuitem->protocol, menuitem->name);
+ remmina_exec_command(REMMINA_COMMAND_NEW, s);
+ g_free(s);
+ break;
+ }
+}
+
+static void remmina_icon_on_edit_item(RemminaAppletMenu *menu, RemminaAppletMenuItem *menuitem, gpointer data)
+{
+ TRACE_CALL(__func__);
+ gchar *s;
+
+ switch (menuitem->item_type) {
+ case REMMINA_APPLET_MENU_ITEM_NEW:
+ remmina_exec_command(REMMINA_COMMAND_NEW, NULL);
+ break;
+ case REMMINA_APPLET_MENU_ITEM_FILE:
+ remmina_exec_command(REMMINA_COMMAND_EDIT, menuitem->filename);
+ break;
+ case REMMINA_APPLET_MENU_ITEM_DISCOVERED:
+ s = g_strdup_printf("%s,%s", menuitem->protocol, menuitem->name);
+ remmina_exec_command(REMMINA_COMMAND_NEW, s);
+ g_free(s);
+ break;
+ }
+}
+
+static void remmina_icon_populate_extra_menu_item(GtkWidget *menu)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *menuitem;
+ gboolean new_ontop;
+ GHashTableIter iter;
+ gchar *tmp;
+
+ new_ontop = remmina_pref.applet_new_ontop;
+
+ /* Iterate all discovered services from Avahi */
+ if (remmina_icon.avahi) {
+ g_hash_table_iter_init(&iter, remmina_icon.avahi->discovered_services);
+ while (g_hash_table_iter_next(&iter, NULL, (gpointer*)&tmp)) {
+ menuitem = remmina_applet_menu_item_new(REMMINA_APPLET_MENU_ITEM_DISCOVERED, tmp);
+ gtk_widget_show(menuitem);
+ remmina_applet_menu_add_item(REMMINA_APPLET_MENU(menu), REMMINA_APPLET_MENU_ITEM(menuitem));
+ }
+ }
+
+ /* Separator */
+ menuitem = gtk_separator_menu_item_new();
+ gtk_widget_show(menuitem);
+ if (new_ontop) {
+ gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
+ }else {
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+ }
+
+ /* New Connection */
+ menuitem = remmina_applet_menu_item_new(REMMINA_APPLET_MENU_ITEM_NEW);
+ gtk_widget_show(menuitem);
+ remmina_applet_menu_register_item(REMMINA_APPLET_MENU(menu), REMMINA_APPLET_MENU_ITEM(menuitem));
+ if (new_ontop) {
+ gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
+ }else {
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+ }
+
+ g_signal_connect(G_OBJECT(menu), "launch-item", G_CALLBACK(remmina_icon_on_launch_item), NULL);
+ g_signal_connect(G_OBJECT(menu), "edit-item", G_CALLBACK(remmina_icon_on_edit_item), NULL);
+}
+
+#ifdef HAVE_LIBAPPINDICATOR
+
+void
+remmina_icon_populate_menu(void)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *menu;
+ GtkWidget *menuitem;
+
+ menu = remmina_applet_menu_new();
+ app_indicator_set_menu(remmina_icon.icon, GTK_MENU(menu));
+
+ remmina_applet_menu_set_hide_count(REMMINA_APPLET_MENU(menu), remmina_pref.applet_hide_count);
+ remmina_applet_menu_populate(REMMINA_APPLET_MENU(menu));
+ remmina_icon_populate_extra_menu_item(menu);
+
+ menuitem = gtk_separator_menu_item_new();
+ gtk_widget_show(menuitem);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+
+ remmina_icon_populate_additional_menu_item(menu);
+}
+
+#else
+
+void remmina_icon_populate_menu(void)
+{
+ TRACE_CALL(__func__);
+}
+
+static void remmina_icon_popdown_menu(GtkWidget *widget, gpointer data)
+{
+ TRACE_CALL(__func__);
+ if (gtk_get_current_event_time() - remmina_icon.popup_time <= 500) {
+ remmina_exec_command(REMMINA_COMMAND_MAIN, NULL);
+ }
+}
+
+static void remmina_icon_on_activate(GtkStatusIcon *icon, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *menu;
+ gint button, event_time;
+
+ remmina_icon.popup_time = gtk_get_current_event_time();
+ menu = remmina_applet_menu_new();
+ remmina_applet_menu_set_hide_count(REMMINA_APPLET_MENU(menu), remmina_pref.applet_hide_count);
+ remmina_applet_menu_populate(REMMINA_APPLET_MENU(menu));
+
+ remmina_icon_populate_extra_menu_item(menu);
+
+ button = 0;
+ event_time = gtk_get_current_event_time();
+
+ g_signal_connect(G_OBJECT(menu), "deactivate", G_CALLBACK(remmina_icon_popdown_menu), NULL);
+
+ gtk_menu_popup(GTK_MENU(menu), NULL, NULL, gtk_status_icon_position_menu, remmina_icon.icon, button, event_time);
+}
+
+static void remmina_icon_on_popup_menu(GtkStatusIcon *icon, guint button, guint activate_time, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *menu;
+
+ menu = gtk_menu_new();
+
+ remmina_icon_populate_additional_menu_item(menu);
+
+ gtk_menu_popup(GTK_MENU(menu), NULL, NULL, gtk_status_icon_position_menu, remmina_icon.icon, button, activate_time);
+}
+
+#endif
+
+static void remmina_icon_save_autostart_file(GKeyFile *gkeyfile)
+{
+ TRACE_CALL(__func__);
+ gchar *content;
+ gsize length;
+
+ content = g_key_file_to_data(gkeyfile, &length, NULL);
+ g_file_set_contents(remmina_icon.autostart_file, content, length, NULL);
+ g_free(content);
+}
+
+static void remmina_icon_create_autostart_file(void)
+{
+ TRACE_CALL(__func__);
+ GKeyFile *gkeyfile;
+
+ if (!g_file_test(remmina_icon.autostart_file, G_FILE_TEST_EXISTS)) {
+ gkeyfile = g_key_file_new();
+ g_key_file_set_string(gkeyfile, "Desktop Entry", "Version", "1.0");
+ g_key_file_set_string(gkeyfile, "Desktop Entry", "Name", _("Remmina Applet"));
+ g_key_file_set_string(gkeyfile, "Desktop Entry", "Comment",
+ _("Connect to remote desktops through the applet menu"));
+ g_key_file_set_string(gkeyfile, "Desktop Entry", "Icon", "remmina");
+ g_key_file_set_string(gkeyfile, "Desktop Entry", "Exec", "remmina -i");
+ g_key_file_set_boolean(gkeyfile, "Desktop Entry", "Terminal", FALSE);
+ g_key_file_set_string(gkeyfile, "Desktop Entry", "Type", "Application");
+ g_key_file_set_boolean(gkeyfile, "Desktop Entry", "Hidden", TRUE);
+ remmina_icon_save_autostart_file(gkeyfile);
+ g_key_file_free(gkeyfile);
+ }
+}
+
+/**
+ * Determine whenever the Remmina icon is available.
+ * Return TRUE if a remmina_icon (status indicator/systray menu) is
+ * available and shown to the user, so the user can continue
+ * its work without the remmina main window.
+ * @return TRUE if the Remmina icon is available.
+ */
+gboolean remmina_icon_is_available(void)
+{
+ TRACE_CALL(__func__);
+ gchar *gsversion;
+ unsigned int gsv_maj, gsv_min, gsv_seq;
+ gboolean gshell_has_legacyTray;
+
+ if (!remmina_icon.icon)
+ return FALSE;
+ if (remmina_pref.disable_tray_icon)
+ return FALSE;
+
+ /* Special treatmen under Gnome Shell */
+ if ((gsversion = remmina_sysinfo_get_gnome_shell_version()) != NULL) {
+ if (sscanf(gsversion, "%u.%u", &gsv_maj, &gsv_min) == 2)
+ gsv_seq = gsv_maj << 16 | gsv_min << 8;
+ else
+ gsv_seq = 0x030000;
+ g_free(gsversion);
+
+ gshell_has_legacyTray = FALSE;
+ if (gsv_seq >= 0x031000 && gsv_seq <= 0x031800) {
+ /* Gnome shell from 3.16 to 3.24, Status Icon (GtkStatusIcon) is visible on the drawer
+ * at the bottom left of the screen */
+ gshell_has_legacyTray = TRUE;
+ }
+
+
+#ifdef HAVE_LIBAPPINDICATOR
+ /** Gnome Shell with compiled in LIBAPPINDICATOR:
+ * ensure have also a working appindicator extension available.
+ */
+ if (remmina_sysinfo_is_appindicator_available()) {
+ /* No libappindicator extension for gnome shell, no remmina_icon */
+ return TRUE;
+ } else if (gshell_has_legacyTray) {
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+#endif
+ /* Gnome Shell without LIBAPPINDICATOR */
+ if (gshell_has_legacyTray) {
+ return TRUE;
+ }else {
+ /* Gnome shell < 3.16, Status Icon (GtkStatusIcon) is hidden
+ * on the message tray */
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+void remmina_icon_init(void)
+{
+ TRACE_CALL(__func__);
+
+ gchar remmina_panel[23];
+ gboolean sni_supported;
+ char msg[200];
+
+ if (remmina_pref.dark_tray_icon) {
+ g_stpcpy(remmina_panel, "remmina-panel-inverted");
+ }else {
+ g_stpcpy(remmina_panel, "remmina-panel");
+ }
+
+ /* Print on stdout the availability of appindicators on DBUS */
+ sni_supported = remmina_sysinfo_is_appindicator_available();
+
+ strcpy(msg, "StatusNotifier/Appindicator support: ");
+ if (sni_supported) {
+ strcat(msg, "your desktop does support it");
+#ifdef HAVE_LIBAPPINDICATOR
+ strcat(msg, " and libappindicator is compiled in remmina. Good!");
+#else
+ strcat(msg, ", but you did not compile remmina with cmake's -DWITH_APPINDICATOR=on");
+#endif
+ } else {
+#ifdef HAVE_LIBAPPINDICATOR
+ strcat(msg, "not supported by desktop. libappindicator will try to fallback to GtkStatusIcon/xembed");
+#else
+ strcat(msg, "not supported by desktop. Remmina will try to fallback to GtkStatusIcon/xembed");
+#endif
+ }
+ strcat(msg, "\n");
+ fputs(msg, stderr);
+
+ remmina_icon.gsversion = remmina_sysinfo_get_gnome_shell_version();
+ if (remmina_icon.gsversion != NULL) {
+ printf("Running under Gnome Shell version %s\n", remmina_icon.gsversion);
+ }
+
+ if (!remmina_icon.icon && !remmina_pref.disable_tray_icon) {
+#ifdef HAVE_LIBAPPINDICATOR
+ remmina_icon.icon = app_indicator_new("remmina-icon", remmina_panel, APP_INDICATOR_CATEGORY_APPLICATION_STATUS);
+ app_indicator_set_icon_theme_path(remmina_icon.icon, REMMINA_RUNTIME_DATADIR G_DIR_SEPARATOR_S "icons");
+
+ app_indicator_set_status(remmina_icon.icon, APP_INDICATOR_STATUS_ACTIVE);
+ app_indicator_set_title(remmina_icon.icon, "Remmina");
+ remmina_icon_populate_menu();
+#else
+ remmina_icon.icon = gtk_status_icon_new_from_icon_name(remmina_panel);
+
+ gtk_status_icon_set_title(remmina_icon.icon, _("Remmina Remote Desktop Client"));
+ gtk_status_icon_set_tooltip_text(remmina_icon.icon, _("Remmina Remote Desktop Client"));
+
+ g_signal_connect(G_OBJECT(remmina_icon.icon), "popup-menu", G_CALLBACK(remmina_icon_on_popup_menu), NULL);
+ g_signal_connect(G_OBJECT(remmina_icon.icon), "activate", G_CALLBACK(remmina_icon_on_activate), NULL);
+#endif
+ }else if (remmina_icon.icon) {
+#ifdef HAVE_LIBAPPINDICATOR
+ app_indicator_set_status(remmina_icon.icon, remmina_pref.disable_tray_icon ?
+ APP_INDICATOR_STATUS_PASSIVE : APP_INDICATOR_STATUS_ACTIVE);
+#else
+ gtk_status_icon_set_visible(remmina_icon.icon, !remmina_pref.disable_tray_icon);
+#endif
+ }
+ if (!remmina_icon.avahi) {
+ remmina_icon.avahi = remmina_avahi_new();
+ }
+ if (remmina_icon.avahi) {
+ if (remmina_pref.applet_enable_avahi) {
+ if (!remmina_icon.avahi->started)
+ remmina_avahi_start(remmina_icon.avahi);
+ }else {
+ remmina_avahi_stop(remmina_icon.avahi);
+ }
+ }
+ if (!remmina_icon.autostart_file) {
+ remmina_icon.autostart_file = g_strdup_printf("%s/.config/autostart/remmina-applet.desktop", g_get_home_dir());
+ remmina_icon_create_autostart_file();
+ }
+}
+
+gboolean remmina_icon_is_autostart(void)
+{
+ TRACE_CALL(__func__);
+ GKeyFile *gkeyfile;
+ gboolean b;
+
+ gkeyfile = g_key_file_new();
+ g_key_file_load_from_file(gkeyfile, remmina_icon.autostart_file, G_KEY_FILE_NONE, NULL);
+ b = !g_key_file_get_boolean(gkeyfile, "Desktop Entry", "Hidden", NULL);
+ g_key_file_free(gkeyfile);
+ return b;
+}
+
+void remmina_icon_set_autostart(gboolean autostart)
+{
+ TRACE_CALL(__func__);
+ GKeyFile *gkeyfile;
+ gboolean b;
+
+ gkeyfile = g_key_file_new();
+ g_key_file_load_from_file(gkeyfile, remmina_icon.autostart_file, G_KEY_FILE_NONE, NULL);
+ b = !g_key_file_get_boolean(gkeyfile, "Desktop Entry", "Hidden", NULL);
+ if (b != autostart) {
+ g_key_file_set_boolean(gkeyfile, "Desktop Entry", "Hidden", !autostart);
+ /* Refresh it in case translation is updated */
+ g_key_file_set_string(gkeyfile, "Desktop Entry", "Name", _("Remmina Applet"));
+ g_key_file_set_string(gkeyfile, "Desktop Entry", "Comment",
+ _("Connect to remote desktops through the applet menu"));
+ remmina_icon_save_autostart_file(gkeyfile);
+ }
+ g_key_file_free(gkeyfile);
+}
+
diff --git a/src/remmina_icon.h b/src/remmina_icon.h
new file mode 100644
index 000000000..46dc15cf9
--- /dev/null
+++ b/src/remmina_icon.h
@@ -0,0 +1,49 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010 Vic Lee
+ * Copyright (C) 2017-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.
+ *
+ */
+
+#pragma once
+
+G_BEGIN_DECLS
+
+void remmina_icon_init(void);
+gboolean remmina_icon_is_autostart(void);
+void remmina_icon_set_autostart(gboolean autostart);
+void remmina_icon_populate_menu(void);
+void remmina_icon_destroy(void);
+gboolean remmina_icon_is_available(void);
+
+G_END_DECLS
+
+
diff --git a/src/remmina_init_dialog.c b/src/remmina_init_dialog.c
new file mode 100644
index 000000000..3b560ea7f
--- /dev/null
+++ b/src/remmina_init_dialog.c
@@ -0,0 +1,830 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2011 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include "config.h"
+#include "remmina_public.h"
+#include "remmina_widget_pool.h"
+#include "remmina_init_dialog.h"
+#include "remmina_masterthread_exec.h"
+#include "remmina/remmina_trace_calls.h"
+
+G_DEFINE_TYPE( RemminaInitDialog, remmina_init_dialog, GTK_TYPE_DIALOG)
+
+static void remmina_init_dialog_class_init(RemminaInitDialogClass *klass)
+{
+ TRACE_CALL(__func__);
+}
+
+static void remmina_init_dialog_destroy(RemminaInitDialog *dialog, gpointer data)
+{
+ TRACE_CALL(__func__);
+ g_free(dialog->title);
+ g_free(dialog->status);
+ g_free(dialog->username);
+ g_free(dialog->domain);
+ g_free(dialog->password);
+ g_free(dialog->cacert);
+ g_free(dialog->cacrl);
+ g_free(dialog->clientcert);
+ g_free(dialog->clientkey);
+}
+
+static void remmina_init_dialog_init(RemminaInitDialog *dialog)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *hbox = NULL;
+ GtkWidget *widget;
+
+ dialog->image = NULL;
+ dialog->content_vbox = NULL;
+ dialog->status_label = NULL;
+ dialog->mode = REMMINA_INIT_MODE_CONNECTING;
+ dialog->title = NULL;
+ dialog->status = NULL;
+ dialog->username = NULL;
+ dialog->domain = NULL;
+ dialog->password = NULL;
+ dialog->save_password = FALSE;
+ dialog->cacert = NULL;
+ dialog->cacrl = NULL;
+ dialog->clientcert = NULL;
+ dialog->clientkey = NULL;
+
+ gtk_dialog_add_buttons(GTK_DIALOG(dialog), _("_Cancel"), GTK_RESPONSE_CANCEL, _("_OK"), GTK_RESPONSE_OK, NULL);
+
+ gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
+
+ gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
+
+ /**** Create the dialog content from here ****/
+
+ /* Create top-level hbox */
+ hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
+ gtk_widget_show(hbox);
+ gtk_container_set_border_width(GTK_CONTAINER(hbox), 15);
+ gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), hbox, TRUE, TRUE, 0);
+
+ /* Icon */
+ widget = gtk_image_new_from_icon_name("dialog-information", GTK_ICON_SIZE_DIALOG);
+ gtk_widget_show(widget);
+ gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, TRUE, 4);
+ dialog->image = widget;
+
+ /* Create vbox for other dialog content */
+ widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
+ gtk_widget_show(widget);
+ gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, TRUE, 4);
+ dialog->content_vbox = widget;
+
+ /* Entries */
+ widget = gtk_label_new(dialog->title);
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_show(widget);
+ gtk_box_pack_start(GTK_BOX(dialog->content_vbox), widget, TRUE, TRUE, 4);
+ dialog->status_label = widget;
+
+ g_signal_connect(G_OBJECT(dialog), "destroy", G_CALLBACK(remmina_init_dialog_destroy), NULL);
+
+ remmina_widget_pool_register(GTK_WIDGET(dialog));
+}
+
+static void remmina_init_dialog_connecting(RemminaInitDialog *dialog)
+{
+ TRACE_CALL(__func__);
+ gtk_label_set_text(GTK_LABEL(dialog->status_label), (dialog->status ? dialog->status : dialog->title));
+ gtk_image_set_from_icon_name(GTK_IMAGE(dialog->image), "dialog-information", GTK_ICON_SIZE_DIALOG);
+ gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), GTK_RESPONSE_OK, FALSE);
+
+ dialog->mode = REMMINA_INIT_MODE_CONNECTING;
+}
+
+GtkWidget* remmina_init_dialog_new(const gchar *title_format, ...)
+{
+ TRACE_CALL(__func__);
+ RemminaInitDialog *dialog;
+ va_list args;
+
+ dialog = REMMINA_INIT_DIALOG(g_object_new(REMMINA_TYPE_INIT_DIALOG, NULL));
+
+ va_start(args, title_format);
+ dialog->title = g_strdup_vprintf(title_format, args);
+ va_end(args);
+ gtk_window_set_title(GTK_WINDOW(dialog), dialog->title);
+
+ remmina_init_dialog_connecting(dialog);
+
+ return GTK_WIDGET(dialog);
+}
+
+void remmina_init_dialog_set_status(RemminaInitDialog *dialog, const gchar *status_format, ...)
+{
+ TRACE_CALL(__func__);
+ /* This function can be called from a non main thread */
+
+ va_list args;
+
+ if (!dialog)
+ return;
+
+ if (status_format) {
+ if (dialog->status)
+ g_free(dialog->status);
+
+ va_start(args, status_format);
+ dialog->status = g_strdup_vprintf(status_format, args);
+ va_end(args);
+
+ if ( remmina_masterthread_exec_is_main_thread() ) {
+ gtk_label_set_text(GTK_LABEL(dialog->status_label), dialog->status);
+ }else {
+ RemminaMTExecData *d;
+ d = (RemminaMTExecData*)g_malloc( sizeof(RemminaMTExecData) );
+ d->func = FUNC_GTK_LABEL_SET_TEXT;
+ d->p.gtk_label_set_text.label = GTK_LABEL(dialog->status_label);
+ d->p.gtk_label_set_text.str = dialog->status;
+ remmina_masterthread_exec_and_wait(d);
+ g_free(d);
+ }
+ }
+}
+
+void remmina_init_dialog_set_status_temp(RemminaInitDialog *dialog, const gchar *status_format, ...)
+{
+ TRACE_CALL(__func__);
+
+ /* This function can be called from a non main thread */
+
+ gchar* s;
+ va_list args;
+
+ if (status_format) {
+ va_start(args, status_format);
+ s = g_strdup_vprintf(status_format, args);
+ va_end(args);
+
+ if ( remmina_masterthread_exec_is_main_thread() ) {
+ gtk_label_set_text(GTK_LABEL(dialog->status_label), dialog->status);
+ }else {
+ RemminaMTExecData *d;
+ d = (RemminaMTExecData*)g_malloc( sizeof(RemminaMTExecData) );
+ d->func = FUNC_GTK_LABEL_SET_TEXT;
+ d->p.gtk_label_set_text.label = GTK_LABEL(dialog->status_label);
+ d->p.gtk_label_set_text.str = s;
+ remmina_masterthread_exec_and_wait(d);
+ g_free(d);
+ }
+
+ g_free(s);
+ }
+}
+
+gint remmina_init_dialog_authpwd(RemminaInitDialog *dialog, const gchar *label, gboolean allow_save)
+{
+ TRACE_CALL(__func__);
+
+ GtkWidget *grid;
+ GtkWidget *password_entry;
+ GtkWidget *save_password_check;
+ GtkWidget *widget;
+ gint ret;
+ gchar *s;
+
+ if ( !remmina_masterthread_exec_is_main_thread() ) {
+ /* Allow the execution of this function from a non main thread */
+ RemminaMTExecData *d;
+ gint retval;
+ d = (RemminaMTExecData*)g_malloc( sizeof(RemminaMTExecData) );
+ d->func = FUNC_DIALOG_AUTHPWD;
+ d->p.dialog_authpwd.dialog = dialog;
+ d->p.dialog_authpwd.label = label;
+ d->p.dialog_authpwd.allow_save = allow_save;
+ remmina_masterthread_exec_and_wait(d);
+ retval = d->p.dialog_authpwd.retval;
+ g_free(d);
+ return retval;
+ }
+
+ gtk_label_set_text(GTK_LABEL(dialog->status_label), (dialog->status ? dialog->status : dialog->title));
+
+ /* Create grid (was a table) */
+ grid = gtk_grid_new();
+ gtk_widget_show(grid);
+ gtk_grid_set_row_spacing(GTK_GRID(grid), 8);
+ gtk_grid_set_column_spacing(GTK_GRID(grid), 8);
+
+ /* Icon */
+ gtk_image_set_from_icon_name(GTK_IMAGE(dialog->image), "dialog-password", GTK_ICON_SIZE_DIALOG);
+
+ /* Entries */
+ widget = gtk_label_new(label);
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, 0, 1, 1);
+
+ password_entry = gtk_entry_new();
+ gtk_widget_show(password_entry);
+ gtk_grid_attach(GTK_GRID(grid), password_entry, 1, 0, 2, 1);
+ gtk_entry_set_max_length(GTK_ENTRY(password_entry), 100);
+ gtk_entry_set_visibility(GTK_ENTRY(password_entry), FALSE);
+ gtk_entry_set_activates_default(GTK_ENTRY(password_entry), TRUE);
+
+ s = g_strdup_printf(_("Save %s"), label);
+ save_password_check = gtk_check_button_new_with_label(s);
+ g_free(s);
+ gtk_widget_show(save_password_check);
+ gtk_grid_attach(GTK_GRID(grid), save_password_check, 0, 1, 2, 1);
+ if (dialog->save_password)
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(save_password_check), TRUE);
+ gtk_widget_set_sensitive(save_password_check, allow_save);
+
+ /* Pack it into the dialog */
+ gtk_box_pack_start(GTK_BOX(dialog->content_vbox), grid, TRUE, TRUE, 4);
+
+ gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), GTK_RESPONSE_OK, TRUE);
+
+ gtk_widget_grab_focus(password_entry);
+
+ dialog->mode = REMMINA_INIT_MODE_AUTHPWD;
+
+ /* Now run it */
+ ret = gtk_dialog_run(GTK_DIALOG(dialog));
+
+ if (ret == GTK_RESPONSE_OK) {
+ dialog->password = g_strdup(gtk_entry_get_text(GTK_ENTRY(password_entry)));
+ dialog->save_password = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(save_password_check));
+ }
+
+ gtk_widget_destroy(grid);
+
+ remmina_init_dialog_connecting(dialog);
+
+ return ret;
+}
+
+gint remmina_init_dialog_authuserpwd(RemminaInitDialog *dialog, gboolean want_domain, const gchar *default_username,
+ const gchar *default_domain, gboolean allow_save)
+{
+ TRACE_CALL(__func__);
+
+ GtkWidget *grid;
+ GtkWidget *username_entry;
+ GtkWidget *password_entry;
+ GtkWidget *domain_entry = NULL;
+ GtkWidget *save_password_check;
+ GtkWidget *widget;
+ gint ret;
+
+ if ( !remmina_masterthread_exec_is_main_thread() ) {
+ /* Allow the execution of this function from a non main thread */
+ RemminaMTExecData *d;
+ gint retval;
+ d = (RemminaMTExecData*)g_malloc( sizeof(RemminaMTExecData) );
+ d->func = FUNC_DIALOG_AUTHUSERPWD;
+ d->p.dialog_authuserpwd.dialog = dialog;
+ d->p.dialog_authuserpwd.want_domain = want_domain;
+ d->p.dialog_authuserpwd.default_username = default_username;
+ d->p.dialog_authuserpwd.default_domain = default_domain;
+ d->p.dialog_authuserpwd.allow_save = allow_save;
+ remmina_masterthread_exec_and_wait(d);
+ retval = d->p.dialog_authuserpwd.retval;
+ g_free(d);
+ return retval;
+ }
+
+ gtk_label_set_text(GTK_LABEL(dialog->status_label), (dialog->status ? dialog->status : dialog->title));
+
+ /* Create grid */
+ grid = gtk_grid_new();
+ gtk_widget_show(grid);
+ gtk_grid_set_row_spacing(GTK_GRID(grid), 8);
+ gtk_grid_set_column_spacing(GTK_GRID(grid), 8);
+ gtk_grid_set_column_homogeneous(GTK_GRID(grid), TRUE);
+
+ /* Icon */
+ gtk_image_set_from_icon_name(GTK_IMAGE(dialog->image), "dialog-password", GTK_ICON_SIZE_DIALOG);
+
+ /* Entries */
+ widget = gtk_label_new(_("User name"));
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, 0, 1, 1);
+
+ username_entry = gtk_entry_new();
+ gtk_widget_show(username_entry);
+ gtk_grid_attach(GTK_GRID(grid), username_entry, 1, 0, 2, 1);
+ gtk_entry_set_max_length(GTK_ENTRY(username_entry), 100);
+ if (default_username && default_username[0] != '\0') {
+ gtk_entry_set_text(GTK_ENTRY(username_entry), default_username);
+ }
+
+ widget = gtk_label_new(_("Password"));
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, 2, 1, 1);
+
+ password_entry = gtk_entry_new();
+ gtk_widget_show(password_entry);
+ gtk_grid_attach(GTK_GRID(grid), password_entry, 1, 2, 2, 1);
+ gtk_entry_set_max_length(GTK_ENTRY(password_entry), 100);
+ gtk_entry_set_visibility(GTK_ENTRY(password_entry), FALSE);
+ gtk_entry_set_activates_default(GTK_ENTRY(password_entry), TRUE);
+
+
+ if (want_domain) {
+ widget = gtk_label_new(_("Domain"));
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, 3, 1, 1);
+
+ domain_entry = gtk_entry_new();
+ gtk_widget_show(domain_entry);
+ gtk_grid_attach(GTK_GRID(grid), domain_entry, 1, 3, 2, 1);
+ gtk_entry_set_max_length(GTK_ENTRY(domain_entry), 100);
+ gtk_entry_set_activates_default(GTK_ENTRY(domain_entry), TRUE);
+ if (default_domain && default_domain[0] != '\0') {
+ gtk_entry_set_text(GTK_ENTRY(domain_entry), default_domain);
+ }
+ }
+
+ save_password_check = gtk_check_button_new_with_label(_("Save password"));
+ if (allow_save) {
+ gtk_widget_show(save_password_check);
+ gtk_grid_attach(GTK_GRID(grid), save_password_check, 0, 4, 2, 3);
+ if (dialog->save_password)
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(save_password_check), TRUE);
+ }else {
+ gtk_widget_set_sensitive(save_password_check, FALSE);
+ }
+
+ /* Pack it into the dialog */
+ gtk_box_pack_start(GTK_BOX(dialog->content_vbox), grid, TRUE, TRUE, 4);
+
+ gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), GTK_RESPONSE_OK, TRUE);
+
+ if (default_username && default_username[0] != '\0') {
+ gtk_widget_grab_focus(password_entry);
+ }else {
+ gtk_widget_grab_focus(username_entry);
+ }
+
+ dialog->mode = REMMINA_INIT_MODE_AUTHUSERPWD;
+
+ /* Now run it */
+ ret = gtk_dialog_run(GTK_DIALOG(dialog));
+ if (ret == GTK_RESPONSE_OK) {
+ dialog->username = g_strdup(gtk_entry_get_text(GTK_ENTRY(username_entry)));
+ dialog->password = g_strdup(gtk_entry_get_text(GTK_ENTRY(password_entry)));
+ dialog->save_password = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(save_password_check));
+
+ if (want_domain)
+ dialog->domain = g_strdup(gtk_entry_get_text(GTK_ENTRY(domain_entry)));
+ }
+
+ gtk_widget_destroy(grid);
+
+ remmina_init_dialog_connecting(dialog);
+
+ return ret;
+}
+
+gint remmina_init_dialog_certificate(RemminaInitDialog* dialog, const gchar* subject, const gchar* issuer, const gchar* fingerprint)
+{
+ TRACE_CALL(__func__);
+
+ gint status;
+ GtkWidget* grid;
+ GtkWidget* widget;
+ gchar* s;
+
+ if ( !remmina_masterthread_exec_is_main_thread() ) {
+ /* Allow the execution of this function from a non main thread */
+ RemminaMTExecData *d;
+ gint retval;
+ d = (RemminaMTExecData*)g_malloc( sizeof(RemminaMTExecData) );
+ d->func = FUNC_DIALOG_CERT;
+ d->p.dialog_certificate.dialog = dialog;
+ d->p.dialog_certificate.subject = subject;
+ d->p.dialog_certificate.issuer = issuer;
+ d->p.dialog_certificate.fingerprint = fingerprint;
+ remmina_masterthread_exec_and_wait(d);
+ retval = d->p.dialog_certificate.retval;
+ g_free(d);
+ return retval;
+ }
+
+ gtk_label_set_text(GTK_LABEL(dialog->status_label), _("Certificate Details:"));
+
+ /* Create table */
+ grid = gtk_grid_new();
+ gtk_widget_show(grid);
+ gtk_grid_set_row_spacing(GTK_GRID(grid), 8);
+ gtk_grid_set_column_spacing(GTK_GRID(grid), 8);
+ //gtk_grid_set_column_homogeneous (GTK_GRID(grid), TRUE);
+
+ /* Icon */
+ gtk_image_set_from_icon_name(GTK_IMAGE(dialog->image), "dialog-password", GTK_ICON_SIZE_DIALOG);
+
+ /* Entries */
+ widget = gtk_label_new(_("Subject:"));
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, 0, 1, 1);
+
+ widget = gtk_label_new(subject);
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 1, 0, 2, 1);
+
+ widget = gtk_label_new(_("Issuer:"));
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, 1, 1, 1);
+
+ widget = gtk_label_new(issuer);
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 1, 1, 2, 1);
+
+ widget = gtk_label_new(_("Fingerprint:"));
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, 2, 1, 1);
+
+ widget = gtk_label_new(fingerprint);
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 1, 2, 2, 1);
+
+ widget = gtk_label_new(NULL);
+ s = g_strdup_printf("<span size=\"large\"><b>%s</b></span>", _("Accept Certificate?"));
+ gtk_label_set_markup(GTK_LABEL(widget), s);
+ g_free(s);
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, 3, 3, 1);
+
+ /* Pack it into the dialog */
+ gtk_box_pack_start(GTK_BOX(dialog->content_vbox), grid, TRUE, TRUE, 4);
+
+ gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), GTK_RESPONSE_OK, TRUE);
+
+ dialog->mode = REMMINA_INIT_MODE_CERTIFICATE;
+
+ /* Now run it */
+ status = gtk_dialog_run(GTK_DIALOG(dialog));
+
+ if (status == GTK_RESPONSE_OK) {
+
+ }
+
+ gtk_widget_destroy(grid);
+
+ return status;
+}
+gint remmina_init_dialog_certificate_changed(RemminaInitDialog* dialog, const gchar* subject, const gchar* issuer, const gchar* new_fingerprint, const gchar* old_fingerprint)
+{
+ TRACE_CALL(__func__);
+
+ gint status;
+ GtkWidget* grid;
+ GtkWidget* widget;
+ gchar* s;
+
+ if ( !remmina_masterthread_exec_is_main_thread() ) {
+ /* Allow the execution of this function from a non main thread */
+ RemminaMTExecData *d;
+ gint retval;
+ d = (RemminaMTExecData*)g_malloc( sizeof(RemminaMTExecData) );
+ d->func = FUNC_DIALOG_CERTCHANGED;
+ d->p.dialog_certchanged.dialog = dialog;
+ d->p.dialog_certchanged.subject = subject;
+ d->p.dialog_certchanged.issuer = issuer;
+ d->p.dialog_certchanged.new_fingerprint = new_fingerprint;
+ d->p.dialog_certchanged.old_fingerprint = old_fingerprint;
+ remmina_masterthread_exec_and_wait(d);
+ retval = d->p.dialog_certchanged.retval;
+ g_free(d);
+ return retval;
+ }
+
+ gtk_label_set_text(GTK_LABEL(dialog->status_label), _("Certificate Changed! Details:"));
+
+ /* Create table */
+ grid = gtk_grid_new();
+ gtk_widget_show(grid);
+ gtk_grid_set_row_spacing(GTK_GRID(grid), 8);
+ gtk_grid_set_column_spacing(GTK_GRID(grid), 8);
+
+ /* Icon */
+ gtk_image_set_from_icon_name(GTK_IMAGE(dialog->image), "dialog-password", GTK_ICON_SIZE_DIALOG);
+
+ /* Entries */
+ /* Not tested */
+ widget = gtk_label_new(_("Subject:"));
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, 0, 1, 1);
+
+
+ widget = gtk_label_new(subject);
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 1, 0, 2, 1);
+
+ widget = gtk_label_new(_("Issuer:"));
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, 1, 1, 1);
+
+ widget = gtk_label_new(issuer);
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 1, 1, 2, 1);
+
+ widget = gtk_label_new(_("Old Fingerprint:"));
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, 2, 1, 1);
+
+ widget = gtk_label_new(old_fingerprint);
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 1, 2, 2, 1);
+
+ widget = gtk_label_new(_("New Fingerprint:"));
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, 3, 1, 1);
+
+ widget = gtk_label_new(new_fingerprint);
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 1, 3, 2, 1);
+
+ widget = gtk_label_new(NULL);
+ s = g_strdup_printf("<span size=\"large\"><b>%s</b></span>", _("Accept Changed Certificate?"));
+ gtk_label_set_markup(GTK_LABEL(widget), s);
+ g_free(s);
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(grid), widget, 0, 4, 3, 1);
+
+ /* Pack it into the dialog */
+ gtk_box_pack_start(GTK_BOX(dialog->content_vbox), grid, TRUE, TRUE, 4);
+
+ gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), GTK_RESPONSE_OK, TRUE);
+
+ dialog->mode = REMMINA_INIT_MODE_CERTIFICATE;
+
+ /* Now run it */
+ status = gtk_dialog_run(GTK_DIALOG(dialog));
+
+ if (status == GTK_RESPONSE_OK) {
+
+ }
+
+ gtk_widget_destroy(grid);
+
+ return status;
+}
+
+/* NOT TESTED */
+static GtkWidget* remmina_init_dialog_create_file_button(GtkGrid *grid, const gchar *label, gint row, const gchar *filename)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *widget;
+ gchar *pkidir;
+
+ widget = gtk_label_new(label);
+ gtk_widget_show(widget);
+ gtk_grid_attach(grid, widget, 0, row, 1, row + 1);
+
+ widget = gtk_file_chooser_button_new(label, GTK_FILE_CHOOSER_ACTION_OPEN);
+ gtk_file_chooser_button_set_width_chars(GTK_FILE_CHOOSER_BUTTON(widget), 25);
+ gtk_widget_show(widget);
+ gtk_grid_attach(grid, widget, 1, row, 2, row + 1);
+ if (filename && filename[0] != '\0') {
+ gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(widget), filename);
+ }else {
+ pkidir = g_strdup_printf("%s/.pki", g_get_home_dir());
+ if (g_file_test(pkidir, G_FILE_TEST_IS_DIR)) {
+ gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(widget), pkidir);
+ }
+ g_free(pkidir);
+ }
+
+ return widget;
+}
+
+gint remmina_init_dialog_authx509(RemminaInitDialog *dialog, const gchar *cacert, const gchar *cacrl, const gchar *clientcert,
+ const gchar *clientkey)
+{
+ TRACE_CALL(__func__);
+
+ GtkWidget *grid;
+ GtkWidget *cacert_button;
+ GtkWidget *cacrl_button;
+ GtkWidget *clientcert_button;
+ GtkWidget *clientkey_button;
+ gint ret;
+
+
+ if ( !remmina_masterthread_exec_is_main_thread() ) {
+ /* Allow the execution of this function from a non main thread */
+ RemminaMTExecData *d;
+ gint retval;
+ d = (RemminaMTExecData*)g_malloc( sizeof(RemminaMTExecData) );
+ d->func = FUNC_DIALOG_AUTHX509;
+ d->p.dialog_authx509.dialog = dialog;
+ d->p.dialog_authx509.cacert = cacert;
+ d->p.dialog_authx509.cacrl = cacrl;
+ d->p.dialog_authx509.clientcert = clientcert;
+ d->p.dialog_authx509.clientkey = clientkey;
+ remmina_masterthread_exec_and_wait(d);
+ retval = d->p.dialog_authx509.retval;
+ g_free(d);
+ return retval;
+ }
+
+ gtk_label_set_text(GTK_LABEL(dialog->status_label), (dialog->status ? dialog->status : dialog->title));
+
+ /* Create table */
+ grid = gtk_grid_new();
+ gtk_widget_show(grid);
+ gtk_grid_set_row_spacing(GTK_GRID(grid), 8);
+ gtk_grid_set_column_spacing(GTK_GRID(grid), 8);
+
+ /* Icon */
+ gtk_image_set_from_icon_name(GTK_IMAGE(dialog->image), "dialog-password", GTK_ICON_SIZE_DIALOG);
+
+ /* Buttons for choosing the certificates */
+ cacert_button = remmina_init_dialog_create_file_button(GTK_GRID(grid), _("CA certificate"), 0, cacert);
+ cacrl_button = remmina_init_dialog_create_file_button(GTK_GRID(grid), _("CA CRL"), 1, cacrl);
+ clientcert_button = remmina_init_dialog_create_file_button(GTK_GRID(grid), _("Client certificate"), 2, clientcert);
+ clientkey_button = remmina_init_dialog_create_file_button(GTK_GRID(grid), _("Client key"), 3, clientkey);
+
+ /* Pack it into the dialog */
+ gtk_box_pack_start(GTK_BOX(dialog->content_vbox), grid, TRUE, TRUE, 4);
+
+ gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), GTK_RESPONSE_OK, TRUE);
+
+ dialog->mode = REMMINA_INIT_MODE_AUTHX509;
+
+ /* Now run it */
+ ret = gtk_dialog_run(GTK_DIALOG(dialog));
+ if (ret == GTK_RESPONSE_OK) {
+ dialog->cacert = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(cacert_button));
+ dialog->cacrl = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(cacrl_button));
+ dialog->clientcert = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(clientcert_button));
+ dialog->clientkey = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(clientkey_button));
+ }
+
+ gtk_widget_destroy(grid);
+
+ remmina_init_dialog_connecting(dialog);
+
+ return ret;
+}
+
+gint remmina_init_dialog_serverkey_confirm(RemminaInitDialog *dialog, const gchar *serverkey, const gchar *prompt)
+{
+ TRACE_CALL(__func__);
+
+ GtkWidget *vbox = NULL;
+ GtkWidget *widget;
+ gint ret;
+
+ if ( !remmina_masterthread_exec_is_main_thread() ) {
+ /* Allow the execution of this function from a non main thread */
+ RemminaMTExecData *d;
+ gint retval;
+ d = (RemminaMTExecData*)g_malloc( sizeof(RemminaMTExecData) );
+ d->func = FUNC_DIALOG_SERVERKEY_CONFIRM;
+ d->p.dialog_serverkey_confirm.dialog = dialog;
+ d->p.dialog_serverkey_confirm.serverkey = serverkey;
+ d->p.dialog_serverkey_confirm.prompt = prompt;
+ remmina_masterthread_exec_and_wait(d);
+ retval = d->p.dialog_serverkey_confirm.retval;
+ g_free(d);
+ return retval;
+ }
+
+ gtk_label_set_text(GTK_LABEL(dialog->status_label), (dialog->status ? dialog->status : dialog->title));
+
+ /* Create vbox */
+ vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
+ gtk_widget_show(vbox);
+
+ /* Icon */
+ gtk_image_set_from_icon_name(GTK_IMAGE(dialog->image), "dialog-warning", GTK_ICON_SIZE_DIALOG);
+
+ /* Entries */
+ widget = gtk_label_new(prompt);
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_show(widget);
+ gtk_box_pack_start(GTK_BOX(vbox), widget, TRUE, TRUE, 4);
+
+ widget = gtk_label_new(serverkey);
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_show(widget);
+ gtk_box_pack_start(GTK_BOX(vbox), widget, TRUE, TRUE, 4);
+
+ widget = gtk_label_new(_("Do you trust the new public key?"));
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ gtk_widget_show(widget);
+ gtk_box_pack_start(GTK_BOX(vbox), widget, TRUE, TRUE, 4);
+
+ /* Pack it into the dialog */
+ gtk_box_pack_start(GTK_BOX(dialog->content_vbox), vbox, TRUE, TRUE, 4);
+
+ gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), GTK_RESPONSE_OK, TRUE);
+
+ dialog->mode = REMMINA_INIT_MODE_SERVERKEY_CONFIRM;
+
+ /* Now run it */
+ ret = gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(vbox);
+ remmina_init_dialog_connecting(dialog);
+
+ return ret;
+}
+
+
+gint remmina_init_dialog_serverkey_unknown(RemminaInitDialog *dialog, const gchar *serverkey)
+{
+ TRACE_CALL(__func__);
+ /* This function can be called from a non main thread */
+
+ return remmina_init_dialog_serverkey_confirm(dialog, serverkey,
+ _("The server is unknown. The public key fingerprint is:"));
+}
+
+gint remmina_init_dialog_serverkey_changed(RemminaInitDialog *dialog, const gchar *serverkey)
+{
+ TRACE_CALL(__func__);
+ /* This function can be called from a non main thread */
+
+ return remmina_init_dialog_serverkey_confirm(dialog, serverkey,
+ _("WARNING: The server has changed its public key. This means either you are under attack,\n"
+ "or the administrator has changed the key. The new public key fingerprint is:"));
+}
+
diff --git a/src/remmina_init_dialog.h b/src/remmina_init_dialog.h
new file mode 100644
index 000000000..8e4203a20
--- /dev/null
+++ b/src/remmina_init_dialog.h
@@ -0,0 +1,102 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2011 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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.
+ *
+ */
+
+#pragma once
+
+G_BEGIN_DECLS
+
+#define REMMINA_TYPE_INIT_DIALOG (remmina_init_dialog_get_type())
+#define REMMINA_INIT_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), REMMINA_TYPE_INIT_DIALOG, RemminaInitDialog))
+#define REMMINA_INIT_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), REMMINA_TYPE_INIT_DIALOG, RemminaInitDialogClass))
+#define REMMINA_IS_INIT_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), REMMINA_TYPE_INIT_DIALOG))
+#define REMMINA_IS_INIT_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), REMMINA_TYPE_INIT_DIALOG))
+#define REMMINA_INIT_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), REMMINA_TYPE_INIT_DIALOG, RemminaInitDialogClass))
+
+enum {
+ REMMINA_INIT_MODE_CONNECTING,
+ REMMINA_INIT_MODE_AUTHPWD,
+ REMMINA_INIT_MODE_AUTHUSERPWD,
+ REMMINA_INIT_MODE_AUTHX509,
+ REMMINA_INIT_MODE_SERVERKEY_CONFIRM,
+ REMMINA_INIT_MODE_CERTIFICATE
+};
+
+typedef struct _RemminaInitDialog {
+ GtkDialog dialog;
+
+ GtkWidget *image;
+ GtkWidget *content_vbox;
+ GtkWidget *status_label;
+
+ gint mode;
+
+ gchar *title;
+ gchar *status;
+ gchar *username;
+ gchar *domain;
+ gchar *password;
+ gboolean save_password;
+ gchar *cacert;
+ gchar *cacrl;
+ gchar *clientcert;
+ gchar *clientkey;
+} RemminaInitDialog;
+
+typedef struct _RemminaInitDialogClass {
+ GtkDialogClass parent_class;
+} RemminaInitDialogClass;
+
+GType remmina_init_dialog_get_type(void)
+G_GNUC_CONST;
+
+GtkWidget* remmina_init_dialog_new(const gchar *title_format, ...);
+void remmina_init_dialog_set_status(RemminaInitDialog *dialog, const gchar *status_format, ...);
+void remmina_init_dialog_set_status_temp(RemminaInitDialog *dialog, const gchar *status_format, ...);
+/* Run authentication. Return GTK_RESPONSE_OK or GTK_RESPONSE_CANCEL. Caller is blocked. */
+gint remmina_init_dialog_authpwd(RemminaInitDialog *dialog, const gchar *label, gboolean allow_save);
+gint remmina_init_dialog_authuserpwd(RemminaInitDialog *dialog, gboolean want_domain, const gchar *default_username,
+ const gchar *default_domain, gboolean allow_save);
+gint remmina_init_dialog_certificate(RemminaInitDialog* dialog, const gchar* subject, const gchar* issuer, const gchar* fingerprint);
+gint remmina_init_dialog_certificate_changed(RemminaInitDialog* dialog, const gchar* subject, const gchar* issuer, const gchar* old_fingerprint, const gchar* new_fingerprint);
+gint remmina_init_dialog_authx509(RemminaInitDialog *dialog, const gchar *cacert, const gchar *cacrl, const gchar *clientcert,
+ const gchar *clientkey);
+gint remmina_init_dialog_serverkey_unknown(RemminaInitDialog *dialog, const gchar *serverkey);
+gint remmina_init_dialog_serverkey_changed(RemminaInitDialog *dialog, const gchar *serverkey);
+gint remmina_init_dialog_serverkey_confirm(RemminaInitDialog *dialog, const gchar *serverkey, const gchar *prompt);
+
+G_END_DECLS
+
+
diff --git a/src/remmina_key_chooser.c b/src/remmina_key_chooser.c
new file mode 100644
index 000000000..684cfce4d
--- /dev/null
+++ b/src/remmina_key_chooser.c
@@ -0,0 +1,133 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2010 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include "remmina_key_chooser.h"
+#include "remmina_public.h"
+#include "remmina/remmina_trace_calls.h"
+
+/* Handle key-presses on the GtkEventBox */
+static gboolean remmina_key_chooser_dialog_on_key_press(GtkWidget *widget, GdkEventKey *event, RemminaKeyChooserArguments *arguments)
+{
+ TRACE_CALL(__func__);
+ if (!arguments->use_modifiers || !event->is_modifier) {
+ arguments->state = event->state;
+ arguments->keyval = gdk_keyval_to_lower(event->keyval);
+ gtk_dialog_response(GTK_DIALOG(gtk_widget_get_toplevel(widget)),
+ event->keyval == GDK_KEY_Escape ? GTK_RESPONSE_CANCEL : GTK_RESPONSE_OK);
+ }
+ return TRUE;
+}
+
+/* Show a key chooser dialog and return the keyval for the selected key */
+RemminaKeyChooserArguments* remmina_key_chooser_new(GtkWindow *parent_window, gboolean use_modifiers)
+{
+ TRACE_CALL(__func__);
+ GtkBuilder *builder = remmina_public_gtk_builder_new_from_file("remmina_key_chooser.glade");
+ GtkDialog *dialog;
+ RemminaKeyChooserArguments *arguments;
+ arguments = g_new0(RemminaKeyChooserArguments, 1);
+ arguments->state = 0;
+ arguments->use_modifiers = use_modifiers;
+
+ /* Setup the dialog */
+ dialog = GTK_DIALOG(gtk_builder_get_object(builder, "KeyChooserDialog"));
+ gtk_window_set_transient_for(GTK_WINDOW(dialog), parent_window);
+ /* Connect the GtkEventBox signal */
+ g_signal_connect(gtk_builder_get_object(builder, "eventbox_key_chooser"), "key-press-event",
+ G_CALLBACK(remmina_key_chooser_dialog_on_key_press), arguments);
+ /* Show the dialog and destroy it after the use */
+ arguments->response = gtk_dialog_run(dialog);
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+ /* The delete button set the keyval 0 */
+ if (arguments->response == GTK_RESPONSE_REJECT)
+ arguments->keyval = 0;
+ return arguments;
+}
+
+/* Get the uppercase character value of a keyval */
+gchar* remmina_key_chooser_get_value(guint keyval, guint state)
+{
+ TRACE_CALL(__func__);
+
+ if (!keyval)
+ return g_strdup(KEY_CHOOSER_NONE);
+
+ return g_strdup_printf("%s%s%s%s%s%s%s",
+ state & GDK_SHIFT_MASK ? KEY_MODIFIER_SHIFT : "",
+ state & GDK_CONTROL_MASK ? KEY_MODIFIER_CTRL : "",
+ state & GDK_MOD1_MASK ? KEY_MODIFIER_ALT : "",
+ state & GDK_SUPER_MASK ? KEY_MODIFIER_SUPER : "",
+ state & GDK_HYPER_MASK ? KEY_MODIFIER_HYPER : "",
+ state & GDK_META_MASK ? KEY_MODIFIER_META : "",
+ gdk_keyval_name(gdk_keyval_to_upper(keyval)));
+}
+
+/* Get the keyval of a (lowercase) character value */
+guint remmina_key_chooser_get_keyval(const gchar *value)
+{
+ TRACE_CALL(__func__);
+ gchar *patterns[] =
+ {
+ KEY_MODIFIER_SHIFT,
+ KEY_MODIFIER_CTRL,
+ KEY_MODIFIER_ALT,
+ KEY_MODIFIER_SUPER,
+ KEY_MODIFIER_HYPER,
+ KEY_MODIFIER_META,
+ NULL
+ };
+ gint i;
+ gchar *tmpvalue;
+ gchar *newvalue;
+ guint keyval;
+
+ if (g_strcmp0(value, KEY_CHOOSER_NONE) == 0)
+ return 0;
+
+ /* Remove any modifier text before to get the keyval */
+ newvalue = g_strdup(value);
+ for (i = 0; i < g_strv_length(patterns); i++) {
+ tmpvalue = remmina_public_str_replace(newvalue, patterns[i], "");
+ g_free(newvalue);
+ newvalue = g_strdup(tmpvalue);
+ g_free(tmpvalue);
+ }
+ keyval = gdk_keyval_to_lower(gdk_keyval_from_name(newvalue));
+ g_free(newvalue);
+ return keyval;
+}
diff --git a/src/remmina_key_chooser.h b/src/remmina_key_chooser.h
new file mode 100644
index 000000000..88960e949
--- /dev/null
+++ b/src/remmina_key_chooser.h
@@ -0,0 +1,64 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010 Vic Lee
+ * Copyright (C) 2017-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.
+ *
+ */
+
+#pragma once
+
+#define KEY_MODIFIER_SHIFT _("Shift+")
+#define KEY_MODIFIER_CTRL _("Ctrl+")
+#define KEY_MODIFIER_ALT _("Alt+")
+#define KEY_MODIFIER_SUPER _("Super+")
+#define KEY_MODIFIER_HYPER _("Hyper+")
+#define KEY_MODIFIER_META _("Meta+")
+#define KEY_CHOOSER_NONE _("<None>")
+
+typedef struct _RemminaKeyChooserArguments {
+ guint keyval;
+ guint state;
+ gboolean use_modifiers;
+ gint response;
+} RemminaKeyChooserArguments;
+
+G_BEGIN_DECLS
+
+/* Show a key chooser dialog and return the keyval for the selected key */
+RemminaKeyChooserArguments* remmina_key_chooser_new(GtkWindow *parent_window, gboolean use_modifiers);
+/* Get the uppercase character value of a keyval */
+gchar* remmina_key_chooser_get_value(guint keyval, guint state);
+/* Get the keyval of a (lowercase) character value */
+guint remmina_key_chooser_get_keyval(const gchar *value);
+
+G_END_DECLS
+
+
diff --git a/src/remmina_log.c b/src/remmina_log.c
new file mode 100644
index 000000000..f0f2abc56
--- /dev/null
+++ b/src/remmina_log.c
@@ -0,0 +1,206 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include "remmina_public.h"
+#include "remmina_log.h"
+#include "remmina_stats_sender.h"
+#include "remmina/remmina_trace_calls.h"
+
+/***** Define the log window GUI *****/
+#define REMMINA_TYPE_LOG_WINDOW (remmina_log_window_get_type())
+#define REMMINA_LOG_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), REMMINA_TYPE_LOG_WINDOW, RemminaLogWindow))
+#define REMMINA_LOG_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), REMMINA_TYPE_LOG_WINDOW, RemminaLogWindowClass))
+#define REMMINA_IS_LOG_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), REMMINA_TYPE_LOG_WINDOW))
+#define REMMINA_IS_LOG_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), REMMINA_TYPE_LOG_WINDOW))
+#define REMMINA_LOG_WINDOW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), REMMINA_TYPE_LOG_WINDOW, RemminaLogWindowClass))
+
+typedef struct _RemminaLogWindow {
+ GtkWindow window;
+
+ GtkWidget *log_view;
+ GtkTextBuffer *log_buffer;
+} RemminaLogWindow;
+
+typedef struct _RemminaLogWindowClass {
+ GtkWindowClass parent_class;
+} RemminaLogWindowClass;
+
+GType remmina_log_window_get_type(void)
+G_GNUC_CONST;
+
+G_DEFINE_TYPE(RemminaLogWindow, remmina_log_window, GTK_TYPE_WINDOW)
+
+static void remmina_log_window_class_init(RemminaLogWindowClass *klass)
+{
+ TRACE_CALL(__func__);
+}
+
+/* We will always only have one log window per instance */
+static GtkWidget *log_window = NULL;
+
+static gboolean remmina_log_on_keypress(GtkWidget *widget, GdkEvent *event, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ // RemminaLogWindow *logwin = (RemminaLogWindow*)user_data;
+ GdkEventKey *e = (GdkEventKey *)event;
+
+ if (!log_window)
+ return FALSE;
+
+ if ((e->state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK) {
+ if ((e->keyval == GDK_KEY_s || e->keyval == GDK_KEY_t) && remmina_stat_sender_can_send()) {
+ remmina_stats_sender_send(e->keyval != GDK_KEY_s);
+ }
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void remmina_log_window_init(RemminaLogWindow *logwin)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *scrolledwindow;
+ GtkWidget *widget;
+
+ gtk_container_set_border_width(GTK_CONTAINER(logwin), 4);
+
+ scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
+ gtk_widget_show(scrolledwindow);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwindow), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
+ gtk_container_add(GTK_CONTAINER(logwin), scrolledwindow);
+
+ widget = gtk_text_view_new();
+ gtk_widget_show(widget);
+ gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(widget), GTK_WRAP_WORD_CHAR);
+ gtk_text_view_set_editable(GTK_TEXT_VIEW(widget), FALSE);
+ gtk_container_add(GTK_CONTAINER(scrolledwindow), widget);
+ logwin->log_view = widget;
+ logwin->log_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
+
+ g_signal_connect(G_OBJECT(logwin->log_view), "key-press-event", G_CALLBACK(remmina_log_on_keypress), (gpointer)logwin);
+}
+
+static GtkWidget*
+remmina_log_window_new(void)
+{
+ TRACE_CALL(__func__);
+ return GTK_WIDGET(g_object_new(REMMINA_TYPE_LOG_WINDOW, NULL));
+}
+
+static void remmina_log_end(GtkWidget *widget, gpointer data)
+{
+ TRACE_CALL(__func__);
+ log_window = NULL;
+}
+
+void remmina_log_start(void)
+{
+ TRACE_CALL(__func__);
+ if (log_window) {
+ gtk_window_present(GTK_WINDOW(log_window));
+ }else {
+ log_window = remmina_log_window_new();
+ gtk_window_set_default_size(GTK_WINDOW(log_window), 640, 480);
+ g_signal_connect(G_OBJECT(log_window), "destroy", G_CALLBACK(remmina_log_end), NULL);
+ gtk_widget_show(log_window);
+ }
+ remmina_log_print("Welcome to the remmina log window\n");
+ if (remmina_stat_sender_can_send())
+ remmina_log_print("Shortcut keys for stats:\n"
+ "\tCTRL+S: collect, show and send stats\n"
+ "\tCTRL+T: collect and show stats\n");
+}
+
+gboolean remmina_log_running(void)
+{
+ TRACE_CALL(__func__);
+ return (log_window != NULL);
+}
+
+static gboolean remmina_log_scroll_to_end(gpointer data)
+{
+ TRACE_CALL(__func__);
+ GtkTextIter iter;
+
+ if (log_window) {
+ gtk_text_buffer_get_end_iter(REMMINA_LOG_WINDOW(log_window)->log_buffer, &iter);
+ gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(REMMINA_LOG_WINDOW(log_window)->log_view), &iter, 0.0, FALSE, 0.0,
+ 0.0);
+ }
+ return FALSE;
+}
+
+static gboolean remmina_log_print_real(gpointer data)
+{
+ TRACE_CALL(__func__);
+ GtkTextIter iter;
+
+ if (log_window) {
+ gtk_text_buffer_get_end_iter(REMMINA_LOG_WINDOW(log_window)->log_buffer, &iter);
+ gtk_text_buffer_insert(REMMINA_LOG_WINDOW(log_window)->log_buffer, &iter, (const gchar*)data, -1);
+ IDLE_ADD(remmina_log_scroll_to_end, NULL);
+ }
+ g_free(data);
+ return FALSE;
+}
+
+void remmina_log_print(const gchar *text)
+{
+ TRACE_CALL(__func__);
+ if (!log_window)
+ return;
+
+ IDLE_ADD(remmina_log_print_real, g_strdup(text));
+}
+
+void remmina_log_printf(const gchar *fmt, ...)
+{
+ TRACE_CALL(__func__);
+ va_list args;
+ gchar *text;
+
+ if (!log_window) return;
+
+ va_start(args, fmt);
+ text = g_strdup_vprintf(fmt, args);
+ va_end(args);
+
+ IDLE_ADD(remmina_log_print_real, text);
+}
+
diff --git a/src/remmina_log.h b/src/remmina_log.h
new file mode 100644
index 000000000..628a80ff3
--- /dev/null
+++ b/src/remmina_log.h
@@ -0,0 +1,47 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010 Vic Lee
+ * Copyright (C) 2017-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.
+ *
+ */
+
+#pragma once
+
+G_BEGIN_DECLS
+
+void remmina_log_start(void);
+gboolean remmina_log_running(void);
+void remmina_log_print(const gchar *text);
+void remmina_log_printf(const gchar *fmt, ...);
+
+G_END_DECLS
+
+
diff --git a/src/remmina_main.c b/src/remmina_main.c
new file mode 100644
index 000000000..e73d16580
--- /dev/null
+++ b/src/remmina_main.c
@@ -0,0 +1,1272 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2011 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 "config.h"
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <glib/gi18n.h>
+#include "remmina_string_array.h"
+#include "remmina_public.h"
+#include "remmina_file.h"
+#include "remmina_file_manager.h"
+#include "remmina_file_editor.h"
+#include "remmina_connection_window.h"
+#include "remmina_about.h"
+#include "remmina_pref.h"
+#include "remmina_pref_dialog.h"
+#include "remmina_widget_pool.h"
+#include "remmina_plugin_manager.h"
+#include "remmina_log.h"
+#include "remmina_icon.h"
+#include "remmina_main.h"
+#include "remmina_exec.h"
+#include "remmina_mpchange.h"
+#include "remmina_external_tools.h"
+#include "remmina/remmina_trace_calls.h"
+#include "remmina_stats_sender.h"
+
+static RemminaMain *remminamain;
+
+#define GET_OBJECT(object_name) gtk_builder_get_object(remminamain->builder, object_name)
+
+enum {
+ PROTOCOL_COLUMN,
+ NAME_COLUMN,
+ GROUP_COLUMN,
+ SERVER_COLUMN,
+ DATE_COLUMN,
+ FILENAME_COLUMN,
+ N_COLUMNS
+};
+
+static GtkTargetEntry remmina_drop_types[] =
+{
+ { "text/uri-list", 0, 1 }
+};
+
+static char *quick_connect_plugin_list[] =
+{
+ "RDP", "VNC", "SSH", "NX", "SPICE"
+};
+
+static void remmina_main_save_size(void)
+{
+ TRACE_CALL(__func__);
+ if ((gdk_window_get_state(gtk_widget_get_window(GTK_WIDGET(remminamain->window))) & GDK_WINDOW_STATE_MAXIMIZED) == 0) {
+ gtk_window_get_size(remminamain->window, &remmina_pref.main_width, &remmina_pref.main_height);
+ remmina_pref.main_maximize = FALSE;
+ }else {
+ remmina_pref.main_maximize = TRUE;
+ }
+}
+
+static void remmina_main_save_expanded_group_func(GtkTreeView *tree_view, GtkTreePath *path, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ GtkTreeIter iter;
+ gchar *group;
+
+ gtk_tree_model_get_iter(remminamain->priv->file_model_sort, &iter, path);
+ gtk_tree_model_get(remminamain->priv->file_model_sort, &iter, GROUP_COLUMN, &group, -1);
+ if (group) {
+ remmina_string_array_add(remminamain->priv->expanded_group, group);
+ g_free(group);
+ }
+}
+
+static void remmina_main_save_expanded_group(void)
+{
+ TRACE_CALL(__func__);
+ if (GTK_IS_TREE_STORE(remminamain->priv->file_model)) {
+ if (remminamain->priv->expanded_group) {
+ remmina_string_array_free(remminamain->priv->expanded_group);
+ }
+ remminamain->priv->expanded_group = remmina_string_array_new();
+ gtk_tree_view_map_expanded_rows(remminamain->tree_files_list,
+ (GtkTreeViewMappingFunc)remmina_main_save_expanded_group_func, NULL);
+ }
+}
+
+void remmina_main_save_before_destroy()
+{
+ if (!remminamain || !remminamain->window)
+ return;
+ remmina_main_save_size();
+ remmina_main_save_expanded_group();
+ remmina_pref_save();
+}
+
+static gboolean remmina_main_dexit(gpointer data)
+{
+ /* Try to exit remmina after a delete window event */
+ TRACE_CALL(__func__);
+ remmina_application_condexit(REMMINA_CONDEXIT_ONMAINWINDELETE);
+ return FALSE;
+}
+
+gboolean remmina_main_on_delete_event(GtkWidget *widget, GdkEvent *event, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ remmina_main_save_before_destroy();
+
+ // Forget the main window: it has been deleted
+ remminamain->window = NULL;
+ g_idle_add(remmina_main_dexit, NULL);
+
+ return FALSE;
+}
+
+void remmina_main_destroy()
+{
+ TRACE_CALL(__func__);
+
+ /* Called when main window is destroyed via a call of gtk_widget_destroy() */
+ if (remminamain) {
+ if (remminamain->window)
+ remmina_main_save_before_destroy();
+ g_free(remmina_pref.expanded_group);
+ remmina_pref.expanded_group = remmina_string_array_to_string(remminamain->priv->expanded_group);
+ remmina_string_array_free(remminamain->priv->expanded_group);
+ remminamain->priv->expanded_group = NULL;
+
+ if (remminamain->priv->file_model)
+ g_object_unref(G_OBJECT(remminamain->priv->file_model));
+ g_object_unref(G_OBJECT(remminamain->priv->file_model_filter));
+ g_object_unref(remminamain->builder);
+ g_free(remminamain->priv->selected_filename);
+ g_free(remminamain->priv->selected_name);
+ g_free(remminamain->priv);
+ g_free(remminamain);
+
+ remminamain = NULL;
+ }
+}
+
+static void remmina_main_clear_selection_data(void)
+{
+ TRACE_CALL(__func__);
+ g_free(remminamain->priv->selected_filename);
+ g_free(remminamain->priv->selected_name);
+ remminamain->priv->selected_filename = NULL;
+ remminamain->priv->selected_name = NULL;
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ gtk_action_group_set_sensitive(remminamain->actiongroup_connection, FALSE);
+ G_GNUC_END_IGNORE_DEPRECATIONS
+}
+
+#ifdef SNAP_BUILD
+
+static void remmina_main_show_snap_welcome()
+{
+ GtkBuilder *dlgbuilder = NULL;
+ GtkWidget *dlg;
+ GtkWindow *parent;
+ int result;
+ static gboolean shown_once = FALSE;
+ gboolean need_snap_interface_connections = FALSE;
+ GtkWidget* dsa;
+ RemminaSecretPlugin *remmina_secret_plugin;
+
+ if (shown_once)
+ return;
+ else
+ shown_once = TRUE;
+
+ g_print("Remmina is compiled as a SNAP package.\n");
+ remmina_secret_plugin = remmina_plugin_manager_get_secret_plugin();
+ if (remmina_secret_plugin == NULL) {
+ g_print(" but we can't find the secret plugin inside the SNAP.\n");
+ need_snap_interface_connections = TRUE;
+ } else {
+ if (!remmina_secret_plugin->is_service_available()) {
+ g_print(" but we can't access a secret service. Secret service or SNAP interface connection is missing.\n");
+ need_snap_interface_connections = TRUE;
+ }
+ }
+
+ if (need_snap_interface_connections && !remmina_pref.prevent_snap_welcome_message) {
+ dlgbuilder = remmina_public_gtk_builder_new_from_file("remmina_snap_info_dialog.glade");
+ dsa = GTK_WIDGET(gtk_builder_get_object(dlgbuilder, "dontshowagain"));
+ if(dlgbuilder) {
+ parent = remmina_main_get_window();
+ dlg = GTK_WIDGET(gtk_builder_get_object(dlgbuilder, "snapwarndlg"));
+ if (parent)
+ gtk_window_set_transient_for(GTK_WINDOW(dlg), parent);
+ gtk_builder_connect_signals(dlgbuilder,NULL);
+ result = gtk_dialog_run(GTK_DIALOG(dlg));
+ if (result == 1) {
+ remmina_pref.prevent_snap_welcome_message = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dsa));
+ remmina_pref_save();
+ }
+ gtk_widget_destroy(dlg);
+ g_object_unref(dlgbuilder);
+ }
+ }
+
+}
+#endif
+
+
+static gboolean remmina_main_selection_func(GtkTreeSelection *selection, GtkTreeModel *model, GtkTreePath *path,
+ gboolean path_currently_selected, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ guint context_id;
+ GtkTreeIter iter;
+ gchar buf[1000];
+
+ if (path_currently_selected)
+ return TRUE;
+
+ if (!gtk_tree_model_get_iter(model, &iter, path))
+ return TRUE;
+
+ remmina_main_clear_selection_data();
+
+ gtk_tree_model_get(model, &iter, NAME_COLUMN, &remminamain->priv->selected_name, FILENAME_COLUMN,
+ &remminamain->priv->selected_filename, -1);
+
+ context_id = gtk_statusbar_get_context_id(remminamain->statusbar_main, "status");
+ gtk_statusbar_pop(remminamain->statusbar_main, context_id);
+ if (remminamain->priv->selected_filename) {
+ g_snprintf(buf, sizeof(buf), "%s (%s)", remminamain->priv->selected_name, remminamain->priv->selected_filename);
+ gtk_statusbar_push(remminamain->statusbar_main, context_id, buf);
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ gtk_action_group_set_sensitive(remminamain->actiongroup_connection, TRUE);
+ G_GNUC_END_IGNORE_DEPRECATIONS
+ }else {
+ gtk_statusbar_push(remminamain->statusbar_main, context_id, remminamain->priv->selected_name);
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ gtk_action_group_set_sensitive(remminamain->actiongroup_connection, FALSE);
+ G_GNUC_END_IGNORE_DEPRECATIONS
+ }
+ return TRUE;
+}
+
+static void remmina_main_load_file_list_callback(RemminaFile *remminafile, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ GtkTreeIter iter;
+ GtkListStore *store;
+ store = GTK_LIST_STORE(user_data);
+ gchar* datetime;
+
+ datetime = remmina_file_get_datetime(remminafile);
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter,
+ PROTOCOL_COLUMN, remmina_file_get_icon_name(remminafile),
+ NAME_COLUMN, remmina_file_get_string(remminafile, "name"),
+ GROUP_COLUMN, remmina_file_get_string(remminafile, "group"),
+ SERVER_COLUMN, remmina_file_get_string(remminafile, "server"),
+ DATE_COLUMN, datetime,
+ FILENAME_COLUMN, remmina_file_get_filename(remminafile),
+ -1);
+ g_free(datetime);
+}
+
+static gboolean remmina_main_load_file_tree_traverse(GNode *node, GtkTreeStore *store, GtkTreeIter *parent)
+{
+ TRACE_CALL(__func__);
+ GtkTreeIter *iter;
+ RemminaGroupData *data;
+ GNode *child;
+
+ iter = NULL;
+ if (node->data) {
+ data = (RemminaGroupData*)node->data;
+ iter = g_new0(GtkTreeIter, 1);
+ gtk_tree_store_append(store, iter, parent);
+ gtk_tree_store_set(store, iter,
+ PROTOCOL_COLUMN, "folder",
+ NAME_COLUMN, data->name,
+ GROUP_COLUMN, data->group,
+ DATE_COLUMN, data->datetime,
+ FILENAME_COLUMN, NULL,
+ -1);
+ }
+ for (child = g_node_first_child(node); child; child = g_node_next_sibling(child)) {
+ remmina_main_load_file_tree_traverse(child, store, iter);
+ }
+ g_free(iter);
+ return FALSE;
+}
+
+static void remmina_main_load_file_tree_group(GtkTreeStore *store)
+{
+ TRACE_CALL(__func__);
+ GNode *root;
+
+ root = remmina_file_manager_get_group_tree();
+ remmina_main_load_file_tree_traverse(root, store, NULL);
+ remmina_file_manager_free_group_tree(root);
+}
+
+static void remmina_main_expand_group_traverse(GtkTreeIter *iter)
+{
+ TRACE_CALL(__func__);
+ GtkTreeModel *tree;
+ gboolean ret;
+ gchar *group, *filename;
+ GtkTreeIter child;
+ GtkTreePath *path;
+
+ tree = remminamain->priv->file_model_sort;
+ ret = TRUE;
+ while (ret) {
+ gtk_tree_model_get(tree, iter, GROUP_COLUMN, &group, FILENAME_COLUMN, &filename, -1);
+ if (filename == NULL) {
+ if (remmina_string_array_find(remminamain->priv->expanded_group, group) >= 0) {
+ path = gtk_tree_model_get_path(tree, iter);
+ gtk_tree_view_expand_row(remminamain->tree_files_list, path, FALSE);
+ gtk_tree_path_free(path);
+ }
+ if (gtk_tree_model_iter_children(tree, &child, iter)) {
+ remmina_main_expand_group_traverse(&child);
+ }
+ }
+ g_free(group);
+ g_free(filename);
+
+ ret = gtk_tree_model_iter_next(tree, iter);
+ }
+}
+
+static void remmina_main_expand_group(void)
+{
+ TRACE_CALL(__func__);
+ GtkTreeIter iter;
+
+ if (gtk_tree_model_get_iter_first(remminamain->priv->file_model_sort, &iter)) {
+ remmina_main_expand_group_traverse(&iter);
+ }
+}
+
+static gboolean remmina_main_load_file_tree_find(GtkTreeModel *tree, GtkTreeIter *iter, const gchar *match_group)
+{
+ TRACE_CALL(__func__);
+ gboolean ret, match;
+ gchar *group, *filename;
+ GtkTreeIter child;
+
+ match = FALSE;
+ ret = TRUE;
+ while (ret) {
+ gtk_tree_model_get(tree, iter, GROUP_COLUMN, &group, FILENAME_COLUMN, &filename, -1);
+ match = (filename == NULL && g_strcmp0(group, match_group) == 0);
+ g_free(group);
+ g_free(filename);
+ if (match)
+ break;
+ if (gtk_tree_model_iter_children(tree, &child, iter)) {
+ match = remmina_main_load_file_tree_find(tree, &child, match_group);
+ if (match) {
+ memcpy(iter, &child, sizeof(GtkTreeIter));
+ break;
+ }
+ }
+ ret = gtk_tree_model_iter_next(tree, iter);
+ }
+ return match;
+}
+
+static void remmina_main_load_file_tree_callback(RemminaFile *remminafile, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ GtkTreeIter iter, child;
+ GtkTreeStore *store;
+ gboolean found;
+ gchar* datetime;
+
+ store = GTK_TREE_STORE(user_data);
+
+ found = FALSE;
+ if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter)) {
+ found = remmina_main_load_file_tree_find(GTK_TREE_MODEL(store), &iter,
+ remmina_file_get_string(remminafile, "group"));
+ }
+
+ datetime = remmina_file_get_datetime(remminafile);
+ gtk_tree_store_append(store, &child, (found ? &iter : NULL));
+ gtk_tree_store_set(store, &child,
+ PROTOCOL_COLUMN, remmina_file_get_icon_name(remminafile),
+ NAME_COLUMN, remmina_file_get_string(remminafile, "name"),
+ GROUP_COLUMN, remmina_file_get_string(remminafile, "group"),
+ SERVER_COLUMN, remmina_file_get_string(remminafile, "server"),
+ DATE_COLUMN, datetime,
+ FILENAME_COLUMN, remmina_file_get_filename(remminafile),
+ -1);
+ g_free(datetime);
+}
+
+static void remmina_main_file_model_on_sort(GtkTreeSortable *sortable, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ gint columnid;
+ GtkSortType order;
+
+ gtk_tree_sortable_get_sort_column_id(sortable, &columnid, &order);
+ remmina_pref.main_sort_column_id = columnid;
+ remmina_pref.main_sort_order = order;
+ remmina_pref_save();
+}
+
+static gboolean remmina_main_filter_visible_func(GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ gchar *text;
+ gchar *protocol, *name, *group, *server, *date, *s;
+ gboolean result = TRUE;
+
+ text = g_ascii_strdown(gtk_entry_get_text(remminamain->entry_quick_connect_server), -1);
+ if (text && text[0]) {
+ gtk_tree_model_get(model, iter,
+ PROTOCOL_COLUMN, &protocol,
+ NAME_COLUMN, &name,
+ GROUP_COLUMN, &group,
+ SERVER_COLUMN, &server,
+ DATE_COLUMN, &date,
+ -1);
+ if (g_strcmp0(protocol, "folder") != 0) {
+ s = g_ascii_strdown(name ? name : "", -1);
+ g_free(name);
+ name = s;
+ s = g_ascii_strdown(group ? group : "", -1);
+ g_free(group);
+ group = s;
+ s = g_ascii_strdown(server ? server : "", -1);
+ g_free(server);
+ server = s;
+ s = g_ascii_strdown(date ? date : "", -1);
+ g_free(date);
+ date = s;
+ result = (strstr(name, text) || strstr(server, text) || strstr(group, text) || strstr(date, text));
+ }
+ g_free(protocol);
+ g_free(name);
+ g_free(group);
+ g_free(server);
+ g_free(date);
+ }
+ g_free(text);
+ return result;
+}
+
+static void remmina_main_select_file(const gchar *filename)
+{
+ TRACE_CALL(__func__);
+ GtkTreeIter iter;
+ GtkTreePath *path;
+ gchar *item_filename;
+ gboolean cmp;
+
+ if (!gtk_tree_model_get_iter_first(remminamain->priv->file_model_sort, &iter))
+ return;
+
+ while (TRUE) {
+ gtk_tree_model_get(remminamain->priv->file_model_sort, &iter, FILENAME_COLUMN, &item_filename, -1);
+ cmp = g_strcmp0(item_filename, filename);
+ g_free(item_filename);
+ if (cmp == 0) {
+ gtk_tree_selection_select_iter(gtk_tree_view_get_selection(remminamain->tree_files_list),
+ &iter);
+ path = gtk_tree_model_get_path(remminamain->priv->file_model_sort, &iter);
+ gtk_tree_view_scroll_to_cell(remminamain->tree_files_list, path, NULL, TRUE, 0.5, 0.0);
+ gtk_tree_path_free(path);
+ return;
+ }
+ if (!gtk_tree_model_iter_next(remminamain->priv->file_model_sort, &iter))
+ return;
+ }
+}
+
+static void remmina_main_load_files()
+{
+ TRACE_CALL(__func__);
+ gint items_count;
+ gchar buf[200];
+ guint context_id;
+ gint view_file_mode;
+ char *save_selected_filename;
+ GtkTreeModel *newmodel;
+
+ save_selected_filename = g_strdup(remminamain->priv->selected_filename);
+ remmina_main_save_expanded_group();
+
+ view_file_mode = remmina_pref.view_file_mode;
+ if (remminamain->priv->override_view_file_mode_to_list)
+ view_file_mode = REMMINA_VIEW_FILE_LIST;
+
+ switch (remmina_pref.view_file_mode) {
+ case REMMINA_VIEW_FILE_TREE:
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(remminamain->menuitem_view_mode_tree), TRUE);
+ break;
+ case REMMINA_VIEW_FILE_LIST:
+ default:
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(remminamain->menuitem_view_mode_list), TRUE);
+ break;
+ }
+
+ switch (view_file_mode) {
+ case REMMINA_VIEW_FILE_TREE:
+ /* Create new GtkTreeStore model */
+ newmodel = GTK_TREE_MODEL(gtk_tree_store_new(6, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING));
+ /* Hide the Group column in the tree view mode */
+ gtk_tree_view_column_set_visible(remminamain->column_files_list_group, FALSE);
+ /* Load groups first */
+ remmina_main_load_file_tree_group(GTK_TREE_STORE(newmodel));
+ /* Load files list */
+ items_count = remmina_file_manager_iterate((GFunc)remmina_main_load_file_tree_callback, (gpointer)newmodel);
+ break;
+
+ case REMMINA_VIEW_FILE_LIST:
+ default:
+ /* Create new GtkListStore model */
+ newmodel = GTK_TREE_MODEL(gtk_list_store_new(6, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING));
+ /* Show the Group column in the list view mode */
+ gtk_tree_view_column_set_visible(remminamain->column_files_list_group, TRUE);
+ /* Load files list */
+ items_count = remmina_file_manager_iterate((GFunc)remmina_main_load_file_list_callback, (gpointer)newmodel);
+ break;
+ }
+
+ /* Unset old model */
+ gtk_tree_view_set_model(remminamain->tree_files_list, NULL);
+
+ /* Destroy the old model and save the new one */
+ remminamain->priv->file_model = newmodel;
+
+ /* Create a sorted filtered model based on newmodel and apply it to the TreeView */
+ remminamain->priv->file_model_filter = gtk_tree_model_filter_new(remminamain->priv->file_model, NULL);
+ gtk_tree_model_filter_set_visible_func(GTK_TREE_MODEL_FILTER(remminamain->priv->file_model_filter),
+ (GtkTreeModelFilterVisibleFunc)remmina_main_filter_visible_func, NULL, NULL);
+ remminamain->priv->file_model_sort = gtk_tree_model_sort_new_with_model(remminamain->priv->file_model_filter);
+ gtk_tree_sortable_set_sort_column_id( GTK_TREE_SORTABLE(remminamain->priv->file_model_sort),
+ remmina_pref.main_sort_column_id,
+ remmina_pref.main_sort_order);
+ gtk_tree_view_set_model(remminamain->tree_files_list, remminamain->priv->file_model_sort);
+ g_signal_connect(G_OBJECT(remminamain->priv->file_model_sort), "sort-column-changed",
+ G_CALLBACK(remmina_main_file_model_on_sort), NULL);
+ remmina_main_expand_group();
+ /* Select the file previously selected */
+ if (save_selected_filename) {
+ remmina_main_select_file(save_selected_filename);
+ g_free(save_selected_filename);
+ }
+ /* Show in the status bar the total number of connections found */
+ g_snprintf(buf, sizeof(buf), ngettext("Total %i item.", "Total %i items.", items_count), items_count);
+ context_id = gtk_statusbar_get_context_id(remminamain->statusbar_main, "status");
+ gtk_statusbar_pop(remminamain->statusbar_main, context_id);
+ gtk_statusbar_push(remminamain->statusbar_main, context_id, buf);
+}
+
+void remmina_main_load_files_cb()
+{
+ TRACE_CALL(__func__);
+ remmina_main_load_files();
+}
+
+void remmina_main_on_action_connection_connect(GtkAction *action, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+
+ RemminaFile *remminafile;
+
+ if (!remminamain->priv->selected_filename)
+ return;
+
+ remminafile = remmina_file_load(remminamain->priv->selected_filename);
+
+ if (remminafile == NULL)
+ return;
+
+ remmina_file_touch(remminafile);
+ remmina_connection_window_open_from_filename(remminamain->priv->selected_filename);
+
+ remmina_file_free(remminafile);
+}
+
+void remmina_main_on_action_connection_external_tools(GtkAction *action, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ if (!remminamain->priv->selected_filename)
+ return;
+
+ remmina_external_tools_from_filename(remminamain, remminamain->priv->selected_filename);
+}
+
+static void remmina_main_file_editor_destroy(GtkWidget *widget, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ remmina_main_load_files();
+}
+
+void remmina_main_on_action_application_mpchange(GtkAction *action, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ RemminaFile *remminafile;
+
+ const gchar *username;
+ const gchar *domain;
+ const gchar *group;
+
+ username = domain = group = "";
+
+ remminafile = NULL;
+ if (remminamain->priv->selected_filename) {
+ remminafile = remmina_file_load(remminamain->priv->selected_filename);
+ if (remminafile != NULL) {
+ username = remmina_file_get_string(remminafile, "username");
+ domain = remmina_file_get_string(remminafile, "domain");
+ group = remmina_file_get_string(remminafile, "group");
+ }
+ }
+
+ remmina_mpchange_schedule(TRUE, group, domain, username, "");
+
+ if (remminafile != NULL)
+ remmina_file_free(remminafile);
+
+}
+
+void remmina_main_on_action_connections_new(GtkAction *action, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *widget;
+
+ widget = remmina_file_editor_new();
+ g_signal_connect(G_OBJECT(widget), "destroy", G_CALLBACK(remmina_main_file_editor_destroy), remminamain);
+ gtk_window_set_transient_for(GTK_WINDOW(widget), remminamain->window);
+ gtk_widget_show(widget);
+ remmina_main_load_files();
+}
+
+void remmina_main_on_action_connection_copy(GtkAction *action, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *widget;
+
+ if (!remminamain->priv->selected_filename)
+ return;
+
+ widget = remmina_file_editor_new_copy(remminamain->priv->selected_filename);
+ if (widget) {
+ g_signal_connect(G_OBJECT(widget), "destroy", G_CALLBACK(remmina_main_file_editor_destroy), remminamain);
+ gtk_window_set_transient_for(GTK_WINDOW(widget), remminamain->window);
+ gtk_widget_show(widget);
+ }
+ /* Select the file previously selected */
+ if (remminamain->priv->selected_filename) {
+ remmina_main_select_file(remminamain->priv->selected_filename);
+ }
+}
+
+void remmina_main_on_action_connection_edit(GtkAction *action, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *widget;
+
+ if (!remminamain->priv->selected_filename)
+ return;
+
+ widget = remmina_file_editor_new_from_filename(remminamain->priv->selected_filename);
+ if (widget) {
+ gtk_window_set_transient_for(GTK_WINDOW(widget), remminamain->window);
+ gtk_widget_show(widget);
+ }
+ /* Select the file previously selected */
+ if (remminamain->priv->selected_filename) {
+ remmina_main_select_file(remminamain->priv->selected_filename);
+ }
+}
+
+void remmina_main_on_action_connection_delete(GtkAction *action, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *dialog;
+ gchar *delfilename;
+
+ if (!remminamain->priv->selected_filename)
+ return;
+
+ dialog = gtk_message_dialog_new(remminamain->window, GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
+ _("Are you sure to delete '%s'"), remminamain->priv->selected_name);
+ if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_YES) {
+ delfilename = g_strdup(remminamain->priv->selected_filename);
+ remmina_file_delete(delfilename);
+ g_free(delfilename);
+ remmina_icon_populate_menu();
+ remmina_main_load_files();
+ }
+ gtk_widget_destroy(dialog);
+ remmina_main_clear_selection_data();
+}
+
+void remmina_main_on_action_application_preferences(GtkAction *action, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ GtkDialog *dialog = remmina_pref_dialog_new(0, remminamain->window);
+ gtk_dialog_run(dialog);
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+}
+
+void remmina_main_on_action_application_quit(GtkAction *action, gpointer user_data)
+{
+ // Called by quit signal in remmina_main.glade
+ TRACE_CALL(__func__);
+ remmina_application_condexit(REMMINA_CONDEXIT_ONQUIT);
+}
+
+void remmina_main_on_action_view_statusbar(GtkToggleAction *action, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ gboolean toggled;
+
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ toggled = gtk_toggle_action_get_active(action);
+ G_GNUC_END_IGNORE_DEPRECATIONS
+ if (toggled) {
+ gtk_widget_show(GTK_WIDGET(remminamain->statusbar_main));
+ }else {
+ gtk_widget_hide(GTK_WIDGET(remminamain->statusbar_main));
+ }
+ if (remminamain->priv->initialized) {
+ remmina_pref.hide_statusbar = !toggled;
+ remmina_pref_save();
+ }
+}
+
+void remmina_main_on_action_view_file_mode(GtkRadioAction *action, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ gint v;
+
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ v = gtk_radio_action_get_current_value(action);
+ G_GNUC_END_IGNORE_DEPRECATIONS
+
+ if (v == REMMINA_VIEW_FILE_TREE) {
+ gtk_entry_set_text(remminamain->entry_quick_connect_server, "");
+ }
+
+ if (remmina_pref.view_file_mode != v) {
+ remmina_pref.view_file_mode = v;
+ gtk_entry_set_text(remminamain->entry_quick_connect_server, "");
+ remmina_pref_save();
+ remmina_main_load_files();
+ }
+
+}
+
+void remmina_main_on_date_column_sort_clicked()
+{
+ if (remmina_pref.view_file_mode != REMMINA_VIEW_FILE_LIST) {
+ remmina_pref.view_file_mode = REMMINA_VIEW_FILE_LIST;
+ gtk_entry_set_text(remminamain->entry_quick_connect_server, "");
+ remmina_pref_save();
+ remmina_main_load_files();
+ }
+}
+
+static void remmina_main_import_file_list(GSList *files)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *dlg;
+ GSList *element;
+ gchar *path;
+ RemminaFilePlugin *plugin;
+ GString *err;
+ RemminaFile *remminafile = NULL;
+ gboolean imported;
+
+ err = g_string_new(NULL);
+ imported = FALSE;
+ for (element = files; element; element = element->next) {
+ path = (gchar*)element->data;
+ plugin = remmina_plugin_manager_get_import_file_handler(path);
+ if (plugin && (remminafile = plugin->import_func(path)) != NULL && remmina_file_get_string(remminafile, "name")) {
+ remmina_file_generate_filename(remminafile);
+ remmina_file_save(remminafile);
+ imported = TRUE;
+ }else {
+ g_string_append(err, path);
+ g_string_append_c(err, '\n');
+ }
+ if (remminafile) {
+ remmina_file_free(remminafile);
+ remminafile = NULL;
+ }
+ g_free(path);
+ }
+ g_slist_free(files);
+ if (err->len > 0) {
+ dlg = gtk_message_dialog_new(remminamain->window, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+ _("Unable to import:\n%s"), err->str);
+ g_signal_connect(G_OBJECT(dlg), "response", G_CALLBACK(gtk_widget_destroy), NULL);
+ gtk_widget_show(dlg);
+ }
+ g_string_free(err, TRUE);
+ if (imported) {
+ remmina_main_load_files();
+ }
+}
+
+static void remmina_main_action_tools_import_on_response(GtkDialog *dialog, gint response_id, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ GSList *files;
+
+ if (response_id == GTK_RESPONSE_ACCEPT) {
+ files = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
+ remmina_main_import_file_list(files);
+ }
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+}
+
+void remmina_main_on_action_tools_import(GtkAction *action, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *dialog;
+
+ dialog = gtk_file_chooser_dialog_new(_("Import"), remminamain->window, GTK_FILE_CHOOSER_ACTION_OPEN, "Import",
+ GTK_RESPONSE_ACCEPT, NULL);
+ gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
+ g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(remmina_main_action_tools_import_on_response), NULL);
+ gtk_widget_show(dialog);
+}
+
+void remmina_main_on_action_tools_export(GtkAction *action, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ RemminaFilePlugin *plugin;
+ RemminaFile *remminafile;
+ GtkWidget *dialog;
+
+ if (!remminamain->priv->selected_filename)
+ return;
+
+ remminafile = remmina_file_load(remminamain->priv->selected_filename);
+ if (remminafile == NULL)
+ return;
+ plugin = remmina_plugin_manager_get_export_file_handler(remminafile);
+ if (plugin) {
+ dialog = gtk_file_chooser_dialog_new(plugin->export_hints, remminamain->window,
+ GTK_FILE_CHOOSER_ACTION_SAVE, _("_Save"), GTK_RESPONSE_ACCEPT, NULL);
+ if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
+ plugin->export_func(remminafile, gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)));
+ }
+ gtk_widget_destroy(dialog);
+ }else {
+ dialog = gtk_message_dialog_new(remminamain->window, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+ _("This protocol does not support exporting."));
+ g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(gtk_widget_destroy), NULL);
+ gtk_widget_show(dialog);
+ }
+ remmina_file_free(remminafile);
+}
+
+void remmina_main_on_action_application_plugins(GtkAction *action, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ remmina_plugin_manager_show(remminamain->window);
+}
+
+void remmina_main_on_action_help_homepage(GtkAction *action, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ g_app_info_launch_default_for_uri("http://www.remmina.org", NULL, NULL);
+}
+
+void remmina_main_on_action_help_wiki(GtkAction *action, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ g_app_info_launch_default_for_uri("https://github.com/FreeRDP/Remmina/wiki", NULL, NULL);
+}
+
+void remmina_main_on_action_help_gplus(GtkAction *action, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ g_app_info_launch_default_for_uri("https://plus.google.com/communities/106276095923371962010", NULL, NULL);
+}
+
+void remmina_main_on_action_help_donations(GtkAction *action, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ g_app_info_launch_default_for_uri("http://www.remmina.org/wp/donations", NULL, NULL);
+}
+
+void remmina_main_on_action_help_debug(GtkAction *action, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ remmina_log_start();
+}
+
+void remmina_main_on_action_application_about(GtkAction *action, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ remmina_about_open(remminamain->window);
+};
+
+static gboolean remmina_main_quickconnect(void)
+{
+ TRACE_CALL(__func__);
+ RemminaFile* remminafile;
+ gchar* server;
+
+ remminafile = remmina_file_new();
+ server = strdup(gtk_entry_get_text(remminamain->entry_quick_connect_server));
+
+ remmina_file_set_string(remminafile, "sound", "off");
+ remmina_file_set_string(remminafile, "server", server);
+ remmina_file_set_string(remminafile, "name", server);
+ remmina_file_set_string(remminafile, "protocol",
+ gtk_combo_box_text_get_active_text(remminamain->combo_quick_connect_protocol));
+ g_free(server);
+
+ remmina_connection_window_open_from_file(remminafile);
+
+ return FALSE;
+}
+
+gboolean remmina_main_quickconnect_on_click(GtkWidget *widget, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ return remmina_main_quickconnect();
+}
+
+/* Select all the text inside the quick search box if there is anything */
+void remmina_main_quick_search_enter(GtkWidget *widget, gpointer user_data)
+{
+ if (gtk_entry_get_text(remminamain->entry_quick_connect_server))
+ gtk_editable_select_region(GTK_EDITABLE(remminamain->entry_quick_connect_server), 0, -1);
+}
+
+/* Handle double click on a row in the connections list */
+void remmina_main_file_list_on_row_activated(GtkTreeView *tree, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ /* If a connection was selected then execute the default action */
+ if (remminamain->priv->selected_filename) {
+ switch (remmina_pref.default_action) {
+ case REMMINA_ACTION_EDIT:
+ remmina_main_on_action_connection_edit(NULL, NULL);
+ break;
+ case REMMINA_ACTION_CONNECT:
+ default:
+ remmina_main_on_action_connection_connect(NULL, NULL);
+ break;
+ }
+ }
+}
+
+/* Show the popup menu by the right button mouse click */
+gboolean remmina_main_file_list_on_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ if (event->button == MOUSE_BUTTON_RIGHT) {
+#if GTK_CHECK_VERSION(3, 22, 0)
+ gtk_menu_popup_at_pointer(GTK_MENU(remminamain->menu_popup), (GdkEvent*)event);
+#else
+ gtk_menu_popup(remminamain->menu_popup, NULL, NULL, NULL, NULL, event->button, event->time);
+#endif
+
+
+ }
+ return FALSE;
+}
+
+/* Show the popup menu by the menu key */
+gboolean remmina_main_file_list_on_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ if (event->keyval == GDK_KEY_Menu) {
+#if GTK_CHECK_VERSION(3, 22, 0)
+ gtk_menu_popup_at_widget(GTK_MENU(remminamain->menu_popup), widget,
+ GDK_GRAVITY_CENTER, GDK_GRAVITY_CENTER,
+ (GdkEvent*)event);
+#else
+ gtk_menu_popup(remminamain->menu_popup, NULL, NULL, NULL, NULL, 0, event->time);
+#endif
+ }
+ return FALSE;
+}
+
+void remmina_main_quick_search_on_icon_press(GtkEntry *entry, GtkEntryIconPosition icon_pos, GdkEvent *event, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ if (icon_pos == GTK_ENTRY_ICON_SECONDARY) {
+ gtk_entry_set_text(entry, "");
+ }
+}
+
+void remmina_main_quick_search_on_changed(GtkEditable *editable, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ /* If a search text was input then temporary set the file mode to list */
+ if (gtk_entry_get_text_length(remminamain->entry_quick_connect_server)) {
+ if (GTK_IS_TREE_STORE(remminamain->priv->file_model)) {
+ /* File view mode changed, put it to override and reload list */
+ remminamain->priv->override_view_file_mode_to_list = TRUE;
+ remmina_main_load_files();
+ }
+ } else {
+ if (remminamain->priv->override_view_file_mode_to_list) {
+ /* File view mode changed, put it to default (disable override) and reload list */
+ remminamain->priv->override_view_file_mode_to_list = FALSE;
+ remmina_main_load_files();
+ }
+ }
+ gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER(remminamain->priv->file_model_filter));
+}
+
+void remmina_main_on_drag_data_received(GtkWidget *widget, GdkDragContext *drag_context, gint x, gint y,
+ GtkSelectionData *data, guint info, guint time, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ gchar **uris;
+ GSList *files = NULL;
+ gint i;
+
+ uris = g_uri_list_extract_uris((const gchar*)gtk_selection_data_get_data(data));
+ for (i = 0; uris[i]; i++) {
+ if (strncmp(uris[i], "file://", 7) != 0)
+ continue;
+ files = g_slist_append(files, g_strdup(uris[i] + 7));
+ }
+ g_strfreev(uris);
+ remmina_main_import_file_list(files);
+}
+
+/* Add a new menuitem to the Tools menu */
+static gboolean remmina_main_add_tool_plugin(gchar *name, RemminaPlugin *plugin, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ RemminaToolPlugin *tool_plugin = (RemminaToolPlugin*)plugin;
+ GtkWidget *menuitem = gtk_menu_item_new_with_label(plugin->description);
+
+ gtk_widget_show(menuitem);
+ gtk_menu_shell_append(GTK_MENU_SHELL(remminamain->menu_popup_full), menuitem);
+ g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(tool_plugin->exec_func), NULL);
+ return FALSE;
+}
+
+gboolean remmina_main_on_window_state_event(GtkWidget *widget, GdkEventWindowState *event, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ return FALSE;
+}
+
+/* Remmina main window initialization */
+static void remmina_main_init(void)
+{
+ TRACE_CALL(__func__);
+ int i;
+ char *name;
+
+ remminamain->priv->expanded_group = remmina_string_array_new_from_string(remmina_pref.expanded_group);
+ gtk_window_set_title(remminamain->window, _("Remmina Remote Desktop Client"));
+ gtk_window_set_default_size(remminamain->window, remmina_pref.main_width, remmina_pref.main_height);
+ if (remmina_pref.main_maximize) {
+ gtk_window_maximize(remminamain->window);
+ }
+
+ /* Add a GtkMenuItem to the Tools menu for each plugin of type REMMINA_PLUGIN_TYPE_TOOL */
+ remmina_plugin_manager_for_each_plugin(REMMINA_PLUGIN_TYPE_TOOL, remmina_main_add_tool_plugin, remminamain);
+
+ /* Add available quick connect protocols to remminamain->combo_quick_connect_protocol */
+ for (i = 0; i < sizeof(quick_connect_plugin_list) / sizeof(quick_connect_plugin_list[0]); i++) {
+ name = quick_connect_plugin_list[i];
+ if (remmina_plugin_manager_get_plugin(REMMINA_PLUGIN_TYPE_PROTOCOL, name))
+ gtk_combo_box_text_append(remminamain->combo_quick_connect_protocol, name, name);
+ }
+ gtk_combo_box_set_active(GTK_COMBO_BOX(remminamain->combo_quick_connect_protocol), 0);
+
+ /* Connect the group accelerators to the GtkWindow */
+ gtk_window_add_accel_group(remminamain->window, remminamain->accelgroup_shortcuts);
+ /* Set the Quick Connection */
+ gtk_entry_set_activates_default(remminamain->entry_quick_connect_server, TRUE);
+ /* Set the TreeView for the files list */
+ gtk_tree_selection_set_select_function(
+ gtk_tree_view_get_selection(remminamain->tree_files_list),
+ remmina_main_selection_func, NULL, NULL);
+ /** @todo Set entry_quick_connect_server as default search entry. Weirdly. This does not work yet. */
+ gtk_tree_view_set_search_entry(remminamain->tree_files_list, GTK_ENTRY(remminamain->entry_quick_connect_server));
+ /* Load the files list */
+ remmina_main_load_files();
+ /* Load the preferences */
+ if (remmina_pref.hide_statusbar) {
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ gtk_toggle_action_set_active(remminamain->action_view_statusbar, FALSE);
+ G_GNUC_END_IGNORE_DEPRECATIONS
+ }
+ if (remmina_pref.view_file_mode) {
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ gtk_toggle_action_set_active(remminamain->action_view_mode_tree, TRUE);
+ G_GNUC_END_IGNORE_DEPRECATIONS
+ }
+
+ /* Drag-n-drop support */
+ gtk_drag_dest_set(GTK_WIDGET(remminamain->window), GTK_DEST_DEFAULT_ALL, remmina_drop_types, 1, GDK_ACTION_COPY);
+
+ /* Finish initialization */
+ remminamain->priv->initialized = TRUE;
+
+ /* Register the window in remmina_widget_pool with GType=GTK_WINDOW and TAG=remmina-main-window */
+ g_object_set_data(G_OBJECT(remminamain->window), "tag", "remmina-main-window");
+ remmina_widget_pool_register(GTK_WIDGET(remminamain->window));
+}
+
+/* Signal handler for "show" on remminamain->window */
+void remmina_main_on_show(GtkWidget *w, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ if (!remmina_pref.periodic_usage_stats_permission_asked) {
+ gtk_widget_set_visible(GTK_WIDGET(remminamain->box_ustat), TRUE);
+ }
+
+#ifdef SNAP_BUILD
+ remmina_main_show_snap_welcome();
+#endif
+
+}
+
+void remmina_main_on_click_ustat_yes(GtkWidget *w, gpointer user_data)
+{
+ remmina_pref.periodic_usage_stats_permission_asked = TRUE;
+ remmina_pref.periodic_usage_stats_permitted = TRUE;
+ remmina_pref_save();
+ gtk_widget_set_visible(GTK_WIDGET(remminamain->box_ustat), FALSE);
+ remmina_stats_sender_schedule();
+}
+
+void remmina_main_on_click_ustat_no(GtkWidget *w, gpointer user_data)
+{
+ remmina_pref.periodic_usage_stats_permission_asked = TRUE;
+ remmina_pref.periodic_usage_stats_permitted = FALSE;
+ remmina_pref_save();
+ gtk_widget_set_visible(GTK_WIDGET(remminamain->box_ustat), FALSE);
+}
+
+
+/* RemminaMain instance */
+GtkWidget* remmina_main_new(void)
+{
+ TRACE_CALL(__func__);
+ remminamain = g_new0(RemminaMain, 1);
+ remminamain->priv = g_new0(RemminaMainPriv, 1);
+ /* Assign UI widgets to the private members */
+ remminamain->builder = remmina_public_gtk_builder_new_from_file("remmina_main.glade");
+ remminamain->window = GTK_WINDOW(gtk_builder_get_object(remminamain->builder, "RemminaMain"));
+ /* Menu widgets */
+ remminamain->menu_popup = GTK_MENU(GET_OBJECT("menu_popup"));
+ remminamain->menu_popup_full = GTK_MENU(GET_OBJECT("menu_popup_full"));
+ /* View mode radios */
+ remminamain->menuitem_view_mode_list = GTK_RADIO_MENU_ITEM(GET_OBJECT("menuitem_view_mode_list"));
+ remminamain->menuitem_view_mode_tree = GTK_RADIO_MENU_ITEM(GET_OBJECT("menuitem_view_mode_tree"));
+ /* Quick connect objects */
+ remminamain->box_quick_connect = GTK_BOX(GET_OBJECT("box_quick_connect"));
+ remminamain->combo_quick_connect_protocol = GTK_COMBO_BOX_TEXT(GET_OBJECT("combo_quick_connect_protocol"));
+ remminamain->entry_quick_connect_server = GTK_ENTRY(GET_OBJECT("entry_quick_connect_server"));
+ /* Other widgets */
+ remminamain->tree_files_list = GTK_TREE_VIEW(GET_OBJECT("tree_files_list"));
+ remminamain->column_files_list_group = GTK_TREE_VIEW_COLUMN(GET_OBJECT("column_files_list_group"));
+ remminamain->statusbar_main = GTK_STATUSBAR(GET_OBJECT("statusbar_main"));
+ remminamain->box_ustat = GTK_BOX(GET_OBJECT("box_ustat"));
+ /* Non widget objects */
+ remminamain->accelgroup_shortcuts = GTK_ACCEL_GROUP(GET_OBJECT("accelgroup_shortcuts"));
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ remminamain->actiongroup_connection = GTK_ACTION_GROUP(GET_OBJECT("actiongroup_connection"));
+ /* Actions from the application ActionGroup */
+ remminamain->action_application_about = GTK_ACTION(GET_OBJECT("action_application_about"));
+ remminamain->action_application_plugins = GTK_ACTION(GET_OBJECT("action_application_plugins"));
+ remminamain->action_application_preferences = GTK_ACTION(GET_OBJECT("action_application_preferences"));
+ remminamain->action_application_quit = GTK_ACTION(GET_OBJECT("action_application_quit"));
+ /* Actions from the connections ActionGroup */
+ remminamain->action_connections_new = GTK_ACTION(GET_OBJECT("action_connections_new"));
+ /* Actions from the connection ActionGroup */
+ remminamain->action_connection_connect = GTK_ACTION(GET_OBJECT("action_connection_connect"));
+ remminamain->action_connection_edit = GTK_ACTION(GET_OBJECT("action_connection_edit"));
+ remminamain->action_connection_copy = GTK_ACTION(GET_OBJECT("action_connection_copy"));
+ remminamain->action_connection_delete = GTK_ACTION(GET_OBJECT("action_connection_delete"));
+ remminamain->action_connection_external_tools = GTK_ACTION(GET_OBJECT("action_connection_external_tools"));
+ /* Actions from the view ActionGroup */
+ remminamain->action_view_statusbar = GTK_TOGGLE_ACTION(GET_OBJECT("action_view_statusbar"));
+ remminamain->action_view_mode_list = GTK_TOGGLE_ACTION(GET_OBJECT("action_view_mode_list"));
+ remminamain->action_view_mode_tree = GTK_TOGGLE_ACTION(GET_OBJECT("action_view_mode_tree"));
+ /* Actions from the tools ActionGroup */
+ remminamain->action_tools_import = GTK_ACTION(GET_OBJECT("action_tools_import"));
+ remminamain->action_tools_export = GTK_ACTION(GET_OBJECT("action_tools_export"));
+ /* Actions from the help ActionGroup */
+ remminamain->action_help_homepage = GTK_ACTION(GET_OBJECT("action_help_homepage"));
+ remminamain->action_help_wiki = GTK_ACTION(GET_OBJECT("action_help_wiki"));
+ remminamain->action_help_debug = GTK_ACTION(GET_OBJECT("action_help_debug"));
+ G_GNUC_END_IGNORE_DEPRECATIONS
+
+ /* Connect signals */
+ gtk_builder_connect_signals(remminamain->builder, NULL);
+ /* Initialize the window and load the preferences */
+ remmina_main_init();
+ return GTK_WIDGET(remminamain->window);
+}
+
+GtkWindow* remmina_main_get_window()
+{
+ if (!remminamain)
+ return NULL;
+ if (!remminamain->priv)
+ return NULL;
+ if (!remminamain->priv->initialized)
+ return NULL;
+ return remminamain->window;
+}
+
+void remmina_main_update_file_datetime(RemminaFile *file)
+{
+ if (!remminamain)
+ return;
+ remmina_main_load_files();
+}
+
+void remmina_main_show_warning_dialog(const gchar* message)
+{
+ GtkWidget* dialog;
+
+ if (remminamain->window) {
+ dialog = gtk_message_dialog_new(remminamain->window, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE,
+ message, g_get_application_name());
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ }
+}
diff --git a/src/remmina_main.h b/src/remmina_main.h
new file mode 100644
index 000000000..252ba6348
--- /dev/null
+++ b/src/remmina_main.h
@@ -0,0 +1,122 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2011 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 "remmina_string_array.h"
+
+#pragma once
+
+#include "remmina_file.h"
+
+typedef struct _RemminaMainPriv RemminaMainPriv;
+
+typedef struct _RemminaMain {
+ GtkBuilder *builder;
+ GtkWindow *window;
+ /* Menu widgets */
+ GtkMenu *menu_popup;
+ GtkMenu *menu_popup_full;
+ GtkRadioMenuItem *menuitem_view_mode_list;
+ GtkRadioMenuItem *menuitem_view_mode_tree;
+ /* Quick connect objects */
+ GtkBox *box_quick_connect;
+ GtkComboBoxText *combo_quick_connect_protocol;
+ GtkEntry *entry_quick_connect_server;
+ GtkButton *button_quick_connect;
+ /* Other widgets */
+ GtkTreeView *tree_files_list;
+ GtkTreeViewColumn *column_files_list_group;
+ GtkStatusbar *statusbar_main;
+ GtkBox *box_ustat;
+ /* Non widget objects */
+ GtkAccelGroup *accelgroup_shortcuts;
+ GtkActionGroup *actiongroup_connection;
+ /* Actions from the application ActionGroup */
+ GtkAction *action_application_about;
+ GtkAction *action_application_plugins;
+ GtkAction *action_application_preferences;
+ GtkAction *action_application_quit;
+ /* Actions from the connections ActionGroup */
+ GtkAction *action_connections_new;
+ /* Actions from the connection ActionGroup */
+ GtkAction *action_connection_connect;
+ GtkAction *action_connection_edit;
+ GtkAction *action_connection_copy;
+ GtkAction *action_connection_delete;
+ GtkAction *action_connection_external_tools;
+ /* Actions from the view ActionGroup */
+ GtkToggleAction *action_view_statusbar;
+ GtkToggleAction *action_view_quick_connect;
+ GtkToggleAction *action_view_mode_list;
+ GtkToggleAction *action_view_mode_tree;
+ /* Actions from the tools ActionGroup */
+ GtkAction *action_tools_import;
+ GtkAction *action_tools_export;
+ /* Actions from the help ActionGroup */
+ GtkAction *action_help_homepage;
+ GtkAction *action_help_wiki;
+ GtkAction *action_help_debug;
+ RemminaMainPriv *priv;
+} RemminaMain;
+
+struct _RemminaMainPriv {
+ GtkTreeModel *file_model;
+ GtkTreeModel *file_model_filter;
+ GtkTreeModel *file_model_sort;
+
+ gboolean initialized;
+
+ gchar *selected_filename;
+ gchar *selected_name;
+ gboolean override_view_file_mode_to_list;
+ RemminaStringArray *expanded_group;
+};
+
+G_BEGIN_DECLS
+
+/* Create the main Remmina window */
+GtkWidget* remmina_main_new(void);
+/* Get the current main window or NULL if not initialized */
+GtkWindow* remmina_main_get_window(void);
+
+void remmina_main_update_file_datetime(RemminaFile *file);
+
+void remmina_main_destroy(void);
+void remmina_main_save_before_destroy(void);
+
+void remmina_main_show_warning_dialog(const gchar* message);
+
+G_END_DECLS
+
diff --git a/src/remmina_marshals.c b/src/remmina_marshals.c
new file mode 100644
index 000000000..1f8223f5e
--- /dev/null
+++ b/src/remmina_marshals.c
@@ -0,0 +1,161 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2011 Vic Lee
+ * Copyright (C) 2011 Marc-Andre Moreau
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 <glib-object.h>
+#include "remmina/remmina_trace_calls.h"
+
+#ifdef G_ENABLE_DEBUG
+#define g_marshal_value_peek_boolean(v) g_value_get_boolean(v)
+#define g_marshal_value_peek_char(v) g_value_get_char(v)
+#define g_marshal_value_peek_uchar(v) g_value_get_uchar(v)
+#define g_marshal_value_peek_int(v) g_value_get_int(v)
+#define g_marshal_value_peek_uint(v) g_value_get_uint(v)
+#define g_marshal_value_peek_long(v) g_value_get_long(v)
+#define g_marshal_value_peek_ulong(v) g_value_get_ulong(v)
+#define g_marshal_value_peek_int64(v) g_value_get_int64(v)
+#define g_marshal_value_peek_uint64(v) g_value_get_uint64(v)
+#define g_marshal_value_peek_enum(v) g_value_get_enum(v)
+#define g_marshal_value_peek_flags(v) g_value_get_flags(v)
+#define g_marshal_value_peek_float(v) g_value_get_float(v)
+#define g_marshal_value_peek_double(v) g_value_get_double(v)
+#define g_marshal_value_peek_string(v) (char*)g_value_get_string(v)
+#define g_marshal_value_peek_param(v) g_value_get_param(v)
+#define g_marshal_value_peek_boxed(v) g_value_get_boxed(v)
+#define g_marshal_value_peek_pointer(v) g_value_get_pointer(v)
+#define g_marshal_value_peek_object(v) g_value_get_object(v)
+#else /* !G_ENABLE_DEBUG */
+/* WARNING: This code accesses GValues directly, which is UNSUPPORTED API.
+ * Do not access GValues directly in your code. Instead, use the
+ * g_value_get_*() functions
+ */
+#define g_marshal_value_peek_boolean(v) (v)->data[0].v_int
+#define g_marshal_value_peek_char(v) (v)->data[0].v_int
+#define g_marshal_value_peek_uchar(v) (v)->data[0].v_uint
+#define g_marshal_value_peek_int(v) (v)->data[0].v_int
+#define g_marshal_value_peek_uint(v) (v)->data[0].v_uint
+#define g_marshal_value_peek_long(v) (v)->data[0].v_long
+#define g_marshal_value_peek_ulong(v) (v)->data[0].v_ulong
+#define g_marshal_value_peek_int64(v) (v)->data[0].v_int64
+#define g_marshal_value_peek_uint64(v) (v)->data[0].v_uint64
+#define g_marshal_value_peek_enum(v) (v)->data[0].v_long
+#define g_marshal_value_peek_flags(v) (v)->data[0].v_ulong
+#define g_marshal_value_peek_float(v) (v)->data[0].v_float
+#define g_marshal_value_peek_double(v) (v)->data[0].v_double
+#define g_marshal_value_peek_string(v) (v)->data[0].v_pointer
+#define g_marshal_value_peek_param(v) (v)->data[0].v_pointer
+#define g_marshal_value_peek_boxed(v) (v)->data[0].v_pointer
+#define g_marshal_value_peek_pointer(v) (v)->data[0].v_pointer
+#define g_marshal_value_peek_object(v) (v)->data[0].v_pointer
+#endif /* !G_ENABLE_DEBUG */
+
+/* BOOLEAN:INT (remminamarshals.list:4) */
+void
+remmina_marshal_BOOLEAN__INT(GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ TRACE_CALL(__func__);
+ typedef gboolean (*GMarshalFunc_BOOLEAN__INT) (gpointer data1,
+ gint arg_1,
+ gpointer data2);
+ register GMarshalFunc_BOOLEAN__INT callback;
+ register GCClosure *cc = (GCClosure*)closure;
+ register gpointer data1, data2;
+ gboolean v_return;
+
+ g_return_if_fail(return_value != NULL);
+ g_return_if_fail(n_param_values == 2);
+
+ if (G_CCLOSURE_SWAP_DATA(closure)) {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer(param_values + 0);
+ }else {
+ data1 = g_value_peek_pointer(param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_BOOLEAN__INT)(marshal_data ? marshal_data : cc->callback);
+
+ v_return = callback(data1,
+ g_marshal_value_peek_int(param_values + 1)
+ ,
+ data2);
+
+ g_value_set_boolean(return_value, v_return);
+}
+
+/* BOOLEAN:INT,STRING (remminamarshals.list:5) */
+void
+remmina_marshal_BOOLEAN__INT_STRING(GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ TRACE_CALL(__func__);
+ typedef gboolean (*GMarshalFunc_BOOLEAN__INT_STRING) (gpointer data1,
+ gint arg_1,
+ gpointer arg_2,
+ gpointer data2);
+ register GMarshalFunc_BOOLEAN__INT_STRING callback;
+ register GCClosure *cc = (GCClosure*)closure;
+ register gpointer data1, data2;
+ gboolean v_return;
+
+ g_return_if_fail(return_value != NULL);
+ g_return_if_fail(n_param_values == 3);
+
+ if (G_CCLOSURE_SWAP_DATA(closure)) {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer(param_values + 0);
+ }else {
+ data1 = g_value_peek_pointer(param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_BOOLEAN__INT_STRING)(marshal_data ? marshal_data : cc->callback);
+
+ v_return = callback(data1,
+ g_marshal_value_peek_int(param_values + 1),
+ g_marshal_value_peek_string(param_values + 2),
+ data2);
+
+ g_value_set_boolean(return_value, v_return);
+}
+
diff --git a/src/remmina_marshals.h b/src/remmina_marshals.h
new file mode 100644
index 000000000..ed64aba56
--- /dev/null
+++ b/src/remmina_marshals.h
@@ -0,0 +1,19 @@
+#ifndef __remmina_marshal_MARSHAL_H__
+#define __remmina_marshal_MARSHAL_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+/* BOOLEAN:INT (remminamarshals.list:4) */
+extern void remmina_marshal_BOOLEAN__INT(GClosure *closure, GValue *return_value, guint n_param_values,
+ const GValue *param_values, gpointer invocation_hint, gpointer marshal_data);
+
+/* BOOLEAN:INT,STRING (remminamarshals.list:5) */
+extern void remmina_marshal_BOOLEAN__INT_STRING(GClosure *closure, GValue *return_value, guint n_param_values,
+ const GValue *param_values, gpointer invocation_hint, gpointer marshal_data);
+
+G_END_DECLS
+
+#endif /* __remmina_marshal_MARSHAL_H__ */
+
diff --git a/src/remmina_marshals.list b/src/remmina_marshals.list
new file mode 100644
index 000000000..ba3e432e3
--- /dev/null
+++ b/src/remmina_marshals.list
@@ -0,0 +1,6 @@
+# Use glib-genmarshal to generate remminamarshals.h and remminamarshals.c:
+# $ glib-genmarshal --header --prefix=remmina_marshal remminamarshals.list > remminamarshals.h
+# $ glib-genmarshal --body --prefix=remmina_marshal remminamarshals.list > remminamarshals.c
+BOOLEAN:INT
+BOOLEAN:INT,STRING
+
diff --git a/src/remmina_masterthread_exec.c b/src/remmina_masterthread_exec.c
new file mode 100644
index 000000000..250a717b7
--- /dev/null
+++ b/src/remmina_masterthread_exec.c
@@ -0,0 +1,160 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2011 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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.
+ *
+ */
+
+/* Support for execution on main thread of some GTK related
+ * functions (due to threads deprecations in GTK) */
+
+#include <gtk/gtk.h>
+
+#include "remmina_masterthread_exec.h"
+
+static pthread_t gMainThreadID;
+
+static gboolean remmina_masterthread_exec_callback(RemminaMTExecData *d)
+{
+
+ /* This function is called on main GTK Thread via gdk_threads_add_idlde()
+ * from remmina_masterthread_exec_and_wait() */
+
+ if (!d->cancelled) {
+ switch (d->func) {
+ case FUNC_INIT_SAVE_CRED:
+ remmina_protocol_widget_init_save_cred(d->p.init_save_creds.gp);
+ break;
+ case FUNC_CHAT_RECEIVE:
+ remmina_protocol_widget_chat_receive(d->p.chat_receive.gp, d->p.chat_receive.text);
+ break;
+ case FUNC_FILE_GET_STRING:
+ d->p.file_get_string.retval = remmina_file_get_string( d->p.file_get_string.remminafile, d->p.file_get_string.setting );
+ break;
+ case FUNC_DIALOG_SERVERKEY_CONFIRM:
+ d->p.dialog_serverkey_confirm.retval = remmina_init_dialog_serverkey_confirm( d->p.dialog_serverkey_confirm.dialog,
+ d->p.dialog_serverkey_confirm.serverkey, d->p.dialog_serverkey_confirm.prompt );
+ break;
+ case FUNC_DIALOG_AUTHPWD:
+ d->p.dialog_authpwd.retval = remmina_init_dialog_authpwd(d->p.dialog_authpwd.dialog,
+ d->p.dialog_authpwd.label, d->p.dialog_authpwd.allow_save);
+ break;
+ case FUNC_GTK_LABEL_SET_TEXT:
+ gtk_label_set_text( d->p.gtk_label_set_text.label, d->p.gtk_label_set_text.str );
+ break;
+ case FUNC_DIALOG_AUTHUSERPWD:
+ d->p.dialog_authuserpwd.retval = remmina_init_dialog_authuserpwd( d->p.dialog_authuserpwd.dialog,
+ d->p.dialog_authuserpwd.want_domain, d->p.dialog_authuserpwd.default_username,
+ d->p.dialog_authuserpwd.default_domain, d->p.dialog_authuserpwd.allow_save );
+ break;
+ case FUNC_DIALOG_CERT:
+ d->p.dialog_certificate.retval = remmina_init_dialog_certificate( d->p.dialog_certificate.dialog,
+ d->p.dialog_certificate.subject, d->p.dialog_certificate.issuer, d->p.dialog_certificate.fingerprint );
+ break;
+ case FUNC_DIALOG_CERTCHANGED:
+ d->p.dialog_certchanged.retval = remmina_init_dialog_certificate_changed( d->p.dialog_certchanged.dialog,
+ d->p.dialog_certchanged.subject, d->p.dialog_certchanged.issuer, d->p.dialog_certchanged.new_fingerprint,
+ d->p.dialog_certchanged.old_fingerprint );
+ break;
+ case FUNC_DIALOG_AUTHX509:
+ d->p.dialog_authx509.retval = remmina_init_dialog_authx509( d->p.dialog_authx509.dialog, d->p.dialog_authx509.cacert,
+ d->p.dialog_authx509.cacrl, d->p.dialog_authx509.clientcert, d->p.dialog_authx509.clientkey );
+ break;
+ case FUNC_FTP_CLIENT_UPDATE_TASK:
+ remmina_ftp_client_update_task( d->p.ftp_client_update_task.client, d->p.ftp_client_update_task.task );
+ break;
+ case FUNC_FTP_CLIENT_GET_WAITING_TASK:
+ d->p.ftp_client_get_waiting_task.retval = remmina_ftp_client_get_waiting_task( d->p.ftp_client_get_waiting_task.client );
+ break;
+ case FUNC_PROTOCOLWIDGET_EMIT_SIGNAL:
+ remmina_protocol_widget_emit_signal(d->p.protocolwidget_emit_signal.gp, d->p.protocolwidget_emit_signal.signal_name);
+ break;
+ case FUNC_SFTP_CLIENT_CONFIRM_RESUME:
+#ifdef HAVE_LIBSSH
+ d->p.sftp_client_confirm_resume.retval = remmina_sftp_client_confirm_resume( d->p.sftp_client_confirm_resume.client,
+ d->p.sftp_client_confirm_resume.path );
+#endif
+ break;
+ case FUNC_VTE_TERMINAL_SET_ENCODING_AND_PTY:
+#if defined (HAVE_LIBSSH) && defined (HAVE_LIBVTE)
+ remmina_plugin_ssh_vte_terminal_set_encoding_and_pty( d->p.vte_terminal_set_encoding_and_pty.terminal,
+ d->p.vte_terminal_set_encoding_and_pty.codeset,
+ d->p.vte_terminal_set_encoding_and_pty.master,
+ d->p.vte_terminal_set_encoding_and_pty.slave);
+#endif
+ break;
+ }
+ pthread_mutex_lock(&d->pt_mutex);
+ d->complete = TRUE;
+ pthread_cond_signal(&d->pt_cond);
+ pthread_mutex_unlock(&d->pt_mutex);
+ }else {
+ /* thread has been cancelled, so we must free d memory here */
+ g_free(d);
+ }
+ return G_SOURCE_REMOVE;
+}
+
+static void remmina_masterthread_exec_cleanup_handler(gpointer data)
+{
+ RemminaMTExecData *d = data;
+
+ d->cancelled = TRUE;
+}
+
+void remmina_masterthread_exec_and_wait(RemminaMTExecData *d)
+{
+ d->cancelled = FALSE;
+ d->complete = FALSE;
+ pthread_cleanup_push(remmina_masterthread_exec_cleanup_handler, (void*)d);
+ pthread_mutex_init(&d->pt_mutex, NULL);
+ pthread_cond_init(&d->pt_cond, NULL);
+ gdk_threads_add_idle((GSourceFunc)remmina_masterthread_exec_callback, (gpointer)d);
+ pthread_mutex_lock(&d->pt_mutex);
+ while (!d->complete)
+ pthread_cond_wait(&d->pt_cond, &d->pt_mutex);
+ pthread_cleanup_pop(0);
+ pthread_mutex_destroy(&d->pt_mutex);
+ pthread_cond_destroy(&d->pt_cond);
+}
+
+void remmina_masterthread_exec_save_main_thread_id()
+{
+ /* To be called from main thread at startup */
+ gMainThreadID = pthread_self();
+}
+
+gboolean remmina_masterthread_exec_is_main_thread()
+{
+ return pthread_equal(gMainThreadID, pthread_self()) != 0;
+}
+
diff --git a/src/remmina_masterthread_exec.h b/src/remmina_masterthread_exec.h
new file mode 100644
index 000000000..d94cbf96b
--- /dev/null
+++ b/src/remmina_masterthread_exec.h
@@ -0,0 +1,159 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2011 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 <pthread.h>
+#include "remmina_protocol_widget.h"
+#include "remmina_init_dialog.h"
+#include "remmina_sftp_client.h"
+#include "remmina_ftp_client.h"
+#include "remmina_ssh_plugin.h"
+
+typedef struct remmina_masterthread_exec_data {
+
+ enum { FUNC_GTK_LABEL_SET_TEXT,
+ FUNC_INIT_SAVE_CRED, FUNC_CHAT_RECEIVE, FUNC_FILE_GET_STRING,
+ FUNC_DIALOG_SERVERKEY_CONFIRM, FUNC_DIALOG_AUTHPWD, FUNC_DIALOG_AUTHUSERPWD,
+ FUNC_DIALOG_CERT, FUNC_DIALOG_CERTCHANGED, FUNC_DIALOG_AUTHX509,
+ FUNC_FTP_CLIENT_UPDATE_TASK, FUNC_FTP_CLIENT_GET_WAITING_TASK,
+ FUNC_SFTP_CLIENT_CONFIRM_RESUME,
+ FUNC_PROTOCOLWIDGET_EMIT_SIGNAL,
+ FUNC_VTE_TERMINAL_SET_ENCODING_AND_PTY } func;
+
+ union {
+ struct {
+ GtkLabel *label;
+ const gchar *str;
+ } gtk_label_set_text;
+ struct {
+ RemminaProtocolWidget* gp;
+ } init_save_creds;
+ struct {
+ RemminaProtocolWidget* gp;
+ const gchar *text;
+ } chat_receive;
+ struct {
+ RemminaFile *remminafile;
+ const gchar *setting;
+ const gchar* retval;
+ } file_get_string;
+ struct {
+ RemminaInitDialog *dialog;
+ const gchar *serverkey;
+ const gchar *prompt;
+ gint retval;
+ } dialog_serverkey_confirm;
+ struct {
+ RemminaInitDialog *dialog;
+ gboolean allow_save;
+ const gchar *label;
+ gint retval;
+ } dialog_authpwd;
+ struct {
+ RemminaInitDialog *dialog;
+ gboolean want_domain;
+ gboolean allow_save;
+ const gchar *default_username;
+ const gchar *default_domain;
+ gint retval;
+ } dialog_authuserpwd;
+ struct {
+ RemminaInitDialog *dialog;
+ const gchar* subject;
+ const gchar* issuer;
+ const gchar* fingerprint;
+ gint retval;
+ } dialog_certificate;
+ struct {
+ RemminaInitDialog *dialog;
+ const gchar* subject;
+ const gchar* issuer;
+ const gchar* new_fingerprint;
+ const gchar* old_fingerprint;
+ gint retval;
+ } dialog_certchanged;
+ struct {
+ RemminaInitDialog *dialog;
+ const gchar *cacert;
+ const gchar *cacrl;
+ const gchar *clientcert;
+ const gchar *clientkey;
+ gint retval;
+ } dialog_authx509;
+ struct {
+ RemminaFTPClient *client;
+ RemminaFTPTask* task;
+ } ftp_client_update_task;
+ struct {
+ RemminaFTPClient *client;
+ RemminaFTPTask* retval;
+ } ftp_client_get_waiting_task;
+ struct {
+ RemminaProtocolWidget* gp;
+ const gchar* signal_name;
+ } protocolwidget_emit_signal;
+#ifdef HAVE_LIBSSH
+ struct {
+ RemminaSFTPClient *client;
+ const gchar *path;
+ gint retval;
+ } sftp_client_confirm_resume;
+#endif
+#ifdef HAVE_LIBVTE
+ struct {
+ VteTerminal *terminal;
+ const char *codeset;
+ int master;
+ int slave;
+ } vte_terminal_set_encoding_and_pty;
+#endif
+ } p;
+
+ /* Mutex for thread synchronization */
+ pthread_mutex_t pt_mutex;
+ pthread_cond_t pt_cond;
+ /* Flag to catch cancellations */
+ gboolean cancelled;
+ gboolean complete;
+
+} RemminaMTExecData;
+
+void remmina_masterthread_exec_and_wait(RemminaMTExecData *d);
+
+void remmina_masterthread_exec_save_main_thread_id(void);
+gboolean remmina_masterthread_exec_is_main_thread(void);
+
+
+
diff --git a/src/remmina_mpchange.c b/src/remmina_mpchange.c
new file mode 100644
index 000000000..d96210338
--- /dev/null
+++ b/src/remmina_mpchange.c
@@ -0,0 +1,451 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2011 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include "config.h"
+#include "remmina_mpchange.h"
+#include "remmina_file.h"
+#include "remmina_file_manager.h"
+#include "remmina_pref.h"
+#include "remmina_public.h"
+#include "remmina_main.h"
+#include "remmina_plugin_manager.h"
+#include "remmina_log.h"
+#include "remmina/remmina_trace_calls.h"
+
+#define GET_DIALOG_OBJECT(object_name) gtk_builder_get_object(bu, object_name)
+
+struct mpchanger_params {
+ gchar *username; // New username
+ gchar *domain; // New domain
+ gchar *password; // New password
+ gchar *group;
+
+ GtkEntry *eGroup, *eUsername, *eDomain;
+ GtkEntry *ePassword1, *ePassword2;
+ GtkListStore* store;
+ GtkDialog* dialog;
+ GtkTreeView* table;
+ GtkButton* btnDoChange;
+ GtkLabel* statusLabel;
+
+ GtkTreeIter iter;
+ int changed_passwords_count;
+ guint sid;
+ guint searchentrychange_timeout_source_id;
+};
+
+enum {
+ COL_F = 0,
+ COL_NAME,
+ COL_GROUP,
+ COL_USERNAME,
+ COL_FILENAME,
+ NUM_COLS
+};
+
+static gboolean remmina_mpchange_fieldcompare(const gchar *needle, const gchar *haystack, int *matchcount)
+{
+ TRACE_CALL(__func__);
+
+ if (needle[0] == 0) {
+ (*matchcount)++;
+ return TRUE;
+ }
+
+ if (strcasecmp(needle, haystack) != 0)
+ return FALSE;
+
+ (*matchcount)++;
+ return TRUE;
+
+}
+
+static void remmina_mpchange_file_list_callback(RemminaFile *remminafile, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ GtkListStore* store;
+ GtkTreeIter iter;
+ int matchcount;
+ const gchar *username, *domain, *group;
+
+ gchar* s;
+ struct mpchanger_params* mpcp;
+
+ mpcp = (struct mpchanger_params*)user_data;
+ store = GTK_LIST_STORE(mpcp->store);
+
+
+ username = remmina_file_get_string(remminafile, "username");
+ domain = remmina_file_get_string(remminafile, "domain");
+ group = remmina_file_get_string(remminafile, "group");
+
+ if (username == NULL)
+ username = "";
+
+ if (domain == NULL)
+ domain = "";
+
+ if (group == NULL)
+ group = "";
+
+ matchcount = 0;
+ if (!remmina_mpchange_fieldcompare(mpcp->username, username, &matchcount))
+ return;
+ if (!remmina_mpchange_fieldcompare(mpcp->domain, domain, &matchcount))
+ return;
+ if (!remmina_mpchange_fieldcompare(mpcp->group, group, &matchcount))
+ return;
+
+ gtk_list_store_append(store, &iter);
+ s = g_strdup_printf("%s\\%s", domain, username);
+
+ gtk_list_store_set(store, &iter,
+ COL_F, matchcount >= 3 ? TRUE : FALSE,
+ COL_NAME, remmina_file_get_string(remminafile, "name"),
+ COL_GROUP, group,
+ COL_USERNAME, s,
+ COL_FILENAME, remminafile->filename,
+ -1);
+ g_free(s);
+
+}
+
+static void remmina_mpchange_checkbox_toggle(GtkCellRendererToggle *cell, gchar *path_string, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ GtkTreeIter iter;
+ struct mpchanger_params* mpcp = (struct mpchanger_params*)user_data;
+ GtkTreePath *path;
+
+ gboolean a = gtk_cell_renderer_toggle_get_active(cell);
+ path = gtk_tree_path_new_from_string(path_string);
+ gtk_tree_model_get_iter(GTK_TREE_MODEL(mpcp->store), &iter, path);
+ gtk_tree_path_free(path);
+ gtk_list_store_set(mpcp->store, &iter, COL_F, !a, -1);
+}
+
+static void remmina_mpchange_dochange(gchar* fname, struct mpchanger_params* mpcp)
+{
+ TRACE_CALL(__func__);
+
+ RemminaFile* remminafile;
+
+ remminafile = remmina_file_load(fname);
+ remmina_file_store_secret_plugin_password(remminafile, "password", mpcp->password);
+ remmina_file_free(remminafile);
+ mpcp->changed_passwords_count++;
+
+}
+
+static void enable_inputs(struct mpchanger_params* mpcp, gboolean ena)
+{
+ gtk_widget_set_sensitive(GTK_WIDGET(mpcp->eGroup), ena);
+ gtk_widget_set_sensitive(GTK_WIDGET(mpcp->eUsername), ena);
+ gtk_widget_set_sensitive(GTK_WIDGET(mpcp->eDomain), ena);
+ gtk_widget_set_sensitive(GTK_WIDGET(mpcp->ePassword1), ena);
+ gtk_widget_set_sensitive(GTK_WIDGET(mpcp->ePassword2), ena);
+ gtk_widget_set_sensitive(GTK_WIDGET(mpcp->btnDoChange), ena);
+ gtk_widget_set_sensitive(GTK_WIDGET(mpcp->table), ena);
+}
+
+static gboolean changenext(gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ struct mpchanger_params* mpcp = (struct mpchanger_params*)user_data;
+ gchar* fname;
+ gboolean sel;
+
+ gtk_tree_model_get(GTK_TREE_MODEL(mpcp->store), &mpcp->iter, COL_F, &sel, -1);
+ gtk_tree_model_get(GTK_TREE_MODEL(mpcp->store), &mpcp->iter, COL_FILENAME, &fname, -1);
+ if (sel) {
+ remmina_mpchange_dochange(fname, mpcp);
+ }
+ g_free(fname);
+
+ if (gtk_tree_model_iter_next(GTK_TREE_MODEL(mpcp->store), &mpcp->iter)) {
+ return G_SOURCE_CONTINUE;
+ }else {
+ gtk_dialog_response(mpcp->dialog, 1);
+ mpcp->sid = 0;
+ return G_SOURCE_REMOVE;
+ }
+}
+
+static void remmina_mpchange_dochange_clicked(GtkButton *btn, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ struct mpchanger_params* mpcp = (struct mpchanger_params*)user_data;
+ const gchar *passwd1, *passwd2;
+
+ if (mpcp->searchentrychange_timeout_source_id) {
+ g_source_remove(mpcp->searchentrychange_timeout_source_id);
+ mpcp->searchentrychange_timeout_source_id = 0;
+ }
+
+ if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(mpcp->store), &mpcp->iter))
+ return;
+
+ passwd1 = gtk_entry_get_text(mpcp->ePassword1);
+ passwd2 = gtk_entry_get_text(mpcp->ePassword2);
+
+ if (g_strcmp0(passwd1, passwd2) != 0) {
+ GtkWidget *msgDialog;
+ msgDialog = gtk_message_dialog_new(GTK_WINDOW(mpcp->dialog),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ _("The passwords do not match"));
+ gtk_dialog_run(GTK_DIALOG(msgDialog));
+ gtk_widget_destroy(msgDialog);
+ return;
+ }
+
+ g_free(mpcp->password);
+ mpcp->password = g_strdup(passwd1);
+ mpcp->changed_passwords_count = 0;
+
+ gtk_label_set_text(mpcp->statusLabel, _("Resetting passwords, please wait..."));
+
+ enable_inputs(mpcp, FALSE);
+ mpcp->sid = g_idle_add(changenext, (gpointer)mpcp);
+
+}
+
+static gboolean remmina_mpchange_searchfield_changed_to(gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ struct mpchanger_params *mpcp = (struct mpchanger_params *)user_data;
+ const gchar *s;
+
+ if (mpcp->searchentrychange_timeout_source_id) {
+ g_source_remove(mpcp->searchentrychange_timeout_source_id);
+ mpcp->searchentrychange_timeout_source_id = 0;
+ }
+
+ s = gtk_entry_get_text(mpcp->eGroup);
+ g_free(mpcp->group);
+ mpcp->group = g_strdup(s);
+
+ s = gtk_entry_get_text(mpcp->eDomain);
+ g_free(mpcp->domain);
+ mpcp->domain = g_strdup(s);
+
+ s = gtk_entry_get_text(mpcp->eUsername);
+ g_free(mpcp->username);
+ mpcp->username = g_strdup(s);
+
+ if (mpcp->store != NULL) {
+ gtk_tree_view_set_model(mpcp->table, NULL);
+ }
+ mpcp->store = gtk_list_store_new(NUM_COLS, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
+
+ if (mpcp->group[0] != 0 || mpcp->domain[0] != 0 || mpcp->username[0] != 0)
+ remmina_file_manager_iterate((GFunc)remmina_mpchange_file_list_callback, (gpointer)mpcp);
+
+ gtk_tree_view_set_model(mpcp->table, GTK_TREE_MODEL(mpcp->store));
+
+ return G_SOURCE_CONTINUE; // Source already remove at the beginning
+
+}
+
+static void remmina_mpchange_searchfield_changed(GtkSearchEntry *se, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ struct mpchanger_params *mpcp = (struct mpchanger_params *)user_data;
+
+ if (mpcp->searchentrychange_timeout_source_id) {
+ g_source_remove(mpcp->searchentrychange_timeout_source_id);
+ mpcp->searchentrychange_timeout_source_id = 0;
+ }
+
+ mpcp->searchentrychange_timeout_source_id = g_timeout_add(500, remmina_mpchange_searchfield_changed_to, user_data);
+}
+
+
+static void remmina_mpchange_stopsearch(GtkSearchEntry *entry, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ /* The stop-search signal is emitted when pressing ESC on a GtkSearchEntry. We end the dialog. */
+ struct mpchanger_params *mpcp = (struct mpchanger_params *)user_data;
+ gtk_dialog_response(mpcp->dialog, 1);
+}
+
+static gboolean remmina_file_multipasswd_changer_mt(gpointer d)
+{
+ TRACE_CALL(__func__);
+ struct mpchanger_params *mpcp = d;
+ GtkBuilder* bu;
+ GtkDialog* dialog;
+ GtkWindow* mainwindow;
+ GtkCellRendererToggle *toggle;
+ RemminaSecretPlugin *secret_plugin;
+ char *initerror;
+
+ mainwindow = remmina_main_get_window();
+
+ /* The multiple password changer works only when a secret plugin is available */
+ initerror = NULL;
+ secret_plugin = remmina_plugin_manager_get_secret_plugin();
+ if (secret_plugin == NULL) {
+ initerror = _("The multi password changer cannot work without a secret plugin.\n");
+ }else {
+ if (!secret_plugin->is_service_available()) {
+ initerror = _("The multi password changer does not work without a secret service.\n");
+ }
+ }
+ if (initerror) {
+ GtkWidget *msgDialog;
+ msgDialog = gtk_message_dialog_new(mainwindow, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
+ "%s", initerror);
+ gtk_dialog_run(GTK_DIALOG(msgDialog));
+ gtk_widget_destroy(msgDialog);
+ return FALSE;
+ }
+
+
+ bu = remmina_public_gtk_builder_new_from_file("remmina_mpc.glade");
+ if (!bu) {
+ remmina_log_printf("Unable to load the multiple password changer glade file interface\n");
+ return FALSE;
+ }
+
+ dialog = GTK_DIALOG(gtk_builder_get_object(bu, "MPCDialog"));
+ mpcp->dialog = dialog;
+ if (mainwindow)
+ gtk_window_set_transient_for(GTK_WINDOW(dialog), mainwindow);
+
+
+ mpcp->eGroup = GTK_ENTRY(GET_DIALOG_OBJECT("groupEntry"));
+ gtk_entry_set_text(mpcp->eGroup, mpcp->group);
+ g_signal_connect(G_OBJECT(mpcp->eGroup), "changed", G_CALLBACK(remmina_mpchange_searchfield_changed), (gpointer)mpcp);
+ g_signal_connect(G_OBJECT(mpcp->eGroup), "stop-search", G_CALLBACK(remmina_mpchange_stopsearch), (gpointer)mpcp);
+
+ mpcp->eUsername = GTK_ENTRY(GET_DIALOG_OBJECT("usernameEntry"));
+ gtk_entry_set_text(mpcp->eUsername, mpcp->username);
+ g_signal_connect(G_OBJECT(mpcp->eUsername), "changed", G_CALLBACK(remmina_mpchange_searchfield_changed), (gpointer)mpcp);
+
+ mpcp->eDomain = GTK_ENTRY(GET_DIALOG_OBJECT("domainEntry"));
+ gtk_entry_set_text(mpcp->eDomain, mpcp->domain);
+ g_signal_connect(G_OBJECT(mpcp->eDomain), "changed", G_CALLBACK(remmina_mpchange_searchfield_changed), (gpointer)mpcp);
+
+ mpcp->ePassword1 = GTK_ENTRY(GET_DIALOG_OBJECT("password1Entry"));
+ gtk_entry_set_text(mpcp->ePassword1, mpcp->password);
+
+ mpcp->ePassword2 = GTK_ENTRY(GET_DIALOG_OBJECT("password2Entry"));
+ gtk_entry_set_text(mpcp->ePassword2, mpcp->password);
+
+ mpcp->statusLabel = GTK_LABEL(GET_DIALOG_OBJECT("statusLabel"));
+
+
+ mpcp->store = NULL;
+
+ mpcp->table = GTK_TREE_VIEW(GET_DIALOG_OBJECT("profchangelist"));
+
+ /* Fire a fake searchfield changed, so a new list store is created */
+ remmina_mpchange_searchfield_changed(NULL, (gpointer)mpcp);
+
+ toggle = GTK_CELL_RENDERER_TOGGLE(GET_DIALOG_OBJECT("cellrenderertoggle1"));
+ g_signal_connect(G_OBJECT(toggle), "toggled", G_CALLBACK(remmina_mpchange_checkbox_toggle), (gpointer)mpcp);
+
+ mpcp->btnDoChange = GTK_BUTTON(GET_DIALOG_OBJECT("btnDoChange"));
+ g_signal_connect(mpcp->btnDoChange, "clicked", G_CALLBACK(remmina_mpchange_dochange_clicked), (gpointer)mpcp);
+
+#if GTK_CHECK_VERSION(3, 16, 0)
+ GList *fc = NULL;
+ fc = g_list_append(fc, mpcp->eGroup);
+ fc = g_list_append(fc, mpcp->eUsername);
+ fc = g_list_append(fc, mpcp->eDomain);
+ fc = g_list_append(fc, mpcp->ePassword1);
+ fc = g_list_append(fc, mpcp->ePassword2);
+ gtk_container_set_focus_chain(GTK_CONTAINER(GET_DIALOG_OBJECT("inputgrid1")), fc);
+#endif
+
+ gtk_dialog_run(dialog);
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+
+ if (mpcp->sid) {
+ g_source_remove(mpcp->sid);
+ mpcp->sid = 0;
+ }
+
+ if (mpcp->searchentrychange_timeout_source_id) {
+ g_source_remove(mpcp->searchentrychange_timeout_source_id);
+ mpcp->searchentrychange_timeout_source_id = 0;
+ }
+
+ if (mpcp->changed_passwords_count) {
+ GtkWidget *msgDialog;
+ msgDialog = gtk_message_dialog_new(GTK_WINDOW(mpcp->dialog),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_INFO,
+ GTK_BUTTONS_OK,
+ ngettext("%d password changed.", "%d passwords changed.", mpcp->changed_passwords_count), mpcp->changed_passwords_count);
+ gtk_dialog_run(GTK_DIALOG(msgDialog));
+ gtk_widget_destroy(msgDialog);
+ }
+
+ // Free data
+ g_free(mpcp->username);
+ g_free(mpcp->password);
+ g_free(mpcp->domain);
+ g_free(mpcp->group);
+ g_free(mpcp);
+ return FALSE;
+}
+
+
+void
+remmina_mpchange_schedule(gboolean has_domain, const gchar *group, const gchar *domain, const gchar *username, const gchar *password)
+{
+ // We could also be called in a subthread after a successful connection
+ // (not currently implemented)
+ // So we just schedule the multipassword changer to be executed on
+ // the main thread
+
+ TRACE_CALL(__func__);
+ struct mpchanger_params *mpcp;
+
+ mpcp = g_malloc0(sizeof(struct mpchanger_params));
+ mpcp->username = g_strdup(username);
+ mpcp->password = g_strdup(password);
+ mpcp->domain = g_strdup(domain);
+ mpcp->group = g_strdup(group);
+ gdk_threads_add_idle(remmina_file_multipasswd_changer_mt, (gpointer)mpcp);
+
+}
+
diff --git a/src/remmina_mpchange.h b/src/remmina_mpchange.h
new file mode 100644
index 000000000..68b874c20
--- /dev/null
+++ b/src/remmina_mpchange.h
@@ -0,0 +1,46 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2011 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 "remmina/types.h"
+
+#pragma once
+
+G_BEGIN_DECLS
+
+
+/* Schedule the multipassword change confirmation dialog to be executed ASAP */
+void remmina_mpchange_schedule(gboolean has_domain, const gchar *group, const gchar *domain, const gchar *username, const gchar *password);
+
diff --git a/src/remmina_plugin_manager.c b/src/remmina_plugin_manager.c
new file mode 100644
index 000000000..abf9786f1
--- /dev/null
+++ b/src/remmina_plugin_manager.c
@@ -0,0 +1,437 @@
+/*
+ * 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-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 "config.h"
+
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <string.h>
+
+#include <gdk/gdkx.h>
+
+#include "remmina_public.h"
+#include "remmina_file_manager.h"
+#include "remmina_pref.h"
+#include "remmina_protocol_widget.h"
+#include "remmina_log.h"
+#include "remmina_widget_pool.h"
+#include "remmina_connection_window.h"
+#include "remmina_plugin_manager.h"
+#include "remmina_public.h"
+#include "remmina_masterthread_exec.h"
+#include "remmina/remmina_trace_calls.h"
+
+static GPtrArray* remmina_plugin_table = NULL;
+
+/* There can be only one secret plugin loaded */
+static RemminaSecretPlugin *remmina_secret_plugin = NULL;
+
+static const gchar *remmina_plugin_type_name[] =
+{ N_("Protocol"), N_("Entry"), N_("File"), N_("Tool"), N_("Preference"), N_("Secret"), NULL };
+
+static gint remmina_plugin_manager_compare_func(RemminaPlugin **a, RemminaPlugin **b)
+{
+ TRACE_CALL(__func__);
+ return g_strcmp0((*a)->name, (*b)->name);
+}
+
+static gboolean remmina_plugin_manager_register_plugin(RemminaPlugin *plugin)
+{
+ TRACE_CALL(__func__);
+ if (plugin->type == REMMINA_PLUGIN_TYPE_SECRET) {
+ if (remmina_secret_plugin) {
+ g_print("Remmina plugin %s (type=%s) bypassed.\n", plugin->name,
+ _(remmina_plugin_type_name[plugin->type]));
+ return FALSE;
+ }
+ remmina_secret_plugin = (RemminaSecretPlugin*)plugin;
+ }
+ g_ptr_array_add(remmina_plugin_table, plugin);
+ g_ptr_array_sort(remmina_plugin_table, (GCompareFunc)remmina_plugin_manager_compare_func);
+ /* g_print("Remmina plugin %s (type=%s) registered.\n", plugin->name, _(remmina_plugin_type_name[plugin->type])); */
+ return TRUE;
+}
+
+static gboolean remmina_gtksocket_available()
+{
+ GdkDisplayManager* dm;
+ GdkDisplay* d;
+ gboolean available;
+
+ dm = gdk_display_manager_get();
+ d = gdk_display_manager_get_default_display(dm);
+ available = FALSE;
+
+#ifdef GDK_WINDOWING_X11
+ if (GDK_IS_X11_DISPLAY(d)) {
+ /* GtkSocket support is available only under Xorg */
+ available = TRUE;
+ }
+#endif
+ return available;
+}
+
+
+
+RemminaPluginService remmina_plugin_manager_service =
+{
+ remmina_plugin_manager_register_plugin,
+ remmina_protocol_widget_get_width,
+ remmina_protocol_widget_set_width,
+ remmina_protocol_widget_get_height,
+ remmina_protocol_widget_set_height,
+ remmina_protocol_widget_get_current_scale_mode,
+ remmina_protocol_widget_get_expand,
+ remmina_protocol_widget_set_expand,
+ remmina_protocol_widget_has_error,
+ remmina_protocol_widget_set_error,
+ remmina_protocol_widget_is_closed,
+ remmina_protocol_widget_get_file,
+ remmina_protocol_widget_emit_signal,
+ remmina_protocol_widget_register_hostkey,
+ remmina_protocol_widget_start_direct_tunnel,
+ remmina_protocol_widget_start_reverse_tunnel,
+ remmina_protocol_widget_start_xport_tunnel,
+ remmina_protocol_widget_set_display,
+ remmina_protocol_widget_close_connection,
+ remmina_protocol_widget_init_authpwd,
+ remmina_protocol_widget_init_authuserpwd,
+ remmina_protocol_widget_init_certificate,
+ remmina_protocol_widget_changed_certificate,
+ remmina_protocol_widget_init_get_username,
+ remmina_protocol_widget_init_get_password,
+ remmina_protocol_widget_init_get_domain,
+ remmina_protocol_widget_init_get_savepassword,
+ remmina_protocol_widget_init_authx509,
+ remmina_protocol_widget_init_get_cacert,
+ remmina_protocol_widget_init_get_cacrl,
+ remmina_protocol_widget_init_get_clientcert,
+ remmina_protocol_widget_init_get_clientkey,
+ remmina_protocol_widget_init_save_cred,
+ remmina_protocol_widget_init_show_listen,
+ remmina_protocol_widget_init_show_retry,
+ remmina_protocol_widget_init_show,
+ remmina_protocol_widget_init_hide,
+ remmina_protocol_widget_ssh_exec,
+ remmina_protocol_widget_chat_open,
+ remmina_protocol_widget_chat_close,
+ remmina_protocol_widget_chat_receive,
+ remmina_protocol_widget_send_keys_signals,
+
+ remmina_file_get_datadir,
+
+ remmina_file_new,
+ remmina_file_get_filename,
+ remmina_file_set_string,
+ remmina_file_get_string,
+ remmina_file_get_secret,
+ remmina_file_set_int,
+ remmina_file_get_int,
+ remmina_file_unsave_password,
+
+ remmina_pref_set_value,
+ remmina_pref_get_value,
+ remmina_pref_get_scale_quality,
+ remmina_pref_get_sshtunnel_port,
+ remmina_pref_get_ssh_loglevel,
+ remmina_pref_get_ssh_parseconfig,
+ remmina_pref_keymap_get_keyval,
+
+ remmina_log_print,
+ remmina_log_printf,
+
+ remmina_widget_pool_register,
+
+ remmina_connection_window_open_from_file_full,
+ remmina_public_get_server_port,
+ remmina_masterthread_exec_is_main_thread,
+ remmina_gtksocket_available,
+ remmina_protocol_widget_get_profile_remote_width,
+ remmina_protocol_widget_get_profile_remote_height
+
+};
+
+static void remmina_plugin_manager_load_plugin(const gchar *name)
+{
+ TRACE_CALL(__func__);
+ GModule *module;
+ RemminaPluginEntryFunc entry;
+
+ module = g_module_open(name, G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL);
+
+ if (!module) {
+ g_print("Failed to load plugin: %s.\n", name);
+ g_print("Error: %s\n", g_module_error());
+ return;
+ }
+
+ if (!g_module_symbol(module, "remmina_plugin_entry", (gpointer*)&entry)) {
+ g_print("Failed to locate plugin entry: %s.\n", name);
+ return;
+ }
+
+ if (!entry(&remmina_plugin_manager_service)) {
+ g_print("Plugin entry returned false: %s.\n", name);
+ return;
+ }
+
+ /* We don't close the module because we will need it throughout the process lifetime */
+}
+
+void remmina_plugin_manager_init(void)
+{
+ TRACE_CALL(__func__);
+ GDir *dir;
+ const gchar *name, *ptr;
+ gchar *fullpath;
+
+ remmina_plugin_table = g_ptr_array_new();
+
+ if (!g_module_supported()) {
+ g_print("Dynamic loading of plugins is not supported in this platform!\n");
+ return;
+ }
+
+ dir = g_dir_open(REMMINA_RUNTIME_PLUGINDIR, 0, NULL);
+ if (dir == NULL)
+ return;
+ while ((name = g_dir_read_name(dir)) != NULL) {
+ if ((ptr = strrchr(name, '.')) == NULL)
+ continue;
+ ptr++;
+ if (g_strcmp0(ptr, G_MODULE_SUFFIX) != 0)
+ continue;
+ fullpath = g_strdup_printf(REMMINA_RUNTIME_PLUGINDIR "/%s", name);
+ remmina_plugin_manager_load_plugin(fullpath);
+ g_free(fullpath);
+ }
+ g_dir_close(dir);
+}
+
+RemminaPlugin* remmina_plugin_manager_get_plugin(RemminaPluginType type, const gchar *name)
+{
+ TRACE_CALL(__func__);
+ RemminaPlugin *plugin;
+ gint i;
+
+ for (i = 0; i < remmina_plugin_table->len; i++) {
+ plugin = (RemminaPlugin*)g_ptr_array_index(remmina_plugin_table, i);
+ if (plugin->type == type && g_strcmp0(plugin->name, name) == 0) {
+ return plugin;
+ }
+ }
+ return NULL;
+}
+
+const gchar *remmina_plugin_manager_get_canonical_setting_name(const RemminaProtocolSetting* setting)
+{
+ if (setting->name == NULL) {
+ if (setting->type == REMMINA_PROTOCOL_SETTING_TYPE_SERVER)
+ return "server";
+ if (setting->type == REMMINA_PROTOCOL_SETTING_TYPE_PASSWORD)
+ return "password";
+ if (setting->type == REMMINA_PROTOCOL_SETTING_TYPE_RESOLUTION)
+ return "resolution";
+ return "missing_setting_name_into_plugin_RemminaProtocolSetting";
+ }
+ return setting->name;
+}
+
+void remmina_plugin_manager_for_each_plugin(RemminaPluginType type, RemminaPluginFunc func, gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaPlugin *plugin;
+ gint i;
+
+ for (i = 0; i < remmina_plugin_table->len; i++) {
+ plugin = (RemminaPlugin*)g_ptr_array_index(remmina_plugin_table, i);
+ if (plugin->type == type) {
+ func((gchar*)plugin->name, plugin, data);
+ }
+ }
+}
+
+/* A copy of remmina_plugin_manager_show and remmina_plugin_manager_show_for_each
+ * This is because we want to print the list of plugins, and their versions, to the standard output
+ * with the remmina command line option --full-version instead of using the plugins widget
+ ** @todo Investigate to use only GListStore and than pass the elements to be shown to 2 separate
+ * functions
+ * WARNING: GListStore is supported only from GLib 2.44 */
+static gboolean remmina_plugin_manager_show_for_each_stdout(RemminaPlugin *plugin)
+{
+ TRACE_CALL(__func__);
+
+ g_print("%-20s%-16s%-64s%-10s\n", plugin->name,
+ _(remmina_plugin_type_name[plugin->type]),
+ g_dgettext(plugin->domain, plugin->description),
+ plugin->version);
+ return FALSE;
+}
+
+void remmina_plugin_manager_show_stdout()
+{
+ TRACE_CALL(__func__);
+ g_print("%-20s%-16s%-64s%-10s\n", "NAME", "TYPE", "DESCRIPTION", "PLUGIN AND LIBRARY VERSION");
+ g_ptr_array_foreach(remmina_plugin_table, (GFunc)remmina_plugin_manager_show_for_each_stdout, NULL);
+}
+
+static gboolean remmina_plugin_manager_show_for_each(RemminaPlugin *plugin, GtkListStore *store)
+{
+ TRACE_CALL(__func__);
+ GtkTreeIter iter;
+
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, 0, plugin->name, 1, _(remmina_plugin_type_name[plugin->type]), 2,
+ g_dgettext(plugin->domain, plugin->description), 3, plugin->version, -1);
+ return FALSE;
+}
+
+void remmina_plugin_manager_show(GtkWindow *parent)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *dialog;
+ GtkWidget *scrolledwindow;
+ GtkWidget *tree;
+ GtkCellRenderer *renderer;
+ GtkTreeViewColumn *column;
+ GtkListStore *store;
+
+ dialog = gtk_dialog_new_with_buttons(_("Plugins"), parent, GTK_DIALOG_MODAL, _("_OK"), GTK_RESPONSE_ACCEPT, NULL);
+ g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(gtk_widget_destroy), dialog);
+ gtk_window_set_default_size(GTK_WINDOW(dialog), 500, 350);
+
+ 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);
+
+ store = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
+ g_ptr_array_foreach(remmina_plugin_table, (GFunc)remmina_plugin_manager_show_for_each, store);
+ gtk_tree_view_set_model(GTK_TREE_VIEW(tree), GTK_TREE_MODEL(store));
+
+ renderer = gtk_cell_renderer_text_new();
+ column = gtk_tree_view_column_new_with_attributes(_("Name"), renderer, "text", 0, NULL);
+ gtk_tree_view_column_set_resizable(column, TRUE);
+ gtk_tree_view_column_set_sort_column_id(column, 0);
+ 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", 1, NULL);
+ gtk_tree_view_column_set_resizable(column, TRUE);
+ gtk_tree_view_column_set_sort_column_id(column, 1);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
+
+ renderer = gtk_cell_renderer_text_new();
+ column = gtk_tree_view_column_new_with_attributes(_("Description"), renderer, "text", 2, NULL);
+ gtk_tree_view_column_set_resizable(column, TRUE);
+ gtk_tree_view_column_set_sort_column_id(column, 2);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
+
+ renderer = gtk_cell_renderer_text_new();
+ column = gtk_tree_view_column_new_with_attributes(_("Version"), renderer, "text", 3, NULL);
+ gtk_tree_view_column_set_resizable(column, TRUE);
+ gtk_tree_view_column_set_sort_column_id(column, 3);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
+
+ gtk_widget_show(dialog);
+}
+
+RemminaFilePlugin* remmina_plugin_manager_get_import_file_handler(const gchar *file)
+{
+ TRACE_CALL(__func__);
+ RemminaFilePlugin *plugin;
+ gint i;
+
+ for (i = 0; i < remmina_plugin_table->len; i++) {
+ plugin = (RemminaFilePlugin*)g_ptr_array_index(remmina_plugin_table, i);
+
+ if (plugin->type != REMMINA_PLUGIN_TYPE_FILE)
+ continue;
+
+ if (plugin->import_test_func(file)) {
+ return plugin;
+ }
+ }
+ return NULL;
+}
+
+RemminaFilePlugin* remmina_plugin_manager_get_export_file_handler(RemminaFile *remminafile)
+{
+ TRACE_CALL(__func__);
+ RemminaFilePlugin *plugin;
+ gint i;
+
+ for (i = 0; i < remmina_plugin_table->len; i++) {
+ plugin = (RemminaFilePlugin*)g_ptr_array_index(remmina_plugin_table, i);
+ if (plugin->type != REMMINA_PLUGIN_TYPE_FILE)
+ continue;
+ if (plugin->export_test_func(remminafile)) {
+ return plugin;
+ }
+ }
+ return NULL;
+}
+
+RemminaSecretPlugin* remmina_plugin_manager_get_secret_plugin(void)
+{
+ TRACE_CALL(__func__);
+ return remmina_secret_plugin;
+}
+
+gboolean remmina_plugin_manager_query_feature_by_type(RemminaPluginType ptype, const gchar* name, RemminaProtocolFeatureType ftype)
+{
+ const RemminaProtocolFeature *feature;
+ RemminaProtocolPlugin* plugin;
+
+ plugin = (RemminaProtocolPlugin*)remmina_plugin_manager_get_plugin(ptype, name);
+
+ if (plugin == NULL) {
+ return FALSE;
+ }
+
+ for (feature = plugin->features; feature && feature->type; feature++) {
+ if (feature->type == ftype)
+ return TRUE;
+ }
+ return FALSE;
+}
+
diff --git a/src/remmina_plugin_manager.h b/src/remmina_plugin_manager.h
new file mode 100644
index 000000000..9a5883deb
--- /dev/null
+++ b/src/remmina_plugin_manager.h
@@ -0,0 +1,61 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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.
+ *
+ */
+
+#pragma once
+
+#include "remmina/plugin.h"
+
+G_BEGIN_DECLS
+
+typedef gboolean (*RemminaPluginFunc)(gchar *name, RemminaPlugin *plugin, gpointer data);
+
+void remmina_plugin_manager_init(void);
+RemminaPlugin* remmina_plugin_manager_get_plugin(RemminaPluginType type, const gchar *name);
+gboolean remmina_plugin_manager_query_feature_by_type(RemminaPluginType ptype, const gchar* name, RemminaProtocolFeatureType ftype);
+void remmina_plugin_manager_for_each_plugin(RemminaPluginType type, RemminaPluginFunc func, gpointer data);
+void remmina_plugin_manager_show(GtkWindow *parent);
+void remmina_plugin_manager_for_each_plugin_stdout(RemminaPluginType type, RemminaPluginFunc func, gpointer data);
+void remmina_plugin_manager_show_stdout();
+RemminaFilePlugin* remmina_plugin_manager_get_import_file_handler(const gchar *file);
+RemminaFilePlugin* remmina_plugin_manager_get_export_file_handler(RemminaFile *remminafile);
+RemminaSecretPlugin* remmina_plugin_manager_get_secret_plugin(void);
+const gchar *remmina_plugin_manager_get_canonical_setting_name(const RemminaProtocolSetting* setting);
+
+extern RemminaPluginService remmina_plugin_manager_service;
+
+G_END_DECLS
+
+
diff --git a/src/remmina_pref.c b/src/remmina_pref.c
new file mode 100644
index 000000000..5dc4a9331
--- /dev/null
+++ b/src/remmina_pref.c
@@ -0,0 +1,1039 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2011 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/utsname.h>
+
+#include <glib/gstdio.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+
+#include "remmina_public.h"
+#include "remmina_string_array.h"
+#include "remmina_pref.h"
+#include "remmina/remmina_trace_calls.h"
+
+const gchar *default_resolutions = "640x480,800x600,1024x768,1152x864,1280x960,1400x1050";
+const gchar *default_keystrokes = "Send hello world§hello world\\n";
+
+gchar *remmina_keymap_file;
+static GHashTable *remmina_keymap_table = NULL;
+
+/* We could customize this further if there are more requirements */
+static const gchar *default_keymap_data = "# Please check gdk/gdkkeysyms.h for a full list of all key names or hex key values\n"
+ "\n"
+ "[Map Meta Keys]\n"
+ "Super_L = Meta_L\n"
+ "Super_R = Meta_R\n"
+ "Meta_L = Super_L\n"
+ "Meta_R = Super_R\n";
+
+static void remmina_pref_gen_secret(void)
+{
+ TRACE_CALL(__func__);
+ guchar s[32];
+ gint i;
+ GTimeVal gtime;
+ GKeyFile *gkeyfile;
+ gchar *content;
+ gsize length;
+
+ g_get_current_time(&gtime);
+ srand(gtime.tv_sec);
+
+ for (i = 0; i < 32; i++) {
+ s[i] = (guchar)(rand() % 256);
+ }
+ remmina_pref.secret = g_base64_encode(s, 32);
+
+ gkeyfile = g_key_file_new();
+ g_key_file_load_from_file(gkeyfile, remmina_pref_file, G_KEY_FILE_NONE, NULL);
+ g_key_file_set_string(gkeyfile, "remmina_pref", "secret", remmina_pref.secret);
+ content = g_key_file_to_data(gkeyfile, &length, NULL);
+ g_file_set_contents(remmina_pref_file, content, length, NULL);
+
+ g_key_file_free(gkeyfile);
+ g_free(content);
+}
+
+static guint remmina_pref_get_keyval_from_str(const gchar *str)
+{
+ TRACE_CALL(__func__);
+ guint k;
+
+ if (!str)
+ return 0;
+
+ k = gdk_keyval_from_name(str);
+ if (!k) {
+ if (sscanf(str, "%x", &k) < 1)
+ k = 0;
+ }
+ return k;
+}
+
+static void remmina_pref_init_keymap(void)
+{
+ TRACE_CALL(__func__);
+ GKeyFile *gkeyfile;
+ gchar **groups;
+ gchar **gptr;
+ gchar **keys;
+ gchar **kptr;
+ gsize nkeys;
+ gchar *value;
+ guint *table;
+ guint *tableptr;
+ guint k1, k2;
+
+ if (remmina_keymap_table)
+ g_hash_table_destroy(remmina_keymap_table);
+ remmina_keymap_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+
+ gkeyfile = g_key_file_new();
+ if (!g_key_file_load_from_file(gkeyfile, remmina_keymap_file, G_KEY_FILE_NONE, NULL)) {
+ if (!g_key_file_load_from_data(gkeyfile, default_keymap_data, strlen(default_keymap_data), G_KEY_FILE_NONE,
+ NULL)) {
+ g_print("Failed to initialize keymap table\n");
+ g_key_file_free(gkeyfile);
+ return;
+ }
+ }
+
+ groups = g_key_file_get_groups(gkeyfile, NULL);
+ gptr = groups;
+ while (*gptr) {
+ keys = g_key_file_get_keys(gkeyfile, *gptr, &nkeys, NULL);
+ table = g_new0(guint, nkeys * 2 + 1);
+ g_hash_table_insert(remmina_keymap_table, g_strdup(*gptr), table);
+
+ kptr = keys;
+ tableptr = table;
+ while (*kptr) {
+ k1 = remmina_pref_get_keyval_from_str(*kptr);
+ if (k1) {
+ value = g_key_file_get_string(gkeyfile, *gptr, *kptr, NULL);
+ k2 = remmina_pref_get_keyval_from_str(value);
+ g_free(value);
+ *tableptr++ = k1;
+ *tableptr++ = k2;
+ }
+ kptr++;
+ }
+ g_strfreev(keys);
+ gptr++;
+ }
+ g_strfreev(groups);
+ g_key_file_free(gkeyfile);
+}
+
+/** @todo remmina_pref_file_do_copy and remmina_file_manager_do_copy to remmina_files_copy */
+static gboolean remmina_pref_file_do_copy(const char *src_path, const char *dst_path)
+{
+ GFile *src = g_file_new_for_path(src_path), *dst = g_file_new_for_path(dst_path);
+ /* We don't overwrite the target if it exists, because overwrite is not set */
+ const gboolean ok = g_file_copy(src, dst, G_FILE_COPY_NONE, NULL, NULL, NULL, NULL);
+
+ g_object_unref(dst);
+ g_object_unref(src);
+
+ return ok;
+}
+
+void remmina_pref_init(void)
+{
+ TRACE_CALL(__func__);
+ GKeyFile *gkeyfile;
+ gchar *remmina_dir;
+ const gchar *filename = g_strdup_printf("%s.pref", g_get_prgname());
+ const gchar *colors_filename = g_strdup_printf("%s.colors", g_get_prgname());
+ gchar *remmina_colors_file;
+ GDir *dir;
+ gchar *legacy = g_strdup_printf(".%s", g_get_prgname());
+ int i;
+
+ remmina_dir = g_build_path( "/", g_get_user_config_dir(), g_get_prgname(), NULL);
+ /* Create the XDG_CONFIG_HOME directory */
+ g_mkdir_with_parents(remmina_dir, 0750);
+
+ g_free(remmina_dir), remmina_dir = NULL;
+ /* Legacy ~/.remmina we copy the existing remmina.pref file inside
+ * XDG_CONFIG_HOME */
+ remmina_dir = g_build_path("/", g_get_home_dir(), legacy, NULL);
+ if (g_file_test(remmina_dir, G_FILE_TEST_IS_DIR)) {
+ dir = g_dir_open(remmina_dir, 0, NULL);
+ remmina_pref_file_do_copy(
+ g_build_path( "/", remmina_dir, filename, NULL),
+ g_build_path( "/", g_get_user_config_dir(),
+ g_get_prgname(), filename, NULL));
+ }
+
+ /* /usr/local/etc/remmina */
+ const gchar * const *dirs = g_get_system_config_dirs();
+ g_free(remmina_dir), remmina_dir = NULL;
+ for (i = 0; dirs[i] != NULL; ++i) {
+ remmina_dir = g_build_path( "/", dirs[i], g_get_prgname(), NULL);
+ if (g_file_test(remmina_dir, G_FILE_TEST_IS_DIR)) {
+ dir = g_dir_open(remmina_dir, 0, NULL);
+ while ((filename = g_dir_read_name(dir)) != NULL) {
+ remmina_pref_file_do_copy(
+ g_build_path( "/", remmina_dir, filename, NULL),
+ g_build_path( "/", g_get_user_config_dir(),
+ g_get_prgname(), filename, NULL));
+ }
+ g_free(remmina_dir), remmina_dir = NULL;
+ }
+ }
+
+ /* The last case we use the home ~/.config/remmina */
+ if (remmina_dir != NULL)
+ g_free(remmina_dir), remmina_dir = NULL;
+ remmina_dir = g_build_path( "/", g_get_user_config_dir(),
+ g_get_prgname(), NULL);
+
+ remmina_pref_file = g_strdup_printf("%s/remmina.pref", remmina_dir);
+ /* remmina.colors */
+ remmina_colors_file = g_strdup_printf("%s/%s", remmina_dir, colors_filename);
+
+ remmina_keymap_file = g_strdup_printf("%s/remmina.keymap", remmina_dir);
+
+ gkeyfile = g_key_file_new();
+ g_key_file_load_from_file(gkeyfile, remmina_pref_file, G_KEY_FILE_NONE, NULL);
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "save_view_mode", NULL))
+ remmina_pref.save_view_mode = g_key_file_get_boolean(gkeyfile, "remmina_pref", "save_view_mode", NULL);
+ else
+ remmina_pref.save_view_mode = TRUE;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "fullscreen_on_auto", NULL))
+ remmina_pref.fullscreen_on_auto = g_key_file_get_boolean(gkeyfile, "remmina_pref", "fullscreen_on_auto", NULL);
+ else
+ remmina_pref.fullscreen_on_auto = TRUE;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "floating_toolbar_placement", NULL))
+ remmina_pref.floating_toolbar_placement = g_key_file_get_integer(gkeyfile, "remmina_pref", "floating_toolbar_placement", NULL);
+ else
+ remmina_pref.floating_toolbar_placement = FLOATING_TOOLBAR_PLACEMENT_TOP;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "prevent_snap_welcome_message", NULL))
+ remmina_pref.prevent_snap_welcome_message = g_key_file_get_boolean(gkeyfile, "remmina_pref", "prevent_snap_welcome_message", NULL);
+ else
+ remmina_pref.prevent_snap_welcome_message = FALSE;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "toolbar_placement", NULL))
+ remmina_pref.toolbar_placement = g_key_file_get_integer(gkeyfile, "remmina_pref", "toolbar_placement", NULL);
+ else
+ remmina_pref.toolbar_placement = TOOLBAR_PLACEMENT_LEFT;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "always_show_tab", NULL))
+ remmina_pref.always_show_tab = g_key_file_get_boolean(gkeyfile, "remmina_pref", "always_show_tab", NULL);
+ else
+ remmina_pref.always_show_tab = TRUE;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "hide_connection_toolbar", NULL))
+ remmina_pref.hide_connection_toolbar = g_key_file_get_boolean(gkeyfile, "remmina_pref",
+ "hide_connection_toolbar", NULL);
+ else
+ remmina_pref.hide_connection_toolbar = FALSE;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "default_action", NULL))
+ remmina_pref.default_action = g_key_file_get_integer(gkeyfile, "remmina_pref", "default_action", NULL);
+ else
+ remmina_pref.default_action = REMMINA_ACTION_CONNECT;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "scale_quality", NULL))
+ remmina_pref.scale_quality = g_key_file_get_integer(gkeyfile, "remmina_pref", "scale_quality", NULL);
+ else
+ remmina_pref.scale_quality = GDK_INTERP_HYPER;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "hide_toolbar", NULL))
+ remmina_pref.hide_toolbar = g_key_file_get_boolean(gkeyfile, "remmina_pref", "hide_toolbar", NULL);
+ else
+ remmina_pref.hide_toolbar = FALSE;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "hide_statusbar", NULL))
+ remmina_pref.hide_statusbar = g_key_file_get_boolean(gkeyfile, "remmina_pref", "hide_statusbar", NULL);
+ else
+ remmina_pref.hide_statusbar = FALSE;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "small_toolbutton", NULL))
+ remmina_pref.small_toolbutton = g_key_file_get_boolean(gkeyfile, "remmina_pref", "small_toolbutton", NULL);
+ else
+ remmina_pref.small_toolbutton = FALSE;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "view_file_mode", NULL))
+ remmina_pref.view_file_mode = g_key_file_get_integer(gkeyfile, "remmina_pref", "view_file_mode", NULL);
+ else
+ remmina_pref.view_file_mode = REMMINA_VIEW_FILE_LIST;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "resolutions", NULL))
+ remmina_pref.resolutions = g_key_file_get_string(gkeyfile, "remmina_pref", "resolutions", NULL);
+ else
+ remmina_pref.resolutions = g_strdup(default_resolutions);
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "keystrokes", NULL))
+ remmina_pref.keystrokes = g_key_file_get_string(gkeyfile, "remmina_pref", "keystrokes", NULL);
+ else
+ remmina_pref.keystrokes = g_strdup(default_keystrokes);
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "main_width", NULL))
+ remmina_pref.main_width = MAX(600, g_key_file_get_integer(gkeyfile, "remmina_pref", "main_width", NULL));
+ else
+ remmina_pref.main_width = 600;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "main_height", NULL))
+ remmina_pref.main_height = MAX(400, g_key_file_get_integer(gkeyfile, "remmina_pref", "main_height", NULL));
+ else
+ remmina_pref.main_height = 400;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "main_maximize", NULL))
+ remmina_pref.main_maximize = g_key_file_get_boolean(gkeyfile, "remmina_pref", "main_maximize", NULL);
+ else
+ remmina_pref.main_maximize = FALSE;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "main_sort_column_id", NULL))
+ remmina_pref.main_sort_column_id = g_key_file_get_integer(gkeyfile, "remmina_pref", "main_sort_column_id",
+ NULL);
+ else
+ remmina_pref.main_sort_column_id = 1;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "main_sort_order", NULL))
+ remmina_pref.main_sort_order = g_key_file_get_integer(gkeyfile, "remmina_pref", "main_sort_order", NULL);
+ else
+ remmina_pref.main_sort_order = 0;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "expanded_group", NULL))
+ remmina_pref.expanded_group = g_key_file_get_string(gkeyfile, "remmina_pref", "expanded_group", NULL);
+ else
+ remmina_pref.expanded_group = g_strdup("");
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "toolbar_pin_down", NULL))
+ remmina_pref.toolbar_pin_down = g_key_file_get_boolean(gkeyfile, "remmina_pref", "toolbar_pin_down", NULL);
+ else
+ remmina_pref.toolbar_pin_down = FALSE;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "ssh_loglevel", NULL))
+ remmina_pref.ssh_loglevel = g_key_file_get_integer(gkeyfile, "remmina_pref", "ssh_loglevel", NULL);
+ else
+ remmina_pref.ssh_loglevel = DEFAULT_SSH_LOGLEVEL;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "screenshot_path", NULL)) {
+ remmina_pref.screenshot_path = g_key_file_get_string(gkeyfile, "remmina_pref", "screenshot_path", NULL);
+ }else{
+ remmina_pref.screenshot_path = g_get_user_special_dir(G_USER_DIRECTORY_PICTURES);
+ if (remmina_pref.screenshot_path == NULL)
+ remmina_pref.screenshot_path = g_get_home_dir();
+ }
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "ssh_parseconfig", NULL))
+ remmina_pref.ssh_parseconfig = g_key_file_get_boolean(gkeyfile, "remmina_pref", "ssh_parseconfig", NULL);
+ else
+ remmina_pref.ssh_parseconfig = DEFAULT_SSH_PARSECONFIG;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "sshtunnel_port", NULL))
+ remmina_pref.sshtunnel_port = g_key_file_get_integer(gkeyfile, "remmina_pref", "sshtunnel_port", NULL);
+ else
+ remmina_pref.sshtunnel_port = DEFAULT_SSHTUNNEL_PORT;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "ssh_tcp_keepidle", NULL))
+ remmina_pref.ssh_tcp_keepidle = g_key_file_get_integer(gkeyfile, "remmina_pref", "ssh_tcp_keepidle", NULL);
+ else
+ remmina_pref.ssh_tcp_keepidle = SSH_SOCKET_TCP_KEEPIDLE;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "ssh_tcp_keepintvl", NULL))
+ remmina_pref.ssh_tcp_keepintvl = g_key_file_get_integer(gkeyfile, "remmina_pref", "ssh_tcp_keepintvl", NULL);
+ else
+ remmina_pref.ssh_tcp_keepintvl = SSH_SOCKET_TCP_KEEPINTVL;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "ssh_tcp_keepcnt", NULL))
+ remmina_pref.ssh_tcp_keepcnt = g_key_file_get_integer(gkeyfile, "remmina_pref", "ssh_tcp_keepcnt", NULL);
+ else
+ remmina_pref.ssh_tcp_keepcnt = SSH_SOCKET_TCP_KEEPCNT;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "ssh_tcp_usrtimeout", NULL))
+ remmina_pref.ssh_tcp_usrtimeout = g_key_file_get_integer(gkeyfile, "remmina_pref", "ssh_tcp_usrtimeout", NULL);
+ else
+ remmina_pref.ssh_tcp_usrtimeout = SSH_SOCKET_TCP_USER_TIMEOUT;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "applet_new_ontop", NULL))
+ remmina_pref.applet_new_ontop = g_key_file_get_boolean(gkeyfile, "remmina_pref", "applet_new_ontop", NULL);
+ else
+ remmina_pref.applet_new_ontop = FALSE;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "applet_hide_count", NULL))
+ remmina_pref.applet_hide_count = g_key_file_get_boolean(gkeyfile, "remmina_pref", "applet_hide_count", NULL);
+ else
+ remmina_pref.applet_hide_count = FALSE;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "applet_enable_avahi", NULL))
+ remmina_pref.applet_enable_avahi = g_key_file_get_boolean(gkeyfile, "remmina_pref", "applet_enable_avahi",
+ NULL);
+ else
+ remmina_pref.applet_enable_avahi = FALSE;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "disable_tray_icon", NULL))
+ remmina_pref.disable_tray_icon = g_key_file_get_boolean(gkeyfile, "remmina_pref", "disable_tray_icon", NULL);
+ else
+ remmina_pref.disable_tray_icon = FALSE;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "dark_tray_icon", NULL))
+ remmina_pref.dark_tray_icon = g_key_file_get_boolean(gkeyfile, "remmina_pref", "dark_tray_icon", NULL);
+ else
+ remmina_pref.dark_tray_icon = FALSE;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "recent_maximum", NULL))
+ remmina_pref.recent_maximum = g_key_file_get_integer(gkeyfile, "remmina_pref", "recent_maximum", NULL);
+ else
+ remmina_pref.recent_maximum = 10;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "default_mode", NULL))
+ remmina_pref.default_mode = g_key_file_get_integer(gkeyfile, "remmina_pref", "default_mode", NULL);
+ else
+ remmina_pref.default_mode = 0;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "tab_mode", NULL))
+ remmina_pref.tab_mode = g_key_file_get_integer(gkeyfile, "remmina_pref", "tab_mode", NULL);
+ else
+ remmina_pref.tab_mode = 0;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "fullscreen_toolbar_visibility", NULL))
+ remmina_pref.fullscreen_toolbar_visibility = g_key_file_get_integer(gkeyfile, "remmina_pref", "fullscreen_toolbar_visibility", NULL);
+ else
+ remmina_pref.fullscreen_toolbar_visibility = FLOATING_TOOLBAR_VISIBILITY_PEEKING;
+ /* Show buttons icons */
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "show_buttons_icons", NULL)) {
+ remmina_pref.show_buttons_icons = g_key_file_get_integer(gkeyfile, "remmina_pref", "show_buttons_icons", NULL);
+ if (remmina_pref.show_buttons_icons) {
+ g_object_set(gtk_settings_get_default(), "gtk-button-images", remmina_pref.show_buttons_icons == 1, NULL);
+ }
+ }else
+ remmina_pref.show_buttons_icons = 0;
+ /* Show menu icons */
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "show_menu_icons", NULL)) {
+ remmina_pref.show_menu_icons = g_key_file_get_integer(gkeyfile, "remmina_pref", "show_menu_icons", NULL);
+ if (remmina_pref.show_menu_icons) {
+ g_object_set(gtk_settings_get_default(), "gtk-menu-images", remmina_pref.show_menu_icons == 1, NULL);
+ }
+ }else
+ remmina_pref.show_menu_icons = 0;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "auto_scroll_step", NULL))
+ remmina_pref.auto_scroll_step = g_key_file_get_integer(gkeyfile, "remmina_pref", "auto_scroll_step", NULL);
+ else
+ remmina_pref.auto_scroll_step = 10;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "hostkey", NULL))
+ remmina_pref.hostkey = g_key_file_get_integer(gkeyfile, "remmina_pref", "hostkey", NULL);
+ else
+ remmina_pref.hostkey = GDK_KEY_Control_R;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "shortcutkey_fullscreen", NULL))
+ remmina_pref.shortcutkey_fullscreen = g_key_file_get_integer(gkeyfile, "remmina_pref", "shortcutkey_fullscreen",
+ NULL);
+ else
+ remmina_pref.shortcutkey_fullscreen = GDK_KEY_f;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "shortcutkey_autofit", NULL))
+ remmina_pref.shortcutkey_autofit = g_key_file_get_integer(gkeyfile, "remmina_pref", "shortcutkey_autofit",
+ NULL);
+ else
+ remmina_pref.shortcutkey_autofit = GDK_KEY_1;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "shortcutkey_nexttab", NULL))
+ remmina_pref.shortcutkey_nexttab = g_key_file_get_integer(gkeyfile, "remmina_pref", "shortcutkey_nexttab",
+ NULL);
+ else
+ remmina_pref.shortcutkey_nexttab = GDK_KEY_Right;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "shortcutkey_prevtab", NULL))
+ remmina_pref.shortcutkey_prevtab = g_key_file_get_integer(gkeyfile, "remmina_pref", "shortcutkey_prevtab",
+ NULL);
+ else
+ remmina_pref.shortcutkey_prevtab = GDK_KEY_Left;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "shortcutkey_scale", NULL))
+ remmina_pref.shortcutkey_scale = g_key_file_get_integer(gkeyfile, "remmina_pref", "shortcutkey_scale", NULL);
+ else
+ remmina_pref.shortcutkey_scale = GDK_KEY_s;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "shortcutkey_viewonly", NULL))
+ remmina_pref.shortcutkey_viewonly = g_key_file_get_integer(gkeyfile, "remmina_pref", "shortcutkey_viewonly", NULL);
+ else
+ remmina_pref.shortcutkey_viewonly = GDK_KEY_m;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "shortcutkey_grab", NULL))
+ remmina_pref.shortcutkey_grab = g_key_file_get_integer(gkeyfile, "remmina_pref", "shortcutkey_grab", NULL);
+ else
+ remmina_pref.shortcutkey_grab = GDK_KEY_Control_R;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "shortcutkey_minimize", NULL))
+ remmina_pref.shortcutkey_minimize = g_key_file_get_integer(gkeyfile, "remmina_pref", "shortcutkey_minimize", NULL);
+ else
+ remmina_pref.shortcutkey_minimize = GDK_KEY_F9;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "shortcutkey_screenshot", NULL))
+ remmina_pref.shortcutkey_screenshot = g_key_file_get_integer(gkeyfile, "remmina_pref", "shortcutkey_screenshot", NULL);
+ else
+ remmina_pref.shortcutkey_screenshot = GDK_KEY_F12;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "shortcutkey_disconnect", NULL))
+ remmina_pref.shortcutkey_disconnect = g_key_file_get_integer(gkeyfile, "remmina_pref", "shortcutkey_disconnect",
+ NULL);
+ else
+ remmina_pref.shortcutkey_disconnect = GDK_KEY_F4;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "shortcutkey_toolbar", NULL))
+ remmina_pref.shortcutkey_toolbar = g_key_file_get_integer(gkeyfile, "remmina_pref", "shortcutkey_toolbar",
+ NULL);
+ else
+ remmina_pref.shortcutkey_toolbar = GDK_KEY_t;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "secret", NULL))
+ remmina_pref.secret = g_key_file_get_string(gkeyfile, "remmina_pref", "secret", NULL);
+ else
+ remmina_pref.secret = NULL;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "uid", NULL))
+ remmina_pref.uid = g_key_file_get_string(gkeyfile, "remmina_pref", "uid", NULL);
+ else
+ remmina_pref.uid = NULL;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "vte_font", NULL))
+ remmina_pref.vte_font = g_key_file_get_string(gkeyfile, "remmina_pref", "vte_font", NULL);
+ else
+ remmina_pref.vte_font = 0;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "vte_allow_bold_text", NULL))
+ remmina_pref.vte_allow_bold_text = g_key_file_get_boolean(gkeyfile, "remmina_pref", "vte_allow_bold_text",
+ NULL);
+ else
+ remmina_pref.vte_allow_bold_text = TRUE;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "vte_lines", NULL))
+ remmina_pref.vte_lines = g_key_file_get_integer(gkeyfile, "remmina_pref", "vte_lines", NULL);
+ else
+ remmina_pref.vte_lines = 512;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "vte_shortcutkey_copy", NULL))
+ remmina_pref.vte_shortcutkey_copy = g_key_file_get_integer(gkeyfile, "remmina_pref", "vte_shortcutkey_copy",
+ NULL);
+ else
+ remmina_pref.vte_shortcutkey_copy = GDK_KEY_c;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "vte_shortcutkey_paste", NULL))
+ remmina_pref.vte_shortcutkey_paste = g_key_file_get_integer(gkeyfile, "remmina_pref", "vte_shortcutkey_paste",
+ NULL);
+ else
+ remmina_pref.vte_shortcutkey_paste = GDK_KEY_v;
+
+ if (g_key_file_has_key(gkeyfile, "remmina_pref", "vte_shortcutkey_select_all", NULL))
+ remmina_pref.vte_shortcutkey_select_all = g_key_file_get_integer(gkeyfile, "remmina_pref", "vte_shortcutkey_select_all",
+ NULL);
+ else
+ remmina_pref.vte_shortcutkey_select_all = GDK_KEY_a;
+
+ /* If we have a color scheme file, we switch to it, GIO will merge it in the
+ * remmina.pref file */
+ if (g_file_test(remmina_colors_file, G_FILE_TEST_IS_REGULAR)) {
+ gkeyfile = g_key_file_new();
+ g_key_file_load_from_file(gkeyfile, remmina_colors_file, G_KEY_FILE_NONE, NULL);
+ g_remove(remmina_colors_file);
+ }
+
+ if (g_key_file_has_key(gkeyfile, "ssh_colors", "background", NULL))
+ remmina_pref.background = g_key_file_get_string(gkeyfile, "ssh_colors", "background",
+ NULL);
+ else
+ remmina_pref.background = "#d5ccba";
+
+ if (g_key_file_has_key(gkeyfile, "ssh_colors", "cursor", NULL))
+ remmina_pref.cursor = g_key_file_get_string(gkeyfile, "ssh_colors", "cursor",
+ NULL);
+ else
+ remmina_pref.cursor = "#45373c";
+
+ if (g_key_file_has_key(gkeyfile, "ssh_colors", "foreground", NULL))
+ remmina_pref.foreground = g_key_file_get_string(gkeyfile, "ssh_colors", "foreground",
+ NULL);
+ else
+ remmina_pref.foreground = "#45373c";
+
+ if (g_key_file_has_key(gkeyfile, "ssh_colors", "color0", NULL))
+ remmina_pref.color0 = g_key_file_get_string(gkeyfile, "ssh_colors", "color0",
+ NULL);
+ else
+ remmina_pref.color0 = "#20111b";
+
+ if (g_key_file_has_key(gkeyfile, "ssh_colors", "color1", NULL))
+ remmina_pref.color1 = g_key_file_get_string(gkeyfile, "ssh_colors", "color1",
+ NULL);
+ else
+ remmina_pref.color1 = "#be100e";
+
+ if (g_key_file_has_key(gkeyfile, "ssh_colors", "color2", NULL))
+ remmina_pref.color2 = g_key_file_get_string(gkeyfile, "ssh_colors", "color2",
+ NULL);
+ else
+ remmina_pref.color2 = "#858162";
+
+ if (g_key_file_has_key(gkeyfile, "ssh_colors", "color3", NULL))
+ remmina_pref.color3 = g_key_file_get_string(gkeyfile, "ssh_colors", "color3",
+ NULL);
+ else
+ remmina_pref.color3 = "#eaa549";
+
+ if (g_key_file_has_key(gkeyfile, "ssh_colors", "color4", NULL))
+ remmina_pref.color4 = g_key_file_get_string(gkeyfile, "ssh_colors", "color4",
+ NULL);
+ else
+ remmina_pref.color4 = "#426a79";
+
+ if (g_key_file_has_key(gkeyfile, "ssh_colors", "color5", NULL))
+ remmina_pref.color5 = g_key_file_get_string(gkeyfile, "ssh_colors", "color5",
+ NULL);
+ else
+ remmina_pref.color5 = "#97522c";
+
+ if (g_key_file_has_key(gkeyfile, "ssh_colors", "color6", NULL))
+ remmina_pref.color6 = g_key_file_get_string(gkeyfile, "ssh_colors", "color6",
+ NULL);
+ else
+ remmina_pref.color6 = "#989a9c";
+
+ if (g_key_file_has_key(gkeyfile, "ssh_colors", "color7", NULL))
+ remmina_pref.color7 = g_key_file_get_string(gkeyfile, "ssh_colors", "color7",
+ NULL);
+ else
+ remmina_pref.color7 = "#968c83";
+
+ if (g_key_file_has_key(gkeyfile, "ssh_colors", "color8", NULL))
+ remmina_pref.color8 = g_key_file_get_string(gkeyfile, "ssh_colors", "color8",
+ NULL);
+ else
+ remmina_pref.color8 = "#5e5252";
+
+ if (g_key_file_has_key(gkeyfile, "ssh_colors", "color9", NULL))
+ remmina_pref.color9 = g_key_file_get_string(gkeyfile, "ssh_colors", "color9",
+ NULL);
+ else
+ remmina_pref.color9 = "#be100e";
+
+ if (g_key_file_has_key(gkeyfile, "ssh_colors", "color10", NULL))
+ remmina_pref.color10 = g_key_file_get_string(gkeyfile, "ssh_colors", "color10",
+ NULL);
+ else
+ remmina_pref.color10 = "#858162";
+
+ if (g_key_file_has_key(gkeyfile, "ssh_colors", "color11", NULL))
+ remmina_pref.color11 = g_key_file_get_string(gkeyfile, "ssh_colors", "color11",
+ NULL);
+ else
+ remmina_pref.color11 = "#eaa549";
+
+ if (g_key_file_has_key(gkeyfile, "ssh_colors", "color12", NULL))
+ remmina_pref.color12 = g_key_file_get_string(gkeyfile, "ssh_colors", "color12",
+ NULL);
+ else
+ remmina_pref.color12 = "#426a79";
+
+ if (g_key_file_has_key(gkeyfile, "ssh_colors", "color13", NULL))
+ remmina_pref.color13 = g_key_file_get_string(gkeyfile, "ssh_colors", "color13",
+ NULL);
+ else
+ remmina_pref.color13 = "#97522c";
+
+ if (g_key_file_has_key(gkeyfile, "ssh_colors", "color14", NULL))
+ remmina_pref.color14 = g_key_file_get_string(gkeyfile, "ssh_colors", "color14",
+ NULL);
+ else
+ remmina_pref.color14 = "#989a9c";
+
+ if (g_key_file_has_key(gkeyfile, "ssh_colors", "color15", NULL))
+ remmina_pref.color15 = g_key_file_get_string(gkeyfile, "ssh_colors", "color15",
+ NULL);
+ else
+ remmina_pref.color15 = "#d5ccba";
+
+ if (g_key_file_has_key(gkeyfile, "usage_stats", "periodic_usage_stats_permission_asked", NULL))
+ remmina_pref.periodic_usage_stats_permission_asked = g_key_file_get_boolean(gkeyfile, "usage_stats", "periodic_usage_stats_permission_asked", NULL);
+ else
+ remmina_pref.periodic_usage_stats_permission_asked = FALSE;
+
+ if (g_key_file_has_key(gkeyfile, "usage_stats", "periodic_usage_stats_permitted", NULL))
+ remmina_pref.periodic_usage_stats_permitted = g_key_file_get_boolean(gkeyfile, "usage_stats", "periodic_usage_stats_permitted", NULL);
+ else
+ remmina_pref.periodic_usage_stats_permitted = FALSE;
+
+ if (g_key_file_has_key(gkeyfile, "usage_stats", "periodic_usage_stats_last_sent", NULL))
+ remmina_pref.periodic_usage_stats_last_sent = g_key_file_get_int64(gkeyfile, "usage_stats", "periodic_usage_stats_last_sent", NULL);
+ else
+ remmina_pref.periodic_usage_stats_last_sent = 0;
+
+ if (g_key_file_has_key(gkeyfile, "usage_stats", "periodic_usage_stats_uuid_prefix", NULL))
+ remmina_pref.periodic_usage_stats_uuid_prefix = g_key_file_get_string(gkeyfile, "usage_stats", "periodic_usage_stats_uuid_prefix", NULL);
+ else
+ remmina_pref.periodic_usage_stats_uuid_prefix = NULL;
+
+
+ g_key_file_free(gkeyfile);
+
+ if (remmina_pref.secret == NULL)
+ remmina_pref_gen_secret();
+
+ remmina_pref_init_keymap();
+}
+
+void remmina_pref_save(void)
+{
+ TRACE_CALL(__func__);
+ GKeyFile *gkeyfile;
+ gchar *content;
+ gsize length;
+
+ gkeyfile = g_key_file_new();
+
+ g_key_file_load_from_file(gkeyfile, remmina_pref_file, G_KEY_FILE_NONE, NULL);
+
+ g_key_file_set_boolean(gkeyfile, "remmina_pref", "save_view_mode", remmina_pref.save_view_mode);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "floating_toolbar_placement", remmina_pref.floating_toolbar_placement);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "toolbar_placement", remmina_pref.toolbar_placement);
+ g_key_file_set_boolean(gkeyfile, "remmina_pref", "prevent_snap_welcome_message", remmina_pref.prevent_snap_welcome_message);
+ g_key_file_set_boolean(gkeyfile, "remmina_pref", "fullscreen_on_auto", remmina_pref.fullscreen_on_auto);
+ g_key_file_set_boolean(gkeyfile, "remmina_pref", "always_show_tab", remmina_pref.always_show_tab);
+ g_key_file_set_boolean(gkeyfile, "remmina_pref", "hide_connection_toolbar", remmina_pref.hide_connection_toolbar);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "default_action", remmina_pref.default_action);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "scale_quality", remmina_pref.scale_quality);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "ssh_loglevel", remmina_pref.ssh_loglevel);
+ g_key_file_set_string(gkeyfile, "remmina_pref", "screenshot_path", remmina_pref.screenshot_path);
+ g_key_file_set_boolean(gkeyfile, "remmina_pref", "ssh_parseconfig", remmina_pref.ssh_parseconfig);
+ g_key_file_set_boolean(gkeyfile, "remmina_pref", "hide_toolbar", remmina_pref.hide_toolbar);
+ g_key_file_set_boolean(gkeyfile, "remmina_pref", "hide_statusbar", remmina_pref.hide_statusbar);
+ g_key_file_set_boolean(gkeyfile, "remmina_pref", "small_toolbutton", remmina_pref.small_toolbutton);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "view_file_mode", remmina_pref.view_file_mode);
+ g_key_file_set_string(gkeyfile, "remmina_pref", "resolutions", remmina_pref.resolutions);
+ g_key_file_set_string(gkeyfile, "remmina_pref", "keystrokes", remmina_pref.keystrokes);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "main_width", remmina_pref.main_width);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "main_height", remmina_pref.main_height);
+ g_key_file_set_boolean(gkeyfile, "remmina_pref", "main_maximize", remmina_pref.main_maximize);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "main_sort_column_id", remmina_pref.main_sort_column_id);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "main_sort_order", remmina_pref.main_sort_order);
+ g_key_file_set_string(gkeyfile, "remmina_pref", "expanded_group", remmina_pref.expanded_group);
+ g_key_file_set_boolean(gkeyfile, "remmina_pref", "toolbar_pin_down", remmina_pref.toolbar_pin_down);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "sshtunnel_port", remmina_pref.sshtunnel_port);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "ssh_tcp_keepidle", remmina_pref.ssh_tcp_keepidle);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "ssh_tcp_keepintvl", remmina_pref.ssh_tcp_keepintvl);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "ssh_tcp_keepcnt", remmina_pref.ssh_tcp_keepcnt);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "ssh_tcp_usrtimeout", remmina_pref.ssh_tcp_usrtimeout);
+ g_key_file_set_boolean(gkeyfile, "remmina_pref", "applet_new_ontop", remmina_pref.applet_new_ontop);
+ g_key_file_set_boolean(gkeyfile, "remmina_pref", "applet_hide_count", remmina_pref.applet_hide_count);
+ g_key_file_set_boolean(gkeyfile, "remmina_pref", "applet_enable_avahi", remmina_pref.applet_enable_avahi);
+ g_key_file_set_boolean(gkeyfile, "remmina_pref", "disable_tray_icon", remmina_pref.disable_tray_icon);
+ g_key_file_set_boolean(gkeyfile, "remmina_pref", "dark_tray_icon", remmina_pref.dark_tray_icon);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "recent_maximum", remmina_pref.recent_maximum);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "default_mode", remmina_pref.default_mode);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "tab_mode", remmina_pref.tab_mode);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "fullscreen_toolbar_visibility", remmina_pref.fullscreen_toolbar_visibility);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "show_buttons_icons", remmina_pref.show_buttons_icons);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "show_menu_icons", remmina_pref.show_menu_icons);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "auto_scroll_step", remmina_pref.auto_scroll_step);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "hostkey", remmina_pref.hostkey);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "shortcutkey_fullscreen", remmina_pref.shortcutkey_fullscreen);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "shortcutkey_autofit", remmina_pref.shortcutkey_autofit);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "shortcutkey_nexttab", remmina_pref.shortcutkey_nexttab);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "shortcutkey_prevtab", remmina_pref.shortcutkey_prevtab);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "shortcutkey_scale", remmina_pref.shortcutkey_scale);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "shortcutkey_grab", remmina_pref.shortcutkey_grab);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "shortcutkey_viewonly", remmina_pref.shortcutkey_viewonly);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "shortcutkey_screenshot", remmina_pref.shortcutkey_screenshot);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "shortcutkey_minimize", remmina_pref.shortcutkey_minimize);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "shortcutkey_disconnect", remmina_pref.shortcutkey_disconnect);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "shortcutkey_toolbar", remmina_pref.shortcutkey_toolbar);
+ g_key_file_set_string(gkeyfile, "remmina_pref", "vte_font", remmina_pref.vte_font ? remmina_pref.vte_font : "");
+ g_key_file_set_boolean(gkeyfile, "remmina_pref", "vte_allow_bold_text", remmina_pref.vte_allow_bold_text);
+ g_key_file_set_integer(gkeyfile, "remmina_pref", "vte_lines", remmina_pref.vte_lines);
+ g_key_file_set_string(gkeyfile, "ssh_colors", "background", remmina_pref.background ? remmina_pref.background : "");
+ g_key_file_set_string(gkeyfile, "ssh_colors", "cursor", remmina_pref.cursor ? remmina_pref.cursor : "");
+ g_key_file_set_string(gkeyfile, "ssh_colors", "foreground", remmina_pref.foreground ? remmina_pref.foreground : "");
+ g_key_file_set_string(gkeyfile, "ssh_colors", "color0", remmina_pref.color0 ? remmina_pref.color0 : "");
+ g_key_file_set_string(gkeyfile, "ssh_colors", "color1", remmina_pref.color1 ? remmina_pref.color1 : "");
+ g_key_file_set_string(gkeyfile, "ssh_colors", "color2", remmina_pref.color2 ? remmina_pref.color2 : "");
+ g_key_file_set_string(gkeyfile, "ssh_colors", "color3", remmina_pref.color3 ? remmina_pref.color3 : "");
+ g_key_file_set_string(gkeyfile, "ssh_colors", "color4", remmina_pref.color4 ? remmina_pref.color4 : "");
+ g_key_file_set_string(gkeyfile, "ssh_colors", "color5", remmina_pref.color5 ? remmina_pref.color5 : "");
+ g_key_file_set_string(gkeyfile, "ssh_colors", "color6", remmina_pref.color6 ? remmina_pref.color6 : "");
+ g_key_file_set_string(gkeyfile, "ssh_colors", "color7", remmina_pref.color7 ? remmina_pref.color7 : "");
+ g_key_file_set_string(gkeyfile, "ssh_colors", "color8", remmina_pref.color8 ? remmina_pref.color8 : "");
+ g_key_file_set_string(gkeyfile, "ssh_colors", "color9", remmina_pref.color9 ? remmina_pref.color9 : "");
+ g_key_file_set_string(gkeyfile, "ssh_colors", "color10", remmina_pref.color10 ? remmina_pref.color10 : "");
+ g_key_file_set_string(gkeyfile, "ssh_colors", "color11", remmina_pref.color11 ? remmina_pref.color11 : "");
+ g_key_file_set_string(gkeyfile, "ssh_colors", "color12", remmina_pref.color12 ? remmina_pref.color12 : "");
+ g_key_file_set_string(gkeyfile, "ssh_colors", "color13", remmina_pref.color13 ? remmina_pref.color13 : "");
+ g_key_file_set_string(gkeyfile, "ssh_colors", "color14", remmina_pref.color14 ? remmina_pref.color14 : "");
+ g_key_file_set_string(gkeyfile, "ssh_colors", "color15", remmina_pref.color15 ? remmina_pref.color15 : "");
+
+ g_key_file_set_boolean(gkeyfile, "usage_stats", "periodic_usage_stats_permission_asked", remmina_pref.periodic_usage_stats_permission_asked);
+ g_key_file_set_boolean(gkeyfile, "usage_stats", "periodic_usage_stats_permitted", remmina_pref.periodic_usage_stats_permitted);
+ g_key_file_set_int64(gkeyfile, "usage_stats", "periodic_usage_stats_last_sent", remmina_pref.periodic_usage_stats_last_sent);
+ g_key_file_set_string(gkeyfile, "usage_stats", "periodic_usage_stats_uuid_prefix",
+ remmina_pref.periodic_usage_stats_uuid_prefix ? remmina_pref.periodic_usage_stats_uuid_prefix : "");
+
+ content = g_key_file_to_data(gkeyfile, &length, NULL);
+ g_file_set_contents(remmina_pref_file, content, length, NULL);
+
+ g_key_file_free(gkeyfile);
+ g_free(content);
+}
+
+void remmina_pref_add_recent(const gchar *protocol, const gchar *server)
+{
+ TRACE_CALL(__func__);
+ RemminaStringArray *array;
+ GKeyFile *gkeyfile;
+ gchar key[20];
+ gchar *val;
+ gchar *content;
+ gsize length;
+
+ if (remmina_pref.recent_maximum <= 0 || server == NULL || server[0] == 0)
+ return;
+
+ /* Load original value into memory */
+ gkeyfile = g_key_file_new();
+
+ g_key_file_load_from_file(gkeyfile, remmina_pref_file, G_KEY_FILE_NONE, NULL);
+
+ g_snprintf(key, sizeof(key), "recent_%s", protocol);
+ array = remmina_string_array_new_from_allocated_string(g_key_file_get_string(gkeyfile, "remmina_pref", key, NULL));
+
+ /* Add the new value */
+ remmina_string_array_remove(array, server);
+ while (array->len >= remmina_pref.recent_maximum) {
+ remmina_string_array_remove_index(array, 0);
+ }
+ remmina_string_array_add(array, server);
+
+ /* Save */
+ val = remmina_string_array_to_string(array);
+ g_key_file_set_string(gkeyfile, "remmina_pref", key, val);
+ g_free(val);
+
+ content = g_key_file_to_data(gkeyfile, &length, NULL);
+ g_file_set_contents(remmina_pref_file, content, length, NULL);
+
+ g_key_file_free(gkeyfile);
+ g_free(content);
+}
+
+gchar*
+remmina_pref_get_recent(const gchar *protocol)
+{
+ TRACE_CALL(__func__);
+ GKeyFile *gkeyfile;
+ gchar key[20];
+ gchar *val;
+
+ gkeyfile = g_key_file_new();
+
+ g_key_file_load_from_file(gkeyfile, remmina_pref_file, G_KEY_FILE_NONE, NULL);
+
+ g_snprintf(key, sizeof(key), "recent_%s", protocol);
+ val = g_key_file_get_string(gkeyfile, "remmina_pref", key, NULL);
+
+ g_key_file_free(gkeyfile);
+
+ return val;
+}
+
+void remmina_pref_clear_recent(void)
+{
+ TRACE_CALL(__func__);
+ GKeyFile *gkeyfile;
+ gchar **keys;
+ gint i;
+ gchar *content;
+ gsize length;
+
+ gkeyfile = g_key_file_new();
+
+ g_key_file_load_from_file(gkeyfile, remmina_pref_file, G_KEY_FILE_NONE, NULL);
+ keys = g_key_file_get_keys(gkeyfile, "remmina_pref", NULL, NULL);
+ if (keys) {
+ for (i = 0; keys[i]; i++) {
+ if (strncmp(keys[i], "recent_", 7) == 0) {
+ g_key_file_set_string(gkeyfile, "remmina_pref", keys[i], "");
+ }
+ }
+ g_strfreev(keys);
+ }
+
+ content = g_key_file_to_data(gkeyfile, &length, NULL);
+ g_file_set_contents(remmina_pref_file, content, length, NULL);
+
+ g_key_file_free(gkeyfile);
+ g_free(content);
+}
+
+guint remmina_pref_keymap_get_keyval(const gchar *keymap, guint keyval)
+{
+ TRACE_CALL(__func__);
+ guint *table;
+ gint i;
+
+ if (!keymap || keymap[0] == '\0')
+ return keyval;
+
+ table = (guint*)g_hash_table_lookup(remmina_keymap_table, keymap);
+ if (!table)
+ return keyval;
+ for (i = 0; table[i] > 0; i += 2) {
+ if (table[i] == keyval)
+ return table[i + 1];
+ }
+ return keyval;
+}
+
+gchar**
+remmina_pref_keymap_groups(void)
+{
+ TRACE_CALL(__func__);
+ GList *list;
+ guint len;
+ gchar **keys;
+ guint i;
+
+ list = g_hash_table_get_keys(remmina_keymap_table);
+ len = g_list_length(list);
+
+ keys = g_new0(gchar*, (len + 1) * 2 + 1);
+ keys[0] = g_strdup("");
+ keys[1] = g_strdup("");
+ for (i = 0; i < len; i++) {
+ keys[(i + 1) * 2] = g_strdup((gchar*)g_list_nth_data(list, i));
+ keys[(i + 1) * 2 + 1] = g_strdup((gchar*)g_list_nth_data(list, i));
+ }
+ g_list_free(list);
+
+ return keys;
+}
+
+gint remmina_pref_get_scale_quality(void)
+{
+ TRACE_CALL(__func__);
+ /* Paranoid programming */
+ if (remmina_pref.scale_quality < 0) {
+ remmina_pref.scale_quality = 0;
+ }
+ return remmina_pref.scale_quality;
+}
+
+gint remmina_pref_get_ssh_loglevel(void)
+{
+ TRACE_CALL(__func__);
+ return remmina_pref.ssh_loglevel;
+}
+
+gboolean remmina_pref_get_ssh_parseconfig(void)
+{
+ TRACE_CALL(__func__);
+ return remmina_pref.ssh_parseconfig;
+}
+
+gint remmina_pref_get_sshtunnel_port(void)
+{
+ TRACE_CALL(__func__);
+ return remmina_pref.sshtunnel_port;
+}
+
+gint remmina_pref_get_ssh_tcp_keepidle(void)
+{
+ TRACE_CALL(__func__);
+ return remmina_pref.ssh_tcp_keepidle;
+}
+
+gint remmina_pref_get_ssh_tcp_keepintvl(void)
+{
+ TRACE_CALL(__func__);
+ return remmina_pref.ssh_tcp_keepintvl;
+}
+
+gint remmina_pref_get_ssh_tcp_keepcnt(void)
+{
+ TRACE_CALL(__func__);
+ return remmina_pref.ssh_tcp_keepcnt;
+}
+
+gint remmina_pref_get_ssh_tcp_usrtimeout(void)
+{
+ TRACE_CALL(__func__);
+ return remmina_pref.ssh_tcp_usrtimeout;
+}
+
+void remmina_pref_set_value(const gchar *key, const gchar *value)
+{
+ TRACE_CALL(__func__);
+ GKeyFile *gkeyfile;
+ gchar *content;
+ gsize length;
+
+ gkeyfile = g_key_file_new();
+ g_key_file_load_from_file(gkeyfile, remmina_pref_file, G_KEY_FILE_NONE, NULL);
+ g_key_file_set_string(gkeyfile, "remmina_pref", key, value);
+ content = g_key_file_to_data(gkeyfile, &length, NULL);
+ g_file_set_contents(remmina_pref_file, content, length, NULL);
+
+ g_key_file_free(gkeyfile);
+ g_free(content);
+}
+
+gchar*
+remmina_pref_get_value(const gchar *key)
+{
+ TRACE_CALL(__func__);
+ GKeyFile *gkeyfile;
+ gchar *value;
+
+ gkeyfile = g_key_file_new();
+ g_key_file_load_from_file(gkeyfile, remmina_pref_file, G_KEY_FILE_NONE, NULL);
+ value = g_key_file_get_string(gkeyfile, "remmina_pref", key, NULL);
+ g_key_file_free(gkeyfile);
+
+ return value;
+}
diff --git a/src/remmina_pref.h b/src/remmina_pref.h
new file mode 100644
index 000000000..75e3be464
--- /dev/null
+++ b/src/remmina_pref.h
@@ -0,0 +1,235 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2011 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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.
+ *
+ */
+
+#pragma once
+
+/*
+ * Remmina Perference Loader
+ */
+
+G_BEGIN_DECLS
+
+enum {
+ REMMINA_VIEW_FILE_LIST,
+ REMMINA_VIEW_FILE_TREE
+};
+
+enum {
+ REMMINA_ACTION_CONNECT = 0,
+ REMMINA_ACTION_EDIT = 1
+};
+
+enum {
+ AUTO_MODE = 0,
+ SCROLLED_WINDOW_MODE = 1,
+ FULLSCREEN_MODE = 2,
+ SCROLLED_FULLSCREEN_MODE = 3,
+ VIEWPORT_FULLSCREEN_MODE = 4
+};
+
+enum {
+ FLOATING_TOOLBAR_PLACEMENT_TOP = 0,
+ FLOATING_TOOLBAR_PLACEMENT_BOTTOM = 1
+};
+
+enum {
+ TOOLBAR_PLACEMENT_TOP = 0,
+ TOOLBAR_PLACEMENT_RIGHT = 1,
+ TOOLBAR_PLACEMENT_BOTTOM = 2,
+ TOOLBAR_PLACEMENT_LEFT = 3
+};
+
+enum {
+ REMMINA_TAB_BY_GROUP = 0,
+ REMMINA_TAB_BY_PROTOCOL = 1,
+ REMMINA_TAB_ALL = 2,
+ REMMINA_TAB_NONE = 3
+};
+
+enum {
+ FLOATING_TOOLBAR_VISIBILITY_PEEKING = 0,
+ FLOATING_TOOLBAR_VISIBILITY_INVISIBLE = 1, //"Invisible" corresponds to the "Hidden" option in the drop-down
+ FLOATING_TOOLBAR_VISIBILITY_DISABLE = 2
+};
+
+typedef struct _RemminaPref {
+ /* In RemminaPrefDialog options tab */
+ gboolean save_view_mode;
+ gint default_action;
+ gint scale_quality;
+ const gchar *screenshot_path;
+ gint auto_scroll_step;
+ gint recent_maximum;
+ gchar *resolutions;
+ gchar *keystrokes;
+ /* In RemminaPrefDialog appearance tab */
+ gboolean fullscreen_on_auto;
+ gboolean always_show_tab;
+ gboolean hide_connection_toolbar;
+ gint default_mode;
+ gint tab_mode;
+ gint fullscreen_toolbar_visibility;
+ gint show_buttons_icons;
+ gint show_menu_icons;
+ /* In RemminaPrefDialog applet tab */
+ gboolean applet_new_ontop;
+ gboolean applet_hide_count;
+ gboolean disable_tray_icon;
+ gboolean dark_tray_icon;
+ /* In RemminaPrefDialog SSH Option tab */
+ gint ssh_loglevel;
+ gboolean ssh_parseconfig;
+ gint sshtunnel_port;
+ gint ssh_tcp_keepidle;
+ gint ssh_tcp_keepintvl;
+ gint ssh_tcp_keepcnt;
+ gint ssh_tcp_usrtimeout;
+ /* In RemminaPrefDialog keyboard tab */
+ guint hostkey;
+ guint shortcutkey_fullscreen;
+ guint shortcutkey_autofit;
+ guint shortcutkey_prevtab;
+ guint shortcutkey_nexttab;
+ guint shortcutkey_dynres;
+ guint shortcutkey_scale;
+ guint shortcutkey_grab;
+ guint shortcutkey_viewonly;
+ guint shortcutkey_screenshot;
+ guint shortcutkey_minimize;
+ guint shortcutkey_disconnect;
+ guint shortcutkey_toolbar;
+ /* In RemminaPrefDialog terminal tab */
+ gchar *vte_font;
+ gboolean vte_allow_bold_text;
+ gboolean vte_system_colors;
+ gint vte_lines;
+ guint vte_shortcutkey_copy;
+ guint vte_shortcutkey_paste;
+ guint vte_shortcutkey_select_all;
+ /* In View menu */
+ gboolean hide_toolbar;
+ gboolean hide_statusbar;
+ gboolean small_toolbutton;
+ gint view_file_mode;
+ /* In tray icon */
+ gboolean applet_enable_avahi;
+ /* Auto */
+ gint main_width;
+ gint main_height;
+ gboolean main_maximize;
+ gint main_sort_column_id;
+ gint main_sort_order;
+ gchar *expanded_group;
+ gboolean toolbar_pin_down;
+ gint floating_toolbar_placement;
+ gint toolbar_placement;
+ gboolean prevent_snap_welcome_message;
+
+ /* Crypto */
+ gchar *secret;
+
+ /* UID */
+ gchar *uid;
+
+ /* Color palette for VTE terminal */
+ gchar *background;
+ gchar *cursor;
+ gchar *foreground;
+ gchar *color0;
+ gchar *color1;
+ gchar *color2;
+ gchar *color3;
+ gchar *color4;
+ gchar *color5;
+ gchar *color6;
+ gchar *color7;
+ gchar *color8;
+ gchar *color9;
+ gchar *color10;
+ gchar *color11;
+ gchar *color12;
+ gchar *color13;
+ gchar *color14;
+ gchar *color15;
+
+ /* Usage stats */
+ gboolean periodic_usage_stats_permission_asked;
+ gboolean periodic_usage_stats_permitted;
+ glong periodic_usage_stats_last_sent;
+ gchar *periodic_usage_stats_uuid_prefix;
+ gchar *last_success;
+
+
+} RemminaPref;
+
+#define DEFAULT_SSH_PARSECONFIG TRUE
+#define DEFAULT_SSHTUNNEL_PORT 4732
+#define DEFAULT_SSH_PORT 22
+#define DEFAULT_SSH_LOGLEVEL 1
+#define SSH_SOCKET_TCP_KEEPIDLE 20
+#define SSH_SOCKET_TCP_KEEPINTVL 10
+#define SSH_SOCKET_TCP_KEEPCNT 3
+#define SSH_SOCKET_TCP_USER_TIMEOUT 60000 // 60 seconds
+
+extern const gchar *default_resolutions;
+extern gchar *remmina_pref_file;
+extern gchar *remmina_colors_file;
+extern RemminaPref remmina_pref;
+
+void remmina_pref_init(void);
+void remmina_pref_save(void);
+
+void remmina_pref_add_recent(const gchar *protocol, const gchar *server);
+gchar* remmina_pref_get_recent(const gchar *protocol);
+void remmina_pref_clear_recent(void);
+
+guint remmina_pref_keymap_get_keyval(const gchar *keymap, guint keyval);
+gchar** remmina_pref_keymap_groups(void);
+
+gint remmina_pref_get_scale_quality(void);
+gint remmina_pref_get_ssh_loglevel(void);
+gboolean remmina_pref_get_ssh_parseconfig(void);
+gint remmina_pref_get_sshtunnel_port(void);
+gint remmina_pref_get_ssh_tcp_keepidle(void);
+gint remmina_pref_get_ssh_tcp_keepintvl(void);
+gint remmina_pref_get_ssh_tcp_keepcnt(void);
+gint remmina_pref_get_ssh_tcp_usrtimeout(void);
+
+void remmina_pref_set_value(const gchar *key, const gchar *value);
+gchar* remmina_pref_get_value(const gchar *key);
+
+G_END_DECLS
+
diff --git a/src/remmina_pref_dialog.c b/src/remmina_pref_dialog.c
new file mode 100644
index 000000000..e5678c86e
--- /dev/null
+++ b/src/remmina_pref_dialog.c
@@ -0,0 +1,625 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2011 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <stdlib.h>
+#include "config.h"
+#if defined (HAVE_LIBSSH) && defined (HAVE_LIBVTE)
+#include <vte/vte.h>
+#endif
+#include "remmina_public.h"
+#include "remmina_string_list.h"
+#include "remmina_widget_pool.h"
+#include "remmina_key_chooser.h"
+#include "remmina_plugin_manager.h"
+#include "remmina_icon.h"
+#include "remmina_pref.h"
+#include "remmina_pref_dialog.h"
+#include "remmina/remmina_trace_calls.h"
+
+static RemminaPrefDialog *remmina_pref_dialog;
+
+#define GET_OBJECT(object_name) gtk_builder_get_object(remmina_pref_dialog->builder, object_name)
+
+/* Show a key chooser dialog */
+void remmina_pref_dialog_on_key_chooser(GtkWidget *widget, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ RemminaKeyChooserArguments *arguments;
+
+ g_return_if_fail(GTK_IS_BUTTON(widget));
+
+ arguments = remmina_key_chooser_new(GTK_WINDOW(remmina_pref_dialog->dialog), FALSE);
+ if (arguments->response != GTK_RESPONSE_CANCEL && arguments->response != GTK_RESPONSE_DELETE_EVENT) {
+ gchar *val = remmina_key_chooser_get_value(arguments->keyval, arguments->state);
+ gtk_button_set_label(GTK_BUTTON(widget), val);
+ g_free(val);
+ }
+ g_free(arguments);
+}
+
+/* Show the available resolutions list dialog */
+void remmina_pref_on_button_resolutions_clicked(GtkWidget *widget, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ GtkDialog *dialog = remmina_string_list_new(FALSE, NULL);
+ remmina_string_list_set_validation_func(remmina_public_resolution_validation_func);
+ remmina_string_list_set_text(remmina_pref.resolutions, TRUE);
+ remmina_string_list_set_titles(_("Resolutions"), _("Configure the available resolutions"));
+ gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(remmina_pref_dialog->dialog));
+ gtk_dialog_run(dialog);
+ g_free(remmina_pref.resolutions);
+ remmina_pref.resolutions = remmina_string_list_get_text();
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+}
+
+/* Re-initialize the remmina_pref_init to reload the color scheme when a color scheme
+ * file is selected*/
+void remmina_pref_on_color_scheme_selected(GtkWidget *widget, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ gchar *sourcepath;
+ gchar *remmina_dir;
+ gchar *destpath;
+ GFile *source;
+ GFile *destination;
+
+ sourcepath = gtk_file_chooser_get_filename(remmina_pref_dialog->button_term_cs);
+ source = g_file_new_for_path(sourcepath);
+
+ remmina_dir = g_build_path( "/", g_get_user_config_dir(), g_get_prgname(), NULL);
+ /* /home/foo/.config/remmina */
+ destpath = g_strdup_printf("%s/remmina.colors", remmina_dir);
+ destination = g_file_new_for_path(destpath);
+
+ if (g_file_test(sourcepath, G_FILE_TEST_IS_REGULAR)) {
+ g_file_copy( source,
+ destination,
+ G_FILE_COPY_OVERWRITE,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ /* Here we should reinitialize the widget */
+ }
+}
+
+void remmina_pref_dialog_clear_recent(GtkWidget *widget, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ GtkDialog *dialog;
+
+ remmina_pref_clear_recent();
+ dialog = GTK_DIALOG(gtk_message_dialog_new(GTK_WINDOW(remmina_pref_dialog->dialog),
+ GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, GTK_BUTTONS_OK,
+ _("Recent lists cleared.")));
+ gtk_dialog_run(dialog);
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+}
+
+/* Configure custom keystrokes to send to the plugins */
+void remmina_pref_on_button_keystrokes_clicked(GtkWidget *widget, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ GtkDialog *dialog = remmina_string_list_new(TRUE, STRING_DELIMITOR2);
+ remmina_string_list_set_text(remmina_pref.keystrokes, TRUE);
+ remmina_string_list_set_titles(_("Keystrokes"), _("Configure the keystrokes"));
+ gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(remmina_pref_dialog->dialog));
+ gtk_dialog_run(dialog);
+ g_free(remmina_pref.keystrokes);
+ remmina_pref.keystrokes = remmina_string_list_get_text();
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+}
+
+void remmina_pref_dialog_on_close_clicked(GtkWidget *widget, RemminaPrefDialog *dialog)
+{
+ TRACE_CALL(__func__);
+ gtk_widget_destroy(GTK_WIDGET(remmina_pref_dialog->dialog));
+}
+
+void remmina_pref_on_dialog_destroy(GtkWidget *widget, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ gboolean b;
+ GdkRGBA color;
+
+ remmina_pref.save_view_mode = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(remmina_pref_dialog->checkbutton_options_remember_last_view_mode));
+ remmina_pref.fullscreen_on_auto = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(remmina_pref_dialog->checkbutton_appearance_fullscreen_on_auto));
+ remmina_pref.always_show_tab = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(remmina_pref_dialog->checkbutton_appearance_show_tabs));
+ remmina_pref.hide_connection_toolbar = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(remmina_pref_dialog->checkbutton_appearance_hide_toolbar));
+
+ b = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(remmina_pref_dialog->checkbutton_permit_send_stats));
+ if (remmina_pref.periodic_usage_stats_permitted) {
+ if (!b) remmina_pref.periodic_usage_stats_permission_asked = FALSE;
+ }
+ else {
+ if (b) remmina_pref.periodic_usage_stats_permission_asked = TRUE;
+ }
+ remmina_pref.periodic_usage_stats_permitted = b;
+
+ remmina_pref.default_action = gtk_combo_box_get_active(remmina_pref_dialog->comboboxtext_options_double_click);
+ remmina_pref.default_mode = gtk_combo_box_get_active(remmina_pref_dialog->comboboxtext_appearance_view_mode);
+ remmina_pref.tab_mode = gtk_combo_box_get_active(remmina_pref_dialog->comboboxtext_appearance_tab_interface);
+ remmina_pref.fullscreen_toolbar_visibility = gtk_combo_box_get_active(remmina_pref_dialog->comboboxtext_appearance_fullscreen_toolbar_visibility);
+ remmina_pref.show_buttons_icons = gtk_combo_box_get_active(remmina_pref_dialog->comboboxtext_appearance_show_buttons_icons);
+ remmina_pref.show_menu_icons = gtk_combo_box_get_active(remmina_pref_dialog->comboboxtext_appearance_show_menu_icons);
+ remmina_pref.scale_quality = gtk_combo_box_get_active(remmina_pref_dialog->comboboxtext_options_scale_quality);
+ remmina_pref.screenshot_path = gtk_file_chooser_get_filename(remmina_pref_dialog->filechooserbutton_options_screenshots_path);
+ remmina_pref.ssh_loglevel = gtk_combo_box_get_active(remmina_pref_dialog->comboboxtext_options_ssh_loglevel);
+ remmina_pref.sshtunnel_port = atoi(gtk_entry_get_text(remmina_pref_dialog->entry_options_ssh_port));
+ if (remmina_pref.sshtunnel_port <= 0)
+ remmina_pref.sshtunnel_port = DEFAULT_SSHTUNNEL_PORT;
+ remmina_pref.ssh_tcp_keepidle = atoi(gtk_entry_get_text(remmina_pref_dialog->entry_options_ssh_tcp_keepidle));
+ if (remmina_pref.ssh_tcp_keepidle <= 0)
+ remmina_pref.ssh_tcp_keepidle = SSH_SOCKET_TCP_KEEPIDLE;
+ remmina_pref.ssh_tcp_keepintvl = atoi(gtk_entry_get_text(remmina_pref_dialog->entry_options_ssh_tcp_keepintvl));
+ if (remmina_pref.ssh_tcp_keepintvl <= 0)
+ remmina_pref.ssh_tcp_keepintvl = SSH_SOCKET_TCP_KEEPINTVL;
+ remmina_pref.ssh_tcp_keepcnt = atoi(gtk_entry_get_text(remmina_pref_dialog->entry_options_ssh_tcp_keepcnt));
+ if (remmina_pref.ssh_tcp_keepcnt <= 0)
+ remmina_pref.ssh_tcp_keepcnt = SSH_SOCKET_TCP_KEEPCNT;
+ remmina_pref.ssh_tcp_usrtimeout = atoi(gtk_entry_get_text(remmina_pref_dialog->entry_options_ssh_tcp_usrtimeout));
+ if (remmina_pref.ssh_tcp_usrtimeout <= 0)
+ remmina_pref.ssh_tcp_usrtimeout = SSH_SOCKET_TCP_USER_TIMEOUT;
+ remmina_pref.ssh_parseconfig = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(remmina_pref_dialog->checkbutton_options_ssh_parseconfig));
+
+ remmina_pref.auto_scroll_step = atoi(gtk_entry_get_text(remmina_pref_dialog->entry_options_scroll));
+ if (remmina_pref.auto_scroll_step < 10)
+ remmina_pref.auto_scroll_step = 10;
+ else if (remmina_pref.auto_scroll_step > 500)
+ remmina_pref.auto_scroll_step = 500;
+
+ remmina_pref.recent_maximum = atoi(gtk_entry_get_text(remmina_pref_dialog->entry_options_recent_items));
+ if (remmina_pref.recent_maximum < 0)
+ remmina_pref.recent_maximum = 0;
+
+ remmina_pref.applet_new_ontop = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(remmina_pref_dialog->checkbutton_applet_new_connection_on_top));
+ remmina_pref.applet_hide_count = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(remmina_pref_dialog->checkbutton_applet_hide_totals));
+ remmina_pref.dark_tray_icon = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(remmina_pref_dialog->checkbutton_applet_light_tray));
+
+ b = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(remmina_pref_dialog->checkbutton_applet_disable_tray));
+ if (remmina_pref.disable_tray_icon != b) {
+ remmina_pref.disable_tray_icon = b;
+ remmina_icon_init();
+ }
+ if (b) {
+ b = FALSE;
+ }else {
+ b = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(remmina_pref_dialog->checkbutton_applet_start_in_tray));
+ }
+ remmina_icon_set_autostart(b);
+
+ remmina_pref.hostkey = remmina_key_chooser_get_keyval(gtk_button_get_label(remmina_pref_dialog->button_keyboard_host_key));
+ remmina_pref.shortcutkey_fullscreen = remmina_key_chooser_get_keyval(gtk_button_get_label(remmina_pref_dialog->button_keyboard_fullscreen));
+ remmina_pref.shortcutkey_autofit = remmina_key_chooser_get_keyval(gtk_button_get_label(remmina_pref_dialog->button_keyboard_auto_fit));
+ remmina_pref.shortcutkey_prevtab = remmina_key_chooser_get_keyval(gtk_button_get_label(remmina_pref_dialog->button_keyboard_switch_tab_left));
+ remmina_pref.shortcutkey_nexttab = remmina_key_chooser_get_keyval(gtk_button_get_label(remmina_pref_dialog->button_keyboard_switch_tab_right));
+ remmina_pref.shortcutkey_scale = remmina_key_chooser_get_keyval(gtk_button_get_label(remmina_pref_dialog->button_keyboard_scaled));
+ remmina_pref.shortcutkey_grab = remmina_key_chooser_get_keyval(gtk_button_get_label(remmina_pref_dialog->button_keyboard_grab_keyboard));
+ remmina_pref.shortcutkey_screenshot = remmina_key_chooser_get_keyval(gtk_button_get_label(remmina_pref_dialog->button_keyboard_screenshot));
+ remmina_pref.shortcutkey_viewonly = remmina_key_chooser_get_keyval(gtk_button_get_label(remmina_pref_dialog->button_keyboard_viewonly));
+ remmina_pref.shortcutkey_minimize = remmina_key_chooser_get_keyval(gtk_button_get_label(remmina_pref_dialog->button_keyboard_minimize));
+ remmina_pref.shortcutkey_disconnect = remmina_key_chooser_get_keyval(gtk_button_get_label(remmina_pref_dialog->button_keyboard_disconnect));
+ remmina_pref.shortcutkey_toolbar = remmina_key_chooser_get_keyval(gtk_button_get_label(remmina_pref_dialog->button_keyboard_toolbar));
+
+ g_free(remmina_pref.vte_font);
+ if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(remmina_pref_dialog->checkbutton_terminal_font_system))) {
+ remmina_pref.vte_font = NULL;
+ }else {
+ remmina_pref.vte_font = g_strdup(gtk_font_chooser_get_font(GTK_FONT_CHOOSER(remmina_pref_dialog->fontbutton_terminal_font)));
+ }
+ remmina_pref.vte_allow_bold_text = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(remmina_pref_dialog->checkbutton_terminal_bold));
+ gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_foreground), &color);
+ remmina_pref.foreground = gdk_rgba_to_string(&color);
+ gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_background), &color);
+ remmina_pref.background = gdk_rgba_to_string(&color);
+ gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_cursor), &color);
+ remmina_pref.cursor = gdk_rgba_to_string(&color);
+ gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_color0), &color);
+ remmina_pref.color0 = gdk_rgba_to_string(&color);
+ gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_color1), &color);
+ remmina_pref.color1 = gdk_rgba_to_string(&color);
+ gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_color2), &color);
+ remmina_pref.color2 = gdk_rgba_to_string(&color);
+ gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_color3), &color);
+ remmina_pref.color3 = gdk_rgba_to_string(&color);
+ gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_color4), &color);
+ remmina_pref.color4 = gdk_rgba_to_string(&color);
+ gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_color5), &color);
+ remmina_pref.color5 = gdk_rgba_to_string(&color);
+ gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_color6), &color);
+ remmina_pref.color6 = gdk_rgba_to_string(&color);
+ gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_color7), &color);
+ remmina_pref.color7 = gdk_rgba_to_string(&color);
+ gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_color8), &color);
+ remmina_pref.color8 = gdk_rgba_to_string(&color);
+ gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_color9), &color);
+ remmina_pref.color9 = gdk_rgba_to_string(&color);
+ gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_color10), &color);
+ remmina_pref.color10 = gdk_rgba_to_string(&color);
+ gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_color11), &color);
+ remmina_pref.color11 = gdk_rgba_to_string(&color);
+ gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_color12), &color);
+ remmina_pref.color12 = gdk_rgba_to_string(&color);
+ gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_color13), &color);
+ remmina_pref.color13 = gdk_rgba_to_string(&color);
+ gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_color14), &color);
+ remmina_pref.color14 = gdk_rgba_to_string(&color);
+ gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_color15), &color);
+ remmina_pref.color15 = gdk_rgba_to_string(&color);
+ remmina_pref.vte_lines = atoi(gtk_entry_get_text(remmina_pref_dialog->entry_scrollback_lines));
+ remmina_pref.vte_lines = atoi(gtk_entry_get_text(remmina_pref_dialog->entry_scrollback_lines));
+ remmina_pref.vte_shortcutkey_copy = remmina_key_chooser_get_keyval(gtk_button_get_label(remmina_pref_dialog->button_keyboard_copy));
+ remmina_pref.vte_shortcutkey_paste = remmina_key_chooser_get_keyval(gtk_button_get_label(remmina_pref_dialog->button_keyboard_paste));
+ remmina_pref.vte_shortcutkey_select_all = remmina_key_chooser_get_keyval(gtk_button_get_label(remmina_pref_dialog->button_keyboard_select_all));
+
+ remmina_pref_save();
+ remmina_pref_init();
+
+ remmina_pref_dialog->dialog = NULL;
+}
+
+static gboolean remmina_pref_dialog_add_pref_plugin(gchar *name, RemminaPlugin *plugin, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ RemminaPrefPlugin *pref_plugin;
+ GtkWidget *vbox;
+ GtkWidget *widget;
+
+ pref_plugin = (RemminaPrefPlugin*)plugin;
+
+ widget = gtk_label_new(pref_plugin->pref_label);
+ gtk_widget_show(widget);
+
+ vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+ gtk_widget_show(vbox);
+ gtk_notebook_append_page(GTK_NOTEBOOK(remmina_pref_dialog->notebook_preferences), vbox, widget);
+
+ widget = pref_plugin->get_pref_body();
+ gtk_box_pack_start(GTK_BOX(vbox), widget, FALSE, FALSE, 0);
+
+ return FALSE;
+}
+
+void remmina_pref_dialog_vte_font_on_toggled(GtkWidget *widget, RemminaPrefDialog *dialog)
+{
+ TRACE_CALL(__func__);
+ gtk_widget_set_sensitive(GTK_WIDGET(remmina_pref_dialog->fontbutton_terminal_font), !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
+}
+
+void remmina_pref_dialog_disable_tray_icon_on_toggled(GtkWidget *widget, RemminaPrefDialog *dialog)
+{
+ TRACE_CALL(__func__);
+ gboolean b;
+
+ b = !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
+
+ gtk_widget_set_sensitive(GTK_WIDGET(remmina_pref_dialog->checkbutton_applet_start_in_tray), b);
+}
+
+/* Helper function for remmina_pref_dialog_init() */
+static void remmina_pref_dialog_set_button_label(GtkButton *button, guint keyval)
+{
+ gchar *val;
+
+ val = remmina_key_chooser_get_value(keyval, 0);
+ gtk_button_set_label(button, val);
+ g_free(val);
+}
+
+/* Remmina preferences initialization */
+static void remmina_pref_dialog_init(void)
+{
+ TRACE_CALL(__func__);
+ gchar buf[100];
+ GdkRGBA color;
+
+#if !defined (HAVE_LIBSSH) || !defined (HAVE_LIBVTE)
+ GtkWidget *align;
+#endif
+
+#if !defined (HAVE_LIBVTE)
+ align = GTK_WIDGET(GET_OBJECT("alignment_terminal"));
+ gtk_widget_set_sensitive(align, FALSE);
+#endif
+
+#if !defined (HAVE_LIBSSH)
+ align = GTK_WIDGET(GET_OBJECT("alignment_ssh"));
+ gtk_widget_set_sensitive(align, FALSE);
+#endif
+
+ gtk_dialog_set_default_response(GTK_DIALOG(remmina_pref_dialog->dialog), GTK_RESPONSE_CLOSE);
+
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(remmina_pref_dialog->checkbutton_options_remember_last_view_mode), remmina_pref.save_view_mode);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(remmina_pref_dialog->checkbutton_appearance_fullscreen_on_auto), remmina_pref.fullscreen_on_auto);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(remmina_pref_dialog->checkbutton_appearance_show_tabs), remmina_pref.always_show_tab);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(remmina_pref_dialog->checkbutton_appearance_hide_toolbar), remmina_pref.hide_connection_toolbar);
+
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(remmina_pref_dialog->checkbutton_permit_send_stats), remmina_pref.periodic_usage_stats_permitted);
+
+ g_snprintf(buf, sizeof(buf), "%i", remmina_pref.sshtunnel_port);
+ gtk_entry_set_text(remmina_pref_dialog->entry_options_ssh_port, buf);
+ g_snprintf(buf, sizeof(buf), "%i", remmina_pref.ssh_tcp_keepidle);
+ gtk_entry_set_text(remmina_pref_dialog->entry_options_ssh_tcp_keepidle, buf);
+ g_snprintf(buf, sizeof(buf), "%i", remmina_pref.ssh_tcp_keepintvl);
+ gtk_entry_set_text(remmina_pref_dialog->entry_options_ssh_tcp_keepintvl, buf);
+ g_snprintf(buf, sizeof(buf), "%i", remmina_pref.ssh_tcp_keepcnt);
+ gtk_entry_set_text(remmina_pref_dialog->entry_options_ssh_tcp_keepcnt, buf);
+ g_snprintf(buf, sizeof(buf), "%i", remmina_pref.ssh_tcp_usrtimeout);
+ gtk_entry_set_text(remmina_pref_dialog->entry_options_ssh_tcp_usrtimeout, buf);
+ g_snprintf(buf, sizeof(buf), "%i", remmina_pref.auto_scroll_step);
+ gtk_entry_set_text(remmina_pref_dialog->entry_options_scroll, buf);
+ g_snprintf(buf, sizeof(buf), "%i", remmina_pref.recent_maximum);
+ gtk_entry_set_text(remmina_pref_dialog->entry_options_recent_items, buf);
+
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(remmina_pref_dialog->checkbutton_applet_new_connection_on_top), remmina_pref.applet_new_ontop);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(remmina_pref_dialog->checkbutton_applet_hide_totals), remmina_pref.applet_hide_count);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(remmina_pref_dialog->checkbutton_applet_disable_tray), remmina_pref.disable_tray_icon);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(remmina_pref_dialog->checkbutton_applet_light_tray), remmina_pref.dark_tray_icon);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(remmina_pref_dialog->checkbutton_applet_start_in_tray), remmina_icon_is_autostart());
+ gtk_widget_set_sensitive(GTK_WIDGET(remmina_pref_dialog->checkbutton_applet_start_in_tray), !remmina_pref.disable_tray_icon);
+
+ remmina_pref_dialog_set_button_label(remmina_pref_dialog->button_keyboard_host_key, remmina_pref.hostkey);
+ remmina_pref_dialog_set_button_label(remmina_pref_dialog->button_keyboard_fullscreen, remmina_pref.shortcutkey_fullscreen);
+ remmina_pref_dialog_set_button_label(remmina_pref_dialog->button_keyboard_auto_fit, remmina_pref.shortcutkey_autofit);
+ remmina_pref_dialog_set_button_label(remmina_pref_dialog->button_keyboard_switch_tab_left, remmina_pref.shortcutkey_prevtab);
+ remmina_pref_dialog_set_button_label(remmina_pref_dialog->button_keyboard_switch_tab_right, remmina_pref.shortcutkey_nexttab);
+ remmina_pref_dialog_set_button_label(remmina_pref_dialog->button_keyboard_scaled, remmina_pref.shortcutkey_scale);
+ remmina_pref_dialog_set_button_label(remmina_pref_dialog->button_keyboard_grab_keyboard, remmina_pref.shortcutkey_grab);
+ remmina_pref_dialog_set_button_label(remmina_pref_dialog->button_keyboard_screenshot, remmina_pref.shortcutkey_screenshot);
+ remmina_pref_dialog_set_button_label(remmina_pref_dialog->button_keyboard_viewonly, remmina_pref.shortcutkey_viewonly);
+ remmina_pref_dialog_set_button_label(remmina_pref_dialog->button_keyboard_minimize, remmina_pref.shortcutkey_minimize);
+ remmina_pref_dialog_set_button_label(remmina_pref_dialog->button_keyboard_disconnect, remmina_pref.shortcutkey_disconnect);
+ remmina_pref_dialog_set_button_label(remmina_pref_dialog->button_keyboard_toolbar, remmina_pref.shortcutkey_toolbar);
+
+ if (!(remmina_pref.vte_font && remmina_pref.vte_font[0])) {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(remmina_pref_dialog->checkbutton_terminal_font_system), TRUE);
+ }
+ if (remmina_pref.vte_font && remmina_pref.vte_font[0]) {
+ gtk_font_chooser_set_font(GTK_FONT_CHOOSER(remmina_pref_dialog->fontbutton_terminal_font), remmina_pref.vte_font);
+ }else {
+ gtk_font_chooser_set_font(GTK_FONT_CHOOSER(remmina_pref_dialog->fontbutton_terminal_font), "Monospace 12");
+ gtk_widget_set_sensitive(GTK_WIDGET(remmina_pref_dialog->fontbutton_terminal_font), FALSE);
+ }
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(remmina_pref_dialog->checkbutton_terminal_bold), remmina_pref.vte_allow_bold_text);
+
+ /* Foreground color option */
+ gdk_rgba_parse(&color, remmina_pref.foreground);
+ gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_foreground), &color);
+ /* Background color option */
+ gdk_rgba_parse(&color, remmina_pref.background);
+ gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_background), &color);
+ /* Cursor color option */
+ gdk_rgba_parse(&color, remmina_pref.cursor);
+ gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_cursor), &color);
+ /* 16 colors */
+ gdk_rgba_parse(&color, remmina_pref.color0);
+ gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_color0), &color);
+ gdk_rgba_parse(&color, remmina_pref.color1);
+ gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_color1), &color);
+ gdk_rgba_parse(&color, remmina_pref.color2);
+ gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_color2), &color);
+ gdk_rgba_parse(&color, remmina_pref.color3);
+ gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_color3), &color);
+ gdk_rgba_parse(&color, remmina_pref.color4);
+ gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_color4), &color);
+ gdk_rgba_parse(&color, remmina_pref.color5);
+ gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_color5), &color);
+ gdk_rgba_parse(&color, remmina_pref.color6);
+ gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_color6), &color);
+ gdk_rgba_parse(&color, remmina_pref.color7);
+ gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_color7), &color);
+ gdk_rgba_parse(&color, remmina_pref.color8);
+ gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_color8), &color);
+ gdk_rgba_parse(&color, remmina_pref.color9);
+ gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_color9), &color);
+ gdk_rgba_parse(&color, remmina_pref.color10);
+ gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_color10), &color);
+ gdk_rgba_parse(&color, remmina_pref.color11);
+ gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_color11), &color);
+ gdk_rgba_parse(&color, remmina_pref.color12);
+ gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_color12), &color);
+ gdk_rgba_parse(&color, remmina_pref.color13);
+ gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_color13), &color);
+ gdk_rgba_parse(&color, remmina_pref.color14);
+ gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_color14), &color);
+ gdk_rgba_parse(&color, remmina_pref.color15);
+ gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(remmina_pref_dialog->colorbutton_color15), &color);
+#if defined (HAVE_LIBSSH) && defined (HAVE_LIBVTE)
+#if !VTE_CHECK_VERSION(0, 38, 0)
+ /* Disable color scheme buttons if old version of VTE */
+ gtk_widget_set_sensitive(GTK_WIDGET(remmina_pref_dialog->colorbutton_cursor), FALSE);
+ gtk_widget_set_sensitive(GTK_WIDGET(remmina_pref_dialog->colorbutton_color0), FALSE);
+ gtk_widget_set_sensitive(GTK_WIDGET(remmina_pref_dialog->colorbutton_color1), FALSE);
+ gtk_widget_set_sensitive(GTK_WIDGET(remmina_pref_dialog->colorbutton_color2), FALSE);
+ gtk_widget_set_sensitive(GTK_WIDGET(remmina_pref_dialog->colorbutton_color3), FALSE);
+ gtk_widget_set_sensitive(GTK_WIDGET(remmina_pref_dialog->colorbutton_color4), FALSE);
+ gtk_widget_set_sensitive(GTK_WIDGET(remmina_pref_dialog->colorbutton_color5), FALSE);
+ gtk_widget_set_sensitive(GTK_WIDGET(remmina_pref_dialog->colorbutton_color6), FALSE);
+ gtk_widget_set_sensitive(GTK_WIDGET(remmina_pref_dialog->colorbutton_color7), FALSE);
+ gtk_widget_set_sensitive(GTK_WIDGET(remmina_pref_dialog->colorbutton_color8), FALSE);
+ gtk_widget_set_sensitive(GTK_WIDGET(remmina_pref_dialog->colorbutton_color9), FALSE);
+ gtk_widget_set_sensitive(GTK_WIDGET(remmina_pref_dialog->colorbutton_color10), FALSE);
+ gtk_widget_set_sensitive(GTK_WIDGET(remmina_pref_dialog->colorbutton_color11), FALSE);
+ gtk_widget_set_sensitive(GTK_WIDGET(remmina_pref_dialog->colorbutton_color12), FALSE);
+ gtk_widget_set_sensitive(GTK_WIDGET(remmina_pref_dialog->colorbutton_color13), FALSE);
+ gtk_widget_set_sensitive(GTK_WIDGET(remmina_pref_dialog->colorbutton_color14), FALSE);
+ gtk_widget_set_sensitive(GTK_WIDGET(remmina_pref_dialog->colorbutton_color15), FALSE);
+#endif
+#endif
+
+ g_snprintf(buf, sizeof(buf), "%i", remmina_pref.vte_lines);
+ gtk_entry_set_text(remmina_pref_dialog->entry_scrollback_lines, buf);
+
+ gtk_combo_box_set_active(remmina_pref_dialog->comboboxtext_options_double_click, remmina_pref.default_action);
+ gtk_combo_box_set_active(remmina_pref_dialog->comboboxtext_appearance_view_mode, remmina_pref.default_mode);
+ gtk_combo_box_set_active(remmina_pref_dialog->comboboxtext_appearance_tab_interface, remmina_pref.tab_mode);
+ gtk_combo_box_set_active(remmina_pref_dialog->comboboxtext_appearance_fullscreen_toolbar_visibility, remmina_pref.fullscreen_toolbar_visibility);
+ gtk_combo_box_set_active(remmina_pref_dialog->comboboxtext_appearance_show_buttons_icons, remmina_pref.show_buttons_icons);
+ gtk_combo_box_set_active(remmina_pref_dialog->comboboxtext_appearance_show_menu_icons, remmina_pref.show_menu_icons);
+ gtk_combo_box_set_active(remmina_pref_dialog->comboboxtext_options_scale_quality, remmina_pref.scale_quality);
+ gtk_combo_box_set_active(remmina_pref_dialog->comboboxtext_options_ssh_loglevel, remmina_pref.ssh_loglevel);
+ if (remmina_pref.screenshot_path != NULL) {
+ gtk_file_chooser_set_filename(remmina_pref_dialog->filechooserbutton_options_screenshots_path, remmina_pref.screenshot_path);
+ }else{
+ gtk_file_chooser_set_filename(remmina_pref_dialog->filechooserbutton_options_screenshots_path, g_get_home_dir());
+ }
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(remmina_pref_dialog->checkbutton_options_ssh_parseconfig), remmina_pref.ssh_parseconfig);
+
+ remmina_pref_dialog_set_button_label(remmina_pref_dialog->button_keyboard_copy, remmina_pref.vte_shortcutkey_copy);
+ remmina_pref_dialog_set_button_label(remmina_pref_dialog->button_keyboard_paste, remmina_pref.vte_shortcutkey_paste);
+ remmina_pref_dialog_set_button_label(remmina_pref_dialog->button_keyboard_select_all, remmina_pref.vte_shortcutkey_select_all);
+
+ remmina_plugin_manager_for_each_plugin(REMMINA_PLUGIN_TYPE_PREF, remmina_pref_dialog_add_pref_plugin, remmina_pref_dialog->dialog);
+
+ g_object_set_data(G_OBJECT(remmina_pref_dialog->dialog), "tag", "remmina-pref-dialog");
+ remmina_widget_pool_register(GTK_WIDGET(remmina_pref_dialog->dialog));
+}
+
+/* RemminaPrefDialog instance */
+GtkDialog* remmina_pref_dialog_new(gint default_tab, GtkWindow *parent)
+{
+ TRACE_CALL(__func__);
+
+ remmina_pref_dialog = g_new0(RemminaPrefDialog, 1);
+ remmina_pref_dialog->priv = g_new0(RemminaPrefDialogPriv, 1);
+
+ remmina_pref_dialog->builder = remmina_public_gtk_builder_new_from_file("remmina_preferences.glade");
+ remmina_pref_dialog->dialog = GTK_DIALOG(gtk_builder_get_object(remmina_pref_dialog->builder, "RemminaPrefDialog"));
+ if (parent)
+ gtk_window_set_transient_for(GTK_WINDOW(remmina_pref_dialog->dialog), parent);
+
+ remmina_pref_dialog->notebook_preferences = GTK_NOTEBOOK(GET_OBJECT("notebook_preferences"));
+
+ remmina_pref_dialog->checkbutton_options_remember_last_view_mode = GTK_CHECK_BUTTON(GET_OBJECT("checkbutton_options_remember_last_view_mode"));
+ remmina_pref_dialog->checkbutton_options_save_settings = GTK_CHECK_BUTTON(GET_OBJECT("checkbutton_options_save_settings"));
+ remmina_pref_dialog->checkbutton_appearance_fullscreen_on_auto = GTK_CHECK_BUTTON(GET_OBJECT("checkbutton_appearance_fullscreen_on_auto"));
+ remmina_pref_dialog->checkbutton_appearance_show_tabs = GTK_CHECK_BUTTON(GET_OBJECT("checkbutton_appearance_show_tabs"));
+ remmina_pref_dialog->checkbutton_appearance_hide_toolbar = GTK_CHECK_BUTTON(GET_OBJECT("checkbutton_appearance_hide_toolbar"));
+ remmina_pref_dialog->checkbutton_permit_send_stats = GTK_CHECK_BUTTON(GET_OBJECT("checkbutton_permit_send_stats"));
+ remmina_pref_dialog->comboboxtext_options_double_click = GTK_COMBO_BOX(GET_OBJECT("comboboxtext_options_double_click"));
+ remmina_pref_dialog->comboboxtext_appearance_view_mode = GTK_COMBO_BOX(GET_OBJECT("comboboxtext_appearance_view_mode"));
+ remmina_pref_dialog->comboboxtext_appearance_tab_interface = GTK_COMBO_BOX(GET_OBJECT("comboboxtext_appearance_tab_interface"));
+ remmina_pref_dialog->comboboxtext_appearance_fullscreen_toolbar_visibility = GTK_COMBO_BOX(GET_OBJECT("comboboxtext_appearance_fullscreen_toolbar_visibility"));
+ remmina_pref_dialog->comboboxtext_appearance_show_buttons_icons = GTK_COMBO_BOX(GET_OBJECT("comboboxtext_appearance_show_buttons_icons"));
+ remmina_pref_dialog->comboboxtext_appearance_show_menu_icons = GTK_COMBO_BOX(GET_OBJECT("comboboxtext_appearance_show_menu_icons"));
+ remmina_pref_dialog->comboboxtext_options_scale_quality = GTK_COMBO_BOX(GET_OBJECT("comboboxtext_options_scale_quality"));
+ remmina_pref_dialog->checkbutton_options_ssh_parseconfig = GTK_CHECK_BUTTON(GET_OBJECT("checkbutton_options_ssh_parseconfig"));
+ remmina_pref_dialog->comboboxtext_options_ssh_loglevel = GTK_COMBO_BOX(GET_OBJECT("comboboxtext_options_ssh_loglevel"));
+ remmina_pref_dialog->filechooserbutton_options_screenshots_path = GTK_FILE_CHOOSER(GET_OBJECT("filechooserbutton_options_screenshots_path"));
+ remmina_pref_dialog->entry_options_ssh_port = GTK_ENTRY(GET_OBJECT("entry_options_ssh_port"));
+ remmina_pref_dialog->entry_options_ssh_tcp_keepidle = GTK_ENTRY(GET_OBJECT("entry_options_ssh_tcp_keepidle"));
+ remmina_pref_dialog->entry_options_ssh_tcp_keepintvl = GTK_ENTRY(GET_OBJECT("entry_options_ssh_tcp_keepintvl"));
+ remmina_pref_dialog->entry_options_ssh_tcp_keepcnt = GTK_ENTRY(GET_OBJECT("entry_options_ssh_tcp_keepcnt"));
+ remmina_pref_dialog->entry_options_ssh_tcp_usrtimeout = GTK_ENTRY(GET_OBJECT("entry_options_ssh_tcp_usrtimeout"));
+ remmina_pref_dialog->entry_options_scroll = GTK_ENTRY(GET_OBJECT("entry_options_scroll"));
+ remmina_pref_dialog->entry_options_recent_items = GTK_ENTRY(GET_OBJECT("entry_options_recent_items"));
+ remmina_pref_dialog->button_options_recent_items_clear = GTK_BUTTON(GET_OBJECT("button_options_recent_items_clear"));
+
+ remmina_pref_dialog->checkbutton_applet_new_connection_on_top = GTK_CHECK_BUTTON(GET_OBJECT("checkbutton_applet_new_connection_on_top"));
+ remmina_pref_dialog->checkbutton_applet_hide_totals = GTK_CHECK_BUTTON(GET_OBJECT("checkbutton_applet_hide_totals"));
+ remmina_pref_dialog->checkbutton_applet_disable_tray = GTK_CHECK_BUTTON(GET_OBJECT("checkbutton_applet_disable_tray"));
+ remmina_pref_dialog->checkbutton_applet_light_tray = GTK_CHECK_BUTTON(GET_OBJECT("checkbutton_applet_light_tray"));
+ remmina_pref_dialog->checkbutton_applet_start_in_tray = GTK_CHECK_BUTTON(GET_OBJECT("checkbutton_applet_start_in_tray"));
+
+ remmina_pref_dialog->button_keyboard_host_key = GTK_BUTTON(GET_OBJECT("button_keyboard_host_key"));
+ remmina_pref_dialog->button_keyboard_fullscreen = GTK_BUTTON(GET_OBJECT("button_keyboard_fullscreen"));
+ remmina_pref_dialog->button_keyboard_auto_fit = GTK_BUTTON(GET_OBJECT("button_keyboard_auto_fit"));
+ remmina_pref_dialog->button_keyboard_switch_tab_left = GTK_BUTTON(GET_OBJECT("button_keyboard_switch_tab_left"));
+ remmina_pref_dialog->button_keyboard_switch_tab_right = GTK_BUTTON(GET_OBJECT("button_keyboard_switch_tabright"));
+ remmina_pref_dialog->button_keyboard_scaled = GTK_BUTTON(GET_OBJECT("button_keyboard_scaled"));
+ remmina_pref_dialog->button_keyboard_grab_keyboard = GTK_BUTTON(GET_OBJECT("button_keyboard_grab_keyboard"));
+ remmina_pref_dialog->button_keyboard_screenshot = GTK_BUTTON(GET_OBJECT("button_keyboard_screenshot"));
+ remmina_pref_dialog->button_keyboard_viewonly = GTK_BUTTON(GET_OBJECT("button_keyboard_viewonly"));
+ remmina_pref_dialog->button_keyboard_minimize = GTK_BUTTON(GET_OBJECT("button_keyboard_minimize"));
+ remmina_pref_dialog->button_keyboard_disconnect = GTK_BUTTON(GET_OBJECT("button_keyboard_disconnect"));
+ remmina_pref_dialog->button_keyboard_toolbar = GTK_BUTTON(GET_OBJECT("button_keyboard_toolbar"));
+
+ remmina_pref_dialog->checkbutton_terminal_font_system = GTK_CHECK_BUTTON(GET_OBJECT("checkbutton_terminal_font_system"));
+ remmina_pref_dialog->fontbutton_terminal_font = GTK_FONT_BUTTON(GET_OBJECT("fontbutton_terminal_font"));
+ remmina_pref_dialog->checkbutton_terminal_bold = GTK_CHECK_BUTTON(GET_OBJECT("checkbutton_terminal_bold"));
+ remmina_pref_dialog->entry_scrollback_lines = GTK_ENTRY(GET_OBJECT("entry_scrollback_lines"));
+ remmina_pref_dialog->button_keyboard_copy = GTK_BUTTON(GET_OBJECT("button_keyboard_copy"));
+ remmina_pref_dialog->button_keyboard_paste = GTK_BUTTON(GET_OBJECT("button_keyboard_paste"));
+ remmina_pref_dialog->button_keyboard_select_all = GTK_BUTTON(GET_OBJECT("button_keyboard_select_all"));
+ remmina_pref_dialog->label_terminal_foreground = GTK_LABEL(GET_OBJECT("label_terminal_foreground"));
+ remmina_pref_dialog->colorbutton_foreground = GTK_COLOR_BUTTON(GET_OBJECT("colorbutton_foreground"));
+ remmina_pref_dialog->label_terminal_background = GTK_LABEL(GET_OBJECT("label_terminal_background"));
+ remmina_pref_dialog->colorbutton_background = GTK_COLOR_BUTTON(GET_OBJECT("colorbutton_background"));
+ remmina_pref_dialog->label_terminal_cursor_color = GTK_LABEL(GET_OBJECT("label_terminal_cursor_color"));
+ remmina_pref_dialog->colorbutton_cursor = GTK_COLOR_BUTTON(GET_OBJECT("colorbutton_cursor"));
+ remmina_pref_dialog->label_terminal_normal_colors = GTK_LABEL(GET_OBJECT("label_terminal_normal_colors"));
+ remmina_pref_dialog->colorbutton_color0 = GTK_COLOR_BUTTON(GET_OBJECT("colorbutton_color0"));
+ remmina_pref_dialog->colorbutton_color1 = GTK_COLOR_BUTTON(GET_OBJECT("colorbutton_color1"));
+ remmina_pref_dialog->colorbutton_color2 = GTK_COLOR_BUTTON(GET_OBJECT("colorbutton_color2"));
+ remmina_pref_dialog->colorbutton_color3 = GTK_COLOR_BUTTON(GET_OBJECT("colorbutton_color3"));
+ remmina_pref_dialog->colorbutton_color4 = GTK_COLOR_BUTTON(GET_OBJECT("colorbutton_color4"));
+ remmina_pref_dialog->colorbutton_color5 = GTK_COLOR_BUTTON(GET_OBJECT("colorbutton_color5"));
+ remmina_pref_dialog->colorbutton_color6 = GTK_COLOR_BUTTON(GET_OBJECT("colorbutton_color6"));
+ remmina_pref_dialog->colorbutton_color7 = GTK_COLOR_BUTTON(GET_OBJECT("colorbutton_color7"));
+ remmina_pref_dialog->label_terminal_bright_colors = GTK_LABEL(GET_OBJECT("label_terminal_bright_colors"));
+ remmina_pref_dialog->colorbutton_color8 = GTK_COLOR_BUTTON(GET_OBJECT("colorbutton_color8"));
+ remmina_pref_dialog->colorbutton_color9 = GTK_COLOR_BUTTON(GET_OBJECT("colorbutton_color9"));
+ remmina_pref_dialog->colorbutton_color10 = GTK_COLOR_BUTTON(GET_OBJECT("colorbutton_color10"));
+ remmina_pref_dialog->colorbutton_color11 = GTK_COLOR_BUTTON(GET_OBJECT("colorbutton_color11"));
+ remmina_pref_dialog->colorbutton_color12 = GTK_COLOR_BUTTON(GET_OBJECT("colorbutton_color12"));
+ remmina_pref_dialog->colorbutton_color13 = GTK_COLOR_BUTTON(GET_OBJECT("colorbutton_color13"));
+ remmina_pref_dialog->colorbutton_color14 = GTK_COLOR_BUTTON(GET_OBJECT("colorbutton_color14"));
+ remmina_pref_dialog->colorbutton_color15 = GTK_COLOR_BUTTON(GET_OBJECT("colorbutton_color15"));
+#if defined (HAVE_LIBSSH) && defined (HAVE_LIBVTE)
+#if VTE_CHECK_VERSION(0, 38, 0)
+ remmina_pref_dialog->button_term_cs = GTK_FILE_CHOOSER(GET_OBJECT("button_term_cs"));
+ gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(remmina_pref_dialog->button_term_cs), REMMINA_RUNTIME_TERM_CS_DIR);
+#endif
+#endif
+
+ /* Connect signals */
+ gtk_builder_connect_signals(remmina_pref_dialog->builder, NULL);
+ /* Initialize the window and load the preferences */
+ remmina_pref_dialog_init();
+
+ if (default_tab > 0)
+ gtk_notebook_set_current_page(remmina_pref_dialog->notebook_preferences, default_tab);
+ return remmina_pref_dialog->dialog;
+}
+
+GtkDialog* remmina_pref_dialog_get_dialog()
+{
+ if (!remmina_pref_dialog)
+ return NULL;
+ return remmina_pref_dialog->dialog;
+}
diff --git a/src/remmina_pref_dialog.h b/src/remmina_pref_dialog.h
new file mode 100644
index 000000000..911638719
--- /dev/null
+++ b/src/remmina_pref_dialog.h
@@ -0,0 +1,147 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2011 Vic Lee
+ * Copyright (C) 2017-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.
+ *
+ */
+
+#pragma once
+
+/*
+ * Remmina Preferences Dialog
+ */
+
+typedef struct _RemminaPrefDialogPriv {
+ GtkWidget *resolutions_list;
+} RemminaPrefDialogPriv;
+
+typedef struct _RemminaPrefDialog {
+ GtkBuilder *builder;
+ GtkDialog *dialog;
+ GtkNotebook *notebook_preferences;
+
+ GtkCheckButton *checkbutton_options_remember_last_view_mode;
+ GtkCheckButton *checkbutton_options_save_settings;
+ GtkCheckButton *checkbutton_appearance_fullscreen_on_auto;
+ GtkCheckButton *checkbutton_appearance_show_tabs;
+ GtkCheckButton *checkbutton_appearance_hide_toolbar;
+ GtkCheckButton *checkbutton_permit_send_stats;
+ GtkComboBox *comboboxtext_options_double_click;
+ GtkComboBox *comboboxtext_appearance_view_mode;
+ GtkComboBox *comboboxtext_appearance_tab_interface;
+ GtkComboBox *comboboxtext_appearance_show_buttons_icons;
+ GtkComboBox *comboboxtext_appearance_show_menu_icons;
+ GtkComboBox *comboboxtext_options_scale_quality;
+ GtkComboBox *comboboxtext_options_ssh_loglevel;
+ GtkComboBox *comboboxtext_appearance_fullscreen_toolbar_visibility;
+ GtkFileChooser *filechooserbutton_options_screenshots_path;
+ GtkCheckButton *checkbutton_options_ssh_parseconfig;
+ GtkEntry *entry_options_ssh_port;
+ GtkEntry *entry_options_ssh_tcp_keepidle;
+ GtkEntry *entry_options_ssh_tcp_keepintvl;
+ GtkEntry *entry_options_ssh_tcp_keepcnt;
+ GtkEntry *entry_options_ssh_tcp_usrtimeout;
+ GtkEntry *entry_options_scroll;
+ GtkEntry *entry_options_recent_items;
+ GtkButton *button_options_recent_items_clear;
+ GtkButton *button_options_resolutions;
+
+ GtkCheckButton *checkbutton_applet_new_connection_on_top;
+ GtkCheckButton *checkbutton_applet_hide_totals;
+ GtkCheckButton *checkbutton_applet_disable_tray;
+ GtkCheckButton *checkbutton_applet_light_tray;
+ GtkCheckButton *checkbutton_applet_start_in_tray;
+
+ GtkButton *button_keyboard_host_key;
+ GtkButton *button_keyboard_fullscreen;
+ GtkButton *button_keyboard_auto_fit;
+ GtkButton *button_keyboard_switch_tab_left;
+ GtkButton *button_keyboard_switch_tab_right;
+ GtkButton *button_keyboard_scaled;
+ GtkButton *button_keyboard_grab_keyboard;
+ GtkButton *button_keyboard_screenshot;
+ GtkButton *button_keyboard_viewonly;
+ GtkButton *button_keyboard_minimize;
+ GtkButton *button_keyboard_disconnect;
+ GtkButton *button_keyboard_toolbar;
+
+ GtkCheckButton *checkbutton_terminal_font_system;
+ GtkFontButton *fontbutton_terminal_font;
+ GtkCheckButton *checkbutton_terminal_bold;
+ GtkCheckButton *checkbutton_terminal_system_colors;
+ GtkLabel *label_terminal_foreground;
+ GtkColorButton *colorbutton_foreground;
+ GtkLabel *label_terminal_background;
+ GtkColorButton *colorbutton_background;
+ GtkEntry *entry_scrollback_lines;
+ GtkButton *button_keyboard_copy;
+ GtkButton *button_keyboard_paste;
+ GtkButton *button_keyboard_select_all;
+ GtkLabel *label_terminal_cursor_color;
+ GtkLabel *label_terminal_normal_colors;
+ GtkLabel *label_terminal_bright_colors;
+ GtkColorButton *colorbutton_cursor;
+ GtkColorButton *colorbutton_color0;
+ GtkColorButton *colorbutton_color1;
+ GtkColorButton *colorbutton_color2;
+ GtkColorButton *colorbutton_color3;
+ GtkColorButton *colorbutton_color4;
+ GtkColorButton *colorbutton_color5;
+ GtkColorButton *colorbutton_color6;
+ GtkColorButton *colorbutton_color7;
+ GtkColorButton *colorbutton_color8;
+ GtkColorButton *colorbutton_color9;
+ GtkColorButton *colorbutton_color10;
+ GtkColorButton *colorbutton_color11;
+ GtkColorButton *colorbutton_color12;
+ GtkColorButton *colorbutton_color13;
+ GtkColorButton *colorbutton_color14;
+ GtkColorButton *colorbutton_color15;
+ GtkFileChooser *button_term_cs;
+
+ RemminaPrefDialogPriv *priv;
+} RemminaPrefDialog;
+
+enum {
+ REMMINA_PREF_OPTIONS_TAB = 0,
+ REMMINA_PREF_APPEARANCE = 1,
+ REMMINA_PREF_APPLET_TAB = 2
+};
+
+G_BEGIN_DECLS
+
+/* RemminaPrefDialog instance */
+GtkDialog* remmina_pref_dialog_new(gint default_tab, GtkWindow *parent);
+/* Get the current PrefDialog or NULL if not initialized */
+GtkDialog* remmina_pref_dialog_get_dialog(void);
+
+G_END_DECLS
+
diff --git a/src/remmina_protocol_widget.c b/src/remmina_protocol_widget.c
new file mode 100644
index 000000000..5f7e2dc54
--- /dev/null
+++ b/src/remmina_protocol_widget.c
@@ -0,0 +1,1315 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2011 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 "config.h"
+
+#include <gtk/gtk.h>
+#include <gtk/gtkx.h>
+#include <glib/gi18n.h>
+#include <stdlib.h>
+
+#include "remmina_chat_window.h"
+#include "remmina_connection_window.h"
+#include "remmina_masterthread_exec.h"
+#include "remmina_ext_exec.h"
+#include "remmina_plugin_manager.h"
+#include "remmina_pref.h"
+#include "remmina_protocol_widget.h"
+#include "remmina_public.h"
+#include "remmina_ssh.h"
+#include "remmina_log.h"
+#include "remmina/remmina_trace_calls.h"
+
+struct _RemminaProtocolWidgetPriv {
+ GtkWidget* init_dialog;
+
+ RemminaFile* remmina_file;
+ RemminaProtocolPlugin* plugin;
+ RemminaProtocolFeature* features;
+
+ gint width;
+ gint height;
+ RemminaScaleMode scalemode;
+ gboolean scaler_expand;
+
+ gboolean has_error;
+ gchar* error_message;
+ RemminaSSHTunnel* ssh_tunnel;
+ RemminaTunnelInitFunc init_func;
+
+ GtkWidget* chat_window;
+
+ gboolean closed;
+
+ RemminaHostkeyFunc hostkey_func;
+ gpointer hostkey_func_data;
+
+ gint profile_remote_width;
+ gint profile_remote_height;
+
+};
+
+G_DEFINE_TYPE(RemminaProtocolWidget, remmina_protocol_widget, GTK_TYPE_EVENT_BOX)
+
+enum {
+ CONNECT_SIGNAL,
+ DISCONNECT_SIGNAL,
+ DESKTOP_RESIZE_SIGNAL,
+ UPDATE_ALIGN_SIGNAL,
+ UNLOCK_DYNRES_SIGNAL,
+ LAST_SIGNAL
+};
+
+typedef struct _RemminaProtocolWidgetSignalData {
+ RemminaProtocolWidget* gp;
+ const gchar* signal_name;
+} RemminaProtocolWidgetSignalData;
+
+static guint remmina_protocol_widget_signals[LAST_SIGNAL] =
+{ 0 };
+
+static void remmina_protocol_widget_class_init(RemminaProtocolWidgetClass *klass)
+{
+ TRACE_CALL(__func__);
+ remmina_protocol_widget_signals[CONNECT_SIGNAL] = g_signal_new("connect", G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaProtocolWidgetClass, connect), NULL, NULL,
+ g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
+ remmina_protocol_widget_signals[DISCONNECT_SIGNAL] = g_signal_new("disconnect", G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaProtocolWidgetClass, disconnect), NULL, NULL,
+ g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
+ remmina_protocol_widget_signals[DESKTOP_RESIZE_SIGNAL] = g_signal_new("desktop-resize", G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaProtocolWidgetClass, desktop_resize), NULL, NULL,
+ g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
+ remmina_protocol_widget_signals[UPDATE_ALIGN_SIGNAL] = g_signal_new("update-align", G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaProtocolWidgetClass, update_align), NULL, NULL,
+ g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
+ remmina_protocol_widget_signals[UNLOCK_DYNRES_SIGNAL] = g_signal_new("unlock-dynres", G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaProtocolWidgetClass, unlock_dynres), NULL, NULL,
+ g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
+}
+
+static void remmina_protocol_widget_init_cancel(RemminaInitDialog *dialog, gint response_id, RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ if ((response_id == GTK_RESPONSE_CANCEL || response_id == GTK_RESPONSE_DELETE_EVENT)
+ && dialog->mode == REMMINA_INIT_MODE_CONNECTING) {
+ remmina_protocol_widget_close_connection(gp);
+ }
+}
+
+static void remmina_protocol_widget_show_init_dialog(RemminaProtocolWidget* gp, const gchar *name)
+{
+ TRACE_CALL(__func__);
+ if (gp->priv->init_dialog) {
+ gtk_widget_destroy(gp->priv->init_dialog);
+ }
+ gp->priv->init_dialog = remmina_init_dialog_new(_("Connecting to '%s'..."), (name ? name : "*"));
+ g_signal_connect(G_OBJECT(gp->priv->init_dialog), "response", G_CALLBACK(remmina_protocol_widget_init_cancel), gp);
+ gtk_widget_show(gp->priv->init_dialog);
+}
+
+static void remmina_protocol_widget_hide_init_dialog(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ if (gp->priv->init_dialog && GTK_IS_WIDGET(gp->priv->init_dialog))
+ gtk_widget_destroy(gp->priv->init_dialog);
+
+ gp->priv->init_dialog = NULL;
+}
+
+static void remmina_protocol_widget_destroy(RemminaProtocolWidget* gp, gpointer data)
+{
+ TRACE_CALL(__func__);
+ remmina_protocol_widget_hide_init_dialog(gp);
+ g_free(gp->priv->features);
+ gp->priv->features = NULL;
+ g_free(gp->priv->error_message);
+ gp->priv->error_message = NULL;
+ g_free(gp->priv->remmina_file);
+ gp->priv->remmina_file = NULL;
+ g_free(gp->priv);
+ gp->priv = NULL;
+}
+
+static void remmina_protocol_widget_connect(RemminaProtocolWidget* gp, gpointer data)
+{
+ TRACE_CALL(__func__);
+#ifdef HAVE_LIBSSH
+ if (gp->priv->ssh_tunnel) {
+ remmina_ssh_tunnel_cancel_accept(gp->priv->ssh_tunnel);
+ }
+#endif
+ remmina_protocol_widget_hide_init_dialog(gp);
+}
+
+static void remmina_protocol_widget_disconnect(RemminaProtocolWidget* gp, gpointer data)
+{
+ TRACE_CALL(__func__);
+ remmina_protocol_widget_hide_init_dialog(gp);
+}
+
+void remmina_protocol_widget_grab_focus(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ GtkWidget* child;
+
+ child = gtk_bin_get_child(GTK_BIN(gp));
+
+ if (child) {
+ gtk_widget_set_can_focus(child, TRUE);
+ gtk_widget_grab_focus(child);
+ }
+}
+
+static void remmina_protocol_widget_init(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidgetPriv *priv;
+
+ priv = g_new0(RemminaProtocolWidgetPriv, 1);
+ gp->priv = priv;
+
+ g_signal_connect(G_OBJECT(gp), "destroy", G_CALLBACK(remmina_protocol_widget_destroy), NULL);
+ g_signal_connect(G_OBJECT(gp), "connect", G_CALLBACK(remmina_protocol_widget_connect), NULL);
+ g_signal_connect(G_OBJECT(gp), "disconnect", G_CALLBACK(remmina_protocol_widget_disconnect), NULL);
+}
+
+void remmina_protocol_widget_open_connection_real(gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget* gp = REMMINA_PROTOCOL_WIDGET(data);
+ RemminaProtocolPlugin* plugin;
+ RemminaFile* remminafile = gp->priv->remmina_file;
+ RemminaProtocolFeature* feature;
+ gint num_plugin;
+ gint num_ssh;
+
+ /* Locate the protocol plugin */
+ plugin = (RemminaProtocolPlugin*)remmina_plugin_manager_get_plugin(REMMINA_PLUGIN_TYPE_PROTOCOL,
+ remmina_file_get_string(remminafile, "protocol"));
+
+ if (!plugin || !plugin->init || !plugin->open_connection) {
+ remmina_protocol_widget_set_error(gp, _("Protocol plugin %s is not installed."),
+ remmina_file_get_string(remminafile, "protocol"));
+ remmina_protocol_widget_close_connection(gp);
+ return;
+ }
+
+ plugin->init(gp);
+
+ gp->priv->plugin = plugin;
+
+ for (num_plugin = 0, feature = (RemminaProtocolFeature*)plugin->features; feature && feature->type; num_plugin++, feature++) {
+ }
+
+ num_ssh = 0;
+#ifdef HAVE_LIBSSH
+ if (remmina_file_get_int(gp->priv->remmina_file, "ssh_enabled", FALSE)) {
+ num_ssh += 2;
+ }
+#endif
+ if (num_plugin + num_ssh == 0) {
+ gp->priv->features = NULL;
+ }else {
+ gp->priv->features = g_new0(RemminaProtocolFeature, num_plugin + num_ssh + 1);
+ feature = gp->priv->features;
+ if (plugin->features) {
+ memcpy(feature, plugin->features, sizeof(RemminaProtocolFeature) * num_plugin);
+ feature += num_plugin;
+ }
+#ifdef HAVE_LIBSSH
+ if (num_ssh) {
+ feature->type = REMMINA_PROTOCOL_FEATURE_TYPE_TOOL;
+ feature->id = REMMINA_PROTOCOL_FEATURE_TOOL_SSH;
+ feature->opt1 = _("Open Secure Shell in New Terminal...");
+ feature->opt2 = "utilities-terminal";
+ feature++;
+
+ feature->type = REMMINA_PROTOCOL_FEATURE_TYPE_TOOL;
+ feature->id = REMMINA_PROTOCOL_FEATURE_TOOL_SFTP;
+ feature->opt1 = _("Open Secure File Transfer...");
+ feature->opt2 = "folder-remote";
+ feature++;
+ }
+ feature->type = REMMINA_PROTOCOL_FEATURE_TYPE_END;
+#endif
+ }
+
+ if (!plugin->open_connection(gp)) {
+ remmina_protocol_widget_close_connection(gp);
+ }
+}
+
+void remmina_protocol_widget_open_connection(RemminaProtocolWidget* gp, RemminaFile* remminafile)
+{
+ TRACE_CALL(__func__);
+ gp->priv->remmina_file = remminafile;
+ gp->priv->scalemode = remmina_file_get_int(remminafile, "scale", FALSE);
+ gp->priv->scaler_expand = remmina_file_get_int(remminafile, "scaler_expand", FALSE);
+
+ /* Exec precommand before everything else */
+ remmina_ext_exec_new(remminafile, "precommand");
+
+ remmina_protocol_widget_show_init_dialog(gp, remmina_file_get_string(remminafile, "name"));
+
+ remmina_protocol_widget_open_connection_real(gp);
+}
+
+gboolean remmina_protocol_widget_close_connection(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ GdkDisplay *display;
+#if GTK_CHECK_VERSION(3, 20, 0)
+ GdkSeat *seat;
+#else
+ GdkDeviceManager *manager;
+#endif
+ GdkDevice *device = NULL;
+ gboolean retval;
+
+ if (!GTK_IS_WIDGET(gp) || gp->priv->closed)
+ return FALSE;
+
+ gp->priv->closed = TRUE;
+
+ display = gtk_widget_get_display(GTK_WIDGET(gp));
+#if GTK_CHECK_VERSION(3, 20, 0)
+ seat = gdk_display_get_default_seat(display);
+ device = gdk_seat_get_pointer(seat);
+#else
+ manager = gdk_display_get_device_manager(display);
+ device = gdk_device_manager_get_client_pointer(manager);
+#endif
+ if (device != NULL) {
+#if GTK_CHECK_VERSION(3, 20, 0)
+ gdk_seat_ungrab(seat);
+#else
+ gdk_device_ungrab(device, GDK_CURRENT_TIME);
+#endif
+ }
+
+ if (gp->priv->chat_window) {
+ gtk_widget_destroy(gp->priv->chat_window);
+ gp->priv->chat_window = NULL;
+ }
+
+ if (!gp->priv->plugin || !gp->priv->plugin->close_connection) {
+ remmina_protocol_widget_emit_signal(gp, "disconnect");
+ return FALSE;
+ }
+
+ retval = gp->priv->plugin->close_connection(gp);
+
+#ifdef HAVE_LIBSSH
+ if (gp->priv->ssh_tunnel) {
+ remmina_ssh_tunnel_free(gp->priv->ssh_tunnel);
+ gp->priv->ssh_tunnel = NULL;
+ }
+#endif
+
+ /* Exec postcommand before to close the connection */
+ remmina_ext_exec_new(gp->priv->remmina_file, "postcommand");
+ return retval;
+}
+
+/** Check if the plugin accepts keystrokes.
+ */
+gboolean remmina_protocol_widget_plugin_receives_keystrokes(RemminaProtocolWidget* gp)
+{
+ return gp->priv->plugin->send_keystrokes ? TRUE : FALSE;
+}
+
+/**
+ * Send to the plugin some keystrokes.
+ */
+void remmina_protocol_widget_send_keystrokes(RemminaProtocolWidget* gp, GtkMenuItem *widget)
+{
+ TRACE_CALL(__func__);
+ gchar *keystrokes = g_object_get_data(G_OBJECT(widget), "keystrokes");
+ guint *keyvals;
+ gint i;
+ GdkKeymap *keymap = gdk_keymap_get_for_display(gdk_display_get_default());
+ gchar *iter = keystrokes;
+ gunichar character;
+ guint keyval;
+ GdkKeymapKey *keys;
+ gint n_keys;
+ /* Single keystroke replace */
+ typedef struct _KeystrokeReplace {
+ gchar *search;
+ gchar *replace;
+ guint keyval;
+ } KeystrokeReplace;
+ /* Special characters to replace */
+ KeystrokeReplace keystrokes_replaces[] =
+ {
+ { "\\n", "\n", GDK_KEY_Return },
+ { "\\t", "\t", GDK_KEY_Tab },
+ { "\\b", "\b", GDK_KEY_BackSpace },
+ { "\\e", "\e", GDK_KEY_Escape },
+ { "\\\\", "\\", GDK_KEY_backslash },
+ { NULL, NULL, 0 }
+ };
+ /* Keystrokes can be sent only to plugins that accepts them */
+ if (remmina_protocol_widget_plugin_receives_keystrokes(gp)) {
+ /* Replace special characters */
+ for (i = 0; keystrokes_replaces[i].replace; i++) {
+ remmina_public_str_replace_in_place(keystrokes,
+ keystrokes_replaces[i].search,
+ keystrokes_replaces[i].replace);
+ }
+ keyvals = (guint*)g_malloc(strlen(keystrokes));
+ while (TRUE) {
+ /* Process each character in the keystrokes */
+ character = g_utf8_get_char_validated(iter, -1);
+ if (character == 0)
+ break;
+ keyval = gdk_unicode_to_keyval(character);
+ /* Replace all the special character with its keyval */
+ for (i = 0; keystrokes_replaces[i].replace; i++) {
+ if (character == keystrokes_replaces[i].replace[0]) {
+ keys = g_new0(GdkKeymapKey, 1);
+ keyval = keystrokes_replaces[i].keyval;
+ /* A special character was generated, no keyval lookup needed */
+ character = 0;
+ break;
+ }
+ }
+ /* Decode character if it's not a special character */
+ if (character) {
+ /* get keyval without modifications */
+ if (!gdk_keymap_get_entries_for_keyval(keymap, keyval, &keys, &n_keys)) {
+ g_warning("keyval 0x%04x has no keycode!", keyval);
+ iter = g_utf8_find_next_char(iter, NULL);
+ continue;
+ }
+ }
+ /* Add modifier keys */
+ n_keys = 0;
+ if (keys->level & 1)
+ keyvals[n_keys++] = GDK_KEY_Shift_L;
+ if (keys->level & 2)
+ keyvals[n_keys++] = GDK_KEY_Alt_R;
+ keyvals[n_keys++] = keyval;
+ /* Send keystroke to the plugin */
+ gp->priv->plugin->send_keystrokes(gp, keyvals, n_keys);
+ g_free(keys);
+ /* Process next character in the keystrokes */
+ iter = g_utf8_find_next_char(iter, NULL);
+ }
+ g_free(keyvals);
+ }
+ g_free(keystrokes);
+ return;
+}
+
+gboolean remmina_protocol_widget_plugin_screenshot(RemminaProtocolWidget* gp, RemminaPluginScreenshotData *rpsd)
+{
+ if (!gp->priv->plugin->get_plugin_screenshot) {
+ remmina_log_printf("plugin screenshot function is not implemented\n");
+ return FALSE;
+ }
+
+ return gp->priv->plugin->get_plugin_screenshot(gp, rpsd);
+
+}
+
+void remmina_protocol_widget_emit_signal(RemminaProtocolWidget* gp, const gchar* signal_name)
+{
+ TRACE_CALL(__func__);
+
+ if ( !remmina_masterthread_exec_is_main_thread() ) {
+ /* Allow the execution of this function from a non main thread */
+ RemminaMTExecData *d;
+ d = (RemminaMTExecData*)g_malloc( sizeof(RemminaMTExecData) );
+ d->func = FUNC_PROTOCOLWIDGET_EMIT_SIGNAL;
+ d->p.protocolwidget_emit_signal.signal_name = signal_name;
+ d->p.protocolwidget_emit_signal.gp = gp;
+ remmina_masterthread_exec_and_wait(d);
+ g_free(d);
+ return;
+ }
+ g_signal_emit_by_name(G_OBJECT(gp), signal_name);
+
+}
+
+const RemminaProtocolFeature* remmina_protocol_widget_get_features(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ return gp->priv->features;
+}
+
+const gchar* remmina_protocol_widget_get_domain(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ return gp->priv->plugin->domain;
+}
+
+gboolean remmina_protocol_widget_query_feature_by_type(RemminaProtocolWidget* gp, RemminaProtocolFeatureType type)
+{
+ TRACE_CALL(__func__);
+ const RemminaProtocolFeature *feature;
+
+#ifdef HAVE_LIBSSH
+ if (type == REMMINA_PROTOCOL_FEATURE_TYPE_TOOL &&
+ remmina_file_get_int(gp->priv->remmina_file, "ssh_enabled", FALSE)) {
+ return TRUE;
+ }
+#endif
+ for (feature = gp->priv->plugin->features; feature && feature->type; feature++) {
+ if (feature->type == type)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+gboolean remmina_protocol_widget_query_feature_by_ref(RemminaProtocolWidget* gp, const RemminaProtocolFeature *feature)
+{
+ TRACE_CALL(__func__);
+ return gp->priv->plugin->query_feature(gp, feature);
+}
+
+void remmina_protocol_widget_call_feature_by_type(RemminaProtocolWidget* gp, RemminaProtocolFeatureType type, gint id)
+{
+ TRACE_CALL(__func__);
+ const RemminaProtocolFeature *feature;
+
+ for (feature = gp->priv->plugin->features; feature && feature->type; feature++) {
+ if (feature->type == type && (id == 0 || feature->id == id)) {
+ remmina_protocol_widget_call_feature_by_ref(gp, feature);
+ break;
+ }
+ }
+}
+
+void remmina_protocol_widget_call_feature_by_ref(RemminaProtocolWidget* gp, const RemminaProtocolFeature *feature)
+{
+ TRACE_CALL(__func__);
+ switch (feature->id) {
+#ifdef HAVE_LIBSSH
+ case REMMINA_PROTOCOL_FEATURE_TOOL_SSH:
+ if (gp->priv->ssh_tunnel) {
+ remmina_connection_window_open_from_file_full(
+ remmina_file_dup_temp_protocol(gp->priv->remmina_file, "SSH"), NULL, gp->priv->ssh_tunnel, NULL);
+ return;
+ }
+ break;
+
+ case REMMINA_PROTOCOL_FEATURE_TOOL_SFTP:
+ if (gp->priv->ssh_tunnel) {
+ remmina_connection_window_open_from_file_full(
+ remmina_file_dup_temp_protocol(gp->priv->remmina_file, "SFTP"), NULL, gp->priv->ssh_tunnel, NULL);
+ return;
+ }
+ break;
+#endif
+ default:
+ break;
+ }
+ gp->priv->plugin->call_feature(gp, feature);
+}
+
+static gboolean remmina_protocol_widget_on_key_press(GtkWidget *widget, GdkEventKey *event, RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ if (gp->priv->hostkey_func) {
+ return gp->priv->hostkey_func(gp, event->keyval, FALSE, gp->priv->hostkey_func_data);
+ }
+ return FALSE;
+}
+
+static gboolean remmina_protocol_widget_on_key_release(GtkWidget *widget, GdkEventKey *event, RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ if (gp->priv->hostkey_func) {
+ return gp->priv->hostkey_func(gp, event->keyval, TRUE, gp->priv->hostkey_func_data);
+ }
+
+ return FALSE;
+}
+
+void remmina_protocol_widget_register_hostkey(RemminaProtocolWidget* gp, GtkWidget *widget)
+{
+ TRACE_CALL(__func__);
+ g_signal_connect(G_OBJECT(widget), "key-press-event", G_CALLBACK(remmina_protocol_widget_on_key_press), gp);
+ g_signal_connect(G_OBJECT(widget), "key-release-event", G_CALLBACK(remmina_protocol_widget_on_key_release), gp);
+}
+
+void remmina_protocol_widget_set_hostkey_func(RemminaProtocolWidget* gp, RemminaHostkeyFunc func, gpointer data)
+{
+ TRACE_CALL(__func__);
+ gp->priv->hostkey_func = func;
+ gp->priv->hostkey_func_data = data;
+}
+
+#ifdef HAVE_LIBSSH
+static gboolean remmina_protocol_widget_init_tunnel(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ RemminaSSHTunnel *tunnel;
+ gint ret;
+
+ /* Reuse existing SSH connection if it's reconnecting to destination */
+ if (gp->priv->ssh_tunnel == NULL) {
+ tunnel = remmina_ssh_tunnel_new_from_file(gp->priv->remmina_file);
+
+ remmina_init_dialog_set_status(REMMINA_INIT_DIALOG(gp->priv->init_dialog),
+ _("Connecting to SSH server %s..."), REMMINA_SSH(tunnel)->server);
+
+ if (!remmina_ssh_init_session(REMMINA_SSH(tunnel))) {
+ remmina_protocol_widget_set_error(gp, REMMINA_SSH(tunnel)->error);
+ remmina_ssh_tunnel_free(tunnel);
+ return FALSE;
+ }
+
+ ret = remmina_ssh_auth_gui(REMMINA_SSH(tunnel), REMMINA_INIT_DIALOG(gp->priv->init_dialog), gp->priv->remmina_file);
+ if (ret <= 0) {
+ if (ret == 0) {
+ remmina_protocol_widget_set_error(gp, REMMINA_SSH(tunnel)->error);
+ }
+ remmina_ssh_tunnel_free(tunnel);
+ return FALSE;
+ }
+
+ gp->priv->ssh_tunnel = tunnel;
+ }
+
+ return TRUE;
+}
+#endif
+
+/**
+ * Start an SSH tunnel if possible and return the host:port string.
+ *
+ */
+gchar* remmina_protocol_widget_start_direct_tunnel(RemminaProtocolWidget* gp, gint default_port, gboolean port_plus)
+{
+ TRACE_CALL(__func__);
+ const gchar *server;
+ gchar *host, *dest;
+ gint port;
+
+ server = remmina_file_get_string(gp->priv->remmina_file, "server");
+
+ if (!server) {
+ return g_strdup("");
+ }
+
+ remmina_public_get_server_port(server, default_port, &host, &port);
+
+ if (port_plus && port < 100) {
+ /* Protocols like VNC supports using instance number :0, :1, etc as port number. */
+ port += default_port;
+ }
+
+#ifdef HAVE_LIBSSH
+ if (!remmina_file_get_int(gp->priv->remmina_file, "ssh_enabled", FALSE)) {
+ dest = g_strdup_printf("[%s]:%i", host, port);
+ g_free(host);
+ return dest;
+ }
+
+ /* If we have a previous ssh tunnel, destroy it */
+ if (gp->priv->ssh_tunnel) {
+ remmina_ssh_tunnel_free(gp->priv->ssh_tunnel);
+ gp->priv->ssh_tunnel = NULL;
+ }
+
+ if (!remmina_protocol_widget_init_tunnel(gp)) {
+ g_free(host);
+ return NULL;
+ }
+
+ remmina_init_dialog_set_status(REMMINA_INIT_DIALOG(gp->priv->init_dialog),
+ _("Connecting to %s through SSH tunnel..."), server);
+
+ if (remmina_file_get_int(gp->priv->remmina_file, "ssh_loopback", FALSE)) {
+ g_free(host);
+ host = g_strdup("127.0.0.1");
+ }
+
+ if (!remmina_ssh_tunnel_open(gp->priv->ssh_tunnel, host, port, remmina_pref.sshtunnel_port)) {
+ g_free(host);
+ remmina_protocol_widget_set_error(gp, REMMINA_SSH(gp->priv->ssh_tunnel)->error);
+ return NULL;
+ }
+
+ g_free(host);
+ return g_strdup_printf("127.0.0.1:%i", remmina_pref.sshtunnel_port);
+
+#else
+
+ dest = g_strdup_printf("[%s]:%i", host, port);
+ g_free(host);
+ return dest;
+
+#endif
+}
+
+gboolean remmina_protocol_widget_start_reverse_tunnel(RemminaProtocolWidget* gp, gint local_port)
+{
+ TRACE_CALL(__func__);
+#ifdef HAVE_LIBSSH
+ if (!remmina_file_get_int(gp->priv->remmina_file, "ssh_enabled", FALSE)) {
+ return TRUE;
+ }
+
+ if (!remmina_protocol_widget_init_tunnel(gp)) {
+ return FALSE;
+ }
+
+ remmina_init_dialog_set_status(REMMINA_INIT_DIALOG(gp->priv->init_dialog),
+ _("Waiting for an incoming SSH tunnel at port %i..."), remmina_file_get_int(gp->priv->remmina_file, "listenport", 0));
+
+ if (!remmina_ssh_tunnel_reverse(gp->priv->ssh_tunnel, remmina_file_get_int(gp->priv->remmina_file, "listenport", 0), local_port)) {
+ remmina_protocol_widget_set_error(gp, REMMINA_SSH(gp->priv->ssh_tunnel)->error);
+ return FALSE;
+ }
+#endif
+
+ return TRUE;
+}
+
+gboolean remmina_protocol_widget_ssh_exec(RemminaProtocolWidget* gp, gboolean wait, const gchar *fmt, ...)
+{
+ TRACE_CALL(__func__);
+#ifdef HAVE_LIBSSH
+ RemminaSSHTunnel *tunnel = gp->priv->ssh_tunnel;
+ ssh_channel channel;
+ gint status;
+ gboolean ret = FALSE;
+ gchar *cmd, *ptr;
+ va_list args;
+
+ if ((channel = ssh_channel_new(REMMINA_SSH(tunnel)->session)) == NULL) {
+ return FALSE;
+ }
+
+ va_start(args, fmt);
+ cmd = g_strdup_vprintf(fmt, args);
+ va_end(args);
+
+ if (ssh_channel_open_session(channel) == SSH_OK &&
+ ssh_channel_request_exec(channel, cmd) == SSH_OK) {
+ if (wait) {
+ ssh_channel_send_eof(channel);
+ status = ssh_channel_get_exit_status(channel);
+ ptr = strchr(cmd, ' ');
+ if (ptr) *ptr = '\0';
+ switch (status) {
+ case 0:
+ ret = TRUE;
+ break;
+ case 127:
+ remmina_ssh_set_application_error(REMMINA_SSH(tunnel),
+ _("Command %s not found on SSH server"), cmd);
+ break;
+ default:
+ remmina_ssh_set_application_error(REMMINA_SSH(tunnel),
+ _("Command %s failed on SSH server (status = %i)."), cmd, status);
+ break;
+ }
+ }else {
+ ret = TRUE;
+ }
+ }else {
+ remmina_ssh_set_error(REMMINA_SSH(tunnel), _("Failed to execute command: %s"));
+ }
+ g_free(cmd);
+ if (wait)
+ ssh_channel_close(channel);
+ ssh_channel_free(channel);
+ return ret;
+
+#else
+
+ return FALSE;
+
+#endif
+}
+
+#ifdef HAVE_LIBSSH
+static gboolean remmina_protocol_widget_tunnel_init_callback(RemminaSSHTunnel *tunnel, gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget* gp = REMMINA_PROTOCOL_WIDGET(data);
+ gchar *server;
+ gint port;
+ gboolean ret;
+
+ remmina_public_get_server_port(remmina_file_get_string(gp->priv->remmina_file, "server"), 177, &server, &port);
+ ret = ((RemminaXPortTunnelInitFunc)gp->priv->init_func)(gp,
+ tunnel->remotedisplay, (tunnel->bindlocalhost ? "localhost" : server), port);
+ g_free(server);
+
+ return ret;
+}
+
+static gboolean remmina_protocol_widget_tunnel_connect_callback(RemminaSSHTunnel* tunnel, gpointer data)
+{
+ TRACE_CALL(__func__);
+ return TRUE;
+}
+
+static gboolean remmina_protocol_widget_tunnel_disconnect_callback(RemminaSSHTunnel* tunnel, gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget* gp = REMMINA_PROTOCOL_WIDGET(data);
+
+ if (REMMINA_SSH(tunnel)->error) {
+ remmina_protocol_widget_set_error(gp, "%s", REMMINA_SSH(tunnel)->error);
+ }
+
+ IDLE_ADD((GSourceFunc)remmina_protocol_widget_close_connection, gp);
+ return TRUE;
+}
+#endif
+
+gboolean remmina_protocol_widget_start_xport_tunnel(RemminaProtocolWidget* gp, RemminaXPortTunnelInitFunc init_func)
+{
+ TRACE_CALL(__func__);
+#ifdef HAVE_LIBSSH
+ gboolean bindlocalhost;
+ gchar *server;
+
+ if (!remmina_protocol_widget_init_tunnel(gp)) return FALSE;
+
+ remmina_init_dialog_set_status(REMMINA_INIT_DIALOG(gp->priv->init_dialog),
+ _("Connecting to %s through SSH tunnel..."), remmina_file_get_string(gp->priv->remmina_file, "server"));
+
+ gp->priv->init_func = init_func;
+ gp->priv->ssh_tunnel->init_func = remmina_protocol_widget_tunnel_init_callback;
+ gp->priv->ssh_tunnel->connect_func = remmina_protocol_widget_tunnel_connect_callback;
+ gp->priv->ssh_tunnel->disconnect_func = remmina_protocol_widget_tunnel_disconnect_callback;
+ gp->priv->ssh_tunnel->callback_data = gp;
+
+ remmina_public_get_server_port(remmina_file_get_string(gp->priv->remmina_file, "server"), 0, &server, NULL);
+ bindlocalhost = (g_strcmp0(REMMINA_SSH(gp->priv->ssh_tunnel)->server, server) == 0);
+ g_free(server);
+
+ if (!remmina_ssh_tunnel_xport(gp->priv->ssh_tunnel, bindlocalhost)) {
+ remmina_protocol_widget_set_error(gp, "Failed to open channel : %s",
+ ssh_get_error(REMMINA_SSH(gp->priv->ssh_tunnel)->session));
+ return FALSE;
+ }
+
+ return TRUE;
+
+#else
+ return FALSE;
+#endif
+}
+
+void remmina_protocol_widget_set_display(RemminaProtocolWidget* gp, gint display)
+{
+ TRACE_CALL(__func__);
+#ifdef HAVE_LIBSSH
+ if (gp->priv->ssh_tunnel->localdisplay) g_free(gp->priv->ssh_tunnel->localdisplay);
+ gp->priv->ssh_tunnel->localdisplay = g_strdup_printf("unix:%i", display);
+#endif
+}
+
+GtkWidget* remmina_protocol_widget_get_init_dialog(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ return gp->priv->init_dialog;
+}
+
+gint remmina_protocol_widget_get_profile_remote_width(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ /* Returns the width of remote desktop as choosen by the user profile */
+ return gp->priv->profile_remote_width;
+}
+
+gint remmina_protocol_widget_get_profile_remote_height(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ /* Returns the height of remote desktop as choosen by the user profile */
+ return gp->priv->profile_remote_height;
+}
+
+
+gint remmina_protocol_widget_get_width(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ return gp->priv->width;
+}
+
+void remmina_protocol_widget_set_width(RemminaProtocolWidget* gp, gint width)
+{
+ TRACE_CALL(__func__);
+ gp->priv->width = width;
+}
+
+gint remmina_protocol_widget_get_height(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ return gp->priv->height;
+}
+
+void remmina_protocol_widget_set_height(RemminaProtocolWidget* gp, gint height)
+{
+ TRACE_CALL(__func__);
+ gp->priv->height = height;
+}
+
+RemminaScaleMode remmina_protocol_widget_get_current_scale_mode(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ return gp->priv->scalemode;
+}
+
+void remmina_protocol_widget_set_current_scale_mode(RemminaProtocolWidget *gp, RemminaScaleMode scalemode)
+{
+ TRACE_CALL(__func__);
+ gp->priv->scalemode = scalemode;
+}
+
+gboolean remmina_protocol_widget_get_expand(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ return gp->priv->scaler_expand;
+}
+
+void remmina_protocol_widget_set_expand(RemminaProtocolWidget* gp, gboolean expand)
+{
+ TRACE_CALL(__func__);
+ gp->priv->scaler_expand = expand;
+ return;
+}
+
+gboolean remmina_protocol_widget_has_error(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ return gp->priv->has_error;
+}
+
+gchar* remmina_protocol_widget_get_error_message(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ return gp->priv->error_message;
+}
+
+void remmina_protocol_widget_set_error(RemminaProtocolWidget* gp, const gchar *fmt, ...)
+{
+ TRACE_CALL(__func__);
+ va_list args;
+
+ if (gp->priv->error_message) g_free(gp->priv->error_message);
+
+ if (fmt == NULL) {
+ gp->priv->has_error = FALSE;
+ gp->priv->error_message = NULL;
+ return;
+ }
+
+ va_start(args, fmt);
+ gp->priv->error_message = g_strdup_vprintf(fmt, args);
+ va_end(args);
+
+ gp->priv->has_error = TRUE;
+}
+
+gboolean remmina_protocol_widget_is_closed(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ return gp->priv->closed;
+}
+
+RemminaFile* remmina_protocol_widget_get_file(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ return gp->priv->remmina_file;
+}
+
+gint remmina_protocol_widget_init_authpwd(RemminaProtocolWidget* gp, RemminaAuthpwdType authpwd_type, gboolean allow_password_saving)
+{
+ TRACE_CALL(__func__);
+ RemminaFile* remminafile = gp->priv->remmina_file;
+ gchar* s;
+ gint ret;
+
+ switch (authpwd_type) {
+ case REMMINA_AUTHPWD_TYPE_PROTOCOL:
+ s = g_strdup_printf(_("%s password"), remmina_file_get_string(remminafile, "protocol"));
+ break;
+ case REMMINA_AUTHPWD_TYPE_SSH_PWD:
+ s = g_strdup(_("SSH password"));
+ break;
+ case REMMINA_AUTHPWD_TYPE_SSH_PRIVKEY:
+ s = g_strdup(_("SSH private key passphrase"));
+ break;
+ default:
+ s = g_strdup(_("Password"));
+ break;
+ }
+
+ ret = remmina_init_dialog_authpwd(
+ REMMINA_INIT_DIALOG(gp->priv->init_dialog),
+ s,
+ (remmina_file_get_filename(remminafile) != NULL &&
+ !remminafile->prevent_saving && allow_password_saving));
+ g_free(s);
+
+ return ret;
+}
+
+gint remmina_protocol_widget_init_authuserpwd(RemminaProtocolWidget* gp, gboolean want_domain, gboolean allow_password_saving)
+{
+ TRACE_CALL(__func__);
+ RemminaFile* remminafile = gp->priv->remmina_file;
+
+ return remmina_init_dialog_authuserpwd(
+ REMMINA_INIT_DIALOG(gp->priv->init_dialog),
+ want_domain,
+ remmina_file_get_string(remminafile, "username"),
+ want_domain ? remmina_file_get_string(remminafile, "domain") : NULL,
+ (remmina_file_get_filename(remminafile) != NULL &&
+ !remminafile->prevent_saving && allow_password_saving));
+}
+
+gint remmina_protocol_widget_init_certificate(RemminaProtocolWidget* gp, const gchar* subject, const gchar* issuer, const gchar* fingerprint)
+{
+ TRACE_CALL(__func__);
+ return remmina_init_dialog_certificate(REMMINA_INIT_DIALOG(gp->priv->init_dialog), subject, issuer, fingerprint);
+}
+gint remmina_protocol_widget_changed_certificate(RemminaProtocolWidget *gp, const gchar* subject, const gchar* issuer, const gchar* new_fingerprint, const gchar* old_fingerprint)
+{
+ TRACE_CALL(__func__);
+ return remmina_init_dialog_certificate_changed(REMMINA_INIT_DIALOG(gp->priv->init_dialog), subject, issuer, new_fingerprint, old_fingerprint);
+}
+
+gchar* remmina_protocol_widget_init_get_username(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ return g_strdup(REMMINA_INIT_DIALOG(gp->priv->init_dialog)->username);
+}
+
+gchar* remmina_protocol_widget_init_get_password(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ return g_strdup(REMMINA_INIT_DIALOG(gp->priv->init_dialog)->password);
+}
+
+gchar* remmina_protocol_widget_init_get_domain(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ return g_strdup(REMMINA_INIT_DIALOG(gp->priv->init_dialog)->domain);
+}
+
+gboolean remmina_protocol_widget_init_get_savepassword(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ return REMMINA_INIT_DIALOG(gp->priv->init_dialog)->save_password;
+}
+
+gint remmina_protocol_widget_init_authx509(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ RemminaFile* remminafile = gp->priv->remmina_file;
+
+ return remmina_init_dialog_authx509(REMMINA_INIT_DIALOG(gp->priv->init_dialog),
+ remmina_file_get_string(remminafile, "cacert"), remmina_file_get_string(remminafile, "cacrl"),
+ remmina_file_get_string(remminafile, "clientcert"), remmina_file_get_string(remminafile, "clientkey"));
+}
+
+gchar* remmina_protocol_widget_init_get_cacert(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ gchar* s;
+
+ s = REMMINA_INIT_DIALOG(gp->priv->init_dialog)->cacert;
+ return (s && s[0] ? g_strdup(s) : NULL);
+}
+
+gchar* remmina_protocol_widget_init_get_cacrl(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ gchar* s;
+
+ s = REMMINA_INIT_DIALOG(gp->priv->init_dialog)->cacrl;
+ return (s && s[0] ? g_strdup(s) : NULL);
+}
+
+gchar* remmina_protocol_widget_init_get_clientcert(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ gchar* s;
+
+ s = REMMINA_INIT_DIALOG(gp->priv->init_dialog)->clientcert;
+ return (s && s[0] ? g_strdup(s) : NULL);
+}
+
+gchar* remmina_protocol_widget_init_get_clientkey(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ gchar* s;
+
+ s = REMMINA_INIT_DIALOG(gp->priv->init_dialog)->clientkey;
+ return (s && s[0] ? g_strdup(s) : NULL);
+}
+
+void remmina_protocol_widget_init_save_cred(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+
+ RemminaFile* remminafile = gp->priv->remmina_file;
+ gchar* s;
+ gboolean save = FALSE;
+
+ if ( !remmina_masterthread_exec_is_main_thread() ) {
+ /* Allow the execution of this function from a non main thread */
+ RemminaMTExecData *d;
+ d = (RemminaMTExecData*)g_malloc( sizeof(RemminaMTExecData) );
+ d->func = FUNC_INIT_SAVE_CRED;
+ d->p.init_save_creds.gp = gp;
+ remmina_masterthread_exec_and_wait(d);
+ g_free(d);
+ return;
+ }
+
+ /* Save user name and certificates if any; save the password if it's requested to do so */
+ s = REMMINA_INIT_DIALOG(gp->priv->init_dialog)->username;
+ if (s && s[0]) {
+ remmina_file_set_string(remminafile, "username", s);
+ save = TRUE;
+ }
+ s = REMMINA_INIT_DIALOG(gp->priv->init_dialog)->cacert;
+ if (s && s[0]) {
+ remmina_file_set_string(remminafile, "cacert", s);
+ save = TRUE;
+ }
+ s = REMMINA_INIT_DIALOG(gp->priv->init_dialog)->cacrl;
+ if (s && s[0]) {
+ remmina_file_set_string(remminafile, "cacrl", s);
+ save = TRUE;
+ }
+ s = REMMINA_INIT_DIALOG(gp->priv->init_dialog)->clientcert;
+ if (s && s[0]) {
+ remmina_file_set_string(remminafile, "clientcert", s);
+ save = TRUE;
+ }
+ s = REMMINA_INIT_DIALOG(gp->priv->init_dialog)->clientkey;
+ if (s && s[0]) {
+ remmina_file_set_string(remminafile, "clientkey", s);
+ save = TRUE;
+ }
+ if (REMMINA_INIT_DIALOG(gp->priv->init_dialog)->save_password) {
+ remmina_file_set_string(remminafile, "password", REMMINA_INIT_DIALOG(gp->priv->init_dialog)->password);
+ save = TRUE;
+ }
+ if (save) {
+ remmina_file_save(remminafile);
+ }
+}
+
+
+void remmina_protocol_widget_init_show_listen(RemminaProtocolWidget* gp, gint port)
+{
+ TRACE_CALL(__func__);
+ remmina_init_dialog_set_status(REMMINA_INIT_DIALOG(gp->priv->init_dialog),
+ _("Listening on port %i for an incoming %s connection..."), port,
+ remmina_file_get_string(gp->priv->remmina_file, "protocol"));
+}
+
+void remmina_protocol_widget_init_show_retry(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ remmina_init_dialog_set_status_temp(REMMINA_INIT_DIALOG(gp->priv->init_dialog),
+ _("Authentication failed. Trying to reconnect..."));
+}
+
+void remmina_protocol_widget_init_show(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ gtk_widget_show(gp->priv->init_dialog);
+}
+
+void remmina_protocol_widget_init_hide(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ gtk_widget_hide(gp->priv->init_dialog);
+}
+
+static void remmina_protocol_widget_chat_on_destroy(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ gp->priv->chat_window = NULL;
+}
+
+void remmina_protocol_widget_chat_open(RemminaProtocolWidget* gp, const gchar *name,
+ void (*on_send)(RemminaProtocolWidget* gp, const gchar *text), void (*on_destroy)(RemminaProtocolWidget* gp))
+{
+ TRACE_CALL(__func__);
+ if (gp->priv->chat_window) {
+ gtk_window_present(GTK_WINDOW(gp->priv->chat_window));
+ }else {
+ gp->priv->chat_window = remmina_chat_window_new(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(gp))), name);
+ g_signal_connect_swapped(G_OBJECT(gp->priv->chat_window), "send", G_CALLBACK(on_send), gp);
+ g_signal_connect_swapped(G_OBJECT(gp->priv->chat_window), "destroy",
+ G_CALLBACK(remmina_protocol_widget_chat_on_destroy), gp);
+ g_signal_connect_swapped(G_OBJECT(gp->priv->chat_window), "destroy", G_CALLBACK(on_destroy), gp);
+ gtk_widget_show(gp->priv->chat_window);
+ }
+}
+
+void remmina_protocol_widget_chat_close(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ if (gp->priv->chat_window) {
+ gtk_widget_destroy(gp->priv->chat_window);
+ }
+}
+
+void remmina_protocol_widget_chat_receive(RemminaProtocolWidget* gp, const gchar* text)
+{
+ TRACE_CALL(__func__);
+ /* This function can be called from a non main thread */
+
+ if (gp->priv->chat_window) {
+ if ( !remmina_masterthread_exec_is_main_thread() ) {
+ /* Allow the execution of this function from a non main thread */
+ RemminaMTExecData *d;
+ d = (RemminaMTExecData*)g_malloc( sizeof(RemminaMTExecData) );
+ d->func = FUNC_CHAT_RECEIVE;
+ d->p.chat_receive.gp = gp;
+ d->p.chat_receive.text = text;
+ remmina_masterthread_exec_and_wait(d);
+ g_free(d);
+ return;
+ }
+ remmina_chat_window_receive(REMMINA_CHAT_WINDOW(gp->priv->chat_window), _("Server"), text);
+ gtk_window_present(GTK_WINDOW(gp->priv->chat_window));
+ }
+}
+
+GtkWidget* remmina_protocol_widget_new(void)
+{
+ return GTK_WIDGET(g_object_new(REMMINA_TYPE_PROTOCOL_WIDGET, NULL));
+}
+
+/* Send one or more keystrokes to a specific widget by firing key-press and
+ * key-release events.
+ * GdkEventType action can be GDK_KEY_PRESS or GDK_KEY_RELEASE or both to
+ * press the keys and release them in reversed order. */
+void remmina_protocol_widget_send_keys_signals(GtkWidget *widget, const guint *keyvals, int keyvals_length, GdkEventType action)
+{
+ TRACE_CALL(__func__);
+ int i;
+ GdkEventKey event;
+ gboolean result;
+ GdkKeymap *keymap = gdk_keymap_get_for_display(gdk_display_get_default());
+
+ event.window = gtk_widget_get_window(widget);
+ event.send_event = TRUE;
+ event.time = GDK_CURRENT_TIME;
+ event.state = 0;
+ event.length = 0;
+ event.string = "";
+ event.group = 0;
+
+ if (action & GDK_KEY_PRESS) {
+ /* Press the requested buttons */
+ event.type = GDK_KEY_PRESS;
+ for (i = 0; i < keyvals_length; i++) {
+ event.keyval = keyvals[i];
+ event.hardware_keycode = remmina_public_get_keycode_for_keyval(keymap, event.keyval);
+ event.is_modifier = (int)remmina_public_get_modifier_for_keycode(keymap, event.hardware_keycode);
+ g_signal_emit_by_name(G_OBJECT(widget), "key-press-event", &event, &result);
+ }
+ }
+
+ if (action & GDK_KEY_RELEASE) {
+ /* Release the requested buttons in reverse order */
+ event.type = GDK_KEY_RELEASE;
+ for (i = (keyvals_length - 1); i >= 0; i--) {
+ event.keyval = keyvals[i];
+ event.hardware_keycode = remmina_public_get_keycode_for_keyval(keymap, event.keyval);
+ event.is_modifier = (int)remmina_public_get_modifier_for_keycode(keymap, event.hardware_keycode);
+ g_signal_emit_by_name(G_OBJECT(widget), "key-release-event", &event, &result);
+ }
+ }
+}
+
+void remmina_protocol_widget_update_remote_resolution(RemminaProtocolWidget* gp, gint w, gint h)
+{
+ TRACE_CALL(__func__);
+ GdkDisplay *display;
+#if GTK_CHECK_VERSION(3, 20, 0)
+ /** @todo rename to "seat" */
+ GdkSeat *seat;
+ GdkDevice *device;
+#else
+ GdkDeviceManager *device_manager;
+ GdkDevice *device;
+#endif
+ GdkScreen *screen;
+#if GTK_CHECK_VERSION(3, 22, 0)
+ GdkMonitor *monitor;
+#else
+ gint monitor;
+#endif
+ gint x, y;
+ GdkRectangle rect;
+
+ if (w <= 0 || h <= 0) {
+ display = gdk_display_get_default();
+ /* gdk_display_get_device_manager deprecated since 3.20, Use gdk_display_get_default_seat */
+#if GTK_CHECK_VERSION(3, 20, 0)
+ seat = gdk_display_get_default_seat(display);
+ device = gdk_seat_get_pointer(seat);
+#else
+ device_manager = gdk_display_get_device_manager(display);
+ device = gdk_device_manager_get_client_pointer(device_manager);
+#endif
+ gdk_device_get_position(device, &screen, &x, &y);
+#if GTK_CHECK_VERSION(3, 22, 0)
+ monitor = gdk_display_get_monitor_at_point(display, x, y);
+ gdk_monitor_get_geometry(monitor, &rect);
+#else
+ monitor = gdk_screen_get_monitor_at_point(screen, x, y);
+ gdk_screen_get_monitor_geometry(screen, monitor, &rect);
+#endif
+ w = rect.width;
+ h = rect.height;
+ }
+ gp->priv->profile_remote_width = w;
+ gp->priv->profile_remote_height = h;
+}
+
+
+
diff --git a/src/remmina_protocol_widget.h b/src/remmina_protocol_widget.h
new file mode 100644
index 000000000..ed9ddbb2e
--- /dev/null
+++ b/src/remmina_protocol_widget.h
@@ -0,0 +1,161 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2011 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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.
+ *
+ */
+
+#pragma once
+
+#include "remmina_init_dialog.h"
+#include "remmina_file.h"
+#include "remmina_ssh.h"
+
+G_BEGIN_DECLS
+
+#define REMMINA_PROTOCOL_FEATURE_TOOL_SSH -1
+#define REMMINA_PROTOCOL_FEATURE_TOOL_SFTP -2
+
+#define REMMINA_TYPE_PROTOCOL_WIDGET (remmina_protocol_widget_get_type())
+#define REMMINA_PROTOCOL_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), REMMINA_TYPE_PROTOCOL_WIDGET, RemminaProtocolWidget))
+#define REMMINA_PROTOCOL_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), REMMINA_TYPE_PROTOCOL_WIDGET, RemminaProtocolWidgetClass))
+#define REMMINA_IS_PROTOCOL_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), REMMINA_TYPE_PROTOCOL_WIDGET))
+#define REMMINA_IS_PROTOCOL_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), REMMINA_TYPE_PROTOCOL_WIDGET))
+#define REMMINA_PROTOCOL_WIDGET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), REMMINA_TYPE_PROTOCOL_WIDGET, RemminaProtocolWidgetClass))
+
+typedef struct _RemminaProtocolWidgetPriv RemminaProtocolWidgetPriv;
+
+struct _RemminaProtocolWidget {
+ GtkEventBox event_box;
+
+ RemminaProtocolWidgetPriv *priv;
+};
+
+struct _RemminaProtocolWidgetClass {
+ GtkEventBoxClass parent_class;
+
+ void (*connect)(RemminaProtocolWidget *gp);
+ void (*disconnect)(RemminaProtocolWidget *gp);
+ void (*desktop_resize)(RemminaProtocolWidget *gp);
+ void (*update_align)(RemminaProtocolWidget *gp);
+ void (*unlock_dynres)(RemminaProtocolWidget *gp);
+};
+
+GType remmina_protocol_widget_get_type(void)
+G_GNUC_CONST;
+
+GtkWidget* remmina_protocol_widget_new(void);
+
+GtkWidget* remmina_protocol_widget_get_init_dialog(RemminaProtocolWidget *gp);
+
+gint remmina_protocol_widget_get_width(RemminaProtocolWidget *gp);
+void remmina_protocol_widget_set_width(RemminaProtocolWidget *gp, gint width);
+gint remmina_protocol_widget_get_height(RemminaProtocolWidget *gp);
+void remmina_protocol_widget_set_height(RemminaProtocolWidget *gp, gint height);
+gint remmina_protocol_widget_get_profile_remote_width(RemminaProtocolWidget* gp);
+gint remmina_protocol_widget_get_profile_remote_height(RemminaProtocolWidget* gp);
+
+RemminaScaleMode remmina_protocol_widget_get_current_scale_mode(RemminaProtocolWidget *gp);
+void remmina_protocol_widget_set_current_scale_mode(RemminaProtocolWidget *gp, RemminaScaleMode scalemode);
+gboolean remmina_protocol_widget_get_expand(RemminaProtocolWidget *gp);
+void remmina_protocol_widget_set_expand(RemminaProtocolWidget *gp, gboolean expand);
+gboolean remmina_protocol_widget_has_error(RemminaProtocolWidget *gp);
+gchar* remmina_protocol_widget_get_error_message(RemminaProtocolWidget *gp);
+void remmina_protocol_widget_set_error(RemminaProtocolWidget *gp, const gchar *fmt, ...);
+gboolean remmina_protocol_widget_is_closed(RemminaProtocolWidget *gp);
+RemminaFile* remmina_protocol_widget_get_file(RemminaProtocolWidget *gp);
+
+void remmina_protocol_widget_open_connection(RemminaProtocolWidget *gp, RemminaFile *remminafile);
+gboolean remmina_protocol_widget_close_connection(RemminaProtocolWidget *gp);
+void remmina_protocol_widget_grab_focus(RemminaProtocolWidget *gp);
+const RemminaProtocolFeature* remmina_protocol_widget_get_features(RemminaProtocolWidget *gp);
+const gchar* remmina_protocol_widget_get_domain(RemminaProtocolWidget *gp);
+gboolean remmina_protocol_widget_query_feature_by_type(RemminaProtocolWidget *gp, RemminaProtocolFeatureType type);
+gboolean remmina_protocol_widget_query_feature_by_ref(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature);
+void remmina_protocol_widget_call_feature_by_type(RemminaProtocolWidget *gp, RemminaProtocolFeatureType type, gint id);
+void remmina_protocol_widget_call_feature_by_ref(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature);
+/* Provide thread-safe way to emit signals */
+void remmina_protocol_widget_emit_signal(RemminaProtocolWidget *gp, const gchar *signal);
+void remmina_protocol_widget_register_hostkey(RemminaProtocolWidget *gp, GtkWidget *widget);
+
+typedef gboolean (*RemminaHostkeyFunc)(RemminaProtocolWidget *gp, guint keyval, gboolean release, gpointer data);
+void remmina_protocol_widget_set_hostkey_func(RemminaProtocolWidget *gp, RemminaHostkeyFunc func, gpointer data);
+
+gboolean remmina_protocol_widget_ssh_exec(RemminaProtocolWidget *gp, gboolean wait, const gchar *fmt, ...);
+
+/* Start a SSH tunnel if it's enabled. Returns a newly allocated string indicating:
+ * 1. The actual destination (host:port) if SSH tunnel is disable
+ * 2. The tunnel local destination (127.0.0.1:port) if SSH tunnel is enabled
+ */
+gchar* remmina_protocol_widget_start_direct_tunnel(RemminaProtocolWidget *gp, gint default_port, gboolean port_plus);
+
+gboolean remmina_protocol_widget_start_reverse_tunnel(RemminaProtocolWidget *gp, gint local_port);
+gboolean remmina_protocol_widget_start_xport_tunnel(RemminaProtocolWidget *gp, RemminaXPortTunnelInitFunc init_func);
+void remmina_protocol_widget_set_display(RemminaProtocolWidget *gp, gint display);
+
+gint remmina_protocol_widget_init_authpwd(RemminaProtocolWidget *gp, RemminaAuthpwdType authpwd_type, gboolean allow_password_saving);
+gint remmina_protocol_widget_init_authuserpwd(RemminaProtocolWidget *gp, gboolean want_domain, gboolean allow_password_saving);
+gint remmina_protocol_widget_init_certificate(RemminaProtocolWidget* gp, const gchar* subject, const gchar* issuer, const gchar* fingerprint);
+gint remmina_protocol_widget_changed_certificate(RemminaProtocolWidget *gp, const gchar* subject, const gchar* issuer, const gchar* new_fingerprint, const gchar* old_fingerprint);
+gchar* remmina_protocol_widget_init_get_username(RemminaProtocolWidget *gp);
+gchar* remmina_protocol_widget_init_get_password(RemminaProtocolWidget *gp);
+gchar* remmina_protocol_widget_init_get_domain(RemminaProtocolWidget *gp);
+gboolean remmina_protocol_widget_init_get_savepassword(RemminaProtocolWidget *gp);
+gint remmina_protocol_widget_init_authx509(RemminaProtocolWidget *gp);
+gchar* remmina_protocol_widget_init_get_cacert(RemminaProtocolWidget *gp);
+gchar* remmina_protocol_widget_init_get_cacrl(RemminaProtocolWidget *gp);
+gchar* remmina_protocol_widget_init_get_clientcert(RemminaProtocolWidget *gp);
+gchar* remmina_protocol_widget_init_get_clientkey(RemminaProtocolWidget *gp);
+void remmina_protocol_widget_init_save_cred(RemminaProtocolWidget *gp);
+void remmina_protocol_widget_init_show_listen(RemminaProtocolWidget *gp, gint port);
+void remmina_protocol_widget_init_show_retry(RemminaProtocolWidget *gp);
+void remmina_protocol_widget_init_show(RemminaProtocolWidget *gp);
+void remmina_protocol_widget_init_hide(RemminaProtocolWidget *gp);
+
+void remmina_protocol_widget_chat_open(RemminaProtocolWidget *gp, const gchar *name,
+ void (*on_send)(RemminaProtocolWidget *gp, const gchar *text), void (*on_destroy)(RemminaProtocolWidget *gp));
+void remmina_protocol_widget_chat_close(RemminaProtocolWidget *gp);
+void remmina_protocol_widget_chat_receive(RemminaProtocolWidget *gp, const gchar *text);
+void remmina_protocol_widget_send_keys_signals(GtkWidget *widget, const guint *keyvals, int length, GdkEventType action);
+/* Check if the plugin accepts keystrokes */
+gboolean remmina_protocol_widget_plugin_receives_keystrokes(RemminaProtocolWidget* gp);
+/* Send to the plugin some keystrokes */
+void remmina_protocol_widget_send_keystrokes(RemminaProtocolWidget* gp, GtkMenuItem *widget);
+/* Take screenshot of plugin */
+gboolean remmina_protocol_widget_plugin_screenshot(RemminaProtocolWidget* gp, RemminaPluginScreenshotData *rpsd);
+
+void remmina_protocol_widget_update_remote_resolution(RemminaProtocolWidget* gp, gint w, gint h);
+
+
+G_END_DECLS
+
+
diff --git a/src/remmina_public.c b/src/remmina_public.c
new file mode 100644
index 000000000..ad7ed6f77
--- /dev/null
+++ b/src/remmina_public.c
@@ -0,0 +1,701 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2011 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 "config.h"
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#ifdef HAVE_SYS_UN_H
+#include <sys/un.h>
+#endif
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+#endif
+#include "remmina_public.h"
+#include "remmina/remmina_trace_calls.h"
+
+GtkWidget*
+remmina_public_create_combo_entry(const gchar *text, const gchar *def, gboolean descending)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *combo;
+ gboolean found;
+ gchar *buf, *ptr1, *ptr2;
+ gint i;
+
+ combo = gtk_combo_box_text_new_with_entry();
+ found = FALSE;
+
+ if (text && text[0] != '\0') {
+ buf = g_strdup(text);
+ ptr1 = buf;
+ i = 0;
+ while (ptr1 && *ptr1 != '\0') {
+ ptr2 = strchr(ptr1, CHAR_DELIMITOR);
+ if (ptr2)
+ *ptr2++ = '\0';
+
+ if (descending) {
+ gtk_combo_box_text_prepend_text(GTK_COMBO_BOX_TEXT(combo), ptr1);
+ if (!found && g_strcmp0(ptr1, def) == 0) {
+ gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
+ found = TRUE;
+ }
+ }else {
+ gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), ptr1);
+ if (!found && g_strcmp0(ptr1, def) == 0) {
+ gtk_combo_box_set_active(GTK_COMBO_BOX(combo), i);
+ found = TRUE;
+ }
+ }
+
+ ptr1 = ptr2;
+ i++;
+ }
+
+ g_free(buf);
+ }
+
+ if (!found && def && def[0] != '\0') {
+ gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(combo))), def);
+ }
+
+ return combo;
+}
+
+GtkWidget*
+remmina_public_create_combo_text_d(const gchar *text, const gchar *def, const gchar *empty_choice)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *combo;
+ GtkListStore *store;
+ GtkCellRenderer *text_renderer;
+
+ store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
+ combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
+
+ text_renderer = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_end(GTK_CELL_LAYOUT(combo), text_renderer, TRUE);
+ gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(combo), text_renderer, "text", 1);
+
+ remmina_public_load_combo_text_d(combo, text, def, empty_choice);
+
+ return combo;
+}
+
+void remmina_public_load_combo_text_d(GtkWidget *combo, const gchar *text, const gchar *def, const gchar *empty_choice)
+{
+ TRACE_CALL(__func__);
+ GtkListStore *store;
+ GtkTreeIter iter;
+ gint i;
+ gchar *buf, *ptr1, *ptr2;
+
+ store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(combo)));
+ gtk_list_store_clear(store);
+
+ i = 0;
+
+ if (empty_choice) {
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, 0, "", 1, empty_choice, -1);
+ gtk_combo_box_set_active(GTK_COMBO_BOX(combo), i);
+ i++;
+ }
+
+ if (text == NULL || text[0] == '\0')
+ return;
+
+ buf = g_strdup(text);
+ ptr1 = buf;
+ while (ptr1 && *ptr1 != '\0') {
+ ptr2 = strchr(ptr1, CHAR_DELIMITOR);
+ if (ptr2)
+ *ptr2++ = '\0';
+
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, 0, ptr1, 1, ptr1, -1);
+
+ if (i == 0 || g_strcmp0(ptr1, def) == 0) {
+ gtk_combo_box_set_active(GTK_COMBO_BOX(combo), i);
+ }
+
+ i++;
+ ptr1 = ptr2;
+ }
+
+ g_free(buf);
+}
+
+GtkWidget*
+remmina_public_create_combo(gboolean use_icon)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *combo;
+ GtkListStore *store;
+ GtkCellRenderer *renderer;
+
+ if (use_icon) {
+ store = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
+ }else {
+ store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
+ }
+ combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
+ gtk_widget_set_hexpand(combo, TRUE);
+
+ if (use_icon) {
+ renderer = gtk_cell_renderer_pixbuf_new();
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, FALSE);
+ gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(combo), renderer, "icon-name", 2);
+ }
+ renderer = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE);
+ gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(combo), renderer, "text", 1);
+ if (use_icon)
+ g_object_set(G_OBJECT(renderer), "xpad", 5, NULL);
+
+ return combo;
+}
+
+GtkWidget*
+remmina_public_create_combo_map(const gpointer *key_value_list, const gchar *def, gboolean use_icon, const gchar *domain)
+{
+ TRACE_CALL(__func__);
+ gint i;
+ GtkWidget *combo;
+ GtkListStore *store;
+ GtkTreeIter iter;
+
+ combo = remmina_public_create_combo(use_icon);
+ store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(combo)));
+
+ for (i = 0; key_value_list[i]; i += (use_icon ? 3 : 2)) {
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(
+ store,
+ &iter,
+ 0,
+ key_value_list[i],
+ 1,
+ key_value_list[i + 1] && ((char*)key_value_list[i + 1])[0] ?
+ g_dgettext(domain, key_value_list[i + 1]) : "", -1);
+ if (use_icon) {
+ gtk_list_store_set(store, &iter, 2, key_value_list[i + 2], -1);
+ }
+ if (i == 0 || g_strcmp0(key_value_list[i], def) == 0) {
+ gtk_combo_box_set_active(GTK_COMBO_BOX(combo), i / (use_icon ? 3 : 2));
+ }
+ }
+ return combo;
+}
+
+GtkWidget*
+remmina_public_create_combo_mapint(const gpointer *key_value_list, gint def, gboolean use_icon, const gchar *domain)
+{
+ TRACE_CALL(__func__);
+ gchar buf[20];
+ g_snprintf(buf, sizeof(buf), "%i", def);
+ return remmina_public_create_combo_map(key_value_list, buf, use_icon, domain);
+}
+
+void remmina_public_create_group(GtkGrid *table, const gchar *group, gint row, gint rows, gint cols)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *widget;
+ gchar *str;
+
+ widget = gtk_label_new(NULL);
+ gtk_widget_show(widget);
+ gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
+ str = g_markup_printf_escaped("<b>%s</b>", group);
+ gtk_label_set_markup(GTK_LABEL(widget), str);
+ g_free(str);
+ gtk_grid_attach(GTK_GRID(table), widget, 0, row, 1, 2);
+
+ widget = gtk_label_new(NULL);
+ gtk_widget_show(widget);
+ gtk_grid_attach(GTK_GRID(table), widget, 0, row + 1, 1, 1);
+}
+
+gchar*
+remmina_public_combo_get_active_text(GtkComboBox *combo)
+{
+ TRACE_CALL(__func__);
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gchar *s;
+
+ if (GTK_IS_COMBO_BOX_TEXT(combo)) {
+ return gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(combo));
+ }
+
+ if (!gtk_combo_box_get_active_iter(combo, &iter))
+ return NULL;
+
+ model = gtk_combo_box_get_model(combo);
+ gtk_tree_model_get(model, &iter, 0, &s, -1);
+
+ return s;
+}
+
+#if !GTK_CHECK_VERSION(3, 22, 0)
+void remmina_public_popup_position(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *widget;
+ gint tx, ty;
+ GtkAllocation allocation;
+
+ widget = GTK_WIDGET(user_data);
+ if (gtk_widget_get_window(widget) == NULL) {
+ *x = 0;
+ *y = 0;
+ *push_in = TRUE;
+ return;
+ }
+ gdk_window_get_origin(gtk_widget_get_window(widget), &tx, &ty);
+ gtk_widget_get_allocation(widget, &allocation);
+ /* I'm unsure why the author made the check about a GdkWindow inside the
+ * widget argument. This function generally is called passing by a ToolButton
+ * which hasn't any GdkWindow, therefore the positioning is wrong
+ * I think the gtk_widget_get_has_window() check should be removed
+ *
+ * While leaving the previous check intact I'm checking also if the provided
+ * widget is a GtkToggleToolButton and position the menu accordingly. */
+ if (gtk_widget_get_has_window(widget) ||
+ g_strcmp0(gtk_widget_get_name(widget), "GtkToggleToolButton") == 0) {
+ tx += allocation.x;
+ ty += allocation.y;
+ }
+
+ *x = tx;
+ *y = ty + allocation.height - 1;
+ *push_in = TRUE;
+}
+#endif
+
+gchar*
+remmina_public_combine_path(const gchar *path1, const gchar *path2)
+{
+ TRACE_CALL(__func__);
+ if (!path1 || path1[0] == '\0')
+ return g_strdup(path2);
+ if (path1[strlen(path1) - 1] == '/')
+ return g_strdup_printf("%s%s", path1, path2);
+ return g_strdup_printf("%s/%s", path1, path2);
+}
+
+void remmina_public_get_server_port(const gchar *server, gint defaultport, gchar **host, gint *port)
+{
+ TRACE_CALL(__func__);
+ gchar *str, *ptr, *ptr2;
+
+ str = g_strdup(server);
+
+ if (str) {
+ /* [server]:port format */
+ ptr = strchr(str, '[');
+ if (ptr) {
+ ptr++;
+ ptr2 = strchr(ptr, ']');
+ if (ptr2) {
+ *ptr2++ = '\0';
+ if (*ptr2 == ':')
+ defaultport = atoi(ptr2 + 1);
+ }
+ if (host)
+ *host = g_strdup(ptr);
+ if (port)
+ *port = defaultport;
+ g_free(str);
+ return;
+ }
+
+ /* server:port format, IPv6 cannot use this format */
+ ptr = strchr(str, ':');
+ if (ptr) {
+ ptr2 = strchr(ptr + 1, ':');
+ if (ptr2 == NULL) {
+ *ptr++ = '\0';
+ defaultport = atoi(ptr);
+ }
+ /* More than one ':' means this is IPv6 address. Treat it as a whole address */
+ }
+ }
+
+ if (host)
+ *host = str;
+ else
+ g_free(str);
+ if (port)
+ *port = defaultport;
+}
+
+gboolean remmina_public_get_xauth_cookie(const gchar *display, gchar **msg)
+{
+ TRACE_CALL(__func__);
+ gchar buf[200];
+ gchar *out = NULL;
+ gchar *ptr;
+ GError *error = NULL;
+ gboolean ret;
+
+ if (!display)
+ display = gdk_display_get_name(gdk_display_get_default());
+
+ g_snprintf(buf, sizeof(buf), "xauth list %s", display);
+ ret = g_spawn_command_line_sync(buf, &out, NULL, NULL, &error);
+ if (ret) {
+ if ((ptr = g_strrstr(out, "MIT-MAGIC-COOKIE-1")) == NULL) {
+ *msg = g_strdup_printf("xauth returns %s", out);
+ ret = FALSE;
+ }else {
+ ptr += 19;
+ while (*ptr == ' ')
+ ptr++;
+ *msg = g_strndup(ptr, 32);
+ }
+ g_free(out);
+ }else {
+ *msg = g_strdup(error->message);
+ }
+ return ret;
+}
+
+gint remmina_public_open_xdisplay(const gchar *disp)
+{
+ TRACE_CALL(__func__);
+ gchar *display;
+ gchar *ptr;
+ gint port;
+ struct sockaddr_un addr;
+ gint sock = -1;
+
+ display = g_strdup(disp);
+ ptr = g_strrstr(display, ":");
+ if (ptr) {
+ *ptr++ = '\0';
+ /* Assume you are using a local display... might need to implement remote display in the future */
+ if (display[0] == '\0' || strcmp(display, "unix") == 0) {
+ port = atoi(ptr);
+ sock = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (sock >= 0) {
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ snprintf(addr.sun_path, sizeof(addr.sun_path), X_UNIX_SOCKET, port);
+ if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
+ close(sock);
+ sock = -1;
+ }
+ }
+ }
+ }
+
+ g_free(display);
+ return sock;
+}
+
+/* This function was copied from GEdit (gedit-utils.c). */
+guint remmina_public_get_current_workspace(GdkScreen *screen)
+{
+ TRACE_CALL(__func__);
+#ifdef GDK_WINDOWING_X11
+#if GTK_CHECK_VERSION(3, 10, 0)
+ g_return_val_if_fail(GDK_IS_SCREEN(screen), 0);
+ if (GDK_IS_X11_DISPLAY(gdk_screen_get_display(screen)))
+ return gdk_x11_screen_get_current_desktop(screen);
+ else
+ return 0;
+
+#else
+ GdkWindow *root_win;
+ GdkDisplay *display;
+ Atom type;
+ gint format;
+ gulong nitems;
+ gulong bytes_after;
+ guint *current_desktop;
+ gint err, result;
+ guint ret = 0;
+
+ g_return_val_if_fail(GDK_IS_SCREEN(screen), 0);
+
+ root_win = gdk_screen_get_root_window(screen);
+ display = gdk_screen_get_display(screen);
+
+ gdk_error_trap_push();
+ result = XGetWindowProperty(GDK_DISPLAY_XDISPLAY(display), GDK_WINDOW_XID(root_win),
+ gdk_x11_get_xatom_by_name_for_display(display, "_NET_CURRENT_DESKTOP"),
+ 0, G_MAXLONG, False, XA_CARDINAL, &type, &format, &nitems,
+ &bytes_after, (gpointer) & current_desktop);
+ err = gdk_error_trap_pop();
+
+ if (err != Success || result != Success)
+ return ret;
+
+ if (type == XA_CARDINAL && format == 32 && nitems > 0)
+ ret = current_desktop[0];
+
+ XFree(current_desktop);
+ return ret;
+#endif
+#else
+ /* FIXME: on mac etc proably there are native APIs
+ * to get the current workspace etc */
+ return 0;
+#endif
+}
+
+/* This function was copied from GEdit (gedit-utils.c). */
+guint remmina_public_get_window_workspace(GtkWindow *gtkwindow)
+{
+ TRACE_CALL(__func__);
+#ifdef GDK_WINDOWING_X11
+#if GTK_CHECK_VERSION(3, 10, 0)
+ GdkWindow *window;
+ g_return_val_if_fail(GTK_IS_WINDOW(gtkwindow), 0);
+ g_return_val_if_fail(gtk_widget_get_realized(GTK_WIDGET(gtkwindow)), 0);
+ window = gtk_widget_get_window(GTK_WIDGET(gtkwindow));
+ if (GDK_IS_X11_DISPLAY(gdk_window_get_display(window)))
+ return gdk_x11_window_get_desktop(window);
+ else
+ return 0;
+#else
+ GdkWindow *window;
+ GdkDisplay *display;
+ Atom type;
+ gint format;
+ gulong nitems;
+ gulong bytes_after;
+ guint *workspace;
+ gint err, result;
+ guint ret = 0;
+
+ g_return_val_if_fail(GTK_IS_WINDOW(gtkwindow), 0);
+ g_return_val_if_fail(gtk_widget_get_realized(GTK_WIDGET(gtkwindow)), 0);
+
+ window = gtk_widget_get_window(GTK_WIDGET(gtkwindow));
+ display = gdk_window_get_display(window);
+
+ gdk_error_trap_push();
+ result = XGetWindowProperty(GDK_DISPLAY_XDISPLAY(display), GDK_WINDOW_XID(window),
+ gdk_x11_get_xatom_by_name_for_display(display, "_NET_WM_DESKTOP"),
+ 0, G_MAXLONG, False, XA_CARDINAL, &type, &format, &nitems,
+ &bytes_after, (gpointer) & workspace);
+ err = gdk_error_trap_pop();
+
+ if (err != Success || result != Success)
+ return ret;
+
+ if (type == XA_CARDINAL && format == 32 && nitems > 0)
+ ret = workspace[0];
+
+ XFree(workspace);
+ return ret;
+#endif
+#else
+ /* FIXME: on mac etc proably there are native APIs
+ * to get the current workspace etc */
+ return 0;
+#endif
+}
+
+/* Find hardware keycode for the requested keyval */
+guint16 remmina_public_get_keycode_for_keyval(GdkKeymap *keymap, guint keyval)
+{
+ TRACE_CALL(__func__);
+ GdkKeymapKey *keys = NULL;
+ gint length = 0;
+ guint16 keycode = 0;
+
+ if (gdk_keymap_get_entries_for_keyval(keymap, keyval, &keys, &length)) {
+ keycode = keys[0].keycode;
+ g_free(keys);
+ }
+ return keycode;
+}
+
+/* Check if the requested keycode is a key modifier */
+gboolean remmina_public_get_modifier_for_keycode(GdkKeymap *keymap, guint16 keycode)
+{
+ TRACE_CALL(__func__);
+ g_return_val_if_fail(keycode > 0, FALSE);
+#ifdef GDK_WINDOWING_X11
+ return gdk_x11_keymap_key_is_modifier(keymap, keycode);
+#else
+ return FALSE;
+#endif
+}
+
+/* Load a GtkBuilder object from a filename */
+GtkBuilder* remmina_public_gtk_builder_new_from_file(gchar *filename)
+{
+ TRACE_CALL(__func__);
+ gchar *ui_path = g_strconcat(REMMINA_RUNTIME_UIDIR, G_DIR_SEPARATOR_S, filename, NULL);
+#if GTK_CHECK_VERSION(3, 10, 0)
+ GtkBuilder *builder = gtk_builder_new_from_file(ui_path);
+#else
+ GtkBuilder *builder = gtk_builder_new();
+ gtk_builder_add_from_file(builder, ui_path, NULL);
+#endif
+ g_free(ui_path);
+ return builder;
+}
+
+/* Change parent container for a widget
+ * If possible use this function instead of the deprecated gtk_widget_reparent */
+void remmina_public_gtk_widget_reparent(GtkWidget *widget, GtkContainer *container)
+{
+ TRACE_CALL(__func__);
+ g_object_ref(widget);
+ gtk_container_remove(GTK_CONTAINER(gtk_widget_get_parent(widget)), widget);
+ gtk_container_add(container, widget);
+ g_object_unref(widget);
+}
+
+/* Validate the inserted value for a new resolution */
+gboolean remmina_public_resolution_validation_func(const gchar *new_str, gchar **error)
+{
+ TRACE_CALL(__func__);
+ gint i;
+ gint width, height;
+ gboolean splitted;
+ gboolean result;
+
+ width = 0;
+ height = 0;
+ splitted = FALSE;
+ result = TRUE;
+ for (i = 0; new_str[i] != '\0'; i++) {
+ if (new_str[i] == 'x') {
+ if (splitted) {
+ result = FALSE;
+ break;
+ }
+ splitted = TRUE;
+ continue;
+ }
+ if (new_str[i] < '0' || new_str[i] > '9') {
+ result = FALSE;
+ break;
+ }
+ if (splitted) {
+ height = 1;
+ }else {
+ width = 1;
+ }
+ }
+
+ if (width == 0 || height == 0)
+ result = FALSE;
+
+ if (!result)
+ *error = g_strdup(_("Please enter format 'widthxheight'."));
+ return result;
+}
+
+/* Used to send desktop notifications */
+void remmina_public_send_notification(const gchar *notification_id,
+ const gchar *notification_title, const gchar *notification_message)
+{
+ TRACE_CALL(__func__);
+
+ GNotification *notification = g_notification_new(notification_title);
+ g_notification_set_body(notification, notification_message);
+#if GLIB_CHECK_VERSION(2, 42, 0)
+ g_notification_set_priority(notification, G_NOTIFICATION_PRIORITY_NORMAL);
+#endif
+ g_application_send_notification(g_application_get_default(), notification_id, notification);
+ g_object_unref(notification);
+}
+
+/* Replaces all occurences of search in a new copy of string by replacement. */
+gchar* remmina_public_str_replace(const gchar *string, const gchar *search, const gchar *replacement)
+{
+ TRACE_CALL(__func__);
+ gchar *str, **arr;
+
+ g_return_val_if_fail(string != NULL, NULL);
+ g_return_val_if_fail(search != NULL, NULL);
+
+ if (replacement == NULL)
+ replacement = "";
+
+ arr = g_strsplit(string, search, -1);
+ if (arr != NULL && arr[0] != NULL)
+ str = g_strjoinv(replacement, arr);
+ else
+ str = g_strdup(string);
+
+ g_strfreev(arr);
+ return str;
+}
+
+/* Replaces all occurences of search in a new copy of string by replacement
+ * and overwrites the original string */
+gchar* remmina_public_str_replace_in_place(gchar *string, const gchar *search, const gchar *replacement)
+{
+ TRACE_CALL(__func__);
+ gchar *new_string = remmina_public_str_replace(string, search, replacement);
+ g_free(string);
+ string = g_strdup(new_string);
+ return string;
+}
+
+int remmina_public_split_resolution_string(const char *resolution_string, int *w, int *h)
+{
+ int lw, lh;
+
+ if (resolution_string == NULL || resolution_string[0] == 0)
+ return 0;
+ if (sscanf(resolution_string, "%dx%d", &lw, &lh) != 2)
+ return 0;
+ *w = lw;
+ *h = lh;
+ return 1;
+}
diff --git a/src/remmina_public.h b/src/remmina_public.h
new file mode 100644
index 000000000..ecbf96718
--- /dev/null
+++ b/src/remmina_public.h
@@ -0,0 +1,118 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2011 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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.
+ *
+ */
+
+#pragma once
+
+#include "config.h"
+
+#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_PATH_LEN 255
+
+#define MAX_X_DISPLAY_NUMBER 99
+#define X_UNIX_SOCKET "/tmp/.X11-unix/X%d"
+
+#define CHAR_DELIMITOR ','
+#define STRING_DELIMITOR ","
+#define STRING_DELIMITOR2 "§"
+
+#define MOUSE_BUTTON_LEFT 1
+#define MOUSE_BUTTON_MIDDLE 2
+#define MOUSE_BUTTON_RIGHT 3
+
+/* Bind a template widget to its class member and callback */
+#define BIND_TEMPLATE_CHILD(wc, type, action, callback) \
+ gtk_widget_class_bind_template_child(wc, type, action); \
+ gtk_widget_class_bind_template_callback(wc, callback);
+
+G_BEGIN_DECLS
+
+/* items is separated by STRING_DELIMTOR */
+GtkWidget* remmina_public_create_combo_entry(const gchar *text, const gchar *def, gboolean descending);
+GtkWidget* remmina_public_create_combo_text_d(const gchar *text, const gchar *def, const gchar *empty_choice);
+void remmina_public_load_combo_text_d(GtkWidget *combo, const gchar *text, const gchar *def, const gchar *empty_choice);
+GtkWidget* remmina_public_create_combo(gboolean use_icon);
+GtkWidget* remmina_public_create_combo_map(const gpointer *key_value_list, const gchar *def, gboolean use_icon,
+ const gchar *domain);
+GtkWidget* remmina_public_create_combo_mapint(const gpointer *key_value_list, gint def, gboolean use_icon, const gchar *domain);
+
+void remmina_public_create_group(GtkGrid *table, const gchar *group, gint row, gint rows, gint cols);
+
+gchar* remmina_public_combo_get_active_text(GtkComboBox *combo);
+
+#if !GTK_CHECK_VERSION(3, 22, 0)
+/* A function for gtk_menu_popup to get the position right below the widget specified by user_data */
+void remmina_public_popup_position(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer user_data);
+#endif
+
+/* Combine two paths into one by correctly handling trailing slash. Return newly allocated string */
+gchar* remmina_public_combine_path(const gchar *path1, const gchar *path2);
+
+/* Parse a server entry with server name and port */
+void remmina_public_get_server_port(const gchar *server, gint defaultport, gchar **host, gint *port);
+
+/* X */
+gboolean remmina_public_get_xauth_cookie(const gchar *display, gchar **msg);
+gint remmina_public_open_xdisplay(const gchar *disp);
+guint remmina_public_get_current_workspace(GdkScreen *screen);
+guint remmina_public_get_window_workspace(GtkWindow *gtkwindow);
+
+/* Find hardware keycode for the requested keyval */
+guint16 remmina_public_get_keycode_for_keyval(GdkKeymap *keymap, guint keyval);
+/* Check if the requested keycode is a key modifier */
+gboolean remmina_public_get_modifier_for_keycode(GdkKeymap *keymap, guint16 keycode);
+/* Load a GtkBuilder object from a filename */
+GtkBuilder* remmina_public_gtk_builder_new_from_file(gchar *filename);
+/* Change parent container for a widget */
+void remmina_public_gtk_widget_reparent(GtkWidget *widget, GtkContainer *container);
+/* Used to send desktop notifications */
+void remmina_public_send_notification(const gchar *notification_id,
+ const gchar *notification_title, const gchar *notification_message);
+/* Validate the inserted value for a new resolution */
+gboolean remmina_public_resolution_validation_func(const gchar *new_str, gchar **error);
+/* Replaces all occurences of search in a new copy of string by replacement. */
+gchar* remmina_public_str_replace(const gchar *string, const gchar *search, const gchar *replacement);
+/* Replaces all occurences of search in a new copy of string by replacement
+ * and overwrites the original string */
+gchar* remmina_public_str_replace_in_place(gchar *string, const gchar *search, const gchar *replacement);
+int remmina_public_split_resolution_string(const char *resolution_string, int *w, int *h);
diff --git a/src/remmina_scrolled_viewport.c b/src/remmina_scrolled_viewport.c
new file mode 100644
index 000000000..a4ffd4f9b
--- /dev/null
+++ b/src/remmina_scrolled_viewport.c
@@ -0,0 +1,203 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009 - Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 <gtk/gtk.h>
+#include "config.h"
+#include "remmina_scrolled_viewport.h"
+#include "remmina_pref.h"
+#include "remmina/remmina_trace_calls.h"
+
+G_DEFINE_TYPE( RemminaScrolledViewport, remmina_scrolled_viewport, GTK_TYPE_EVENT_BOX)
+
+static void remmina_scrolled_viewport_get_preferred_width(GtkWidget* widget, gint* minimum_width, gint* natural_width)
+{
+ TRACE_CALL(__func__);
+ /* Just return a fake small size, so gtk_window_fullscreen() will not fail
+ * because our content is too big*/
+ if (minimum_width != NULL) *minimum_width = 100;
+ if (natural_width != NULL) *natural_width = 100;
+}
+
+static void remmina_scrolled_viewport_get_preferred_height(GtkWidget* widget, gint* minimum_height, gint* natural_height)
+{
+ TRACE_CALL(__func__);
+ /* Just return a fake small size, so gtk_window_fullscreen() will not fail
+ * because our content is too big*/
+ if (minimum_height != NULL) *minimum_height = 100;
+ if (natural_height != NULL) *natural_height = 100;
+}
+
+/* Event handler when mouse move on borders */
+static gboolean remmina_scrolled_viewport_motion_timeout(gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaScrolledViewport *gsv;
+ GtkWidget *child;
+ GdkDisplay *display;
+#if GTK_CHECK_VERSION(3, 20, 0)
+ GdkSeat *seat;
+#else
+ GdkDeviceManager *device_manager;
+#endif
+ GdkDevice *pointer;
+ GdkScreen *screen;
+ GdkWindow *gsvwin;
+ gint x, y, mx, my, w, h, rootx, rooty;
+ GtkAdjustment *adj;
+ gdouble value;
+
+ if (!REMMINA_IS_SCROLLED_VIEWPORT(data))
+ return FALSE;
+ if (!GTK_IS_BIN(data))
+ return FALSE;
+ gsv = REMMINA_SCROLLED_VIEWPORT(data);
+ if (!gsv->viewport_motion)
+ return FALSE;
+ child = gtk_bin_get_child(GTK_BIN(gsv));
+ if (!GTK_IS_VIEWPORT(child))
+ return FALSE;
+
+ gsvwin = gtk_widget_get_window(GTK_WIDGET(gsv));
+ if (!gsv)
+ return FALSE;
+
+ display = gdk_display_get_default();
+ if (!display)
+ return FALSE;
+#if GTK_CHECK_VERSION(3, 20, 0)
+ seat = gdk_display_get_default_seat(display);
+ pointer = gdk_seat_get_pointer(seat);
+#else
+ device_manager = gdk_display_get_device_manager(display);
+ pointer = gdk_device_manager_get_client_pointer(device_manager);
+#endif
+ gdk_device_get_position(pointer, &screen, &x, &y);
+
+ w = gdk_window_get_width(gsvwin) + 2; // Add 2px of black scroll border
+ h = gdk_window_get_height(gsvwin) + 2; // Add 2px of black scroll border
+
+ gdk_window_get_root_origin(gsvwin, &rootx, &rooty );
+
+ x -= rootx;
+ y -= rooty;
+
+ mx = (x == 0 ? -1 : (x >= w - 1 ? 1 : 0));
+ my = (y == 0 ? -1 : (y >= h - 1 ? 1 : 0));
+ if (mx != 0) {
+ gint step = MAX(10, MIN(remmina_pref.auto_scroll_step, w / 5));
+ adj = gtk_scrollable_get_hadjustment(GTK_SCROLLABLE(child));
+ value = gtk_adjustment_get_value(GTK_ADJUSTMENT(adj)) + (gdouble)(mx * step);
+ value = MAX(0, MIN(value, gtk_adjustment_get_upper(GTK_ADJUSTMENT(adj)) - (gdouble)w + 2.0));
+ gtk_adjustment_set_value(GTK_ADJUSTMENT(adj), value);
+ }
+ if (my != 0) {
+ gint step = MAX(10, MIN(remmina_pref.auto_scroll_step, h / 5));
+ adj = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(child));
+ value = gtk_adjustment_get_value(GTK_ADJUSTMENT(adj)) + (gdouble)(my * step);
+ value = MAX(0, MIN(value, gtk_adjustment_get_upper(GTK_ADJUSTMENT(adj)) - (gdouble)h + 2.0));
+ gtk_adjustment_set_value(GTK_ADJUSTMENT(adj), value);
+ }
+ return TRUE;
+}
+
+static gboolean remmina_scrolled_viewport_enter(GtkWidget *widget, GdkEventCrossing *event, gpointer data)
+{
+ TRACE_CALL(__func__);
+ remmina_scrolled_viewport_remove_motion(REMMINA_SCROLLED_VIEWPORT(widget));
+ return FALSE;
+}
+
+static gboolean remmina_scrolled_viewport_leave(GtkWidget *widget, GdkEventCrossing *event, gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaScrolledViewport *gsv = REMMINA_SCROLLED_VIEWPORT(widget);
+ gsv->viewport_motion = TRUE;
+ gsv->viewport_motion_handler = g_timeout_add(20, remmina_scrolled_viewport_motion_timeout, gsv);
+ return FALSE;
+}
+
+static void remmina_scrolled_viewport_destroy(GtkWidget *widget, gpointer data)
+{
+ TRACE_CALL(__func__);
+ remmina_scrolled_viewport_remove_motion(REMMINA_SCROLLED_VIEWPORT(widget));
+}
+
+static void remmina_scrolled_viewport_class_init(RemminaScrolledViewportClass *klass)
+{
+ TRACE_CALL(__func__);
+ GtkWidgetClass *widget_class;
+ widget_class = (GtkWidgetClass*)klass;
+
+ widget_class->get_preferred_width = remmina_scrolled_viewport_get_preferred_width;
+ widget_class->get_preferred_height = remmina_scrolled_viewport_get_preferred_height;
+
+}
+
+static void remmina_scrolled_viewport_init(RemminaScrolledViewport *gsv)
+{
+ TRACE_CALL(__func__);
+}
+
+void remmina_scrolled_viewport_remove_motion(RemminaScrolledViewport *gsv)
+{
+ TRACE_CALL(__func__);
+ if (gsv->viewport_motion) {
+ gsv->viewport_motion = FALSE;
+ g_source_remove(gsv->viewport_motion_handler);
+ gsv->viewport_motion_handler = 0;
+ }
+}
+
+GtkWidget*
+remmina_scrolled_viewport_new(void)
+{
+ TRACE_CALL(__func__);
+ RemminaScrolledViewport *gsv;
+
+ gsv = REMMINA_SCROLLED_VIEWPORT(g_object_new(REMMINA_TYPE_SCROLLED_VIEWPORT, NULL));
+
+ gsv->viewport_motion = FALSE;
+ gsv->viewport_motion_handler = 0;
+
+ gtk_widget_set_size_request(GTK_WIDGET(gsv), 1, 1);
+ gtk_widget_add_events(GTK_WIDGET(gsv), GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
+ g_signal_connect(G_OBJECT(gsv), "destroy", G_CALLBACK(remmina_scrolled_viewport_destroy), NULL);
+ g_signal_connect(G_OBJECT(gsv), "enter-notify-event", G_CALLBACK(remmina_scrolled_viewport_enter), NULL);
+ g_signal_connect(G_OBJECT(gsv), "leave-notify-event", G_CALLBACK(remmina_scrolled_viewport_leave), NULL);
+
+ return GTK_WIDGET(gsv);
+}
+
diff --git a/src/remmina_scrolled_viewport.h b/src/remmina_scrolled_viewport.h
new file mode 100644
index 000000000..8caae965d
--- /dev/null
+++ b/src/remmina_scrolled_viewport.h
@@ -0,0 +1,74 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009 - Vic Lee
+ * Copyright (C) 2017-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.
+ *
+ */
+
+#pragma once
+
+G_BEGIN_DECLS
+
+#define REMMINA_TYPE_SCROLLED_VIEWPORT \
+ (remmina_scrolled_viewport_get_type())
+#define REMMINA_SCROLLED_VIEWPORT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), REMMINA_TYPE_SCROLLED_VIEWPORT, RemminaScrolledViewport))
+#define REMMINA_SCROLLED_VIEWPORT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), REMMINA_TYPE_SCROLLED_VIEWPORT, RemminaScrolledViewportClass))
+#define REMMINA_IS_SCROLLED_VIEWPORT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), REMMINA_TYPE_SCROLLED_VIEWPORT))
+#define REMMINA_IS_SCROLLED_VIEWPORT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), REMMINA_TYPE_SCROLLED_VIEWPORT))
+#define REMMINA_SCROLLED_VIEWPORT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS((obj), REMMINA_TYPE_SCROLLED_VIEWPORT, RemminaScrolledViewportClass))
+
+typedef struct _RemminaScrolledViewport {
+ GtkEventBox event_box;
+
+ /* Motion activates in Viewport Fullscreen mode */
+ gboolean viewport_motion;
+ guint viewport_motion_handler;
+
+} RemminaScrolledViewport;
+
+typedef struct _RemminaScrolledViewportClass {
+ GtkEventBoxClass parent_class;
+} RemminaScrolledViewportClass;
+
+GType remmina_scrolled_viewport_get_type(void)
+G_GNUC_CONST;
+
+GtkWidget* remmina_scrolled_viewport_new(void);
+void remmina_scrolled_viewport_remove_motion(RemminaScrolledViewport *gsv);
+
+G_END_DECLS
+
+
diff --git a/src/remmina_sftp_client.c b/src/remmina_sftp_client.c
new file mode 100644
index 000000000..f472da89c
--- /dev/null
+++ b/src/remmina_sftp_client.c
@@ -0,0 +1,1000 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2010 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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.
+ *
+ */
+
+#define _FILE_OFFSET_BITS 64
+#include "config.h"
+
+#ifdef HAVE_LIBSSH
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <pthread.h>
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#include "remmina_public.h"
+#include "remmina_pref.h"
+#include "remmina_ssh.h"
+#include "remmina_sftp_client.h"
+#include "remmina_masterthread_exec.h"
+#include "remmina/remmina_trace_calls.h"
+
+G_DEFINE_TYPE(RemminaSFTPClient, remmina_sftp_client, REMMINA_TYPE_FTP_CLIENT)
+
+#define SET_CURSOR(cur) \
+ if (GDK_IS_WINDOW(gtk_widget_get_window(GTK_WIDGET(client)))) \
+ { \
+ gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(client)), cur); \
+ }
+
+static void
+remmina_sftp_client_class_init(RemminaSFTPClientClass *klass)
+{
+ TRACE_CALL(__func__);
+}
+
+#define GET_SFTPATTR_TYPE(a, type) \
+ if (a->type == 0) \
+ { \
+ type = ((a->permissions & 040000) ? REMMINA_FTP_FILE_TYPE_DIR : REMMINA_FTP_FILE_TYPE_FILE); \
+ } \
+ else \
+ { \
+ type = (a->type == SSH_FILEXFER_TYPE_DIRECTORY ? REMMINA_FTP_FILE_TYPE_DIR : REMMINA_FTP_FILE_TYPE_FILE); \
+ }
+
+/* ------------------------ The Task Thread routines ----------------------------- */
+
+static gboolean remmina_sftp_client_refresh(RemminaSFTPClient *client);
+
+#define THREAD_CHECK_EXIT \
+ (!client->taskid || client->thread_abort)
+
+
+
+static gboolean
+remmina_sftp_client_thread_update_task(RemminaSFTPClient *client, RemminaFTPTask *task)
+{
+ TRACE_CALL(__func__);
+ if (THREAD_CHECK_EXIT) return FALSE;
+
+ remmina_ftp_client_update_task(REMMINA_FTP_CLIENT(client), task);
+
+ return TRUE;
+}
+
+static void
+remmina_sftp_client_thread_set_error(RemminaSFTPClient *client, RemminaFTPTask *task, const gchar *error_format, ...)
+{
+ TRACE_CALL(__func__);
+ va_list args;
+
+ task->status = REMMINA_FTP_TASK_STATUS_ERROR;
+ g_free(task->tooltip);
+ if (error_format) {
+ va_start(args, error_format);
+ task->tooltip = g_strdup_vprintf(error_format, args);
+ va_end(args);
+ }else {
+ task->tooltip = NULL;
+ }
+
+ remmina_sftp_client_thread_update_task(client, task);
+}
+
+static void
+remmina_sftp_client_thread_set_finish(RemminaSFTPClient *client, RemminaFTPTask *task)
+{
+ TRACE_CALL(__func__);
+ task->status = REMMINA_FTP_TASK_STATUS_FINISH;
+ g_free(task->tooltip);
+ task->tooltip = NULL;
+
+ remmina_sftp_client_thread_update_task(client, task);
+}
+
+static RemminaFTPTask*
+remmina_sftp_client_thread_get_task(RemminaSFTPClient *client)
+{
+ TRACE_CALL(__func__);
+ RemminaFTPTask *task;
+
+ if (client->thread_abort) return NULL;
+
+ task = remmina_ftp_client_get_waiting_task(REMMINA_FTP_CLIENT(client));
+ if (task) {
+ client->taskid = task->taskid;
+
+ task->status = REMMINA_FTP_TASK_STATUS_RUN;
+ remmina_ftp_client_update_task(REMMINA_FTP_CLIENT(client), task);
+ }
+
+ return task;
+}
+
+static gboolean
+remmina_sftp_client_thread_download_file(RemminaSFTPClient *client, RemminaSFTP *sftp, RemminaFTPTask *task,
+ const gchar *remote_path, const gchar *local_path, guint64 *donesize)
+{
+ TRACE_CALL(__func__);
+ sftp_file remote_file;
+ FILE *local_file;
+ gchar *tmp;
+ gchar buf[20480];
+ gint len;
+ gint response;
+ uint64_t size;
+
+ if (THREAD_CHECK_EXIT) return FALSE;
+
+ /* Ensure local dir exists */
+ g_strlcpy(buf, local_path, sizeof(buf));
+ tmp = g_strrstr(buf, "/");
+ if (tmp && tmp != buf) {
+ *tmp = '\0';
+ if (g_mkdir_with_parents(buf, 0755) < 0) {
+ remmina_sftp_client_thread_set_error(client, task, _("Error creating directory %s."), buf);
+ return FALSE;
+ }
+ }
+
+ local_file = g_fopen(local_path, "ab");
+ if (!local_file) {
+ remmina_sftp_client_thread_set_error(client, task, _("Error creating file %s."), local_path);
+ return FALSE;
+ }
+
+ fseeko(local_file, 0, SEEK_END);
+ size = ftello(local_file);
+ if (size > 0) {
+ response = remmina_sftp_client_confirm_resume(client, local_path);
+
+ switch (response) {
+ case GTK_RESPONSE_CANCEL:
+ case GTK_RESPONSE_DELETE_EVENT:
+ fclose(local_file);
+ remmina_sftp_client_thread_set_error(client, task, NULL);
+ return FALSE;
+
+ case GTK_RESPONSE_ACCEPT:
+ fclose(local_file);
+ local_file = g_fopen(local_path, "wb");
+ if (!local_file) {
+ remmina_sftp_client_thread_set_error(client, task, _("Error creating file %s."), local_path);
+ return FALSE;
+ }
+ size = 0;
+ break;
+
+ case GTK_RESPONSE_APPLY:
+ break;
+ }
+ }
+
+ tmp = remmina_ssh_unconvert(REMMINA_SSH(sftp), remote_path);
+ remote_file = sftp_open(sftp->sftp_sess, tmp, O_RDONLY, 0);
+ g_free(tmp);
+
+ if (!remote_file) {
+ fclose(local_file);
+ remmina_sftp_client_thread_set_error(client, task, _("Error opening file %s on server. %s"),
+ remote_path, ssh_get_error(REMMINA_SSH(client->sftp)->session));
+ return FALSE;
+ }
+
+ if (size > 0) {
+ if (sftp_seek64(remote_file, size) < 0) {
+ sftp_close(remote_file);
+ fclose(local_file);
+ remmina_sftp_client_thread_set_error(client, task, "Error seeking remote file %s. %s",
+ remote_path, ssh_get_error(REMMINA_SSH(client->sftp)->session));
+ return FALSE;
+ }
+ *donesize = size;
+ }
+
+ while (!THREAD_CHECK_EXIT && (len = sftp_read(remote_file, buf, sizeof(buf))) > 0) {
+ if (THREAD_CHECK_EXIT) break;
+
+ if (fwrite(buf, 1, len, local_file) < len) {
+ sftp_close(remote_file);
+ fclose(local_file);
+ remmina_sftp_client_thread_set_error(client, task, _("Error writing file %s."), local_path);
+ return FALSE;
+ }
+
+ *donesize += (guint64)len;
+ task->donesize = (gfloat)(*donesize);
+
+ if (!remmina_sftp_client_thread_update_task(client, task)) break;
+ }
+
+ sftp_close(remote_file);
+ fclose(local_file);
+ return TRUE;
+}
+
+static gboolean
+remmina_sftp_client_thread_recursive_dir(RemminaSFTPClient *client, RemminaSFTP *sftp, RemminaFTPTask *task,
+ const gchar *rootdir_path, const gchar *subdir_path, GPtrArray *array)
+{
+ TRACE_CALL(__func__);
+ sftp_dir sftpdir;
+ sftp_attributes sftpattr;
+ gchar *tmp;
+ gchar *dir_path;
+ gchar *file_path;
+ gint type;
+ gboolean ret = TRUE;
+
+ if (THREAD_CHECK_EXIT) return FALSE;
+
+ if (subdir_path) {
+ dir_path = remmina_public_combine_path(rootdir_path, subdir_path);
+ }else {
+ dir_path = g_strdup(rootdir_path);
+ }
+ tmp = remmina_ssh_unconvert(REMMINA_SSH(sftp), dir_path);
+ sftpdir = sftp_opendir(sftp->sftp_sess, tmp);
+ g_free(tmp);
+
+ if (!sftpdir) {
+ remmina_sftp_client_thread_set_error(client, task, _("Error opening directory %s. %s"),
+ dir_path, ssh_get_error(REMMINA_SSH(client->sftp)->session));
+ g_free(dir_path);
+ return FALSE;
+ }
+
+ g_free(dir_path);
+
+ while ((sftpattr = sftp_readdir(sftp->sftp_sess, sftpdir))) {
+ if (g_strcmp0(sftpattr->name, ".") != 0 &&
+ g_strcmp0(sftpattr->name, "..") != 0) {
+ GET_SFTPATTR_TYPE(sftpattr, type);
+
+ tmp = remmina_ssh_convert(REMMINA_SSH(sftp), sftpattr->name);
+ if (subdir_path) {
+ file_path = remmina_public_combine_path(subdir_path, tmp);
+ g_free(tmp);
+ }else {
+ file_path = tmp;
+ }
+
+ if (type == REMMINA_FTP_FILE_TYPE_DIR) {
+ ret = remmina_sftp_client_thread_recursive_dir(client, sftp, task, rootdir_path, file_path, array);
+ g_free(file_path);
+ if (!ret) {
+ sftp_attributes_free(sftpattr);
+ break;
+ }
+ }else {
+ task->size += (gfloat)sftpattr->size;
+ g_ptr_array_add(array, file_path);
+
+ if (!remmina_sftp_client_thread_update_task(client, task)) {
+ sftp_attributes_free(sftpattr);
+ break;
+ }
+ }
+ }
+ sftp_attributes_free(sftpattr);
+
+ if (THREAD_CHECK_EXIT) break;
+ }
+
+ sftp_closedir(sftpdir);
+ return ret;
+}
+
+static gboolean
+remmina_sftp_client_thread_recursive_localdir(RemminaSFTPClient *client, RemminaFTPTask *task,
+ const gchar *rootdir_path, const gchar *subdir_path, GPtrArray *array)
+{
+ TRACE_CALL(__func__);
+ GDir *dir;
+ gchar *path;
+ const gchar *name;
+ gchar *relpath;
+ gchar *abspath;
+ struct stat st;
+ gboolean ret = TRUE;
+
+ path = g_build_filename(rootdir_path, subdir_path, NULL);
+ dir = g_dir_open(path, 0, NULL);
+ if (dir == NULL) {
+ g_free(path);
+ return FALSE;
+ }
+ while ((name = g_dir_read_name(dir)) != NULL) {
+ if (THREAD_CHECK_EXIT) {
+ ret = FALSE;
+ break;
+ }
+ if (g_strcmp0(name, ".") == 0 || g_strcmp0(name, "..") == 0) continue;
+ abspath = g_build_filename(path, name, NULL);
+ if (g_stat(abspath, &st) < 0) {
+ g_free(abspath);
+ continue;
+ }
+ relpath = g_build_filename(subdir_path ? subdir_path : "", name, NULL);
+ g_ptr_array_add(array, relpath);
+ if (g_file_test(abspath, G_FILE_TEST_IS_DIR)) {
+ ret = remmina_sftp_client_thread_recursive_localdir(client, task, rootdir_path, relpath, array);
+ if (!ret) break;
+ }else {
+ task->size += (gfloat)st.st_size;
+ }
+ g_free(abspath);
+ }
+ g_free(path);
+ g_dir_close(dir);
+ return ret;
+}
+
+static gboolean
+remmina_sftp_client_thread_mkdir(RemminaSFTPClient *client, RemminaSFTP *sftp, RemminaFTPTask *task, const gchar *path)
+{
+ TRACE_CALL(__func__);
+ sftp_attributes sftpattr;
+
+ sftpattr = sftp_stat(sftp->sftp_sess, path);
+ if (sftpattr != NULL) {
+ sftp_attributes_free(sftpattr);
+ return TRUE;
+ }
+ if (sftp_mkdir(sftp->sftp_sess, path, 0755) < 0) {
+ remmina_sftp_client_thread_set_error(client, task, _("Error creating folder %s on server. %s"),
+ path, ssh_get_error(REMMINA_SSH(client->sftp)->session));
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static gboolean
+remmina_sftp_client_thread_upload_file(RemminaSFTPClient *client, RemminaSFTP *sftp, RemminaFTPTask *task,
+ const gchar *remote_path, const gchar *local_path, guint64 *donesize)
+{
+ TRACE_CALL(__func__);
+ sftp_file remote_file;
+ FILE *local_file;
+ gchar *tmp;
+ gchar buf[20480];
+ gint len;
+ sftp_attributes attr;
+ gint response;
+ uint64_t size;
+
+ if (THREAD_CHECK_EXIT) return FALSE;
+
+ tmp = remmina_ssh_unconvert(REMMINA_SSH(sftp), remote_path);
+ remote_file = sftp_open(sftp->sftp_sess, tmp, O_WRONLY | O_CREAT, 0644);
+ g_free(tmp);
+
+ if (!remote_file) {
+ remmina_sftp_client_thread_set_error(client, task, _("Error creating file %s on server. %s"),
+ remote_path, ssh_get_error(REMMINA_SSH(client->sftp)->session));
+ return FALSE;
+ }
+ attr = sftp_fstat(remote_file);
+ size = attr->size;
+ sftp_attributes_free(attr);
+ if (size > 0) {
+ response = remmina_sftp_client_confirm_resume(client, remote_path);
+ switch (response) {
+ case GTK_RESPONSE_CANCEL:
+ case GTK_RESPONSE_DELETE_EVENT:
+ sftp_close(remote_file);
+ remmina_sftp_client_thread_set_error(client, task, NULL);
+ return FALSE;
+
+ case GTK_RESPONSE_ACCEPT:
+ sftp_close(remote_file);
+ tmp = remmina_ssh_unconvert(REMMINA_SSH(sftp), remote_path);
+ remote_file = sftp_open(sftp->sftp_sess, tmp, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ g_free(tmp);
+ if (!remote_file) {
+ remmina_sftp_client_thread_set_error(client, task, _("Error creating file %s on server. %s"),
+ remote_path, ssh_get_error(REMMINA_SSH(client->sftp)->session));
+ return FALSE;
+ }
+ size = 0;
+ break;
+
+ case GTK_RESPONSE_APPLY:
+ if (sftp_seek64(remote_file, size) < 0) {
+ sftp_close(remote_file);
+ remmina_sftp_client_thread_set_error(client, task, "Error seeking remote file %s. %s",
+ remote_path, ssh_get_error(REMMINA_SSH(client->sftp)->session));
+ return FALSE;
+ }
+ break;
+ }
+ }
+
+ local_file = g_fopen(local_path, "rb");
+ if (!local_file) {
+ sftp_close(remote_file);
+ remmina_sftp_client_thread_set_error(client, task, _("Error opening file %s."), local_path);
+ return FALSE;
+ }
+
+ if (size > 0) {
+ if (fseeko(local_file, size, SEEK_SET) < 0) {
+ sftp_close(remote_file);
+ fclose(local_file);
+ remmina_sftp_client_thread_set_error(client, task, "Error seeking local file %s.", local_path);
+ return FALSE;
+ }
+ *donesize = size;
+ }
+
+ while (!THREAD_CHECK_EXIT && (len = fread(buf, 1, sizeof(buf), local_file)) > 0) {
+ if (THREAD_CHECK_EXIT) break;
+
+ if (sftp_write(remote_file, buf, len) < len) {
+ sftp_close(remote_file);
+ fclose(local_file);
+ remmina_sftp_client_thread_set_error(client, task, _("Error writing file %s on server. %s"),
+ remote_path, ssh_get_error(REMMINA_SSH(client->sftp)->session));
+ return FALSE;
+ }
+
+ *donesize += (guint64)len;
+ task->donesize = (gfloat)(*donesize);
+
+ if (!remmina_sftp_client_thread_update_task(client, task)) break;
+ }
+
+ sftp_close(remote_file);
+ fclose(local_file);
+ return TRUE;
+}
+
+static gpointer
+remmina_sftp_client_thread_main(gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaSFTPClient *client = REMMINA_SFTP_CLIENT(data);
+ RemminaSFTP *sftp = NULL;
+ RemminaFTPTask *task;
+ gchar *remote, *local;
+ guint64 size;
+ GPtrArray *array;
+ gint i;
+ gchar *remote_file, *local_file;
+ gboolean ret;
+ gchar *refreshdir = NULL;
+ gchar *tmp;
+ gboolean refresh = FALSE;
+
+ task = remmina_sftp_client_thread_get_task(client);
+ while (task) {
+ size = 0;
+ if (!sftp) {
+ sftp = remmina_sftp_new_from_ssh(REMMINA_SSH(client->sftp));
+ if (!remmina_ssh_init_session(REMMINA_SSH(sftp)) ||
+ remmina_ssh_auth(REMMINA_SSH(sftp), NULL) <= 0 ||
+ !remmina_sftp_open(sftp)) {
+ remmina_sftp_client_thread_set_error(client, task, (REMMINA_SSH(sftp))->error);
+ remmina_ftp_task_free(task);
+ break;
+ }
+ }
+
+ remote = remmina_public_combine_path(task->remotedir, task->name);
+ local = remmina_public_combine_path(task->localdir, task->name);
+
+ switch (task->tasktype) {
+ case REMMINA_FTP_TASK_TYPE_DOWNLOAD:
+ switch (task->type) {
+ case REMMINA_FTP_FILE_TYPE_FILE:
+ ret = remmina_sftp_client_thread_download_file(client, sftp, task,
+ remote, local, &size);
+ break;
+
+ case REMMINA_FTP_FILE_TYPE_DIR:
+ array = g_ptr_array_new();
+ ret = remmina_sftp_client_thread_recursive_dir(client, sftp, task, remote, NULL, array);
+ if (ret) {
+ for (i = 0; i < array->len; i++) {
+ if (THREAD_CHECK_EXIT) {
+ ret = FALSE;
+ break;
+ }
+ remote_file = remmina_public_combine_path(remote, (gchar*)g_ptr_array_index(array, i));
+ local_file = remmina_public_combine_path(local, (gchar*)g_ptr_array_index(array, i));
+ ret = remmina_sftp_client_thread_download_file(client, sftp, task,
+ remote_file, local_file, &size);
+ g_free(remote_file);
+ g_free(local_file);
+ if (!ret) break;
+ }
+ }
+ g_ptr_array_foreach(array, (GFunc)g_free, NULL);
+ g_ptr_array_free(array, TRUE);
+ break;
+
+ default:
+ ret = 0;
+ break;
+ }
+ if (ret) {
+ remmina_sftp_client_thread_set_finish(client, task);
+ }
+ break;
+
+ case REMMINA_FTP_TASK_TYPE_UPLOAD:
+ switch (task->type) {
+ case REMMINA_FTP_FILE_TYPE_FILE:
+ ret = remmina_sftp_client_thread_upload_file(client, sftp, task,
+ remote, local, &size);
+ break;
+
+ case REMMINA_FTP_FILE_TYPE_DIR:
+ ret = remmina_sftp_client_thread_mkdir(client, sftp, task, remote);
+ if (!ret) break;
+ array = g_ptr_array_new();
+ ret = remmina_sftp_client_thread_recursive_localdir(client, task, local, NULL, array);
+ if (ret) {
+ for (i = 0; i < array->len; i++) {
+ if (THREAD_CHECK_EXIT) {
+ ret = FALSE;
+ break;
+ }
+ remote_file = remmina_public_combine_path(remote, (gchar*)g_ptr_array_index(array, i));
+ local_file = g_build_filename(local, (gchar*)g_ptr_array_index(array, i), NULL);
+ if (g_file_test(local_file, G_FILE_TEST_IS_DIR)) {
+ ret = remmina_sftp_client_thread_mkdir(client, sftp, task, remote_file);
+ }else {
+ ret = remmina_sftp_client_thread_upload_file(client, sftp, task,
+ remote_file, local_file, &size);
+ }
+ g_free(remote_file);
+ g_free(local_file);
+ if (!ret) break;
+ }
+ }
+ g_ptr_array_foreach(array, (GFunc)g_free, NULL);
+ g_ptr_array_free(array, TRUE);
+ break;
+
+ default:
+ ret = 0;
+ break;
+ }
+ if (ret) {
+ remmina_sftp_client_thread_set_finish(client, task);
+ tmp = remmina_ftp_client_get_dir(REMMINA_FTP_CLIENT(client));
+ if (g_strcmp0(tmp, task->remotedir) == 0) {
+ refresh = TRUE;
+ g_free(refreshdir);
+ refreshdir = tmp;
+ }else {
+ g_free(tmp);
+ }
+ }
+ break;
+ }
+
+ g_free(remote);
+ g_free(local);
+
+ remmina_ftp_task_free(task);
+ client->taskid = 0;
+
+ if (client->thread_abort) break;
+
+ task = remmina_sftp_client_thread_get_task(client);
+ }
+
+ if (sftp) {
+ remmina_sftp_free(sftp);
+ }
+
+ if (!client->thread_abort && refresh) {
+ tmp = remmina_ftp_client_get_dir(REMMINA_FTP_CLIENT(client));
+ if (g_strcmp0(tmp, refreshdir) == 0) {
+ IDLE_ADD((GSourceFunc)remmina_sftp_client_refresh, client);
+ }
+ g_free(tmp);
+ }
+ g_free(refreshdir);
+ client->thread = 0;
+
+ return NULL;
+}
+
+/* ------------------------ The SFTP Client routines ----------------------------- */
+
+static void
+remmina_sftp_client_destroy(RemminaSFTPClient *client, gpointer data)
+{
+ TRACE_CALL(__func__);
+ if (client->sftp) {
+ remmina_sftp_free(client->sftp);
+ client->sftp = NULL;
+ }
+ client->thread_abort = TRUE;
+ /* We will wait for the thread to quit itself, and hopefully the thread is handling things correctly */
+ while (client->thread) {
+ /* gdk_threads_leave (); */
+ sleep(1);
+ /* gdk_threads_enter (); */
+ }
+}
+
+static sftp_dir
+remmina_sftp_client_sftp_session_opendir(RemminaSFTPClient *client, const gchar *dir)
+{
+ TRACE_CALL(__func__);
+ sftp_dir sftpdir;
+ GtkWidget *dialog;
+
+ sftpdir = sftp_opendir(client->sftp->sftp_sess, (gchar*)dir);
+ if (!sftpdir) {
+ dialog = gtk_message_dialog_new(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(client))),
+ GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+ _("Failed to open directory %s. %s"), dir,
+ ssh_get_error(REMMINA_SSH(client->sftp)->session));
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ return NULL;
+ }
+ return sftpdir;
+}
+
+static gboolean
+remmina_sftp_client_sftp_session_closedir(RemminaSFTPClient *client, sftp_dir sftpdir)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *dialog;
+
+ if (!sftp_dir_eof(sftpdir)) {
+ dialog = gtk_message_dialog_new(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(client))),
+ GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+ _("Failed reading directory. %s"), ssh_get_error(REMMINA_SSH(client->sftp)->session));
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ return FALSE;
+ }
+ sftp_closedir(sftpdir);
+ return TRUE;
+}
+
+static void
+remmina_sftp_client_on_opendir(RemminaSFTPClient *client, gchar *dir, gpointer data)
+{
+ TRACE_CALL(__func__);
+ sftp_dir sftpdir;
+ sftp_attributes sftpattr;
+ GtkWidget *dialog;
+ gchar *newdir;
+ gchar *newdir_conv;
+ gchar *tmp;
+ gint type;
+
+ if (client->sftp == NULL) return;
+
+ if (!dir || dir[0] == '\0') {
+ newdir = g_strdup(".");
+ }else if (dir[0] == '/') {
+ newdir = g_strdup(dir);
+ }else {
+ tmp = remmina_ftp_client_get_dir(REMMINA_FTP_CLIENT(client));
+ if (tmp) {
+ newdir = remmina_public_combine_path(tmp, dir);
+ g_free(tmp);
+ }else {
+ newdir = g_strdup_printf("./%s", dir);
+ }
+ }
+
+ tmp = remmina_ssh_unconvert(REMMINA_SSH(client->sftp), newdir);
+ newdir_conv = sftp_canonicalize_path(client->sftp->sftp_sess, tmp);
+ g_free(tmp);
+ g_free(newdir);
+ newdir = remmina_ssh_convert(REMMINA_SSH(client->sftp), newdir_conv);
+ if (!newdir) {
+ dialog = gtk_message_dialog_new(NULL,
+ GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+ _("Failed to open directory %s. %s"), dir,
+ ssh_get_error(REMMINA_SSH(client->sftp)->session));
+ gtk_widget_show(dialog);
+ g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(gtk_widget_destroy), NULL);
+ g_free(newdir_conv);
+ return;
+ }
+
+ sftpdir = remmina_sftp_client_sftp_session_opendir(client, newdir_conv);
+ g_free(newdir_conv);
+ if (!sftpdir) {
+ g_free(newdir);
+ return;
+ }
+
+ remmina_ftp_client_clear_file_list(REMMINA_FTP_CLIENT(client));
+
+ while ((sftpattr = sftp_readdir(client->sftp->sftp_sess, sftpdir))) {
+ if (g_strcmp0(sftpattr->name, ".") != 0 &&
+ g_strcmp0(sftpattr->name, "..") != 0) {
+ GET_SFTPATTR_TYPE(sftpattr, type);
+
+ tmp = remmina_ssh_convert(REMMINA_SSH(client->sftp), sftpattr->name);
+ remmina_ftp_client_add_file(REMMINA_FTP_CLIENT(client),
+ REMMINA_FTP_FILE_COLUMN_TYPE, type,
+ REMMINA_FTP_FILE_COLUMN_NAME, tmp,
+ REMMINA_FTP_FILE_COLUMN_SIZE, (gfloat)sftpattr->size,
+ REMMINA_FTP_FILE_COLUMN_USER, sftpattr->owner,
+ REMMINA_FTP_FILE_COLUMN_GROUP, sftpattr->group,
+ REMMINA_FTP_FILE_COLUMN_PERMISSION, sftpattr->permissions,
+ -1);
+ g_free(tmp);
+ }
+ sftp_attributes_free(sftpattr);
+ }
+ remmina_sftp_client_sftp_session_closedir(client, sftpdir);
+
+ remmina_ftp_client_set_dir(REMMINA_FTP_CLIENT(client), newdir);
+ g_free(newdir);
+}
+
+static void
+remmina_sftp_client_on_newtask(RemminaSFTPClient *client, gpointer data)
+{
+ TRACE_CALL(__func__);
+ if (client->thread) return;
+
+ if (pthread_create(&client->thread, NULL, remmina_sftp_client_thread_main, client)) {
+ client->thread = 0;
+ }
+}
+
+static gboolean
+remmina_sftp_client_on_canceltask(RemminaSFTPClient *client, gint taskid, gpointer data)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *dialog;
+ gint ret;
+
+ if (client->taskid != taskid) return TRUE;
+
+ dialog = gtk_message_dialog_new(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(client))),
+ GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
+ _("File transfer currently in progress.\nAre you sure to cancel it?"));
+ ret = gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ if (ret == GTK_RESPONSE_YES) {
+ /* Make sure we are still handling the same task before we clear the flag */
+ if (client->taskid == taskid) client->taskid = 0;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static gboolean
+remmina_sftp_client_on_deletefile(RemminaSFTPClient *client, gint type, gchar *name, gpointer data)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *dialog;
+ gint ret = 0;
+ gchar *tmp;
+
+ tmp = remmina_ssh_unconvert(REMMINA_SSH(client->sftp), name);
+ switch (type) {
+ case REMMINA_FTP_FILE_TYPE_DIR:
+ ret = sftp_rmdir(client->sftp->sftp_sess, tmp);
+ break;
+
+ case REMMINA_FTP_FILE_TYPE_FILE:
+ ret = sftp_unlink(client->sftp->sftp_sess, tmp);
+ break;
+ }
+ g_free(tmp);
+
+ if (ret != 0) {
+ dialog = gtk_message_dialog_new(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(client))),
+ GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+ _("Failed to delete '%s'. %s"),
+ name, ssh_get_error(REMMINA_SSH(client->sftp)->session));
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void
+remmina_sftp_client_init(RemminaSFTPClient *client)
+{
+ TRACE_CALL(__func__);
+ client->sftp = NULL;
+ client->thread = 0;
+ client->taskid = 0;
+ client->thread_abort = FALSE;
+
+ /* Setup the internal signals */
+ g_signal_connect(G_OBJECT(client), "destroy",
+ G_CALLBACK(remmina_sftp_client_destroy), NULL);
+ g_signal_connect(G_OBJECT(client), "open-dir",
+ G_CALLBACK(remmina_sftp_client_on_opendir), NULL);
+ g_signal_connect(G_OBJECT(client), "new-task",
+ G_CALLBACK(remmina_sftp_client_on_newtask), NULL);
+ g_signal_connect(G_OBJECT(client), "cancel-task",
+ G_CALLBACK(remmina_sftp_client_on_canceltask), NULL);
+ g_signal_connect(G_OBJECT(client), "delete-file",
+ G_CALLBACK(remmina_sftp_client_on_deletefile), NULL);
+}
+
+static gboolean
+remmina_sftp_client_refresh(RemminaSFTPClient *client)
+{
+ TRACE_CALL(__func__);
+
+ GdkDisplay *display = gdk_display_get_default();
+
+ SET_CURSOR(gdk_cursor_new_for_display(display, GDK_WATCH));
+ gdk_display_flush(display);
+
+ remmina_sftp_client_on_opendir(client, ".", NULL);
+
+ SET_CURSOR(NULL);
+
+ return FALSE;
+}
+
+gint
+remmina_sftp_client_confirm_resume(RemminaSFTPClient *client, const gchar *path)
+{
+ TRACE_CALL(__func__);
+
+ GtkWidget *dialog;
+ gint response;
+ GtkWidget *hbox;
+ GtkWidget *vbox;
+ GtkWidget *widget;
+ const gchar *filename;
+
+ /* Always reply ACCEPT if overwrite_all was already set */
+ if (remmina_ftp_client_get_overwrite_status(REMMINA_FTP_CLIENT(client)))
+ return GTK_RESPONSE_ACCEPT;
+
+ if ( !remmina_masterthread_exec_is_main_thread() ) {
+ /* Allow the execution of this function from a non main thread */
+ RemminaMTExecData *d;
+ gint retval;
+ d = (RemminaMTExecData*)g_malloc( sizeof(RemminaMTExecData) );
+ d->func = FUNC_SFTP_CLIENT_CONFIRM_RESUME;
+ d->p.sftp_client_confirm_resume.client = client;
+ d->p.sftp_client_confirm_resume.path = path;
+ remmina_masterthread_exec_and_wait(d);
+ retval = d->p.sftp_client_confirm_resume.retval;
+ g_free(d);
+ return retval;
+ }
+
+ filename = strrchr(path, '/');
+ filename = filename ? filename + 1 : path;
+
+ dialog = gtk_dialog_new_with_buttons(_("File exists"),
+ GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(client))),
+ GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
+ _("Resume"), GTK_RESPONSE_APPLY,
+ _("Overwrite"), GTK_RESPONSE_ACCEPT,
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ NULL);
+ gtk_container_set_border_width(GTK_CONTAINER(dialog), 4);
+
+ hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
+ gtk_widget_show(hbox);
+ gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))),
+ hbox, TRUE, TRUE, 4);
+
+ widget = gtk_image_new_from_icon_name(_("Question"), GTK_ICON_SIZE_DIALOG);
+ gtk_widget_show(widget);
+ gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, TRUE, 4);
+
+ vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
+ gtk_widget_show(vbox);
+ gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 4);
+
+ widget = gtk_label_new(_("The following file already exists in the target folder:"));
+ 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_box_pack_start(GTK_BOX(vbox), widget, TRUE, TRUE, 4);
+
+ widget = gtk_label_new(filename);
+ 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_box_pack_start(GTK_BOX(vbox), widget, TRUE, TRUE, 4);
+
+ response = gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ return response;
+}
+
+GtkWidget*
+remmina_sftp_client_new(void)
+{
+ TRACE_CALL(__func__);
+ return GTK_WIDGET(g_object_new(REMMINA_TYPE_SFTP_CLIENT, NULL));
+}
+
+void
+remmina_sftp_client_open(RemminaSFTPClient *client, RemminaSFTP *sftp)
+{
+ TRACE_CALL(__func__);
+ client->sftp = sftp;
+
+ g_idle_add((GSourceFunc)remmina_sftp_client_refresh, client);
+}
+
+GtkWidget*
+remmina_sftp_client_new_init(RemminaSFTP *sftp)
+{
+ TRACE_CALL(__func__);
+ GdkDisplay *display;
+ GtkWidget *client;
+ GtkWidget *dialog;
+
+ display = gdk_display_get_default();
+ client = remmina_sftp_client_new();
+
+
+ SET_CURSOR(gdk_cursor_new_for_display(display, GDK_WATCH));
+ gdk_display_flush(display);
+
+ if (!remmina_ssh_init_session(REMMINA_SSH(sftp)) ||
+ remmina_ssh_auth(REMMINA_SSH(sftp), NULL) <= 0 ||
+ !remmina_sftp_open(sftp)) {
+ dialog = gtk_message_dialog_new(GTK_WINDOW(gtk_widget_get_toplevel(client)),
+ GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+ (REMMINA_SSH(sftp))->error, NULL);
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ gtk_widget_destroy(client);
+ return NULL;
+ }
+
+ SET_CURSOR(NULL);
+
+ g_idle_add((GSourceFunc)remmina_sftp_client_refresh, client);
+ return client;
+}
+
+#endif
diff --git a/src/remmina_sftp_client.h b/src/remmina_sftp_client.h
new file mode 100644
index 000000000..885e0b11b
--- /dev/null
+++ b/src/remmina_sftp_client.h
@@ -0,0 +1,82 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2010 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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.
+ *
+ */
+
+#pragma once
+
+#include "config.h"
+
+#ifdef HAVE_LIBSSH
+
+#include "remmina_file.h"
+#include "remmina_ftp_client.h"
+#include "remmina_ssh.h"
+
+G_BEGIN_DECLS
+
+#define REMMINA_TYPE_SFTP_CLIENT (remmina_sftp_client_get_type())
+#define REMMINA_SFTP_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), REMMINA_TYPE_SFTP_CLIENT, RemminaSFTPClient))
+#define REMMINA_SFTP_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), REMMINA_TYPE_SFTP_CLIENT, RemminaSFTPClientClass))
+#define REMMINA_IS_SFTP_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), REMMINA_TYPE_SFTP_CLIENT))
+#define REMMINA_IS_SFTP_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), REMMINA_TYPE_SFTP_CLIENT))
+#define REMMINA_SFTP_CLIENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), REMMINA_TYPE_SFTP_CLIENT, RemminaSFTPClientClass))
+
+typedef struct _RemminaSFTPClient {
+ RemminaFTPClient client;
+
+ RemminaSFTP *sftp;
+
+ pthread_t thread;
+ gint taskid;
+ gboolean thread_abort;
+} RemminaSFTPClient;
+
+typedef struct _RemminaSFTPClientClass {
+ RemminaFTPClientClass parent_class;
+
+} RemminaSFTPClientClass;
+
+GType remmina_sftp_client_get_type(void) G_GNUC_CONST;
+
+GtkWidget* remmina_sftp_client_new(void);
+
+void remmina_sftp_client_open(RemminaSFTPClient *client, RemminaSFTP *sftp);
+gint remmina_sftp_client_confirm_resume(RemminaSFTPClient *client, const gchar *path);
+
+G_END_DECLS
+
+#endif /* HAVE_LIBSSH */
+
+
diff --git a/src/remmina_sftp_plugin.c b/src/remmina_sftp_plugin.c
new file mode 100644
index 000000000..59f63e8e5
--- /dev/null
+++ b/src/remmina_sftp_plugin.c
@@ -0,0 +1,403 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 "config.h"
+#include "remmina/remmina_trace_calls.h"
+
+#ifdef HAVE_LIBSSH
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include "remmina_public.h"
+#include "remmina_sftp_client.h"
+#include "remmina_plugin_manager.h"
+#include "remmina_ssh.h"
+#include "remmina_protocol_widget.h"
+#include "remmina_sftp_plugin.h"
+
+#define REMMINA_PLUGIN_SFTP_FEATURE_PREF_SHOW_HIDDEN 1
+#define REMMINA_PLUGIN_SFTP_FEATURE_PREF_OVERWRITE_ALL 2
+
+#define REMMINA_PLUGIN_SFTP_FEATURE_PREF_OVERWRITE_ALL_KEY "overwrite_all"
+
+#define GET_PLUGIN_DATA(gp) (RemminaPluginSftpData*)g_object_get_data(G_OBJECT(gp), "plugin-data");
+
+typedef struct _RemminaPluginSftpData {
+ GtkWidget *client;
+ pthread_t thread;
+ RemminaSFTP *sftp;
+} RemminaPluginSftpData;
+
+static RemminaPluginService *remmina_plugin_service = NULL;
+
+static gpointer
+remmina_plugin_sftp_main_thread(gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp = (RemminaProtocolWidget*)data;
+ RemminaPluginSftpData *gpdata;
+ RemminaFile *remminafile;
+ RemminaSSH *ssh;
+ RemminaSFTP *sftp = NULL;
+ gboolean cont = FALSE;
+ gint ret;
+ const gchar *cs;
+ const gchar *saveserver;
+ const gchar *saveusername;
+ gchar *hostport;
+ gchar tunneluser[33]; /* On linux a username can have a 32 char lenght */
+ gchar tunnelserver[256]; /* On linux a servername can have a 255 char lenght */
+ gchar tunnelport[6]; /* A TCP port can have a maximum value of 65535 */
+ gchar *host;
+ gint port;
+
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+ CANCEL_ASYNC
+
+ gpdata = GET_PLUGIN_DATA(gp);
+
+ /* remmina_plugin_service->protocol_plugin_start_direct_tunnel start the
+ * SSH Tunnel and return the server + port string
+ * Therefore we set the SSH Tunnel username here, before the tunnel
+ * is established and than set it back to the destination SSH user.
+ *
+ * */
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ /* We save the ssh server name, so that we can restore it at the end of the connection */
+ saveserver = remmina_plugin_service->file_get_string(remminafile, "ssh_server");
+ remmina_plugin_service->file_set_string(remminafile, "save_ssh_server", g_strdup(saveserver));
+ /* We save the ssh username, so that we can restore it at the end of the connection */
+ saveusername = remmina_plugin_service->file_get_string(remminafile, "ssh_username");
+ remmina_plugin_service->file_set_string(remminafile, "save_ssh_username", g_strdup(saveusername));
+
+ if (saveserver) {
+ /* if the server string contains the character @ we extract the user
+ * and the server string (host:port)
+ * */
+ if (strchr(saveserver, '@')) {
+ sscanf(saveserver, "%[_a-zA-Z0-9.]@%[_a-zA-Z0-9.]:%[0-9]",
+ tunneluser, tunnelserver, tunnelport);
+ g_print ("Username: %s, tunneluser: %s\n",
+ remmina_plugin_service->file_get_string(remminafile, "ssh_username"), tunneluser);
+ if (saveusername != NULL && tunneluser[0] != '\0') {
+ remmina_plugin_service->file_set_string(remminafile, "ssh_username", NULL);
+ }
+ remmina_plugin_service->file_set_string(remminafile, "ssh_username",
+ g_strdup(tunneluser));
+ remmina_plugin_service->file_set_string(remminafile, "ssh_server",
+ g_strconcat (tunnelserver, ":", tunnelport, NULL));
+ }
+ }
+
+ hostport = remmina_plugin_service->protocol_plugin_start_direct_tunnel(gp, 22, FALSE);
+ /* We restore the ssh username as the tunnel is set */
+ remmina_plugin_service->file_set_string(remminafile, "ssh_username", g_strdup(saveusername));
+ if (hostport == NULL) {
+ return FALSE;
+ }
+ remmina_plugin_service->get_server_port(hostport, 22, &host, &port);
+
+ ssh = g_object_get_data(G_OBJECT(gp), "user-data");
+ if (ssh) {
+ /* Create SFTP connection based on existing SSH session */
+ sftp = remmina_sftp_new_from_ssh(ssh);
+ if (remmina_ssh_init_session(REMMINA_SSH(sftp)) &&
+ remmina_ssh_auth(REMMINA_SSH(sftp), NULL) > 0 &&
+ remmina_sftp_open(sftp)) {
+ cont = TRUE;
+ }
+ }else {
+ /* New SFTP connection */
+ if (remmina_plugin_service->file_get_int(remminafile, "ssh_enabled", FALSE)) {
+ remmina_plugin_service->file_set_string(remminafile, "ssh_server", g_strdup(hostport));
+ }else {
+ remmina_plugin_service->file_set_string(remminafile, "ssh_server",
+ remmina_plugin_service->file_get_string(remminafile, "server"));
+ }
+ g_free(hostport);
+ g_free(host);
+
+ sftp = remmina_sftp_new_from_file(remminafile);
+ while (1) {
+ if (!remmina_ssh_init_session(REMMINA_SSH(sftp))) {
+ remmina_plugin_service->protocol_plugin_set_error(gp, "%s", REMMINA_SSH(sftp)->error);
+ break;
+ }
+
+ ret = remmina_ssh_auth_gui(REMMINA_SSH(sftp),
+ REMMINA_INIT_DIALOG(remmina_protocol_widget_get_init_dialog(gp)),
+ remminafile);
+ if (ret == 0) {
+ remmina_plugin_service->protocol_plugin_set_error(gp, "%s", REMMINA_SSH(sftp)->error);
+ }
+ if (ret <= 0) break;
+
+ if (!remmina_sftp_open(sftp)) {
+ remmina_plugin_service->protocol_plugin_set_error(gp, "%s", REMMINA_SSH(sftp)->error);
+ break;
+ }
+
+ cs = remmina_plugin_service->file_get_string(remminafile, "execpath");
+ if (cs && cs[0]) {
+ remmina_ftp_client_set_dir(REMMINA_FTP_CLIENT(gpdata->client), cs);
+ }
+
+ cont = TRUE;
+ break;
+ }
+
+ /* We restore the ssh_server name */
+ remmina_plugin_service->file_set_string(remminafile, "ssh_server",
+ remmina_plugin_service->file_get_string(remminafile, "save_ssh_server"));
+ }
+ if (!cont) {
+ if (sftp) remmina_sftp_free(sftp);
+ IDLE_ADD((GSourceFunc)remmina_plugin_service->protocol_plugin_close_connection, gp);
+ return NULL;
+ }
+
+ remmina_sftp_client_open(REMMINA_SFTP_CLIENT(gpdata->client), sftp);
+ /* RemminaSFTPClient owns the object, we just take the reference */
+ gpdata->sftp = sftp;
+
+ remmina_plugin_service->protocol_plugin_emit_signal(gp, "connect");
+
+ gpdata->thread = 0;
+ return NULL;
+}
+
+static void
+remmina_plugin_sftp_client_on_realize(GtkWidget *widget, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaFile *remminafile;
+
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ remmina_ftp_client_load_state(REMMINA_FTP_CLIENT(widget), remminafile);
+}
+
+static void
+remmina_plugin_sftp_init(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginSftpData *gpdata;
+ RemminaFile *remminafile;
+
+ gpdata = g_new0(RemminaPluginSftpData, 1);
+ g_object_set_data_full(G_OBJECT(gp), "plugin-data", gpdata, g_free);
+
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ gpdata->client = remmina_sftp_client_new();
+ gtk_widget_show(gpdata->client);
+ gtk_container_add(GTK_CONTAINER(gp), gpdata->client);
+
+ remmina_ftp_client_set_show_hidden(REMMINA_FTP_CLIENT(gpdata->client),
+ remmina_plugin_service->file_get_int(remminafile, "showhidden", FALSE));
+
+ remmina_ftp_client_set_overwrite_status(REMMINA_FTP_CLIENT(gpdata->client),
+ remmina_plugin_service->file_get_int(remminafile,
+ REMMINA_PLUGIN_SFTP_FEATURE_PREF_OVERWRITE_ALL_KEY, FALSE));
+
+ remmina_plugin_service->protocol_plugin_register_hostkey(gp, gpdata->client);
+
+ g_signal_connect(G_OBJECT(gpdata->client),
+ "realize", G_CALLBACK(remmina_plugin_sftp_client_on_realize), gp);
+}
+
+static gboolean
+remmina_plugin_sftp_open_connection(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginSftpData *gpdata = GET_PLUGIN_DATA(gp);
+
+ remmina_plugin_service->protocol_plugin_set_expand(gp, TRUE);
+ remmina_plugin_service->protocol_plugin_set_width(gp, 640);
+ remmina_plugin_service->protocol_plugin_set_height(gp, 480);
+
+ if (pthread_create(&gpdata->thread, NULL, remmina_plugin_sftp_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;
+ }
+ return TRUE;
+}
+
+static gboolean
+remmina_plugin_sftp_close_connection(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginSftpData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile;
+
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ if (gpdata->thread) {
+ pthread_cancel(gpdata->thread);
+ if (gpdata->thread) pthread_join(gpdata->thread, NULL);
+ }
+
+ remmina_ftp_client_save_state(REMMINA_FTP_CLIENT(gpdata->client), remminafile);
+ remmina_plugin_service->protocol_plugin_emit_signal(gp, "disconnect");
+ /* The session preference overwrite_all is always saved to FALSE in order
+ * to avoid unwanted overwriting.
+ * If we'd change idea just remove the next line to save the preference. */
+ remmina_file_set_int(remminafile,
+ REMMINA_PLUGIN_SFTP_FEATURE_PREF_OVERWRITE_ALL_KEY, FALSE);
+ return FALSE;
+}
+
+static gboolean
+remmina_plugin_sftp_query_feature(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
+{
+ TRACE_CALL(__func__);
+ return TRUE;
+}
+
+static void
+remmina_plugin_sftp_call_feature(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginSftpData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile;
+
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ switch (feature->id) {
+ case REMMINA_PROTOCOL_FEATURE_TOOL_SSH:
+ remmina_plugin_service->open_connection(
+ remmina_file_dup_temp_protocol(remmina_plugin_service->protocol_plugin_get_file(gp), "SSH"),
+ NULL, gpdata->sftp, NULL);
+ return;
+ case REMMINA_PROTOCOL_FEATURE_TOOL_SFTP:
+ remmina_plugin_service->open_connection(
+ remmina_file_dup_temp_protocol(remmina_plugin_service->protocol_plugin_get_file(gp), "SFTP"),
+ NULL, gpdata->sftp, NULL);
+ return;
+ case REMMINA_PLUGIN_SFTP_FEATURE_PREF_SHOW_HIDDEN:
+ remmina_ftp_client_set_show_hidden(REMMINA_FTP_CLIENT(gpdata->client),
+ remmina_plugin_service->file_get_int(remminafile, "showhidden", FALSE));
+ return;
+ case REMMINA_PLUGIN_SFTP_FEATURE_PREF_OVERWRITE_ALL:
+ remmina_ftp_client_set_overwrite_status(REMMINA_FTP_CLIENT(gpdata->client),
+ remmina_plugin_service->file_get_int(remminafile,
+ REMMINA_PLUGIN_SFTP_FEATURE_PREF_OVERWRITE_ALL_KEY, FALSE));
+ return;
+ }
+}
+
+static gpointer ssh_auth[] =
+{
+ "0", N_("Password"),
+ "1", N_("SSH identity file"),
+ "2", N_("SSH agent"),
+ "3", N_("Public key (automatic)"),
+ "4", N_("Kerberos (GSSAPI)"),
+ NULL
+};
+
+/* Array for available features.
+ * The last element of the array must be REMMINA_PROTOCOL_FEATURE_TYPE_END. */
+static const RemminaProtocolFeature remmina_plugin_sftp_features[] =
+{
+ { REMMINA_PROTOCOL_FEATURE_TYPE_PREF, REMMINA_PLUGIN_SFTP_FEATURE_PREF_SHOW_HIDDEN, GINT_TO_POINTER(REMMINA_PROTOCOL_FEATURE_PREF_CHECK), "showhidden",
+ N_("Show Hidden Files") },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_PREF, REMMINA_PLUGIN_SFTP_FEATURE_PREF_OVERWRITE_ALL, GINT_TO_POINTER(REMMINA_PROTOCOL_FEATURE_PREF_CHECK),
+ REMMINA_PLUGIN_SFTP_FEATURE_PREF_OVERWRITE_ALL_KEY, N_("Overwrite all") },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_END, 0, NULL, 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_sftp_basic_settings[] =
+{
+ { REMMINA_PROTOCOL_SETTING_TYPE_SERVER, "ssh_server", NULL, FALSE, "_sftp-ssh._tcp", NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "ssh_username", N_("User name"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_PASSWORD, "ssh_password", N_("User password"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "ssh_auth", N_("Authentication type"), FALSE, ssh_auth, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_FILE, "ssh_privatekey", N_("Identity file"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_PASSWORD, "ssh_passphrase", N_("Private key passphrase"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_END, NULL, NULL, FALSE, NULL, NULL }
+};
+
+/* Protocol plugin definition and features */
+static RemminaProtocolPlugin remmina_plugin_sftp =
+{
+ REMMINA_PLUGIN_TYPE_PROTOCOL, // Type
+ "SFTP", // Name
+ N_("SFTP - Secure File Transfer"), // Description
+ GETTEXT_PACKAGE, // Translation domain
+ VERSION, // Version number
+ "remmina-sftp", // Icon for normal connection
+ "remmina-sftp", // Icon for SSH connection
+ remmina_sftp_basic_settings, // Array for basic settings
+ NULL, // Array for advanced settings
+ REMMINA_PROTOCOL_SSH_SETTING_TUNNEL, // SSH settings type
+ remmina_plugin_sftp_features, // Array for available features
+ remmina_plugin_sftp_init, // Plugin initialization
+ remmina_plugin_sftp_open_connection, // Plugin open connection
+ remmina_plugin_sftp_close_connection, // Plugin close connection
+ remmina_plugin_sftp_query_feature, // Query for available features
+ remmina_plugin_sftp_call_feature, // Call a feature
+ NULL // Send a keystroke
+};
+
+void
+remmina_sftp_plugin_register(void)
+{
+ TRACE_CALL(__func__);
+ remmina_plugin_service = &remmina_plugin_manager_service;
+ remmina_plugin_service->register_plugin((RemminaPlugin*)&remmina_plugin_sftp);
+}
+
+#else
+
+void remmina_sftp_plugin_register(void)
+{
+ TRACE_CALL(__func__);
+}
+
+#endif
+
diff --git a/src/remmina_sftp_plugin.h b/src/remmina_sftp_plugin.h
new file mode 100644
index 000000000..c403511c6
--- /dev/null
+++ b/src/remmina_sftp_plugin.h
@@ -0,0 +1,45 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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.
+ *
+ */
+
+#pragma once
+
+G_BEGIN_DECLS
+
+void remmina_sftp_plugin_register(void);
+
+G_END_DECLS
+
+
diff --git a/src/remmina_ssh.c b/src/remmina_ssh.c
new file mode 100644
index 000000000..6c8393d99
--- /dev/null
+++ b/src/remmina_ssh.c
@@ -0,0 +1,1732 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2011 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 "config.h"
+
+#ifdef HAVE_LIBSSH
+
+/* Define this before stdlib.h to have posix_openpt */
+#define _XOPEN_SOURCE 600
+
+#define LIBSSH_STATIC 1
+#include <libssh/libssh.h>
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <time.h>
+#include <sys/types.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
+#ifdef HAVE_TERMIOS_H
+#include <termios.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_PTY_H
+#include <pty.h>
+#endif
+#include "remmina_public.h"
+#include "remmina_file.h"
+#include "remmina_log.h"
+#include "remmina_pref.h"
+#include "remmina_ssh.h"
+#include "remmina/remmina_trace_calls.h"
+
+#ifdef HAVE_NETINET_TCP_H
+#include <netinet/tcp.h>
+
+#if defined(__FreeBSD__) || defined(__OpenBSD__)
+#ifndef SOL_TCP
+#define SOL_TCP IPPROTO_TCP
+#endif
+#endif
+
+#endif
+
+
+/*-----------------------------------------------------------------------------*
+* SSH Base *
+*-----------------------------------------------------------------------------*/
+
+#define LOCK_SSH(ssh) pthread_mutex_lock(&REMMINA_SSH(ssh)->ssh_mutex);
+#define UNLOCK_SSH(ssh) pthread_mutex_unlock(&REMMINA_SSH(ssh)->ssh_mutex);
+
+static const gchar *common_identities[] =
+{
+ ".ssh/id_ed25519",
+ ".ssh/id_rsa",
+ ".ssh/id_dsa",
+ ".ssh/identity",
+ NULL
+};
+
+gchar*
+remmina_ssh_identity_path(const gchar *id)
+{
+ TRACE_CALL(__func__);
+ if (id == NULL) return NULL;
+ if (id[0] == '/') return g_strdup(id);
+ return g_strdup_printf("%s/%s", g_get_home_dir(), id);
+}
+
+gchar*
+remmina_ssh_find_identity(void)
+{
+ TRACE_CALL(__func__);
+ gchar *path;
+ gint i;
+
+ for (i = 0; common_identities[i]; i++) {
+ path = remmina_ssh_identity_path(common_identities[i]);
+ if (g_file_test(path, G_FILE_TEST_IS_REGULAR | G_FILE_TEST_EXISTS)) {
+ return path;
+ }
+ g_free(path);
+ }
+ return NULL;
+}
+
+void
+remmina_ssh_set_error(RemminaSSH *ssh, const gchar *fmt)
+{
+ TRACE_CALL(__func__);
+ const gchar *err;
+
+ err = ssh_get_error(ssh->session);
+ ssh->error = g_strdup_printf(fmt, err);
+}
+
+void
+remmina_ssh_set_application_error(RemminaSSH *ssh, const gchar *fmt, ...)
+{
+ TRACE_CALL(__func__);
+ va_list args;
+
+ va_start(args, fmt);
+ ssh->error = g_strdup_vprintf(fmt, args);
+ va_end(args);
+}
+
+static gint
+remmina_ssh_auth_interactive(RemminaSSH *ssh)
+{
+ TRACE_CALL(__func__);
+ gint ret;
+ gint n;
+ gint i;
+
+ ret = SSH_AUTH_ERROR;
+ if (ssh->authenticated) return 1;
+ if (ssh->password == NULL) return -1;
+
+ while ((ret = ssh_userauth_kbdint(ssh->session, NULL, NULL)) == SSH_AUTH_INFO) {
+ n = ssh_userauth_kbdint_getnprompts(ssh->session);
+ for (i = 0; i < n; i++) {
+ ssh_userauth_kbdint_setanswer(ssh->session, i, ssh->password);
+ }
+ }
+
+ if (ret != SSH_AUTH_SUCCESS) {
+ /* We pass the control to remmina_ssh_auth_password */
+ return 0;
+ }
+
+ ssh->authenticated = TRUE;
+ return 1;
+}
+
+static gint
+remmina_ssh_auth_password(RemminaSSH *ssh)
+{
+ TRACE_CALL(__func__);
+ gint ret;
+
+ ret = SSH_AUTH_ERROR;
+ if (ssh->authenticated) return 1;
+ if (ssh->password == NULL) return -1;
+
+ ret = ssh_userauth_password(ssh->session, NULL, ssh->password);
+ if (ret != SSH_AUTH_SUCCESS) {
+ remmina_ssh_set_error(ssh, _("SSH password authentication failed: %s"));
+ return 0;
+ }
+
+ ssh->authenticated = TRUE;
+ return 1;
+}
+
+static gint
+remmina_ssh_auth_pubkey(RemminaSSH *ssh)
+{
+ TRACE_CALL(__func__);
+ gint ret;
+ ssh_key priv_key;
+
+ if (ssh->authenticated) return 1;
+
+ if (ssh->privkeyfile == NULL) {
+ ssh->error = g_strdup_printf(_("SSH public key authentication failed: %s"),
+ _("SSH Key file not yet set."));
+ return 0;
+ }
+
+ if ( ssh_pki_import_privkey_file( ssh->privkeyfile, (ssh->passphrase ? ssh->passphrase : ""),
+ NULL, NULL, &priv_key ) != SSH_OK ) {
+ if (ssh->passphrase == NULL || ssh->passphrase[0] == '\0') return -1;
+
+ remmina_ssh_set_error(ssh, _("SSH public key authentication failed: %s"));
+ return 0;
+ }
+
+ ret = ssh_userauth_publickey(ssh->session, NULL, priv_key);
+ ssh_key_free(priv_key);
+
+ if (ret != SSH_AUTH_SUCCESS) {
+ remmina_ssh_set_error(ssh, _("SSH public key authentication failed: %s"));
+ return 0;
+ }
+
+ ssh->authenticated = TRUE;
+ return 1;
+}
+
+static gint
+remmina_ssh_auth_auto_pubkey(RemminaSSH* ssh)
+{
+ TRACE_CALL(__func__);
+ gint ret = ssh_userauth_publickey_auto(ssh->session, NULL, ssh->passphrase);
+
+ if (ret != SSH_AUTH_SUCCESS) {
+ remmina_ssh_set_error(ssh, _("SSH automatic public key authentication failed: %s"));
+ return -1;
+ }
+
+ ssh->authenticated = TRUE;
+ return 1;
+}
+
+static gint
+remmina_ssh_auth_agent(RemminaSSH* ssh)
+{
+ TRACE_CALL(__func__);
+ gint ret;
+ ret = ssh_userauth_agent(ssh->session, NULL);
+
+ if (ret != SSH_AUTH_SUCCESS) {
+ remmina_ssh_set_error(ssh, _("SSH public key authentication with ssh agent failed: %s"));
+ return 0;
+ }
+
+ ssh->authenticated = TRUE;
+ return 1;
+}
+
+static gint
+remmina_ssh_auth_gssapi(RemminaSSH *ssh)
+{
+ TRACE_CALL(__func__);
+ gint ret;
+
+ if (ssh->authenticated) return 1;
+
+ ret = ssh_userauth_gssapi(ssh->session);
+
+ if (ret != SSH_AUTH_SUCCESS) {
+ remmina_ssh_set_error(ssh, _("SSH Kerberos/GSSAPI authentication failed: %s"));
+ return 0;
+ }
+
+ ssh->authenticated = TRUE;
+ return 1;
+}
+
+gint
+remmina_ssh_auth(RemminaSSH *ssh, const gchar *password)
+{
+ TRACE_CALL(__func__);
+ gint method;
+ gint ret;
+
+ /* Check known host again to ensure it's still the original server when user forks
+ a new session from existing one */
+ if (ssh_is_server_known(ssh->session) != SSH_SERVER_KNOWN_OK) {
+ remmina_ssh_set_application_error(ssh, "SSH public key has changed!");
+ return 0;
+ }
+
+ if (password) {
+ g_free(ssh->password);
+ g_free(ssh->passphrase);
+ ssh->password = g_strdup(password);
+ ssh->passphrase = g_strdup(password);
+ }
+
+ /** @todo Here we should call
+ * gint method;
+ * method = ssh_userauth_list(ssh->session, NULL);
+ * And than test both the method and the option selected by the user
+ */
+ method = ssh_userauth_list(ssh->session, NULL);
+ switch (ssh->auth) {
+
+ case SSH_AUTH_PASSWORD:
+ ret = 0;
+ if (method & SSH_AUTH_METHOD_INTERACTIVE || method & SSH_AUTH_METHOD_PASSWORD) {
+ ret = remmina_ssh_auth_interactive(ssh);
+ if (!ssh->authenticated)
+ return remmina_ssh_auth_password(ssh);
+ }
+ return ret;
+
+ case SSH_AUTH_PUBLICKEY:
+ if (method & SSH_AUTH_METHOD_PUBLICKEY)
+ return remmina_ssh_auth_pubkey(ssh);
+
+ case SSH_AUTH_AGENT:
+ return remmina_ssh_auth_agent(ssh);
+
+ case SSH_AUTH_AUTO_PUBLICKEY:
+ /* ssh_agent or none */
+ return remmina_ssh_auth_auto_pubkey(ssh);
+
+#if 0
+ /* Not yet supported by libssh */
+ case SSH_AUTH_HOSTBASED:
+ if (method & SSH_AUTH_METHOD_HOSTBASED)
+ //return remmina_ssh_auth_hostbased;
+ return 0;
+#endif
+
+ case SSH_AUTH_GSSAPI:
+ if (method & SSH_AUTH_METHOD_GSSAPI_MIC)
+ return remmina_ssh_auth_gssapi(ssh);
+
+ default:
+ return 0;
+ }
+}
+
+gint
+remmina_ssh_auth_gui(RemminaSSH *ssh, RemminaInitDialog *dialog, RemminaFile *remminafile)
+{
+ TRACE_CALL(__func__);
+ gchar *tips;
+ gchar *keyname;
+ gchar *pwdtype;
+ gint ret;
+ size_t len;
+ guchar *pubkey;
+ ssh_key server_pubkey;
+ gboolean disablepasswordstoring;
+
+ /* Check if the server's public key is known */
+ ret = ssh_is_server_known(ssh->session);
+ switch (ret) {
+ case SSH_SERVER_KNOWN_OK:
+ break; /* ok */
+
+ case SSH_SERVER_FILE_NOT_FOUND:
+ /* fallback to SSH_SERVER_NOT_KNOWN behavior */
+ case SSH_SERVER_NOT_KNOWN:
+ case SSH_SERVER_KNOWN_CHANGED:
+ case SSH_SERVER_FOUND_OTHER:
+ if ( ssh_get_publickey(ssh->session, &server_pubkey) != SSH_OK ) {
+ remmina_ssh_set_error(ssh, "ssh_get_publickey() has failed: %s");
+ return 0;
+ }
+ if ( ssh_get_publickey_hash(server_pubkey, SSH_PUBLICKEY_HASH_MD5, &pubkey, &len) != 0 ) {
+ ssh_key_free(server_pubkey);
+ remmina_ssh_set_error(ssh, "ssh_get_publickey_hash() has failed: %s");
+ return 0;
+ }
+ ssh_key_free(server_pubkey);
+ keyname = ssh_get_hexa(pubkey, len);
+
+
+ if (ret == SSH_SERVER_NOT_KNOWN || ret == SSH_SERVER_FILE_NOT_FOUND) {
+ ret = remmina_init_dialog_serverkey_unknown(dialog, keyname);
+ }else {
+ ret = remmina_init_dialog_serverkey_changed(dialog, keyname);
+ }
+
+ ssh_string_free_char(keyname);
+ ssh_clean_pubkey_hash(&pubkey);
+ if (ret != GTK_RESPONSE_OK) return -1;
+ ssh_write_knownhost(ssh->session);
+ break;
+ case SSH_SERVER_ERROR:
+ default:
+ remmina_ssh_set_error(ssh, "SSH known host checking failed: %s");
+ return 0;
+ }
+
+ switch (ssh->auth) {
+ case SSH_AUTH_PASSWORD:
+ tips = _("Authenticating %s's password to SSH server %s...");
+ keyname = _("SSH password");
+ pwdtype = "ssh_password";
+ break;
+ case SSH_AUTH_PUBLICKEY:
+ case SSH_AUTH_AGENT:
+ case SSH_AUTH_AUTO_PUBLICKEY:
+ tips = _("Authenticating %s's identity to SSH server %s...");
+ keyname = _("SSH private key passphrase");
+ pwdtype = "ssh_passphrase";
+ break;
+ case SSH_AUTH_GSSAPI:
+ tips = _("Authenticating %s's Kerberos to SSH server %s...");
+ keyname = _("SSH Kerberos/GSSAPI");
+ pwdtype = "kerberos_token";
+ break;
+ default:
+ return FALSE;
+ }
+ /* Try empty password or existing password/passphrase first */
+ ret = remmina_ssh_auth(ssh, remmina_file_get_string(remminafile, pwdtype));
+ if (ret > 0) return 1;
+
+ /* Requested for a non-empty password */
+ if (ret < 0) {
+ if (!dialog) return -1;
+
+ remmina_init_dialog_set_status(dialog, tips, ssh->user, ssh->server);
+ disablepasswordstoring = remmina_file_get_int(remminafile, "disablepasswordstoring", FALSE);
+ ret = remmina_init_dialog_authpwd(dialog, keyname, !disablepasswordstoring);
+
+ if (ret == GTK_RESPONSE_OK) {
+ if (dialog->save_password)
+ remmina_file_set_string(remminafile, pwdtype, dialog->password);
+ }else {
+ return -1;
+ }
+ ret = remmina_ssh_auth(ssh, dialog->password);
+ }
+
+ if (ret <= 0) {
+ return 0;
+ }
+
+ return 1;
+}
+
+void
+remmina_ssh_log_callback(ssh_session session, int priority, const char *message, void *userdata)
+{
+ TRACE_CALL(__func__);
+ remmina_log_printf("[SSH] %s\n", message);
+}
+
+gboolean
+remmina_ssh_init_session(RemminaSSH *ssh)
+{
+ TRACE_CALL(__func__);
+ gint verbosity;
+ gint rc;
+#ifdef HAVE_NETINET_TCP_H
+ socket_t sshsock;
+ gint optval;
+#endif
+
+ ssh->callback = g_new0(struct ssh_callbacks_struct, 1);
+
+ /* Init & startup the SSH session */
+ ssh->session = ssh_new();
+ ssh_options_set(ssh->session, SSH_OPTIONS_HOST, ssh->server);
+ ssh_options_set(ssh->session, SSH_OPTIONS_PORT, &ssh->port);
+ /** @todo add an option to set the compression nad set it to no as the default option */
+ //ssh_options_set(ssh->session, SSH_OPTIONS_COMPRESSION, "yes");
+ /* When SSH_OPTIONS_USER is not set, the local user account is used */
+ if (*ssh->user != 0)
+ ssh_options_set(ssh->session, SSH_OPTIONS_USER, ssh->user);
+ if (ssh->privkeyfile && *ssh->privkeyfile != 0) {
+ rc = ssh_options_set(ssh->session, SSH_OPTIONS_IDENTITY, ssh->privkeyfile);
+ if (rc == 0) {
+ remmina_log_printf("[SSH] SSH_OPTIONS_IDENTITY has been set to: %s\n", ssh->privkeyfile);
+ }else {
+ remmina_log_printf("[SSH] SSH_OPTIONS_IDENTITY is not set, by default identity, id_dsa and id_rsa are checked.\n");
+ }
+ }
+
+#ifdef SNAP_BUILD
+ ssh_options_set(ssh->session, SSH_OPTIONS_SSH_DIR, g_strdup_printf("%s/.ssh", g_getenv("SNAP_USER_COMMON")));
+#endif
+ rc = ssh_options_set(ssh->session, SSH_OPTIONS_KEY_EXCHANGE, ssh->kex_algorithms);
+ if (rc == 0) {
+ remmina_log_printf("[SSH] SSH_OPTIONS_KEY_EXCHANGE has been set to: %s\n", ssh->kex_algorithms);
+ }else {
+ remmina_log_printf("[SSH] SSH_OPTIONS_KEY_EXCHANGE does not have a valid value: %s\n", ssh->kex_algorithms);
+ }
+ rc = ssh_options_set(ssh->session, SSH_OPTIONS_CIPHERS_C_S, ssh->ciphers);
+ if (rc == 0) {
+ remmina_log_printf("[SSH] SSH_OPTIONS_CIPHERS_C_S has been set to: %s\n", ssh->ciphers);
+ }else {
+ remmina_log_printf("[SSH] SSH_OPTIONS_CIPHERS_C_S does not have a valid value: %s\n", ssh->ciphers);
+ }
+ rc = ssh_options_set(ssh->session, SSH_OPTIONS_HOSTKEYS, ssh->hostkeytypes);
+ if (rc == 0) {
+ remmina_log_printf("[SSH] SSH_OPTIONS_HOSTKEYS has been set to: %s\n", ssh->hostkeytypes);
+ }else {
+ remmina_log_printf("[SSH] SSH_OPTIONS_HOSTKEYS does not have a valid value: %s\n", ssh->hostkeytypes);
+ }
+ rc = ssh_options_set(ssh->session, SSH_OPTIONS_PROXYCOMMAND, ssh->proxycommand);
+ if (rc == 0) {
+ remmina_log_printf("[SSH] SSH_OPTIONS_PROXYCOMMAND has been set to: %s\n", ssh->proxycommand);
+ }else {
+ remmina_log_printf("[SSH] SSH_OPTIONS_PROXYCOMMAND does not have a valid value: %s\n", ssh->proxycommand);
+ }
+ rc = ssh_options_set(ssh->session, SSH_OPTIONS_STRICTHOSTKEYCHECK, &ssh->stricthostkeycheck);
+ if (rc == 0) {
+ remmina_log_printf("[SSH] SSH_OPTIONS_STRICTHOSTKEYCHECK has been set to: %d\n", ssh->stricthostkeycheck);
+ }else {
+ remmina_log_printf("[SSH] SSH_OPTIONS_STRICTHOSTKEYCHECK does not have a valid value: %d\n", ssh->stricthostkeycheck);
+ }
+ rc = ssh_options_set(ssh->session, SSH_OPTIONS_COMPRESSION, ssh->compression);
+ if (rc == 0) {
+ remmina_log_printf("[SSH] SSH_OPTIONS_COMPRESSION has been set to: %s\n", ssh->compression);
+ }else {
+ remmina_log_printf("[SSH] SSH_OPTIONS_COMPRESSION does not have a valid value: %s\n", ssh->compression);
+ }
+
+ ssh_callbacks_init(ssh->callback);
+ if (remmina_log_running()) {
+ verbosity = remmina_pref.ssh_loglevel;
+ ssh_options_set(ssh->session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity);
+ ssh->callback->log_function = remmina_ssh_log_callback;
+ /* Reset libssh legacy userdata. This is a workaround for a libssh bug */
+ ssh_set_log_userdata(ssh->session);
+ }
+ ssh->callback->userdata = ssh;
+ ssh_set_callbacks(ssh->session, ssh->callback);
+
+ /* As the latest parse the ~/.ssh/config file */
+ if (remmina_pref.ssh_parseconfig) {
+ ssh_options_parse_config(ssh->session, NULL);
+ }
+
+ if (ssh_connect(ssh->session)) {
+ remmina_ssh_set_error(ssh, _("Failed to startup SSH session: %s"));
+ return FALSE;
+ }
+
+#ifdef HAVE_NETINET_TCP_H
+ /* Set keepalive on ssh socket, so we can keep firewalls awaken and detect
+ * when we loss the tunnel */
+ sshsock = ssh_get_fd(ssh->session);
+ if (sshsock >= 0) {
+ optval = 1;
+ if (setsockopt(sshsock, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)) < 0) {
+ remmina_log_printf("[SSH] TCP KeepAlive not set\n");
+ }else {
+ remmina_log_printf("[SSH] TCP KeepAlive enabled\n");
+ }
+#ifdef TCP_KEEPIDLE
+ optval = remmina_pref.ssh_tcp_keepidle;
+ if (setsockopt(sshsock, IPPROTO_TCP, TCP_KEEPIDLE, &optval, sizeof(optval)) < 0) {
+ remmina_log_printf("[SSH] TCP_KEEPIDLE not set\n");
+ }else {
+ remmina_log_printf("[SSH] TCP_KEEPIDLE set to %i\n", optval);
+ }
+#endif
+#ifdef TCP_KEEPCNT
+ optval = remmina_pref.ssh_tcp_keepcnt;
+ if (setsockopt(sshsock, IPPROTO_TCP, TCP_KEEPCNT, &optval, sizeof(optval)) < 0) {
+ remmina_log_printf("[SSH] TCP_KEEPCNT not set\n");
+ }else {
+ remmina_log_printf("[SSH] TCP_KEEPCNT set to %i\n", optval);
+ }
+#endif
+#ifdef TCP_KEEPINTVL
+ optval = remmina_pref.ssh_tcp_keepintvl;
+ if (setsockopt(sshsock, IPPROTO_TCP, TCP_KEEPINTVL, &optval, sizeof(optval)) < 0) {
+ remmina_log_printf("[SSH] TCP_KEEPINTVL not set\n");
+ }else {
+ remmina_log_printf("[SSH] TCP_KEEPINTVL set to %i\n", optval);
+ }
+#endif
+#ifdef TCP_USER_TIMEOUT
+ optval = remmina_pref.ssh_tcp_usrtimeout;
+ if (setsockopt(sshsock, IPPROTO_TCP, TCP_USER_TIMEOUT, &optval, sizeof(optval)) < 0) {
+ remmina_log_printf("[SSH] TCP_USER_TIMEOUT not set\n");
+ }else {
+ remmina_log_printf("[SSH] TCP_USER_TIMEOUT set to %i\n", optval);
+ }
+#endif
+ }
+#endif
+
+ /* Try the "none" authentication */
+ if (ssh_userauth_none(ssh->session, NULL) == SSH_AUTH_SUCCESS) {
+ ssh->authenticated = TRUE;
+ }
+ return TRUE;
+}
+
+gboolean
+remmina_ssh_init_from_file(RemminaSSH *ssh, RemminaFile *remminafile)
+{
+ TRACE_CALL(__func__);
+ const gchar *ssh_server;
+ const gchar *ssh_username;
+ const gchar *ssh_privatekey;
+ const gchar *server;
+ gchar *s;
+
+ ssh->session = NULL;
+ ssh->callback = NULL;
+ ssh->authenticated = FALSE;
+ ssh->error = NULL;
+ ssh->passphrase = NULL;
+ pthread_mutex_init(&ssh->ssh_mutex, NULL);
+
+ /* Parse the address and port */
+ ssh_server = remmina_file_get_string(remminafile, "ssh_server");
+ ssh_username = remmina_file_get_string(remminafile, "ssh_username");
+ ssh_privatekey = remmina_file_get_string(remminafile, "ssh_privatekey");
+ server = remmina_file_get_string(remminafile, "server");
+ if (ssh_server) {
+ remmina_public_get_server_port(ssh_server, 22, &ssh->server, &ssh->port);
+ if (ssh->server[0] == '\0') {
+ g_free(ssh->server);
+ remmina_public_get_server_port(server, 0, &ssh->server, NULL);
+ }
+ }else if (server == NULL) {
+ ssh->server = g_strdup("localhost");
+ ssh->port = 22;
+ }else {
+ remmina_public_get_server_port(server, 0, &ssh->server, NULL);
+ ssh->port = 22;
+ }
+
+ ssh->user = g_strdup(ssh_username ? ssh_username : g_get_user_name());
+ ssh->password = NULL;
+ ssh->auth = remmina_file_get_int(remminafile, "ssh_auth", 0);
+ ssh->charset = g_strdup(remmina_file_get_string(remminafile, "ssh_charset"));
+ ssh->kex_algorithms = g_strdup(remmina_file_get_string(remminafile, "ssh_kex_algorithms"));
+ ssh->ciphers = g_strdup(remmina_file_get_string(remminafile, "ssh_ciphers"));
+ ssh->hostkeytypes = g_strdup(remmina_file_get_string(remminafile, "ssh_hostkeytypes"));
+ ssh->proxycommand = g_strdup(remmina_file_get_string(remminafile, "ssh_proxycommand"));
+ ssh->stricthostkeycheck = remmina_file_get_int(remminafile, "ssh_stricthostkeycheck", 0);
+ gint c = remmina_file_get_int(remminafile, "ssh_compression", 0);
+ ssh->compression = (c == 1) ? "yes" : "no";
+
+ /* Public/Private keys */
+ s = (ssh_privatekey ? g_strdup(ssh_privatekey) : remmina_ssh_find_identity());
+ if (s) {
+ ssh->privkeyfile = remmina_ssh_identity_path(s);
+ g_free(s);
+ }else {
+ ssh->privkeyfile = NULL;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+remmina_ssh_init_from_ssh(RemminaSSH *ssh, const RemminaSSH *ssh_src)
+{
+ TRACE_CALL(__func__);
+ ssh->session = NULL;
+ ssh->authenticated = FALSE;
+ ssh->error = NULL;
+ pthread_mutex_init(&ssh->ssh_mutex, NULL);
+
+ ssh->server = g_strdup(ssh_src->server);
+ ssh->port = ssh_src->port;
+ ssh->user = g_strdup(ssh_src->user);
+ ssh->auth = ssh_src->auth;
+ ssh->password = g_strdup(ssh_src->password);
+ ssh->privkeyfile = g_strdup(ssh_src->privkeyfile);
+ ssh->charset = g_strdup(ssh_src->charset);
+ ssh->proxycommand = g_strdup(ssh_src->proxycommand);
+ ssh->kex_algorithms = g_strdup(ssh_src->kex_algorithms);
+ ssh->ciphers = g_strdup(ssh_src->ciphers);
+ ssh->hostkeytypes = g_strdup(ssh_src->hostkeytypes);
+ ssh->compression = ssh_src->compression;
+
+ return TRUE;
+}
+
+gchar*
+remmina_ssh_convert(RemminaSSH *ssh, const gchar *from)
+{
+ TRACE_CALL(__func__);
+ gchar *to = NULL;
+
+ if (ssh->charset && from) {
+ to = g_convert(from, -1, "UTF-8", ssh->charset, NULL, NULL, NULL);
+ }
+ if (!to) to = g_strdup(from);
+ return to;
+}
+
+gchar*
+remmina_ssh_unconvert(RemminaSSH *ssh, const gchar *from)
+{
+ TRACE_CALL(__func__);
+ gchar *to = NULL;
+
+ if (ssh->charset && from) {
+ to = g_convert(from, -1, ssh->charset, "UTF-8", NULL, NULL, NULL);
+ }
+ if (!to) to = g_strdup(from);
+ return to;
+}
+
+void
+remmina_ssh_free(RemminaSSH *ssh)
+{
+ TRACE_CALL(__func__);
+ if (ssh->session) {
+ ssh_disconnect(ssh->session);
+ ssh_free(ssh->session);
+ ssh->session = NULL;
+ }
+ g_free(ssh->callback);
+ g_free(ssh->server);
+ g_free(ssh->user);
+ g_free(ssh->password);
+ g_free(ssh->privkeyfile);
+ g_free(ssh->charset);
+ g_free(ssh->error);
+ pthread_mutex_destroy(&ssh->ssh_mutex);
+ g_free(ssh);
+}
+
+/*-----------------------------------------------------------------------------*
+* SSH Tunnel *
+*-----------------------------------------------------------------------------*/
+struct _RemminaSSHTunnelBuffer {
+ gchar *data;
+ gchar *ptr;
+ ssize_t len;
+};
+
+static RemminaSSHTunnelBuffer*
+remmina_ssh_tunnel_buffer_new(ssize_t len)
+{
+ TRACE_CALL(__func__);
+ RemminaSSHTunnelBuffer *buffer;
+
+ buffer = g_new(RemminaSSHTunnelBuffer, 1);
+ buffer->data = (gchar*)g_malloc(len);
+ buffer->ptr = buffer->data;
+ buffer->len = len;
+ return buffer;
+}
+
+static void
+remmina_ssh_tunnel_buffer_free(RemminaSSHTunnelBuffer *buffer)
+{
+ TRACE_CALL(__func__);
+ if (buffer) {
+ g_free(buffer->data);
+ g_free(buffer);
+ }
+}
+
+RemminaSSHTunnel*
+remmina_ssh_tunnel_new_from_file(RemminaFile *remminafile)
+{
+ TRACE_CALL(__func__);
+ RemminaSSHTunnel *tunnel;
+
+ tunnel = g_new(RemminaSSHTunnel, 1);
+
+ remmina_ssh_init_from_file(REMMINA_SSH(tunnel), remminafile);
+
+ tunnel->tunnel_type = -1;
+ tunnel->channels = NULL;
+ tunnel->sockets = NULL;
+ tunnel->socketbuffers = NULL;
+ tunnel->num_channels = 0;
+ tunnel->max_channels = 0;
+ tunnel->x11_channel = NULL;
+ tunnel->thread = 0;
+ tunnel->running = FALSE;
+ tunnel->server_sock = -1;
+ tunnel->dest = NULL;
+ tunnel->port = 0;
+ tunnel->buffer = NULL;
+ tunnel->buffer_len = 0;
+ tunnel->channels_out = NULL;
+ tunnel->remotedisplay = 0;
+ tunnel->localdisplay = NULL;
+ tunnel->init_func = NULL;
+ tunnel->connect_func = NULL;
+ tunnel->disconnect_func = NULL;
+ tunnel->callback_data = NULL;
+
+ return tunnel;
+}
+
+static void
+remmina_ssh_tunnel_close_all_channels(RemminaSSHTunnel *tunnel)
+{
+ TRACE_CALL(__func__);
+ int i;
+
+ for (i = 0; i < tunnel->num_channels; i++) {
+ close(tunnel->sockets[i]);
+ remmina_ssh_tunnel_buffer_free(tunnel->socketbuffers[i]);
+ ssh_channel_close(tunnel->channels[i]);
+ ssh_channel_send_eof(tunnel->channels[i]);
+ ssh_channel_free(tunnel->channels[i]);
+ }
+
+ g_free(tunnel->channels);
+ tunnel->channels = NULL;
+ g_free(tunnel->sockets);
+ tunnel->sockets = NULL;
+ g_free(tunnel->socketbuffers);
+ tunnel->socketbuffers = NULL;
+
+ tunnel->num_channels = 0;
+ tunnel->max_channels = 0;
+
+ if (tunnel->x11_channel) {
+ ssh_channel_close(tunnel->x11_channel);
+ ssh_channel_send_eof(tunnel->x11_channel);
+ ssh_channel_free(tunnel->x11_channel);
+ tunnel->x11_channel = NULL;
+ }
+}
+
+static void
+remmina_ssh_tunnel_remove_channel(RemminaSSHTunnel *tunnel, gint n)
+{
+ TRACE_CALL(__func__);
+ ssh_channel_close(tunnel->channels[n]);
+ ssh_channel_send_eof(tunnel->channels[n]);
+ ssh_channel_free(tunnel->channels[n]);
+ close(tunnel->sockets[n]);
+ remmina_ssh_tunnel_buffer_free(tunnel->socketbuffers[n]);
+ tunnel->num_channels--;
+ tunnel->channels[n] = tunnel->channels[tunnel->num_channels];
+ tunnel->channels[tunnel->num_channels] = NULL;
+ tunnel->sockets[n] = tunnel->sockets[tunnel->num_channels];
+ tunnel->socketbuffers[n] = tunnel->socketbuffers[tunnel->num_channels];
+}
+
+/* Register the new channel/socket pair */
+static void
+remmina_ssh_tunnel_add_channel(RemminaSSHTunnel *tunnel, ssh_channel channel, gint sock)
+{
+ TRACE_CALL(__func__);
+ gint flags;
+ gint i;
+
+ i = tunnel->num_channels++;
+ if (tunnel->num_channels > tunnel->max_channels) {
+ /* Allocate an extra NULL pointer in channels for ssh_select */
+ tunnel->channels = (ssh_channel*)g_realloc(tunnel->channels,
+ sizeof(ssh_channel) * (tunnel->num_channels + 1));
+ tunnel->sockets = (gint*)g_realloc(tunnel->sockets,
+ sizeof(gint) * tunnel->num_channels);
+ tunnel->socketbuffers = (RemminaSSHTunnelBuffer**)g_realloc(tunnel->socketbuffers,
+ sizeof(RemminaSSHTunnelBuffer*) * tunnel->num_channels);
+ tunnel->max_channels = tunnel->num_channels;
+
+ tunnel->channels_out = (ssh_channel*)g_realloc(tunnel->channels_out,
+ sizeof(ssh_channel) * (tunnel->num_channels + 1));
+ }
+ tunnel->channels[i] = channel;
+ tunnel->channels[i + 1] = NULL;
+ tunnel->sockets[i] = sock;
+ tunnel->socketbuffers[i] = NULL;
+
+ flags = fcntl(sock, F_GETFL, 0);
+ fcntl(sock, F_SETFL, flags | O_NONBLOCK);
+}
+
+static int
+remmina_ssh_tunnel_accept_local_connection(RemminaSSHTunnel *tunnel, gboolean blocking)
+{
+ gint sock, sock_flags;
+
+ sock_flags = fcntl(tunnel->server_sock, F_GETFL, 0);
+ if (blocking) {
+ sock_flags &= ~O_NONBLOCK;
+ }else {
+ sock_flags |= O_NONBLOCK;
+ }
+ fcntl(tunnel->server_sock, F_SETFL, sock_flags);
+
+ /* Accept a local connection */
+ sock = accept(tunnel->server_sock, NULL, NULL);
+ if (sock < 0) {
+ REMMINA_SSH(tunnel)->error = g_strdup("Failed to accept local socket");
+ }
+
+ return sock;
+}
+
+static ssh_channel
+remmina_ssh_tunnel_create_forward_channel(RemminaSSHTunnel *tunnel)
+{
+ ssh_channel channel = NULL;
+
+ channel = ssh_channel_new(tunnel->ssh.session);
+ if (!channel) {
+ remmina_ssh_set_error(REMMINA_SSH(tunnel), "Failed to create channel : %s");
+ return NULL;
+ }
+
+ /* Request the SSH server to connect to the destination */
+ if (ssh_channel_open_forward(channel, tunnel->dest, tunnel->port, "127.0.0.1", 0) != SSH_OK) {
+ ssh_channel_close(channel);
+ ssh_channel_send_eof(channel);
+ ssh_channel_free(channel);
+ remmina_ssh_set_error(REMMINA_SSH(tunnel), _("Failed to connect to the SSH tunnel destination: %s"));
+ return NULL;
+ }
+
+ return channel;
+}
+
+static gpointer
+remmina_ssh_tunnel_main_thread_proc(gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaSSHTunnel *tunnel = (RemminaSSHTunnel*)data;
+ gchar *ptr;
+ ssize_t len = 0, lenw = 0;
+ fd_set set;
+ struct timeval timeout;
+ GTimeVal t1, t2;
+ glong diff;
+ ssh_channel channel = NULL;
+ gboolean first = TRUE;
+ gboolean disconnected;
+ gint sock;
+ gint maxfd;
+ gint i;
+ gint ret;
+ struct sockaddr_in sin;
+
+ g_get_current_time(&t1);
+ t2 = t1;
+
+ switch (tunnel->tunnel_type) {
+ case REMMINA_SSH_TUNNEL_OPEN:
+ sock = remmina_ssh_tunnel_accept_local_connection(tunnel, TRUE);
+ if (sock < 0) {
+ tunnel->thread = 0;
+ return NULL;
+ }
+
+ channel = remmina_ssh_tunnel_create_forward_channel(tunnel);
+ if (!tunnel) {
+ close(sock);
+ tunnel->thread = 0;
+ return NULL;
+ }
+
+ remmina_ssh_tunnel_add_channel(tunnel, channel, sock);
+ break;
+
+ case REMMINA_SSH_TUNNEL_X11:
+ if ((tunnel->x11_channel = ssh_channel_new(tunnel->ssh.session)) == NULL) {
+ remmina_ssh_set_error(REMMINA_SSH(tunnel), "Failed to create channel : %s");
+ tunnel->thread = 0;
+ return NULL;
+ }
+ if (!remmina_public_get_xauth_cookie(tunnel->localdisplay, &ptr)) {
+ remmina_ssh_set_application_error(REMMINA_SSH(tunnel), "%s", ptr);
+ g_free(ptr);
+ tunnel->thread = 0;
+ return NULL;
+ }
+ if (ssh_channel_open_session(tunnel->x11_channel) ||
+ ssh_channel_request_x11(tunnel->x11_channel, TRUE, NULL, ptr,
+ gdk_x11_screen_get_screen_number(gdk_screen_get_default()))) {
+ g_free(ptr);
+ remmina_ssh_set_error(REMMINA_SSH(tunnel), "Failed to open channel : %s");
+ tunnel->thread = 0;
+ return NULL;
+ }
+ g_free(ptr);
+ if (ssh_channel_request_exec(tunnel->x11_channel, tunnel->dest)) {
+ ptr = g_strdup_printf(_("Failed to execute %s on SSH server : %%s"), tunnel->dest);
+ remmina_ssh_set_error(REMMINA_SSH(tunnel), ptr);
+ g_free(ptr);
+ tunnel->thread = 0;
+ return NULL;
+ }
+
+ if (tunnel->init_func &&
+ !(*tunnel->init_func)(tunnel, tunnel->callback_data)) {
+ if (tunnel->disconnect_func) {
+ (*tunnel->disconnect_func)(tunnel, tunnel->callback_data);
+ }
+ tunnel->thread = 0;
+ return NULL;
+ }
+
+ break;
+
+ case REMMINA_SSH_TUNNEL_XPORT:
+ /* Detect the next available port starting from 6010 on the server */
+ for (i = 10; i <= MAX_X_DISPLAY_NUMBER; i++) {
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ if (ssh_forward_listen(REMMINA_SSH(tunnel)->session, (tunnel->bindlocalhost ? "localhost" : NULL), 6000 + i, NULL)) {
+ continue;
+ }else {
+ tunnel->remotedisplay = i;
+ break;
+ }
+ G_GNUC_END_IGNORE_DEPRECATIONS
+ }
+ if (tunnel->remotedisplay < 1) {
+ remmina_ssh_set_error(REMMINA_SSH(tunnel), _("Failed to request port forwarding : %s"));
+ if (tunnel->disconnect_func) {
+ (*tunnel->disconnect_func)(tunnel, tunnel->callback_data);
+ }
+ tunnel->thread = 0;
+ return NULL;
+ }
+
+ if (tunnel->init_func &&
+ !(*tunnel->init_func)(tunnel, tunnel->callback_data)) {
+ if (tunnel->disconnect_func) {
+ (*tunnel->disconnect_func)(tunnel, tunnel->callback_data);
+ }
+ tunnel->thread = 0;
+ return NULL;
+ }
+
+ break;
+
+ case REMMINA_SSH_TUNNEL_REVERSE:
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ if (ssh_forward_listen(REMMINA_SSH(tunnel)->session, NULL, tunnel->port, NULL)) {
+ remmina_ssh_set_error(REMMINA_SSH (tunnel), _("Failed to request port forwarding : %s"));
+ if (tunnel->disconnect_func) {
+ (*tunnel->disconnect_func)(tunnel, tunnel->callback_data);
+ }
+ tunnel->thread = 0;
+ return NULL;
+ }
+ G_GNUC_END_IGNORE_DEPRECATIONS
+
+ if (tunnel->init_func &&
+ !(*tunnel->init_func)(tunnel, tunnel->callback_data)) {
+ if (tunnel->disconnect_func) {
+ (*tunnel->disconnect_func)(tunnel, tunnel->callback_data);
+ }
+ tunnel->thread = 0;
+ return NULL;
+ }
+
+ break;
+ }
+
+ tunnel->buffer_len = 10240;
+ tunnel->buffer = g_malloc(tunnel->buffer_len);
+
+ /* Start the tunnel data transmittion */
+ while (tunnel->running) {
+ if (tunnel->tunnel_type == REMMINA_SSH_TUNNEL_XPORT ||
+ tunnel->tunnel_type == REMMINA_SSH_TUNNEL_X11 ||
+ tunnel->tunnel_type == REMMINA_SSH_TUNNEL_REVERSE) {
+ if (first) {
+ first = FALSE;
+ /* Wait for a period of time for the first incoming connection */
+ if (tunnel->tunnel_type == REMMINA_SSH_TUNNEL_X11) {
+ channel = ssh_channel_accept_x11(tunnel->x11_channel, 15000);
+ }else {
+ channel = ssh_channel_accept_forward(REMMINA_SSH(tunnel)->session, 15000, &tunnel->port);
+ }
+ if (!channel) {
+ remmina_ssh_set_application_error(REMMINA_SSH(tunnel), _("No response from the server."));
+ if (tunnel->disconnect_func) {
+ (*tunnel->disconnect_func)(tunnel, tunnel->callback_data);
+ }
+ tunnel->thread = 0;
+ return NULL;
+ }
+ if (tunnel->connect_func) {
+ (*tunnel->connect_func)(tunnel, tunnel->callback_data);
+ }
+ if (tunnel->tunnel_type == REMMINA_SSH_TUNNEL_REVERSE) {
+ /* For reverse tunnel, we only need one connection. */
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ ssh_forward_cancel(REMMINA_SSH (tunnel)->session, NULL, tunnel->port);
+ G_GNUC_END_IGNORE_DEPRECATIONS
+ }
+ }else if (tunnel->tunnel_type != REMMINA_SSH_TUNNEL_REVERSE) {
+ /* Poll once per some period of time if no incoming connections.
+ * Don't try to poll continuously as it will significantly slow down the loop */
+ g_get_current_time(&t1);
+ diff = (t1.tv_sec - t2.tv_sec) * 10 + (t1.tv_usec - t2.tv_usec) / 100000;
+ if (diff > 1) {
+ if (tunnel->tunnel_type == REMMINA_SSH_TUNNEL_X11) {
+ channel = ssh_channel_accept_x11(tunnel->x11_channel, 0);
+ }else {
+ channel = ssh_channel_accept_forward(REMMINA_SSH(tunnel)->session, 0, &tunnel->port);
+ }
+ if (channel == NULL) {
+ t2 = t1;
+ }
+ }
+ }
+
+ if (channel) {
+ if (tunnel->tunnel_type == REMMINA_SSH_TUNNEL_REVERSE) {
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(tunnel->localport);
+ sin.sin_addr.s_addr = inet_addr("127.0.0.1");
+ sock = socket(AF_INET, SOCK_STREAM, 0);
+ if (connect(sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
+ remmina_ssh_set_application_error(REMMINA_SSH(tunnel),
+ "Cannot connect to local port %i.", tunnel->localport);
+ close(sock);
+ sock = -1;
+ }
+ }else {
+ sock = remmina_public_open_xdisplay(tunnel->localdisplay);
+ }
+ if (sock >= 0) {
+ remmina_ssh_tunnel_add_channel(tunnel, channel, sock);
+ }else {
+ /* Failed to create unix socket. Will this happen? */
+ ssh_channel_close(channel);
+ ssh_channel_send_eof(channel);
+ ssh_channel_free(channel);
+ }
+ channel = NULL;
+ }
+ }
+
+ if (tunnel->num_channels <= 0) {
+ /* No more connections. We should quit */
+ break;
+ }
+
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 200000;
+
+ FD_ZERO(&set);
+ maxfd = 0;
+ for (i = 0; i < tunnel->num_channels; i++) {
+ if (tunnel->sockets[i] > maxfd) {
+ maxfd = tunnel->sockets[i];
+ }
+ FD_SET(tunnel->sockets[i], &set);
+ }
+
+ ret = ssh_select(tunnel->channels, tunnel->channels_out, maxfd + 1, &set, &timeout);
+ if (!tunnel->running) break;
+ if (ret == SSH_EINTR) continue;
+ if (ret == -1) break;
+
+ i = 0;
+ while (tunnel->running && i < tunnel->num_channels) {
+ disconnected = FALSE;
+ if (FD_ISSET(tunnel->sockets[i], &set)) {
+ while (!disconnected &&
+ (len = read(tunnel->sockets[i], tunnel->buffer, tunnel->buffer_len)) > 0) {
+ for (ptr = tunnel->buffer, lenw = 0; len > 0; len -= lenw, ptr += lenw) {
+ lenw = ssh_channel_write(tunnel->channels[i], (char*)ptr, len);
+ if (lenw <= 0) {
+ disconnected = TRUE;
+ remmina_ssh_set_error(REMMINA_SSH(tunnel), "ssh_channel_write() returned an error: %s");
+ break;
+ }
+ }
+ }
+ if (len == 0) {
+ remmina_ssh_set_error(REMMINA_SSH(tunnel), "read on tunnel listening socket returned an error: %s");
+ disconnected = TRUE;
+ }
+ }
+ if (disconnected) {
+ remmina_log_printf("[SSH] tunnel has been disconnected. Reason: %s\n", REMMINA_SSH(tunnel)->error);
+ remmina_ssh_tunnel_remove_channel(tunnel, i);
+ continue;
+ }
+ i++;
+ }
+ if (!tunnel->running) break;
+
+ i = 0;
+ while (tunnel->running && i < tunnel->num_channels) {
+ disconnected = FALSE;
+ if (!tunnel->socketbuffers[i]) {
+ len = ssh_channel_poll(tunnel->channels[i], 0);
+ if (len == SSH_ERROR || len == SSH_EOF) {
+ remmina_ssh_set_error(REMMINA_SSH(tunnel), "ssh_channel_poll() returned an error : %s");
+ disconnected = TRUE;
+ }else if (len > 0) {
+ tunnel->socketbuffers[i] = remmina_ssh_tunnel_buffer_new(len);
+ len = ssh_channel_read_nonblocking(tunnel->channels[i], tunnel->socketbuffers[i]->data, len, 0);
+ if (len <= 0) {
+ remmina_ssh_set_error(REMMINA_SSH(tunnel), "ssh_channel_read_nonblocking() returned an error : %s");
+ disconnected = TRUE;
+ }else {
+ tunnel->socketbuffers[i]->len = len;
+ }
+ }
+ }
+
+ if (!disconnected && tunnel->socketbuffers[i]) {
+ for (lenw = 0; tunnel->socketbuffers[i]->len > 0;
+ tunnel->socketbuffers[i]->len -= lenw, tunnel->socketbuffers[i]->ptr += lenw) {
+ lenw = write(tunnel->sockets[i], tunnel->socketbuffers[i]->ptr, tunnel->socketbuffers[i]->len);
+ if (lenw == -1 && errno == EAGAIN && tunnel->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) {
+ remmina_ssh_set_error(REMMINA_SSH(tunnel), "write on tunnel listening socket returned an error: %s");
+ disconnected = TRUE;
+ break;
+ }
+ }
+ if (tunnel->socketbuffers[i]->len <= 0) {
+ remmina_ssh_tunnel_buffer_free(tunnel->socketbuffers[i]);
+ tunnel->socketbuffers[i] = NULL;
+ }
+ }
+
+ if (disconnected) {
+ remmina_log_printf("[SSH] tunnel has been disconnected. Reason: %s\n", REMMINA_SSH(tunnel)->error);
+ remmina_ssh_tunnel_remove_channel(tunnel, i);
+ continue;
+ }
+ i++;
+ }
+ /**
+ * Some protocols may open new connections during the session.
+ * e.g: SPICE opens a new connection for some channels.
+ */
+ sock = remmina_ssh_tunnel_accept_local_connection(tunnel, FALSE);
+ if (sock > 0) {
+ channel = remmina_ssh_tunnel_create_forward_channel(tunnel);
+ if (!channel) {
+ remmina_log_printf("[SSH] Failed to open new connection: %s\n", REMMINA_SSH(tunnel)->error);
+ close(sock);
+ /* Leave thread loop */
+ tunnel->running = FALSE;
+ }else {
+ remmina_ssh_tunnel_add_channel(tunnel, channel, sock);
+ }
+ }
+ }
+
+ remmina_ssh_tunnel_close_all_channels(tunnel);
+
+ return NULL;
+}
+
+static gpointer
+remmina_ssh_tunnel_main_thread(gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaSSHTunnel *tunnel = (RemminaSSHTunnel*)data;
+
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+
+ while (TRUE) {
+ remmina_ssh_tunnel_main_thread_proc(data);
+ if (tunnel->server_sock < 0 || tunnel->thread == 0 || !tunnel->running) break;
+ }
+ tunnel->thread = 0;
+ return NULL;
+}
+
+void
+remmina_ssh_tunnel_cancel_accept(RemminaSSHTunnel *tunnel)
+{
+ TRACE_CALL(__func__);
+ if (tunnel->server_sock >= 0) {
+ close(tunnel->server_sock);
+ tunnel->server_sock = -1;
+ }
+}
+
+gboolean
+remmina_ssh_tunnel_open(RemminaSSHTunnel* tunnel, const gchar *host, gint port, gint local_port)
+{
+ TRACE_CALL(__func__);
+ gint sock;
+ gint sockopt = 1;
+ struct sockaddr_in sin;
+
+ tunnel->tunnel_type = REMMINA_SSH_TUNNEL_OPEN;
+ tunnel->dest = g_strdup(host);
+ tunnel->port = port;
+ if (tunnel->port == 0) {
+ REMMINA_SSH(tunnel)->error = g_strdup("Destination port has not been assigned");
+ return FALSE;
+ }
+
+ /* Create the server socket that listens on the local port */
+ sock = socket(AF_INET, SOCK_STREAM, 0);
+ if (sock < 0) {
+ REMMINA_SSH(tunnel)->error = g_strdup("Failed to create socket.");
+ return FALSE;
+ }
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &sockopt, sizeof(sockopt));
+
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(local_port);
+ sin.sin_addr.s_addr = inet_addr("127.0.0.1");
+
+ if (bind(sock, (struct sockaddr *)&sin, sizeof(sin))) {
+ REMMINA_SSH(tunnel)->error = g_strdup("Failed to bind on local port.");
+ close(sock);
+ return FALSE;
+ }
+
+ if (listen(sock, 1)) {
+ REMMINA_SSH(tunnel)->error = g_strdup("Failed to listen on local port.");
+ close(sock);
+ return FALSE;
+ }
+
+ tunnel->server_sock = sock;
+ tunnel->running = TRUE;
+
+ if (pthread_create(&tunnel->thread, NULL, remmina_ssh_tunnel_main_thread, tunnel)) {
+ remmina_ssh_set_application_error(REMMINA_SSH(tunnel), "Failed to initialize pthread.");
+ tunnel->thread = 0;
+ return FALSE;
+ }
+ return TRUE;
+}
+
+gboolean
+remmina_ssh_tunnel_x11(RemminaSSHTunnel *tunnel, const gchar *cmd)
+{
+ TRACE_CALL(__func__);
+ tunnel->tunnel_type = REMMINA_SSH_TUNNEL_X11;
+ tunnel->dest = g_strdup(cmd);
+ tunnel->running = TRUE;
+
+ if (pthread_create(&tunnel->thread, NULL, remmina_ssh_tunnel_main_thread, tunnel)) {
+ remmina_ssh_set_application_error(REMMINA_SSH(tunnel), "Failed to initialize pthread.");
+ tunnel->thread = 0;
+ return FALSE;
+ }
+ return TRUE;
+}
+
+gboolean
+remmina_ssh_tunnel_xport(RemminaSSHTunnel *tunnel, gboolean bindlocalhost)
+{
+ TRACE_CALL(__func__);
+ tunnel->tunnel_type = REMMINA_SSH_TUNNEL_XPORT;
+ tunnel->bindlocalhost = bindlocalhost;
+ tunnel->running = TRUE;
+
+ if (pthread_create(&tunnel->thread, NULL, remmina_ssh_tunnel_main_thread, tunnel)) {
+ remmina_ssh_set_application_error(REMMINA_SSH(tunnel), "Failed to initialize pthread.");
+ tunnel->thread = 0;
+ return FALSE;
+ }
+ return TRUE;
+}
+
+gboolean
+remmina_ssh_tunnel_reverse(RemminaSSHTunnel *tunnel, gint port, gint local_port)
+{
+ TRACE_CALL(__func__);
+ tunnel->tunnel_type = REMMINA_SSH_TUNNEL_REVERSE;
+ tunnel->port = port;
+ tunnel->localport = local_port;
+ tunnel->running = TRUE;
+
+ if (pthread_create(&tunnel->thread, NULL, remmina_ssh_tunnel_main_thread, tunnel)) {
+ remmina_ssh_set_application_error(REMMINA_SSH(tunnel), "Failed to initialize pthread.");
+ tunnel->thread = 0;
+ return FALSE;
+ }
+ return TRUE;
+}
+
+gboolean
+remmina_ssh_tunnel_terminated(RemminaSSHTunnel* tunnel)
+{
+ TRACE_CALL(__func__);
+ return (tunnel->thread == 0);
+}
+
+void
+remmina_ssh_tunnel_free(RemminaSSHTunnel* tunnel)
+{
+ TRACE_CALL(__func__);
+ pthread_t thread;
+
+ thread = tunnel->thread;
+ if (thread != 0) {
+ tunnel->running = FALSE;
+ pthread_cancel(thread);
+ pthread_join(thread, NULL);
+ tunnel->thread = 0;
+ }
+
+ if (tunnel->tunnel_type == REMMINA_SSH_TUNNEL_XPORT && tunnel->remotedisplay > 0) {
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ ssh_forward_cancel(REMMINA_SSH (tunnel)->session, NULL, 6000 + tunnel->remotedisplay);
+ G_GNUC_END_IGNORE_DEPRECATIONS
+ }
+ if (tunnel->server_sock >= 0) {
+ close(tunnel->server_sock);
+ tunnel->server_sock = -1;
+ }
+ remmina_ssh_tunnel_close_all_channels(tunnel);
+
+ g_free(tunnel->buffer);
+ g_free(tunnel->channels_out);
+ g_free(tunnel->dest);
+ g_free(tunnel->localdisplay);
+
+ remmina_ssh_free(REMMINA_SSH(tunnel));
+}
+
+/*-----------------------------------------------------------------------------*
+* SSH sFTP *
+*-----------------------------------------------------------------------------*/
+
+RemminaSFTP*
+remmina_sftp_new_from_file(RemminaFile *remminafile)
+{
+ TRACE_CALL(__func__);
+ RemminaSFTP *sftp;
+
+ sftp = g_new(RemminaSFTP, 1);
+
+ remmina_ssh_init_from_file(REMMINA_SSH(sftp), remminafile);
+
+ sftp->sftp_sess = NULL;
+
+ return sftp;
+}
+
+RemminaSFTP*
+remmina_sftp_new_from_ssh(RemminaSSH *ssh)
+{
+ TRACE_CALL(__func__);
+ RemminaSFTP *sftp;
+
+ sftp = g_new(RemminaSFTP, 1);
+
+ remmina_ssh_init_from_ssh(REMMINA_SSH(sftp), ssh);
+
+ sftp->sftp_sess = NULL;
+
+ return sftp;
+}
+
+gboolean
+remmina_sftp_open(RemminaSFTP *sftp)
+{
+ TRACE_CALL(__func__);
+ sftp->sftp_sess = sftp_new(sftp->ssh.session);
+ if (!sftp->sftp_sess) {
+ remmina_ssh_set_error(REMMINA_SSH(sftp), _("Failed to create sftp session: %s"));
+ return FALSE;
+ }
+ if (sftp_init(sftp->sftp_sess)) {
+ remmina_ssh_set_error(REMMINA_SSH(sftp), _("Failed to initialize sftp session: %s"));
+ return FALSE;
+ }
+ return TRUE;
+}
+
+void
+remmina_sftp_free(RemminaSFTP *sftp)
+{
+ TRACE_CALL(__func__);
+ if (sftp->sftp_sess) {
+ sftp_free(sftp->sftp_sess);
+ sftp->sftp_sess = NULL;
+ }
+ remmina_ssh_free(REMMINA_SSH(sftp));
+}
+
+/*-----------------------------------------------------------------------------*
+* SSH Shell *
+*-----------------------------------------------------------------------------*/
+
+RemminaSSHShell*
+remmina_ssh_shell_new_from_file(RemminaFile *remminafile)
+{
+ TRACE_CALL(__func__);
+ RemminaSSHShell *shell;
+
+ shell = g_new0(RemminaSSHShell, 1);
+
+ remmina_ssh_init_from_file(REMMINA_SSH(shell), remminafile);
+
+ shell->master = -1;
+ shell->slave = -1;
+ shell->exec = g_strdup(remmina_file_get_string(remminafile, "exec"));
+
+ return shell;
+}
+
+RemminaSSHShell*
+remmina_ssh_shell_new_from_ssh(RemminaSSH *ssh)
+{
+ TRACE_CALL(__func__);
+ RemminaSSHShell *shell;
+
+ shell = g_new0(RemminaSSHShell, 1);
+
+ remmina_ssh_init_from_ssh(REMMINA_SSH(shell), ssh);
+
+ shell->master = -1;
+ shell->slave = -1;
+
+ return shell;
+}
+
+static gboolean
+remmina_ssh_call_exit_callback_on_main_thread(gpointer data)
+{
+ TRACE_CALL(__func__);
+
+ RemminaSSHShell *shell = (RemminaSSHShell*)data;
+ if ( shell->exit_callback )
+ shell->exit_callback( shell->user_data );
+ return FALSE;
+}
+
+static gpointer
+remmina_ssh_shell_thread(gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaSSHShell *shell = (RemminaSSHShell*)data;
+ fd_set fds;
+ struct timeval timeout;
+ ssh_channel channel = NULL;
+ ssh_channel ch[2], chout[2];
+ gchar *buf = NULL;
+ gint buf_len;
+ gint len;
+ gint i, ret;
+
+ LOCK_SSH(shell)
+
+ if ((channel = ssh_channel_new(REMMINA_SSH(shell)->session)) == NULL ||
+ ssh_channel_open_session(channel)) {
+ UNLOCK_SSH(shell)
+ remmina_ssh_set_error(REMMINA_SSH(shell), "Failed to open channel : %s");
+ if (channel) ssh_channel_free(channel);
+ shell->thread = 0;
+ return NULL;
+ }
+
+ ssh_channel_request_pty(channel);
+ if (shell->exec && shell->exec[0]) {
+ ret = ssh_channel_request_exec(channel, shell->exec);
+ }else {
+ ret = ssh_channel_request_shell(channel);
+ }
+ if (ret) {
+ UNLOCK_SSH(shell)
+ remmina_ssh_set_error(REMMINA_SSH(shell), "Failed to request shell : %s");
+ ssh_channel_close(channel);
+ ssh_channel_send_eof(channel);
+ ssh_channel_free(channel);
+ shell->thread = 0;
+ return NULL;
+ }
+
+ shell->channel = channel;
+
+ UNLOCK_SSH(shell)
+
+ buf_len = 1000;
+ buf = g_malloc(buf_len + 1);
+
+ ch[0] = channel;
+ ch[1] = NULL;
+
+ while (!shell->closed) {
+ timeout.tv_sec = 1;
+ timeout.tv_usec = 0;
+
+ FD_ZERO(&fds);
+ FD_SET(shell->slave, &fds);
+
+ ret = ssh_select(ch, chout, shell->slave + 1, &fds, &timeout);
+ if (ret == SSH_EINTR) continue;
+ if (ret == -1) break;
+
+ if (FD_ISSET(shell->slave, &fds)) {
+ len = read(shell->slave, buf, buf_len);
+ if (len <= 0) break;
+ LOCK_SSH(shell)
+ ssh_channel_write(channel, buf, len);
+ UNLOCK_SSH(shell)
+ }
+ for (i = 0; i < 2; i++) {
+ LOCK_SSH(shell)
+ len = ssh_channel_poll(channel, i);
+ UNLOCK_SSH(shell)
+ if (len == SSH_ERROR || len == SSH_EOF) {
+ shell->closed = TRUE;
+ break;
+ }
+ if (len <= 0) continue;
+ if (len > buf_len) {
+ buf_len = len;
+ buf = (gchar*)g_realloc(buf, buf_len + 1);
+ }
+ LOCK_SSH(shell)
+ len = ssh_channel_read_nonblocking(channel, buf, len, i);
+ UNLOCK_SSH(shell)
+ if (len <= 0) {
+ shell->closed = TRUE;
+ break;
+ }
+ while (len > 0) {
+ ret = write(shell->slave, buf, len);
+ if (ret <= 0) break;
+ len -= ret;
+ }
+ }
+ }
+
+ LOCK_SSH(shell)
+ shell->channel = NULL;
+ ssh_channel_close(channel);
+ ssh_channel_send_eof(channel);
+ ssh_channel_free(channel);
+ UNLOCK_SSH(shell)
+
+ g_free(buf);
+ shell->thread = 0;
+
+ if ( shell->exit_callback ) {
+ IDLE_ADD((GSourceFunc)remmina_ssh_call_exit_callback_on_main_thread, (gpointer)shell );
+ }
+ return NULL;
+}
+
+
+gboolean
+remmina_ssh_shell_open(RemminaSSHShell *shell, RemminaSSHExitFunc exit_callback, gpointer data)
+{
+ TRACE_CALL(__func__);
+ gchar *slavedevice;
+ struct termios stermios;
+
+ shell->master = posix_openpt(O_RDWR | O_NOCTTY);
+ if (shell->master == -1 ||
+ grantpt(shell->master) == -1 ||
+ unlockpt(shell->master) == -1 ||
+ (slavedevice = ptsname(shell->master)) == NULL ||
+ (shell->slave = open(slavedevice, O_RDWR | O_NOCTTY)) < 0) {
+ REMMINA_SSH(shell)->error = g_strdup("Failed to create pty device.");
+ return FALSE;
+ }
+
+ /* As per libssh documentation */
+ tcgetattr(shell->slave, &stermios);
+ stermios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
+ stermios.c_oflag &= ~OPOST;
+ stermios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
+ stermios.c_cflag &= ~(CSIZE | PARENB);
+ stermios.c_cflag |= CS8;
+ tcsetattr(shell->slave, TCSANOW, &stermios);
+
+ shell->exit_callback = exit_callback;
+ shell->user_data = data;
+
+ /* Once the process started, we should always TRUE and assume the pthread will be created always */
+ pthread_create(&shell->thread, NULL, remmina_ssh_shell_thread, shell);
+
+ return TRUE;
+}
+
+void
+remmina_ssh_shell_set_size(RemminaSSHShell *shell, gint columns, gint rows)
+{
+ TRACE_CALL(__func__);
+ LOCK_SSH(shell)
+ if (shell->channel) {
+ ssh_channel_change_pty_size(shell->channel, columns, rows);
+ }
+ UNLOCK_SSH(shell)
+}
+
+void
+remmina_ssh_shell_free(RemminaSSHShell *shell)
+{
+ TRACE_CALL(__func__);
+ pthread_t thread = shell->thread;
+
+ shell->exit_callback = NULL;
+ if (thread) {
+ shell->closed = TRUE;
+ pthread_join(thread, NULL);
+ }
+ close(shell->slave);
+ if (shell->exec) {
+ g_free(shell->exec);
+ shell->exec = NULL;
+ }
+ /* It's not necessary to close shell->slave since the other end (vte) will close it */;
+ remmina_ssh_free(REMMINA_SSH(shell));
+}
+
+#endif /* HAVE_LIBSSH */
+
diff --git a/src/remmina_ssh.h b/src/remmina_ssh.h
new file mode 100644
index 000000000..0bfc1e292
--- /dev/null
+++ b/src/remmina_ssh.h
@@ -0,0 +1,268 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2011 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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.
+ *
+ */
+
+#pragma once
+
+#include "config.h"
+
+#ifdef HAVE_LIBSSH
+
+#define LIBSSH_STATIC 1
+#include <libssh/libssh.h>
+#include <libssh/callbacks.h>
+#include <libssh/sftp.h>
+#include <pthread.h>
+#include "remmina_file.h"
+#include "remmina_init_dialog.h"
+
+G_BEGIN_DECLS
+
+/*-----------------------------------------------------------------------------*
+* SSH Base *
+*-----------------------------------------------------------------------------*/
+
+#define REMMINA_SSH(a) ((RemminaSSH*)a)
+
+typedef struct _RemminaSSH {
+ ssh_session session;
+ ssh_callbacks callback;
+ gboolean authenticated;
+
+ gchar *server;
+ gint port;
+ gchar *user;
+ gint auth;
+ gchar *password;
+ gchar *privkeyfile;
+
+ gchar *charset;
+ gchar *kex_algorithms;
+ gchar *ciphers;
+ gchar *hostkeytypes;
+ gchar *proxycommand;
+ gint stricthostkeycheck;
+ const gchar *compression;
+
+ gchar *error;
+
+ pthread_mutex_t ssh_mutex;
+
+ gchar *passphrase;
+} RemminaSSH;
+
+gchar* remmina_ssh_identity_path(const gchar *id);
+
+/* Auto-detect commonly used private key identities */
+gchar* remmina_ssh_find_identity(void);
+
+/* Initialize the ssh object */
+gboolean remmina_ssh_init_from_file(RemminaSSH *ssh, RemminaFile *remminafile);
+
+/* Initialize the SSH session */
+gboolean remmina_ssh_init_session(RemminaSSH *ssh);
+
+/* Authenticate SSH session */
+/* -1: Require password; 0: Failed; 1: Succeeded */
+gint remmina_ssh_auth(RemminaSSH *ssh, const gchar *password);
+
+/* -1: Cancelled; 0: Failed; 1: Succeeded */
+gint remmina_ssh_auth_gui(RemminaSSH *ssh, RemminaInitDialog *dialog, RemminaFile *remminafile);
+
+/* Error handling */
+#define remmina_ssh_has_error(ssh) (((RemminaSSH*)ssh)->error != NULL)
+void remmina_ssh_set_error(RemminaSSH *ssh, const gchar *fmt);
+void remmina_ssh_set_application_error(RemminaSSH *ssh, const gchar *fmt, ...);
+
+/* Converts a string to/from UTF-8, or simply duplicate it if no conversion */
+gchar* remmina_ssh_convert(RemminaSSH *ssh, const gchar *from);
+gchar* remmina_ssh_unconvert(RemminaSSH *ssh, const gchar *from);
+
+void remmina_ssh_free(RemminaSSH *ssh);
+
+/*-----------------------------------------------------------------------------*
+* SSH Tunnel *
+*-----------------------------------------------------------------------------*/
+typedef struct _RemminaSSHTunnel RemminaSSHTunnel;
+typedef struct _RemminaSSHTunnelBuffer RemminaSSHTunnelBuffer;
+
+typedef gboolean (*RemminaSSHTunnelCallback) (RemminaSSHTunnel*, gpointer);
+
+enum {
+ REMMINA_SSH_TUNNEL_OPEN,
+ REMMINA_SSH_TUNNEL_X11,
+ REMMINA_SSH_TUNNEL_XPORT,
+ REMMINA_SSH_TUNNEL_REVERSE
+};
+
+struct _RemminaSSHTunnel {
+ RemminaSSH ssh;
+
+ gint tunnel_type;
+
+ ssh_channel *channels;
+ gint *sockets;
+ RemminaSSHTunnelBuffer **socketbuffers;
+ gint num_channels;
+ gint max_channels;
+
+ ssh_channel x11_channel;
+
+ pthread_t thread;
+ gboolean running;
+
+ gchar *buffer;
+ gint buffer_len;
+ ssh_channel *channels_out;
+
+ gint server_sock;
+ gchar *dest;
+ gint port;
+ gint localport;
+
+ gint remotedisplay;
+ gboolean bindlocalhost;
+ gchar *localdisplay;
+
+ RemminaSSHTunnelCallback init_func;
+ RemminaSSHTunnelCallback connect_func;
+ RemminaSSHTunnelCallback disconnect_func;
+ gpointer callback_data;
+};
+
+/* Create a new SSH Tunnel session and connects to the SSH server */
+RemminaSSHTunnel* remmina_ssh_tunnel_new_from_file(RemminaFile *remminafile);
+
+/* Open the tunnel. A new thread will be started and listen on a local port.
+ * dest: The host:port of the remote destination
+ * local_port: The listening local port for the tunnel
+ */
+gboolean remmina_ssh_tunnel_open(RemminaSSHTunnel *tunnel, const gchar *host, gint port, gint local_port);
+
+/* Cancel accepting any incoming tunnel request.
+ * Typically called after the connection has already been establish.
+ */
+void remmina_ssh_tunnel_cancel_accept(RemminaSSHTunnel *tunnel);
+
+/* Accept the X11 tunnel. A new thread will be started and connect to local display.
+ * cmd: The remote X11 application to be executed
+ */
+gboolean remmina_ssh_tunnel_x11(RemminaSSHTunnel *tunnel, const gchar *cmd);
+
+/* start X Port Forwarding */
+gboolean remmina_ssh_tunnel_xport(RemminaSSHTunnel *tunnel, gboolean bindlocalhost);
+
+/* start reverse tunnel. A new thread will be started and waiting for incoming connection.
+ * port: the port listening on the remote server side.
+ * local_port: the port listening on the local side. When connection on the server side comes
+ * in, it will connect to the local port and create the tunnel. The caller should
+ * start listening on the local port before calling it or in connect_func callback.
+ */
+gboolean remmina_ssh_tunnel_reverse(RemminaSSHTunnel *tunnel, gint port, gint local_port);
+
+/* Tells if the tunnel is terminated after start */
+gboolean remmina_ssh_tunnel_terminated(RemminaSSHTunnel *tunnel);
+
+/* Free the tunnel */
+void remmina_ssh_tunnel_free(RemminaSSHTunnel *tunnel);
+
+/*-----------------------------------------------------------------------------*
+* SSH sFTP *
+*-----------------------------------------------------------------------------*/
+
+typedef struct _RemminaSFTP {
+ RemminaSSH ssh;
+
+ sftp_session sftp_sess;
+} RemminaSFTP;
+
+/* Create a new SFTP session object from RemminaFile */
+RemminaSFTP* remmina_sftp_new_from_file(RemminaFile *remminafile);
+
+/* Create a new SFTP session object from existing SSH session */
+RemminaSFTP* remmina_sftp_new_from_ssh(RemminaSSH *ssh);
+
+/* open the SFTP session, assuming the session already authenticated */
+gboolean remmina_sftp_open(RemminaSFTP *sftp);
+
+/* Free the SFTP session */
+void remmina_sftp_free(RemminaSFTP *sftp);
+
+/*-----------------------------------------------------------------------------*
+* SSH Shell *
+*-----------------------------------------------------------------------------*/
+typedef void (*RemminaSSHExitFunc) (gpointer data);
+
+typedef struct _RemminaSSHShell {
+ RemminaSSH ssh;
+
+ gint master;
+ gint slave;
+ gchar *exec;
+ pthread_t thread;
+ ssh_channel channel;
+ gboolean closed;
+ RemminaSSHExitFunc exit_callback;
+ gpointer user_data;
+} RemminaSSHShell;
+
+/* Create a new SSH Shell session object from RemminaFile */
+RemminaSSHShell* remmina_ssh_shell_new_from_file(RemminaFile *remminafile);
+
+/* Create a new SSH Shell session object from existing SSH session */
+RemminaSSHShell* remmina_ssh_shell_new_from_ssh(RemminaSSH *ssh);
+
+/* open the SSH Shell, assuming the session already authenticated */
+gboolean remmina_ssh_shell_open(RemminaSSHShell *shell, RemminaSSHExitFunc exit_callback, gpointer data);
+
+/* Change the SSH Shell terminal size */
+void remmina_ssh_shell_set_size(RemminaSSHShell *shell, gint columns, gint rows);
+
+/* Free the SFTP session */
+void remmina_ssh_shell_free(RemminaSSHShell *shell);
+
+G_END_DECLS
+
+#else
+
+#define RemminaSSH void
+#define RemminaSSHTunnel void
+#define RemminaSFTP void
+#define RemminaSSHShell void
+typedef void (*RemminaSSHTunnelCallback)(void);
+
+#endif /* HAVE_LIBSSH */
+
+
diff --git a/src/remmina_ssh_plugin.c b/src/remmina_ssh_plugin.c
new file mode 100644
index 000000000..af661651f
--- /dev/null
+++ b/src/remmina_ssh_plugin.c
@@ -0,0 +1,1064 @@
+/**
+ * Remmina - The GTK+ Remote Desktop Client - SSH plugin.
+ *
+ * @copyright Copyright (C) 2010-2011 Vic Lee.
+ * @copyright Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo.
+ * @copyright 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 "config.h"
+#include "remmina/remmina_trace_calls.h"
+
+#if defined (HAVE_LIBSSH) && defined (HAVE_LIBVTE)
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <vte/vte.h>
+#include <locale.h>
+#include <langinfo.h>
+#include "remmina_public.h"
+#include "remmina_plugin_manager.h"
+#include "remmina_ssh.h"
+#include "remmina_protocol_widget.h"
+#include "remmina_pref.h"
+#include "remmina_ssh_plugin.h"
+#include "remmina_masterthread_exec.h"
+
+#define REMMINA_PLUGIN_SSH_FEATURE_TOOL_COPY 1
+#define REMMINA_PLUGIN_SSH_FEATURE_TOOL_PASTE 2
+#define REMMINA_PLUGIN_SSH_FEATURE_TOOL_SELECT_ALL 3
+
+#define GET_PLUGIN_DATA(gp) (RemminaPluginSshData*)g_object_get_data(G_OBJECT(gp), "plugin-data");
+
+/** Palette colors taken from sakura */
+#define PALETTE_SIZE 16
+
+enum color_schemes { GRUVBOX, TANGO, LINUX, SOLARIZED_DARK, SOLARIZED_LIGHT, XTERM, CUSTOM };
+
+/** 16 color palettes in GdkRGBA format (red, green, blue, alpha).
+ * Text displayed in the first 8 colors (0-7) is meek (uses thin strokes).
+ * Text displayed in the second 8 colors (8-15) is bold (uses thick strokes).
+ **/
+const GdkRGBA gruvbox_palette[PALETTE_SIZE] = {
+ { 0.156863, 0.156863, 0.156863, 1.000000 },
+ { 0.800000, 0.141176, 0.113725, 1.000000 },
+ { 0.596078, 0.592157, 0.101961, 1.000000 },
+ { 0.843137, 0.600000, 0.129412, 1.000000 },
+ { 0.270588, 0.521569, 0.533333, 1.000000 },
+ { 0.694118, 0.384314, 0.525490, 1.000000 },
+ { 0.407843, 0.615686, 0.415686, 1.000000 },
+ { 0.658824, 0.600000, 0.517647, 1.000000 },
+ { 0.572549, 0.513725, 0.454902, 1.000000 },
+ { 0.984314, 0.286275, 0.203922, 1.000000 },
+ { 0.721569, 0.733333, 0.149020, 1.000000 },
+ { 0.980392, 0.741176, 0.184314, 1.000000 },
+ { 0.513725, 0.647059, 0.596078, 1.000000 },
+ { 0.827451, 0.525490, 0.607843, 1.000000 },
+ { 0.556863, 0.752941, 0.486275, 1.000000 },
+ { 0.921569, 0.858824, 0.698039, 1.000000 },
+};
+
+const GdkRGBA tango_palette[PALETTE_SIZE] = {
+ { 0, 0, 0, 1 },
+ { 0.8, 0, 0, 1 },
+ { 0.305882, 0.603922, 0.023529, 1 },
+ { 0.768627, 0.627451, 0, 1 },
+ { 0.203922, 0.396078, 0.643137, 1 },
+ { 0.458824, 0.313725, 0.482353, 1 },
+ { 0.0235294, 0.596078, 0.603922, 1 },
+ { 0.827451, 0.843137, 0.811765, 1 },
+ { 0.333333, 0.341176, 0.32549, 1 },
+ { 0.937255, 0.160784, 0.160784, 1 },
+ { 0.541176, 0.886275, 0.203922, 1 },
+ { 0.988235, 0.913725, 0.309804, 1 },
+ { 0.447059, 0.623529, 0.811765, 1 },
+ { 0.678431, 0.498039, 0.658824, 1 },
+ { 0.203922, 0.886275, 0.886275, 1 },
+ { 0.933333, 0.933333, 0.92549, 1 }
+};
+
+const GdkRGBA linux_palette[PALETTE_SIZE] = {
+ { 0, 0, 0, 1 },
+ { 0.666667, 0, 0, 1 },
+ { 0, 0.666667, 0, 1 },
+ { 0.666667, 0.333333, 0, 1 },
+ { 0, 0, 0.666667, 1 },
+ { 0.666667, 0, 0.666667, 1 },
+ { 0, 0.666667, 0.666667, 1 },
+ { 0.666667, 0.666667, 0.666667, 1 },
+ { 0.333333, 0.333333, 0.333333, 1 },
+ { 1, 0.333333, 0.333333, 1 },
+ { 0.333333, 1, 0.333333, 1 },
+ { 1, 1, 0.333333, 1 },
+ { 0.333333, 0.333333, 1, 1 },
+ { 1, 0.333333, 1, 1 },
+ { 0.333333, 1, 1, 1 },
+ { 1, 1, 1, 1 }
+};
+
+const GdkRGBA solarized_dark_palette[PALETTE_SIZE] = {
+ { 0.027451, 0.211765, 0.258824, 1 },
+ { 0.862745, 0.196078, 0.184314, 1 },
+ { 0.521569, 0.600000, 0.000000, 1 },
+ { 0.709804, 0.537255, 0.000000, 1 },
+ { 0.149020, 0.545098, 0.823529, 1 },
+ { 0.827451, 0.211765, 0.509804, 1 },
+ { 0.164706, 0.631373, 0.596078, 1 },
+ { 0.933333, 0.909804, 0.835294, 1 },
+ { 0.000000, 0.168627, 0.211765, 1 },
+ { 0.796078, 0.294118, 0.086275, 1 },
+ { 0.345098, 0.431373, 0.458824, 1 },
+ { 0.396078, 0.482353, 0.513725, 1 },
+ { 0.513725, 0.580392, 0.588235, 1 },
+ { 0.423529, 0.443137, 0.768627, 1 },
+ { 0.576471, 0.631373, 0.631373, 1 },
+ { 0.992157, 0.964706, 0.890196, 1 }
+};
+
+const GdkRGBA solarized_light_palette[PALETTE_SIZE] = {
+ { 0.933333, 0.909804, 0.835294, 1 },
+ { 0.862745, 0.196078, 0.184314, 1 },
+ { 0.521569, 0.600000, 0.000000, 1 },
+ { 0.709804, 0.537255, 0.000000, 1 },
+ { 0.149020, 0.545098, 0.823529, 1 },
+ { 0.827451, 0.211765, 0.509804, 1 },
+ { 0.164706, 0.631373, 0.596078, 1 },
+ { 0.027451, 0.211765, 0.258824, 1 },
+ { 0.992157, 0.964706, 0.890196, 1 },
+ { 0.796078, 0.294118, 0.086275, 1 },
+ { 0.576471, 0.631373, 0.631373, 1 },
+ { 0.513725, 0.580392, 0.588235, 1 },
+ { 0.396078, 0.482353, 0.513725, 1 },
+ { 0.423529, 0.443137, 0.768627, 1 },
+ { 0.345098, 0.431373, 0.458824, 1 },
+ { 0.000000, 0.168627, 0.211765, 1 }
+};
+
+const GdkRGBA xterm_palette[PALETTE_SIZE] = {
+ { 0, 0, 0, 1 },
+ { 0.803922, 0, 0, 1 },
+ { 0, 0.803922, 0, 1 },
+ { 0.803922, 0.803922, 0, 1 },
+ { 0.117647, 0.564706, 1, 1 },
+ { 0.803922, 0, 0.803922, 1 },
+ { 0, 0.803922, 0.803922, 1 },
+ { 0.898039, 0.898039, 0.898039, 1 },
+ { 0.298039, 0.298039, 0.298039, 1 },
+ { 1, 0, 0, 1 },
+ { 0, 1, 0, 1 },
+ { 1, 1, 0, 1 },
+ { 0.27451, 0.509804, 0.705882, 1 },
+ { 1, 0, 1, 1 },
+ { 0, 1, 1, 1 },
+ { 1, 1, 1, 1 }
+};
+
+#if VTE_CHECK_VERSION(0, 38, 0)
+static struct {
+ const GdkRGBA *palette;
+} remminavte;
+#endif
+
+#define DEFAULT_PALETTE "linux_palette"
+
+
+/** The SSH plugin implementation */
+typedef struct _RemminaPluginSshData {
+ RemminaSSHShell *shell;
+ GFile *vte_session_file;
+ GtkWidget *vte;
+
+ const GdkRGBA *palette;
+
+ pthread_t thread;
+} RemminaPluginSshData;
+
+
+static RemminaPluginService *remmina_plugin_service = NULL;
+
+/**
+ * Remmina Protocol plugin main function.
+ *
+ * First it starts the SSH tunnel if needed and than the SSH connection.
+ *
+ */
+static gpointer
+remmina_plugin_ssh_main_thread(gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp = (RemminaProtocolWidget*)data;
+ RemminaPluginSshData *gpdata;
+ RemminaFile *remminafile;
+ RemminaSSH *ssh;
+ RemminaSSHShell *shell = NULL;
+ gboolean cont = FALSE;
+ gint ret;
+ gchar *charset;
+ const gchar *saveserver;
+ const gchar *saveusername;
+ gchar *hostport;
+ gchar tunneluser[33]; /**< On linux a username can have a 32 char lenght */
+ gchar tunnelserver[256]; /**< On linux a servername can have a 255 char lenght */
+ gchar tunnelport[6]; /**< A TCP port can have a maximum value of 65535 */
+ gchar *host;
+ gint port;
+
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+ CANCEL_ASYNC
+
+ gpdata = GET_PLUGIN_DATA(gp);
+
+ /**
+ * remmina_plugin_service->protocol_plugin_start_direct_tunnel start the
+ * SSH Tunnel and return the server + port string
+ * Therefore we set the SSH Tunnel username here, before the tunnel
+ * is established and than set it back to the destination SSH user.
+ *
+ **/
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ /* We save the ssh server name, so that we can restore it at the end of the connection */
+ saveserver = remmina_plugin_service->file_get_string(remminafile, "ssh_server");
+ remmina_plugin_service->file_set_string(remminafile, "save_ssh_server", g_strdup(saveserver));
+ /* We save the ssh username, so that we can restore it at the end of the connection */
+ saveusername = remmina_plugin_service->file_get_string(remminafile, "ssh_username");
+ remmina_plugin_service->file_set_string(remminafile, "save_ssh_username", g_strdup(saveusername));
+
+ if (saveserver) {
+ /** if the server string contains the character @ we extract the user
+ * and the server string (host:port)
+ **/
+ if (strchr(saveserver, '@')) {
+ sscanf(saveserver, "%[_a-zA-Z0-9.]@%[_a-zA-Z0-9.]:%[0-9]",
+ tunneluser, tunnelserver, tunnelport);
+ g_print ("Username: %s, tunneluser: %s\n",
+ remmina_plugin_service->file_get_string(remminafile, "ssh_username"), tunneluser);
+ if (saveusername != NULL && tunneluser[0] != '\0') {
+ remmina_plugin_service->file_set_string(remminafile, "ssh_username", NULL);
+ }
+ remmina_plugin_service->file_set_string(remminafile, "ssh_username",
+ g_strdup(tunneluser));
+ remmina_plugin_service->file_set_string(remminafile, "ssh_server",
+ g_strconcat (tunnelserver, ":", tunnelport, NULL));
+ }
+ }
+
+ hostport = remmina_plugin_service->protocol_plugin_start_direct_tunnel(gp, 22, FALSE);
+ /* We restore the ssh username as the tunnel is set */
+ remmina_plugin_service->file_set_string(remminafile, "ssh_username", g_strdup(saveusername));
+ if (hostport == NULL) {
+ return FALSE;
+ }
+ remmina_plugin_service->get_server_port(hostport, 22, &host, &port);
+
+ ssh = g_object_get_data(G_OBJECT(gp), "user-data");
+ if (ssh) {
+ /* Create SSH Shell connection based on existing SSH session */
+ shell = remmina_ssh_shell_new_from_ssh(ssh);
+ if (remmina_ssh_init_session(REMMINA_SSH(shell)) &&
+ remmina_ssh_auth(REMMINA_SSH(shell), NULL) > 0 &&
+ remmina_ssh_shell_open(shell, (RemminaSSHExitFunc)
+ remmina_plugin_service->protocol_plugin_close_connection, gp)) {
+ cont = TRUE;
+ }
+ }else {
+ /* New SSH Shell connection */
+ if (remmina_plugin_service->file_get_int(remminafile, "ssh_enabled", FALSE)) {
+ remmina_plugin_service->file_set_string(remminafile, "ssh_server", g_strdup(hostport));
+ }else {
+ remmina_plugin_service->file_set_string(remminafile, "ssh_server",
+ remmina_plugin_service->file_get_string(remminafile, "server"));
+ }
+ g_free(hostport);
+ g_free(host);
+
+ shell = remmina_ssh_shell_new_from_file(remminafile);
+ while (1) {
+ if (!remmina_ssh_init_session(REMMINA_SSH(shell))) {
+ remmina_plugin_service->protocol_plugin_set_error(gp, "%s", REMMINA_SSH(shell)->error);
+ break;
+ }
+
+ ret = remmina_ssh_auth_gui(REMMINA_SSH(shell),
+ REMMINA_INIT_DIALOG(remmina_protocol_widget_get_init_dialog(gp)),
+ remminafile);
+ if (ret == 0) {
+ remmina_plugin_service->protocol_plugin_set_error(gp, "%s", REMMINA_SSH(shell)->error);
+ }
+ if (ret <= 0) break;
+
+ if (!remmina_ssh_shell_open(shell, (RemminaSSHExitFunc)
+ remmina_plugin_service->protocol_plugin_close_connection, gp)) {
+ remmina_plugin_service->protocol_plugin_set_error(gp, "%s", REMMINA_SSH(shell)->error);
+ break;
+ }
+
+ cont = TRUE;
+ break;
+ }
+
+ /* We restore the ssh_server name */
+ remmina_plugin_service->file_set_string(remminafile, "ssh_server",
+ remmina_plugin_service->file_get_string(remminafile, "save_ssh_server"));
+ }
+ if (!cont) {
+ if (shell) remmina_ssh_shell_free(shell);
+ IDLE_ADD((GSourceFunc)remmina_plugin_service->protocol_plugin_close_connection, gp);
+ return NULL;
+ }
+
+ gpdata->shell = shell;
+
+ charset = REMMINA_SSH(shell)->charset;
+ remmina_plugin_ssh_vte_terminal_set_encoding_and_pty(VTE_TERMINAL(gpdata->vte), charset, shell->master, shell->slave);
+ remmina_plugin_service->protocol_plugin_emit_signal(gp, "connect");
+
+ gpdata->thread = 0;
+ return NULL;
+}
+
+void remmina_plugin_ssh_vte_terminal_set_encoding_and_pty(VteTerminal *terminal, const char *codeset, int master, int slave)
+{
+ TRACE_CALL(__func__);
+ if ( !remmina_masterthread_exec_is_main_thread() ) {
+ /* Allow the execution of this function from a non main thread */
+ RemminaMTExecData *d;
+ d = (RemminaMTExecData*)g_malloc( sizeof(RemminaMTExecData) );
+ d->func = FUNC_VTE_TERMINAL_SET_ENCODING_AND_PTY;
+ d->p.vte_terminal_set_encoding_and_pty.terminal = terminal;
+ d->p.vte_terminal_set_encoding_and_pty.codeset = codeset;
+ d->p.vte_terminal_set_encoding_and_pty.master = master;
+ remmina_masterthread_exec_and_wait(d);
+ g_free(d);
+ return;
+ }
+
+ setlocale(LC_ALL, "");
+ if (codeset && codeset[0] != '\0') {
+#if VTE_CHECK_VERSION(0, 38, 0)
+ vte_terminal_set_encoding(terminal, codeset, NULL);
+#else
+ vte_terminal_set_emulation(terminal, "xterm");
+ vte_terminal_set_encoding(terminal, codeset);
+#endif
+ }
+
+ vte_terminal_set_backspace_binding(terminal, VTE_ERASE_ASCII_DELETE);
+ vte_terminal_set_delete_binding(terminal, VTE_ERASE_DELETE_SEQUENCE);
+
+#if VTE_CHECK_VERSION(0, 38, 0)
+ /* vte_pty_new_foreig expect master FD, see https://bugzilla.gnome.org/show_bug.cgi?id=765382 */
+ vte_terminal_set_pty(terminal, vte_pty_new_foreign_sync(master, NULL, NULL));
+#else
+ vte_terminal_set_pty(terminal, master);
+#endif
+
+}
+
+static gboolean
+remmina_plugin_ssh_on_focus_in(GtkWidget *widget, GdkEventFocus *event, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginSshData *gpdata = GET_PLUGIN_DATA(gp);
+
+ gtk_widget_grab_focus(gpdata->vte);
+ return TRUE;
+}
+
+static gboolean
+remmina_plugin_ssh_on_size_allocate(GtkWidget *widget, GtkAllocation *alloc, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginSshData *gpdata = GET_PLUGIN_DATA(gp);
+ gint cols, rows;
+
+ if (!gtk_widget_get_mapped(widget)) return FALSE;
+
+ cols = vte_terminal_get_column_count(VTE_TERMINAL(widget));
+ rows = vte_terminal_get_row_count(VTE_TERMINAL(widget));
+
+ remmina_ssh_shell_set_size(gpdata->shell, cols, rows);
+
+ return FALSE;
+}
+
+static void
+remmina_plugin_ssh_set_vte_pref(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginSshData *gpdata = GET_PLUGIN_DATA(gp);
+
+ if (remmina_pref.vte_font && remmina_pref.vte_font[0]) {
+#if !VTE_CHECK_VERSION(0, 38, 0)
+ vte_terminal_set_font_from_string(VTE_TERMINAL(gpdata->vte), remmina_pref.vte_font);
+#else
+ vte_terminal_set_font(VTE_TERMINAL(gpdata->vte),
+ pango_font_description_from_string(remmina_pref.vte_font));
+#endif
+ }
+ vte_terminal_set_allow_bold(VTE_TERMINAL(gpdata->vte), remmina_pref.vte_allow_bold_text);
+ if (remmina_pref.vte_lines > 0) {
+ vte_terminal_set_scrollback_lines(VTE_TERMINAL(gpdata->vte), remmina_pref.vte_lines);
+ }
+}
+
+void
+remmina_plugin_ssh_vte_select_all(GtkMenuItem *menuitem, gpointer vte)
+{
+ TRACE_CALL(__func__);
+ vte_terminal_select_all(VTE_TERMINAL(vte));
+ /** @todo we should add the vte_terminal_unselect_all as well */
+}
+
+void
+remmina_plugin_ssh_vte_copy_clipboard(GtkMenuItem *menuitem, gpointer vte)
+{
+ TRACE_CALL(__func__);
+#if VTE_CHECK_VERSION(0, 50, 0)
+ vte_terminal_copy_clipboard_format(VTE_TERMINAL(vte), VTE_FORMAT_TEXT);
+#else
+ vte_terminal_copy_clipboard(VTE_TERMINAL(vte));
+#endif
+}
+
+void
+remmina_plugin_ssh_vte_paste_clipboard(GtkMenuItem *menuitem, gpointer vte)
+{
+ TRACE_CALL(__func__);
+ vte_terminal_paste_clipboard(VTE_TERMINAL(vte));
+}
+
+void
+remmina_plugin_ssh_vte_save_session(GtkMenuItem *menuitem, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginSshData *gpdata = GET_PLUGIN_DATA(gp);
+
+ GtkWidget* widget;
+ GError* err = NULL;
+
+ GFileOutputStream *stream = g_file_replace(gpdata->vte_session_file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &err);
+
+ if (err != NULL) {
+ widget = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+ _("%s"), err->message);
+ g_signal_connect(G_OBJECT(widget), "response", G_CALLBACK(gtk_widget_destroy), NULL);
+ gtk_widget_show(widget);
+ return;
+ }
+
+ if (stream != NULL)
+#if VTE_CHECK_VERSION(0, 38, 0)
+ vte_terminal_write_contents_sync(VTE_TERMINAL(gpdata->vte), G_OUTPUT_STREAM(stream),
+ VTE_WRITE_DEFAULT, NULL, &err);
+#else
+ vte_terminal_write_contents(VTE_TERMINAL(gpdata->vte), G_OUTPUT_STREAM(stream),
+ VTE_TERMINAL_WRITE_DEFAULT, NULL, &err);
+#endif
+
+ if (err == NULL) {
+ remmina_public_send_notification("remmina-terminal-saved",
+ _("Terminal content saved under"),
+ g_file_get_path(gpdata->vte_session_file));
+ }
+
+ g_object_unref(stream);
+ g_free(err);
+
+}
+
+/** Send a keystroke to the plugin window */
+static void remmina_ssh_keystroke(RemminaProtocolWidget *gp, const guint keystrokes[], const gint keylen)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginSshData *gpdata = GET_PLUGIN_DATA(gp);
+ remmina_plugin_service->protocol_plugin_send_keys_signals(gpdata->vte,
+ keystrokes, keylen, GDK_KEY_PRESS | GDK_KEY_RELEASE);
+ return;
+}
+
+gboolean
+remmina_ssh_plugin_popup_menu(GtkWidget *widget, GdkEvent *event, GtkWidget *menu)
+{
+
+ if ((event->type == GDK_BUTTON_PRESS) && (((GdkEventButton*)event)->button == 3)) {
+
+#if GTK_CHECK_VERSION(3, 22, 0)
+ gtk_menu_popup_at_pointer(GTK_MENU(menu), NULL);
+#else
+ gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
+ ((GdkEventButton*)event)->button, gtk_get_current_event_time());
+#endif
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * Remmina SSH plugin terminal popup menu.
+ *
+ * This is the context menu that popup when you right click in a terminal window.
+ * You can than select, copy, paste text and save the whole buffer to a file.
+ * Each menu entry call back the following functions:
+ * - remmina_plugin_ssh_vte_select_all()
+ * - remmina_plugin_ssh_vte_copy_clipboard()
+ * - remmina_plugin_ssh_vte_paste_clipboard()
+ * - remmina_plugin_ssh_vte_save_session()
+ * .
+ *
+ */
+void remmina_plugin_ssh_popup_ui(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginSshData *gpdata = GET_PLUGIN_DATA(gp);
+ /* Context menu for slection and clipboard */
+ GtkWidget *menu = gtk_menu_new();
+
+ GtkWidget *select_all = gtk_menu_item_new_with_label(_("Select All (Host+a)"));
+ GtkWidget *copy = gtk_menu_item_new_with_label(_("Copy (Host+c)"));
+ GtkWidget *paste = gtk_menu_item_new_with_label(_("Paste (Host+v)"));
+ GtkWidget *save = gtk_menu_item_new_with_label(_("Save session to file"));
+
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), select_all);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), copy);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), paste);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), save);
+
+ g_signal_connect(G_OBJECT(gpdata->vte), "button_press_event",
+ G_CALLBACK(remmina_ssh_plugin_popup_menu), menu);
+
+ g_signal_connect(G_OBJECT(select_all), "activate",
+ G_CALLBACK(remmina_plugin_ssh_vte_select_all), gpdata->vte);
+ g_signal_connect(G_OBJECT(copy), "activate",
+ G_CALLBACK(remmina_plugin_ssh_vte_copy_clipboard), gpdata->vte);
+ g_signal_connect(G_OBJECT(paste), "activate",
+ G_CALLBACK(remmina_plugin_ssh_vte_paste_clipboard), gpdata->vte);
+ g_signal_connect(G_OBJECT(save), "activate",
+ G_CALLBACK(remmina_plugin_ssh_vte_save_session), gp);
+
+ gtk_widget_show_all(menu);
+}
+
+/**
+ * Remmina SSH plugin initialization.
+ *
+ * This is the main function used to create the widget that will be embedded in the
+ * Remmina Connection Window.
+ * Initialize the terminal colours based on the user, everything is needed for the
+ * terminal window, the terminal session logging and the terminal popup menu.
+ *
+ * @see remmina_plugin_ssh_popup_ui
+ * @see RemminaProtocolWidget
+ * @see https://github.com/FreeRDP/Remmina/wiki/Remmina-SSH-Terminal-colour-schemes
+ */
+static void
+remmina_plugin_ssh_init(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginSshData *gpdata;
+ RemminaFile *remminafile;
+ GtkWidget *hbox;
+ GtkAdjustment *vadjustment;
+ GtkWidget *vscrollbar;
+ GtkWidget *vte;
+ GdkRGBA foreground_color;
+ GdkRGBA background_color;
+
+
+#if !VTE_CHECK_VERSION(0, 38, 0)
+ GdkColor foreground_gdkcolor;
+ GdkColor background_gdkcolor;
+#endif /* VTE_CHECK_VERSION(0,38,0) */
+
+ gpdata = g_new0(RemminaPluginSshData, 1);
+ g_object_set_data_full(G_OBJECT(gp), "plugin-data", gpdata, g_free);
+
+ hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_widget_show(hbox);
+ gtk_container_add(GTK_CONTAINER(gp), hbox);
+ g_signal_connect(G_OBJECT(hbox), "focus-in-event", G_CALLBACK(remmina_plugin_ssh_on_focus_in), gp);
+
+ vte = vte_terminal_new();
+ gtk_widget_show(vte);
+ vte_terminal_set_size(VTE_TERMINAL(vte), 80, 25);
+ vte_terminal_set_scroll_on_keystroke(VTE_TERMINAL(vte), TRUE);
+#if !VTE_CHECK_VERSION(0, 38, 0)
+ gdk_rgba_parse(&foreground_color, remmina_pref.foreground);
+ gdk_rgba_parse(&background_color, remmina_pref.background);
+#endif
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+#if VTE_CHECK_VERSION(0, 38, 0)
+ GdkRGBA cp[PALETTE_SIZE];
+ GdkRGBA cursor_color;
+ /* Set colors to GdkRGBA */
+ switch (remmina_plugin_service->file_get_int(remminafile, "ssh_color_scheme", FALSE)) {
+ case GRUVBOX:
+ gdk_rgba_parse(&foreground_color, "#ebdbb2");
+ gdk_rgba_parse(&background_color, "#282828");
+ gdk_rgba_parse(&cursor_color, "#d3869b");
+ remminavte.palette = gruvbox_palette;
+ break;
+ case TANGO:
+ gdk_rgba_parse(&foreground_color, "#eeeeec");
+ gdk_rgba_parse(&background_color, "#2e3436");
+ gdk_rgba_parse(&cursor_color, "#8ae234");
+ remminavte.palette = tango_palette;
+ break;
+ case LINUX:
+ gdk_rgba_parse(&foreground_color, "#ffffff");
+ gdk_rgba_parse(&background_color, "#000000");
+ gdk_rgba_parse(&cursor_color, "#ffffff");
+ remminavte.palette = linux_palette;
+ break;
+ case SOLARIZED_DARK:
+ gdk_rgba_parse(&foreground_color, "#839496");
+ gdk_rgba_parse(&background_color, "#002b36");
+ gdk_rgba_parse(&cursor_color, "#93a1a1");
+ remminavte.palette = solarized_dark_palette;
+ break;
+ case SOLARIZED_LIGHT:
+ gdk_rgba_parse(&foreground_color, "#657b83");
+ gdk_rgba_parse(&background_color, "#fdf6e3");
+ gdk_rgba_parse(&cursor_color, "#586e75");
+ remminavte.palette = solarized_light_palette;
+ break;
+ case XTERM:
+ gdk_rgba_parse(&foreground_color, "#000000");
+ gdk_rgba_parse(&background_color, "#ffffff");
+ gdk_rgba_parse(&cursor_color, "#000000");
+ remminavte.palette = xterm_palette;
+ break;
+ case CUSTOM:
+ gdk_rgba_parse(&foreground_color, remmina_pref.foreground);
+ gdk_rgba_parse(&background_color, remmina_pref.background);
+ gdk_rgba_parse(&cursor_color, remmina_pref.cursor);
+
+ gdk_rgba_parse(&cp[0], remmina_pref.color0);
+ gdk_rgba_parse(&cp[1], remmina_pref.color1);
+ gdk_rgba_parse(&cp[2], remmina_pref.color2);
+ gdk_rgba_parse(&cp[3], remmina_pref.color3);
+ gdk_rgba_parse(&cp[4], remmina_pref.color4);
+ gdk_rgba_parse(&cp[5], remmina_pref.color5);
+ gdk_rgba_parse(&cp[6], remmina_pref.color6);
+ gdk_rgba_parse(&cp[7], remmina_pref.color7);
+ gdk_rgba_parse(&cp[8], remmina_pref.color8);
+ gdk_rgba_parse(&cp[9], remmina_pref.color9);
+ gdk_rgba_parse(&cp[10], remmina_pref.color10);
+ gdk_rgba_parse(&cp[11], remmina_pref.color11);
+ gdk_rgba_parse(&cp[12], remmina_pref.color12);
+ gdk_rgba_parse(&cp[13], remmina_pref.color13);
+ gdk_rgba_parse(&cp[14], remmina_pref.color14);
+ gdk_rgba_parse(&cp[15], remmina_pref.color15);
+
+ const GdkRGBA custom_palette[PALETTE_SIZE] = {
+ cp[0], cp[1], cp[2], cp[3],
+ cp[4], cp[5], cp[6], cp[7],
+ cp[8], cp[9], cp[10], cp[11],
+ cp[12], cp[13], cp[14], cp[15]
+ };
+
+ remminavte.palette = custom_palette;
+ break;
+ default:
+ remminavte.palette = linux_palette;
+ break;
+ }
+ vte_terminal_set_colors(VTE_TERMINAL(vte), &foreground_color, &background_color, remminavte.palette, PALETTE_SIZE);
+ vte_terminal_set_color_foreground(VTE_TERMINAL(vte), &foreground_color);
+ vte_terminal_set_color_background(VTE_TERMINAL(vte), &background_color);
+ vte_terminal_set_color_cursor(VTE_TERMINAL(vte), &cursor_color);
+#else
+ /* VTE <= 2.90 doesn't support GdkRGBA so we must convert GdkRGBA to GdkColor */
+ foreground_gdkcolor.red = (guint16)(foreground_color.red * 0xFFFF);
+ foreground_gdkcolor.green = (guint16)(foreground_color.green * 0xFFFF);
+ foreground_gdkcolor.blue = (guint16)(foreground_color.blue * 0xFFFF);
+ background_gdkcolor.red = (guint16)(background_color.red * 0xFFFF);
+ background_gdkcolor.green = (guint16)(background_color.green * 0xFFFF);
+ background_gdkcolor.blue = (guint16)(background_color.blue * 0xFFFF);
+ /* Set colors to GdkColor */
+ vte_terminal_set_colors(VTE_TERMINAL(vte), &foreground_gdkcolor, &background_gdkcolor, NULL, 0);
+#endif
+
+ gtk_box_pack_start(GTK_BOX(hbox), vte, TRUE, TRUE, 0);
+ gpdata->vte = vte;
+ remmina_plugin_ssh_set_vte_pref(gp);
+ g_signal_connect(G_OBJECT(vte), "size-allocate", G_CALLBACK(remmina_plugin_ssh_on_size_allocate), gp);
+
+ remmina_plugin_ssh_on_size_allocate(GTK_WIDGET(vte), NULL, gp);
+
+ remmina_plugin_service->protocol_plugin_register_hostkey(gp, vte);
+
+#if VTE_CHECK_VERSION(0, 28, 0)
+ vadjustment = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(vte));
+#else
+ vadjustment = vte_terminal_get_adjustment(VTE_TERMINAL(vc->vte.terminal));
+#endif
+
+ vscrollbar = gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, vadjustment);
+
+ gtk_widget_show(vscrollbar);
+ gtk_box_pack_start(GTK_BOX(hbox), vscrollbar, FALSE, TRUE, 0);
+
+ const gchar *dir;
+ const gchar *sshlogname;
+ const gchar *fp;
+ GFile *rf;
+
+ rf = g_file_new_for_path(remminafile->filename);
+
+ if (remmina_plugin_service->file_get_string(remminafile, "sshlogfolder") == NULL) {
+ dir = g_build_path( "/", g_get_user_cache_dir(), g_get_prgname(), NULL);
+ }else {
+ dir = remmina_plugin_service->file_get_string(remminafile, "sshlogfolder");
+ }
+
+ if (remmina_plugin_service->file_get_string(remminafile, "sshlogname") == NULL) {
+ sshlogname = g_strconcat(g_file_get_basename(rf), ".", "log", NULL);
+ }else {
+ sshlogname = remmina_plugin_service->file_get_string(remminafile, "sshlogname");
+ }
+
+ fp = g_strconcat(dir, "/", sshlogname, NULL);
+ gpdata->vte_session_file = g_file_new_for_path(fp);
+
+ remmina_plugin_ssh_popup_ui(gp);
+}
+
+/**
+ * Initialize the the main window properties and the pthread.
+ *
+ * The call of this function is a requirement of remmina_protocol_widget_open_connection_real().
+ * @return TRUE
+ * @return FALSE and remmina_protocol_widget_open_connection_real() will fails.
+ */
+static gboolean
+remmina_plugin_ssh_open_connection(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginSshData *gpdata = GET_PLUGIN_DATA(gp);
+
+ remmina_plugin_service->protocol_plugin_set_expand(gp, TRUE);
+ remmina_plugin_service->protocol_plugin_set_width(gp, 640);
+ remmina_plugin_service->protocol_plugin_set_height(gp, 480);
+
+ if (pthread_create(&gpdata->thread, NULL, remmina_plugin_ssh_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;
+ }
+ return TRUE;
+}
+
+static gboolean
+remmina_plugin_ssh_close_connection(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginSshData *gpdata = GET_PLUGIN_DATA(gp);
+
+ RemminaFile *remminafile;
+
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ if (remmina_file_get_int(remminafile, "sshlogenabled", FALSE)) {
+ remmina_plugin_ssh_vte_save_session(NULL, gp);
+ }
+ if (gpdata->thread) {
+ pthread_cancel(gpdata->thread);
+ if (gpdata->thread) pthread_join(gpdata->thread, NULL);
+ }
+ if (gpdata->shell) {
+ remmina_ssh_shell_free(gpdata->shell);
+ gpdata->shell = NULL;
+ }
+
+ remmina_plugin_service->protocol_plugin_emit_signal(gp, "disconnect");
+ return FALSE;
+}
+
+/**
+ * Not used by the the plugin.
+ *
+ * @return Always TRUE
+ */
+static gboolean
+remmina_plugin_ssh_query_feature(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
+{
+ TRACE_CALL(__func__);
+ return TRUE;
+}
+
+/**
+ * Functions to call when an entry in the Tool menu in the Remmina Connection Window is clicked.
+ *
+ * In the Remmina Connection Window toolbar, there is a tool menu, this function is used to
+ * call the right function for each entry with its parameters.
+ *
+ * At the moment it's possible to:
+ * - Open a new SSH session.
+ * - Open an SFTP session.
+ * - Select, copy and paste text.
+ * - Send recorded Key Strokes.
+ * .
+ *
+ * @return the return value of the calling function.
+ *
+ */
+static void
+remmina_plugin_ssh_call_feature(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginSshData *gpdata = GET_PLUGIN_DATA(gp);
+
+ switch (feature->id) {
+ case REMMINA_PROTOCOL_FEATURE_TOOL_SSH:
+ remmina_plugin_service->open_connection(
+ remmina_file_dup_temp_protocol(remmina_plugin_service->protocol_plugin_get_file(gp), "SSH"),
+ NULL, gpdata->shell, NULL);
+ return;
+ case REMMINA_PROTOCOL_FEATURE_TOOL_SFTP:
+ remmina_plugin_service->open_connection(
+ /** @todo start the direct tunnel here */
+ remmina_file_dup_temp_protocol(remmina_plugin_service->protocol_plugin_get_file(gp), "SFTP"),
+ NULL, gpdata->shell, NULL);
+ return;
+ case REMMINA_PLUGIN_SSH_FEATURE_TOOL_COPY:
+#if VTE_CHECK_VERSION(0, 50, 0)
+ vte_terminal_copy_clipboard_format(VTE_TERMINAL(gpdata->vte), VTE_FORMAT_TEXT);
+#else
+ vte_terminal_copy_clipboard(VTE_TERMINAL(gpdata->vte));
+#endif
+ return;
+ case REMMINA_PLUGIN_SSH_FEATURE_TOOL_PASTE:
+ vte_terminal_paste_clipboard(VTE_TERMINAL(gpdata->vte));
+ return;
+ case REMMINA_PLUGIN_SSH_FEATURE_TOOL_SELECT_ALL:
+ vte_terminal_select_all(VTE_TERMINAL(gpdata->vte));
+ return;
+ }
+}
+
+/** Array of key/value pairs for ssh auth type*/
+static gpointer ssh_auth[] =
+{
+ "0", N_("Password"),
+ "1", N_("SSH identity file"),
+ "2", N_("SSH agent"),
+ "3", N_("Public key (automatic)"),
+ "4", N_("Kerberos (GSSAPI)"),
+ NULL
+};
+
+/** Charset list */
+static gpointer ssh_charset_list[] =
+{
+ "", "",
+ "", "ASCII",
+ "", "BIG5",
+ "", "CP437",
+ "", "CP720",
+ "", "CP737",
+ "", "CP775",
+ "", "CP850",
+ "", "CP852",
+ "", "CP855",
+ "", "CP857",
+ "", "CP858",
+ "", "CP862",
+ "", "CP866",
+ "", "CP874",
+ "", "CP1125",
+ "", "CP1250",
+ "", "CP1251",
+ "", "CP1252",
+ "", "CP1253",
+ "", "CP1254",
+ "", "CP1255",
+ "", "CP1256",
+ "", "CP1257",
+ "", "CP1258",
+ "", "EUC-JP",
+ "", "EUC-KR",
+ "", "GBK",
+ "", "ISO-8859-1",
+ "", "ISO-8859-2",
+ "", "ISO-8859-3",
+ "", "ISO-8859-4",
+ "", "ISO-8859-5",
+ "", "ISO-8859-6",
+ "", "ISO-8859-7",
+ "", "ISO-8859-8",
+ "", "ISO-8859-9",
+ "", "ISO-8859-10",
+ "", "ISO-8859-11",
+ "", "ISO-8859-12",
+ "", "ISO-8859-13",
+ "", "ISO-8859-14",
+ "", "ISO-8859-15",
+ "", "ISO-8859-16",
+ "", "KOI8-R",
+ "", "SJIS",
+ "", "UTF-8",
+ NULL
+};
+
+static gpointer ssh_terminal_palette[] =
+{
+ "0", "Linux",
+ "1", "Tango",
+ "2", "Gruvbox",
+ "3", "Solarized Dark",
+ "4", "Solarized Light",
+ "5", "XTerm",
+ "6", "Custom (Configured in Remmina preferences)",
+ NULL
+};
+
+/**
+ * Array for available features.
+ * The last element of the array must be REMMINA_PROTOCOL_FEATURE_TYPE_END.
+ */
+static RemminaProtocolFeature remmina_plugin_ssh_features[] =
+{
+ { REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_PLUGIN_SSH_FEATURE_TOOL_COPY, N_("Copy"), N_("_Copy"), NULL },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_PLUGIN_SSH_FEATURE_TOOL_PASTE, N_("Paste"), N_("_Paste"), NULL },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_PLUGIN_SSH_FEATURE_TOOL_SELECT_ALL, N_("Select all"), N_("_Select all"), NULL },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_END, 0, NULL, NULL, NULL }
+};
+
+/**
+ * Array of RemminaProtocolSetting for basic settings.
+ * - Each item is composed by:
+ * 1. RemminaProtocolSettingType for setting type.
+ * 2. Setting name.
+ * 3. Setting description.
+ * 4. Compact disposition.
+ * 5. Values for REMMINA_PROTOCOL_SETTING_TYPE_SELECT or REMMINA_PROTOCOL_SETTING_TYPE_COMBO.
+ * 6. Unused pointer.
+ * .
+ */
+static const RemminaProtocolSetting remmina_ssh_basic_settings[] =
+{
+ { REMMINA_PROTOCOL_SETTING_TYPE_SERVER, "ssh_server", NULL, FALSE, "_ssh._tcp", NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "ssh_username", N_("User name"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_PASSWORD, "ssh_password", N_("User password"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "ssh_auth", N_("Authentication type"), FALSE, ssh_auth, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_FILE, "ssh_privatekey", N_("Identity file"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_PASSWORD, "ssh_passphrase", N_("Private key passphrase"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "exec", N_("Startup program"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_END, NULL, NULL, FALSE, NULL, NULL }
+};
+
+/**
+ * Array of RemminaProtocolSetting for advanced settings.
+ * - Each item is composed by:
+ * 1. RemminaProtocolSettingType for setting type.
+ * 2. Setting name.
+ * 3. Setting description.
+ * 4. Compact disposition.
+ * 5. Values for REMMINA_PROTOCOL_SETTING_TYPE_SELECT or REMMINA_PROTOCOL_SETTING_TYPE_COMBO.
+ * 6. Unused pointer.
+ *
+ */
+static const RemminaProtocolSetting remmina_ssh_advanced_settings[] =
+{
+ { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "ssh_color_scheme", N_("Terminal color scheme"), FALSE, ssh_terminal_palette, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "ssh_charset", N_("Character set"), FALSE, ssh_charset_list, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "ssh_proxycommand", N_("SSH Proxy Command"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "ssh_kex_algorithms", N_("KEX (Key Exchange) algorithms"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "ssh_ciphers", N_("Symmetric cipher client to server"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "ssh_hostkeytypes", N_("Preferred server host key types"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_FOLDER, "sshlogfolder", N_("SSH session log folder"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "sshlogname", N_("SSH session log file name"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "sshlogenabled", N_("Enable SSH session logging at exit"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "ssh_compression", N_("Enable SSH compression"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disablepasswordstoring", N_("Disable password storing"), TRUE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "ssh_stricthostkeycheck", N_("Strict host key checking"), TRUE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_END, NULL, NULL, FALSE, NULL, NULL }
+};
+
+/**
+ * SSH Protocol plugin definition and features.
+ *
+ * Array used to define the SSH Protocol plugin Type, name, description, version
+ * Plugin icon, features, initialization and closing functions.
+ */
+static RemminaProtocolPlugin remmina_plugin_ssh =
+{
+ REMMINA_PLUGIN_TYPE_PROTOCOL, /**< Type */
+ "SSH", /**< Name */
+ N_("SSH - Secure Shell"), /**< Description */
+ GETTEXT_PACKAGE, /**< Translation domain */
+ VERSION, /**< Version number */
+ "utilities-terminal", /**< Icon for normal connection */
+ "utilities-terminal", /**< Icon for SSH connection */
+ remmina_ssh_basic_settings, /**< Array for basic settings */
+ remmina_ssh_advanced_settings, /**< Array for advanced settings */
+ REMMINA_PROTOCOL_SSH_SETTING_TUNNEL, /**< SSH settings type */
+ remmina_plugin_ssh_features, /**< Array for available features */
+ remmina_plugin_ssh_init, /**< Plugin initialization */
+ remmina_plugin_ssh_open_connection, /**< Plugin open connection */
+ remmina_plugin_ssh_close_connection, /**< Plugin close connection */
+ remmina_plugin_ssh_query_feature, /**< Query for available features */
+ remmina_plugin_ssh_call_feature, /**< Call a feature */
+ remmina_ssh_keystroke /**< Send a keystroke */
+};
+
+void
+remmina_ssh_plugin_register(void)
+{
+ TRACE_CALL(__func__);
+ remmina_plugin_ssh_features[0].opt3 = GUINT_TO_POINTER(remmina_pref.vte_shortcutkey_copy);
+ remmina_plugin_ssh_features[1].opt3 = GUINT_TO_POINTER(remmina_pref.vte_shortcutkey_paste);
+ remmina_plugin_ssh_features[2].opt3 = GUINT_TO_POINTER(remmina_pref.vte_shortcutkey_select_all);
+ remmina_plugin_ssh_features[3].opt3 = GUINT_TO_POINTER(remmina_pref.vte_shortcutkey_select_all);
+
+ remmina_plugin_service = &remmina_plugin_manager_service;
+ remmina_plugin_service->register_plugin((RemminaPlugin*)&remmina_plugin_ssh);
+
+ ssh_threads_set_callbacks(ssh_threads_get_pthread());
+ ssh_init();
+}
+
+#else
+
+void remmina_ssh_plugin_register(void)
+{
+ TRACE_CALL(__func__);
+}
+
+#endif
+
diff --git a/src/remmina_ssh_plugin.h b/src/remmina_ssh_plugin.h
new file mode 100644
index 000000000..fe6351e38
--- /dev/null
+++ b/src/remmina_ssh_plugin.h
@@ -0,0 +1,58 @@
+/*
+ * 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-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.
+ *
+ */
+
+#pragma once
+
+#ifdef HAVE_LIBVTE
+#include <vte/vte.h>
+#endif
+
+G_BEGIN_DECLS
+
+void remmina_ssh_plugin_register(void);
+
+/* For callback in main thread */
+#if defined (HAVE_LIBSSH) && defined (HAVE_LIBVTE)
+void remmina_plugin_ssh_vte_terminal_set_encoding_and_pty(VteTerminal *terminal, const char *codeset, int master, int slave);
+void remmina_plugin_ssh_vte_select_all(GtkMenuItem *menuitem, gpointer user_data);
+void remmina_plugin_ssh_vte_copy_clipboard(GtkMenuItem *menuitem, gpointer user_data);
+void remmina_plugin_ssh_vte_paste_clipboard(GtkMenuItem *menuitem, gpointer user_data);
+gboolean remmina_ssh_plugin_popup_menu(GtkWidget *widget, GdkEvent *event, GtkWidget *menu);
+#endif
+
+G_END_DECLS
+
+
diff --git a/src/remmina_stats.c b/src/remmina_stats.c
new file mode 100644
index 000000000..cfa19580e
--- /dev/null
+++ b/src/remmina_stats.c
@@ -0,0 +1,777 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * 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.
+ *
+ */
+
+/**
+ * @file remmina_stats.c
+ * @brief Remmina usage statistics module.
+ * @author Antenore Gatta and Giovanni Panozzo
+ * @date 12 Feb 2018
+ *
+ * When Remmina starts asks the user if she/he wants to share some usage statistics
+ * with the Remmina developers. As per the opt-in model
+ * (https://en.wikipedia.org/wiki/Opt-in_email), without the consent of the user,
+ * none of these data will be collected.
+ * Additionally a user can asks, at any moment, that any data linked to his/her
+ * profiles to be deleted, and he/she can change the Remmina settings to stop
+ * collecting and sharing usage statistics.
+ *
+ * All the data are encrypted at client side using RSA, through the OpenSSL
+ * libraries, and decrypted offline to maximize security.
+ *
+ * The following example show which kind of data are collected.
+ *
+ * @code
+ * {
+ *
+ * "UID": "P0M20TXN03DWF4-9a1e6da2ad"
+ * "REMMINAVERSION": {
+ * "version": "1.2.0-rcgit-26"
+ * "git_revision": "9c5c4805"
+ * "snap_build": 0
+ * }
+ * "SYSTEM": {
+ * "kernel_name": "Linux"
+ * "kernel_release": "4.14.11-200.fc26.x86_64"
+ * "kernel_arch": "x86_64"
+ * "lsb_distributor": "Fedora"
+ * "lsb_distro_description": "Fedora release 26 (Twenty Six)"
+ * "lsb_distro_release": "26"
+ * "lsb_distro_codename": "TwentySix"
+ * "etc_release": "Fedora release 26 (Twenty Six)"
+ * }
+ * "GTKVERSION": {
+ * "major": 3
+ * "minor": 22
+ * "micro": 21
+ * }
+ * "GTKBACKEND": "X11"
+ * "WINDOWMANAGER": {
+ * "window_manager": "GNOME i3-gnome"
+ * }
+ * "APPINDICATOR": {
+ * "appindicator_supported": 0
+ * "icon_is_active": 1
+ * "appindicator_type": "AppIndicator on GtkStatusIcon/xembed"
+ * }
+ * "PROFILES": {
+ * "profile_count": 457
+ * "SSH": 431
+ * "NX": 1
+ * "RDP": 7
+ * "TERMINAL": 2
+ * "X2GO": 5
+ * "SFTP": 4
+ * "PYTHON_SIMPLE": 4
+ * "SPICE": 3
+ * "DATE_SSH": "20180209"
+ * "DATE_NX": ""
+ * "DATE_RDP": "20180208"
+ * "DATE_TERMINAL": ""
+ * "DATE_X2GO": ""
+ * "DATE_SFTP": ""
+ * "DATE_PYTHON_SIMPLE": ""
+ * "DATE_SPICE": ""
+ * }
+ *
+ * }
+ * @endcode
+ *
+ * All of these data are solely transmitted to understand:
+ * - On which type of system Remmina is used
+ * - Operating System
+ * - Architecture (32/64bit)
+ * - Linux distributor or OS vendor
+ * - Desktop Environment type.
+ * - Main library versions installed on the system in use by Remmina.
+ * - Protocols used
+ * - Last time each protocol has been used (globally).
+ *
+ * @see http://www.remmina.org/wp for more information.
+ */
+
+
+#include "config.h"
+#include <string.h>
+#include <sys/utsname.h>
+#include <unistd.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
+
+#include "remmina_file.h"
+#include "remmina_file_manager.h"
+#include "remmina_icon.h"
+#include "remmina_log.h"
+#include "remmina_pref.h"
+#include "remmina_sysinfo.h"
+#include "remmina_utils.h"
+#include "remmina/remmina_trace_calls.h"
+
+#ifdef GDK_WINDOWING_WAYLAND
+ #include <gdk/gdkwayland.h>
+#endif
+#ifdef GDK_WINDOWING_X11
+ #include <gdk/gdkx.h>
+#endif
+#include "remmina_stats.h"
+
+struct utsname u;
+
+struct ProfilesData {
+ GHashTable *proto_count;
+ GHashTable *proto_date;
+ const gchar *protocol; /** Key in the proto_count hash table.*/
+ const gchar *pdatestr; /** Date in string format in the proto_date hash table. */
+ gint pcount;
+ gchar datestr;
+};
+
+static gchar* remmina_stats_gen_random_uuid_prefix()
+{
+ TRACE_CALL(__func__);
+ GRand *rand;
+ GTimeVal t;
+ gchar *result;
+ int i;
+ static char alpha[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+
+ result = g_malloc0(15);
+
+ g_get_current_time(&t);
+ rand = g_rand_new_with_seed((guint32)t.tv_sec ^ (guint32)t.tv_usec);
+
+ for (i = 0; i < 7; i++) {
+ result[i] = alpha[g_rand_int_range(rand, 0, sizeof(alpha) - 1)];
+ }
+
+ g_rand_set_seed(rand, (guint32)t.tv_usec);
+ for (i = 0; i < 7; i++) {
+ result[i + 7] = alpha[g_rand_int_range(rand, 0, sizeof(alpha) - 1)];
+ }
+ g_rand_free(rand);
+
+ return result;
+}
+
+JsonNode *remmina_stats_get_uid()
+{
+ TRACE_CALL(__func__);
+ JsonNode *r;
+ GChecksum *chs;
+ const gchar *uname, *hname;
+ const gchar *uid_suffix;
+ gchar *uid_prefix;
+ gchar *uid;
+
+ /** @warning this function is usually executed on a dedicated thread,
+ * not on the main thread
+ */
+
+ if (remmina_pref.periodic_usage_stats_uuid_prefix == NULL || remmina_pref.periodic_usage_stats_uuid_prefix[0] == 0) {
+ /* Generate a new UUID_PREFIX for this installation */
+ uid_prefix = remmina_stats_gen_random_uuid_prefix();
+ if (remmina_pref.periodic_usage_stats_uuid_prefix)
+ g_free(remmina_pref.periodic_usage_stats_uuid_prefix);
+ remmina_pref.periodic_usage_stats_uuid_prefix = uid_prefix;
+ remmina_pref_save();
+ }
+
+ uname = g_get_user_name();
+ hname = g_get_host_name();
+ chs = g_checksum_new(G_CHECKSUM_SHA256);
+ g_checksum_update(chs, (const guchar*)uname, strlen(uname));
+ g_checksum_update(chs, (const guchar*)hname, strlen(hname));
+ uid_suffix = g_checksum_get_string(chs);
+
+ uid = g_strdup_printf("%s-%.10s", remmina_pref.periodic_usage_stats_uuid_prefix, uid_suffix);
+ g_checksum_free(chs);
+
+ r = json_node_alloc();
+ json_node_init_string(r, uid);
+
+ g_free(uid);
+
+ return r;
+
+}
+
+JsonNode *remmina_stats_get_os_info()
+{
+ TRACE_CALL(__func__);
+ JsonBuilder *b;
+ JsonNode *r;
+
+ gchar *kernel_name;
+ gchar *kernel_release;
+ gchar *kernel_arch;
+ gchar *id;
+ gchar *description;
+ GHashTable *etc_release;
+ gchar *release;
+ gchar *codename;
+ GHashTableIter iter;
+ gchar *key, *value;
+
+ /** @warning this function is usually executed on a dedicated thread,
+ * not on the main thread */
+
+ b = json_builder_new();
+ json_builder_begin_object(b);
+
+ json_builder_set_member_name(b, "kernel_name");
+ kernel_name = g_strdup_printf("%s", remmina_utils_get_kernel_name());
+ if (!kernel_name || kernel_name[0] == '\0') {
+ json_builder_add_null_value(b);
+ }else {
+ json_builder_add_string_value(b, kernel_name);
+ }
+ g_free(kernel_name);
+
+ json_builder_set_member_name(b, "kernel_release");
+ kernel_release = g_strdup_printf("%s", remmina_utils_get_kernel_release());
+ if (!kernel_release || kernel_release[0] == '\0') {
+ json_builder_add_null_value(b);
+ }else {
+ json_builder_add_string_value(b, kernel_release);
+ }
+ g_free(kernel_release);
+
+ json_builder_set_member_name(b, "kernel_arch");
+ kernel_arch = g_strdup_printf("%s", remmina_utils_get_kernel_arch());
+ if (!kernel_arch || kernel_arch[0] == '\0') {
+ json_builder_add_null_value(b);
+ }else {
+ json_builder_add_string_value(b, kernel_arch);
+ }
+ g_free(kernel_arch);
+
+ json_builder_set_member_name(b, "lsb_distributor");
+ id = remmina_utils_get_lsb_id();
+ if (!id || id[0] == '\0') {
+ json_builder_add_null_value(b);
+ }else {
+ json_builder_add_string_value(b, id);
+ }
+ g_free(id);
+
+ json_builder_set_member_name(b, "lsb_distro_description");
+ description = remmina_utils_get_lsb_description();
+ if (!description || description[0] == '\0') {
+ json_builder_add_null_value(b);
+ }else {
+ json_builder_add_string_value(b, description);
+ }
+ g_free(description);
+
+ json_builder_set_member_name(b, "lsb_distro_release");
+ release = remmina_utils_get_lsb_release();
+ if (!release || release[0] == '\0') {
+ json_builder_add_null_value(b);
+ }else {
+ json_builder_add_string_value(b, release);
+ }
+ g_free(release);
+
+ json_builder_set_member_name(b, "lsb_distro_codename");
+ codename = remmina_utils_get_lsb_codename();
+ if (!codename || codename[0] == '\0') {
+ json_builder_add_null_value(b);
+ }else {
+ json_builder_add_string_value(b, codename);
+ }
+ g_free(codename);
+
+ etc_release = remmina_utils_get_etc_release();
+ json_builder_set_member_name(b, "etc_release");
+ if (etc_release) {
+ json_builder_begin_object(b);
+ g_hash_table_iter_init (&iter, etc_release);
+ while (g_hash_table_iter_next (&iter, (gpointer)&key, (gpointer)&value)) {
+ json_builder_set_member_name(b, key);
+ json_builder_add_string_value(b, value);
+ }
+ json_builder_end_object(b);
+ g_hash_table_remove_all(etc_release);
+ g_hash_table_unref(etc_release);
+ }else {
+ json_builder_add_null_value(b);
+ }
+
+ /** @todo Add other means to identify a release name/description
+ * to cover as much OS as possible, like /etc/issue
+ */
+
+ json_builder_end_object(b);
+ r = json_builder_get_root(b);
+ g_object_unref(b);
+ return r;
+}
+
+JsonNode *remmina_stats_get_version()
+{
+ TRACE_CALL(__func__);
+ JsonBuilder *b;
+ JsonNode *r;
+ gchar *flatpak_info;
+
+ /** @warning this function is usually executed on a dedicated thread,
+ * not on the main thread */
+
+ b = json_builder_new();
+ json_builder_begin_object(b);
+ json_builder_set_member_name(b, "version");
+ json_builder_add_string_value(b, VERSION);
+ json_builder_set_member_name(b, "git_revision");
+ json_builder_add_string_value(b, REMMINA_GIT_REVISION);
+ json_builder_set_member_name(b, "snap_build");
+#ifdef SNAP_BUILD
+ json_builder_add_int_value(b, 1);
+#else
+ json_builder_add_int_value(b, 0);
+#endif
+
+ /**
+ * Detect if we are running under Flatpak
+ */
+ json_builder_set_member_name(b, "flatpak_build");
+ /* Flatpak sandbox should contain the file ${XDG_RUNTIME_DIR}/flatpak-info */
+ flatpak_info = g_build_filename(g_get_user_runtime_dir(), "flatpak-info", NULL);
+ if (g_file_test(flatpak_info, G_FILE_TEST_EXISTS)) {
+ json_builder_add_int_value(b, 1);
+ } else {
+ json_builder_add_int_value(b, 0);
+ }
+ g_free(flatpak_info);
+
+ json_builder_end_object(b);
+ r = json_builder_get_root(b);
+ g_object_unref(b);
+ return r;
+}
+
+JsonNode *remmina_stats_get_gtk_version()
+{
+ TRACE_CALL(__func__);
+ JsonBuilder *b;
+ JsonNode *r;
+
+ /** @warning this function is usually executed on a dedicated thread,
+ * not on the main thread
+ */
+
+ b = json_builder_new();
+ json_builder_begin_object(b);
+ json_builder_set_member_name(b, "major");
+ json_builder_add_int_value(b, gtk_get_major_version());
+ json_builder_set_member_name(b, "minor");
+ json_builder_add_int_value(b, gtk_get_minor_version());
+ json_builder_set_member_name(b, "micro");
+ json_builder_add_int_value(b, gtk_get_micro_version());
+ json_builder_end_object(b);
+ r = json_builder_get_root(b);
+ g_object_unref(b);
+ return r;
+
+}
+
+JsonNode *remmina_stats_get_gtk_backend()
+{
+ TRACE_CALL(__func__);
+ JsonNode *r;
+ GdkDisplay *disp;
+ gchar *bkend;
+
+ /** @warning this function is usually executed on a dedicated thread,
+ * not on the main thread
+ */
+
+ disp = gdk_display_get_default();
+
+#ifdef GDK_WINDOWING_WAYLAND
+ if (GDK_IS_WAYLAND_DISPLAY(disp)) {
+ bkend = "Wayland";
+ }else
+#endif
+#ifdef GDK_WINDOWING_X11
+ if (GDK_IS_X11_DISPLAY(disp)) {
+ bkend = "X11";
+ } else
+#endif
+ bkend = "n/a";
+
+ r = json_node_alloc();
+ json_node_init_string(r, bkend);
+
+ return r;
+
+}
+
+JsonNode *remmina_stats_get_wm_name()
+{
+ TRACE_CALL(__func__);
+ JsonBuilder *b;
+ JsonNode *r;
+ gchar *wmver;
+ gchar *wmname;
+
+ b = json_builder_new();
+ json_builder_begin_object(b);
+
+ json_builder_set_member_name(b, "window_manager");
+
+ /** We try to get the Gnome SHELL version */
+ wmver = remmina_sysinfo_get_gnome_shell_version();
+ if (!wmver || wmver[0] == '\0') {
+ remmina_log_print("Gnome Shell not found\n");
+ }else {
+ remmina_log_printf("Gnome Shell version: %s\n", wmver);
+ json_builder_add_string_value(b, "Gnome Shell");
+ json_builder_set_member_name(b, "gnome_shell_ver");
+ json_builder_add_string_value(b, wmver);
+ goto end;
+ }
+ g_free(wmver);
+
+ wmname = remmina_sysinfo_get_wm_name();
+ if (!wmname || wmname[0] == '\0') {
+ /** When everything else fails with set the WM name to NULL **/
+ remmina_log_print("Cannot determine the Window Manger name\n");
+ json_builder_add_string_value(b, "n/a");
+ }else {
+ remmina_log_printf("Window Manger names %s\n", wmname);
+ json_builder_add_string_value(b, wmname);
+ }
+ g_free(wmname);
+
+ end:
+ json_builder_end_object(b);
+ r = json_builder_get_root(b);
+ g_object_unref(b);
+ return r;
+}
+
+JsonNode *remmina_stats_get_indicator()
+{
+ TRACE_CALL(__func__);
+ JsonBuilder *b;
+ JsonNode *r;
+ gboolean sni; /** Support for StatusNotifier or AppIndicator */
+
+ b = json_builder_new();
+ json_builder_begin_object(b);
+
+ json_builder_set_member_name(b, "appindicator_supported");
+ sni = remmina_sysinfo_is_appindicator_available();
+ if (sni) {
+ /** StatusNotifier/Appindicator supported by desktop */
+ json_builder_add_int_value(b, 1);
+ json_builder_set_member_name(b, "appindicator_compiled");
+#ifdef HAVE_LIBAPPINDICATOR
+ /** libappindicator is compiled in remmina. */
+ json_builder_add_int_value(b, 1);
+#else
+ /** Remmina not compiled with -DWITH_APPINDICATOR=on */
+ json_builder_add_int_value(b, 0);
+#endif
+ } else {
+ /** StatusNotifier/Appindicator NOT supported by desktop */
+ json_builder_add_int_value(b, 0);
+ json_builder_set_member_name(b, "icon_is_active");
+ if (remmina_icon_is_available()) {
+ /** Remmina icon is active */
+ json_builder_add_int_value(b, 1);
+ json_builder_set_member_name(b, "appindicator_type");
+#ifdef HAVE_LIBAPPINDICATOR
+ /** libappindicator fallback to GtkStatusIcon/xembed"); */
+ json_builder_add_string_value(b, "AppIndicator on GtkStatusIcon/xembed");
+#else
+ /** Remmina fallback to GtkStatusIcon/xembed */
+ json_builder_add_string_value(b, "Remmina icon on GtkStatusIcon/xembed");
+#endif
+ }else {
+ /** Remmina icon is NOT active */
+ json_builder_add_int_value(b, 0);
+ }
+ }
+ json_builder_end_object(b);
+ r = json_builder_get_root(b);
+ g_object_unref(b);
+ return r;
+}
+
+/**
+ * Given a remmina file, fills a structure containing profiles keys/value tuples.
+ *
+ * This is used as a callback function with remmina_file_manager_iterate.
+ * @todo Move this in a separate file.
+ */
+static void remmina_profiles_get_data(RemminaFile *remminafile, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+
+ gint count = 0;
+ gpointer pcount, kpo;
+ gpointer pdate, kdo;
+ gchar *sday, *smonth, *syear;
+ gchar *dday, *dmonth, *dyear;
+
+ GDateTime *ds; /** Source date -> from profile */
+ GDateTime *dd; /** Destination date -> The date in the pdata structure */
+
+ struct ProfilesData* pdata;
+ pdata = (struct ProfilesData*)user_data;
+
+ pdata->protocol = remmina_file_get_string(remminafile, "protocol");
+ pdata->pdatestr = remmina_file_get_string(remminafile, "last_success");
+
+ ds = dd = NULL;
+ if (pdata->pdatestr && pdata->pdatestr[0] != '\0' && strlen(pdata->pdatestr) >= 6) {
+ dyear = g_strdup_printf("%.4s", pdata->pdatestr);
+ dmonth = g_strdup_printf("%.2s", pdata->pdatestr + 4);
+ dday = g_strdup_printf("%.2s", pdata->pdatestr + 6);
+ dd = g_date_time_new_local(g_ascii_strtoll(dyear, NULL, 0),
+ g_ascii_strtoll(dmonth, NULL, 0),
+ g_ascii_strtoll(dday, NULL, 0), 0, 0, 0.0);
+ g_free(dyear);
+ g_free(dmonth);
+ g_free(dday);
+ }
+
+
+ if (pdata->protocol && pdata->protocol[0] != '\0') {
+ if (g_hash_table_lookup_extended(pdata->proto_count, pdata->protocol, &kpo, &pcount)) {
+ count = GPOINTER_TO_INT(pcount) + 1;
+ }else {
+ count = 1;
+ g_hash_table_insert(pdata->proto_count, g_strdup(pdata->protocol), GINT_TO_POINTER(count));
+ }
+ g_hash_table_replace(pdata->proto_count, g_strdup(pdata->protocol), GINT_TO_POINTER(count));
+ pdate = NULL;
+ if (g_hash_table_lookup_extended(pdata->proto_date, pdata->protocol, &kdo, &pdate)) {
+
+ ds = NULL;
+ if (pdate && strlen(pdate) >= 6) {
+ syear = g_strdup_printf("%.4s", (char*)pdate);
+ smonth = g_strdup_printf("%.2s", (char*)pdate + 4);
+ sday = g_strdup_printf("%.2s", (char*)pdate + 6);
+ ds = g_date_time_new_local(g_ascii_strtoll(syear, NULL, 0),
+ g_ascii_strtoll(smonth, NULL, 0),
+ g_ascii_strtoll(sday, NULL, 0), 0, 0, 0.0);
+ g_free(syear);
+ g_free(smonth);
+ g_free(sday);
+ }
+
+ /** When both date in the has and in the profile are valid we compare the date */
+ if (ds && dd) {
+ gint res = g_date_time_compare( ds, dd );
+ /** If the date in the hash less than the date in the profile, we take the latter */
+ if (res < 0 ) {
+ //remmina_log_printf("Date %s is newer than the one inside pdata->protocol for protocol %s\n", g_strdup(pdata->pdatestr), g_strdup(pdata->protocol));
+ g_hash_table_replace(pdata->proto_date, g_strdup(pdata->protocol), g_strdup(pdata->pdatestr));
+ }
+ }
+ /** If the date in the hash is NOT valid and the date in the profile is valid we keep the latter */
+ if (!ds && dd) {
+ //remmina_log_printf("Date %s inserted in pdata->protocol for protocol %s\n", g_strdup(pdata->pdatestr), g_strdup(pdata->protocol));
+ g_hash_table_replace(pdata->proto_date, g_strdup(pdata->protocol), g_strdup(pdata->pdatestr));
+ }
+ /** If both date are NULL, we insert NULL for that protocol */
+ if ((!ds && !dd) && pdata->pdatestr) {
+ //remmina_log_printf("Date NULL inserted in pdata->protocol for protocol %s\n", g_strdup(pdata->protocol));
+ g_hash_table_replace(pdata->proto_date, g_strdup(pdata->protocol), NULL);
+ }
+ }else {
+ /** If there is not the protocol in the hash, we add it */
+ /** If the date in the profile is not NULL we use it */
+ if (pdata->pdatestr) {
+ //remmina_log_printf("Date %s inserted in pdata->protocol for protocol %s\n", g_strdup(pdata->pdatestr), g_strdup(pdata->protocol));
+ g_hash_table_replace(pdata->proto_date, g_strdup(pdata->protocol), g_strdup(pdata->pdatestr));
+ }else {
+ /** Otherwise we set it to NULL */
+ //remmina_log_printf("We set %s protocol date to NULL\n", g_strdup(pdata->protocol));
+ g_hash_table_replace(pdata->proto_date, g_strdup(pdata->protocol), NULL);
+ }
+ }
+ }
+ if (dd)
+ g_date_time_unref(dd);
+ if (ds)
+ g_date_time_unref(ds);
+}
+
+/**
+ * Add a json member profile_count with a child for each protocol used by the user.
+ * Count how many profiles are in use and for each protocol in use counts of how many
+ * profiles that uses such protocol.
+ *
+ * The data can be expressed as follows:
+ *
+ * | PROTO | PROF COUNT |
+ * |:-------|-----------:|
+ * | RDP | 2560 |
+ * | SPICE | 334 |
+ * | SSH | 1540 |
+ * | VNC | 2 |
+ *
+ * | PROTO | LAST USED |
+ * |:-------|----------:|
+ * | RDP | 20180129 |
+ * | SPICE | 20171122 |
+ * | SSH | 20180111 |
+ *
+ *
+ */
+JsonNode *remmina_stats_get_profiles()
+{
+ TRACE_CALL(__func__);
+
+ JsonBuilder *b;
+ JsonNode *r;
+ gchar *s;
+
+ gint profiles_count;
+ GHashTableIter pcountiter, pdateiter;
+ gpointer pcountkey, pcountvalue;
+ gpointer pdatekey, pdatevalue;
+
+ struct ProfilesData *pdata;
+ pdata = g_malloc0(sizeof(struct ProfilesData));
+
+ b = json_builder_new();
+ json_builder_begin_object(b);
+
+ json_builder_set_member_name(b, "profile_count");
+
+ /** @warning this function is usually executed on a dedicated thread,
+ * not on the main thread */
+
+ pdata->proto_date = g_hash_table_new_full(g_str_hash, g_str_equal,
+ (GDestroyNotify)g_free, (GDestroyNotify)g_free);
+ pdata->proto_count = g_hash_table_new_full(g_str_hash, g_str_equal,
+ (GDestroyNotify)g_free, NULL);
+
+ profiles_count = remmina_file_manager_iterate(
+ (GFunc)remmina_profiles_get_data,
+ (gpointer)pdata);
+
+ json_builder_add_int_value(b, profiles_count);
+
+ g_hash_table_iter_init(&pcountiter, pdata->proto_count);
+ while (g_hash_table_iter_next(&pcountiter, &pcountkey, &pcountvalue)) {
+ json_builder_set_member_name(b, (gchar*)pcountkey);
+ json_builder_add_int_value(b, GPOINTER_TO_INT(pcountvalue));
+ }
+
+ g_hash_table_iter_init(&pdateiter, pdata->proto_date);
+ while (g_hash_table_iter_next(&pdateiter, &pdatekey, &pdatevalue)) {
+ s = g_strdup_printf("DATE_%s", (gchar*)pdatekey);
+ json_builder_set_member_name(b, s);
+ g_free(s);
+ json_builder_add_string_value(b, (gchar*)pdatevalue);
+ }
+
+ json_builder_end_object(b);
+ r = json_builder_get_root(b);
+ g_object_unref(b);
+
+ g_hash_table_remove_all(pdata->proto_date);
+ g_hash_table_unref(pdata->proto_date);
+ g_hash_table_remove_all(pdata->proto_count);
+ g_hash_table_unref(pdata->proto_count);
+
+ g_free(pdata);
+
+ return r;
+}
+
+/**
+ * Get all statistics in json format to send periodically to the PHP server.
+ * The caller should free the returned buffer with g_free()
+ * @warning This function is usually executed on a dedicated thread,
+ * not on the main thread.
+ * @return a pointer to the JSON string.
+ */
+JsonNode *remmina_stats_get_all()
+{
+ TRACE_CALL(__func__);
+ JsonBuilder *b;
+ JsonNode *n;
+
+ b = json_builder_new();
+ json_builder_begin_object(b);
+
+ n = remmina_stats_get_uid();
+ json_builder_set_member_name(b, "UID");
+ json_builder_add_value(b, n);
+
+ n = remmina_stats_get_version();
+ json_builder_set_member_name(b, "REMMINAVERSION");
+ json_builder_add_value(b, n);
+
+ if (uname(&u) == -1)
+ g_print("uname:");
+
+ n = remmina_stats_get_os_info();
+ json_builder_set_member_name(b, "SYSTEM");
+ json_builder_add_value(b, n);
+
+ n = remmina_stats_get_gtk_version();
+ json_builder_set_member_name(b, "GTKVERSION");
+ json_builder_add_value(b, n);
+
+ n = remmina_stats_get_gtk_backend();
+ json_builder_set_member_name(b, "GTKBACKEND");
+ json_builder_add_value(b, n);
+
+ n = remmina_stats_get_wm_name();
+ json_builder_set_member_name(b, "WINDOWMANAGER");
+ json_builder_add_value(b, n);
+
+ n = remmina_stats_get_indicator();
+ json_builder_set_member_name(b, "APPINDICATOR");
+ json_builder_add_value(b, n);
+
+ n = remmina_stats_get_profiles();
+ json_builder_set_member_name(b, "PROFILES");
+ json_builder_add_value(b, n);
+
+ json_builder_end_object(b);
+ n = json_builder_get_root(b);
+ g_object_unref(b);
+
+ return n;
+
+}
diff --git a/src/remmina_stats.h b/src/remmina_stats.h
new file mode 100644
index 000000000..4dd3e5208
--- /dev/null
+++ b/src/remmina_stats.h
@@ -0,0 +1,45 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * 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.
+ *
+ */
+
+#pragma once
+
+G_BEGIN_DECLS
+
+#include "json-glib/json-glib.h"
+
+JsonNode *remmina_stats_get_all(void);
+
+G_END_DECLS
+
+
diff --git a/src/remmina_stats_sender.c b/src/remmina_stats_sender.c
new file mode 100644
index 000000000..56cfb1f3c
--- /dev/null
+++ b/src/remmina_stats_sender.c
@@ -0,0 +1,341 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * 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 "config.h"
+#include <gtk/gtk.h>
+#include <string.h>
+#include <libsoup/soup.h>
+#include <openssl/rsa.h>
+#include <openssl/pem.h>
+#include <openssl/err.h>
+#include "remmina/remmina_trace_calls.h"
+#include "remmina_log.h"
+#include "remmina_stats.h"
+#include "remmina_pref.h"
+
+#if !JSON_CHECK_VERSION(1, 2, 0)
+ #define json_node_unref(x) json_node_free(x)
+#endif
+
+/* Timers */
+#define PERIODIC_CHECK_1ST_MS 60000
+#define PERIODIC_CHECK_INTERVAL_MS 1200000
+
+#define PERIODIC_UPLOAD_INTERVAL_SEC 2678400
+
+#define PERIODIC_UPLOAD_URL "https://www.remmina.org/stats/upload_stats.php"
+
+
+static gint periodic_check_source;
+static gint periodic_check_counter;
+
+static char *remmina_RSA_PubKey_v1 =
+ "-----BEGIN PUBLIC KEY-----\n"
+ "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwuI8eOnDV2y9uPdhN+6Q\n"
+ "Cju8+YapN0wKlvwfy1ccQBS+4YnM7/+vzelOzLXJwWBDr/He7G5XEIzOcc9LZsRw\n"
+ "XYAoeB3+kP4OrNIVmKfxL7uijoh+79t3WpR8OOOTFDLmtk23tvdJVj+KfRpm0REK\n"
+ "BmdPHP8NpBzQElEDgXP9weHwQhPLB6MqpaJmfR4AqSumAcsukjbSaCWhqjO2rEiA\n"
+ "eXqJ0JE+PIe4WO1IBvKyYBYP3S77FEMJojkVWGVsjOUGe2VqpX02GaRajRkbqzNK\n"
+ "dGmLQt//kcCuPkiqm/qQQTZc0JJYUrmOjFJW9jODQKXHdZrSz8Xz5+v6VJ49v2TM\n"
+ "PwIDAQAB\n"
+ "-----END PUBLIC KEY-----\n";
+
+typedef struct {
+ gboolean show_only;
+ JsonNode *statsroot;
+} sc_tdata;
+
+static void soup_callback(SoupSession *session, SoupMessage *msg, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ gchar *s = (gchar*)user_data;
+ SoupBuffer *sb;
+ gboolean passed;
+ GTimeVal t;
+
+ g_free(s);
+
+ if (msg->status_code != 200) {
+ remmina_log_printf("HTTP status error sending stats: %d\n",msg->status_code);
+ return;
+ }
+
+ passed = FALSE;
+ sb = soup_message_body_flatten(msg->response_body);
+ remmina_log_printf("STATS script response: %.40s\n", sb->data);
+ if (strncmp(sb->data, "200 ", 4) != 0) {
+ remmina_log_printf("STATS http upload error from server side script: %s\n", sb->data);
+ } else {
+ passed = TRUE;
+ }
+ soup_buffer_free(sb);
+
+ if (passed) {
+ g_get_current_time(&t);
+ remmina_pref.periodic_usage_stats_last_sent = t.tv_sec;
+ remmina_pref_save();
+ }
+
+}
+
+static gchar *rsa_encrypt_string(RSA *pubKey, const char *instr)
+{
+ TRACE_CALL(__func__);
+ /* Calls RSA_public_encrypt multiple times to encrypt instr.
+ * At the end, base64 encode the resulting buffer
+ * Return a buffer ptr. Use g_free() to deallocate it */
+
+ int rsaLen = RSA_size(pubKey);
+ int inLen = strlen(instr);
+ int remaining, r;
+ int blksz, maxblksz;
+ int ebufSize;
+ unsigned char *ebuf, *outptr;
+ gchar *enc;
+
+ maxblksz = rsaLen - 12;
+ ebufSize = (((inLen - 1) / maxblksz) + 1) * rsaLen;
+ ebuf = g_malloc(ebufSize);
+ outptr = ebuf;
+ remaining = strlen(instr);
+
+ while(remaining > 0) {
+ blksz = remaining > maxblksz ? maxblksz : remaining;
+ r = RSA_public_encrypt(blksz,
+ (const unsigned char *)instr,
+ outptr,
+ pubKey, RSA_PKCS1_PADDING); /* Our poor JS libraries only supports RSA_PKCS1_PADDING */
+ if (r == -1 ) {
+ unsigned long e;
+ ERR_load_crypto_strings();
+ e = ERR_get_error();
+ g_print("Error RSA_public_encrypt(): %s - func: %s - reason: %s\n", ERR_lib_error_string(e), ERR_func_error_string(e), ERR_reason_error_string(e));
+ g_free(ebuf);
+ ERR_free_strings();
+ return NULL;
+ }
+ instr += blksz;
+ remaining -= blksz;
+ outptr += r;
+ }
+
+ enc = g_base64_encode(ebuf, ebufSize);
+ g_free(ebuf);
+
+ return enc;
+
+
+}
+
+static gboolean remmina_stats_collector_done(gpointer data)
+{
+ TRACE_CALL(__func__);
+ JsonNode *n;
+ JsonGenerator *g;
+ gchar *unenc_s, *enc_s;
+ SoupSession *ss;
+ SoupMessage *msg;
+ JsonBuilder *b;
+ JsonObject *o;
+ BIO *pkbio;
+ RSA *pubkey;
+ gchar *uid;
+ sc_tdata *sctdata;
+
+ sctdata = (sc_tdata *)data;
+ if (sctdata == NULL)
+ return G_SOURCE_REMOVE;
+
+ n = sctdata->statsroot;
+ if (n == NULL) {
+ g_free(data);
+ return G_SOURCE_REMOVE;
+ }
+
+ if ((o = json_node_get_object(n)) == NULL) {
+ g_free(data);
+ return G_SOURCE_REMOVE;
+ }
+
+ uid = g_strdup(json_object_get_string_member(o, "UID"));
+
+ g = json_generator_new();
+ json_generator_set_root(g, n);
+ json_node_unref(n);
+ unenc_s = json_generator_to_data(g, NULL); // unenc_s=serialized stats
+ remmina_log_printf("STATS upload: JSON data%s\n", unenc_s);
+ g_object_unref(g);
+
+ /* Now encrypt "s" with remminastats public key */
+
+ pkbio = BIO_new_mem_buf(remmina_RSA_PubKey_v1, -1);
+ pubkey = PEM_read_bio_RSA_PUBKEY(pkbio, NULL, NULL, NULL);
+ if (pubkey == NULL) {
+ ERR_load_crypto_strings();
+ unsigned long e;
+ e = ERR_get_error();
+ g_print("Failure in PEM_read_bio_RSAPublicKey: %s - func: %s - reason: %s\n", ERR_lib_error_string(e), ERR_func_error_string(e), ERR_reason_error_string(e));
+ g_print("%s\n", ERR_error_string( e, NULL ));
+ BIO_free(pkbio);
+ g_free(unenc_s);
+ ERR_free_strings();
+ g_free(data);
+ return G_SOURCE_REMOVE;
+ }
+
+ enc_s = rsa_encrypt_string(pubkey, unenc_s);
+
+ g_free(unenc_s);
+ BIO_free(pkbio);
+
+
+ /* Create new json encrypted object */
+
+ b = json_builder_new();
+ json_builder_begin_object(b);
+ json_builder_set_member_name(b, "keyversion");
+ json_builder_add_int_value(b, 1);
+ json_builder_set_member_name(b, "encdata");
+ json_builder_add_string_value(b, enc_s);
+ json_builder_set_member_name(b, "UID");
+ json_builder_add_string_value(b, uid);
+ json_builder_end_object(b);
+ n = json_builder_get_root(b);
+ g_object_unref(b);
+
+ g_free(uid);
+ g_free(enc_s);
+
+ if (!sctdata->show_only) {
+
+ g = json_generator_new();
+ json_generator_set_root(g, n);
+ enc_s = json_generator_to_data(g, NULL); // unenc_s=serialized stats
+ g_object_unref(g);
+
+ ss = soup_session_new();
+ msg = soup_message_new("POST", PERIODIC_UPLOAD_URL);
+ soup_message_set_request(msg, "application/json",
+ SOUP_MEMORY_COPY, enc_s, strlen(enc_s));
+ soup_session_queue_message(ss, msg, soup_callback, enc_s);
+
+ remmina_log_printf("STATS upload: Starting upload to url %s\n", PERIODIC_UPLOAD_URL);
+ }
+
+ json_node_unref(n);
+ g_free(data);
+
+ return G_SOURCE_REMOVE;
+}
+
+
+static gpointer remmina_stats_collector(gpointer data)
+{
+ TRACE_CALL(__func__);
+ JsonNode *n;
+ sc_tdata *sctdata;
+
+ sctdata = (sc_tdata *)data;
+ n = remmina_stats_get_all();
+
+ /* stats collecting is done. Notify main thread calling
+ * remmina_stats_collector_done() */
+ sctdata->statsroot = n;
+ g_idle_add(remmina_stats_collector_done, sctdata);
+
+ return NULL;
+}
+
+void remmina_stats_sender_send(gboolean show_only)
+{
+ TRACE_CALL(__func__);
+
+ sc_tdata *sctdata;
+
+ sctdata = g_malloc(sizeof(sc_tdata));
+ sctdata->show_only = show_only;
+
+ g_thread_new("stats_collector", remmina_stats_collector, (gpointer)sctdata);
+
+}
+
+gboolean remmina_stat_sender_can_send()
+{
+ if (remmina_pref.periodic_usage_stats_permission_asked && remmina_pref.periodic_usage_stats_permitted)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+static gboolean remmina_stats_sender_periodic_check(gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ GTimeVal t;
+ glong next;
+
+ if (!remmina_stat_sender_can_send())
+ return G_SOURCE_REMOVE;
+
+ /* Calculate "next" upload time based on last sent time */
+ next = remmina_pref.periodic_usage_stats_last_sent + PERIODIC_UPLOAD_INTERVAL_SEC;
+ g_get_current_time(&t);
+ /* If current time is after "next" or clock is going back (but > 1/1/2018), then do send stats */
+ if (t.tv_sec > next || (t.tv_sec < remmina_pref.periodic_usage_stats_last_sent && t.tv_sec > 1514764800)) {
+ remmina_stats_sender_send(FALSE);
+ }
+
+ periodic_check_counter++;
+ if (periodic_check_counter <= 1) {
+ /* Reschedule periodic check less frequently after 1st tick.
+ * Note that PERIODIC_CHECK_INTERVAL_MS becomes also a retry interval in case of
+ * upload failure */
+ periodic_check_source = g_timeout_add_full(G_PRIORITY_LOW, PERIODIC_CHECK_INTERVAL_MS, remmina_stats_sender_periodic_check, NULL, NULL);
+ return G_SOURCE_REMOVE;
+ }
+ return G_SOURCE_CONTINUE;
+}
+
+void remmina_stats_sender_schedule()
+{
+ TRACE_CALL(__func__);
+ /* If permitted, schedule the 1st statistics periodic check */
+ if (remmina_stat_sender_can_send()) {
+ periodic_check_counter = 0;
+ periodic_check_source = g_timeout_add_full(G_PRIORITY_LOW, PERIODIC_CHECK_1ST_MS, remmina_stats_sender_periodic_check, NULL, NULL);
+ } else
+ periodic_check_source = 0;
+}
+
+
+
diff --git a/src/remmina_stats_sender.h b/src/remmina_stats_sender.h
new file mode 100644
index 000000000..67d09100a
--- /dev/null
+++ b/src/remmina_stats_sender.h
@@ -0,0 +1,47 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * 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.
+ *
+ */
+
+#pragma once
+
+G_BEGIN_DECLS
+
+void remmina_stats_sender_schedule(void);
+gboolean remmina_stat_sender_can_send(void);
+
+/* This is only for testing purposes: force a SEND */
+void remmina_stats_sender_send(gboolean show_only);
+
+G_END_DECLS
+
+
diff --git a/src/remmina_string_array.c b/src/remmina_string_array.c
new file mode 100644
index 000000000..03176f932
--- /dev/null
+++ b/src/remmina_string_array.c
@@ -0,0 +1,175 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009 - Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 <glib.h>
+#include <string.h>
+#include "remmina_string_array.h"
+#include "remmina/remmina_trace_calls.h"
+
+RemminaStringArray*
+remmina_string_array_new(void)
+{
+ TRACE_CALL(__func__);
+ return g_ptr_array_new();
+}
+
+RemminaStringArray*
+remmina_string_array_new_from_string(const gchar *strs)
+{
+ TRACE_CALL(__func__);
+ RemminaStringArray *array;
+ gchar *buf, *ptr1, *ptr2;
+
+ array = remmina_string_array_new();
+ if (!strs || strs[0] == '\0')
+ return array;
+
+ buf = g_strdup(strs);
+ ptr1 = buf;
+ while (ptr1) {
+ ptr2 = strchr(ptr1, ',');
+ if (ptr2)
+ *ptr2++ = '\0';
+ remmina_string_array_add(array, ptr1);
+ ptr1 = ptr2;
+ }
+
+ g_free(buf);
+
+ return array;
+}
+
+RemminaStringArray*
+remmina_string_array_new_from_allocated_string(gchar *strs)
+{
+ TRACE_CALL(__func__);
+ RemminaStringArray *array;
+ array = remmina_string_array_new_from_string(strs);
+ g_free(strs);
+ return array;
+}
+
+void remmina_string_array_add(RemminaStringArray* array, const gchar *str)
+{
+ TRACE_CALL(__func__);
+ g_ptr_array_add(array, g_strdup(str));
+}
+
+gint remmina_string_array_find(RemminaStringArray* array, const gchar *str)
+{
+ TRACE_CALL(__func__);
+ gint i;
+
+ for (i = 0; i < array->len; i++) {
+ if (g_strcmp0(remmina_string_array_index(array, i), str) == 0)
+ return i;
+ }
+ return -1;
+}
+
+void remmina_string_array_remove_index(RemminaStringArray* array, gint i)
+{
+ TRACE_CALL(__func__);
+ g_ptr_array_remove_index(array, i);
+}
+
+void remmina_string_array_remove(RemminaStringArray* array, const gchar *str)
+{
+ TRACE_CALL(__func__);
+ gint i;
+
+ i = remmina_string_array_find(array, str);
+ if (i >= 0) {
+ remmina_string_array_remove_index(array, i);
+ }
+}
+
+void remmina_string_array_intersect(RemminaStringArray* array, const gchar *dest_strs)
+{
+ TRACE_CALL(__func__);
+ RemminaStringArray *dest_array;
+ gint i, j;
+
+ dest_array = remmina_string_array_new_from_string(dest_strs);
+
+ i = 0;
+ while (i < array->len) {
+ j = remmina_string_array_find(dest_array, remmina_string_array_index(array, i));
+ if (j < 0) {
+ remmina_string_array_remove_index(array, i);
+ continue;
+ }
+ i++;
+ }
+
+ remmina_string_array_free(dest_array);
+}
+
+static gint remmina_string_array_compare_func(const gchar **a, const gchar **b)
+{
+ TRACE_CALL(__func__);
+ return g_strcmp0(*a, *b);
+}
+
+void remmina_string_array_sort(RemminaStringArray *array)
+{
+ TRACE_CALL(__func__);
+ g_ptr_array_sort(array, (GCompareFunc)remmina_string_array_compare_func);
+}
+
+gchar*
+remmina_string_array_to_string(RemminaStringArray* array)
+{
+ TRACE_CALL(__func__);
+ GString *gstr;
+ gint i;
+
+ gstr = g_string_new("");
+ for (i = 0; i < array->len; i++) {
+ if (i > 0)
+ g_string_append_c(gstr, ',');
+ g_string_append(gstr, remmina_string_array_index(array, i));
+ }
+ return g_string_free(gstr, FALSE);
+}
+
+void remmina_string_array_free(RemminaStringArray *array)
+{
+ TRACE_CALL(__func__);
+ g_ptr_array_foreach(array, (GFunc)g_free, NULL);
+ g_ptr_array_free(array, TRUE);
+}
+
diff --git a/src/remmina_string_array.h b/src/remmina_string_array.h
new file mode 100644
index 000000000..f476185b9
--- /dev/null
+++ b/src/remmina_string_array.h
@@ -0,0 +1,57 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009 - Vic Lee
+ * Copyright (C) 2017-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.
+ *
+ */
+
+#pragma once
+
+G_BEGIN_DECLS
+
+typedef GPtrArray RemminaStringArray;
+
+RemminaStringArray* remmina_string_array_new(void);
+#define remmina_string_array_index(array, i) (gchar*)g_ptr_array_index(array, i)
+RemminaStringArray* remmina_string_array_new_from_string(const gchar *strs);
+RemminaStringArray* remmina_string_array_new_from_allocated_string(gchar *strs);
+void remmina_string_array_add(RemminaStringArray *array, const gchar *str);
+gint remmina_string_array_find(RemminaStringArray *array, const gchar *str);
+void remmina_string_array_remove_index(RemminaStringArray *array, gint i);
+void remmina_string_array_remove(RemminaStringArray *array, const gchar *str);
+void remmina_string_array_intersect(RemminaStringArray *array, const gchar *dest_strs);
+void remmina_string_array_sort(RemminaStringArray *array);
+gchar* remmina_string_array_to_string(RemminaStringArray *array);
+void remmina_string_array_free(RemminaStringArray *array);
+
+G_END_DECLS
+
+
diff --git a/src/remmina_string_list.c b/src/remmina_string_list.c
new file mode 100644
index 000000000..15004ff82
--- /dev/null
+++ b/src/remmina_string_list.c
@@ -0,0 +1,313 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009 - Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 <gtk/gtk.h>
+#include <string.h>
+#include "config.h"
+#include "remmina_public.h"
+#include "remmina_string_list.h"
+#include "remmina/remmina_trace_calls.h"
+
+static RemminaStringList *string_list;
+
+#define COLUMN_DESCRIPTION 0
+#define COLUMN_VALUE 1
+#define GET_OBJECT(object_name) gtk_builder_get_object(string_list->builder, object_name)
+
+/* Update the buttons state on the items in the TreeModel */
+void remmina_string_list_update_buttons_state(void)
+{
+ gint items_count = gtk_tree_model_iter_n_children(
+ GTK_TREE_MODEL(string_list->liststore_items), NULL);
+
+ gtk_widget_set_sensitive(GTK_WIDGET(string_list->button_remove), items_count > 0);
+ gtk_widget_set_sensitive(GTK_WIDGET(string_list->button_up), items_count > 1);
+ gtk_widget_set_sensitive(GTK_WIDGET(string_list->button_down), items_count > 1);
+}
+
+/* Check the text inserted in the list */
+void remmina_string_list_on_cell_edited(GtkCellRendererText *cell, const gchar *path_string, const gchar *new_text)
+{
+ TRACE_CALL(__func__);
+ gchar *text;
+ gchar *error;
+ GtkTreePath *path = gtk_tree_path_new_from_string(path_string);
+ GtkTreeIter iter;
+
+ gtk_tree_model_get_iter(GTK_TREE_MODEL(string_list->liststore_items), &iter, path);
+ /* Remove delimitors from the string */
+ text = remmina_public_str_replace(new_text, STRING_DELIMITOR, " ");
+ if (cell == string_list->cellrenderertext_item1) {
+ gtk_list_store_set(string_list->liststore_items, &iter, COLUMN_DESCRIPTION, text, -1);
+ }else {
+ /* Check for validation only in second field */
+ if (string_list->priv->validation_func) {
+ if (!((*string_list->priv->validation_func)(text, &error))) {
+ gtk_label_set_text(string_list->label_status, error);
+ gtk_widget_show(GTK_WIDGET(string_list->label_status));
+ g_free(error);
+ }else {
+ gtk_widget_hide(GTK_WIDGET(string_list->label_status));
+ }
+ }
+ gtk_list_store_set(string_list->liststore_items, &iter, COLUMN_VALUE, text, -1);
+ }
+ gtk_tree_path_free(path);
+ g_free(text);
+}
+
+/* Move a TreeIter position */
+static void remmina_string_list_move_iter(GtkTreeIter *from, GtkTreeIter *to)
+{
+ TRACE_CALL(__func__);
+ GtkTreePath *path;
+
+ gtk_list_store_swap(string_list->liststore_items, from, to);
+ path = gtk_tree_model_get_path(GTK_TREE_MODEL(string_list->liststore_items), from);
+ gtk_tree_view_scroll_to_cell(string_list->treeview_items, path, NULL, 0, 0, 0);
+ gtk_tree_path_free(path);
+}
+
+/* Move down the selected TreeRow */
+void remmina_string_list_on_action_down(GtkWidget *widget, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ GtkTreeIter iter;
+ GtkTreeIter target_iter;
+
+ if (gtk_tree_selection_get_selected(string_list->treeview_selection, NULL, &iter)) {
+ gtk_tree_selection_get_selected(string_list->treeview_selection, NULL, &target_iter);
+ if (gtk_tree_model_iter_next(GTK_TREE_MODEL(string_list->liststore_items), &target_iter)) {
+ remmina_string_list_move_iter(&iter, &target_iter);
+ }
+ }
+}
+
+/* Move up the selected TreeRow */
+void remmina_string_list_on_action_up(GtkWidget *widget, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ GtkTreeIter iter;
+ GtkTreeIter target_iter;
+ GtkTreePath *path;
+
+ if (gtk_tree_selection_get_selected(string_list->treeview_selection, NULL, &iter)) {
+ gtk_tree_selection_get_selected(string_list->treeview_selection, NULL, &target_iter);
+ path = gtk_tree_model_get_path(GTK_TREE_MODEL(string_list->liststore_items), &target_iter);
+ /* Before moving the TreeRow check if there's a previous item */
+ if (gtk_tree_path_prev(path)) {
+ gtk_tree_model_get_iter(GTK_TREE_MODEL(string_list->liststore_items), &target_iter, path);
+ gtk_tree_path_free(path);
+ remmina_string_list_move_iter(&iter, &target_iter);
+ }
+ }
+}
+
+/* Add a new TreeRow to the list */
+void remmina_string_list_on_action_add(GtkWidget *widget, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ GtkTreeIter iter;
+ GtkTreePath *path;
+
+ gtk_list_store_append(string_list->liststore_items, &iter);
+ gtk_tree_selection_select_iter(string_list->treeview_selection, &iter);
+
+ path = gtk_tree_model_get_path(GTK_TREE_MODEL(string_list->liststore_items), &iter);
+ gtk_tree_view_set_cursor_on_cell(string_list->treeview_items, path,
+ string_list->treeviewcolumn_item,
+ GTK_CELL_RENDERER(string_list->priv->two_columns ? string_list->cellrenderertext_item1 : string_list->cellrenderertext_item2),
+ TRUE);
+ gtk_tree_path_free(path);
+ remmina_string_list_update_buttons_state();
+}
+
+/* Remove the selected TreeRow from the list */
+void remmina_string_list_on_action_remove(GtkWidget *widget, gpointer user_data)
+{
+ TRACE_CALL(__func__);
+ GtkTreeIter iter;
+
+ if (gtk_tree_selection_get_selected(string_list->treeview_selection, NULL, &iter)) {
+ gtk_list_store_remove(string_list->liststore_items, &iter);
+ }
+ gtk_widget_hide(GTK_WIDGET(string_list->label_status));
+ remmina_string_list_update_buttons_state();
+}
+
+/* Load a string list by splitting a string value */
+void remmina_string_list_set_text(const gchar *text, const gboolean clear_data)
+{
+ TRACE_CALL(__func__);
+ GtkTreeIter iter;
+ gchar **items;
+ gchar **values;
+ gint i;
+ /* Clear the data before to load new items */
+ if (clear_data)
+ gtk_list_store_clear(string_list->liststore_items);
+ /* Split the string and insert each snippet in the string list */
+ items = g_strsplit(text, STRING_DELIMITOR, -1);
+ for (i = 0; i < g_strv_length(items); i++) {
+ values = g_strsplit(items[i], string_list->priv->fields_separator, -1);
+ gtk_list_store_append(string_list->liststore_items, &iter);
+ if (g_strv_length(values) > 1) {
+ /* Two columns data */
+ gtk_list_store_set(string_list->liststore_items, &iter,
+ COLUMN_DESCRIPTION, values[0],
+ COLUMN_VALUE, values[1],
+ -1);
+ }else {
+ /* Single column data */
+ gtk_list_store_set(string_list->liststore_items, &iter,
+ COLUMN_VALUE, values[0],
+ -1);
+ }
+ g_strfreev(values);
+ }
+ g_strfreev(items);
+ remmina_string_list_update_buttons_state();
+}
+
+/* Get a string value representing the string list */
+gchar* remmina_string_list_get_text(void)
+{
+ TRACE_CALL(__func__);
+ GString *str;
+ GtkTreeIter iter;
+ gboolean first;
+ gboolean ret;
+ const gchar *item_description;
+ const gchar *item_value;
+
+ str = g_string_new(NULL);
+ first = TRUE;
+ /* Cycle each GtkTreeIter in the ListStore */
+ ret = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(string_list->liststore_items), &iter);
+ while (ret) {
+ gtk_tree_model_get(GTK_TREE_MODEL(string_list->liststore_items), &iter,
+ COLUMN_DESCRIPTION, &item_description,
+ COLUMN_VALUE, &item_value,
+ -1);
+ if (!item_description)
+ item_description = "";
+ if (item_value && strlen(item_value) > 0) {
+ /* Add a delimitor after the first element */
+ if (!first) {
+ g_string_append(str, STRING_DELIMITOR);
+ }else {
+ first = FALSE;
+ }
+ /* Add the description for two columns list */
+ if (string_list->priv->two_columns) {
+ g_string_append(str, item_description);
+ g_string_append(str, string_list->priv->fields_separator);
+ }
+ /* Add the element to the string */
+ g_string_append(str, item_value);
+ }
+ ret = gtk_tree_model_iter_next(GTK_TREE_MODEL(string_list->liststore_items), &iter);
+ }
+ return g_string_free(str, FALSE);
+}
+
+/* Set a function that will be used to validate the new rows */
+void remmina_string_list_set_validation_func(RemminaStringListValidationFunc func)
+{
+ TRACE_CALL(__func__);
+ string_list->priv->validation_func = func;
+}
+
+/* Set the dialog titles */
+void remmina_string_list_set_titles(gchar *title1, gchar *title2)
+{
+ /* Set dialog titlebar */
+ gtk_window_set_title(GTK_WINDOW(string_list->dialog),
+ (title1 && strlen(title1) > 0) ? title1 : "");
+ /* Set title label */
+ if (title2 && strlen(title2) > 0) {
+ gtk_label_set_text(string_list->label_title, title2);
+ gtk_widget_show(GTK_WIDGET(string_list->label_title));
+ }else {
+ gtk_widget_hide(GTK_WIDGET(string_list->label_title));
+ }
+}
+
+/* RemminaStringList initialization */
+static void remmina_string_list_init(void)
+{
+ TRACE_CALL(__func__);
+ string_list->priv->validation_func = NULL;
+ /* When two columns are requested, show also the first column */
+ if (string_list->priv->two_columns)
+ gtk_cell_renderer_set_visible(GTK_CELL_RENDERER(string_list->cellrenderertext_item1), TRUE);
+ remmina_string_list_update_buttons_state();
+}
+
+/* RemminaStringList instance */
+GtkDialog* remmina_string_list_new(gboolean two_columns, const gchar *fields_separator)
+{
+ TRACE_CALL(__func__);
+ string_list = g_new0(RemminaStringList, 1);
+ string_list->priv = g_new0(RemminaStringListPriv, 1);
+
+ string_list->builder = remmina_public_gtk_builder_new_from_file("remmina_string_list.glade");
+ string_list->dialog = GTK_DIALOG(gtk_builder_get_object(string_list->builder, "DialogStringList"));
+
+ string_list->liststore_items = GTK_LIST_STORE(GET_OBJECT("liststore_items"));
+ string_list->treeview_items = GTK_TREE_VIEW(GET_OBJECT("treeview_items"));
+ string_list->treeviewcolumn_item = GTK_TREE_VIEW_COLUMN(GET_OBJECT("treeviewcolumn_item"));
+ string_list->treeview_selection = GTK_TREE_SELECTION(GET_OBJECT("treeview_selection"));
+ string_list->cellrenderertext_item1 = GTK_CELL_RENDERER_TEXT(GET_OBJECT("cellrenderertext_item1"));
+ string_list->cellrenderertext_item2 = GTK_CELL_RENDERER_TEXT(GET_OBJECT("cellrenderertext_item2"));
+ string_list->button_add = GTK_BUTTON(GET_OBJECT("button_add"));
+ string_list->button_remove = GTK_BUTTON(GET_OBJECT("button_remove"));
+ string_list->button_up = GTK_BUTTON(GET_OBJECT("button_up"));
+ string_list->button_down = GTK_BUTTON(GET_OBJECT("button_down"));
+ string_list->label_title = GTK_LABEL(GET_OBJECT("label_title"));
+ string_list->label_status = GTK_LABEL(GET_OBJECT("label_status"));
+
+ /* Connect signals */
+ gtk_builder_connect_signals(string_list->builder, NULL);
+ /* Initialize the window and load the values */
+ if (!fields_separator)
+ fields_separator = STRING_DELIMITOR2;
+ string_list->priv->fields_separator = fields_separator;
+ string_list->priv->two_columns = two_columns;
+ remmina_string_list_init();
+
+ return string_list->dialog;
+}
diff --git a/src/remmina_string_list.h b/src/remmina_string_list.h
new file mode 100644
index 000000000..7abb277ea
--- /dev/null
+++ b/src/remmina_string_list.h
@@ -0,0 +1,83 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009 - Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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.
+ *
+ */
+
+#pragma once
+
+typedef gboolean (*RemminaStringListValidationFunc)(const gchar *new_str, gchar **error);
+
+typedef struct _RemminaStringListPriv {
+ RemminaStringListValidationFunc validation_func;
+ const gchar *fields_separator;
+ gboolean two_columns;
+} RemminaStringListPriv;
+
+typedef struct _RemminaStringList {
+ GtkBuilder *builder;
+ GtkDialog *dialog;
+
+ GtkListStore *liststore_items;
+ GtkTreeView *treeview_items;
+ GtkTreeViewColumn *treeviewcolumn_item;
+ GtkTreeSelection *treeview_selection;
+ GtkCellRendererText *cellrenderertext_item1;
+ GtkCellRendererText *cellrenderertext_item2;
+
+ GtkButton *button_add;
+ GtkButton *button_remove;
+ GtkButton *button_up;
+ GtkButton *button_down;
+
+ GtkLabel *label_title;
+ GtkLabel *label_status;
+
+ RemminaStringListPriv *priv;
+} RemminaStringList;
+
+G_BEGIN_DECLS
+
+/* RemminaStringList instance */
+GtkDialog* remmina_string_list_new(gboolean two_columns, const gchar *fields_separator);
+/* Load a string list by splitting a string value */
+void remmina_string_list_set_text(const gchar *text, const gboolean clear_data);
+/* Get a string value representing the string list */
+gchar* remmina_string_list_get_text(void);
+/* Set the dialog titles */
+void remmina_string_list_set_titles(gchar *title1, gchar *title2);
+/* Set a function that will be used to validate the new rows */
+void remmina_string_list_set_validation_func(RemminaStringListValidationFunc func);
+
+G_END_DECLS
+
diff --git a/src/remmina_sysinfo.c b/src/remmina_sysinfo.c
new file mode 100644
index 000000000..09a2232c2
--- /dev/null
+++ b/src/remmina_sysinfo.c
@@ -0,0 +1,153 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 "config.h"
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include "remmina/remmina_trace_calls.h"
+#include "remmina_sysinfo.h"
+
+gboolean remmina_sysinfo_is_appindicator_available()
+{
+ /* Check if we have an appindicator available (which uses
+ * DBUS KDE StatusNotifier)
+ */
+
+ TRACE_CALL(__func__);
+ GDBusConnection *con;
+ GVariant *v;
+ GError *error;
+ gboolean available;
+
+ available = FALSE;
+ con = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
+ if (con) {
+ error = NULL;
+ v = g_dbus_connection_call_sync(con,
+ "org.kde.StatusNotifierWatcher",
+ "/StatusNotifierWatcher",
+ "org.freedesktop.DBus.Introspectable",
+ "Introspect",
+ NULL,
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ if (v) {
+ available = TRUE;
+ g_variant_unref(v);
+ }
+ g_object_unref(con);
+ }
+ return available;
+}
+
+/**
+ * Query DBUS to get gnome shell version.
+ * @return the gnome shell version as a string or NULL if error or no gnome shell found.
+ * @warning The returned string must be freed with g_free.
+ */
+gchar *remmina_sysinfo_get_gnome_shell_version()
+{
+ TRACE_CALL(__func__);
+ GDBusConnection *con;
+ GDBusProxy *p;
+ GVariant *v;
+ GError *error;
+ gsize sz;
+ gchar *ret;
+
+ ret = NULL;
+
+ con = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
+ if (con) {
+ error = NULL;
+ p = g_dbus_proxy_new_sync(con,
+ G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+ NULL,
+ "org.gnome.Shell",
+ "/org/gnome/Shell",
+ "org.gnome.Shell",
+ NULL,
+ &error);
+ if (p) {
+ v = g_dbus_proxy_get_cached_property(p, "ShellVersion");
+ if (v) {
+ if (g_variant_is_of_type(v, G_VARIANT_TYPE_STRING)) {
+ ret = g_strdup(g_variant_get_string(v, &sz));
+ }
+ g_variant_unref(v);
+ }
+ g_object_unref(p);
+ }
+ g_object_unref(con);
+ }
+ return ret;
+}
+
+/**
+ * Query environment variables to get the Window manager name..
+ * @return a string composed by XDG_CURRENT_DESKTOP and GDMSESSION as a string
+ * or \0 if nothing has been found.
+ * @warning The returned string must be freed with g_free.
+ */
+gchar *remmina_sysinfo_get_wm_name()
+{
+ TRACE_CALL(__func__);
+ const gchar *xdg_current_desktop;
+ const gchar *gdmsession;
+ gchar *ret;
+
+ xdg_current_desktop = g_environ_getenv(g_get_environ(), "XDG_CURRENT_DESKTOP");
+ gdmsession = g_environ_getenv(g_get_environ(), "GDMSESSION");
+
+ if (!xdg_current_desktop || xdg_current_desktop[0] == '\0') {
+ if (!gdmsession || gdmsession[0] == '\0') {
+ ret = NULL;
+ }else {
+ ret = g_strdup_printf("%s", gdmsession);
+ }
+ }else if (!gdmsession || gdmsession[0] == '\0') {
+ ret = g_strdup_printf("%s", xdg_current_desktop);
+ return ret;
+ }else if (g_strcmp0(xdg_current_desktop,gdmsession) == 0) {
+ ret = g_strdup_printf("%s", xdg_current_desktop);
+ }else {
+ ret = g_strdup_printf("%s %s", xdg_current_desktop, gdmsession);
+ }
+ return ret;
+}
diff --git a/src/remmina_sysinfo.h b/src/remmina_sysinfo.h
new file mode 100644
index 000000000..429d4e10a
--- /dev/null
+++ b/src/remmina_sysinfo.h
@@ -0,0 +1,47 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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.
+ *
+ */
+
+#pragma once
+
+G_BEGIN_DECLS
+
+gboolean remmina_sysinfo_is_appindicator_available(void);
+gchar *remmina_sysinfo_get_gnome_shell_version(void);
+gchar *remmina_sysinfo_get_wm_name();
+
+G_END_DECLS
+
+
diff --git a/src/remmina_utils.c b/src/remmina_utils.c
new file mode 100644
index 000000000..212c2f2bf
--- /dev/null
+++ b/src/remmina_utils.c
@@ -0,0 +1,429 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * 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.
+ *
+ */
+
+/**
+ * General utility functions, non-GTK related.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/utsname.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gio/gio.h>
+#include "remmina/remmina_trace_calls.h"
+
+/** Returns @c TRUE if @a ptr is @c NULL or @c *ptr is @c FALSE. */
+#define EMPTY(ptr) \
+ (!(ptr) || !*(ptr))
+
+struct utsname u;
+
+/* Copyright (C) 1998 VMware, Inc. All rights reserved.
+ * Some of the code in this file is taken from the VMware open client.
+ */
+typedef struct lsb_distro_info {
+ gchar *name;
+ gchar *scanstring;
+} LSBDistroInfo;
+
+/*
+static LSBDistroInfo lsbFields[] = {
+ { "DISTRIB_ID=", "DISTRIB_ID=%s" },
+ { "DISTRIB_RELEASE=", "DISTRIB_RELEASE=%s" },
+ { "DISTRIB_CODENAME=", "DISTRIB_CODENAME=%s" },
+ { "DISTRIB_DESCRIPTION=", "DISTRIB_DESCRIPTION=%s" },
+ { NULL, NULL },
+};
+*/
+
+typedef struct distro_info {
+ gchar *name;
+ gchar *filename;
+} DistroInfo;
+
+static DistroInfo distroArray[] = {
+ { "RedHat", "/etc/redhat-release" },
+ { "RedHat", "/etc/redhat_version" },
+ { "Sun", "/etc/sun-release" },
+ { "SuSE", "/etc/SuSE-release" },
+ { "SuSE", "/etc/novell-release" },
+ { "SuSE", "/etc/sles-release" },
+ { "SuSE", "/etc/os-release" },
+ { "Debian", "/etc/debian_version" },
+ { "Debian", "/etc/debian_release" },
+ { "Ubuntu", "/etc/lsb-release" },
+ { "Mandrake", "/etc/mandrake-release" },
+ { "Mandriva", "/etc/mandriva-release" },
+ { "Mandrake", "/etc/mandrakelinux-release" },
+ { "TurboLinux", "/etc/turbolinux-release" },
+ { "Fedora Core", "/etc/fedora-release" },
+ { "Gentoo", "/etc/gentoo-release" },
+ { "Novell", "/etc/nld-release" },
+ { "Annvix", "/etc/annvix-release" },
+ { "Arch", "/etc/arch-release" },
+ { "Arklinux", "/etc/arklinux-release" },
+ { "Aurox", "/etc/aurox-release" },
+ { "BlackCat", "/etc/blackcat-release" },
+ { "Cobalt", "/etc/cobalt-release" },
+ { "Conectiva", "/etc/conectiva-release" },
+ { "Immunix", "/etc/immunix-release" },
+ { "Knoppix", "/etc/knoppix_version" },
+ { "Linux-From-Scratch", "/etc/lfs-release" },
+ { "Linux-PPC", "/etc/linuxppc-release" },
+ { "MkLinux", "/etc/mklinux-release" },
+ { "PLD", "/etc/pld-release" },
+ { "Slackware", "/etc/slackware-version" },
+ { "Slackware", "/etc/slackware-release" },
+ { "SMEServer", "/etc/e-smith-release" },
+ { "Solaris", "/etc/release" },
+ { "Solus", "/etc/solus-release" },
+ { "Tiny Sofa", "/etc/tinysofa-release" },
+ { "UltraPenguin", "/etc/ultrapenguin-release" },
+ { "UnitedLinux", "/etc/UnitedLinux-release" },
+ { "VALinux", "/etc/va-release" },
+ { "Yellow Dog", "/etc/yellowdog-release" },
+ { NULL, NULL },
+};
+
+gint remmina_utils_strpos(const gchar *haystack, const gchar *needle)
+{
+ TRACE_CALL(__func__);
+ const gchar *sub;
+
+ if (!*needle)
+ return -1;
+
+ sub = strstr(haystack, needle);
+ if (!sub)
+ return -1;
+
+ return sub - haystack;
+}
+
+/* end can be -1 for haystack->len.
+ * returns: position of found text or -1.
+ * (C) Taken from geany */
+gint remmina_utils_string_find(GString *haystack, gint start, gint end, const gchar *needle)
+{
+ TRACE_CALL(__func__);
+ gint pos;
+
+ g_return_val_if_fail(haystack != NULL, -1);
+ if (haystack->len == 0)
+ return -1;
+
+ g_return_val_if_fail(start >= 0, -1);
+ if (start >= (gint)haystack->len)
+ return -1;
+
+ g_return_val_if_fail(!EMPTY(needle), -1);
+
+ if (end < 0)
+ end = haystack->len;
+
+ pos = remmina_utils_strpos(haystack->str + start, needle);
+ if (pos == -1)
+ return -1;
+
+ pos += start;
+ if (pos >= end)
+ return -1;
+ return pos;
+}
+
+/* Replaces @len characters from offset @a pos.
+ * len can be -1 to replace the remainder of @a str.
+ * returns: pos + strlen(replace).
+ * (C) Taken from geany */
+gint remmina_utils_string_replace(GString *str, gint pos, gint len, const gchar *replace)
+{
+ TRACE_CALL(__func__);
+ g_string_erase(str, pos, len);
+ if (replace) {
+ g_string_insert(str, pos, replace);
+ pos += strlen(replace);
+ }
+ return pos;
+}
+
+/**
+ * Replaces all occurrences of @a needle in @a haystack with @a replace.
+ *
+ * @param haystack The input string to operate on. This string is modified in place.
+ * @param needle The string which should be replaced.
+ * @param replace The replacement for @a needle.
+ *
+ * @return Number of replacements made.
+ **/
+guint remmina_utils_string_replace_all(GString *haystack, const gchar *needle, const gchar *replace)
+{
+ TRACE_CALL(__func__);
+ guint count = 0;
+ gint pos = 0;
+ gsize needle_length = strlen(needle);
+
+ while (1) {
+ pos = remmina_utils_string_find(haystack, pos, -1, needle);
+
+ if (pos == -1)
+ break;
+
+ pos = remmina_utils_string_replace(haystack, pos, needle_length, replace);
+ count++;
+ }
+ return count;
+}
+
+/**
+ * Strip \n, \t and \" from a given string.
+ * This function is particulary useful with g_spawn_command_line_sync that does
+ * not strip control characters from the output.
+ * @warning the result should be freed.
+ * @param a string.
+ * @return a newly allocated copy of string cleaned by \t, \n and \"
+ */
+gchar *remmina_utils_string_strip(const gchar *s)
+{
+ gchar *p = g_malloc(strlen(s) + 1);
+
+ if (p) {
+ gchar *p2 = p;
+ while (*s != '\0') {
+ if (*s != '\t' && *s != '\n' && *s != '\"') {
+ *p2++ = *s++;
+ } else {
+ ++s;
+ }
+ }
+ *p2 = '\0';
+ }
+ return p;
+}
+
+/** OS related functions */
+
+/**
+ * remmina_utils_read_distrofile.
+ *
+ * Look for a distro version file /etc/xxx-release.
+ * Once found, read the file in and figure out which distribution.
+ *
+ * @param filename The file path of a Linux distribution release file.
+ * @param distroSize The size of the distribition name.
+ * @param distro The full distro name.
+ * @return Returns a string containing distro information verbatium from /etc/xxx-release (distro). Use g_free to free the string.
+ *
+ */
+static gchar* remmina_utils_read_distrofile(gchar *filename)
+{
+ TRACE_CALL(__func__);
+ gsize file_sz;
+ struct stat st;
+ gchar *distro_desc = NULL;
+ GError *err = NULL;
+
+ if (g_stat(filename, &st) == -1) {
+ g_debug("%s: could not stat the file %s\n", __func__, filename);
+ return NULL;
+ }
+
+ g_debug("%s: File %s is %lu bytes long\n", __func__, filename, st.st_size);
+ if (st.st_size > 131072)
+ return NULL;
+
+ if (!g_file_get_contents(filename, &distro_desc, &file_sz, &err)) {
+ g_debug("%s: could not get the file content%s: %s\n", __func__, filename, err->message);
+ g_error_free(err);
+ return NULL;
+ }
+
+ if (file_sz == 0) {
+ g_debug("%s: Cannot work with empty file.\n", __FUNCTION__);
+ return NULL;
+ }
+
+ g_debug("%s: Distro description %s\n", __func__, distro_desc);
+ return distro_desc;
+}
+
+/**
+ * Return the OS name as in "uname -s".
+ * @return The OS name or NULL.
+ */
+const gchar* remmina_utils_get_kernel_name()
+{
+ TRACE_CALL(__func__);
+ if (u.sysname)
+ return u.sysname;
+}
+
+const gchar* remmina_utils_get_kernel_release()
+/**
+ * Return the OS version as in "uname -r".
+ * @return The OS release or NULL.
+ */
+{
+ TRACE_CALL(__func__);
+ if (u.release)
+ return u.release;
+}
+
+/**
+ * Return the machine hardware name as in "uname -m".
+ * @return The machine hardware name or NULL.
+ */
+const gchar* remmina_utils_get_kernel_arch()
+{
+ TRACE_CALL(__func__);
+ if (u.machine)
+ return u.machine;
+}
+
+/**
+ * Print the Distributor as specified by the lsb_release command.
+ * @return the distributor ID string or NULL. Caller must free it with g_free().
+ */
+gchar* remmina_utils_get_lsb_id()
+{
+ TRACE_CALL(__func__);
+ gchar *lsb_id = NULL;
+ if (g_spawn_command_line_sync("/usr/bin/lsb_release -si", &lsb_id, NULL, NULL, NULL)) {
+ return lsb_id;
+ }
+ return NULL;
+}
+
+/**
+ * Print the Distribution description as specified by the lsb_release command.
+ * @return the Distribution description string or NULL. Caller must free it with g_free().
+ */
+gchar* remmina_utils_get_lsb_description()
+{
+ TRACE_CALL(__func__);
+ gchar *lsb_description = NULL;
+ GError *err = NULL;
+
+ if (g_spawn_command_line_sync("/usr/bin/lsb_release -sd", &lsb_description, NULL, NULL, &err)) {
+ return lsb_description;
+ }else {
+ g_debug("%s: could not execute lsb_release %s\n", __func__, err->message);
+ g_error_free(err);
+ }
+ g_debug("%s: lsb_release %s\n", __func__, lsb_description);
+ return NULL;
+}
+
+/**
+ * Print the Distribution release name as specified by the lsb_release command.
+ * @return the Distribution release name string or NULL. Caller must free it with g_free().
+ */
+gchar* remmina_utils_get_lsb_release()
+{
+ TRACE_CALL(__func__);
+ gchar *lsb_release = NULL;
+ if (g_spawn_command_line_sync("/usr/bin/lsb_release -sr", &lsb_release, NULL, NULL, NULL)) {
+ return lsb_release;
+ }
+ return NULL;
+}
+
+/**
+ * Print the Distribution codename as specified by the lsb_release command.
+ * @return the codename string or NULL. Caller must free it with g_free().
+ */
+gchar* remmina_utils_get_lsb_codename()
+{
+ TRACE_CALL(__func__);
+ gchar *lsb_codename = NULL;
+ if (g_spawn_command_line_sync("/usr/bin/lsb_release -sc", &lsb_codename, NULL, NULL, NULL)) {
+ return lsb_codename;
+ }
+ return NULL;
+}
+
+/**
+ * Print the distribution description if found.
+ * Test each known distribution specific information file and print it's content.
+ * @return a string or NULL. Caller must free it with g_free().
+ */
+GHashTable* remmina_utils_get_etc_release()
+{
+ TRACE_CALL(__func__);
+ gchar *etc_release = NULL;
+ gint i;
+ GHashTable *r;
+
+ r = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
+
+ for (i = 0; distroArray[i].filename != NULL; i++) {
+ g_debug("%s: File %s\n", __func__, distroArray[i].filename);
+ etc_release = remmina_utils_read_distrofile(distroArray[i].filename);
+ if (etc_release) {
+ if (etc_release[0] != '\0') {
+ g_debug("%s: Distro description %s\n", __func__, etc_release);
+ g_hash_table_insert(r, distroArray[i].filename, etc_release);
+ } else
+ g_free(etc_release);
+ }
+ }
+ return r;
+}
+
+/**
+ * A sample function to show how use the other fOS releated functions.
+ * @return a semicolon separated OS data like in "uname -srm".
+ */
+const gchar* remmina_utils_get_os_info()
+{
+ TRACE_CALL(__func__);
+ gchar *kernel_string;
+
+ if (uname(&u) == -1)
+ g_print("uname:");
+
+ kernel_string = g_strdup_printf("%s;%s;%s\n",
+ remmina_utils_get_kernel_name(),
+ remmina_utils_get_kernel_release(),
+ remmina_utils_get_kernel_arch());
+ if (!kernel_string || kernel_string[0] == '\0')
+ kernel_string = g_strdup_printf("%s;%s;%s\n",
+ "UNKNOWN",
+ "UNKNOWN",
+ "UNKNOWN");
+ return kernel_string;
+}
+
diff --git a/src/remmina_utils.h b/src/remmina_utils.h
new file mode 100644
index 000000000..8fc179388
--- /dev/null
+++ b/src/remmina_utils.h
@@ -0,0 +1,56 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * 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.
+ *
+ */
+
+/**
+ * @file: remmina_utils.h
+ * General utility functions, non-GTK related.
+ */
+
+#pragma once
+
+G_BEGIN_DECLS
+gint remmina_utils_string_find(GString *haystack, gint start, gint end, const gchar *needle);
+gint remmina_utils_string_replace(GString *str, gint pos, gint len, const gchar *replace);
+guint remmina_utils_string_replace_all(GString *haystack, const gchar *needle, const gchar *replace);
+gchar *remmina_utils_string_strip(const gchar *s);
+
+const gchar* remmina_utils_get_kernel_name();
+const gchar* remmina_utils_get_kernel_release();
+const gchar* remmina_utils_get_kernel_arch();
+gchar* remmina_utils_get_lsb_id();
+gchar* remmina_utils_get_lsb_description();
+gchar* remmina_utils_get_lsb_release();
+gchar* remmina_utils_get_lsb_codename();
+GHashTable* remmina_utils_get_etc_release();
+const gchar* remmina_utils_get_os_info();
diff --git a/src/remmina_widget_pool.c b/src/remmina_widget_pool.c
new file mode 100644
index 000000000..93f8c3e26
--- /dev/null
+++ b/src/remmina_widget_pool.c
@@ -0,0 +1,144 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009 - Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * 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 <gtk/gtk.h>
+#include "remmina_public.h"
+#include "remmina_widget_pool.h"
+#include "remmina/remmina_trace_calls.h"
+
+static GPtrArray *remmina_widget_pool = NULL;
+
+void remmina_widget_pool_init(void)
+{
+ TRACE_CALL(__func__);
+ remmina_widget_pool = g_ptr_array_new();
+}
+
+static void remmina_widget_pool_on_widget_destroy(GtkWidget *widget, gpointer data)
+{
+ TRACE_CALL(__func__);
+ g_ptr_array_remove(remmina_widget_pool, widget);
+}
+
+void remmina_widget_pool_register(GtkWidget *widget)
+{
+ TRACE_CALL(__func__);
+ g_ptr_array_add(remmina_widget_pool, widget);
+ g_signal_connect(G_OBJECT(widget), "destroy", G_CALLBACK(remmina_widget_pool_on_widget_destroy), NULL);
+}
+
+GtkWidget*
+remmina_widget_pool_find(GType type, const gchar *tag)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *widget;
+ gint i;
+
+ if (remmina_widget_pool == NULL)
+ return NULL;
+
+ for (i = 0; i < remmina_widget_pool->len; i++) {
+ widget = GTK_WIDGET(g_ptr_array_index(remmina_widget_pool, i));
+ if (!G_TYPE_CHECK_INSTANCE_TYPE(widget, type))
+ continue;
+ if (tag && g_strcmp0((const gchar*)g_object_get_data(G_OBJECT(widget), "tag"), tag) != 0)
+ continue;
+ return widget;
+ }
+ return NULL;
+}
+
+GtkWidget*
+remmina_widget_pool_find_by_window(GType type, GdkWindow *window)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *widget;
+ gint i;
+ GdkWindow *parent;
+
+ if (window == NULL || remmina_widget_pool == NULL)
+ return NULL;
+
+ for (i = 0; i < remmina_widget_pool->len; i++) {
+ widget = GTK_WIDGET(g_ptr_array_index(remmina_widget_pool, i));
+ if (!G_TYPE_CHECK_INSTANCE_TYPE(widget, type))
+ continue;
+ /* gdk_window_get_toplevel won't work here, if the window is an embedded client. So we iterate the window tree */
+ for (parent = window; parent && parent != GDK_WINDOW_ROOT; parent = gdk_window_get_parent(parent)) {
+ if (gtk_widget_get_window(widget) == parent)
+ return widget;
+ }
+ }
+ return NULL;
+}
+
+gint remmina_widget_pool_foreach(RemminaWidgetPoolForEachFunc callback, gpointer data)
+{
+ TRACE_CALL(__func__);
+ GtkWidget *widget;
+ gint i;
+ gint n = 0;
+ GPtrArray *wpcpy = NULL;
+
+ if (remmina_widget_pool == NULL)
+ return 0;
+
+ /* Make a copy of remmina_widget_pool, so we can survive when callback()
+ * remove an element from remmina_widget_pool */
+
+ wpcpy = g_ptr_array_sized_new(remmina_widget_pool->len);
+
+ for (i = 0; i < remmina_widget_pool->len; i++)
+ g_ptr_array_add(wpcpy, g_ptr_array_index(remmina_widget_pool, i));
+
+ /* Scan the remmina_widget_pool and call callbac() on every element */
+ for (i = 0; i < wpcpy->len; i++) {
+ widget = GTK_WIDGET(g_ptr_array_index(wpcpy, i));
+ if (callback(widget, data))
+ n++;
+ }
+
+ /* Free the copy */
+ g_ptr_array_unref(wpcpy);
+ return n;
+}
+
+gint remmina_widget_pool_count()
+{
+ TRACE_CALL(__func__);
+ return remmina_widget_pool->len;
+}
+
diff --git a/src/remmina_widget_pool.h b/src/remmina_widget_pool.h
new file mode 100644
index 000000000..27a8f3089
--- /dev/null
+++ b/src/remmina_widget_pool.h
@@ -0,0 +1,51 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009 - Vic Lee
+ * Copyright (C) 2017-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.
+ *
+ */
+
+#pragma once
+
+G_BEGIN_DECLS
+
+typedef gboolean (*RemminaWidgetPoolForEachFunc)(GtkWidget *widget, gpointer data);
+
+void remmina_widget_pool_init(void);
+void remmina_widget_pool_register(GtkWidget *widget);
+GtkWidget* remmina_widget_pool_find(GType type, const gchar *tag);
+GtkWidget* remmina_widget_pool_find_by_window(GType type, GdkWindow *window);
+gint remmina_widget_pool_foreach(RemminaWidgetPoolForEachFunc callback, gpointer data);
+gint remmina_widget_pool_count(void);
+
+G_END_DECLS
+
+