2020-04-11 16:24:55 +00:00
# include <gtk/gtk.h>
2020-04-10 01:22:16 +00:00
# include <epoxy/gl.h>
2020-04-11 16:24:55 +00:00
# include <signal.h>
# include <stdbool.h>
# include <stdio.h>
# include <string.h>
# include <Core/gb.h>
2019-09-24 14:14:06 +00:00
2019-09-27 21:10:28 +00:00
# include "macros.h"
2019-09-24 14:14:06 +00:00
# include "settings.h"
2020-04-10 01:22:16 +00:00
# include "shader.h"
2020-04-11 16:24:55 +00:00
2019-09-22 22:47:42 +00:00
typedef struct UserData {
bool fullscreen ;
GFile * file ;
2019-09-26 18:31:58 +00:00
gchar * boot_rom_path ;
2019-09-25 00:48:07 +00:00
gchar * config_path ;
2019-09-23 22:34:21 +00:00
GB_model_t model ;
2019-09-22 22:47:42 +00:00
} UserData ;
typedef struct {
int16_t x , y ;
uint16_t w , h ;
} Rect ;
2019-09-28 21:34:18 +00:00
# define BUTTON_MASK_A 0x01
# define BUTTON_MASK_B 0x02
# define BUTTON_MASK_START 0x04
# define BUTTON_MASK_SELECT 0x08
# define BUTTON_MASK_UP 0x10
# define BUTTON_MASK_DOWN 0x20
# define BUTTON_MASK_LEFT 0x40
# define BUTTON_MASK_RIGHT 0x80
static uint8_t pressed_buttons ;
2019-09-26 18:31:58 +00:00
static void run ( GApplication * app , UserData * user_data ) ;
2019-09-22 22:47:42 +00:00
2019-09-23 22:34:21 +00:00
static GtkApplication * main_application ;
static GtkBuilder * builder ;
static GtkGLArea * gl_area ;
2019-09-29 23:49:41 +00:00
static GtkDrawingArea * fallback_canvas ;
2019-09-25 20:47:33 +00:00
static GtkApplicationWindow * main_window ;
2019-09-29 23:49:41 +00:00
static GtkBox * main_window_container ;
2019-09-27 22:15:37 +00:00
static GtkWindow * preferences ;
2019-09-25 20:47:33 +00:00
static GtkWindow * vram_viewer ;
static GtkWindow * memory_viewer ;
static GtkWindow * console ;
static GtkWindow * printer ;
2019-09-23 22:34:21 +00:00
static shader_t shader ;
2020-04-11 16:24:55 +00:00
2019-09-28 21:34:18 +00:00
static UserData user_data = { NULL } ;
2019-09-23 22:34:21 +00:00
static GB_gameboy_t gb ;
2019-09-22 22:47:42 +00:00
static uint32_t * image_buffers [ 3 ] ;
static unsigned char current_buffer ;
2019-09-29 23:49:41 +00:00
static bool supports_gl ;
2020-04-10 01:22:16 +00:00
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 ;
2019-09-22 22:47:42 +00:00
static Rect rect ;
2019-09-25 20:47:33 +00:00
static bool vram_viewer_visible = false ;
2019-09-23 14:35:10 +00:00
static bool running = true ;
2019-09-25 20:47:33 +00:00
static const size_t tileset_buffer_length = 256 * 192 * 4 ;
static uint32_t tileset_buffer [ tileset_buffer_length ] = { 0 } ;
static const size_t tilemap_buffer_length = 256 * 256 * 4 ;
static uint32_t tilemap_buffer [ tilemap_buffer_length ] = { 0 } ;
2019-09-23 22:34:21 +00:00
static unsigned char number_of_buffers ( void ) {
2019-09-29 23:49:41 +00:00
bool should_blend = ! fallback_canvas ;
2020-04-11 16:24:55 +00:00
2019-09-22 22:47:42 +00:00
return should_blend ? 3 : 2 ;
}
2020-04-11 16:24:55 +00:00
2019-09-23 22:34:21 +00:00
static void flip ( void ) {
2019-09-22 22:47:42 +00:00
current_buffer = ( current_buffer + 1 ) % number_of_buffers ( ) ;
}
2020-04-10 01:22:16 +00:00
2019-09-23 22:34:21 +00:00
static uint32_t * get_pixels ( void ) {
2019-09-22 22:47:42 +00:00
return image_buffers [ ( current_buffer + 1 ) % number_of_buffers ( ) ] ;
}
2020-04-10 01:22:16 +00:00
2019-09-23 22:34:21 +00:00
static uint32_t * get_current_buffer ( void ) {
2019-09-22 22:47:42 +00:00
return image_buffers [ current_buffer ] ;
}
2019-09-23 22:34:21 +00:00
static uint32_t * get_previous_buffer ( void ) {
2019-09-22 22:47:42 +00:00
return image_buffers [ ( current_buffer + 2 ) % number_of_buffers ( ) ] ;
}
2020-04-10 01:22:16 +00:00
2019-09-23 22:34:21 +00:00
static void render_texture ( void * pixels , void * previous ) {
2020-04-10 01:22:16 +00:00
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 ) ;
2019-09-22 22:47:42 +00:00
}
2019-09-28 21:34:18 +00:00
static void handle_events ( GB_gameboy_t * gb ) {
while ( gtk_events_pending ( ) ) {
gtk_main_iteration ( ) ;
}
GB_set_key_state ( gb , GB_KEY_RIGHT , pressed_buttons & BUTTON_MASK_RIGHT ) ;
GB_set_key_state ( gb , GB_KEY_LEFT , pressed_buttons & BUTTON_MASK_LEFT ) ;
GB_set_key_state ( gb , GB_KEY_UP , pressed_buttons & BUTTON_MASK_UP ) ;
GB_set_key_state ( gb , GB_KEY_DOWN , pressed_buttons & BUTTON_MASK_DOWN ) ;
GB_set_key_state ( gb , GB_KEY_A , pressed_buttons & BUTTON_MASK_A ) ;
GB_set_key_state ( gb , GB_KEY_B , pressed_buttons & BUTTON_MASK_B ) ;
GB_set_key_state ( gb , GB_KEY_SELECT , pressed_buttons & BUTTON_MASK_SELECT ) ;
GB_set_key_state ( gb , GB_KEY_START , pressed_buttons & BUTTON_MASK_START ) ;
}
2019-09-22 22:47:42 +00:00
static void vblank ( GB_gameboy_t * gb ) {
flip ( ) ;
GB_set_pixels_output ( gb , get_pixels ( ) ) ;
// Queue drawing of the current frame
2019-09-29 23:49:41 +00:00
if ( fallback_canvas ) {
gtk_widget_queue_draw ( GTK_WIDGET ( main_window ) ) ;
}
else if ( gl_area ) {
gtk_gl_area_queue_render ( gl_area ) ;
}
2019-09-25 20:47:33 +00:00
if ( vram_viewer_visible ) {
// TODO: Only update what is needed
GB_draw_tileset ( gb , tileset_buffer , GB_PALETTE_NONE , 0 ) ;
GB_draw_tilemap ( gb , tilemap_buffer , GB_PALETTE_AUTO , 0 , GB_MAP_AUTO , GB_TILESET_AUTO ) ;
// Queue a redraw of the VRAM viewer
gtk_widget_queue_draw ( GTK_WIDGET ( vram_viewer ) ) ;
}
2020-04-10 01:22:16 +00:00
}
2019-09-23 22:34:21 +00:00
static void update_viewport ( void ) {
2019-09-29 23:49:41 +00:00
GtkWidget * w = fallback_canvas ? GTK_WIDGET ( fallback_canvas ) : GTK_WIDGET ( gl_area ) ;
int win_width = gtk_widget_get_allocated_width ( w ) ;
int win_height = gtk_widget_get_allocated_height ( w ) ;
2019-09-22 22:47:42 +00:00
2019-09-24 18:22:07 +00:00
double x_factor = win_width / ( double ) GB_get_screen_width ( & gb ) ;
2019-09-22 22:47:42 +00:00
double y_factor = win_height / ( double ) GB_get_screen_height ( & gb ) ;
2019-09-25 00:48:07 +00:00
if ( config . use_integer_scaling ) {
2019-09-22 22:47:42 +00:00
x_factor = ( int ) ( x_factor ) ;
y_factor = ( int ) ( y_factor ) ;
}
2019-09-25 00:48:07 +00:00
if ( config . keep_aspect_ratio ) {
2019-09-24 18:22:07 +00:00
if ( x_factor > y_factor ) {
x_factor = y_factor ;
}
else {
y_factor = x_factor ;
}
2019-09-22 22:47:42 +00:00
}
2019-09-24 18:22:07 +00:00
unsigned new_width = x_factor * GB_get_screen_width ( & gb ) ;
2019-09-22 22:47:42 +00:00
unsigned new_height = y_factor * GB_get_screen_height ( & gb ) ;
2019-09-24 18:22:07 +00:00
rect = ( Rect ) {
( win_width - new_width ) / 2 ,
( win_height - new_height ) / 2 ,
new_width ,
new_height
} ;
2019-09-22 22:47:42 +00:00
2019-09-29 23:49:41 +00:00
if ( ! fallback_canvas ) glViewport ( rect . x , rect . y , rect . w , rect . h ) ;
2020-04-10 01:22:16 +00:00
}
2020-04-11 16:24:55 +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 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 ;
}
2019-09-23 14:35:10 +00:00
static void quit ( GApplication * app ) {
2019-09-26 18:31:58 +00:00
// Tell our own main loop to quit.
// This will allow our run() and therefore our activate() methods to end.
2019-09-23 14:35:10 +00:00
running = false ;
2019-09-28 21:34:18 +00:00
// Quit our application properly.
// This fires the “shutdown” signal.
g_application_quit ( app ) ;
2019-09-23 14:35:10 +00:00
}
2020-04-11 16:24:55 +00:00
// app.quit GAction
// Exits the application
2019-09-28 21:34:18 +00:00
static void activate_quit ( GSimpleAction * action , GVariant * parameter , gpointer app ) {
quit ( G_APPLICATION ( app ) ) ;
2020-04-11 16:24:55 +00:00
}
// app.about GAction
// Opens the about dialog
2019-09-28 21:34:18 +00:00
static void activate_about ( GSimpleAction * action , GVariant * parameter , gpointer app ) {
2020-04-11 16:24:55 +00:00
GObject * dialog = get_object ( " about_dialog " ) ;
gtk_dialog_run ( GTK_DIALOG ( dialog ) ) ;
gtk_widget_hide ( GTK_WIDGET ( dialog ) ) ;
}
2019-09-23 22:34:21 +00:00
// app.open_gtk_debugger GAction
// Opens the GTK debugger
2019-09-28 21:34:18 +00:00
static void activate_open_gtk_debugger ( GSimpleAction * action , GVariant * parameter , gpointer app ) {
2019-09-23 22:34:21 +00:00
gtk_window_set_interactive_debugging ( true ) ;
}
// app.preferences GAction
// Opens the preferences window
2019-09-28 21:34:18 +00:00
static void activate_preferences ( GSimpleAction * action , GVariant * parameter , gpointer app ) {
2019-09-23 22:34:21 +00:00
gtk_widget_show_all ( GTK_WIDGET ( get_object ( " preferences " ) ) ) ;
}
2019-09-25 20:47:33 +00:00
// app.open_vram_viewer GAction
// Opens the VRAM viewer window
2019-09-28 21:34:18 +00:00
static void activate_open_vram_viewer ( GSimpleAction * action , GVariant * parameter , gpointer app ) {
2019-09-25 20:47:33 +00:00
gtk_widget_show_all ( GTK_WIDGET ( vram_viewer ) ) ;
}
// app.open_memory_viewer GAction
// Opens the memory viewer window
2019-09-28 21:34:18 +00:00
static void activate_open_memory_viewer ( GSimpleAction * action , GVariant * parameter , gpointer app ) {
2019-09-25 20:47:33 +00:00
gtk_widget_show_all ( GTK_WIDGET ( memory_viewer ) ) ;
}
2019-09-28 21:34:18 +00:00
// app.open GAction
// Opens a ROM file
static void activate_open ( GSimpleAction * action , GVariant * parameter , gpointer app ) {
GtkFileChooserNative * native = gtk_file_chooser_native_new ( " Open File " , GTK_WINDOW ( main_window ) , GTK_FILE_CHOOSER_ACTION_OPEN , " _Open " , " _Cancel " ) ;
gint res = gtk_native_dialog_run ( GTK_NATIVE_DIALOG ( native ) ) ;
if ( res = = GTK_RESPONSE_ACCEPT ) {
g_print ( " %s \n " , gtk_file_chooser_get_filename ( GTK_FILE_CHOOSER ( native ) ) ) ;
}
g_object_unref ( native ) ;
}
2020-04-11 16:24:55 +00:00
// List of GActions for the `app` prefix
static GActionEntry app_entries [ ] = {
{ " quit " , activate_quit , NULL , NULL , NULL } ,
{ " about " , activate_about , NULL , NULL , NULL } ,
2019-09-28 21:34:18 +00:00
{ " open " , activate_open , NULL , NULL , NULL } ,
2019-09-23 22:34:21 +00:00
{ " open_gtk_debugger " , activate_open_gtk_debugger , NULL , NULL , NULL } ,
{ " preferences " , activate_preferences , NULL , NULL , NULL } ,
2019-09-25 20:47:33 +00:00
{ " open_vram_viewer " , activate_open_vram_viewer , NULL , NULL , NULL } ,
{ " open_memory_viewer " , activate_open_memory_viewer , NULL , NULL , NULL } ,
2020-04-11 16:24:55 +00:00
} ;
2019-09-28 21:34:18 +00:00
G_MODULE_EXPORT gboolean on_key_press ( GtkWidget * w , GdkEventKey * event , gpointer data ) {
uint8_t mask ;
// TODO: Allow control remapping in the GUI
switch ( event - > keyval ) {
case GDK_KEY_w : mask = BUTTON_MASK_UP ; break ;
case GDK_KEY_a : mask = BUTTON_MASK_LEFT ; break ;
case GDK_KEY_s : mask = BUTTON_MASK_DOWN ; break ;
case GDK_KEY_d : mask = BUTTON_MASK_RIGHT ; break ;
case GDK_KEY_g : mask = BUTTON_MASK_SELECT ; break ;
case GDK_KEY_h : mask = BUTTON_MASK_START ; break ;
case GDK_KEY_k : mask = BUTTON_MASK_B ; break ;
case GDK_KEY_l : mask = BUTTON_MASK_A ; break ;
}
if ( event - > type = = GDK_KEY_PRESS ) {
pressed_buttons | = mask ;
}
else if ( event - > type = = GDK_KEY_RELEASE ) {
pressed_buttons & = ~ mask ;
}
return FALSE ;
}
2019-09-23 14:35:10 +00:00
G_MODULE_EXPORT void on_quit ( GtkWidget * w , gpointer app ) {
quit ( G_APPLICATION ( app ) ) ;
}
2020-04-11 16:24:55 +00:00
G_MODULE_EXPORT void on_show_window ( GtkWidget * w , gpointer window ) {
gtk_widget_show_all ( GTK_WIDGET ( window ) ) ;
}
2019-09-29 23:49:41 +00:00
G_MODULE_EXPORT gboolean on_draw_fallback ( GtkWidget * widget , cairo_t * cr , gpointer data ) {
GtkStyleContext * context = gtk_widget_get_style_context ( widget ) ;
guint width = gtk_widget_get_allocated_width ( widget ) ;
guint height = gtk_widget_get_allocated_height ( widget ) ;
2020-04-10 01:22:16 +00:00
2019-09-29 23:49:41 +00:00
guint screen_width = GB_get_screen_width ( & gb ) ;
guint screen_height = GB_get_screen_height ( & gb ) ;
2020-04-10 01:22:16 +00:00
2019-09-29 23:49:41 +00:00
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 ,
screen_width ,
screen_height ,
cairo_format_stride_for_width ( CAIRO_FORMAT_RGB24 , screen_width )
) ;
2020-04-10 01:22:16 +00:00
2019-09-29 23:49:41 +00:00
cairo_translate ( cr , rect . x , rect . y ) ;
cairo_scale ( cr , rect . w / screen_width , rect . h / screen_height ) ;
cairo_set_source_surface ( cr , surface , 0 , 0 ) ;
cairo_pattern_set_filter ( cairo_get_source ( cr ) , CAIRO_FILTER_NEAREST ) ;
cairo_paint ( cr ) ;
return FALSE ;
2020-04-10 01:22:16 +00:00
}
2019-09-29 23:49:41 +00:00
G_MODULE_EXPORT void resize ( ) {
2019-09-22 22:47:42 +00:00
update_viewport ( ) ;
}
2019-09-29 23:49:41 +00:00
static void create_fallback_canvas ( void ) {
fallback_canvas = GTK_DRAWING_AREA ( gtk_drawing_area_new ( ) ) ;
g_signal_connect ( fallback_canvas , " draw " , G_CALLBACK ( on_draw_fallback ) , NULL ) ;
g_signal_connect ( fallback_canvas , " size-allocate " , G_CALLBACK ( resize ) , NULL ) ;
gtk_box_pack_end ( GTK_BOX ( main_window_container ) , GTK_WIDGET ( fallback_canvas ) , TRUE , TRUE , 0 ) ;
}
2019-09-22 22:47:42 +00:00
G_MODULE_EXPORT void gl_draw ( ) {
render_texture ( get_current_buffer ( ) , get_previous_buffer ( ) ) ;
}
G_MODULE_EXPORT void gl_finish ( ) { }
2019-09-29 23:49:41 +00:00
G_MODULE_EXPORT void gl_init ( GtkWidget * w ) {
GtkGLArea * gl_area = GTK_GL_AREA ( w ) ;
g_print ( " GL_INIT \n " ) ;
const char * renderer ;
g_print ( " GL Context: %p \n " , gtk_gl_area_get_context ( gl_area ) ) ;
gtk_gl_area_make_current ( gl_area ) ;
if ( gtk_gl_area_get_error ( gl_area ) ! = NULL ) {
goto error ;
}
renderer = ( char * ) glGetString ( GL_RENDERER ) ;
g_print ( " GtkGLArea on %s \n " , renderer ? renderer : " Unknown " ) ;
if ( config . shader = = NULL | | ! init_shader_with_name ( & shader , config . shader ) | | ! init_shader_with_name ( & shader , " NearestNeighbor " ) ) {
GError * error = g_error_new_literal ( g_quark_from_string ( " sameboy-gl-error " ) , 1 , " Failed to initialize shaders " ) ;
gtk_gl_area_set_error ( gl_area , error ) ;
}
else {
g_signal_connect ( gl_area , " render " , G_CALLBACK ( gl_draw ) , NULL ) ;
g_signal_connect ( gl_area , " resize " , G_CALLBACK ( resize ) , NULL ) ;
g_signal_connect ( gl_area , " unrealize " , G_CALLBACK ( gl_finish ) , NULL ) ;
return ;
}
error :
if ( gtk_gl_area_get_error ( gl_area ) ! = NULL ) {
g_printerr ( " GtkGLArea: %s \n " , gtk_gl_area_get_error ( gl_area ) - > message ) ;
}
create_fallback_canvas ( ) ;
}
2019-09-27 21:10:28 +00:00
G_MODULE_EXPORT void on_vram_viewer_realize ( ) {
2019-09-25 20:47:33 +00:00
vram_viewer_visible = true ;
}
2019-09-27 21:10:28 +00:00
G_MODULE_EXPORT void on_vram_viewer_unrealize ( ) {
2019-09-25 20:47:33 +00:00
vram_viewer_visible = false ;
}
G_MODULE_EXPORT gboolean on_draw_vram_viewer_tileset ( GtkWidget * widget , cairo_t * cr , gpointer data ) {
guint width , height ;
GtkStyleContext * context ;
context = gtk_widget_get_style_context ( widget ) ;
width = gtk_widget_get_allocated_width ( widget ) ;
height = gtk_widget_get_allocated_height ( widget ) ;
gtk_render_background ( context , cr , 0 , 0 , width , height ) ;
cairo_surface_t * surface = cairo_image_surface_create_for_data (
( unsigned char * ) tileset_buffer ,
CAIRO_FORMAT_RGB24 ,
256 ,
192 ,
cairo_format_stride_for_width ( CAIRO_FORMAT_RGB24 , 256 )
) ;
cairo_set_source_surface ( cr , surface , 0 , 0 ) ;
cairo_paint ( cr ) ;
return FALSE ;
}
2019-09-29 23:49:41 +00:00
G_MODULE_EXPORT gboolean on_draw_vram_viewer_tilemap ( GtkWidget * widget , cairo_t * cr , gpointer data ) {
2019-09-25 20:47:33 +00:00
guint width , height ;
GtkStyleContext * context ;
context = gtk_widget_get_style_context ( widget ) ;
width = gtk_widget_get_allocated_width ( widget ) ;
height = gtk_widget_get_allocated_height ( widget ) ;
gtk_render_background ( context , cr , 0 , 0 , width , height ) ;
cairo_surface_t * surface = cairo_image_surface_create_for_data (
( unsigned char * ) tilemap_buffer ,
CAIRO_FORMAT_RGB24 ,
256 ,
256 ,
cairo_format_stride_for_width ( CAIRO_FORMAT_RGB24 , 256 )
) ;
cairo_set_source_surface ( cr , surface , 0 , 0 ) ;
cairo_paint ( cr ) ;
return FALSE ;
}
2019-09-26 16:06:14 +00:00
G_MODULE_EXPORT void on_highpass_filter_changed ( GtkWidget * w , gpointer user_data_gptr ) {
config . high_pass_filter_id = ( gchar * ) gtk_combo_box_get_active_id ( GTK_COMBO_BOX ( w ) ) ;
if ( GB_is_inited ( & gb ) ) {
GB_set_highpass_filter_mode ( & gb , get_highpass_mode ( ) ) ;
}
}
G_MODULE_EXPORT void on_use_integer_scaling_changed ( GtkWidget * w , gpointer user_data_gptr ) {
GtkCheckButton * button = GTK_CHECK_BUTTON ( w ) ;
gboolean value = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON ( button ) ) ;
config . use_integer_scaling = value ;
update_viewport ( ) ;
}
G_MODULE_EXPORT void on_keep_aspect_ratio_changed ( GtkWidget * w , gpointer user_data_gptr ) {
GtkCheckButton * button = GTK_CHECK_BUTTON ( w ) ;
gboolean value = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON ( button ) ) ;
config . keep_aspect_ratio = value ;
update_viewport ( ) ;
}
G_MODULE_EXPORT void on_color_correction_changed ( GtkWidget * w , gpointer user_data_gptr ) {
GtkComboBox * box = GTK_COMBO_BOX ( w ) ;
config . color_correction_id = ( gchar * ) gtk_combo_box_get_active_id ( box ) ;
if ( GB_is_inited ( & gb ) ) {
GB_set_color_correction_mode ( & gb , get_color_correction_mode ( ) ) ;
}
}
G_MODULE_EXPORT void on_graphic_filter_changed ( GtkWidget * w , gpointer user_data_gptr ) {
GtkComboBox * box = GTK_COMBO_BOX ( w ) ;
config . shader = ( gchar * ) gtk_combo_box_get_active_id ( box ) ;
init_shader_with_name ( & shader , config . shader ) ;
}
G_MODULE_EXPORT void on_cgb_model_changed ( GtkWidget * w , gpointer user_data_gptr ) {
GtkComboBox * box = GTK_COMBO_BOX ( w ) ;
g_print ( " New value: %s \n " , gtk_combo_box_get_active_id ( box ) ) ;
}
G_MODULE_EXPORT void on_sgb_model_changed ( GtkWidget * w , gpointer user_data_gptr ) {
GtkComboBox * box = GTK_COMBO_BOX ( w ) ;
g_print ( " New value: %s \n " , gtk_combo_box_get_active_id ( box ) ) ;
}
G_MODULE_EXPORT void on_dmg_model_changed ( GtkWidget * w , gpointer user_data_gptr ) {
GtkComboBox * box = GTK_COMBO_BOX ( w ) ;
g_print ( " New value: %s \n " , gtk_combo_box_get_active_id ( box ) ) ;
}
G_MODULE_EXPORT void on_rewind_duration_changed ( GtkWidget * w , gpointer user_data_gptr ) {
GtkComboBox * box = GTK_COMBO_BOX ( w ) ;
g_print ( " New value: %s \n " , gtk_combo_box_get_active_id ( box ) ) ;
}
G_MODULE_EXPORT void on_boot_rom_location_changed ( GtkWidget * w , gpointer user_data_gptr ) {
GtkComboBox * box = GTK_COMBO_BOX ( w ) ;
2019-09-27 21:10:28 +00:00
const gchar * id = gtk_combo_box_get_active_id ( box ) ;
2019-09-27 22:15:37 +00:00
if ( id = = NULL ) return ;
2019-09-27 21:10:28 +00:00
if ( g_strcmp0 ( id , " other " ) = = 0 ) {
2019-09-27 22:15:37 +00:00
GtkFileChooserNative * native = gtk_file_chooser_native_new ( " Select Folder " , preferences , GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER , " _Select " , " _Cancel " ) ;
gint res = gtk_native_dialog_run ( GTK_NATIVE_DIALOG ( native ) ) ;
if ( res = = GTK_RESPONSE_ACCEPT ) {
config . boot_rom_path = gtk_file_chooser_get_filename ( GTK_FILE_CHOOSER ( native ) ) ;
update_boot_rom_selector ( builder ) ;
}
g_object_unref ( native ) ;
2019-09-27 21:10:28 +00:00
}
else {
config . boot_rom_path = ( gchar * ) id ;
}
}
G_MODULE_EXPORT void on_color_menubar_override_changed ( GtkWidget * w , gpointer user_data_gptr ) {
config . menubar_override = ( gchar * ) gtk_combo_box_get_active_id ( GTK_COMBO_BOX ( w ) ) ;
2019-09-26 16:06:14 +00:00
}
2019-09-29 23:49:41 +00:00
static void setup_menu ( GApplication * app ) {
GMenuModel * menubar_model = get_menu_model ( app , " menubar " ) ;
enum menubar_type_t menubar_type = get_show_menubar ( ) ;
// Try to use a sane default
if ( menubar_type = = MENUBAR_AUTO ) {
GtkSettings * settings = gtk_settings_get_default ( ) ;
gboolean show_in_shell ;
g_object_get ( settings , " gtk-shell-shows-menubar " , & show_in_shell , NULL ) ;
const gchar * xdg_current_desktop = g_getenv ( " XDG_CURRENT_DESKTOP " ) ;
const gchar * gdm_session = g_getenv ( " GDMSESSION " ) ;
const gchar * desktop_session = g_getenv ( " DESKTOP_SESSION " ) ;
gchar * desktop = ( gchar * ) xdg_current_desktop ;
if ( desktop = = NULL | | g_str_equal ( desktop , " " ) ) desktop = ( gchar * ) gdm_session ;
if ( desktop = = NULL | | g_str_equal ( desktop , " " ) ) desktop = ( gchar * ) desktop_session ;
g_print ( " XDG_CURRENT_DESKTOP: %s \n GDMSESSION: %s \n DESKTOP_SESSION: %s \n Chosen value: %s \n Show menu in shell: %d \n " , xdg_current_desktop , gdm_session , desktop_session , desktop , show_in_shell ) ;
if ( desktop ! = NULL & & show_in_shell ) {
menubar_type = MENUBAR_SHOW_IN_SHELL ;
}
else if ( desktop ! = NULL & & g_str_match_string ( " GNOME " , desktop , FALSE ) ) {
if ( g_str_match_string ( " GNOME-Flashback " , desktop , FALSE ) | | g_str_match_string ( " GNOME-Classic " , desktop , FALSE ) ) {
menubar_type = MENUBAR_SHOW_IN_WINDOW ;
}
else if ( gdm_session ! = NULL & & ( g_str_match_string ( " gnome-classic " , gdm_session , FALSE ) | | g_str_match_string ( " gnome-flashback " , gdm_session , FALSE ) ) ) {
menubar_type = MENUBAR_SHOW_IN_WINDOW ;
}
else {
menubar_type = MENUBAR_SHOW_HAMBURGER ;
}
}
else {
menubar_type = MENUBAR_SHOW_IN_WINDOW ;
}
}
switch ( menubar_type ) {
case MENUBAR_AUTO :
g_error ( " Unreachable \n " ) ;
break ;
case MENUBAR_SHOW_IN_SHELL :
g_print ( " Showing menu in the shell \n " ) ;
gtk_application_set_menubar ( GTK_APPLICATION ( app ) , menubar_model ) ;
break ;
case MENUBAR_SHOW_IN_WINDOW : {
g_print ( " Showing menu in the window \n " ) ;
GtkMenuBar * menubar = GTK_MENU_BAR ( gtk_menu_bar_new_from_model ( menubar_model ) ) ;
gtk_box_pack_start ( GTK_BOX ( main_window_container ) , GTK_WIDGET ( menubar ) , FALSE , FALSE , 0 ) ;
break ;
}
case MENUBAR_SHOW_HAMBURGER : {
g_print ( " Showing hamburger \n " ) ;
// Attach a custom title bar
GtkWidget * titlebar = gtkget ( GTK_WIDGET , " main_header_bar " ) ;
gtk_window_set_titlebar ( GTK_WINDOW ( main_window ) , 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 " ) ) ;
gtk_menu_button_set_menu_model ( hamburger_button , menubar_model ) ;
break ;
}
}
}
G_MODULE_EXPORT void gl_check_realize ( GtkWidget * w , gpointer user_data_gptr ) {
gboolean * result = ( gboolean * ) user_data_gptr ;
GError * error = NULL ;
GdkWindow * gdk_window = gtk_widget_get_window ( w ) ;
GdkGLContext * context = gdk_window_create_gl_context ( gdk_window , & error ) ;
if ( error ! = NULL ) {
g_printerr ( " Failed to create context: %s \n " , error - > message ) ;
g_error_free ( error ) ;
* result = FALSE ;
}
else {
gdk_gl_context_make_current ( context ) ;
int version = epoxy_gl_version ( ) ;
g_object_run_dispose ( G_OBJECT ( context ) ) ;
g_object_unref ( context ) ;
context = NULL ;
gdk_gl_context_clear_current ( ) ;
g_print ( " OpenGL version: %d \n " , version ) ;
* result = version > = 32 ;
}
}
gboolean test_gl_support ( void ) {
gboolean result = FALSE ;
gtk_init ( NULL , NULL ) ;
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 ) ;
window = NULL ;
return result ;
}
2020-04-11 16:24:55 +00:00
// This functions gets called immediately after registration of the GApplication
static void startup ( GApplication * app , gpointer user_data_gptr ) {
2019-09-24 14:14:06 +00:00
UserData * user_data = user_data_gptr ;
2019-09-29 23:49:41 +00:00
// Very ugly workaround for GtkGlArea!
// When a GtkGlArea is realized and it creates a legacy GL 1.4 context
// it tries to use GL 2.0 functions to render the window which leads to the application crashing.
// So we initialize GTK, create a dummy GtkWindow object, attach a `realize` callback and
// in this callback create a GdkGLContext on this window. But instead of running the GTK main loop
// we just realize and destroy the dummy window and compare the context’ s version in the realize callback.
supports_gl = test_gl_support ( ) ;
g_print ( " OpenGL supported: %s \n " , supports_gl ? " Yes " : " No " ) ;
2020-04-11 16:24:55 +00:00
builder = gtk_builder_new_from_resource ( RESOURCE_PREFIX " ui/window.ui " ) ;
gtk_builder_connect_signals ( builder , NULL ) ;
2019-09-27 21:10:28 +00:00
// Setup application actions
2020-04-11 16:24:55 +00:00
g_action_map_add_action_entries ( G_ACTION_MAP ( app ) , app_entries , G_N_ELEMENTS ( app_entries ) , app ) ;
2019-09-27 21:10:28 +00:00
# if NDEBUG
// Disable when not compiled in debug mode
g_simple_action_set_enabled ( G_SIMPLE_ACTION ( g_action_map_lookup_action ( G_ACTION_MAP ( app ) , " open_gtk_debugger " ) ) , false ) ;
// Remove the menubar override
gtk_widget_destroy ( gtkget ( GTK_WIDGET , " menubar_override_selector_label " ) ) ;
gtk_widget_destroy ( gtkget ( GTK_WIDGET , " menubar_override_selector " ) ) ;
# endif
2019-09-27 22:15:37 +00:00
preferences = GTK_WINDOW ( get_object ( " preferences " ) ) ;
2020-04-11 16:24:55 +00:00
2019-09-27 21:10:28 +00:00
g_signal_connect ( preferences , " realize " , G_CALLBACK ( on_preferences_realize ) , ( gpointer ) builder ) ;
2019-09-25 20:47:33 +00:00
init_settings ( user_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 " ) ) ;
2019-09-22 22:47:42 +00:00
// setup main window
main_window = GTK_APPLICATION_WINDOW ( gtk_application_window_new ( GTK_APPLICATION ( app ) ) ) ;
2019-09-29 23:49:41 +00:00
main_window_container = GTK_BOX ( gtk_box_new ( GTK_ORIENTATION_VERTICAL , 0 ) ) ;
gtk_window_set_title ( GTK_WINDOW ( main_window ) , " SameBoy " ) ;
2019-09-29 00:07:33 +00:00
gtk_application_window_set_show_menubar ( main_window , false ) ;
2019-09-29 23:49:41 +00:00
gtk_container_add ( GTK_CONTAINER ( main_window ) , GTK_WIDGET ( main_window_container ) ) ;
2020-04-11 16:24:55 +00:00
2019-09-29 23:49:41 +00:00
setup_menu ( app ) ;
2019-09-25 20:47:33 +00:00
2019-09-27 21:10:28 +00:00
// Insert separators into `GtkComboBox`es
set_combo_box_row_separator_func ( GTK_CONTAINER ( preferences ) ) ;
set_combo_box_row_separator_func ( GTK_CONTAINER ( vram_viewer ) ) ;
set_combo_box_row_separator_func ( GTK_CONTAINER ( memory_viewer ) ) ;
2020-04-11 16:24:55 +00:00
// 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
2019-09-22 22:47:42 +00:00
gtk_window_set_icon_list ( GTK_WINDOW ( main_window ) , icon_list ) ;
2020-04-11 16:24:55 +00:00
// 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 ) {
2019-09-22 22:47:42 +00:00
gtk_window_fullscreen ( GTK_WINDOW ( main_window ) ) ;
2020-04-11 16:24:55 +00:00
}
2019-09-29 23:49:41 +00:00
// Connect signal handlers
gtk_widget_add_events ( GTK_WIDGET ( main_window ) , GDK_KEY_PRESS_MASK ) ;
gtk_widget_add_events ( GTK_WIDGET ( main_window ) , GDK_KEY_RELEASE_MASK ) ;
2019-09-23 14:35:10 +00:00
g_signal_connect ( main_window , " destroy " , G_CALLBACK ( on_quit ) , app ) ;
2019-09-29 23:49:41 +00:00
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 ( vram_viewer , " realize " , G_CALLBACK ( on_vram_viewer_realize ) , NULL ) ;
g_signal_connect ( vram_viewer , " unrealize " , G_CALLBACK ( on_vram_viewer_unrealize ) , NULL ) ;
// Just hide our sub-windows when closing them
g_signal_connect ( preferences , " delete-event " , G_CALLBACK ( gtk_widget_hide_on_delete ) , NULL ) ;
g_signal_connect ( vram_viewer , " delete-event " , G_CALLBACK ( gtk_widget_hide_on_delete ) , NULL ) ;
g_signal_connect ( memory_viewer , " delete-event " , G_CALLBACK ( gtk_widget_hide_on_delete ) , NULL ) ;
g_signal_connect ( console , " delete-event " , G_CALLBACK ( gtk_widget_hide_on_delete ) , NULL ) ;
g_signal_connect ( printer , " delete-event " , G_CALLBACK ( gtk_widget_hide_on_delete ) , NULL ) ;
g_signal_connect ( get_object ( " vram_viewer_tileset_canvas " ) , " draw " , G_CALLBACK ( on_draw_vram_viewer_tileset ) , NULL ) ;
g_signal_connect ( get_object ( " vram_viewer_tilemap_canvas " ) , " draw " , G_CALLBACK ( on_draw_vram_viewer_tilemap ) , NULL ) ;
// create our renderer area
if ( supports_gl ) {
gl_area = GTK_GL_AREA ( gtk_gl_area_new ( ) ) ;
gtk_gl_area_set_required_version ( gl_area , 3 , 2 ) ;
gtk_gl_area_set_auto_render ( gl_area , false ) ;
gtk_gl_area_set_has_alpha ( gl_area , false ) ;
gtk_gl_area_set_has_depth_buffer ( gl_area , false ) ;
gtk_gl_area_set_has_stencil_buffer ( gl_area , false ) ;
g_signal_connect ( gl_area , " realize " , G_CALLBACK ( gl_init ) , NULL ) ;
gtk_box_pack_end ( GTK_BOX ( main_window_container ) , GTK_WIDGET ( gl_area ) , TRUE , TRUE , 0 ) ;
}
else {
create_fallback_canvas ( ) ;
}
2019-09-23 14:35:10 +00:00
2019-09-22 22:47:42 +00:00
gtk_application_add_window ( GTK_APPLICATION ( app ) , GTK_WINDOW ( main_window ) ) ;
2020-04-11 16:24:55 +00:00
gtk_widget_show_all ( GTK_WIDGET ( main_window ) ) ;
2020-04-10 01:22:16 +00:00
2019-09-23 14:35:10 +00:00
// Start the emulation loop.
// This loop takes care of the GTK main loop.
2019-09-26 18:31:58 +00:00
run ( app , user_data ) ;
2020-04-11 16:24:55 +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 " ) ;
2019-09-23 22:34:21 +00:00
exit ( EXIT_FAILURE ) ;
2020-04-11 16:24:55 +00:00
}
user_data - > file = files [ 0 ] ;
// We have handled the files, now activate the application
activate ( app , user_data_gptr ) ;
}
2019-09-26 18:31:58 +00:00
// This function gets called when the application is closed.
2019-09-23 14:35:10 +00:00
static void shutdown ( GApplication * app , GFile * * files , gint n_files , const gchar * hint , gpointer user_data_gptr ) {
g_print ( " SHUTDOWN \n " ) ;
2019-09-23 22:34:21 +00:00
save_settings ( ) ;
2019-09-25 00:48:07 +00:00
free_settings ( ) ;
2019-09-23 14:35:10 +00:00
}
2020-04-11 16:24:55 +00:00
// 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 ;
}
2019-09-23 22:34:21 +00:00
// 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 ) {
user_data - > model = GB_MODEL_DMG_B ;
}
else {
user_data - > model = GB_MODEL_DMG_B ;
g_printerr ( " Unsupported revision: %s \n Falling 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 ;
}
else if ( g_str_has_suffix ( model_name , " -PAL " ) ) {
user_data - > model = GB_MODEL_SGB | GB_MODEL_PAL_BIT ;
}
else if ( g_str_has_suffix ( model_name , " 2 " ) ) {
user_data - > model = GB_MODEL_SGB2 ;
}
else {
user_data - > model = GB_MODEL_SGB2 ;
g_printerr ( " Unsupported revision: %s \n Falling 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 ;
}
else if ( g_str_has_suffix ( model_name , " -E " ) | | g_strcmp0 ( model_name , " CGB " ) = = 0 ) {
user_data - > model = GB_MODEL_CGB_E ;
}
else {
user_data - > model = GB_MODEL_CGB_E ;
g_printerr ( " Unsupported revision: %s \n Falling back to CGB-E " , model_name ) ;
}
}
else if ( g_str_has_prefix ( model_name , " AGB " ) ) {
user_data - > model = GB_MODEL_AGB ;
}
else {
g_printerr ( " Unknown model: %s \n " , model_name ) ;
exit ( EXIT_FAILURE ) ;
}
}
2020-04-11 16:24:55 +00:00
return - 1 ;
}
2020-04-10 01:22:16 +00:00
static uint32_t rgb_encode ( GB_gameboy_t * gb , uint8_t r , uint8_t g , uint8_t b ) {
2019-09-23 16:38:16 +00:00
// We use GL_RGBA and GL_UNSIGNED_BYTE for our texture upload,
// so OpenGL expects pixel data in RGBA order in memory.
# ifdef GB_LITTLE_ENDIAN
// ABGR
uint32_t color = 0xFF000000 | ( b < < 16 ) | ( g < < 8 ) | r ;
# else
// RGBA
uint32_t color = ( r < < 24 ) | ( g < < 16 ) | ( b < < 8 ) | 0xFF ;
# endif
2020-04-10 01:22:16 +00:00
return color ;
}
2019-09-29 23:49:41 +00:00
static uint32_t rgb_encode_fallback ( GB_gameboy_t * gb , uint8_t r , uint8_t g , uint8_t b ) {
# ifdef GB_LITTLE_ENDIAN
// ARGB
uint32_t color = 0xFF000000 | ( r < < 16 ) | ( g < < 8 ) | b ;
# else
// BGRA
uint32_t color = ( b < < 24 ) | ( g < < 16 ) | ( r < < 8 ) | 0xFF ;
# endif
return color ;
}
2019-09-22 22:47:42 +00:00
static void update_window_geometry ( ) {
// Set size hints
GdkGeometry hints ;
hints . min_width = GB_get_screen_width ( & gb ) ;
hints . min_height = GB_get_screen_height ( & gb ) ;
gtk_window_set_geometry_hints (
GTK_WINDOW ( main_window ) ,
NULL ,
& hints ,
( GdkWindowHints ) ( GDK_HINT_MIN_SIZE )
) ;
gtk_window_resize ( GTK_WINDOW ( main_window ) ,
GB_get_screen_width ( & gb ) * 2 ,
GB_get_screen_height ( & gb ) * 2
) ;
2019-09-23 16:38:16 +00:00
// Setup our image buffers
2019-09-22 22:47:42 +00:00
if ( image_buffers [ 0 ] ) free ( image_buffers [ 0 ] ) ;
if ( image_buffers [ 1 ] ) free ( image_buffers [ 1 ] ) ;
if ( image_buffers [ 2 ] ) 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 ) ;
2020-04-10 01:22:16 +00:00
}
2019-09-26 18:31:58 +00:00
static void run ( GApplication * app , UserData * user_data ) {
2019-09-22 22:47:42 +00:00
GB_model_t prev_model = GB_get_model ( & gb ) ;
2019-09-23 22:34:21 +00:00
GB_model_t model = user_data - > model ? user_data - > model : GB_MODEL_CGB_E ; // TODO: Model from config
2020-04-10 01:22:16 +00:00
if ( GB_is_inited ( & gb ) ) {
GB_switch_model_and_reset ( & gb , model ) ;
2019-09-22 22:47:42 +00:00
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 ( ) ;
}
2020-04-10 01:22:16 +00:00
}
else {
GB_init ( & gb , model ) ;
2019-09-22 22:47:42 +00:00
update_window_geometry ( ) ;
2020-04-10 01:22:16 +00:00
GB_set_vblank_callback ( & gb , ( GB_vblank_callback_t ) vblank ) ;
2019-09-22 22:47:42 +00:00
GB_set_pixels_output ( & gb , get_current_buffer ( ) ) ;
2019-09-29 23:49:41 +00:00
GB_set_rgb_encode_callback ( & gb , fallback_canvas ? rgb_encode_fallback : rgb_encode ) ;
2020-04-10 01:22:16 +00:00
// GB_set_sample_rate(&gb, have_aspec.freq);
2019-09-26 14:22:30 +00:00
GB_set_color_correction_mode ( & gb , get_color_correction_mode ( ) ) ;
GB_set_highpass_filter_mode ( & gb , get_highpass_mode ( ) ) ;
2019-09-25 00:48:07 +00:00
GB_set_rewind_length ( & gb , config . rewind_duration ) ;
2019-09-28 21:34:18 +00:00
GB_set_update_input_hint_callback ( & gb , handle_events ) ;
2020-04-10 01:22:16 +00:00
// GB_apu_set_sample_callback(&gb, gb_audio_callback);
}
2019-09-23 22:34:21 +00:00
GError * error ;
char * boot_rom_path ;
2019-09-26 18:31:58 +00:00
char * boot_rom_name ;
2020-04-10 01:22:16 +00:00
GBytes * boot_rom_f ;
const guchar * boot_rom_data ;
gsize boot_rom_size ;
2019-09-29 00:07:33 +00:00
if ( user_data - > boot_rom_path ! = NULL ) {
2019-09-26 18:31:58 +00:00
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 ) ) {
g_printerr ( " Falling back to boot ROM from config \n " ) ;
goto config_boot_rom ;
2019-09-23 22:34:21 +00:00
}
}
2019-09-26 18:31:58 +00:00
else { config_boot_rom :
2019-09-23 22:34:21 +00:00
switch ( model ) {
case GB_MODEL_DMG_B :
2019-09-26 18:31:58 +00:00
boot_rom_name = " dmg_boot.bin " ;
2019-09-23 22:34:21 +00:00
break ;
case GB_MODEL_SGB :
case GB_MODEL_SGB_PAL :
case GB_MODEL_SGB_NO_SFC :
2019-09-26 18:31:58 +00:00
boot_rom_name = " sgb_boot.bin " ;
2019-09-23 22:34:21 +00:00
break ;
case GB_MODEL_SGB2 :
case GB_MODEL_SGB2_NO_SFC :
2019-09-26 18:31:58 +00:00
boot_rom_name = " sgb2_boot.bin " ;
2019-09-23 22:34:21 +00:00
break ;
case GB_MODEL_CGB_C :
case GB_MODEL_CGB_E :
2019-09-26 18:31:58 +00:00
boot_rom_name = " cgb_boot.bin " ;
2019-09-23 22:34:21 +00:00
break ;
case GB_MODEL_AGB :
2019-09-26 18:31:58 +00:00
boot_rom_name = " agb_boot.bin " ;
2019-09-23 22:34:21 +00:00
break ;
}
2019-09-29 00:07:33 +00:00
if ( config . boot_rom_path ! = NULL & & g_strcmp0 ( config . boot_rom_path , " other " ) ! = 0 & & g_strcmp0 ( config . boot_rom_path , " auto " ) ! = 0 ) {
2019-09-26 18:31:58 +00:00
boot_rom_path = g_build_filename ( config . boot_rom_path , boot_rom_name , NULL ) ;
g_print ( " Trying to load boot ROM from %s \n " , boot_rom_path ) ;
if ( GB_load_boot_rom ( & gb , boot_rom_path ) ) {
g_printerr ( " Falling back to internal boot ROM \n " ) ;
goto internal_boot_rom ;
}
2019-09-23 22:34:21 +00:00
}
2019-09-26 18:31:58 +00:00
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 ) ;
if ( boot_rom_f = = NULL ) {
g_printerr ( " Failed to load internal boot ROM: %s \n " , boot_rom_path ) ;
g_error_free ( error ) ;
exit ( EXIT_FAILURE ) ;
}
2019-09-23 22:34:21 +00:00
2019-09-26 18:31:58 +00:00
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 ) ;
}
2019-09-23 22:34:21 +00:00
}
2020-04-10 01:22:16 +00:00
2019-09-28 21:34:18 +00:00
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 ) {
while ( gtk_events_pending ( ) ) {
gtk_main_iteration ( ) ;
}
2020-04-10 01:22:16 +00:00
}
2019-09-28 21:34:18 +00:00
else {
if ( do_rewind ) {
2020-04-10 01:22:16 +00:00
GB_rewind_pop ( & gb ) ;
2019-09-28 21:34:18 +00:00
if ( turbo_down ) {
GB_rewind_pop ( & gb ) ;
}
if ( ! GB_rewind_pop ( & gb ) ) {
rewind_paused = true ;
}
do_rewind = false ;
2020-04-10 01:22:16 +00:00
}
2019-09-28 21:34:18 +00:00
GB_run ( & gb ) ;
2020-04-10 01:22:16 +00:00
}
}
}
}
int main ( int argc , char * argv [ ] ) {
2020-04-11 16:24:55 +00:00
// Create our GApplication and tell GTK that we are able to handle files
2019-09-30 01:41:00 +00:00
main_application = gtk_application_new ( APP_ID , G_APPLICATION_NON_UNIQUE | G_APPLICATION_HANDLES_OPEN ) ;
2020-04-11 16:24:55 +00:00
// Define our command line parameters
GOptionEntry entries [ ] = {
2019-09-24 14:14:06 +00:00
{ " version " , ' v ' , G_OPTION_FLAG_NONE , G_OPTION_ARG_NONE , NULL , " Show the application version " , NULL } ,
2020-04-11 16:24:55 +00:00
{ " fullscreen " , ' f ' , G_OPTION_FLAG_NONE , G_OPTION_ARG_NONE , NULL , " Start in fullscreen mode " , NULL } ,
2019-09-26 18:31:58 +00:00
{ " bootrom " , ' b ' , G_OPTION_FLAG_NONE , G_OPTION_ARG_STRING , & user_data . boot_rom_path , " Path to the boot ROM to use " , " <file path> " } ,
2019-09-24 14:14:06 +00:00
{ " 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> " } ,
2020-04-11 16:24:55 +00:00
{ NULL }
} ;
// Setup our command line information
2019-09-23 14:35:10 +00:00
g_application_add_main_option_entries ( G_APPLICATION ( main_application ) , entries ) ;
g_application_set_option_context_parameter_string ( G_APPLICATION ( main_application ) , " [FILE…] " ) ;
g_application_set_option_context_summary ( G_APPLICATION ( main_application ) , " SameBoy is an open source Game Boy (DMG) and Game Boy Color (CGB) emulator. " ) ;
2020-04-11 16:24:55 +00:00
// Add signal handlers
2019-09-23 14:35:10 +00:00
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 ) ;
2020-04-11 16:24:55 +00:00
// Start our GApplication main loop
2019-09-23 14:35:10 +00:00
int status = g_application_run ( G_APPLICATION ( main_application ) , argc , argv ) ;
g_object_unref ( main_application ) ;
2020-04-11 16:24:55 +00:00
return status ;
}