From e4ceb3d93bdb3776b930cdf21258b2a8239d231f Mon Sep 17 00:00:00 2001 From: ISSOtm Date: Tue, 25 Jun 2024 18:53:37 +0200 Subject: [PATCH 01/22] 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; +} From b3cecf24130aa7a9417ebf60ee0eeb3ea0b3d231 Mon Sep 17 00:00:00 2001 From: ISSOtm Date: Wed, 26 Jun 2024 22:53:23 +0200 Subject: [PATCH 02/22] Implement the "plumbing" around thumbnail generation Only the actual thumbnail generation is left! --- .gitattributes | 1 + Core/gb.c | 2 +- Makefile | 13 +-- XdgThumbnailer/main.c | 73 ++++++++-------- XdgThumbnailer/main.h | 14 ++++ XdgThumbnailer/tasks.c | 102 +++++++++++++++++++++++ XdgThumbnailer/tasks.h | 15 ++++ XdgThumbnailer/thumbnail.c | 166 +++++++++++++++++++++++++++++++++++++ XdgThumbnailer/thumbnail.h | 10 +++ 9 files changed, 353 insertions(+), 43 deletions(-) create mode 100644 XdgThumbnailer/main.h create mode 100644 XdgThumbnailer/tasks.c create mode 100644 XdgThumbnailer/tasks.h create mode 100644 XdgThumbnailer/thumbnail.c create mode 100644 XdgThumbnailer/thumbnail.h diff --git a/.gitattributes b/.gitattributes index 2149ea1dc..49c17bea4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -7,4 +7,5 @@ HexFiend/* linguist-vendored Core/*.h linguist-language=C SDL/*.h linguist-language=C Windows/*.h linguist-language=C +XdgThumbnailer/*.h linguist-language=C Cocoa/*.h linguist-language=Objective-C diff --git a/Core/gb.c b/Core/gb.c index 2369fbf9d..8d520c2f1 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -549,7 +549,7 @@ int GB_load_isx(GB_gameboy_t *gb, const char *path) bank = byte; if (byte >= 0x80) { READ(byte); - /* TODO: This is just a guess, the docs don't elaborator on how banks > 0xFF are saved, + /* TODO: This is just a guess, the docs don't elaborate on how banks > 0xFF are saved, other than the fact that banks >= 80 requires two bytes to store them, and I haven't encountered an ISX file for a ROM larger than 4MBs yet. */ bank += byte << 7; diff --git a/Makefile b/Makefile index 40f8a37d5..eeda729f1 100644 --- a/Makefile +++ b/Makefile @@ -338,7 +338,7 @@ endif cocoa: $(BIN)/SameBoy.app quicklook: $(BIN)/SameBoy.qlgenerator -xdg-thumbnailer: $(BIN)/XdgThumbnailer/sameboy-thumbnailer +xdg-thumbnailer: $(BIN)/XdgThumbnailer/sameboy-thumbnailer $(BIN)/SDL/cgb_boot_fast.bin 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 @@ -423,21 +423,21 @@ $(OBJ)/SDL/%.c.o: SDL/%.c $(OBJ)/XdgThumbnailer/%.c.o: XdgThumbnailer/%.c -@$(MKDIR) -p $(dir $@) - $(CC) $(CFLAGS) $(FRONTEND_CFLAGS) $(FAT_FLAGS) $(GIO_CFLAGS) -c $< -o $@ + $(CC) $(CFLAGS) $(GIO_CFLAGS) -DG_LOG_DOMAIN='"sameboy-thumbnailer"' -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 $@ + $(CC) $(CFLAGS) $(GIO_CFLAGS) -DG_LOG_DOMAIN='"sameboy-thumbnailer"' -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 $@ + $(CC) $(CFLAGS) $(SDL_CFLAGS) $(GL_CFLAGS) -c $< -o $@ $(OBJ)/%.c.o: %.c @@ -688,7 +688,8 @@ ifneq ($(FREEDESKTOP),) ICON_NAMES := apps/sameboy mimetypes/x-gameboy-rom mimetypes/x-gameboy-color-rom ICON_SIZES := 16x16 32x32 64x64 128x128 256x256 512x512 ICONS := $(foreach name,$(ICON_NAMES), $(foreach size,$(ICON_SIZES),$(DESTDIR)$(PREFIX)/share/icons/hicolor/$(size)/$(name).png)) -install: sdl $(DESTDIR)$(PREFIX)/share/mime/packages/sameboy.xml $(ICONS) FreeDesktop/sameboy.desktop +# TODO: install the thumbnailer as well +install: sdl xdg-thumbnailer $(DESTDIR)$(PREFIX)/share/mime/packages/sameboy.xml $(ICONS) FreeDesktop/sameboy.desktop -@$(MKDIR) -p $(dir $(DESTDIR)$(PREFIX)) mkdir -p $(DESTDIR)$(DATA_DIR)/ $(DESTDIR)$(PREFIX)/bin/ cp -rf $(BIN)/SDL/* $(DESTDIR)$(DATA_DIR)/ @@ -725,7 +726,7 @@ endif ios: @$(MAKE) _ios - + $(BIN)/SameBoy-iOS.ipa: ios iOS/sideload.entitlements $(MKDIR) -p $(OBJ)/Payload cp -rf $(BIN)/SameBoy-iOS.app $(OBJ)/Payload/SameBoy-iOS.app diff --git a/XdgThumbnailer/main.c b/XdgThumbnailer/main.c index da44ca606..54b4c5848 100644 --- a/XdgThumbnailer/main.c +++ b/XdgThumbnailer/main.c @@ -1,4 +1,4 @@ -#define G_LOG_DOMAIN "sameboy-thumbnailer" +#include "main.h" #include #include @@ -8,45 +8,46 @@ #include #include +#include "tasks.h" +#include "thumbnail.h" + // 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 -} +ThumbnailerSpecializedThumbnailer1 *thumbnailer_interface = NULL; +static unsigned max_nb_worker_threads; -static gboolean handle_queue(ThumbnailerSpecializedThumbnailer1 *object, - GDBusMethodInvocation *invocation, char const *uri, char const *mime_type, - char const *flavor, gboolean urgent) +static gboolean handle_queue(void *instance, GDBusMethodInvocation *invocation, char const *uri, + char const *mime_type, char const *flavor, gboolean urgent, + void *user_data) { - g_info("Received Queue(uri=\"%s\", mime_type=\"%s\", flavor=\"%s\", urgent=%s) request", uri, mime_type, flavor, urgent ? "true" : "false"); + ThumbnailerSpecializedThumbnailer1 *skeleton = instance; + g_info("Received Queue(uri=\"%s\", mime_type=\"%s\", flavor=\"%s\", urgent=%s) request", uri, + mime_type, flavor, urgent ? "true" : "false"); + g_assert(skeleton == thumbnailer_interface); - // TODO + struct NewTaskInfo task_info = new_task(urgent); + start_thumbnailing(task_info.handle, task_info.cancellable, urgent, uri, mime_type); + thumbnailer_specialized_thumbnailer1_complete_queue(skeleton, invocation, task_info.handle); return G_DBUS_METHOD_INVOCATION_HANDLED; } -static gboolean handle_dequeue(ThumbnailerSpecializedThumbnailer1 *object, - GDBusMethodInvocation *invocation, unsigned handle) +static gboolean handle_dequeue(void *instance, GDBusMethodInvocation *invocation, unsigned handle, + void *user_data) { + ThumbnailerSpecializedThumbnailer1 *skeleton = instance; g_info("Received Dequeue(handle=%u) request", handle); + g_assert(skeleton == thumbnailer_interface); - // TODO + cancel_task(handle); 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) @@ -55,16 +56,13 @@ static void on_bus_acquired(GDBusConnection *connection, const char *name, void (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(); + 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; + GError *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); @@ -108,14 +106,11 @@ static gboolean handle_sigterm(void *user_data) 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. + max_nb_worker_threads = g_get_num_processors(); + // unsigned active_worker_threads = 0; + // Create the task queue *before* starting to accept tasks from D-Bus. + init_tasks(); + load_boot_roms(); // Likewise, create the main loop before then, so it can be aborted even before entering it. main_loop = g_main_loop_new(NULL, FALSE); @@ -127,13 +122,19 @@ int main(int argc, char const *argv[]) 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. + // This must be done before destroying the main loop. + gboolean removed = g_source_remove(sigterm_source_id); g_assert(removed); g_info("Waiting for outstanding tasks..."); - g_thread_pool_free(thread_pool, FALSE, TRUE); + cleanup_tasks(); // Also waits for any remaining tasks. + // "Pedantic" cleanup for Valgrind et al. + unload_boot_roms(); g_main_loop_unref(main_loop); g_bus_unown_name(owner_id); + if (thumbnailer_interface) { + g_dbus_interface_skeleton_unexport(G_DBUS_INTERFACE_SKELETON(thumbnailer_interface)); + } + g_object_unref(thumbnailer_interface); return 0; } diff --git a/XdgThumbnailer/main.h b/XdgThumbnailer/main.h new file mode 100644 index 000000000..76438e10a --- /dev/null +++ b/XdgThumbnailer/main.h @@ -0,0 +1,14 @@ +#pragma once + +// As defined in the thumbnailer spec. +enum ErrorCode { + ERROR_UNKNOWN_SCHEME_OR_MIME, + ERROR_SPECIALIZED_THUMBNAILER_CONNECTION_FAILED, + ERROR_INVALID_DATA, + ERROR_THUMBNAIILING_THUMBNAIL, + ERROR_COULD_NOT_WRITE, + ERROR_UNSUPPORTED_FLAVOR, +}; + +struct _ThumbnailerSpecializedThumbnailer1; +extern struct _ThumbnailerSpecializedThumbnailer1 *thumbnailer_interface; diff --git a/XdgThumbnailer/tasks.c b/XdgThumbnailer/tasks.c new file mode 100644 index 000000000..b99d3b6b0 --- /dev/null +++ b/XdgThumbnailer/tasks.c @@ -0,0 +1,102 @@ +#include "tasks.h" + +#include +#include + +#define URGENT_FLAG (1u << (sizeof(unsigned) * CHAR_BIT - 1)) // The compiler should warn if this shift is out of range. + +struct Tasks { + // Note that the lock only applies to the whole array; individual elements may be mutated + // in-place just fine by the readers. + GRWLock lock; + GArray /* of GCancellable* */ *tasks; +}; +static struct Tasks urgent_tasks, tasks; + +static void init_task_list(struct Tasks *task_list) +{ + g_rw_lock_init(&task_list->lock); + task_list->tasks = g_array_new(FALSE, FALSE, sizeof(GCancellable *)); +} +void init_tasks(void) +{ + init_task_list(&urgent_tasks); + init_task_list(&tasks); +} + +static void cleanup_task_list(struct Tasks *task_list) { + // TODO: wait for the remaining tasks to end? + g_rw_lock_clear(&task_list->lock); + g_array_unref(task_list->tasks); +} +void cleanup_tasks(void) +{ + cleanup_task_list(&urgent_tasks); + cleanup_task_list(&tasks); +} + +struct NewTaskInfo new_task(gboolean is_urgent) +{ + struct Tasks *task_list = is_urgent ? &urgent_tasks : &tasks; + GCancellable **array = (void *)task_list->tasks->data; + + GCancellable *cancellable = g_cancellable_new(); + + // We may reallocate the array, so we need a writer lock. + g_rw_lock_writer_lock(&task_list->lock); + // First, look for a free slot in the array. + unsigned index = 0; + for (unsigned i = 0; i < task_list->tasks->len; ++i) { + if (array[i] == NULL) { + array[i] = cancellable; + index = i + 1; + goto got_slot; + } + } + // We need to allocate a new slot. + + // Each task list cannot contain 0x7FFFFFFF handles, as otherwise bit 7 cannot differentiate + // between regular and urgent tasks. + // Note that index 0 is invalid, since it's reserved for "no handle", so that's 1 less. + if (task_list->tasks->len == URGENT_FLAG - 2) { + g_object_unref(cancellable); + return (struct NewTaskInfo){.handle = 0}; + } + g_array_append_val(task_list->tasks, cancellable); + index = task_list->tasks->len; // We want the new index *plus one*. +got_slot: + g_rw_lock_writer_unlock(&task_list->lock); + + g_assert_cmpuint(index, !=, 0); + g_assert_cmpuint(index, <, URGENT_FLAG); + + return (struct NewTaskInfo){.handle = is_urgent ? (index | URGENT_FLAG) : index, + .cancellable = cancellable}; +} + +void cancel_task(unsigned handle) +{ + struct Tasks *task_list = (handle & URGENT_FLAG) ? &urgent_tasks : &tasks; + + g_rw_lock_reader_lock(&task_list->lock); + GCancellable **slot = &((GCancellable **)task_list->tasks->data)[(handle & ~URGENT_FLAG) - 1]; + GCancellable *cancellable = *slot; + *slot = NULL; + g_rw_lock_reader_unlock(&task_list->lock); + + g_cancellable_cancel(cancellable); + g_object_unref(cancellable); +} + +void finished_task(unsigned handle) +{ + struct Tasks *task_list = (handle & URGENT_FLAG) ? &urgent_tasks : &tasks; + + g_rw_lock_reader_lock(&task_list->lock); + GCancellable **slot = &((GCancellable **)task_list->tasks->data)[(handle & ~URGENT_FLAG) - 1]; + GCancellable *cancellable = *slot; + *slot = NULL; + g_rw_lock_reader_unlock(&task_list->lock); + + g_object_unref(cancellable); +} diff --git a/XdgThumbnailer/tasks.h b/XdgThumbnailer/tasks.h new file mode 100644 index 000000000..ea050af8c --- /dev/null +++ b/XdgThumbnailer/tasks.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +void init_tasks(void); +void cleanup_tasks(void); + +struct NewTaskInfo { + unsigned handle; + GCancellable *cancellable; +}; +struct NewTaskInfo new_task(gboolean is_urgent); +void cancel_task(unsigned handle); +void finished_task(unsigned handle); diff --git a/XdgThumbnailer/thumbnail.c b/XdgThumbnailer/thumbnail.c new file mode 100644 index 000000000..8b59753fa --- /dev/null +++ b/XdgThumbnailer/thumbnail.c @@ -0,0 +1,166 @@ +#include "thumbnail.h" + +#include +#include +#include + +#include "Core/gb.h" +#include "XdgThumbnailer/tasks.h" +#include "main.h" + +#define THUMBNAILING_ERROR_DOMAIN (g_quark_from_static_string("thumbnailing")) + +enum FileKind { + KIND_GB, + KIND_GBC, + KIND_ISX, +}; + +#define BOOT_ROM_SIZE (0x100 + 0x800) // The two "parts" of it, which are stored contiguously. +static char *boot_rom; + +void load_boot_roms(void) +{ + static char const *boot_rom_path = DATA_DIR "/cgb_boot_fast.bin"; + + size_t length; + GError *error = NULL; + g_file_get_contents(boot_rom_path, &boot_rom, &length, &error); + + if (error) { + g_error("Error loading boot ROM from \"%s\": %s", boot_rom_path, error->message); + // NOTREACHED + } + else if (length != BOOT_ROM_SIZE) { + g_error("Error loading boot ROM from \"%s\": expected to read %d bytes, got %zu", + boot_rom_path, BOOT_ROM_SIZE, length); + // NOTREACHED + } +} + +void unload_boot_roms(void) { g_free(boot_rom); } + +struct TaskData { + char *contents; + size_t length; + enum FileKind kind; +}; + +static void destroy_task_data(void *data) +{ + struct TaskData *task_data = data; + + g_free(task_data->contents); + g_slice_free(struct TaskData, task_data); +} + +static void generate_thumbnail(GTask *task, void *source_object, void *data, + GCancellable *cancellable) +{ + struct TaskData *task_data = data; + + GB_gameboy_t gb; + GB_init(&gb, GB_MODEL_CGB_E); + GB_load_boot_rom_from_buffer(&gb, (unsigned char const *)boot_rom, sizeof(boot_rom)); + + if (task_data->kind == KIND_ISX) { + g_assert_not_reached(); // TODO: implement GB_load_isx_from_buffer + } + else { + GB_load_rom_from_buffer(&gb, (unsigned char const *)task_data->contents, task_data->length); + } + // TODO + + GB_free(&gb); + + g_task_return_boolean(task, TRUE); + g_object_unref(task); +} + +// Callback when an async file operation completes. +static void on_file_ready(GObject *source_object, GAsyncResult *res, void *user_data) +{ + GFile *file = G_FILE(source_object); + GTask *task = user_data; + char const *uri = g_task_get_name(task); + g_debug("File \"%s\" is done being read", uri); + struct TaskData *task_data = g_task_get_task_data(task); + + GError *error = NULL; + g_file_load_contents_finish(file, res, &task_data->contents, &task_data->length, NULL, &error); + g_object_unref(file); + + if (error) { + g_task_return_new_error(task, THUMBNAILING_ERROR_DOMAIN, ERROR_UNKNOWN_SCHEME_OR_MIME, + "Failed to load URI \"%s\": %s", uri, error->message); + g_object_unref(task); + return; + } + + if (g_task_return_error_if_cancelled(task)) { + g_object_unref(task); + return; + } + + // TODO: cap the max number of active threads. + g_task_run_in_thread(task, generate_thumbnail); +} + +static void on_thumbnailing_end(GObject *source_object, GAsyncResult *res, void *user_data) +{ + // TODO: start a new thread if some task is pending. + + g_assert_null(source_object); // The object that was passed to `g_task_new`. + GTask *task = G_TASK(res); + g_debug("Ending thumbnailing for \"%s\"", g_task_get_name(task)); + unsigned handle = GPOINTER_TO_UINT(user_data); + char const *uri = g_task_get_name(task); + + GError *error = NULL; + if (g_task_propagate_boolean(task, &error)) { + g_signal_emit_by_name(thumbnailer_interface, "ready", handle, uri); + } + else if (!g_cancellable_is_cancelled(g_task_get_cancellable(task))) { + // If the task was cancelled, do not emit an error response. + g_signal_emit_by_name(thumbnailer_interface, "error", handle, uri, error->code, + error->message); + } + g_signal_emit_by_name(thumbnailer_interface, "finished", handle); + + finished_task(handle); +} + +void start_thumbnailing(unsigned handle, GCancellable *cancellable, gboolean is_urgent, + char const *uri, char const *mime_type) +{ + g_signal_emit_by_name(thumbnailer_interface, "started", handle); + + GTask *task = g_task_new(NULL, cancellable, on_thumbnailing_end, GUINT_TO_POINTER(handle)); + g_task_set_priority(task, is_urgent ? G_PRIORITY_HIGH : G_PRIORITY_DEFAULT); + g_task_set_name(task, uri); + + enum FileKind kind; + if (g_strcmp0(mime_type, "application/x-gameboy-color-rom") == 0) { + kind = KIND_GBC; + } + else if (g_strcmp0(mime_type, "application/x-gameboy-rom") == 0) { + kind = KIND_GB; + } + else if (g_strcmp0(mime_type, "application/x-gameboy-isx") == 0) { + kind = KIND_ISX; + } + else { + g_task_return_new_error(task, THUMBNAILING_ERROR_DOMAIN, ERROR_UNKNOWN_SCHEME_OR_MIME, + "Unsupported MIME type %s", mime_type); + g_object_unref(task); + return; + } + + struct TaskData *task_data = g_slice_new(struct TaskData); + task_data->contents = NULL; + task_data->kind = kind; + g_task_set_task_data(task, task_data, destroy_task_data); + + GFile *file = g_file_new_for_uri(uri); + g_file_load_contents_async(file, cancellable, on_file_ready, task); +} diff --git a/XdgThumbnailer/thumbnail.h b/XdgThumbnailer/thumbnail.h new file mode 100644 index 000000000..35db9ff75 --- /dev/null +++ b/XdgThumbnailer/thumbnail.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include + +void load_boot_roms(void); +void unload_boot_roms(void); + +void start_thumbnailing(unsigned handle, GCancellable *cancellable, gboolean is_urgent, + char const *uri, char const *mime_type); From 446fc1552159c1110f9a6531b05928b41b095e27 Mon Sep 17 00:00:00 2001 From: ISSOtm Date: Thu, 27 Jun 2024 00:00:32 +0200 Subject: [PATCH 03/22] Emulate the ROM to be thumbnailed Now all that's left is actually rendering that! --- Makefile | 41 ++++++++++------ XdgThumbnailer/emulate.c | 96 ++++++++++++++++++++++++++++++++++++++ XdgThumbnailer/emulate.h | 15 ++++++ XdgThumbnailer/main.c | 7 +-- XdgThumbnailer/thumbnail.c | 55 ++++------------------ XdgThumbnailer/thumbnail.h | 3 -- 6 files changed, 150 insertions(+), 67 deletions(-) create mode 100644 XdgThumbnailer/emulate.c create mode 100644 XdgThumbnailer/emulate.h diff --git a/Makefile b/Makefile index eeda729f1..dc6645c73 100644 --- a/Makefile +++ b/Makefile @@ -338,7 +338,7 @@ endif cocoa: $(BIN)/SameBoy.app quicklook: $(BIN)/SameBoy.qlgenerator -xdg-thumbnailer: $(BIN)/XdgThumbnailer/sameboy-thumbnailer $(BIN)/SDL/cgb_boot_fast.bin +xdg-thumbnailer: $(BIN)/XdgThumbnailer/sameboy-thumbnailer $(BIN)/XdgThumbnailer/cgb_boot_fast.bin $(patsubst QuickLook/%.png,$(BIN)/XdgThumbnailer/%.png,$(wildcard QuickLook/*.png)) 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 @@ -615,41 +615,51 @@ $(BIN)/tester/sameboy_tester.exe: $(CORE_OBJECTS) $(SDL_OBJECTS) -@$(MKDIR) -p $(dir $@) $(CC) $^ -o $@ $(LDFLAGS) -Wl,/subsystem:console -$(BIN)/SDL/%.bin: $(BOOTROMS_DIR)/%.bin - -@$(MKDIR) -p $(dir $@) - cp -f $^ $@ - $(BIN)/tester/%.bin: $(BOOTROMS_DIR)/%.bin -@$(MKDIR) -p $(dir $@) - cp -f $^ $@ + cp -f $< $@ $(BIN)/SameBoy.app/Contents/Resources/%.bin: $(BOOTROMS_DIR)/%.bin -@$(MKDIR) -p $(dir $@) - cp -f $^ $@ + cp -f $< $@ $(BIN)/SameBoy-iOS.app/%.bin: $(BOOTROMS_DIR)/%.bin -@$(MKDIR) -p $(dir $@) - cp -f $^ $@ + cp -f $< $@ + +$(BIN)/XdgThumbnailer/%.png: QuickLook/%.png + -@$(MKDIR) -p $(dir $@) + cp -f $< $@ + +$(BIN)/XdgThumbnailer/%.bin: $(BOOTROMS_DIR)/%.bin + -@$(MKDIR) -p $(dir $@) + cp -f $< $@ + +$(BIN)/SDL/%.bin: $(BOOTROMS_DIR)/%.bin + -@$(MKDIR) -p $(dir $@) + cp -f $< $@ $(BIN)/SDL/LICENSE: LICENSE -@$(MKDIR) -p $(dir $@) - grep -v "^ " $^ > $@ + grep -v "^ " $< > $@ $(BIN)/SDL/registers.sym: Misc/registers.sym -@$(MKDIR) -p $(dir $@) - cp -f $^ $@ + cp -f $< $@ $(BIN)/SDL/background.bmp: SDL/background.bmp -@$(MKDIR) -p $(dir $@) - cp -f $^ $@ + cp -f $< $@ -$(BIN)/SDL/Shaders: Shaders +$(BIN)/SDL/Shaders: $(wildcard Shaders/*.fsh) -@$(MKDIR) -p $@ - cp -rf Shaders/*.fsh $@ + cp -rf $^ $@ + touch $@ -$(BIN)/SDL/Palettes: Misc/Palettes +$(BIN)/SDL/Palettes: $(wildcard Misc/Palettes/*.sbp) -@$(MKDIR) -p $@ - cp -rf Misc/Palettes/*.sbp $@ + cp -rf $^ $@ + touch $@ # Boot ROMs @@ -693,6 +703,7 @@ install: sdl xdg-thumbnailer $(DESTDIR)$(PREFIX)/share/mime/packages/sameboy.xml -@$(MKDIR) -p $(dir $(DESTDIR)$(PREFIX)) mkdir -p $(DESTDIR)$(DATA_DIR)/ $(DESTDIR)$(PREFIX)/bin/ cp -rf $(BIN)/SDL/* $(DESTDIR)$(DATA_DIR)/ + cp -rf $(BIN)/XdgThumbnailer/* $(DESTDIR)$(DATA_DIR)/ mv $(DESTDIR)$(DATA_DIR)/sameboy $(DESTDIR)$(PREFIX)/bin/sameboy ifeq ($(DESTDIR),) -update-mime-database -n $(PREFIX)/share/mime diff --git a/XdgThumbnailer/emulate.c b/XdgThumbnailer/emulate.c new file mode 100644 index 000000000..0c8c84810 --- /dev/null +++ b/XdgThumbnailer/emulate.c @@ -0,0 +1,96 @@ +#include "emulate.h" + +#include +#include + +#include "Core/gb.h" +#include "Core/memory.h" +#include "glibconfig.h" + +#define NB_FRAMES_TO_EMULATE (60 * 10) + +#define BOOT_ROM_SIZE (0x100 + 0x800) // The two "parts" of it, which are stored contiguously. +static char *boot_rom; + +void load_boot_rom(void) +{ + static char const *boot_rom_path = DATA_DIR "/cgb_boot_fast.bin"; + + size_t length; + GError *error = NULL; + g_file_get_contents(boot_rom_path, &boot_rom, &length, &error); + + if (error) { + g_error("Error loading boot ROM from \"%s\": %s", boot_rom_path, error->message); + // NOTREACHED + } + else if (length != BOOT_ROM_SIZE) { + g_error("Error loading boot ROM from \"%s\": expected to read %d bytes, got %zu", + boot_rom_path, BOOT_ROM_SIZE, length); + // NOTREACHED + } +} + +void unload_boot_rom(void) { g_free(boot_rom); } + +/* --- */ + +static char *async_input_callback(GB_gameboy_t *gb) +{ + return NULL; +} + +static void log_callback(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes) +{ + // Swallow any logs. +} + +static void vblank_callback(GB_gameboy_t *gb, GB_vblank_type_t type) +{ + unsigned nb_frames_left = GPOINTER_TO_UINT(GB_get_user_data(gb)); + nb_frames_left--; + GB_set_user_data(gb, GUINT_TO_POINTER(nb_frames_left)); + + // *Do* render the very last frame. + if (nb_frames_left == 0) { + GB_set_rendering_disabled(gb, false); + } +} + +static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) +{ + return r | g << 8 | b << 16 | 0xFF << 24; +} + +unsigned emulate(enum FileKind kind, unsigned char const *rom, size_t rom_size, uint32_t screen[static 160 * 144]) +{ + GB_gameboy_t gb; + GB_init(&gb, GB_MODEL_CGB_E); + + GB_load_boot_rom_from_buffer(&gb, (unsigned char const *)boot_rom, sizeof(boot_rom)); + if (kind == KIND_ISX) { + g_assert_not_reached(); // TODO: implement GB_load_isx_from_buffer + } + else { + GB_load_rom_from_buffer(&gb, rom, rom_size); + } + + GB_set_user_data(&gb, GUINT_TO_POINTER(NB_FRAMES_TO_EMULATE)); + + GB_set_vblank_callback(&gb, vblank_callback); + GB_set_pixels_output(&gb, screen); + GB_set_rgb_encode_callback(&gb, rgb_encode); + GB_set_async_input_callback(&gb, async_input_callback); + GB_set_log_callback(&gb, log_callback); + GB_set_color_correction_mode(&gb, GB_COLOR_CORRECTION_MODERN_BALANCED); + + GB_set_rendering_disabled(&gb, true); + GB_set_turbo_mode(&gb, true, true); + while (GPOINTER_TO_UINT(GB_get_user_data(&gb))) { + GB_run(&gb); + } + + unsigned cgb_flag = GB_read_memory(&gb, 0x143) & 0xC0; + GB_free(&gb); + return cgb_flag; +} diff --git a/XdgThumbnailer/emulate.h b/XdgThumbnailer/emulate.h new file mode 100644 index 000000000..480837032 --- /dev/null +++ b/XdgThumbnailer/emulate.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +enum FileKind { + KIND_GB, + KIND_GBC, + KIND_ISX, +}; + +void load_boot_rom(void); +void unload_boot_rom(void); + +unsigned emulate(enum FileKind kind, unsigned char const *rom, size_t rom_size, uint32_t screen[static 160 * 144]); diff --git a/XdgThumbnailer/main.c b/XdgThumbnailer/main.c index 54b4c5848..663b60317 100644 --- a/XdgThumbnailer/main.c +++ b/XdgThumbnailer/main.c @@ -8,6 +8,7 @@ #include #include +#include "emulate.h" #include "tasks.h" #include "thumbnail.h" @@ -110,7 +111,7 @@ int main(int argc, char const *argv[]) // unsigned active_worker_threads = 0; // Create the task queue *before* starting to accept tasks from D-Bus. init_tasks(); - load_boot_roms(); + load_boot_rom(); // Likewise, create the main loop before then, so it can be aborted even before entering it. main_loop = g_main_loop_new(NULL, FALSE); @@ -129,12 +130,12 @@ int main(int argc, char const *argv[]) g_info("Waiting for outstanding tasks..."); cleanup_tasks(); // Also waits for any remaining tasks. // "Pedantic" cleanup for Valgrind et al. - unload_boot_roms(); + unload_boot_rom(); g_main_loop_unref(main_loop); g_bus_unown_name(owner_id); if (thumbnailer_interface) { g_dbus_interface_skeleton_unexport(G_DBUS_INTERFACE_SKELETON(thumbnailer_interface)); + g_object_unref(thumbnailer_interface); } - g_object_unref(thumbnailer_interface); return 0; } diff --git a/XdgThumbnailer/thumbnail.c b/XdgThumbnailer/thumbnail.c index 8b59753fa..f95a89086 100644 --- a/XdgThumbnailer/thumbnail.c +++ b/XdgThumbnailer/thumbnail.c @@ -2,44 +2,15 @@ #include #include +#include #include -#include "Core/gb.h" -#include "XdgThumbnailer/tasks.h" +#include "emulate.h" #include "main.h" +#include "tasks.h" #define THUMBNAILING_ERROR_DOMAIN (g_quark_from_static_string("thumbnailing")) -enum FileKind { - KIND_GB, - KIND_GBC, - KIND_ISX, -}; - -#define BOOT_ROM_SIZE (0x100 + 0x800) // The two "parts" of it, which are stored contiguously. -static char *boot_rom; - -void load_boot_roms(void) -{ - static char const *boot_rom_path = DATA_DIR "/cgb_boot_fast.bin"; - - size_t length; - GError *error = NULL; - g_file_get_contents(boot_rom_path, &boot_rom, &length, &error); - - if (error) { - g_error("Error loading boot ROM from \"%s\": %s", boot_rom_path, error->message); - // NOTREACHED - } - else if (length != BOOT_ROM_SIZE) { - g_error("Error loading boot ROM from \"%s\": expected to read %d bytes, got %zu", - boot_rom_path, BOOT_ROM_SIZE, length); - // NOTREACHED - } -} - -void unload_boot_roms(void) { g_free(boot_rom); } - struct TaskData { char *contents; size_t length; @@ -59,19 +30,11 @@ static void generate_thumbnail(GTask *task, void *source_object, void *data, { struct TaskData *task_data = data; - GB_gameboy_t gb; - GB_init(&gb, GB_MODEL_CGB_E); - GB_load_boot_rom_from_buffer(&gb, (unsigned char const *)boot_rom, sizeof(boot_rom)); - - if (task_data->kind == KIND_ISX) { - g_assert_not_reached(); // TODO: implement GB_load_isx_from_buffer - } - else { - GB_load_rom_from_buffer(&gb, (unsigned char const *)task_data->contents, task_data->length); - } - // TODO - - GB_free(&gb); + uint32_t screen[160 * 144]; + unsigned cgb_flag = emulate(task_data->kind, (unsigned char const *)task_data->contents, + task_data->length, screen); + // TODO: generate the thumbnail from `screen` and `cgb_flag`. + (void)cgb_flag; g_task_return_boolean(task, TRUE); g_object_unref(task); @@ -112,7 +75,7 @@ static void on_thumbnailing_end(GObject *source_object, GAsyncResult *res, void g_assert_null(source_object); // The object that was passed to `g_task_new`. GTask *task = G_TASK(res); - g_debug("Ending thumbnailing for \"%s\"", g_task_get_name(task)); + g_info("Ending thumbnailing for \"%s\"", g_task_get_name(task)); unsigned handle = GPOINTER_TO_UINT(user_data); char const *uri = g_task_get_name(task); diff --git a/XdgThumbnailer/thumbnail.h b/XdgThumbnailer/thumbnail.h index 35db9ff75..8880adb71 100644 --- a/XdgThumbnailer/thumbnail.h +++ b/XdgThumbnailer/thumbnail.h @@ -3,8 +3,5 @@ #include #include -void load_boot_roms(void); -void unload_boot_roms(void); - void start_thumbnailing(unsigned handle, GCancellable *cancellable, gboolean is_urgent, char const *uri, char const *mime_type); From d873abfadf73c92a9476eab4ad8391fdd2fdffa8 Mon Sep 17 00:00:00 2001 From: ISSOtm Date: Thu, 27 Jun 2024 02:27:07 +0200 Subject: [PATCH 04/22] Switch to embedding cartridge templates and boot ROM as GResources Also set up gdk-pixbuf in the Makefile, which will be used for image ops --- Makefile | 34 ++++++++++++++----------- XdgThumbnailer/emulate.c | 16 +++++++++--- XdgThumbnailer/main.c | 1 - XdgThumbnailer/main.h | 6 ++--- XdgThumbnailer/resources.gresource.xml | 9 +++++++ XdgThumbnailer/thumbnail.c | 35 ++++++++++++++++++++++++-- 6 files changed, 78 insertions(+), 23 deletions(-) create mode 100644 XdgThumbnailer/resources.gresource.xml diff --git a/Makefile b/Makefile index dc6645c73..9f754276a 100644 --- a/Makefile +++ b/Makefile @@ -225,6 +225,12 @@ GIO_CFLAGS += -DG_ENABLE_DEBUG else GIO_CFLAGS += -DG_DISABLE_ASSERT endif + +GDK_PIXBUF_CFLAGS := $(shell $(PKG_CONFIG) --cflags gdk-pixbuf-2.0) +GDK_PIXBUF_LDFLAGS := $(shell $(PKG_CONFIG) --libs gdk-pixbuf-2.0) + +LIBPNG_CFLAGS := $(shell $(PKG_CONFIG) --cflags libpng) +LIBPNG_LDFLAGS := $(shell $(PKG_CONFIG) --libs libpng) endif ifeq (,$(PKG_CONFIG)) @@ -338,7 +344,7 @@ endif cocoa: $(BIN)/SameBoy.app quicklook: $(BIN)/SameBoy.qlgenerator -xdg-thumbnailer: $(BIN)/XdgThumbnailer/sameboy-thumbnailer $(BIN)/XdgThumbnailer/cgb_boot_fast.bin $(patsubst QuickLook/%.png,$(BIN)/XdgThumbnailer/%.png,$(wildcard QuickLook/*.png)) +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 @@ -377,7 +383,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 +XDG_THUMBNAILER_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(XDG_THUMBNAILER_SOURCES)) $(OBJ)/XdgThumbnailer/interface.c.o $(OBJ)/XdgThumbnailer/resources.c.o lib: $(PUBLIC_HEADERS) @@ -423,18 +429,26 @@ $(OBJ)/SDL/%.c.o: SDL/%.c $(OBJ)/XdgThumbnailer/%.c.o: XdgThumbnailer/%.c -@$(MKDIR) -p $(dir $@) - $(CC) $(CFLAGS) $(GIO_CFLAGS) -DG_LOG_DOMAIN='"sameboy-thumbnailer"' -c $< -o $@ + $(CC) $(CFLAGS) $(GIO_CFLAGS) $(GDK_PIXBUF_CFLAGS) $(LIBPNG_CFLAGS) -DG_LOG_DOMAIN='"sameboy-thumbnailer"' -c $< -o $@ # Make sure not to attempt compiling this before generating the interface code. $(OBJ)/XdgThumbnailer/main.c.o: $(OBJ)/XdgThumbnailer/interface.h +# Make sure not to attempt compiling this before generating the resource code. +$(OBJ)/XdgThumbnailer/emulate.c.o: $(OBJ)/XdgThumbnailer/resources.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 +$(OBJ)/XdgThumbnailer/%.c.o: $(OBJ)/XdgThumbnailer/%.c -@$(MKDIR) -p $(dir $@) - $(CC) $(CFLAGS) $(GIO_CFLAGS) -DG_LOG_DOMAIN='"sameboy-thumbnailer"' -w -c $< -o $@ + $(CC) $(CFLAGS) $(GIO_CFLAGS) $(GDK_PIXBUF_CFLAGS) $(LIBPNG_CFLAGS) -DG_LOG_DOMAIN='"sameboy-thumbnailer"' -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)/XdgThumbnailer/resources.c $(OBJ)/XdgThumbnailer/resources.h: %: XdgThumbnailer/resources.gresource.xml $(BIN)/BootROMs/cgb_boot_fast.bin + -@$(MKDIR) -p $(dir $@) + CC=$(CC) glib-compile-resources --dependency-file $@.mk --generate-phony-targets --generate --target $@ $< +-include $(OBJ)/XdgThumbnailer/resources.c.mk $(OBJ)/XdgThumbnailer/resources.h.mk + $(OBJ)/OpenDialog/%.c.o: OpenDialog/%.c -@$(MKDIR) -p $(dir $@) $(CC) $(CFLAGS) $(SDL_CFLAGS) $(GL_CFLAGS) -c $< -o $@ @@ -560,7 +574,7 @@ $(BIN)/SameBoy.qlgenerator/Contents/Resources/cgb_boot_fast.bin: $(BIN)/BootROMs $(BIN)/XdgThumbnailer/sameboy-thumbnailer: $(CORE_OBJECTS) $(XDG_THUMBNAILER_OBJECTS) -@$(MKDIR) -p $(dir $@) - $(CC) $^ -o $@ $(LDFLAGS) $(FAT_FLAGS) $(GIO_LDFLAGS) + $(CC) $^ -o $@ $(LDFLAGS) $(GIO_LDFLAGS) $(GDK_PIXBUF_LDFLAGS) $(LIBPNG_LDFLAGS) # SDL Port @@ -627,14 +641,6 @@ $(BIN)/SameBoy-iOS.app/%.bin: $(BOOTROMS_DIR)/%.bin -@$(MKDIR) -p $(dir $@) cp -f $< $@ -$(BIN)/XdgThumbnailer/%.png: QuickLook/%.png - -@$(MKDIR) -p $(dir $@) - cp -f $< $@ - -$(BIN)/XdgThumbnailer/%.bin: $(BOOTROMS_DIR)/%.bin - -@$(MKDIR) -p $(dir $@) - cp -f $< $@ - $(BIN)/SDL/%.bin: $(BOOTROMS_DIR)/%.bin -@$(MKDIR) -p $(dir $@) cp -f $< $@ diff --git a/XdgThumbnailer/emulate.c b/XdgThumbnailer/emulate.c index 0c8c84810..bd403e177 100644 --- a/XdgThumbnailer/emulate.c +++ b/XdgThumbnailer/emulate.c @@ -1,11 +1,13 @@ #include "emulate.h" #include +#include #include #include "Core/gb.h" -#include "Core/memory.h" -#include "glibconfig.h" + +// Auto-generated via `glib-compile-resources` from `resources.gresource.xml`. +#include "build/obj/XdgThumbnailer/resources.h" #define NB_FRAMES_TO_EMULATE (60 * 10) @@ -67,7 +69,15 @@ unsigned emulate(enum FileKind kind, unsigned char const *rom, size_t rom_size, GB_gameboy_t gb; GB_init(&gb, GB_MODEL_CGB_E); - GB_load_boot_rom_from_buffer(&gb, (unsigned char const *)boot_rom, sizeof(boot_rom)); + GError *error = NULL; + GBytes *boot_rom = g_resource_lookup_data(resources_get_resource(), "/thumbnailer/cgb_boot_fast.bin", G_RESOURCE_LOOKUP_FLAGS_NONE, &error); + g_assert_no_error(error); + size_t boot_rom_size; + unsigned char const *boot_rom_data = g_bytes_get_data(boot_rom, &boot_rom_size); + g_assert_cmpuint(boot_rom_size, ==, BOOT_ROM_SIZE); + GB_load_boot_rom_from_buffer(&gb, boot_rom_data, boot_rom_size); + g_bytes_unref(boot_rom); + if (kind == KIND_ISX) { g_assert_not_reached(); // TODO: implement GB_load_isx_from_buffer } diff --git a/XdgThumbnailer/main.c b/XdgThumbnailer/main.c index 663b60317..ff384025c 100644 --- a/XdgThumbnailer/main.c +++ b/XdgThumbnailer/main.c @@ -111,7 +111,6 @@ int main(int argc, char const *argv[]) // unsigned active_worker_threads = 0; // Create the task queue *before* starting to accept tasks from D-Bus. init_tasks(); - load_boot_rom(); // Likewise, create the main loop before then, so it can be aborted even before entering it. main_loop = g_main_loop_new(NULL, FALSE); diff --git a/XdgThumbnailer/main.h b/XdgThumbnailer/main.h index 76438e10a..76a32c0c2 100644 --- a/XdgThumbnailer/main.h +++ b/XdgThumbnailer/main.h @@ -3,9 +3,9 @@ // As defined in the thumbnailer spec. enum ErrorCode { ERROR_UNKNOWN_SCHEME_OR_MIME, - ERROR_SPECIALIZED_THUMBNAILER_CONNECTION_FAILED, - ERROR_INVALID_DATA, - ERROR_THUMBNAIILING_THUMBNAIL, + ERROR_SPECIALIZED_THUMBNAILER_CONNECTION_FAILED, // Not applicable. + ERROR_INVALID_DATA, // Any file can be decoded as a GB ROM, apparently! + ERROR_THUMBNAIILING_THUMBNAIL, // We defer checking this to the generic thumbnailer. ERROR_COULD_NOT_WRITE, ERROR_UNSUPPORTED_FLAVOR, }; diff --git a/XdgThumbnailer/resources.gresource.xml b/XdgThumbnailer/resources.gresource.xml new file mode 100644 index 000000000..f30ec174d --- /dev/null +++ b/XdgThumbnailer/resources.gresource.xml @@ -0,0 +1,9 @@ + + + + QuickLook/CartridgeTemplate.png + QuickLook/ColorCartridgeTemplate.png + QuickLook/UniversalCartridgeTemplate.png + build/bin/BootROMs/cgb_boot_fast.bin + + diff --git a/XdgThumbnailer/thumbnail.c b/XdgThumbnailer/thumbnail.c index f95a89086..e1ce61fa8 100644 --- a/XdgThumbnailer/thumbnail.c +++ b/XdgThumbnailer/thumbnail.c @@ -11,6 +11,20 @@ #define THUMBNAILING_ERROR_DOMAIN (g_quark_from_static_string("thumbnailing")) +/* --- */ + +enum CartridgeType { + CART_DMG_ONLY, + CART_DUAL, + CART_CGB_ONLY, +}; + +void load_cartridge_images(void) { + // TODO +} + +/* --- */ + struct TaskData { char *contents; size_t length; @@ -25,6 +39,8 @@ static void destroy_task_data(void *data) g_slice_free(struct TaskData, task_data); } +/* --- */ + static void generate_thumbnail(GTask *task, void *source_object, void *data, GCancellable *cancellable) { @@ -33,8 +49,21 @@ static void generate_thumbnail(GTask *task, void *source_object, void *data, uint32_t screen[160 * 144]; unsigned cgb_flag = emulate(task_data->kind, (unsigned char const *)task_data->contents, task_data->length, screen); - // TODO: generate the thumbnail from `screen` and `cgb_flag`. - (void)cgb_flag; + + // Generate the thumbnail from `screen` and `cgb_flag`. + enum CartridgeType type; + switch (cgb_flag) { + case 0xC0: + type = CART_CGB_ONLY; + break; + case 0x80: + type = CART_DUAL; + break; + default: + type = CART_DMG_ONLY; + break; + } + (void)type; g_task_return_boolean(task, TRUE); g_object_unref(task); @@ -69,6 +98,8 @@ static void on_file_ready(GObject *source_object, GAsyncResult *res, void *user_ g_task_run_in_thread(task, generate_thumbnail); } +/* --- */ + static void on_thumbnailing_end(GObject *source_object, GAsyncResult *res, void *user_data) { // TODO: start a new thread if some task is pending. From 0b6a73f380b6222443bd76a2f7ef644ea88b2b1f Mon Sep 17 00:00:00 2001 From: ISSOtm Date: Fri, 28 Jun 2024 16:14:58 +0200 Subject: [PATCH 05/22] Implement image compositing and rendering Not saving to the correct place yet, but almost there! --- Makefile | 17 +++--- XdgThumbnailer/emulate.c | 30 ++++++---- XdgThumbnailer/main.c | 24 ++++---- XdgThumbnailer/main.h | 12 ++-- XdgThumbnailer/tasks.c | 9 +-- XdgThumbnailer/tasks.h | 2 +- XdgThumbnailer/thumbnail.c | 119 ++++++++++++++++++++++++++----------- XdgThumbnailer/thumbnail.h | 6 +- 8 files changed, 137 insertions(+), 82 deletions(-) diff --git a/Makefile b/Makefile index 9f754276a..113aba8e0 100644 --- a/Makefile +++ b/Makefile @@ -228,9 +228,6 @@ endif GDK_PIXBUF_CFLAGS := $(shell $(PKG_CONFIG) --cflags gdk-pixbuf-2.0) GDK_PIXBUF_LDFLAGS := $(shell $(PKG_CONFIG) --libs gdk-pixbuf-2.0) - -LIBPNG_CFLAGS := $(shell $(PKG_CONFIG) --cflags libpng) -LIBPNG_LDFLAGS := $(shell $(PKG_CONFIG) --libs libpng) endif ifeq (,$(PKG_CONFIG)) @@ -429,7 +426,7 @@ $(OBJ)/SDL/%.c.o: SDL/%.c $(OBJ)/XdgThumbnailer/%.c.o: XdgThumbnailer/%.c -@$(MKDIR) -p $(dir $@) - $(CC) $(CFLAGS) $(GIO_CFLAGS) $(GDK_PIXBUF_CFLAGS) $(LIBPNG_CFLAGS) -DG_LOG_DOMAIN='"sameboy-thumbnailer"' -c $< -o $@ + $(CC) $(CFLAGS) $(GIO_CFLAGS) $(GDK_PIXBUF_CFLAGS) -DG_LOG_DOMAIN='"sameboy-thumbnailer"' -c $< -o $@ # Make sure not to attempt compiling this before generating the interface code. $(OBJ)/XdgThumbnailer/main.c.o: $(OBJ)/XdgThumbnailer/interface.h # Make sure not to attempt compiling this before generating the resource code. @@ -438,7 +435,7 @@ $(OBJ)/XdgThumbnailer/emulate.c.o: $(OBJ)/XdgThumbnailer/resources.h # compilation with some version of the generator and/or compiler. $(OBJ)/XdgThumbnailer/%.c.o: $(OBJ)/XdgThumbnailer/%.c -@$(MKDIR) -p $(dir $@) - $(CC) $(CFLAGS) $(GIO_CFLAGS) $(GDK_PIXBUF_CFLAGS) $(LIBPNG_CFLAGS) -DG_LOG_DOMAIN='"sameboy-thumbnailer"' -w -c $< -o $@ + $(CC) $(CFLAGS) $(GIO_CFLAGS) $(GDK_PIXBUF_CFLAGS) -DG_LOG_DOMAIN='"sameboy-thumbnailer"' -w -c $< -o $@ $(OBJ)/XdgThumbnailer/interface.c $(OBJ)/XdgThumbnailer/interface.h: XdgThumbnailer/interface.xml -@$(MKDIR) -p $(dir $@) @@ -574,7 +571,7 @@ $(BIN)/SameBoy.qlgenerator/Contents/Resources/cgb_boot_fast.bin: $(BIN)/BootROMs $(BIN)/XdgThumbnailer/sameboy-thumbnailer: $(CORE_OBJECTS) $(XDG_THUMBNAILER_OBJECTS) -@$(MKDIR) -p $(dir $@) - $(CC) $^ -o $@ $(LDFLAGS) $(GIO_LDFLAGS) $(GDK_PIXBUF_LDFLAGS) $(LIBPNG_LDFLAGS) + $(CC) $^ -o $@ $(LDFLAGS) $(GIO_LDFLAGS) $(GDK_PIXBUF_LDFLAGS) # SDL Port @@ -657,14 +654,14 @@ $(BIN)/SDL/background.bmp: SDL/background.bmp -@$(MKDIR) -p $(dir $@) cp -f $< $@ -$(BIN)/SDL/Shaders: $(wildcard Shaders/*.fsh) +$(BIN)/SDL/Shaders: Shaders -@$(MKDIR) -p $@ - cp -rf $^ $@ + cp -rf $< $@ touch $@ -$(BIN)/SDL/Palettes: $(wildcard Misc/Palettes/*.sbp) +$(BIN)/SDL/Palettes: Misc/Palettes -@$(MKDIR) -p $@ - cp -rf $^ $@ + cp -rf $< $@ touch $@ # Boot ROMs diff --git a/XdgThumbnailer/emulate.c b/XdgThumbnailer/emulate.c index bd403e177..86a77fec1 100644 --- a/XdgThumbnailer/emulate.c +++ b/XdgThumbnailer/emulate.c @@ -1,7 +1,7 @@ #include "emulate.h" -#include #include +#include #include #include "Core/gb.h" @@ -27,8 +27,8 @@ void load_boot_rom(void) // NOTREACHED } else if (length != BOOT_ROM_SIZE) { - g_error("Error loading boot ROM from \"%s\": expected to read %d bytes, got %zu", - boot_rom_path, BOOT_ROM_SIZE, length); + g_error("Error loading boot ROM from \"%s\": expected to read %d bytes, got %zu", boot_rom_path, BOOT_ROM_SIZE, + length); // NOTREACHED } } @@ -37,10 +37,7 @@ void unload_boot_rom(void) { g_free(boot_rom); } /* --- */ -static char *async_input_callback(GB_gameboy_t *gb) -{ - return NULL; -} +static char *async_input_callback(GB_gameboy_t *gb) { return NULL; } static void log_callback(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes) { @@ -54,14 +51,26 @@ static void vblank_callback(GB_gameboy_t *gb, GB_vblank_type_t type) GB_set_user_data(gb, GUINT_TO_POINTER(nb_frames_left)); // *Do* render the very last frame. - if (nb_frames_left == 0) { + if (nb_frames_left == 1) { GB_set_rendering_disabled(gb, false); } } static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) { - return r | g << 8 | b << 16 | 0xFF << 24; + uint32_t rgba; + // The GdkPixbuf that will be created from the screen buffer later, expects components in the + // order [red, green, blue, alpha], from a uint8_t[] buffer. + // But SameBoy requires a uint32_t[] buffer, and don't know the endianness of `uint32_t`. + // So we treat each uint32_t as a 4-byte buffer, and write the bytes accordingly. + // This is guaranteed to not be UB, because casting a `T*` to any flavour of `char*` accesses + // and modifies the `T`'s "object representation". + unsigned char *bytes = (uint8_t *)&rgba; + bytes[0] = r; + bytes[1] = g; + bytes[2] = b; + bytes[3] = 0xFF; + return rgba; } unsigned emulate(enum FileKind kind, unsigned char const *rom, size_t rom_size, uint32_t screen[static 160 * 144]) @@ -70,7 +79,8 @@ unsigned emulate(enum FileKind kind, unsigned char const *rom, size_t rom_size, GB_init(&gb, GB_MODEL_CGB_E); GError *error = NULL; - GBytes *boot_rom = g_resource_lookup_data(resources_get_resource(), "/thumbnailer/cgb_boot_fast.bin", G_RESOURCE_LOOKUP_FLAGS_NONE, &error); + GBytes *boot_rom = g_resource_lookup_data(resources_get_resource(), "/thumbnailer/cgb_boot_fast.bin", + G_RESOURCE_LOOKUP_FLAGS_NONE, &error); g_assert_no_error(error); size_t boot_rom_size; unsigned char const *boot_rom_data = g_bytes_get_data(boot_rom, &boot_rom_size); diff --git a/XdgThumbnailer/main.c b/XdgThumbnailer/main.c index ff384025c..8e3297ec0 100644 --- a/XdgThumbnailer/main.c +++ b/XdgThumbnailer/main.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -21,13 +22,12 @@ static char const *const object_path = "/com/github/liji32/sameboy/XdgThumbnaile ThumbnailerSpecializedThumbnailer1 *thumbnailer_interface = NULL; static unsigned max_nb_worker_threads; -static gboolean handle_queue(void *instance, GDBusMethodInvocation *invocation, char const *uri, - char const *mime_type, char const *flavor, gboolean urgent, - void *user_data) +static gboolean handle_queue(void *instance, GDBusMethodInvocation *invocation, char const *uri, char const *mime_type, + char const *flavor, gboolean urgent, void *user_data) { ThumbnailerSpecializedThumbnailer1 *skeleton = instance; - g_info("Received Queue(uri=\"%s\", mime_type=\"%s\", flavor=\"%s\", urgent=%s) request", uri, - mime_type, flavor, urgent ? "true" : "false"); + g_info("Received Queue(uri=\"%s\", mime_type=\"%s\", flavor=\"%s\", urgent=%s) request", uri, mime_type, flavor, + urgent ? "true" : "false"); g_assert(skeleton == thumbnailer_interface); struct NewTaskInfo task_info = new_task(urgent); @@ -37,8 +37,7 @@ static gboolean handle_queue(void *instance, GDBusMethodInvocation *invocation, return G_DBUS_METHOD_INVOCATION_HANDLED; } -static gboolean handle_dequeue(void *instance, GDBusMethodInvocation *invocation, unsigned handle, - void *user_data) +static gboolean handle_dequeue(void *instance, GDBusMethodInvocation *invocation, unsigned handle, void *user_data) { ThumbnailerSpecializedThumbnailer1 *skeleton = instance; g_info("Received Dequeue(handle=%u) request", handle); @@ -69,8 +68,8 @@ static void on_bus_acquired(GDBusConnection *connection, const char *name, void 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); + g_error("Error exporting interface \"%s\" at \"%s\": %s", g_dbus_interface_skeleton_get_info(interface)->name, + object_path, error->message); // NOTREACHED } } @@ -112,13 +111,12 @@ int main(int argc, char const *argv[]) // Create the task queue *before* starting to accept tasks from D-Bus. init_tasks(); // Likewise, create the main loop before then, so it can be aborted even before entering it. - main_loop = g_main_loop_new(NULL, FALSE); + 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 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); diff --git a/XdgThumbnailer/main.h b/XdgThumbnailer/main.h index 76a32c0c2..846a013ae 100644 --- a/XdgThumbnailer/main.h +++ b/XdgThumbnailer/main.h @@ -2,12 +2,12 @@ // As defined in the thumbnailer spec. enum ErrorCode { - ERROR_UNKNOWN_SCHEME_OR_MIME, - ERROR_SPECIALIZED_THUMBNAILER_CONNECTION_FAILED, // Not applicable. - ERROR_INVALID_DATA, // Any file can be decoded as a GB ROM, apparently! - ERROR_THUMBNAIILING_THUMBNAIL, // We defer checking this to the generic thumbnailer. - ERROR_COULD_NOT_WRITE, - ERROR_UNSUPPORTED_FLAVOR, + ERROR_UNKNOWN_SCHEME_OR_MIME, + ERROR_SPECIALIZED_THUMBNAILER_CONNECTION_FAILED, // Not applicable. + ERROR_INVALID_DATA, // Any file can be decoded as a GB ROM, apparently! + ERROR_THUMBNAIILING_THUMBNAIL, // We defer checking this to the generic thumbnailer. + ERROR_COULD_NOT_WRITE, + ERROR_UNSUPPORTED_FLAVOR, }; struct _ThumbnailerSpecializedThumbnailer1; diff --git a/XdgThumbnailer/tasks.c b/XdgThumbnailer/tasks.c index b99d3b6b0..e7a489432 100644 --- a/XdgThumbnailer/tasks.c +++ b/XdgThumbnailer/tasks.c @@ -2,6 +2,7 @@ #include #include +#include #define URGENT_FLAG (1u << (sizeof(unsigned) * CHAR_BIT - 1)) // The compiler should warn if this shift is out of range. @@ -16,7 +17,7 @@ static struct Tasks urgent_tasks, tasks; static void init_task_list(struct Tasks *task_list) { g_rw_lock_init(&task_list->lock); - task_list->tasks = g_array_new(FALSE, FALSE, sizeof(GCancellable *)); + task_list->tasks = g_array_new(false, false, sizeof(GCancellable *)); } void init_tasks(void) { @@ -24,7 +25,8 @@ void init_tasks(void) init_task_list(&tasks); } -static void cleanup_task_list(struct Tasks *task_list) { +static void cleanup_task_list(struct Tasks *task_list) +{ // TODO: wait for the remaining tasks to end? g_rw_lock_clear(&task_list->lock); g_array_unref(task_list->tasks); @@ -70,8 +72,7 @@ struct NewTaskInfo new_task(gboolean is_urgent) g_assert_cmpuint(index, !=, 0); g_assert_cmpuint(index, <, URGENT_FLAG); - return (struct NewTaskInfo){.handle = is_urgent ? (index | URGENT_FLAG) : index, - .cancellable = cancellable}; + return (struct NewTaskInfo){.handle = is_urgent ? (index | URGENT_FLAG) : index, .cancellable = cancellable}; } void cancel_task(unsigned handle) diff --git a/XdgThumbnailer/tasks.h b/XdgThumbnailer/tasks.h index ea050af8c..2d462145b 100644 --- a/XdgThumbnailer/tasks.h +++ b/XdgThumbnailer/tasks.h @@ -1,7 +1,7 @@ #pragma once -#include #include +#include void init_tasks(void); void cleanup_tasks(void); diff --git a/XdgThumbnailer/thumbnail.c b/XdgThumbnailer/thumbnail.c index e1ce61fa8..85b63e36b 100644 --- a/XdgThumbnailer/thumbnail.c +++ b/XdgThumbnailer/thumbnail.c @@ -1,27 +1,22 @@ #include "thumbnail.h" +#include #include #include +#include #include +#include #include #include "emulate.h" #include "main.h" #include "tasks.h" -#define THUMBNAILING_ERROR_DOMAIN (g_quark_from_static_string("thumbnailing")) - -/* --- */ +#define DMG_ONLY_RESOURCE_PATH "/thumbnailer/CartridgeTemplate.png" +#define DUAL_RESOURCE_PATH "/thumbnailer/UniversalCartridgeTemplate.png" +#define CGB_ONLY_RESOURCE_PATH "/thumbnailer/ColorCartridgeTemplate.png" -enum CartridgeType { - CART_DMG_ONLY, - CART_DUAL, - CART_CGB_ONLY, -}; - -void load_cartridge_images(void) { - // TODO -} +#define THUMBNAILING_ERROR_DOMAIN (g_quark_from_static_string("thumbnailing")) /* --- */ @@ -39,33 +34,88 @@ static void destroy_task_data(void *data) g_slice_free(struct TaskData, task_data); } +char const *mime_type(enum FileKind kind) +{ + switch (kind) { + case KIND_GB: + return "application/x-gameboy-rom"; + case KIND_GBC: + return "application/x-gameboy-color-rom"; + case KIND_ISX: + return "application/x-gameboy-isx"; + } +} + /* --- */ -static void generate_thumbnail(GTask *task, void *source_object, void *data, - GCancellable *cancellable) +#define GB_SCREEN_WIDTH 160 +#define GB_SCREEN_HEIGHT 144 + +static void generate_thumbnail(GTask *task, void *source_object, void *data, GCancellable *cancellable) { struct TaskData *task_data = data; - uint32_t screen[160 * 144]; - unsigned cgb_flag = emulate(task_data->kind, (unsigned char const *)task_data->contents, - task_data->length, screen); + uint32_t screen_raw[GB_SCREEN_WIDTH * GB_SCREEN_HEIGHT]; + unsigned cgb_flag = + emulate(task_data->kind, (unsigned char const *)task_data->contents, task_data->length, screen_raw); + + // Generate the thumbnail from `screen_raw` and `cgb_flag`. + + // `screen_raw` is properly formatted for this operation; see the comment in `rgb_encode` for a + // discussion of why and how. + GdkPixbuf *screen = gdk_pixbuf_new_from_data((uint8_t *)screen_raw, GDK_COLORSPACE_RGB, + true, // Yes, we have alpha! + 8, // bpp + GB_SCREEN_WIDTH, GB_SCREEN_HEIGHT, // Size. + GB_SCREEN_WIDTH * sizeof(*screen_raw), // Row stride. + NULL, NULL); // Do not free the buffer. + // Scale the screen and position it in the appropriate place for compositing the cartridge templates. + GdkPixbuf *scaled_screen = gdk_pixbuf_new(GDK_COLORSPACE_RGB, true, 8, 1024, 1024); + gdk_pixbuf_scale(screen, scaled_screen, 192, 298, // Match the displacement below. + GB_SCREEN_WIDTH * 4, GB_SCREEN_HEIGHT * 4, // Cropping the scaled rectangle... + 192, 298, // Displace the scaled screen so it lines up with the template. + 4, 4, // Scaling factors. + GDK_INTERP_NEAREST); + g_object_unref(screen); - // Generate the thumbnail from `screen` and `cgb_flag`. - enum CartridgeType type; + GError *error = NULL; + GdkPixbuf *template; switch (cgb_flag) { - case 0xC0: - type = CART_CGB_ONLY; - break; - case 0x80: - type = CART_DUAL; - break; - default: - type = CART_DMG_ONLY; - break; + case 0xC0: + template = gdk_pixbuf_new_from_resource(CGB_ONLY_RESOURCE_PATH, &error); + break; + case 0x80: + template = gdk_pixbuf_new_from_resource(DUAL_RESOURCE_PATH, &error); + break; + default: + template = gdk_pixbuf_new_from_resource(DMG_ONLY_RESOURCE_PATH, &error); + break; } - (void)type; - - g_task_return_boolean(task, TRUE); + g_assert_no_error(error); + g_assert_cmpint(gdk_pixbuf_get_width(template), ==, 1024); + g_assert_cmpint(gdk_pixbuf_get_height(template), ==, 1024); + gdk_pixbuf_composite(template, // Source. + scaled_screen, // Destination. + 0, 0, // Match the displacement below. + 1024, 1024, // Crop of the scaled rectangle. + 0, 0, // Displacement of the scaled rectangle. + 1, 1, // Scaling factors. + GDK_INTERP_NEAREST, // Doesn't really matter, but should be a little faster. + 255); // Blending factor of the source. + g_object_unref(template); + + char file_size[sizeof(G_STRINGIFY(SIZE_MAX))]; + sprintf(file_size, "%zu", task_data->length); + // TODO: proper file name + gdk_pixbuf_save(scaled_screen, "/tmp/output.png", "png", &error, // "Base" parameters. + "tEXt::Thumb::URI", g_task_get_name(task), // URI of the file being thumbnailed. + "tEXt::Thumb::MTime", "", // TODO + "tEXt::Thumb::Size", file_size, // Size (in bytes) of the file being thumbnailed. + "tEXt::Thumb::Mimetype", mime_type(task_data->kind), // MIME type of the file being thumbnailed. + NULL); + g_object_unref(scaled_screen); + + g_task_return_boolean(task, true); g_object_unref(task); } @@ -116,16 +166,15 @@ static void on_thumbnailing_end(GObject *source_object, GAsyncResult *res, void } else if (!g_cancellable_is_cancelled(g_task_get_cancellable(task))) { // If the task was cancelled, do not emit an error response. - g_signal_emit_by_name(thumbnailer_interface, "error", handle, uri, error->code, - error->message); + g_signal_emit_by_name(thumbnailer_interface, "error", handle, uri, error->code, error->message); } g_signal_emit_by_name(thumbnailer_interface, "finished", handle); finished_task(handle); } -void start_thumbnailing(unsigned handle, GCancellable *cancellable, gboolean is_urgent, - char const *uri, char const *mime_type) +void start_thumbnailing(unsigned handle, GCancellable *cancellable, gboolean is_urgent, char const *uri, + char const *mime_type) { g_signal_emit_by_name(thumbnailer_interface, "started", handle); diff --git a/XdgThumbnailer/thumbnail.h b/XdgThumbnailer/thumbnail.h index 8880adb71..5759d38d0 100644 --- a/XdgThumbnailer/thumbnail.h +++ b/XdgThumbnailer/thumbnail.h @@ -1,7 +1,7 @@ #pragma once -#include #include +#include -void start_thumbnailing(unsigned handle, GCancellable *cancellable, gboolean is_urgent, - char const *uri, char const *mime_type); +void start_thumbnailing(unsigned handle, GCancellable *cancellable, gboolean is_urgent, char const *uri, + char const *mime_type); From c6103d23fa753f446d19ebaca893f43c0eba594f Mon Sep 17 00:00:00 2001 From: ISSOtm Date: Sat, 29 Jun 2024 22:26:49 +0200 Subject: [PATCH 06/22] Address review comments --- Makefile | 20 ++++++++++++++++---- XdgThumbnailer/emulate.c | 22 ---------------------- XdgThumbnailer/emulate.h | 3 --- XdgThumbnailer/main.c | 5 ++--- XdgThumbnailer/thumbnail.c | 32 ++++++++++++++++++-------------- 5 files changed, 36 insertions(+), 46 deletions(-) diff --git a/Makefile b/Makefile index 113aba8e0..e3c5f3224 100644 --- a/Makefile +++ b/Makefile @@ -205,19 +205,23 @@ SDL_LDFLAGS += -lopenal endif SDL_AUDIO_DRIVERS += openal endif -else +else # ifneq ($(PKG_CONFIG),) SDL_CFLAGS := $(shell $(PKG_CONFIG) --cflags sdl2) SDL_LDFLAGS := $(shell $(PKG_CONFIG) --libs sdl2) -lpthread # Allow OpenAL to be disabled even if the development libraries are available ifneq ($(ENABLE_OPENAL),0) -ifeq ($(shell $(PKG_CONFIG) --exists openal && echo 0),0) +ifneq ($(shell $(PKG_CONFIG) --exists openal && echo 0),) SDL_CFLAGS += $(shell $(PKG_CONFIG) --cflags openal) -DENABLE_OPENAL SDL_LDFLAGS += $(shell $(PKG_CONFIG) --libs openal) SDL_AUDIO_DRIVERS += openal endif endif +ifneq ($(shell pkg-config --exists gio-2.0 || echo 0),) +GIO_CFLAGS = $(error The Gio library could not be found) +GIO_LDFLAGS = $(error The Gio library could not be found) +else 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) @@ -225,10 +229,16 @@ GIO_CFLAGS += -DG_ENABLE_DEBUG else GIO_CFLAGS += -DG_DISABLE_ASSERT endif +endif +ifneq ($(shell pkg-config --exists gdk-pixbuf-2.0 || echo 0),) +GDK_PIXBUF_CFLAGS = $(error The Gdk-Pixbuf library could not be found) +GDK_PIXBUF_LDFLAGS = $(error The Gdk-Pixbuf library could not be found) +else GDK_PIXBUF_CFLAGS := $(shell $(PKG_CONFIG) --cflags gdk-pixbuf-2.0) GDK_PIXBUF_LDFLAGS := $(shell $(PKG_CONFIG) --libs gdk-pixbuf-2.0) endif +endif ifeq (,$(PKG_CONFIG)) GL_LDFLAGS := -lGL @@ -656,12 +666,12 @@ $(BIN)/SDL/background.bmp: SDL/background.bmp $(BIN)/SDL/Shaders: Shaders -@$(MKDIR) -p $@ - cp -rf $< $@ + cp -rfT $< $@ touch $@ $(BIN)/SDL/Palettes: Misc/Palettes -@$(MKDIR) -p $@ - cp -rf $< $@ + cp -rfT $< $@ touch $@ # Boot ROMs @@ -698,6 +708,8 @@ libretro: # If you somehow find a reasonable way to make associate an icon with an extension in this dumpster # fire of a desktop environment, open an issue or a pull request ifneq ($(FREEDESKTOP),) +all: xdg-thumbnailer + ICON_NAMES := apps/sameboy mimetypes/x-gameboy-rom mimetypes/x-gameboy-color-rom ICON_SIZES := 16x16 32x32 64x64 128x128 256x256 512x512 ICONS := $(foreach name,$(ICON_NAMES), $(foreach size,$(ICON_SIZES),$(DESTDIR)$(PREFIX)/share/icons/hicolor/$(size)/$(name).png)) diff --git a/XdgThumbnailer/emulate.c b/XdgThumbnailer/emulate.c index 86a77fec1..2ccf63545 100644 --- a/XdgThumbnailer/emulate.c +++ b/XdgThumbnailer/emulate.c @@ -12,28 +12,6 @@ #define NB_FRAMES_TO_EMULATE (60 * 10) #define BOOT_ROM_SIZE (0x100 + 0x800) // The two "parts" of it, which are stored contiguously. -static char *boot_rom; - -void load_boot_rom(void) -{ - static char const *boot_rom_path = DATA_DIR "/cgb_boot_fast.bin"; - - size_t length; - GError *error = NULL; - g_file_get_contents(boot_rom_path, &boot_rom, &length, &error); - - if (error) { - g_error("Error loading boot ROM from \"%s\": %s", boot_rom_path, error->message); - // NOTREACHED - } - else if (length != BOOT_ROM_SIZE) { - g_error("Error loading boot ROM from \"%s\": expected to read %d bytes, got %zu", boot_rom_path, BOOT_ROM_SIZE, - length); - // NOTREACHED - } -} - -void unload_boot_rom(void) { g_free(boot_rom); } /* --- */ diff --git a/XdgThumbnailer/emulate.h b/XdgThumbnailer/emulate.h index 480837032..84bbe1ed1 100644 --- a/XdgThumbnailer/emulate.h +++ b/XdgThumbnailer/emulate.h @@ -9,7 +9,4 @@ enum FileKind { KIND_ISX, }; -void load_boot_rom(void); -void unload_boot_rom(void); - unsigned emulate(enum FileKind kind, unsigned char const *rom, size_t rom_size, uint32_t screen[static 160 * 144]); diff --git a/XdgThumbnailer/main.c b/XdgThumbnailer/main.c index 8e3297ec0..98f99a3e4 100644 --- a/XdgThumbnailer/main.c +++ b/XdgThumbnailer/main.c @@ -16,8 +16,8 @@ // 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"; +static char const name_on_bus[] = "com.github.liji32.sameboy.XdgThumbnailer"; +static char const object_path[] = "/com/github/liji32/sameboy/XdgThumbnailer"; ThumbnailerSpecializedThumbnailer1 *thumbnailer_interface = NULL; static unsigned max_nb_worker_threads; @@ -127,7 +127,6 @@ int main(int argc, char const *argv[]) g_info("Waiting for outstanding tasks..."); cleanup_tasks(); // Also waits for any remaining tasks. // "Pedantic" cleanup for Valgrind et al. - unload_boot_rom(); g_main_loop_unref(main_loop); g_bus_unown_name(owner_id); if (thumbnailer_interface) { diff --git a/XdgThumbnailer/thumbnail.c b/XdgThumbnailer/thumbnail.c index 85b63e36b..80cf7f5b3 100644 --- a/XdgThumbnailer/thumbnail.c +++ b/XdgThumbnailer/thumbnail.c @@ -12,12 +12,16 @@ #include "main.h" #include "tasks.h" -#define DMG_ONLY_RESOURCE_PATH "/thumbnailer/CartridgeTemplate.png" -#define DUAL_RESOURCE_PATH "/thumbnailer/UniversalCartridgeTemplate.png" -#define CGB_ONLY_RESOURCE_PATH "/thumbnailer/ColorCartridgeTemplate.png" - #define THUMBNAILING_ERROR_DOMAIN (g_quark_from_static_string("thumbnailing")) +static char const dmg_only_resource_path[] = "/thumbnailer/CartridgeTemplate.png"; +static char const dual_resource_path[] = "/thumbnailer/UniversalCartridgeTemplate.png"; +static char const cgb_only_resource_path[] = "/thumbnailer/ColorCartridgeTemplate.png"; + +static char const gb_mime_type[] = "application/x-gameboy-rom"; +static char const gbc_mime_type[] = "application/x-gameboy-color-rom"; +static char const isx_mime_type[] = "application/x-gameboy-isx"; + /* --- */ struct TaskData { @@ -38,11 +42,11 @@ char const *mime_type(enum FileKind kind) { switch (kind) { case KIND_GB: - return "application/x-gameboy-rom"; + return gb_mime_type; case KIND_GBC: - return "application/x-gameboy-color-rom"; + return gbc_mime_type; case KIND_ISX: - return "application/x-gameboy-isx"; + return isx_mime_type; } } @@ -82,13 +86,13 @@ static void generate_thumbnail(GTask *task, void *source_object, void *data, GCa GdkPixbuf *template; switch (cgb_flag) { case 0xC0: - template = gdk_pixbuf_new_from_resource(CGB_ONLY_RESOURCE_PATH, &error); + template = gdk_pixbuf_new_from_resource(cgb_only_resource_path, &error); break; case 0x80: - template = gdk_pixbuf_new_from_resource(DUAL_RESOURCE_PATH, &error); + template = gdk_pixbuf_new_from_resource(dual_resource_path, &error); break; default: - template = gdk_pixbuf_new_from_resource(DMG_ONLY_RESOURCE_PATH, &error); + template = gdk_pixbuf_new_from_resource(dmg_only_resource_path, &error); break; } g_assert_no_error(error); @@ -109,7 +113,7 @@ static void generate_thumbnail(GTask *task, void *source_object, void *data, GCa // TODO: proper file name gdk_pixbuf_save(scaled_screen, "/tmp/output.png", "png", &error, // "Base" parameters. "tEXt::Thumb::URI", g_task_get_name(task), // URI of the file being thumbnailed. - "tEXt::Thumb::MTime", "", // TODO + // "tEXt::Thumb::MTime", "", // TODO "tEXt::Thumb::Size", file_size, // Size (in bytes) of the file being thumbnailed. "tEXt::Thumb::Mimetype", mime_type(task_data->kind), // MIME type of the file being thumbnailed. NULL); @@ -183,13 +187,13 @@ void start_thumbnailing(unsigned handle, GCancellable *cancellable, gboolean is_ g_task_set_name(task, uri); enum FileKind kind; - if (g_strcmp0(mime_type, "application/x-gameboy-color-rom") == 0) { + if (g_strcmp0(mime_type, gbc_mime_type) == 0) { kind = KIND_GBC; } - else if (g_strcmp0(mime_type, "application/x-gameboy-rom") == 0) { + else if (g_strcmp0(mime_type, gb_mime_type) == 0) { kind = KIND_GB; } - else if (g_strcmp0(mime_type, "application/x-gameboy-isx") == 0) { + else if (g_strcmp0(mime_type, isx_mime_type) == 0) { kind = KIND_ISX; } else { From 323f3e89b7058a8ce1171cac4c58fff4e4c87ebd Mon Sep 17 00:00:00 2001 From: ISSOtm Date: Thu, 4 Jul 2024 11:14:13 +0200 Subject: [PATCH 07/22] Generate thumbnails under the appropriate path --- Makefile | 2 +- XdgThumbnailer/emulate.c | 1 + XdgThumbnailer/main.c | 16 +++- XdgThumbnailer/main.h | 4 + XdgThumbnailer/thumbnail.c | 172 +++++++++++++++++++++++++++++++++---- XdgThumbnailer/thumbnail.h | 5 +- 6 files changed, 180 insertions(+), 20 deletions(-) diff --git a/Makefile b/Makefile index e3c5f3224..8c82a724a 100644 --- a/Makefile +++ b/Makefile @@ -171,7 +171,7 @@ endif # These must come before the -Wno- flags WARNINGS += -Werror -Wall -Wno-unknown-warning -Wno-unknown-warning-option -Wno-missing-braces -WARNINGS += -Wno-nonnull -Wno-unused-result -Wno-multichar -Wno-int-in-bool-context -Wno-format-truncation +WARNINGS += -Wno-nonnull -Wno-unused-result -Wno-multichar -Wno-int-in-bool-context -Wno-format-truncation -Wformat-overflow=2 # Only add this flag if the compiler supports it ifeq ($(shell $(CC) -x c -c $(NULL) -o $(NULL) -Werror -Wpartial-availability 2> $(NULL); echo $$?),0) diff --git a/XdgThumbnailer/emulate.c b/XdgThumbnailer/emulate.c index 2ccf63545..e1abebf27 100644 --- a/XdgThumbnailer/emulate.c +++ b/XdgThumbnailer/emulate.c @@ -85,6 +85,7 @@ unsigned emulate(enum FileKind kind, unsigned char const *rom, size_t rom_size, GB_set_rendering_disabled(&gb, true); GB_set_turbo_mode(&gb, true, true); while (GPOINTER_TO_UINT(GB_get_user_data(&gb))) { + // TODO: handle cancellation GB_run(&gb); } diff --git a/XdgThumbnailer/main.c b/XdgThumbnailer/main.c index 98f99a3e4..e7ffa96bc 100644 --- a/XdgThumbnailer/main.c +++ b/XdgThumbnailer/main.c @@ -8,8 +8,8 @@ #include #include #include +#include -#include "emulate.h" #include "tasks.h" #include "thumbnail.h" @@ -22,6 +22,8 @@ static char const object_path[] = "/com/github/liji32/sameboy/XdgThumbnailer"; ThumbnailerSpecializedThumbnailer1 *thumbnailer_interface = NULL; static unsigned max_nb_worker_threads; +pid_t pid; + static gboolean handle_queue(void *instance, GDBusMethodInvocation *invocation, char const *uri, char const *mime_type, char const *flavor, gboolean urgent, void *user_data) { @@ -31,7 +33,7 @@ static gboolean handle_queue(void *instance, GDBusMethodInvocation *invocation, g_assert(skeleton == thumbnailer_interface); struct NewTaskInfo task_info = new_task(urgent); - start_thumbnailing(task_info.handle, task_info.cancellable, urgent, uri, mime_type); + start_thumbnailing(task_info.handle, task_info.cancellable, urgent, uri, mime_type, flavor); thumbnailer_specialized_thumbnailer1_complete_queue(skeleton, invocation, task_info.handle); return G_DBUS_METHOD_INVOCATION_HANDLED; @@ -104,11 +106,18 @@ static gboolean handle_sigterm(void *user_data) return G_SOURCE_CONTINUE; // Do not remove this source ourselves, let the post-main loop do so. } +/* --- */ + int main(int argc, char const *argv[]) { + pid = getpid(); + + locate_and_create_thumbnail_dir(); + max_nb_worker_threads = g_get_num_processors(); // unsigned active_worker_threads = 0; - // Create the task queue *before* starting to accept tasks from D-Bus. + + // Create the task queue *before* starting to accept tasks from D-Bus. init_tasks(); // Likewise, create the main loop before then, so it can be aborted even before entering it. main_loop = g_main_loop_new(NULL, false); @@ -133,5 +142,6 @@ int main(int argc, char const *argv[]) g_dbus_interface_skeleton_unexport(G_DBUS_INTERFACE_SKELETON(thumbnailer_interface)); g_object_unref(thumbnailer_interface); } + free_thumbnail_dir_path(); return 0; } diff --git a/XdgThumbnailer/main.h b/XdgThumbnailer/main.h index 846a013ae..d9898e6f1 100644 --- a/XdgThumbnailer/main.h +++ b/XdgThumbnailer/main.h @@ -1,5 +1,7 @@ #pragma once +#include + // As defined in the thumbnailer spec. enum ErrorCode { ERROR_UNKNOWN_SCHEME_OR_MIME, @@ -12,3 +14,5 @@ enum ErrorCode { struct _ThumbnailerSpecializedThumbnailer1; extern struct _ThumbnailerSpecializedThumbnailer1 *thumbnailer_interface; + +extern pid_t pid; diff --git a/XdgThumbnailer/thumbnail.c b/XdgThumbnailer/thumbnail.c index 80cf7f5b3..b8f588d73 100644 --- a/XdgThumbnailer/thumbnail.c +++ b/XdgThumbnailer/thumbnail.c @@ -1,14 +1,20 @@ #include "thumbnail.h" +#include #include #include +#include #include +#include #include #include #include #include +#include +#include #include "emulate.h" +#include "glibconfig.h" #include "main.h" #include "tasks.h" @@ -24,10 +30,82 @@ static char const isx_mime_type[] = "application/x-gameboy-isx"; /* --- */ +enum ThumbnailFlavor { + FLAVOR_NORMAL, // 128×128. + FLAVOR_LARGE, // 256×256. + FLAVOR_X_LARGE, // 512×512. + FLAVOR_XX_LARGE, // 1024×1024. +}; + +static char const flavor_dir_names[][9] = { + [FLAVOR_NORMAL] = "normal", + [FLAVOR_LARGE] = "large", + [FLAVOR_X_LARGE] = "x-large", + [FLAVOR_XX_LARGE] = "xx-large", +}; + +static GPathBuf thumbnail_dir_path; + +static __attribute__((returns_nonnull)) char const *get_home_path(void) +{ + char const *home = g_getenv("HOME"); // Statically allocated, so no need to free. + if (home != NULL) return home; + + // This is pushing it, but let's give it one last shot. + uid_t uid = geteuid(); + struct passwd *passwd_entry; + do { + errno = 0; // It may be left alone if no such entry can be found. + passwd_entry = getpwuid(uid); // Statically allocated, so no need to free. + } while (!passwd_entry && errno == EINTR); + if (passwd_entry == NULL) { + g_error("`XDG_CACHE_HOME` is unset, and failed to get the path to user %ju's home: %s", (uintmax_t)uid, + strerror(errno)); + // NOTREACHED + } + return passwd_entry->pw_dir; +} + +void locate_and_create_thumbnail_dir(void) +{ + // Compute the path to the thumbnail directory. + char const *cache_home = g_getenv("XDG_CACHE_HOME"); // Statically allocated, so no need to free. + if (cache_home != NULL) { + g_path_buf_init_from_path(&thumbnail_dir_path, cache_home); + } + else { + char const *home = get_home_path(); + g_path_buf_init_from_path(&thumbnail_dir_path, home); + g_path_buf_push(&thumbnail_dir_path, ".cache"); + } + g_path_buf_push(&thumbnail_dir_path, "thumbnails"); + + // Create the thumbnail directories if they don't already exist. + for (size_t i = 0; i < G_N_ELEMENTS(flavor_dir_names); ++i) { + g_path_buf_push(&thumbnail_dir_path, flavor_dir_names[i]); + char *path = g_path_buf_to_path(&thumbnail_dir_path); + + if (g_mkdir_with_parents(path, S_IRWXU) != 0) { // The permissions are mandated by the thumbnail spec. + g_error("Failed to create thumbnail cache directory at \"%s\": %s", path, strerror(errno)); + // NOTREACHED + } + + g_free(path); + // Restore the GPathBuf to its original state (pointing at the thumbnail cache's root dir). + g_path_buf_pop(&thumbnail_dir_path); + } + // ...thus, we end with the path buf pointing to the root dir, which is what the rest of this module expects. +} + +void free_thumbnail_dir_path(void) { g_path_buf_clear(&thumbnail_dir_path); } + +/* --- */ + struct TaskData { char *contents; size_t length; enum FileKind kind; + enum ThumbnailFlavor flavor; }; static void destroy_task_data(void *data) @@ -38,7 +116,7 @@ static void destroy_task_data(void *data) g_slice_free(struct TaskData, task_data); } -char const *mime_type(enum FileKind kind) +__attribute__((returns_nonnull)) char const *mime_type(enum FileKind kind) { switch (kind) { case KIND_GB: @@ -48,6 +126,7 @@ char const *mime_type(enum FileKind kind) case KIND_ISX: return isx_mime_type; } + __builtin_unreachable(); } /* --- */ @@ -58,6 +137,8 @@ char const *mime_type(enum FileKind kind) static void generate_thumbnail(GTask *task, void *source_object, void *data, GCancellable *cancellable) { struct TaskData *task_data = data; + char const *uri = g_task_get_name(task); + char *md5 = g_compute_checksum_for_string(G_CHECKSUM_MD5, uri, -1); uint32_t screen_raw[GB_SCREEN_WIDTH * GB_SCREEN_HEIGHT]; unsigned cgb_flag = @@ -68,15 +149,17 @@ static void generate_thumbnail(GTask *task, void *source_object, void *data, GCa // `screen_raw` is properly formatted for this operation; see the comment in `rgb_encode` for a // discussion of why and how. GdkPixbuf *screen = gdk_pixbuf_new_from_data((uint8_t *)screen_raw, GDK_COLORSPACE_RGB, - true, // Yes, we have alpha! - 8, // bpp - GB_SCREEN_WIDTH, GB_SCREEN_HEIGHT, // Size. - GB_SCREEN_WIDTH * sizeof(*screen_raw), // Row stride. - NULL, NULL); // Do not free the buffer. + true, // Yes, we have alpha! + 8, // bpp + GB_SCREEN_WIDTH, GB_SCREEN_HEIGHT, // Size. + GB_SCREEN_WIDTH * sizeof(screen_raw[0]), // Row stride. + NULL, NULL); // Do not free the buffer. // Scale the screen and position it in the appropriate place for compositing the cartridge templates. GdkPixbuf *scaled_screen = gdk_pixbuf_new(GDK_COLORSPACE_RGB, true, 8, 1024, 1024); - gdk_pixbuf_scale(screen, scaled_screen, 192, 298, // Match the displacement below. - GB_SCREEN_WIDTH * 4, GB_SCREEN_HEIGHT * 4, // Cropping the scaled rectangle... + gdk_pixbuf_scale(screen, // Source. + scaled_screen, // Destination. + 192, 298, // Match the displacement below. + GB_SCREEN_WIDTH * 4, GB_SCREEN_HEIGHT * 4, // How the scaled rectangle should be cropped. 192, 298, // Displace the scaled screen so it lines up with the template. 4, 4, // Scaling factors. GDK_INTERP_NEAREST); @@ -105,19 +188,56 @@ static void generate_thumbnail(GTask *task, void *source_object, void *data, GCa 0, 0, // Displacement of the scaled rectangle. 1, 1, // Scaling factors. GDK_INTERP_NEAREST, // Doesn't really matter, but should be a little faster. - 255); // Blending factor of the source. + 255); // Blending factor of the source onto the destination. g_object_unref(template); + GPathBuf *output_path_buf = g_path_buf_copy(&thumbnail_dir_path); + g_path_buf_push(output_path_buf, flavor_dir_names[task_data->flavor]); + g_assert_cmpuint(strlen(md5), ==, 32); + // The buffer's size is checked by the compiler (`-Wformat-overflow=2`). + char temp_file_name[1 + 32 + 1 + 20 + 1 + 10 + 4 + 1]; // (".%s_%lu_%u.png", md5, pid, handle). + temp_file_name[0] = '.'; + memcpy(&temp_file_name[1], md5, 32); + sprintf(&temp_file_name[1 + 32], "_%lu_%u.png", + pid + 0lu, // Promote the `pid_t` to `long int` if possible; `-Wformat` will warn otherwise. + GPOINTER_TO_UINT(g_task_get_task_data(task))); + g_path_buf_push(output_path_buf, temp_file_name); + char *temp_file_path = g_path_buf_to_path(output_path_buf); + + g_debug("Saving pixel buf for \"%s\" to \"%s\"", g_task_get_name(task), temp_file_path); char file_size[sizeof(G_STRINGIFY(SIZE_MAX))]; sprintf(file_size, "%zu", task_data->length); - // TODO: proper file name - gdk_pixbuf_save(scaled_screen, "/tmp/output.png", "png", &error, // "Base" parameters. - "tEXt::Thumb::URI", g_task_get_name(task), // URI of the file being thumbnailed. + gdk_pixbuf_save(scaled_screen, temp_file_path, "png", &error, // "Base" parameters. + "tEXt::Thumb::URI", uri, // URI of the file being thumbnailed. // "tEXt::Thumb::MTime", "", // TODO - "tEXt::Thumb::Size", file_size, // Size (in bytes) of the file being thumbnailed. + "tEXt::Thumb::Size", file_size, // Size (in bytes) of the file being thumbnailed. "tEXt::Thumb::Mimetype", mime_type(task_data->kind), // MIME type of the file being thumbnailed. NULL); g_object_unref(scaled_screen); + if (error) { + g_path_buf_free(output_path_buf); + g_free(temp_file_path); + + g_task_return_new_error(task, THUMBNAILING_ERROR_DOMAIN, ERROR_COULD_NOT_WRITE, "Failed to write image: %s", error->message); + g_error_free(error); + g_object_unref(task); + return; + } + + // Trim off all of the "temporary" parts. + memcpy(&temp_file_name[1 + 32], ".png", sizeof(".png")); + g_path_buf_set_filename(output_path_buf, &temp_file_name[1]); + char *output_path = g_path_buf_free_to_path(output_path_buf); + g_debug("Moving thumbnail for \"%s\" from \"%s\" to \"%s\"", g_task_get_name(task), temp_file_path, output_path); + int rename_ret = rename(temp_file_path, output_path); + int rename_errno = errno; + g_free(temp_file_path); + g_free(output_path); + if (rename_ret != 0) { + g_task_return_new_error(task, THUMBNAILING_ERROR_DOMAIN, ERROR_COULD_NOT_WRITE, "Failed to rename image from temp path: %s", strerror(rename_errno)); + g_object_unref(task); + return; + } g_task_return_boolean(task, true); g_object_unref(task); @@ -139,6 +259,7 @@ static void on_file_ready(GObject *source_object, GAsyncResult *res, void *user_ if (error) { g_task_return_new_error(task, THUMBNAILING_ERROR_DOMAIN, ERROR_UNKNOWN_SCHEME_OR_MIME, "Failed to load URI \"%s\": %s", uri, error->message); + g_error_free(error); g_object_unref(task); return; } @@ -178,7 +299,7 @@ static void on_thumbnailing_end(GObject *source_object, GAsyncResult *res, void } void start_thumbnailing(unsigned handle, GCancellable *cancellable, gboolean is_urgent, char const *uri, - char const *mime_type) + char const *mime_type, char const *flavor_name) { g_signal_emit_by_name(thumbnailer_interface, "started", handle); @@ -198,7 +319,27 @@ void start_thumbnailing(unsigned handle, GCancellable *cancellable, gboolean is_ } else { g_task_return_new_error(task, THUMBNAILING_ERROR_DOMAIN, ERROR_UNKNOWN_SCHEME_OR_MIME, - "Unsupported MIME type %s", mime_type); + "Unsupported MIME type \"%s\"", mime_type); + g_object_unref(task); + return; + } + + enum ThumbnailFlavor flavor; + if (g_strcmp0(flavor_name, "normal") == 0) { + flavor = FLAVOR_NORMAL; + } + else if (g_strcmp0(flavor_name, "large") == 0) { + flavor = FLAVOR_LARGE; + } + else if (g_strcmp0(flavor_name, "x-large") == 0) { + flavor = FLAVOR_X_LARGE; + } + else if (g_strcmp0(flavor_name, "xx-large") == 0) { + flavor = FLAVOR_XX_LARGE; + } + else { + g_task_return_new_error(task, THUMBNAILING_ERROR_DOMAIN, ERROR_UNSUPPORTED_FLAVOR, + "Unsupported thumbnail size/flavor \"%s\"", flavor_name); g_object_unref(task); return; } @@ -206,6 +347,7 @@ void start_thumbnailing(unsigned handle, GCancellable *cancellable, gboolean is_ struct TaskData *task_data = g_slice_new(struct TaskData); task_data->contents = NULL; task_data->kind = kind; + task_data->flavor = flavor; g_task_set_task_data(task, task_data, destroy_task_data); GFile *file = g_file_new_for_uri(uri); diff --git a/XdgThumbnailer/thumbnail.h b/XdgThumbnailer/thumbnail.h index 5759d38d0..1112a8331 100644 --- a/XdgThumbnailer/thumbnail.h +++ b/XdgThumbnailer/thumbnail.h @@ -3,5 +3,8 @@ #include #include +void locate_and_create_thumbnail_dir(void); +void free_thumbnail_dir_path(void); + void start_thumbnailing(unsigned handle, GCancellable *cancellable, gboolean is_urgent, char const *uri, - char const *mime_type); + char const *mime_type, char const *flavor_name); From 4cc34f97ecc37c8960b06372ec4a1a4153a79c56 Mon Sep 17 00:00:00 2001 From: ISSOtm Date: Thu, 4 Jul 2024 20:02:51 +0200 Subject: [PATCH 08/22] Scrap all of that, and use the undocumented spec that everyone actually uses --- Makefile | 10 +- XdgThumbnailer/emulate.c | 36 +-- XdgThumbnailer/emulate.h | 10 +- XdgThumbnailer/interface.xml | 32 --- XdgThumbnailer/main.c | 221 ++++++++---------- XdgThumbnailer/main.h | 18 -- XdgThumbnailer/sameboy.thumbnailer | 4 + XdgThumbnailer/tasks.c | 103 --------- XdgThumbnailer/tasks.h | 15 -- XdgThumbnailer/thumbnail.c | 355 ----------------------------- XdgThumbnailer/thumbnail.h | 10 - 11 files changed, 131 insertions(+), 683 deletions(-) delete mode 100644 XdgThumbnailer/interface.xml delete mode 100644 XdgThumbnailer/main.h create mode 100644 XdgThumbnailer/sameboy.thumbnailer delete mode 100644 XdgThumbnailer/tasks.c delete mode 100644 XdgThumbnailer/tasks.h delete mode 100644 XdgThumbnailer/thumbnail.c delete mode 100644 XdgThumbnailer/thumbnail.h diff --git a/Makefile b/Makefile index 8c82a724a..fc07385e4 100644 --- a/Makefile +++ b/Makefile @@ -222,8 +222,8 @@ ifneq ($(shell pkg-config --exists gio-2.0 || echo 0),) GIO_CFLAGS = $(error The Gio library could not be found) GIO_LDFLAGS = $(error The Gio library could not be found) else -GIO_CFLAGS := $(shell $(PKG_CONFIG) --cflags gio-2.0) -DG_LOG_USE_STRUCTURED -GIO_LDFLAGS := $(shell $(PKG_CONFIG) --libs gio-2.0) +GIO_CFLAGS := $(shell $(PKG_CONFIG) --cflags gio-unix-2.0) -DG_LOG_USE_STRUCTURED +GIO_LDFLAGS := $(shell $(PKG_CONFIG) --libs gio-unix-2.0) ifeq ($(CONF),debug) GIO_CFLAGS += -DG_ENABLE_DEBUG else @@ -437,8 +437,6 @@ $(OBJ)/SDL/%.c.o: SDL/%.c $(OBJ)/XdgThumbnailer/%.c.o: XdgThumbnailer/%.c -@$(MKDIR) -p $(dir $@) $(CC) $(CFLAGS) $(GIO_CFLAGS) $(GDK_PIXBUF_CFLAGS) -DG_LOG_DOMAIN='"sameboy-thumbnailer"' -c $< -o $@ -# Make sure not to attempt compiling this before generating the interface code. -$(OBJ)/XdgThumbnailer/main.c.o: $(OBJ)/XdgThumbnailer/interface.h # Make sure not to attempt compiling this before generating the resource code. $(OBJ)/XdgThumbnailer/emulate.c.o: $(OBJ)/XdgThumbnailer/resources.h # Silence warnings for this. It is code generated not by us, so we do not want `-Werror` to break @@ -447,10 +445,6 @@ $(OBJ)/XdgThumbnailer/%.c.o: $(OBJ)/XdgThumbnailer/%.c -@$(MKDIR) -p $(dir $@) $(CC) $(CFLAGS) $(GIO_CFLAGS) $(GDK_PIXBUF_CFLAGS) -DG_LOG_DOMAIN='"sameboy-thumbnailer"' -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)/XdgThumbnailer/resources.c $(OBJ)/XdgThumbnailer/resources.h: %: XdgThumbnailer/resources.gresource.xml $(BIN)/BootROMs/cgb_boot_fast.bin -@$(MKDIR) -p $(dir $@) CC=$(CC) glib-compile-resources --dependency-file $@.mk --generate-phony-targets --generate --target $@ $< diff --git a/XdgThumbnailer/emulate.c b/XdgThumbnailer/emulate.c index e1abebf27..4582cc5dc 100644 --- a/XdgThumbnailer/emulate.c +++ b/XdgThumbnailer/emulate.c @@ -1,8 +1,10 @@ #include "emulate.h" -#include #include +#include #include +#include +#include #include "Core/gb.h" @@ -15,15 +17,21 @@ /* --- */ -static char *async_input_callback(GB_gameboy_t *gb) { return NULL; } +static char *async_input_callback(GB_gameboy_t *gb) +{ + (void)gb; + return NULL; +} static void log_callback(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes) { - // Swallow any logs. + (void)gb, (void)string, (void)attributes; // Swallow any logs. } static void vblank_callback(GB_gameboy_t *gb, GB_vblank_type_t type) { + (void)type; // Ignore the type, we use VBlank counting as a kind of pacing (and to avoid tearing). + unsigned nb_frames_left = GPOINTER_TO_UINT(GB_get_user_data(gb)); nb_frames_left--; GB_set_user_data(gb, GUINT_TO_POINTER(nb_frames_left)); @@ -51,45 +59,43 @@ static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) return rgba; } -unsigned emulate(enum FileKind kind, unsigned char const *rom, size_t rom_size, uint32_t screen[static 160 * 144]) +uint8_t emulate(char const *path, uint32_t screen[static GB_SCREEN_WIDTH * GB_SCREEN_HEIGHT]) { GB_gameboy_t gb; GB_init(&gb, GB_MODEL_CGB_E); + char const *last_dot = strrchr(path, '.'); + bool is_isx = last_dot && strcmp(last_dot + 1, "isx") == 0; + if (is_isx ? GB_load_isx(&gb, path) : GB_load_rom(&gb, path)) { + exit(EXIT_FAILURE); + } + GError *error = NULL; GBytes *boot_rom = g_resource_lookup_data(resources_get_resource(), "/thumbnailer/cgb_boot_fast.bin", G_RESOURCE_LOOKUP_FLAGS_NONE, &error); - g_assert_no_error(error); + g_assert_no_error(error); // This shouldn't be able to fail. size_t boot_rom_size; unsigned char const *boot_rom_data = g_bytes_get_data(boot_rom, &boot_rom_size); g_assert_cmpuint(boot_rom_size, ==, BOOT_ROM_SIZE); GB_load_boot_rom_from_buffer(&gb, boot_rom_data, boot_rom_size); g_bytes_unref(boot_rom); - if (kind == KIND_ISX) { - g_assert_not_reached(); // TODO: implement GB_load_isx_from_buffer - } - else { - GB_load_rom_from_buffer(&gb, rom, rom_size); - } - GB_set_user_data(&gb, GUINT_TO_POINTER(NB_FRAMES_TO_EMULATE)); GB_set_vblank_callback(&gb, vblank_callback); GB_set_pixels_output(&gb, screen); GB_set_rgb_encode_callback(&gb, rgb_encode); GB_set_async_input_callback(&gb, async_input_callback); - GB_set_log_callback(&gb, log_callback); + GB_set_log_callback(&gb, log_callback); // Anything bizarre the ROM does during emulation, we don't care about. GB_set_color_correction_mode(&gb, GB_COLOR_CORRECTION_MODERN_BALANCED); GB_set_rendering_disabled(&gb, true); GB_set_turbo_mode(&gb, true, true); while (GPOINTER_TO_UINT(GB_get_user_data(&gb))) { - // TODO: handle cancellation GB_run(&gb); } - unsigned cgb_flag = GB_read_memory(&gb, 0x143) & 0xC0; + int cgb_flag = GB_read_memory(&gb, 0x143) & 0xC0; GB_free(&gb); return cgb_flag; } diff --git a/XdgThumbnailer/emulate.h b/XdgThumbnailer/emulate.h index 84bbe1ed1..f1a67010b 100644 --- a/XdgThumbnailer/emulate.h +++ b/XdgThumbnailer/emulate.h @@ -1,12 +1,8 @@ #pragma once -#include #include -enum FileKind { - KIND_GB, - KIND_GBC, - KIND_ISX, -}; +#define GB_SCREEN_WIDTH 160 +#define GB_SCREEN_HEIGHT 144 -unsigned emulate(enum FileKind kind, unsigned char const *rom, size_t rom_size, uint32_t screen[static 160 * 144]); +uint8_t emulate(char const *path, uint32_t screen[static GB_SCREEN_WIDTH * GB_SCREEN_HEIGHT]); diff --git a/XdgThumbnailer/interface.xml b/XdgThumbnailer/interface.xml deleted file mode 100644 index 33505b4cc..000000000 --- a/XdgThumbnailer/interface.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/XdgThumbnailer/main.c b/XdgThumbnailer/main.c index e7ffa96bc..02b4daa99 100644 --- a/XdgThumbnailer/main.c +++ b/XdgThumbnailer/main.c @@ -1,147 +1,128 @@ -#include "main.h" - -#include -#include -#include +#include +#include #include -#include #include +#include +#include #include #include -#include - -#include "tasks.h" -#include "thumbnail.h" - -// Auto-generated via `gdbus-codegen` from `interface.xml`. -#include "build/obj/XdgThumbnailer/interface.h" -static char const name_on_bus[] = "com.github.liji32.sameboy.XdgThumbnailer"; -static char const object_path[] = "/com/github/liji32/sameboy/XdgThumbnailer"; +#include "emulate.h" -ThumbnailerSpecializedThumbnailer1 *thumbnailer_interface = NULL; -static unsigned max_nb_worker_threads; +static char const dmg_only_resource_path[] = "/thumbnailer/CartridgeTemplate.png"; +static char const dual_resource_path[] = "/thumbnailer/UniversalCartridgeTemplate.png"; +static char const cgb_only_resource_path[] = "/thumbnailer/ColorCartridgeTemplate.png"; -pid_t pid; - -static gboolean handle_queue(void *instance, GDBusMethodInvocation *invocation, char const *uri, char const *mime_type, - char const *flavor, gboolean urgent, void *user_data) +static GdkPixbuf *generate_thumbnail(char const *input_path) { - ThumbnailerSpecializedThumbnailer1 *skeleton = instance; - g_info("Received Queue(uri=\"%s\", mime_type=\"%s\", flavor=\"%s\", urgent=%s) request", uri, mime_type, flavor, - urgent ? "true" : "false"); - g_assert(skeleton == thumbnailer_interface); - - struct NewTaskInfo task_info = new_task(urgent); - start_thumbnailing(task_info.handle, task_info.cancellable, urgent, uri, mime_type, flavor); + uint32_t screen_raw[GB_SCREEN_WIDTH * GB_SCREEN_HEIGHT]; + uint8_t cgb_flag = emulate(input_path, screen_raw); + + // Generate the thumbnail from `screen_raw` and `cgb_flag`. + + // `screen_raw` is properly formatted for this operation; see the comment in `rgb_encode` for a + // discussion of why and how. + GdkPixbuf *screen = gdk_pixbuf_new_from_data((uint8_t *)screen_raw, GDK_COLORSPACE_RGB, + true, // Yes, we have alpha! + 8, // bpp + GB_SCREEN_WIDTH, GB_SCREEN_HEIGHT, // Size. + GB_SCREEN_WIDTH * sizeof(screen_raw[0]), // Row stride. + NULL, NULL); // Do not free the buffer. + // Scale the screen and position it in the appropriate place for compositing the cartridge templates. + GdkPixbuf *scaled_screen = gdk_pixbuf_new(GDK_COLORSPACE_RGB, true, 8, 1024, 1024); + gdk_pixbuf_scale(screen, // Source. + scaled_screen, // Destination. + 192, 298, // Match the displacement below. + GB_SCREEN_WIDTH * 4, GB_SCREEN_HEIGHT * 4, // How the scaled rectangle should be cropped. + 192, 298, // Displace the scaled screen so it lines up with the template. + 4, 4, // Scaling factors. + GDK_INTERP_NEAREST); + g_object_unref(screen); - thumbnailer_specialized_thumbnailer1_complete_queue(skeleton, invocation, task_info.handle); - return G_DBUS_METHOD_INVOCATION_HANDLED; + GError *error = NULL; + GdkPixbuf *template; + switch (cgb_flag) { + case 0xC0: + template = gdk_pixbuf_new_from_resource(cgb_only_resource_path, &error); + break; + case 0x80: + template = gdk_pixbuf_new_from_resource(dual_resource_path, &error); + break; + default: + template = gdk_pixbuf_new_from_resource(dmg_only_resource_path, &error); + break; + } + g_assert_no_error(error); + g_assert_cmpint(gdk_pixbuf_get_width(template), ==, 1024); + g_assert_cmpint(gdk_pixbuf_get_height(template), ==, 1024); + gdk_pixbuf_composite(template, // Source. + scaled_screen, // Destination. + 0, 0, // Match the displacement below. + 1024, 1024, // Crop of the scaled rectangle. + 0, 0, // Displacement of the scaled rectangle. + 1, 1, // Scaling factors. + GDK_INTERP_NEAREST, // Doesn't really matter, but should be a little faster. + 255); // Blending factor of the source onto the destination. + g_object_unref(template); + + return scaled_screen; } -static gboolean handle_dequeue(void *instance, GDBusMethodInvocation *invocation, unsigned handle, void *user_data) +static GdkPixbuf *enforce_max_size(GdkPixbuf *thumbnail, unsigned long max_size) { - ThumbnailerSpecializedThumbnailer1 *skeleton = instance; - g_info("Received Dequeue(handle=%u) request", handle); - g_assert(skeleton == thumbnailer_interface); - - cancel_task(handle); - - return G_DBUS_METHOD_INVOCATION_HANDLED; + g_assert_cmpuint(gdk_pixbuf_get_width(thumbnail), ==, gdk_pixbuf_get_height(thumbnail)); + g_assert_cmpuint(gdk_pixbuf_get_width(thumbnail), ==, 1024); + // This is only a *max* size; don't bother scaling up. + // (This also prevents any overflow errors—notice that the scale function takes `int` size parameters!) + if (max_size > 1024) return thumbnail; + GdkPixbuf *scaled = gdk_pixbuf_scale_simple(thumbnail, max_size, max_size, GDK_INTERP_BILINEAR); + g_object_unref(thumbnail); + return scaled; } -static GMainLoop *main_loop; - -static void on_bus_acquired(GDBusConnection *connection, const char *name, void *user_data) +static void write_thumbnail(GdkPixbuf *thumbnail, char const *output_path) { - g_assert_cmpstr(name, ==, name_on_bus); - (void)user_data; - g_info("Acquired bus"); - - // Create the interface, and hook up callbacks for when its methods are called. - 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. GError *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); + // Intentionally be "not a good citizen": + // - Write directly to the provided path, instead of atomically replacing it with a fully-formed file; + // this is necessary for at least Tumbler (XFCE's thumbnailer daemon), which creates the file **and** keeps the + // returned FD—which keeps pointing to the deleted file... which is still empty! + // - Do not save any metadata to the PNG, since the thumbnailer daemon (again, at least XFCE's, the only one I have + // tested with) appears to read the PNG's pixels, and write a new one with the appropriate metadata. + // (Thank you! Saves me all that work.) + gdk_pixbuf_save(thumbnail, output_path, "png", &error, NULL); if (error) { - g_error("Error exporting interface \"%s\" at \"%s\": %s", g_dbus_interface_skeleton_get_info(interface)->name, - object_path, error->message); + g_error("Failed to save thumbnail: %s", 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) +int main(int argc, char *argv[]) { - g_assert_cmpstr(name, ==, name_on_bus); - (void)user_data; - - if (connection != NULL) { - g_info("Lost the name \"%s\" on the session bus", name); + if (argc != 3 && argc != 4) { + g_error("Usage: %s []", argv[0] ? argv[0] : "sameboy-thumbnailer"); + // NOTREACHED } - else { - g_error("Failed to connect to session bus"); + char const *input_path = argv[1]; + char *output_path = argv[2]; // Gets mutated in-place. + char const *max_size = argv[3]; // May be NULL. + + g_debug("%s -> %s [%s]", input_path, output_path, max_size ? max_size : "(none)"); + + GdkPixbuf *thumbnail = generate_thumbnail(input_path); + if (max_size) { + char *endptr; + errno = 0; + unsigned long size = strtoul(max_size, &endptr, 10); + if (errno != 0 || *max_size == '\0' || *endptr != '\0') { + g_error("Invalid size parameter \"%s\": %s", max_size, strerror(errno == 0 ? EINVAL : errno)); + // NOTREACHED + } + + thumbnail = enforce_max_size(thumbnail, size); } - 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. -} - -/* --- */ + write_thumbnail(thumbnail, output_path); + g_object_unref(thumbnail); -int main(int argc, char const *argv[]) -{ - pid = getpid(); - - locate_and_create_thumbnail_dir(); - - max_nb_worker_threads = g_get_num_processors(); - // unsigned active_worker_threads = 0; - - // Create the task queue *before* starting to accept tasks from D-Bus. - init_tasks(); - // 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); - // This must be done before destroying the main loop. - gboolean removed = g_source_remove(sigterm_source_id); - g_assert(removed); - - g_info("Waiting for outstanding tasks..."); - cleanup_tasks(); // Also waits for any remaining tasks. - // "Pedantic" cleanup for Valgrind et al. - g_main_loop_unref(main_loop); - g_bus_unown_name(owner_id); - if (thumbnailer_interface) { - g_dbus_interface_skeleton_unexport(G_DBUS_INTERFACE_SKELETON(thumbnailer_interface)); - g_object_unref(thumbnailer_interface); - } - free_thumbnail_dir_path(); return 0; } diff --git a/XdgThumbnailer/main.h b/XdgThumbnailer/main.h deleted file mode 100644 index d9898e6f1..000000000 --- a/XdgThumbnailer/main.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include - -// As defined in the thumbnailer spec. -enum ErrorCode { - ERROR_UNKNOWN_SCHEME_OR_MIME, - ERROR_SPECIALIZED_THUMBNAILER_CONNECTION_FAILED, // Not applicable. - ERROR_INVALID_DATA, // Any file can be decoded as a GB ROM, apparently! - ERROR_THUMBNAIILING_THUMBNAIL, // We defer checking this to the generic thumbnailer. - ERROR_COULD_NOT_WRITE, - ERROR_UNSUPPORTED_FLAVOR, -}; - -struct _ThumbnailerSpecializedThumbnailer1; -extern struct _ThumbnailerSpecializedThumbnailer1 *thumbnailer_interface; - -extern pid_t pid; diff --git a/XdgThumbnailer/sameboy.thumbnailer b/XdgThumbnailer/sameboy.thumbnailer new file mode 100644 index 000000000..63be4cfbb --- /dev/null +++ b/XdgThumbnailer/sameboy.thumbnailer @@ -0,0 +1,4 @@ +[Thumbnailer Entry] +TryExec=/home/issotm/SameBoy/build/bin/XdgThumbnailer/sameboy-thumbnailer +Exec=/home/issotm/SameBoy/build/bin/XdgThumbnailer/sameboy-thumbnailer %i %o %s +MimeType=application/x-gameboy-rom;application/x-gameboy-color-rom;application/x-gameboy-isx diff --git a/XdgThumbnailer/tasks.c b/XdgThumbnailer/tasks.c deleted file mode 100644 index e7a489432..000000000 --- a/XdgThumbnailer/tasks.c +++ /dev/null @@ -1,103 +0,0 @@ -#include "tasks.h" - -#include -#include -#include - -#define URGENT_FLAG (1u << (sizeof(unsigned) * CHAR_BIT - 1)) // The compiler should warn if this shift is out of range. - -struct Tasks { - // Note that the lock only applies to the whole array; individual elements may be mutated - // in-place just fine by the readers. - GRWLock lock; - GArray /* of GCancellable* */ *tasks; -}; -static struct Tasks urgent_tasks, tasks; - -static void init_task_list(struct Tasks *task_list) -{ - g_rw_lock_init(&task_list->lock); - task_list->tasks = g_array_new(false, false, sizeof(GCancellable *)); -} -void init_tasks(void) -{ - init_task_list(&urgent_tasks); - init_task_list(&tasks); -} - -static void cleanup_task_list(struct Tasks *task_list) -{ - // TODO: wait for the remaining tasks to end? - g_rw_lock_clear(&task_list->lock); - g_array_unref(task_list->tasks); -} -void cleanup_tasks(void) -{ - cleanup_task_list(&urgent_tasks); - cleanup_task_list(&tasks); -} - -struct NewTaskInfo new_task(gboolean is_urgent) -{ - struct Tasks *task_list = is_urgent ? &urgent_tasks : &tasks; - GCancellable **array = (void *)task_list->tasks->data; - - GCancellable *cancellable = g_cancellable_new(); - - // We may reallocate the array, so we need a writer lock. - g_rw_lock_writer_lock(&task_list->lock); - // First, look for a free slot in the array. - unsigned index = 0; - for (unsigned i = 0; i < task_list->tasks->len; ++i) { - if (array[i] == NULL) { - array[i] = cancellable; - index = i + 1; - goto got_slot; - } - } - // We need to allocate a new slot. - - // Each task list cannot contain 0x7FFFFFFF handles, as otherwise bit 7 cannot differentiate - // between regular and urgent tasks. - // Note that index 0 is invalid, since it's reserved for "no handle", so that's 1 less. - if (task_list->tasks->len == URGENT_FLAG - 2) { - g_object_unref(cancellable); - return (struct NewTaskInfo){.handle = 0}; - } - g_array_append_val(task_list->tasks, cancellable); - index = task_list->tasks->len; // We want the new index *plus one*. -got_slot: - g_rw_lock_writer_unlock(&task_list->lock); - - g_assert_cmpuint(index, !=, 0); - g_assert_cmpuint(index, <, URGENT_FLAG); - - return (struct NewTaskInfo){.handle = is_urgent ? (index | URGENT_FLAG) : index, .cancellable = cancellable}; -} - -void cancel_task(unsigned handle) -{ - struct Tasks *task_list = (handle & URGENT_FLAG) ? &urgent_tasks : &tasks; - - g_rw_lock_reader_lock(&task_list->lock); - GCancellable **slot = &((GCancellable **)task_list->tasks->data)[(handle & ~URGENT_FLAG) - 1]; - GCancellable *cancellable = *slot; - *slot = NULL; - g_rw_lock_reader_unlock(&task_list->lock); - - g_cancellable_cancel(cancellable); - g_object_unref(cancellable); -} - -void finished_task(unsigned handle) -{ - struct Tasks *task_list = (handle & URGENT_FLAG) ? &urgent_tasks : &tasks; - - g_rw_lock_reader_lock(&task_list->lock); - GCancellable **slot = &((GCancellable **)task_list->tasks->data)[(handle & ~URGENT_FLAG) - 1]; - GCancellable *cancellable = *slot; - *slot = NULL; - g_rw_lock_reader_unlock(&task_list->lock); - - g_object_unref(cancellable); -} diff --git a/XdgThumbnailer/tasks.h b/XdgThumbnailer/tasks.h deleted file mode 100644 index 2d462145b..000000000 --- a/XdgThumbnailer/tasks.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include -#include - -void init_tasks(void); -void cleanup_tasks(void); - -struct NewTaskInfo { - unsigned handle; - GCancellable *cancellable; -}; -struct NewTaskInfo new_task(gboolean is_urgent); -void cancel_task(unsigned handle); -void finished_task(unsigned handle); diff --git a/XdgThumbnailer/thumbnail.c b/XdgThumbnailer/thumbnail.c deleted file mode 100644 index b8f588d73..000000000 --- a/XdgThumbnailer/thumbnail.c +++ /dev/null @@ -1,355 +0,0 @@ -#include "thumbnail.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "emulate.h" -#include "glibconfig.h" -#include "main.h" -#include "tasks.h" - -#define THUMBNAILING_ERROR_DOMAIN (g_quark_from_static_string("thumbnailing")) - -static char const dmg_only_resource_path[] = "/thumbnailer/CartridgeTemplate.png"; -static char const dual_resource_path[] = "/thumbnailer/UniversalCartridgeTemplate.png"; -static char const cgb_only_resource_path[] = "/thumbnailer/ColorCartridgeTemplate.png"; - -static char const gb_mime_type[] = "application/x-gameboy-rom"; -static char const gbc_mime_type[] = "application/x-gameboy-color-rom"; -static char const isx_mime_type[] = "application/x-gameboy-isx"; - -/* --- */ - -enum ThumbnailFlavor { - FLAVOR_NORMAL, // 128×128. - FLAVOR_LARGE, // 256×256. - FLAVOR_X_LARGE, // 512×512. - FLAVOR_XX_LARGE, // 1024×1024. -}; - -static char const flavor_dir_names[][9] = { - [FLAVOR_NORMAL] = "normal", - [FLAVOR_LARGE] = "large", - [FLAVOR_X_LARGE] = "x-large", - [FLAVOR_XX_LARGE] = "xx-large", -}; - -static GPathBuf thumbnail_dir_path; - -static __attribute__((returns_nonnull)) char const *get_home_path(void) -{ - char const *home = g_getenv("HOME"); // Statically allocated, so no need to free. - if (home != NULL) return home; - - // This is pushing it, but let's give it one last shot. - uid_t uid = geteuid(); - struct passwd *passwd_entry; - do { - errno = 0; // It may be left alone if no such entry can be found. - passwd_entry = getpwuid(uid); // Statically allocated, so no need to free. - } while (!passwd_entry && errno == EINTR); - if (passwd_entry == NULL) { - g_error("`XDG_CACHE_HOME` is unset, and failed to get the path to user %ju's home: %s", (uintmax_t)uid, - strerror(errno)); - // NOTREACHED - } - return passwd_entry->pw_dir; -} - -void locate_and_create_thumbnail_dir(void) -{ - // Compute the path to the thumbnail directory. - char const *cache_home = g_getenv("XDG_CACHE_HOME"); // Statically allocated, so no need to free. - if (cache_home != NULL) { - g_path_buf_init_from_path(&thumbnail_dir_path, cache_home); - } - else { - char const *home = get_home_path(); - g_path_buf_init_from_path(&thumbnail_dir_path, home); - g_path_buf_push(&thumbnail_dir_path, ".cache"); - } - g_path_buf_push(&thumbnail_dir_path, "thumbnails"); - - // Create the thumbnail directories if they don't already exist. - for (size_t i = 0; i < G_N_ELEMENTS(flavor_dir_names); ++i) { - g_path_buf_push(&thumbnail_dir_path, flavor_dir_names[i]); - char *path = g_path_buf_to_path(&thumbnail_dir_path); - - if (g_mkdir_with_parents(path, S_IRWXU) != 0) { // The permissions are mandated by the thumbnail spec. - g_error("Failed to create thumbnail cache directory at \"%s\": %s", path, strerror(errno)); - // NOTREACHED - } - - g_free(path); - // Restore the GPathBuf to its original state (pointing at the thumbnail cache's root dir). - g_path_buf_pop(&thumbnail_dir_path); - } - // ...thus, we end with the path buf pointing to the root dir, which is what the rest of this module expects. -} - -void free_thumbnail_dir_path(void) { g_path_buf_clear(&thumbnail_dir_path); } - -/* --- */ - -struct TaskData { - char *contents; - size_t length; - enum FileKind kind; - enum ThumbnailFlavor flavor; -}; - -static void destroy_task_data(void *data) -{ - struct TaskData *task_data = data; - - g_free(task_data->contents); - g_slice_free(struct TaskData, task_data); -} - -__attribute__((returns_nonnull)) char const *mime_type(enum FileKind kind) -{ - switch (kind) { - case KIND_GB: - return gb_mime_type; - case KIND_GBC: - return gbc_mime_type; - case KIND_ISX: - return isx_mime_type; - } - __builtin_unreachable(); -} - -/* --- */ - -#define GB_SCREEN_WIDTH 160 -#define GB_SCREEN_HEIGHT 144 - -static void generate_thumbnail(GTask *task, void *source_object, void *data, GCancellable *cancellable) -{ - struct TaskData *task_data = data; - char const *uri = g_task_get_name(task); - char *md5 = g_compute_checksum_for_string(G_CHECKSUM_MD5, uri, -1); - - uint32_t screen_raw[GB_SCREEN_WIDTH * GB_SCREEN_HEIGHT]; - unsigned cgb_flag = - emulate(task_data->kind, (unsigned char const *)task_data->contents, task_data->length, screen_raw); - - // Generate the thumbnail from `screen_raw` and `cgb_flag`. - - // `screen_raw` is properly formatted for this operation; see the comment in `rgb_encode` for a - // discussion of why and how. - GdkPixbuf *screen = gdk_pixbuf_new_from_data((uint8_t *)screen_raw, GDK_COLORSPACE_RGB, - true, // Yes, we have alpha! - 8, // bpp - GB_SCREEN_WIDTH, GB_SCREEN_HEIGHT, // Size. - GB_SCREEN_WIDTH * sizeof(screen_raw[0]), // Row stride. - NULL, NULL); // Do not free the buffer. - // Scale the screen and position it in the appropriate place for compositing the cartridge templates. - GdkPixbuf *scaled_screen = gdk_pixbuf_new(GDK_COLORSPACE_RGB, true, 8, 1024, 1024); - gdk_pixbuf_scale(screen, // Source. - scaled_screen, // Destination. - 192, 298, // Match the displacement below. - GB_SCREEN_WIDTH * 4, GB_SCREEN_HEIGHT * 4, // How the scaled rectangle should be cropped. - 192, 298, // Displace the scaled screen so it lines up with the template. - 4, 4, // Scaling factors. - GDK_INTERP_NEAREST); - g_object_unref(screen); - - GError *error = NULL; - GdkPixbuf *template; - switch (cgb_flag) { - case 0xC0: - template = gdk_pixbuf_new_from_resource(cgb_only_resource_path, &error); - break; - case 0x80: - template = gdk_pixbuf_new_from_resource(dual_resource_path, &error); - break; - default: - template = gdk_pixbuf_new_from_resource(dmg_only_resource_path, &error); - break; - } - g_assert_no_error(error); - g_assert_cmpint(gdk_pixbuf_get_width(template), ==, 1024); - g_assert_cmpint(gdk_pixbuf_get_height(template), ==, 1024); - gdk_pixbuf_composite(template, // Source. - scaled_screen, // Destination. - 0, 0, // Match the displacement below. - 1024, 1024, // Crop of the scaled rectangle. - 0, 0, // Displacement of the scaled rectangle. - 1, 1, // Scaling factors. - GDK_INTERP_NEAREST, // Doesn't really matter, but should be a little faster. - 255); // Blending factor of the source onto the destination. - g_object_unref(template); - - GPathBuf *output_path_buf = g_path_buf_copy(&thumbnail_dir_path); - g_path_buf_push(output_path_buf, flavor_dir_names[task_data->flavor]); - g_assert_cmpuint(strlen(md5), ==, 32); - // The buffer's size is checked by the compiler (`-Wformat-overflow=2`). - char temp_file_name[1 + 32 + 1 + 20 + 1 + 10 + 4 + 1]; // (".%s_%lu_%u.png", md5, pid, handle). - temp_file_name[0] = '.'; - memcpy(&temp_file_name[1], md5, 32); - sprintf(&temp_file_name[1 + 32], "_%lu_%u.png", - pid + 0lu, // Promote the `pid_t` to `long int` if possible; `-Wformat` will warn otherwise. - GPOINTER_TO_UINT(g_task_get_task_data(task))); - g_path_buf_push(output_path_buf, temp_file_name); - char *temp_file_path = g_path_buf_to_path(output_path_buf); - - g_debug("Saving pixel buf for \"%s\" to \"%s\"", g_task_get_name(task), temp_file_path); - char file_size[sizeof(G_STRINGIFY(SIZE_MAX))]; - sprintf(file_size, "%zu", task_data->length); - gdk_pixbuf_save(scaled_screen, temp_file_path, "png", &error, // "Base" parameters. - "tEXt::Thumb::URI", uri, // URI of the file being thumbnailed. - // "tEXt::Thumb::MTime", "", // TODO - "tEXt::Thumb::Size", file_size, // Size (in bytes) of the file being thumbnailed. - "tEXt::Thumb::Mimetype", mime_type(task_data->kind), // MIME type of the file being thumbnailed. - NULL); - g_object_unref(scaled_screen); - if (error) { - g_path_buf_free(output_path_buf); - g_free(temp_file_path); - - g_task_return_new_error(task, THUMBNAILING_ERROR_DOMAIN, ERROR_COULD_NOT_WRITE, "Failed to write image: %s", error->message); - g_error_free(error); - g_object_unref(task); - return; - } - - // Trim off all of the "temporary" parts. - memcpy(&temp_file_name[1 + 32], ".png", sizeof(".png")); - g_path_buf_set_filename(output_path_buf, &temp_file_name[1]); - char *output_path = g_path_buf_free_to_path(output_path_buf); - g_debug("Moving thumbnail for \"%s\" from \"%s\" to \"%s\"", g_task_get_name(task), temp_file_path, output_path); - int rename_ret = rename(temp_file_path, output_path); - int rename_errno = errno; - g_free(temp_file_path); - g_free(output_path); - if (rename_ret != 0) { - g_task_return_new_error(task, THUMBNAILING_ERROR_DOMAIN, ERROR_COULD_NOT_WRITE, "Failed to rename image from temp path: %s", strerror(rename_errno)); - g_object_unref(task); - return; - } - - g_task_return_boolean(task, true); - g_object_unref(task); -} - -// Callback when an async file operation completes. -static void on_file_ready(GObject *source_object, GAsyncResult *res, void *user_data) -{ - GFile *file = G_FILE(source_object); - GTask *task = user_data; - char const *uri = g_task_get_name(task); - g_debug("File \"%s\" is done being read", uri); - struct TaskData *task_data = g_task_get_task_data(task); - - GError *error = NULL; - g_file_load_contents_finish(file, res, &task_data->contents, &task_data->length, NULL, &error); - g_object_unref(file); - - if (error) { - g_task_return_new_error(task, THUMBNAILING_ERROR_DOMAIN, ERROR_UNKNOWN_SCHEME_OR_MIME, - "Failed to load URI \"%s\": %s", uri, error->message); - g_error_free(error); - g_object_unref(task); - return; - } - - if (g_task_return_error_if_cancelled(task)) { - g_object_unref(task); - return; - } - - // TODO: cap the max number of active threads. - g_task_run_in_thread(task, generate_thumbnail); -} - -/* --- */ - -static void on_thumbnailing_end(GObject *source_object, GAsyncResult *res, void *user_data) -{ - // TODO: start a new thread if some task is pending. - - g_assert_null(source_object); // The object that was passed to `g_task_new`. - GTask *task = G_TASK(res); - g_info("Ending thumbnailing for \"%s\"", g_task_get_name(task)); - unsigned handle = GPOINTER_TO_UINT(user_data); - char const *uri = g_task_get_name(task); - - GError *error = NULL; - if (g_task_propagate_boolean(task, &error)) { - g_signal_emit_by_name(thumbnailer_interface, "ready", handle, uri); - } - else if (!g_cancellable_is_cancelled(g_task_get_cancellable(task))) { - // If the task was cancelled, do not emit an error response. - g_signal_emit_by_name(thumbnailer_interface, "error", handle, uri, error->code, error->message); - } - g_signal_emit_by_name(thumbnailer_interface, "finished", handle); - - finished_task(handle); -} - -void start_thumbnailing(unsigned handle, GCancellable *cancellable, gboolean is_urgent, char const *uri, - char const *mime_type, char const *flavor_name) -{ - g_signal_emit_by_name(thumbnailer_interface, "started", handle); - - GTask *task = g_task_new(NULL, cancellable, on_thumbnailing_end, GUINT_TO_POINTER(handle)); - g_task_set_priority(task, is_urgent ? G_PRIORITY_HIGH : G_PRIORITY_DEFAULT); - g_task_set_name(task, uri); - - enum FileKind kind; - if (g_strcmp0(mime_type, gbc_mime_type) == 0) { - kind = KIND_GBC; - } - else if (g_strcmp0(mime_type, gb_mime_type) == 0) { - kind = KIND_GB; - } - else if (g_strcmp0(mime_type, isx_mime_type) == 0) { - kind = KIND_ISX; - } - else { - g_task_return_new_error(task, THUMBNAILING_ERROR_DOMAIN, ERROR_UNKNOWN_SCHEME_OR_MIME, - "Unsupported MIME type \"%s\"", mime_type); - g_object_unref(task); - return; - } - - enum ThumbnailFlavor flavor; - if (g_strcmp0(flavor_name, "normal") == 0) { - flavor = FLAVOR_NORMAL; - } - else if (g_strcmp0(flavor_name, "large") == 0) { - flavor = FLAVOR_LARGE; - } - else if (g_strcmp0(flavor_name, "x-large") == 0) { - flavor = FLAVOR_X_LARGE; - } - else if (g_strcmp0(flavor_name, "xx-large") == 0) { - flavor = FLAVOR_XX_LARGE; - } - else { - g_task_return_new_error(task, THUMBNAILING_ERROR_DOMAIN, ERROR_UNSUPPORTED_FLAVOR, - "Unsupported thumbnail size/flavor \"%s\"", flavor_name); - g_object_unref(task); - return; - } - - struct TaskData *task_data = g_slice_new(struct TaskData); - task_data->contents = NULL; - task_data->kind = kind; - task_data->flavor = flavor; - g_task_set_task_data(task, task_data, destroy_task_data); - - GFile *file = g_file_new_for_uri(uri); - g_file_load_contents_async(file, cancellable, on_file_ready, task); -} diff --git a/XdgThumbnailer/thumbnail.h b/XdgThumbnailer/thumbnail.h deleted file mode 100644 index 1112a8331..000000000 --- a/XdgThumbnailer/thumbnail.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include -#include - -void locate_and_create_thumbnail_dir(void); -void free_thumbnail_dir_path(void); - -void start_thumbnailing(unsigned handle, GCancellable *cancellable, gboolean is_urgent, char const *uri, - char const *mime_type, char const *flavor_name); From 8e2769b9465f88d45b42cadc441db64f37af54ff Mon Sep 17 00:00:00 2001 From: ISSOtm Date: Thu, 4 Jul 2024 22:00:27 +0200 Subject: [PATCH 09/22] Clean up `make install` --- FreeDesktop/sameboy.xml | 40 ++++++++++---------- Makefile | 61 ++++++++++-------------------- XdgThumbnailer/sameboy.thumbnailer | 4 +- 3 files changed, 42 insertions(+), 63 deletions(-) diff --git a/FreeDesktop/sameboy.xml b/FreeDesktop/sameboy.xml index 18123edcf..5d7c6f156 100644 --- a/FreeDesktop/sameboy.xml +++ b/FreeDesktop/sameboy.xml @@ -1,23 +1,23 @@ - - Game Boy ROM - - - - - - - Game Boy Color ROM - - - - - - - Game Boy ISX binary - - - - + + Game Boy ROM + + + + + + + Game Boy Color ROM + + + + + + + Game Boy ISX binary + + + + diff --git a/Makefile b/Makefile index fc07385e4..dd367b0aa 100644 --- a/Makefile +++ b/Makefile @@ -390,7 +390,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 $(OBJ)/XdgThumbnailer/resources.c.o +XDG_THUMBNAILER_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(XDG_THUMBNAILER_SOURCES)) $(OBJ)/XdgThumbnailer/resources.c.o lib: $(PUBLIC_HEADERS) @@ -697,51 +697,30 @@ $(BIN)/BootROMs/%.bin: BootROMs/%.asm $(OBJ)/BootROMs/SameBoyLogo.pb12 libretro: CFLAGS="$(WARNINGS)" $(MAKE) -C libretro BOOTROMS_DIR=$(abspath $(BOOTROMS_DIR)) BIN=$(abspath $(BIN)) -# install for Linux/FreeDesktop/etc. -# Does not install mimetype icons because FreeDesktop is cursed abomination with no right to exist. -# If you somehow find a reasonable way to make associate an icon with an extension in this dumpster -# fire of a desktop environment, open an issue or a pull request +# Install for Linux, and other FreeDesktop platforms. ifneq ($(FREEDESKTOP),) -all: xdg-thumbnailer - -ICON_NAMES := apps/sameboy mimetypes/x-gameboy-rom mimetypes/x-gameboy-color-rom -ICON_SIZES := 16x16 32x32 64x64 128x128 256x256 512x512 -ICONS := $(foreach name,$(ICON_NAMES), $(foreach size,$(ICON_SIZES),$(DESTDIR)$(PREFIX)/share/icons/hicolor/$(size)/$(name).png)) -# TODO: install the thumbnailer as well -install: sdl xdg-thumbnailer $(DESTDIR)$(PREFIX)/share/mime/packages/sameboy.xml $(ICONS) FreeDesktop/sameboy.desktop - -@$(MKDIR) -p $(dir $(DESTDIR)$(PREFIX)) - mkdir -p $(DESTDIR)$(DATA_DIR)/ $(DESTDIR)$(PREFIX)/bin/ - cp -rf $(BIN)/SDL/* $(DESTDIR)$(DATA_DIR)/ - cp -rf $(BIN)/XdgThumbnailer/* $(DESTDIR)$(DATA_DIR)/ +install: $(BIN)/XdgThumbnailer/sameboy-thumbnailer sdl $(shell find FreeDesktop) + install -Dm 644 -st $(DESTDIR)$(PREFIX)/bin/ $< + install -Dm 644 -st $(DESTDIR)$(DATA_DIR)/ $(BIN)/SDL/* mv $(DESTDIR)$(DATA_DIR)/sameboy $(DESTDIR)$(PREFIX)/bin/sameboy + chmod +x $(DESTDIR)$(PREFIX)/bin/sameboy ifeq ($(DESTDIR),) - -update-mime-database -n $(PREFIX)/share/mime - -xdg-desktop-menu install --novendor --mode system FreeDesktop/sameboy.desktop - -xdg-icon-resource forceupdate --mode system - -xdg-desktop-menu forceupdate --mode system -ifneq ($(SUDO_USER),) - -su $(SUDO_USER) -c "xdg-desktop-menu forceupdate --mode system" -endif + xdg-mime install --novendor FreeDesktop/sameboy.xml + xdg-desktop-menu install --novendor FreeDesktop/sameboy.desktop + for size in 16 32 64 128 256 512; do \ + xdg-icon-resource install --novendor --theme hicolor --context apps FreeDesktop/AppIcon/$$size.png sameboy; \ + xdg-icon-resource install --novendor --theme hicolor --context mimetypes FreeDesktop/Cartridge/$$size.png x-gameboy-rom; \ + xdg-icon-resource install --novendor --theme hicolor --context mimetypes FreeDesktop/ColorCartridge/$$size.png x-gameboy-color-rom; \ + done else - -@$(MKDIR) -p $(DESTDIR)$(PREFIX)/share/applications/ - cp FreeDesktop/sameboy.desktop $(DESTDIR)$(PREFIX)/share/applications/sameboy.desktop + install -Dm 644 -t $(DESTDIR)$(PREFIX)/share/mime FreeDesktop/sameboy.xml + install -Dm 644 -t $(DESTDIR)$(PREFIX)/share/applications FreeDesktop/sameboy.desktop + for size in 16 32 64 128 256 512; do \ + install -TDm 644 FreeDesktop/AppIcon/$$size.png $(DESTDIR)$(PREFIX)/share/pixmaps/$$size/apps/sameboy.png; \ + install -TDm 644 FreeDesktop/Cartridge/$$size.png $(DESTDIR)$(PREFIX)/share/pixmaps/$$size/mimetypes/x-gameboy-rom.png; \ + install -TDm 644 FreeDesktop/ColorCartridge/$$size.png $(DESTDIR)$(PREFIX)/share/pixmaps/$$size/mimetypes/x-gameboy-color-rom.png; \ + done endif - -$(DESTDIR)$(PREFIX)/share/icons/hicolor/%/apps/sameboy.png: FreeDesktop/AppIcon/%.png - -@$(MKDIR) -p $(dir $@) - cp -f $^ $@ - -$(DESTDIR)$(PREFIX)/share/icons/hicolor/%/mimetypes/x-gameboy-rom.png: FreeDesktop/Cartridge/%.png - -@$(MKDIR) -p $(dir $@) - cp -f $^ $@ - -$(DESTDIR)$(PREFIX)/share/icons/hicolor/%/mimetypes/x-gameboy-color-rom.png: FreeDesktop/ColorCartridge/%.png - -@$(MKDIR) -p $(dir $@) - cp -f $^ $@ - -$(DESTDIR)$(PREFIX)/share/mime/packages/sameboy.xml: FreeDesktop/sameboy.xml - -@$(MKDIR) -p $(dir $@) - cp -f $^ $@ endif ios: diff --git a/XdgThumbnailer/sameboy.thumbnailer b/XdgThumbnailer/sameboy.thumbnailer index 63be4cfbb..eee621a9d 100644 --- a/XdgThumbnailer/sameboy.thumbnailer +++ b/XdgThumbnailer/sameboy.thumbnailer @@ -1,4 +1,4 @@ [Thumbnailer Entry] -TryExec=/home/issotm/SameBoy/build/bin/XdgThumbnailer/sameboy-thumbnailer -Exec=/home/issotm/SameBoy/build/bin/XdgThumbnailer/sameboy-thumbnailer %i %o %s +TryExec=sameboy-thumbnailer +Exec=sameboy-thumbnailer %i %o %s MimeType=application/x-gameboy-rom;application/x-gameboy-color-rom;application/x-gameboy-isx From 64d45dd23ae09d70ca530af3991e471a8f91abcf Mon Sep 17 00:00:00 2001 From: ISSOtm Date: Thu, 4 Jul 2024 22:14:59 +0200 Subject: [PATCH 10/22] Only strip the correct file on install --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index dd367b0aa..cb23231fd 100644 --- a/Makefile +++ b/Makefile @@ -701,9 +701,10 @@ libretro: ifneq ($(FREEDESKTOP),) install: $(BIN)/XdgThumbnailer/sameboy-thumbnailer sdl $(shell find FreeDesktop) install -Dm 644 -st $(DESTDIR)$(PREFIX)/bin/ $< - install -Dm 644 -st $(DESTDIR)$(DATA_DIR)/ $(BIN)/SDL/* + install -Dm 644 -t $(DESTDIR)$(DATA_DIR)/ $(BIN)/SDL/* mv $(DESTDIR)$(DATA_DIR)/sameboy $(DESTDIR)$(PREFIX)/bin/sameboy chmod +x $(DESTDIR)$(PREFIX)/bin/sameboy + strip $(DESTDIR)$(PREFIX)/bin/sameboy ifeq ($(DESTDIR),) xdg-mime install --novendor FreeDesktop/sameboy.xml xdg-desktop-menu install --novendor FreeDesktop/sameboy.desktop From a26c57d5825b113a88f4c30f08b3b3fb42fb4784 Mon Sep 17 00:00:00 2001 From: ISSOtm Date: Thu, 4 Jul 2024 22:34:34 +0200 Subject: [PATCH 11/22] Use `icons` directory for icons instead of legacy `pixmaps` --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index cb23231fd..bf97576f9 100644 --- a/Makefile +++ b/Makefile @@ -717,9 +717,9 @@ else install -Dm 644 -t $(DESTDIR)$(PREFIX)/share/mime FreeDesktop/sameboy.xml install -Dm 644 -t $(DESTDIR)$(PREFIX)/share/applications FreeDesktop/sameboy.desktop for size in 16 32 64 128 256 512; do \ - install -TDm 644 FreeDesktop/AppIcon/$$size.png $(DESTDIR)$(PREFIX)/share/pixmaps/$$size/apps/sameboy.png; \ - install -TDm 644 FreeDesktop/Cartridge/$$size.png $(DESTDIR)$(PREFIX)/share/pixmaps/$$size/mimetypes/x-gameboy-rom.png; \ - install -TDm 644 FreeDesktop/ColorCartridge/$$size.png $(DESTDIR)$(PREFIX)/share/pixmaps/$$size/mimetypes/x-gameboy-color-rom.png; \ + install -TDm 644 FreeDesktop/AppIcon/$$size.png $(DESTDIR)$(PREFIX)/share/icons/$$size/apps/sameboy.png; \ + install -TDm 644 FreeDesktop/Cartridge/$$size.png $(DESTDIR)$(PREFIX)/share/icons/$$size/mimetypes/x-gameboy-rom.png; \ + install -TDm 644 FreeDesktop/ColorCartridge/$$size.png $(DESTDIR)$(PREFIX)/share/icons/$$size/mimetypes/x-gameboy-color-rom.png; \ done endif endif From 64ad67d5a51a6a8e9760230c22a61d34f8f2e157 Mon Sep 17 00:00:00 2001 From: ISSOtm Date: Thu, 4 Jul 2024 22:36:32 +0200 Subject: [PATCH 12/22] Avoid overriding globs used to detect MIME types --- FreeDesktop/sameboy.xml | 3 --- 1 file changed, 3 deletions(-) diff --git a/FreeDesktop/sameboy.xml b/FreeDesktop/sameboy.xml index 5d7c6f156..5ee1a348f 100644 --- a/FreeDesktop/sameboy.xml +++ b/FreeDesktop/sameboy.xml @@ -3,21 +3,18 @@ Game Boy ROM - Game Boy Color ROM - Game Boy ISX binary - From 4f25521cba9f0520779c5901d53b20b60b2fa5f6 Mon Sep 17 00:00:00 2001 From: ISSOtm Date: Thu, 4 Jul 2024 23:18:00 +0200 Subject: [PATCH 13/22] Improve MIME type description a little --- FreeDesktop/sameboy.xml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/FreeDesktop/sameboy.xml b/FreeDesktop/sameboy.xml index 5ee1a348f..2e15fdedc 100644 --- a/FreeDesktop/sameboy.xml +++ b/FreeDesktop/sameboy.xml @@ -5,16 +5,34 @@ + + + + + + + Game Boy Color ROM + + + + + + + Game Boy ISX binary + + + + From 17f1b91b8c2397f07313c279610baab5250a3b51 Mon Sep 17 00:00:00 2001 From: ISSOtm Date: Thu, 4 Jul 2024 23:25:16 +0200 Subject: [PATCH 14/22] Fix and simplify `make install` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `install` does not operate recursively on directories. Exclude the `sameboy` executable from that copy, and use a single `install` command to perform all of it. The icon names are in the `NxN` format, including the target dirs. --- Makefile | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index bf97576f9..223ff6145 100644 --- a/Makefile +++ b/Makefile @@ -700,15 +700,13 @@ libretro: # Install for Linux, and other FreeDesktop platforms. ifneq ($(FREEDESKTOP),) install: $(BIN)/XdgThumbnailer/sameboy-thumbnailer sdl $(shell find FreeDesktop) - install -Dm 644 -st $(DESTDIR)$(PREFIX)/bin/ $< - install -Dm 644 -t $(DESTDIR)$(DATA_DIR)/ $(BIN)/SDL/* - mv $(DESTDIR)$(DATA_DIR)/sameboy $(DESTDIR)$(PREFIX)/bin/sameboy - chmod +x $(DESTDIR)$(PREFIX)/bin/sameboy - strip $(DESTDIR)$(PREFIX)/bin/sameboy + (cd $(BIN)/SDL && find . \! -name sameboy -type f -exec install -Dm 644 -T {} "$(DESTDIR)$(DATA_DIR)/{}" \; ) + install -Dm 755 -st $(DESTDIR)$(PREFIX)/bin/ $(BIN)/SDL/sameboy + install -Dm 755 -st $(DESTDIR)$(PREFIX)/bin/ $< ifeq ($(DESTDIR),) xdg-mime install --novendor FreeDesktop/sameboy.xml xdg-desktop-menu install --novendor FreeDesktop/sameboy.desktop - for size in 16 32 64 128 256 512; do \ + for size in 16x16 32x32 64x64 128x128 256x256 512x512; do \ xdg-icon-resource install --novendor --theme hicolor --context apps FreeDesktop/AppIcon/$$size.png sameboy; \ xdg-icon-resource install --novendor --theme hicolor --context mimetypes FreeDesktop/Cartridge/$$size.png x-gameboy-rom; \ xdg-icon-resource install --novendor --theme hicolor --context mimetypes FreeDesktop/ColorCartridge/$$size.png x-gameboy-color-rom; \ @@ -716,7 +714,7 @@ ifeq ($(DESTDIR),) else install -Dm 644 -t $(DESTDIR)$(PREFIX)/share/mime FreeDesktop/sameboy.xml install -Dm 644 -t $(DESTDIR)$(PREFIX)/share/applications FreeDesktop/sameboy.desktop - for size in 16 32 64 128 256 512; do \ + for size in 16x16 32x32 64x64 128x128 256x256 512x512; do \ install -TDm 644 FreeDesktop/AppIcon/$$size.png $(DESTDIR)$(PREFIX)/share/icons/$$size/apps/sameboy.png; \ install -TDm 644 FreeDesktop/Cartridge/$$size.png $(DESTDIR)$(PREFIX)/share/icons/$$size/mimetypes/x-gameboy-rom.png; \ install -TDm 644 FreeDesktop/ColorCartridge/$$size.png $(DESTDIR)$(PREFIX)/share/icons/$$size/mimetypes/x-gameboy-color-rom.png; \ From 1dde5c1ce1d3df59c1b9a041bfc8a434273792de Mon Sep 17 00:00:00 2001 From: ISSOtm Date: Thu, 4 Jul 2024 23:37:18 +0200 Subject: [PATCH 15/22] Have `make install` install the thumbnailer file as well --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 223ff6145..d13bd4183 100644 --- a/Makefile +++ b/Makefile @@ -699,10 +699,11 @@ libretro: # Install for Linux, and other FreeDesktop platforms. ifneq ($(FREEDESKTOP),) -install: $(BIN)/XdgThumbnailer/sameboy-thumbnailer sdl $(shell find FreeDesktop) +install: $(BIN)/XdgThumbnailer/sameboy-thumbnailer sdl $(shell find FreeDesktop) XdgThumbnailer/sameboy.thumbnailer (cd $(BIN)/SDL && find . \! -name sameboy -type f -exec install -Dm 644 -T {} "$(DESTDIR)$(DATA_DIR)/{}" \; ) install -Dm 755 -st $(DESTDIR)$(PREFIX)/bin/ $(BIN)/SDL/sameboy install -Dm 755 -st $(DESTDIR)$(PREFIX)/bin/ $< + install -Dm 644 -t $(DESTDIR)$(PREFIX)/share/thumbnailers/ XdgThumbnailer/sameboy.thumbnailer ifeq ($(DESTDIR),) xdg-mime install --novendor FreeDesktop/sameboy.xml xdg-desktop-menu install --novendor FreeDesktop/sameboy.desktop From 488f17941f872a045cc0d6b3044689b1740da577 Mon Sep 17 00:00:00 2001 From: ISSOtm Date: Thu, 4 Jul 2024 23:51:49 +0200 Subject: [PATCH 16/22] Add forgotten theme name when installing FreeDesktop icons *facepalm* --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index d13bd4183..af8fcbc5d 100644 --- a/Makefile +++ b/Makefile @@ -716,9 +716,9 @@ else install -Dm 644 -t $(DESTDIR)$(PREFIX)/share/mime FreeDesktop/sameboy.xml install -Dm 644 -t $(DESTDIR)$(PREFIX)/share/applications FreeDesktop/sameboy.desktop for size in 16x16 32x32 64x64 128x128 256x256 512x512; do \ - install -TDm 644 FreeDesktop/AppIcon/$$size.png $(DESTDIR)$(PREFIX)/share/icons/$$size/apps/sameboy.png; \ - install -TDm 644 FreeDesktop/Cartridge/$$size.png $(DESTDIR)$(PREFIX)/share/icons/$$size/mimetypes/x-gameboy-rom.png; \ - install -TDm 644 FreeDesktop/ColorCartridge/$$size.png $(DESTDIR)$(PREFIX)/share/icons/$$size/mimetypes/x-gameboy-color-rom.png; \ + install -TDm 644 FreeDesktop/AppIcon/$$size.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/$$size/apps/sameboy.png; \ + install -TDm 644 FreeDesktop/Cartridge/$$size.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/$$size/mimetypes/x-gameboy-rom.png; \ + install -TDm 644 FreeDesktop/ColorCartridge/$$size.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/$$size/mimetypes/x-gameboy-color-rom.png; \ done endif endif @@ -733,7 +733,7 @@ $(BIN)/SameBoy-iOS.ipa: ios iOS/sideload.entitlements (cd $(OBJ) && zip -q $(abspath $@) -r Payload) rm -rf $(OBJ)/Payload - + $(BIN)/SameBoy-iOS.deb: $(OBJ)/debian-binary $(OBJ)/control.tar.gz $(OBJ)/data.tar.gz -@$(MKDIR) -p $(dir $@) (cd $(OBJ) && ar cr $(abspath $@) $(notdir $^)) From 6e1112157c496daddde569f2141d7d15a2b66346 Mon Sep 17 00:00:00 2001 From: ISSOtm Date: Fri, 5 Jul 2024 22:16:06 +0200 Subject: [PATCH 17/22] Address review comments --- Makefile | 10 ++++++---- XdgThumbnailer/emulate.c | 18 +++++++++--------- XdgThumbnailer/main.c | 5 +++-- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/Makefile b/Makefile index af8fcbc5d..2be0c117f 100644 --- a/Makefile +++ b/Makefile @@ -171,7 +171,7 @@ endif # These must come before the -Wno- flags WARNINGS += -Werror -Wall -Wno-unknown-warning -Wno-unknown-warning-option -Wno-missing-braces -WARNINGS += -Wno-nonnull -Wno-unused-result -Wno-multichar -Wno-int-in-bool-context -Wno-format-truncation -Wformat-overflow=2 +WARNINGS += -Wno-nonnull -Wno-unused-result -Wno-multichar -Wno-int-in-bool-context -Wno-format-truncation # Only add this flag if the compiler supports it ifeq ($(shell $(CC) -x c -c $(NULL) -o $(NULL) -Werror -Wpartial-availability 2> $(NULL); echo $$?),0) @@ -367,6 +367,9 @@ all: sdl tester libretro lib ifeq ($(PLATFORM),Darwin) all: cocoa ios-ipa ios-deb endif +ifneq ($(FREEDESKTOP),) +all: xdg-thumbnailer +endif # Get a list of our source files and their respective object file targets @@ -658,9 +661,8 @@ $(BIN)/SDL/background.bmp: SDL/background.bmp -@$(MKDIR) -p $(dir $@) cp -f $< $@ -$(BIN)/SDL/Shaders: Shaders - -@$(MKDIR) -p $@ - cp -rfT $< $@ +$(BIN)/SDL/Shaders: $(wildcard Shaders/*.fsh) + install -Dt $@ $^ touch $@ $(BIN)/SDL/Palettes: Misc/Palettes diff --git a/XdgThumbnailer/emulate.c b/XdgThumbnailer/emulate.c index 4582cc5dc..607170710 100644 --- a/XdgThumbnailer/emulate.c +++ b/XdgThumbnailer/emulate.c @@ -32,12 +32,11 @@ static void vblank_callback(GB_gameboy_t *gb, GB_vblank_type_t type) { (void)type; // Ignore the type, we use VBlank counting as a kind of pacing (and to avoid tearing). - unsigned nb_frames_left = GPOINTER_TO_UINT(GB_get_user_data(gb)); - nb_frames_left--; - GB_set_user_data(gb, GUINT_TO_POINTER(nb_frames_left)); + unsigned *nb_frames_left = GB_get_user_data(gb); + (*nb_frames_left)--; // *Do* render the very last frame. - if (nb_frames_left == 1) { + if (*nb_frames_left == 1) { GB_set_rendering_disabled(gb, false); } } @@ -51,7 +50,7 @@ static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) // So we treat each uint32_t as a 4-byte buffer, and write the bytes accordingly. // This is guaranteed to not be UB, because casting a `T*` to any flavour of `char*` accesses // and modifies the `T`'s "object representation". - unsigned char *bytes = (uint8_t *)&rgba; + uint8_t *bytes = (uint8_t *)&rgba; bytes[0] = r; bytes[1] = g; bytes[2] = b; @@ -75,13 +74,11 @@ uint8_t emulate(char const *path, uint32_t screen[static GB_SCREEN_WIDTH * GB_SC G_RESOURCE_LOOKUP_FLAGS_NONE, &error); g_assert_no_error(error); // This shouldn't be able to fail. size_t boot_rom_size; - unsigned char const *boot_rom_data = g_bytes_get_data(boot_rom, &boot_rom_size); + uint8_t const *boot_rom_data = g_bytes_get_data(boot_rom, &boot_rom_size); g_assert_cmpuint(boot_rom_size, ==, BOOT_ROM_SIZE); GB_load_boot_rom_from_buffer(&gb, boot_rom_data, boot_rom_size); g_bytes_unref(boot_rom); - GB_set_user_data(&gb, GUINT_TO_POINTER(NB_FRAMES_TO_EMULATE)); - GB_set_vblank_callback(&gb, vblank_callback); GB_set_pixels_output(&gb, screen); GB_set_rgb_encode_callback(&gb, rgb_encode); @@ -89,9 +86,12 @@ uint8_t emulate(char const *path, uint32_t screen[static GB_SCREEN_WIDTH * GB_SC GB_set_log_callback(&gb, log_callback); // Anything bizarre the ROM does during emulation, we don't care about. GB_set_color_correction_mode(&gb, GB_COLOR_CORRECTION_MODERN_BALANCED); + unsigned nb_frames_left = NB_FRAMES_TO_EMULATE; + GB_set_user_data(&gb, &nb_frames_left); + GB_set_rendering_disabled(&gb, true); GB_set_turbo_mode(&gb, true, true); - while (GPOINTER_TO_UINT(GB_get_user_data(&gb))) { + while (nb_frames_left) { GB_run(&gb); } diff --git a/XdgThumbnailer/main.c b/XdgThumbnailer/main.c index 02b4daa99..7d8b5c5bb 100644 --- a/XdgThumbnailer/main.c +++ b/XdgThumbnailer/main.c @@ -68,7 +68,7 @@ static GdkPixbuf *generate_thumbnail(char const *input_path) return scaled_screen; } -static GdkPixbuf *enforce_max_size(GdkPixbuf *thumbnail, unsigned long max_size) +static GdkPixbuf *enforce_max_size(GdkPixbuf *thumbnail, unsigned max_size) { g_assert_cmpuint(gdk_pixbuf_get_width(thumbnail), ==, gdk_pixbuf_get_height(thumbnail)); g_assert_cmpuint(gdk_pixbuf_get_width(thumbnail), ==, 1024); @@ -113,7 +113,8 @@ int main(int argc, char *argv[]) if (max_size) { char *endptr; errno = 0; - unsigned long size = strtoul(max_size, &endptr, 10); + // This can implicitly truncate, but that's the behaviour Liji wants. + unsigned size = strtoul(max_size, &endptr, 10); if (errno != 0 || *max_size == '\0' || *endptr != '\0') { g_error("Invalid size parameter \"%s\": %s", max_size, strerror(errno == 0 ? EINVAL : errno)); // NOTREACHED From 366c3744615f50bc321df05fafcdf42d2acc84b5 Mon Sep 17 00:00:00 2001 From: ISSOtm Date: Fri, 5 Jul 2024 22:27:30 +0200 Subject: [PATCH 18/22] Switch to "west `const`" instead of "east `const`" --- XdgThumbnailer/emulate.c | 6 +++--- XdgThumbnailer/emulate.h | 2 +- XdgThumbnailer/main.c | 14 +++++++------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/XdgThumbnailer/emulate.c b/XdgThumbnailer/emulate.c index 607170710..f39ca4e06 100644 --- a/XdgThumbnailer/emulate.c +++ b/XdgThumbnailer/emulate.c @@ -58,12 +58,12 @@ static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) return rgba; } -uint8_t emulate(char const *path, uint32_t screen[static GB_SCREEN_WIDTH * GB_SCREEN_HEIGHT]) +uint8_t emulate(const char *path, uint32_t screen[static GB_SCREEN_WIDTH * GB_SCREEN_HEIGHT]) { GB_gameboy_t gb; GB_init(&gb, GB_MODEL_CGB_E); - char const *last_dot = strrchr(path, '.'); + const char *last_dot = strrchr(path, '.'); bool is_isx = last_dot && strcmp(last_dot + 1, "isx") == 0; if (is_isx ? GB_load_isx(&gb, path) : GB_load_rom(&gb, path)) { exit(EXIT_FAILURE); @@ -74,7 +74,7 @@ uint8_t emulate(char const *path, uint32_t screen[static GB_SCREEN_WIDTH * GB_SC G_RESOURCE_LOOKUP_FLAGS_NONE, &error); g_assert_no_error(error); // This shouldn't be able to fail. size_t boot_rom_size; - uint8_t const *boot_rom_data = g_bytes_get_data(boot_rom, &boot_rom_size); + const uint8_t *boot_rom_data = g_bytes_get_data(boot_rom, &boot_rom_size); g_assert_cmpuint(boot_rom_size, ==, BOOT_ROM_SIZE); GB_load_boot_rom_from_buffer(&gb, boot_rom_data, boot_rom_size); g_bytes_unref(boot_rom); diff --git a/XdgThumbnailer/emulate.h b/XdgThumbnailer/emulate.h index f1a67010b..3e75d4a91 100644 --- a/XdgThumbnailer/emulate.h +++ b/XdgThumbnailer/emulate.h @@ -5,4 +5,4 @@ #define GB_SCREEN_WIDTH 160 #define GB_SCREEN_HEIGHT 144 -uint8_t emulate(char const *path, uint32_t screen[static GB_SCREEN_WIDTH * GB_SCREEN_HEIGHT]); +uint8_t emulate(const char *path, uint32_t screen[static GB_SCREEN_WIDTH * GB_SCREEN_HEIGHT]); diff --git a/XdgThumbnailer/main.c b/XdgThumbnailer/main.c index 7d8b5c5bb..5872fb419 100644 --- a/XdgThumbnailer/main.c +++ b/XdgThumbnailer/main.c @@ -9,11 +9,11 @@ #include "emulate.h" -static char const dmg_only_resource_path[] = "/thumbnailer/CartridgeTemplate.png"; -static char const dual_resource_path[] = "/thumbnailer/UniversalCartridgeTemplate.png"; -static char const cgb_only_resource_path[] = "/thumbnailer/ColorCartridgeTemplate.png"; +static const char dmg_only_resource_path[] = "/thumbnailer/CartridgeTemplate.png"; +static const char dual_resource_path[] = "/thumbnailer/UniversalCartridgeTemplate.png"; +static const char cgb_only_resource_path[] = "/thumbnailer/ColorCartridgeTemplate.png"; -static GdkPixbuf *generate_thumbnail(char const *input_path) +static GdkPixbuf *generate_thumbnail(const char *input_path) { uint32_t screen_raw[GB_SCREEN_WIDTH * GB_SCREEN_HEIGHT]; uint8_t cgb_flag = emulate(input_path, screen_raw); @@ -80,7 +80,7 @@ static GdkPixbuf *enforce_max_size(GdkPixbuf *thumbnail, unsigned max_size) return scaled; } -static void write_thumbnail(GdkPixbuf *thumbnail, char const *output_path) +static void write_thumbnail(GdkPixbuf *thumbnail, const char *output_path) { GError *error = NULL; // Intentionally be "not a good citizen": @@ -103,9 +103,9 @@ int main(int argc, char *argv[]) g_error("Usage: %s []", argv[0] ? argv[0] : "sameboy-thumbnailer"); // NOTREACHED } - char const *input_path = argv[1]; + const char *input_path = argv[1]; char *output_path = argv[2]; // Gets mutated in-place. - char const *max_size = argv[3]; // May be NULL. + const char *max_size = argv[3]; // May be NULL. g_debug("%s -> %s [%s]", input_path, output_path, max_size ? max_size : "(none)"); From 3dad68618e284e1cbc655760a3bab9fca7a224b4 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 6 Jul 2024 15:03:41 +0300 Subject: [PATCH 19/22] Fix build on macOS --- Makefile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 2be0c117f..2b3d0b004 100644 --- a/Makefile +++ b/Makefile @@ -662,12 +662,13 @@ $(BIN)/SDL/background.bmp: SDL/background.bmp cp -f $< $@ $(BIN)/SDL/Shaders: $(wildcard Shaders/*.fsh) - install -Dt $@ $^ + -@$(MKDIR) -p $@ + cp -f $^ $@ touch $@ $(BIN)/SDL/Palettes: Misc/Palettes -@$(MKDIR) -p $@ - cp -rfT $< $@ + cp -f $ Date: Sat, 6 Jul 2024 15:09:13 +0300 Subject: [PATCH 20/22] Minor adjustments --- Makefile | 12 ++++++------ XdgThumbnailer/main.c | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 2b3d0b004..077819df1 100644 --- a/Makefile +++ b/Makefile @@ -218,7 +218,7 @@ SDL_AUDIO_DRIVERS += openal endif endif -ifneq ($(shell pkg-config --exists gio-2.0 || echo 0),) +ifneq ($(shell $(PKG_CONFIG) --exists gio-unix-2.0 || echo 0),) GIO_CFLAGS = $(error The Gio library could not be found) GIO_LDFLAGS = $(error The Gio library could not be found) else @@ -231,7 +231,7 @@ GIO_CFLAGS += -DG_DISABLE_ASSERT endif endif -ifneq ($(shell pkg-config --exists gdk-pixbuf-2.0 || echo 0),) +ifneq ($(shell $(PKG_CONFIG) --exists gdk-pixbuf-2.0 || echo 0),) GDK_PIXBUF_CFLAGS = $(error The Gdk-Pixbuf library could not be found) GDK_PIXBUF_LDFLAGS = $(error The Gdk-Pixbuf library could not be found) else @@ -698,7 +698,7 @@ $(BIN)/BootROMs/%.bin: BootROMs/%.asm $(OBJ)/BootROMs/SameBoyLogo.pb12 # Libretro Core (uses its own build system) libretro: - CFLAGS="$(WARNINGS)" $(MAKE) -C libretro BOOTROMS_DIR=$(abspath $(BOOTROMS_DIR)) BIN=$(abspath $(BIN)) + CC=$(CC) CFLAGS="$(WARNINGS)" $(MAKE) -C libretro BOOTROMS_DIR=$(abspath $(BOOTROMS_DIR)) BIN=$(abspath $(BIN)) # Install for Linux, and other FreeDesktop platforms. ifneq ($(FREEDESKTOP),) @@ -711,9 +711,9 @@ ifeq ($(DESTDIR),) xdg-mime install --novendor FreeDesktop/sameboy.xml xdg-desktop-menu install --novendor FreeDesktop/sameboy.desktop for size in 16x16 32x32 64x64 128x128 256x256 512x512; do \ - xdg-icon-resource install --novendor --theme hicolor --context apps FreeDesktop/AppIcon/$$size.png sameboy; \ - xdg-icon-resource install --novendor --theme hicolor --context mimetypes FreeDesktop/Cartridge/$$size.png x-gameboy-rom; \ - xdg-icon-resource install --novendor --theme hicolor --context mimetypes FreeDesktop/ColorCartridge/$$size.png x-gameboy-color-rom; \ + xdg-icon-resource install --novendor --theme hicolor --size $$size --context apps FreeDesktop/AppIcon/$$size.png sameboy; \ + xdg-icon-resource install --novendor --theme hicolor --size $$size --context mimetypes FreeDesktop/Cartridge/$$size.png x-gameboy-rom; \ + xdg-icon-resource install --novendor --theme hicolor --size $$size --context mimetypes FreeDesktop/ColorCartridge/$$size.png x-gameboy-color-rom; \ done else install -Dm 644 -t $(DESTDIR)$(PREFIX)/share/mime FreeDesktop/sameboy.xml diff --git a/XdgThumbnailer/main.c b/XdgThumbnailer/main.c index 5872fb419..2a263fbd7 100644 --- a/XdgThumbnailer/main.c +++ b/XdgThumbnailer/main.c @@ -113,7 +113,8 @@ int main(int argc, char *argv[]) if (max_size) { char *endptr; errno = 0; - // This can implicitly truncate, but that's the behaviour Liji wants. + /* This will implicitly truncate, but enforce_max_size will cap size to 1024 anyway. + (Not that 4 billion pixels wide icons make sense to begin with)*/ unsigned size = strtoul(max_size, &endptr, 10); if (errno != 0 || *max_size == '\0' || *endptr != '\0') { g_error("Invalid size parameter \"%s\": %s", max_size, strerror(errno == 0 ? EINVAL : errno)); From 5dbddb344decac8a7bdf6b7a5437730ba7c96438 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 6 Jul 2024 15:44:01 +0300 Subject: [PATCH 21/22] Size expects an integer --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 077819df1..fc2a7f111 100644 --- a/Makefile +++ b/Makefile @@ -710,10 +710,10 @@ install: $(BIN)/XdgThumbnailer/sameboy-thumbnailer sdl $(shell find FreeDesktop) ifeq ($(DESTDIR),) xdg-mime install --novendor FreeDesktop/sameboy.xml xdg-desktop-menu install --novendor FreeDesktop/sameboy.desktop - for size in 16x16 32x32 64x64 128x128 256x256 512x512; do \ - xdg-icon-resource install --novendor --theme hicolor --size $$size --context apps FreeDesktop/AppIcon/$$size.png sameboy; \ - xdg-icon-resource install --novendor --theme hicolor --size $$size --context mimetypes FreeDesktop/Cartridge/$$size.png x-gameboy-rom; \ - xdg-icon-resource install --novendor --theme hicolor --size $$size --context mimetypes FreeDesktop/ColorCartridge/$$size.png x-gameboy-color-rom; \ + for size in 16 32 64 128 256 512; do \ + xdg-icon-resource install --novendor --theme hicolor --size $$size --context apps FreeDesktop/AppIcon/$$size*$$size.png sameboy; \ + xdg-icon-resource install --novendor --theme hicolor --size $$size --context mimetypes FreeDesktop/Cartridge/$$size*$$size.png x-gameboy-rom; \ + xdg-icon-resource install --novendor --theme hicolor --size $$size --context mimetypes FreeDesktop/ColorCartridge/$$size*$$size.png x-gameboy-color-rom; \ done else install -Dm 644 -t $(DESTDIR)$(PREFIX)/share/mime FreeDesktop/sameboy.xml From 136e11cc132a4e901d735dc1f3fc775e4f14505d Mon Sep 17 00:00:00 2001 From: ISSOtm Date: Mon, 8 Jul 2024 01:14:03 +0200 Subject: [PATCH 22/22] Avoid using GNU-isms in `make install` For macOS compat --- Makefile | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index fc2a7f111..fc764a285 100644 --- a/Makefile +++ b/Makefile @@ -704,24 +704,24 @@ libretro: ifneq ($(FREEDESKTOP),) install: $(BIN)/XdgThumbnailer/sameboy-thumbnailer sdl $(shell find FreeDesktop) XdgThumbnailer/sameboy.thumbnailer (cd $(BIN)/SDL && find . \! -name sameboy -type f -exec install -Dm 644 -T {} "$(DESTDIR)$(DATA_DIR)/{}" \; ) - install -Dm 755 -st $(DESTDIR)$(PREFIX)/bin/ $(BIN)/SDL/sameboy - install -Dm 755 -st $(DESTDIR)$(PREFIX)/bin/ $< - install -Dm 644 -t $(DESTDIR)$(PREFIX)/share/thumbnailers/ XdgThumbnailer/sameboy.thumbnailer + install -Dm 755 -s $(BIN)/SDL/sameboy $(DESTDIR)$(PREFIX)/bin/sameboy + install -Dm 755 -s $(BIN)/XdgThumbnailer/sameboy-thumbnailer $(DESTDIR)$(PREFIX)/bin/sameboy-thumbnailer + install -Dm 644 XdgThumbnailer/sameboy.thumbnailer $(DESTDIR)$(PREFIX)/share/thumbnailers/sameboy.thumbnailer ifeq ($(DESTDIR),) xdg-mime install --novendor FreeDesktop/sameboy.xml xdg-desktop-menu install --novendor FreeDesktop/sameboy.desktop for size in 16 32 64 128 256 512; do \ - xdg-icon-resource install --novendor --theme hicolor --size $$size --context apps FreeDesktop/AppIcon/$$size*$$size.png sameboy; \ - xdg-icon-resource install --novendor --theme hicolor --size $$size --context mimetypes FreeDesktop/Cartridge/$$size*$$size.png x-gameboy-rom; \ - xdg-icon-resource install --novendor --theme hicolor --size $$size --context mimetypes FreeDesktop/ColorCartridge/$$size*$$size.png x-gameboy-color-rom; \ + xdg-icon-resource install --novendor --theme hicolor --size $$size --context apps FreeDesktop/AppIcon/$${size}x$${size}.png sameboy; \ + xdg-icon-resource install --novendor --theme hicolor --size $$size --context mimetypes FreeDesktop/Cartridge/$${size}x$${size}.png x-gameboy-rom; \ + xdg-icon-resource install --novendor --theme hicolor --size $$size --context mimetypes FreeDesktop/ColorCartridge/$${size}x$${size}.png x-gameboy-color-rom; \ done else - install -Dm 644 -t $(DESTDIR)$(PREFIX)/share/mime FreeDesktop/sameboy.xml - install -Dm 644 -t $(DESTDIR)$(PREFIX)/share/applications FreeDesktop/sameboy.desktop + install -Dm 644 FreeDesktop/sameboy.xml $(DESTDIR)$(PREFIX)/share/mime/sameboy.xml + install -Dm 644 FreeDesktop/sameboy.desktop $(DESTDIR)$(PREFIX)/share/applications/sameboy.desktop for size in 16x16 32x32 64x64 128x128 256x256 512x512; do \ - install -TDm 644 FreeDesktop/AppIcon/$$size.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/$$size/apps/sameboy.png; \ - install -TDm 644 FreeDesktop/Cartridge/$$size.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/$$size/mimetypes/x-gameboy-rom.png; \ - install -TDm 644 FreeDesktop/ColorCartridge/$$size.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/$$size/mimetypes/x-gameboy-color-rom.png; \ + install -Dm 644 FreeDesktop/AppIcon/$$size.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/$$size/apps/sameboy.png; \ + install -Dm 644 FreeDesktop/Cartridge/$$size.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/$$size/mimetypes/x-gameboy-rom.png; \ + install -Dm 644 FreeDesktop/ColorCartridge/$$size.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/$$size/mimetypes/x-gameboy-color-rom.png; \ done endif endif