1 /* GIO - GLib Input, Output and Streaming Library
3 * Copyright (C) 2014 Patrick Griffis
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.
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.
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/>.
22 #include "gcontenttype.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
);
36 * create_cfstring_from_cstr:
39 * Converts a cstr to a utf8 cfstring
40 * It must be CFReleased()'d.
44 create_cfstring_from_cstr (const gchar
*cstr
)
46 return CFStringCreateWithCString (NULL
, cstr
, kCFStringEncodingUTF8
);
50 * create_cstr_from_cfstring:
51 * @str: a #CFStringRef
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.
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
);
79 * create_cstr_from_cfstring_with_fallback:
80 * @str: a #CFStringRef
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.
90 create_cstr_from_cfstring_with_fallback (CFStringRef str
,
91 const gchar
*fallback
)
96 cstr
= create_cstr_from_cfstring (str
);
98 return g_strdup (fallback
);
104 g_content_type_equals (const gchar
*type1
,
107 CFStringRef str1
, str2
;
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)
116 str1
= create_cfstring_from_cstr (type1
);
117 str2
= create_cfstring_from_cstr (type2
);
119 ret
= UTTypeEqual (str1
, str2
);
128 g_content_type_is_a (const gchar
*ctype
,
129 const gchar
*csupertype
)
131 CFStringRef type
, supertype
;
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
);
143 CFRelease (supertype
);
149 g_content_type_is_mime_type (const gchar
*type
,
150 const gchar
*mime_type
)
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
);
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."))
173 /* application/octet-stream */
174 else if (g_strcmp0 (type
, "public.data") == 0)
181 g_content_type_get_description (const gchar
*type
)
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
);
192 return create_cstr_from_cfstring_with_fallback (desc_str
, "unknown");
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.
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
217 _get_generic_icon_name_from_mime_type (const gchar
*mime_type
)
219 const gchar
*xdg_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
)
229 const char *suffix
= "-x-generic";
232 p
= strchr (mime_type
, '/');
234 prefix_len
= strlen (mime_type
);
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;
245 icon_name
= g_strdup (xdg_icon_name
);
253 g_content_type_get_icon_internal (const gchar
*uti
,
258 char *generic_mimetype_icon
= NULL
;
263 const char *xdg_icon
;
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
);
275 icon_names
[n
++] = g_strdup (xdg_icon
);
277 mimetype_icon
= g_strdup (mime_type
);
278 while ((q
= strchr (mimetype_icon
, '/')) != NULL
)
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
;
290 for (i
= 0; i
< n
; i
++)
292 icon_names
[n
+ i
] = icon_names
[i
];
293 icon_names
[i
] = g_strconcat (icon_names
[i
], "-symbolic", NULL
);
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
]);
310 g_content_type_get_icon (const gchar
*type
)
312 return g_content_type_get_icon_internal (type
, FALSE
);
316 g_content_type_get_symbolic_icon (const gchar
*type
)
318 return g_content_type_get_icon_internal (type
, TRUE
);
322 g_content_type_get_generic_icon_name (const gchar
*type
)
328 g_content_type_can_be_executable (const gchar
*type
)
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
))
339 else if (UTTypeConformsTo (uti
, CFSTR("public.executable")))
341 else if (UTTypeConformsTo (uti
, CFSTR("public.script")))
343 /* Our tests assert that all text can be executable... */
344 else if (UTTypeConformsTo (uti
, CFSTR("public.text")))
352 g_content_type_from_mime_type (const gchar
*mime_type
)
354 CFStringRef mime_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
, "*"))
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");
372 /* Some exceptions are needed for gdk-pixbuf.
373 * This list is not exhaustive.
375 if (g_str_has_prefix (mime_type
, "image"))
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 ");
385 /* These are also not supported...
386 * Used in glocalfileinfo.c
388 if (g_str_has_prefix (mime_type
, "inode"))
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");
396 /* This is correct according to the Apple docs:
397 https://developer.apple.com/library/content/documentation/Miscellaneous/Reference/UTIRef/Articles/System-DeclaredUniformTypeIdentifiers.html
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");
414 g_content_type_get_mime_type (const gchar
*type
)
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.
424 if (g_str_has_prefix (type
, "public"))
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");
442 uti_str
= create_cfstring_from_cstr (type
);
443 mime_str
= UTTypeCopyPreferredTagWithClass(uti_str
, kUTTagClassMIMEType
);
446 return create_cstr_from_cfstring_with_fallback (mime_str
, "application/octet-stream");
450 looks_like_text (const guchar
*data
,
456 for (i
= 0; i
< data_size
; i
++)
459 if (g_ascii_iscntrl (c
) && !g_ascii_isspace (c
) && c
!= '\b')
466 g_content_type_guess (const gchar
*filename
,
469 gboolean
*result_uncertain
)
471 CFStringRef uti
= NULL
;
473 CFStringRef extension
;
476 g_return_val_if_fail (data_size
!= (gsize
) -1, NULL
);
478 if (filename
&& *filename
)
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] == '/')
486 if (g_strcmp0 (dirname
, "/Volumes") == 0)
488 uti
= CFStringCreateCopy (NULL
, kUTTypeVolume
);
490 else if ((cextension
= strrchr (basename
, '.')) != NULL
)
493 extension
= create_cfstring_from_cstr (cextension
);
494 uti
= UTTypeCreatePreferredIdentifierForTag (kUTTagClassFilenameExtension
,
496 CFRelease (extension
);
498 if (CFStringHasPrefix (uti
, CFSTR ("dyn.")))
501 uti
= CFStringCreateCopy (NULL
, kUTTypeFolder
);
507 uti
= CFStringCreateCopy (NULL
, kUTTypeFolder
);
508 uncertain
= TRUE
; /* Matches Unix backend */
513 /* GTK needs this... */
514 if (g_str_has_suffix (basename
, ".ui"))
516 uti
= CFStringCreateCopy (NULL
, kUTTypeXML
);
518 else if (g_str_has_suffix (basename
, ".txt"))
520 uti
= CFStringCreateCopy (NULL
, CFSTR ("public.text"));
522 else if ((cextension
= strrchr (basename
, '.')) != NULL
)
525 extension
= create_cfstring_from_cstr (cextension
);
526 uti
= UTTypeCreatePreferredIdentifierForTag (kUTTagClassFilenameExtension
,
528 CFRelease (extension
);
534 if (data
&& (!filename
|| !uti
||
535 CFStringCompare (uti
, CFSTR ("public.data"), 0) == kCFCompareEqualTo
))
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
)
543 gchar
*uti_str
= g_content_type_from_mime_type (sniffed_mimetype
);
544 uti
= create_cfstring_from_cstr (uti_str
);
547 if (!uti
&& looks_like_text (data
, data_size
))
549 if (g_str_has_prefix ((const gchar
*)data
, "#!/"))
550 uti
= CFStringCreateCopy (NULL
, CFSTR ("public.script"));
552 uti
= CFStringCreateCopy (NULL
, CFSTR ("public.text"));
558 /* Generic data type */
559 uti
= CFStringCreateCopy (NULL
, CFSTR ("public.data"));
560 if (result_uncertain
)
561 *result_uncertain
= TRUE
;
563 else if (result_uncertain
)
565 *result_uncertain
= uncertain
== -1 ? FALSE
: uncertain
;
568 return create_cstr_from_cfstring (uti
);
572 g_content_types_get_registered (void)
574 /* TODO: UTTypeCreateAllIdentifiersForTag? */
579 g_content_type_guess_for_tree (GFile
*root
)