From 5342ea68feec6b40130712b845d33bec4077aa21 Mon Sep 17 00:00:00 2001 From: Antenore Gatta Date: Thu, 10 Aug 2023 15:15:41 +0000 Subject: Automatic doc build by remmina-ci --- public/rcw_8c.html | 154 +++++++++++------------ public/rcw_8c_source.html | 158 ++++++++++++------------ public/rcw_8h.html | 16 +-- public/rcw_8h_source.html | 16 +-- public/remmina__exec_8c_source.html | 6 +- public/remmina__file__editor_8c_source.html | 2 +- public/remmina__main_8c_source.html | 4 +- public/remmina__plugin__manager_8c_source.html | 2 +- public/remmina__protocol__widget_8c_source.html | 10 +- 9 files changed, 184 insertions(+), 184 deletions(-) diff --git a/public/rcw_8c.html b/public/rcw_8c.html index b517ee0a2..01f259f96 100644 --- a/public/rcw_8c.html +++ b/public/rcw_8c.html @@ -474,7 +474,7 @@ Variables GTKSOCKET_NOT_AVAIL_RESPONSE_NUM  -

Definition at line 4468 of file rcw.c.

+

Definition at line 4465 of file rcw.c.

@@ -513,7 +513,7 @@ Variables
-

Definition at line 4310 of file rcw.c.

+

Definition at line 4307 of file rcw.c.

@@ -541,7 +541,7 @@ Variables
-

Definition at line 2921 of file rcw.c.

+

Definition at line 2918 of file rcw.c.

@@ -659,7 +659,7 @@ Variables
-

Definition at line 3455 of file rcw.c.

+

Definition at line 3452 of file rcw.c.

@@ -763,7 +763,7 @@ Variables
-

Definition at line 4424 of file rcw.c.

+

Definition at line 4421 of file rcw.c.

@@ -791,7 +791,7 @@ Variables
-

Definition at line 2765 of file rcw.c.

+

Definition at line 2762 of file rcw.c.

@@ -997,7 +997,7 @@ Variables
-

Definition at line 3476 of file rcw.c.

+

Definition at line 3473 of file rcw.c.

@@ -1063,7 +1063,7 @@ Variables
-

Definition at line 3522 of file rcw.c.

+

Definition at line 3519 of file rcw.c.

@@ -1091,7 +1091,7 @@ Variables
-

Definition at line 3565 of file rcw.c.

+

Definition at line 3562 of file rcw.c.

@@ -1123,7 +1123,7 @@ Variables

Deletes a RemminaMessagePanel from the current cnnobj and if it was visible, make visible the last remaining one.

-

Definition at line 4718 of file rcw.c.

+

Definition at line 4715 of file rcw.c.

@@ -1187,7 +1187,7 @@ Variables
-

Definition at line 2887 of file rcw.c.

+

Definition at line 2884 of file rcw.c.

@@ -1305,7 +1305,7 @@ Variables
-

Definition at line 2862 of file rcw.c.

+

Definition at line 2859 of file rcw.c.

@@ -1335,7 +1335,7 @@ Variables
-

Definition at line 3511 of file rcw.c.

+

Definition at line 3508 of file rcw.c.

@@ -1366,7 +1366,7 @@ Variables

Remember recent list for quick connect, and save the current date in the last_used field.

-

Definition at line 4271 of file rcw.c.

+

Definition at line 4268 of file rcw.c.

@@ -1396,7 +1396,7 @@ Variables
-

Definition at line 4366 of file rcw.c.

+

Definition at line 4363 of file rcw.c.

@@ -1426,7 +1426,7 @@ Variables
-

Definition at line 4316 of file rcw.c.

+

Definition at line 4313 of file rcw.c.

@@ -1456,7 +1456,7 @@ Variables
-

Definition at line 4383 of file rcw.c.

+

Definition at line 4380 of file rcw.c.

@@ -1486,7 +1486,7 @@ Variables
-

Definition at line 4392 of file rcw.c.

+

Definition at line 4389 of file rcw.c.

@@ -1516,7 +1516,7 @@ Variables
-

Definition at line 4375 of file rcw.c.

+

Definition at line 4372 of file rcw.c.

@@ -1617,7 +1617,7 @@ Variables

Each cnnobj->page can have more than one RemminaMessagePanel, but 0 or 1 are visible.

This function adds a RemminaMessagePanel to cnnobj->page, move it to top, and makes it the only visible one.

-

Definition at line 4765 of file rcw.c.

+

Definition at line 4762 of file rcw.c.

@@ -1675,7 +1675,7 @@ Variables
-

