summaryrefslogtreecommitdiffstats
path: root/src/remmina_ssh_plugin.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 17:06:32 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 17:06:32 +0000
commit2dad5357405ad33cfa792f04b3ab62a5d188841e (patch)
treeb8f8893942060fe3cfb04ac374cda96fdfc8f453 /src/remmina_ssh_plugin.c
parentInitial commit. (diff)
downloadremmina-2dad5357405ad33cfa792f04b3ab62a5d188841e.tar.xz
remmina-2dad5357405ad33cfa792f04b3ab62a5d188841e.zip
Adding upstream version 1.4.34+dfsg.upstream/1.4.34+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/remmina_ssh_plugin.c')
-rw-r--r--src/remmina_ssh_plugin.c1738
1 files changed, 1738 insertions, 0 deletions
diff --git a/src/remmina_ssh_plugin.c b/src/remmina_ssh_plugin.c
new file mode 100644
index 0000000..93f692f
--- /dev/null
+++ b/src/remmina_ssh_plugin.c
@@ -0,0 +1,1738 @@
+/**
+ * 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-2022 Antenore Gatta, Giovanni Panozzo.
+ * @copyright Copyright (C) 2022-2023 Antenore Gatta, Giovanni Panozzo, Hiroyuki Tanaka
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL. * If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. * If you
+ * do not wish to do so, delete 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 <glib-object.h>
+#include <gobject/gvaluecollector.h>
+#include <vte/vte.h>
+#include <locale.h>
+#include <langinfo.h>
+#include "remmina_log.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 PCRE2_CODE_UNIT_WIDTH 8
+#include <pcre2.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 REMMINA_PLUGIN_SSH_FEATURE_TOOL_INCREASE_FONT 4
+#define REMMINA_PLUGIN_SSH_FEATURE_TOOL_DECREASE_FONT 5
+#define REMMINA_PLUGIN_SSH_FEATURE_TOOL_SEARCH 6
+
+#define GET_PLUGIN_DATA(gp) (RemminaPluginSshData *)g_object_get_data(G_OBJECT(gp), "plugin-data");
+
+/** Palette colors taken from sakura */
+#define PALETTE_SIZE 16
+/* Min fontsize and increase */
+#define FONT_SCALE 0.75
+#define SCALE_FACTOR 0.1
+#define FONT_MINIMAL_SIZE (PANGO_SCALE * 6)
+
+enum color_schemes { LINUX, TANGO, GRUVBOX, SOLARIZED_DARK, SOLARIZED_LIGHT, XTERM, CUSTOM };
+
+/** 16 colour palettes in GdkRGBA format (red, green, blue, alpha).
+ * Text displayed in the first 8 colours (0-7) is meek (uses thin strokes).
+ * Text displayed in the second 8 colours (8-15) is bold (uses thick strokes).
+ **/
+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 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 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 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 }
+};
+
+#define DEFAULT_PALETTE "linux_palette"
+
+
+/** The SSH plugin implementation */
+typedef struct _RemminaSshSearch {
+ GtkWidget * parent;
+
+ GtkBuilder * builder;
+ GtkWidget * window;
+ GtkWidget * search_entry;
+ GtkWidget * search_prev_button;
+ GtkWidget * search_next_button;
+ GtkWidget * close_button;
+ GtkToggleButton * match_case_checkbutton;
+ GtkToggleButton * entire_word_checkbutton;
+ GtkToggleButton * regex_checkbutton;
+ GtkToggleButton * wrap_around_checkbutton;
+ GtkWidget * reveal_button;
+ GtkWidget * revealer;
+
+ //VteTerminal *terminal;
+ gboolean regex_caseless;
+ gboolean has_regex;
+ gchar * regex_pattern;
+} RemminaSshSearch;
+
+typedef struct _RemminaPluginSshData {
+ RemminaSSHShell * shell;
+ GFile * vte_session_file;
+ GtkWidget * vte;
+
+ const GdkRGBA * palette;
+
+ pthread_t thread;
+
+ gboolean closed;
+
+ RemminaSshSearch * search_widget;
+} RemminaPluginSshData;
+
+
+#define GET_OBJECT(object_name) gtk_builder_get_object(search_widget->builder, object_name)
+
+static RemminaPluginService *remmina_plugin_service = NULL;
+
+static gboolean
+remmina_plugin_ssh_on_size_allocate(GtkWidget *widget, GtkAllocation *alloc, RemminaProtocolWidget *gp);
+
+static gboolean
+valid_color(GdkRGBA const *color)
+{
+ return color->red >= 0. && color->red <= 1. &&
+ color->green >= 0. && color->green <= 1. &&
+ color->blue >= 0. && color->blue <= 1. &&
+ color->alpha >= 0. && color->alpha <= 1.;
+}
+
+
+/**
+ * Remmina protocol plugin main function.
+ *
+ * First it starts the SSH tunnel if needed and then 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;
+ gboolean partial = FALSE;
+ gchar *hostport;
+ gint ret = REMMINA_SSH_AUTH_NULL;
+
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+ CANCEL_ASYNC
+
+ gpdata = GET_PLUGIN_DATA(gp);
+
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ /* we may need to open a new tunnel */
+ REMMINA_DEBUG("Tentatively create an SSH tunnel");
+ hostport = remmina_plugin_service->protocol_plugin_start_direct_tunnel(gp, 22, FALSE);
+ if (hostport == NULL) {
+ remmina_plugin_service->protocol_plugin_signal_connection_closed(gp);
+ return NULL;
+ }
+ REMMINA_DEBUG("protocol_plugin_start_direct_tunnel returned hostport: %s", hostport);
+
+ ssh = g_object_get_data(G_OBJECT(gp), "user-data");
+ if (ssh) {
+ REMMINA_DEBUG("Creating SSH shell based on existing SSH session");
+ shell = remmina_ssh_shell_new_from_ssh(ssh);
+ REMMINA_DEBUG("Calling remmina_public_get_server_port");
+ remmina_plugin_service->get_server_port(hostport, 22, &ssh->tunnel_entrance_host, &ssh->tunnel_entrance_port);
+ REMMINA_DEBUG("tunnel_entrance_host: %s, tunnel_entrance_port: %d", ssh->tunnel_entrance_host, ssh->tunnel_entrance_port);
+
+ if (remmina_ssh_init_session(REMMINA_SSH(shell)) &&
+ remmina_ssh_auth(REMMINA_SSH(shell), NULL, gp, remminafile) == REMMINA_SSH_AUTH_SUCCESS &&
+ remmina_ssh_shell_open(shell, (RemminaSSHExitFunc)
+ remmina_plugin_service->protocol_plugin_signal_connection_closed, gp))
+ cont = TRUE;
+ } else {
+ /* New SSH Shell connection */
+ REMMINA_DEBUG("Creating SSH shell based on a new SSH session");
+ shell = remmina_ssh_shell_new_from_file(remminafile);
+ ssh = REMMINA_SSH(shell);
+ REMMINA_DEBUG("Calling remmina_public_get_server_port");
+ remmina_plugin_service->get_server_port(hostport, 22, &ssh->tunnel_entrance_host, &ssh->tunnel_entrance_port);
+ REMMINA_DEBUG("tunnel_entrance_host: %s, tunnel_entrance_port: %d", ssh->tunnel_entrance_host, ssh->tunnel_entrance_port);
+
+ while (1) {
+ if (!partial) {
+ if (!remmina_ssh_init_session(ssh)) {
+ REMMINA_DEBUG("init session error: %s", ssh->error);
+ remmina_plugin_service->protocol_plugin_set_error(gp, "%s", ssh->error);
+ // exit the loop here: OK
+ break;
+ }
+ }
+
+ ret = remmina_ssh_auth_gui(ssh, gp, remminafile);
+ switch (ret) {
+ case REMMINA_SSH_AUTH_SUCCESS:
+ REMMINA_DEBUG("Authentication success");
+ if (!remmina_ssh_shell_open(shell, (RemminaSSHExitFunc)
+ remmina_plugin_service->protocol_plugin_signal_connection_closed, gp)) {
+ remmina_plugin_service->protocol_plugin_set_error(gp, "%s", ssh->error);
+ break;
+ }
+ gchar *server;
+ gint port;
+ remmina_plugin_service->get_server_port(remmina_plugin_service->file_get_string(remminafile, "server"),
+ 22,
+ &server,
+ &port);
+
+ REMMINA_AUDIT(_("Connected to %s:%d via SSH"), server, port);
+ g_free(server), server = NULL;
+ break;
+ case REMMINA_SSH_AUTH_PARTIAL:
+ REMMINA_DEBUG("Continue with the next auth method");
+ partial = TRUE;
+ // Continue the loop: OK
+ continue;
+ break;
+ case REMMINA_SSH_AUTH_RECONNECT:
+ REMMINA_DEBUG("Reconnecting…");
+ if (ssh->session) {
+ ssh_disconnect(ssh->session);
+ ssh_free(ssh->session);
+ ssh->session = NULL;
+ }
+ g_free(ssh->callback);
+ // Continue the loop: OK
+ continue;
+ break;
+ case REMMINA_SSH_AUTH_USERCANCEL:
+ REMMINA_DEBUG("Interrupted by the user");
+ // TODO: exit the loop here: OK
+ goto BREAK;
+ break;
+ default:
+ REMMINA_DEBUG("Error during the authentication: %s", ssh->error);
+ remmina_plugin_service->protocol_plugin_set_error(gp, "%s", ssh->error);
+ // TODO: exit the loop here: OK
+ goto BREAK;
+ }
+
+
+ cont = TRUE;
+ break;
+ }
+ }
+
+BREAK:
+ REMMINA_DEBUG("Authentication terminated with exit status %d", ret);
+
+ g_free(hostport);
+
+ if (!cont) {
+ if (shell) remmina_ssh_shell_free(shell);
+ remmina_plugin_service->protocol_plugin_signal_connection_closed(gp);
+ return NULL;
+ }
+
+ gpdata->shell = shell;
+
+ gchar *charset = REMMINA_SSH(shell)->charset;
+
+ remmina_plugin_ssh_vte_terminal_set_encoding_and_pty(VTE_TERMINAL(gpdata->vte), charset, shell->master, shell->slave);
+
+ /* TODO: The following call should be moved on the main thread, or something weird could happen */
+ //remmina_plugin_ssh_on_size_allocate(GTK_WIDGET(gpdata->vte), NULL, gp);
+
+ remmina_plugin_service->protocol_plugin_signal_connection_opened(gp);
+
+ 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') {
+ }
+
+ 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_foreign 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));
+
+ if (gpdata->shell)
+ 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);
+ RemminaFile *remminafile;
+
+ remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+
+ if (remmina_plugin_service->file_get_int(remminafile, "audiblebell", FALSE)) {
+ vte_terminal_set_audible_bell(VTE_TERMINAL(gpdata->vte), TRUE);
+ g_info("audible_bell set to %i", vte_terminal_get_audible_bell(VTE_TERMINAL(gpdata->vte)));
+ }
+ 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
+ }
+
+#if VTE_CHECK_VERSION(0, 51, 3)
+ REMMINA_DEBUG("Using vte_terminal_set_bold_is_bright instead of vte_terminal_set_allow_bold as deprecated");
+ vte_terminal_set_bold_is_bright(VTE_TERMINAL(gpdata->vte), remmina_pref.vte_allow_bold_text);
+#else
+ vte_terminal_set_allow_bold(VTE_TERMINAL(gpdata->vte), remmina_pref.vte_allow_bold_text);
+#endif
+ 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_decrease_font(GtkMenuItem *menuitem, gpointer vte)
+{
+ TRACE_CALL(__func__);
+ vte_terminal_set_font_scale(VTE_TERMINAL(vte), vte_terminal_get_font_scale(VTE_TERMINAL(vte)) - SCALE_FACTOR);
+}
+
+void
+remmina_plugin_ssh_vte_increase_font(GtkMenuItem *menuitem, gpointer vte)
+{
+ TRACE_CALL(__func__);
+ vte_terminal_set_font_scale(VTE_TERMINAL(vte), vte_terminal_get_font_scale(VTE_TERMINAL(vte)) + SCALE_FACTOR);
+}
+
+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) {
+ // TRANSLATORS: %s is a placeholder for an error message
+ widget = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Error: %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 in"),
+ 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;
+}
+
+
+/* regex */
+
+static void jit_regex(VteRegex *regex, char const *pattern)
+{
+ TRACE_CALL(__func__);
+ GError *error;
+
+ if (!vte_regex_jit(regex, PCRE2_JIT_COMPLETE, &error) ||
+ !vte_regex_jit(regex, PCRE2_JIT_PARTIAL_SOFT, &error))
+ if (!g_error_matches(error, VTE_REGEX_ERROR, -45 /* PCRE2_ERROR_JIT_BADOPTION: JIT not supported */))
+ REMMINA_DEBUG("JITing regex “%s” failed: %s\n", pattern, error->message);
+}
+
+static VteRegex *compile_regex_for_search(char const *pattern, gboolean caseless, GError **error)
+{
+ TRACE_CALL(__func__);
+ uint32_t flags = PCRE2_UTF | PCRE2_NO_UTF_CHECK | PCRE2_MULTILINE;
+
+ if (caseless)
+ flags |= PCRE2_CASELESS;
+
+ VteRegex *regex = vte_regex_new_for_search(pattern, strlen(pattern), flags, error);
+
+ if (regex != NULL)
+ jit_regex(regex, pattern);
+
+ return regex;
+}
+
+static void
+remmina_search_widget_update_sensitivity(RemminaSshSearch *search_widget)
+{
+ TRACE_CALL(__func__);
+ gboolean can_search = search_widget->has_regex;
+
+ gtk_widget_set_sensitive(search_widget->search_next_button, can_search);
+ gtk_widget_set_sensitive(search_widget->search_prev_button, can_search);
+}
+
+static void
+remmina_search_widget_update_regex(RemminaPluginSshData *gpdata)
+{
+ TRACE_CALL(__func__);
+ GError *error = NULL;
+
+ RemminaSshSearch *search_widget = gpdata->search_widget;
+ char const *search_text = gtk_entry_get_text(GTK_ENTRY(search_widget->search_entry));
+ gboolean caseless = gtk_toggle_button_get_active(search_widget->match_case_checkbutton) == FALSE;
+
+ char *pattern;
+
+ if (gtk_toggle_button_get_active(search_widget->regex_checkbutton))
+ pattern = g_strdup(search_text);
+ else
+ pattern = g_regex_escape_string(search_text, -1);
+
+ if (gtk_toggle_button_get_active(search_widget->regex_checkbutton)) {
+ char *tmp = g_strdup_printf("\\b%s\\b", pattern);
+ g_free(pattern);
+ pattern = tmp;
+ }
+
+ if (caseless == search_widget->regex_caseless &&
+ g_strcmp0(pattern, search_widget->regex_pattern) == 0)
+ return;
+
+ search_widget->regex_caseless = caseless;
+ g_free(search_widget->regex_pattern);
+ search_widget->regex_pattern = NULL;
+
+ if (search_text[0] != '\0') {
+ REMMINA_DEBUG("Search text is: %s", search_text);
+ VteRegex *regex = compile_regex_for_search(pattern, caseless, &error);
+ vte_terminal_search_set_regex(VTE_TERMINAL(gpdata->vte), regex, 0);
+ if (regex != NULL)
+ vte_regex_unref(regex);
+
+ if (!error) {
+ search_widget->has_regex = TRUE;
+ search_widget->regex_pattern = pattern; /* adopt */
+ pattern = NULL; /* adopted */
+ gtk_widget_set_tooltip_text(search_widget->search_entry, NULL);
+ } else {
+ search_widget->has_regex = FALSE;
+ REMMINA_DEBUG("Regex not set, cannot search");
+ gtk_widget_set_tooltip_text(search_widget->search_entry, error->message);
+ }
+ }
+
+ g_free(pattern);
+ g_free(error);
+
+ remmina_search_widget_update_sensitivity(search_widget);
+}
+
+static void
+remmina_search_widget_wrap_around_toggled(GtkToggleButton *button, RemminaPluginSshData *gpdata)
+{
+ TRACE_CALL(__func__);
+
+ vte_terminal_search_set_wrap_around(VTE_TERMINAL(gpdata->vte), gtk_toggle_button_get_active(button));
+}
+
+static void
+remmina_search_widget_search_forward(RemminaPluginSshData *gpdata)
+{
+ TRACE_CALL(__func__);
+
+ RemminaSshSearch *search_sidget = gpdata->search_widget;
+
+ if (!search_sidget->has_regex)
+ return;
+ vte_terminal_search_find_next(VTE_TERMINAL(gpdata->vte));
+}
+
+static void
+remmina_search_widget_search_backward(RemminaPluginSshData *gpdata)
+{
+ TRACE_CALL(__func__);
+
+ RemminaSshSearch *search_sidget = gpdata->search_widget;
+
+ if (!search_sidget->has_regex)
+ return;
+ vte_terminal_search_find_previous(VTE_TERMINAL(gpdata->vte));
+}
+
+GtkWidget *remmina_plugin_pop_search_new(GtkWidget *relative_to, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginSshData *gpdata = GET_PLUGIN_DATA(gp);
+
+ gpdata->search_widget = g_new0(RemminaSshSearch, 1);
+ RemminaSshSearch *search_widget = gpdata->search_widget;
+
+ search_widget->regex_caseless = FALSE;
+ search_widget->has_regex = FALSE;
+ search_widget->regex_pattern = NULL;
+
+ search_widget->builder = remmina_public_gtk_builder_new_from_resource("/org/remmina/Remmina/src/../data/ui/remmina_search.glade");
+ search_widget->window = GTK_WIDGET(GET_OBJECT("RemminaSearchWidget"));
+ search_widget->search_entry = GTK_WIDGET(GET_OBJECT("search_entry"));
+ search_widget->search_prev_button = GTK_WIDGET(GET_OBJECT("search_prev_button"));
+ search_widget->search_next_button = GTK_WIDGET(GET_OBJECT("search_next_button"));
+ search_widget->close_button = GTK_WIDGET(GET_OBJECT("close_button"));
+ search_widget->match_case_checkbutton = GTK_TOGGLE_BUTTON(GET_OBJECT("match_case_checkbutton"));
+ search_widget->entire_word_checkbutton = GTK_TOGGLE_BUTTON(GET_OBJECT("entire_word_checkbutton"));
+ search_widget->regex_checkbutton = GTK_TOGGLE_BUTTON(GET_OBJECT("regex_checkbutton"));
+ search_widget->wrap_around_checkbutton = GTK_TOGGLE_BUTTON(GET_OBJECT("wrap_around_checkbutton"));
+ search_widget->reveal_button = GTK_WIDGET(GET_OBJECT("reveal_button"));
+ search_widget->revealer = GTK_WIDGET(GET_OBJECT("revealer"));
+
+ gtk_widget_set_can_default(search_widget->search_next_button, TRUE);
+ gtk_widget_grab_default(search_widget->search_next_button);
+
+ gtk_entry_set_activates_default(GTK_ENTRY(search_widget->search_entry), TRUE);
+
+ /* Connect signals */
+ gtk_builder_connect_signals(search_widget->builder, NULL);
+
+ g_signal_connect_swapped(search_widget->close_button, "clicked", G_CALLBACK(gtk_widget_destroy), GTK_WIDGET(search_widget->window));
+
+ g_object_bind_property(search_widget->reveal_button, "active",
+ search_widget->revealer, "reveal-child",
+ G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+
+ g_signal_connect_swapped(search_widget->search_entry, "next-match", G_CALLBACK(remmina_search_widget_search_forward), gpdata);
+ g_signal_connect_swapped(search_widget->search_entry, "previous-match", G_CALLBACK(remmina_search_widget_search_backward), gpdata);
+ g_signal_connect_swapped(search_widget->search_entry, "search-changed", G_CALLBACK(remmina_search_widget_update_regex), gpdata);
+
+ g_signal_connect_swapped(search_widget->search_next_button, "clicked", G_CALLBACK(remmina_search_widget_search_forward), gpdata);
+ g_signal_connect_swapped(search_widget->search_prev_button, "clicked", G_CALLBACK(remmina_search_widget_search_backward), gpdata);
+
+ g_signal_connect_swapped(search_widget->match_case_checkbutton, "toggled", G_CALLBACK(remmina_search_widget_update_regex), gpdata);
+ g_signal_connect_swapped(search_widget->entire_word_checkbutton, "toggled", G_CALLBACK(remmina_search_widget_update_regex), gpdata);
+ g_signal_connect_swapped(search_widget->regex_checkbutton, "toggled", G_CALLBACK(remmina_search_widget_update_regex), gpdata);
+ g_signal_connect_swapped(search_widget->match_case_checkbutton, "toggled", G_CALLBACK(remmina_search_widget_update_regex), gpdata);
+
+ g_signal_connect(search_widget->wrap_around_checkbutton, "toggled", G_CALLBACK(remmina_search_widget_wrap_around_toggled), gpdata);
+
+ remmina_search_widget_update_sensitivity(search_widget);
+ return search_widget->window;
+}
+
+void remmina_plugin_pop_search(GtkMenuItem *menuitem, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginSshData *gpdata = GET_PLUGIN_DATA(gp);
+
+ GtkWindow *parent = NULL;
+
+ REMMINA_DEBUG("Before popover");
+ GtkWidget *window = remmina_plugin_pop_search_new(gpdata->vte, gp);
+ GtkWidget *toplevel = gtk_widget_get_toplevel(gpdata->vte);
+
+ if (GTK_IS_WINDOW(toplevel)) {
+ parent = GTK_WINDOW(toplevel);
+ gtk_window_set_transient_for(GTK_WINDOW(window), parent);
+ gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER_ON_PARENT);
+ }
+ gtk_widget_show(window);
+ REMMINA_DEBUG("After popover");
+}
+
+void remmina_plugin_ssh_call_sftp(GtkMenuItem *menuitem, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ remmina_protocol_widget_call_feature_by_type(gp, REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_PROTOCOL_FEATURE_TOOL_SFTP);
+}
+
+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"));
+ GtkWidget *font_incr = gtk_menu_item_new_with_label(_("Increase font size (host+Page Up)"));
+ GtkWidget *font_decr = gtk_menu_item_new_with_label(_("Decrease font size (host+Page Down)"));
+ GtkWidget *find_text = gtk_menu_item_new_with_label(_("Find text (host+G)"));
+ GtkWidget *sftp = gtk_menu_item_new_with_label(_("Open SFTP transfer…"));
+
+ 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);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), font_incr);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), font_decr);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), find_text);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), sftp);
+
+ 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);
+ g_signal_connect(G_OBJECT(font_incr), "activate",
+ G_CALLBACK(remmina_plugin_ssh_vte_increase_font), gpdata->vte);
+ g_signal_connect(G_OBJECT(font_decr), "activate",
+ G_CALLBACK(remmina_plugin_ssh_vte_decrease_font), gpdata->vte);
+ g_signal_connect(G_OBJECT(find_text), "activate",
+ G_CALLBACK(remmina_plugin_pop_search), gp);
+ g_signal_connect(G_OBJECT(sftp), "activate",
+ G_CALLBACK(remmina_plugin_ssh_call_sftp), gp);
+
+ gtk_widget_show_all(menu);
+}
+
+static void
+remmina_plugin_ssh_eof(VteTerminal *vteterminal, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+
+ RemminaFile *remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
+ RemminaPluginSshData *gpdata = GET_PLUGIN_DATA(gp);
+
+ if (gpdata->closed)
+ return;
+
+ if (remmina_file_get_int(remminafile, "sshlogenabled", FALSE))
+ remmina_plugin_ssh_vte_save_session(NULL, gp);
+
+ gchar *server;
+ gint port;
+ remmina_plugin_service->get_server_port(remmina_plugin_service->file_get_string(remminafile, "server"),
+ 22,
+ &server,
+ &port);
+
+ REMMINA_AUDIT(_("Disconnected from %s:%d via SSH"), server, port);
+ g_free(server), server = NULL;
+ gpdata->closed = TRUE;
+}
+
+static gboolean
+remmina_plugin_ssh_close_connection(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginSshData *gpdata = GET_PLUGIN_DATA(gp);
+
+ RemminaFile *remminafile;
+
+ REMMINA_DEBUG("Requesting to close the connection");
+ 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);
+ remmina_plugin_ssh_eof(VTE_TERMINAL(gpdata->vte), 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_signal_connection_closed(gp);
+ return FALSE;
+}
+
+/**
+ * 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://gitlab.com/Remmina/Remmina/wikis/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;
+ GdkRGBA palette[PALETTE_SIZE];
+
+
+#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);
+
+ gpdata->closed = FALSE;
+
+ 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.color_pref.foreground);
+ gdk_rgba_parse(&background_color, remmina_pref.color_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;
+ GdkRGBA cursor_foreground;
+ GdkRGBA highlight;
+ GdkRGBA highlight_foreground;
+ GdkRGBA colorBD;
+ unsigned int i = 0;
+
+ /*
+ * custom colors reside inside of the 'theme' subdir of the remmina config folder (.config/remmina/theme)
+ * with the file extension '.colors'. The name of the colorfile came from the menu (see below)
+ * sideeffect: It is possible to overwrite the standard colours with a dedicated colourfile like
+ * '0.colors' for GRUVBOX, '1.colors' for TANGO and so on
+ */
+ const gchar *color_name = remmina_plugin_service->file_get_string(remminafile, "ssh_color_scheme");
+
+ gchar *remmina_dir = g_build_path("/", g_get_user_config_dir(), "remmina", "theme", NULL);
+ gchar *remmina_colors_file = g_strdup_printf("%s/%s.colors", remmina_dir, color_name);
+ g_free(remmina_dir);
+
+ /*
+ * try to load theme from one of the system data dirs (based on XDG_DATA_DIRS environment var)
+ */
+ if (!g_file_test(remmina_colors_file, G_FILE_TEST_IS_REGULAR)) {
+ GError *error = NULL;
+ const gchar *const *dirs = g_get_system_data_dirs();
+
+ for (i = 0; dirs[i] != NULL; ++i) {
+ remmina_dir = g_build_path("/", dirs[i], "remmina", "theme", NULL);
+ GDir *system_data_dir = g_dir_open(remmina_dir, 0, &error);
+ // ignoring this error is OK, because the folder may not exist
+ if (error) {
+ g_error_free(error);
+ error = NULL;
+ } else {
+ if (system_data_dir) {
+ g_dir_close(system_data_dir);
+ g_free(remmina_colors_file);
+ remmina_colors_file = g_strdup_printf("%s/%s.colors", remmina_dir, color_name);
+ if (g_file_test(remmina_colors_file, G_FILE_TEST_IS_REGULAR))
+ break;
+ }
+ }
+ g_free(remmina_dir);
+ }
+ }
+
+ if (g_file_test(remmina_colors_file, G_FILE_TEST_IS_REGULAR)) {
+ GKeyFile *gkeyfile;
+ RemminaColorPref color_pref;
+
+ gkeyfile = g_key_file_new();
+ g_key_file_load_from_file(gkeyfile, remmina_colors_file, G_KEY_FILE_NONE, NULL);
+ remmina_pref_file_load_colors(gkeyfile, &color_pref);
+
+ REMMINA_DEBUG("Load custom theme for SSH teminal");
+ g_warn_if_fail(gdk_rgba_parse(&foreground_color, color_pref.foreground));
+ g_warn_if_fail(gdk_rgba_parse(&background_color, color_pref.background));
+ g_warn_if_fail(gdk_rgba_parse(&cursor_color, color_pref.cursor));
+ g_warn_if_fail(gdk_rgba_parse(&cursor_foreground, color_pref.cursor_foreground));
+ g_warn_if_fail(gdk_rgba_parse(&highlight, color_pref.highlight));
+ g_warn_if_fail(gdk_rgba_parse(&highlight_foreground, color_pref.highlight_foreground));
+ g_warn_if_fail(gdk_rgba_parse(&colorBD, color_pref.colorBD));
+
+ g_warn_if_fail(gdk_rgba_parse(&cp[0], color_pref.color0));
+ g_warn_if_fail(gdk_rgba_parse(&cp[1], color_pref.color1));
+ g_warn_if_fail(gdk_rgba_parse(&cp[2], color_pref.color2));
+ g_warn_if_fail(gdk_rgba_parse(&cp[3], color_pref.color3));
+ g_warn_if_fail(gdk_rgba_parse(&cp[4], color_pref.color4));
+ g_warn_if_fail(gdk_rgba_parse(&cp[5], color_pref.color5));
+ g_warn_if_fail(gdk_rgba_parse(&cp[6], color_pref.color6));
+ g_warn_if_fail(gdk_rgba_parse(&cp[7], color_pref.color7));
+ g_warn_if_fail(gdk_rgba_parse(&cp[8], color_pref.color8));
+ g_warn_if_fail(gdk_rgba_parse(&cp[9], color_pref.color9));
+ g_warn_if_fail(gdk_rgba_parse(&cp[10], color_pref.color10));
+ g_warn_if_fail(gdk_rgba_parse(&cp[11], color_pref.color11));
+ g_warn_if_fail(gdk_rgba_parse(&cp[12], color_pref.color12));
+ g_warn_if_fail(gdk_rgba_parse(&cp[13], color_pref.color13));
+ g_warn_if_fail(gdk_rgba_parse(&cp[14], color_pref.color14));
+ g_warn_if_fail(gdk_rgba_parse(&cp[15], color_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]
+ };
+
+ for (i = 0; i < PALETTE_SIZE; i++)
+ palette[i] = custom_palette[i];
+ } else {
+ /* Set colors to GdkRGBA */
+ switch (remmina_plugin_service->file_get_int(remminafile, "ssh_color_scheme", FALSE)) {
+ case LINUX:
+ gdk_rgba_parse(&foreground_color, "#ffffff");
+ gdk_rgba_parse(&background_color, "#000000");
+ gdk_rgba_parse(&cursor_color, "#ffffff");
+ gdk_rgba_parse(&cursor_foreground, "#00000");
+ gdk_rgba_parse(&highlight, "#ffffff");
+ gdk_rgba_parse(&highlight_foreground, "#00000");
+ gdk_rgba_parse(&colorBD, "#ffffff");
+ for (i = 0; i < PALETTE_SIZE; i++)
+ palette[i] = linux_palette[i];
+ break;
+ case TANGO:
+ gdk_rgba_parse(&foreground_color, "#ffffff");
+ gdk_rgba_parse(&background_color, "#000000");
+ gdk_rgba_parse(&cursor_color, "#000000");
+ gdk_rgba_parse(&cursor_foreground, "#ffffff");
+ gdk_rgba_parse(&highlight, "#ffffff");
+ gdk_rgba_parse(&highlight_foreground, "#00000");
+ gdk_rgba_parse(&colorBD, "#000000");
+ for (i = 0; i < PALETTE_SIZE; i++)
+ palette[i] = tango_palette[i];
+ break;
+ case GRUVBOX:
+ gdk_rgba_parse(&foreground_color, "#e6d4a3");
+ gdk_rgba_parse(&background_color, "#1e1e1e");
+ gdk_rgba_parse(&cursor_color, "#e6d4a3");
+ gdk_rgba_parse(&cursor_foreground, "#e6d4a3");
+ gdk_rgba_parse(&highlight, "#e6d4a3");
+ gdk_rgba_parse(&highlight_foreground, "#1e1e1e");
+ gdk_rgba_parse(&colorBD, "#ffffff");
+ for (i = 0; i < PALETTE_SIZE; i++)
+ palette[i] = gruvbox_palette[i];
+ break;
+ case SOLARIZED_DARK:
+ gdk_rgba_parse(&foreground_color, "#839496");
+ gdk_rgba_parse(&background_color, "#002b36");
+ gdk_rgba_parse(&cursor_color, "#93a1a1");
+ gdk_rgba_parse(&cursor_foreground, "#839496");
+ gdk_rgba_parse(&highlight, "#839496");
+ gdk_rgba_parse(&highlight_foreground, "#002b36");
+ gdk_rgba_parse(&colorBD, "#819090");
+ for (i = 0; i < PALETTE_SIZE; i++)
+ palette[i] = solarized_dark_palette[i];
+ break;
+ case SOLARIZED_LIGHT:
+ gdk_rgba_parse(&foreground_color, "#657b83");
+ gdk_rgba_parse(&background_color, "#fdf6e3");
+ gdk_rgba_parse(&cursor_color, "#586e75");
+ gdk_rgba_parse(&cursor_foreground, "#657b83");
+ gdk_rgba_parse(&highlight, "#657b83");
+ gdk_rgba_parse(&highlight_foreground, "#fdf6e3");
+ gdk_rgba_parse(&colorBD, "#475b62");
+ for (i = 0; i < PALETTE_SIZE; i++)
+ palette[i] = solarized_light_palette[i];
+ break;
+ case XTERM:
+ gdk_rgba_parse(&foreground_color, "#000000");
+ gdk_rgba_parse(&background_color, "#ffffff");
+ gdk_rgba_parse(&cursor_color, "#000000");
+ gdk_rgba_parse(&cursor_foreground, "#ffffff");
+ gdk_rgba_parse(&highlight, "#000000");
+ gdk_rgba_parse(&highlight_foreground, "#ffffff");
+ gdk_rgba_parse(&colorBD, "#000000");
+ for (i = 0; i < PALETTE_SIZE; i++)
+ palette[i] = xterm_palette[i];
+ break;
+ case CUSTOM:
+ REMMINA_DEBUG("Custom colors");
+ g_warn_if_fail(gdk_rgba_parse(&foreground_color, remmina_pref.color_pref.foreground));
+ g_warn_if_fail(gdk_rgba_parse(&background_color, remmina_pref.color_pref.background));
+ g_warn_if_fail(gdk_rgba_parse(&cursor_color, remmina_pref.color_pref.cursor));
+ g_warn_if_fail(gdk_rgba_parse(&cursor_foreground, remmina_pref.color_pref.cursor_foreground));
+ g_warn_if_fail(gdk_rgba_parse(&highlight, remmina_pref.color_pref.highlight));
+ g_warn_if_fail(gdk_rgba_parse(&highlight_foreground, remmina_pref.color_pref.highlight_foreground));
+ g_warn_if_fail(gdk_rgba_parse(&colorBD, remmina_pref.color_pref.colorBD));
+
+ g_warn_if_fail(gdk_rgba_parse(&cp[0], remmina_pref.color_pref.color0));
+ g_warn_if_fail(gdk_rgba_parse(&cp[1], remmina_pref.color_pref.color1));
+ g_warn_if_fail(gdk_rgba_parse(&cp[2], remmina_pref.color_pref.color2));
+ g_warn_if_fail(gdk_rgba_parse(&cp[3], remmina_pref.color_pref.color3));
+ g_warn_if_fail(gdk_rgba_parse(&cp[4], remmina_pref.color_pref.color4));
+ g_warn_if_fail(gdk_rgba_parse(&cp[5], remmina_pref.color_pref.color5));
+ g_warn_if_fail(gdk_rgba_parse(&cp[6], remmina_pref.color_pref.color6));
+ g_warn_if_fail(gdk_rgba_parse(&cp[7], remmina_pref.color_pref.color7));
+ g_warn_if_fail(gdk_rgba_parse(&cp[8], remmina_pref.color_pref.color8));
+ g_warn_if_fail(gdk_rgba_parse(&cp[9], remmina_pref.color_pref.color9));
+ g_warn_if_fail(gdk_rgba_parse(&cp[10], remmina_pref.color_pref.color10));
+ g_warn_if_fail(gdk_rgba_parse(&cp[11], remmina_pref.color_pref.color11));
+ g_warn_if_fail(gdk_rgba_parse(&cp[12], remmina_pref.color_pref.color12));
+ g_warn_if_fail(gdk_rgba_parse(&cp[13], remmina_pref.color_pref.color13));
+ g_warn_if_fail(gdk_rgba_parse(&cp[14], remmina_pref.color_pref.color14));
+ g_warn_if_fail(gdk_rgba_parse(&cp[15], remmina_pref.color_pref.color15));
+
+ const GdkRGBA custom_palette[PALETTE_SIZE] = {
+ cp[0], // remmina_pref.color_pref.color0
+ cp[1], // remmina_pref.color_pref.color1
+ cp[2], // remmina_pref.color_pref.color2
+ cp[3], // remmina_pref.color_pref.color3
+ cp[4], // remmina_pref.color_pref.color4
+ cp[5], // remmina_pref.color_pref.color5
+ cp[6], // remmina_pref.color_pref.color6
+ cp[7], // remmina_pref.color_pref.color7
+ cp[8], // remmina_pref.color_pref.color8
+ cp[9], // remmina_pref.color_pref.color9
+ cp[10], // remmina_pref.color_pref.color10
+ cp[11], // remmina_pref.color_pref.color11
+ cp[12], // remmina_pref.color_pref.color12
+ cp[13], // remmina_pref.color_pref.color13
+ cp[14], // remmina_pref.color_pref.color14
+ cp[15] // remmina_pref.color_pref.color15
+ };
+
+ for (i = 0; i < PALETTE_SIZE; i++)
+ palette[i] = custom_palette[i];
+ break;
+ default:
+ REMMINA_DEBUG("Linux paelette colors");
+ for (i = 0; i < PALETTE_SIZE; i++)
+ palette[i] = linux_palette[i];
+ break;
+ }
+ }
+ g_free(remmina_colors_file);
+ REMMINA_DEBUG("foreground_color.red %f, background_color.red %f", foreground_color.red, background_color.red);
+ REMMINA_DEBUG("foreground_color.blue %f, background_color.blue %f", foreground_color.blue, background_color.blue);
+ REMMINA_DEBUG("foreground_color.green %f, background_color.green %f", foreground_color.green, background_color.green);
+ REMMINA_DEBUG("foreground_color.alpha %f, background_color.alpha %f", foreground_color.alpha, background_color.alpha);
+ for (i = 0; i < PALETTE_SIZE; i++) {
+ REMMINA_DEBUG("index: %d, palette validation for red: %f", i, palette[i].red);
+ REMMINA_DEBUG("index: %d, palette validation for green: %f", i, palette[i].green);
+ REMMINA_DEBUG("index: %d, palette validation for blue: %f", i, palette[i].blue);
+ REMMINA_DEBUG("index: %d, palette validation for alpha: %f", i, palette[i].alpha);
+ g_warn_if_fail(valid_color(&palette[i]));
+ }
+ vte_terminal_set_colors(VTE_TERMINAL(vte), &foreground_color, &background_color, 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);
+ vte_terminal_set_color_cursor_foreground(VTE_TERMINAL(vte), &cursor_foreground);
+ vte_terminal_set_color_highlight(VTE_TERMINAL(vte), &highlight);
+ vte_terminal_set_color_highlight_foreground(VTE_TERMINAL(vte), &highlight_foreground);
+ vte_terminal_set_color_bold(VTE_TERMINAL(vte), &colorBD);
+
+#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);
+
+ 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;
+ gchar *fp;
+
+ GFile *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(), "remmina", 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");
+
+ sshlogname = remmina_file_format_properties(remminafile, sshlogname);
+
+ fp = g_strconcat(dir, "/", sshlogname, NULL);
+ g_free((gpointer) sshlogname);
+
+ gpdata->vte_session_file = g_file_new_for_path(fp);
+ g_free(fp);
+
+ g_signal_connect(G_OBJECT(vte), "size-allocate", G_CALLBACK(remmina_plugin_ssh_on_size_allocate), gp);
+ g_signal_connect(G_OBJECT(vte), "unrealize", G_CALLBACK(remmina_plugin_ssh_eof), gp);
+ g_signal_connect(G_OBJECT(vte), "eof", G_CALLBACK(remmina_plugin_ssh_eof), gp);
+ g_signal_connect(G_OBJECT(vte), "child-exited", G_CALLBACK(remmina_plugin_ssh_eof), gp);
+ remmina_plugin_ssh_popup_ui(gp);
+ gtk_widget_show_all(hbox);
+}
+
+/**
+ * Initialize 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;
+}
+
+/**
+ * Not used by 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;
+ case REMMINA_PLUGIN_SSH_FEATURE_TOOL_INCREASE_FONT:
+ vte_terminal_set_font_scale(VTE_TERMINAL(gpdata->vte),
+ vte_terminal_get_font_scale(VTE_TERMINAL(gpdata->vte)) + SCALE_FACTOR);
+ return;
+ case REMMINA_PLUGIN_SSH_FEATURE_TOOL_DECREASE_FONT:
+ vte_terminal_set_font_scale(VTE_TERMINAL(gpdata->vte),
+ vte_terminal_get_font_scale(VTE_TERMINAL(gpdata->vte)) - SCALE_FACTOR);
+ return;
+ case REMMINA_PLUGIN_SSH_FEATURE_TOOL_SEARCH:
+ remmina_plugin_pop_search(NULL, gp);
+ return;
+ }
+}
+
+/** Array of key/value pairs for SSH auth type
+ * libssh methods:
+ *
+ * #define SSH_AUTH_METHOD_UNKNOWN 0x0000u
+ * #define SSH_AUTH_METHOD_NONE 0x0001u
+ * #define SSH_AUTH_METHOD_PASSWORD 0x0002u
+ * #define SSH_AUTH_METHOD_PUBLICKEY 0x0004u
+ * #define SSH_AUTH_METHOD_HOSTBASED 0x0008u
+ * #define SSH_AUTH_METHOD_INTERACTIVE 0x0010u
+ * #define SSH_AUTH_METHOD_GSSAPI_MIC 0x0020u
+ */
+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, 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_TOOL, REMMINA_PLUGIN_SSH_FEATURE_TOOL_INCREASE_FONT, N_("Increase font size"), N_("_Increase font size"), NULL },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_PLUGIN_SSH_FEATURE_TOOL_DECREASE_FONT, N_("Decrease font size"), N_("_Decrease font size"), NULL },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_PLUGIN_SSH_FEATURE_TOOL_SEARCH, N_("Find text"), N_("_Find text"), NULL },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_PROTOCOL_FEATURE_TOOL_SFTP, N_("Open SFTP transfer…"), "folder-remote", NULL },
+ { 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) Setting tooltip
+ * g) Validation data pointer, will be passed to the validation callback method.
+ * h) Validation callback method (Can be NULL. Every entry will be valid then.)
+ * use following prototype:
+ * gboolean mysetting_validator_method(gpointer key, gpointer value,
+ * gpointer validator_data);
+ * gpointer key is a gchar* containing the setting's name,
+ * gpointer value contains the value which should be validated,
+ * gpointer validator_data contains your passed data.
+ */
+static const RemminaProtocolSetting remmina_ssh_basic_settings[] =
+{
+ { REMMINA_PROTOCOL_SETTING_TYPE_SERVER, "server", NULL, FALSE, "_ssh._tcp", NULL, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "ssh_auth", N_("Authentication type"), FALSE, ssh_auth, NULL, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "username", N_("Username"), FALSE, NULL, NULL, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_PASSWORD, "password", N_("User password"), FALSE, NULL, NULL, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_FILE, "ssh_privatekey", N_("SSH identity file"), FALSE, NULL, NULL, NULL, NULL },
+#if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0, 9, 0)
+ { REMMINA_PROTOCOL_SETTING_TYPE_FILE, "ssh_certfile", N_("SSH certificate file"), FALSE, NULL, NULL, NULL, NULL },
+#endif
+ { REMMINA_PROTOCOL_SETTING_TYPE_PASSWORD, "ssh_passphrase", N_("Password to unlock private key"), FALSE, NULL, NULL, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "run_line", N_("Opening command"), FALSE, NULL, NULL, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "exec", N_("Start-up background program"), FALSE, NULL, NULL, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_END, NULL, NULL, FALSE, NULL, NULL, NULL, NULL }
+};
+
+static gchar log_tips[] =
+ N_("The filename can use the following placeholders:\n\n"
+ " • %h is substituted with the server name\n"
+ " • %t is substituted with the SSH server name\n"
+ " • %u is substituted with the username\n"
+ " • %U is substituted with the SSH username\n"
+ " • %p is substituted with Remmina profile name\n"
+ " • %g is substituted with Remmina profile group name\n"
+ " • %d is substituted with local date and time in ISO 8601 format\n");
+
+/**
+ * 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. Setting Tooltip.
+ *
+ */
+static const RemminaProtocolSetting remmina_ssh_advanced_settings[] =
+{
+ { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "ssh_color_scheme", N_("Terminal colour 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_TEXT, "sshlogfolder", N_("Folder for SSH session log"), FALSE, NULL, N_("Full path of an existing folder") },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "sshlogname", N_("Filename for SSH session log"), FALSE, NULL, log_tips },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "sshlogenabled", N_("Log SSH session when exiting Remmina"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "sshsavesession", N_("Log SSH session asynchronously"), TRUE, NULL, N_("Saving the session asynchronously may have a notable performance impact") },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "audiblebell", N_("Audible terminal bell"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "ssh_forward_x11", N_("SSH X11 Forwarding"), TRUE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "ssh_compression", N_("SSH compression"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disablepasswordstoring", N_("Don't remember passwords"), TRUE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "ssh_stricthostkeycheck", N_("Strict host key checking"), FALSE, 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 */
+ "org.remmina.Remmina-ssh-symbolic", /**< Icon for normal connection */
+ "org.remmina.Remmina-ssh-symbolic", /**< 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 */
+ NULL, /**< No screenshot support available */
+ NULL, /**< RCW map event */
+ NULL /**< RCW unmap event */
+};
+
+
+/*
+ * this function is used for
+ * - inserting into the list to became a sorted list [g_list_insert_sorted()]
+ * - checking the list to avoid duplicate entries [g_list_find_custom()]
+ */
+static gint
+compare(gconstpointer a, gconstpointer b)
+{
+ return strcmp((gchar *)a, (gchar *)b);
+}
+
+void
+remmina_ssh_plugin_load_terminal_palettes(gpointer *ssh_terminal_palette_new)
+{
+ unsigned int preset_rec_size = sizeof(ssh_terminal_palette) / sizeof(gpointer);
+
+ GError *error = NULL;
+ GList *files = NULL;
+ unsigned int rec_size = 0;
+ /*
+ * count number of (all) files to reserve enough memory
+ */
+ /* /usr/local/share/remmina */
+ const gchar *const *dirs = g_get_system_data_dirs();
+
+ unsigned int i = 0;
+
+ for (i = 0; dirs[i] != NULL; ++i) {
+ GDir *system_data_dir = NULL;
+ gchar *remmina_dir = g_build_path("/", dirs[i], "remmina", "theme", NULL);
+ system_data_dir = g_dir_open(remmina_dir, 0, &error);
+ g_free(remmina_dir);
+ // ignoring this error is OK, because the folder may not exist
+ if (error) {
+ g_error_free(error);
+ error = NULL;
+ } else {
+ if (system_data_dir) {
+ const gchar *filename;
+ while ((filename = g_dir_read_name(system_data_dir))) {
+ if (!g_file_test(filename, G_FILE_TEST_IS_DIR)) {
+ if (g_str_has_suffix(filename, ".colors")) {
+ gsize len = strrchr(filename, '.') - filename;
+ gchar *menu_str = g_strndup(filename, len);
+ if (g_list_find_custom(files, menu_str, compare) == NULL)
+ files = g_list_insert_sorted(files, menu_str, compare);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /* ~/.config/remmina/colors */
+ gchar *remmina_dir = g_build_path("/", g_get_user_config_dir(), "remmina", "theme", NULL);
+ GDir *user_data_dir;
+
+ user_data_dir = g_dir_open(remmina_dir, 0, &error);
+ g_free(remmina_dir);
+ if (error) {
+ g_error_free(error);
+ error = NULL;
+ } else {
+ if (user_data_dir) {
+ const gchar *filename;
+ while ((filename = g_dir_read_name(user_data_dir))) {
+ if (!g_file_test(filename, G_FILE_TEST_IS_DIR)) {
+ if (g_str_has_suffix(filename, ".colors")) {
+ char *menu_str = g_malloc(strlen(filename) + 1);
+ strcpy(menu_str, filename);
+ char *t2 = strrchr(menu_str, '.');
+ t2[0] = 0;
+ if (g_list_find_custom(files, menu_str, compare) == NULL)
+ files = g_list_insert_sorted(files, menu_str, compare);
+ }
+ }
+ }
+ }
+ }
+
+ rec_size = g_list_length(files) * 2;
+
+ gpointer *color_palette = g_malloc((preset_rec_size + rec_size) * sizeof(gpointer));
+
+ unsigned int field_idx = 0;
+
+ *ssh_terminal_palette_new = color_palette;
+ // preset with (old) static ssh_terminal_palette data
+ for (; field_idx < preset_rec_size; field_idx++) {
+ color_palette[field_idx] = ssh_terminal_palette[field_idx];
+ if (!color_palette[field_idx])
+ break;
+ }
+
+ GList *l_files = NULL;
+
+ for (l_files = g_list_first(files); l_files != NULL; l_files = l_files->next) {
+ gchar *menu_str = (gchar *)l_files->data;
+
+ color_palette[field_idx++] = menu_str;
+ color_palette[field_idx++] = menu_str;
+ }
+ g_list_free(files);
+
+ color_palette[field_idx] = NULL;
+}
+
+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_increase_font);
+ remmina_plugin_ssh_features[4].opt3 = GUINT_TO_POINTER(remmina_pref.vte_shortcutkey_decrease_font);
+ remmina_plugin_ssh_features[5].opt3 = GUINT_TO_POINTER(remmina_pref.vte_shortcutkey_search_text);
+
+ remmina_plugin_service = &remmina_plugin_manager_service;
+
+ RemminaProtocolSettingOpt *settings;
+
+ // preset new settings with (old) static remmina_ssh_advanced_settings data
+#if GLIB_CHECK_VERSION(2,68,0)
+ settings = g_memdup2(remmina_ssh_advanced_settings, sizeof(remmina_ssh_advanced_settings));
+#else
+ settings = g_memdup(remmina_ssh_advanced_settings, sizeof(remmina_ssh_advanced_settings));
+#endif
+
+ // create dynamic advanced settings to made replacing of ssh_terminal_palette possible
+ gpointer ssh_terminal_palette_new = NULL;
+
+ remmina_ssh_plugin_load_terminal_palettes(&ssh_terminal_palette_new);
+
+ settings[0].opt1 = ssh_terminal_palette_new;
+ remmina_plugin_ssh.advanced_settings = (RemminaProtocolSetting *)settings;
+
+ 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