Skip to content

Instantly share code, notes, and snippets.

@Dudemanguy
Forked from ahodesuka/glib-thumbnailer.patch
Last active June 29, 2024 20:37
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
@raphial
Copy link

raphial commented Jan 31, 2022

Could you explain how to use/apply this patch? Thanks!

@Dudemanguy
Copy link
Author

You need to check out the glib2 source code, apply this patch, and then compile it yourself.

@asabatep
Copy link

fyi this fails to apply on glib past commit e7a68531 (which has been cherry-picked by ubuntu kinetic and debian sid)

@Dudemanguy
Copy link
Author

Finally got around to updating it for glib 2.74.1. Should be fine now.

@Kyuunex
Copy link

Kyuunex commented Feb 22, 2024

i should probably bring this up in here https://bbs.archlinux.org/viewtopic.php?pid=2152437#p2152437

@Dudemanguy
Copy link
Author

I have no idea why this would affect portals.

@Kyuunex
Copy link

Kyuunex commented Feb 22, 2024

Me neither

@luebking
Copy link

Just a hunch:

+ g_main_context_push_thread_default (thread_context);
…
+  if (!result || error)
+    {
+      g_warning ("generate_thumbnail (): g_dbus_proxy_call_sync() failed: %s", error->message);
+      return FALSE;
+    }
…
+  g_main_context_pop_thread_default (thread_context);

If g_dbus_proxy_call_sync fails it leaves quite some stuff behind, incl. a thread on some stack (I guess, I've no real experience w/ gtk code at all)
Some of the unrefs should™ probably be in the early exit as well?

@Dudemanguy
Copy link
Author

While that should probably be cleaned up, that should just be a memory leak at the worst. I think the problem is likely that it is stuck running the main loop because the callback from the signal isn't working as expected for some reason.

@luebking
Copy link

thread_context never gets destroyed and remains the default context because it's also no popped.
If the main loop was stuck or waiting for a dbus timeout, I'd understood a delay, but not the interference w/ xdg-desktop-portal.

Should be easy enough for Kyuunex to test, though.

@Kyuunex
Copy link

Kyuunex commented Feb 23, 2024

give the updated patch and i can test

@luebking
Copy link

In gio/glocalfileinfo.c, copy

+  g_object_unref (proxy);
+  g_object_unref (connection);
+  g_main_loop_unref (state.mainloop);
+  g_main_context_pop_thread_default (thread_context);
+  g_main_context_unref (thread_context);

into the if (!result || error)block

@Dudemanguy
Copy link
Author

If the main loop was stuck or waiting for a dbus timeout, I'd understood a delay

I thought that was exactly the issue though?

@Kyuunex: If you need it, try this. I'll clean it up later but I don't think it will solve your issue (wouldn't mind being wrong though).

@Kyuunex
Copy link

Kyuunex commented Feb 23, 2024

i tried lue's solution and was unable to build. i'll test later

@luebking
Copy link

There's a delay but

  1. afaiu the client stll starts
  2. what's more weird is that xdg-desktop-portal doesn't event attempt to launch any implementation w/ the patch in place.

And the failing xdg-desktop-portal backend alone can explain the delay.

@Dudemanguy
Copy link
Author

Good news, I possibly may have fixed it and bumped the AUR package with the update. Well I don't use portals so I can't say for sure but there was a very longstanding problem with this patch that I finally solved. Also thanks to @luebking for pointing out the incomplete cleanup.

@Kyuunex
Copy link

Kyuunex commented Feb 24, 2024

testing

@Kyuunex
Copy link

Kyuunex commented Feb 24, 2024

On a clean rebuild of the AUR package glib2-patched-thumbnailer, I do not have major issues with xdg-portals starting anymore.
One noticeable thing is there is a small delay (1-2 seconds) with the first app that starts that makes uses of portals, in my case, waybar.

Thanks for fixing the issue!

@Dudemanguy
Copy link
Author

Hmm still not really sure how this even affects portals but not being stuck in an infinite loop is definitely an improvement.

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