summaryrefslogtreecommitdiffstats
path: root/src/remmina_protocol_widget.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/remmina_protocol_widget.c')
-rw-r--r--src/remmina_protocol_widget.c2226
1 files changed, 2226 insertions, 0 deletions
diff --git a/src/remmina_protocol_widget.c b/src/remmina_protocol_widget.c
new file mode 100644
index 0000000..7becda0
--- /dev/null
+++ b/src/remmina_protocol_widget.c
@@ -0,0 +1,2226 @@
+/*
+ * 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-2023 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 <gmodule.h>
+#include <stdlib.h>
+
+#include "remmina_chat_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"
+
+#ifdef GDK_WINDOWING_WAYLAND
+#include <gdk/gdkwayland.h>
+#endif
+
+struct _RemminaProtocolWidgetPriv {
+ RemminaFile * remmina_file;
+ RemminaProtocolPlugin * plugin;
+ RemminaProtocolFeature *features;
+
+ gint width;
+ gint height;
+ RemminaScaleMode scalemode;
+ gboolean scaler_expand;
+
+ gboolean has_error;
+ gchar * error_message;
+ gboolean user_disconnect;
+ /* ssh_tunnels is an array of RemminaSSHTunnel*
+ * the 1st one is the "main" tunnel, other tunnels are used for example in sftp commands */
+ GPtrArray * ssh_tunnels;
+ RemminaTunnelInitFunc init_func;
+
+ GtkWidget * chat_window;
+
+ gboolean closed;
+
+ RemminaHostkeyFunc hostkey_func;
+
+ gint profile_remote_width;
+ gint profile_remote_height;
+ gint multimon;
+
+ RemminaMessagePanel * connect_message_panel;
+ RemminaMessagePanel * listen_message_panel;
+ RemminaMessagePanel * auth_message_panel;
+ RemminaMessagePanel * retry_message_panel;
+
+ /* Data saved from the last message_panel when the user confirm */
+ gchar * username;
+ gchar * password;
+ gchar * domain;
+ gboolean save_password;
+
+ gchar * cacert;
+ gchar * cacrl;
+ gchar * clientcert;
+ gchar * clientkey;
+};
+
+enum panel_type {
+ RPWDT_AUTH,
+ RPWDT_QUESTIONYESNO,
+ RPWDT_AUTHX509
+};
+
+G_DEFINE_TYPE(RemminaProtocolWidget, remmina_protocol_widget, GTK_TYPE_EVENT_BOX)
+
+enum {
+ CONNECT_SIGNAL,
+ DISCONNECT_SIGNAL,
+ DESKTOP_RESIZE_SIGNAL,
+ UPDATE_ALIGN_SIGNAL,
+ LOCK_DYNRES_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[LOCK_DYNRES_SIGNAL] = g_signal_new("lock-dynres", G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaProtocolWidgetClass, lock_dynres), 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_close_all_tunnels(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ int i;
+
+ if (gp->priv && gp->priv->ssh_tunnels) {
+ for (i = 0; i < gp->priv->ssh_tunnels->len; i++) {
+#ifdef HAVE_LIBSSH
+ remmina_ssh_tunnel_free((RemminaSSHTunnel *)gp->priv->ssh_tunnels->pdata[i]);
+#else
+ REMMINA_DEBUG("LibSSH support turned off, no need to free SSH tunnel data");
+#endif
+ }
+ g_ptr_array_set_size(gp->priv->ssh_tunnels, 0);
+ }
+}
+
+
+static void remmina_protocol_widget_destroy(RemminaProtocolWidget *gp, gpointer data)
+{
+ TRACE_CALL(__func__);
+
+ g_free(gp->priv->username);
+ gp->priv->username = NULL;
+
+ g_free(gp->priv->password);
+ gp->priv->password = NULL;
+
+ g_free(gp->priv->domain);
+ gp->priv->domain = NULL;
+
+ g_free(gp->priv->cacert);
+ gp->priv->cacert = NULL;
+
+ g_free(gp->priv->cacrl);
+ gp->priv->cacrl = NULL;
+
+ g_free(gp->priv->clientcert);
+ gp->priv->clientcert = NULL;
+
+ g_free(gp->priv->clientkey);
+ gp->priv->clientkey = NULL;
+
+ 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;
+
+ remmina_protocol_widget_close_all_tunnels(gp);
+
+ if (gp->priv && gp->priv->ssh_tunnels) {
+ g_ptr_array_free(gp->priv->ssh_tunnels, TRUE);
+ gp->priv->ssh_tunnels = NULL;
+ }
+}
+
+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;
+ gp->priv->user_disconnect = FALSE;
+ gp->priv->closed = TRUE;
+ gp->priv->ssh_tunnels = g_ptr_array_new();
+
+ g_signal_connect(G_OBJECT(gp), "destroy", G_CALLBACK(remmina_protocol_widget_destroy), NULL);
+}
+
+void remmina_protocol_widget_open_connection_real(gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp = REMMINA_PROTOCOL_WIDGET(data);
+
+ REMMINA_DEBUG("Opening connection");
+
+ RemminaProtocolPlugin *plugin;
+ RemminaProtocolFeature *feature;
+ gint num_plugin;
+ gint num_ssh;
+
+ gp->priv->closed = FALSE;
+
+ plugin = gp->priv->plugin;
+ plugin->init(gp);
+
+ 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_tunnel_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
+ REMMINA_DEBUG("Have SSH");
+ if (num_ssh) {
+ feature->type = REMMINA_PROTOCOL_FEATURE_TYPE_TOOL;
+ feature->id = REMMINA_PROTOCOL_FEATURE_TOOL_SSH;
+ feature->opt1 = _("Connect via SSH from a new terminal");
+ feature->opt1_type_hint = REMMINA_TYPEHINT_STRING;
+ feature->opt2 = "utilities-terminal";
+ feature->opt2_type_hint = REMMINA_TYPEHINT_STRING;
+ feature->opt3 = NULL;
+ feature->opt3_type_hint = REMMINA_TYPEHINT_UNDEFINED;
+ feature++;
+
+ feature->type = REMMINA_PROTOCOL_FEATURE_TYPE_TOOL;
+ feature->id = REMMINA_PROTOCOL_FEATURE_TOOL_SFTP;
+ feature->opt1 = _("Open SFTP transfer…");
+ feature->opt1_type_hint = REMMINA_TYPEHINT_STRING;
+ feature->opt2 = "folder-remote";
+ feature->opt2_type_hint = REMMINA_TYPEHINT_STRING;
+ feature->opt3 = NULL;
+ feature->opt3_type_hint = REMMINA_TYPEHINT_UNDEFINED;
+ feature++;
+ }
+ feature->type = REMMINA_PROTOCOL_FEATURE_TYPE_END;
+#endif
+ }
+
+ if (!plugin->open_connection(gp))
+ remmina_protocol_widget_close_connection(gp);
+}
+
+static void cancel_open_connection_cb(void *cbdata, int btn)
+{
+ RemminaProtocolWidget *gp = (RemminaProtocolWidget *)cbdata;
+
+ remmina_protocol_widget_close_connection(gp);
+}
+
+void remmina_protocol_widget_open_connection(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ gchar *s;
+ const gchar *name;
+ RemminaMessagePanel *mp;
+
+ /* Exec precommand before everything else */
+ mp = remmina_message_panel_new();
+ remmina_message_panel_setup_progress(mp, _("Executing external commands…"), NULL, NULL);
+ rco_show_message_panel(gp->cnnobj, mp);
+
+ remmina_ext_exec_new(gp->priv->remmina_file, "precommand");
+ rco_destroy_message_panel(gp->cnnobj, mp);
+
+ name = remmina_file_get_string(gp->priv->remmina_file, "name");
+ // TRANSLATORS: “%s” is a placeholder for the connection profile name
+ s = g_strdup_printf(_("Connecting to “%s”…"), (name ? name : "*"));
+
+ mp = remmina_message_panel_new();
+ remmina_message_panel_setup_progress(mp, s, cancel_open_connection_cb, gp);
+ g_free(s);
+ gp->priv->connect_message_panel = mp;
+ rco_show_message_panel(gp->cnnobj, mp);
+
+ remmina_protocol_widget_open_connection_real(gp);
+}
+
+static gboolean conn_closed_real(gpointer data, int button){
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp = (RemminaProtocolWidget *)data;
+
+ #ifdef HAVE_LIBSSH
+ /* This will close all tunnels */
+ remmina_protocol_widget_close_all_tunnels(gp);
+#endif
+ /* Exec postcommand */
+ remmina_ext_exec_new(gp->priv->remmina_file, "postcommand");
+ /* Notify listeners (usually rcw) that the connection is closed */
+ g_signal_emit_by_name(G_OBJECT(gp), "disconnect");
+ return G_SOURCE_REMOVE;
+
+}
+
+static gboolean conn_closed(gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp = (RemminaProtocolWidget *)data;
+ int disconnect_prompt = remmina_file_get_int(gp->priv->remmina_file, "disconnect-prompt", FALSE);
+ if (!gp->priv->user_disconnect && !gp->priv->has_error && disconnect_prompt){
+ const char* msg = "Plugin Disconnected";
+ if (gp->priv->has_error){
+ msg = remmina_protocol_widget_get_error_message(gp);
+ remmina_protocol_widget_set_error(gp, NULL);
+ }
+ gp->priv->user_disconnect = FALSE;
+ RemminaMessagePanel* mp = remmina_message_panel_new();
+ remmina_message_panel_setup_message(mp, msg, (RemminaMessagePanelCallback)conn_closed_real, gp);
+ rco_show_message_panel(gp->cnnobj, mp);
+ return G_SOURCE_REMOVE;
+ }
+ else{
+ return conn_closed_real(gp, 0);
+ }
+
+}
+
+void remmina_protocol_widget_signal_connection_closed(RemminaProtocolWidget *gp)
+{
+ /* User told us that they closed the connection,
+ * or the connection was closed with a known error,
+ * add async event to main thread to complete our close tasks */
+ TRACE_CALL(__func__);
+ gp->priv->closed = TRUE;
+ g_idle_add(conn_closed, (gpointer)gp);
+}
+
+static gboolean conn_opened(gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp = (RemminaProtocolWidget *)data;
+
+#ifdef HAVE_LIBSSH
+ if (gp->priv->ssh_tunnels) {
+ for (guint i = 0; i < gp->priv->ssh_tunnels->len; i++)
+ remmina_ssh_tunnel_cancel_accept((RemminaSSHTunnel *)gp->priv->ssh_tunnels->pdata[i]);
+ }
+#endif
+ if (gp->priv->listen_message_panel) {
+ rco_destroy_message_panel(gp->cnnobj, gp->priv->listen_message_panel);
+ gp->priv->listen_message_panel = NULL;
+ }
+ if (gp->priv->connect_message_panel) {
+ rco_destroy_message_panel(gp->cnnobj, gp->priv->connect_message_panel);
+ gp->priv->connect_message_panel = NULL;
+ }
+ if (gp->priv->retry_message_panel) {
+ rco_destroy_message_panel(gp->cnnobj, gp->priv->retry_message_panel);
+ gp->priv->retry_message_panel = NULL;
+ }
+ g_signal_emit_by_name(G_OBJECT(gp), "connect");
+ return G_SOURCE_REMOVE;
+}
+
+void remmina_protocol_widget_signal_connection_opened(RemminaProtocolWidget *gp)
+{
+ /* Plugin told us that it opened the connection,
+ * add async event to main thread to complete our close tasks */
+ TRACE_CALL(__func__);
+ g_idle_add(conn_opened, (gpointer)gp);
+}
+
+static gboolean update_align(gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp = (RemminaProtocolWidget *)data;
+
+ g_signal_emit_by_name(G_OBJECT(gp), "update-align");
+ return G_SOURCE_REMOVE;
+}
+
+void remmina_protocol_widget_update_align(RemminaProtocolWidget *gp)
+{
+ /* Called by the plugin to do updates on rcw */
+ TRACE_CALL(__func__);
+ g_idle_add(update_align, (gpointer)gp);
+}
+
+static gboolean lock_dynres(gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp = (RemminaProtocolWidget *)data;
+
+ g_signal_emit_by_name(G_OBJECT(gp), "lock-dynres");
+ return G_SOURCE_REMOVE;
+}
+
+static gboolean unlock_dynres(gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp = (RemminaProtocolWidget *)data;
+
+ g_signal_emit_by_name(G_OBJECT(gp), "unlock-dynres");
+ return G_SOURCE_REMOVE;
+}
+
+void remmina_protocol_widget_lock_dynres(RemminaProtocolWidget *gp)
+{
+ /* Called by the plugin to do updates on rcw */
+ TRACE_CALL(__func__);
+ g_idle_add(lock_dynres, (gpointer)gp);
+}
+
+void remmina_protocol_widget_unlock_dynres(RemminaProtocolWidget *gp)
+{
+ /* Called by the plugin to do updates on rcw */
+ TRACE_CALL(__func__);
+ g_idle_add(unlock_dynres, (gpointer)gp);
+}
+
+static gboolean desktop_resize(gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp = (RemminaProtocolWidget *)data;
+
+ g_signal_emit_by_name(G_OBJECT(gp), "desktop-resize");
+ return G_SOURCE_REMOVE;
+}
+
+void remmina_protocol_widget_desktop_resize(RemminaProtocolWidget *gp)
+{
+ /* Called by the plugin to do updates on rcw */
+ TRACE_CALL(__func__);
+ g_idle_add(desktop_resize, (gpointer)gp);
+}
+
+
+void remmina_protocol_widget_close_connection(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+
+ /* kindly ask the protocol plugin to close the connection.
+ * Nothing else is done here. */
+
+ if (!GTK_IS_WIDGET(gp))
+ return;
+
+ if (gp->priv->chat_window) {
+ gtk_widget_destroy(gp->priv->chat_window);
+ gp->priv->chat_window = NULL;
+ }
+
+ if (gp->priv->closed) {
+ /* Connection is already closed by the plugin, but
+ * rcw is asking to close again (usually after an error panel)
+ */
+ /* Clear the current error, or "disconnect" signal func will reshow a panel */
+ remmina_protocol_widget_set_error(gp, NULL);
+ g_signal_emit_by_name(G_OBJECT(gp), "disconnect");
+ return;
+ }
+ gp->priv->user_disconnect = TRUE;
+
+ /* Ask the plugin to close, async.
+ * The plugin will emit a "disconnect" signal on gp to call our
+ * remmina_protocol_widget_on_disconnected() when done */
+ gp->priv->plugin->close_connection(gp);
+
+ return;
+}
+
+/** 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());
+ 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 only be sent 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_DEBUG("Keystrokes before replacement is \'%s\'", keystrokes);
+ keystrokes = g_strdup(remmina_public_str_replace_in_place(keystrokes,
+ keystrokes_replaces[i].search,
+ keystrokes_replaces[i].replace));
+ REMMINA_DEBUG("Keystrokes after replacement is \'%s\'", keystrokes);
+ }
+ gchar *iter = g_strdup(keystrokes);
+ 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;
+}
+
+/**
+ * Send to the plugin some keystrokes from the content of the clipboard
+ * This is a copy of remmina_protocol_widget_send_keystrokes but it uses the clipboard content
+ * get from remmina_protocol_widget_send_clipboard
+ * Probably we don't need the replacement table.
+ */
+void remmina_protocol_widget_send_clip_strokes(GtkClipboard *clipboard, const gchar *clip_text, gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp = REMMINA_PROTOCOL_WIDGET(data);
+ gchar *text = g_utf8_normalize(clip_text, -1, G_NORMALIZE_DEFAULT_COMPOSE);
+ guint *keyvals;
+ gint i;
+ GdkKeymap *keymap = gdk_keymap_get_for_display(gdk_display_get_default());
+ 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 text_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 }
+ };
+
+ if (remmina_protocol_widget_plugin_receives_keystrokes(gp)) {
+ if (text) {
+ /* Replace special characters */
+ for (i = 0; text_replaces[i].replace; i++) {
+ REMMINA_DEBUG("Text clipboard before replacement is \'%s\'", text);
+ text = g_strdup(remmina_public_str_replace_in_place(text,
+ text_replaces[i].search,
+ text_replaces[i].replace));
+ REMMINA_DEBUG("Text clipboard after replacement is \'%s\'", text);
+ }
+ gchar *iter = g_strdup(text);
+ REMMINA_DEBUG("Iter: %s", iter),
+ keyvals = (guint *)g_malloc(strlen(text));
+ while (TRUE) {
+ /* Process each character in the keystrokes */
+ character = g_utf8_get_char_validated(iter, -1);
+ REMMINA_DEBUG("Char: U+%04" G_GINT32_FORMAT"X", character);
+ if (character == 0)
+ break;
+ keyval = gdk_unicode_to_keyval(character);
+ REMMINA_DEBUG("Keyval: %u", keyval);
+ /* Replace all the special character with its keyval */
+ for (i = 0; text_replaces[i].replace; i++) {
+ if (character == text_replaces[i].replace[0]) {
+ keys = g_new0(GdkKeymapKey, 1);
+ keyval = text_replaces[i].keyval;
+ REMMINA_DEBUG("Special Keyval: %u", 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)) {
+ REMMINA_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;
+ /*
+ * @fixme heap buffer overflow
+ * In some cases, for example sending \t as the only sequence
+ * may lead to a buffer overflow
+ */
+ 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(text);
+ }
+ return;
+}
+
+void remmina_protocol_widget_send_clipboard(RemminaProtocolWidget *gp, GObject*widget)
+{
+ TRACE_CALL(__func__);
+ GtkClipboard *clipboard;
+
+ clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
+
+ /* Request the contents of the clipboard, contents_received will be
+ * called when we do get the contents.
+ */
+ gtk_clipboard_request_text(clipboard,
+ remmina_protocol_widget_send_clip_strokes, gp);
+}
+
+gboolean remmina_protocol_widget_plugin_screenshot(RemminaProtocolWidget *gp, RemminaPluginScreenshotData *rpsd)
+{
+ TRACE_CALL(__func__);
+ if (!gp->priv->plugin->get_plugin_screenshot) {
+ REMMINA_DEBUG("plugin screenshot function is not implemented, using core Remmina functionality");
+ return FALSE;
+ }
+
+ return gp->priv->plugin->get_plugin_screenshot(gp, rpsd);
+}
+
+gboolean remmina_protocol_widget_map_event(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ if (!gp->priv->plugin->map_event) {
+ REMMINA_DEBUG("Map plugin function not implemented");
+ return FALSE;
+ }
+
+ REMMINA_DEBUG("Calling plugin mapping function");
+ return gp->priv->plugin->map_event(gp);
+}
+
+gboolean remmina_protocol_widget_unmap_event(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ if (!gp->priv->plugin->unmap_event) {
+ REMMINA_DEBUG("Unmap plugin function not implemented");
+ return FALSE;
+ }
+
+ REMMINA_DEBUG("Calling plugin unmapping function");
+ return gp->priv->plugin->unmap_event(gp);
+}
+
+void remmina_protocol_widget_emit_signal(RemminaProtocolWidget *gp, const gchar *signal_name)
+{
+ TRACE_CALL(__func__);
+
+ REMMINA_DEBUG("Emitting signals should be used from the object itself, not from another object");
+ raise(SIGINT);
+
+ 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;
+}
+
+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_tunnel_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_tunnels && gp->priv->ssh_tunnels->len > 0) {
+ rcw_open_from_file_full(
+ remmina_file_dup_temp_protocol(gp->priv->remmina_file, "SSH"), NULL,
+ (RemminaSSHTunnel *)gp->priv->ssh_tunnels->pdata[0], NULL);
+ return;
+ }
+ break;
+
+ case REMMINA_PROTOCOL_FEATURE_TOOL_SFTP:
+ if (gp->priv->ssh_tunnels && gp->priv->ssh_tunnels->len > 0) {
+ rcw_open_from_file_full(
+ remmina_file_dup_temp_protocol(gp->priv->remmina_file, "SFTP"), NULL,
+ gp->priv->ssh_tunnels->pdata[0], 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);
+ 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);
+
+ 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)
+{
+ TRACE_CALL(__func__);
+ gp->priv->hostkey_func = func;
+}
+
+RemminaMessagePanel *remmina_protocol_widget_mpprogress(RemminaConnectionObject *cnnobj, const gchar *msg, RemminaMessagePanelCallback response_callback, gpointer response_callback_data)
+{
+ RemminaMessagePanel *mp;
+
+ 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_MPPROGRESS;
+ d->p.protocolwidget_mpprogress.cnnobj = cnnobj;
+ d->p.protocolwidget_mpprogress.message = msg;
+ d->p.protocolwidget_mpprogress.response_callback = response_callback;
+ d->p.protocolwidget_mpprogress.response_callback_data = response_callback_data;
+ remmina_masterthread_exec_and_wait(d);
+ mp = d->p.protocolwidget_mpprogress.ret_mp;
+ g_free(d);
+ return mp;
+ }
+
+ mp = remmina_message_panel_new();
+ remmina_message_panel_setup_progress(mp, msg, response_callback, response_callback_data);
+ rco_show_message_panel(cnnobj, mp);
+ return mp;
+}
+
+void remmina_protocol_widget_mpdestroy(RemminaConnectionObject *cnnobj, RemminaMessagePanel *mp)
+{
+ 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_MPDESTROY;
+ d->p.protocolwidget_mpdestroy.cnnobj = cnnobj;
+ d->p.protocolwidget_mpdestroy.mp = mp;
+ remmina_masterthread_exec_and_wait(d);
+ g_free(d);
+ return;
+ }
+ rco_destroy_message_panel(cnnobj, mp);
+}
+
+#ifdef HAVE_LIBSSH
+static void cancel_init_tunnel_cb(void *cbdata, int btn)
+{
+ printf("Remmina: Cancelling an opening tunnel is not implemented\n");
+}
+
+static RemminaSSHTunnel *remmina_protocol_widget_init_tunnel(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaSSHTunnel *tunnel;
+ gint ret;
+ gchar *msg;
+ RemminaMessagePanel *mp;
+ gboolean partial = FALSE;
+ gboolean cont = FALSE;
+
+ tunnel = remmina_ssh_tunnel_new_from_file(gp->priv->remmina_file);
+
+ REMMINA_DEBUG("Creating SSH tunnel to “%s” via SSH…", REMMINA_SSH(tunnel)->server);
+ // TRANSLATORS: “%s” is a placeholder for an hostname or an IP address.
+ msg = g_strdup_printf(_("Connecting to “%s” via SSH…"), REMMINA_SSH(tunnel)->server);
+
+ mp = remmina_protocol_widget_mpprogress(gp->cnnobj, msg, cancel_init_tunnel_cb, NULL);
+ g_free(msg);
+
+
+
+ while (1) {
+ if (!partial) {
+ if (!remmina_ssh_init_session(REMMINA_SSH(tunnel))) {
+ REMMINA_DEBUG("SSH Tunnel init session error: %s", REMMINA_SSH(tunnel)->error);
+ remmina_protocol_widget_set_error(gp, REMMINA_SSH(tunnel)->error);
+ // exit the loop here: OK
+ break;
+ }
+ }
+
+ ret = remmina_ssh_auth_gui(REMMINA_SSH(tunnel), gp, gp->priv->remmina_file);
+ REMMINA_DEBUG("Tunnel auth returned %d", ret);
+ switch (ret) {
+ case REMMINA_SSH_AUTH_SUCCESS:
+ REMMINA_DEBUG("Authentication success");
+ 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 (REMMINA_SSH(tunnel)->session) {
+ ssh_disconnect(REMMINA_SSH(tunnel)->session);
+ ssh_free(REMMINA_SSH(tunnel)->session);
+ REMMINA_SSH(tunnel)->session = NULL;
+ }
+ g_free(REMMINA_SSH(tunnel)->callback);
+ // Continue the loop: OK
+ continue;
+ break;
+ case REMMINA_SSH_AUTH_USERCANCEL:
+ REMMINA_DEBUG("Interrupted by the user");
+ // exit the loop here: OK
+ goto BREAK;
+ break;
+ default:
+ REMMINA_DEBUG("Error during the authentication: %s", REMMINA_SSH(tunnel)->error);
+ remmina_protocol_widget_set_error(gp, REMMINA_SSH(tunnel)->error);
+ // exit the loop here: OK
+ goto BREAK;
+ }
+
+
+ cont = TRUE;
+ break;
+ }
+
+#if 0
+
+ if (!remmina_ssh_init_session(REMMINA_SSH(tunnel))) {
+ REMMINA_DEBUG("Cannot init SSH session with tunnel struct");
+ remmina_protocol_widget_set_error(gp, REMMINA_SSH(tunnel)->error);
+ remmina_ssh_tunnel_free(tunnel);
+ return NULL;
+ }
+
+ ret = remmina_ssh_auth_gui(REMMINA_SSH(tunnel), gp, gp->priv->remmina_file);
+ REMMINA_DEBUG("Tunnel auth returned %d", ret);
+ if (ret != REMMINA_SSH_AUTH_SUCCESS) {
+ if (ret != REMMINA_SSH_AUTH_USERCANCEL)
+ remmina_protocol_widget_set_error(gp, REMMINA_SSH(tunnel)->error);
+ remmina_ssh_tunnel_free(tunnel);
+ return NULL;
+ }
+
+#endif
+
+BREAK:
+ if (!cont) {
+ remmina_ssh_tunnel_free(tunnel);
+ return NULL;
+ }
+ remmina_protocol_widget_mpdestroy(gp->cnnobj, mp);
+
+ return tunnel;
+}
+#endif
+
+
+#ifdef HAVE_LIBSSH
+static void cancel_start_direct_tunnel_cb(void *cbdata, int btn)
+{
+ printf("Remmina: Cancelling start_direct_tunnel is not implemented\n");
+}
+
+static gboolean remmina_protocol_widget_tunnel_destroy(RemminaSSHTunnel *tunnel, gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp = REMMINA_PROTOCOL_WIDGET(data);
+ guint idx = 0;
+
+#if GLIB_CHECK_VERSION(2, 54, 0)
+ gboolean found = g_ptr_array_find(gp->priv->ssh_tunnels, tunnel, &idx);
+#else
+ int i;
+ gboolean found = FALSE;
+ for (i = 0; i < gp->priv->ssh_tunnels->len; i++) {
+ if ((RemminaSSHTunnel *)gp->priv->ssh_tunnels->pdata[i] == tunnel) {
+ found = TRUE;
+ idx = i;
+ }
+ }
+#endif
+
+ printf("Tunnel %s found at idx = %d\n", found ? "yes": "not", idx);
+
+ if (found) {
+#ifdef HAVE_LIBSSH
+ REMMINA_DEBUG("[Tunnel with idx %u has been disconnected", idx);
+ remmina_ssh_tunnel_free(tunnel);
+#endif
+ g_ptr_array_remove(gp->priv->ssh_tunnels, 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;
+ const gchar *ssh_tunnel_server;
+ gchar *ssh_tunnel_host, *srv_host, *dest;
+ gint srv_port, ssh_tunnel_port = 0;
+
+ REMMINA_DEBUG("SSH tunnel initialization…");
+
+ server = remmina_file_get_string(gp->priv->remmina_file, "server");
+ ssh_tunnel_server = remmina_file_get_string(gp->priv->remmina_file, "ssh_tunnel_server");
+
+ if (!server)
+ return g_strdup("");
+
+ if (strstr(g_strdup(server), "unix:///") != NULL) {
+ REMMINA_DEBUG("%s is a UNIX socket", server);
+ return g_strdup(server);
+ }
+
+ REMMINA_DEBUG("Calling remmina_public_get_server_port");
+ remmina_public_get_server_port(server, default_port, &srv_host, &srv_port);
+ REMMINA_DEBUG("Calling remmina_public_get_server_port (tunnel)");
+ remmina_public_get_server_port(ssh_tunnel_server, 22, &ssh_tunnel_host, &ssh_tunnel_port);
+ REMMINA_DEBUG("server: %s, port: %d", srv_host, srv_port);
+
+ if (port_plus && srv_port < 100)
+ /* Protocols like VNC supports using instance number :0, :1, etc. as port number. */
+ srv_port += default_port;
+
+#ifdef HAVE_LIBSSH
+ gchar *msg;
+ RemminaMessagePanel *mp;
+ RemminaSSHTunnel *tunnel;
+
+ if (!remmina_file_get_int(gp->priv->remmina_file, "ssh_tunnel_enabled", FALSE)) {
+ dest = g_strdup_printf("[%s]:%i", srv_host, srv_port);
+ g_free(srv_host);
+ g_free(ssh_tunnel_host);
+ return dest;
+ }
+
+ tunnel = remmina_protocol_widget_init_tunnel(gp);
+ if (!tunnel) {
+ g_free(srv_host);
+ g_free(ssh_tunnel_host);
+ REMMINA_DEBUG("remmina_protocol_widget_init_tunnel failed with error is %s",
+ remmina_protocol_widget_get_error_message(gp));
+ return NULL;
+ }
+
+ // TRANSLATORS: “%s” is a placeholder for an hostname or an IP address.
+ msg = g_strdup_printf(_("Connecting to “%s” via SSH…"), server);
+ mp = remmina_protocol_widget_mpprogress(gp->cnnobj, msg, cancel_start_direct_tunnel_cb, NULL);
+ g_free(msg);
+
+ if (remmina_file_get_int(gp->priv->remmina_file, "ssh_tunnel_loopback", FALSE)) {
+ g_free(srv_host);
+ g_free(ssh_tunnel_host);
+ ssh_tunnel_host = NULL;
+ srv_host = g_strdup("127.0.0.1");
+ }
+
+ REMMINA_DEBUG("Starting tunnel to: %s, port: %d", ssh_tunnel_host, ssh_tunnel_port);
+ if (!remmina_ssh_tunnel_open(tunnel, srv_host, srv_port, remmina_pref.sshtunnel_port)) {
+ g_free(srv_host);
+ g_free(ssh_tunnel_host);
+ remmina_protocol_widget_set_error(gp, REMMINA_SSH(tunnel)->error);
+ remmina_ssh_tunnel_free(tunnel);
+ return NULL;
+ }
+ g_free(srv_host);
+ g_free(ssh_tunnel_host);
+
+ remmina_protocol_widget_mpdestroy(gp->cnnobj, mp);
+
+ tunnel->destroy_func = remmina_protocol_widget_tunnel_destroy;
+ tunnel->destroy_func_callback_data = (gpointer)gp;
+
+ g_ptr_array_add(gp->priv->ssh_tunnels, tunnel);
+
+ return g_strdup_printf("127.0.0.1:%i", remmina_pref.sshtunnel_port);
+
+#else
+
+ dest = g_strdup_printf("[%s]:%i", srv_host, srv_port);
+ g_free(srv_host);
+ g_free(ssh_tunnel_host);
+ return dest;
+
+#endif
+}
+
+#ifdef HAVE_LIBSSH
+static void cancel_start_reverse_tunnel_cb(void *cbdata, int btn)
+{
+ printf("Remmina: Cancelling start_reverse_tunnel is not implemented\n");
+}
+#endif
+
+
+gboolean remmina_protocol_widget_start_reverse_tunnel(RemminaProtocolWidget *gp, gint local_port)
+{
+ TRACE_CALL(__func__);
+#ifdef HAVE_LIBSSH
+ gchar *msg;
+ RemminaMessagePanel *mp;
+ RemminaSSHTunnel *tunnel;
+
+ if (!remmina_file_get_int(gp->priv->remmina_file, "ssh_tunnel_enabled", FALSE))
+ return TRUE;
+
+ if (!(tunnel = remmina_protocol_widget_init_tunnel(gp)))
+ return FALSE;
+
+ // TRANSLATORS: “%i” is a placeholder for a TCP port number.
+ msg = g_strdup_printf(_("Awaiting incoming SSH connection on port %i…"), remmina_file_get_int(gp->priv->remmina_file, "listenport", 0));
+ mp = remmina_protocol_widget_mpprogress(gp->cnnobj, msg, cancel_start_reverse_tunnel_cb, NULL);
+ g_free(msg);
+
+ if (!remmina_ssh_tunnel_reverse(tunnel, remmina_file_get_int(gp->priv->remmina_file, "listenport", 0), local_port)) {
+ remmina_ssh_tunnel_free(tunnel);
+ remmina_protocol_widget_set_error(gp, REMMINA_SSH(tunnel)->error);
+ return FALSE;
+ }
+ remmina_protocol_widget_mpdestroy(gp->cnnobj, mp);
+ g_ptr_array_add(gp->priv->ssh_tunnels, tunnel);
+#endif
+
+ return TRUE;
+}
+
+gboolean remmina_protocol_widget_ssh_exec(RemminaProtocolWidget *gp, gboolean wait, const gchar *fmt, ...)
+{
+ TRACE_CALL(__func__);
+#ifdef HAVE_LIBSSH
+ RemminaSSHTunnel *tunnel;
+ ssh_channel channel;
+ gint status;
+ gboolean ret = FALSE;
+ gchar *cmd, *ptr;
+ va_list args;
+
+ if (gp->priv->ssh_tunnels->len < 1)
+ return FALSE;
+
+ tunnel = (RemminaSSHTunnel *)gp->priv->ssh_tunnels->pdata[0];
+
+ 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:
+ // TRANSLATORS: “%s” is a place holder for a unix command path.
+ remmina_ssh_set_application_error(REMMINA_SSH(tunnel),
+ _("The “%s” command is not available on the SSH server."), cmd);
+ break;
+ default:
+ // TRANSLATORS: “%s” is a place holder for a unix command path. “%i” is a placeholder for an error code number.
+ remmina_ssh_set_application_error(REMMINA_SSH(tunnel),
+ _("Could not run the “%s” command on the SSH server (status = %i)."), cmd, status);
+ break;
+ }
+ } else {
+ ret = TRUE;
+ }
+ } else {
+ // TRANSLATORS: %s is a placeholder for an error message
+ remmina_ssh_set_error(REMMINA_SSH(tunnel), _("Could not run 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_xport_tunnel_init_callback(RemminaSSHTunnel *tunnel, gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp = REMMINA_PROTOCOL_WIDGET(data);
+ gchar *server;
+ gint port;
+ gboolean ret;
+
+ REMMINA_DEBUG("Calling remmina_public_get_server_port");
+ 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_xport_tunnel_connect_callback(RemminaSSHTunnel *tunnel, gpointer data)
+{
+ TRACE_CALL(__func__);
+ return TRUE;
+}
+
+static gboolean remmina_protocol_widget_xport_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
+#ifdef HAVE_LIBSSH
+static void cancel_connect_xport_cb(void *cbdata, int btn)
+{
+ printf("Remmina: Cancelling an XPort connection is not implemented\n");
+}
+#endif
+gboolean remmina_protocol_widget_start_xport_tunnel(RemminaProtocolWidget *gp, RemminaXPortTunnelInitFunc init_func)
+{
+ TRACE_CALL(__func__);
+#ifdef HAVE_LIBSSH
+ gboolean bindlocalhost;
+ gchar *server;
+ gchar *msg;
+ RemminaMessagePanel *mp;
+ RemminaSSHTunnel *tunnel;
+
+ if (!(tunnel = remmina_protocol_widget_init_tunnel(gp))) return FALSE;
+
+ // TRANSLATORS: “%s” is a placeholder for a hostname or IP address.
+ msg = g_strdup_printf(_("Connecting to %s via SSH…"), remmina_file_get_string(gp->priv->remmina_file, "server"));
+ mp = remmina_protocol_widget_mpprogress(gp->cnnobj, msg, cancel_connect_xport_cb, NULL);
+ g_free(msg);
+
+ gp->priv->init_func = init_func;
+ tunnel->init_func = remmina_protocol_widget_xport_tunnel_init_callback;
+ tunnel->connect_func = remmina_protocol_widget_xport_tunnel_connect_callback;
+ tunnel->disconnect_func = remmina_protocol_widget_xport_tunnel_disconnect_callback;
+ tunnel->callback_data = gp;
+
+ REMMINA_DEBUG("Calling remmina_public_get_server_port");
+ remmina_public_get_server_port(remmina_file_get_string(gp->priv->remmina_file, "server"), 0, &server, NULL);
+ bindlocalhost = (g_strcmp0(REMMINA_SSH(tunnel)->server, server) == 0);
+ g_free(server);
+
+ if (!remmina_ssh_tunnel_xport(tunnel, bindlocalhost)) {
+ remmina_protocol_widget_set_error(gp, "Could not open channel, %s",
+ ssh_get_error(REMMINA_SSH(tunnel)->session));
+ remmina_ssh_tunnel_free(tunnel);
+ return FALSE;
+ }
+
+ remmina_protocol_widget_mpdestroy(gp->cnnobj, mp);
+ g_ptr_array_add(gp->priv->ssh_tunnels, tunnel);
+
+ return TRUE;
+
+#else
+ return FALSE;
+#endif
+}
+
+void remmina_protocol_widget_set_display(RemminaProtocolWidget *gp, gint display)
+{
+ TRACE_CALL(__func__);
+#ifdef HAVE_LIBSSH
+ RemminaSSHTunnel *tunnel;
+ if (gp->priv->ssh_tunnels->len < 1)
+ return;
+ tunnel = (RemminaSSHTunnel *)gp->priv->ssh_tunnels->pdata[0];
+ if (tunnel->localdisplay) g_free(tunnel->localdisplay);
+ tunnel->localdisplay = g_strdup_printf("unix:%i", display);
+#endif
+}
+
+gint remmina_protocol_widget_get_profile_remote_width(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ /* Returns the width of remote desktop as chosen by the user profile */
+ return gp->priv->profile_remote_width;
+}
+
+gint remmina_protocol_widget_get_multimon(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ /* Returns ehenever multi monitor is enabled (1) */
+ gp->priv->multimon = remmina_file_get_int(gp->priv->remmina_file, "multimon", -1);
+ REMMINA_DEBUG("Multi monitor is set to %d", gp->priv->multimon);
+ return gp->priv->multimon;
+}
+
+gint remmina_protocol_widget_get_profile_remote_height(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ /* Returns the height of remote desktop as chosen by the user profile */
+ return gp->priv->profile_remote_height;
+}
+
+const gchar* remmina_protocol_widget_get_name(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ return gp ? gp->plugin ? gp->plugin->name : NULL : NULL;
+}
+
+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;
+}
+
+const 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;
+}
+
+struct remmina_protocol_widget_dialog_mt_data_t {
+ /* Input data */
+ RemminaProtocolWidget * gp;
+ gchar * title;
+ gchar * default_username;
+ gchar * default_password;
+ gchar * default_domain;
+ gchar * strpasswordlabel;
+ enum panel_type dtype;
+ RemminaMessagePanelFlags pflags;
+ gboolean called_from_subthread;
+ /* Running status */
+ pthread_mutex_t pt_mutex;
+ pthread_cond_t pt_cond;
+ /* Output/retval */
+ int rcbutton;
+};
+
+static void authpanel_mt_cb(void *user_data, int button)
+{
+ struct remmina_protocol_widget_dialog_mt_data_t *d = (struct remmina_protocol_widget_dialog_mt_data_t *)user_data;
+
+ d->rcbutton = button;
+ if (button == GTK_RESPONSE_OK) {
+ if (d->dtype == RPWDT_AUTH) {
+ d->gp->priv->password = remmina_message_panel_field_get_string(d->gp->priv->auth_message_panel, REMMINA_MESSAGE_PANEL_PASSWORD);
+ d->gp->priv->username = remmina_message_panel_field_get_string(d->gp->priv->auth_message_panel, REMMINA_MESSAGE_PANEL_USERNAME);
+ d->gp->priv->domain = remmina_message_panel_field_get_string(d->gp->priv->auth_message_panel, REMMINA_MESSAGE_PANEL_DOMAIN);
+ d->gp->priv->save_password = remmina_message_panel_field_get_switch_state(d->gp->priv->auth_message_panel, REMMINA_MESSAGE_PANEL_FLAG_SAVEPASSWORD);
+ } else if (d->dtype == RPWDT_AUTHX509) {
+ d->gp->priv->cacert = remmina_message_panel_field_get_filename(d->gp->priv->auth_message_panel, REMMINA_MESSAGE_PANEL_CACERTFILE);
+ d->gp->priv->cacrl = remmina_message_panel_field_get_filename(d->gp->priv->auth_message_panel, REMMINA_MESSAGE_PANEL_CACRLFILE);
+ d->gp->priv->clientcert = remmina_message_panel_field_get_filename(d->gp->priv->auth_message_panel, REMMINA_MESSAGE_PANEL_CLIENTCERTFILE);
+ d->gp->priv->clientkey = remmina_message_panel_field_get_filename(d->gp->priv->auth_message_panel, REMMINA_MESSAGE_PANEL_CLIENTKEYFILE);
+ }
+ }
+
+ if (d->called_from_subthread) {
+ /* Hide and destroy message panel, we can do it now because we are on the main thread */
+ rco_destroy_message_panel(d->gp->cnnobj, d->gp->priv->auth_message_panel);
+
+ /* Awake the locked subthread, when called from subthread */
+ pthread_mutex_lock(&d->pt_mutex);
+ pthread_cond_signal(&d->pt_cond);
+ pthread_mutex_unlock(&d->pt_mutex);
+ } else {
+ /* Signal completion, when called from main thread. Message panel will be destroyed by the caller */
+ remmina_message_panel_response(d->gp->priv->auth_message_panel, button);
+ }
+}
+
+static gboolean remmina_protocol_widget_dialog_mt_setup(gpointer user_data)
+{
+ struct remmina_protocol_widget_dialog_mt_data_t *d = (struct remmina_protocol_widget_dialog_mt_data_t *)user_data;
+
+ RemminaFile *remminafile = d->gp->priv->remmina_file;
+ RemminaMessagePanel *mp;
+ const gchar *s;
+
+ if (d->gp->cnnobj == NULL)
+ return FALSE;
+
+ mp = remmina_message_panel_new();
+
+ if (d->dtype == RPWDT_AUTH) {
+ if (d->pflags & REMMINA_MESSAGE_PANEL_FLAG_USERNAME) {
+ remmina_message_panel_field_set_string(mp, REMMINA_MESSAGE_PANEL_USERNAME, d->default_username);
+ }
+ remmina_message_panel_setup_auth(mp, authpanel_mt_cb, d, d->title, d->strpasswordlabel, d->pflags);
+ remmina_message_panel_field_set_string(mp, REMMINA_MESSAGE_PANEL_USERNAME, d->default_username);
+ if (d->pflags & REMMINA_MESSAGE_PANEL_FLAG_DOMAIN)
+ remmina_message_panel_field_set_string(mp, REMMINA_MESSAGE_PANEL_DOMAIN, d->default_domain);
+ remmina_message_panel_field_set_string(mp, REMMINA_MESSAGE_PANEL_PASSWORD, d->default_password);
+ if (d->pflags & REMMINA_MESSAGE_PANEL_FLAG_SAVEPASSWORD)
+ remmina_message_panel_field_set_switch(mp, REMMINA_MESSAGE_PANEL_FLAG_SAVEPASSWORD, (d->default_password == NULL || d->default_password[0] == 0) ? FALSE: TRUE);
+ } else if (d->dtype == RPWDT_QUESTIONYESNO) {
+ remmina_message_panel_setup_question(mp, d->title, authpanel_mt_cb, d);
+ } else if (d->dtype == RPWDT_AUTHX509) {
+ remmina_message_panel_setup_auth_x509(mp, authpanel_mt_cb, d);
+ if ((s = remmina_file_get_string(remminafile, "cacert")) != NULL)
+ remmina_message_panel_field_set_filename(mp, REMMINA_MESSAGE_PANEL_CACERTFILE, s);
+ if ((s = remmina_file_get_string(remminafile, "cacrl")) != NULL)
+ remmina_message_panel_field_set_filename(mp, REMMINA_MESSAGE_PANEL_CACRLFILE, s);
+ if ((s = remmina_file_get_string(remminafile, "clientcert")) != NULL)
+ remmina_message_panel_field_set_filename(mp, REMMINA_MESSAGE_PANEL_CLIENTCERTFILE, s);
+ if ((s = remmina_file_get_string(remminafile, "clientkey")) != NULL)
+ remmina_message_panel_field_set_filename(mp, REMMINA_MESSAGE_PANEL_CLIENTKEYFILE, s);
+ }
+
+ d->gp->priv->auth_message_panel = mp;
+ rco_show_message_panel(d->gp->cnnobj, mp);
+
+ return FALSE;
+}
+
+typedef struct {
+ RemminaMessagePanel * mp;
+ GMainLoop * loop;
+ gint response;
+ gboolean destroyed;
+} MpRunInfo;
+
+static void shutdown_loop(MpRunInfo *mpri)
+{
+ if (g_main_loop_is_running(mpri->loop))
+ g_main_loop_quit(mpri->loop);
+}
+
+static void run_response_handler(RemminaMessagePanel *mp, gint response_id, gpointer data)
+{
+ MpRunInfo *mpri = (MpRunInfo *)data;
+
+ mpri->response = response_id;
+ shutdown_loop(mpri);
+}
+
+static void run_unmap_handler(RemminaMessagePanel *mp, gpointer data)
+{
+ MpRunInfo *mpri = (MpRunInfo *)data;
+
+ mpri->response = GTK_RESPONSE_CANCEL;
+ shutdown_loop(mpri);
+}
+
+static void run_destroy_handler(RemminaMessagePanel *mp, gpointer data)
+{
+ MpRunInfo *mpri = (MpRunInfo *)data;
+
+ mpri->destroyed = TRUE;
+ mpri->response = GTK_RESPONSE_CANCEL;
+ shutdown_loop(mpri);
+}
+
+static int remmina_protocol_widget_dialog(enum panel_type dtype, RemminaProtocolWidget *gp, RemminaMessagePanelFlags pflags,
+ const gchar *title, const gchar *default_username, const gchar *default_password, const gchar *default_domain,
+ const gchar *strpasswordlabel)
+{
+ TRACE_CALL(__func__);
+
+ struct remmina_protocol_widget_dialog_mt_data_t *d = (struct remmina_protocol_widget_dialog_mt_data_t *)g_malloc(sizeof(struct remmina_protocol_widget_dialog_mt_data_t));
+ int rcbutton;
+
+ d->gp = gp;
+ d->pflags = pflags;
+ d->dtype = dtype;
+ d->title = g_strdup(title);
+ d->strpasswordlabel = g_strdup(strpasswordlabel);
+ d->default_username = g_strdup(default_username);
+ d->default_password = g_strdup(default_password);
+ d->default_domain = g_strdup(default_domain);
+ d->called_from_subthread = FALSE;
+
+ if (remmina_masterthread_exec_is_main_thread()) {
+ /* Run the MessagePanel in main thread, in a very similar way of gtk_dialog_run() */
+ MpRunInfo mpri = { NULL, NULL, GTK_RESPONSE_CANCEL, FALSE };
+
+ gulong unmap_handler;
+ gulong destroy_handler;
+ gulong response_handler;
+
+ remmina_protocol_widget_dialog_mt_setup(d);
+
+ mpri.mp = d->gp->priv->auth_message_panel;
+
+ if (!gtk_widget_get_visible(GTK_WIDGET(mpri.mp)))
+ gtk_widget_show(GTK_WIDGET(mpri.mp));
+ response_handler = g_signal_connect(mpri.mp, "response", G_CALLBACK(run_response_handler), &mpri);
+ unmap_handler = g_signal_connect(mpri.mp, "unmap", G_CALLBACK(run_unmap_handler), &mpri);
+ destroy_handler = g_signal_connect(mpri.mp, "destroy", G_CALLBACK(run_destroy_handler), &mpri);
+
+ g_object_ref(mpri.mp);
+
+ mpri.loop = g_main_loop_new(NULL, FALSE);
+ g_main_loop_run(mpri.loop);
+ g_main_loop_unref(mpri.loop);
+
+ if (!mpri.destroyed) {
+ g_signal_handler_disconnect(mpri.mp, response_handler);
+ g_signal_handler_disconnect(mpri.mp, destroy_handler);
+ g_signal_handler_disconnect(mpri.mp, unmap_handler);
+ }
+ g_object_unref(mpri.mp);
+
+ rco_destroy_message_panel(d->gp->cnnobj, d->gp->priv->auth_message_panel);
+
+ rcbutton = mpri.response;
+ } else {
+ d->called_from_subthread = TRUE;
+ // pthread_cleanup_push(ptcleanup, (void*)d);
+ pthread_cond_init(&d->pt_cond, NULL);
+ pthread_mutex_init(&d->pt_mutex, NULL);
+ g_idle_add(remmina_protocol_widget_dialog_mt_setup, d);
+ pthread_mutex_lock(&d->pt_mutex);
+ 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);
+
+ rcbutton = d->rcbutton;
+ }
+
+ g_free(d->title);
+ g_free(d->strpasswordlabel);
+ g_free(d->default_username);
+ g_free(d->default_password);
+ g_free(d->default_domain);
+ g_free(d);
+ return rcbutton;
+}
+
+gint remmina_protocol_widget_panel_question_yesno(RemminaProtocolWidget *gp, const char *msg)
+{
+ return remmina_protocol_widget_dialog(RPWDT_QUESTIONYESNO, gp, 0, msg, NULL, NULL, NULL, NULL);
+}
+
+gint remmina_protocol_widget_panel_auth(RemminaProtocolWidget *gp, RemminaMessagePanelFlags pflags,
+ const gchar *title, const gchar *default_username, const gchar *default_password, const gchar *default_domain, const gchar *password_prompt)
+{
+ TRACE_CALL(__func__);
+ return remmina_protocol_widget_dialog(RPWDT_AUTH, gp, pflags, title, default_username,
+ default_password, default_domain, password_prompt == NULL ? _("Password") : password_prompt);
+}
+
+gint remmina_protocol_widget_panel_authuserpwd_ssh_tunnel(RemminaProtocolWidget *gp, gboolean want_domain, gboolean allow_password_saving)
+{
+ TRACE_CALL(__func__);
+ unsigned pflags;
+ RemminaFile *remminafile = gp->priv->remmina_file;
+ const gchar *username, *password;
+
+ pflags = REMMINA_MESSAGE_PANEL_FLAG_USERNAME;
+ if (remmina_file_get_filename(remminafile) != NULL &&
+ !remminafile->prevent_saving && allow_password_saving)
+ pflags |= REMMINA_MESSAGE_PANEL_FLAG_SAVEPASSWORD;
+
+ username = remmina_file_get_string(remminafile, "ssh_tunnel_username");
+ password = remmina_file_get_string(remminafile, "ssh_tunnel_password");
+
+ return remmina_protocol_widget_dialog(RPWDT_AUTH, gp, pflags, _("Type in SSH username and password."), username,
+ password, NULL, _("Password"));
+}
+
+/*
+ * gint remmina_protocol_widget_panel_authpwd(RemminaProtocolWidget* gp, RemminaAuthpwdType authpwd_type, gboolean allow_password_saving)
+ * {
+ * TRACE_CALL(__func__);
+ * unsigned pflags;
+ * RemminaFile* remminafile = gp->priv->remmina_file;
+ * char *password_prompt;
+ * int rc;
+ *
+ * pflags = 0;
+ * if (remmina_file_get_filename(remminafile) != NULL &&
+ * !remminafile->prevent_saving && allow_password_saving)
+ * pflags |= REMMINA_MESSAGE_PANEL_FLAG_SAVEPASSWORD;
+ *
+ * switch (authpwd_type) {
+ * case REMMINA_AUTHPWD_TYPE_PROTOCOL:
+ * password_prompt = g_strdup_printf(_("%s password"), remmina_file_get_string(remminafile, "protocol"));
+ * break;
+ * case REMMINA_AUTHPWD_TYPE_SSH_PWD:
+ * password_prompt = g_strdup(_("SSH password"));
+ * break;
+ * case REMMINA_AUTHPWD_TYPE_SSH_PRIVKEY:
+ * password_prompt = g_strdup(_("SSH private key passphrase"));
+ * break;
+ * default:
+ * password_prompt = g_strdup(_("Password"));
+ * break;
+ * }
+ *
+ * rc = remmina_protocol_widget_dialog(RPWDT_AUTH, gp, pflags, password_prompt);
+ * g_free(password_prompt);
+ * return rc;
+ *
+ * }
+ */
+gint remmina_protocol_widget_panel_authx509(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+
+ return remmina_protocol_widget_dialog(RPWDT_AUTHX509, gp, 0, NULL, NULL, NULL, NULL, NULL);
+}
+
+
+gint remmina_protocol_widget_panel_new_certificate(RemminaProtocolWidget *gp, const gchar *subject, const gchar *issuer, const gchar *fingerprint)
+{
+ TRACE_CALL(__func__);
+ gchar *s;
+ int rc;
+
+ if (remmina_pref_get_boolean("trust_all")) {
+ /* For compatibility with plugin API: The plugin expects GTK_RESPONSE_OK when user confirms new cert */
+ remmina_public_send_notification("remmina-security-trust-all-id", _("Fingerprint automatically accepted"), fingerprint);
+ rc = GTK_RESPONSE_OK;
+ return rc;
+ }
+ // For markup see https://developer.gnome.org/pygtk/stable/pango-markup-language.html
+ s = g_strdup_printf(
+ "<big>%s</big>\n\n%s %s\n%s %s\n%s %s\n\n<big>%s</big>",
+ // TRANSLATORS: The user is asked to verify a new SSL certificate.
+ _("Certificate details:"),
+ // TRANSLATORS: An SSL certificate subject is usually the remote server the user connect to.
+ _("Subject:"), subject,
+ // TRANSLATORS: The name or email of the entity that have issued the SSL certificate
+ _("Issuer:"), issuer,
+ // TRANSLATORS: An SSL certificate fingerprint, is a hash of a certificate calculated on all certificate's data and its signature.
+ _("Fingerprint:"), fingerprint,
+ // TRANSLATORS: The user is asked to accept or refuse a new SSL certificate.
+ _("Accept certificate?"));
+ rc = remmina_protocol_widget_dialog(RPWDT_QUESTIONYESNO, gp, 0, s, NULL, NULL, NULL, NULL);
+ g_free(s);
+
+ /* For compatibility with plugin API: the plugin expects GTK_RESPONSE_OK when user confirms new cert */
+ return rc == GTK_RESPONSE_YES ? GTK_RESPONSE_OK : rc;
+}
+
+gint remmina_protocol_widget_panel_changed_certificate(RemminaProtocolWidget *gp, const gchar *subject, const gchar *issuer, const gchar *new_fingerprint, const gchar *old_fingerprint)
+{
+ TRACE_CALL(__func__);
+ gchar *s;
+ int rc;
+
+ if (remmina_pref_get_boolean("trust_all")) {
+ /* For compatibility with plugin API: The plugin expects GTK_RESPONSE_OK when user confirms new cert */
+ remmina_public_send_notification("remmina-security-trust-all-id", _("Fingerprint automatically accepted"), new_fingerprint);
+ rc = GTK_RESPONSE_OK;
+ return rc;
+ }
+ // For markup see https://developer.gnome.org/pygtk/stable/pango-markup-language.html
+ s = g_strdup_printf(
+ "<big>%s</big>\n\n%s %s\n%s %s\n%s %s\n%s %s\n\n<big>%s</big>",
+ // TRANSLATORS: The user is asked to verify a new SSL certificate.
+ _("The certificate changed! Details:"),
+ // TRANSLATORS: An SSL certificate subject is usually the remote server the user connect to.
+ _("Subject:"), subject,
+ // TRANSLATORS: The name or email of the entity that have issued the SSL certificate
+ _("Issuer:"), issuer,
+ // TRANSLATORS: An SSL certificate fingerprint, is a hash of a certificate calculated on all certificate's data and its signature.
+ _("Old fingerprint:"), old_fingerprint,
+ // TRANSLATORS: An SSL certificate fingerprint, is a hash of a certificate calculated on all certificate's data and its signature.
+ _("New fingerprint:"), new_fingerprint,
+ // TRANSLATORS: The user is asked to accept or refuse a new SSL certificate.
+ _("Accept changed certificate?"));
+ rc = remmina_protocol_widget_dialog(RPWDT_QUESTIONYESNO, gp, 0, s, NULL, NULL, NULL, NULL);
+ g_free(s);
+
+ /* For compatibility with plugin API: The plugin expects GTK_RESPONSE_OK when user confirms new cert */
+ return rc == GTK_RESPONSE_YES ? GTK_RESPONSE_OK : rc;
+}
+
+gchar *remmina_protocol_widget_get_username(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ return g_strdup(gp->priv->username);
+}
+
+gchar *remmina_protocol_widget_get_password(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ return g_strdup(gp->priv->password);
+}
+
+gchar *remmina_protocol_widget_get_domain(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ return g_strdup(gp->priv->domain);
+}
+
+gboolean remmina_protocol_widget_get_savepassword(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ return gp->priv->save_password;
+}
+
+gchar *remmina_protocol_widget_get_cacert(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ gchar *s;
+
+ s = gp->priv->cacert;
+ return s && s[0] ? g_strdup(s) : NULL;
+}
+
+gchar *remmina_protocol_widget_get_cacrl(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ gchar *s;
+
+ s = gp->priv->cacrl;
+ return s && s[0] ? g_strdup(s) : NULL;
+}
+
+gchar *remmina_protocol_widget_get_clientcert(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ gchar *s;
+
+ s = gp->priv->clientcert;
+ return s && s[0] ? g_strdup(s) : NULL;
+}
+
+gchar *remmina_protocol_widget_get_clientkey(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ gchar *s;
+
+ s = gp->priv->clientkey;
+ return s && s[0] ? g_strdup(s) : NULL;
+}
+
+void remmina_protocol_widget_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 username and certificates if any; save the password if it’s requested to do so */
+ s = gp->priv->username;
+ if (s && s[0]) {
+ remmina_file_set_string(remminafile, "username", s);
+ save = TRUE;
+ }
+ s = gp->priv->cacert;
+ if (s && s[0]) {
+ remmina_file_set_string(remminafile, "cacert", s);
+ save = TRUE;
+ }
+ s = gp->priv->cacrl;
+ if (s && s[0]) {
+ remmina_file_set_string(remminafile, "cacrl", s);
+ save = TRUE;
+ }
+ s = gp->priv->clientcert;
+ if (s && s[0]) {
+ remmina_file_set_string(remminafile, "clientcert", s);
+ save = TRUE;
+ }
+ s = gp->priv->clientkey;
+ if (s && s[0]) {
+ remmina_file_set_string(remminafile, "clientkey", s);
+ save = TRUE;
+ }
+ if (gp->priv->save_password) {
+ remmina_file_set_string(remminafile, "password", gp->priv->password);
+ save = TRUE;
+ }
+ if (save)
+ remmina_file_save(remminafile);
+}
+
+
+void remmina_protocol_widget_panel_show_listen(RemminaProtocolWidget *gp, gint port)
+{
+ TRACE_CALL(__func__);
+ RemminaMessagePanel *mp;
+ gchar *s;
+
+ 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_PANELSHOWLISTEN;
+ d->p.protocolwidget_panelshowlisten.gp = gp;
+ d->p.protocolwidget_panelshowlisten.port = port;
+ remmina_masterthread_exec_and_wait(d);
+ g_free(d);
+ return;
+ }
+
+ mp = remmina_message_panel_new();
+ s = g_strdup_printf(
+ // TRANSLATORS: “%i” is a placeholder for a port number. “%s” is a placeholder for a protocol name (VNC).
+ _("Listening on port %i for an incoming %s connection…"), port,
+ remmina_file_get_string(gp->priv->remmina_file, "protocol"));
+ remmina_message_panel_setup_progress(mp, s, NULL, NULL);
+ g_free(s);
+ gp->priv->listen_message_panel = mp;
+ rco_show_message_panel(gp->cnnobj, mp);
+}
+
+void remmina_protocol_widget_panel_show_retry(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaMessagePanel *mp;
+
+ 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_MPSHOWRETRY;
+ d->p.protocolwidget_mpshowretry.gp = gp;
+ remmina_masterthread_exec_and_wait(d);
+ g_free(d);
+ return;
+ }
+
+ mp = remmina_message_panel_new();
+ remmina_message_panel_setup_progress(mp, _("Could not authenticate, attempting reconnection…"), NULL, NULL);
+ gp->priv->retry_message_panel = mp;
+ rco_show_message_panel(gp->cnnobj, mp);
+}
+
+void remmina_protocol_widget_panel_show(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ printf("Remmina: The %s function is not implemented, and is left here only for plugin API compatibility.\n", __func__);
+}
+
+void remmina_protocol_widget_panel_hide(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ printf("Remmina: The %s function is not implemented, and is left here only for plugin API compatibility.\n", __func__);
+}
+
+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));
+ }
+}
+
+void remmina_protocol_widget_setup(RemminaProtocolWidget *gp, RemminaFile *remminafile, RemminaConnectionObject *cnnobj)
+{
+ RemminaProtocolPlugin *plugin;
+
+ gp->priv->remmina_file = remminafile;
+ gp->cnnobj = cnnobj;
+
+ /* 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) {
+ // TRANSLATORS: “%s” is a placeholder for a protocol name, like “RDP”.
+ remmina_protocol_widget_set_error(gp, _("Install the %s protocol plugin first."),
+ remmina_file_get_string(remminafile, "protocol"));
+ gp->priv->plugin = NULL;
+ return;
+ }
+ gp->priv->plugin = plugin;
+ gp->plugin = plugin;
+
+ gp->priv->scalemode = remmina_file_get_int(gp->priv->remmina_file, "scale", FALSE);
+ gp->priv->scaler_expand = remmina_file_get_int(gp->priv->remmina_file, "scaler_expand", FALSE);
+}
+
+GtkWindow *remmina_protocol_widget_get_gtkwindow(RemminaProtocolWidget *gp)
+{
+ return rcw_get_gtkwindow(gp->cnnobj);
+}
+
+GtkWidget *remmina_protocol_widget_gtkviewport(RemminaProtocolWidget *gp)
+{
+ return rcw_get_gtkviewport(gp->cnnobj);
+}
+
+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);
+ REMMINA_DEBUG("Sending keyval: %u, hardware_keycode: %u", event.keyval, 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)
+{
+ TRACE_CALL(__func__);
+ GdkRectangle rect;
+ gint w, h;
+ gint wfile, hfile;
+ RemminaProtocolWidgetResolutionMode res_mode;
+ RemminaScaleMode scalemode;
+
+ rco_get_monitor_geometry(gp->cnnobj, &rect);
+
+ /* Integrity check: check that we have a cnnwin visible and get t */
+
+ res_mode = remmina_file_get_int(gp->priv->remmina_file, "resolution_mode", RES_INVALID);
+ scalemode = remmina_file_get_int(gp->priv->remmina_file, "scale", REMMINA_PROTOCOL_WIDGET_SCALE_MODE_NONE);
+ wfile = remmina_file_get_int(gp->priv->remmina_file, "resolution_width", -1);
+ hfile = remmina_file_get_int(gp->priv->remmina_file, "resolution_height", -1);
+
+ /* If resolution_mode is non-existent (-1), then we try to calculate it
+ * as we did before having resolution_mode */
+ if (res_mode == RES_INVALID) {
+ if (wfile <= 0 || hfile <= 0)
+ res_mode = RES_USE_INITIAL_WINDOW_SIZE;
+ else
+ res_mode = RES_USE_CUSTOM;
+ }
+
+ if (res_mode == RES_USE_INITIAL_WINDOW_SIZE || scalemode == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES) {
+ /* Use internal window size as remote desktop size */
+ GtkAllocation al;
+ gtk_widget_get_allocation(GTK_WIDGET(gp), &al);
+ /* use a multiple of four to mitigate scaling when remote host rounds up */
+ w = al.width - al.width % 4;
+ h = al.height - al.height % 4;
+ if (w < 10) {
+ printf("Remmina warning: %s RemminaProtocolWidget w=%d h=%d are too small, adjusting to 640x480\n", __func__, w, h);
+ w = 640;
+ h = 480;
+ }
+ /* Due to approximations while GTK calculates scaling, (w x h) may exceed our monitor geometry
+ * Adjust to fit. */
+ if (w > rect.width)
+ w = rect.width;
+ if (h > rect.height)
+ h = rect.height;
+ } else if (res_mode == RES_USE_CLIENT) {
+ w = rect.width;
+ h = rect.height;
+ } else {
+ w = wfile;
+ h = hfile;
+ }
+ gp->priv->profile_remote_width = w;
+ gp->priv->profile_remote_height = h;
+}