[GTK3] Implement more menu handlers
Also fix some memory leaks.
This commit is contained in:
parent
a3389f2b71
commit
2dd5abfae7
477
gtk3/main.c
477
gtk3/main.c
@ -38,21 +38,23 @@ static GtkWindow *printer;
|
||||
|
||||
static shader_t shader;
|
||||
|
||||
static UserData user_data = { NULL };
|
||||
static GuiData gui_data = { NULL };
|
||||
static GB_gameboy_t gb;
|
||||
static uint32_t *image_buffers[3];
|
||||
static unsigned char current_buffer;
|
||||
|
||||
static bool supports_gl;
|
||||
static bool is_fullscreen;
|
||||
|
||||
static bool paused = false;
|
||||
static bool underclock_down = false, rewind_down = false, do_rewind = false, rewind_paused = false, turbo_down = false;
|
||||
static double clock_mutliplier = 1.0;
|
||||
static char *battery_save_path_ptr;
|
||||
static Rect viewport = {0};
|
||||
static Rect scrollRect = {0};
|
||||
static bool vram_viewer_visible = false;
|
||||
static bool running = true;
|
||||
|
||||
static bool running = false;
|
||||
static bool stopping = false;
|
||||
|
||||
#define tileset_buffer_length 256 * 192 * 4
|
||||
static uint32_t tileset_buffer[tileset_buffer_length] = {0};
|
||||
@ -81,6 +83,12 @@ static const GActionEntry app_entries[] = {
|
||||
{ "open_memory_viewer", activate_open_memory_viewer, NULL, NULL, NULL },
|
||||
{ "open_vram_viewer", activate_open_vram_viewer, NULL, NULL, NULL },
|
||||
{ "preferences", activate_preferences, NULL, NULL, NULL },
|
||||
{ "reset", activate_reset, NULL, NULL, NULL },
|
||||
{ "toggle_blend_frames", NULL, NULL, "true", NULL },
|
||||
{ "toggle_developer_mode", NULL, NULL, "false", NULL },
|
||||
{ "toggle_mute", NULL, NULL, "false", on_mute_changed },
|
||||
{ "change_model", NULL, "s", "@s 'CGB'", on_model_changed },
|
||||
{ "pause", NULL, NULL, "false", on_pause_changed },
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
@ -91,9 +99,9 @@ int main(int argc, char *argv[]) {
|
||||
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, NULL, "Start in fullscreen mode", NULL },
|
||||
{ "bootrom", 'b', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &user_data.boot_rom_path, "Path to the boot ROM to use", "<file path>" },
|
||||
{ "bootrom", 'b', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &gui_data.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, &user_data.config_path, "Override the path of the configuration file", "<file path>" },
|
||||
{ "config", 'c', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &gui_data.config_path, "Override the path of the configuration file", "<file path>" },
|
||||
{ NULL }
|
||||
};
|
||||
// Setup our command line information
|
||||
@ -102,11 +110,11 @@ int main(int argc, char *argv[]) {
|
||||
g_application_set_option_context_summary(G_APPLICATION(main_application), "SameBoy is an open source Game Boy (DMG) and Game Boy Color (CGB) emulator.");
|
||||
|
||||
// Add signal handlers
|
||||
g_signal_connect(main_application, "handle-local-options", G_CALLBACK(handle_local_options), &user_data);
|
||||
g_signal_connect(main_application, "startup", G_CALLBACK(startup), &user_data);
|
||||
g_signal_connect(main_application, "activate", G_CALLBACK(activate), &user_data);
|
||||
g_signal_connect(main_application, "open", G_CALLBACK(open), &user_data);
|
||||
g_signal_connect(main_application, "shutdown", G_CALLBACK(shutdown), &user_data);
|
||||
g_signal_connect(main_application, "handle-local-options", G_CALLBACK(handle_local_options), &gui_data);
|
||||
g_signal_connect(main_application, "startup", G_CALLBACK(startup), &gui_data);
|
||||
g_signal_connect(main_application, "activate", G_CALLBACK(activate), &gui_data);
|
||||
g_signal_connect(main_application, "open", G_CALLBACK(open), &gui_data);
|
||||
g_signal_connect(main_application, "shutdown", G_CALLBACK(shutdown), &gui_data);
|
||||
|
||||
// Start our GApplication main loop
|
||||
int status = g_application_run(G_APPLICATION(main_application), argc, argv);
|
||||
@ -116,8 +124,8 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
// This function gets called after the parsing of the commandline options has occurred.
|
||||
static gint handle_local_options(GApplication *app, GVariantDict *options, gpointer user_data_gptr) {
|
||||
UserData *user_data = user_data_gptr;
|
||||
static gint handle_local_options(GApplication *app, GVariantDict *options, gpointer gui_data_gptr) {
|
||||
GuiData *gui_data = gui_data_gptr;
|
||||
guint32 count;
|
||||
|
||||
if (g_variant_dict_lookup(options, "version", "b", &count)) {
|
||||
@ -126,7 +134,7 @@ static gint handle_local_options(GApplication *app, GVariantDict *options, gpoin
|
||||
}
|
||||
|
||||
if (g_variant_dict_lookup(options, "fullscreen", "b", &count)) {
|
||||
user_data->fullscreen = true;
|
||||
gui_data->fullscreen = true;
|
||||
}
|
||||
|
||||
// Handle model override
|
||||
@ -137,42 +145,42 @@ static gint handle_local_options(GApplication *app, GVariantDict *options, gpoin
|
||||
// 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) {
|
||||
user_data->model = GB_MODEL_DMG_B;
|
||||
gui_data->model = GB_MODEL_DMG_B;
|
||||
}
|
||||
else {
|
||||
user_data->model = GB_MODEL_DMG_B;
|
||||
gui_data->model = GB_MODEL_DMG_B;
|
||||
g_printerr("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) {
|
||||
user_data->model = GB_MODEL_SGB;
|
||||
gui_data->model = GB_MODEL_SGB;
|
||||
}
|
||||
else if (g_str_has_suffix(model_name, "-PAL")) {
|
||||
user_data->model = GB_MODEL_SGB | GB_MODEL_PAL_BIT;
|
||||
gui_data->model = GB_MODEL_SGB | GB_MODEL_PAL_BIT;
|
||||
}
|
||||
else if (g_str_has_suffix(model_name, "2")) {
|
||||
user_data->model = GB_MODEL_SGB2;
|
||||
gui_data->model = GB_MODEL_SGB2;
|
||||
}
|
||||
else {
|
||||
user_data->model = GB_MODEL_SGB2;
|
||||
gui_data->model = GB_MODEL_SGB2;
|
||||
g_printerr("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")) {
|
||||
user_data->model = GB_MODEL_CGB_C;
|
||||
gui_data->model = GB_MODEL_CGB_C;
|
||||
}
|
||||
else if (g_str_has_suffix(model_name, "-E") || g_strcmp0(model_name, "CGB") == 0) {
|
||||
user_data->model = GB_MODEL_CGB_E;
|
||||
gui_data->model = GB_MODEL_CGB_E;
|
||||
}
|
||||
else {
|
||||
user_data->model = GB_MODEL_CGB_E;
|
||||
gui_data->model = GB_MODEL_CGB_E;
|
||||
g_printerr("Unsupported revision: %s\nFalling back to CGB-E", model_name);
|
||||
}
|
||||
}
|
||||
else if (g_str_has_prefix(model_name, "AGB")) {
|
||||
user_data->model = GB_MODEL_AGB;
|
||||
gui_data->model = GB_MODEL_AGB;
|
||||
}
|
||||
else {
|
||||
g_printerr("Unknown model: %s\n", model_name);
|
||||
@ -190,7 +198,7 @@ static gint handle_local_options(GApplication *app, GVariantDict *options, gpoin
|
||||
gboolean test_gl_support(void) {
|
||||
gboolean result = FALSE;
|
||||
|
||||
GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
||||
GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
||||
g_signal_connect(window, "realize", G_CALLBACK(gl_check_realize), &result);
|
||||
gtk_widget_realize(window);
|
||||
gtk_widget_destroy(window);
|
||||
@ -271,13 +279,18 @@ static gboolean init_controllers() {
|
||||
}
|
||||
|
||||
static gboolean init_audio() {
|
||||
bool audio_playing = SDL_GetAudioDeviceStatus(device_id) == SDL_AUDIO_PLAYING;
|
||||
SDL_PauseAudioDevice(device_id, 1);
|
||||
SDL_ClearQueuedAudio(device_id);
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
|
||||
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
|
||||
g_print("Failed to initialize audio: %s\n", SDL_GetError());
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
memset(&want_aspec, 0, sizeof(want_aspec));
|
||||
want_aspec.freq = DEFAULT_AUDIO_SAMPLE_RATE;
|
||||
want_aspec.freq = gui_data.sample_rate;
|
||||
want_aspec.format = AUDIO_S16SYS;
|
||||
want_aspec.channels = 2;
|
||||
want_aspec.samples = 512;
|
||||
@ -302,7 +315,10 @@ static gboolean init_audio() {
|
||||
|
||||
device_id = SDL_OpenAudioDevice(0, 0, &want_aspec, &have_aspec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | SDL_AUDIO_ALLOW_SAMPLES_CHANGE);
|
||||
|
||||
SDL_PauseAudioDevice(device_id, 0);
|
||||
g_print("Requested Sample Rate: %d Hz\nUsed Sample Rate: %d Hz\n", want_aspec.freq, have_aspec.freq);
|
||||
|
||||
SDL_PauseAudioDevice(device_id, audio_playing? 0 : 1);
|
||||
GB_set_sample_rate(&gb, have_aspec.freq);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
@ -327,6 +343,8 @@ static void gb_audio_callback(GB_gameboy_t *gb, GB_sample_t *sample) {
|
||||
}
|
||||
|
||||
static char *sync_console_input(GB_gameboy_t *gb) {
|
||||
console_log(gb, ">", 0);
|
||||
|
||||
g_mutex_lock(&debugger_input_mutex);
|
||||
g_cond_wait(&debugger_input_cond, &debugger_input_mutex);
|
||||
|
||||
@ -359,12 +377,6 @@ static char *async_console_input(GB_gameboy_t *gb) {
|
||||
return input;
|
||||
}
|
||||
|
||||
typedef struct LogData {
|
||||
GB_gameboy_t *gb;
|
||||
const char *string;
|
||||
GB_log_attributes attributes;
|
||||
} LogData;
|
||||
|
||||
static void on_console_log(gpointer user_data_gptr) {
|
||||
LogData *log_data = (LogData *)user_data_gptr;
|
||||
GB_gameboy_t *gb = log_data->gb;
|
||||
@ -512,21 +524,19 @@ static void setup_menu(GApplication *app) {
|
||||
// Recursively goes through all children of the given container and sets
|
||||
// our `is_separator` function to all children of type`GtkComboBox`
|
||||
static void set_combo_box_row_separator_func(GtkContainer *container) {
|
||||
GList *list = gtk_container_get_children(container);
|
||||
GList *children = gtk_container_get_children(container);
|
||||
|
||||
while (list) {
|
||||
if (GTK_IS_COMBO_BOX(list->data)) {
|
||||
gtk_combo_box_set_row_separator_func(GTK_COMBO_BOX(list->data), is_separator, NULL, NULL);
|
||||
for (GList *l = children; l; l = l->next) {
|
||||
if (GTK_IS_COMBO_BOX(l->data)) {
|
||||
gtk_combo_box_set_row_separator_func(GTK_COMBO_BOX(l->data), is_separator, NULL, NULL);
|
||||
}
|
||||
|
||||
if (GTK_IS_CONTAINER(list->data)) {
|
||||
set_combo_box_row_separator_func(GTK_CONTAINER(list->data));
|
||||
if (GTK_IS_CONTAINER(l->data)) {
|
||||
set_combo_box_row_separator_func(GTK_CONTAINER(l->data));
|
||||
}
|
||||
|
||||
list = list->next;
|
||||
}
|
||||
|
||||
g_list_free_full(list, NULL);
|
||||
g_list_free(children);
|
||||
}
|
||||
|
||||
// Determines if a ComboBox entry should be converted into a separator.
|
||||
@ -536,14 +546,19 @@ static gboolean is_separator(GtkTreeModel *model, GtkTreeIter *iter, gpointer da
|
||||
|
||||
gtk_tree_model_get(model, iter, 0, &text, -1);
|
||||
gboolean result = g_strcmp0("<separator>", text) == 0;
|
||||
g_free(text);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Determines how many frame buffers to use
|
||||
static unsigned char number_of_buffers(void) {
|
||||
// TODO
|
||||
bool should_blend = !fallback_canvas;
|
||||
if (fallback_canvas) return 2;
|
||||
|
||||
// TODO: Should we cache the action?
|
||||
GAction *action = g_action_map_lookup_action(G_ACTION_MAP(main_application), "toggle_blend_frames");
|
||||
GVariant *value = g_action_get_state(action);
|
||||
gboolean should_blend = g_variant_get_boolean(value);
|
||||
|
||||
return should_blend? 3 : 2;
|
||||
}
|
||||
@ -573,8 +588,8 @@ static void quit_interrupt(int ignored) {
|
||||
}
|
||||
|
||||
// This functions gets called immediately after registration of the GApplication
|
||||
static void startup(GApplication *app, gpointer user_data_gptr) {
|
||||
UserData *user_data = user_data_gptr;
|
||||
static void startup(GApplication *app, gpointer gui_data_gptr) {
|
||||
GuiData *gui_data = gui_data_gptr;
|
||||
|
||||
signal(SIGINT, quit_interrupt);
|
||||
|
||||
@ -605,14 +620,21 @@ static void startup(GApplication *app, gpointer user_data_gptr) {
|
||||
preferences = GTK_WINDOW(get_object("preferences"));
|
||||
|
||||
g_signal_connect(preferences, "realize", G_CALLBACK(on_preferences_realize), (gpointer) builder);
|
||||
init_settings(user_data->config_path, preferences);
|
||||
init_settings(gui_data->config_path, preferences);
|
||||
|
||||
vram_viewer = GTK_WINDOW(get_object("vram_viewer"));
|
||||
memory_viewer = GTK_WINDOW(get_object("memory_viewer"));
|
||||
|
||||
|
||||
console = GTK_WINDOW(get_object("console"));
|
||||
printer = GTK_WINDOW(get_object("printer"));
|
||||
|
||||
if (config.sample_rate == -1) {
|
||||
gui_data->sample_rate = DEFAULT_AUDIO_SAMPLE_RATE;
|
||||
}
|
||||
else {
|
||||
gui_data->sample_rate = config.sample_rate;
|
||||
}
|
||||
|
||||
// setup main window
|
||||
main_window = GTK_APPLICATION_WINDOW(gtk_application_window_new(GTK_APPLICATION(app)));
|
||||
main_window_container = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
|
||||
@ -652,18 +674,20 @@ static void startup(GApplication *app, gpointer user_data_gptr) {
|
||||
|
||||
// Add missing information to the about dialog
|
||||
GtkAboutDialog *about_dialog = GTK_ABOUT_DIALOG(get_object("about_dialog"));
|
||||
gtk_about_dialog_set_logo(about_dialog, g_list_nth_data(icon_list, 3)); // reuse the 64x64 icon
|
||||
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(icon_list);
|
||||
g_list_free_full(icon_list, g_object_unref);
|
||||
}
|
||||
|
||||
// This function gets called when the GApplication gets activated, i.e. it is ready to show widgets.
|
||||
static void activate(GApplication *app, gpointer user_data_gptr) {
|
||||
UserData *user_data = user_data_gptr;
|
||||
static void activate(GApplication *app, gpointer gui_data_gptr) {
|
||||
GuiData *gui_data = gui_data_gptr;
|
||||
|
||||
if (user_data->fullscreen) {
|
||||
gtk_window_fullscreen(GTK_WINDOW(main_window));
|
||||
}
|
||||
// initialize SameBoy core
|
||||
init(gui_data);
|
||||
|
||||
init_audio();
|
||||
init_controllers();
|
||||
|
||||
// Connect signal handlers
|
||||
gtk_widget_add_events(GTK_WIDGET(main_window), GDK_KEY_PRESS_MASK);
|
||||
@ -672,7 +696,8 @@ static void activate(GApplication *app, gpointer user_data_gptr) {
|
||||
g_signal_connect(main_window, "destroy", G_CALLBACK(on_quit), app);
|
||||
g_signal_connect(main_window, "key_press_event", G_CALLBACK(on_key_press), NULL);
|
||||
g_signal_connect(main_window, "key_release_event", G_CALLBACK(on_key_press), NULL);
|
||||
|
||||
g_signal_connect(main_window, "window-state-event", G_CALLBACK(on_window_state_change), NULL);
|
||||
|
||||
g_signal_connect(vram_viewer, "realize", G_CALLBACK(on_vram_viewer_realize), NULL);
|
||||
g_signal_connect(vram_viewer, "unrealize", G_CALLBACK(on_vram_viewer_unrealize), NULL);
|
||||
|
||||
@ -712,7 +737,7 @@ static void activate(GApplication *app, gpointer user_data_gptr) {
|
||||
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);
|
||||
gtk_style_context_add_provider_for_screen(screen, GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
|
||||
GtkTextView *text_view = builder_get(GTK_TEXT_VIEW, "console_screen");
|
||||
GtkTextBuffer *text_buf = gtk_text_view_get_buffer(text_view);
|
||||
@ -720,35 +745,61 @@ static void activate(GApplication *app, gpointer user_data_gptr) {
|
||||
gtk_text_buffer_create_tag(text_buf, "underline", "underline", PANGO_UNDERLINE_SINGLE, "underline-set", TRUE, NULL);
|
||||
gtk_text_buffer_create_tag(text_buf, "dashed_underline", "underline", PANGO_UNDERLINE_DOUBLE, "underline-set", TRUE, NULL);
|
||||
|
||||
if (gui_data->fullscreen) {
|
||||
gtk_window_fullscreen(GTK_WINDOW(main_window));
|
||||
}
|
||||
|
||||
gtk_application_add_window(GTK_APPLICATION(app), GTK_WINDOW(main_window));
|
||||
gtk_widget_show_all(GTK_WIDGET(main_window));
|
||||
|
||||
g_thread_new("CoreLoop", run, user_data);
|
||||
g_mutex_init(&debugger_input_mutex);
|
||||
g_cond_init(&debugger_input_cond);
|
||||
g_mutex_init(&console_output_lock);
|
||||
|
||||
if (!debugger_input_queue) {
|
||||
debugger_input_queue = g_ptr_array_sized_new(4);
|
||||
}
|
||||
|
||||
// Start the emulation thread
|
||||
run(gui_data);
|
||||
}
|
||||
|
||||
// This function gets called when the application is closed.
|
||||
static void shutdown(GApplication *app, GFile **files, gint n_files, const gchar *hint, gpointer user_data_gptr) {
|
||||
static void shutdown(GApplication *app, GFile **files, gint n_files, const gchar *hint, gpointer gui_data_gptr) {
|
||||
g_print("SHUTDOWN\n");
|
||||
|
||||
stop(&gui_data);
|
||||
while (stopping);
|
||||
|
||||
g_object_unref(builder);
|
||||
|
||||
save_settings();
|
||||
free_settings();
|
||||
SDL_Quit();
|
||||
|
||||
if (image_buffers[0]) g_free(image_buffers[0]);
|
||||
if (image_buffers[1]) g_free(image_buffers[1]);
|
||||
if (image_buffers[2]) g_free(image_buffers[2]);
|
||||
free_shader(&shader);
|
||||
free_master_shader();
|
||||
|
||||
GB_free(&gb);
|
||||
}
|
||||
|
||||
// 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 user_data_gptr) {
|
||||
UserData *user_data = user_data_gptr;
|
||||
static void open(GApplication *app, GFile **files, gint n_files, const gchar *hint, gpointer gui_data_gptr) {
|
||||
GuiData *gui_data = gui_data_gptr;
|
||||
|
||||
if (n_files > 1) {
|
||||
g_printerr("More than one file specified\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
user_data->file = g_file_dup(files[0]);
|
||||
gui_data->file = g_file_dup(files[0]);
|
||||
|
||||
// We have handled the files, now activate the application
|
||||
activate(app, user_data_gptr);
|
||||
activate(app, gui_data_gptr);
|
||||
}
|
||||
|
||||
// Tell our application to quit.
|
||||
@ -782,6 +833,17 @@ static gboolean on_key_press(GtkWidget *w, GdkEventKey *event, gpointer data) {
|
||||
|
||||
case GDK_KEY_k: mask = BUTTON_MASK_B; break;
|
||||
case GDK_KEY_l: mask = BUTTON_MASK_A; break;
|
||||
|
||||
case GDK_KEY_F11: {
|
||||
if (event->type == GDK_KEY_RELEASE) {
|
||||
if (is_fullscreen) {
|
||||
gtk_window_unfullscreen(GTK_WINDOW(main_window));
|
||||
}
|
||||
else {
|
||||
gtk_window_fullscreen(GTK_WINDOW(main_window));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (event->type == GDK_KEY_PRESS) {
|
||||
@ -794,6 +856,10 @@ static gboolean on_key_press(GtkWidget *w, GdkEventKey *event, gpointer data) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void on_window_state_change(GtkWidget *w, GdkEventWindowState *event, gpointer data) {
|
||||
is_fullscreen = event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN;
|
||||
}
|
||||
|
||||
// app.about GAction
|
||||
// Opens the about dialog
|
||||
static void activate_about(GSimpleAction *action, GVariant *parameter, gpointer app) {
|
||||
@ -858,6 +924,72 @@ static void activate_quit(GSimpleAction *action, GVariant *parameter, gpointer a
|
||||
quit(G_APPLICATION(app));
|
||||
}
|
||||
|
||||
// app.reset GAction
|
||||
// Resets the emulation
|
||||
static void activate_reset(GSimpleAction *action, GVariant *parameter, gpointer app) {
|
||||
if (gui_data.stopped) {
|
||||
reset(&gui_data);
|
||||
}
|
||||
else {
|
||||
stop(&gui_data);
|
||||
reset(&gui_data);
|
||||
run(&gui_data);
|
||||
}
|
||||
}
|
||||
|
||||
static void on_model_changed(GSimpleAction *action, GVariant *value, gpointer user_data) {
|
||||
const gchar *model_str = g_variant_get_string(value, NULL);
|
||||
|
||||
GtkMessageDialog *dialog = GTK_MESSAGE_DIALOG(gtk_message_dialog_new(
|
||||
GTK_WINDOW(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(&gui_data);
|
||||
gint result = gtk_dialog_run(GTK_DIALOG(dialog));
|
||||
|
||||
switch (result) {
|
||||
case GTK_RESPONSE_YES:
|
||||
g_print("TODO: RESET!\n");
|
||||
g_simple_action_set_state(action, value);
|
||||
break;
|
||||
default:
|
||||
// Action has been canceled
|
||||
break;
|
||||
}
|
||||
|
||||
run(&gui_data);
|
||||
gtk_widget_destroy(GTK_WIDGET(dialog));
|
||||
}
|
||||
|
||||
static void on_mute_changed(GSimpleAction *action, GVariant *value, gpointer user_data) {
|
||||
gboolean do_mute = g_variant_get_boolean(value);
|
||||
|
||||
if (do_mute) {
|
||||
SDL_PauseAudioDevice(device_id, 1);
|
||||
}
|
||||
else {
|
||||
SDL_ClearQueuedAudio(device_id);
|
||||
SDL_PauseAudioDevice(device_id, 0);
|
||||
}
|
||||
|
||||
g_simple_action_set_state(action, value);
|
||||
}
|
||||
|
||||
static void on_pause_changed(GSimpleAction *action, GVariant *value, gpointer user_data) {
|
||||
if (g_variant_get_boolean(value)) {
|
||||
stop(&gui_data);
|
||||
}
|
||||
else {
|
||||
run(&gui_data);
|
||||
}
|
||||
|
||||
g_simple_action_set_state(action, value);
|
||||
}
|
||||
|
||||
// `destroy` signal GCallback
|
||||
// Exits the application
|
||||
static void on_quit(GtkWidget *w, gpointer app) {
|
||||
@ -919,7 +1051,7 @@ static gboolean on_draw_fallback(GtkWidget *widget, cairo_t *cr, gpointer data)
|
||||
guint screen_height = GB_get_screen_height(&gb);
|
||||
|
||||
gtk_render_background(context, cr, 0, 0, width, height);
|
||||
|
||||
|
||||
cairo_surface_t *surface = cairo_image_surface_create_for_data(
|
||||
(unsigned char *) get_current_buffer(),
|
||||
CAIRO_FORMAT_RGB24,
|
||||
@ -1225,6 +1357,7 @@ G_MODULE_EXPORT void on_graphic_filter_changed(GtkWidget *w, gpointer user_data_
|
||||
GtkComboBox *box = GTK_COMBO_BOX(w);
|
||||
config.shader = (gchar *)gtk_combo_box_get_active_id(box);
|
||||
|
||||
free_shader(&shader);
|
||||
init_shader_with_name(&shader, config.shader);
|
||||
}
|
||||
|
||||
@ -1248,6 +1381,20 @@ G_MODULE_EXPORT void on_rewind_duration_changed(GtkWidget *w, gpointer user_data
|
||||
config.rewind_duration = g_ascii_strtoll(gtk_combo_box_get_active_id(box), NULL, 10);
|
||||
}
|
||||
|
||||
G_MODULE_EXPORT void on_sample_rate_changed(GtkWidget *w, gpointer user_data_gptr) {
|
||||
GtkComboBox *box = GTK_COMBO_BOX(w);
|
||||
config.sample_rate = g_ascii_strtoll(gtk_combo_box_get_active_id(box), NULL, 10);
|
||||
|
||||
if (config.sample_rate == -1) {
|
||||
gui_data.sample_rate = DEFAULT_AUDIO_SAMPLE_RATE;
|
||||
}
|
||||
else {
|
||||
gui_data.sample_rate = config.sample_rate;
|
||||
}
|
||||
|
||||
init_audio();
|
||||
}
|
||||
|
||||
G_MODULE_EXPORT void on_sgb_model_changed(GtkWidget *w, gpointer user_data_gptr) {
|
||||
GtkComboBox *box = GTK_COMBO_BOX(w);
|
||||
config.sgb_revision_name = (gchar *)gtk_combo_box_get_active_id(box);
|
||||
@ -1343,15 +1490,19 @@ static void update_window_geometry() {
|
||||
);
|
||||
|
||||
// Setup our image buffers
|
||||
if (image_buffers[0]) free(image_buffers[0]);
|
||||
if (image_buffers[1]) free(image_buffers[1]);
|
||||
if (image_buffers[2]) free(image_buffers[2]);
|
||||
if (image_buffers[0]) g_free(image_buffers[0]);
|
||||
if (image_buffers[1]) g_free(image_buffers[1]);
|
||||
if (image_buffers[2]) g_free(image_buffers[2]);
|
||||
|
||||
size_t buffer_size = sizeof(image_buffers[0][0]) * GB_get_screen_width(&gb) * GB_get_screen_height(&gb);
|
||||
|
||||
image_buffers[0] = malloc(buffer_size);
|
||||
image_buffers[1] = malloc(buffer_size);
|
||||
image_buffers[2] = malloc(buffer_size);
|
||||
image_buffers[0] = g_malloc0(buffer_size);
|
||||
image_buffers[1] = g_malloc0(buffer_size);
|
||||
image_buffers[2] = g_malloc0(buffer_size);
|
||||
|
||||
if (GB_is_inited(&gb)) {
|
||||
GB_set_pixels_output(&gb, get_pixels());
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_events(GB_gameboy_t *gb) {
|
||||
@ -1630,52 +1781,53 @@ static void vblank(GB_gameboy_t *gb) {
|
||||
g_idle_add((GSourceFunc) on_vblank, NULL);
|
||||
}
|
||||
|
||||
static gpointer run(gpointer user_data_gptr) {
|
||||
UserData *user_data = user_data_gptr;
|
||||
static void run(GuiData *gui_data) {
|
||||
if (running) return;
|
||||
while (stopping);
|
||||
|
||||
GB_model_t prev_model = GB_get_model(&gb);
|
||||
GB_model_t model = user_data->model? user_data->model : GB_MODEL_CGB_E; // TODO: Model from config
|
||||
g_thread_new("CoreLoop", run_thread, gui_data);
|
||||
}
|
||||
|
||||
if (!debugger_input_queue) {
|
||||
g_mutex_init(&debugger_input_mutex);
|
||||
g_cond_init(&debugger_input_cond);
|
||||
g_mutex_init(&console_output_lock);
|
||||
debugger_input_queue = g_ptr_array_sized_new(4);
|
||||
}
|
||||
static gpointer run_thread(gpointer gui_data_gptr) {
|
||||
GuiData *gui_data = gui_data_gptr;
|
||||
|
||||
if (GB_is_inited(&gb)) {
|
||||
GB_switch_model_and_reset(&gb, model);
|
||||
|
||||
GtkRequisition minimum_size;
|
||||
GtkRequisition natural_size;
|
||||
gtk_widget_get_preferred_size(GTK_WIDGET(main_window), &minimum_size, &natural_size);
|
||||
|
||||
// Check SGB -> non-SGB and non-SGB to SGB transitions
|
||||
if (GB_get_screen_width(&gb) != minimum_size.width || GB_get_screen_height(&gb) != minimum_size.height) {
|
||||
update_window_geometry();
|
||||
}
|
||||
if (gui_data->stopped) {
|
||||
start(gui_data);
|
||||
}
|
||||
else {
|
||||
init_audio();
|
||||
init_controllers();
|
||||
|
||||
GB_init(&gb, model);
|
||||
update_window_geometry();
|
||||
|
||||
GB_set_vblank_callback(&gb, vblank);
|
||||
GB_set_pixels_output(&gb, get_current_buffer());
|
||||
GB_set_rgb_encode_callback(&gb, rgb_encode);
|
||||
GB_set_sample_rate(&gb, DEFAULT_AUDIO_SAMPLE_RATE);
|
||||
GB_set_color_correction_mode(&gb, get_color_correction_mode());
|
||||
GB_set_highpass_filter_mode(&gb, get_highpass_mode());
|
||||
GB_set_rewind_length(&gb, config.rewind_duration);
|
||||
GB_set_update_input_hint_callback(&gb, handle_events);
|
||||
GB_apu_set_sample_callback(&gb, gb_audio_callback);
|
||||
GB_set_input_callback(&gb, sync_console_input);
|
||||
GB_set_async_input_callback(&gb, async_console_input);
|
||||
GB_set_log_callback(&gb, console_log);
|
||||
init(gui_data);
|
||||
reset(gui_data);
|
||||
start(gui_data);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void init(GuiData *gui_data) {
|
||||
if (GB_is_inited(&gb)) return;
|
||||
g_print("init: %p\n", g_thread_self());
|
||||
|
||||
GB_model_t prev_model = GB_get_model(&gb);
|
||||
gui_data->model = gui_data->model? gui_data->model : GB_MODEL_CGB_E; // TODO: Model from config
|
||||
|
||||
GB_init(&gb, gui_data->model);
|
||||
update_window_geometry();
|
||||
|
||||
GB_set_vblank_callback(&gb, vblank);
|
||||
GB_set_pixels_output(&gb, get_current_buffer());
|
||||
GB_set_rgb_encode_callback(&gb, rgb_encode);
|
||||
GB_set_sample_rate(&gb, gui_data->sample_rate);
|
||||
GB_set_color_correction_mode(&gb, get_color_correction_mode());
|
||||
GB_set_highpass_filter_mode(&gb, get_highpass_mode());
|
||||
GB_set_rewind_length(&gb, config.rewind_duration);
|
||||
GB_set_update_input_hint_callback(&gb, handle_events);
|
||||
GB_apu_set_sample_callback(&gb, gb_audio_callback);
|
||||
GB_set_input_callback(&gb, sync_console_input);
|
||||
GB_set_async_input_callback(&gb, async_console_input);
|
||||
GB_set_log_callback(&gb, console_log);
|
||||
}
|
||||
|
||||
static void load_boot_rom(GuiData *gui_data) {
|
||||
GError *error;
|
||||
char *boot_rom_path;
|
||||
char *boot_rom_name;
|
||||
@ -1683,15 +1835,15 @@ static gpointer run(gpointer user_data_gptr) {
|
||||
const guchar *boot_rom_data;
|
||||
gsize boot_rom_size;
|
||||
|
||||
if (user_data->boot_rom_path != NULL) {
|
||||
g_print("Trying to load boot ROM from %s\n", user_data->boot_rom_path);
|
||||
if (GB_load_boot_rom(&gb, user_data->boot_rom_path)) {
|
||||
if (gui_data->boot_rom_path != NULL) {
|
||||
g_print("Trying to load boot ROM from %s\n", gui_data->boot_rom_path);
|
||||
if (GB_load_boot_rom(&gb, gui_data->boot_rom_path)) {
|
||||
g_printerr("Falling back to boot ROM from config\n");
|
||||
goto config_boot_rom;
|
||||
}
|
||||
}
|
||||
else { config_boot_rom:
|
||||
switch (model) {
|
||||
switch (gui_data->model) {
|
||||
case GB_MODEL_DMG_B:
|
||||
boot_rom_name = "dmg_boot.bin";
|
||||
break;
|
||||
@ -1722,13 +1874,17 @@ static gpointer run(gpointer user_data_gptr) {
|
||||
g_print("Trying to load boot ROM from %s\n", boot_rom_path);
|
||||
|
||||
if (GB_load_boot_rom(&gb, boot_rom_path)) {
|
||||
g_free(boot_rom_path);
|
||||
g_printerr("Falling back to internal boot ROM\n");
|
||||
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_free(boot_rom_path);
|
||||
|
||||
if (boot_rom_f == NULL) {
|
||||
g_printerr("Failed to load internal boot ROM: %s\n", boot_rom_path);
|
||||
@ -1738,30 +1894,79 @@ static gpointer run(gpointer user_data_gptr) {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (user_data->file != NULL && GB_load_rom(&gb, g_file_get_path(user_data->file)) == 0) {
|
||||
/* Run emulation */
|
||||
while (running) {
|
||||
if (paused || rewind_paused) {
|
||||
handle_events(&gb);
|
||||
}
|
||||
else {
|
||||
if (do_rewind) {
|
||||
GB_rewind_pop(&gb);
|
||||
if (turbo_down) {
|
||||
GB_rewind_pop(&gb);
|
||||
}
|
||||
if (!GB_rewind_pop(&gb)) {
|
||||
rewind_paused = true;
|
||||
}
|
||||
do_rewind = false;
|
||||
}
|
||||
GB_run(&gb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void stop(GuiData *gui_data) {
|
||||
if (!running) return;
|
||||
|
||||
SDL_PauseAudioDevice(device_id, 1);
|
||||
GB_debugger_set_disabled(&gb, true);
|
||||
|
||||
if (GB_debugger_is_stopped(&gb)) {
|
||||
// [self interruptDebugInputRead];
|
||||
}
|
||||
|
||||
stopping = true;
|
||||
running = false;
|
||||
while (stopping);
|
||||
|
||||
GB_debugger_set_disabled(&gb, false);
|
||||
gui_data->stopped = true;
|
||||
}
|
||||
|
||||
static void reset(GuiData *gui_data) {
|
||||
GB_switch_model_and_reset(&gb, gui_data->model);
|
||||
|
||||
GtkRequisition minimum_size;
|
||||
GtkRequisition natural_size;
|
||||
gtk_widget_get_preferred_size(GTK_WIDGET(main_window), &minimum_size, &natural_size);
|
||||
|
||||
// Check SGB -> non-SGB and non-SGB to SGB transitions
|
||||
if (GB_get_screen_width(&gb) != minimum_size.width || GB_get_screen_height(&gb) != minimum_size.height) {
|
||||
update_window_geometry();
|
||||
}
|
||||
|
||||
load_boot_rom(gui_data);
|
||||
|
||||
char *path = g_file_get_path(gui_data->file);
|
||||
|
||||
if (GB_load_rom(&gb, path) != 0) {
|
||||
g_print("Failed to load ROM: %s", path);
|
||||
}
|
||||
|
||||
g_free(path);
|
||||
}
|
||||
|
||||
static void start(GuiData *gui_data) {
|
||||
running = true;
|
||||
gui_data->stopped = false;
|
||||
|
||||
SDL_ClearQueuedAudio(device_id);
|
||||
SDL_PauseAudioDevice(device_id, 0);
|
||||
|
||||
/* Run emulation */
|
||||
while (running) {
|
||||
if (rewind_paused) {
|
||||
handle_events(&gb);
|
||||
}
|
||||
else {
|
||||
if (do_rewind) {
|
||||
GB_rewind_pop(&gb);
|
||||
if (turbo_down) {
|
||||
GB_rewind_pop(&gb);
|
||||
}
|
||||
if (!GB_rewind_pop(&gb)) {
|
||||
rewind_paused = true;
|
||||
}
|
||||
do_rewind = false;
|
||||
}
|
||||
GB_run(&gb);
|
||||
}
|
||||
}
|
||||
|
||||
stopping = false;
|
||||
}
|
||||
|
58
gtk3/main.h
58
gtk3/main.h
@ -16,19 +16,39 @@
|
||||
#include "settings.h"
|
||||
#include "shader.h"
|
||||
|
||||
typedef struct UserData {
|
||||
enum generic_model {
|
||||
MODEL_NONE,
|
||||
MODEL_DMG,
|
||||
MODEL_CGB,
|
||||
MODEL_AGB,
|
||||
MODEL_SGB,
|
||||
};
|
||||
|
||||
typedef struct GuiData {
|
||||
bool fullscreen;
|
||||
gint sample_rate;
|
||||
|
||||
GFile *file;
|
||||
gchar *boot_rom_path;
|
||||
gchar *config_path;
|
||||
|
||||
enum generic_model generic_model;
|
||||
GB_model_t model;
|
||||
} UserData;
|
||||
|
||||
bool stopped;
|
||||
} GuiData;
|
||||
|
||||
typedef struct{
|
||||
int16_t x, y;
|
||||
uint16_t w, h;
|
||||
} Rect;
|
||||
|
||||
typedef struct LogData {
|
||||
GB_gameboy_t *gb;
|
||||
const char *string;
|
||||
GB_log_attributes attributes;
|
||||
} LogData;
|
||||
|
||||
#define JOYSTICK_HIGH 0x4000
|
||||
#define JOYSTICK_LOW 0x3800
|
||||
|
||||
@ -46,8 +66,16 @@ int main(int argc, char *argv[]);
|
||||
static gint handle_local_options(GApplication *app, GVariantDict *options, gpointer user_data_gptr);
|
||||
|
||||
// GtkGlArea crash workaround
|
||||
void gl_check_realize(GtkWidget *w, gpointer user_data_gptr);
|
||||
gboolean test_gl_support(void);
|
||||
void gl_check_realize(GtkWidget *w, gpointer user_data_gptr);
|
||||
|
||||
static gboolean init_controllers();
|
||||
static gboolean init_audio();
|
||||
static void gb_audio_callback(GB_gameboy_t *gb, GB_sample_t *sample);
|
||||
static char *sync_console_input(GB_gameboy_t *gb);
|
||||
static char *async_console_input(GB_gameboy_t *gb);
|
||||
static void on_console_log(gpointer user_data_gptr);
|
||||
static void console_log(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes);
|
||||
|
||||
static GMenuModel *get_menu_model(GApplication *app, const char *id);
|
||||
static void create_fallback_canvas(void);
|
||||
@ -62,6 +90,8 @@ static uint32_t *get_current_buffer(void);
|
||||
static uint32_t *get_previous_buffer(void);
|
||||
static void flip(void);
|
||||
|
||||
static void quit_interrupt(int ignored);
|
||||
|
||||
// GApplication signals
|
||||
static void startup(GApplication *app, gpointer user_data_gptr);
|
||||
static void activate(GApplication *app, gpointer user_data_gptr);
|
||||
@ -69,6 +99,7 @@ static void shutdown(GApplication *app, GFile **files, gint n_files, const gchar
|
||||
static void open(GApplication *app, GFile **files, gint n_files, const gchar *hint, gpointer user_data_gptr);
|
||||
static void quit(GApplication *app);
|
||||
static gboolean on_key_press(GtkWidget *w, GdkEventKey *event, gpointer data);
|
||||
static void on_window_state_change(GtkWidget *w, GdkEventWindowState *event, gpointer data);
|
||||
|
||||
// App actions
|
||||
static void activate_about(GSimpleAction *action, GVariant *parameter, gpointer app);
|
||||
@ -79,6 +110,13 @@ static void activate_open_vram_viewer(GSimpleAction *action, GVariant *parameter
|
||||
static void activate_open(GSimpleAction *action, GVariant *parameter, gpointer app);
|
||||
static void activate_preferences(GSimpleAction *action, GVariant *parameter, gpointer app);
|
||||
static void activate_quit(GSimpleAction *action, GVariant *parameter, gpointer app);
|
||||
static void activate_reset(GSimpleAction *action, GVariant *parameter, gpointer app);
|
||||
|
||||
static void on_model_changed(GSimpleAction *action, GVariant *value, gpointer user_data);
|
||||
static void on_mute_changed(GSimpleAction *action, GVariant *value, gpointer user_data);
|
||||
static void on_pause_changed(GSimpleAction *action, GVariant *value, gpointer user_data);
|
||||
|
||||
static void on_quit(GtkWidget *w, gpointer app);
|
||||
|
||||
// Signal callback
|
||||
static void on_quit(GtkWidget *w, gpointer app);
|
||||
@ -93,8 +131,8 @@ static void resize();
|
||||
// VRAM viewer bindings
|
||||
static void on_vram_viewer_realize();
|
||||
static void on_vram_viewer_unrealize();
|
||||
static gboolean on_draw_vram_viewer_tilemap(GtkWidget *widget, cairo_t *cr, gpointer data);
|
||||
static gboolean on_draw_vram_viewer_tileset(GtkWidget *widget, cairo_t *cr, gpointer data);
|
||||
static gboolean on_draw_vram_viewer_tilemap(GtkWidget *widget, cairo_t *cr, gpointer data);
|
||||
static gboolean on_motion_vram_viewer_tileset(GtkWidget *widget, GdkEventMotion *event);
|
||||
static gboolean on_motion_vram_viewer_tilemap(GtkWidget *widget, GdkEventMotion *event);
|
||||
static void on_vram_tab_change(GtkWidget *widget, GParamSpec *pspec, GtkStackSwitcher *self);
|
||||
@ -111,16 +149,26 @@ G_MODULE_EXPORT void on_keep_aspect_ratio_changed(GtkWidget *w, gpointer user_da
|
||||
G_MODULE_EXPORT void on_rewind_duration_changed(GtkWidget *w, gpointer user_data_gptr);
|
||||
G_MODULE_EXPORT void on_sgb_model_changed(GtkWidget *w, gpointer user_data_gptr);
|
||||
G_MODULE_EXPORT void on_use_integer_scaling_changed(GtkWidget *w, gpointer user_data_gptr);
|
||||
G_MODULE_EXPORT void console_on_enter(GtkWidget *w, gpointer user_data_gptr);
|
||||
|
||||
static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b);
|
||||
static void render_texture(void *pixels, void *previous);
|
||||
static void update_viewport(void);
|
||||
static void update_window_geometry();
|
||||
|
||||
static void run(GuiData *gui_data);
|
||||
static gpointer run_thread(gpointer user_data_gptr);
|
||||
static void init(GuiData *gui_data);
|
||||
static void load_boot_rom(GuiData *gui_data);
|
||||
|
||||
static void handle_events(GB_gameboy_t *gb);
|
||||
static uint32_t convert_color(uint16_t color);
|
||||
static void palette_color_data_func(GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data);
|
||||
static void on_vblank(gpointer data);
|
||||
static void vblank(GB_gameboy_t *gb);
|
||||
static gpointer run(gpointer user_data_gptr);
|
||||
|
||||
static void reset(GuiData *gui_data);
|
||||
static void stop(GuiData *gui_data);
|
||||
static void start(GuiData *gui_data);
|
||||
|
||||
#endif /* main_h */
|
||||
|
@ -288,22 +288,22 @@ Author: Maximilian Mader
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Game Boy</attribute>
|
||||
<attribute name="action">app.change_model</attribute>
|
||||
<attribute name="model">DMG</attribute>
|
||||
<attribute name="target">DMG</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Super Game Boy</attribute>
|
||||
<attribute name="action">app.change_model</attribute>
|
||||
<attribute name="model">SGB</attribute>
|
||||
<attribute name="target">SGB</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Game Boy Color</attribute>
|
||||
<attribute name="action">app.change_model</attribute>
|
||||
<attribute name="model">CGB</attribute>
|
||||
<attribute name="target">CGB</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Game Boy Advance</attribute>
|
||||
<attribute name="action">app.change_model</attribute>
|
||||
<attribute name="model">AGB</attribute>
|
||||
<attribute name="target">AGB</attribute>
|
||||
</item>
|
||||
</section>
|
||||
|
||||
|
@ -195,9 +195,10 @@ Maximilian Mader https://github.com/max-m</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkTextView">
|
||||
<object class="GtkTextView" id="console_sidebar_input">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="wrap_mode">word</property>
|
||||
<property name="left_margin">5</property>
|
||||
<property name="right_margin">5</property>
|
||||
<property name="top_margin">5</property>
|
||||
@ -235,10 +236,11 @@ Maximilian Mader https://github.com/max-m</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkTextView">
|
||||
<object class="GtkTextView" id="console_sidebar_output">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="editable">False</property>
|
||||
<property name="wrap_mode">word</property>
|
||||
<property name="left_margin">5</property>
|
||||
<property name="right_margin">5</property>
|
||||
<property name="top_margin">5</property>
|
||||
@ -653,23 +655,6 @@ Maximilian Mader https://github.com/max-m</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="aspect_ratio_toggle">
|
||||
<property name="label" translatable="yes">Keep Aspect Ratio</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">10</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<signal name="toggled" handler="on_keep_aspect_ratio_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="integer_scaling_toggle">
|
||||
<property name="label" translatable="yes">Use Integer Scaling</property>
|
||||
@ -687,6 +672,23 @@ Maximilian Mader https://github.com/max-m</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="aspect_ratio_toggle">
|
||||
<property name="label" translatable="yes">Keep Aspect Ratio</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">10</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<signal name="toggled" handler="on_keep_aspect_ratio_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="menubar_override_selector_label">
|
||||
<property name="can_focus">False</property>
|
||||
@ -797,6 +799,37 @@ Maximilian Mader https://github.com/max-m</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Sample Rate:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkComboBoxText" id="sample_rate_selector">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<items>
|
||||
<item id="-1" translatable="yes">Default</item>
|
||||
<item translatable="yes"><separator></item>
|
||||
<item id="44100" translatable="yes">44.1 kHz</item>
|
||||
<item id="48000" translatable="yes">48 kHz</item>
|
||||
<item id="96000" translatable="yes">96 kHz</item>
|
||||
</items>
|
||||
<signal name="changed" handler="on_sample_rate_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">2</property>
|
||||
|
@ -107,6 +107,7 @@ void on_preferences_realize(GtkWidget *w, gpointer builder_ptr) {
|
||||
gtk_toggle_button_set_active(builder_get(GTK_TOGGLE_BUTTON, "integer_scaling_toggle"), config.use_integer_scaling);
|
||||
gtk_toggle_button_set_active(builder_get(GTK_TOGGLE_BUTTON, "aspect_ratio_toggle"), config.keep_aspect_ratio);
|
||||
gtk_combo_box_set_active_id(builder_get(GTK_COMBO_BOX, "highpass_filter_selector"), config.high_pass_filter_id);
|
||||
gtk_combo_box_set_active_id(builder_get(GTK_COMBO_BOX, "sample_rate_selector"), itoa(config.sample_rate));
|
||||
|
||||
#if ! NDEBUG
|
||||
gtk_combo_box_set_active_id(builder_get(GTK_COMBO_BOX, "menubar_override_selector"), config.menubar_override);
|
||||
|
@ -45,6 +45,7 @@
|
||||
) \
|
||||
EXPAND_GROUP(Audio, \
|
||||
EXPAND_GROUP_MEMBER(high_pass_filter_id, string, "emulate_hardware") \
|
||||
EXPAND_GROUP_MEMBER(sample_rate, integer, -1) \
|
||||
) \
|
||||
EXPAND_GROUP(Controls, \
|
||||
\
|
||||
|
@ -8,6 +8,10 @@ gl_Position = aPosition;\n\
|
||||
}\n\
|
||||
";
|
||||
|
||||
static GBytes *master_shader_f = NULL;
|
||||
static const gchar *master_shader_code;
|
||||
static gsize master_shader_code_size = 0;
|
||||
|
||||
static GLuint create_shader(const char *source, GLenum type)
|
||||
{
|
||||
// Create the shader object
|
||||
@ -66,9 +70,6 @@ bool init_shader_with_name(shader_t *shader, const char *name)
|
||||
}
|
||||
|
||||
GError *error = NULL;
|
||||
GBytes *master_shader_f = NULL;
|
||||
static const gchar *master_shader_code;
|
||||
static gsize master_shader_code_size;
|
||||
static char final_shader_code[0x10801] = {0,};
|
||||
static signed long filter_token_location = 0;
|
||||
|
||||
@ -108,7 +109,7 @@ bool init_shader_with_name(shader_t *shader, const char *name)
|
||||
strcpy(final_shader_code + filter_token_location, shader_code);
|
||||
strcat(final_shader_code + filter_token_location,
|
||||
master_shader_code + filter_token_location + sizeof("{filter}") - 1);
|
||||
|
||||
|
||||
g_bytes_unref(shader_f);
|
||||
|
||||
shader->program = create_program(vertex_shader, final_shader_code);
|
||||
@ -191,11 +192,7 @@ void render_bitmap_with_shader(shader_t *shader, void *bitmap, void *previous,
|
||||
|
||||
void free_shader(shader_t *shader)
|
||||
{
|
||||
GLint major = 0, minor = 0;
|
||||
glGetIntegerv(GL_MAJOR_VERSION, &major);
|
||||
glGetIntegerv(GL_MINOR_VERSION, &minor);
|
||||
|
||||
if (major * 0x100 + minor < 0x302) {
|
||||
if (epoxy_gl_version() < 32) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -203,3 +200,8 @@ void free_shader(shader_t *shader)
|
||||
glDeleteTextures(1, &shader->texture);
|
||||
glDeleteTextures(1, &shader->previous_texture);
|
||||
}
|
||||
|
||||
void free_master_shader(void) {
|
||||
g_bytes_unref(master_shader_f);
|
||||
master_shader_code_size = 0;
|
||||
}
|
||||
|
@ -26,5 +26,6 @@ void render_bitmap_with_shader(shader_t *shader, void *bitmap, void *previous,
|
||||
unsigned source_width, unsigned source_height,
|
||||
unsigned x, unsigned y, unsigned w, unsigned h);
|
||||
void free_shader(struct shader_s *shader);
|
||||
void free_master_shader(void);
|
||||
|
||||
#endif /* shader_h */
|
||||
|
Loading…
Reference in New Issue
Block a user