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-05-15 22:23:03 +00:00
# include "check_menu_radio_group.h"
2020-04-30 01:36:02 +00:00
2020-05-19 00:31:31 +00:00
# include "gb_screen.h"
2020-05-21 20:37:25 +00:00
# include "console_window.h"
2020-05-20 01:41:33 +00:00
# include "preferences_window.h"
2020-05-18 15:50:19 +00:00
# include "vram_viewer_window.h"
2020-05-17 03:33:41 +00:00
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
. 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-20 01:41:33 +00:00
static 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 [ ] = {
2021-01-01 16:03:16 +00:00
2020-04-11 22:38:18 +00:00
} ;
static const GActionEntry emulation_entries [ ] = {
{ " reset " , activate_reset , NULL , NULL , NULL } ,
{ " pause " , NULL , NULL , " false " , on_pause_changed } ,
{ " save_state " , NULL , NULL , NULL , NULL } ,
{ " load_state " , NULL , NULL , NULL , NULL } ,
} ;
static const GActionEntry developer_entries [ ] = {
2019-10-07 22:36:16 +00:00
{ " show_console " , activate_show_console , NULL , NULL , NULL } ,
2019-09-30 14:40:55 +00:00
{ " open_gtk_debugger " , activate_open_gtk_debugger , NULL , NULL , NULL } ,
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
}
2021-01-01 16:03:16 +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 ( ) ) ;
2021-01-01 16:03:16 +00:00
2020-04-28 21:42:57 +00:00
// 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-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-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 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 20:51:27 +00:00
static gboolean on_vblank ( GB_gameboy_t * gb ) {
2020-05-19 14:46:08 +00:00
gb_screen_queue_render ( gui_data . screen ) ;
2020-05-17 03:33:41 +00:00
gtk_widget_queue_draw ( GTK_WIDGET ( gui_data . vram_viewer ) ) ;
2020-05-17 20:51:27 +00:00
return false ;
2020-04-11 22:38:18 +00:00
}
2020-04-30 01:36:02 +00:00
static void vblank ( GB_gameboy_t * gb ) {
2020-05-19 00:31:31 +00:00
gb_screen_flip ( gui_data . screen ) ;
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-05-19 00:31:31 +00:00
gb_screen_set_resolution ( gui_data . screen , GB_get_screen_width ( gb ) , GB_get_screen_height ( gb ) ) ;
GB_set_pixels_output ( gb , gb_screen_get_pixels ( gui_data . screen ) ) ;
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-05-19 00:31:31 +00:00
GB_set_pixels_output ( gb , gb_screen_get_pixels ( gui_data . screen ) ) ;
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 ) ;
2020-05-19 00:31:31 +00:00
GB_frame_blending_mode_t mode = config_get_frame_blending_mode ( ) ;
if ( ! gb_screen_get_previous_buffer ( gui_data . screen ) ) {
mode = GB_FRAME_BLENDING_MODE_DISABLED ;
}
else if ( mode = = GB_FRAME_BLENDING_MODE_ACCURATE ) {
mode = GB_FRAME_BLENDING_MODE_DISABLED ;
if ( GB_is_sgb ( gb ) ) {
mode = GB_FRAME_BLENDING_MODE_SIMPLE ;
}
else {
mode = GB_is_odd_frame ( gb ) ? GB_FRAME_BLENDING_MODE_ACCURATE_ODD : GB_FRAME_BLENDING_MODE_ACCURATE_EVEN ;
}
}
gb_screen_set_blending_mode ( gui_data . screen , mode ) ;
2020-05-17 03:33:41 +00:00
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 ;
}
2021-01-01 16:03:16 +00:00
2020-05-03 22:02:15 +00:00
if ( SDL_GameControllerGetButton ( s - > controller , SDL_CONTROLLER_BUTTON_B ) ) {
gui_data . last_used_controller = s ;
controller_state | = BUTTON_MASK_B ;
}
2021-01-01 16:03:16 +00:00
2020-05-03 22:02:15 +00:00
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-05-21 20:37:25 +00:00
static char * wrapped_console_get_async_input ( GB_gameboy_t * gb ) {
return console_get_async_input ( gui_data . console , gb ) ;
}
static char * wrapped_console_get_sync_input ( GB_gameboy_t * gb ) {
return console_get_sync_input ( gui_data . console , gb ) ;
}
static void wrapped_console_log ( GB_gameboy_t * gb , const char * message , GB_log_attributes attributes ) {
console_log ( gui_data . console , message , attributes ) ;
}
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_rgb_encode_callback ( & gb , rgb_encode ) ;
2020-05-21 20:37:25 +00:00
GB_set_pixels_output ( & gb , gb_screen_get_current_buffer ( gui_data . screen ) ) ;
2020-05-16 14:56:09 +00:00
GB_set_color_correction_mode ( & gb , config_get_color_correction_mode ( ) ) ;
2020-05-21 20:37:25 +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
GB_apu_set_sample_callback ( & gb , gb_audio_callback ) ;
2021-01-01 16:03:16 +00:00
2020-05-21 20:37:25 +00:00
GB_set_sample_rate ( & gb , GB_audio_get_sample_rate ( ) ) ;
GB_set_highpass_filter_mode ( & gb , config_get_highpass_mode ( ) ) ;
2021-01-01 16:03:16 +00:00
GB_set_interference_volume ( & gb , ( double ) config . audio . interference_volume / 255.0 ) ;
2019-09-27 22:15:37 +00:00
2020-05-21 20:37:25 +00:00
GB_set_log_callback ( & gb , wrapped_console_log ) ;
GB_set_input_callback ( & gb , wrapped_console_get_sync_input ) ;
GB_set_async_input_callback ( & gb , wrapped_console_get_async_input ) ;
GB_set_boot_rom_load_callback ( & gb , load_boot_rom ) ;
GB_set_update_input_hint_callback ( & gb , handle_events ) ;
2020-05-03 19:11:21 +00:00
GB_set_rumble_callback ( & gb , rumble_callback ) ;
2020-05-21 20:37:25 +00:00
GB_set_rumble_mode ( & gb , config_get_rumble_mode ( ) ) ;
GB_set_rewind_length ( & gb , config . emulation . rewind_duration ) ;
2020-04-30 01:36:02 +00:00
}
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-05-19 00:31:31 +00:00
gb_screen_set_resolution ( gui_data . screen , GB_get_screen_width ( & gb ) , GB_get_screen_height ( & gb ) ) ;
GB_set_pixels_output ( & gb , gb_screen_get_pixels ( gui_data . screen ) ) ;
2020-04-30 01:36:02 +00:00
}
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 ) ;
2021-01-01 16:03:16 +00:00
2020-04-30 04:02:48 +00:00
/* 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
2021-01-01 16:03:16 +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 ;
2020-05-02 16:44:24 +00:00
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
}
2021-01-01 16:03:16 +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-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-05-20 01:41:33 +00:00
init_config ( app , gui_data . cli_options . config_path , & gui_data . config_modification_date ) ;
2020-04-10 15:45:35 +00:00
2020-05-19 00:31:31 +00:00
gui_data . screen = gb_screen_new ( gui_data . cli_options . force_software_renderer ) ;
2020-05-21 20:37:25 +00:00
gui_data . console = console_window_new ( ) ;
2020-05-20 01:41:33 +00:00
gui_data . preferences = preferences_window_new ( & gb ) ;
gui_data . vram_viewer = vram_viewer_window_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 . 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-05-19 00:31:31 +00:00
gtk_box_pack_end ( GTK_BOX ( gui_data . main_window_container ) , GTK_WIDGET ( gui_data . screen ) , true , true , 0 ) ;
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 . 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 ) ;
2020-05-19 00:31:31 +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-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-05-20 01:41:33 +00:00
void on_preferences_notify_border ( PreferencesWindow * pref , const gchar * name ) {
gui_data . border_mode_changed = true ;
}
void on_preferences_notify_shader ( PreferencesWindow * pref , const gchar * name ) {
gb_screen_set_shader ( gui_data . screen , name ) ;
}
void on_preferences_notify_sample_rate ( PreferencesWindow * pref , const guint * sample_rate ) {
if ( * sample_rate = = - 1 ) {
gui_data . sample_rate = GB_audio_default_sample_rate ( ) ;
}
else {
gui_data . sample_rate = * sample_rate ;
}
init_audio ( ) ;
}
2021-01-01 16:03:16 +00:00
void on_preferences_notify_interference_volume ( PreferencesWindow * pref , const guint * interference_volume ) {
if ( GB_is_inited ( & gb ) ) {
GB_set_interference_volume ( & gb , ( double ) * interference_volume / 255.0 ) ;
}
}
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-05-24 13:20:16 +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 ) ;
2020-04-30 02:53:45 +00:00
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 ) ;
2020-05-20 01:41:33 +00:00
g_signal_connect ( gui_data . preferences , " pref-update::video-display-border-mode " , G_CALLBACK ( on_preferences_notify_border ) , NULL ) ;
g_signal_connect ( gui_data . preferences , " pref-update::video-shader " , G_CALLBACK ( on_preferences_notify_shader ) , NULL ) ;
g_signal_connect ( gui_data . preferences , " pref-update::audio-sample-rate " , G_CALLBACK ( on_preferences_notify_sample_rate ) , NULL ) ;
2021-01-01 16:03:16 +00:00
g_signal_connect ( gui_data . preferences , " pref-update::audio-interference-volume " , G_CALLBACK ( on_preferences_notify_interference_volume ) , NULL ) ;
2019-09-30 14:40:55 +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 ) ;
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-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 ( ) ;
GB_free ( & gb ) ;
2021-01-01 16:03:16 +00:00
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 ) ) ;
}
2020-05-21 20:37:25 +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 ( gui_data . preferences ) ) ;
}
2020-04-30 01:36:02 +00:00
// app.show_console GAction
// Opens the console
static void activate_show_console ( GSimpleAction * action , GVariant * parameter , gpointer app ) {
2020-05-21 20:37:25 +00:00
gtk_widget_show_all ( GTK_WIDGET ( gui_data . 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-05-21 20:37:25 +00:00
// app.clear_console GAction
// Clears the debugger console
static void activate_clear_console ( GSimpleAction * action , GVariant * parameter , gpointer app ) {
console_clear ( gui_data . console ) ;
}
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 ) ;
2021-01-01 16:03:16 +00:00
2020-05-19 00:31:31 +00:00
gb_screen_clear ( gui_data . screen ) ;
2020-05-19 14:46:08 +00:00
gb_screen_queue_render ( gui_data . screen ) ;
2019-10-14 19:28:39 +00:00
2020-05-17 21:13:59 +00:00
vram_viewer_clear ( gui_data . 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 ) ;
2021-01-01 16:03:16 +00:00
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.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
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
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
}