Skip to content

Instantly share code, notes, and snippets.

@Dudemanguy
Forked from ahodesuka/glib-thumbnailer.patch
Last active April 26, 2025 03:53
Show Gist options
  • Save Dudemanguy/d199759b46a79782cc1b301649dec8a5 to your computer and use it in GitHub Desktop.
Save Dudemanguy/d199759b46a79782cc1b301649dec8a5 to your computer and use it in GitHub Desktop.
Sends a dbus signal for generating thumbnails.
From 01ce72fb8086fb6984e4409739a821408045612e Mon Sep 17 00:00:00 2001
From: Dudemanguy <[email protected]>
Date: Sat, 24 Feb 2024 15:07:31 -0600
Subject: [PATCH] glocalfileinfo: add a dbus thumbnail generator
---
gio/glocalfileinfo.c | 186 +++++++++++++++++++++++++++++++++++++++++--
1 file changed, 179 insertions(+), 7 deletions(-)
diff --git a/gio/glocalfileinfo.c b/gio/glocalfileinfo.c
index 4f51427f5..5e1698da0 100644
--- a/gio/glocalfileinfo.c
+++ b/gio/glocalfileinfo.c
@@ -68,6 +68,12 @@
#include "glib-private.h"
#include "thumbnail-verify.h"
+#ifdef HAVE_DBUS1
+#define FREEDESKTOP_THUMBNAILER
+#include <gio/gio.h>
+#include <gio/gdbusproxy.h>
+#include <glib-object.h>
+#endif /* HAVE_DBUS1 */
#ifdef G_OS_WIN32
#include <windows.h>
@@ -109,6 +115,16 @@ struct ThumbMD5Context {
unsigned char in[64];
};
+#ifdef FREEDESKTOP_THUMBNAILER
+typedef struct
+{
+ GMainLoop *mainloop;
+ guint32 handle;
+ gboolean success;
+ const char *error_message;
+} ThumbnailerState;
+#endif /* FREEDESKTOP_THUMBNAILER */
+
#ifndef G_OS_WIN32
typedef struct {
@@ -1419,18 +1435,137 @@ get_thumbnail_dirname_from_size (ThumbnailSize size)
g_return_val_if_reached (NULL);
}
+#ifdef FREEDESKTOP_THUMBNAILER
+static void
+thumbnailer_signal_cb (GDBusProxy *proxy,
+ gchar *sender_name,
+ gchar *signal_name,
+ GVariant *parameters,
+ gpointer thumbnailer_state)
+{
+ ThumbnailerState *state = (ThumbnailerState *) thumbnailer_state;
+ guint32 signal_handle;
+ const gchar **uris;
+
+
+ if (g_strcmp0 (signal_name, "Error") == 0)
+ {
+ g_variant_get (parameters, "(uasis)", &signal_handle, NULL, NULL, &state->error_message);
+ state->success = FALSE;
+ }
+ else if (g_strcmp0 (signal_name, "Ready") == 0)
+ {
+ g_variant_get (parameters, "(u^as)", &signal_handle, &uris);
+ state->success = TRUE;
+ }
+ else if (g_strcmp0 (signal_name, "Finished") == 0)
+ {
+ g_main_loop_quit (state->mainloop);
+ }
+
+}
+
+static gboolean
+generate_thumbnail(const char *uri, const char *mime_type)
+{
+ GMainContext *thread_context;
+ GDBusConnection *connection;
+ GDBusProxy *proxy;
+ GVariant *result = NULL;
+ GError *error = NULL;
+ ThumbnailerState state;
+ const gchar *uris[2] = { uri, NULL };
+ const gchar *mime_types[2] = { mime_type, NULL };
+
+ thread_context = g_main_context_new ();
+ state.mainloop = g_main_loop_new (thread_context, FALSE);
+ state.success = FALSE;
+
+ connection = g_dbus_connection_new_for_address_sync (
+ g_dbus_address_get_for_bus_sync (G_BUS_TYPE_SESSION, NULL, NULL),
+ G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION | G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
+ NULL,
+ NULL,
+ &error);
+
+ g_main_context_push_thread_default (thread_context);
+ proxy = g_dbus_proxy_new_sync (connection,
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
+ NULL,
+ "org.freedesktop.thumbnails.Thumbnailer1",
+ "/org/freedesktop/thumbnails/Thumbnailer1",
+ "org.freedesktop.thumbnails.Thumbnailer1",
+ NULL, /* TODO: cancellable */
+ &error);
+ if (!proxy)
+ {
+ g_warning ("generate_thumbnail (): g_dbus_proxy_new_sync failed");
+ goto done;
+ }
+ else
+ g_debug("generate_thumbnail (): connected to D-Bus");
+
+ g_signal_connect (G_OBJECT (proxy), "g-signal",
+ G_CALLBACK (thumbnailer_signal_cb), &state);
+
+ result = g_dbus_proxy_call_sync (proxy,
+ "Queue",
+ g_variant_new("(^as^asssu)",
+ uris,
+ mime_types,
+ "normal",
+ "default",
+ 0),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ if (!result || error)
+ {
+ g_warning ("generate_thumbnail (): g_dbus_proxy_call_sync() failed: %s", error->message);
+ goto done;
+ }
+ g_variant_get (result, "(u)", &(state.handle));
+ g_variant_unref (result);
+ // block until the loop is terminated in thumbnailer_signal_cb ()
+ g_main_loop_run (state.mainloop);
+
+done:
+ if (proxy)
+ g_object_unref (proxy);
+
+ if (connection)
+ g_object_unref (connection);
+
+ g_main_loop_unref (state.mainloop);
+ g_main_context_pop_thread_default (thread_context);
+ g_main_context_unref (thread_context);
+
+ if (state.success)
+ {
+ g_debug ("generate_thumbnail (): Thumbnail generated for %s", uris[0]);
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+#endif /* FREEDESKTOP_THUMBNAILER */
+
+
/* @stat_buf is the pre-calculated result of stat(path), or %NULL if that failed. */
static void
get_thumbnail_attributes (const char *path,
GFileInfo *info,
const GLocalFileStat *stat_buf,
- ThumbnailSize size)
+ ThumbnailSize size,
+ gboolean generate)
{
GChecksum *checksum;
const char *dirname;
char *uri;
char *filename = NULL;
char *basename;
+ const char *content_type;
guint32 failed_attr_id;
guint32 is_valid_attr_id;
guint32 path_attr_id;
@@ -1507,6 +1642,7 @@ get_thumbnail_attributes (const char *path,
_g_file_info_set_attribute_byte_string_by_id (info, path_attr_id, filename);
_g_file_info_set_attribute_boolean_by_id (info, is_valid_attr_id,
thumbnail_verify (filename, uri, stat_buf));
+ generate = FALSE;
}
else
{
@@ -1521,9 +1657,31 @@ get_thumbnail_attributes (const char *path,
_g_file_info_set_attribute_boolean_by_id (info, failed_attr_id, TRUE);
_g_file_info_set_attribute_boolean_by_id (info, is_valid_attr_id,
thumbnail_verify (filename, uri, stat_buf));
+ generate = FALSE;
}
}
+ if (generate)
+ {
+#ifdef FREEDESKTOP_THUMBNAILER
+ content_type = g_file_info_get_content_type (info);
+ if (content_type)
+ {
+ g_debug ("invoking Freedesktop Thumbnailer for %s (%s)", uri, content_type);
+ if (generate_thumbnail (uri, content_type))
+ {
+ /* Now that the thumbnail is generated, find it. */
+ get_thumbnail_attributes (path, info, stat_buf, size, FALSE);
+ }
+ /*else
+ {
+ _g_file_info_set_attribute_boolean_by_id (info, G_FILE_ATTRIBUTE_ID_THUMBNAILING_FAILED, TRUE);
+ _g_file_info_set_attribute_boolean_by_id (info, G_FILE_ATTRIBUTE_ID_THUMBNAIL_IS_VALID, FALSE);
+ }*/
+ }
+#endif /* FREEDESKTOP_THUMBNAILER */
+ }
+
g_free (basename);
g_free (filename);
g_free (uri);
@@ -1914,6 +2072,18 @@ _g_local_file_info_get (const char *basename,
info = g_file_info_new ();
+ /* Thumbnail generation requires a content-type.
+ * TODO: implement g_file_attribute_matcher_add () in gfileinfo.c */
+ if (_g_file_attribute_matcher_matches_id (attribute_matcher, G_FILE_ATTRIBUTE_ID_THUMBNAIL_PATH)
+ && !_g_file_attribute_matcher_matches_id (attribute_matcher, G_FILE_ATTRIBUTE_ID_STANDARD_CONTENT_TYPE))
+ {
+ char *attributes = g_file_attribute_matcher_to_string (attribute_matcher);
+ char *_attributes = g_strdup_printf ("%s,standard::content-type", attributes);
+ attribute_matcher = g_file_attribute_matcher_new (_attributes);
+ g_free (attributes);
+ g_free (_attributes);
+ }
+
/* Make sure we don't set any unwanted attributes */
g_file_info_set_attribute_mask (info, attribute_matcher);
@@ -2049,7 +2219,9 @@ _g_local_file_info_get (const char *basename,
_g_file_attribute_matcher_matches_id (attribute_matcher,
G_FILE_ATTRIBUTE_ID_STANDARD_ICON) ||
_g_file_attribute_matcher_matches_id (attribute_matcher,
- G_FILE_ATTRIBUTE_ID_STANDARD_SYMBOLIC_ICON))
+ G_FILE_ATTRIBUTE_ID_STANDARD_SYMBOLIC_ICON) ||
+ _g_file_attribute_matcher_matches_id (attribute_matcher,
+ G_FILE_ATTRIBUTE_ID_THUMBNAIL_PATH))
{
char *content_type = get_content_type (basename, path, stat_ok ? &statbuf : NULL, is_symlink, symlink_broken, flags, FALSE);
@@ -2165,7 +2337,7 @@ _g_local_file_info_get (const char *basename,
_g_file_attribute_matcher_matches_id (attribute_matcher,
G_FILE_ATTRIBUTE_ID_THUMBNAILING_FAILED))
{
- get_thumbnail_attributes (path, info, stat_ok ? &statbuf : NULL, THUMBNAIL_SIZE_AUTO);
+ get_thumbnail_attributes (path, info, stat_ok ? &statbuf : NULL, THUMBNAIL_SIZE_AUTO, TRUE);
}
if (_g_file_attribute_matcher_matches_id (attribute_matcher,
@@ -2175,7 +2347,7 @@ _g_local_file_info_get (const char *basename,
_g_file_attribute_matcher_matches_id (attribute_matcher,
G_FILE_ATTRIBUTE_ID_THUMBNAILING_FAILED_NORMAL))
{
- get_thumbnail_attributes (path, info, stat_ok ? &statbuf : NULL, THUMBNAIL_SIZE_NORMAL);
+ get_thumbnail_attributes (path, info, stat_ok ? &statbuf : NULL, THUMBNAIL_SIZE_NORMAL, TRUE);
}
if (_g_file_attribute_matcher_matches_id (attribute_matcher,
@@ -2185,7 +2357,7 @@ _g_local_file_info_get (const char *basename,
_g_file_attribute_matcher_matches_id (attribute_matcher,
G_FILE_ATTRIBUTE_ID_THUMBNAILING_FAILED_LARGE))
{
- get_thumbnail_attributes (path, info, stat_ok ? &statbuf : NULL, THUMBNAIL_SIZE_LARGE);
+ get_thumbnail_attributes (path, info, stat_ok ? &statbuf : NULL, THUMBNAIL_SIZE_LARGE, TRUE);
}
if (_g_file_attribute_matcher_matches_id (attribute_matcher,
@@ -2195,7 +2367,7 @@ _g_local_file_info_get (const char *basename,
_g_file_attribute_matcher_matches_id (attribute_matcher,
G_FILE_ATTRIBUTE_ID_THUMBNAILING_FAILED_XLARGE))
{
- get_thumbnail_attributes (path, info, stat_ok ? &statbuf : NULL, THUMBNAIL_SIZE_XLARGE);
+ get_thumbnail_attributes (path, info, stat_ok ? &statbuf : NULL, THUMBNAIL_SIZE_XLARGE, TRUE);
}
if (_g_file_attribute_matcher_matches_id (attribute_matcher,
@@ -2205,7 +2377,7 @@ _g_local_file_info_get (const char *basename,
_g_file_attribute_matcher_matches_id (attribute_matcher,
G_FILE_ATTRIBUTE_ID_THUMBNAILING_FAILED_XXLARGE))
{
- get_thumbnail_attributes (path, info, stat_ok ? &statbuf : NULL, THUMBNAIL_SIZE_XXLARGE);
+ get_thumbnail_attributes (path, info, stat_ok ? &statbuf : NULL, THUMBNAIL_SIZE_XXLARGE, TRUE);
}
vfs = g_vfs_get_default ();
--
2.43.2
@luebking
Copy link

To address this, the glib-thumbnailer patch sends a signal to dbus to generate thumbnails while the filechooser dialog is open. Note that in some applications, the thumbnail generation does not work when opening the dialog due to some mysterious dbus error (chromium is known to fail).

see dbus-monitor and the system journal,
typical dbus problems involve a degenerated session and broken session bus (nb. that I more or less randomly stumbled into this gist from the bbs) and the chromium comment in that repo sounds like xdg-desktop-portal (again) so try eg. zenity.

@titan-speakerman
Copy link

testing with the file picker from firefox (zenity uses gtk4 so I couldn't use it for the testing) and looking at the output of dbus-monitor, navigating in the file picker doesn't seem to be generating any signals related to thumbnailing, just an array of bytes to ca.desrt.dconf when I close the dialog. Is it possible that HAVE_DBUS1 could be falsely not defined for some reason so none of the code from the patch is even running in the first place?

@luebking
Copy link

luebking commented Apr 23, 2025

+#ifdef HAVE_DBUS1
+#warning HAVE_DBUS

+  if (!result || error)
+    {
+      g_warning ("generate_thumbnail (): g_dbus_proxy_call_sync() failed: %s", error->message);
+      goto done;
+    }
+ else
+     g_warning ("generate_thumbnail (): g_dbus_proxy_call_sync() SUCCESS");

You could use gtk3-demo-application as testcase then

@titan-speakerman
Copy link

I was right, even after adding that line you suggested I'm not getting the HAVE_DBUS compiler warning. I have dbus-glib installed, do you have any idea why that macro wouldn't be defined?

@luebking
Copy link

This is in config.h which gets defined by AM/CMake (whatever you're using to build glib2), depending on your distro you'll need some *-dev pacakge(s)

@titan-speakerman
Copy link

Thanks, I got it to work.

For anyone else struggling to get this patch to work in Portage, or using the meson build system for some other reason, just add this patch as well and it should work:

--- a/glib/glibconfig.h.in      2025-02-20 08:08:16.000000000 -0500
+++ b/glib/glibconfig.h.in      2025-04-25 22:58:07.841968626 -0400
@@ -60,6 +60,7 @@ 
 #define G_GINT32_FORMAT @gint32_format@
 #define G_GUINT32_FORMAT @guint32_format@
 
+#define HAVE_DBUS1 1
 
 #define G_HAVE_GINT64 1          /* deprecated, always true */ 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment