#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", "" }, { "model", 'm', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, NULL, "Override the model type to emulate", "" }, { "config", 'c', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &app->cli_options.config_path, "Override the path of the configuration file", "" }, { "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; }