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;
+}