remmina_connection_window.c 134 KB
Newer Older
Antenore Gatta's avatar
Antenore Gatta committed
1 2 3 4
/*
 * Remmina - The GTK+ Remote Desktop Client
 * Copyright (C) 2009-2011 Vic Lee
 * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
5
 * Copyright (C) 2016-2018 Antenore Gatta, Giovanni Panozzo
Antenore Gatta's avatar
Antenore Gatta committed
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA  02110-1301, USA.
 *
 *  In addition, as a special exception, the copyright holders give
 *  permission to link the code of portions of this program with the
 *  OpenSSL library under certain conditions as described in each
 *  individual source file, and distribute linked combinations
 *  including the two.
 *  You must obey the GNU General Public License in all respects
 *  for all of the code used other than OpenSSL. *  If you modify
 *  file(s) with this exception, you may extend this exception to your
 *  version of the file(s), but you are not obligated to do so. *  If you
 *  do not wish to do so, delete this exception statement from your
 *  version. *  If you delete this exception statement from all source
 *  files in the program, then also delete it here.
 *
 */
36

37 38
#include "config.h"

39
#include <cairo/cairo-xlib.h>
40 41
#include <gdk/gdk.h>
#include <gdk/gdkkeysyms.h>
42
#include <glib/gi18n.h>
43
#include <gtk/gtk.h>
44
#include <stdlib.h>
45

46
#include "remmina.h"
47
#include "remmina_connection_window.h"
48 49 50
#include "remmina_file.h"
#include "remmina_file_manager.h"
#include "remmina_init_dialog.h"
51
#include "remmina_ext_exec.h"
52
#include "remmina_plugin_manager.h"
53
#include "remmina_pref.h"
54 55
#include "remmina_protocol_widget.h"
#include "remmina_public.h"
56 57
#include "remmina_scrolled_viewport.h"
#include "remmina_widget_pool.h"
58
#include "remmina_log.h"
59
#include "remmina/remmina_trace_calls.h"
60

61
#define DEBUG_KB_GRABBING 0
62
#include "remmina_exec.h"
63

64 65 66
gchar *remmina_pref_file;
RemminaPref remmina_pref;

67
G_DEFINE_TYPE( RemminaConnectionWindow, remmina_connection_window, GTK_TYPE_WINDOW)
68 69 70

#define MOTION_TIME 100

71 72 73
/* default timeout used to hide the floating toolbar wen switching profile */
#define TB_HIDE_TIME_TIME 1000

Giovanni Panozzo's avatar
Giovanni Panozzo committed
74 75
#define FLOATING_TOOLBAR_WIDGET (GTK_CHECK_VERSION(3, 10, 0))

76
typedef struct _RemminaConnectionHolder RemminaConnectionHolder;
77

78

Antenore Gatta's avatar
Antenore Gatta committed
79
struct _RemminaConnectionWindowPriv {
80
	RemminaConnectionHolder* cnnhld;
81

82
	GtkWidget* notebook;
83

84
	guint switch_page_handler;
85

Giovanni Panozzo's avatar
Giovanni Panozzo committed
86 87 88 89 90 91 92 93 94 95 96
#if FLOATING_TOOLBAR_WIDGET
	GtkWidget* floating_toolbar_widget;
	GtkWidget* overlay;
	GtkWidget* revealer;
	GtkWidget* overlay_ftb_overlay;
#else
	GtkWidget* floating_toolbar_window;
	gboolean floating_toolbar_motion_show;
	gboolean floating_toolbar_motion_visible;
#endif

97
	GtkWidget* floating_toolbar_label;
98 99 100
	gdouble floating_toolbar_opacity;
	/* To avoid strange event-loop */
	guint floating_toolbar_motion_handler;
Giovanni Panozzo's avatar
Giovanni Panozzo committed
101 102
	/* Other event sources to remove when deleting the object */
	guint ftb_hide_eventsource;
103 104 105
	/* Timer to hide the toolbar */
	guint hidetb_timer;

106 107 108
	/* Timer to save new window state and wxh */
	guint savestate_eventsourceid;

109

110
	GtkWidget* toolbar;
111
	GtkWidget* grid;
112

113
	/* Toolitems that need to be handled */
114 115 116
	GtkToolItem* toolitem_autofit;
	GtkToolItem* toolitem_fullscreen;
	GtkToolItem* toolitem_switch_page;
117
	GtkToolItem* toolitem_dynres;
118 119 120 121
	GtkToolItem* toolitem_scale;
	GtkToolItem* toolitem_grab;
	GtkToolItem* toolitem_preferences;
	GtkToolItem* toolitem_tools;
122
	GtkToolItem* toolitem_screenshot;
123
	GtkWidget* fullscreen_option_button;
124 125
	GtkWidget* fullscreen_scaler_button;
	GtkWidget* scaler_option_button;
126 127

	GtkWidget* pin_button;
128
	gboolean pin_down;
129

130
	gboolean sticky;
131

132
	gint view_mode;
Giovanni Panozzo's avatar
Giovanni Panozzo committed
133

134 135 136
	gboolean kbcaptured;
	gboolean mouse_pointer_entered;

137 138
	RemminaConnectionWindowOnDeleteConfirmMode on_delete_confirm_mode;

139 140
};

Antenore Gatta's avatar
Antenore Gatta committed
141
typedef struct _RemminaConnectionObject {
142
	RemminaConnectionHolder* cnnhld;
143

144
	RemminaFile* remmina_file;
145

146
	/* A dummy window which will be realized as a container during initialize, before reparent to the real window */
147
	GtkWidget* window;
148

149
	/* Containers for RemminaProtocolWidget: RemminaProtocolWidget->aspectframe->viewport->scrolledcontainer->...->window */
150
	GtkWidget* proto;
151
	GtkWidget* aspectframe;
152
	GtkWidget* viewport;
153

154
	/* Scrolled containers */
155
	GtkWidget* scrolled_container;
156

157 158
	gboolean plugin_can_scale;

159
	gboolean connected;
160 161
	gboolean dynres_unlocked;

162 163 164 165
	/* The time of the GTK event which called remmina_connection_window_open_from_file_full().
	 * Needed to make gtk_window_present_with_time() work under wayland */
	guint32 open_from_file_event_time;

166 167
} RemminaConnectionObject;

Antenore Gatta's avatar
Antenore Gatta committed
168
struct _RemminaConnectionHolder {
169
	RemminaConnectionWindow* cnnwin;
170
	gint fullscreen_view_mode;
171

172 173
	gboolean hostkey_activated;
	gboolean hostkey_used;
Giovanni Panozzo's avatar
Giovanni Panozzo committed
174

175 176
};

Antenore Gatta's avatar
Antenore Gatta committed
177
enum {
178 179 180 181 182 183 184
	TOOLBARPLACE_SIGNAL,
	LAST_SIGNAL
};

static guint remmina_connection_window_signals[LAST_SIGNAL] =
{ 0 };

185
#define DECLARE_CNNOBJ \
Antenore Gatta's avatar
Antenore Gatta committed
186 187 188 189
	if (!cnnhld || !cnnhld->cnnwin || gtk_notebook_get_current_page(GTK_NOTEBOOK(cnnhld->cnnwin->priv->notebook)) < 0) return; \
	RemminaConnectionObject* cnnobj = (RemminaConnectionObject*)g_object_get_data( \
	G_OBJECT(gtk_notebook_get_nth_page(GTK_NOTEBOOK(cnnhld->cnnwin->priv->notebook), \
			gtk_notebook_get_current_page(GTK_NOTEBOOK(cnnhld->cnnwin->priv->notebook)))), "cnnobj");
190 191

