# HG changeset patch # User Michael Pavone # Date 1697941321 25200 # Node ID 94cf5cc8922770d463abdee62f0decb409b45612 # Parent a773b8f09292e63dbebb4b32ae5fbeb6715b65cf Add an option to use the system file picker on Linux and Windows diff -r a773b8f09292 -r 94cf5cc89227 Makefile --- a/Makefile Thu Oct 19 23:27:57 2023 -0700 +++ b/Makefile Sat Oct 21 19:22:01 2023 -0700 @@ -15,6 +15,7 @@ MEM:=mem_win.o TERMINAL:=terminal_win.o FONT:=nuklear_ui/font_win.o +CHOOSER:=nuklear_ui/filechooser_win.o NET:=net_win.o EXE:=.exe SO:=dll @@ -32,7 +33,7 @@ endif GLEW32S_LIB:=$(GLEW_PREFIX)/lib/Release/$(GLUDIR)/glew32s.lib CFLAGS:=-std=gnu99 -Wreturn-type -Werror=return-type -Werror=implicit-function-declaration -Wpointer-arith -Werror=pointer-arith -LDFLAGS:=-lm -lmingw32 -lws2_32 -mwindows +LDFLAGS:=-lm -lmingw32 -lws2_32 -lcomdlg32 -mwindows ifneq ($(MAKECMDGOALS),libblastem.dll) CFLAGS+= -I"$(SDL2_PREFIX)/include/SDL2" -I"$(GLEW_PREFIX)/include" -DGLEW_STATIC LDFLAGS+= $(GLEW32S_LIB) -L"$(SDL2_PREFIX)/lib" -lSDL2main -lSDL2 -lopengl32 -lglu32 @@ -52,6 +53,7 @@ ifeq ($(OS),Darwin) LIBS=sdl2 glew FONT:=nuklear_ui/font_mac.o +CHOOSER:=nuklear_ui/filechooser_null.o SO:=dylib else SO:=so @@ -71,6 +73,19 @@ endif #USE_GLES endif #USE_FBDEV FONT:=nuklear_ui/font.o +CHOOSER:=nuklear_ui/filechooser_gtk.o +GTKFLAGS:=$(shell pkg-config --cflags gtk+-3.0 2>/dev/null) +ifeq ($(GTKFLAGS),) +GTKFLAGS:=$(shell pkg-config --cflags gtk+-2.0 2>/dev/null) +ifeq ($(GTKFLAGS),) +CHOOSER:=nuklear_ui/filechooser_null.o +endif +endif +ifeq ($(GTKFLAGS),) +else +EXTRA_NUKLEAR_LDFLAGS:=-ldl +endif +CFLAGS+= $(GTKFLAGS) endif #Darwin ifdef HOST_ZLIB @@ -84,23 +99,24 @@ #This should really be based on whether or not the C compiler is clang rather than based on the OS CFLAGS+= -Wno-logical-op-parentheses endif + ifdef PORTABLE ifdef USE_GLES ifndef GLES_LIB GLES_LIB:=$(shell pkg-config --libs glesv2) endif LDFLAGS:=-lm $(GLES_LIB) -else +else #USE_GLES CFLAGS+= -DGLEW_STATIC -Iglew/include LDFLAGS:=-lm glew/lib/libGLEW.a -lEGL -endif +endif #USE_GLES ifeq ($(OS),Darwin) SDL_INCLUDE_PATH:=Frameworks/SDL2.framework/Headers CFLAGS+= -mmacosx-version-min=10.10 LDFLAGS+= -FFrameworks -framework SDL2 -framework OpenGL -framework AppKit -mmacosx-version-min=10.10 FIXUP:=install_name_tool -change @rpath/SDL2.framework/Versions/A/SDL2 @executable_path/Frameworks/SDL2.framework/Versions/A/SDL2 -else +else #Darwin SDL_INCLUDE_PATH:=sdl/include LDFLAGS+= -Wl,-rpath='$$ORIGIN/lib' -Llib -lSDL2 ifndef USE_GLES @@ -109,7 +125,7 @@ endif #Darwin CFLAGS+= -I$(SDL_INCLUDE_PATH) -else +else #PORTABLE ifeq ($(MAKECMDGOALS),libblastem.$(SO)) LDFLAGS:=-lm else @@ -180,6 +196,10 @@ TRANSOBJS=gen.o backend.o $(MEM) arena.o tern.o M68KOBJS=68kinst.o disasm.o +ifdef NO_FILE_CHOOSER +CHOOSER:=nuklear_ui/filechooser_nulll.o +endif + ifdef NEW_CORE Z80OBJS=z80.o z80inst.o M68KOBJS+= m68k.o @@ -198,7 +218,7 @@ endif AUDIOOBJS=ym2612.o psg.o wave.o flac.o vgm.o event_log.o render_audio.o rf5c164.o CONFIGOBJS=config.o tern.o util.o paths.o -NUKLEAROBJS=$(FONT) nuklear_ui/blastem_nuklear.o nuklear_ui/sfnt.o +NUKLEAROBJS=$(FONT) $(CHOOSER) nuklear_ui/blastem_nuklear.o nuklear_ui/sfnt.o RENDEROBJS=ppm.o controller_info.o ifdef USE_FBDEV RENDEROBJS+= render_fbdev.o @@ -226,6 +246,7 @@ CFLAGS+= -DDISABLE_NUKLEAR else MAINOBJS+= $(NUKLEAROBJS) +LDFLAGS+=$(EXTRA_NUKLEAR_LDFLAGS) endif ifeq ($(CPU),x86_64) diff -r a773b8f09292 -r 94cf5cc89227 default.cfg --- a/default.cfg Thu Oct 19 23:27:57 2023 -0700 +++ b/default.cfg Sat Oct 21 19:22:01 2023 -0700 @@ -404,6 +404,8 @@ extensions bin gen md smd sms gg zip gz cue iso #specifies the preferred save-state format, set to gst for Genecyst compatible states state_format native + #set to on to use the native file picker on your OS instead of the builtin one + use_native_filechooser off } system { @@ -435,4 +437,4 @@ } #Don't manually edit `version`, it's used for automatic config migration -version 3 +version 7 diff -r a773b8f09292 -r 94cf5cc89227 nuklear_ui/blastem_nuklear.c --- a/nuklear_ui/blastem_nuklear.c Thu Oct 19 23:27:57 2023 -0700 +++ b/nuklear_ui/blastem_nuklear.c Sat Oct 21 19:22:01 2023 -0700 @@ -9,6 +9,7 @@ #include "blastem_nuklear.h" #include "nuklear_rawfb.h" #include "font.h" +#include "filechooser.h" #include "../render.h" #include "../render_sdl.h" #include "../util.h" @@ -77,6 +78,35 @@ static const char *browser_setting_path; static const char **browser_ext_list; static uint32_t browser_num_exts; +static uint8_t use_native_filechooser; + +static void handle_chooser_result(uint8_t normal_open, char *full_path) +{ + if(normal_open) { + if (current_system) { + current_system->next_rom = full_path; + current_system->request_exit(current_system); + } else { + init_system_with_media(full_path, SYSTEM_UNKNOWN); + free(full_path); + } + + clear_view_stack(); + show_play_view(); + } else if (browser_setting_path) { + config = tern_insert_path(config, browser_setting_path, (tern_val){.ptrval = full_path}, TVAL_PTR); + config_dirty = 1; + browser_ext_list = NULL; + pop_view(); + } else { + lockon_media(full_path); + free(full_path); + + clear_view_stack(); + show_play_view(); + } +} + void view_file_browser(struct nk_context *context, uint8_t normal_open) { static dir_entry *entries; @@ -88,6 +118,18 @@ if (!browser_cur_path) { get_initial_browse_path(&browser_cur_path); } + if (use_native_filechooser && native_filechooser_available()) { + char *path = native_filechooser_pick(browser_label, browser_cur_path); + if (path) { + free(browser_cur_path); + browser_cur_path = path_dirname(path); + handle_chooser_result(normal_open, path); + } else { + browser_ext_list = NULL; + pop_view(); + } + return; + } if (!entries) { entries = get_dir_list(browser_cur_path, &num_entries); if (entries) { @@ -155,29 +197,7 @@ free_dir_list(entries, num_entries); entries = NULL; } else { - if(normal_open) { - if (current_system) { - current_system->next_rom = full_path; - current_system->request_exit(current_system); - } else { - init_system_with_media(full_path, SYSTEM_UNKNOWN); - free(full_path); - } - - clear_view_stack(); - show_play_view(); - } else if (browser_setting_path) { - config = tern_insert_path(config, browser_setting_path, (tern_val){.ptrval = full_path}, TVAL_PTR); - config_dirty = 1; - browser_ext_list = NULL; - pop_view(); - } else { - lockon_media(full_path); - free(full_path); - - clear_view_stack(); - show_play_view(); - } + handle_chooser_result(normal_open, full_path); } selected_entry = -1; } @@ -2277,10 +2297,15 @@ } selected_init = settings_dropdown(context, "Initial RAM Value", ram_inits, num_inits, selected_init, "system\0ram_init\0"); settings_toggle(context, "Remember ROM Path", "ui\0remember_path\0", 1); + settings_toggle(context, "Use Native File Picker", "ui\0use_native_filechooser\0", 0); settings_toggle(context, "Save config with EXE", "ui\0config_in_exe_dir\0", 0); settings_string(context, "Game Save Path", "ui\0save_path\0", "$USERDATA/blastem/$ROMNAME"); if (nk_button_label(context, "Back")) { + if (config_dirty) { + char *unf = tern_find_path(config, "ui\0use_native_filechooser\0", TVAL_PTR).ptrval; + use_native_filechooser = unf && !strcmp(unf, "on"); + } pop_view(); } nk_end(context); @@ -2689,6 +2714,8 @@ render_set_ui_render_fun(blastem_nuklear_render); render_set_event_handler(handle_event); render_set_gl_context_handlers(context_destroyed, context_created); + char *unf = tern_find_path(config, "ui\0use_native_filechooser\0", TVAL_PTR).ptrval; + use_native_filechooser = unf && !strcmp(unf, "on"); atexit(persist_config_exit); diff -r a773b8f09292 -r 94cf5cc89227 nuklear_ui/filechooser.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nuklear_ui/filechooser.h Sat Oct 21 19:22:01 2023 -0700 @@ -0,0 +1,7 @@ +#ifndef FILECHOOSER_H_ +#define FILECHOOSER_H_ + +uint8_t native_filechooser_available(void); +char* native_filechooser_pick(const char *title, const char *start_directory); + +#endif //FILECHOOSER_H_ diff -r a773b8f09292 -r 94cf5cc89227 nuklear_ui/filechooser_gtk.c --- /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 +#include +#include +#include +#include + +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; +} diff -r a773b8f09292 -r 94cf5cc89227 nuklear_ui/filechooser_null.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nuklear_ui/filechooser_null.c Sat Oct 21 19:22:01 2023 -0700 @@ -0,0 +1,12 @@ +#include +#include + +uint8_t native_filechooser_available(void) +{ + return 0; +} + +char* native_filechooser_pick(const char *title, const char *start_directory) +{ + return NULL; +} diff -r a773b8f09292 -r 94cf5cc89227 nuklear_ui/filechooser_win.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nuklear_ui/filechooser_win.c Sat Oct 21 19:22:01 2023 -0700 @@ -0,0 +1,34 @@ +#include +#include + +uint8_t native_filechooser_available(void) +{ + return 1; +} + +char* native_filechooser_pick(const char *title, const char *start_directory) +{ + char file_name[MAX_PATH] = ""; + OPENFILENAMEA ofn = { + .lStructSize = sizeof(ofn), + .hwndOwner = NULL, //TODO: should probably get the HWND of the main window + .lpstrFilter = + "All Files\0*.*\0" + "All Supported Types\0*.zip;*.bin;*.bin.gz;*.gen;*.gen.gz;*.md;*.md.gz;*.sms;*.sms.gz;*.gg;*.gg.gz;*.sg;*.sg.gz;*.cue;*.toc;*.flac;*.vgm;*.vgz;*.vgm.gz\0" + "Genesis/MD\0.zip;*.bin;*.bin.gz;*.gen;*.gen*.gz;*.md;*.md.gz\0" + "Sega/Mega CD\0*.cue;*.toc\0" + "Sega 8-bit\0*.sms;*.sms.gz;*.gg;*.gg.gz;*.sg;*.sg.gz\0" + "Audio/VGM\0*.flac;*.vgm;*.vgz;*.vgm.gz\0", + .nFilterIndex = 2, + .lpstrFile = file_name, + .nMaxFile = sizeof(file_name), + .lpstrInitialDir = start_directory, + .lpstrTitle = title, + .Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_LONGNAMES, + }; + char *ret = NULL; + if (GetOpenFileNameA(&ofn)) { + ret = strdup(file_name); + } + return ret; +}