From e4ceb3d93bdb3776b930cdf21258b2a8239d231f Mon Sep 17 00:00:00 2001 From: ISSOtm Date: Tue, 25 Jun 2024 18:53:37 +0200 Subject: [PATCH] Begin implementing thumbnailer for Linux --- Makefile | 37 +++++++++- XdgThumbnailer/interface.xml | 32 ++++++++ XdgThumbnailer/main.c | 139 +++++++++++++++++++++++++++++++++++ 3 files changed, 205 insertions(+), 3 deletions(-) create mode 100644 XdgThumbnailer/interface.xml create mode 100644 XdgThumbnailer/main.c diff --git a/Makefile b/Makefile index 719ebb9e2..40f8a37d5 100644 --- a/Makefile +++ b/Makefile @@ -217,6 +217,14 @@ SDL_LDFLAGS += $(shell $(PKG_CONFIG) --libs openal) SDL_AUDIO_DRIVERS += openal endif endif + +GIO_CFLAGS := $(shell $(PKG_CONFIG) --cflags gio-2.0) -DG_LOG_USE_STRUCTURED +GIO_LDFLAGS := $(shell $(PKG_CONFIG) --libs gio-2.0) +ifeq ($(CONF),debug) +GIO_CFLAGS += -DG_ENABLE_DEBUG +else +GIO_CFLAGS += -DG_DISABLE_ASSERT +endif endif ifeq (,$(PKG_CONFIG)) @@ -330,6 +338,7 @@ endif cocoa: $(BIN)/SameBoy.app quicklook: $(BIN)/SameBoy.qlgenerator +xdg-thumbnailer: $(BIN)/XdgThumbnailer/sameboy-thumbnailer sdl: $(SDL_TARGET) $(BIN)/SDL/dmg_boot.bin $(BIN)/SDL/mgb_boot.bin $(BIN)/SDL/cgb0_boot.bin $(BIN)/SDL/cgb_boot.bin $(BIN)/SDL/agb_boot.bin $(BIN)/SDL/sgb_boot.bin $(BIN)/SDL/sgb2_boot.bin $(BIN)/SDL/LICENSE $(BIN)/SDL/registers.sym $(BIN)/SDL/background.bmp $(BIN)/SDL/Shaders $(BIN)/SDL/Palettes bootroms: $(BIN)/BootROMs/agb_boot.bin $(BIN)/BootROMs/cgb_boot.bin $(BIN)/BootROMs/cgb0_boot.bin $(BIN)/BootROMs/dmg_boot.bin $(BIN)/BootROMs/mgb_boot.bin $(BIN)/BootROMs/sgb_boot.bin $(BIN)/BootROMs/sgb2_boot.bin tester: $(TESTER_TARGET) $(BIN)/tester/dmg_boot.bin $(BIN)/tester/cgb_boot.bin $(BIN)/tester/agb_boot.bin $(BIN)/tester/sgb_boot.bin $(BIN)/tester/sgb2_boot.bin @@ -355,6 +364,7 @@ TESTER_SOURCES := $(shell ls Tester/*.c) IOS_SOURCES := $(filter-out iOS/installer.m, $(shell ls iOS/*.m)) $(shell ls AppleCommon/*.m) COCOA_SOURCES := $(shell ls Cocoa/*.m) $(shell ls HexFiend/*.m) $(shell ls JoyKit/*.m) $(shell ls AppleCommon/*.m) QUICKLOOK_SOURCES := $(shell ls QuickLook/*.m) $(shell ls QuickLook/*.c) +XDG_THUMBNAILER_SOURCES := $(shell ls XdgThumbnailer/*.c) ifeq ($(PLATFORM),windows32) CORE_SOURCES += $(shell ls Windows/*.c) @@ -367,6 +377,7 @@ IOS_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(IOS_SOURCES)) QUICKLOOK_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(QUICKLOOK_SOURCES)) SDL_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(SDL_SOURCES)) TESTER_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(TESTER_SOURCES)) +XDG_THUMBNAILER_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(XDG_THUMBNAILER_SOURCES)) $(OBJ)/XdgThumbnailer/interface.c.o lib: $(PUBLIC_HEADERS) @@ -410,6 +421,20 @@ $(OBJ)/SDL/%.c.o: SDL/%.c -@$(MKDIR) -p $(dir $@) $(CC) $(CFLAGS) $(FRONTEND_CFLAGS) $(FAT_FLAGS) $(SDL_CFLAGS) $(GL_CFLAGS) -c $< -o $@ +$(OBJ)/XdgThumbnailer/%.c.o: XdgThumbnailer/%.c + -@$(MKDIR) -p $(dir $@) + $(CC) $(CFLAGS) $(FRONTEND_CFLAGS) $(FAT_FLAGS) $(GIO_CFLAGS) -c $< -o $@ +# Make sure not to attempt compiling this before generating the interface code. +$(OBJ)/XdgThumbnailer/main.c.o: $(OBJ)/XdgThumbnailer/interface.h +# Silence warnings for this. It is code generated not by us, so we do not want `-Werror` to break +# compilation with some version of the generator and/or compiler. +$(OBJ)/XdgThumbnailer/interface.c.o: $(OBJ)/XdgThumbnailer/interface.c + -@$(MKDIR) -p $(dir $@) + $(CC) $(CFLAGS) $(FRONTEND_CFLAGS) $(FAT_FLAGS) $(GIO_CFLAGS) -w -c $< -o $@ +$(OBJ)/XdgThumbnailer/interface.c $(OBJ)/XdgThumbnailer/interface.h: XdgThumbnailer/interface.xml + -@$(MKDIR) -p $(dir $@) + gdbus-codegen --c-generate-autocleanup none --c-namespace Thumbnailer --interface-prefix org.freedesktop.thumbnails. --generate-c-code $(OBJ)/XdgThumbnailer/interface $< + $(OBJ)/OpenDialog/%.c.o: OpenDialog/%.c -@$(MKDIR) -p $(dir $@) $(CC) $(CFLAGS) $(FRONTEND_CFLAGS) $(FAT_FLAGS) $(SDL_CFLAGS) $(GL_CFLAGS) -c $< -o $@ @@ -427,7 +452,7 @@ $(OBJ)/HexFiend/%.m.o: HexFiend/%.m $(OBJ)/%.m.o: %.m -@$(MKDIR) -p $(dir $@) $(CC) $(CFLAGS) $(FRONTEND_CFLAGS) $(FAT_FLAGS) $(OCFLAGS) -c $< -o $@ - + # iOS Port $(BIN)/SameBoy-iOS.app: $(BIN)/SameBoy-iOS.app/SameBoy \ @@ -530,7 +555,13 @@ endif $(BIN)/SameBoy.qlgenerator/Contents/Resources/cgb_boot_fast.bin: $(BIN)/BootROMs/cgb_boot_fast.bin -@$(MKDIR) -p $(dir $@) cp -f $^ $@ - + +# XDG thumbnailer + +$(BIN)/XdgThumbnailer/sameboy-thumbnailer: $(CORE_OBJECTS) $(XDG_THUMBNAILER_OBJECTS) + -@$(MKDIR) -p $(dir $@) + $(CC) $^ -o $@ $(LDFLAGS) $(FAT_FLAGS) $(GIO_LDFLAGS) + # SDL Port # Unix versions build only one binary @@ -615,7 +646,7 @@ $(BIN)/SDL/background.bmp: SDL/background.bmp $(BIN)/SDL/Shaders: Shaders -@$(MKDIR) -p $@ cp -rf Shaders/*.fsh $@ - + $(BIN)/SDL/Palettes: Misc/Palettes -@$(MKDIR) -p $@ cp -rf Misc/Palettes/*.sbp $@ diff --git a/XdgThumbnailer/interface.xml b/XdgThumbnailer/interface.xml new file mode 100644 index 000000000..33505b4cc --- /dev/null +++ b/XdgThumbnailer/interface.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/XdgThumbnailer/main.c b/XdgThumbnailer/main.c new file mode 100644 index 000000000..da44ca606 --- /dev/null +++ b/XdgThumbnailer/main.c @@ -0,0 +1,139 @@ +#define G_LOG_DOMAIN "sameboy-thumbnailer" + +#include +#include +#include +#include +#include +#include +#include + +// Auto-generated via `gdbus-codegen` from `interface.xml`. +#include "build/obj/XdgThumbnailer/interface.h" + +static char const *const name_on_bus = "com.github.liji32.sameboy.XdgThumbnailer"; +static char const *const object_path = "/com/github/liji32/sameboy/XdgThumbnailer"; + +/* --- The main work being performed here --- */ + +static GThreadPool *thread_pool; + +// The function called by the threads in `thread_pool`. +static void generate_thumbnail(void *data, void *user_data) +{ + // TODO +} + +static gboolean handle_queue(ThumbnailerSpecializedThumbnailer1 *object, + GDBusMethodInvocation *invocation, char const *uri, char const *mime_type, + char const *flavor, gboolean urgent) +{ + g_info("Received Queue(uri=\"%s\", mime_type=\"%s\", flavor=\"%s\", urgent=%s) request", uri, mime_type, flavor, urgent ? "true" : "false"); + + // TODO + + return G_DBUS_METHOD_INVOCATION_HANDLED; +} + +static gboolean handle_dequeue(ThumbnailerSpecializedThumbnailer1 *object, + GDBusMethodInvocation *invocation, unsigned handle) +{ + g_info("Received Dequeue(handle=%u) request", handle); + + // TODO + + return G_DBUS_METHOD_INVOCATION_HANDLED; +} + +/* --- "Glue"; or, how the above is orchestrated / wired up --- */ + +static GMainLoop *main_loop; + +static void on_bus_acquired(GDBusConnection *connection, const char *name, void *user_data) +{ + g_assert_cmpstr(name, ==, name_on_bus); + (void)user_data; + g_info("Acquired bus"); + + GError *error; + + // Create the interface, and hook up callbacks for when its methods are called. + ThumbnailerSpecializedThumbnailer1 *thumbnailer_interface = + thumbnailer_specialized_thumbnailer1_skeleton_new(); + g_signal_connect(thumbnailer_interface, "handle-queue", G_CALLBACK(handle_queue), NULL); + g_signal_connect(thumbnailer_interface, "handle-dequeue", G_CALLBACK(handle_dequeue), NULL); + + // Export the interface on the bus. + error = NULL; + GDBusInterfaceSkeleton *interface = G_DBUS_INTERFACE_SKELETON(thumbnailer_interface); + gboolean res = g_dbus_interface_skeleton_export(interface, connection, object_path, &error); + g_assert(res); + g_assert_no_error(error); + if (error) { + g_error("Error exporting interface \"%s\" at \"%s\": %s", + g_dbus_interface_skeleton_get_info(interface)->name, object_path, error->message); + // NOTREACHED + } +} + +static void on_name_acquired(GDBusConnection *connection, const char *name, void *user_data) +{ + g_assert_cmpstr(name, ==, name_on_bus); + (void)user_data; + + g_info("Acquired the name \"%s\" on the session bus", name); +} + +static void on_name_lost(GDBusConnection *connection, const char *name, void *user_data) +{ + g_assert_cmpstr(name, ==, name_on_bus); + (void)user_data; + + if (connection != NULL) { + g_info("Lost the name \"%s\" on the session bus", name); + } + else { + g_error("Failed to connect to session bus"); + } + g_main_loop_quit(main_loop); +} + +static gboolean handle_sigterm(void *user_data) +{ + g_info("SIGTERM received! Quitting..."); + + g_main_loop_quit(main_loop); + return G_SOURCE_CONTINUE; // Do not remove this source ourselves, let the post-main loop do so. +} + +int main(int argc, char const *argv[]) +{ + GError *error; + + // Create the thread pool *before* starting to accept tasks from D-Bus. + // Make it non-exclusive so that the number of spawned threads grows dynamically, to consume + // fewer system resources when no thumbnails are being generated. + thread_pool = + g_thread_pool_new(generate_thumbnail, NULL, g_get_num_processors(), FALSE, &error); + g_assert_no_error(error); // Creating a non-exclusive thread pool cannot generate errors. + // Likewise, create the main loop before then, so it can be aborted even before entering it. + main_loop = g_main_loop_new(NULL, FALSE); + + // Refuse to replace the name or be replaced; there should only be one instance of the + // thumbnailer on the bus at all times. To replace this program, kill it. + unsigned owner_id = + g_bus_own_name(G_BUS_TYPE_SESSION, name_on_bus, G_BUS_NAME_OWNER_FLAGS_DO_NOT_QUEUE, + on_bus_acquired, on_name_acquired, on_name_lost, NULL, NULL); + + unsigned sigterm_source_id = g_unix_signal_add(SIGTERM, handle_sigterm, NULL); + g_main_loop_run(main_loop); + gboolean removed = + g_source_remove(sigterm_source_id); // This must be done before destroying the main loop. + g_assert(removed); + + g_info("Waiting for outstanding tasks..."); + g_thread_pool_free(thread_pool, FALSE, TRUE); + g_main_loop_unref(main_loop); + g_bus_unown_name(owner_id); + return 0; +}