2020-04-30 01:36:02 +00:00
# define G_LOG_USE_STRUCTURED
# include <gtk/gtk.h>
# include <signal.h>
# include <stdbool.h>
# include <stdio.h>
# include <string.h>
2020-04-30 04:02:48 +00:00
2020-04-30 01:36:02 +00:00
# include <Core/gb.h>
2020-05-16 15:48:29 +00:00
# include "types.h"
2020-05-16 14:56:09 +00:00
# include "config.h"
2020-05-16 15:48:29 +00:00
# include "util.h"
2020-04-30 01:36:02 +00:00
# include "shader.h"
2020-05-15 22:23:03 +00:00
# include "check_menu_radio_group.h"
2020-04-30 01:36:02 +00:00
2020-05-17 03:33:41 +00:00
# include "vram_viewer.h"
2020-04-30 01:36:02 +00:00
// used for audio and game controllers
# include "SDL.h"
2020-04-28 21:42:57 +00:00
# include "../SDL/audio/audio.h"
2020-04-10 01:25:53 +00:00
2020-04-30 01:36:02 +00:00
# define JOYSTICK_HIGH 0x4000
# define JOYSTICK_LOW 0x3800
# define BUTTON_MASK_A 0x01
# define BUTTON_MASK_B 0x02
# define BUTTON_MASK_START 0x04
# define BUTTON_MASK_SELECT 0x08
# define BUTTON_MASK_UP 0x10
# define BUTTON_MASK_DOWN 0x20
# define BUTTON_MASK_LEFT 0x40
# define BUTTON_MASK_RIGHT 0x80
2020-04-30 02:53:45 +00:00
# define str(x) #x
# define xstr(x) str(x)
# define get_object(id) gtk_builder_get_object(gui_data.builder, id)
# define builder_get(type, id) type(get_object(id))
# define action_set_enabled(map, name, value) g_simple_action_set_enabled(G_SIMPLE_ACTION(g_action_map_lookup_action(G_ACTION_MAP(map), name)), value);
// Initialize the GuiData
static GuiData gui_data = {
. cli_options = {
. fullscreen = false ,
. model = - 1 ,
} ,
2019-09-23 14:35:10 +00:00
2020-04-30 02:53:45 +00:00
. prev_model = - 1 ,
2019-09-25 20:47:33 +00:00
2020-04-30 02:53:45 +00:00
. running = false ,
. stopping = false ,
. stopped = false ,
2019-09-25 20:47:33 +00:00
2020-04-30 02:53:45 +00:00
. in_sync_input = false ,
. log_to_sidebar = false ,
. should_clear_sidebar = false ,
2019-10-03 23:40:50 +00:00
2020-04-30 02:53:45 +00:00
. audio_initialized = false ,
. border_mode_changed = false ,
2019-10-14 21:46:03 +00:00
2020-04-30 02:53:45 +00:00
. underclock_down = false ,
. rewind_down = false ,
. rewind_paused = false ,
. turbo_down = false ,
. clock_mutliplier = 1.0 ,
2020-05-10 22:05:59 +00:00
. analog_clock_multiplier = 1.0 ,
2020-04-30 02:53:45 +00:00
} ;
2020-05-02 16:44:24 +00:00
2020-05-16 15:48:29 +00:00
GB_gameboy_t gb ;
2020-05-02 16:44:24 +00:00
2020-04-30 01:36:02 +00:00
// Forward declarations of the actions
static void activate_open ( GSimpleAction * action , GVariant * parameter , gpointer app ) ;
static void activate_close ( GSimpleAction * action , GVariant * parameter , gpointer app ) ;
static void activate_reset ( GSimpleAction * action , GVariant * parameter , gpointer app ) ;
static void activate_show_console ( GSimpleAction * action , GVariant * parameter , gpointer app ) ;
static void activate_open_gtk_debugger ( GSimpleAction * action , GVariant * parameter , gpointer app ) ;
static void activate_open_memory_viewer ( GSimpleAction * action , GVariant * parameter , gpointer app ) ;
static void activate_open_vram_viewer ( GSimpleAction * action , GVariant * parameter , gpointer app ) ;
static void activate_clear_console ( GSimpleAction * action , GVariant * parameter , gpointer app ) ;
static void activate_quit ( GSimpleAction * action , GVariant * parameter , gpointer app ) ;
static void activate_about ( GSimpleAction * action , GVariant * parameter , gpointer app ) ;
static void activate_preferences ( GSimpleAction * action , GVariant * parameter , gpointer app ) ;
static void on_pause_changed ( GSimpleAction * action , GVariant * value , gpointer user_data_ptr ) ;
static void on_mute_changed ( GSimpleAction * action , GVariant * value , gpointer user_data_ptr ) ;
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 [ ] = {
} ;
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 } ,
2020-05-14 02:22:58 +00:00
// { "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 } ,
} ;
2020-05-03 19:11:21 +00:00
static const char * get_sdl_joystick_power_level_name ( SDL_JoystickPowerLevel level ) {
switch ( level ) {
case SDL_JOYSTICK_POWER_EMPTY : return " Empty " ;
case SDL_JOYSTICK_POWER_LOW : return " Low " ;
case SDL_JOYSTICK_POWER_MEDIUM : return " Medium " ;
case SDL_JOYSTICK_POWER_FULL : return " Full " ;
case SDL_JOYSTICK_POWER_WIRED : return " Wired " ;
case SDL_JOYSTICK_POWER_MAX : return " Max " ;
case SDL_JOYSTICK_POWER_UNKNOWN :
default :
return " Unknown " ;
}
}
2019-09-30 14:40:55 +00:00
// This function gets called after the parsing of the commandline options has occurred.
2020-04-30 01:36:02 +00:00
static gint handle_local_options ( GApplication * app , GVariantDict * options , gpointer null_ptr ) {
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
// 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 " ) ) {
if ( g_str_has_suffix ( model_name , " -B " ) | | g_strcmp0 ( model_name , " DMG " ) = = 0 ) {
2020-04-30 01:36:02 +00:00
gui_data . cli_options . model = GB_MODEL_DMG_B ;
2019-09-30 14:40:55 +00:00
}
else {
2020-04-30 01:36:02 +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 " ) ) {
if ( g_str_has_suffix ( model_name , " -NTSC " ) | | g_strcmp0 ( model_name , " SGB " ) = = 0 ) {
2020-04-30 01:36:02 +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 " ) ) {
2020-04-30 01:36:02 +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 " ) ) {
2020-04-30 01:36:02 +00:00
gui_data . cli_options . model = GB_MODEL_SGB2 ;
2019-09-30 14:40:55 +00:00
}
else {
2020-04-30 01:36:02 +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 " ) ) {
if ( g_str_has_suffix ( model_name , " -C " ) ) {
2020-04-30 01:36:02 +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 ) {
2020-04-30 01:36:02 +00:00
gui_data . cli_options . model = GB_MODEL_CGB_E ;
2019-09-30 14:40:55 +00:00
}
else {
2020-04-30 01:36:02 +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 " ) ) {
2020-04-30 01:36:02 +00:00
gui_data . cli_options . model = GB_MODEL_AGB ;
2019-09-24 18:22:07 +00:00
}
else {
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
}
2020-04-30 01:36:02 +00:00
static gboolean init_controllers ( void ) {
2020-05-02 16:15:57 +00:00
SDL_version compiled ;
SDL_version linked ;
SDL_VERSION ( & compiled ) ;
SDL_GetVersion ( & linked ) ;
g_debug ( " Compiled against SDL version %d.%d.%d " , compiled . major , compiled . minor , compiled . patch ) ;
g_debug ( " Linked against SDL version %d.%d.%d " , linked . major , linked . minor , linked . patch ) ;
2020-05-03 22:02:15 +00:00
g_debug ( " Initializing game controllers " ) ;
2020-04-10 01:25:53 +00:00
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
}
2020-05-03 23:41:14 +00:00
g_debug ( " Initializing haptic feedback " ) ;
if ( SDL_InitSubSystem ( SDL_INIT_HAPTIC ) < 0 ) {
g_warning ( " Failed to initialize haptic feedback support: %s " , SDL_GetError ( ) ) ;
}
2020-04-10 01:25:53 +00:00
2020-05-03 22:02:15 +00:00
g_debug ( " Loading custom game controller database " ) ;
2020-04-10 01:25:53 +00:00
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 ) ;
2020-05-03 19:11:21 +00:00
g_message ( " Number of found controllers: %d " , SDL_NumJoysticks ( ) ) ;
2020-05-02 16:15:57 +00:00
2020-05-03 19:11:21 +00:00
// In the “worst” case all joysticks are valid game controllers
gui_data . controllers = g_malloc0 ( sizeof ( struct Controller_t ) * SDL_NumJoysticks ( ) ) ;
2020-05-02 16:15:57 +00:00
for ( int i = 0 ; i < SDL_NumJoysticks ( ) ; + + i ) {
2020-05-03 19:11:21 +00:00
if ( SDL_IsGameController ( i ) ) {
struct Controller_t * s = & gui_data . controllers [ i ] ;
s - > controller = SDL_GameControllerOpen ( i ) ;
2020-05-02 16:15:57 +00:00
2020-05-03 19:11:21 +00:00
if ( s - > controller ) {
SDL_Joystick * joystick = SDL_GameControllerGetJoystick ( s - > controller ) ;
SDL_JoystickPowerLevel power_level = SDL_JoystickCurrentPowerLevel ( joystick ) ;
2020-05-02 16:15:57 +00:00
2020-05-03 23:41:14 +00:00
if ( SDL_JoystickIsHaptic ( joystick ) ) {
s - > haptic = SDL_HapticOpenFromJoystick ( joystick ) ;
2020-05-03 22:02:15 +00:00
2020-05-03 23:41:14 +00:00
if ( s - > haptic & & SDL_HapticRumbleSupported ( s - > haptic ) ) {
SDL_HapticRumbleInit ( s - > haptic ) ;
}
else {
if ( s - > haptic = = NULL ) {
g_warning ( " %s " , SDL_GetError ( ) ) ;
2020-05-03 22:02:15 +00:00
}
2020-05-03 23:41:14 +00:00
SDL_HapticClose ( s - > haptic ) ;
s - > haptic = NULL ;
2020-05-03 22:02:15 +00:00
}
2020-05-03 23:41:14 +00:00
}
2020-05-02 16:15:57 +00:00
2020-05-04 00:04:28 +00:00
// Blacklist the WUP-028 for now
if ( SDL_JoystickGetVendor ( joystick ) = = 0x057e
& & SDL_JoystickGetProduct ( joystick ) = = 0x0337
& & SDL_JoystickGetProductVersion ( joystick ) = = 0x0100 ) {
s - > ignore_rumble = true ;
}
2020-05-03 19:11:21 +00:00
char guid_str [ 33 ] ;
SDL_JoystickGUID guid = SDL_JoystickGetGUID ( joystick ) ;
SDL_JoystickGetGUIDString ( guid , guid_str , sizeof ( guid_str ) ) ;
2020-05-03 22:02:15 +00:00
g_message ( " Controller #%u (%s): %s; Haptic Feedback: %d; Power level: %s; Player index: %u; Instance ID: %u " , i , guid_str , SDL_GameControllerName ( s - > controller ) , s - > haptic ! = NULL , get_sdl_joystick_power_level_name ( power_level ) , SDL_JoystickGetPlayerIndex ( joystick ) , SDL_JoystickInstanceID ( joystick ) ) ;
2020-05-03 19:11:21 +00:00
gui_data . controller_count + + ;
2020-04-10 01:25:53 +00:00
}
else {
2020-05-02 16:15:57 +00:00
g_warning ( " Could not open controller %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
}
2020-04-30 01:36:02 +00:00
static gboolean init_audio ( void ) {
2020-04-28 21:42:57 +00:00
bool audio_playing = GB_audio_is_playing ( ) ;
2019-10-12 21:11:26 +00:00
2020-04-30 02:53:45 +00:00
if ( gui_data . audio_initialized ) {
2020-04-28 21:42:57 +00:00
GB_audio_destroy ( ) ;
2020-04-30 02:53:45 +00:00
gui_data . audio_initialized = false ;
2020-04-28 21:42:57 +00:00
}
2020-05-03 22:02:15 +00:00
# ifdef USE_SDL_AUDIO
if ( SDL_InitSubSystem ( SDL_INIT_AUDIO ) < 0 ) {
g_warning ( " Failed to initialize audio: %s " , SDL_GetError ( ) ) ;
return false ;
}
# endif
2020-04-10 01:25:53 +00:00
2020-04-28 21:42:57 +00:00
GB_audio_init ( gui_data . sample_rate ) ;
GB_set_sample_rate ( & gb , GB_audio_get_sample_rate ( ) ) ;
// restore playing state
GB_audio_set_paused ( ! audio_playing ) ;
2020-04-10 01:25:53 +00:00
2020-04-30 02:53:45 +00:00
return gui_data . audio_initialized = true ;
2020-04-10 01:25:53 +00:00
}
static void gb_audio_callback ( GB_gameboy_t * gb , GB_sample_t * sample ) {
2020-04-30 02:53:45 +00:00
if ( gui_data . turbo_down ) {
2020-04-10 01:25:53 +00:00
static unsigned skip = 0 ;
skip + + ;
2020-04-28 21:42:57 +00:00
if ( skip = = GB_audio_get_sample_rate ( ) / 8 ) {
2020-04-10 01:25:53 +00:00
skip = 0 ;
}
2020-04-28 21:42:57 +00:00
if ( skip > GB_audio_get_sample_rate ( ) / 16 ) {
2020-04-10 01:25:53 +00:00
return ;
}
}
2020-04-28 21:42:57 +00:00
if ( GB_audio_get_queue_length ( ) / sizeof ( * sample ) > GB_audio_get_sample_rate ( ) / 4 ) {
2020-04-10 01:25:53 +00:00
return ;
}
2020-04-28 21:42:57 +00:00
GB_audio_queue_sample ( sample ) ;
2020-04-10 01:25:53 +00:00
}
2020-05-03 19:11:21 +00:00
static void rumble_callback ( GB_gameboy_t * gb , double amp ) {
2020-05-04 00:04:28 +00:00
if ( ! gui_data . controllers | | gui_data . controller_count = = 0 | | ! gui_data . last_used_controller ) return ;
2020-05-03 19:11:21 +00:00
2020-05-03 22:02:15 +00:00
struct Controller_t * s = gui_data . last_used_controller ;
2020-05-03 19:11:21 +00:00
2020-05-04 00:04:28 +00:00
if ( s - > ignore_rumble ) return ;
2020-05-03 23:41:14 +00:00
if ( s - > haptic ) {
if ( amp > 0.0 ) {
SDL_HapticRumblePlay ( s - > haptic , amp , 100.0 ) ;
2020-05-03 22:02:15 +00:00
}
2020-05-03 23:41:14 +00:00
else {
SDL_HapticRumbleStop ( s - > haptic ) ;
}
}
else {
2020-05-03 22:02:15 +00:00
if ( amp = = 0.0 ) {
SDL_GameControllerRumble ( s - > controller , 0 , 0 , 0 ) ;
2020-05-03 19:11:21 +00:00
}
else {
2020-05-03 22:02:15 +00:00
Uint16 intensity = ( float ) 0xFFFF * amp ;
SDL_GameControllerRumble ( s - > controller , intensity , intensity , 100 ) ;
2020-05-03 19:11:21 +00:00
}
2020-05-03 23:41:14 +00:00
}
2020-05-03 19:11:21 +00:00
}
2020-04-30 01:36:02 +00:00
static void clear_sidebar ( void ) {
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-12 21:11:26 +00:00
2020-04-30 01:36:02 +00:00
static gboolean scroll_to_bottom ( GtkTextView * textview , GtkTextMark * mark ) {
GtkTextBuffer * buffer = gtk_text_view_get_buffer ( textview ) ;
GtkTextIter iter ;
2019-10-07 22:36:16 +00:00
2020-04-30 01:36:02 +00:00
gtk_text_buffer_get_end_iter ( buffer , & iter ) ;
gtk_text_iter_set_line_offset ( & iter , 0 ) ;
2019-10-07 22:36:16 +00:00
2020-04-30 01:36:02 +00:00
gtk_text_buffer_move_mark ( buffer , mark , & iter ) ;
gtk_text_view_scroll_to_mark ( textview , mark , 0.0 , true , 0.0 , 0.10 ) ;
2019-10-07 22:36:16 +00:00
2020-04-30 01:36:02 +00:00
gtk_text_buffer_delete_mark ( buffer , mark ) ;
2019-10-14 21:46:03 +00:00
2020-04-30 01:36:02 +00:00
return true ;
2019-10-07 22:36:16 +00:00
}
2020-04-30 01:36:02 +00:00
static void append_pending_output ( void ) {
2020-04-30 02:53:45 +00:00
g_rec_mutex_lock ( & gui_data . console_output_lock ) ;
2020-01-31 00:53:02 +00:00
2020-04-30 02:53:45 +00:00
if ( gui_data . should_clear_sidebar ) {
2020-04-30 01:36:02 +00:00
clear_sidebar ( ) ;
2020-04-30 02:53:45 +00:00
gui_data . should_clear_sidebar = false ;
2020-04-30 01:36:02 +00:00
}
2020-01-31 00:53:02 +00:00
2020-04-30 02:53:45 +00:00
if ( gtk_text_buffer_get_char_count ( gui_data . pending_console_output ) > 0 ) {
GtkTextView * text_view = builder_get ( GTK_TEXT_VIEW , gui_data . log_to_sidebar ? " console_sidebar_output " : " console_screen " ) ;
2020-04-30 01:36:02 +00:00
GtkTextBuffer * text_buf = gtk_text_view_get_buffer ( text_view ) ;
GtkTextIter start ;
GtkTextIter end ;
2020-04-30 02:53:45 +00:00
gtk_text_buffer_get_start_iter ( gui_data . pending_console_output , & start ) ;
gtk_text_buffer_get_end_iter ( gui_data . pending_console_output , & end ) ;
2019-10-07 22:36:16 +00:00
2020-04-30 01:36:02 +00:00
GtkTextIter iter ;
gtk_text_buffer_get_end_iter ( text_buf , & iter ) ;
gtk_text_buffer_insert_range ( text_buf , & iter , & start , & end ) ;
2019-10-07 22:36:16 +00:00
2020-04-30 01:36:02 +00:00
scroll_to_bottom ( text_view , gtk_text_buffer_create_mark ( text_buf , NULL , & iter , true ) ) ;
2019-10-07 22:36:16 +00:00
2020-04-30 02:53:45 +00:00
gtk_text_buffer_set_text ( gui_data . pending_console_output , " " , - 1 ) ;
2020-04-30 01:36:02 +00:00
}
2019-10-07 22:36:16 +00:00
2020-04-30 02:53:45 +00:00
g_rec_mutex_unlock ( & gui_data . console_output_lock ) ;
2019-10-07 22:36:16 +00:00
}
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-30 02:53:45 +00:00
if ( gui_data . main_thread ! = g_thread_self ( ) ) {
2020-04-10 02:10:28 +00:00
g_idle_add ( ( GSourceFunc ) update_debugger_sidebar , gb ) ;
return ;
}
2020-04-30 02:53:45 +00:00
g_rec_mutex_lock ( & gui_data . console_output_lock ) ;
gui_data . should_clear_sidebar = true ;
2020-04-10 02:10:28 +00:00
append_pending_output ( ) ;
2020-04-30 02:53:45 +00:00
gui_data . log_to_sidebar = true ;
g_rec_mutex_unlock ( & gui_data . console_output_lock ) ;
2020-04-10 02:10:28 +00:00
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-30 02:53:45 +00:00
g_rec_mutex_lock ( & gui_data . console_output_lock ) ;
2020-04-10 02:10:28 +00:00
append_pending_output ( ) ;
2020-04-30 02:53:45 +00:00
gui_data . log_to_sidebar = false ;
g_rec_mutex_unlock ( & gui_data . console_output_lock ) ;
2020-04-10 02:10:28 +00:00
}
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-30 02:53:45 +00:00
g_rec_mutex_lock ( & gui_data . console_output_lock ) ;
2020-04-10 02:10:28 +00:00
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 ;
2020-04-30 02:53:45 +00:00
// Append attributed text to "gui_data.pending_console_output" GtkTextBuffer
gtk_text_buffer_get_end_iter ( gui_data . pending_console_output , & iter ) ;
GtkTextMark * start_mark = gtk_text_buffer_create_mark ( gui_data . pending_console_output , NULL , & iter , true ) ;
gtk_text_buffer_insert ( gui_data . pending_console_output , & iter , g_strdup ( string ) , - 1 ) ;
gtk_text_buffer_get_iter_at_mark ( gui_data . pending_console_output , & start , start_mark ) ;
2020-04-10 02:10:28 +00:00
if ( attributes & GB_LOG_BOLD ) {
2020-04-30 02:53:45 +00:00
gtk_text_buffer_apply_tag_by_name ( gui_data . pending_console_output , " bold " , & start , & iter ) ;
2020-04-10 02:10:28 +00:00
}
if ( attributes & GB_LOG_DASHED_UNDERLINE ) {
2020-04-30 02:53:45 +00:00
gtk_text_buffer_apply_tag_by_name ( gui_data . pending_console_output , " dashed_underline " , & start , & iter ) ;
2020-04-10 02:10:28 +00:00
}
if ( attributes & GB_LOG_UNDERLINE ) {
2020-04-30 02:53:45 +00:00
gtk_text_buffer_apply_tag_by_name ( gui_data . pending_console_output , " underline " , & start , & iter ) ;
2020-04-10 02:10:28 +00:00
}
2020-04-30 02:53:45 +00:00
gtk_text_buffer_delete_mark ( gui_data . 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
2020-04-30 02:53:45 +00:00
g_rec_mutex_unlock ( & gui_data . console_output_lock ) ;
2019-10-07 22:36:16 +00:00
}
2020-04-30 01:36:02 +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
static char * sync_console_input ( GB_gameboy_t * gb ) {
update_debugger_sidebar ( gb ) ;
console_log ( gb , " > " , 0 ) ;
2020-04-30 02:53:45 +00:00
gui_data . in_sync_input = true ;
2020-04-30 01:36:02 +00:00
2020-04-30 02:53:45 +00:00
g_mutex_lock ( & gui_data . debugger_input_mutex ) ;
g_cond_wait ( & gui_data . debugger_input_cond , & gui_data . debugger_input_mutex ) ;
2020-04-30 01:36:02 +00:00
gchar * input = NULL ;
2020-04-30 02:53:45 +00:00
const gchar * _input = g_ptr_array_index ( gui_data . debugger_input_queue , 0 ) ;
2020-04-30 01:36:02 +00:00
input = g_strdup ( _input ) ;
2020-04-30 02:53:45 +00:00
gpointer ptr = g_ptr_array_remove_index ( gui_data . debugger_input_queue , 0 ) ;
2020-04-30 01:36:02 +00:00
if ( ptr ) g_free ( ptr ) ;
2020-04-30 02:53:45 +00:00
g_mutex_unlock ( & gui_data . debugger_input_mutex ) ;
2020-04-30 01:36:02 +00:00
2020-04-30 02:53:45 +00:00
gui_data . in_sync_input = false ;
2020-04-30 01:36:02 +00:00
return input ;
}
static char * async_console_input ( GB_gameboy_t * gb ) {
// TODO: This is rather ugly
g_idle_add ( ( GSourceFunc ) clear_sidebar , NULL ) ;
2020-04-30 02:53:45 +00:00
if ( gui_data . debugger_input_queue - > len = = 0 ) return NULL ;
2020-04-30 01:36:02 +00:00
2020-04-30 02:53:45 +00:00
g_mutex_lock ( & gui_data . debugger_input_mutex ) ;
2020-04-30 01:36:02 +00:00
gchar * input = NULL ;
2020-04-30 02:53:45 +00:00
const gchar * _input = g_ptr_array_index ( gui_data . debugger_input_queue , 0 ) ;
2020-04-30 01:36:02 +00:00
if ( _input ) {
input = g_strdup ( _input ) ;
2020-04-30 02:53:45 +00:00
gpointer ptr = g_ptr_array_remove_index ( gui_data . debugger_input_queue , 0 ) ;
2020-04-30 01:36:02 +00:00
if ( ptr ) g_free ( ptr ) ;
}
2020-04-30 02:53:45 +00:00
g_mutex_unlock ( & gui_data . debugger_input_mutex ) ;
2020-04-30 01:36:02 +00:00
return input ;
}
2020-05-14 20:53:02 +00:00
// Creating these items in the UI defintion files was buggy in some desktop
// environments and the manual call of `g_signal_connect` was needed anyway
// because the UI definition can’ t define string arguments for signal handlers.
static void create_model_menu_items ( ) {
2020-05-15 22:23:03 +00:00
bool on_change_model ( GtkWidget * , gpointer ) ;
2020-04-11 16:24:55 +00:00
2020-05-14 20:53:02 +00:00
static const char * const model_names [ ] = {
" Game Boy " ,
" Super Game Boy " ,
" Game Boy Color " ,
2020-05-15 22:23:03 +00:00
" Game Boy Advance " ,
NULL
2020-05-14 20:53:02 +00:00
} ;
2020-04-11 16:24:55 +00:00
2020-05-14 20:53:02 +00:00
static const char * const model_codes [ ] = {
" DMG " ,
" SGB " ,
" CGB " ,
2020-05-15 22:23:03 +00:00
" GBA " ,
NULL
2020-05-14 20:53:02 +00:00
} ;
2020-04-10 02:10:28 +00:00
2020-05-14 20:53:02 +00:00
// Find the menu item index of the previous sibling of the new menu items
GtkWidget * before = builder_get ( GTK_WIDGET , " before_model_changer " ) ;
GtkContainer * parent = GTK_CONTAINER ( gtk_widget_get_parent ( before ) ) ;
g_autoptr ( GList ) list = gtk_container_get_children ( parent ) ;
gint position = g_list_index ( list , before ) ;
2019-09-23 22:34:21 +00:00
2020-05-15 22:23:03 +00:00
CheckMenuItemGroup * model_group = check_menu_item_group_new ( ( char * * ) model_names , ( char * * ) model_codes ) ;
check_menu_item_group_insert_into_menu_shell ( model_group , GTK_MENU_SHELL ( parent ) , position + 1 ) ;
check_menu_item_group_connect_toggle_signal ( model_group , on_change_model ) ;
check_menu_item_group_activate ( model_group , config . emulation . model ) ;
2019-09-25 20:47:33 +00:00
2020-05-14 20:53:02 +00:00
static const char * const peripheral_names [ ] = {
" None " ,
" Game Boy Printer " ,
2020-05-15 22:23:03 +00:00
NULL
2020-05-14 20:53:02 +00:00
} ;
2019-09-25 20:47:33 +00:00
2020-05-14 20:53:02 +00:00
static const char * const peripheral_codes [ ] = {
" NONE " ,
" PRINTER " ,
2020-05-15 22:23:03 +00:00
NULL ,
2020-05-14 20:53:02 +00:00
} ;
2019-09-28 21:34:18 +00:00
2020-05-15 22:23:03 +00:00
CheckMenuItemGroup * link_group = check_menu_item_group_new ( ( char * * ) peripheral_names , ( char * * ) peripheral_codes ) ;
check_menu_item_group_insert_into_menu_shell ( link_group , GTK_MENU_SHELL ( builder_get ( GTK_MENU_SHELL , " link_menu " ) ) , 0 ) ;
// check_menu_item_group_connect_toggle_signal(link_group, on_change_linked_device);
check_menu_item_group_activate ( link_group , " NONE " ) ;
2019-09-28 21:34:18 +00:00
}
2020-05-14 20:53:02 +00:00
// Create our application’ s menu.
//
// 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 ) {
create_model_menu_items ( ) ;
GtkMenuBar * menubar = builder_get ( GTK_MENU_BAR , " main_menu " ) ;
gtk_box_pack_start ( GTK_BOX ( gui_data . main_window_container ) , GTK_WIDGET ( menubar ) , false , false , 0 ) ;
}
2020-04-30 01:36:02 +00:00
// Determines if a ComboBox entry should be converted into a separator.
// Each element with a text value of `<separator>` will be converted into a separator element.
static gboolean is_separator ( GtkTreeModel * model , GtkTreeIter * iter , gpointer data ) {
gchar * text = NULL ;
gtk_tree_model_get ( model , iter , 0 , & text , - 1 ) ;
gboolean result = g_strcmp0 ( " <separator> " , text ) = = 0 ;
g_free ( text ) ;
return result ;
}
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 how many frame buffers to use
static unsigned char number_of_buffers ( void ) {
2020-04-30 02:53:45 +00:00
if ( gui_data . fallback_canvas ) return 2 ;
2019-10-12 21:11:26 +00:00
2020-05-16 14:56:09 +00:00
bool should_blend = config_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 ) {
2020-04-30 02:53:45 +00:00
return gui_data . image_buffers [ ( gui_data . 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 ) {
2020-04-30 02:53:45 +00:00
return gui_data . image_buffers [ gui_data . 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 ) {
2020-04-30 02:53:45 +00:00
return gui_data . image_buffers [ ( gui_data . 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 ) {
2020-04-30 02:53:45 +00:00
gui_data . current_buffer = ( gui_data . current_buffer + 1 ) % number_of_buffers ( ) ;
2019-09-30 14:40:55 +00:00
}
2019-09-22 22:47:42 +00:00
2020-05-14 20:53:02 +00:00
static void update_viewport ( void ) {
GtkWidget * w = gui_data . fallback_canvas ? GTK_WIDGET ( gui_data . fallback_canvas ) : GTK_WIDGET ( gui_data . 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 . video . use_integer_scaling ) {
x_factor = ( int ) ( x_factor ) ;
y_factor = ( int ) ( y_factor ) ;
}
if ( config . video . keep_aspect_ratio ) {
if ( x_factor > y_factor ) {
x_factor = y_factor ;
}
else {
y_factor = x_factor ;
}
}
2020-05-17 03:33:41 +00:00
if ( gui_data . gl_area ) {
unsigned new_width = x_factor * GB_get_screen_width ( & gb ) ;
unsigned new_height = y_factor * GB_get_screen_height ( & gb ) ;
gui_data . viewport = ( Rect ) {
( win_width - new_width ) / 2 ,
( win_height - new_height ) / 2 ,
new_width ,
new_height
} ;
2020-05-14 20:53:02 +00:00
2020-05-17 03:33:41 +00:00
glViewport ( gui_data . viewport . x , gui_data . viewport . y , gui_data . viewport . width , gui_data . viewport . height ) ;
}
2020-05-14 20:53:02 +00:00
}
2020-04-11 22:38:18 +00:00
// 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 ;
2020-04-30 02:53:45 +00:00
action_set_enabled ( gui_data . main_application , entry - > name , value ) ;
2020-04-11 22:38:18 +00:00
}
}
2020-04-30 01:36:02 +00:00
static void update_window_geometry ( void ) {
2020-05-05 19:03:08 +00:00
g_debug ( " update_window_geometry: %u× %u → %u× %u " , gui_data . last_screen_width , gui_data . last_screen_height , GB_get_screen_width ( & gb ) , GB_get_screen_height ( & gb ) ) ;
GtkWidget * w = gui_data . fallback_canvas ? GTK_WIDGET ( gui_data . fallback_canvas ) : GTK_WIDGET ( gui_data . gl_area ) ;
2020-05-14 20:53:02 +00:00
signed win_width = gtk_widget_get_allocated_width ( w ) ;
signed win_height = gtk_widget_get_allocated_height ( w ) ;
signed menu_height = gtk_widget_get_allocated_height ( builder_get ( GTK_WIDGET , " main_menu " ) ) ;
unsigned _factor = win_width > win_height ? win_width / GB_get_screen_width ( & gb ) : win_height / GB_get_screen_height ( & gb ) ;
unsigned factor = _factor < 2 ? 2 : _factor ;
unsigned new_width = GB_get_screen_width ( & gb ) * factor ;
unsigned new_height = GB_get_screen_height ( & gb ) * factor + menu_height ;
2020-05-05 19:03:08 +00:00
2020-04-30 01:36:02 +00:00
// Set size hints
GdkGeometry hints ;
hints . min_width = GB_get_screen_width ( & gb ) ;
2020-05-14 20:53:02 +00:00
hints . min_height = GB_get_screen_height ( & gb ) + menu_height ;
2020-04-10 01:25:53 +00:00
2020-04-30 01:36:02 +00:00
gtk_window_set_geometry_hints (
2020-04-30 02:53:45 +00:00
GTK_WINDOW ( gui_data . main_window ) ,
2020-04-30 01:36:02 +00:00
NULL ,
& hints ,
( GdkWindowHints ) ( GDK_HINT_MIN_SIZE )
) ;
2019-09-29 23:49:41 +00:00
2020-05-05 19:03:08 +00:00
if ( new_width > win_width | | new_height > win_height ) {
gtk_window_resize ( GTK_WINDOW ( gui_data . main_window ) , new_width , new_height ) ;
}
2020-04-10 01:25:53 +00:00
2020-04-30 01:36:02 +00:00
// Setup our image buffers
2020-04-30 02:53:45 +00:00
if ( gui_data . image_buffers [ 0 ] ) g_free ( gui_data . image_buffers [ 0 ] ) ;
if ( gui_data . image_buffers [ 1 ] ) g_free ( gui_data . image_buffers [ 1 ] ) ;
if ( gui_data . image_buffers [ 2 ] ) g_free ( gui_data . image_buffers [ 2 ] ) ;
2019-09-29 23:49:41 +00:00
2020-04-30 02:53:45 +00:00
size_t buffer_size = sizeof ( gui_data . image_buffers [ 0 ] [ 0 ] ) * GB_get_screen_width ( & gb ) * GB_get_screen_height ( & gb ) ;
2019-09-29 23:49:41 +00:00
2020-04-30 02:53:45 +00:00
gui_data . image_buffers [ 0 ] = g_malloc0 ( buffer_size ) ;
gui_data . image_buffers [ 1 ] = g_malloc0 ( buffer_size ) ;
gui_data . image_buffers [ 2 ] = g_malloc0 ( buffer_size ) ;
2019-09-29 23:49:41 +00:00
2020-05-05 19:03:08 +00:00
gui_data . last_screen_width = GB_get_screen_width ( & gb ) ;
gui_data . last_screen_height = GB_get_screen_height ( & gb ) ;
2020-04-30 01:36:02 +00:00
if ( GB_is_inited ( & gb ) ) {
GB_set_pixels_output ( & gb , get_pixels ( ) ) ;
2019-10-14 22:16:59 +00:00
}
2020-04-30 01:36:02 +00:00
}
2019-10-14 22:16:59 +00:00
2020-04-30 01:36:02 +00:00
static void stop ( void ) {
2020-04-30 02:53:45 +00:00
if ( ! gui_data . running ) return ;
2019-09-29 23:49:41 +00:00
2020-04-30 01:36:02 +00:00
GB_audio_set_paused ( true ) ;
GB_debugger_set_disabled ( & gb , true ) ;
2019-09-29 23:49:41 +00:00
2020-04-30 01:36:02 +00:00
if ( GB_debugger_is_stopped ( & gb ) ) {
// [self interruptDebugInputRead];
}
2019-09-29 23:49:41 +00:00
2020-04-30 02:53:45 +00:00
gui_data . stopping = true ;
gui_data . running = false ;
while ( gui_data . stopping ) ;
2019-10-12 21:11:26 +00:00
2020-04-30 01:36:02 +00:00
GB_debugger_set_disabled ( & gb , false ) ;
gui_data . stopped = true ;
}
2019-09-25 20:47:33 +00:00
2020-05-17 03:33:41 +00:00
static void on_vblank ( GB_gameboy_t * gb ) {
2020-04-30 01:36:02 +00:00
// Queue drawing of the current frame
2020-04-30 02:53:45 +00:00
if ( gui_data . fallback_canvas ) {
gtk_widget_queue_draw ( GTK_WIDGET ( gui_data . main_window ) ) ;
2020-04-30 01:36:02 +00:00
}
2020-04-30 02:53:45 +00:00
else if ( gui_data . gl_area ) {
gtk_gl_area_queue_render ( gui_data . gl_area ) ;
2020-04-10 02:10:28 +00:00
}
2020-05-17 03:33:41 +00:00
gtk_widget_queue_draw ( GTK_WIDGET ( gui_data . vram_viewer ) ) ;
2020-04-11 22:38:18 +00:00
}
2020-04-30 01:36:02 +00:00
static void vblank ( GB_gameboy_t * gb ) {
flip ( ) ;
2020-04-11 22:38:18 +00:00
2020-04-30 02:53:45 +00:00
if ( gui_data . border_mode_changed ) {
2020-05-16 14:56:09 +00:00
GB_set_border_mode ( gb , config_get_display_border_mode ( ) ) ;
2020-04-30 01:36:02 +00:00
update_window_geometry ( ) ;
2020-04-11 22:38:18 +00:00
2020-04-30 02:53:45 +00:00
gui_data . border_mode_changed = false ;
2020-04-30 01:36:02 +00:00
}
2020-04-11 22:38:18 +00:00
2020-04-30 01:36:02 +00:00
GB_set_pixels_output ( gb , get_pixels ( ) ) ;
2020-04-11 22:38:18 +00:00
2020-05-10 22:05:59 +00:00
// Handle the speed modifiers:
// The binary slowdown is limited to half speed.
// The analog multiplier can go down to a third and up to three times full speed.
if ( gui_data . underclock_down & & gui_data . clock_mutliplier > 0.5 ) {
gui_data . clock_mutliplier - = 1.0 / 16 ;
//gui_data.clock_mutliplier = clamp_double(0.5, 1.0, gui_data.clock_mutliplier - 1.0 / 16);
2020-04-30 02:53:45 +00:00
GB_set_clock_multiplier ( gb , gui_data . clock_mutliplier ) ;
2020-04-30 01:36:02 +00:00
}
2020-04-30 02:53:45 +00:00
else if ( ! gui_data . underclock_down & & gui_data . clock_mutliplier < 1.0 ) {
2020-05-10 22:05:59 +00:00
gui_data . clock_mutliplier + = 1.0 / 16 ;
//gui_data.clock_mutliplier = clamp_double(0.5, 1.0, gui_data.clock_mutliplier + 1.0 / 16);
2020-04-30 02:53:45 +00:00
GB_set_clock_multiplier ( gb , gui_data . clock_mutliplier ) ;
2020-04-11 22:38:18 +00:00
}
2020-05-12 18:55:29 +00:00
else if ( config . controls . analog_speed_controls & & gui_data . analog_clock_multiplier_valid ) {
2020-05-10 22:05:59 +00:00
GB_set_clock_multiplier ( gb , gui_data . analog_clock_multiplier ) ;
if ( gui_data . analog_clock_multiplier = = 1.0 ) {
gui_data . analog_clock_multiplier_valid = false ;
}
}
2020-04-11 22:38:18 +00:00
2020-04-30 02:53:45 +00:00
gui_data . do_rewind = gui_data . rewind_down ;
2019-09-26 16:06:14 +00:00
2020-05-17 03:33:41 +00:00
vram_viewer_update ( gui_data . vram_viewer , gb ) ;
g_idle_add ( ( GSourceFunc ) on_vblank , gb ) ;
2019-09-26 16:06:14 +00:00
}
2020-04-30 01:36:02 +00:00
static void handle_events ( GB_gameboy_t * gb ) {
SDL_GameControllerUpdate ( ) ;
2019-09-30 14:40:55 +00:00
2020-04-30 01:36:02 +00:00
uint8_t controller_state = 0 ;
2020-05-12 18:55:29 +00:00
gui_data . analog_clock_multiplier = 1.0 ;
2019-09-26 16:06:14 +00:00
2020-05-03 22:02:15 +00:00
for ( unsigned i = 0 ; i < gui_data . controller_count ; i + + ) {
struct Controller_t * s = & gui_data . controllers [ i ] ;
2020-05-04 01:30:10 +00:00
int16_t left_x_axis = SDL_GameControllerGetAxis ( s - > controller , SDL_CONTROLLER_AXIS_LEFTX ) ;
int16_t left_y_axis = SDL_GameControllerGetAxis ( s - > controller , SDL_CONTROLLER_AXIS_LEFTY ) ;
2019-09-30 14:40:55 +00:00
2020-05-04 01:30:10 +00:00
int16_t right_x_axis = SDL_GameControllerGetAxis ( s - > controller , SDL_CONTROLLER_AXIS_RIGHTX ) ;
int16_t right_y_axis = SDL_GameControllerGetAxis ( s - > controller , SDL_CONTROLLER_AXIS_RIGHTY ) ;
2020-05-12 18:55:29 +00:00
if ( config . controls . analog_speed_controls ) {
2020-05-04 01:30:10 +00:00
double left_trigger = ( double ) SDL_GameControllerGetAxis ( s - > controller , SDL_CONTROLLER_AXIS_TRIGGERLEFT ) / ( double ) 32767 ;
double right_trigger = ( double ) SDL_GameControllerGetAxis ( s - > controller , SDL_CONTROLLER_AXIS_TRIGGERRIGHT ) / ( double ) 32767 ;
if ( left_trigger > 0.0 ) {
2020-05-12 18:55:29 +00:00
gui_data . analog_clock_multiplier = min_double ( gui_data . analog_clock_multiplier , clamp_double ( 1.0 / 3 , 1.0 , 1 - left_trigger + 0.2 ) ) ;
2020-05-10 22:05:59 +00:00
gui_data . analog_clock_multiplier_valid = true ;
2020-05-04 01:30:10 +00:00
}
else if ( right_trigger > 0.0 ) {
2020-05-12 18:55:29 +00:00
gui_data . analog_clock_multiplier = max_double ( gui_data . analog_clock_multiplier , clamp_double ( 1.0 , 3.0 , right_trigger * 3 + 0.8 ) ) ;
2020-05-10 22:05:59 +00:00
gui_data . analog_clock_multiplier_valid = true ;
2020-05-04 01:30:10 +00:00
}
}
if ( left_x_axis > = JOYSTICK_HIGH | | right_x_axis > = JOYSTICK_HIGH ) {
2020-05-03 22:02:15 +00:00
gui_data . last_used_controller = s ;
2020-04-30 01:36:02 +00:00
controller_state | = BUTTON_MASK_RIGHT ;
}
2020-05-04 01:30:10 +00:00
else if ( left_x_axis < = - JOYSTICK_HIGH | | right_x_axis < = - JOYSTICK_HIGH ) {
2020-05-03 22:02:15 +00:00
gui_data . last_used_controller = s ;
2020-04-30 01:36:02 +00:00
controller_state | = BUTTON_MASK_LEFT ;
}
2020-04-10 02:10:28 +00:00
2020-05-04 01:30:10 +00:00
if ( left_y_axis > = JOYSTICK_HIGH | | right_y_axis > = JOYSTICK_HIGH ) {
2020-05-03 22:02:15 +00:00
gui_data . last_used_controller = s ;
2020-04-30 01:36:02 +00:00
controller_state | = BUTTON_MASK_DOWN ;
}
2020-05-04 01:30:10 +00:00
else if ( left_y_axis < = - JOYSTICK_HIGH | | right_y_axis < = - JOYSTICK_HIGH ) {
2020-05-03 22:02:15 +00:00
gui_data . last_used_controller = s ;
controller_state | = BUTTON_MASK_UP ;
}
if ( SDL_GameControllerGetButton ( s - > controller , SDL_CONTROLLER_BUTTON_DPAD_RIGHT ) ) {
gui_data . last_used_controller = s ;
controller_state | = BUTTON_MASK_RIGHT ;
}
if ( SDL_GameControllerGetButton ( s - > controller , SDL_CONTROLLER_BUTTON_DPAD_LEFT ) ) {
gui_data . last_used_controller = s ;
controller_state | = BUTTON_MASK_LEFT ;
}
if ( SDL_GameControllerGetButton ( s - > controller , SDL_CONTROLLER_BUTTON_DPAD_UP ) ) {
gui_data . last_used_controller = s ;
2020-04-30 01:36:02 +00:00
controller_state | = BUTTON_MASK_UP ;
}
2020-04-10 02:10:28 +00:00
2020-05-03 22:02:15 +00:00
if ( SDL_GameControllerGetButton ( s - > controller , SDL_CONTROLLER_BUTTON_DPAD_DOWN ) ) {
gui_data . last_used_controller = s ;
controller_state | = BUTTON_MASK_DOWN ;
}
if ( SDL_GameControllerGetButton ( s - > controller , SDL_CONTROLLER_BUTTON_A ) ) {
gui_data . last_used_controller = s ;
controller_state | = BUTTON_MASK_A ;
}
if ( SDL_GameControllerGetButton ( s - > controller , SDL_CONTROLLER_BUTTON_B ) ) {
gui_data . last_used_controller = s ;
controller_state | = BUTTON_MASK_B ;
}
if ( SDL_GameControllerGetButton ( s - > controller , SDL_CONTROLLER_BUTTON_BACK ) ) {
gui_data . last_used_controller = s ;
controller_state | = BUTTON_MASK_SELECT ;
}
if ( SDL_GameControllerGetButton ( s - > controller , SDL_CONTROLLER_BUTTON_START ) ) {
gui_data . last_used_controller = s ;
controller_state | = BUTTON_MASK_START ;
}
2020-04-30 01:36:02 +00:00
}
2019-10-12 21:11:26 +00:00
2020-04-30 02:53:45 +00:00
GB_set_key_state ( gb , GB_KEY_RIGHT , ( gui_data . pressed_buttons & BUTTON_MASK_RIGHT ) | ( controller_state & BUTTON_MASK_RIGHT ) ) ;
GB_set_key_state ( gb , GB_KEY_LEFT , ( gui_data . pressed_buttons & BUTTON_MASK_LEFT ) | ( controller_state & BUTTON_MASK_LEFT ) ) ;
GB_set_key_state ( gb , GB_KEY_UP , ( gui_data . pressed_buttons & BUTTON_MASK_UP ) | ( controller_state & BUTTON_MASK_UP ) ) ;
GB_set_key_state ( gb , GB_KEY_DOWN , ( gui_data . pressed_buttons & BUTTON_MASK_DOWN ) | ( controller_state & BUTTON_MASK_DOWN ) ) ;
GB_set_key_state ( gb , GB_KEY_A , ( gui_data . pressed_buttons & BUTTON_MASK_A ) | ( controller_state & BUTTON_MASK_A ) ) ;
GB_set_key_state ( gb , GB_KEY_B , ( gui_data . pressed_buttons & BUTTON_MASK_B ) | ( controller_state & BUTTON_MASK_B ) ) ;
GB_set_key_state ( gb , GB_KEY_SELECT , ( gui_data . pressed_buttons & BUTTON_MASK_SELECT ) | ( controller_state & BUTTON_MASK_SELECT ) ) ;
GB_set_key_state ( gb , GB_KEY_START , ( gui_data . pressed_buttons & BUTTON_MASK_START ) | ( controller_state & BUTTON_MASK_START ) ) ;
2020-04-30 01:36:02 +00:00
}
2019-10-14 16:25:40 +00:00
2020-04-30 01:36:02 +00:00
static void load_boot_rom ( GB_gameboy_t * gb , GB_boot_rom_t type ) {
GError * error = NULL ;
char * boot_rom_path = NULL ;
GBytes * boot_rom_f = NULL ;
const guchar * boot_rom_data ;
gsize boot_rom_size ;
2019-10-14 16:25:40 +00:00
2020-04-30 01:36:02 +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 " ,
} ;
2019-10-14 16:25:40 +00:00
2020-04-30 01:36:02 +00:00
const char * const boot_rom_name = names [ type ] ;
2019-10-14 16:25:40 +00:00
2020-04-30 01:36:02 +00:00
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 ) ) {
g_warning ( " Falling back to boot ROM from config " ) ;
goto config_boot_rom ;
}
2019-09-30 14:40:55 +00:00
}
2020-04-30 01:36:02 +00:00
else { config_boot_rom :
2020-05-12 18:55:29 +00:00
if ( config . emulation . boot_rom_path ! = NULL & & g_strcmp0 ( config . emulation . boot_rom_path , " other " ) ! = 0 & & g_strcmp0 ( config . emulation . boot_rom_path , " auto " ) ! = 0 ) {
boot_rom_path = g_build_filename ( config . emulation . boot_rom_path , boot_rom_name , NULL ) ;
2020-04-30 01:36:02 +00:00
g_message ( " Trying to load boot ROM from %s " , boot_rom_path ) ;
2020-04-10 02:10:28 +00:00
2020-04-30 01:36:02 +00:00
if ( GB_load_boot_rom ( gb , boot_rom_path ) ) {
g_free ( boot_rom_path ) ;
g_warning ( " Falling back to internal boot ROM " ) ;
goto internal_boot_rom ;
}
2019-09-30 14:40:55 +00:00
2020-04-30 01:36:02 +00:00
g_free ( boot_rom_path ) ;
}
else { internal_boot_rom :
boot_rom_path = g_build_filename ( RESOURCE_PREFIX " bootroms/ " , boot_rom_name , NULL ) ;
boot_rom_f = g_resources_lookup_data ( boot_rom_path , G_RESOURCE_LOOKUP_FLAGS_NONE , & error ) ;
2019-09-26 16:06:14 +00:00
2020-04-30 01:36:02 +00:00
g_message ( " Loading internal boot ROM: %s " , boot_rom_path ) ;
g_free ( boot_rom_path ) ;
2019-10-12 21:11:26 +00:00
2020-04-30 01:36:02 +00:00
if ( boot_rom_f = = NULL ) {
g_warning ( " Failed to load internal boot ROM: %s " , boot_rom_path ) ;
g_error_free ( error ) ;
exit ( EXIT_FAILURE ) ;
}
2019-09-26 16:06:14 +00:00
2020-04-30 01:36:02 +00:00
boot_rom_data = g_bytes_get_data ( boot_rom_f , & boot_rom_size ) ;
GB_load_boot_rom_from_buffer ( gb , boot_rom_data , boot_rom_size ) ;
g_bytes_unref ( boot_rom_f ) ;
2019-10-07 22:36:16 +00:00
}
}
}
2020-04-30 01:36:02 +00:00
static void init ( void ) {
if ( GB_is_inited ( & gb ) ) return ;
2019-09-26 16:06:14 +00:00
2020-05-16 15:48:29 +00:00
GB_init ( & gb , config_get_model_type ( & gui_data ) ) ;
2019-09-27 21:10:28 +00:00
2020-04-30 01:36:02 +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 , GB_audio_get_sample_rate ( ) ) ;
2020-05-16 14:56:09 +00:00
GB_set_color_correction_mode ( & gb , config_get_color_correction_mode ( ) ) ;
GB_set_highpass_filter_mode ( & gb , config_get_highpass_mode ( ) ) ;
2020-05-12 18:55:29 +00:00
GB_set_rewind_length ( & gb , config . emulation . rewind_duration ) ;
2020-04-30 01:36:02 +00:00
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 ) ;
GB_set_boot_rom_load_callback ( & gb , load_boot_rom ) ;
2019-09-27 22:15:37 +00:00
2020-05-03 19:11:21 +00:00
GB_set_rumble_callback ( & gb , rumble_callback ) ;
2020-05-16 14:56:09 +00:00
GB_set_rumble_mode ( & gb , config_get_rumble_mode ( ) ) ;
2020-05-03 19:11:21 +00:00
2020-05-16 14:56:09 +00:00
if ( config_get_display_border_mode ( ) < = GB_BORDER_ALWAYS ) {
GB_set_border_mode ( & gb , config_get_display_border_mode ( ) ) ;
2020-04-30 01:36:02 +00:00
}
2020-04-11 22:38:18 +00:00
2020-04-30 01:36:02 +00:00
update_window_geometry ( ) ;
}
2020-04-10 02:10:28 +00:00
2020-04-30 01:36:02 +00:00
static void reset ( void ) {
2020-05-16 15:48:29 +00:00
g_debug ( " Reset: %d == %d " , config_get_model_type ( & gui_data ) , gui_data . prev_model ) ;
GB_model_t current_model = config_get_model_type ( & gui_data ) ;
2020-04-11 16:55:21 +00:00
2020-04-30 01:36:02 +00:00
if ( gui_data . prev_model = = - 1 | | gui_data . prev_model = = current_model ) {
GB_reset ( & gb ) ;
2019-09-27 21:10:28 +00:00
}
2020-04-11 22:38:18 +00:00
else {
2020-04-30 01:36:02 +00:00
GB_switch_model_and_reset ( & gb , current_model ) ;
2020-04-11 22:38:18 +00:00
}
2019-09-30 14:40:55 +00:00
2020-05-16 14:56:09 +00:00
GB_set_palette ( & gb , config_get_monochrome_palette ( ) ) ;
2019-09-27 21:10:28 +00:00
2020-05-16 15:48:29 +00:00
gui_data . prev_model = config_get_model_type ( & gui_data ) ;
2020-04-11 22:38:18 +00:00
2020-04-30 01:36:02 +00:00
// Check SGB -> non-SGB and non-SGB to SGB transitions
2020-05-05 19:03:08 +00:00
if ( GB_get_screen_width ( & gb ) ! = gui_data . last_screen_width | | GB_get_screen_height ( & gb ) ! = gui_data . last_screen_height ) {
2020-04-30 01:36:02 +00:00
update_window_geometry ( ) ;
}
2020-04-11 22:38:18 +00:00
2020-04-30 01:36:02 +00:00
bool success = false ;
if ( gui_data . file ) {
char * path = g_file_get_path ( gui_data . file ) ;
2020-05-14 02:22:58 +00:00
char * ext = strrchr ( path , ' . ' ) ;
int result ;
GB_debugger_clear_symbols ( & gb ) ;
if ( g_strcmp0 ( ext + 1 , " isx " ) = = 0 ) {
result = GB_load_isx ( & gb , path ) ;
}
else {
result = GB_load_rom ( & gb , path ) ;
}
2020-04-11 23:35:15 +00:00
2020-05-14 02:22:58 +00:00
if ( result = = 0 ) {
2020-04-30 01:36:02 +00:00
success = true ;
}
else {
g_warning ( " Failed to load ROM: %s " , path ) ;
}
2020-04-11 22:38:18 +00:00
2020-04-30 04:02:48 +00:00
GB_load_battery ( & gb , gui_data . battery_save_path ) ;
GB_load_cheats ( & gb , gui_data . cheats_save_path ) ;
2020-04-30 19:45:46 +00:00
GError * error = NULL ;
GBytes * register_sym_f = g_resources_lookup_data ( RESOURCE_PREFIX " Misc/registers.sym " , G_RESOURCE_LOOKUP_FLAGS_NONE , & error ) ;
if ( register_sym_f ) {
gsize register_sym_size ;
const gchar * register_sym_data = g_bytes_get_data ( register_sym_f , & register_sym_size ) ;
GB_debugger_load_symbol_file_from_buffer ( & gb , register_sym_data , register_sym_size ) ;
g_bytes_unref ( register_sym_f ) ;
2020-04-30 04:02:48 +00:00
}
size_t path_length = strlen ( path ) ;
char sym_file_path [ path_length + 5 ] ;
replace_extension ( path , path_length , sym_file_path , " .sym " ) ;
GB_debugger_load_symbol_file ( & gb , sym_file_path ) ;
2020-04-30 01:36:02 +00:00
g_free ( path ) ;
}
2020-04-30 02:53:45 +00:00
action_set_enabled ( gui_data . main_application , " close " , success ) ;
2020-04-30 01:36:02 +00:00
action_entries_set_enabled ( emulation_entries , G_N_ELEMENTS ( emulation_entries ) , success ) ;
2019-09-26 16:06:14 +00:00
}
2020-04-30 01:36:02 +00:00
static void start ( void ) {
2020-04-30 02:53:45 +00:00
gui_data . running = true ;
2020-04-30 01:36:02 +00:00
gui_data . stopped = false ;
GB_audio_clear_queue ( ) ;
2020-05-12 21:32:49 +00:00
GB_audio_set_paused ( config . audio . muted ) ;
2020-04-30 01:36:02 +00:00
/* Run emulation */
2020-04-30 02:53:45 +00:00
while ( gui_data . running ) {
if ( gui_data . rewind_paused ) {
2020-04-30 01:36:02 +00:00
handle_events ( & gb ) ;
g_usleep ( G_USEC_PER_SEC / 8 ) ;
}
else {
2020-04-30 02:53:45 +00:00
if ( gui_data . do_rewind ) {
2020-04-30 01:36:02 +00:00
GB_rewind_pop ( & gb ) ;
2020-04-30 02:53:45 +00:00
if ( gui_data . turbo_down ) {
2020-04-30 01:36:02 +00:00
GB_rewind_pop ( & gb ) ;
}
if ( ! GB_rewind_pop ( & gb ) ) {
2020-04-30 02:53:45 +00:00
gui_data . rewind_paused = true ;
2020-04-30 01:36:02 +00:00
}
2020-04-30 02:53:45 +00:00
gui_data . do_rewind = false ;
2020-04-30 01:36:02 +00:00
}
GB_run ( & gb ) ;
}
}
2020-04-30 04:02:48 +00:00
if ( gui_data . file ) {
GB_save_battery ( & gb , gui_data . battery_save_path ) ;
GB_save_cheats ( & gb , gui_data . cheats_save_path ) ;
}
2020-04-30 02:53:45 +00:00
gui_data . stopping = false ;
2019-09-30 14:40:55 +00:00
}
2019-09-29 23:49:41 +00:00
2020-04-30 01:36:02 +00:00
// Prevent dependency loop
static void run ( void ) ;
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 ) ) {
2020-04-30 01:36:02 +00:00
init ( ) ;
2019-10-12 21:11:26 +00:00
}
2020-04-11 22:38:18 +00:00
2020-04-30 01:36:02 +00:00
stop ( ) ;
reset ( ) ;
run ( ) ;
2019-10-12 21:11:26 +00:00
}
2020-04-30 01:36:02 +00:00
static gpointer run_thread ( gpointer null_ptr ) {
if ( ! gui_data . file ) return NULL ;
2020-04-10 02:10:28 +00:00
2020-04-30 04:02:48 +00:00
char * path = g_file_get_path ( gui_data . file ) ;
size_t path_length = strlen ( path ) ;
/* At the worst case, size is strlen(path) + 4 bytes for .sav + NULL */
char battery_save_path [ path_length + 5 ] ;
char cheats_save_path [ path_length + 5 ] ;
replace_extension ( path , path_length , battery_save_path , " .sav " ) ;
replace_extension ( path , path_length , cheats_save_path , " .cht " ) ;
gui_data . battery_save_path = battery_save_path ;
gui_data . cheats_save_path = cheats_save_path ;
2020-05-12 18:55:29 +00:00
if ( ! GB_is_inited ( & gb ) ) {
init ( ) ;
}
2020-04-30 01:36:02 +00:00
if ( gui_data . stopped ) {
start ( ) ;
2019-10-14 22:16:59 +00:00
}
2020-04-30 01:36:02 +00:00
else {
reset ( ) ;
start ( ) ;
2019-10-12 21:11:26 +00:00
}
2020-04-30 01:36:02 +00:00
return NULL ;
2019-10-12 21:11:26 +00:00
}
2020-04-30 01:36:02 +00:00
static void run ( void ) {
2020-04-30 02:53:45 +00:00
if ( gui_data . running ) return ;
while ( gui_data . stopping ) ;
2019-10-12 21:11:26 +00:00
2020-04-30 01:36:02 +00:00
g_thread_new ( " CoreLoop " , run_thread , NULL ) ;
2019-10-12 21:11:26 +00:00
}
2020-04-30 01:36:02 +00:00
// Tell our application to quit.
// After this functions has been called the `shutdown` signal will be issued.
//
// TODO: Make sure we have a way to quit our emulation loop before `shutdown` gets called
static void quit ( void ) {
2020-05-12 22:25:13 +00:00
g_debug ( " quit(void); " ) ;
2020-04-30 01:36:02 +00:00
stop ( ) ;
2020-05-12 22:25:13 +00:00
GtkWindow * window = gui_data . main_window ? GTK_WINDOW ( gui_data . main_window ) : NULL ;
2020-05-16 14:56:09 +00:00
save_config ( window , gui_data . config_modification_date ) ;
free_config ( ) ;
2019-10-12 21:11:26 +00:00
2020-05-03 19:11:21 +00:00
for ( unsigned i = 0 ; i < gui_data . controller_count ; i + + ) {
struct Controller_t * s = & gui_data . controllers [ i ] ;
2020-05-03 23:41:14 +00:00
SDL_HapticClose ( s - > haptic ) ;
2020-05-03 19:11:21 +00:00
SDL_GameControllerClose ( s - > controller ) ;
}
2020-04-30 01:36:02 +00:00
// Quit our application properly.
// This fires the “shutdown” signal.
2020-04-30 02:53:45 +00:00
g_application_quit ( G_APPLICATION ( gui_data . main_application ) ) ;
2019-10-12 21:11:26 +00:00
}
2020-04-30 01:36:02 +00:00
static void quit_interrupt ( int ignored ) {
2020-05-12 22:25:13 +00:00
g_debug ( " quit_interrupt(%d); " , ignored ) ;
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
2020-04-30 01:36:02 +00:00
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 ) ;
2019-09-29 23:49:41 +00:00
2020-04-30 01:36:02 +00:00
action_set_enabled ( app , " close " , false ) ;
action_entries_set_enabled ( emulation_entries , G_N_ELEMENTS ( emulation_entries ) , false ) ;
}
2019-09-29 23:49:41 +00:00
2020-04-30 01:36:02 +00:00
static gboolean on_key_press ( GtkWidget * w , GdkEventKey * event , gpointer data ) {
uint8_t mask ;
2019-09-29 23:49:41 +00:00
2020-05-02 16:44:24 +00:00
if ( event - > keyval = = key_map [ INPUT_UP ] ) mask = BUTTON_MASK_UP ;
if ( event - > keyval = = key_map [ INPUT_DOWN ] ) mask = BUTTON_MASK_DOWN ;
if ( event - > keyval = = key_map [ INPUT_LEFT ] ) mask = BUTTON_MASK_LEFT ;
if ( event - > keyval = = key_map [ INPUT_RIGHT ] ) mask = BUTTON_MASK_RIGHT ;
if ( event - > keyval = = key_map [ INPUT_START ] ) mask = BUTTON_MASK_START ;
if ( event - > keyval = = key_map [ INPUT_SELECT ] ) mask = BUTTON_MASK_SELECT ;
if ( event - > keyval = = key_map [ INPUT_A ] ) mask = BUTTON_MASK_A ;
if ( event - > keyval = = key_map [ INPUT_B ] ) mask = BUTTON_MASK_B ;
if ( event - > keyval = = key_map [ INPUT_REWIND ] ) {
gui_data . rewind_down = event - > type = = GDK_KEY_PRESS ;
GB_set_turbo_mode ( & gb , gui_data . turbo_down , gui_data . turbo_down & & gui_data . rewind_down ) ;
if ( event - > type = = GDK_KEY_RELEASE ) {
gui_data . rewind_paused = false ;
}
}
2019-09-29 23:49:41 +00:00
2020-05-02 16:44:24 +00:00
if ( event - > keyval = = key_map [ INPUT_TURBO ] ) {
gui_data . turbo_down = event - > type = = GDK_KEY_PRESS ;
2020-05-10 22:05:59 +00:00
gui_data . analog_clock_multiplier_valid = false ;
2020-05-02 16:44:24 +00:00
GB_audio_clear_queue ( ) ;
GB_set_turbo_mode ( & gb , gui_data . turbo_down , gui_data . turbo_down & & gui_data . rewind_down ) ;
}
2019-09-29 23:49:41 +00:00
2020-05-02 16:44:24 +00:00
if ( event - > keyval = = key_map [ INPUT_SLOWDOWN ] ) {
gui_data . underclock_down = event - > type = = GDK_KEY_PRESS ;
2020-05-10 22:05:59 +00:00
gui_data . analog_clock_multiplier_valid = false ;
2020-05-02 16:44:24 +00:00
}
2019-09-29 23:49:41 +00:00
2020-05-02 16:44:24 +00:00
if ( event - > keyval = = key_map [ INPUT_FULLSCREEN ] ) {
if ( event - > type = = GDK_KEY_RELEASE ) {
if ( gui_data . is_fullscreen ) {
gtk_window_unfullscreen ( GTK_WINDOW ( gui_data . main_window ) ) ;
2020-04-30 01:36:02 +00:00
}
2020-05-02 16:44:24 +00:00
else {
gtk_window_fullscreen ( GTK_WINDOW ( gui_data . main_window ) ) ;
2020-04-30 01:36:02 +00:00
}
2020-05-02 16:44:24 +00:00
}
2020-04-30 01:36:02 +00:00
}
2020-05-02 16:44:24 +00:00
2020-04-30 01:36:02 +00:00
if ( event - > type = = GDK_KEY_PRESS ) {
2020-04-30 02:53:45 +00:00
gui_data . pressed_buttons | = mask ;
2020-04-30 01:36:02 +00:00
}
else if ( event - > type = = GDK_KEY_RELEASE ) {
2020-04-30 02:53:45 +00:00
gui_data . pressed_buttons & = ~ mask ;
2020-04-30 01:36:02 +00:00
}
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
2020-04-30 01:36:02 +00:00
static void on_window_state_change ( GtkWidget * w , GdkEventWindowState * event , gpointer data ) {
2020-04-30 02:53:45 +00:00
gui_data . is_fullscreen = event - > new_window_state & GDK_WINDOW_STATE_FULLSCREEN ;
2019-09-30 14:40:55 +00:00
}
2020-04-11 16:24:55 +00:00
2020-04-30 01:36:02 +00:00
// This functions gets called immediately after registration of the GApplication
static void startup ( GApplication * app , gpointer null_ptr ) {
signal ( SIGINT , quit_interrupt ) ;
2019-09-29 23:49:41 +00:00
2020-05-05 19:03:08 +00:00
g_debug ( " GTK version %u.%u.%u " , gtk_get_major_version ( ) , gtk_get_minor_version ( ) , gtk_get_micro_version ( ) ) ;
2020-05-05 19:38:56 +00:00
if ( gui_data . cli_options . force_software_renderer ) {
g_message ( " Forcing fallback renderer! " ) ;
}
else {
// 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.
gui_data . supports_gl = test_gl_support ( ) ;
g_debug ( " OpenGL supported: %s " , gui_data . supports_gl ? " Yes " : " No " ) ;
}
2020-04-10 02:10:28 +00:00
2020-04-30 02:53:45 +00:00
gui_data . builder = gtk_builder_new_from_resource ( RESOURCE_PREFIX " ui/window.ui " ) ;
gtk_builder_connect_signals ( gui_data . builder , NULL ) ;
2019-09-29 23:49:41 +00:00
2020-04-30 01:36:02 +00:00
create_action_groups ( app ) ;
2020-05-03 22:02:15 +00:00
# if NDEBUG
// Disable when not compiled in debug mode
action_set_enabled ( app , " open_gtk_debugger " , false ) ;
# endif
2020-04-11 16:24:55 +00:00
2020-04-30 02:53:45 +00:00
gui_data . preferences = GTK_WINDOW ( get_object ( " preferences " ) ) ;
2020-04-11 16:24:55 +00:00
2020-04-30 02:53:45 +00:00
g_signal_connect ( gui_data . preferences , " realize " , G_CALLBACK ( on_preferences_realize ) , ( gpointer ) gui_data . builder ) ;
2020-05-16 14:56:09 +00:00
init_config ( app , gui_data . cli_options . config_path , & gui_data . config_modification_date , gui_data . preferences ) ;
2020-04-10 15:45:35 +00:00
2020-05-17 03:33:41 +00:00
gui_data . vram_viewer = vram_viewer_new ( ) ;
2020-04-30 02:53:45 +00:00
gui_data . memory_viewer = GTK_WINDOW ( get_object ( " memory_viewer " ) ) ;
2020-04-10 19:42:03 +00:00
2020-04-30 02:53:45 +00:00
gui_data . console = GTK_WINDOW ( get_object ( " console " ) ) ;
gui_data . printer = GTK_WINDOW ( get_object ( " printer " ) ) ;
2020-04-10 19:42:03 +00:00
2020-05-12 18:55:29 +00:00
if ( config . audio . sample_rate = = - 1 ) {
2020-04-30 01:36:02 +00:00
gui_data . sample_rate = GB_audio_default_sample_rate ( ) ;
}
else {
2020-05-12 18:55:29 +00:00
gui_data . sample_rate = config . audio . sample_rate ;
2020-04-30 01:36:02 +00:00
}
2020-04-10 20:19:40 +00:00
2020-04-30 01:36:02 +00:00
// setup main window
2020-04-30 02:53:45 +00:00
gui_data . main_window = GTK_APPLICATION_WINDOW ( gtk_application_window_new ( GTK_APPLICATION ( app ) ) ) ;
gui_data . main_window_container = GTK_BOX ( gtk_box_new ( GTK_ORIENTATION_VERTICAL , 0 ) ) ;
2020-04-10 20:19:40 +00:00
2020-04-30 02:53:45 +00:00
gtk_window_set_title ( GTK_WINDOW ( gui_data . main_window ) , " SameBoy " ) ;
gtk_application_window_set_show_menubar ( gui_data . main_window , false ) ;
gtk_container_add ( GTK_CONTAINER ( gui_data . main_window ) , GTK_WIDGET ( gui_data . main_window_container ) ) ;
2020-04-11 16:24:55 +00:00
2020-04-30 01:36:02 +00:00
setup_menu ( app ) ;
2020-04-11 16:24:55 +00:00
2020-04-30 01:36:02 +00:00
// Insert separators into `GtkComboBox`es
2020-04-30 02:53:45 +00:00
set_combo_box_row_separator_func ( GTK_CONTAINER ( gui_data . preferences ) ) ;
set_combo_box_row_separator_func ( GTK_CONTAINER ( gui_data . vram_viewer ) ) ;
set_combo_box_row_separator_func ( GTK_CONTAINER ( gui_data . memory_viewer ) ) ;
2019-09-23 22:34:21 +00:00
2020-04-30 01:36:02 +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-23 14:35:10 +00:00
2020-04-30 01:36:02 +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 ;
2020-04-11 16:24:55 +00:00
2020-04-30 01:36:02 +00:00
icon_list = g_list_prepend ( icon_list , icon ) ;
2020-04-11 16:24:55 +00:00
}
2020-04-30 01:36:02 +00:00
// Let GTK choose the proper icon
2020-05-14 02:22:58 +00:00
gtk_window_set_default_icon_list ( icon_list ) ;
2020-04-11 16:24:55 +00:00
2020-04-30 01:36:02 +00:00
// Add missing information to the about dialog
GtkAboutDialog * about_dialog = GTK_ABOUT_DIALOG ( get_object ( " about_dialog " ) ) ;
gtk_about_dialog_set_logo ( about_dialog , gdk_pixbuf_new_from_resource ( icons [ 2 ] , NULL ) ) ; // reuse the 64x64 icon
gtk_about_dialog_set_version ( about_dialog , " v " xstr ( VERSION ) ) ;
g_list_free_full ( icon_list , g_object_unref ) ;
2019-09-30 14:40:55 +00:00
}
2019-09-23 22:34:21 +00:00
2020-05-14 02:22:58 +00:00
G_MODULE_EXPORT void on_quit_activate ( GtkWidget * w , gpointer user_data_ptr ) {
quit ( ) ;
}
2020-05-15 22:23:03 +00:00
bool on_change_model ( GtkWidget * widget , gpointer user_data ) {
GtkCheckMenuItem * check_menu_item = GTK_CHECK_MENU_ITEM ( widget ) ;
gchar * model_str = ( gchar * ) user_data ;
if ( ! gtk_check_menu_item_get_active ( check_menu_item ) ) {
return true ;
}
else if ( ! GB_is_inited ( & gb ) ) {
gui_data . cli_options . model = - 1 ;
2020-05-16 14:30:29 +00:00
config . emulation . model = model_str ;
2020-05-15 22:23:03 +00:00
return false ;
2020-05-14 02:22:58 +00:00
}
GtkMessageDialog * dialog = GTK_MESSAGE_DIALOG ( gtk_message_dialog_new (
GTK_WINDOW ( gui_data . 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 ( ) ;
gint result = gtk_dialog_run ( GTK_DIALOG ( dialog ) ) ;
switch ( result ) {
case GTK_RESPONSE_YES :
// Reset the CLI model override
gui_data . cli_options . model = - 1 ;
2020-05-16 14:30:29 +00:00
config . emulation . model = model_str ;
2020-05-14 02:22:58 +00:00
reset ( ) ;
break ;
default :
// Action has been canceled
break ;
}
run ( ) ;
gtk_widget_destroy ( GTK_WIDGET ( dialog ) ) ;
2020-05-15 22:23:03 +00:00
return result ! = GTK_RESPONSE_YES ;
2020-05-14 02:22:58 +00:00
}
2020-04-30 01:36:02 +00:00
static void connect_signal_handlers ( GApplication * app ) {
// Connect signal handlers
2020-04-30 02:53:45 +00:00
gtk_widget_add_events ( GTK_WIDGET ( gui_data . main_window ) , GDK_KEY_PRESS_MASK ) ;
gtk_widget_add_events ( GTK_WIDGET ( gui_data . main_window ) , GDK_KEY_RELEASE_MASK ) ;
2019-10-12 21:11:26 +00:00
2020-05-14 02:22:58 +00:00
g_signal_connect ( gui_data . main_window , " destroy " , G_CALLBACK ( on_quit_activate ) , app ) ;
2020-04-30 02:53:45 +00:00
g_signal_connect ( gui_data . main_window , " key_press_event " , G_CALLBACK ( on_key_press ) , NULL ) ;
g_signal_connect ( gui_data . main_window , " key_release_event " , G_CALLBACK ( on_key_press ) , NULL ) ;
g_signal_connect ( gui_data . main_window , " window-state-event " , G_CALLBACK ( on_window_state_change ) , NULL ) ;
2019-10-14 21:46:03 +00:00
2020-04-30 01:36:02 +00:00
// Just hide our sub-windows when closing them
2020-04-30 02:53:45 +00:00
g_signal_connect ( gui_data . preferences , " delete-event " , G_CALLBACK ( gtk_widget_hide_on_delete ) , NULL ) ;
g_signal_connect ( gui_data . vram_viewer , " delete-event " , G_CALLBACK ( gtk_widget_hide_on_delete ) , NULL ) ;
g_signal_connect ( gui_data . memory_viewer , " delete-event " , G_CALLBACK ( gtk_widget_hide_on_delete ) , NULL ) ;
g_signal_connect ( gui_data . console , " delete-event " , G_CALLBACK ( gtk_widget_hide_on_delete ) , NULL ) ;
g_signal_connect ( gui_data . printer , " delete-event " , G_CALLBACK ( gtk_widget_hide_on_delete ) , NULL ) ;
2019-09-30 14:40:55 +00:00
}
2020-04-30 01:36:02 +00:00
// TODO: Comment
static void gl_draw ( void ) {
uint32_t * pixels = get_current_buffer ( ) ;
uint32_t * previous = get_previous_buffer ( ) ;
2019-09-22 22:47:42 +00:00
2020-04-30 01:36:02 +00:00
static void * _pixels = NULL ;
2019-10-12 21:11:26 +00:00
2020-04-30 01:36:02 +00:00
if ( pixels ) {
_pixels = pixels ;
2019-10-12 21:11:26 +00:00
}
2020-04-10 01:25:53 +00:00
2020-04-30 01:36:02 +00:00
glClearColor ( 0 , 0 , 0 , 1 ) ;
glClear ( GL_COLOR_BUFFER_BIT ) ;
2020-04-10 01:25:53 +00:00
2020-05-16 14:56:09 +00:00
GB_frame_blending_mode_t mode = config_get_frame_blending_mode ( ) ;
2020-04-30 01:36:02 +00:00
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 ;
2020-04-10 01:25:53 +00:00
}
2020-04-30 01:36:02 +00:00
else {
mode = GB_is_odd_frame ( & gb ) ? GB_FRAME_BLENDING_MODE_ACCURATE_ODD : GB_FRAME_BLENDING_MODE_ACCURATE_EVEN ;
2020-04-10 01:25:53 +00:00
}
}
2020-04-30 01:36:02 +00:00
render_bitmap_with_shader (
2020-04-30 02:53:45 +00:00
& gui_data . shader , _pixels , previous ,
2020-04-30 01:36:02 +00:00
GB_get_screen_width ( & gb ) , GB_get_screen_height ( & gb ) ,
2020-05-17 03:33:41 +00:00
gui_data . viewport . x , gui_data . viewport . y , gui_data . viewport . width , gui_data . viewport . height ,
2020-04-30 01:36:02 +00:00
mode
) ;
2019-09-30 14:40:55 +00:00
}
2020-04-30 01:36:02 +00:00
// TODO: Comment
static void gl_finish ( void ) { }
2019-10-04 22:57:03 +00:00
2020-04-30 01:36:02 +00:00
// TODO: Comment
static void resize ( void ) {
update_viewport ( ) ;
2019-10-04 22:57:03 +00:00
}
2020-04-30 01:36:02 +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-10-04 22:57:03 +00:00
2020-04-30 01:36:02 +00:00
guint screen_width = GB_get_screen_width ( & gb ) ;
guint screen_height = GB_get_screen_height ( & gb ) ;
2019-10-04 22:57:03 +00:00
2020-04-30 01:36:02 +00:00
gtk_render_background ( context , cr , 0 , 0 , width , height ) ;
2020-05-17 03:33:41 +00:00
gtk_render_frame ( context , cr , 0 , 0 , width , height ) ;
2019-10-04 22:57:03 +00:00
2020-04-30 01:36:02 +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-10-04 22:57:03 +00:00
2020-04-30 02:53:45 +00:00
cairo_translate ( cr , gui_data . viewport . x , gui_data . viewport . y ) ;
2020-05-17 03:33:41 +00:00
cairo_scale ( cr , gui_data . viewport . width / screen_width , gui_data . viewport . height / screen_height ) ;
2020-04-30 01:36:02 +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-10-04 22:57:03 +00:00
2020-04-30 01:36:02 +00:00
return false ;
2019-10-04 22:57:03 +00:00
}
2020-04-30 01:36:02 +00:00
// Create a GtkDrawingArea as a fallback in case we can’ t use OpenGL
static void create_fallback_canvas ( void ) {
2020-04-30 02:53:45 +00:00
gui_data . fallback_canvas = GTK_DRAWING_AREA ( gtk_drawing_area_new ( ) ) ;
g_signal_connect ( gui_data . fallback_canvas , " draw " , G_CALLBACK ( on_draw_fallback ) , NULL ) ;
g_signal_connect ( gui_data . fallback_canvas , " size-allocate " , G_CALLBACK ( resize ) , NULL ) ;
gtk_box_pack_end ( GTK_BOX ( gui_data . main_window_container ) , GTK_WIDGET ( gui_data . fallback_canvas ) , true , true , 0 ) ;
2020-04-30 01:36:02 +00:00
}
2019-10-03 21:26:40 +00:00
2020-04-30 01:36:02 +00:00
// TODO: Comment
static void gl_init ( GtkWidget * w ) {
GtkGLArea * gl_area = GTK_GL_AREA ( w ) ;
2019-10-04 22:57:03 +00:00
2020-04-30 01:36:02 +00:00
g_debug ( " GL_INIT " ) ;
const char * renderer ;
2019-10-03 23:40:50 +00:00
2020-04-30 01:36:02 +00:00
g_debug ( " GL Context: %p " , gtk_gl_area_get_context ( gl_area ) ) ;
2019-10-03 23:40:50 +00:00
2020-04-30 01:36:02 +00:00
gtk_gl_area_make_current ( gl_area ) ;
2020-04-10 02:10:28 +00:00
2020-04-30 01:36:02 +00:00
if ( gtk_gl_area_get_error ( gl_area ) ! = NULL ) {
goto error ;
}
2019-10-03 23:40:50 +00:00
2020-04-30 01:36:02 +00:00
renderer = ( char * ) glGetString ( GL_RENDERER ) ;
g_debug ( " GtkGLArea on %s " , renderer ? renderer : " Unknown " ) ;
2019-10-04 22:57:03 +00:00
2020-05-12 18:55:29 +00:00
if ( config . video . shader = = NULL | | ( ! init_shader_with_name ( & gui_data . shader , config . video . shader ) & & ! init_shader_with_name ( & gui_data . shader , " NearestNeighbor " ) ) ) {
2020-04-30 01:36:02 +00:00
GError * error = g_error_new_literal ( g_quark_from_string ( " sameboy-gl-error " ) , 1 , " Failed to initialize shaders " ) ;
gtk_gl_area_set_error ( gl_area , error ) ;
}
else {
g_signal_connect ( gl_area , " render " , G_CALLBACK ( gl_draw ) , NULL ) ;
g_signal_connect ( gl_area , " resize " , G_CALLBACK ( resize ) , NULL ) ;
g_signal_connect ( gl_area , " unrealize " , G_CALLBACK ( gl_finish ) , NULL ) ;
return ;
}
2019-10-04 22:57:03 +00:00
2020-04-30 01:36:02 +00:00
error :
if ( gtk_gl_area_get_error ( gl_area ) ! = NULL ) {
g_warning ( " GtkGLArea: %s " , gtk_gl_area_get_error ( gl_area ) - > message ) ;
2019-10-03 21:26:40 +00:00
}
2019-10-04 22:57:03 +00:00
2020-04-30 01:36:02 +00:00
create_fallback_canvas ( ) ;
}
2019-10-04 22:57:03 +00:00
2020-04-30 01:36:02 +00:00
static void create_canvas ( void ) {
// create our renderer area
2020-04-30 02:53:45 +00:00
if ( gui_data . supports_gl ) {
gui_data . gl_area = GTK_GL_AREA ( gtk_gl_area_new ( ) ) ;
gtk_gl_area_set_required_version ( gui_data . gl_area , 3 , 2 ) ;
gtk_gl_area_set_auto_render ( gui_data . gl_area , false ) ;
gtk_gl_area_set_has_alpha ( gui_data . gl_area , false ) ;
gtk_gl_area_set_has_depth_buffer ( gui_data . gl_area , false ) ;
gtk_gl_area_set_has_stencil_buffer ( gui_data . gl_area , false ) ;
g_signal_connect ( gui_data . gl_area , " realize " , G_CALLBACK ( gl_init ) , NULL ) ;
gtk_box_pack_end ( GTK_BOX ( gui_data . main_window_container ) , GTK_WIDGET ( gui_data . gl_area ) , true , true , 0 ) ;
2020-04-30 01:36:02 +00:00
}
else {
create_fallback_canvas ( ) ;
}
2019-10-04 22:57:03 +00:00
2020-04-30 01:36:02 +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 " ) ;
gtk_style_context_add_provider_for_screen ( screen , GTK_STYLE_PROVIDER ( provider ) , GTK_STYLE_PROVIDER_PRIORITY_APPLICATION ) ;
}
2019-10-03 21:26:40 +00:00
2020-04-30 01:36:02 +00:00
static void setup_console ( void ) {
GtkTextView * text_view = builder_get ( GTK_TEXT_VIEW , " console_screen " ) ;
GtkTextBuffer * text_buf = gtk_text_view_get_buffer ( text_view ) ;
2019-10-04 22:57:03 +00:00
2020-04-30 01:36:02 +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-04 22:57:03 +00:00
2020-04-30 01:36:02 +00:00
gtk_text_buffer_create_tag ( text_buf , " bold " , " weight " , PANGO_WEIGHT_BOLD , NULL ) ;
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-04 22:57:03 +00:00
2020-04-30 02:53:45 +00:00
g_mutex_init ( & gui_data . debugger_input_mutex ) ;
g_cond_init ( & gui_data . debugger_input_cond ) ;
g_rec_mutex_init ( & gui_data . console_output_lock ) ;
2019-10-04 22:57:03 +00:00
2020-04-30 02:53:45 +00:00
if ( ! gui_data . debugger_input_queue ) {
gui_data . debugger_input_queue = g_ptr_array_sized_new ( 4 ) ;
2020-04-30 01:36:02 +00:00
}
2019-10-04 22:57:03 +00:00
2020-04-30 02:53:45 +00:00
if ( ! gui_data . pending_console_output ) {
gui_data . pending_console_output = gtk_text_buffer_new ( gtk_text_buffer_get_tag_table ( text_buf ) ) ;
2020-04-30 01:36:02 +00:00
}
}
2019-10-14 19:28:39 +00:00
2020-04-30 01:36:02 +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 null_ptr ) {
init_audio ( ) ;
init_controllers ( ) ;
2019-10-04 22:57:03 +00:00
2020-04-30 01:36:02 +00:00
connect_signal_handlers ( app ) ;
create_canvas ( ) ;
setup_console ( ) ;
2019-10-04 22:57:03 +00:00
2020-04-30 01:36:02 +00:00
if ( gui_data . cli_options . fullscreen ) {
2020-04-30 02:53:45 +00:00
gtk_window_fullscreen ( GTK_WINDOW ( gui_data . main_window ) ) ;
2020-04-30 01:36:02 +00:00
}
2019-10-03 01:10:48 +00:00
2020-04-30 02:53:45 +00:00
gtk_application_add_window ( GTK_APPLICATION ( app ) , GTK_WINDOW ( gui_data . main_window ) ) ;
gtk_widget_show_all ( GTK_WIDGET ( gui_data . main_window ) ) ;
2019-10-14 19:28:39 +00:00
2020-05-14 20:53:02 +00:00
update_window_geometry ( ) ;
2020-04-30 01:36:02 +00:00
// Start the emulation thread
run ( ) ;
}
2019-10-14 19:28:39 +00:00
2020-04-30 01:36:02 +00:00
// This function gets called when the application is closed.
static void shutdown ( GApplication * app , GFile * * files , gint n_files , const gchar * hint , gpointer null_ptr ) {
g_debug ( " SHUTDOWN " ) ;
stop ( ) ;
SDL_Quit ( ) ;
2020-04-30 02:53:45 +00:00
if ( gui_data . image_buffers [ 0 ] ) g_free ( gui_data . image_buffers [ 0 ] ) ;
if ( gui_data . image_buffers [ 1 ] ) g_free ( gui_data . image_buffers [ 1 ] ) ;
if ( gui_data . image_buffers [ 2 ] ) g_free ( gui_data . image_buffers [ 2 ] ) ;
free_shader ( & gui_data . shader ) ;
2020-04-30 01:36:02 +00:00
free_master_shader ( ) ;
GB_free ( & gb ) ;
2020-05-12 22:25:13 +00:00
g_object_unref ( gui_data . builder ) ;
2020-04-30 01:36:02 +00:00
}
// This function gets called when there are files to open.
// Note: When `open` gets called `activate` won’ t fire unless we call it ourselves.
static void open ( GApplication * app , GFile * * files , gint n_files , const gchar * hint , gpointer null_ptr ) {
if ( n_files > 1 ) {
g_warning ( " More than one file specified " ) ;
exit ( EXIT_FAILURE ) ;
2019-10-14 19:28:39 +00:00
}
2020-04-30 01:36:02 +00:00
gui_data . file = g_file_dup ( files [ 0 ] ) ;
// We have handled the files, now activate the application
activate ( app , NULL ) ;
}
// 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 ) ) ;
}
// app.show_console GAction
// Opens the console
static void activate_show_console ( GSimpleAction * action , GVariant * parameter , gpointer app ) {
2020-04-30 02:53:45 +00:00
if ( gui_data . debugger_input_queue ) {
while ( gui_data . debugger_input_queue - > len ) {
g_ptr_array_remove_index_fast ( gui_data . debugger_input_queue , gui_data . debugger_input_queue - > len - 1 ) ;
2020-04-30 01:36:02 +00:00
}
2019-09-30 14:40:55 +00:00
}
2020-04-30 01:36:02 +00:00
gtk_widget_show_all ( builder_get ( GTK_WIDGET , " console " ) ) ;
2019-09-30 14:40:55 +00:00
}
2020-04-30 01:36:02 +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 ) ;
}
2020-04-10 19:42:03 +00:00
2020-04-30 01:36:02 +00:00
// app.open_memory_viewer GAction
// Opens the memory viewer window
static void activate_open_memory_viewer ( GSimpleAction * action , GVariant * parameter , gpointer app ) {
2020-04-30 02:53:45 +00:00
gtk_widget_show_all ( GTK_WIDGET ( gui_data . memory_viewer ) ) ;
2020-04-30 01:36:02 +00:00
}
2020-04-10 20:19:40 +00:00
2020-04-30 01:36:02 +00:00
// app.open_vram_viewer GAction
// Opens the VRAM viewer window
static void activate_open_vram_viewer ( GSimpleAction * action , GVariant * parameter , gpointer app ) {
2020-04-30 02:53:45 +00:00
gtk_widget_show_all ( GTK_WIDGET ( gui_data . vram_viewer ) ) ;
2020-04-30 01:36:02 +00:00
}
2020-04-10 19:42:03 +00:00
2020-04-30 01:36:02 +00:00
// Closes a ROM
2020-05-14 02:22:58 +00:00
static void close_rom ( void ) {
2020-04-30 01:36:02 +00:00
stop ( ) ;
GB_free ( & gb ) ;
// Clear the screen as side effect
update_window_geometry ( ) ;
2020-05-14 02:22:58 +00:00
if ( gui_data . fallback_canvas ) {
gtk_widget_queue_draw ( GTK_WIDGET ( gui_data . main_window ) ) ;
}
else if ( gui_data . gl_area ) {
gtk_gl_area_queue_render ( gui_data . gl_area ) ;
}
2019-10-14 19:28:39 +00:00
2020-04-30 01:36:02 +00:00
// Clear the VRAM viewer
2020-05-17 03:33:41 +00:00
// g_mutex_lock(&gui_data.tileset_buffer_mutex);
// memset(gui_data.tileset_buffer, 0, sizeof gui_data.tileset_buffer);
// g_mutex_unlock(&gui_data.tileset_buffer_mutex);
2020-04-30 01:36:02 +00:00
2020-05-17 03:33:41 +00:00
// g_mutex_lock(&gui_data.tilemap_buffer_mutex);
// memset(gui_data.tilemap_buffer, 0, sizeof gui_data.tilemap_buffer);
// g_mutex_unlock(&gui_data.tilemap_buffer_mutex);
2019-10-14 19:28:39 +00:00
2020-04-30 01:36:02 +00:00
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 ) ;
2019-10-14 19:28:39 +00:00
2020-04-30 01:36:02 +00:00
// Redraw the VRAM viewer
2020-04-30 02:53:45 +00:00
gtk_widget_queue_draw ( GTK_WIDGET ( gui_data . vram_viewer ) ) ;
2019-10-14 19:28:39 +00:00
2020-04-30 01:36:02 +00:00
// Update menu action states
2020-04-30 02:53:45 +00:00
action_set_enabled ( gui_data . main_application , " close " , false ) ;
2020-04-30 01:36:02 +00:00
action_entries_set_enabled ( emulation_entries , G_N_ELEMENTS ( emulation_entries ) , false ) ;
2020-05-14 02:22:58 +00:00
// Try force the queued redraws
while ( g_main_context_pending ( NULL ) ) {
g_main_context_iteration ( NULL , FALSE ) ;
}
}
// app.open GAction
// Opens a ROM file
static void activate_open ( GSimpleAction * action , GVariant * parameter , gpointer app ) {
stop ( ) ;
GtkFileChooserNative * native = gtk_file_chooser_native_new ( " Open File " , GTK_WINDOW ( gui_data . main_window ) , GTK_FILE_CHOOSER_ACTION_OPEN , " _Open " , " _Cancel " ) ;
gint res = gtk_native_dialog_run ( GTK_NATIVE_DIALOG ( native ) ) ;
if ( res = = GTK_RESPONSE_ACCEPT ) {
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 ) ;
}
else {
run ( ) ;
}
g_object_unref ( native ) ;
}
// app.close GAction
static void activate_close ( GSimpleAction * action , GVariant * parameter , gpointer app ) {
close_rom ( ) ;
2020-04-30 01:36:02 +00:00
}
2019-10-14 19:28:39 +00:00
2020-04-30 01:36:02 +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-10-14 19:28:39 +00:00
2020-04-30 01:36:02 +00:00
// app.quit GAction
// Exits the application
static void activate_quit ( GSimpleAction * action , GVariant * parameter , gpointer app ) {
quit ( ) ;
}
2019-10-14 19:28:39 +00:00
2020-04-30 01:36:02 +00:00
// app.clear_console GAction
// Clears the debugger console
static void activate_clear_console ( GSimpleAction * action , GVariant * parameter , gpointer app ) {
2020-04-30 02:53:45 +00:00
g_rec_mutex_lock ( & gui_data . console_output_lock ) ;
2020-04-30 01:36:02 +00:00
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 ) ;
2020-04-30 02:53:45 +00:00
g_rec_mutex_unlock ( & gui_data . console_output_lock ) ;
2020-04-30 01:36:02 +00:00
}
static void on_mute_changed ( GSimpleAction * action , GVariant * value , gpointer user_data_ptr ) {
2020-05-12 21:32:49 +00:00
config . audio . muted = g_variant_get_boolean ( value ) ;
2020-04-10 01:22:16 +00:00
2020-05-12 21:32:49 +00:00
GB_audio_set_paused ( config . audio . muted ) ;
2020-04-30 01:36:02 +00:00
g_simple_action_set_state ( action , value ) ;
2019-10-12 21:11:26 +00:00
}
2019-09-22 22:47:42 +00:00
2020-04-30 01:36:02 +00:00
static void on_pause_changed ( GSimpleAction * action , GVariant * value , gpointer user_data_ptr ) {
if ( g_variant_get_boolean ( value ) ) {
stop ( ) ;
2020-04-10 01:22:16 +00:00
}
else {
2020-04-30 01:36:02 +00:00
run ( ) ;
2019-10-12 21:11:26 +00:00
}
2020-04-10 01:25:53 +00:00
2020-04-30 01:36:02 +00:00
g_simple_action_set_state ( action , value ) ;
2019-10-12 21:11:26 +00:00
}
2020-04-11 22:38:18 +00:00
2020-05-14 02:22:58 +00:00
G_MODULE_EXPORT void on_open_recent_activate ( GtkRecentChooser * chooser , gpointer user_data_ptr ) {
stop ( ) ;
gchar * uri = gtk_recent_chooser_get_current_uri ( chooser ) ;
GFile * file = g_file_new_for_uri ( uri ) ;
if ( g_file_query_exists ( file , NULL ) ) {
gui_data . file = file ;
// Add the file back to the top of the list
GtkRecentManager * manager = gtk_recent_manager_get_default ( ) ;
gtk_recent_manager_add_item ( manager , uri ) ;
// TODO: Not nice
activate_reset ( NULL , NULL , NULL ) ;
}
else {
// TODO
g_warning ( " File not found: %s " , uri ) ;
close_rom ( ) ;
}
}
2020-04-30 01:36:02 +00:00
G_MODULE_EXPORT void on_boot_rom_location_changed ( GtkWidget * w , gpointer user_data_ptr ) {
GtkComboBox * box = GTK_COMBO_BOX ( w ) ;
const gchar * id = gtk_combo_box_get_active_id ( box ) ;
if ( id = = NULL ) return ;
2019-10-12 21:11:26 +00:00
2020-04-30 01:36:02 +00:00
if ( g_strcmp0 ( id , " other " ) = = 0 ) {
2020-04-30 02:53:45 +00:00
GtkFileChooserNative * native = gtk_file_chooser_native_new ( " Select Folder " , gui_data . preferences , GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER , " _Select " , " _Cancel " ) ;
2020-04-30 01:36:02 +00:00
gint res = gtk_native_dialog_run ( GTK_NATIVE_DIALOG ( native ) ) ;
2019-10-12 21:11:26 +00:00
2020-04-30 01:36:02 +00:00
if ( res = = GTK_RESPONSE_ACCEPT ) {
2020-05-12 18:55:29 +00:00
config . emulation . boot_rom_path = gtk_file_chooser_get_filename ( GTK_FILE_CHOOSER ( native ) ) ;
2020-04-30 02:53:45 +00:00
update_boot_rom_selector ( gui_data . builder ) ;
2020-04-30 01:36:02 +00:00
}
2020-04-10 19:42:03 +00:00
2020-04-30 01:36:02 +00:00
g_object_unref ( native ) ;
}
else {
2020-05-12 18:55:29 +00:00
config . emulation . boot_rom_path = ( gchar * ) id ;
2020-04-10 19:42:03 +00:00
}
2019-10-12 21:11:26 +00:00
}
2020-04-30 01:36:02 +00:00
G_MODULE_EXPORT void on_cgb_model_changed ( GtkWidget * w , gpointer user_data_ptr ) {
GtkComboBox * box = GTK_COMBO_BOX ( w ) ;
2020-05-12 18:55:29 +00:00
config . emulation . cgb_revision_name = ( gchar * ) gtk_combo_box_get_active_id ( box ) ;
2020-04-30 01:36:02 +00:00
}
2020-04-10 16:10:09 +00:00
2020-04-30 01:36:02 +00:00
G_MODULE_EXPORT void on_color_correction_changed ( GtkWidget * w , gpointer user_data_ptr ) {
GtkComboBox * box = GTK_COMBO_BOX ( w ) ;
2020-05-12 18:55:29 +00:00
config . video . color_correction_id = ( gchar * ) gtk_combo_box_get_active_id ( box ) ;
2020-04-10 16:10:09 +00:00
2020-04-30 01:36:02 +00:00
if ( GB_is_inited ( & gb ) ) {
2020-05-16 14:56:09 +00:00
GB_set_color_correction_mode ( & gb , config_get_color_correction_mode ( ) ) ;
2019-09-23 22:34:21 +00:00
}
2020-04-30 01:36:02 +00:00
}
2020-04-10 02:10:28 +00:00
2020-04-30 01:36:02 +00:00
G_MODULE_EXPORT void on_frame_blending_changed ( GtkWidget * w , gpointer user_data_ptr ) {
GtkComboBox * box = GTK_COMBO_BOX ( w ) ;
2020-05-12 18:55:29 +00:00
config . video . frame_blending_mode = ( gchar * ) gtk_combo_box_get_active_id ( box ) ;
2020-04-30 01:36:02 +00:00
}
2020-04-10 02:10:28 +00:00
2020-04-30 01:36:02 +00:00
G_MODULE_EXPORT void on_display_border_changed ( GtkWidget * w , gpointer user_data_ptr ) {
GtkComboBox * box = GTK_COMBO_BOX ( w ) ;
2020-05-12 18:55:29 +00:00
config . video . display_border_mode = ( gchar * ) gtk_combo_box_get_active_id ( box ) ;
2020-04-10 19:42:03 +00:00
2020-04-30 02:53:45 +00:00
gui_data . border_mode_changed = true ;
2020-04-30 01:36:02 +00:00
}
2019-09-26 18:31:58 +00:00
2020-04-30 01:36:02 +00:00
G_MODULE_EXPORT void on_monochrome_palette_changed ( GtkWidget * w , gpointer user_data_ptr ) {
GtkComboBox * box = GTK_COMBO_BOX ( w ) ;
2020-05-12 18:55:29 +00:00
config . video . monochrome_palette_id = ( gchar * ) gtk_combo_box_get_active_id ( box ) ;
2019-09-23 22:34:21 +00:00
2020-05-16 14:56:09 +00:00
GB_set_palette ( & gb , config_get_monochrome_palette ( ) ) ;
2020-04-30 01:36:02 +00:00
}
2020-04-10 02:10:28 +00:00
2020-04-30 01:36:02 +00:00
G_MODULE_EXPORT void on_dmg_model_changed ( GtkWidget * w , gpointer user_data_ptr ) {
GtkComboBox * box = GTK_COMBO_BOX ( w ) ;
2020-05-12 18:55:29 +00:00
config . emulation . dmg_revision_name = ( gchar * ) gtk_combo_box_get_active_id ( box ) ;
2020-04-30 01:36:02 +00:00
}
2019-10-12 21:11:26 +00:00
2020-04-30 01:36:02 +00:00
G_MODULE_EXPORT void on_graphic_filter_changed ( GtkWidget * w , gpointer user_data_ptr ) {
GtkComboBox * box = GTK_COMBO_BOX ( w ) ;
2020-05-12 18:55:29 +00:00
config . video . shader = ( gchar * ) gtk_combo_box_get_active_id ( box ) ;
2019-10-12 21:11:26 +00:00
2020-04-30 02:53:45 +00:00
free_shader ( & gui_data . shader ) ;
2020-05-12 18:55:29 +00:00
init_shader_with_name ( & gui_data . shader , config . video . shader ) ;
2020-04-30 01:36:02 +00:00
}
G_MODULE_EXPORT void on_highpass_filter_changed ( GtkWidget * w , gpointer user_data_ptr ) {
2020-05-12 18:55:29 +00:00
config . audio . high_pass_filter_id = ( gchar * ) gtk_combo_box_get_active_id ( GTK_COMBO_BOX ( w ) ) ;
2020-04-30 01:36:02 +00:00
if ( GB_is_inited ( & gb ) ) {
2020-05-16 14:56:09 +00:00
GB_set_highpass_filter_mode ( & gb , config_get_highpass_mode ( ) ) ;
2019-10-12 21:11:26 +00:00
}
2020-04-30 01:36:02 +00:00
}
2019-10-12 21:11:26 +00:00
2020-04-30 01:36:02 +00:00
G_MODULE_EXPORT void on_keep_aspect_ratio_changed ( GtkWidget * w , gpointer user_data_ptr ) {
GtkCheckButton * button = GTK_CHECK_BUTTON ( w ) ;
gboolean value = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON ( button ) ) ;
2020-05-12 18:55:29 +00:00
config . video . keep_aspect_ratio = value ;
2020-04-30 01:36:02 +00:00
update_viewport ( ) ;
}
2019-10-12 21:11:26 +00:00
2020-04-30 01:36:02 +00:00
G_MODULE_EXPORT void on_rewind_duration_changed ( GtkWidget * w , gpointer user_data_ptr ) {
GtkComboBox * box = GTK_COMBO_BOX ( w ) ;
2020-05-12 18:55:29 +00:00
config . emulation . rewind_duration = g_ascii_strtoll ( gtk_combo_box_get_active_id ( box ) , NULL , 10 ) ;
GB_set_rewind_length ( & gb , config . emulation . rewind_duration ) ;
2019-10-12 21:11:26 +00:00
}
2020-04-30 01:36:02 +00:00
G_MODULE_EXPORT void on_sample_rate_changed ( GtkWidget * w , gpointer user_data_ptr ) {
GtkComboBox * box = GTK_COMBO_BOX ( w ) ;
2020-05-12 18:55:29 +00:00
config . audio . sample_rate = g_ascii_strtoll ( gtk_combo_box_get_active_id ( box ) , NULL , 10 ) ;
2019-10-14 14:01:51 +00:00
2020-05-12 18:55:29 +00:00
if ( config . audio . sample_rate = = - 1 ) {
2020-04-30 01:36:02 +00:00
gui_data . sample_rate = GB_audio_default_sample_rate ( ) ;
2019-10-14 14:01:51 +00:00
}
else {
2020-05-12 18:55:29 +00:00
gui_data . sample_rate = config . audio . sample_rate ;
2019-10-14 14:01:51 +00:00
}
2020-04-30 01:36:02 +00:00
init_audio ( ) ;
}
G_MODULE_EXPORT void on_sgb_model_changed ( GtkWidget * w , gpointer user_data_ptr ) {
GtkComboBox * box = GTK_COMBO_BOX ( w ) ;
2020-05-12 18:55:29 +00:00
config . emulation . sgb_revision_name = ( gchar * ) gtk_combo_box_get_active_id ( box ) ;
2020-04-30 01:36:02 +00:00
}
2020-04-10 20:19:40 +00:00
2020-04-30 01:36:02 +00:00
G_MODULE_EXPORT void on_use_integer_scaling_changed ( GtkWidget * w , gpointer user_data_ptr ) {
GtkCheckButton * button = GTK_CHECK_BUTTON ( w ) ;
gboolean value = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON ( button ) ) ;
2020-05-12 18:55:29 +00:00
config . video . use_integer_scaling = value ;
2020-04-30 01:36:02 +00:00
update_viewport ( ) ;
}
2019-10-12 21:11:26 +00:00
2020-04-30 01:36:02 +00:00
G_MODULE_EXPORT void console_on_enter ( GtkWidget * w , gpointer user_data_ptr ) {
GtkEntry * input = GTK_ENTRY ( w ) ;
const gchar * _text = gtk_entry_get_text ( input ) ;
gchar * text = g_strdup ( _text ) ;
2019-10-12 21:11:26 +00:00
2020-04-30 02:53:45 +00:00
if ( g_strcmp0 ( " " , text ) = = 0 & & g_strcmp0 ( " " , gui_data . last_console_input ) < 0 ) {
text = g_strdup ( gui_data . last_console_input ) ;
2020-04-30 01:36:02 +00:00
}
else if ( text ) {
2020-04-30 02:53:45 +00:00
if ( gui_data . last_console_input ! = NULL ) g_free ( gui_data . last_console_input ) ;
gui_data . last_console_input = g_strdup ( text ) ;
2019-10-12 21:11:26 +00:00
}
2020-04-30 02:53:45 +00:00
if ( ! gui_data . in_sync_input ) {
2020-04-30 01:36:02 +00:00
console_log ( & gb , " > " , 0 ) ;
}
2020-04-11 22:38:18 +00:00
2020-04-30 01:36:02 +00:00
console_log ( & gb , text , 0 ) ;
console_log ( & gb , " \n " , 0 ) ;
2019-10-12 21:11:26 +00:00
2020-04-30 02:53:45 +00:00
g_mutex_lock ( & gui_data . debugger_input_mutex ) ;
g_ptr_array_add ( gui_data . debugger_input_queue , ( gpointer ) text ) ;
g_cond_signal ( & gui_data . debugger_input_cond ) ;
g_mutex_unlock ( & gui_data . debugger_input_mutex ) ;
2019-10-12 21:11:26 +00:00
2020-04-30 01:36:02 +00:00
// clear input
gtk_entry_set_text ( input , " " ) ;
2019-10-12 21:11:26 +00:00
}
2020-05-03 23:41:14 +00:00
G_MODULE_EXPORT void on_rumble_mode_changed ( GtkWidget * w , gpointer user_data_ptr ) {
GtkComboBox * box = GTK_COMBO_BOX ( w ) ;
2020-05-12 18:55:29 +00:00
config . controls . rumble_mode = ( gchar * ) gtk_combo_box_get_active_id ( box ) ;
2020-05-03 23:41:14 +00:00
2020-05-16 14:56:09 +00:00
GB_set_rumble_mode ( & gb , config_get_rumble_mode ( ) ) ;
2020-05-03 23:41:14 +00:00
}
G_MODULE_EXPORT void on_analog_speed_controls_changed ( GtkWidget * w , gpointer user_data_ptr ) {
GtkCheckButton * button = GTK_CHECK_BUTTON ( w ) ;
gboolean value = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON ( button ) ) ;
2020-05-12 18:55:29 +00:00
config . controls . analog_speed_controls = value ;
2020-05-03 23:41:14 +00:00
}
2020-04-30 01:36:02 +00:00
int main ( int argc , char * argv [ ] ) {
2020-04-30 02:53:45 +00:00
gui_data . main_thread = g_thread_self ( ) ;
2019-10-12 21:11:26 +00:00
2020-04-30 01:36:02 +00:00
// Create our GApplication and tell GTK that we are able to handle files
2020-04-30 02:53:45 +00:00
gui_data . main_application = gtk_application_new ( APP_ID , G_APPLICATION_NON_UNIQUE | G_APPLICATION_HANDLES_OPEN ) ;
2019-10-05 19:09:30 +00:00
2020-04-30 01:36:02 +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 } ,
2020-05-05 19:38:56 +00:00
{ " fullscreen " , ' f ' , G_OPTION_FLAG_NONE , G_OPTION_ARG_NONE , & gui_data . cli_options . fullscreen , " Start in fullscreen mode " , NULL } ,
2020-04-30 01:36:02 +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> " } ,
{ " model " , ' m ' , G_OPTION_FLAG_NONE , G_OPTION_ARG_STRING , NULL , " Override the model type to emulate " , " <model type> " } ,
{ " config " , ' c ' , G_OPTION_FLAG_NONE , G_OPTION_ARG_STRING , & gui_data . cli_options . config_path , " Override the path of the configuration file " , " <file path> " } ,
2020-05-05 19:38:56 +00:00
{ " no-gl " , ' s ' , G_OPTION_FLAG_NONE , G_OPTION_ARG_NONE , & gui_data . cli_options . force_software_renderer , " Do not use OpenGL for rendering " , NULL } ,
2020-04-30 01:36:02 +00:00
{ NULL }
} ;
// Setup our command line information
2020-04-30 02:53:45 +00:00
g_application_add_main_option_entries ( G_APPLICATION ( gui_data . main_application ) , entries ) ;
g_application_set_option_context_parameter_string ( G_APPLICATION ( gui_data . main_application ) , " [FILE…] " ) ;
g_application_set_option_context_summary ( G_APPLICATION ( gui_data . main_application ) , " SameBoy is an open source Game Boy (DMG) and Game Boy Color (CGB) emulator. " ) ;
2020-04-30 01:36:02 +00:00
// Add signal handlers
2020-04-30 02:53:45 +00:00
g_signal_connect ( gui_data . main_application , " handle-local-options " , G_CALLBACK ( handle_local_options ) , NULL ) ;
g_signal_connect ( gui_data . main_application , " startup " , G_CALLBACK ( startup ) , NULL ) ;
g_signal_connect ( gui_data . main_application , " activate " , G_CALLBACK ( activate ) , NULL ) ;
g_signal_connect ( gui_data . main_application , " open " , G_CALLBACK ( open ) , NULL ) ;
g_signal_connect ( gui_data . main_application , " shutdown " , G_CALLBACK ( shutdown ) , NULL ) ;
2020-04-30 01:36:02 +00:00
// Start our GApplication main loop
2020-04-30 02:53:45 +00:00
int status = g_application_run ( G_APPLICATION ( gui_data . main_application ) , argc , argv ) ;
g_object_unref ( gui_data . main_application ) ;
2020-04-30 01:36:02 +00:00
return status ;
2020-04-10 01:22:16 +00:00
}