comparison 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
comparison
equal deleted inserted replaced
2354:a773b8f09292 2355:94cf5cc89227
1 #include <stddef.h>
2 #include <stdint.h>
3 #include <stdio.h>
4 #include <gtk/gtk.h>
5 #include <dlfcn.h>
6
7 typedef GtkWidget* (*gtk_file_chooser_dialog_new_t)(const gchar *title, GtkWindow *parent, GtkFileChooserAction action, const gchar *first_button_text, ...);
8 typedef gint (*gtk_dialog_run_t)(GtkDialog *dialog);
9 typedef void (*gtk_widget_destroy_t)(GtkWidget *widget);
10 typedef gchar* (*gtk_file_chooser_get_filename_t)(GtkFileChooser *chooser);
11 typedef gboolean (*gtk_init_check_t)(int *argc, char **argv);
12 typedef gboolean (*gtk_file_chooser_set_current_folder_t)(GtkFileChooser *chooser, const gchar *filename);
13 typedef void (*gtk_file_chooser_setadd_filter_t)(GtkFileChooser *chooser, GtkFileFilter *filter);
14 typedef gboolean (*gtk_events_pending_t)(void);
15 typedef gboolean (*gtk_main_iteration_t)(void);
16 typedef GtkFileFilter* (*gtk_file_filter_new_t)(void);
17 typedef void (*gtk_file_filter_set_name_t)(GtkFileFilter *filter, const gchar *name);
18 typedef void (*gtk_file_filter_add_pattern_t)(GtkFileFilter *filter, const gchar *pattern);
19
20 typedef struct {
21 gtk_file_chooser_dialog_new_t gtk_file_chooser_dialog_new;
22 gtk_dialog_run_t gtk_dialog_run;
23 gtk_widget_destroy_t gtk_widget_destroy;
24 gtk_file_chooser_get_filename_t gtk_file_chooser_get_filename;
25 gtk_file_chooser_set_current_folder_t gtk_file_chooser_set_current_folder;
26 gtk_file_chooser_setadd_filter_t gtk_file_chooser_add_filter;
27 gtk_file_chooser_setadd_filter_t gtk_file_chooser_set_filter;
28 gtk_file_filter_new_t gtk_file_filter_new;
29 gtk_file_filter_set_name_t gtk_file_filter_set_name;
30 gtk_file_filter_add_pattern_t gtk_file_filter_add_pattern;
31 gtk_init_check_t gtk_init_check;
32 gtk_events_pending_t gtk_events_pending;
33 gtk_main_iteration_t gtk_main_iteration;
34 } gtk;
35
36 #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; }
37
38 static gtk* check_init_gtk(void)
39 {
40 static const char *so_paths[] = {
41 #ifdef X86_64
42 "/usr/lib/x86_64-linux-gnu/libgtk-3.so.0",
43 "/usr/lib/x86_64-linux-gnu/libgtk-x11-2.0.so.0",
44 #elif X86_32
45 "/usr/lib/i386-linux-gnu/libgtk-3.so.0",
46 "/usr/lib/i386-linux-gnu/libgtk-x11-2.0.so.0",
47 #else
48 //TODO: what are these paths on ARM?
49 #endif
50 };
51 static gtk *funcs;
52 static uint8_t already_init;
53 if (!already_init) {
54 void *so = NULL;
55 for (int i = 0; !so && i < sizeof(so_paths)/sizeof(*so_paths); i++)
56 {
57 so = dlopen(so_paths[i], RTLD_NOW | RTLD_LOCAL);
58 }
59 if (so) {
60 funcs = calloc(1, sizeof(gtk));
61
62 LOAD_SYM(so, funcs, gtk_file_chooser_dialog_new)
63 LOAD_SYM(so, funcs, gtk_dialog_run)
64 LOAD_SYM(so, funcs, gtk_widget_destroy)
65 LOAD_SYM(so, funcs, gtk_file_chooser_get_filename)
66 LOAD_SYM(so, funcs, gtk_file_chooser_set_current_folder)
67 LOAD_SYM(so, funcs, gtk_file_chooser_add_filter)
68 LOAD_SYM(so, funcs, gtk_file_chooser_set_filter)
69 LOAD_SYM(so, funcs, gtk_file_filter_new)
70 LOAD_SYM(so, funcs, gtk_file_filter_set_name)
71 LOAD_SYM(so, funcs, gtk_file_filter_add_pattern)
72 LOAD_SYM(so, funcs, gtk_init_check)
73 LOAD_SYM(so, funcs, gtk_events_pending)
74 LOAD_SYM(so, funcs, gtk_main_iteration)
75
76 if (funcs->gtk_init_check(NULL, NULL)) {
77 return funcs;
78 }
79
80 error_cleanup:
81 free(funcs);
82 dlclose(so);
83 }
84 }
85 return funcs;
86 }
87
88 uint8_t native_filechooser_available(void)
89 {
90 return !!check_init_gtk();
91 }
92
93 char* native_filechooser_pick(const char *title, const char *start_directory)
94 {
95 gtk *g = check_init_gtk();
96 if (!g) {
97 return NULL;
98 }
99 GtkFileChooser *chooser = (GtkFileChooser *)g->gtk_file_chooser_dialog_new(
100 title, NULL, GTK_FILE_CHOOSER_ACTION_OPEN,
101 "Cancel", GTK_RESPONSE_CANCEL,
102 "Open", GTK_RESPONSE_ACCEPT,
103 NULL
104 );
105 if (!chooser) {
106 return NULL;
107 }
108 if (start_directory) {
109 g->gtk_file_chooser_set_current_folder(chooser, start_directory);
110 }
111 GtkFileFilter *filter = g->gtk_file_filter_new();
112 g->gtk_file_filter_set_name(filter, "All Files");
113 g->gtk_file_filter_add_pattern(filter, "*");
114 g->gtk_file_chooser_add_filter(chooser, filter);
115
116 filter = g->gtk_file_filter_new();
117 g->gtk_file_filter_set_name(filter, "All Supported Types");
118 g->gtk_file_filter_add_pattern(filter, "*.zip");
119 g->gtk_file_filter_add_pattern(filter, "*.bin");
120 g->gtk_file_filter_add_pattern(filter, "*.bin.gz");
121 g->gtk_file_filter_add_pattern(filter, "*.gen");
122 g->gtk_file_filter_add_pattern(filter, "*.gen.gz");
123 g->gtk_file_filter_add_pattern(filter, "*.md");
124 g->gtk_file_filter_add_pattern(filter, "*.md.gz");
125 g->gtk_file_filter_add_pattern(filter, "*.sms");
126 g->gtk_file_filter_add_pattern(filter, "*.sms.gz");
127 g->gtk_file_filter_add_pattern(filter, "*.gg");
128 g->gtk_file_filter_add_pattern(filter, "*.gg.gz");
129 g->gtk_file_filter_add_pattern(filter, "*.sg");
130 g->gtk_file_filter_add_pattern(filter, "*.sg.gz");
131 g->gtk_file_filter_add_pattern(filter, "*.cue");
132 g->gtk_file_filter_add_pattern(filter, "*.toc");
133 g->gtk_file_filter_add_pattern(filter, "*.flac");
134 g->gtk_file_filter_add_pattern(filter, "*.vgm");
135 g->gtk_file_filter_add_pattern(filter, "*.vgz");
136 g->gtk_file_filter_add_pattern(filter, "*.vgm.gz");
137 g->gtk_file_chooser_add_filter(chooser, filter);
138 g->gtk_file_chooser_set_filter(chooser, filter);
139
140 filter = g->gtk_file_filter_new();
141 g->gtk_file_filter_set_name(filter, "Genesis/MD");
142 g->gtk_file_filter_add_pattern(filter, "*.zip");
143 g->gtk_file_filter_add_pattern(filter, "*.bin");
144 g->gtk_file_filter_add_pattern(filter, "*.bin.gz");
145 g->gtk_file_filter_add_pattern(filter, "*.gen");
146 g->gtk_file_filter_add_pattern(filter, "*.gen.gz");
147 g->gtk_file_filter_add_pattern(filter, "*.md");
148 g->gtk_file_filter_add_pattern(filter, "*.md.gz");
149 g->gtk_file_chooser_add_filter(chooser, filter);
150
151 filter = g->gtk_file_filter_new();
152 g->gtk_file_filter_set_name(filter, "Sega/Mega CD");
153 g->gtk_file_filter_add_pattern(filter, "*.cue");
154 g->gtk_file_filter_add_pattern(filter, "*.toc");
155 g->gtk_file_chooser_add_filter(chooser, filter);
156
157 filter = g->gtk_file_filter_new();
158 g->gtk_file_filter_set_name(filter, "Sega 8-bit");
159 g->gtk_file_filter_add_pattern(filter, "*.sms");
160 g->gtk_file_filter_add_pattern(filter, "*.sms.gz");
161 g->gtk_file_filter_add_pattern(filter, "*.gg");
162 g->gtk_file_filter_add_pattern(filter, "*.gg.gz");
163 g->gtk_file_filter_add_pattern(filter, "*.sg");
164 g->gtk_file_filter_add_pattern(filter, "*.sg.gz");
165 g->gtk_file_chooser_add_filter(chooser, filter);
166
167 filter = g->gtk_file_filter_new();
168 g->gtk_file_filter_set_name(filter, "Audio/VGM");
169 g->gtk_file_filter_add_pattern(filter, "*.flac");
170 g->gtk_file_filter_add_pattern(filter, "*.vgm");
171 g->gtk_file_filter_add_pattern(filter, "*.vgz");
172 g->gtk_file_filter_add_pattern(filter, "*.vgm.gz");
173 g->gtk_file_chooser_add_filter(chooser, filter);
174
175 char *ret = NULL;
176 if (GTK_RESPONSE_ACCEPT == g->gtk_dialog_run((GtkDialog*)chooser)) {
177 ret = g->gtk_file_chooser_get_filename(chooser);
178 }
179 g->gtk_widget_destroy((GtkWidget *)chooser);
180 while (g->gtk_events_pending())
181 {
182 g->gtk_main_iteration();
183 }
184 return ret;
185 }