Remmina - The GTK+ Remote Desktop Client  v1.4.33
Remmina is a remote desktop client written in GTK+, aiming to be useful for system administrators and travellers, who need to work with lots of remote computers in front of either large monitors or tiny netbooks. Remmina supports multiple network protocols in an integrated and consistent user interface. Currently RDP, VNC, NX, XDMCP and SSH are supported.
remmina_main.c
Go to the documentation of this file.
1 /*
2  * Remmina - The GTK+ Remote Desktop Client
3  * Copyright (C) 2009-2011 Vic Lee
4  * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
5  * Copyright (C) 2016-2022 Antenore Gatta, Giovanni Panozzo
6  * Copyright (C) 2022-2023 Antenore Gatta, Giovanni Panozzo, Hiroyuki Tanaka
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  *
23  * In addition, as a special exception, the copyright holders give
24  * permission to link the code of portions of this program with the
25  * OpenSSL library under certain conditions as described in each
26  * individual source file, and distribute linked combinations
27  * including the two.
28  * You must obey the GNU General Public License in all respects
29  * for all of the code used other than OpenSSL. * If you modify
30  * file(s) with this exception, you may extend this exception to your
31  * version of the file(s), but you are not obligated to do so. * If you
32  * do not wish to do so, delete this exception statement from your
33  * version. * If you delete this exception statement from all source
34  * files in the program, then also delete it here.
35  *
36  */
37 
38 #include "config.h"
39 #include <ctype.h>
40 #include <gio/gio.h>
41 #ifndef __APPLE__
42 #include <gio/gdesktopappinfo.h>
43 #endif
44 #include <gdk/gdkkeysyms.h>
45 #include <glib/gi18n.h>
46 #include <gtk/gtk.h>
47 
48 #include "remmina.h"
49 #include "remmina_string_array.h"
50 #include "remmina_public.h"
51 #include "remmina_file.h"
52 #include "remmina_file_manager.h"
53 #include "remmina_file_editor.h"
54 #include "rcw.h"
55 #include "remmina_about.h"
56 #include "remmina_pref.h"
57 #include "remmina_pref_dialog.h"
58 #include "remmina_widget_pool.h"
59 #include "remmina_plugin_manager.h"
60 #include "remmina_log.h"
61 #include "remmina_icon.h"
62 #include "remmina_main.h"
63 #include "remmina_exec.h"
64 #include "remmina_mpchange.h"
65 #include "remmina_external_tools.h"
66 #include "remmina_unlock.h"
68 
70 
71 #define RM_GET_OBJECT(object_name) gtk_builder_get_object(remminamain->builder, object_name)
72 
73 enum {
84 };
85 
86 static
87 const gchar *supported_mime_types[] = {
88  "x-scheme-handler/rdp",
89  "x-scheme-handler/spice",
90  "x-scheme-handler/vnc",
91  "x-scheme-handler/remmina",
92  "application/x-remmina",
93  NULL
94 };
95 
96 static GActionEntry app_actions[] = {
97  { "about", remmina_main_on_action_application_about, NULL, NULL, NULL },
98  { "default", remmina_main_on_action_application_default, NULL, NULL, NULL },
99  { "mpchange", remmina_main_on_action_application_mpchange, NULL, NULL, NULL },
100  { "plugins", remmina_main_on_action_application_plugins, NULL, NULL, NULL },
101  { "preferences", remmina_main_on_action_application_preferences, "i", NULL, NULL },
102  { "dark", remmina_main_on_action_application_dark_theme, NULL, NULL, NULL },
103  { "debug", remmina_main_on_action_help_debug, NULL, NULL, NULL },
104  { "community", remmina_main_on_action_help_community, NULL, NULL, NULL },
105  { "donations", remmina_main_on_action_help_donations, NULL, NULL, NULL },
106  { "homepage", remmina_main_on_action_help_homepage, NULL, NULL, NULL },
107  { "wiki", remmina_main_on_action_help_wiki, NULL, NULL, NULL },
108  { "quit", remmina_main_on_action_application_quit, NULL, NULL, NULL },
109 };
110 
111 static GActionEntry main_actions[] = {
112  { "connect", remmina_main_on_action_connection_connect, NULL, NULL, NULL },
113  { "copy", remmina_main_on_action_connection_copy, NULL, NULL, NULL },
114  { "delete", remmina_main_on_action_connection_delete, NULL, NULL, NULL },
115  { "delete_multiple", remmina_main_on_action_connection_delete_multiple, NULL, NULL, NULL },
116  { "edit", remmina_main_on_action_connection_edit, NULL, NULL, NULL },
117  { "exttools", remmina_main_on_action_connection_external_tools, NULL, NULL, NULL },
118  { "new", remmina_main_on_action_connection_new, NULL, NULL, NULL },
119  { "export", remmina_main_on_action_tools_export, NULL, NULL, NULL },
120  { "import", remmina_main_on_action_tools_import, NULL, NULL, NULL },
121  { "expand", remmina_main_on_action_expand, NULL, NULL, NULL },
122  { "collapse", remmina_main_on_action_collapse, NULL, NULL, NULL },
123  { "search", remmina_main_on_action_search_toggle, NULL, NULL, NULL },
124 };
125 
126 static GtkTargetEntry remmina_drop_types[] =
127 {
128  { "text/uri-list", 0, 1 }
129 };
130 
132 {
133  "RDP", "VNC", "SSH", "NX", "SPICE", "X2GO"
134 };
135 
139 static void remmina_main_save_size(void)
140 {
141  TRACE_CALL(__func__);
142  if ((gdk_window_get_state(gtk_widget_get_window(GTK_WIDGET(remminamain->window))) & GDK_WINDOW_STATE_MAXIMIZED) == 0) {
143  gtk_window_get_size(remminamain->window, &remmina_pref.main_width, &remmina_pref.main_height);
144  remmina_pref.main_maximize = FALSE;
145  } else {
147  }
148 }
149 
150 static void remmina_main_save_expanded_group_func(GtkTreeView *tree_view, GtkTreePath *path, gpointer user_data)
151 {
152  TRACE_CALL(__func__);
153  GtkTreeIter iter;
154  gchar *group;
155 
156  gtk_tree_model_get_iter(remminamain->priv->file_model_sort, &iter, path);
157  gtk_tree_model_get(remminamain->priv->file_model_sort, &iter, GROUP_COLUMN, &group, -1);
158  if (group) {
159  remmina_string_array_add(remminamain->priv->expanded_group, group);
160  g_free(group);
161  }
162 }
163 
165 {
166  TRACE_CALL(__func__);
167  if (GTK_IS_TREE_STORE(remminamain->priv->file_model)) {
168  if (remminamain->priv->expanded_group)
170  remminamain->priv->expanded_group = remmina_string_array_new();
171  gtk_tree_view_map_expanded_rows(remminamain->tree_files_list,
172  (GtkTreeViewMappingFunc)remmina_main_save_expanded_group_func, NULL);
173  }
174 }
175 
181 {
182  TRACE_CALL(__func__);
183  if (!remminamain || !remminamain->window)
184  return;
185 
191 }
192 
194 {
195  TRACE_CALL(__func__);
196 
197  if (remminamain) {
198  if (remminamain->window)
199  gtk_widget_destroy(GTK_WIDGET(remminamain->window));
200 
201  g_object_unref(remminamain->builder);
203  remminamain->priv->expanded_group = NULL;
204  if (remminamain->priv->file_model)
205  g_object_unref(G_OBJECT(remminamain->priv->file_model));
206  g_object_unref(G_OBJECT(remminamain->priv->file_model_filter));
207  g_free(remminamain->priv->selected_filename);
208  g_free(remminamain->priv->selected_name);
209  g_free(remminamain->priv);
210  g_free(remminamain);
211  remminamain = NULL;
212  }
213 }
214 
218 static gboolean remmina_main_dexit(gpointer data)
219 {
220  TRACE_CALL(__func__);
222  return FALSE;
223 }
224 
225 gboolean remmina_main_on_delete_event(GtkWidget *widget, GdkEvent *event, gpointer user_data)
226 {
227  TRACE_CALL(__func__);
229 
230  g_idle_add(remmina_main_dexit, NULL);
231 
232  return FALSE;
233 }
234 
235 gboolean remmina_main_idle_destroy(gpointer data)
236 {
237  TRACE_CALL(__func__);
238 
239  if (remminamain)
241 
242  return G_SOURCE_REMOVE;
243 }
244 
249 {
250  TRACE_CALL(__func__);
251 
252  if (remminamain) {
253  /* Invalidate remminamain->window to avoid multiple destructions */
254  remminamain->window = NULL;
255  /* Destroy remminamain struct, later. We can't destroy
256  * important objects like the builder now */
257  g_idle_add(remmina_main_idle_destroy, NULL);
258  }
259 }
260 
262 {
263  TRACE_CALL(__func__);
264  g_free(remminamain->priv->selected_filename);
265  g_free(remminamain->priv->selected_name);
266  remminamain->priv->selected_filename = NULL;
267  remminamain->priv->selected_name = NULL;
268 }
269 
270 #ifdef SNAP_BUILD
271 
273 {
274  GtkBuilder *dlgbuilder = NULL;
275  GtkWidget *dlg;
276  GtkWindow *parent;
277  int result;
278  static gboolean shown_once = FALSE;
279  gboolean need_snap_interface_connections = FALSE;
280  GtkWidget *dsa;
282 
283  if (shown_once)
284  return;
285  else
286  shown_once = TRUE;
287 
288  g_print("Remmina is compiled as a SNAP package.\n");
289  remmina_secret_plugin = remmina_plugin_manager_get_secret_plugin();
290  if (remmina_secret_plugin == NULL) {
291  g_print(" but we can’t find the secret plugin inside the SNAP.\n");
292  need_snap_interface_connections = TRUE;
293  } else {
294  if (!remmina_secret_plugin->is_service_available(remmina_secret_plugin)) {
295  g_print(" but we can’t access a secret service. Secret service or SNAP interface connection is missing.\n");
296  need_snap_interface_connections = TRUE;
297  }
298  }
299 
300  if (need_snap_interface_connections && !remmina_pref.prevent_snap_welcome_message) {
301  dlgbuilder = remmina_public_gtk_builder_new_from_resource("/org/remmina/Remmina/src/../data/ui/remmina_snap_info_dialog.glade");
302  dsa = GTK_WIDGET(gtk_builder_get_object(dlgbuilder, "dontshowagain"));
303  if (dlgbuilder) {
304  parent = remmina_main_get_window();
305  dlg = GTK_WIDGET(gtk_builder_get_object(dlgbuilder, "SnapInfoDlg"));
306  if (parent)
307  gtk_window_set_transient_for(GTK_WINDOW(dlg), parent);
308  gtk_builder_connect_signals(dlgbuilder, NULL);
309  result = gtk_dialog_run(GTK_DIALOG(dlg));
310  if (result == 1) {
311  remmina_pref.prevent_snap_welcome_message = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dsa));
313  }
314  gtk_widget_destroy(dlg);
315  g_object_unref(dlgbuilder);
316  }
317  }
318 }
319 #endif
320 
321 
322 static gboolean remmina_main_selection_func(GtkTreeSelection *selection, GtkTreeModel *model, GtkTreePath *path,
323  gboolean path_currently_selected, gpointer user_data)
324 {
325  TRACE_CALL(__func__);
326  guint context_id;
327  GtkTreeIter iter;
328  gchar buf[1000];
329 
330  if (path_currently_selected)
331  return TRUE;
332 
333  if (!gtk_tree_model_get_iter(model, &iter, path))
334  return TRUE;
335 
337 
338  gtk_tree_model_get(model, &iter,
339  NAME_COLUMN, &remminamain->priv->selected_name,
340  FILENAME_COLUMN, &remminamain->priv->selected_filename,
341  -1);
342 
343  context_id = gtk_statusbar_get_context_id(remminamain->statusbar_main, "status");
344  gtk_statusbar_pop(remminamain->statusbar_main, context_id);
345  if (remminamain->priv->selected_filename) {
346  g_snprintf(buf, sizeof(buf), "%s (%s)", remminamain->priv->selected_name, remminamain->priv->selected_filename);
347  gtk_statusbar_push(remminamain->statusbar_main, context_id, buf);
348  } else {
349  gtk_statusbar_push(remminamain->statusbar_main, context_id, remminamain->priv->selected_name);
350  }
351 
352  return TRUE;
353 }
354 
355 static void remmina_main_load_file_list_callback(RemminaFile *remminafile, gpointer user_data)
356 {
357  TRACE_CALL(__func__);
358  GtkTreeIter iter;
359  GtkListStore *store;
360 
361  store = GTK_LIST_STORE(user_data);
362  gchar *datetime;
363 
364  datetime = remmina_file_get_datetime(remminafile);
365  gtk_list_store_append(store, &iter);
366  gtk_list_store_set(store, &iter,
368  NAME_COLUMN, remmina_file_get_string(remminafile, "name"),
369  NOTES_COLUMN, g_uri_unescape_string(remmina_file_get_string(remminafile, "notes_text"), NULL),
370  GROUP_COLUMN, remmina_file_get_string(remminafile, "group"),
371  SERVER_COLUMN, remmina_file_get_string(remminafile, "server"),
372  PLUGIN_COLUMN, remmina_file_get_string(remminafile, "protocol"),
373  DATE_COLUMN, datetime,
375  LABELS_COLUMN, remmina_file_get_string(remminafile, "labels"),
376  -1);
377  g_free(datetime);
378 }
379 
380 static gboolean remmina_main_load_file_tree_traverse(GNode *node, GtkTreeStore *store, GtkTreeIter *parent)
381 {
382  TRACE_CALL(__func__);
383  GtkTreeIter *iter;
384  RemminaGroupData *data;
385  GNode *child;
386 
387  iter = NULL;
388  if (node->data) {
389  data = (RemminaGroupData *)node->data;
390  iter = g_new0(GtkTreeIter, 1);
391  gtk_tree_store_append(store, iter, parent);
392  gtk_tree_store_set(store, iter,
393  PROTOCOL_COLUMN, "folder-symbolic",
394  NAME_COLUMN, data->name,
395  GROUP_COLUMN, data->group,
396  DATE_COLUMN, data->datetime,
397  FILENAME_COLUMN, NULL,
398  LABELS_COLUMN, data->labels,
399  -1);
400  }
401  for (child = g_node_first_child(node); child; child = g_node_next_sibling(child))
402  remmina_main_load_file_tree_traverse(child, store, iter);
403  g_free(iter);
404  return FALSE;
405 }
406 
407 static void remmina_main_load_file_tree_group(GtkTreeStore *store)
408 {
409  TRACE_CALL(__func__);
410  GNode *root;
411 
413  remmina_main_load_file_tree_traverse(root, store, NULL);
415 }
416 
417 static void remmina_main_expand_group_traverse(GtkTreeIter *iter)
418 {
419  TRACE_CALL(__func__);
420  GtkTreeModel *tree;
421  gboolean ret;
422  gchar *group, *filename;
423  GtkTreeIter child;
424  GtkTreePath *path;
425 
426  tree = remminamain->priv->file_model_sort;
427  ret = TRUE;
428  while (ret) {
429  gtk_tree_model_get(tree, iter, GROUP_COLUMN, &group, FILENAME_COLUMN, &filename, -1);
430  if (filename == NULL) {
431  if (remmina_string_array_find(remminamain->priv->expanded_group, group) >= 0) {
432  path = gtk_tree_model_get_path(tree, iter);
433  gtk_tree_view_expand_row(remminamain->tree_files_list, path, FALSE);
434  gtk_tree_path_free(path);
435  }
436  if (gtk_tree_model_iter_children(tree, &child, iter))
438  }
439  g_free(group);
440  g_free(filename);
441 
442  ret = gtk_tree_model_iter_next(tree, iter);
443  }
444 }
445 
446 static void remmina_main_expand_group(void)
447 {
448  TRACE_CALL(__func__);
449  GtkTreeIter iter;
450 
451  if (gtk_tree_model_get_iter_first(remminamain->priv->file_model_sort, &iter))
453 }
454 
455 static gboolean remmina_main_load_file_tree_find(GtkTreeModel *tree, GtkTreeIter *iter, const gchar *match_group)
456 {
457  TRACE_CALL(__func__);
458  gboolean ret, match;
459  gchar *group, *filename;
460  GtkTreeIter child;
461 
462  match = FALSE;
463  ret = TRUE;
464  while (ret) {
465  gtk_tree_model_get(tree, iter, GROUP_COLUMN, &group, FILENAME_COLUMN, &filename, -1);
466  match = (filename == NULL && g_strcmp0(group, match_group) == 0);
467  g_free(group);
468  g_free(filename);
469  if (match)
470  break;
471  if (gtk_tree_model_iter_children(tree, &child, iter)) {
472  match = remmina_main_load_file_tree_find(tree, &child, match_group);
473  if (match) {
474  memcpy(iter, &child, sizeof(GtkTreeIter));
475  break;
476  }
477  }
478  ret = gtk_tree_model_iter_next(tree, iter);
479  }
480  return match;
481 }
482 
483 static void remmina_main_load_file_tree_callback(RemminaFile *remminafile, gpointer user_data)
484 {
485  TRACE_CALL(__func__);
486  GtkTreeIter iter, child;
487  GtkTreeStore *store;
488  gboolean found;
489  gchar *datetime = NULL;
490 
491  store = GTK_TREE_STORE(user_data);
492 
493  found = FALSE;
494  if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter))
495  found = remmina_main_load_file_tree_find(GTK_TREE_MODEL(store), &iter,
496  remmina_file_get_string(remminafile, "group"));
497 
498  datetime = remmina_file_get_datetime(remminafile);
499  //REMMINA_DEBUG("The date is %s", datetime);
500  gtk_tree_store_append(store, &child, (found ? &iter : NULL));
501  gtk_tree_store_set(store, &child,
503  NAME_COLUMN, remmina_file_get_string(remminafile, "name"),
504  NOTES_COLUMN, g_uri_unescape_string(remmina_file_get_string(remminafile, "notes_text"), NULL),
505  GROUP_COLUMN, remmina_file_get_string(remminafile, "group"),
506  SERVER_COLUMN, remmina_file_get_string(remminafile, "server"),
507  PLUGIN_COLUMN, remmina_file_get_string(remminafile, "protocol"),
508  DATE_COLUMN, datetime,
510  LABELS_COLUMN, remmina_file_get_string(remminafile, "labels"),
511  -1);
512  g_free(datetime);
513 }
514 
515 static void remmina_main_file_model_on_sort(GtkTreeSortable *sortable, gpointer user_data)
516 {
517  TRACE_CALL(__func__);
518  gint columnid;
519  GtkSortType order;
520 
521  gtk_tree_sortable_get_sort_column_id(sortable, &columnid, &order);
525 }
526 
527 static gboolean remmina_main_filter_visible_func(GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
528 {
529  TRACE_CALL(__func__);
530  gchar *text;
531  gchar *protocol, *name, *labels, *group, *server, *plugin, *date, *s;
532  gboolean result = TRUE;
533 
534  text = g_ascii_strdown(gtk_entry_get_text(remminamain->entry_quick_connect_server), -1);
535  if (text && text[0]) {
536  gtk_tree_model_get(model, iter,
537  PROTOCOL_COLUMN, &protocol,
538  NAME_COLUMN, &name,
539  GROUP_COLUMN, &group,
540  SERVER_COLUMN, &server,
541  PLUGIN_COLUMN, &plugin,
542  DATE_COLUMN, &date,
543  LABELS_COLUMN, &labels,
544  -1);
545  if (g_strcmp0(protocol, "folder-symbolic") != 0) {
546  s = g_ascii_strdown(name ? name : "", -1);
547  g_free(name);
548  name = s;
549  s = g_ascii_strdown(group ? group : "", -1);
550  g_free(group);
551  group = s;
552  s = g_ascii_strdown(server ? server : "", -1);
553  g_free(server);
554  server = s;
555  s = g_ascii_strdown(plugin ? plugin : "", -1);
556  g_free(plugin);
557  plugin = s;
558  s = g_ascii_strdown(date ? date : "", -1);
559  g_free(date);
560  date = s;
561  result = (strstr(name, text) || strstr(group, text) || strstr(server, text) || strstr(plugin, text) || strstr(date, text));
562 
563  // Filter by labels
564 
565  s = g_ascii_strdown(labels ? labels : "", -1);
566  g_free(labels);
567  labels = s;
568 
569  if (strlen(labels) > 0) {
570  gboolean labels_result = TRUE;
571  gchar **labels_array = g_strsplit(labels, ",", -1);
572  gchar **text_array = g_strsplit(text, ",", -1);
573 
574  for (int t = 0; (NULL != text_array[t]); t++) {
575  if (0 == strlen(text_array[t])) {
576  continue;
577  }
578 
579  gboolean text_result = FALSE;
580 
581  for (int l = 0; (NULL != labels_array[l]); l++) {
582  if (0 == strlen(labels_array[l])) {
583  continue;
584  }
585 
586  text_result = (text_result || strstr(labels_array[l], text_array[t]));
587 
588  if (text_result) {
589  break;
590  }
591  }
592 
593  labels_result = (labels_result && text_result);
594 
595  if (!labels_result) {
596  break;
597  }
598  }
599 
600  result = (result || labels_result);
601 
602  g_strfreev(labels_array);
603  g_strfreev(text_array);
604  }
605  }
606  g_free(protocol);
607  g_free(name);
608  g_free(labels);
609  g_free(group);
610  g_free(server);
611  g_free(plugin);
612  g_free(date);
613  }
614  g_free(text);
615  return result;
616 }
617 
618 static void remmina_main_select_file(const gchar *filename)
619 {
620  TRACE_CALL(__func__);
621  GtkTreeIter iter;
622  GtkTreePath *path;
623  gchar *item_filename;
624  gboolean cmp;
625 
626  if (!gtk_tree_model_get_iter_first(remminamain->priv->file_model_sort, &iter))
627  return;
628 
629  while (TRUE) {
630  gtk_tree_model_get(remminamain->priv->file_model_sort, &iter, FILENAME_COLUMN, &item_filename, -1);
631  cmp = g_strcmp0(item_filename, filename);
632  g_free(item_filename);
633  if (cmp == 0) {
634  gtk_tree_selection_select_iter(gtk_tree_view_get_selection(remminamain->tree_files_list),
635  &iter);
636  path = gtk_tree_model_get_path(remminamain->priv->file_model_sort, &iter);
637  gtk_tree_view_scroll_to_cell(remminamain->tree_files_list, path, NULL, TRUE, 0.5, 0.0);
638  gtk_tree_path_free(path);
639  return;
640  }
641  if (!gtk_tree_model_iter_next(remminamain->priv->file_model_sort, &iter))
642  return;
643  }
644 }
645 
647 {
648  TRACE_CALL(__func__);
649  gint items_count;
650  gchar buf[200];
651  guint context_id;
652  gint view_file_mode;
653  gboolean always_show_notes;
654  char *save_selected_filename;
655  GtkTreeModel *newmodel;
656  const gchar *neticon;
657  const gchar *connection_tooltip;
658 
659  save_selected_filename = g_strdup(remminamain->priv->selected_filename);
661 
662  view_file_mode = remmina_pref.view_file_mode;
663  if (remminamain->priv->override_view_file_mode_to_list)
664  view_file_mode = REMMINA_VIEW_FILE_LIST;
665 
666  switch (remmina_pref.view_file_mode) {
668  gtk_toggle_button_set_active(remminamain->view_toggle_button, FALSE);
669  break;
671  default:
672  gtk_toggle_button_set_active(remminamain->view_toggle_button, TRUE);
673  break;
674  }
675 
676  switch (view_file_mode) {
678  /* Create new GtkTreeStore model */
679  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));
680  /* Hide the Group column in the tree view mode */
681  gtk_tree_view_column_set_visible(remminamain->column_files_list_group, FALSE);
682  /* Load groups first */
683  remmina_main_load_file_tree_group(GTK_TREE_STORE(newmodel));
684  /* Load files list */
685  items_count = remmina_file_manager_iterate((GFunc)remmina_main_load_file_tree_callback, (gpointer)newmodel);
686  break;
687 
689  default:
690  /* Create new GtkListStore model */
691  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));
692  /* Show the Group column in the list view mode */
693  gtk_tree_view_column_set_visible(remminamain->column_files_list_group, TRUE);
694  /* Load files list */
695  items_count = remmina_file_manager_iterate((GFunc)remmina_main_load_file_list_callback, (gpointer)newmodel);
696  break;
697  }
698 
699  /* Set note column visibility*/
700  always_show_notes = remmina_pref.always_show_notes;
701  if (!always_show_notes){
702  gtk_tree_view_column_set_visible(remminamain->column_files_list_notes, FALSE);
703  }
704 
705  /* Unset old model */
706  gtk_tree_view_set_model(remminamain->tree_files_list, NULL);
707 
708  /* Destroy the old model and save the new one */
709  remminamain->priv->file_model = newmodel;
710 
711  /* Create a sorted filtered model based on newmodel and apply it to the TreeView */
712  remminamain->priv->file_model_filter = gtk_tree_model_filter_new(remminamain->priv->file_model, NULL);
713  gtk_tree_model_filter_set_visible_func(GTK_TREE_MODEL_FILTER(remminamain->priv->file_model_filter),
714  (GtkTreeModelFilterVisibleFunc)remmina_main_filter_visible_func, NULL, NULL);
715  remminamain->priv->file_model_sort = gtk_tree_model_sort_new_with_model(remminamain->priv->file_model_filter);
716  gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(remminamain->priv->file_model_sort),
719  gtk_tree_view_set_model(remminamain->tree_files_list, remminamain->priv->file_model_sort);
720  g_signal_connect(G_OBJECT(remminamain->priv->file_model_sort), "sort-column-changed",
721  G_CALLBACK(remmina_main_file_model_on_sort), NULL);
723  /* Select the file previously selected */
724  if (save_selected_filename) {
725  remmina_main_select_file(save_selected_filename);
726  g_free(save_selected_filename);
727  }
728  gtk_tree_view_column_set_widget(remminamain->column_files_list_date, NULL);
729 
730  GtkWidget *label = gtk_tree_view_column_get_button(remminamain->column_files_list_date);
731 
732  gtk_widget_set_tooltip_text(GTK_WIDGET(label),
733  _("The latest successful connection attempt, or a pre-computed date"));
734  /* Show in the status bar the total number of connections found */
735  g_snprintf(buf, sizeof(buf), ngettext("Total %i item.", "Total %i items.", items_count), items_count);
736  context_id = gtk_statusbar_get_context_id(remminamain->statusbar_main, "status");
737  gtk_statusbar_pop(remminamain->statusbar_main, context_id);
738  gtk_statusbar_push(remminamain->statusbar_main, context_id, buf);
739 
741  if (remminamain->monitor->connected){
742  neticon = g_strdup("network-transmit-receive-symbolic");
743  connection_tooltip = g_strdup(_("Network status: fully online"));
744  } else {
745  neticon = g_strdup("network-offline-symbolic");
746  connection_tooltip = g_strdup(_("Network status: offline"));
747  }
748 
749  if (GTK_IS_WIDGET(remminamain->network_icon))
750  gtk_widget_destroy(remminamain->network_icon);
751  GIcon *icon = g_themed_icon_new (neticon);
752  remminamain->network_icon = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_BUTTON);
753  gtk_widget_set_tooltip_text (remminamain->network_icon, connection_tooltip);
754 
755  g_object_unref (icon);
756 
757  gtk_box_pack_start (GTK_BOX(remminamain->statusbar_main), remminamain->network_icon, FALSE, FALSE, 0);
758  gtk_widget_show (remminamain->network_icon);
759 
760 }
761 
762 void remmina_main_load_files_cb(GtkEntry *entry, char *string, gpointer user_data)
763 {
764  TRACE_CALL(__func__);
766 }
767 
768 void remmina_main_on_action_connection_connect(GSimpleAction *action, GVariant *param, gpointer data)
769 {
770  TRACE_CALL(__func__);
771 
772  RemminaFile *remminafile;
773 
774  if (!remminamain->priv->selected_filename)
775  return;
776 
777  remminafile = remmina_file_load(remminamain->priv->selected_filename);
778 
779  if (remminafile == NULL)
780  return;
781 
782  if (remmina_pref_get_boolean("use_primary_password")
783  && remmina_pref_get_boolean("lock_connect")
784  && remmina_unlock_new(remminamain->window) == 0)
785  return;
786  if (remmina_file_get_int (remminafile, "profile-lock", FALSE) == 1
787  && remmina_unlock_new(remminamain->window) == 0)
788  return;
789 
790  remmina_file_touch(remminafile);
792 
793  remmina_file_free(remminafile);
794 }
795 
796 void remmina_main_on_action_connection_external_tools(GSimpleAction *action, GVariant *param, gpointer data)
797 {
798  TRACE_CALL(__func__);
799  if (!remminamain->priv->selected_filename)
800  return;
801 
802  remmina_external_tools_from_filename(remminamain, remminamain->priv->selected_filename);
803 }
804 
805 static void remmina_main_file_editor_destroy(GtkWidget *widget, gpointer user_data)
806 {
807  TRACE_CALL(__func__);
808 
809  if (!remminamain)
810  return;
812 }
813 
814 void remmina_main_on_action_application_mpchange(GSimpleAction *action, GVariant *param, gpointer data)
815 {
816  TRACE_CALL(__func__);
817  RemminaFile *remminafile;
818 
819  const gchar *username;
820  const gchar *domain;
821  const gchar *group;
822  const gchar *gatewayusername;
823  const gchar *gatewaydomain;
824 
825  username = domain = group = gatewayusername = gatewaydomain = "";
826 
827  remminafile = NULL;
828 
829  if (remmina_pref_get_boolean("use_primary_password")
830  && remmina_pref_get_boolean("lock_edit")
831  && remmina_unlock_new(remminamain->window) == 0)
832  return;
833 
834  if (remminamain->priv->selected_filename) {
835  remminafile = remmina_file_load(remminamain->priv->selected_filename);
836  if (remminafile != NULL) {
837  username = remmina_file_get_string(remminafile, "username");
838  domain = remmina_file_get_string(remminafile, "domain");
839  group = remmina_file_get_string(remminafile, "group");
840  gatewayusername = remmina_file_get_string(remminafile, "gateway_username");
841  gatewaydomain = remmina_file_get_string(remminafile, "gateway_domain");
842  }
843  }
844 
845  remmina_mpchange_schedule(TRUE, group, domain, username, "", gatewayusername, gatewaydomain, "");
846 
847  if (remminafile != NULL)
848  remmina_file_free(remminafile);
849 }
850 
851 void remmina_main_on_action_connection_new(GSimpleAction *action, GVariant *param, gpointer data)
852 {
853  TRACE_CALL(__func__);
854  if (kioskmode && kioskmode == TRUE)
855  return;
856  GtkWidget *widget;
857 
858  if (remmina_pref_get_boolean("use_primary_password")
859  && remmina_pref_get_boolean("lock_edit")
860  && remmina_unlock_new(remminamain->window) == 0)
861  return;
862 
863  widget = remmina_file_editor_new();
864  g_signal_connect(G_OBJECT(widget), "destroy", G_CALLBACK(remmina_main_file_editor_destroy), remminamain);
865  gtk_window_set_transient_for(GTK_WINDOW(widget), remminamain->window);
866  gtk_widget_show(widget);
868 }
869 
870 static gboolean remmina_main_search_key_event(GtkWidget *search_entry, GdkEventKey *event, gpointer user_data)
871 {
872  TRACE_CALL(__func__);
873  if (event->keyval == GDK_KEY_Escape) {
874  gtk_entry_set_text(remminamain->entry_quick_connect_server, "");
875  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(RM_GET_OBJECT("search_toggle")), FALSE);
876  return TRUE;
877  }
878  return FALSE;
879 }
880 
881 static gboolean remmina_main_tree_row_activated(GtkTreeView *tree, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data)
882 {
883  TRACE_CALL(__func__);
884  if (gtk_tree_view_row_expanded(tree, path))
885  gtk_tree_view_collapse_row(tree, path);
886  else
887  gtk_tree_view_expand_row(tree, path, FALSE);
888  return TRUE;
889 }
890 
892 {
893  if (gtk_toggle_button_get_active(remminamain->view_toggle_button)) {
896  gtk_entry_set_text(remminamain->entry_quick_connect_server, "");
899  }
900  } else {
903  gtk_entry_set_text(remminamain->entry_quick_connect_server, "");
906  }
907  }
908 }
909 
910 void remmina_main_on_action_connection_copy(GSimpleAction *action, GVariant *param, gpointer data)
911 {
912  TRACE_CALL(__func__);
913  GtkWidget *widget;
914 
915  if (remmina_pref_get_boolean("use_primary_password")
916  && remmina_unlock_new(remminamain->window) == 0)
917  return;
918 
919  if (!remminamain->priv->selected_filename)
920  return;
921 
922  RemminaFile *remminafile = remmina_file_load(remminamain->priv->selected_filename);
923 
924  if (((remmina_pref_get_boolean("lock_edit")
925  && remmina_pref_get_boolean("use_primary_password"))
926  || remmina_file_get_int (remminafile, "profile-lock", FALSE))
927  && remmina_unlock_new(remminamain->window) == 0)
928  return;
929 
930  if (remminafile) {
931  remmina_file_free(remminafile);
932  remminafile = NULL;
933  }
934 
935  widget = remmina_file_editor_new_copy(remminamain->priv->selected_filename);
936  if (widget) {
937  g_signal_connect(G_OBJECT(widget), "destroy", G_CALLBACK(remmina_main_file_editor_destroy), remminamain);
938  gtk_window_set_transient_for(GTK_WINDOW(widget), remminamain->window);
939  gtk_widget_show(widget);
940  }
941  /* Select the file previously selected */
942  if (remminamain->priv->selected_filename)
944 }
945 
946 void remmina_main_on_action_connection_edit(GSimpleAction *action, GVariant *param, gpointer data)
947 {
948  TRACE_CALL(__func__);
949  GtkWidget *widget;
950 
951  if (!remminamain->priv->selected_filename)
952  return;
953 
954  RemminaFile *remminafile = remmina_file_load(remminamain->priv->selected_filename);
955 
956  if (remmina_pref_get_boolean("use_primary_password")
957  && (remmina_pref_get_boolean("lock_edit")
958  || remmina_file_get_int (remminafile, "profile-lock", FALSE))
959  && remmina_unlock_new(remminamain->window) == 0)
960  return;
961 
962  if (remminafile) {
963  remmina_file_free(remminafile);
964  remminafile = NULL;
965  }
966 
968  if (widget) {
969  gtk_window_set_transient_for(GTK_WINDOW(widget), remminamain->window);
970  gtk_widget_show(widget);
971  }
972 /* Select the file previously selected */
973  if (remminamain->priv->selected_filename)
975 }
976 
977 void remmina_main_on_action_connection_delete(GSimpleAction *action, GVariant *param, gpointer data)
978 {
979  TRACE_CALL(__func__);
980  GtkWidget *dialog;
981 
982  if (!remminamain->priv->selected_filename)
983  return;
984 
985  RemminaFile *remminafile = remmina_file_load(remminamain->priv->selected_filename);
986 
987  if (((remmina_pref_get_boolean("lock_edit")
988  && remmina_pref_get_boolean("use_primary_password"))
989  || remmina_file_get_int (remminafile, "profile-lock", FALSE))
990  && remmina_unlock_new(remminamain->window) == 0)
991  return;
992 
993  if (remminafile) {
994  remmina_file_free(remminafile);
995  remminafile = NULL;
996  }
997 
998  dialog = gtk_message_dialog_new(remminamain->window, GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
999  _("Are you sure you want to delete “%s”?"), remminamain->priv->selected_name);
1000  if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_YES) {
1001  gchar *delfilename = g_strdup(remminamain->priv->selected_filename);
1002  remmina_file_delete(delfilename);
1003  g_free(delfilename), delfilename = NULL;
1006  }
1007  gtk_widget_destroy(dialog);
1009 }
1010 
1011 void remmina_main_on_action_connection_delete_multiple(GSimpleAction *action, GVariant *param, gpointer data)
1012 {
1013  TRACE_CALL(__func__);
1014  GtkWidget *dialog;
1015  GtkTreeSelection *sel = gtk_tree_view_get_selection(remminamain->tree_files_list);
1016  GtkTreeModel *model = gtk_tree_view_get_model(remminamain->tree_files_list);
1017  GList *list = gtk_tree_selection_get_selected_rows(sel, &model);
1018  gchar *file_to_delete;
1019 
1020  dialog = gtk_message_dialog_new(remminamain->window, GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
1021  _("Are you sure you want to delete the selected files?"));
1022 
1023  // Delete files if Yes is clicked
1024  if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_YES) {
1025  while (list) {
1026  GtkTreePath *path = list->data;
1027  GtkTreeIter iter;
1028 
1029  if (!gtk_tree_model_get_iter(model, &iter, path)) {
1030  GtkWidget *dialog_warning;
1031  dialog_warning = gtk_message_dialog_new(remminamain->window, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
1032  _("Failed to delete files!"));
1033  gtk_dialog_run(GTK_DIALOG(dialog_warning));
1034  gtk_widget_destroy(dialog_warning);
1035  gtk_widget_destroy(dialog);
1037  return;
1038  }
1039 
1040  gtk_tree_model_get(model, &iter,
1041  FILENAME_COLUMN, &file_to_delete, -1);
1042 
1043  RemminaFile *remminafile = remmina_file_load(file_to_delete);
1044 
1045  if (((remmina_pref_get_boolean("lock_edit")
1046  && remmina_pref_get_boolean("use_primary_password"))
1047  || remmina_file_get_int (remminafile, "profile-lock", FALSE))
1048  && remmina_unlock_new(remminamain->window) == 0)
1049  return;
1050 
1051  if (remminafile) {
1052  remmina_file_free(remminafile);
1053  remminafile = NULL;
1054  }
1055 
1056  gchar *delfilename = g_strdup(file_to_delete);
1057  remmina_file_delete(delfilename);
1058  g_free(delfilename), delfilename = NULL;
1061  list = g_list_next(list);
1062  }
1063  }
1064 
1065  gtk_widget_destroy(dialog);
1067 }
1068 
1069 void remmina_main_on_accel_application_preferences(GSimpleAction *action, GVariant *param, gpointer data)
1070 {
1071  TRACE_CALL(__func__);
1072  GVariant *v = g_variant_new("i", 0);
1073 
1075 }
1076 
1078 {
1079  GtkSettings *settings;
1080  settings = gtk_settings_get_default();
1081  g_object_set(settings, "gtk-application-prefer-dark-theme", remmina_pref.dark_theme, NULL);
1083  gtk_toggle_button_set_active(remminamain->search_toggle, FALSE);
1084  }
1085  else{
1086  gtk_toggle_button_set_active(remminamain->search_toggle, TRUE);
1087  }
1088  gtk_tree_view_column_set_visible(remminamain->column_files_list_notes, remmina_pref.always_show_notes);
1089 }
1090 
1091 void remmina_main_on_action_application_preferences(GSimpleAction *action, GVariant *param, gpointer data)
1092 {
1093  TRACE_CALL(__func__);
1094 
1095  REMMINA_DEBUG("Opening the preferences");
1096  gint32 tab_num;
1097 
1098  if (param) {
1099  REMMINA_DEBUG("Parameter passed to preferences of type %s", g_variant_get_type_string(param));
1100  tab_num = g_variant_get_int32(param);
1101  REMMINA_DEBUG("We got a parameter for the preferences: %d", tab_num);
1102  } else {
1103  tab_num = 0;
1104  }
1105 
1106  if (remmina_pref_get_boolean("use_primary_password")
1107  && remmina_unlock_new(remminamain->window) == 0)
1108  return;
1109 
1110  GtkWidget *widget = remmina_pref_dialog_new(tab_num, remminamain->window);
1111 
1112  gtk_widget_show_all(widget);
1113 }
1114 
1115 void remmina_main_on_action_application_default(GSimpleAction *action, GVariant *param, gpointer data)
1116 {
1117  TRACE_CALL(__func__);
1118 #ifndef __APPLE__
1119  g_autoptr(GError) error = NULL;
1120  GDesktopAppInfo *desktop_info;
1121  GAppInfo *info = NULL;
1122  g_autofree gchar *id = g_strconcat(REMMINA_APP_ID, ".desktop", NULL);
1123  int i;
1124 
1125  desktop_info = g_desktop_app_info_new(id);
1126  if (!desktop_info)
1127  return;
1128 
1129  info = G_APP_INFO(desktop_info);
1130 
1131  for (i = 0; supported_mime_types[i]; i++) {
1132  if (!g_app_info_set_as_default_for_type(info, supported_mime_types[i], &error))
1133  g_warning("Failed to set '%s' as the default application for secondary content type '%s': %s",
1134  g_app_info_get_name(info), supported_mime_types[i], error->message);
1135  else
1136  g_debug("Set '%s' as the default application for '%s'",
1137  g_app_info_get_name(info),
1139  }
1140 #endif
1141 }
1142 
1143 void remmina_main_on_action_application_quit(GSimpleAction *action, GVariant *param, gpointer data)
1144 {
1145  // Called by quit signal in remmina_main.glade
1146  TRACE_CALL(__func__);
1147  g_debug("Quit intercept");
1149 }
1150 
1152 {
1155  gtk_entry_set_text(remminamain->entry_quick_connect_server, "");
1158  }
1159 }
1160 
1161 void remmina_main_toggle_password_view(GtkWidget *widget, gpointer data)
1162 {
1163  GtkWindow *mainwindow;
1164  gboolean visible = gtk_entry_get_visibility(GTK_ENTRY(widget));
1165 
1166  mainwindow = remmina_main_get_window();
1167  if (remmina_pref_get_boolean("use_primary_password") && remmina_pref_get_boolean("lock_view_passwords") && remmina_unlock_new(mainwindow) == 0)
1168  return;
1169 
1170  if (visible) {
1171  gtk_entry_set_visibility(GTK_ENTRY(widget), FALSE);
1172  gtk_entry_set_icon_from_icon_name(GTK_ENTRY(widget), GTK_ENTRY_ICON_SECONDARY, "org.remmina.Remmina-password-reveal-symbolic");
1173  } else {
1174  gtk_entry_set_visibility(GTK_ENTRY(widget), TRUE);
1175  gtk_entry_set_icon_from_icon_name(GTK_ENTRY(widget), GTK_ENTRY_ICON_SECONDARY, "org.remmina.Remmina-password-conceal-symbolic");
1176  }
1177 }
1178 
1179 static void remmina_main_import_file_list(GSList *files)
1180 {
1181  TRACE_CALL(__func__);
1182  GtkWidget *dlg;
1183  GSList *element;
1184  gchar *path;
1185  RemminaFilePlugin *plugin;
1186  GString *err;
1187  RemminaFile *remminafile = NULL;
1188  gboolean imported;
1189 
1190  err = g_string_new(NULL);
1191  imported = FALSE;
1192  for (element = files; element; element = element->next) {
1193  path = (gchar *)element->data;
1195  if (plugin && (remminafile = plugin->import_func(plugin, path)) != NULL && remmina_file_get_string(remminafile, "name")) {
1196  remmina_file_generate_filename(remminafile);
1197  remmina_file_save(remminafile);
1198  imported = TRUE;
1199  } else {
1200  g_string_append(err, path);
1201  g_string_append_c(err, '\n');
1202  }
1203  if (remminafile) {
1204  remmina_file_free(remminafile);
1205  remminafile = NULL;
1206  }
1207  g_free(path);
1208  }
1209  g_slist_free(files);
1210  if (err->len > 0) {
1211  // TRANSLATORS: The placeholder %s is an error message
1212  dlg = gtk_message_dialog_new(remminamain->window, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
1213  _("Unable to import:\n%s"), err->str);
1214  g_signal_connect(G_OBJECT(dlg), "response", G_CALLBACK(gtk_widget_destroy), NULL);
1215  gtk_widget_show(dlg);
1216  }
1217  g_string_free(err, TRUE);
1218  if (imported)
1220 }
1221 
1222 static void remmina_main_action_tools_import_on_response(GtkDialog *dialog, gint response_id, gpointer user_data)
1223 {
1224  TRACE_CALL(__func__);
1225  GSList *files;
1226 
1227  if (response_id == GTK_RESPONSE_ACCEPT) {
1228  files = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
1230  }
1231  gtk_widget_destroy(GTK_WIDGET(dialog));
1232 }
1233 
1234 void remmina_main_on_action_tools_import(GSimpleAction *action, GVariant *param, gpointer data)
1235 {
1236  TRACE_CALL(__func__);
1237  GtkWidget *dialog;
1238 
1239  dialog = gtk_file_chooser_dialog_new(_("Import"), remminamain->window, GTK_FILE_CHOOSER_ACTION_OPEN, "Import",
1240  GTK_RESPONSE_ACCEPT, NULL);
1241  gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
1242  g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(remmina_main_action_tools_import_on_response), NULL);
1243  gtk_widget_show(dialog);
1244 }
1245 
1246 void remmina_main_on_action_tools_export(GSimpleAction *action, GVariant *param, gpointer data)
1247 {
1248  TRACE_CALL(__func__);
1249  RemminaFilePlugin *plugin;
1250  RemminaFile *remminafile;
1251  GtkWidget *dialog;
1252 
1253  if (!remminamain->priv->selected_filename)
1254  return;
1255 
1256  remminafile = remmina_file_load(remminamain->priv->selected_filename);
1257  if (remminafile == NULL)
1258  return;
1259  plugin = remmina_plugin_manager_get_export_file_handler(remminafile);
1260  if (plugin) {
1261  dialog = gtk_file_chooser_dialog_new(plugin->export_hints, remminamain->window,
1262  GTK_FILE_CHOOSER_ACTION_SAVE, _("_Save"), GTK_RESPONSE_ACCEPT, NULL);
1263  if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
1264  plugin->export_func(plugin, remminafile, gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)));
1265  gtk_widget_destroy(dialog);
1266  } else {
1267  dialog = gtk_message_dialog_new(remminamain->window, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
1268  _("This protocol does not support exporting."));
1269  g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(gtk_widget_destroy), NULL);
1270  gtk_widget_show(dialog);
1271  }
1272  remmina_file_free(remminafile);
1273 }
1274 
1275 void remmina_main_on_action_application_plugins(GSimpleAction *action, GVariant *param, gpointer data)
1276 {
1277  TRACE_CALL(__func__);
1278  remmina_plugin_manager_show(remminamain->window);
1279 }
1280 
1281 void remmina_main_on_action_application_dark_theme(GSimpleAction *action, GVariant *param, gpointer data)
1282 {
1283  TRACE_CALL(__func__);
1284  GtkSettings *settings;
1285 
1286  settings = gtk_settings_get_default();
1287 
1288  if (gtk_switch_get_active(remminamain->switch_dark_mode))
1290  else
1293 
1294  g_object_set(settings, "gtk-application-prefer-dark-theme", remmina_pref.dark_theme, NULL);
1295 }
1296 
1297 void remmina_main_on_action_help_homepage(GSimpleAction *action, GVariant *param, gpointer data)
1298 {
1299  TRACE_CALL(__func__);
1300  g_app_info_launch_default_for_uri("https://www.remmina.org", NULL, NULL);
1301 }
1302 
1303 void remmina_main_on_action_help_wiki(GSimpleAction *action, GVariant *param, gpointer data)
1304 {
1305  TRACE_CALL(__func__);
1306  g_app_info_launch_default_for_uri("https://gitlab.com/Remmina/Remmina/wikis/home", NULL, NULL);
1307 }
1308 
1309 void remmina_main_on_action_help_community(GSimpleAction *action, GVariant *param, gpointer data)
1310 {
1311  TRACE_CALL(__func__);
1312  g_app_info_launch_default_for_uri("https://remmina.org/community", NULL, NULL);
1313 }
1314 
1315 void remmina_main_on_action_help_donations(GSimpleAction *action, GVariant *param, gpointer data)
1316 {
1317  TRACE_CALL(__func__);
1318  g_app_info_launch_default_for_uri("https://www.remmina.org/donations", NULL, NULL);
1319 }
1320 
1321 void remmina_main_on_action_help_debug(GSimpleAction *action, GVariant *param, gpointer data)
1322 {
1323  TRACE_CALL(__func__);
1325 }
1326 
1327 void remmina_main_on_action_application_about(GSimpleAction *action, GVariant *param, gpointer data)
1328 {
1329  TRACE_CALL(__func__);
1330  remmina_about_open(remminamain->window);
1331 };
1332 
1333 static gboolean is_empty(const gchar *s)
1334 {
1335  if (s == NULL)
1336  return TRUE;
1337  while (*s != 0) {
1338  if (!isspace((unsigned char)*s))
1339  return FALSE;
1340  s++;
1341  }
1342  return TRUE;
1343 }
1344 
1345 static gboolean remmina_main_quickconnect(void)
1346 {
1347  TRACE_CALL(__func__);
1348  RemminaFile *remminafile;
1349  gchar *server;
1350  gchar *server_trimmed;
1351  gchar *qcp;
1352 
1353 
1354  /* Save quick connect protocol if different from the previous one */
1355  qcp = gtk_combo_box_text_get_active_text(remminamain->combo_quick_connect_protocol);
1356  if (qcp && strcmp(qcp, remmina_pref.last_quickconnect_protocol) != 0) {
1358  remmina_pref.last_quickconnect_protocol = g_strdup(qcp);
1360  }
1361 
1362  remminafile = remmina_file_new();
1363  server = g_strdup(gtk_entry_get_text(remminamain->entry_quick_connect_server));
1364  if (g_hostname_to_ascii(server) == NULL)
1365  return FALSE;
1366  /* If server contain /, e.g. vnc://, it won't connect
1367  * We could search for an array of invalid characters, but
1368  * it's better to find a way to correctly parse and validate addresses
1369  */
1370  if (g_strrstr(server, "/") != NULL)
1371  return FALSE;
1372  //if (g_str_has_suffix (server, "/"))
1373  //return FALSE;
1374  if (is_empty(server))
1375  return FALSE;
1376 
1377  /* check if server is an IP address and trim whitespace if so */
1378  server_trimmed = g_strdup(server);
1379  g_strstrip(server_trimmed);
1380  gchar **strings = g_strsplit(server_trimmed, ":", 2);
1381 
1382  if (strings[0] != NULL)
1383  if (g_hostname_is_ip_address(strings[0]))
1384  g_stpcpy(server, server_trimmed);
1385 
1386  remmina_file_set_string(remminafile, "sound", "off");
1387  remmina_file_set_string(remminafile, "server", server);
1388  remmina_file_set_string(remminafile, "name", server);
1389  remmina_file_set_string(remminafile, "protocol", qcp);
1390  g_free(server);
1391  g_free(server_trimmed);
1392  g_free(qcp);
1393 
1394  rcw_open_from_file(remminafile);
1395 
1396  return FALSE;
1397 }
1398 
1399 gboolean remmina_main_quickconnect_on_click(GtkWidget *widget, gpointer user_data)
1400 {
1401  TRACE_CALL(__func__);
1402  if (!kioskmode && kioskmode == FALSE)
1403  return remmina_main_quickconnect();
1404  return FALSE;
1405 }
1406 
1407 /* Select all the text inside the quick search box if there is anything */
1408 void remmina_main_quick_search_enter(GtkWidget *widget, gpointer user_data)
1409 {
1410  if (gtk_entry_get_text(remminamain->entry_quick_connect_server))
1411  gtk_editable_select_region(GTK_EDITABLE(remminamain->entry_quick_connect_server), 0, -1);
1412 }
1413 
1414 void remmina_main_on_action_collapse(GSimpleAction *action, GVariant *param, gpointer data)
1415 {
1416  TRACE_CALL(__func__);
1417  gtk_tree_view_collapse_all(remminamain->tree_files_list);
1418 }
1419 
1420 void remmina_main_on_action_search_toggle(GSimpleAction *action, GVariant *param, gpointer data)
1421 {
1422  TRACE_CALL(__func__);
1423  REMMINA_DEBUG("Search toggle triggered");
1424 
1425  gboolean toggle_status = gtk_toggle_button_get_active(remminamain->search_toggle);
1426 
1427  gtk_search_bar_set_search_mode(remminamain->search_bar, toggle_status);
1428  if (toggle_status) {
1429  REMMINA_DEBUG("Search toggle is active");
1430  gtk_widget_grab_focus(GTK_WIDGET(remminamain->entry_quick_connect_server));
1431  } else {
1432  REMMINA_DEBUG("Search toggle is not active, focus is tree_files_list");
1433  gtk_widget_grab_focus(GTK_WIDGET(remminamain->tree_files_list));
1434  }
1435 }
1436 
1438 {
1439  TRACE_CALL(__func__);
1440  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(remminamain->search_toggle), TRUE);
1441 }
1442 
1443 void remmina_main_on_action_expand(GSimpleAction *action, GVariant *param, gpointer data)
1444 {
1445  TRACE_CALL(__func__);
1446  gtk_tree_view_expand_all(remminamain->tree_files_list);
1447 }
1448 
1449 /* Handle double click on a row in the connections list */
1450 void remmina_main_file_list_on_row_activated(GtkTreeView *tree, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data)
1451 {
1452  TRACE_CALL(__func__);
1453 /* If a connection was selected then execute the default action */
1454  if (remminamain->priv->selected_filename) {
1455  switch (remmina_pref.default_action) {
1456  case REMMINA_ACTION_EDIT:
1457  remmina_main_on_action_connection_edit(NULL, NULL, NULL);
1458  break;
1460  default:
1462  break;
1463  }
1464  }
1465 }
1466 
1467 /* Show the popup menu by the right button mouse click */
1468 gboolean remmina_main_file_list_on_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1469 {
1470  TRACE_CALL(__func__);
1471  if (event->button == MOUSE_BUTTON_RIGHT) {
1472  if (!kioskmode && kioskmode == FALSE) {
1473 #if GTK_CHECK_VERSION(3, 22, 0)
1474  // For now, if more than one selected row, display only a delete menu option
1475  if (gtk_tree_selection_count_selected_rows(gtk_tree_view_get_selection(remminamain->tree_files_list)) > 1) {
1476  gtk_menu_popup_at_pointer(GTK_MENU(remminamain->menu_popup_delete_rc), (GdkEvent *)event);
1477  return GDK_EVENT_STOP;
1478  }
1479  else {
1480  gtk_menu_popup_at_pointer(GTK_MENU(remminamain->menu_popup), (GdkEvent *)event);
1481  }
1482 #else
1483  gtk_menu_popup(remminamain->menu_popup, NULL, NULL, NULL, NULL, event->button, event->time);
1484 #endif
1485  }
1486  }
1487  return FALSE;
1488 }
1489 
1490 /* Show the popup menu by the menu key */
1491 gboolean remmina_main_file_list_on_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
1492 {
1493  TRACE_CALL(__func__);
1494  if (event->keyval == GDK_KEY_Menu) {
1495 #if GTK_CHECK_VERSION(3, 22, 0)
1496  gtk_menu_popup_at_widget(GTK_MENU(remminamain->menu_popup), widget,
1497  GDK_GRAVITY_CENTER, GDK_GRAVITY_CENTER,
1498  (GdkEvent *)event);
1499 #else
1500  gtk_menu_popup(remminamain->menu_popup, NULL, NULL, NULL, NULL, 0, event->time);
1501 #endif
1502  }
1503  return FALSE;
1504 }
1505 
1506 void remmina_main_quick_search_on_icon_press(GtkEntry *entry, GtkEntryIconPosition icon_pos, GdkEvent *event, gpointer user_data)
1507 {
1508  TRACE_CALL(__func__);
1509  if (icon_pos == GTK_ENTRY_ICON_SECONDARY)
1510  gtk_entry_set_text(entry, "");
1511 }
1512 
1513 void remmina_main_quick_search_on_changed(GtkEditable *editable, gpointer user_data)
1514 {
1515  TRACE_CALL(__func__);
1516  /* If a search text was input then temporary set the file mode to list */
1517  if (gtk_entry_get_text_length(remminamain->entry_quick_connect_server)) {
1518  if (GTK_IS_TREE_STORE(remminamain->priv->file_model)) {
1519  /* File view mode changed, put it to override and reload list */
1520  remminamain->priv->override_view_file_mode_to_list = TRUE;
1522  }
1523  } else {
1524  if (remminamain->priv->override_view_file_mode_to_list) {
1525  /* File view mode changed, put it to default (disable override) and reload list */
1526  remminamain->priv->override_view_file_mode_to_list = FALSE;
1528  }
1529  }
1530  gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER(remminamain->priv->file_model_filter));
1531 }
1532 
1533 void remmina_main_on_drag_data_received(GtkWidget *widget, GdkDragContext *drag_context, gint x, gint y,
1534  GtkSelectionData *data, guint info, guint time, gpointer user_data)
1535 {
1536  TRACE_CALL(__func__);
1537  gchar **uris;
1538  GSList *files = NULL;
1539  gint i;
1540 
1541  uris = g_uri_list_extract_uris((const gchar *)gtk_selection_data_get_data(data));
1542  for (i = 0; uris[i]; i++) {
1543  if (strncmp(uris[i], "file://", 7) != 0)
1544  continue;
1545  files = g_slist_append(files, g_strdup(uris[i] + 7));
1546  }
1547  g_strfreev(uris);
1549 }
1550 
1551 /* Add a new menuitem to the Tools menu */
1552 static gboolean remmina_main_add_tool_plugin(gchar *name, RemminaPlugin *plugin, gpointer user_data)
1553 {
1554  TRACE_CALL(__func__);
1555  RemminaToolPlugin *tool_plugin = (RemminaToolPlugin *)plugin;
1556  GtkWidget *menuitem = gtk_menu_item_new_with_label(plugin->description);
1557 
1558  gtk_widget_show(menuitem);
1559  gtk_menu_shell_append(GTK_MENU_SHELL(remminamain->menu_popup_full), menuitem);
1560  g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(tool_plugin->exec_func), tool_plugin);
1561  return FALSE;
1562 }
1563 
1564 gboolean remmina_main_on_window_state_event(GtkWidget *widget, GdkEventWindowState *event, gpointer user_data)
1565 {
1566  TRACE_CALL(__func__);
1567  return FALSE;
1568 }
1569 
1570 /* Remmina main window initialization */
1571 static void remmina_main_init(void)
1572 {
1573  TRACE_CALL(__func__);
1574  int i, qcp_idx, qcp_actidx;
1575  char *name;
1576  GtkSettings *settings;
1577 
1578  REMMINA_DEBUG("Initializing the Remmina main window");
1579  /* Switch to a dark theme if the user enabled it */
1580  settings = gtk_settings_get_default();
1581  g_object_set(settings, "gtk-application-prefer-dark-theme", remmina_pref.dark_theme, NULL);
1582 
1583  REMMINA_DEBUG ("Initializing monitor");
1584  remminamain->monitor = remmina_network_monitor_new();
1585 
1587  if (!kioskmode && kioskmode == FALSE)
1588  gtk_window_set_title(remminamain->window, _("Remmina Remote Desktop Client"));
1589  else
1590  gtk_window_set_title(remminamain->window, _("Remmina Kiosk"));
1591  if (!kioskmode && kioskmode == FALSE) {
1592  gtk_window_set_default_size(remminamain->window, remmina_pref.main_width, remmina_pref.main_height);
1594  gtk_window_maximize(remminamain->window);
1595  }
1596  /* Honor global preferences Search Bar visibility */
1598  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(RM_GET_OBJECT("search_toggle")), FALSE);
1599 
1600  /* Add a GtkMenuItem to the Tools menu for each plugin of type REMMINA_PLUGIN_TYPE_TOOL */
1602 
1603  /* Add available quick connect protocols to remminamain->combo_quick_connect_protocol */
1604  qcp_idx = qcp_actidx = 0;
1605  for (i = 0; i < sizeof(quick_connect_plugin_list) / sizeof(quick_connect_plugin_list[0]); i++) {
1606  name = quick_connect_plugin_list[i];
1608  gtk_combo_box_text_append(remminamain->combo_quick_connect_protocol, name, name);
1610  qcp_actidx = qcp_idx;
1611  qcp_idx++;
1612  }
1613  }
1614  gtk_combo_box_set_active(GTK_COMBO_BOX(remminamain->combo_quick_connect_protocol), qcp_actidx);
1615 
1616  /* Connect the group accelerators to the GtkWindow */
1617  //gtk_window_add_accel_group(remminamain->window, remminamain->accelgroup_shortcuts);
1618  /* Set the Quick Connection */
1619  gtk_entry_set_activates_default(remminamain->entry_quick_connect_server, TRUE);
1620  /* Set the TreeView for the files list */
1621  gtk_tree_selection_set_select_function(
1622  gtk_tree_view_get_selection(remminamain->tree_files_list),
1623  remmina_main_selection_func, NULL, NULL);
1625  gtk_tree_view_set_search_entry(remminamain->tree_files_list, GTK_ENTRY(remminamain->entry_quick_connect_server));
1627  gtk_widget_grab_focus(GTK_WIDGET(remminamain->tree_files_list));
1628  /* Load the files list */
1630 
1631  /* Drag-n-drop support */
1632  gtk_drag_dest_set(GTK_WIDGET(remminamain->window), GTK_DEST_DEFAULT_ALL, remmina_drop_types, 1, GDK_ACTION_COPY);
1633 
1634  /* Finish initialization */
1635  remminamain->priv->initialized = TRUE;
1636 
1637  /* Register the window in remmina_widget_pool with GType=GTK_WINDOW and TAG=remmina-main-window */
1638  g_object_set_data(G_OBJECT(remminamain->window), "tag", "remmina-main-window");
1639  remmina_widget_pool_register(GTK_WIDGET(remminamain->window));
1640 }
1641 
1642 /* Signal handler for "show" on remminamain->window */
1643 void remmina_main_on_show(GtkWidget *w, gpointer user_data)
1644 {
1645  TRACE_CALL(__func__);
1646 #ifdef SNAP_BUILD
1648 #endif
1649 }
1650 
1651 /* RemminaMain instance */
1652 GtkWidget *remmina_main_new(void)
1653 {
1654  TRACE_CALL(__func__);
1655  GSimpleActionGroup *actions;
1656  GtkAccelGroup *accel_group = NULL;
1657 
1658  remminamain = g_new0(RemminaMain, 1);
1659  remminamain->priv = g_new0(RemminaMainPriv, 1);
1660  /* Assign UI widgets to the private members */
1661  remminamain->builder = remmina_public_gtk_builder_new_from_resource("/org/remmina/Remmina/src/../data/ui/remmina_main.glade");
1662  remminamain->window = GTK_WINDOW(RM_GET_OBJECT("RemminaMain"));
1663  if (kioskmode && kioskmode == TRUE) {
1664  gtk_window_set_position(remminamain->window, GTK_WIN_POS_CENTER_ALWAYS);
1665  gtk_window_set_default_size(remminamain->window, 800, 400);
1666  gtk_window_set_resizable(remminamain->window, FALSE);
1667  }
1668  /* New Button */
1669  remminamain->button_new = GTK_BUTTON(RM_GET_OBJECT("button_new"));
1670  if (kioskmode && kioskmode == TRUE)
1671  gtk_widget_set_sensitive(GTK_WIDGET(remminamain->button_new), FALSE);
1672  /* Search bar */
1673  remminamain->search_toggle = GTK_TOGGLE_BUTTON(RM_GET_OBJECT("search_toggle"));
1674  remminamain->search_bar = GTK_SEARCH_BAR(RM_GET_OBJECT("search_bar"));
1675  /* view mode list/tree */
1676  remminamain->view_toggle_button = GTK_TOGGLE_BUTTON(RM_GET_OBJECT("view_toggle_button"));
1677  if (kioskmode && kioskmode == TRUE)
1678  gtk_widget_set_sensitive(GTK_WIDGET(remminamain->view_toggle_button), FALSE);
1679 
1680  /* Menu widgets */
1681  remminamain->menu_popup = GTK_MENU(RM_GET_OBJECT("menu_popup"));
1682  remminamain->menu_header_button = GTK_MENU_BUTTON(RM_GET_OBJECT("menu_header_button"));
1683  remminamain->menu_popup_full = GTK_MENU(RM_GET_OBJECT("menu_popup_full"));
1684  remminamain->menu_popup_delete_rc = GTK_MENU(RM_GET_OBJECT("menu_popup_delete_rc"));
1685  if (kioskmode && kioskmode == TRUE) {
1686  gtk_widget_set_sensitive(GTK_WIDGET(remminamain->menu_popup_full), FALSE);
1687  gtk_widget_set_sensitive(GTK_WIDGET(remminamain->menu_header_button), FALSE);
1688  }
1689  /* View mode radios */
1690  remminamain->menuitem_view_mode_list = GTK_RADIO_MENU_ITEM(RM_GET_OBJECT("menuitem_view_mode_list"));
1691  remminamain->menuitem_view_mode_tree = GTK_RADIO_MENU_ITEM(RM_GET_OBJECT("menuitem_view_mode_tree"));
1692  /* Quick connect objects */
1693  remminamain->box_quick_connect = GTK_BOX(RM_GET_OBJECT("box_quick_connect"));
1694  remminamain->combo_quick_connect_protocol = GTK_COMBO_BOX_TEXT(RM_GET_OBJECT("combo_quick_connect_protocol"));
1695  if (kioskmode && kioskmode == TRUE)
1696  gtk_widget_set_sensitive(GTK_WIDGET(remminamain->combo_quick_connect_protocol), FALSE);
1697  remminamain->entry_quick_connect_server = GTK_ENTRY(RM_GET_OBJECT("entry_quick_connect_server"));
1698  /* Other widgets */
1699  remminamain->tree_files_list = GTK_TREE_VIEW(RM_GET_OBJECT("tree_files_list"));
1700  remminamain->column_files_list_name = GTK_TREE_VIEW_COLUMN(RM_GET_OBJECT("column_files_list_name"));
1701  remminamain->column_files_list_group = GTK_TREE_VIEW_COLUMN(RM_GET_OBJECT("column_files_list_group"));
1702  remminamain->column_files_list_server = GTK_TREE_VIEW_COLUMN(RM_GET_OBJECT("column_files_list_server"));
1703  remminamain->column_files_list_plugin = GTK_TREE_VIEW_COLUMN(RM_GET_OBJECT("column_files_list_plugin"));
1704  remminamain->column_files_list_date = GTK_TREE_VIEW_COLUMN(RM_GET_OBJECT("column_files_list_date"));
1705  remminamain->column_files_list_notes = GTK_TREE_VIEW_COLUMN(RM_GET_OBJECT("column_files_list_notes"));
1706  gtk_tree_view_column_set_fixed_width(remminamain->column_files_list_notes, 100);
1707  remminamain->statusbar_main = GTK_STATUSBAR(RM_GET_OBJECT("statusbar_main"));
1708  /* signals */
1709  g_signal_connect(remminamain->entry_quick_connect_server, "key-release-event", G_CALLBACK(remmina_main_search_key_event), NULL);
1710  g_signal_connect(remminamain->tree_files_list, "row-activated", G_CALLBACK(remmina_main_tree_row_activated), NULL);
1711  /* Non widget objects */
1712  actions = g_simple_action_group_new();
1713  g_action_map_add_action_entries(G_ACTION_MAP(actions), app_actions, G_N_ELEMENTS(app_actions), remminamain->window);
1714  gtk_widget_insert_action_group(GTK_WIDGET(remminamain->window), "app", G_ACTION_GROUP(actions));
1715  g_action_map_add_action_entries(G_ACTION_MAP(actions), main_actions, G_N_ELEMENTS(main_actions), remminamain->window);
1716  gtk_widget_insert_action_group(GTK_WIDGET(remminamain->window), "main", G_ACTION_GROUP(actions));
1717  g_object_unref(actions);
1718  /* Accelerators */
1719  accel_group = gtk_accel_group_new();
1720  gtk_window_add_accel_group(remminamain->window, accel_group);
1721  gtk_accel_group_connect(accel_group, GDK_KEY_Q, GDK_CONTROL_MASK, 0,
1722  g_cclosure_new_swap(G_CALLBACK(remmina_main_on_action_application_quit), NULL, NULL));
1723  // TODO: This crash remmina because the function doesn't receive the parameter we expect
1724  gtk_accel_group_connect(accel_group, GDK_KEY_P, GDK_CONTROL_MASK, 0,
1725  g_cclosure_new_swap(G_CALLBACK(remmina_main_on_accel_application_preferences), NULL, NULL));
1726  gtk_accel_group_connect(accel_group, GDK_KEY_F, GDK_CONTROL_MASK, 0,
1727  g_cclosure_new_swap(G_CALLBACK(remmina_main_on_accel_search_toggle), remminamain, NULL));
1728 
1729  /* Connect signals */
1730  gtk_builder_connect_signals(remminamain->builder, NULL);
1731  /* Initialize the window and load the preferences */
1733  return GTK_WIDGET(remminamain->window);
1734 }
1735 
1737 {
1738  if (!remminamain)
1739  return NULL;
1740  if (!remminamain->priv)
1741  return NULL;
1742  if (!remminamain->priv->initialized)
1743  return NULL;
1744  remminamain->window = GTK_WINDOW(RM_GET_OBJECT("RemminaMain"));
1745  return remminamain->window;
1746 }
1747 
1749 {
1750  if (!remminamain)
1751  return;
1753 }
1754 
1755 void remmina_main_show_dialog(GtkMessageType msg, GtkButtonsType buttons, const gchar* message) {
1756  GtkWidget *dialog;
1757 
1758  if (remminamain->window) {
1759  dialog = gtk_message_dialog_new(remminamain->window, GTK_DIALOG_MODAL, msg, buttons, "%s", message);
1760  gtk_dialog_run(GTK_DIALOG(dialog));
1761  gtk_widget_destroy(dialog);
1762  }
1763 }
1764 
1765 void remmina_main_show_warning_dialog(const gchar *message) {
1766  GtkWidget *dialog;
1767 
1768  if (remminamain->window) {
1769  dialog = gtk_message_dialog_new(remminamain->window, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE,
1770  message, g_get_application_name());
1771  gtk_dialog_run(GTK_DIALOG(dialog));
1772  gtk_widget_destroy(dialog);
1773  }
1774 }
G_BEGIN_DECLS struct _RemminaGroupData RemminaGroupData
RemminaFile * remmina_file_load(const gchar *filename)
Definition: remmina_file.c:350
static RemminaSecretPlugin * remmina_secret_plugin
void remmina_log_start(void)
Definition: remmina_log.c:116
static GActionEntry main_actions[]
Definition: remmina_main.c:111
gboolean hide_searchbar
Definition: remmina_pref.h:155
void remmina_file_free(RemminaFile *remminafile)
Definition: remmina_file.c:709
GtkToggleButton * search_toggle
Definition: remmina_main.h:63
const gchar * remmina_file_get_string(RemminaFile *remminafile, const gchar *setting)
Definition: remmina_file.c:516
GtkRadioMenuItem * menuitem_view_mode_list
Definition: remmina_main.h:56
static void remmina_main_load_file_tree_callback(RemminaFile *remminafile, gpointer user_data)
Definition: remmina_main.c:483
void remmina_main_on_action_help_homepage(GSimpleAction *action, GVariant *param, gpointer data)
GtkWidget * network_icon
Definition: remmina_main.h:82
gint default_action
Definition: remmina_pref.h:141
static void remmina_main_show_snap_welcome()
Definition: remmina_main.c:272
GtkWindow * remmina_main_get_window()
gchar * selected_filename
Definition: remmina_main.h:96
GtkTreeModel * file_model
Definition: remmina_main.h:90
static void remmina_main_save_expanded_group(void)
Definition: remmina_main.c:164
gboolean remmina_external_tools_from_filename(RemminaMain *remminamain, gchar *remminafilename)
RemminaMainPriv * priv
Definition: remmina_main.h:85
void remmina_main_reload_preferences()
const gchar * remmina_file_get_filename(RemminaFile *remminafile)
Definition: remmina_file.c:210
void remmina_main_on_action_connection_connect(GSimpleAction *action, GVariant *param, gpointer data)
Definition: remmina_main.c:768
typedefG_BEGIN_DECLS struct _RemminaFile RemminaFile
Definition: types.h:44
GtkRadioMenuItem * menuitem_view_mode_tree
Definition: remmina_main.h:57
GtkTreeViewColumn * column_files_list_name
Definition: remmina_main.h:75
const gchar * export_hints
Definition: plugin.h:111
void remmina_plugin_manager_for_each_plugin(RemminaPluginType type, RemminaPluginFunc func, gpointer data)
GtkMenu * menu_popup_delete_rc
Definition: remmina_main.h:55
void remmina_widget_pool_register(GtkWidget *widget)
gchar * last_quickconnect_protocol
Definition: remmina_pref.h:228
gboolean remmina_main_file_list_on_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
RemminaFile *(* import_func)(struct _RemminaFilePlugin *instance, const gchar *from_file)
Definition: plugin.h:108
static gboolean remmina_main_filter_visible_func(GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
Definition: remmina_main.c:527
gboolean remmina_network_monitor_status(RemminaMonitor *rm_monitor)
RemminaFilePlugin * remmina_plugin_manager_get_export_file_handler(RemminaFile *remminafile)
void remmina_file_manager_free_group_tree(GNode *node)
RemminaFilePlugin * remmina_plugin_manager_get_import_file_handler(const gchar *file)
static gboolean remmina_main_search_key_event(GtkWidget *search_entry, GdkEventKey *event, gpointer user_data)
Definition: remmina_main.c:870
void remmina_main_on_action_tools_import(GSimpleAction *action, GVariant *param, gpointer data)
RemminaMonitor * remmina_network_monitor_new()
RemminaSecretPlugin * remmina_plugin_manager_get_secret_plugin(void)
static void remmina_main_save_size(void)
Save the Remmina Main Window size to assure the main geometry at each restart.
Definition: remmina_main.c:139
void remmina_main_on_action_application_mpchange(GSimpleAction *action, GVariant *param, gpointer data)
Definition: remmina_main.c:814
static GActionEntry app_actions[]
Definition: remmina_main.c:96
void(* exec_func)(GtkMenuItem *item, struct _RemminaToolPlugin *instance)
Definition: plugin.h:121
void remmina_main_on_view_toggle()
Definition: remmina_main.c:891
gboolean(* is_service_available)(struct _RemminaSecretPlugin *instance)
Definition: plugin.h:144
void remmina_icon_populate_menu(void)
Definition: remmina_icon.c:256
void remmina_main_on_action_connection_delete_multiple(GSimpleAction *action, GVariant *param, gpointer data)
gchar * remmina_file_get_datetime(RemminaFile *remminafile)
Return the string date of the last time a Remmina state file has been modified.
static void remmina_main_file_model_on_sort(GtkTreeSortable *sortable, gpointer user_data)
Definition: remmina_main.c:515
GtkWidget * remmina_main_new(void)
void remmina_main_on_action_help_debug(GSimpleAction *action, GVariant *param, gpointer data)
gint remmina_string_array_find(RemminaStringArray *array, const gchar *str)
gchar * remmina_string_array_to_string(RemminaStringArray *array)
GtkSwitch * switch_dark_mode
Definition: remmina_main.h:64
void remmina_main_on_destroy_event()
Called when the remminamain->window widget is destroyed (glade event handler)
Definition: remmina_main.c:248
node_t * node
Definition: remmina_ssh.c:142
void remmina_file_generate_filename(RemminaFile *remminafile)
Generate a new Remmina connection profile file name.
Definition: remmina_file.c:114
void remmina_main_save_before_destroy()
Save the Remmina Main Window size and the expanded group before to close Remmina. ...
Definition: remmina_main.c:180
const gchar * description
Definition: plugin.h:59
void remmina_main_on_accel_application_preferences(GSimpleAction *action, GVariant *param, gpointer data)
static void remmina_main_load_file_list_callback(RemminaFile *remminafile, gpointer user_data)
Definition: remmina_main.c:355
static gboolean remmina_main_load_file_tree_traverse(GNode *node, GtkTreeStore *store, GtkTreeIter *parent)
Definition: remmina_main.c:380
GtkTreeModel * file_model_filter
Definition: remmina_main.h:91
static gboolean remmina_main_load_file_tree_find(GtkTreeModel *tree, GtkTreeIter *iter, const gchar *match_group)
Definition: remmina_main.c:455
gchar * selected_name
Definition: remmina_main.h:97
void remmina_main_on_action_help_wiki(GSimpleAction *action, GVariant *param, gpointer data)
RemminaMonitor * monitor
Definition: remmina_main.h:86
gint main_sort_order
Definition: remmina_pref.h:222
void remmina_main_on_action_application_default(GSimpleAction *action, GVariant *param, gpointer data)
void remmina_main_load_files_cb(GtkEntry *entry, char *string, gpointer user_data)
Definition: remmina_main.c:762
gboolean remmina_main_on_window_state_event(GtkWidget *widget, GdkEventWindowState *event, gpointer user_data)
void remmina_main_update_file_datetime(RemminaFile *file)
GtkMenuButton * menu_header_button
Definition: remmina_main.h:53
static void remmina_main_load_file_tree_group(GtkTreeStore *store)
Definition: remmina_main.c:407
void remmina_main_on_action_connection_copy(GSimpleAction *action, GVariant *param, gpointer data)
Definition: remmina_main.c:910
void remmina_plugin_manager_show(GtkWindow *parent)
static void remmina_main_import_file_list(GSList *files)
void remmina_main_on_action_collapse(GSimpleAction *action, GVariant *param, gpointer data)
GtkMenu * menu_popup
Definition: remmina_main.h:52
gboolean remmina_main_idle_destroy(gpointer data)
Definition: remmina_main.c:235
static gboolean remmina_main_tree_row_activated(GtkTreeView *tree, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data)
Definition: remmina_main.c:881
void remmina_main_on_action_help_donations(GSimpleAction *action, GVariant *param, gpointer data)
void remmina_main_show_dialog(GtkMessageType msg, GtkButtonsType buttons, const gchar *message)
gboolean initialized
Definition: remmina_main.h:94
GtkComboBoxText * combo_quick_connect_protocol
Definition: remmina_main.h:70
gboolean prevent_snap_welcome_message
Definition: remmina_pref.h:227
gchar * expanded_group
Definition: remmina_pref.h:223
GtkBox * box_quick_connect
Definition: remmina_main.h:69
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)
GtkTreeViewColumn * column_files_list_server
Definition: remmina_main.h:77
void remmina_main_show_warning_dialog(const gchar *message)
void remmina_main_on_action_connection_new(GSimpleAction *action, GVariant *param, gpointer data)
Definition: remmina_main.c:851
void remmina_main_quick_search_enter(GtkWidget *widget, gpointer user_data)
void remmina_file_delete(const gchar *filename)
Definition: remmina_file.c:911
GtkButton * button_new
Definition: remmina_main.h:60
GtkMenu * menu_popup_full
Definition: remmina_main.h:54
void remmina_string_array_free(RemminaStringArray *array)
gint main_sort_column_id
Definition: remmina_pref.h:221
gboolean main_maximize
Definition: remmina_pref.h:220
GtkStatusbar * statusbar_main
Definition: remmina_main.h:81
GtkTreeViewColumn * column_files_list_plugin
Definition: remmina_main.h:78
void remmina_main_quick_search_on_changed(GtkEditable *editable, gpointer user_data)
void remmina_main_on_action_application_preferences(GSimpleAction *action, GVariant *param, gpointer data)
GtkBuilder * builder
Definition: remmina_main.h:49
GtkBuilder * remmina_public_gtk_builder_new_from_resource(gchar *resource)
void remmina_main_on_action_connection_delete(GSimpleAction *action, GVariant *param, gpointer data)
Definition: remmina_main.c:977
static gboolean is_empty(const gchar *s)
RemminaStringArray * remmina_string_array_new_from_string(const gchar *strs)
gboolean remmina_pref_get_boolean(const gchar *key)
gint view_file_mode
Definition: remmina_pref.h:214
RemminaStringArray * remmina_string_array_new(void)
void remmina_file_touch(RemminaFile *remminafile)
Update the atime and mtime of a given filename.
GtkWidget * remmina_file_editor_new_from_filename(const gchar *filename)
static void remmina_main_save_expanded_group_func(GtkTreeView *tree_view, GtkTreePath *path, gpointer user_data)
Definition: remmina_main.c:150
void remmina_main_on_show(GtkWidget *w, gpointer user_data)
static void remmina_main_init(void)
GtkTreeView * tree_files_list
Definition: remmina_main.h:74
static void remmina_main_select_file(const gchar *filename)
Definition: remmina_main.c:618
gboolean remmina_main_on_delete_event(GtkWidget *widget, GdkEvent *event, gpointer user_data)
Definition: remmina_main.c:225
void remmina_main_on_action_help_community(GSimpleAction *action, GVariant *param, gpointer data)
GtkToggleButton * view_toggle_button
Definition: remmina_main.h:65
void remmina_main_destroy()
Definition: remmina_main.c:193
RemminaFile * remmina_file_new(void)
Definition: remmina_file.c:93
GtkTreeViewColumn * column_files_list_group
Definition: remmina_main.h:76
void remmina_main_file_list_on_row_activated(GtkTreeView *tree, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data)
static void remmina_main_expand_group_traverse(GtkTreeIter *iter)
Definition: remmina_main.c:417
RemminaStringArray * expanded_group
Definition: remmina_main.h:99
static gboolean remmina_main_selection_func(GtkTreeSelection *selection, GtkTreeModel *model, GtkTreePath *path, gboolean path_currently_selected, gpointer user_data)
Definition: remmina_main.c:322
void remmina_main_on_action_connection_external_tools(GSimpleAction *action, GVariant *param, gpointer data)
Definition: remmina_main.c:796
static void remmina_main_clear_selection_data(void)
Definition: remmina_main.c:261
void rcw_open_from_file(RemminaFile *remminafile)
Definition: rcw.c:4470
void remmina_main_on_date_column_sort_clicked()
RemminaPref remmina_pref
Definition: rcw.c:79
gboolean remmina_main_file_list_on_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
gint remmina_file_get_int(RemminaFile *remminafile, const gchar *setting, gint default_value)
Definition: remmina_file.c:603
void remmina_main_on_action_application_dark_theme(GSimpleAction *action, GVariant *param, gpointer data)
void remmina_about_open(GtkWindow *parent)
Definition: remmina_about.c:44
GtkWidget * remmina_file_editor_new(void)
RemminaPlugin * remmina_plugin_manager_get_plugin(RemminaPluginType type, const gchar *name)
static gboolean remmina_main_add_tool_plugin(gchar *name, RemminaPlugin *plugin, gpointer user_data)
gboolean remmina_pref_save(void)
Definition: remmina_pref.c:790
void remmina_main_on_action_connection_edit(GSimpleAction *action, GVariant *param, gpointer data)
Definition: remmina_main.c:946
void remmina_main_toggle_password_view(GtkWidget *widget, gpointer data)
GtkTreeModel * file_model_sort
Definition: remmina_main.h:92
gboolean rcw_open_from_filename(const gchar *filename)
Definition: rcw.c:4422
static const gchar * supported_mime_types[]
Definition: remmina_main.c:87
void remmina_main_on_action_expand(GSimpleAction *action, GVariant *param, gpointer data)
gboolean(* export_func)(struct _RemminaFilePlugin *instance, RemminaFile *file, const gchar *to_file)
Definition: plugin.h:110
gboolean kioskmode
Definition: remmina.c:87
GtkWindow * window
Definition: remmina_main.h:50
void remmina_application_condexit(RemminaCondExitType why)
Definition: remmina_exec.c:137
void remmina_file_set_string(RemminaFile *remminafile, const gchar *setting, const gchar *value)
Definition: remmina_file.c:469
static char * quick_connect_plugin_list[]
Definition: remmina_main.c:131
void remmina_file_save(RemminaFile *remminafile)
Definition: remmina_file.c:730
GtkTreeViewColumn * column_files_list_notes
Definition: remmina_main.h:80
static gboolean remmina_main_quickconnect(void)
gboolean always_show_notes
Definition: remmina_pref.h:153
void remmina_string_array_add(RemminaStringArray *array, const gchar *str)
static GtkTargetEntry remmina_drop_types[]
Definition: remmina_main.c:126
GtkSearchBar * search_bar
Definition: remmina_main.h:67
static RemminaMain * remminamain
Definition: remmina_main.c:69
GtkWidget * remmina_file_editor_new_copy(const gchar *filename)
void remmina_main_on_action_application_quit(GSimpleAction *action, GVariant *param, gpointer data)
GtkEntry * entry_quick_connect_server
Definition: remmina_main.h:71
gint remmina_unlock_new(GtkWindow *parent)
const gchar * remmina_file_get_icon_name(RemminaFile *remminafile)
Definition: remmina_file.c:885
void remmina_mpchange_schedule(gboolean has_domain, const gchar *group, const gchar *domain, const gchar *username, const gchar *password, const gchar *gatewayusername, const gchar *gatewaydomain, const gchar *gatewaypassword)
GtkWidget * remmina_pref_dialog_new(gint default_tab, GtkWindow *parent)
GNode * remmina_file_manager_get_group_tree(void)
void remmina_main_on_action_search_toggle(GSimpleAction *action, GVariant *param, gpointer data)
gboolean override_view_file_mode_to_list
Definition: remmina_main.h:98
void remmina_main_on_action_tools_export(GSimpleAction *action, GVariant *param, gpointer data)
static void remmina_main_file_editor_destroy(GtkWidget *widget, gpointer user_data)
Definition: remmina_main.c:805
gint remmina_file_manager_iterate(GFunc func, gpointer user_data)
gboolean dark_theme
Definition: remmina_pref.h:149
static void remmina_main_expand_group(void)
Definition: remmina_main.c:446
void remmina_main_quick_search_on_icon_press(GtkEntry *entry, GtkEntryIconPosition icon_pos, GdkEvent *event, gpointer user_data)
void remmina_main_on_accel_search_toggle(RemminaMain *remminamain)
static void remmina_main_load_files()
Definition: remmina_main.c:646
GtkTreeViewColumn * column_files_list_date
Definition: remmina_main.h:79
void remmina_main_on_action_application_about(GSimpleAction *action, GVariant *param, gpointer data)
static void remmina_main_action_tools_import_on_response(GtkDialog *dialog, gint response_id, gpointer user_data)
void remmina_main_on_action_application_plugins(GSimpleAction *action, GVariant *param, gpointer data)
static gboolean remmina_main_dexit(gpointer data)
Try to exit remmina after a delete window event.
Definition: remmina_main.c:218
gboolean remmina_main_quickconnect_on_click(GtkWidget *widget, gpointer user_data)