diff options
author | Antenore Gatta <antenore@simbiosi.org> | 2018-05-03 15:38:44 +0300 |
---|---|---|
committer | Antenore Gatta <antenore@simbiosi.org> | 2018-05-03 15:38:44 +0300 |
commit | d77997ca5c06c1dd971cf85383e81bd6584be746 (patch) | |
tree | 78a08a34126a30a25655ca748b9e2197e918102b /src | |
parent | bcf9990358f0ac9423734421be45c38fdccdf1f0 (diff) |
Renamed remmina in src and moved source files
Diffstat (limited to 'src')
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(>ime); + + 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(>ime); + 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 + + |