summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTingPing <tingping@tingping.se>2014-08-15 06:11:38 -0400
committerMatthias Clasen <mclasen@redhat.com>2017-03-11 09:48:43 -0500
commit42ed4e93acef45c8a1f2f0098da27ace645e89d2 (patch)
treef84b2cfc9e50218041f5a2e17870b97c8f40b95f
parent626e9e6b6e3c362e5293b5f1393ac0a42a63d291 (diff)
downloadglib-42ed4e93acef45c8a1f2f0098da27ace645e89d2.tar.gz
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
-rw-r--r--gio/Makefile.am24
-rw-r--r--gio/gcontenttype.c5
-rw-r--r--gio/glocalfileinfo.c2
-rw-r--r--gio/gosxcontenttype.c423
4 files changed, 449 insertions, 5 deletions
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 <dirent.h>
@@ -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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include "gcontenttype.h"
+#include "gicon.h"
+#include "gthemedicon.h"
+
+#include <CoreServices/CoreServices.h>
+
+
+/*< 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;
+}