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