#define DECLARE_CNNOBJ_WITH_RETURN(r) \
Antenore Gatta's avatar
Antenore Gatta committed
192 193 194 195
	if (!cnnhld || !cnnhld->cnnwin || gtk_notebook_get_current_page(GTK_NOTEBOOK(cnnhld->cnnwin->priv->notebook)) < 0) return r; \
	RemminaConnectionObject* cnnobj = (RemminaConnectionObject*)g_object_get_data( \
	G_OBJECT(gtk_notebook_get_nth_page(GTK_NOTEBOOK(cnnhld->cnnwin->priv->notebook), \
			gtk_notebook_get_current_page(GTK_NOTEBOOK(cnnhld->cnnwin->priv->notebook)))), "cnnobj");
196

197 198
static void remmina_connection_holder_create_scrolled(RemminaConnectionHolder* cnnhld, RemminaConnectionObject* cnnobj);
static void remmina_connection_holder_create_fullscreen(RemminaConnectionHolder* cnnhld, RemminaConnectionObject* cnnobj,
Antenore Gatta's avatar
Antenore Gatta committed
199
							gint view_mode);
200
static gboolean remmina_connection_window_hostkey_func(RemminaProtocolWidget* gp, guint keyval, gboolean release,
Antenore Gatta's avatar
Antenore Gatta committed
201
						       RemminaConnectionHolder* cnnhld);
202

203
static void remmina_connection_holder_grab_focus(GtkNotebook *notebook);
204 205
static GtkWidget* remmina_connection_holder_create_toolbar(RemminaConnectionHolder* cnnhld, gint mode);
static void remmina_connection_holder_place_toolbar(GtkToolbar *toolbar, GtkGrid *grid, GtkWidget *sibling, int toolbar_placement);
206

Giovanni Panozzo's avatar
Giovanni Panozzo committed
207 208 209 210 211
#if FLOATING_TOOLBAR_WIDGET
static void remmina_connection_window_ftb_drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer user_data);
#endif


212
static const GtkTargetEntry dnd_targets_ftb[] =
213
{
Giovanni Panozzo's avatar
Giovanni Panozzo committed
214
	{
Antenore Gatta's avatar
Antenore Gatta committed
215 216
		(char*)"text/x-remmina-ftb",
		GTK_TARGET_SAME_APP | GTK_TARGET_OTHER_WIDGET,
Giovanni Panozzo's avatar
Giovanni Panozzo committed
217 218 219 220
		0
	},
};

221 222 223
static const GtkTargetEntry dnd_targets_tb[] =
{
	{
Antenore Gatta's avatar
Antenore Gatta committed
224
		(char*)"text/x-remmina-tb",
225 226 227 228
		GTK_TARGET_SAME_APP,
		0
	},
};
229

