/* * Remmina - The GTK+ Remote Desktop Client * Copyright (C) 2009-2011 Vic Lee * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo * Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo * * 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. * */ #include "config.h" #ifdef GDK_WINDOWING_X11 #include #else #include #endif #include #include #include #include #include "remmina.h" #include "remmina_main.h" #include "rcw.h" #include "remmina_applet_menu_item.h" #include "remmina_applet_menu.h" #include "remmina_file.h" #include "remmina_file_manager.h" #include "remmina_log.h" #include "remmina_message_panel.h" #include "remmina_ext_exec.h" #include "remmina_plugin_manager.h" #include "remmina_pref.h" #include "remmina_protocol_widget.h" #include "remmina_public.h" #include "remmina_scrolled_viewport.h" #include "remmina_unlock.h" #include "remmina_utils.h" #include "remmina_widget_pool.h" #include "remmina/remmina_trace_calls.h" #ifdef GDK_WINDOWING_WAYLAND #include #endif #define DEBUG_KB_GRABBING 0 #include "remmina_exec.h" gchar *remmina_pref_file; RemminaPref remmina_pref; G_DEFINE_TYPE(RemminaConnectionWindow, rcw, GTK_TYPE_WINDOW) #define MOTION_TIME 100 /* default timeout used to hide the floating toolbar when switching profile */ #define TB_HIDE_TIME_TIME 1500 #define FULL_SCREEN_TARGET_MONITOR_UNDEFINED -1 struct _RemminaConnectionWindowPriv { GtkNotebook * notebook; GtkWidget * floating_toolbar_widget; GtkWidget * overlay; GtkWidget * revealer; GtkWidget * overlay_ftb_overlay; GtkWidget * overlay_ftb_fr; GtkWidget * floating_toolbar_label; gdouble floating_toolbar_opacity; /* Various delayed and timer event source ids */ guint acs_eventsourceid; // timeout guint spf_eventsourceid; // idle guint grab_retry_eventsourceid; // timeout guint delayed_grab_eventsourceid; guint ftb_hide_eventsource; // timeout guint tar_eventsource; // timeout guint hidetb_eventsource; // timeout guint dwp_eventsourceid; // timeout GtkWidget * toolbar; GtkWidget * grid; /* Toolitems that need to be handled */ GtkToolItem * toolitem_menu; GtkToolItem * toolitem_autofit; GtkToolItem * toolitem_fullscreen; GtkToolItem * toolitem_switch_page; GtkToolItem * toolitem_dynres; GtkToolItem * toolitem_scale; GtkToolItem * toolitem_grab; GtkToolItem * toolitem_multimon; GtkToolItem * toolitem_preferences; GtkToolItem * toolitem_tools; GtkToolItem * toolitem_new; GtkToolItem * toolitem_duplicate; GtkToolItem * toolitem_screenshot; GtkWidget * fullscreen_option_button; GtkWidget * fullscreen_scaler_button; GtkWidget * scaler_option_button; GtkWidget * pin_button; gboolean pin_down; gboolean sticky; /* Flag to turn off toolbar signal handling when toolbar is * reconfiguring, usually due to a tab switch */ gboolean toolbar_is_reconfiguring; /* This is the current view mode, i.e. VIEWPORT_FULLSCREEN_MODE, * as saved on the "viwemode" profile preference file */ gint view_mode; /* Status variables used when in fullscreen mode. Needed * to restore a fullscreen mode after coming from scrolled */ gint fss_view_mode; /* Status variables used when in scrolled window mode. Needed * to restore a scrolled window mode after coming from fullscreen */ gint ss_width, ss_height; gboolean ss_maximized; gboolean kbcaptured; gboolean pointer_captured; gboolean hostkey_activated; gboolean hostkey_used; gboolean pointer_entered; RemminaConnectionWindowOnDeleteConfirmMode on_delete_confirm_mode; }; typedef struct _RemminaConnectionObject { RemminaConnectionWindow * cnnwin; RemminaFile * remmina_file; GtkWidget * proto; GtkWidget * aspectframe; GtkWidget * viewport; GtkWidget * scrolled_container; gboolean plugin_can_scale; gboolean connected; gboolean dynres_unlocked; gulong deferred_open_size_allocate_handler; } RemminaConnectionObject; enum { TOOLBARPLACE_SIGNAL, LAST_SIGNAL }; static guint rcw_signals[LAST_SIGNAL] = { 0 }; static RemminaConnectionWindow *rcw_create_scrolled(gint width, gint height, gboolean maximize); static RemminaConnectionWindow *rcw_create_fullscreen(GtkWindow *old, gint view_mode); static gboolean rcw_hostkey_func(RemminaProtocolWidget *gp, guint keyval, gboolean release); static GtkWidget *rco_create_tab_page(RemminaConnectionObject *cnnobj); static GtkWidget *rco_create_tab_label(RemminaConnectionObject *cnnobj); void rcw_grab_focus(RemminaConnectionWindow *cnnwin); static GtkWidget *rcw_create_toolbar(RemminaConnectionWindow *cnnwin, gint mode); static void rcw_place_toolbar(GtkToolbar *toolbar, GtkGrid *grid, GtkWidget *sibling, int toolbar_placement); static void rco_update_toolbar(RemminaConnectionObject *cnnobj); static void rcw_keyboard_grab(RemminaConnectionWindow *cnnwin); static GtkWidget *rcw_append_new_page(RemminaConnectionWindow *cnnwin, RemminaConnectionObject *cnnobj); static void rcw_ftb_drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer user_data); static const GtkTargetEntry dnd_targets_ftb[] = { { (char *)"text/x-remmina-ftb", GTK_TARGET_SAME_APP | GTK_TARGET_OTHER_WIDGET, 0 }, }; static const GtkTargetEntry dnd_targets_tb[] = { { (char *)"text/x-remmina-tb", GTK_TARGET_SAME_APP, 0 }, }; static void rcw_class_init(RemminaConnectionWindowClass *klass) { TRACE_CALL(__func__); GtkCssProvider *provider; provider = gtk_css_provider_new(); /* 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 */ #if GTK_CHECK_VERSION(3, 14, 0) 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" "}\n" "GtkToolbar {\n" " -GtkWidget-window-dragging: 0;\n" "}\n" "#remmina-connection-window-fullscreen {\n" " border-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-tab-page {\n" " background-color: black;\n" "}\n" "#remmina-scrolled-container {\n" "}\n" "#remmina-scrolled-container.undershoot {\n" " background: none;\n" "}\n" "#remmina-tab-page {\n" "}\n" "#ftbbox-upper {\n" " background-color: white;\n" " color: black;\n" " border-style: none solid solid solid;\n" " border-width: 1px;\n" " border-radius: 4px;\n" " padding: 0px;\n" "}\n" "#ftbbox-lower {\n" " background-color: white;\n" " color: black;\n" " border-style: solid solid none solid;\n" " border-width: 1px;\n" " border-radius: 4px;\n" " padding: 0px;\n" "}\n" "#ftb-handle {\n" "}\n" ".message_panel {\n" " border: 0px solid;\n" " padding: 20px 20px 20px 20px;\n" "}\n" ".message_panel entry {\n" " background-image: none;\n" " border-width: 4px;\n" " border-radius: 8px;\n" "}\n" ".message_panel .title_label {\n" " font-size: 2em; \n" "}\n" , -1, NULL); #else 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" "#remmina-cw-message-panel {\n" "}\n" "GtkDrawingArea {\n" "}\n" "GtkToolbar {\n" " -GtkWidget-window-dragging: 0;\n" "}\n" "#remmina-connection-window-fullscreen {\n" " border-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" "}\n" "#remmina-scrolled-container.undershoot {\n" " background: none\n" "}\n" "#remmina-tab-page {\n" "}\n" "#ftbbox-upper {\n" " border-style: none solid solid solid;\n" " border-width: 1px;\n" " border-radius: 4px;\n" " padding: 0px;\n" "}\n" "#ftbbox-lower {\n" " border-style: solid solid none solid;\n" " border-width: 1px;\n" " border-radius: 4px;\n" " padding: 0px;\n" "}\n" "#ftb-handle {\n" "}\n" , -1, NULL); #endif gtk_style_context_add_provider_for_screen(gdk_screen_get_default(), GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); g_object_unref(provider); /* Define a signal used to notify all rcws of toolbar move */ rcw_signals[TOOLBARPLACE_SIGNAL] = g_signal_new("toolbar-place", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaConnectionWindowClass, toolbar_place), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } static RemminaConnectionObject *rcw_get_cnnobj_at_page(RemminaConnectionWindow *cnnwin, gint npage) { GtkWidget *po; if (!cnnwin->priv->notebook) return NULL; po = gtk_notebook_get_nth_page(GTK_NOTEBOOK(cnnwin->priv->notebook), npage); return g_object_get_data(G_OBJECT(po), "cnnobj"); } static RemminaConnectionObject *rcw_get_visible_cnnobj(RemminaConnectionWindow *cnnwin) { gint np; if (cnnwin != NULL && cnnwin->priv != NULL && cnnwin->priv->notebook != NULL) { np = gtk_notebook_get_current_page(GTK_NOTEBOOK(cnnwin->priv->notebook)); if (np < 0) return NULL; return rcw_get_cnnobj_at_page(cnnwin, np); } else { return NULL; } } static RemminaScaleMode get_current_allowed_scale_mode(RemminaConnectionObject *cnnobj, gboolean *dynres_avail, gboolean *scale_avail) { TRACE_CALL(__func__); 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), REMMINA_PROTOCOL_FEATURE_TYPE_DYNRESUPDATE); plugin_can_scale = remmina_protocol_widget_query_feature_by_type(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), REMMINA_PROTOCOL_FEATURE_TYPE_SCALE); /* Forbid scalemode REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES when not possible */ if ((!plugin_has_dynres) && scalemode == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES) 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; } static void rco_disconnect_current_page(RemminaConnectionObject *cnnobj) { TRACE_CALL(__func__); /* Disconnects the connection which is currently in view in the notebook */ remmina_protocol_widget_close_connection(REMMINA_PROTOCOL_WIDGET(cnnobj->proto)); } static void rcw_kp_ungrab(RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); GdkDisplay *display; #if GTK_CHECK_VERSION(3, 20, 0) GdkSeat *seat; #else GdkDeviceManager *manager; GdkDevice *keyboard = NULL; #endif if (cnnwin->priv->grab_retry_eventsourceid) { g_source_remove(cnnwin->priv->grab_retry_eventsourceid); cnnwin->priv->grab_retry_eventsourceid = 0; } if (cnnwin->priv->delayed_grab_eventsourceid) { g_source_remove(cnnwin->priv->delayed_grab_eventsourceid); cnnwin->priv->delayed_grab_eventsourceid = 0; } display = gtk_widget_get_display(GTK_WIDGET(cnnwin)); #if GTK_CHECK_VERSION(3, 20, 0) seat = gdk_display_get_default_seat(display); // keyboard = gdk_seat_get_pointer(seat); #else manager = gdk_display_get_device_manager(display); keyboard = gdk_device_manager_get_client_pointer(manager); #endif if (!cnnwin->priv->kbcaptured && !cnnwin->priv->pointer_captured) return; #if DEBUG_KB_GRABBING printf("DEBUG_KB_GRABBING: --- ungrabbing\n"); #endif #if GTK_CHECK_VERSION(3, 20, 0) /* We can use gtk_seat_grab()/_ungrab() only after GTK 3.24 */ gdk_seat_ungrab(seat); #else if (keyboard != NULL) { if (gdk_device_get_source(keyboard) != GDK_SOURCE_KEYBOARD) keyboard = gdk_device_get_associated_device(keyboard); G_GNUC_BEGIN_IGNORE_DEPRECATIONS gdk_device_ungrab(keyboard, GDK_CURRENT_TIME); G_GNUC_END_IGNORE_DEPRECATIONS } #endif cnnwin->priv->kbcaptured = FALSE; cnnwin->priv->pointer_captured = FALSE; } static gboolean rcw_keyboard_grab_retry(gpointer user_data) { TRACE_CALL(__func__); RemminaConnectionWindow *cnnwin = (RemminaConnectionWindow *)user_data; #if DEBUG_KB_GRABBING printf("%s retry grab\n", __func__); #endif rcw_keyboard_grab(cnnwin); cnnwin->priv->grab_retry_eventsourceid = 0; return G_SOURCE_REMOVE; } static void rcw_pointer_ungrab(RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); #if GTK_CHECK_VERSION(3, 20, 0) GdkSeat *seat; GdkDisplay *display; if (!cnnwin->priv->pointer_captured) return; display = gtk_widget_get_display(GTK_WIDGET(cnnwin)); seat = gdk_display_get_default_seat(display); gdk_seat_ungrab(seat); #endif } static void rcw_pointer_grab(RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); /* This function in Wayland is useless and generates a spurious leave-notify event. * Should we remove it ? https://gitlab.gnome.org/GNOME/mutter/-/issues/2450#note_1588081 */ #if GTK_CHECK_VERSION(3, 20, 0) GdkSeat *seat; GdkDisplay *display; GdkGrabStatus ggs; if (cnnwin->priv->pointer_captured) { #if DEBUG_KB_GRABBING printf("DEBUG_KB_GRABBING: pointer_captured is true, it should not\n"); #endif return; } display = gtk_widget_get_display(GTK_WIDGET(cnnwin)); seat = gdk_display_get_default_seat(display); ggs = gdk_seat_grab(seat, gtk_widget_get_window(GTK_WIDGET(cnnwin)), GDK_SEAT_CAPABILITY_ALL_POINTING, TRUE, NULL, NULL, NULL, NULL); if (ggs != GDK_GRAB_SUCCESS) { #if DEBUG_KB_GRABBING printf("DEBUG_KB_GRABBING: GRAB of POINTER failed. GdkGrabStatus: %d\n", (int)ggs); #endif } else { cnnwin->priv->pointer_captured = TRUE; } #endif } static void rcw_keyboard_grab(RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); GdkDisplay *display; #if GTK_CHECK_VERSION(3, 20, 0) GdkSeat *seat; #else GdkDeviceManager *manager; #endif GdkGrabStatus ggs; GdkDevice *keyboard = NULL; if (cnnwin->priv->kbcaptured) { #if DEBUG_KB_GRABBING printf("DEBUG_KB_GRABBING: %s not grabbing because already grabbed.\n", __func__); #endif return; } display = gtk_widget_get_display(GTK_WIDGET(cnnwin)); #if GTK_CHECK_VERSION(3, 20, 0) seat = gdk_display_get_default_seat(display); keyboard = gdk_seat_get_pointer(seat); #else manager = gdk_display_get_device_manager(display); keyboard = gdk_device_manager_get_client_pointer(manager); #endif if (keyboard != NULL) { if (gdk_device_get_source(keyboard) != GDK_SOURCE_KEYBOARD) keyboard = gdk_device_get_associated_device(keyboard); #if DEBUG_KB_GRABBING printf("DEBUG_KB_GRABBING: profile asks for grabbing, let’s try.\n"); #endif /* Up to GTK version 3.20 we can grab the keyboard with gdk_device_grab(). * in GTK 3.20 gdk_seat_grab() should be used instead of gdk_device_grab(). * There is a bug in GTK up to 3.22: When gdk_device_grab() fails * the widget is hidden: * https://gitlab.gnome.org/GNOME/gtk/commit/726ad5a5ae7c4f167e8dd454cd7c250821c400ab * The bugfix will be released with GTK 3.24. * Also please note that the newer gdk_seat_grab() is still calling gdk_device_grab(). * * Warning: gdk_seat_grab() will call XGrabKeyboard() or XIGrabDevice() * which in turn will generate a core X input event FocusOut and FocusIn * but not Xinput2 events. * In some cases, GTK is unable to neutralize FocusIn and FocusOut core * events (ie: i3wm+Plasma with GDK_CORE_DEVICE_EVENTS=1 because detail=NotifyNonlinear * instead of detail=NotifyAncestor/detail=NotifyInferior) * Receiving a FocusOut event for Remmina at this time will cause an infinite loop. * Therefore is important for GTK to use Xinput2 instead of core X events * by unsetting GDK_CORE_DEVICE_EVENTS */ #if GTK_CHECK_VERSION(3, 20, 0) ggs = gdk_seat_grab(seat, gtk_widget_get_window(GTK_WIDGET(cnnwin)), GDK_SEAT_CAPABILITY_KEYBOARD, TRUE, NULL, NULL, NULL, NULL); #else ggs = gdk_device_grab(keyboard, gtk_widget_get_window(GTK_WIDGET(cnnwin)), GDK_OWNERSHIP_WINDOW, TRUE, GDK_KEY_PRESS | GDK_KEY_RELEASE, NULL, GDK_CURRENT_TIME); #endif if (ggs != GDK_GRAB_SUCCESS) { #if DEBUG_KB_GRABBING printf("GRAB of keyboard failed.\n"); #endif /* Reschedule grabbing in half a second if not already done */ if (cnnwin->priv->grab_retry_eventsourceid == 0) cnnwin->priv->grab_retry_eventsourceid = g_timeout_add(500, (GSourceFunc)rcw_keyboard_grab_retry, cnnwin); } else { #if DEBUG_KB_GRABBING printf("Keyboard grabbed\n"); #endif if (cnnwin->priv->grab_retry_eventsourceid != 0) { g_source_remove(cnnwin->priv->grab_retry_eventsourceid); cnnwin->priv->grab_retry_eventsourceid = 0; } cnnwin->priv->kbcaptured = TRUE; } } else { rcw_kp_ungrab(cnnwin); } } static void rcw_close_all_connections(RemminaConnectionWindow *cnnwin) { RemminaConnectionWindowPriv *priv = cnnwin->priv; GtkNotebook *notebook = GTK_NOTEBOOK(priv->notebook); GtkWidget *w; RemminaConnectionObject *cnnobj; gint i, n; if (GTK_IS_WIDGET(notebook)) { n = gtk_notebook_get_n_pages(notebook); for (i = n - 1; i >= 0; i--) { w = gtk_notebook_get_nth_page(notebook, i); cnnobj = (RemminaConnectionObject *)g_object_get_data(G_OBJECT(w), "cnnobj"); /* Do close the connection on this tab */ remmina_protocol_widget_close_connection(REMMINA_PROTOCOL_WIDGET(cnnobj->proto)); } } } gboolean rcw_delete(RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); RemminaConnectionWindowPriv *priv = cnnwin->priv; GtkNotebook *notebook = GTK_NOTEBOOK(priv->notebook); GtkWidget *dialog; gint i, n, nopen; if (!REMMINA_IS_CONNECTION_WINDOW(cnnwin)) return TRUE; if (cnnwin->priv->on_delete_confirm_mode != RCW_ONDELETE_NOCONFIRM) { n = gtk_notebook_get_n_pages(notebook); nopen = 0; /* count all non-closed connections */ for(i = 0; i < n; i ++) { RemminaConnectionObject *cnnobj = rcw_get_cnnobj_at_page(cnnwin, i); if (!remmina_protocol_widget_is_closed((RemminaProtocolWidget *)cnnobj->proto)) nopen ++; } if (nopen > 1) { dialog = gtk_message_dialog_new(GTK_WINDOW(cnnwin), GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, _("Are you sure you want to close %i active connections in the current window?"), nopen); i = gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); if (i != GTK_RESPONSE_YES) return FALSE; } else if (nopen == 1) { if (remmina_pref.confirm_close) { dialog = gtk_message_dialog_new(GTK_WINDOW(cnnwin), GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, _("Are you sure you want to close this last active connection?")); i = gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); if (i != GTK_RESPONSE_YES) return FALSE; } } } rcw_close_all_connections(cnnwin); return TRUE; } static gboolean rcw_delete_event(GtkWidget *widget, GdkEvent *event, gpointer data) { TRACE_CALL(__func__); rcw_delete(RCW(widget)); return TRUE; } static void rcw_destroy(GtkWidget *widget, gpointer data) { TRACE_CALL(__func__); RemminaConnectionWindowPriv *priv; RemminaConnectionWindow *cnnwin; if (!REMMINA_IS_CONNECTION_WINDOW(widget)) return; cnnwin = (RemminaConnectionWindow *)widget; priv = cnnwin->priv; if (priv->kbcaptured) rcw_kp_ungrab(cnnwin); if (priv->acs_eventsourceid) { g_source_remove(priv->acs_eventsourceid); priv->acs_eventsourceid = 0; } if (priv->spf_eventsourceid) { g_source_remove(priv->spf_eventsourceid); priv->spf_eventsourceid = 0; } if (priv->grab_retry_eventsourceid) { g_source_remove(priv->grab_retry_eventsourceid); priv->grab_retry_eventsourceid = 0; } if (cnnwin->priv->delayed_grab_eventsourceid) { g_source_remove(cnnwin->priv->delayed_grab_eventsourceid); cnnwin->priv->delayed_grab_eventsourceid = 0; } if (priv->ftb_hide_eventsource) { g_source_remove(priv->ftb_hide_eventsource); priv->ftb_hide_eventsource = 0; } if (priv->tar_eventsource) { g_source_remove(priv->tar_eventsource); priv->tar_eventsource = 0; } if (priv->hidetb_eventsource) { g_source_remove(priv->hidetb_eventsource); priv->hidetb_eventsource = 0; } if (priv->dwp_eventsourceid) { g_source_remove(priv->dwp_eventsourceid); priv->dwp_eventsourceid = 0; } /* There is no need to destroy priv->floating_toolbar_widget, * because it’s our child and will be destroyed automatically */ cnnwin->priv = NULL; g_free(priv); } gboolean rcw_notify_widget_toolbar_placement(GtkWidget *widget, gpointer data) { TRACE_CALL(__func__); GType rcwtype; rcwtype = rcw_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 rcw_tb_drag_failed(GtkWidget *widget, GdkDragContext *context, GtkDragResult result, gpointer user_data) { TRACE_CALL(__func__); RemminaConnectionWindowPriv *priv; RemminaConnectionWindow *cnnwin; cnnwin = (RemminaConnectionWindow *)user_data; priv = cnnwin->priv; if (priv->toolbar) gtk_widget_show(GTK_WIDGET(priv->toolbar)); return TRUE; } static gboolean rcw_tb_drag_drop(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, gpointer user_data) { TRACE_CALL(__func__); GtkAllocation wa; gint new_toolbar_placement; RemminaConnectionWindowPriv *priv; RemminaConnectionWindow *cnnwin; cnnwin = (RemminaConnectionWindow *)user_data; priv = 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; } else { 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); if (new_toolbar_placement != remmina_pref.toolbar_placement) { /* 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(rcw_notify_widget_toolbar_placement, NULL); } if (priv->toolbar) gtk_widget_show(GTK_WIDGET(priv->toolbar)); return TRUE; } static void rcw_tb_drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer user_data) { TRACE_CALL(__func__); 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); } void rcw_update_toolbar_opacity(RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); RemminaConnectionWindowPriv *priv = cnnwin->priv; RemminaConnectionObject *cnnobj; cnnobj = rcw_get_visible_cnnobj(cnnwin); if (!cnnobj) return; 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; if (priv->floating_toolbar_widget) gtk_widget_set_opacity(GTK_WIDGET(priv->overlay_ftb_overlay), priv->floating_toolbar_opacity); } static gboolean rcw_floating_toolbar_make_invisible(gpointer data) { TRACE_CALL(__func__); RemminaConnectionWindowPriv *priv = (RemminaConnectionWindowPriv *)data; gtk_widget_set_opacity(GTK_WIDGET(priv->overlay_ftb_overlay), 0.0); priv->ftb_hide_eventsource = 0; return G_SOURCE_REMOVE; } static void rcw_floating_toolbar_show(RemminaConnectionWindow *cnnwin, gboolean show) { TRACE_CALL(__func__); RemminaConnectionWindowPriv *priv = cnnwin->priv; if (priv->floating_toolbar_widget == NULL) return; if (show || priv->pin_down) { /* Make the FTB no longer transparent, in case we have an hidden toolbar */ rcw_update_toolbar_opacity(cnnwin); /* Remove outstanding hide events, if not yet active */ if (priv->ftb_hide_eventsource) { g_source_remove(priv->ftb_hide_eventsource); priv->ftb_hide_eventsource = 0; } } else { /* If we are hiding and the toolbar must be made invisible, schedule * a later toolbar hide */ if (remmina_pref.fullscreen_toolbar_visibility == FLOATING_TOOLBAR_VISIBILITY_INVISIBLE) if (priv->ftb_hide_eventsource == 0) priv->ftb_hide_eventsource = g_timeout_add(1000, rcw_floating_toolbar_make_invisible, priv); } gtk_revealer_set_reveal_child(GTK_REVEALER(priv->revealer), show || priv->pin_down); } static void rco_get_desktop_size(RemminaConnectionObject *cnnobj, gint *width, gint *height) { TRACE_CALL(__func__); RemminaProtocolWidget *gp = REMMINA_PROTOCOL_WIDGET(cnnobj->proto); *width = remmina_protocol_widget_get_width(gp); *height = remmina_protocol_widget_get_height(gp); if (*width == 0) { /* Before connecting we do not have real remote width/height, * so we ask profile values */ *width = remmina_protocol_widget_get_profile_remote_width(gp); *height = remmina_protocol_widget_get_profile_remote_height(gp); } } void rco_set_scrolled_policy(RemminaScaleMode scalemode, GtkScrolledWindow *scrolled_window) { TRACE_CALL(__func__); 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); } static GtkWidget *rco_create_scrolled_container(RemminaScaleMode scalemode, int view_mode) { GtkWidget *scrolled_container; if (view_mode == VIEWPORT_FULLSCREEN_MODE) { scrolled_container = remmina_scrolled_viewport_new(); } else { scrolled_container = gtk_scrolled_window_new(NULL, NULL); rco_set_scrolled_policy(scalemode, GTK_SCROLLED_WINDOW(scrolled_container)); gtk_container_set_border_width(GTK_CONTAINER(scrolled_container), 0); gtk_widget_set_can_focus(scrolled_container, FALSE); } gtk_widget_set_name(scrolled_container, "remmina-scrolled-container"); gtk_widget_show(scrolled_container); return scrolled_container; } gboolean rcw_toolbar_autofit_restore(RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); RemminaConnectionWindowPriv *priv = cnnwin->priv; RemminaConnectionObject *cnnobj; gint dwidth, dheight; GtkAllocation nba, ca, ta; cnnwin->priv->tar_eventsource = 0; if (priv->toolbar_is_reconfiguring) return G_SOURCE_REMOVE; if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return FALSE; if (cnnobj->connected && GTK_IS_SCROLLED_WINDOW(cnnobj->scrolled_container)) { rco_get_desktop_size(cnnobj, &dwidth, &dheight); gtk_widget_get_allocation(GTK_WIDGET(priv->notebook), &nba); gtk_widget_get_allocation(cnnobj->scrolled_container, &ca); gtk_widget_get_allocation(priv->toolbar, &ta); if (remmina_pref.toolbar_placement == TOOLBAR_PLACEMENT_LEFT || remmina_pref.toolbar_placement == TOOLBAR_PLACEMENT_RIGHT) gtk_window_resize(GTK_WINDOW(cnnobj->cnnwin), MAX(1, dwidth + ta.width + nba.width - ca.width), MAX(1, dheight + nba.height - ca.height)); else gtk_window_resize(GTK_WINDOW(cnnobj->cnnwin), MAX(1, dwidth + nba.width - ca.width), MAX(1, dheight + ta.height + nba.height - ca.height)); gtk_container_check_resize(GTK_CONTAINER(cnnobj->cnnwin)); } if (GTK_IS_SCROLLED_WINDOW(cnnobj->scrolled_container)) { RemminaScaleMode scalemode = get_current_allowed_scale_mode(cnnobj, NULL, NULL); rco_set_scrolled_policy(scalemode, GTK_SCROLLED_WINDOW(cnnobj->scrolled_container)); } return G_SOURCE_REMOVE; } static void rcw_toolbar_autofit(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); RemminaConnectionObject *cnnobj; if (cnnwin->priv->toolbar_is_reconfiguring) return; if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return; if (GTK_IS_SCROLLED_WINDOW(cnnobj->scrolled_container)) { if ((gdk_window_get_state(gtk_widget_get_window(GTK_WIDGET(cnnwin))) & GDK_WINDOW_STATE_MAXIMIZED) != 0) gtk_window_unmaximize(GTK_WINDOW(cnnwin)); /* 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); cnnwin->priv->tar_eventsource = g_timeout_add(200, (GSourceFunc)rcw_toolbar_autofit_restore, cnnwin); } } void rco_get_monitor_geometry(RemminaConnectionObject *cnnobj, GdkRectangle *sz) { TRACE_CALL(__func__); /* Fill sz with the monitor (or workarea) size and position * of the monitor (or workarea) where cnnobj->cnnwin is located */ GdkRectangle monitor_geometry; sz->x = sz->y = sz->width = sz->height = 0; if (!cnnobj) return; if (!cnnobj->cnnwin) return; if (!gtk_widget_is_visible(GTK_WIDGET(cnnobj->cnnwin))) return; #if GTK_CHECK_VERSION(3, 22, 0) GdkDisplay *display; GdkMonitor *monitor; display = gtk_widget_get_display(GTK_WIDGET(cnnobj->cnnwin)); monitor = gdk_display_get_monitor_at_window(display, gtk_widget_get_window(GTK_WIDGET(cnnobj->cnnwin))); #else GdkScreen *screen; gint monitor; screen = gtk_window_get_screen(GTK_WINDOW(cnnobj->cnnwin)); monitor = gdk_screen_get_monitor_at_window(screen, gtk_widget_get_window(GTK_WIDGET(cnnobj->cnnwin))); #endif #if GTK_CHECK_VERSION(3, 22, 0) gdk_monitor_get_workarea(monitor, &monitor_geometry); /* Under Wayland, GTK 3.22, all values returned by gdk_monitor_get_geometry() * and gdk_monitor_get_workarea() seem to have been divided by the * gdk scale factor, so we need to adjust the returned rect * undoing the division */ #ifdef GDK_WINDOWING_WAYLAND if (GDK_IS_WAYLAND_DISPLAY(display)) { int monitor_scale_factor = gdk_monitor_get_scale_factor(monitor); monitor_geometry.width *= monitor_scale_factor; monitor_geometry.height *= monitor_scale_factor; } #endif #elif gdk_screen_get_monitor_workarea gdk_screen_get_monitor_workarea(screen, monitor, &monitor_geometry); #else gdk_screen_get_monitor_geometry(screen, monitor, &monitor_geometry); #endif *sz = monitor_geometry; } static void rco_check_resize(RemminaConnectionObject *cnnobj) { TRACE_CALL(__func__); gboolean scroll_required = FALSE; GdkRectangle monitor_geometry; gint rd_width, rd_height; gint bordersz; gint scalemode; scalemode = remmina_protocol_widget_get_current_scale_mode(REMMINA_PROTOCOL_WIDGET(cnnobj->proto)); /* Get remote destkop size */ rco_get_desktop_size(cnnobj, &rd_width, &rd_height); /* Get our monitor size */ rco_get_monitor_geometry(cnnobj, &monitor_geometry); if (!remmina_protocol_widget_get_expand(REMMINA_PROTOCOL_WIDGET(cnnobj->proto)) && (monitor_geometry.width < rd_width || monitor_geometry.height < rd_height) && scalemode == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_NONE) scroll_required = TRUE; switch (cnnobj->cnnwin->priv->view_mode) { case SCROLLED_FULLSCREEN_MODE: gtk_window_resize(GTK_WINDOW(cnnobj->cnnwin), monitor_geometry.width, monitor_geometry.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 ? SCROLL_BORDER_SIZE : 0; gtk_window_resize(GTK_WINDOW(cnnobj->cnnwin), monitor_geometry.width, monitor_geometry.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); break; case SCROLLED_WINDOW_MODE: if (remmina_file_get_int(cnnobj->remmina_file, "viewmode", UNDEFINED_MODE) == UNDEFINED_MODE) { /* ToDo: is this really needed ? When ? */ gtk_window_set_default_size(GTK_WINDOW(cnnobj->cnnwin), MIN(rd_width, monitor_geometry.width), MIN(rd_height, monitor_geometry.height)); if (rd_width >= monitor_geometry.width || rd_height >= monitor_geometry.height) { gtk_window_maximize(GTK_WINDOW(cnnobj->cnnwin)); remmina_file_set_int(cnnobj->remmina_file, "window_maximize", TRUE); } else { rcw_toolbar_autofit(NULL, cnnobj->cnnwin); remmina_file_set_int(cnnobj->remmina_file, "window_maximize", FALSE); } } else { if (remmina_file_get_int(cnnobj->remmina_file, "window_maximize", FALSE)) gtk_window_maximize(GTK_WINDOW(cnnobj->cnnwin)); } break; default: break; } } static void rcw_set_tooltip(GtkWidget *item, const gchar *tip, guint key1, guint key2) { TRACE_CALL(__func__); gchar *s1; gchar *s2; if (remmina_pref.hostkey && key1) { if (key2) s1 = g_strdup_printf(" (%s + %s,%s)", gdk_keyval_name(remmina_pref.hostkey), gdk_keyval_name(gdk_keyval_to_upper(key1)), gdk_keyval_name(gdk_keyval_to_upper(key2))); else if (key1 == remmina_pref.hostkey) s1 = g_strdup_printf(" (%s)", gdk_keyval_name(remmina_pref.hostkey)); else s1 = g_strdup_printf(" (%s + %s)", gdk_keyval_name(remmina_pref.hostkey), gdk_keyval_name(gdk_keyval_to_upper(key1))); } else { s1 = NULL; } s2 = g_strdup_printf("%s%s", tip, s1 ? s1 : ""); gtk_widget_set_tooltip_text(item, s2); g_free(s2); g_free(s1); } static void remmina_protocol_widget_update_alignment(RemminaConnectionObject *cnnobj) { TRACE_CALL(__func__); RemminaScaleMode scalemode; gboolean scaledexpandedmode; int rdwidth, rdheight; gfloat aratio; if (!cnnobj->plugin_can_scale) { /* If we have a plugin that cannot scale, * (i.e. SFTP plugin), then we expand proto */ gtk_widget_set_halign(GTK_WIDGET(cnnobj->proto), GTK_ALIGN_FILL); gtk_widget_set_valign(GTK_WIDGET(cnnobj->proto), GTK_ALIGN_FILL); } else { /* Plugin can scale */ scalemode = get_current_allowed_scale_mode(cnnobj, NULL, NULL); scaledexpandedmode = remmina_protocol_widget_get_expand(REMMINA_PROTOCOL_WIDGET(cnnobj->proto)); /* Check if we need aspectframe and create/destroy it accordingly */ if (scalemode == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED && !scaledexpandedmode) { /* 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; if (!cnnobj->aspectframe) { /* We need a new aspectframe */ cnnobj->aspectframe = gtk_aspect_frame_new(NULL, 0.5, 0.5, aratio, FALSE); gtk_widget_set_name(cnnobj->aspectframe, "remmina-cw-aspectframe"); 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); if (cnnobj != NULL && cnnobj->cnnwin != NULL && cnnobj->cnnwin->priv->notebook != NULL) rcw_grab_focus(cnnobj->cnnwin); } else { gtk_aspect_frame_set(GTK_ASPECT_FRAME(cnnobj->aspectframe), 0.5, 0.5, aratio, FALSE); } } else { /* We do not need an aspectframe as a parent of proto */ if (cnnobj->aspectframe) { /* 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); if (cnnobj != NULL && cnnobj->cnnwin != NULL && cnnobj->cnnwin->priv->notebook != NULL) rcw_grab_focus(cnnobj->cnnwin); } } if (scalemode == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED || scalemode == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES) { /* 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 */ gtk_widget_set_halign(GTK_WIDGET(cnnobj->proto), GTK_ALIGN_FILL); gtk_widget_set_valign(GTK_WIDGET(cnnobj->proto), GTK_ALIGN_FILL); } else { /* Plugin can scale, but no scaling is active. Ensure that we have * aspectframe with a ratio of 1 */ gtk_widget_set_halign(GTK_WIDGET(cnnobj->proto), GTK_ALIGN_CENTER); gtk_widget_set_valign(GTK_WIDGET(cnnobj->proto), GTK_ALIGN_CENTER); } } } static void nb_set_current_page(GtkNotebook *notebook, GtkWidget *page) { gint np, i; np = gtk_notebook_get_n_pages(notebook); for (i = 0; i < np; i++) { if (gtk_notebook_get_nth_page(notebook, i) == page) { gtk_notebook_set_current_page(notebook, i); break; } } } static void nb_migrate_message_panels(GtkWidget *frompage, GtkWidget *topage) { /* Migrate a single connection tab from a notebook to another one */ GList *lst, *l; /* Reparent message panels */ lst = gtk_container_get_children(GTK_CONTAINER(frompage)); for (l = lst; l != NULL; l = l->next) { if (REMMINA_IS_MESSAGE_PANEL(l->data)) { g_object_ref(l->data); gtk_container_remove(GTK_CONTAINER(frompage), GTK_WIDGET(l->data)); gtk_container_add(GTK_CONTAINER(topage), GTK_WIDGET(l->data)); g_object_unref(l->data); gtk_box_reorder_child(GTK_BOX(topage), GTK_WIDGET(l->data), 0); } } g_list_free(lst); } static void rcw_migrate(RemminaConnectionWindow *from, RemminaConnectionWindow *to) { /* Migrate a complete notebook from a window to another */ gchar *tag; gint cp, np, i; GtkNotebook *from_notebook; GtkWidget *frompage, *newpage, *old_scrolled_container; RemminaConnectionObject *cnnobj; RemminaScaleMode scalemode; /* Migrate TAG */ tag = g_strdup((gchar *)g_object_get_data(G_OBJECT(from), "tag")); g_object_set_data_full(G_OBJECT(to), "tag", tag, (GDestroyNotify)g_free); /* Migrate notebook content */ from_notebook = from->priv->notebook; if (from_notebook && GTK_IS_NOTEBOOK(from_notebook)) { cp = gtk_notebook_get_current_page(from_notebook); np = gtk_notebook_get_n_pages(from_notebook); /* Create pages on dest notebook and migrate * page content */ for (i = 0; i < np; i++) { frompage = gtk_notebook_get_nth_page(from_notebook, i); cnnobj = g_object_get_data(G_OBJECT(frompage), "cnnobj"); /* A scrolled container must be recreated, because it can be different on the new window/page depending on view_mode */ scalemode = get_current_allowed_scale_mode(cnnobj, NULL, NULL); old_scrolled_container = cnnobj->scrolled_container; cnnobj->scrolled_container = rco_create_scrolled_container(scalemode, to->priv->view_mode); newpage = rcw_append_new_page(to, cnnobj); nb_migrate_message_panels(frompage, newpage); /* Reparent the viewport (which is inside scrolled_container) to the new page */ g_object_ref(cnnobj->viewport); gtk_container_remove(GTK_CONTAINER(old_scrolled_container), cnnobj->viewport); gtk_container_add(GTK_CONTAINER(cnnobj->scrolled_container), cnnobj->viewport); g_object_unref(cnnobj->viewport); /* Destroy old scrolled_container. Not really needed, it will be destroyed * when removing the page from the notepad */ gtk_widget_destroy(old_scrolled_container); } /* Remove all the pages from source notebook */ for (i = np - 1; i >= 0; i--) gtk_notebook_remove_page(from_notebook, i); gtk_notebook_set_current_page(to->priv->notebook, cp); } } static void rcw_switch_viewmode(RemminaConnectionWindow *cnnwin, int newmode) { GdkWindowState s; RemminaConnectionWindow *newwin; gint old_width, old_height; int old_mode; old_mode = cnnwin->priv->view_mode; if (old_mode == newmode) return; if (newmode == VIEWPORT_FULLSCREEN_MODE || newmode == SCROLLED_FULLSCREEN_MODE) { if (old_mode == SCROLLED_WINDOW_MODE) { /* We are leaving SCROLLED_WINDOW_MODE, save W,H, and maximized * status before self destruction of cnnwin */ gtk_window_get_size(GTK_WINDOW(cnnwin), &old_width, &old_height); s = gdk_window_get_state(gtk_widget_get_window(GTK_WIDGET(cnnwin))); } newwin = rcw_create_fullscreen(GTK_WINDOW(cnnwin), cnnwin->priv->fss_view_mode); rcw_migrate(cnnwin, newwin); if (old_mode == SCROLLED_WINDOW_MODE) { newwin->priv->ss_maximized = (s & GDK_WINDOW_STATE_MAXIMIZED) ? TRUE : FALSE; newwin->priv->ss_width = old_width; newwin->priv->ss_height = old_height; } } else { newwin = rcw_create_scrolled(cnnwin->priv->ss_width, cnnwin->priv->ss_height, cnnwin->priv->ss_maximized); rcw_migrate(cnnwin, newwin); if (old_mode == VIEWPORT_FULLSCREEN_MODE || old_mode == SCROLLED_FULLSCREEN_MODE) /* We are leaving a FULLSCREEN mode, save some parameters * status before self destruction of cnnwin */ newwin->priv->fss_view_mode = old_mode; } /* Prevent unreleased hostkey from old window to be released here */ newwin->priv->hostkey_used = TRUE; } static void rcw_toolbar_fullscreen(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); RemminaConnectionObject *cnnobj; if (cnnwin->priv->toolbar_is_reconfiguring) return; if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return; RemminaProtocolWidget *gp = REMMINA_PROTOCOL_WIDGET(cnnobj->proto); if (remmina_protocol_widget_get_multimon(gp) >= 1) { REMMINA_DEBUG("Fullscreen on all monitor"); gdk_window_set_fullscreen_mode(gtk_widget_get_window(GTK_WIDGET(toggle)), GDK_FULLSCREEN_ON_ALL_MONITORS); } else { REMMINA_DEBUG("Fullscreen on one monitor"); } if ((toggle != NULL && toggle == cnnwin->priv->toolitem_fullscreen)) { if (gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(toggle))) { if (remmina_protocol_widget_get_multimon(gp) >= 1) gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(cnnwin->priv->toolitem_multimon), TRUE); rcw_switch_viewmode(cnnwin, cnnwin->priv->fss_view_mode); } else { rcw_switch_viewmode(cnnwin, SCROLLED_WINDOW_MODE); } } else if (gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(cnnwin->priv->toolitem_multimon))) { rcw_switch_viewmode(cnnwin, cnnwin->priv->fss_view_mode); } else { rcw_switch_viewmode(cnnwin, SCROLLED_WINDOW_MODE); } } static void rco_viewport_fullscreen_mode(GtkWidget *widget, RemminaConnectionObject *cnnobj) { TRACE_CALL(__func__); RemminaConnectionWindow *newwin; if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget))) return; cnnobj->cnnwin->priv->fss_view_mode = VIEWPORT_FULLSCREEN_MODE; newwin = rcw_create_fullscreen(GTK_WINDOW(cnnobj->cnnwin), VIEWPORT_FULLSCREEN_MODE); rcw_migrate(cnnobj->cnnwin, newwin); } static void rco_scrolled_fullscreen_mode(GtkWidget *widget, RemminaConnectionObject *cnnobj) { TRACE_CALL(__func__); RemminaConnectionWindow *newwin; if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget))) return; cnnobj->cnnwin->priv->fss_view_mode = SCROLLED_FULLSCREEN_MODE; newwin = rcw_create_fullscreen(GTK_WINDOW(cnnobj->cnnwin), SCROLLED_FULLSCREEN_MODE); rcw_migrate(cnnobj->cnnwin, newwin); } static void rcw_fullscreen_option_popdown(GtkWidget *widget, RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); RemminaConnectionWindowPriv *priv = cnnwin->priv; priv->sticky = FALSE; gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->fullscreen_option_button), FALSE); rcw_floating_toolbar_show(cnnwin, FALSE); } void rcw_toolbar_fullscreen_option(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); RemminaConnectionObject *cnnobj; GtkWidget *menu; GtkWidget *menuitem; GSList *group; if (cnnwin->priv->toolbar_is_reconfiguring) return; if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return; if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toggle))) return; cnnwin->priv->sticky = TRUE; menu = gtk_menu_new(); menuitem = gtk_radio_menu_item_new_with_label(NULL, _("Viewport fullscreen mode")); gtk_widget_show(menuitem); gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(menuitem)); if (cnnwin->priv->view_mode == VIEWPORT_FULLSCREEN_MODE) gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE); g_signal_connect(G_OBJECT(menuitem), "toggled", G_CALLBACK(rco_viewport_fullscreen_mode), cnnobj); menuitem = gtk_radio_menu_item_new_with_label(group, _("Scrolled fullscreen")); gtk_widget_show(menuitem); gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); if (cnnwin->priv->view_mode == SCROLLED_FULLSCREEN_MODE) gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE); g_signal_connect(G_OBJECT(menuitem), "toggled", G_CALLBACK(rco_scrolled_fullscreen_mode), cnnobj); g_signal_connect(G_OBJECT(menu), "deactivate", G_CALLBACK(rcw_fullscreen_option_popdown), cnnwin); #if GTK_CHECK_VERSION(3, 22, 0) gtk_menu_popup_at_widget(GTK_MENU(menu), GTK_WIDGET(toggle), GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, NULL); #else gtk_menu_popup(GTK_MENU(menu), NULL, NULL, remmina_public_popup_position, cnnwin->priv->toolitem_fullscreen, 0, gtk_get_current_event_time()); #endif } static void rcw_scaler_option_popdown(GtkWidget *widget, RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); RemminaConnectionWindowPriv *priv = cnnwin->priv; if (priv->toolbar_is_reconfiguring) return; priv->sticky = FALSE; gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->scaler_option_button), FALSE); rcw_floating_toolbar_show(cnnwin, FALSE); } static void rcw_scaler_expand(GtkWidget *widget, RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); RemminaConnectionObject *cnnobj; if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget))) return; cnnobj = rcw_get_visible_cnnobj(cnnwin); if (!cnnobj) return; remmina_protocol_widget_set_expand(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), TRUE); remmina_file_set_int(cnnobj->remmina_file, "scaler_expand", TRUE); remmina_protocol_widget_update_alignment(cnnobj); } static void rcw_scaler_keep_aspect(GtkWidget *widget, RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); RemminaConnectionObject *cnnobj; if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget))) return; cnnobj = rcw_get_visible_cnnobj(cnnwin); if (!cnnobj) return; remmina_protocol_widget_set_expand(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), FALSE); remmina_file_set_int(cnnobj->remmina_file, "scaler_expand", FALSE); remmina_protocol_widget_update_alignment(cnnobj); } static void rcw_toolbar_scaler_option(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); RemminaConnectionWindowPriv *priv; RemminaConnectionObject *cnnobj; GtkWidget *menu; GtkWidget *menuitem; GSList *group; gboolean scaler_expand; if (cnnwin->priv->toolbar_is_reconfiguring) return; if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return; priv = cnnwin->priv; if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toggle))) return; scaler_expand = remmina_protocol_widget_get_expand(REMMINA_PROTOCOL_WIDGET(cnnobj->proto)); priv->sticky = TRUE; menu = gtk_menu_new(); menuitem = gtk_radio_menu_item_new_with_label(NULL, _("Keep aspect ratio when scaled")); gtk_widget_show(menuitem); gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(menuitem)); if (!scaler_expand) gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE); g_signal_connect(G_OBJECT(menuitem), "toggled", G_CALLBACK(rcw_scaler_keep_aspect), cnnwin); menuitem = gtk_radio_menu_item_new_with_label(group, _("Fill client window when scaled")); gtk_widget_show(menuitem); gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); if (scaler_expand) gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE); g_signal_connect(G_OBJECT(menuitem), "toggled", G_CALLBACK(rcw_scaler_expand), cnnwin); g_signal_connect(G_OBJECT(menu), "deactivate", G_CALLBACK(rcw_scaler_option_popdown), cnnwin); #if GTK_CHECK_VERSION(3, 22, 0) gtk_menu_popup_at_widget(GTK_MENU(menu), GTK_WIDGET(toggle), GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, NULL); #else gtk_menu_popup(GTK_MENU(menu), NULL, NULL, remmina_public_popup_position, priv->toolitem_scale, 0, gtk_get_current_event_time()); #endif } void rco_switch_page_activate(GtkMenuItem *menuitem, RemminaConnectionObject *cnnobj) { TRACE_CALL(__func__); RemminaConnectionWindowPriv *priv = cnnobj->cnnwin->priv; gint page_num; page_num = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menuitem), "new-page-num")); gtk_notebook_set_current_page(GTK_NOTEBOOK(priv->notebook), page_num); } void rcw_toolbar_switch_page_popdown(GtkWidget *widget, RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); RemminaConnectionWindowPriv *priv = cnnwin->priv; priv->sticky = FALSE; gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_switch_page), FALSE); rcw_floating_toolbar_show(cnnwin, FALSE); } static void rcw_toolbar_switch_page(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); RemminaConnectionWindowPriv *priv = cnnwin->priv; RemminaConnectionObject *cnnobj; GtkWidget *menu; GtkWidget *menuitem; GtkWidget *image; gint i, n; if (priv->toolbar_is_reconfiguring) return; if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return; if (!gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(toggle))) return; priv->sticky = TRUE; menu = gtk_menu_new(); n = gtk_notebook_get_n_pages(GTK_NOTEBOOK(priv->notebook)); for (i = 0; i < n; i++) { cnnobj = rcw_get_cnnobj_at_page(cnnobj->cnnwin, i); menuitem = gtk_menu_item_new_with_label(remmina_file_get_string(cnnobj->remmina_file, "name")); gtk_widget_show(menuitem); gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); image = gtk_image_new_from_icon_name(remmina_file_get_icon_name(cnnobj->remmina_file), GTK_ICON_SIZE_MENU); gtk_widget_show(image); g_object_set_data(G_OBJECT(menuitem), "new-page-num", GINT_TO_POINTER(i)); g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(rco_switch_page_activate), cnnobj); if (i == gtk_notebook_get_current_page(GTK_NOTEBOOK(priv->notebook))) gtk_widget_set_sensitive(menuitem, FALSE); } g_signal_connect(G_OBJECT(menu), "deactivate", G_CALLBACK(rcw_toolbar_switch_page_popdown), cnnwin); #if GTK_CHECK_VERSION(3, 22, 0) gtk_menu_popup_at_widget(GTK_MENU(menu), GTK_WIDGET(toggle), GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, NULL); #else gtk_menu_popup(GTK_MENU(menu), NULL, NULL, remmina_public_popup_position, widget, 0, gtk_get_current_event_time()); #endif } void rco_update_toolbar_autofit_button(RemminaConnectionObject *cnnobj) { TRACE_CALL(__func__); RemminaConnectionWindowPriv *priv = cnnobj->cnnwin->priv; GtkToolItem *toolitem; RemminaScaleMode sc; toolitem = priv->toolitem_autofit; if (toolitem) { if (priv->view_mode != SCROLLED_WINDOW_MODE) { gtk_widget_set_sensitive(GTK_WIDGET(toolitem), FALSE); } else { sc = get_current_allowed_scale_mode(cnnobj, NULL, NULL); gtk_widget_set_sensitive(GTK_WIDGET(toolitem), sc == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_NONE); } } } static void rco_change_scalemode(RemminaConnectionObject *cnnobj, gboolean bdyn, gboolean bscale) { RemminaScaleMode scalemode; RemminaConnectionWindowPriv *priv = cnnobj->cnnwin->priv; if (bdyn) scalemode = REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES; else if (bscale) scalemode = REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED; else scalemode = REMMINA_PROTOCOL_WIDGET_SCALE_MODE_NONE; remmina_protocol_widget_set_current_scale_mode(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), scalemode); remmina_file_set_int(cnnobj->remmina_file, "scale", scalemode); gtk_widget_set_sensitive(GTK_WIDGET(priv->scaler_option_button), scalemode == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED); rco_update_toolbar_autofit_button(cnnobj); remmina_protocol_widget_call_feature_by_type(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), REMMINA_PROTOCOL_FEATURE_TYPE_SCALE, 0); if (cnnobj->cnnwin->priv->view_mode != SCROLLED_WINDOW_MODE) rco_check_resize(cnnobj); if (GTK_IS_SCROLLED_WINDOW(cnnobj->scrolled_container)) { rco_set_scrolled_policy(scalemode, GTK_SCROLLED_WINDOW(cnnobj->scrolled_container)); } } static void rcw_toolbar_dynres(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); gboolean bdyn, bscale; RemminaConnectionObject *cnnobj; if (cnnwin->priv->toolbar_is_reconfiguring) return; if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return; if (cnnobj->connected) { bdyn = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(toggle)); bscale = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(cnnobj->cnnwin->priv->toolitem_scale)); if (bdyn && bscale) { gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(cnnobj->cnnwin->priv->toolitem_scale), FALSE); bscale = FALSE; } rco_change_scalemode(cnnobj, bdyn, bscale); } } static void rcw_toolbar_scaled_mode(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); gboolean bdyn, bscale; RemminaConnectionObject *cnnobj; if (cnnwin->priv->toolbar_is_reconfiguring) return; if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return; bdyn = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(cnnobj->cnnwin->priv->toolitem_dynres)); bscale = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(toggle)); if (bdyn && bscale) { gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(cnnobj->cnnwin->priv->toolitem_dynres), FALSE); bdyn = FALSE; } rco_change_scalemode(cnnobj, bdyn, bscale); } static void rcw_toolbar_multi_monitor_mode(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); RemminaConnectionObject *cnnobj; if (cnnwin->priv->toolbar_is_reconfiguring) return; if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return; if (gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(toggle))) { REMMINA_DEBUG("Saving multimon as 1"); remmina_file_set_int(cnnobj->remmina_file, "multimon", 1); remmina_file_save(cnnobj->remmina_file); remmina_protocol_widget_call_feature_by_type(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), REMMINA_PROTOCOL_FEATURE_TYPE_MULTIMON, 0); if (!gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(cnnwin->priv->toolitem_fullscreen))) gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(cnnwin->priv->toolitem_fullscreen), TRUE); } else { REMMINA_DEBUG("Saving multimon as 0"); remmina_file_set_int(cnnobj->remmina_file, "multimon", 0); remmina_file_save(cnnobj->remmina_file); rcw_toolbar_fullscreen(NULL, cnnwin); } } static void rcw_toolbar_open_main(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); if (cnnwin->priv->toolbar_is_reconfiguring) return; remmina_exec_command(REMMINA_COMMAND_MAIN, NULL); } static void rcw_toolbar_preferences_popdown(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); RemminaConnectionObject *cnnobj; if (cnnwin->priv->toolbar_is_reconfiguring) return; if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return; cnnobj->cnnwin->priv->sticky = FALSE; gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(cnnobj->cnnwin->priv->toolitem_preferences), FALSE); rcw_floating_toolbar_show(cnnwin, FALSE); } void rcw_toolbar_menu_popdown(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); RemminaConnectionWindowPriv *priv = cnnwin->priv; if (priv->toolbar_is_reconfiguring) return; priv->sticky = FALSE; gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_menu), FALSE); rcw_floating_toolbar_show(cnnwin, FALSE); } void rcw_toolbar_tools_popdown(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); RemminaConnectionWindowPriv *priv = cnnwin->priv; if (priv->toolbar_is_reconfiguring) return; priv->sticky = FALSE; gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_tools), FALSE); rcw_floating_toolbar_show(cnnwin, FALSE); } static void rco_call_protocol_feature_radio(GtkMenuItem *menuitem, RemminaConnectionObject *cnnobj) { TRACE_CALL(__func__); RemminaProtocolFeature *feature; gpointer value; if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem))) { feature = (RemminaProtocolFeature *)g_object_get_data(G_OBJECT(menuitem), "feature-type"); value = g_object_get_data(G_OBJECT(menuitem), "feature-value"); remmina_file_set_string(cnnobj->remmina_file, (const gchar *)feature->opt2, (const gchar *)value); remmina_protocol_widget_call_feature_by_ref(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), feature); } } static void rco_call_protocol_feature_check(GtkMenuItem *menuitem, RemminaConnectionObject *cnnobj) { TRACE_CALL(__func__); RemminaProtocolFeature *feature; gboolean value; feature = (RemminaProtocolFeature *)g_object_get_data(G_OBJECT(menuitem), "feature-type"); value = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem)); remmina_file_set_int(cnnobj->remmina_file, (const gchar *)feature->opt2, value); remmina_protocol_widget_call_feature_by_ref(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), feature); } static void rco_call_protocol_feature_activate(GtkMenuItem *menuitem, RemminaConnectionObject *cnnobj) { TRACE_CALL(__func__); RemminaProtocolFeature *feature; feature = (RemminaProtocolFeature *)g_object_get_data(G_OBJECT(menuitem), "feature-type"); remmina_protocol_widget_call_feature_by_ref(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), feature); } void rcw_toolbar_preferences_radio(RemminaConnectionObject *cnnobj, RemminaFile *remminafile, GtkWidget *menu, const RemminaProtocolFeature *feature, const gchar *domain, gboolean enabled) { TRACE_CALL(__func__); GtkWidget *menuitem; GSList *group; gint i; const gchar **list; const gchar *value; group = NULL; value = remmina_file_get_string(remminafile, (const gchar *)feature->opt2); list = (const gchar **)feature->opt3; for (i = 0; list[i]; i += 2) { menuitem = gtk_radio_menu_item_new_with_label(group, g_dgettext(domain, list[i + 1])); group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(menuitem)); gtk_widget_show(menuitem); gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); if (enabled) { g_object_set_data(G_OBJECT(menuitem), "feature-type", (gpointer)feature); g_object_set_data(G_OBJECT(menuitem), "feature-value", (gpointer)list[i]); if (value && g_strcmp0(list[i], value) == 0) gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE); g_signal_connect(G_OBJECT(menuitem), "toggled", G_CALLBACK(rco_call_protocol_feature_radio), cnnobj); } else { gtk_widget_set_sensitive(menuitem, FALSE); } } } void rcw_toolbar_preferences_check(RemminaConnectionObject *cnnobj, GtkWidget *menu, const RemminaProtocolFeature *feature, const gchar *domain, gboolean enabled) { TRACE_CALL(__func__); GtkWidget *menuitem; menuitem = gtk_check_menu_item_new_with_label(g_dgettext(domain, (const gchar *)feature->opt3)); gtk_widget_show(menuitem); gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); if (enabled) { g_object_set_data(G_OBJECT(menuitem), "feature-type", (gpointer)feature); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), remmina_file_get_int(cnnobj->remmina_file, (const gchar *)feature->opt2, FALSE)); g_signal_connect(G_OBJECT(menuitem), "toggled", G_CALLBACK(rco_call_protocol_feature_check), cnnobj); } else { gtk_widget_set_sensitive(menuitem, FALSE); } } static void rcw_toolbar_preferences(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); RemminaConnectionWindowPriv *priv; RemminaConnectionObject *cnnobj; const RemminaProtocolFeature *feature; GtkWidget *menu; GtkWidget *menuitem; gboolean separator; gchar *domain; gboolean enabled; if (cnnwin->priv->toolbar_is_reconfiguring) return; if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return; priv = cnnobj->cnnwin->priv; if (!gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(toggle))) return; priv->sticky = TRUE; separator = FALSE; domain = remmina_protocol_widget_get_domain(REMMINA_PROTOCOL_WIDGET(cnnobj->proto)); menu = gtk_menu_new(); for (feature = remmina_protocol_widget_get_features(REMMINA_PROTOCOL_WIDGET(cnnobj->proto)); feature && feature->type; feature++) { if (feature->type != REMMINA_PROTOCOL_FEATURE_TYPE_PREF) continue; if (separator) { menuitem = gtk_separator_menu_item_new(); gtk_widget_show(menuitem); gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); separator = FALSE; } enabled = remmina_protocol_widget_query_feature_by_ref(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), feature); switch (GPOINTER_TO_INT(feature->opt1)) { case REMMINA_PROTOCOL_FEATURE_PREF_RADIO: rcw_toolbar_preferences_radio(cnnobj, cnnobj->remmina_file, menu, feature, domain, enabled); separator = TRUE; break; case REMMINA_PROTOCOL_FEATURE_PREF_CHECK: rcw_toolbar_preferences_check(cnnobj, menu, feature, domain, enabled); break; } } g_free(domain); g_signal_connect(G_OBJECT(menu), "deactivate", G_CALLBACK(rcw_toolbar_preferences_popdown), cnnwin); #if GTK_CHECK_VERSION(3, 22, 0) gtk_menu_popup_at_widget(GTK_MENU(menu), GTK_WIDGET(toggle), GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, NULL); #else gtk_menu_popup(GTK_MENU(menu), NULL, NULL, remmina_public_popup_position, widget, 0, gtk_get_current_event_time()); #endif } static void rcw_toolbar_menu_on_launch_item(RemminaAppletMenu *menu, RemminaAppletMenuItem *menuitem, gpointer data) { TRACE_CALL(__func__); gchar *s; switch (menuitem->item_type) { case REMMINA_APPLET_MENU_ITEM_NEW: remmina_exec_command(REMMINA_COMMAND_NEW, NULL); break; case REMMINA_APPLET_MENU_ITEM_FILE: remmina_exec_command(REMMINA_COMMAND_CONNECT, menuitem->filename); break; case REMMINA_APPLET_MENU_ITEM_DISCOVERED: s = g_strdup_printf("%s,%s", menuitem->protocol, menuitem->name); remmina_exec_command(REMMINA_COMMAND_NEW, s); g_free(s); break; } } static void rcw_toolbar_menu(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); RemminaConnectionWindowPriv *priv; RemminaConnectionObject *cnnobj; GtkWidget *menu; GtkWidget *menuitem = NULL; if (cnnwin->priv->toolbar_is_reconfiguring) return; if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return; priv = cnnobj->cnnwin->priv; if (!gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(toggle))) return; priv->sticky = TRUE; menu = remmina_applet_menu_new(); remmina_applet_menu_set_hide_count(REMMINA_APPLET_MENU(menu), remmina_pref.applet_hide_count); remmina_applet_menu_populate(REMMINA_APPLET_MENU(menu)); g_signal_connect(G_OBJECT(menu), "launch-item", G_CALLBACK(rcw_toolbar_menu_on_launch_item), NULL); //g_signal_connect(G_OBJECT(menu), "edit-item", G_CALLBACK(rcw_toolbar_menu_on_edit_item), NULL); menuitem = gtk_separator_menu_item_new(); gtk_widget_show(menuitem); gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); #if GTK_CHECK_VERSION(3, 22, 0) gtk_menu_popup_at_widget(GTK_MENU(menu), GTK_WIDGET(toggle), GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, NULL); #else gtk_menu_popup(GTK_MENU(menu), NULL, NULL, remmina_public_popup_position, widget, 0, gtk_get_current_event_time()); #endif g_signal_connect(G_OBJECT(menu), "deactivate", G_CALLBACK(rcw_toolbar_menu_popdown), cnnwin); } static void rcw_toolbar_tools(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); RemminaConnectionWindowPriv *priv; RemminaConnectionObject *cnnobj; const RemminaProtocolFeature *feature; GtkWidget *menu; GtkWidget *menuitem = NULL; GtkMenu *submenu_keystrokes; const gchar *domain; gboolean enabled; gchar **keystrokes; gchar **keystroke_values; gint i; if (cnnwin->priv->toolbar_is_reconfiguring) return; if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return; priv = cnnobj->cnnwin->priv; if (!gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(toggle))) return; priv->sticky = TRUE; domain = remmina_protocol_widget_get_domain(REMMINA_PROTOCOL_WIDGET(cnnobj->proto)); menu = gtk_menu_new(); for (feature = remmina_protocol_widget_get_features(REMMINA_PROTOCOL_WIDGET(cnnobj->proto)); feature && feature->type; feature++) { if (feature->type != REMMINA_PROTOCOL_FEATURE_TYPE_TOOL) continue; if (feature->opt1) menuitem = gtk_menu_item_new_with_label(g_dgettext(domain, (const gchar *)feature->opt1)); if (feature->opt3) rcw_set_tooltip(menuitem, "", GPOINTER_TO_UINT(feature->opt3), 0); gtk_widget_show(menuitem); gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); enabled = remmina_protocol_widget_query_feature_by_ref(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), feature); if (enabled) { g_object_set_data(G_OBJECT(menuitem), "feature-type", (gpointer)feature); g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(rco_call_protocol_feature_activate), cnnobj); } else { gtk_widget_set_sensitive(menuitem, FALSE); } } /* If the plugin accepts keystrokes include the keystrokes menu */ if (remmina_protocol_widget_plugin_receives_keystrokes(REMMINA_PROTOCOL_WIDGET(cnnobj->proto))) { /* Get the registered keystrokes list */ keystrokes = g_strsplit(remmina_pref.keystrokes, STRING_DELIMITOR, -1); if (g_strv_length(keystrokes)) { /* Add a keystrokes submenu */ menuitem = gtk_menu_item_new_with_label(_("Keystrokes")); submenu_keystrokes = GTK_MENU(gtk_menu_new()); gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), GTK_WIDGET(submenu_keystrokes)); gtk_widget_show(menuitem); gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); /* Add each registered keystroke */ for (i = 0; i < g_strv_length(keystrokes); i++) { keystroke_values = g_strsplit(keystrokes[i], STRING_DELIMITOR2, -1); if (g_strv_length(keystroke_values) > 1) { /* Add the keystroke if no description was available */ menuitem = gtk_menu_item_new_with_label( g_strdup(keystroke_values[strlen(keystroke_values[0]) ? 0 : 1])); g_object_set_data(G_OBJECT(menuitem), "keystrokes", g_strdup(keystroke_values[1])); g_signal_connect_swapped(G_OBJECT(menuitem), "activate", G_CALLBACK(remmina_protocol_widget_send_keystrokes), REMMINA_PROTOCOL_WIDGET(cnnobj->proto)); gtk_widget_show(menuitem); gtk_menu_shell_append(GTK_MENU_SHELL(submenu_keystrokes), menuitem); } g_strfreev(keystroke_values); } menuitem = gtk_menu_item_new_with_label(_("Send clipboard content as keystrokes")); static gchar k_tooltip[] = N_("CAUTION: Pasted text will be sent as a sequence of key-codes as if typed on your local keyboard.\n" "\n" " • For best results use same keyboard settings for both, client and server.\n" "\n" " • If client-keyboard is different from server-keyboard the received text can contain wrong or erroneous characters.\n" "\n" " • Unicode characters and other special characters that can't be translated to local key-codes won’t be sent to the server.\n" "\n"); gtk_widget_set_tooltip_text(menuitem, k_tooltip); gtk_menu_shell_append(GTK_MENU_SHELL(submenu_keystrokes), menuitem); g_signal_connect_swapped(G_OBJECT(menuitem), "activate", G_CALLBACK(remmina_protocol_widget_send_clipboard), REMMINA_PROTOCOL_WIDGET(cnnobj->proto)); gtk_widget_show(menuitem); } g_strfreev(keystrokes); } g_signal_connect(G_OBJECT(menu), "deactivate", G_CALLBACK(rcw_toolbar_tools_popdown), cnnwin); #if GTK_CHECK_VERSION(3, 22, 0) gtk_menu_popup_at_widget(GTK_MENU(menu), GTK_WIDGET(toggle), GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, NULL); #else gtk_menu_popup(GTK_MENU(menu), NULL, NULL, remmina_public_popup_position, widget, 0, gtk_get_current_event_time()); #endif } static void rcw_toolbar_duplicate(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); RemminaConnectionObject *cnnobj; if (cnnwin->priv->toolbar_is_reconfiguring) return; if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return; remmina_file_save(cnnobj->remmina_file); remmina_exec_command(REMMINA_COMMAND_CONNECT, cnnobj->remmina_file->filename); } static void rcw_toolbar_screenshot(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); GdkPixbuf *screenshot; GdkWindow *active_window; cairo_t *cr; gint width, height; GString *pngstr; gchar *pngname; GtkWidget *dialog; RemminaProtocolWidget *gp; RemminaPluginScreenshotData rpsd; RemminaConnectionObject *cnnobj; cairo_surface_t *srcsurface; cairo_format_t cairo_format; cairo_surface_t *surface; int stride; if (cnnwin->priv->toolbar_is_reconfiguring) return; if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return; GDateTime *date = g_date_time_new_now_utc(); // We will take a screenshot of the currently displayed RemminaProtocolWidget. gp = REMMINA_PROTOCOL_WIDGET(cnnobj->proto); gchar *denyclip = remmina_pref_get_value("deny_screenshot_clipboard"); REMMINA_DEBUG("deny_screenshot_clipboard is set to %s", denyclip); GtkClipboard *c = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); // Ask the plugin if it can give us a screenshot if (remmina_protocol_widget_plugin_screenshot(gp, &rpsd)) { // Good, we have a screenshot from the plugin ! REMMINA_DEBUG("Screenshot from plugin: w=%d h=%d bpp=%d bytespp=%d\n", rpsd.width, rpsd.height, rpsd.bitsPerPixel, rpsd.bytesPerPixel); width = rpsd.width; height = rpsd.height; if (rpsd.bitsPerPixel == 32) cairo_format = CAIRO_FORMAT_ARGB32; else if (rpsd.bitsPerPixel == 24) cairo_format = CAIRO_FORMAT_RGB24; else cairo_format = CAIRO_FORMAT_RGB16_565; stride = cairo_format_stride_for_width(cairo_format, width); srcsurface = cairo_image_surface_create_for_data(rpsd.buffer, cairo_format, width, height, stride); // Transfer the PixBuf in the main clipboard selection if (denyclip && (g_strcmp0(denyclip, "true"))) gtk_clipboard_set_image(c, gdk_pixbuf_get_from_surface( srcsurface, 0, 0, width, height)); surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height); cr = cairo_create(surface); cairo_set_source_surface(cr, srcsurface, 0, 0); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_paint(cr); cairo_surface_destroy(srcsurface); free(rpsd.buffer); } else { // The plugin is not releasing us a screenshot, just try to catch one via GTK /* Warn the user if image is distorted */ if (cnnobj->plugin_can_scale && get_current_allowed_scale_mode(cnnobj, NULL, NULL) == REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED) { dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("Turn off scaling to avoid screenshot distortion.")); g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(gtk_widget_destroy), NULL); gtk_widget_show(dialog); } // Get the screenshot. active_window = gtk_widget_get_window(GTK_WIDGET(gp)); // width = gdk_window_get_width(gtk_widget_get_window(GTK_WIDGET(cnnobj->cnnwin))); width = gdk_window_get_width(active_window); // height = gdk_window_get_height(gtk_widget_get_window(GTK_WIDGET(cnnobj->cnnwin))); height = gdk_window_get_height(active_window); screenshot = gdk_pixbuf_get_from_window(active_window, 0, 0, width, height); if (screenshot == NULL) g_print("gdk_pixbuf_get_from_window failed\n"); // Transfer the PixBuf in the main clipboard selection if (denyclip && (g_strcmp0(denyclip, "true"))) gtk_clipboard_set_image(c, screenshot); // Prepare the destination Cairo surface. surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height); cr = cairo_create(surface); // Copy the source pixbuf to the surface and paint it. gdk_cairo_set_source_pixbuf(cr, screenshot, 0, 0); cairo_paint(cr); // Deallocate screenshot pixbuf g_object_unref(screenshot); } //home/antenore/Pictures/remmina_%p_%h_%Y %m %d-%H%M%S.png pngname //home/antenore/Pictures/remmina_st_ _2018 9 24-151958.240374.png pngstr = g_string_new(g_strdup_printf("%s/%s.png", remmina_pref.screenshot_path, remmina_pref.screenshot_name)); remmina_utils_string_replace_all(pngstr, "%p", remmina_file_get_string(cnnobj->remmina_file, "name")); remmina_utils_string_replace_all(pngstr, "%h", remmina_file_get_string(cnnobj->remmina_file, "server")); remmina_utils_string_replace_all(pngstr, "%Y", g_strdup_printf("%d", g_date_time_get_year(date))); remmina_utils_string_replace_all(pngstr, "%m", g_strdup_printf("%02d", g_date_time_get_month(date))); remmina_utils_string_replace_all(pngstr, "%d", g_strdup_printf("%02d", g_date_time_get_day_of_month(date))); remmina_utils_string_replace_all(pngstr, "%H", g_strdup_printf("%02d", g_date_time_get_hour(date))); remmina_utils_string_replace_all(pngstr, "%M", g_strdup_printf("%02d", g_date_time_get_minute(date))); remmina_utils_string_replace_all(pngstr, "%S", g_strdup_printf("%02d", g_date_time_get_second(date))); g_date_time_unref(date); pngname = g_string_free(pngstr, FALSE); cairo_surface_write_to_png(surface, pngname); /* send a desktop notification */ if (g_file_test(pngname, G_FILE_TEST_EXISTS)) remmina_public_send_notification("remmina-screenshot-is-ready-id", _("Screenshot taken"), pngname); //Clean up and return. cairo_destroy(cr); cairo_surface_destroy(surface); } static void rcw_toolbar_minimize(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); if (cnnwin->priv->toolbar_is_reconfiguring) return; rcw_floating_toolbar_show(cnnwin, FALSE); gtk_window_iconify(GTK_WINDOW(cnnwin)); } static void rcw_toolbar_disconnect(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); RemminaConnectionObject *cnnobj; if (cnnwin->priv->toolbar_is_reconfiguring) return; if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return; rco_disconnect_current_page(cnnobj); } static void rcw_toolbar_grab(GtkToolItem *toggle, RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); gboolean capture; RemminaConnectionObject *cnnobj; if (cnnwin->priv->toolbar_is_reconfiguring) return; if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return; capture = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(toggle)); if (cnnobj->connected){ remmina_file_set_int(cnnobj->remmina_file, "keyboard_grab", capture); } if (capture && cnnobj->connected) { #if DEBUG_KB_GRABBING printf("DEBUG_KB_GRABBING: Grabbing for button\n"); #endif rcw_keyboard_grab(cnnobj->cnnwin); if (cnnobj->cnnwin->priv->pointer_entered) rcw_pointer_grab(cnnobj->cnnwin); } else { rcw_kp_ungrab(cnnobj->cnnwin); } rco_update_toolbar(cnnobj); } static GtkWidget * rcw_create_toolbar(RemminaConnectionWindow *cnnwin, gint mode) { TRACE_CALL(__func__); RemminaConnectionWindowPriv *priv = cnnwin->priv; RemminaConnectionObject *cnnobj; GtkWidget *toolbar; GtkToolItem *toolitem; GtkWidget *widget; GtkWidget *arrow; GdkDisplay *display; gint n_monitors; display = gdk_display_get_default(); n_monitors = gdk_display_get_n_monitors(display); cnnobj = rcw_get_visible_cnnobj(cnnwin); priv->toolbar_is_reconfiguring = TRUE; toolbar = gtk_toolbar_new(); gtk_widget_show(toolbar); gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_ICONS); /* Main actions */ /* Menu */ toolitem = gtk_toggle_tool_button_new(); gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "view-more-symbolic"); gtk_tool_button_set_label(GTK_TOOL_BUTTON(toolitem), _("_Menu")); gtk_tool_item_set_tooltip_text(toolitem, _("Menu")); gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1); gtk_widget_show(GTK_WIDGET(toolitem)); g_signal_connect(G_OBJECT(toolitem), "toggled", G_CALLBACK(rcw_toolbar_menu), cnnwin); priv->toolitem_menu = toolitem; /* Open Main window */ toolitem = gtk_tool_button_new(NULL, "Open Remmina Main window"); gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "go-home-symbolic"); gtk_tool_item_set_tooltip_text(toolitem, _("Open the Remmina main window")); gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1); gtk_widget_show(GTK_WIDGET(toolitem)); g_signal_connect(G_OBJECT(toolitem), "clicked", G_CALLBACK(rcw_toolbar_open_main), cnnwin); priv->toolitem_new = toolitem; /* Duplicate session */ toolitem = gtk_tool_button_new(NULL, "Duplicate connection"); gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "org.remmina.Remmina-duplicate-symbolic"); gtk_tool_item_set_tooltip_text(toolitem, _("Duplicate current connection")); gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1); gtk_widget_show(GTK_WIDGET(toolitem)); g_signal_connect(G_OBJECT(toolitem), "clicked", G_CALLBACK(rcw_toolbar_duplicate), cnnwin); if (!cnnobj) gtk_widget_set_sensitive(GTK_WIDGET(toolitem), FALSE); priv->toolitem_duplicate = toolitem; /* Separator */ toolitem = gtk_separator_tool_item_new(); gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1); gtk_widget_show(GTK_WIDGET(toolitem)); /* Auto-Fit */ toolitem = gtk_tool_button_new(NULL, NULL); gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "org.remmina.Remmina-fit-window-symbolic"); rcw_set_tooltip(GTK_WIDGET(toolitem), _("Resize the window to fit in remote resolution"), remmina_pref.shortcutkey_autofit, 0); g_signal_connect(G_OBJECT(toolitem), "clicked", G_CALLBACK(rcw_toolbar_autofit), cnnwin); priv->toolitem_autofit = toolitem; gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1); gtk_widget_show(GTK_WIDGET(toolitem)); /* Fullscreen toggle */ toolitem = gtk_toggle_tool_button_new(); gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "org.remmina.Remmina-fullscreen-symbolic"); rcw_set_tooltip(GTK_WIDGET(toolitem), _("Toggle fullscreen mode"), remmina_pref.shortcutkey_fullscreen, 0); gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1); gtk_widget_show(GTK_WIDGET(toolitem)); priv->toolitem_fullscreen = toolitem; if (kioskmode) { gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(toolitem), FALSE); } else { gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(toolitem), mode != SCROLLED_WINDOW_MODE); g_signal_connect(G_OBJECT(toolitem), "clicked", G_CALLBACK(rcw_toolbar_fullscreen), cnnwin); } /* Fullscreen drop-down options */ toolitem = gtk_tool_item_new(); gtk_widget_show(GTK_WIDGET(toolitem)); gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1); widget = gtk_toggle_button_new(); gtk_widget_show(widget); gtk_container_set_border_width(GTK_CONTAINER(widget), 0); gtk_button_set_relief(GTK_BUTTON(widget), GTK_RELIEF_NONE); #if GTK_CHECK_VERSION(3, 20, 0) gtk_widget_set_focus_on_click(GTK_WIDGET(widget), FALSE); if (remmina_pref.small_toolbutton) gtk_widget_set_name(widget, "remmina-small-button"); #else gtk_button_set_focus_on_click(GTK_BUTTON(widget), FALSE); #endif gtk_container_add(GTK_CONTAINER(toolitem), widget); #if GTK_CHECK_VERSION(3, 14, 0) arrow = gtk_image_new_from_icon_name("org.remmina.Remmina-pan-down-symbolic", GTK_ICON_SIZE_SMALL_TOOLBAR); #else arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE); #endif gtk_widget_show(arrow); gtk_container_add(GTK_CONTAINER(widget), arrow); g_signal_connect(G_OBJECT(widget), "toggled", G_CALLBACK(rcw_toolbar_fullscreen_option), cnnwin); priv->fullscreen_option_button = widget; if (mode == SCROLLED_WINDOW_MODE) gtk_widget_set_sensitive(GTK_WIDGET(widget), FALSE); /* Multi monitor */ if (n_monitors > 1) { toolitem = gtk_toggle_tool_button_new(); gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "org.remmina.Remmina-multi-monitor-symbolic"); rcw_set_tooltip(GTK_WIDGET(toolitem), _("Multi monitor"), remmina_pref.shortcutkey_multimon, 0); gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1); gtk_widget_show(GTK_WIDGET(toolitem)); g_signal_connect(G_OBJECT(toolitem), "toggled", G_CALLBACK(rcw_toolbar_multi_monitor_mode), cnnwin); priv->toolitem_multimon = toolitem; if (!cnnobj) gtk_widget_set_sensitive(GTK_WIDGET(toolitem), FALSE); else gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(toolitem), remmina_file_get_int(cnnobj->remmina_file, "multimon", FALSE)); } /* Dynamic Resolution Update */ toolitem = gtk_toggle_tool_button_new(); gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "org.remmina.Remmina-dynres-symbolic"); rcw_set_tooltip(GTK_WIDGET(toolitem), _("Toggle dynamic resolution update"), remmina_pref.shortcutkey_dynres, 0); gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1); gtk_widget_show(GTK_WIDGET(toolitem)); g_signal_connect(G_OBJECT(toolitem), "toggled", G_CALLBACK(rcw_toolbar_dynres), cnnwin); priv->toolitem_dynres = toolitem; /* Scaler button */ toolitem = gtk_toggle_tool_button_new(); gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "org.remmina.Remmina-scale-symbolic"); rcw_set_tooltip(GTK_WIDGET(toolitem), _("Toggle scaled mode"), remmina_pref.shortcutkey_scale, 0); gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1); gtk_widget_show(GTK_WIDGET(toolitem)); g_signal_connect(G_OBJECT(toolitem), "toggled", G_CALLBACK(rcw_toolbar_scaled_mode), cnnwin); priv->toolitem_scale = toolitem; /* Scaler aspect ratio dropdown menu */ toolitem = gtk_tool_item_new(); gtk_widget_show(GTK_WIDGET(toolitem)); gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1); widget = gtk_toggle_button_new(); gtk_widget_show(widget); gtk_container_set_border_width(GTK_CONTAINER(widget), 0); gtk_button_set_relief(GTK_BUTTON(widget), GTK_RELIEF_NONE); #if GTK_CHECK_VERSION(3, 20, 0) gtk_widget_set_focus_on_click(GTK_WIDGET(widget), FALSE); #else gtk_button_set_focus_on_click(GTK_BUTTON(widget), FALSE); #endif if (remmina_pref.small_toolbutton) gtk_widget_set_name(widget, "remmina-small-button"); gtk_container_add(GTK_CONTAINER(toolitem), widget); #if GTK_CHECK_VERSION(3, 14, 0) arrow = gtk_image_new_from_icon_name("org.remmina.Remmina-pan-down-symbolic", GTK_ICON_SIZE_SMALL_TOOLBAR); #else arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE); #endif gtk_widget_show(arrow); gtk_container_add(GTK_CONTAINER(widget), arrow); g_signal_connect(G_OBJECT(widget), "toggled", G_CALLBACK(rcw_toolbar_scaler_option), cnnwin); priv->scaler_option_button = widget; /* Separator */ toolitem = gtk_separator_tool_item_new(); gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1); gtk_widget_show(GTK_WIDGET(toolitem)); /* Switch tabs */ toolitem = gtk_toggle_tool_button_new(); gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "org.remmina.Remmina-switch-page-symbolic"); rcw_set_tooltip(GTK_WIDGET(toolitem), _("Switch tab pages"), remmina_pref.shortcutkey_prevtab, remmina_pref.shortcutkey_nexttab); gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1); gtk_widget_show(GTK_WIDGET(toolitem)); g_signal_connect(G_OBJECT(toolitem), "toggled", G_CALLBACK(rcw_toolbar_switch_page), cnnwin); priv->toolitem_switch_page = toolitem; /* Grab keyboard button */ toolitem = gtk_toggle_tool_button_new(); gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "org.remmina.Remmina-keyboard-symbolic"); rcw_set_tooltip(GTK_WIDGET(toolitem), _("Grab all keyboard events"), remmina_pref.shortcutkey_grab, 0); gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1); gtk_widget_show(GTK_WIDGET(toolitem)); g_signal_connect(G_OBJECT(toolitem), "toggled", G_CALLBACK(rcw_toolbar_grab), cnnwin); priv->toolitem_grab = toolitem; if (!cnnobj) gtk_widget_set_sensitive(GTK_WIDGET(toolitem), FALSE); else { const gchar *protocol = remmina_file_get_string(cnnobj->remmina_file, "protocol"); if (g_strcmp0(protocol, "SFTP") == 0 || g_strcmp0(protocol, "SSH") == 0) gtk_widget_set_sensitive(GTK_WIDGET(toolitem), FALSE); } /* Preferences */ toolitem = gtk_toggle_tool_button_new(); gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "org.remmina.Remmina-preferences-system-symbolic"); gtk_tool_item_set_tooltip_text(toolitem, _("Preferences")); gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1); gtk_widget_show(GTK_WIDGET(toolitem)); g_signal_connect(G_OBJECT(toolitem), "toggled", G_CALLBACK(rcw_toolbar_preferences), cnnwin); priv->toolitem_preferences = toolitem; /* Tools */ toolitem = gtk_toggle_tool_button_new(); gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "org.remmina.Remmina-system-run-symbolic"); gtk_tool_button_set_label(GTK_TOOL_BUTTON(toolitem), _("_Tools")); gtk_tool_item_set_tooltip_text(toolitem, _("Tools")); gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1); gtk_widget_show(GTK_WIDGET(toolitem)); g_signal_connect(G_OBJECT(toolitem), "toggled", G_CALLBACK(rcw_toolbar_tools), cnnwin); priv->toolitem_tools = toolitem; /* Separator */ toolitem = gtk_separator_tool_item_new(); gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1); gtk_widget_show(GTK_WIDGET(toolitem)); toolitem = gtk_tool_button_new(NULL, "_Screenshot"); gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "org.remmina.Remmina-camera-photo-symbolic"); rcw_set_tooltip(GTK_WIDGET(toolitem), _("Screenshot"), remmina_pref.shortcutkey_screenshot, 0); gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1); gtk_widget_show(GTK_WIDGET(toolitem)); g_signal_connect(G_OBJECT(toolitem), "clicked", G_CALLBACK(rcw_toolbar_screenshot), cnnwin); priv->toolitem_screenshot = toolitem; /* Separator */ toolitem = gtk_separator_tool_item_new(); gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1); gtk_widget_show(GTK_WIDGET(toolitem)); /* Minimize */ toolitem = gtk_tool_button_new(NULL, "_Bottom"); gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "org.remmina.Remmina-go-bottom-symbolic"); rcw_set_tooltip(GTK_WIDGET(toolitem), _("Minimize window"), remmina_pref.shortcutkey_minimize, 0); gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1); gtk_widget_show(GTK_WIDGET(toolitem)); g_signal_connect(G_OBJECT(toolitem), "clicked", G_CALLBACK(rcw_toolbar_minimize), cnnwin); if (kioskmode) gtk_widget_set_sensitive(GTK_WIDGET(toolitem), FALSE); /* Disconnect */ toolitem = gtk_tool_button_new(NULL, "_Disconnect"); gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolitem), "org.remmina.Remmina-disconnect-symbolic"); rcw_set_tooltip(GTK_WIDGET(toolitem), _("Disconnect"), remmina_pref.shortcutkey_disconnect, 0); gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1); gtk_widget_show(GTK_WIDGET(toolitem)); g_signal_connect(G_OBJECT(toolitem), "clicked", G_CALLBACK(rcw_toolbar_disconnect), cnnwin); priv->toolbar_is_reconfiguring = FALSE; return toolbar; } static void rcw_place_toolbar(GtkToolbar *toolbar, GtkGrid *grid, GtkWidget *sibling, int toolbar_placement) { /* Place the toolbar inside the grid and set its orientation */ if (toolbar_placement == TOOLBAR_PLACEMENT_LEFT || toolbar_placement == TOOLBAR_PLACEMENT_RIGHT) gtk_orientable_set_orientation(GTK_ORIENTABLE(toolbar), GTK_ORIENTATION_VERTICAL); else gtk_orientable_set_orientation(GTK_ORIENTABLE(toolbar), GTK_ORIENTATION_HORIZONTAL); switch (toolbar_placement) { case TOOLBAR_PLACEMENT_TOP: gtk_widget_set_hexpand(GTK_WIDGET(toolbar), TRUE); gtk_widget_set_vexpand(GTK_WIDGET(toolbar), FALSE); gtk_grid_attach_next_to(GTK_GRID(grid), GTK_WIDGET(toolbar), sibling, GTK_POS_TOP, 1, 1); break; case TOOLBAR_PLACEMENT_RIGHT: gtk_widget_set_vexpand(GTK_WIDGET(toolbar), TRUE); gtk_widget_set_hexpand(GTK_WIDGET(toolbar), FALSE); gtk_grid_attach_next_to(GTK_GRID(grid), GTK_WIDGET(toolbar), sibling, GTK_POS_RIGHT, 1, 1); break; case TOOLBAR_PLACEMENT_BOTTOM: gtk_widget_set_hexpand(GTK_WIDGET(toolbar), TRUE); gtk_widget_set_vexpand(GTK_WIDGET(toolbar), FALSE); gtk_grid_attach_next_to(GTK_GRID(grid), GTK_WIDGET(toolbar), sibling, GTK_POS_BOTTOM, 1, 1); break; case TOOLBAR_PLACEMENT_LEFT: gtk_widget_set_vexpand(GTK_WIDGET(toolbar), TRUE); gtk_widget_set_hexpand(GTK_WIDGET(toolbar), FALSE); gtk_grid_attach_next_to(GTK_GRID(grid), GTK_WIDGET(toolbar), sibling, GTK_POS_LEFT, 1, 1); break; } } static void rco_update_toolbar(RemminaConnectionObject *cnnobj) { TRACE_CALL(__func__); RemminaConnectionWindowPriv *priv = cnnobj->cnnwin->priv; GtkToolItem *toolitem; gboolean bval, dynres_avail, scale_avail; gboolean test_floating_toolbar; RemminaScaleMode scalemode; priv->toolbar_is_reconfiguring = TRUE; rco_update_toolbar_autofit_button(cnnobj); toolitem = priv->toolitem_switch_page; if (kioskmode) bval = FALSE; else bval = (gtk_notebook_get_n_pages(GTK_NOTEBOOK(priv->notebook)) > 1); gtk_widget_set_sensitive(GTK_WIDGET(toolitem), bval); if (cnnobj->remmina_file->filename) gtk_widget_set_sensitive(GTK_WIDGET(priv->toolitem_duplicate), TRUE); else gtk_widget_set_sensitive(GTK_WIDGET(priv->toolitem_duplicate), FALSE); scalemode = get_current_allowed_scale_mode(cnnobj, &dynres_avail, &scale_avail); gtk_widget_set_sensitive(GTK_WIDGET(priv->toolitem_dynres), dynres_avail && cnnobj->connected); gtk_widget_set_sensitive(GTK_WIDGET(priv->toolitem_scale), scale_avail && cnnobj->connected); switch (scalemode) { case REMMINA_PROTOCOL_WIDGET_SCALE_MODE_NONE: gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_dynres), FALSE); gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_scale), FALSE); gtk_widget_set_sensitive(GTK_WIDGET(priv->scaler_option_button), FALSE); break; case REMMINA_PROTOCOL_WIDGET_SCALE_MODE_SCALED: gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_dynres), FALSE); gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_scale), TRUE); gtk_widget_set_sensitive(GTK_WIDGET(priv->scaler_option_button), TRUE && cnnobj->connected); break; case REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES: gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_dynres), TRUE); gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_scale), FALSE); gtk_widget_set_sensitive(GTK_WIDGET(priv->scaler_option_button), FALSE); break; } /* REMMINA_PROTOCOL_FEATURE_TYPE_MULTIMON */ toolitem = priv->toolitem_multimon; if (toolitem) { gint hasmultimon = remmina_protocol_widget_query_feature_by_type(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), REMMINA_PROTOCOL_FEATURE_TYPE_MULTIMON); gtk_widget_set_sensitive(GTK_WIDGET(toolitem), cnnobj->connected); gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(toolitem), remmina_file_get_int(cnnobj->remmina_file, "multimon", FALSE)); gtk_widget_set_sensitive(GTK_WIDGET(toolitem), hasmultimon); } toolitem = priv->toolitem_grab; gtk_widget_set_sensitive(GTK_WIDGET(toolitem), cnnobj->connected); gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(toolitem), remmina_file_get_int(cnnobj->remmina_file, "keyboard_grab", FALSE)); const gchar *protocol = remmina_file_get_string(cnnobj->remmina_file, "protocol"); if (g_strcmp0(protocol, "SFTP") == 0 || g_strcmp0(protocol, "SSH") == 0) { gtk_widget_set_sensitive(GTK_WIDGET(toolitem), FALSE); gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(toolitem), FALSE); remmina_file_set_int(cnnobj->remmina_file, "keyboard_grab", FALSE); } toolitem = priv->toolitem_preferences; gtk_widget_set_sensitive(GTK_WIDGET(toolitem), cnnobj->connected); bval = remmina_protocol_widget_query_feature_by_type(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), REMMINA_PROTOCOL_FEATURE_TYPE_PREF); gtk_widget_set_sensitive(GTK_WIDGET(toolitem), bval && cnnobj->connected); toolitem = priv->toolitem_tools; bval = remmina_protocol_widget_query_feature_by_type(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), REMMINA_PROTOCOL_FEATURE_TYPE_TOOL); gtk_widget_set_sensitive(GTK_WIDGET(toolitem), bval && cnnobj->connected); gtk_widget_set_sensitive(GTK_WIDGET(priv->toolitem_screenshot), cnnobj->connected); gtk_window_set_title(GTK_WINDOW(cnnobj->cnnwin), remmina_file_get_string(cnnobj->remmina_file, "name")); test_floating_toolbar = (priv->floating_toolbar_widget != NULL); if (test_floating_toolbar) { const gchar *str = remmina_file_get_string(cnnobj->remmina_file, "name"); const gchar *format; GdkRGBA rgba; gchar *bg; bg = g_strdup(remmina_pref.grab_color); if (!gdk_rgba_parse(&rgba, bg)) { REMMINA_DEBUG("%s cannot be parsed as a color", bg); bg = g_strdup("#00FF00"); } else { REMMINA_DEBUG("Using %s as background color", bg); } if (remmina_file_get_int(cnnobj->remmina_file, "keyboard_grab", FALSE)) { if (remmina_pref_get_boolean("grab_color_switch")) { gtk_widget_override_background_color(priv->overlay_ftb_fr, GTK_STATE_NORMAL, &rgba); format = g_strconcat("(G: ON) - \%s", NULL); } else { gtk_widget_override_background_color(priv->overlay_ftb_fr, GTK_STATE_NORMAL, NULL); format = "(G: ON) - \%s"; } } else { gtk_widget_override_background_color(priv->overlay_ftb_fr, GTK_STATE_NORMAL, NULL); format = "(G:OFF) - \%s"; } gchar *markup; markup = g_markup_printf_escaped(format, str); gtk_label_set_markup(GTK_LABEL(priv->floating_toolbar_label), markup); g_free(markup); g_free(bg); } priv->toolbar_is_reconfiguring = FALSE; } static void rcw_set_toolbar_visibility(RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); RemminaConnectionWindowPriv *priv = cnnwin->priv; if (priv->view_mode == SCROLLED_WINDOW_MODE) { if (remmina_pref.hide_connection_toolbar) gtk_widget_hide(priv->toolbar); else gtk_widget_show(priv->toolbar); } } #if DEBUG_KB_GRABBING static void print_crossing_event(GdkEventCrossing *event) { printf("DEBUG_KB_GRABBING: --- Crossing event detail: "); switch (event->detail) { case GDK_NOTIFY_ANCESTOR: printf("GDK_NOTIFY_ANCESTOR"); break; case GDK_NOTIFY_VIRTUAL: printf("GDK_NOTIFY_VIRTUAL"); break; case GDK_NOTIFY_NONLINEAR: printf("GDK_NOTIFY_NONLINEAR"); break; case GDK_NOTIFY_NONLINEAR_VIRTUAL: printf("GDK_NOTIFY_NONLINEAR_VIRTUAL"); break; case GDK_NOTIFY_UNKNOWN: printf("GDK_NOTIFY_UNKNOWN"); break; case GDK_NOTIFY_INFERIOR: printf("GDK_NOTIFY_INFERIOR"); break; default: printf("unknown"); } printf("\n"); printf("DEBUG_KB_GRABBING: --- Crossing event mode="); switch (event->mode) { case GDK_CROSSING_NORMAL: printf("GDK_CROSSING_NORMAL"); break; case GDK_CROSSING_GRAB: printf("GDK_CROSSING_GRAB"); break; case GDK_CROSSING_UNGRAB: printf("GDK_CROSSING_UNGRAB"); break; case GDK_CROSSING_GTK_GRAB: printf("GDK_CROSSING_GTK_GRAB"); break; case GDK_CROSSING_GTK_UNGRAB: printf("GDK_CROSSING_GTK_UNGRAB"); break; case GDK_CROSSING_STATE_CHANGED: printf("GDK_CROSSING_STATE_CHANGED"); break; case GDK_CROSSING_TOUCH_BEGIN: printf("GDK_CROSSING_TOUCH_BEGIN"); break; case GDK_CROSSING_TOUCH_END: printf("GDK_CROSSING_TOUCH_END"); break; case GDK_CROSSING_DEVICE_SWITCH: printf("GDK_CROSSING_DEVICE_SWITCH"); break; default: printf("unknown"); } printf("\n"); } #endif static gboolean rcw_floating_toolbar_on_enter(GtkWidget *widget, GdkEventCrossing *event, RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); rcw_floating_toolbar_show(cnnwin, TRUE); return TRUE; } static gboolean rcw_floating_toolbar_on_leave(GtkWidget *widget, GdkEventCrossing *event, RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); if (event->detail != GDK_NOTIFY_INFERIOR) rcw_floating_toolbar_show(cnnwin, FALSE); return TRUE; } static gboolean rcw_on_enter_notify_event(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data) { TRACE_CALL(__func__); #if DEBUG_KB_GRABBING printf("DEBUG_KB_GRABBING: enter-notify-event on rcw received\n"); print_crossing_event(event); #endif return FALSE; } static gboolean rcw_on_leave_notify_event(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data) { TRACE_CALL(__func__); RemminaConnectionWindow *cnnwin = (RemminaConnectionWindow *)widget; #if DEBUG_KB_GRABBING printf("DEBUG_KB_GRABBING: leave-notify-event on rcw received\n"); print_crossing_event(event); #endif if (event->mode != GDK_CROSSING_NORMAL && event->mode != GDK_CROSSING_UNGRAB) { #if DEBUG_KB_GRABBING printf("DEBUG_KB_GRABBING: ignored because mode is not GDK_CROSSING_NORMAL GDK_CROSSING_UNGRAB\n"); #endif return FALSE; } if (cnnwin->priv->delayed_grab_eventsourceid) { g_source_remove(cnnwin->priv->delayed_grab_eventsourceid); cnnwin->priv->delayed_grab_eventsourceid = 0; } /* Workaround for https://gitlab.gnome.org/GNOME/mutter/-/issues/2450#note_1586570 */ if (event->mode != GDK_CROSSING_UNGRAB) { rcw_kp_ungrab(cnnwin); rcw_pointer_ungrab(cnnwin); } else { #if DEBUG_KB_GRABBING printf("DEBUG_KB_GRABBING: not ungrabbing, this event seems to be an unwanted event from GTK\n"); #endif } return FALSE; } static gboolean rco_leave_protocol_widget(GtkWidget *widget, GdkEventCrossing *event, RemminaConnectionObject *cnnobj) { TRACE_CALL(__func__); #if DEBUG_KB_GRABBING printf("DEBUG_KB_GRABBING: received leave event on RCO.\n"); print_crossing_event(event); #endif if (cnnobj->cnnwin->priv->delayed_grab_eventsourceid) { g_source_remove(cnnobj->cnnwin->priv->delayed_grab_eventsourceid); cnnobj->cnnwin->priv->delayed_grab_eventsourceid = 0; } cnnobj->cnnwin->priv->pointer_entered = FALSE; /* Ungrab only if the leave is due to normal mouse motion and not to an inferior */ if (event->mode == GDK_CROSSING_NORMAL && event->detail != GDK_NOTIFY_INFERIOR) rcw_kp_ungrab(cnnobj->cnnwin); return FALSE; } gboolean rco_enter_protocol_widget(GtkWidget *widget, GdkEventCrossing *event, RemminaConnectionObject *cnnobj) { TRACE_CALL(__func__); gboolean active; #if DEBUG_KB_GRABBING printf("DEBUG_KB_GRABBING: %s: enter on protocol widget event received\n", __func__); print_crossing_event(event); #endif RemminaConnectionWindowPriv *priv = cnnobj->cnnwin->priv; if (!priv->sticky && event->mode == GDK_CROSSING_NORMAL) rcw_floating_toolbar_show(cnnobj->cnnwin, FALSE); priv->pointer_entered = TRUE; if (event->mode == GDK_CROSSING_UNGRAB) { // Someone steal our grab, take note and do not attempt to regrab cnnobj->cnnwin->priv->kbcaptured = FALSE; cnnobj->cnnwin->priv->pointer_captured = FALSE; return FALSE; } /* Check if we need grabbing */ active = gtk_window_is_active(GTK_WINDOW(cnnobj->cnnwin)); if (remmina_file_get_int(cnnobj->remmina_file, "keyboard_grab", FALSE) && active) { rcw_keyboard_grab(cnnobj->cnnwin); rcw_pointer_grab(cnnobj->cnnwin); } return FALSE; } static gboolean focus_in_delayed_grab(RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); #if DEBUG_KB_GRABBING printf("DEBUG_KB_GRABBING: %s\n", __func__); #endif if (cnnwin->priv->pointer_entered) { #if DEBUG_KB_GRABBING printf("DEBUG_KB_GRABBING: delayed requesting kb and pointer grab, because of pointer inside\n"); #endif rcw_keyboard_grab(cnnwin); rcw_pointer_grab(cnnwin); } #if DEBUG_KB_GRABBING else { printf("DEBUG_KB_GRABBING: %s not grabbing because pointer_entered is false\n", __func__); } #endif cnnwin->priv->delayed_grab_eventsourceid = 0; return G_SOURCE_REMOVE; } static void rcw_focus_in(RemminaConnectionWindow *cnnwin) { /* This function is the default signal handler for focus-in-event, * but can also be called after a window focus state change event * from rcw_state_event(). So expect to be called twice * when cnnwin gains the focus */ TRACE_CALL(__func__); RemminaConnectionObject *cnnobj; if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return; if (cnnobj && cnnobj->connected && remmina_file_get_int(cnnobj->remmina_file, "keyboard_grab", FALSE)) { #if DEBUG_KB_GRABBING printf("DEBUG_KB_GRABBING: Received focus in on rcw, grabbing enabled: requesting kb grab, delayed\n"); #endif if (cnnwin->priv->delayed_grab_eventsourceid == 0) cnnwin->priv->delayed_grab_eventsourceid = g_timeout_add(300, (GSourceFunc)focus_in_delayed_grab, cnnwin); } #if DEBUG_KB_GRABBING else { printf("DEBUG_KB_GRABBING: Received focus in on rcw, but a condition will prevent to grab\n"); } #endif } static void rcw_focus_out(RemminaConnectionWindow *cnnwin) { /* This function is the default signal handler for focus-out-event, * but can also be called after a window focus state change event * from rcw_state_event(). So expect to be called twice * when cnnwin loses the focus */ TRACE_CALL(__func__); RemminaConnectionObject *cnnobj; rcw_kp_ungrab(cnnwin); cnnwin->priv->hostkey_activated = FALSE; if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return; if (REMMINA_IS_SCROLLED_VIEWPORT(cnnobj->scrolled_container)) remmina_scrolled_viewport_remove_motion(REMMINA_SCROLLED_VIEWPORT(cnnobj->scrolled_container)); if (cnnobj->proto && cnnobj->scrolled_container) remmina_protocol_widget_call_feature_by_type(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), REMMINA_PROTOCOL_FEATURE_TYPE_UNFOCUS, 0); } static gboolean rcw_floating_toolbar_hide(RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); RemminaConnectionWindowPriv *priv = cnnwin->priv; priv->hidetb_eventsource = 0; rcw_floating_toolbar_show(cnnwin, FALSE); return G_SOURCE_REMOVE; } static gboolean rcw_floating_toolbar_on_scroll(GtkWidget *widget, GdkEventScroll *event, RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); RemminaConnectionObject *cnnobj; int opacity; cnnobj = rcw_get_visible_cnnobj(cnnwin); if (!cnnobj) return TRUE; opacity = remmina_file_get_int(cnnobj->remmina_file, "toolbar_opacity", 0); switch (event->direction) { case GDK_SCROLL_UP: if (opacity > 0) { remmina_file_set_int(cnnobj->remmina_file, "toolbar_opacity", opacity - 1); rcw_update_toolbar_opacity(cnnwin); return TRUE; } break; case GDK_SCROLL_DOWN: if (opacity < TOOLBAR_OPACITY_LEVEL) { remmina_file_set_int(cnnobj->remmina_file, "toolbar_opacity", opacity + 1); rcw_update_toolbar_opacity(cnnwin); return TRUE; } break; #if GTK_CHECK_VERSION(3, 4, 0) case GDK_SCROLL_SMOOTH: if (event->delta_y < 0 && opacity > 0) { remmina_file_set_int(cnnobj->remmina_file, "toolbar_opacity", opacity - 1); rcw_update_toolbar_opacity(cnnwin); return TRUE; } if (event->delta_y > 0 && opacity < TOOLBAR_OPACITY_LEVEL) { remmina_file_set_int(cnnobj->remmina_file, "toolbar_opacity", opacity + 1); rcw_update_toolbar_opacity(cnnwin); return TRUE; } break; #endif default: break; } return TRUE; } static gboolean rcw_after_configure_scrolled(gpointer user_data) { TRACE_CALL(__func__); gint width, height; GdkWindowState s; gint ipg, npages; RemminaConnectionWindow *cnnwin; cnnwin = (RemminaConnectionWindow *)user_data; if (!cnnwin || !cnnwin->priv) return FALSE; s = gdk_window_get_state(gtk_widget_get_window(GTK_WIDGET(cnnwin))); /* Changed window_maximize, window_width and window_height for all * connections inside the notebook */ npages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(cnnwin->priv->notebook)); for (ipg = 0; ipg < npages; ipg++) { RemminaConnectionObject *cnnobj; cnnobj = g_object_get_data( G_OBJECT(gtk_notebook_get_nth_page(GTK_NOTEBOOK(cnnwin->priv->notebook), ipg)), "cnnobj"); if (s & GDK_WINDOW_STATE_MAXIMIZED) { remmina_file_set_int(cnnobj->remmina_file, "window_maximize", TRUE); } else { gtk_window_get_size(GTK_WINDOW(cnnobj->cnnwin), &width, &height); remmina_file_set_int(cnnobj->remmina_file, "window_width", width); remmina_file_set_int(cnnobj->remmina_file, "window_height", height); remmina_file_set_int(cnnobj->remmina_file, "window_maximize", FALSE); } } cnnwin->priv->acs_eventsourceid = 0; return FALSE; } static gboolean rcw_on_configure(GtkWidget *widget, GdkEventConfigure *event, gpointer data) { TRACE_CALL(__func__); RemminaConnectionWindow *cnnwin; RemminaConnectionObject *cnnobj; if (!REMMINA_IS_CONNECTION_WINDOW(widget)) return FALSE; cnnwin = (RemminaConnectionWindow *)widget; if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return FALSE; if (cnnwin->priv->acs_eventsourceid) { g_source_remove(cnnwin->priv->acs_eventsourceid); cnnwin->priv->acs_eventsourceid = 0; } if (gtk_widget_get_window(GTK_WIDGET(cnnwin)) && cnnwin->priv->view_mode == SCROLLED_WINDOW_MODE) /* Under GNOME Shell we receive this configure_event BEFORE a window * is really unmaximized, so we must read its new state and dimensions * later, not now */ cnnwin->priv->acs_eventsourceid = g_timeout_add(500, rcw_after_configure_scrolled, cnnwin); if (cnnwin->priv->view_mode != SCROLLED_WINDOW_MODE) /* Notify window of change so that scroll border can be hidden or shown if needed */ rco_check_resize(cnnobj); return FALSE; } static void rcw_update_pin(RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); if (cnnwin->priv->pin_down) gtk_button_set_image(GTK_BUTTON(cnnwin->priv->pin_button), gtk_image_new_from_icon_name("org.remmina.Remmina-pin-down-symbolic", GTK_ICON_SIZE_MENU)); else gtk_button_set_image(GTK_BUTTON(cnnwin->priv->pin_button), gtk_image_new_from_icon_name("org.remmina.Remmina-pin-up-symbolic", GTK_ICON_SIZE_MENU)); } static void rcw_toolbar_pin(GtkWidget *widget, RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); remmina_pref.toolbar_pin_down = cnnwin->priv->pin_down = !cnnwin->priv->pin_down; remmina_pref_save(); rcw_update_pin(cnnwin); } static void rcw_create_floating_toolbar(RemminaConnectionWindow *cnnwin, gint mode) { TRACE_CALL(__func__); RemminaConnectionWindowPriv *priv = cnnwin->priv; GtkWidget *ftb_widget; GtkWidget *vbox; GtkWidget *hbox; GtkWidget *label; GtkWidget *pinbutton; GtkWidget *tb; /* A widget to be used for GtkOverlay for GTK >= 3.10 */ ftb_widget = gtk_event_box_new(); vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); gtk_widget_show(vbox); gtk_container_add(GTK_CONTAINER(ftb_widget), vbox); tb = rcw_create_toolbar(cnnwin, mode); hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); gtk_widget_show(hbox); /* The pin button */ pinbutton = gtk_button_new(); gtk_widget_show(pinbutton); gtk_box_pack_start(GTK_BOX(hbox), pinbutton, FALSE, FALSE, 0); gtk_button_set_relief(GTK_BUTTON(pinbutton), GTK_RELIEF_NONE); #if GTK_CHECK_VERSION(3, 20, 0) gtk_widget_set_focus_on_click(GTK_WIDGET(pinbutton), FALSE); #else gtk_button_set_focus_on_click(GTK_BUTTON(pinbutton), FALSE); #endif gtk_widget_set_name(pinbutton, "remmina-pin-button"); g_signal_connect(G_OBJECT(pinbutton), "clicked", G_CALLBACK(rcw_toolbar_pin), cnnwin); priv->pin_button = pinbutton; priv->pin_down = remmina_pref.toolbar_pin_down; rcw_update_pin(cnnwin); label = gtk_label_new(""); gtk_label_set_max_width_chars(GTK_LABEL(label), 50); gtk_widget_show(label); gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0); priv->floating_toolbar_label = label; if (remmina_pref.floating_toolbar_placement == FLOATING_TOOLBAR_PLACEMENT_BOTTOM) { gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox), tb, FALSE, FALSE, 0); } else { gtk_box_pack_start(GTK_BOX(vbox), tb, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); } priv->floating_toolbar_widget = ftb_widget; gtk_widget_show(ftb_widget); } static void rcw_toolbar_place_signal(RemminaConnectionWindow *cnnwin, gpointer data) { TRACE_CALL(__func__); RemminaConnectionWindowPriv *priv; priv = cnnwin->priv; /* Detach old toolbar widget and reattach in new position in the grid */ if (priv->toolbar && priv->grid) { g_object_ref(priv->toolbar); gtk_container_remove(GTK_CONTAINER(priv->grid), priv->toolbar); rcw_place_toolbar(GTK_TOOLBAR(priv->toolbar), GTK_GRID(priv->grid), GTK_WIDGET(priv->notebook), remmina_pref.toolbar_placement); g_object_unref(priv->toolbar); } } static void rcw_init(RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); RemminaConnectionWindowPriv *priv; priv = g_new0(RemminaConnectionWindowPriv, 1); cnnwin->priv = priv; priv->view_mode = SCROLLED_WINDOW_MODE; if (kioskmode && kioskmode == TRUE) priv->view_mode = VIEWPORT_FULLSCREEN_MODE; priv->floating_toolbar_opacity = 1.0; priv->kbcaptured = FALSE; priv->pointer_captured = FALSE; priv->pointer_entered = FALSE; priv->fss_view_mode = VIEWPORT_FULLSCREEN_MODE; priv->ss_width = 640; priv->ss_height = 480; priv->ss_maximized = FALSE; remmina_widget_pool_register(GTK_WIDGET(cnnwin)); } static gboolean rcw_focus_in_event(GtkWidget *widget, GdkEventWindowState *event, gpointer user_data) { TRACE_CALL(__func__); #if DEBUG_KB_GRABBING printf("DEBUG_KB_GRABBING: RCW focus-in-event received\n"); #endif rcw_focus_in((RemminaConnectionWindow *)widget); return FALSE; } static gboolean rcw_focus_out_event(GtkWidget *widget, GdkEventWindowState *event, gpointer user_data) { TRACE_CALL(__func__); #if DEBUG_KB_GRABBING printf("DEBUG_KB_GRABBING: RCW focus-out-event received\n"); #endif rcw_focus_out((RemminaConnectionWindow *)widget); return FALSE; } static gboolean rcw_state_event(GtkWidget *widget, GdkEventWindowState *event, gpointer user_data) { TRACE_CALL(__func__); if (!REMMINA_IS_CONNECTION_WINDOW(widget)) return FALSE; #if DEBUG_KB_GRABBING printf("DEBUG_KB_GRABBING: window-state-event received\n"); #endif if (event->changed_mask & GDK_WINDOW_STATE_FOCUSED) { if (event->new_window_state & GDK_WINDOW_STATE_FOCUSED) rcw_focus_in((RemminaConnectionWindow *)widget); else rcw_focus_out((RemminaConnectionWindow *)widget); } return FALSE; } static gboolean rcw_map_event(GtkWidget *widget, GdkEvent *event, gpointer data) { TRACE_CALL(__func__); RemminaConnectionWindow *cnnwin = (RemminaConnectionWindow *)widget; RemminaConnectionObject *cnnobj; RemminaProtocolWidget *gp; if (cnnwin->priv->toolbar_is_reconfiguring) return FALSE; if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return FALSE; gp = REMMINA_PROTOCOL_WIDGET(cnnobj->proto); REMMINA_DEBUG("Mapping: %s", gtk_widget_get_name(widget)); if (remmina_protocol_widget_map_event(gp)) REMMINA_DEBUG("Called plugin mapping function"); return FALSE; } static gboolean rcw_unmap_event(GtkWidget *widget, GdkEvent *event, gpointer data) { TRACE_CALL(__func__); RemminaConnectionWindow *cnnwin = (RemminaConnectionWindow *)widget; RemminaConnectionObject *cnnobj; RemminaProtocolWidget *gp; if (cnnwin->priv->toolbar_is_reconfiguring) return FALSE; if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return FALSE; gp = REMMINA_PROTOCOL_WIDGET(cnnobj->proto); REMMINA_DEBUG("Unmapping: %s", gtk_widget_get_name(widget)); if (remmina_protocol_widget_unmap_event(gp)) REMMINA_DEBUG("Called plugin mapping function"); return FALSE; } static gboolean rcw_map_event_fullscreen(GtkWidget *widget, GdkEvent *event, gpointer data) { TRACE_CALL(__func__); RemminaConnectionObject *cnnobj; gint target_monitor; REMMINA_DEBUG("Mapping: %s", gtk_widget_get_name(widget)); if (!REMMINA_IS_CONNECTION_WINDOW(widget)) { REMMINA_DEBUG("Remmina Connection Window undefined, cannot go fullscreen"); return FALSE; } //RemminaConnectionWindow *cnnwin = (RemminaConnectionWindow *)data; cnnobj = rcw_get_visible_cnnobj((RemminaConnectionWindow *)widget); //cnnobj = g_object_get_data(G_OBJECT(widget), "cnnobj"); if (!cnnobj) { REMMINA_DEBUG("Remmina Connection Object undefined, cannot go fullscreen"); return FALSE; } RemminaProtocolWidget *gp = REMMINA_PROTOCOL_WIDGET(cnnobj->proto); if (!gp) REMMINA_DEBUG("Remmina Protocol Widget undefined, cannot go fullscreen"); if (remmina_protocol_widget_get_multimon(gp) >= 1) { REMMINA_DEBUG("Fullscreen on all monitor"); gdk_window_set_fullscreen_mode(gtk_widget_get_window(widget), GDK_FULLSCREEN_ON_ALL_MONITORS); gdk_window_fullscreen(gtk_widget_get_window(widget)); return TRUE; } else { REMMINA_DEBUG("Fullscreen on one monitor"); } target_monitor = GPOINTER_TO_INT(data); #if GTK_CHECK_VERSION(3, 18, 0) if (remmina_pref.fullscreen_on_auto) { if (target_monitor == FULL_SCREEN_TARGET_MONITOR_UNDEFINED) gtk_window_fullscreen(GTK_WINDOW(widget)); else gtk_window_fullscreen_on_monitor(GTK_WINDOW(widget), gtk_window_get_screen(GTK_WINDOW(widget)), target_monitor); } else { REMMINA_DEBUG("Fullscreen managed by WM or by the user, as per settings"); gtk_window_fullscreen(GTK_WINDOW(widget)); } #else REMMINA_DEBUG("Cannot fullscreen on a specific monitor, feature available from GTK 3.18"); gtk_window_fullscreen(GTK_WINDOW(widget)); #endif if (remmina_protocol_widget_map_event(gp)) REMMINA_DEBUG("Called plugin mapping function"); return FALSE; } static RemminaConnectionWindow * rcw_new(gboolean fullscreen, int full_screen_target_monitor) { TRACE_CALL(__func__); RemminaConnectionWindow *cnnwin; cnnwin = RCW(g_object_new(REMMINA_TYPE_CONNECTION_WINDOW, NULL)); cnnwin->priv->on_delete_confirm_mode = RCW_ONDELETE_CONFIRM_IF_2_OR_MORE; if (fullscreen) /* Put the window in fullscreen after it is mapped to have it appear on the same monitor */ g_signal_connect(G_OBJECT(cnnwin), "map-event", G_CALLBACK(rcw_map_event_fullscreen), GINT_TO_POINTER(full_screen_target_monitor)); else g_signal_connect(G_OBJECT(cnnwin), "map-event", G_CALLBACK(rcw_map_event), NULL); g_signal_connect(G_OBJECT(cnnwin), "unmap-event", G_CALLBACK(rcw_unmap_event), NULL); gtk_container_set_border_width(GTK_CONTAINER(cnnwin), 0); g_signal_connect(G_OBJECT(cnnwin), "toolbar-place", G_CALLBACK(rcw_toolbar_place_signal), NULL); g_signal_connect(G_OBJECT(cnnwin), "delete-event", G_CALLBACK(rcw_delete_event), NULL); g_signal_connect(G_OBJECT(cnnwin), "destroy", G_CALLBACK(rcw_destroy), NULL); /* Under Xorg focus-in-event and focus-out-event don’t work when keyboard is grabbed * via gdk_device_grab. So we listen for window-state-event to detect focus in and focus out. * But we must also listen focus-in-event and focus-out-event because some * window managers missing _NET_WM_STATE_FOCUSED hint, does not update the window state * in case of focus change */ g_signal_connect(G_OBJECT(cnnwin), "window-state-event", G_CALLBACK(rcw_state_event), NULL); g_signal_connect(G_OBJECT(cnnwin), "focus-in-event", G_CALLBACK(rcw_focus_in_event), NULL); g_signal_connect(G_OBJECT(cnnwin), "focus-out-event", G_CALLBACK(rcw_focus_out_event), NULL); g_signal_connect(G_OBJECT(cnnwin), "enter-notify-event", G_CALLBACK(rcw_on_enter_notify_event), NULL); g_signal_connect(G_OBJECT(cnnwin), "leave-notify-event", G_CALLBACK(rcw_on_leave_notify_event), NULL); g_signal_connect(G_OBJECT(cnnwin), "configure_event", G_CALLBACK(rcw_on_configure), NULL); return cnnwin; } /* This function will be called for the first connection. A tag is set to the window so that * other connections can determine if whether a new tab should be append to the same window */ static void rcw_update_tag(RemminaConnectionWindow *cnnwin, RemminaConnectionObject *cnnobj) { TRACE_CALL(__func__); gchar *tag; switch (remmina_pref.tab_mode) { case REMMINA_TAB_BY_GROUP: tag = g_strdup(remmina_file_get_string(cnnobj->remmina_file, "group")); break; case REMMINA_TAB_BY_PROTOCOL: tag = g_strdup(remmina_file_get_string(cnnobj->remmina_file, "protocol")); break; default: tag = NULL; break; } g_object_set_data_full(G_OBJECT(cnnwin), "tag", tag, (GDestroyNotify)g_free); } void rcw_grab_focus(RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); RemminaConnectionObject *cnnobj; if (!(cnnobj = rcw_get_visible_cnnobj(cnnwin))) return; if (GTK_IS_WIDGET(cnnobj->proto)) remmina_protocol_widget_grab_focus(REMMINA_PROTOCOL_WIDGET(cnnobj->proto)); } static GtkWidget *nb_find_page_by_cnnobj(GtkNotebook *notebook, RemminaConnectionObject *cnnobj) { gint i, np; GtkWidget *found_page, *pg; if (cnnobj == NULL || cnnobj->cnnwin == NULL || cnnobj->cnnwin->priv == NULL) return NULL; found_page = NULL; np = gtk_notebook_get_n_pages(cnnobj->cnnwin->priv->notebook); for (i = 0; i < np; i++) { pg = gtk_notebook_get_nth_page(cnnobj->cnnwin->priv->notebook, i); if (g_object_get_data(G_OBJECT(pg), "cnnobj") == cnnobj) { found_page = pg; break; } } return found_page; } void rco_closewin(RemminaProtocolWidget *gp) { TRACE_CALL(__func__); RemminaConnectionObject *cnnobj = gp->cnnobj; GtkWidget *page_to_remove; if (cnnobj && cnnobj->scrolled_container && REMMINA_IS_SCROLLED_VIEWPORT(cnnobj->scrolled_container)) { REMMINA_DEBUG("deleting motion"); remmina_scrolled_viewport_remove_motion(REMMINA_SCROLLED_VIEWPORT(cnnobj->scrolled_container)); } if (cnnobj && cnnobj->cnnwin) { page_to_remove = nb_find_page_by_cnnobj(cnnobj->cnnwin->priv->notebook, cnnobj); if (page_to_remove) { gtk_notebook_remove_page( cnnobj->cnnwin->priv->notebook, gtk_notebook_page_num(cnnobj->cnnwin->priv->notebook, page_to_remove)); /* Invalidate pointers to objects destroyed by page removal */ cnnobj->aspectframe = NULL; cnnobj->viewport = NULL; cnnobj->scrolled_container = NULL; /* we cannot invalidate cnnobj->proto, because it can be already been * detached from the widget hierarchy in rco_on_disconnect() */ } } if (cnnobj) { cnnobj->remmina_file = NULL; g_free(cnnobj); gp->cnnobj = NULL; } remmina_application_condexit(REMMINA_CONDEXIT_ONDISCONNECT); } void rco_on_close_button_clicked(GtkButton *button, RemminaConnectionObject *cnnobj) { TRACE_CALL(__func__); if (REMMINA_IS_PROTOCOL_WIDGET(cnnobj->proto)) { if (!remmina_protocol_widget_is_closed((RemminaProtocolWidget *)cnnobj->proto)) remmina_protocol_widget_close_connection(REMMINA_PROTOCOL_WIDGET(cnnobj->proto)); else rco_closewin((RemminaProtocolWidget *)cnnobj->proto); } } static GtkWidget *rco_create_tab_label(RemminaConnectionObject *cnnobj) { TRACE_CALL(__func__); GtkWidget *hbox; GtkWidget *widget; GtkWidget *button; hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4); gtk_widget_show(hbox); widget = gtk_image_new_from_icon_name(remmina_file_get_icon_name(cnnobj->remmina_file), GTK_ICON_SIZE_MENU); gtk_widget_show(widget); gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, FALSE, 0); widget = gtk_label_new(remmina_file_get_string(cnnobj->remmina_file, "name")); gtk_widget_set_valign(widget, GTK_ALIGN_CENTER); gtk_widget_set_halign(widget, GTK_ALIGN_CENTER); gtk_widget_show(widget); gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, TRUE, 0); button = gtk_button_new(); // The "x" to close the tab gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE); #if GTK_CHECK_VERSION(3, 20, 0) gtk_widget_set_focus_on_click(GTK_WIDGET(widget), FALSE); #else gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE); #endif gtk_widget_set_name(button, "remmina-small-button"); gtk_widget_show(button); widget = gtk_image_new_from_icon_name("window-close", GTK_ICON_SIZE_MENU); gtk_widget_show(widget); gtk_container_add(GTK_CONTAINER(button), widget); gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0); g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(rco_on_close_button_clicked), cnnobj); return hbox; } static GtkWidget *rco_create_tab_page(RemminaConnectionObject *cnnobj) { GtkWidget *page; page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); gtk_widget_set_name(page, "remmina-tab-page"); return page; } static GtkWidget *rcw_append_new_page(RemminaConnectionWindow *cnnwin, RemminaConnectionObject *cnnobj) { TRACE_CALL(__func__); GtkWidget *page, *label; GtkNotebook *notebook; notebook = cnnwin->priv->notebook; page = rco_create_tab_page(cnnobj); g_object_set_data(G_OBJECT(page), "cnnobj", cnnobj); label = rco_create_tab_label(cnnobj); cnnobj->cnnwin = cnnwin; gtk_notebook_append_page(notebook, page, label); gtk_notebook_set_tab_reorderable(notebook, page, TRUE); gtk_notebook_set_tab_detachable(notebook, page, TRUE); /* This trick prevents the tab label from being focused */ gtk_widget_set_can_focus(gtk_widget_get_parent(label), FALSE); if (gtk_widget_get_parent(cnnobj->scrolled_container) != NULL) printf("REMMINA WARNING in %s: scrolled_container already has a parent\n", __func__); gtk_box_pack_start(GTK_BOX(page), cnnobj->scrolled_container, TRUE, TRUE, 0); gtk_widget_show(page); return page; } static void rcw_update_notebook(RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); GtkNotebook *notebook; gint n; notebook = GTK_NOTEBOOK(cnnwin->priv->notebook); switch (cnnwin->priv->view_mode) { case SCROLLED_WINDOW_MODE: n = gtk_notebook_get_n_pages(notebook); gtk_notebook_set_show_tabs(notebook, remmina_pref.always_show_tab ? TRUE : n > 1); gtk_notebook_set_show_border(notebook, remmina_pref.always_show_tab ? TRUE : n > 1); break; default: gtk_notebook_set_show_tabs(notebook, FALSE); gtk_notebook_set_show_border(notebook, FALSE); break; } } static gboolean rcw_on_switch_page_finalsel(gpointer user_data) { TRACE_CALL(__func__); RemminaConnectionWindowPriv *priv; RemminaConnectionObject *cnnobj; if (!user_data) return FALSE; cnnobj = (RemminaConnectionObject *)user_data; if (!cnnobj->cnnwin) return FALSE; priv = cnnobj->cnnwin->priv; if (GTK_IS_WIDGET(cnnobj->cnnwin)) { rcw_floating_toolbar_show(cnnobj->cnnwin, TRUE); if (!priv->hidetb_eventsource) priv->hidetb_eventsource = g_timeout_add(TB_HIDE_TIME_TIME, (GSourceFunc) rcw_floating_toolbar_hide, cnnobj->cnnwin); rco_update_toolbar(cnnobj); rcw_grab_focus(cnnobj->cnnwin); if (priv->view_mode != SCROLLED_WINDOW_MODE) rco_check_resize(cnnobj); } priv->spf_eventsourceid = 0; return FALSE; } static void rcw_on_switch_page(GtkNotebook *notebook, GtkWidget *newpage, guint page_num, RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); RemminaConnectionWindowPriv *priv = cnnwin->priv; RemminaConnectionObject *cnnobj_newpage; cnnobj_newpage = g_object_get_data(G_OBJECT(newpage), "cnnobj"); if (priv->spf_eventsourceid) g_source_remove(priv->spf_eventsourceid); priv->spf_eventsourceid = g_idle_add(rcw_on_switch_page_finalsel, cnnobj_newpage); } static void rcw_on_page_added(GtkNotebook *notebook, GtkWidget *child, guint page_num, RemminaConnectionWindow *cnnwin) { if (gtk_notebook_get_n_pages(GTK_NOTEBOOK(cnnwin->priv->notebook)) > 0) rcw_update_notebook(cnnwin); } static void rcw_on_page_removed(GtkNotebook *notebook, GtkWidget *child, guint page_num, RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); if (gtk_notebook_get_n_pages(GTK_NOTEBOOK(cnnwin->priv->notebook)) <= 0) gtk_widget_destroy(GTK_WIDGET(cnnwin)); } static GtkNotebook * rcw_on_notebook_create_window(GtkNotebook *notebook, GtkWidget *page, gint x, gint y, gpointer data) { /* This signal callback is called by GTK when a detachable tab is dropped on the root window * or in an existing window */ TRACE_CALL(__func__); RemminaConnectionWindow *srccnnwin; RemminaConnectionWindow *dstcnnwin; RemminaConnectionObject *cnnobj; GdkWindow *window; gchar *srctag; gint width, height; #if GTK_CHECK_VERSION(3, 20, 0) GdkSeat *seat; #else GdkDeviceManager *manager; #endif GdkDevice *device = NULL; #if GTK_CHECK_VERSION(3, 20, 0) seat = gdk_display_get_default_seat(gdk_display_get_default()); device = gdk_seat_get_pointer(seat); #else manager = gdk_display_get_device_manager(gdk_display_get_default()); device = gdk_device_manager_get_client_pointer(manager); #endif window = gdk_device_get_window_at_position(device, &x, &y); srccnnwin = RCW(gtk_widget_get_toplevel(GTK_WIDGET(notebook))); dstcnnwin = RCW(remmina_widget_pool_find_by_window(REMMINA_TYPE_CONNECTION_WINDOW, window)); if (srccnnwin == dstcnnwin) return NULL; if (gtk_notebook_get_n_pages(GTK_NOTEBOOK(srccnnwin->priv->notebook)) == 1 && !dstcnnwin) return NULL; cnnobj = (RemminaConnectionObject *)g_object_get_data(G_OBJECT(page), "cnnobj"); if (!dstcnnwin) { /* Drop is directed to a new rcw: create a new scrolled window to accommodate * the dropped connectionand move our cnnobj there. Width and * height of the new window are cloned from the current window */ srctag = (gchar *)g_object_get_data(G_OBJECT(srccnnwin), "tag"); gtk_window_get_size(GTK_WINDOW(srccnnwin), &width, &height); dstcnnwin = rcw_create_scrolled(width, height, FALSE); // New dropped window is never maximized g_object_set_data_full(G_OBJECT(dstcnnwin), "tag", g_strdup(srctag), (GDestroyNotify)g_free); /* when returning, GTK will move the whole tab to the new notebook. * Prepare cnnobj to be hosted in the new cnnwin */ cnnobj->cnnwin = dstcnnwin; } else { cnnobj->cnnwin = dstcnnwin; } remmina_protocol_widget_set_hostkey_func(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), (RemminaHostkeyFunc)rcw_hostkey_func); return GTK_NOTEBOOK(cnnobj->cnnwin->priv->notebook); } static GtkNotebook * rcw_create_notebook(RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); GtkNotebook *notebook; notebook = GTK_NOTEBOOK(gtk_notebook_new()); gtk_notebook_set_scrollable(GTK_NOTEBOOK(notebook), TRUE); gtk_widget_show(GTK_WIDGET(notebook)); g_signal_connect(G_OBJECT(notebook), "create-window", G_CALLBACK(rcw_on_notebook_create_window), NULL); g_signal_connect(G_OBJECT(notebook), "switch-page", G_CALLBACK(rcw_on_switch_page), cnnwin); g_signal_connect(G_OBJECT(notebook), "page-added", G_CALLBACK(rcw_on_page_added), cnnwin); g_signal_connect(G_OBJECT(notebook), "page-removed", G_CALLBACK(rcw_on_page_removed), cnnwin); gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE); return notebook; } /* Create a scrolled toplevel window */ static RemminaConnectionWindow *rcw_create_scrolled(gint width, gint height, gboolean maximize) { TRACE_CALL(__func__); RemminaConnectionWindow *cnnwin; GtkWidget *grid; GtkWidget *toolbar; GtkNotebook *notebook; GtkSettings *settings = gtk_settings_get_default(); cnnwin = rcw_new(FALSE, 0); gtk_widget_realize(GTK_WIDGET(cnnwin)); gtk_window_set_default_size(GTK_WINDOW(cnnwin), width, height); g_object_set(settings, "gtk-application-prefer-dark-theme", remmina_pref.dark_theme, NULL); /* Create the toolbar */ toolbar = rcw_create_toolbar(cnnwin, SCROLLED_WINDOW_MODE); /* Create the notebook */ notebook = rcw_create_notebook(cnnwin); /* Create the grid container for toolbars+notebook and populate it */ grid = gtk_grid_new(); gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(notebook), 0, 0, 1, 1); gtk_widget_set_hexpand(GTK_WIDGET(notebook), TRUE); gtk_widget_set_vexpand(GTK_WIDGET(notebook), TRUE); rcw_place_toolbar(GTK_TOOLBAR(toolbar), GTK_GRID(grid), GTK_WIDGET(notebook), remmina_pref.toolbar_placement); gtk_container_add(GTK_CONTAINER(cnnwin), grid); /* Add drag capabilities to the toolbar */ gtk_drag_source_set(GTK_WIDGET(toolbar), GDK_BUTTON1_MASK, dnd_targets_tb, sizeof dnd_targets_tb / sizeof *dnd_targets_tb, GDK_ACTION_MOVE); g_signal_connect_after(GTK_WIDGET(toolbar), "drag-begin", G_CALLBACK(rcw_tb_drag_begin), NULL); g_signal_connect(GTK_WIDGET(toolbar), "drag-failed", G_CALLBACK(rcw_tb_drag_failed), cnnwin); /* Add drop capabilities to the drop/dest target for the toolbar (the notebook) */ gtk_drag_dest_set(GTK_WIDGET(notebook), GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT, dnd_targets_tb, sizeof dnd_targets_tb / sizeof *dnd_targets_tb, GDK_ACTION_MOVE); gtk_drag_dest_set_track_motion(GTK_WIDGET(notebook), TRUE); g_signal_connect(GTK_WIDGET(notebook), "drag-drop", G_CALLBACK(rcw_tb_drag_drop), cnnwin); cnnwin->priv->view_mode = SCROLLED_WINDOW_MODE; cnnwin->priv->toolbar = toolbar; cnnwin->priv->grid = grid; cnnwin->priv->notebook = notebook; cnnwin->priv->ss_width = width; cnnwin->priv->ss_height = height; cnnwin->priv->ss_maximized = maximize; /* The notebook and all its child must be realized now, or a reparent will * call unrealize() and will destroy a GtkSocket */ gtk_widget_show(grid); gtk_widget_show(GTK_WIDGET(cnnwin)); GtkWindowGroup *wingrp = gtk_window_group_new(); gtk_window_group_add_window(wingrp, GTK_WINDOW(cnnwin)); gtk_window_set_transient_for(GTK_WINDOW(cnnwin), NULL); if (maximize) gtk_window_maximize(GTK_WINDOW(cnnwin)); rcw_set_toolbar_visibility(cnnwin); return cnnwin; } static void rcw_create_overlay_ftb_overlay(RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); GtkWidget *revealer; RemminaConnectionWindowPriv *priv; priv = cnnwin->priv; if (priv->overlay_ftb_overlay != NULL) { gtk_widget_destroy(priv->overlay_ftb_overlay); priv->overlay_ftb_overlay = NULL; priv->revealer = NULL; } if (priv->overlay_ftb_fr != NULL) { gtk_widget_destroy(priv->overlay_ftb_fr); priv->overlay_ftb_fr = NULL; } rcw_create_floating_toolbar(cnnwin, priv->fss_view_mode); priv->overlay_ftb_overlay = gtk_event_box_new(); GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); gtk_container_set_border_width(GTK_CONTAINER(vbox), 0); GtkWidget *handle = gtk_drawing_area_new(); gtk_widget_set_size_request(handle, 4, 4); gtk_widget_set_name(handle, "ftb-handle"); revealer = gtk_revealer_new(); gtk_widget_set_halign(GTK_WIDGET(priv->overlay_ftb_overlay), GTK_ALIGN_CENTER); if (remmina_pref.floating_toolbar_placement == FLOATING_TOOLBAR_PLACEMENT_BOTTOM) { gtk_box_pack_start(GTK_BOX(vbox), handle, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox), revealer, FALSE, FALSE, 0); gtk_revealer_set_transition_type(GTK_REVEALER(revealer), GTK_REVEALER_TRANSITION_TYPE_SLIDE_UP); gtk_widget_set_valign(GTK_WIDGET(priv->overlay_ftb_overlay), GTK_ALIGN_END); } else { gtk_box_pack_start(GTK_BOX(vbox), revealer, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox), handle, FALSE, FALSE, 0); gtk_revealer_set_transition_type(GTK_REVEALER(revealer), GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN); gtk_widget_set_valign(GTK_WIDGET(priv->overlay_ftb_overlay), GTK_ALIGN_START); } gtk_container_add(GTK_CONTAINER(revealer), priv->floating_toolbar_widget); gtk_widget_set_halign(GTK_WIDGET(revealer), GTK_ALIGN_CENTER); gtk_widget_set_valign(GTK_WIDGET(revealer), GTK_ALIGN_START); priv->revealer = revealer; GtkWidget *fr; fr = gtk_frame_new(NULL); priv->overlay_ftb_fr = fr; gtk_container_add(GTK_CONTAINER(priv->overlay_ftb_overlay), fr); gtk_container_add(GTK_CONTAINER(fr), vbox); gtk_widget_show(vbox); gtk_widget_show(revealer); gtk_widget_show(handle); gtk_widget_show(priv->overlay_ftb_overlay); gtk_widget_show(fr); if (remmina_pref.floating_toolbar_placement == FLOATING_TOOLBAR_PLACEMENT_BOTTOM) gtk_widget_set_name(fr, "ftbbox-lower"); else gtk_widget_set_name(fr, "ftbbox-upper"); gtk_overlay_add_overlay(GTK_OVERLAY(priv->overlay), priv->overlay_ftb_overlay); rcw_floating_toolbar_show(cnnwin, TRUE); g_signal_connect(G_OBJECT(priv->overlay_ftb_overlay), "enter-notify-event", G_CALLBACK(rcw_floating_toolbar_on_enter), cnnwin); g_signal_connect(G_OBJECT(priv->overlay_ftb_overlay), "leave-notify-event", G_CALLBACK(rcw_floating_toolbar_on_leave), cnnwin); g_signal_connect(G_OBJECT(priv->overlay_ftb_overlay), "scroll-event", G_CALLBACK(rcw_floating_toolbar_on_scroll), cnnwin); gtk_widget_add_events( GTK_WIDGET(priv->overlay_ftb_overlay), GDK_SCROLL_MASK #if GTK_CHECK_VERSION(3, 4, 0) | GDK_SMOOTH_SCROLL_MASK #endif ); /* Add drag and drop capabilities to the source */ gtk_drag_source_set(GTK_WIDGET(priv->overlay_ftb_overlay), GDK_BUTTON1_MASK, dnd_targets_ftb, sizeof dnd_targets_ftb / sizeof *dnd_targets_ftb, GDK_ACTION_MOVE); g_signal_connect_after(GTK_WIDGET(priv->overlay_ftb_overlay), "drag-begin", G_CALLBACK(rcw_ftb_drag_begin), cnnwin); if (remmina_pref.fullscreen_toolbar_visibility == FLOATING_TOOLBAR_VISIBILITY_DISABLE) /* toolbar in fullscreenmode disabled, hide everything */ gtk_widget_hide(fr); } static gboolean rcw_ftb_drag_drop(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, RemminaConnectionWindow *cnnwin) { TRACE_CALL(__func__); GtkAllocation wa; gint new_floating_toolbar_placement; RemminaConnectionObject *cnnobj; gtk_widget_get_allocation(widget, &wa); if (y >= wa.height / 2) new_floating_toolbar_placement = FLOATING_TOOLBAR_PLACEMENT_BOTTOM; else new_floating_toolbar_placement = FLOATING_TOOLBAR_PLACEMENT_TOP; gtk_drag_finish(context, TRUE, TRUE, time); if (new_floating_toolbar_placement != remmina_pref.floating_toolbar_placement) { /* Destroy and recreate the FTB */ remmina_pref.floating_toolbar_placement = new_floating_toolbar_placement; remmina_pref_save(); rcw_create_overlay_ftb_overlay(cnnwin); cnnobj = rcw_get_visible_cnnobj(cnnwin); if (cnnobj) rco_update_toolbar(cnnobj); } return TRUE; } static void rcw_ftb_drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer user_data) { TRACE_CALL(__func__); 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, wa.width, wa.height); cr = cairo_create(surface); cairo_set_source_rgb(cr, 0.6, 0.6, 0.6); cairo_set_line_width(cr, 2); cairo_set_dash(cr, dashes, 1, 0); cairo_rectangle(cr, 0, 0, wa.width, wa.height); cairo_stroke(cr); cairo_destroy(cr); gtk_drag_set_icon_surface(context, surface); } RemminaConnectionWindow *rcw_create_fullscreen(GtkWindow *old, gint view_mode) { TRACE_CALL(__func__); RemminaConnectionWindow *cnnwin; GtkNotebook *notebook; #if GTK_CHECK_VERSION(3, 22, 0) gint n_monitors; gint i; GdkMonitor *old_monitor; GdkDisplay *old_display; GdkWindow *old_window; #endif gint full_screen_target_monitor; full_screen_target_monitor = FULL_SCREEN_TARGET_MONITOR_UNDEFINED; if (old) { #if GTK_CHECK_VERSION(3, 22, 0) old_window = gtk_widget_get_window(GTK_WIDGET(old)); old_display = gdk_window_get_display(old_window); old_monitor = gdk_display_get_monitor_at_window(old_display, old_window); n_monitors = gdk_display_get_n_monitors(old_display); for (i = 0; i < n_monitors; ++i) { if (gdk_display_get_monitor(old_display, i) == old_monitor) { full_screen_target_monitor = i; break; } } #else full_screen_target_monitor = gdk_screen_get_monitor_at_window( gdk_screen_get_default(), gtk_widget_get_window(GTK_WIDGET(old))); #endif } cnnwin = rcw_new(TRUE, full_screen_target_monitor); gtk_widget_set_name(GTK_WIDGET(cnnwin), "remmina-connection-window-fullscreen"); gtk_widget_realize(GTK_WIDGET(cnnwin)); if (!view_mode) view_mode = VIEWPORT_FULLSCREEN_MODE; notebook = rcw_create_notebook(cnnwin); cnnwin->priv->overlay = gtk_overlay_new(); gtk_container_add(GTK_CONTAINER(cnnwin), cnnwin->priv->overlay); gtk_container_add(GTK_CONTAINER(cnnwin->priv->overlay), GTK_WIDGET(notebook)); gtk_widget_show(GTK_WIDGET(cnnwin->priv->overlay)); cnnwin->priv->notebook = notebook; cnnwin->priv->view_mode = view_mode; cnnwin->priv->fss_view_mode = view_mode; /* Create the floating toolbar */ rcw_create_overlay_ftb_overlay(cnnwin); /* Add drag and drop capabilities to the drop/dest target for floating toolbar */ gtk_drag_dest_set(GTK_WIDGET(cnnwin->priv->overlay), GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT, dnd_targets_ftb, sizeof dnd_targets_ftb / sizeof *dnd_targets_ftb, GDK_ACTION_MOVE); gtk_drag_dest_set_track_motion(GTK_WIDGET(cnnwin->priv->notebook), TRUE); g_signal_connect(GTK_WIDGET(cnnwin->priv->overlay), "drag-drop", G_CALLBACK(rcw_ftb_drag_drop), cnnwin); gtk_widget_show(GTK_WIDGET(cnnwin)); GtkWindowGroup *wingrp = gtk_window_group_new(); gtk_window_group_add_window(wingrp, GTK_WINDOW(cnnwin)); gtk_window_set_transient_for(GTK_WINDOW(cnnwin), NULL); return cnnwin; } static gboolean rcw_hostkey_func(RemminaProtocolWidget *gp, guint keyval, gboolean release) { TRACE_CALL(__func__); RemminaConnectionObject *cnnobj = gp->cnnobj; RemminaConnectionWindowPriv *priv = cnnobj->cnnwin->priv; const RemminaProtocolFeature *feature; gint i; if (release) { if (remmina_pref.hostkey && keyval == remmina_pref.hostkey) { priv->hostkey_activated = FALSE; if (priv->hostkey_used) /* hostkey pressed + something else */ return TRUE; } /* If hostkey is released without pressing other keys, we should execute the * shortcut key which is the same as hostkey. Be default, this is grab/ungrab * keyboard */ else if (priv->hostkey_activated) { /* Trap all key releases when hostkey is pressed */ /* hostkey pressed + something else */ return TRUE; } else { /* Any key pressed, no hostkey */ return FALSE; } } else if (remmina_pref.hostkey && keyval == remmina_pref.hostkey) { /** @todo Add callback for hostname transparent overlay #832 */ priv->hostkey_activated = TRUE; priv->hostkey_used = FALSE; return TRUE; } else if (!priv->hostkey_activated) { /* Any key pressed, no hostkey */ return FALSE; } priv->hostkey_used = TRUE; keyval = gdk_keyval_to_lower(keyval); if (keyval == GDK_KEY_Up || keyval == GDK_KEY_Down || keyval == GDK_KEY_Left || keyval == GDK_KEY_Right) { GtkAdjustment *adjust; int pos; if (cnnobj->connected && GTK_IS_SCROLLED_WINDOW(cnnobj->scrolled_container)) { if (keyval == GDK_KEY_Up || keyval == GDK_KEY_Down) adjust = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(cnnobj->scrolled_container)); else adjust = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(cnnobj->scrolled_container)); if (keyval == GDK_KEY_Up || keyval == GDK_KEY_Left) pos = 0; else pos = gtk_adjustment_get_upper(adjust); gtk_adjustment_set_value(adjust, pos); if (keyval == GDK_KEY_Up || keyval == GDK_KEY_Down) gtk_scrolled_window_set_vadjustment(GTK_SCROLLED_WINDOW(cnnobj->scrolled_container), adjust); else gtk_scrolled_window_set_hadjustment(GTK_SCROLLED_WINDOW(cnnobj->scrolled_container), adjust); } else if (REMMINA_IS_SCROLLED_VIEWPORT(cnnobj->scrolled_container)) { RemminaScrolledViewport *gsv; GtkWidget *child; GdkWindow *gsvwin; gint sz; GtkAdjustment *adj; gdouble value; if (!GTK_IS_BIN(cnnobj->scrolled_container)) return FALSE; gsv = REMMINA_SCROLLED_VIEWPORT(cnnobj->scrolled_container); child = gtk_bin_get_child(GTK_BIN(gsv)); if (!GTK_IS_VIEWPORT(child)) return FALSE; gsvwin = gtk_widget_get_window(GTK_WIDGET(gsv)); if (!gsv) return FALSE; if (keyval == GDK_KEY_Up || keyval == GDK_KEY_Down) { sz = gdk_window_get_height(gsvwin) + 2; // Add 2px of black scroll border adj = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(child)); } else { sz = gdk_window_get_width(gsvwin) + 2; // Add 2px of black scroll border adj = gtk_scrollable_get_hadjustment(GTK_SCROLLABLE(child)); } if (keyval == GDK_KEY_Up || keyval == GDK_KEY_Left) value = 0; else value = gtk_adjustment_get_upper(GTK_ADJUSTMENT(adj)) - (gdouble)sz + 2.0; gtk_adjustment_set_value(GTK_ADJUSTMENT(adj), value); } } if (keyval == remmina_pref.shortcutkey_fullscreen && !extrahardening) { switch (priv->view_mode) { case SCROLLED_WINDOW_MODE: rcw_switch_viewmode(cnnobj->cnnwin, priv->fss_view_mode); break; case SCROLLED_FULLSCREEN_MODE: case VIEWPORT_FULLSCREEN_MODE: rcw_switch_viewmode(cnnobj->cnnwin, SCROLLED_WINDOW_MODE); break; default: break; } } else if (keyval == remmina_pref.shortcutkey_autofit && !extrahardening) { if (priv->toolitem_autofit && gtk_widget_is_sensitive(GTK_WIDGET(priv->toolitem_autofit))) rcw_toolbar_autofit(GTK_TOOL_ITEM(gp), cnnobj->cnnwin); } else if (keyval == remmina_pref.shortcutkey_nexttab && !extrahardening) { i = gtk_notebook_get_current_page(GTK_NOTEBOOK(priv->notebook)) + 1; if (i >= gtk_notebook_get_n_pages(GTK_NOTEBOOK(priv->notebook))) i = 0; gtk_notebook_set_current_page(GTK_NOTEBOOK(priv->notebook), i); } else if (keyval == remmina_pref.shortcutkey_prevtab && !extrahardening) { i = gtk_notebook_get_current_page(GTK_NOTEBOOK(priv->notebook)) - 1; if (i < 0) i = gtk_notebook_get_n_pages(GTK_NOTEBOOK(priv->notebook)) - 1; gtk_notebook_set_current_page(GTK_NOTEBOOK(priv->notebook), i); } else if (keyval == remmina_pref.shortcutkey_clipboard && !extrahardening) { if (remmina_protocol_widget_plugin_receives_keystrokes(REMMINA_PROTOCOL_WIDGET(cnnobj->proto))) { remmina_protocol_widget_send_clipboard((RemminaProtocolWidget*)cnnobj->proto, G_OBJECT(cnnobj->proto)); } } else if (keyval == remmina_pref.shortcutkey_scale && !extrahardening) { if (gtk_widget_is_sensitive(GTK_WIDGET(priv->toolitem_scale))) { gtk_toggle_tool_button_set_active( GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_scale), !gtk_toggle_tool_button_get_active( GTK_TOGGLE_TOOL_BUTTON( priv->toolitem_scale))); } } else if (keyval == remmina_pref.shortcutkey_grab && !extrahardening) { gtk_toggle_tool_button_set_active( GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_grab), !gtk_toggle_tool_button_get_active( GTK_TOGGLE_TOOL_BUTTON( priv->toolitem_grab))); } else if (keyval == remmina_pref.shortcutkey_minimize && !extrahardening) { rcw_toolbar_minimize(GTK_TOOL_ITEM(gp), cnnobj->cnnwin); } else if (keyval == remmina_pref.shortcutkey_viewonly && !extrahardening) { remmina_file_set_int(cnnobj->remmina_file, "viewonly", (remmina_file_get_int(cnnobj->remmina_file, "viewonly", 0) == 0) ? 1 : 0); } else if (keyval == remmina_pref.shortcutkey_screenshot && !extrahardening) { rcw_toolbar_screenshot(GTK_TOOL_ITEM(gp), cnnobj->cnnwin); } else if (keyval == remmina_pref.shortcutkey_disconnect && !extrahardening) { rco_disconnect_current_page(cnnobj); } else if (keyval == remmina_pref.shortcutkey_toolbar && !extrahardening) { if (priv->view_mode == SCROLLED_WINDOW_MODE) { remmina_pref.hide_connection_toolbar = !remmina_pref.hide_connection_toolbar; rcw_set_toolbar_visibility(cnnobj->cnnwin); } } else { for (feature = remmina_protocol_widget_get_features( REMMINA_PROTOCOL_WIDGET( cnnobj->proto)); feature && feature->type; feature++) { if (feature->type == REMMINA_PROTOCOL_FEATURE_TYPE_TOOL && GPOINTER_TO_UINT( feature->opt3) == keyval) { remmina_protocol_widget_call_feature_by_ref( REMMINA_PROTOCOL_WIDGET( cnnobj->proto), feature); break; } } } /* If a keypress makes the current cnnobj to move to another window, * priv is now invalid. So we can no longer use priv here */ cnnobj->cnnwin->priv->hostkey_activated = FALSE; /* Trap all keypresses when hostkey is pressed */ return TRUE; } static RemminaConnectionWindow *rcw_find(RemminaFile *remminafile) { TRACE_CALL(__func__); const gchar *tag; switch (remmina_pref.tab_mode) { case REMMINA_TAB_BY_GROUP: tag = remmina_file_get_string(remminafile, "group"); break; case REMMINA_TAB_BY_PROTOCOL: tag = remmina_file_get_string(remminafile, "protocol"); break; case REMMINA_TAB_ALL: tag = NULL; break; case REMMINA_TAB_NONE: default: return NULL; } return RCW(remmina_widget_pool_find(REMMINA_TYPE_CONNECTION_WINDOW, tag)); } gboolean rcw_delayed_window_present(gpointer user_data) { RemminaConnectionWindow *cnnwin = (RemminaConnectionWindow *)user_data; if (cnnwin) { gtk_window_present_with_time(GTK_WINDOW(cnnwin), (guint32)(g_get_monotonic_time() / 1000)); rcw_grab_focus(cnnwin); } cnnwin->priv->dwp_eventsourceid = 0; return G_SOURCE_REMOVE; } void rco_on_connect(RemminaProtocolWidget *gp, RemminaConnectionObject *cnnobj) { TRACE_CALL(__func__); REMMINA_DEBUG("Connect signal emitted"); /* This signal handler is called by a plugin when it’s correctly connected * (and authenticated) */ cnnobj->connected = TRUE; remmina_protocol_widget_set_hostkey_func(REMMINA_PROTOCOL_WIDGET(cnnobj->proto), (RemminaHostkeyFunc)rcw_hostkey_func); /** Remember recent list for quick connect, and save the current date * in the last_used field. */ if (remmina_file_get_filename(cnnobj->remmina_file) == NULL) remmina_pref_add_recent(remmina_file_get_string(cnnobj->remmina_file, "protocol"), remmina_file_get_string(cnnobj->remmina_file, "server")); REMMINA_DEBUG("We save the last successful connection date"); //remmina_file_set_string(cnnobj->remmina_file, "last_success", last_success); remmina_file_state_last_success(cnnobj->remmina_file); //REMMINA_DEBUG("Last connection made on %s.", last_success); REMMINA_DEBUG("Saving credentials"); /* Save credentials */ remmina_file_save(cnnobj->remmina_file); if (cnnobj->cnnwin->priv->floating_toolbar_widget) gtk_widget_show(cnnobj->cnnwin->priv->floating_toolbar_widget); rco_update_toolbar(cnnobj); REMMINA_DEBUG("Trying to present the window"); /* Try to present window */ cnnobj->cnnwin->priv->dwp_eventsourceid = g_timeout_add(200, rcw_delayed_window_present, (gpointer)cnnobj->cnnwin); } static void cb_lasterror_confirmed(void *cbdata, int btn) { TRACE_CALL(__func__); rco_closewin((RemminaProtocolWidget *)cbdata); } void rco_on_disconnect(RemminaProtocolWidget *gp, gpointer data) { TRACE_CALL(__func__); RemminaConnectionObject *cnnobj = gp->cnnobj; RemminaConnectionWindowPriv *priv = cnnobj->cnnwin->priv; GtkWidget *pparent; REMMINA_DEBUG("Disconnect signal received on RemminaProtocolWidget"); /* Detach the protocol widget from the notebook now, or we risk that a * window delete will destroy cnnobj->proto before we complete disconnection. */ pparent = gtk_widget_get_parent(cnnobj->proto); if (pparent != NULL) { g_object_ref(cnnobj->proto); gtk_container_remove(GTK_CONTAINER(pparent), cnnobj->proto); } cnnobj->connected = FALSE; if (remmina_pref.save_view_mode) { if (cnnobj->cnnwin) remmina_file_set_int(cnnobj->remmina_file, "viewmode", cnnobj->cnnwin->priv->view_mode); remmina_file_save(cnnobj->remmina_file); } rcw_kp_ungrab(cnnobj->cnnwin); gtk_toggle_tool_button_set_active( GTK_TOGGLE_TOOL_BUTTON(priv->toolitem_grab), FALSE); if (remmina_protocol_widget_has_error(gp)) { /* We cannot close window immediately, but we must show a message panel */ RemminaMessagePanel *mp; /* Destroy scrolled_container (and viewport) and all its children the plugin created * on it, so they will not receive GUI signals */ if (cnnobj->scrolled_container) { gtk_widget_destroy(cnnobj->scrolled_container); cnnobj->scrolled_container = NULL; } cnnobj->viewport = NULL; mp = remmina_message_panel_new(); remmina_message_panel_setup_message(mp, remmina_protocol_widget_get_error_message(gp), cb_lasterror_confirmed, gp); rco_show_message_panel(gp->cnnobj, mp); REMMINA_DEBUG("Could not disconnect"); } else { rco_closewin(gp); REMMINA_DEBUG("Disconnected"); } } void rco_on_desktop_resize(RemminaProtocolWidget *gp, gpointer data) { TRACE_CALL(__func__); RemminaConnectionObject *cnnobj = gp->cnnobj; if (cnnobj && cnnobj->cnnwin && cnnobj->cnnwin->priv->view_mode != SCROLLED_WINDOW_MODE) rco_check_resize(cnnobj); } void rco_on_update_align(RemminaProtocolWidget *gp, gpointer data) { TRACE_CALL(__func__); RemminaConnectionObject *cnnobj = gp->cnnobj; remmina_protocol_widget_update_alignment(cnnobj); } void rco_on_lock_dynres(RemminaProtocolWidget *gp, gpointer data) { TRACE_CALL(__func__); RemminaConnectionObject *cnnobj = gp->cnnobj; cnnobj->dynres_unlocked = FALSE; rco_update_toolbar(cnnobj); } void rco_on_unlock_dynres(RemminaProtocolWidget *gp, gpointer data) { TRACE_CALL(__func__); RemminaConnectionObject *cnnobj = gp->cnnobj; cnnobj->dynres_unlocked = TRUE; rco_update_toolbar(cnnobj); } gboolean rcw_open_from_filename(const gchar *filename) { TRACE_CALL(__func__); RemminaFile *remminafile; GtkWidget *dialog; remminafile = remmina_file_manager_load_file(filename); if (remminafile) { if (remmina_file_get_int (remminafile, "profile-lock", FALSE) && remmina_unlock_new(remmina_main_get_window()) == 0) return FALSE; rcw_open_from_file(remminafile); return TRUE; } else { dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, _("The file “%s” is corrupted, unreadable, or could not be found."), filename); g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(gtk_widget_destroy), NULL); gtk_widget_show(dialog); remmina_widget_pool_register(dialog); return FALSE; } } static gboolean open_connection_last_stage(gpointer user_data) { RemminaProtocolWidget *gp = (RemminaProtocolWidget *)user_data; /* Now we have an allocated size for our RemminaProtocolWidget. We can proceed with the connection */ remmina_protocol_widget_update_remote_resolution(gp); remmina_protocol_widget_open_connection(gp); rco_check_resize(gp->cnnobj); return FALSE; } static void rpw_size_allocated_on_connection(GtkWidget *w, GdkRectangle *allocation, gpointer user_data) { RemminaProtocolWidget *gp = (RemminaProtocolWidget *)w; /* Disconnect signal handler to avoid to be called again after a normal resize */ g_signal_handler_disconnect(w, gp->cnnobj->deferred_open_size_allocate_handler); /* Allow extra 100 ms for size allocation (do we really need it?) */ g_timeout_add(100, open_connection_last_stage, gp); return; } void rcw_open_from_file(RemminaFile *remminafile) { TRACE_CALL(__func__); rcw_open_from_file_full(remminafile, NULL, NULL, NULL); } static void set_label_selectable(gpointer data, gpointer user_data) { TRACE_CALL(__func__); GtkWidget *widget = GTK_WIDGET(data); if (GTK_IS_LABEL(widget)) gtk_label_set_selectable(GTK_LABEL(widget), TRUE); } /** * @brief These define the response id's of the * gtksocket-is-not-available-warning-dialog buttons. */ enum GTKSOCKET_NOT_AVAIL_RESPONSE_TYPE { GTKSOCKET_NOT_AVAIL_RESPONSE_OPEN_BROWSER = 0, GTKSOCKET_NOT_AVAIL_RESPONSE_NUM }; /** * @brief Gets called if the user interacts with the * gtksocket-is-not-available-warning-dialog */ static void rcw_gtksocket_not_available_dialog_response(GtkDialog * self, gint response_id, RemminaConnectionObject * cnnobj) { TRACE_CALL(__func__); GError *error = NULL; if (response_id == GTKSOCKET_NOT_AVAIL_RESPONSE_OPEN_BROWSER) { gtk_show_uri_on_window( NULL, // TRANSLATORS: This should be a link to the Remmina wiki page: // TRANSLATORS: 'GtkSocket feature is not available'. _("https://gitlab.com/Remmina/Remmina/-/wikis/GtkSocket-feature-is-not-available-in-a-Wayland-session"), GDK_CURRENT_TIME, &error ); } // Close the current page since it's useless without GtkSocket. // The user would need to manually click the close button. if (cnnobj) rco_disconnect_current_page(cnnobj); gtk_widget_destroy(GTK_WIDGET(self)); } GtkWidget *rcw_open_from_file_full(RemminaFile *remminafile, GCallback disconnect_cb, gpointer data, guint *handler) { TRACE_CALL(__func__); RemminaConnectionObject *cnnobj; gint ret; GtkWidget *dialog; GtkWidget *newpage; gint width, height; gboolean maximize; gint view_mode; const gchar *msg; RemminaScaleMode scalemode; if (disconnect_cb) { g_print("disconnect_cb is deprecated inside rcw_open_from_file_full() and should be null\n"); return NULL; } /* Create the RemminaConnectionObject */ cnnobj = g_new0(RemminaConnectionObject, 1); cnnobj->remmina_file = remminafile; /* Create the RemminaProtocolWidget */ cnnobj->proto = remmina_protocol_widget_new(); remmina_protocol_widget_setup((RemminaProtocolWidget *)cnnobj->proto, remminafile, cnnobj); if (remmina_protocol_widget_has_error((RemminaProtocolWidget *)cnnobj->proto)) { GtkWindow *wparent; wparent = remmina_main_get_window(); msg = remmina_protocol_widget_get_error_message((RemminaProtocolWidget *)cnnobj->proto); dialog = gtk_message_dialog_new(wparent, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", msg); gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); /* We should destroy cnnobj->proto and cnnobj now… TODO: Fix this leak */ return NULL; } /* Set a name for the widget, for CSS selector */ gtk_widget_set_name(GTK_WIDGET(cnnobj->proto), "remmina-protocol-widget"); gtk_widget_set_halign(GTK_WIDGET(cnnobj->proto), GTK_ALIGN_FILL); gtk_widget_set_valign(GTK_WIDGET(cnnobj->proto), GTK_ALIGN_FILL); if (data) g_object_set_data(G_OBJECT(cnnobj->proto), "user-data", data); view_mode = remmina_file_get_int(cnnobj->remmina_file, "viewmode", 0); if (kioskmode) view_mode = VIEWPORT_FULLSCREEN_MODE; gint ismultimon = remmina_file_get_int(cnnobj->remmina_file, "multimon", 0); if (ismultimon) view_mode = VIEWPORT_FULLSCREEN_MODE; if (fullscreen) view_mode = VIEWPORT_FULLSCREEN_MODE; /* Create the viewport to make the RemminaProtocolWidget scrollable */ cnnobj->viewport = gtk_viewport_new(NULL, NULL); gtk_widget_set_name(cnnobj->viewport, "remmina-cw-viewport"); gtk_widget_show(cnnobj->viewport); gtk_container_set_border_width(GTK_CONTAINER(cnnobj->viewport), 0); gtk_viewport_set_shadow_type(GTK_VIEWPORT(cnnobj->viewport), GTK_SHADOW_NONE); /* Create the scrolled container */ scalemode = get_current_allowed_scale_mode(cnnobj, NULL, NULL); cnnobj->scrolled_container = rco_create_scrolled_container(scalemode, view_mode); gtk_container_add(GTK_CONTAINER(cnnobj->scrolled_container), cnnobj->viewport); /* Determine whether the plugin can scale or not. If the plugin can scale and we do * not want to expand, then we add a GtkAspectFrame to maintain aspect ratio during scaling */ cnnobj->plugin_can_scale = remmina_plugin_manager_query_feature_by_type(REMMINA_PLUGIN_TYPE_PROTOCOL, remmina_file_get_string(remminafile, "protocol"), REMMINA_PROTOCOL_FEATURE_TYPE_SCALE); cnnobj->aspectframe = NULL; gtk_container_add(GTK_CONTAINER(cnnobj->viewport), cnnobj->proto); /* Determine whether this connection will be put on a new window * or in an existing one */ cnnobj->cnnwin = rcw_find(remminafile); if (!cnnobj->cnnwin) { /* Connection goes on a new toplevel window */ switch (view_mode) { case SCROLLED_FULLSCREEN_MODE: case VIEWPORT_FULLSCREEN_MODE: cnnobj->cnnwin = rcw_create_fullscreen(NULL, view_mode); break; case SCROLLED_WINDOW_MODE: default: width = remmina_file_get_int(cnnobj->remmina_file, "window_width", 640); height = remmina_file_get_int(cnnobj->remmina_file, "window_height", 480); maximize = remmina_file_get_int(cnnobj->remmina_file, "window_maximize", FALSE) ? TRUE : FALSE; cnnobj->cnnwin = rcw_create_scrolled(width, height, maximize); break; } rcw_update_tag(cnnobj->cnnwin, cnnobj); rcw_append_new_page(cnnobj->cnnwin, cnnobj); } else { newpage = rcw_append_new_page(cnnobj->cnnwin, cnnobj); gtk_window_present(GTK_WINDOW(cnnobj->cnnwin)); nb_set_current_page(cnnobj->cnnwin->priv->notebook, newpage); } // Do not call remmina_protocol_widget_update_alignment(cnnobj); here or cnnobj->proto will not fill its parent size // and remmina_protocol_widget_update_remote_resolution() cannot autodetect available space gtk_widget_show(cnnobj->proto); g_signal_connect(G_OBJECT(cnnobj->proto), "connect", G_CALLBACK(rco_on_connect), cnnobj); g_signal_connect(G_OBJECT(cnnobj->proto), "disconnect", G_CALLBACK(rco_on_disconnect), NULL); g_signal_connect(G_OBJECT(cnnobj->proto), "desktop-resize", G_CALLBACK(rco_on_desktop_resize), NULL); g_signal_connect(G_OBJECT(cnnobj->proto), "update-align", G_CALLBACK(rco_on_update_align), NULL); g_signal_connect(G_OBJECT(cnnobj->proto), "lock-dynres", G_CALLBACK(rco_on_lock_dynres), NULL); g_signal_connect(G_OBJECT(cnnobj->proto), "unlock-dynres", G_CALLBACK(rco_on_unlock_dynres), NULL); g_signal_connect(G_OBJECT(cnnobj->proto), "enter-notify-event", G_CALLBACK(rco_enter_protocol_widget), cnnobj); g_signal_connect(G_OBJECT(cnnobj->proto), "leave-notify-event", G_CALLBACK(rco_leave_protocol_widget), cnnobj); if (!remmina_pref.save_view_mode) remmina_file_set_int(cnnobj->remmina_file, "viewmode", remmina_pref.default_mode); /* If it is a GtkSocket plugin and X11 is not available, we inform the * user and close the connection */ ret = remmina_plugin_manager_query_feature_by_type(REMMINA_PLUGIN_TYPE_PROTOCOL, remmina_file_get_string(remminafile, "protocol"), REMMINA_PROTOCOL_FEATURE_TYPE_GTKSOCKET); if (ret && !remmina_gtksocket_available()) { gchar *title = _("Warning: This plugin requires GtkSocket, but this " "feature is unavailable in a Wayland session."); gchar *err_msg = // TRANSLATORS: This should be a link to the Remmina wiki page: // 'GtkSocket feature is not available'. _("Plugins relying on GtkSocket can't run in a " "Wayland session.\nFor more info and a possible " "workaround, please visit the Remmina wiki at:\n\n" "https://gitlab.com/Remmina/Remmina/-/wikis/GtkSocket-feature-is-not-available-in-a-Wayland-session"); dialog = gtk_message_dialog_new( GTK_WINDOW(cnnobj->cnnwin), GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, "%s", title); gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "%s", err_msg); gtk_dialog_add_button(GTK_DIALOG(dialog), _("Open in web browser"), GTKSOCKET_NOT_AVAIL_RESPONSE_OPEN_BROWSER); REMMINA_CRITICAL(g_strdup_printf("%s\n%s", title, err_msg)); g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(rcw_gtksocket_not_available_dialog_response), cnnobj); // Make Text selectable. Usefull because of the link in the text. GtkWidget *area = gtk_message_dialog_get_message_area( GTK_MESSAGE_DIALOG(dialog)); GtkContainer *box = (GtkContainer *)area; GList *children = gtk_container_get_children(box); g_list_foreach(children, set_label_selectable, NULL); g_list_free(children); gtk_widget_show(dialog); return NULL; /* Should we destroy something before returning? */ } if (cnnobj->cnnwin->priv->floating_toolbar_widget) gtk_widget_show(cnnobj->cnnwin->priv->floating_toolbar_widget); if (remmina_protocol_widget_has_error((RemminaProtocolWidget *)cnnobj->proto)) { printf("OK, an error occurred in initializing the protocol plugin before connecting. The error is %s.\n" "TODO: Put this string as an error to show", remmina_protocol_widget_get_error_message((RemminaProtocolWidget *)cnnobj->proto)); return cnnobj->proto; } /* GTK window setup is done here, and we are almost ready to call remmina_protocol_widget_open_connection(). * But size has not yet been allocated by GTK * to the widgets. If we are in RES_USE_INITIAL_WINDOW_SIZE resolution mode or scale is REMMINA_PROTOCOL_WIDGET_SCALE_MODE_DYNRES, * we should wait for a size allocation from GTK for cnnobj->proto * before connecting */ cnnobj->deferred_open_size_allocate_handler = g_signal_connect(G_OBJECT(cnnobj->proto), "size-allocate", G_CALLBACK(rpw_size_allocated_on_connection), NULL); return cnnobj->proto; } GtkWindow *rcw_get_gtkwindow(RemminaConnectionObject *cnnobj) { return &cnnobj->cnnwin->window; } GtkWidget *rcw_get_gtkviewport(RemminaConnectionObject *cnnobj) { return cnnobj->viewport; } void rcw_set_delete_confirm_mode(RemminaConnectionWindow *cnnwin, RemminaConnectionWindowOnDeleteConfirmMode mode) { TRACE_CALL(__func__); cnnwin->priv->on_delete_confirm_mode = mode; } /** * Deletes a RemminaMessagePanel from the current cnnobj * and if it was visible, make visible the last remaining one. */ void rco_destroy_message_panel(RemminaConnectionObject *cnnobj, RemminaMessagePanel *mp) { TRACE_CALL(__func__); GList *childs, *cc; RemminaMessagePanel *lastPanel; gboolean was_visible; GtkWidget *page; page = nb_find_page_by_cnnobj(cnnobj->cnnwin->priv->notebook, cnnobj); childs = gtk_container_get_children(GTK_CONTAINER(page)); cc = g_list_first(childs); while (cc != NULL) { if ((RemminaMessagePanel *)cc->data == mp) break; cc = g_list_next(cc); } g_list_free(childs); if (cc == NULL) { printf("Remmina: Warning. There was a request to destroy a RemminaMessagePanel that is not on the page\n"); return; } was_visible = gtk_widget_is_visible(GTK_WIDGET(mp)); gtk_widget_destroy(GTK_WIDGET(mp)); /* And now, show the last remaining message panel, if needed */ if (was_visible) { childs = gtk_container_get_children(GTK_CONTAINER(page)); cc = g_list_first(childs); lastPanel = NULL; while (cc != NULL) { if (G_TYPE_CHECK_INSTANCE_TYPE(cc->data, REMMINA_TYPE_MESSAGE_PANEL)) lastPanel = (RemminaMessagePanel *)cc->data; cc = g_list_next(cc); } g_list_free(childs); if (lastPanel) gtk_widget_show(GTK_WIDGET(lastPanel)); } } /** * 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. */ void rco_show_message_panel(RemminaConnectionObject *cnnobj, RemminaMessagePanel *mp) { TRACE_CALL(__func__); GList *childs, *cc; GtkWidget *page; /* Hides all RemminaMessagePanels childs of cnnobj->page */ page = nb_find_page_by_cnnobj(cnnobj->cnnwin->priv->notebook, cnnobj); childs = gtk_container_get_children(GTK_CONTAINER(page)); cc = g_list_first(childs); while (cc != NULL) { if (G_TYPE_CHECK_INSTANCE_TYPE(cc->data, REMMINA_TYPE_MESSAGE_PANEL)) gtk_widget_hide(GTK_WIDGET(cc->data)); cc = g_list_next(cc); } g_list_free(childs); /* Add the new message panel at the top of cnnobj->page */ gtk_box_pack_start(GTK_BOX(page), GTK_WIDGET(mp), FALSE, FALSE, 0); gtk_box_reorder_child(GTK_BOX(page), GTK_WIDGET(mp), 0); /* Show the message panel */ gtk_widget_show_all(GTK_WIDGET(mp)); /* Focus the correct field of the RemminaMessagePanel */ remmina_message_panel_focus_auth_entry(mp); }