From 42ed4e93acef45c8a1f2f0098da27ace645e89d2 Mon Sep 17 00:00:00 2001 From: TingPing Date: Fri, 15 Aug 2014 06:11:38 -0400 Subject: [PATCH] Implement GContentType on OSX This is an implementation of most of GContentType using the OS X UTType APIs. Missing at this point is an implementation of g_content_types_get_registered() and g_content_type_guess_for_tree(). https://bugzilla.gnome.org/show_bug.cgi?id=734946 --- gio/Makefile.am | 24 ++- gio/gcontenttype.c | 5 +- gio/glocalfileinfo.c | 2 +- gio/gosxcontenttype.c | 423 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 449 insertions(+), 5 deletions(-) create mode 100644 gio/gosxcontenttype.c diff --git a/gio/Makefile.am b/gio/Makefile.am index f6aab5770..fbf486991 100644 --- a/gio/Makefile.am +++ b/gio/Makefile.am @@ -3,8 +3,10 @@ include $(top_srcdir)/glib.mk SUBDIRS = gdbus-2.0/codegen if OS_UNIX +if !OS_COCOA SUBDIRS += xdgmime endif +endif if OS_WIN32_AND_DLL_COMPILATION if MS_LIB_AVAILABLE @@ -221,6 +223,7 @@ local_sources = \ platform_libadd = platform_deps = appinfo_sources = +contenttype_sources = if HAVE_INOTIFY SUBDIRS += inotify @@ -247,9 +250,13 @@ SUBDIRS += fam endif if OS_UNIX -unix_appinfo_sources = gdesktopappinfo.c +if !OS_COCOA platform_libadd += xdgmime/libxdgmime.la platform_deps += xdgmime/libxdgmime.la +endif + +appinfo_sources += gdesktopappinfo.c + unix_sources = \ gfiledescriptorbased.c \ gunixconnection.c \ @@ -266,7 +273,6 @@ unix_sources = \ gunixvolumemonitor.h \ gunixinputstream.c \ gunixoutputstream.c \ - gcontenttype.c \ gcontenttypeprivate.h \ gfdonotificationbackend.c \ ggtknotificationbackend.c \ @@ -321,7 +327,6 @@ win32_actual_sources = \ $(gdbus_daemon_sources) \ gwin32registrykey.c \ gwin32registrykey.h \ - gcontenttype-win32.c \ gwin32mount.c \ gwin32mount.h \ gwin32volumemonitor.c \ @@ -344,6 +349,8 @@ win32_more_sources_for_vcproj = \ if OS_WIN32 win32_appinfo_sources = gwin32appinfo.c gwin32appinfo.h +appinfo_sources += gwin32appinfo.c gwin32appinfo.h +contenttype_sources += contenttype-win32.c platform_libadd += -lshlwapi -lws2_32 -ldnsapi -liphlpapi win32_sources = $(win32_actual_sources) appinfo_sources += $(win32_appinfo_sources) @@ -387,6 +394,16 @@ portal_sources = \ $(xdp_dbus_built_sources) \ $(NULL) +if OS_COCOA +contenttype_sources += gosxcontenttype.c +endif + +if OS_UNIX +if !OS_COCOA +contenttype_sources += gcontenttype.c +endif +endif + gio_base_sources = \ gappinfo.c \ gappinfoprivate.h \ @@ -542,6 +559,7 @@ gio_base_sources = \ libgio_2_0_la_SOURCES = \ $(gio_base_sources) \ $(appinfo_sources) \ + $(contenttype_sources) \ $(unix_sources) \ $(win32_sources) \ $(settings_sources) \ diff --git a/gio/gcontenttype.c b/gio/gcontenttype.c index d5c2b5a00..339fc4e9d 100644 --- a/gio/gcontenttype.c +++ b/gio/gcontenttype.c @@ -46,6 +46,8 @@ * On Win32 it is an extension string like ".doc", ".txt" or a perceived * string like "audio". Such strings can be looked up in the registry at * HKEY_CLASSES_ROOT. + * On OSX it is a [Uniform Type Identifier](https://en.wikipedia.org/wiki/Uniform_Type_Identifier) + * such as "com.apple.application". **/ #include @@ -187,7 +189,8 @@ g_content_type_is_mime_type (const gchar *type, * * Checks if the content type is the generic "unknown" type. * On UNIX this is the "application/octet-stream" mimetype, - * while on win32 it is "*". + * while on win32 it is "*" and on OSX it is a dynamic type + * or octet-stream. * * Returns: %TRUE if the type is the unknown type. */ diff --git a/gio/glocalfileinfo.c b/gio/glocalfileinfo.c index 595d73654..113a20bc1 100644 --- a/gio/glocalfileinfo.c +++ b/gio/glocalfileinfo.c @@ -1259,7 +1259,7 @@ get_content_type (const char *basename, content_type = g_content_type_guess (basename, NULL, 0, &result_uncertain); -#ifndef G_OS_WIN32 +#if !defined(G_OS_WIN32) && !defined(HAVE_COCOA) if (!fast && result_uncertain && path != NULL) { guchar sniff_buffer[4096]; diff --git a/gio/gosxcontenttype.c b/gio/gosxcontenttype.c new file mode 100644 index 000000000..04c74391f --- /dev/null +++ b/gio/gosxcontenttype.c @@ -0,0 +1,423 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2014 Patrick Griffis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see . + * + */ + +#include "config.h" + +#include "gcontenttype.h" +#include "gicon.h" +#include "gthemedicon.h" + +#include + + +/*< internal > + * create_cfstring_from_cstr: + * @cstr: a #gchar + * + * Converts a cstr to a utf8 cfstring + * It must be CFReleased()'d. + * + */ +static CFStringRef +create_cfstring_from_cstr (const gchar *cstr) +{ + return CFStringCreateWithCString (NULL, cstr, kCFStringEncodingUTF8); +} + +/*< internal > + * create_cstr_from_cfstring: + * @str: a #CFStringRef + * + * Converts a cfstring to a utf8 cstring. + * The incoming cfstring is released for you. + * The returned string must be g_free()'d. + * + */ +static gchar * +create_cstr_from_cfstring (CFStringRef str) +{ + const gchar *cstr; + + if (str == NULL) + return NULL; + + cstr = CFStringGetCStringPtr (str, kCFStringEncodingUTF8); + CFRelease (str); + + return g_strdup (cstr); +} + +/*< internal > + * create_cstr_from_cfstring_with_fallback: + * @str: a #CFStringRef + * @fallback: a #gchar + * + * Tries to convert a cfstring to a utf8 cstring. + * If @str is NULL or conversion fails @fallback is returned. + * The incoming cfstring is released for you. + * The returned string must be g_free()'d. + * + */ +static gchar * +create_cstr_from_cfstring_with_fallback (CFStringRef str, + const gchar *fallback) +{ + gchar *cstr; + + cstr = create_cstr_from_cfstring (str); + if (!cstr) + return g_strdup (fallback); + + return cstr; +} + +gboolean +g_content_type_equals (const gchar *type1, + const gchar *type2) +{ + CFStringRef str1, str2; + gboolean ret; + + g_return_val_if_fail (type1 != NULL, FALSE); + g_return_val_if_fail (type2 != NULL, FALSE); + + if (g_ascii_strcasecmp (type1, type2) == 0) + return TRUE; + + str1 = create_cfstring_from_cstr (type1); + str2 = create_cfstring_from_cstr (type2); + + ret = UTTypeEqual (str1, str2); + + CFRelease (str1); + CFRelease (str2); + + return ret; +} + +gboolean +g_content_type_is_a (const gchar *ctype, + const gchar *csupertype) +{ + CFStringRef type, supertype; + gboolean ret; + + g_return_val_if_fail (ctype != NULL, FALSE); + g_return_val_if_fail (csupertype != NULL, FALSE); + + type = create_cfstring_from_cstr (ctype); + supertype = create_cfstring_from_cstr (csupertype); + + ret = UTTypeConformsTo (type, supertype); + + CFRelease (type); + CFRelease (supertype); + + return ret; +} + +gboolean +g_content_type_is_unknown (const gchar *type) +{ + g_return_val_if_fail (type != NULL, FALSE); + + /* Should dynamic types be considered "unknown"? */ + if (g_str_has_prefix (type, "dyn.")) + return TRUE; + /* application/octet-stream */ + else if (g_strcmp0 (type, "public.data") == 0) + return TRUE; + + return FALSE; +} + +gchar * +g_content_type_get_description (const gchar *type) +{ + CFStringRef str; + CFStringRef desc_str; + + g_return_val_if_fail (type != NULL, NULL); + + str = create_cfstring_from_cstr (type); + desc_str = UTTypeCopyDescription (str); + + CFRelease (str); + return create_cstr_from_cfstring_with_fallback (desc_str, "unknown"); +} + +static GIcon * +g_content_type_get_icon_internal (const gchar *type, + gboolean symbolic) +{ + GIcon *icon = NULL; + gchar *name; + + g_return_val_if_fail (type != NULL, NULL); + + /* TODO: Show mimetype icons. */ + if (g_content_type_can_be_executable (type)) + name = "gtk-execute"; + else if (g_content_type_is_a (type, "public.directory")) + name = symbolic ? "inode-directory-symbolic" : "inode-directory"; + else + name = "gtk-file"; + + icon = g_themed_icon_new_with_default_fallbacks (name); + + return icon; +} + +GIcon * +g_content_type_get_icon (const gchar *type) +{ + return g_content_type_get_icon_internal (type, FALSE); +} + +GIcon * +g_content_type_get_symbolic_icon (const gchar *type) +{ + return g_content_type_get_icon_internal (type, TRUE); +} + +gchar * +g_content_type_get_generic_icon_name (const gchar *type) +{ + return NULL; +} + +gboolean +g_content_type_can_be_executable (const gchar *type) +{ + CFStringRef uti; + gboolean ret = FALSE; + + g_return_val_if_fail (type != NULL, FALSE); + + uti = create_cfstring_from_cstr (type); + + if (UTTypeConformsTo (uti, kUTTypeApplication)) + ret = TRUE; + else if (UTTypeConformsTo (uti, CFSTR("public.executable"))) + ret = TRUE; + else if (UTTypeConformsTo (uti, CFSTR("public.script"))) + ret = TRUE; + + CFRelease (uti); + return ret; +} + +gchar * +g_content_type_from_mime_type (const gchar *mime_type) +{ + CFStringRef mime_str; + CFStringRef uti_str; + + g_return_val_if_fail (mime_type != NULL, NULL); + + /* Their api does not handle globs but they are common. */ + if (g_str_has_suffix (mime_type, "*")) + { + if (g_str_has_prefix (mime_type, "audio")) + return g_strdup ("public.audio"); + if (g_str_has_prefix (mime_type, "image")) + return g_strdup ("public.image"); + if (g_str_has_prefix (mime_type, "text")) + return g_strdup ("public.text"); + if (g_str_has_prefix (mime_type, "video")) + return g_strdup ("public.movie"); + } + + /* Some exceptions are needed for gdk-pixbuf. + * This list is not exhaustive. + */ + if (g_str_has_prefix (mime_type, "image")) + { + if (g_str_has_suffix (mime_type, "x-icns")) + return g_strdup ("com.apple.icns"); + if (g_str_has_suffix (mime_type, "x-tga")) + return g_strdup ("com.truevision.tga-image"); + if (g_str_has_suffix (mime_type, "x-ico")) + return g_strdup ("com.microsoft.ico "); + } + + /* These are also not supported... + * Used in glocalfileinfo.c + */ + if (g_str_has_prefix (mime_type, "inode")) + { + if (g_str_has_suffix (mime_type, "directory")) + return g_strdup ("public.directory"); + if (g_str_has_suffix (mime_type, "symlink")) + return g_strdup ("public.symlink"); + } + + mime_str = create_cfstring_from_cstr (mime_type); + uti_str = UTTypeCreatePreferredIdentifierForTag (kUTTagClassMIMEType, mime_str, NULL); + + CFRelease (mime_str); + return create_cstr_from_cfstring_with_fallback (uti_str, "public.data"); +} + +gchar * +g_content_type_get_mime_type (const gchar *type) +{ + CFStringRef uti_str; + CFStringRef mime_str; + + g_return_val_if_fail (type != NULL, NULL); + + /* We must match the additions above + * so conversions back and forth work. + */ + if (g_str_has_prefix (type, "public")) + { + if (g_str_has_suffix (type, ".image")) + return g_strdup ("image/*"); + if (g_str_has_suffix (type, ".movie")) + return g_strdup ("video/*"); + if (g_str_has_suffix (type, ".text")) + return g_strdup ("text/*"); + if (g_str_has_suffix (type, ".audio")) + return g_strdup ("audio/*"); + if (g_str_has_suffix (type, ".directory")) + return g_strdup ("inode/directory"); + if (g_str_has_suffix (type, ".symlink")) + return g_strdup ("inode/symlink"); + } + + uti_str = create_cfstring_from_cstr (type); + mime_str = UTTypeCopyPreferredTagWithClass(uti_str, kUTTagClassMIMEType); + + CFRelease (uti_str); + return create_cstr_from_cfstring_with_fallback (mime_str, "application/octet-stream"); +} + +static gboolean +looks_like_text (const guchar *data, + gsize data_size) +{ + gsize i; + guchar c; + + for (i = 0; i < data_size; i++) + { + c = data[i]; + if (g_ascii_iscntrl (c) && !g_ascii_isspace (c) && c != '\b') + return FALSE; + } + return TRUE; +} + +gchar * +g_content_type_guess (const gchar *filename, + const guchar *data, + gsize data_size, + gboolean *result_uncertain) +{ + CFStringRef uti = NULL; + gchar *cextension; + CFStringRef extension; + + g_return_val_if_fail (data_size != (gsize) -1, NULL); + + if (filename && *filename) + { + gchar *basename = g_path_get_basename (filename); + gchar *dirname = g_path_get_dirname (filename); + gsize i = strlen (filename); + + if (filename[i - 1] == '/') + { + if (g_strcmp0 (dirname, "/Volumes") == 0) + { + uti = CFStringCreateCopy (NULL, kUTTypeVolume); + } + else if ((cextension = strrchr (basename, '.')) != NULL) + { + cextension++; + extension = create_cfstring_from_cstr (cextension); + uti = UTTypeCreatePreferredIdentifierForTag (kUTTagClassFilenameExtension, + extension, NULL); + CFRelease (extension); + + if (CFStringHasPrefix (uti, CFSTR ("dyn."))) + { + CFRelease (uti); + uti = CFStringCreateCopy (NULL, kUTTypeFolder); + } + } + else + { + uti = CFStringCreateCopy (NULL, kUTTypeFolder); + } + } + else + { + /* GTK needs this... */ + if (g_str_has_suffix (basename, ".ui")) + { + uti = CFStringCreateCopy (NULL, kUTTypeXML); + } + else if ((cextension = strrchr (basename, '.')) != NULL) + { + cextension++; + extension = create_cfstring_from_cstr (cextension); + uti = UTTypeCreatePreferredIdentifierForTag (kUTTagClassFilenameExtension, + extension, NULL); + CFRelease (extension); + } + g_free (basename); + g_free (dirname); + } + } + else if (data) + { + if (looks_like_text (data, data_size)) + { + if (g_str_has_prefix ((const gchar*)data, "#!/")) + uti = CFStringCreateCopy (NULL, CFSTR ("public.script")); + else + uti = CFStringCreateCopy (NULL, kUTTypePlainText); + } + } + + if (!uti) + { + /* Generic data type */ + uti = CFStringCreateCopy (NULL, CFSTR ("public.data")); + if (result_uncertain) + *result_uncertain = TRUE; + } + + return create_cstr_from_cfstring (uti); +} + +GList * +g_content_types_get_registered (void) +{ + /* TODO: UTTypeCreateAllIdentifiersForTag? */ + return NULL; +} + +gchar ** +g_content_type_guess_for_tree (GFile *root) +{ + return NULL; +}