2019-09-30 14:40:55 +00:00
# include "main.h"
2020-04-28 16:36:47 +00:00
# include "audio/audio.h"
2019-09-22 22:47:42 +00:00
2020-04-10 01:25:53 +00:00
# ifndef _WIN32
# define DEFAULT_AUDIO_SAMPLE_RATE 96000
# else
/* Windows (well, at least my VM) can't handle 96KHz sound well :( */
/* felsqualle says: For SDL 2.0.6+ using the WASAPI driver, the highest freq.
we can get is 48000. 96000 also works , but always has some faint crackling in
the audio , no matter how high or low I set the buffer length . . .
Not quite satisfied with that solution , because acc . to SDL2 docs ,
96 k + WASAPI * should * work . */
# define DEFAULT_AUDIO_SAMPLE_RATE 48000
# endif
/* Compatibility with older SDL versions */
# ifndef SDL_AUDIO_ALLOW_SAMPLES_CHANGE
# define SDL_AUDIO_ALLOW_SAMPLES_CHANGE 0
# endif
static SDL_GameController * controller = NULL ;
static SDL_AudioSpec want_aspec , have_aspec ;
static SDL_AudioDeviceID device_id ;
2020-04-10 02:10:28 +00:00
static const GThread * main_thread ;
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-10-14 14:01:51 +00:00
static GuiData gui_data = { { NULL } , 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 ;
2019-10-12 21:11:26 +00:00
static bool is_fullscreen ;
2019-09-29 23:49:41 +00:00
2020-04-10 01:22:16 +00:00
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-10-03 01:10:48 +00:00
static Rect viewport = { 0 } ;
static Rect scrollRect = { 0 } ;
2019-09-25 20:47:33 +00:00
static bool vram_viewer_visible = false ;
2019-10-14 19:28:39 +00:00
static bool vram_viewer_updating = false ;
static gchar * vram_viewer_active_tab = " " ;
static gboolean vram_viewer_is_cgb = false ;
static uint8_t vram_viewer_palette_data [ 16 ] [ 0x40 ] ;
2020-04-10 19:42:03 +00:00
static bool border_mode_changed = false ;
2019-10-12 21:11:26 +00:00
2020-01-31 00:53:02 +00:00
static volatile bool running = false ;
static volatile bool stopping = false ;
2019-09-23 14:35:10 +00:00
2019-10-01 17:56:53 +00:00
# define tileset_buffer_length 256 * 192 * 4
2019-09-25 20:47:33 +00:00
static uint32_t tileset_buffer [ tileset_buffer_length ] = { 0 } ;
2020-04-11 23:35:15 +00:00
static GMutex tileset_buffer_mutex ;
2019-09-25 20:47:33 +00:00
2019-10-01 17:56:53 +00:00
# define tilemap_buffer_length 256 * 256 * 4
2019-09-25 20:47:33 +00:00
static uint32_t tilemap_buffer [ tilemap_buffer_length ] = { 0 } ;
2020-04-11 23:35:15 +00:00
static GMutex tilemap_buffer_mutex ;
2019-09-25 20:47:33 +00:00
2019-10-03 23:40:50 +00:00
static GB_oam_info_t oamInfo [ 40 ] ;
static uint16_t oamCount ;
static uint8_t oamHeight ;
2019-09-30 14:40:55 +00:00
static uint8_t pressed_buttons ;
2020-04-10 01:22:16 +00:00
2020-04-10 02:10:28 +00:00
static GtkTextBuffer * pending_console_output = NULL ;
2019-10-14 21:46:03 +00:00
static gboolean in_sync_input = false ;
static gchar * last_console_input = NULL ;
2020-04-10 02:10:28 +00:00
static gboolean log_to_sidebar = false ;
static gboolean should_clear_sidebar = false ;
2019-10-14 21:46:03 +00:00
2019-10-07 22:36:16 +00:00
static GMutex debugger_input_mutex ;
static GCond debugger_input_cond ;
2020-04-10 02:10:28 +00:00
static GRecMutex console_output_lock ;
2019-10-07 22:36:16 +00:00
static GPtrArray * debugger_input_queue ;
2020-04-11 22:38:18 +00:00
static const GActionEntry file_entries [ ] = {
2019-09-30 14:40:55 +00:00
{ " open " , activate_open , NULL , NULL , NULL } ,
2020-04-11 22:38:18 +00:00
{ " close " , activate_close , NULL , NULL , NULL } ,
} ;
static const GActionEntry edit_entries [ ] = {
2020-04-28 16:36:47 +00:00
2020-04-11 22:38:18 +00:00
} ;
static const GActionEntry emulation_entries [ ] = {
{ " reset " , activate_reset , NULL , NULL , NULL } ,
{ " pause " , NULL , NULL , " false " , on_pause_changed } ,
{ " save_state " , NULL , NULL , NULL , NULL } ,
{ " load_state " , NULL , NULL , NULL , NULL } ,
} ;
static const GActionEntry developer_entries [ ] = {
2019-10-07 22:36:16 +00:00
{ " show_console " , activate_show_console , NULL , NULL , NULL } ,
2019-09-30 14:40:55 +00:00
{ " open_gtk_debugger " , activate_open_gtk_debugger , NULL , NULL , NULL } ,
{ " open_memory_viewer " , activate_open_memory_viewer , NULL , NULL , NULL } ,
2019-10-07 22:36:16 +00:00
{ " open_vram_viewer " , activate_open_vram_viewer , NULL , NULL , NULL } ,
2019-10-12 21:11:26 +00:00
{ " toggle_developer_mode " , NULL , NULL , " false " , NULL } ,
2020-04-10 02:10:28 +00:00
{ " clear_console " , activate_clear_console , NULL , NULL , NULL } ,
2019-09-30 14:40:55 +00:00
} ;
2019-09-22 22:47:42 +00:00
2020-04-11 22:38:18 +00:00
static const GActionEntry app_entries [ ] = {
{ " quit " , activate_quit , NULL , NULL , NULL } ,
{ " about " , activate_about , NULL , NULL , NULL } ,
{ " preferences " , activate_preferences , NULL , NULL , NULL } ,
{ " toggle_mute " , NULL , NULL , " false " , on_mute_changed } ,
{ " change_model " , NULL , " s " , " @s 'CGB' " , on_model_changed } ,
} ;
2019-09-30 14:40:55 +00:00
int main ( int argc , char * argv [ ] ) {
2020-04-10 02:10:28 +00:00
main_thread = g_thread_self ( ) ;
2019-10-14 14:01:51 +00:00
// initialize GB_model_t to invalid value
gui_data . cli_options . model = - 1 ;
gui_data . prev_model = - 1 ;
2019-09-30 14:40:55 +00:00
// Create our GApplication and tell GTK that we are able to handle files
main_application = gtk_application_new ( APP_ID , G_APPLICATION_NON_UNIQUE | G_APPLICATION_HANDLES_OPEN ) ;
2020-04-10 01:22:16 +00:00
2019-09-30 14:40:55 +00:00
// 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 } ,
2019-10-14 14:01:51 +00:00
{ " bootrom " , ' b ' , G_OPTION_FLAG_NONE , G_OPTION_ARG_STRING , & gui_data . cli_options . boot_rom_path , " Path to the boot ROM to use " , " <file path> " } ,
2019-09-30 14:40:55 +00:00
{ " model " , ' m ' , G_OPTION_FLAG_NONE , G_OPTION_ARG_STRING , NULL , " Override the model type to emulate " , " <model type> " } ,
2019-10-14 14:01:51 +00:00
{ " config " , ' c ' , G_OPTION_FLAG_NONE , G_OPTION_ARG_STRING , & gui_data . cli_options . config_path , " Override the path of the configuration file " , " <file path> " } ,
2019-09-30 14:40:55 +00:00
{ NULL }
} ;
// Setup our command line information
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-10 01:22:16 +00:00
2019-09-30 14:40:55 +00:00
// Add signal handlers
2019-10-12 21:11:26 +00:00
g_signal_connect ( main_application , " handle-local-options " , G_CALLBACK ( handle_local_options ) , & gui_data ) ;
g_signal_connect ( main_application , " startup " , G_CALLBACK ( startup ) , & gui_data ) ;
g_signal_connect ( main_application , " activate " , G_CALLBACK ( activate ) , & gui_data ) ;
g_signal_connect ( main_application , " open " , G_CALLBACK ( open ) , & gui_data ) ;
g_signal_connect ( main_application , " shutdown " , G_CALLBACK ( shutdown ) , & gui_data ) ;
2019-09-22 22:47:42 +00:00
2019-09-30 14:40:55 +00:00
// Start our GApplication main loop
int status = g_application_run ( G_APPLICATION ( main_application ) , argc , argv ) ;
g_object_unref ( main_application ) ;
2019-09-28 21:34:18 +00:00
2019-09-30 14:40:55 +00:00
return status ;
2019-09-28 21:34:18 +00:00
}
2019-09-30 14:40:55 +00:00
// This function gets called after the parsing of the commandline options has occurred.
2019-10-12 21:11:26 +00:00
static gint handle_local_options ( GApplication * app , GVariantDict * options , gpointer gui_data_gptr ) {
GuiData * gui_data = gui_data_gptr ;
2019-09-30 14:40:55 +00:00
guint32 count ;
2019-09-22 22:47:42 +00:00
2019-09-30 14:40:55 +00:00
if ( g_variant_dict_lookup ( options , " version " , " b " , & count ) ) {
2020-04-10 02:07:01 +00:00
g_message ( " SameBoy v " xstr ( VERSION ) ) ;
2019-09-30 14:40:55 +00:00
return EXIT_SUCCESS ;
2019-09-29 23:49:41 +00:00
}
2019-09-25 20:47:33 +00:00
2019-09-30 14:40:55 +00:00
if ( g_variant_dict_lookup ( options , " fullscreen " , " b " , & count ) ) {
2019-10-14 14:01:51 +00:00
gui_data - > cli_options . fullscreen = true ;
2019-09-25 20:47:33 +00:00
}
2019-09-22 22:47:42 +00:00
2019-09-30 14:40:55 +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 ) ;
2019-09-22 22:47:42 +00:00
2019-09-30 14:40:55 +00:00
// TODO: Synchronize with GB_model_t (Core/gb.h)
if ( g_str_has_prefix ( model_name , " DMG " ) ) {
2019-10-14 22:16:59 +00:00
gui_data - > cli_options . prefix = " DMG " ;
2019-09-30 14:40:55 +00:00
if ( g_str_has_suffix ( model_name , " -B " ) | | g_strcmp0 ( model_name , " DMG " ) = = 0 ) {
2019-10-14 14:01:51 +00:00
gui_data - > cli_options . model = GB_MODEL_DMG_B ;
2019-09-30 14:40:55 +00:00
}
else {
2019-10-14 14:01:51 +00:00
gui_data - > cli_options . model = GB_MODEL_DMG_B ;
2020-04-10 02:07:01 +00:00
g_warning ( " Unsupported revision: %s \n Falling back to DMG-B " , model_name ) ;
2019-09-30 14:40:55 +00:00
}
}
else if ( g_str_has_prefix ( model_name , " SGB " ) ) {
2019-10-14 22:16:59 +00:00
gui_data - > cli_options . prefix = " SGB " ;
2019-09-30 14:40:55 +00:00
if ( g_str_has_suffix ( model_name , " -NTSC " ) | | g_strcmp0 ( model_name , " SGB " ) = = 0 ) {
2019-10-14 14:01:51 +00:00
gui_data - > cli_options . model = GB_MODEL_SGB ;
2019-09-30 14:40:55 +00:00
}
else if ( g_str_has_suffix ( model_name , " -PAL " ) ) {
2019-10-14 14:01:51 +00:00
gui_data - > cli_options . model = GB_MODEL_SGB | GB_MODEL_PAL_BIT ;
2019-09-30 14:40:55 +00:00
}
else if ( g_str_has_suffix ( model_name , " 2 " ) ) {
2019-10-14 14:01:51 +00:00
gui_data - > cli_options . model = GB_MODEL_SGB2 ;
2019-09-30 14:40:55 +00:00
}
else {
2019-10-14 14:01:51 +00:00
gui_data - > cli_options . model = GB_MODEL_SGB2 ;
2020-04-10 02:07:01 +00:00
g_warning ( " Unsupported revision: %s \n Falling back to SGB2 " , model_name ) ;
2019-09-30 14:40:55 +00:00
}
}
else if ( g_str_has_prefix ( model_name , " CGB " ) ) {
2019-10-14 22:16:59 +00:00
gui_data - > cli_options . prefix = " CGB " ;
2019-09-30 14:40:55 +00:00
if ( g_str_has_suffix ( model_name , " -C " ) ) {
2019-10-14 14:01:51 +00:00
gui_data - > cli_options . model = GB_MODEL_CGB_C ;
2019-09-30 14:40:55 +00:00
}
else if ( g_str_has_suffix ( model_name , " -E " ) | | g_strcmp0 ( model_name , " CGB " ) = = 0 ) {
2019-10-14 14:01:51 +00:00
gui_data - > cli_options . model = GB_MODEL_CGB_E ;
2019-09-30 14:40:55 +00:00
}
else {
2019-10-14 14:01:51 +00:00
gui_data - > cli_options . model = GB_MODEL_CGB_E ;
2020-04-10 02:07:01 +00:00
g_warning ( " Unsupported revision: %s \n Falling back to CGB-E " , model_name ) ;
2019-09-30 14:40:55 +00:00
}
}
else if ( g_str_has_prefix ( model_name , " AGB " ) ) {
2019-10-14 22:16:59 +00:00
gui_data - > cli_options . prefix = " AGB " ;
2019-10-14 14:01:51 +00:00
gui_data - > cli_options . model = GB_MODEL_AGB ;
2019-09-24 18:22:07 +00:00
}
else {
2019-10-14 22:16:59 +00:00
gui_data - > cli_options . prefix = NULL ;
2020-04-10 02:07:01 +00:00
g_warning ( " Unknown model: %s " , model_name ) ;
2019-09-30 14:40:55 +00:00
exit ( EXIT_FAILURE ) ;
2019-09-24 18:22:07 +00:00
}
2019-09-22 22:47:42 +00:00
}
2019-09-30 14:40:55 +00:00
return - 1 ;
2020-04-10 01:22:16 +00:00
}
2019-09-30 14:40:55 +00:00
// Workaround to figure out if we have proper OpenGL support.
// Otherwise the application would crash after our GtkGlArea is realized
// and the context it uses is a legacy OpenGL 1.4 context because
// GTK3 calls OpenGL 2.0+ functions on it.
gboolean test_gl_support ( void ) {
2020-04-11 23:35:15 +00:00
gboolean result = false ;
2020-04-11 16:24:55 +00:00
2019-10-12 21:11:26 +00:00
GtkWidget * window = gtk_window_new ( GTK_WINDOW_TOPLEVEL ) ;
2019-09-30 14:40:55 +00:00
g_signal_connect ( window , " realize " , G_CALLBACK ( gl_check_realize ) , & result ) ;
gtk_widget_realize ( window ) ;
gtk_widget_destroy ( window ) ;
window = NULL ;
2020-04-11 16:24:55 +00:00
return result ;
}
2019-09-30 14:40:55 +00:00
// The main function for the OpenGL version check workaround
void gl_check_realize ( GtkWidget * w , gpointer user_data_gptr ) {
gboolean * result = ( gboolean * ) user_data_gptr ;
2020-04-11 16:24:55 +00:00
2019-09-30 14:40:55 +00:00
GError * error = NULL ;
GdkWindow * gdk_window = gtk_widget_get_window ( w ) ;
GdkGLContext * context = gdk_window_create_gl_context ( gdk_window , & error ) ;
2020-04-11 16:24:55 +00:00
2019-09-30 14:40:55 +00:00
if ( error ! = NULL ) {
2020-04-10 02:07:01 +00:00
g_warning ( " Failed to create context: %s " , error - > message ) ;
2019-09-30 14:40:55 +00:00
g_error_free ( error ) ;
2020-04-11 23:35:15 +00:00
* result = false ;
2020-04-11 16:24:55 +00:00
}
2019-09-30 14:40:55 +00:00
else {
gdk_gl_context_make_current ( context ) ;
int version = epoxy_gl_version ( ) ;
2020-04-10 02:10:28 +00:00
2019-09-30 14:40:55 +00:00
g_object_run_dispose ( G_OBJECT ( context ) ) ;
g_object_unref ( context ) ;
context = NULL ;
2020-04-11 16:24:55 +00:00
2019-09-30 14:40:55 +00:00
gdk_gl_context_clear_current ( ) ;
2020-04-10 02:07:01 +00:00
g_debug ( " OpenGL version: %d " , version ) ;
2019-09-30 14:40:55 +00:00
* result = version > = 32 ;
}
2020-04-11 16:24:55 +00:00
}
2020-04-10 01:25:53 +00:00
static gboolean init_controllers ( ) {
if ( SDL_InitSubSystem ( SDL_INIT_GAMECONTROLLER ) < 0 ) {
2020-04-10 02:07:01 +00:00
g_warning ( " Failed to initialize game controller support: %s " , SDL_GetError ( ) ) ;
2020-04-11 23:35:15 +00:00
return false ;
2020-04-10 01:25:53 +00:00
}
SDL_QuitSubSystem ( SDL_INIT_EVENTS ) ;
SDL_GameControllerEventState ( SDL_IGNORE ) ;
GError * error = NULL ;
GBytes * db_f = g_resources_lookup_data ( RESOURCE_PREFIX " gamecontrollerdb.txt " , G_RESOURCE_LOOKUP_FLAGS_NONE , & error ) ;
if ( db_f ! = NULL ) {
gsize db_data_size = 0 ;
const guchar * db_data = g_bytes_get_data ( db_f , & db_data_size ) ;
const gint val = SDL_GameControllerAddMappingsFromRW ( SDL_RWFromMem ( ( void * ) db_data , db_data_size ) , 1 ) ;
if ( val < 0 ) {
2020-04-10 02:07:01 +00:00
g_warning ( " Failed to load controller mappings: %s " , SDL_GetError ( ) ) ;
2020-04-10 01:25:53 +00:00
}
2020-04-10 02:10:28 +00:00
2020-04-10 01:25:53 +00:00
g_bytes_unref ( db_f ) ;
}
if ( error ! = NULL ) g_clear_error ( & error ) ;
// Open the first available controller
for ( int i = 0 ; i < SDL_NumJoysticks ( ) ; + + i ) {
if ( SDL_IsGameController ( i ) ) {
controller = SDL_GameControllerOpen ( i ) ;
if ( controller ) {
break ;
}
else {
2020-04-10 02:07:01 +00:00
g_warning ( " Could not open gamecontroller %i: %s " , i , SDL_GetError ( ) ) ;
2020-04-10 01:25:53 +00:00
}
}
}
2020-04-11 23:35:15 +00:00
return true ;
2020-04-10 01:25:53 +00:00
}
2019-10-14 14:01:51 +00:00
static GB_model_t get_model ( ) {
if ( gui_data . cli_options . model ! = - 1 ) {
return gui_data . cli_options . model ;
}
GAction * action = g_action_map_lookup_action ( G_ACTION_MAP ( main_application ) , " change_model " ) ;
GVariant * value = g_action_get_state ( action ) ;
const gchar * family = g_variant_get_string ( value , NULL ) ;
if ( g_strcmp0 ( family , " DMG " ) = = 0 ) {
return get_dmg_model ( ) ;
}
else if ( g_strcmp0 ( family , " AGB " ) = = 0 ) {
return GB_MODEL_AGB ;
}
else if ( g_strcmp0 ( family , " SGB " ) = = 0 ) {
return get_sgb_model ( ) ;
}
2020-04-10 02:10:28 +00:00
2019-10-14 14:01:51 +00:00
return get_cgb_model ( ) ;
}
2020-04-10 01:25:53 +00:00
static void gb_audio_callback ( GB_gameboy_t * gb , GB_sample_t * sample ) {
if ( turbo_down ) {
static unsigned skip = 0 ;
skip + + ;
2020-04-28 16:36:47 +00:00
if ( skip = = GB_audio_get_frequency ( ) / 8 ) {
2020-04-10 01:25:53 +00:00
skip = 0 ;
}
2020-04-28 16:36:47 +00:00
if ( skip > GB_audio_get_frequency ( ) / 16 ) {
2020-04-10 01:25:53 +00:00
return ;
}
}
2020-04-28 16:36:47 +00:00
if ( GB_audio_get_queue_length ( ) / sizeof ( * sample ) > GB_audio_get_frequency ( ) / 4 ) {
2020-04-10 01:25:53 +00:00
return ;
}
2020-04-28 16:36:47 +00:00
GB_audio_queue_sample ( sample ) ;
2020-04-10 01:25:53 +00:00
}
2020-04-10 02:10:28 +00:00
// Console TODO:
// TODO: clear sidebar when switching to async mode
// TODO: Command history (up / down arrow in input)
// TODO: reverse search of commands
// TODO: search in output
2019-10-07 22:36:16 +00:00
static char * sync_console_input ( GB_gameboy_t * gb ) {
2020-04-10 02:10:28 +00:00
update_debugger_sidebar ( gb ) ;
2019-10-14 21:46:03 +00:00
console_log ( gb , " > " , 0 ) ;
in_sync_input = true ;
2019-10-12 21:11:26 +00:00
2019-10-07 22:36:16 +00:00
g_mutex_lock ( & debugger_input_mutex ) ;
g_cond_wait ( & debugger_input_cond , & debugger_input_mutex ) ;
gchar * input = NULL ;
const gchar * _input = g_ptr_array_index ( debugger_input_queue , 0 ) ;
input = g_strdup ( _input ) ;
gpointer ptr = g_ptr_array_remove_index ( debugger_input_queue , 0 ) ;
if ( ptr ) g_free ( ptr ) ;
g_mutex_unlock ( & debugger_input_mutex ) ;
2019-10-14 21:46:03 +00:00
in_sync_input = false ;
2019-10-07 22:36:16 +00:00
return input ;
}
2020-01-31 00:53:02 +00:00
static void clear_sidebar ( ) {
GtkTextView * sidebar_output = builder_get ( GTK_TEXT_VIEW , " console_sidebar_output " ) ;
GtkTextBuffer * sidebar_output_text_buf = gtk_text_view_get_buffer ( sidebar_output ) ;
gtk_text_buffer_set_text ( sidebar_output_text_buf , " " , - 1 ) ;
}
2019-10-07 22:36:16 +00:00
static char * async_console_input ( GB_gameboy_t * gb ) {
2020-01-31 00:53:02 +00:00
// TODO: This is rather ugly
g_idle_add ( ( GSourceFunc ) clear_sidebar , NULL ) ;
2019-10-07 22:36:16 +00:00
if ( debugger_input_queue - > len = = 0 ) return NULL ;
g_mutex_lock ( & debugger_input_mutex ) ;
gchar * input = NULL ;
const gchar * _input = g_ptr_array_index ( debugger_input_queue , 0 ) ;
if ( _input ) {
input = g_strdup ( _input ) ;
gpointer ptr = g_ptr_array_remove_index ( debugger_input_queue , 0 ) ;
if ( ptr ) g_free ( ptr ) ;
}
g_mutex_unlock ( & debugger_input_mutex ) ;
return input ;
}
2020-04-10 02:10:28 +00:00
static void update_debugger_sidebar ( GB_gameboy_t * gb ) {
if ( ! GB_debugger_is_stopped ( gb ) ) {
return ;
}
2019-10-07 22:36:16 +00:00
2020-04-10 02:10:28 +00:00
if ( main_thread ! = g_thread_self ( ) ) {
g_idle_add ( ( GSourceFunc ) update_debugger_sidebar , gb ) ;
return ;
}
g_rec_mutex_lock ( & console_output_lock ) ;
should_clear_sidebar = true ;
append_pending_output ( ) ;
log_to_sidebar = true ;
g_rec_mutex_unlock ( & console_output_lock ) ;
GtkTextView * sidebar_input = builder_get ( GTK_TEXT_VIEW , " console_sidebar_input " ) ;
GtkTextBuffer * sidebar_input_text_buf = gtk_text_view_get_buffer ( sidebar_input ) ;
2019-10-07 22:36:16 +00:00
2020-04-10 02:10:28 +00:00
gint line_count = gtk_text_buffer_get_line_count ( sidebar_input_text_buf ) ;
for ( unsigned line = 0 ; line < line_count ; + + line ) {
GtkTextIter start_iter ;
GtkTextIter end_iter ;
gunichar ch ;
gtk_text_buffer_get_iter_at_line ( sidebar_input_text_buf , & start_iter , line ) ;
end_iter = start_iter ;
do {
ch = gtk_text_iter_get_char ( & end_iter ) ;
if ( ! gtk_text_iter_forward_char ( & end_iter ) ) {
break ;
}
}
while ( ch ! = ' \n ' ) ;
gchar * cmd = gtk_text_buffer_get_text ( sidebar_input_text_buf , & start_iter , & end_iter , false ) ;
g_strchug ( cmd ) ; // trim leading whitespace
g_strchomp ( cmd ) ; // trim trailing whitespace
if ( g_strcmp0 ( " " , cmd ) ! = 0 ) {
char * duped = g_strdup ( cmd ) ;
GB_attributed_log ( gb , GB_LOG_BOLD , " %s: \n " , duped ) ;
GB_debugger_execute_command ( gb , duped ) ;
GB_log ( gb , " \n " ) ;
g_free ( duped ) ;
}
g_free ( cmd ) ;
2019-10-07 22:36:16 +00:00
}
2020-04-10 02:10:28 +00:00
g_rec_mutex_lock ( & console_output_lock ) ;
append_pending_output ( ) ;
log_to_sidebar = false ;
g_rec_mutex_unlock ( & console_output_lock ) ;
}
static gboolean scroll_to_bottom ( GtkTextView * textview , GtkTextMark * mark ) {
GtkTextBuffer * buffer = gtk_text_view_get_buffer ( textview ) ;
GtkTextIter iter ;
gtk_text_buffer_get_end_iter ( buffer , & iter ) ;
gtk_text_iter_set_line_offset ( & iter , 0 ) ;
gtk_text_buffer_move_mark ( buffer , mark , & iter ) ;
2020-04-11 23:35:15 +00:00
gtk_text_view_scroll_to_mark ( textview , mark , 0.0 , true , 0.0 , 0.10 ) ;
2020-04-10 02:10:28 +00:00
gtk_text_buffer_delete_mark ( buffer , mark ) ;
2020-04-11 23:35:15 +00:00
return true ;
2020-04-10 02:10:28 +00:00
}
static void append_pending_output ( void ) {
g_rec_mutex_lock ( & console_output_lock ) ;
if ( should_clear_sidebar ) {
2020-01-31 00:53:02 +00:00
clear_sidebar ( ) ;
2020-04-11 23:35:15 +00:00
should_clear_sidebar = false ;
2019-10-07 22:36:16 +00:00
}
2020-04-10 02:10:28 +00:00
if ( gtk_text_buffer_get_char_count ( pending_console_output ) > 0 ) {
GtkTextView * text_view = builder_get ( GTK_TEXT_VIEW , log_to_sidebar ? " console_sidebar_output " : " console_screen " ) ;
GtkTextBuffer * text_buf = gtk_text_view_get_buffer ( text_view ) ;
GtkTextIter start ;
GtkTextIter end ;
gtk_text_buffer_get_start_iter ( pending_console_output , & start ) ;
gtk_text_buffer_get_end_iter ( pending_console_output , & end ) ;
GtkTextIter iter ;
gtk_text_buffer_get_end_iter ( text_buf , & iter ) ;
gtk_text_buffer_insert_range ( text_buf , & iter , & start , & end ) ;
2020-04-11 23:35:15 +00:00
scroll_to_bottom ( text_view , gtk_text_buffer_create_mark ( text_buf , NULL , & iter , true ) ) ;
2020-04-10 02:10:28 +00:00
gtk_text_buffer_set_text ( pending_console_output , " " , - 1 ) ;
2019-10-07 22:36:16 +00:00
}
2020-04-10 02:10:28 +00:00
g_rec_mutex_unlock ( & console_output_lock ) ;
2019-10-07 22:36:16 +00:00
}
static void console_log ( GB_gameboy_t * gb , const char * string , GB_log_attributes attributes ) {
2020-04-10 02:10:28 +00:00
g_rec_mutex_lock ( & console_output_lock ) ;
2019-10-07 22:36:16 +00:00
if ( string ! = NULL & & ! g_str_equal ( " " , string ) ) {
2020-04-10 02:10:28 +00:00
GtkTextIter iter ;
GtkTextIter start ;
// Append attributed text to "pending_console_output" GtkTextBuffer
gtk_text_buffer_get_end_iter ( pending_console_output , & iter ) ;
2020-04-11 23:35:15 +00:00
GtkTextMark * start_mark = gtk_text_buffer_create_mark ( pending_console_output , NULL , & iter , true ) ;
2020-04-10 02:10:28 +00:00
gtk_text_buffer_insert ( pending_console_output , & iter , g_strdup ( string ) , - 1 ) ;
gtk_text_buffer_get_iter_at_mark ( pending_console_output , & start , start_mark ) ;
if ( attributes & GB_LOG_BOLD ) {
gtk_text_buffer_apply_tag_by_name ( pending_console_output , " bold " , & start , & iter ) ;
}
if ( attributes & GB_LOG_DASHED_UNDERLINE ) {
gtk_text_buffer_apply_tag_by_name ( pending_console_output , " dashed_underline " , & start , & iter ) ;
}
if ( attributes & GB_LOG_UNDERLINE ) {
gtk_text_buffer_apply_tag_by_name ( pending_console_output , " underline " , & start , & iter ) ;
}
gtk_text_buffer_delete_mark ( pending_console_output , start_mark ) ;
2019-10-07 22:36:16 +00:00
2020-04-10 02:10:28 +00:00
g_idle_add ( ( GSourceFunc ) append_pending_output , NULL ) ;
2019-10-07 22:36:16 +00:00
}
2020-04-10 02:10:28 +00:00
g_rec_mutex_unlock ( & console_output_lock ) ;
2019-10-07 22:36:16 +00:00
}
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-30 14:40:55 +00:00
// Create a GtkDrawingArea as a fallback in case we can’ t use OpenGL
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 ) ;
2020-04-11 23:35:15 +00:00
gtk_box_pack_end ( GTK_BOX ( main_window_container ) , GTK_WIDGET ( fallback_canvas ) , true , true , 0 ) ;
2019-09-23 14:35:10 +00:00
}
2019-09-30 14:40:55 +00:00
// Create our application’ s menu.
2020-04-10 02:10:28 +00:00
//
2019-09-30 14:40:55 +00:00
// This function tries to stick to the desktop environment’ s conventions.
// For the GNOME Shell it uses a hamburger menu, otherwise it either lets
// the desktop environment shell handle the menu if it signals support for it
// or uses a standard menubar inside the window.
static void setup_menu ( GApplication * app ) {
GMenuModel * menubar_model = get_menu_model ( app , " menubar " ) ;
enum menubar_type_t menubar_type = get_show_menubar ( ) ;
2020-04-11 16:24:55 +00:00
2019-09-30 14:40:55 +00:00
// 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 ) ;
2020-04-11 16:24:55 +00:00
2019-09-30 14:40:55 +00:00
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 " ) ;
2020-04-10 02:10:28 +00:00
2019-09-30 14:40:55 +00:00
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 ;
2019-09-23 22:34:21 +00:00
2020-04-10 02:07:01 +00:00
g_debug ( " XDG_CURRENT_DESKTOP: %s \n GDMSESSION: %s \n DESKTOP_SESSION: %s \n Chosen value: %s \n Show menu in shell: %d " , xdg_current_desktop , gdm_session , desktop_session , desktop , show_in_shell ) ;
2019-09-23 22:34:21 +00:00
2019-09-30 14:40:55 +00:00
if ( desktop ! = NULL & & show_in_shell ) {
menubar_type = MENUBAR_SHOW_IN_SHELL ;
}
2020-04-11 23:35:15 +00:00
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 ) ) {
2019-09-30 14:40:55 +00:00
menubar_type = MENUBAR_SHOW_IN_WINDOW ;
}
2020-04-11 23:35:15 +00:00
else if ( gdm_session ! = NULL & & ( g_str_match_string ( " gnome-classic " , gdm_session , false ) | | g_str_match_string ( " gnome-flashback " , gdm_session , false ) ) ) {
2019-09-30 14:40:55 +00:00
menubar_type = MENUBAR_SHOW_IN_WINDOW ;
}
else {
menubar_type = MENUBAR_SHOW_HAMBURGER ;
}
}
else {
menubar_type = MENUBAR_SHOW_IN_WINDOW ;
}
}
2019-09-25 20:47:33 +00:00
2019-09-30 14:40:55 +00:00
switch ( menubar_type ) {
case MENUBAR_AUTO :
2020-04-10 02:07:01 +00:00
g_warning ( " Unreachable " ) ;
2019-09-30 14:40:55 +00:00
break ;
2019-09-25 20:47:33 +00:00
2019-09-30 14:40:55 +00:00
case MENUBAR_SHOW_IN_SHELL :
2020-04-10 02:07:01 +00:00
g_debug ( " Showing menu in the shell " ) ;
2019-09-30 14:40:55 +00:00
gtk_application_set_menubar ( GTK_APPLICATION ( app ) , menubar_model ) ;
break ;
2019-09-28 21:34:18 +00:00
2019-09-30 14:40:55 +00:00
case MENUBAR_SHOW_IN_WINDOW : {
2020-04-10 02:07:01 +00:00
g_debug ( " Showing menu in the window " ) ;
2019-09-30 14:40:55 +00:00
GtkMenuBar * menubar = GTK_MENU_BAR ( gtk_menu_bar_new_from_model ( menubar_model ) ) ;
2020-04-11 23:35:15 +00:00
gtk_box_pack_start ( GTK_BOX ( main_window_container ) , GTK_WIDGET ( menubar ) , false , false , 0 ) ;
2019-09-30 14:40:55 +00:00
break ;
}
2019-09-28 21:34:18 +00:00
2019-09-30 14:40:55 +00:00
case MENUBAR_SHOW_HAMBURGER : {
2020-04-10 02:07:01 +00:00
g_debug ( " Showing hamburger " ) ;
2019-09-30 14:40:55 +00:00
// Attach a custom title bar
2019-10-03 01:13:53 +00:00
GtkWidget * titlebar = builder_get ( GTK_WIDGET , " main_header_bar " ) ;
2019-09-30 20:30:33 +00:00
gtk_header_bar_set_title ( GTK_HEADER_BAR ( titlebar ) , gtk_window_get_title ( GTK_WINDOW ( main_window ) ) ) ;
2019-09-30 14:40:55 +00:00
gtk_window_set_titlebar ( GTK_WINDOW ( main_window ) , titlebar ) ;
2020-04-11 16:24:55 +00:00
2019-09-30 14:40:55 +00:00
// Disable menubar
gtk_application_set_menubar ( GTK_APPLICATION ( app ) , NULL ) ;
2019-09-28 21:34:18 +00:00
2019-09-30 14:40:55 +00:00
// 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 ;
}
2019-09-28 21:34:18 +00:00
}
}
2019-09-30 14:40:55 +00:00
// 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 ) {
2019-10-12 21:11:26 +00:00
GList * children = gtk_container_get_children ( container ) ;
2019-09-30 14:40:55 +00:00
2019-10-12 21:11:26 +00:00
for ( GList * l = children ; l ; l = l - > next ) {
if ( GTK_IS_COMBO_BOX ( l - > data ) ) {
gtk_combo_box_set_row_separator_func ( GTK_COMBO_BOX ( l - > data ) , is_separator , NULL , NULL ) ;
2019-09-30 14:40:55 +00:00
}
2019-10-12 21:11:26 +00:00
if ( GTK_IS_CONTAINER ( l - > data ) ) {
set_combo_box_row_separator_func ( GTK_CONTAINER ( l - > data ) ) ;
2019-09-30 14:40:55 +00:00
}
}
2019-09-23 14:35:10 +00:00
2019-10-12 21:11:26 +00:00
g_list_free ( children ) ;
2020-04-11 16:24:55 +00:00
}
2019-09-30 14:40: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 ;
2020-04-10 01:22:16 +00:00
2019-09-30 14:40:55 +00:00
gtk_tree_model_get ( model , iter , 0 , & text , - 1 ) ;
gboolean result = g_strcmp0 ( " <separator> " , text ) = = 0 ;
2019-10-12 21:11:26 +00:00
g_free ( text ) ;
2020-04-10 01:22:16 +00:00
2019-09-30 14:40:55 +00:00
return result ;
}
2020-04-10 01:22:16 +00:00
2019-09-30 14:40:55 +00:00
// Determines how many frame buffers to use
static unsigned char number_of_buffers ( void ) {
2019-10-12 21:11:26 +00:00
if ( fallback_canvas ) return 2 ;
2020-04-10 15:45:35 +00:00
bool should_blend = get_frame_blending_mode ( ) ! = GB_FRAME_BLENDING_MODE_DISABLED ;
2019-09-29 23:49:41 +00:00
2019-09-30 14:40:55 +00:00
return should_blend ? 3 : 2 ;
2020-04-10 01:22:16 +00:00
}
2019-09-30 14:40:55 +00:00
// Returns the buffer that should be used by the Core to render a new frame to
static uint32_t * get_pixels ( void ) {
return image_buffers [ ( current_buffer + 1 ) % number_of_buffers ( ) ] ;
2019-09-22 22:47:42 +00:00
}
2019-09-30 14:40:55 +00:00
// Returns the current finished frame
static uint32_t * get_current_buffer ( void ) {
return image_buffers [ current_buffer ] ;
2019-09-29 23:49:41 +00:00
}
2019-09-30 14:40:55 +00:00
// Returns the previous finished frame
static uint32_t * get_previous_buffer ( void ) {
return image_buffers [ ( current_buffer + 2 ) % number_of_buffers ( ) ] ;
2019-09-22 22:47:42 +00:00
}
2019-09-30 14:40:55 +00:00
// Cycles the buffers
static void flip ( void ) {
current_buffer = ( current_buffer + 1 ) % number_of_buffers ( ) ;
}
2019-09-22 22:47:42 +00:00
2020-04-10 01:25:53 +00:00
static void quit_interrupt ( int ignored ) {
2020-04-11 22:38:18 +00:00
quit ( ) ;
}
// WHY DO WE NEED SUCH AN UGLY METHOD, GTK?!
static void action_entries_set_enabled ( const GActionEntry * entries , unsigned n_entries , bool value ) {
// Assumes null-terminated if n_entries == -1
for ( unsigned i = 0 ; n_entries = = - 1 ? entries [ i ] . name ! = NULL : i < n_entries ; i + + ) {
const GActionEntry * entry = & entries [ i ] ;
if ( entry - > name = = NULL ) continue ;
action_set_enabled ( main_application , entry - > name , value ) ;
}
}
static void create_action_groups ( GApplication * app ) {
g_action_map_add_action_entries ( G_ACTION_MAP ( app ) , emulation_entries , G_N_ELEMENTS ( emulation_entries ) , NULL ) ;
g_action_map_add_action_entries ( G_ACTION_MAP ( app ) , developer_entries , G_N_ELEMENTS ( developer_entries ) , NULL ) ;
g_action_map_add_action_entries ( G_ACTION_MAP ( app ) , app_entries , G_N_ELEMENTS ( app_entries ) , NULL ) ;
g_action_map_add_action_entries ( G_ACTION_MAP ( app ) , file_entries , G_N_ELEMENTS ( file_entries ) , NULL ) ;
g_action_map_add_action_entries ( G_ACTION_MAP ( app ) , edit_entries , G_N_ELEMENTS ( edit_entries ) , NULL ) ;
action_set_enabled ( app , " close " , false ) ;
action_entries_set_enabled ( emulation_entries , G_N_ELEMENTS ( emulation_entries ) , false ) ;
2020-04-10 01:25:53 +00:00
}
2019-09-30 14:40:55 +00:00
// This functions gets called immediately after registration of the GApplication
2019-10-12 21:11:26 +00:00
static void startup ( GApplication * app , gpointer gui_data_gptr ) {
GuiData * gui_data = gui_data_gptr ;
2019-09-29 23:49:41 +00:00
2020-04-10 01:25:53 +00:00
signal ( SIGINT , quit_interrupt ) ;
2019-09-30 14:40:55 +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 ( ) ;
2020-04-10 02:07:01 +00:00
g_debug ( " OpenGL supported: %s " , supports_gl ? " Yes " : " No " ) ;
2019-09-29 23:49:41 +00:00
2019-09-30 14:40:55 +00:00
builder = gtk_builder_new_from_resource ( RESOURCE_PREFIX " ui/window.ui " ) ;
gtk_builder_connect_signals ( builder , NULL ) ;
2019-09-29 23:49:41 +00:00
2020-04-11 22:38:18 +00:00
create_action_groups ( app ) ;
2019-09-29 23:49:41 +00:00
2019-10-14 22:16:59 +00:00
if ( gui_data - > cli_options . prefix ! = NULL ) {
GAction * action = g_action_map_lookup_action ( G_ACTION_MAP ( main_application ) , " change_model " ) ;
g_action_change_state ( action , g_variant_new_string ( gui_data - > cli_options . prefix ) ) ;
}
2019-09-30 14:40:55 +00:00
# if NDEBUG
// Disable when not compiled in debug mode
2020-04-11 22:38:18 +00:00
action_set_enabled ( app , " open_gtk_debugger " , false ) ;
2019-09-29 23:49:41 +00:00
2019-09-30 14:40:55 +00:00
// Remove the menubar override
2019-10-03 01:13:53 +00:00
gtk_widget_destroy ( builder_get ( GTK_WIDGET , " menubar_override_selector_label " ) ) ;
gtk_widget_destroy ( builder_get ( GTK_WIDGET , " menubar_override_selector " ) ) ;
2019-09-30 14:40:55 +00:00
# endif
2019-09-29 23:49:41 +00:00
2019-09-30 14:40:55 +00:00
preferences = GTK_WINDOW ( get_object ( " preferences " ) ) ;
2019-09-29 23:49:41 +00:00
2019-09-30 14:40:55 +00:00
g_signal_connect ( preferences , " realize " , G_CALLBACK ( on_preferences_realize ) , ( gpointer ) builder ) ;
2019-10-14 14:01:51 +00:00
init_settings ( gui_data - > cli_options . config_path , preferences ) ;
2019-09-29 23:49:41 +00:00
2019-09-30 14:40:55 +00:00
vram_viewer = GTK_WINDOW ( get_object ( " vram_viewer " ) ) ;
memory_viewer = GTK_WINDOW ( get_object ( " memory_viewer " ) ) ;
2019-10-12 21:11:26 +00:00
2019-09-30 14:40:55 +00:00
console = GTK_WINDOW ( get_object ( " console " ) ) ;
printer = GTK_WINDOW ( get_object ( " printer " ) ) ;
2019-09-25 20:47:33 +00:00
2019-10-12 21:11:26 +00:00
if ( config . sample_rate = = - 1 ) {
gui_data - > sample_rate = DEFAULT_AUDIO_SAMPLE_RATE ;
}
else {
gui_data - > sample_rate = config . sample_rate ;
}
2019-09-30 14:40:55 +00:00
// setup main window
main_window = GTK_APPLICATION_WINDOW ( gtk_application_window_new ( GTK_APPLICATION ( app ) ) ) ;
main_window_container = GTK_BOX ( gtk_box_new ( GTK_ORIENTATION_VERTICAL , 0 ) ) ;
2019-09-25 20:47:33 +00:00
2019-09-30 14:40:55 +00:00
gtk_window_set_title ( GTK_WINDOW ( main_window ) , " SameBoy " ) ;
gtk_application_window_set_show_menubar ( main_window , false ) ;
gtk_container_add ( GTK_CONTAINER ( main_window ) , GTK_WIDGET ( main_window_container ) ) ;
2019-09-25 20:47:33 +00:00
2019-09-30 14:40:55 +00:00
setup_menu ( app ) ;
2019-09-25 20:47:33 +00:00
2019-09-30 14:40:55 +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 ) ) ;
2019-09-25 20:47:33 +00:00
2019-09-30 14:40: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 "
} ;
2019-09-25 20:47:33 +00:00
2019-09-30 14:40:55 +00:00
// 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 ( GTK_WINDOW ( main_window ) , icon_list ) ;
// Add missing information to the about dialog
GtkAboutDialog * about_dialog = GTK_ABOUT_DIALOG ( get_object ( " about_dialog " ) ) ;
2019-10-12 21:11:26 +00:00
gtk_about_dialog_set_logo ( about_dialog , gdk_pixbuf_new_from_resource ( icons [ 2 ] , NULL ) ) ; // reuse the 64x64 icon
2019-09-30 14:40:55 +00:00
gtk_about_dialog_set_version ( about_dialog , " v " xstr ( VERSION ) ) ;
2019-10-12 21:11:26 +00:00
g_list_free_full ( icon_list , g_object_unref ) ;
2019-09-25 20:47:33 +00:00
}
2020-04-11 22:38:18 +00:00
static void connect_signal_handlers ( GApplication * app ) {
2019-09-30 14:40:55 +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 ) ;
g_signal_connect ( main_window , " destroy " , G_CALLBACK ( on_quit ) , app ) ;
g_signal_connect ( main_window , " key_press_event " , G_CALLBACK ( on_key_press ) , NULL ) ;
g_signal_connect ( main_window , " key_release_event " , G_CALLBACK ( on_key_press ) , NULL ) ;
2019-10-12 21:11:26 +00:00
g_signal_connect ( main_window , " window-state-event " , G_CALLBACK ( on_window_state_change ) , NULL ) ;
2019-09-30 14:40:55 +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 ) ;
2019-09-25 20:47:33 +00:00
2019-09-30 14:40:55 +00:00
// 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 ) ;
2020-04-10 02:10:28 +00:00
2019-09-30 14:40:55 +00:00
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 ) ;
2019-09-25 20:47:33 +00:00
2019-10-05 14:21:53 +00:00
gtk_widget_add_events ( builder_get ( GTK_WIDGET , " vram_viewer_tileset_canvas " ) , GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK ) ;
gtk_widget_add_events ( builder_get ( GTK_WIDGET , " vram_viewer_tilemap_canvas " ) , GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK ) ;
g_signal_connect ( get_object ( " vram_viewer_tileset_canvas " ) , " motion_notify_event " , G_CALLBACK ( on_motion_vram_viewer_tileset ) , NULL ) ;
g_signal_connect ( get_object ( " vram_viewer_tilemap_canvas " ) , " motion_notify_event " , G_CALLBACK ( on_motion_vram_viewer_tilemap ) , NULL ) ;
g_signal_connect ( get_object ( " vram_viewer_stack " ) , " notify::visible-child " , G_CALLBACK ( on_vram_tab_change ) , NULL ) ;
2020-04-11 22:38:18 +00:00
}
2019-10-05 14:21:53 +00:00
2020-04-11 22:38:18 +00:00
static void create_canvas ( ) {
2019-09-30 14:40:55 +00:00
// 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 ) ;
2020-04-11 23:35:15 +00:00
gtk_box_pack_end ( GTK_BOX ( main_window_container ) , GTK_WIDGET ( gl_area ) , true , true , 0 ) ;
2019-09-30 14:40:55 +00:00
}
else {
create_fallback_canvas ( ) ;
}
2019-09-25 20:47:33 +00:00
2019-10-03 14:04:22 +00:00
GdkScreen * screen = gdk_screen_get_default ( ) ;
GtkCssProvider * provider = gtk_css_provider_new ( ) ;
gtk_css_provider_load_from_resource ( provider , RESOURCE_PREFIX " css/main.css " ) ;
2019-10-12 21:11:26 +00:00
gtk_style_context_add_provider_for_screen ( screen , GTK_STYLE_PROVIDER ( provider ) , GTK_STYLE_PROVIDER_PRIORITY_APPLICATION ) ;
2020-04-11 22:38:18 +00:00
}
2019-10-03 14:04:22 +00:00
2020-04-11 22:38:18 +00:00
static void setup_console ( ) {
2019-10-07 22:36:16 +00:00
GtkTextView * text_view = builder_get ( GTK_TEXT_VIEW , " console_screen " ) ;
GtkTextBuffer * text_buf = gtk_text_view_get_buffer ( text_view ) ;
2020-04-10 02:10:28 +00:00
gtk_text_view_set_buffer (
builder_get ( GTK_TEXT_VIEW , " console_sidebar_output " ) ,
gtk_text_buffer_new ( gtk_text_buffer_get_tag_table ( text_buf ) )
) ;
2019-10-07 22:36:16 +00:00
gtk_text_buffer_create_tag ( text_buf , " bold " , " weight " , PANGO_WEIGHT_BOLD , NULL ) ;
2020-04-11 23:35:15 +00:00
gtk_text_buffer_create_tag ( text_buf , " underline " , " underline " , PANGO_UNDERLINE_SINGLE , " underline-set " , true , NULL ) ;
gtk_text_buffer_create_tag ( text_buf , " dashed_underline " , " underline " , PANGO_UNDERLINE_DOUBLE , " underline-set " , true , NULL ) ;
2019-10-07 22:36:16 +00:00
2019-10-12 21:11:26 +00:00
g_mutex_init ( & debugger_input_mutex ) ;
g_cond_init ( & debugger_input_cond ) ;
2020-04-10 02:10:28 +00:00
g_rec_mutex_init ( & console_output_lock ) ;
2019-10-12 21:11:26 +00:00
if ( ! debugger_input_queue ) {
debugger_input_queue = g_ptr_array_sized_new ( 4 ) ;
}
2020-04-10 02:10:28 +00:00
if ( ! pending_console_output ) {
pending_console_output = gtk_text_buffer_new ( gtk_text_buffer_get_tag_table ( text_buf ) ) ;
}
2020-04-11 22:38:18 +00:00
}
// This function gets called when the GApplication gets activated, i.e. it is ready to show widgets.
static void activate ( GApplication * app , gpointer gui_data_gptr ) {
GuiData * gui_data = gui_data_gptr ;
// initialize SameBoy core
init ( gui_data ) ;
2020-04-28 16:36:47 +00:00
GB_audio_init ( ) ;
2020-04-11 22:38:18 +00:00
init_controllers ( ) ;
connect_signal_handlers ( app ) ;
create_canvas ( ) ;
setup_console ( ) ;
if ( gui_data - > cli_options . fullscreen ) {
gtk_window_fullscreen ( GTK_WINDOW ( main_window ) ) ;
}
gtk_application_add_window ( GTK_APPLICATION ( app ) , GTK_WINDOW ( main_window ) ) ;
gtk_widget_show_all ( GTK_WIDGET ( main_window ) ) ;
2020-04-10 02:10:28 +00:00
2019-10-12 21:11:26 +00:00
// Start the emulation thread
run ( gui_data ) ;
2019-09-26 16:06:14 +00:00
}
2019-09-30 14:40:55 +00:00
// This function gets called when the application is closed.
2019-10-12 21:11:26 +00:00
static void shutdown ( GApplication * app , GFile * * files , gint n_files , const gchar * hint , gpointer gui_data_gptr ) {
2020-04-10 02:07:01 +00:00
g_debug ( " SHUTDOWN " ) ;
2019-09-26 16:06:14 +00:00
2019-10-12 21:11:26 +00:00
stop ( & gui_data ) ;
while ( stopping ) ;
g_object_unref ( builder ) ;
2019-09-30 14:40:55 +00:00
save_settings ( ) ;
free_settings ( ) ;
2020-04-10 01:25:53 +00:00
SDL_Quit ( ) ;
2019-10-12 21:11:26 +00:00
if ( image_buffers [ 0 ] ) g_free ( image_buffers [ 0 ] ) ;
if ( image_buffers [ 1 ] ) g_free ( image_buffers [ 1 ] ) ;
if ( image_buffers [ 2 ] ) g_free ( image_buffers [ 2 ] ) ;
free_shader ( & shader ) ;
free_master_shader ( ) ;
GB_free ( & gb ) ;
2019-09-26 16:06:14 +00:00
}
2019-09-30 14:40: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.
2019-10-12 21:11:26 +00:00
static void open ( GApplication * app , GFile * * files , gint n_files , const gchar * hint , gpointer gui_data_gptr ) {
GuiData * gui_data = gui_data_gptr ;
2019-09-26 16:06:14 +00:00
2019-09-30 14:40:55 +00:00
if ( n_files > 1 ) {
2020-04-10 02:07:01 +00:00
g_warning ( " More than one file specified " ) ;
2019-09-30 14:40:55 +00:00
exit ( EXIT_FAILURE ) ;
2019-09-26 16:06:14 +00:00
}
2019-10-12 21:11:26 +00:00
gui_data - > file = g_file_dup ( files [ 0 ] ) ;
2019-09-26 16:06:14 +00:00
2019-09-30 14:40:55 +00:00
// We have handled the files, now activate the application
2019-10-12 21:11:26 +00:00
activate ( app , gui_data_gptr ) ;
2019-09-26 16:06:14 +00:00
}
2019-09-30 14:40:55 +00:00
// Tell our application to quit.
// After this functions has been called the `shutdown` signal will be issued.
2020-04-10 02:10:28 +00:00
//
2019-09-30 14:40:55 +00:00
// TODO: Make sure we have a way to quit our emulation loop before `shutdown` gets called
2020-04-11 22:38:18 +00:00
static void quit ( ) {
stop ( & gui_data ) ;
2019-09-30 14:40:55 +00:00
2020-04-11 22:38:18 +00:00
// Quit our application properly.
// This fires the “shutdown” signal.
g_application_quit ( G_APPLICATION ( main_application ) ) ;
2019-09-26 16:06:14 +00:00
}
2019-09-30 14:40:55 +00:00
static 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 ;
2020-04-10 02:10:28 +00:00
2019-09-30 14:40:55 +00:00
case GDK_KEY_g : mask = BUTTON_MASK_SELECT ; break ;
case GDK_KEY_h : mask = BUTTON_MASK_START ; break ;
2020-04-10 02:10:28 +00:00
2019-09-30 14:40:55 +00:00
case GDK_KEY_k : mask = BUTTON_MASK_B ; break ;
case GDK_KEY_l : mask = BUTTON_MASK_A ; break ;
2019-10-12 21:11:26 +00:00
2019-10-14 16:25:40 +00:00
case GDK_KEY_Tab : {
rewind_down = event - > type = = GDK_KEY_PRESS ;
GB_set_turbo_mode ( & gb , turbo_down , turbo_down & & rewind_down ) ;
if ( event - > type = = GDK_KEY_RELEASE ) {
rewind_paused = false ;
}
break ; }
case GDK_KEY_space : {
turbo_down = event - > type = = GDK_KEY_PRESS ;
SDL_ClearQueuedAudio ( device_id ) ;
GB_set_turbo_mode ( & gb , turbo_down , turbo_down & & rewind_down ) ;
break ; }
case GDK_KEY_dead_acute : // fall through
case GDK_KEY_acute : // fall through
case GDK_KEY_apostrophe :
underclock_down = event - > type = = GDK_KEY_PRESS ;
break ;
2019-10-12 21:11:26 +00:00
case GDK_KEY_F11 : {
if ( event - > type = = GDK_KEY_RELEASE ) {
if ( is_fullscreen ) {
gtk_window_unfullscreen ( GTK_WINDOW ( main_window ) ) ;
}
else {
gtk_window_fullscreen ( GTK_WINDOW ( main_window ) ) ;
}
}
2019-10-14 16:25:40 +00:00
break ; }
2019-09-30 14:40:55 +00:00
}
2020-04-10 02:10:28 +00:00
2019-09-30 14:40:55 +00:00
if ( event - > type = = GDK_KEY_PRESS ) {
pressed_buttons | = mask ;
}
else if ( event - > type = = GDK_KEY_RELEASE ) {
pressed_buttons & = ~ mask ;
}
2020-04-11 23:35:15 +00:00
return false ;
2019-09-26 16:06:14 +00:00
}
2019-10-12 21:11:26 +00:00
static void on_window_state_change ( GtkWidget * w , GdkEventWindowState * event , gpointer data ) {
is_fullscreen = event - > new_window_state & GDK_WINDOW_STATE_FULLSCREEN ;
}
2019-09-30 14:40:55 +00:00
// app.about GAction
// Opens the about dialog
static void activate_about ( GSimpleAction * action , GVariant * parameter , gpointer app ) {
GObject * dialog = get_object ( " about_dialog " ) ;
gtk_dialog_run ( GTK_DIALOG ( dialog ) ) ;
gtk_widget_hide ( GTK_WIDGET ( dialog ) ) ;
2019-09-26 16:06:14 +00:00
}
2019-10-07 22:36:16 +00:00
// app.show_console GAction
// Opens the console
static void activate_show_console ( GSimpleAction * action , GVariant * parameter , gpointer app ) {
if ( debugger_input_queue ) {
while ( debugger_input_queue - > len ) {
g_ptr_array_remove_index_fast ( debugger_input_queue , debugger_input_queue - > len - 1 ) ;
}
}
gtk_widget_show_all ( builder_get ( GTK_WIDGET , " console " ) ) ;
}
2019-09-30 14:40:55 +00:00
// app.open_gtk_debugger GAction
// Opens the GTK debugger
static void activate_open_gtk_debugger ( GSimpleAction * action , GVariant * parameter , gpointer app ) {
gtk_window_set_interactive_debugging ( true ) ;
2019-09-26 16:06:14 +00:00
}
2019-09-30 14:40:55 +00:00
// app.open_memory_viewer GAction
// Opens the memory viewer window
static void activate_open_memory_viewer ( GSimpleAction * action , GVariant * parameter , gpointer app ) {
gtk_widget_show_all ( GTK_WIDGET ( memory_viewer ) ) ;
}
2019-09-27 21:10:28 +00:00
2019-09-30 14:40:55 +00:00
// app.open_vram_viewer GAction
// Opens the VRAM viewer window
static void activate_open_vram_viewer ( GSimpleAction * action , GVariant * parameter , gpointer app ) {
gtk_widget_show_all ( GTK_WIDGET ( vram_viewer ) ) ;
}
2019-09-27 22:15:37 +00:00
2019-09-30 14:40:55 +00:00
// app.open GAction
// Opens a ROM file
static void activate_open ( GSimpleAction * action , GVariant * parameter , gpointer app ) {
2020-04-11 22:38:18 +00:00
stop ( & gui_data ) ;
2019-09-30 14:40:55 +00:00
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 ) ) ;
2020-04-10 02:10:28 +00:00
2019-09-30 14:40:55 +00:00
if ( res = = GTK_RESPONSE_ACCEPT ) {
2020-04-11 16:55:21 +00:00
const char * path = gtk_file_chooser_get_filename ( GTK_FILE_CHOOSER ( native ) ) ;
gui_data . file = g_file_new_for_path ( path ) ;
activate_reset ( action , parameter , app ) ;
2019-09-27 21:10:28 +00:00
}
2020-04-11 22:38:18 +00:00
else {
run ( & gui_data ) ;
}
2019-09-30 14:40:55 +00:00
g_object_unref ( native ) ;
2019-09-27 21:10:28 +00:00
}
2020-04-11 22:38:18 +00:00
// app.close GAction
// Closes a ROM
static void activate_close ( GSimpleAction * action , GVariant * parameter , gpointer app ) {
stop ( & gui_data ) ;
GB_free ( & gb ) ;
2020-04-28 16:36:47 +00:00
2020-04-11 22:38:18 +00:00
// Clear the screen as side effect
update_window_geometry ( ) ;
2020-04-11 23:35:15 +00:00
gtk_widget_queue_draw ( fallback_canvas ? GTK_WIDGET ( fallback_canvas ) : GTK_WIDGET ( gl_area ) ) ;
2020-04-11 22:38:18 +00:00
2020-04-11 23:35:15 +00:00
// Clear the VRAM viewer
g_mutex_lock ( & tileset_buffer_mutex ) ;
memset ( tileset_buffer , 0 , sizeof tileset_buffer ) ;
g_mutex_unlock ( & tileset_buffer_mutex ) ;
2020-04-28 16:36:47 +00:00
2020-04-11 23:35:15 +00:00
g_mutex_lock ( & tilemap_buffer_mutex ) ;
memset ( tilemap_buffer , 0 , sizeof tilemap_buffer ) ;
g_mutex_unlock ( & tilemap_buffer_mutex ) ;
gtk_stack_set_visible_child_name ( builder_get ( GTK_STACK , " vram_viewer_stack " ) , " vram_viewer_tileset " ) ;
gtk_tree_view_set_model ( builder_get ( GTK_TREE_VIEW , " vram_viewer_sprites " ) , NULL ) ;
gtk_tree_view_set_model ( builder_get ( GTK_TREE_VIEW , " vram_viewer_palettes " ) , NULL ) ;
2020-04-11 22:38:18 +00:00
2020-04-11 23:35:15 +00:00
// Redraw the VRAM viewer
gtk_widget_queue_draw ( GTK_WIDGET ( vram_viewer ) ) ;
// Update menu action states
2020-04-11 22:38:18 +00:00
action_set_enabled ( main_application , " close " , false ) ;
action_entries_set_enabled ( emulation_entries , G_N_ELEMENTS ( emulation_entries ) , false ) ;
}
2019-09-30 14:40:55 +00:00
// app.preferences GAction
// Opens the preferences window
static void activate_preferences ( GSimpleAction * action , GVariant * parameter , gpointer app ) {
gtk_widget_show_all ( GTK_WIDGET ( get_object ( " preferences " ) ) ) ;
2019-09-26 16:06:14 +00:00
}
2019-09-30 14:40:55 +00:00
// app.quit GAction
// Exits the application
static void activate_quit ( GSimpleAction * action , GVariant * parameter , gpointer app ) {
2020-04-11 22:38:18 +00:00
quit ( ) ;
2019-09-30 14:40:55 +00:00
}
2019-09-29 23:49:41 +00:00
2019-10-12 21:11:26 +00:00
// app.reset GAction
// Resets the emulation
static void activate_reset ( GSimpleAction * action , GVariant * parameter , gpointer app ) {
2020-04-11 22:38:18 +00:00
if ( ! GB_is_inited ( & gb ) ) {
init ( & gui_data ) ;
2019-10-12 21:11:26 +00:00
}
2020-04-11 22:38:18 +00:00
stop ( & gui_data ) ;
reset ( & gui_data ) ;
run ( & gui_data ) ;
2019-10-12 21:11:26 +00:00
}
2020-04-10 02:10:28 +00:00
// app.clear_console GAction
// Clears the debugger console
static void activate_clear_console ( GSimpleAction * action , GVariant * parameter , gpointer app ) {
g_rec_mutex_lock ( & console_output_lock ) ;
GtkTextView * text_view = builder_get ( GTK_TEXT_VIEW , " console_screen " ) ;
GtkTextBuffer * text_buf = gtk_text_view_get_buffer ( text_view ) ;
gtk_text_buffer_set_text ( text_buf , " " , - 1 ) ;
g_rec_mutex_unlock ( & console_output_lock ) ;
}
2019-10-12 21:11:26 +00:00
static void on_model_changed ( GSimpleAction * action , GVariant * value , gpointer user_data ) {
2019-10-14 22:16:59 +00:00
if ( ! GB_is_inited ( & gb ) ) {
g_simple_action_set_state ( action , value ) ;
return ;
}
2019-10-12 21:11:26 +00:00
const gchar * model_str = g_variant_get_string ( value , NULL ) ;
GtkMessageDialog * dialog = GTK_MESSAGE_DIALOG ( gtk_message_dialog_new (
GTK_WINDOW ( main_window ) ,
GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT ,
GTK_MESSAGE_QUESTION ,
GTK_BUTTONS_YES_NO ,
" Changing the emulated model requires a reset. \n Change model and reset game? "
) ) ;
stop ( & gui_data ) ;
gint result = gtk_dialog_run ( GTK_DIALOG ( dialog ) ) ;
switch ( result ) {
case GTK_RESPONSE_YES :
2019-10-14 22:16:59 +00:00
// Reset the CLI model override
gui_data . cli_options . model = - 1 ;
2019-10-12 21:11:26 +00:00
g_simple_action_set_state ( action , value ) ;
2019-10-14 14:01:51 +00:00
reset ( & gui_data ) ;
2019-10-12 21:11:26 +00:00
break ;
default :
// Action has been canceled
break ;
}
run ( & gui_data ) ;
gtk_widget_destroy ( GTK_WIDGET ( dialog ) ) ;
}
static void on_mute_changed ( GSimpleAction * action , GVariant * value , gpointer user_data ) {
2020-04-28 16:36:47 +00:00
GB_audio_set_paused ( g_variant_get_boolean ( value ) ) ;
2019-10-12 21:11:26 +00:00
g_simple_action_set_state ( action , value ) ;
}
static void on_pause_changed ( GSimpleAction * action , GVariant * value , gpointer user_data ) {
if ( g_variant_get_boolean ( value ) ) {
stop ( & gui_data ) ;
}
else {
run ( & gui_data ) ;
}
g_simple_action_set_state ( action , value ) ;
}
2019-09-30 14:40:55 +00:00
// `destroy` signal GCallback
// Exits the application
static void on_quit ( GtkWidget * w , gpointer app ) {
2020-04-11 22:38:18 +00:00
quit ( ) ;
2019-09-30 14:40:55 +00:00
}
2019-09-29 23:49:41 +00:00
2019-09-30 14:40:55 +00:00
// TODO: Comment
static void gl_init ( GtkWidget * w ) {
GtkGLArea * gl_area = GTK_GL_AREA ( w ) ;
2019-09-29 23:49:41 +00:00
2020-04-10 02:07:01 +00:00
g_debug ( " GL_INIT " ) ;
2019-09-30 14:40:55 +00:00
const char * renderer ;
2019-09-29 23:49:41 +00:00
2020-04-10 02:07:01 +00:00
g_debug ( " GL Context: %p " , gtk_gl_area_get_context ( gl_area ) ) ;
2019-09-29 23:49:41 +00:00
2019-09-30 14:40:55 +00:00
gtk_gl_area_make_current ( gl_area ) ;
2020-04-10 02:10:28 +00:00
2019-09-30 14:40:55 +00:00
if ( gtk_gl_area_get_error ( gl_area ) ! = NULL ) {
goto error ;
2019-09-29 23:49:41 +00:00
}
2019-09-30 14:40:55 +00:00
renderer = ( char * ) glGetString ( GL_RENDERER ) ;
2020-04-10 02:07:01 +00:00
g_debug ( " GtkGLArea on %s " , renderer ? renderer : " Unknown " ) ;
2019-09-29 23:49:41 +00:00
2019-09-30 14:40:55 +00:00
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 ) ;
2019-09-29 23:49:41 +00:00
}
else {
2019-09-30 14:40:55 +00:00
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 ;
}
2019-09-29 23:49:41 +00:00
2019-09-30 14:40:55 +00:00
error :
if ( gtk_gl_area_get_error ( gl_area ) ! = NULL ) {
2020-04-10 02:07:01 +00:00
g_warning ( " GtkGLArea: %s " , gtk_gl_area_get_error ( gl_area ) - > message ) ;
2019-09-30 14:40:55 +00:00
}
2019-09-29 23:49:41 +00:00
2019-09-30 14:40:55 +00:00
create_fallback_canvas ( ) ;
}
2019-09-29 23:49:41 +00:00
2019-09-30 14:40:55 +00:00
// TODO: Comment
static void gl_draw ( ) {
render_texture ( get_current_buffer ( ) , get_previous_buffer ( ) ) ;
2019-09-29 23:49:41 +00:00
}
2019-09-30 14:40:55 +00:00
// TODO: Comment
static void gl_finish ( ) { }
2019-09-29 23:49:41 +00:00
2019-09-30 14:40:55 +00:00
// TODO: Comment
static 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 ) ;
2019-09-29 23:49:41 +00:00
2019-09-30 14:40:55 +00:00
guint screen_width = GB_get_screen_width ( & gb ) ;
guint screen_height = GB_get_screen_height ( & gb ) ;
2019-09-29 23:49:41 +00:00
2019-09-30 14:40:55 +00:00
gtk_render_background ( context , cr , 0 , 0 , width , height ) ;
2019-10-12 21:11:26 +00:00
2019-09-30 14:40:55 +00:00
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 )
) ;
2019-09-24 14:14:06 +00:00
2019-10-03 01:10:48 +00:00
cairo_translate ( cr , viewport . x , viewport . y ) ;
cairo_scale ( cr , viewport . w / screen_width , viewport . h / screen_height ) ;
2019-09-30 14:40:55 +00:00
cairo_set_source_surface ( cr , surface , 0 , 0 ) ;
cairo_pattern_set_filter ( cairo_get_source ( cr ) , CAIRO_FILTER_NEAREST ) ;
cairo_paint ( cr ) ;
2019-09-29 23:49:41 +00:00
2020-04-11 23:35:15 +00:00
return false ;
2019-09-30 14:40:55 +00:00
}
2020-04-11 16:24:55 +00:00
2019-09-30 14:40:55 +00:00
// TODO: Comment
static void resize ( ) {
update_viewport ( ) ;
}
2020-04-11 16:24:55 +00:00
2019-09-30 14:40:55 +00:00
// Gets called when the VRAM viewer gets realized
static void on_vram_viewer_realize ( ) {
vram_viewer_visible = true ;
2019-10-14 19:28:39 +00:00
vram_viewer_active_tab = ( gchar * ) gtk_stack_get_visible_child_name ( builder_get ( GTK_STACK , " vram_viewer_stack " ) ) ;
2019-09-30 14:40:55 +00:00
}
2019-09-27 21:10:28 +00:00
2019-09-30 14:40:55 +00:00
// Gets called when the VRAM viewer gets unrealized
static void on_vram_viewer_unrealize ( ) {
vram_viewer_visible = false ;
}
2019-09-27 21:10:28 +00:00
2019-09-30 14:40:55 +00:00
// Gets called when the tileset viewer should be redrawn
static gboolean on_draw_vram_viewer_tileset ( GtkWidget * widget , cairo_t * cr , gpointer data ) {
2020-04-11 23:35:15 +00:00
g_mutex_lock ( & tileset_buffer_mutex ) ;
2019-09-30 14:40:55 +00:00
guint width , height ;
GtkStyleContext * context ;
2020-04-11 16:24:55 +00:00
2019-09-30 14:40:55 +00:00
context = gtk_widget_get_style_context ( widget ) ;
width = gtk_widget_get_allocated_width ( widget ) ;
height = gtk_widget_get_allocated_height ( widget ) ;
2019-09-25 20:47:33 +00:00
2019-09-30 14:40:55 +00:00
gtk_render_background ( context , cr , 0 , 0 , width , height ) ;
2020-04-10 02:10:28 +00:00
2019-09-30 14:40:55 +00:00
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 )
) ;
2020-04-11 16:24:55 +00:00
2019-10-02 21:53:18 +00:00
cairo_scale ( cr , 2.0 , 2.0 ) ;
2019-09-30 14:40:55 +00:00
cairo_set_source_surface ( cr , surface , 0 , 0 ) ;
2019-10-02 21:53:18 +00:00
cairo_pattern_set_filter ( cairo_get_source ( cr ) , CAIRO_FILTER_NEAREST ) ;
2019-09-30 14:40:55 +00:00
cairo_paint ( cr ) ;
2019-09-25 20:47:33 +00:00
2019-10-03 01:13:53 +00:00
if ( gtk_toggle_button_get_active ( builder_get ( GTK_TOGGLE_BUTTON , " vram_viewer_tileset_toggle_grid_button " ) ) ) {
2019-10-02 21:53:18 +00:00
cairo_set_source_rgba ( cr , 0.0 , 0.0 , 0.0 , 0.25 ) ;
cairo_set_line_width ( cr , 1 ) ;
const int divisions_x = 256 / 8 ;
const int divisions_y = 192 / 8 ;
for ( int i = 0 ; i < divisions_x ; i + + ) {
const int j = 256 * i ;
cairo_move_to ( cr , j / divisions_x , 0 ) ;
cairo_line_to ( cr , j / divisions_x , 192 ) ;
}
for ( int i = 0 ; i < divisions_y ; i + + ) {
const int j = 192 * i ;
cairo_move_to ( cr , 0 , j / divisions_y ) ;
cairo_line_to ( cr , 256 , j / divisions_y ) ;
}
cairo_stroke ( cr ) ;
}
2020-04-11 23:35:15 +00:00
g_mutex_unlock ( & tileset_buffer_mutex ) ;
return false ;
2019-09-30 14:40:55 +00:00
}
2019-09-27 21:10:28 +00:00
2019-09-30 14:40:55 +00:00
// Gets called when the tilemap viewer should be redrawn
static gboolean on_draw_vram_viewer_tilemap ( GtkWidget * widget , cairo_t * cr , gpointer data ) {
2020-04-11 23:35:15 +00:00
g_mutex_lock ( & tilemap_buffer_mutex ) ;
2019-09-30 14:40:55 +00:00
guint width , height ;
GtkStyleContext * context ;
2020-04-11 16:24:55 +00:00
2019-09-30 14:40:55 +00:00
context = gtk_widget_get_style_context ( widget ) ;
width = gtk_widget_get_allocated_width ( widget ) ;
height = gtk_widget_get_allocated_height ( widget ) ;
2020-04-11 16:24:55 +00:00
2019-09-30 14:40:55 +00:00
gtk_render_background ( context , cr , 0 , 0 , width , height ) ;
2019-10-03 01:10:48 +00:00
2019-09-30 14:40:55 +00:00
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 )
) ;
2020-04-11 16:24:55 +00:00
2019-10-02 21:53:18 +00:00
cairo_scale ( cr , 2.0 , 2.0 ) ;
2019-09-30 14:40:55 +00:00
cairo_set_source_surface ( cr , surface , 0 , 0 ) ;
2019-10-02 21:53:18 +00:00
cairo_pattern_set_filter ( cairo_get_source ( cr ) , CAIRO_FILTER_NEAREST ) ;
2019-09-30 14:40:55 +00:00
cairo_paint ( cr ) ;
2020-04-11 16:24:55 +00:00
2019-10-03 01:13:53 +00:00
if ( gtk_toggle_button_get_active ( builder_get ( GTK_TOGGLE_BUTTON , " vram_viewer_tilemap_toggle_grid_button " ) ) ) {
2019-10-02 21:53:18 +00:00
cairo_set_source_rgba ( cr , 0.0 , 0.0 , 0.0 , 0.25 ) ;
cairo_set_line_width ( cr , 1 ) ;
const int divisions = 256 / 8 ;
for ( int i = 0 ; i < divisions ; i + + ) {
const int j = 256 * i ;
cairo_move_to ( cr , j / divisions , 0 ) ;
cairo_line_to ( cr , j / divisions , 256 ) ;
cairo_move_to ( cr , 0 , j / divisions ) ;
cairo_line_to ( cr , 256 , j / divisions ) ;
}
cairo_stroke ( cr ) ;
}
2019-10-03 01:13:53 +00:00
if ( gtk_toggle_button_get_active ( builder_get ( GTK_TOGGLE_BUTTON , " vram_viewer_tilemap_toggle_scrolling_button " ) ) ) {
2019-10-03 13:26:49 +00:00
cairo_rectangle ( cr , - 2 , - 2 , width + 2 , height + 2 ) ;
2019-10-03 01:10:48 +00:00
for ( unsigned x = 0 ; x < 2 ; x + + ) {
for ( unsigned y = 0 ; y < 2 ; y + + ) {
Rect rect = scrollRect ;
rect . x - = 256 * x ;
rect . y + = 256 * y ;
cairo_rectangle ( cr , rect . x , rect . y , rect . w , rect . h ) ;
}
}
cairo_set_fill_rule ( cr , CAIRO_FILL_RULE_EVEN_ODD ) ;
cairo_set_line_width ( cr , 2 ) ;
cairo_set_line_join ( cr , CAIRO_LINE_JOIN_ROUND ) ;
cairo_set_source_rgba ( cr , 0.2 , 0.2 , 0.2 , 0.5 ) ;
cairo_fill_preserve ( cr ) ;
cairo_clip_preserve ( cr ) ;
cairo_set_source_rgba ( cr , 0.0 , 0.0 , 0.0 , 0.6 ) ;
cairo_stroke ( cr ) ;
}
2020-04-11 23:35:15 +00:00
g_mutex_unlock ( & tilemap_buffer_mutex ) ;
return false ;
2020-04-11 16:24:55 +00:00
}
2019-10-05 14:21:53 +00:00
static gboolean on_motion_vram_viewer_tileset ( GtkWidget * widget , GdkEventMotion * event ) {
int x , y ;
if ( event - > is_hint ) {
gdk_window_get_pointer ( event - > window , & x , & y , NULL ) ;
}
else {
x = event - > x ;
y = event - > y ;
}
// Compensate for our canvas scale
x / = 2 ;
y / = 2 ;
uint8_t bank = x > = 128 ? 1 : 0 ;
x & = 127 ;
uint16_t tile = x / 8 + y / 8 * 16 ;
GtkLabel * status = builder_get ( GTK_LABEL , " vram_viewer_status " ) ;
gtk_label_set_text ( status , g_strdup_printf ( " Tile number $%02x at %d:$%04x " , tile & 0xFF , bank , 0x8000 + tile * 0x10 ) ) ;
2020-04-11 23:35:15 +00:00
return true ;
2019-10-05 14:21:53 +00:00
}
static gboolean on_motion_vram_viewer_tilemap ( GtkWidget * widget , GdkEventMotion * event ) {
int x , y ;
if ( event - > is_hint ) {
gdk_window_get_pointer ( event - > window , & x , & y , NULL ) ;
}
else {
x = event - > x ;
y = event - > y ;
}
// Compensate for our canvas scale
x / = 2 ;
y / = 2 ;
GtkLabel * status = builder_get ( GTK_LABEL , " vram_viewer_status " ) ;
uint16_t map_offset = x / 8 + y / 8 * 32 ;
uint16_t map_base = 0x1800 ;
GB_map_type_t map_type = GB_MAP_AUTO ;
const gchar * map_type_id = gtk_combo_box_get_active_id ( builder_get ( GTK_COMBO_BOX , " vram_viewer_tilemap_tilemap_selector " ) ) ;
if ( g_strcmp0 ( " auto " , map_type_id ) ! = 0 ) {
map_type = ( g_strcmp0 ( " 9800 " , map_type_id ) = = 0 ) ? GB_MAP_9800 : GB_MAP_9C00 ;
}
GB_tileset_type_t tileset_type = GB_TILESET_AUTO ;
const gchar * tileset_type_id = gtk_combo_box_get_active_id ( builder_get ( GTK_COMBO_BOX , " vram_viewer_tilemap_tileset_selector " ) ) ;
if ( g_strcmp0 ( " auto " , tileset_type_id ) ! = 0 ) {
tileset_type = ( g_strcmp0 ( " 8800 " , tileset_type_id ) = = 0 ) ? GB_TILESET_8800 : GB_TILESET_8000 ;
}
uint8_t lcdc = ( ( uint8_t * ) GB_get_direct_access ( & gb , GB_DIRECT_ACCESS_IO , NULL , NULL ) ) [ GB_IO_LCDC ] ;
uint8_t * vram = GB_get_direct_access ( & gb , GB_DIRECT_ACCESS_VRAM , NULL , NULL ) ;
if ( map_type = = GB_MAP_9C00 | | ( map_type = = GB_MAP_AUTO & & lcdc & 0x08 ) ) {
map_base = 0x1c00 ;
}
if ( tileset_type = = GB_TILESET_AUTO ) {
tileset_type = ( lcdc & 0x10 ) ? GB_TILESET_8800 : GB_TILESET_8000 ;
}
uint8_t tile = vram [ map_base + map_offset ] ;
uint16_t tile_address = 0 ;
if ( tileset_type = = GB_TILESET_8000 ) {
tile_address = 0x8000 + tile * 0x10 ;
}
else {
tile_address = 0x9000 + ( int8_t ) tile * 0x10 ;
}
if ( GB_is_cgb ( & gb ) ) {
uint8_t attributes = vram [ map_base + map_offset + 0x2000 ] ;
gtk_label_set_text ( status , g_strdup_printf ( " Tile number $%02x (%d:$%04x) at map address $%04x (Attributes: %c%c%c%d%d) " ,
tile ,
attributes & 0x8 ? 1 : 0 ,
tile_address ,
0x8000 + map_base + map_offset ,
( attributes & 0x80 ) ? ' P ' : ' - ' ,
( attributes & 0x40 ) ? ' V ' : ' - ' ,
( attributes & 0x20 ) ? ' H ' : ' - ' ,
attributes & 0x8 ? 1 : 0 ,
attributes & 0x7
) ) ;
}
else {
gtk_label_set_text ( status , g_strdup_printf ( " Tile number $%02x ($%04x) at map address $%04x " ,
tile ,
tile_address ,
0x8000 + map_base + map_offset
) ) ;
}
2020-04-11 23:35:15 +00:00
return true ;
2019-10-05 14:21:53 +00:00
}
static void on_vram_tab_change ( GtkWidget * widget , GParamSpec * pspec , GtkStackSwitcher * self ) {
gtk_label_set_text ( builder_get ( GTK_LABEL , " vram_viewer_status " ) , " " ) ;
2019-10-14 19:28:39 +00:00
vram_viewer_active_tab = ( gchar * ) gtk_stack_get_visible_child_name ( builder_get ( GTK_STACK , " vram_viewer_stack " ) ) ;
2019-10-05 14:21:53 +00:00
}
2019-09-30 14:40:55 +00:00
G_MODULE_EXPORT void on_boot_rom_location_changed ( GtkWidget * w , gpointer user_data_gptr ) {
GtkComboBox * box = GTK_COMBO_BOX ( w ) ;
const gchar * id = gtk_combo_box_get_active_id ( box ) ;
if ( id = = NULL ) return ;
2019-09-29 23:49:41 +00:00
2019-09-30 14:40:55 +00:00
if ( g_strcmp0 ( id , " other " ) = = 0 ) {
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 ) ) ;
2020-04-10 02:10:28 +00:00
2019-09-30 14:40:55 +00:00
if ( res = = GTK_RESPONSE_ACCEPT ) {
config . boot_rom_path = gtk_file_chooser_get_filename ( GTK_FILE_CHOOSER ( native ) ) ;
update_boot_rom_selector ( builder ) ;
}
2019-09-29 23:49:41 +00:00
2019-09-30 14:40:55 +00:00
g_object_unref ( native ) ;
2019-09-29 23:49:41 +00:00
}
else {
2019-09-30 14:40:55 +00:00
config . boot_rom_path = ( gchar * ) id ;
2019-09-29 23:49:41 +00:00
}
2019-09-30 14:40:55 +00:00
}
2019-09-23 14:35:10 +00:00
2019-09-30 14:40:55 +00:00
G_MODULE_EXPORT void on_cgb_model_changed ( GtkWidget * w , gpointer user_data_gptr ) {
GtkComboBox * box = GTK_COMBO_BOX ( w ) ;
2019-09-30 20:30:33 +00:00
config . cgb_revision_name = ( gchar * ) gtk_combo_box_get_active_id ( box ) ;
2020-04-11 16:24:55 +00:00
}
2019-09-30 14:40:55 +00:00
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 ) ;
2020-04-11 16:24:55 +00:00
2019-09-30 14:40:55 +00:00
if ( GB_is_inited ( & gb ) ) {
GB_set_color_correction_mode ( & gb , get_color_correction_mode ( ) ) ;
2020-04-11 16:24:55 +00:00
}
2019-09-30 14:40:55 +00:00
}
2020-04-11 16:24:55 +00:00
2020-04-10 15:45:35 +00:00
G_MODULE_EXPORT void on_frame_blending_changed ( GtkWidget * w , gpointer user_data_gptr ) {
GtkComboBox * box = GTK_COMBO_BOX ( w ) ;
config . frame_blending_mode = ( gchar * ) gtk_combo_box_get_active_id ( box ) ;
}
2020-04-10 19:42:03 +00:00
G_MODULE_EXPORT void on_display_border_changed ( GtkWidget * w , gpointer user_data_gptr ) {
GtkComboBox * box = GTK_COMBO_BOX ( w ) ;
config . display_border_mode = ( gchar * ) gtk_combo_box_get_active_id ( box ) ;
border_mode_changed = true ;
}
2020-04-10 20:19:40 +00:00
G_MODULE_EXPORT void on_monochrome_palette_changed ( GtkWidget * w , gpointer user_data_gptr ) {
GtkComboBox * box = GTK_COMBO_BOX ( w ) ;
config . monochrome_palette_id = ( gchar * ) gtk_combo_box_get_active_id ( box ) ;
GB_set_palette ( & gb , get_monochrome_palette ( ) ) ;
}
2019-09-30 14:40:55 +00:00
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 ) ) ;
}
2020-04-11 16:24:55 +00:00
2019-09-30 14:40:55 +00:00
G_MODULE_EXPORT void on_dmg_model_changed ( GtkWidget * w , gpointer user_data_gptr ) {
GtkComboBox * box = GTK_COMBO_BOX ( w ) ;
2019-09-30 20:30:33 +00:00
config . dmg_revision_name = ( gchar * ) gtk_combo_box_get_active_id ( box ) ;
2020-04-11 16:24:55 +00:00
}
2019-09-30 14:40:55 +00:00
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 ) ;
2019-09-23 22:34:21 +00:00
2019-10-12 21:11:26 +00:00
free_shader ( & shader ) ;
2019-09-30 14:40:55 +00:00
init_shader_with_name ( & shader , config . shader ) ;
2019-09-23 14:35:10 +00:00
}
2019-09-30 14:40:55 +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 ) ) ;
2020-04-11 16:24:55 +00:00
2019-09-30 14:40:55 +00:00
if ( GB_is_inited ( & gb ) ) {
GB_set_highpass_filter_mode ( & gb , get_highpass_mode ( ) ) ;
2020-04-11 16:24:55 +00:00
}
2019-09-30 14:40:55 +00:00
}
2020-04-11 16:24:55 +00:00
2019-09-30 14:40:55 +00:00
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 ( ) ;
}
2020-04-11 16:24:55 +00:00
2019-09-30 14:40:55 +00:00
G_MODULE_EXPORT void on_rewind_duration_changed ( GtkWidget * w , gpointer user_data_gptr ) {
GtkComboBox * box = GTK_COMBO_BOX ( w ) ;
2019-09-30 20:30:33 +00:00
config . rewind_duration = g_ascii_strtoll ( gtk_combo_box_get_active_id ( box ) , NULL , 10 ) ;
2019-10-14 16:25:40 +00:00
GB_set_rewind_length ( & gb , config . rewind_duration ) ;
2019-09-30 14:40:55 +00:00
}
2019-09-23 22:34:21 +00:00
2019-10-12 21:11:26 +00:00
G_MODULE_EXPORT void on_sample_rate_changed ( GtkWidget * w , gpointer user_data_gptr ) {
GtkComboBox * box = GTK_COMBO_BOX ( w ) ;
config . sample_rate = g_ascii_strtoll ( gtk_combo_box_get_active_id ( box ) , NULL , 10 ) ;
if ( config . sample_rate = = - 1 ) {
gui_data . sample_rate = DEFAULT_AUDIO_SAMPLE_RATE ;
}
else {
gui_data . sample_rate = config . sample_rate ;
}
2020-04-28 16:36:47 +00:00
GB_audio_set_paused ( true ) ;
GB_audio_init ( ) ;
GB_audio_set_paused ( false ) ;
2019-10-12 21:11:26 +00:00
}
2019-09-30 14:40:55 +00:00
G_MODULE_EXPORT void on_sgb_model_changed ( GtkWidget * w , gpointer user_data_gptr ) {
GtkComboBox * box = GTK_COMBO_BOX ( w ) ;
2019-09-30 20:30:33 +00:00
config . sgb_revision_name = ( gchar * ) gtk_combo_box_get_active_id ( box ) ;
2019-09-30 14:40:55 +00:00
}
2019-09-23 22:34:21 +00:00
2019-09-30 14:40:55 +00:00
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 ( ) ;
2020-04-11 16:24:55 +00:00
}
2019-10-07 22:36:16 +00:00
G_MODULE_EXPORT void console_on_enter ( GtkWidget * w , gpointer user_data_gptr ) {
GtkEntry * input = GTK_ENTRY ( w ) ;
const gchar * _text = gtk_entry_get_text ( input ) ;
2019-10-14 21:46:03 +00:00
gchar * text = g_strdup ( _text ) ;
if ( g_strcmp0 ( " " , text ) = = 0 & & g_strcmp0 ( " " , last_console_input ) < 0 ) {
text = g_strdup ( last_console_input ) ;
}
else if ( text ) {
if ( last_console_input ! = NULL ) g_free ( last_console_input ) ;
last_console_input = g_strdup ( text ) ;
}
if ( ! in_sync_input ) {
console_log ( & gb , " > " , 0 ) ;
}
console_log ( & gb , text , 0 ) ;
console_log ( & gb , " \n " , 0 ) ;
2019-10-07 22:36:16 +00:00
g_mutex_lock ( & debugger_input_mutex ) ;
g_ptr_array_add ( debugger_input_queue , ( gpointer ) text ) ;
g_cond_signal ( & debugger_input_cond ) ;
g_mutex_unlock ( & debugger_input_mutex ) ;
2019-10-14 21:46:03 +00:00
// clear input
gtk_entry_set_text ( input , " " ) ;
2019-10-07 22:36:16 +00:00
}
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-30 15:18:58 +00:00
return 0xFF000000 | ( r < < 16 ) | ( g < < 8 ) | b ;
2019-09-29 23:49:41 +00:00
}
2019-09-30 14:40:55 +00:00
static 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 ) ;
2020-04-10 02:10:28 +00:00
2020-04-10 02:07:01 +00:00
GB_frame_blending_mode_t mode = get_frame_blending_mode ( ) ;
if ( ! previous ) {
mode = GB_FRAME_BLENDING_MODE_DISABLED ;
}
else if ( mode = = GB_FRAME_BLENDING_MODE_ACCURATE ) {
if ( GB_is_sgb ( & gb ) ) {
mode = GB_FRAME_BLENDING_MODE_SIMPLE ;
}
else {
mode = GB_is_odd_frame ( & gb ) ? GB_FRAME_BLENDING_MODE_ACCURATE_ODD : GB_FRAME_BLENDING_MODE_ACCURATE_EVEN ;
}
}
render_bitmap_with_shader (
& shader , _pixels , previous ,
GB_get_screen_width ( & gb ) , GB_get_screen_height ( & gb ) ,
viewport . x , viewport . y , viewport . w , viewport . h ,
mode
) ;
2019-09-30 14:40:55 +00:00
}
static void update_viewport ( void ) {
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 ) ;
double x_factor = win_width / ( double ) GB_get_screen_width ( & gb ) ;
double y_factor = win_height / ( double ) GB_get_screen_height ( & gb ) ;
if ( config . use_integer_scaling ) {
x_factor = ( int ) ( x_factor ) ;
y_factor = ( int ) ( y_factor ) ;
}
if ( config . keep_aspect_ratio ) {
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 ) ;
2019-10-03 01:10:48 +00:00
viewport = ( Rect ) {
2019-09-30 14:40:55 +00:00
( win_width - new_width ) / 2 ,
( win_height - new_height ) / 2 ,
new_width ,
new_height
} ;
2019-10-03 01:10:48 +00:00
if ( ! fallback_canvas ) glViewport ( viewport . x , viewport . y , viewport . w , viewport . h ) ;
2019-09-30 14:40:55 +00:00
}
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 ) ;
2020-04-10 02:10:28 +00:00
2019-09-22 22:47:42 +00:00
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-10-12 21:11:26 +00:00
if ( image_buffers [ 0 ] ) g_free ( image_buffers [ 0 ] ) ;
if ( image_buffers [ 1 ] ) g_free ( image_buffers [ 1 ] ) ;
if ( image_buffers [ 2 ] ) g_free ( image_buffers [ 2 ] ) ;
2019-09-22 22:47:42 +00:00
size_t buffer_size = sizeof ( image_buffers [ 0 ] [ 0 ] ) * GB_get_screen_width ( & gb ) * GB_get_screen_height ( & gb ) ;
2019-10-12 21:11:26 +00:00
image_buffers [ 0 ] = g_malloc0 ( buffer_size ) ;
image_buffers [ 1 ] = g_malloc0 ( buffer_size ) ;
image_buffers [ 2 ] = g_malloc0 ( buffer_size ) ;
if ( GB_is_inited ( & gb ) ) {
GB_set_pixels_output ( & gb , get_pixels ( ) ) ;
}
2020-04-10 01:22:16 +00:00
}
2019-09-30 14:40:55 +00:00
static void handle_events ( GB_gameboy_t * gb ) {
2020-04-10 01:25:53 +00:00
SDL_GameControllerUpdate ( ) ;
uint8_t controller_state = 0 ;
if ( controller ) {
int16_t x_axis = SDL_GameControllerGetAxis ( controller , SDL_CONTROLLER_AXIS_LEFTX ) ;
int16_t y_axis = SDL_GameControllerGetAxis ( controller , SDL_CONTROLLER_AXIS_LEFTY ) ;
if ( x_axis > = JOYSTICK_HIGH ) {
controller_state | = BUTTON_MASK_RIGHT ;
}
else if ( x_axis < = - JOYSTICK_HIGH ) {
controller_state | = BUTTON_MASK_LEFT ;
}
if ( y_axis > = JOYSTICK_HIGH ) {
controller_state | = BUTTON_MASK_DOWN ;
}
else if ( y_axis < = - JOYSTICK_HIGH ) {
controller_state | = BUTTON_MASK_UP ;
}
if ( SDL_GameControllerGetButton ( controller , SDL_CONTROLLER_BUTTON_DPAD_RIGHT ) ) controller_state | = BUTTON_MASK_RIGHT ;
if ( SDL_GameControllerGetButton ( controller , SDL_CONTROLLER_BUTTON_DPAD_LEFT ) ) controller_state | = BUTTON_MASK_LEFT ;
if ( SDL_GameControllerGetButton ( controller , SDL_CONTROLLER_BUTTON_DPAD_UP ) ) controller_state | = BUTTON_MASK_UP ;
if ( SDL_GameControllerGetButton ( controller , SDL_CONTROLLER_BUTTON_DPAD_DOWN ) ) controller_state | = BUTTON_MASK_DOWN ;
if ( SDL_GameControllerGetButton ( controller , SDL_CONTROLLER_BUTTON_A ) ) controller_state | = BUTTON_MASK_A ;
if ( SDL_GameControllerGetButton ( controller , SDL_CONTROLLER_BUTTON_B ) ) controller_state | = BUTTON_MASK_B ;
if ( SDL_GameControllerGetButton ( controller , SDL_CONTROLLER_BUTTON_BACK ) ) controller_state | = BUTTON_MASK_SELECT ;
if ( SDL_GameControllerGetButton ( controller , SDL_CONTROLLER_BUTTON_START ) ) controller_state | = BUTTON_MASK_START ;
}
GB_set_key_state ( gb , GB_KEY_RIGHT , ( pressed_buttons & BUTTON_MASK_RIGHT ) | ( controller_state & BUTTON_MASK_RIGHT ) ) ;
GB_set_key_state ( gb , GB_KEY_LEFT , ( pressed_buttons & BUTTON_MASK_LEFT ) | ( controller_state & BUTTON_MASK_LEFT ) ) ;
GB_set_key_state ( gb , GB_KEY_UP , ( pressed_buttons & BUTTON_MASK_UP ) | ( controller_state & BUTTON_MASK_UP ) ) ;
GB_set_key_state ( gb , GB_KEY_DOWN , ( pressed_buttons & BUTTON_MASK_DOWN ) | ( controller_state & BUTTON_MASK_DOWN ) ) ;
GB_set_key_state ( gb , GB_KEY_A , ( pressed_buttons & BUTTON_MASK_A ) | ( controller_state & BUTTON_MASK_A ) ) ;
GB_set_key_state ( gb , GB_KEY_B , ( pressed_buttons & BUTTON_MASK_B ) | ( controller_state & BUTTON_MASK_B ) ) ;
GB_set_key_state ( gb , GB_KEY_SELECT , ( pressed_buttons & BUTTON_MASK_SELECT ) | ( controller_state & BUTTON_MASK_SELECT ) ) ;
GB_set_key_state ( gb , GB_KEY_START , ( pressed_buttons & BUTTON_MASK_START ) | ( controller_state & BUTTON_MASK_START ) ) ;
2019-09-30 14:40:55 +00:00
}
2019-10-04 22:57:03 +00:00
static uint32_t convert_color ( uint16_t color ) {
const uint8_t r = ( ( uint16_t ) ( color & 0x1F ) * 255 ) / 31 ;
const uint8_t g = ( ( uint16_t ) ( ( color > > 5 ) & 0x1F ) * 255 ) / 31 ;
const uint8_t b = ( ( uint16_t ) ( ( color > > 10 ) & 0x1F ) * 255 ) / 31 ;
return ( r < < 16 ) | ( g < < 8 ) | b ;
}
static void palette_color_data_func ( GtkTreeViewColumn * col , GtkCellRenderer * renderer , GtkTreeModel * model , GtkTreeIter * iter , gpointer user_data ) {
const gchar * title = gtk_tree_view_column_get_title ( col ) ;
const uint8_t color_index = g_ascii_strtoll ( & title [ 6 ] , NULL , 10 ) ;
const uint8_t column_index = 2 + ( 2 * color_index ) ;
GValue color_val = G_VALUE_INIT ;
gtk_tree_model_get_value ( model , iter , column_index , & color_val ) ;
gint color = g_value_get_int ( & color_val ) ;
gchar * color_string = g_strdup_printf ( " #%06x " , color ) ;
gint lightness = 0.299 * ( ( color > > 16 ) & 0xFF ) + 0.587 * ( ( color > > 8 ) & 0xFF ) + 0.114 * ( color & 0xFF ) ;
GValue color_str = G_VALUE_INIT ;
g_value_init ( & color_str , G_TYPE_STRING ) ;
g_value_set_string ( & color_str , color_string ) ;
g_object_set_property ( G_OBJECT ( renderer ) , " background " , & color_str ) ;
GValue fg_color_str = G_VALUE_INIT ;
g_value_init ( & fg_color_str , G_TYPE_STRING ) ;
g_value_set_static_string ( & fg_color_str , ( lightness > 0x7F ) ? " #000000 " : " #FFFFFF " ) ;
g_object_set_property ( G_OBJECT ( renderer ) , " foreground " , & fg_color_str ) ;
g_value_unset ( & color_val ) ;
g_value_unset ( & color_str ) ;
g_value_unset ( & fg_color_str ) ;
g_free ( color_string ) ;
}
2019-10-05 19:09:30 +00:00
static void on_vblank ( gpointer data ) {
2019-10-14 19:28:39 +00:00
if ( ! vram_viewer_updating & & vram_viewer_visible ) {
vram_viewer_updating = true ;
2019-10-03 21:26:40 +00:00
2019-10-14 19:28:39 +00:00
if ( g_strcmp0 ( " vram_viewer_sprites " , vram_viewer_active_tab ) = = 0 ) {
2019-10-03 23:40:50 +00:00
GtkTreeIter iter ;
2019-10-04 22:57:03 +00:00
GtkTreeView * tree_view = builder_get ( GTK_TREE_VIEW , " vram_viewer_sprites " ) ;
// gtk_tree_view_set_model(tree_view, NULL); // Do we need this?
GtkListStore * store = gtk_list_store_new ( 7 ,
GDK_TYPE_PIXBUF , // Preview image
G_TYPE_STRING , // X position
G_TYPE_STRING , // Y position
G_TYPE_STRING , // Tile
G_TYPE_STRING , // Tile Address
G_TYPE_STRING , // OAM Address
G_TYPE_STRING // Attributes
) ;
2019-10-03 23:40:50 +00:00
gtk_tree_model_get_iter_first ( GTK_TREE_MODEL ( store ) , & iter ) ;
for ( unsigned row = 0 ; row < oamCount ; + + row ) {
GdkPixbuf * pixbuf = gdk_pixbuf_new_from_bytes (
g_bytes_new ( oamInfo [ row ] . image , 128 * sizeof ( uint32_t ) ) ,
GDK_COLORSPACE_RGB , true , 8 , 8 , oamHeight , 8 * sizeof ( uint32_t )
) ;
2020-04-10 02:10:28 +00:00
2019-10-03 23:40:50 +00:00
GdkPixbuf * dest = gdk_pixbuf_new ( GDK_COLORSPACE_RGB , true , 8 , 8 * 2 , oamHeight * 2 ) ;
gdk_pixbuf_scale ( pixbuf , dest ,
0 , 0 , 8 * 2 , oamHeight * 2 ,
0 , 0 , 2.0 , 2.0 ,
GDK_INTERP_NEAREST
) ;
gtk_list_store_insert_with_values ( store , & iter , - 1 ,
0 , dest ,
1 , itoa ( oamInfo [ row ] . x - 8 ) ,
2 , itoa ( oamInfo [ row ] . y - 16 ) ,
3 , g_strdup_printf ( " $%02x " , oamInfo [ row ] . tile ) ,
4 , g_strdup_printf ( " $%04x " , 0x8000 + oamInfo [ row ] . tile * 0x10 ) ,
5 , g_strdup_printf ( " $%04x " , oamInfo [ row ] . oam_addr ) ,
2019-10-14 19:28:39 +00:00
6 , vram_viewer_is_cgb
2019-10-03 23:40:50 +00:00
? g_strdup_printf ( " %c%c%c%d%d " ,
oamInfo [ row ] . flags & 0x80 ? ' P ' : ' - ' ,
oamInfo [ row ] . flags & 0x40 ? ' Y ' : ' - ' ,
oamInfo [ row ] . flags & 0x20 ? ' X ' : ' - ' ,
oamInfo [ row ] . flags & 0x08 ? 1 : 0 ,
oamInfo [ row ] . flags & 0x07 )
: g_strdup_printf ( " %c%c%c%d " ,
oamInfo [ row ] . flags & 0x80 ? ' P ' : ' - ' ,
oamInfo [ row ] . flags & 0x40 ? ' Y ' : ' - ' ,
oamInfo [ row ] . flags & 0x20 ? ' X ' : ' - ' ,
oamInfo [ row ] . flags & 0x10 ? 1 : 0 ) ,
- 1
) ;
2019-10-04 22:57:03 +00:00
g_object_unref ( pixbuf ) ;
g_object_unref ( dest ) ;
2019-10-03 23:40:50 +00:00
}
2019-10-04 22:57:03 +00:00
gtk_tree_view_set_model ( tree_view , GTK_TREE_MODEL ( store ) ) ;
g_object_unref ( store ) ;
2019-10-03 21:26:40 +00:00
}
2019-10-14 19:28:39 +00:00
else if ( g_strcmp0 ( " vram_viewer_palettes " , vram_viewer_active_tab ) = = 0 ) {
2019-10-04 22:57:03 +00:00
GtkTreeIter iter ;
GtkTreeView * tree_view = builder_get ( GTK_TREE_VIEW , " vram_viewer_palettes " ) ;
// gtk_tree_view_set_model(tree_view, NULL); // Do we need this?
GtkListStore * store = gtk_list_store_new ( 9 ,
G_TYPE_STRING , // Name
G_TYPE_STRING , // Color 0 string
G_TYPE_INT , // Color 0 integer
G_TYPE_STRING , // Color 1 string
G_TYPE_INT , // Color 1 integer
2019-10-03 21:26:40 +00:00
2019-10-04 22:57:03 +00:00
G_TYPE_STRING , // Color 2 string
G_TYPE_INT , // Color 2 integer
G_TYPE_STRING , // Color 3 string
G_TYPE_INT // Color 3 integer
) ;
gtk_tree_model_get_iter_first ( GTK_TREE_MODEL ( store ) , & iter ) ;
for ( unsigned row = 0 ; row < 16 ; + + row ) {
uint8_t offset = ( row & 7 ) * 4 ;
2019-10-14 19:28:39 +00:00
uint16_t color_0 = ( vram_viewer_palette_data [ row ] [ ( ( 0 + offset ) < < 1 ) + 1 ] < < 8 ) | vram_viewer_palette_data [ row ] [ ( ( 0 + offset ) < < 1 ) ] ;
uint16_t color_1 = ( vram_viewer_palette_data [ row ] [ ( ( 1 + offset ) < < 1 ) + 1 ] < < 8 ) | vram_viewer_palette_data [ row ] [ ( ( 1 + offset ) < < 1 ) ] ;
uint16_t color_2 = ( vram_viewer_palette_data [ row ] [ ( ( 2 + offset ) < < 1 ) + 1 ] < < 8 ) | vram_viewer_palette_data [ row ] [ ( ( 2 + offset ) < < 1 ) ] ;
uint16_t color_3 = ( vram_viewer_palette_data [ row ] [ ( ( 3 + offset ) < < 1 ) + 1 ] < < 8 ) | vram_viewer_palette_data [ row ] [ ( ( 3 + offset ) < < 1 ) ] ;
2019-10-04 22:57:03 +00:00
gtk_list_store_insert_with_values ( store , & iter , - 1 ,
0 , g_strdup_printf ( " %s %d " , row > = 8 ? " Object " : " Background " , row & 7 ) ,
1 , g_strdup_printf ( " $%04x " , color_0 & 0x7FFF ) ,
2 , convert_color ( color_0 ) ,
3 , g_strdup_printf ( " $%04x " , color_1 & 0x7FFF ) ,
4 , convert_color ( color_1 ) ,
5 , g_strdup_printf ( " $%04x " , color_2 & 0x7FFF ) ,
6 , convert_color ( color_2 ) ,
7 , g_strdup_printf ( " $%04x " , color_3 & 0x7FFF ) ,
8 , convert_color ( color_3 ) ,
- 1
) ;
}
2019-10-14 19:28:39 +00:00
2019-10-04 22:57:03 +00:00
GtkTreeViewColumn * column_0 = gtk_tree_view_get_column ( tree_view , 1 ) ;
GtkTreeViewColumn * column_1 = gtk_tree_view_get_column ( tree_view , 2 ) ;
GtkTreeViewColumn * column_2 = gtk_tree_view_get_column ( tree_view , 3 ) ;
GtkTreeViewColumn * column_3 = gtk_tree_view_get_column ( tree_view , 4 ) ;
GtkCellRendererText * cell_renderer_0 = builder_get ( GTK_CELL_RENDERER_TEXT , " vram_viewer_palette_cell_renderer_0 " ) ;
GtkCellRendererText * cell_renderer_1 = builder_get ( GTK_CELL_RENDERER_TEXT , " vram_viewer_palette_cell_renderer_1 " ) ;
GtkCellRendererText * cell_renderer_2 = builder_get ( GTK_CELL_RENDERER_TEXT , " vram_viewer_palette_cell_renderer_2 " ) ;
GtkCellRendererText * cell_renderer_3 = builder_get ( GTK_CELL_RENDERER_TEXT , " vram_viewer_palette_cell_renderer_3 " ) ;
gtk_tree_view_column_set_cell_data_func ( column_0 , GTK_CELL_RENDERER ( cell_renderer_0 ) , palette_color_data_func , NULL , NULL ) ;
gtk_tree_view_column_set_cell_data_func ( column_1 , GTK_CELL_RENDERER ( cell_renderer_1 ) , palette_color_data_func , NULL , NULL ) ;
gtk_tree_view_column_set_cell_data_func ( column_2 , GTK_CELL_RENDERER ( cell_renderer_2 ) , palette_color_data_func , NULL , NULL ) ;
gtk_tree_view_column_set_cell_data_func ( column_3 , GTK_CELL_RENDERER ( cell_renderer_3 ) , palette_color_data_func , NULL , NULL ) ;
gtk_tree_view_set_model ( tree_view , GTK_TREE_MODEL ( store ) ) ;
g_object_unref ( store ) ;
2019-10-03 21:26:40 +00:00
}
2019-10-03 01:10:48 +00:00
2019-09-30 14:40:55 +00:00
// Queue a redraw of the VRAM viewer
gtk_widget_queue_draw ( GTK_WIDGET ( vram_viewer ) ) ;
2019-10-14 19:28:39 +00:00
vram_viewer_updating = false ;
}
// Queue drawing of the current frame
if ( fallback_canvas ) {
gtk_widget_queue_draw ( GTK_WIDGET ( main_window ) ) ;
}
else if ( gl_area ) {
gtk_gl_area_queue_render ( gl_area ) ;
2019-09-30 14:40:55 +00:00
}
}
2019-10-05 19:09:30 +00:00
static void vblank ( GB_gameboy_t * gb ) {
flip ( ) ;
2020-04-10 19:42:03 +00:00
if ( border_mode_changed ) {
GB_set_border_mode ( gb , get_display_border_mode ( ) ) ;
update_window_geometry ( ) ;
2020-04-10 20:19:40 +00:00
2020-04-10 19:42:03 +00:00
border_mode_changed = false ;
}
2019-10-05 19:09:30 +00:00
GB_set_pixels_output ( gb , get_pixels ( ) ) ;
2019-10-14 16:25:40 +00:00
if ( underclock_down & & clock_mutliplier > 0.5 ) {
clock_mutliplier - = 1.0 / 16 ;
GB_set_clock_multiplier ( gb , clock_mutliplier ) ;
}
else if ( ! underclock_down & & clock_mutliplier < 1.0 ) {
clock_mutliplier + = 1.0 / 16 ;
GB_set_clock_multiplier ( gb , clock_mutliplier ) ;
}
2019-10-14 19:28:39 +00:00
if ( g_strcmp0 ( " vram_viewer_tileset " , vram_viewer_active_tab ) = = 0 ) {
const gchar * palette_id = gtk_combo_box_get_active_id ( builder_get ( GTK_COMBO_BOX , " vram_viewer_tileset_palette_selector " ) ) ;
GB_palette_type_t palette_type = g_str_has_prefix ( palette_id , " bg " ) ? GB_PALETTE_BACKGROUND : GB_PALETTE_OAM ;
uint8_t palette_index = g_ascii_digit_value ( palette_id [ palette_type = = GB_PALETTE_OAM ? 3 : 2 ] ) ;
GB_draw_tileset ( gb , tileset_buffer ,
palette_type ,
palette_index
) ;
}
else if ( g_strcmp0 ( " vram_viewer_tilemap " , vram_viewer_active_tab ) = = 0 ) {
const gchar * palette_id = gtk_combo_box_get_active_id ( builder_get ( GTK_COMBO_BOX , " vram_viewer_tilemap_palette_selector " ) ) ;
uint8_t palette_index = 0 ;
GB_palette_type_t palette_type = GB_PALETTE_AUTO ;
if ( g_strcmp0 ( " auto " , palette_id ) ! = 0 ) {
palette_type = g_str_has_prefix ( palette_id , " bg " ) ? GB_PALETTE_BACKGROUND : GB_PALETTE_OAM ;
palette_index = g_ascii_digit_value ( palette_id [ palette_type = = GB_PALETTE_OAM ? 3 : 2 ] ) ;
}
GB_map_type_t map_type = GB_MAP_AUTO ;
const gchar * map_type_id = gtk_combo_box_get_active_id ( builder_get ( GTK_COMBO_BOX , " vram_viewer_tilemap_tilemap_selector " ) ) ;
if ( g_strcmp0 ( " auto " , map_type_id ) ! = 0 ) {
map_type = ( g_strcmp0 ( " 9800 " , map_type_id ) = = 0 ) ? GB_MAP_9800 : GB_MAP_9C00 ;
}
GB_tileset_type_t tileset_type = GB_TILESET_AUTO ;
const gchar * tileset_type_id = gtk_combo_box_get_active_id ( builder_get ( GTK_COMBO_BOX , " vram_viewer_tilemap_tileset_selector " ) ) ;
if ( g_strcmp0 ( " auto " , tileset_type_id ) ! = 0 ) {
tileset_type = ( g_strcmp0 ( " 8800 " , tileset_type_id ) = = 0 ) ? GB_TILESET_8800 : GB_TILESET_8000 ;
}
GB_draw_tilemap ( gb , tilemap_buffer ,
palette_type ,
palette_index ,
map_type ,
tileset_type
) ;
scrollRect = ( Rect ) {
GB_read_memory ( gb , 0xFF00 | GB_IO_SCX ) ,
GB_read_memory ( gb , 0xFF00 | GB_IO_SCY ) ,
160 , 144
} ;
}
else if ( g_strcmp0 ( " vram_viewer_sprites " , vram_viewer_active_tab ) = = 0 ) {
oamCount = GB_get_oam_info ( gb , oamInfo , & oamHeight ) ;
vram_viewer_is_cgb = GB_is_cgb ( gb ) ;
}
else if ( g_strcmp0 ( " vram_viewer_palettes " , vram_viewer_active_tab ) = = 0 ) {
size_t size ;
for ( unsigned row = 0 ; row < 16 ; + + row ) {
uint8_t * palette_data = GB_get_direct_access ( gb , row > = 8 ? GB_DIRECT_ACCESS_OBP : GB_DIRECT_ACCESS_BGP , & size , NULL ) ;
memcpy ( vram_viewer_palette_data [ row ] , palette_data , size ) ;
}
}
2019-10-14 16:25:40 +00:00
do_rewind = rewind_down ;
2019-10-14 19:28:39 +00:00
g_idle_add ( ( GSourceFunc ) on_vblank , NULL ) ;
2019-10-05 19:09:30 +00:00
}
2019-10-12 21:11:26 +00:00
static void run ( GuiData * gui_data ) {
if ( running ) return ;
while ( stopping ) ;
2020-04-10 01:22:16 +00:00
2019-10-12 21:11:26 +00:00
g_thread_new ( " CoreLoop " , run_thread , gui_data ) ;
}
2019-09-22 22:47:42 +00:00
2019-10-12 21:11:26 +00:00
static gpointer run_thread ( gpointer gui_data_gptr ) {
GuiData * gui_data = gui_data_gptr ;
2019-09-22 22:47:42 +00:00
2020-04-11 16:55:21 +00:00
if ( ! gui_data - > file ) return NULL ;
2020-04-11 22:38:18 +00:00
2019-10-12 21:11:26 +00:00
if ( gui_data - > stopped ) {
start ( gui_data ) ;
2020-04-10 01:22:16 +00:00
}
else {
2019-10-12 21:11:26 +00:00
init ( gui_data ) ;
reset ( gui_data ) ;
start ( gui_data ) ;
}
2020-04-10 01:25:53 +00:00
2019-10-12 21:11:26 +00:00
return NULL ;
}
2020-04-11 22:38:18 +00:00
2019-10-12 21:11:26 +00:00
static void init ( GuiData * gui_data ) {
if ( GB_is_inited ( & gb ) ) return ;
2019-10-14 14:01:51 +00:00
GB_init ( & gb , get_model ( ) ) ;
2019-10-12 21:11:26 +00:00
GB_set_vblank_callback ( & gb , vblank ) ;
GB_set_pixels_output ( & gb , get_current_buffer ( ) ) ;
GB_set_rgb_encode_callback ( & gb , rgb_encode ) ;
GB_set_sample_rate ( & gb , gui_data - > sample_rate ) ;
GB_set_color_correction_mode ( & gb , get_color_correction_mode ( ) ) ;
GB_set_highpass_filter_mode ( & gb , get_highpass_mode ( ) ) ;
GB_set_rewind_length ( & gb , config . rewind_duration ) ;
GB_set_update_input_hint_callback ( & gb , handle_events ) ;
GB_apu_set_sample_callback ( & gb , gb_audio_callback ) ;
GB_set_input_callback ( & gb , sync_console_input ) ;
GB_set_async_input_callback ( & gb , async_console_input ) ;
GB_set_log_callback ( & gb , console_log ) ;
2020-04-10 16:10:09 +00:00
GB_set_boot_rom_load_callback ( & gb , load_boot_rom ) ;
2020-04-10 19:42:03 +00:00
if ( get_display_border_mode ( ) < = GB_BORDER_ALWAYS ) {
GB_set_border_mode ( & gb , get_display_border_mode ( ) ) ;
}
update_window_geometry ( ) ;
2019-10-12 21:11:26 +00:00
}
2020-04-10 16:10:09 +00:00
static void load_boot_rom ( GB_gameboy_t * gb , GB_boot_rom_t type ) {
2019-10-14 14:01:51 +00:00
GError * error = NULL ;
char * boot_rom_path = NULL ;
GBytes * boot_rom_f = NULL ;
2020-04-10 01:22:16 +00:00
const guchar * boot_rom_data ;
gsize boot_rom_size ;
2020-04-10 16:10:09 +00:00
static const char * const names [ ] = {
[ GB_BOOT_ROM_DMG0 ] = " dmg0_boot.bin " ,
[ GB_BOOT_ROM_DMG ] = " dmg_boot.bin " ,
[ GB_BOOT_ROM_MGB ] = " mgb_boot.bin " ,
[ GB_BOOT_ROM_SGB ] = " sgb_boot.bin " ,
[ GB_BOOT_ROM_SGB2 ] = " sgb2_boot.bin " ,
[ GB_BOOT_ROM_CGB0 ] = " cgb0_boot.bin " ,
[ GB_BOOT_ROM_CGB ] = " cgb_boot.bin " ,
[ GB_BOOT_ROM_AGB ] = " agb_boot.bin " ,
} ;
const char * const boot_rom_name = names [ type ] ;
if ( gui_data . cli_options . boot_rom_path ! = NULL ) {
g_message ( " [CLI override] Trying to load boot ROM from %s " , gui_data . cli_options . boot_rom_path ) ;
if ( GB_load_boot_rom ( gb , gui_data . cli_options . boot_rom_path ) ) {
2020-04-10 02:07:01 +00:00
g_warning ( " Falling back to boot ROM from config " ) ;
2019-09-26 18:31:58 +00:00
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-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 ) ;
2020-04-10 02:07:01 +00:00
g_message ( " Trying to load boot ROM from %s " , boot_rom_path ) ;
2020-04-10 02:10:28 +00:00
2020-04-10 16:10:09 +00:00
if ( GB_load_boot_rom ( gb , boot_rom_path ) ) {
2019-10-12 21:11:26 +00:00
g_free ( boot_rom_path ) ;
2020-04-10 02:07:01 +00:00
g_warning ( " Falling back to internal boot ROM " ) ;
2019-09-26 18:31:58 +00:00
goto internal_boot_rom ;
}
2020-04-10 02:10:28 +00:00
2019-10-12 21:11:26 +00:00
g_free ( boot_rom_path ) ;
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 ) ;
2020-04-10 19:42:03 +00:00
2020-04-10 16:10:09 +00:00
g_message ( " Loading internal boot ROM: %s " , boot_rom_path ) ;
2019-10-12 21:11:26 +00:00
g_free ( boot_rom_path ) ;
2019-09-26 18:31:58 +00:00
if ( boot_rom_f = = NULL ) {
2020-04-10 02:07:01 +00:00
g_warning ( " Failed to load internal boot ROM: %s " , boot_rom_path ) ;
2019-09-26 18:31:58 +00:00
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 ) ;
2020-04-10 16:10:09 +00:00
GB_load_boot_rom_from_buffer ( gb , boot_rom_data , boot_rom_size ) ;
2020-04-10 02:10:28 +00:00
2019-10-12 21:11:26 +00:00
g_bytes_unref ( boot_rom_f ) ;
2019-09-26 18:31:58 +00:00
}
2019-09-23 22:34:21 +00:00
}
2019-10-12 21:11:26 +00:00
}
2019-10-05 19:09:30 +00:00
2019-10-12 21:11:26 +00:00
static void stop ( GuiData * gui_data ) {
if ( ! running ) return ;
2020-04-28 16:36:47 +00:00
GB_audio_set_paused ( true ) ;
2019-10-12 21:11:26 +00:00
GB_debugger_set_disabled ( & gb , true ) ;
if ( GB_debugger_is_stopped ( & gb ) ) {
// [self interruptDebugInputRead];
}
stopping = true ;
running = false ;
while ( stopping ) ;
GB_debugger_set_disabled ( & gb , false ) ;
gui_data - > stopped = true ;
}
static void reset ( GuiData * gui_data ) {
2020-04-10 02:07:01 +00:00
g_debug ( " Reset: %d == %d " , get_model ( ) , gui_data - > prev_model ) ;
2019-10-14 14:01:51 +00:00
GB_model_t current_model = get_model ( ) ;
if ( gui_data - > prev_model = = - 1 | | gui_data - > prev_model = = current_model ) {
GB_reset ( & gb ) ;
}
else {
GB_switch_model_and_reset ( & gb , current_model ) ;
}
2020-04-10 20:19:40 +00:00
GB_set_palette ( & gb , get_monochrome_palette ( ) ) ;
2019-10-14 14:01:51 +00:00
gui_data - > prev_model = get_model ( ) ;
2019-10-12 21:11:26 +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-11 22:38:18 +00:00
bool success = false ;
if ( gui_data - > file ) {
char * path = g_file_get_path ( gui_data - > file ) ;
if ( GB_load_rom ( & gb , path ) = = 0 ) {
success = true ;
}
else {
g_warning ( " Failed to load ROM: %s " , path ) ;
}
2019-10-12 21:11:26 +00:00
2020-04-11 22:38:18 +00:00
g_free ( path ) ;
2019-10-12 21:11:26 +00:00
}
2020-04-11 22:38:18 +00:00
action_set_enabled ( main_application , " close " , success ) ;
action_entries_set_enabled ( emulation_entries , G_N_ELEMENTS ( emulation_entries ) , success ) ;
2019-10-12 21:11:26 +00:00
}
static void start ( GuiData * gui_data ) {
running = true ;
gui_data - > stopped = false ;
2020-04-28 16:36:47 +00:00
GB_audio_set_paused ( false ) ;
2019-10-12 21:11:26 +00:00
/* Run emulation */
while ( running ) {
if ( rewind_paused ) {
handle_events ( & gb ) ;
2019-10-14 16:25:40 +00:00
g_usleep ( G_USEC_PER_SEC / 8 ) ;
2019-10-12 21:11:26 +00:00
}
else {
if ( do_rewind ) {
GB_rewind_pop ( & gb ) ;
if ( turbo_down ) {
2020-04-10 01:22:16 +00:00
GB_rewind_pop ( & gb ) ;
}
2019-10-12 21:11:26 +00:00
if ( ! GB_rewind_pop ( & gb ) ) {
rewind_paused = true ;
}
do_rewind = false ;
2020-04-10 01:22:16 +00:00
}
2019-10-12 21:11:26 +00:00
GB_run ( & gb ) ;
2020-04-10 01:22:16 +00:00
}
}
2019-10-05 19:09:30 +00:00
2019-10-12 21:11:26 +00:00
stopping = false ;
2020-04-10 01:22:16 +00:00
}