Remmina - The GTK+ Remote Desktop Client  v1.4.31
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  /* 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)
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:4762
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:4499
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:4715
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:147
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:155
void remmina_message_panel_field_set_filename(RemminaMessagePanel *mp, int entryid, const gchar *filename)
GtkWindow * rcw_get_gtkwindow(RemminaConnectionObject *cnnobj)
Definition: rcw.c:4696
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_protocol_widget_send_clipboard(RemminaProtocolWidget *gp, GtkMenuItem *widget)
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 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:4700
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:141
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)
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:1009
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)