[GTK3] WIP! Start refactoring of the spaghetti main.c into a subclass of GtkApplication

This commit is contained in:
Maximilian Mader 2021-01-07 22:35:41 +01:00
parent cc4be1f903
commit 39af396004
Signed by: Max
GPG Key ID: F71D56A3151C4FB3
16 changed files with 732 additions and 1533 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
The MIT License (MIT)
Copyright (c) 2015-2021 Lior Halphon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Author: Maximilian Mader
-->
<interface>
<requires lib="gtk+" version="3.22"/>
<!-- interface-license-type mit -->
<!-- interface-name SameBoy -->
<!-- interface-description SameBoy is an open source Game Boy (DMG) and Game Boy Color (CGB) emulator, written in portable C. -->
<!-- interface-copyright 2015-2021 Lior Halphon -->
<!-- interface-authors Maximilian Mader -->
<template class="AboutDialog">
<property name="can_focus">False</property>
<property name="type_hint">normal</property>
<property name="program_name">SameBoy</property>
<property name="copyright" translatable="yes">Copyright © 2015-2021 Lior Halphon</property>
<property name="website">https://sameboy.github.io</property>
<property name="website_label" translatable="yes">sameboy.github.io</property>
<property name="authors">Lior Halphon https://github.com/LIJI32
GTK3 frontend by
Maximilian Mader https://github.com/max-m</property>
<property name="logo_icon_name"/>
<property name="license_type">mit-x11</property>
<child internal-child="vbox">
<object class="GtkBox">
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
<property name="layout_style">end</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
<child type="titlebar">
<placeholder/>
</child>
</template>
</interface>

View File

@ -2,6 +2,7 @@
<gresources>
<gresource prefix="/io/github/sameboy">
<file preprocess="xml-stripblanks" compressed="true">ui/window.ui</file>
<file preprocess="xml-stripblanks" compressed="true">ui/about_dialog.ui</file>
<file preprocess="xml-stripblanks" compressed="true">ui/main_window.ui</file>
<file preprocess="xml-stripblanks" compressed="true">ui/main_menu.ui</file>
<file preprocess="xml-stripblanks" compressed="true">ui/console_window.ui</file>

388
gtk3/sameboy_application.c Normal file
View File

@ -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", "<file path>" },
{ "model", 'm', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, NULL, "Override the model type to emulate", "<model type>" },
{ "config", 'c', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &app->cli_options.config_path, "Override the path of the configuration file", "<file path>" },
{ "no-gl", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &app->cli_options.force_software_renderer, "Do not use OpenGL for rendering", NULL },
{ NULL }
};
// Setup our command line information
g_application_add_main_option_entries(G_APPLICATION(app), entries);
g_application_set_option_context_parameter_string(G_APPLICATION(app), "[FILE…]");
g_application_set_option_context_summary(G_APPLICATION(app), "SameBoy is an open source Game Boy (DMG) and Game Boy Color (CGB) emulator.");
}
static gint sameboy_application_handle_local_options(GApplication *gapp, GVariantDict *options) {
SameBoyApplication *app = SAMEBOY_APPLICATION(gapp);
g_debug("sameboy_application_handle_local_options");
guint32 count;
if (g_variant_dict_lookup(options, "version", "b", &count)) {
g_message("SameBoy v" xstr(VERSION));
return EXIT_SUCCESS;
}
// Handle model override
GVariant *model_name_var = g_variant_dict_lookup_value(options, "model", G_VARIANT_TYPE_STRING);
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);
}

View File

@ -0,0 +1,10 @@
#ifndef sameboy_application_h
#define sameboy_application_h
#include <gtk/gtk.h>
#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

View File

@ -45,7 +45,6 @@ typedef struct GuiData {
PreferencesWindow *preferences;
VramViewerWindow *vram_viewer;
PrinterWindow *printer;
GtkWindow *memory_viewer;
// Audio and video

View File

@ -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);
}

View File

@ -0,0 +1,11 @@
#ifndef about_dialog_h
#define about_dialog_h
#include <gtk/gtk.h>
#define ABOUT_DIALOG_TYPE (about_dialog_get_type())
G_DECLARE_FINAL_TYPE(AboutDialog, about_dialog, SAMEBOY, ABOUT_DIALOG, GtkAboutDialog)
AboutDialog *about_dialog_new();
#endif

View File

@ -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;
}

View File

@ -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

View File

@ -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");
}

View File

@ -1,14 +1,30 @@
#include "main_window.h"
#include <Core/gb.h>
#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)) {
// wouldnt 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)) {
// wouldnt 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);

View File

@ -4,15 +4,21 @@
#include <stdbool.h>
#include <gtk/gtk.h>
#include <Core/gb.h>
#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);

View File

@ -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) {

View File

@ -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

View File

@ -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) {