583 lines
20 KiB
C
583 lines
20 KiB
C
#include "main_window.h"
|
||
#include <Core/gb.h>
|
||
|
||
#include "../../SDL/audio/audio.h"
|
||
#include "../config.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;
|
||
MainMenu *main_menu;
|
||
|
||
bool force_software_renderer;
|
||
|
||
// The local SameBoy core instance
|
||
GB_gameboy_t *gb;
|
||
unsigned sample_rate;
|
||
bool running; // Indicates that the emulation thread is running
|
||
bool stopping; // Indicates that the emulation thread is about to be stopped
|
||
|
||
GFile *file;
|
||
char *battery_save_path;
|
||
char *cheats_save_path;
|
||
|
||
// Fast forward / slow motion
|
||
bool underclock_down;
|
||
bool rewind_down;
|
||
bool do_rewind;
|
||
bool rewind_paused;
|
||
bool turbo_down;
|
||
double clock_mutliplier;
|
||
double analog_clock_multiplier;
|
||
bool analog_clock_multiplier_valid;
|
||
|
||
// Local sub-windows
|
||
ConsoleWindow *console;
|
||
GtkWindow *memory_viewer; // TODO
|
||
PrinterWindow *printer;
|
||
VramViewerWindow *vram_viewer;
|
||
};
|
||
|
||
G_DEFINE_TYPE(MainWindow, main_window, GTK_TYPE_APPLICATION_WINDOW);
|
||
|
||
typedef enum {
|
||
PROP_FORCE_SOFTWARE_RENDERER = 1,
|
||
|
||
N_PROPERTIES
|
||
} MainWindowProperty;
|
||
|
||
static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };
|
||
|
||
#define perform_atomic(self, block) { \
|
||
while (!GB_is_inited(self->gb)); \
|
||
bool was_running = self->running && !GB_debugger_is_stopped(self->gb); \
|
||
if (was_running) { main_window_stop(self); } \
|
||
block; \
|
||
if (was_running) { main_window_start(self); } \
|
||
}
|
||
|
||
static void main_window_set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) {
|
||
MainWindow *self = (MainWindow *) object;
|
||
|
||
switch ((MainWindowProperty) property_id) {
|
||
case PROP_FORCE_SOFTWARE_RENDERER: self->force_software_renderer = g_value_get_boolean(value); break;
|
||
default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
|
||
}
|
||
}
|
||
|
||
static void main_window_get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec) {
|
||
MainWindow *self = (MainWindow *) object;
|
||
|
||
switch ((MainWindowProperty) property_id) {
|
||
case PROP_FORCE_SOFTWARE_RENDERER: g_value_set_boolean(value, self->force_software_renderer); break;
|
||
default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
|
||
}
|
||
}
|
||
|
||
static void on_update_color_correction(PreferencesWindow *pref, const GB_color_correction_mode_t mode, MainWindow *self) {
|
||
g_debug("on_update_color_correction(%d)", mode);
|
||
}
|
||
|
||
static void on_update_video_color_temperature(PreferencesWindow *pref, const gint light_temperature, MainWindow *self) {
|
||
g_debug("on_update_video_color_temperature(%d)", light_temperature);
|
||
|
||
if (GB_is_inited(self->gb)) {
|
||
// wouldn’t it be nice to use the value set in the GtkAdjustment of the slider instead of 256.0 here?
|
||
GB_set_light_temperature(self->gb, (double) light_temperature / 256.0);
|
||
}
|
||
}
|
||
|
||
static void on_update_monochrome_palette(PreferencesWindow *pref, const GB_palette_t *palette, MainWindow *self) {
|
||
g_debug(
|
||
"on_update_monochrome_palette(\n\trgb(%d, %d, %d),\n\trgb(%d, %d, %d),\n\trgb(%d, %d, %d),\n\trgb(%d, %d, %d),\n\trgb(%d, %d, %d)\n)",
|
||
palette->colors[0].r, palette->colors[0].g, palette->colors[0].b,
|
||
palette->colors[1].r, palette->colors[1].g, palette->colors[1].b,
|
||
palette->colors[2].r, palette->colors[2].g, palette->colors[2].b,
|
||
palette->colors[3].r, palette->colors[3].g, palette->colors[3].b,
|
||
palette->colors[4].r, palette->colors[4].g, palette->colors[4].b
|
||
);
|
||
}
|
||
|
||
static void on_update_highpass(PreferencesWindow *pref, const GB_highpass_mode_t mode, MainWindow *self) {
|
||
g_debug("on_update_highpass(%d)", mode);
|
||
}
|
||
|
||
static void on_update_rewind_duration(PreferencesWindow *pref, const guint rewind_duration, MainWindow *self) {
|
||
g_debug("on_update_rewind_duration(%d)", rewind_duration);
|
||
}
|
||
|
||
static void on_update_rumble_mode(PreferencesWindow *pref, const GB_rumble_mode_t mode, MainWindow *self) {
|
||
g_debug("on_update_rumble_mode(%d)", mode);
|
||
}
|
||
|
||
static void on_update_video_display_border_mode(PreferencesWindow *pref, const gchar *name, MainWindow *self) {
|
||
g_debug("on_update_video_display_border_mode(%s)", name);
|
||
// self->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) {
|
||
self->sample_rate = GB_audio_default_sample_rate();
|
||
}
|
||
else {
|
||
self->sample_rate = sample_rate;
|
||
}
|
||
|
||
// init_audio();
|
||
}
|
||
|
||
static void on_update_audio_interference_volume(PreferencesWindow *pref, const guint *interference_volume, MainWindow *self) {
|
||
g_debug("on_update_audio_interference_volume(%d)", *interference_volume);
|
||
|
||
if (GB_is_inited(self->gb)) {
|
||
// wouldn’t it be nice to use the value set in the GtkAdjustment of the slider instead of 100.0 here?
|
||
GB_set_interference_volume(self->gb, (double) *interference_volume / 100.0);
|
||
}
|
||
}
|
||
|
||
static bool on_change_model(GtkWidget *parent, GtkWidget *widget, gpointer user_data) {
|
||
MainWindow *self = SAMEBOY_MAIN_WINDOW(parent);
|
||
g_debug("on_change_model");
|
||
return false;
|
||
}
|
||
|
||
struct PrinterCallbackData {
|
||
PrinterWindow *printer_window;
|
||
uint32_t *image;
|
||
uint8_t height;
|
||
uint8_t top_margin;
|
||
uint8_t bottom_margin;
|
||
uint8_t exposure;
|
||
};
|
||
|
||
static gboolean draw_printer_image(struct PrinterCallbackData *data) {
|
||
printer_window_update(data->printer_window, data->image, data->height, data->top_margin, data->bottom_margin, data->exposure);
|
||
|
||
g_free(data->image);
|
||
g_slice_free(struct PrinterCallbackData, data);
|
||
return false;
|
||
}
|
||
|
||
static void emu_thread_on_print_image(GB_gameboy_t *gb, uint32_t *image, uint8_t height, uint8_t top_margin, uint8_t bottom_margin, uint8_t exposure) {
|
||
MainWindow *self = SAMEBOY_MAIN_WINDOW(GB_get_user_data(gb));
|
||
|
||
struct PrinterCallbackData *data = g_slice_alloc(sizeof(struct PrinterCallbackData));
|
||
|
||
data->printer_window = self->printer;
|
||
data->image = g_malloc0(160 * height * sizeof(image[0]));
|
||
memcpy(data->image, image, 160 * height * sizeof(image[0]));
|
||
|
||
data->height = height;
|
||
data->top_margin = top_margin;
|
||
data->bottom_margin = bottom_margin;
|
||
data->exposure = exposure;
|
||
|
||
// We must make sure to run this function on the GTK thread
|
||
g_idle_add((GSourceFunc) draw_printer_image, data);
|
||
}
|
||
|
||
static bool on_change_linked_device(GtkWidget *parent, GtkWidget *widget, gpointer user_data) {
|
||
MainWindow *self = SAMEBOY_MAIN_WINDOW(parent);
|
||
g_debug("on_change_linked_device");
|
||
|
||
GtkCheckMenuItem *check_menu_item = GTK_CHECK_MENU_ITEM(widget);
|
||
gchar *device_id = (gchar *) user_data;
|
||
|
||
if (!gtk_check_menu_item_get_active(check_menu_item)) {
|
||
return true;
|
||
}
|
||
else if (!GB_is_inited(self->gb)) {
|
||
return false;
|
||
}
|
||
|
||
perform_atomic(self, {
|
||
if (g_strcmp0(device_id, "NONE") == 0) {
|
||
g_debug("Disconnecting serial device");
|
||
GB_disconnect_serial(self->gb);
|
||
}
|
||
else if (g_strcmp0(device_id, "PRINTER") == 0) {
|
||
g_debug("Connecting printer");
|
||
GB_connect_printer(self->gb, emu_thread_on_print_image);
|
||
}
|
||
else if (g_strcmp0(device_id, "WORKBOY") == 0) {
|
||
g_debug("Connecting WorkBoy");
|
||
|
||
g_warning("Not yet implemented!")
|
||
GB_disconnect_serial(self->gb);
|
||
}
|
||
});
|
||
|
||
return false;
|
||
}
|
||
|
||
static void emu_thread_on_load_boot_rom(GB_gameboy_t *gb, GB_boot_rom_t type) {
|
||
g_debug("on_load_boot_rom(%p, %d)", gb, type);
|
||
|
||
MainWindow *self = SAMEBOY_MAIN_WINDOW(GB_get_user_data(gb));
|
||
SameBoyApplication *app = SAMEBOY_APPLICATION(gtk_window_get_application(GTK_WINDOW(self)));
|
||
struct CliOptionData *cli_options = sameboy_application_get_cli_options(app);
|
||
|
||
GError *error = NULL;
|
||
char *boot_rom_path = NULL;
|
||
GBytes *boot_rom_f = NULL;
|
||
const guchar *boot_rom_data;
|
||
gsize boot_rom_size;
|
||
|
||
static const char *const names[] = {
|
||
[GB_BOOT_ROM_DMG0] = "dmg0_boot.bin",
|
||
[GB_BOOT_ROM_DMG] = "dmg_boot.bin",
|
||
[GB_BOOT_ROM_MGB] = "mgb_boot.bin",
|
||
[GB_BOOT_ROM_SGB] = "sgb_boot.bin",
|
||
[GB_BOOT_ROM_SGB2] = "sgb2_boot.bin",
|
||
[GB_BOOT_ROM_CGB0] = "cgb0_boot.bin",
|
||
[GB_BOOT_ROM_CGB] = "cgb_boot.bin",
|
||
[GB_BOOT_ROM_AGB] = "agb_boot.bin",
|
||
};
|
||
|
||
const char *const boot_rom_name = names[type];
|
||
|
||
if (cli_options->boot_rom_path != NULL) {
|
||
g_message("[CLI override] Trying to load boot ROM from %s", cli_options->boot_rom_path);
|
||
if (GB_load_boot_rom(gb, cli_options->boot_rom_path)) {
|
||
g_warning("Falling back to boot ROM from config");
|
||
goto config_boot_rom;
|
||
}
|
||
}
|
||
else { config_boot_rom:
|
||
if (config.emulation.boot_rom_path != NULL && g_strcmp0(config.emulation.boot_rom_path, "other") != 0 && g_strcmp0(config.emulation.boot_rom_path, "auto") != 0) {
|
||
boot_rom_path = g_build_filename(config.emulation.boot_rom_path, boot_rom_name, NULL);
|
||
g_message("Trying to load boot ROM from %s", boot_rom_path);
|
||
|
||
if (GB_load_boot_rom(gb, boot_rom_path)) {
|
||
g_free(boot_rom_path);
|
||
g_warning("Falling back to internal boot ROM");
|
||
goto internal_boot_rom;
|
||
}
|
||
|
||
g_free(boot_rom_path);
|
||
}
|
||
else { internal_boot_rom:
|
||
boot_rom_path = g_build_filename(RESOURCE_PREFIX "bootroms/", boot_rom_name, NULL);
|
||
boot_rom_f = g_resources_lookup_data(boot_rom_path, G_RESOURCE_LOOKUP_FLAGS_NONE, &error);
|
||
|
||
g_message("Loading internal boot ROM: %s", boot_rom_path);
|
||
|
||
if (boot_rom_f == NULL) {
|
||
g_warning("Failed to load internal boot ROM: %s", boot_rom_path);
|
||
g_error_free(error);
|
||
// exit(EXIT_FAILURE);
|
||
}
|
||
else {
|
||
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);
|
||
}
|
||
|
||
g_free(boot_rom_path);
|
||
}
|
||
}
|
||
}
|
||
|
||
static void main_window_init_core(MainWindow *self) {
|
||
if (GB_is_inited(self->gb)) return;
|
||
|
||
SameBoyApplication *app = SAMEBOY_APPLICATION(gtk_window_get_application(GTK_WINDOW(self)));
|
||
struct CliOptionData *cli_options = sameboy_application_get_cli_options(app);
|
||
|
||
GB_init(self->gb, config_get_model_type(cli_options));
|
||
GB_set_user_data(self->gb, self);
|
||
|
||
if (config.audio.sample_rate == -1) {
|
||
self->sample_rate = GB_audio_default_sample_rate();
|
||
}
|
||
else {
|
||
self->sample_rate = config.audio.sample_rate;
|
||
}
|
||
|
||
// GB_set_vblank_callback(self->gb, emu_thread_on_vblank);
|
||
// GB_set_rgb_encode_callback(self->gb, emu_thread_rgb_encode);
|
||
|
||
GB_set_pixels_output(self->gb, main_window_get_current_buffer(self));
|
||
GB_set_color_correction_mode(self->gb, config_get_color_correction_mode());
|
||
GB_set_light_temperature(self->gb, (double) config.video.light_temperature / 256.0);
|
||
if (config_get_display_border_mode() <= GB_BORDER_ALWAYS) {
|
||
GB_set_border_mode(self->gb, config_get_display_border_mode());
|
||
}
|
||
|
||
// GB_apu_set_sample_callback(self->gb, emu_thread_audio_callback);
|
||
|
||
GB_set_sample_rate(self->gb, GB_audio_get_sample_rate());
|
||
GB_set_highpass_filter_mode(self->gb, config_get_highpass_mode());
|
||
GB_set_interference_volume(self->gb, (double) config.audio.interference_volume / 100.0);
|
||
|
||
// GB_set_log_callback(self->gb, emu_thread_console_log);
|
||
// GB_set_input_callback(self->gb, emu_thread_get_sync_input);
|
||
// GB_set_async_input_callback(self->gb, emu_thread_get_async_input);
|
||
|
||
GB_set_boot_rom_load_callback(self->gb, emu_thread_on_load_boot_rom);
|
||
// GB_set_update_input_hint_callback(self->gb, emu_thread_handle_events);
|
||
// GB_set_rumble_callback(self->gb, emu_thread_rumble_callback);
|
||
|
||
GB_set_rumble_mode(self->gb, config_get_rumble_mode());
|
||
GB_set_rewind_length(self->gb, config.emulation.rewind_duration);
|
||
}
|
||
|
||
static void on_application_set(MainWindow *self, GObject *object) {
|
||
SameBoyApplication *app = SAMEBOY_APPLICATION(gtk_window_get_application(GTK_WINDOW(self)));
|
||
g_debug("on_application_set(%p, %p) => %p", self, object, app);
|
||
|
||
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);
|
||
|
||
main_window_init_core(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) {
|
||
gtk_widget_init_template(GTK_WIDGET(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));
|
||
|
||
// Just hide our sub-windows when closing them
|
||
g_signal_connect(self->console, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
|
||
// g_signal_connect(self->memory_viewer, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
|
||
g_signal_connect(self->printer, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
|
||
g_signal_connect(self->vram_viewer, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
|
||
}
|
||
|
||
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_CLASS(class), // itype
|
||
G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE | G_SIGNAL_ACTION, // signal_flags
|
||
0, // class_offset
|
||
NULL, // accumulator
|
||
NULL, // accumulator_data
|
||
NULL, // c_marshaller,
|
||
G_TYPE_NONE, // return_type
|
||
0 // n_params
|
||
);
|
||
|
||
obj_properties[PROP_FORCE_SOFTWARE_RENDERER] = g_param_spec_boolean(
|
||
"force_software_renderer", "Software Renderer", "Forces the use of software rendering via Cairo",
|
||
false,
|
||
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE
|
||
);
|
||
|
||
G_OBJECT_CLASS(class)->set_property = main_window_set_property;
|
||
G_OBJECT_CLASS(class)->get_property = main_window_get_property;
|
||
G_OBJECT_CLASS(class)->constructed = main_window_constructed;
|
||
|
||
g_object_class_install_properties(G_OBJECT_CLASS(class), N_PROPERTIES, obj_properties);
|
||
}
|
||
|
||
MainWindow *main_window_new(SameBoyApplication *application, bool force_software_renderer) {
|
||
g_debug("main_window_new(%p, %d)", application, 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_fullscreen(GTK_WINDOW(self));
|
||
}
|
||
else {
|
||
gtk_window_unfullscreen(GTK_WINDOW(self));
|
||
}
|
||
}
|
||
|
||
void main_window_setup_menu(MainWindow *self, char *model_string) {
|
||
main_menu_setup(self->main_menu, model_string, self, on_change_model, on_change_linked_device);
|
||
}
|
||
|
||
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);
|
||
}
|
||
|
||
uint32_t *main_window_get_pixels(MainWindow *self) {
|
||
return gb_screen_get_pixels(self->screen);
|
||
}
|
||
|
||
uint32_t *main_window_get_current_buffer(MainWindow *self) {
|
||
return gb_screen_get_current_buffer(self->screen);
|
||
}
|
||
|
||
uint32_t *main_window_get_previous_buffer(MainWindow *self) {
|
||
return gb_screen_get_previous_buffer(self->screen);
|
||
}
|
||
|
||
void main_window_flip(MainWindow *self) {
|
||
return gb_screen_flip(self->screen);
|
||
}
|
||
|
||
void main_window_set_resolution(MainWindow *self, unsigned width, unsigned height) {
|
||
return gb_screen_set_resolution(self->screen, width, height);
|
||
}
|
||
|
||
void main_window_set_blending_mode(MainWindow *self, GB_frame_blending_mode_t mode) {
|
||
return gb_screen_set_blending_mode(self->screen, mode);
|
||
}
|
||
|
||
void main_window_set_shader(MainWindow *self, const char *shader_name) {
|
||
return gb_screen_set_shader(self->screen, shader_name);
|
||
}
|
||
|
||
void main_window_queue_render(MainWindow *self) {
|
||
return gb_screen_queue_render(self->screen);
|
||
}
|
||
|
||
// Core functions
|
||
static void emulation_thread(MainWindow *self) {
|
||
|
||
}
|
||
|
||
static void main_window_start_emulation_thread(MainWindow *self) {
|
||
if (self->running) return;
|
||
while (self->stopping);
|
||
|
||
g_thread_new("EmulationThread", (GThreadFunc)emulation_thread, self);
|
||
}
|
||
|
||
void main_window_start(MainWindow *self) {
|
||
g_debug("main_window_start");
|
||
self->running = true;
|
||
|
||
// TODO: Clear audio queue
|
||
|
||
while (self->running) {
|
||
if (self->rewind_paused) {
|
||
// handle_events(self->gb);
|
||
g_usleep(G_USEC_PER_SEC / 8);
|
||
}
|
||
else {
|
||
if (self->do_rewind) {
|
||
GB_rewind_pop(self->gb);
|
||
if (self->turbo_down) {
|
||
GB_rewind_pop(self->gb);
|
||
}
|
||
if (!GB_rewind_pop(self->gb)) {
|
||
self->rewind_paused = true;
|
||
}
|
||
self->do_rewind = false;
|
||
}
|
||
GB_run(self->gb);
|
||
}
|
||
}
|
||
|
||
if (self->file) {
|
||
GB_save_battery(self->gb, self->battery_save_path);
|
||
GB_save_cheats(self->gb, self->cheats_save_path);
|
||
}
|
||
|
||
self->stopping = false;
|
||
}
|
||
|
||
void main_window_stop(MainWindow *self) {
|
||
g_debug("main_window_stop");
|
||
if (self->running) return;
|
||
|
||
// GB_audio_set_paused(true);
|
||
GB_debugger_set_disabled(self->gb, true);
|
||
|
||
self->stopping = true;
|
||
self->running = false;
|
||
|
||
if (GB_debugger_is_stopped(self->gb)) {
|
||
console_window_abort_debugger(self->console);
|
||
}
|
||
|
||
while (self->stopping);
|
||
|
||
GB_debugger_set_disabled(self->gb, false);
|
||
}
|
||
|
||
void main_window_reset(MainWindow *self) {
|
||
g_debug("main_window_reset");
|
||
}
|