1 /*
2 * Copyright © 2001, 2002 Havoc Pennington
3 * Copyright © 2002 Red Hat, Inc.
4 * Copyright © 2002 Sun Microsystems
5 * Copyright © 2003 Mariano Suarez-Alvarez
6 * Copyright © 2008, 2011 Christian Persch
7 *
8 * This program is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 #include "config.h"
23
24 #include <string.h>
25 #include <stdlib.h>
26 #include <time.h>
27 #include <unistd.h>
28 #include <sys/types.h>
29 #include <langinfo.h>
30 #include <errno.h>
31
32 #include <glib.h>
33 #include <glib/gi18n.h>
34
35 #include <gio/gio.h>
36 #include <gtk/gtk.h>
37
38 #include <gdesktop-enums.h>
39
40 #include "terminal-accels.hh"
41 #include "terminal-app.hh"
42 #include "terminal-intl.hh"
43 #include "terminal-util.hh"
44 #include "terminal-version.hh"
45 #include "terminal-libgsystem.hh"
46
47 /**
48 * terminal_util_show_error_dialog:
49 * @transient_parent: parent of the future dialog window;
50 * @weap_ptr: pointer to a #Widget pointer, to control the population.
51 * @error: a #GError, or %nullptr
52 * @message_format: printf() style format string
53 *
54 * Create a #GtkMessageDialog window with the message, and present it, handling its buttons.
55 * If @weap_ptr is not #nullptr, only create the dialog if <literal>*weap_ptr</literal> is #nullptr
56 * (and in that * case, set @weap_ptr to be a weak pointer to the new dialog), otherwise just
57 * present <literal>*weak_ptr</literal>. Note that in this last case, the message <emph>will</emph>
58 * be changed.
59 */
60 void
terminal_util_show_error_dialog(GtkWindow * transient_parent,GtkWidget ** weak_ptr,GError * error,const char * message_format,...)61 terminal_util_show_error_dialog (GtkWindow *transient_parent,
62 GtkWidget **weak_ptr,
63 GError *error,
64 const char *message_format,
65 ...)
66 {
67 gs_free char *message;
68 va_list args;
69
70 if (message_format)
71 {
72 va_start (args, message_format);
73 message = g_strdup_vprintf (message_format, args);
74 va_end (args);
75 }
76 else message = nullptr;
77
78 if (weak_ptr == nullptr || *weak_ptr == nullptr)
79 {
80 GtkWidget *dialog;
81 dialog = gtk_message_dialog_new (transient_parent,
82 GTK_DIALOG_DESTROY_WITH_PARENT,
83 GTK_MESSAGE_ERROR,
84 GTK_BUTTONS_OK,
85 message ? "%s" : nullptr,
86 message);
87
88 if (error != nullptr)
89 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
90 "%s", error->message);
91
92 g_signal_connect (G_OBJECT (dialog), "response", G_CALLBACK (gtk_widget_destroy), nullptr);
93
94 if (weak_ptr != nullptr)
95 {
96 *weak_ptr = dialog;
97 g_object_add_weak_pointer (G_OBJECT (dialog), (void**)weak_ptr);
98 }
99
100 gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
101
102 gtk_widget_show_all (dialog);
103 }
104 else
105 {
106 g_return_if_fail (GTK_IS_MESSAGE_DIALOG (*weak_ptr));
107
108 /* Sucks that there's no direct accessor for "text" property */
109 g_object_set (G_OBJECT (*weak_ptr), "text", message, nullptr);
110
111 gtk_window_present (GTK_WINDOW (*weak_ptr));
112 }
113 }
114
115 static gboolean
open_url(GtkWindow * parent,const char * uri,guint32 user_time,GError ** error)116 open_url (GtkWindow *parent,
117 const char *uri,
118 guint32 user_time,
119 GError **error)
120 {
121 GdkScreen *screen;
122 gs_free char *uri_fixed;
123
124 if (parent)
125 screen = gtk_widget_get_screen (GTK_WIDGET (parent));
126 else
127 screen = gdk_screen_get_default ();
128
129 uri_fixed = terminal_util_uri_fixup (uri, error);
130 if (uri_fixed == nullptr)
131 return FALSE;
132
133 return gtk_show_uri (screen, uri_fixed, user_time, error);
134 }
135
136 void
terminal_util_show_help(const char * topic)137 terminal_util_show_help (const char *topic)
138 {
139 gs_free_error GError *error = nullptr;
140 gs_free char *uri;
141
142 if (topic) {
143 uri = g_strdup_printf ("help:gnome-terminal/%s", topic);
144 } else {
145 uri = g_strdup ("help:gnome-terminal");
146 }
147
148 if (!open_url (nullptr, uri, gtk_get_current_event_time (), &error))
149 {
150 terminal_util_show_error_dialog (nullptr, nullptr, error,
151 _("There was an error displaying help"));
152 }
153 }
154
155 #define ABOUT_GROUP "About"
156 #define ABOUT_URL "https://wiki.gnome.org/Apps/Terminal"
157 #define EMAILIFY(string) (g_strdelimit ((string), "%", '@'))
158
159 void
terminal_util_show_about(void)160 terminal_util_show_about (void)
161 {
162 static const char copyright[] =
163 "Copyright © 2002–2004 Havoc Pennington\n"
164 "Copyright © 2003–2004, 2007 Mariano Suárez-Alvarez\n"
165 "Copyright © 2006 Guilherme de S. Pastore\n"
166 "Copyright © 2007–2019 Christian Persch\n"
167 "Copyright © 2013–2019 Egmont Koblinger";
168 char *licence_text;
169 GKeyFile *key_file;
170 GBytes *bytes;
171 const guint8 *data;
172 gsize data_len;
173 GError *error = nullptr;
174 char **authors, **contributors, **artists, **documenters, **array_strv;
175 gsize n_authors = 0, n_contributors = 0, n_artists = 0, n_documenters = 0 , i;
176 GPtrArray *array;
177 gs_free char *comment;
178 gs_free char *version;
179 gs_free char *vte_version;
180 GtkWindow *dialog;
181
182 bytes = g_resources_lookup_data (TERMINAL_RESOURCES_PATH_PREFIX "/ui/terminal.about",
183 G_RESOURCE_LOOKUP_FLAGS_NONE,
184 &error);
185 g_assert_no_error (error);
186
187 data = (guint8 const*)g_bytes_get_data (bytes, &data_len);
188 key_file = g_key_file_new ();
189 g_key_file_load_from_data (key_file, (const char *) data, data_len, GKeyFileFlags(0), &error);
190 g_assert_no_error (error);
191
192 authors = g_key_file_get_string_list (key_file, ABOUT_GROUP, "Authors", &n_authors, nullptr);
193 contributors = g_key_file_get_string_list (key_file, ABOUT_GROUP, "Contributors", &n_contributors, nullptr);
194 artists = g_key_file_get_string_list (key_file, ABOUT_GROUP, "Artists", &n_artists, nullptr);
195 documenters = g_key_file_get_string_list (key_file, ABOUT_GROUP, "Documenters", &n_documenters, nullptr);
196
197 g_key_file_free (key_file);
198 g_bytes_unref (bytes);
199
200 array = g_ptr_array_new ();
201
202 for (i = 0; i < n_authors; ++i)
203 g_ptr_array_add (array, EMAILIFY (authors[i]));
204 g_free (authors); /* strings are now owned by the array */
205
206 if (n_contributors > 0)
207 {
208 g_ptr_array_add (array, g_strdup (""));
209 g_ptr_array_add (array, g_strdup (_("Contributors:")));
210 for (i = 0; i < n_contributors; ++i)
211 g_ptr_array_add (array, EMAILIFY (contributors[i]));
212 }
213 g_free (contributors); /* strings are now owned by the array */
214
215 g_ptr_array_add (array, nullptr);
216 array_strv = (char **) g_ptr_array_free (array, FALSE);
217
218 for (i = 0; i < n_artists; ++i)
219 artists[i] = EMAILIFY (artists[i]);
220 for (i = 0; i < n_documenters; ++i)
221 documenters[i] = EMAILIFY (documenters[i]);
222
223 licence_text = terminal_util_get_licence_text ();
224
225 /* gnome 40 corresponds to g-t 3.40.x. After that, gnome version
226 * increases by 1 while the g-t minor version increases by 2 between
227 * stable releases.
228 */
229 auto const gnome_version = 40 + (TERMINAL_MINOR_VERSION - 40 + 1) / 2;
230 version = g_strdup_printf (_("Version %s for GNOME %d"),
231 VERSION,
232 gnome_version);
233
234 vte_version = g_strdup_printf (_("Using VTE version %u.%u.%u"),
235 vte_get_major_version (),
236 vte_get_minor_version (),
237 vte_get_micro_version ());
238
239 comment = g_strdup_printf("%s\n%s %s",
240 _("A terminal emulator for the GNOME desktop"),
241 vte_version,
242 vte_get_features ());
243
244 dialog = (GtkWindow*)g_object_new (GTK_TYPE_ABOUT_DIALOG,
245 /* Hold the application while the window is shown */
246 "application", terminal_app_get (),
247 "program-name", _("GNOME Terminal"),
248 "copyright", copyright,
249 "comments", comment,
250 "version", version,
251 "authors", array_strv,
252 "artists", artists,
253 "documenters", documenters,
254 "license", licence_text,
255 "wrap-license", TRUE,
256 "website", ABOUT_URL,
257 "translator-credits", _("translator-credits"),
258 "logo-icon-name", GNOME_TERMINAL_ICON_NAME,
259 nullptr);
260
261 g_signal_connect (dialog, "response", G_CALLBACK (gtk_widget_destroy), nullptr);
262 gtk_window_present (dialog);
263
264 g_strfreev (array_strv);
265 g_strfreev (artists);
266 g_strfreev (documenters);
267 g_free (licence_text);
268 }
269
270 /* sets accessible name and description for the widget */
271
272 void
terminal_util_set_atk_name_description(GtkWidget * widget,const char * name,const char * desc)273 terminal_util_set_atk_name_description (GtkWidget *widget,
274 const char *name,
275 const char *desc)
276 {
277 AtkObject *obj;
278
279 obj = gtk_widget_get_accessible (widget);
280
281 if (obj == nullptr)
282 {
283 g_warning ("%s: for some reason widget has no GtkAccessible",
284 G_STRFUNC);
285 return;
286 }
287
288 if (!GTK_IS_ACCESSIBLE (obj))
289 return; /* This means GAIL is not loaded so we have the NoOp accessible */
290
291 g_return_if_fail (GTK_IS_ACCESSIBLE (obj));
292 if (desc)
293 atk_object_set_description (obj, desc);
294 if (name)
295 atk_object_set_name (obj, name);
296 }
297
298 void
terminal_util_open_url(GtkWidget * parent,const char * orig_url,TerminalURLFlavor flavor,guint32 user_time)299 terminal_util_open_url (GtkWidget *parent,
300 const char *orig_url,
301 TerminalURLFlavor flavor,
302 guint32 user_time)
303 {
304 gs_free_error GError *error = nullptr;
305 gs_free char *uri = nullptr;
306
307 g_return_if_fail (orig_url != nullptr);
308
309 switch (flavor)
310 {
311 case FLAVOR_DEFAULT_TO_HTTP:
312 uri = g_strdup_printf ("http://%s", orig_url);
313 break;
314 case FLAVOR_EMAIL:
315 if (g_ascii_strncasecmp ("mailto:", orig_url, 7) != 0)
316 uri = g_strdup_printf ("mailto:%s", orig_url);
317 else
318 uri = g_strdup (orig_url);
319 break;
320 case FLAVOR_VOIP_CALL:
321 case FLAVOR_AS_IS:
322 uri = g_strdup (orig_url);
323 break;
324 default:
325 uri = nullptr;
326 g_assert_not_reached ();
327 }
328
329 if (!open_url (GTK_WINDOW (parent), uri, user_time, &error))
330 {
331 terminal_util_show_error_dialog (GTK_WINDOW (parent), nullptr, error,
332 _("Could not open the address “%s”"),
333 uri);
334 }
335 }
336
337 /**
338 * terminal_util_transform_uris_to_quoted_fuse_paths:
339 * @uris:
340 *
341 * Transforms those URIs in @uris to shell-quoted paths that point to
342 * GIO fuse paths.
343 */
344 void
terminal_util_transform_uris_to_quoted_fuse_paths(char ** uris)345 terminal_util_transform_uris_to_quoted_fuse_paths (char **uris)
346 {
347 guint i;
348
349 if (!uris)
350 return;
351
352 for (i = 0; uris[i]; ++i)
353 {
354 gs_unref_object GFile *file;
355 gs_free char *path;
356
357 file = g_file_new_for_uri (uris[i]);
358
359 path = g_file_get_path (file);
360 if (path)
361 {
362 char *quoted;
363
364 quoted = g_shell_quote (path);
365 g_free (uris[i]);
366
367 uris[i] = quoted;
368 }
369 }
370 }
371
372 char *
terminal_util_concat_uris(char ** uris,gsize * length)373 terminal_util_concat_uris (char **uris,
374 gsize *length)
375 {
376 GString *string;
377 gsize len;
378 guint i;
379
380 len = 0;
381 for (i = 0; uris[i]; ++i)
382 len += strlen (uris[i]) + 1;
383
384 if (length)
385 *length = len;
386
387 string = g_string_sized_new (len + 1);
388 for (i = 0; uris[i]; ++i)
389 {
390 g_string_append (string, uris[i]);
391 g_string_append_c (string, ' ');
392 }
393
394 return g_string_free (string, FALSE);
395 }
396
397 char *
terminal_util_get_licence_text(void)398 terminal_util_get_licence_text (void)
399 {
400 const gchar *license[] = {
401 N_("GNOME Terminal is free software: you can redistribute it and/or modify "
402 "it under the terms of the GNU General Public License as published by "
403 "the Free Software Foundation, either version 3 of the License, or "
404 "(at your option) any later version."),
405 N_("GNOME Terminal is distributed in the hope that it will be useful, "
406 "but WITHOUT ANY WARRANTY; without even the implied warranty of "
407 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the "
408 "GNU General Public License for more details."),
409 N_("You should have received a copy of the GNU General Public License "
410 "along with GNOME Terminal. If not, see <http://www.gnu.org/licenses/>.")
411 };
412
413 return g_strjoin ("\n\n", _(license[0]), _(license[1]), _(license[2]), nullptr);
414 }
415
416 static void
main_object_destroy_cb(GtkWidget * widget)417 main_object_destroy_cb (GtkWidget *widget)
418 {
419 g_object_set_data (G_OBJECT (widget), "builder", nullptr);
420 }
421
422 GtkBuilder *
terminal_util_load_widgets_resource(const char * path,const char * main_object_name,const char * object_name,...)423 terminal_util_load_widgets_resource (const char *path,
424 const char *main_object_name,
425 const char *object_name,
426 ...)
427 {
428 GtkBuilder *builder;
429 GError *error = nullptr;
430 va_list args;
431
432 builder = gtk_builder_new ();
433 gtk_builder_add_from_resource (builder, path, &error);
434 g_assert_no_error (error);
435
436 va_start (args, object_name);
437
438 while (object_name) {
439 GObject **objectptr;
440
441 objectptr = va_arg (args, GObject**);
442 *objectptr = gtk_builder_get_object (builder, object_name);
443 if (!*objectptr)
444 g_error ("Failed to fetch object \"%s\" from resource \"%s\"\n", object_name, path);
445
446 object_name = va_arg (args, const char*);
447 }
448
449 va_end (args);
450
451 if (main_object_name) {
452 GObject *main_object;
453 GtkWidget *action_area;
454
455 main_object = gtk_builder_get_object (builder, main_object_name);
456 g_object_set_data_full (main_object, "builder", g_object_ref (builder), (GDestroyNotify) g_object_unref);
457 g_signal_connect (main_object, "destroy", G_CALLBACK (main_object_destroy_cb), nullptr);
458
459 /* Fixup dialogue padding, #735242 */
460 if (GTK_IS_DIALOG (main_object) &&
461 (action_area = (GtkWidget *) gtk_builder_get_object (builder, "dialog-action-area"))) {
462 gtk_widget_set_margin_start (action_area, 5);
463 gtk_widget_set_margin_end (action_area, 5);
464 gtk_widget_set_margin_top (action_area, 5);
465 gtk_widget_set_margin_bottom (action_area, 5);
466 }
467 }
468 return builder;
469 }
470
471 void
terminal_util_load_objects_resource(const char * path,const char * object_name,...)472 terminal_util_load_objects_resource (const char *path,
473 const char *object_name,
474 ...)
475 {
476 gs_unref_object GtkBuilder *builder;
477 GError *error = nullptr;
478 va_list args;
479
480 builder = gtk_builder_new ();
481 gtk_builder_add_from_resource (builder, path, &error);
482 g_assert_no_error (error);
483
484 va_start (args, object_name);
485
486 while (object_name) {
487 GObject **objectptr;
488
489 objectptr = va_arg (args, GObject**);
490 *objectptr = gtk_builder_get_object (builder, object_name);
491 if (*objectptr)
492 g_object_ref (*objectptr);
493 else
494 g_error ("Failed to fetch object \"%s\" from resource \"%s\"\n", object_name, path);
495
496 object_name = va_arg (args, const char*);
497 }
498
499 va_end (args);
500 }
501
502 gboolean
terminal_util_dialog_response_on_delete(GtkWindow * widget)503 terminal_util_dialog_response_on_delete (GtkWindow *widget)
504 {
505 gtk_dialog_response (GTK_DIALOG (widget), GTK_RESPONSE_DELETE_EVENT);
506 return TRUE;
507 }
508
509 void
terminal_util_dialog_focus_widget(GtkBuilder * builder,const char * widget_name)510 terminal_util_dialog_focus_widget (GtkBuilder *builder,
511 const char *widget_name)
512 {
513 GtkWidget *widget, *page, *page_parent;
514
515 if (widget_name == nullptr)
516 return;
517
518 widget = GTK_WIDGET (gtk_builder_get_object (builder, widget_name));
519 if (widget == nullptr)
520 return;
521
522 page = widget;
523 while (page != nullptr &&
524 (page_parent = gtk_widget_get_parent (page)) != nullptr &&
525 !GTK_IS_NOTEBOOK (page_parent))
526 page = page_parent;
527
528 page_parent = gtk_widget_get_parent (page);
529 if (page != nullptr && GTK_IS_NOTEBOOK (page_parent)) {
530 GtkNotebook *notebook;
531
532 notebook = GTK_NOTEBOOK (page_parent);
533 gtk_notebook_set_current_page (notebook, gtk_notebook_page_num (notebook, page));
534 }
535
536 if (gtk_widget_is_sensitive (widget))
537 gtk_widget_grab_focus (widget);
538 }
539
540 /* Proxy stuff */
541
542 /*
543 * set_proxy_env:
544 * @env_table: a #GHashTable
545 * @key: the env var name
546 * @value: the env var value
547 *
548 * Adds @value for @key to @env_table, taking care to never overwrite an
549 * existing value for @key. @value is consumed.
550 */
551 static void
set_proxy_env(GHashTable * env_table,const char * key,char * value)552 set_proxy_env (GHashTable *env_table,
553 const char *key,
554 char *value /* consumed */)
555 {
556 char *key1 = nullptr, *key2 = nullptr;
557 char *value1 = nullptr, *value2 = nullptr;
558
559 if (!value)
560 return;
561
562 if (g_hash_table_lookup (env_table, key) == nullptr)
563 key1 = g_strdup (key);
564
565 key2 = g_ascii_strup (key, -1);
566 if (g_hash_table_lookup (env_table, key) != nullptr)
567 {
568 g_free (key2);
569 key2 = nullptr;
570 }
571
572 if (key1 && key2)
573 {
574 value1 = value;
575 value2 = g_strdup (value);
576 }
577 else if (key1)
578 value1 = value;
579 else if (key2)
580 value2 = value;
581 else
582 g_free (value);
583
584 if (key1)
585 g_hash_table_replace (env_table, key1, value1);
586 if (key2)
587 g_hash_table_replace (env_table, key2, value2);
588 }
589
590 static void
setup_proxy_env(TerminalApp * app,TerminalProxyProtocol protocol,const char * proxy_scheme,const char * env_name,GHashTable * env_table)591 setup_proxy_env (TerminalApp* app,
592 TerminalProxyProtocol protocol,
593 const char *proxy_scheme,
594 const char *env_name,
595 GHashTable *env_table)
596 {
597 GString *buf;
598 gs_free char *host;
599 int port;
600
601 gboolean is_http = (protocol == TERMINAL_PROXY_HTTP);
602
603 GSettings *child_settings = terminal_app_get_proxy_settings_for_protocol(app, protocol);
604
605 host = g_settings_get_string (child_settings, "host");
606 port = g_settings_get_int (child_settings, "port");
607 if (host[0] == '\0' || port == 0)
608 return;
609
610 buf = g_string_sized_new (64);
611
612 g_string_append_printf (buf, "%s://", proxy_scheme);
613
614 if (is_http &&
615 g_settings_get_boolean (child_settings, "use-authentication"))
616 {
617 gs_free char *user;
618
619 user = g_settings_get_string (child_settings, "authentication-user");
620 if (user[0])
621 {
622 gs_free char *password;
623
624 g_string_append_uri_escaped (buf, user, nullptr, TRUE);
625
626 password = g_settings_get_string (child_settings, "authentication-password");
627 if (password[0])
628 {
629 g_string_append_c (buf, ':');
630 g_string_append_uri_escaped (buf, password, nullptr, TRUE);
631 }
632 g_string_append_c (buf, '@');
633 }
634 }
635
636 g_string_append_printf (buf, "%s:%d/", host, port);
637 set_proxy_env (env_table, env_name, g_string_free (buf, FALSE));
638 }
639
640 static void
setup_ignore_proxy_env(GSettings * proxy_settings,GHashTable * env_table)641 setup_ignore_proxy_env (GSettings *proxy_settings,
642 GHashTable *env_table)
643 {
644 GString *buf;
645 gs_strfreev char **ignore;
646 int i;
647
648 g_settings_get (proxy_settings, "ignore-hosts", "^as", &ignore);
649 if (ignore == nullptr)
650 return;
651
652 buf = g_string_sized_new (64);
653 for (i = 0; ignore[i] != nullptr; ++i)
654 {
655 if (buf->len)
656 g_string_append_c (buf, ',');
657 g_string_append (buf, ignore[i]);
658 }
659
660 set_proxy_env (env_table, "no_proxy", g_string_free (buf, FALSE));
661 }
662
663 /**
664 * terminal_util_add_proxy_env:
665 * @env_table: a #GHashTable
666 *
667 * Adds the proxy env variables to @env_table.
668 */
669 void
terminal_util_add_proxy_env(GHashTable * env_table)670 terminal_util_add_proxy_env (GHashTable *env_table)
671 {
672 auto const app = terminal_app_get();
673 auto const proxy_settings = terminal_app_get_proxy_settings(app);
674 auto const mode = GDesktopProxyMode(g_settings_get_enum (proxy_settings, "mode"));
675
676 if (mode == G_DESKTOP_PROXY_MODE_MANUAL)
677 {
678 setup_proxy_env (app, TERMINAL_PROXY_HTTP, "http", "http_proxy", env_table);
679 /* Even though it's https, the proxy scheme is 'http'. See bug #624440. */
680 setup_proxy_env (app, TERMINAL_PROXY_HTTPS, "http", "https_proxy", env_table);
681 /* Even though it's ftp, the proxy scheme is 'http'. See bug #624440. */
682 setup_proxy_env (app, TERMINAL_PROXY_FTP, "http", "ftp_proxy", env_table);
683 setup_proxy_env (app, TERMINAL_PROXY_SOCKS, "socks", "all_proxy", env_table);
684 setup_ignore_proxy_env (proxy_settings, env_table);
685 }
686 else if (mode == G_DESKTOP_PROXY_MODE_AUTO)
687 {
688 /* Not supported */
689 }
690 }
691
692 /**
693 * terminal_util_get_etc_shells:
694 *
695 * Returns: (transfer full) the contents of /etc/shells
696 */
697 char **
terminal_util_get_etc_shells(void)698 terminal_util_get_etc_shells (void)
699 {
700 GError *err = nullptr;
701 gsize len;
702 gs_free char *contents = nullptr;
703 char *str, *nl, *end;
704 GPtrArray *arr;
705
706 if (!g_file_get_contents ("/etc/shells", &contents, &len, &err) || len == 0) {
707 /* Defaults as per man:getusershell(3) */
708 char *default_shells[3] = {
709 (char*) "/bin/sh",
710 (char*) "/bin/csh",
711 nullptr
712 };
713 return g_strdupv (default_shells);
714 }
715
716 arr = g_ptr_array_new ();
717 str = contents;
718 end = contents + len;
719 while (str < end && (nl = strchr (str, '\n')) != nullptr) {
720 if (str != nl) /* non-empty? */
721 g_ptr_array_add (arr, g_strndup (str, nl - str));
722 str = nl + 1;
723 }
724 /* Anything non-empty left? */
725 if (str < end && str[0])
726 g_ptr_array_add (arr, g_strdup (str));
727
728 g_ptr_array_add (arr, nullptr);
729 return (char **) g_ptr_array_free (arr, FALSE);
730 }
731
732 /**
733 * terminal_util_get_is_shell:
734 * @command: a string
735 *
736 * Returns wether @command is a valid shell as defined by the contents of /etc/shells.
737 *
738 * Returns: whether @command is a shell
739 */
740 gboolean
terminal_util_get_is_shell(const char * command)741 terminal_util_get_is_shell (const char *command)
742 {
743 gs_strfreev char **shells;
744 guint i;
745
746 shells = terminal_util_get_etc_shells ();
747 if (shells == nullptr)
748 return FALSE;
749
750 for (i = 0; shells[i]; i++)
751 if (g_str_equal (command, shells[i]))
752 return TRUE;
753
754 return FALSE;
755 }
756
757 static gboolean
s_to_rgba(GVariant * variant,gpointer * result,gpointer user_data)758 s_to_rgba (GVariant *variant,
759 gpointer *result,
760 gpointer user_data)
761 {
762 GdkRGBA *color = (GdkRGBA*)user_data;
763 const char *str;
764
765 if (variant == nullptr) {
766 /* Fallback */
767 *result = nullptr;
768 return TRUE;
769 }
770
771 g_variant_get (variant, "&s", &str);
772 if (!gdk_rgba_parse (color, str))
773 return FALSE;
774
775 color->alpha = 1.0;
776 *result = color;
777 return TRUE;
778 }
779
780 /**
781 * terminal_g_settings_get_rgba:
782 * @settings: a #GSettings
783 * @key: a valid key in @settings of type "s"
784 * @color: location to store the parsed color
785 *
786 * Gets a color from @key in @settings.
787 *
788 * Returns: @color if parsing succeeded, or %nullptr otherwise
789 */
790 const GdkRGBA *
terminal_g_settings_get_rgba(GSettings * settings,const char * key,GdkRGBA * color)791 terminal_g_settings_get_rgba (GSettings *settings,
792 const char *key,
793 GdkRGBA *color)
794 {
795 g_return_val_if_fail (color != nullptr, FALSE);
796
797 return (GdkRGBA const*)g_settings_get_mapped (settings, key,
798 s_to_rgba,
799 color);
800 }
801
802 /**
803 * terminal_g_settings_set_rgba:
804 * @settings: a #GSettings
805 * @key: a valid key in @settings of type "s"
806 * @color: a #GdkRGBA
807 *
808 * Sets a color in @key in @settings.
809 */
810 void
terminal_g_settings_set_rgba(GSettings * settings,const char * key,const GdkRGBA * color)811 terminal_g_settings_set_rgba (GSettings *settings,
812 const char *key,
813 const GdkRGBA *color)
814 {
815 gs_free char *str;
816
817 str = gdk_rgba_to_string (color);
818 g_settings_set_string (settings, key, str);
819 }
820
821 static gboolean
as_to_rgba_palette(GVariant * variant,gpointer * result,gpointer user_data)822 as_to_rgba_palette (GVariant *variant,
823 gpointer *result,
824 gpointer user_data)
825 {
826 gsize *n_colors = (gsize*)user_data;
827 gs_free GdkRGBA *colors = nullptr;
828 gsize n = 0;
829 GVariantIter iter;
830 const char *str;
831 gsize i;
832
833 /* Fallback */
834 if (variant == nullptr)
835 goto out;
836
837 g_variant_iter_init (&iter, variant);
838 n = g_variant_iter_n_children (&iter);
839 colors = g_new (GdkRGBA, n);
840
841 i = 0;
842 while (g_variant_iter_next (&iter, "&s", &str)) {
843 if (!gdk_rgba_parse (&colors[i++], str)) {
844 return FALSE;
845 }
846 }
847
848 out:
849 gs_transfer_out_value (result, &colors);
850 if (n_colors)
851 *n_colors = n;
852
853 return TRUE;
854 }
855
856 /**
857 * terminal_g_settings_get_rgba_palette:
858 * @settings: a #GSettings
859 * @key: a valid key in @settings or type "s"
860 * @n_colors: (allow-none): location to store the number of palette entries, or %nullptr
861 *
862 * Returns: (transfer full):
863 */
864 GdkRGBA *
terminal_g_settings_get_rgba_palette(GSettings * settings,const char * key,gsize * n_colors)865 terminal_g_settings_get_rgba_palette (GSettings *settings,
866 const char *key,
867 gsize *n_colors)
868 {
869 return (GdkRGBA*)g_settings_get_mapped (settings, key,
870 as_to_rgba_palette,
871 n_colors);
872 }
873
874 void
terminal_g_settings_set_rgba_palette(GSettings * settings,const char * key,const GdkRGBA * colors,gsize n_colors)875 terminal_g_settings_set_rgba_palette (GSettings *settings,
876 const char *key,
877 const GdkRGBA *colors,
878 gsize n_colors)
879 {
880 gs_strfreev char **strv;
881 gsize i;
882
883 strv = g_new (char *, n_colors + 1);
884 for (i = 0; i < n_colors; ++i)
885 strv[i] = gdk_rgba_to_string (&colors[i]);
886 strv[n_colors] = nullptr;
887
888 g_settings_set (settings, key, "^as", strv);
889 }
890
891 static void
mnemonic_label_set_sensitive_cb(GtkWidget * widget,GParamSpec * pspec,GtkWidget * label)892 mnemonic_label_set_sensitive_cb (GtkWidget *widget,
893 GParamSpec *pspec,
894 GtkWidget *label)
895 {
896 gtk_widget_set_sensitive (label, gtk_widget_get_sensitive (widget));
897 }
898
899 /**
900 * terminal_util_bind_mnemonic_label_sensitivity:
901 * @container: a #GtkContainer
902 */
903 void
terminal_util_bind_mnemonic_label_sensitivity(GtkWidget * widget)904 terminal_util_bind_mnemonic_label_sensitivity (GtkWidget *widget)
905 {
906 GList *list, *l;
907
908 list = gtk_widget_list_mnemonic_labels (widget);
909 for (l = list; l != nullptr; l = l->next) {
910 GtkWidget *label = (GtkWidget*)l->data;
911
912 if (gtk_widget_is_ancestor (label, widget))
913 continue;
914
915 #if 0
916 g_print ("Widget %s has mnemonic label %s\n",
917 gtk_buildable_get_name (GTK_BUILDABLE (widget)),
918 gtk_buildable_get_name (GTK_BUILDABLE (label)));
919 #endif
920
921 mnemonic_label_set_sensitive_cb (widget, nullptr, label);
922 g_signal_connect (widget, "notify::sensitive",
923 G_CALLBACK (mnemonic_label_set_sensitive_cb),
924 label);
925 }
926 g_list_free (list);
927
928 if (GTK_IS_CONTAINER (widget))
929 gtk_container_foreach (GTK_CONTAINER (widget),
930 /* See #96 for double casting. */
931 (GtkCallback) (GCallback) terminal_util_bind_mnemonic_label_sensitivity,
932 nullptr);
933 }
934
935 /*
936 * "1234567", "'", 3 -> "1'234'567"
937 */
938 static char *
add_separators(const char * in,const char * sep,int groupby)939 add_separators (const char *in, const char *sep, int groupby)
940 {
941 int inlen, outlen, seplen, firstgrouplen;
942 char *out, *ret;
943
944 if (in[0] == '\0')
945 return g_strdup("");
946
947 inlen = strlen(in);
948 seplen = strlen(sep);
949 outlen = inlen + (inlen - 1) / groupby * seplen;
950 ret = out = (char*)g_malloc(outlen + 1);
951
952 firstgrouplen = (inlen - 1) % groupby + 1;
953 memcpy(out, in, firstgrouplen);
954 in += firstgrouplen;
955 out += firstgrouplen;
956
957 while (*in != '\0') {
958 memcpy(out, sep, seplen);
959 out += seplen;
960 memcpy(out, in, groupby);
961 in += groupby;
962 out += groupby;
963 }
964
965 g_assert(out - ret == outlen);
966 *out = '\0';
967 return ret;
968 }
969
970 /**
971 * terminal_util_number_info:
972 * @str: a dec or hex number as string
973 *
974 * Returns: (transfer full): Useful info about @str, or %nullptr if it's too large
975 */
976 char *
terminal_util_number_info(const char * str)977 terminal_util_number_info (const char *str)
978 {
979 gs_free char *decstr = nullptr;
980 gs_free char *hextmp = nullptr;
981 gs_free char *hexstr = nullptr;
982 gs_free char *magnitudestr = nullptr;
983 gboolean exact = TRUE;
984 gboolean hex = FALSE;
985 const char *thousep;
986
987 /* Deliberately not handle octal */
988 if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) {
989 str += 2;
990 hex = TRUE;
991 }
992
993 errno = 0;
994 char* end;
995 gint64 num = g_ascii_strtoull(str, &end, hex ? 16 : 10);
996 if (errno || str == end || num == -1)
997 return nullptr;
998
999 /* No use in dec-hex conversion for so small numbers */
1000 if (num < 10) {
1001 return nullptr;
1002 }
1003
1004 /* Group the decimal digits */
1005 thousep = nl_langinfo(THOUSEP);
1006 if (thousep[0] != '\0') {
1007 /* If thousep is nonempty, use printf's magic which can handle
1008 more complex separating logics, e.g. 2+2+2+3 for some locales */
1009 decstr = g_strdup_printf("%'" G_GINT64_FORMAT, num);
1010 } else {
1011 /* If, however, thousep is empty, override it with a space so that we
1012 do always group the digits (that's the whole point of this feature;
1013 the choice of space guarantees not conflicting with the decimal separator) */
1014 gs_free char *tmp = g_strdup_printf("%" G_GINT64_FORMAT, num);
1015 thousep = " ";
1016 decstr = add_separators(tmp, thousep, 3);
1017 }
1018
1019 /* Group the hex digits by 4 using the same nonempty separator */
1020 hextmp = g_strdup_printf("%" G_GINT64_MODIFIER "x", (guint64)(num));
1021 hexstr = add_separators(hextmp, thousep, 4);
1022
1023 /* Find out the human-readable magnitude, e.g. 15.99 Mi */
1024 if (num >= 1024) {
1025 int power = 0;
1026 while (num >= 1024 * 1024) {
1027 power++;
1028 if (num % 1024 != 0)
1029 exact = FALSE;
1030 num /= 1024;
1031 }
1032 /* Show 2 fraction digits, always rounding downwards. Printf rounds floats to the nearest representable value,
1033 so do the calculation with integers until we get 100-fold the desired value, and then switch to float. */
1034 if (100 * num % 1024 != 0)
1035 exact = FALSE;
1036 num = 100 * num / 1024;
1037 magnitudestr = g_strdup_printf(" %s %.2f %ci", exact ? "=" : "≈", (double) num / 100, "KMGTPE"[power]);
1038 } else {
1039 magnitudestr = g_strdup("");
1040 }
1041
1042 return g_strdup_printf(hex ? "0x%2$s = %1$s%3$s" : "%s = 0x%s%s", decstr, hexstr, magnitudestr);
1043 }
1044
1045 /**
1046 * terminal_util_timestamp_info:
1047 * @str: a dec or hex number as string
1048 *
1049 * Returns: (transfer full): Formatted localtime if @str is decimal and looks like a timestamp, or %nullptr
1050 */
1051 char *
terminal_util_timestamp_info(const char * str)1052 terminal_util_timestamp_info (const char *str)
1053 {
1054 /* Bail out on hex numbers */
1055 if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) {
1056 return nullptr;
1057 }
1058
1059 /* Deliberately not handle octal */
1060 errno = 0;
1061 char* end;
1062 gint64 num = g_ascii_strtoull (str, &end, 10);
1063 if (errno || end == str || num == -1)
1064 return nullptr;
1065
1066 /* Java uses Unix time in milliseconds. */
1067 if (num >= 1000000000000 && num <= 1999999999999)
1068 num /= 1000;
1069
1070 /* Fun: use inclusive interval so you can right-click on these numbers
1071 * and check the human-readable time in gnome-terminal.
1072 * (They're Sep 9 2001 and May 18 2033 by the way.) */
1073 if (num < 1000000000 || num > 1999999999)
1074 return nullptr;
1075
1076 gs_unref_date_time GDateTime* date = g_date_time_new_from_unix_utc (num);
1077 if (date == nullptr)
1078 return nullptr;
1079
1080 return g_date_time_format(date, "%c");
1081 }
1082
1083 /**
1084 * terminal_util_uri_fixup:
1085 * @uri: The URI to verify and maybe fixup
1086 * @error: a #GError that is returned in case of errors
1087 *
1088 * Checks if gnome-terminal should attempt to handle the given URI,
1089 * and rewrites if necessary.
1090 *
1091 * Currently URIs of "file://some-other-host/..." are refused because
1092 * GIO (e.g. gtk_show_uri()) silently strips off the remote hostname
1093 * and opens the local counterpart which is incorrect and misleading.
1094 *
1095 * Furthermore, once the hostname is verified, it is stripped off to
1096 * avoid potential confusion around short hostname vs. fqdn, and to
1097 * work around bug 781800 (LibreOffice bug 107461).
1098 *
1099 * Returns: The possibly rewritten URI if gnome-terminal should attempt
1100 * to handle it, nullptr if it should refuse to handle.
1101 */
1102 char *
terminal_util_uri_fixup(const char * uri,GError ** error)1103 terminal_util_uri_fixup (const char *uri,
1104 GError **error)
1105 {
1106 gs_free char *filename;
1107 gs_free char *hostname;
1108
1109 filename = g_filename_from_uri (uri, &hostname, nullptr);
1110 if (filename != nullptr &&
1111 hostname != nullptr &&
1112 hostname[0] != '\0') {
1113 /* "file" scheme and nonempty hostname */
1114 if (g_ascii_strcasecmp (hostname, "localhost") == 0 ||
1115 g_ascii_strcasecmp (hostname, g_get_host_name()) == 0) {
1116 /* hostname corresponds to localhost */
1117 char const *slash1, *slash2, *slash3;
1118
1119 /* We shouldn't enter this branch in case of URIs like
1120 * "file:/etc/passwd", but just in case we do, or encounter
1121 * something else unexpected, leave the URI unchanged. */
1122 slash1 = strchr(uri, '/');
1123 if (slash1 == nullptr)
1124 return g_strdup (uri);
1125
1126 slash2 = slash1 + 1;
1127 if (*slash2 != '/')
1128 return g_strdup (uri);
1129
1130 slash3 = strchr(slash2 + 1, '/');
1131 if (slash3 == nullptr)
1132 return g_strdup (uri);
1133
1134 return g_strdup_printf("%.*s%s",
1135 (int) (slash2 + 1 - uri),
1136 uri,
1137 slash3);
1138 } else {
1139 /* hostname refers to another host (e.g. the OSC 8 escape sequence
1140 * was correctly emitted by a utility inside an ssh session) */
1141 g_set_error_literal (error,
1142 G_IO_ERROR,
1143 G_IO_ERROR_NOT_SUPPORTED,
1144 _("“file” scheme with remote hostname not supported"));
1145 return nullptr;
1146 }
1147 } else {
1148 /* "file" scheme without hostname, or some other scheme */
1149 return g_strdup (uri);
1150 }
1151 }
1152
1153 /**
1154 * terminal_util_hyperlink_uri_label:
1155 * @uri: a URI
1156 *
1157 * Formats @uri to be displayed in a tooltip.
1158 * Performs URI-decoding and converts IDN hostname to UTF-8.
1159 *
1160 * Returns: (transfer full): The human readable URI as plain text
1161 */
terminal_util_hyperlink_uri_label(const char * uri)1162 char *terminal_util_hyperlink_uri_label (const char *uri)
1163 {
1164 gs_free char *unesc = nullptr;
1165 gboolean replace_hostname;
1166
1167 if (uri == nullptr)
1168 return nullptr;
1169
1170 unesc = g_uri_unescape_string(uri, nullptr);
1171 if (unesc == nullptr)
1172 unesc = g_strdup(uri);
1173
1174 if (g_ascii_strncasecmp(unesc, "ftp://", 6) == 0 ||
1175 g_ascii_strncasecmp(unesc, "http://", 7) == 0 ||
1176 g_ascii_strncasecmp(unesc, "https://", 8) == 0) {
1177 gs_free char *unidn = nullptr;
1178 char *hostname = strchr(unesc, '/') + 2;
1179 char *hostname_end = strchrnul(hostname, '/');
1180 char save = *hostname_end;
1181 *hostname_end = '\0';
1182 unidn = g_hostname_to_unicode(hostname);
1183 replace_hostname = unidn != nullptr && g_ascii_strcasecmp(unidn, hostname) != 0;
1184 *hostname_end = save;
1185 if (replace_hostname) {
1186 char *new_unesc = g_strdup_printf("%.*s%s%s",
1187 (int) (hostname - unesc),
1188 unesc,
1189 unidn,
1190 hostname_end);
1191 g_free(unesc);
1192 unesc = new_unesc;
1193 }
1194 }
1195
1196 return g_utf8_make_valid (unesc, -1);
1197 }
1198
1199 #define TERMINAL_CACHE_DIR "gnome-terminal"
1200 #define TERMINAL_PRINT_SETTINGS_FILENAME "print-settings.ini"
1201 #define TERMINAL_PRINT_SETTINGS_GROUP_NAME "Print Settings"
1202 #define TERMINAL_PAGE_SETUP_GROUP_NAME "Page Setup"
1203
1204 #define KEYFILE_FLAGS_FOR_LOAD GKeyFileFlags(G_KEY_FILE_NONE)
1205 #define KEYFILE_FLAGS_FOR_SAVE GKeyFileFlags(G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS)
1206
1207 static char *
get_cache_dir(void)1208 get_cache_dir (void)
1209 {
1210 return g_build_filename (g_get_user_cache_dir (), TERMINAL_CACHE_DIR, nullptr);
1211 }
1212
1213 static gboolean
ensure_cache_dir(void)1214 ensure_cache_dir (void)
1215 {
1216 gs_free char *cache_dir;
1217 int r;
1218
1219 cache_dir = get_cache_dir ();
1220 errno = 0;
1221 r = g_mkdir_with_parents (cache_dir, 0700);
1222 if (r == -1 && errno != EEXIST)
1223 g_printerr ("Failed to create cache dir: %m\n");
1224 return r == 0;
1225 }
1226
1227 static char *
get_cache_filename(const char * filename)1228 get_cache_filename (const char *filename)
1229 {
1230 gs_free char *cache_dir = get_cache_dir ();
1231 return g_build_filename (cache_dir, filename, nullptr);
1232 }
1233
1234 static GKeyFile *
load_cache_keyfile(const char * filename,GKeyFileFlags flags,gboolean ignore_error)1235 load_cache_keyfile (const char *filename,
1236 GKeyFileFlags flags,
1237 gboolean ignore_error)
1238 {
1239 gs_free char *path;
1240 GKeyFile *keyfile;
1241
1242 path = get_cache_filename (filename);
1243 keyfile = g_key_file_new ();
1244 if (g_key_file_load_from_file (keyfile, path, flags, nullptr) || ignore_error)
1245 return keyfile;
1246
1247 g_key_file_unref (keyfile);
1248 return nullptr;
1249 }
1250
1251 static void
save_cache_keyfile(GKeyFile * keyfile,const char * filename)1252 save_cache_keyfile (GKeyFile *keyfile,
1253 const char *filename)
1254 {
1255 gs_free char *path = nullptr;
1256 gs_free char *data = nullptr;
1257 gsize len = 0;
1258
1259 if (!ensure_cache_dir ())
1260 return;
1261
1262 data = g_key_file_to_data (keyfile, &len, nullptr);
1263 if (data == nullptr || len == 0)
1264 return;
1265
1266 path = get_cache_filename (filename);
1267
1268 /* Ignore errors */
1269 GError *err = nullptr;
1270 if (!g_file_set_contents (path, data, len, &err)) {
1271 g_printerr ("Error saving print settings: %s\n", err->message);
1272 g_error_free (err);
1273 }
1274 }
1275
1276 static void
keyfile_remove_keys(GKeyFile * keyfile,const char * group_name,...)1277 keyfile_remove_keys (GKeyFile *keyfile,
1278 const char *group_name,
1279 ...)
1280 {
1281 va_list args;
1282 const char *key;
1283
1284 va_start (args, group_name);
1285 while ((key = va_arg (args, const char *)) != nullptr) {
1286 g_key_file_remove_key (keyfile, group_name, key, nullptr);
1287 }
1288 va_end (args);
1289 }
1290
1291 /**
1292 * terminal_util_load_print_settings:
1293 *
1294 * Loads the saved print settings, if any.
1295 */
1296 void
terminal_util_load_print_settings(GtkPrintSettings ** settings,GtkPageSetup ** page_setup)1297 terminal_util_load_print_settings (GtkPrintSettings **settings,
1298 GtkPageSetup **page_setup)
1299 {
1300 gs_unref_key_file GKeyFile *keyfile = load_cache_keyfile (TERMINAL_PRINT_SETTINGS_FILENAME,
1301 KEYFILE_FLAGS_FOR_LOAD,
1302 FALSE);
1303 if (keyfile == nullptr) {
1304 *settings = nullptr;
1305 *page_setup = nullptr;
1306 return;
1307 }
1308
1309 /* Ignore errors */
1310 *settings = gtk_print_settings_new_from_key_file (keyfile,
1311 TERMINAL_PRINT_SETTINGS_GROUP_NAME,
1312 nullptr);
1313 *page_setup = gtk_page_setup_new_from_key_file (keyfile,
1314 TERMINAL_PAGE_SETUP_GROUP_NAME,
1315 nullptr);
1316 }
1317
1318 /**
1319 * terminal_util_save_print_settings:
1320 * @settings: (allow-none): a #GtkPrintSettings
1321 * @page_setup: (allow-none): a #GtkPageSetup
1322 *
1323 * Saves the print settings.
1324 */
1325 void
terminal_util_save_print_settings(GtkPrintSettings * settings,GtkPageSetup * page_setup)1326 terminal_util_save_print_settings (GtkPrintSettings *settings,
1327 GtkPageSetup *page_setup)
1328 {
1329 gs_unref_key_file GKeyFile *keyfile = nullptr;
1330
1331 keyfile = load_cache_keyfile (TERMINAL_PRINT_SETTINGS_FILENAME,
1332 KEYFILE_FLAGS_FOR_SAVE,
1333 TRUE);
1334 g_assert (keyfile != nullptr);
1335
1336 if (settings != nullptr)
1337 gtk_print_settings_to_key_file (settings, keyfile,
1338 TERMINAL_PRINT_SETTINGS_GROUP_NAME);
1339
1340 /* Some keys are not desirable to persist; remove these.
1341 * This list comes from evince.
1342 */
1343 keyfile_remove_keys (keyfile,
1344 TERMINAL_PRINT_SETTINGS_GROUP_NAME,
1345 GTK_PRINT_SETTINGS_COLLATE,
1346 GTK_PRINT_SETTINGS_NUMBER_UP,
1347 GTK_PRINT_SETTINGS_N_COPIES,
1348 GTK_PRINT_SETTINGS_OUTPUT_URI,
1349 GTK_PRINT_SETTINGS_PAGE_RANGES,
1350 GTK_PRINT_SETTINGS_PAGE_SET,
1351 GTK_PRINT_SETTINGS_PRINT_PAGES,
1352 GTK_PRINT_SETTINGS_REVERSE,
1353 GTK_PRINT_SETTINGS_SCALE,
1354 nullptr);
1355
1356 if (page_setup != nullptr)
1357 gtk_page_setup_to_key_file (page_setup, keyfile,
1358 TERMINAL_PAGE_SETUP_GROUP_NAME);
1359
1360 /* Some keys are not desirable to persist; remove these.
1361 * This list comes from evince.
1362 */
1363 keyfile_remove_keys (keyfile,
1364 TERMINAL_PAGE_SETUP_GROUP_NAME,
1365 "page-setup-orientation",
1366 "page-setup-margin-bottom",
1367 "page-setup-margin-left",
1368 "page-setup-margin-right",
1369 "page-setup-margin-top",
1370 nullptr);
1371
1372 save_cache_keyfile (keyfile, TERMINAL_PRINT_SETTINGS_FILENAME);
1373 }
1374
1375 /*
1376 * terminal_util_translate_encoding:
1377 * @encoding: the encoding name
1378 *
1379 * Translates old encoding name to the one supported by ICU, or
1380 * to %nullptr if the encoding is not known to ICU.
1381 *
1382 * Returns: (transfer none): the translated encoding, or %nullptr if
1383 * not translation was possible.
1384 */
1385 const char*
terminal_util_translate_encoding(const char * encoding)1386 terminal_util_translate_encoding (const char *encoding)
1387 {
1388 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
1389 if (vte_get_encoding_supported (encoding))
1390 return encoding;
1391 G_GNUC_END_IGNORE_DEPRECATIONS;
1392
1393 /* ICU knows (or has aliases for) most of the old names, except the following */
1394 struct {
1395 const char *name;
1396 const char *replacement;
1397 } translations[] = {
1398 { "ARMSCII-8", nullptr }, /* apparently not supported by ICU */
1399 { "GEORGIAN-PS", nullptr }, /* no idea which charset this even is */
1400 { "ISO-IR-111", nullptr }, /* ISO-IR-111 refers to ECMA-94, but that
1401 * standard does not contain cyrillic letters.
1402 * ECMA-94 refers to ECMA-113 (ISO-IR-144),
1403 * whose assignment differs greatly from ISO-IR-111,
1404 * so it cannot be that either.
1405 */
1406 /* All the MAC_* charsets appear to be unknown to even glib iconv, so
1407 * why did we have them in our list in the first place?
1408 */
1409 { "MAC_DEVANAGARI", nullptr }, /* apparently not supported by ICU */
1410 { "MAC_FARSI", nullptr }, /* apparently not supported by ICU */
1411 { "MAC_GREEK", "x-MacGreek" },
1412 { "MAC_GUJARATI", nullptr }, /* apparently not supported by ICU */
1413 { "MAC_GURMUKHI", nullptr }, /* apparently not supported by ICU */
1414 { "MAC_ICELANDIC", nullptr }, /* apparently not supported by ICU */
1415 { "MAC_ROMANIAN", "x-macroman" }, /* not sure this is the right one */
1416 { "MAC_TURKISH", "x-MacTurkish" },
1417 { "MAC_UKRAINIAN", "x-MacUkraine" },
1418
1419 { "TCVN", nullptr }, /* apparently not supported by ICU */
1420 { "UHC", "cp949" },
1421 { "VISCII", nullptr }, /* apparently not supported by ICU */
1422
1423 /* ISO-2022-* are known to ICU, but they simply cannot work in vte as
1424 * I/O encoding, so don't even try.
1425 */
1426 { "ISO-2022-JP", nullptr },
1427 { "ISO-2022-KR", nullptr },
1428 };
1429
1430 const char *replacement = nullptr;
1431 for (guint i = 0; i < G_N_ELEMENTS (translations); ++i) {
1432 if (g_str_equal (encoding, translations[i].name)) {
1433 replacement = translations[i].replacement;
1434 break;
1435 }
1436 }
1437
1438 return replacement;
1439 }
1440
1441 /* BEGIN code copied from glib
1442 *
1443 * Copyright (C) 1995-1998 Peter Mattis, Spencer Kimball and Josh MacDonald
1444 *
1445 * Code originally under LGPL2+; used and modified here under GPL3+
1446 * Changes:
1447 * Remove win32 support.
1448 * Make @program nullable.
1449 * Use @path instead of getenv("PATH").
1450 * Use strchrnul
1451 */
1452
1453 /**
1454 * terminal_util_find_program_in_path:
1455 * @path: (type filename) (nullable): the search path (delimited by G_SEARCHPATH_SEPARATOR)
1456 * @program: (type filename) (nullable): the programme to find in @path
1457 *
1458 * Like g_find_program_in_path(), but uses @path instead of the
1459 * PATH environment variable as the search path.
1460 *
1461 * Returns: (type filename) (transfer full) (nullable): a newly allocated
1462 * string containing the full path to @program, or %nullptr if @program
1463 * could not be found in @path.
1464 */
1465 char *
terminal_util_find_program_in_path(const char * path,const char * program)1466 terminal_util_find_program_in_path (const char *path,
1467 const char *program)
1468 {
1469 const gchar *p;
1470 gchar *name, *freeme;
1471 gsize len;
1472 gsize pathlen;
1473
1474 if (program == nullptr)
1475 return nullptr;
1476
1477 /* If it is an absolute path, or a relative path including subdirectories,
1478 * don't look in PATH.
1479 */
1480 if (g_path_is_absolute (program)
1481 || strchr (program, G_DIR_SEPARATOR) != nullptr
1482 )
1483 {
1484 if (g_file_test (program, G_FILE_TEST_IS_EXECUTABLE) &&
1485 !g_file_test (program, G_FILE_TEST_IS_DIR))
1486 return g_strdup (program);
1487 else
1488 return nullptr;
1489 }
1490
1491 if (path == nullptr)
1492 {
1493 /* There is no 'PATH' in the environment. The default
1494 * search path in GNU libc is the current directory followed by
1495 * the path 'confstr' returns for '_CS_PATH'.
1496 */
1497
1498 /* In GLib we put . last, for security, and don't use the
1499 * unportable confstr(); UNIX98 does not actually specify
1500 * what to search if PATH is unset. POSIX may, dunno.
1501 */
1502
1503 path = "/bin:/usr/bin:.";
1504 }
1505
1506 len = strlen (program) + 1;
1507 pathlen = strlen (path);
1508 freeme = name = (char*)g_malloc (pathlen + len + 1);
1509
1510 /* Copy the file name at the top, including '\0' */
1511 memcpy (name + pathlen + 1, program, len);
1512 name = name + pathlen;
1513 /* And add the slash before the filename */
1514 *name = G_DIR_SEPARATOR;
1515
1516 p = path;
1517 do
1518 {
1519 char *startp;
1520
1521 path = p;
1522 p = strchrnul (path, G_SEARCHPATH_SEPARATOR);
1523
1524 if (p == path)
1525 /* Two adjacent colons, or a colon at the beginning or the end
1526 * of 'PATH' means to search the current directory.
1527 */
1528 startp = name + 1;
1529 else
1530 startp = (char*)memcpy (name - (p - path), path, p - path);
1531
1532 if (g_file_test (startp, G_FILE_TEST_IS_EXECUTABLE) &&
1533 !g_file_test (startp, G_FILE_TEST_IS_DIR))
1534 {
1535 gchar *ret;
1536 ret = g_strdup (startp);
1537 g_free (freeme);
1538 return ret;
1539 }
1540 }
1541 while (*p++ != '\0');
1542
1543 g_free (freeme);
1544 return nullptr;
1545 }
1546
1547 /* END code copied from glib */
1548
1549 /*
1550 * terminal_util_check_envv:
1551 * @strv:
1552 *
1553 * Validates that each element is of the form 'KEY=VALUE'.
1554 */
1555 gboolean
terminal_util_check_envv(char const * const * strv)1556 terminal_util_check_envv(char const* const* strv)
1557 {
1558 if (!strv)
1559 return TRUE;
1560
1561 for (int i = 0; strv[i]; ++i) {
1562 const char *str = strv[i];
1563 const char *equal = strchr(str, '=');
1564 if (equal == nullptr || equal == str)
1565 return FALSE;
1566 }
1567
1568 return TRUE;
1569 }
1570
1571 #define TERMINAL_SCHEMA_VERIFIER_ERROR (g_quark_from_static_string("TerminalSchemaVerifier"))
1572
1573 typedef enum {
1574 TERMINAL_SCHEMA_VERIFIER_SCHEMA_MISSING,
1575 TERMINAL_SCHEMA_VERIFIER_SCHEMA_PATH,
1576 TERMINAL_SCHEMA_VERIFIER_KEY_MISSING,
1577 TERMINAL_SCHEMA_VERIFIER_KEY_TYPE,
1578 TERMINAL_SCHEMA_VERIFIER_KEY_DEFAULT,
1579 TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_TYPE,
1580 TERMINAL_SCHEMA_VERIFIER_KEY_RANGE,
1581 TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_TYPE_UNKNOWN,
1582 TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_TYPE_MISMATCH,
1583 TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_ENUM_VALUE,
1584 TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_INTERVAL,
1585 TERMINAL_SCHEMA_VERIFIER_CHILD_MISSING,
1586 } TerminalSchemaVerifierError;
1587
1588 static gboolean
strv_contains(char const * const * strv,char const * str)1589 strv_contains(char const* const* strv,
1590 char const* str)
1591 {
1592 if (strv == nullptr)
1593 return FALSE;
1594
1595 for (size_t i = 0; strv[i]; i++) {
1596 if (g_str_equal (strv[i], str))
1597 return TRUE;
1598 }
1599
1600 return FALSE;
1601 }
1602
1603 static gboolean
schema_key_range_compatible(GSettingsSchema * source_schema,GSettingsSchemaKey * source_key,char const * key,GSettingsSchemaKey * reference_key,GError ** error)1604 schema_key_range_compatible(GSettingsSchema* source_schema,
1605 GSettingsSchemaKey* source_key,
1606 char const* key,
1607 GSettingsSchemaKey* reference_key,
1608 GError** error)
1609 {
1610 gs_unref_variant GVariant* source_range =
1611 g_settings_schema_key_get_range(source_key);
1612 gs_unref_variant GVariant* reference_range =
1613 g_settings_schema_key_get_range(reference_key);
1614
1615 char const* source_type = nullptr;
1616 gs_unref_variant GVariant* source_data = nullptr;
1617 g_variant_get(source_range, "(&sv)", &source_type, &source_data);
1618
1619 char const* reference_type = nullptr;
1620 gs_unref_variant GVariant* reference_data = nullptr;
1621 g_variant_get(reference_range, "(&sv)", &reference_type, &reference_data);
1622
1623 if (!g_str_equal(source_type, reference_type)) {
1624 g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
1625 TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_TYPE,
1626 "Schema \"%s\" key \"%s\" has range type \"%s\" but reference range type is \"%s\"",
1627 g_settings_schema_get_id(source_schema),
1628 key, source_type, reference_type);
1629 return FALSE;
1630 }
1631
1632 if (g_str_equal(reference_type, "type"))
1633 ; /* no constraints; this is fine */
1634 else if (g_str_equal(reference_type, "enum")) {
1635 size_t source_values_len = 0;
1636 gs_free char const** source_values = g_variant_get_strv(source_data, &source_values_len);
1637
1638 size_t reference_values_len = 0;
1639 gs_free char const** reference_values = g_variant_get_strv(reference_data, &reference_values_len);
1640
1641 /* Check that every enum value in source is valid according to the reference */
1642 for (size_t i = 0; i < source_values_len; ++i) {
1643 if (!strv_contains(reference_values, source_values[i])) {
1644 g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
1645 TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_ENUM_VALUE,
1646 "Schema \"%s\" key \"%s\" has enum value \"%s\" not in reference schema",
1647 g_settings_schema_get_id(source_schema),
1648 key, source_values[i]);
1649 return FALSE;
1650 }
1651 }
1652 } else if (g_str_equal(reference_type, "flags")) {
1653 /* Our schemas don't use flags. If that changes, need to implement this! */
1654 g_assert_not_reached();
1655 } else if (g_str_equal(reference_type, "range")) {
1656 if (!g_variant_is_of_type(source_data,
1657 g_variant_get_type(reference_data))) {
1658 char const* source_type_str = g_variant_get_type_string(source_data);
1659 char const* reference_type_str = g_variant_get_type_string(reference_data);
1660 g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
1661 TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_TYPE_MISMATCH,
1662 "Schema \"%s\" key \"%s\" has range type \"%s\" but reference range type is \"%s\"",
1663 g_settings_schema_get_id(source_schema),
1664 key, source_type_str, reference_type_str);
1665 return FALSE;
1666 }
1667
1668 gs_unref_variant GVariant* reference_min = nullptr;
1669 gs_unref_variant GVariant* reference_max = nullptr;
1670 g_variant_get(reference_data, "(**)", &reference_min, &reference_max);
1671
1672 gs_unref_variant GVariant* source_min = nullptr;
1673 gs_unref_variant GVariant* source_max = nullptr;
1674 g_variant_get(source_data, "(**)", &source_min, &source_max);
1675
1676 /* The source interval must be contained within the reference interval */
1677 if (g_variant_compare(source_min, reference_min) < 0 ||
1678 g_variant_compare(source_max, reference_max) > 0) {
1679 g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
1680 TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_INTERVAL,
1681 "Schema \"%s\" key \"%s\" has range interval not contained in reference range interval",
1682 g_settings_schema_get_id(source_schema), key);
1683 return FALSE;
1684 }
1685 } else {
1686 g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
1687 TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_TYPE_UNKNOWN,
1688 "Schema \"%s\" key \"%s\" has unknown range type \"%s\"",
1689 g_settings_schema_get_id(source_schema),
1690 key, reference_type);
1691 return FALSE;
1692 }
1693
1694 return TRUE;
1695 }
1696
1697 static gboolean
schema_verify_key(GSettingsSchema * source_schema,char const * key,GSettingsSchema * reference_schema,GError ** error)1698 schema_verify_key(GSettingsSchema* source_schema,
1699 char const* key,
1700 GSettingsSchema* reference_schema,
1701 GError** error)
1702 {
1703 if (!g_settings_schema_has_key(source_schema, key)) {
1704 g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
1705 TERMINAL_SCHEMA_VERIFIER_KEY_MISSING,
1706 "Schema \"%s\" has missing key \"%s\"",
1707 g_settings_schema_get_id(source_schema), key);
1708 return FALSE;
1709 }
1710
1711 gs_unref_settings_schema_key GSettingsSchemaKey* source_key =
1712 g_settings_schema_get_key(source_schema, key);
1713 g_assert_nonnull(source_key);
1714
1715 gs_unref_settings_schema_key GSettingsSchemaKey* reference_key =
1716 g_settings_schema_get_key(reference_schema, key);
1717 g_assert_nonnull(reference_key);
1718
1719 GVariantType const* source_type = g_settings_schema_key_get_value_type(source_key);
1720 GVariantType const* reference_type = g_settings_schema_key_get_value_type(reference_key);
1721 if (!g_variant_type_equal(source_type, reference_type)) {
1722 gs_free char* source_type_str = g_variant_type_dup_string(source_type);
1723 gs_free char* reference_type_str = g_variant_type_dup_string(reference_type);
1724 g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
1725 TERMINAL_SCHEMA_VERIFIER_KEY_TYPE,
1726 "Schema \"%s\" has type \"%s\" but reference type is \"%s\"",
1727 g_settings_schema_get_id(source_schema),
1728 source_type_str, reference_type_str);
1729 return FALSE;
1730 }
1731
1732 gs_unref_variant GVariant* source_default = g_settings_schema_key_get_default_value(source_key);
1733 if (!g_settings_schema_key_range_check(reference_key, source_default)) {
1734 gs_free char* source_value_str = g_variant_print(source_default, TRUE);
1735 g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
1736 TERMINAL_SCHEMA_VERIFIER_KEY_DEFAULT,
1737 "Schema \"%s\" default value \"%s\" does not conform to reference schema",
1738 g_settings_schema_get_id(source_schema), source_value_str);
1739 return FALSE;
1740 }
1741
1742 if (!schema_key_range_compatible(source_schema,
1743 source_key,
1744 key,
1745 reference_key,
1746 error))
1747 return FALSE;
1748
1749 return TRUE;
1750 }
1751
1752 static gboolean
schema_verify_child(GSettingsSchema * source_schema,char const * child_name,GSettingsSchema * reference_schema,GError ** error)1753 schema_verify_child(GSettingsSchema* source_schema,
1754 char const* child_name,
1755 GSettingsSchema* reference_schema,
1756 GError** error)
1757 {
1758 /* Should verify the child's schema ID is as expected and exists in
1759 * the source, but there appears to be no API to get the schema ID of
1760 * the child.
1761 *
1762 * We work around this missing verification by never calling
1763 * g_settings_get_child() and instead always constructing the child
1764 * GSettings directly; and the existence and correctness of that
1765 * schema is verified by the per-schema checks.
1766 */
1767
1768 return TRUE;
1769 }
1770
1771 static gboolean
schema_verify(GSettingsSchema * source_schema,GSettingsSchema * reference_schema,GError ** error)1772 schema_verify(GSettingsSchema* source_schema,
1773 GSettingsSchema* reference_schema,
1774 GError** error)
1775 {
1776 /* Verify path */
1777 char const* source_path = g_settings_schema_get_path(source_schema);
1778 char const* reference_path = g_settings_schema_get_path(reference_schema);
1779 if (g_strcmp0(source_path, reference_path) != 0) {
1780 g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
1781 TERMINAL_SCHEMA_VERIFIER_SCHEMA_PATH,
1782 "Schema \"%s\" has path \"%s\" but reference path is \"%s\"",
1783 g_settings_schema_get_id(source_schema),
1784 source_path ? source_path : "(null)",
1785 reference_path ? reference_path : "(null)");
1786 return FALSE;
1787 }
1788
1789 /* Verify keys */
1790 gs_strfreev char** keys = g_settings_schema_list_keys(reference_schema);
1791 if (keys) {
1792 for (int i = 0; keys[i]; ++i) {
1793 if (!schema_verify_key(source_schema,
1794 keys[i],
1795 reference_schema,
1796 error))
1797 return FALSE;
1798 }
1799 }
1800
1801 /* Verify child schemas */
1802 gs_strfreev char** source_children = g_settings_schema_list_children(source_schema);
1803 gs_strfreev char** reference_children = g_settings_schema_list_children(reference_schema);
1804 if (reference_children) {
1805 for (size_t i = 0; reference_children[i]; ++i) {
1806 if (!strv_contains((char const* const*)source_children, reference_children[i])) {
1807 g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
1808 TERMINAL_SCHEMA_VERIFIER_CHILD_MISSING,
1809 "Schema \"%s\" has missing child \"%s\"",
1810 g_settings_schema_get_id(source_schema),
1811 reference_children[i]);
1812 return FALSE;
1813 }
1814
1815 if (!schema_verify_child(source_schema,
1816 reference_children[i],
1817 reference_schema,
1818 error))
1819 return FALSE;
1820 }
1821 }
1822
1823 return TRUE;
1824 }
1825
1826 static gboolean
schemas_source_verify_schema_by_name(GSettingsSchemaSource * source,char const * schema_name,GSettingsSchemaSource * reference_source,GError ** error)1827 schemas_source_verify_schema_by_name(GSettingsSchemaSource* source,
1828 char const* schema_name,
1829 GSettingsSchemaSource* reference_source,
1830 GError** error)
1831 {
1832 gs_unref_settings_schema GSettingsSchema* source_schema =
1833 g_settings_schema_source_lookup(source, schema_name, TRUE /* recursive */);
1834
1835 if (!source_schema) {
1836 g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
1837 TERMINAL_SCHEMA_VERIFIER_SCHEMA_MISSING,
1838 "Schema \"%s\" is missing", schema_name);
1839 return FALSE;
1840 }
1841
1842 gs_unref_settings_schema GSettingsSchema* reference_schema =
1843 g_settings_schema_source_lookup(reference_source,
1844 schema_name,
1845 FALSE /* recursive */);
1846 g_assert_nonnull(reference_schema);
1847
1848 return schema_verify(source_schema,
1849 reference_schema,
1850 error);
1851 }
1852
1853 static gboolean
schemas_source_verify_schemas(GSettingsSchemaSource * source,char const * const * schemas,GSettingsSchemaSource * reference_source,GError ** error)1854 schemas_source_verify_schemas(GSettingsSchemaSource* source,
1855 char const* const* schemas,
1856 GSettingsSchemaSource* reference_source,
1857 GError** error)
1858 {
1859 if (!schemas)
1860 return TRUE;
1861
1862 for (int i = 0; schemas[i]; ++i) {
1863 if (!schemas_source_verify_schema_by_name(source,
1864 schemas[i],
1865 reference_source,
1866 error))
1867 return FALSE;
1868 }
1869
1870 return TRUE;
1871 }
1872
1873 static gboolean
schemas_source_verify(GSettingsSchemaSource * source,GSettingsSchemaSource * reference_source,GError ** error)1874 schemas_source_verify(GSettingsSchemaSource* source,
1875 GSettingsSchemaSource* reference_source,
1876 GError** error)
1877 {
1878 gs_strfreev char** reloc_schemas = nullptr;
1879 gs_strfreev char** nonreloc_schemas = nullptr;
1880
1881 g_settings_schema_source_list_schemas(reference_source,
1882 FALSE /* recursive */,
1883 &reloc_schemas,
1884 &nonreloc_schemas);
1885
1886 if (!schemas_source_verify_schemas(source,
1887 (char const* const*)reloc_schemas,
1888 reference_source,
1889 error))
1890 return FALSE;
1891
1892 if (!schemas_source_verify_schemas(source,
1893 (char const* const*)nonreloc_schemas,
1894 reference_source,
1895 error))
1896 return FALSE;
1897
1898 return TRUE;
1899 }
1900
1901
1902 GSettingsSchemaSource*
terminal_g_settings_schema_source_get_default(void)1903 terminal_g_settings_schema_source_get_default(void)
1904 {
1905 GSettingsSchemaSource* default_source = g_settings_schema_source_get_default();
1906
1907 gs_free_error GError* error = nullptr;
1908 GSettingsSchemaSource* reference_source =
1909 g_settings_schema_source_new_from_directory(TERM_PKGLIBDIR,
1910 nullptr /* parent source */,
1911 FALSE /* trusted */,
1912 &error);
1913 if (!reference_source) {
1914 /* Can only use the installed schemas, or abort here. */
1915 g_printerr("Failed to load reference schemas: %s\n"
1916 "Using unverified installed schemas.\n",
1917 error->message);
1918
1919 return g_settings_schema_source_ref(default_source);
1920 }
1921
1922 if (!schemas_source_verify(default_source, reference_source, &error)) {
1923 g_printerr("Installed schemas failed verification: %s\n"
1924 "Falling back to built-in reference schemas.\n",
1925 error->message);
1926
1927 return reference_source; /* transfer */
1928 }
1929
1930 /* Installed schemas verified; use them. */
1931 g_settings_schema_source_unref(reference_source);
1932 return g_settings_schema_source_ref(default_source);
1933 }
1934