From 0b974403905dedf43a9f0a545edc691b10807da1 Mon Sep 17 00:00:00 2001 From: Antenore Gatta Date: Wed, 5 Jul 2023 12:32:23 +0000 Subject: Automatic doc build by remmina-ci --- public/remmina__protocol__widget_8c_source.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/remmina__protocol__widget_8c_source.html b/public/remmina__protocol__widget_8c_source.html index d7d699b37..895d05df4 100644 --- a/public/remmina__protocol__widget_8c_source.html +++ b/public/remmina__protocol__widget_8c_source.html @@ -86,7 +86,7 @@ $(document).ready(function(){initNavTree('remmina__protocol__widget_8c_source.ht
remmina_protocol_widget.c
-Go to the documentation of this file.
1 /*
2  * Remmina - The GTK+ Remote Desktop Client
3  * Copyright (C) 2009-2011 Vic Lee
4  * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
5  * Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  *
22  * In addition, as a special exception, the copyright holders give
23  * permission to link the code of portions of this program with the
24  * OpenSSL library under certain conditions as described in each
25  * individual source file, and distribute linked combinations
26  * including the two.
27  * You must obey the GNU General Public License in all respects
28  * for all of the code used other than OpenSSL. * If you modify
29  * file(s) with this exception, you may extend this exception to your
30  * version of the file(s), but you are not obligated to do so. * If you
31  * do not wish to do so, delete this exception statement from your
32  * version. * If you delete this exception statement from all source
33  * files in the program, then also delete it here.
34  *
35  */
36 
37 #include "config.h"
38 
39 #include <gtk/gtk.h>
40 #include <gtk/gtkx.h>
41 #include <glib/gi18n.h>
42 #include <gmodule.h>
43 #include <stdlib.h>
44 
45 #include "remmina_chat_window.h"
47 #include "remmina_ext_exec.h"
48 #include "remmina_plugin_manager.h"
49 #include "remmina_pref.h"
51 #include "remmina_public.h"
52 #include "remmina_ssh.h"
53 #include "remmina_log.h"
55 
56 #ifdef GDK_WINDOWING_WAYLAND
57 #include <gdk/gdkwayland.h>
58 #endif
59 
64 
65  gint width;
66  gint height;
68  gboolean scaler_expand;
69 
70  gboolean has_error;
71  gchar * error_message;
72  /* ssh_tunnels is an array of RemminaSSHTunnel*
73  * the 1st one is the "main" tunnel, other tunnels are used for example in sftp commands */
74  GPtrArray * ssh_tunnels;
76 
77  GtkWidget * chat_window;
78 
79  gboolean closed;
80 
82 
85  gint multimon;
86 
87  RemminaMessagePanel * connect_message_panel;
88  RemminaMessagePanel * listen_message_panel;
89  RemminaMessagePanel * auth_message_panel;
90  RemminaMessagePanel * retry_message_panel;
91 
92  /* Data saved from the last message_panel when the user confirm */
93  gchar * username;
94  gchar * password;
95  gchar * domain;
96  gboolean save_password;
97 
98  gchar * cacert;
99  gchar * cacrl;
100  gchar * clientcert;
101  gchar * clientkey;
102 };
103 
108 };
109 
110 G_DEFINE_TYPE(RemminaProtocolWidget, remmina_protocol_widget, GTK_TYPE_EVENT_BOX)
111 
112 enum {
113  CONNECT_SIGNAL,
114  DISCONNECT_SIGNAL,
115  DESKTOP_RESIZE_SIGNAL,
116  UPDATE_ALIGN_SIGNAL,
117  LOCK_DYNRES_SIGNAL,
118  UNLOCK_DYNRES_SIGNAL,
120 };
121 
124  const gchar * signal_name;
126 
128 { 0 };
129 
131 {
132  TRACE_CALL(__func__);
133  remmina_protocol_widget_signals[CONNECT_SIGNAL] = g_signal_new("connect", G_TYPE_FROM_CLASS(klass),
134  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaProtocolWidgetClass, connect), NULL, NULL,
135  g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
136  remmina_protocol_widget_signals[DISCONNECT_SIGNAL] = g_signal_new("disconnect", G_TYPE_FROM_CLASS(klass),
137  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaProtocolWidgetClass, disconnect), NULL, NULL,
138  g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
139  remmina_protocol_widget_signals[DESKTOP_RESIZE_SIGNAL] = g_signal_new("desktop-resize", G_TYPE_FROM_CLASS(klass),
140  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaProtocolWidgetClass, desktop_resize), NULL, NULL,
141  g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
142  remmina_protocol_widget_signals[UPDATE_ALIGN_SIGNAL] = g_signal_new("update-align", G_TYPE_FROM_CLASS(klass),
143  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaProtocolWidgetClass, update_align), NULL, NULL,
144  g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
145  remmina_protocol_widget_signals[LOCK_DYNRES_SIGNAL] = g_signal_new("lock-dynres", G_TYPE_FROM_CLASS(klass),
146  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaProtocolWidgetClass, lock_dynres), NULL, NULL,
147  g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
148  remmina_protocol_widget_signals[UNLOCK_DYNRES_SIGNAL] = g_signal_new("unlock-dynres", G_TYPE_FROM_CLASS(klass),
149  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaProtocolWidgetClass, unlock_dynres), NULL, NULL,
150  g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
151 }
152 
153 
155 {
156  TRACE_CALL(__func__);
157  int i;
158 
159  if (gp->priv && gp->priv->ssh_tunnels) {
160  for (i = 0; i < gp->priv->ssh_tunnels->len; i++) {
161 #ifdef HAVE_LIBSSH
163 #else
164  REMMINA_DEBUG("LibSSH support turned off, no need to free SSH tunnel data");
165 #endif
166  }
167  g_ptr_array_set_size(gp->priv->ssh_tunnels, 0);
168  }
169 }
170 
171 
173 {
174  TRACE_CALL(__func__);
175 
176  g_free(gp->priv->username);
177  gp->priv->username = NULL;
178 
179  g_free(gp->priv->password);
180  gp->priv->password = NULL;
181 
182  g_free(gp->priv->domain);
183  gp->priv->domain = NULL;
184 
185  g_free(gp->priv->cacert);
186  gp->priv->cacert = NULL;
187 
188  g_free(gp->priv->cacrl);
189  gp->priv->cacrl = NULL;
190 
191  g_free(gp->priv->clientcert);
192  gp->priv->clientcert = NULL;
193 
194  g_free(gp->priv->clientkey);
195  gp->priv->clientkey = NULL;
196 
197  g_free(gp->priv->features);
198  gp->priv->features = NULL;
199 
200  g_free(gp->priv->error_message);
201  gp->priv->error_message = NULL;
202 
203  g_free(gp->priv->remmina_file);
204  gp->priv->remmina_file = NULL;
205 
206  g_free(gp->priv);
207  gp->priv = NULL;
208 
210 
211  if (gp->priv && gp->priv->ssh_tunnels) {
212  g_ptr_array_free(gp->priv->ssh_tunnels, TRUE);
213  gp->priv->ssh_tunnels = NULL;
214  }
215 }
216 
218 {
219  TRACE_CALL(__func__);
220  GtkWidget *child;
221 
222  child = gtk_bin_get_child(GTK_BIN(gp));
223 
224  if (child) {
225  gtk_widget_set_can_focus(child, TRUE);
226  gtk_widget_grab_focus(child);
227  }
228 }
229 
231 {
232  TRACE_CALL(__func__);
234 
235  priv = g_new0(RemminaProtocolWidgetPriv, 1);
236  gp->priv = priv;
237  gp->priv->closed = TRUE;
238  gp->priv->ssh_tunnels = g_ptr_array_new();
239 
240  g_signal_connect(G_OBJECT(gp), "destroy", G_CALLBACK(remmina_protocol_widget_destroy), NULL);
241 }
242 
244 {
245  TRACE_CALL(__func__);
246  RemminaProtocolWidget *gp = REMMINA_PROTOCOL_WIDGET(data);
247 
248  REMMINA_DEBUG("Opening connection");
249 
251  RemminaProtocolFeature *feature;
252  gint num_plugin;
253  gint num_ssh;
254 
255  gp->priv->closed = FALSE;
256 
257  plugin = gp->priv->plugin;
258  plugin->init(gp);
259 
260  for (num_plugin = 0, feature = (RemminaProtocolFeature *)plugin->features; feature && feature->type; num_plugin++, feature++) {
261  }
262 
263  num_ssh = 0;
264 #ifdef HAVE_LIBSSH
265  if (remmina_file_get_int(gp->priv->remmina_file, "ssh_tunnel_enabled", FALSE))
266  num_ssh += 2;
267 
268 #endif
269  if (num_plugin + num_ssh == 0) {
270  gp->priv->features = NULL;
271  } else {
272  gp->priv->features = g_new0(RemminaProtocolFeature, num_plugin + num_ssh + 1);
273  feature = gp->priv->features;
274  if (plugin->features) {
275  memcpy(feature, plugin->features, sizeof(RemminaProtocolFeature) * num_plugin);
276  feature += num_plugin;
277  }
278 #ifdef HAVE_LIBSSH
279  REMMINA_DEBUG("Have SSH");
280  if (num_ssh) {
282  feature->id = REMMINA_PROTOCOL_FEATURE_TOOL_SSH;
283  feature->opt1 = _("Connect via SSH from a new terminal");
285  feature->opt2 = "utilities-terminal";
287  feature->opt3 = NULL;
289  feature++;
290 
292  feature->id = REMMINA_PROTOCOL_FEATURE_TOOL_SFTP;
293  feature->opt1 = _("Open SFTP transfer…");
295  feature->opt2 = "folder-remote";
297  feature->opt3 = NULL;
299  feature++;
300  }
302 #endif
303  }
304 
305  if (!plugin->open_connection(gp))
307 }
308 
309 static void cancel_open_connection_cb(void *cbdata, int btn)
310 {
312 
314 }
315 
317 {
318  TRACE_CALL(__func__);
319  gchar *s;
320  const gchar *name;
321  RemminaMessagePanel *mp;
322 
323  /* Exec precommand before everything else */
325  remmina_message_panel_setup_progress(mp, _("Executing external commands…"), NULL, NULL);
327 
328  remmina_ext_exec_new(gp->priv->remmina_file, "precommand");
330 
331  name = remmina_file_get_string(gp->priv->remmina_file, "name");
332  // TRANSLATORS: “%s” is a placeholder for the connection profile name
333  s = g_strdup_printf(_("Connecting to “%s”…"), (name ? name : "*"));
334 
337  g_free(s);
338  gp->priv->connect_message_panel = mp;
340 
342 }
343 
344 static gboolean conn_closed(gpointer data)
345 {
346  TRACE_CALL(__func__);
348 
349 #ifdef HAVE_LIBSSH
350  /* This will close all tunnels */
352 #endif
353  /* Exec postcommand */
354  remmina_ext_exec_new(gp->priv->remmina_file, "postcommand");
355  /* Notify listeners (usually rcw) that the connection is closed */
356  g_signal_emit_by_name(G_OBJECT(gp), "disconnect");
357  return G_SOURCE_REMOVE;
358 }
359 
361 {
362  /* Plugin told us that it closed the connection,
363  * add async event to main thread to complete our close tasks */
364  TRACE_CALL(__func__);
365  gp->priv->closed = TRUE;
366  g_idle_add(conn_closed, (gpointer)gp);
367 }
368 
369 static gboolean conn_opened(gpointer data)
370 {
371  TRACE_CALL(__func__);
373 
374 #ifdef HAVE_LIBSSH
375  if (gp->priv->ssh_tunnels) {
376  for (guint i = 0; i < gp->priv->ssh_tunnels->len; i++)
378  }
379 #endif
380  if (gp->priv->listen_message_panel) {
382  gp->priv->listen_message_panel = NULL;
383  }
384  if (gp->priv->connect_message_panel) {
386  gp->priv->connect_message_panel = NULL;
387  }
388  if (gp->priv->retry_message_panel) {
390  gp->priv->retry_message_panel = NULL;
391  }
392  g_signal_emit_by_name(G_OBJECT(gp), "connect");
393  return G_SOURCE_REMOVE;
394 }
395 
397 {
398  /* Plugin told us that it opened the connection,
399  * add async event to main thread to complete our close tasks */
400  TRACE_CALL(__func__);
401  g_idle_add(conn_opened, (gpointer)gp);
402 }
403 
404 static gboolean update_align(gpointer data)
405 {
406  TRACE_CALL(__func__);
408 
409  g_signal_emit_by_name(G_OBJECT(gp), "update-align");
410  return G_SOURCE_REMOVE;
411 }
412 
414 {
415  /* Called by the plugin to do updates on rcw */
416  TRACE_CALL(__func__);
417  g_idle_add(update_align, (gpointer)gp);
418 }
419 
420 static gboolean lock_dynres(gpointer data)
421 {
422  TRACE_CALL(__func__);
424 
425  g_signal_emit_by_name(G_OBJECT(gp), "lock-dynres");
426  return G_SOURCE_REMOVE;
427 }
428 
429 static gboolean unlock_dynres(gpointer data)
430 {
431  TRACE_CALL(__func__);
433 
434  g_signal_emit_by_name(G_OBJECT(gp), "unlock-dynres");
435  return G_SOURCE_REMOVE;
436 }
437 
439 {
440  /* Called by the plugin to do updates on rcw */
441  TRACE_CALL(__func__);
442  g_idle_add(lock_dynres, (gpointer)gp);
443 }
444 
446 {
447  /* Called by the plugin to do updates on rcw */
448  TRACE_CALL(__func__);
449  g_idle_add(unlock_dynres, (gpointer)gp);
450 }
451 
452 static gboolean desktop_resize(gpointer data)
453 {
454  TRACE_CALL(__func__);
456 
457  g_signal_emit_by_name(G_OBJECT(gp), "desktop-resize");
458  return G_SOURCE_REMOVE;
459 }
460 
462 {
463  /* Called by the plugin to do updates on rcw */
464  TRACE_CALL(__func__);
465  g_idle_add(desktop_resize, (gpointer)gp);
466 }
467 
468 
470 {
471  TRACE_CALL(__func__);
472 
473  /* kindly ask the protocol plugin to close the connection.
474  * Nothing else is done here. */
475 
476  if (!GTK_IS_WIDGET(gp))
477  return;
478 
479  if (gp->priv->chat_window) {
480  gtk_widget_destroy(gp->priv->chat_window);
481  gp->priv->chat_window = NULL;
482  }
483 
484  if (gp->priv->closed) {
485  /* Connection is already closed by the plugin, but
486  * rcw is asking to close again (usually after an error panel)
487  */
488  /* Clear the current error, or "disconnect" signal func will reshow a panel */
490  g_signal_emit_by_name(G_OBJECT(gp), "disconnect");
491  return;
492  }
493 
494  /* Ask the plugin to close, async.
495  * The plugin will emit a "disconnect" signal on gp to call our
496  * remmina_protocol_widget_on_disconnected() when done */
497  gp->priv->plugin->close_connection(gp);
498 
499  return;
500 }
501 
505 {
506  return gp->priv->plugin->send_keystrokes ? TRUE : FALSE;
507 }
508 
513 {
514  TRACE_CALL(__func__);
515  gchar *keystrokes = g_object_get_data(G_OBJECT(widget), "keystrokes");
516  guint *keyvals;
517  gint i;
518  GdkKeymap *keymap = gdk_keymap_get_for_display(gdk_display_get_default());
519  gunichar character;
520  guint keyval;
521  GdkKeymapKey *keys;
522  gint n_keys;
523 
524  /* Single keystroke replace */
525  typedef struct _KeystrokeReplace {
526  gchar * search;
527  gchar * replace;
528  guint keyval;
529  } KeystrokeReplace;
530  /* Special characters to replace */
531  KeystrokeReplace keystrokes_replaces[] =
532  {
533  { "\\n", "\n", GDK_KEY_Return },
534  { "\\t", "\t", GDK_KEY_Tab },
535  { "\\b", "\b", GDK_KEY_BackSpace },
536  { "\\e", "\e", GDK_KEY_Escape },
537  { "\\\\", "\\", GDK_KEY_backslash },
538  { NULL, NULL, 0 }
539  };
540 
541  /* Keystrokes can only be sent to plugins that accepts them */
543  /* Replace special characters */
544  for (i = 0; keystrokes_replaces[i].replace; i++) {
545  REMMINA_DEBUG("Keystrokes before replacement is \'%s\'", keystrokes);
546  keystrokes = g_strdup(remmina_public_str_replace_in_place(keystrokes,
547  keystrokes_replaces[i].search,
548  keystrokes_replaces[i].replace));
549  REMMINA_DEBUG("Keystrokes after replacement is \'%s\'", keystrokes);
550  }
551  gchar *iter = g_strdup(keystrokes);
552  keyvals = (guint *)g_malloc(strlen(keystrokes));
553  while (TRUE) {
554  /* Process each character in the keystrokes */
555  character = g_utf8_get_char_validated(iter, -1);
556  if (character == 0)
557  break;
558  keyval = gdk_unicode_to_keyval(character);
559  /* Replace all the special character with its keyval */
560  for (i = 0; keystrokes_replaces[i].replace; i++) {
561  if (character == keystrokes_replaces[i].replace[0]) {
562  keys = g_new0(GdkKeymapKey, 1);
563  keyval = keystrokes_replaces[i].keyval;
564  /* A special character was generated, no keyval lookup needed */
565  character = 0;
566  break;
567  }
568  }
569  /* Decode character if it’s not a special character */
570  if (character) {
571  /* get keyval without modifications */
572  if (!gdk_keymap_get_entries_for_keyval(keymap, keyval, &keys, &n_keys)) {
573  g_warning("keyval 0x%04x has no keycode!", keyval);
574  iter = g_utf8_find_next_char(iter, NULL);
575  continue;
576  }
577  }
578  /* Add modifier keys */
579  n_keys = 0;
580  if (keys->level & 1)
581  keyvals[n_keys++] = GDK_KEY_Shift_L;
582  if (keys->level & 2)
583  keyvals[n_keys++] = GDK_KEY_Alt_R;
584  keyvals[n_keys++] = keyval;
585  /* Send keystroke to the plugin */
586  gp->priv->plugin->send_keystrokes(gp, keyvals, n_keys);
587  g_free(keys);
588  /* Process next character in the keystrokes */
589  iter = g_utf8_find_next_char(iter, NULL);
590  }
591  g_free(keyvals);
592  }
593  g_free(keystrokes);
594  return;
595 }
596 
603 void remmina_protocol_widget_send_clip_strokes(GtkClipboard *clipboard, const gchar *clip_text, gpointer data)
604 {
605  TRACE_CALL(__func__);
606  RemminaProtocolWidget *gp = REMMINA_PROTOCOL_WIDGET(data);
607  gchar *text = g_utf8_normalize(clip_text, -1, G_NORMALIZE_DEFAULT_COMPOSE);
608  guint *keyvals;
609  gint i;
610  GdkKeymap *keymap = gdk_keymap_get_for_display(gdk_display_get_default());
611  gunichar character;
612  guint keyval;
613  GdkKeymapKey *keys;
614  gint n_keys;
615 
616  /* Single keystroke replace */
617  typedef struct _KeystrokeReplace {
618  gchar * search;
619  gchar * replace;
620  guint keyval;
621  } KeystrokeReplace;
622  /* Special characters to replace */
623  KeystrokeReplace text_replaces[] =
624  {
625  { "\\n", "\n", GDK_KEY_Return },
626  { "\\t", "\t", GDK_KEY_Tab },
627  { "\\b", "\b", GDK_KEY_BackSpace },
628  { "\\e", "\e", GDK_KEY_Escape },
629  { "\\\\", "\\", GDK_KEY_backslash },
630  { NULL, NULL, 0 }
631  };
632 
634  if (text) {
635  /* Replace special characters */
636  for (i = 0; text_replaces[i].replace; i++) {
637  REMMINA_DEBUG("Text clipboard before replacement is \'%s\'", text);
638  text = g_strdup(remmina_public_str_replace_in_place(text,
639  text_replaces[i].search,
640  text_replaces[i].replace));
641  REMMINA_DEBUG("Text clipboard after replacement is \'%s\'", text);
642  }
643  gchar *iter = g_strdup(text);
644  REMMINA_DEBUG("Iter: %s", iter),
645  keyvals = (guint *)g_malloc(strlen(text));
646  while (TRUE) {
647  /* Process each character in the keystrokes */
648  character = g_utf8_get_char_validated(iter, -1);
649  REMMINA_DEBUG("Char: U+%04" G_GINT32_FORMAT"X", character);
650  if (character == 0)
651  break;
652  keyval = gdk_unicode_to_keyval(character);
653  REMMINA_DEBUG("Keyval: %u", keyval);
654  /* Replace all the special character with its keyval */
655  for (i = 0; text_replaces[i].replace; i++) {
656  if (character == text_replaces[i].replace[0]) {
657  keys = g_new0(GdkKeymapKey, 1);
658  keyval = text_replaces[i].keyval;
659  REMMINA_DEBUG("Special Keyval: %u", keyval);
660  /* A special character was generated, no keyval lookup needed */
661  character = 0;
662  break;
663  }
664  }
665  /* Decode character if it’s not a special character */
666  if (character) {
667  /* get keyval without modifications */
668  if (!gdk_keymap_get_entries_for_keyval(keymap, keyval, &keys, &n_keys)) {
669  REMMINA_WARNING("keyval 0x%04x has no keycode!", keyval);
670  iter = g_utf8_find_next_char(iter, NULL);
671  continue;
672  }
673  }
674  /* Add modifier keys */
675  n_keys = 0;
676  if (keys->level & 1)
677  keyvals[n_keys++] = GDK_KEY_Shift_L;
678  if (keys->level & 2)
679  keyvals[n_keys++] = GDK_KEY_Alt_R;
680  /*
681  * @fixme heap buffer overflow
682  * In some cases, for example sending \t as the only sequence
683  * may lead to a buffer overflow
684  */
685  keyvals[n_keys++] = keyval;
686  /* Send keystroke to the plugin */
687  gp->priv->plugin->send_keystrokes(gp, keyvals, n_keys);
688  g_free(keys);
689  /* Process next character in the keystrokes */
690  iter = g_utf8_find_next_char(iter, NULL);
691  }
692  g_free(keyvals);
693  }
694  g_free(text);
695  }
696  return;
697 }
698 
700 {
701  TRACE_CALL(__func__);
702  GtkClipboard *clipboard;
703 
704  clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
705 
706  /* Request the contents of the clipboard, contents_received will be
707  * called when we do get the contents.
708  */
709  gtk_clipboard_request_text(clipboard,
711 }
712 
714 {
715  TRACE_CALL(__func__);
716  if (!gp->priv->plugin->get_plugin_screenshot) {
717  REMMINA_DEBUG("plugin screenshot function is not implemented, using core Remmina functionality");
718  return FALSE;
719  }
720 
721  return gp->priv->plugin->get_plugin_screenshot(gp, rpsd);
722 }
723 
725 {
726  TRACE_CALL(__func__);
727  if (!gp->priv->plugin->map_event) {
728  REMMINA_DEBUG("Map plugin function not implemented");
729  return FALSE;
730  }
731 
732  REMMINA_DEBUG("Calling plugin mapping function");
733  return gp->priv->plugin->map_event(gp);
734 }
735 
737 {
738  TRACE_CALL(__func__);
739  if (!gp->priv->plugin->get_plugin_screenshot) {
740  REMMINA_DEBUG("Unmap plugin function not implemented");
741  return FALSE;
742  }
743 
744  REMMINA_DEBUG("Calling plugin unmapping function");
745  return gp->priv->plugin->unmap_event(gp);
746 }
747 
749 {
750  TRACE_CALL(__func__);
751 
752  REMMINA_DEBUG("Emitting signals should be used from the object itself, not from another object");
753  raise(SIGINT);
754 
756  /* Allow the execution of this function from a non main thread */
758  d = (RemminaMTExecData *)g_malloc(sizeof(RemminaMTExecData));
759  d->func = FUNC_PROTOCOLWIDGET_EMIT_SIGNAL;
760  d->p.protocolwidget_emit_signal.signal_name = signal_name;
761  d->p.protocolwidget_emit_signal.gp = gp;
763  g_free(d);
764  return;
765  }
766  g_signal_emit_by_name(G_OBJECT(gp), signal_name);
767 }
768 
770 {
771  TRACE_CALL(__func__);
772  return gp->priv->features;
773 }
774 
776 {
777  TRACE_CALL(__func__);
778  const RemminaProtocolFeature *feature;
779 
780 #ifdef HAVE_LIBSSH
782  remmina_file_get_int(gp->priv->remmina_file, "ssh_tunnel_enabled", FALSE))
783  return TRUE;
784 
785 #endif
786  for (feature = gp->priv->plugin->features; feature && feature->type; feature++)
787  if (feature->type == type)
788  return TRUE;
789  return FALSE;
790 }
791 
793 {
794  TRACE_CALL(__func__);
795  return gp->priv->plugin->query_feature(gp, feature);
796 }
797 
799 {
800  TRACE_CALL(__func__);
801  const RemminaProtocolFeature *feature;
802 
803  for (feature = gp->priv->plugin->features; feature && feature->type; feature++) {
804  if (feature->type == type && (id == 0 || feature->id == id)) {
806  break;
807  }
808  }
809 }
810 
812 {
813  TRACE_CALL(__func__);
814  switch (feature->id) {
815 #ifdef HAVE_LIBSSH
816  case REMMINA_PROTOCOL_FEATURE_TOOL_SSH:
817  if (gp->priv->ssh_tunnels && gp->priv->ssh_tunnels->len > 0) {
820  (RemminaSSHTunnel *)gp->priv->ssh_tunnels->pdata[0], NULL);
821  return;
822  }
823  break;
824 
825  case REMMINA_PROTOCOL_FEATURE_TOOL_SFTP:
826  if (gp->priv->ssh_tunnels && gp->priv->ssh_tunnels->len > 0) {
829  gp->priv->ssh_tunnels->pdata[0], NULL);
830  return;
831  }
832  break;
833 #endif
834  default:
835  break;
836  }
837  gp->priv->plugin->call_feature(gp, feature);
838 }
839 
840 static gboolean remmina_protocol_widget_on_key_press(GtkWidget *widget, GdkEventKey *event, RemminaProtocolWidget *gp)
841 {
842  TRACE_CALL(__func__);
843  if (gp->priv->hostkey_func)
844  return gp->priv->hostkey_func(gp, event->keyval, FALSE);
845  return FALSE;
846 }
847 
848 static gboolean remmina_protocol_widget_on_key_release(GtkWidget *widget, GdkEventKey *event, RemminaProtocolWidget *gp)
849 {
850  TRACE_CALL(__func__);
851  if (gp->priv->hostkey_func)
852  return gp->priv->hostkey_func(gp, event->keyval, TRUE);
853 
854  return FALSE;
855 }
856 
858 {
859  TRACE_CALL(__func__);
860  g_signal_connect(G_OBJECT(widget), "key-press-event", G_CALLBACK(remmina_protocol_widget_on_key_press), gp);
861  g_signal_connect(G_OBJECT(widget), "key-release-event", G_CALLBACK(remmina_protocol_widget_on_key_release), gp);
862 }
863 
865 {
866  TRACE_CALL(__func__);
867  gp->priv->hostkey_func = func;
868 }
869 
870 RemminaMessagePanel *remmina_protocol_widget_mpprogress(RemminaConnectionObject *cnnobj, const gchar *msg, RemminaMessagePanelCallback response_callback, gpointer response_callback_data)
871 {
872  RemminaMessagePanel *mp;
873 
875  /* Allow the execution of this function from a non main thread */
877  d = (RemminaMTExecData *)g_malloc(sizeof(RemminaMTExecData));
878  d->func = FUNC_PROTOCOLWIDGET_MPPROGRESS;
879  d->p.protocolwidget_mpprogress.cnnobj = cnnobj;
880  d->p.protocolwidget_mpprogress.message = msg;
881  d->p.protocolwidget_mpprogress.response_callback = response_callback;
882  d->p.protocolwidget_mpprogress.response_callback_data = response_callback_data;
884  mp = d->p.protocolwidget_mpprogress.ret_mp;
885  g_free(d);
886  return mp;
887  }
888 
890  remmina_message_panel_setup_progress(mp, msg, response_callback, response_callback_data);
891  rco_show_message_panel(cnnobj, mp);
892  return mp;
893 }
894 
895 void remmina_protocol_widget_mpdestroy(RemminaConnectionObject *cnnobj, RemminaMessagePanel *mp)
896 {
898  /* Allow the execution of this function from a non main thread */
900  d = (RemminaMTExecData *)g_malloc(sizeof(RemminaMTExecData));
901  d->func = FUNC_PROTOCOLWIDGET_MPDESTROY;
902  d->p.protocolwidget_mpdestroy.cnnobj = cnnobj;
903  d->p.protocolwidget_mpdestroy.mp = mp;
905  g_free(d);
906  return;
907  }
908  rco_destroy_message_panel(cnnobj, mp);
909 }
910 
911 #ifdef HAVE_LIBSSH
912 static void cancel_init_tunnel_cb(void *cbdata, int btn)
913 {
914  printf("Remmina: Cancelling an opening tunnel is not implemented\n");
915 }
916 
918 {
919  TRACE_CALL(__func__);
920  RemminaSSHTunnel *tunnel;
921  gint ret;
922  gchar *msg;
923  RemminaMessagePanel *mp;
924  gboolean partial = FALSE;
925  gboolean cont = FALSE;
926 
928 
929  REMMINA_DEBUG("Creating SSH tunnel to “%s” via SSH…", REMMINA_SSH(tunnel)->server);
930  // TRANSLATORS: “%s” is a placeholder for an hostname or an IP address.
931  msg = g_strdup_printf(_("Connecting to “%s” via SSH…"), REMMINA_SSH(tunnel)->server);
932 
934  g_free(msg);
935 
936 
937 
938  while (1) {
939  if (!partial) {
940  if (!remmina_ssh_init_session(REMMINA_SSH(tunnel))) {
941  REMMINA_DEBUG("SSH Tunnel init session error: %s", REMMINA_SSH(tunnel)->error);
942  remmina_protocol_widget_set_error(gp, REMMINA_SSH(tunnel)->error);
943  // exit the loop here: OK
944  break;
945  }
946  }
947 
948  ret = remmina_ssh_auth_gui(REMMINA_SSH(tunnel), gp, gp->priv->remmina_file);
949  REMMINA_DEBUG("Tunnel auth returned %d", ret);
950  switch (ret) {
952  REMMINA_DEBUG("Authentication success");
953  break;
955  REMMINA_DEBUG("Continue with the next auth method");
956  partial = TRUE;
957  // Continue the loop: OK
958  continue;
959  break;
961  REMMINA_DEBUG("Reconnecting…");
962  if (REMMINA_SSH(tunnel)->session) {
963  ssh_disconnect(REMMINA_SSH(tunnel)->session);
964  ssh_free(REMMINA_SSH(tunnel)->session);
965  REMMINA_SSH(tunnel)->session = NULL;
966  }
967  g_free(REMMINA_SSH(tunnel)->callback);
968  // Continue the loop: OK
969  continue;
970  break;
972  REMMINA_DEBUG("Interrupted by the user");
973  // exit the loop here: OK
974  goto BREAK;
975  break;
976  default:
977  REMMINA_DEBUG("Error during the authentication: %s", REMMINA_SSH(tunnel)->error);
978  remmina_protocol_widget_set_error(gp, REMMINA_SSH(tunnel)->error);
979  // exit the loop here: OK
980  goto BREAK;
981  }
982 
983 
984  cont = TRUE;
985  break;
986  }
987 
988 #if 0
989 
990  if (!remmina_ssh_init_session(REMMINA_SSH(tunnel))) {
991  REMMINA_DEBUG("Cannot init SSH session with tunnel struct");
992  remmina_protocol_widget_set_error(gp, REMMINA_SSH(tunnel)->error);
993  remmina_ssh_tunnel_free(tunnel);
994  return NULL;
995  }
996 
997  ret = remmina_ssh_auth_gui(REMMINA_SSH(tunnel), gp, gp->priv->remmina_file);
998  REMMINA_DEBUG("Tunnel auth returned %d", ret);
999  if (ret != REMMINA_SSH_AUTH_SUCCESS) {
1000  if (ret != REMMINA_SSH_AUTH_USERCANCEL)
1001  remmina_protocol_widget_set_error(gp, REMMINA_SSH(tunnel)->error);
1002  remmina_ssh_tunnel_free(tunnel);
1003  return NULL;
1004  }
1005 
1006 #endif
1007 
1008 BREAK:
1009  if (!cont) {
1010  remmina_ssh_tunnel_free(tunnel);
1011  return NULL;
1012  }
1014 
1015  return tunnel;
1016 }
1017 #endif
1018 
1019 
1020 #ifdef HAVE_LIBSSH
1021 static void cancel_start_direct_tunnel_cb(void *cbdata, int btn)
1022 {
1023  printf("Remmina: Cancelling start_direct_tunnel is not implemented\n");
1024 }
1025 
1026 static gboolean remmina_protocol_widget_tunnel_destroy(RemminaSSHTunnel *tunnel, gpointer data)
1027 {
1028  TRACE_CALL(__func__);
1029  RemminaProtocolWidget *gp = REMMINA_PROTOCOL_WIDGET(data);
1030  guint idx = 0;
1031 
1032 #if GLIB_CHECK_VERSION(2, 54, 0)
1033  gboolean found = g_ptr_array_find(gp->priv->ssh_tunnels, tunnel, &idx);
1034 #else
1035  int i;
1036  gboolean found = FALSE;
1037  for (i = 0; i < gp->priv->ssh_tunnels->len; i++) {
1038  if ((RemminaSSHTunnel *)gp->priv->ssh_tunnels->pdata[i] == tunnel) {
1039  found = TRUE;
1040  idx = i;
1041  }
1042  }
1043 #endif
1044 
1045  printf("Tunnel %s found at idx = %d\n", found ? "yes": "not", idx);
1046 
1047  if (found) {
1048 #ifdef HAVE_LIBSSH
1049  REMMINA_DEBUG("[Tunnel with idx %u has been disconnected", idx);
1050  remmina_ssh_tunnel_free(tunnel);
1051 #endif
1052  g_ptr_array_remove(gp->priv->ssh_tunnels, tunnel);
1053  }
1054  return TRUE;
1055 }
1056 #endif
1057 
1062 gchar *remmina_protocol_widget_start_direct_tunnel(RemminaProtocolWidget *gp, gint default_port, gboolean port_plus)
1063 {
1064  TRACE_CALL(__func__);
1065  const gchar *server;
1066  const gchar *ssh_tunnel_server;
1067  gchar *ssh_tunnel_host, *srv_host, *dest;
1068  gint srv_port, ssh_tunnel_port = 0;
1069 
1070  REMMINA_DEBUG("SSH tunnel initialization…");
1071 
1072  server = remmina_file_get_string(gp->priv->remmina_file, "server");
1073  ssh_tunnel_server = remmina_file_get_string(gp->priv->remmina_file, "ssh_tunnel_server");
1074 
1075  if (!server)
1076  return g_strdup("");
1077 
1078  if (strstr(g_strdup(server), "unix:///") != NULL) {
1079  REMMINA_DEBUG("%s is a UNIX socket", server);
1080  return g_strdup(server);
1081  }
1082 
1083  REMMINA_DEBUG("Calling remmina_public_get_server_port");
1084  remmina_public_get_server_port(server, default_port, &srv_host, &srv_port);
1085  REMMINA_DEBUG("Calling remmina_public_get_server_port (tunnel)");
1086  remmina_public_get_server_port(ssh_tunnel_server, 22, &ssh_tunnel_host, &ssh_tunnel_port);
1087  REMMINA_DEBUG("server: %s, port: %d", srv_host, srv_port);
1088 
1089  if (port_plus && srv_port < 100)
1090  /* Protocols like VNC supports using instance number :0, :1, etc. as port number. */
1091  srv_port += default_port;
1092 
1093 #ifdef HAVE_LIBSSH
1094  gchar *msg;
1095  RemminaMessagePanel *mp;
1096  RemminaSSHTunnel *tunnel;
1097 
1098  if (!remmina_file_get_int(gp->priv->remmina_file, "ssh_tunnel_enabled", FALSE)) {
1099  dest = g_strdup_printf("[%s]:%i", srv_host, srv_port);
1100  g_free(srv_host);
1101  g_free(ssh_tunnel_host);
1102  return dest;
1103  }
1104 
1106  if (!tunnel) {
1107  g_free(srv_host);
1108  g_free(ssh_tunnel_host);
1109  REMMINA_DEBUG("remmina_protocol_widget_init_tunnel failed with error is %s",
1111  return NULL;
1112  }
1113 
1114  // TRANSLATORS: “%s” is a placeholder for an hostname or an IP address.
1115  msg = g_strdup_printf(_("Connecting to “%s” via SSH…"), server);
1117  g_free(msg);
1118 
1119  if (remmina_file_get_int(gp->priv->remmina_file, "ssh_tunnel_loopback", FALSE)) {
1120  g_free(srv_host);
1121  g_free(ssh_tunnel_host);
1122  ssh_tunnel_host = NULL;
1123  srv_host = g_strdup("127.0.0.1");
1124  }
1125 
1126  REMMINA_DEBUG("Starting tunnel to: %s, port: %d", ssh_tunnel_host, ssh_tunnel_port);
1127  if (!remmina_ssh_tunnel_open(tunnel, srv_host, srv_port, remmina_pref.sshtunnel_port)) {
1128  g_free(srv_host);
1129  g_free(ssh_tunnel_host);
1130  remmina_protocol_widget_set_error(gp, REMMINA_SSH(tunnel)->error);
1131  remmina_ssh_tunnel_free(tunnel);
1132  return NULL;
1133  }
1134  g_free(srv_host);
1135  g_free(ssh_tunnel_host);
1136 
1138 
1140  tunnel->destroy_func_callback_data = (gpointer)gp;
1141 
1142  g_ptr_array_add(gp->priv->ssh_tunnels, tunnel);
1143 
1144  return g_strdup_printf("127.0.0.1:%i", remmina_pref.sshtunnel_port);
1145 
1146 #else
1147 
1148  dest = g_strdup_printf("[%s]:%i", srv_host, srv_port);
1149  g_free(srv_host);
1150  g_free(ssh_tunnel_host);
1151  return dest;
1152 
1153 #endif
1154 }
1155 
1156 #ifdef HAVE_LIBSSH
1157 static void cancel_start_reverse_tunnel_cb(void *cbdata, int btn)
1158 {
1159  printf("Remmina: Cancelling start_reverse_tunnel is not implemented\n");
1160 }
1161 #endif
1162 
1163 
1165 {
1166  TRACE_CALL(__func__);
1167 #ifdef HAVE_LIBSSH
1168  gchar *msg;
1169  RemminaMessagePanel *mp;
1170  RemminaSSHTunnel *tunnel;
1171 
1172  if (!remmina_file_get_int(gp->priv->remmina_file, "ssh_tunnel_enabled", FALSE))
1173  return TRUE;
1174 
1175  if (!(tunnel = remmina_protocol_widget_init_tunnel(gp)))
1176  return FALSE;
1177 
1178  // TRANSLATORS: “%i” is a placeholder for a TCP port number.
1179  msg = g_strdup_printf(_("Awaiting incoming SSH connection on port %i…"), remmina_file_get_int(gp->priv->remmina_file, "listenport", 0));
1181  g_free(msg);
1182 
1183  if (!remmina_ssh_tunnel_reverse(tunnel, remmina_file_get_int(gp->priv->remmina_file, "listenport", 0), local_port)) {
1184  remmina_ssh_tunnel_free(tunnel);
1185  remmina_protocol_widget_set_error(gp, REMMINA_SSH(tunnel)->error);
1186  return FALSE;
1187  }
1189  g_ptr_array_add(gp->priv->ssh_tunnels, tunnel);
1190 #endif
1191 
1192  return TRUE;
1193 }
1194 
1195 gboolean remmina_protocol_widget_ssh_exec(RemminaProtocolWidget *gp, gboolean wait, const gchar *fmt, ...)
1196 {
1197  TRACE_CALL(__func__);
1198 #ifdef HAVE_LIBSSH
1199  RemminaSSHTunnel *tunnel;
1200  ssh_channel channel;
1201  gint status;
1202  gboolean ret = FALSE;
1203  gchar *cmd, *ptr;
1204  va_list args;
1205 
1206  if (gp->priv->ssh_tunnels->len < 1)
1207  return FALSE;
1208 
1209  tunnel = (RemminaSSHTunnel *)gp->priv->ssh_tunnels->pdata[0];
1210 
1211  if ((channel = ssh_channel_new(REMMINA_SSH(tunnel)->session)) == NULL)
1212  return FALSE;
1213 
1214  va_start(args, fmt);
1215  cmd = g_strdup_vprintf(fmt, args);
1216  va_end(args);
1217 
1218  if (ssh_channel_open_session(channel) == SSH_OK &&
1219  ssh_channel_request_exec(channel, cmd) == SSH_OK) {
1220  if (wait) {
1221  ssh_channel_send_eof(channel);
1222  status = ssh_channel_get_exit_status(channel);
1223  ptr = strchr(cmd, ' ');
1224  if (ptr) *ptr = '\0';
1225  switch (status) {
1226  case 0:
1227  ret = TRUE;
1228  break;
1229  case 127:
1230  // TRANSLATORS: “%s” is a place holder for a unix command path.
1231  remmina_ssh_set_application_error(REMMINA_SSH(tunnel),
1232  _("The “%s” command is not available on the SSH server."), cmd);
1233  break;
1234  default:
1235  // TRANSLATORS: “%s” is a place holder for a unix command path. “%i” is a placeholder for an error code number.
1236  remmina_ssh_set_application_error(REMMINA_SSH(tunnel),
1237  _("Could not run the “%s” command on the SSH server (status = %i)."), cmd, status);
1238  break;
1239  }
1240  } else {
1241  ret = TRUE;
1242  }
1243  } else {
1244  // TRANSLATORS: %s is a placeholder for an error message
1245  remmina_ssh_set_error(REMMINA_SSH(tunnel), _("Could not run command. %s"));
1246  }
1247  g_free(cmd);
1248  if (wait)
1249  ssh_channel_close(channel);
1250  ssh_channel_free(channel);
1251  return ret;
1252 
1253 #else
1254 
1255  return FALSE;
1256 
1257 #endif
1258 }
1259 
1260 #ifdef HAVE_LIBSSH
1262 {
1263  TRACE_CALL(__func__);
1264  RemminaProtocolWidget *gp = REMMINA_PROTOCOL_WIDGET(data);
1265  gchar *server;
1266  gint port;
1267  gboolean ret;
1268 
1269  REMMINA_DEBUG("Calling remmina_public_get_server_port");
1270  remmina_public_get_server_port(remmina_file_get_string(gp->priv->remmina_file, "server"), 177, &server, &port);
1271  ret = ((RemminaXPortTunnelInitFunc)gp->priv->init_func)(gp,
1272  tunnel->remotedisplay, (tunnel->bindlocalhost ? "localhost" : server), port);
1273  g_free(server);
1274 
1275  return ret;
1276 }
1277 
1279 {
1280  TRACE_CALL(__func__);
1281  return TRUE;
1282 }
1283 
1285 {
1286  TRACE_CALL(__func__);
1287  RemminaProtocolWidget *gp = REMMINA_PROTOCOL_WIDGET(data);
1288 
1289  if (REMMINA_SSH(tunnel)->error)
1290  remmina_protocol_widget_set_error(gp, "%s", REMMINA_SSH(tunnel)->error);
1291 
1292  IDLE_ADD((GSourceFunc)remmina_protocol_widget_close_connection, gp);
1293  return TRUE;
1294 }
1295 #endif
1296 #ifdef HAVE_LIBSSH
1297 static void cancel_connect_xport_cb(void *cbdata, int btn)
1298 {
1299  printf("Remmina: Cancelling an XPort connection is not implemented\n");
1300 }
1301 #endif
1303 {
1304  TRACE_CALL(__func__);
1305 #ifdef HAVE_LIBSSH
1306  gboolean bindlocalhost;
1307  gchar *server;
1308  gchar *msg;
1309  RemminaMessagePanel *mp;
1310  RemminaSSHTunnel *tunnel;
1311 
1312  if (!(tunnel = remmina_protocol_widget_init_tunnel(gp))) return FALSE;
1313 
1314  // TRANSLATORS: “%s” is a placeholder for a hostname or IP address.
1315  msg = g_strdup_printf(_("Connecting to %s via SSH…"), remmina_file_get_string(gp->priv->remmina_file, "server"));
1317  g_free(msg);
1318 
1319  gp->priv->init_func = init_func;
1323  tunnel->callback_data = gp;
1324 
1325  REMMINA_DEBUG("Calling remmina_public_get_server_port");
1326  remmina_public_get_server_port(remmina_file_get_string(gp->priv->remmina_file, "server"), 0, &server, NULL);
1327  bindlocalhost = (g_strcmp0(REMMINA_SSH(tunnel)->server, server) == 0);
1328  g_free(server);
1329 
1330  if (!remmina_ssh_tunnel_xport(tunnel, bindlocalhost)) {
1331  remmina_protocol_widget_set_error(gp, "Could not open channel, %s",
1332  ssh_get_error(REMMINA_SSH(tunnel)->session));
1333  remmina_ssh_tunnel_free(tunnel);
1334  return FALSE;
1335  }
1336 
1338  g_ptr_array_add(gp->priv->ssh_tunnels, tunnel);
1339 
1340  return TRUE;
1341 
1342 #else
1343  return FALSE;
1344 #endif
1345 }
1346 
1348 {
1349  TRACE_CALL(__func__);
1350 #ifdef HAVE_LIBSSH
1351  RemminaSSHTunnel *tunnel;
1352  if (gp->priv->ssh_tunnels->len < 1)
1353  return;
1354  tunnel = (RemminaSSHTunnel *)gp->priv->ssh_tunnels->pdata[0];
1355  if (tunnel->localdisplay) g_free(tunnel->localdisplay);
1356  tunnel->localdisplay = g_strdup_printf("unix:%i", display);
1357 #endif
1358 }
1359 
1361 {
1362  TRACE_CALL(__func__);
1363  /* Returns the width of remote desktop as chosen by the user profile */
1364  return gp->priv->profile_remote_width;
1365 }
1366 
1368 {
1369  TRACE_CALL(__func__);
1370  /* Returns ehenever multi monitor is enabled (1) */
1371  gp->priv->multimon = remmina_file_get_int(gp->priv->remmina_file, "multimon", -1);
1372  REMMINA_DEBUG("Multi monitor is set to %d", gp->priv->multimon);
1373  return gp->priv->multimon;
1374 }
1375 
1377 {
1378  TRACE_CALL(__func__);
1379  /* Returns the height of remote desktop as chosen by the user profile */
1380  return gp->priv->profile_remote_height;
1381 }
1382 
1384 {
1385  TRACE_CALL(__func__);
1386  return gp ? gp->plugin ? gp->plugin->name : NULL : NULL;
1387 }
1388 
1390 {
1391  TRACE_CALL(__func__);
1392  return gp->priv->width;
1393 }
1394 
1396 {
1397  TRACE_CALL(__func__);
1398  gp->priv->width = width;
1399 }
1400 
1402 {
1403  TRACE_CALL(__func__);
1404  return gp->priv->height;
1405 }
1406 
1408 {
1409  TRACE_CALL(__func__);
1410  gp->priv->height = height;
1411 }
1412 
1414 {
1415  TRACE_CALL(__func__);
1416  return gp->priv->scalemode;
1417 }
1418 
1420 {
1421  TRACE_CALL(__func__);
1422  gp->priv->scalemode = scalemode;
1423 }
1424 
1426 {
1427  TRACE_CALL(__func__);
1428  return gp->priv->scaler_expand;
1429 }
1430 
1432 {
1433  TRACE_CALL(__func__);
1434  gp->priv->scaler_expand = expand;
1435  return;
1436 }
1437 
1439 {
1440  TRACE_CALL(__func__);
1441  return gp->priv->has_error;
1442 }
1443 
1445 {
1446  TRACE_CALL(__func__);
1447  return gp->priv->error_message;
1448 }
1449 
1451 {
1452  TRACE_CALL(__func__);
1453  va_list args;
1454 
1455  if (gp->priv->error_message) g_free(gp->priv->error_message);
1456 
1457  if (fmt == NULL) {
1458  gp->priv->has_error = FALSE;
1459  gp->priv->error_message = NULL;
1460  return;
1461  }
1462 
1463  va_start(args, fmt);
1464  gp->priv->error_message = g_strdup_vprintf(fmt, args);
1465  va_end(args);
1466 
1467  gp->priv->has_error = TRUE;
1468 }
1469 
1471 {
1472  TRACE_CALL(__func__);
1473  return gp->priv->closed;
1474 }
1475 
1477 {
1478  TRACE_CALL(__func__);
1479  return gp->priv->remmina_file;
1480 }
1481 
1483  /* Input data */
1485  gchar * title;
1490  enum panel_type dtype;
1493  /* Running status */
1494  pthread_mutex_t pt_mutex;
1495  pthread_cond_t pt_cond;
1496  /* Output/retval */
1498 };
1499 
1500 static void authpanel_mt_cb(void *user_data, int button)
1501 {
1503 
1504  d->rcbutton = button;
1505  if (button == GTK_RESPONSE_OK) {
1506  if (d->dtype == RPWDT_AUTH) {
1511  } else if (d->dtype == RPWDT_AUTHX509) {
1516  }
1517  }
1518 
1519  if (d->called_from_subthread) {
1520  /* Hide and destroy message panel, we can do it now because we are on the main thread */
1522 
1523  /* Awake the locked subthread, when called from subthread */
1524  pthread_mutex_lock(&d->pt_mutex);
1525  pthread_cond_signal(&d->pt_cond);
1526  pthread_mutex_unlock(&d->pt_mutex);
1527  } else {
1528  /* Signal completion, when called from main thread. Message panel will be destroyed by the caller */
1530  }
1531 }
1532 
1533 static gboolean remmina_protocol_widget_dialog_mt_setup(gpointer user_data)
1534 {
1536 
1537  RemminaFile *remminafile = d->gp->priv->remmina_file;
1538  RemminaMessagePanel *mp;
1539  const gchar *s;
1540 
1541  if (d->gp->cnnobj == NULL)
1542  return FALSE;
1543 
1545 
1546  if (d->dtype == RPWDT_AUTH) {
1554  } else if (d->dtype == RPWDT_QUESTIONYESNO) {
1556  } else if (d->dtype == RPWDT_AUTHX509) {
1558  if ((s = remmina_file_get_string(remminafile, "cacert")) != NULL)
1560  if ((s = remmina_file_get_string(remminafile, "cacrl")) != NULL)
1562  if ((s = remmina_file_get_string(remminafile, "clientcert")) != NULL)
1564  if ((s = remmina_file_get_string(remminafile, "clientkey")) != NULL)
1566  }
1567 
1568  d->gp->priv->auth_message_panel = mp;
1569  rco_show_message_panel(d->gp->cnnobj, mp);
1570 
1571  return FALSE;
1572 }
1573 
1574 typedef struct {
1575  RemminaMessagePanel * mp;
1576  GMainLoop * loop;
1577  gint response;
1578  gboolean destroyed;
1579 } MpRunInfo;
1580 
1581 static void shutdown_loop(MpRunInfo *mpri)
1582 {
1583  if (g_main_loop_is_running(mpri->loop))
1584  g_main_loop_quit(mpri->loop);
1585 }
1586 
1587 static void run_response_handler(RemminaMessagePanel *mp, gint response_id, gpointer data)
1588 {
1589  MpRunInfo *mpri = (MpRunInfo *)data;
1590 
1591  mpri->response = response_id;
1592  shutdown_loop(mpri);
1593 }
1594 
1595 static void run_unmap_handler(RemminaMessagePanel *mp, gpointer data)
1596 {
1597  MpRunInfo *mpri = (MpRunInfo *)data;
1598 
1599  mpri->response = GTK_RESPONSE_CANCEL;
1600  shutdown_loop(mpri);
1601 }
1602 
1603 static void run_destroy_handler(RemminaMessagePanel *mp, gpointer data)
1604 {
1605  MpRunInfo *mpri = (MpRunInfo *)data;
1606 
1607  mpri->destroyed = TRUE;
1608  mpri->response = GTK_RESPONSE_CANCEL;
1609  shutdown_loop(mpri);
1610 }
1611 
1613  const gchar *title, const gchar *default_username, const gchar *default_password, const gchar *default_domain,
1614  const gchar *strpasswordlabel)
1615 {
1616  TRACE_CALL(__func__);
1617 
1619  int rcbutton;
1620 
1621  d->gp = gp;
1622  d->pflags = pflags;
1623  d->dtype = dtype;
1624  d->title = g_strdup(title);
1625  d->strpasswordlabel = g_strdup(strpasswordlabel);
1626  d->default_username = g_strdup(default_username);
1627  d->default_password = g_strdup(default_password);
1628  d->default_domain = g_strdup(default_domain);
1629  d->called_from_subthread = FALSE;
1630 
1632  /* Run the MessagePanel in main thread, in a very similar way of gtk_dialog_run() */
1633  MpRunInfo mpri = { NULL, NULL, GTK_RESPONSE_CANCEL, FALSE };
1634 
1635  gulong unmap_handler;
1636  gulong destroy_handler;
1637  gulong response_handler;
1638 
1640 
1641  mpri.mp = d->gp->priv->auth_message_panel;
1642 
1643  if (!gtk_widget_get_visible(GTK_WIDGET(mpri.mp)))
1644  gtk_widget_show(GTK_WIDGET(mpri.mp));
1645  response_handler = g_signal_connect(mpri.mp, "response", G_CALLBACK(run_response_handler), &mpri);
1646  unmap_handler = g_signal_connect(mpri.mp, "unmap", G_CALLBACK(run_unmap_handler), &mpri);
1647  destroy_handler = g_signal_connect(mpri.mp, "destroy", G_CALLBACK(run_destroy_handler), &mpri);
1648 
1649  g_object_ref(mpri.mp);
1650 
1651  mpri.loop = g_main_loop_new(NULL, FALSE);
1652  g_main_loop_run(mpri.loop);
1653  g_main_loop_unref(mpri.loop);
1654 
1655  if (!mpri.destroyed) {
1656  g_signal_handler_disconnect(mpri.mp, response_handler);
1657  g_signal_handler_disconnect(mpri.mp, destroy_handler);
1658  g_signal_handler_disconnect(mpri.mp, unmap_handler);
1659  }
1660  g_object_unref(mpri.mp);
1661 
1663 
1664  rcbutton = mpri.response;
1665  } else {
1666  d->called_from_subthread = TRUE;
1667  // pthread_cleanup_push(ptcleanup, (void*)d);
1668  pthread_cond_init(&d->pt_cond, NULL);
1669  pthread_mutex_init(&d->pt_mutex, NULL);
1671  pthread_mutex_lock(&d->pt_mutex);
1672  pthread_cond_wait(&d->pt_cond, &d->pt_mutex);
1673  // pthread_cleanup_pop(0);
1674  pthread_mutex_destroy(&d->pt_mutex);
1675  pthread_cond_destroy(&d->pt_cond);
1676 
1677  rcbutton = d->rcbutton;
1678  }
1679 
1680  g_free(d->title);
1681  g_free(d->strpasswordlabel);
1682  g_free(d->default_username);
1683  g_free(d->default_password);
1684  g_free(d->default_domain);
1685  g_free(d);
1686  return rcbutton;
1687 }
1688 
1690 {
1691  return remmina_protocol_widget_dialog(RPWDT_QUESTIONYESNO, gp, 0, msg, NULL, NULL, NULL, NULL);
1692 }
1693 
1695  const gchar *title, const gchar *default_username, const gchar *default_password, const gchar *default_domain, const gchar *password_prompt)
1696 {
1697  TRACE_CALL(__func__);
1698  return remmina_protocol_widget_dialog(RPWDT_AUTH, gp, pflags, title, default_username,
1699  default_password, default_domain, password_prompt == NULL ? _("Password") : password_prompt);
1700 }
1701 
1702 gint remmina_protocol_widget_panel_authuserpwd_ssh_tunnel(RemminaProtocolWidget *gp, gboolean want_domain, gboolean allow_password_saving)
1703 {
1704  TRACE_CALL(__func__);
1705  unsigned pflags;
1706  RemminaFile *remminafile = gp->priv->remmina_file;
1707  const gchar *username, *password;
1708 
1710  if (remmina_file_get_filename(remminafile) != NULL &&
1711  !remminafile->prevent_saving && allow_password_saving)
1713 
1714  username = remmina_file_get_string(remminafile, "ssh_tunnel_username");
1715  password = remmina_file_get_string(remminafile, "ssh_tunnel_password");
1716 
1717  return remmina_protocol_widget_dialog(RPWDT_AUTH, gp, pflags, _("Type in SSH username and password."), username,
1718  password, NULL, _("Password"));
1719 }
1720 
1721 /*
1722  * gint remmina_protocol_widget_panel_authpwd(RemminaProtocolWidget* gp, RemminaAuthpwdType authpwd_type, gboolean allow_password_saving)
1723  * {
1724  * TRACE_CALL(__func__);
1725  * unsigned pflags;
1726  * RemminaFile* remminafile = gp->priv->remmina_file;
1727  * char *password_prompt;
1728  * int rc;
1729  *
1730  * pflags = 0;
1731  * if (remmina_file_get_filename(remminafile) != NULL &&
1732  * !remminafile->prevent_saving && allow_password_saving)
1733  * pflags |= REMMINA_MESSAGE_PANEL_FLAG_SAVEPASSWORD;
1734  *
1735  * switch (authpwd_type) {
1736  * case REMMINA_AUTHPWD_TYPE_PROTOCOL:
1737  * password_prompt = g_strdup_printf(_("%s password"), remmina_file_get_string(remminafile, "protocol"));
1738  * break;
1739  * case REMMINA_AUTHPWD_TYPE_SSH_PWD:
1740  * password_prompt = g_strdup(_("SSH password"));
1741  * break;
1742  * case REMMINA_AUTHPWD_TYPE_SSH_PRIVKEY:
1743  * password_prompt = g_strdup(_("SSH private key passphrase"));
1744  * break;
1745  * default:
1746  * password_prompt = g_strdup(_("Password"));
1747  * break;
1748  * }
1749  *
1750  * rc = remmina_protocol_widget_dialog(RPWDT_AUTH, gp, pflags, password_prompt);
1751  * g_free(password_prompt);
1752  * return rc;
1753  *
1754  * }
1755  */
1757 {
1758  TRACE_CALL(__func__);
1759 
1760  return remmina_protocol_widget_dialog(RPWDT_AUTHX509, gp, 0, NULL, NULL, NULL, NULL, NULL);
1761 }
1762 
1763 
1764 gint remmina_protocol_widget_panel_new_certificate(RemminaProtocolWidget *gp, const gchar *subject, const gchar *issuer, const gchar *fingerprint)
1765 {
1766  TRACE_CALL(__func__);
1767  gchar *s;
1768  int rc;
1769 
1770  if (remmina_pref_get_boolean("trust_all")) {
1771  /* For compatibility with plugin API: The plugin expects GTK_RESPONSE_OK when user confirms new cert */
1772  remmina_public_send_notification("remmina-security-trust-all-id", _("Fingerprint automatically accepted"), fingerprint);
1773  rc = GTK_RESPONSE_OK;
1774  return rc;
1775  }
1776  // For markup see https://developer.gnome.org/pygtk/stable/pango-markup-language.html
1777  s = g_strdup_printf(
1778  "<big>%s</big>\n\n%s %s\n%s %s\n%s %s\n\n<big>%s</big>",
1779  // TRANSLATORS: The user is asked to verify a new SSL certificate.
1780  _("Certificate details:"),
1781  // TRANSLATORS: An SSL certificate subject is usually the remote server the user connect to.
1782  _("Subject:"), subject,
1783  // TRANSLATORS: The name or email of the entity that have issued the SSL certificate
1784  _("Issuer:"), issuer,
1785  // TRANSLATORS: An SSL certificate fingerprint, is a hash of a certificate calculated on all certificate's data and its signature.
1786  _("Fingerprint:"), fingerprint,
1787  // TRANSLATORS: The user is asked to accept or refuse a new SSL certificate.
1788  _("Accept certificate?"));
1789  rc = remmina_protocol_widget_dialog(RPWDT_QUESTIONYESNO, gp, 0, s, NULL, NULL, NULL, NULL);
1790  g_free(s);
1791 
1792  /* For compatibility with plugin API: the plugin expects GTK_RESPONSE_OK when user confirms new cert */
1793  return rc == GTK_RESPONSE_YES ? GTK_RESPONSE_OK : rc;
1794 }
1795 
1796 gint remmina_protocol_widget_panel_changed_certificate(RemminaProtocolWidget *gp, const gchar *subject, const gchar *issuer, const gchar *new_fingerprint, const gchar *old_fingerprint)
1797 {
1798  TRACE_CALL(__func__);
1799  gchar *s;
1800  int rc;
1801 
1802  if (remmina_pref_get_boolean("trust_all")) {
1803  /* For compatibility with plugin API: The plugin expects GTK_RESPONSE_OK when user confirms new cert */
1804  remmina_public_send_notification("remmina-security-trust-all-id", _("Fingerprint automatically accepted"), new_fingerprint);
1805  rc = GTK_RESPONSE_OK;
1806  return rc;
1807  }
1808  // For markup see https://developer.gnome.org/pygtk/stable/pango-markup-language.html
1809  s = g_strdup_printf(
1810  "<big>%s</big>\n\n%s %s\n%s %s\n%s %s\n%s %s\n\n<big>%s</big>",
1811  // TRANSLATORS: The user is asked to verify a new SSL certificate.
1812  _("The certificate changed! Details:"),
1813  // TRANSLATORS: An SSL certificate subject is usually the remote server the user connect to.
1814  _("Subject:"), subject,
1815  // TRANSLATORS: The name or email of the entity that have issued the SSL certificate
1816  _("Issuer:"), issuer,
1817  // TRANSLATORS: An SSL certificate fingerprint, is a hash of a certificate calculated on all certificate's data and its signature.
1818  _("Old fingerprint:"), old_fingerprint,
1819  // TRANSLATORS: An SSL certificate fingerprint, is a hash of a certificate calculated on all certificate's data and its signature.
1820  _("New fingerprint:"), new_fingerprint,
1821  // TRANSLATORS: The user is asked to accept or refuse a new SSL certificate.
1822  _("Accept changed certificate?"));
1823  rc = remmina_protocol_widget_dialog(RPWDT_QUESTIONYESNO, gp, 0, s, NULL, NULL, NULL, NULL);
1824  g_free(s);
1825 
1826  /* For compatibility with plugin API: The plugin expects GTK_RESPONSE_OK when user confirms new cert */
1827  return rc == GTK_RESPONSE_YES ? GTK_RESPONSE_OK : rc;
1828 }
1829 
1831 {
1832  TRACE_CALL(__func__);
1833  return g_strdup(gp->priv->username);
1834 }
1835 
1837 {
1838  TRACE_CALL(__func__);
1839  return g_strdup(gp->priv->password);
1840 }
1841 
1843 {
1844  TRACE_CALL(__func__);
1845  return g_strdup(gp->priv->domain);
1846 }
1847 
1849 {
1850  TRACE_CALL(__func__);
1851  return gp->priv->save_password;
1852 }
1853 
1855 {
1856  TRACE_CALL(__func__);
1857  gchar *s;
1858 
1859  s = gp->priv->cacert;
1860  return s && s[0] ? g_strdup(s) : NULL;
1861 }
1862 
1864 {
1865  TRACE_CALL(__func__);
1866  gchar *s;
1867 
1868  s = gp->priv->cacrl;
1869  return s && s[0] ? g_strdup(s) : NULL;
1870 }
1871 
1873 {
1874  TRACE_CALL(__func__);
1875  gchar *s;
1876 
1877  s = gp->priv->clientcert;
1878  return s && s[0] ? g_strdup(s) : NULL;
1879 }
1880 
1882 {
1883  TRACE_CALL(__func__);
1884  gchar *s;
1885 
1886  s = gp->priv->clientkey;
1887  return s && s[0] ? g_strdup(s) : NULL;
1888 }
1889 
1891 {
1892  TRACE_CALL(__func__);
1893 
1894  RemminaFile *remminafile = gp->priv->remmina_file;
1895  gchar *s;
1896  gboolean save = FALSE;
1897 
1899  /* Allow the execution of this function from a non main thread */
1900  RemminaMTExecData *d;
1901  d = (RemminaMTExecData *)g_malloc(sizeof(RemminaMTExecData));
1902  d->func = FUNC_INIT_SAVE_CRED;
1903  d->p.init_save_creds.gp = gp;
1905  g_free(d);
1906  return;
1907  }
1908 
1909  /* Save username and certificates if any; save the password if it’s requested to do so */
1910  s = gp->priv->username;
1911  if (s && s[0]) {
1912  remmina_file_set_string(remminafile, "username", s);
1913  save = TRUE;
1914  }
1915  s = gp->priv->cacert;
1916  if (s && s[0]) {
1917  remmina_file_set_string(remminafile, "cacert", s);
1918  save = TRUE;
1919  }
1920  s = gp->priv->cacrl;
1921  if (s && s[0]) {
1922  remmina_file_set_string(remminafile, "cacrl", s);
1923  save = TRUE;
1924  }
1925  s = gp->priv->clientcert;
1926  if (s && s[0]) {
1927  remmina_file_set_string(remminafile, "clientcert", s);
1928  save = TRUE;
1929  }
1930  s = gp->priv->clientkey;
1931  if (s && s[0]) {
1932  remmina_file_set_string(remminafile, "clientkey", s);
1933  save = TRUE;
1934  }
1935  if (gp->priv->save_password) {
1936  remmina_file_set_string(remminafile, "password", gp->priv->password);
1937  save = TRUE;
1938  }
1939  if (save)
1940  remmina_file_save(remminafile);
1941 }
1942 
1943 
1945 {
1946  TRACE_CALL(__func__);
1947  RemminaMessagePanel *mp;
1948  gchar *s;
1949 
1951  /* Allow the execution of this function from a non main thread */
1952  RemminaMTExecData *d;
1953  d = (RemminaMTExecData *)g_malloc(sizeof(RemminaMTExecData));
1954  d->func = FUNC_PROTOCOLWIDGET_PANELSHOWLISTEN;
1956  d->p.protocolwidget_panelshowlisten.port = port;
1958  g_free(d);
1959  return;
1960  }
1961 
1963  s = g_strdup_printf(
1964  // TRANSLATORS: “%i” is a placeholder for a port number. “%s” is a placeholder for a protocol name (VNC).
1965  _("Listening on port %i for an incoming %s connection…"), port,
1966  remmina_file_get_string(gp->priv->remmina_file, "protocol"));
1967  remmina_message_panel_setup_progress(mp, s, NULL, NULL);
1968  g_free(s);
1969  gp->priv->listen_message_panel = mp;
1970  rco_show_message_panel(gp->cnnobj, mp);
1971 }
1972 
1974 {
1975  TRACE_CALL(__func__);
1976  RemminaMessagePanel *mp;
1977 
1979  /* Allow the execution of this function from a non main thread */
1980  RemminaMTExecData *d;
1981  d = (RemminaMTExecData *)g_malloc(sizeof(RemminaMTExecData));
1982  d->func = FUNC_PROTOCOLWIDGET_MPSHOWRETRY;
1985  g_free(d);
1986  return;
1987  }
1988 
1990  remmina_message_panel_setup_progress(mp, _("Could not authenticate, attempting reconnection…"), NULL, NULL);
1991  gp->priv->retry_message_panel = mp;
1992  rco_show_message_panel(gp->cnnobj, mp);
1993 }
1994 
1996 {
1997  TRACE_CALL(__func__);
1998  printf("Remmina: The %s function is not implemented, and is left here only for plugin API compatibility.\n", __func__);
1999 }
2000 
2002 {
2003  TRACE_CALL(__func__);
2004  printf("Remmina: The %s function is not implemented, and is left here only for plugin API compatibility.\n", __func__);
2005 }
2006 
2008 {
2009  TRACE_CALL(__func__);
2010  gp->priv->chat_window = NULL;
2011 }
2012 
2014  void (*on_send)(RemminaProtocolWidget *gp, const gchar *text), void (*on_destroy)(RemminaProtocolWidget *gp))
2015 {
2016  TRACE_CALL(__func__);
2017  if (gp->priv->chat_window) {
2018  gtk_window_present(GTK_WINDOW(gp->priv->chat_window));
2019  } else {
2020  gp->priv->chat_window = remmina_chat_window_new(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(gp))), name);
2021  g_signal_connect_swapped(G_OBJECT(gp->priv->chat_window), "send", G_CALLBACK(on_send), gp);
2022  g_signal_connect_swapped(G_OBJECT(gp->priv->chat_window), "destroy",
2023  G_CALLBACK(remmina_protocol_widget_chat_on_destroy), gp);
2024  g_signal_connect_swapped(G_OBJECT(gp->priv->chat_window), "destroy", G_CALLBACK(on_destroy), gp);
2025  gtk_widget_show(gp->priv->chat_window);
2026  }
2027 }
2028 
2030 {
2031  TRACE_CALL(__func__);
2032  if (gp->priv->chat_window)
2033  gtk_widget_destroy(gp->priv->chat_window);
2034 }
2035 
2037 {
2038  TRACE_CALL(__func__);
2039  /* This function can be called from a non main thread */
2040 
2041  if (gp->priv->chat_window) {
2043  /* Allow the execution of this function from a non main thread */
2044  RemminaMTExecData *d;
2045  d = (RemminaMTExecData *)g_malloc(sizeof(RemminaMTExecData));
2046  d->func = FUNC_CHAT_RECEIVE;
2047  d->p.chat_receive.gp = gp;
2048  d->p.chat_receive.text = text;
2050  g_free(d);
2051  return;
2052  }
2053  remmina_chat_window_receive(REMMINA_CHAT_WINDOW(gp->priv->chat_window), _("Server"), text);
2054  gtk_window_present(GTK_WINDOW(gp->priv->chat_window));
2055  }
2056 }
2057 
2059 {
2061 
2062  gp->priv->remmina_file = remminafile;
2063  gp->cnnobj = cnnobj;
2064 
2065  /* Locate the protocol plugin */
2067  remmina_file_get_string(remminafile, "protocol"));
2068 
2069  if (!plugin || !plugin->init || !plugin->open_connection) {
2070  // TRANSLATORS: “%s” is a placeholder for a protocol name, like “RDP”.
2071  remmina_protocol_widget_set_error(gp, _("Install the %s protocol plugin first."),
2072  remmina_file_get_string(remminafile, "protocol"));
2073  gp->priv->plugin = NULL;
2074  return;
2075  }
2076  gp->priv->plugin = plugin;
2077  gp->plugin = plugin;
2078 
2079  gp->priv->scalemode = remmina_file_get_int(gp->priv->remmina_file, "scale", FALSE);
2080  gp->priv->scaler_expand = remmina_file_get_int(gp->priv->remmina_file, "scaler_expand", FALSE);
2081 }
2082 
2084 {
2085  return rcw_get_gtkwindow(gp->cnnobj);
2086 }
2087 
2089 {
2090  return rcw_get_gtkviewport(gp->cnnobj);
2091 }
2092 
2094 {
2095  return GTK_WIDGET(g_object_new(REMMINA_TYPE_PROTOCOL_WIDGET, NULL));
2096 }
2097 
2098 /* Send one or more keystrokes to a specific widget by firing key-press and
2099  * key-release events.
2100  * GdkEventType action can be GDK_KEY_PRESS or GDK_KEY_RELEASE or both to
2101  * press the keys and release them in reversed order. */
2102 void remmina_protocol_widget_send_keys_signals(GtkWidget *widget, const guint *keyvals, int keyvals_length, GdkEventType action)
2103 {
2104  TRACE_CALL(__func__);
2105  int i;
2106  GdkEventKey event;
2107  gboolean result;
2108  GdkKeymap *keymap = gdk_keymap_get_for_display(gdk_display_get_default());
2109 
2110  event.window = gtk_widget_get_window(widget);
2111  event.send_event = TRUE;
2112  event.time = GDK_CURRENT_TIME;
2113  event.state = 0;
2114  event.length = 0;
2115  event.string = "";
2116  event.group = 0;
2117 
2118  if (action & GDK_KEY_PRESS) {
2119  /* Press the requested buttons */
2120  event.type = GDK_KEY_PRESS;
2121  for (i = 0; i < keyvals_length; i++) {
2122  event.keyval = keyvals[i];
2123  event.hardware_keycode = remmina_public_get_keycode_for_keyval(keymap, event.keyval);
2124  event.is_modifier = (int)remmina_public_get_modifier_for_keycode(keymap, event.hardware_keycode);
2125  REMMINA_DEBUG("Sending keyval: %u, hardware_keycode: %u", event.keyval, event.hardware_keycode);
2126  g_signal_emit_by_name(G_OBJECT(widget), "key-press-event", &event, &result);
2127  }
2128  }
2129 
2130  if (action & GDK_KEY_RELEASE) {
2131  /* Release the requested buttons in reverse order */
2132  event.type = GDK_KEY_RELEASE;
2133  for (i = (keyvals_length - 1); i >= 0; i--) {
2134  event.keyval = keyvals[i];
2135  event.hardware_keycode = remmina_public_get_keycode_for_keyval(keymap, event.keyval);
2136  event.is_modifier = (int)remmina_public_get_modifier_for_keycode(keymap, event.hardware_keycode);
2137  g_signal_emit_by_name(G_OBJECT(widget), "key-release-event", &event, &result);
2138  }
2139  }
2140 }
2141 
2143 {
2144  TRACE_CALL(__func__);
2145  GdkRectangle rect;
2146  gint w, h;
2147  gint wfile, hfile;
2150 
2151  rco_get_monitor_geometry(gp->cnnobj, &rect);
2152 
2153  /* Integrity check: check that we have a cnnwin visible and get t */
2154 
2155  res_mode = remmina_file_get_int(gp->priv->remmina_file, "resolution_mode", RES_INVALID);
2157  wfile = remmina_file_get_int(gp->priv->remmina_file, "resolution_width", -1);
2158  hfile = remmina_file_get_int(gp->priv->remmina_file, "resolution_height", -1);
2159 
2160  /* If resolution_mode is non-existent (-1), then we try to calculate it
2161  * as we did before having resolution_mode */
2162  if (res_mode == RES_INVALID) {
2163  if (wfile <= 0 || hfile <= 0)
2164  res_mode = RES_USE_INITIAL_WINDOW_SIZE;
2165  else
2166  res_mode = RES_USE_CUSTOM;
2167  }
2168 
2170  /* Use internal window size as remote desktop size */
2171  GtkAllocation al;
2172  gtk_widget_get_allocation(GTK_WIDGET(gp), &al);
2173  /* use a multiple of four to mitigate scaling when remote host rounds up */
2174  w = al.width - al.width % 4;
2175  h = al.height - al.height % 4;
2176  if (w < 10) {
2177  printf("Remmina warning: %s RemminaProtocolWidget w=%d h=%d are too small, adjusting to 640x480\n", __func__, w, h);
2178  w = 640;
2179  h = 480;
2180  }
2181  /* Due to approximations while GTK calculates scaling, (w x h) may exceed our monitor geometry
2182  * Adjust to fit. */
2183  if (w > rect.width)
2184  w = rect.width;
2185  if (h > rect.height)
2186  h = rect.height;
2187  } else if (res_mode == RES_USE_CLIENT) {
2188  w = rect.width;
2189  h = rect.height;
2190  } else {
2191  w = wfile;
2192  h = hfile;
2193  }
2194  gp->priv->profile_remote_width = w;
2195  gp->priv->profile_remote_height = h;
2196 }
+Go to the documentation of this file.
1 /*
2  * Remmina - The GTK+ Remote Desktop Client
3  * Copyright (C) 2009-2011 Vic Lee
4  * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
5  * Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  *
22  * In addition, as a special exception, the copyright holders give
23  * permission to link the code of portions of this program with the
24  * OpenSSL library under certain conditions as described in each
25  * individual source file, and distribute linked combinations
26  * including the two.
27  * You must obey the GNU General Public License in all respects
28  * for all of the code used other than OpenSSL. * If you modify
29  * file(s) with this exception, you may extend this exception to your
30  * version of the file(s), but you are not obligated to do so. * If you
31  * do not wish to do so, delete this exception statement from your
32  * version. * If you delete this exception statement from all source
33  * files in the program, then also delete it here.
34  *
35  */
36 
37 #include "config.h"
38 
39 #include <gtk/gtk.h>
40 #include <gtk/gtkx.h>
41 #include <glib/gi18n.h>
42 #include <gmodule.h>
43 #include <stdlib.h>
44 
45 #include "remmina_chat_window.h"
47 #include "remmina_ext_exec.h"
48 #include "remmina_plugin_manager.h"
49 #include "remmina_pref.h"
51 #include "remmina_public.h"
52 #include "remmina_ssh.h"
53 #include "remmina_log.h"
55 
56 #ifdef GDK_WINDOWING_WAYLAND
57 #include <gdk/gdkwayland.h>
58 #endif
59 
64 
65  gint width;
66  gint height;
68  gboolean scaler_expand;
69 
70  gboolean has_error;
71  gchar * error_message;
72  /* ssh_tunnels is an array of RemminaSSHTunnel*
73  * the 1st one is the "main" tunnel, other tunnels are used for example in sftp commands */
74  GPtrArray * ssh_tunnels;
76 
77  GtkWidget * chat_window;
78 
79  gboolean closed;
80 
82 
85  gint multimon;
86 
87  RemminaMessagePanel * connect_message_panel;
88  RemminaMessagePanel * listen_message_panel;
89  RemminaMessagePanel * auth_message_panel;
90  RemminaMessagePanel * retry_message_panel;
91 
92  /* Data saved from the last message_panel when the user confirm */
93  gchar * username;
94  gchar * password;
95  gchar * domain;
96  gboolean save_password;
97 
98  gchar * cacert;
99  gchar * cacrl;
100  gchar * clientcert;
101  gchar * clientkey;
102 };
103 
108 };
109 
110 G_DEFINE_TYPE(RemminaProtocolWidget, remmina_protocol_widget, GTK_TYPE_EVENT_BOX)
111 
112 enum {
113  CONNECT_SIGNAL,
114  DISCONNECT_SIGNAL,
115  DESKTOP_RESIZE_SIGNAL,
116  UPDATE_ALIGN_SIGNAL,
117  LOCK_DYNRES_SIGNAL,
118  UNLOCK_DYNRES_SIGNAL,
120 };
121 
124  const gchar * signal_name;
126 
128 { 0 };
129 
131 {
132  TRACE_CALL(__func__);
133  remmina_protocol_widget_signals[CONNECT_SIGNAL] = g_signal_new("connect", G_TYPE_FROM_CLASS(klass),
134  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaProtocolWidgetClass, connect), NULL, NULL,
135  g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
136  remmina_protocol_widget_signals[DISCONNECT_SIGNAL] = g_signal_new("disconnect", G_TYPE_FROM_CLASS(klass),
137  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaProtocolWidgetClass, disconnect), NULL, NULL,
138  g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
139  remmina_protocol_widget_signals[DESKTOP_RESIZE_SIGNAL] = g_signal_new("desktop-resize", G_TYPE_FROM_CLASS(klass),
140  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaProtocolWidgetClass, desktop_resize), NULL, NULL,
141  g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
142  remmina_protocol_widget_signals[UPDATE_ALIGN_SIGNAL] = g_signal_new("update-align", G_TYPE_FROM_CLASS(klass),
143  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaProtocolWidgetClass, update_align), NULL, NULL,
144  g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
145  remmina_protocol_widget_signals[LOCK_DYNRES_SIGNAL] = g_signal_new("lock-dynres", G_TYPE_FROM_CLASS(klass),
146  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaProtocolWidgetClass, lock_dynres), NULL, NULL,
147  g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
148  remmina_protocol_widget_signals[UNLOCK_DYNRES_SIGNAL] = g_signal_new("unlock-dynres", G_TYPE_FROM_CLASS(klass),
149  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaProtocolWidgetClass, unlock_dynres), NULL, NULL,
150  g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
151 }
152 
153 
155 {
156  TRACE_CALL(__func__);
157  int i;
158 
159  if (gp->priv && gp->priv->ssh_tunnels) {
160  for (i = 0; i < gp->priv->ssh_tunnels->len; i++) {
161 #ifdef HAVE_LIBSSH
163 #else
164  REMMINA_DEBUG("LibSSH support turned off, no need to free SSH tunnel data");
165 #endif
166  }
167  g_ptr_array_set_size(gp->priv->ssh_tunnels, 0);
168  }
169 }
170 
171 
173 {
174  TRACE_CALL(__func__);
175 
176  g_free(gp->priv->username);
177  gp->priv->username = NULL;
178 
179  g_free(gp->priv->password);
180  gp->priv->password = NULL;
181 
182  g_free(gp->priv->domain);
183  gp->priv->domain = NULL;
184 
185  g_free(gp->priv->cacert);
186  gp->priv->cacert = NULL;
187 
188  g_free(gp->priv->cacrl);
189  gp->priv->cacrl = NULL;
190 
191  g_free(gp->priv->clientcert);
192  gp->priv->clientcert = NULL;
193 
194  g_free(gp->priv->clientkey);
195  gp->priv->clientkey = NULL;
196 
197  g_free(gp->priv->features);
198  gp->priv->features = NULL;
199 
200  g_free(gp->priv->error_message);
201  gp->priv->error_message = NULL;
202 
203  g_free(gp->priv->remmina_file);
204  gp->priv->remmina_file = NULL;
205 
206  g_free(gp->priv);
207  gp->priv = NULL;
208 
210 
211  if (gp->priv && gp->priv->ssh_tunnels) {
212  g_ptr_array_free(gp->priv->ssh_tunnels, TRUE);
213  gp->priv->ssh_tunnels = NULL;
214  }
215 }
216 
218 {
219  TRACE_CALL(__func__);
220  GtkWidget *child;
221 
222  child = gtk_bin_get_child(GTK_BIN(gp));
223 
224  if (child) {
225  gtk_widget_set_can_focus(child, TRUE);
226  gtk_widget_grab_focus(child);
227  }
228 }
229 
231 {
232  TRACE_CALL(__func__);
234 
235  priv = g_new0(RemminaProtocolWidgetPriv, 1);
236  gp->priv = priv;
237  gp->priv->closed = TRUE;
238  gp->priv->ssh_tunnels = g_ptr_array_new();
239 
240  g_signal_connect(G_OBJECT(gp), "destroy", G_CALLBACK(remmina_protocol_widget_destroy), NULL);
241 }
242 
244 {
245  TRACE_CALL(__func__);
246  RemminaProtocolWidget *gp = REMMINA_PROTOCOL_WIDGET(data);
247 
248  REMMINA_DEBUG("Opening connection");
249 
251  RemminaProtocolFeature *feature;
252  gint num_plugin;
253  gint num_ssh;
254 
255  gp->priv->closed = FALSE;
256 
257  plugin = gp->priv->plugin;
258  plugin->init(gp);
259 
260  for (num_plugin = 0, feature = (RemminaProtocolFeature *)plugin->features; feature && feature->type; num_plugin++, feature++) {
261  }
262 
263  num_ssh = 0;
264 #ifdef HAVE_LIBSSH
265  if (remmina_file_get_int(gp->priv->remmina_file, "ssh_tunnel_enabled", FALSE))
266  num_ssh += 2;
267 
268 #endif
269  if (num_plugin + num_ssh == 0) {
270  gp->priv->features = NULL;
271  } else {
272  gp->priv->features = g_new0(RemminaProtocolFeature, num_plugin + num_ssh + 1);
273  feature = gp->priv->features;
274  if (plugin->features) {
275  memcpy(feature, plugin->features, sizeof(RemminaProtocolFeature) * num_plugin);
276  feature += num_plugin;
277  }
278 #ifdef HAVE_LIBSSH
279  REMMINA_DEBUG("Have SSH");
280  if (num_ssh) {
282  feature->id = REMMINA_PROTOCOL_FEATURE_TOOL_SSH;
283  feature->opt1 = _("Connect via SSH from a new terminal");
285  feature->opt2 = "utilities-terminal";
287  feature->opt3 = NULL;
289  feature++;
290 
292  feature->id = REMMINA_PROTOCOL_FEATURE_TOOL_SFTP;
293  feature->opt1 = _("Open SFTP transfer…");
295  feature->opt2 = "folder-remote";
297  feature->opt3 = NULL;
299  feature++;
300  }
302 #endif
303  }
304 
305  if (!plugin->open_connection(gp))
307 }
308 
309 static void cancel_open_connection_cb(void *cbdata, int btn)
310 {
312 
314 }
315 
317 {
318  TRACE_CALL(__func__);
319  gchar *s;
320  const gchar *name;
321  RemminaMessagePanel *mp;
322 
323  /* Exec precommand before everything else */
325  remmina_message_panel_setup_progress(mp, _("Executing external commands…"), NULL, NULL);
327 
328  remmina_ext_exec_new(gp->priv->remmina_file, "precommand");
330 
331  name = remmina_file_get_string(gp->priv->remmina_file, "name");
332  // TRANSLATORS: “%s” is a placeholder for the connection profile name
333  s = g_strdup_printf(_("Connecting to “%s”…"), (name ? name : "*"));
334 
337  g_free(s);
338  gp->priv->connect_message_panel = mp;
340 
342 }
343 
344 static gboolean conn_closed(gpointer data)
345 {
346  TRACE_CALL(__func__);
348 
349 #ifdef HAVE_LIBSSH
350  /* This will close all tunnels */
352 #endif
353  /* Exec postcommand */
354  remmina_ext_exec_new(gp->priv->remmina_file, "postcommand");
355  /* Notify listeners (usually rcw) that the connection is closed */
356  g_signal_emit_by_name(G_OBJECT(gp), "disconnect");
357  return G_SOURCE_REMOVE;
358 }
359 
361 {
362  /* Plugin told us that it closed the connection,
363  * add async event to main thread to complete our close tasks */
364  TRACE_CALL(__func__);
365  gp->priv->closed = TRUE;
366  g_idle_add(conn_closed, (gpointer)gp);
367 }
368 
369 static gboolean conn_opened(gpointer data)
370 {
371  TRACE_CALL(__func__);
373 
374 #ifdef HAVE_LIBSSH
375  if (gp->priv->ssh_tunnels) {
376  for (guint i = 0; i < gp->priv->ssh_tunnels->len; i++)
378  }
379 #endif
380  if (gp->priv->listen_message_panel) {
382  gp->priv->listen_message_panel = NULL;
383  }
384  if (gp->priv->connect_message_panel) {
386  gp->priv->connect_message_panel = NULL;
387  }
388  if (gp->priv->retry_message_panel) {
390  gp->priv->retry_message_panel = NULL;
391  }
392  g_signal_emit_by_name(G_OBJECT(gp), "connect");
393  return G_SOURCE_REMOVE;
394 }
395 
397 {
398  /* Plugin told us that it opened the connection,
399  * add async event to main thread to complete our close tasks */
400  TRACE_CALL(__func__);
401  g_idle_add(conn_opened, (gpointer)gp);
402 }
403 
404 static gboolean update_align(gpointer data)
405 {
406  TRACE_CALL(__func__);
408 
409  g_signal_emit_by_name(G_OBJECT(gp), "update-align");
410  return G_SOURCE_REMOVE;
411 }
412 
414 {
415  /* Called by the plugin to do updates on rcw */
416  TRACE_CALL(__func__);
417  g_idle_add(update_align, (gpointer)gp);
418 }
419 
420 static gboolean lock_dynres(gpointer data)
421 {
422  TRACE_CALL(__func__);
424 
425  g_signal_emit_by_name(G_OBJECT(gp), "lock-dynres");
426  return G_SOURCE_REMOVE;
427 }
428 
429 static gboolean unlock_dynres(gpointer data)
430 {
431  TRACE_CALL(__func__);
433 
434  g_signal_emit_by_name(G_OBJECT(gp), "unlock-dynres");
435  return G_SOURCE_REMOVE;
436 }
437 
439 {
440  /* Called by the plugin to do updates on rcw */
441  TRACE_CALL(__func__);
442  g_idle_add(lock_dynres, (gpointer)gp);
443 }
444 
446 {
447  /* Called by the plugin to do updates on rcw */
448  TRACE_CALL(__func__);
449  g_idle_add(unlock_dynres, (gpointer)gp);
450 }
451 
452 static gboolean desktop_resize(gpointer data)
453 {
454  TRACE_CALL(__func__);
456 
457  g_signal_emit_by_name(G_OBJECT(gp), "desktop-resize");
458  return G_SOURCE_REMOVE;
459 }
460 
462 {
463  /* Called by the plugin to do updates on rcw */
464  TRACE_CALL(__func__);
465  g_idle_add(desktop_resize, (gpointer)gp);
466 }
467 
468 
470 {
471  TRACE_CALL(__func__);
472 
473  /* kindly ask the protocol plugin to close the connection.
474  * Nothing else is done here. */
475 
476  if (!GTK_IS_WIDGET(gp))
477  return;
478 
479  if (gp->priv->chat_window) {
480  gtk_widget_destroy(gp->priv->chat_window);
481  gp->priv->chat_window = NULL;
482  }
483 
484  if (gp->priv->closed) {
485  /* Connection is already closed by the plugin, but
486  * rcw is asking to close again (usually after an error panel)
487  */
488  /* Clear the current error, or "disconnect" signal func will reshow a panel */
490  g_signal_emit_by_name(G_OBJECT(gp), "disconnect");
491  return;
492  }
493 
494  /* Ask the plugin to close, async.
495  * The plugin will emit a "disconnect" signal on gp to call our
496  * remmina_protocol_widget_on_disconnected() when done */
497  gp->priv->plugin->close_connection(gp);
498 
499  return;
500 }
501 
505 {
506  return gp->priv->plugin->send_keystrokes ? TRUE : FALSE;
507 }
508 
513 {
514  TRACE_CALL(__func__);
515  gchar *keystrokes = g_object_get_data(G_OBJECT(widget), "keystrokes");
516  guint *keyvals;
517  gint i;
518  GdkKeymap *keymap = gdk_keymap_get_for_display(gdk_display_get_default());
519  gunichar character;
520  guint keyval;
521  GdkKeymapKey *keys;
522  gint n_keys;
523 
524  /* Single keystroke replace */
525  typedef struct _KeystrokeReplace {
526  gchar * search;
527  gchar * replace;
528  guint keyval;
529  } KeystrokeReplace;
530  /* Special characters to replace */
531  KeystrokeReplace keystrokes_replaces[] =
532  {
533  { "\\n", "\n", GDK_KEY_Return },
534  { "\\t", "\t", GDK_KEY_Tab },
535  { "\\b", "\b", GDK_KEY_BackSpace },
536  { "\\e", "\e", GDK_KEY_Escape },
537  { "\\\\", "\\", GDK_KEY_backslash },
538  { NULL, NULL, 0 }
539  };
540 
541  /* Keystrokes can only be sent to plugins that accepts them */
543  /* Replace special characters */
544  for (i = 0; keystrokes_replaces[i].replace; i++) {
545  REMMINA_DEBUG("Keystrokes before replacement is \'%s\'", keystrokes);
546  keystrokes = g_strdup(remmina_public_str_replace_in_place(keystrokes,
547  keystrokes_replaces[i].search,
548  keystrokes_replaces[i].replace));
549  REMMINA_DEBUG("Keystrokes after replacement is \'%s\'", keystrokes);
550  }
551  gchar *iter = g_strdup(keystrokes);
552  keyvals = (guint *)g_malloc(strlen(keystrokes));
553  while (TRUE) {
554  /* Process each character in the keystrokes */
555  character = g_utf8_get_char_validated(iter, -1);
556  if (character == 0)
557  break;
558  keyval = gdk_unicode_to_keyval(character);
559  /* Replace all the special character with its keyval */
560  for (i = 0; keystrokes_replaces[i].replace; i++) {
561  if (character == keystrokes_replaces[i].replace[0]) {
562  keys = g_new0(GdkKeymapKey, 1);
563  keyval = keystrokes_replaces[i].keyval;
564  /* A special character was generated, no keyval lookup needed */
565  character = 0;
566  break;
567  }
568  }
569  /* Decode character if it’s not a special character */
570  if (character) {
571  /* get keyval without modifications */
572  if (!gdk_keymap_get_entries_for_keyval(keymap, keyval, &keys, &n_keys)) {
573  g_warning("keyval 0x%04x has no keycode!", keyval);
574  iter = g_utf8_find_next_char(iter, NULL);
575  continue;
576  }
577  }
578  /* Add modifier keys */
579  n_keys = 0;
580  if (keys->level & 1)
581  keyvals[n_keys++] = GDK_KEY_Shift_L;
582  if (keys->level & 2)
583  keyvals[n_keys++] = GDK_KEY_Alt_R;
584  keyvals[n_keys++] = keyval;
585  /* Send keystroke to the plugin */
586  gp->priv->plugin->send_keystrokes(gp, keyvals, n_keys);
587  g_free(keys);
588  /* Process next character in the keystrokes */
589  iter = g_utf8_find_next_char(iter, NULL);
590  }
591  g_free(keyvals);
592  }
593  g_free(keystrokes);
594  return;
595 }
596 
603 void remmina_protocol_widget_send_clip_strokes(GtkClipboard *clipboard, const gchar *clip_text, gpointer data)
604 {
605  TRACE_CALL(__func__);
606  RemminaProtocolWidget *gp = REMMINA_PROTOCOL_WIDGET(data);
607  gchar *text = g_utf8_normalize(clip_text, -1, G_NORMALIZE_DEFAULT_COMPOSE);
608  guint *keyvals;
609  gint i;
610  GdkKeymap *keymap = gdk_keymap_get_for_display(gdk_display_get_default());
611  gunichar character;
612  guint keyval;
613  GdkKeymapKey *keys;
614  gint n_keys;
615 
616  /* Single keystroke replace */
617  typedef struct _KeystrokeReplace {
618  gchar * search;
619  gchar * replace;
620  guint keyval;
621  } KeystrokeReplace;
622  /* Special characters to replace */
623  KeystrokeReplace text_replaces[] =
624  {
625  { "\\n", "\n", GDK_KEY_Return },
626  { "\\t", "\t", GDK_KEY_Tab },
627  { "\\b", "\b", GDK_KEY_BackSpace },
628  { "\\e", "\e", GDK_KEY_Escape },
629  { "\\\\", "\\", GDK_KEY_backslash },
630  { NULL, NULL, 0 }
631  };
632 
634  if (text) {
635  /* Replace special characters */
636  for (i = 0; text_replaces[i].replace; i++) {
637  REMMINA_DEBUG("Text clipboard before replacement is \'%s\'", text);
638  text = g_strdup(remmina_public_str_replace_in_place(text,
639  text_replaces[i].search,
640  text_replaces[i].replace));
641  REMMINA_DEBUG("Text clipboard after replacement is \'%s\'", text);
642  }
643  gchar *iter = g_strdup(text);
644  REMMINA_DEBUG("Iter: %s", iter),
645  keyvals = (guint *)g_malloc(strlen(text));
646  while (TRUE) {
647  /* Process each character in the keystrokes */
648  character = g_utf8_get_char_validated(iter, -1);
649  REMMINA_DEBUG("Char: U+%04" G_GINT32_FORMAT"X", character);
650  if (character == 0)
651  break;
652  keyval = gdk_unicode_to_keyval(character);
653  REMMINA_DEBUG("Keyval: %u", keyval);
654  /* Replace all the special character with its keyval */
655  for (i = 0; text_replaces[i].replace; i++) {
656  if (character == text_replaces[i].replace[0]) {
657  keys = g_new0(GdkKeymapKey, 1);
658  keyval = text_replaces[i].keyval;
659  REMMINA_DEBUG("Special Keyval: %u", keyval);
660  /* A special character was generated, no keyval lookup needed */
661  character = 0;
662  break;
663  }
664  }
665  /* Decode character if it’s not a special character */
666  if (character) {
667  /* get keyval without modifications */
668  if (!gdk_keymap_get_entries_for_keyval(keymap, keyval, &keys, &n_keys)) {
669  REMMINA_WARNING("keyval 0x%04x has no keycode!", keyval);
670  iter = g_utf8_find_next_char(iter, NULL);
671  continue;
672  }
673  }
674  /* Add modifier keys */
675  n_keys = 0;
676  if (keys->level & 1)
677  keyvals[n_keys++] = GDK_KEY_Shift_L;
678  if (keys->level & 2)
679  keyvals[n_keys++] = GDK_KEY_Alt_R;
680  /*
681  * @fixme heap buffer overflow
682  * In some cases, for example sending \t as the only sequence
683  * may lead to a buffer overflow
684  */
685  keyvals[n_keys++] = keyval;
686  /* Send keystroke to the plugin */
687  gp->priv->plugin->send_keystrokes(gp, keyvals, n_keys);
688  g_free(keys);
689  /* Process next character in the keystrokes */
690  iter = g_utf8_find_next_char(iter, NULL);
691  }
692  g_free(keyvals);
693  }
694  g_free(text);
695  }
696  return;
697 }
698 
700 {
701  TRACE_CALL(__func__);
702  GtkClipboard *clipboard;
703 
704  clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
705 
706  /* Request the contents of the clipboard, contents_received will be
707  * called when we do get the contents.
708  */
709  gtk_clipboard_request_text(clipboard,
711 }
712 
714 {
715  TRACE_CALL(__func__);
716  if (!gp->priv->plugin->get_plugin_screenshot) {
717  REMMINA_DEBUG("plugin screenshot function is not implemented, using core Remmina functionality");
718  return FALSE;
719  }
720 
721  return gp->priv->plugin->get_plugin_screenshot(gp, rpsd);
722 }
723 
725 {
726  TRACE_CALL(__func__);
727  if (!gp->priv->plugin->map_event) {
728  REMMINA_DEBUG("Map plugin function not implemented");
729  return FALSE;
730  }
731 
732  REMMINA_DEBUG("Calling plugin mapping function");
733  return gp->priv->plugin->map_event(gp);
734 }
735 
737 {
738  TRACE_CALL(__func__);
739  if (!gp->priv->plugin->unmap_event) {
740  REMMINA_DEBUG("Unmap plugin function not implemented");
741  return FALSE;
742  }
743 
744  REMMINA_DEBUG("Calling plugin unmapping function");
745  return gp->priv->plugin->unmap_event(gp);
746 }
747 
749 {
750  TRACE_CALL(__func__);
751 
752  REMMINA_DEBUG("Emitting signals should be used from the object itself, not from another object");
753  raise(SIGINT);
754 
756  /* Allow the execution of this function from a non main thread */
758  d = (RemminaMTExecData *)g_malloc(sizeof(RemminaMTExecData));
759  d->func = FUNC_PROTOCOLWIDGET_EMIT_SIGNAL;
760  d->p.protocolwidget_emit_signal.signal_name = signal_name;
761  d->p.protocolwidget_emit_signal.gp = gp;
763  g_free(d);
764  return;
765  }
766  g_signal_emit_by_name(G_OBJECT(gp), signal_name);
767 }
768 
770 {
771  TRACE_CALL(__func__);
772  return gp->priv->features;
773 }
774 
776 {
777  TRACE_CALL(__func__);
778  const RemminaProtocolFeature *feature;
779 
780 #ifdef HAVE_LIBSSH
782  remmina_file_get_int(gp->priv->remmina_file, "ssh_tunnel_enabled", FALSE))
783  return TRUE;
784 
785 #endif
786  for (feature = gp->priv->plugin->features; feature && feature->type; feature++)
787  if (feature->type == type)
788  return TRUE;
789  return FALSE;
790 }
791 
793 {
794  TRACE_CALL(__func__);
795  return gp->priv->plugin->query_feature(gp, feature);
796 }
797 
799 {
800  TRACE_CALL(__func__);
801  const RemminaProtocolFeature *feature;
802 
803  for (feature = gp->priv->plugin->features; feature && feature->type; feature++) {
804  if (feature->type == type && (id == 0 || feature->id == id)) {
806  break;
807  }
808  }
809 }
810 
812 {
813  TRACE_CALL(__func__);
814  switch (feature->id) {
815 #ifdef HAVE_LIBSSH
816  case REMMINA_PROTOCOL_FEATURE_TOOL_SSH:
817  if (gp->priv->ssh_tunnels && gp->priv->ssh_tunnels->len > 0) {
820  (RemminaSSHTunnel *)gp->priv->ssh_tunnels->pdata[0], NULL);
821  return;
822  }
823  break;
824 
825  case REMMINA_PROTOCOL_FEATURE_TOOL_SFTP:
826  if (gp->priv->ssh_tunnels && gp->priv->ssh_tunnels->len > 0) {
829  gp->priv->ssh_tunnels->pdata[0], NULL);
830  return;
831  }
832  break;
833 #endif
834  default:
835  break;
836  }
837  gp->priv->plugin->call_feature(gp, feature);
838 }
839 
840 static gboolean remmina_protocol_widget_on_key_press(GtkWidget *widget, GdkEventKey *event, RemminaProtocolWidget *gp)
841 {
842  TRACE_CALL(__func__);
843  if (gp->priv->hostkey_func)
844  return gp->priv->hostkey_func(gp, event->keyval, FALSE);
845  return FALSE;
846 }
847 
848 static gboolean remmina_protocol_widget_on_key_release(GtkWidget *widget, GdkEventKey *event, RemminaProtocolWidget *gp)
849 {
850  TRACE_CALL(__func__);
851  if (gp->priv->hostkey_func)
852  return gp->priv->hostkey_func(gp, event->keyval, TRUE);
853 
854  return FALSE;
855 }
856 
858 {
859  TRACE_CALL(__func__);
860  g_signal_connect(G_OBJECT(widget), "key-press-event", G_CALLBACK(remmina_protocol_widget_on_key_press), gp);
861  g_signal_connect(G_OBJECT(widget), "key-release-event", G_CALLBACK(remmina_protocol_widget_on_key_release), gp);
862 }
863 
865 {
866  TRACE_CALL(__func__);
867  gp->priv->hostkey_func = func;
868 }
869 
870 RemminaMessagePanel *remmina_protocol_widget_mpprogress(RemminaConnectionObject *cnnobj, const gchar *msg, RemminaMessagePanelCallback response_callback, gpointer response_callback_data)
871 {
872  RemminaMessagePanel *mp;
873 
875  /* Allow the execution of this function from a non main thread */
877  d = (RemminaMTExecData *)g_malloc(sizeof(RemminaMTExecData));
878  d->func = FUNC_PROTOCOLWIDGET_MPPROGRESS;
879  d->p.protocolwidget_mpprogress.cnnobj = cnnobj;
880  d->p.protocolwidget_mpprogress.message = msg;
881  d->p.protocolwidget_mpprogress.response_callback = response_callback;
882  d->p.protocolwidget_mpprogress.response_callback_data = response_callback_data;
884  mp = d->p.protocolwidget_mpprogress.ret_mp;
885  g_free(d);
886  return mp;
887  }
888 
890  remmina_message_panel_setup_progress(mp, msg, response_callback, response_callback_data);
891  rco_show_message_panel(cnnobj, mp);
892  return mp;
893 }
894 
895 void remmina_protocol_widget_mpdestroy(RemminaConnectionObject *cnnobj, RemminaMessagePanel *mp)
896 {
898  /* Allow the execution of this function from a non main thread */
900  d = (RemminaMTExecData *)g_malloc(sizeof(RemminaMTExecData));
901  d->func = FUNC_PROTOCOLWIDGET_MPDESTROY;
902  d->p.protocolwidget_mpdestroy.cnnobj = cnnobj;
903  d->p.protocolwidget_mpdestroy.mp = mp;
905  g_free(d);
906  return;
907  }
908  rco_destroy_message_panel(cnnobj, mp);
909 }
910 
911 #ifdef HAVE_LIBSSH
912 static void cancel_init_tunnel_cb(void *cbdata, int btn)
913 {
914  printf("Remmina: Cancelling an opening tunnel is not implemented\n");
915 }
916 
918 {
919  TRACE_CALL(__func__);
920  RemminaSSHTunnel *tunnel;
921  gint ret;
922  gchar *msg;
923  RemminaMessagePanel *mp;
924  gboolean partial = FALSE;
925  gboolean cont = FALSE;
926 
928 
929  REMMINA_DEBUG("Creating SSH tunnel to “%s” via SSH…", REMMINA_SSH(tunnel)->server);
930  // TRANSLATORS: “%s” is a placeholder for an hostname or an IP address.
931  msg = g_strdup_printf(_("Connecting to “%s” via SSH…"), REMMINA_SSH(tunnel)->server);
932 
934  g_free(msg);
935 
936 
937 
938  while (1) {
939  if (!partial) {
940  if (!remmina_ssh_init_session(REMMINA_SSH(tunnel))) {
941  REMMINA_DEBUG("SSH Tunnel init session error: %s", REMMINA_SSH(tunnel)->error);
942  remmina_protocol_widget_set_error(gp, REMMINA_SSH(tunnel)->error);
943  // exit the loop here: OK
944  break;
945  }
946  }
947 
948  ret = remmina_ssh_auth_gui(REMMINA_SSH(tunnel), gp, gp->priv->remmina_file);
949  REMMINA_DEBUG("Tunnel auth returned %d", ret);
950  switch (ret) {
952  REMMINA_DEBUG("Authentication success");
953  break;
955  REMMINA_DEBUG("Continue with the next auth method");
956  partial = TRUE;
957  // Continue the loop: OK
958  continue;
959  break;
961  REMMINA_DEBUG("Reconnecting…");
962  if (REMMINA_SSH(tunnel)->session) {
963  ssh_disconnect(REMMINA_SSH(tunnel)->session);
964  ssh_free(REMMINA_SSH(tunnel)->session);
965  REMMINA_SSH(tunnel)->session = NULL;
966  }
967  g_free(REMMINA_SSH(tunnel)->callback);
968  // Continue the loop: OK
969  continue;
970  break;
972  REMMINA_DEBUG("Interrupted by the user");
973  // exit the loop here: OK
974  goto BREAK;
975  break;
976  default:
977  REMMINA_DEBUG("Error during the authentication: %s", REMMINA_SSH(tunnel)->error);
978  remmina_protocol_widget_set_error(gp, REMMINA_SSH(tunnel)->error);
979  // exit the loop here: OK
980  goto BREAK;
981  }
982 
983 
984  cont = TRUE;
985  break;
986  }
987 
988 #if 0
989 
990  if (!remmina_ssh_init_session(REMMINA_SSH(tunnel))) {
991  REMMINA_DEBUG("Cannot init SSH session with tunnel struct");
992  remmina_protocol_widget_set_error(gp, REMMINA_SSH(tunnel)->error);
993  remmina_ssh_tunnel_free(tunnel);
994  return NULL;
995  }
996 
997  ret = remmina_ssh_auth_gui(REMMINA_SSH(tunnel), gp, gp->priv->remmina_file);
998  REMMINA_DEBUG("Tunnel auth returned %d", ret);
999  if (ret != REMMINA_SSH_AUTH_SUCCESS) {
1000  if (ret != REMMINA_SSH_AUTH_USERCANCEL)
1001  remmina_protocol_widget_set_error(gp, REMMINA_SSH(tunnel)->error);
1002  remmina_ssh_tunnel_free(tunnel);
1003  return NULL;
1004  }
1005 
1006 #endif
1007 
1008 BREAK:
1009  if (!cont) {
1010  remmina_ssh_tunnel_free(tunnel);
1011  return NULL;
1012  }
1014 
1015  return tunnel;
1016 }
1017 #endif
1018 
1019 
1020 #ifdef HAVE_LIBSSH
1021 static void cancel_start_direct_tunnel_cb(void *cbdata, int btn)
1022 {
1023  printf("Remmina: Cancelling start_direct_tunnel is not implemented\n");
1024 }
1025 
1026 static gboolean remmina_protocol_widget_tunnel_destroy(RemminaSSHTunnel *tunnel, gpointer data)
1027 {
1028  TRACE_CALL(__func__);
1029  RemminaProtocolWidget *gp = REMMINA_PROTOCOL_WIDGET(data);
1030  guint idx = 0;
1031 
1032 #if GLIB_CHECK_VERSION(2, 54, 0)
1033  gboolean found = g_ptr_array_find(gp->priv->ssh_tunnels, tunnel, &idx);
1034 #else
1035  int i;
1036  gboolean found = FALSE;
1037  for (i = 0; i < gp->priv->ssh_tunnels->len; i++) {
1038  if ((RemminaSSHTunnel *)gp->priv->ssh_tunnels->pdata[i] == tunnel) {
1039  found = TRUE;
1040  idx = i;
1041  }
1042  }
1043 #endif
1044 
1045  printf("Tunnel %s found at idx = %d\n", found ? "yes": "not", idx);
1046 
1047  if (found) {
1048 #ifdef HAVE_LIBSSH
1049  REMMINA_DEBUG("[Tunnel with idx %u has been disconnected", idx);
1050  remmina_ssh_tunnel_free(tunnel);
1051 #endif
1052  g_ptr_array_remove(gp->priv->ssh_tunnels, tunnel);
1053  }
1054  return TRUE;
1055 }
1056 #endif
1057 
1062 gchar *remmina_protocol_widget_start_direct_tunnel(RemminaProtocolWidget *gp, gint default_port, gboolean port_plus)
1063 {
1064  TRACE_CALL(__func__);
1065  const gchar *server;
1066  const gchar *ssh_tunnel_server;
1067  gchar *ssh_tunnel_host, *srv_host, *dest;
1068  gint srv_port, ssh_tunnel_port = 0;
1069 
1070  REMMINA_DEBUG("SSH tunnel initialization…");
1071 
1072  server = remmina_file_get_string(gp->priv->remmina_file, "server");
1073  ssh_tunnel_server = remmina_file_get_string(gp->priv->remmina_file, "ssh_tunnel_server");
1074 
1075  if (!server)
1076  return g_strdup("");
1077 
1078  if (strstr(g_strdup(server), "unix:///") != NULL) {
1079  REMMINA_DEBUG("%s is a UNIX socket", server);
1080  return g_strdup(server);
1081  }
1082 
1083  REMMINA_DEBUG("Calling remmina_public_get_server_port");
1084  remmina_public_get_server_port(server, default_port, &srv_host, &srv_port);
1085  REMMINA_DEBUG("Calling remmina_public_get_server_port (tunnel)");
1086  remmina_public_get_server_port(ssh_tunnel_server, 22, &ssh_tunnel_host, &ssh_tunnel_port);
1087  REMMINA_DEBUG("server: %s, port: %d", srv_host, srv_port);
1088 
1089  if (port_plus && srv_port < 100)
1090  /* Protocols like VNC supports using instance number :0, :1, etc. as port number. */
1091  srv_port += default_port;
1092 
1093 #ifdef HAVE_LIBSSH
1094  gchar *msg;
1095  RemminaMessagePanel *mp;
1096  RemminaSSHTunnel *tunnel;
1097 
1098  if (!remmina_file_get_int(gp->priv->remmina_file, "ssh_tunnel_enabled", FALSE)) {
1099  dest = g_strdup_printf("[%s]:%i", srv_host, srv_port);
1100  g_free(srv_host);
1101  g_free(ssh_tunnel_host);
1102  return dest;
1103  }
1104 
1106  if (!tunnel) {
1107  g_free(srv_host);
1108  g_free(ssh_tunnel_host);
1109  REMMINA_DEBUG("remmina_protocol_widget_init_tunnel failed with error is %s",
1111  return NULL;
1112  }
1113 
1114  // TRANSLATORS: “%s” is a placeholder for an hostname or an IP address.
1115  msg = g_strdup_printf(_("Connecting to “%s” via SSH…"), server);
1117  g_free(msg);
1118 
1119  if (remmina_file_get_int(gp->priv->remmina_file, "ssh_tunnel_loopback", FALSE)) {
1120  g_free(srv_host);
1121  g_free(ssh_tunnel_host);
1122  ssh_tunnel_host = NULL;
1123  srv_host = g_strdup("127.0.0.1");
1124  }
1125 
1126  REMMINA_DEBUG("Starting tunnel to: %s, port: %d", ssh_tunnel_host, ssh_tunnel_port);
1127  if (!remmina_ssh_tunnel_open(tunnel, srv_host, srv_port, remmina_pref.sshtunnel_port)) {
1128  g_free(srv_host);
1129  g_free(ssh_tunnel_host);
1130  remmina_protocol_widget_set_error(gp, REMMINA_SSH(tunnel)->error);
1131  remmina_ssh_tunnel_free(tunnel);
1132  return NULL;
1133  }
1134  g_free(srv_host);
1135  g_free(ssh_tunnel_host);
1136 
1138 
1140  tunnel->destroy_func_callback_data = (gpointer)gp;
1141 
1142  g_ptr_array_add(gp->priv->ssh_tunnels, tunnel);
1143 
1144  return g_strdup_printf("127.0.0.1:%i", remmina_pref.sshtunnel_port);
1145 
1146 #else
1147 
1148  dest = g_strdup_printf("[%s]:%i", srv_host, srv_port);
1149  g_free(srv_host);
1150  g_free(ssh_tunnel_host);
1151  return dest;
1152 
1153 #endif
1154 }
1155 
1156 #ifdef HAVE_LIBSSH
1157 static void cancel_start_reverse_tunnel_cb(void *cbdata, int btn)
1158 {
1159  printf("Remmina: Cancelling start_reverse_tunnel is not implemented\n");
1160 }
1161 #endif
1162 
1163 
1165 {
1166  TRACE_CALL(__func__);
1167 #ifdef HAVE_LIBSSH
1168  gchar *msg;
1169  RemminaMessagePanel *mp;
1170  RemminaSSHTunnel *tunnel;
1171 
1172  if (!remmina_file_get_int(gp->priv->remmina_file, "ssh_tunnel_enabled", FALSE))
1173  return TRUE;
1174 
1175  if (!(tunnel = remmina_protocol_widget_init_tunnel(gp)))
1176  return FALSE;
1177 
1178  // TRANSLATORS: “%i” is a placeholder for a TCP port number.
1179  msg = g_strdup_printf(_("Awaiting incoming SSH connection on port %i…"), remmina_file_get_int(gp->priv->remmina_file, "listenport", 0));
1181  g_free(msg);
1182 
1183  if (!remmina_ssh_tunnel_reverse(tunnel, remmina_file_get_int(gp->priv->remmina_file, "listenport", 0), local_port)) {
1184  remmina_ssh_tunnel_free(tunnel);
1185  remmina_protocol_widget_set_error(gp, REMMINA_SSH(tunnel)->error);
1186  return FALSE;
1187  }
1189  g_ptr_array_add(gp->priv->ssh_tunnels, tunnel);
1190 #endif
1191 
1192  return TRUE;
1193 }
1194 
1195 gboolean remmina_protocol_widget_ssh_exec(RemminaProtocolWidget *gp, gboolean wait, const gchar *fmt, ...)
1196 {
1197  TRACE_CALL(__func__);
1198 #ifdef HAVE_LIBSSH
1199  RemminaSSHTunnel *tunnel;
1200  ssh_channel channel;
1201  gint status;
1202  gboolean ret = FALSE;
1203  gchar *cmd, *ptr;
1204  va_list args;
1205 
1206  if (gp->priv->ssh_tunnels->len < 1)
1207  return FALSE;
1208 
1209  tunnel = (RemminaSSHTunnel *)gp->priv->ssh_tunnels->pdata[0];
1210 
1211  if ((channel = ssh_channel_new(REMMINA_SSH(tunnel)->session)) == NULL)
1212  return FALSE;
1213 
1214  va_start(args, fmt);
1215  cmd = g_strdup_vprintf(fmt, args);
1216  va_end(args);
1217 
1218  if (ssh_channel_open_session(channel) == SSH_OK &&
1219  ssh_channel_request_exec(channel, cmd) == SSH_OK) {
1220  if (wait) {
1221  ssh_channel_send_eof(channel);
1222  status = ssh_channel_get_exit_status(channel);
1223  ptr = strchr(cmd, ' ');
1224  if (ptr) *ptr = '\0';
1225  switch (status) {
1226  case 0:
1227  ret = TRUE;
1228  break;
1229  case 127:
1230  // TRANSLATORS: “%s” is a place holder for a unix command path.
1231  remmina_ssh_set_application_error(REMMINA_SSH(tunnel),
1232  _("The “%s” command is not available on the SSH server."), cmd);
1233  break;
1234  default:
1235  // TRANSLATORS: “%s” is a place holder for a unix command path. “%i” is a placeholder for an error code number.
1236  remmina_ssh_set_application_error(REMMINA_SSH(tunnel),
1237  _("Could not run the “%s” command on the SSH server (status = %i)."), cmd, status);
1238  break;
1239  }
1240  } else {
1241  ret = TRUE;
1242  }
1243  } else {
1244  // TRANSLATORS: %s is a placeholder for an error message
1245  remmina_ssh_set_error(REMMINA_SSH(tunnel), _("Could not run command. %s"));
1246  }
1247  g_free(cmd);
1248  if (wait)
1249  ssh_channel_close(channel);
1250  ssh_channel_free(channel);
1251  return ret;
1252 
1253 #else
1254 
1255  return FALSE;
1256 
1257 #endif
1258 }
1259 
1260 #ifdef HAVE_LIBSSH
1262 {
1263  TRACE_CALL(__func__);
1264  RemminaProtocolWidget *gp = REMMINA_PROTOCOL_WIDGET(data);
1265  gchar *server;
1266  gint port;
1267  gboolean ret;
1268 
1269  REMMINA_DEBUG("Calling remmina_public_get_server_port");
1270  remmina_public_get_server_port(remmina_file_get_string(gp->priv->remmina_file, "server"), 177, &server, &port);
1271  ret = ((RemminaXPortTunnelInitFunc)gp->priv->init_func)(gp,
1272  tunnel->remotedisplay, (tunnel->bindlocalhost ? "localhost" : server), port);
1273  g_free(server);
1274 
1275  return ret;
1276 }
1277 
1279 {
1280  TRACE_CALL(__func__);
1281  return TRUE;
1282 }
1283 
1285 {
1286  TRACE_CALL(__func__);
1287  RemminaProtocolWidget *gp = REMMINA_PROTOCOL_WIDGET(data);
1288 
1289  if (REMMINA_SSH(tunnel)->error)
1290  remmina_protocol_widget_set_error(gp, "%s", REMMINA_SSH(tunnel)->error);
1291 
1292  IDLE_ADD((GSourceFunc)remmina_protocol_widget_close_connection, gp);
1293  return TRUE;
1294 }
1295 #endif
1296 #ifdef HAVE_LIBSSH
1297 static void cancel_connect_xport_cb(void *cbdata, int btn)
1298 {
1299  printf("Remmina: Cancelling an XPort connection is not implemented\n");
1300 }
1301 #endif
1303 {
1304  TRACE_CALL(__func__);
1305 #ifdef HAVE_LIBSSH
1306  gboolean bindlocalhost;
1307  gchar *server;
1308  gchar *msg;
1309  RemminaMessagePanel *mp;
1310  RemminaSSHTunnel *tunnel;
1311 
1312  if (!(tunnel = remmina_protocol_widget_init_tunnel(gp))) return FALSE;
1313 
1314  // TRANSLATORS: “%s” is a placeholder for a hostname or IP address.
1315  msg = g_strdup_printf(_("Connecting to %s via SSH…"), remmina_file_get_string(gp->priv->remmina_file, "server"));
1317  g_free(msg);
1318 
1319  gp->priv->init_func = init_func;
1323  tunnel->callback_data = gp;
1324 
1325  REMMINA_DEBUG("Calling remmina_public_get_server_port");
1326  remmina_public_get_server_port(remmina_file_get_string(gp->priv->remmina_file, "server"), 0, &server, NULL);
1327  bindlocalhost = (g_strcmp0(REMMINA_SSH(tunnel)->server, server) == 0);
1328  g_free(server);
1329 
1330  if (!remmina_ssh_tunnel_xport(tunnel, bindlocalhost)) {
1331  remmina_protocol_widget_set_error(gp, "Could not open channel, %s",
1332  ssh_get_error(REMMINA_SSH(tunnel)->session));
1333  remmina_ssh_tunnel_free(tunnel);
1334  return FALSE;
1335  }
1336 
1338  g_ptr_array_add(gp->priv->ssh_tunnels, tunnel);
1339 
1340  return TRUE;
1341 
1342 #else
1343  return FALSE;
1344 #endif
1345 }
1346 
1348 {
1349  TRACE_CALL(__func__);
1350 #ifdef HAVE_LIBSSH
1351  RemminaSSHTunnel *tunnel;
1352  if (gp->priv->ssh_tunnels->len < 1)
1353  return;
1354  tunnel = (RemminaSSHTunnel *)gp->priv->ssh_tunnels->pdata[0];
1355  if (tunnel->localdisplay) g_free(tunnel->localdisplay);
1356  tunnel->localdisplay = g_strdup_printf("unix:%i", display);
1357 #endif
1358 }
1359 
1361 {
1362  TRACE_CALL(__func__);
1363  /* Returns the width of remote desktop as chosen by the user profile */
1364  return gp->priv->profile_remote_width;
1365 }
1366 
1368 {
1369  TRACE_CALL(__func__);
1370  /* Returns ehenever multi monitor is enabled (1) */
1371  gp->priv->multimon = remmina_file_get_int(gp->priv->remmina_file, "multimon", -1);
1372  REMMINA_DEBUG("Multi monitor is set to %d", gp->priv->multimon);
1373  return gp->priv->multimon;
1374 }
1375 
1377 {
1378  TRACE_CALL(__func__);
1379  /* Returns the height of remote desktop as chosen by the user profile */
1380  return gp->priv->profile_remote_height;
1381 }
1382 
1384 {
1385  TRACE_CALL(__func__);
1386  return gp ? gp->plugin ? gp->plugin->name : NULL : NULL;
1387 }
1388 
1390 {
1391  TRACE_CALL(__func__);
1392  return gp->priv->width;
1393 }
1394 
1396 {
1397  TRACE_CALL(__func__);
1398  gp->priv->width = width;
1399 }
1400 
1402 {
1403  TRACE_CALL(__func__);
1404  return gp->priv->height;
1405 }
1406 
1408 {
1409  TRACE_CALL(__func__);
1410  gp->priv->height = height;
1411 }
1412 
1414 {
1415  TRACE_CALL(__func__);
1416  return gp->priv->scalemode;
1417 }
1418 
1420 {
1421  TRACE_CALL(__func__);
1422  gp->priv->scalemode = scalemode;
1423 }
1424 
1426 {
1427  TRACE_CALL(__func__);
1428  return gp->priv->scaler_expand;
1429 }
1430 
1432 {
1433  TRACE_CALL(__func__);
1434  gp->priv->scaler_expand = expand;
1435  return;
1436 }
1437 
1439 {
1440  TRACE_CALL(__func__);
1441  return gp->priv->has_error;
1442 }
1443 
1445 {
1446  TRACE_CALL(__func__);
1447  return gp->priv->error_message;
1448 }
1449 
1451 {
1452  TRACE_CALL(__func__);
1453  va_list args;
1454 
1455  if (gp->priv->error_message) g_free(gp->priv->error_message);
1456 
1457  if (fmt == NULL) {
1458  gp->priv->has_error = FALSE;
1459  gp->priv->error_message = NULL;
1460  return;
1461  }
1462 
1463  va_start(args, fmt);
1464  gp->priv->error_message = g_strdup_vprintf(fmt, args);
1465  va_end(args);
1466 
1467  gp->priv->has_error = TRUE;
1468 }
1469 
1471 {
1472  TRACE_CALL(__func__);
1473  return gp->priv->closed;
1474 }
1475 
1477 {
1478  TRACE_CALL(__func__);
1479  return gp->priv->remmina_file;
1480 }
1481 
1483  /* Input data */
1485  gchar * title;
1490  enum panel_type dtype;
1493  /* Running status */
1494  pthread_mutex_t pt_mutex;
1495  pthread_cond_t pt_cond;
1496  /* Output/retval */
1498 };
1499 
1500 static void authpanel_mt_cb(void *user_data, int button)
1501 {
1503 
1504  d->rcbutton = button;
1505  if (button == GTK_RESPONSE_OK) {
1506  if (d->dtype == RPWDT_AUTH) {
1511  } else if (d->dtype == RPWDT_AUTHX509) {
1516  }
1517  }
1518 
1519  if (d->called_from_subthread) {
1520  /* Hide and destroy message panel, we can do it now because we are on the main thread */
1522 
1523  /* Awake the locked subthread, when called from subthread */
1524  pthread_mutex_lock(&d->pt_mutex);
1525  pthread_cond_signal(&d->pt_cond);
1526  pthread_mutex_unlock(&d->pt_mutex);
1527  } else {
1528  /* Signal completion, when called from main thread. Message panel will be destroyed by the caller */
1530  }
1531 }
1532 
1533 static gboolean remmina_protocol_widget_dialog_mt_setup(gpointer user_data)
1534 {
1536 
1537  RemminaFile *remminafile = d->gp->priv->remmina_file;
1538  RemminaMessagePanel *mp;
1539  const gchar *s;
1540 
1541  if (d->gp->cnnobj == NULL)
1542  return FALSE;
1543 
1545 
1546  if (d->dtype == RPWDT_AUTH) {
1554  } else if (d->dtype == RPWDT_QUESTIONYESNO) {
1556  } else if (d->dtype == RPWDT_AUTHX509) {
1558  if ((s = remmina_file_get_string(remminafile, "cacert")) != NULL)
1560  if ((s = remmina_file_get_string(remminafile, "cacrl")) != NULL)
1562  if ((s = remmina_file_get_string(remminafile, "clientcert")) != NULL)
1564  if ((s = remmina_file_get_string(remminafile, "clientkey")) != NULL)
1566  }
1567 
1568  d->gp->priv->auth_message_panel = mp;
1569  rco_show_message_panel(d->gp->cnnobj, mp);
1570 
1571  return FALSE;
1572 }
1573 
1574 typedef struct {
1575  RemminaMessagePanel * mp;
1576  GMainLoop * loop;
1577  gint response;
1578  gboolean destroyed;
1579 } MpRunInfo;
1580 
1581 static void shutdown_loop(MpRunInfo *mpri)
1582 {
1583  if (g_main_loop_is_running(mpri->loop))
1584  g_main_loop_quit(mpri->loop);
1585 }
1586 
1587 static void run_response_handler(RemminaMessagePanel *mp, gint response_id, gpointer data)
1588 {
1589  MpRunInfo *mpri = (MpRunInfo *)data;
1590 
1591  mpri->response = response_id;
1592  shutdown_loop(mpri);
1593 }
1594 
1595 static void run_unmap_handler(RemminaMessagePanel *mp, gpointer data)
1596 {
1597  MpRunInfo *mpri = (MpRunInfo *)data;
1598 
1599  mpri->response = GTK_RESPONSE_CANCEL;
1600  shutdown_loop(mpri);
1601 }
1602 
1603 static void run_destroy_handler(RemminaMessagePanel *mp, gpointer data)
1604 {
1605  MpRunInfo *mpri = (MpRunInfo *)data;
1606 
1607  mpri->destroyed = TRUE;
1608  mpri->response = GTK_RESPONSE_CANCEL;
1609  shutdown_loop(mpri);
1610 }
1611 
1613  const gchar *title, const gchar *default_username, const gchar *default_password, const gchar *default_domain,
1614  const gchar *strpasswordlabel)
1615 {
1616  TRACE_CALL(__func__);
1617 
1619  int rcbutton;
1620 
1621  d->gp = gp;
1622  d->pflags = pflags;
1623  d->dtype = dtype;
1624  d->title = g_strdup(title);
1625  d->strpasswordlabel = g_strdup(strpasswordlabel);
1626  d->default_username = g_strdup(default_username);
1627  d->default_password = g_strdup(default_password);
1628  d->default_domain = g_strdup(default_domain);
1629  d->called_from_subthread = FALSE;
1630 
1632  /* Run the MessagePanel in main thread, in a very similar way of gtk_dialog_run() */
1633  MpRunInfo mpri = { NULL, NULL, GTK_RESPONSE_CANCEL, FALSE };
1634 
1635  gulong unmap_handler;
1636  gulong destroy_handler;
1637  gulong response_handler;
1638 
1640 
1641  mpri.mp = d->gp->priv->auth_message_panel;
1642 
1643  if (!gtk_widget_get_visible(GTK_WIDGET(mpri.mp)))
1644  gtk_widget_show(GTK_WIDGET(mpri.mp));
1645  response_handler = g_signal_connect(mpri.mp, "response", G_CALLBACK(run_response_handler), &mpri);
1646  unmap_handler = g_signal_connect(mpri.mp, "unmap", G_CALLBACK(run_unmap_handler), &mpri);
1647  destroy_handler = g_signal_connect(mpri.mp, "destroy", G_CALLBACK(run_destroy_handler), &mpri);
1648 
1649  g_object_ref(mpri.mp);
1650 
1651  mpri.loop = g_main_loop_new(NULL, FALSE);
1652  g_main_loop_run(mpri.loop);
1653  g_main_loop_unref(mpri.loop);
1654 
1655  if (!mpri.destroyed) {
1656  g_signal_handler_disconnect(mpri.mp, response_handler);
1657  g_signal_handler_disconnect(mpri.mp, destroy_handler);
1658  g_signal_handler_disconnect(mpri.mp, unmap_handler);
1659  }
1660  g_object_unref(mpri.mp);
1661 
1663 
1664  rcbutton = mpri.response;
1665  } else {
1666  d->called_from_subthread = TRUE;
1667  // pthread_cleanup_push(ptcleanup, (void*)d);
1668  pthread_cond_init(&d->pt_cond, NULL);
1669  pthread_mutex_init(&d->pt_mutex, NULL);
1671  pthread_mutex_lock(&d->pt_mutex);
1672  pthread_cond_wait(&d->pt_cond, &d->pt_mutex);
1673  // pthread_cleanup_pop(0);
1674  pthread_mutex_destroy(&d->pt_mutex);
1675  pthread_cond_destroy(&d->pt_cond);
1676 
1677  rcbutton = d->rcbutton;
1678  }
1679 
1680  g_free(d->title);
1681  g_free(d->strpasswordlabel);
1682  g_free(d->default_username);
1683  g_free(d->default_password);
1684  g_free(d->default_domain);
1685  g_free(d);
1686  return rcbutton;
1687 }
1688 
1690 {
1691  return remmina_protocol_widget_dialog(RPWDT_QUESTIONYESNO, gp, 0, msg, NULL, NULL, NULL, NULL);
1692 }
1693 
1695  const gchar *title, const gchar *default_username, const gchar *default_password, const gchar *default_domain, const gchar *password_prompt)
1696 {
1697  TRACE_CALL(__func__);
1698  return remmina_protocol_widget_dialog(RPWDT_AUTH, gp, pflags, title, default_username,
1699  default_password, default_domain, password_prompt == NULL ? _("Password") : password_prompt);
1700 }
1701 
1702 gint remmina_protocol_widget_panel_authuserpwd_ssh_tunnel(RemminaProtocolWidget *gp, gboolean want_domain, gboolean allow_password_saving)
1703 {
1704  TRACE_CALL(__func__);
1705  unsigned pflags;
1706  RemminaFile *remminafile = gp->priv->remmina_file;
1707  const gchar *username, *password;
1708 
1710  if (remmina_file_get_filename(remminafile) != NULL &&
1711  !remminafile->prevent_saving && allow_password_saving)
1713 
1714  username = remmina_file_get_string(remminafile, "ssh_tunnel_username");
1715  password = remmina_file_get_string(remminafile, "ssh_tunnel_password");
1716 
1717  return remmina_protocol_widget_dialog(RPWDT_AUTH, gp, pflags, _("Type in SSH username and password."), username,
1718  password, NULL, _("Password"));
1719 }
1720 
1721 /*
1722  * gint remmina_protocol_widget_panel_authpwd(RemminaProtocolWidget* gp, RemminaAuthpwdType authpwd_type, gboolean allow_password_saving)
1723  * {
1724  * TRACE_CALL(__func__);
1725  * unsigned pflags;
1726  * RemminaFile* remminafile = gp->priv->remmina_file;
1727  * char *password_prompt;
1728  * int rc;
1729  *
1730  * pflags = 0;
1731  * if (remmina_file_get_filename(remminafile) != NULL &&
1732  * !remminafile->prevent_saving && allow_password_saving)
1733  * pflags |= REMMINA_MESSAGE_PANEL_FLAG_SAVEPASSWORD;
1734  *
1735  * switch (authpwd_type) {
1736  * case REMMINA_AUTHPWD_TYPE_PROTOCOL:
1737  * password_prompt = g_strdup_printf(_("%s password"), remmina_file_get_string(remminafile, "protocol"));
1738  * break;
1739  * case REMMINA_AUTHPWD_TYPE_SSH_PWD:
1740  * password_prompt = g_strdup(_("SSH password"));
1741  * break;
1742  * case REMMINA_AUTHPWD_TYPE_SSH_PRIVKEY:
1743  * password_prompt = g_strdup(_("SSH private key passphrase"));
1744  * break;
1745  * default:
1746  * password_prompt = g_strdup(_("Password"));
1747  * break;
1748  * }
1749  *
1750  * rc = remmina_protocol_widget_dialog(RPWDT_AUTH, gp, pflags, password_prompt);
1751  * g_free(password_prompt);
1752  * return rc;
1753  *
1754  * }
1755  */
1757 {
1758  TRACE_CALL(__func__);
1759 
1760  return remmina_protocol_widget_dialog(RPWDT_AUTHX509, gp, 0, NULL, NULL, NULL, NULL, NULL);
1761 }
1762 
1763 
1764 gint remmina_protocol_widget_panel_new_certificate(RemminaProtocolWidget *gp, const gchar *subject, const gchar *issuer, const gchar *fingerprint)
1765 {
1766  TRACE_CALL(__func__);
1767  gchar *s;
1768  int rc;
1769 
1770  if (remmina_pref_get_boolean("trust_all")) {
1771  /* For compatibility with plugin API: The plugin expects GTK_RESPONSE_OK when user confirms new cert */
1772  remmina_public_send_notification("remmina-security-trust-all-id", _("Fingerprint automatically accepted"), fingerprint);
1773  rc = GTK_RESPONSE_OK;
1774  return rc;
1775  }
1776  // For markup see https://developer.gnome.org/pygtk/stable/pango-markup-language.html
1777  s = g_strdup_printf(
1778  "<big>%s</big>\n\n%s %s\n%s %s\n%s %s\n\n<big>%s</big>",
1779  // TRANSLATORS: The user is asked to verify a new SSL certificate.
1780  _("Certificate details:"),
1781  // TRANSLATORS: An SSL certificate subject is usually the remote server the user connect to.
1782  _("Subject:"), subject,
1783  // TRANSLATORS: The name or email of the entity that have issued the SSL certificate
1784  _("Issuer:"), issuer,
1785  // TRANSLATORS: An SSL certificate fingerprint, is a hash of a certificate calculated on all certificate's data and its signature.
1786  _("Fingerprint:"), fingerprint,
1787  // TRANSLATORS: The user is asked to accept or refuse a new SSL certificate.
1788  _("Accept certificate?"));
1789  rc = remmina_protocol_widget_dialog(RPWDT_QUESTIONYESNO, gp, 0, s, NULL, NULL, NULL, NULL);
1790  g_free(s);
1791 
1792  /* For compatibility with plugin API: the plugin expects GTK_RESPONSE_OK when user confirms new cert */
1793  return rc == GTK_RESPONSE_YES ? GTK_RESPONSE_OK : rc;
1794 }
1795 
1796 gint remmina_protocol_widget_panel_changed_certificate(RemminaProtocolWidget *gp, const gchar *subject, const gchar *issuer, const gchar *new_fingerprint, const gchar *old_fingerprint)
1797 {
1798  TRACE_CALL(__func__);
1799  gchar *s;
1800  int rc;
1801 
1802  if (remmina_pref_get_boolean("trust_all")) {
1803  /* For compatibility with plugin API: The plugin expects GTK_RESPONSE_OK when user confirms new cert */
1804  remmina_public_send_notification("remmina-security-trust-all-id", _("Fingerprint automatically accepted"), new_fingerprint);
1805  rc = GTK_RESPONSE_OK;
1806  return rc;
1807  }
1808  // For markup see https://developer.gnome.org/pygtk/stable/pango-markup-language.html
1809  s = g_strdup_printf(
1810  "<big>%s</big>\n\n%s %s\n%s %s\n%s %s\n%s %s\n\n<big>%s</big>",
1811  // TRANSLATORS: The user is asked to verify a new SSL certificate.
1812  _("The certificate changed! Details:"),
1813  // TRANSLATORS: An SSL certificate subject is usually the remote server the user connect to.
1814  _("Subject:"), subject,
1815  // TRANSLATORS: The name or email of the entity that have issued the SSL certificate
1816  _("Issuer:"), issuer,
1817  // TRANSLATORS: An SSL certificate fingerprint, is a hash of a certificate calculated on all certificate's data and its signature.
1818  _("Old fingerprint:"), old_fingerprint,
1819  // TRANSLATORS: An SSL certificate fingerprint, is a hash of a certificate calculated on all certificate's data and its signature.
1820  _("New fingerprint:"), new_fingerprint,
1821  // TRANSLATORS: The user is asked to accept or refuse a new SSL certificate.
1822  _("Accept changed certificate?"));
1823  rc = remmina_protocol_widget_dialog(RPWDT_QUESTIONYESNO, gp, 0, s, NULL, NULL, NULL, NULL);
1824  g_free(s);
1825 
1826  /* For compatibility with plugin API: The plugin expects GTK_RESPONSE_OK when user confirms new cert */
1827  return rc == GTK_RESPONSE_YES ? GTK_RESPONSE_OK : rc;
1828 }
1829 
1831 {
1832  TRACE_CALL(__func__);
1833  return g_strdup(gp->priv->username);
1834 }
1835 
1837 {
1838  TRACE_CALL(__func__);
1839  return g_strdup(gp->priv->password);
1840 }
1841 
1843 {
1844  TRACE_CALL(__func__);
1845  return g_strdup(gp->priv->domain);
1846 }
1847 
1849 {
1850  TRACE_CALL(__func__);
1851  return gp->priv->save_password;
1852 }
1853 
1855 {
1856  TRACE_CALL(__func__);
1857  gchar *s;
1858 
1859  s = gp->priv->cacert;
1860  return s && s[0] ? g_strdup(s) : NULL;
1861 }
1862 
1864 {
1865  TRACE_CALL(__func__);
1866  gchar *s;
1867 
1868  s = gp->priv->cacrl;
1869  return s && s[0] ? g_strdup(s) : NULL;
1870 }
1871 
1873 {
1874  TRACE_CALL(__func__);
1875  gchar *s;
1876 
1877  s = gp->priv->clientcert;
1878  return s && s[0] ? g_strdup(s) : NULL;
1879 }
1880 
1882 {
1883  TRACE_CALL(__func__);
1884  gchar *s;
1885 
1886  s = gp->priv->clientkey;
1887  return s && s[0] ? g_strdup(s) : NULL;
1888 }
1889 
1891 {
1892  TRACE_CALL(__func__);
1893 
1894  RemminaFile *remminafile = gp->priv->remmina_file;
1895  gchar *s;
1896  gboolean save = FALSE;
1897 
1899  /* Allow the execution of this function from a non main thread */
1900  RemminaMTExecData *d;
1901  d = (RemminaMTExecData *)g_malloc(sizeof(RemminaMTExecData));
1902  d->func = FUNC_INIT_SAVE_CRED;
1903  d->p.init_save_creds.gp = gp;
1905  g_free(d);
1906  return;
1907  }
1908 
1909  /* Save username and certificates if any; save the password if it’s requested to do so */
1910  s = gp->priv->username;
1911  if (s && s[0]) {
1912  remmina_file_set_string(remminafile, "username", s);
1913  save = TRUE;
1914  }
1915  s = gp->priv->cacert;
1916  if (s && s[0]) {
1917  remmina_file_set_string(remminafile, "cacert", s);
1918  save = TRUE;
1919  }
1920  s = gp->priv->cacrl;
1921  if (s && s[0]) {
1922  remmina_file_set_string(remminafile, "cacrl", s);
1923  save = TRUE;
1924  }
1925  s = gp->priv->clientcert;
1926  if (s && s[0]) {
1927  remmina_file_set_string(remminafile, "clientcert", s);
1928  save = TRUE;
1929  }
1930  s = gp->priv->clientkey;
1931  if (s && s[0]) {
1932  remmina_file_set_string(remminafile, "clientkey", s);
1933  save = TRUE;
1934  }
1935  if (gp->priv->save_password) {
1936  remmina_file_set_string(remminafile, "password", gp->priv->password);
1937  save = TRUE;
1938  }
1939  if (save)
1940  remmina_file_save(remminafile);
1941 }
1942 
1943 
1945 {
1946  TRACE_CALL(__func__);
1947  RemminaMessagePanel *mp;
1948  gchar *s;
1949 
1951  /* Allow the execution of this function from a non main thread */
1952  RemminaMTExecData *d;
1953  d = (RemminaMTExecData *)g_malloc(sizeof(RemminaMTExecData));
1954  d->func = FUNC_PROTOCOLWIDGET_PANELSHOWLISTEN;
1956  d->p.protocolwidget_panelshowlisten.port = port;
1958  g_free(d);
1959  return;
1960  }
1961 
1963  s = g_strdup_printf(
1964  // TRANSLATORS: “%i” is a placeholder for a port number. “%s” is a placeholder for a protocol name (VNC).
1965  _("Listening on port %i for an incoming %s connection…"), port,
1966  remmina_file_get_string(gp->priv->remmina_file, "protocol"));
1967  remmina_message_panel_setup_progress(mp, s, NULL, NULL);
1968  g_free(s);
1969  gp->priv->listen_message_panel = mp;
1970  rco_show_message_panel(gp->cnnobj, mp);
1971 }
1972 
1974 {
1975  TRACE_CALL(__func__);
1976  RemminaMessagePanel *mp;
1977 
1979  /* Allow the execution of this function from a non main thread */
1980  RemminaMTExecData *d;
1981  d = (RemminaMTExecData *)g_malloc(sizeof(RemminaMTExecData));
1982  d->func = FUNC_PROTOCOLWIDGET_MPSHOWRETRY;
1985  g_free(d);
1986  return;
1987  }
1988 
1990  remmina_message_panel_setup_progress(mp, _("Could not authenticate, attempting reconnection…"), NULL, NULL);
1991  gp->priv->retry_message_panel = mp;
1992  rco_show_message_panel(gp->cnnobj, mp);
1993 }
1994 
1996 {
1997  TRACE_CALL(__func__);
1998  printf("Remmina: The %s function is not implemented, and is left here only for plugin API compatibility.\n", __func__);
1999 }
2000 
2002 {
2003  TRACE_CALL(__func__);
2004  printf("Remmina: The %s function is not implemented, and is left here only for plugin API compatibility.\n", __func__);
2005 }
2006 
2008 {
2009  TRACE_CALL(__func__);
2010  gp->priv->chat_window = NULL;
2011 }
2012 
2014  void (*on_send)(RemminaProtocolWidget *gp, const gchar *text), void (*on_destroy)(RemminaProtocolWidget *gp))
2015 {
2016  TRACE_CALL(__func__);
2017  if (gp->priv->chat_window) {
2018  gtk_window_present(GTK_WINDOW(gp->priv->chat_window));
2019  } else {
2020  gp->priv->chat_window = remmina_chat_window_new(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(gp))), name);
2021  g_signal_connect_swapped(G_OBJECT(gp->priv->chat_window), "send", G_CALLBACK(on_send), gp);
2022  g_signal_connect_swapped(G_OBJECT(gp->priv->chat_window), "destroy",
2023  G_CALLBACK(remmina_protocol_widget_chat_on_destroy), gp);
2024  g_signal_connect_swapped(G_OBJECT(gp->priv->chat_window), "destroy", G_CALLBACK(on_destroy), gp);
2025  gtk_widget_show(gp->priv->chat_window);
2026  }
2027 }
2028 
2030 {
2031  TRACE_CALL(__func__);
2032  if (gp->priv->chat_window)
2033  gtk_widget_destroy(gp->priv->chat_window);
2034 }
2035 
2037 {
2038  TRACE_CALL(__func__);
2039  /* This function can be called from a non main thread */
2040 
2041  if (gp->priv->chat_window) {
2043  /* Allow the execution of this function from a non main thread */
2044  RemminaMTExecData *d;
2045  d = (RemminaMTExecData *)g_malloc(sizeof(RemminaMTExecData));
2046  d->func = FUNC_CHAT_RECEIVE;
2047  d->p.chat_receive.gp = gp;
2048  d->p.chat_receive.text = text;
2050  g_free(d);
2051  return;
2052  }
2053  remmina_chat_window_receive(REMMINA_CHAT_WINDOW(gp->priv->chat_window), _("Server"), text);
2054  gtk_window_present(GTK_WINDOW(gp->priv->chat_window));
2055  }
2056 }
2057 
2059 {
2061 
2062  gp->priv->remmina_file = remminafile;
2063  gp->cnnobj = cnnobj;
2064 
2065  /* Locate the protocol plugin */
2067  remmina_file_get_string(remminafile, "protocol"));
2068 
2069  if (!plugin || !plugin->init || !plugin->open_connection) {
2070  // TRANSLATORS: “%s” is a placeholder for a protocol name, like “RDP”.
2071  remmina_protocol_widget_set_error(gp, _("Install the %s protocol plugin first."),
2072  remmina_file_get_string(remminafile, "protocol"));
2073  gp->priv->plugin = NULL;
2074  return;
2075  }
2076  gp->priv->plugin = plugin;
2077  gp->plugin = plugin;
2078 
2079  gp->priv->scalemode = remmina_file_get_int(gp->priv->remmina_file, "scale", FALSE);
2080  gp->priv->scaler_expand = remmina_file_get_int(gp->priv->remmina_file, "scaler_expand", FALSE);
2081 }
2082 
2084 {
2085  return rcw_get_gtkwindow(gp->cnnobj);
2086 }
2087 
2089 {
2090  return rcw_get_gtkviewport(gp->cnnobj);
2091 }
2092 
2094 {
2095  return GTK_WIDGET(g_object_new(REMMINA_TYPE_PROTOCOL_WIDGET, NULL));
2096 }
2097 
2098 /* Send one or more keystrokes to a specific widget by firing key-press and
2099  * key-release events.
2100  * GdkEventType action can be GDK_KEY_PRESS or GDK_KEY_RELEASE or both to
2101  * press the keys and release them in reversed order. */
2102 void remmina_protocol_widget_send_keys_signals(GtkWidget *widget, const guint *keyvals, int keyvals_length, GdkEventType action)
2103 {
2104  TRACE_CALL(__func__);
2105  int i;
2106  GdkEventKey event;
2107  gboolean result;
2108  GdkKeymap *keymap = gdk_keymap_get_for_display(gdk_display_get_default());
2109 
2110  event.window = gtk_widget_get_window(widget);
2111  event.send_event = TRUE;
2112  event.time = GDK_CURRENT_TIME;
2113  event.state = 0;
2114  event.length = 0;
2115  event.string = "";
2116  event.group = 0;
2117 
2118  if (action & GDK_KEY_PRESS) {
2119  /* Press the requested buttons */
2120  event.type = GDK_KEY_PRESS;
2121  for (i = 0; i < keyvals_length; i++) {
2122  event.keyval = keyvals[i];
2123  event.hardware_keycode = remmina_public_get_keycode_for_keyval(keymap, event.keyval);
2124  event.is_modifier = (int)remmina_public_get_modifier_for_keycode(keymap, event.hardware_keycode);
2125  REMMINA_DEBUG("Sending keyval: %u, hardware_keycode: %u", event.keyval, event.hardware_keycode);
2126  g_signal_emit_by_name(G_OBJECT(widget), "key-press-event", &event, &result);
2127  }
2128  }
2129 
2130  if (action & GDK_KEY_RELEASE) {
2131  /* Release the requested buttons in reverse order */
2132  event.type = GDK_KEY_RELEASE;
2133  for (i = (keyvals_length - 1); i >= 0; i--) {
2134  event.keyval = keyvals[i];
2135  event.hardware_keycode = remmina_public_get_keycode_for_keyval(keymap, event.keyval);
2136  event.is_modifier = (int)remmina_public_get_modifier_for_keycode(keymap, event.hardware_keycode);
2137  g_signal_emit_by_name(G_OBJECT(widget), "key-release-event", &event, &result);
2138  }
2139  }
2140 }
2141 
2143 {
2144  TRACE_CALL(__func__);
2145  GdkRectangle rect;
2146  gint w, h;
2147  gint wfile, hfile;
2150 
2151  rco_get_monitor_geometry(gp->cnnobj, &rect);
2152 
2153  /* Integrity check: check that we have a cnnwin visible and get t */
2154 
2155  res_mode = remmina_file_get_int(gp->priv->remmina_file, "resolution_mode", RES_INVALID);
2157  wfile = remmina_file_get_int(gp->priv->remmina_file, "resolution_width", -1);
2158  hfile = remmina_file_get_int(gp->priv->remmina_file, "resolution_height", -1);
2159 
2160  /* If resolution_mode is non-existent (-1), then we try to calculate it
2161  * as we did before having resolution_mode */
2162  if (res_mode == RES_INVALID) {
2163  if (wfile <= 0 || hfile <= 0)
2164  res_mode = RES_USE_INITIAL_WINDOW_SIZE;
2165  else
2166  res_mode = RES_USE_CUSTOM;
2167  }
2168 
2170  /* Use internal window size as remote desktop size */
2171  GtkAllocation al;
2172  gtk_widget_get_allocation(GTK_WIDGET(gp), &al);
2173  /* use a multiple of four to mitigate scaling when remote host rounds up */
2174  w = al.width - al.width % 4;
2175  h = al.height - al.height % 4;
2176  if (w < 10) {
2177  printf("Remmina warning: %s RemminaProtocolWidget w=%d h=%d are too small, adjusting to 640x480\n", __func__, w, h);
2178  w = 640;
2179  h = 480;
2180  }
2181  /* Due to approximations while GTK calculates scaling, (w x h) may exceed our monitor geometry
2182  * Adjust to fit. */
2183  if (w > rect.width)
2184  w = rect.width;
2185  if (h > rect.height)
2186  h = rect.height;
2187  } else if (res_mode == RES_USE_CLIENT) {
2188  w = rect.width;
2189  h = rect.height;
2190  } else {
2191  w = wfile;
2192  h = hfile;
2193  }
2194  gp->priv->profile_remote_width = w;
2195  gp->priv->profile_remote_height = h;
2196 }
gboolean remmina_protocol_widget_get_expand(RemminaProtocolWidget *gp)
-- cgit v1.2.3