Remmina - The GTK+ Remote Desktop Client  v1.4.33
Remmina is a remote desktop client written in GTK+, aiming to be useful for system administrators and travellers, who need to work with lots of remote computers in front of either large monitors or tiny netbooks. Remmina supports multiple network protocols in an integrated and consistent user interface. Currently RDP, VNC, NX, XDMCP and SSH are supported.
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  gboolean user_disconnect;
73  /* ssh_tunnels is an array of RemminaSSHTunnel*
74  * the 1st one is the "main" tunnel, other tunnels are used for example in sftp commands */
75  GPtrArray * ssh_tunnels;
77 
78  GtkWidget * chat_window;
79 
80  gboolean closed;
81 
83 
86  gint multimon;
87 
88  RemminaMessagePanel * connect_message_panel;
89  RemminaMessagePanel * listen_message_panel;
90  RemminaMessagePanel * auth_message_panel;
91  RemminaMessagePanel * retry_message_panel;
92 
93  /* Data saved from the last message_panel when the user confirm */
94  gchar * username;
95  gchar * password;
96  gchar * domain;
97  gboolean save_password;
98 
99  gchar * cacert;
100  gchar * cacrl;
101  gchar * clientcert;
102  gchar * clientkey;
103 };
104 
109 };
110 
111 G_DEFINE_TYPE(RemminaProtocolWidget, remmina_protocol_widget, GTK_TYPE_EVENT_BOX)
112 
113 enum {
114  CONNECT_SIGNAL,
115  DISCONNECT_SIGNAL,
116  DESKTOP_RESIZE_SIGNAL,
117  UPDATE_ALIGN_SIGNAL,
118  LOCK_DYNRES_SIGNAL,
119  UNLOCK_DYNRES_SIGNAL,
121 };
122 
125  const gchar * signal_name;
127 
129 { 0 };
130 
132 {
133  TRACE_CALL(__func__);
134  remmina_protocol_widget_signals[CONNECT_SIGNAL] = g_signal_new("connect", G_TYPE_FROM_CLASS(klass),
135  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaProtocolWidgetClass, connect), NULL, NULL,
136  g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
137  remmina_protocol_widget_signals[DISCONNECT_SIGNAL] = g_signal_new("disconnect", G_TYPE_FROM_CLASS(klass),
138  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaProtocolWidgetClass, disconnect), NULL, NULL,
139  g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
140  remmina_protocol_widget_signals[DESKTOP_RESIZE_SIGNAL] = g_signal_new("desktop-resize", G_TYPE_FROM_CLASS(klass),
141  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaProtocolWidgetClass, desktop_resize), NULL, NULL,
142  g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
143  remmina_protocol_widget_signals[UPDATE_ALIGN_SIGNAL] = g_signal_new("update-align", G_TYPE_FROM_CLASS(klass),
144  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaProtocolWidgetClass, update_align), NULL, NULL,
145  g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
146  remmina_protocol_widget_signals[LOCK_DYNRES_SIGNAL] = g_signal_new("lock-dynres", G_TYPE_FROM_CLASS(klass),
147  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaProtocolWidgetClass, lock_dynres), NULL, NULL,
148  g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
149  remmina_protocol_widget_signals[UNLOCK_DYNRES_SIGNAL] = g_signal_new("unlock-dynres", G_TYPE_FROM_CLASS(klass),
150  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaProtocolWidgetClass, unlock_dynres), NULL, NULL,
151  g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
152 }
153 
154 
156 {
157  TRACE_CALL(__func__);
158  int i;
159 
160  if (gp->priv && gp->priv->ssh_tunnels) {
161  for (i = 0; i < gp->priv->ssh_tunnels->len; i++) {
162 #ifdef HAVE_LIBSSH
164 #else
165  REMMINA_DEBUG("LibSSH support turned off, no need to free SSH tunnel data");
166 #endif
167  }
168  g_ptr_array_set_size(gp->priv->ssh_tunnels, 0);
169  }
170 }
171 
172 
174 {
175  TRACE_CALL(__func__);
176 
177  g_free(gp->priv->username);
178  gp->priv->username = NULL;
179 
180  g_free(gp->priv->password);
181  gp->priv->password = NULL;
182 
183  g_free(gp->priv->domain);
184  gp->priv->domain = NULL;
185 
186  g_free(gp->priv->cacert);
187  gp->priv->cacert = NULL;
188 
189  g_free(gp->priv->cacrl);
190  gp->priv->cacrl = NULL;
191 
192  g_free(gp->priv->clientcert);
193  gp->priv->clientcert = NULL;
194 
195  g_free(gp->priv->clientkey);
196  gp->priv->clientkey = NULL;
197 
198  g_free(gp->priv->features);
199  gp->priv->features = NULL;
200 
201  g_free(gp->priv->error_message);
202  gp->priv->error_message = NULL;
203 
204  g_free(gp->priv->remmina_file);
205  gp->priv->remmina_file = NULL;
206 
207  g_free(gp->priv);
208  gp->priv = NULL;
209 
211 
212  if (gp->priv && gp->priv->ssh_tunnels) {
213  g_ptr_array_free(gp->priv->ssh_tunnels, TRUE);
214  gp->priv->ssh_tunnels = NULL;
215  }
216 }
217 
219 {
220  TRACE_CALL(__func__);
221  GtkWidget *child;
222 
223  child = gtk_bin_get_child(GTK_BIN(gp));
224 
225  if (child) {
226  gtk_widget_set_can_focus(child, TRUE);
227  gtk_widget_grab_focus(child);
228  }
229 }
230 
232 {
233  TRACE_CALL(__func__);
235 
236  priv = g_new0(RemminaProtocolWidgetPriv, 1);
237  gp->priv = priv;
238  gp->priv->user_disconnect = FALSE;
239  gp->priv->closed = TRUE;
240  gp->priv->ssh_tunnels = g_ptr_array_new();
241 
242  g_signal_connect(G_OBJECT(gp), "destroy", G_CALLBACK(remmina_protocol_widget_destroy), NULL);
243 }
244 
246 {
247  TRACE_CALL(__func__);
248  RemminaProtocolWidget *gp = REMMINA_PROTOCOL_WIDGET(data);
249 
250  REMMINA_DEBUG("Opening connection");
251 
253  RemminaProtocolFeature *feature;
254  gint num_plugin;
255  gint num_ssh;
256 
257  gp->priv->closed = FALSE;
258 
259  plugin = gp->priv->plugin;
260  plugin->init(gp);
261 
262  for (num_plugin = 0, feature = (RemminaProtocolFeature *)plugin->features; feature && feature->type; num_plugin++, feature++) {
263  }
264 
265  num_ssh = 0;
266 #ifdef HAVE_LIBSSH
267  if (remmina_file_get_int(gp->priv->remmina_file, "ssh_tunnel_enabled", FALSE))
268  num_ssh += 2;
269 
270 #endif
271  if (num_plugin + num_ssh == 0) {
272  gp->priv->features = NULL;
273  } else {
274  gp->priv->features = g_new0(RemminaProtocolFeature, num_plugin + num_ssh + 1);
275  feature = gp->priv->features;
276  if (plugin->features) {
277  memcpy(feature, plugin->features, sizeof(RemminaProtocolFeature) * num_plugin);
278  feature += num_plugin;
279  }
280 #ifdef HAVE_LIBSSH
281  REMMINA_DEBUG("Have SSH");
282  if (num_ssh) {
284  feature->id = REMMINA_PROTOCOL_FEATURE_TOOL_SSH;
285  feature->opt1 = _("Connect via SSH from a new terminal");
287  feature->opt2 = "utilities-terminal";
289  feature->opt3 = NULL;
291  feature++;
292 
294  feature->id = REMMINA_PROTOCOL_FEATURE_TOOL_SFTP;
295  feature->opt1 = _("Open SFTP transfer…");
297  feature->opt2 = "folder-remote";
299  feature->opt3 = NULL;
301  feature++;
302  }
304 #endif
305  }
306 
307  if (!plugin->open_connection(gp))
309 }
310 
311 static void cancel_open_connection_cb(void *cbdata, int btn)
312 {
314 
316 }
317 
319 {
320  TRACE_CALL(__func__);
321  gchar *s;
322  const gchar *name;
323  RemminaMessagePanel *mp;
324 
325  /* Exec precommand before everything else */
327  remmina_message_panel_setup_progress(mp, _("Executing external commands…"), NULL, NULL);
329 
330  remmina_ext_exec_new(gp->priv->remmina_file, "precommand");
332 
333  name = remmina_file_get_string(gp->priv->remmina_file, "name");
334  // TRANSLATORS: “%s” is a placeholder for the connection profile name
335  s = g_strdup_printf(_("Connecting to “%s”…"), (name ? name : "*"));
336 
339  g_free(s);
340  gp->priv->connect_message_panel = mp;
342 
344 }
345 
346 static gboolean conn_closed_real(gpointer data, int button){
347  TRACE_CALL(__func__);
349 
350  #ifdef HAVE_LIBSSH
351  /* This will close all tunnels */
353 #endif
354  /* Exec postcommand */
355  remmina_ext_exec_new(gp->priv->remmina_file, "postcommand");
356  /* Notify listeners (usually rcw) that the connection is closed */
357  g_signal_emit_by_name(G_OBJECT(gp), "disconnect");
358  return G_SOURCE_REMOVE;
359 
360 }
361 
362 static gboolean conn_closed(gpointer data)
363 {
364  TRACE_CALL(__func__);
366  int disconnect_prompt = remmina_file_get_int(gp->priv->remmina_file, "disconnect-prompt", FALSE);
367  if (!gp->priv->user_disconnect && !gp->priv->has_error && disconnect_prompt){
368  const char* msg = "Plugin Disconnected";
369  if (gp->priv->has_error){
372  }
373  gp->priv->user_disconnect = FALSE;
374  RemminaMessagePanel* mp = remmina_message_panel_new();
377  return G_SOURCE_REMOVE;
378  }
379  else{
380  return conn_closed_real(gp, 0);
381  }
382 
383 }
384 
386 {
387  /* User told us that they closed the connection,
388  * or the connection was closed with a known error,
389  * add async event to main thread to complete our close tasks */
390  TRACE_CALL(__func__);
391  gp->priv->closed = TRUE;
392  g_idle_add(conn_closed, (gpointer)gp);
393 }
394 
395 static gboolean conn_opened(gpointer data)
396 {
397  TRACE_CALL(__func__);
399 
400 #ifdef HAVE_LIBSSH
401  if (gp->priv->ssh_tunnels) {
402  for (guint i = 0; i < gp->priv->ssh_tunnels->len; i++)
404  }
405 #endif
406  if (gp->priv->listen_message_panel) {
408  gp->priv->listen_message_panel = NULL;
409  }
410  if (gp->priv->connect_message_panel) {
412  gp->priv->connect_message_panel = NULL;
413  }
414  if (gp->priv->retry_message_panel) {
416  gp->priv->retry_message_panel = NULL;
417  }
418  g_signal_emit_by_name(G_OBJECT(gp), "connect");
419  return G_SOURCE_REMOVE;
420 }
421 
423 {
424  /* Plugin told us that it opened the connection,
425  * add async event to main thread to complete our close tasks */
426  TRACE_CALL(__func__);
427  g_idle_add(conn_opened, (gpointer)gp);
428 }
429 
430 static gboolean update_align(gpointer data)
431 {
432  TRACE_CALL(__func__);
434 
435  g_signal_emit_by_name(G_OBJECT(gp), "update-align");
436  return G_SOURCE_REMOVE;
437 }
438 
440 {
441  /* Called by the plugin to do updates on rcw */
442  TRACE_CALL(__func__);
443  g_idle_add(update_align, (gpointer)gp);
444 }
445 
446 static gboolean lock_dynres(gpointer data)
447 {
448  TRACE_CALL(__func__);
450 
451  g_signal_emit_by_name(G_OBJECT(gp), "lock-dynres");
452  return G_SOURCE_REMOVE;
453 }
454 
455 static gboolean unlock_dynres(gpointer data)
456 {
457  TRACE_CALL(__func__);
459 
460  g_signal_emit_by_name(G_OBJECT(gp), "unlock-dynres");
461  return G_SOURCE_REMOVE;
462 }
463 
465 {
466  /* Called by the plugin to do updates on rcw */
467  TRACE_CALL(__func__);
468  g_idle_add(lock_dynres, (gpointer)gp);
469 }
470 
472 {
473  /* Called by the plugin to do updates on rcw */
474  TRACE_CALL(__func__);
475  g_idle_add(unlock_dynres, (gpointer)gp);
476 }
477 
478 static gboolean desktop_resize(gpointer data)
479 {
480  TRACE_CALL(__func__);
482 
483  g_signal_emit_by_name(G_OBJECT(gp), "desktop-resize");
484  return G_SOURCE_REMOVE;
485 }
486 
488 {
489  /* Called by the plugin to do updates on rcw */
490  TRACE_CALL(__func__);
491  g_idle_add(desktop_resize, (gpointer)gp);
492 }
493 
494 
496 {
497  TRACE_CALL(__func__);
498 
499  /* kindly ask the protocol plugin to close the connection.
500  * Nothing else is done here. */
501 
502  if (!GTK_IS_WIDGET(gp))
503  return;
504 
505  if (gp->priv->chat_window) {
506  gtk_widget_destroy(gp->priv->chat_window);
507  gp->priv->chat_window = NULL;
508  }
509 
510  if (gp->priv->closed) {
511  /* Connection is already closed by the plugin, but
512  * rcw is asking to close again (usually after an error panel)
513  */
514  /* Clear the current error, or "disconnect" signal func will reshow a panel */
516  g_signal_emit_by_name(G_OBJECT(gp), "disconnect");
517  return;
518  }
519  gp->priv->user_disconnect = TRUE;
520 
521  /* Ask the plugin to close, async.
522  * The plugin will emit a "disconnect" signal on gp to call our
523  * remmina_protocol_widget_on_disconnected() when done */
524  gp->priv->plugin->close_connection(gp);
525 
526  return;
527 }
528 
532 {
533  return gp->priv->plugin->send_keystrokes ? TRUE : FALSE;
534 }
535 
540 {
541  TRACE_CALL(__func__);
542  gchar *keystrokes = g_object_get_data(G_OBJECT(widget), "keystrokes");
543  guint *keyvals;
544  gint i;
545  GdkKeymap *keymap = gdk_keymap_get_for_display(gdk_display_get_default());
546  gunichar character;
547  guint keyval;
548  GdkKeymapKey *keys;
549  gint n_keys;
550 
551  /* Single keystroke replace */
552  typedef struct _KeystrokeReplace {
553  gchar * search;
554  gchar * replace;
555  guint keyval;
556  } KeystrokeReplace;
557  /* Special characters to replace */
558  KeystrokeReplace keystrokes_replaces[] =
559  {
560  { "\\n", "\n", GDK_KEY_Return },
561  { "\\t", "\t", GDK_KEY_Tab },
562  { "\\b", "\b", GDK_KEY_BackSpace },
563  { "\\e", "\e", GDK_KEY_Escape },
564  { "\\\\", "\\", GDK_KEY_backslash },
565  { NULL, NULL, 0 }
566  };
567 
568  /* Keystrokes can only be sent to plugins that accepts them */
570  /* Replace special characters */
571  for (i = 0; keystrokes_replaces[i].replace; i++) {
572  REMMINA_DEBUG("Keystrokes before replacement is \'%s\'", keystrokes);
573  keystrokes = g_strdup(remmina_public_str_replace_in_place(keystrokes,
574  keystrokes_replaces[i].search,
575  keystrokes_replaces[i].replace));
576  REMMINA_DEBUG("Keystrokes after replacement is \'%s\'", keystrokes);
577  }
578  gchar *iter = g_strdup(keystrokes);
579  keyvals = (guint *)g_malloc(strlen(keystrokes));
580  while (TRUE) {
581  /* Process each character in the keystrokes */
582  character = g_utf8_get_char_validated(iter, -1);
583  if (character == 0)
584  break;
585  keyval = gdk_unicode_to_keyval(character);
586  /* Replace all the special character with its keyval */
587  for (i = 0; keystrokes_replaces[i].replace; i++) {
588  if (character == keystrokes_replaces[i].replace[0]) {
589  keys = g_new0(GdkKeymapKey, 1);
590  keyval = keystrokes_replaces[i].keyval;
591  /* A special character was generated, no keyval lookup needed */
592  character = 0;
593  break;
594  }
595  }
596  /* Decode character if it’s not a special character */
597  if (character) {
598  /* get keyval without modifications */
599  if (!gdk_keymap_get_entries_for_keyval(keymap, keyval, &keys, &n_keys)) {
600  g_warning("keyval 0x%04x has no keycode!", keyval);
601  iter = g_utf8_find_next_char(iter, NULL);
602  continue;
603  }
604  }
605  /* Add modifier keys */
606  n_keys = 0;
607  if (keys->level & 1)
608  keyvals[n_keys++] = GDK_KEY_Shift_L;
609  if (keys->level & 2)
610  keyvals[n_keys++] = GDK_KEY_Alt_R;
611  keyvals[n_keys++] = keyval;
612  /* Send keystroke to the plugin */
613  gp->priv->plugin->send_keystrokes(gp, keyvals, n_keys);
614  g_free(keys);
615  /* Process next character in the keystrokes */
616  iter = g_utf8_find_next_char(iter, NULL);
617  }
618  g_free(keyvals);
619  }
620  g_free(keystrokes);
621  return;
622 }
623 
630 void remmina_protocol_widget_send_clip_strokes(GtkClipboard *clipboard, const gchar *clip_text, gpointer data)
631 {
632  TRACE_CALL(__func__);
633  RemminaProtocolWidget *gp = REMMINA_PROTOCOL_WIDGET(data);
634  gchar *text = g_utf8_normalize(clip_text, -1, G_NORMALIZE_DEFAULT_COMPOSE);
635  guint *keyvals;
636  gint i;
637  GdkKeymap *keymap = gdk_keymap_get_for_display(gdk_display_get_default());
638  gunichar character;
639  guint keyval;
640  GdkKeymapKey *keys;
641  gint n_keys;
642 
643  /* Single keystroke replace */
644  typedef struct _KeystrokeReplace {
645  gchar * search;
646  gchar * replace;
647  guint keyval;
648  } KeystrokeReplace;
649  /* Special characters to replace */
650  KeystrokeReplace text_replaces[] =
651  {
652  { "\\n", "\n", GDK_KEY_Return },
653  { "\\t", "\t", GDK_KEY_Tab },
654  { "\\b", "\b", GDK_KEY_BackSpace },
655  { "\\e", "\e", GDK_KEY_Escape },
656  { "\\\\", "\\", GDK_KEY_backslash },
657  { NULL, NULL, 0 }
658  };
659 
661  if (text) {
662  /* Replace special characters */
663  for (i = 0; text_replaces[i].replace; i++) {
664  REMMINA_DEBUG("Text clipboard before replacement is \'%s\'", text);
665  text = g_strdup(remmina_public_str_replace_in_place(text,
666  text_replaces[i].search,
667  text_replaces[i].replace));
668  REMMINA_DEBUG("Text clipboard after replacement is \'%s\'", text);
669  }
670  gchar *iter = g_strdup(text);
671  REMMINA_DEBUG("Iter: %s", iter),
672  keyvals = (guint *)g_malloc(strlen(text));
673  while (TRUE) {
674  /* Process each character in the keystrokes */
675  character = g_utf8_get_char_validated(iter, -1);
676  REMMINA_DEBUG("Char: U+%04" G_GINT32_FORMAT"X", character);
677  if (character == 0)
678  break;
679  keyval = gdk_unicode_to_keyval(character);
680  REMMINA_DEBUG("Keyval: %u", keyval);
681  /* Replace all the special character with its keyval */
682  for (i = 0; text_replaces[i].replace; i++) {
683  if (character == text_replaces[i].replace[0]) {
684  keys = g_new0(GdkKeymapKey, 1);
685  keyval = text_replaces[i].keyval;
686  REMMINA_DEBUG("Special Keyval: %u", keyval);
687  /* A special character was generated, no keyval lookup needed */
688  character = 0;
689  break;
690  }
691  }
692  /* Decode character if it’s not a special character */
693  if (character) {
694  /* get keyval without modifications */
695  if (!gdk_keymap_get_entries_for_keyval(keymap, keyval, &keys, &n_keys)) {
696  REMMINA_WARNING("keyval 0x%04x has no keycode!", keyval);
697  iter = g_utf8_find_next_char(iter, NULL);
698  continue;
699  }
700  }
701  /* Add modifier keys */
702  n_keys = 0;
703  if (keys->level & 1)
704  keyvals[n_keys++] = GDK_KEY_Shift_L;
705  if (keys->level & 2)
706  keyvals[n_keys++] = GDK_KEY_Alt_R;
707  /*
708  * @fixme heap buffer overflow
709  * In some cases, for example sending \t as the only sequence
710  * may lead to a buffer overflow
711  */
712  keyvals[n_keys++] = keyval;
713  /* Send keystroke to the plugin */
714  gp->priv->plugin->send_keystrokes(gp, keyvals, n_keys);
715  g_free(keys);
716  /* Process next character in the keystrokes */
717  iter = g_utf8_find_next_char(iter, NULL);
718  }
719  g_free(keyvals);
720  }
721  g_free(text);
722  }
723  return;
724 }
725 
727 {
728  TRACE_CALL(__func__);
729  GtkClipboard *clipboard;
730 
731  clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
732 
733  /* Request the contents of the clipboard, contents_received will be
734  * called when we do get the contents.
735  */
736  gtk_clipboard_request_text(clipboard,
738 }
739 
741 {
742  TRACE_CALL(__func__);
743  if (!gp->priv->plugin->get_plugin_screenshot) {
744  REMMINA_DEBUG("plugin screenshot function is not implemented, using core Remmina functionality");
745  return FALSE;
746  }
747 
748  return gp->priv->plugin->get_plugin_screenshot(gp, rpsd);
749 }
750 
752 {
753  TRACE_CALL(__func__);
754  if (!gp->priv->plugin->map_event) {
755  REMMINA_DEBUG("Map plugin function not implemented");
756  return FALSE;
757  }
758 
759  REMMINA_DEBUG("Calling plugin mapping function");
760  return gp->priv->plugin->map_event(gp);
761 }
762 
764 {
765  TRACE_CALL(__func__);
766  if (!gp->priv->plugin->unmap_event) {
767  REMMINA_DEBUG("Unmap plugin function not implemented");
768  return FALSE;
769  }
770 
771  REMMINA_DEBUG("Calling plugin unmapping function");
772  return gp->priv->plugin->unmap_event(gp);
773 }
774 
776 {
777  TRACE_CALL(__func__);
778 
779  REMMINA_DEBUG("Emitting signals should be used from the object itself, not from another object");
780  raise(SIGINT);
781 
783  /* Allow the execution of this function from a non main thread */
785  d = (RemminaMTExecData *)g_malloc(sizeof(RemminaMTExecData));
786  d->func = FUNC_PROTOCOLWIDGET_EMIT_SIGNAL;
787  d->p.protocolwidget_emit_signal.signal_name = signal_name;
788  d->p.protocolwidget_emit_signal.gp = gp;
790  g_free(d);
791  return;
792  }
793  g_signal_emit_by_name(G_OBJECT(gp), signal_name);
794 }
795 
797 {
798  TRACE_CALL(__func__);
799  return gp->priv->features;
800 }
801 
803 {
804  TRACE_CALL(__func__);
805  const RemminaProtocolFeature *feature;
806 
807 #ifdef HAVE_LIBSSH
809  remmina_file_get_int(gp->priv->remmina_file, "ssh_tunnel_enabled", FALSE))
810  return TRUE;
811 
812 #endif
813  for (feature = gp->priv->plugin->features; feature && feature->type; feature++)
814  if (feature->type == type)
815  return TRUE;
816  return FALSE;
817 }
818 
820 {
821  TRACE_CALL(__func__);
822  return gp->priv->plugin->query_feature(gp, feature);
823 }
824 
826 {
827  TRACE_CALL(__func__);
828  const RemminaProtocolFeature *feature;
829 
830  for (feature = gp->priv->plugin->features; feature && feature->type; feature++) {
831  if (feature->type == type && (id == 0 || feature->id == id)) {
833  break;
834  }
835  }
836 }
837 
839 {
840  TRACE_CALL(__func__);
841  switch (feature->id) {
842 #ifdef HAVE_LIBSSH
843  case REMMINA_PROTOCOL_FEATURE_TOOL_SSH:
844  if (gp->priv->ssh_tunnels && gp->priv->ssh_tunnels->len > 0) {
847  (RemminaSSHTunnel *)gp->priv->ssh_tunnels->pdata[0], NULL);
848  return;
849  }
850  break;
851 
852  case REMMINA_PROTOCOL_FEATURE_TOOL_SFTP:
853  if (gp->priv->ssh_tunnels && gp->priv->ssh_tunnels->len > 0) {
856  gp->priv->ssh_tunnels->pdata[0], NULL);
857  return;
858  }
859  break;
860 #endif
861  default:
862  break;
863  }
864  gp->priv->plugin->call_feature(gp, feature);
865 }
866 
867 static gboolean remmina_protocol_widget_on_key_press(GtkWidget *widget, GdkEventKey *event, RemminaProtocolWidget *gp)
868 {
869  TRACE_CALL(__func__);
870  if (gp->priv->hostkey_func)
871  return gp->priv->hostkey_func(gp, event->keyval, FALSE);
872  return FALSE;
873 }
874 
875 static gboolean remmina_protocol_widget_on_key_release(GtkWidget *widget, GdkEventKey *event, RemminaProtocolWidget *gp)
876 {
877  TRACE_CALL(__func__);
878  if (gp->priv->hostkey_func)
879  return gp->priv->hostkey_func(gp, event->keyval, TRUE);
880 
881  return FALSE;
882 }
883 
885 {
886  TRACE_CALL(__func__);
887  g_signal_connect(G_OBJECT(widget), "key-press-event", G_CALLBACK(remmina_protocol_widget_on_key_press), gp);
888  g_signal_connect(G_OBJECT(widget), "key-release-event", G_CALLBACK(remmina_protocol_widget_on_key_release), gp);
889 }
890 
892 {
893  TRACE_CALL(__func__);
894  gp->priv->hostkey_func = func;
895 }
896 
897 RemminaMessagePanel *remmina_protocol_widget_mpprogress(RemminaConnectionObject *cnnobj, const gchar *msg, RemminaMessagePanelCallback response_callback, gpointer response_callback_data)
898 {
899  RemminaMessagePanel *mp;
900 
902  /* Allow the execution of this function from a non main thread */
904  d = (RemminaMTExecData *)g_malloc(sizeof(RemminaMTExecData));
905  d->func = FUNC_PROTOCOLWIDGET_MPPROGRESS;
906  d->p.protocolwidget_mpprogress.cnnobj = cnnobj;
907  d->p.protocolwidget_mpprogress.message = msg;
908  d->p.protocolwidget_mpprogress.response_callback = response_callback;
909  d->p.protocolwidget_mpprogress.response_callback_data = response_callback_data;
911  mp = d->p.protocolwidget_mpprogress.ret_mp;
912  g_free(d);
913  return mp;
914  }
915 
917  remmina_message_panel_setup_progress(mp, msg, response_callback, response_callback_data);
918  rco_show_message_panel(cnnobj, mp);
919  return mp;
920 }
921 
922 void remmina_protocol_widget_mpdestroy(RemminaConnectionObject *cnnobj, RemminaMessagePanel *mp)
923 {
925  /* Allow the execution of this function from a non main thread */
927  d = (RemminaMTExecData *)g_malloc(sizeof(RemminaMTExecData));
928  d->func = FUNC_PROTOCOLWIDGET_MPDESTROY;
929  d->p.protocolwidget_mpdestroy.cnnobj = cnnobj;
930  d->p.protocolwidget_mpdestroy.mp = mp;
932  g_free(d);
933  return;
934  }
935  rco_destroy_message_panel(cnnobj, mp);
936 }
937 
938 #ifdef HAVE_LIBSSH
939 static void cancel_init_tunnel_cb(void *cbdata, int btn)
940 {
941  printf("Remmina: Cancelling an opening tunnel is not implemented\n");
942 }
943 
945 {
946  TRACE_CALL(__func__);
947  RemminaSSHTunnel *tunnel;
948  gint ret;
949  gchar *msg;
950  RemminaMessagePanel *mp;
951  gboolean partial = FALSE;
952  gboolean cont = FALSE;
953 
955 
956  REMMINA_DEBUG("Creating SSH tunnel to “%s” via SSH…", REMMINA_SSH(tunnel)->server);
957  // TRANSLATORS: “%s” is a placeholder for an hostname or an IP address.
958  msg = g_strdup_printf(_("Connecting to “%s” via SSH…"), REMMINA_SSH(tunnel)->server);
959 
961  g_free(msg);
962 
963 
964 
965  while (1) {
966  if (!partial) {
967  if (!remmina_ssh_init_session(REMMINA_SSH(tunnel))) {
968  REMMINA_DEBUG("SSH Tunnel init session error: %s", REMMINA_SSH(tunnel)->error);
969  remmina_protocol_widget_set_error(gp, REMMINA_SSH(tunnel)->error);
970  // exit the loop here: OK
971  break;
972  }
973  }
974 
975  ret = remmina_ssh_auth_gui(REMMINA_SSH(tunnel), gp, gp->priv->remmina_file);
976  REMMINA_DEBUG("Tunnel auth returned %d", ret);
977  switch (ret) {
979  REMMINA_DEBUG("Authentication success");
980  break;
982  REMMINA_DEBUG("Continue with the next auth method");
983  partial = TRUE;
984  // Continue the loop: OK
985  continue;
986  break;
988  REMMINA_DEBUG("Reconnecting…");
989  if (REMMINA_SSH(tunnel)->session) {
990  ssh_disconnect(REMMINA_SSH(tunnel)->session);
991  ssh_free(REMMINA_SSH(tunnel)->session);
992  REMMINA_SSH(tunnel)->session = NULL;
993  }
994  g_free(REMMINA_SSH(tunnel)->callback);
995  // Continue the loop: OK
996  continue;
997  break;
999  REMMINA_DEBUG("Interrupted by the user");
1000  // exit the loop here: OK
1001  goto BREAK;
1002  break;
1003  default:
1004  REMMINA_DEBUG("Error during the authentication: %s", REMMINA_SSH(tunnel)->error);
1005  remmina_protocol_widget_set_error(gp, REMMINA_SSH(tunnel)->error);
1006  // exit the loop here: OK
1007  goto BREAK;
1008  }
1009 
1010 
1011  cont = TRUE;
1012  break;
1013  }
1014 
1015 #if 0
1016 
1017  if (!remmina_ssh_init_session(REMMINA_SSH(tunnel))) {
1018  REMMINA_DEBUG("Cannot init SSH session with tunnel struct");
1019  remmina_protocol_widget_set_error(gp, REMMINA_SSH(tunnel)->error);
1020  remmina_ssh_tunnel_free(tunnel);
1021  return NULL;
1022  }
1023 
1024  ret = remmina_ssh_auth_gui(REMMINA_SSH(tunnel), gp, gp->priv->remmina_file);
1025  REMMINA_DEBUG("Tunnel auth returned %d", ret);
1026  if (ret != REMMINA_SSH_AUTH_SUCCESS) {
1027  if (ret != REMMINA_SSH_AUTH_USERCANCEL)
1028  remmina_protocol_widget_set_error(gp, REMMINA_SSH(tunnel)->error);
1029  remmina_ssh_tunnel_free(tunnel);
1030  return NULL;
1031  }
1032 
1033 #endif
1034 
1035 BREAK:
1036  if (!cont) {
1037  remmina_ssh_tunnel_free(tunnel);
1038  return NULL;
1039  }
1041 
1042  return tunnel;
1043 }
1044 #endif
1045 
1046 
1047 #ifdef HAVE_LIBSSH
1048 static void cancel_start_direct_tunnel_cb(void *cbdata, int btn)
1049 {
1050  printf("Remmina: Cancelling start_direct_tunnel is not implemented\n");
1051 }
1052 
1053 static gboolean remmina_protocol_widget_tunnel_destroy(RemminaSSHTunnel *tunnel, gpointer data)
1054 {
1055  TRACE_CALL(__func__);
1056  RemminaProtocolWidget *gp = REMMINA_PROTOCOL_WIDGET(data);
1057  guint idx = 0;
1058 
1059 #if GLIB_CHECK_VERSION(2, 54, 0)
1060  gboolean found = g_ptr_array_find(gp->priv->ssh_tunnels, tunnel, &idx);
1061 #else
1062  int i;
1063  gboolean found = FALSE;
1064  for (i = 0; i < gp->priv->ssh_tunnels->len; i++) {
1065  if ((RemminaSSHTunnel *)gp->priv->ssh_tunnels->pdata[i] == tunnel) {
1066  found = TRUE;
1067  idx = i;
1068  }
1069  }
1070 #endif
1071 
1072  printf("Tunnel %s found at idx = %d\n", found ? "yes": "not", idx);
1073 
1074  if (found) {
1075 #ifdef HAVE_LIBSSH
1076  REMMINA_DEBUG("[Tunnel with idx %u has been disconnected", idx);
1077  remmina_ssh_tunnel_free(tunnel);
1078 #endif
1079  g_ptr_array_remove(gp->priv->ssh_tunnels, tunnel);
1080  }
1081  return TRUE;
1082 }
1083 #endif
1084 
1089 gchar *remmina_protocol_widget_start_direct_tunnel(RemminaProtocolWidget *gp, gint default_port, gboolean port_plus)
1090 {
1091  TRACE_CALL(__func__);
1092  const gchar *server;
1093  const gchar *ssh_tunnel_server;
1094  gchar *ssh_tunnel_host, *srv_host, *dest;
1095  gint srv_port, ssh_tunnel_port = 0;
1096 
1097  REMMINA_DEBUG("SSH tunnel initialization…");
1098 
1099  server = remmina_file_get_string(gp->priv->remmina_file, "server");
1100  ssh_tunnel_server = remmina_file_get_string(gp->priv->remmina_file, "ssh_tunnel_server");
1101 
1102  if (!server)
1103  return g_strdup("");
1104 
1105  if (strstr(g_strdup(server), "unix:///") != NULL) {
1106  REMMINA_DEBUG("%s is a UNIX socket", server);
1107  return g_strdup(server);
1108  }
1109 
1110  REMMINA_DEBUG("Calling remmina_public_get_server_port");
1111  remmina_public_get_server_port(server, default_port, &srv_host, &srv_port);
1112  REMMINA_DEBUG("Calling remmina_public_get_server_port (tunnel)");
1113  remmina_public_get_server_port(ssh_tunnel_server, 22, &ssh_tunnel_host, &ssh_tunnel_port);
1114  REMMINA_DEBUG("server: %s, port: %d", srv_host, srv_port);
1115 
1116  if (port_plus && srv_port < 100)
1117  /* Protocols like VNC supports using instance number :0, :1, etc. as port number. */
1118  srv_port += default_port;
1119 
1120 #ifdef HAVE_LIBSSH
1121  gchar *msg;
1122  RemminaMessagePanel *mp;
1123  RemminaSSHTunnel *tunnel;
1124 
1125  if (!remmina_file_get_int(gp->priv->remmina_file, "ssh_tunnel_enabled", FALSE)) {
1126  dest = g_strdup_printf("[%s]:%i", srv_host, srv_port);
1127  g_free(srv_host);
1128  g_free(ssh_tunnel_host);
1129  return dest;
1130  }
1131 
1133  if (!tunnel) {
1134  g_free(srv_host);
1135  g_free(ssh_tunnel_host);
1136  REMMINA_DEBUG("remmina_protocol_widget_init_tunnel failed with error is %s",
1138  return NULL;
1139  }
1140 
1141  // TRANSLATORS: “%s” is a placeholder for an hostname or an IP address.
1142  msg = g_strdup_printf(_("Connecting to “%s” via SSH…"), server);
1144  g_free(msg);
1145 
1146  if (remmina_file_get_int(gp->priv->remmina_file, "ssh_tunnel_loopback", FALSE)) {
1147  g_free(srv_host);
1148  g_free(ssh_tunnel_host);
1149  ssh_tunnel_host = NULL;
1150  srv_host = g_strdup("127.0.0.1");
1151  }
1152 
1153  REMMINA_DEBUG("Starting tunnel to: %s, port: %d", ssh_tunnel_host, ssh_tunnel_port);
1154  if (!remmina_ssh_tunnel_open(tunnel, srv_host, srv_port, remmina_pref.sshtunnel_port)) {
1155  g_free(srv_host);
1156  g_free(ssh_tunnel_host);
1157  remmina_protocol_widget_set_error(gp, REMMINA_SSH(tunnel)->error);
1158  remmina_ssh_tunnel_free(tunnel);
1159  return NULL;
1160  }
1161  g_free(srv_host);
1162  g_free(ssh_tunnel_host);
1163 
1165 
1167  tunnel->destroy_func_callback_data = (gpointer)gp;
1168 
1169  g_ptr_array_add(gp->priv->ssh_tunnels, tunnel);
1170 
1171  return g_strdup_printf("127.0.0.1:%i", remmina_pref.sshtunnel_port);
1172 
1173 #else
1174 
1175  dest = g_strdup_printf("[%s]:%i", srv_host, srv_port);
1176  g_free(srv_host);
1177  g_free(ssh_tunnel_host);
1178  return dest;
1179 
1180 #endif
1181 }
1182 
1183 #ifdef HAVE_LIBSSH
1184 static void cancel_start_reverse_tunnel_cb(void *cbdata, int btn)
1185 {
1186  printf("Remmina: Cancelling start_reverse_tunnel is not implemented\n");
1187 }
1188 #endif
1189 
1190 
1192 {
1193  TRACE_CALL(__func__);
1194 #ifdef HAVE_LIBSSH
1195  gchar *msg;
1196  RemminaMessagePanel *mp;
1197  RemminaSSHTunnel *tunnel;
1198 
1199  if (!remmina_file_get_int(gp->priv->remmina_file, "ssh_tunnel_enabled", FALSE))
1200  return TRUE;
1201 
1202  if (!(tunnel = remmina_protocol_widget_init_tunnel(gp)))
1203  return FALSE;
1204 
1205  // TRANSLATORS: “%i” is a placeholder for a TCP port number.
1206  msg = g_strdup_printf(_("Awaiting incoming SSH connection on port %i…"), remmina_file_get_int(gp->priv->remmina_file, "listenport", 0));
1208  g_free(msg);
1209 
1210  if (!remmina_ssh_tunnel_reverse(tunnel, remmina_file_get_int(gp->priv->remmina_file, "listenport", 0), local_port)) {
1211  remmina_ssh_tunnel_free(tunnel);
1212  remmina_protocol_widget_set_error(gp, REMMINA_SSH(tunnel)->error);
1213  return FALSE;
1214  }
1216  g_ptr_array_add(gp->priv->ssh_tunnels, tunnel);
1217 #endif
1218 
1219  return TRUE;
1220 }
1221 
1222 gboolean remmina_protocol_widget_ssh_exec(RemminaProtocolWidget *gp, gboolean wait, const gchar *fmt, ...)
1223 {
1224  TRACE_CALL(__func__);
1225 #ifdef HAVE_LIBSSH
1226  RemminaSSHTunnel *tunnel;
1227  ssh_channel channel;
1228  gint status;
1229  gboolean ret = FALSE;
1230  gchar *cmd, *ptr;
1231  va_list args;
1232 
1233  if (gp->priv->ssh_tunnels->len < 1)
1234  return FALSE;
1235 
1236  tunnel = (RemminaSSHTunnel *)gp->priv->ssh_tunnels->pdata[0];
1237 
1238  if ((channel = ssh_channel_new(REMMINA_SSH(tunnel)->session)) == NULL)
1239  return FALSE;
1240 
1241  va_start(args, fmt);
1242  cmd = g_strdup_vprintf(fmt, args);
1243  va_end(args);
1244 
1245  if (ssh_channel_open_session(channel) == SSH_OK &&
1246  ssh_channel_request_exec(channel, cmd) == SSH_OK) {
1247  if (wait) {
1248  ssh_channel_send_eof(channel);
1249  status = ssh_channel_get_exit_status(channel);
1250  ptr = strchr(cmd, ' ');
1251  if (ptr) *ptr = '\0';
1252  switch (status) {
1253  case 0:
1254  ret = TRUE;
1255  break;
1256  case 127:
1257  // TRANSLATORS: “%s” is a place holder for a unix command path.
1258  remmina_ssh_set_application_error(REMMINA_SSH(tunnel),
1259  _("The “%s” command is not available on the SSH server."), cmd);
1260  break;
1261  default:
1262  // TRANSLATORS: “%s” is a place holder for a unix command path. “%i” is a placeholder for an error code number.
1263  remmina_ssh_set_application_error(REMMINA_SSH(tunnel),
1264  _("Could not run the “%s” command on the SSH server (status = %i)."), cmd, status);
1265  break;
1266  }
1267  } else {
1268  ret = TRUE;
1269  }
1270  } else {
1271  // TRANSLATORS: %s is a placeholder for an error message
1272  remmina_ssh_set_error(REMMINA_SSH(tunnel), _("Could not run command. %s"));
1273  }
1274  g_free(cmd);
1275  if (wait)
1276  ssh_channel_close(channel);
1277  ssh_channel_free(channel);
1278  return ret;
1279 
1280 #else
1281 
1282  return FALSE;
1283 
1284 #endif
1285 }
1286 
1287 #ifdef HAVE_LIBSSH
1289 {
1290  TRACE_CALL(__func__);
1291  RemminaProtocolWidget *gp = REMMINA_PROTOCOL_WIDGET(data);
1292  gchar *server;
1293  gint port;
1294  gboolean ret;
1295 
1296  REMMINA_DEBUG("Calling remmina_public_get_server_port");
1297  remmina_public_get_server_port(remmina_file_get_string(gp->priv->remmina_file, "server"), 177, &server, &port);
1298  ret = ((RemminaXPortTunnelInitFunc)gp->priv->init_func)(gp,
1299  tunnel->remotedisplay, (tunnel->bindlocalhost ? "localhost" : server), port);
1300  g_free(server);
1301 
1302  return ret;
1303 }
1304 
1306 {
1307  TRACE_CALL(__func__);
1308  return TRUE;
1309 }
1310 
1312 {
1313  TRACE_CALL(__func__);
1314  RemminaProtocolWidget *gp = REMMINA_PROTOCOL_WIDGET(data);
1315 
1316  if (REMMINA_SSH(tunnel)->error)
1317  remmina_protocol_widget_set_error(gp, "%s", REMMINA_SSH(tunnel)->error);
1318 
1319  IDLE_ADD((GSourceFunc)remmina_protocol_widget_close_connection, gp);
1320  return TRUE;
1321 }
1322 #endif
1323 #ifdef HAVE_LIBSSH
1324 static void cancel_connect_xport_cb(void *cbdata, int btn)
1325 {
1326  printf("Remmina: Cancelling an XPort connection is not implemented\n");
1327 }
1328 #endif
1330 {
1331  TRACE_CALL(__func__);
1332 #ifdef HAVE_LIBSSH
1333  gboolean bindlocalhost;
1334  gchar *server;
1335  gchar *msg;
1336  RemminaMessagePanel *mp;
1337  RemminaSSHTunnel *tunnel;
1338 
1339  if (!(tunnel = remmina_protocol_widget_init_tunnel(gp))) return FALSE;
1340 
1341  // TRANSLATORS: “%s” is a placeholder for a hostname or IP address.
1342  msg = g_strdup_printf(_("Connecting to %s via SSH…"), remmina_file_get_string(gp->priv->remmina_file, "server"));
1344  g_free(msg);
1345 
1346  gp->priv->init_func = init_func;
1350  tunnel->callback_data = gp;
1351 
1352  REMMINA_DEBUG("Calling remmina_public_get_server_port");
1353  remmina_public_get_server_port(remmina_file_get_string(gp->priv->remmina_file, "server"), 0, &server, NULL);
1354  bindlocalhost = (g_strcmp0(REMMINA_SSH(tunnel)->server, server) == 0);
1355  g_free(server);
1356 
1357  if (!remmina_ssh_tunnel_xport(tunnel, bindlocalhost)) {
1358  remmina_protocol_widget_set_error(gp, "Could not open channel, %s",
1359  ssh_get_error(REMMINA_SSH(tunnel)->session));
1360  remmina_ssh_tunnel_free(tunnel);
1361  return FALSE;
1362  }
1363 
1365  g_ptr_array_add(gp->priv->ssh_tunnels, tunnel);
1366 
1367  return TRUE;
1368 
1369 #else
1370  return FALSE;
1371 #endif
1372 }
1373 
1375 {
1376  TRACE_CALL(__func__);
1377 #ifdef HAVE_LIBSSH
1378  RemminaSSHTunnel *tunnel;
1379  if (gp->priv->ssh_tunnels->len < 1)
1380  return;
1381  tunnel = (RemminaSSHTunnel *)gp->priv->ssh_tunnels->pdata[0];
1382  if (tunnel->localdisplay) g_free(tunnel->localdisplay);
1383  tunnel->localdisplay = g_strdup_printf("unix:%i", display);
1384 #endif
1385 }
1386 
1388 {
1389  TRACE_CALL(__func__);
1390  /* Returns the width of remote desktop as chosen by the user profile */
1391  return gp->priv->profile_remote_width;
1392 }
1393 
1395 {
1396  TRACE_CALL(__func__);
1397  /* Returns ehenever multi monitor is enabled (1) */
1398  gp->priv->multimon = remmina_file_get_int(gp->priv->remmina_file, "multimon", -1);
1399  REMMINA_DEBUG("Multi monitor is set to %d", gp->priv->multimon);
1400  return gp->priv->multimon;
1401 }
1402 
1404 {
1405  TRACE_CALL(__func__);
1406  /* Returns the height of remote desktop as chosen by the user profile */
1407  return gp->priv->profile_remote_height;
1408 }
1409 
1411 {
1412  TRACE_CALL(__func__);
1413  return gp ? gp->plugin ? gp->plugin->name : NULL : NULL;
1414 }
1415 
1417 {
1418  TRACE_CALL(__func__);
1419  return gp->priv->width;
1420 }
1421 
1423 {
1424  TRACE_CALL(__func__);
1425  gp->priv->width = width;
1426 }
1427 
1429 {
1430  TRACE_CALL(__func__);
1431  return gp->priv->height;
1432 }
1433 
1435 {
1436  TRACE_CALL(__func__);
1437  gp->priv->height = height;
1438 }
1439 
1441 {
1442  TRACE_CALL(__func__);
1443  return gp->priv->scalemode;
1444 }
1445 
1447 {
1448  TRACE_CALL(__func__);
1449  gp->priv->scalemode = scalemode;
1450 }
1451 
1453 {
1454  TRACE_CALL(__func__);
1455  return gp->priv->scaler_expand;
1456 }
1457 
1459 {
1460  TRACE_CALL(__func__);
1461  gp->priv->scaler_expand = expand;
1462  return;
1463 }
1464 
1466 {
1467  TRACE_CALL(__func__);
1468  return gp->priv->has_error;
1469 }
1470 
1472 {
1473  TRACE_CALL(__func__);
1474  return gp->priv->error_message;
1475 }
1476 
1478 {
1479  TRACE_CALL(__func__);
1480  va_list args;
1481 
1482  if (gp->priv->error_message) g_free(gp->priv->error_message);
1483 
1484  if (fmt == NULL) {
1485  gp->priv->has_error = FALSE;
1486  gp->priv->error_message = NULL;
1487  return;
1488  }
1489 
1490  va_start(args, fmt);
1491  gp->priv->error_message = g_strdup_vprintf(fmt, args);
1492  va_end(args);
1493 
1494  gp->priv->has_error = TRUE;
1495 }
1496 
1498 {
1499  TRACE_CALL(__func__);
1500  return gp->priv->closed;
1501 }
1502 
1504 {
1505  TRACE_CALL(__func__);
1506  return gp->priv->remmina_file;
1507 }
1508 
1510  /* Input data */
1512  gchar * title;
1517  enum panel_type dtype;
1520  /* Running status */
1521  pthread_mutex_t pt_mutex;
1522  pthread_cond_t pt_cond;
1523  /* Output/retval */
1525 };
1526 
1527 static void authpanel_mt_cb(void *user_data, int button)
1528 {
1530 
1531  d->rcbutton = button;
1532  if (button == GTK_RESPONSE_OK) {
1533  if (d->dtype == RPWDT_AUTH) {
1538  } else if (d->dtype == RPWDT_AUTHX509) {
1543  }
1544  }
1545 
1546  if (d->called_from_subthread) {
1547  /* Hide and destroy message panel, we can do it now because we are on the main thread */
1549 
1550  /* Awake the locked subthread, when called from subthread */
1551  pthread_mutex_lock(&d->pt_mutex);
1552  pthread_cond_signal(&d->pt_cond);
1553  pthread_mutex_unlock(&d->pt_mutex);
1554  } else {
1555  /* Signal completion, when called from main thread. Message panel will be destroyed by the caller */
1557  }
1558 }
1559 
1560 static gboolean remmina_protocol_widget_dialog_mt_setup(gpointer user_data)
1561 {
1563 
1564  RemminaFile *remminafile = d->gp->priv->remmina_file;
1565  RemminaMessagePanel *mp;
1566  const gchar *s;
1567 
1568  if (d->gp->cnnobj == NULL)
1569  return FALSE;
1570 
1572 
1573  if (d->dtype == RPWDT_AUTH) {
1581  } else if (d->dtype == RPWDT_QUESTIONYESNO) {
1583  } else if (d->dtype == RPWDT_AUTHX509) {
1585  if ((s = remmina_file_get_string(remminafile, "cacert")) != NULL)
1587  if ((s = remmina_file_get_string(remminafile, "cacrl")) != NULL)
1589  if ((s = remmina_file_get_string(remminafile, "clientcert")) != NULL)
1591  if ((s = remmina_file_get_string(remminafile, "clientkey")) != NULL)
1593  }
1594 
1595  d->gp->priv->auth_message_panel = mp;
1596  rco_show_message_panel(d->gp->cnnobj, mp);
1597 
1598  return FALSE;
1599 }
1600 
1601 typedef struct {
1602  RemminaMessagePanel * mp;
1603  GMainLoop * loop;
1604  gint response;
1605  gboolean destroyed;
1606 } MpRunInfo;
1607 
1608 static void shutdown_loop(MpRunInfo *mpri)
1609 {
1610  if (g_main_loop_is_running(mpri->loop))
1611  g_main_loop_quit(mpri->loop);
1612 }
1613 
1614 static void run_response_handler(RemminaMessagePanel *mp, gint response_id, gpointer data)
1615 {
1616  MpRunInfo *mpri = (MpRunInfo *)data;
1617 
1618  mpri->response = response_id;
1619  shutdown_loop(mpri);
1620 }
1621 
1622 static void run_unmap_handler(RemminaMessagePanel *mp, gpointer data)
1623 {
1624  MpRunInfo *mpri = (MpRunInfo *)data;
1625 
1626  mpri->response = GTK_RESPONSE_CANCEL;
1627  shutdown_loop(mpri);
1628 }
1629 
1630 static void run_destroy_handler(RemminaMessagePanel *mp, gpointer data)
1631 {
1632  MpRunInfo *mpri = (MpRunInfo *)data;
1633 
1634  mpri->destroyed = TRUE;
1635  mpri->response = GTK_RESPONSE_CANCEL;
1636  shutdown_loop(mpri);
1637 }
1638 
1640  const gchar *title, const gchar *default_username, const gchar *default_password, const gchar *default_domain,
1641  const gchar *strpasswordlabel)
1642 {
1643  TRACE_CALL(__func__);
1644 
1646  int rcbutton;
1647 
1648  d->gp = gp;
1649  d->pflags = pflags;
1650  d->dtype = dtype;
1651  d->title = g_strdup(title);
1652  d->strpasswordlabel = g_strdup(strpasswordlabel);
1653  d->default_username = g_strdup(default_username);
1654  d->default_password = g_strdup(default_password);
1655  d->default_domain = g_strdup(default_domain);
1656  d->called_from_subthread = FALSE;
1657 
1659  /* Run the MessagePanel in main thread, in a very similar way of gtk_dialog_run() */
1660  MpRunInfo mpri = { NULL, NULL, GTK_RESPONSE_CANCEL, FALSE };
1661 
1662  gulong unmap_handler;
1663  gulong destroy_handler;
1664  gulong response_handler;
1665 
1667 
1668  mpri.mp = d->gp->priv->auth_message_panel;
1669 
1670  if (!gtk_widget_get_visible(GTK_WIDGET(mpri.mp)))
1671  gtk_widget_show(GTK_WIDGET(mpri.mp));
1672  response_handler = g_signal_connect(mpri.mp, "response", G_CALLBACK(run_response_handler), &mpri);
1673  unmap_handler = g_signal_connect(mpri.mp, "unmap", G_CALLBACK(run_unmap_handler), &mpri);
1674  destroy_handler = g_signal_connect(mpri.mp, "destroy", G_CALLBACK(run_destroy_handler), &mpri);
1675 
1676  g_object_ref(mpri.mp);
1677 
1678  mpri.loop = g_main_loop_new(NULL, FALSE);
1679  g_main_loop_run(mpri.loop);
1680  g_main_loop_unref(mpri.loop);
1681 
1682  if (!mpri.destroyed) {
1683  g_signal_handler_disconnect(mpri.mp, response_handler);
1684  g_signal_handler_disconnect(mpri.mp, destroy_handler);
1685  g_signal_handler_disconnect(mpri.mp, unmap_handler);
1686  }
1687  g_object_unref(mpri.mp);
1688 
1690 
1691  rcbutton = mpri.response;
1692  } else {
1693  d->called_from_subthread = TRUE;
1694  // pthread_cleanup_push(ptcleanup, (void*)d);
1695  pthread_cond_init(&d->pt_cond, NULL);
1696  pthread_mutex_init(&d->pt_mutex, NULL);
1698  pthread_mutex_lock(&d->pt_mutex);
1699  pthread_cond_wait(&d->pt_cond, &d->pt_mutex);
1700  // pthread_cleanup_pop(0);
1701  pthread_mutex_destroy(&d->pt_mutex);
1702  pthread_cond_destroy(&d->pt_cond);
1703 
1704  rcbutton = d->rcbutton;
1705  }
1706 
1707  g_free(d->title);
1708  g_free(d->strpasswordlabel);
1709  g_free(d->default_username);
1710  g_free(d->default_password);
1711  g_free(d->default_domain);
1712  g_free(d);
1713  return rcbutton;
1714 }
1715 
1717 {
1718  return remmina_protocol_widget_dialog(RPWDT_QUESTIONYESNO, gp, 0, msg, NULL, NULL, NULL, NULL);
1719 }
1720 
1722  const gchar *title, const gchar *default_username, const gchar *default_password, const gchar *default_domain, const gchar *password_prompt)
1723 {
1724  TRACE_CALL(__func__);
1725  return remmina_protocol_widget_dialog(RPWDT_AUTH, gp, pflags, title, default_username,
1726  default_password, default_domain, password_prompt == NULL ? _("Password") : password_prompt);
1727 }
1728 
1729 gint remmina_protocol_widget_panel_authuserpwd_ssh_tunnel(RemminaProtocolWidget *gp, gboolean want_domain, gboolean allow_password_saving)
1730 {
1731  TRACE_CALL(__func__);
1732  unsigned pflags;
1733  RemminaFile *remminafile = gp->priv->remmina_file;
1734  const gchar *username, *password;
1735 
1737  if (remmina_file_get_filename(remminafile) != NULL &&
1738  !remminafile->prevent_saving && allow_password_saving)
1740 
1741  username = remmina_file_get_string(remminafile, "ssh_tunnel_username");
1742  password = remmina_file_get_string(remminafile, "ssh_tunnel_password");
1743 
1744  return remmina_protocol_widget_dialog(RPWDT_AUTH, gp, pflags, _("Type in SSH username and password."), username,
1745  password, NULL, _("Password"));
1746 }
1747 
1748 /*
1749  * gint remmina_protocol_widget_panel_authpwd(RemminaProtocolWidget* gp, RemminaAuthpwdType authpwd_type, gboolean allow_password_saving)
1750  * {
1751  * TRACE_CALL(__func__);
1752  * unsigned pflags;
1753  * RemminaFile* remminafile = gp->priv->remmina_file;
1754  * char *password_prompt;
1755  * int rc;
1756  *
1757  * pflags = 0;
1758  * if (remmina_file_get_filename(remminafile) != NULL &&
1759  * !remminafile->prevent_saving && allow_password_saving)
1760  * pflags |= REMMINA_MESSAGE_PANEL_FLAG_SAVEPASSWORD;
1761  *
1762  * switch (authpwd_type) {
1763  * case REMMINA_AUTHPWD_TYPE_PROTOCOL:
1764  * password_prompt = g_strdup_printf(_("%s password"), remmina_file_get_string(remminafile, "protocol"));
1765  * break;
1766  * case REMMINA_AUTHPWD_TYPE_SSH_PWD:
1767  * password_prompt = g_strdup(_("SSH password"));
1768  * break;
1769  * case REMMINA_AUTHPWD_TYPE_SSH_PRIVKEY:
1770  * password_prompt = g_strdup(_("SSH private key passphrase"));
1771  * break;
1772  * default:
1773  * password_prompt = g_strdup(_("Password"));
1774  * break;
1775  * }
1776  *
1777  * rc = remmina_protocol_widget_dialog(RPWDT_AUTH, gp, pflags, password_prompt);
1778  * g_free(password_prompt);
1779  * return rc;
1780  *
1781  * }
1782  */
1784 {
1785  TRACE_CALL(__func__);
1786 
1787  return remmina_protocol_widget_dialog(RPWDT_AUTHX509, gp, 0, NULL, NULL, NULL, NULL, NULL);
1788 }
1789 
1790 
1791 gint remmina_protocol_widget_panel_new_certificate(RemminaProtocolWidget *gp, const gchar *subject, const gchar *issuer, const gchar *fingerprint)
1792 {
1793  TRACE_CALL(__func__);
1794  gchar *s;
1795  int rc;
1796 
1797  if (remmina_pref_get_boolean("trust_all")) {
1798  /* For compatibility with plugin API: The plugin expects GTK_RESPONSE_OK when user confirms new cert */
1799  remmina_public_send_notification("remmina-security-trust-all-id", _("Fingerprint automatically accepted"), fingerprint);
1800  rc = GTK_RESPONSE_OK;
1801  return rc;
1802  }
1803  // For markup see https://developer.gnome.org/pygtk/stable/pango-markup-language.html
1804  s = g_strdup_printf(
1805  "<big>%s</big>\n\n%s %s\n%s %s\n%s %s\n\n<big>%s</big>",
1806  // TRANSLATORS: The user is asked to verify a new SSL certificate.
1807  _("Certificate details:"),
1808  // TRANSLATORS: An SSL certificate subject is usually the remote server the user connect to.
1809  _("Subject:"), subject,
1810  // TRANSLATORS: The name or email of the entity that have issued the SSL certificate
1811  _("Issuer:"), issuer,
1812  // TRANSLATORS: An SSL certificate fingerprint, is a hash of a certificate calculated on all certificate's data and its signature.
1813  _("Fingerprint:"), fingerprint,
1814  // TRANSLATORS: The user is asked to accept or refuse a new SSL certificate.
1815  _("Accept certificate?"));
1816  rc = remmina_protocol_widget_dialog(RPWDT_QUESTIONYESNO, gp, 0, s, NULL, NULL, NULL, NULL);
1817  g_free(s);
1818 
1819  /* For compatibility with plugin API: the plugin expects GTK_RESPONSE_OK when user confirms new cert */
1820  return rc == GTK_RESPONSE_YES ? GTK_RESPONSE_OK : rc;
1821 }
1822 
1823 gint remmina_protocol_widget_panel_changed_certificate(RemminaProtocolWidget *gp, const gchar *subject, const gchar *issuer, const gchar *new_fingerprint, const gchar *old_fingerprint)
1824 {
1825  TRACE_CALL(__func__);
1826  gchar *s;
1827  int rc;
1828 
1829  if (remmina_pref_get_boolean("trust_all")) {
1830  /* For compatibility with plugin API: The plugin expects GTK_RESPONSE_OK when user confirms new cert */
1831  remmina_public_send_notification("remmina-security-trust-all-id", _("Fingerprint automatically accepted"), new_fingerprint);
1832  rc = GTK_RESPONSE_OK;
1833  return rc;
1834  }
1835  // For markup see https://developer.gnome.org/pygtk/stable/pango-markup-language.html
1836  s = g_strdup_printf(
1837  "<big>%s</big>\n\n%s %s\n%s %s\n%s %s\n%s %s\n\n<big>%s</big>",
1838  // TRANSLATORS: The user is asked to verify a new SSL certificate.
1839  _("The certificate changed! Details:"),
1840  // TRANSLATORS: An SSL certificate subject is usually the remote server the user connect to.
1841  _("Subject:"), subject,
1842  // TRANSLATORS: The name or email of the entity that have issued the SSL certificate
1843  _("Issuer:"), issuer,
1844  // TRANSLATORS: An SSL certificate fingerprint, is a hash of a certificate calculated on all certificate's data and its signature.
1845  _("Old fingerprint:"), old_fingerprint,
1846  // TRANSLATORS: An SSL certificate fingerprint, is a hash of a certificate calculated on all certificate's data and its signature.
1847  _("New fingerprint:"), new_fingerprint,
1848  // TRANSLATORS: The user is asked to accept or refuse a new SSL certificate.
1849  _("Accept changed certificate?"));
1850  rc = remmina_protocol_widget_dialog(RPWDT_QUESTIONYESNO, gp, 0, s, NULL, NULL, NULL, NULL);
1851  g_free(s);
1852 
1853  /* For compatibility with plugin API: The plugin expects GTK_RESPONSE_OK when user confirms new cert */
1854  return rc == GTK_RESPONSE_YES ? GTK_RESPONSE_OK : rc;
1855 }
1856 
1858 {
1859  TRACE_CALL(__func__);
1860  return g_strdup(gp->priv->username);
1861 }
1862 
1864 {
1865  TRACE_CALL(__func__);
1866  return g_strdup(gp->priv->password);
1867 }
1868 
1870 {
1871  TRACE_CALL(__func__);
1872  return g_strdup(gp->priv->domain);
1873 }
1874 
1876 {
1877  TRACE_CALL(__func__);
1878  return gp->priv->save_password;
1879 }
1880 
1882 {
1883  TRACE_CALL(__func__);
1884  gchar *s;
1885 
1886  s = gp->priv->cacert;
1887  return s && s[0] ? g_strdup(s) : NULL;
1888 }
1889 
1891 {
1892  TRACE_CALL(__func__);
1893  gchar *s;
1894 
1895  s = gp->priv->cacrl;
1896  return s && s[0] ? g_strdup(s) : NULL;
1897 }
1898 
1900 {
1901  TRACE_CALL(__func__);
1902  gchar *s;
1903 
1904  s = gp->priv->clientcert;
1905  return s && s[0] ? g_strdup(s) : NULL;
1906 }
1907 
1909 {
1910  TRACE_CALL(__func__);
1911  gchar *s;
1912 
1913  s = gp->priv->clientkey;
1914  return s && s[0] ? g_strdup(s) : NULL;
1915 }
1916 
1918 {
1919  TRACE_CALL(__func__);
1920 
1921  RemminaFile *remminafile = gp->priv->remmina_file;
1922  gchar *s;
1923  gboolean save = FALSE;
1924 
1926  /* Allow the execution of this function from a non main thread */
1927  RemminaMTExecData *d;
1928  d = (RemminaMTExecData *)g_malloc(sizeof(RemminaMTExecData));
1929  d->func = FUNC_INIT_SAVE_CRED;
1930  d->p.init_save_creds.gp = gp;
1932  g_free(d);
1933  return;
1934  }
1935 
1936  /* Save username and certificates if any; save the password if it’s requested to do so */
1937  s = gp->priv->username;
1938  if (s && s[0]) {
1939  remmina_file_set_string(remminafile, "username", s);
1940  save = TRUE;
1941  }
1942  s = gp->priv->cacert;
1943  if (s && s[0]) {
1944  remmina_file_set_string(remminafile, "cacert", s);
1945  save = TRUE;
1946  }
1947  s = gp->priv->cacrl;
1948  if (s && s[0]) {
1949  remmina_file_set_string(remminafile, "cacrl", s);
1950  save = TRUE;
1951  }
1952  s = gp->priv->clientcert;
1953  if (s && s[0]) {
1954  remmina_file_set_string(remminafile, "clientcert", s);
1955  save = TRUE;
1956  }
1957  s = gp->priv->clientkey;
1958  if (s && s[0]) {
1959  remmina_file_set_string(remminafile, "clientkey", s);
1960  save = TRUE;
1961  }
1962  if (gp->priv->save_password) {
1963  remmina_file_set_string(remminafile, "password", gp->priv->password);
1964  save = TRUE;
1965  }
1966  if (save)
1967  remmina_file_save(remminafile);
1968 }
1969 
1970 
1972 {
1973  TRACE_CALL(__func__);
1974  RemminaMessagePanel *mp;
1975  gchar *s;
1976 
1978  /* Allow the execution of this function from a non main thread */
1979  RemminaMTExecData *d;
1980  d = (RemminaMTExecData *)g_malloc(sizeof(RemminaMTExecData));
1981  d->func = FUNC_PROTOCOLWIDGET_PANELSHOWLISTEN;
1983  d->p.protocolwidget_panelshowlisten.port = port;
1985  g_free(d);
1986  return;
1987  }
1988 
1990  s = g_strdup_printf(
1991  // TRANSLATORS: “%i” is a placeholder for a port number. “%s” is a placeholder for a protocol name (VNC).
1992  _("Listening on port %i for an incoming %s connection…"), port,
1993  remmina_file_get_string(gp->priv->remmina_file, "protocol"));
1994  remmina_message_panel_setup_progress(mp, s, NULL, NULL);
1995  g_free(s);
1996  gp->priv->listen_message_panel = mp;
1997  rco_show_message_panel(gp->cnnobj, mp);
1998 }
1999 
2001 {
2002  TRACE_CALL(__func__);
2003  RemminaMessagePanel *mp;
2004 
2006  /* Allow the execution of this function from a non main thread */
2007  RemminaMTExecData *d;
2008  d = (RemminaMTExecData *)g_malloc(sizeof(RemminaMTExecData));
2009  d->func = FUNC_PROTOCOLWIDGET_MPSHOWRETRY;
2012  g_free(d);
2013  return;
2014  }
2015 
2017  remmina_message_panel_setup_progress(mp, _("Could not authenticate, attempting reconnection…"), NULL, NULL);
2018  gp->priv->retry_message_panel = mp;
2019  rco_show_message_panel(gp->cnnobj, mp);
2020 }
2021 
2023 {
2024  TRACE_CALL(__func__);
2025  printf("Remmina: The %s function is not implemented, and is left here only for plugin API compatibility.\n", __func__);
2026 }
2027 
2029 {
2030  TRACE_CALL(__func__);
2031  printf("Remmina: The %s function is not implemented, and is left here only for plugin API compatibility.\n", __func__);
2032 }
2033 
2035 {
2036  TRACE_CALL(__func__);
2037  gp->priv->chat_window = NULL;
2038 }
2039 
2041  void (*on_send)(RemminaProtocolWidget *gp, const gchar *text), void (*on_destroy)(RemminaProtocolWidget *gp))
2042 {
2043  TRACE_CALL(__func__);
2044  if (gp->priv->chat_window) {
2045  gtk_window_present(GTK_WINDOW(gp->priv->chat_window));
2046  } else {
2047  gp->priv->chat_window = remmina_chat_window_new(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(gp))), name);
2048  g_signal_connect_swapped(G_OBJECT(gp->priv->chat_window), "send", G_CALLBACK(on_send), gp);
2049  g_signal_connect_swapped(G_OBJECT(gp->priv->chat_window), "destroy",
2050  G_CALLBACK(remmina_protocol_widget_chat_on_destroy), gp);
2051  g_signal_connect_swapped(G_OBJECT(gp->priv->chat_window), "destroy", G_CALLBACK(on_destroy), gp);
2052  gtk_widget_show(gp->priv->chat_window);
2053  }
2054 }
2055 
2057 {
2058  TRACE_CALL(__func__);
2059  if (gp->priv->chat_window)
2060  gtk_widget_destroy(gp->priv->chat_window);
2061 }
2062 
2064 {
2065  TRACE_CALL(__func__);
2066  /* This function can be called from a non main thread */
2067 
2068  if (gp->priv->chat_window) {
2070  /* Allow the execution of this function from a non main thread */
2071  RemminaMTExecData *d;
2072  d = (RemminaMTExecData *)g_malloc(sizeof(RemminaMTExecData));
2073  d->func = FUNC_CHAT_RECEIVE;
2074  d->p.chat_receive.gp = gp;
2075  d->p.chat_receive.text = text;
2077  g_free(d);
2078  return;
2079  }
2080  remmina_chat_window_receive(REMMINA_CHAT_WINDOW(gp->priv->chat_window), _("Server"), text);
2081  gtk_window_present(GTK_WINDOW(gp->priv->chat_window));
2082  }
2083 }
2084 
2086 {
2088 
2089  gp->priv->remmina_file = remminafile;
2090  gp->cnnobj = cnnobj;
2091 
2092  /* Locate the protocol plugin */
2094  remmina_file_get_string(remminafile, "protocol"));
2095 
2096  if (!plugin || !plugin->init || !plugin->open_connection) {
2097  // TRANSLATORS: “%s” is a placeholder for a protocol name, like “RDP”.
2098  remmina_protocol_widget_set_error(gp, _("Install the %s protocol plugin first."),
2099  remmina_file_get_string(remminafile, "protocol"));
2100  gp->priv->plugin = NULL;
2101  return;
2102  }
2103  gp->priv->plugin = plugin;
2104  gp->plugin = plugin;
2105 
2106  gp->priv->scalemode = remmina_file_get_int(gp->priv->remmina_file, "scale", FALSE);
2107  gp->priv->scaler_expand = remmina_file_get_int(gp->priv->remmina_file, "scaler_expand", FALSE);
2108 }
2109 
2111 {
2112  return rcw_get_gtkwindow(gp->cnnobj);
2113 }
2114 
2116 {
2117  return rcw_get_gtkviewport(gp->cnnobj);
2118 }
2119 
2121 {
2122  return GTK_WIDGET(g_object_new(REMMINA_TYPE_PROTOCOL_WIDGET, NULL));
2123 }
2124 
2125 /* Send one or more keystrokes to a specific widget by firing key-press and
2126  * key-release events.
2127  * GdkEventType action can be GDK_KEY_PRESS or GDK_KEY_RELEASE or both to
2128  * press the keys and release them in reversed order. */
2129 void remmina_protocol_widget_send_keys_signals(GtkWidget *widget, const guint *keyvals, int keyvals_length, GdkEventType action)
2130 {
2131  TRACE_CALL(__func__);
2132  int i;
2133  GdkEventKey event;
2134  gboolean result;
2135  GdkKeymap *keymap = gdk_keymap_get_for_display(gdk_display_get_default());
2136 
2137  event.window = gtk_widget_get_window(widget);
2138  event.send_event = TRUE;
2139  event.time = GDK_CURRENT_TIME;
2140  event.state = 0;
2141  event.length = 0;
2142  event.string = "";
2143  event.group = 0;
2144 
2145  if (action & GDK_KEY_PRESS) {
2146  /* Press the requested buttons */
2147  event.type = GDK_KEY_PRESS;
2148  for (i = 0; i < keyvals_length; i++) {
2149  event.keyval = keyvals[i];
2150  event.hardware_keycode = remmina_public_get_keycode_for_keyval(keymap, event.keyval);
2151  event.is_modifier = (int)remmina_public_get_modifier_for_keycode(keymap, event.hardware_keycode);
2152  REMMINA_DEBUG("Sending keyval: %u, hardware_keycode: %u", event.keyval, event.hardware_keycode);
2153  g_signal_emit_by_name(G_OBJECT(widget), "key-press-event", &event, &result);
2154  }
2155  }
2156 
2157  if (action & GDK_KEY_RELEASE) {
2158  /* Release the requested buttons in reverse order */
2159  event.type = GDK_KEY_RELEASE;
2160  for (i = (keyvals_length - 1); i >= 0; i--) {
2161  event.keyval = keyvals[i];
2162  event.hardware_keycode = remmina_public_get_keycode_for_keyval(keymap, event.keyval);
2163  event.is_modifier = (int)remmina_public_get_modifier_for_keycode(keymap, event.hardware_keycode);
2164  g_signal_emit_by_name(G_OBJECT(widget), "key-release-event", &event, &result);
2165  }
2166  }
2167 }
2168 
2170 {
2171  TRACE_CALL(__func__);
2172  GdkRectangle rect;
2173  gint w, h;
2174  gint wfile, hfile;
2177 
2178  rco_get_monitor_geometry(gp->cnnobj, &rect);
2179 
2180  /* Integrity check: check that we have a cnnwin visible and get t */
2181 
2182  res_mode = remmina_file_get_int(gp->priv->remmina_file, "resolution_mode", RES_INVALID);
2184  wfile = remmina_file_get_int(gp->priv->remmina_file, "resolution_width", -1);
2185  hfile = remmina_file_get_int(gp->priv->remmina_file, "resolution_height", -1);
2186 
2187  /* If resolution_mode is non-existent (-1), then we try to calculate it
2188  * as we did before having resolution_mode */
2189  if (res_mode == RES_INVALID) {
2190  if (wfile <= 0 || hfile <= 0)
2191  res_mode = RES_USE_INITIAL_WINDOW_SIZE;
2192  else
2193  res_mode = RES_USE_CUSTOM;
2194  }
2195 
2197  /* Use internal window size as remote desktop size */
2198  GtkAllocation al;
2199  gtk_widget_get_allocation(GTK_WIDGET(gp), &al);
2200  /* use a multiple of four to mitigate scaling when remote host rounds up */
2201  w = al.width - al.width % 4;
2202  h = al.height - al.height % 4;
2203  if (w < 10) {
2204  printf("Remmina warning: %s RemminaProtocolWidget w=%d h=%d are too small, adjusting to 640x480\n", __func__, w, h);
2205  w = 640;
2206  h = 480;
2207  }
2208  /* Due to approximations while GTK calculates scaling, (w x h) may exceed our monitor geometry
2209  * Adjust to fit. */
2210  if (w > rect.width)
2211  w = rect.width;
2212  if (h > rect.height)
2213  h = rect.height;
2214  } else if (res_mode == RES_USE_CLIENT) {
2215  w = rect.width;
2216  h = rect.height;
2217  } else {
2218  w = wfile;
2219  h = hfile;
2220  }
2221  gp->priv->profile_remote_width = w;
2222  gp->priv->profile_remote_height = h;
2223 }
gboolean remmina_protocol_widget_get_expand(RemminaProtocolWidget *gp)
static gboolean desktop_resize(gpointer data)
void remmina_protocol_widget_panel_show(RemminaProtocolWidget *gp)
const gchar * remmina_protocol_widget_get_error_message(RemminaProtocolWidget *gp)
struct remmina_masterthread_exec_data::@12::@14 init_save_creds
void remmina_protocol_widget_set_error(RemminaProtocolWidget *gp, const gchar *fmt,...)
enum remmina_ssh_auth_result remmina_ssh_auth_gui(RemminaSSH *ssh, RemminaProtocolWidget *gp, RemminaFile *remminafile)
gboolean remmina_protocol_widget_query_feature_by_type(RemminaProtocolWidget *gp, RemminaProtocolFeatureType type)
const RemminaProtocolFeature * remmina_protocol_widget_get_features(RemminaProtocolWidget *gp)
struct _RemminaProtocolWidgetSignalData RemminaProtocolWidgetSignalData
static gboolean lock_dynres(gpointer data)
const gchar * remmina_file_get_string(RemminaFile *remminafile, const gchar *setting)
Definition: remmina_file.c:516
void remmina_protocol_widget_panel_hide(RemminaProtocolWidget *gp)
static gboolean unlock_dynres(gpointer data)
static gboolean remmina_protocol_widget_on_key_press(GtkWidget *widget, GdkEventKey *event, RemminaProtocolWidget *gp)
gchar * remmina_protocol_widget_start_direct_tunnel(RemminaProtocolWidget *gp, gint default_port, gboolean port_plus)
Start an SSH tunnel if possible and return the host:port string.
RemminaSSHTunnelCallback destroy_func
Definition: remmina_ssh.h:180
gboolean remmina_protocol_widget_query_feature_by_ref(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
gboolean remmina_ssh_tunnel_reverse(RemminaSSHTunnel *tunnel, gint port, gint local_port)
void remmina_message_panel_setup_auth(RemminaMessagePanel *mp, RemminaMessagePanelCallback response_callback, gpointer response_callback_data, const gchar *title, const gchar *password_prompt, unsigned flags)
void(* call_feature)(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
Definition: plugin.h:83
static void remmina_protocol_widget_destroy(RemminaProtocolWidget *gp, gpointer data)
static gboolean remmina_protocol_widget_dialog_mt_setup(gpointer user_data)
void remmina_message_panel_setup_question(RemminaMessagePanel *mp, const gchar *message, RemminaMessagePanelCallback response_callback, gpointer response_callback_data)
void remmina_protocol_widget_chat_receive(RemminaProtocolWidget *gp, const gchar *text)
gchar * remmina_message_panel_field_get_filename(RemminaMessagePanel *mp, int entryid)
gint remmina_protocol_widget_get_width(RemminaProtocolWidget *gp)
gint remmina_protocol_widget_get_profile_remote_width(RemminaProtocolWidget *gp)
gint remmina_protocol_widget_panel_auth(RemminaProtocolWidget *gp, RemminaMessagePanelFlags pflags, const gchar *title, const gchar *default_username, const gchar *default_password, const gchar *default_domain, const gchar *password_prompt)
void remmina_protocol_widget_save_cred(RemminaProtocolWidget *gp)
const gchar * remmina_file_get_filename(RemminaFile *remminafile)
Definition: remmina_file.c:210
static gboolean remmina_protocol_widget_xport_tunnel_connect_callback(RemminaSSHTunnel *tunnel, gpointer data)
void remmina_public_send_notification(const gchar *notification_id, const gchar *notification_title, const gchar *notification_message)
gboolean remmina_protocol_widget_get_savepassword(RemminaProtocolWidget *gp)
GtkWidget * remmina_protocol_widget_gtkviewport(RemminaProtocolWidget *gp)
typedefG_BEGIN_DECLS struct _RemminaFile RemminaFile
Definition: types.h:44
gboolean remmina_protocol_widget_has_error(RemminaProtocolWidget *gp)
void remmina_protocol_widget_register_hostkey(RemminaProtocolWidget *gp, GtkWidget *widget)
void rco_show_message_panel(RemminaConnectionObject *cnnobj, RemminaMessagePanel *mp)
Each cnnobj->page can have more than one RemminaMessagePanel, but 0 or 1 are visible.
Definition: rcw.c:4786
void remmina_protocol_widget_send_clipboard(RemminaProtocolWidget *gp, GObject *widget)
gboolean remmina_protocol_widget_start_reverse_tunnel(RemminaProtocolWidget *gp, gint local_port)
gchar * remmina_protocol_widget_get_domain(RemminaProtocolWidget *gp)
static gboolean remmina_protocol_widget_on_key_release(GtkWidget *widget, GdkEventKey *event, RemminaProtocolWidget *gp)
static void shutdown_loop(MpRunInfo *mpri)
gpointer destroy_func_callback_data
Definition: remmina_ssh.h:181
RemminaFile * remmina_file_dup_temp_protocol(RemminaFile *remminafile, const gchar *new_protocol)
Definition: remmina_file.c:899
GtkWidget * rcw_open_from_file_full(RemminaFile *remminafile, GCallback disconnect_cb, gpointer data, guint *handler)
Definition: rcw.c:4523
void remmina_message_panel_setup_auth_x509(RemminaMessagePanel *mp, RemminaMessagePanelCallback response_callback, gpointer response_callback_data)
static int remmina_protocol_widget_dialog(enum panel_type dtype, RemminaProtocolWidget *gp, RemminaMessagePanelFlags pflags, const gchar *title, const gchar *default_username, const gchar *default_password, const gchar *default_domain, const gchar *strpasswordlabel)
static void run_response_handler(RemminaMessagePanel *mp, gint response_id, gpointer data)
static gboolean remmina_protocol_widget_xport_tunnel_init_callback(RemminaSSHTunnel *tunnel, gpointer data)
void(* init)(RemminaProtocolWidget *gp)
Definition: plugin.h:79
static void cancel_init_tunnel_cb(void *cbdata, int btn)
void remmina_protocol_widget_setup(RemminaProtocolWidget *gp, RemminaFile *remminafile, RemminaConnectionObject *cnnobj)
RemminaMessagePanel * listen_message_panel
static RemminaSSHTunnel * remmina_protocol_widget_init_tunnel(RemminaProtocolWidget *gp)
const RemminaProtocolFeature * features
Definition: plugin.h:77
gchar * remmina_protocol_widget_get_cacert(RemminaProtocolWidget *gp)
struct remmina_masterthread_exec_data::@12::@24 protocolwidget_panelshowlisten
void remmina_protocol_widget_call_feature_by_type(RemminaProtocolWidget *gp, RemminaProtocolFeatureType type, gint id)
void remmina_protocol_widget_lock_dynres(RemminaProtocolWidget *gp)
void rco_destroy_message_panel(RemminaConnectionObject *cnnobj, RemminaMessagePanel *mp)
Deletes a RemminaMessagePanel from the current cnnobj and if it was visible, make visible the last re...
Definition: rcw.c:4739
void remmina_protocol_widget_set_hostkey_func(RemminaProtocolWidget *gp, RemminaHostkeyFunc func)
gboolean bindlocalhost
Definition: remmina_ssh.h:172
gint remmina_protocol_widget_get_height(RemminaProtocolWidget *gp)
RemminaScaleMode remmina_protocol_widget_get_current_scale_mode(RemminaProtocolWidget *gp)
void remmina_protocol_widget_update_align(RemminaProtocolWidget *gp)
void remmina_protocol_widget_set_current_scale_mode(RemminaProtocolWidget *gp, RemminaScaleMode scalemode)
static gboolean conn_opened(gpointer data)
void remmina_protocol_widget_send_clip_strokes(GtkClipboard *clipboard, const gchar *clip_text, gpointer data)
Send to the plugin some keystrokes from the content of the clipboard This is a copy of remmina_protoc...
gint remmina_protocol_widget_panel_authx509(RemminaProtocolWidget *gp)
static void cancel_open_connection_cb(void *cbdata, int btn)
gboolean(* open_connection)(RemminaProtocolWidget *gp)
Definition: plugin.h:80
RemminaMessagePanel * retry_message_panel
void remmina_message_panel_field_set_string(RemminaMessagePanel *mp, int entryid, const gchar *text)
RemminaProtocolPlugin * plugin
static void run_unmap_handler(RemminaMessagePanel *mp, gpointer data)
static gboolean conn_closed(gpointer data)
void remmina_protocol_widget_set_display(RemminaProtocolWidget *gp, gint display)
RemminaProtocolWidgetResolutionMode
Definition: types.h:148
void remmina_protocol_widget_set_width(RemminaProtocolWidget *gp, gint width)
RemminaSSHTunnelCallback disconnect_func
Definition: remmina_ssh.h:177
void remmina_protocol_widget_open_connection(RemminaProtocolWidget *gp)
RemminaProtocolFeature * features
gchar * remmina_public_str_replace_in_place(gchar *string, const gchar *search, const gchar *replacement)
GtkDialog * remmina_ext_exec_new(RemminaFile *remminafile, const char *remmina_ext_exec_type)
gboolean remmina_protocol_widget_unmap_event(RemminaProtocolWidget *gp)
void remmina_protocol_widget_send_keystrokes(RemminaProtocolWidget *gp, GtkMenuItem *widget)
Send to the plugin some keystrokes.
const gchar * remmina_protocol_widget_get_name(RemminaProtocolWidget *gp)
void remmina_protocol_widget_panel_show_listen(RemminaProtocolWidget *gp, gint port)
static void cancel_start_reverse_tunnel_cb(void *cbdata, int btn)
struct remmina_masterthread_exec_data::@12::@21 protocolwidget_mpprogress
RemminaMessagePanelFlags
Definition: types.h:156
void remmina_message_panel_field_set_filename(RemminaMessagePanel *mp, int entryid, const gchar *filename)
GtkWindow * rcw_get_gtkwindow(RemminaConnectionObject *cnnobj)
Definition: rcw.c:4720
gboolean remmina_masterthread_exec_is_main_thread()
struct remmina_masterthread_exec_data::@12::@22 protocolwidget_mpdestroy
void remmina_protocol_widget_signal_connection_closed(RemminaProtocolWidget *gp)
gboolean(* unmap_event)(RemminaProtocolWidget *gp)
Definition: plugin.h:87
GtkWidget * remmina_protocol_widget_new(void)
static guint remmina_protocol_widget_signals[LAST_SIGNAL]
struct remmina_masterthread_exec_data::@12::@23 protocolwidget_mpshowretry
void remmina_protocol_widget_desktop_resize(RemminaProtocolWidget *gp)
RemminaMessagePanel * auth_message_panel
RemminaProtocolFeatureType type
Definition: types.h:73
gboolean remmina_protocol_widget_map_event(RemminaProtocolWidget *gp)
gint remmina_protocol_widget_panel_question_yesno(RemminaProtocolWidget *gp, const char *msg)
RemminaProtocolPlugin * plugin
void(* send_keystrokes)(RemminaProtocolWidget *gp, const guint keystrokes[], const gint keylen)
Definition: plugin.h:84
gint remmina_protocol_widget_panel_changed_certificate(RemminaProtocolWidget *gp, const gchar *subject, const gchar *issuer, const gchar *new_fingerprint, const gchar *old_fingerprint)
gchar * remmina_protocol_widget_get_clientcert(RemminaProtocolWidget *gp)
void remmina_protocol_widget_mpdestroy(RemminaConnectionObject *cnnobj, RemminaMessagePanel *mp)
void remmina_ssh_set_error(RemminaSSH *ssh, const gchar *fmt)
Definition: remmina_ssh.c:653
RemminaMessagePanel * mp
static void cancel_connect_xport_cb(void *cbdata, int btn)
gboolean remmina_ssh_tunnel_xport(RemminaSSHTunnel *tunnel, gboolean bindlocalhost)
void remmina_protocol_widget_unlock_dynres(RemminaProtocolWidget *gp)
RemminaSSHTunnel * remmina_ssh_tunnel_new_from_file(RemminaFile *remminafile)
RemminaProtocolWidgetPriv * priv
static void remmina_protocol_widget_init(RemminaProtocolWidget *gp)
static void authpanel_mt_cb(void *user_data, int button)
RemminaMessagePanel * remmina_protocol_widget_mpprogress(RemminaConnectionObject *cnnobj, const gchar *msg, RemminaMessagePanelCallback response_callback, gpointer response_callback_data)
RemminaSSHTunnelCallback init_func
Definition: remmina_ssh.h:175
gboolean(* RemminaHostkeyFunc)(RemminaProtocolWidget *gp, guint keyval, gboolean release)
G_DEFINE_TYPE(RemminaProtocolWidget, remmina_protocol_widget, GTK_TYPE_EVENT_BOX)
void remmina_protocol_widget_panel_show_retry(RemminaProtocolWidget *gp)
void remmina_message_panel_field_set_switch(RemminaMessagePanel *mp, int entryid, gboolean state)
gpointer callback_data
Definition: remmina_ssh.h:178
RemminaConnectionObject * cnnobj
void remmina_protocol_widget_update_remote_resolution(RemminaProtocolWidget *gp)
GtkWindow * remmina_protocol_widget_get_gtkwindow(RemminaProtocolWidget *gp)
RemminaTypeHint opt1_type_hint
Definition: types.h:78
gint remmina_protocol_widget_panel_new_certificate(RemminaProtocolWidget *gp, const gchar *subject, const gchar *issuer, const gchar *fingerprint)
void remmina_protocol_widget_close_connection(RemminaProtocolWidget *gp)
gboolean remmina_protocol_widget_plugin_screenshot(RemminaProtocolWidget *gp, RemminaPluginScreenshotData *rpsd)
gboolean remmina_pref_get_boolean(const gchar *key)
void remmina_ssh_tunnel_cancel_accept(RemminaSSHTunnel *tunnel)
void remmina_protocol_widget_grab_focus(RemminaProtocolWidget *gp)
RemminaMessagePanel * connect_message_panel
gboolean remmina_protocol_widget_ssh_exec(RemminaProtocolWidget *gp, gboolean wait, const gchar *fmt,...)
gchar * remmina_protocol_widget_get_cacrl(RemminaProtocolWidget *gp)
void remmina_ssh_tunnel_free(RemminaSSHTunnel *tunnel)
static gboolean conn_closed_real(gpointer data, int button)
static void remmina_protocol_widget_close_all_tunnels(RemminaProtocolWidget *gp)
static void remmina_protocol_widget_chat_on_destroy(RemminaProtocolWidget *gp)
static gboolean remmina_protocol_widget_xport_tunnel_disconnect_callback(RemminaSSHTunnel *tunnel, gpointer data)
RemminaProtocolFeatureType
Definition: types.h:46
gint remmina_protocol_widget_get_multimon(RemminaProtocolWidget *gp)
void remmina_protocol_widget_set_height(RemminaProtocolWidget *gp, gint height)
gboolean(* close_connection)(RemminaProtocolWidget *gp)
Definition: plugin.h:81
void remmina_masterthread_exec_and_wait(RemminaMTExecData *d)
GtkWidget * rcw_get_gtkviewport(RemminaConnectionObject *cnnobj)
Definition: rcw.c:4724
void(* RemminaMessagePanelCallback)(void *user_data, int button)
void remmina_protocol_widget_emit_signal(RemminaProtocolWidget *gp, const gchar *signal_name)
gchar * remmina_message_panel_field_get_string(RemminaMessagePanel *mp, int entryid)
RemminaTypeHint opt2_type_hint
Definition: types.h:79
gboolean remmina_protocol_widget_is_closed(RemminaProtocolWidget *gp)
gchar * remmina_protocol_widget_get_clientkey(RemminaProtocolWidget *gp)
RemminaPref remmina_pref
Definition: rcw.c:79
static void run_destroy_handler(RemminaMessagePanel *mp, gpointer data)
void remmina_message_panel_setup_progress(RemminaMessagePanel *mp, const gchar *message, RemminaMessagePanelCallback response_callback, gpointer response_callback_data)
RemminaScaleMode
Definition: types.h:142
gint remmina_file_get_int(RemminaFile *remminafile, const gchar *setting, gint default_value)
Definition: remmina_file.c:603
gboolean remmina_protocol_widget_start_xport_tunnel(RemminaProtocolWidget *gp, RemminaXPortTunnelInitFunc init_func)
gboolean(* map_event)(RemminaProtocolWidget *gp)
Definition: plugin.h:86
void remmina_protocol_widget_call_feature_by_ref(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
gboolean(* query_feature)(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
Definition: plugin.h:82
static gboolean remmina_protocol_widget_tunnel_destroy(RemminaSSHTunnel *tunnel, gpointer data)
RemminaPlugin * remmina_plugin_manager_get_plugin(RemminaPluginType type, const gchar *name)
void remmina_protocol_widget_signal_connection_opened(RemminaProtocolWidget *gp)
gint remmina_protocol_widget_get_profile_remote_height(RemminaProtocolWidget *gp)
guint16 remmina_public_get_keycode_for_keyval(GdkKeymap *keymap, guint keyval)
gpointer RemminaTunnelInitFunc
Definition: types.h:94
const gchar * name
Definition: plugin.h:67
gchar * remmina_protocol_widget_get_password(RemminaProtocolWidget *gp)
gboolean remmina_public_get_modifier_for_keycode(GdkKeymap *keymap, guint16 keycode)
struct remmina_masterthread_exec_data::@12::@15 chat_receive
union remmina_masterthread_exec_data::@12 p
gint sshtunnel_port
Definition: remmina_pref.h:168
gint remmina_protocol_widget_panel_authuserpwd_ssh_tunnel(RemminaProtocolWidget *gp, gboolean want_domain, gboolean allow_password_saving)
static void cancel_start_direct_tunnel_cb(void *cbdata, int btn)
struct remmina_masterthread_exec_data::@12::@20 protocolwidget_emit_signal
static gboolean update_align(gpointer data)
void remmina_message_panel_setup_message(RemminaMessagePanel *mp, const gchar *message, RemminaMessagePanelCallback response_callback, gpointer response_callback_data)
gboolean(* get_plugin_screenshot)(RemminaProtocolWidget *gp, RemminaPluginScreenshotData *rpsd)
Definition: plugin.h:85
void remmina_protocol_widget_set_expand(RemminaProtocolWidget *gp, gboolean expand)
gboolean remmina_message_panel_field_get_switch_state(RemminaMessagePanel *mp, int entryid)
void remmina_file_set_string(RemminaFile *remminafile, const gchar *setting, const gchar *value)
Definition: remmina_file.c:469
void remmina_file_save(RemminaFile *remminafile)
Definition: remmina_file.c:730
static void remmina_protocol_widget_class_init(RemminaProtocolWidgetClass *klass)
void rco_get_monitor_geometry(RemminaConnectionObject *cnnobj, GdkRectangle *sz)
Definition: rcw.c:1011
gboolean remmina_protocol_widget_plugin_receives_keystrokes(RemminaProtocolWidget *gp)
Check if the plugin accepts keystrokes.
void remmina_protocol_widget_send_keys_signals(GtkWidget *widget, const guint *keyvals, int keyvals_length, GdkEventType action)
void remmina_ssh_set_application_error(RemminaSSH *ssh, const gchar *fmt,...)
Definition: remmina_ssh.c:663
void remmina_chat_window_receive(RemminaChatWindow *window, const gchar *name, const gchar *text)
void remmina_protocol_widget_chat_close(RemminaProtocolWidget *gp)
void remmina_public_get_server_port(const gchar *server, gint defaultport, gchar **host, gint *port)
gboolean remmina_ssh_tunnel_open(RemminaSSHTunnel *tunnel, const gchar *host, gint port, gint local_port)
GtkWidget * remmina_chat_window_new(GtkWindow *parent, const gchar *chat_with)
void remmina_protocol_widget_open_connection_real(gpointer data)
RemminaFile * remmina_protocol_widget_get_file(RemminaProtocolWidget *gp)
enum remmina_masterthread_exec_data::@11 func
gchar * localdisplay
Definition: remmina_ssh.h:173
gboolean(* RemminaXPortTunnelInitFunc)(RemminaProtocolWidget *gp, gint remotedisplay, const gchar *server, gint port)
Definition: types.h:95
void remmina_message_panel_response(RemminaMessagePanel *mp, gint response_id)
void remmina_protocol_widget_chat_open(RemminaProtocolWidget *gp, const gchar *name, void(*on_send)(RemminaProtocolWidget *gp, const gchar *text), void(*on_destroy)(RemminaProtocolWidget *gp))
RemminaMessagePanel * remmina_message_panel_new()
RemminaTypeHint opt3_type_hint
Definition: types.h:80
RemminaSSHTunnelCallback connect_func
Definition: remmina_ssh.h:176
gchar * remmina_protocol_widget_get_username(RemminaProtocolWidget *gp)
gboolean remmina_ssh_init_session(RemminaSSH *ssh)