230
static void remmina_connection_window_class_init(RemminaConnectionWindowClass* klass)
231
{
232
	TRACE_CALL(__func__);
233 234 235
	GtkCssProvider  *provider;

	provider = gtk_css_provider_new();
236 237 238 239

	/* It's important to remove padding, border and shadow from GtkViewport or
	 * we will never know its internal area size, because GtkViweport::viewport_get_view_allocation,
	 * which returns the internal size of the GtkViewport, is private and we cannot access it */
240 241

#if GTK_CHECK_VERSION(3, 14, 0)
Antenore Gatta's avatar
Antenore Gatta committed
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
	gtk_css_provider_load_from_data(provider,
		"#remmina-cw-viewport, #remmina-cw-aspectframe {\n"
		"  padding:0;\n"
		"  border:0;\n"
		"  background-color: black;\n"
		"}\n"
		"GtkDrawingArea {\n"
		"  background-color: black;\n"
		"}\n"
		"GtkToolbar {\n"
		"  -GtkWidget-window-dragging: 0;\n"
		"}\n"
		"#remmina-connection-window-fullscreen {\n"
		"  background-color: black;\n"
		"}\n"
		"#remmina-small-button {\n"
		"  outline-offset: 0;\n"
		"  outline-width: 0;\n"
		"  padding: 0;\n"
		"  border: 0;\n"
		"}\n"
		"#remmina-pin-button {\n"
		"  outline-offset: 0;\n"
		"  outline-width: 0;\n"
		"  padding: 2px;\n"
		"  border: 0;\n"
		"}\n"
		"#remmina-scrolled-container {\n"
		"  background-color: black;\n"
		"}\n"
		"#remmina-scrolled-container.undershoot {\n"
		"  background: none\n"
		"}\n"
		"#ftbbox-upper {\n"
		"  border-style: none solid solid solid;\n"
		"  border-width: 1px;\n"
		"  border-radius: 4px;\n"
		"  border-color: #808080;\n"
		"  padding: 0px;\n"
		"  background-color: #f0f0f0;\n"
		"}\n"
		"#ftbbox-lower {\n"
		"  border-style: solid solid none solid;\n"
		"  border-width: 1px;\n"
		"  border-radius: 4px;\n"
		"  border-color: #808080;\n"
		"  padding: 0px;\n"
		"  background-color: #f0f0f0;\n"
		"}\n"
		"#ftb-handle {\n"
		"  background-color: #f0f0f0;\n"
		"}\n"

		, -1, NULL);
296 297

#else
Antenore Gatta's avatar
Antenore Gatta committed
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
	gtk_css_provider_load_from_data(provider,
		"#remmina-cw-viewport, #remmina-cw-aspectframe {\n"
		"  padding:0;\n"
		"  border:0;\n"
		"  background-color: black;\n"
		"}\n"
		"GtkDrawingArea {\n"
		"  background-color: black;\n"
		"}\n"
		"GtkToolbar {\n"
		"  -GtkWidget-window-dragging: 0;\n"
		"}\n"
		"#remmina-connection-window-fullscreen {\n"
		"  background-color: black;\n"
		"}\n"
		"#remmina-small-button {\n"
		"  -GtkWidget-focus-padding: 0;\n"
		"  -GtkWidget-focus-line-width: 0;\n"
		"  padding: 0;\n"
		"  border: 0;\n"
		"}\n"
		"#remmina-pin-button {\n"
		"  -GtkWidget-focus-padding: 0;\n"
		"  -GtkWidget-focus-line-width: 0;\n"
		"  padding: 2px;\n"
		"  border: 0;\n"
		"}\n"
		"#remmina-scrolled-container {\n"
		"  background-color: black;\n"
		"}\n"
		"#remmina-scrolled-container.undershoot {\n"
		"  background: none\n"
		"}\n"
		"#ftbbox-upper {\n"
		"  border-style: none solid solid solid;\n"
		"  border-width: 1px;\n"
		"  border-radius: 4px;\n"
		"  border-color: #808080;\n"
		"  padding: 0px;\n"
		"  background-color: #f0f0f0;\n"
		"}\n"
		"#ftbbox-lower {\n"
		"  border-style: solid solid none solid;\n"
		"  border-width: 1px;\n"
		"  border-radius: 4px;\n"
		"  border-color: #808080;\n"
		"  padding: 0px;\n"
		"  background-color: #f0f0f0;\n"
		"}\n"
		"#ftb-handle {\n"
		"  background-color: #f0f0f0;\n"
		"}\n"

		, -1, NULL);
352
#endif
353

Antenore Gatta's avatar
Antenore Gatta committed
354 355 356
	gtk_style_context_add_provider_for_screen(gdk_screen_get_default(),
		GTK_STYLE_PROVIDER(provider),
		GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
Giovanni Panozzo's avatar
Giovanni Panozzo committed
357

358
	g_object_unref(provider);
359 360 361

	/* Define a signal used to notify all remmina_connection_windows of toolbar move */
	remmina_connection_window_signals[TOOLBARPLACE_SIGNAL] = g_signal_new("toolbar-place", G_TYPE_FROM_CLASS(klass),
Antenore Gatta's avatar
Antenore Gatta committed
362 363
		G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaConnectionWindowClass, toolbar_place), NULL, NULL,
		g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
364

365 366
}

367 368 369 370 371 372 373 374
static RemminaScaleMode get_current_allowed_scale_mode(RemminaConnectionObject* cnnobj, gboolean *dynres_avail, gboolean *scale_avail)
{
	RemminaScaleMode scalemode;
	gboolean plugin_has_dynres, plugin_can_scale;

	scalemode = remmina_protocol_widget_get_current_scale_mode(REMMINA_PROTOCOL_WIDGET(cnnobj->proto));

	plugin_has_dynres = remmina_protocol_widget_query_feature_by_type(REMMINA_PROTOCOL_WIDGET(cnnobj->proto),
Antenore Gatta's avatar
Antenore Gatta committed
375
		REMMINA_PROTOCOL_FEATURE_TYPE_SCALE);
376 377

	plugin_can_scale = remmina_protocol_widget_query_feature_by_type(REMMINA_PROTOCOL_WIDGET(cnnobj->proto),
Antenore Gatta's avatar
Antenore Gatta committed
378
		REMMINA_PROTOCOL_FEATURE_TYPE_SCALE);
379 380

	/* forbid scalemode REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES when not possible */
381
	if ((!plugin_has_dynres) && scalemode == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES)
382 383 384 385 386 387 388 389 390 391 392 393 394 395 396
		scalemode = REMMINA_PROTOCOL_WIDGET_SCALE_MODE_NONE;

	/* forbid scalemode REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED when not possible */
	if (!plugin_can_scale && scalemode == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED)
		scalemode = REMMINA_PROTOCOL_WIDGET_SCALE_MODE_NONE;

	if (scale_avail)
		*scale_avail = plugin_can_scale;
	if (dynres_avail)
		*dynres_avail = (plugin_has_dynres && cnnobj->dynres_unlocked);

	return scalemode;

}

Giovanni Panozzo's avatar
Giovanni Panozzo committed
397
static void remmina_connection_holder_disconnect_current_page(RemminaConnectionHolder* cnnhld)
398
{
399
	TRACE_CALL(__func__);
400 401
	DECLARE_CNNOBJ

Antenore Gatta's avatar
Antenore Gatta committed
402
	/* Disconnects the connection which is currently in view in the notebook */
Giovanni Panozzo's avatar
Giovanni Panozzo committed
403

Antenore Gatta's avatar
Antenore Gatta committed
404
	remmina_protocol_widget_close_connection(REMMINA_PROTOCOL_WIDGET(cnnobj->proto));
405 406
}

407
static void remmina_connection_holder_keyboard_ungrab(RemminaConnectionHolder* cnnhld)
408
{
409
	TRACE_CALL(__func__);
Antenore Gatta's avatar
Antenore Gatta committed
410
	GdkDisplay *display;
411 412 413
#if GTK_CHECK_VERSION(3, 20, 0)
	GdkSeat *seat;
#else
414
	GdkDeviceManager *manager;
415
#endif
416
	GdkDevice *keyboard = NULL;
417

418
	display = gtk_widget_get_display(GTK_WIDGET(cnnhld->cnnwin));
419 420 421 422
#if GTK_CHECK_VERSION(3, 20, 0)
	seat = gdk_display_get_default_seat(display);
	keyboard = gdk_seat_get_pointer(seat);
#else
423 424
	manager = gdk_display_get_device_manager(display);
	keyboard = gdk_device_manager_get_client_pointer(manager);
425 426
#endif

427

Antenore Gatta's avatar
Antenore Gatta committed
428
	if (!cnnhld->cnnwin->priv->kbcaptured) {
429 430
		return;
	}
431

Antenore Gatta's avatar
Antenore Gatta committed
432 433
	if (keyboard != NULL) {
		if ( gdk_device_get_source(keyboard) != GDK_SOURCE_KEYBOARD ) {
434
			keyboard = gdk_device_get_associated_device(keyboard);
435
		}
436 437 438
#if DEBUG_KB_GRABBING
		printf("DEBUG_KB_GRABBING: --- ungrabbing\n");
#endif
439

440
#if GTK_CHECK_VERSION(3, 20, 0)
441
		gdk_seat_ungrab(seat);
442 443 444 445 446
#else
		gdk_device_ungrab(keyboard, GDK_CURRENT_TIME);
#endif
		cnnhld->cnnwin->priv->kbcaptured = FALSE;

447 448 449
	}
}

450
static void remmina_connection_holder_keyboard_grab(RemminaConnectionHolder* cnnhld)
451
{
452
	TRACE_CALL(__func__);
453
	DECLARE_CNNOBJ
454
	GdkDisplay *display;
455 456 457
#if GTK_CHECK_VERSION(3, 20, 0)
	GdkSeat *seat;
#else
458
	GdkDeviceManager *manager;
459
#endif
460 461
	GdkDevice *keyboard = NULL;

Antenore Gatta's avatar
Antenore Gatta committed
462
	if (cnnhld->cnnwin->priv->kbcaptured || !cnnhld->cnnwin->priv->mouse_pointer_entered) {
463 464 465
		return;
	}

466
	display = gtk_widget_get_display(GTK_WIDGET(cnnhld->cnnwin));
467 468 469 470
#if GTK_CHECK_VERSION(3, 20, 0)
	seat = gdk_display_get_default_seat(display);
	keyboard = gdk_seat_get_pointer(seat);
#else
471 472
	manager = gdk_display_get_device_manager(display);
	keyboard = gdk_device_manager_get_client_pointer(manager);
473
#endif
474

Antenore Gatta's avatar
Antenore Gatta committed
475
	if (keyboard != NULL) {
476

Antenore Gatta's avatar
Antenore Gatta committed
477
		if ( gdk_device_get_source(keyboard) != GDK_SOURCE_KEYBOARD) {
478
			keyboard = gdk_device_get_associated_device( keyboard );
479 480
		}

Antenore Gatta's avatar
Antenore Gatta committed
481
		if (remmina_file_get_int(cnnobj->remmina_file, "keyboard_grab", FALSE)) {
482 483 484
#if DEBUG_KB_GRABBING
			printf("DEBUG_KB_GRABBING: +++ grabbing\n");
#endif
485
#if GTK_CHECK_VERSION(3, 20, 0)
486
			if (gdk_seat_grab(seat, gtk_widget_get_window(GTK_WIDGET(cnnhld->cnnwin)),
Antenore Gatta's avatar
Antenore Gatta committed
487
				    GDK_SEAT_CAPABILITY_KEYBOARD, FALSE, NULL, NULL, NULL, NULL) == GDK_GRAB_SUCCESS)
488 489
#else
			if (gdk_device_grab(keyboard, gtk_widget_get_window(GTK_WIDGET(cnnhld->cnnwin)), GDK_OWNERSHIP_WINDOW,
Antenore Gatta's avatar
Antenore Gatta committed
490
				    TRUE, GDK_KEY_PRESS | GDK_KEY_RELEASE, NULL, GDK_CURRENT_TIME) == GDK_GRAB_SUCCESS)
491 492 493 494
#endif
			{
				cnnhld->cnnwin->priv->kbcaptured = TRUE;
			}
495
		}else {
496 497 498
			remmina_connection_holder_keyboard_ungrab(cnnhld);
		}
	}
499 500
}

Giovanni Panozzo's avatar
Giovanni Panozzo committed
501 502 503 504 505 506 507 508
static void remmina_connection_window_close_all_connections(RemminaConnectionWindow* cnnwin)
{
	RemminaConnectionWindowPriv* priv = cnnwin->priv;
	GtkNotebook* notebook = GTK_NOTEBOOK(priv->notebook);
	GtkWidget* w;
	RemminaConnectionObject* cnnobj;
	gint i, n;

Antenore Gatta's avatar
Antenore Gatta committed
509
	if (GTK_IS_WIDGET(notebook)) {
Giovanni Panozzo's avatar
Giovanni Panozzo committed
510
		n = gtk_notebook_get_n_pages(notebook);
Antenore Gatta's avatar
Antenore Gatta committed
511
		for (i = n - 1; i >= 0; i--) {
Giovanni Panozzo's avatar
Giovanni Panozzo committed
512
			w = gtk_notebook_get_nth_page(notebook, i);
Antenore Gatta's avatar
Antenore Gatta committed
513
			cnnobj = (RemminaConnectionObject*)g_object_get_data(G_OBJECT(w), "cnnobj");
Giovanni Panozzo's avatar
Giovanni Panozzo committed
514 515 516 517 518 519 520
			/* Do close the connection on this tab */
			remmina_protocol_widget_close_connection(REMMINA_PROTOCOL_WIDGET(cnnobj->proto));
		}
	}

}

521
gboolean remmina_connection_window_delete(RemminaConnectionWindow* cnnwin)
522
{
523
	TRACE_CALL(__func__);
Giovanni Panozzo's avatar
Giovanni Panozzo committed
524 525
	RemminaConnectionWindowPriv* priv = cnnwin->priv;
	RemminaConnectionHolder *cnnhld = cnnwin->priv->cnnhld;
526 527
	GtkNotebook* notebook = GTK_NOTEBOOK(priv->notebook);
	GtkWidget* dialog;
528 529
	gint i, n;

Giovanni Panozzo's avatar
Giovanni Panozzo committed
530
	if (!REMMINA_IS_CONNECTION_WINDOW(cnnwin))
531
		return TRUE;
Giovanni Panozzo's avatar
Giovanni Panozzo committed
532

533 534
	if (cnnwin->priv->on_delete_confirm_mode != REMMINA_CONNECTION_WINDOW_ONDELETE_NOCONFIRM) {
		n = gtk_notebook_get_n_pages(notebook);
Antenore Gatta's avatar
Antenore Gatta committed
535
		if (n > 1) {
536
			dialog = gtk_message_dialog_new(GTK_WINDOW(cnnwin), GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION,
Antenore Gatta's avatar
Antenore Gatta committed
537 538
				GTK_BUTTONS_YES_NO,
				_("There are %i active connections in the current window. Are you sure to close?"), n);
539 540 541 542 543
			i = gtk_dialog_run(GTK_DIALOG(dialog));
			gtk_widget_destroy(dialog);
			if (i != GTK_RESPONSE_YES)
				return FALSE;
		}
544
	}
Giovanni Panozzo's avatar
Giovanni Panozzo committed
545
	remmina_connection_window_close_all_connections(cnnwin);
Giovanni Panozzo's avatar
Giovanni Panozzo committed
546

547 548 549 550 551
	/* After remmina_connection_window_close_all_connections() call, cnnwin
	 * has been already destroyed during a last page of notebook removal.
	 * So we must rely on cnnhld */
	if (cnnhld->cnnwin != NULL && GTK_IS_WIDGET(cnnhld->cnnwin))
		gtk_widget_destroy(GTK_WIDGET(cnnhld->cnnwin));
Giovanni Panozzo's avatar
Giovanni Panozzo committed
552 553
	cnnhld->cnnwin = NULL;

554
	return TRUE;
Giovanni Panozzo's avatar
Giovanni Panozzo committed
555 556 557 558
}

static gboolean remmina_connection_window_delete_event(GtkWidget* widget, GdkEvent* event, gpointer data)
{
559
	TRACE_CALL(__func__);
Giovanni Panozzo's avatar
Giovanni Panozzo committed
560
	remmina_connection_window_delete(REMMINA_CONNECTION_WINDOW(widget));
561 562 563
	return TRUE;
}

564
static void remmina_connection_window_destroy(GtkWidget* widget, RemminaConnectionHolder* cnnhld)
565
{
566
	TRACE_CALL(__func__);
567
	RemminaConnectionWindowPriv* priv = REMMINA_CONNECTION_WINDOW(widget)->priv;
568

569 570 571
	if (priv->kbcaptured)
		remmina_connection_holder_keyboard_ungrab(cnnhld);

Antenore Gatta's avatar
Antenore Gatta committed
572
	if (priv->floating_toolbar_motion_handler) {
573 574 575
		g_source_remove(priv->floating_toolbar_motion_handler);
		priv->floating_toolbar_motion_handler = 0;
	}
Antenore Gatta's avatar
Antenore Gatta committed
576
	if (priv->ftb_hide_eventsource) {
Giovanni Panozzo's avatar
Giovanni Panozzo committed
577 578
		g_source_remove(priv->ftb_hide_eventsource);
		priv->ftb_hide_eventsource = 0;
579
	}
Antenore Gatta's avatar
Antenore Gatta committed
580
	if (priv->savestate_eventsourceid) {
581 582 583
		g_source_remove(priv->savestate_eventsourceid);
		priv->savestate_eventsourceid = 0;
	}
Giovanni Panozzo's avatar
Giovanni Panozzo committed
584 585 586 587 588

#if FLOATING_TOOLBAR_WIDGET
	/* There is no need to destroy priv->floating_toolbar_widget,
	 * because it's our child and will be destroyed automatically */
#else
Antenore Gatta's avatar
Antenore Gatta committed
589
	if (priv->floating_toolbar_window != NULL) {
Giovanni Panozzo's avatar
Giovanni Panozzo committed
590 591
		gtk_widget_destroy(priv->floating_toolbar_window);
		priv->floating_toolbar_window = NULL;
592
	}
Giovanni Panozzo's avatar
Giovanni Panozzo committed
593
#endif
594
	/* Timer used to hide the toolbar */
Antenore Gatta's avatar
Antenore Gatta committed
595
	if (priv->hidetb_timer) {
596
		g_source_remove(priv->hidetb_timer);
Antenore Gatta's avatar
Antenore Gatta committed
597
		priv->hidetb_timer = 0;
598
	}
Antenore Gatta's avatar
Antenore Gatta committed
599
	if (priv->switch_page_handler) {
600 601 602 603
		g_source_remove(priv->switch_page_handler);
		priv->switch_page_handler = 0;
	}
	g_free(priv);
604

Antenore Gatta's avatar
Antenore Gatta committed
605
	if (GTK_WIDGET(cnnhld->cnnwin) == widget) {
606 607 608
		cnnhld->cnnwin->priv = NULL;
		cnnhld->cnnwin = NULL;
	}
Giovanni Panozzo's avatar
Giovanni Panozzo committed
609

610 611
}

612 613
gboolean remmina_connection_window_notify_widget_toolbar_placement(GtkWidget *widget, gpointer data)
{
614
	TRACE_CALL(__func__);
615 616 617 618 619 620 621 622 623 624
	GType rcwtype;
	rcwtype = remmina_connection_window_get_type();
	if (G_TYPE_CHECK_INSTANCE_TYPE(widget, rcwtype)) {
		g_signal_emit_by_name(G_OBJECT(widget), "toolbar-place");
		return TRUE;
	}
	return FALSE;
}

static gboolean remmina_connection_window_tb_drag_failed(GtkWidget *widget, GdkDragContext *context,
Antenore Gatta's avatar
Antenore Gatta committed
625
							 GtkDragResult result, gpointer user_data)
626
{
627
	TRACE_CALL(__func__);
628 629 630 631 632 633 634 635 636 637 638 639 640
	RemminaConnectionHolder* cnnhld;
	RemminaConnectionWindowPriv* priv;

	cnnhld = (RemminaConnectionHolder*)user_data;
	priv = cnnhld->cnnwin->priv;

	if (priv->toolbar)
		gtk_widget_show(GTK_WIDGET(priv->toolbar));

	return TRUE;
}

static gboolean remmina_connection_window_tb_drag_drop(GtkWidget *widget, GdkDragContext *context,
Antenore Gatta's avatar
Antenore Gatta committed
641
						       gint x, gint y, guint time, gpointer user_data)
642
{
643
	TRACE_CALL(__func__);
644 645 646 647 648 649 650 651 652 653 654 655 656 657 658
	GtkAllocation wa;
	gint new_toolbar_placement;
	RemminaConnectionHolder* cnnhld;
	RemminaConnectionWindowPriv* priv;

	cnnhld = (RemminaConnectionHolder*)user_data;
	priv = cnnhld->cnnwin->priv;

	gtk_widget_get_allocation(widget, &wa);

	if (wa.width * y >= wa.height * x) {
		if (wa.width * y > wa.height * ( wa.width - x) )
			new_toolbar_placement = TOOLBAR_PLACEMENT_BOTTOM;
		else
			new_toolbar_placement = TOOLBAR_PLACEMENT_LEFT;
659
	}else {
660 661 662 663 664 665 666 667
		if (wa.width * y > wa.height * ( wa.width - x) )
			new_toolbar_placement = TOOLBAR_PLACEMENT_RIGHT;
		else
			new_toolbar_placement = TOOLBAR_PLACEMENT_TOP;
	}

	gtk_drag_finish(context, TRUE, TRUE, time);

Antenore Gatta's avatar
Antenore Gatta committed
668
	if (new_toolbar_placement !=  remmina_pref.toolbar_placement) {
669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685
		/* Save new position */
		remmina_pref.toolbar_placement = new_toolbar_placement;
		remmina_pref_save();

		/* Signal all windows that the toolbar must be moved */
		remmina_widget_pool_foreach(remmina_connection_window_notify_widget_toolbar_placement, NULL);

	}
	if (priv->toolbar)
		gtk_widget_show(GTK_WIDGET(priv->toolbar));

	return TRUE;

}

static void remmina_connection_window_tb_drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer user_data)
{
686
	TRACE_CALL(__func__);
687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709

	cairo_surface_t *surface;
	cairo_t *cr;
	GtkAllocation wa;
	double dashes[] = { 10 };

	gtk_widget_get_allocation(widget, &wa);

	surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 16, 16);
	cr = cairo_create(surface);
	cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
	cairo_set_line_width(cr, 4);
	cairo_set_dash(cr, dashes, 1, 0 );
	cairo_rectangle(cr, 0, 0, 16, 16);
	cairo_stroke(cr);
	cairo_destroy(cr);

	gtk_widget_hide(widget);

	gtk_drag_set_icon_surface(context, surface);

}

