1 /*
2  * This file is part of YAD.
3  *
4  * YAD is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * YAD is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with YAD. If not, see <http://www.gnu.org/licenses/>.
16  *
17  * Copyright (C) 2008-2019, Victor Ananjevsky <ananasik@gmail.com>
18  */
19 
20 #ifndef _GNU_SOURCE
21 #define _GNU_SOURCE 1
22 #endif
23 
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <unistd.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <errno.h>
30 
31 #include <sys/ipc.h>
32 #include <sys/shm.h>
33 
34 #include "yad.h"
35 
36 const YadStock yad_stock_items[] = {
37   { "yad-about", N_("About"), "help-about" },
38   { "yad-add",  N_("Add"), "list-add" },
39   { "yad-apply",  N_("Apply"), "gtk-apply" },
40   { "yad-cancel",  N_("Cancel"), "gtk-cancel" },
41   { "yad-clear",  N_("Clear"), "document-clear" },
42   { "yad-close",  N_("Close"), "window-close" },
43   { "yad-edit",  N_("Edit"), "gtk-edit" },
44   { "yad-execute",  N_("Execute"), "system-run" },
45   { "yad-no",  N_("No"), "gtk-no" },
46   { "yad-ok",  N_("OK"), "gtk-ok" },
47   { "yad-open",  N_("Open"), "document-open" },
48   { "yad-print",  N_("Print"), "document-print" },
49   { "yad-quit",  N_("Quit"), "application-exit" },
50   { "yad-refresh",  N_("Refresh"), "view-refresh" },
51   { "yad-remove",  N_("Remove"), "list-remove" },
52   { "yad-save",  N_("Save"), "document-save" },
53   { "yad-search", N_("Search"), "system-search" },
54   { "yad-settings",  N_("Settings"), "gtk-preferences" },
55   { "yad-yes",  N_("Yes"), "gtk-yes" }
56 };
57 
58 gboolean
stock_lookup(gchar * key,YadStock * it)59 stock_lookup (gchar *key, YadStock *it)
60 {
61   gint i;
62   gboolean found = FALSE;
63 
64   if (key == NULL || strncmp (key, "yad-", 4) != 0)
65     return FALSE;
66 
67   for (i = 0; i < YAD_STOCK_COUNT; i++)
68     {
69       if (strcmp (key, yad_stock_items[i].key) == 0)
70         {
71           it->key = yad_stock_items[i].key;
72           it->label = _(yad_stock_items[i].label);
73           it->icon = yad_stock_items[i].icon;
74           found = TRUE;
75           break;
76         }
77     }
78 
79   return found;
80 }
81 
82 GdkPixbuf *
get_pixbuf(gchar * name,YadIconSize size,gboolean force)83 get_pixbuf (gchar *name, YadIconSize size, gboolean force)
84 {
85   gint w, h;
86   GdkPixbuf *pb = NULL;
87   GError *err = NULL;
88 
89   if (size == YAD_BIG_ICON)
90     gtk_icon_size_lookup (GTK_ICON_SIZE_DIALOG, &w, &h);
91   else
92     gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &w, &h);
93 
94   if (g_file_test (name, G_FILE_TEST_EXISTS))
95     {
96       pb = gdk_pixbuf_new_from_file (name, &err);
97       if (!pb)
98         {
99           g_printerr ("yad: get_pixbuf(): %s\n", err->message);
100           g_error_free (err);
101         }
102     }
103   else
104     pb = gtk_icon_theme_load_icon (yad_icon_theme, name, MIN (w, h), GTK_ICON_LOOKUP_GENERIC_FALLBACK, NULL);
105 
106   if (!pb)
107     {
108       if (size == YAD_BIG_ICON)
109         pb = g_object_ref (big_fallback_image);
110       else
111         pb = g_object_ref (small_fallback_image);
112     }
113 
114   /* force scaling image to specific size */
115   if (!options.data.keep_icon_size && force && pb)
116     {
117       gint iw = gdk_pixbuf_get_width (pb);
118       gint ih = gdk_pixbuf_get_height (pb);
119 
120       if (w != iw || h != ih)
121         {
122           GdkPixbuf *spb;
123           spb = gdk_pixbuf_scale_simple (pb, w, h, GDK_INTERP_BILINEAR);
124           g_object_unref (pb);
125           pb = spb;
126         }
127     }
128 
129   return pb;
130 }
131 
132 gchar *
get_color(GdkRGBA * c)133 get_color (GdkRGBA *c)
134 {
135   gchar *res = NULL;
136 
137   switch (options.color_data.mode)
138     {
139     case YAD_COLOR_HEX:
140       if (options.color_data.alpha)
141         res = g_strdup_printf ("#%02X%02X%02X%02X", (int) (c->red * 255), (int) (c->green * 255), (int) (c->blue * 255), (int) (c->alpha * 255));
142       else
143         res = g_strdup_printf ("#%02X%02X%02X", (int) (c->red * 255), (int) (c->green * 255), (int) (c->blue * 255));
144       break;
145     case YAD_COLOR_RGB:
146       res = gdk_rgba_to_string (c);
147       break;
148     }
149   return res;
150 }
151 
152 void
update_preview(GtkFileChooser * chooser,GtkWidget * p)153 update_preview (GtkFileChooser * chooser, GtkWidget *p)
154 {
155   gchar *uri;
156   static gchar *normal_path = NULL;
157   static gchar *large_path = NULL;
158 
159   /* init thumbnails path */
160   if (!normal_path)
161     normal_path = g_build_filename (g_get_user_cache_dir (), "thumbnails", "normal", NULL);
162   if (!large_path)
163     large_path = g_build_filename (g_get_user_cache_dir (), "thumbnails", "large", NULL);
164 
165   /* load preview */
166   uri = gtk_file_chooser_get_preview_uri (chooser);
167   if (uri)
168     {
169       gchar *file;
170       GChecksum *chs;
171       GdkPixbuf *pb = NULL;
172 
173       chs = g_checksum_new (G_CHECKSUM_MD5);
174       g_checksum_update (chs, (const guchar *) uri, -1);
175       /* first try to get preview from large thumbnail */
176       file = g_strdup_printf ("%s/%s.png", large_path, g_checksum_get_string (chs));
177       if (options.common_data.large_preview && g_file_test (file, G_FILE_TEST_EXISTS))
178         pb = gdk_pixbuf_new_from_file (file, NULL);
179       else
180         {
181           /* try to get preview from normal thumbnail */
182           g_free (file);
183           file = g_strdup_printf ("%s/%s.png", normal_path, g_checksum_get_string (chs));
184           if (!options.common_data.large_preview && g_file_test (file, G_FILE_TEST_EXISTS))
185             pb = gdk_pixbuf_new_from_file (file, NULL);
186           else
187             {
188               /* try to create it */
189               g_free (file);
190               file = g_filename_from_uri (uri, NULL, NULL);
191               if (options.common_data.large_preview)
192                 pb = gdk_pixbuf_new_from_file_at_scale (file, 256, 256, TRUE, NULL);
193               else
194                 pb = gdk_pixbuf_new_from_file_at_scale (file, 128, 128, TRUE, NULL);
195               if (pb)
196                 {
197                   struct stat st;
198                   gchar *smtime;
199 
200                   stat (file, &st);
201                   smtime = g_strdup_printf ("%d", st.st_mtime);
202                   g_free (file);
203 
204                   /* save thumbnail */
205                   if (options.common_data.large_preview)
206                     {
207                       g_mkdir_with_parents (large_path, 0755);
208                       file = g_strdup_printf ("%s/%s.png", large_path, g_checksum_get_string (chs));
209                     }
210                   else
211                     {
212                       g_mkdir_with_parents (normal_path, 0755);
213                       file = g_strdup_printf ("%s/%s.png", normal_path, g_checksum_get_string (chs));
214                     }
215                   gdk_pixbuf_save (pb, file, "png", NULL,
216                                    "tEXt::Thumb::URI", uri,
217                                    "tEXt::Thumb::MTime", smtime,
218                                    NULL);
219                   g_free (smtime);
220                 }
221             }
222         }
223       g_checksum_free (chs);
224       g_free (file);
225 
226       if (pb)
227         {
228           gtk_image_set_from_pixbuf (GTK_IMAGE (p), pb);
229           g_object_unref (pb);
230           gtk_file_chooser_set_preview_widget_active (chooser, TRUE);
231         }
232       else
233         gtk_file_chooser_set_preview_widget_active (chooser, FALSE);
234 
235       g_free (uri);
236     }
237   else
238     gtk_file_chooser_set_preview_widget_active (chooser, FALSE);
239 }
240 
241 gchar **
split_arg(const gchar * str)242 split_arg (const gchar * str)
243 {
244   gchar **res;
245   gchar *p_col;
246 
247   res = g_new0 (gchar *, 3);
248 
249   p_col = g_strrstr (str, ":");
250   if (p_col && p_col[1])
251     {
252       res[0] = g_strndup (str, p_col - str);
253       res[1] = g_strdup (p_col + 1);
254     }
255   else
256     res[0] = g_strdup (str);
257 
258   return res;
259 }
260 
261 YadNTabs *
get_tabs(key_t key,gboolean create)262 get_tabs (key_t key, gboolean create)
263 {
264   YadNTabs *t = NULL;
265   int shmid, i, max_tab;
266 
267   /* get shared memory */
268 #ifndef STANDALONE
269   max_tab = g_settings_get_int (settings, "max-tab") + 1;
270 #else
271   max_tab = MAX_TABS + 1;
272 #endif
273   if (create)
274     {
275       if ((shmid = shmget (key, max_tab * sizeof (YadNTabs), IPC_CREAT | IPC_EXCL | 0644)) == -1)
276         {
277           g_printerr ("yad: cannot create shared memory for key %d: %s\n", key, strerror (errno));
278           return NULL;
279         }
280     }
281   else
282     {
283       if ((shmid = shmget (key, max_tab * sizeof (YadNTabs), 0)) == -1)
284         {
285           if (errno != ENOENT)
286             g_printerr ("yad: cannot get shared memory for key %d: %s\n", key, strerror (errno));
287           return NULL;
288         }
289     }
290 
291   /* attach shared memory */
292   if ((t = shmat (shmid, NULL, 0)) == (YadNTabs *) -1)
293     {
294       g_printerr ("yad: cannot attach shared memory for key %d: %s\n", key, strerror (errno));
295       return NULL;
296     }
297 
298   /* initialize memory */
299   if (create)
300     {
301       for (i = 1; i < max_tab; i++)
302         {
303           t[i].pid = -1;
304           t[i].xid = 0;
305         }
306       t[0].pid = shmid;
307       /* lastly, allow plugs to write shmem */
308       t[0].xid = 1;
309     }
310 
311   return t;
312 }
313 
314 GtkWidget *
get_label(gchar * str,guint border,GtkWidget * w)315 get_label (gchar *str, guint border, GtkWidget *w)
316 {
317   GtkWidget *t, *i, *l;
318   YadStock it;
319   gchar **vals;
320 
321   if (!str || !*str)
322     return gtk_label_new (NULL);
323 
324   l = i = NULL;
325 
326   t = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
327   gtk_container_set_border_width (GTK_CONTAINER (t), border);
328 
329   gtk_widget_set_halign (t, GTK_ALIGN_CENTER);
330   gtk_widget_set_valign (t, GTK_ALIGN_CENTER);
331 
332   vals = g_strsplit_set (str, options.common_data.item_separator, 3);
333   if (stock_lookup (vals[0], &it))
334     {
335       l = gtk_label_new_with_mnemonic (it.label);
336       i = gtk_image_new_from_pixbuf (get_pixbuf (it.icon, YAD_SMALL_ICON, TRUE));
337     }
338   else
339     {
340       if (vals[0] && *vals[0])
341         {
342           l = gtk_label_new (NULL);
343           if (!options.data.no_markup)
344             gtk_label_set_markup_with_mnemonic (GTK_LABEL (l), vals[0]);
345           else
346             gtk_label_set_text_with_mnemonic (GTK_LABEL (l), vals[0]);
347         }
348 
349       if (vals[1] && *vals[1])
350         i = gtk_image_new_from_pixbuf (get_pixbuf (vals[1], YAD_SMALL_ICON, TRUE));
351     }
352 
353   if (i)
354     gtk_container_add (GTK_CONTAINER (t), i);
355 
356   if (l)
357     {
358       if (w)
359         gtk_label_set_mnemonic_widget (GTK_LABEL (l), w);
360       gtk_label_set_xalign (GTK_LABEL (l), 0.0);
361       gtk_box_pack_start (GTK_BOX (t), l, FALSE, FALSE, 1);
362     }
363 
364   /* !!! must check both 1 and 2 values for !NULL */
365   if (vals[1] && vals[2] && *vals[2])
366     {
367       if (!options.data.no_markup)
368         gtk_widget_set_tooltip_markup (t, vals[2]);
369       else
370         gtk_widget_set_tooltip_text (t, vals[2]);
371     }
372 
373   g_strfreev (vals);
374 
375   gtk_widget_show_all (t);
376 
377   return t;
378 }
379 
380 gchar *
escape_str(gchar * str)381 escape_str (gchar *str)
382 {
383   gchar *res, *buf = str;
384   guint i = 0, len;
385 
386   if (!str)
387     return NULL;
388 
389   len = strlen (str);
390   res = (gchar *) calloc (len * 2 + 1, sizeof (gchar));
391 
392   while (*buf)
393     {
394       switch (*buf)
395         {
396         case '\n':
397           strcpy (res + i, "\\n");
398           i += 2;
399           break;
400         case '\t':
401           strcpy (res + i, "\\t");
402           i += 2;
403           break;
404         case '\\':
405           strcpy (res + i, "\\\\");
406           i += 2;
407           break;
408         default:
409           *(res + i) = *buf;
410           i++;
411           break;
412         }
413       buf++;
414     }
415   res[i] = '\0';
416 
417   return res;
418 }
419 
420 gchar *
escape_char(gchar * str,gchar ch)421 escape_char (gchar *str, gchar ch)
422 {
423   gchar *res, *buf = str;
424   guint i = 0, len;
425 
426   if (!str)
427     return NULL;
428 
429   len = strlen (str);
430   res = (gchar *) calloc (len * 2 + 1, sizeof (gchar));
431 
432   while (*buf)
433     {
434       if (*buf == ch)
435         {
436           strcpy (res + i, "\\\"");
437           i += 2;
438         }
439       else
440         {
441           *(res + i) = *buf;
442           i++;
443         }
444       buf++;
445     }
446   res[i] = '\0';
447 
448   return res;
449 }
450 
451 gboolean
check_complete(GtkEntryCompletion * c,const gchar * key,GtkTreeIter * iter,gpointer data)452 check_complete (GtkEntryCompletion *c, const gchar *key, GtkTreeIter *iter, gpointer data)
453 {
454   gchar *value = NULL;
455   GtkTreeModel *model = gtk_entry_completion_get_model (c);
456   gboolean found = FALSE;
457 
458   if (!model || !key || !key[0])
459     return FALSE;
460 
461   gtk_tree_model_get (model, iter, 0, &value, -1);
462 
463   if (value)
464     {
465       gchar **words = NULL;
466       guint i = 0;
467 
468       switch (options.common_data.complete)
469         {
470         case YAD_COMPLETE_ANY:
471           words = g_strsplit_set (key, " \t", -1);
472           while (words[i])
473             {
474               if (strcasestr (value, words[i]) != NULL)
475                 {
476                   /* found one of the words */
477                   found = TRUE;
478                   break;
479                 }
480               i++;
481             }
482           break;
483         case YAD_COMPLETE_ALL:
484           words = g_strsplit_set (key, " \t", -1);
485           found = TRUE;
486           while (words[i])
487             {
488               if (strcasestr (value, words[i]) == NULL)
489                 {
490                   /* not found one of the words */
491                   found = FALSE;
492                   break;
493                 }
494               i++;
495             }
496           break;
497         case YAD_COMPLETE_REGEX:
498           found = g_regex_match_simple (key, value, G_REGEX_CASELESS | G_REGEX_OPTIMIZE, G_REGEX_MATCH_NOTEMPTY);
499           break;
500         default: ;
501         }
502 
503       if (words)
504         g_strfreev (words);
505     }
506 
507   return found;
508 }
509 
510 void
parse_geometry()511 parse_geometry ()
512 {
513   gchar *geom, *ptr;
514   gint w = -1, h = -1, x = 0, y = 0;
515   gboolean usexy = FALSE;
516   guint i = 0;
517 
518   if (!options.data.geometry)
519     return;
520 
521   geom = options.data.geometry;
522 
523   if (geom[i] != '+' && geom[i] != '-')
524     {
525       ptr = geom + i;
526       w = atoi (ptr);
527 
528       while (geom[i] && geom[i] != 'x') i++;
529 
530       if (!geom[i])
531         return;
532 
533       ptr = geom + i + 1;
534       h = atoi (ptr);
535 
536       while (geom[i] && geom[i] != '-' && geom[i] != '+') i++;
537     }
538 
539   if (geom[i])
540     {
541       usexy = TRUE;
542 
543       ptr = geom + i;
544       x = atoi (ptr);
545 
546       i++;
547       while (geom[i] && geom[i] != '-' && geom[i] != '+') i++;
548 
549       if (!geom[i])
550         return;
551 
552       ptr = geom + i;
553       y = atoi (ptr);
554     }
555 
556   if (w != -1)
557     options.data.width = w;
558   if (h != -1)
559     options.data.height = h;
560   options.data.posx = x;
561   options.data.posy = y;
562   options.data.use_posx = options.data.use_posy = usexy;
563 }
564 
565 gboolean
get_bool_val(gchar * str)566 get_bool_val (gchar *str)
567 {
568   if (!str || !str[0])
569     return FALSE;
570 
571   switch (str[0])
572     {
573     case '1':
574     case 't':
575     case 'T':
576     case 'y':
577     case 'Y':
578       if (strcasecmp (str, "t") == 0 || strcasecmp (str, "true") == 0 ||
579           strcasecmp (str, "y") == 0 || strcasecmp (str, "yes") == 0 ||
580           strcmp (str, "1") == 0)
581         return TRUE;
582       break;
583     case '0':
584     case 'f':
585     case 'F':
586     case 'n':
587     case 'N':
588       if (strcasecmp (str, "f") == 0 || strcasecmp (str, "false") == 0 ||
589           strcasecmp (str, "n") == 0 || strcasecmp (str, "no") == 0 ||
590           strcmp (str, "0") == 0)
591         return FALSE;
592       break;
593     case 'o':
594     case 'O':
595       if (strcasecmp (str, "on") == 0)
596         return TRUE;
597       else if (strcasecmp (str, "off") == 0)
598         return FALSE;
599       break;
600     default: ;
601     }
602 
603  g_printerr ("yad: wrong boolean value '%s'\n", str);
604  return FALSE;
605 }
606 
607 gchar *
print_bool_val(gboolean val)608 print_bool_val (gboolean val)
609 {
610   gchar *ret = "";
611 
612   switch (options.common_data.bool_fmt)
613     {
614     case YAD_BOOL_FMT_UT:
615       ret = val ? "TRUE" : "FALSE";
616       break;
617     case YAD_BOOL_FMT_UY:
618       ret = val ? "YES" : "NO";
619       break;
620     case YAD_BOOL_FMT_UO:
621       ret = val ? "ON" : "OFF";
622       break;
623     case YAD_BOOL_FMT_LT:
624       ret = val ? "true" : "false";
625       break;
626     case YAD_BOOL_FMT_LY:
627       ret = val ? "yes" : "no";
628       break;
629     case YAD_BOOL_FMT_LO:
630       ret = val ? "on" : "off";
631       break;
632     case YAD_BOOL_FMT_1:
633       ret = val ? "1" : "0";
634       break;
635     }
636 
637   return ret;
638 }
639 
640 gint
run_command_sync(gchar * cmd,gchar ** out)641 run_command_sync (gchar *cmd, gchar **out)
642 {
643   gint ret = 0;
644   gchar *full_cmd = NULL;
645   GError *err = NULL;
646 
647   if (options.data.use_interp)
648     {
649       if (g_strstr_len (options.data.interp, -1, "%s") != NULL)
650         full_cmd = g_strdup_printf (options.data.interp, cmd);
651       else
652         full_cmd = g_strdup_printf ("%s %s", options.data.interp, cmd);
653     }
654   else
655     full_cmd = g_strdup (cmd);
656 
657   if (!g_spawn_command_line_sync (full_cmd, out, NULL, &ret, &err))
658     {
659       if (options.debug)
660         g_printerr (_("WARNING: Run command failed: %s\n"), err->message);
661       g_error_free (err);
662     }
663 
664   g_free (full_cmd);
665   return ret;
666 }
667 
668 void
run_command_async(gchar * cmd)669 run_command_async (gchar *cmd)
670 {
671   gchar *full_cmd = NULL;
672   GError *err = NULL;
673 
674   if (options.data.use_interp)
675     {
676       if (g_strstr_len (options.data.interp, -1, "%s") != NULL)
677         full_cmd = g_strdup_printf (options.data.interp, cmd);
678       else
679         full_cmd = g_strdup_printf ("%s %s", options.data.interp, cmd);
680     }
681   else
682     full_cmd = g_strdup (cmd);
683 
684   if (!g_spawn_command_line_async (full_cmd, &err))
685     {
686       if (options.debug)
687         g_printerr (_("WARNING: Run command failed: %s\n"), err->message);
688       g_error_free (err);
689     }
690 
691   g_free (full_cmd);
692 }
693 
694 gchar *
pango_to_css(gchar * font)695 pango_to_css (gchar *font)
696 {
697   PangoFontDescription *desc;
698   PangoFontMask mask;
699   GString *str;
700   gchar *res;
701 
702   str = g_string_new (NULL);
703 
704   desc = pango_font_description_from_string (font);
705   mask = pango_font_description_get_set_fields (desc);
706 
707   if (mask & PANGO_FONT_MASK_STYLE)
708     {
709       switch (pango_font_description_get_style (desc))
710         {
711         case PANGO_STYLE_OBLIQUE:
712           g_string_append (str, "oblique ");
713           break;
714         case PANGO_STYLE_ITALIC:
715           g_string_append (str, "italic ");
716           break;
717         }
718     }
719   if (mask & PANGO_FONT_MASK_VARIANT)
720     {
721       if (pango_font_description_get_variant (desc) == PANGO_VARIANT_SMALL_CAPS)
722         g_string_append (str, "small-caps ");
723     }
724 
725   if (mask & PANGO_FONT_MASK_WEIGHT)
726     {
727       switch (pango_font_description_get_weight (desc))
728         {
729         case PANGO_WEIGHT_THIN:
730           g_string_append (str, "Thin ");
731           break;
732         case PANGO_WEIGHT_ULTRALIGHT:
733           g_string_append (str, "Ultralight ");
734           break;
735         case PANGO_WEIGHT_LIGHT:
736           g_string_append (str, "Light ");
737           break;
738         case PANGO_WEIGHT_SEMILIGHT:
739           g_string_append (str, "Semilight ");
740           break;
741         case PANGO_WEIGHT_BOOK:
742           g_string_append (str, "Book ");
743           break;
744         case PANGO_WEIGHT_MEDIUM:
745           g_string_append (str, "Medium ");
746           break;
747         case PANGO_WEIGHT_SEMIBOLD:
748           g_string_append (str, "Semibold ");
749           break;
750         case PANGO_WEIGHT_BOLD:
751           g_string_append (str, "Bold ");
752           break;
753         case PANGO_WEIGHT_ULTRABOLD:
754           g_string_append (str, "Ultrabold ");
755           break;
756         case PANGO_WEIGHT_HEAVY:
757           g_string_append (str, "Heavy ");
758           break;
759         case PANGO_WEIGHT_ULTRAHEAVY:
760           g_string_append (str, "Ultraheavy ");
761           break;
762         }
763     }
764 
765   if (mask & PANGO_FONT_MASK_SIZE)
766     {
767       if (pango_font_description_get_size_is_absolute (desc))
768         g_string_append_printf (str, "%dpx ", pango_font_description_get_size (desc) / PANGO_SCALE);
769       else
770         g_string_append_printf (str, "%dpt ", pango_font_description_get_size (desc) / PANGO_SCALE);
771     }
772 
773   if (mask & PANGO_FONT_MASK_FAMILY)
774     g_string_append (str, pango_font_description_get_family (desc));
775 
776   if (str->str)
777     res = str->str;
778   else
779     res = g_strdup (font);
780 
781   return res;
782 }
783 
784 void
open_uri(const gchar * uri)785 open_uri (const gchar *uri)
786 {
787   gchar *cmdline;
788 
789   if (!uri || !uri[0])
790     return;
791 
792   if (g_strstr_len (options.data.uri_handler, -1, "%s") != NULL)
793     cmdline = g_strdup_printf (options.data.uri_handler, uri);
794   else
795     cmdline = g_strdup_printf ("%s '%s'", options.data.uri_handler, uri);
796   run_command_async (cmdline);
797   g_free (cmdline);
798 }
799 
800 #ifdef HAVE_SPELL
801 void
show_langs()802 show_langs ()
803 {
804   const GList *lng;
805 
806   for (lng = gspell_language_get_available (); lng; lng = lng->next)
807     {
808       const GspellLanguage *l = lng->data;
809       g_print ("%s\n", gspell_language_get_code (l));
810     }
811 }
812 #endif
813 
814 #ifdef HAVE_SOURCEVIEW
815 void
show_themes()816 show_themes ()
817 {
818   GtkSourceStyleSchemeManager *sm;
819   const gchar **si;
820   guint i = 0;
821 
822   sm = gtk_source_style_scheme_manager_get_default ();
823   if ((si = (const gchar **) gtk_source_style_scheme_manager_get_scheme_ids (sm)) == NULL)
824     return;
825 
826   while (si[i])
827     {
828       GtkSourceStyleScheme *s = gtk_source_style_scheme_manager_get_scheme (sm, si[i]);
829       g_print ("%s\n", gtk_source_style_scheme_get_name (s));
830       i++;
831     }
832 }
833 #endif
834