SameBoy/gtk3/sameboy_application.c

397 lines
15 KiB
C
Raw Normal View History

#include "sameboy_application.h"
#include "config.h"
#include "widgets/main_window.h"
#include "widgets/about_dialog.h"
#include "widgets/preferences_window.h"
#define str(x) #x
#define xstr(x) str(x)
#define action_set_enabled(map, name, value) \
g_simple_action_set_enabled( \
G_SIMPLE_ACTION( \
g_action_map_lookup_action(G_ACTION_MAP(map), name) \
), \
value \
);
struct _SameBoyApplication {
GtkApplication parent;
struct CliOptionData cli_options;
const GThread *main_thread;
PreferencesWindow *preferences;
AboutDialog *about_dialog;
GDateTime *config_modification_date;
};
G_DEFINE_TYPE(SameBoyApplication, sameboy_application, GTK_TYPE_APPLICATION);
static void sameboy_application_init(SameBoyApplication *app) {
g_debug("sameboy_application_init(%p)", app);
// Define our command line parameters
GOptionEntry entries[] = {
{ "version", 'v', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, NULL, "Show the application version", NULL },
{ "fullscreen", 'f', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &app->cli_options.fullscreen, "Start in fullscreen mode", NULL },
{ "bootrom", 'b', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &app->cli_options.boot_rom_path, "Path to the boot ROM to use", "<file path>" },
{ "model", 'm', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, NULL, "Override the model type to emulate", "<model type>" },
{ "config", 'c', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &app->cli_options.config_path, "Override the path of the configuration file", "<file path>" },
{ "no-gl", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &app->cli_options.force_software_renderer, "Do not use OpenGL for rendering", NULL },
{ NULL }
};
// Setup our command line information
g_application_add_main_option_entries(G_APPLICATION(app), entries);
g_application_set_option_context_parameter_string(G_APPLICATION(app), "[FILE…]");
g_application_set_option_context_summary(G_APPLICATION(app), "SameBoy is an open source Game Boy (DMG) and Game Boy Color (CGB) emulator.");
}
static gint sameboy_application_handle_local_options(GApplication *gapp, GVariantDict *options) {
SameBoyApplication *app = SAMEBOY_APPLICATION(gapp);
g_debug("sameboy_application_handle_local_options");
guint32 count;
if (g_variant_dict_lookup(options, "version", "b", &count)) {
g_message("SameBoy v" xstr(VERSION));
return EXIT_SUCCESS;
}
// Handle model override
GVariant *model_name_var = g_variant_dict_lookup_value(options, "model", G_VARIANT_TYPE_STRING);
app->cli_options.model = -1;
if (model_name_var != NULL) {
const gchar *model_name = g_variant_get_string(model_name_var, NULL);
// TODO: Synchronize with GB_model_t (Core/gb.h)
if (g_str_has_prefix(model_name, "DMG")) {
if (g_str_has_suffix(model_name, "-B") || g_strcmp0(model_name, "DMG") == 0) {
app->cli_options.model = GB_MODEL_DMG_B;
}
else {
app->cli_options.model = GB_MODEL_DMG_B;
g_warning("Unsupported revision: %s\nFalling back to DMG-B", model_name);
}
}
else if (g_str_has_prefix(model_name, "SGB")) {
if (g_str_has_suffix(model_name, "-NTSC") || g_strcmp0(model_name, "SGB") == 0) {
app->cli_options.model = GB_MODEL_SGB;
}
else if (g_str_has_suffix(model_name, "-PAL")) {
app->cli_options.model = GB_MODEL_SGB | GB_MODEL_PAL_BIT;
}
else if (g_str_has_suffix(model_name, "2")) {
app->cli_options.model = GB_MODEL_SGB2;
}
else {
app->cli_options.model = GB_MODEL_SGB2;
g_warning("Unsupported revision: %s\nFalling back to SGB2", model_name);
}
}
else if (g_str_has_prefix(model_name, "CGB")) {
if (g_str_has_suffix(model_name, "-C")) {
app->cli_options.model = GB_MODEL_CGB_C;
}
else if (g_str_has_suffix(model_name, "-E") || g_strcmp0(model_name, "CGB") == 0) {
app->cli_options.model = GB_MODEL_CGB_E;
}
else {
app->cli_options.model = GB_MODEL_CGB_E;
g_warning("Unsupported revision: %s\nFalling back to CGB-E", model_name);
}
}
else if (g_str_has_prefix(model_name, "AGB")) {
app->cli_options.model = GB_MODEL_AGB;
}
else {
g_warning("Unknown model: %s", model_name);
exit(EXIT_FAILURE);
}
}
return G_APPLICATION_CLASS(sameboy_application_parent_class)->handle_local_options(G_APPLICATION(app), options);
}
static void activate_open(GSimpleAction *action, GVariant *parameter, gpointer user_data) {
SameBoyApplication *app = SAMEBOY_APPLICATION(user_data);
MainWindow *window = SAMEBOY_MAIN_WINDOW(gtk_application_get_active_window(GTK_APPLICATION(app)));
}
static void activate_close(GSimpleAction *action, GVariant *parameter, gpointer user_data) {
SameBoyApplication *app = SAMEBOY_APPLICATION(user_data);
MainWindow *window = SAMEBOY_MAIN_WINDOW(gtk_application_get_active_window(GTK_APPLICATION(app)));
}
static void activate_reset(GSimpleAction *action, GVariant *parameter, gpointer user_data) {
SameBoyApplication *app = SAMEBOY_APPLICATION(user_data);
MainWindow *window = SAMEBOY_MAIN_WINDOW(gtk_application_get_active_window(GTK_APPLICATION(app)));
}
static void on_pause_changed(GSimpleAction *action, GVariant *parameter, gpointer user_data) {
SameBoyApplication *app = SAMEBOY_APPLICATION(user_data);
MainWindow *window = SAMEBOY_MAIN_WINDOW(gtk_application_get_active_window(GTK_APPLICATION(app)));
}
static void activate_show_console(GSimpleAction *action, GVariant *parameter, gpointer user_data) {
SameBoyApplication *app = SAMEBOY_APPLICATION(user_data);
MainWindow *window = SAMEBOY_MAIN_WINDOW(gtk_application_get_active_window(GTK_APPLICATION(app)));
main_window_open_console_window(window);
}
static void activate_open_memory_viewer(GSimpleAction *action, GVariant *parameter, gpointer user_data) {
SameBoyApplication *app = SAMEBOY_APPLICATION(user_data);
MainWindow *window = SAMEBOY_MAIN_WINDOW(gtk_application_get_active_window(GTK_APPLICATION(app)));
main_window_open_memory_viewer_window(window);
}
static void activate_open_vram_viewer(GSimpleAction *action, GVariant *parameter, gpointer user_data) {
SameBoyApplication *app = SAMEBOY_APPLICATION(user_data);
MainWindow *window = SAMEBOY_MAIN_WINDOW(gtk_application_get_active_window(GTK_APPLICATION(app)));
main_window_open_vram_viewer_window(window);
}
static void activate_break_debugger(GSimpleAction *action, GVariant *parameter, gpointer user_data) {
SameBoyApplication *app = SAMEBOY_APPLICATION(user_data);
MainWindow *window = SAMEBOY_MAIN_WINDOW(gtk_application_get_active_window(GTK_APPLICATION(app)));
}
static void on_developer_mode_changed(GSimpleAction *action, GVariant *parameter, gpointer user_data) {
SameBoyApplication *app = SAMEBOY_APPLICATION(user_data);
MainWindow *window = SAMEBOY_MAIN_WINDOW(gtk_application_get_active_window(GTK_APPLICATION(app)));
}
static void activate_clear_console(GSimpleAction *action, GVariant *parameter, gpointer user_data) {
SameBoyApplication *app = SAMEBOY_APPLICATION(user_data);
MainWindow *window = SAMEBOY_MAIN_WINDOW(gtk_application_get_active_window(GTK_APPLICATION(app)));
}
static void activate_open_gtk_debugger(GSimpleAction *action, GVariant *parameter, gpointer user_data) {
gtk_window_set_interactive_debugging(true);
}
static void activate_quit(GSimpleAction *action, GVariant *parameter, gpointer user_data) {
SameBoyApplication *app = SAMEBOY_APPLICATION(user_data);
MainWindow *window = SAMEBOY_MAIN_WINDOW(gtk_application_get_active_window(GTK_APPLICATION(app)));
}
/**
* Opens the global about dialog.
*/
static void activate_about(GSimpleAction *action, GVariant *parameter, gpointer user_data) {
SameBoyApplication *app = SAMEBOY_APPLICATION(user_data);
gtk_dialog_run(GTK_DIALOG(app->about_dialog));
gtk_widget_hide(GTK_WIDGET(app->about_dialog));
}
/**
* Opens the global preferences menu.
*/
static void activate_preferences(GSimpleAction *action, GVariant *parameter, gpointer user_data) {
SameBoyApplication *app = SAMEBOY_APPLICATION(user_data);
gtk_widget_show_all(GTK_WIDGET(app->preferences));
}
static void on_mute_changed(GSimpleAction *action, GVariant *parameter, gpointer user_data) {
SameBoyApplication *app = SAMEBOY_APPLICATION(user_data);
MainWindow *window = SAMEBOY_MAIN_WINDOW(gtk_application_get_active_window(GTK_APPLICATION(app)));
}
static const GActionEntry file_entries[] = {
{ "open", activate_open, NULL, NULL, NULL },
{ "close", activate_close, NULL, NULL, NULL },
};
static const GActionEntry emulation_entries[] = {
{ "reset", activate_reset, NULL, NULL, NULL },
{ "pause", NULL, NULL, "false", on_pause_changed },
{ "save_state", NULL, NULL, NULL, NULL },
{ "load_state", NULL, NULL, NULL, NULL },
};
static const GActionEntry developer_entries[] = {
{ "show_console", activate_show_console, NULL, NULL, NULL },
{ "open_memory_viewer", activate_open_memory_viewer, NULL, NULL, NULL },
{ "open_vram_viewer", activate_open_vram_viewer, NULL, NULL, NULL },
{ "break_debugger", activate_break_debugger, NULL, NULL, NULL },
{ "toggle_developer_mode", NULL, NULL, "false", on_developer_mode_changed },
{ "clear_console", activate_clear_console, NULL, NULL, NULL },
{ "open_gtk_debugger", activate_open_gtk_debugger, NULL, NULL, NULL },
};
static GActionEntry app_entries[] = {
{ "quit", activate_quit, NULL, NULL, NULL },
{ "about", activate_about, NULL, NULL, NULL },
{ "preferences", activate_preferences, NULL, NULL, NULL },
{ "toggle_mute", NULL, NULL, "false", on_mute_changed },
};
// WHY DO WE NEED SUCH AN UGLY METHOD, GTK?!
static void action_entries_set_enabled(SameBoyApplication *app, const GActionEntry *entries, unsigned n_entries, bool value) {
// Assumes null-terminated if n_entries == -1
for (unsigned i = 0; n_entries == -1 ? entries[i].name != NULL : i < n_entries; i++) {
const GActionEntry *entry = &entries[i];
if (entry->name == NULL) continue;
action_set_enabled(app, entry->name, value);
}
}
static void create_action_groups(SameBoyApplication *app) {
g_action_map_add_action_entries(G_ACTION_MAP(app), emulation_entries, G_N_ELEMENTS(emulation_entries), app);
g_action_map_add_action_entries(G_ACTION_MAP(app), developer_entries, G_N_ELEMENTS(developer_entries), app);
g_action_map_add_action_entries(G_ACTION_MAP(app), app_entries, G_N_ELEMENTS(app_entries), app);
g_action_map_add_action_entries(G_ACTION_MAP(app), file_entries, G_N_ELEMENTS(file_entries), app);
action_set_enabled(app, "close", false);
action_entries_set_enabled(app, emulation_entries, G_N_ELEMENTS(emulation_entries), false);
}
static void sameboy_application_startup(GApplication *gapp) {
G_APPLICATION_CLASS(sameboy_application_parent_class)->startup(gapp);
// TODO:
// signal(SIGINT, quit_interrupt);
g_debug("GTK version %u.%u.%u", gtk_get_major_version(), gtk_get_minor_version(), gtk_get_micro_version());
SameBoyApplication *app = SAMEBOY_APPLICATION(gapp);
g_debug("sameboy_application_startup");
g_debug("config_path: %s", app->cli_options.config_path);
g_debug("boot_rom_path: %s", app->cli_options.boot_rom_path);
g_debug("fullscreen: %d", app->cli_options.fullscreen);
g_debug("model: %d", app->cli_options.model);
g_debug("force_software_renderer: %d", app->cli_options.force_software_renderer);
init_config(G_APPLICATION(app), app->cli_options.config_path, &app->config_modification_date);
app->preferences = preferences_window_new();
app->about_dialog = about_dialog_new();
gtk_application_add_window(GTK_APPLICATION(gapp), GTK_WINDOW(app->preferences));
create_action_groups(app);
#if NDEBUG
// Disable when not compiled in debug mode
action_set_enabled(app, "open_gtk_debugger", false);
#endif
GdkScreen *screen = gdk_screen_get_default();
GtkCssProvider *provider = gtk_css_provider_new();
gtk_css_provider_load_from_resource(provider, RESOURCE_PREFIX "css/main.css");
gtk_style_context_add_provider_for_screen(screen, GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
// Just hide our sub-windows when closing them
g_signal_connect(app->preferences, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
}
static void open_file(SameBoyApplication *app, GFile *file) {
if (file != NULL) {
gchar *path = g_file_get_path(file);
g_debug("File path: %s", path);
g_free(path);
}
MainWindow *window = main_window_new(SAMEBOY_APPLICATION(app), app->cli_options.force_software_renderer);
main_window_setup_menu(window, config.emulation.model);
// Define a set of window icons
GList *icon_list = NULL;
static char* icons[] = {
RESOURCE_PREFIX "logo_256.png",
RESOURCE_PREFIX "logo_128.png",
RESOURCE_PREFIX "logo_64.png",
RESOURCE_PREFIX "logo_48.png",
RESOURCE_PREFIX "logo_32.png",
RESOURCE_PREFIX "logo_16.png"
};
GdkPixbuf *icon = gdk_pixbuf_new_from_resource(icons[5], NULL);
if (icon) {
gtk_window_set_icon(GTK_WINDOW(window), icon);
gtk_window_set_default_icon(icon);
}
// Create list of GdkPixbufs
for (int i = 0; i < (sizeof(icons) / sizeof(const char*)); ++i) {
GdkPixbuf *icon = gdk_pixbuf_new_from_resource(icons[i], NULL);
if (!icon) continue;
icon_list = g_list_prepend(icon_list, icon);
}
// Let GTK choose the proper icon
gtk_window_set_icon_list(GTK_WINDOW(window), icon_list);
gtk_window_set_default_icon_list(icon_list);
gtk_about_dialog_set_logo(GTK_ABOUT_DIALOG(app->about_dialog), gdk_pixbuf_new_from_resource(icons[2], NULL)); // reuse the 64x64 icon
gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(app->about_dialog), "v" xstr(VERSION));
g_list_free_full(icon_list, g_object_unref);
if (app->cli_options.fullscreen) {
main_window_fullscreen(window, true);
}
gtk_window_present(GTK_WINDOW(window));
}
static void sameboy_application_activate(GApplication *gapp) {
SameBoyApplication *app = SAMEBOY_APPLICATION(gapp);
g_debug("sameboy_application_activate");
open_file(app, NULL);
G_APPLICATION_CLASS(sameboy_application_parent_class)->activate(gapp);
}
static void sameboy_application_open(GApplication *gapp, GFile **files, int n_files, const char *hint) {
SameBoyApplication *app = SAMEBOY_APPLICATION(gapp);
g_debug("sameboy_application_open(hint = \"%s\")", hint);
if (n_files >= 1) {
if (n_files > 1) {
g_warning("More than one file specified");
}
open_file(app, files[0]);
}
G_APPLICATION_CLASS(sameboy_application_parent_class)->open(gapp, files, n_files, hint);
}
static void sameboy_application_shutdown(GApplication *gapp) {
SameBoyApplication *app = SAMEBOY_APPLICATION(gapp);
g_debug("sameboy_application_shutdown");
G_APPLICATION_CLASS(sameboy_application_parent_class)->shutdown(gapp);
}
static void sameboy_application_class_init(SameBoyApplicationClass *class) {
G_APPLICATION_CLASS(class)->handle_local_options = sameboy_application_handle_local_options;
G_APPLICATION_CLASS(class)->startup = sameboy_application_startup;
G_APPLICATION_CLASS(class)->activate = sameboy_application_activate;
G_APPLICATION_CLASS(class)->open = sameboy_application_open;
G_APPLICATION_CLASS(class)->shutdown = sameboy_application_shutdown;
}
SameBoyApplication *sameboy_application_new(void) {
return g_object_new(
SAMEBOY_APPLICATION_TYPE,
"application-id", APP_ID,
// "flags", G_APPLICATION_NON_UNIQUE | G_APPLICATION_HANDLES_OPEN,
"flags", G_APPLICATION_HANDLES_OPEN,
NULL
);
}
void sameboy_application_preferences_signal_connect(SameBoyApplication *app, const gchar *detailed_signal, GCallback c_handler, gpointer data) {
g_signal_connect(app->preferences, detailed_signal, c_handler, data);
}
struct CliOptionData *sameboy_application_get_cli_options(SameBoyApplication *self) {
return &self->cli_options;
}