710
static void remmina_connection_holder_update_toolbar_opacity(RemminaConnectionHolder* cnnhld)
711
{
712
	TRACE_CALL(__func__);
713
	DECLARE_CNNOBJ
Antenore Gatta's avatar
Antenore Gatta committed
714
	RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
715

Antenore Gatta's avatar
Antenore Gatta committed
716 717 718
	priv->floating_toolbar_opacity = (1.0 - TOOLBAR_OPACITY_MIN) / ((gdouble)TOOLBAR_OPACITY_LEVEL)
					 * ((gdouble)(TOOLBAR_OPACITY_LEVEL - remmina_file_get_int(cnnobj->remmina_file, "toolbar_opacity", 0)))
					 + TOOLBAR_OPACITY_MIN;
Giovanni Panozzo's avatar
Giovanni Panozzo committed
719
#if FLOATING_TOOLBAR_WIDGET
Antenore Gatta's avatar
Antenore Gatta committed
720
	if (priv->floating_toolbar_widget) {
Giovanni Panozzo's avatar
Giovanni Panozzo committed
721 722
		gtk_widget_set_opacity(GTK_WIDGET(priv->overlay_ftb_overlay), priv->floating_toolbar_opacity);
	}
723
#else
Antenore Gatta's avatar
Antenore Gatta committed
724
	if (priv->floating_toolbar_window) {
Antenore Gatta's avatar
Antenore Gatta committed
725
#if GTK_CHECK_VERSION(3, 8, 0)
Giovanni Panozzo's avatar
Giovanni Panozzo committed
726
		gtk_widget_set_opacity(GTK_WIDGET(priv->floating_toolbar_window), priv->floating_toolbar_opacity);
Antenore Gatta's avatar
Antenore Gatta committed
727
#else
Giovanni Panozzo's avatar
Giovanni Panozzo committed
728
		gtk_window_set_opacity(GTK_WINDOW(priv->floating_toolbar_window), priv->floating_toolbar_opacity);
Antenore Gatta's avatar
Antenore Gatta committed
729
#endif
730
	}
Giovanni Panozzo's avatar
Giovanni Panozzo committed
731
#endif
732 733
}

