/* * 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-2022 Antenore Gatta, Giovanni Panozzo * Copyright (C) 2022-2023 Antenore Gatta, Giovanni Panozzo, Hiroyuki Tanaka * * 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" #include #include #ifndef __APPLE__ #include #endif #include #include #include #include "remmina.h" #include "remmina_string_array.h" #include "remmina_public.h" #include "remmina_file.h" #include "remmina_file_manager.h" #include "remmina_file_editor.h" #include "rcw.h" #include "remmina_about.h" #include "remmina_pref.h" #include "remmina_pref_dialog.h" #include "remmina_widget_pool.h" #include "remmina_plugin_manager.h" #include "remmina_log.h" #include "remmina_icon.h" #include "remmina_main.h" #include "remmina_exec.h" #include "remmina_mpchange.h" #include "remmina_external_tools.h" #include "remmina_unlock.h" #include "remmina/remmina_trace_calls.h" static RemminaMain *remminamain; #define RM_GET_OBJECT(object_name) gtk_builder_get_object(remminamain->builder, object_name) enum { PROTOCOL_COLUMN, NAME_COLUMN, GROUP_COLUMN, SERVER_COLUMN, PLUGIN_COLUMN, DATE_COLUMN, FILENAME_COLUMN, LABELS_COLUMN, NOTES_COLUMN, N_COLUMNS }; static const gchar *supported_mime_types[] = { "x-scheme-handler/rdp", "x-scheme-handler/spice", "x-scheme-handler/vnc", "x-scheme-handler/remmina", "application/x-remmina", NULL }; static GActionEntry app_actions[] = { { "about", remmina_main_on_action_application_about, NULL, NULL, NULL }, { "default", remmina_main_on_action_application_default, NULL, NULL, NULL }, { "mpchange", remmina_main_on_action_application_mpchange, NULL, NULL, NULL }, { "plugins", remmina_main_on_action_application_plugins, NULL, NULL, NULL }, { "preferences", remmina_main_on_action_application_preferences, "i", NULL, NULL }, { "dark", remmina_main_on_action_application_dark_theme, NULL, NULL, NULL }, { "debug", remmina_main_on_action_help_debug, NULL, NULL, NULL }, { "community", remmina_main_on_action_help_community, NULL, NULL, NULL }, { "donations", remmina_main_on_action_help_donations, NULL, NULL, NULL }, { "homepage", remmina_main_on_action_help_homepage, NULL, NULL, NULL }, { "wiki", remmina_main_on_action_help_wiki, NULL, NULL, NULL }, { "quit", remmina_main_on_action_application_quit, NULL, NULL, NULL }, }; static GActionEntry main_actions[] = { { "connect", remmina_main_on_action_connection_connect, NULL, NULL, NULL }, { "copy", remmina_main_on_action_connection_copy, NULL, NULL, NULL }, { "delete", remmina_main_on_action_connection_delete, NULL, NULL, NULL }, { "delete_multiple", remmina_main_on_action_connection_delete_multiple, NULL, NULL, NULL }, { "edit", remmina_main_on_action_connection_edit, NULL, NULL, NULL }, { "exttools", remmina_main_on_action_connection_external_tools, NULL, NULL, NULL }, { "new", remmina_main_on_action_connection_new, NULL, NULL, NULL }, { "export", remmina_main_on_action_tools_export, NULL, NULL, NULL }, { "import", remmina_main_on_action_tools_import, NULL, NULL, NULL }, { "expand", remmina_main_on_action_expand, NULL, NULL, NULL }, { "collapse", remmina_main_on_action_collapse, NULL, NULL, NULL }, { "search", remmina_main_on_action_search_toggle, NULL, NULL, NULL }, }; static GtkTargetEntry remmina_drop_types[] = { { "text/uri-list", 0, 1 } }; static char *quick_connect_plugin_list[] = { "RDP", "VNC", "SSH", "NX", "SPICE", "X2GO" }; /** * Save the Remmina Main Window size to assure the main geometry at each restart */ static void remmina_main_save_size(void) { TRACE_CALL(__func__); if ((gdk_window_get_state(gtk_widget_get_window(GTK_WIDGET(remminamain->window))) & GDK_WINDOW_STATE_MAXIMIZED) == 0) { gtk_window_get_size(remminamain->window, &remmina_pref.main_width, &remmina_pref.main_height); remmina_pref.main_maximize = FALSE; } else { remmina_pref.main_maximize = TRUE; } } static void remmina_main_save_expanded_group_func(GtkTreeView *tree_view, GtkTreePath *path, gpointer user_data) { TRACE_CALL(__func__); GtkTreeIter iter; gchar *group; gtk_tree_model_get_iter(remminamain->priv->file_model_sort, &iter, path); gtk_tree_model_get(remminamain->priv->file_model_sort, &iter, GROUP_COLUMN, &group, -1); if (group) { remmina_string_array_add(remminamain->priv->expanded_group, group); g_free(group); } } static void remmina_main_save_expanded_group(void) { TRACE_CALL(__func__); if (GTK_IS_TREE_STORE(remminamain->priv->file_model)) { if (remminamain->priv->expanded_group) remmina_string_array_free(remminamain->priv->expanded_group); remminamain->priv->expanded_group = remmina_string_array_new(); gtk_tree_view_map_expanded_rows(remminamain->tree_files_list, (GtkTreeViewMappingFunc)remmina_main_save_expanded_group_func, NULL); } } /** * Save the Remmina Main Window size and the expanded group before to close Remmina. * This function uses remmina_main_save_size and remmina_main_save_expanded_group. */ void remmina_main_save_before_destroy() { TRACE_CALL(__func__); if (!remminamain || !remminamain->window) return; remmina_main_save_size(); remmina_main_save_expanded_group(); g_free(remmina_pref.expanded_group); remmina_pref.expanded_group = remmina_string_array_to_string(remminamain->priv->expanded_group); remmina_pref_save(); } void remmina_main_destroy() { TRACE_CALL(__func__); if (remminamain) { if (remminamain->window) gtk_widget_destroy(GTK_WIDGET(remminamain->window)); g_object_unref(remminamain->builder); remmina_string_array_free(remminamain->priv->expanded_group); remminamain->priv->expanded_group = NULL; if (remminamain->priv->file_model) g_object_unref(G_OBJECT(remminamain->priv->file_model)); g_object_unref(G_OBJECT(remminamain->priv->file_model_filter)); g_free(remminamain->priv->selected_filename); g_free(remminamain->priv->selected_name); g_free(remminamain->priv); g_free(remminamain); remminamain = NULL; } } /** * Try to exit remmina after a delete window event */ static gboolean remmina_main_dexit(gpointer data) { TRACE_CALL(__func__); remmina_application_condexit(REMMINA_CONDEXIT_ONMAINWINDELETE); return FALSE; } gboolean remmina_main_on_delete_event(GtkWidget *widget, GdkEvent *event, gpointer user_data) { TRACE_CALL(__func__); remmina_main_save_before_destroy(); g_idle_add(remmina_main_dexit, NULL); return FALSE; } gboolean remmina_main_idle_destroy(gpointer data) { TRACE_CALL(__func__); if (remminamain) remmina_main_destroy(); return G_SOURCE_REMOVE; } /** * Called when the remminamain->window widget is destroyed (glade event handler) */ void remmina_main_on_destroy_event() { TRACE_CALL(__func__); if (remminamain) { /* Invalidate remminamain->window to avoid multiple destructions */ remminamain->window = NULL; /* Destroy remminamain struct, later. We can't destroy * important objects like the builder now */ g_idle_add(remmina_main_idle_destroy, NULL); } } static void remmina_main_clear_selection_data(void) { TRACE_CALL(__func__); g_free(remminamain->priv->selected_filename); g_free(remminamain->priv->selected_name); remminamain->priv->selected_filename = NULL; remminamain->priv->selected_name = NULL; } #ifdef SNAP_BUILD static void remmina_main_show_snap_welcome() { GtkBuilder *dlgbuilder = NULL; GtkWidget *dlg; GtkWindow *parent; int result; static gboolean shown_once = FALSE; gboolean need_snap_interface_connections = FALSE; GtkWidget *dsa; RemminaSecretPlugin *remmina_secret_plugin; if (shown_once) return; else shown_once = TRUE; g_print("Remmina is compiled as a SNAP package.\n"); remmina_secret_plugin = remmina_plugin_manager_get_secret_plugin(); if (remmina_secret_plugin == NULL) { g_print(" but we can’t find the secret plugin inside the SNAP.\n"); need_snap_interface_connections = TRUE; } else { if (!remmina_secret_plugin->is_service_available(remmina_secret_plugin)) { g_print(" but we can’t access a secret service. Secret service or SNAP interface connection is missing.\n"); need_snap_interface_connections = TRUE; } } if (need_snap_interface_connections && !remmina_pref.prevent_snap_welcome_message) { dlgbuilder = remmina_public_gtk_builder_new_from_resource("/org/remmina/Remmina/src/../data/ui/remmina_snap_info_dialog.glade"); dsa = GTK_WIDGET(gtk_builder_get_object(dlgbuilder, "dontshowagain")); if (dlgbuilder) { parent = remmina_main_get_window(); dlg = GTK_WIDGET(gtk_builder_get_object(dlgbuilder, "SnapInfoDlg")); if (parent) gtk_window_set_transient_for(GTK_WINDOW(dlg), parent); gtk_builder_connect_signals(dlgbuilder, NULL); result = gtk_dialog_run(GTK_DIALOG(dlg)); if (result == 1) { remmina_pref.prevent_snap_welcome_message = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dsa)); remmina_pref_save(); } gtk_widget_destroy(dlg); g_object_unref(dlgbuilder); } } } #endif static gboolean remmina_main_selection_func(GtkTreeSelection *selection, GtkTreeModel *model, GtkTreePath *path, gboolean path_currently_selected, gpointer user_data) { TRACE_CALL(__func__); guint context_id; GtkTreeIter iter; gchar buf[1000]; if (path_currently_selected) return TRUE; if (!gtk_tree_model_get_iter(model, &iter, path)) return TRUE; remmina_main_clear_selection_data(); gtk_tree_model_get(model, &iter, NAME_COLUMN, &remminamain->priv->selected_name, FILENAME_COLUMN, &remminamain->priv->selected_filename, -1); context_id = gtk_statusbar_get_context_id(remminamain->statusbar_main, "status"); gtk_statusbar_pop(remminamain->statusbar_main, context_id); if (remminamain->priv->selected_filename) { g_snprintf(buf, sizeof(buf), "%s (%s)", remminamain->priv->selected_name, remminamain->priv->selected_filename); gtk_statusbar_push(remminamain->statusbar_main, context_id, buf); } else { gtk_statusbar_push(remminamain->statusbar_main, context_id, remminamain->priv->selected_name); } return TRUE; } static void remmina_main_load_file_list_callback(RemminaFile *remminafile, gpointer user_data) { TRACE_CALL(__func__); GtkTreeIter iter; GtkListStore *store; store = GTK_LIST_STORE(user_data); gchar *datetime; datetime = remmina_file_get_datetime(remminafile); gtk_list_store_append(store, &iter); gtk_list_store_set(store, &iter, PROTOCOL_COLUMN, remmina_file_get_icon_name(remminafile), NAME_COLUMN, remmina_file_get_string(remminafile, "name"), NOTES_COLUMN, g_uri_unescape_string(remmina_file_get_string(remminafile, "notes_text"), NULL), GROUP_COLUMN, remmina_file_get_string(remminafile, "group"), SERVER_COLUMN, remmina_file_get_string(remminafile, "server"), PLUGIN_COLUMN, remmina_file_get_string(remminafile, "protocol"), DATE_COLUMN, datetime, FILENAME_COLUMN, remmina_file_get_filename(remminafile), LABELS_COLUMN, remmina_file_get_string(remminafile, "labels"), -1); g_free(datetime); } static gboolean remmina_main_load_file_tree_traverse(GNode *node, GtkTreeStore *store, GtkTreeIter *parent) { TRACE_CALL(__func__); GtkTreeIter *iter; RemminaGroupData *data; GNode *child; iter = NULL; if (node->data) { data = (RemminaGroupData *)node->data; iter = g_new0(GtkTreeIter, 1); gtk_tree_store_append(store, iter, parent); gtk_tree_store_set(store, iter, PROTOCOL_COLUMN, "folder-symbolic", NAME_COLUMN, data->name, GROUP_COLUMN, data->group, DATE_COLUMN, data->datetime, FILENAME_COLUMN, NULL, LABELS_COLUMN, data->labels, -1); } for (child = g_node_first_child(node); child; child = g_node_next_sibling(child)) remmina_main_load_file_tree_traverse(child, store, iter); g_free(iter); return FALSE; } static void remmina_main_load_file_tree_group(GtkTreeStore *store) { TRACE_CALL(__func__); GNode *root; root = remmina_file_manager_get_group_tree(); remmina_main_load_file_tree_traverse(root, store, NULL); remmina_file_manager_free_group_tree(root); } static void remmina_main_expand_group_traverse(GtkTreeIter *iter) { TRACE_CALL(__func__); GtkTreeModel *tree; gboolean ret; gchar *group, *filename; GtkTreeIter child; GtkTreePath *path; tree = remminamain->priv->file_model_sort; ret = TRUE; while (ret) { gtk_tree_model_get(tree, iter, GROUP_COLUMN, &group, FILENAME_COLUMN, &filename, -1); if (filename == NULL) { if (remmina_string_array_find(remminamain->priv->expanded_group, group) >= 0) { path = gtk_tree_model_get_path(tree, iter); gtk_tree_view_expand_row(remminamain->tree_files_list, path, FALSE); gtk_tree_path_free(path); } if (gtk_tree_model_iter_children(tree, &child, iter)) remmina_main_expand_group_traverse(&child); } g_free(group); g_free(filename); ret = gtk_tree_model_iter_next(tree, iter); } } static void remmina_main_expand_group(void) { TRACE_CALL(__func__); GtkTreeIter iter; if (gtk_tree_model_get_iter_first(remminamain->priv->file_model_sort, &iter)) remmina_main_expand_group_traverse(&iter); } static gboolean remmina_main_load_file_tree_find(GtkTreeModel *tree, GtkTreeIter *iter, const gchar *match_group) { TRACE_CALL(__func__); gboolean ret, match; gchar *group, *filename; GtkTreeIter child; match = FALSE; ret = TRUE; while (ret) { gtk_tree_model_get(tree, iter, GROUP_COLUMN, &group, FILENAME_COLUMN, &filename, -1); match = (filename == NULL && g_strcmp0(group, match_group) == 0); g_free(group); g_free(filename); if (match) break; if (gtk_tree_model_iter_children(tree, &child, iter)) { match = remmina_main_load_file_tree_find(tree, &child, match_group); if (match) { memcpy(iter, &child, sizeof(GtkTreeIter)); break; } } ret = gtk_tree_model_iter_next(tree, iter); } return match; } static void remmina_main_load_file_tree_callback(RemminaFile *remminafile, gpointer user_data) { TRACE_CALL(__func__); GtkTreeIter iter, child; GtkTreeStore *store; gboolean found; gchar *datetime = NULL; store = GTK_TREE_STORE(user_data); found = FALSE; if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter)) found = remmina_main_load_file_tree_find(GTK_TREE_MODEL(store), &iter, remmina_file_get_string(remminafile, "group")); datetime = remmina_file_get_datetime(remminafile); //REMMINA_DEBUG("The date is %s", datetime); gtk_tree_store_append(store, &child, (found ? &iter : NULL)); gtk_tree_store_set(store, &child, PROTOCOL_COLUMN, remmina_file_get_icon_name(remminafile), NAME_COLUMN, remmina_file_get_string(remminafile, "name"), NOTES_COLUMN, g_uri_unescape_string(remmina_file_get_string(remminafile, "notes_text"), NULL), GROUP_COLUMN, remmina_file_get_string(remminafile, "group"), SERVER_COLUMN, remmina_file_get_string(remminafile, "server"), PLUGIN_COLUMN, remmina_file_get_string(remminafile, "protocol"), DATE_COLUMN, datetime, FILENAME_COLUMN, remmina_file_get_filename(remminafile), LABELS_COLUMN, remmina_file_get_string(remminafile, "labels"), -1); g_free(datetime); } static void remmina_main_file_model_on_sort(GtkTreeSortable *sortable, gpointer user_data) { TRACE_CALL(__func__); gint columnid; GtkSortType order; gtk_tree_sortable_get_sort_column_id(sortable, &columnid, &order); remmina_pref.main_sort_column_id = columnid; remmina_pref.main_sort_order = order; remmina_pref_save(); } static gboolean remmina_main_filter_visible_func(GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data) { TRACE_CALL(__func__); gchar *text; gchar *protocol, *name, *labels, *group, *server, *plugin, *date, *s; gboolean result = TRUE; text = g_ascii_strdown(gtk_entry_get_text(remminamain->entry_quick_connect_server), -1); if (text && text[0]) { gtk_tree_model_get(model, iter, PROTOCOL_COLUMN, &protocol, NAME_COLUMN, &name, GROUP_COLUMN, &group, SERVER_COLUMN, &server, PLUGIN_COLUMN, &plugin, DATE_COLUMN, &date, LABELS_COLUMN, &labels, -1); if (g_strcmp0(protocol, "folder-symbolic") != 0) { s = g_ascii_strdown(name ? name : "", -1); g_free(name); name = s; s = g_ascii_strdown(group ? group : "", -1); g_free(group); group = s; s = g_ascii_strdown(server ? server : "", -1); g_free(server); server = s; s = g_ascii_strdown(plugin ? plugin : "", -1); g_free(plugin); plugin = s; s = g_ascii_strdown(date ? date : "", -1); g_free(date); date = s; result = (strstr(name, text) || strstr(group, text) || strstr(server, text) || strstr(plugin, text) || strstr(date, text)); // Filter by labels s = g_ascii_strdown(labels ? labels : "", -1); g_free(labels); labels = s; if (strlen(labels) > 0) { gboolean labels_result = TRUE; gchar **labels_array = g_strsplit(labels, ",", -1); gchar **text_array = g_strsplit(text, ",", -1); for (int t = 0; (NULL != text_array[t]); t++) { if (0 == strlen(text_array[t])) { continue; } gboolean text_result = FALSE; for (int l = 0; (NULL != labels_array[l]); l++) { if (0 == strlen(labels_array[l])) { continue; } text_result = (text_result || strstr(labels_array[l], text_array[t])); if (text_result) { break; } } labels_result = (labels_result && text_result); if (!labels_result) { break; } } result = (result || labels_result); g_strfreev(labels_array); g_strfreev(text_array); } } g_free(protocol); g_free(name); g_free(labels); g_free(group); g_free(server); g_free(plugin); g_free(date); } g_free(text); return result; } static void remmina_main_select_file(const gchar *filename) { TRACE_CALL(__func__); GtkTreeIter iter; GtkTreePath *path; gchar *item_filename; gboolean cmp; if (!gtk_tree_model_get_iter_first(remminamain->priv->file_model_sort, &iter)) return; while (TRUE) { gtk_tree_model_get(remminamain->priv->file_model_sort, &iter, FILENAME_COLUMN, &item_filename, -1); cmp = g_strcmp0(item_filename, filename); g_free(item_filename); if (cmp == 0) { gtk_tree_selection_select_iter(gtk_tree_view_get_selection(remminamain->tree_files_list), &iter); path = gtk_tree_model_get_path(remminamain->priv->file_model_sort, &iter); gtk_tree_view_scroll_to_cell(remminamain->tree_files_list, path, NULL, TRUE, 0.5, 0.0); gtk_tree_path_free(path); return; } if (!gtk_tree_model_iter_next(remminamain->priv->file_model_sort, &iter)) return; } } static void remmina_main_load_files() { TRACE_CALL(__func__); gint items_count; gchar buf[200]; guint context_id; gint view_file_mode; gboolean always_show_notes; char *save_selected_filename; GtkTreeModel *newmodel; const gchar *neticon; const gchar *connection_tooltip; save_selected_filename = g_strdup(remminamain->priv->selected_filename); remmina_main_save_expanded_group(); view_file_mode = remmina_pref.view_file_mode; if (remminamain->priv->override_view_file_mode_to_list) view_file_mode = REMMINA_VIEW_FILE_LIST; switch (remmina_pref.view_file_mode) { case REMMINA_VIEW_FILE_TREE: gtk_toggle_button_set_active(remminamain->view_toggle_button, FALSE); break; case REMMINA_VIEW_FILE_LIST: default: gtk_toggle_button_set_active(remminamain->view_toggle_button, TRUE); break; } switch (view_file_mode) { case REMMINA_VIEW_FILE_TREE: /* Create new GtkTreeStore model */ newmodel = GTK_TREE_MODEL(gtk_tree_store_new(9, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING)); /* Hide the Group column in the tree view mode */ gtk_tree_view_column_set_visible(remminamain->column_files_list_group, FALSE); /* Load groups first */ remmina_main_load_file_tree_group(GTK_TREE_STORE(newmodel)); /* Load files list */ items_count = remmina_file_manager_iterate((GFunc)remmina_main_load_file_tree_callback, (gpointer)newmodel); break; case REMMINA_VIEW_FILE_LIST: default: /* Create new GtkListStore model */ newmodel = GTK_TREE_MODEL(gtk_list_store_new(9, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING)); /* Show the Group column in the list view mode */ gtk_tree_view_column_set_visible(remminamain->column_files_list_group, TRUE); /* Load files list */ items_count = remmina_file_manager_iterate((GFunc)remmina_main_load_file_list_callback, (gpointer)newmodel); break; } /* Set note column visibility*/ always_show_notes = remmina_pref.always_show_notes; if (!always_show_notes){ gtk_tree_view_column_set_visible(remminamain->column_files_list_notes, FALSE); } /* Unset old model */ gtk_tree_view_set_model(remminamain->tree_files_list, NULL); /* Destroy the old model and save the new one */ remminamain->priv->file_model = newmodel; /* Create a sorted filtered model based on newmodel and apply it to the TreeView */ remminamain->priv->file_model_filter = gtk_tree_model_filter_new(remminamain->priv->file_model, NULL); gtk_tree_model_filter_set_visible_func(GTK_TREE_MODEL_FILTER(remminamain->priv->file_model_filter), (GtkTreeModelFilterVisibleFunc)remmina_main_filter_visible_func, NULL, NULL); remminamain->priv->file_model_sort = gtk_tree_model_sort_new_with_model(remminamain->priv->file_model_filter); gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(remminamain->priv->file_model_sort), remmina_pref.main_sort_column_id, remmina_pref.main_sort_order); gtk_tree_view_set_model(remminamain->tree_files_list, remminamain->priv->file_model_sort); g_signal_connect(G_OBJECT(remminamain->priv->file_model_sort), "sort-column-changed", G_CALLBACK(remmina_main_file_model_on_sort), NULL); remmina_main_expand_group(); /* Select the file previously selected */ if (save_selected_filename) { remmina_main_select_file(save_selected_filename); g_free(save_selected_filename); } gtk_tree_view_column_set_widget(remminamain->column_files_list_date, NULL); GtkWidget *label = gtk_tree_view_column_get_button(remminamain->column_files_list_date); gtk_widget_set_tooltip_text(GTK_WIDGET(label), _("The latest successful connection attempt, or a pre-computed date")); /* Show in the status bar the total number of connections found */ g_snprintf(buf, sizeof(buf), ngettext("Total %i item.", "Total %i items.", items_count), items_count); context_id = gtk_statusbar_get_context_id(remminamain->statusbar_main, "status"); gtk_statusbar_pop(remminamain->statusbar_main, context_id); gtk_statusbar_push(remminamain->statusbar_main, context_id, buf); remmina_network_monitor_status (remminamain->monitor); if (remminamain->monitor->connected){ neticon = g_strdup("network-transmit-receive-symbolic"); connection_tooltip = g_strdup(_("Network status: fully online")); } else { neticon = g_strdup("network-offline-symbolic"); connection_tooltip = g_strdup(_("Network status: offline")); } if (GTK_IS_WIDGET(remminamain->network_icon)) gtk_widget_destroy(remminamain->network_icon); GIcon *icon = g_themed_icon_new (neticon); remminamain->network_icon = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_BUTTON); gtk_widget_set_tooltip_text (remminamain->network_icon, connection_tooltip); g_object_unref (icon); gtk_box_pack_start (GTK_BOX(remminamain->statusbar_main), remminamain->network_icon, FALSE, FALSE, 0); gtk_widget_show (remminamain->network_icon); } void remmina_main_load_files_cb(GtkEntry *entry, char *string, gpointer user_data) { TRACE_CALL(__func__); remmina_main_load_files(); } void remmina_main_on_action_connection_connect(GSimpleAction *action, GVariant *param, gpointer data) { TRACE_CALL(__func__); RemminaFile *remminafile; if (!remminamain->priv->selected_filename) return; remminafile = remmina_file_load(remminamain->priv->selected_filename); if (remminafile == NULL) return; if (remmina_pref_get_boolean("use_primary_password") && remmina_pref_get_boolean("lock_connect") && remmina_unlock_new(remminamain->window) == 0) return; if (remmina_file_get_int (remminafile, "profile-lock", FALSE) == 1 && remmina_unlock_new(remminamain->window) == 0) return; remmina_file_touch(remminafile); rcw_open_from_filename(remminamain->priv->selected_filename); remmina_file_free(remminafile); } void remmina_main_on_action_connection_external_tools(GSimpleAction *action, GVariant *param, gpointer data) { TRACE_CALL(__func__); if (!remminamain->priv->selected_filename) return; remmina_external_tools_from_filename(remminamain, remminamain->priv->selected_filename); } static void remmina_main_file_editor_destroy(GtkWidget *widget, gpointer user_data) { TRACE_CALL(__func__); if (!remminamain) return; remmina_main_load_files(); } void remmina_main_on_action_application_mpchange(GSimpleAction *action, GVariant *param, gpointer data) { TRACE_CALL(__func__); RemminaFile *remminafile; const gchar *username; const gchar *domain; const gchar *group; const gchar *gatewayusername; const gchar *gatewaydomain; username = domain = group = gatewayusername = gatewaydomain = ""; remminafile = NULL; if (remmina_pref_get_boolean("use_primary_password") && remmina_pref_get_boolean("lock_edit") && remmina_unlock_new(remminamain->window) == 0) return; if (remminamain->priv->selected_filename) { remminafile = remmina_file_load(remminamain->priv->selected_filename); if (remminafile != NULL) { username = remmina_file_get_string(remminafile, "username"); domain = remmina_file_get_string(remminafile, "domain"); group = remmina_file_get_string(remminafile, "group"); gatewayusername = remmina_file_get_string(remminafile, "gateway_username"); gatewaydomain = remmina_file_get_string(remminafile, "gateway_domain"); } } remmina_mpchange_schedule(TRUE, group, domain, username, "", gatewayusername, gatewaydomain, ""); if (remminafile != NULL) remmina_file_free(remminafile); } void remmina_main_on_action_connection_new(GSimpleAction *action, GVariant *param, gpointer data) { TRACE_CALL(__func__); if (kioskmode && kioskmode == TRUE) return; GtkWidget *widget; if (remmina_pref_get_boolean("use_primary_password") && remmina_pref_get_boolean("lock_edit") && remmina_unlock_new(remminamain->window) == 0) return; widget = remmina_file_editor_new(); g_signal_connect(G_OBJECT(widget), "destroy", G_CALLBACK(remmina_main_file_editor_destroy), remminamain); gtk_window_set_transient_for(GTK_WINDOW(widget), remminamain->window); gtk_widget_show(widget); remmina_main_load_files(); } static gboolean remmina_main_search_key_event(GtkWidget *search_entry, GdkEventKey *event, gpointer user_data) { TRACE_CALL(__func__); if (event->keyval == GDK_KEY_Escape) { gtk_entry_set_text(remminamain->entry_quick_connect_server, ""); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(RM_GET_OBJECT("search_toggle")), FALSE); return TRUE; } return FALSE; } static gboolean remmina_main_tree_row_activated(GtkTreeView *tree, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data) { TRACE_CALL(__func__); if (gtk_tree_view_row_expanded(tree, path)) gtk_tree_view_collapse_row(tree, path); else gtk_tree_view_expand_row(tree, path, FALSE); return TRUE; } void remmina_main_on_view_toggle() { if (gtk_toggle_button_get_active(remminamain->view_toggle_button)) { if (remmina_pref.view_file_mode != REMMINA_VIEW_FILE_LIST) { remmina_pref.view_file_mode = REMMINA_VIEW_FILE_LIST; gtk_entry_set_text(remminamain->entry_quick_connect_server, ""); remmina_pref_save(); remmina_main_load_files(); } } else { if (remmina_pref.view_file_mode != REMMINA_VIEW_FILE_TREE) { remmina_pref.view_file_mode = REMMINA_VIEW_FILE_TREE; gtk_entry_set_text(remminamain->entry_quick_connect_server, ""); remmina_pref_save(); remmina_main_load_files(); } } } void remmina_main_on_action_connection_copy(GSimpleAction *action, GVariant *param, gpointer data) { TRACE_CALL(__func__); GtkWidget *widget; if (remmina_pref_get_boolean("use_primary_password") && remmina_unlock_new(remminamain->window) == 0) return; if (!remminamain->priv->selected_filename) return; RemminaFile *remminafile = remmina_file_load(remminamain->priv->selected_filename); if (((remmina_pref_get_boolean("lock_edit") && remmina_pref_get_boolean("use_primary_password")) || remmina_file_get_int (remminafile, "profile-lock", FALSE)) && remmina_unlock_new(remminamain->window) == 0) return; if (remminafile) { remmina_file_free(remminafile); remminafile = NULL; } widget = remmina_file_editor_new_copy(remminamain->priv->selected_filename); if (widget) { g_signal_connect(G_OBJECT(widget), "destroy", G_CALLBACK(remmina_main_file_editor_destroy), remminamain); gtk_window_set_transient_for(GTK_WINDOW(widget), remminamain->window); gtk_widget_show(widget); } /* Select the file previously selected */ if (remminamain->priv->selected_filename) remmina_main_select_file(remminamain->priv->selected_filename); } void remmina_main_on_action_connection_edit(GSimpleAction *action, GVariant *param, gpointer data) { TRACE_CALL(__func__); GtkWidget *widget; if (!remminamain->priv->selected_filename) return; RemminaFile *remminafile = remmina_file_load(remminamain->priv->selected_filename); if (remmina_pref_get_boolean("use_primary_password") && (remmina_pref_get_boolean("lock_edit") || remmina_file_get_int (remminafile, "profile-lock", FALSE)) && remmina_unlock_new(remminamain->window) == 0) return; if (remminafile) { remmina_file_free(remminafile); remminafile = NULL; } widget = remmina_file_editor_new_from_filename(remminamain->priv->selected_filename); if (widget) { gtk_window_set_transient_for(GTK_WINDOW(widget), remminamain->window); gtk_widget_show(widget); } /* Select the file previously selected */ if (remminamain->priv->selected_filename) remmina_main_select_file(remminamain->priv->selected_filename); } void remmina_main_on_action_connection_delete(GSimpleAction *action, GVariant *param, gpointer data) { TRACE_CALL(__func__); GtkWidget *dialog; if (!remminamain->priv->selected_filename) return; RemminaFile *remminafile = remmina_file_load(remminamain->priv->selected_filename); if (((remmina_pref_get_boolean("lock_edit") && remmina_pref_get_boolean("use_primary_password")) || remmina_file_get_int (remminafile, "profile-lock", FALSE)) && remmina_unlock_new(remminamain->window) == 0) return; if (remminafile) { remmina_file_free(remminafile); remminafile = NULL; } dialog = gtk_message_dialog_new(remminamain->window, GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, _("Are you sure you want to delete “%s”?"), remminamain->priv->selected_name); if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_YES) { gchar *delfilename = g_strdup(remminamain->priv->selected_filename); remmina_file_delete(delfilename); g_free(delfilename), delfilename = NULL; remmina_icon_populate_menu(); remmina_main_load_files(); } gtk_widget_destroy(dialog); remmina_main_clear_selection_data(); } void remmina_main_on_action_connection_delete_multiple(GSimpleAction *action, GVariant *param, gpointer data) { TRACE_CALL(__func__); GtkWidget *dialog; GtkTreeSelection *sel = gtk_tree_view_get_selection(remminamain->tree_files_list); GtkTreeModel *model = gtk_tree_view_get_model(remminamain->tree_files_list); GList *list = gtk_tree_selection_get_selected_rows(sel, &model); gchar *file_to_delete; dialog = gtk_message_dialog_new(remminamain->window, GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, _("Are you sure you want to delete the selected files?")); // Delete files if Yes is clicked if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_YES) { while (list) { GtkTreePath *path = list->data; GtkTreeIter iter; if (!gtk_tree_model_get_iter(model, &iter, path)) { GtkWidget *dialog_warning; dialog_warning = gtk_message_dialog_new(remminamain->window, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("Failed to delete files!")); gtk_dialog_run(GTK_DIALOG(dialog_warning)); gtk_widget_destroy(dialog_warning); gtk_widget_destroy(dialog); remmina_main_clear_selection_data(); return; } gtk_tree_model_get(model, &iter, FILENAME_COLUMN, &file_to_delete, -1); RemminaFile *remminafile = remmina_file_load(file_to_delete); if (((remmina_pref_get_boolean("lock_edit") && remmina_pref_get_boolean("use_primary_password")) || remmina_file_get_int (remminafile, "profile-lock", FALSE)) && remmina_unlock_new(remminamain->window) == 0) return; if (remminafile) { remmina_file_free(remminafile); remminafile = NULL; } gchar *delfilename = g_strdup(file_to_delete); remmina_file_delete(delfilename); g_free(delfilename), delfilename = NULL; remmina_icon_populate_menu(); remmina_main_load_files(); list = g_list_next(list); } } gtk_widget_destroy(dialog); remmina_main_clear_selection_data(); } void remmina_main_on_accel_application_preferences(GSimpleAction *action, GVariant *param, gpointer data) { TRACE_CALL(__func__); GVariant *v = g_variant_new("i", 0); remmina_main_on_action_application_preferences(NULL, v, NULL); } void remmina_main_reload_preferences() { GtkSettings *settings; settings = gtk_settings_get_default(); g_object_set(settings, "gtk-application-prefer-dark-theme", remmina_pref.dark_theme, NULL); if (remminamain) { if(remmina_pref.hide_searchbar){ gtk_toggle_button_set_active(remminamain->search_toggle, FALSE); } else{ gtk_toggle_button_set_active(remminamain->search_toggle, TRUE); } gtk_tree_view_column_set_visible(remminamain->column_files_list_notes, remmina_pref.always_show_notes); } } void remmina_main_on_action_application_preferences(GSimpleAction *action, GVariant *param, gpointer data) { TRACE_CALL(__func__); REMMINA_DEBUG("Opening the preferences"); gint32 tab_num; if (param) { REMMINA_DEBUG("Parameter passed to preferences of type %s", g_variant_get_type_string(param)); tab_num = g_variant_get_int32(param); REMMINA_DEBUG("We got a parameter for the preferences: %d", tab_num); } else { tab_num = 0; } if (remmina_pref_get_boolean("use_primary_password") && remmina_unlock_new(remminamain->window) == 0) return; GtkWidget *widget = remmina_pref_dialog_new(tab_num, remminamain->window); gtk_widget_show_all(widget); } void remmina_main_on_action_application_default(GSimpleAction *action, GVariant *param, gpointer data) { TRACE_CALL(__func__); #ifndef __APPLE__ g_autoptr(GError) error = NULL; GDesktopAppInfo *desktop_info; GAppInfo *info = NULL; g_autofree gchar *id = g_strconcat(REMMINA_APP_ID, ".desktop", NULL); int i; desktop_info = g_desktop_app_info_new(id); if (!desktop_info) return; info = G_APP_INFO(desktop_info); for (i = 0; supported_mime_types[i]; i++) { if (!g_app_info_set_as_default_for_type(info, supported_mime_types[i], &error)) g_warning("Failed to set '%s' as the default application for secondary content type '%s': %s", g_app_info_get_name(info), supported_mime_types[i], error->message); else g_debug("Set '%s' as the default application for '%s'", g_app_info_get_name(info), supported_mime_types[i]); } #endif } void remmina_main_on_action_application_quit(GSimpleAction *action, GVariant *param, gpointer data) { // Called by quit signal in remmina_main.glade TRACE_CALL(__func__); g_debug("Quit intercept"); remmina_application_condexit(REMMINA_CONDEXIT_ONQUIT); } void remmina_main_on_date_column_sort_clicked() { if (remmina_pref.view_file_mode != REMMINA_VIEW_FILE_LIST) { remmina_pref.view_file_mode = REMMINA_VIEW_FILE_LIST; gtk_entry_set_text(remminamain->entry_quick_connect_server, ""); remmina_pref_save(); remmina_main_load_files(); } } void remmina_main_toggle_password_view(GtkWidget *widget, gpointer data) { GtkWindow *mainwindow; gboolean visible = gtk_entry_get_visibility(GTK_ENTRY(widget)); mainwindow = remmina_main_get_window(); if (remmina_pref_get_boolean("use_primary_password") && remmina_pref_get_boolean("lock_view_passwords") && remmina_unlock_new(mainwindow) == 0) return; if (visible) { gtk_entry_set_visibility(GTK_ENTRY(widget), FALSE); gtk_entry_set_icon_from_icon_name(GTK_ENTRY(widget), GTK_ENTRY_ICON_SECONDARY, "org.remmina.Remmina-password-reveal-symbolic"); } else { gtk_entry_set_visibility(GTK_ENTRY(widget), TRUE); gtk_entry_set_icon_from_icon_name(GTK_ENTRY(widget), GTK_ENTRY_ICON_SECONDARY, "org.remmina.Remmina-password-conceal-symbolic"); } } static void remmina_main_import_file_list(GSList *files) { TRACE_CALL(__func__); GtkWidget *dlg; GSList *element; gchar *path; RemminaFilePlugin *plugin; GString *err; RemminaFile *remminafile = NULL; gboolean imported; err = g_string_new(NULL); imported = FALSE; for (element = files; element; element = element->next) { path = (gchar *)element->data; plugin = remmina_plugin_manager_get_import_file_handler(path); if (plugin && (remminafile = plugin->import_func(plugin, path)) != NULL && remmina_file_get_string(remminafile, "name")) { remmina_file_generate_filename(remminafile); remmina_file_save(remminafile); imported = TRUE; } else { g_string_append(err, path); g_string_append_c(err, '\n'); } if (remminafile) { remmina_file_free(remminafile); remminafile = NULL; } g_free(path); } g_slist_free(files); if (err->len > 0) { // TRANSLATORS: The placeholder %s is an error message dlg = gtk_message_dialog_new(remminamain->window, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Unable to import:\n%s"), err->str); g_signal_connect(G_OBJECT(dlg), "response", G_CALLBACK(gtk_widget_destroy), NULL); gtk_widget_show(dlg); } g_string_free(err, TRUE); if (imported) remmina_main_load_files(); } static void remmina_main_action_tools_import_on_response(GtkNativeDialog *dialog, gint response_id, gpointer user_data) { TRACE_CALL(__func__); GSList *files; if (response_id == GTK_RESPONSE_ACCEPT) { files = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog)); remmina_main_import_file_list(files); } gtk_native_dialog_destroy(dialog); } static void remmina_set_file_chooser_filters(GtkFileChooser *chooser) { GtkFileFilter *filter; g_return_if_fail(GTK_IS_FILE_CHOOSER(chooser)); filter = gtk_file_filter_new(); gtk_file_filter_set_name(filter, _("RDP Files")); gtk_file_filter_add_pattern(filter, "*.rdp"); gtk_file_filter_add_pattern(filter, "*.rdpx"); gtk_file_filter_add_pattern(filter, "*.RDP"); gtk_file_filter_add_pattern(filter, "*.RDPX"); gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(chooser), filter); gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(chooser), filter); filter = gtk_file_filter_new(); gtk_file_filter_set_name(filter, _("All Files")); gtk_file_filter_add_pattern(filter, "*"); gtk_file_chooser_add_filter(chooser, filter); } void remmina_main_on_action_tools_import(GSimpleAction *action, GVariant *param, gpointer data) { TRACE_CALL(__func__); GtkFileChooserNative *chooser; chooser = gtk_file_chooser_native_new(_("Import"), remminamain->window, GTK_FILE_CHOOSER_ACTION_OPEN, _("Import"), _("_Cancel")); gtk_native_dialog_set_modal(GTK_NATIVE_DIALOG(chooser), TRUE); remmina_set_file_chooser_filters(GTK_FILE_CHOOSER(chooser)); gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(chooser), TRUE); g_signal_connect(chooser, "response", G_CALLBACK(remmina_main_action_tools_import_on_response), NULL); gtk_native_dialog_show(GTK_NATIVE_DIALOG(chooser)); } static void on_export_save_response (GtkFileChooserNative *dialog, int response, RemminaFile *remminafile) { if (response == GTK_RESPONSE_ACCEPT) { RemminaFilePlugin *plugin = remmina_plugin_manager_get_export_file_handler(remminafile); if (plugin){ gchar *path = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); plugin->export_func(plugin, remminafile, path); g_free(path); } } remmina_file_free(remminafile); gtk_native_dialog_destroy(GTK_NATIVE_DIALOG(dialog)); } void remmina_main_on_action_tools_export(GSimpleAction *action, GVariant *param, gpointer data) { TRACE_CALL(__func__); RemminaFilePlugin *plugin; RemminaFile *remminafile; GtkWidget *dialog; GtkFileChooserNative *chooser; gchar *export_name; if (!remminamain->priv->selected_filename) { dialog = gtk_message_dialog_new(remminamain->window, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Select the connection profile.")); g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(gtk_widget_destroy), NULL); gtk_widget_show(dialog); return; } remminafile = remmina_file_load(remminamain->priv->selected_filename); if (remminafile == NULL) { dialog = gtk_message_dialog_new(remminamain->window, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Remmina couldn't export.")); g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(gtk_widget_destroy), NULL); gtk_widget_show(dialog); return; } plugin = remmina_plugin_manager_get_export_file_handler(remminafile); if (plugin) { chooser = gtk_file_chooser_native_new(plugin->export_hints, remminamain->window, GTK_FILE_CHOOSER_ACTION_SAVE, _("_Save"), _("_Cancel")); gtk_native_dialog_set_modal(GTK_NATIVE_DIALOG(chooser), TRUE); remmina_set_file_chooser_filters(GTK_FILE_CHOOSER(chooser)); gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(chooser), TRUE); export_name = g_strdup_printf("%s.rdp", remminamain->priv->selected_name); gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(chooser), export_name); g_free(export_name); g_signal_connect(chooser, "response", G_CALLBACK(on_export_save_response), remminafile); gtk_native_dialog_show(GTK_NATIVE_DIALOG(chooser)); } else { remmina_file_free(remminafile); dialog = gtk_message_dialog_new(remminamain->window, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("This protocol does not support exporting.")); g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(gtk_widget_destroy), NULL); gtk_widget_show(dialog); return; } } void remmina_main_on_action_application_plugins(GSimpleAction *action, GVariant *param, gpointer data) { TRACE_CALL(__func__); remmina_plugin_manager_show(remminamain->window); } void remmina_main_on_action_application_dark_theme(GSimpleAction *action, GVariant *param, gpointer data) { TRACE_CALL(__func__); GtkSettings *settings; settings = gtk_settings_get_default(); if (gtk_switch_get_active(remminamain->switch_dark_mode)) remmina_pref.dark_theme = 1; else remmina_pref.dark_theme = 0; remmina_pref_save(); g_object_set(settings, "gtk-application-prefer-dark-theme", remmina_pref.dark_theme, NULL); } void remmina_main_on_action_help_homepage(GSimpleAction *action, GVariant *param, gpointer data) { TRACE_CALL(__func__); g_app_info_launch_default_for_uri("https://www.remmina.org", NULL, NULL); } void remmina_main_on_action_help_wiki(GSimpleAction *action, GVariant *param, gpointer data) { TRACE_CALL(__func__); g_app_info_launch_default_for_uri("https://gitlab.com/Remmina/Remmina/wikis/home", NULL, NULL); } void remmina_main_on_action_help_community(GSimpleAction *action, GVariant *param, gpointer data) { TRACE_CALL(__func__); g_app_info_launch_default_for_uri("https://remmina.org/community", NULL, NULL); } void remmina_main_on_action_help_donations(GSimpleAction *action, GVariant *param, gpointer data) { TRACE_CALL(__func__); g_app_info_launch_default_for_uri("https://www.remmina.org/donations", NULL, NULL); } void remmina_main_on_action_help_debug(GSimpleAction *action, GVariant *param, gpointer data) { TRACE_CALL(__func__); remmina_log_start(); } void remmina_main_on_action_application_about(GSimpleAction *action, GVariant *param, gpointer data) { TRACE_CALL(__func__); remmina_about_open(remminamain->window); }; static gboolean is_empty(const gchar *s) { if (s == NULL) return TRUE; while (*s != 0) { if (!isspace((unsigned char)*s)) return FALSE; s++; } return TRUE; } static gboolean remmina_main_quickconnect(void) { TRACE_CALL(__func__); RemminaFile *remminafile; gchar *server; gchar *server_trimmed; gchar *qcp; /* Save quick connect protocol if different from the previous one */ qcp = gtk_combo_box_text_get_active_text(remminamain->combo_quick_connect_protocol); if (qcp && strcmp(qcp, remmina_pref.last_quickconnect_protocol) != 0) { g_free(remmina_pref.last_quickconnect_protocol); remmina_pref.last_quickconnect_protocol = g_strdup(qcp); remmina_pref_save(); } remminafile = remmina_file_new(); server = g_strdup(gtk_entry_get_text(remminamain->entry_quick_connect_server)); if (g_hostname_to_ascii(server) == NULL) return FALSE; /* If server contain /, e.g. vnc://, it won't connect * We could search for an array of invalid characters, but * it's better to find a way to correctly parse and validate addresses */ if (g_strrstr(server, "/") != NULL) return FALSE; //if (g_str_has_suffix (server, "/")) //return FALSE; if (is_empty(server)) return FALSE; /* check if server is an IP address and trim whitespace if so */ server_trimmed = g_strdup(server); g_strstrip(server_trimmed); gchar **strings = g_strsplit(server_trimmed, ":", 2); if (strings[0] != NULL) if (g_hostname_is_ip_address(strings[0])) g_stpcpy(server, server_trimmed); remmina_file_set_string(remminafile, "sound", "off"); remmina_file_set_string(remminafile, "server", server); remmina_file_set_string(remminafile, "name", server); remmina_file_set_string(remminafile, "protocol", qcp); g_free(server); g_free(server_trimmed); g_free(qcp); rcw_open_from_file(remminafile); return FALSE; } gboolean remmina_main_quickconnect_on_click(GtkWidget *widget, gpointer user_data) { TRACE_CALL(__func__); if (!kioskmode && kioskmode == FALSE) return remmina_main_quickconnect(); return FALSE; } /* Select all the text inside the quick search box if there is anything */ void remmina_main_quick_search_enter(GtkWidget *widget, gpointer user_data) { if (gtk_entry_get_text(remminamain->entry_quick_connect_server)) gtk_editable_select_region(GTK_EDITABLE(remminamain->entry_quick_connect_server), 0, -1); } void remmina_main_on_action_collapse(GSimpleAction *action, GVariant *param, gpointer data) { TRACE_CALL(__func__); gtk_tree_view_collapse_all(remminamain->tree_files_list); } void remmina_main_on_action_search_toggle(GSimpleAction *action, GVariant *param, gpointer data) { TRACE_CALL(__func__); REMMINA_DEBUG("Search toggle triggered"); gboolean toggle_status = gtk_toggle_button_get_active(remminamain->search_toggle); gtk_search_bar_set_search_mode(remminamain->search_bar, toggle_status); if (toggle_status) { REMMINA_DEBUG("Search toggle is active"); gtk_widget_grab_focus(GTK_WIDGET(remminamain->entry_quick_connect_server)); } else { REMMINA_DEBUG("Search toggle is not active, focus is tree_files_list"); gtk_widget_grab_focus(GTK_WIDGET(remminamain->tree_files_list)); } } void remmina_main_on_accel_search_toggle(RemminaMain *remminamain) { TRACE_CALL(__func__); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(remminamain->search_toggle), TRUE); } void remmina_main_on_action_expand(GSimpleAction *action, GVariant *param, gpointer data) { TRACE_CALL(__func__); gtk_tree_view_expand_all(remminamain->tree_files_list); } /* Handle double click on a row in the connections list */ void remmina_main_file_list_on_row_activated(GtkTreeView *tree, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data) { TRACE_CALL(__func__); /* If a connection was selected then execute the default action */ if (remminamain->priv->selected_filename) { switch (remmina_pref.default_action) { case REMMINA_ACTION_EDIT: remmina_main_on_action_connection_edit(NULL, NULL, NULL); break; case REMMINA_ACTION_CONNECT: default: remmina_main_on_action_connection_connect(NULL, NULL, NULL); break; } } } /* Show the popup menu by the right button mouse click */ gboolean remmina_main_file_list_on_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data) { TRACE_CALL(__func__); if (event->button == MOUSE_BUTTON_RIGHT) { if (!kioskmode && kioskmode == FALSE) { #if GTK_CHECK_VERSION(3, 22, 0) // For now, if more than one selected row, display only a delete menu option if (gtk_tree_selection_count_selected_rows(gtk_tree_view_get_selection(remminamain->tree_files_list)) > 1) { gtk_menu_popup_at_pointer(GTK_MENU(remminamain->menu_popup_delete_rc), (GdkEvent *)event); return GDK_EVENT_STOP; } else { gtk_menu_popup_at_pointer(GTK_MENU(remminamain->menu_popup), (GdkEvent *)event); } #else gtk_menu_popup(remminamain->menu_popup, NULL, NULL, NULL, NULL, event->button, event->time); #endif } } return FALSE; } /* Show the popup menu by the menu key */ gboolean remmina_main_file_list_on_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data) { TRACE_CALL(__func__); if (event->keyval == GDK_KEY_Menu) { #if GTK_CHECK_VERSION(3, 22, 0) gtk_menu_popup_at_widget(GTK_MENU(remminamain->menu_popup), widget, GDK_GRAVITY_CENTER, GDK_GRAVITY_CENTER, (GdkEvent *)event); #else gtk_menu_popup(remminamain->menu_popup, NULL, NULL, NULL, NULL, 0, event->time); #endif } return FALSE; } void remmina_main_quick_search_on_icon_press(GtkEntry *entry, GtkEntryIconPosition icon_pos, GdkEvent *event, gpointer user_data) { TRACE_CALL(__func__); if (icon_pos == GTK_ENTRY_ICON_SECONDARY) gtk_entry_set_text(entry, ""); } void remmina_main_quick_search_on_changed(GtkEditable *editable, gpointer user_data) { TRACE_CALL(__func__); /* If a search text was input then temporary set the file mode to list */ if (gtk_entry_get_text_length(remminamain->entry_quick_connect_server)) { if (GTK_IS_TREE_STORE(remminamain->priv->file_model)) { /* File view mode changed, put it to override and reload list */ remminamain->priv->override_view_file_mode_to_list = TRUE; remmina_main_load_files(); } } else { if (remminamain->priv->override_view_file_mode_to_list) { /* File view mode changed, put it to default (disable override) and reload list */ remminamain->priv->override_view_file_mode_to_list = FALSE; remmina_main_load_files(); } } gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER(remminamain->priv->file_model_filter)); } void remmina_main_on_drag_data_received(GtkWidget *widget, GdkDragContext *drag_context, gint x, gint y, GtkSelectionData *data, guint info, guint time, gpointer user_data) { TRACE_CALL(__func__); gchar **uris; GSList *files = NULL; gint i; uris = g_uri_list_extract_uris((const gchar *)gtk_selection_data_get_data(data)); for (i = 0; uris[i]; i++) { if (strncmp(uris[i], "file://", 7) != 0) continue; files = g_slist_append(files, g_strdup(uris[i] + 7)); } g_strfreev(uris); remmina_main_import_file_list(files); } /* Add a new menuitem to the Tools menu */ static gboolean remmina_main_add_tool_plugin(gchar *name, RemminaPlugin *plugin, gpointer user_data) { TRACE_CALL(__func__); RemminaToolPlugin *tool_plugin = (RemminaToolPlugin *)plugin; GtkWidget *menuitem = gtk_menu_item_new_with_label(plugin->description); gtk_widget_show(menuitem); gtk_menu_shell_append(GTK_MENU_SHELL(remminamain->menu_popup_full), menuitem); g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(tool_plugin->exec_func), tool_plugin); return FALSE; } gboolean remmina_main_on_window_state_event(GtkWidget *widget, GdkEventWindowState *event, gpointer user_data) { TRACE_CALL(__func__); return FALSE; } /* Remmina main window initialization */ static void remmina_main_init(void) { TRACE_CALL(__func__); int i, qcp_idx, qcp_actidx; char *name; GtkSettings *settings; REMMINA_DEBUG("Initializing the Remmina main window"); /* Switch to a dark theme if the user enabled it */ settings = gtk_settings_get_default(); g_object_set(settings, "gtk-application-prefer-dark-theme", remmina_pref.dark_theme, NULL); REMMINA_DEBUG ("Initializing monitor"); remminamain->monitor = remmina_network_monitor_new(); remminamain->priv->expanded_group = remmina_string_array_new_from_string(remmina_pref.expanded_group); if (!kioskmode && kioskmode == FALSE) gtk_window_set_title(remminamain->window, _("Remmina Remote Desktop Client")); else gtk_window_set_title(remminamain->window, _("Remmina Kiosk")); if (!kioskmode && kioskmode == FALSE) { gtk_window_set_default_size(remminamain->window, remmina_pref.main_width, remmina_pref.main_height); if (remmina_pref.main_maximize) gtk_window_maximize(remminamain->window); } /* Honor global preferences Search Bar visibility */ if (remmina_pref.hide_searchbar) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(RM_GET_OBJECT("search_toggle")), FALSE); /* Add a GtkMenuItem to the Tools menu for each plugin of type REMMINA_PLUGIN_TYPE_TOOL */ remmina_plugin_manager_for_each_plugin(REMMINA_PLUGIN_TYPE_TOOL, remmina_main_add_tool_plugin, remminamain); /* Add available quick connect protocols to remminamain->combo_quick_connect_protocol */ qcp_idx = qcp_actidx = 0; for (i = 0; i < sizeof(quick_connect_plugin_list) / sizeof(quick_connect_plugin_list[0]); i++) { name = quick_connect_plugin_list[i]; if (remmina_plugin_manager_get_plugin(REMMINA_PLUGIN_TYPE_PROTOCOL, name)) { gtk_combo_box_text_append(remminamain->combo_quick_connect_protocol, name, name); if (remmina_pref.last_quickconnect_protocol != NULL && strcmp(name, remmina_pref.last_quickconnect_protocol) == 0) qcp_actidx = qcp_idx; qcp_idx++; } } gtk_combo_box_set_active(GTK_COMBO_BOX(remminamain->combo_quick_connect_protocol), qcp_actidx); /* Connect the group accelerators to the GtkWindow */ //gtk_window_add_accel_group(remminamain->window, remminamain->accelgroup_shortcuts); /* Set the Quick Connection */ gtk_entry_set_activates_default(remminamain->entry_quick_connect_server, TRUE); /* Set the TreeView for the files list */ gtk_tree_selection_set_select_function( gtk_tree_view_get_selection(remminamain->tree_files_list), remmina_main_selection_func, NULL, NULL); /** @todo Set entry_quick_connect_server as default search entry. Weirdly. This does not work yet. */ gtk_tree_view_set_search_entry(remminamain->tree_files_list, GTK_ENTRY(remminamain->entry_quick_connect_server)); if (remmina_pref.hide_searchbar) gtk_widget_grab_focus(GTK_WIDGET(remminamain->tree_files_list)); /* Load the files list */ remmina_main_load_files(); /* Drag-n-drop support */ gtk_drag_dest_set(GTK_WIDGET(remminamain->window), GTK_DEST_DEFAULT_ALL, remmina_drop_types, 1, GDK_ACTION_COPY); /* Finish initialization */ remminamain->priv->initialized = TRUE; /* Register the window in remmina_widget_pool with GType=GTK_WINDOW and TAG=remmina-main-window */ g_object_set_data(G_OBJECT(remminamain->window), "tag", "remmina-main-window"); remmina_widget_pool_register(GTK_WIDGET(remminamain->window)); } /* Signal handler for "show" on remminamain->window */ void remmina_main_on_show(GtkWidget *w, gpointer user_data) { TRACE_CALL(__func__); #ifdef SNAP_BUILD remmina_main_show_snap_welcome(); #endif } /* RemminaMain instance */ GtkWidget *remmina_main_new(void) { TRACE_CALL(__func__); GSimpleActionGroup *actions; GtkAccelGroup *accel_group = NULL; remminamain = g_new0(RemminaMain, 1); remminamain->priv = g_new0(RemminaMainPriv, 1); /* Assign UI widgets to the private members */ remminamain->builder = remmina_public_gtk_builder_new_from_resource("/org/remmina/Remmina/src/../data/ui/remmina_main.glade"); remminamain->window = GTK_WINDOW(RM_GET_OBJECT("RemminaMain")); if (kioskmode && kioskmode == TRUE) { gtk_window_set_position(remminamain->window, GTK_WIN_POS_CENTER_ALWAYS); gtk_window_set_default_size(remminamain->window, 800, 400); gtk_window_set_resizable(remminamain->window, FALSE); } /* New Button */ remminamain->button_new = GTK_BUTTON(RM_GET_OBJECT("button_new")); if (kioskmode && kioskmode == TRUE) gtk_widget_set_sensitive(GTK_WIDGET(remminamain->button_new), FALSE); /* Search bar */ remminamain->search_toggle = GTK_TOGGLE_BUTTON(RM_GET_OBJECT("search_toggle")); remminamain->search_bar = GTK_SEARCH_BAR(RM_GET_OBJECT("search_bar")); /* view mode list/tree */ remminamain->view_toggle_button = GTK_TOGGLE_BUTTON(RM_GET_OBJECT("view_toggle_button")); if (kioskmode && kioskmode == TRUE) gtk_widget_set_sensitive(GTK_WIDGET(remminamain->view_toggle_button), FALSE); /* Menu widgets */ remminamain->menu_popup = GTK_MENU(RM_GET_OBJECT("menu_popup")); remminamain->menu_header_button = GTK_MENU_BUTTON(RM_GET_OBJECT("menu_header_button")); remminamain->menu_popup_full = GTK_MENU(RM_GET_OBJECT("menu_popup_full")); remminamain->menu_popup_delete_rc = GTK_MENU(RM_GET_OBJECT("menu_popup_delete_rc")); if (kioskmode && kioskmode == TRUE) { gtk_widget_set_sensitive(GTK_WIDGET(remminamain->menu_popup_full), FALSE); gtk_widget_set_sensitive(GTK_WIDGET(remminamain->menu_header_button), FALSE); } /* View mode radios */ remminamain->menuitem_view_mode_list = GTK_RADIO_MENU_ITEM(RM_GET_OBJECT("menuitem_view_mode_list")); remminamain->menuitem_view_mode_tree = GTK_RADIO_MENU_ITEM(RM_GET_OBJECT("menuitem_view_mode_tree")); /* Quick connect objects */ remminamain->box_quick_connect = GTK_BOX(RM_GET_OBJECT("box_quick_connect")); remminamain->combo_quick_connect_protocol = GTK_COMBO_BOX_TEXT(RM_GET_OBJECT("combo_quick_connect_protocol")); if (kioskmode && kioskmode == TRUE) gtk_widget_set_sensitive(GTK_WIDGET(remminamain->combo_quick_connect_protocol), FALSE); remminamain->entry_quick_connect_server = GTK_ENTRY(RM_GET_OBJECT("entry_quick_connect_server")); /* Other widgets */ remminamain->tree_files_list = GTK_TREE_VIEW(RM_GET_OBJECT("tree_files_list")); remminamain->column_files_list_name = GTK_TREE_VIEW_COLUMN(RM_GET_OBJECT("column_files_list_name")); remminamain->column_files_list_group = GTK_TREE_VIEW_COLUMN(RM_GET_OBJECT("column_files_list_group")); remminamain->column_files_list_server = GTK_TREE_VIEW_COLUMN(RM_GET_OBJECT("column_files_list_server")); remminamain->column_files_list_plugin = GTK_TREE_VIEW_COLUMN(RM_GET_OBJECT("column_files_list_plugin")); remminamain->column_files_list_date = GTK_TREE_VIEW_COLUMN(RM_GET_OBJECT("column_files_list_date")); remminamain->column_files_list_notes = GTK_TREE_VIEW_COLUMN(RM_GET_OBJECT("column_files_list_notes")); gtk_tree_view_column_set_fixed_width(remminamain->column_files_list_notes, 100); remminamain->statusbar_main = GTK_STATUSBAR(RM_GET_OBJECT("statusbar_main")); /* signals */ g_signal_connect(remminamain->entry_quick_connect_server, "key-release-event", G_CALLBACK(remmina_main_search_key_event), NULL); g_signal_connect(remminamain->tree_files_list, "row-activated", G_CALLBACK(remmina_main_tree_row_activated), NULL); /* Non widget objects */ actions = g_simple_action_group_new(); g_action_map_add_action_entries(G_ACTION_MAP(actions), app_actions, G_N_ELEMENTS(app_actions), remminamain->window); gtk_widget_insert_action_group(GTK_WIDGET(remminamain->window), "app", G_ACTION_GROUP(actions)); g_action_map_add_action_entries(G_ACTION_MAP(actions), main_actions, G_N_ELEMENTS(main_actions), remminamain->window); gtk_widget_insert_action_group(GTK_WIDGET(remminamain->window), "main", G_ACTION_GROUP(actions)); g_object_unref(actions); /* Accelerators */ accel_group = gtk_accel_group_new(); gtk_window_add_accel_group(remminamain->window, accel_group); gtk_accel_group_connect(accel_group, GDK_KEY_Q, GDK_CONTROL_MASK, 0, g_cclosure_new_swap(G_CALLBACK(remmina_main_on_action_application_quit), NULL, NULL)); // TODO: This crash remmina because the function doesn't receive the parameter we expect gtk_accel_group_connect(accel_group, GDK_KEY_P, GDK_CONTROL_MASK, 0, g_cclosure_new_swap(G_CALLBACK(remmina_main_on_accel_application_preferences), NULL, NULL)); gtk_accel_group_connect(accel_group, GDK_KEY_F, GDK_CONTROL_MASK, 0, g_cclosure_new_swap(G_CALLBACK(remmina_main_on_accel_search_toggle), remminamain, NULL)); /* Connect signals */ gtk_builder_connect_signals(remminamain->builder, NULL); /* Initialize the window and load the preferences */ remmina_main_init(); return GTK_WIDGET(remminamain->window); } GtkWindow *remmina_main_get_window() { if (!remminamain) return NULL; if (!remminamain->priv) return NULL; if (!remminamain->priv->initialized) return NULL; remminamain->window = GTK_WINDOW(RM_GET_OBJECT("RemminaMain")); return remminamain->window; } void remmina_main_update_file_datetime(RemminaFile *file) { if (!remminamain) return; remmina_main_load_files(); } void remmina_main_show_dialog(GtkMessageType msg, GtkButtonsType buttons, const gchar* message) { GtkWidget *dialog; if (remminamain->window) { dialog = gtk_message_dialog_new(remminamain->window, GTK_DIALOG_MODAL, msg, buttons, "%s", message); gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); } } void remmina_main_show_warning_dialog(const gchar *message) { GtkWidget *dialog; if (remminamain->window) { dialog = gtk_message_dialog_new(remminamain->window, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE, message, g_get_application_name()); gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); } }