2019-09-21 19:56:19 +00:00
|
|
|
|
#include <gtk/gtk.h>
|
2019-09-22 01:07:14 +00:00
|
|
|
|
#include <epoxy/gl.h>
|
2019-09-21 19:56:19 +00:00
|
|
|
|
#include <signal.h>
|
|
|
|
|
#include <stdbool.h>
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <Core/gb.h>
|
2019-09-22 01:07:14 +00:00
|
|
|
|
#include "shader.h"
|
2019-09-21 19:56:19 +00:00
|
|
|
|
|
|
|
|
|
#define str(x) #x
|
|
|
|
|
#define xstr(x) str(x)
|
|
|
|
|
|
|
|
|
|
GtkBuilder *builder;
|
|
|
|
|
GtkWindow *main_window;
|
|
|
|
|
GtkGLArea *gl_area;
|
2019-09-22 01:07:14 +00:00
|
|
|
|
shader_t shader;
|
2019-09-21 19:56:19 +00:00
|
|
|
|
|
|
|
|
|
GB_gameboy_t gb;
|
2019-09-22 01:07:14 +00:00
|
|
|
|
static bool paused = false;
|
|
|
|
|
static uint32_t pixel_buffer_1[256 * 224], pixel_buffer_2[256 * 224];
|
|
|
|
|
static uint32_t *active_pixel_buffer = pixel_buffer_1, *previous_pixel_buffer = pixel_buffer_2;
|
|
|
|
|
static bool underclock_down = false, rewind_down = false, do_rewind = false, rewind_paused = false, turbo_down = false;
|
|
|
|
|
static double clock_mutliplier = 1.0;
|
|
|
|
|
|
|
|
|
|
static typeof(free) *free_function = NULL;
|
|
|
|
|
static char *battery_save_path_ptr;
|
|
|
|
|
|
2019-09-21 19:56:19 +00:00
|
|
|
|
|
|
|
|
|
typedef struct UserData {
|
|
|
|
|
bool fullscreen;
|
|
|
|
|
GFile *file;
|
|
|
|
|
} UserData;
|
|
|
|
|
|
2019-09-22 01:07:14 +00:00
|
|
|
|
typedef struct{
|
|
|
|
|
int16_t x, y;
|
|
|
|
|
uint16_t w, h;
|
|
|
|
|
} Rect;
|
|
|
|
|
|
|
|
|
|
static void run(UserData *user_data);
|
|
|
|
|
|
|
|
|
|
static Rect rect;
|
|
|
|
|
|
|
|
|
|
void render_texture(void *pixels, void *previous) {
|
|
|
|
|
static void *_pixels = NULL;
|
|
|
|
|
if (pixels) {
|
|
|
|
|
_pixels = pixels;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
glClearColor(0, 0, 0, 1);
|
|
|
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
|
|
|
|
|
|
|
|
render_bitmap_with_shader(&shader, _pixels, previous, GB_get_screen_width(&gb), GB_get_screen_height(&gb), rect.x, rect.y, rect.w, rect.h);
|
|
|
|
|
gtk_widget_queue_draw(GTK_WIDGET(gl_area));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void update_viewport(void) {
|
|
|
|
|
int win_width = gtk_widget_get_allocated_width(GTK_WIDGET(gl_area));
|
|
|
|
|
int win_height = gtk_widget_get_allocated_height(GTK_WIDGET(gl_area));
|
|
|
|
|
|
|
|
|
|
double x_factor = win_width / (double) GB_get_screen_width(&gb);
|
|
|
|
|
double y_factor = win_height / (double) GB_get_screen_height(&gb);
|
|
|
|
|
|
|
|
|
|
if (true /*configuration.scaling_mode == GB_SDL_SCALING_INTEGER_FACTOR*/) {
|
|
|
|
|
x_factor = (int)(x_factor);
|
|
|
|
|
y_factor = (int)(y_factor);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*if (configuration.scaling_mode != GB_SDL_SCALING_ENTIRE_WINDOW) {
|
|
|
|
|
if (x_factor > y_factor) {
|
|
|
|
|
x_factor = y_factor;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
y_factor = x_factor;
|
|
|
|
|
}
|
|
|
|
|
}*/
|
|
|
|
|
|
|
|
|
|
unsigned new_width = x_factor * GB_get_screen_width(&gb);
|
|
|
|
|
unsigned new_height = y_factor * GB_get_screen_height(&gb);
|
|
|
|
|
|
|
|
|
|
rect = (Rect){(win_width - new_width) / 2, (win_height - new_height) / 2, new_width, new_height};
|
|
|
|
|
|
|
|
|
|
glViewport(rect.x, rect.y, rect.w, rect.h);
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-21 19:56:19 +00:00
|
|
|
|
// Determines if a ComboBox entry should be converted into a separator.
|
|
|
|
|
// Each element with a text value of `<separator>` will be converted into a separator element.
|
|
|
|
|
static gboolean is_separator(GtkTreeModel *model, GtkTreeIter *iter, gpointer data) {
|
|
|
|
|
gchar *text = NULL;
|
|
|
|
|
|
|
|
|
|
gtk_tree_model_get(model, iter, 0, &text, -1);
|
|
|
|
|
gboolean result = g_strcmp0("<separator>", text) == 0;
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (GTK_IS_CONTAINER(list->data)) {
|
|
|
|
|
set_combo_box_row_separator_func(GTK_CONTAINER(list->data));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
list = list->next;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_list_free_full(list, NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Returns true if the application should show a menubar
|
|
|
|
|
static gboolean show_menubar(void) {
|
|
|
|
|
GtkSettings *settings = gtk_settings_get_default();
|
|
|
|
|
gboolean result;
|
|
|
|
|
|
|
|
|
|
g_object_get(settings, "gtk-shell-shows-menubar", &result, NULL);
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Returns a GObject by ID from our GtkBuilder instance
|
|
|
|
|
static GObject *get_object(gchararray id) {
|
|
|
|
|
return gtk_builder_get_object(builder, id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Returns a `GApplication`s `GMenuModel` by ID
|
|
|
|
|
// GApplication menus are loaded from `gtk/menus.ui`, `gtk/menus-traditional.ui` and `gtk/menus-common.ui`.
|
|
|
|
|
static GMenuModel *get_menu_model(GApplication *app, const char *id) {
|
|
|
|
|
GMenu *menu;
|
|
|
|
|
|
|
|
|
|
menu = gtk_application_get_menu_by_id(GTK_APPLICATION(app), id);
|
|
|
|
|
|
|
|
|
|
return menu ? G_MENU_MODEL(g_object_ref_sink(menu)) : NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// app.quit GAction
|
|
|
|
|
// Exits the application
|
|
|
|
|
static void activate_quit(GSimpleAction *action, GVariant *parameter, gpointer user_data) {
|
|
|
|
|
g_application_quit(G_APPLICATION(user_data));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// app.about GAction
|
|
|
|
|
// Opens the about dialog
|
|
|
|
|
static void activate_about(GSimpleAction *action, GVariant *parameter, gpointer user_data) {
|
|
|
|
|
GObject *dialog = get_object("about_dialog");
|
|
|
|
|
gtk_dialog_run(GTK_DIALOG(dialog));
|
|
|
|
|
gtk_widget_hide(GTK_WIDGET(dialog));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// List of GActions for the `app` prefix
|
|
|
|
|
static GActionEntry app_entries[] = {
|
|
|
|
|
{ "quit", activate_quit, NULL, NULL, NULL },
|
|
|
|
|
{ "about", activate_about, NULL, NULL, NULL },
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
G_MODULE_EXPORT void on_show_window(GtkWidget *w, gpointer window) {
|
|
|
|
|
gtk_widget_show_all(GTK_WIDGET(window));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
G_MODULE_EXPORT void on_boot_rom_location_changed(GtkWidget *w, gpointer user_data_gptr) {
|
|
|
|
|
GtkComboBox *box = GTK_COMBO_BOX(w);
|
|
|
|
|
|
|
|
|
|
g_print("Active: %s", gtk_combo_box_get_active_id(box));
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-22 01:07:14 +00:00
|
|
|
|
G_MODULE_EXPORT void gl_draw(GtkWidget *window) {
|
|
|
|
|
render_texture(active_pixel_buffer, NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
G_MODULE_EXPORT void gl_init(GtkWidget *window) {
|
|
|
|
|
const char *renderer;
|
|
|
|
|
|
|
|
|
|
GList *children = gtk_container_get_children(GTK_CONTAINER(window));
|
|
|
|
|
|
|
|
|
|
while (children) {
|
|
|
|
|
if (GTK_IS_GL_AREA(children->data)) {
|
|
|
|
|
gl_area = GTK_GL_AREA(children->data);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
children = children->next;
|
|
|
|
|
}
|
|
|
|
|
g_list_free(children);
|
|
|
|
|
|
|
|
|
|
if (gl_area == NULL) {
|
|
|
|
|
g_printerr("Unable to find GtkGLArea in window\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gtk_gl_area_make_current(gl_area);
|
|
|
|
|
if (gtk_gl_area_get_error(gl_area) != NULL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
renderer = (char *) glGetString(GL_RENDERER);
|
|
|
|
|
g_print("GtkGLArea on %s\n", renderer ? renderer : "Unknown");
|
|
|
|
|
|
|
|
|
|
if (!init_shader_with_name(&shader, /*configuration.filter*/ "OmniScale")) {
|
|
|
|
|
init_shader_with_name(&shader, "NearestNeighbor");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-21 19:56:19 +00:00
|
|
|
|
// 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;
|
|
|
|
|
|
|
|
|
|
builder = gtk_builder_new_from_resource(RESOURCE_PREFIX "ui/window.ui");
|
|
|
|
|
gtk_builder_connect_signals(builder, NULL);
|
|
|
|
|
|
|
|
|
|
g_action_map_add_action_entries(G_ACTION_MAP(app), app_entries, G_N_ELEMENTS(app_entries), app);
|
|
|
|
|
|
|
|
|
|
GtkWindow *preferences = GTK_WINDOW(get_object("preferences"));
|
|
|
|
|
set_combo_box_row_separator_func(GTK_CONTAINER(preferences));
|
|
|
|
|
|
|
|
|
|
GtkWindow *vram_viewer = GTK_WINDOW(get_object("vram_viewer"));
|
|
|
|
|
set_combo_box_row_separator_func(GTK_CONTAINER(vram_viewer));
|
|
|
|
|
|
|
|
|
|
// Handle the whole menubar situation …
|
|
|
|
|
if (show_menubar()) {
|
|
|
|
|
// Use a standard window as main window
|
|
|
|
|
main_window = GTK_WINDOW(get_object("main_no_titlebar"));
|
|
|
|
|
gtk_widget_destroy(GTK_WIDGET(get_object("main_with_titlebar")));
|
|
|
|
|
|
|
|
|
|
// Hide hamburger button
|
|
|
|
|
GtkMenuButton *hamburger_button = GTK_MENU_BUTTON(get_object("hamburger_button"));
|
|
|
|
|
gtk_widget_hide(GTK_WIDGET(hamburger_button));
|
|
|
|
|
gtk_widget_set_no_show_all(GTK_WIDGET(hamburger_button), TRUE);
|
|
|
|
|
|
|
|
|
|
GMenuModel *menubar = get_menu_model(app, "menubar");
|
|
|
|
|
gtk_application_set_menubar(GTK_APPLICATION(app), menubar);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// Use a window with a custom title bar
|
|
|
|
|
main_window = GTK_WINDOW(get_object("main_with_titlebar"));
|
|
|
|
|
gtk_widget_destroy(GTK_WIDGET(get_object("main_no_titlebar")));
|
|
|
|
|
|
|
|
|
|
// Disable menubar
|
|
|
|
|
gtk_application_set_menubar(GTK_APPLICATION(app), NULL);
|
|
|
|
|
|
|
|
|
|
// Hook menubar up to the hamburger button
|
|
|
|
|
GtkMenuButton *hamburger_button = GTK_MENU_BUTTON(get_object("hamburger_button"));
|
|
|
|
|
GMenuModel *hamburger_menu = get_menu_model(app, "menubar");
|
|
|
|
|
gtk_menu_button_set_menu_model(hamburger_button, hamburger_menu);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gtk_window_set_title(main_window, "SameBoy v" xstr(VERSION));
|
|
|
|
|
|
|
|
|
|
// Define a set of window icons
|
|
|
|
|
GList *icon_list = NULL;
|
|
|
|
|
static char* icons[] = {
|
|
|
|
|
RESOURCE_PREFIX "logo_256.png",
|
|
|
|
|
RESOURCE_PREFIX "logo_128.png",
|
|
|
|
|
RESOURCE_PREFIX "logo_64.png",
|
|
|
|
|
RESOURCE_PREFIX "logo_48.png",
|
|
|
|
|
RESOURCE_PREFIX "logo_32.png",
|
|
|
|
|
RESOURCE_PREFIX "logo_16.png"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Create list of GdkPixbufs
|
|
|
|
|
for (int i = 0; i < (sizeof(icons) / sizeof(const char*)); ++i) {
|
|
|
|
|
GdkPixbuf *icon = gdk_pixbuf_new_from_resource(icons[i], NULL);
|
|
|
|
|
if (!icon) continue;
|
|
|
|
|
|
|
|
|
|
icon_list = g_list_prepend(icon_list, icon);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Let GTK choose the proper icon
|
|
|
|
|
gtk_window_set_icon_list(main_window, icon_list);
|
|
|
|
|
|
|
|
|
|
// Add missing information to the about dialog
|
|
|
|
|
GtkAboutDialog *about_dialog = GTK_ABOUT_DIALOG(get_object("about_dialog"));
|
|
|
|
|
gtk_about_dialog_set_logo(about_dialog, g_list_nth_data(icon_list, 3)); // reuse the 64x64 icon
|
|
|
|
|
gtk_about_dialog_set_version(about_dialog, "v" xstr(VERSION));
|
|
|
|
|
g_list_free(icon_list);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
|
|
|
|
if (user_data->fullscreen) {
|
|
|
|
|
gtk_window_fullscreen(main_window);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_signal_connect(main_window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
|
|
|
|
|
gtk_application_add_window(GTK_APPLICATION(app), main_window);
|
|
|
|
|
gtk_widget_show_all(GTK_WIDGET(main_window));
|
2019-09-22 01:07:14 +00:00
|
|
|
|
|
|
|
|
|
update_viewport();
|
|
|
|
|
run(user_data);
|
2019-09-21 19:56:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
|
|
|
|
if (n_files > 1) {
|
|
|
|
|
g_printerr("More than one file specified\n");
|
|
|
|
|
g_application_quit(app);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
user_data->file = files[0];
|
|
|
|
|
|
|
|
|
|
// We have handled the files, now activate the application
|
|
|
|
|
activate(app, user_data_gptr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
guint32 count;
|
|
|
|
|
|
|
|
|
|
if (g_variant_dict_lookup(options, "version", "b", &count)) {
|
|
|
|
|
g_print("SameBoy v" xstr(VERSION) "\n");
|
|
|
|
|
return EXIT_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (g_variant_dict_lookup(options, "fullscreen", "b", &count)) {
|
|
|
|
|
user_data->fullscreen = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-22 01:07:14 +00:00
|
|
|
|
static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) {
|
|
|
|
|
uint32_t color = 0xFF000000 | (b << 16) | (g << 8) | r; // abgr
|
|
|
|
|
return color;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void vblank(GB_gameboy_t *gb) {
|
|
|
|
|
// render_texture(active_pixel_buffer, NULL);
|
|
|
|
|
|
|
|
|
|
while (gtk_events_pending()) {
|
|
|
|
|
gtk_main_iteration();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void run(UserData *user_data) {
|
|
|
|
|
GB_model_t model = GB_MODEL_CGB_E;
|
|
|
|
|
|
|
|
|
|
if (GB_is_inited(&gb)) {
|
|
|
|
|
GB_switch_model_and_reset(&gb, model);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
GB_init(&gb, model);
|
|
|
|
|
|
|
|
|
|
GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank);
|
|
|
|
|
GB_set_pixels_output(&gb, active_pixel_buffer);
|
|
|
|
|
GB_set_rgb_encode_callback(&gb, rgb_encode);
|
|
|
|
|
// GB_set_sample_rate(&gb, have_aspec.freq);
|
|
|
|
|
// GB_set_color_correction_mode(&gb, configuration.color_correction_mode);
|
|
|
|
|
// GB_set_highpass_filter_mode(&gb, configuration.highpass_mode);
|
|
|
|
|
// GB_set_rewind_length(&gb, configuration.rewind_length);
|
|
|
|
|
// GB_set_update_input_hint_callback(&gb, handle_events);
|
|
|
|
|
// GB_apu_set_sample_callback(&gb, gb_audio_callback);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GError *gerror;
|
|
|
|
|
GBytes *boot_rom_f;
|
|
|
|
|
const guchar *boot_rom_data;
|
|
|
|
|
gsize boot_rom_size;
|
|
|
|
|
|
|
|
|
|
boot_rom_f = g_resources_lookup_data(RESOURCE_PREFIX "bootroms/cgb_boot.bin", G_RESOURCE_LOOKUP_FLAGS_NONE, &gerror);
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
if (user_data->file != NULL) {
|
|
|
|
|
GB_load_rom(&gb, g_file_get_path(user_data->file));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Run emulation */
|
|
|
|
|
while (true) {
|
|
|
|
|
if (paused || rewind_paused) {
|
|
|
|
|
while (gtk_events_pending()) {
|
|
|
|
|
gtk_main_iteration();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int main(int argc, char *argv[]) {
|
2019-09-21 19:56:19 +00:00
|
|
|
|
// Create our GApplication and tell GTK that we are able to handle files
|
|
|
|
|
GtkApplication *app = gtk_application_new(APP_ID, G_APPLICATION_HANDLES_OPEN);
|
|
|
|
|
|
|
|
|
|
UserData user_data = { NULL };
|
|
|
|
|
|
|
|
|
|
// 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, NULL, "Start in fullscreen mode", 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.");
|
|
|
|
|
|
|
|
|
|
// Add signal handlers
|
|
|
|
|
g_signal_connect(app, "handle-local-options", G_CALLBACK(handle_local_options), &user_data);
|
|
|
|
|
g_signal_connect(app, "startup", G_CALLBACK(startup), &user_data);
|
|
|
|
|
g_signal_connect(app, "activate", G_CALLBACK(activate), &user_data);
|
|
|
|
|
g_signal_connect(app, "open", G_CALLBACK(open), &user_data);
|
|
|
|
|
|
|
|
|
|
// Start our GApplication main loop
|
|
|
|
|
int status = g_application_run(G_APPLICATION(app), argc, argv);
|
|
|
|
|
g_object_unref(app);
|
|
|
|
|
|
|
|
|
|
return status;
|
|
|
|
|
}
|