Giovanni Panozzo's avatar
Giovanni Panozzo committed
734
#if !FLOATING_TOOLBAR_WIDGET
735
static gboolean remmina_connection_holder_floating_toolbar_motion(RemminaConnectionHolder* cnnhld)
736
{
737
	TRACE_CALL(__func__);
Giovanni Panozzo's avatar
Giovanni Panozzo committed
738 739


740
	RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
741
	GtkRequisition req;
742
	gint x, y, t, cnnwin_x, cnnwin_y;
743

Antenore Gatta's avatar
Antenore Gatta committed
744
	if (priv->floating_toolbar_window == NULL) {
745 746 747 748
		priv->floating_toolbar_motion_handler = 0;
		return FALSE;
	}

Giovanni Panozzo's avatar
Giovanni Panozzo committed
749
	gtk_widget_get_preferred_size(priv->floating_toolbar_window, &req, NULL);
750

Giovanni Panozzo's avatar
Giovanni Panozzo committed
751
	gtk_window_get_position(GTK_WINDOW(priv->floating_toolbar_window), &x, &y);
752 753 754
	gtk_window_get_position(GTK_WINDOW(cnnhld->cnnwin), &cnnwin_x, &cnnwin_y );
	x -= cnnwin_x;
	y -= cnnwin_y;
755

Antenore Gatta's avatar
Antenore Gatta committed
756
	if (priv->floating_toolbar_motion_show || priv->floating_toolbar_motion_visible) {
757 758 759 760 761 762 763 764 765 766
		if (priv->floating_toolbar_motion_show)
			y += 2;
		else
			y -= 2;
		t = (priv->pin_down ? 18 : 2) - req.height;
		if (y > 0)
			y = 0;
		if (y < t)
			y = t;

Giovanni Panozzo's avatar
Giovanni Panozzo committed
767
		gtk_window_move(GTK_WINDOW(priv->floating_toolbar_window), x + cnnwin_x, y + cnnwin_y);
Antenore Gatta's avatar
Antenore Gatta committed
768
		if (remmina_pref.fullscreen_toolbar_visibility == FLOATING_TOOLBAR_VISIBILITY_INVISIBLE && !priv->pin_down) {
769
#if GTK_CHECK_VERSION(3, 8, 0)
Giovanni Panozzo's avatar
Giovanni Panozzo committed
770
			gtk_widget_set_opacity(GTK_WIDGET(priv->floating_toolbar_window),
Antenore Gatta's avatar
Antenore Gatta committed
771
				(gdouble)(y - t) / (gdouble)(-t) * priv->floating_toolbar_opacity);
772
#else
Giovanni Panozzo's avatar
Giovanni Panozzo committed
773
			gtk_window_set_opacity(GTK_WINDOW(priv->floating_toolbar_window),
Antenore Gatta's avatar
Antenore Gatta committed
774
				(gdouble)(y - t) / (gdouble)(-t) * priv->floating_toolbar_opacity);
775
#endif
776
		}
Antenore Gatta's avatar
Antenore Gatta committed
777
		if ((priv->floating_toolbar_motion_show && y >= 0) || (!priv->floating_toolbar_motion_show && y <= t)) {
778 779 780
			priv->floating_toolbar_motion_handler = 0;
			return FALSE;
		}
781
	}else {
Giovanni Panozzo's avatar
Giovanni Panozzo committed
782
		gtk_window_move(GTK_WINDOW(priv->floating_toolbar_window), x + cnnwin_x, -20 - req.height + cnnwin_y);
783 784 785 786 787 788
		priv->floating_toolbar_motion_handler = 0;
		return FALSE;
	}
	return TRUE;
}

