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