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