789
static void remmina_connection_holder_floating_toolbar_update(RemminaConnectionHolder* cnnhld)
790
{
791
	TRACE_CALL(__func__);
792
	RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
793

Antenore Gatta's avatar
Antenore Gatta committed
794
	if (priv->floating_toolbar_motion_show || priv->floating_toolbar_motion_visible) {
795 796 797
		if (priv->floating_toolbar_motion_handler)
			g_source_remove(priv->floating_toolbar_motion_handler);
		priv->floating_toolbar_motion_handler = g_idle_add(
Antenore Gatta's avatar
Antenore Gatta committed
798
			(GSourceFunc)remmina_connection_holder_floating_toolbar_motion, cnnhld);
799
	}else {
Antenore Gatta's avatar
Antenore Gatta committed
800
		if (priv->floating_toolbar_motion_handler == 0) {
801
			priv->floating_toolbar_motion_handler = g_timeout_add(MOTION_TIME,
Antenore Gatta's avatar
Antenore Gatta committed
802
				(GSourceFunc)remmina_connection_holder_floating_toolbar_motion, cnnhld);
803 804 805
		}
	}
}
Giovanni Panozzo's avatar
Giovanni Panozzo committed
806 807 808 809 810
#endif /* !FLOATING_TOOLBAR_WIDGET */

#if FLOATING_TOOLBAR_WIDGET
static gboolean remmina_connection_holder_floating_toolbar_make_invisible(gpointer data)
{
811
	TRACE_CALL(__func__);
Giovanni Panozzo's avatar
Giovanni Panozzo committed
812 813 814 815 816 817
	RemminaConnectionWindowPriv* priv = (RemminaConnectionWindowPriv*)data;
	gtk_widget_set_opacity(GTK_WIDGET(priv->overlay_ftb_overlay), 0.0);
	priv->ftb_hide_eventsource = 0;
	return FALSE;
}
#endif
818

819
static void remmina_connection_holder_floating_toolbar_show(RemminaConnectionHolder* cnnhld, gboolean show)
820
{
821
	TRACE_CALL(__func__);
822
	RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
823

Giovanni Panozzo's avatar
Giovanni Panozzo committed
824 825 826 827
#if FLOATING_TOOLBAR_WIDGET
	if (priv->floating_toolbar_widget == NULL)
		return;

Antenore Gatta's avatar
Antenore Gatta committed
828
	if (show || priv->pin_down) {
Giovanni Panozzo's avatar
Giovanni Panozzo committed
829 830 831
		/* Make the FTB no longer transparent, in case we have an hidden toolbar */
		remmina_connection_holder_update_toolbar_opacity(cnnhld);
		/* Remove outstanding hide events, if not yet active */
Antenore Gatta's avatar
Antenore Gatta committed
832
		if (priv->ftb_hide_eventsource) {
Giovanni Panozzo's avatar
Giovanni Panozzo committed
833 834 835
			g_source_remove(priv->ftb_hide_eventsource);
			priv->ftb_hide_eventsource = 0;
		}
836
	}else {
Giovanni Panozzo's avatar
Giovanni Panozzo committed
837 838
		/* If we are hiding and the toolbar must be made invisible, schedule
		 * a later toolbar hide */
Antenore Gatta's avatar
Antenore Gatta committed
839
		if (remmina_pref.fullscreen_toolbar_visibility == FLOATING_TOOLBAR_VISIBILITY_INVISIBLE) {
Giovanni Panozzo's avatar
Giovanni Panozzo committed
840 841 842 843 844 845 846 847 848
			if (priv->ftb_hide_eventsource == 0)
				priv->ftb_hide_eventsource = g_timeout_add(1000, remmina_connection_holder_floating_toolbar_make_invisible, priv);
		}
	}

	gtk_revealer_set_reveal_child(GTK_REVEALER(priv->revealer), show || priv->pin_down);
#else

	if (priv->floating_toolbar_window == NULL)
849 850 851 852 853
		return;

	priv->floating_toolbar_motion_show = show;

	remmina_connection_holder_floating_toolbar_update(cnnhld);
Giovanni Panozzo's avatar
Giovanni Panozzo committed
854
#endif
855 856
}

857
static void remmina_connection_holder_floating_toolbar_visible(RemminaConnectionHolder* cnnhld, gboolean visible)
858
{
859
	TRACE_CALL(__func__);
Giovanni Panozzo's avatar
Giovanni Panozzo committed
860
#if !FLOATING_TOOLBAR_WIDGET
861
	RemminaConnectionWindowPriv* priv = cnnhld->cnnwin->priv;
862

Giovanni Panozzo's avatar
Giovanni Panozzo committed
863
	if (priv->floating_toolbar_window == NULL)
864 865 866 867 868
		return;

	priv->floating_toolbar_motion_visible = visible;

	remmina_connection_holder_floating_toolbar_update(cnnhld);
Giovanni Panozzo's avatar
Giovanni Panozzo committed
869
#endif
870 871
}

872
static void remmina_connection_holder_get_desktop_size(RemminaConnectionHolder* cnnhld, gint* width, gint* height)
873
{
874
	TRACE_CALL(__func__);
875
	DECLARE_CNNOBJ
Antenore Gatta's avatar
Antenore Gatta committed
876
	RemminaProtocolWidget* gp = REMMINA_PROTOCOL_WIDGET(cnnobj->proto);
877

878

879 880 881 882
	*width = remmina_protocol_widget_get_width(gp);
	*height = remmina_protocol_widget_get_height(gp);
}

883
static void remmina_connection_object_set_scrolled_policy(RemminaConnectionObject* cnnobj, GtkScrolledWindow* scrolled_window)
884
{
885
	TRACE_CALL(__func__);
886 887 888 889 890
	RemminaScaleMode scalemode;
	scalemode = get_current_allowed_scale_mode(cnnobj, NULL, NULL);
	gtk_scrolled_window_set_policy(scrolled_window,
		scalemode == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED ? GTK_POLICY_NEVER : GTK_POLICY_AUTOMATIC,
		scalemode == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED ? GTK_POLICY_NEVER : GTK_POLICY_AUTOMATIC);
891
}
892

893
static gboolean remmina_connection_holder_toolbar_autofit_restore(RemminaConnectionHolder* cnnhld)
894
{
895
	TRACE_CALL(__func__);
896
	DECLARE_CNNOBJ_WITH_RETURN(FALSE)
Antenore Gatta's avatar
Antenore Gatta committed
897
	RemminaConnectionWindowPriv * priv = cnnhld->cnnwin->priv;
898 899
	gint dwidth, dheight;
	GtkAllocation nba, ca, ta;
900

Antenore Gatta's avatar
Antenore Gatta committed
901
	if (cnnobj->connected && GTK_IS_SCROLLED_WINDOW(cnnobj->scrolled_container)) {
902 903
		remmina_connection_holder_get_desktop_size(cnnhld, &dwidth, &dheight);
		gtk_widget_get_allocation(priv->notebook, &nba);
904
		gtk_widget_get_allocation(cnnobj->scrolled_container, &ca);
905
		gtk_widget_get_allocation(priv->toolbar, &ta);
906
		if (remmina_pref.toolbar_placement == TOOLBAR_PLACEMENT_LEFT ||
Antenore Gatta's avatar
Antenore Gatta committed
907
		    remmina_pref.toolbar_placement == TOOLBAR_PLACEMENT_RIGHT) {
908
			gtk_window_resize(GTK_WINDOW(cnnhld->cnnwin), MAX(1, dwidth + ta.width + nba.width - ca.width),
Antenore Gatta's avatar
Antenore Gatta committed
909
				MAX(1, dheight + nba.height - ca.height));
910
		}else {
911
			gtk_window_resize(GTK_WINDOW(cnnhld->cnnwin), MAX(1, dwidth + nba.width - ca.width),
Antenore Gatta's avatar
Antenore Gatta committed
912
				MAX(1, dheight + ta.height + nba.height - ca.height));
913
		}
914 915
		gtk_container_check_resize(GTK_CONTAINER(cnnhld->cnnwin));
	}
Antenore Gatta's avatar
Antenore Gatta committed
916
	if (GTK_IS_SCROLLED_WINDOW(cnnobj->scrolled_container)) {
917 918 919
		remmina_connection_object_set_scrolled_policy(cnnobj, GTK_SCROLLED_WINDOW(cnnobj->scrolled_container));
	}
	return FALSE;
920 921
}