Definition at line 2631 of file rcw.c.

+

Definition at line 2628 of file rcw.c.

@@ -1761,7 +1761,7 @@ Variables
-

Definition at line 3052 of file rcw.c.

+

Definition at line 3049 of file rcw.c.

@@ -1799,7 +1799,7 @@ Variables
-

Definition at line 3576 of file rcw.c.

+

Definition at line 3573 of file rcw.c.

@@ -1893,7 +1893,7 @@ Variables
-

Definition at line 3140 of file rcw.c.

+

Definition at line 3137 of file rcw.c.

@@ -1931,7 +1931,7 @@ Variables
-

Definition at line 3987 of file rcw.c.

+

Definition at line 3984 of file rcw.c.

@@ -1959,7 +1959,7 @@ Variables
-

Definition at line 3749 of file rcw.c.

+

Definition at line 3746 of file rcw.c.

@@ -1987,7 +1987,7 @@ Variables
-

Definition at line 3841 of file rcw.c.

+

Definition at line 3838 of file rcw.c.

@@ -2031,7 +2031,7 @@ Variables
-

Definition at line 3769 of file rcw.c.

+

Definition at line 3766 of file rcw.c.

@@ -2069,7 +2069,7 @@ Variables
-

Definition at line 2325 of file rcw.c.

+

Definition at line 2322 of file rcw.c.

@@ -2089,7 +2089,7 @@ Variables
-

Definition at line 4259 of file rcw.c.

+

Definition at line 4256 of file rcw.c.

@@ -2219,7 +2219,7 @@ Variables
-

Definition at line 4237 of file rcw.c.

+

Definition at line 4234 of file rcw.c.

@@ -2247,7 +2247,7 @@ Variables
-

Definition at line 2994 of file rcw.c.

+

Definition at line 2991 of file rcw.c.

@@ -2319,7 +2319,7 @@ Variables
-

Definition at line 2794 of file rcw.c.

+

Definition at line 2791 of file rcw.c.

@@ -2363,7 +2363,7 @@ Variables
-

Definition at line 2802 of file rcw.c.

+

Definition at line 2799 of file rcw.c.

@@ -2407,7 +2407,7 @@ Variables
-

Definition at line 3004 of file rcw.c.

+

Definition at line 3001 of file rcw.c.

@@ -2473,7 +2473,7 @@ Variables
-

Definition at line 2944 of file rcw.c.

+

Definition at line 2941 of file rcw.c.

@@ -2517,7 +2517,7 @@ Variables
-

Definition at line 3243 of file rcw.c.

+

Definition at line 3240 of file rcw.c.

@@ -2545,7 +2545,7 @@ Variables
-

Definition at line 2970 of file rcw.c.

+

Definition at line 2967 of file rcw.c.

@@ -2589,7 +2589,7 @@ Variables
-

Definition at line 3253 of file rcw.c.

+

Definition at line 3250 of file rcw.c.

@@ -2633,7 +2633,7 @@ Variables
-

Definition at line 3964 of file rcw.c.

+

Definition at line 3961 of file rcw.c.

@@ -2695,7 +2695,7 @@ Variables
-

Definition at line 3935 of file rcw.c.

+

Definition at line 3932 of file rcw.c.

@@ -2791,7 +2791,7 @@ Variables
-

Definition at line 4703 of file rcw.c.

+

Definition at line 4700 of file rcw.c.

@@ -2811,7 +2811,7 @@ Variables
-

Definition at line 4699 of file rcw.c.

+

Definition at line 4696 of file rcw.c.

@@ -2859,7 +2859,7 @@ Variables
-

Definition at line 3444 of file rcw.c.

+

Definition at line 3441 of file rcw.c.

@@ -2905,7 +2905,7 @@ Variables

Gets called if the user interacts with the gtksocket-is-not-available-warning-dialog.

-

Definition at line 4477 of file rcw.c.

+

Definition at line 4474 of file rcw.c.

@@ -2950,7 +2950,7 @@ Variables
Todo:
Add callback for hostname transparent overlay #832
-

Definition at line 4056 of file rcw.c.

+

Definition at line 4053 of file rcw.c.

@@ -2978,7 +2978,7 @@ Variables
-

Definition at line 3219 of file rcw.c.

+

Definition at line 3216 of file rcw.c.

@@ -3106,7 +3106,7 @@ Variables
-

Definition at line 3285 of file rcw.c.

+

Definition at line 3282 of file rcw.c.

@@ -3150,7 +3150,7 @@ Variables
-

Definition at line 3323 of file rcw.c.

+

Definition at line 3320 of file rcw.c.

