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_file_editor.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-2023 Antenore Gatta, Giovanni Panozzo
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  *
22  * In addition, as a special exception, the copyright holders give
23  * permission to link the code of portions of this program with the
24  * OpenSSL library under certain conditions as described in each
25  * individual source file, and distribute linked combinations
26  * including the two.
27  * You must obey the GNU General Public License in all respects
28  * for all of the code used other than OpenSSL. * If you modify
29  * file(s) with this exception, you may extend this exception to your
30  * version of the file(s), but you are not obligated to do so. * If you
31  * do not wish to do so, delete this exception statement from your
32  * version. * If you delete this exception statement from all source
33  * files in the program, then also delete it here.
34  *
35  */
36 
37 #include <ctype.h>
38 #include "config.h"
39 #ifdef HAVE_LIBAVAHI_UI
40 #include <avahi-ui/avahi-ui.h>
41 #endif
42 #include "remmina_public.h"
43 #include "remmina_pref.h"
44 #include "rcw.h"
46 #include "remmina_file.h"
47 #include "remmina_file_editor.h"
48 #include "remmina_file_manager.h"
49 #include "remmina_icon.h"
50 #include "remmina_main.h"
51 #include "remmina_plugin_manager.h"
52 #include "remmina_pref_dialog.h"
53 #include "remmina_ssh.h"
54 #include "remmina_string_list.h"
55 #include "remmina_unlock.h"
56 #include "remmina_widget_pool.h"
57 
58 G_DEFINE_TYPE(RemminaFileEditor, remmina_file_editor, GTK_TYPE_DIALOG)
59 
60 static const gchar *server_tips = N_("<big>"
61  "Supported formats\n"
62  "• server\n"
63  "• server[:port]\n"
64  "VNC additional formats\n"
65  "• ID:repeater ID number\n"
66  "• unix:///path/socket.sock"
67  "</big>");
68 
69 static const gchar *cmd_tips = N_("<big>"
70  "• command in PATH args %h\n"
71  "• /path/to/foo -options %h %u\n"
72  "• %h is substituted with the server name\n"
73  "• %t is substituted with the SSH server name\n"
74  "• %u is substituted with the username\n"
75  "• %U is substituted with the SSH username\n"
76  "• %p is substituted with Remmina profile name\n"
77  "• %g is substituted with Remmina profile group name\n"
78  "• %d is substituted with local date and time in ISO 8601 format\n"
79  "Do not run in background if you want the command to be executed before connecting.\n"
80  "</big>");
81 
82 #ifdef HAVE_LIBSSH
83 static const gchar *server_tips2 = N_("<big>"
84  "Supported formats\n"
85  "• server\n"
86  "• server[:port]\n"
87  "• username@server[:port] (SSH protocol only)"
88  "</big>");
89 #endif
90 
91 struct _RemminaFileEditorPriv {
92  RemminaFile * remmina_file;
93  RemminaProtocolPlugin * plugin;
94  const gchar * avahi_service_type;
95 
96  GtkWidget * name_entry;
97  GtkWidget * labels_entry;
98  GtkWidget * group_combo;
99  GtkWidget * protocol_combo;
100  GtkWidget * save_button;
101 
102  GtkWidget * config_box;
103  GtkWidget * config_scrollable;
104  GtkWidget * config_viewport;
105  GtkWidget * config_container;
106 
107  GtkWidget * server_combo;
108  GtkWidget * resolution_iws_radio;
109  GtkWidget * resolution_auto_radio;
110  GtkWidget * resolution_custom_radio;
111  GtkWidget * resolution_custom_combo;
112  GtkWidget * keymap_combo;
113 
114  GtkWidget * assistance_toggle;
115  GtkWidget * assistance_file;
116  GtkWidget * assistance_password;
117  GtkWidget * assistance_file_label;
118  GtkWidget * assistance_password_label;
119 
120  GtkWidget * behavior_autostart_check;
121  GtkWidget * behavior_precommand_entry;
122  GtkWidget * behavior_postcommand_entry;
123  GtkWidget * behavior_lock_check;
124  GtkWidget * behavior_disconnect;
125 
126  GtkWidget * ssh_tunnel_enabled_check;
127  GtkWidget * ssh_tunnel_loopback_check;
128  GtkWidget * ssh_tunnel_server_default_radio;
129  GtkWidget * ssh_tunnel_server_custom_radio;
130  GtkWidget * ssh_tunnel_server_entry;
131  GtkWidget * ssh_tunnel_auth_agent_radio;
132  GtkWidget * ssh_tunnel_auth_password_radio;
133  GtkWidget * ssh_tunnel_auth_password;
134  GtkWidget * ssh_tunnel_passphrase;
135  GtkWidget * ssh_tunnel_auth_publickey_radio;
136  GtkWidget * ssh_tunnel_auth_auto_publickey_radio;
137  GtkWidget * ssh_tunnel_auth_combo;
138  GtkWidget * ssh_tunnel_username_entry;
139  GtkWidget * ssh_tunnel_privatekey_chooser;
140  GtkWidget * ssh_tunnel_certfile_chooser;
141 
142  GHashTable * setting_widgets;
143 };
144 
146 {
147  TRACE_CALL(__func__);
148 }
149 
166  GtkWidget * failed_widget,
167  GError * err)
168 {
169  if (!err) {
170  err = NULL; // g_set_error doesn't like overwriting errors.
171  g_set_error(&err, 1, 1, _("Input is invalid."));
172  }
173 
174  if (!gfe || !failed_widget) {
175  g_critical("(%s): Parameters RemminaFileEditor 'gfe' or "
176  "GtkWidget* 'failed_widget' are 'NULL'!",
177  __func__);
178  return;
179  }
180 
181  gint widget_width = gtk_widget_get_allocated_width(failed_widget);
182  gint widget_height = gtk_widget_get_allocated_height(failed_widget);
183 
184  GtkWidget *err_label = gtk_label_new("");
185  GtkWidget *alert_icon = NULL;
186  GtkWindow *err_window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
187  GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
188  GdkWindow *window = gtk_widget_get_window(failed_widget);
189 
190  GtkAllocation allocation;
191  gint failed_widget_x, failed_widget_y;
192 
193  gchar *markup = g_strdup_printf("<span size='large'>%s</span>", err->message);
194 
195  // Setup err_window
196  gtk_window_set_decorated(err_window, FALSE);
197  gtk_window_set_type_hint(err_window, GDK_WINDOW_TYPE_HINT_TOOLTIP);
198  gtk_window_set_default_size(err_window, widget_width, widget_height);
199  gtk_window_set_title(err_window, "Error");
200  gtk_window_set_resizable(err_window, TRUE);
201 
202  // Move err_window under failed_widget
203  gtk_window_set_attached_to(err_window, failed_widget);
204  gtk_window_set_transient_for(err_window, GTK_WINDOW(gfe));
205  gdk_window_get_origin(GDK_WINDOW(window), &failed_widget_x, &failed_widget_y);
206  gtk_widget_get_allocation(failed_widget, &allocation);
207  failed_widget_x += allocation.x;
208  failed_widget_y += allocation.y + allocation.height;
209  gtk_window_move(err_window, failed_widget_x, failed_widget_y);
210 
211  // Setup label
212  gtk_label_set_selectable(GTK_LABEL(err_label), FALSE);
213  gtk_label_set_max_width_chars(GTK_LABEL(err_label), 1);
214  gtk_widget_set_hexpand(GTK_WIDGET(err_label), TRUE);
215  gtk_widget_set_vexpand(GTK_WIDGET(err_label), TRUE);
216  gtk_label_set_ellipsize(GTK_LABEL(err_label), PANGO_ELLIPSIZE_END);
217  gtk_label_set_line_wrap(GTK_LABEL(err_label), TRUE);
218  gtk_label_set_line_wrap_mode(GTK_LABEL(err_label), PANGO_WRAP_WORD_CHAR);
219  gtk_label_set_markup(GTK_LABEL(err_label), markup);
220 
221  alert_icon = gtk_image_new_from_icon_name("dialog-warning-symbolic",
222  GTK_ICON_SIZE_DND);
223 
224  // Fill icon and label into a box.
225  gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(alert_icon), FALSE, FALSE, 0);
226  gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(err_label), TRUE, TRUE, 5);
227 
228  // Attach box to err_window
229  gtk_container_add(GTK_CONTAINER(err_window), GTK_WIDGET(box));
230 
231  // Display everything.
232  gtk_widget_show_all(GTK_WIDGET(err_window));
233 
234  // Mouse click and focus-loss will delete the err_window.
235  g_signal_connect(G_OBJECT(err_window), "focus-out-event",
236  G_CALLBACK(gtk_window_close), NULL);
237  g_signal_connect(G_OBJECT(err_window), "button-press-event",
238  G_CALLBACK(gtk_window_close), NULL);
239 }
240 
241 #ifdef HAVE_LIBAVAHI_UI
242 
243 static void remmina_file_editor_browse_avahi(GtkWidget *button, RemminaFileEditor *gfe)
244 {
245  TRACE_CALL(__func__);
246  GtkWidget *dialog;
247  gchar *host;
248 
249  dialog = aui_service_dialog_new(_("Choose a Remote Desktop Server"),
250  GTK_WINDOW(gfe),
251  _("_Cancel"), GTK_RESPONSE_CANCEL,
252  _("_OK"), GTK_RESPONSE_ACCEPT,
253  NULL);
254 
255  gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(gfe));
256  aui_service_dialog_set_resolve_service(AUI_SERVICE_DIALOG(dialog), TRUE);
257  aui_service_dialog_set_resolve_host_name(AUI_SERVICE_DIALOG(dialog), TRUE);
258  aui_service_dialog_set_browse_service_types(AUI_SERVICE_DIALOG(dialog),
259  gfe->priv->avahi_service_type, NULL);
260 
261  if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
262  host = g_strdup_printf("[%s]:%i",
263  aui_service_dialog_get_host_name(AUI_SERVICE_DIALOG(dialog)),
264  aui_service_dialog_get_port(AUI_SERVICE_DIALOG(dialog)));
265  } else {
266  host = NULL;
267  }
268  gtk_widget_destroy(dialog);
269 
270  if (host) {
271  gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(gfe->priv->server_combo))), host);
272  g_free(host);
273  }
274 }
275 #endif
276 
277 static void remmina_file_editor_on_realize(GtkWidget *widget, gpointer user_data)
278 {
279  TRACE_CALL(__func__);
280  RemminaFileEditor *gfe;
281  GtkWidget *defaultwidget;
282 
283  gfe = REMMINA_FILE_EDITOR(widget);
284 
285  defaultwidget = gfe->priv->server_combo;
286 
287  if (defaultwidget) {
288  if (GTK_IS_EDITABLE(defaultwidget))
289  gtk_editable_select_region(GTK_EDITABLE(defaultwidget), 0, -1);
290  gtk_widget_grab_focus(defaultwidget);
291  }
292 }
293 
294 static void remmina_file_editor_destroy(GtkWidget *widget, gpointer data)
295 {
296  TRACE_CALL(__func__);
297  remmina_file_free(REMMINA_FILE_EDITOR(widget)->priv->remmina_file);
298  g_hash_table_destroy(REMMINA_FILE_EDITOR(widget)->priv->setting_widgets);
299  g_free(REMMINA_FILE_EDITOR(widget)->priv);
300 }
301 
302 static void remmina_file_editor_button_on_toggled(GtkToggleButton *togglebutton, GtkWidget *widget)
303 {
304  TRACE_CALL(__func__);
305  gtk_widget_set_sensitive(widget, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(togglebutton)));
306 }
307 
309 {
310  TRACE_CALL(__func__);
311  /* Create the notebook */
312  gfe->priv->config_container = gtk_notebook_new();
313  gfe->priv->config_viewport = gtk_viewport_new(NULL, NULL);
314  gfe->priv->config_scrollable = gtk_scrolled_window_new(NULL, NULL);
315  gtk_container_set_border_width(GTK_CONTAINER(gfe->priv->config_scrollable), 2);
316  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(gfe->priv->config_scrollable),
317  GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
318  gtk_widget_show(gfe->priv->config_scrollable);
319 
320  gtk_container_add(GTK_CONTAINER(gfe->priv->config_viewport), gfe->priv->config_container);
321  gtk_container_set_border_width(GTK_CONTAINER(gfe->priv->config_viewport), 2);
322  gtk_widget_show(gfe->priv->config_viewport);
323  gtk_container_add(GTK_CONTAINER(gfe->priv->config_scrollable), gfe->priv->config_viewport);
324  gtk_container_set_border_width(GTK_CONTAINER(gfe->priv->config_container), 2);
325  gtk_widget_show(gfe->priv->config_container);
326 
327  gtk_container_add(GTK_CONTAINER(gfe->priv->config_box), gfe->priv->config_scrollable);
328 }
329 
331  const gchar *stock_id, const gchar *label, gint rows, gint cols)
332 {
333  TRACE_CALL(__func__);
334  GtkWidget *tablabel;
335  GtkWidget *tabbody;
336  GtkWidget *grid;
337  GtkWidget *widget;
338 
339  tablabel = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
340  gtk_widget_show(tablabel);
341 
342  widget = gtk_image_new_from_icon_name(stock_id, GTK_ICON_SIZE_BUTTON);
343  gtk_box_pack_start(GTK_BOX(tablabel), widget, FALSE, FALSE, 0);
344  gtk_widget_show(widget);
345 
346  widget = gtk_label_new(label);
347  gtk_box_pack_start(GTK_BOX(tablabel), widget, FALSE, FALSE, 0);
348  gtk_widget_show(widget);
349 
350  tabbody = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
351  gtk_widget_show(tabbody);
352  gtk_notebook_append_page(GTK_NOTEBOOK(gfe->priv->config_container), tabbody, tablabel);
353 
354  grid = gtk_grid_new();
355  gtk_widget_show(grid);
356  gtk_grid_set_row_spacing(GTK_GRID(grid), 8);
357  gtk_grid_set_column_spacing(GTK_GRID(grid), 8);
358  gtk_container_set_border_width(GTK_CONTAINER(grid), 15);
359  gtk_box_pack_start(GTK_BOX(tabbody), grid, FALSE, FALSE, 0);
360 
361  return grid;
362 }
363 
364 
365 static void remmina_file_editor_assistance_enabled_check_on_toggled(GtkToggleButton *togglebutton,
366  RemminaFileEditor *gfe)
367 {
368  TRACE_CALL(__func__);
369  gboolean enabled = TRUE;
370 
371  if (gfe->priv->assistance_toggle) {
372  enabled = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gfe->priv->assistance_toggle));
373  if (gfe->priv->assistance_file)
374  gtk_widget_set_sensitive(GTK_WIDGET(gfe->priv->assistance_file), enabled);
375  if (gfe->priv->assistance_password)
376  gtk_widget_set_sensitive(GTK_WIDGET(gfe->priv->assistance_password), enabled);
377  if (gfe->priv->assistance_file_label)
378  gtk_widget_set_sensitive(GTK_WIDGET(gfe->priv->assistance_file_label), enabled);
379  if (gfe->priv->assistance_password_label)
380  gtk_widget_set_sensitive(GTK_WIDGET(gfe->priv->assistance_password_label), enabled);
381  }
382 }
383 
384 #ifdef HAVE_LIBSSH
385 
387 {
388  TRACE_CALL(__func__);
389  gtk_widget_set_sensitive(GTK_WIDGET(gfe->priv->ssh_tunnel_server_entry),
390  gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gfe->priv->ssh_tunnel_enabled_check)) &&
391  (gfe->priv->ssh_tunnel_server_custom_radio == NULL ||
392  gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gfe->priv->ssh_tunnel_server_custom_radio))));
393 }
394 
395 
396 static void remmina_file_editor_ssh_tunnel_enabled_check_on_toggled(GtkToggleButton *togglebutton,
398 {
399  TRACE_CALL(__func__);
400  RemminaFileEditorPriv *priv = gfe->priv;
401  gboolean enabled = TRUE;
402  gchar *p;
403  const gchar *cp;
404  const gchar *s = NULL;
405 
406  if (gfe->priv->ssh_tunnel_enabled_check) {
407  enabled = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gfe->priv->ssh_tunnel_enabled_check));
408  if (gfe->priv->ssh_tunnel_loopback_check)
409  gtk_widget_set_sensitive(GTK_WIDGET(gfe->priv->ssh_tunnel_loopback_check), enabled);
410  if (gfe->priv->ssh_tunnel_server_default_radio)
411  gtk_widget_set_sensitive(GTK_WIDGET(gfe->priv->ssh_tunnel_server_default_radio), enabled);
412  if (gfe->priv->ssh_tunnel_server_custom_radio)
413  gtk_widget_set_sensitive(GTK_WIDGET(gfe->priv->ssh_tunnel_server_custom_radio), enabled);
415  p = remmina_public_combo_get_active_text(GTK_COMBO_BOX(priv->protocol_combo));
416  // if (!(g_strcmp0(p, "SFTP") == 0 || g_strcmp0(p, "SSH") == 0)) {
417  gtk_widget_set_sensitive(GTK_WIDGET(gfe->priv->ssh_tunnel_username_entry), enabled);
418  gtk_widget_set_sensitive(GTK_WIDGET(gfe->priv->ssh_tunnel_auth_password), enabled);
419  gtk_widget_set_sensitive(GTK_WIDGET(gfe->priv->ssh_tunnel_auth_combo), enabled);
420  //}
421  g_free(p);
422  }
423  // remmina_file_editor_ssh_tunnel_auth_publickey_radio_on_toggled(NULL, gfe);
424  s = remmina_file_get_string(gfe->priv->remmina_file, "ssh_tunnel_privatekey");
425  if (s)
426  gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(gfe->priv->ssh_tunnel_privatekey_chooser), s);
427  s = remmina_file_get_string(gfe->priv->remmina_file, "ssh_tunnel_certfile");
428  if (s)
429  gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(gfe->priv->ssh_tunnel_certfile_chooser), s);
430 
431  if (gfe->priv->ssh_tunnel_username_entry)
432  if (enabled && gtk_entry_get_text(GTK_ENTRY(gfe->priv->ssh_tunnel_username_entry))[0] == '\0') {
433  cp = remmina_file_get_string(priv->remmina_file, "ssh_tunnel_username");
434  gtk_entry_set_text(GTK_ENTRY(gfe->priv->ssh_tunnel_username_entry), cp ? cp : "");
435  }
436 
437  if (gfe->priv->ssh_tunnel_auth_password) {
438  if (enabled && gtk_entry_get_text(GTK_ENTRY(gfe->priv->ssh_tunnel_auth_password))[0] == '\0') {
439  cp = remmina_file_get_string(priv->remmina_file, "ssh_tunnel_password");
440  gtk_entry_set_text(GTK_ENTRY(gfe->priv->ssh_tunnel_auth_password), cp ? cp : "");
441  }
442  }
443  if (gfe->priv->ssh_tunnel_passphrase) {
444  if (enabled && gtk_entry_get_text(GTK_ENTRY(gfe->priv->ssh_tunnel_passphrase))[0] == '\0') {
445  cp = remmina_file_get_string(priv->remmina_file, "ssh_tunnel_passphrase");
446  gtk_entry_set_text(GTK_ENTRY(gfe->priv->ssh_tunnel_passphrase), cp ? cp : "");
447  }
448  }
449 }
450 
451 #endif
452 
453 static void remmina_file_editor_create_server(RemminaFileEditor *gfe, const RemminaProtocolSetting *setting, GtkWidget *grid,
454  gint row)
455 {
456  TRACE_CALL(__func__);
457  RemminaProtocolPlugin *plugin = gfe->priv->plugin;
458  GtkWidget *widget;
459 #ifdef HAVE_LIBAVAHI_UI
460  GtkWidget *hbox;
461 #endif
462  gchar *s;
463 
464  widget = gtk_label_new(_("Server"));
465  gtk_widget_show(widget);
466  gtk_widget_set_valign(widget, GTK_ALIGN_START);
467  gtk_widget_set_halign(widget, GTK_ALIGN_START);
468  gtk_grid_attach(GTK_GRID(grid), widget, 0, row, 1, row + 1);
469 
470  s = remmina_pref_get_recent(plugin->name);
471  widget = remmina_public_create_combo_entry(s, remmina_file_get_string(gfe->priv->remmina_file, "server"), TRUE);
472  gtk_widget_set_hexpand(widget, TRUE);
473  gtk_widget_show(widget);
474  gtk_widget_set_tooltip_markup(widget, _(server_tips));
475  gtk_entry_set_activates_default(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(widget))), TRUE);
476  gfe->priv->server_combo = widget;
477  g_free(s);
478 
479 #ifdef HAVE_LIBAVAHI_UI
480  if (setting->opt1) {
481  gfe->priv->avahi_service_type = (const gchar *)setting->opt1;
482 
483  hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
484  gtk_widget_show(hbox);
485  gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, TRUE, 0);
486 
487  widget = gtk_button_new_with_label("…");
488  s = g_strdup_printf(_("Browse the network to find a %s server"), plugin->name);
489  gtk_widget_set_tooltip_text(widget, s);
490  g_free(s);
491  gtk_widget_show(widget);
492  gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, FALSE, 0);
493  g_signal_connect(G_OBJECT(widget), "clicked", G_CALLBACK(remmina_file_editor_browse_avahi), gfe);
494 
495  gtk_grid_attach(GTK_GRID(grid), hbox, 1, row, 1, 1);
496  } else
497 #endif
498  {
499  gtk_grid_attach(GTK_GRID(grid), widget, 1, row, 1, 1);
500  }
501 }
502 
503 
504 static GtkWidget *remmina_file_editor_create_password(RemminaFileEditor *gfe, GtkWidget *grid, gint row, gint col, const gchar *label, const gchar *value, gchar *setting_name)
505 {
506  TRACE_CALL(__func__);
507  GtkWidget *widget;
508 
509  widget = gtk_label_new(label);
510  gtk_widget_show(widget);
511 #if GTK_CHECK_VERSION(3, 12, 0)
512  gtk_widget_set_margin_end(widget, 40);
513 #else
514  gtk_widget_set_margin_right(widget, 40);
515 #endif
516  gtk_widget_set_valign(widget, GTK_ALIGN_START);
517  gtk_widget_set_halign(widget, GTK_ALIGN_START);
518  gtk_grid_attach(GTK_GRID(grid), widget, 0, row, 1, 1);
519 
520  widget = gtk_entry_new();
521  gtk_widget_show(widget);
522  gtk_grid_attach(GTK_GRID(grid), widget, 1, row, 1, 1);
523  gtk_entry_set_max_length(GTK_ENTRY(widget), 0);
524  gtk_entry_set_visibility(GTK_ENTRY(widget), FALSE);
525  gtk_widget_set_hexpand(widget, TRUE);
526  gtk_entry_set_activates_default(GTK_ENTRY(widget), TRUE);
527  if (setting_name)
528  gtk_widget_set_name(widget, setting_name);
529 
530  if (value)
531  gtk_entry_set_text(GTK_ENTRY(widget), value);
532  /* Password view Toogle*/
533  if (setting_name) {
534  gtk_entry_set_icon_from_icon_name(GTK_ENTRY(widget), GTK_ENTRY_ICON_SECONDARY, "org.remmina.Remmina-password-reveal-symbolic");
535  gtk_entry_set_icon_activatable(GTK_ENTRY(widget), GTK_ENTRY_ICON_SECONDARY, TRUE);
536  g_signal_connect(widget, "icon-press", G_CALLBACK(remmina_main_toggle_password_view), NULL);
537  }
538  return widget;
539 }
540 
541 static void remmina_file_editor_update_resolution(GtkWidget *widget, RemminaFileEditor *gfe)
542 {
543  TRACE_CALL(__func__);
544  gchar *res_str;
545  res_str = g_strdup_printf("%dx%d",
546  remmina_file_get_int(gfe->priv->remmina_file, "resolution_width", 0),
547  remmina_file_get_int(gfe->priv->remmina_file, "resolution_height", 0));
548  remmina_public_load_combo_text_d(gfe->priv->resolution_custom_combo, remmina_pref.resolutions,
549  res_str, NULL);
550  g_free(res_str);
551 }
552 
553 static void remmina_file_editor_browse_resolution(GtkWidget *button, RemminaFileEditor *gfe)
554 {
555  TRACE_CALL(__func__);
556 
557  GtkDialog *dialog = remmina_string_list_new(FALSE, NULL);
560  remmina_string_list_set_titles(_("Resolutions"), _("Configure the available resolutions"));
561  gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(gfe));
562  gtk_dialog_run(dialog);
563  g_free(remmina_pref.resolutions);
565  g_signal_connect(G_OBJECT(dialog), "destroy", G_CALLBACK(remmina_file_editor_update_resolution), gfe);
566  gtk_widget_destroy(GTK_WIDGET(dialog));
567 }
568 
570  GtkWidget *grid, gint row)
571 {
572  TRACE_CALL(__func__);
573  GtkWidget *widget;
574  GtkWidget *hbox;
575  int resolution_w, resolution_h;
576  gchar *res_str;
578 
579  res_mode = remmina_file_get_int(gfe->priv->remmina_file, "resolution_mode", RES_INVALID);
580  resolution_w = remmina_file_get_int(gfe->priv->remmina_file, "resolution_width", -1);
581  resolution_h = remmina_file_get_int(gfe->priv->remmina_file, "resolution_height", -1);
582 
583  /* If resolution_mode is non-existent (-1), then we try to calculate it
584  * as we did before having resolution_mode */
585  if (res_mode == RES_INVALID) {
586  if (resolution_w <= 0 || resolution_h <= 0)
587  res_mode = RES_USE_INITIAL_WINDOW_SIZE;
588  else
589  res_mode = RES_USE_CUSTOM;
590  }
591  if (res_mode == RES_USE_CUSTOM)
592  res_str = g_strdup_printf("%dx%d", resolution_w, resolution_h);
593  else
594  res_str = NULL;
595 
596  widget = gtk_label_new(_("Resolution"));
597  gtk_widget_show(widget);
598  gtk_widget_set_valign(widget, GTK_ALIGN_START);
599  gtk_widget_set_halign(widget, GTK_ALIGN_START);
600  gtk_grid_attach(GTK_GRID(grid), widget, 0, row, 1, 1);
601 
602  hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
603  widget = gtk_radio_button_new_with_label(NULL, _("Use initial window size"));
604  gtk_widget_show(widget);
605  gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, TRUE, 0);
606  gfe->priv->resolution_iws_radio = widget;
607  widget = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(gfe->priv->resolution_iws_radio), _("Use client resolution"));
608  gtk_widget_show(widget);
609  gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, TRUE, 0);
610  gfe->priv->resolution_auto_radio = widget;
611  gtk_grid_attach(GTK_GRID(grid), hbox, 1, row, 1, 1);
612  gtk_widget_show(hbox);
613 
614  hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
615  gtk_widget_show(hbox);
616  gtk_grid_attach(GTK_GRID(grid), hbox, 1, row + 1, 1, 1);
617 
618  widget = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(gfe->priv->resolution_iws_radio), _("Custom"));
619  gtk_widget_show(widget);
620  gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, FALSE, 0);
621  gfe->priv->resolution_custom_radio = widget;
622 
624  gtk_widget_show(widget);
625  gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, TRUE, 0);
626  gfe->priv->resolution_custom_combo = widget;
627 
628  widget = gtk_button_new_with_label("…");
629  gtk_widget_show(widget);
630  gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, FALSE, 0);
631  g_signal_connect(G_OBJECT(widget), "clicked", G_CALLBACK(remmina_file_editor_browse_resolution), gfe);
632 
633  g_signal_connect(G_OBJECT(gfe->priv->resolution_custom_radio), "toggled",
634  G_CALLBACK(remmina_file_editor_button_on_toggled), gfe->priv->resolution_custom_combo);
635 
636  if (res_mode == RES_USE_CUSTOM)
637  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gfe->priv->resolution_custom_radio), TRUE);
638  else if (res_mode == RES_USE_CLIENT)
639  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gfe->priv->resolution_auto_radio), TRUE);
640  else
641  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gfe->priv->resolution_iws_radio), TRUE);
642 
643  gtk_widget_set_sensitive(gfe->priv->resolution_custom_combo, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gfe->priv->resolution_custom_radio)));
644 
645  g_free(res_str);
646 }
647 
648 
650  GtkWidget *grid, gint row)
651 {
652  TRACE_CALL(__func__);
653  GtkWidget *widget;
654 
655 
656 
657  widget = gtk_toggle_button_new_with_label(_("Assistance Mode"));
658  gtk_widget_set_halign(widget, GTK_ALIGN_START);
659  gtk_widget_show(widget);
660  gtk_grid_attach(GTK_GRID(grid), widget, 0, row, 1, 1);
661  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), remmina_file_get_int(gfe->priv->remmina_file, "assistance_mode", 0));
662  gfe->priv->assistance_toggle = widget;
663  g_signal_connect(widget, "toggled", G_CALLBACK(remmina_file_editor_assistance_enabled_check_on_toggled), gfe);
664 
665 
666  widget = gtk_label_new("Assistance file");
667  gtk_widget_set_halign(widget, GTK_ALIGN_END);
668  gtk_widget_show(widget);
669  gtk_grid_attach(GTK_GRID(grid), widget, 0, row+1, 1, 1);
670  gfe->priv->assistance_file_label = widget;
671 
672  widget = gtk_entry_new();
673  gtk_widget_set_halign(widget, GTK_ALIGN_START);
674  gtk_widget_show(widget);
675  gtk_entry_set_text(GTK_ENTRY(widget), remmina_file_get_string(gfe->priv->remmina_file, "assistance_file"));
676  gtk_grid_attach(GTK_GRID(grid), widget, 1, row+1, 1, 1);
677  gfe->priv->assistance_file = widget;
678 
679  widget = gtk_label_new("Assistance Password");
680  gtk_widget_set_halign(widget, GTK_ALIGN_END);
681  gtk_widget_show(widget);
682  gtk_grid_attach(GTK_GRID(grid), widget, 0, row+2, 1, 1);
683  gfe->priv->assistance_password_label = widget;
684 
685  widget = gtk_entry_new();
686  gtk_widget_set_halign(widget, GTK_ALIGN_START);
687  gtk_widget_show(widget);
688  gtk_entry_set_text(GTK_ENTRY(widget), remmina_file_get_string(gfe->priv->remmina_file, "assistance_pass"));
689  gtk_grid_attach(GTK_GRID(grid), widget, 1, row+2, 1, 1);
690  gfe->priv->assistance_password = widget;
691 
693 
694 }
695 
696 
697 static GtkWidget *remmina_file_editor_create_text2(RemminaFileEditor *gfe, GtkWidget *grid,
698  gint row, gint col, const gchar *label, const gchar *value, gint left,
699  gint right, gchar *setting_name)
700 {
701  TRACE_CALL(__func__);
702  GtkWidget *widget;
703 
704  widget = gtk_label_new(label);
705  gtk_widget_show(widget);
706 #if GTK_CHECK_VERSION(3, 12, 0)
707  gtk_widget_set_margin_start(widget, left);
708  gtk_widget_set_margin_end(widget, right);
709 #else
710  gtk_widget_set_margin_left(widget, left);
711  gtk_widget_set_margin_right(widget, right);
712 #endif
713  gtk_widget_set_valign(widget, GTK_ALIGN_START);
714  gtk_widget_set_halign(widget, GTK_ALIGN_START);
715  gtk_grid_attach(GTK_GRID(grid), widget, col, row, 1, 1);
716 
717  widget = gtk_entry_new();
718  gtk_widget_show(widget);
719  gtk_grid_attach(GTK_GRID(grid), widget, col + 1, row, 1, 1);
720  gtk_entry_set_max_length(GTK_ENTRY(widget), 300);
721  gtk_widget_set_hexpand(widget, TRUE);
722  if (setting_name)
723  gtk_widget_set_name(widget, setting_name);
724 
725  if (value)
726  gtk_entry_set_text(GTK_ENTRY(widget), value);
727 
728  return widget;
729 }
730 
731 static GtkWidget *remmina_file_editor_create_text(RemminaFileEditor *gfe, GtkWidget *grid,
732  gint row, gint col, const gchar *label, const gchar *value,
733  gchar *setting_name)
734 {
735  TRACE_CALL(__func__);
736  return remmina_file_editor_create_text2(gfe, grid, row, col, label, value, 0, 40,
737  setting_name);
738 }
739 
740 static GtkWidget *remmina_file_editor_create_textarea(RemminaFileEditor *gfe, GtkWidget *grid,
741  gint row, gint col, const gchar *label, const gchar *value,
742  gchar *setting_name)
743 {
744  TRACE_CALL(__func__);
745  GtkWidget *widget;
746  GtkTextView *view;
747  GtkTextBuffer *buffer;
748  GtkTextIter start;
749 
750  widget = gtk_text_view_new();
751  view = GTK_TEXT_VIEW(widget);
752  gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(view), GTK_WRAP_WORD);
753  gtk_text_view_set_top_margin(GTK_TEXT_VIEW(view), 20);
754  gtk_text_view_set_bottom_margin(GTK_TEXT_VIEW(view), 20);
755  gtk_text_view_set_left_margin(GTK_TEXT_VIEW(view), 20);
756  gtk_text_view_set_right_margin(GTK_TEXT_VIEW(view), 20);
757  gtk_text_view_set_monospace(view, TRUE);
758  if (setting_name)
759  gtk_widget_set_name(widget, setting_name);
760  if (value) {
761  buffer = gtk_text_view_get_buffer(view);
762  gtk_text_buffer_set_text(buffer, value, -1);
763  gtk_text_buffer_get_start_iter(buffer, &start);
764  gtk_text_buffer_place_cursor(buffer, &start);
765  }
766  gtk_widget_show(widget);
767  gtk_widget_set_hexpand(widget, TRUE);
768  gtk_widget_set_size_request(GTK_WIDGET(view), 320, 300);
769  gtk_grid_attach(GTK_GRID(grid), widget, 0, row, 1, 1);
770  return widget;
771 }
772 
773 static GtkWidget *remmina_file_editor_create_select(RemminaFileEditor *gfe, GtkWidget *grid,
774  gint row, gint col, const gchar *label, const gpointer *list,
775  const gchar *value, gchar *setting_name)
776 {
777  TRACE_CALL(__func__);
778  GtkWidget *widget;
779 
780  widget = gtk_label_new(label);
781  gtk_widget_show(widget);
782  gtk_widget_set_valign(widget, GTK_ALIGN_START);
783  gtk_widget_set_halign(widget, GTK_ALIGN_START);
784  if (setting_name)
785  gtk_widget_set_name(widget, setting_name);
786  gtk_grid_attach(GTK_GRID(grid), widget, 0, row, 1, 1);
787 
788  widget = remmina_public_create_combo_map(list, value, FALSE, gfe->priv->plugin->domain);
789  gtk_widget_show(widget);
790  gtk_grid_attach(GTK_GRID(grid), widget, 1, row, 1, 1);
791 
792  return widget;
793 }
794 
795 static GtkWidget *remmina_file_editor_create_combo(RemminaFileEditor *gfe, GtkWidget *grid,
796  gint row, gint col, const gchar *label, const gchar *list,
797  const gchar *value, gchar *setting_name)
798 {
799  TRACE_CALL(__func__);
800  GtkWidget *widget;
801 
802  widget = gtk_label_new(label);
803  gtk_widget_show(widget);
804  gtk_widget_set_valign(widget, GTK_ALIGN_START);
805  gtk_widget_set_halign(widget, GTK_ALIGN_START);
806  gtk_grid_attach(GTK_GRID(grid), widget, 0, row, 1, 1);
807 
808  widget = remmina_public_create_combo_entry(list, value, FALSE);
809  gtk_widget_show(widget);
810  gtk_widget_set_hexpand(widget, TRUE);
811  if (setting_name)
812  gtk_widget_set_name(widget, setting_name);
813  gtk_grid_attach(GTK_GRID(grid), widget, 1, row, 1, 1);
814 
815  return widget;
816 }
817 
818 static GtkWidget *remmina_file_editor_create_check(RemminaFileEditor *gfe, GtkWidget *grid,
819  gint row, gint top, const gchar *label, gboolean value,
820  gchar *setting_name)
821 {
822  TRACE_CALL(__func__);
823  GtkWidget *widget;
824  widget = gtk_check_button_new_with_label(label);
825  gtk_widget_show(widget);
826  if (setting_name)
827  gtk_widget_set_name(widget, setting_name);
828  gtk_grid_attach(GTK_GRID(grid), widget, top, row, 1, 1);
829 
830  if (value)
831  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), TRUE);
832 
833  return widget;
834 }
835 
845 static GtkWidget *
846 remmina_file_editor_create_chooser(RemminaFileEditor *gfe, GtkWidget *grid, gint row, gint col, const gchar *label,
847  const gchar *value, gint type, gchar *setting_name)
848 {
849  TRACE_CALL(__func__);
850  GtkWidget *check;
851  GtkWidget *widget;
852  GtkWidget *hbox;
853 
854  widget = gtk_label_new(label);
855  gtk_widget_show(widget);
856  gtk_widget_set_valign(widget, GTK_ALIGN_START);
857  gtk_widget_set_halign(widget, GTK_ALIGN_START);
858  gtk_grid_attach(GTK_GRID(grid), widget, 0, row, 1, 1);
859 
860  hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
861  gtk_widget_show(hbox);
862  gtk_grid_attach(GTK_GRID(grid), hbox, 1, row, 1, 1);
863 
864  check = gtk_check_button_new();
865  gtk_widget_show(check);
866  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), (value && value[0] == '/'));
867  gtk_box_pack_start(GTK_BOX(hbox), check, FALSE, FALSE, 0);
868 
869  widget = gtk_file_chooser_button_new(label, type);
870  if (setting_name)
871  gtk_widget_set_name(widget, setting_name);
872  gtk_widget_show(widget);
873  if (value)
874  gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(widget), value);
875  gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, TRUE, 0);
876 
877  g_signal_connect(G_OBJECT(check), "toggled", G_CALLBACK(remmina_file_editor_button_on_toggled), widget);
878  remmina_file_editor_button_on_toggled(GTK_TOGGLE_BUTTON(check), widget);
879 
880  return widget;
881 }
882 
883 // used to filter out invalid characters for REMMINA_PROTOCOL_SETTING_TYPE_INT
884 void remmina_file_editor_int_setting_filter(GtkEditable *editable, const gchar *text,
885  gint length, gint *position, gpointer data)
886 {
887  for (int i = 0; i < length; i++) {
888  if (!isdigit(text[i]) && text[i] != '-') {
889  g_signal_stop_emission_by_name(G_OBJECT(editable), "insert-text");
890  return;
891  }
892  }
893 }
894 
895 // used to filter out invalid characters for REMMINA_PROTOCOL_SETTING_TYPE_DOUBLE
896 // '.' and ',' can't be used interchangeably! It depends on the language setting
897 // of the user.
898 void remmina_file_editor_double_setting_filter(GtkEditable *editable, const gchar *text,
899  gint length, gint *position, gpointer data)
900 {
901  for (int i = 0; i < length; i++) {
902  if (!isdigit(text[i]) && text[i] != '-' && text[i] != '.' && text[i] != ',') {
903  g_signal_stop_emission_by_name(G_OBJECT(editable), "insert-text");
904  return;
905  }
906  }
907 }
908 
909 static GtkWidget *remmina_file_editor_create_int(RemminaFileEditor *gfe, GtkWidget *grid,
910  gint row, gint col, const gchar *label, const gint value,
911  gint left, gint right, gchar *setting_name)
912 {
913  TRACE_CALL(__func__);
914  GtkWidget *widget;
915 
916  widget = gtk_label_new(label);
917  gtk_widget_show(widget);
918 #if GTK_CHECK_VERSION(3, 12, 0)
919  gtk_widget_set_margin_start(widget, left);
920  gtk_widget_set_margin_end(widget, right);
921 #else
922  gtk_widget_set_margin_left(widget, left);
923  gtk_widget_set_margin_right(widget, right);
924 #endif
925  gtk_widget_set_valign(widget, GTK_ALIGN_START);
926  gtk_widget_set_halign(widget, GTK_ALIGN_START);
927  gtk_grid_attach(GTK_GRID(grid), widget, col, row, 1, 1);
928 
929  widget = gtk_entry_new();
930  gtk_widget_show(widget);
931  gtk_grid_attach(GTK_GRID(grid), widget, col + 1, row, 1, 1);
932  gtk_entry_set_max_length(GTK_ENTRY(widget), 300);
933  gtk_widget_set_hexpand(widget, TRUE);
934  if (setting_name)
935  gtk_widget_set_name(widget, setting_name);
936 
937  // Convert int to str.
938  int length = snprintf(NULL, 0, "%d", value) + 1; // +1 '\0' byte
939  char *str = malloc(length);
940  snprintf(str, length, "%d", value);
941 
942  gtk_entry_set_text(GTK_ENTRY(widget), str);
943  free(str);
944 
945  g_signal_connect(G_OBJECT(widget), "insert-text",
946  G_CALLBACK(remmina_file_editor_int_setting_filter), NULL);
947 
948  return widget;
949 }
950 
952  GtkWidget *grid, gint row, gint col,
953  const gchar *label, gdouble value, gint left,
954  gint right, gchar *setting_name)
955 {
956  TRACE_CALL(__func__);
957  GtkWidget *widget;
958 
959  widget = gtk_label_new(label);
960  gtk_widget_show(widget);
961 #if GTK_CHECK_VERSION(3, 12, 0)
962  gtk_widget_set_margin_start(widget, left);
963  gtk_widget_set_margin_end(widget, right);
964 #else
965  gtk_widget_set_margin_left(widget, left);
966  gtk_widget_set_margin_right(widget, right);
967 #endif
968  gtk_widget_set_valign(widget, GTK_ALIGN_START);
969  gtk_widget_set_halign(widget, GTK_ALIGN_START);
970  gtk_grid_attach(GTK_GRID(grid), widget, col, row, 1, 1);
971 
972  widget = gtk_entry_new();
973  gtk_widget_show(widget);
974  gtk_grid_attach(GTK_GRID(grid), widget, col + 1, row, 1, 1);
975  gtk_entry_set_max_length(GTK_ENTRY(widget), 300);
976  gtk_widget_set_hexpand(widget, TRUE);
977  if (setting_name)
978  gtk_widget_set_name(widget, setting_name);
979 
980  // Convert double to str.
981  int length = snprintf(NULL, 0, "%.8g", value) + 1; // +1 '\0' byte
982  char *str = malloc(length);
983  snprintf(str, length, "%f", value);
984 
985  gtk_entry_set_text(GTK_ENTRY(widget), str);
986  free(str);
987 
988  g_signal_connect(G_OBJECT(widget), "insert-text",
990 
991  return widget;
992 }
993 
994 
995 
996 static void remmina_file_editor_create_settings(RemminaFileEditor *gfe, GtkWidget *grid,
997  const RemminaProtocolSetting *settings)
998 {
999  TRACE_CALL(__func__);
1000  RemminaFileEditorPriv *priv = gfe->priv;
1001  GtkWidget *widget;
1002  gint grid_row = 0;
1003  gint grid_column = 0;
1004  gchar **strarr;
1005  gchar *setting_name;
1006  const gchar *escaped;
1007 
1008  while (settings->type != REMMINA_PROTOCOL_SETTING_TYPE_END) {
1009  setting_name = (gchar *)(remmina_plugin_manager_get_canonical_setting_name(settings));
1010  switch (settings->type) {
1012  remmina_file_editor_create_server(gfe, settings, grid, grid_row);
1013  break;
1014 
1016  widget = remmina_file_editor_create_password(gfe, grid, grid_row, 0,
1017  g_dgettext(priv->plugin->domain, settings->label),
1018  remmina_file_get_string(priv->remmina_file, setting_name),
1019  setting_name);
1020  g_hash_table_insert(priv->setting_widgets, setting_name, widget);
1021  grid_row++;
1022  break;
1023 
1025  remmina_file_editor_create_resolution(gfe, settings, grid, grid_row);
1026  grid_row ++;
1027  break;
1028 
1030  remmina_file_editor_create_assistance(gfe, settings, grid, grid_row);
1031  grid_row += 3;
1032  break;
1033 
1035  strarr = remmina_pref_keymap_groups();
1036  priv->keymap_combo = remmina_file_editor_create_select(gfe, grid,
1037  grid_row + 1, 0,
1038  _("Keyboard mapping"), (const gpointer *)strarr,
1039  remmina_file_get_string(priv->remmina_file, "keymap"),
1040  setting_name);
1041  g_strfreev(strarr);
1042  grid_row++;
1043  break;
1044 
1046  widget = remmina_file_editor_create_text(gfe, grid, grid_row, 0,
1047  g_dgettext(priv->plugin->domain, settings->label),
1048  remmina_file_get_string(priv->remmina_file, setting_name),
1049  setting_name);
1050  g_hash_table_insert(priv->setting_widgets, setting_name, widget);
1051  if (settings->opt2)
1052  gtk_widget_set_tooltip_text(widget, _((const gchar *)settings->opt2));
1053  grid_row++;
1054  break;
1055 
1057  escaped = remmina_file_get_string(priv->remmina_file, setting_name);
1058  escaped = g_uri_unescape_string(escaped, NULL);
1059  widget = remmina_file_editor_create_textarea(gfe, grid, grid_row, 0,
1060  g_dgettext(priv->plugin->domain, settings->label), escaped,
1061  setting_name);
1062  g_hash_table_insert(priv->setting_widgets, setting_name, widget);
1063  grid_row++;
1064  break;
1065 
1067  widget = remmina_file_editor_create_select(gfe, grid, grid_row, 0,
1068  g_dgettext(priv->plugin->domain, settings->label),
1069  (const gpointer *)settings->opt1,
1070  remmina_file_get_string(priv->remmina_file, setting_name),
1071  setting_name);
1072  g_hash_table_insert(priv->setting_widgets, setting_name, widget);
1073  if (settings->opt2)
1074  gtk_widget_set_tooltip_text(widget, _((const gchar *)settings->opt2));
1075  break;
1076 
1078  widget = remmina_file_editor_create_combo(gfe, grid, grid_row, 0,
1079  g_dgettext(priv->plugin->domain, settings->label),
1080  (const gchar *)settings->opt1,
1081  remmina_file_get_string(priv->remmina_file, setting_name),
1082  setting_name);
1083  g_hash_table_insert(priv->setting_widgets, setting_name, widget);
1084  if (settings->opt2)
1085  gtk_widget_set_tooltip_text(widget, _((const gchar *)settings->opt2));
1086  break;
1087 
1089  widget = remmina_file_editor_create_check(gfe, grid, grid_row, grid_column,
1090  g_dgettext(priv->plugin->domain, settings->label),
1091  remmina_file_get_int(priv->remmina_file, setting_name, FALSE),
1092  setting_name);
1093  g_hash_table_insert(priv->setting_widgets, setting_name, widget);
1094  if (settings->opt2)
1095  gtk_widget_set_tooltip_text(widget, _((const gchar *)settings->opt2));
1096  break;
1097 
1099  widget = remmina_file_editor_create_chooser(gfe, grid, grid_row, 0,
1100  g_dgettext(priv->plugin->domain, settings->label),
1101  remmina_file_get_string(priv->remmina_file, setting_name),
1102  GTK_FILE_CHOOSER_ACTION_OPEN, setting_name);
1103  g_hash_table_insert(priv->setting_widgets, setting_name, widget);
1104  if (settings->opt2)
1105  gtk_widget_set_tooltip_text(widget, _((const gchar *)settings->opt2));
1106  break;
1107 
1109  widget = remmina_file_editor_create_chooser(gfe, grid, grid_row, 0,
1110  g_dgettext(priv->plugin->domain, settings->label),
1111  remmina_file_get_string(priv->remmina_file, setting_name),
1112  GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
1113  setting_name);
1114  g_hash_table_insert(priv->setting_widgets, setting_name, widget);
1115  if (settings->opt2)
1116  gtk_widget_set_tooltip_text(widget, _((const gchar *)settings->opt2));
1117  break;
1119  widget = remmina_file_editor_create_int(gfe, grid, grid_row, 0,
1120  g_dgettext(priv->plugin->domain, settings->label),
1121  remmina_file_get_int(priv->remmina_file, setting_name, 0),
1122  0, 40, setting_name);
1123  g_hash_table_insert(priv->setting_widgets, setting_name, widget);
1124  if (settings->opt2)
1125  gtk_widget_set_tooltip_text(widget, _((const gchar *)settings->opt2));
1126  grid_row++;
1127  break;
1129  widget = remmina_file_editor_create_double(gfe, grid, grid_row, 0,
1130  g_dgettext(priv->plugin->domain, settings->label),
1131  remmina_file_get_double(priv->remmina_file, setting_name, 0.0f),
1132  0, 40, setting_name);
1133  g_hash_table_insert(priv->setting_widgets, setting_name, widget);
1134  if (settings->opt2)
1135  gtk_widget_set_tooltip_text(widget, _((const gchar *)settings->opt2));
1136  grid_row++;
1137  break;
1138 
1139  default:
1140  break;
1141  }
1142  /* If the setting wants compactness, move to the next column */
1143  if (settings->compact)
1144  grid_column++;
1145  /* Add a new settings row and move to the first column
1146  * if the setting doesn’t want the compactness
1147  * or we already have two columns */
1148  if (!settings->compact || grid_column > 1) {
1149  grid_row++;
1150  grid_column = 0;
1151  }
1152  settings++;
1153  }
1154 }
1155 
1157 {
1158  TRACE_CALL(__func__);
1159  RemminaFileEditorPriv *priv = gfe->priv;
1160  GtkWidget *grid;
1161  GtkWidget *widget;
1162  const gchar *cs;
1163 
1164  /* The Behavior tab (implementation) */
1165  grid = remmina_file_editor_create_notebook_tab(gfe, NULL, _("Behavior"), 20, 2);
1166 
1167  /* Execute Command frame */
1168  remmina_public_create_group(GTK_GRID(grid), _("Execute a Command"), 0, 1, 2);
1169 
1170  /* PRE connection command */
1171  cs = remmina_file_get_string(priv->remmina_file, "precommand");
1172  widget = remmina_file_editor_create_text2(gfe, grid, 2, 0, _("Before connecting"), cs, 24, 26, "precommand");
1173  priv->behavior_precommand_entry = widget;
1174  gtk_entry_set_placeholder_text(GTK_ENTRY(widget), _("command %h %u %t %U %p %g --option"));
1175  gtk_widget_set_tooltip_markup(widget, _(cmd_tips));
1176 
1177  /* POST connection command */
1178  cs = remmina_file_get_string(priv->remmina_file, "postcommand");
1179  widget = remmina_file_editor_create_text2(gfe, grid, 3, 0, _("After connecting"), cs, 24, 16, "postcommand");
1180  priv->behavior_postcommand_entry = widget;
1181  gtk_entry_set_placeholder_text(GTK_ENTRY(widget), _("/path/to/command -opt1 arg %h %u %t -opt2 %U %p %g"));
1182  gtk_widget_set_tooltip_markup(widget, _(cmd_tips));
1183 
1184  /* Startup frame */
1185  remmina_public_create_group(GTK_GRID(grid), _("Start-up"), 4, 1, 2);
1186 
1187  /* Autostart profile option */
1188  priv->behavior_autostart_check = remmina_file_editor_create_check(gfe, grid, 6, 1, _("Auto-start this profile"),
1189  remmina_file_get_int(priv->remmina_file, "enable-autostart", FALSE), "enable-autostart");
1190 
1191  /* Startup frame */
1192  remmina_public_create_group(GTK_GRID(grid), _("Connection profile security"), 8, 1, 2);
1193 
1194  /* Autostart profile option */
1195  priv->behavior_lock_check = remmina_file_editor_create_check(gfe, grid, 10, 1, _("Require password to connect or edit the profile"),
1196  remmina_file_get_int(priv->remmina_file, "profile-lock", FALSE), "profile-lock");
1197 
1198  /* Startup frame */
1199  remmina_public_create_group(GTK_GRID(grid), _("Unexpected disconnect"), 12, 1, 2);
1200 
1201  /* Autostart profile option */
1202  priv->behavior_disconnect = remmina_file_editor_create_check(gfe, grid, 16, 1, _("Keep window from closing if not disconnected by Remmina"),
1203  remmina_file_get_int(priv->remmina_file, "disconnect-prompt", FALSE), "disconnect-prompt");
1204 }
1205 
1206 #ifdef HAVE_LIBSSH
1207 static gpointer ssh_tunnel_auth_list[] =
1208 {
1209  "0", N_("Password"),
1210  "1", N_("SSH identity file"),
1211  "2", N_("SSH agent"),
1212  "3", N_("Public key (automatic)"),
1213  "4", N_("Kerberos (GSSAPI)"),
1214  NULL
1215 };
1216 #endif
1217 
1219 {
1220  TRACE_CALL(__func__);
1221 #ifdef HAVE_LIBSSH
1222  RemminaFileEditorPriv *priv = gfe->priv;
1223  GtkWidget *grid;
1224  GtkWidget *widget;
1225  const gchar *cs;
1226  gchar *s;
1227  gchar *p;
1228  gint row = 0;
1229 
1230  if (ssh_setting == REMMINA_PROTOCOL_SSH_SETTING_NONE)
1231  return;
1232 
1233  /* The SSH tab (implementation) */
1234  grid = remmina_file_editor_create_notebook_tab(gfe, NULL,
1235  _("SSH Tunnel"), 9, 3);
1236  widget = gtk_toggle_button_new_with_label(_("Enable SSH tunnel"));
1237  gtk_widget_set_halign(widget, GTK_ALIGN_START);
1238  gtk_grid_attach(GTK_GRID(grid), widget, 0, row, 1, 1);
1239  g_signal_connect(G_OBJECT(widget), "toggled",
1241  priv->ssh_tunnel_enabled_check = widget;
1242 
1243  widget = gtk_check_button_new_with_label(_("Tunnel via loopback address"));
1244  gtk_grid_attach(GTK_GRID(grid), widget, 1, row, 2, 1);
1245  priv->ssh_tunnel_loopback_check = widget;
1246 
1247  // 1
1248  row++;
1249  /* SSH Server group */
1250 
1251  switch (ssh_setting) {
1253  s = g_strdup_printf(_("Same server at port %i"), DEFAULT_SSH_PORT);
1254  widget = gtk_radio_button_new_with_label(NULL, s);
1255  g_free(s);
1256  gtk_grid_attach(GTK_GRID(grid), widget, 0, row, 3, 1);
1257  priv->ssh_tunnel_server_default_radio = widget;
1258  // 2
1259  row++;
1260 
1261  widget = gtk_radio_button_new_with_label_from_widget(
1262  GTK_RADIO_BUTTON(priv->ssh_tunnel_server_default_radio), _("Custom"));
1263  gtk_grid_attach(GTK_GRID(grid), widget, 0, row, 1, 1);
1264  g_signal_connect(G_OBJECT(widget), "toggled",
1266  priv->ssh_tunnel_server_custom_radio = widget;
1267 
1268  widget = gtk_entry_new();
1269  gtk_entry_set_max_length(GTK_ENTRY(widget), 100);
1270  gtk_widget_set_tooltip_markup(widget, _(server_tips2));
1271  gtk_grid_attach(GTK_GRID(grid), widget, 1, row, 2, 1);
1272  priv->ssh_tunnel_server_entry = widget;
1273  // 3
1274  row++;
1275  break;
1276 
1278  priv->ssh_tunnel_server_default_radio = NULL;
1279  priv->ssh_tunnel_server_custom_radio = NULL;
1280 
1281  priv->ssh_tunnel_server_entry = remmina_file_editor_create_text(gfe, grid, 1, 0,
1282  _("Server"), NULL, "ssh_reverse_tunnel_server");
1283  gtk_widget_set_tooltip_markup(priv->ssh_tunnel_server_entry, _(server_tips));
1284  // 2
1285  row++;
1286  break;
1289  priv->ssh_tunnel_server_default_radio = NULL;
1290  priv->ssh_tunnel_server_custom_radio = NULL;
1291  priv->ssh_tunnel_server_entry = NULL;
1292 
1293  break;
1294 
1295  default:
1296  break;
1297  }
1298 
1299  /* This is not used? */
1300  p = remmina_public_combo_get_active_text(GTK_COMBO_BOX(priv->protocol_combo));
1301  if (ssh_setting == REMMINA_PROTOCOL_SSH_SETTING_SFTP) {
1302  widget = remmina_file_editor_create_text(gfe, grid, row, 1,
1303  _("Start-up path"), NULL, "start-up-path");
1304  cs = remmina_file_get_string(priv->remmina_file, "execpath");
1305  gtk_entry_set_text(GTK_ENTRY(widget), cs ? cs : "");
1306  g_hash_table_insert(priv->setting_widgets, "execpath", widget);
1307  // 2
1308  row++;
1309  }
1310 
1311  /* SSH Authentication frame */
1312  remmina_public_create_group(GTK_GRID(grid), _("SSH Authentication"), row, 6, 1);
1313  // 5
1314  row += 2;
1315 
1316  priv->ssh_tunnel_auth_combo = remmina_file_editor_create_select(gfe, grid, row, 0,
1317  _("Authentication type"),
1318  (const gpointer *)ssh_tunnel_auth_list,
1319  remmina_file_get_string(priv->remmina_file, "ssh_tunnel_auth"), "ssh_tunnel_auth");
1320  row++;
1321 
1322  if (ssh_setting == REMMINA_PROTOCOL_SSH_SETTING_TUNNEL ||
1324  priv->ssh_tunnel_username_entry =
1325  remmina_file_editor_create_text(gfe, grid, row, 0,
1326  _("Username"), NULL, "ssh_tunnel_username");
1327  // 5
1328  row++;
1329  }
1330 
1331  widget = remmina_file_editor_create_password(gfe, grid, row, 0,
1332  _("Password"),
1333  remmina_file_get_string(priv->remmina_file, "ssh_tunnel_password"),
1334  "ssh_tunnel_password");
1335  priv->ssh_tunnel_auth_password = widget;
1336  row++;
1337 
1338  priv->ssh_tunnel_privatekey_chooser = remmina_file_editor_create_chooser(gfe, grid, row, 0,
1339  _("SSH private key file"),
1340  remmina_file_get_string(priv->remmina_file, "ssh_tunnel_privatekey"),
1341  GTK_FILE_CHOOSER_ACTION_OPEN, "ssh_tunnel_privatekey");
1342  row++;
1343 
1344  priv->ssh_tunnel_certfile_chooser = remmina_file_editor_create_chooser(gfe, grid, row, 0,
1345  _("SSH certificate file"),
1346  remmina_file_get_string(priv->remmina_file, "ssh_tunnel_certfile"),
1347  GTK_FILE_CHOOSER_ACTION_OPEN, "ssh_tunnel_certfile");
1348  row++;
1349 
1350  widget = gtk_label_new(_("Password to unlock private key"));
1351  gtk_grid_attach(GTK_GRID(grid), widget, 0, row, 1, 1);
1352  widget = gtk_entry_new();
1353  gtk_grid_attach(GTK_GRID(grid), widget, 1, row, 2, 1);
1354  gtk_entry_set_max_length(GTK_ENTRY(widget), 300);
1355  gtk_entry_set_visibility(GTK_ENTRY(widget), FALSE);
1356  gtk_widget_set_hexpand(widget, TRUE);
1357  priv->ssh_tunnel_passphrase = widget;
1358  row++;
1359 
1360  /* Set the values */
1361  cs = remmina_file_get_string(priv->remmina_file, "ssh_tunnel_server");
1362  if (ssh_setting == REMMINA_PROTOCOL_SSH_SETTING_TUNNEL) {
1363  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->ssh_tunnel_enabled_check),
1364  remmina_file_get_int(priv->remmina_file, "ssh_tunnel_enabled", FALSE));
1365  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->ssh_tunnel_loopback_check),
1366  remmina_file_get_int(priv->remmina_file, "ssh_tunnel_loopback", FALSE));
1367 
1368  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cs ? priv->ssh_tunnel_server_custom_radio : priv->ssh_tunnel_server_default_radio), TRUE);
1369  gtk_entry_set_text(GTK_ENTRY(priv->ssh_tunnel_server_entry),
1370  cs ? cs : "");
1371  } else if (ssh_setting == REMMINA_PROTOCOL_SSH_SETTING_REVERSE_TUNNEL) {
1372  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->ssh_tunnel_enabled_check),
1373  remmina_file_get_int(priv->remmina_file, "ssh_tunnel_enabled", FALSE));
1374  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->ssh_tunnel_loopback_check),
1375  remmina_file_get_int(priv->remmina_file, "ssh_tunnel_loopback", FALSE));
1376  gtk_entry_set_text(GTK_ENTRY(priv->ssh_tunnel_server_entry),
1377  cs ? cs : "");
1378  }
1379 
1381  gtk_widget_show_all(grid);
1382  g_free(p);
1383 #endif
1384 }
1385 
1387 {
1388  TRACE_CALL(__func__);
1389  RemminaFileEditorPriv *priv = gfe->priv;
1390  GtkWidget *grid;
1391 
1392  static const RemminaProtocolSetting notes_settings[] =
1393  {
1394  { REMMINA_PROTOCOL_SETTING_TYPE_TEXTAREA, "notes_text", NULL, FALSE, NULL, NULL },
1395  { REMMINA_PROTOCOL_SETTING_TYPE_END, NULL, NULL, FALSE, NULL, NULL }
1396  };
1397 
1399 
1400  /* The Basic tab */
1401  if (priv->plugin->basic_settings) {
1402  grid = remmina_file_editor_create_notebook_tab(gfe, NULL, _("Basic"), 20, 2);
1403  remmina_file_editor_create_settings(gfe, grid, priv->plugin->basic_settings);
1404  }
1405 
1406  /* The Advanced tab */
1407  if (priv->plugin->advanced_settings) {
1408  grid = remmina_file_editor_create_notebook_tab(gfe, NULL, _("Advanced"), 20, 2);
1409  remmina_file_editor_create_settings(gfe, grid, priv->plugin->advanced_settings);
1410  }
1411 
1412  /* The Behavior tab */
1414 
1415  /* The SSH tab */
1416  remmina_file_editor_create_ssh_tunnel_tab(gfe, priv->plugin->ssh_setting);
1417 
1418  /* Notes tab */
1419  grid = remmina_file_editor_create_notebook_tab(gfe, NULL, _("Notes"), 1, 1);
1420  remmina_file_editor_create_settings(gfe, grid, notes_settings);
1421 }
1422 
1424 {
1425  TRACE_CALL(__func__);
1426  RemminaFileEditorPriv *priv = gfe->priv;
1427  gchar *protocol;
1428 
1429  if (priv->config_container) {
1430  gtk_widget_destroy(priv->config_container);
1431  priv->config_container = NULL;
1432  gtk_widget_destroy(priv->config_viewport);
1433  priv->config_viewport = NULL;
1434  gtk_widget_destroy(priv->config_scrollable);
1435  priv->config_scrollable = NULL;
1436  }
1437 
1438  priv->server_combo = NULL;
1439  priv->resolution_iws_radio = NULL;
1440  priv->resolution_auto_radio = NULL;
1441  priv->resolution_custom_radio = NULL;
1442  priv->resolution_custom_combo = NULL;
1443  priv->keymap_combo = NULL;
1444 
1445  priv->ssh_tunnel_enabled_check = NULL;
1446  priv->ssh_tunnel_loopback_check = NULL;
1447  priv->ssh_tunnel_server_default_radio = NULL;
1448  priv->ssh_tunnel_server_custom_radio = NULL;
1449  priv->ssh_tunnel_server_entry = NULL;
1450  priv->ssh_tunnel_username_entry = NULL;
1451  priv->ssh_tunnel_auth_combo = NULL;
1452  priv->ssh_tunnel_auth_password = NULL;
1453  priv->ssh_tunnel_privatekey_chooser = NULL;
1454  priv->ssh_tunnel_certfile_chooser = NULL;
1455 
1456  g_hash_table_remove_all(priv->setting_widgets);
1457 
1458  protocol = remmina_public_combo_get_active_text(combo);
1459  if (protocol) {
1461  protocol);
1462  g_free(protocol);
1464  }
1465 }
1466 
1468 {
1469  TRACE_CALL(__func__);
1470  RemminaFileEditorPriv *priv = gfe->priv;
1471 
1472  remmina_file_set_string(priv->remmina_file, "precommand", gtk_entry_get_text(GTK_ENTRY(priv->behavior_precommand_entry)));
1473  remmina_file_set_string(priv->remmina_file, "postcommand", gtk_entry_get_text(GTK_ENTRY(priv->behavior_postcommand_entry)));
1474 
1475  gboolean autostart_enabled = (priv->behavior_autostart_check ? gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->behavior_autostart_check)) : FALSE);
1476  remmina_file_set_int(priv->remmina_file, "enable-autostart", autostart_enabled);
1477  gboolean lock_enabled = (priv->behavior_lock_check ? gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->behavior_lock_check)) : FALSE);
1478  remmina_file_set_int(priv->remmina_file, "profile-lock", lock_enabled);
1479  gboolean disconect_prompt = (priv->behavior_disconnect ? gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->behavior_disconnect)) : FALSE);
1480  remmina_file_set_int(priv->remmina_file, "disconnect-prompt", disconect_prompt);
1481 }
1482 
1484 {
1485  TRACE_CALL(__func__);
1486  RemminaFileEditorPriv *priv = gfe->priv;
1487  gboolean ssh_tunnel_enabled;
1488  int ssh_tunnel_auth;
1489 
1490  ssh_tunnel_enabled = (priv->ssh_tunnel_enabled_check ? gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->ssh_tunnel_enabled_check)) : FALSE);
1491  remmina_file_set_int(priv->remmina_file,
1492  "ssh_tunnel_loopback",
1493  (priv->ssh_tunnel_loopback_check ? gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->ssh_tunnel_loopback_check)) : FALSE));
1494  remmina_file_set_int(priv->remmina_file, "ssh_tunnel_enabled", ssh_tunnel_enabled);
1495  remmina_file_set_string(priv->remmina_file, "ssh_tunnel_auth",
1496  remmina_public_combo_get_active_text(GTK_COMBO_BOX(priv->ssh_tunnel_auth_combo)));
1497  remmina_file_set_string(priv->remmina_file, "ssh_tunnel_username",
1498  (ssh_tunnel_enabled ? gtk_entry_get_text(GTK_ENTRY(priv->ssh_tunnel_username_entry)) : NULL));
1500  priv->remmina_file,
1501  "ssh_tunnel_server",
1502  (ssh_tunnel_enabled && priv->ssh_tunnel_server_entry && (priv->ssh_tunnel_server_custom_radio == NULL || gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->ssh_tunnel_server_custom_radio))) ? gtk_entry_get_text(GTK_ENTRY(priv->ssh_tunnel_server_entry)) : NULL));
1503 
1504  ssh_tunnel_auth = gtk_combo_box_get_active(GTK_COMBO_BOX(priv->ssh_tunnel_auth_combo));
1505 
1507  priv->remmina_file,
1508  "ssh_tunnel_auth",
1509  ssh_tunnel_auth);
1510 
1511  // If box is unchecked for private key and certfile file choosers,
1512  // set the string to NULL in the remmina file
1513  if (gtk_widget_get_sensitive(priv->ssh_tunnel_privatekey_chooser)) {
1515  priv->remmina_file,
1516  "ssh_tunnel_privatekey",
1517  (priv->ssh_tunnel_privatekey_chooser ? gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(priv->ssh_tunnel_privatekey_chooser)) : NULL));
1518  }
1519  else {
1520  remmina_file_set_string(priv->remmina_file, "ssh_tunnel_privatekey", NULL);
1521  }
1522 
1523  if (gtk_widget_get_sensitive(priv->ssh_tunnel_certfile_chooser)) {
1525  priv->remmina_file,
1526  "ssh_tunnel_certfile",
1527  (priv->ssh_tunnel_certfile_chooser ? gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(priv->ssh_tunnel_certfile_chooser)) : NULL));
1528  }
1529  else {
1530  remmina_file_set_string(priv->remmina_file, "ssh_tunnel_certfile", NULL);
1531  }
1532 
1534  priv->remmina_file,
1535  "ssh_tunnel_password",
1536  (ssh_tunnel_enabled && (ssh_tunnel_auth == SSH_AUTH_PASSWORD)) ? gtk_entry_get_text(GTK_ENTRY(priv->ssh_tunnel_auth_password)) : NULL);
1537 
1539  priv->remmina_file,
1540  "ssh_tunnel_passphrase",
1541  (ssh_tunnel_enabled && (ssh_tunnel_auth == SSH_AUTH_PUBLICKEY || ssh_tunnel_auth == SSH_AUTH_AUTO_PUBLICKEY)) ? gtk_entry_get_text(GTK_ENTRY(priv->ssh_tunnel_passphrase)) : NULL);
1542 }
1543 
1545  gchar * setting_name_to_validate,
1546  gconstpointer value,
1547  GError ** err)
1548 {
1549  if (!setting_name_to_validate || !value || !gfe) {
1550  if (!setting_name_to_validate) {
1551  g_critical(_("(%s: %i): Can't validate setting '%s' since 'value' or 'gfe' "
1552  "are NULL!"),
1553  __func__, __LINE__, setting_name_to_validate);
1554  } else {
1555  g_critical(_("(%s: %i): Can't validate user input since "
1556  "'setting_name_to_validate', 'value' or 'gfe' are NULL!"),
1557  __func__, __LINE__);
1558  }
1559  g_set_error(err, 1, 1, _("Internal error."));
1560  return FALSE;
1561  }
1562 
1563  if (strcmp(setting_name_to_validate, "notes_text") == 0) {
1564  // Not a plugin setting. Bail out early.
1565  return TRUE;
1566  }
1567 
1568  const RemminaProtocolSetting *setting_iter;
1569  RemminaProtocolPlugin *protocol_plugin;
1570  RemminaFileEditorPriv *priv = gfe->priv;
1571  protocol_plugin = priv->plugin;
1572 
1573  setting_iter = protocol_plugin->basic_settings;
1574  if (setting_iter) {
1575  // gboolean found = FALSE;
1576  while (setting_iter->type != REMMINA_PROTOCOL_SETTING_TYPE_END) {
1577  if (setting_iter->name == NULL) {
1578  g_error("Internal error: a setting name in protocol plugin %s is "
1579  "null. Please fix RemminaProtocolSetting struct content.",
1580  protocol_plugin->name);
1581  } else if ((gchar *)setting_name_to_validate) {
1582  if (strcmp((gchar *)setting_name_to_validate, setting_iter->name) == 0) {
1583  // found = TRUE;
1584 
1585  gpointer validator_data = setting_iter->validator_data;
1586  GCallback validator = setting_iter->validator;
1587 
1588  // Default behaviour is that everything is valid,
1589  // except a validator is given and its returned GError is not NULL.
1590  GError *err_ret = NULL;
1591 
1592  g_debug("Checking setting '%s' for validation.", setting_iter->name);
1593  if (validator != NULL) {
1594  // Looks weird but it calls the setting's validator
1595  // function using setting_name_to_validate, value and
1596  // validator_data as parameters and it returns a GError*.
1597  err_ret = ((GError * (*)(gpointer, gconstpointer, gpointer)) validator)(setting_name_to_validate, value, validator_data);
1598  }
1599 
1600  if (err_ret) {
1601  g_debug("it has a validator function and it had an error!");
1602  // pass err (returned value) to function caller.
1603  *err = err_ret;
1604  return FALSE;
1605  }
1606 
1607  break;
1608  }
1609  }
1610  setting_iter++;
1611  }
1612 
1613  // if (!found) {
1614  // TOO VERBOSE:
1615  // g_warning("%s is not a plugin setting!", setting_name_to_validate);
1616  // }
1617  }
1618 
1619  return TRUE;
1620 }
1621 
1623  GtkWidget ** failed_widget)
1624 {
1625  TRACE_CALL(__func__);
1626  RemminaFileEditorPriv *priv = gfe->priv;
1627  GHashTableIter iter;
1628  gpointer key;
1629  gpointer widget;
1630  GtkTextBuffer *buffer;
1631  gchar *escaped, *unescaped;
1632  GtkTextIter start, end;
1633 
1634  GError *err = NULL;
1635  *failed_widget = NULL;
1636 
1637  g_hash_table_iter_init(&iter, priv->setting_widgets);
1638  while (g_hash_table_iter_next(&iter, &key, &widget)) {
1639 
1640  // We don't want to save or validate grayed-out settings.
1641  // If widget is a file chooser, it was made not sensitive because
1642  // the box was unchecked. In that case, don't continue. The
1643  // relevant file strings will be set to NULL in the remmina file.
1644  if (!gtk_widget_get_sensitive(GTK_WIDGET(widget)) && !GTK_IS_FILE_CHOOSER(widget)) {
1645  g_debug("Grayed-out setting-widget '%s' will not be saved.",
1646  gtk_widget_get_name(widget));
1647  continue;
1648  }
1649 
1650  if (GTK_IS_ENTRY(widget)) {
1651  const gchar *value = gtk_entry_get_text(GTK_ENTRY(widget));
1652 
1653  if (!remmina_file_editor_validate_settings(gfe, (gchar *)key, value, &err)) {
1654  // Error while validating!
1655  // err should be set now.
1656  *failed_widget = widget;
1657  break;
1658  }
1659 
1660  remmina_file_set_string(priv->remmina_file, (gchar *)key, value);
1661  } else if (GTK_IS_TEXT_VIEW(widget)) {
1662  buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
1663  gtk_text_buffer_get_start_iter(buffer, &start);
1664  gtk_text_buffer_get_end_iter(buffer, &end);
1665  unescaped = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1666  escaped = g_uri_escape_string(unescaped, NULL, TRUE);
1667 
1668  if (!remmina_file_editor_validate_settings(gfe, (gchar *)key, escaped, &err)) {
1669  // Error while validating!
1670  // err should be set now.
1671  *failed_widget = widget;
1672  break;
1673  }
1674 
1675  remmina_file_set_string(priv->remmina_file, (gchar *)key, escaped);
1676  g_free(escaped);
1677  } else if (GTK_IS_COMBO_BOX(widget)) {
1678  gchar *value = remmina_public_combo_get_active_text(GTK_COMBO_BOX(widget));
1679 
1680  if (!remmina_file_editor_validate_settings(gfe, (gchar *)key, value, &err)) {
1681  // Error while validating!
1682  // err should be set now.
1683  *failed_widget = widget;
1684  break;
1685  }
1686 
1687  remmina_file_set_string(priv->remmina_file, (gchar *)key, value);
1688  } else if (GTK_IS_FILE_CHOOSER(widget)) {
1689  gchar *value = gtk_widget_get_sensitive(GTK_WIDGET(widget)) ? gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget)) : NULL;
1690 
1691  if (!gtk_widget_get_sensitive(GTK_WIDGET(widget))) {
1692  remmina_file_set_string(priv->remmina_file, (gchar *)key, value);
1693  continue;
1694  }
1695 
1696  if (!remmina_file_editor_validate_settings(gfe, (gchar *)key, value, &err)) {
1697  // Error while validating!
1698  // err should be set now.
1699  g_free(value);
1700  *failed_widget = widget;
1701  break;
1702  }
1703 
1704  remmina_file_set_string(priv->remmina_file, (gchar *)key, value);
1705  g_free(value);
1706  } else if (GTK_IS_TOGGLE_BUTTON(widget)) {
1707  gboolean value = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
1708 
1709  if (!remmina_file_editor_validate_settings(gfe, (gchar *)key, &value, &err)) {
1710  // Error while validating!
1711  // err should be set now.
1712  *failed_widget = widget;
1713  break;
1714  }
1715 
1716  remmina_file_set_int(priv->remmina_file, (gchar *)key, value);
1717  }
1718  }
1719 
1720  if (err) {
1721  return err;
1722  }
1723 
1724  return NULL;
1725 }
1726 
1728  GtkWidget ** failed_widget)
1729 {
1730  TRACE_CALL(__func__);
1731  int res_w, res_h;
1732  gchar *custom_resolution;
1734 
1735  RemminaFileEditorPriv *priv = gfe->priv;
1736 
1737  remmina_file_set_string(priv->remmina_file, "name", gtk_entry_get_text(GTK_ENTRY(priv->name_entry)));
1738 
1739  remmina_file_set_string(priv->remmina_file, "labels", gtk_entry_get_text(GTK_ENTRY(priv->labels_entry)));
1740 
1741  remmina_file_set_string(priv->remmina_file, "group",
1742  (priv->group_combo ? remmina_public_combo_get_active_text(GTK_COMBO_BOX(priv->group_combo)) : NULL));
1743 
1744  remmina_file_set_string(priv->remmina_file, "protocol",
1745  remmina_public_combo_get_active_text(GTK_COMBO_BOX(priv->protocol_combo)));
1746 
1747  remmina_file_set_string(priv->remmina_file, "server",
1748  (priv->server_combo ? remmina_public_combo_get_active_text(GTK_COMBO_BOX(priv->server_combo)) : NULL));
1749 
1750  if (priv->resolution_auto_radio) {
1751  if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->resolution_auto_radio))) {
1752  /* Resolution is set to auto (which means: Use client fullscreen resolution, aka use client resolution) */
1753  res_w = res_h = 0;
1754  res_mode = RES_USE_CLIENT;
1755  } else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->resolution_iws_radio))) {
1756  /* Resolution is set to initial window size */
1757  res_w = res_h = 0;
1758  res_mode = RES_USE_INITIAL_WINDOW_SIZE;
1759  } else {
1760  /* Resolution is set to a value from the list */
1761  custom_resolution = remmina_public_combo_get_active_text(GTK_COMBO_BOX(priv->resolution_custom_combo));
1762  if (remmina_public_split_resolution_string(custom_resolution, &res_w, &res_h))
1763  res_mode = RES_USE_CUSTOM;
1764  else
1765  res_mode = RES_USE_INITIAL_WINDOW_SIZE;
1766  g_free(custom_resolution);
1767  }
1768  remmina_file_set_int(priv->remmina_file, "resolution_mode", res_mode);
1769  remmina_file_set_int(priv->remmina_file, "resolution_width", res_w);
1770  remmina_file_set_int(priv->remmina_file, "resolution_height", res_h);
1771  }
1772 
1773  if (priv->assistance_toggle){
1774  if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->assistance_toggle))) {
1775  remmina_file_set_string(priv->remmina_file, "assistance_file", gtk_entry_get_text(GTK_ENTRY(priv->assistance_file)));
1776  remmina_file_set_string(priv->remmina_file, "assistance_pass", gtk_entry_get_text(GTK_ENTRY(priv->assistance_password)));
1777  remmina_file_set_int(priv->remmina_file, "assistance_mode", 1);
1778  }else{
1779  remmina_file_set_int(priv->remmina_file, "assistance_mode", 0);
1780  }
1781 
1782  }
1783 
1784  if (priv->keymap_combo)
1785  remmina_file_set_string(priv->remmina_file, "keymap",
1786  remmina_public_combo_get_active_text(GTK_COMBO_BOX(priv->keymap_combo)));
1787 
1790  return remmina_file_editor_update_settings(gfe, failed_widget);
1791 }
1792 
1793 static void remmina_file_editor_on_default(GtkWidget *button, RemminaFileEditor *gfe)
1794 {
1795  TRACE_CALL(__func__);
1796  RemminaFile *gf;
1797  GtkWidget *dialog;
1798 
1799  GtkWidget *failed_widget = NULL;
1800  GError *err = remmina_file_editor_update(gfe, &failed_widget);
1801  if (err) {
1802  g_warning(_("Couldn't validate user input. %s"), err->message);
1803  remmina_file_editor_show_validation_error_popup(gfe, failed_widget, err);
1804  return;
1805  }
1806 
1807  gf = remmina_file_dup(gfe->priv->remmina_file);
1808 
1810 
1811  /* Clear properties that should never be default */
1812  remmina_file_set_string(gf, "name", NULL);
1813  remmina_file_set_string(gf, "server", NULL);
1814  remmina_file_set_string(gf, "password", NULL);
1815  remmina_file_set_string(gf, "precommand", NULL);
1816  remmina_file_set_string(gf, "postcommand", NULL);
1817 
1818  remmina_file_set_string(gf, "ssh_tunnel_server", NULL);
1819  remmina_file_set_string(gf, "ssh_tunnel_password", NULL);
1820  remmina_file_set_string(gf, "ssh_tunnel_passphrase", NULL);
1821 
1822  remmina_file_save(gf);
1823  remmina_file_free(gf);
1824 
1825  dialog = gtk_message_dialog_new(GTK_WINDOW(gfe), GTK_DIALOG_MODAL, GTK_MESSAGE_INFO,
1826  GTK_BUTTONS_OK, _("Default settings saved."));
1827  gtk_dialog_run(GTK_DIALOG(dialog));
1828  gtk_widget_destroy(dialog);
1829 }
1830 
1831 static void remmina_file_editor_on_save(GtkWidget *button, RemminaFileEditor *gfe)
1832 {
1833  TRACE_CALL(__func__);
1834 
1835  GtkWidget *failed_widget = NULL;
1836  GError *err = remmina_file_editor_update(gfe, &failed_widget);
1837  if (err) {
1838  g_warning(_("Couldn't validate user input. %s"), err->message);
1839  remmina_file_editor_show_validation_error_popup(gfe, failed_widget, err);
1840  return;
1841  }
1842 
1844 
1845  remmina_file_save(gfe->priv->remmina_file);
1847 
1848  gtk_widget_destroy(GTK_WIDGET(gfe));
1849 }
1850 
1851 static void remmina_file_editor_on_connect(GtkWidget *button, RemminaFileEditor *gfe)
1852 {
1853  TRACE_CALL(__func__);
1854  RemminaFile *gf;
1855 
1856  GtkWidget *failed_widget = NULL;
1857  GError *err = remmina_file_editor_update(gfe, &failed_widget);
1858  if (err) {
1859  g_warning(_("Couldn't validate user input. %s"), err->message);
1860  remmina_file_editor_show_validation_error_popup(gfe, failed_widget, err);
1861  return;
1862  }
1863 
1864  gf = remmina_file_dup(gfe->priv->remmina_file);
1865  /* Put server into name for "Quick Connect" */
1866  if (remmina_file_get_filename(gf) == NULL)
1867  remmina_file_set_string(gf, "name", remmina_file_get_string(gf, "server"));
1868  gtk_widget_destroy(GTK_WIDGET(gfe));
1869  gf->prevent_saving = TRUE;
1870  rcw_open_from_file(gf);
1871 }
1872 
1873 static void remmina_file_editor_on_save_connect(GtkWidget *button, RemminaFileEditor *gfe)
1874 {
1875  TRACE_CALL(__func__);
1877  RemminaFile *gf;
1878 
1879  GtkWidget *failed_widget = NULL;
1880  GError *err = remmina_file_editor_update(gfe, &failed_widget);
1881  if (err) {
1882  g_warning(_("Couldn't validate user input. %s"), err->message);
1883  remmina_file_editor_show_validation_error_popup(gfe, failed_widget, err);
1884  return;
1885  }
1886 
1888 
1889  remmina_file_save(gfe->priv->remmina_file);
1891 
1892  gf = remmina_file_dup(gfe->priv->remmina_file);
1893  /* Put server into name for Quick Connect */
1894  if (remmina_file_get_filename(gf) == NULL)
1895  remmina_file_set_string(gf, "name", remmina_file_get_string(gf, "server"));
1896  gtk_widget_destroy(GTK_WIDGET(gfe));
1897  rcw_open_from_file(gf);
1898 }
1899 
1900 static void remmina_file_editor_on_cancel(GtkWidget *button, RemminaFileEditor *gfe)
1901 {
1902  TRACE_CALL(__func__);
1903  gtk_widget_destroy(GTK_WIDGET(gfe));
1904 }
1905 
1907 {
1908  TRACE_CALL(__func__);
1909  RemminaFileEditorPriv *priv;
1910  GtkWidget *widget;
1911 
1912  priv = g_new0(RemminaFileEditorPriv, 1);
1913  gfe->priv = priv;
1914 
1915  /* Create the editor dialog */
1916  gtk_window_set_title(GTK_WINDOW(gfe), _("Remote Connection Profile"));
1917 
1918  widget = gtk_dialog_add_button(GTK_DIALOG(gfe), (_("_Cancel")), GTK_RESPONSE_CANCEL);
1919  g_signal_connect(G_OBJECT(widget), "clicked", G_CALLBACK(remmina_file_editor_on_cancel), gfe);
1920 
1921  /* Default button */
1922  widget = gtk_dialog_add_button(GTK_DIALOG(gfe), (_("Save as Default")), GTK_RESPONSE_OK);
1923  gtk_widget_set_tooltip_text(GTK_WIDGET(widget), _("Use the current settings as the default for all new connection profiles"));
1924  g_signal_connect(G_OBJECT(widget), "clicked", G_CALLBACK(remmina_file_editor_on_default), gfe);
1925 
1926  widget = gtk_dialog_add_button(GTK_DIALOG(gfe), (_("_Save")), GTK_RESPONSE_APPLY);
1927  g_signal_connect(G_OBJECT(widget), "clicked", G_CALLBACK(remmina_file_editor_on_save), gfe);
1928  gtk_widget_set_sensitive(widget, FALSE);
1929  priv->save_button = widget;
1930 
1931  widget = gtk_dialog_add_button(GTK_DIALOG(gfe), (_("Connect")), GTK_RESPONSE_ACCEPT);
1932  g_signal_connect(G_OBJECT(widget), "clicked", G_CALLBACK(remmina_file_editor_on_connect), gfe);
1933 
1934  widget = gtk_dialog_add_button(GTK_DIALOG(gfe), (_("_Save and Connect")), GTK_RESPONSE_OK);
1935  gtk_widget_set_can_default(widget, TRUE);
1936  g_signal_connect(G_OBJECT(widget), "clicked", G_CALLBACK(remmina_file_editor_on_save_connect), gfe);
1937 
1938  gtk_dialog_set_default_response(GTK_DIALOG(gfe), GTK_RESPONSE_OK);
1939  gtk_window_set_default_size(GTK_WINDOW(gfe), 800, 600);
1940 
1941  g_signal_connect(G_OBJECT(gfe), "destroy", G_CALLBACK(remmina_file_editor_destroy), NULL);
1942  g_signal_connect(G_OBJECT(gfe), "realize", G_CALLBACK(remmina_file_editor_on_realize), NULL);
1943 
1944  priv->setting_widgets = g_hash_table_new(g_str_hash, g_str_equal);
1945 
1946  remmina_widget_pool_register(GTK_WIDGET(gfe));
1947 }
1948 
1949 static gboolean remmina_file_editor_iterate_protocol(gchar *protocol, RemminaPlugin *plugin, gpointer data)
1950 {
1951  TRACE_CALL(__func__);
1952  RemminaFileEditor *gfe = REMMINA_FILE_EDITOR(data);
1953  GtkListStore *store;
1954  GtkTreeIter iter;
1955  gboolean first;
1956 
1957  store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(gfe->priv->protocol_combo)));
1958 
1959  first = !gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);
1960 
1961  gtk_list_store_append(store, &iter);
1962  gtk_list_store_set(store, &iter, 0, protocol, 1, g_dgettext(plugin->domain, plugin->description), 2,
1963  ((RemminaProtocolPlugin *)plugin)->icon_name, -1);
1964 
1965  if (first || g_strcmp0(protocol, remmina_file_get_string(gfe->priv->remmina_file, "protocol")) == 0)
1966  gtk_combo_box_set_active_iter(GTK_COMBO_BOX(gfe->priv->protocol_combo), &iter);
1967 
1968  return FALSE;
1969 }
1970 
1972 {
1973  TRACE_CALL(__func__);
1974  RemminaFileEditorPriv *priv;
1975 
1976  priv = gfe->priv;
1977  gtk_widget_set_sensitive(priv->group_combo, TRUE);
1978  gtk_widget_set_sensitive(priv->save_button, TRUE);
1979 }
1980 
1981 static void remmina_file_editor_entry_on_changed(GtkEditable *editable, RemminaFileEditor *gfe)
1982 {
1983  TRACE_CALL(__func__);
1984  RemminaFileEditorPriv *priv;
1985 
1986  priv = gfe->priv;
1987  if (remmina_file_get_filename(priv->remmina_file) == NULL) {
1988  remmina_file_generate_filename(priv->remmina_file);
1989  /* TODO: Probably to be removed */
1991  } else {
1992  remmina_file_delete(remmina_file_get_filename(priv->remmina_file));
1993  remmina_file_generate_filename(priv->remmina_file);
1995  }
1996 }
1997 
1999 {
2000  TRACE_CALL(__func__);
2001  RemminaFileEditorPriv *priv;
2002 
2003  priv = gfe->priv;
2004  if (remmina_file_get_filename(priv->remmina_file) == NULL) {
2005  remmina_file_generate_filename(priv->remmina_file);
2006  } else {
2007  remmina_file_delete(remmina_file_get_filename(priv->remmina_file));
2008  remmina_file_generate_filename(priv->remmina_file);
2009  }
2010 }
2011 
2013 {
2014  TRACE_CALL(__func__);
2015  RemminaFileEditor *gfe;
2016  RemminaFileEditorPriv *priv;
2017  GtkWidget *grid;
2018  GtkWidget *widget;
2019  gchar *groups;
2020  gchar *s;
2021  const gchar *cs;
2022 
2023  gfe = REMMINA_FILE_EDITOR(g_object_new(REMMINA_TYPE_FILE_EDITOR, NULL));
2024  priv = gfe->priv;
2025  priv->remmina_file = remminafile;
2026 
2027  if (remmina_file_get_filename(remminafile) == NULL)
2028  gtk_dialog_set_response_sensitive(GTK_DIALOG(gfe), GTK_RESPONSE_APPLY, FALSE);
2029 
2030  /* Create the "Profile" group on the top (for name and protocol) */
2031  grid = gtk_grid_new();
2032  gtk_widget_show(grid);
2033  gtk_grid_set_row_spacing(GTK_GRID(grid), 4);
2034  gtk_grid_set_column_spacing(GTK_GRID(grid), 8);
2035  gtk_grid_set_column_homogeneous(GTK_GRID(grid), TRUE);
2036  gtk_container_set_border_width(GTK_CONTAINER(grid), 8);
2037  gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(gfe))), grid, FALSE, FALSE, 2);
2038 
2039  // remmina_public_create_group(GTK_GRID(grid), _("Profile"), 0, 4, 3);
2040 
2041  gboolean profile_file_exists = (remmina_file_get_filename(remminafile) != NULL);
2042 
2043  /* Profile: Name */
2044  widget = gtk_label_new(_("Name"));
2045  gtk_widget_show(widget);
2046  gtk_widget_set_valign(widget, GTK_ALIGN_START);
2047  gtk_widget_set_halign(widget, GTK_ALIGN_START);
2048  gtk_grid_attach(GTK_GRID(grid), widget, 0, 3, 2, 1);
2049  gtk_grid_set_column_spacing(GTK_GRID(grid), 10);
2050 
2051  widget = gtk_entry_new();
2052  gtk_widget_show(widget);
2053  gtk_grid_attach(GTK_GRID(grid), widget, 1, 3, 3, 1);
2054  gtk_entry_set_max_length(GTK_ENTRY(widget), 100);
2055  priv->name_entry = widget;
2056 
2057  if (!profile_file_exists) {
2058  gtk_entry_set_text(GTK_ENTRY(widget), _("Quick Connect"));
2059 #if GTK_CHECK_VERSION(3, 16, 0)
2060  gtk_entry_grab_focus_without_selecting(GTK_ENTRY(widget));
2061 #endif
2062  g_signal_connect(G_OBJECT(widget), "changed", G_CALLBACK(remmina_file_editor_entry_on_changed), gfe);
2063  } else {
2064  cs = remmina_file_get_string(remminafile, "name");
2065  gtk_entry_set_text(GTK_ENTRY(widget), cs ? cs : "");
2066  }
2067 
2068  /* Profile: Group */
2069  widget = gtk_label_new(_("Group"));
2070  gtk_widget_show(widget);
2071  gtk_widget_set_valign(widget, GTK_ALIGN_START);
2072  gtk_widget_set_halign(widget, GTK_ALIGN_START);
2073  gtk_grid_attach(GTK_GRID(grid), widget, 0, 6, 2, 1);
2074 
2076  priv->group_combo = remmina_public_create_combo_entry(groups, remmina_file_get_string(remminafile, "group"), FALSE);
2077  g_free(groups);
2078  gtk_widget_show(priv->group_combo);
2079  gtk_grid_attach(GTK_GRID(grid), priv->group_combo, 1, 6, 3, 1);
2080  gtk_widget_set_sensitive(priv->group_combo, FALSE);
2081 
2082  s = g_strdup_printf(_("Use '%s' as subgroup delimiter"), "/");
2083  gtk_widget_set_tooltip_text(priv->group_combo, s);
2084  g_free(s);
2085 
2086  /* Profile: Labels */
2087  widget = gtk_label_new(_("Labels"));
2088  gtk_widget_show(widget);
2089  gtk_widget_set_valign(widget, GTK_ALIGN_START);
2090  gtk_widget_set_halign(widget, GTK_ALIGN_START);
2091  gtk_grid_attach(GTK_GRID(grid), widget, 0, 9, 2, 1);
2092  gtk_grid_set_column_spacing(GTK_GRID(grid), 10);
2093 
2094  widget = gtk_entry_new();
2095  gtk_widget_show(widget);
2096  gtk_grid_attach(GTK_GRID(grid), widget, 1, 9, 3, 1);
2097  gtk_entry_set_max_length(GTK_ENTRY(widget), 255);
2098  priv->labels_entry = widget;
2099 
2100  if (!profile_file_exists) {
2101  gtk_widget_set_tooltip_text(widget, _("Label1,Label2"));
2102 #if GTK_CHECK_VERSION(3, 16, 0)
2103  gtk_entry_grab_focus_without_selecting(GTK_ENTRY(widget));
2104 #endif
2105  g_signal_connect(G_OBJECT(widget), "changed", G_CALLBACK(remmina_file_editor_entry_on_changed), gfe);
2106  } else {
2107  cs = remmina_file_get_string(remminafile, "labels");
2108  gtk_entry_set_text(GTK_ENTRY(widget), cs ? cs : "");
2109  }
2110 
2111  /* Profile: Protocol */
2112  widget = gtk_label_new(_("Protocol"));
2113  gtk_widget_show(widget);
2114  gtk_widget_set_valign(widget, GTK_ALIGN_START);
2115  gtk_widget_set_halign(widget, GTK_ALIGN_START);
2116  gtk_grid_attach(GTK_GRID(grid), widget, 0, 12, 2, 1);
2117 
2118  widget = remmina_public_create_combo(TRUE);
2119  gtk_widget_show(widget);
2120  gtk_grid_attach(GTK_GRID(grid), widget, 1, 12, 3, 1);
2121  priv->protocol_combo = widget;
2123  g_signal_connect(G_OBJECT(widget), "changed", G_CALLBACK(remmina_file_editor_protocol_combo_on_changed), gfe);
2124 
2125  /* Create the "Preference" frame */
2126  widget = gtk_event_box_new();
2127  gtk_widget_show(widget);
2128  gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(gfe))), widget, TRUE, TRUE, 2);
2129  priv->config_box = widget;
2130 
2131  priv->config_container = NULL;
2132  priv->config_scrollable = NULL;
2133 
2134  remmina_file_editor_protocol_combo_on_changed(GTK_COMBO_BOX(priv->protocol_combo), gfe);
2135 
2137 
2138  return GTK_WIDGET(gfe);
2139 }
2140 
2141 GtkWidget *remmina_file_editor_new(void)
2142 {
2143  TRACE_CALL(__func__);
2144  return remmina_file_editor_new_full(NULL, NULL);
2145 }
2146 
2147 GtkWidget *remmina_file_editor_new_full(const gchar *server, const gchar *protocol)
2148 {
2149  TRACE_CALL(__func__);
2150  RemminaFile *remminafile;
2151 
2152  remminafile = remmina_file_new();
2153  if (server)
2154  remmina_file_set_string(remminafile, "server", server);
2155  if (protocol)
2156  remmina_file_set_string(remminafile, "protocol", protocol);
2157 
2158  return remmina_file_editor_new_from_file(remminafile);
2159 }
2160 
2161 GtkWidget *remmina_file_editor_new_copy(const gchar *filename)
2162 {
2163  TRACE_CALL(__func__);
2164  RemminaFile *remminafile;
2165  GtkWidget *dialog;
2166 
2167  remminafile = remmina_file_copy(filename);
2168 
2169  if (remminafile) {
2170  return remmina_file_editor_new_from_file(remminafile);
2171  } else {
2172  dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
2173  _("Could not find the file “%s”."), filename);
2174  gtk_dialog_run(GTK_DIALOG(dialog));
2175  gtk_widget_destroy(dialog);
2176  return NULL;
2177  }
2178 }
2179 
2180 GtkWidget *remmina_file_editor_new_from_filename(const gchar *filename)
2181 {
2182  TRACE_CALL(__func__);
2183  RemminaFile *remminafile;
2184 
2185  remminafile = remmina_file_manager_load_file(filename);
2186  if (remminafile) {
2187  if (remmina_file_get_int(remminafile, "profile-lock", FALSE) && remmina_unlock_new(remmina_main_get_window()) == 0)
2188  return NULL;
2189  return remmina_file_editor_new_from_file(remminafile);
2190  } else {
2191  GtkWidget *dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
2192  _("Could not find the file “%s”."), filename);
2193  gtk_dialog_run(GTK_DIALOG(dialog));
2194  gtk_widget_destroy(dialog);
2195  return NULL;
2196  }
2197 }
void remmina_public_create_group(GtkGrid *grid, const gchar *group, gint row, gint rows, gint cols)
const gchar * remmina_plugin_manager_get_canonical_setting_name(const RemminaProtocolSetting *setting)
GtkDialog * remmina_string_list_new(gboolean two_columns, const gchar *fields_separator)
void remmina_file_free(RemminaFile *remminafile)
Definition: remmina_file.c:709
const gchar * remmina_file_get_string(RemminaFile *remminafile, const gchar *setting)
Definition: remmina_file.c:516
gchar ** remmina_pref_keymap_groups(void)
static void remmina_file_editor_create_resolution(RemminaFileEditor *gfe, const RemminaProtocolSetting *setting, GtkWidget *grid, gint row)
static GtkWidget * remmina_file_editor_create_combo(RemminaFileEditor *gfe, GtkWidget *grid, gint row, gint col, const gchar *label, const gchar *list, const gchar *value, gchar *setting_name)
RemminaFile * remmina_file_dup(RemminaFile *remminafile)
Definition: remmina_file.c:865
GtkWidget * remmina_public_create_combo_entry(const gchar *text, const gchar *def, gboolean descending)
static void remmina_file_editor_on_connect(GtkWidget *button, RemminaFileEditor *gfe)
gchar * remmina_pref_file
Definition: rcw.c:78
GtkWindow * remmina_main_get_window()
void remmina_string_list_set_validation_func(RemminaStringListValidationFunc func)
G_DEFINE_TYPE(RemminaFileEditor, remmina_file_editor, GTK_TYPE_DIALOG)
static GtkWidget * remmina_file_editor_create_int(RemminaFileEditor *gfe, GtkWidget *grid, gint row, gint col, const gchar *label, const gint value, gint left, gint right, gchar *setting_name)
static void remmina_file_editor_ssh_tunnel_enabled_check_on_toggled(GtkToggleButton *togglebutton, RemminaFileEditor *gfe, RemminaProtocolSSHSetting ssh_setting)
const gchar * remmina_file_get_filename(RemminaFile *remminafile)
Definition: remmina_file.c:210
static GtkWidget * remmina_file_editor_create_double(RemminaFileEditor *gfe, GtkWidget *grid, gint row, gint col, const gchar *label, gdouble value, gint left, gint right, gchar *setting_name)
static GtkWidget * remmina_file_editor_create_select(RemminaFileEditor *gfe, GtkWidget *grid, gint row, gint col, const gchar *label, const gpointer *list, const gchar *value, gchar *setting_name)
typedefG_BEGIN_DECLS struct _RemminaFile RemminaFile
Definition: types.h:44
static void remmina_file_editor_protocol_combo_on_changed(GtkComboBox *combo, RemminaFileEditor *gfe)
void remmina_plugin_manager_for_each_plugin(RemminaPluginType type, RemminaPluginFunc func, gpointer data)
void remmina_widget_pool_register(GtkWidget *widget)
static void remmina_file_editor_destroy(GtkWidget *widget, gpointer data)
gchar * remmina_public_combo_get_active_text(GtkComboBox *combo)
gboolean remmina_public_resolution_validation_func(const gchar *new_str, gchar **error)
const gchar * domain
Definition: plugin.h:60
gchar * remmina_pref_get_recent(const gchar *protocol)
Definition: remmina_pref.c:980
static void remmina_file_editor_create_server(RemminaFileEditor *gfe, const RemminaProtocolSetting *setting, GtkWidget *grid, gint row)
void remmina_icon_populate_menu(void)
Definition: remmina_icon.c:256
GtkWidget * remmina_file_editor_new_full(const gchar *server, const gchar *protocol)
gdouble remmina_file_get_double(RemminaFile *remminafile, const gchar *setting, gdouble default_value)
Definition: remmina_file.c:629
static void remmina_file_editor_init(RemminaFileEditor *gfe)
gchar * resolutions
Definition: remmina_pref.h:145
void remmina_file_generate_filename(RemminaFile *remminafile)
Generate a new Remmina connection profile file name.
Definition: remmina_file.c:114
const gchar * description
Definition: plugin.h:59
int remmina_public_split_resolution_string(const char *resolution_string, int *w, int *h)
RemminaProtocolWidgetResolutionMode
Definition: types.h:148
static void remmina_file_editor_show_validation_error_popup(RemminaFileEditor *gfe, GtkWidget *failed_widget, GError *err)
Shows a tooltip-like window which tells the user what they did wrong to trigger the validation functi...
GtkWidget * remmina_public_create_combo_map(const gpointer *key_value_list, const gchar *def, gboolean use_icon, const gchar *domain)
static GtkWidget * remmina_file_editor_create_chooser(RemminaFileEditor *gfe, GtkWidget *grid, gint row, gint col, const gchar *label, const gchar *value, gint type, gchar *setting_name)
Create checkbox + gtk_file_chooser for open files and select folders.
static GtkWidget * remmina_file_editor_create_password(RemminaFileEditor *gfe, GtkWidget *grid, gint row, gint col, const gchar *label, const gchar *value, gchar *setting_name)
void remmina_file_set_filename(RemminaFile *remminafile, const gchar *filename)
Definition: remmina_file.c:182
static gpointer ssh_tunnel_auth_list[]
static GError * remmina_file_editor_update_settings(RemminaFileEditor *gfe, GtkWidget **failed_widget)
void remmina_file_editor_check_profile(RemminaFileEditor *gfe)
GtkWidget * remmina_public_create_combo_text_d(const gchar *text, const gchar *def, const gchar *empty_choice)
struct _RemminaFileEditorPriv RemminaFileEditorPriv
static GtkWidget * remmina_file_editor_create_notebook_tab(RemminaFileEditor *gfe, const gchar *stock_id, const gchar *label, gint rows, gint cols)
GtkWidget * remmina_public_create_combo(gboolean use_icon)
RemminaProtocolSSHSetting
Definition: types.h:128
void remmina_file_set_int(RemminaFile *remminafile, const gchar *setting, gint value)
Definition: remmina_file.c:585
void remmina_file_delete(const gchar *filename)
Definition: remmina_file.c:911
static void remmina_file_editor_entry_on_changed(GtkEditable *editable, RemminaFileEditor *gfe)
static GtkWidget * remmina_file_editor_create_text2(RemminaFileEditor *gfe, GtkWidget *grid, gint row, gint col, const gchar *label, const gchar *value, gint left, gint right, gchar *setting_name)
const RemminaProtocolSetting * basic_settings
Definition: plugin.h:74
static GtkWidget * remmina_file_editor_create_check(RemminaFileEditor *gfe, GtkWidget *grid, gint row, gint top, const gchar *label, gboolean value, gchar *setting_name)
static void remmina_file_editor_create_all_settings(RemminaFileEditor *gfe)
static void remmina_file_editor_create_notebook_container(RemminaFileEditor *gfe)
GtkWidget * remmina_file_editor_new_from_file(RemminaFile *remminafile)
gpointer validator_data
Definition: types.h:124
void remmina_file_editor_int_setting_filter(GtkEditable *editable, const gchar *text, gint length, gint *position, gpointer data)
void remmina_string_list_set_text(const gchar *text, const gboolean clear_data)
GtkWidget * remmina_file_editor_new_from_filename(const gchar *filename)
static void remmina_file_editor_create_ssh_tunnel_tab(RemminaFileEditor *gfe, RemminaProtocolSSHSetting ssh_setting)
RemminaProtocolSettingType type
Definition: types.h:118
RemminaFile * remmina_file_new(void)
Definition: remmina_file.c:93
void remmina_string_list_set_titles(gchar *title1, gchar *title2)
GCallback validator
Definition: types.h:125
static void remmina_file_editor_create_settings(RemminaFileEditor *gfe, GtkWidget *grid, const RemminaProtocolSetting *settings)
static void remmina_file_editor_on_save_connect(GtkWidget *button, RemminaFileEditor *gfe)
void rcw_open_from_file(RemminaFile *remminafile)
Definition: rcw.c:4470
RemminaPref remmina_pref
Definition: rcw.c:79
void remmina_file_editor_file_save(RemminaFileEditor *gfe)
gint remmina_file_get_int(RemminaFile *remminafile, const gchar *setting, gint default_value)
Definition: remmina_file.c:603
GtkWidget * remmina_file_editor_new(void)
static void remmina_file_editor_on_realize(GtkWidget *widget, gpointer user_data)
static void remmina_file_editor_button_on_toggled(GtkToggleButton *togglebutton, GtkWidget *widget)
static void remmina_file_editor_on_cancel(GtkWidget *button, RemminaFileEditor *gfe)
RemminaPlugin * remmina_plugin_manager_get_plugin(RemminaPluginType type, const gchar *name)
static void remmina_file_editor_on_save(GtkWidget *button, RemminaFileEditor *gfe)
static void remmina_file_editor_on_default(GtkWidget *button, RemminaFileEditor *gfe)
static void remmina_file_editor_assistance_enabled_check_on_toggled(GtkToggleButton *togglebutton, RemminaFileEditor *gfe)
const gchar * name
Definition: plugin.h:67
void remmina_main_toggle_password_view(GtkWidget *widget, gpointer data)
void remmina_file_editor_double_setting_filter(GtkEditable *editable, const gchar *text, gint length, gint *position, gpointer data)
gchar * remmina_file_manager_get_groups(void)
static GtkWidget * remmina_file_editor_create_text(RemminaFileEditor *gfe, GtkWidget *grid, gint row, gint col, const gchar *label, const gchar *value, gchar *setting_name)
void remmina_file_set_string(RemminaFile *remminafile, const gchar *setting, const gchar *value)
Definition: remmina_file.c:469
static void remmina_file_editor_save_behavior_tab(RemminaFileEditor *gfe)
static void remmina_file_editor_create_behavior_tab(RemminaFileEditor *gfe)
void remmina_file_save(RemminaFile *remminafile)
Definition: remmina_file.c:730
static void remmina_file_editor_browse_resolution(GtkWidget *button, RemminaFileEditor *gfe)
static gboolean remmina_file_editor_validate_settings(RemminaFileEditor *gfe, gchar *setting_name_to_validate, gconstpointer value, GError **err)
static void remmina_file_editor_ssh_tunnel_server_custom_radio_on_toggled(GtkToggleButton *togglebutton, RemminaFileEditor *gfe)
RemminaFile * remmina_file_copy(const gchar *filename)
Definition: remmina_file.c:217
static void remmina_file_editor_create_assistance(RemminaFileEditor *gfe, const RemminaProtocolSetting *setting, GtkWidget *grid, gint row)
RemminaFile * remmina_file_manager_load_file(const gchar *filename)
static gboolean remmina_file_editor_iterate_protocol(gchar *protocol, RemminaPlugin *plugin, gpointer data)
static GError * remmina_file_editor_update(RemminaFileEditor *gfe, GtkWidget **failed_widget)
GtkWidget * remmina_file_editor_new_copy(const gchar *filename)
RemminaFileEditorPriv * priv
static GtkWidget * remmina_file_editor_create_textarea(RemminaFileEditor *gfe, GtkWidget *grid, gint row, gint col, const gchar *label, const gchar *value, gchar *setting_name)
static void remmina_file_editor_browse_avahi(GtkWidget *button, RemminaFileEditor *gfe)
gint remmina_unlock_new(GtkWindow *parent)
void remmina_public_load_combo_text_d(GtkWidget *combo, const gchar *text, const gchar *def, const gchar *empty_choice)
static void remmina_file_editor_update_resolution(GtkWidget *widget, RemminaFileEditor *gfe)
N_("Unable to connect to VNC server")
Definition: vnc_plugin.c:953
static void remmina_file_editor_class_init(RemminaFileEditorClass *klass)
const gchar * label
Definition: types.h:120
static void remmina_file_editor_save_ssh_tunnel_tab(RemminaFileEditor *gfe)
gchar * remmina_string_list_get_text(void)
const gchar * name
Definition: types.h:119