From 39af3960049ca6cf8bc3ad882ebc63fa07815048 Mon Sep 17 00:00:00 2001 From: Maximilian Mader Date: Thu, 7 Jan 2021 22:35:41 +0100 Subject: [PATCH] [GTK3] WIP! Start refactoring of the spaghetti main.c into a subclass of GtkApplication --- gtk3/main.c | 1433 +---------------------------- gtk3/resources/ui/about_dialog.ui | 74 ++ gtk3/sameboy.gresource.xml | 1 + gtk3/sameboy_application.c | 388 ++++++++ gtk3/sameboy_application.h | 10 + gtk3/types.h | 1 - gtk3/widgets/about_dialog.c | 19 + gtk3/widgets/about_dialog.h | 11 + gtk3/widgets/console_window.c | 22 +- gtk3/widgets/console_window.h | 15 +- gtk3/widgets/main_menu.c | 4 +- gtk3/widgets/main_window.c | 180 +++- gtk3/widgets/main_window.h | 10 +- gtk3/widgets/preferences_window.c | 89 +- gtk3/widgets/preferences_window.h | 2 +- gtk3/widgets/printer_window.c | 6 +- 16 files changed, 732 insertions(+), 1533 deletions(-) create mode 100644 gtk3/resources/ui/about_dialog.ui create mode 100644 gtk3/sameboy_application.c create mode 100644 gtk3/sameboy_application.h create mode 100644 gtk3/widgets/about_dialog.c create mode 100644 gtk3/widgets/about_dialog.h diff --git a/gtk3/main.c b/gtk3/main.c index 5b4a143..4b36c56 100644 --- a/gtk3/main.c +++ b/gtk3/main.c @@ -1,1435 +1,6 @@ #define G_LOG_USE_STRUCTURED - -#include -#include -#include -#include -#include - -#include - -#include "types.h" -#include "config.h" -#include "util.h" - -#include "widgets/console_window.h" -#include "widgets/main_window.h" -#include "widgets/preferences_window.h" -#include "widgets/printer_window.h" -#include "widgets/vram_viewer_window.h" - -// used for audio and game controllers -#include "SDL.h" -#include "../SDL/audio/audio.h" - -#define JOYSTICK_HIGH 0x4000 -#define JOYSTICK_LOW 0x3800 - -#define BUTTON_MASK_A 0x01 -#define BUTTON_MASK_B 0x02 -#define BUTTON_MASK_START 0x04 -#define BUTTON_MASK_SELECT 0x08 -#define BUTTON_MASK_UP 0x10 -#define BUTTON_MASK_DOWN 0x20 -#define BUTTON_MASK_LEFT 0x40 -#define BUTTON_MASK_RIGHT 0x80 - -#define str(x) #x -#define xstr(x) str(x) -#define get_object(id) gtk_builder_get_object(gui_data.builder, id) -#define builder_get(type, id) type(get_object(id)) -#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); - -// Initialize the GuiData -static GuiData gui_data = { - .cli_options = { - .fullscreen = false, - .model = -1, - .force_software_renderer = false, - }, - - .prev_model = -1, - - .running = false, - .stopping = false, - .stopped = false, - - .audio_initialized = false, - .border_mode_changed = false, - - .underclock_down = false, - .rewind_down = false, - .rewind_paused = false, - .turbo_down = false, - .clock_mutliplier = 1.0, - .analog_clock_multiplier = 1.0, -}; - -static GB_gameboy_t gb; - -// Forward declarations of the actions -static void activate_open(GSimpleAction *action, GVariant *parameter, gpointer app); -static void activate_close(GSimpleAction *action, GVariant *parameter, gpointer app); -static void activate_reset(GSimpleAction *action, GVariant *parameter, gpointer app); -static void activate_show_console(GSimpleAction *action, GVariant *parameter, gpointer app); -static void activate_break_debugger(GSimpleAction *action, GVariant *parameter, gpointer app); -static void activate_open_gtk_debugger(GSimpleAction *action, GVariant *parameter, gpointer app); -static void activate_open_memory_viewer(GSimpleAction *action, GVariant *parameter, gpointer app); -static void activate_open_vram_viewer(GSimpleAction *action, GVariant *parameter, gpointer app); -static void activate_clear_console(GSimpleAction *action, GVariant *parameter, gpointer app); -static void activate_quit(GSimpleAction *action, GVariant *parameter, gpointer app); -static void activate_about(GSimpleAction *action, GVariant *parameter, gpointer app); -static void activate_preferences(GSimpleAction *action, GVariant *parameter, gpointer app); -static void on_pause_changed(GSimpleAction *action, GVariant *value, gpointer user_data_ptr); -static void on_developer_mode_changed(GSimpleAction *action, GVariant *value, gpointer user_data_ptr); -static void on_mute_changed(GSimpleAction *action, GVariant *value, gpointer user_data_ptr); - -static const GActionEntry file_entries[] = { - { "open", activate_open, NULL, NULL, NULL }, - { "close", activate_close, NULL, NULL, NULL }, -}; - -static const GActionEntry edit_entries[] = { - -}; - -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 const 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 }, -}; - -static const char* get_sdl_joystick_power_level_name(SDL_JoystickPowerLevel level) { - switch (level) { - case SDL_JOYSTICK_POWER_EMPTY: return "Empty"; - case SDL_JOYSTICK_POWER_LOW: return "Low"; - case SDL_JOYSTICK_POWER_MEDIUM: return "Medium"; - case SDL_JOYSTICK_POWER_FULL: return "Full"; - case SDL_JOYSTICK_POWER_WIRED: return "Wired"; - case SDL_JOYSTICK_POWER_MAX: return "Max"; - - case SDL_JOYSTICK_POWER_UNKNOWN: - default: - return "Unknown"; - } -} - -// This function gets called after the parsing of the commandline options has occurred. -static gint handle_local_options(GApplication *app, GVariantDict *options, gpointer null_ptr) { - 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); - 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) { - gui_data.cli_options.model = GB_MODEL_DMG_B; - } - else { - gui_data.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) { - gui_data.cli_options.model = GB_MODEL_SGB; - } - else if (g_str_has_suffix(model_name, "-PAL")) { - gui_data.cli_options.model = GB_MODEL_SGB | GB_MODEL_PAL_BIT; - } - else if (g_str_has_suffix(model_name, "2")) { - gui_data.cli_options.model = GB_MODEL_SGB2; - } - else { - gui_data.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")) { - gui_data.cli_options.model = GB_MODEL_CGB_C; - } - else if (g_str_has_suffix(model_name, "-E") || g_strcmp0(model_name, "CGB") == 0) { - gui_data.cli_options.model = GB_MODEL_CGB_E; - } - else { - gui_data.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")) { - gui_data.cli_options.model = GB_MODEL_AGB; - } - else { - g_warning("Unknown model: %s", model_name); - exit(EXIT_FAILURE); - } - } - - return -1; -} - -static gboolean init_controllers(void) { - SDL_version compiled; - SDL_version linked; - SDL_VERSION(&compiled); - SDL_GetVersion(&linked); - g_debug("Compiled against SDL version %d.%d.%d", compiled.major, compiled.minor, compiled.patch); - g_debug("Linked against SDL version %d.%d.%d", linked.major, linked.minor, linked.patch); - - g_debug("Initializing game controllers"); - if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) < 0) { - g_warning("Failed to initialize game controller support: %s", SDL_GetError()); - return false; - } - - g_debug("Initializing haptic feedback"); - if (SDL_InitSubSystem(SDL_INIT_HAPTIC) < 0) { - g_warning("Failed to initialize haptic feedback support: %s", SDL_GetError()); - } - - g_debug("Loading custom game controller database"); - GError *error = NULL; - GBytes *db_f = g_resources_lookup_data(RESOURCE_PREFIX "gamecontrollerdb.txt", G_RESOURCE_LOOKUP_FLAGS_NONE, &error); - - if (db_f != NULL) { - gsize db_data_size = 0; - const guchar *db_data = g_bytes_get_data(db_f, &db_data_size); - const gint val = SDL_GameControllerAddMappingsFromRW(SDL_RWFromMem((void *)db_data, db_data_size), 1); - - if (val < 0) { - g_warning("Failed to load controller mappings: %s", SDL_GetError()); - } - - g_bytes_unref(db_f); - } - - if (error != NULL) g_clear_error(&error); - - g_message("Number of found controllers: %d", SDL_NumJoysticks()); - - // In the “worst” case all joysticks are valid game controllers - gui_data.controllers = g_malloc0(sizeof(struct Controller_t) * SDL_NumJoysticks()); - - for (int i = 0; i < SDL_NumJoysticks(); ++i) { - if (SDL_IsGameController(i)) { - struct Controller_t *s = &gui_data.controllers[i]; - s->controller = SDL_GameControllerOpen(i); - - if (s->controller) { - SDL_Joystick *joystick = SDL_GameControllerGetJoystick(s->controller); - SDL_JoystickPowerLevel power_level = SDL_JoystickCurrentPowerLevel(joystick); - - if (SDL_JoystickIsHaptic(joystick)) { - s->haptic = SDL_HapticOpenFromJoystick(joystick); - - if (s->haptic && SDL_HapticRumbleSupported(s->haptic)) { - SDL_HapticRumbleInit(s->haptic); - } - else { - if (s->haptic == NULL) { - g_warning("%s", SDL_GetError()); - } - - SDL_HapticClose(s->haptic); - s->haptic = NULL; - } - } - - // Blacklist the WUP-028 for now - if (SDL_JoystickGetVendor(joystick) == 0x057e - && SDL_JoystickGetProduct(joystick) == 0x0337 - && SDL_JoystickGetProductVersion(joystick) == 0x0100) { - s->ignore_rumble = true; - } - - char guid_str[33]; - SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick); - SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str)); - - g_message("Controller #%u (%s): %s; Haptic Feedback: %d; Power level: %s; Player index: %u; Instance ID: %u", i, guid_str, SDL_GameControllerName(s->controller), s->haptic != NULL, get_sdl_joystick_power_level_name(power_level), SDL_JoystickGetPlayerIndex(joystick), SDL_JoystickInstanceID(joystick)); - gui_data.controller_count++; - } - else { - g_warning("Could not open controller %i: %s", i, SDL_GetError()); - } - } - } - - return true; -} - -static gboolean init_audio(void) { - bool audio_playing = GB_audio_is_playing(); - - if (gui_data.audio_initialized) { - GB_audio_destroy(); - gui_data.audio_initialized = false; - } - - #ifdef USE_SDL_AUDIO - if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { - g_warning("Failed to initialize audio: %s", SDL_GetError()); - return false; - } - #endif - - GB_audio_init(gui_data.sample_rate); - GB_set_sample_rate(&gb, GB_audio_get_sample_rate()); - - // restore playing state - GB_audio_set_paused(!audio_playing); - - return gui_data.audio_initialized = true; -} - -static void gb_audio_callback(GB_gameboy_t *gb, GB_sample_t *sample) { - if (gui_data.turbo_down) { - static unsigned skip = 0; - skip++; - - if (skip == GB_audio_get_sample_rate() / 8) { - skip = 0; - } - - if (skip > GB_audio_get_sample_rate() / 16) { - return; - } - } - - if (GB_audio_get_queue_length() / sizeof(*sample) > GB_audio_get_sample_rate() / 4) { - return; - } - - GB_audio_queue_sample(sample); -} - -static void rumble_callback(GB_gameboy_t *gb, double amp) { - if (!gui_data.controllers || gui_data.controller_count == 0 || !gui_data.last_used_controller) return; - - struct Controller_t *s = gui_data.last_used_controller; - - if (s->ignore_rumble) return; - - if (s->haptic) { - if (amp > 0.0) { - SDL_HapticRumblePlay(s->haptic, amp, 100.0); - } - else { - SDL_HapticRumbleStop(s->haptic); - } - } - else { - if (amp == 0.0) { - SDL_GameControllerRumble(s->controller, 0, 0, 0); - } - else { - Uint16 intensity = (float) 0xFFFF * amp; - SDL_GameControllerRumble(s->controller, intensity, intensity, 100); - } - } -} - -// WHY DO WE NEED SUCH AN UGLY METHOD, GTK?! -static void action_entries_set_enabled(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(gui_data.main_application, entry->name, value); - } -} - -static void stop(void) { - if (!gui_data.running) return; - - GB_audio_set_paused(true); - GB_debugger_set_disabled(&gb, true); - - gui_data.stopping = true; - gui_data.running = false; - - if (GB_debugger_is_stopped(&gb)) { - abort_debugger(gui_data.console); - } - - while (gui_data.stopping); - - GB_debugger_set_disabled(&gb, false); - gui_data.stopped = true; -} - -static gboolean on_vblank(GB_gameboy_t *gb) { - main_window_queue_render(gui_data.main_window); - gtk_widget_queue_draw(GTK_WIDGET(gui_data.vram_viewer)); - - return false; -} - -static void vblank(GB_gameboy_t *gb) { - main_window_flip(gui_data.main_window); - - if (gui_data.border_mode_changed) { - GB_set_border_mode(gb, config_get_display_border_mode()); - - main_window_set_resolution(gui_data.main_window, GB_get_screen_width(gb), GB_get_screen_height(gb)); - GB_set_pixels_output(gb, main_window_get_pixels(gui_data.main_window)); - - gui_data.border_mode_changed = false; - } - - GB_set_pixels_output(gb, main_window_get_pixels(gui_data.main_window)); - - // Handle the speed modifiers: - // The binary slowdown is limited to half speed. - // The analog multiplier can go down to a third and up to three times full speed. - if (gui_data.underclock_down && gui_data.clock_mutliplier > 0.5) { - gui_data.clock_mutliplier -= 1.0 / 16; - //gui_data.clock_mutliplier = clamp_double(0.5, 1.0, gui_data.clock_mutliplier - 1.0 / 16); - GB_set_clock_multiplier(gb, gui_data.clock_mutliplier); - } - else if (!gui_data.underclock_down && gui_data.clock_mutliplier < 1.0) { - gui_data.clock_mutliplier += 1.0 / 16; - //gui_data.clock_mutliplier = clamp_double(0.5, 1.0, gui_data.clock_mutliplier + 1.0 / 16); - GB_set_clock_multiplier(gb, gui_data.clock_mutliplier); - } - else if (config.controls.analog_speed_controls && gui_data.analog_clock_multiplier_valid) { - GB_set_clock_multiplier(gb, gui_data.analog_clock_multiplier); - - if (gui_data.analog_clock_multiplier == 1.0) { - gui_data.analog_clock_multiplier_valid = false; - } - } - - gui_data.do_rewind = gui_data.rewind_down; - - vram_viewer_update(gui_data.vram_viewer, gb); - - GB_frame_blending_mode_t mode = config_get_frame_blending_mode(); - - if (!main_window_get_previous_buffer(gui_data.main_window)) { - mode = GB_FRAME_BLENDING_MODE_DISABLED; - } - else if (mode == GB_FRAME_BLENDING_MODE_ACCURATE) { - mode = GB_FRAME_BLENDING_MODE_DISABLED; - - if (GB_is_sgb(gb)) { - mode = GB_FRAME_BLENDING_MODE_SIMPLE; - } - else { - mode = GB_is_odd_frame(gb)? GB_FRAME_BLENDING_MODE_ACCURATE_ODD : GB_FRAME_BLENDING_MODE_ACCURATE_EVEN; - } - } - - main_window_set_blending_mode(gui_data.main_window, mode); - - g_idle_add((GSourceFunc) on_vblank, gb); -} - -static void handle_events(GB_gameboy_t *gb) { - SDL_GameControllerUpdate(); - - uint8_t controller_state = 0; - gui_data.analog_clock_multiplier = 1.0; - - for (unsigned i = 0; i < gui_data.controller_count; i++) { - struct Controller_t *s = &gui_data.controllers[i]; - - int16_t left_x_axis = SDL_GameControllerGetAxis(s->controller, SDL_CONTROLLER_AXIS_LEFTX); - int16_t left_y_axis = SDL_GameControllerGetAxis(s->controller, SDL_CONTROLLER_AXIS_LEFTY); - - int16_t right_x_axis = SDL_GameControllerGetAxis(s->controller, SDL_CONTROLLER_AXIS_RIGHTX); - int16_t right_y_axis = SDL_GameControllerGetAxis(s->controller, SDL_CONTROLLER_AXIS_RIGHTY); - - if (config.controls.analog_speed_controls) { - double left_trigger = (double) SDL_GameControllerGetAxis(s->controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT) / (double) 32767; - double right_trigger = (double) SDL_GameControllerGetAxis(s->controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT) / (double) 32767; - - if (left_trigger > 0.0) { - gui_data.analog_clock_multiplier = min_double(gui_data.analog_clock_multiplier, clamp_double(1.0 / 3, 1.0, 1 - left_trigger + 0.2)); - gui_data.analog_clock_multiplier_valid = true; - } - else if (right_trigger > 0.0) { - gui_data.analog_clock_multiplier = max_double(gui_data.analog_clock_multiplier, clamp_double(1.0, 3.0, right_trigger * 3 + 0.8)); - gui_data.analog_clock_multiplier_valid = true; - } - } - - if (left_x_axis >= JOYSTICK_HIGH || right_x_axis >= JOYSTICK_HIGH) { - gui_data.last_used_controller = s; - controller_state |= BUTTON_MASK_RIGHT; - } - else if (left_x_axis <= -JOYSTICK_HIGH || right_x_axis <= -JOYSTICK_HIGH) { - gui_data.last_used_controller = s; - controller_state |= BUTTON_MASK_LEFT; - } - - if (left_y_axis >= JOYSTICK_HIGH || right_y_axis >= JOYSTICK_HIGH) { - gui_data.last_used_controller = s; - controller_state |= BUTTON_MASK_DOWN; - } - else if (left_y_axis <= -JOYSTICK_HIGH || right_y_axis <= -JOYSTICK_HIGH) { - gui_data.last_used_controller = s; - controller_state |= BUTTON_MASK_UP; - } - - if (SDL_GameControllerGetButton(s->controller, SDL_CONTROLLER_BUTTON_DPAD_RIGHT)) { - gui_data.last_used_controller = s; - controller_state |= BUTTON_MASK_RIGHT; - } - - if (SDL_GameControllerGetButton(s->controller, SDL_CONTROLLER_BUTTON_DPAD_LEFT)) { - gui_data.last_used_controller = s; - controller_state |= BUTTON_MASK_LEFT; - } - - if (SDL_GameControllerGetButton(s->controller, SDL_CONTROLLER_BUTTON_DPAD_UP)) { - gui_data.last_used_controller = s; - controller_state |= BUTTON_MASK_UP; - } - - if (SDL_GameControllerGetButton(s->controller, SDL_CONTROLLER_BUTTON_DPAD_DOWN)) { - gui_data.last_used_controller = s; - controller_state |= BUTTON_MASK_DOWN; - } - - if (SDL_GameControllerGetButton(s->controller, SDL_CONTROLLER_BUTTON_A)) { - gui_data.last_used_controller = s; - controller_state |= BUTTON_MASK_A; - } - - if (SDL_GameControllerGetButton(s->controller, SDL_CONTROLLER_BUTTON_B)) { - gui_data.last_used_controller = s; - controller_state |= BUTTON_MASK_B; - } - - if (SDL_GameControllerGetButton(s->controller, SDL_CONTROLLER_BUTTON_BACK)) { - gui_data.last_used_controller = s; - controller_state |= BUTTON_MASK_SELECT; - } - - if (SDL_GameControllerGetButton(s->controller, SDL_CONTROLLER_BUTTON_START)) { - gui_data.last_used_controller = s; - controller_state |= BUTTON_MASK_START; - } - } - - GB_set_key_state(gb, GB_KEY_RIGHT, (gui_data.pressed_buttons & BUTTON_MASK_RIGHT) | (controller_state & BUTTON_MASK_RIGHT)); - GB_set_key_state(gb, GB_KEY_LEFT, (gui_data.pressed_buttons & BUTTON_MASK_LEFT) | (controller_state & BUTTON_MASK_LEFT)); - GB_set_key_state(gb, GB_KEY_UP, (gui_data.pressed_buttons & BUTTON_MASK_UP) | (controller_state & BUTTON_MASK_UP)); - GB_set_key_state(gb, GB_KEY_DOWN, (gui_data.pressed_buttons & BUTTON_MASK_DOWN) | (controller_state & BUTTON_MASK_DOWN)); - GB_set_key_state(gb, GB_KEY_A, (gui_data.pressed_buttons & BUTTON_MASK_A) | (controller_state & BUTTON_MASK_A)); - GB_set_key_state(gb, GB_KEY_B, (gui_data.pressed_buttons & BUTTON_MASK_B) | (controller_state & BUTTON_MASK_B)); - GB_set_key_state(gb, GB_KEY_SELECT, (gui_data.pressed_buttons & BUTTON_MASK_SELECT) | (controller_state & BUTTON_MASK_SELECT)); - GB_set_key_state(gb, GB_KEY_START, (gui_data.pressed_buttons & BUTTON_MASK_START) | (controller_state & BUTTON_MASK_START)); -} - -static void load_boot_rom(GB_gameboy_t *gb, GB_boot_rom_t type) { - GError *error = NULL; - char *boot_rom_path = NULL; - GBytes *boot_rom_f = NULL; - const guchar *boot_rom_data; - gsize boot_rom_size; - - static const char *const names[] = { - [GB_BOOT_ROM_DMG0] = "dmg0_boot.bin", - [GB_BOOT_ROM_DMG] = "dmg_boot.bin", - [GB_BOOT_ROM_MGB] = "mgb_boot.bin", - [GB_BOOT_ROM_SGB] = "sgb_boot.bin", - [GB_BOOT_ROM_SGB2] = "sgb2_boot.bin", - [GB_BOOT_ROM_CGB0] = "cgb0_boot.bin", - [GB_BOOT_ROM_CGB] = "cgb_boot.bin", - [GB_BOOT_ROM_AGB] = "agb_boot.bin", - }; - - const char *const boot_rom_name = names[type]; - - if (gui_data.cli_options.boot_rom_path != NULL) { - g_message("[CLI override] Trying to load boot ROM from %s", gui_data.cli_options.boot_rom_path); - if (GB_load_boot_rom(gb, gui_data.cli_options.boot_rom_path)) { - g_warning("Falling back to boot ROM from config"); - goto config_boot_rom; - } - } - else { config_boot_rom: - if (config.emulation.boot_rom_path != NULL && g_strcmp0(config.emulation.boot_rom_path, "other") != 0 && g_strcmp0(config.emulation.boot_rom_path, "auto") != 0) { - boot_rom_path = g_build_filename(config.emulation.boot_rom_path, boot_rom_name, NULL); - g_message("Trying to load boot ROM from %s", boot_rom_path); - - if (GB_load_boot_rom(gb, boot_rom_path)) { - g_free(boot_rom_path); - g_warning("Falling back to internal boot ROM"); - goto internal_boot_rom; - } - - g_free(boot_rom_path); - } - else { internal_boot_rom: - boot_rom_path = g_build_filename(RESOURCE_PREFIX "bootroms/", boot_rom_name, NULL); - boot_rom_f = g_resources_lookup_data(boot_rom_path, G_RESOURCE_LOOKUP_FLAGS_NONE, &error); - - g_message("Loading internal boot ROM: %s", boot_rom_path); - g_free(boot_rom_path); - - if (boot_rom_f == NULL) { - g_warning("Failed to load internal boot ROM: %s", boot_rom_path); - g_error_free(error); - exit(EXIT_FAILURE); - } - - boot_rom_data = g_bytes_get_data(boot_rom_f, &boot_rom_size); - GB_load_boot_rom_from_buffer(gb, boot_rom_data, boot_rom_size); - - g_bytes_unref(boot_rom_f); - } - } -} - -static char *wrapped_console_get_async_input(GB_gameboy_t *gb) { - return console_get_async_input(gui_data.console, gb); -} - -static char *wrapped_console_get_sync_input(GB_gameboy_t *gb) { - return console_get_sync_input(gui_data.console, gb); -} - -struct ConsoleLogData { - const char *message; - GB_log_attributes attributes; -}; - -static bool main_thread_console_log(gpointer data) { - struct ConsoleLogData *args = data; - console_log(gui_data.console, args->message, args->attributes); - g_slice_free(struct ConsoleLogData, args); - - return false; -} - -static void wrapped_console_log(GB_gameboy_t *gb, const char *message, GB_log_attributes attributes) { - struct ConsoleLogData *data = g_slice_alloc(sizeof(struct ConsoleLogData)); - data->message = g_strdup(message); - data->attributes = attributes; - - g_idle_add((GSourceFunc) main_thread_console_log, data); -} - -static void init(void) { - if (GB_is_inited(&gb)) return; - - GB_init(&gb, config_get_model_type(&gui_data)); - - GB_set_vblank_callback(&gb, vblank); - GB_set_rgb_encode_callback(&gb, rgb_encode); - - GB_set_pixels_output(&gb, main_window_get_current_buffer(gui_data.main_window)); - GB_set_color_correction_mode(&gb, config_get_color_correction_mode()); - GB_set_light_temperature(&gb, (double) config.video.light_temperature / 256.0); - if (config_get_display_border_mode() <= GB_BORDER_ALWAYS) { - GB_set_border_mode(&gb, config_get_display_border_mode()); - } - - GB_apu_set_sample_callback(&gb, gb_audio_callback); - - GB_set_sample_rate(&gb, GB_audio_get_sample_rate()); - GB_set_highpass_filter_mode(&gb, config_get_highpass_mode()); - GB_set_interference_volume(&gb, (double) config.audio.interference_volume / 100.0); - - GB_set_log_callback(&gb, wrapped_console_log); - GB_set_input_callback(&gb, wrapped_console_get_sync_input); - GB_set_async_input_callback(&gb, wrapped_console_get_async_input); - - GB_set_boot_rom_load_callback(&gb, load_boot_rom); - GB_set_update_input_hint_callback(&gb, handle_events); - GB_set_rumble_callback(&gb, rumble_callback); - - GB_set_rumble_mode(&gb, config_get_rumble_mode()); - GB_set_rewind_length(&gb, config.emulation.rewind_duration); -} - -static void reset(void) { - g_debug("Reset: %d == %d", config_get_model_type(&gui_data), gui_data.prev_model); - GB_model_t current_model = config_get_model_type(&gui_data); - - if (gui_data.prev_model == -1 || gui_data.prev_model == current_model) { - GB_reset(&gb); - } - else { - GB_switch_model_and_reset(&gb, current_model); - } - - GB_set_palette(&gb, config_get_monochrome_palette()); - - gui_data.prev_model = config_get_model_type(&gui_data); - - // Check SGB -> non-SGB and non-SGB to SGB transitions - if (GB_get_screen_width(&gb) != gui_data.last_screen_width || GB_get_screen_height(&gb) != gui_data.last_screen_height) { - main_window_set_resolution(gui_data.main_window, GB_get_screen_width(&gb), GB_get_screen_height(&gb)); - GB_set_pixels_output(&gb, main_window_get_pixels(gui_data.main_window)); - } - - bool success = false; - if (gui_data.file) { - char *path = g_file_get_path(gui_data.file); - char *ext = strrchr(path, '.'); - int result; - - GB_debugger_clear_symbols(&gb); - - if (g_strcmp0(ext + 1, "isx") == 0) { - result = GB_load_isx(&gb, path); - } - else { - result = GB_load_rom(&gb, path); - } - - if (result == 0) { - success = true; - } - else { - g_warning("Failed to load ROM: %s", path); - } - - GB_load_battery(&gb, gui_data.battery_save_path); - GB_load_cheats(&gb, gui_data.cheats_save_path); - - size_t path_length = strlen(path); - char printer_suggestion_prefix[path_length]; - replace_extension(path, path_length, printer_suggestion_prefix, ""); - printer_window_set_suggestion_prefix(gui_data.printer, printer_suggestion_prefix); - - GError *error = NULL; - GBytes *register_sym_f = g_resources_lookup_data(RESOURCE_PREFIX "Misc/registers.sym", G_RESOURCE_LOOKUP_FLAGS_NONE, &error); - if (register_sym_f) { - gsize register_sym_size; - const gchar *register_sym_data = g_bytes_get_data(register_sym_f, ®ister_sym_size); - GB_debugger_load_symbol_file_from_buffer(&gb, register_sym_data, register_sym_size); - g_bytes_unref(register_sym_f); - } - - char sym_file_path[path_length + 5]; - replace_extension(path, path_length, sym_file_path, ".sym"); - GB_debugger_load_symbol_file(&gb, sym_file_path); - - g_free(path); - } - - action_set_enabled(gui_data.main_application, "close", success); - action_entries_set_enabled(emulation_entries, G_N_ELEMENTS(emulation_entries), success); -} - -static void start(void) { - gui_data.running = true; - gui_data.stopped = false; - - GB_audio_clear_queue(); - GB_audio_set_paused(config.audio.muted); - - /* Run emulation */ - while (gui_data.running) { - if (gui_data.rewind_paused) { - handle_events(&gb); - g_usleep(G_USEC_PER_SEC / 8); - } - else { - if (gui_data.do_rewind) { - GB_rewind_pop(&gb); - if (gui_data.turbo_down) { - GB_rewind_pop(&gb); - } - if (!GB_rewind_pop(&gb)) { - gui_data.rewind_paused = true; - } - gui_data.do_rewind = false; - } - GB_run(&gb); - } - } - - if (gui_data.file) { - GB_save_battery(&gb, gui_data.battery_save_path); - GB_save_cheats(&gb, gui_data.cheats_save_path); - } - - gui_data.stopping = false; -} - -// Prevent dependency loop -static void run(void); - -gpointer perform_atomic(gpointer (*fn)(gpointer args), gpointer args) { - while (!GB_is_inited(&gb)); - bool was_running = gui_data.running && !GB_debugger_is_stopped(&gb); - - if (was_running) { - stop(); - } - - // run the callback - gpointer ret_val = (*fn)(args); - - if (was_running) { - run(); - } - - return ret_val; -} - -// app.reset GAction -// Resets the emulation -static void activate_reset(GSimpleAction *action, GVariant *parameter, gpointer app) { - if (!GB_is_inited(&gb)) { - init(); - } - - stop(); - reset(); - run(); -} - -static gpointer run_thread(gpointer null_ptr) { - if (!gui_data.file) return NULL; - - char *path = g_file_get_path(gui_data.file); - size_t path_length = strlen(path); - - /* At the worst case, size is strlen(path) + 4 bytes for .sav + NULL */ - char battery_save_path[path_length + 5]; - char cheats_save_path[path_length + 5]; - - replace_extension(path, path_length, battery_save_path, ".sav"); - replace_extension(path, path_length, cheats_save_path, ".cht"); - - gui_data.battery_save_path = battery_save_path; - gui_data.cheats_save_path = cheats_save_path; - - if (!GB_is_inited(&gb)) { - init(); - } - - if (gui_data.stopped) { - start(); - } - else { - reset(); - start(); - } - - return NULL; -} - -static void run(void) { - if (gui_data.running) return; - while (gui_data.stopping); - - g_thread_new("CoreLoop", run_thread, NULL); -} - -// Tell our application to quit. -// After this functions has been called the `shutdown` signal will be issued. -// -// TODO: Make sure we have a way to quit our emulation loop before `shutdown` gets called -static void quit(void) { - g_debug("quit(void);"); - - stop(); - - GtkWindow *window = gui_data.main_window ? GTK_WINDOW(gui_data.main_window) : NULL; - save_config(window, gui_data.config_modification_date); - free_config(); - - for (unsigned i = 0; i < gui_data.controller_count; i++) { - struct Controller_t *s = &gui_data.controllers[i]; - - SDL_HapticClose(s->haptic); - SDL_GameControllerClose(s->controller); - } - - // Quit our application properly. - // This fires the “shutdown” signal. - g_application_quit(G_APPLICATION(gui_data.main_application)); -} - -static void quit_interrupt(int ignored) { - g_debug("quit_interrupt(%d);", ignored); - - quit(); -} - -static void create_action_groups(GApplication *app) { - g_action_map_add_action_entries(G_ACTION_MAP(app), emulation_entries, G_N_ELEMENTS(emulation_entries), NULL); - g_action_map_add_action_entries(G_ACTION_MAP(app), developer_entries, G_N_ELEMENTS(developer_entries), NULL); - g_action_map_add_action_entries(G_ACTION_MAP(app), app_entries, G_N_ELEMENTS(app_entries), NULL); - g_action_map_add_action_entries(G_ACTION_MAP(app), file_entries, G_N_ELEMENTS(file_entries), NULL); - g_action_map_add_action_entries(G_ACTION_MAP(app), edit_entries, G_N_ELEMENTS(edit_entries), NULL); - - action_set_enabled(app, "close", false); - action_entries_set_enabled(emulation_entries, G_N_ELEMENTS(emulation_entries), false); -} - -static gboolean on_key_press(GtkWidget *w, GdkEventKey *event, gpointer data) { - uint8_t mask; - - if (event->keyval == key_map[INPUT_UP]) mask = BUTTON_MASK_UP; - if (event->keyval == key_map[INPUT_DOWN]) mask = BUTTON_MASK_DOWN; - if (event->keyval == key_map[INPUT_LEFT]) mask = BUTTON_MASK_LEFT; - if (event->keyval == key_map[INPUT_RIGHT]) mask = BUTTON_MASK_RIGHT; - if (event->keyval == key_map[INPUT_START]) mask = BUTTON_MASK_START; - if (event->keyval == key_map[INPUT_SELECT]) mask = BUTTON_MASK_SELECT; - if (event->keyval == key_map[INPUT_A]) mask = BUTTON_MASK_A; - if (event->keyval == key_map[INPUT_B]) mask = BUTTON_MASK_B; - - if (event->keyval == key_map[INPUT_REWIND]) { - gui_data.rewind_down = event->type == GDK_KEY_PRESS; - GB_set_turbo_mode(&gb, gui_data.turbo_down, gui_data.turbo_down && gui_data.rewind_down); - - if (event->type == GDK_KEY_RELEASE) { - gui_data.rewind_paused = false; - } - } - - if (event->keyval == key_map[INPUT_TURBO]) { - gui_data.turbo_down = event->type == GDK_KEY_PRESS; - gui_data.analog_clock_multiplier_valid = false; - - GB_audio_clear_queue(); - GB_set_turbo_mode(&gb, gui_data.turbo_down, gui_data.turbo_down && gui_data.rewind_down); - } - - if (event->keyval == key_map[INPUT_SLOWDOWN]) { - gui_data.underclock_down = event->type == GDK_KEY_PRESS; - gui_data.analog_clock_multiplier_valid = false; - } - - if (event->keyval == key_map[INPUT_FULLSCREEN]) { - if (event->type == GDK_KEY_RELEASE) { - main_window_fullscreen(gui_data.main_window, !gui_data.is_fullscreen); - } - } - - if (event->type == GDK_KEY_PRESS) { - gui_data.pressed_buttons |= mask; - } - else if (event->type == GDK_KEY_RELEASE) { - gui_data.pressed_buttons &= ~mask; - } - - return false; -} - -static void on_window_state_change(GtkWidget *w, GdkEventWindowState *event, gpointer data) { - gui_data.is_fullscreen = event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN; -} - -// This functions gets called immediately after registration of the GApplication -static void startup(GApplication *app, gpointer null_ptr) { - signal(SIGINT, quit_interrupt); - - g_debug("GTK version %u.%u.%u", gtk_get_major_version(), gtk_get_minor_version(), gtk_get_micro_version()); - - gui_data.builder = gtk_builder_new_from_resource(RESOURCE_PREFIX "ui/window.ui"); - gtk_builder_connect_signals(gui_data.builder, NULL); - - create_action_groups(app); - - #if NDEBUG - // Disable when not compiled in debug mode - action_set_enabled(app, "open_gtk_debugger", false); - #endif - - init_config(app, gui_data.cli_options.config_path, &gui_data.config_modification_date); - - gui_data.main_window = main_window_new(app, gui_data.cli_options.force_software_renderer); - gui_data.console = console_window_new(&gb); - gui_data.preferences = preferences_window_new(&gb); - gui_data.vram_viewer = vram_viewer_window_new(); - gui_data.printer = printer_window_new(); - - gui_data.memory_viewer = GTK_WINDOW(get_object("memory_viewer")); - - if (config.audio.sample_rate == -1) { - gui_data.sample_rate = GB_audio_default_sample_rate(); - } - else { - gui_data.sample_rate = config.audio.sample_rate; - } - - main_window_setup_menu(gui_data.main_window, config.emulation.model); - - // Insert separators into `GtkComboBox`es - set_combo_box_row_separator_func(GTK_CONTAINER(gui_data.memory_viewer)); - - // 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" - }; - - // 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_default_icon_list(icon_list); - - // Add missing information to the about dialog - GtkAboutDialog *about_dialog = GTK_ABOUT_DIALOG(get_object("about_dialog")); - gtk_about_dialog_set_logo(about_dialog, gdk_pixbuf_new_from_resource(icons[2], NULL)); // reuse the 64x64 icon - gtk_about_dialog_set_version(about_dialog, "v" xstr(VERSION)); - g_list_free_full(icon_list, g_object_unref); - - 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); -} - -G_MODULE_EXPORT void break_debugger_keyboard(GtkWidget *w, gpointer user_data_ptr) { - break_debugger(gui_data.console, false); -} - -G_MODULE_EXPORT void on_quit_activate(GtkWidget *w, gpointer user_data_ptr) { - quit(); -} - -static gboolean draw_printer_image(struct PrinterData *data) { - printer_window_update(gui_data.printer, data); - - g_free(data->image); - g_slice_free(struct PrinterData, data); - return false; -} - -static void print_image(GB_gameboy_t *gb, uint32_t *image, uint8_t height, uint8_t top_margin, uint8_t bottom_margin, uint8_t exposure) { - struct PrinterData *data = g_slice_alloc(sizeof(struct PrinterData)); - - data->image = g_malloc0(160 * height * sizeof(image[0])); - memcpy(data->image, image, 160 * height * sizeof(image[0])); - - data->height = height; - data->top_margin = top_margin; - data->bottom_margin = bottom_margin; - data->exposure = exposure; - - g_idle_add((GSourceFunc) draw_printer_image, data); -} - -gpointer change_serial_device(gpointer ptr) { - gchar *device_id = ptr; - - if (g_strcmp0(device_id, "NONE") == 0) { - g_debug("Disconnecting serial device"); - GB_disconnect_serial(&gb); - } - else if (g_strcmp0(device_id, "PRINTER") == 0) { - g_debug("Connecting printer"); - GB_connect_printer(&gb, print_image); - } - - return NULL; -} - -bool on_change_linked_device(GtkWidget *widget, gpointer user_data) { - GtkCheckMenuItem *check_menu_item = GTK_CHECK_MENU_ITEM(widget); - gchar *device_id = (gchar *) user_data; - - if (!gtk_check_menu_item_get_active(check_menu_item)) { - return true; - } - else if (!GB_is_inited(&gb)) { - return false; - } - - perform_atomic(change_serial_device, device_id); - - return false; -} - -bool on_change_model(GtkWidget *widget, gpointer user_data) { - GtkCheckMenuItem *check_menu_item = GTK_CHECK_MENU_ITEM(widget); - gchar *model_str = (gchar *) user_data; - - if (!gtk_check_menu_item_get_active(check_menu_item)) { - return true; - } - else if (!GB_is_inited(&gb)) { - gui_data.cli_options.model = -1; - config.emulation.model = model_str; - - return false; - } - - GtkMessageDialog *dialog = GTK_MESSAGE_DIALOG(gtk_message_dialog_new( - GTK_WINDOW(gui_data.main_window), - GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_QUESTION, - GTK_BUTTONS_YES_NO, - "Changing the emulated model requires a reset.\nChange model and reset game?" - )); - - stop(); - gint result = gtk_dialog_run(GTK_DIALOG(dialog)); - - switch (result) { - case GTK_RESPONSE_YES: - // Reset the CLI model override - gui_data.cli_options.model = -1; - - config.emulation.model = model_str; - - reset(); - break; - default: - // Action has been canceled - break; - } - - run(); - gtk_widget_destroy(GTK_WIDGET(dialog)); - - return result != GTK_RESPONSE_YES; -} - -void on_preferences_notify_border(PreferencesWindow *pref, const gchar *name) { - gui_data.border_mode_changed = true; -} - -void on_preferences_notify_shader(PreferencesWindow *pref, const gchar *name) { - main_window_set_shader(gui_data.main_window, name); -} - -void on_preferences_notify_light_temperature(PreferencesWindow *pref, const gint *light_temperature) { - if (GB_is_inited(&gb)) { - // wouldn’t it be nice to use the value set in the GtkAdjustment of the slider instead of 256.0 here? - GB_set_light_temperature(&gb, (double) *light_temperature / 256.0); - } -} - -void on_preferences_notify_sample_rate(PreferencesWindow *pref, const guint *sample_rate) { - if (*sample_rate == -1) { - gui_data.sample_rate = GB_audio_default_sample_rate(); - } - else { - gui_data.sample_rate = *sample_rate; - } - - init_audio(); -} - -void on_preferences_notify_interference_volume(PreferencesWindow *pref, const guint *interference_volume) { - if (GB_is_inited(&gb)) { - // wouldn’t it be nice to use the value set in the GtkAdjustment of the slider instead of 100.0 here? - GB_set_interference_volume(&gb, (double) *interference_volume / 100.0); - } -} - -static void connect_signal_handlers(GApplication *app) { - g_signal_connect(gui_data.main_window, "destroy", G_CALLBACK(on_quit_activate), app); - g_signal_connect(gui_data.main_window, "key-press-event", G_CALLBACK(on_key_press), NULL); - g_signal_connect(gui_data.main_window, "key-release-event", G_CALLBACK(on_key_press), NULL); - g_signal_connect(gui_data.main_window, "window-state-event", G_CALLBACK(on_window_state_change), NULL); - g_signal_connect(gui_data.main_window, "break-debugger-keyboard", G_CALLBACK(break_debugger_keyboard), NULL); - - // Just hide our sub-windows when closing them - g_signal_connect(gui_data.preferences, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL); - g_signal_connect(gui_data.vram_viewer, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL); - g_signal_connect(gui_data.memory_viewer, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL); - g_signal_connect(gui_data.console, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL); - g_signal_connect(gui_data.printer, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL); - - g_signal_connect(gui_data.preferences, "pref-update::video-display-border-mode", G_CALLBACK(on_preferences_notify_border), NULL); - g_signal_connect(gui_data.preferences, "pref-update::video-shader", G_CALLBACK(on_preferences_notify_shader), NULL); - g_signal_connect(gui_data.preferences, "pref-update::video-color-temperature", G_CALLBACK(on_preferences_notify_light_temperature), NULL); - g_signal_connect(gui_data.preferences, "pref-update::audio-sample-rate", G_CALLBACK(on_preferences_notify_sample_rate), NULL); - g_signal_connect(gui_data.preferences, "pref-update::audio-interference-volume", G_CALLBACK(on_preferences_notify_interference_volume), NULL); -} - -// This function gets called when the GApplication gets activated, i.e. it is ready to show widgets. -static void activate(GApplication *app, gpointer null_ptr) { - init_audio(); - init_controllers(); - - connect_signal_handlers(app); - - if (gui_data.cli_options.fullscreen) { - main_window_fullscreen(gui_data.main_window, true); - } - - gtk_application_add_window(GTK_APPLICATION(app), GTK_WINDOW(gui_data.main_window)); - gtk_widget_show_all(GTK_WIDGET(gui_data.main_window)); - - // Start the emulation thread - run(); -} - -// This function gets called when the application is closed. -static void shutdown(GApplication *app, GFile **files, gint n_files, const gchar *hint, gpointer null_ptr) { - g_debug("SHUTDOWN"); - - stop(); - SDL_Quit(); - GB_free(&gb); - - g_object_unref(gui_data.builder); -} - -// This function gets called when there are files to open. -// Note: When `open` gets called `activate` won’t fire unless we call it ourselves. -static void open(GApplication *app, GFile **files, gint n_files, const gchar *hint, gpointer null_ptr) { - if (n_files > 1) { - g_warning("More than one file specified"); - exit(EXIT_FAILURE); - } - - gui_data.file = g_file_dup(files[0]); - - // We have handled the files, now activate the application - activate(app, NULL); -} - -// app.about GAction -// Opens the about dialog -static void activate_about(GSimpleAction *action, GVariant *parameter, gpointer app) { - GObject *dialog = get_object("about_dialog"); - gtk_dialog_run(GTK_DIALOG(dialog)); - gtk_widget_hide(GTK_WIDGET(dialog)); -} - -// app.preferences GAction -// Opens the preferences window -static void activate_preferences(GSimpleAction *action, GVariant *parameter, gpointer app) { - gtk_widget_show_all(GTK_WIDGET(gui_data.preferences)); -} - -// app.show_console GAction -// Opens the console -static void activate_show_console(GSimpleAction *action, GVariant *parameter, gpointer app) { - gtk_widget_show_all(GTK_WIDGET(gui_data.console)); -} - -// app.open_gtk_debugger GAction -// Opens the GTK debugger -static void activate_open_gtk_debugger(GSimpleAction *action, GVariant *parameter, gpointer app) { - gtk_window_set_interactive_debugging(true); -} - -// app.open_memory_viewer GAction -// Opens the memory viewer window -static void activate_open_memory_viewer(GSimpleAction *action, GVariant *parameter, gpointer app) { - gtk_widget_show_all(GTK_WIDGET(gui_data.memory_viewer)); -} - -// app.open_vram_viewer GAction -// Opens the VRAM viewer window -static void activate_open_vram_viewer(GSimpleAction *action, GVariant *parameter, gpointer app) { - gtk_widget_show_all(GTK_WIDGET(gui_data.vram_viewer)); -} - -// app.clear_console GAction -// Clears the debugger console -static void activate_clear_console(GSimpleAction *action, GVariant *parameter, gpointer app) { - console_clear(gui_data.console); -} - -// app.break_debugger GAction -// Clears the debugger console -static void activate_break_debugger(GSimpleAction *action, GVariant *parameter, gpointer app) { - break_debugger(gui_data.console, true); -} - -// Closes a ROM -static void close_rom(void) { - stop(); - GB_free(&gb); - - main_window_clear(gui_data.main_window); - main_window_queue_render(gui_data.main_window); - - vram_viewer_clear(gui_data.vram_viewer); - gtk_widget_queue_draw(GTK_WIDGET(gui_data.vram_viewer)); - - // Update menu action states - action_set_enabled(gui_data.main_application, "close", false); - action_entries_set_enabled(emulation_entries, G_N_ELEMENTS(emulation_entries), false); - - // Try force the queued redraws - while (g_main_context_pending(NULL)) { - g_main_context_iteration(NULL, FALSE); - } -} - -// app.open GAction -// Opens a ROM file -static void activate_open(GSimpleAction *action, GVariant *parameter, gpointer app) { - stop(); - - GtkFileChooserNative *native = gtk_file_chooser_native_new("Open File", GTK_WINDOW(gui_data.main_window), GTK_FILE_CHOOSER_ACTION_OPEN, "_Open", "_Cancel"); - - GtkFileFilter *filter = gtk_file_filter_new(); - gtk_file_filter_add_pattern(filter, "*.gb"); - gtk_file_filter_add_pattern(filter, "*.gbc"); - gtk_file_filter_add_pattern(filter, "*.isx"); - gtk_file_filter_add_mime_type(filter, "application/x-gameboy-rom"); - gtk_file_filter_add_mime_type(filter, "application/x-gameboy-color-rom"); - gtk_file_filter_set_name(filter, "All Supported Files (*.gb, *.gbc, *.isx)"); - gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(native), filter); - - filter = gtk_file_filter_new(); - gtk_file_filter_add_pattern(filter, "*.gb"); - gtk_file_filter_add_mime_type(filter, "application/x-gameboy-rom"); - gtk_file_filter_set_name(filter, "Game Boy (*.gb)"); - gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(native), filter); - - filter = gtk_file_filter_new(); - gtk_file_filter_add_pattern(filter, "*.gbc"); - gtk_file_filter_add_mime_type(filter, "application/x-gameboy-color-rom"); - gtk_file_filter_set_name(filter, "Game Boy Color (*.gbc)"); - gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(native), filter); - - filter = gtk_file_filter_new(); - gtk_file_filter_add_pattern(filter, "*"); - gtk_file_filter_set_name(filter, "All Files"); - gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(native), filter); - - gint res = gtk_native_dialog_run(GTK_NATIVE_DIALOG(native)); - - if (res == GTK_RESPONSE_ACCEPT) { - const char* path = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(native)); - gui_data.file = g_file_new_for_path(path); - - activate_reset(action, parameter, app); - } - else { - run(); - } - - g_object_unref(native); -} - -// app.close GAction -static void activate_close(GSimpleAction *action, GVariant *parameter, gpointer app) { - close_rom(); -} - -// app.quit GAction -// Exits the application -static void activate_quit(GSimpleAction *action, GVariant *parameter, gpointer app) { - quit(); -} - -static void on_developer_mode_changed(GSimpleAction *action, GVariant *value, gpointer user_data_ptr) { - set_developer_mode(gui_data.console, g_variant_get_boolean(value)); - g_simple_action_set_state(action, value); -} - -static void on_mute_changed(GSimpleAction *action, GVariant *value, gpointer user_data_ptr) { - config.audio.muted = g_variant_get_boolean(value); - - GB_audio_set_paused(config.audio.muted); - g_simple_action_set_state(action, value); -} - -static void on_pause_changed(GSimpleAction *action, GVariant *value, gpointer user_data_ptr) { - if (g_variant_get_boolean(value)) { - stop(); - } - else { - run(); - } - - g_simple_action_set_state(action, value); -} - -G_MODULE_EXPORT void on_open_recent_activate(GtkRecentChooser *chooser, gpointer user_data_ptr) { - stop(); - - gchar *uri = gtk_recent_chooser_get_current_uri(chooser); - GFile *file = g_file_new_for_uri(uri); - - if (g_file_query_exists(file, NULL)) { - gui_data.file = file; - - // Add the file back to the top of the list - GtkRecentManager *manager = gtk_recent_manager_get_default(); - gtk_recent_manager_add_item(manager, uri); - - // TODO: Not nice - activate_reset(NULL, NULL, NULL); - } - else { - // TODO - g_warning("File not found: %s", uri); - close_rom(); - } -} +#include "sameboy_application.h" int main(int argc, char *argv[]) { - gui_data.main_thread = g_thread_self(); - - // Create our GApplication and tell GTK that we are able to handle files - gui_data.main_application = gtk_application_new(APP_ID, G_APPLICATION_NON_UNIQUE | G_APPLICATION_HANDLES_OPEN); - - // 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, &gui_data.cli_options.fullscreen, "Start in fullscreen mode", NULL }, - { "bootrom", 'b', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &gui_data.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, &gui_data.cli_options.config_path, "Override the path of the configuration file", "" }, - { "no-gl", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &gui_data.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(gui_data.main_application), entries); - g_application_set_option_context_parameter_string(G_APPLICATION(gui_data.main_application), "[FILE…]"); - g_application_set_option_context_summary(G_APPLICATION(gui_data.main_application), "SameBoy is an open source Game Boy (DMG) and Game Boy Color (CGB) emulator."); - - // Add signal handlers - g_signal_connect(gui_data.main_application, "handle-local-options", G_CALLBACK(handle_local_options), NULL); - g_signal_connect(gui_data.main_application, "startup", G_CALLBACK(startup), NULL); - g_signal_connect(gui_data.main_application, "activate", G_CALLBACK(activate), NULL); - g_signal_connect(gui_data.main_application, "open", G_CALLBACK(open), NULL); - g_signal_connect(gui_data.main_application, "shutdown", G_CALLBACK(shutdown), NULL); - - // Start our GApplication main loop - int status = g_application_run(G_APPLICATION(gui_data.main_application), argc, argv); - g_object_unref(gui_data.main_application); - - return status; + return g_application_run(G_APPLICATION(sameboy_application_new()), argc, argv); } diff --git a/gtk3/resources/ui/about_dialog.ui b/gtk3/resources/ui/about_dialog.ui new file mode 100644 index 0000000..9bfe3b9 --- /dev/null +++ b/gtk3/resources/ui/about_dialog.ui @@ -0,0 +1,74 @@ + + + + + + + + + + + diff --git a/gtk3/sameboy.gresource.xml b/gtk3/sameboy.gresource.xml index f3b95b9..3bc234f 100644 --- a/gtk3/sameboy.gresource.xml +++ b/gtk3/sameboy.gresource.xml @@ -2,6 +2,7 @@ ui/window.ui + ui/about_dialog.ui ui/main_window.ui ui/main_menu.ui ui/console_window.ui diff --git a/gtk3/sameboy_application.c b/gtk3/sameboy_application.c new file mode 100644 index 0000000..af9cf76 --- /dev/null +++ b/gtk3/sameboy_application.c @@ -0,0 +1,388 @@ +#include "sameboy_application.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; + + const GThread *main_thread; + PreferencesWindow *preferences; + AboutDialog *about_dialog; + + struct CliOptionData { + gchar *config_path; + gchar *boot_rom_path; + gboolean fullscreen; + GB_model_t model; + gboolean force_software_renderer; + } cli_options; +}; + +G_DEFINE_TYPE(SameBoyApplication, sameboy_application, GTK_TYPE_APPLICATION); + +static void sameboy_application_init(SameBoyApplication *app) { + g_debug("sameboy_application_init"); + + // 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); + 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"); + + 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); +} + +static void open_file(SameBoyApplication *app, GFile *file) { + 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); + + 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), false /* force_software_renderer */); + + // 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); +} diff --git a/gtk3/sameboy_application.h b/gtk3/sameboy_application.h new file mode 100644 index 0000000..690ad6b --- /dev/null +++ b/gtk3/sameboy_application.h @@ -0,0 +1,10 @@ +#ifndef sameboy_application_h +#define sameboy_application_h + #include + + #define SAMEBOY_APPLICATION_TYPE (sameboy_application_get_type()) + G_DECLARE_FINAL_TYPE(SameBoyApplication, sameboy_application, SAMEBOY, APPLICATION, GtkApplication) + + SameBoyApplication *sameboy_application_new(void); + void sameboy_application_preferences_signal_connect(SameBoyApplication *app, const gchar *detailed_signal, GCallback c_handler, gpointer data); +#endif diff --git a/gtk3/types.h b/gtk3/types.h index 6be0374..cef73bf 100644 --- a/gtk3/types.h +++ b/gtk3/types.h @@ -45,7 +45,6 @@ typedef struct GuiData { PreferencesWindow *preferences; VramViewerWindow *vram_viewer; PrinterWindow *printer; - GtkWindow *memory_viewer; // Audio and video diff --git a/gtk3/widgets/about_dialog.c b/gtk3/widgets/about_dialog.c new file mode 100644 index 0000000..48c4951 --- /dev/null +++ b/gtk3/widgets/about_dialog.c @@ -0,0 +1,19 @@ +#include "about_dialog.h" + +struct _AboutDialog { + GtkMenuBarClass parent_class; +}; + +G_DEFINE_TYPE(AboutDialog, about_dialog, GTK_TYPE_ABOUT_DIALOG); + +static void about_dialog_init(AboutDialog *self) { + gtk_widget_init_template(GTK_WIDGET(self)); +} + +static void about_dialog_class_init(AboutDialogClass *class) { + gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS(class), RESOURCE_PREFIX "ui/about_dialog.ui"); +} + +AboutDialog *about_dialog_new() { + return g_object_new(ABOUT_DIALOG_TYPE, NULL); +} diff --git a/gtk3/widgets/about_dialog.h b/gtk3/widgets/about_dialog.h new file mode 100644 index 0000000..ce620d2 --- /dev/null +++ b/gtk3/widgets/about_dialog.h @@ -0,0 +1,11 @@ +#ifndef about_dialog_h +#define about_dialog_h + +#include + +#define ABOUT_DIALOG_TYPE (about_dialog_get_type()) +G_DECLARE_FINAL_TYPE(AboutDialog, about_dialog, SAMEBOY, ABOUT_DIALOG, GtkAboutDialog) + +AboutDialog *about_dialog_new(); + +#endif diff --git a/gtk3/widgets/console_window.c b/gtk3/widgets/console_window.c index 7433876..b6ea3ab 100644 --- a/gtk3/widgets/console_window.c +++ b/gtk3/widgets/console_window.c @@ -451,7 +451,7 @@ ConsoleWindow *console_window_new(GB_gameboy_t *gb) { } // This function gets called every VBlank while the emulation is running. -char *console_get_async_input(ConsoleWindow *self, GB_gameboy_t *gb) { +char *console_window_get_async_input(ConsoleWindow *self, GB_gameboy_t *gb) { self->clear_sidebar = true; char *command = (char *)g_async_queue_try_pop(self->input_queue); @@ -466,7 +466,7 @@ char *console_get_async_input(ConsoleWindow *self, GB_gameboy_t *gb) { // This will only be called if the debugger is in stopped mode (after a breakpoint hit for example), // thus we block the emulation thread until input is available. -char *console_get_sync_input(ConsoleWindow *self, GB_gameboy_t *gb) { +char *console_window_get_sync_input(ConsoleWindow *self, GB_gameboy_t *gb) { update_sidebar(self, gb); char *command = (char *)g_async_queue_pop(self->input_queue); @@ -479,17 +479,17 @@ char *console_get_sync_input(ConsoleWindow *self, GB_gameboy_t *gb) { return command; } -void focus(ConsoleWindow *self) { +void console_window_focus(ConsoleWindow *self) { gtk_window_present_with_time(GTK_WINDOW(self), time(NULL)); gtk_widget_grab_focus(GTK_WIDGET(self->input)); } // Queues a message to be logged to the console -void console_log(ConsoleWindow *self, const char *message, GB_log_attributes attributes) { +void console_window_log(ConsoleWindow *self, const char *message, GB_log_attributes attributes) { if (!message || g_str_equal("", message)) return; if (self->developer_mode) { - focus(self); + console_window_focus(self); } AttributedMessage *attr_msg = g_new(AttributedMessage, 1); @@ -504,27 +504,27 @@ void console_log(ConsoleWindow *self, const char *message, GB_log_attributes att } // Marks the console as to be cleared on the next redraw -void console_clear(ConsoleWindow *self) { +void console_window_clear(ConsoleWindow *self) { self->should_clear = true; // mark as dirty gtk_widget_queue_draw(GTK_WIDGET(self)); } -void break_debugger(ConsoleWindow *self, bool forced) { +void console_window_break_debugger(ConsoleWindow *self, bool forced) { if (!forced && !self->developer_mode) return; GB_debugger_break(self->gb); - focus(self); + console_window_focus(self); } // Hack to avoid deadlocking on queue reads ... -void abort_debugger(ConsoleWindow *self) { +void console_window_abort_debugger(ConsoleWindow *self) { g_async_queue_push(self->input_queue, g_strdup("c\0")); g_async_queue_push(self->output_queue, g_strdup("c\0")); - console_clear(self); + console_window_clear(self); } -void set_developer_mode(ConsoleWindow *self, bool value) { +void console_window_set_developer_mode(ConsoleWindow *self, bool value) { self->developer_mode = value; } diff --git a/gtk3/widgets/console_window.h b/gtk3/widgets/console_window.h index 0096860..4e40631 100644 --- a/gtk3/widgets/console_window.h +++ b/gtk3/widgets/console_window.h @@ -9,11 +9,12 @@ G_DECLARE_FINAL_TYPE(ConsoleWindow, console_window, SAMEBOY, CONSOLE_WINDOW, GtkWindow) ConsoleWindow *console_window_new(GB_gameboy_t *gb); -char *console_get_async_input(ConsoleWindow *self, GB_gameboy_t *gb); -char *console_get_sync_input(ConsoleWindow *self, GB_gameboy_t *gb); -void console_log(ConsoleWindow *self, const char *message, GB_log_attributes attributes); -void console_clear(ConsoleWindow *self); -void break_debugger(ConsoleWindow *self, bool forced); -void abort_debugger(ConsoleWindow *self); -void set_developer_mode(ConsoleWindow *self, bool value); +char *console_window_get_async_input(ConsoleWindow *self, GB_gameboy_t *gb); +char *console_window_get_sync_input(ConsoleWindow *self, GB_gameboy_t *gb); +void console_window_log(ConsoleWindow *self, const char *message, GB_log_attributes attributes); +void console_window_clear(ConsoleWindow *self); +void console_window_focus(ConsoleWindow *self); +void console_window_break_debugger(ConsoleWindow *self, bool forced); +void console_window_abort_debugger(ConsoleWindow *self); +void console_window_set_developer_mode(ConsoleWindow *self, bool value); #endif \ No newline at end of file diff --git a/gtk3/widgets/main_menu.c b/gtk3/widgets/main_menu.c index 1edbe64..045e126 100644 --- a/gtk3/widgets/main_menu.c +++ b/gtk3/widgets/main_menu.c @@ -64,7 +64,7 @@ void main_menu_setup(MainMenu *self, char *model_string) { CheckMenuItemGroup *model_group = check_menu_item_group_new((char **) model_names, (char **) model_codes); check_menu_item_group_insert_into_menu_shell(model_group, GTK_MENU_SHELL(parent), position + 1); - check_menu_item_group_connect_toggle_signal(model_group, on_change_model); + // check_menu_item_group_connect_toggle_signal(model_group, on_change_model); check_menu_item_group_activate(model_group, model_string); static const char *const peripheral_names[] = { @@ -81,6 +81,6 @@ void main_menu_setup(MainMenu *self, char *model_string) { CheckMenuItemGroup *link_group = check_menu_item_group_new((char **) peripheral_names, (char **) peripheral_codes); check_menu_item_group_insert_into_menu_shell(link_group, GTK_MENU_SHELL(self->link_menu), 0); - check_menu_item_group_connect_toggle_signal(link_group, on_change_linked_device); + // check_menu_item_group_connect_toggle_signal(link_group, on_change_linked_device); check_menu_item_group_activate(link_group, "NONE"); } diff --git a/gtk3/widgets/main_window.c b/gtk3/widgets/main_window.c index 9a81ed5..08434f7 100644 --- a/gtk3/widgets/main_window.c +++ b/gtk3/widgets/main_window.c @@ -1,14 +1,30 @@ #include "main_window.h" +#include + #include "gb_screen.h" #include "main_menu.h" +#include "console_window.h" +#include "printer_window.h" +#include "preferences_window.h" +#include "vram_viewer_window.h" struct _MainWindow { GtkApplicationWindowClass parent_class; + // Child nodes GtkBox *container; GbScreen *screen; bool force_software_renderer; MainMenu *main_menu; + + // The local SameBoy core instance + GB_gameboy_t *gb; + + // Local sub-windows + ConsoleWindow *console; + GtkWindow *memory_viewer; // TODO + PrinterWindow *printer; + VramViewerWindow *vram_viewer; }; G_DEFINE_TYPE(MainWindow, main_window, GTK_TYPE_APPLICATION_WINDOW); @@ -39,11 +55,98 @@ static void main_window_get_property(GObject *object, guint property_id, GValue } } +static void on_update_color_correction(PreferencesWindow *pref, const GB_color_correction_mode_t mode, MainWindow *self) { + g_debug("on_update_color_correction(%d)", mode); +} + +static void on_update_video_color_temperature(PreferencesWindow *pref, const gint light_temperature, MainWindow *self) { + g_debug("on_update_video_color_temperature(%d)", light_temperature); + + if (GB_is_inited(self->gb)) { + // wouldn’t it be nice to use the value set in the GtkAdjustment of the slider instead of 256.0 here? + GB_set_light_temperature(self->gb, (double) light_temperature / 256.0); + } +} + +static void on_update_monochrome_palette(PreferencesWindow *pref, const GB_palette_t *palette, MainWindow *self) { + g_debug( + "on_update_monochrome_palette(\n\trgb(%d, %d, %d),\n\trgb(%d, %d, %d),\n\trgb(%d, %d, %d),\n\trgb(%d, %d, %d),\n\trgb(%d, %d, %d)\n)", + palette->colors[0].r, palette->colors[0].g, palette->colors[0].b, + palette->colors[1].r, palette->colors[1].g, palette->colors[1].b, + palette->colors[2].r, palette->colors[2].g, palette->colors[2].b, + palette->colors[3].r, palette->colors[3].g, palette->colors[3].b, + palette->colors[4].r, palette->colors[4].g, palette->colors[4].b + ); +} + +static void on_update_highpass(PreferencesWindow *pref, const GB_highpass_mode_t mode, MainWindow *self) { + g_debug("on_update_highpass(%d)", mode); +} + +static void on_update_rewind_duration(PreferencesWindow *pref, const guint rewind_duration, MainWindow *self) { + g_debug("on_update_rewind_duration(%d)", rewind_duration); +} + +static void on_update_rumble_mode(PreferencesWindow *pref, const GB_rumble_mode_t mode, MainWindow *self) { + g_debug("on_update_rumble_mode(%d)", mode); +} + +static void on_update_video_display_border_mode(PreferencesWindow *pref, const gchar *name, MainWindow *self) { + g_debug("on_update_video_display_border_mode(%s)", name); + // gui_data.border_mode_changed = true; +} + +static void on_update_video_shader(PreferencesWindow *pref, const gchar *name, MainWindow *self) { + g_debug("on_update_video_shader(%s)", name); + main_window_set_shader(self, name); +} + +static void on_update_audio_sample_rate(PreferencesWindow *pref, const guint sample_rate, MainWindow *self) { + g_debug("on_update_audio_sample_rate(%d)", sample_rate); + + if (sample_rate == -1) { + // gui_data.sample_rate = GB_audio_default_sample_rate(); + } + else { + // gui_data.sample_rate = *sample_rate; + } + + // init_audio(); +} + +static void on_update_audio_interference_volume(PreferencesWindow *pref, const guint *interference_volume, MainWindow *self) { + g_debug("on_update_audio_interference_volume(%d)", *interference_volume); + + if (GB_is_inited(self->gb)) { + // wouldn’t it be nice to use the value set in the GtkAdjustment of the slider instead of 100.0 here? + GB_set_interference_volume(self->gb, (double) *interference_volume / 100.0); + } +} + +static void on_application_set(MainWindow *self, GObject *object) { + SameBoyApplication *app = SAMEBOY_APPLICATION(gtk_window_get_application(GTK_WINDOW(self))); + + sameboy_application_preferences_signal_connect(app, "pref-update::color-correction", G_CALLBACK(on_update_color_correction), self); + sameboy_application_preferences_signal_connect(app, "pref-update::video-color-temperature", G_CALLBACK(on_update_video_color_temperature), self); + sameboy_application_preferences_signal_connect(app, "pref-update::monochrome-palette", G_CALLBACK(on_update_monochrome_palette), self); + sameboy_application_preferences_signal_connect(app, "pref-update::highpass", G_CALLBACK(on_update_highpass), self); + sameboy_application_preferences_signal_connect(app, "pref-update::rewind-duration", G_CALLBACK(on_update_rewind_duration), self); + sameboy_application_preferences_signal_connect(app, "pref-update::rumble-mode", G_CALLBACK(on_update_rumble_mode), self); + sameboy_application_preferences_signal_connect(app, "pref-update::video-display-border-mode", G_CALLBACK(on_update_video_display_border_mode), self); + sameboy_application_preferences_signal_connect(app, "pref-update::video-shader", G_CALLBACK(on_update_video_shader), self); + sameboy_application_preferences_signal_connect(app, "pref-update::audio-sample-rate", G_CALLBACK(on_update_audio_sample_rate), self); + sameboy_application_preferences_signal_connect(app, "pref-update::audio-interference-volume", G_CALLBACK(on_update_audio_interference_volume), self); +} + static void main_window_constructed(GObject *object) { + G_OBJECT_CLASS(main_window_parent_class)->constructed(object); + MainWindow *self = (MainWindow *) object; self->screen = gb_screen_new(self->force_software_renderer); gtk_box_pack_end(GTK_BOX(self->container), GTK_WIDGET(self->screen), true, true, 0); + + g_signal_connect(self, "notify::application", G_CALLBACK(on_application_set), self); } static void main_window_init(MainWindow *self) { @@ -52,9 +155,40 @@ static void main_window_init(MainWindow *self) { gtk_window_set_title(GTK_WINDOW(self), "SameBoy"); gtk_application_window_set_show_menubar(GTK_APPLICATION_WINDOW(self), false); + // Connect signal handlers + gtk_widget_add_events(GTK_WIDGET(self), GDK_KEY_PRESS_MASK); + gtk_widget_add_events(GTK_WIDGET(self), GDK_KEY_RELEASE_MASK); + + GtkAccelGroup *accelGroup = gtk_accel_group_new(); + gtk_window_add_accel_group(GTK_WINDOW(self), accelGroup); + gtk_widget_add_accelerator(GTK_WIDGET(self), "break-debugger-keyboard", accelGroup, GDK_KEY_C, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE); + + self->gb = g_malloc(sizeof(GB_gameboy_t)); + if (self->gb == NULL) { + g_warning("Out of memory!"); + // TODO: Try to stop gracefully + exit(EXIT_FAILURE); + } + + self->console = console_window_new(self->gb); + gtk_window_set_attached_to(GTK_WINDOW(self->console), GTK_WIDGET(self)); + + self->vram_viewer = vram_viewer_window_new(); + gtk_window_set_attached_to(GTK_WINDOW(self->vram_viewer), GTK_WIDGET(self)); + + self->printer = printer_window_new(); + gtk_window_set_attached_to(GTK_WINDOW(self->printer), GTK_WIDGET(self)); +} + +static void main_window_class_init(MainWindowClass *class) { + gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS(class), RESOURCE_PREFIX "ui/main_window.ui"); + + gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), MainWindow, container); + gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), MainWindow, main_menu); + g_signal_new( "break-debugger-keyboard", // signal name - G_TYPE_FROM_INSTANCE(self), // itype + G_TYPE_FROM_CLASS(class), // itype G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE | G_SIGNAL_ACTION, // signal_flags 0, // class_offset NULL, // accumulator @@ -64,21 +198,6 @@ static void main_window_init(MainWindow *self) { 0 // n_params ); - // Connect signal handlers - gtk_widget_add_events(GTK_WIDGET(self), GDK_KEY_PRESS_MASK); - gtk_widget_add_events(GTK_WIDGET(self), GDK_KEY_RELEASE_MASK); - - GtkAccelGroup *accelGroup = gtk_accel_group_new(); - gtk_window_add_accel_group(GTK_WINDOW(self), accelGroup); - gtk_widget_add_accelerator(GTK_WIDGET(self), "break-debugger-keyboard", accelGroup, GDK_KEY_C, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE); -} - -static void main_window_class_init(MainWindowClass *class) { - gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS(class), RESOURCE_PREFIX "ui/main_window.ui"); - - gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), MainWindow, container); - gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), MainWindow, main_menu); - obj_properties[PROP_FORCE_SOFTWARE_RENDERER] = g_param_spec_boolean( "force_software_renderer", "Software Renderer", "Forces the use of software rendering via Cairo", false, @@ -92,16 +211,21 @@ static void main_window_class_init(MainWindowClass *class) { g_object_class_install_properties(G_OBJECT_CLASS(class), N_PROPERTIES, obj_properties); } -MainWindow *main_window_new(GApplication *application, bool force_software_renderer) { - return g_object_new(MAIN_WINDOW_TYPE, "application", application, "force_software_renderer", force_software_renderer, NULL); +MainWindow *main_window_new(SameBoyApplication *application, bool force_software_renderer) { + return g_object_new( + MAIN_WINDOW_TYPE, + "application", G_APPLICATION(application), + "force_software_renderer", force_software_renderer, + NULL + ); } void main_window_fullscreen(MainWindow *self, bool make_fullscreen) { if (make_fullscreen) { - gtk_window_unfullscreen(GTK_WINDOW(self)); + gtk_window_fullscreen(GTK_WINDOW(self)); } else { - gtk_window_fullscreen(GTK_WINDOW(self)); + gtk_window_unfullscreen(GTK_WINDOW(self)); } } @@ -109,6 +233,22 @@ void main_window_setup_menu(MainWindow *self, char *model_string) { main_menu_setup(self->main_menu, model_string); } +void main_window_open_console_window(MainWindow *self) { + gtk_widget_show_all(GTK_WIDGET(self->console)); +} + +void main_window_open_memory_viewer_window(MainWindow *self) { + g_warning("Not yet implemented!"); +} + +void main_window_open_vram_viewer_window(MainWindow *self) { + gtk_widget_show_all(GTK_WIDGET(self->vram_viewer)); +} + +void main_window_open_printer_window(MainWindow *self) { + gtk_widget_show_all(GTK_WIDGET(self->printer)); +} + // GbScreen wrappers void main_window_clear(MainWindow *self) { return gb_screen_clear(self->screen); diff --git a/gtk3/widgets/main_window.h b/gtk3/widgets/main_window.h index 9b9f1b7..6c551e6 100644 --- a/gtk3/widgets/main_window.h +++ b/gtk3/widgets/main_window.h @@ -4,15 +4,21 @@ #include #include #include +#include "../sameboy_application.h" #include "../shader.h" #define MAIN_WINDOW_TYPE (main_window_get_type()) G_DECLARE_FINAL_TYPE(MainWindow, main_window, SAMEBOY, MAIN_WINDOW, GtkApplicationWindow) -MainWindow *main_window_new(GApplication *app, bool force_software_renderer); -void main_window_fullscreen(MainWindow *self, bool make_fullscreen); +MainWindow *main_window_new(SameBoyApplication *app, bool force_software_renderer); void main_window_setup_menu(MainWindow *self, char *model_string); +void main_window_fullscreen(MainWindow *self, bool make_fullscreen); +void main_window_open_console_window(MainWindow *self); +void main_window_open_memory_viewer_window(MainWindow *self); +void main_window_open_vram_viewer_window(MainWindow *self); +void main_window_open_printer_window(MainWindow *self); + // GbScreen wrappers void main_window_clear(MainWindow *self); uint32_t *main_window_get_pixels(MainWindow *self); diff --git a/gtk3/widgets/preferences_window.c b/gtk3/widgets/preferences_window.c index 466445f..ce685ed 100644 --- a/gtk3/widgets/preferences_window.c +++ b/gtk3/widgets/preferences_window.c @@ -30,14 +30,6 @@ struct _PreferencesWindow { G_DEFINE_TYPE(PreferencesWindow, preferences_window, GTK_TYPE_WINDOW); -typedef enum { - PROP_GB_PTR = 1, - - N_PROPERTIES -} PreferencesWindowProperty; - -static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, }; - typedef enum { PREF_UPDATE, N_SIGNALS @@ -45,24 +37,6 @@ typedef enum { static guint preferences_signals[N_SIGNALS] = { 0, }; -static void preferences_window_set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { - PreferencesWindow *self = (PreferencesWindow *) object; - - switch ((PreferencesWindowProperty) property_id) { - case PROP_GB_PTR: self->gb = g_value_get_pointer(value); break; - default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); - } -} - -static void preferences_window_get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { - PreferencesWindow *self = (PreferencesWindow *) object; - - switch ((PreferencesWindowProperty) property_id) { - case PROP_GB_PTR: g_value_set_pointer(value, self->gb); break; - default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); - } -} - static void preferences_window_init(PreferencesWindow *self) { gtk_widget_init_template(GTK_WIDGET(self)); @@ -139,9 +113,12 @@ static void on_color_correction_changed(GtkWidget *w, PreferencesWindow *self) { GtkComboBox *box = GTK_COMBO_BOX(w); config.video.color_correction_id = (gchar *)gtk_combo_box_get_active_id(box); - if (self->gb) { - GB_set_color_correction_mode(self->gb, config_get_color_correction_mode()); - } + g_signal_emit( + self, + preferences_signals[PREF_UPDATE], + g_quark_from_static_string("color-correction"), + config_get_color_correction_mode() + ); } static void on_light_temperature_changed(GtkWidget *w, PreferencesWindow *self) { @@ -153,7 +130,7 @@ static void on_light_temperature_changed(GtkWidget *w, PreferencesWindow *self) self, preferences_signals[PREF_UPDATE], g_quark_from_static_string("video-color-temperature"), - &config.video.light_temperature + config.video.light_temperature ); } @@ -161,35 +138,47 @@ static void on_monochrome_palette_changed(GtkWidget *w, PreferencesWindow *self) GtkComboBox *box = GTK_COMBO_BOX(w); config.video.monochrome_palette_id = (gchar *)gtk_combo_box_get_active_id(box); - if (self->gb) { - GB_set_palette(self->gb, config_get_monochrome_palette()); - } + g_signal_emit( + self, + preferences_signals[PREF_UPDATE], + g_quark_from_static_string("monochrome-palette"), + config_get_monochrome_palette() + ); } static void on_highpass_filter_changed(GtkWidget *w, PreferencesWindow *self) { config.audio.high_pass_filter_id = (gchar *)gtk_combo_box_get_active_id(GTK_COMBO_BOX(w)); - if (self->gb) { - GB_set_highpass_filter_mode(self->gb, config_get_highpass_mode()); - } + g_signal_emit( + self, + preferences_signals[PREF_UPDATE], + g_quark_from_static_string("highpass"), + config_get_highpass_mode() + ); } static void on_rewind_duration_changed(GtkWidget *w, PreferencesWindow *self) { GtkComboBox *box = GTK_COMBO_BOX(w); config.emulation.rewind_duration = g_ascii_strtoll(gtk_combo_box_get_active_id(box), NULL, 10); - if (self->gb) { - GB_set_rewind_length(self->gb, config.emulation.rewind_duration); - } + g_signal_emit( + self, + preferences_signals[PREF_UPDATE], + g_quark_from_static_string("rewind-duration"), + config.emulation.rewind_duration + ); } static void on_rumble_mode_changed(GtkWidget *w, PreferencesWindow *self) { GtkComboBox *box = GTK_COMBO_BOX(w); config.controls.rumble_mode = (gchar *)gtk_combo_box_get_active_id(box); - if (self->gb) { - GB_set_rumble_mode(self->gb, config_get_rumble_mode()); - } + g_signal_emit( + self, + preferences_signals[PREF_UPDATE], + g_quark_from_static_string("rumble-mode"), + config_get_rumble_mode() + ); } static void on_display_border_changed(GtkWidget *w, PreferencesWindow *self) { @@ -224,7 +213,7 @@ static void on_sample_rate_changed(GtkWidget *w, PreferencesWindow *self) { self, preferences_signals[PREF_UPDATE], g_quark_from_static_string("audio-sample-rate"), - &config.audio.sample_rate + config.audio.sample_rate ); } @@ -319,16 +308,6 @@ static void preferences_window_class_init(PreferencesWindowClass *class) { GTK_WIDGET_CLASS(class)->realize = preferences_window_realize; - obj_properties[PROP_GB_PTR] = g_param_spec_pointer( - "gb", "SameBoy core pointer", "SameBoy Core pointer (GB_gameboy_t)", - G_PARAM_CONSTRUCT | G_PARAM_READWRITE - ); - - G_OBJECT_CLASS(class)->set_property = preferences_window_set_property; - G_OBJECT_CLASS(class)->get_property = preferences_window_get_property; - - g_object_class_install_properties(G_OBJECT_CLASS(class), N_PROPERTIES, obj_properties); - preferences_signals[PREF_UPDATE] = g_signal_new( "pref-update", // signal name G_TYPE_FROM_CLASS(G_OBJECT_CLASS(class)), // itype @@ -343,8 +322,8 @@ static void preferences_window_class_init(PreferencesWindowClass *class) { ); } -PreferencesWindow *preferences_window_new(GB_gameboy_t *gb) { - return g_object_new(PREFERENCES_WINDOW_TYPE, "gb", gb, NULL); +PreferencesWindow *preferences_window_new(void) { + return g_object_new(PREFERENCES_WINDOW_TYPE, NULL); } void preferences_window_update_boot_rom_selector(PreferencesWindow *self) { diff --git a/gtk3/widgets/preferences_window.h b/gtk3/widgets/preferences_window.h index bec34e6..b807656 100644 --- a/gtk3/widgets/preferences_window.h +++ b/gtk3/widgets/preferences_window.h @@ -7,7 +7,7 @@ #define PREFERENCES_WINDOW_TYPE (preferences_window_get_type()) G_DECLARE_FINAL_TYPE(PreferencesWindow, preferences_window, SAMEBOY, PREFERENCES_WINDOW, GtkWindow) -PreferencesWindow *preferences_window_new(GB_gameboy_t *gb); +PreferencesWindow *preferences_window_new(void); void preferences_window_update_boot_rom_selector(PreferencesWindow *self); #endif \ No newline at end of file diff --git a/gtk3/widgets/printer_window.c b/gtk3/widgets/printer_window.c index e46117c..55bbe0a 100644 --- a/gtk3/widgets/printer_window.c +++ b/gtk3/widgets/printer_window.c @@ -43,11 +43,11 @@ static gboolean on_printer_draw(GtkWidget *widget, cairo_t *cr, PrinterWindow *w static void on_printer_save(GtkWidget *w, PrinterWindow *self) { // This function is defined in `main.c` ... - gpointer perform_atomic(gpointer (*fn)(gpointer args), gpointer args); + // gpointer perform_atomic(gpointer (*fn)(gpointer args), gpointer args); // This is ugly, yup. - bool success = perform_atomic((gpointer)(*printer_window_save), self); - g_debug("File saving status: %d", success); + // bool success = perform_atomic((gpointer)(*printer_window_save), self); + // g_debug("File saving status: %d", success); } static void on_printer_clear(GtkWidget *w, PrinterWindow *self) {