Merge branch 'issue-699' into 'master'
[glib.git] / gio / gosxcontenttype.m
blob52ba5763afc64a78f959fd381e72c511fb1b4425
1 /* GIO - GLib Input, Output and Streaming Library
2  *
3  * Copyright (C) 2014 Patrick Griffis
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General
16  * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
17  *
18  */
20 #include "config.h"
22 #include "gcontenttype.h"
23 #include "gicon.h"
24 #include "gthemedicon.h"
26 #include <CoreServices/CoreServices.h>
28 #define XDG_PREFIX _gio_xdg
29 #include "xdgmime/xdgmime.h"
31 /* We lock this mutex whenever we modify global state in this module.  */
32 G_LOCK_DEFINE_STATIC (gio_xdgmime);
35 /*< internal >
36  * create_cfstring_from_cstr:
37  * @cstr: a #gchar
38  *
39  * Converts a cstr to a utf8 cfstring
40  * It must be CFReleased()'d.
41  *
42  */
43 static CFStringRef
44 create_cfstring_from_cstr (const gchar *cstr)
46   return CFStringCreateWithCString (NULL, cstr, kCFStringEncodingUTF8);
49 /*< internal >
50  * create_cstr_from_cfstring:
51  * @str: a #CFStringRef
52  *
53  * Converts a cfstring to a utf8 cstring.
54  * The incoming cfstring is released for you.
55  * The returned string must be g_free()'d.
56  *
57  */
58 static gchar *
59 create_cstr_from_cfstring (CFStringRef str)
61   g_return_val_if_fail (str != NULL, NULL);
63   CFIndex length = CFStringGetLength (str);
64   CFIndex maxlen = CFStringGetMaximumSizeForEncoding (length, kCFStringEncodingUTF8);
65   gchar *buffer = g_malloc (maxlen + 1);
66   Boolean success = CFStringGetCString (str, (char *) buffer, maxlen,
67                                         kCFStringEncodingUTF8);
68   CFRelease (str);
69   if (success)
70     return buffer;
71   else
72     {
73       g_free (buffer);
74       return NULL;
75     }
78 /*< internal >
79  * create_cstr_from_cfstring_with_fallback:
80  * @str: a #CFStringRef
81  * @fallback: a #gchar
82  *
83  * Tries to convert a cfstring to a utf8 cstring.
84  * If @str is NULL or conversion fails @fallback is returned.
85  * The incoming cfstring is released for you.
86  * The returned string must be g_free()'d.
87  *
88  */
89 static gchar *
90 create_cstr_from_cfstring_with_fallback (CFStringRef  str,
91                                          const gchar *fallback)
93   gchar *cstr = NULL;
95   if (str)
96     cstr = create_cstr_from_cfstring (str);
97   if (!cstr)
98     return g_strdup (fallback);
100   return cstr;
103 gboolean
104 g_content_type_equals (const gchar *type1,
105                        const gchar *type2)
107   CFStringRef str1, str2;
108   gboolean ret;
110   g_return_val_if_fail (type1 != NULL, FALSE);
111   g_return_val_if_fail (type2 != NULL, FALSE);
113   if (g_ascii_strcasecmp (type1, type2) == 0)
114     return TRUE;
116   str1 = create_cfstring_from_cstr (type1);
117   str2 = create_cfstring_from_cstr (type2);
119   ret = UTTypeEqual (str1, str2);
121   CFRelease (str1);
122   CFRelease (str2);
124   return ret;
127 gboolean
128 g_content_type_is_a (const gchar *ctype,
129                      const gchar *csupertype)
131   CFStringRef type, supertype;
132   gboolean ret;
134   g_return_val_if_fail (ctype != NULL, FALSE);
135   g_return_val_if_fail (csupertype != NULL, FALSE);
137   type = create_cfstring_from_cstr (ctype);
138   supertype = create_cfstring_from_cstr (csupertype);
140   ret = UTTypeConformsTo (type, supertype);
142   CFRelease (type);
143   CFRelease (supertype);
145   return ret;
148 gboolean
149 g_content_type_is_mime_type (const gchar *type,
150                              const gchar *mime_type)
152   gchar *content_type;
153   gboolean ret;
155   g_return_val_if_fail (type != NULL, FALSE);
156   g_return_val_if_fail (mime_type != NULL, FALSE);
158   content_type = g_content_type_from_mime_type (mime_type);
159   ret = g_content_type_is_a (type, content_type);
160   g_free (content_type);
162   return ret;
165 gboolean
166 g_content_type_is_unknown (const gchar *type)
168   g_return_val_if_fail (type != NULL, FALSE);
170   /* Should dynamic types be considered "unknown"? */
171   if (g_str_has_prefix (type, "dyn."))
172     return TRUE;
173   /* application/octet-stream */
174   else if (g_strcmp0 (type, "public.data") == 0)
175     return TRUE;
177   return FALSE;
180 gchar *
181 g_content_type_get_description (const gchar *type)
183   CFStringRef str;
184   CFStringRef desc_str;
186   g_return_val_if_fail (type != NULL, NULL);
188   str = create_cfstring_from_cstr (type);
189   desc_str = UTTypeCopyDescription (str);
191   CFRelease (str);
192   return create_cstr_from_cfstring_with_fallback (desc_str, "unknown");
195 /* <internal>
196  * _get_generic_icon_name_from_mime_type
198  * This function produces a generic icon name from a @mime_type.
199  * If no generic icon name is found in the xdg mime database, the
200  * generic icon name is constructed.
202  * Background:
203  * generic-icon elements specify the icon to use as a generic icon for this
204  * particular mime-type, given by the name attribute. This is used if there
205  * is no specific icon (see icon for how these are found). These are used
206  * for categories of similar types (like spreadsheets or archives) that can
207  * use a common icon. The Icon Naming Specification lists a set of such
208  * icon names. If this element is not specified then the mimetype is used
209  * to generate the generic icon by using the top-level media type
210  * (e.g. "video" in "video/ogg") and appending "-x-generic"
211  * (i.e. "video-x-generic" in the previous example).
213  * From: https://specifications.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-0.18.html
214  */
216 static gchar *
217 _get_generic_icon_name_from_mime_type (const gchar *mime_type)
219   const gchar *xdg_icon_name;
220   gchar *icon_name;
222   G_LOCK (gio_xdgmime);
223   xdg_icon_name = xdg_mime_get_generic_icon (mime_type);
224   G_UNLOCK (gio_xdgmime);
226   if (xdg_icon_name == NULL)
227     {
228       const char *p;
229       const char *suffix = "-x-generic";
230       gsize prefix_len;
232       p = strchr (mime_type, '/');
233       if (p == NULL)
234         prefix_len = strlen (mime_type);
235       else
236         prefix_len = p - mime_type;
238       icon_name = g_malloc (prefix_len + strlen (suffix) + 1);
239       memcpy (icon_name, mime_type, prefix_len);
240       memcpy (icon_name + prefix_len, suffix, strlen (suffix));
241       icon_name[prefix_len + strlen (suffix)] = 0;
242     }
243   else
244     {
245       icon_name = g_strdup (xdg_icon_name);
246     }
248   return icon_name;
252 static GIcon *
253 g_content_type_get_icon_internal (const gchar *uti,
254                                   gboolean     symbolic)
256   char *mimetype_icon;
257   char *mime_type;
258   char *generic_mimetype_icon = NULL;
259   char *q;
260   char *icon_names[6];
261   int n = 0;
262   GIcon *themed_icon;
263   const char  *xdg_icon;
264   int i;
266   g_return_val_if_fail (uti != NULL, NULL);
268   mime_type = g_content_type_get_mime_type (uti);
270   G_LOCK (gio_xdgmime);
271   xdg_icon = xdg_mime_get_icon (mime_type);
272   G_UNLOCK (gio_xdgmime);
274   if (xdg_icon)
275     icon_names[n++] = g_strdup (xdg_icon);
277   mimetype_icon = g_strdup (mime_type);
278   while ((q = strchr (mimetype_icon, '/')) != NULL)
279     *q = '-';
281   icon_names[n++] = mimetype_icon;
283   generic_mimetype_icon = _get_generic_icon_name_from_mime_type (mime_type);
285   if (generic_mimetype_icon)
286     icon_names[n++] = generic_mimetype_icon;
288   if (symbolic)
289     {
290       for (i = 0; i < n; i++)
291         {
292           icon_names[n + i] = icon_names[i];
293           icon_names[i] = g_strconcat (icon_names[i], "-symbolic", NULL);
294         }
296       n += n;
297     }
299   themed_icon = g_themed_icon_new_from_names (icon_names, n);
301   for (i = 0; i < n; i++)
302     g_free (icon_names[i]);
304   g_free(mime_type);
306   return themed_icon;
309 GIcon *
310 g_content_type_get_icon (const gchar *type)
312   return g_content_type_get_icon_internal (type, FALSE);
315 GIcon *
316 g_content_type_get_symbolic_icon (const gchar *type)
318   return g_content_type_get_icon_internal (type, TRUE);
321 gchar *
322 g_content_type_get_generic_icon_name (const gchar *type)
324   return NULL;
327 gboolean
328 g_content_type_can_be_executable (const gchar *type)
330   CFStringRef uti;
331   gboolean ret = FALSE;
333   g_return_val_if_fail (type != NULL, FALSE);
335   uti = create_cfstring_from_cstr (type);
337   if (UTTypeConformsTo (uti, kUTTypeApplication))
338     ret = TRUE;
339   else if (UTTypeConformsTo (uti, CFSTR("public.executable")))
340     ret = TRUE;
341   else if (UTTypeConformsTo (uti, CFSTR("public.script")))
342     ret = TRUE;
343   /* Our tests assert that all text can be executable... */
344   else if (UTTypeConformsTo (uti, CFSTR("public.text")))
345       ret = TRUE;
347   CFRelease (uti);
348   return ret;
351 gchar *
352 g_content_type_from_mime_type (const gchar *mime_type)
354   CFStringRef mime_str;
355   CFStringRef uti_str;
357   g_return_val_if_fail (mime_type != NULL, NULL);
359   /* Their api does not handle globs but they are common. */
360   if (g_str_has_suffix (mime_type, "*"))
361     {
362       if (g_str_has_prefix (mime_type, "audio"))
363         return g_strdup ("public.audio");
364       if (g_str_has_prefix (mime_type, "image"))
365         return g_strdup ("public.image");
366       if (g_str_has_prefix (mime_type, "text"))
367         return g_strdup ("public.text");
368       if (g_str_has_prefix (mime_type, "video"))
369         return g_strdup ("public.movie");
370     }
372   /* Some exceptions are needed for gdk-pixbuf.
373    * This list is not exhaustive.
374    */
375   if (g_str_has_prefix (mime_type, "image"))
376     {
377       if (g_str_has_suffix (mime_type, "x-icns"))
378         return g_strdup ("com.apple.icns");
379       if (g_str_has_suffix (mime_type, "x-tga"))
380         return g_strdup ("com.truevision.tga-image");
381       if (g_str_has_suffix (mime_type, "x-ico"))
382         return g_strdup ("com.microsoft.ico ");
383     }
385   /* These are also not supported...
386    * Used in glocalfileinfo.c
387    */
388   if (g_str_has_prefix (mime_type, "inode"))
389     {
390       if (g_str_has_suffix (mime_type, "directory"))
391         return g_strdup ("public.folder");
392       if (g_str_has_suffix (mime_type, "symlink"))
393         return g_strdup ("public.symlink");
394     }
396   /* This is correct according to the Apple docs:
397      https://developer.apple.com/library/content/documentation/Miscellaneous/Reference/UTIRef/Articles/System-DeclaredUniformTypeIdentifiers.html
398   */
399   if (strcmp (mime_type, "text/plain") == 0)
400     return g_strdup ("public.text");
402   /* Non standard type */
403   if (strcmp (mime_type, "application/x-executable") == 0)
404     return g_strdup ("public.executable");
406   mime_str = create_cfstring_from_cstr (mime_type);
407   uti_str = UTTypeCreatePreferredIdentifierForTag (kUTTagClassMIMEType, mime_str, NULL);
409   CFRelease (mime_str);
410   return create_cstr_from_cfstring_with_fallback (uti_str, "public.data");
413 gchar *
414 g_content_type_get_mime_type (const gchar *type)
416   CFStringRef uti_str;
417   CFStringRef mime_str;
419   g_return_val_if_fail (type != NULL, NULL);
421   /* We must match the additions above
422    * so conversions back and forth work.
423    */
424   if (g_str_has_prefix (type, "public"))
425     {
426       if (g_str_has_suffix (type, ".image"))
427         return g_strdup ("image/*");
428       if (g_str_has_suffix (type, ".movie"))
429         return g_strdup ("video/*");
430       if (g_str_has_suffix (type, ".text"))
431         return g_strdup ("text/*");
432       if (g_str_has_suffix (type, ".audio"))
433         return g_strdup ("audio/*");
434       if (g_str_has_suffix (type, ".folder"))
435         return g_strdup ("inode/directory");
436       if (g_str_has_suffix (type, ".symlink"))
437         return g_strdup ("inode/symlink");
438       if (g_str_has_suffix (type, ".executable"))
439         return g_strdup ("application/x-executable");
440     }
442   uti_str = create_cfstring_from_cstr (type);
443   mime_str = UTTypeCopyPreferredTagWithClass(uti_str, kUTTagClassMIMEType);
445   CFRelease (uti_str);
446   return create_cstr_from_cfstring_with_fallback (mime_str, "application/octet-stream");
449 static gboolean
450 looks_like_text (const guchar *data,
451                  gsize         data_size)
453   gsize i;
454   guchar c;
456   for (i = 0; i < data_size; i++)
457     {
458       c = data[i];
459       if (g_ascii_iscntrl (c) && !g_ascii_isspace (c) && c != '\b')
460         return FALSE;
461     }
462   return TRUE;
465 gchar *
466 g_content_type_guess (const gchar  *filename,
467                       const guchar *data,
468                       gsize         data_size,
469                       gboolean     *result_uncertain)
471   CFStringRef uti = NULL;
472   gchar *cextension;
473   CFStringRef extension;
474   int uncertain = -1;
476   g_return_val_if_fail (data_size != (gsize) -1, NULL);
478   if (filename && *filename)
479     {
480       gchar *basename = g_path_get_basename (filename);
481       gchar *dirname = g_path_get_dirname (filename);
482       gsize i = strlen (filename);
484       if (filename[i - 1] == '/')
485         {
486           if (g_strcmp0 (dirname, "/Volumes") == 0)
487             {
488               uti = CFStringCreateCopy (NULL, kUTTypeVolume);
489             }
490           else if ((cextension = strrchr (basename, '.')) != NULL)
491             {
492               cextension++;
493               extension = create_cfstring_from_cstr (cextension);
494               uti = UTTypeCreatePreferredIdentifierForTag (kUTTagClassFilenameExtension,
495                                                            extension, NULL);
496               CFRelease (extension);
498               if (CFStringHasPrefix (uti, CFSTR ("dyn.")))
499                 {
500                   CFRelease (uti);
501                   uti = CFStringCreateCopy (NULL, kUTTypeFolder);
502                   uncertain = TRUE;
503                 }
504             }
505           else
506             {
507               uti = CFStringCreateCopy (NULL, kUTTypeFolder);
508               uncertain = TRUE; /* Matches Unix backend */
509             }
510         }
511       else
512         {
513           /* GTK needs this... */
514           if (g_str_has_suffix (basename, ".ui"))
515             {
516               uti = CFStringCreateCopy (NULL, kUTTypeXML);
517             }
518           else if (g_str_has_suffix (basename, ".txt"))
519             {
520               uti = CFStringCreateCopy (NULL, CFSTR ("public.text"));
521             }
522           else if ((cextension = strrchr (basename, '.')) != NULL)
523             {
524               cextension++;
525               extension = create_cfstring_from_cstr (cextension);
526               uti = UTTypeCreatePreferredIdentifierForTag (kUTTagClassFilenameExtension,
527                                                            extension, NULL);
528               CFRelease (extension);
529             }
530           g_free (basename);
531           g_free (dirname);
532         }
533     }
534   if (data && (!filename || !uti ||
535                CFStringCompare (uti, CFSTR ("public.data"), 0) == kCFCompareEqualTo))
536     {
537       const char *sniffed_mimetype;
538       G_LOCK (gio_xdgmime);
539       sniffed_mimetype = xdg_mime_get_mime_type_for_data (data, data_size, NULL);
540       G_UNLOCK (gio_xdgmime);
541       if (sniffed_mimetype != XDG_MIME_TYPE_UNKNOWN)
542         {
543           gchar *uti_str = g_content_type_from_mime_type (sniffed_mimetype);
544           uti = create_cfstring_from_cstr (uti_str);
545           g_free (uti_str);
546         }
547       if (!uti && looks_like_text (data, data_size))
548         {
549           if (g_str_has_prefix ((const gchar*)data, "#!/"))
550             uti = CFStringCreateCopy (NULL, CFSTR ("public.script"));
551           else
552             uti = CFStringCreateCopy (NULL, CFSTR ("public.text"));
553         }
554     }
556   if (!uti)
557     {
558       /* Generic data type */
559       uti = CFStringCreateCopy (NULL, CFSTR ("public.data"));
560       if (result_uncertain)
561         *result_uncertain = TRUE;
562     }
563   else if (result_uncertain)
564     {
565       *result_uncertain = uncertain == -1 ? FALSE : uncertain;
566     }
568   return create_cstr_from_cfstring (uti);
571 GList *
572 g_content_types_get_registered (void)
574   /* TODO: UTTypeCreateAllIdentifiersForTag? */
575   return NULL;
578 gchar **
579 g_content_type_guess_for_tree (GFile *root)
581   return NULL;