922
static void remmina_connection_holder_toolbar_autofit(GtkWidget* widget, RemminaConnectionHolder* cnnhld)
923
{
924
	TRACE_CALL(__func__);
925
	DECLARE_CNNOBJ
926

Antenore Gatta's avatar
Antenore Gatta committed
927 928 929 930
	if (GTK_IS_SCROLLED_WINDOW(cnnobj->scrolled_container)) {
		if ((gdk_window_get_state(gtk_widget_get_window(GTK_WIDGET(cnnhld->cnnwin))) & GDK_WINDOW_STATE_MAXIMIZED) != 0) {
			gtk_window_unmaximize(GTK_WINDOW(cnnhld->cnnwin));
		}
931

Antenore Gatta's avatar
Antenore Gatta committed
932 933 934 935
		/* It's tricky to make the toolbars disappear automatically, while keeping scrollable.
		   Please tell me if you know a better way to do this */
		gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(cnnobj->scrolled_container), GTK_POLICY_NEVER,
			GTK_POLICY_NEVER);
936

937
		/** @todo save returned source id in priv->something and then delete when main object is destroyed */
Antenore Gatta's avatar
Antenore Gatta committed
938 939
		g_timeout_add(200, (GSourceFunc)remmina_connection_holder_toolbar_autofit_restore, cnnhld);
	}
940 941 942 943

}


944
static void remmina_connection_holder_check_resize(RemminaConnectionHolder* cnnhld)
945
{
946
	TRACE_CALL(__func__);
947
	DECLARE_CNNOBJ
Antenore Gatta's avatar
Antenore Gatta committed
948
	gboolean scroll_required = FALSE;
949 950 951 952 953

#if GTK_CHECK_VERSION(3, 22, 0)
	GdkDisplay* display;
	GdkMonitor* monitor;
#else
954
	GdkScreen* screen;
955
	gint monitor;
956
#endif
957
	GdkRectangle screen_size;
958 959
	gint screen_width, screen_height;
	gint server_width, server_height;
Giovanni Panozzo's avatar
Giovanni Panozzo committed
960
	gint bordersz;
961

962
	remmina_connection_holder_get_desktop_size(cnnhld, &server_width, &server_height);
963 964 965 966
#if GTK_CHECK_VERSION(3, 22, 0)
	display = gtk_widget_get_display(GTK_WIDGET(cnnhld->cnnwin));
	monitor = gdk_display_get_monitor_at_window(display, gtk_widget_get_window(GTK_WIDGET(cnnhld->cnnwin)));
#else
967 968
	screen = gtk_window_get_screen(GTK_WINDOW(cnnhld->cnnwin));
	monitor = gdk_screen_get_monitor_at_window(screen, gtk_widget_get_window(GTK_WIDGET(cnnhld->cnnwin)));
969 970 971 972
#endif
#if GTK_CHECK_VERSION(3, 22, 0)
	gdk_monitor_get_workarea(monitor, &screen_size);
#elif gdk_screen_get_monitor_workarea
973 974 975 976 977 978
	gdk_screen_get_monitor_workarea(screen, monitor, &screen_size);
#else
	gdk_screen_get_monitor_geometry(screen, monitor, &screen_size);
#endif
	screen_width = screen_size.width;
	screen_height = screen_size.height;
979 980

	if (!remmina_protocol_widget_get_expand(REMMINA_PROTOCOL_WIDGET(cnnobj->proto))
Antenore Gatta's avatar
Antenore Gatta committed
981 982
	    && (server_width <= 0 || server_height <= 0 || screen_width < server_width
		|| screen_height < server_height)) {
983 984
		scroll_required = TRUE;
	}
985

Antenore Gatta's avatar
Antenore Gatta committed
986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001
	switch (cnnhld->cnnwin->priv->view_mode) {
	case SCROLLED_FULLSCREEN_MODE:
		gtk_window_resize(GTK_WINDOW(cnnhld->cnnwin), screen_width, screen_height);
		gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(cnnobj->scrolled_container),
			(scroll_required ? GTK_POLICY_AUTOMATIC : GTK_POLICY_NEVER),
			(scroll_required ? GTK_POLICY_AUTOMATIC : GTK_POLICY_NEVER));
		break;

	case VIEWPORT_FULLSCREEN_MODE:
		bordersz = scroll_required ? 1 : 0;
		gtk_window_resize(GTK_WINDOW(cnnhld->cnnwin), screen_width, screen_height);
		if (REMMINA_IS_SCROLLED_VIEWPORT(cnnobj->scrolled_container)) {
			/* Put a border around Notebook content (RemminaScrolledViewpord), so we can
			 * move the mouse over the border to scroll */
			gtk_container_set_border_width(GTK_CONTAINER(cnnobj->scrolled_container), bordersz);
		}
1002

Antenore Gatta's avatar
Antenore Gatta committed
1003 1004 1005 1006 1007 1008 1009 1010 1011
		break;

	case SCROLLED_WINDOW_MODE:
		if (remmina_file_get_int(cnnobj->remmina_file, "viewmode", AUTO_MODE) == AUTO_MODE) {
			gtk_window_set_default_size(GTK_WINDOW(cnnhld->cnnwin),
				MIN(server_width, screen_width), MIN(server_height, screen_height));
			if (server_width >= screen_width || server_height >= screen_height) {
				gtk_window_maximize(GTK_WINDOW(cnnhld->cnnwin));
				remmina_file_set_int(cnnobj->remmina_file, "window_maximize", TRUE);
1012
			}else {
Antenore Gatta's avatar
Antenore Gatta committed
1013 1014
				remmina_connection_holder_toolbar_autofit(NULL, cnnhld);
				remmina_file_set_int(cnnobj->remmina_file, "window_maximize", FALSE);
Jean-Louis Dupond's avatar
Jean-Louis Dupond committed
1015
			}
1016
		}else {
Antenore Gatta's avatar
Antenore Gatta committed
1017 1018
			if (remmina_file_get_int(cnnobj->remmina_file, "window_maximize", FALSE)) {
				gtk_window_maximize(GTK_WINDOW(cnnhld->cnnwin));
Jean-Louis Dupond's avatar
Jean-Louis Dupond committed
1019
			}
Antenore Gatta's avatar
Antenore Gatta committed
1020 1021
		}
		break;
Jean-Louis Dupond's avatar
Jean-Louis Dupond committed
1022

Antenore Gatta's avatar
Antenore Gatta committed
1023 1024
	default:
		break;
Jean-Louis Dupond's avatar
Jean-Louis Dupond committed
1025 1026
	}
}
1027

1028
static void remmina_connection_holder_set_tooltip(GtkWidget* item, const gchar* tip, guint key1, guint key2)
1029
{
1030
	TRACE_CALL(__func__);
1031 1032
	gchar* s1;
	gchar* s2;
1033

Antenore Gatta's avatar
Antenore Gatta committed
1034 1035
	if (remmina_pref.hostkey && key1) {
		if (key2) {
1036
			s1 = g_strdup_printf(" (%s + %s,%s)", gdk_keyval_name(remmina_pref.hostkey),
Antenore Gatta's avatar
Antenore Gatta committed
1037 1038
				gdk_keyval_name(gdk_keyval_to_upper(key1)), gdk_keyval_name(gdk_keyval_to_upper(key2)));
		}else if (key1 == remmina_pref.hostkey) {
Antenore Gatta's avatar
Antenore Gatta committed
1039
			s1 = g_strdup_printf(" (%s)", gdk_keyval_name(remmina_pref.hostkey));
1040
		}else {
Antenore Gatta's avatar
Antenore Gatta committed
1041
			s1 = g_strdup_printf(" (%s + %s)", gdk_keyval_name(remmina_pref.hostkey),
Antenore Gatta's avatar
Antenore Gatta committed
1042
				gdk_keyval_name(gdk_keyval_to_upper(key1)));
1043
		}
1044
	}else {
1045 1046 1047 1048 1049 1050
		s1 = NULL;
	}
	s2 = g_strdup_printf("%s%s", tip, s1 ? s1 : "");
	gtk_widget_set_tooltip_text(item, s2);
	g_free(s2);
	g_free(s1);
1051 1052
}

