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