diff options
author | Ernestas Kulik <ernestask@gnome.org> | 2018-04-28 13:21:50 +0300 |
---|---|---|
committer | Ernestas Kulik <ernestask@gnome.org> | 2018-05-22 16:52:48 +0300 |
commit | 673c81cf9f1d68b71041220e6e44624dee44dbfc (patch) | |
tree | 85637603bbf6e2333ae7a0bbcb52068ca655ad8d /src | |
parent | 0aca206f8a47c3b35d3d0bc2964e5f672d7a3794 (diff) | |
download | nautilus-673c81cf9f1d68b71041220e6e44624dee44dbfc.tar.gz |
general: Copy gnome-desktop thumbnailing code
This is one of prerequisite steps to take before fully switching to GTK+
4, as gnome-desktop has code, depending on GTK+ 3. Since the
thumbnailing machinery is self-contained, it can easily be just copied
over.
Diffstat (limited to 'src')
-rw-r--r-- | src/gnome-desktop/gnome-desktop-thumbnail-script.c | 832 | ||||
-rw-r--r-- | src/gnome-desktop/gnome-desktop-thumbnail-script.h | 38 | ||||
-rw-r--r-- | src/gnome-desktop/gnome-desktop-thumbnail.c | 1301 | ||||
-rw-r--r-- | src/gnome-desktop/gnome-desktop-thumbnail.h | 102 | ||||
-rw-r--r-- | src/meson.build | 7 | ||||
-rw-r--r-- | src/nautilus-properties-window.c | 2 | ||||
-rw-r--r-- | src/nautilus-thumbnails.c | 2 |
7 files changed, 2281 insertions, 3 deletions
diff --git a/src/gnome-desktop/gnome-desktop-thumbnail-script.c b/src/gnome-desktop/gnome-desktop-thumbnail-script.c new file mode 100644 index 000000000..14e2fed3a --- /dev/null +++ b/src/gnome-desktop/gnome-desktop-thumbnail-script.c @@ -0,0 +1,832 @@ +/* + * Copyright (C) 2002, 2017 Red Hat, Inc. + * Copyright (C) 2010 Carlos Garcia Campos <carlosgc@gnome.org> + * + * This file is part of the Gnome Library. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * Authors: Alexander Larsson <alexl@redhat.com> + * Carlos Garcia Campos <carlosgc@gnome.org> + * Bastien Nocera <hadess@hadess.net> + */ + +#include "config.h" + +#include <gio/gio.h> +#include <glib/gstdio.h> +#include <string.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <fcntl.h> + +#ifdef ENABLE_SECCOMP +#include <errno.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <sys/utsname.h> +#include <seccomp.h> +#endif + +#include "gnome-desktop-thumbnail-script.h" + +typedef struct { + gboolean sandbox; + char *thumbnailer_name; + GArray *fd_array; + /* Input/output file paths outside the sandbox */ + char *infile; + char *infile_tmp; /* the host version of /tmp/gnome-desktop-file-to-thumbnail.* */ + char *outfile; + char *outdir; /* outdir is outfile's parent dir, if it needs to be deleted */ + /* I/O file paths inside the sandbox */ + char *s_infile; + char *s_outfile; +} ScriptExec; + +static char * +expand_thumbnailing_elem (const char *elem, + const int size, + const char *infile, + const char *outfile, + gboolean *got_input, + gboolean *got_output) +{ + GString *str; + const char *p, *last; + char *inuri; + + str = g_string_new (NULL); + + last = elem; + while ((p = strchr (last, '%')) != NULL) + { + g_string_append_len (str, last, p - last); + p++; + + switch (*p) { + case 'u': + inuri = g_filename_to_uri (infile, NULL, NULL); + if (inuri) + { + g_string_append (str, inuri); + *got_input = TRUE; + g_free (inuri); + } + p++; + break; + case 'i': + g_string_append (str, infile); + *got_input = TRUE; + p++; + break; + case 'o': + g_string_append (str, outfile); + *got_output = TRUE; + p++; + break; + case 's': + g_string_append_printf (str, "%d", size); + p++; + break; + case '%': + g_string_append_c (str, '%'); + p++; + break; + case 0: + default: + break; + } + last = p; + } + g_string_append (str, last); + + return g_string_free (str, FALSE); +} + +/* From https://github.com/flatpak/flatpak/blob/master/common/flatpak-run.c */ +G_GNUC_NULL_TERMINATED +static void +add_args (GPtrArray *argv_array, ...) +{ + va_list args; + const gchar *arg; + + va_start (args, argv_array); + while ((arg = va_arg (args, const gchar *))) + g_ptr_array_add (argv_array, g_strdup (arg)); + va_end (args); +} + +static void +add_env (GPtrArray *array, + const char *envvar) +{ + if (g_getenv (envvar) != NULL) + add_args (array, + "--setenv", envvar, g_getenv (envvar), + NULL); +} + +static char * +get_extension (const char *path) +{ + g_autofree char *basename = NULL; + char *p; + + basename = g_path_get_basename (path); + p = strrchr (basename, '.'); + if (p == NULL) + return NULL; + return g_strdup (p + 1); +} + +#ifdef ENABLE_SECCOMP +static gboolean +flatpak_fail (GError **error, + const char *msg, + ...) +{ + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, msg); + return FALSE; +} + +/* From https://github.com/flatpak/flatpak/blob/master/common/flatpak-utils.c */ +#if !defined(__i386__) && !defined(__x86_64__) && !defined(__aarch64__) && !defined(__arm__) +static const char * +flatpak_get_kernel_arch (void) +{ + static struct utsname buf; + static char *arch = NULL; + char *m; + + if (arch != NULL) + return arch; + + if (uname (&buf)) + { + arch = "unknown"; + return arch; + } + + /* By default, just pass on machine, good enough for most arches */ + arch = buf.machine; + + /* Override for some arches */ + + m = buf.machine; + /* i?86 */ + if (strlen (m) == 4 && m[0] == 'i' && m[2] == '8' && m[3] == '6') + { + arch = "i386"; + } + else if (g_str_has_prefix (m, "arm")) + { + if (g_str_has_suffix (m, "b")) + arch = "armeb"; + else + arch = "arm"; + } + else if (strcmp (m, "mips") == 0) + { +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + arch = "mipsel"; +#endif + } + else if (strcmp (m, "mips64") == 0) + { +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + arch = "mips64el"; +#endif + } + + return arch; +} +#endif + +/* This maps the kernel-reported uname to a single string representing + * the cpu family, in the sense that all members of this family would + * be able to understand and link to a binary file with such cpu + * opcodes. That doesn't necessarily mean that all members of the + * family can run all opcodes, for instance for modern 32bit intel we + * report "i386", even though they support instructions that the + * original i386 cpu cannot run. Still, such an executable would + * at least try to execute a 386, whereas an arm binary would not. + */ +static const char * +flatpak_get_arch (void) +{ + /* Avoid using uname on multiarch machines, because uname reports the kernels + * arch, and that may be different from userspace. If e.g. the kernel is 64bit and + * the userspace is 32bit we want to use 32bit by default. So, we take the current build + * arch as the default. */ +#if defined(__i386__) + return "i386"; +#elif defined(__x86_64__) + return "x86_64"; +#elif defined(__aarch64__) + return "aarch64"; +#elif defined(__arm__) +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + return "arm"; +#else + return "armeb"; +#endif +#else + return flatpak_get_kernel_arch (); +#endif +} + +/* From https://github.com/flatpak/flatpak/blob/master/common/flatpak-run.c */ +static const uint32_t seccomp_x86_64_extra_arches[] = { SCMP_ARCH_X86, 0, }; + +#ifdef SCMP_ARCH_AARCH64 +static const uint32_t seccomp_aarch64_extra_arches[] = { SCMP_ARCH_ARM, 0 }; +#endif + +static inline void +cleanup_seccomp (void *p) +{ + scmp_filter_ctx *pp = (scmp_filter_ctx *) p; + + if (*pp) + seccomp_release (*pp); +} + +static gboolean +setup_seccomp (GPtrArray *argv_array, + GArray *fd_array, + const char *arch, + gboolean multiarch, + gboolean devel, + GError **error) +{ + __attribute__((cleanup (cleanup_seccomp))) scmp_filter_ctx seccomp = NULL; + + /**** BEGIN NOTE ON CODE SHARING + * + * There are today a number of different Linux container + * implementations. That will likely continue for long into the + * future. But we can still try to share code, and it's important + * to do so because it affects what library and application writers + * can do, and we should support code portability between different + * container tools. + * + * This syscall blacklist is copied from linux-user-chroot, which was in turn + * clearly influenced by the Sandstorm.io blacklist. + * + * If you make any changes here, I suggest sending the changes along + * to other sandbox maintainers. Using the libseccomp list is also + * an appropriate venue: + * https://groups.google.com/forum/#!topic/libseccomp + * + * A non-exhaustive list of links to container tooling that might + * want to share this blacklist: + * + * https://github.com/sandstorm-io/sandstorm + * in src/sandstorm/supervisor.c++ + * http://cgit.freedesktop.org/xdg-app/xdg-app/ + * in common/flatpak-run.c + * https://git.gnome.org/browse/linux-user-chroot + * in src/setup-seccomp.c + * + **** END NOTE ON CODE SHARING + */ + struct + { + int scall; + struct scmp_arg_cmp *arg; + } syscall_blacklist[] = { + /* Block dmesg */ + {SCMP_SYS (syslog)}, + /* Useless old syscall */ + {SCMP_SYS (uselib)}, + /* Don't allow you to switch to bsd emulation or whatnot */ + {SCMP_SYS (personality)}, + /* Don't allow disabling accounting */ + {SCMP_SYS (acct)}, + /* 16-bit code is unnecessary in the sandbox, and modify_ldt is a + historic source of interesting information leaks. */ + {SCMP_SYS (modify_ldt)}, + /* Don't allow reading current quota use */ + {SCMP_SYS (quotactl)}, + + /* Don't allow access to the kernel keyring */ + {SCMP_SYS (add_key)}, + {SCMP_SYS (keyctl)}, + {SCMP_SYS (request_key)}, + + /* Scary VM/NUMA ops */ + {SCMP_SYS (move_pages)}, + {SCMP_SYS (mbind)}, + {SCMP_SYS (get_mempolicy)}, + {SCMP_SYS (set_mempolicy)}, + {SCMP_SYS (migrate_pages)}, + + /* Don't allow subnamespace setups: */ + {SCMP_SYS (unshare)}, + {SCMP_SYS (mount)}, + {SCMP_SYS (pivot_root)}, + {SCMP_SYS (clone), &SCMP_A0 (SCMP_CMP_MASKED_EQ, CLONE_NEWUSER, CLONE_NEWUSER)}, + + /* Don't allow faking input to the controlling tty (CVE-2017-5226) */ + {SCMP_SYS (ioctl), &SCMP_A1(SCMP_CMP_EQ, (int)TIOCSTI)}, + }; + + struct + { + int scall; + struct scmp_arg_cmp *arg; + } syscall_nondevel_blacklist[] = { + /* Profiling operations; we expect these to be done by tools from outside + * the sandbox. In particular perf has been the source of many CVEs. + */ + {SCMP_SYS (perf_event_open)}, + {SCMP_SYS (ptrace)} + }; + /* Blacklist all but unix, inet, inet6 and netlink */ + int socket_family_blacklist[] = { + AF_AX25, + AF_IPX, + AF_APPLETALK, + AF_NETROM, + AF_BRIDGE, + AF_ATMPVC, + AF_X25, + AF_ROSE, + AF_DECnet, + AF_NETBEUI, + AF_SECURITY, + AF_KEY, + AF_NETLINK + 1, /* Last gets CMP_GE, so order is important */ + }; + guint i; + int r; + int fd = -1; + g_autofree char *fd_str = NULL; + g_autofree char *path = NULL; + + seccomp = seccomp_init (SCMP_ACT_ALLOW); + if (!seccomp) + return flatpak_fail (error, "Initialize seccomp failed"); + + if (arch != NULL) + { + uint32_t arch_id = 0; + const uint32_t *extra_arches = NULL; + + if (strcmp (arch, "i386") == 0) + { + arch_id = SCMP_ARCH_X86; + } + else if (strcmp (arch, "x86_64") == 0) + { + arch_id = SCMP_ARCH_X86_64; + extra_arches = seccomp_x86_64_extra_arches; + } + else if (strcmp (arch, "arm") == 0) + { + arch_id = SCMP_ARCH_ARM; + } +#ifdef SCMP_ARCH_AARCH64 + else if (strcmp (arch, "aarch64") == 0) + { + arch_id = SCMP_ARCH_AARCH64; + extra_arches = seccomp_aarch64_extra_arches; + } +#endif + + /* We only really need to handle arches on multiarch systems. + * If only one arch is supported the default is fine */ + if (arch_id != 0) + { + /* This *adds* the target arch, instead of replacing the + native one. This is not ideal, because we'd like to only + allow the target arch, but we can't really disallow the + native arch at this point, because then bubblewrap + couldn't continue running. */ + r = seccomp_arch_add (seccomp, arch_id); + if (r < 0 && r != -EEXIST) + return flatpak_fail (error, "Failed to add architecture to seccomp filter"); + + if (multiarch && extra_arches != NULL) + { + for (i = 0; extra_arches[i] != 0; i++) + { + r = seccomp_arch_add (seccomp, extra_arches[i]); + if (r < 0 && r != -EEXIST) + return flatpak_fail (error, "Failed to add multiarch architecture to seccomp filter"); + } + } + } + } + + /* TODO: Should we filter the kernel keyring syscalls in some way? + * We do want them to be used by desktop apps, but they could also perhaps + * leak system stuff or secrets from other apps. + */ + + for (i = 0; i < G_N_ELEMENTS (syscall_blacklist); i++) + { + int scall = syscall_blacklist[i].scall; + if (syscall_blacklist[i].arg) + r = seccomp_rule_add (seccomp, SCMP_ACT_ERRNO (EPERM), scall, 1, *syscall_blacklist[i].arg); + else + r = seccomp_rule_add (seccomp, SCMP_ACT_ERRNO (EPERM), scall, 0); + if (r < 0 && r == -EFAULT /* unknown syscall */) + return flatpak_fail (error, "Failed to block syscall %d", scall); + } + + if (!devel) + { + for (i = 0; i < G_N_ELEMENTS (syscall_nondevel_blacklist); i++) + { + int scall = syscall_nondevel_blacklist[i].scall; + if (syscall_nondevel_blacklist[i].arg) + r = seccomp_rule_add (seccomp, SCMP_ACT_ERRNO (EPERM), scall, 1, *syscall_nondevel_blacklist[i].arg); + else + r = seccomp_rule_add (seccomp, SCMP_ACT_ERRNO (EPERM), scall, 0); + + if (r < 0 && r == -EFAULT /* unknown syscall */) + return flatpak_fail (error, "Failed to block syscall %d", scall); + } + } + + /* Socket filtering doesn't work on e.g. i386, so ignore failures here + * However, we need to user seccomp_rule_add_exact to avoid libseccomp doing + * something else: https://github.com/seccomp/libseccomp/issues/8 */ + for (i = 0; i < G_N_ELEMENTS (socket_family_blacklist); i++) + { + int family = socket_family_blacklist[i]; + if (i == G_N_ELEMENTS (socket_family_blacklist) - 1) + seccomp_rule_add_exact (seccomp, SCMP_ACT_ERRNO (EAFNOSUPPORT), SCMP_SYS (socket), 1, SCMP_A0 (SCMP_CMP_GE, family)); + else + seccomp_rule_add_exact (seccomp, SCMP_ACT_ERRNO (EAFNOSUPPORT), SCMP_SYS (socket), 1, SCMP_A0 (SCMP_CMP_EQ, family)); + } + + fd = g_file_open_tmp ("flatpak-seccomp-XXXXXX", &path, error); + if (fd == -1) + return FALSE; + + unlink (path); + + if (seccomp_export_bpf (seccomp, fd) != 0) + { + close (fd); + return flatpak_fail (error, "Failed to export bpf"); + } + + lseek (fd, 0, SEEK_SET); + + fd_str = g_strdup_printf ("%d", fd); + if (fd_array) + g_array_append_val (fd_array, fd); + + add_args (argv_array, + "--seccomp", fd_str, + NULL); + + fd = -1; /* Don't close on success */ + + return TRUE; +} +#endif + +#ifdef HAVE_BWRAP +static gboolean +add_bwrap (GPtrArray *array, + ScriptExec *script) +{ + g_return_val_if_fail (script->outdir != NULL, FALSE); + g_return_val_if_fail (script->s_infile != NULL, FALSE); + + add_args (array, + "bwrap", + "--ro-bind", "/usr", "/usr", + "--ro-bind", "/lib", "/lib", + "--ro-bind", "/lib64", "/lib64", + "--proc", "/proc", + "--dev", "/dev", + "--symlink", "usr/bin", "/bin", + "--symlink", "usr/sbin", "/sbin", + "--chdir", "/", + "--setenv", "GIO_USE_VFS", "local", + "--unshare-all", + "--die-with-parent", + NULL); + + add_env (array, "G_MESSAGES_DEBUG"); + add_env (array, "G_MESSAGES_PREFIXED"); + + /* Add gnome-desktop's install prefix if needed */ + if (g_strcmp0 (INSTALL_PREFIX, "") != 0 && + g_strcmp0 (INSTALL_PREFIX, "/usr") != 0 && + g_strcmp0 (INSTALL_PREFIX, "/usr/") != 0) + { + add_args (array, + "--ro-bind", INSTALL_PREFIX, INSTALL_PREFIX, + NULL); + } + + g_ptr_array_add (array, g_strdup ("--bind")); + g_ptr_array_add (array, g_strdup (script->outdir)); + g_ptr_array_add (array, g_strdup ("/tmp")); + + /* We make sure to also re-use the original file's original + * extension in case it's useful for the thumbnailer to + * identify the file type */ + g_ptr_array_add (array, g_strdup ("--ro-bind")); + g_ptr_array_add (array, g_strdup (script->infile)); + g_ptr_array_add (array, g_strdup (script->s_infile)); + + return TRUE; +} +#endif /* HAVE_BWRAP */ + +static char ** +expand_thumbnailing_cmd (const char *cmd, + ScriptExec *script, + int size, + GError **error) +{ + GPtrArray *array; + g_auto(GStrv) cmd_elems = NULL; + guint i; + gboolean got_in, got_out; + + if (!g_shell_parse_argv (cmd, NULL, &cmd_elems, error)) + return NULL; + + script->thumbnailer_name = g_strdup (cmd_elems[0]); + + array = g_ptr_array_new_with_free_func (g_free); + +#ifdef HAVE_BWRAP + if (script->sandbox) + { + if (!add_bwrap (array, script)) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Bubblewrap setup failed"); + goto bail; + } + } +#endif + +#ifdef ENABLE_SECCOMP + if (script->sandbox) + { + const char *arch; + + arch = flatpak_get_arch (); + g_assert (arch); + if (!setup_seccomp (array, + script->fd_array, + arch, + FALSE, + FALSE, + error)) + { + goto bail; + } + } +#endif + + got_in = got_out = FALSE; + for (i = 0; cmd_elems[i] != NULL; i++) + { + char *expanded; + + expanded = expand_thumbnailing_elem (cmd_elems[i], + size, + script->s_infile ? script->s_infile : script->infile, + script->s_outfile ? script->s_outfile : script->outfile, + &got_in, + &got_out); + + g_ptr_array_add (array, expanded); + } + + if (!got_in) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Input file could not be set"); + goto bail; + } + else if (!got_out) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Output file could not be set"); + goto bail; + } + + g_ptr_array_add (array, NULL); + + return (char **) g_ptr_array_free (array, FALSE); + +bail: + g_ptr_array_free (array, TRUE); + return NULL; +} + +static void +child_setup (gpointer user_data) +{ + GArray *fd_array = user_data; + guint i; + + /* If no fd_array was specified, don't care. */ + if (fd_array == NULL) + return; + + /* Otherwise, mark not - close-on-exec all the fds in the array */ + for (i = 0; i < fd_array->len; i++) + fcntl (g_array_index (fd_array, int, i), F_SETFD, 0); +} + +static void +script_exec_free (ScriptExec *exec) +{ + if (exec == NULL) + return; + + g_free (exec->thumbnailer_name); + g_free (exec->infile); + if (exec->infile_tmp) + { + g_unlink (exec->infile_tmp); + g_free (exec->infile_tmp); + } + if (exec->outfile) + { + g_unlink (exec->outfile); + g_free (exec->outfile); + } + if (exec->outdir) + { + if (g_rmdir (exec->outdir) < 0) + { + g_warning ("Could not remove %s, thumbnailer %s left files in directory", + exec->outdir, exec->thumbnailer_name); + } + g_free (exec->outdir); + } + g_free (exec->s_infile); + g_free (exec->s_outfile); + if (exec->fd_array) + g_array_free (exec->fd_array, TRUE); + g_free (exec); +} + +static void +clear_fd (gpointer data) +{ + int *fd_p = data; + if (fd_p != NULL && *fd_p != -1) + close (*fd_p); +} + +static ScriptExec * +script_exec_new (const char *uri, + GError **error) +{ + ScriptExec *exec; + g_autoptr(GFile) file = NULL; + + exec = g_new0 (ScriptExec, 1); +#ifdef HAVE_BWRAP + /* Bubblewrap is not used if the application is already sandboxed in + * Flatpak as all privileges to create a new namespace are dropped when + * the initial one is created. */ + if (!g_file_test ("/.flatpak-info", G_FILE_TEST_IS_REGULAR)) + exec->sandbox = TRUE; +#endif + + file = g_file_new_for_uri (uri); + + exec->infile = g_file_get_path (file); + if (!exec->infile) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Could not get path for URI '%s'", uri); + goto bail; + } + +#ifdef HAVE_BWRAP + if (exec->sandbox) + { + char *tmpl; + g_autofree char *ext = NULL; + g_autofree char *infile = NULL; + + exec->fd_array = g_array_new (FALSE, TRUE, sizeof (int)); + g_array_set_clear_func (exec->fd_array, clear_fd); + + tmpl = g_strdup ("/tmp/gnome-desktop-thumbnailer-XXXXXX"); + exec->outdir = g_mkdtemp (tmpl); + if (!exec->outdir) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Could not create temporary sandbox directory"); + goto bail; + } + exec->outfile = g_build_filename (exec->outdir, "gnome-desktop-thumbnailer.png", NULL); + ext = get_extension (exec->infile); + infile = g_strdup_printf ("gnome-desktop-file-to-thumbnail.%s", ext); + exec->infile_tmp = g_build_filename (exec->outdir, infile, NULL); + + exec->s_infile = g_build_filename ("/tmp/", infile, NULL); + exec->s_outfile = g_build_filename ("/tmp/", "gnome-desktop-thumbnailer.png", NULL); + } + else +#endif + { + int fd; + g_autofree char *tmpname = NULL; + + fd = g_file_open_tmp (".gnome_desktop_thumbnail.XXXXXX", &tmpname, error); + if (fd == -1) + goto bail; + close (fd); + exec->outfile = g_steal_pointer (&tmpname); + } + + return exec; + +bail: + script_exec_free (exec); + return NULL; +} + +static void +print_script_debug (GStrv expanded_script) +{ + GString *out; + guint i; + + out = g_string_new (NULL); + + for (i = 0; expanded_script[i]; i++) + g_string_append_printf (out, "%s ", expanded_script[i]); + g_string_append_printf (out, "\n"); + + g_debug ("About to launch script: %s", out->str); + g_string_free (out, TRUE); +} + +GBytes * +gnome_desktop_thumbnail_script_exec (const char *cmd, + int size, + const char *uri, + GError **error) +{ + g_autofree char *error_out = NULL; + g_auto(GStrv) expanded_script = NULL; + int exit_status; + gboolean ret; + GBytes *image = NULL; + ScriptExec *exec; + + exec = script_exec_new (uri, error); + if (!exec) + goto out; + expanded_script = expand_thumbnailing_cmd (cmd, exec, size, error); + if (expanded_script == NULL) + goto out; + + print_script_debug (expanded_script); + + ret = g_spawn_sync (NULL, expanded_script, NULL, G_SPAWN_SEARCH_PATH, + child_setup, exec->fd_array, NULL, &error_out, + &exit_status, error); + if (ret && g_spawn_check_exit_status (exit_status, error)) + { + char *contents; + gsize length; + + if (g_file_get_contents (exec->outfile, &contents, &length, error)) + image = g_bytes_new_take (contents, length); + } + else + { + g_debug ("Failed to launch script: %s", !ret ? (*error)->message : error_out); + } + +out: + script_exec_free (exec); + return image; +} + diff --git a/src/gnome-desktop/gnome-desktop-thumbnail-script.h b/src/gnome-desktop/gnome-desktop-thumbnail-script.h new file mode 100644 index 000000000..cbd6bbf67 --- /dev/null +++ b/src/gnome-desktop/gnome-desktop-thumbnail-script.h @@ -0,0 +1,38 @@ +/* + * gnome-thumbnail.h: Utilities for handling thumbnails + * + * Copyright (C) 2002, 2017 Red Hat, Inc. + * + * This file is part of the Gnome Library. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * Authors: Alexander Larsson <alexl@redhat.com> + * Bastien Nocera <hadess@hadess.net> + */ + +#ifndef GNOME_DESKTOP_THUMBNAIL_SCRIPT_H +#define GNOME_DESKTOP_THUMBNAIL_SCRIPT_H + +#include <glib.h> + +GBytes * +gnome_desktop_thumbnail_script_exec (const char *cmd, + int size, + const char *uri, + GError **error); + +#endif /* GNOME_DESKTOP_THUMBNAIL_SCRIPT_H */ diff --git a/src/gnome-desktop/gnome-desktop-thumbnail.c b/src/gnome-desktop/gnome-desktop-thumbnail.c new file mode 100644 index 000000000..b31bad58d --- /dev/null +++ b/src/gnome-desktop/gnome-desktop-thumbnail.c @@ -0,0 +1,1301 @@ +/* + * gnome-thumbnail.c: Utilities for handling thumbnails + * + * Copyright (C) 2002 Red Hat, Inc. + * Copyright (C) 2010 Carlos Garcia Campos <carlosgc@gnome.org> + * + * This file is part of the Gnome Library. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +/** + * SECTION:gnome-desktop-thumbnail + * @short_description: Generates and looks up thumbnails of files and + * directories + * @stability: Unstable + * @include: libgnome-desktop/gnome-desktop-thumbnail.h + * + * #GnomeDesktopThumbnailFactory allows generation and loading of thumbnails for + * local and remote files and directories. It uses a collection of programs + * called <firstterm>thumbnailers</firstterm>, each one generating thumbnails + * for a specific set of content-types of files. For example, + * <application>totem-video-thumbnailer</application> generates thumbnails for + * video files using GStreamer; <application>evince-thumbnailer</application> + * generates thumbnails for PDFs and other document files. If no specific + * thumbnailer exists for a file, or if the thumbnailer fails, gdk-pixbuf is + * used as a fallback. + * + * To generate a thumbnail, an appropriate thumbnailer program is selected then + * executed, passing it the URI of the file to thumbnail, plus a path to write + * the thumbnail image to. If thumbnailing succeeds, the thumbnailer should have + * written the image to disk before terminating; but if thumbnailing fails, no + * image should be written, and the thumbnailer should return a non-zero exit + * status. #GnomeDesktopThumbnailFactory will then fall back to using gdk-pixbuf + * to generate a thumbnail, if possible. + * + * Thumbnailers are chosen by examining a series of + * <filename>.thumbnailer</filename> files in + * <filename><replaceable>$PREFIX</replaceable>/share/thumbnailers</filename>. + * Each is in a simple key-file format: + * <informalexample><programlisting> + * [Thumbnailer Entry] + * Exec=evince-thumbnailer -s %s %u %o + * MimeType=application/pdf;application/x-bzpdf;application/x-gzpdf; + * </programlisting></informalexample> + * + * The <filename>.thumbnailer</filename> format supports three keys: + * <variablelist> + * <varlistentry><term><code>Exec</code></term><listitem><para> + * Required. The command to execute the thumbnailer. It supports a few different + * parameters which are replaced before calling the thumbnailer: + * <replaceable>%u</replaceable> is the URI of the file being thumbnailed; + * <replaceable>%i</replaceable> is its path; <replaceable>%o</replaceable> + * is the path of the image file to be written to; + * <replaceable>%s</replaceable> is the maximum desired size of the thumbnail + * image (the maximum width or height, in pixels); and + * <replaceable>%%</replaceable> is a literal percent character. + * </para></listitem></varlistentry> + * <varlistentry><term><code>MimeType</code></term><listitem><para> + * Required. A semicolon-separated list of MIME types which the thumbnailer + * supports generating thumbnails for. + * </para></listitem></varlistentry> + * </variablelist> + * + * So in the example <filename>.thumbnailer</filename> file above, the command + * passes the requested thumbnail size, then the input file’s URI, then the + * path for the output image file to + * <application>evince-thumbnailer</application>. + * + * The code to examine and call a thumbnailer is contained in + * #GnomeDesktopThumbnailFactory, which handles looking up the right thumbnailer + * script, building and executing the command for it, and loading the resulting + * thumbnail image into a #GdkPixbuf. + * + * Thumbnail caching is also supported by #GnomeDesktopThumbnailFactory. When + * calling a thumbnailer, the path passed for the output image file is in + * <filename><envar>$XDG_CACHE_HOME</envar>/thumbnails/ + * <replaceable>$SIZE</replaceable>/</filename>. The cached image file is given + * a (probably) unique filename, generated by hashing the original file’s URI, + * so the thumbnail can be looked up in future. #GnomeDesktopThumbnailFactory + * supports two sizes of thumbnails: %GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL and + * %GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE. Normal thumbnails are up to 128×128 + * pixels, whereas large thumbnails are up to 256×256 pixels. Thumbnails which + * are larger than this are scaled down before being cached, and non-square + * thumbnails are scaled so their largest dimension is at most 128 or 256 + * pixels. + * + * #GnomeDesktopThumbnailFactory also handles failed thumbnails. If a + * thumbnailer can’t generate a thumbnail for a file (e.g. because the file is + * corrupt or because the right video codecs aren’t available), it returns a + * non-zero exit status. The thumbnail factory then writes an entry to + * <filename><envar>$XDG_CACHE_HOME</envar>/thumbnails/fail/ + * gnome-thumbnail-factory/</filename> which is named after the hash of the + * input file URI (just like a successful cached thumbnail). For future queries + * for thumbnails for that file, #GnomeDesktopThumbnailFactory can immediately + * return an error after looking up the fail entry. + * + * If a file changes content, #GnomeDesktopThumbnailFactory will generate a new + * thumbnail because each cached image has associated metadata (stored as PNG + * tEXt keys) storing the full URI of the thumbnailed file (to check for hash + * collisions) and its last modification time at the point of thumbnailing. If + * the stored modification time doesn’t match the file’s current one, a new + * thumbnail is generated. + * + * Since: 2.2 + */ + +#include <config.h> + +#include <glib.h> +#include <glib/gstdio.h> +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <string.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <fcntl.h> + +#define GNOME_DESKTOP_USE_UNSTABLE_API +#include "gnome-desktop-thumbnail.h" +#include "gnome-desktop-thumbnail-script.h" + +static void +thumbnailers_directory_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + GnomeDesktopThumbnailFactory *factory); + +struct _GnomeDesktopThumbnailFactoryPrivate { + GnomeDesktopThumbnailSize size; + + GMutex lock; + + GList *thumbnailers; + GHashTable *mime_types_map; + GList *monitors; + + GSettings *settings; + gboolean loaded : 1; + gboolean disabled : 1; + gchar **disabled_types; +}; + +static const char *appname = "gnome-thumbnail-factory"; + +G_DEFINE_TYPE (GnomeDesktopThumbnailFactory, + gnome_desktop_thumbnail_factory, + G_TYPE_OBJECT) +#define parent_class gnome_desktop_thumbnail_factory_parent_class + +#define GNOME_DESKTOP_THUMBNAIL_FACTORY_GET_PRIVATE(object) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((object), GNOME_DESKTOP_TYPE_THUMBNAIL_FACTORY, GnomeDesktopThumbnailFactoryPrivate)) + +#define THUMBNAILER_ENTRY_GROUP "Thumbnailer Entry" +#define THUMBNAILER_EXTENSION ".thumbnailer" + +typedef struct { + volatile gint ref_count; + + gchar *path; + + gchar *command; + gchar **mime_types; +} Thumbnailer; + +static Thumbnailer * +thumbnailer_ref (Thumbnailer *thumb) +{ + g_return_val_if_fail (thumb != NULL, NULL); + g_return_val_if_fail (thumb->ref_count > 0, NULL); + + g_atomic_int_inc (&thumb->ref_count); + return thumb; +} + +static void +thumbnailer_unref (Thumbnailer *thumb) +{ + g_return_if_fail (thumb != NULL); + g_return_if_fail (thumb->ref_count > 0); + + if (g_atomic_int_dec_and_test (&thumb->ref_count)) + { + g_free (thumb->path); + g_free (thumb->command); + g_strfreev (thumb->mime_types); + + g_slice_free (Thumbnailer, thumb); + } +} + +static Thumbnailer * +thumbnailer_load (Thumbnailer *thumb) +{ + GKeyFile *key_file; + GError *error = NULL; + + key_file = g_key_file_new (); + if (!g_key_file_load_from_file (key_file, thumb->path, 0, &error)) + { + g_warning ("Failed to load thumbnailer from \"%s\": %s\n", thumb->path, error->message); + g_error_free (error); + thumbnailer_unref (thumb); + g_key_file_free (key_file); + + return NULL; + } + + if (!g_key_file_has_group (key_file, THUMBNAILER_ENTRY_GROUP)) + { + g_warning ("Invalid thumbnailer: missing group \"%s\"\n", THUMBNAILER_ENTRY_GROUP); + thumbnailer_unref (thumb); + g_key_file_free (key_file); + + return NULL; + } + + thumb->command = g_key_file_get_string (key_file, THUMBNAILER_ENTRY_GROUP, "Exec", NULL); + if (!thumb->command) + { + g_warning ("Invalid thumbnailer: missing Exec key\n"); + thumbnailer_unref (thumb); + g_key_file_free (key_file); + + return NULL; + } + + thumb->mime_types = g_key_file_get_string_list (key_file, THUMBNAILER_ENTRY_GROUP, "MimeType", NULL, NULL); + if (!thumb->mime_types) + { + g_warning ("Invalid thumbnailer: missing MimeType key\n"); + thumbnailer_unref (thumb); + g_key_file_free (key_file); + + return NULL; + } + + g_key_file_free (key_file); + + return thumb; +} + +static Thumbnailer * +thumbnailer_reload (Thumbnailer *thumb) +{ + g_return_val_if_fail (thumb != NULL, NULL); + + g_free (thumb->command); + thumb->command = NULL; + g_strfreev (thumb->mime_types); + thumb->mime_types = NULL; + + return thumbnailer_load (thumb); +} + +static Thumbnailer * +thumbnailer_new (const gchar *path) +{ + Thumbnailer *thumb; + + thumb = g_slice_new0 (Thumbnailer); + thumb->ref_count = 1; + thumb->path = g_strdup (path); + + return thumbnailer_load (thumb); +} + +static gpointer +init_thumbnailers_dirs (gpointer data) +{ + const gchar * const *data_dirs; + GPtrArray *thumbs_dirs; + guint i; + + data_dirs = g_get_system_data_dirs (); + thumbs_dirs = g_ptr_array_new (); + + g_ptr_array_add (thumbs_dirs, g_build_filename (g_get_user_data_dir (), "thumbnailers", NULL)); + for (i = 0; data_dirs[i] != NULL; i++) + g_ptr_array_add (thumbs_dirs, g_build_filename (data_dirs[i], "thumbnailers", NULL)); + g_ptr_array_add (thumbs_dirs, NULL); + + return g_ptr_array_free (thumbs_dirs, FALSE); +} + +static const gchar * const * +get_thumbnailers_dirs (void) +{ + static GOnce once_init = G_ONCE_INIT; + return g_once (&once_init, init_thumbnailers_dirs, NULL); +} + +/* These should be called with the lock held */ +static void +gnome_desktop_thumbnail_factory_register_mime_types (GnomeDesktopThumbnailFactory *factory, + Thumbnailer *thumb) +{ + GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv; + gint i; + + for (i = 0; thumb->mime_types[i]; i++) + { + if (!g_hash_table_lookup (priv->mime_types_map, thumb->mime_types[i])) + g_hash_table_insert (priv->mime_types_map, + g_strdup (thumb->mime_types[i]), + thumbnailer_ref (thumb)); + } +} + +static void +gnome_desktop_thumbnail_factory_add_thumbnailer (GnomeDesktopThumbnailFactory *factory, + Thumbnailer *thumb) +{ + GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv; + + gnome_desktop_thumbnail_factory_register_mime_types (factory, thumb); + priv->thumbnailers = g_list_prepend (priv->thumbnailers, thumb); +} + +static gboolean +gnome_desktop_thumbnail_factory_is_disabled (GnomeDesktopThumbnailFactory *factory, + const gchar *mime_type) +{ + GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv; + guint i; + + if (priv->disabled) + return TRUE; + + if (!priv->disabled_types) + return FALSE; + + for (i = 0; priv->disabled_types[i]; i++) + { + if (g_strcmp0 (priv->disabled_types[i], mime_type) == 0) + return TRUE; + } + + return FALSE; +} + +static gboolean +remove_thumbnailer_from_mime_type_map (gchar *key, + Thumbnailer *value, + gchar *path) +{ + return (strcmp (value->path, path) == 0); +} + +static void +update_or_create_thumbnailer (GnomeDesktopThumbnailFactory *factory, + const gchar *path) +{ + GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv; + GList *l; + Thumbnailer *thumb; + gboolean found = FALSE; + + g_mutex_lock (&priv->lock); + + for (l = priv->thumbnailers; l && !found; l = g_list_next (l)) + { + thumb = (Thumbnailer *)l->data; + + if (strcmp (thumb->path, path) == 0) + { + found = TRUE; + + /* First remove the mime_types associated to this thumbnailer */ + g_hash_table_foreach_remove (priv->mime_types_map, + (GHRFunc)remove_thumbnailer_from_mime_type_map, + (gpointer)path); + if (!thumbnailer_reload (thumb)) + priv->thumbnailers = g_list_delete_link (priv->thumbnailers, l); + else + gnome_desktop_thumbnail_factory_register_mime_types (factory, thumb); + } + } + + if (!found) + { + thumb = thumbnailer_new (path); + if (thumb) + gnome_desktop_thumbnail_factory_add_thumbnailer (factory, thumb); + } + + g_mutex_unlock (&priv->lock); +} + +static void +remove_thumbnailer (GnomeDesktopThumbnailFactory *factory, + const gchar *path) +{ + GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv; + GList *l; + Thumbnailer *thumb; + + g_mutex_lock (&priv->lock); + + for (l = priv->thumbnailers; l; l = g_list_next (l)) + { + thumb = (Thumbnailer *)l->data; + + if (strcmp (thumb->path, path) == 0) + { + priv->thumbnailers = g_list_delete_link (priv->thumbnailers, l); + g_hash_table_foreach_remove (priv->mime_types_map, + (GHRFunc)remove_thumbnailer_from_mime_type_map, + (gpointer)path); + thumbnailer_unref (thumb); + + break; + } + } + + g_mutex_unlock (&priv->lock); +} + +static void +remove_thumbnailers_for_dir (GnomeDesktopThumbnailFactory *factory, + const gchar *thumbnailer_dir, + GFileMonitor *monitor) +{ + GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv; + GList *l; + Thumbnailer *thumb; + + g_mutex_lock (&priv->lock); + + /* Remove all the thumbnailers inside this @thumbnailer_dir. */ + for (l = priv->thumbnailers; l; l = g_list_next (l)) + { + thumb = (Thumbnailer *)l->data; + + if (g_str_has_prefix (thumb->path, thumbnailer_dir) == TRUE) + { + priv->thumbnailers = g_list_delete_link (priv->thumbnailers, l); + g_hash_table_foreach_remove (priv->mime_types_map, + (GHRFunc)remove_thumbnailer_from_mime_type_map, + (gpointer)thumb->path); + thumbnailer_unref (thumb); + + break; + } + } + + /* Remove the monitor for @thumbnailer_dir. */ + priv->monitors = g_list_remove (priv->monitors, monitor); + g_signal_handlers_disconnect_by_func (monitor, thumbnailers_directory_changed, factory); + + g_mutex_unlock (&priv->lock); +} + +static void +gnome_desktop_thumbnail_factory_load_thumbnailers_for_dir (GnomeDesktopThumbnailFactory *factory, + const gchar *path) +{ + GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv; + GDir *dir; + GFile *dir_file; + GFileMonitor *monitor; + const gchar *dirent; + + dir = g_dir_open (path, 0, NULL); + if (!dir) + return; + + /* Monitor dir */ + dir_file = g_file_new_for_path (path); + monitor = g_file_monitor_directory (dir_file, + G_FILE_MONITOR_NONE, + NULL, NULL); + if (monitor) + { + g_signal_connect (monitor, "changed", + G_CALLBACK (thumbnailers_directory_changed), + factory); + priv->monitors = g_list_prepend (priv->monitors, monitor); + } + g_object_unref (dir_file); + + while ((dirent = g_dir_read_name (dir))) + { + Thumbnailer *thumb; + gchar *filename; + + if (!g_str_has_suffix (dirent, THUMBNAILER_EXTENSION)) + continue; + + filename = g_build_filename (path, dirent, NULL); + thumb = thumbnailer_new (filename); + g_free (filename); + + if (thumb) + gnome_desktop_thumbnail_factory_add_thumbnailer (factory, thumb); + } + + g_dir_close (dir); +} + +static void +thumbnailers_directory_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + GnomeDesktopThumbnailFactory *factory) +{ + gchar *path; + + switch (event_type) + { + case G_FILE_MONITOR_EVENT_CREATED: + case G_FILE_MONITOR_EVENT_CHANGED: + case G_FILE_MONITOR_EVENT_DELETED: + path = g_file_get_path (file); + if (!g_str_has_suffix (path, THUMBNAILER_EXTENSION)) + { + g_free (path); + return; + } + + if (event_type == G_FILE_MONITOR_EVENT_DELETED) + remove_thumbnailer (factory, path); + else + update_or_create_thumbnailer (factory, path); + + g_free (path); + break; + case G_FILE_MONITOR_EVENT_UNMOUNTED: + case G_FILE_MONITOR_EVENT_MOVED: + path = g_file_get_path (file); + remove_thumbnailers_for_dir (factory, path, monitor); + + if (event_type == G_FILE_MONITOR_EVENT_MOVED) + gnome_desktop_thumbnail_factory_load_thumbnailers_for_dir (factory, path); + + g_free (path); + break; + case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: + case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED: + case G_FILE_MONITOR_EVENT_PRE_UNMOUNT: + case G_FILE_MONITOR_EVENT_RENAMED: + case G_FILE_MONITOR_EVENT_MOVED_IN: + case G_FILE_MONITOR_EVENT_MOVED_OUT: + default: + break; + } +} + +static void +gnome_desktop_thumbnail_factory_load_thumbnailers (GnomeDesktopThumbnailFactory *factory) +{ + GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv; + const gchar * const *dirs; + guint i; + + if (priv->loaded) + return; + + dirs = get_thumbnailers_dirs (); + for (i = 0; dirs[i]; i++) + { + gnome_desktop_thumbnail_factory_load_thumbnailers_for_dir (factory, dirs[i]); + } + + priv->loaded = TRUE; +} + +static void +external_thumbnailers_disabled_all_changed_cb (GSettings *settings, + const gchar *key, + GnomeDesktopThumbnailFactory *factory) +{ + GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv; + + g_mutex_lock (&priv->lock); + + priv->disabled = g_settings_get_boolean (priv->settings, "disable-all"); + if (priv->disabled) + { + g_strfreev (priv->disabled_types); + priv->disabled_types = NULL; + } + else + { + priv->disabled_types = g_settings_get_strv (priv->settings, "disable"); + gnome_desktop_thumbnail_factory_load_thumbnailers (factory); + } + + g_mutex_unlock (&priv->lock); +} + +static void +external_thumbnailers_disabled_changed_cb (GSettings *settings, + const gchar *key, + GnomeDesktopThumbnailFactory *factory) +{ + GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv; + + g_mutex_lock (&priv->lock); + + if (!priv->disabled) + { + g_strfreev (priv->disabled_types); + priv->disabled_types = g_settings_get_strv (priv->settings, "disable"); + } + + g_mutex_unlock (&priv->lock); +} + +static void +gnome_desktop_thumbnail_factory_init (GnomeDesktopThumbnailFactory *factory) +{ + GnomeDesktopThumbnailFactoryPrivate *priv; + + factory->priv = GNOME_DESKTOP_THUMBNAIL_FACTORY_GET_PRIVATE (factory); + + priv = factory->priv; + + priv->size = GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL; + + priv->mime_types_map = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify)g_free, + (GDestroyNotify)thumbnailer_unref); + + g_mutex_init (&priv->lock); + + priv->settings = g_settings_new ("org.gnome.desktop.thumbnailers"); + priv->disabled = g_settings_get_boolean (priv->settings, "disable-all"); + if (!priv->disabled) + priv->disabled_types = g_settings_get_strv (priv->settings, "disable"); + g_signal_connect (priv->settings, "changed::disable-all", + G_CALLBACK (external_thumbnailers_disabled_all_changed_cb), + factory); + g_signal_connect (priv->settings, "changed::disable", + G_CALLBACK (external_thumbnailers_disabled_changed_cb), + factory); + + if (!priv->disabled) + gnome_desktop_thumbnail_factory_load_thumbnailers (factory); +} + +static void +gnome_desktop_thumbnail_factory_finalize (GObject *object) +{ + GnomeDesktopThumbnailFactory *factory; + GnomeDesktopThumbnailFactoryPrivate *priv; + + factory = GNOME_DESKTOP_THUMBNAIL_FACTORY (object); + + priv = factory->priv; + + if (priv->thumbnailers) + { + g_list_free_full (priv->thumbnailers, (GDestroyNotify)thumbnailer_unref); + priv->thumbnailers = NULL; + } + + g_clear_pointer (&priv->mime_types_map, g_hash_table_destroy); + + if (priv->monitors) + { + g_list_free_full (priv->monitors, (GDestroyNotify)g_object_unref); + priv->monitors = NULL; + } + + g_mutex_clear (&priv->lock); + + g_clear_pointer (&priv->disabled_types, g_strfreev); + + if (priv->settings) + { + g_signal_handlers_disconnect_by_func (priv->settings, + external_thumbnailers_disabled_all_changed_cb, + factory); + g_signal_handlers_disconnect_by_func (priv->settings, + external_thumbnailers_disabled_changed_cb, + factory); + g_clear_object (&priv->settings); + } + + if (G_OBJECT_CLASS (parent_class)->finalize) + (* G_OBJECT_CLASS (parent_class)->finalize) (object); +} + +static void +gnome_desktop_thumbnail_factory_class_init (GnomeDesktopThumbnailFactoryClass *class) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (class); + + gobject_class->finalize = gnome_desktop_thumbnail_factory_finalize; + + g_type_class_add_private (class, sizeof (GnomeDesktopThumbnailFactoryPrivate)); +} + +/** + * gnome_desktop_thumbnail_factory_new: + * @size: The thumbnail size to use + * + * Creates a new #GnomeDesktopThumbnailFactory. + * + * This function must be called on the main thread and is non-blocking. + * + * Return value: a new #GnomeDesktopThumbnailFactory + * + * Since: 2.2 + **/ +GnomeDesktopThumbnailFactory * +gnome_desktop_thumbnail_factory_new (GnomeDesktopThumbnailSize size) +{ + GnomeDesktopThumbnailFactory *factory; + + factory = g_object_new (GNOME_DESKTOP_TYPE_THUMBNAIL_FACTORY, NULL); + + factory->priv->size = size; + + return factory; +} + +static char * +thumbnail_filename (const char *uri) +{ + GChecksum *checksum; + guint8 digest[16]; + gsize digest_len = sizeof (digest); + char *file; + + checksum = g_checksum_new (G_CHECKSUM_MD5); + g_checksum_update (checksum, (const guchar *) uri, strlen (uri)); + + g_checksum_get_digest (checksum, digest, &digest_len); + g_assert (digest_len == 16); + + file = g_strconcat (g_checksum_get_string (checksum), ".png", NULL); + + g_checksum_free (checksum); + + return file; +} + +static char * +thumbnail_path (const char *uri, + GnomeDesktopThumbnailSize size) +{ + char *path, *file; + + file = thumbnail_filename (uri); + path = g_build_filename (g_get_user_cache_dir (), + "thumbnails", + size == GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE ? "large" : "normal", + file, + NULL); + g_free (file); + return path; +} + +static char * +thumbnail_failed_path (const char *uri) +{ + char *path, *file; + + file = thumbnail_filename (uri); + /* XXX: appname is only used for failed thumbnails. Is this a mistake? */ + path = g_build_filename (g_get_user_cache_dir (), + "thumbnails", + "fail", + appname, + file, + NULL); + g_free (file); + return path; +} + +static char * +validate_thumbnail_path (char *path, + const char *uri, + time_t mtime, + GnomeDesktopThumbnailSize size) +{ + GdkPixbuf *pixbuf; + + pixbuf = gdk_pixbuf_new_from_file (path, NULL); + if (pixbuf == NULL || + !gnome_desktop_thumbnail_is_valid (pixbuf, uri, mtime)) { + g_free (path); + return NULL; + } + + g_clear_object (&pixbuf); + + return path; +} + +static char * +lookup_thumbnail_path (const char *uri, + time_t mtime, + GnomeDesktopThumbnailSize size) +{ + char *path = thumbnail_path (uri, size); + return validate_thumbnail_path (path, uri, mtime, size); +} + +static char * +lookup_failed_thumbnail_path (const char *uri, + time_t mtime, + GnomeDesktopThumbnailSize size) +{ + char *path = thumbnail_failed_path (uri); + return validate_thumbnail_path (path, uri, mtime, size); +} + +/** + * gnome_desktop_thumbnail_factory_lookup: + * @factory: a #GnomeDesktopThumbnailFactory + * @uri: the uri of a file + * @mtime: the mtime of the file + * + * Tries to locate an existing thumbnail for the file specified. + * + * Usage of this function is threadsafe and does blocking I/O. + * + * Return value: The absolute path of the thumbnail, or %NULL if none exist. + * + * Since: 2.2 + **/ +char * +gnome_desktop_thumbnail_factory_lookup (GnomeDesktopThumbnailFactory *factory, + const char *uri, + time_t mtime) +{ + GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv; + + g_return_val_if_fail (uri != NULL, NULL); + + return lookup_thumbnail_path (uri, mtime, priv->size); +} + +/** + * gnome_desktop_thumbnail_factory_has_valid_failed_thumbnail: + * @factory: a #GnomeDesktopThumbnailFactory + * @uri: the uri of a file + * @mtime: the mtime of the file + * + * Tries to locate an failed thumbnail for the file specified. Writing + * and looking for failed thumbnails is important to avoid to try to + * thumbnail e.g. broken images several times. + * + * Usage of this function is threadsafe and does blocking I/O. + * + * Return value: TRUE if there is a failed thumbnail for the file. + * + * Since: 2.2 + **/ +gboolean +gnome_desktop_thumbnail_factory_has_valid_failed_thumbnail (GnomeDesktopThumbnailFactory *factory, + const char *uri, + time_t mtime) +{ + char *path; + + g_return_val_if_fail (uri != NULL, FALSE); + + path = lookup_failed_thumbnail_path (uri, mtime, factory->priv->size); + if (path == NULL) + return FALSE; + + g_free (path); + + return TRUE; +} + +/** + * gnome_desktop_thumbnail_factory_can_thumbnail: + * @factory: a #GnomeDesktopThumbnailFactory + * @uri: the uri of a file + * @mime_type: the mime type of the file + * @mtime: the mtime of the file + * + * Returns TRUE if this GnomeDesktopThumbnailFactory can (at least try) to thumbnail + * this file. Thumbnails or files with failed thumbnails won't be thumbnailed. + * + * Usage of this function is threadsafe and does blocking I/O. + * + * Return value: TRUE if the file can be thumbnailed. + * + * Since: 2.2 + **/ +gboolean +gnome_desktop_thumbnail_factory_can_thumbnail (GnomeDesktopThumbnailFactory *factory, + const char *uri, + const char *mime_type, + time_t mtime) +{ + gboolean have_script = FALSE; + + /* Don't thumbnail thumbnails */ + if (uri && + strncmp (uri, "file:/", 6) == 0 && + strstr (uri, "/thumbnails/") != NULL) + return FALSE; + + if (!mime_type) + return FALSE; + + g_mutex_lock (&factory->priv->lock); + if (!gnome_desktop_thumbnail_factory_is_disabled (factory, mime_type)) + { + Thumbnailer *thumb; + + thumb = g_hash_table_lookup (factory->priv->mime_types_map, mime_type); + have_script = (thumb != NULL); + } + g_mutex_unlock (&factory->priv->lock); + + if (have_script) + { + return !gnome_desktop_thumbnail_factory_has_valid_failed_thumbnail (factory, + uri, + mtime); + } + + return FALSE; +} + +static GdkPixbuf * +get_preview_thumbnail (const char *uri, + int size) +{ + GdkPixbuf *pixbuf; + GFile *file; + GFileInfo *file_info; + GInputStream *input_stream; + GObject *object; + + g_return_val_if_fail (uri != NULL, NULL); + + input_stream = NULL; + + file = g_file_new_for_uri (uri); + + /* First see if we can get an input stream via preview::icon */ + file_info = g_file_query_info (file, + G_FILE_ATTRIBUTE_PREVIEW_ICON, + G_FILE_QUERY_INFO_NONE, + NULL, /* GCancellable */ + NULL); /* return location for GError */ + g_object_unref (file); + + if (file_info == NULL) + return NULL; + + object = g_file_info_get_attribute_object (file_info, + G_FILE_ATTRIBUTE_PREVIEW_ICON); + g_object_unref (file_info); + + if (!object) + return NULL; + if (!G_IS_LOADABLE_ICON (object)) { + g_object_unref (object); + return NULL; + } + + input_stream = g_loadable_icon_load (G_LOADABLE_ICON (object), + 0, /* size */ + NULL, /* return location for type */ + NULL, /* GCancellable */ + NULL); /* return location for GError */ + g_object_unref (object); + + if (!input_stream) + return NULL; + + pixbuf = gdk_pixbuf_new_from_stream_at_scale (input_stream, + size, size, + TRUE, NULL, NULL); + g_object_unref (input_stream); + + return pixbuf; +} + +static GdkPixbuf * +pixbuf_new_from_bytes (GBytes *bytes, + GError **error) +{ + g_autoptr(GdkPixbufLoader) loader = NULL; + + loader = gdk_pixbuf_loader_new_with_mime_type ("image/png", error); + if (!loader) + return NULL; + + if (!gdk_pixbuf_loader_write (loader, + g_bytes_get_data (bytes, NULL), + g_bytes_get_size (bytes), + error)) + { + return NULL; + } + + if (!gdk_pixbuf_loader_close (loader, error)) + return NULL; + + return g_object_ref (gdk_pixbuf_loader_get_pixbuf (loader)); +} + +/** + * gnome_desktop_thumbnail_factory_generate_thumbnail: + * @factory: a #GnomeDesktopThumbnailFactory + * @uri: the uri of a file + * @mime_type: the mime type of the file + * + * Tries to generate a thumbnail for the specified file. If it succeeds + * it returns a pixbuf that can be used as a thumbnail. + * + * Usage of this function is threadsafe and does blocking I/O. + * + * Return value: (transfer full): thumbnail pixbuf if thumbnailing succeeded, %NULL otherwise. + * + * Since: 2.2 + **/ +GdkPixbuf * +gnome_desktop_thumbnail_factory_generate_thumbnail (GnomeDesktopThumbnailFactory *factory, + const char *uri, + const char *mime_type) +{ + GdkPixbuf *pixbuf; + char *script; + int size; + + g_return_val_if_fail (uri != NULL, NULL); + g_return_val_if_fail (mime_type != NULL, NULL); + + /* Doesn't access any volatile fields in factory, so it's threadsafe */ + + size = 128; + if (factory->priv->size == GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE) + size = 256; + + pixbuf = get_preview_thumbnail (uri, size); + if (pixbuf != NULL) + return pixbuf; + + script = NULL; + g_mutex_lock (&factory->priv->lock); + if (!gnome_desktop_thumbnail_factory_is_disabled (factory, mime_type)) + { + Thumbnailer *thumb; + + thumb = g_hash_table_lookup (factory->priv->mime_types_map, mime_type); + if (thumb) + script = g_strdup (thumb->command); + } + g_mutex_unlock (&factory->priv->lock); + + if (script) + { + GBytes *data; + GError *error = NULL; + + data = gnome_desktop_thumbnail_script_exec (script, size, uri, &error); + if (data) + { + pixbuf = pixbuf_new_from_bytes (data, &error); + if (!pixbuf) + { + g_debug ("Could not load thumbnail pixbuf: %s", error->message); + g_error_free (error); + } + g_bytes_unref (data); + } + else + { + g_debug ("Thumbnail script ('%s') failed for '%s': %s", + script, uri, error ? error->message : "no details"); + g_clear_error (&error); + } + } + else + { + g_debug ("Could not find thumbnailer for mime-type '%s'", + mime_type); + } + + g_free (script); + + return pixbuf; +} + +static gboolean +save_thumbnail (GdkPixbuf *pixbuf, + char *path, + const char *uri, + time_t mtime) +{ + char *dirname; + char *tmp_path = NULL; + int tmp_fd; + char mtime_str[21]; + gboolean ret = FALSE; + GError *error = NULL; + const char *width, *height; + + if (pixbuf == NULL) + return FALSE; + + dirname = g_path_get_dirname (path); + + if (g_mkdir_with_parents (dirname, 0700) != 0) + goto out; + + tmp_path = g_strconcat (path, ".XXXXXX", NULL); + tmp_fd = g_mkstemp (tmp_path); + + if (tmp_fd == -1) + goto out; + close (tmp_fd); + + g_snprintf (mtime_str, 21, "%" G_GINT64_FORMAT, (gint64) mtime); + width = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::Image::Width"); + height = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::Image::Height"); + + error = NULL; + if (width != NULL && height != NULL) + ret = gdk_pixbuf_save (pixbuf, + tmp_path, + "png", &error, + "tEXt::Thumb::Image::Width", width, + "tEXt::Thumb::Image::Height", height, + "tEXt::Thumb::URI", uri, + "tEXt::Thumb::MTime", mtime_str, + "tEXt::Software", "GNOME::ThumbnailFactory", + NULL); + else + ret = gdk_pixbuf_save (pixbuf, + tmp_path, + "png", &error, + "tEXt::Thumb::URI", uri, + "tEXt::Thumb::MTime", mtime_str, + "tEXt::Software", "GNOME::ThumbnailFactory", + NULL); + + if (!ret) + goto out; + + g_chmod (tmp_path, 0600); + g_rename (tmp_path, path); + + out: + if (error != NULL) + { + g_warning ("Failed to create thumbnail %s: %s", tmp_path, error->message); + g_error_free (error); + } + g_unlink (tmp_path); + g_free (tmp_path); + g_free (dirname); + return ret; +} + +static GdkPixbuf * +make_failed_thumbnail (void) +{ + GdkPixbuf *pixbuf; + + pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, 1, 1); + gdk_pixbuf_fill (pixbuf, 0x00000000); + return pixbuf; +} + +/** + * gnome_desktop_thumbnail_factory_save_thumbnail: + * @factory: a #GnomeDesktopThumbnailFactory + * @thumbnail: the thumbnail as a pixbuf + * @uri: the uri of a file + * @original_mtime: the modification time of the original file + * + * Saves @thumbnail at the right place. If the save fails a + * failed thumbnail is written. + * + * Usage of this function is threadsafe and does blocking I/O. + * + * Since: 2.2 + **/ +void +gnome_desktop_thumbnail_factory_save_thumbnail (GnomeDesktopThumbnailFactory *factory, + GdkPixbuf *thumbnail, + const char *uri, + time_t original_mtime) +{ + char *path; + + path = thumbnail_path (uri, factory->priv->size); + if (!save_thumbnail (thumbnail, path, uri, original_mtime)) + { + thumbnail = make_failed_thumbnail (); + g_free (path); + path = thumbnail_failed_path (uri); + save_thumbnail (thumbnail, path, uri, original_mtime); + g_object_unref (thumbnail); + } + g_free (path); +} + +/** + * gnome_desktop_thumbnail_factory_create_failed_thumbnail: + * @factory: a #GnomeDesktopThumbnailFactory + * @uri: the uri of a file + * @mtime: the modification time of the file + * + * Creates a failed thumbnail for the file so that we don't try + * to re-thumbnail the file later. + * + * Usage of this function is threadsafe and does blocking I/O. + * + * Since: 2.2 + **/ +void +gnome_desktop_thumbnail_factory_create_failed_thumbnail (GnomeDesktopThumbnailFactory *factory, + const char *uri, + time_t mtime) +{ + char *path; + GdkPixbuf *pixbuf; + + path = thumbnail_failed_path (uri); + pixbuf = make_failed_thumbnail (); + save_thumbnail (pixbuf, path, uri, mtime); + g_free (path); + g_object_unref (pixbuf); +} + +/** + * gnome_desktop_thumbnail_path_for_uri: + * @uri: an uri + * @size: a thumbnail size + * + * Returns the filename that a thumbnail of size @size for @uri would have. + * This function is threadsafe and does no blocking I/O. + * + * Return value: an absolute filename + * + * Since: 2.2 + **/ +char * +gnome_desktop_thumbnail_path_for_uri (const char *uri, + GnomeDesktopThumbnailSize size) +{ + return thumbnail_path (uri, size); +} + +/** + * gnome_desktop_thumbnail_is_valid: + * @pixbuf: an loaded thumbnail #GdkPixbuf + * @uri: a uri + * @mtime: the mtime + * + * Returns whether the thumbnail has the correct uri and mtime embedded in the + * png options. This function is threadsafe and does no blocking I/O. + * + * Return value: TRUE if the thumbnail has the right @uri and @mtime + * + * Since: 2.2 + **/ +gboolean +gnome_desktop_thumbnail_is_valid (GdkPixbuf *pixbuf, + const char *uri, + time_t mtime) +{ + const char *thumb_uri, *thumb_mtime_str; + time_t thumb_mtime; + + thumb_uri = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::URI"); + if (g_strcmp0 (uri, thumb_uri) != 0) + return FALSE; + + thumb_mtime_str = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::MTime"); + if (!thumb_mtime_str) + return FALSE; + thumb_mtime = atol (thumb_mtime_str); + if (mtime != thumb_mtime) + return FALSE; + + return TRUE; +} diff --git a/src/gnome-desktop/gnome-desktop-thumbnail.h b/src/gnome-desktop/gnome-desktop-thumbnail.h new file mode 100644 index 000000000..186534ff3 --- /dev/null +++ b/src/gnome-desktop/gnome-desktop-thumbnail.h @@ -0,0 +1,102 @@ +/* + * gnome-thumbnail.h: Utilities for handling thumbnails + * + * Copyright (C) 2002 Red Hat, Inc. + * + * This file is part of the Gnome Library. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef GNOME_DESKTOP_THUMBNAIL_H +#define GNOME_DESKTOP_THUMBNAIL_H + +#ifndef GNOME_DESKTOP_USE_UNSTABLE_API +#error GnomeDesktopThumbnail is unstable API. You must define GNOME_DESKTOP_USE_UNSTABLE_API before including gnome-desktop-thumbnail.h +#endif + +#include <glib.h> +#include <glib-object.h> +#include <time.h> +#include <gdk-pixbuf/gdk-pixbuf.h> + +G_BEGIN_DECLS + +typedef enum { + GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL, + GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE +} GnomeDesktopThumbnailSize; + +#define GNOME_DESKTOP_TYPE_THUMBNAIL_FACTORY (gnome_desktop_thumbnail_factory_get_type ()) +#define GNOME_DESKTOP_THUMBNAIL_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNOME_DESKTOP_TYPE_THUMBNAIL_FACTORY, GnomeDesktopThumbnailFactory)) +#define GNOME_DESKTOP_THUMBNAIL_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GNOME_DESKTOP_TYPE_THUMBNAIL_FACTORY, GnomeDesktopThumbnailFactoryClass)) +#define GNOME_DESKTOP_IS_THUMBNAIL_FACTORY(obj) (G_TYPE_INSTANCE_CHECK_TYPE ((obj), GNOME_DESKTOP_TYPE_THUMBNAIL_FACTORY)) +#define GNOME_DESKTOP_IS_THUMBNAIL_FACTORY_CLASS(klass) (G_TYPE_CLASS_CHECK_CLASS_TYPE ((klass), GNOME_DESKTOP_TYPE_THUMBNAIL_FACTORY)) + +typedef struct _GnomeDesktopThumbnailFactory GnomeDesktopThumbnailFactory; +typedef struct _GnomeDesktopThumbnailFactoryClass GnomeDesktopThumbnailFactoryClass; +typedef struct _GnomeDesktopThumbnailFactoryPrivate GnomeDesktopThumbnailFactoryPrivate; + +struct _GnomeDesktopThumbnailFactory { + GObject parent; + + GnomeDesktopThumbnailFactoryPrivate *priv; +}; + +struct _GnomeDesktopThumbnailFactoryClass { + GObjectClass parent; +}; + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(GnomeDesktopThumbnailFactory, g_object_unref) + +GType gnome_desktop_thumbnail_factory_get_type (void); +GnomeDesktopThumbnailFactory *gnome_desktop_thumbnail_factory_new (GnomeDesktopThumbnailSize size); + +char * gnome_desktop_thumbnail_factory_lookup (GnomeDesktopThumbnailFactory *factory, + const char *uri, + time_t mtime); + +gboolean gnome_desktop_thumbnail_factory_has_valid_failed_thumbnail (GnomeDesktopThumbnailFactory *factory, + const char *uri, + time_t mtime); +gboolean gnome_desktop_thumbnail_factory_can_thumbnail (GnomeDesktopThumbnailFactory *factory, + const char *uri, + const char *mime_type, + time_t mtime); +GdkPixbuf * gnome_desktop_thumbnail_factory_generate_thumbnail (GnomeDesktopThumbnailFactory *factory, + const char *uri, + const char *mime_type); +void gnome_desktop_thumbnail_factory_save_thumbnail (GnomeDesktopThumbnailFactory *factory, + GdkPixbuf *thumbnail, + const char *uri, + time_t original_mtime); +void gnome_desktop_thumbnail_factory_create_failed_thumbnail (GnomeDesktopThumbnailFactory *factory, + const char *uri, + time_t mtime); + + +/* Thumbnailing utils: */ +gboolean gnome_desktop_thumbnail_is_valid (GdkPixbuf *pixbuf, + const char *uri, + time_t mtime); +char * gnome_desktop_thumbnail_path_for_uri (const char *uri, + GnomeDesktopThumbnailSize size); + +G_END_DECLS + +#endif /* GNOME_DESKTOP_THUMBNAIL_H */ diff --git a/src/meson.build b/src/meson.build index a947a05f2..3f7ae077d 100644 --- a/src/meson.build +++ b/src/meson.build @@ -54,6 +54,10 @@ libnautilus_sources = [ 'animation/ide-box-theatric.h', 'animation/ide-cairo.c', 'animation/ide-cairo.h', + 'gnome-desktop/gnome-desktop-thumbnail.c', + 'gnome-desktop/gnome-desktop-thumbnail.h', + 'gnome-desktop/gnome-desktop-thumbnail-script.c', + 'gnome-desktop/gnome-desktop-thumbnail-script.h', 'gtk/nautilusgtkplacesview.c', 'gtk/nautilusgtkplacesviewprivate.h', 'gtk/nautilusgtkplacesviewrow.c', @@ -271,9 +275,10 @@ nautilus_deps = [ gio_unix, gmodule, gnome_autoar, - gnome_desktop, + gsettings_desktop_schemas, libgd_dep, nautilus_extension, + seccomp, selinux, tracker_sparql, xml diff --git a/src/nautilus-properties-window.c b/src/nautilus-properties-window.c index d211873e4..d239263e2 100644 --- a/src/nautilus-properties-window.c +++ b/src/nautilus-properties-window.c @@ -35,7 +35,7 @@ #include <sys/stat.h> #define GNOME_DESKTOP_USE_UNSTABLE_API -#include <libgnome-desktop/gnome-desktop-thumbnail.h> +#include "gnome-desktop/gnome-desktop-thumbnail.h" #include "nautilus-enums.h" #include "nautilus-error-reporting.h" diff --git a/src/nautilus-thumbnails.c b/src/nautilus-thumbnails.c index 991bfb6f6..b90dc0daa 100644 --- a/src/nautilus-thumbnails.c +++ b/src/nautilus-thumbnails.c @@ -40,7 +40,7 @@ #include <sys/wait.h> #include <unistd.h> #include <signal.h> -#include <libgnome-desktop/gnome-desktop-thumbnail.h> +#include "gnome-desktop/gnome-desktop-thumbnail.h" #define DEBUG_FLAG NAUTILUS_DEBUG_THUMBNAILS #include "nautilus-debug.h" |