Add myself to the DOAP for GLib
[glib.git] / gio / gosxcontenttype.c
blob52ba5763afc64a78f959fd381e72c511fb1b4425
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/>.
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
39 * Converts a cstr to a utf8 cfstring
40 * It must be CFReleased()'d.
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
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.
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
73 g_free (buffer);
74 return NULL;
78 /*< internal >
79 * create_cstr_from_cfstring_with_fallback:
80 * @str: a #CFStringRef
81 * @fallback: a #gchar
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.
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
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)
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;
243 else
245 icon_name = g_strdup (xdg_icon_name);
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)
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);
296 n += n;
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, "*"))
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");
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.
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);
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++)
458 c = data[i];
459 if (g_ascii_iscntrl (c) && !g_ascii_isspace (c) && c != '\b')
460 return FALSE;
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)
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)
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.")))
500 CFRelease (uti);
501 uti = CFStringCreateCopy (NULL, kUTTypeFolder);
502 uncertain = TRUE;
505 else
507 uti = CFStringCreateCopy (NULL, kUTTypeFolder);
508 uncertain = TRUE; /* Matches Unix backend */
511 else
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)
524 cextension++;
525 extension = create_cfstring_from_cstr (cextension);
526 uti = UTTypeCreatePreferredIdentifierForTag (kUTTagClassFilenameExtension,
527 extension, NULL);
528 CFRelease (extension);
530 g_free (basename);
531 g_free (dirname);
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);
545 g_free (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"));
551 else
552 uti = CFStringCreateCopy (NULL, CFSTR ("public.text"));
556 if (!uti)
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);
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;