1053 1054
static void remmina_protocol_widget_update_alignment(RemminaConnectionObject* cnnobj)
{
1055
	TRACE_CALL(__func__);
1056
	RemminaScaleMode scalemode;
1057 1058 1059 1060
	gboolean scaledexpandedmode;
	int rdwidth, rdheight;
	gfloat aratio;

Antenore Gatta's avatar
Antenore Gatta committed
1061
	if (!cnnobj->plugin_can_scale) {
1062 1063
		/* If we have a plugin that cannot scale,
		 * (i.e. SFTP plugin), then we expand proto */
Antenore Gatta's avatar
Antenore Gatta committed
1064 1065
		gtk_widget_set_halign(GTK_WIDGET(cnnobj->proto), GTK_ALIGN_FILL);
		gtk_widget_set_valign(GTK_WIDGET(cnnobj->proto), GTK_ALIGN_FILL);
1066
	}else {
1067 1068
		/* Plugin can scale */

1069
		scalemode = get_current_allowed_scale_mode(cnnobj, NULL, NULL);
1070 1071 1072
		scaledexpandedmode = remmina_protocol_widget_get_expand(REMMINA_PROTOCOL_WIDGET(cnnobj->proto));

		/* Check if we need aspectframe and create/destroy it accordingly */
Antenore Gatta's avatar
Antenore Gatta committed
1073
		if (scalemode == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED && !scaledexpandedmode) {
1074 1075 1076 1077
			/* We need an aspectframe as a parent of proto */
			rdwidth = remmina_protocol_widget_get_width(REMMINA_PROTOCOL_WIDGET(cnnobj->proto));
			rdheight = remmina_protocol_widget_get_height(REMMINA_PROTOCOL_WIDGET(cnnobj->proto));
			aratio = (gfloat)rdwidth / (gfloat)rdheight;
Antenore Gatta's avatar
Antenore Gatta committed
1078
			if (!cnnobj->aspectframe) {
1079 1080
				/* We need a new aspectframe */
				cnnobj->aspectframe = gtk_aspect_frame_new(NULL, 0.5, 0.5, aratio, FALSE);
1081
				gtk_widget_set_name(cnnobj->aspectframe, "remmina-cw-aspectframe");
1082 1083 1084 1085 1086 1087 1088
				gtk_frame_set_shadow_type(GTK_FRAME(cnnobj->aspectframe), GTK_SHADOW_NONE);
				g_object_ref(cnnobj->proto);
				gtk_container_remove(GTK_CONTAINER(cnnobj->viewport), cnnobj->proto);
				gtk_container_add(GTK_CONTAINER(cnnobj->viewport), cnnobj->aspectframe);
				gtk_container_add(GTK_CONTAINER(cnnobj->aspectframe), cnnobj->proto);
				g_object_unref(cnnobj->proto);
				gtk_widget_show(cnnobj->aspectframe);
1089 1090
				if (cnnobj->cnnhld != NULL && cnnobj->cnnhld->cnnwin != NULL && cnnobj->cnnhld->cnnwin->priv->notebook != NULL)
					remmina_connection_holder_grab_focus(GTK_NOTEBOOK(cnnobj->cnnhld->cnnwin->priv->notebook));
1091
			}else {
1092 1093
				gtk_aspect_frame_set(GTK_ASPECT_FRAME(cnnobj->aspectframe), 0.5, 0.5, aratio, FALSE);
			}
1094
		}else {
1095
			/* We do not need an aspectframe as a parent of proto */
Antenore Gatta's avatar
Antenore Gatta committed
1096
			if (cnnobj->aspectframe) {
1097 1098 1099 1100 1101 1102 1103 1104 1105
				/* We must remove the old aspectframe reparenting proto to viewport */
				g_object_ref(cnnobj->aspectframe);
				g_object_ref(cnnobj->proto);
				gtk_container_remove(GTK_CONTAINER(cnnobj->aspectframe), cnnobj->proto);
				gtk_container_remove(GTK_CONTAINER(cnnobj->viewport), cnnobj->aspectframe);
				g_object_unref(cnnobj->aspectframe);
				cnnobj->aspectframe = NULL;
				gtk_container_add(GTK_CONTAINER(cnnobj->viewport), cnnobj->proto);
				g_object_unref(cnnobj->proto);
1106 1107
				if (cnnobj->cnnhld != NULL && cnnobj->cnnhld->cnnwin != NULL && cnnobj->cnnhld->cnnwin->priv->notebook != NULL)
					remmina_connection_holder_grab_focus(GTK_NOTEBOOK(cnnobj->cnnhld->cnnwin->priv->notebook));
1108 1109 1110
			}
		}

Antenore Gatta's avatar
Antenore Gatta committed
1111
		if (scalemode == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED || scalemode == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES) {
1112 1113 1114
			/* We have a plugin that can be scaled, and the scale button
			 * has been pressed. Give it the correct WxH maintaining aspect
			 * ratio of remote destkop size */
Antenore Gatta's avatar
Antenore Gatta committed
1115 1116
			gtk_widget_set_halign(GTK_WIDGET(cnnobj->proto), GTK_ALIGN_FILL);
			gtk_widget_set_valign(GTK_WIDGET(cnnobj->proto), GTK_ALIGN_FILL);
1117
		}else {
1118 1119
			/* Plugin can scale, but no scaling is active. Ensure that we have
			 * aspectframe with a ratio of 1 */
Antenore Gatta's avatar
Antenore Gatta committed
1120 1121
			gtk_widget_set_halign(GTK_WIDGET(cnnobj->proto), GTK_ALIGN_CENTER);
			gtk_widget_set_valign(GTK_WIDGET(cnnobj->proto), GTK_ALIGN_CENTER);
1122 1123 1124 1125 1126
		}
	}
}


1127
static void remmina_connection_holder_toolbar_fullscreen(GtkWidget* widget, RemminaConnectionHolder* cnnhld)
1128
{
1129
	TRACE_CALL(__func__);
Antenore Gatta's avatar
Antenore Gatta committed
1130
	if (gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(widget))) {
1131
		remmina_connection_holder_create_fullscreen(cnnhld, NULL, cnnhld->fullscreen_view_mode);
1132
	}else {
1133 1134 1135 1136
		remmina_connection_holder_create_scrolled(cnnhld, NULL);
	}
}

1137
static void remmina_connection_holder_viewport_fullscreen_mode(GtkWidget* widget, RemminaConnectionHolder* cnnhld)
1138
{
1139
	TRACE_CALL(__func__);
1140 1141 1142 1143 1144 1145
	if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
		return;
	cnnhld->fullscreen_view_mode = VIEWPORT_FULLSCREEN_MODE;
	remmina_connection_holder_create_fullscreen(cnnhld, NULL, cnnhld->fullscreen_view_mode);
}

1146
static void remmina_connection_holder_scrolled_fullscreen_mode(GtkWidget* widget, RemminaConnectionHolder* cnnhld)
1147
{
1148
	TRACE_CALL(__func__);
1149 1150 1151 1152 1153 1154
	if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
		return;
	cnnhld->fullscreen_view_mode = SCROLLED_FULLSCREEN_MODE;
	remmina_connection_holder_create_fullscreen(cnnhld, NULL, cnnhld->fullscreen_view_mode);
}

1155
static void remmina_connection_holder_fullscreen_option_popdown(GtkWidget