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
# include "settings.h"
2020-04-10 01:22:16 +00:00
# include "shader.h"
2020-04-11 16:24:55 +00:00
# define str(x) #x
# define xstr(x) str(x)
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-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-25 20:47:33 +00:00
static GtkApplicationWindow * main_window ;
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-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 ;
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 } ;
// Returns a GObject by ID from our GtkBuilder instance
static GObject * get_object ( gchararray id ) {
return gtk_builder_get_object ( builder , id ) ;
}
2019-09-23 22:34:21 +00:00
static unsigned char number_of_buffers ( void ) {
2019-09-22 22:47:42 +00:00
bool should_blend = true ;
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
}
static void vblank ( GB_gameboy_t * gb ) {
flip ( ) ;
GB_set_pixels_output ( gb , get_pixels ( ) ) ;
// Queue drawing of the current frame
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 ) ) ;
}
2019-09-22 22:47:42 +00:00
while ( gtk_events_pending ( ) ) {
gtk_main_iteration ( ) ;
}
2020-04-10 01:22:16 +00:00
}
2019-09-23 22:34:21 +00:00
static void update_viewport ( void ) {
2019-09-24 18:22:07 +00:00
int win_width = gtk_widget_get_allocated_width ( GTK_WIDGET ( gl_area ) ) ;
2019-09-22 22:47:42 +00:00
int win_height = gtk_widget_get_allocated_height ( GTK_WIDGET ( gl_area ) ) ;
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
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 true if the application should show a menubar
static gboolean show_menubar ( void ) {
2019-09-25 20:47:33 +00:00
switch ( get_show_menubar ( ) ) {
case MENUBAR_AUTO : {
GtkSettings * settings = gtk_settings_get_default ( ) ;
gboolean result ;
2020-04-11 16:24:55 +00:00
2019-09-25 20:47:33 +00:00
g_object_get ( settings , " gtk-shell-shows-menubar " , & result , NULL ) ;
2020-04-11 16:24:55 +00:00
2019-09-25 20:47:33 +00:00
return result ;
}
case MENUBAR_SHOW : return true ;
case MENUBAR_HIDE : return false ;
}
2020-04-11 16:24:55 +00:00
}
// 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 ;
}
2020-04-11 16:24:55 +00:00
// app.quit GAction
// Exits the application
static void activate_quit ( GSimpleAction * action , GVariant * parameter , gpointer user_data ) {
2019-09-23 14:35:10 +00:00
quit ( G_APPLICATION ( user_data ) ) ;
2020-04-11 16:24:55 +00:00
}
// 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 ) ) ;
}
2019-09-23 22:34:21 +00:00
// app.open_gtk_debugger GAction
// Opens the GTK debugger
static void activate_open_gtk_debugger ( GSimpleAction * action , GVariant * parameter , gpointer user_data ) {
gtk_window_set_interactive_debugging ( true ) ;
}
// app.preferences GAction
// Opens the preferences window
static void activate_preferences ( GSimpleAction * action , GVariant * parameter , gpointer user_data ) {
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
static void activate_open_vram_viewer ( GSimpleAction * action , GVariant * parameter , gpointer user_data ) {
gtk_widget_show_all ( GTK_WIDGET ( vram_viewer ) ) ;
}
// app.open_memory_viewer GAction
// Opens the memory viewer window
static void activate_open_memory_viewer ( GSimpleAction * action , GVariant * parameter , gpointer user_data ) {
gtk_widget_show_all ( GTK_WIDGET ( memory_viewer ) ) ;
}
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-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-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-22 22:47:42 +00:00
G_MODULE_EXPORT void gl_init ( ) {
2020-04-10 01:22:16 +00:00
const char * renderer ;
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 " ) ;
2019-09-25 00:48:07 +00:00
if ( config . shader = = NULL | | ! init_shader_with_name ( & shader , config . shader ) ) {
2020-04-10 01:22:16 +00:00
init_shader_with_name ( & shader , " NearestNeighbor " ) ;
}
}
2019-09-22 22:47:42 +00:00
G_MODULE_EXPORT void gl_resize ( ) {
update_viewport ( ) ;
}
G_MODULE_EXPORT void gl_draw ( ) {
render_texture ( get_current_buffer ( ) , get_previous_buffer ( ) ) ;
}
G_MODULE_EXPORT void gl_finish ( ) { }
2019-09-25 20:47:33 +00:00
G_MODULE_EXPORT void on_vram_viewer_realize ( gpointer visible ) {
vram_viewer_visible = true ;
}
G_MODULE_EXPORT void on_vram_viewer_unrealize ( gpointer visible ) {
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 ;
}
G_MODULE_EXPORT gboolean on_vram_viewer_tilemap ( 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 * ) 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 ) ;
g_print ( " New value: %s \n " , gtk_combo_box_get_active_id ( box ) ) ;
}
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 ;
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 ) ;
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 ) ) ;
2019-09-25 20:47:33 +00:00
init_settings ( user_data - > config_path , preferences ) ;
vram_viewer = GTK_WINDOW ( get_object ( " vram_viewer " ) ) ;
2020-04-11 16:24:55 +00:00
set_combo_box_row_separator_func ( GTK_CONTAINER ( vram_viewer ) ) ;
2019-09-25 20:47:33 +00:00
memory_viewer = GTK_WINDOW ( get_object ( " memory_viewer " ) ) ;
set_combo_box_row_separator_func ( GTK_CONTAINER ( 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 ) ) ) ;
gtk_application_window_set_show_menubar ( main_window , true ) ;
2020-04-11 16:24:55 +00:00
2019-09-22 22:47:42 +00:00
// create our renderer area
gl_area = GTK_GL_AREA ( gtk_gl_area_new ( ) ) ;
gtk_gl_area_set_auto_render ( gl_area , false ) ;
2019-09-25 20:47:33 +00:00
// Connect signal handlers
g_signal_connect ( gl_area , " realize " , G_CALLBACK ( gl_init ) , NULL ) ;
g_signal_connect ( gl_area , " render " , G_CALLBACK ( gl_draw ) , NULL ) ;
g_signal_connect ( gl_area , " resize " , G_CALLBACK ( gl_resize ) , NULL ) ;
2019-09-22 22:47:42 +00:00
g_signal_connect ( gl_area , " unrealize " , G_CALLBACK ( gl_finish ) , NULL ) ;
2019-09-24 18:22:07 +00:00
2019-09-25 20:47:33 +00:00
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_vram_viewer_tilemap ) , NULL ) ;
2019-09-22 22:47:42 +00:00
gtk_container_add ( GTK_CONTAINER ( main_window ) , GTK_WIDGET ( gl_area ) ) ;
2020-04-11 16:24:55 +00:00
2019-09-22 22:47:42 +00:00
// Handle the whole menubar situation …
if ( show_menubar ( ) ) {
// Show a classic menubar
2020-04-11 16:24:55 +00:00
GMenuModel * menubar = get_menu_model ( app , " menubar " ) ;
gtk_application_set_menubar ( GTK_APPLICATION ( app ) , menubar ) ;
}
else {
2019-09-22 22:47:42 +00:00
// Attach a custom title bar
GtkWidget * titlebar = GTK_WIDGET ( gtk_builder_get_object ( builder , " main_header_bar " ) ) ;
gtk_window_set_titlebar ( GTK_WINDOW ( main_window ) , titlebar ) ;
2020-04-11 16:24:55 +00:00
// 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 ) ;
}
2019-09-25 20:47:33 +00:00
gtk_window_set_title ( GTK_WINDOW ( main_window ) , " SameBoy " ) ;
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-23 14:35:10 +00:00
g_signal_connect ( main_window , " destroy " , G_CALLBACK ( on_quit ) , app ) ;
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-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 ( ) ) ;
2020-04-10 01:22:16 +00:00
GB_set_rgb_encode_callback ( & gb , rgb_encode ) ;
// 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 ) ;
2020-04-10 01:22:16 +00:00
// GB_set_update_input_hint_callback(&gb, handle_events);
// 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-26 18:31:58 +00:00
if ( user_data - > boot_rom_path ) {
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-26 18:31:58 +00:00
if ( config . boot_rom_path ! = NULL ) {
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
if ( user_data - > file ! = NULL ) {
GB_load_rom ( & gb , g_file_get_path ( user_data - > file ) ) ;
}
/* Run emulation */
2019-09-23 14:35:10 +00:00
while ( running ) {
2020-04-10 01:22:16 +00:00
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 ) ;
}
}
2019-09-26 18:31:58 +00:00
// Quit our application properly.
// This fires the “shutdown” signal.
g_application_quit ( app ) ;
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-23 14:35:10 +00:00
main_application = gtk_application_new ( APP_ID , G_APPLICATION_HANDLES_OPEN ) ;
2020-04-11 16:24:55 +00:00
UserData user_data = { NULL } ;
// 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 ;
}