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