diff nuklear_ui/filechooser_gtk.c @ 2355:94cf5cc89227

Add an option to use the system file picker on Linux and Windows
author Michael Pavone <pavone@retrodev.com>
date Sat, 21 Oct 2023 19:22:01 -0700
parents
children 12d594e69e04
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nuklear_ui/filechooser_gtk.c	Sat Oct 21 19:22:01 2023 -0700
@@ -0,0 +1,185 @@
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <gtk/gtk.h>
+#include <dlfcn.h>
+
+typedef GtkWidget* (*gtk_file_chooser_dialog_new_t)(const gchar *title, GtkWindow *parent, GtkFileChooserAction action, const gchar *first_button_text, ...);
+typedef gint (*gtk_dialog_run_t)(GtkDialog *dialog);
+typedef void (*gtk_widget_destroy_t)(GtkWidget *widget);
+typedef gchar* (*gtk_file_chooser_get_filename_t)(GtkFileChooser *chooser);
+typedef gboolean (*gtk_init_check_t)(int *argc, char **argv);
+typedef gboolean (*gtk_file_chooser_set_current_folder_t)(GtkFileChooser *chooser, const gchar *filename);
+typedef void (*gtk_file_chooser_setadd_filter_t)(GtkFileChooser *chooser, GtkFileFilter *filter);
+typedef gboolean (*gtk_events_pending_t)(void);
+typedef gboolean (*gtk_main_iteration_t)(void);
+typedef GtkFileFilter* (*gtk_file_filter_new_t)(void);
+typedef void (*gtk_file_filter_set_name_t)(GtkFileFilter *filter, const gchar *name);
+typedef void (*gtk_file_filter_add_pattern_t)(GtkFileFilter *filter, const gchar *pattern);
+
+typedef struct {
+	gtk_file_chooser_dialog_new_t gtk_file_chooser_dialog_new;
+	gtk_dialog_run_t gtk_dialog_run;
+	gtk_widget_destroy_t gtk_widget_destroy;
+	gtk_file_chooser_get_filename_t gtk_file_chooser_get_filename;
+	gtk_file_chooser_set_current_folder_t gtk_file_chooser_set_current_folder;
+	gtk_file_chooser_setadd_filter_t gtk_file_chooser_add_filter;
+	gtk_file_chooser_setadd_filter_t gtk_file_chooser_set_filter;
+	gtk_file_filter_new_t gtk_file_filter_new;
+	gtk_file_filter_set_name_t gtk_file_filter_set_name;
+	gtk_file_filter_add_pattern_t gtk_file_filter_add_pattern;
+	gtk_init_check_t gtk_init_check;
+	gtk_events_pending_t gtk_events_pending;
+	gtk_main_iteration_t gtk_main_iteration;
+} gtk;
+
+#define LOAD_SYM(s, t, name) t->name = dlsym(s, #name); if (!t->name) { fputs("filechooser_gtk: Failed to load " #name "\n", stderr); goto error_cleanup; }
+
+static gtk* check_init_gtk(void)
+{
+	static const char *so_paths[] = {
+#ifdef X86_64
+		"/usr/lib/x86_64-linux-gnu/libgtk-3.so.0",
+		"/usr/lib/x86_64-linux-gnu/libgtk-x11-2.0.so.0",
+#elif X86_32
+		"/usr/lib/i386-linux-gnu/libgtk-3.so.0",
+		"/usr/lib/i386-linux-gnu/libgtk-x11-2.0.so.0",
+#else
+	//TODO: what are these paths on ARM?
+#endif
+	};
+	static gtk *funcs;
+	static uint8_t already_init;
+	if (!already_init) {
+		void *so = NULL;
+		for (int i = 0; !so && i < sizeof(so_paths)/sizeof(*so_paths); i++)
+		{
+			so = dlopen(so_paths[i], RTLD_NOW | RTLD_LOCAL);
+		}
+		if (so) {
+			funcs = calloc(1, sizeof(gtk));
+
+			LOAD_SYM(so, funcs, gtk_file_chooser_dialog_new)
+			LOAD_SYM(so, funcs, gtk_dialog_run)
+			LOAD_SYM(so, funcs, gtk_widget_destroy)
+			LOAD_SYM(so, funcs, gtk_file_chooser_get_filename)
+			LOAD_SYM(so, funcs, gtk_file_chooser_set_current_folder)
+			LOAD_SYM(so, funcs, gtk_file_chooser_add_filter)
+			LOAD_SYM(so, funcs, gtk_file_chooser_set_filter)
+			LOAD_SYM(so, funcs, gtk_file_filter_new)
+			LOAD_SYM(so, funcs, gtk_file_filter_set_name)
+			LOAD_SYM(so, funcs, gtk_file_filter_add_pattern)
+			LOAD_SYM(so, funcs, gtk_init_check)
+			LOAD_SYM(so, funcs, gtk_events_pending)
+			LOAD_SYM(so, funcs, gtk_main_iteration)
+
+			if (funcs->gtk_init_check(NULL, NULL)) {
+				return funcs;
+			}
+
+error_cleanup:
+			free(funcs);
+			dlclose(so);
+		}
+	}
+	return funcs;
+}
+
+uint8_t native_filechooser_available(void)
+{
+	return !!check_init_gtk();
+}
+
+char* native_filechooser_pick(const char *title, const char *start_directory)
+{
+	gtk *g = check_init_gtk();
+	if (!g) {
+		return NULL;
+	}
+	GtkFileChooser *chooser = (GtkFileChooser *)g->gtk_file_chooser_dialog_new(
+		title, NULL, GTK_FILE_CHOOSER_ACTION_OPEN,
+		"Cancel", GTK_RESPONSE_CANCEL,
+		"Open", GTK_RESPONSE_ACCEPT,
+		NULL
+	);
+	if (!chooser) {
+		return NULL;
+	}
+	if (start_directory) {
+		g->gtk_file_chooser_set_current_folder(chooser, start_directory);
+	}
+	GtkFileFilter *filter = g->gtk_file_filter_new();
+	g->gtk_file_filter_set_name(filter, "All Files");
+	g->gtk_file_filter_add_pattern(filter, "*");
+	g->gtk_file_chooser_add_filter(chooser, filter);
+
+	filter = g->gtk_file_filter_new();
+	g->gtk_file_filter_set_name(filter, "All Supported Types");
+	g->gtk_file_filter_add_pattern(filter, "*.zip");
+	g->gtk_file_filter_add_pattern(filter, "*.bin");
+	g->gtk_file_filter_add_pattern(filter, "*.bin.gz");
+	g->gtk_file_filter_add_pattern(filter, "*.gen");
+	g->gtk_file_filter_add_pattern(filter, "*.gen.gz");
+	g->gtk_file_filter_add_pattern(filter, "*.md");
+	g->gtk_file_filter_add_pattern(filter, "*.md.gz");
+	g->gtk_file_filter_add_pattern(filter, "*.sms");
+	g->gtk_file_filter_add_pattern(filter, "*.sms.gz");
+	g->gtk_file_filter_add_pattern(filter, "*.gg");
+	g->gtk_file_filter_add_pattern(filter, "*.gg.gz");
+	g->gtk_file_filter_add_pattern(filter, "*.sg");
+	g->gtk_file_filter_add_pattern(filter, "*.sg.gz");
+	g->gtk_file_filter_add_pattern(filter, "*.cue");
+	g->gtk_file_filter_add_pattern(filter, "*.toc");
+	g->gtk_file_filter_add_pattern(filter, "*.flac");
+	g->gtk_file_filter_add_pattern(filter, "*.vgm");
+	g->gtk_file_filter_add_pattern(filter, "*.vgz");
+	g->gtk_file_filter_add_pattern(filter, "*.vgm.gz");
+	g->gtk_file_chooser_add_filter(chooser, filter);
+	g->gtk_file_chooser_set_filter(chooser, filter);
+
+	filter = g->gtk_file_filter_new();
+	g->gtk_file_filter_set_name(filter, "Genesis/MD");
+	g->gtk_file_filter_add_pattern(filter, "*.zip");
+	g->gtk_file_filter_add_pattern(filter, "*.bin");
+	g->gtk_file_filter_add_pattern(filter, "*.bin.gz");
+	g->gtk_file_filter_add_pattern(filter, "*.gen");
+	g->gtk_file_filter_add_pattern(filter, "*.gen.gz");
+	g->gtk_file_filter_add_pattern(filter, "*.md");
+	g->gtk_file_filter_add_pattern(filter, "*.md.gz");
+	g->gtk_file_chooser_add_filter(chooser, filter);
+
+	filter = g->gtk_file_filter_new();
+	g->gtk_file_filter_set_name(filter, "Sega/Mega CD");
+	g->gtk_file_filter_add_pattern(filter, "*.cue");
+	g->gtk_file_filter_add_pattern(filter, "*.toc");
+	g->gtk_file_chooser_add_filter(chooser, filter);
+
+	filter = g->gtk_file_filter_new();
+	g->gtk_file_filter_set_name(filter, "Sega 8-bit");
+	g->gtk_file_filter_add_pattern(filter, "*.sms");
+	g->gtk_file_filter_add_pattern(filter, "*.sms.gz");
+	g->gtk_file_filter_add_pattern(filter, "*.gg");
+	g->gtk_file_filter_add_pattern(filter, "*.gg.gz");
+	g->gtk_file_filter_add_pattern(filter, "*.sg");
+	g->gtk_file_filter_add_pattern(filter, "*.sg.gz");
+	g->gtk_file_chooser_add_filter(chooser, filter);
+
+	filter = g->gtk_file_filter_new();
+	g->gtk_file_filter_set_name(filter, "Audio/VGM");
+	g->gtk_file_filter_add_pattern(filter, "*.flac");
+	g->gtk_file_filter_add_pattern(filter, "*.vgm");
+	g->gtk_file_filter_add_pattern(filter, "*.vgz");
+	g->gtk_file_filter_add_pattern(filter, "*.vgm.gz");
+	g->gtk_file_chooser_add_filter(chooser, filter);
+
+	char *ret = NULL;
+	if (GTK_RESPONSE_ACCEPT == g->gtk_dialog_run((GtkDialog*)chooser)) {
+		ret = g->gtk_file_chooser_get_filename(chooser);
+	}
+	g->gtk_widget_destroy((GtkWidget *)chooser);
+	while (g->gtk_events_pending())
+	{
+		g->gtk_main_iteration();
+	}
+	return ret;
+}