@@ -3226,7 +3226,7 @@ Variables
-

Definition at line 3383 of file rcw.c.

+

Definition at line 3380 of file rcw.c.

@@ -3300,7 +3300,7 @@ Variables
-

Definition at line 3089 of file rcw.c.

+

Definition at line 3086 of file rcw.c.

@@ -3344,7 +3344,7 @@ Variables
-

Definition at line 2812 of file rcw.c.

+

Definition at line 2809 of file rcw.c.

@@ -3388,7 +3388,7 @@ Variables
-

Definition at line 2825 of file rcw.c.

+

Definition at line 2822 of file rcw.c.

@@ -3444,7 +3444,7 @@ Variables
-

Definition at line 3687 of file rcw.c.

+

Definition at line 3684 of file rcw.c.

@@ -3494,7 +3494,7 @@ Variables
-

Definition at line 3669 of file rcw.c.

+

Definition at line 3666 of file rcw.c.

@@ -3544,7 +3544,7 @@ Variables
-

Definition at line 3676 of file rcw.c.

+

Definition at line 3673 of file rcw.c.

@@ -3594,7 +3594,7 @@ Variables
-

Definition at line 3656 of file rcw.c.

+

Definition at line 3653 of file rcw.c.

@@ -3622,7 +3622,7 @@ Variables
-

Definition at line 3627 of file rcw.c.

+

Definition at line 3624 of file rcw.c.

@@ -3642,7 +3642,7 @@ Variables
-

Definition at line 4449 of file rcw.c.

+

Definition at line 4446 of file rcw.c.

@@ -3684,7 +3684,7 @@ Variables
-

Definition at line 4502 of file rcw.c.

+

Definition at line 4499 of file rcw.c.

@@ -3704,7 +3704,7 @@ Variables
-

Definition at line 4401 of file rcw.c.

+

Definition at line 4398 of file rcw.c.

@@ -3754,7 +3754,7 @@ Variables
-

Definition at line 2597 of file rcw.c.

+

Definition at line 2594 of file rcw.c.

@@ -3954,7 +3954,7 @@ Variables
-

Definition at line 4708 of file rcw.c.

+

Definition at line 4705 of file rcw.c.

@@ -3982,7 +3982,7 @@ Variables
-

Definition at line 2751 of file rcw.c.

+

Definition at line 2748 of file rcw.c.

@@ -4076,7 +4076,7 @@ Variables
-

Definition at line 3264 of file rcw.c.

+

Definition at line 3261 of file rcw.c.

@@ -4366,7 +4366,7 @@ Variables
-

Definition at line 2288 of file rcw.c.

+

Definition at line 2286 of file rcw.c.

@@ -4548,7 +4548,7 @@ Variables
-

Definition at line 2299 of file rcw.c.

+

Definition at line 2297 of file rcw.c.

@@ -4698,7 +4698,7 @@ Variables
-

Definition at line 2277 of file rcw.c.

+

Definition at line 2275 of file rcw.c.

@@ -4812,7 +4812,7 @@ Variables
-

Definition at line 3132 of file rcw.c.

+

Definition at line 3129 of file rcw.c.

@@ -4850,7 +4850,7 @@ Variables
-

Definition at line 3203 of file rcw.c.

+

Definition at line 3200 of file rcw.c.

@@ -5142,7 +5142,7 @@ Variables
-

Definition at line 2137 of file rcw.c.

+

Definition at line 2135 of file rcw.c.

@@ -5322,7 +5322,7 @@ Variables
-

Definition at line 3305 of file rcw.c.

+

Definition at line 3302 of file rcw.c.

@@ -5350,7 +5350,7 @@ Variables
-

Definition at line 3606 of file rcw.c.

+

Definition at line 3603 of file rcw.c.

@@ -5378,7 +5378,7 @@ Variables
-

Definition at line 3121 of file rcw.c.

+

Definition at line 3118 of file rcw.c.

@@ -5416,7 +5416,7 @@ Variables
-

Definition at line 3425 of file rcw.c.

+

Definition at line 3422 of file rcw.c.

@@ -5508,7 +5508,7 @@ Variables
-

Definition at line 4436 of file rcw.c.

+

Definition at line 4433 of file rcw.c.

@@ -5546,7 +5546,7 @@ Variables
-

Definition at line 4455 of file rcw.c.

+

Definition at line 4452 of file rcw.c.

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

Deletes a RemminaMessagePanel from the current cnnobj and if it was visible, make visible the last remaining one.

-

Definition at line 4718 of file rcw.c.

+

Definition at line 4715 of file rcw.c.

