diff --git a/gtk3/main.c b/gtk3/main.c
index 4d3ea40..1b4a61d 100644
--- a/gtk3/main.c
+++ b/gtk3/main.c
@@ -220,7 +220,6 @@ static void activate_about(GSimpleAction *action, GVariant *parameter, gpointer
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_mute_changed(GSimpleAction *action, GVariant *value, gpointer user_data_ptr);
-static void on_model_changed(GSimpleAction *action, GVariant *value, gpointer user_data_ptr);
static const GActionEntry file_entries[] = {
{ "open", activate_open, NULL, NULL, NULL },
@@ -241,7 +240,7 @@ static const GActionEntry emulation_entries[] = {
static const GActionEntry developer_entries[] = {
{ "show_console", activate_show_console, NULL, NULL, NULL },
{ "open_gtk_debugger", activate_open_gtk_debugger, NULL, NULL, NULL },
- { "open_memory_viewer", activate_open_memory_viewer, NULL, NULL, NULL },
+ // { "open_memory_viewer", activate_open_memory_viewer, NULL, NULL, NULL },
{ "open_vram_viewer", activate_open_vram_viewer, NULL, NULL, NULL },
{ "toggle_developer_mode", NULL, NULL, "false", NULL },
{ "clear_console", activate_clear_console, NULL, NULL, NULL },
@@ -252,9 +251,9 @@ static const GActionEntry app_entries[] = {
{ "about", activate_about, NULL, NULL, NULL },
{ "preferences", activate_preferences, NULL, NULL, NULL },
{ "toggle_mute", NULL, NULL, "false", on_mute_changed },
- { "change_model", NULL, "s", "@s 'CGB'", on_model_changed },
};
+
static void replace_extension(const char *src, size_t length, char *dest, const char *ext) {
memcpy(dest, src, length);
dest[length] = 0;
@@ -817,14 +816,17 @@ static char *async_console_input(GB_gameboy_t *gb) {
return input;
}
-// Returns a `GApplication`s `GMenuModel` by ID
-// GApplication menus are loaded from `gtk/menus.ui`, `gtk/menus-traditional.ui` and `gtk/menus-common.ui`.
-static GMenuModel *get_menu_model(GApplication *app, const char *id) {
- GMenu *menu;
+GtkWidget *menubar_to_menu(GtkMenuBar *menubar) {
+ GtkWidget *menu = gtk_menu_new();
+ g_autoptr(GList) iter = gtk_container_get_children(GTK_CONTAINER (menubar));
+
+ while (iter) {
+ GtkWidget *item = GTK_WIDGET(iter->data);
+ gtk_widget_reparent(item, menu);
+ iter = g_list_next(iter);
+ }
- menu = gtk_application_get_menu_by_id(GTK_APPLICATION(app), id);
-
- return menu ? G_MENU_MODEL(g_object_ref_sink(menu)) : NULL;
+ return menu;
}
// Create our application’s menu.
@@ -834,7 +836,7 @@ static GMenuModel *get_menu_model(GApplication *app, const char *id) {
// the desktop environment shell handle the menu if it signals support for it
// or uses a standard menubar inside the window.
static void setup_menu(GApplication *app) {
- GMenuModel *menubar_model = get_menu_model(app, "menubar");
+ GtkMenuBar *menubar = builder_get(GTK_MENU_BAR, "main_menu");
enum menubar_type_t menubar_type = get_show_menubar();
// Try to use a sane default
@@ -878,13 +880,8 @@ static void setup_menu(GApplication *app) {
break;
case MENUBAR_SHOW_IN_SHELL:
- g_debug("Showing menu in the shell");
- gtk_application_set_menubar(GTK_APPLICATION(app), menubar_model);
- break;
-
case MENUBAR_SHOW_IN_WINDOW: {
g_debug("Showing menu in the window");
- GtkMenuBar *menubar = GTK_MENU_BAR(gtk_menu_bar_new_from_model(menubar_model));
gtk_box_pack_start(GTK_BOX(gui_data.main_window_container), GTK_WIDGET(menubar), false, false, 0);
break;
}
@@ -901,7 +898,7 @@ static void setup_menu(GApplication *app) {
// Hook menubar up to the hamburger button
GtkMenuButton *hamburger_button = GTK_MENU_BUTTON(get_object("hamburger_button"));
- gtk_menu_button_set_menu_model(hamburger_button, menubar_model);
+ gtk_menu_button_set_popup(hamburger_button, GTK_WIDGET(menubar_to_menu(menubar)));
break;
}
}
@@ -1490,8 +1487,19 @@ static void reset(void) {
bool success = false;
if (gui_data.file) {
char *path = g_file_get_path(gui_data.file);
+ char *ext = strrchr(path, '.');
+ int result;
- if (GB_load_rom(&gb, path) == 0) {
+ 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 {
@@ -1643,14 +1651,6 @@ static void quit_interrupt(int ignored) {
quit();
}
-// `destroy` signal GCallback
-// Exits the application
-static void on_quit(GtkWidget *w, gpointer app) {
- g_debug("on_quit(%p, %p);", w, &app);
-
- 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);
@@ -1990,8 +1990,10 @@ static void startup(GApplication *app, gpointer null_ptr) {
create_action_groups(app);
if (gui_data.cli_options.prefix != NULL) {
- GAction *action = g_action_map_lookup_action(G_ACTION_MAP(gui_data.main_application), "change_model");
- g_action_change_state(action, g_variant_new_string(gui_data.cli_options.prefix));
+ // TODO
+
+ //GAction *action = g_action_map_lookup_action(G_ACTION_MAP(gui_data.main_application), "change_model");
+ //g_action_change_state(action, g_variant_new_string(gui_data.cli_options.prefix));
}
#if NDEBUG
@@ -2056,7 +2058,7 @@ static void startup(GApplication *app, gpointer null_ptr) {
}
// Let GTK choose the proper icon
- gtk_window_set_icon_list(GTK_WINDOW(gui_data.main_window), icon_list);
+ 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"));
@@ -2065,12 +2067,53 @@ static void startup(GApplication *app, gpointer null_ptr) {
g_list_free_full(icon_list, g_object_unref);
}
+G_MODULE_EXPORT void on_quit_activate(GtkWidget *w, gpointer user_data_ptr) {
+ quit();
+}
+
+G_MODULE_EXPORT void on_change_model(GtkCheckMenuItem *check_menu_item, const gchar *model_str) {
+ if (!GB_is_inited(&gb) || !gtk_check_menu_item_get_active(check_menu_item)) {
+ return;
+ }
+
+ 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;
+
+ // g_simple_action_set_state(action, value);
+
+ g_free(config.emulation.model);
+ config.emulation.model = g_strdup(model_str);
+
+ reset();
+ break;
+ default:
+ // Action has been canceled
+ break;
+ }
+
+ run();
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+}
+
static void connect_signal_handlers(GApplication *app) {
// Connect signal handlers
gtk_widget_add_events(GTK_WIDGET(gui_data.main_window), GDK_KEY_PRESS_MASK);
gtk_widget_add_events(GTK_WIDGET(gui_data.main_window), GDK_KEY_RELEASE_MASK);
- g_signal_connect(gui_data.main_window, "destroy", G_CALLBACK(on_quit), 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);
@@ -2095,6 +2138,12 @@ static void connect_signal_handlers(GApplication *app) {
g_signal_connect(get_object("vram_viewer_tilemap_canvas"), "motion_notify_event", G_CALLBACK(on_motion_vram_viewer_tilemap), NULL);
g_signal_connect(get_object("vram_viewer_stack"), "notify::visible-child", G_CALLBACK(on_vram_tab_change), NULL);
+
+ // We can’t set these values in the UI definition file
+ g_signal_connect(get_object("change_model_dmg"), "toggled", G_CALLBACK(on_change_model), (gpointer) "DMG");
+ g_signal_connect(get_object("change_model_sgb"), "toggled", G_CALLBACK(on_change_model), (gpointer) "SGB");
+ g_signal_connect(get_object("change_model_cgb"), "toggled", G_CALLBACK(on_change_model), (gpointer) "CGB");
+ g_signal_connect(get_object("change_model_agb"), "toggled", G_CALLBACK(on_change_model), (gpointer) "AGB");
}
static void update_viewport(void) {
@@ -2388,6 +2437,47 @@ static void activate_open_vram_viewer(GSimpleAction *action, GVariant *parameter
gtk_widget_show_all(GTK_WIDGET(gui_data.vram_viewer));
}
+// Closes a ROM
+static void close_rom(void) {
+ stop();
+ GB_free(&gb);
+
+ // Clear the screen as side effect
+ update_window_geometry();
+
+ if (gui_data.fallback_canvas) {
+ gtk_widget_queue_draw(GTK_WIDGET(gui_data.main_window));
+ }
+ else if (gui_data.gl_area) {
+ gtk_gl_area_queue_render(gui_data.gl_area);
+ }
+
+ // Clear the VRAM viewer
+ g_mutex_lock(&gui_data.tileset_buffer_mutex);
+ memset(gui_data.tileset_buffer, 0, sizeof gui_data.tileset_buffer);
+ g_mutex_unlock(&gui_data.tileset_buffer_mutex);
+
+ g_mutex_lock(&gui_data.tilemap_buffer_mutex);
+ memset(gui_data.tilemap_buffer, 0, sizeof gui_data.tilemap_buffer);
+ g_mutex_unlock(&gui_data.tilemap_buffer_mutex);
+
+ gtk_stack_set_visible_child_name(builder_get(GTK_STACK, "vram_viewer_stack"), "vram_viewer_tileset");
+ gtk_tree_view_set_model(builder_get(GTK_TREE_VIEW, "vram_viewer_sprites"), NULL);
+ gtk_tree_view_set_model(builder_get(GTK_TREE_VIEW, "vram_viewer_palettes"), NULL);
+
+ // Redraw the 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) {
@@ -2410,34 +2500,8 @@ static void activate_open(GSimpleAction *action, GVariant *parameter, gpointer a
}
// app.close GAction
-// Closes a ROM
static void activate_close(GSimpleAction *action, GVariant *parameter, gpointer app) {
- stop();
- GB_free(&gb);
-
- // Clear the screen as side effect
- update_window_geometry();
- gtk_widget_queue_draw(gui_data.fallback_canvas ? GTK_WIDGET(gui_data.fallback_canvas) : GTK_WIDGET(gui_data.gl_area));
-
- // Clear the VRAM viewer
- g_mutex_lock(&gui_data.tileset_buffer_mutex);
- memset(gui_data.tileset_buffer, 0, sizeof gui_data.tileset_buffer);
- g_mutex_unlock(&gui_data.tileset_buffer_mutex);
-
- g_mutex_lock(&gui_data.tilemap_buffer_mutex);
- memset(gui_data.tilemap_buffer, 0, sizeof gui_data.tilemap_buffer);
- g_mutex_unlock(&gui_data.tilemap_buffer_mutex);
-
- gtk_stack_set_visible_child_name(builder_get(GTK_STACK, "vram_viewer_stack"), "vram_viewer_tileset");
- gtk_tree_view_set_model(builder_get(GTK_TREE_VIEW, "vram_viewer_sprites"), NULL);
- gtk_tree_view_set_model(builder_get(GTK_TREE_VIEW, "vram_viewer_palettes"), NULL);
-
- // Redraw the 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);
+ close_rom();
}
// app.preferences GAction
@@ -2464,46 +2528,6 @@ static void activate_clear_console(GSimpleAction *action, GVariant *parameter, g
g_rec_mutex_unlock(&gui_data.console_output_lock);
}
-static void on_model_changed(GSimpleAction *action, GVariant *value, gpointer user_data_ptr) {
- if (!GB_is_inited(&gb)) {
- g_simple_action_set_state(action, value);
- return;
- }
-
- const gchar *model_str = g_variant_get_string(value, NULL);
-
- 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;
-
- g_simple_action_set_state(action, value);
-
- g_free(config.emulation.model);
- config.emulation.model = g_strdup(g_variant_get_string(value, NULL));
-
- reset();
- break;
- default:
- // Action has been canceled
- break;
- }
-
- run();
- gtk_widget_destroy(GTK_WIDGET(dialog));
-}
-
static void on_mute_changed(GSimpleAction *action, GVariant *value, gpointer user_data_ptr) {
config.audio.muted = g_variant_get_boolean(value);
@@ -2522,6 +2546,29 @@ static void on_pause_changed(GSimpleAction *action, GVariant *value, gpointer us
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();
+ }
+}
+
G_MODULE_EXPORT void on_boot_rom_location_changed(GtkWidget *w, gpointer user_data_ptr) {
GtkComboBox *box = GTK_COMBO_BOX(w);
const gchar *id = gtk_combo_box_get_active_id(box);
diff --git a/gtk3/resources/ui/window.ui b/gtk3/resources/ui/window.ui
index a8039ff..645ed51 100644
--- a/gtk3/resources/ui/window.ui
+++ b/gtk3/resources/ui/window.ui
@@ -1,5 +1,5 @@
-
-
+
@@ -46,9 +46,6 @@ GTK3 frontend by
Maximilian Mader https://github.com/max-m
mit-x11
-
-
-
+
+
+
+
+
+
@@ -317,9 +315,6 @@ Maximilian Mader https://github.com/max-m
False
Preferences
False
-
-
-
True
@@ -1207,6 +1202,9 @@ Maximilian Mader https://github.com/max-m
+
+
+
True
@@ -1231,9 +1229,6 @@ Maximilian Mader https://github.com/max-m
False
Memory Viewer
-
-
-
True
@@ -1278,12 +1273,35 @@ Maximilian Mader https://github.com/max-m
+
+
+
False
False
320
432
+
+
+ True
+ True
+ in
+
+
+ True
+ False
+ none
+
+
+ True
+ False
+
+
+
+
+
+
+
+
+
+ application/x-gameboy-rom
+ application/x-gameboy-color-rom
+ application/octet-stream
+
+
+ *.gb
+ *.gbc
+ *.isx
+
+
+ sameboy
+
+
+
+ True
+ False
-
+
diff --git a/gtk3/sameboy.gresource.xml b/gtk3/sameboy.gresource.xml
index e13a3f1..289a671 100644
--- a/gtk3/sameboy.gresource.xml
+++ b/gtk3/sameboy.gresource.xml
@@ -2,9 +2,11 @@
ui/window.ui
+
css/main.css
gamecontrollerdb_456ad4d.txt