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.
rcw.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 
38 #include "config.h"
39 
40 #ifdef GDK_WINDOWING_X11
41 #include <cairo/cairo-xlib.h>
42 #else
43 #include <cairo/cairo.h>
44 #endif
45 #include <gdk/gdk.h>
46 #include <gdk/gdkkeysyms.h>
47 #include <glib/gi18n.h>
48 #include <stdlib.h>
49 
50 #include "remmina.h"
51 #include "remmina_main.h"
52 #include "rcw.h"
54 #include "remmina_applet_menu.h"
55 #include "remmina_file.h"
56 #include "remmina_file_manager.h"
57 #include "remmina_log.h"
58 #include "remmina_message_panel.h"
59 #include "remmina_ext_exec.h"
60 #include "remmina_plugin_manager.h"
61 #include "remmina_pref.h"
63 #include "remmina_public.h"
65 #include "remmina_unlock.h"
66 #include "remmina_utils.h"
67 #include "remmina_widget_pool.h"
69 
70 #ifdef GDK_WINDOWING_WAYLAND
71 #include <gdk/gdkwayland.h>
72 #endif
73 
74 
75 #define DEBUG_KB_GRABBING 0
76 #include "remmina_exec.h"
77 
80 
81 G_DEFINE_TYPE(RemminaConnectionWindow, rcw, GTK_TYPE_WINDOW)
82 
83 #define MOTION_TIME 100
84 
85 /* default timeout used to hide the floating toolbar when switching profile */
86 #define TB_HIDE_TIME_TIME 1500
87 
88 #define FULL_SCREEN_TARGET_MONITOR_UNDEFINED -1
89 
90 struct _RemminaConnectionWindowPriv {
91  GtkNotebook * notebook;
92  GtkWidget * floating_toolbar_widget;
93  GtkWidget * overlay;
94  GtkWidget * revealer;
95  GtkWidget * overlay_ftb_overlay;
96 
97  GtkWidget * floating_toolbar_label;
98  gdouble floating_toolbar_opacity;
99 
100  /* Various delayed and timer event source ids */
101  guint acs_eventsourceid; // timeout
102  guint spf_eventsourceid; // idle
103  guint grab_retry_eventsourceid; // timeout
104  guint delayed_grab_eventsourceid;
105  guint ftb_hide_eventsource; // timeout
106  guint tar_eventsource; // timeout
107  guint hidetb_eventsource; // timeout
108  guint dwp_eventsourceid; // timeout
109 
110  GtkWidget * toolbar;
111  GtkWidget * grid;
112 
113  /* Toolitems that need to be handled */
114  GtkToolItem * toolitem_menu;
115  GtkToolItem * toolitem_autofit;
116  GtkToolItem * toolitem_fullscreen;
117  GtkToolItem * toolitem_switch_page;
118  GtkToolItem * toolitem_dynres;
119  GtkToolItem * toolitem_scale;
120  GtkToolItem * toolitem_grab;
121  GtkToolItem * toolitem_multimon;
122  GtkToolItem * toolitem_preferences;
123  GtkToolItem * toolitem_tools;
124  GtkToolItem * toolitem_new;
125  GtkToolItem * toolitem_duplicate;
126  GtkToolItem * toolitem_screenshot;
127  GtkWidget * fullscreen_option_button;
128  GtkWidget * fullscreen_scaler_button;
129  GtkWidget * scaler_option_button;
130 
131  GtkWidget * pin_button;
132  gboolean pin_down;
133 
134  gboolean sticky;
135 
136  /* Flag to turn off toolbar signal handling when toolbar is
137  * reconfiguring, usually due to a tab switch */
138  gboolean toolbar_is_reconfiguring;
139 
140  /* This is the current view mode, i.e. VIEWPORT_FULLSCREEN_MODE,
141  * as saved on the "viwemode" profile preference file */
142  gint view_mode;
143 
144  /* Status variables used when in fullscreen mode. Needed
145  * to restore a fullscreen mode after coming from scrolled */
146  gint fss_view_mode;
147  /* Status variables used when in scrolled window mode. Needed
148  * to restore a scrolled window mode after coming from fullscreen */
149  gint ss_width, ss_height;
150  gboolean ss_maximized;
151 
152  gboolean kbcaptured;
153  gboolean pointer_captured;
154  gboolean hostkey_activated;
155  gboolean hostkey_used;
156 
157  gboolean pointer_entered;
158 
159  RemminaConnectionWindowOnDeleteConfirmMode on_delete_confirm_mode;
160 };
161 
162 typedef struct _RemminaConnectionObject {
165 
166  GtkWidget * proto;
167  GtkWidget * aspectframe;
168  GtkWidget * viewport;
169 
170  GtkWidget * scrolled_container;
171 
173 
174  gboolean connected;
175  gboolean dynres_unlocked;
176 
179 
180 enum {
183 };
184 
185 static guint rcw_signals[LAST_SIGNAL] =
186 { 0 };
187 
188 static RemminaConnectionWindow *rcw_create_scrolled(gint width, gint height, gboolean maximize);
189 static RemminaConnectionWindow *rcw_create_fullscreen(GtkWindow *old, gint view_mode);
190 static gboolean rcw_hostkey_func(RemminaProtocolWidget *gp, guint keyval, gboolean release);
191 static GtkWidget *rco_create_tab_page(RemminaConnectionObject *cnnobj);
192 static GtkWidget *rco_create_tab_label(RemminaConnectionObject *cnnobj);
193 
195 static GtkWidget *rcw_create_toolbar(RemminaConnectionWindow *cnnwin, gint mode);
196 static void rcw_place_toolbar(GtkToolbar *toolbar, GtkGrid *grid, GtkWidget *sibling, int toolbar_placement);
197 static void rcw_keyboard_grab(RemminaConnectionWindow *cnnwin);
198 static GtkWidget *rcw_append_new_page(RemminaConnectionWindow *cnnwin, RemminaConnectionObject *cnnobj);
199 
200 
201 static void rcw_ftb_drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer user_data);
202 
203 static const GtkTargetEntry dnd_targets_ftb[] =
204 {
205  {
206  (char *)"text/x-remmina-ftb",
207  GTK_TARGET_SAME_APP | GTK_TARGET_OTHER_WIDGET,
208  0
209  },
210 };
211 
212 static const GtkTargetEntry dnd_targets_tb[] =
213 {
214  {
215  (char *)"text/x-remmina-tb",
216  GTK_TARGET_SAME_APP,
217  0
218  },
219 };
220 
222 {
223  TRACE_CALL(__func__);
224  GtkCssProvider *provider;
225 
226  provider = gtk_css_provider_new();
227 
228  /* It’s important to remove padding, border and shadow from GtkViewport or
229  * we will never know its internal area size, because GtkViweport::viewport_get_view_allocation,
230  * which returns the internal size of the GtkViewport, is private and we cannot access it */
231 
232 #if GTK_CHECK_VERSION(3, 14, 0)
233  gtk_css_provider_load_from_data(provider,
234  "#remmina-cw-viewport, #remmina-cw-aspectframe {\n"
235  " padding:0;\n"
236  " border:0;\n"
237  " background-color: black;\n"
238  "}\n"
239  "GtkDrawingArea {\n"
240  "}\n"
241  "GtkToolbar {\n"
242  " -GtkWidget-window-dragging: 0;\n"
243  "}\n"
244  "#remmina-connection-window-fullscreen {\n"
245  " border-color: black;\n"
246  "}\n"
247  "#remmina-small-button {\n"
248  " outline-offset: 0;\n"
249  " outline-width: 0;\n"
250  " padding: 0;\n"
251  " border: 0;\n"
252  "}\n"
253  "#remmina-pin-button {\n"
254  " outline-offset: 0;\n"
255  " outline-width: 0;\n"
256  " padding: 2px;\n"
257  " border: 0;\n"
258  "}\n"
259  "#remmina-tab-page {\n"
260  " background-color: black;\n"
261  "}\n"
262  "#remmina-scrolled-container {\n"
263  "}\n"
264  "#remmina-scrolled-container.undershoot {\n"
265  " background: none;\n"
266  "}\n"
267  "#remmina-tab-page {\n"
268  "}\n"
269  "#ftbbox-upper {\n"
270  " background-color: white;\n"
271  " color: black;\n"
272  " border-style: none solid solid solid;\n"
273  " border-width: 1px;\n"
274  " border-radius: 4px;\n"
275  " padding: 0px;\n"
276  "}\n"
277  "#ftbbox-lower {\n"
278  " background-color: white;\n"
279  " color: black;\n"
280  " border-style: solid solid none solid;\n"
281  " border-width: 1px;\n"
282  " border-radius: 4px;\n"
283  " padding: 0px;\n"
284  "}\n"
285  "#ftb-handle {\n"
286  "}\n"
287  ".message_panel {\n"
288  " border: 0px solid;\n"
289  " padding: 20px 20px 20px 20px;\n"
290  "}\n"
291  ".message_panel entry {\n"
292  " background-image: none;\n"
293  " border-width: 4px;\n"
294  " border-radius: 8px;\n"
295  "}\n"
296  ".message_panel .title_label {\n"
297  " font-size: 2em; \n"
298  "}\n"
299  , -1, NULL);
300 
301 #else
302  gtk_css_provider_load_from_data(provider,
303  "#remmina-cw-viewport, #remmina-cw-aspectframe {\n"
304  " padding:0;\n"
305  " border:0;\n"
306  " background-color: black;\n"
307  "}\n"
308  "#remmina-cw-message-panel {\n"
309  "}\n"
310  "GtkDrawingArea {\n"
311  "}\n"
312  "GtkToolbar {\n"
313  " -GtkWidget-window-dragging: 0;\n"
314  "}\n"
315  "#remmina-connection-window-fullscreen {\n"
316  " border-color: black;\n"
317  "}\n"
318  "#remmina-small-button {\n"
319  " -GtkWidget-focus-padding: 0;\n"
320  " -GtkWidget-focus-line-width: 0;\n"
321  " padding: 0;\n"
322  " border: 0;\n"
323  "}\n"
324  "#remmina-pin-button {\n"
325  " -GtkWidget-focus-padding: 0;\n"
326  " -GtkWidget-focus-line-width: 0;\n"
327  " padding: 2px;\n"
328  " border: 0;\n"
329  "}\n"
330  "#remmina-scrolled-container {\n"
331  "}\n"
332  "#remmina-scrolled-container.undershoot {\n"
333  " background: none\n"
334  "}\n"
335  "#remmina-tab-page {\n"
336  "}\n"
337  "#ftbbox-upper {\n"
338  " border-style: none solid solid solid;\n"
339  " border-width: 1px;\n"
340  " border-radius: 4px;\n"
341  " padding: 0px;\n"
342  "}\n"
343  "#ftbbox-lower {\n"
344  " border-style: solid solid none solid;\n"
345  " border-width: 1px;\n"
346  " border-radius: 4px;\n"
347  " padding: 0px;\n"
348  "}\n"
349  "#ftb-handle {\n"
350  "}\n"
351 
352  , -1, NULL);
353 #endif
354 
355  gtk_style_context_add_provider_for_screen(gdk_screen_get_default(),
356  GTK_STYLE_PROVIDER(provider),
357  GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
358 
359  g_object_unref(provider);
360 
361  /* Define a signal used to notify all rcws of toolbar move */
362  rcw_signals[TOOLBARPLACE_SIGNAL] = g_signal_new("toolbar-place", G_TYPE_FROM_CLASS(klass),
363  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaConnectionWindowClass, toolbar_place), NULL, NULL,
364  g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
365 }
366 
368 {
369  GtkWidget *po;
370 
371  if (!cnnwin->priv->notebook)
372  return NULL;
373  po = gtk_notebook_get_nth_page(GTK_NOTEBOOK(cnnwin->priv->notebook), npage);
374  return g_object_get_data(G_OBJECT(po), "cnnobj");
375 }
376 
378 {
379  gint np;
380 
381  if (cnnwin != NULL && cnnwin->priv != NULL && cnnwin->priv->notebook != NULL) {
382  np = gtk_notebook_get_current_page(GTK_NOTEBOOK(cnnwin->priv->notebook));
383  if (np < 0)
384  return NULL;
385  return rcw_get_cnnobj_at_page(cnnwin, np);
386  } else {
387  return NULL;
388  }
389 }
390 
391 static RemminaScaleMode get_current_allowed_scale_mode(RemminaConnectionObject *cnnobj, gboolean *dynres_avail, gboolean *scale_avail)
392 {
393  TRACE_CALL(__func__);
394  RemminaScaleMode scalemode;
395  gboolean plugin_has_dynres, plugin_can_scale;
396 
397  scalemode = remmina_protocol_widget_get_current_scale_mode(REMMINA_PROTOCOL_WIDGET(cnnobj->proto));
398 
399  plugin_has_dynres = remmina_protocol_widget_query_feature_by_type(REMMINA_PROTOCOL_WIDGET(cnnobj->proto),
401 
402  plugin_can_scale = remmina_protocol_widget_query_feature_by_type(REMMINA_PROTOCOL_WIDGET(cnnobj->proto),
404 
405  /* Forbid scalemode REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES when not possible */
406  if ((!plugin_has_dynres) && scalemode == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES)
408 
409  /* Forbid scalemode REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED when not possible */
410  if (!plugin_can_scale && scalemode == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED)
412 
413  if (scale_avail)
414  *scale_avail = plugin_can_scale;
415  if (dynres_avail)
416  *dynres_avail = (plugin_has_dynres && cnnobj->dynres_unlocked);
417 
418  return scalemode;
419 }
420 
422 {
423  TRACE_CALL(__func__);
424 
425  /* Disconnects the connection which is currently in view in the notebook */
426  remmina_protocol_widget_close_connection(REMMINA_PROTOCOL_WIDGET(cnnobj->proto));
427 }
428 
430 {
431  TRACE_CALL(__func__);
432  GdkDisplay *display;
433 
434 #if GTK_CHECK_VERSION(3, 20, 0)
435  GdkSeat *seat;
436 #else
437  GdkDeviceManager *manager;
438  GdkDevice *keyboard = NULL;
439 #endif
440 
441  if (cnnwin->priv->grab_retry_eventsourceid) {
442  g_source_remove(cnnwin->priv->grab_retry_eventsourceid);
443  cnnwin->priv->grab_retry_eventsourceid = 0;
444  }
445  if (cnnwin->priv->delayed_grab_eventsourceid) {
446  g_source_remove(cnnwin->priv->delayed_grab_eventsourceid);
447  cnnwin->priv->delayed_grab_eventsourceid = 0;
448  }
449 
450  display = gtk_widget_get_display(GTK_WIDGET(cnnwin));
451 #if GTK_CHECK_VERSION(3, 20, 0)
452  seat = gdk_display_get_default_seat(display);
453  // keyboard = gdk_seat_get_pointer(seat);
454 #else
455  manager = gdk_display_get_device_manager(display);
456  keyboard = gdk_device_manager_get_client_pointer(manager);
457 #endif
458 
459  if (!cnnwin->priv->kbcaptured && !cnnwin->priv->pointer_captured)
460  return;
461 
462 #if DEBUG_KB_GRABBING
463  printf("DEBUG_KB_GRABBING: --- ungrabbing\n");
464 #endif
465 
466 
467 
468 #if GTK_CHECK_VERSION(3, 20, 0)
469  /* We can use gtk_seat_grab()/_ungrab() only after GTK 3.24 */
470  gdk_seat_ungrab(seat);
471 #else
472  if (keyboard != NULL) {
473  if (gdk_device_get_source(keyboard) != GDK_SOURCE_KEYBOARD)
474  keyboard = gdk_device_get_associated_device(keyboard);
475  G_GNUC_BEGIN_IGNORE_DEPRECATIONS
476  gdk_device_ungrab(keyboard, GDK_CURRENT_TIME);
477  G_GNUC_END_IGNORE_DEPRECATIONS
478  }
479 #endif
480  cnnwin->priv->kbcaptured = FALSE;
481  cnnwin->priv->pointer_captured = FALSE;
482 }
483 
484 static gboolean rcw_keyboard_grab_retry(gpointer user_data)
485 {
486  TRACE_CALL(__func__);
487  RemminaConnectionWindow *cnnwin = (RemminaConnectionWindow *)user_data;
488 
489 #if DEBUG_KB_GRABBING
490  printf("%s retry grab\n", __func__);
491 #endif
492  rcw_keyboard_grab(cnnwin);
493  cnnwin->priv->grab_retry_eventsourceid = 0;
494  return G_SOURCE_REMOVE;
495 }
496 
498 {
499  TRACE_CALL(__func__);
500 #if GTK_CHECK_VERSION(3, 20, 0)
501  GdkSeat *seat;
502  GdkDisplay *display;
503  if (!cnnwin->priv->pointer_captured)
504  return;
505 
506  display = gtk_widget_get_display(GTK_WIDGET(cnnwin));
507  seat = gdk_display_get_default_seat(display);
508  gdk_seat_ungrab(seat);
509 #endif
510 }
511 
513 {
514  TRACE_CALL(__func__);
515  /* This function in Wayland is useless and generates a spurious leave-notify event.
516  * Should we remove it ? https://gitlab.gnome.org/GNOME/mutter/-/issues/2450#note_1588081 */
517 #if GTK_CHECK_VERSION(3, 20, 0)
518  GdkSeat *seat;
519  GdkDisplay *display;
520  GdkGrabStatus ggs;
521 
522 
523  if (cnnwin->priv->pointer_captured) {
524 #if DEBUG_KB_GRABBING
525  printf("DEBUG_KB_GRABBING: pointer_captured is true, it should not\n");
526 #endif
527  return;
528  }
529 
530  display = gtk_widget_get_display(GTK_WIDGET(cnnwin));
531  seat = gdk_display_get_default_seat(display);
532  ggs = gdk_seat_grab(seat, gtk_widget_get_window(GTK_WIDGET(cnnwin)),
533  GDK_SEAT_CAPABILITY_ALL_POINTING, TRUE, NULL, NULL, NULL, NULL);
534  if (ggs != GDK_GRAB_SUCCESS) {
535 #if DEBUG_KB_GRABBING
536  printf("DEBUG_KB_GRABBING: GRAB of POINTER failed. GdkGrabStatus: %d\n", (int)ggs);
537 #endif
538  } else {
539  cnnwin->priv->pointer_captured = TRUE;
540  }
541 
542 #endif
543 }
544 
546 {
547  TRACE_CALL(__func__);
548  GdkDisplay *display;
549 
550 #if GTK_CHECK_VERSION(3, 20, 0)
551  GdkSeat *seat;
552 #else
553  GdkDeviceManager *manager;
554 #endif
555  GdkGrabStatus ggs;
556  GdkDevice *keyboard = NULL;
557 
558  if (cnnwin->priv->kbcaptured) {
559 #if DEBUG_KB_GRABBING
560  printf("DEBUG_KB_GRABBING: %s not grabbing because already grabbed.\n", __func__);
561 #endif
562  return;
563  }
564 
565  display = gtk_widget_get_display(GTK_WIDGET(cnnwin));
566 #if GTK_CHECK_VERSION(3, 20, 0)
567  seat = gdk_display_get_default_seat(display);
568  keyboard = gdk_seat_get_pointer(seat);
569 #else
570  manager = gdk_display_get_device_manager(display);
571  keyboard = gdk_device_manager_get_client_pointer(manager);
572 #endif
573 
574  if (keyboard != NULL) {
575  if (gdk_device_get_source(keyboard) != GDK_SOURCE_KEYBOARD)
576  keyboard = gdk_device_get_associated_device(keyboard);
577 
578 
579 #if DEBUG_KB_GRABBING
580  printf("DEBUG_KB_GRABBING: profile asks for grabbing, let’s try.\n");
581 #endif
582  /* Up to GTK version 3.20 we can grab the keyboard with gdk_device_grab().
583  * in GTK 3.20 gdk_seat_grab() should be used instead of gdk_device_grab().
584  * There is a bug in GTK up to 3.22: When gdk_device_grab() fails
585  * the widget is hidden:
586  * https://gitlab.gnome.org/GNOME/gtk/commit/726ad5a5ae7c4f167e8dd454cd7c250821c400ab
587  * The bugfix will be released with GTK 3.24.
588  * Also please note that the newer gdk_seat_grab() is still calling gdk_device_grab().
589  *
590  * Warning: gdk_seat_grab() will call XGrabKeyboard() or XIGrabDevice()
591  * which in turn will generate a core X input event FocusOut and FocusIn
592  * but not Xinput2 events.
593  * In some cases, GTK is unable to neutralize FocusIn and FocusOut core
594  * events (ie: i3wm+Plasma with GDK_CORE_DEVICE_EVENTS=1 because detail=NotifyNonlinear
595  * instead of detail=NotifyAncestor/detail=NotifyInferior)
596  * Receiving a FocusOut event for Remmina at this time will cause an infinite loop.
597  * Therefore is important for GTK to use Xinput2 instead of core X events
598  * by unsetting GDK_CORE_DEVICE_EVENTS
599  */
600 #if GTK_CHECK_VERSION(3, 20, 0)
601  ggs = gdk_seat_grab(seat, gtk_widget_get_window(GTK_WIDGET(cnnwin)),
602  GDK_SEAT_CAPABILITY_KEYBOARD, TRUE, NULL, NULL, NULL, NULL);
603 #else
604  ggs = gdk_device_grab(keyboard, gtk_widget_get_window(GTK_WIDGET(cnnwin)), GDK_OWNERSHIP_WINDOW,
605  TRUE, GDK_KEY_PRESS | GDK_KEY_RELEASE, NULL, GDK_CURRENT_TIME);
606 #endif
607  if (ggs != GDK_GRAB_SUCCESS) {
608 #if DEBUG_KB_GRABBING
609  printf("GRAB of keyboard failed.\n");
610 #endif
611  /* Reschedule grabbing in half a second if not already done */
612  if (cnnwin->priv->grab_retry_eventsourceid == 0)
613  cnnwin->priv->grab_retry_eventsourceid = g_timeout_add(500, (GSourceFunc)rcw_keyboard_grab_retry, cnnwin);
614  } else {
615 #if DEBUG_KB_GRABBING
616  printf("Keyboard grabbed\n");
617 #endif
618  if (cnnwin->priv->grab_retry_eventsourceid != 0) {
619  g_source_remove(cnnwin->priv->grab_retry_eventsourceid);
620  cnnwin->priv->grab_retry_eventsourceid = 0;
621  }
622  cnnwin->priv->kbcaptured = TRUE;
623  }
624  } else {
625  rcw_kp_ungrab(cnnwin);
626  }
627 }
628 
630 {
631  RemminaConnectionWindowPriv *priv = cnnwin->priv;
632  GtkNotebook *notebook = GTK_NOTEBOOK(priv->notebook);
633  GtkWidget *w;
634  RemminaConnectionObject *cnnobj;
635  gint i, n;
636 
637  if (GTK_IS_WIDGET(notebook)) {
638  n = gtk_notebook_get_n_pages(notebook);
639  for (i = n - 1; i >= 0; i--) {
640  w = gtk_notebook_get_nth_page(notebook, i);
641  cnnobj = (RemminaConnectionObject *)g_object_get_data(G_OBJECT(w), "cnnobj");
642  /* Do close the connection on this tab */
643  remmina_protocol_widget_close_connection(REMMINA_PROTOCOL_WIDGET(cnnobj->proto));
644  }
645  }
646 }
647 
649 {
650  TRACE_CALL(__func__);
651  RemminaConnectionWindowPriv *priv = cnnwin->priv;
652  GtkNotebook *notebook = GTK_NOTEBOOK(priv->notebook);
653  GtkWidget *dialog;
654  gint i, n, nopen;
655 
656  if (!REMMINA_IS_CONNECTION_WINDOW(cnnwin))
657  return TRUE;
658 
659  if (cnnwin->priv->on_delete_confirm_mode != RCW_ONDELETE_NOCONFIRM) {
660  n = gtk_notebook_get_n_pages(notebook);
661  nopen = 0;
662  /* count all non-closed connections */
663  for(i = 0; i < n; i ++) {
664  RemminaConnectionObject *cnnobj = rcw_get_cnnobj_at_page(cnnwin, i);
666  nopen ++;
667  }
668  if (nopen > 1) {
669  dialog = gtk_message_dialog_new(GTK_WINDOW(cnnwin), GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION,
670  GTK_BUTTONS_YES_NO,
671  _("Are you sure you want to close %i active connections in the current window?"), nopen);
672  i = gtk_dialog_run(GTK_DIALOG(dialog));
673  gtk_widget_destroy(dialog);
674  if (i != GTK_RESPONSE_YES)
675  return FALSE;
676  }
677  else if (nopen == 1) {
678  if (remmina_pref.confirm_close) {
679  dialog = gtk_message_dialog_new(GTK_WINDOW(cnnwin), GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION,
680  GTK_BUTTONS_YES_NO,
681  _("Are you sure you want to close this last active connection?"));
682  i = gtk_dialog_run(GTK_DIALOG(dialog));
683  gtk_widget_destroy(dialog);
684  if (i != GTK_RESPONSE_YES)
685  return FALSE;
686  }
687  }
688  }
690 
691  return TRUE;
692 }
693 
694 static gboolean rcw_delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
695 {
696  TRACE_CALL(__func__);
697  rcw_delete(RCW(widget));
698  return TRUE;
699 }
700 
701 static void rcw_destroy(GtkWidget *widget, gpointer data)
702 {
703  TRACE_CALL(__func__);
705  RemminaConnectionWindow *cnnwin;
706 
707  if (!REMMINA_IS_CONNECTION_WINDOW(widget))
708  return;
709 
710  cnnwin = (RemminaConnectionWindow *)widget;
711  priv = cnnwin->priv;
712 
713  if (priv->kbcaptured)
714  rcw_kp_ungrab(cnnwin);
715 
716  if (priv->acs_eventsourceid) {
717  g_source_remove(priv->acs_eventsourceid);
718  priv->acs_eventsourceid = 0;
719  }
720  if (priv->spf_eventsourceid) {
721  g_source_remove(priv->spf_eventsourceid);
722  priv->spf_eventsourceid = 0;
723  }
724  if (priv->grab_retry_eventsourceid) {
725  g_source_remove(priv->grab_retry_eventsourceid);
726  priv->grab_retry_eventsourceid = 0;
727  }
728  if (cnnwin->priv->delayed_grab_eventsourceid) {
729  g_source_remove(cnnwin->priv->delayed_grab_eventsourceid);
730  cnnwin->priv->delayed_grab_eventsourceid = 0;
731  }
732  if (priv->ftb_hide_eventsource) {
733  g_source_remove(priv->ftb_hide_eventsource);
734  priv->ftb_hide_eventsource = 0;
735  }
736  if (priv->tar_eventsource) {
737  g_source_remove(priv->tar_eventsource);
738  priv->tar_eventsource = 0;
739  }
740  if (priv->hidetb_eventsource) {
741  g_source_remove(priv->hidetb_eventsource);
742  priv->hidetb_eventsource = 0;
743  }
744  if (priv->dwp_eventsourceid) {
745  g_source_remove(priv->dwp_eventsourceid);
746  priv->dwp_eventsourceid = 0;
747  }
748 
749  /* There is no need to destroy priv->floating_toolbar_widget,
750  * because it’s our child and will be destroyed automatically */
751 
752  cnnwin->priv = NULL;
753  g_free(priv);
754 }
755 
756 gboolean rcw_notify_widget_toolbar_placement(GtkWidget *widget, gpointer data)
757 {
758  TRACE_CALL(__func__);
759  GType rcwtype;
760 
761  rcwtype = rcw_get_type();
762  if (G_TYPE_CHECK_INSTANCE_TYPE(widget, rcwtype)) {
763  g_signal_emit_by_name(G_OBJECT(widget), "toolbar-place");
764  return TRUE;
765  }
766  return FALSE;
767 }
768 
769 static gboolean rcw_tb_drag_failed(GtkWidget *widget, GdkDragContext *context,
770  GtkDragResult result, gpointer user_data)
771 {
772  TRACE_CALL(__func__);
774  RemminaConnectionWindow *cnnwin;
775 
776 
777  cnnwin = (RemminaConnectionWindow *)user_data;
778  priv = cnnwin->priv;
779 
780  if (priv->toolbar)
781  gtk_widget_show(GTK_WIDGET(priv->toolbar));
782 
783  return TRUE;
784 }
785 
786 static gboolean rcw_tb_drag_drop(GtkWidget *widget, GdkDragContext *context,
787  gint x, gint y, guint time, gpointer user_data)
788 {
789  TRACE_CALL(__func__);
790  GtkAllocation wa;
791  gint new_toolbar_placement;
793  RemminaConnectionWindow *cnnwin;
794 
795  cnnwin = (RemminaConnectionWindow *)user_data;
796  priv = cnnwin->priv;
797 
798  gtk_widget_get_allocation(widget, &wa);
799 
800  if (wa.width * y >= wa.height * x) {
801  if (wa.width * y > wa.height * (wa.width - x))
802  new_toolbar_placement = TOOLBAR_PLACEMENT_BOTTOM;
803  else
804  new_toolbar_placement = TOOLBAR_PLACEMENT_LEFT;
805  } else {
806  if (wa.width * y > wa.height * (wa.width - x))
807  new_toolbar_placement = TOOLBAR_PLACEMENT_RIGHT;
808  else
809  new_toolbar_placement = TOOLBAR_PLACEMENT_TOP;
810  }
811 
812  gtk_drag_finish(context, TRUE, TRUE, time);
813 
814  if (new_toolbar_placement != remmina_pref.toolbar_placement) {
815  /* Save new position */
816  remmina_pref.toolbar_placement = new_toolbar_placement;
818 
819  /* Signal all windows that the toolbar must be moved */
821  }
822  if (priv->toolbar)
823  gtk_widget_show(GTK_WIDGET(priv->toolbar));
824 
825  return TRUE;
826 }
827 
828 static void rcw_tb_drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer user_data)
829 {
830  TRACE_CALL(__func__);
831 
832  cairo_surface_t *surface;
833  cairo_t *cr;
834  GtkAllocation wa;
835  double dashes[] = { 10 };
836 
837  gtk_widget_get_allocation(widget, &wa);
838 
839  surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 16, 16);
840  cr = cairo_create(surface);
841  cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
842  cairo_set_line_width(cr, 4);
843  cairo_set_dash(cr, dashes, 1, 0);
844  cairo_rectangle(cr, 0, 0, 16, 16);
845  cairo_stroke(cr);
846  cairo_destroy(cr);
847 
848  gtk_widget_hide(widget);
849 
850  gtk_drag_set_icon_surface(context, surface);
851 }
852 
854 {
855  TRACE_CALL(__func__);
856  RemminaConnectionWindowPriv *priv = cnnwin->priv;
857  RemminaConnectionObject *cnnobj;
858 
859  cnnobj = rcw_get_visible_cnnobj(cnnwin);
860  if (!cnnobj) return;
861 
862  priv->floating_toolbar_opacity = (1.0 - TOOLBAR_OPACITY_MIN) / ((gdouble)TOOLBAR_OPACITY_LEVEL)
863  * ((gdouble)(TOOLBAR_OPACITY_LEVEL - remmina_file_get_int(cnnobj->remmina_file, "toolbar_opacity", 0)))
864  + TOOLBAR_OPACITY_MIN;
865  if (priv->floating_toolbar_widget)
866  gtk_widget_set_opacity(GTK_WIDGET(priv->overlay_ftb_overlay), priv->floating_toolbar_opacity);
867 }
868 
869 static gboolean rcw_floating_toolbar_make_invisible(gpointer data)
870 {
871  TRACE_CALL(__func__);
873 
874  gtk_widget_set_opacity(GTK_WIDGET(priv->overlay_ftb_overlay), 0.0);
875  priv->ftb_hide_eventsource = 0;
876  return G_SOURCE_REMOVE;
877 }
878 
879 static void rcw_floating_toolbar_show(RemminaConnectionWindow *cnnwin, gboolean show)
880 {
881  TRACE_CALL(__func__);
882  RemminaConnectionWindowPriv *priv = cnnwin->priv;
883 
884  if (priv->floating_toolbar_widget == NULL)
885  return;
886 
887  if (show || priv->pin_down) {
888  /* Make the FTB no longer transparent, in case we have an hidden toolbar */
890  /* Remove outstanding hide events, if not yet active */
891  if (priv->ftb_hide_eventsource) {
892  g_source_remove(priv->ftb_hide_eventsource);
893  priv->ftb_hide_eventsource = 0;
894  }
895  } else {
896  /* If we are hiding and the toolbar must be made invisible, schedule
897  * a later toolbar hide */
899  if (priv->ftb_hide_eventsource == 0)
900  priv->ftb_hide_eventsource = g_timeout_add(1000, rcw_floating_toolbar_make_invisible, priv);
901  }
902 
903  gtk_revealer_set_reveal_child(GTK_REVEALER(priv->revealer), show || priv->pin_down);
904 }
905 
906 static void rco_get_desktop_size(RemminaConnectionObject *cnnobj, gint *width, gint *height)
907 {
908  TRACE_CALL(__func__);
909  RemminaProtocolWidget *gp = REMMINA_PROTOCOL_WIDGET(cnnobj->proto);
910 
911 
914  if (*width == 0) {
915  /* Before connecting we do not have real remote width/height,
916  * so we ask profile values */
919  }
920 }
921 
922 void rco_set_scrolled_policy(RemminaScaleMode scalemode, GtkScrolledWindow *scrolled_window)
923 {
924  TRACE_CALL(__func__);
925 
926  gtk_scrolled_window_set_policy(scrolled_window,
927  scalemode == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED ? GTK_POLICY_NEVER : GTK_POLICY_AUTOMATIC,
928  scalemode == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED ? GTK_POLICY_NEVER : GTK_POLICY_AUTOMATIC);
929 }
930 
931 static GtkWidget *rco_create_scrolled_container(RemminaScaleMode scalemode, int view_mode)
932 {
933  GtkWidget *scrolled_container;
934 
935  if (view_mode == VIEWPORT_FULLSCREEN_MODE) {
936  scrolled_container = remmina_scrolled_viewport_new();
937  } else {
938  scrolled_container = gtk_scrolled_window_new(NULL, NULL);
939  rco_set_scrolled_policy(scalemode, GTK_SCROLLED_WINDOW(scrolled_container));
940  gtk_container_set_border_width(GTK_CONTAINER(scrolled_container), 0);
941  gtk_widget_set_can_focus(scrolled_container, FALSE);
942  }
943 
944  gtk_widget_set_name(scrolled_container, "remmina-scrolled-container");
945  gtk_widget_show(scrolled_container);
946 
947  return scrolled_container;
948 }
949 
951 {
952  TRACE_CALL(__func__);
953 
954  RemminaConnectionWindowPriv *priv = cnnwin->priv;
955  RemminaConnectionObject *cnnobj;
956  gint dwidth, dheight;
957  GtkAllocation nba, ca, ta;
958 
959  cnnwin->priv->tar_eventsource = 0;
960 
961  if (priv->toolbar_is_reconfiguring)
962  return G_SOURCE_REMOVE;
963 
964  if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return FALSE;
965 
966  if (cnnobj->connected && GTK_IS_SCROLLED_WINDOW(cnnobj->scrolled_container)) {
967  rco_get_desktop_size(cnnobj, &dwidth, &dheight);
968  gtk_widget_get_allocation(GTK_WIDGET(priv->notebook), &nba);
969  gtk_widget_get_allocation(cnnobj->scrolled_container, &ca);
970  gtk_widget_get_allocation(priv->toolbar, &ta);
971  if (remmina_pref.toolbar_placement == TOOLBAR_PLACEMENT_LEFT ||
973  gtk_window_resize(GTK_WINDOW(cnnobj->cnnwin), MAX(1, dwidth + ta.width + nba.width - ca.width),
974  MAX(1, dheight + nba.height - ca.height));
975  else
976  gtk_window_resize(GTK_WINDOW(cnnobj->cnnwin), MAX(1, dwidth + nba.width - ca.width),
977  MAX(1, dheight + ta.height + nba.height - ca.height));
978  gtk_container_check_resize(GTK_CONTAINER(cnnobj->cnnwin));
979  }
980  if (GTK_IS_SCROLLED_WINDOW(cnnobj->scrolled_container)) {
981  RemminaScaleMode scalemode = get_current_allowed_scale_mode(cnnobj, NULL, NULL);
982  rco_set_scrolled_policy(scalemode, GTK_SCROLLED_WINDOW(cnnobj->scrolled_container));
983  }
984 
985  return G_SOURCE_REMOVE;
986 }
987 
988 static void rcw_toolbar_autofit(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
989 {
990  TRACE_CALL(__func__);
991  RemminaConnectionObject *cnnobj;
992 
993  if (cnnwin->priv->toolbar_is_reconfiguring)
994  return;
995  if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return;
996 
997  if (GTK_IS_SCROLLED_WINDOW(cnnobj->scrolled_container)) {
998  if ((gdk_window_get_state(gtk_widget_get_window(GTK_WIDGET(cnnwin))) & GDK_WINDOW_STATE_MAXIMIZED) != 0)
999  gtk_window_unmaximize(GTK_WINDOW(cnnwin));
1000 
1001  /* It’s tricky to make the toolbars disappear automatically, while keeping scrollable.
1002  * Please tell me if you know a better way to do this */
1003  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(cnnobj->scrolled_container), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
1004 
1005  cnnwin->priv->tar_eventsource = g_timeout_add(200, (GSourceFunc)rcw_toolbar_autofit_restore, cnnwin);
1006  }
1007 }
1008 
1009 void rco_get_monitor_geometry(RemminaConnectionObject *cnnobj, GdkRectangle *sz)
1010 {
1011  TRACE_CALL(__func__);
1012 
1013  /* Fill sz with the monitor (or workarea) size and position
1014  * of the monitor (or workarea) where cnnobj->cnnwin is located */
1015 
1016  GdkRectangle monitor_geometry;
1017 
1018  sz->x = sz->y = sz->width = sz->height = 0;
1019 
1020  if (!cnnobj)
1021  return;
1022  if (!cnnobj->cnnwin)
1023  return;
1024  if (!gtk_widget_is_visible(GTK_WIDGET(cnnobj->cnnwin)))
1025  return;
1026 
1027 #if GTK_CHECK_VERSION(3, 22, 0)
1028  GdkDisplay *display;
1029  GdkMonitor *monitor;
1030  display = gtk_widget_get_display(GTK_WIDGET(cnnobj->cnnwin));
1031  monitor = gdk_display_get_monitor_at_window(display, gtk_widget_get_window(GTK_WIDGET(cnnobj->cnnwin)));
1032 #else
1033  GdkScreen *screen;
1034  gint monitor;
1035  screen = gtk_window_get_screen(GTK_WINDOW(cnnobj->cnnwin));
1036  monitor = gdk_screen_get_monitor_at_window(screen, gtk_widget_get_window(GTK_WIDGET(cnnobj->cnnwin)));
1037 #endif
1038 
1039 #if GTK_CHECK_VERSION(3, 22, 0)
1040  gdk_monitor_get_workarea(monitor, &monitor_geometry);
1041  /* Under Wayland, GTK 3.22, all values returned by gdk_monitor_get_geometry()
1042  * and gdk_monitor_get_workarea() seem to have been divided by the
1043  * gdk scale factor, so we need to adjust the returned rect
1044  * undoing the division */
1045 #ifdef GDK_WINDOWING_WAYLAND
1046  if (GDK_IS_WAYLAND_DISPLAY(display)) {
1047  int monitor_scale_factor = gdk_monitor_get_scale_factor(monitor);
1048  monitor_geometry.width *= monitor_scale_factor;
1049  monitor_geometry.height *= monitor_scale_factor;
1050  }
1051 #endif
1052 #elif gdk_screen_get_monitor_workarea
1053  gdk_screen_get_monitor_workarea(screen, monitor, &monitor_geometry);
1054 #else
1055  gdk_screen_get_monitor_geometry(screen, monitor, &monitor_geometry);
1056 #endif
1057  *sz = monitor_geometry;
1058 }
1059 
1061 {
1062  TRACE_CALL(__func__);
1063  gboolean scroll_required = FALSE;
1064 
1065  GdkRectangle monitor_geometry;
1066  gint rd_width, rd_height;
1067  gint bordersz;
1068  gint scalemode;
1069 
1070  scalemode = remmina_protocol_widget_get_current_scale_mode(REMMINA_PROTOCOL_WIDGET(cnnobj->proto));
1071 
1072  /* Get remote destkop size */
1073  rco_get_desktop_size(cnnobj, &rd_width, &rd_height);
1074 
1075  /* Get our monitor size */
1076  rco_get_monitor_geometry(cnnobj, &monitor_geometry);
1077 
1078  if (!remmina_protocol_widget_get_expand(REMMINA_PROTOCOL_WIDGET(cnnobj->proto)) &&
1079  (monitor_geometry.width < rd_width || monitor_geometry.height < rd_height) &&
1081  scroll_required = TRUE;
1082 
1083  switch (cnnobj->cnnwin->priv->view_mode) {
1085  gtk_window_resize(GTK_WINDOW(cnnobj->cnnwin), monitor_geometry.width, monitor_geometry.height);
1086  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(cnnobj->scrolled_container),
1087  (scroll_required ? GTK_POLICY_AUTOMATIC : GTK_POLICY_NEVER),
1088  (scroll_required ? GTK_POLICY_AUTOMATIC : GTK_POLICY_NEVER));
1089  break;
1090 
1092  bordersz = scroll_required ? SCROLL_BORDER_SIZE : 0;
1093  gtk_window_resize(GTK_WINDOW(cnnobj->cnnwin), monitor_geometry.width, monitor_geometry.height);
1094  if (REMMINA_IS_SCROLLED_VIEWPORT(cnnobj->scrolled_container))
1095  /* Put a border around Notebook content (RemminaScrolledViewpord), so we can
1096  * move the mouse over the border to scroll */
1097  gtk_container_set_border_width(GTK_CONTAINER(cnnobj->scrolled_container), bordersz);
1098 
1099  break;
1100 
1101  case SCROLLED_WINDOW_MODE:
1102  if (remmina_file_get_int(cnnobj->remmina_file, "viewmode", UNDEFINED_MODE) == UNDEFINED_MODE) {
1103  /* ToDo: is this really needed ? When ? */
1104  gtk_window_set_default_size(GTK_WINDOW(cnnobj->cnnwin),
1105  MIN(rd_width, monitor_geometry.width), MIN(rd_height, monitor_geometry.height));
1106  if (rd_width >= monitor_geometry.width || rd_height >= monitor_geometry.height) {
1107  gtk_window_maximize(GTK_WINDOW(cnnobj->cnnwin));
1108  remmina_file_set_int(cnnobj->remmina_file, "window_maximize", TRUE);
1109  } else {
1110  rcw_toolbar_autofit(NULL, cnnobj->cnnwin);
1111  remmina_file_set_int(cnnobj->remmina_file, "window_maximize", FALSE);
1112  }
1113  } else {
1114  if (remmina_file_get_int(cnnobj->remmina_file, "window_maximize", FALSE))
1115  gtk_window_maximize(GTK_WINDOW(cnnobj->cnnwin));
1116  }
1117  break;
1118 
1119  default:
1120  break;
1121  }
1122 }
1123 
1124 static void rcw_set_tooltip(GtkWidget *item, const gchar *tip, guint key1, guint key2)
1125 {
1126  TRACE_CALL(__func__);
1127  gchar *s1;
1128  gchar *s2;
1129 
1130  if (remmina_pref.hostkey && key1) {
1131  if (key2)
1132  s1 = g_strdup_printf(" (%s + %s,%s)", gdk_keyval_name(remmina_pref.hostkey),
1133  gdk_keyval_name(gdk_keyval_to_upper(key1)), gdk_keyval_name(gdk_keyval_to_upper(key2)));
1134  else if (key1 == remmina_pref.hostkey)
1135  s1 = g_strdup_printf(" (%s)", gdk_keyval_name(remmina_pref.hostkey));
1136  else
1137  s1 = g_strdup_printf(" (%s + %s)", gdk_keyval_name(remmina_pref.hostkey),
1138  gdk_keyval_name(gdk_keyval_to_upper(key1)));
1139  } else {
1140  s1 = NULL;
1141  }
1142  s2 = g_strdup_printf("%s%s", tip, s1 ? s1 : "");
1143  gtk_widget_set_tooltip_text(item, s2);
1144  g_free(s2);
1145  g_free(s1);
1146 }
1147 
1149 {
1150  TRACE_CALL(__func__);
1151  RemminaScaleMode scalemode;
1152  gboolean scaledexpandedmode;
1153  int rdwidth, rdheight;
1154  gfloat aratio;
1155 
1156  if (!cnnobj->plugin_can_scale) {
1157  /* If we have a plugin that cannot scale,
1158  * (i.e. SFTP plugin), then we expand proto */
1159  gtk_widget_set_halign(GTK_WIDGET(cnnobj->proto), GTK_ALIGN_FILL);
1160  gtk_widget_set_valign(GTK_WIDGET(cnnobj->proto), GTK_ALIGN_FILL);
1161  } else {
1162  /* Plugin can scale */
1163 
1164  scalemode = get_current_allowed_scale_mode(cnnobj, NULL, NULL);
1165  scaledexpandedmode = remmina_protocol_widget_get_expand(REMMINA_PROTOCOL_WIDGET(cnnobj->proto));
1166 
1167  /* Check if we need aspectframe and create/destroy it accordingly */
1168  if (scalemode == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED && !scaledexpandedmode) {
1169  /* We need an aspectframe as a parent of proto */
1170  rdwidth = remmina_protocol_widget_get_width(REMMINA_PROTOCOL_WIDGET(cnnobj->proto));
1171  rdheight = remmina_protocol_widget_get_height(REMMINA_PROTOCOL_WIDGET(cnnobj->proto));
1172  aratio = (gfloat)rdwidth / (gfloat)rdheight;
1173  if (!cnnobj->aspectframe) {
1174  /* We need a new aspectframe */
1175  cnnobj->aspectframe = gtk_aspect_frame_new(NULL, 0.5, 0.5, aratio, FALSE);
1176  gtk_widget_set_name(cnnobj->aspectframe, "remmina-cw-aspectframe");
1177  gtk_frame_set_shadow_type(GTK_FRAME(cnnobj->aspectframe), GTK_SHADOW_NONE);
1178  g_object_ref(cnnobj->proto);
1179  gtk_container_remove(GTK_CONTAINER(cnnobj->viewport), cnnobj->proto);
1180  gtk_container_add(GTK_CONTAINER(cnnobj->viewport), cnnobj->aspectframe);
1181  gtk_container_add(GTK_CONTAINER(cnnobj->aspectframe), cnnobj->proto);
1182  g_object_unref(cnnobj->proto);
1183  gtk_widget_show(cnnobj->aspectframe);
1184  if (cnnobj != NULL && cnnobj->cnnwin != NULL && cnnobj->cnnwin->priv->notebook != NULL)
1185  rcw_grab_focus(cnnobj->cnnwin);
1186  } else {
1187  gtk_aspect_frame_set(GTK_ASPECT_FRAME(cnnobj->aspectframe), 0.5, 0.5, aratio, FALSE);
1188  }
1189  } else {
1190  /* We do not need an aspectframe as a parent of proto */
1191  if (cnnobj->aspectframe) {
1192  /* We must remove the old aspectframe reparenting proto to viewport */
1193  g_object_ref(cnnobj->aspectframe);
1194  g_object_ref(cnnobj->proto);
1195  gtk_container_remove(GTK_CONTAINER(cnnobj->aspectframe), cnnobj->proto);
1196  gtk_container_remove(GTK_CONTAINER(cnnobj->viewport), cnnobj->aspectframe);
1197  g_object_unref(cnnobj->aspectframe);
1198  cnnobj->aspectframe = NULL;
1199  gtk_container_add(GTK_CONTAINER(cnnobj->viewport), cnnobj->proto);
1200  g_object_unref(cnnobj->proto);
1201  if (cnnobj != NULL && cnnobj->cnnwin != NULL && cnnobj->cnnwin->priv->notebook != NULL)
1202  rcw_grab_focus(cnnobj->cnnwin);
1203  }
1204  }
1205 
1207  /* We have a plugin that can be scaled, and the scale button
1208  * has been pressed. Give it the correct WxH maintaining aspect
1209  * ratio of remote destkop size */
1210  gtk_widget_set_halign(GTK_WIDGET(cnnobj->proto), GTK_ALIGN_FILL);
1211  gtk_widget_set_valign(GTK_WIDGET(cnnobj->proto), GTK_ALIGN_FILL);
1212  } else {
1213  /* Plugin can scale, but no scaling is active. Ensure that we have
1214  * aspectframe with a ratio of 1 */
1215  gtk_widget_set_halign(GTK_WIDGET(cnnobj->proto), GTK_ALIGN_CENTER);
1216  gtk_widget_set_valign(GTK_WIDGET(cnnobj->proto), GTK_ALIGN_CENTER);
1217  }
1218  }
1219 }
1220 
1221 static void nb_set_current_page(GtkNotebook *notebook, GtkWidget *page)
1222 {
1223  gint np, i;
1224 
1225  np = gtk_notebook_get_n_pages(notebook);
1226  for (i = 0; i < np; i++) {
1227  if (gtk_notebook_get_nth_page(notebook, i) == page) {
1228  gtk_notebook_set_current_page(notebook, i);
1229  break;
1230  }
1231  }
1232 }
1233 
1234 static void nb_migrate_message_panels(GtkWidget *frompage, GtkWidget *topage)
1235 {
1236  /* Migrate a single connection tab from a notebook to another one */
1237  GList *lst, *l;
1238 
1239  /* Reparent message panels */
1240  lst = gtk_container_get_children(GTK_CONTAINER(frompage));
1241  for (l = lst; l != NULL; l = l->next) {
1242  if (REMMINA_IS_MESSAGE_PANEL(l->data)) {
1243  g_object_ref(l->data);
1244  gtk_container_remove(GTK_CONTAINER(frompage), GTK_WIDGET(l->data));
1245  gtk_container_add(GTK_CONTAINER(topage), GTK_WIDGET(l->data));
1246  g_object_unref(l->data);
1247  gtk_box_reorder_child(GTK_BOX(topage), GTK_WIDGET(l->data), 0);
1248  }
1249  }
1250  g_list_free(lst);
1251 
1252 }
1253 
1255 {
1256  /* Migrate a complete notebook from a window to another */
1257 
1258  gchar *tag;
1259  gint cp, np, i;
1260  GtkNotebook *from_notebook;
1261  GtkWidget *frompage, *newpage, *old_scrolled_container;
1262  RemminaConnectionObject *cnnobj;
1263  RemminaScaleMode scalemode;
1264 
1265  /* Migrate TAG */
1266  tag = g_strdup((gchar *)g_object_get_data(G_OBJECT(from), "tag"));
1267  g_object_set_data_full(G_OBJECT(to), "tag", tag, (GDestroyNotify)g_free);
1268 
1269  /* Migrate notebook content */
1270  from_notebook = from->priv->notebook;
1271  if (from_notebook && GTK_IS_NOTEBOOK(from_notebook)) {
1272 
1273  cp = gtk_notebook_get_current_page(from_notebook);
1274  np = gtk_notebook_get_n_pages(from_notebook);
1275  /* Create pages on dest notebook and migrate
1276  * page content */
1277  for (i = 0; i < np; i++) {
1278  frompage = gtk_notebook_get_nth_page(from_notebook, i);
1279  cnnobj = g_object_get_data(G_OBJECT(frompage), "cnnobj");
1280 
1281  /* A scrolled container must be recreated, because it can be different on the new window/page
1282  depending on view_mode */
1283  scalemode = get_current_allowed_scale_mode(cnnobj, NULL, NULL);
1284  old_scrolled_container = cnnobj->scrolled_container;
1285  cnnobj->scrolled_container = rco_create_scrolled_container(scalemode, to->priv->view_mode);
1286 
1287  newpage = rcw_append_new_page(to, cnnobj);
1288 
1289  nb_migrate_message_panels(frompage, newpage);
1290 
1291  /* Reparent the viewport (which is inside scrolled_container) to the new page */
1292  g_object_ref(cnnobj->viewport);
1293  gtk_container_remove(GTK_CONTAINER(old_scrolled_container), cnnobj->viewport);
1294  gtk_container_add(GTK_CONTAINER(cnnobj->scrolled_container), cnnobj->viewport);
1295  g_object_unref(cnnobj->viewport);
1296 
1297  /* Destroy old scrolled_container. Not really needed, it will be destroyed
1298  * when removing the page from the notepad */
1299  gtk_widget_destroy(old_scrolled_container);
1300 
1301  }
1302 
1303  /* Remove all the pages from source notebook */
1304  for (i = np - 1; i >= 0; i--)
1305  gtk_notebook_remove_page(from_notebook, i);
1306  gtk_notebook_set_current_page(to->priv->notebook, cp);
1307 
1308  }
1309 }
1310 
1311 static void rcw_switch_viewmode(RemminaConnectionWindow *cnnwin, int newmode)
1312 {
1313  GdkWindowState s;
1314  RemminaConnectionWindow *newwin;
1315  gint old_width, old_height;
1316  int old_mode;
1317 
1318  old_mode = cnnwin->priv->view_mode;
1319  if (old_mode == newmode)
1320  return;
1321 
1322  if (newmode == VIEWPORT_FULLSCREEN_MODE || newmode == SCROLLED_FULLSCREEN_MODE) {
1323  if (old_mode == SCROLLED_WINDOW_MODE) {
1324  /* We are leaving SCROLLED_WINDOW_MODE, save W,H, and maximized
1325  * status before self destruction of cnnwin */
1326  gtk_window_get_size(GTK_WINDOW(cnnwin), &old_width, &old_height);
1327  s = gdk_window_get_state(gtk_widget_get_window(GTK_WIDGET(cnnwin)));
1328  }
1329  newwin = rcw_create_fullscreen(GTK_WINDOW(cnnwin), cnnwin->priv->fss_view_mode);
1330  rcw_migrate(cnnwin, newwin);
1331  if (old_mode == SCROLLED_WINDOW_MODE) {
1332  newwin->priv->ss_maximized = (s & GDK_WINDOW_STATE_MAXIMIZED) ? TRUE : FALSE;
1333  newwin->priv->ss_width = old_width;
1334  newwin->priv->ss_height = old_height;
1335  }
1336  } else {
1337  newwin = rcw_create_scrolled(cnnwin->priv->ss_width, cnnwin->priv->ss_height,
1338  cnnwin->priv->ss_maximized);
1339  rcw_migrate(cnnwin, newwin);
1340  if (old_mode == VIEWPORT_FULLSCREEN_MODE || old_mode == SCROLLED_FULLSCREEN_MODE)
1341  /* We are leaving a FULLSCREEN mode, save some parameters
1342  * status before self destruction of cnnwin */
1343  newwin->priv->fss_view_mode = old_mode;
1344  }
1345 
1346  /* Prevent unreleased hostkey from old window to be released here */
1347  newwin->priv->hostkey_used = TRUE;
1348 }
1349 
1350 
1351 static void rcw_toolbar_fullscreen(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
1352 {
1353  TRACE_CALL(__func__);
1354 
1355  RemminaConnectionObject *cnnobj;
1356 
1357  if (cnnwin->priv->toolbar_is_reconfiguring)
1358  return;
1359 
1360  if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return;
1361 
1362  RemminaProtocolWidget *gp = REMMINA_PROTOCOL_WIDGET(cnnobj->proto);
1363 
1364  if (remmina_protocol_widget_get_multimon(gp) >= 1) {
1365  REMMINA_DEBUG("Fullscreen on all monitor");
1366  gdk_window_set_fullscreen_mode(gtk_widget_get_window(GTK_WIDGET(toggle)), GDK_FULLSCREEN_ON_ALL_MONITORS);
1367  } else {
1368  REMMINA_DEBUG("Fullscreen on one monitor");
1369  }
1370 
1371  if ((toggle != NULL && toggle == cnnwin->priv->toolitem_fullscreen)) {
1372  if (gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(toggle))) {
1374  gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(cnnwin->priv->toolitem_multimon), TRUE);
1375  rcw_switch_viewmode(cnnwin, cnnwin->priv->fss_view_mode);
1376  } else {
1378  }
1379  } else
1380  if (gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(cnnwin->priv->toolitem_multimon))) {
1381  rcw_switch_viewmode(cnnwin, cnnwin->priv->fss_view_mode);
1382  } else {
1384  }
1385 }
1386 
1387 static void rco_viewport_fullscreen_mode(GtkWidget *widget, RemminaConnectionObject *cnnobj)
1388 {
1389  TRACE_CALL(__func__);
1390  RemminaConnectionWindow *newwin;
1391 
1392  if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
1393  return;
1394  cnnobj->cnnwin->priv->fss_view_mode = VIEWPORT_FULLSCREEN_MODE;
1395  newwin = rcw_create_fullscreen(GTK_WINDOW(cnnobj->cnnwin), VIEWPORT_FULLSCREEN_MODE);
1396  rcw_migrate(cnnobj->cnnwin, newwin);
1397 }
1398 
1399 static void rco_scrolled_fullscreen_mode(GtkWidget *widget, RemminaConnectionObject *cnnobj)
1400 {
1401  TRACE_CALL(__func__);
1402  RemminaConnectionWindow *newwin;
1403 
1404  if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
1405  return;
1406  cnnobj->cnnwin->priv->fss_view_mode = SCROLLED_FULLSCREEN_MODE;
1407  newwin = rcw_create_fullscreen(GTK_WINDOW(cnnobj->cnnwin), SCROLLED_FULLSCREEN_MODE);
1408  rcw_migrate(cnnobj->cnnwin, newwin);
1409 }
1410 
1411 static void rcw_fullscreen_option_popdown(GtkWidget *widget, RemminaConnectionWindow *cnnwin)
1412 {
1413  TRACE_CALL(__func__);
1414  RemminaConnectionWindowPriv *priv = cnnwin->priv;
1415 
1416  priv->sticky = FALSE;
1417  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->fullscreen_option_button), FALSE);
1418  rcw_floating_toolbar_show(cnnwin, FALSE);
1419 }
1420 
1421 void rcw_toolbar_fullscreen_option(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
1422 {
1423  TRACE_CALL(__func__);
1424  RemminaConnectionObject *cnnobj;
1425  GtkWidget *menu;
1426  GtkWidget *menuitem;
1427  GSList *group;
1428 
1429  if (cnnwin->priv->toolbar_is_reconfiguring)
1430  return;
1431 
1432  if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return;
1433 
1434  if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toggle)))
1435  return;
1436 
1437  cnnwin->priv->sticky = TRUE;
1438 
1439  menu = gtk_menu_new();
1440 
1441  menuitem = gtk_radio_menu_item_new_with_label(NULL, _("Viewport fullscreen mode"));
1442  gtk_widget_show(menuitem);
1443  gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1444  group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(menuitem));
1445  if (cnnwin->priv->view_mode == VIEWPORT_FULLSCREEN_MODE)
1446  gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
1447  g_signal_connect(G_OBJECT(menuitem), "toggled", G_CALLBACK(rco_viewport_fullscreen_mode), cnnobj);
1448 
1449  menuitem = gtk_radio_menu_item_new_with_label(group, _("Scrolled fullscreen"));
1450  gtk_widget_show(menuitem);
1451  gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1452  if (cnnwin->priv->view_mode == SCROLLED_FULLSCREEN_MODE)
1453  gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
1454  g_signal_connect(G_OBJECT(menuitem), "toggled", G_CALLBACK(rco_scrolled_fullscreen_mode), cnnobj);
1455 
1456  g_signal_connect(G_OBJECT(menu), "deactivate", G_CALLBACK(rcw_fullscreen_option_popdown), cnnwin);
1457 
1458 #if GTK_CHECK_VERSION(3, 22, 0)
1459  gtk_menu_popup_at_widget(GTK_MENU(menu), GTK_WIDGET(toggle),
1460  GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, NULL);
1461 #else
1462  gtk_menu_popup(GTK_MENU(menu), NULL, NULL, remmina_public_popup_position, cnnwin->priv->toolitem_fullscreen, 0,
1463  gtk_get_current_event_time());
1464 #endif
1465 }
1466 
1467 
1468 static void rcw_scaler_option_popdown(GtkWidget *widget, RemminaConnectionWindow *cnnwin)
1469 {
1470  TRACE_CALL(__func__);
1471  RemminaConnectionWindowPriv *priv = cnnwin->priv;
1472 
1473  if (priv->toolbar_is_reconfiguring)
1474  return;
1475  priv->sticky = FALSE;
1476  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->scaler_option_button), FALSE);
1477  rcw_floating_toolbar_show(cnnwin, FALSE);
1478 }
1479 
1480 static void rcw_scaler_expand(GtkWidget *widget, RemminaConnectionWindow *cnnwin)
1481 {
1482  TRACE_CALL(__func__);
1483  RemminaConnectionObject *cnnobj;
1484 
1485  if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
1486  return;
1487  cnnobj = rcw_get_visible_cnnobj(cnnwin);
1488  if (!cnnobj)
1489  return;
1490  remmina_protocol_widget_set_expand(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), TRUE);
1491  remmina_file_set_int(cnnobj->remmina_file, "scaler_expand", TRUE);
1493 }
1494 static void rcw_scaler_keep_aspect(GtkWidget *widget, RemminaConnectionWindow *cnnwin)
1495 {
1496  TRACE_CALL(__func__);
1497  RemminaConnectionObject *cnnobj;
1498 
1499  if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
1500  return;
1501  cnnobj = rcw_get_visible_cnnobj(cnnwin);
1502  if (!cnnobj)
1503  return;
1504 
1505  remmina_protocol_widget_set_expand(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), FALSE);
1506  remmina_file_set_int(cnnobj->remmina_file, "scaler_expand", FALSE);
1508 }
1509 
1510 static void rcw_toolbar_scaler_option(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
1511 {
1512  TRACE_CALL(__func__);
1514  RemminaConnectionObject *cnnobj;
1515  GtkWidget *menu;
1516  GtkWidget *menuitem;
1517  GSList *group;
1518  gboolean scaler_expand;
1519 
1520  if (cnnwin->priv->toolbar_is_reconfiguring)
1521  return;
1522 
1523  if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return;
1524  priv = cnnwin->priv;
1525 
1526  if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toggle)))
1527  return;
1528 
1529  scaler_expand = remmina_protocol_widget_get_expand(REMMINA_PROTOCOL_WIDGET(cnnobj->proto));
1530 
1531  priv->sticky = TRUE;
1532 
1533  menu = gtk_menu_new();
1534 
1535  menuitem = gtk_radio_menu_item_new_with_label(NULL, _("Keep aspect ratio when scaled"));
1536  gtk_widget_show(menuitem);
1537  gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1538  group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(menuitem));
1539  if (!scaler_expand)
1540  gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
1541  g_signal_connect(G_OBJECT(menuitem), "toggled", G_CALLBACK(rcw_scaler_keep_aspect), cnnwin);
1542 
1543  menuitem = gtk_radio_menu_item_new_with_label(group, _("Fill client window when scaled"));
1544  gtk_widget_show(menuitem);
1545  gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1546  if (scaler_expand)
1547  gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
1548  g_signal_connect(G_OBJECT(menuitem), "toggled", G_CALLBACK(rcw_scaler_expand), cnnwin);
1549 
1550  g_signal_connect(G_OBJECT(menu), "deactivate", G_CALLBACK(rcw_scaler_option_popdown), cnnwin);
1551 
1552 #if GTK_CHECK_VERSION(3, 22, 0)
1553  gtk_menu_popup_at_widget(GTK_MENU(menu), GTK_WIDGET(toggle),
1554  GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, NULL);
1555 #else
1556  gtk_menu_popup(GTK_MENU(menu), NULL, NULL, remmina_public_popup_position, priv->toolitem_scale, 0,
1557  gtk_get_current_event_time());
1558 #endif
1559 }
1560 
1561 void rco_switch_page_activate(GtkMenuItem *menuitem, RemminaConnectionObject *cnnobj)
1562 {
1563  TRACE_CALL(__func__);
1564  RemminaConnectionWindowPriv *priv = cnnobj->cnnwin->priv;
1565  gint page_num;
1566 
1567  page_num = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menuitem), "new-page-num"));
1568  gtk_notebook_set_current_page(GTK_NOTEBOOK(priv->notebook), page_num);
1569 }
1570 
1572 {
1573  TRACE_CALL(__func__);
1574  RemminaConnectionWindowPriv *priv = cnnwin->priv;
1575 
1576  priv->sticky = FALSE;
1577 
1578  gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_switch_page), FALSE);
1579  rcw_floating_toolbar_show(cnnwin, FALSE);
1580 }
1581 
1582 static void rcw_toolbar_switch_page(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
1583 {
1584  TRACE_CALL(__func__);
1585 
1586  RemminaConnectionWindowPriv *priv = cnnwin->priv;
1587  RemminaConnectionObject *cnnobj;
1588 
1589  GtkWidget *menu;
1590  GtkWidget *menuitem;
1591  GtkWidget *image;
1592  gint i, n;
1593 
1594  if (priv->toolbar_is_reconfiguring)
1595  return;
1596  if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return;
1597 
1598  if (!gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(toggle)))
1599  return;
1600 
1601  priv->sticky = TRUE;
1602 
1603  menu = gtk_menu_new();
1604 
1605  n = gtk_notebook_get_n_pages(GTK_NOTEBOOK(priv->notebook));
1606  for (i = 0; i < n; i++) {
1607  cnnobj = rcw_get_cnnobj_at_page(cnnobj->cnnwin, i);
1608 
1609  menuitem = gtk_menu_item_new_with_label(remmina_file_get_string(cnnobj->remmina_file, "name"));
1610  gtk_widget_show(menuitem);
1611  gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1612 
1613  image = gtk_image_new_from_icon_name(remmina_file_get_icon_name(cnnobj->remmina_file), GTK_ICON_SIZE_MENU);
1614  gtk_widget_show(image);
1615 
1616  g_object_set_data(G_OBJECT(menuitem), "new-page-num", GINT_TO_POINTER(i));
1617  g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(rco_switch_page_activate), cnnobj);
1618  if (i == gtk_notebook_get_current_page(GTK_NOTEBOOK(priv->notebook)))
1619  gtk_widget_set_sensitive(menuitem, FALSE);
1620  }
1621 
1622  g_signal_connect(G_OBJECT(menu), "deactivate", G_CALLBACK(rcw_toolbar_switch_page_popdown),
1623  cnnwin);
1624 
1625 #if GTK_CHECK_VERSION(3, 22, 0)
1626  gtk_menu_popup_at_widget(GTK_MENU(menu), GTK_WIDGET(toggle),
1627  GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, NULL);
1628 #else
1629  gtk_menu_popup(GTK_MENU(menu), NULL, NULL, remmina_public_popup_position, widget, 0, gtk_get_current_event_time());
1630 #endif
1631 }
1632 
1634 {
1635  TRACE_CALL(__func__);
1636  RemminaConnectionWindowPriv *priv = cnnobj->cnnwin->priv;
1637  GtkToolItem *toolitem;
1638  RemminaScaleMode sc;
1639 
1640  toolitem = priv->toolitem_autofit;
1641  if (toolitem) {
1642  if (priv->view_mode != SCROLLED_WINDOW_MODE) {
1643  gtk_widget_set_sensitive(GTK_WIDGET(toolitem), FALSE);
1644  } else {
1645  sc = get_current_allowed_scale_mode(cnnobj, NULL, NULL);
1646  gtk_widget_set_sensitive(GTK_WIDGET(toolitem), sc == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_NONE);
1647  }
1648  }
1649 }
1650 
1651 static void rco_change_scalemode(RemminaConnectionObject *cnnobj, gboolean bdyn, gboolean bscale)
1652 {
1653  RemminaScaleMode scalemode;
1654  RemminaConnectionWindowPriv *priv = cnnobj->cnnwin->priv;
1655 
1656  if (bdyn)
1658  else if (bscale)
1660  else
1662 
1663  remmina_protocol_widget_set_current_scale_mode(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), scalemode);
1664  remmina_file_set_int(cnnobj->remmina_file, "scale", scalemode);
1665  gtk_widget_set_sensitive(GTK_WIDGET(priv->scaler_option_button), scalemode == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED);
1667 
1668  remmina_protocol_widget_call_feature_by_type(REMMINA_PROTOCOL_WIDGET(cnnobj->proto),
1670 
1671  if (cnnobj->cnnwin->priv->view_mode != SCROLLED_WINDOW_MODE)
1672  rco_check_resize(cnnobj);
1673  if (GTK_IS_SCROLLED_WINDOW(cnnobj->scrolled_container)) {
1674  rco_set_scrolled_policy(scalemode, GTK_SCROLLED_WINDOW(cnnobj->scrolled_container));
1675  }
1676 }
1677 
1678 static void rcw_toolbar_dynres(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
1679 {
1680  TRACE_CALL(__func__);
1681  gboolean bdyn, bscale;
1682  RemminaConnectionObject *cnnobj;
1683 
1684  if (cnnwin->priv->toolbar_is_reconfiguring)
1685  return;
1686  if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return;
1687 
1688  if (cnnobj->connected) {
1689  bdyn = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(toggle));
1690  bscale = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(cnnobj->cnnwin->priv->toolitem_scale));
1691 
1692  if (bdyn && bscale) {
1693  gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(cnnobj->cnnwin->priv->toolitem_scale), FALSE);
1694  bscale = FALSE;
1695  }
1696 
1697  rco_change_scalemode(cnnobj, bdyn, bscale);
1698  }
1699 }
1700 
1701 static void rcw_toolbar_scaled_mode(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
1702 {
1703  TRACE_CALL(__func__);
1704  gboolean bdyn, bscale;
1705  RemminaConnectionObject *cnnobj;
1706 
1707  if (cnnwin->priv->toolbar_is_reconfiguring)
1708  return;
1709  if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return;
1710 
1711  bdyn = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(cnnobj->cnnwin->priv->toolitem_dynres));
1712  bscale = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(toggle));
1713 
1714  if (bdyn && bscale) {
1715  gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(cnnobj->cnnwin->priv->toolitem_dynres), FALSE);
1716  bdyn = FALSE;
1717  }
1718 
1719  rco_change_scalemode(cnnobj, bdyn, bscale);
1720 }
1721 
1722 static void rcw_toolbar_multi_monitor_mode(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
1723 {
1724  TRACE_CALL(__func__);
1725  RemminaConnectionObject *cnnobj;
1726 
1727  if (cnnwin->priv->toolbar_is_reconfiguring)
1728  return;
1729 
1730  if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return;
1731 
1732  if (gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(toggle))) {
1733  REMMINA_DEBUG("Saving multimon as 1");
1734  remmina_file_set_int(cnnobj->remmina_file, "multimon", 1);
1736  remmina_protocol_widget_call_feature_by_type(REMMINA_PROTOCOL_WIDGET(cnnobj->proto),
1738  if (!gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(cnnwin->priv->toolitem_fullscreen)))
1739  gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(cnnwin->priv->toolitem_fullscreen), TRUE);
1740  } else {
1741  REMMINA_DEBUG("Saving multimon as 0");
1742  remmina_file_set_int(cnnobj->remmina_file, "multimon", 0);
1744  rcw_toolbar_fullscreen(NULL, cnnwin);
1745  }
1746 }
1747 
1748 static void rcw_toolbar_open_main(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
1749 {
1750  TRACE_CALL(__func__);
1751 
1752  if (cnnwin->priv->toolbar_is_reconfiguring)
1753  return;
1754 
1756 }
1757 
1758 static void rcw_toolbar_preferences_popdown(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
1759 {
1760  TRACE_CALL(__func__);
1761  RemminaConnectionObject *cnnobj;
1762 
1763  if (cnnwin->priv->toolbar_is_reconfiguring)
1764  return;
1765  if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return;
1766 
1767  cnnobj->cnnwin->priv->sticky = FALSE;
1768 
1769  gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(cnnobj->cnnwin->priv->toolitem_preferences), FALSE);
1770  rcw_floating_toolbar_show(cnnwin, FALSE);
1771 }
1772 
1773 void rcw_toolbar_menu_popdown(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
1774 {
1775  TRACE_CALL(__func__);
1776  RemminaConnectionWindowPriv *priv = cnnwin->priv;
1777 
1778  if (priv->toolbar_is_reconfiguring)
1779  return;
1780 
1781  priv->sticky = FALSE;
1782 
1783  gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_menu), FALSE);
1784  rcw_floating_toolbar_show(cnnwin, FALSE);
1785 }
1786 
1787 void rcw_toolbar_tools_popdown(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
1788 {
1789  TRACE_CALL(__func__);
1790  RemminaConnectionWindowPriv *priv = cnnwin->priv;
1791 
1792  if (priv->toolbar_is_reconfiguring)
1793  return;
1794 
1795  priv->sticky = FALSE;
1796 
1797  gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_tools), FALSE);
1798  rcw_floating_toolbar_show(cnnwin, FALSE);
1799 }
1800 
1801 static void rco_call_protocol_feature_radio(GtkMenuItem *menuitem, RemminaConnectionObject *cnnobj)
1802 {
1803  TRACE_CALL(__func__);
1804  RemminaProtocolFeature *feature;
1805  gpointer value;
1806 
1807  if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem))) {
1808  feature = (RemminaProtocolFeature *)g_object_get_data(G_OBJECT(menuitem), "feature-type");
1809  value = g_object_get_data(G_OBJECT(menuitem), "feature-value");
1810 
1811  remmina_file_set_string(cnnobj->remmina_file, (const gchar *)feature->opt2, (const gchar *)value);
1812  remmina_protocol_widget_call_feature_by_ref(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), feature);
1813  }
1814 }
1815 
1816 static void rco_call_protocol_feature_check(GtkMenuItem *menuitem, RemminaConnectionObject *cnnobj)
1817 {
1818  TRACE_CALL(__func__);
1819  RemminaProtocolFeature *feature;
1820  gboolean value;
1821 
1822  feature = (RemminaProtocolFeature *)g_object_get_data(G_OBJECT(menuitem), "feature-type");
1823  value = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem));
1824  remmina_file_set_int(cnnobj->remmina_file, (const gchar *)feature->opt2, value);
1825  remmina_protocol_widget_call_feature_by_ref(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), feature);
1826 }
1827 
1828 static void rco_call_protocol_feature_activate(GtkMenuItem *menuitem, RemminaConnectionObject *cnnobj)
1829 {
1830  TRACE_CALL(__func__);
1831  RemminaProtocolFeature *feature;
1832 
1833  feature = (RemminaProtocolFeature *)g_object_get_data(G_OBJECT(menuitem), "feature-type");
1834  remmina_protocol_widget_call_feature_by_ref(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), feature);
1835 }
1836 
1838  GtkWidget *menu, const RemminaProtocolFeature *feature, const gchar *domain, gboolean enabled)
1839 {
1840  TRACE_CALL(__func__);
1841  GtkWidget *menuitem;
1842  GSList *group;
1843  gint i;
1844  const gchar **list;
1845  const gchar *value;
1846 
1847  group = NULL;
1848  value = remmina_file_get_string(remminafile, (const gchar *)feature->opt2);
1849  list = (const gchar **)feature->opt3;
1850  for (i = 0; list[i]; i += 2) {
1851  menuitem = gtk_radio_menu_item_new_with_label(group, g_dgettext(domain, list[i + 1]));
1852  group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(menuitem));
1853  gtk_widget_show(menuitem);
1854  gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1855 
1856  if (enabled) {
1857  g_object_set_data(G_OBJECT(menuitem), "feature-type", (gpointer)feature);
1858  g_object_set_data(G_OBJECT(menuitem), "feature-value", (gpointer)list[i]);
1859 
1860  if (value && g_strcmp0(list[i], value) == 0)
1861  gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
1862 
1863  g_signal_connect(G_OBJECT(menuitem), "toggled",
1864  G_CALLBACK(rco_call_protocol_feature_radio), cnnobj);
1865  } else {
1866  gtk_widget_set_sensitive(menuitem, FALSE);
1867  }
1868  }
1869 }
1870 
1872  GtkWidget *menu, const RemminaProtocolFeature *feature,
1873  const gchar *domain, gboolean enabled)
1874 {
1875  TRACE_CALL(__func__);
1876  GtkWidget *menuitem;
1877 
1878  menuitem = gtk_check_menu_item_new_with_label(g_dgettext(domain, (const gchar *)feature->opt3));
1879  gtk_widget_show(menuitem);
1880  gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1881 
1882  if (enabled) {
1883  g_object_set_data(G_OBJECT(menuitem), "feature-type", (gpointer)feature);
1884 
1885  gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
1886  remmina_file_get_int(cnnobj->remmina_file, (const gchar *)feature->opt2, FALSE));
1887 
1888  g_signal_connect(G_OBJECT(menuitem), "toggled",
1889  G_CALLBACK(rco_call_protocol_feature_check), cnnobj);
1890  } else {
1891  gtk_widget_set_sensitive(menuitem, FALSE);
1892  }
1893 }
1894 
1895 static void rcw_toolbar_preferences(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
1896 {
1897  TRACE_CALL(__func__);
1899  RemminaConnectionObject *cnnobj;
1900  const RemminaProtocolFeature *feature;
1901  GtkWidget *menu;
1902  GtkWidget *menuitem;
1903  gboolean separator;
1904  gchar *domain;
1905  gboolean enabled;
1906 
1907  if (cnnwin->priv->toolbar_is_reconfiguring)
1908  return;
1909  if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return;
1910  priv = cnnobj->cnnwin->priv;
1911 
1912  if (!gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(toggle)))
1913  return;
1914 
1915  priv->sticky = TRUE;
1916 
1917  separator = FALSE;
1918 
1919  domain = remmina_protocol_widget_get_domain(REMMINA_PROTOCOL_WIDGET(cnnobj->proto));
1920  menu = gtk_menu_new();
1921  for (feature = remmina_protocol_widget_get_features(REMMINA_PROTOCOL_WIDGET(cnnobj->proto)); feature && feature->type;
1922  feature++) {
1923  if (feature->type != REMMINA_PROTOCOL_FEATURE_TYPE_PREF)
1924  continue;
1925 
1926  if (separator) {
1927  menuitem = gtk_separator_menu_item_new();
1928  gtk_widget_show(menuitem);
1929  gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1930  separator = FALSE;
1931  }
1932  enabled = remmina_protocol_widget_query_feature_by_ref(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), feature);
1933  switch (GPOINTER_TO_INT(feature->opt1)) {
1934  case REMMINA_PROTOCOL_FEATURE_PREF_RADIO:
1935  rcw_toolbar_preferences_radio(cnnobj, cnnobj->remmina_file, menu, feature,
1936  domain, enabled);
1937  separator = TRUE;
1938  break;
1939  case REMMINA_PROTOCOL_FEATURE_PREF_CHECK:
1940  rcw_toolbar_preferences_check(cnnobj, menu, feature,
1941  domain, enabled);
1942  break;
1943  }
1944  }
1945 
1946  g_free(domain);
1947 
1948  g_signal_connect(G_OBJECT(menu), "deactivate", G_CALLBACK(rcw_toolbar_preferences_popdown), cnnwin);
1949 
1950 #if GTK_CHECK_VERSION(3, 22, 0)
1951  gtk_menu_popup_at_widget(GTK_MENU(menu), GTK_WIDGET(toggle),
1952  GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, NULL);
1953 #else
1954  gtk_menu_popup(GTK_MENU(menu), NULL, NULL, remmina_public_popup_position, widget, 0, gtk_get_current_event_time());
1955 #endif
1956 }
1957 
1959 {
1960  TRACE_CALL(__func__);
1961  gchar *s;
1962 
1963  switch (menuitem->item_type) {
1966  break;
1969  break;
1971  s = g_strdup_printf("%s,%s", menuitem->protocol, menuitem->name);
1973  g_free(s);
1974  break;
1975  }
1976 }
1977 
1978 static void rcw_toolbar_menu(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
1979 {
1980  TRACE_CALL(__func__);
1982  RemminaConnectionObject *cnnobj;
1983  GtkWidget *menu;
1984  GtkWidget *menuitem = NULL;
1985 
1986  if (cnnwin->priv->toolbar_is_reconfiguring)
1987  return;
1988 
1989  if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return;
1990  priv = cnnobj->cnnwin->priv;
1991 
1992  if (!gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(toggle)))
1993  return;
1994 
1995  priv->sticky = TRUE;
1996 
1997  menu = remmina_applet_menu_new();
1998  remmina_applet_menu_set_hide_count(REMMINA_APPLET_MENU(menu), remmina_pref.applet_hide_count);
1999  remmina_applet_menu_populate(REMMINA_APPLET_MENU(menu));
2000 
2001  g_signal_connect(G_OBJECT(menu), "launch-item", G_CALLBACK(rcw_toolbar_menu_on_launch_item), NULL);
2002  //g_signal_connect(G_OBJECT(menu), "edit-item", G_CALLBACK(rcw_toolbar_menu_on_edit_item), NULL);
2003  menuitem = gtk_separator_menu_item_new();
2004  gtk_widget_show(menuitem);
2005  gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2006 #if GTK_CHECK_VERSION(3, 22, 0)
2007  gtk_menu_popup_at_widget(GTK_MENU(menu), GTK_WIDGET(toggle),
2008  GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, NULL);
2009 #else
2010  gtk_menu_popup(GTK_MENU(menu), NULL, NULL, remmina_public_popup_position, widget, 0, gtk_get_current_event_time());
2011 #endif
2012  g_signal_connect(G_OBJECT(menu), "deactivate", G_CALLBACK(rcw_toolbar_menu_popdown), cnnwin);
2013 }
2014 
2015 static void rcw_toolbar_tools(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
2016 {
2017  TRACE_CALL(__func__);
2019  RemminaConnectionObject *cnnobj;
2020  const RemminaProtocolFeature *feature;
2021  GtkWidget *menu;
2022  GtkWidget *menuitem = NULL;
2023  GtkMenu *submenu_keystrokes;
2024  const gchar *domain;
2025  gboolean enabled;
2026  gchar **keystrokes;
2027  gchar **keystroke_values;
2028  gint i;
2029 
2030  if (cnnwin->priv->toolbar_is_reconfiguring)
2031  return;
2032  if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return;
2033  priv = cnnobj->cnnwin->priv;
2034 
2035  if (!gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(toggle)))
2036  return;
2037 
2038  priv->sticky = TRUE;
2039 
2040  domain = remmina_protocol_widget_get_domain(REMMINA_PROTOCOL_WIDGET(cnnobj->proto));
2041  menu = gtk_menu_new();
2042  for (feature = remmina_protocol_widget_get_features(REMMINA_PROTOCOL_WIDGET(cnnobj->proto)); feature && feature->type;
2043  feature++) {
2044  if (feature->type != REMMINA_PROTOCOL_FEATURE_TYPE_TOOL)
2045  continue;
2046 
2047  if (feature->opt1)
2048  menuitem = gtk_menu_item_new_with_label(g_dgettext(domain, (const gchar *)feature->opt1));
2049  if (feature->opt3)
2050  rcw_set_tooltip(menuitem, "", GPOINTER_TO_UINT(feature->opt3), 0);
2051  gtk_widget_show(menuitem);
2052  gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2053 
2054  enabled = remmina_protocol_widget_query_feature_by_ref(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), feature);
2055  if (enabled) {
2056  g_object_set_data(G_OBJECT(menuitem), "feature-type", (gpointer)feature);
2057 
2058  g_signal_connect(G_OBJECT(menuitem), "activate",
2059  G_CALLBACK(rco_call_protocol_feature_activate), cnnobj);
2060  } else {
2061  gtk_widget_set_sensitive(menuitem, FALSE);
2062  }
2063  }
2064 
2065  /* If the plugin accepts keystrokes include the keystrokes menu */
2066  if (remmina_protocol_widget_plugin_receives_keystrokes(REMMINA_PROTOCOL_WIDGET(cnnobj->proto))) {
2067  /* Get the registered keystrokes list */
2068  keystrokes = g_strsplit(remmina_pref.keystrokes, STRING_DELIMITOR, -1);
2069  if (g_strv_length(keystrokes)) {
2070  /* Add a keystrokes submenu */
2071  menuitem = gtk_menu_item_new_with_label(_("Keystrokes"));
2072  submenu_keystrokes = GTK_MENU(gtk_menu_new());
2073  gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), GTK_WIDGET(submenu_keystrokes));
2074  gtk_widget_show(menuitem);
2075  gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
2076  /* Add each registered keystroke */
2077  for (i = 0; i < g_strv_length(keystrokes); i++) {
2078  keystroke_values = g_strsplit(keystrokes[i], STRING_DELIMITOR2, -1);
2079  if (g_strv_length(keystroke_values) > 1) {
2080  /* Add the keystroke if no description was available */
2081  menuitem = gtk_menu_item_new_with_label(
2082  g_strdup(keystroke_values[strlen(keystroke_values[0]) ? 0 : 1]));
2083  g_object_set_data(G_OBJECT(menuitem), "keystrokes", g_strdup(keystroke_values[1]));
2084  g_signal_connect_swapped(G_OBJECT(menuitem), "activate",
2086  REMMINA_PROTOCOL_WIDGET(cnnobj->proto));
2087  gtk_widget_show(menuitem);
2088  gtk_menu_shell_append(GTK_MENU_SHELL(submenu_keystrokes), menuitem);
2089  }
2090  g_strfreev(keystroke_values);
2091  }
2092  menuitem = gtk_menu_item_new_with_label(_("Send clipboard content as keystrokes"));
2093  static gchar k_tooltip[] =
2094  N_("CAUTION: Pasted text will be sent as a sequence of key-codes as if typed on your local keyboard.\n"
2095  "\n"
2096  " • For best results use same keyboard settings for both, client and server.\n"
2097  "\n"
2098  " • If client-keyboard is different from server-keyboard the received text can contain wrong or erroneous characters.\n"
2099  "\n"
2100  " • Unicode characters and other special characters that can't be translated to local key-codes won’t be sent to the server.\n"
2101  "\n");
2102  gtk_widget_set_tooltip_text(menuitem, k_tooltip);
2103  gtk_menu_shell_append(GTK_MENU_SHELL(submenu_keystrokes), menuitem);
2104  g_signal_connect_swapped(G_OBJECT(menuitem), "activate",
2106  REMMINA_PROTOCOL_WIDGET(cnnobj->proto));
2107  gtk_widget_show(menuitem);
2108  }
2109  g_strfreev(keystrokes);
2110  }
2111 
2112  g_signal_connect(G_OBJECT(menu), "deactivate", G_CALLBACK(rcw_toolbar_tools_popdown), cnnwin);
2113 
2114 #if GTK_CHECK_VERSION(3, 22, 0)
2115  gtk_menu_popup_at_widget(GTK_MENU(menu), GTK_WIDGET(toggle),
2116  GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, NULL);
2117 #else
2118  gtk_menu_popup(GTK_MENU(menu), NULL, NULL, remmina_public_popup_position, widget, 0, gtk_get_current_event_time());
2119 #endif
2120 }
2121 
2122 static void rcw_toolbar_duplicate(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
2123 {
2124  TRACE_CALL(__func__);
2125 
2126  RemminaConnectionObject *cnnobj;
2127 
2128  if (cnnwin->priv->toolbar_is_reconfiguring)
2129  return;
2130  if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return;
2131 
2133 }
2134 
2135 static void rcw_toolbar_screenshot(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
2136 {
2137  TRACE_CALL(__func__);
2138 
2139  GdkPixbuf *screenshot;
2140  GdkWindow *active_window;
2141  cairo_t *cr;
2142  gint width, height;
2143  GString *pngstr;
2144  gchar *pngname;
2145  GtkWidget *dialog;
2148  RemminaConnectionObject *cnnobj;
2149  cairo_surface_t *srcsurface;
2150  cairo_format_t cairo_format;
2151  cairo_surface_t *surface;
2152  int stride;
2153 
2154  if (cnnwin->priv->toolbar_is_reconfiguring)
2155  return;
2156  if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return;
2157 
2158  GDateTime *date = g_date_time_new_now_utc();
2159 
2160  // We will take a screenshot of the currently displayed RemminaProtocolWidget.
2161  gp = REMMINA_PROTOCOL_WIDGET(cnnobj->proto);
2162 
2163  gchar *denyclip = remmina_pref_get_value("deny_screenshot_clipboard");
2164 
2165  REMMINA_DEBUG("deny_screenshot_clipboard is set to %s", denyclip);
2166 
2167  GtkClipboard *c = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
2168 
2169  // Ask the plugin if it can give us a screenshot
2171  // Good, we have a screenshot from the plugin !
2172 
2173  REMMINA_DEBUG("Screenshot from plugin: w=%d h=%d bpp=%d bytespp=%d\n",
2174  rpsd.width, rpsd.height, rpsd.bitsPerPixel, rpsd.bytesPerPixel);
2175 
2176  width = rpsd.width;
2177  height = rpsd.height;
2178 
2179  if (rpsd.bitsPerPixel == 32)
2180  cairo_format = CAIRO_FORMAT_ARGB32;
2181  else if (rpsd.bitsPerPixel == 24)
2182  cairo_format = CAIRO_FORMAT_RGB24;
2183  else
2184  cairo_format = CAIRO_FORMAT_RGB16_565;
2185 
2186  stride = cairo_format_stride_for_width(cairo_format, width);
2187 
2188  srcsurface = cairo_image_surface_create_for_data(rpsd.buffer, cairo_format, width, height, stride);
2189  // Transfer the PixBuf in the main clipboard selection
2190  if (denyclip && (g_strcmp0(denyclip, "true")))
2191  gtk_clipboard_set_image(c, gdk_pixbuf_get_from_surface(
2192  srcsurface, 0, 0, width, height));
2193  surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height);
2194  cr = cairo_create(surface);
2195  cairo_set_source_surface(cr, srcsurface, 0, 0);
2196  cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
2197  cairo_paint(cr);
2198  cairo_surface_destroy(srcsurface);
2199 
2200  free(rpsd.buffer);
2201  } else {
2202  // The plugin is not releasing us a screenshot, just try to catch one via GTK
2203 
2204  /* Warn the user if image is distorted */
2205  if (cnnobj->plugin_can_scale &&
2207  dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
2208  _("Turn off scaling to avoid screenshot distortion."));
2209  g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(gtk_widget_destroy), NULL);
2210  gtk_widget_show(dialog);
2211  }
2212 
2213  // Get the screenshot.
2214  active_window = gtk_widget_get_window(GTK_WIDGET(gp));
2215  // width = gdk_window_get_width(gtk_widget_get_window(GTK_WIDGET(cnnobj->cnnwin)));
2216  width = gdk_window_get_width(active_window);
2217  // height = gdk_window_get_height(gtk_widget_get_window(GTK_WIDGET(cnnobj->cnnwin)));
2218  height = gdk_window_get_height(active_window);
2219 
2220  screenshot = gdk_pixbuf_get_from_window(active_window, 0, 0, width, height);
2221  if (screenshot == NULL)
2222  g_print("gdk_pixbuf_get_from_window failed\n");
2223 
2224  // Transfer the PixBuf in the main clipboard selection
2225  if (denyclip && (g_strcmp0(denyclip, "true")))
2226  gtk_clipboard_set_image(c, screenshot);
2227  // Prepare the destination Cairo surface.
2228  surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height);
2229  cr = cairo_create(surface);
2230 
2231  // Copy the source pixbuf to the surface and paint it.
2232  gdk_cairo_set_source_pixbuf(cr, screenshot, 0, 0);
2233  cairo_paint(cr);
2234 
2235  // Deallocate screenshot pixbuf
2236  g_object_unref(screenshot);
2237  }
2238 
2239  //home/antenore/Pictures/remmina_%p_%h_%Y %m %d-%H%M%S.png pngname
2240  //home/antenore/Pictures/remmina_st_ _2018 9 24-151958.240374.png
2241 
2242  pngstr = g_string_new(g_strdup_printf("%s/%s.png",
2243  remmina_pref.screenshot_path,
2244  remmina_pref.screenshot_name));
2245  remmina_utils_string_replace_all(pngstr, "%p",
2246  remmina_file_get_string(cnnobj->remmina_file, "name"));
2247  remmina_utils_string_replace_all(pngstr, "%h",
2248  remmina_file_get_string(cnnobj->remmina_file, "server"));
2249  remmina_utils_string_replace_all(pngstr, "%Y",
2250  g_strdup_printf("%d", g_date_time_get_year(date)));
2251  remmina_utils_string_replace_all(pngstr, "%m", g_strdup_printf("%02d",
2252  g_date_time_get_month(date)));
2253  remmina_utils_string_replace_all(pngstr, "%d",
2254  g_strdup_printf("%02d", g_date_time_get_day_of_month(date)));
2255  remmina_utils_string_replace_all(pngstr, "%H",
2256  g_strdup_printf("%02d", g_date_time_get_hour(date)));
2257  remmina_utils_string_replace_all(pngstr, "%M",
2258  g_strdup_printf("%02d", g_date_time_get_minute(date)));
2259  remmina_utils_string_replace_all(pngstr, "%S",
2260  g_strdup_printf("%02d", g_date_time_get_second(date)));
2261  g_date_time_unref(date);
2262  pngname = g_string_free(pngstr, FALSE);
2263 
2264  cairo_surface_write_to_png(surface, pngname);
2265 
2266  /* send a desktop notification */
2267  if (g_file_test(pngname, G_FILE_TEST_EXISTS))
2268  remmina_public_send_notification("remmina-screenshot-is-ready-id", _("Screenshot taken"), pngname);
2269 
2270  //Clean up and return.
2271  cairo_destroy(cr);
2272  cairo_surface_destroy(surface);
2273 }
2274 
2275 static void rcw_toolbar_minimize(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
2276 {
2277  TRACE_CALL(__func__);
2278 
2279  if (cnnwin->priv->toolbar_is_reconfiguring)
2280  return;
2281 
2282  rcw_floating_toolbar_show(cnnwin, FALSE);
2283  gtk_window_iconify(GTK_WINDOW(cnnwin));
2284 }
2285 
2286 static void rcw_toolbar_disconnect(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
2287 {
2288  TRACE_CALL(__func__);
2289  RemminaConnectionObject *cnnobj;
2290 
2291  if (cnnwin->priv->toolbar_is_reconfiguring)
2292  return;
2293  if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return;
2295 }
2296 
2297 static void rcw_toolbar_grab(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
2298 {
2299  TRACE_CALL(__func__);
2300  gboolean capture;
2301  RemminaConnectionObject *cnnobj;
2302 
2303  if (cnnwin->priv->toolbar_is_reconfiguring)
2304  return;
2305  if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return;
2306 
2307  capture = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(toggle));
2308  remmina_file_set_int(cnnobj->remmina_file, "keyboard_grab", capture);
2309  if (capture && cnnobj->connected) {
2310 #if DEBUG_KB_GRABBING
2311  printf("DEBUG_KB_GRABBING: Grabbing for button\n");
2312 #endif
2313  rcw_keyboard_grab(cnnobj->cnnwin);
2314  if (cnnobj->cnnwin->priv->pointer_entered)
2315  rcw_pointer_grab(cnnobj->cnnwin);
2316  } else {
2317  rcw_kp_ungrab(cnnobj->cnnwin);
2318  }
2319 }
2320 
2321 static GtkWidget *
2323 {
2324  TRACE_CALL(__func__);
2325  RemminaConnectionWindowPriv *priv = cnnwin->priv;
2326  RemminaConnectionObject *cnnobj;
2327  GtkWidget *toolbar;
2328  GtkToolItem *toolitem;
2329  GtkWidget *widget;
2330  GtkWidget *arrow;
2331 
2332  GdkDisplay *display;
2333  gint n_monitors;
2334 
2335  display = gdk_display_get_default();
2336  n_monitors = gdk_display_get_n_monitors(display);
2337 
2338  cnnobj = rcw_get_visible_cnnobj(cnnwin);
2339 
2340  priv->toolbar_is_reconfiguring = TRUE;
2341 
2342  toolbar = gtk_toolbar_new();
2343  gtk_widget_show(toolbar);
2344  gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_ICONS);
2345 
2346  /* Main actions */
2347 
2348  /* Menu */
2349  toolitem = gtk_toggle_tool_button_new();
2350  gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "view-more-symbolic");
2351  gtk_tool_button_set_label(GTK_TOOL_BUTTON(toolitem), _("_Menu"));
2352  gtk_tool_item_set_tooltip_text(toolitem, _("Menu"));
2353  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
2354  gtk_widget_show(GTK_WIDGET(toolitem));
2355  g_signal_connect(G_OBJECT(toolitem), "toggled", G_CALLBACK(rcw_toolbar_menu), cnnwin);
2356  priv->toolitem_menu = toolitem;
2357 
2358  /* Open Main window */
2359  toolitem = gtk_tool_button_new(NULL, "Open Remmina Main window");
2360  gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "go-home-symbolic");
2361  gtk_tool_item_set_tooltip_text(toolitem, _("Open the Remmina main window"));
2362  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
2363  gtk_widget_show(GTK_WIDGET(toolitem));
2364  g_signal_connect(G_OBJECT(toolitem), "clicked", G_CALLBACK(rcw_toolbar_open_main), cnnwin);
2365 
2366  priv->toolitem_new = toolitem;
2367 
2368  /* Duplicate session */
2369  toolitem = gtk_tool_button_new(NULL, "Duplicate connection");
2370  gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "org.remmina.Remmina-duplicate-symbolic");
2371  gtk_tool_item_set_tooltip_text(toolitem, _("Duplicate current connection"));
2372  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
2373  gtk_widget_show(GTK_WIDGET(toolitem));
2374  g_signal_connect(G_OBJECT(toolitem), "clicked", G_CALLBACK(rcw_toolbar_duplicate), cnnwin);
2375  if (!cnnobj)
2376  gtk_widget_set_sensitive(GTK_WIDGET(toolitem), FALSE);
2377 
2378  priv->toolitem_duplicate = toolitem;
2379 
2380  /* Separator */
2381  toolitem = gtk_separator_tool_item_new();
2382  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
2383  gtk_widget_show(GTK_WIDGET(toolitem));
2384 
2385  /* Auto-Fit */
2386  toolitem = gtk_tool_button_new(NULL, NULL);
2387  gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "org.remmina.Remmina-fit-window-symbolic");
2388  rcw_set_tooltip(GTK_WIDGET(toolitem), _("Resize the window to fit in remote resolution"),
2389  remmina_pref.shortcutkey_autofit, 0);
2390  g_signal_connect(G_OBJECT(toolitem), "clicked", G_CALLBACK(rcw_toolbar_autofit), cnnwin);
2391  priv->toolitem_autofit = toolitem;
2392  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
2393  gtk_widget_show(GTK_WIDGET(toolitem));
2394 
2395 
2396  /* Fullscreen toggle */
2397  toolitem = gtk_toggle_tool_button_new();
2398  gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "org.remmina.Remmina-fullscreen-symbolic");
2399  rcw_set_tooltip(GTK_WIDGET(toolitem), _("Toggle fullscreen mode"),
2400  remmina_pref.shortcutkey_fullscreen, 0);
2401  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
2402  gtk_widget_show(GTK_WIDGET(toolitem));
2403  priv->toolitem_fullscreen = toolitem;
2404  if (kioskmode) {
2405  gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(toolitem), FALSE);
2406  } else {
2407  gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(toolitem), mode != SCROLLED_WINDOW_MODE);
2408  g_signal_connect(G_OBJECT(toolitem), "clicked", G_CALLBACK(rcw_toolbar_fullscreen), cnnwin);
2409  }
2410 
2411  /* Fullscreen drop-down options */
2412  toolitem = gtk_tool_item_new();
2413  gtk_widget_show(GTK_WIDGET(toolitem));
2414  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
2415  widget = gtk_toggle_button_new();
2416  gtk_widget_show(widget);
2417  gtk_container_set_border_width(GTK_CONTAINER(widget), 0);
2418  gtk_button_set_relief(GTK_BUTTON(widget), GTK_RELIEF_NONE);
2419 #if GTK_CHECK_VERSION(3, 20, 0)
2420  gtk_widget_set_focus_on_click(GTK_WIDGET(widget), FALSE);
2421  if (remmina_pref.small_toolbutton)
2422  gtk_widget_set_name(widget, "remmina-small-button");
2423 
2424 #else
2425  gtk_button_set_focus_on_click(GTK_BUTTON(widget), FALSE);
2426 #endif
2427  gtk_container_add(GTK_CONTAINER(toolitem), widget);
2428 
2429 #if GTK_CHECK_VERSION(3, 14, 0)
2430  arrow = gtk_image_new_from_icon_name("org.remmina.Remmina-pan-down-symbolic", GTK_ICON_SIZE_SMALL_TOOLBAR);
2431 #else
2432  arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
2433 #endif
2434  gtk_widget_show(arrow);
2435  gtk_container_add(GTK_CONTAINER(widget), arrow);
2436  g_signal_connect(G_OBJECT(widget), "toggled", G_CALLBACK(rcw_toolbar_fullscreen_option), cnnwin);
2437  priv->fullscreen_option_button = widget;
2438  if (mode == SCROLLED_WINDOW_MODE)
2439  gtk_widget_set_sensitive(GTK_WIDGET(widget), FALSE);
2440 
2441  /* Multi monitor */
2442  if (n_monitors > 1) {
2443  toolitem = gtk_toggle_tool_button_new();
2444  gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "org.remmina.Remmina-multi-monitor-symbolic");
2445  rcw_set_tooltip(GTK_WIDGET(toolitem), _("Multi monitor"),
2446  remmina_pref.shortcutkey_multimon, 0);
2447  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
2448  gtk_widget_show(GTK_WIDGET(toolitem));
2449  g_signal_connect(G_OBJECT(toolitem), "toggled", G_CALLBACK(rcw_toolbar_multi_monitor_mode), cnnwin);
2450  priv->toolitem_multimon = toolitem;
2451  if (!cnnobj)
2452  gtk_widget_set_sensitive(GTK_WIDGET(toolitem), FALSE);
2453  else
2454  gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(toolitem),
2455  remmina_file_get_int(cnnobj->remmina_file, "multimon", FALSE));
2456  }
2457 
2458  /* Dynamic Resolution Update */
2459  toolitem = gtk_toggle_tool_button_new();
2460  gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "org.remmina.Remmina-dynres-symbolic");
2461  rcw_set_tooltip(GTK_WIDGET(toolitem), _("Toggle dynamic resolution update"),
2462  remmina_pref.shortcutkey_dynres, 0);
2463  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
2464  gtk_widget_show(GTK_WIDGET(toolitem));
2465  g_signal_connect(G_OBJECT(toolitem), "toggled", G_CALLBACK(rcw_toolbar_dynres), cnnwin);
2466  priv->toolitem_dynres = toolitem;
2467 
2468  /* Scaler button */
2469  toolitem = gtk_toggle_tool_button_new();
2470  gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "org.remmina.Remmina-scale-symbolic");
2471  rcw_set_tooltip(GTK_WIDGET(toolitem), _("Toggle scaled mode"), remmina_pref.shortcutkey_scale, 0);
2472  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
2473  gtk_widget_show(GTK_WIDGET(toolitem));
2474  g_signal_connect(G_OBJECT(toolitem), "toggled", G_CALLBACK(rcw_toolbar_scaled_mode), cnnwin);
2475  priv->toolitem_scale = toolitem;
2476 
2477  /* Scaler aspect ratio dropdown menu */
2478  toolitem = gtk_tool_item_new();
2479  gtk_widget_show(GTK_WIDGET(toolitem));
2480  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
2481  widget = gtk_toggle_button_new();
2482  gtk_widget_show(widget);
2483  gtk_container_set_border_width(GTK_CONTAINER(widget), 0);
2484  gtk_button_set_relief(GTK_BUTTON(widget), GTK_RELIEF_NONE);
2485 #if GTK_CHECK_VERSION(3, 20, 0)
2486  gtk_widget_set_focus_on_click(GTK_WIDGET(widget), FALSE);
2487 #else
2488  gtk_button_set_focus_on_click(GTK_BUTTON(widget), FALSE);
2489 #endif
2490  if (remmina_pref.small_toolbutton)
2491  gtk_widget_set_name(widget, "remmina-small-button");
2492  gtk_container_add(GTK_CONTAINER(toolitem), widget);
2493 #if GTK_CHECK_VERSION(3, 14, 0)
2494  arrow = gtk_image_new_from_icon_name("org.remmina.Remmina-pan-down-symbolic", GTK_ICON_SIZE_SMALL_TOOLBAR);
2495 #else
2496  arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
2497 #endif
2498  gtk_widget_show(arrow);
2499  gtk_container_add(GTK_CONTAINER(widget), arrow);
2500  g_signal_connect(G_OBJECT(widget), "toggled", G_CALLBACK(rcw_toolbar_scaler_option), cnnwin);
2501  priv->scaler_option_button = widget;
2502 
2503  /* Separator */
2504  toolitem = gtk_separator_tool_item_new();
2505  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
2506  gtk_widget_show(GTK_WIDGET(toolitem));
2507 
2508  /* Switch tabs */
2509  toolitem = gtk_toggle_tool_button_new();
2510  gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "org.remmina.Remmina-switch-page-symbolic");
2511  rcw_set_tooltip(GTK_WIDGET(toolitem), _("Switch tab pages"), remmina_pref.shortcutkey_prevtab,
2512  remmina_pref.shortcutkey_nexttab);
2513  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
2514  gtk_widget_show(GTK_WIDGET(toolitem));
2515  g_signal_connect(G_OBJECT(toolitem), "toggled", G_CALLBACK(rcw_toolbar_switch_page), cnnwin);
2516  priv->toolitem_switch_page = toolitem;
2517 
2518  /* Grab keyboard button */
2519  toolitem = gtk_toggle_tool_button_new();
2520  gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "org.remmina.Remmina-keyboard-symbolic");
2521  rcw_set_tooltip(GTK_WIDGET(toolitem), _("Grab all keyboard events"),
2522  remmina_pref.shortcutkey_grab, 0);
2523  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
2524  gtk_widget_show(GTK_WIDGET(toolitem));
2525  g_signal_connect(G_OBJECT(toolitem), "toggled", G_CALLBACK(rcw_toolbar_grab), cnnwin);
2526  priv->toolitem_grab = toolitem;
2527  if (!cnnobj)
2528  gtk_widget_set_sensitive(GTK_WIDGET(toolitem), FALSE);
2529  else {
2530  const gchar *protocol = remmina_file_get_string(cnnobj->remmina_file, "protocol");
2531  if (g_strcmp0(protocol, "SFTP") == 0 || g_strcmp0(protocol, "SSH") == 0)
2532  gtk_widget_set_sensitive(GTK_WIDGET(toolitem), FALSE);
2533  }
2534 
2535  /* Preferences */
2536  toolitem = gtk_toggle_tool_button_new();
2537  gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "org.remmina.Remmina-preferences-system-symbolic");
2538  gtk_tool_item_set_tooltip_text(toolitem, _("Preferences"));
2539  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
2540  gtk_widget_show(GTK_WIDGET(toolitem));
2541  g_signal_connect(G_OBJECT(toolitem), "toggled", G_CALLBACK(rcw_toolbar_preferences), cnnwin);
2542  priv->toolitem_preferences = toolitem;
2543 
2544  /* Tools */
2545  toolitem = gtk_toggle_tool_button_new();
2546  gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "org.remmina.Remmina-system-run-symbolic");
2547  gtk_tool_button_set_label(GTK_TOOL_BUTTON(toolitem), _("_Tools"));
2548  gtk_tool_item_set_tooltip_text(toolitem, _("Tools"));
2549  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
2550  gtk_widget_show(GTK_WIDGET(toolitem));
2551  g_signal_connect(G_OBJECT(toolitem), "toggled", G_CALLBACK(rcw_toolbar_tools), cnnwin);
2552  priv->toolitem_tools = toolitem;
2553 
2554  /* Separator */
2555  toolitem = gtk_separator_tool_item_new();
2556  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
2557  gtk_widget_show(GTK_WIDGET(toolitem));
2558 
2559  toolitem = gtk_tool_button_new(NULL, "_Screenshot");
2560  gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "org.remmina.Remmina-camera-photo-symbolic");
2561  rcw_set_tooltip(GTK_WIDGET(toolitem), _("Screenshot"), remmina_pref.shortcutkey_screenshot, 0);
2562  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
2563  gtk_widget_show(GTK_WIDGET(toolitem));
2564  g_signal_connect(G_OBJECT(toolitem), "clicked", G_CALLBACK(rcw_toolbar_screenshot), cnnwin);
2565  priv->toolitem_screenshot = toolitem;
2566 
2567  /* Separator */
2568  toolitem = gtk_separator_tool_item_new();
2569  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
2570  gtk_widget_show(GTK_WIDGET(toolitem));
2571 
2572  /* Minimize */
2573  toolitem = gtk_tool_button_new(NULL, "_Bottom");
2574  gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "org.remmina.Remmina-go-bottom-symbolic");
2575  rcw_set_tooltip(GTK_WIDGET(toolitem), _("Minimize window"), remmina_pref.shortcutkey_minimize, 0);
2576  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
2577  gtk_widget_show(GTK_WIDGET(toolitem));
2578  g_signal_connect(G_OBJECT(toolitem), "clicked", G_CALLBACK(rcw_toolbar_minimize), cnnwin);
2579  if (kioskmode)
2580  gtk_widget_set_sensitive(GTK_WIDGET(toolitem), FALSE);
2581 
2582  /* Disconnect */
2583  toolitem = gtk_tool_button_new(NULL, "_Disconnect");
2584  gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "org.remmina.Remmina-disconnect-symbolic");
2585  rcw_set_tooltip(GTK_WIDGET(toolitem), _("Disconnect"), remmina_pref.shortcutkey_disconnect, 0);
2586  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
2587  gtk_widget_show(GTK_WIDGET(toolitem));
2588  g_signal_connect(G_OBJECT(toolitem), "clicked", G_CALLBACK(rcw_toolbar_disconnect), cnnwin);
2589 
2590  priv->toolbar_is_reconfiguring = FALSE;
2591  return toolbar;
2592 }
2593 
2594 static void rcw_place_toolbar(GtkToolbar *toolbar, GtkGrid *grid, GtkWidget *sibling, int toolbar_placement)
2595 {
2596  /* Place the toolbar inside the grid and set its orientation */
2597 
2598  if (toolbar_placement == TOOLBAR_PLACEMENT_LEFT || toolbar_placement == TOOLBAR_PLACEMENT_RIGHT)
2599  gtk_orientable_set_orientation(GTK_ORIENTABLE(toolbar), GTK_ORIENTATION_VERTICAL);
2600  else
2601  gtk_orientable_set_orientation(GTK_ORIENTABLE(toolbar), GTK_ORIENTATION_HORIZONTAL);
2602 
2603 
2604  switch (toolbar_placement) {
2605  case TOOLBAR_PLACEMENT_TOP:
2606  gtk_widget_set_hexpand(GTK_WIDGET(toolbar), TRUE);
2607  gtk_widget_set_vexpand(GTK_WIDGET(toolbar), FALSE);
2608  gtk_grid_attach_next_to(GTK_GRID(grid), GTK_WIDGET(toolbar), sibling, GTK_POS_TOP, 1, 1);
2609  break;
2611  gtk_widget_set_vexpand(GTK_WIDGET(toolbar), TRUE);
2612  gtk_widget_set_hexpand(GTK_WIDGET(toolbar), FALSE);
2613  gtk_grid_attach_next_to(GTK_GRID(grid), GTK_WIDGET(toolbar), sibling, GTK_POS_RIGHT, 1, 1);
2614  break;
2616  gtk_widget_set_hexpand(GTK_WIDGET(toolbar), TRUE);
2617  gtk_widget_set_vexpand(GTK_WIDGET(toolbar), FALSE);
2618  gtk_grid_attach_next_to(GTK_GRID(grid), GTK_WIDGET(toolbar), sibling, GTK_POS_BOTTOM, 1, 1);
2619  break;
2621  gtk_widget_set_vexpand(GTK_WIDGET(toolbar), TRUE);
2622  gtk_widget_set_hexpand(GTK_WIDGET(toolbar), FALSE);
2623  gtk_grid_attach_next_to(GTK_GRID(grid), GTK_WIDGET(toolbar), sibling, GTK_POS_LEFT, 1, 1);
2624  break;
2625  }
2626 }
2627 
2629 {
2630  TRACE_CALL(__func__);
2631  RemminaConnectionWindowPriv *priv = cnnobj->cnnwin->priv;
2632  GtkToolItem *toolitem;
2633  gboolean bval, dynres_avail, scale_avail;
2634  gboolean test_floating_toolbar;
2635  RemminaScaleMode scalemode;
2636 
2637  priv->toolbar_is_reconfiguring = TRUE;
2638 
2640 
2641  toolitem = priv->toolitem_switch_page;
2642  if (kioskmode)
2643  bval = FALSE;
2644  else
2645  bval = (gtk_notebook_get_n_pages(GTK_NOTEBOOK(priv->notebook)) > 1);
2646  gtk_widget_set_sensitive(GTK_WIDGET(toolitem), bval);
2647 
2648  if (cnnobj->remmina_file->filename)
2649  gtk_widget_set_sensitive(GTK_WIDGET(priv->toolitem_duplicate), TRUE);
2650  else
2651  gtk_widget_set_sensitive(GTK_WIDGET(priv->toolitem_duplicate), FALSE);
2652 
2653  scalemode = get_current_allowed_scale_mode(cnnobj, &dynres_avail, &scale_avail);
2654  gtk_widget_set_sensitive(GTK_WIDGET(priv->toolitem_dynres), dynres_avail && cnnobj->connected);
2655  gtk_widget_set_sensitive(GTK_WIDGET(priv->toolitem_scale), scale_avail && cnnobj->connected);
2656 
2657  switch (scalemode) {
2659  gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_dynres), FALSE);
2660  gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_scale), FALSE);
2661  gtk_widget_set_sensitive(GTK_WIDGET(priv->scaler_option_button), FALSE);
2662  break;
2664  gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_dynres), FALSE);
2665  gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_scale), TRUE);
2666  gtk_widget_set_sensitive(GTK_WIDGET(priv->scaler_option_button), TRUE && cnnobj->connected);
2667  break;
2669  gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_dynres), TRUE);
2670  gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_scale), FALSE);
2671  gtk_widget_set_sensitive(GTK_WIDGET(priv->scaler_option_button), FALSE);
2672  break;
2673  }
2674 
2675  /* REMMINA_PROTOCOL_FEATURE_TYPE_MULTIMON */
2676  toolitem = priv->toolitem_multimon;
2677  if (toolitem) {
2678  gint hasmultimon = remmina_protocol_widget_query_feature_by_type(REMMINA_PROTOCOL_WIDGET(cnnobj->proto),
2680 
2681  gtk_widget_set_sensitive(GTK_WIDGET(toolitem), cnnobj->connected);
2682  gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(toolitem),
2683  remmina_file_get_int(cnnobj->remmina_file, "multimon", FALSE));
2684  gtk_widget_set_sensitive(GTK_WIDGET(toolitem), hasmultimon);
2685  }
2686 
2687  toolitem = priv->toolitem_grab;
2688  gtk_widget_set_sensitive(GTK_WIDGET(toolitem), cnnobj->connected);
2689  gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(toolitem),
2690  remmina_file_get_int(cnnobj->remmina_file, "keyboard_grab", FALSE));
2691  const gchar *protocol = remmina_file_get_string(cnnobj->remmina_file, "protocol");
2692  if (g_strcmp0(protocol, "SFTP") == 0 || g_strcmp0(protocol, "SSH") == 0) {
2693  gtk_widget_set_sensitive(GTK_WIDGET(toolitem), FALSE);
2694  gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(toolitem), FALSE);
2695  remmina_file_set_int(cnnobj->remmina_file, "keyboard_grab", FALSE);
2696  }
2697 
2698  toolitem = priv->toolitem_preferences;
2699  gtk_widget_set_sensitive(GTK_WIDGET(toolitem), cnnobj->connected);
2700  bval = remmina_protocol_widget_query_feature_by_type(REMMINA_PROTOCOL_WIDGET(cnnobj->proto),
2702  gtk_widget_set_sensitive(GTK_WIDGET(toolitem), bval && cnnobj->connected);
2703 
2704  toolitem = priv->toolitem_tools;
2705  bval = remmina_protocol_widget_query_feature_by_type(REMMINA_PROTOCOL_WIDGET(cnnobj->proto),
2707  gtk_widget_set_sensitive(GTK_WIDGET(toolitem), bval && cnnobj->connected);
2708 
2709  gtk_widget_set_sensitive(GTK_WIDGET(priv->toolitem_screenshot), cnnobj->connected);
2710 
2711  gtk_window_set_title(GTK_WINDOW(cnnobj->cnnwin), remmina_file_get_string(cnnobj->remmina_file, "name"));
2712 
2713  test_floating_toolbar = (priv->floating_toolbar_widget != NULL);
2714 
2715  if (test_floating_toolbar) {
2716  const gchar *str = remmina_file_get_string(cnnobj->remmina_file, "name");
2717  const gchar *format;
2718  GdkRGBA rgba;
2719  gchar *bg;
2720 
2721  bg = g_strdup(remmina_pref.grab_color);
2722  if (!gdk_rgba_parse(&rgba, bg)) {
2723  REMMINA_DEBUG("%s cannot be parsed as a color", bg);
2724  bg = g_strdup("#00FF00");
2725  } else {
2726  REMMINA_DEBUG("Using %s as background color", bg);
2727  }
2728 
2729  if (remmina_file_get_int(cnnobj->remmina_file, "keyboard_grab", FALSE)) {
2730  if (remmina_pref_get_boolean("grab_color_switch"))
2731  format = g_strconcat("<span bgcolor=\"", bg, "\" size=\"large\"><b>(G:ON) - \%s</b></span>", NULL);
2732  else
2733  format = "<big><b>(G:ON) - \%s</b></big>";
2734  } else {
2735  format = "<big><b>(G:OFF) - \%s</b></big>";
2736  }
2737  gchar *markup;
2738 
2739  markup = g_markup_printf_escaped(format, str);
2740  gtk_label_set_markup(GTK_LABEL(priv->floating_toolbar_label), markup);
2741  g_free(markup);
2742  g_free(bg);
2743  }
2744 
2745  priv->toolbar_is_reconfiguring = FALSE;
2746 }
2747 
2749 {
2750  TRACE_CALL(__func__);
2751  RemminaConnectionWindowPriv *priv = cnnwin->priv;
2752 
2753  if (priv->view_mode == SCROLLED_WINDOW_MODE) {
2754  if (remmina_pref.hide_connection_toolbar)
2755  gtk_widget_hide(priv->toolbar);
2756  else
2757  gtk_widget_show(priv->toolbar);
2758  }
2759 }
2760 
2761 #if DEBUG_KB_GRABBING
2762 static void print_crossing_event(GdkEventCrossing *event) {
2763  printf("DEBUG_KB_GRABBING: --- Crossing event detail: ");
2764  switch (event->detail) {
2765  case GDK_NOTIFY_ANCESTOR: printf("GDK_NOTIFY_ANCESTOR"); break;
2766  case GDK_NOTIFY_VIRTUAL: printf("GDK_NOTIFY_VIRTUAL"); break;
2767  case GDK_NOTIFY_NONLINEAR: printf("GDK_NOTIFY_NONLINEAR"); break;
2768  case GDK_NOTIFY_NONLINEAR_VIRTUAL: printf("GDK_NOTIFY_NONLINEAR_VIRTUAL"); break;
2769  case GDK_NOTIFY_UNKNOWN: printf("GDK_NOTIFY_UNKNOWN"); break;
2770  case GDK_NOTIFY_INFERIOR: printf("GDK_NOTIFY_INFERIOR"); break;
2771  default: printf("unknown");
2772  }
2773  printf("\n");
2774  printf("DEBUG_KB_GRABBING: --- Crossing event mode=");
2775  switch (event->mode) {
2776  case GDK_CROSSING_NORMAL: printf("GDK_CROSSING_NORMAL"); break;
2777  case GDK_CROSSING_GRAB: printf("GDK_CROSSING_GRAB"); break;
2778  case GDK_CROSSING_UNGRAB: printf("GDK_CROSSING_UNGRAB"); break;
2779  case GDK_CROSSING_GTK_GRAB: printf("GDK_CROSSING_GTK_GRAB"); break;
2780  case GDK_CROSSING_GTK_UNGRAB: printf("GDK_CROSSING_GTK_UNGRAB"); break;
2781  case GDK_CROSSING_STATE_CHANGED: printf("GDK_CROSSING_STATE_CHANGED"); break;
2782  case GDK_CROSSING_TOUCH_BEGIN: printf("GDK_CROSSING_TOUCH_BEGIN"); break;
2783  case GDK_CROSSING_TOUCH_END: printf("GDK_CROSSING_TOUCH_END"); break;
2784  case GDK_CROSSING_DEVICE_SWITCH: printf("GDK_CROSSING_DEVICE_SWITCH"); break;
2785  default: printf("unknown");
2786  }
2787  printf("\n");
2788 }
2789 #endif
2790 
2791 static gboolean rcw_floating_toolbar_on_enter(GtkWidget *widget, GdkEventCrossing *event,
2792  RemminaConnectionWindow *cnnwin)
2793 {
2794  TRACE_CALL(__func__);
2795  rcw_floating_toolbar_show(cnnwin, TRUE);
2796  return TRUE;
2797 }
2798 
2799 static gboolean rcw_floating_toolbar_on_leave(GtkWidget *widget, GdkEventCrossing *event,
2800  RemminaConnectionWindow *cnnwin)
2801 {
2802  TRACE_CALL(__func__);
2803  if (event->detail != GDK_NOTIFY_INFERIOR)
2804  rcw_floating_toolbar_show(cnnwin, FALSE);
2805  return TRUE;
2806 }
2807 
2808 
2809 static gboolean rcw_on_enter_notify_event(GtkWidget *widget, GdkEventCrossing *event,
2810  gpointer user_data)
2811 {
2812  TRACE_CALL(__func__);
2813 #if DEBUG_KB_GRABBING
2814  printf("DEBUG_KB_GRABBING: enter-notify-event on rcw received\n");
2815  print_crossing_event(event);
2816 #endif
2817  return FALSE;
2818 }
2819 
2820 
2821 
2822 static gboolean rcw_on_leave_notify_event(GtkWidget *widget, GdkEventCrossing *event,
2823  gpointer user_data)
2824 {
2825  TRACE_CALL(__func__);
2827 
2828 #if DEBUG_KB_GRABBING
2829  printf("DEBUG_KB_GRABBING: leave-notify-event on rcw received\n");
2830  print_crossing_event(event);
2831 #endif
2832 
2833  if (event->mode != GDK_CROSSING_NORMAL && event->mode != GDK_CROSSING_UNGRAB) {
2834 #if DEBUG_KB_GRABBING
2835  printf("DEBUG_KB_GRABBING: ignored because mode is not GDK_CROSSING_NORMAL GDK_CROSSING_UNGRAB\n");
2836 #endif
2837  return FALSE;
2838  }
2839 
2840  if (cnnwin->priv->delayed_grab_eventsourceid) {
2841  g_source_remove(cnnwin->priv->delayed_grab_eventsourceid);
2842  cnnwin->priv->delayed_grab_eventsourceid = 0;
2843  }
2844 
2845  /* Workaround for https://gitlab.gnome.org/GNOME/mutter/-/issues/2450#note_1586570 */
2846  if (event->mode != GDK_CROSSING_UNGRAB) {
2847  rcw_kp_ungrab(cnnwin);
2848  rcw_pointer_ungrab(cnnwin);
2849  } else {
2850 #if DEBUG_KB_GRABBING
2851  printf("DEBUG_KB_GRABBING: not ungrabbing, this event seems to be an unwanted event from GTK\n");
2852 #endif
2853  }
2854 
2855  return FALSE;
2856 }
2857 
2858 
2859 static gboolean rco_leave_protocol_widget(GtkWidget *widget, GdkEventCrossing *event,
2860  RemminaConnectionObject *cnnobj)
2861 {
2862  TRACE_CALL(__func__);
2863 
2864 #if DEBUG_KB_GRABBING
2865  printf("DEBUG_KB_GRABBING: received leave event on RCO.\n");
2866  print_crossing_event(event);
2867 #endif
2868 
2869  if (cnnobj->cnnwin->priv->delayed_grab_eventsourceid) {
2870  g_source_remove(cnnobj->cnnwin->priv->delayed_grab_eventsourceid);
2871  cnnobj->cnnwin->priv->delayed_grab_eventsourceid = 0;
2872  }
2873 
2874  cnnobj->cnnwin->priv->pointer_entered = FALSE;
2875 
2876  /* Ungrab only if the leave is due to normal mouse motion and not to an inferior */
2877  if (event->mode == GDK_CROSSING_NORMAL && event->detail != GDK_NOTIFY_INFERIOR)
2878  rcw_kp_ungrab(cnnobj->cnnwin);
2879 
2880  return FALSE;
2881 }
2882 
2883 
2884 gboolean rco_enter_protocol_widget(GtkWidget *widget, GdkEventCrossing *event,
2885  RemminaConnectionObject *cnnobj)
2886 {
2887  TRACE_CALL(__func__);
2888  gboolean active;
2889 
2890 #if DEBUG_KB_GRABBING
2891  printf("DEBUG_KB_GRABBING: %s: enter on protocol widget event received\n", __func__);
2892  print_crossing_event(event);
2893 #endif
2894 
2895  RemminaConnectionWindowPriv *priv = cnnobj->cnnwin->priv;
2896  if (!priv->sticky && event->mode == GDK_CROSSING_NORMAL)
2897  rcw_floating_toolbar_show(cnnobj->cnnwin, FALSE);
2898 
2899  priv->pointer_entered = TRUE;
2900 
2901  if (event->mode == GDK_CROSSING_UNGRAB) {
2902  // Someone steal our grab, take note and do not attempt to regrab
2903  cnnobj->cnnwin->priv->kbcaptured = FALSE;
2904  cnnobj->cnnwin->priv->pointer_captured = FALSE;
2905  return FALSE;
2906  }
2907 
2908  /* Check if we need grabbing */
2909  active = gtk_window_is_active(GTK_WINDOW(cnnobj->cnnwin));
2910  if (remmina_file_get_int(cnnobj->remmina_file, "keyboard_grab", FALSE) && active) {
2911  rcw_keyboard_grab(cnnobj->cnnwin);
2912  rcw_pointer_grab(cnnobj->cnnwin);
2913  }
2914 
2915  return FALSE;
2916 }
2917 
2919 {
2920  TRACE_CALL(__func__);
2921 
2922 #if DEBUG_KB_GRABBING
2923  printf("DEBUG_KB_GRABBING: %s\n", __func__);
2924 #endif
2925  if (cnnwin->priv->pointer_entered) {
2926 #if DEBUG_KB_GRABBING
2927  printf("DEBUG_KB_GRABBING: delayed requesting kb and pointer grab, because of pointer inside\n");
2928 #endif
2929  rcw_keyboard_grab(cnnwin);
2930  rcw_pointer_grab(cnnwin);
2931  }
2932 #if DEBUG_KB_GRABBING
2933  else {
2934  printf("DEBUG_KB_GRABBING: %s not grabbing because pointer_entered is false\n", __func__);
2935  }
2936 #endif
2937  cnnwin->priv->delayed_grab_eventsourceid = 0;
2938  return G_SOURCE_REMOVE;
2939 }
2940 
2942 {
2943  /* This function is the default signal handler for focus-in-event,
2944  * but can also be called after a window focus state change event
2945  * from rcw_state_event(). So expect to be called twice
2946  * when cnnwin gains the focus */
2947 
2948  TRACE_CALL(__func__);
2949  RemminaConnectionObject *cnnobj;
2950 
2951  if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return;
2952 
2953  if (cnnobj && cnnobj->connected && remmina_file_get_int(cnnobj->remmina_file, "keyboard_grab", FALSE)) {
2954 #if DEBUG_KB_GRABBING
2955  printf("DEBUG_KB_GRABBING: Received focus in on rcw, grabbing enabled: requesting kb grab, delayed\n");
2956 #endif
2957  if (cnnwin->priv->delayed_grab_eventsourceid == 0)
2958  cnnwin->priv->delayed_grab_eventsourceid = g_timeout_add(300, (GSourceFunc)focus_in_delayed_grab, cnnwin);
2959  }
2960 #if DEBUG_KB_GRABBING
2961  else {
2962  printf("DEBUG_KB_GRABBING: Received focus in on rcw, but a condition will prevent to grab\n");
2963  }
2964 #endif
2965 }
2966 
2968 {
2969  /* This function is the default signal handler for focus-out-event,
2970  * but can also be called after a window focus state change event
2971  * from rcw_state_event(). So expect to be called twice
2972  * when cnnwin loses the focus */
2973 
2974  TRACE_CALL(__func__);
2975  RemminaConnectionObject *cnnobj;
2976 
2977  rcw_kp_ungrab(cnnwin);
2978 
2979  cnnwin->priv->hostkey_activated = FALSE;
2980  if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return;
2981 
2982  if (REMMINA_IS_SCROLLED_VIEWPORT(cnnobj->scrolled_container))
2983  remmina_scrolled_viewport_remove_motion(REMMINA_SCROLLED_VIEWPORT(cnnobj->scrolled_container));
2984 
2985  if (cnnobj->proto && cnnobj->scrolled_container)
2986  remmina_protocol_widget_call_feature_by_type(REMMINA_PROTOCOL_WIDGET(cnnobj->proto),
2988 }
2989 
2990 static gboolean
2992 {
2993  TRACE_CALL(__func__);
2994  RemminaConnectionWindowPriv *priv = cnnwin->priv;
2995 
2996  priv->hidetb_eventsource = 0;
2997  rcw_floating_toolbar_show(cnnwin, FALSE);
2998  return G_SOURCE_REMOVE;
2999 }
3000 
3001 static gboolean rcw_floating_toolbar_on_scroll(GtkWidget *widget, GdkEventScroll *event,
3002  RemminaConnectionWindow *cnnwin)
3003 {
3004  TRACE_CALL(__func__);
3005  RemminaConnectionObject *cnnobj;
3006 
3007  int opacity;
3008 
3009  cnnobj = rcw_get_visible_cnnobj(cnnwin);
3010  if (!cnnobj)
3011  return TRUE;
3012 
3013  opacity = remmina_file_get_int(cnnobj->remmina_file, "toolbar_opacity", 0);
3014  switch (event->direction) {
3015  case GDK_SCROLL_UP:
3016  if (opacity > 0) {
3017  remmina_file_set_int(cnnobj->remmina_file, "toolbar_opacity", opacity - 1);
3019  return TRUE;
3020  }
3021  break;
3022  case GDK_SCROLL_DOWN:
3023  if (opacity < TOOLBAR_OPACITY_LEVEL) {
3024  remmina_file_set_int(cnnobj->remmina_file, "toolbar_opacity", opacity + 1);
3026  return TRUE;
3027  }
3028  break;
3029 #if GTK_CHECK_VERSION(3, 4, 0)
3030  case GDK_SCROLL_SMOOTH:
3031  if (event->delta_y < 0 && opacity > 0) {
3032  remmina_file_set_int(cnnobj->remmina_file, "toolbar_opacity", opacity - 1);
3034  return TRUE;
3035  }
3036  if (event->delta_y > 0 && opacity < TOOLBAR_OPACITY_LEVEL) {
3037  remmina_file_set_int(cnnobj->remmina_file, "toolbar_opacity", opacity + 1);
3039  return TRUE;
3040  }
3041  break;
3042 #endif
3043  default:
3044  break;
3045  }
3046  return TRUE;
3047 }
3048 
3049 static gboolean rcw_after_configure_scrolled(gpointer user_data)
3050 {
3051  TRACE_CALL(__func__);
3052  gint width, height;
3053  GdkWindowState s;
3054  gint ipg, npages;
3055  RemminaConnectionWindow *cnnwin;
3056 
3057  cnnwin = (RemminaConnectionWindow *)user_data;
3058 
3059  if (!cnnwin || !cnnwin->priv)
3060  return FALSE;
3061 
3062  s = gdk_window_get_state(gtk_widget_get_window(GTK_WIDGET(cnnwin)));
3063 
3064 
3065  /* Changed window_maximize, window_width and window_height for all
3066  * connections inside the notebook */
3067  npages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(cnnwin->priv->notebook));
3068  for (ipg = 0; ipg < npages; ipg++) {
3069  RemminaConnectionObject *cnnobj;
3070  cnnobj = g_object_get_data(
3071  G_OBJECT(gtk_notebook_get_nth_page(GTK_NOTEBOOK(cnnwin->priv->notebook), ipg)),
3072  "cnnobj");
3073  if (s & GDK_WINDOW_STATE_MAXIMIZED) {
3074  remmina_file_set_int(cnnobj->remmina_file, "window_maximize", TRUE);
3075  } else {
3076  gtk_window_get_size(GTK_WINDOW(cnnobj->cnnwin), &width, &height);
3077  remmina_file_set_int(cnnobj->remmina_file, "window_width", width);
3078  remmina_file_set_int(cnnobj->remmina_file, "window_height", height);
3079  remmina_file_set_int(cnnobj->remmina_file, "window_maximize", FALSE);
3080  }
3081  }
3082  cnnwin->priv->acs_eventsourceid = 0;
3083  return FALSE;
3084 }
3085 
3086 static gboolean rcw_on_configure(GtkWidget *widget, GdkEventConfigure *event,
3087  gpointer data)
3088 {
3089  TRACE_CALL(__func__);
3090  RemminaConnectionWindow *cnnwin;
3091  RemminaConnectionObject *cnnobj;
3092 
3093  if (!REMMINA_IS_CONNECTION_WINDOW(widget))
3094  return FALSE;
3095 
3096  cnnwin = (RemminaConnectionWindow *)widget;
3097 
3098  if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return FALSE;
3099 
3100  if (cnnwin->priv->acs_eventsourceid) {
3101  g_source_remove(cnnwin->priv->acs_eventsourceid);
3102  cnnwin->priv->acs_eventsourceid = 0;
3103  }
3104 
3105  if (gtk_widget_get_window(GTK_WIDGET(cnnwin))
3106  && cnnwin->priv->view_mode == SCROLLED_WINDOW_MODE)
3107  /* Under GNOME Shell we receive this configure_event BEFORE a window
3108  * is really unmaximized, so we must read its new state and dimensions
3109  * later, not now */
3110  cnnwin->priv->acs_eventsourceid = g_timeout_add(500, rcw_after_configure_scrolled, cnnwin);
3111 
3112  if (cnnwin->priv->view_mode != SCROLLED_WINDOW_MODE)
3113  /* Notify window of change so that scroll border can be hidden or shown if needed */
3114  rco_check_resize(cnnobj);
3115  return FALSE;
3116 }
3117 
3119 {
3120  TRACE_CALL(__func__);
3121  if (cnnwin->priv->pin_down)
3122  gtk_button_set_image(GTK_BUTTON(cnnwin->priv->pin_button),
3123  gtk_image_new_from_icon_name("org.remmina.Remmina-pin-down-symbolic", GTK_ICON_SIZE_MENU));
3124  else
3125  gtk_button_set_image(GTK_BUTTON(cnnwin->priv->pin_button),
3126  gtk_image_new_from_icon_name("org.remmina.Remmina-pin-up-symbolic", GTK_ICON_SIZE_MENU));
3127 }
3128 
3129 static void rcw_toolbar_pin(GtkWidget *widget, RemminaConnectionWindow *cnnwin)
3130 {
3131  TRACE_CALL(__func__);
3132  remmina_pref.toolbar_pin_down = cnnwin->priv->pin_down = !cnnwin->priv->pin_down;
3134  rcw_update_pin(cnnwin);
3135 }
3136 
3138 {
3139  TRACE_CALL(__func__);
3140 
3141  RemminaConnectionWindowPriv *priv = cnnwin->priv;
3142  GtkWidget *ftb_widget;
3143  GtkWidget *vbox;
3144  GtkWidget *hbox;
3145  GtkWidget *label;
3146  GtkWidget *pinbutton;
3147  GtkWidget *tb;
3148 
3149 
3150  /* A widget to be used for GtkOverlay for GTK >= 3.10 */
3151  ftb_widget = gtk_event_box_new();
3152 
3153  vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
3154  gtk_widget_show(vbox);
3155 
3156  gtk_container_add(GTK_CONTAINER(ftb_widget), vbox);
3157 
3158  tb = rcw_create_toolbar(cnnwin, mode);
3159  hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
3160  gtk_widget_show(hbox);
3161 
3162 
3163  /* The pin button */
3164  pinbutton = gtk_button_new();
3165  gtk_widget_show(pinbutton);
3166  gtk_box_pack_start(GTK_BOX(hbox), pinbutton, FALSE, FALSE, 0);
3167  gtk_button_set_relief(GTK_BUTTON(pinbutton), GTK_RELIEF_NONE);
3168 #if GTK_CHECK_VERSION(3, 20, 0)
3169  gtk_widget_set_focus_on_click(GTK_WIDGET(pinbutton), FALSE);
3170 #else
3171  gtk_button_set_focus_on_click(GTK_BUTTON(pinbutton), FALSE);
3172 #endif
3173  gtk_widget_set_name(pinbutton, "remmina-pin-button");
3174  g_signal_connect(G_OBJECT(pinbutton), "clicked", G_CALLBACK(rcw_toolbar_pin), cnnwin);
3175  priv->pin_button = pinbutton;
3176  priv->pin_down = remmina_pref.toolbar_pin_down;
3177  rcw_update_pin(cnnwin);
3178 
3179 
3180  label = gtk_label_new("");
3181  gtk_label_set_max_width_chars(GTK_LABEL(label), 50);
3182  gtk_widget_show(label);
3183 
3184  gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
3185 
3186  priv->floating_toolbar_label = label;
3187 
3189  gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
3190  gtk_box_pack_start(GTK_BOX(vbox), tb, FALSE, FALSE, 0);
3191  } else {
3192  gtk_box_pack_start(GTK_BOX(vbox), tb, FALSE, FALSE, 0);
3193  gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
3194  }
3195 
3196  priv->floating_toolbar_widget = ftb_widget;
3197  gtk_widget_show(ftb_widget);
3198 }
3199 
3200 static void rcw_toolbar_place_signal(RemminaConnectionWindow *cnnwin, gpointer data)
3201 {
3202  TRACE_CALL(__func__);
3204 
3205  priv = cnnwin->priv;
3206  /* Detach old toolbar widget and reattach in new position in the grid */
3207  if (priv->toolbar && priv->grid) {
3208  g_object_ref(priv->toolbar);
3209  gtk_container_remove(GTK_CONTAINER(priv->grid), priv->toolbar);
3210  rcw_place_toolbar(GTK_TOOLBAR(priv->toolbar), GTK_GRID(priv->grid), GTK_WIDGET(priv->notebook), remmina_pref.toolbar_placement);
3211  g_object_unref(priv->toolbar);
3212  }
3213 }
3214 
3215 
3216 static void rcw_init(RemminaConnectionWindow *cnnwin)
3217 {
3218  TRACE_CALL(__func__);
3220 
3221  priv = g_new0(RemminaConnectionWindowPriv, 1);
3222  cnnwin->priv = priv;
3223 
3224  priv->view_mode = SCROLLED_WINDOW_MODE;
3225  if (kioskmode && kioskmode == TRUE)
3226  priv->view_mode = VIEWPORT_FULLSCREEN_MODE;
3227 
3228  priv->floating_toolbar_opacity = 1.0;
3229  priv->kbcaptured = FALSE;
3230  priv->pointer_captured = FALSE;
3231  priv->pointer_entered = FALSE;
3232  priv->fss_view_mode = VIEWPORT_FULLSCREEN_MODE;
3233  priv->ss_width = 640;
3234  priv->ss_height = 480;
3235  priv->ss_maximized = FALSE;
3236 
3237  remmina_widget_pool_register(GTK_WIDGET(cnnwin));
3238 }
3239 
3240 static gboolean rcw_focus_in_event(GtkWidget *widget, GdkEventWindowState *event, gpointer user_data)
3241 {
3242  TRACE_CALL(__func__);
3243 #if DEBUG_KB_GRABBING
3244  printf("DEBUG_KB_GRABBING: RCW focus-in-event received\n");
3245 #endif
3247  return FALSE;
3248 }
3249 
3250 static gboolean rcw_focus_out_event(GtkWidget *widget, GdkEventWindowState *event, gpointer user_data)
3251 {
3252  TRACE_CALL(__func__);
3253 #if DEBUG_KB_GRABBING
3254  printf("DEBUG_KB_GRABBING: RCW focus-out-event received\n");
3255 #endif
3257  return FALSE;
3258 }
3259 
3260 
3261 static gboolean rcw_state_event(GtkWidget *widget, GdkEventWindowState *event, gpointer user_data)
3262 {
3263  TRACE_CALL(__func__);
3264 
3265  if (!REMMINA_IS_CONNECTION_WINDOW(widget))
3266  return FALSE;
3267 
3268 #if DEBUG_KB_GRABBING
3269  printf("DEBUG_KB_GRABBING: window-state-event received\n");
3270 #endif
3271 
3272  if (event->changed_mask & GDK_WINDOW_STATE_FOCUSED) {
3273  if (event->new_window_state & GDK_WINDOW_STATE_FOCUSED)
3275  else
3277  }
3278 
3279  return FALSE;
3280 }
3281 
3282 static gboolean rcw_map_event(GtkWidget *widget, GdkEvent *event, gpointer data)
3283 {
3284  TRACE_CALL(__func__);
3285 
3286 
3287 
3289  RemminaConnectionObject *cnnobj;
3291 
3292  if (cnnwin->priv->toolbar_is_reconfiguring) return FALSE;
3293  if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return FALSE;
3294 
3295  gp = REMMINA_PROTOCOL_WIDGET(cnnobj->proto);
3296  REMMINA_DEBUG("Mapping: %s", gtk_widget_get_name(widget));
3298  REMMINA_DEBUG("Called plugin mapping function");
3299  return FALSE;
3300 }
3301 
3302 static gboolean rcw_unmap_event(GtkWidget *widget, GdkEvent *event, gpointer data)
3303 {
3304  TRACE_CALL(__func__);
3305 
3307  RemminaConnectionObject *cnnobj;
3309 
3310  if (cnnwin->priv->toolbar_is_reconfiguring) return FALSE;
3311  if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return FALSE;
3312 
3313  gp = REMMINA_PROTOCOL_WIDGET(cnnobj->proto);
3314  REMMINA_DEBUG("Unmapping: %s", gtk_widget_get_name(widget));
3316  REMMINA_DEBUG("Called plugin mapping function");
3317  return FALSE;
3318 }
3319 
3320 static gboolean rcw_map_event_fullscreen(GtkWidget *widget, GdkEvent *event, gpointer data)
3321 {
3322  TRACE_CALL(__func__);
3323  RemminaConnectionObject *cnnobj;
3324  gint target_monitor;
3325 
3326  REMMINA_DEBUG("Mapping: %s", gtk_widget_get_name(widget));
3327 
3328  if (!REMMINA_IS_CONNECTION_WINDOW(widget)) {
3329  REMMINA_DEBUG("Remmina Connection Window undefined, cannot go fullscreen");
3330  return FALSE;
3331  }
3332 
3333  //RemminaConnectionWindow *cnnwin = (RemminaConnectionWindow *)data;
3334  cnnobj = rcw_get_visible_cnnobj((RemminaConnectionWindow *)widget);
3335  //cnnobj = g_object_get_data(G_OBJECT(widget), "cnnobj");
3336  if (!cnnobj) {
3337  REMMINA_DEBUG("Remmina Connection Object undefined, cannot go fullscreen");
3338  return FALSE;
3339  }
3340 
3341  RemminaProtocolWidget *gp = REMMINA_PROTOCOL_WIDGET(cnnobj->proto);
3342 
3343  if (!gp)
3344  REMMINA_DEBUG("Remmina Protocol Widget undefined, cannot go fullscreen");
3345 
3346  if (remmina_protocol_widget_get_multimon(gp) >= 1) {
3347  REMMINA_DEBUG("Fullscreen on all monitor");
3348  gdk_window_set_fullscreen_mode(gtk_widget_get_window(widget), GDK_FULLSCREEN_ON_ALL_MONITORS);
3349  gdk_window_fullscreen(gtk_widget_get_window(widget));
3350  return TRUE;
3351  } else {
3352  REMMINA_DEBUG("Fullscreen on one monitor");
3353  }
3354 
3355  target_monitor = GPOINTER_TO_INT(data);
3356 
3357 #if GTK_CHECK_VERSION(3, 18, 0)
3358  if (remmina_pref.fullscreen_on_auto) {
3359  if (target_monitor == FULL_SCREEN_TARGET_MONITOR_UNDEFINED)
3360  gtk_window_fullscreen(GTK_WINDOW(widget));
3361  else
3362  gtk_window_fullscreen_on_monitor(GTK_WINDOW(widget), gtk_window_get_screen(GTK_WINDOW(widget)),
3363  target_monitor);
3364  } else {
3365  REMMINA_DEBUG("Fullscreen managed by WM or by the user, as per settings");
3366  gtk_window_fullscreen(GTK_WINDOW(widget));
3367  }
3368 #else
3369  REMMINA_DEBUG("Cannot fullscreen on a specific monitor, feature available from GTK 3.18");
3370  gtk_window_fullscreen(GTK_WINDOW(widget));
3371 #endif
3372 
3374  REMMINA_DEBUG("Called plugin mapping function");
3375 
3376  return FALSE;
3377 }
3378 
3379 static RemminaConnectionWindow *
3380 rcw_new(gboolean fullscreen, int full_screen_target_monitor)
3381 {
3382  TRACE_CALL(__func__);
3383  RemminaConnectionWindow *cnnwin;
3384 
3385  cnnwin = RCW(g_object_new(REMMINA_TYPE_CONNECTION_WINDOW, NULL));
3386  cnnwin->priv->on_delete_confirm_mode = RCW_ONDELETE_CONFIRM_IF_2_OR_MORE;
3387 
3388  if (fullscreen)
3389  /* Put the window in fullscreen after it is mapped to have it appear on the same monitor */
3390  g_signal_connect(G_OBJECT(cnnwin), "map-event", G_CALLBACK(rcw_map_event_fullscreen), GINT_TO_POINTER(full_screen_target_monitor));
3391  else
3392  g_signal_connect(G_OBJECT(cnnwin), "map-event", G_CALLBACK(rcw_map_event), NULL);
3393  g_signal_connect(G_OBJECT(cnnwin), "unmap-event", G_CALLBACK(rcw_unmap_event), NULL);
3394 
3395  gtk_container_set_border_width(GTK_CONTAINER(cnnwin), 0);
3396  g_signal_connect(G_OBJECT(cnnwin), "toolbar-place", G_CALLBACK(rcw_toolbar_place_signal), NULL);
3397 
3398  g_signal_connect(G_OBJECT(cnnwin), "delete-event", G_CALLBACK(rcw_delete_event), NULL);
3399  g_signal_connect(G_OBJECT(cnnwin), "destroy", G_CALLBACK(rcw_destroy), NULL);
3400 
3401  /* Under Xorg focus-in-event and focus-out-event don’t work when keyboard is grabbed
3402  * via gdk_device_grab. So we listen for window-state-event to detect focus in and focus out.
3403  * But we must also listen focus-in-event and focus-out-event because some
3404  * window managers missing _NET_WM_STATE_FOCUSED hint, does not update the window state
3405  * in case of focus change */
3406  g_signal_connect(G_OBJECT(cnnwin), "window-state-event", G_CALLBACK(rcw_state_event), NULL);
3407  g_signal_connect(G_OBJECT(cnnwin), "focus-in-event", G_CALLBACK(rcw_focus_in_event), NULL);
3408  g_signal_connect(G_OBJECT(cnnwin), "focus-out-event", G_CALLBACK(rcw_focus_out_event), NULL);
3409 
3410  g_signal_connect(G_OBJECT(cnnwin), "enter-notify-event", G_CALLBACK(rcw_on_enter_notify_event), NULL);
3411  g_signal_connect(G_OBJECT(cnnwin), "leave-notify-event", G_CALLBACK(rcw_on_leave_notify_event), NULL);
3412 
3413 
3414  g_signal_connect(G_OBJECT(cnnwin), "configure_event", G_CALLBACK(rcw_on_configure), NULL);
3415 
3416  return cnnwin;
3417 }
3418 
3419 /* This function will be called for the first connection. A tag is set to the window so that
3420  * other connections can determine if whether a new tab should be append to the same window
3421  */
3423 {
3424  TRACE_CALL(__func__);
3425  gchar *tag;
3426 
3427  switch (remmina_pref.tab_mode) {
3428  case REMMINA_TAB_BY_GROUP:
3429  tag = g_strdup(remmina_file_get_string(cnnobj->remmina_file, "group"));
3430  break;
3432  tag = g_strdup(remmina_file_get_string(cnnobj->remmina_file, "protocol"));
3433  break;
3434  default:
3435  tag = NULL;
3436  break;
3437  }
3438  g_object_set_data_full(G_OBJECT(cnnwin), "tag", tag, (GDestroyNotify)g_free);
3439 }
3440 
3442 {
3443  TRACE_CALL(__func__);
3444  RemminaConnectionObject *cnnobj;
3445 
3446  if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return;
3447 
3448  if (GTK_IS_WIDGET(cnnobj->proto))
3449  remmina_protocol_widget_grab_focus(REMMINA_PROTOCOL_WIDGET(cnnobj->proto));
3450 }
3451 
3452 static GtkWidget *nb_find_page_by_cnnobj(GtkNotebook *notebook, RemminaConnectionObject *cnnobj)
3453 {
3454  gint i, np;
3455  GtkWidget *found_page, *pg;
3456 
3457  if (cnnobj == NULL || cnnobj->cnnwin == NULL || cnnobj->cnnwin->priv == NULL)
3458  return NULL;
3459  found_page = NULL;
3460  np = gtk_notebook_get_n_pages(cnnobj->cnnwin->priv->notebook);
3461  for (i = 0; i < np; i++) {
3462  pg = gtk_notebook_get_nth_page(cnnobj->cnnwin->priv->notebook, i);
3463  if (g_object_get_data(G_OBJECT(pg), "cnnobj") == cnnobj) {
3464  found_page = pg;
3465  break;
3466  }
3467  }
3468 
3469  return found_page;
3470 }
3471 
3472 
3474 {
3475  TRACE_CALL(__func__);
3476  RemminaConnectionObject *cnnobj = gp->cnnobj;
3477  GtkWidget *page_to_remove;
3478 
3479 
3480  if (cnnobj && cnnobj->scrolled_container && REMMINA_IS_SCROLLED_VIEWPORT(cnnobj->scrolled_container)) {
3481  REMMINA_DEBUG("deleting motion");
3482  remmina_scrolled_viewport_remove_motion(REMMINA_SCROLLED_VIEWPORT(cnnobj->scrolled_container));
3483  }
3484 
3485  if (cnnobj && cnnobj->cnnwin) {
3486  page_to_remove = nb_find_page_by_cnnobj(cnnobj->cnnwin->priv->notebook, cnnobj);
3487  if (page_to_remove) {
3488  gtk_notebook_remove_page(
3489  cnnobj->cnnwin->priv->notebook,
3490  gtk_notebook_page_num(cnnobj->cnnwin->priv->notebook, page_to_remove));
3491  /* Invalidate pointers to objects destroyed by page removal */
3492  cnnobj->aspectframe = NULL;
3493  cnnobj->viewport = NULL;
3494  cnnobj->scrolled_container = NULL;
3495  /* we cannot invalidate cnnobj->proto, because it can be already been
3496  * detached from the widget hierarchy in rco_on_disconnect() */
3497  }
3498  }
3499  if (cnnobj) {
3500  cnnobj->remmina_file = NULL;
3501  g_free(cnnobj);
3502  gp->cnnobj = NULL;
3503  }
3504 
3506 }
3507 
3509 {
3510  TRACE_CALL(__func__);
3511  if (REMMINA_IS_PROTOCOL_WIDGET(cnnobj->proto)) {
3513  remmina_protocol_widget_close_connection(REMMINA_PROTOCOL_WIDGET(cnnobj->proto));
3514  else
3516  }
3517 }
3518 
3520 {
3521  TRACE_CALL(__func__);
3522  GtkWidget *hbox;
3523  GtkWidget *widget;
3524  GtkWidget *button;
3525 
3526  hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
3527  gtk_widget_show(hbox);
3528 
3529  widget = gtk_image_new_from_icon_name(remmina_file_get_icon_name(cnnobj->remmina_file), GTK_ICON_SIZE_MENU);
3530  gtk_widget_show(widget);
3531  gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, FALSE, 0);
3532 
3533  widget = gtk_label_new(remmina_file_get_string(cnnobj->remmina_file, "name"));
3534  gtk_widget_set_valign(widget, GTK_ALIGN_CENTER);
3535  gtk_widget_set_halign(widget, GTK_ALIGN_CENTER);
3536 
3537  gtk_widget_show(widget);
3538  gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, TRUE, 0);
3539 
3540  button = gtk_button_new(); // The "x" to close the tab
3541  gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
3542 #if GTK_CHECK_VERSION(3, 20, 0)
3543  gtk_widget_set_focus_on_click(GTK_WIDGET(widget), FALSE);
3544 #else
3545  gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
3546 #endif
3547  gtk_widget_set_name(button, "remmina-small-button");
3548  gtk_widget_show(button);
3549 
3550  widget = gtk_image_new_from_icon_name("window-close", GTK_ICON_SIZE_MENU);
3551  gtk_widget_show(widget);
3552  gtk_container_add(GTK_CONTAINER(button), widget);
3553 
3554  gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
3555 
3556  g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(rco_on_close_button_clicked), cnnobj);
3557 
3558 
3559  return hbox;
3560 }
3561 
3563 {
3564  GtkWidget *page;
3565 
3566  page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
3567  gtk_widget_set_name(page, "remmina-tab-page");
3568 
3569 
3570  return page;
3571 }
3572 
3574 {
3575  TRACE_CALL(__func__);
3576  GtkWidget *page, *label;
3577  GtkNotebook *notebook;
3578 
3579  notebook = cnnwin->priv->notebook;
3580 
3581  page = rco_create_tab_page(cnnobj);
3582  g_object_set_data(G_OBJECT(page), "cnnobj", cnnobj);
3583  label = rco_create_tab_label(cnnobj);
3584 
3585  cnnobj->cnnwin = cnnwin;
3586 
3587  gtk_notebook_append_page(notebook, page, label);
3588  gtk_notebook_set_tab_reorderable(notebook, page, TRUE);
3589  gtk_notebook_set_tab_detachable(notebook, page, TRUE);
3590  /* This trick prevents the tab label from being focused */
3591  gtk_widget_set_can_focus(gtk_widget_get_parent(label), FALSE);
3592 
3593  if (gtk_widget_get_parent(cnnobj->scrolled_container) != NULL)
3594  printf("REMMINA WARNING in %s: scrolled_container already has a parent\n", __func__);
3595  gtk_box_pack_start(GTK_BOX(page), cnnobj->scrolled_container, TRUE, TRUE, 0);
3596 
3597  gtk_widget_show(page);
3598 
3599  return page;
3600 }
3601 
3602 
3604 {
3605  TRACE_CALL(__func__);
3606  GtkNotebook *notebook;
3607  gint n;
3608 
3609  notebook = GTK_NOTEBOOK(cnnwin->priv->notebook);
3610 
3611  switch (cnnwin->priv->view_mode) {
3612  case SCROLLED_WINDOW_MODE:
3613  n = gtk_notebook_get_n_pages(notebook);
3614  gtk_notebook_set_show_tabs(notebook, remmina_pref.always_show_tab ? TRUE : n > 1);
3615  gtk_notebook_set_show_border(notebook, remmina_pref.always_show_tab ? TRUE : n > 1);
3616  break;
3617  default:
3618  gtk_notebook_set_show_tabs(notebook, FALSE);
3619  gtk_notebook_set_show_border(notebook, FALSE);
3620  break;
3621  }
3622 }
3623 
3624 static gboolean rcw_on_switch_page_finalsel(gpointer user_data)
3625 {
3626  TRACE_CALL(__func__);
3628  RemminaConnectionObject *cnnobj;
3629 
3630  if (!user_data)
3631  return FALSE;
3632 
3633  cnnobj = (RemminaConnectionObject *)user_data;
3634  if (!cnnobj->cnnwin)
3635  return FALSE;
3636 
3637  priv = cnnobj->cnnwin->priv;
3638 
3639  if (GTK_IS_WIDGET(cnnobj->cnnwin)) {
3640  rcw_floating_toolbar_show(cnnobj->cnnwin, TRUE);
3641  if (!priv->hidetb_eventsource)
3642  priv->hidetb_eventsource = g_timeout_add(TB_HIDE_TIME_TIME, (GSourceFunc)
3644  rco_update_toolbar(cnnobj);
3645  rcw_grab_focus(cnnobj->cnnwin);
3646  if (priv->view_mode != SCROLLED_WINDOW_MODE)
3647  rco_check_resize(cnnobj);
3648  }
3649  priv->spf_eventsourceid = 0;
3650  return FALSE;
3651 }
3652 
3653 static void rcw_on_switch_page(GtkNotebook *notebook, GtkWidget *newpage, guint page_num,
3654  RemminaConnectionWindow *cnnwin)
3655 {
3656  TRACE_CALL(__func__);
3657  RemminaConnectionWindowPriv *priv = cnnwin->priv;
3658  RemminaConnectionObject *cnnobj_newpage;
3659 
3660  cnnobj_newpage = g_object_get_data(G_OBJECT(newpage), "cnnobj");
3661  if (priv->spf_eventsourceid)
3662  g_source_remove(priv->spf_eventsourceid);
3663  priv->spf_eventsourceid = g_idle_add(rcw_on_switch_page_finalsel, cnnobj_newpage);
3664 }
3665 
3666 static void rcw_on_page_added(GtkNotebook *notebook, GtkWidget *child, guint page_num,
3667  RemminaConnectionWindow *cnnwin)
3668 {
3669  if (gtk_notebook_get_n_pages(GTK_NOTEBOOK(cnnwin->priv->notebook)) > 0)
3670  rcw_update_notebook(cnnwin);
3671 }
3672 
3673 static void rcw_on_page_removed(GtkNotebook *notebook, GtkWidget *child, guint page_num,
3674  RemminaConnectionWindow *cnnwin)
3675 {
3676  TRACE_CALL(__func__);
3677 
3678  if (gtk_notebook_get_n_pages(GTK_NOTEBOOK(cnnwin->priv->notebook)) <= 0)
3679  gtk_widget_destroy(GTK_WIDGET(cnnwin));
3680 
3681 }
3682 
3683 static GtkNotebook *
3684 rcw_on_notebook_create_window(GtkNotebook *notebook, GtkWidget *page, gint x, gint y, gpointer data)
3685 {
3686  /* This signal callback is called by GTK when a detachable tab is dropped on the root window
3687  * or in an existing window */
3688 
3689  TRACE_CALL(__func__);
3690  RemminaConnectionWindow *srccnnwin;
3691  RemminaConnectionWindow *dstcnnwin;
3692  RemminaConnectionObject *cnnobj;
3693  GdkWindow *window;
3694  gchar *srctag;
3695  gint width, height;
3696 
3697 #if GTK_CHECK_VERSION(3, 20, 0)
3698  GdkSeat *seat;
3699 #else
3700  GdkDeviceManager *manager;
3701 #endif
3702  GdkDevice *device = NULL;
3703 
3704 #if GTK_CHECK_VERSION(3, 20, 0)
3705  seat = gdk_display_get_default_seat(gdk_display_get_default());
3706  device = gdk_seat_get_pointer(seat);
3707 #else
3708  manager = gdk_display_get_device_manager(gdk_display_get_default());
3709  device = gdk_device_manager_get_client_pointer(manager);
3710 #endif
3711 
3712  window = gdk_device_get_window_at_position(device, &x, &y);
3713  srccnnwin = RCW(gtk_widget_get_toplevel(GTK_WIDGET(notebook)));
3714  dstcnnwin = RCW(remmina_widget_pool_find_by_window(REMMINA_TYPE_CONNECTION_WINDOW, window));
3715 
3716  if (srccnnwin == dstcnnwin)
3717  return NULL;
3718 
3719  if (gtk_notebook_get_n_pages(GTK_NOTEBOOK(srccnnwin->priv->notebook)) == 1 && !dstcnnwin)
3720  return NULL;
3721 
3722  cnnobj = (RemminaConnectionObject *)g_object_get_data(G_OBJECT(page), "cnnobj");
3723 
3724  if (!dstcnnwin) {
3725  /* Drop is directed to a new rcw: create a new scrolled window to accommodate
3726  * the dropped connectionand move our cnnobj there. Width and
3727  * height of the new window are cloned from the current window */
3728  srctag = (gchar *)g_object_get_data(G_OBJECT(srccnnwin), "tag");
3729  gtk_window_get_size(GTK_WINDOW(srccnnwin), &width, &height);
3730  dstcnnwin = rcw_create_scrolled(width, height, FALSE); // New dropped window is never maximized
3731  g_object_set_data_full(G_OBJECT(dstcnnwin), "tag", g_strdup(srctag), (GDestroyNotify)g_free);
3732  /* when returning, GTK will move the whole tab to the new notebook.
3733  * Prepare cnnobj to be hosted in the new cnnwin */
3734  cnnobj->cnnwin = dstcnnwin;
3735  } else {
3736  cnnobj->cnnwin = dstcnnwin;
3737  }
3738 
3739  remmina_protocol_widget_set_hostkey_func(REMMINA_PROTOCOL_WIDGET(cnnobj->proto),
3740  (RemminaHostkeyFunc)rcw_hostkey_func);
3741 
3742  return GTK_NOTEBOOK(cnnobj->cnnwin->priv->notebook);
3743 }
3744 
3745 static GtkNotebook *
3747 {
3748  TRACE_CALL(__func__);
3749  GtkNotebook *notebook;
3750 
3751  notebook = GTK_NOTEBOOK(gtk_notebook_new());
3752 
3753  gtk_notebook_set_scrollable(GTK_NOTEBOOK(notebook), TRUE);
3754  gtk_widget_show(GTK_WIDGET(notebook));
3755 
3756  g_signal_connect(G_OBJECT(notebook), "create-window", G_CALLBACK(rcw_on_notebook_create_window), NULL);
3757  g_signal_connect(G_OBJECT(notebook), "switch-page", G_CALLBACK(rcw_on_switch_page), cnnwin);
3758  g_signal_connect(G_OBJECT(notebook), "page-added", G_CALLBACK(rcw_on_page_added), cnnwin);
3759  g_signal_connect(G_OBJECT(notebook), "page-removed", G_CALLBACK(rcw_on_page_removed), cnnwin);
3760  gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
3761 
3762  return notebook;
3763 }
3764 
3765 /* Create a scrolled toplevel window */
3766 static RemminaConnectionWindow *rcw_create_scrolled(gint width, gint height, gboolean maximize)
3767 {
3768  TRACE_CALL(__func__);
3769  RemminaConnectionWindow *cnnwin;
3770  GtkWidget *grid;
3771  GtkWidget *toolbar;
3772  GtkNotebook *notebook;
3773  GtkSettings *settings = gtk_settings_get_default();
3774 
3775  cnnwin = rcw_new(FALSE, 0);
3776  gtk_widget_realize(GTK_WIDGET(cnnwin));
3777 
3778  gtk_window_set_default_size(GTK_WINDOW(cnnwin), width, height);
3779  g_object_set(settings, "gtk-application-prefer-dark-theme", remmina_pref.dark_theme, NULL);
3780 
3781  /* Create the toolbar */
3782  toolbar = rcw_create_toolbar(cnnwin, SCROLLED_WINDOW_MODE);
3783 
3784  /* Create the notebook */
3785  notebook = rcw_create_notebook(cnnwin);
3786 
3787  /* Create the grid container for toolbars+notebook and populate it */
3788  grid = gtk_grid_new();
3789 
3790 
3791  gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(notebook), 0, 0, 1, 1);
3792 
3793  gtk_widget_set_hexpand(GTK_WIDGET(notebook), TRUE);
3794  gtk_widget_set_vexpand(GTK_WIDGET(notebook), TRUE);
3795 
3796  rcw_place_toolbar(GTK_TOOLBAR(toolbar), GTK_GRID(grid), GTK_WIDGET(notebook), remmina_pref.toolbar_placement);
3797 
3798  gtk_container_add(GTK_CONTAINER(cnnwin), grid);
3799 
3800  /* Add drag capabilities to the toolbar */
3801  gtk_drag_source_set(GTK_WIDGET(toolbar), GDK_BUTTON1_MASK,
3802  dnd_targets_tb, sizeof dnd_targets_tb / sizeof *dnd_targets_tb, GDK_ACTION_MOVE);
3803  g_signal_connect_after(GTK_WIDGET(toolbar), "drag-begin", G_CALLBACK(rcw_tb_drag_begin), NULL);
3804  g_signal_connect(GTK_WIDGET(toolbar), "drag-failed", G_CALLBACK(rcw_tb_drag_failed), cnnwin);
3805 
3806  /* Add drop capabilities to the drop/dest target for the toolbar (the notebook) */
3807  gtk_drag_dest_set(GTK_WIDGET(notebook), GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT,
3808  dnd_targets_tb, sizeof dnd_targets_tb / sizeof *dnd_targets_tb, GDK_ACTION_MOVE);
3809  gtk_drag_dest_set_track_motion(GTK_WIDGET(notebook), TRUE);
3810  g_signal_connect(GTK_WIDGET(notebook), "drag-drop", G_CALLBACK(rcw_tb_drag_drop), cnnwin);
3811 
3812  cnnwin->priv->view_mode = SCROLLED_WINDOW_MODE;
3813  cnnwin->priv->toolbar = toolbar;
3814  cnnwin->priv->grid = grid;
3815  cnnwin->priv->notebook = notebook;
3816  cnnwin->priv->ss_width = width;
3817  cnnwin->priv->ss_height = height;
3818  cnnwin->priv->ss_maximized = maximize;
3819 
3820  /* The notebook and all its child must be realized now, or a reparent will
3821  * call unrealize() and will destroy a GtkSocket */
3822  gtk_widget_show(grid);
3823  gtk_widget_show(GTK_WIDGET(cnnwin));
3824  GtkWindowGroup *wingrp = gtk_window_group_new();
3825 
3826  gtk_window_group_add_window(wingrp, GTK_WINDOW(cnnwin));
3827  gtk_window_set_transient_for(GTK_WINDOW(cnnwin), NULL);
3828 
3829  if (maximize)
3830  gtk_window_maximize(GTK_WINDOW(cnnwin));
3831 
3832 
3834 
3835  return cnnwin;
3836 }
3837 
3839 {
3840  TRACE_CALL(__func__);
3841 
3842  GtkWidget *revealer;
3844 
3845  priv = cnnwin->priv;
3846 
3847  if (priv->overlay_ftb_overlay != NULL) {
3848  gtk_widget_destroy(priv->overlay_ftb_overlay);
3849  priv->overlay_ftb_overlay = NULL;
3850  priv->revealer = NULL;
3851  }
3852 
3853  rcw_create_floating_toolbar(cnnwin, priv->fss_view_mode);
3854 
3855  priv->overlay_ftb_overlay = gtk_event_box_new();
3856 
3857  GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
3858 
3859  gtk_container_set_border_width(GTK_CONTAINER(vbox), 0);
3860 
3861  GtkWidget *handle = gtk_drawing_area_new();
3862 
3863  gtk_widget_set_size_request(handle, 4, 4);
3864  gtk_widget_set_name(handle, "ftb-handle");
3865 
3866  revealer = gtk_revealer_new();
3867 
3868  gtk_widget_set_halign(GTK_WIDGET(priv->overlay_ftb_overlay), GTK_ALIGN_CENTER);
3869 
3871  gtk_box_pack_start(GTK_BOX(vbox), handle, FALSE, FALSE, 0);
3872  gtk_box_pack_start(GTK_BOX(vbox), revealer, FALSE, FALSE, 0);
3873  gtk_revealer_set_transition_type(GTK_REVEALER(revealer), GTK_REVEALER_TRANSITION_TYPE_SLIDE_UP);
3874  gtk_widget_set_valign(GTK_WIDGET(priv->overlay_ftb_overlay), GTK_ALIGN_END);
3875  } else {
3876  gtk_box_pack_start(GTK_BOX(vbox), revealer, FALSE, FALSE, 0);
3877  gtk_box_pack_start(GTK_BOX(vbox), handle, FALSE, FALSE, 0);
3878  gtk_revealer_set_transition_type(GTK_REVEALER(revealer), GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN);
3879  gtk_widget_set_valign(GTK_WIDGET(priv->overlay_ftb_overlay), GTK_ALIGN_START);
3880  }
3881 
3882 
3883  gtk_container_add(GTK_CONTAINER(revealer), priv->floating_toolbar_widget);
3884  gtk_widget_set_halign(GTK_WIDGET(revealer), GTK_ALIGN_CENTER);
3885  gtk_widget_set_valign(GTK_WIDGET(revealer), GTK_ALIGN_START);
3886 
3887  priv->revealer = revealer;
3888 
3889  GtkWidget *fr;
3890 
3891  fr = gtk_frame_new(NULL);
3892  gtk_container_add(GTK_CONTAINER(priv->overlay_ftb_overlay), fr);
3893  gtk_container_add(GTK_CONTAINER(fr), vbox);
3894 
3895  gtk_widget_show(vbox);
3896  gtk_widget_show(revealer);
3897  gtk_widget_show(handle);
3898  gtk_widget_show(priv->overlay_ftb_overlay);
3899  gtk_widget_show(fr);
3900 
3902  gtk_widget_set_name(fr, "ftbbox-lower");
3903  else
3904  gtk_widget_set_name(fr, "ftbbox-upper");
3905 
3906  gtk_overlay_add_overlay(GTK_OVERLAY(priv->overlay), priv->overlay_ftb_overlay);
3907 
3908  rcw_floating_toolbar_show(cnnwin, TRUE);
3909 
3910  g_signal_connect(G_OBJECT(priv->overlay_ftb_overlay), "enter-notify-event", G_CALLBACK(rcw_floating_toolbar_on_enter), cnnwin);
3911  g_signal_connect(G_OBJECT(priv->overlay_ftb_overlay), "leave-notify-event", G_CALLBACK(rcw_floating_toolbar_on_leave), cnnwin);
3912  g_signal_connect(G_OBJECT(priv->overlay_ftb_overlay), "scroll-event", G_CALLBACK(rcw_floating_toolbar_on_scroll), cnnwin);
3913  gtk_widget_add_events(
3914  GTK_WIDGET(priv->overlay_ftb_overlay),
3915  GDK_SCROLL_MASK
3916 #if GTK_CHECK_VERSION(3, 4, 0)
3917  | GDK_SMOOTH_SCROLL_MASK
3918 #endif
3919  );
3920 
3921  /* Add drag and drop capabilities to the source */
3922  gtk_drag_source_set(GTK_WIDGET(priv->overlay_ftb_overlay), GDK_BUTTON1_MASK,
3923  dnd_targets_ftb, sizeof dnd_targets_ftb / sizeof *dnd_targets_ftb, GDK_ACTION_MOVE);
3924  g_signal_connect_after(GTK_WIDGET(priv->overlay_ftb_overlay), "drag-begin", G_CALLBACK(rcw_ftb_drag_begin), cnnwin);
3925 
3927  /* toolbar in fullscreenmode disabled, hide everything */
3928  gtk_widget_hide(fr);
3929 }
3930 
3931 
3932 static gboolean rcw_ftb_drag_drop(GtkWidget *widget, GdkDragContext *context,
3933  gint x, gint y, guint time, RemminaConnectionWindow *cnnwin)
3934 {
3935  TRACE_CALL(__func__);
3936  GtkAllocation wa;
3937  gint new_floating_toolbar_placement;
3938  RemminaConnectionObject *cnnobj;
3939 
3940  gtk_widget_get_allocation(widget, &wa);
3941 
3942  if (y >= wa.height / 2)
3943  new_floating_toolbar_placement = FLOATING_TOOLBAR_PLACEMENT_BOTTOM;
3944  else
3945  new_floating_toolbar_placement = FLOATING_TOOLBAR_PLACEMENT_TOP;
3946 
3947  gtk_drag_finish(context, TRUE, TRUE, time);
3948 
3949  if (new_floating_toolbar_placement != remmina_pref.floating_toolbar_placement) {
3950  /* Destroy and recreate the FTB */
3951  remmina_pref.floating_toolbar_placement = new_floating_toolbar_placement;
3954  cnnobj = rcw_get_visible_cnnobj(cnnwin);
3955  if (cnnobj) rco_update_toolbar(cnnobj);
3956  }
3957 
3958  return TRUE;
3959 }
3960 
3961 static void rcw_ftb_drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer user_data)
3962 {
3963  TRACE_CALL(__func__);
3964 
3965  cairo_surface_t *surface;
3966  cairo_t *cr;
3967  GtkAllocation wa;
3968  double dashes[] = { 10 };
3969 
3970  gtk_widget_get_allocation(widget, &wa);
3971 
3972  surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, wa.width, wa.height);
3973  cr = cairo_create(surface);
3974  cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
3975  cairo_set_line_width(cr, 2);
3976  cairo_set_dash(cr, dashes, 1, 0);
3977  cairo_rectangle(cr, 0, 0, wa.width, wa.height);
3978  cairo_stroke(cr);
3979  cairo_destroy(cr);
3980 
3981  gtk_drag_set_icon_surface(context, surface);
3982 }
3983 
3984 RemminaConnectionWindow *rcw_create_fullscreen(GtkWindow *old, gint view_mode)
3985 {
3986  TRACE_CALL(__func__);
3987  RemminaConnectionWindow *cnnwin;
3988  GtkNotebook *notebook;
3989 
3990 #if GTK_CHECK_VERSION(3, 22, 0)
3991  gint n_monitors;
3992  gint i;
3993  GdkMonitor *old_monitor;
3994  GdkDisplay *old_display;
3995  GdkWindow *old_window;
3996 #endif
3997  gint full_screen_target_monitor;
3998 
3999  full_screen_target_monitor = FULL_SCREEN_TARGET_MONITOR_UNDEFINED;
4000  if (old) {
4001 #if GTK_CHECK_VERSION(3, 22, 0)
4002  old_window = gtk_widget_get_window(GTK_WIDGET(old));
4003  old_display = gdk_window_get_display(old_window);
4004  old_monitor = gdk_display_get_monitor_at_window(old_display, old_window);
4005  n_monitors = gdk_display_get_n_monitors(old_display);
4006  for (i = 0; i < n_monitors; ++i) {
4007  if (gdk_display_get_monitor(old_display, i) == old_monitor) {
4008  full_screen_target_monitor = i;
4009  break;
4010  }
4011  }
4012 #else
4013  full_screen_target_monitor = gdk_screen_get_monitor_at_window(
4014  gdk_screen_get_default(),
4015  gtk_widget_get_window(GTK_WIDGET(old)));
4016 #endif
4017  }
4018 
4019  cnnwin = rcw_new(TRUE, full_screen_target_monitor);
4020  gtk_widget_set_name(GTK_WIDGET(cnnwin), "remmina-connection-window-fullscreen");
4021  gtk_widget_realize(GTK_WIDGET(cnnwin));
4022 
4023  if (!view_mode)
4024  view_mode = VIEWPORT_FULLSCREEN_MODE;
4025 
4026  notebook = rcw_create_notebook(cnnwin);
4027 
4028  cnnwin->priv->overlay = gtk_overlay_new();
4029  gtk_container_add(GTK_CONTAINER(cnnwin), cnnwin->priv->overlay);
4030  gtk_container_add(GTK_CONTAINER(cnnwin->priv->overlay), GTK_WIDGET(notebook));
4031  gtk_widget_show(GTK_WIDGET(cnnwin->priv->overlay));
4032 
4033  cnnwin->priv->notebook = notebook;
4034  cnnwin->priv->view_mode = view_mode;
4035  cnnwin->priv->fss_view_mode = view_mode;
4036 
4037  /* Create the floating toolbar */
4039  /* Add drag and drop capabilities to the drop/dest target for floating toolbar */
4040  gtk_drag_dest_set(GTK_WIDGET(cnnwin->priv->overlay), GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT,
4041  dnd_targets_ftb, sizeof dnd_targets_ftb / sizeof *dnd_targets_ftb, GDK_ACTION_MOVE);
4042  gtk_drag_dest_set_track_motion(GTK_WIDGET(cnnwin->priv->notebook), TRUE);
4043  g_signal_connect(GTK_WIDGET(cnnwin->priv->overlay), "drag-drop", G_CALLBACK(rcw_ftb_drag_drop), cnnwin);
4044 
4045  gtk_widget_show(GTK_WIDGET(cnnwin));
4046  GtkWindowGroup *wingrp = gtk_window_group_new();
4047  gtk_window_group_add_window(wingrp, GTK_WINDOW(cnnwin));
4048  gtk_window_set_transient_for(GTK_WINDOW(cnnwin), NULL);
4049 
4050  return cnnwin;
4051 }
4052 
4053 static gboolean rcw_hostkey_func(RemminaProtocolWidget *gp, guint keyval, gboolean release)
4054 {
4055  TRACE_CALL(__func__);
4056  RemminaConnectionObject *cnnobj = gp->cnnobj;
4057  RemminaConnectionWindowPriv *priv = cnnobj->cnnwin->priv;
4058  const RemminaProtocolFeature *feature;
4059  gint i;
4060 
4061  if (release) {
4062  if (remmina_pref.hostkey && keyval == remmina_pref.hostkey) {
4063  priv->hostkey_activated = FALSE;
4064  if (priv->hostkey_used)
4065  /* hostkey pressed + something else */
4066  return TRUE;
4067  }
4068  /* If hostkey is released without pressing other keys, we should execute the
4069  * shortcut key which is the same as hostkey. Be default, this is grab/ungrab
4070  * keyboard */
4071  else if (priv->hostkey_activated) {
4072  /* Trap all key releases when hostkey is pressed */
4073  /* hostkey pressed + something else */
4074  return TRUE;
4075  } else {
4076  /* Any key pressed, no hostkey */
4077  return FALSE;
4078  }
4079  } else if (remmina_pref.hostkey && keyval == remmina_pref.hostkey) {
4081  priv->hostkey_activated = TRUE;
4082  priv->hostkey_used = FALSE;
4083  return TRUE;
4084  } else if (!priv->hostkey_activated) {
4085  /* Any key pressed, no hostkey */
4086  return FALSE;
4087  }
4088 
4089  priv->hostkey_used = TRUE;
4090  keyval = gdk_keyval_to_lower(keyval);
4091  if (keyval == GDK_KEY_Up || keyval == GDK_KEY_Down
4092  || keyval == GDK_KEY_Left || keyval == GDK_KEY_Right) {
4093  GtkAdjustment *adjust;
4094  int pos;
4095 
4096  if (cnnobj->connected && GTK_IS_SCROLLED_WINDOW(cnnobj->scrolled_container)) {
4097  if (keyval == GDK_KEY_Up || keyval == GDK_KEY_Down)
4098  adjust = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(cnnobj->scrolled_container));
4099  else
4100  adjust = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(cnnobj->scrolled_container));
4101 
4102  if (keyval == GDK_KEY_Up || keyval == GDK_KEY_Left)
4103  pos = 0;
4104  else
4105  pos = gtk_adjustment_get_upper(adjust);
4106 
4107  gtk_adjustment_set_value(adjust, pos);
4108  if (keyval == GDK_KEY_Up || keyval == GDK_KEY_Down)
4109  gtk_scrolled_window_set_vadjustment(GTK_SCROLLED_WINDOW(cnnobj->scrolled_container), adjust);
4110  else
4111  gtk_scrolled_window_set_hadjustment(GTK_SCROLLED_WINDOW(cnnobj->scrolled_container), adjust);
4112  } else if (REMMINA_IS_SCROLLED_VIEWPORT(cnnobj->scrolled_container)) {
4114  GtkWidget *child;
4115  GdkWindow *gsvwin;
4116  gint sz;
4117  GtkAdjustment *adj;
4118  gdouble value;
4119 
4120  if (!GTK_IS_BIN(cnnobj->scrolled_container))
4121  return FALSE;
4122 
4123  gsv = REMMINA_SCROLLED_VIEWPORT(cnnobj->scrolled_container);
4124  child = gtk_bin_get_child(GTK_BIN(gsv));
4125  if (!GTK_IS_VIEWPORT(child))
4126  return FALSE;
4127 
4128  gsvwin = gtk_widget_get_window(GTK_WIDGET(gsv));
4129  if (!gsv)
4130  return FALSE;
4131 
4132  if (keyval == GDK_KEY_Up || keyval == GDK_KEY_Down) {
4133  sz = gdk_window_get_height(gsvwin) + 2; // Add 2px of black scroll border
4134  adj = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(child));
4135  } else {
4136  sz = gdk_window_get_width(gsvwin) + 2; // Add 2px of black scroll border
4137  adj = gtk_scrollable_get_hadjustment(GTK_SCROLLABLE(child));
4138  }
4139 
4140  if (keyval == GDK_KEY_Up || keyval == GDK_KEY_Left)
4141  value = 0;
4142  else
4143  value = gtk_adjustment_get_upper(GTK_ADJUSTMENT(adj)) - (gdouble)sz + 2.0;
4144 
4145  gtk_adjustment_set_value(GTK_ADJUSTMENT(adj), value);
4146  }
4147  }
4148 
4149  if (keyval == remmina_pref.shortcutkey_fullscreen && !extrahardening) {
4150  switch (priv->view_mode) {
4151  case SCROLLED_WINDOW_MODE:
4152  rcw_switch_viewmode(cnnobj->cnnwin, priv->fss_view_mode);
4153  break;
4157  break;
4158  default:
4159  break;
4160  }
4161  } else if (keyval == remmina_pref.shortcutkey_autofit && !extrahardening) {
4162  if (priv->toolitem_autofit && gtk_widget_is_sensitive(GTK_WIDGET(priv->toolitem_autofit)))
4163  rcw_toolbar_autofit(GTK_TOOL_ITEM(gp), cnnobj->cnnwin);
4164  } else if (keyval == remmina_pref.shortcutkey_nexttab && !extrahardening) {
4165  i = gtk_notebook_get_current_page(GTK_NOTEBOOK(priv->notebook)) + 1;
4166  if (i >= gtk_notebook_get_n_pages(GTK_NOTEBOOK(priv->notebook)))
4167  i = 0;
4168  gtk_notebook_set_current_page(GTK_NOTEBOOK(priv->notebook), i);
4169  } else if (keyval == remmina_pref.shortcutkey_prevtab && !extrahardening) {
4170  i = gtk_notebook_get_current_page(GTK_NOTEBOOK(priv->notebook)) - 1;
4171  if (i < 0)
4172  i = gtk_notebook_get_n_pages(GTK_NOTEBOOK(priv->notebook)) - 1;
4173  gtk_notebook_set_current_page(GTK_NOTEBOOK(priv->notebook), i);
4174  } else if (keyval == remmina_pref.shortcutkey_scale && !extrahardening) {
4175  if (gtk_widget_is_sensitive(GTK_WIDGET(priv->toolitem_scale))) {
4176  gtk_toggle_tool_button_set_active(
4177  GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_scale),
4178  !gtk_toggle_tool_button_get_active(
4179  GTK_TOGGLE_TOOL_BUTTON(
4180  priv->toolitem_scale)));
4181  }
4182  } else if (keyval == remmina_pref.shortcutkey_grab && !extrahardening) {
4183  gtk_toggle_tool_button_set_active(
4184  GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_grab),
4185  !gtk_toggle_tool_button_get_active(
4186  GTK_TOGGLE_TOOL_BUTTON(
4187  priv->toolitem_grab)));
4188  } else if (keyval == remmina_pref.shortcutkey_minimize && !extrahardening) {
4189  rcw_toolbar_minimize(GTK_TOOL_ITEM(gp),
4190  cnnobj->cnnwin);
4191  } else if (keyval == remmina_pref.shortcutkey_viewonly && !extrahardening) {
4192  remmina_file_set_int(cnnobj->remmina_file, "viewonly",
4193  (remmina_file_get_int(cnnobj->remmina_file, "viewonly", 0)
4194  == 0) ? 1 : 0);
4195  } else if (keyval == remmina_pref.shortcutkey_screenshot && !extrahardening) {
4196  rcw_toolbar_screenshot(GTK_TOOL_ITEM(gp),
4197  cnnobj->cnnwin);
4198  } else if (keyval == remmina_pref.shortcutkey_disconnect && !extrahardening) {
4200  } else if (keyval == remmina_pref.shortcutkey_toolbar && !extrahardening) {
4201  if (priv->view_mode == SCROLLED_WINDOW_MODE) {
4202  remmina_pref.hide_connection_toolbar =
4203  !remmina_pref.hide_connection_toolbar;
4205  }
4206  } else {
4207  for (feature =
4209  REMMINA_PROTOCOL_WIDGET(
4210  cnnobj->proto));
4211  feature && feature->type;
4212  feature++) {
4213  if (feature->type
4215  && GPOINTER_TO_UINT(
4216  feature->opt3)
4217  == keyval) {
4219  REMMINA_PROTOCOL_WIDGET(
4220  cnnobj->proto),
4221  feature);
4222  break;
4223  }
4224  }
4225  }
4226  /* If a keypress makes the current cnnobj to move to another window,
4227  * priv is now invalid. So we can no longer use priv here */
4228  cnnobj->cnnwin->priv->hostkey_activated = FALSE;
4229 
4230  /* Trap all keypresses when hostkey is pressed */
4231  return TRUE;
4232 }
4233 
4235 {
4236  TRACE_CALL(__func__);
4237  const gchar *tag;
4238 
4239  switch (remmina_pref.tab_mode) {
4240  case REMMINA_TAB_BY_GROUP:
4241  tag = remmina_file_get_string(remminafile, "group");
4242  break;
4244  tag = remmina_file_get_string(remminafile, "protocol");
4245  break;
4246  case REMMINA_TAB_ALL:
4247  tag = NULL;
4248  break;
4249  case REMMINA_TAB_NONE:
4250  default:
4251  return NULL;
4252  }
4253  return RCW(remmina_widget_pool_find(REMMINA_TYPE_CONNECTION_WINDOW, tag));
4254 }
4255 
4256 gboolean rcw_delayed_window_present(gpointer user_data)
4257 {
4258  RemminaConnectionWindow *cnnwin = (RemminaConnectionWindow *)user_data;
4259 
4260  if (cnnwin) {
4261  gtk_window_present_with_time(GTK_WINDOW(cnnwin), (guint32)(g_get_monotonic_time() / 1000));
4262  rcw_grab_focus(cnnwin);
4263  }
4264  cnnwin->priv->dwp_eventsourceid = 0;
4265  return G_SOURCE_REMOVE;
4266 }
4267 
4269 {
4270  TRACE_CALL(__func__);
4271 
4272  REMMINA_DEBUG("Connect signal emitted");
4273 
4274  /* This signal handler is called by a plugin when it’s correctly connected
4275  * (and authenticated) */
4276 
4277  cnnobj->connected = TRUE;
4278 
4279  remmina_protocol_widget_set_hostkey_func(REMMINA_PROTOCOL_WIDGET(cnnobj->proto),
4280  (RemminaHostkeyFunc)rcw_hostkey_func);
4281 
4285  if (remmina_file_get_filename(cnnobj->remmina_file) == NULL)
4287  remmina_file_get_string(cnnobj->remmina_file, "server"));
4288  REMMINA_DEBUG("We save the last successful connection date");
4289  //remmina_file_set_string(cnnobj->remmina_file, "last_success", last_success);
4291  //REMMINA_DEBUG("Last connection made on %s.", last_success);
4292 
4293  REMMINA_DEBUG("Saving credentials");
4294  /* Save credentials */
4296 
4297  if (cnnobj->cnnwin->priv->floating_toolbar_widget)
4298  gtk_widget_show(cnnobj->cnnwin->priv->floating_toolbar_widget);
4299 
4300  rco_update_toolbar(cnnobj);
4301 
4302  REMMINA_DEBUG("Trying to present the window");
4303  /* Try to present window */
4304  cnnobj->cnnwin->priv->dwp_eventsourceid = g_timeout_add(200, rcw_delayed_window_present, (gpointer)cnnobj->cnnwin);
4305 }
4306 
4307 static void cb_lasterror_confirmed(void *cbdata, int btn)
4308 {
4309  TRACE_CALL(__func__);
4311 }
4312 
4314 {
4315  TRACE_CALL(__func__);
4316  RemminaConnectionObject *cnnobj = gp->cnnobj;
4317  RemminaConnectionWindowPriv *priv = cnnobj->cnnwin->priv;
4318  GtkWidget *pparent;
4319 
4320  REMMINA_DEBUG("Disconnect signal received on RemminaProtocolWidget");
4321  /* Detach the protocol widget from the notebook now, or we risk that a
4322  * window delete will destroy cnnobj->proto before we complete disconnection.
4323  */
4324  pparent = gtk_widget_get_parent(cnnobj->proto);
4325  if (pparent != NULL) {
4326  g_object_ref(cnnobj->proto);
4327  gtk_container_remove(GTK_CONTAINER(pparent), cnnobj->proto);
4328  }
4329 
4330  cnnobj->connected = FALSE;
4331 
4332  if (remmina_pref.save_view_mode) {
4333  if (cnnobj->cnnwin)
4334  remmina_file_set_int(cnnobj->remmina_file, "viewmode", cnnobj->cnnwin->priv->view_mode);
4336  }
4337 
4338  rcw_kp_ungrab(cnnobj->cnnwin);
4339  gtk_toggle_tool_button_set_active(
4340  GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_grab),
4341  FALSE);
4342 
4344  /* We cannot close window immediately, but we must show a message panel */
4345  RemminaMessagePanel *mp;
4346  /* Destroy scrolled_container (and viewport) and all its children the plugin created
4347  * on it, so they will not receive GUI signals */
4348  if (cnnobj->scrolled_container) {
4349  gtk_widget_destroy(cnnobj->scrolled_container);
4350  cnnobj->scrolled_container = NULL;
4351  }
4352  cnnobj->viewport = NULL;
4355  rco_show_message_panel(gp->cnnobj, mp);
4356  REMMINA_DEBUG("Could not disconnect");
4357  } else {
4358  rco_closewin(gp);
4359  REMMINA_DEBUG("Disconnected");
4360  }
4361 }
4362 
4364 {
4365  TRACE_CALL(__func__);
4366  RemminaConnectionObject *cnnobj = gp->cnnobj;
4367 
4368  if (cnnobj && cnnobj->cnnwin && cnnobj->cnnwin->priv->view_mode != SCROLLED_WINDOW_MODE)
4369  rco_check_resize(cnnobj);
4370 }
4371 
4373 {
4374  TRACE_CALL(__func__);
4375  RemminaConnectionObject *cnnobj = gp->cnnobj;
4376 
4378 }
4379 
4381 {
4382  TRACE_CALL(__func__);
4383  RemminaConnectionObject *cnnobj = gp->cnnobj;
4384 
4385  cnnobj->dynres_unlocked = FALSE;
4386  rco_update_toolbar(cnnobj);
4387 }
4388 
4390 {
4391  TRACE_CALL(__func__);
4392  RemminaConnectionObject *cnnobj = gp->cnnobj;
4393 
4394  cnnobj->dynres_unlocked = TRUE;
4395  rco_update_toolbar(cnnobj);
4396 }
4397 
4398 gboolean rcw_open_from_filename(const gchar *filename)
4399 {
4400  TRACE_CALL(__func__);
4401  RemminaFile *remminafile;
4402  GtkWidget *dialog;
4403 
4404  remminafile = remmina_file_manager_load_file(filename);
4405  if (remminafile) {
4406  if (remmina_file_get_int (remminafile, "profile-lock", FALSE)
4408  return FALSE;
4409  rcw_open_from_file(remminafile);
4410  return TRUE;
4411  } else {
4412  dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
4413  _("The file “%s” is corrupted, unreadable, or could not be found."), filename);
4414  g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(gtk_widget_destroy), NULL);
4415  gtk_widget_show(dialog);
4417  return FALSE;
4418  }
4419 }
4420 
4421 static gboolean open_connection_last_stage(gpointer user_data)
4422 {
4423  RemminaProtocolWidget *gp = (RemminaProtocolWidget *)user_data;
4424 
4425  /* Now we have an allocated size for our RemminaProtocolWidget. We can proceed with the connection */
4428  rco_check_resize(gp->cnnobj);
4429 
4430  return FALSE;
4431 }
4432 
4433 static void rpw_size_allocated_on_connection(GtkWidget *w, GdkRectangle *allocation, gpointer user_data)
4434 {
4436 
4437  /* Disconnect signal handler to avoid to be called again after a normal resize */
4438  g_signal_handler_disconnect(w, gp->cnnobj->deferred_open_size_allocate_handler);
4439 
4440  /* Allow extra 100 ms for size allocation (do we really need it?) */
4441  g_timeout_add(100, open_connection_last_stage, gp);
4442 
4443  return;
4444 }
4445 
4447 {
4448  TRACE_CALL(__func__);
4449  rcw_open_from_file_full(remminafile, NULL, NULL, NULL);
4450 }
4451 
4452 static void set_label_selectable(gpointer data, gpointer user_data)
4453 {
4454  TRACE_CALL(__func__);
4455  GtkWidget *widget = GTK_WIDGET(data);
4456 
4457  if (GTK_IS_LABEL(widget))
4458  gtk_label_set_selectable(GTK_LABEL(widget), TRUE);
4459 }
4460 
4468 };
4469 
4474 static void rcw_gtksocket_not_available_dialog_response(GtkDialog * self,
4475  gint response_id,
4476  RemminaConnectionObject * cnnobj)
4477 {
4478  TRACE_CALL(__func__);
4479 
4480  GError *error = NULL;
4481 
4482  if (response_id == GTKSOCKET_NOT_AVAIL_RESPONSE_OPEN_BROWSER) {
4483  gtk_show_uri_on_window(
4484  NULL,
4485  // TRANSLATORS: This should be a link to the Remmina wiki page:
4486  // TRANSLATORS: 'GtkSocket feature is not available'.
4487  _("https://gitlab.com/Remmina/Remmina/-/wikis/GtkSocket-feature-is-not-available-in-a-Wayland-session"),
4488  GDK_CURRENT_TIME, &error
4489  );
4490  }
4491 
4492  // Close the current page since it's useless without GtkSocket.
4493  // The user would need to manually click the close button.
4494  if (cnnobj) rco_disconnect_current_page(cnnobj);
4495 
4496  gtk_widget_destroy(GTK_WIDGET(self));
4497 }
4498 
4499 GtkWidget *rcw_open_from_file_full(RemminaFile *remminafile, GCallback disconnect_cb, gpointer data, guint *handler)
4500 {
4501  TRACE_CALL(__func__);
4502  RemminaConnectionObject *cnnobj;
4503 
4504  gint ret;
4505  GtkWidget *dialog;
4506  GtkWidget *newpage;
4507  gint width, height;
4508  gboolean maximize;
4509  gint view_mode;
4510  const gchar *msg;
4511  RemminaScaleMode scalemode;
4512 
4513  if (disconnect_cb) {
4514  g_print("disconnect_cb is deprecated inside rcw_open_from_file_full() and should be null\n");
4515  return NULL;
4516  }
4517 
4518 
4519  /* Create the RemminaConnectionObject */
4520  cnnobj = g_new0(RemminaConnectionObject, 1);
4521  cnnobj->remmina_file = remminafile;
4522 
4523  /* Create the RemminaProtocolWidget */
4524  cnnobj->proto = remmina_protocol_widget_new();
4525  remmina_protocol_widget_setup((RemminaProtocolWidget *)cnnobj->proto, remminafile, cnnobj);
4527  GtkWindow *wparent;
4528  wparent = remmina_main_get_window();
4530  dialog = gtk_message_dialog_new(wparent, GTK_DIALOG_DESTROY_WITH_PARENT,
4531  GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", msg);
4532  gtk_dialog_run(GTK_DIALOG(dialog));
4533  gtk_widget_destroy(dialog);
4534  /* We should destroy cnnobj->proto and cnnobj now… TODO: Fix this leak */
4535  return NULL;
4536  }
4537 
4538 
4539 
4540  /* Set a name for the widget, for CSS selector */
4541  gtk_widget_set_name(GTK_WIDGET(cnnobj->proto), "remmina-protocol-widget");
4542 
4543  gtk_widget_set_halign(GTK_WIDGET(cnnobj->proto), GTK_ALIGN_FILL);
4544  gtk_widget_set_valign(GTK_WIDGET(cnnobj->proto), GTK_ALIGN_FILL);
4545 
4546  if (data)
4547  g_object_set_data(G_OBJECT(cnnobj->proto), "user-data", data);
4548 
4549  view_mode = remmina_file_get_int(cnnobj->remmina_file, "viewmode", 0);
4550  if (kioskmode)
4551  view_mode = VIEWPORT_FULLSCREEN_MODE;
4552  gint ismultimon = remmina_file_get_int(cnnobj->remmina_file, "multimon", 0);
4553 
4554  if (ismultimon)
4555  view_mode = VIEWPORT_FULLSCREEN_MODE;
4556 
4557  if (fullscreen)
4558  view_mode = VIEWPORT_FULLSCREEN_MODE;
4559 
4560  /* Create the viewport to make the RemminaProtocolWidget scrollable */
4561  cnnobj->viewport = gtk_viewport_new(NULL, NULL);
4562  gtk_widget_set_name(cnnobj->viewport, "remmina-cw-viewport");
4563  gtk_widget_show(cnnobj->viewport);
4564  gtk_container_set_border_width(GTK_CONTAINER(cnnobj->viewport), 0);
4565  gtk_viewport_set_shadow_type(GTK_VIEWPORT(cnnobj->viewport), GTK_SHADOW_NONE);
4566 
4567  /* Create the scrolled container */
4568  scalemode = get_current_allowed_scale_mode(cnnobj, NULL, NULL);
4569  cnnobj->scrolled_container = rco_create_scrolled_container(scalemode, view_mode);
4570 
4571  gtk_container_add(GTK_CONTAINER(cnnobj->scrolled_container), cnnobj->viewport);
4572 
4573  /* Determine whether the plugin can scale or not. If the plugin can scale and we do
4574  * not want to expand, then we add a GtkAspectFrame to maintain aspect ratio during scaling */
4576  remmina_file_get_string(remminafile, "protocol"),
4578 
4579  cnnobj->aspectframe = NULL;
4580  gtk_container_add(GTK_CONTAINER(cnnobj->viewport), cnnobj->proto);
4581 
4582  /* Determine whether this connection will be put on a new window
4583  * or in an existing one */
4584  cnnobj->cnnwin = rcw_find(remminafile);
4585  if (!cnnobj->cnnwin) {
4586  /* Connection goes on a new toplevel window */
4587  switch (view_mode) {
4590  cnnobj->cnnwin = rcw_create_fullscreen(NULL, view_mode);
4591  break;
4592  case SCROLLED_WINDOW_MODE:
4593  default:
4594  width = remmina_file_get_int(cnnobj->remmina_file, "window_width", 640);
4595  height = remmina_file_get_int(cnnobj->remmina_file, "window_height", 480);
4596  maximize = remmina_file_get_int(cnnobj->remmina_file, "window_maximize", FALSE) ? TRUE : FALSE;
4597  cnnobj->cnnwin = rcw_create_scrolled(width, height, maximize);
4598  break;
4599  }
4600  rcw_update_tag(cnnobj->cnnwin, cnnobj);
4601  rcw_append_new_page(cnnobj->cnnwin, cnnobj);
4602  } else {
4603  newpage = rcw_append_new_page(cnnobj->cnnwin, cnnobj);
4604  gtk_window_present(GTK_WINDOW(cnnobj->cnnwin));
4605  nb_set_current_page(cnnobj->cnnwin->priv->notebook, newpage);
4606  }
4607 
4608  // Do not call remmina_protocol_widget_update_alignment(cnnobj); here or cnnobj->proto will not fill its parent size
4609  // and remmina_protocol_widget_update_remote_resolution() cannot autodetect available space
4610 
4611  gtk_widget_show(cnnobj->proto);
4612  g_signal_connect(G_OBJECT(cnnobj->proto), "connect", G_CALLBACK(rco_on_connect), cnnobj);
4613  g_signal_connect(G_OBJECT(cnnobj->proto), "disconnect", G_CALLBACK(rco_on_disconnect), NULL);
4614  g_signal_connect(G_OBJECT(cnnobj->proto), "desktop-resize", G_CALLBACK(rco_on_desktop_resize), NULL);
4615  g_signal_connect(G_OBJECT(cnnobj->proto), "update-align", G_CALLBACK(rco_on_update_align), NULL);
4616  g_signal_connect(G_OBJECT(cnnobj->proto), "lock-dynres", G_CALLBACK(rco_on_lock_dynres), NULL);
4617  g_signal_connect(G_OBJECT(cnnobj->proto), "unlock-dynres", G_CALLBACK(rco_on_unlock_dynres), NULL);
4618  g_signal_connect(G_OBJECT(cnnobj->proto), "enter-notify-event", G_CALLBACK(rco_enter_protocol_widget), cnnobj);
4619  g_signal_connect(G_OBJECT(cnnobj->proto), "leave-notify-event", G_CALLBACK(rco_leave_protocol_widget), cnnobj);
4620 
4621  if (!remmina_pref.save_view_mode)
4622  remmina_file_set_int(cnnobj->remmina_file, "viewmode", remmina_pref.default_mode);
4623 
4624 
4625  /* If it is a GtkSocket plugin and X11 is not available, we inform the
4626  * user and close the connection */
4628  remmina_file_get_string(remminafile, "protocol"),
4630  if (ret && !remmina_gtksocket_available()) {
4631  gchar *title = _("Warning: This plugin requires GtkSocket, but this "
4632  "feature is unavailable in a Wayland session.");
4633 
4634  gchar *err_msg =
4635  // TRANSLATORS: This should be a link to the Remmina wiki page:
4636  // 'GtkSocket feature is not available'.
4637  _("Plugins relying on GtkSocket can't run in a "
4638  "Wayland session.\nFor more info and a possible "
4639  "workaround, please visit the Remmina wiki at:\n\n"
4640  "https://gitlab.com/Remmina/Remmina/-/wikis/GtkSocket-feature-is-not-available-in-a-Wayland-session");
4641 
4642  dialog = gtk_message_dialog_new(
4643  GTK_WINDOW(cnnobj->cnnwin),
4644  GTK_DIALOG_MODAL,
4645  GTK_MESSAGE_WARNING,
4646  GTK_BUTTONS_OK,
4647  "%s", title);
4648 
4649  gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "%s",
4650  err_msg);
4651  gtk_dialog_add_button(GTK_DIALOG(dialog), _("Open in web browser"),
4653 
4654  REMMINA_CRITICAL(g_strdup_printf("%s\n%s", title, err_msg));
4655 
4656  g_signal_connect(G_OBJECT(dialog),
4657  "response",
4659  cnnobj);
4660 
4661  // Make Text selectable. Usefull because of the link in the text.
4662  GtkWidget *area = gtk_message_dialog_get_message_area(
4663  GTK_MESSAGE_DIALOG(dialog));
4664  GtkContainer *box = (GtkContainer *)area;
4665 
4666  GList *children = gtk_container_get_children(box);
4667  g_list_foreach(children, set_label_selectable, NULL);
4668  g_list_free(children);
4669 
4670  gtk_widget_show(dialog);
4671 
4672  return NULL; /* Should we destroy something before returning? */
4673  }
4674 
4675  if (cnnobj->cnnwin->priv->floating_toolbar_widget)
4676  gtk_widget_show(cnnobj->cnnwin->priv->floating_toolbar_widget);
4677 
4679  printf("OK, an error occurred in initializing the protocol plugin before connecting. The error is %s.\n"
4680  "TODO: Put this string as an error to show", remmina_protocol_widget_get_error_message((RemminaProtocolWidget *)cnnobj->proto));
4681  return cnnobj->proto;
4682  }
4683 
4684 
4685  /* GTK window setup is done here, and we are almost ready to call remmina_protocol_widget_open_connection().
4686  * But size has not yet been allocated by GTK
4687  * to the widgets. If we are in RES_USE_INITIAL_WINDOW_SIZE resolution mode or scale is REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES,
4688  * we should wait for a size allocation from GTK for cnnobj->proto
4689  * before connecting */
4690 
4691  cnnobj->deferred_open_size_allocate_handler = g_signal_connect(G_OBJECT(cnnobj->proto), "size-allocate", G_CALLBACK(rpw_size_allocated_on_connection), NULL);
4692 
4693  return cnnobj->proto;
4694 }
4695 
4697 {
4698  return &cnnobj->cnnwin->window;
4699 }
4701 {
4702  return cnnobj->viewport;
4703 }
4704 
4706 {
4707  TRACE_CALL(__func__);
4708  cnnwin->priv->on_delete_confirm_mode = mode;
4709 }
4710 
4715 void rco_destroy_message_panel(RemminaConnectionObject *cnnobj, RemminaMessagePanel *mp)
4716 {
4717  TRACE_CALL(__func__);
4718  GList *childs, *cc;
4719  RemminaMessagePanel *lastPanel;
4720  gboolean was_visible;
4721  GtkWidget *page;
4722 
4723  page = nb_find_page_by_cnnobj(cnnobj->cnnwin->priv->notebook, cnnobj);
4724  childs = gtk_container_get_children(GTK_CONTAINER(page));
4725  cc = g_list_first(childs);
4726  while (cc != NULL) {
4727  if ((RemminaMessagePanel *)cc->data == mp)
4728  break;
4729  cc = g_list_next(cc);
4730  }
4731  g_list_free(childs);
4732 
4733  if (cc == NULL) {
4734  printf("Remmina: Warning. There was a request to destroy a RemminaMessagePanel that is not on the page\n");
4735  return;
4736  }
4737  was_visible = gtk_widget_is_visible(GTK_WIDGET(mp));
4738  gtk_widget_destroy(GTK_WIDGET(mp));
4739 
4740  /* And now, show the last remaining message panel, if needed */
4741  if (was_visible) {
4742  childs = gtk_container_get_children(GTK_CONTAINER(page));
4743  cc = g_list_first(childs);
4744  lastPanel = NULL;
4745  while (cc != NULL) {
4746  if (G_TYPE_CHECK_INSTANCE_TYPE(cc->data, REMMINA_TYPE_MESSAGE_PANEL))
4747  lastPanel = (RemminaMessagePanel *)cc->data;
4748  cc = g_list_next(cc);
4749  }
4750  g_list_free(childs);
4751  if (lastPanel)
4752  gtk_widget_show(GTK_WIDGET(lastPanel));
4753  }
4754 }
4755 
4762 void rco_show_message_panel(RemminaConnectionObject *cnnobj, RemminaMessagePanel *mp)
4763 {
4764  TRACE_CALL(__func__);
4765  GList *childs, *cc;
4766  GtkWidget *page;
4767 
4768  /* Hides all RemminaMessagePanels childs of cnnobj->page */
4769  page = nb_find_page_by_cnnobj(cnnobj->cnnwin->priv->notebook, cnnobj);
4770  childs = gtk_container_get_children(GTK_CONTAINER(page));
4771  cc = g_list_first(childs);
4772  while (cc != NULL) {
4773  if (G_TYPE_CHECK_INSTANCE_TYPE(cc->data, REMMINA_TYPE_MESSAGE_PANEL))
4774  gtk_widget_hide(GTK_WIDGET(cc->data));
4775  cc = g_list_next(cc);
4776  }
4777  g_list_free(childs);
4778 
4779  /* Add the new message panel at the top of cnnobj->page */
4780  gtk_box_pack_start(GTK_BOX(page), GTK_WIDGET(mp), FALSE, FALSE, 0);
4781  gtk_box_reorder_child(GTK_BOX(page), GTK_WIDGET(mp), 0);
4782 
4783  /* Show the message panel */
4784  gtk_widget_show_all(GTK_WIDGET(mp));
4785 
4786  /* Focus the correct field of the RemminaMessagePanel */
4788 }
static RemminaConnectionWindow * rcw_find(RemminaFile *remminafile)
Definition: rcw.c:4234
gboolean remmina_protocol_widget_get_expand(RemminaProtocolWidget *gp)
guint shortcutkey_fullscreen
Definition: remmina_pref.h:175
static void rco_update_toolbar(RemminaConnectionObject *cnnobj)
Definition: rcw.c:2628
const gchar * remmina_protocol_widget_get_error_message(RemminaProtocolWidget *gp)
gulong deferred_open_size_allocate_handler
Definition: rcw.c:177
gint floating_toolbar_placement
Definition: remmina_pref.h:224
static gboolean rcw_after_configure_scrolled(gpointer user_data)
Definition: rcw.c:3049
static void rcw_focus_in(RemminaConnectionWindow *cnnwin)
Definition: rcw.c:2941
static void rcw_init(RemminaConnectionWindow *cnnwin)
Definition: rcw.c:3216
static RemminaScaleMode get_current_allowed_scale_mode(RemminaConnectionObject *cnnobj, gboolean *dynres_avail, gboolean *scale_avail)
Definition: rcw.c:391
gboolean remmina_protocol_widget_query_feature_by_type(RemminaProtocolWidget *gp, RemminaProtocolFeatureType type)
const RemminaProtocolFeature * remmina_protocol_widget_get_features(RemminaProtocolWidget *gp)
guint shortcutkey_minimize
Definition: remmina_pref.h:185
const gchar * remmina_file_get_string(RemminaFile *remminafile, const gchar *setting)
Definition: remmina_file.c:516
static void rcw_update_tag(RemminaConnectionWindow *cnnwin, RemminaConnectionObject *cnnobj)
Definition: rcw.c:3422
GtkWidget * remmina_widget_pool_find_by_window(GType type, GdkWindow *window)
const gchar * grab_color
Definition: remmina_pref.h:159
static void rcw_tb_drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer user_data)
Definition: rcw.c:828
gboolean remmina_protocol_widget_query_feature_by_ref(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
static void rcw_toolbar_multi_monitor_mode(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
Definition: rcw.c:1722
static void rco_call_protocol_feature_check(GtkMenuItem *menuitem, RemminaConnectionObject *cnnobj)
Definition: rcw.c:1816
gchar * remmina_pref_file
Definition: rcw.c:78
static void rcw_toolbar_fullscreen(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
Definition: rcw.c:1351
static void rcw_on_switch_page(GtkNotebook *notebook, GtkWidget *newpage, guint page_num, RemminaConnectionWindow *cnnwin)
Definition: rcw.c:3653
GtkWindow * remmina_main_get_window()
gint remmina_protocol_widget_get_width(RemminaProtocolWidget *gp)
static gboolean rcw_hostkey_func(RemminaProtocolWidget *gp, guint keyval, gboolean release)
Definition: rcw.c:4053
void remmina_scrolled_viewport_remove_motion(RemminaScrolledViewport *gsv)
gint remmina_protocol_widget_get_profile_remote_width(RemminaProtocolWidget *gp)
static void rcw_toolbar_preferences(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
Definition: rcw.c:1895
static void nb_set_current_page(GtkNotebook *notebook, GtkWidget *page)
Definition: rcw.c:1221
gchar * keystrokes
Definition: remmina_pref.h:146
static gboolean rco_leave_protocol_widget(GtkWidget *widget, GdkEventCrossing *event, RemminaConnectionObject *cnnobj)
Definition: rcw.c:2859
const gchar * remmina_file_get_filename(RemminaFile *remminafile)
Definition: remmina_file.c:210
guint shortcutkey_dynres
Definition: remmina_pref.h:179
void remmina_public_send_notification(const gchar *notification_id, const gchar *notification_title, const gchar *notification_message)
void rco_on_disconnect(RemminaProtocolWidget *gp, gpointer data)
Definition: rcw.c:4313
guint shortcutkey_screenshot
Definition: remmina_pref.h:184
typedefG_BEGIN_DECLS struct _RemminaFile RemminaFile
Definition: types.h:44
guint shortcutkey_prevtab
Definition: remmina_pref.h:177
static RemminaConnectionWindow * rcw_create_scrolled(gint width, gint height, gboolean maximize)
Definition: rcw.c:3766
gboolean remmina_protocol_widget_has_error(RemminaProtocolWidget *gp)
static void rcw_toolbar_dynres(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
Definition: rcw.c:1678
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
static void rcw_toolbar_grab(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
Definition: rcw.c:2297
gchar * remmina_protocol_widget_get_domain(RemminaProtocolWidget *gp)
gboolean plugin_can_scale
Definition: rcw.c:172
GtkWidget * rcw_open_from_file_full(RemminaFile *remminafile, GCallback disconnect_cb, gpointer data, guint *handler)
Definition: rcw.c:4499
void remmina_widget_pool_register(GtkWidget *widget)
static gboolean rcw_ftb_drag_drop(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, RemminaConnectionWindow *cnnwin)
Definition: rcw.c:3932
static void rcw_focus_out(RemminaConnectionWindow *cnnwin)
Definition: rcw.c:2967
static void rcw_toolbar_pin(GtkWidget *widget, RemminaConnectionWindow *cnnwin)
Definition: rcw.c:3129
static void rcw_destroy(GtkWidget *widget, gpointer data)
Definition: rcw.c:701
static void rcw_toolbar_menu_on_launch_item(RemminaAppletMenu *menu, RemminaAppletMenuItem *menuitem, gpointer data)
Definition: rcw.c:1958
static void rcw_create_floating_toolbar(RemminaConnectionWindow *cnnwin, gint mode)
Definition: rcw.c:3137
void rcw_toolbar_preferences_radio(RemminaConnectionObject *cnnobj, RemminaFile *remminafile, GtkWidget *menu, const RemminaProtocolFeature *feature, const gchar *domain, gboolean enabled)
Definition: rcw.c:1837
void remmina_protocol_widget_setup(RemminaProtocolWidget *gp, RemminaFile *remminafile, RemminaConnectionObject *cnnobj)
static void rcw_create_overlay_ftb_overlay(RemminaConnectionWindow *cnnwin)
Definition: rcw.c:3838
gboolean rcw_delete(RemminaConnectionWindow *cnnwin)
Definition: rcw.c:648
void remmina_message_panel_focus_auth_entry(RemminaMessagePanel *mp)
static void rcw_scaler_keep_aspect(GtkWidget *widget, RemminaConnectionWindow *cnnwin)
Definition: rcw.c:1494
static void set_label_selectable(gpointer data, gpointer user_data)
Definition: rcw.c:4452
static gboolean rcw_tb_drag_drop(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, gpointer user_data)
Definition: rcw.c:786
static void rcw_scaler_expand(GtkWidget *widget, RemminaConnectionWindow *cnnwin)
Definition: rcw.c:1480
static void rco_call_protocol_feature_radio(GtkMenuItem *menuitem, RemminaConnectionObject *cnnobj)
Definition: rcw.c:1801
GtkWidget * viewport
Definition: rcw.c:168
static void rcw_place_toolbar(GtkToolbar *toolbar, GtkGrid *grid, GtkWidget *sibling, int toolbar_placement)
Definition: rcw.c:2594
guint shortcutkey_autofit
Definition: remmina_pref.h:176
static void rcw_toolbar_preferences_popdown(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
Definition: rcw.c:1758
void remmina_protocol_widget_call_feature_by_type(RemminaProtocolWidget *gp, RemminaProtocolFeatureType type, gint id)
static GtkWidget * rco_create_tab_label(RemminaConnectionObject *cnnobj)
Definition: rcw.c:3519
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)
static void nb_migrate_message_panels(GtkWidget *frompage, GtkWidget *topage)
Definition: rcw.c:1234
static void rco_viewport_fullscreen_mode(GtkWidget *widget, RemminaConnectionObject *cnnobj)
Definition: rcw.c:1387
gint remmina_protocol_widget_get_height(RemminaProtocolWidget *gp)
RemminaScaleMode remmina_protocol_widget_get_current_scale_mode(RemminaProtocolWidget *gp)
static void rcw_update_pin(RemminaConnectionWindow *cnnwin)
Definition: rcw.c:3118
void rcw_toolbar_fullscreen_option(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
Definition: rcw.c:1421
void remmina_protocol_widget_set_current_scale_mode(RemminaProtocolWidget *gp, RemminaScaleMode scalemode)
void rco_on_connect(RemminaProtocolWidget *gp, RemminaConnectionObject *cnnobj)
Definition: rcw.c:4268
GtkWidget * proto
Definition: rcw.c:166
static GtkWidget * nb_find_page_by_cnnobj(GtkNotebook *notebook, RemminaConnectionObject *cnnobj)
Definition: rcw.c:3452
static gboolean rcw_keyboard_grab_retry(gpointer user_data)
Definition: rcw.c:484
void remmina_pref_add_recent(const gchar *protocol, const gchar *server)
Definition: remmina_pref.c:936
static gboolean rcw_floating_toolbar_hide(RemminaConnectionWindow *cnnwin)
Definition: rcw.c:2991
static gboolean rcw_floating_toolbar_on_enter(GtkWidget *widget, GdkEventCrossing *event, RemminaConnectionWindow *cnnwin)
Definition: rcw.c:2791
static const GtkTargetEntry dnd_targets_tb[]
Definition: rcw.c:212
static void cb_lasterror_confirmed(void *cbdata, int btn)
Definition: rcw.c:4307
static void rcw_set_toolbar_visibility(RemminaConnectionWindow *cnnwin)
Definition: rcw.c:2748
RemminaAppletMenuItemType item_type
void rco_switch_page_activate(GtkMenuItem *menuitem, RemminaConnectionObject *cnnobj)
Definition: rcw.c:1561
static void rcw_migrate(RemminaConnectionWindow *from, RemminaConnectionWindow *to)
Definition: rcw.c:1254
static void rco_get_desktop_size(RemminaConnectionObject *cnnobj, gint *width, gint *height)
Definition: rcw.c:906
gchar * remmina_pref_get_value(const gchar *key)
static RemminaConnectionObject * rcw_get_visible_cnnobj(RemminaConnectionWindow *cnnwin)
Definition: rcw.c:377
static gboolean rcw_focus_in_event(GtkWidget *widget, GdkEventWindowState *event, gpointer user_data)
Definition: rcw.c:3240
void remmina_protocol_widget_open_connection(RemminaProtocolWidget *gp)
void rcw_toolbar_menu_popdown(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
Definition: rcw.c:1773
GtkWidget * remmina_applet_menu_new(void)
RemminaConnectionWindowOnDeleteConfirmMode
Definition: rcw.h:66
static void rco_check_resize(RemminaConnectionObject *cnnobj)
Definition: rcw.c:1060
struct _RemminaConnectionWindowPriv RemminaConnectionWindowPriv
Definition: rcw.h:52
gboolean remmina_protocol_widget_unmap_event(RemminaProtocolWidget *gp)
G_DEFINE_TYPE(RemminaConnectionWindow, rcw, GTK_TYPE_WINDOW)
Definition: rcw.c:81
static gboolean rcw_map_event_fullscreen(GtkWidget *widget, GdkEvent *event, gpointer data)
Definition: rcw.c:3320
void remmina_protocol_widget_send_keystrokes(RemminaProtocolWidget *gp, GtkMenuItem *widget)
Send to the plugin some keystrokes.
static void remmina_protocol_widget_update_alignment(RemminaConnectionObject *cnnobj)
Definition: rcw.c:1148
GtkWidget * remmina_scrolled_viewport_new(void)
static gboolean rcw_on_switch_page_finalsel(gpointer user_data)
Definition: rcw.c:3624
void remmina_file_state_last_success(RemminaFile *remminafile)
Definition: remmina_file.c:948
static void print_crossing_event(GdkEventCrossing *event)
Definition: rcw.c:2762
static void rcw_toolbar_autofit(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
Definition: rcw.c:988
General utility functions, non-GTK related.
void rcw_toolbar_tools_popdown(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
Definition: rcw.c:1787
guint shortcutkey_multimon
Definition: remmina_pref.h:181
static void rcw_ftb_drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer user_data)
Definition: rcw.c:3961
const gchar * screenshot_path
Definition: remmina_pref.h:137
GtkWindow * rcw_get_gtkwindow(RemminaConnectionObject *cnnobj)
Definition: rcw.c:4696
GtkWidget * remmina_protocol_widget_new(void)
static const GtkTargetEntry dnd_targets_ftb[]
Definition: rcw.c:203
gboolean confirm_close
Definition: remmina_pref.h:147
static void rpw_size_allocated_on_connection(GtkWidget *w, GdkRectangle *allocation, gpointer user_data)
Definition: rcw.c:4433
gint toolbar_placement
Definition: remmina_pref.h:225
RemminaProtocolFeatureType type
Definition: types.h:73
gboolean rcw_toolbar_autofit_restore(RemminaConnectionWindow *cnnwin)
Definition: rcw.c:950
gboolean remmina_protocol_widget_map_event(RemminaProtocolWidget *gp)
GtkWidget * aspectframe
Definition: rcw.c:167
static void rcw_toolbar_tools(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
Definition: rcw.c:2015
static gboolean rcw_on_enter_notify_event(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition: rcw.c:2809
void remmina_applet_menu_set_hide_count(RemminaAppletMenu *menu, gboolean hide_count)
gboolean remmina_plugin_manager_query_feature_by_type(RemminaPluginType ptype, const gchar *name, RemminaProtocolFeatureType ftype)
static void rcw_on_page_removed(GtkNotebook *notebook, GtkWidget *child, guint page_num, RemminaConnectionWindow *cnnwin)
Definition: rcw.c:3673
static RemminaConnectionWindow * rcw_new(gboolean fullscreen, int full_screen_target_monitor)
Definition: rcw.c:3380
static gboolean rcw_on_leave_notify_event(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition: rcw.c:2822
void rcw_set_delete_confirm_mode(RemminaConnectionWindow *cnnwin, RemminaConnectionWindowOnDeleteConfirmMode mode)
Definition: rcw.c:4705
void rco_on_update_align(RemminaProtocolWidget *gp, gpointer data)
Definition: rcw.c:4372
gboolean rcw_notify_widget_toolbar_placement(GtkWidget *widget, gpointer data)
Definition: rcw.c:756
static void rco_scrolled_fullscreen_mode(GtkWidget *widget, RemminaConnectionObject *cnnobj)
Definition: rcw.c:1399
static void rco_disconnect_current_page(RemminaConnectionObject *cnnobj)
Definition: rcw.c:421
void remmina_file_set_int(RemminaFile *remminafile, const gchar *setting, gint value)
Definition: remmina_file.c:585
guint shortcutkey_disconnect
Definition: remmina_pref.h:186
void remmina_exec_command(RemminaCommandType command, const gchar *data)
Definition: remmina_exec.c:368
gboolean remmina_gtksocket_available()
gboolean small_toolbutton
Definition: remmina_pref.h:212
guint shortcutkey_grab
Definition: remmina_pref.h:182
gboolean(* RemminaHostkeyFunc)(RemminaProtocolWidget *gp, guint keyval, gboolean release)
gboolean always_show_tab
Definition: remmina_pref.h:152
gboolean dynres_unlocked
Definition: rcw.c:175
gboolean toolbar_pin_down
Definition: remmina_pref.h:223
static void rcw_toolbar_switch_page(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
Definition: rcw.c:1582
guint shortcutkey_scale
Definition: remmina_pref.h:180
static void rcw_toolbar_place_signal(RemminaConnectionWindow *cnnwin, gpointer data)
Definition: rcw.c:3200
void remmina_protocol_widget_send_clipboard(RemminaProtocolWidget *gp, GtkMenuItem *widget)
static GtkNotebook * rcw_on_notebook_create_window(GtkNotebook *notebook, GtkWidget *page, gint x, gint y, gpointer data)
Definition: rcw.c:3684
struct _RemminaConnectionObject RemminaConnectionObject
static void rcw_toolbar_minimize(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
Definition: rcw.c:2275
RemminaConnectionObject * cnnobj
static void rcw_pointer_grab(RemminaConnectionWindow *cnnwin)
Definition: rcw.c:512
static void rco_call_protocol_feature_activate(GtkMenuItem *menuitem, RemminaConnectionObject *cnnobj)
Definition: rcw.c:1828
static gboolean rcw_unmap_event(GtkWidget *widget, GdkEvent *event, gpointer data)
Definition: rcw.c:3302
static void rcw_toolbar_menu(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
Definition: rcw.c:1978
static guint rcw_signals[LAST_SIGNAL]
Definition: rcw.c:185
void remmina_protocol_widget_update_remote_resolution(RemminaProtocolWidget *gp)
gboolean fullscreen
Definition: remmina.c:89
void remmina_protocol_widget_close_connection(RemminaProtocolWidget *gp)
void remmina_public_popup_position(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer user_data)
gboolean remmina_protocol_widget_plugin_screenshot(RemminaProtocolWidget *gp, RemminaPluginScreenshotData *rpsd)
void remmina_applet_menu_populate(RemminaAppletMenu *menu)
gboolean remmina_pref_get_boolean(const gchar *key)
GTKSOCKET_NOT_AVAIL_RESPONSE_TYPE
These define the response id&#39;s of the gtksocket-is-not-available-warning-dialog buttons.
Definition: rcw.c:4465
static void rcw_kp_ungrab(RemminaConnectionWindow *cnnwin)
Definition: rcw.c:429
void remmina_protocol_widget_grab_focus(RemminaProtocolWidget *gp)
static void rcw_toolbar_scaled_mode(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
Definition: rcw.c:1701
void rco_closewin(RemminaProtocolWidget *gp)
Definition: rcw.c:3473
static gboolean rcw_on_configure(GtkWidget *widget, GdkEventConfigure *event, gpointer data)
Definition: rcw.c:3086
static void rcw_fullscreen_option_popdown(GtkWidget *widget, RemminaConnectionWindow *cnnwin)
Definition: rcw.c:1411
void rcw_toolbar_preferences_check(RemminaConnectionObject *cnnobj, GtkWidget *menu, const RemminaProtocolFeature *feature, const gchar *domain, gboolean enabled)
Definition: rcw.c:1871
static RemminaConnectionWindow * rcw_create_fullscreen(GtkWindow *old, gint view_mode)
Definition: rcw.c:3984
static GtkWidget * rcw_create_toolbar(RemminaConnectionWindow *cnnwin, gint mode)
Definition: rcw.c:2322
static gboolean open_connection_last_stage(gpointer user_data)
Definition: rcw.c:4421
guint shortcutkey_nexttab
Definition: remmina_pref.h:178
static RemminaConnectionObject * rcw_get_cnnobj_at_page(RemminaConnectionWindow *cnnwin, gint npage)
Definition: rcw.c:367
static gboolean rcw_tb_drag_failed(GtkWidget *widget, GdkDragContext *context, GtkDragResult result, gpointer user_data)
Definition: rcw.c:769
gboolean applet_hide_count
Definition: remmina_pref.h:163
gint remmina_protocol_widget_get_multimon(RemminaProtocolWidget *gp)
GtkWidget * rcw_get_gtkviewport(RemminaConnectionObject *cnnobj)
Definition: rcw.c:4700
static gboolean rcw_floating_toolbar_on_leave(GtkWidget *widget, GdkEventCrossing *event, RemminaConnectionWindow *cnnwin)
Definition: rcw.c:2799
static gboolean rcw_state_event(GtkWidget *widget, GdkEventWindowState *event, gpointer user_data)
Definition: rcw.c:3261
static void rcw_class_init(RemminaConnectionWindowClass *klass)
Definition: rcw.c:221
void rco_on_unlock_dynres(RemminaProtocolWidget *gp, gpointer data)
Definition: rcw.c:4389
static void rcw_toolbar_open_main(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
Definition: rcw.c:1748
static GtkNotebook * rcw_create_notebook(RemminaConnectionWindow *cnnwin)
Definition: rcw.c:3746
static void rcw_toolbar_disconnect(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
Definition: rcw.c:2286
RemminaConnectionWindowPriv * priv
Definition: rcw.h:56
gboolean remmina_protocol_widget_is_closed(RemminaProtocolWidget *gp)
void rcw_open_from_file(RemminaFile *remminafile)
Definition: rcw.c:4446
RemminaPref remmina_pref
Definition: rcw.c:79
RemminaScaleMode
Definition: types.h:141
static void rcw_floating_toolbar_show(RemminaConnectionWindow *cnnwin, gboolean show)
Definition: rcw.c:879
GType rcw_get_type(void) G_GNUC_CONST
static GtkWidget * rco_create_scrolled_container(RemminaScaleMode scalemode, int view_mode)
Definition: rcw.c:931
static void rcw_toolbar_scaler_option(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
Definition: rcw.c:1510
gint remmina_file_get_int(RemminaFile *remminafile, const gchar *setting, gint default_value)
Definition: remmina_file.c:603
void rco_on_desktop_resize(RemminaProtocolWidget *gp, gpointer data)
Definition: rcw.c:4363
guint remmina_utils_string_replace_all(GString *haystack, const gchar *needle, const gchar *replace)
Replaces all occurrences of needle in haystack with replace.
static gboolean rcw_focus_out_event(GtkWidget *widget, GdkEventWindowState *event, gpointer user_data)
Definition: rcw.c:3250
static void rcw_pointer_ungrab(RemminaConnectionWindow *cnnwin)
Definition: rcw.c:497
guint shortcutkey_toolbar
Definition: remmina_pref.h:187
GtkWidget * scrolled_container
Definition: rcw.c:170
void remmina_protocol_widget_call_feature_by_ref(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
RemminaFile * remmina_file
Definition: rcw.c:164
static void rcw_gtksocket_not_available_dialog_response(GtkDialog *self, gint response_id, RemminaConnectionObject *cnnobj)
Gets called if the user interacts with the gtksocket-is-not-available-warning-dialog.
Definition: rcw.c:4474
static void rco_change_scalemode(RemminaConnectionObject *cnnobj, gboolean bdyn, gboolean bscale)
Definition: rcw.c:1651
gint remmina_protocol_widget_get_profile_remote_height(RemminaProtocolWidget *gp)
static void rcw_update_notebook(RemminaConnectionWindow *cnnwin)
Definition: rcw.c:3603
gboolean remmina_pref_save(void)
Definition: remmina_pref.c:785
void rcw_toolbar_switch_page_popdown(GtkWidget *widget, RemminaConnectionWindow *cnnwin)
Definition: rcw.c:1571
static gboolean rcw_floating_toolbar_make_invisible(gpointer data)
Definition: rcw.c:869
const gchar * screenshot_name
Definition: remmina_pref.h:139
static gboolean rcw_floating_toolbar_on_scroll(GtkWidget *widget, GdkEventScroll *event, RemminaConnectionWindow *cnnwin)
Definition: rcw.c:3001
gboolean hide_connection_toolbar
Definition: remmina_pref.h:154
void rcw_update_toolbar_opacity(RemminaConnectionWindow *cnnwin)
Definition: rcw.c:853
gboolean rcw_open_from_filename(const gchar *filename)
Definition: rcw.c:4398
static void rcw_close_all_connections(RemminaConnectionWindow *cnnwin)
Definition: rcw.c:629
void rco_set_scrolled_policy(RemminaScaleMode scalemode, GtkScrolledWindow *scrolled_window)
Definition: rcw.c:922
static void rcw_toolbar_screenshot(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
Definition: rcw.c:2135
RemminaConnectionWindow * cnnwin
Definition: rcw.c:163
void remmina_message_panel_setup_message(RemminaMessagePanel *mp, const gchar *message, RemminaMessagePanelCallback response_callback, gpointer response_callback_data)
gboolean kioskmode
Definition: remmina.c:87
void remmina_application_condexit(RemminaCondExitType why)
Definition: remmina_exec.c:123
void remmina_protocol_widget_set_expand(RemminaProtocolWidget *gp, gboolean expand)
static gboolean focus_in_delayed_grab(RemminaConnectionWindow *cnnwin)
Definition: rcw.c:2918
void rco_update_toolbar_autofit_button(RemminaConnectionObject *cnnobj)
Definition: rcw.c:1633
gboolean extrahardening
Definition: remmina.c:90
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 GtkWidget * rcw_append_new_page(RemminaConnectionWindow *cnnwin, RemminaConnectionObject *cnnobj)
Definition: rcw.c:3573
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.
RemminaFile * remmina_file_manager_load_file(const gchar *filename)
gboolean rco_enter_protocol_widget(GtkWidget *widget, GdkEventCrossing *event, RemminaConnectionObject *cnnobj)
Definition: rcw.c:2884
static GtkWidget * rco_create_tab_page(RemminaConnectionObject *cnnobj)
Definition: rcw.c:3562
static gboolean rcw_delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
Definition: rcw.c:694
static gboolean rcw_map_event(GtkWidget *widget, GdkEvent *event, gpointer data)
Definition: rcw.c:3282
gint remmina_unlock_new(GtkWindow *parent)
const gchar * remmina_file_get_icon_name(RemminaFile *remminafile)
Definition: remmina_file.c:885
gint remmina_widget_pool_foreach(RemminaWidgetPoolForEachFunc callback, gpointer data)
gboolean connected
Definition: rcw.c:174
guint shortcutkey_viewonly
Definition: remmina_pref.h:183
void rco_on_close_button_clicked(GtkButton *button, RemminaConnectionObject *cnnobj)
Definition: rcw.c:3508
static void rcw_switch_viewmode(RemminaConnectionWindow *cnnwin, int newmode)
Definition: rcw.c:1311
unsigned char * buffer
Definition: types.h:84
static void rcw_on_page_added(GtkNotebook *notebook, GtkWidget *child, guint page_num, RemminaConnectionWindow *cnnwin)
Definition: rcw.c:3666
gboolean fullscreen_on_auto
Definition: remmina_pref.h:151
N_("Unable to connect to VNC server")
Definition: vnc_plugin.c:953
static void rcw_toolbar_duplicate(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin)
Definition: rcw.c:2122
void rcw_grab_focus(RemminaConnectionWindow *cnnwin)
Definition: rcw.c:3441
gboolean dark_theme
Definition: remmina_pref.h:149
RemminaMessagePanel * remmina_message_panel_new()
static void rcw_scaler_option_popdown(GtkWidget *widget, RemminaConnectionWindow *cnnwin)
Definition: rcw.c:1468
gboolean save_view_mode
Definition: remmina_pref.h:140
static void rcw_set_tooltip(GtkWidget *item, const gchar *tip, guint key1, guint key2)
Definition: rcw.c:1124
gint fullscreen_toolbar_visibility
Definition: remmina_pref.h:158
GtkWidget * remmina_widget_pool_find(GType type, const gchar *tag)
gboolean rcw_delayed_window_present(gpointer user_data)
Definition: rcw.c:4256
GtkWindow window
Definition: rcw.h:55
void rco_on_lock_dynres(RemminaProtocolWidget *gp, gpointer data)
Definition: rcw.c:4380
static void rcw_keyboard_grab(RemminaConnectionWindow *cnnwin)
Definition: rcw.c:545