@@ -320,7 +320,7 @@ Functions

Each cnnobj->page can have more than one RemminaMessagePanel, but 0 or 1 are visible.

This function adds a RemminaMessagePanel to cnnobj->page, move it to top, and makes it the only visible one.

-

Definition at line 4765 of file rcw.c.

+

Definition at line 4762 of file rcw.c.

@@ -360,7 +360,7 @@ Functions
-

Definition at line 4703 of file rcw.c.

+

Definition at line 4700 of file rcw.c.

@@ -380,7 +380,7 @@ Functions
-

Definition at line 4699 of file rcw.c.

+

Definition at line 4696 of file rcw.c.

@@ -418,7 +418,7 @@ Functions
-

Definition at line 4449 of file rcw.c.

+

Definition at line 4446 of file rcw.c.

@@ -460,7 +460,7 @@ Functions
-

Definition at line 4502 of file rcw.c.

+

Definition at line 4499 of file rcw.c.

@@ -480,7 +480,7 @@ Functions
-

Definition at line 4401 of file rcw.c.

+

Definition at line 4398 of file rcw.c.

@@ -510,7 +510,7 @@ Functions
-

Definition at line 4708 of file rcw.c.

+

Definition at line 4705 of file rcw.c.

diff --git a/public/rcw_8h_source.html b/public/rcw_8h_source.html index 2f57fd146..c661ca81a 100644 --- a/public/rcw_8h_source.html +++ b/public/rcw_8h_source.html @@ -86,21 +86,21 @@ $(document).ready(function(){initNavTree('rcw_8h_source.html','');});
rcw.h
-Go to the documentation of this file.
1 /*
2  * Remmina - The GTK+ Remote Desktop Client
3  * Copyright (C) 2009 - Vic Lee
4  * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
5  * Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  *
22  * In addition, as a special exception, the copyright holders give
23  * permission to link the code of portions of this program with the
24  * OpenSSL library under certain conditions as described in each
25  * individual source file, and distribute linked combinations
26  * including the two.
27  * You must obey the GNU General Public License in all respects
28  * for all of the code used other than OpenSSL. * If you modify
29  * file(s) with this exception, you may extend this exception to your
30  * version of the file(s), but you are not obligated to do so. * If you
31  * do not wish to do so, delete this exception statement from your
32  * version. * If you delete this exception statement from all source
33  * files in the program, then also delete it here.
34  *
35  */
36 
37 #pragma once
38 
39 #include <gtk/gtk.h>
40 #include "remmina_file.h"
41 #include "remmina_message_panel.h"
42 
43 G_BEGIN_DECLS
44 
45 #define REMMINA_TYPE_CONNECTION_WINDOW (rcw_get_type())
46 #define RCW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), REMMINA_TYPE_CONNECTION_WINDOW, RemminaConnectionWindow))
47 #define RCW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), REMMINA_TYPE_CONNECTION_WINDOW, RemminaConnectionWindowClass))
48 #define REMMINA_IS_CONNECTION_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), REMMINA_TYPE_CONNECTION_WINDOW))
49 #define REMMINA_IS_CONNECTION_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), REMMINA_TYPE_CONNECTION_WINDOW))
50 #define RCW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), REMMINA_TYPE_CONNECTION_WINDOW, RemminaConnectionWindowClass))
51 
52 typedef struct _RemminaConnectionWindowPriv RemminaConnectionWindowPriv;
53 
54 typedef struct _RemminaConnectionWindow {
55  GtkWindow window;
58 
60  GtkWindowClass parent_class;
61  void (*toolbar_place)(RemminaConnectionWindow *gp);
63 
65 
66 typedef enum {
70 
71 GType rcw_get_type(void)
72 G_GNUC_CONST;
73 
74 /* Open a new connection window for a .remmina file */
75 gboolean rcw_open_from_filename(const gchar *filename);
76 /* Open a new connection window for a given RemminaFile struct. The struct will be freed after the call */
77 void rcw_open_from_file(RemminaFile *remminafile);
80 GtkWidget *rcw_open_from_file_full(RemminaFile *remminafile, GCallback disconnect_cb, gpointer data, guint *handler);
81 GtkWindow* rcw_get_gtkwindow(RemminaConnectionObject *cnnobj);
83 
84 void rco_destroy_message_panel(RemminaConnectionObject *cnnobj, RemminaMessagePanel *mp);
85 void rco_show_message_panel(RemminaConnectionObject *cnnobj, RemminaMessagePanel *mp);
86 void rco_get_monitor_geometry(RemminaConnectionObject *cnnobj, GdkRectangle *sz);
87 
88 
89 
90 #define MESSAGE_PANEL_SPINNER 0x00000001
91 #define MESSAGE_PANEL_OKBUTTON 0x00000002
92 
93 G_END_DECLS
void rcw_set_delete_confirm_mode(RemminaConnectionWindow *cnnwin, RemminaConnectionWindowOnDeleteConfirmMode mode)
Definition: rcw.c:4708
+Go to the documentation of this file.
1 /*
2  * Remmina - The GTK+ Remote Desktop Client
3  * Copyright (C) 2009 - Vic Lee
4  * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
5  * Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  *
22  * In addition, as a special exception, the copyright holders give
23  * permission to link the code of portions of this program with the
24  * OpenSSL library under certain conditions as described in each
25  * individual source file, and distribute linked combinations
26  * including the two.
27  * You must obey the GNU General Public License in all respects
28  * for all of the code used other than OpenSSL. * If you modify
29  * file(s) with this exception, you may extend this exception to your
30  * version of the file(s), but you are not obligated to do so. * If you
31  * do not wish to do so, delete this exception statement from your
32  * version. * If you delete this exception statement from all source
33  * files in the program, then also delete it here.
34  *
35  */
36 
37 #pragma once
38 
39 #include <gtk/gtk.h>
40 #include "remmina_file.h"
41 #include "remmina_message_panel.h"
42 
43 G_BEGIN_DECLS
44 
45 #define REMMINA_TYPE_CONNECTION_WINDOW (rcw_get_type())
46 #define RCW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), REMMINA_TYPE_CONNECTION_WINDOW, RemminaConnectionWindow))
47 #define RCW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), REMMINA_TYPE_CONNECTION_WINDOW, RemminaConnectionWindowClass))
48 #define REMMINA_IS_CONNECTION_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), REMMINA_TYPE_CONNECTION_WINDOW))
49 #define REMMINA_IS_CONNECTION_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), REMMINA_TYPE_CONNECTION_WINDOW))
50 #define RCW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), REMMINA_TYPE_CONNECTION_WINDOW, RemminaConnectionWindowClass))
51 
52 typedef struct _RemminaConnectionWindowPriv RemminaConnectionWindowPriv;
53 
54 typedef struct _RemminaConnectionWindow {
55  GtkWindow window;
58 
60  GtkWindowClass parent_class;
61  void (*toolbar_place)(RemminaConnectionWindow *gp);
63 
65 
66 typedef enum {
70 
71 GType rcw_get_type(void)
72 G_GNUC_CONST;
73 
74 /* Open a new connection window for a .remmina file */
75 gboolean rcw_open_from_filename(const gchar *filename);
76 /* Open a new connection window for a given RemminaFile struct. The struct will be freed after the call */
77 void rcw_open_from_file(RemminaFile *remminafile);
80 GtkWidget *rcw_open_from_file_full(RemminaFile *remminafile, GCallback disconnect_cb, gpointer data, guint *handler);
81 GtkWindow* rcw_get_gtkwindow(RemminaConnectionObject *cnnobj);
83 
84 void rco_destroy_message_panel(RemminaConnectionObject *cnnobj, RemminaMessagePanel *mp);
85 void rco_show_message_panel(RemminaConnectionObject *cnnobj, RemminaMessagePanel *mp);
86 void rco_get_monitor_geometry(RemminaConnectionObject *cnnobj, GdkRectangle *sz);
87 
88 
89 
90 #define MESSAGE_PANEL_SPINNER 0x00000001
91 #define MESSAGE_PANEL_OKBUTTON 0x00000002
92 
93 G_END_DECLS
void rcw_set_delete_confirm_mode(RemminaConnectionWindow *cnnwin, RemminaConnectionWindowOnDeleteConfirmMode mode)
Definition: rcw.c:4705
typedefG_BEGIN_DECLS struct _RemminaFile RemminaFile
Definition: types.h:44
-
GtkWindow * rcw_get_gtkwindow(RemminaConnectionObject *cnnobj)
Definition: rcw.c:4699
-
GtkWidget * rcw_open_from_file_full(RemminaFile *remminafile, GCallback disconnect_cb, gpointer data, guint *handler)
Definition: rcw.c:4502
+
GtkWindow * rcw_get_gtkwindow(RemminaConnectionObject *cnnobj)
Definition: rcw.c:4696
+
GtkWidget * rcw_open_from_file_full(RemminaFile *remminafile, GCallback disconnect_cb, gpointer data, guint *handler)
Definition: rcw.c:4499
-
void rco_destroy_message_panel(RemminaConnectionObject *cnnobj, RemminaMessagePanel *mp)
Deletes a RemminaMessagePanel from the current cnnobj and if it was visible, make visible the last re...
Definition: rcw.c:4718
+
void rco_destroy_message_panel(RemminaConnectionObject *cnnobj, RemminaMessagePanel *mp)
Deletes a RemminaMessagePanel from the current cnnobj and if it was visible, make visible the last re...
Definition: rcw.c:4715
RemminaConnectionWindowOnDeleteConfirmMode
Definition: rcw.h:66
struct _RemminaConnectionWindowPriv RemminaConnectionWindowPriv
Definition: rcw.h:52
struct _RemminaConnectionWindow RemminaConnectionWindow
-
void rcw_open_from_file(RemminaFile *remminafile)
Definition: rcw.c:4449
-
gboolean rcw_open_from_filename(const gchar *filename)
Definition: rcw.c:4401
+
void rcw_open_from_file(RemminaFile *remminafile)
Definition: rcw.c:4446
+
gboolean rcw_open_from_filename(const gchar *filename)
Definition: rcw.c:4398
GtkWindowClass parent_class
Definition: rcw.h:60
-
void rco_show_message_panel(RemminaConnectionObject *cnnobj, RemminaMessagePanel *mp)
Each cnnobj->page can have more than one RemminaMessagePanel, but 0 or 1 are visible.
Definition: rcw.c:4765
+
void rco_show_message_panel(RemminaConnectionObject *cnnobj, RemminaMessagePanel *mp)
Each cnnobj->page can have more than one RemminaMessagePanel, but 0 or 1 are visible.
Definition: rcw.c:4762
RemminaConnectionWindowPriv * priv
Definition: rcw.h:56
GType rcw_get_type(void) G_GNUC_CONST
gboolean rcw_delete(RemminaConnectionWindow *cnnwin)
Definition: rcw.c:648
@@ -110,7 +110,7 @@ $(document).ready(function(){initNavTree('rcw_8h_source.html','');});
RemminaConnectionWindow * cnnwin
Definition: rcw.c:163
-
GtkWidget * rcw_get_gtkviewport(RemminaConnectionObject *cnnobj)
Definition: rcw.c:4703
+
GtkWidget * rcw_get_gtkviewport(RemminaConnectionObject *cnnobj)
Definition: rcw.c:4700
GtkWindow window
Definition: rcw.h:55
void rco_get_monitor_geometry(RemminaConnectionObject *cnnobj, GdkRectangle *sz)
Definition: rcw.c:1009
diff --git a/public/remmina__exec_8c_source.html b/public/remmina__exec_8c_source.html index 031bfce41..5311bffea 100644 --- a/public/remmina__exec_8c_source.html +++ b/public/remmina__exec_8c_source.html @@ -121,7 +121,7 @@ $(document).ready(function(){initNavTree('remmina__exec_8c_source.html','');});
gint remmina_widget_pool_count()
gchar * remmina_crypt_encrypt(const gchar *str)
Definition: remmina_crypt.c:93
-
void rcw_set_delete_confirm_mode(RemminaConnectionWindow *cnnwin, RemminaConnectionWindowOnDeleteConfirmMode mode)
Definition: rcw.c:4708
+
void rcw_set_delete_confirm_mode(RemminaConnectionWindow *cnnwin, RemminaConnectionWindowOnDeleteConfirmMode mode)
Definition: rcw.c:4705
void remmina_exec_command(RemminaCommandType command, const gchar *data)
Definition: remmina_exec.c:368
@@ -133,7 +133,7 @@ $(document).ready(function(){initNavTree('remmina__exec_8c_source.html','');});
void remmina_main_destroy()
Definition: remmina_main.c:193
RemminaFile * remmina_file_new(void)
Definition: remmina_file.c:93
-
void rcw_open_from_file(RemminaFile *remminafile)
Definition: rcw.c:4449
+
void rcw_open_from_file(RemminaFile *remminafile)
Definition: rcw.c:4446
gint remmina_file_get_int(RemminaFile *remminafile, const gchar *setting, gint default_value)
Definition: remmina_file.c:603
void remmina_about_open(GtkWindow *parent)
Definition: remmina_about.c:44
@@ -144,7 +144,7 @@ $(document).ready(function(){initNavTree('remmina__exec_8c_source.html','');}); -
gboolean rcw_open_from_filename(const gchar *filename)
Definition: rcw.c:4401
+
gboolean rcw_open_from_filename(const gchar *filename)
Definition: rcw.c:4398
static gboolean cb_closewidget(GtkWidget *widget, gpointer data)
Definition: remmina_exec.c:67
gboolean kioskmode
Definition: remmina.c:87
void remmina_application_condexit(RemminaCondExitType why)
Definition: remmina_exec.c:123
diff --git a/public/remmina__file__editor_8c_source.html b/public/remmina__file__editor_8c_source.html index bcaf2ef67..ae1cff2ce 100644 --- a/public/remmina__file__editor_8c_source.html +++ b/public/remmina__file__editor_8c_source.html @@ -199,7 +199,7 @@ $(document).ready(function(){initNavTree('remmina__file__editor_8c_source.html',
static void remmina_file_editor_create_settings(RemminaFileEditor *gfe, GtkWidget *grid, const RemminaProtocolSetting *settings)
static void remmina_file_editor_on_save_connect(GtkWidget *button, RemminaFileEditor *gfe)
-
void rcw_open_from_file(RemminaFile *remminafile)
Definition: rcw.c:4449
+
void rcw_open_from_file(RemminaFile *remminafile)
Definition: rcw.c:4446
RemminaPref remmina_pref
Definition: rcw.c:79
void remmina_file_editor_file_save(RemminaFileEditor *gfe)
diff --git a/public/remmina__main_8c_source.html b/public/remmina__main_8c_source.html index 048c989d5..a3d449c57 100644 --- a/public/remmina__main_8c_source.html +++ b/public/remmina__main_8c_source.html @@ -254,7 +254,7 @@ $(document).ready(function(){initNavTree('remmina__main_8c_source.html','');});
void remmina_main_on_action_connection_external_tools(GSimpleAction *action, GVariant *param, gpointer data)
Definition: remmina_main.c:796
static void remmina_main_clear_selection_data(void)
Definition: remmina_main.c:261
-
void rcw_open_from_file(RemminaFile *remminafile)
Definition: rcw.c:4449
+
void rcw_open_from_file(RemminaFile *remminafile)
Definition: rcw.c:4446
void remmina_main_on_date_column_sort_clicked()
RemminaPref remmina_pref
Definition: rcw.c:79
gboolean remmina_main_file_list_on_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
@@ -274,7 +274,7 @@ $(document).ready(function(){initNavTree('remmina__main_8c_source.html','');});
void remmina_main_on_action_connection_edit(GSimpleAction *action, GVariant *param, gpointer data)
Definition: remmina_main.c:946
void remmina_main_toggle_password_view(GtkWidget *widget, gpointer data)
GtkTreeModel * file_model_sort
Definition: remmina_main.h:92
-
gboolean rcw_open_from_filename(const gchar *filename)
Definition: rcw.c:4401
+
gboolean rcw_open_from_filename(const gchar *filename)
Definition: rcw.c:4398
static const gchar * supported_mime_types[]
Definition: remmina_main.c:87
void remmina_main_on_action_expand(GSimpleAction *action, GVariant *param, gpointer data)
diff --git a/public/remmina__plugin__manager_8c_source.html b/public/remmina__plugin__manager_8c_source.html index d0908752c..711737bea 100644 --- a/public/remmina__plugin__manager_8c_source.html +++ b/public/remmina__plugin__manager_8c_source.html @@ -127,7 +127,7 @@ $(document).ready(function(){initNavTree('remmina__plugin__manager_8c_source.htm
G_BEGIN_DECLS typedef gboolean(* RemminaPluginFunc)(gchar *name, RemminaPlugin *plugin, gpointer data)
void remmina_plugin_manager_for_each_plugin(RemminaPluginType type, RemminaPluginFunc func, gpointer data)
gchar * remmina_protocol_widget_get_domain(RemminaProtocolWidget *gp)
-
GtkWidget * rcw_open_from_file_full(RemminaFile *remminafile, GCallback disconnect_cb, gpointer data, guint *handler)
Definition: rcw.c:4502
+
GtkWidget * rcw_open_from_file_full(RemminaFile *remminafile, GCallback disconnect_cb, gpointer data, guint *handler)
Definition: rcw.c:4499
void remmina_widget_pool_register(GtkWidget *widget)
void _remmina_critical(const gchar *fun, const gchar *fmt,...)
Definition: remmina_log.c:382
diff --git a/public/remmina__protocol__widget_8c_source.html b/public/remmina__protocol__widget_8c_source.html index d5ab71d5a..432bec51a 100644 --- a/public/remmina__protocol__widget_8c_source.html +++ b/public/remmina__protocol__widget_8c_source.html @@ -143,7 +143,7 @@ $(document).ready(function(){initNavTree('remmina__protocol__widget_8c_source.ht
void remmina_protocol_widget_register_hostkey(RemminaProtocolWidget *gp, GtkWidget *widget)
-
void rco_show_message_panel(RemminaConnectionObject *cnnobj, RemminaMessagePanel *mp)
Each cnnobj->page can have more than one RemminaMessagePanel, but 0 or 1 are visible.
Definition: rcw.c:4765
+
void rco_show_message_panel(RemminaConnectionObject *cnnobj, RemminaMessagePanel *mp)
Each cnnobj->page can have more than one RemminaMessagePanel, but 0 or 1 are visible.
Definition: rcw.c:4762
gboolean remmina_protocol_widget_start_reverse_tunnel(RemminaProtocolWidget *gp, gint local_port)
gchar * remmina_protocol_widget_get_domain(RemminaProtocolWidget *gp)
@@ -151,7 +151,7 @@ $(document).ready(function(){initNavTree('remmina__protocol__widget_8c_source.ht
static void shutdown_loop(MpRunInfo *mpri)
gpointer destroy_func_callback_data
Definition: remmina_ssh.h:181
RemminaFile * remmina_file_dup_temp_protocol(RemminaFile *remminafile, const gchar *new_protocol)
Definition: remmina_file.c:899
-
GtkWidget * rcw_open_from_file_full(RemminaFile *remminafile, GCallback disconnect_cb, gpointer data, guint *handler)
Definition: rcw.c:4502
+
GtkWidget * rcw_open_from_file_full(RemminaFile *remminafile, GCallback disconnect_cb, gpointer data, guint *handler)
Definition: rcw.c:4499
void remmina_message_panel_setup_auth_x509(RemminaMessagePanel *mp, RemminaMessagePanelCallback response_callback, gpointer response_callback_data)
static int remmina_protocol_widget_dialog(enum panel_type dtype, RemminaProtocolWidget *gp, RemminaMessagePanelFlags pflags, const gchar *title, const gchar *default_username, const gchar *default_password, const gchar *default_domain, const gchar *strpasswordlabel)
@@ -174,7 +174,7 @@ $(document).ready(function(){initNavTree('remmina__protocol__widget_8c_source.ht
struct remmina_masterthread_exec_data::@12::@24 protocolwidget_panelshowlisten
void remmina_protocol_widget_call_feature_by_type(RemminaProtocolWidget *gp, RemminaProtocolFeatureType type, gint id)
void remmina_protocol_widget_lock_dynres(RemminaProtocolWidget *gp)
-
void rco_destroy_message_panel(RemminaConnectionObject *cnnobj, RemminaMessagePanel *mp)
Deletes a RemminaMessagePanel from the current cnnobj and if it was visible, make visible the last re...
Definition: rcw.c:4718
+
void rco_destroy_message_panel(RemminaConnectionObject *cnnobj, RemminaMessagePanel *mp)
Deletes a RemminaMessagePanel from the current cnnobj and if it was visible, make visible the last re...
Definition: rcw.c:4715
void remmina_protocol_widget_set_hostkey_func(RemminaProtocolWidget *gp, RemminaHostkeyFunc func)
@@ -225,7 +225,7 @@ $(document).ready(function(){initNavTree('remmina__protocol__widget_8c_source.ht
void remmina_message_panel_field_set_filename(RemminaMessagePanel *mp, int entryid, const gchar *filename)
-
GtkWindow * rcw_get_gtkwindow(RemminaConnectionObject *cnnobj)
Definition: rcw.c:4699
+
GtkWindow * rcw_get_gtkwindow(RemminaConnectionObject *cnnobj)
Definition: rcw.c:4696
gboolean remmina_masterthread_exec_is_main_thread()
struct remmina_masterthread_exec_data::@12::@22 protocolwidget_mpdestroy
@@ -302,7 +302,7 @@ $(document).ready(function(){initNavTree('remmina__protocol__widget_8c_source.ht
gboolean(* close_connection)(RemminaProtocolWidget *gp)
Definition: plugin.h:81
void remmina_masterthread_exec_and_wait(RemminaMTExecData *d)
-
GtkWidget * rcw_get_gtkviewport(RemminaConnectionObject *cnnobj)
Definition: rcw.c:4703
+
GtkWidget * rcw_get_gtkviewport(RemminaConnectionObject *cnnobj)
Definition: rcw.c:4700
void(* RemminaMessagePanelCallback)(void *user_data, int button)
void remmina_protocol_widget_emit_signal(RemminaProtocolWidget *gp, const gchar *signal_name)
gchar * remmina_message_panel_field_get_string(RemminaMessagePanel *mp, int entryid)
-- cgit v1.2.3