1 /*
2  * xed-utils.c
3  * This file is part of xed
4  *
5  * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
6  * Copyright (C) 2000, 2002 Chema Celorio, Paolo Maggi
7  * Copyright (C) 2003-2005 Paolo Maggi
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin St, Fifth Floor,
22  * Boston, MA 02110-1301, USA.
23  */
24 
25 /*
26  * Modified by the xed Team, 1998-2005. See the AUTHORS file for a
27  * list of people on the xed Team.
28  * See the ChangeLog files for a list of changes.
29  *
30  * $Id$
31  */
32 
33 #include <config.h>
34 #include <errno.h>
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <sys/time.h>
38 #include <fcntl.h>
39 #include <string.h>
40 #include <glib.h>
41 #include <glib/gi18n.h>
42 #include <gio/gio.h>
43 #include <gtksourceview/gtksource.h>
44 
45 #include "xed-utils.h"
46 #include "xed-document.h"
47 #include "xed-debug.h"
48 
49 /* For the workspace/viewport stuff */
50 #ifdef GDK_WINDOWING_X11
51 #include <gdk/gdkx.h>
52 #include <X11/Xatom.h>
53 #endif
54 
55 #define STDIN_DELAY_MICROSECONDS 100000
56 
57 /* FIXME: remove this with gtk 2.12, it has gdk_color_to_string */
58 gchar *
xed_gdk_color_to_string(GdkColor color)59 xed_gdk_color_to_string (GdkColor color)
60 {
61     return g_strdup_printf ("#%04x%04x%04x", color.red, color.green, color.blue);
62 }
63 
64 gint
xed_string_to_clamped_gint(const gchar * text)65 xed_string_to_clamped_gint (const gchar *text)
66 {
67     long int long_line = strtol (text, NULL, 10);
68     return CLAMP (long_line, INT_MIN, INT_MAX);
69 }
70 
71 /*
72  * n: len of the string in bytes
73  */
74 gboolean
g_utf8_caselessnmatch(const char * s1,const char * s2,gssize n1,gssize n2)75 g_utf8_caselessnmatch (const char *s1,
76                        const char *s2,
77                        gssize      n1,
78                        gssize      n2)
79 {
80     gchar *casefold;
81     gchar *normalized_s1;
82     gchar *normalized_s2;
83     gint len_s1;
84     gint len_s2;
85     gboolean ret = FALSE;
86 
87     g_return_val_if_fail (s1 != NULL, FALSE);
88     g_return_val_if_fail (s2 != NULL, FALSE);
89     g_return_val_if_fail (n1 > 0, FALSE);
90     g_return_val_if_fail (n2 > 0, FALSE);
91 
92     casefold = g_utf8_casefold (s1, n1);
93     normalized_s1 = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
94     g_free (casefold);
95 
96     casefold = g_utf8_casefold (s2, n2);
97     normalized_s2 = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
98     g_free (casefold);
99 
100     len_s1 = strlen (normalized_s1);
101     len_s2 = strlen (normalized_s2);
102 
103     if (len_s1 < len_s2)
104     {
105         goto finally_2;
106     }
107 
108     ret = (strncmp (normalized_s1, normalized_s2, len_s2) == 0);
109 
110 finally_2:
111     g_free (normalized_s1);
112     g_free (normalized_s2);
113 
114     return ret;
115 }
116 
117 /**
118  * xed_utils_set_atk_name_description:
119  * @widget: The Gtk widget for which name/description to be set
120  * @name: Atk name string
121  * @description: Atk description string
122  *
123  * This function sets up name and description
124  * for a specified gtk widget.
125  */
126 void
xed_utils_set_atk_name_description(GtkWidget * widget,const gchar * name,const gchar * description)127 xed_utils_set_atk_name_description (GtkWidget   *widget,
128                                     const gchar *name,
129                                     const gchar *description)
130 {
131     AtkObject *aobj;
132 
133     aobj = gtk_widget_get_accessible (widget);
134 
135     if (!(GTK_IS_ACCESSIBLE (aobj)))
136     {
137         return;
138     }
139 
140     if (name)
141     {
142         atk_object_set_name (aobj, name);
143     }
144 
145     if (description)
146     {
147         atk_object_set_description (aobj, description);
148     }
149 }
150 
151 /**
152  * xed_set_atk_relation:
153  * @obj1: specified widget.
154  * @obj2: specified widget.
155  * @rel_type: the type of relation to set up.
156  *
157  * This function establishes atk relation
158  * between 2 specified widgets.
159  */
160 void
xed_utils_set_atk_relation(GtkWidget * obj1,GtkWidget * obj2,AtkRelationType rel_type)161 xed_utils_set_atk_relation (GtkWidget       *obj1,
162                             GtkWidget       *obj2,
163                             AtkRelationType  rel_type )
164 {
165     AtkObject *atk_obj1, *atk_obj2;
166     AtkRelationSet *relation_set;
167     AtkObject *targets[1];
168     AtkRelation *relation;
169 
170     atk_obj1 = gtk_widget_get_accessible (obj1);
171     atk_obj2 = gtk_widget_get_accessible (obj2);
172 
173     if (!(GTK_IS_ACCESSIBLE (atk_obj1)) || !(GTK_IS_ACCESSIBLE (atk_obj2)))
174     {
175         return;
176     }
177 
178     relation_set = atk_object_ref_relation_set (atk_obj1);
179     targets[0] = atk_obj2;
180 
181     relation = atk_relation_new (targets, 1, rel_type);
182     atk_relation_set_add (relation_set, relation);
183 
184     g_object_unref (G_OBJECT (relation));
185 }
186 
187 void
xed_warning(GtkWindow * parent,const gchar * format,...)188 xed_warning (GtkWindow  *parent,
189             const gchar *format,
190             ...)
191 {
192     va_list         args;
193     gchar          *str;
194     GtkWidget      *dialog;
195     GtkWindowGroup *wg = NULL;
196 
197     g_return_if_fail (format != NULL);
198 
199     if (parent != NULL)
200     {
201         wg = gtk_window_get_group (parent);
202     }
203 
204     va_start (args, format);
205     str = g_strdup_vprintf (format, args);
206     va_end (args);
207 
208     dialog = gtk_message_dialog_new_with_markup (parent,
209                                                  GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
210                                                  GTK_MESSAGE_ERROR,
211                                                  GTK_BUTTONS_OK,
212                                                  "%s", str);
213 
214     g_free (str);
215 
216     if (wg != NULL)
217     {
218         gtk_window_group_add_window (wg, GTK_WINDOW (dialog));
219     }
220 
221     gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
222 
223     gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
224 
225     g_signal_connect (G_OBJECT (dialog), "response",
226                       G_CALLBACK (gtk_widget_destroy), NULL);
227 
228     gtk_widget_show (dialog);
229 }
230 
231 /*
232  * Doubles underscore to avoid spurious menu accels.
233  */
234 gchar *
xed_utils_escape_underscores(const gchar * text,gssize length)235 xed_utils_escape_underscores (const gchar* text,
236                               gssize       length)
237 {
238     GString *str;
239     const gchar *p;
240     const gchar *end;
241 
242     g_return_val_if_fail (text != NULL, NULL);
243 
244     if (length < 0)
245     {
246         length = strlen (text);
247     }
248 
249     str = g_string_sized_new (length);
250 
251     p = text;
252     end = text + length;
253 
254     while (p != end)
255     {
256         const gchar *next;
257         next = g_utf8_next_char (p);
258 
259         switch (*p)
260         {
261             case '_':
262                 g_string_append (str, "__");
263                 break;
264             default:
265                 g_string_append_len (str, p, next - p);
266                 break;
267         }
268 
269         p = next;
270     }
271 
272     return g_string_free (str, FALSE);
273 }
274 
275 /* the following functions are taken from eel */
276 
277 static gchar *
xed_utils_str_truncate(const gchar * string,guint truncate_length,gboolean middle)278 xed_utils_str_truncate (const gchar *string,
279                         guint        truncate_length,
280                         gboolean     middle)
281 {
282     GString     *truncated;
283     guint        length;
284     guint        n_chars;
285     guint        num_left_chars;
286     guint        right_offset;
287     guint        delimiter_length;
288     const gchar *delimiter = "\342\200\246";
289 
290     g_return_val_if_fail (string != NULL, NULL);
291 
292     length = strlen (string);
293 
294     g_return_val_if_fail (g_utf8_validate (string, length, NULL), NULL);
295 
296     /* It doesnt make sense to truncate strings to less than
297      * the size of the delimiter plus 2 characters (one on each
298      * side)
299      */
300     delimiter_length = g_utf8_strlen (delimiter, -1);
301     if (truncate_length < (delimiter_length + 2))
302     {
303         return g_strdup (string);
304     }
305 
306     n_chars = g_utf8_strlen (string, length);
307 
308     /* Make sure the string is not already small enough. */
309     if (n_chars <= truncate_length)
310     {
311         return g_strdup (string);
312     }
313 
314     /* Find the 'middle' where the truncation will occur. */
315     if (middle)
316     {
317         num_left_chars = (truncate_length - delimiter_length) / 2;
318         right_offset = n_chars - truncate_length + num_left_chars + delimiter_length;
319 
320         truncated = g_string_new_len (string, g_utf8_offset_to_pointer (string, num_left_chars) - string);
321         g_string_append (truncated, delimiter);
322         g_string_append (truncated, g_utf8_offset_to_pointer (string, right_offset));
323     }
324     else
325     {
326         num_left_chars = truncate_length - delimiter_length;
327         truncated = g_string_new_len (string, g_utf8_offset_to_pointer (string, num_left_chars) - string);
328         g_string_append (truncated, delimiter);
329     }
330 
331     return g_string_free (truncated, FALSE);
332 }
333 
334 gchar *
xed_utils_str_middle_truncate(const gchar * string,guint truncate_length)335 xed_utils_str_middle_truncate (const gchar *string,
336                                guint        truncate_length)
337 {
338     return xed_utils_str_truncate (string, truncate_length, TRUE);
339 }
340 
341 gchar *
xed_utils_str_end_truncate(const gchar * string,guint truncate_length)342 xed_utils_str_end_truncate (const gchar *string,
343                             guint        truncate_length)
344 {
345     return xed_utils_str_truncate (string, truncate_length, FALSE);
346 }
347 
348 gchar *
xed_utils_make_valid_utf8(const char * name)349 xed_utils_make_valid_utf8 (const char *name)
350 {
351     GString *string;
352     const char *remainder, *invalid;
353     int remaining_bytes, valid_bytes;
354 
355     g_return_val_if_fail (name != NULL, NULL);
356 
357     string = NULL;
358     remainder = name;
359     remaining_bytes = strlen (name);
360 
361     while (remaining_bytes != 0)
362     {
363         if (g_utf8_validate (remainder, remaining_bytes, &invalid))
364         {
365             break;
366         }
367         valid_bytes = invalid - remainder;
368 
369         if (string == NULL)
370         {
371             string = g_string_sized_new (remaining_bytes);
372         }
373         g_string_append_len (string, remainder, valid_bytes);
374         /* append U+FFFD REPLACEMENT CHARACTER */
375         g_string_append (string, "\357\277\275");
376 
377         remaining_bytes -= valid_bytes + 1;
378         remainder = invalid + 1;
379     }
380 
381     if (string == NULL)
382     {
383         return g_strdup (name);
384     }
385 
386     g_string_append (string, remainder);
387 
388     g_assert (g_utf8_validate (string->str, -1, NULL));
389 
390     return g_string_free (string, FALSE);
391 }
392 
393 /**
394  * xed_utils_uri_get_dirname:
395  * @uri: the URI to extract the dirname from
396  *
397  * Note: this function replace home dir with ~
398  */
399 gchar *
xed_utils_uri_get_dirname(const gchar * uri)400 xed_utils_uri_get_dirname (const gchar *uri)
401 {
402     gchar *res;
403     gchar *str;
404 
405     g_return_val_if_fail (uri != NULL, NULL);
406 
407     /* CHECK: does it work with uri chaining? - Paolo */
408     str = g_path_get_dirname (uri);
409     g_return_val_if_fail (str != NULL, g_strdup ("."));
410 
411     if ((strlen (str) == 1) && (*str == '.'))
412     {
413         g_free (str);
414 
415         return NULL;
416     }
417 
418     res = xed_utils_replace_home_dir_with_tilde (str);
419 
420     g_free (str);
421 
422     return res;
423 }
424 
425 /* the following two functions are courtesy of galeon */
426 
427 /**
428  * xed_utils_get_current_workspace:
429  * @screen: a #GdkScreen
430  *
431  * Get the currently visible workspace for the #GdkScreen.
432  *
433  * If the X11 window property isn't found, 0 (the first workspace)
434  * is returned.
435  */
436 guint
xed_utils_get_current_workspace(GdkScreen * screen)437 xed_utils_get_current_workspace (GdkScreen *screen)
438 {
439 #ifdef GDK_WINDOWING_X11
440     GdkWindow *root_win;
441     GdkDisplay *display;
442     guint ret = 0;
443 
444     g_return_val_if_fail (GDK_IS_SCREEN (screen), 0);
445 
446     root_win = gdk_screen_get_root_window (screen);
447     display = gdk_screen_get_display (screen);
448 
449     if (GDK_IS_X11_DISPLAY (display))
450     {
451         Atom type;
452         gint format;
453         gulong nitems;
454         gulong bytes_after;
455         guint *current_desktop;
456         gint err, result;
457 
458         gdk_x11_display_error_trap_push (display);
459         result = XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display), GDK_WINDOW_XID (root_win),
460                                      gdk_x11_get_xatom_by_name_for_display (display, "_NET_CURRENT_DESKTOP"),
461                                      0, G_MAXLONG, False, XA_CARDINAL, &type, &format, &nitems,
462                                      &bytes_after, (gpointer) &current_desktop);
463         err = gdk_x11_display_error_trap_pop (display);
464 
465         if (err != Success || result != Success)
466             return ret;
467 
468         if (type == XA_CARDINAL && format == 32 && nitems > 0)
469             ret = current_desktop[0];
470 
471         XFree (current_desktop);
472     }
473 
474     return ret;
475 #else
476     /* FIXME: on mac etc proably there are native APIs
477      * to get the current workspace etc */
478     return 0;
479 #endif
480 }
481 
482 /**
483  * xed_utils_get_window_workspace:
484  * @gtkwindow: a #GtkWindow.
485  *
486  * Get the workspace the window is on.
487  *
488  * This function gets the workspace that the #GtkWindow is visible on,
489  * it returns XED_ALL_WORKSPACES if the window is sticky, or if
490  * the window manager doesn't support this function.
491  *
492  * Returns: the workspace the window is on.
493  */
494 guint
xed_utils_get_window_workspace(GtkWindow * gtkwindow)495 xed_utils_get_window_workspace (GtkWindow *gtkwindow)
496 {
497 #ifdef GDK_WINDOWING_X11
498     GdkWindow *window;
499     GdkDisplay *display;
500     Atom type;
501     gint format;
502     gulong nitems;
503     gulong bytes_after;
504     guint *workspace;
505     gint err, result;
506     guint ret = XED_ALL_WORKSPACES;
507 
508     g_return_val_if_fail (GTK_IS_WINDOW (gtkwindow), 0);
509     g_return_val_if_fail (gtk_widget_get_realized (GTK_WIDGET (gtkwindow)), 0);
510 
511     window = gtk_widget_get_window (GTK_WIDGET (gtkwindow));
512     display = gdk_window_get_display (window);
513 
514     if (GDK_IS_X11_DISPLAY (display))
515     {
516         gdk_x11_display_error_trap_push (display);
517         result = XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display), GDK_WINDOW_XID (window),
518                                      gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_DESKTOP"),
519                                      0, G_MAXLONG, False, XA_CARDINAL, &type, &format, &nitems,
520                                      &bytes_after, (gpointer) &workspace);
521         err = gdk_x11_display_error_trap_pop (display);
522 
523         if (err != Success || result != Success)
524             return ret;
525 
526         if (type == XA_CARDINAL && format == 32 && nitems > 0)
527             ret = workspace[0];
528 
529         XFree (workspace);
530     }
531 
532     return ret;
533 #else
534     /* FIXME: on mac etc proably there are native APIs
535      * to get the current workspace etc */
536     return 0;
537 #endif
538 }
539 
540 /**
541  * xed_utils_get_current_viewport:
542  * @screen: a #GdkScreen
543  * @x: (out): x-axis point.
544  * @y: (out): y-axis point.
545  *
546  * Get the currently visible viewport origin for the #GdkScreen.
547  *
548  * If the X11 window property isn't found, (0, 0) is returned.
549  */
550 void
xed_utils_get_current_viewport(GdkScreen * screen,gint * x,gint * y)551 xed_utils_get_current_viewport (GdkScreen    *screen,
552                                 gint         *x,
553                                 gint         *y)
554 {
555 #ifdef GDK_WINDOWING_X11
556     GdkWindow *root_win;
557     GdkDisplay *display;
558     Atom type;
559     gint format;
560     gulong nitems;
561     gulong bytes_after;
562     gulong *coordinates;
563     gint err, result;
564 
565     g_return_if_fail (GDK_IS_SCREEN (screen));
566     g_return_if_fail (x != NULL && y != NULL);
567 
568     /* Default values for the viewport origin */
569     *x = 0;
570     *y = 0;
571 
572     root_win = gdk_screen_get_root_window (screen);
573     display = gdk_screen_get_display (screen);
574 
575     if (GDK_IS_X11_DISPLAY (display))
576     {
577         gdk_x11_display_error_trap_push (display);
578         result = XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display), GDK_WINDOW_XID (root_win),
579                                      gdk_x11_get_xatom_by_name_for_display (display, "_NET_DESKTOP_VIEWPORT"),
580                                      0, G_MAXLONG, False, XA_CARDINAL, &type, &format, &nitems,
581                                      &bytes_after, (void*) &coordinates);
582         err = gdk_x11_display_error_trap_pop (display);
583 
584         if (err != Success || result != Success)
585             return;
586 
587         if (type != XA_CARDINAL || format != 32 || nitems < 2)
588         {
589             XFree (coordinates);
590             return;
591         }
592 
593         *x = coordinates[0];
594         *y = coordinates[1];
595         XFree (coordinates);
596     }
597 #else
598     /* FIXME: on mac etc proably there are native APIs
599      * to get the current workspace etc */
600     *x = 0;
601     *y = 0;
602 #endif
603 }
604 
605 /**
606  * xed_utils_location_get_dirname_for_display
607  * @location: the location
608  *
609  * Returns a string suitable to be displayed in the UI indicating
610  * the name of the directory where the file is located.
611  * For remote files it may also contain the hostname etc.
612  * For local files it tries to replace the home dir with ~.
613  *
614  * Returns: a string to display the dirname
615  */
616 gchar *
xed_utils_location_get_dirname_for_display(GFile * location)617 xed_utils_location_get_dirname_for_display (GFile *location)
618 {
619     gchar *uri;
620     gchar *res;
621     GMount *mount;
622 
623     g_return_val_if_fail (location != NULL, NULL);
624 
625     /* we use the parse name, that is either the local path
626      * or an uri but which is utf8 safe */
627     uri = g_file_get_parse_name (location);
628 
629     /* FIXME: this is sync... is it a problem? */
630     mount = g_file_find_enclosing_mount (location, NULL, NULL);
631     if (mount != NULL)
632     {
633         gchar *mount_name;
634         gchar *path = NULL;
635         gchar *dirname;
636 
637         mount_name = g_mount_get_name (mount);
638         g_object_unref (mount);
639 
640         /* obtain the "path" part of the uri */
641         xed_utils_decode_uri (uri,
642                     NULL, NULL,
643                     NULL, NULL,
644                     &path);
645 
646         if (path == NULL)
647         {
648             dirname = xed_utils_uri_get_dirname (uri);
649         }
650         else
651         {
652             dirname = xed_utils_uri_get_dirname (path);
653         }
654 
655         if (dirname == NULL || strcmp (dirname, ".") == 0)
656         {
657             res = mount_name;
658         }
659         else
660         {
661             res = g_strdup_printf ("%s %s", mount_name, dirname);
662             g_free (mount_name);
663         }
664 
665         g_free (path);
666         g_free (dirname);
667     }
668     else
669     {
670         /* fallback for local files or uris without mounts */
671         res = xed_utils_uri_get_dirname (uri);
672     }
673 
674     g_free (uri);
675 
676     return res;
677 }
678 
679 gchar *
xed_utils_replace_home_dir_with_tilde(const gchar * uri)680 xed_utils_replace_home_dir_with_tilde (const gchar *uri)
681 {
682     gchar *tmp;
683     gchar *home;
684 
685     g_return_val_if_fail (uri != NULL, NULL);
686 
687     /* Note that g_get_home_dir returns a const string */
688     tmp = (gchar *)g_get_home_dir ();
689 
690     if (tmp == NULL)
691     {
692         return g_strdup (uri);
693     }
694 
695     home = g_filename_to_utf8 (tmp, -1, NULL, NULL, NULL);
696     if (home == NULL)
697     {
698         return g_strdup (uri);
699     }
700 
701     if (strcmp (uri, home) == 0)
702     {
703         g_free (home);
704 
705         return g_strdup ("~");
706     }
707 
708     tmp = home;
709     home = g_strdup_printf ("%s/", tmp);
710     g_free (tmp);
711 
712     if (g_str_has_prefix (uri, home))
713     {
714         gchar *res;
715 
716         res = g_strdup_printf ("~/%s", uri + strlen (home));
717 
718         g_free (home);
719 
720         return res;
721     }
722 
723     g_free (home);
724 
725     return g_strdup (uri);
726 }
727 
728 static gboolean
is_valid_scheme_character(gchar c)729 is_valid_scheme_character (gchar c)
730 {
731     return g_ascii_isalnum (c) || c == '+' || c == '-' || c == '.';
732 }
733 
734 static gboolean
has_valid_scheme(const gchar * uri)735 has_valid_scheme (const gchar *uri)
736 {
737     const gchar *p;
738 
739     p = uri;
740 
741     if (!is_valid_scheme_character (*p))
742     {
743         return FALSE;
744     }
745 
746     do
747     {
748         p++;
749     } while (is_valid_scheme_character (*p));
750 
751     return *p == ':';
752 }
753 
754 gboolean
xed_utils_is_valid_location(GFile * location)755 xed_utils_is_valid_location (GFile *location)
756 {
757     const guchar *p;
758     gchar *uri;
759     gboolean is_valid;
760 
761     if (location == NULL)
762     {
763         return FALSE;
764     }
765 
766     uri = g_file_get_uri (location);
767 
768     if (!has_valid_scheme (uri))
769     {
770         g_free (uri);
771         return FALSE;
772     }
773 
774     is_valid = TRUE;
775 
776     /* We expect to have a fully valid set of characters */
777     for (p = (const guchar *)uri; *p; p++)
778     {
779         if (*p == '%')
780         {
781             ++p;
782             if (!g_ascii_isxdigit (*p))
783             {
784                 is_valid = FALSE;
785                 break;
786             }
787 
788             ++p;
789             if (!g_ascii_isxdigit (*p))
790             {
791                 is_valid = FALSE;
792                 break;
793             }
794         }
795         else
796         {
797             if (*p <= 32 || *p >= 128)
798             {
799                 is_valid = FALSE;
800                 break;
801             }
802         }
803     }
804 
805     g_free (uri);
806 
807     return is_valid;
808 }
809 
810 static GtkWidget *handle_builder_error (const gchar *message, ...) G_GNUC_PRINTF (1, 2);
811 
812 static GtkWidget *
handle_builder_error(const gchar * message,...)813 handle_builder_error (const gchar *message,
814                       ...)
815 {
816     GtkWidget *label;
817     gchar *msg;
818     gchar *msg_plain;
819     va_list args;
820 
821     va_start (args, message);
822     msg_plain = g_strdup_vprintf (message, args);
823     va_end (args);
824 
825     label = gtk_label_new (NULL);
826     gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
827 
828     msg = g_strconcat ("<span size=\"large\" weight=\"bold\">",
829                        msg_plain, "</span>\n\n",
830                        _("Please check your installation."),
831                        NULL);
832 
833     gtk_label_set_markup (GTK_LABEL (label), msg);
834 
835     g_free (msg_plain);
836     g_free (msg);
837 
838     gtk_widget_set_margin_start (label, 5);
839     gtk_widget_set_margin_end (label, 5);
840     gtk_widget_set_margin_top (label, 5);
841     gtk_widget_set_margin_bottom (label, 5);
842 
843     return label;
844 }
845 
846 /* FIXME this is an issue for introspection */
847 /**
848  * xed_utils_get_ui_objects:
849  * @filename: the path to the gtk builder file
850  * @root_objects: a %NULL terminated list of root objects to load or NULL to
851  *                load all objects
852  * @error_widget: a pointer were a #GtkLabel
853  * @object_name: the name of the first object
854  * @...: a pointer were the first object is returned, followed by more
855  *       name / object pairs and terminated by %NULL.
856  *
857  * This function gets the requested objects from a GtkBuilder ui file. In case
858  * of error it returns %FALSE and sets error_widget to a GtkLabel containing
859  * the error message to display.
860  *
861  * Returns: %FALSE if an error occurs, %TRUE on success.
862  */
863 gboolean
xed_utils_get_ui_objects(const gchar * filename,gchar ** root_objects,GtkWidget ** error_widget,const gchar * object_name,...)864 xed_utils_get_ui_objects (const gchar  *filename,
865                           gchar       **root_objects,
866                           GtkWidget   **error_widget,
867                           const gchar  *object_name,
868                           ...)
869 {
870 
871     GtkBuilder *builder;
872     va_list args;
873     const gchar *name;
874     GError *error = NULL;
875     gchar *filename_markup;
876     gboolean ret = TRUE;
877 
878     g_return_val_if_fail (filename != NULL, FALSE);
879     g_return_val_if_fail (error_widget != NULL, FALSE);
880     g_return_val_if_fail (object_name != NULL, FALSE);
881 
882     filename_markup = g_markup_printf_escaped ("<i>%s</i>", filename);
883     *error_widget = NULL;
884 
885     builder = gtk_builder_new ();
886 
887     if (root_objects != NULL)
888     {
889         gtk_builder_add_objects_from_file (builder, filename, root_objects, &error);
890     }
891     else
892     {
893         gtk_builder_add_from_file (builder, filename, &error);
894     }
895 
896     if (error != NULL)
897     {
898         *error_widget = handle_builder_error (_("Unable to open UI file %s. Error: %s"),
899                                               filename_markup,
900                                               error->message);
901         g_error_free (error);
902         g_free (filename_markup);
903         g_object_unref (builder);
904 
905         return FALSE;
906     }
907 
908     va_start (args, object_name);
909     for (name = object_name; name; name = va_arg (args, const gchar *) )
910     {
911         GObject **gobj;
912 
913         gobj = va_arg (args, GObject **);
914         *gobj = gtk_builder_get_object (builder, name);
915 
916         if (!*gobj)
917         {
918             *error_widget = handle_builder_error (_("Unable to find the object '%s' inside file %s."),
919                                                   name,
920                                                   filename_markup),
921             ret = FALSE;
922             break;
923         }
924 
925         /* we return a new ref for the root objects,
926          * the others are already reffed by their parent root object */
927         if (root_objects != NULL)
928         {
929             gint i;
930 
931             for (i = 0; root_objects[i] != NULL; ++i)
932             {
933                 if ((strcmp (name, root_objects[i]) == 0))
934                 {
935                     g_object_ref (*gobj);
936                 }
937             }
938         }
939     }
940     va_end (args);
941 
942     g_free (filename_markup);
943     g_object_unref (builder);
944 
945     return ret;
946 }
947 
948 gchar *
xed_utils_make_canonical_uri_from_shell_arg(const gchar * str)949 xed_utils_make_canonical_uri_from_shell_arg (const gchar *str)
950 {
951     GFile *gfile;
952     gchar *uri;
953 
954     g_return_val_if_fail (str != NULL, NULL);
955     g_return_val_if_fail (*str != '\0', NULL);
956 
957     /* Note for the future:
958      * FIXME: is still still relevant?
959      *
960      * <federico> paolo: and flame whoever tells
961      * you that file:///mate/test_files/hëllò
962      * doesn't work --- that's not a valid URI
963      *
964      * <paolo> federico: well, another solution that
965      * does not requires patch to _from_shell_args
966      * is to check that the string returned by it
967      * contains only ASCII chars
968      * <federico> paolo: hmmmm, isn't there
969      * mate_vfs_is_uri_valid() or something?
970      * <paolo>: I will use xed_utils_is_valid_uri ()
971      *
972      */
973 
974     gfile = g_file_new_for_commandline_arg (str);
975 
976     if (xed_utils_is_valid_location (gfile))
977     {
978         uri = g_file_get_uri (gfile);
979         g_object_unref (gfile);
980         return uri;
981     }
982 
983     g_object_unref (gfile);
984     return NULL;
985 }
986 
987 /**
988  * xed_utils_file_has_parent:
989  * @gfile: the GFile to check the parent for
990  *
991  * Return %TRUE if the specified gfile has a parent (is not the root), %FALSE
992  * otherwise
993  */
994 gboolean
xed_utils_file_has_parent(GFile * gfile)995 xed_utils_file_has_parent (GFile *gfile)
996 {
997     GFile *parent;
998     gboolean ret;
999 
1000     parent = g_file_get_parent (gfile);
1001     ret = parent != NULL;
1002 
1003     if (parent)
1004     {
1005         g_object_unref (parent);
1006     }
1007 
1008     return ret;
1009 }
1010 
1011 /**
1012  * xed_utils_basename_for_display:
1013  * @location: location for which the basename should be displayed
1014  *
1015  * Return the basename of a file suitable for display to users.
1016  */
1017 gchar *
xed_utils_basename_for_display(GFile * location)1018 xed_utils_basename_for_display (GFile *location)
1019 {
1020     gchar *name;
1021     gchar *hn;
1022     gchar *uri;
1023 
1024     g_return_val_if_fail (G_IS_FILE (location), NULL);
1025 
1026     uri = g_file_get_uri (location);
1027 
1028     /* First, try to query the display name, but only on local files */
1029     if (g_file_has_uri_scheme (location, "file"))
1030     {
1031         GFileInfo *info;
1032         info = g_file_query_info (location,
1033                                   G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
1034                                   G_FILE_QUERY_INFO_NONE,
1035                                   NULL,
1036                                   NULL);
1037 
1038         if (info)
1039         {
1040             /* Simply get the display name to use as the basename */
1041             name = g_strdup (g_file_info_get_display_name (info));
1042             g_object_unref (info);
1043         }
1044         else
1045         {
1046             /* This is a local file, and therefore we will use
1047              * g_filename_display_basename on the local path */
1048             gchar *local_path;
1049 
1050             local_path = g_file_get_path (location);
1051             name = g_filename_display_basename (local_path);
1052             g_free (local_path);
1053         }
1054     }
1055     else if (xed_utils_file_has_parent (location) || !xed_utils_decode_uri (uri, NULL, NULL, &hn, NULL, NULL))
1056     {
1057         /* For remote files with a parent (so not just http://foo.com)
1058            or remote file for which the decoding of the host name fails,
1059            use the _parse_name and take basename of that */
1060         gchar *parse_name;
1061         gchar *base;
1062 
1063         parse_name = g_file_get_parse_name (location);
1064         base = g_filename_display_basename (parse_name);
1065         name = g_uri_unescape_string (base, NULL);
1066 
1067         g_free (base);
1068         g_free (parse_name);
1069     }
1070     else
1071     {
1072         /* display '/ on <host>' using the decoded host */
1073         gchar *hn_utf8;
1074 
1075         if  (hn != NULL)
1076         {
1077             hn_utf8 = xed_utils_make_valid_utf8 (hn);
1078         }
1079         else
1080         {
1081             /* we should never get here */
1082             hn_utf8 = g_strdup ("?");
1083         }
1084 
1085         /* Translators: '/ on <remote-share>' */
1086         name = g_strdup_printf (_("/ on %s"), hn_utf8);
1087 
1088         g_free (hn_utf8);
1089         g_free (hn);
1090     }
1091 
1092     g_free (uri);
1093 
1094     return name;
1095 }
1096 
1097 /**
1098  * xed_utils_drop_get_uris:
1099  * @selection_data: the #GtkSelectionData from drag_data_received
1100  *
1101  * Create a list of valid uri's from a uri-list drop.
1102  *
1103  * Return value: (transfer full): a string array which will hold the uris or %NULL if there
1104  *       were no valid uris. g_strfreev should be used when the
1105  *       string array is no longer used
1106  */
1107 gchar **
xed_utils_drop_get_uris(GtkSelectionData * selection_data)1108 xed_utils_drop_get_uris (GtkSelectionData *selection_data)
1109 {
1110     gchar **uris;
1111     gint i;
1112     gint p = 0;
1113     gchar **uri_list;
1114 
1115     uris = g_uri_list_extract_uris ((gchar *) gtk_selection_data_get_data (selection_data));
1116     uri_list = g_new0(gchar *, g_strv_length (uris) + 1);
1117 
1118     for (i = 0; uris[i] != NULL; i++)
1119     {
1120         gchar *uri;
1121 
1122         uri = xed_utils_make_canonical_uri_from_shell_arg (uris[i]);
1123 
1124         /* Silently ignore malformed URI/filename */
1125         if (uri != NULL)
1126         {
1127             uri_list[p++] = uri;
1128         }
1129     }
1130 
1131     g_strfreev (uris);
1132 
1133     if (*uri_list == NULL)
1134     {
1135         g_free(uri_list);
1136         return NULL;
1137     }
1138 
1139     return uri_list;
1140 }
1141 
1142 static void
null_ptr(gchar ** ptr)1143 null_ptr (gchar **ptr)
1144 {
1145     if (ptr)
1146     {
1147         *ptr = NULL;
1148     }
1149 }
1150 
1151 /**
1152  * xed_utils_decode_uri:
1153  * @uri: the uri to decode
1154  * @scheme: (allow-none): return value pointer for the uri's
1155  * scheme (e.g. http, sftp, ...), or %NULL
1156  * @user: (allow-none): return value pointer for the uri user info, or %NULL
1157  * @port: (allow-none): return value pointer for the uri port, or %NULL
1158  * @host: (allow-none): return value pointer for the uri host, or %NULL
1159  * @path: (allow-none): return value pointer for the uri path, or %NULL
1160  *
1161  * Parse and break an uri apart in its individual components like the uri
1162  * scheme, user info, port, host and path. The return value pointer can be
1163  * %NULL to ignore certain parts of the uri. If the function returns %TRUE, then
1164  * all return value pointers should be freed using g_free
1165  *
1166  * Return value: %TRUE if the uri could be properly decoded, %FALSE otherwise.
1167  */
1168 gboolean
xed_utils_decode_uri(const gchar * uri,gchar ** scheme,gchar ** user,gchar ** host,gchar ** port,gchar ** path)1169 xed_utils_decode_uri (const gchar  *uri,
1170                       gchar       **scheme,
1171                       gchar       **user,
1172                       gchar       **host,
1173                       gchar       **port,
1174                       gchar       **path
1175 )
1176 {
1177     /* Largely copied from glib/gio/gdummyfile.c:_g_decode_uri. This
1178      * functionality should be in glib/gio, but for now we implement it
1179      * ourselves (see bug #546182) */
1180 
1181     const char *p, *in, *hier_part_start, *hier_part_end;
1182     char *out;
1183     char c;
1184 
1185     /* From RFC 3986 Decodes:
1186      * URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
1187      */
1188 
1189     p = uri;
1190 
1191     null_ptr (scheme);
1192     null_ptr (user);
1193     null_ptr (port);
1194     null_ptr (host);
1195     null_ptr (path);
1196 
1197     /* Decode scheme:
1198      * scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
1199      */
1200 
1201     if (!g_ascii_isalpha (*p))
1202     {
1203         return FALSE;
1204     }
1205 
1206     while (1)
1207     {
1208         c = *p++;
1209 
1210         if (c == ':')
1211         {
1212             break;
1213         }
1214 
1215         if (!(g_ascii_isalnum(c) || c == '+' || c == '-' || c == '.'))
1216         {
1217             return FALSE;
1218         }
1219     }
1220 
1221     if (scheme)
1222     {
1223         *scheme = g_malloc (p - uri);
1224         out = *scheme;
1225 
1226         for (in = uri; in < p - 1; in++)
1227         {
1228             *out++ = g_ascii_tolower (*in);
1229         }
1230 
1231         *out = '\0';
1232     }
1233 
1234     hier_part_start = p;
1235     hier_part_end = p + strlen (p);
1236 
1237     if (hier_part_start[0] == '/' && hier_part_start[1] == '/')
1238     {
1239         const char *authority_start, *authority_end;
1240         const char *userinfo_start, *userinfo_end;
1241         const char *host_start, *host_end;
1242         const char *port_start;
1243 
1244         authority_start = hier_part_start + 2;
1245         /* authority is always followed by / or nothing */
1246         authority_end = memchr (authority_start, '/', hier_part_end - authority_start);
1247 
1248         if (authority_end == NULL)
1249         {
1250             authority_end = hier_part_end;
1251         }
1252 
1253         /* 3.2:
1254          * authority = [ userinfo "@" ] host [ ":" port ]
1255          */
1256 
1257         userinfo_end = memchr (authority_start, '@', authority_end - authority_start);
1258 
1259         if (userinfo_end)
1260         {
1261             userinfo_start = authority_start;
1262 
1263             if (user)
1264             {
1265                 *user = g_uri_unescape_segment (userinfo_start, userinfo_end, NULL);
1266             }
1267 
1268             if (user && *user == NULL)
1269             {
1270                 if (scheme)
1271                 {
1272                     g_free (*scheme);
1273                 }
1274 
1275                 return FALSE;
1276             }
1277 
1278             host_start = userinfo_end + 1;
1279         }
1280         else
1281         {
1282             host_start = authority_start;
1283         }
1284 
1285         port_start = memchr (host_start, ':', authority_end - host_start);
1286 
1287         if (port_start)
1288         {
1289             host_end = port_start++;
1290 
1291             if (port)
1292             {
1293                 *port = g_strndup (port_start, authority_end - port_start);
1294             }
1295         }
1296         else
1297         {
1298             host_end = authority_end;
1299         }
1300 
1301         if (host)
1302         {
1303             *host = g_strndup (host_start, host_end - host_start);
1304         }
1305 
1306         hier_part_start = authority_end;
1307     }
1308 
1309     if (path)
1310     {
1311         *path = g_uri_unescape_segment (hier_part_start, hier_part_end, "/");
1312     }
1313 
1314     return TRUE;
1315 }
1316 
1317 static gboolean
data_exists(GSList * list,const gpointer data)1318 data_exists (GSList         *list,
1319              const gpointer  data)
1320 {
1321     for (; list != NULL; list = g_slist_next (list))
1322     {
1323         if (list->data == data)
1324         {
1325             return TRUE;
1326         }
1327     }
1328 
1329     return FALSE;
1330 }
1331 
1332 GSList *
_xed_utils_encoding_strv_to_list(const gchar * const * enc_str)1333 _xed_utils_encoding_strv_to_list (const gchar * const *enc_str)
1334 {
1335     GSList *res = NULL;
1336     gchar **p;
1337 
1338     for (p = (gchar **)enc_str; p != NULL && *p != NULL; p++)
1339     {
1340         const gchar *charset = *p;
1341         const GtkSourceEncoding *enc;
1342 
1343         if (g_str_equal (charset, "CURRENT"))
1344         {
1345             g_get_charset (&charset);
1346         }
1347 
1348         g_return_val_if_fail (charset != NULL, NULL);
1349         enc = gtk_source_encoding_get_from_charset (charset);
1350 
1351         if (enc != NULL &&
1352             !data_exists (res, (gpointer)enc))
1353         {
1354             res = g_slist_prepend (res, (gpointer)enc);
1355         }
1356     }
1357 
1358     return g_slist_reverse (res);
1359 }
1360 
1361 gchar **
_xed_utils_encoding_list_to_strv(const GSList * enc_list)1362 _xed_utils_encoding_list_to_strv (const GSList *enc_list)
1363 {
1364     GSList *l;
1365     GPtrArray *array;
1366 
1367     array = g_ptr_array_sized_new (g_slist_length ((GSList *)enc_list) + 1);
1368 
1369     for (l = (GSList *)enc_list; l != NULL; l = g_slist_next (l))
1370     {
1371         const GtkSourceEncoding *enc = l->data;
1372         const gchar *charset = gtk_source_encoding_get_charset (enc);
1373 
1374         g_return_val_if_fail (charset != NULL, NULL);
1375 
1376         g_ptr_array_add (array, g_strdup (charset));
1377     }
1378 
1379     g_ptr_array_add (array, NULL);
1380 
1381    return (gchar **)g_ptr_array_free (array, FALSE);
1382 }
1383