mirror of https://gitee.com/openkylin/glib2.0.git
609 lines
16 KiB
Objective-C
609 lines
16 KiB
Objective-C
/* 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.1 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>
|
||
|
||
#define XDG_PREFIX _gio_xdg
|
||
#include "xdgmime/xdgmime.h"
|
||
|
||
/* We lock this mutex whenever we modify global state in this module. */
|
||
G_LOCK_DEFINE_STATIC (gio_xdgmime);
|
||
|
||
|
||
/*< 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)
|
||
{
|
||
CFIndex length;
|
||
CFIndex maxlen;
|
||
gchar *buffer;
|
||
Boolean success;
|
||
|
||
g_return_val_if_fail (str != NULL, NULL);
|
||
|
||
length = CFStringGetLength (str);
|
||
maxlen = CFStringGetMaximumSizeForEncoding (length, kCFStringEncodingUTF8);
|
||
buffer = g_malloc (maxlen + 1);
|
||
success = CFStringGetCString (str, (char *) buffer, maxlen,
|
||
kCFStringEncodingUTF8);
|
||
CFRelease (str);
|
||
if (success)
|
||
return buffer;
|
||
else
|
||
{
|
||
g_free (buffer);
|
||
return NULL;
|
||
}
|
||
}
|
||
|
||
/*< 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 = NULL;
|
||
|
||
if (str)
|
||
cstr = create_cstr_from_cfstring (str);
|
||
if (!cstr)
|
||
return g_strdup (fallback);
|
||
|
||
return cstr;
|
||
}
|
||
|
||
/*< private >*/
|
||
void
|
||
g_content_type_set_mime_dirs (const gchar * const *dirs)
|
||
{
|
||
/* noop on macOS */
|
||
}
|
||
|
||
/*< private >*/
|
||
const gchar * const *
|
||
g_content_type_get_mime_dirs (void)
|
||
{
|
||
const gchar * const *mime_dirs = { NULL };
|
||
return mime_dirs;
|
||
}
|
||
|
||
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_mime_type (const gchar *type,
|
||
const gchar *mime_type)
|
||
{
|
||
gchar *content_type;
|
||
gboolean ret;
|
||
|
||
g_return_val_if_fail (type != NULL, FALSE);
|
||
g_return_val_if_fail (mime_type != NULL, FALSE);
|
||
|
||
content_type = g_content_type_from_mime_type (mime_type);
|
||
ret = g_content_type_is_a (type, content_type);
|
||
g_free (content_type);
|
||
|
||
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");
|
||
}
|
||
|
||
/* <internal>
|
||
* _get_generic_icon_name_from_mime_type
|
||
*
|
||
* This function produces a generic icon name from a @mime_type.
|
||
* If no generic icon name is found in the xdg mime database, the
|
||
* generic icon name is constructed.
|
||
*
|
||
* Background:
|
||
* generic-icon elements specify the icon to use as a generic icon for this
|
||
* particular mime-type, given by the name attribute. This is used if there
|
||
* is no specific icon (see icon for how these are found). These are used
|
||
* for categories of similar types (like spreadsheets or archives) that can
|
||
* use a common icon. The Icon Naming Specification lists a set of such
|
||
* icon names. If this element is not specified then the mimetype is used
|
||
* to generate the generic icon by using the top-level media type
|
||
* (e.g. "video" in "video/ogg") and appending "-x-generic"
|
||
* (i.e. "video-x-generic" in the previous example).
|
||
*
|
||
* From: https://specifications.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-0.18.html
|
||
*/
|
||
|
||
static gchar *
|
||
_get_generic_icon_name_from_mime_type (const gchar *mime_type)
|
||
{
|
||
const gchar *xdg_icon_name;
|
||
gchar *icon_name;
|
||
|
||
G_LOCK (gio_xdgmime);
|
||
xdg_icon_name = xdg_mime_get_generic_icon (mime_type);
|
||
G_UNLOCK (gio_xdgmime);
|
||
|
||
if (xdg_icon_name == NULL)
|
||
{
|
||
const char *p;
|
||
const char *suffix = "-x-generic";
|
||
gsize prefix_len;
|
||
|
||
p = strchr (mime_type, '/');
|
||
if (p == NULL)
|
||
prefix_len = strlen (mime_type);
|
||
else
|
||
prefix_len = p - mime_type;
|
||
|
||
icon_name = g_malloc (prefix_len + strlen (suffix) + 1);
|
||
memcpy (icon_name, mime_type, prefix_len);
|
||
memcpy (icon_name + prefix_len, suffix, strlen (suffix));
|
||
icon_name[prefix_len + strlen (suffix)] = 0;
|
||
}
|
||
else
|
||
{
|
||
icon_name = g_strdup (xdg_icon_name);
|
||
}
|
||
|
||
return icon_name;
|
||
}
|
||
|
||
|
||
static GIcon *
|
||
g_content_type_get_icon_internal (const gchar *uti,
|
||
gboolean symbolic)
|
||
{
|
||
char *mimetype_icon;
|
||
char *mime_type;
|
||
char *generic_mimetype_icon = NULL;
|
||
char *q;
|
||
char *icon_names[6];
|
||
int n = 0;
|
||
GIcon *themed_icon;
|
||
const char *xdg_icon;
|
||
int i;
|
||
|
||
g_return_val_if_fail (uti != NULL, NULL);
|
||
|
||
mime_type = g_content_type_get_mime_type (uti);
|
||
|
||
G_LOCK (gio_xdgmime);
|
||
xdg_icon = xdg_mime_get_icon (mime_type);
|
||
G_UNLOCK (gio_xdgmime);
|
||
|
||
if (xdg_icon)
|
||
icon_names[n++] = g_strdup (xdg_icon);
|
||
|
||
mimetype_icon = g_strdup (mime_type);
|
||
while ((q = strchr (mimetype_icon, '/')) != NULL)
|
||
*q = '-';
|
||
|
||
icon_names[n++] = mimetype_icon;
|
||
|
||
generic_mimetype_icon = _get_generic_icon_name_from_mime_type (mime_type);
|
||
|
||
if (generic_mimetype_icon)
|
||
icon_names[n++] = generic_mimetype_icon;
|
||
|
||
if (symbolic)
|
||
{
|
||
for (i = 0; i < n; i++)
|
||
{
|
||
icon_names[n + i] = icon_names[i];
|
||
icon_names[i] = g_strconcat (icon_names[i], "-symbolic", NULL);
|
||
}
|
||
|
||
n += n;
|
||
}
|
||
|
||
themed_icon = g_themed_icon_new_from_names (icon_names, n);
|
||
|
||
for (i = 0; i < n; i++)
|
||
g_free (icon_names[i]);
|
||
|
||
g_free(mime_type);
|
||
|
||
return themed_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;
|
||
/* Our tests assert that all text can be executable... */
|
||
else if (UTTypeConformsTo (uti, CFSTR("public.text")))
|
||
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.folder");
|
||
if (g_str_has_suffix (mime_type, "symlink"))
|
||
return g_strdup ("public.symlink");
|
||
}
|
||
|
||
/* This is correct according to the Apple docs:
|
||
https://developer.apple.com/library/content/documentation/Miscellaneous/Reference/UTIRef/Articles/System-DeclaredUniformTypeIdentifiers.html
|
||
*/
|
||
if (strcmp (mime_type, "text/plain") == 0)
|
||
return g_strdup ("public.text");
|
||
|
||
/* I don’t know of an appropriate equivalent for application/x-zerosize, but
|
||
* historically GLib has returned public.text for zero-sized files, so let’s
|
||
* continue doing that. */
|
||
if (strcmp (mime_type, "application/x-zerosize") == 0)
|
||
return g_strdup ("public.text");
|
||
|
||
/* Non standard type */
|
||
if (strcmp (mime_type, "application/x-executable") == 0)
|
||
return g_strdup ("public.executable");
|
||
|
||
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, ".folder"))
|
||
return g_strdup ("inode/directory");
|
||
if (g_str_has_suffix (type, ".symlink"))
|
||
return g_strdup ("inode/symlink");
|
||
if (g_str_has_suffix (type, ".executable"))
|
||
return g_strdup ("application/x-executable");
|
||
}
|
||
|
||
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;
|
||
int uncertain = -1;
|
||
|
||
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);
|
||
uncertain = TRUE;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
uti = CFStringCreateCopy (NULL, kUTTypeFolder);
|
||
uncertain = TRUE; /* Matches Unix backend */
|
||
}
|
||
}
|
||
else
|
||
{
|
||
/* GTK needs this... */
|
||
if (g_str_has_suffix (basename, ".ui"))
|
||
{
|
||
uti = CFStringCreateCopy (NULL, kUTTypeXML);
|
||
}
|
||
else if (g_str_has_suffix (basename, ".txt"))
|
||
{
|
||
uti = CFStringCreateCopy (NULL, CFSTR ("public.text"));
|
||
}
|
||
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);
|
||
}
|
||
}
|
||
if (data && (!filename || !uti ||
|
||
CFStringCompare (uti, CFSTR ("public.data"), 0) == kCFCompareEqualTo))
|
||
{
|
||
const char *sniffed_mimetype;
|
||
G_LOCK (gio_xdgmime);
|
||
sniffed_mimetype = xdg_mime_get_mime_type_for_data (data, data_size, NULL);
|
||
G_UNLOCK (gio_xdgmime);
|
||
if (sniffed_mimetype != XDG_MIME_TYPE_UNKNOWN)
|
||
{
|
||
gchar *uti_str = g_content_type_from_mime_type (sniffed_mimetype);
|
||
uti = create_cfstring_from_cstr (uti_str);
|
||
g_free (uti_str);
|
||
}
|
||
if (!uti && looks_like_text (data, data_size))
|
||
{
|
||
if (g_str_has_prefix ((const gchar*)data, "#!/"))
|
||
uti = CFStringCreateCopy (NULL, CFSTR ("public.script"));
|
||
else
|
||
uti = CFStringCreateCopy (NULL, CFSTR ("public.text"));
|
||
}
|
||
}
|
||
|
||
if (!uti)
|
||
{
|
||
/* Generic data type */
|
||
uti = CFStringCreateCopy (NULL, CFSTR ("public.data"));
|
||
if (result_uncertain)
|
||
*result_uncertain = TRUE;
|
||
}
|
||
else if (result_uncertain)
|
||
{
|
||
*result_uncertain = uncertain == -1 ? FALSE : uncertain;
|
||
}
|
||
|
||
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;
|
||
}
|