2021-06-17 21:32:24 +00:00
# include <unistd.h>
# include <SDL/SDL.h>
2022-06-11 01:59:57 +00:00
# include <libreborn/libreborn.h>
2021-06-17 21:32:24 +00:00
2021-08-27 02:52:18 +00:00
# ifndef MCPI_HEADLESS_MODE
2022-10-01 05:37:20 +00:00
# include <time.h>
2021-06-17 21:32:24 +00:00
# define GLFW_INCLUDE_NONE
# include <GLFW/glfw3.h>
2022-03-14 23:09:25 +00:00
# endif
2021-06-17 21:32:24 +00:00
2021-09-12 03:18:12 +00:00
# include <media-layer/core.h>
# include <media-layer/internal.h>
2021-06-17 21:32:24 +00:00
2021-09-14 22:10:32 +00:00
# ifndef MCPI_HEADLESS_MODE
2021-09-12 03:18:12 +00:00
# include "audio/engine.h"
2022-03-14 23:09:25 +00:00
# endif
2021-09-12 03:18:12 +00:00
2021-11-14 04:29:48 +00:00
// Allow Disabling Interaction
static void update_cursor ( ) ;
2022-10-01 23:29:11 +00:00
# ifndef MCPI_HEADLESS_MODE
static void emit_events_after_is_interactable_change ( ) ;
# endif
2021-11-14 04:29:48 +00:00
static int is_interactable = 1 ;
void media_set_interactable ( int toggle ) {
2022-10-01 05:37:20 +00:00
if ( toggle ! = is_interactable ) {
is_interactable = toggle ;
update_cursor ( ) ;
# ifndef MCPI_HEADLESS_MODE
emit_events_after_is_interactable_change ( ) ;
# endif
}
2021-11-14 04:29:48 +00:00
}
2022-10-01 05:37:20 +00:00
// Track Media Layer State
static volatile int is_running = 0 ;
// Store Cursor State
static int cursor_grabbed = 0 ;
static int cursor_visible = 1 ;
2024-03-19 04:25:50 +00:00
// Track If Raw Mouse Motion Is Enabled
static int raw_mouse_motion_enabled = 1 ;
2021-09-12 03:18:12 +00:00
// GLFW Code Not Needed In Headless Mode
2021-08-27 02:52:18 +00:00
# ifndef MCPI_HEADLESS_MODE
2021-06-17 21:32:24 +00:00
2022-03-07 01:07:49 +00:00
static GLFWwindow * glfw_window = NULL ;
2021-06-17 21:32:24 +00:00
// Handle GLFW Error
static void glfw_error ( __attribute__ ( ( unused ) ) int error , const char * description ) {
WARN ( " GLFW Error: %s " , description ) ;
}
2021-07-04 23:02:45 +00:00
// Pass Character Event
static void character_event ( char c ) {
// SDL_UserEvent Is Never Used In MCPI, So It Is Repurposed For Character Events
SDL_Event event ;
event . type = SDL_USEREVENT ;
2023-05-28 04:45:58 +00:00
event . user . code = USER_EVENT_CHARACTER ;
event . user . data1 = ( int ) c ;
2021-07-04 23:02:45 +00:00
SDL_PushEvent ( & event ) ;
}
2021-06-17 21:32:24 +00:00
// Convert GLFW Key To SDL Key
2022-10-07 04:06:50 +00:00
# define IMAGINARY_GLFW_CRAFTING_KEY GLFW_KEY_LAST
2021-06-17 21:32:24 +00:00
static SDLKey glfw_key_to_sdl_key ( int key ) {
switch ( key ) {
// Movement
case GLFW_KEY_W :
return SDLK_w ;
case GLFW_KEY_A :
return SDLK_a ;
case GLFW_KEY_S :
return SDLK_s ;
case GLFW_KEY_D :
return SDLK_d ;
case GLFW_KEY_SPACE :
return SDLK_SPACE ;
case GLFW_KEY_LEFT_SHIFT :
return SDLK_LSHIFT ;
case GLFW_KEY_RIGHT_SHIFT :
return SDLK_RSHIFT ;
// Inventory
case GLFW_KEY_E :
return SDLK_e ;
2021-07-04 23:02:45 +00:00
// Drop Item
case GLFW_KEY_Q :
return SDLK_q ;
2022-10-02 05:23:46 +00:00
// Toolbar
2021-06-17 21:32:24 +00:00
case GLFW_KEY_1 :
return SDLK_1 ;
case GLFW_KEY_2 :
return SDLK_2 ;
case GLFW_KEY_3 :
return SDLK_3 ;
case GLFW_KEY_4 :
return SDLK_4 ;
case GLFW_KEY_5 :
return SDLK_5 ;
case GLFW_KEY_6 :
return SDLK_6 ;
case GLFW_KEY_7 :
return SDLK_7 ;
case GLFW_KEY_8 :
return SDLK_8 ;
2021-10-27 22:14:30 +00:00
case GLFW_KEY_9 :
return SDLK_9 ;
case GLFW_KEY_0 :
return SDLK_0 ;
2021-06-17 21:32:24 +00:00
// UI Control
case GLFW_KEY_ESCAPE :
return SDLK_ESCAPE ;
case GLFW_KEY_UP :
return SDLK_UP ;
case GLFW_KEY_DOWN :
return SDLK_DOWN ;
case GLFW_KEY_LEFT :
return SDLK_LEFT ;
case GLFW_KEY_RIGHT :
return SDLK_RIGHT ;
case GLFW_KEY_TAB :
return SDLK_TAB ;
case GLFW_KEY_ENTER :
return SDLK_RETURN ;
case GLFW_KEY_BACKSPACE :
return SDLK_BACKSPACE ;
2024-02-01 08:12:24 +00:00
case GLFW_KEY_DELETE :
return SDLK_DELETE ;
2021-06-17 21:32:24 +00:00
// Fullscreen
case GLFW_KEY_F11 :
return SDLK_F11 ;
// Screenshot
case GLFW_KEY_F2 :
return SDLK_F2 ;
// Hide GUI
case GLFW_KEY_F1 :
return SDLK_F1 ;
// Third Person
case GLFW_KEY_F5 :
return SDLK_F5 ;
// Chat
case GLFW_KEY_T :
return SDLK_t ;
2022-10-07 04:06:50 +00:00
// Crafting
case IMAGINARY_GLFW_CRAFTING_KEY :
return SDLK_WORLD_0 ;
2021-06-17 21:32:24 +00:00
// Unknown
default :
return SDLK_UNKNOWN ;
}
}
2021-07-04 23:02:45 +00:00
// Convert GLFW Key Modifier To SDL Key Modifier
static SDLMod glfw_modifier_to_sdl_modifier ( int mods ) {
SDLMod ret = KMOD_NONE ;
// Control
if ( ( mods & GLFW_MOD_CONTROL ) ! = 0 ) {
ret | = KMOD_CTRL ;
}
// Shift
if ( ( mods & GLFW_MOD_SHIFT ) ! = 0 ) {
ret | = KMOD_SHIFT ;
}
// Alt
if ( ( mods & GLFW_MOD_ALT ) ! = 0 ) {
ret | = KMOD_ALT ;
}
// Return
return ret ;
2021-06-17 21:32:24 +00:00
}
// Pass Key Presses To SDL
2023-05-28 04:45:58 +00:00
static void glfw_key_raw ( int key , int scancode , int action , int mods ) {
SDL_Event event1 ;
2022-10-01 05:37:20 +00:00
int up = action = = GLFW_RELEASE ;
2023-05-28 04:45:58 +00:00
event1 . type = up ? SDL_KEYUP : SDL_KEYDOWN ;
event1 . key . state = up ? SDL_RELEASED : SDL_PRESSED ;
event1 . key . keysym . scancode = scancode ;
event1 . key . keysym . mod = glfw_modifier_to_sdl_modifier ( mods ) ;
event1 . key . keysym . sym = glfw_key_to_sdl_key ( key ) ;
SDL_PushEvent ( & event1 ) ;
// Allow MCPI To Access Original GLFW Keycode
SDL_Event event2 ;
event2 . type = SDL_USEREVENT ;
event2 . user . code = USER_EVENT_REAL_KEY ;
event2 . user . data1 = event1 . key . state ;
event2 . user . data2 = key ;
SDL_PushEvent ( & event2 ) ;
2022-10-01 05:37:20 +00:00
}
2022-03-06 20:53:27 +00:00
static void glfw_key ( __attribute__ ( ( unused ) ) GLFWwindow * window , int key , int scancode , int action , int mods ) {
2021-11-14 04:29:48 +00:00
if ( is_interactable ) {
2022-10-01 05:37:20 +00:00
glfw_key_raw ( key , scancode , action , mods ) ;
2021-06-17 21:32:24 +00:00
}
}
// Pass Text To Minecraft
2022-07-31 03:52:50 +00:00
static void codepoint_to_utf8 ( unsigned char * const buffer , const unsigned int code ) {
// https://stackoverflow.com/a/42013433/16198887
if ( code < = 0x7f ) {
buffer [ 0 ] = code ;
} else if ( code < = 0x7ff ) {
buffer [ 0 ] = 0xc0 | ( code > > 6 ) ; // 110xxxxx
buffer [ 1 ] = 0x80 | ( code & 0x3f ) ; // 10xxxxxx
} else if ( code < = 0xffff ) {
buffer [ 0 ] = 0xe0 | ( code > > 12 ) ; // 1110xxxx
buffer [ 1 ] = 0x80 | ( ( code > > 6 ) & 0x3f ) ; // 10xxxxxx
buffer [ 2 ] = 0x80 | ( code & 0x3f ) ; // 10xxxxxx
} else if ( code < = 0x10ffff ) {
buffer [ 0 ] = 0xf0 | ( code > > 18 ) ; // 11110xxx
buffer [ 1 ] = 0x80 | ( ( code > > 12 ) & 0x3f ) ; // 10xxxxxx
buffer [ 2 ] = 0x80 | ( ( code > > 6 ) & 0x3f ) ; // 10xxxxxx
buffer [ 3 ] = 0x80 | ( code & 0x3f ) ; // 10xxxxxx
}
}
2021-06-17 21:32:24 +00:00
static void glfw_char ( __attribute__ ( ( unused ) ) GLFWwindow * window , unsigned int codepoint ) {
2021-11-14 04:29:48 +00:00
if ( is_interactable ) {
2022-07-31 03:52:50 +00:00
// Convert
size_t str_size = 4 /* Maximum UTF-8 character size */ + 1 /* NULL-terminator */ ;
char str [ str_size ] ;
memset ( str , 0 , str_size ) ;
codepoint_to_utf8 ( ( unsigned char * ) str , codepoint ) ;
char * cp437_str = to_cp437 ( str ) ;
2024-02-02 09:20:34 +00:00
// Send Event
2022-07-31 03:52:50 +00:00
for ( int i = 0 ; cp437_str [ i ] ! = ' \0 ' ; i + + ) {
character_event ( cp437_str [ i ] ) ;
2022-07-20 06:58:14 +00:00
}
// Free
2022-07-31 03:52:50 +00:00
free ( cp437_str ) ;
2021-11-14 04:29:48 +00:00
}
2021-06-17 21:32:24 +00:00
}
2021-11-14 04:29:48 +00:00
// Last Mouse Location
2021-06-17 21:32:24 +00:00
static double last_mouse_x = 0 ;
static double last_mouse_y = 0 ;
2021-11-14 04:29:48 +00:00
// Ignore Relative Cursor Motion
static int ignore_relative_motion = 0 ;
2021-06-17 21:32:24 +00:00
2024-02-24 01:50:56 +00:00
// Convert Screen Coordinates To Pixels
static void convert_to_pixels ( GLFWwindow * window , double * xpos , double * ypos ) {
2024-03-19 04:25:50 +00:00
// Skip If Cursor Is Grabbed
if ( cursor_grabbed & & raw_mouse_motion_enabled ) {
return ;
}
2024-02-24 01:50:56 +00:00
// Get Window Size
int window_width ;
int window_height ;
glfwGetWindowSize ( window , & window_width , & window_height ) ;
// Get Framebuffer Size
int framebuffer_width ;
int framebuffer_height ;
glfwGetFramebufferSize ( window , & framebuffer_width , & framebuffer_height ) ;
// Calculate Ratios
double width_ratio = ( ( double ) framebuffer_width ) / ( ( double ) window_width ) ;
double height_ratio = ( ( double ) framebuffer_height ) / ( ( double ) window_height ) ;
// Multiply
* xpos * = width_ratio ;
* ypos * = height_ratio ;
}
2021-06-17 21:32:24 +00:00
// Pass Mouse Movement To SDL
static void glfw_motion ( __attribute__ ( ( unused ) ) GLFWwindow * window , double xpos , double ypos ) {
2024-02-24 01:50:56 +00:00
convert_to_pixels ( window , & xpos , & ypos ) ;
2021-11-14 04:29:48 +00:00
if ( is_interactable ) {
SDL_Event event ;
event . type = SDL_MOUSEMOTION ;
event . motion . x = xpos ;
event . motion . y = ypos ;
event . motion . xrel = ! ignore_relative_motion ? ( xpos - last_mouse_x ) : 0 ;
event . motion . yrel = ! ignore_relative_motion ? ( ypos - last_mouse_y ) : 0 ;
SDL_PushEvent ( & event ) ;
}
ignore_relative_motion = 0 ;
2021-06-17 21:32:24 +00:00
last_mouse_x = xpos ;
last_mouse_y = ypos ;
}
// Create And Push SDL Mouse Click Event
static void click_event ( int button , int up ) {
2022-10-01 05:37:20 +00:00
SDL_Event event ;
event . type = up ? SDL_MOUSEBUTTONUP : SDL_MOUSEBUTTONDOWN ;
event . button . x = last_mouse_x ;
event . button . y = last_mouse_y ;
event . button . state = up ? SDL_RELEASED : SDL_PRESSED ;
event . button . button = button ;
SDL_PushEvent ( & event ) ;
2021-06-17 21:32:24 +00:00
}
// Pass Mouse Click To SDL
2022-10-01 05:37:20 +00:00
static void glfw_click_raw ( int button , int action ) {
int up = action = = GLFW_RELEASE ;
int sdl_button = button = = GLFW_MOUSE_BUTTON_RIGHT ? SDL_BUTTON_RIGHT : ( button = = GLFW_MOUSE_BUTTON_LEFT ? SDL_BUTTON_LEFT : SDL_BUTTON_MIDDLE ) ;
click_event ( sdl_button , up ) ;
}
2021-06-17 21:32:24 +00:00
static void glfw_click ( __attribute__ ( ( unused ) ) GLFWwindow * window , int button , int action , __attribute__ ( ( unused ) ) int mods ) {
2021-11-14 04:29:48 +00:00
if ( is_interactable ) {
2022-10-01 05:37:20 +00:00
glfw_click_raw ( button , action ) ;
2021-11-14 04:29:48 +00:00
}
2021-06-17 21:32:24 +00:00
}
// Pass Mouse Scroll To SDL
static void glfw_scroll ( __attribute__ ( ( unused ) ) GLFWwindow * window , __attribute__ ( ( unused ) ) double xoffset , double yoffset ) {
2021-11-14 04:29:48 +00:00
if ( is_interactable & & yoffset ! = 0 ) {
2021-06-17 21:32:24 +00:00
int sdl_button = yoffset > 0 ? SDL_BUTTON_WHEELUP : SDL_BUTTON_WHEELDOWN ;
click_event ( sdl_button , 0 ) ;
click_event ( sdl_button , 1 ) ;
}
}
2022-10-01 05:37:20 +00:00
// Controller Events
static SDLKey glfw_controller_button_to_key ( int button ) {
switch ( button ) {
// Jump
case GLFW_GAMEPAD_BUTTON_A :
return GLFW_KEY_SPACE ;
// Drop Item
case GLFW_GAMEPAD_BUTTON_DPAD_DOWN :
return GLFW_KEY_Q ;
// Inventory
case GLFW_GAMEPAD_BUTTON_Y :
return GLFW_KEY_E ;
// Third-Person
case GLFW_GAMEPAD_BUTTON_DPAD_UP :
return GLFW_KEY_F5 ;
// Sneak
case GLFW_GAMEPAD_BUTTON_B :
return GLFW_KEY_LEFT_SHIFT ;
// Chat
case GLFW_GAMEPAD_BUTTON_DPAD_RIGHT :
return GLFW_KEY_T ;
// Pause
case GLFW_GAMEPAD_BUTTON_START :
case GLFW_GAMEPAD_BUTTON_BACK :
return GLFW_KEY_ESCAPE ;
2022-10-07 04:06:50 +00:00
// Crafting
case GLFW_GAMEPAD_BUTTON_X :
return IMAGINARY_GLFW_CRAFTING_KEY ;
2022-10-01 05:37:20 +00:00
// Unknown
default :
return GLFW_KEY_UNKNOWN ;
}
}
static void glfw_controller_button ( int button , int action ) {
int key = glfw_controller_button_to_key ( button ) ;
if ( key ! = GLFW_KEY_UNKNOWN ) {
2022-10-02 05:23:46 +00:00
// Press Key
2022-10-01 05:37:20 +00:00
glfw_key_raw ( key , glfwGetKeyScancode ( key ) , action , 0 ) ;
} else {
// Scrolling
if ( button = = GLFW_GAMEPAD_BUTTON_LEFT_BUMPER ) {
key = SDL_BUTTON_WHEELUP ;
} else if ( button = = GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER ) {
key = SDL_BUTTON_WHEELDOWN ;
}
if ( key ! = GLFW_KEY_UNKNOWN ) {
click_event ( key , action = = GLFW_PRESS ) ;
}
}
}
2021-06-17 21:32:24 +00:00
2022-10-01 05:37:20 +00:00
// Controller Movement Axis
static int controller_horizontal_key = GLFW_KEY_UNKNOWN ;
static int controller_vertical_key = GLFW_KEY_UNKNOWN ;
static void release_and_press_key ( int * old_key , int new_key ) {
if ( * old_key ! = new_key ) {
if ( * old_key ! = GLFW_KEY_UNKNOWN ) {
glfw_key_raw ( * old_key , glfwGetKeyScancode ( * old_key ) , GLFW_RELEASE , 0 ) ;
}
if ( new_key ! = GLFW_KEY_UNKNOWN ) {
glfw_key_raw ( new_key , glfwGetKeyScancode ( new_key ) , GLFW_PRESS , 0 ) ;
}
}
* old_key = new_key ;
}
# define verify_controller_axis_value(value, threshold) \
if ( ( value < ( threshold ) & & value > 0 ) | | ( value > - ( threshold ) & & value < 0 ) ) { \
value = 0 ; \
}
# define CONTROLLER_MOVEMENT_AXIS_THRESHOLD 0.5f
static void glfw_controller_movement ( float x , float y ) {
// Verify
verify_controller_axis_value ( x , CONTROLLER_MOVEMENT_AXIS_THRESHOLD ) ;
verify_controller_axis_value ( y , CONTROLLER_MOVEMENT_AXIS_THRESHOLD ) ;
// Horizontal Movement
if ( x > 0 ) {
release_and_press_key ( & controller_horizontal_key , GLFW_KEY_D ) ;
} else if ( x < 0 ) {
release_and_press_key ( & controller_horizontal_key , GLFW_KEY_A ) ;
} else {
release_and_press_key ( & controller_horizontal_key , GLFW_KEY_UNKNOWN ) ;
}
// Vertical Movement
if ( y < 0 ) {
release_and_press_key ( & controller_vertical_key , GLFW_KEY_W ) ;
} else if ( y > 0 ) {
release_and_press_key ( & controller_vertical_key , GLFW_KEY_S ) ;
} else {
release_and_press_key ( & controller_vertical_key , GLFW_KEY_UNKNOWN ) ;
}
}
// Get Time
# define NANOSECONDS_IN_SECOND 1000000000ll
static long long int get_time ( ) {
struct timespec ts ;
clock_gettime ( CLOCK_MONOTONIC_RAW , & ts ) ;
long long int a = ( long long int ) ts . tv_nsec ;
long long int b = ( ( long long int ) ts . tv_sec ) * NANOSECONDS_IN_SECOND ;
return a + b ;
}
// Controller Look Axis
# define CONTROLLER_LOOK_EVENT_PERIOD 50000000ll // 1/20 Seconds
# define CONTROLLER_LOOK_AXIS_THRESHOLD 0.2f
# define CONTROLLER_LOOK_AXIS_SENSITIVITY 70
static void glfw_controller_look ( float x , float y ) {
// Current Time
long long int current_time = get_time ( ) ;
// Last Time
static long long int last_time = 0 ;
static int is_last_time_set = 0 ;
if ( ! is_last_time_set ) {
is_last_time_set = 1 ;
last_time = current_time ;
}
// Check If Period Has Passed
if ( ( current_time - last_time ) > CONTROLLER_LOOK_EVENT_PERIOD ) {
// Reset Last Time
last_time = current_time ;
// Verify
verify_controller_axis_value ( x , CONTROLLER_LOOK_AXIS_THRESHOLD ) ;
verify_controller_axis_value ( y , CONTROLLER_LOOK_AXIS_THRESHOLD ) ;
// Send Event
2024-05-12 01:14:14 +00:00
if ( is_interactable ) {
SDL_Event event ;
event . type = SDL_MOUSEMOTION ;
event . motion . x = last_mouse_x ;
event . motion . y = last_mouse_y ;
event . motion . xrel = x * CONTROLLER_LOOK_AXIS_SENSITIVITY ;
event . motion . yrel = y * CONTROLLER_LOOK_AXIS_SENSITIVITY ;
SDL_PushEvent ( & event ) ;
}
2022-10-01 05:37:20 +00:00
}
}
// Controller Place/Mine Triggers
# define CONTROLLER_TRIGGER_THRESHOLD 0
# define CONTROLLER_TRIGGER_COUNT 2
static void glfw_controller_trigger ( int trigger , int action ) {
glfw_click_raw ( trigger = = GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER ? GLFW_MOUSE_BUTTON_LEFT : GLFW_MOUSE_BUTTON_RIGHT , action ) ;
}
// Current Controller
static int current_controller = - 1 ;
// Track Controller State
static void update_controller_state ( ) {
// Store Button/Trigger State
static int controller_buttons [ GLFW_GAMEPAD_BUTTON_LAST + 1 ] ;
static int controller_triggers [ CONTROLLER_TRIGGER_COUNT ] ;
// Get State
GLFWgamepadstate state ;
int controller_enabled = cursor_grabbed & & is_interactable ;
int controller_valid = controller_enabled & & current_controller ! = - 1 & & glfwGetGamepadState ( current_controller , & state ) ;
if ( ! controller_valid ) {
// Invalid Controller
// Generate Blank State
for ( int i = GLFW_GAMEPAD_BUTTON_A ; i < = GLFW_GAMEPAD_BUTTON_LAST ; i + + ) {
state . buttons [ i ] = GLFW_RELEASE ;
}
for ( int i = GLFW_GAMEPAD_AXIS_LEFT_X ; i < = GLFW_GAMEPAD_AXIS_LAST ; i + + ) {
int is_trigger = i = = GLFW_GAMEPAD_AXIS_LEFT_TRIGGER | | i = = GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER ;
state . axes [ i ] = is_trigger ? - 1 : 0 ;
}
}
// Check Buttons
for ( int i = GLFW_GAMEPAD_BUTTON_A ; i < = GLFW_GAMEPAD_BUTTON_LAST ; i + + ) {
int old_state = controller_buttons [ i ] ;
controller_buttons [ i ] = state . buttons [ i ] ;
if ( old_state ! = controller_buttons [ i ] ) {
// State Changed
glfw_controller_button ( i , controller_buttons [ i ] ) ;
}
}
// Handle Movement & Look
glfw_controller_movement ( state . axes [ GLFW_GAMEPAD_AXIS_LEFT_X ] , state . axes [ GLFW_GAMEPAD_AXIS_LEFT_Y ] ) ;
glfw_controller_look ( state . axes [ GLFW_GAMEPAD_AXIS_RIGHT_X ] , state . axes [ GLFW_GAMEPAD_AXIS_RIGHT_Y ] ) ;
// Check Triggers
for ( int i = 0 ; i < CONTROLLER_TRIGGER_COUNT ; i + + ) {
int old_state = controller_triggers [ i ] ;
int trigger_id = i = = 0 ? GLFW_GAMEPAD_AXIS_LEFT_TRIGGER : GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER ;
controller_triggers [ i ] = state . axes [ trigger_id ] < CONTROLLER_TRIGGER_THRESHOLD ? GLFW_RELEASE : GLFW_PRESS ;
if ( old_state ! = controller_triggers [ i ] ) {
// State Changed
glfw_controller_trigger ( trigger_id , controller_triggers [ i ] ) ;
}
}
}
// Pick Controller
static int joysticks [ GLFW_JOYSTICK_LAST + 1 ] ;
static void pick_new_controller ( ) {
current_controller = - 1 ;
for ( int i = GLFW_JOYSTICK_1 ; i < = GLFW_JOYSTICK_LAST ; i + + ) {
if ( joysticks [ i ] = = 1 ) {
current_controller = i ;
DEBUG ( " Using Controller: %s (%s) " , glfwGetGamepadName ( i ) , glfwGetJoystickName ( i ) ) ;
break ;
}
}
}
static void find_controllers ( ) {
for ( int i = GLFW_JOYSTICK_1 ; i < = GLFW_JOYSTICK_LAST ; i + + ) {
joysticks [ i ] = glfwJoystickIsGamepad ( i ) ;
}
pick_new_controller ( ) ;
}
static void glfw_joystick ( int jid , int event ) {
if ( event = = GLFW_CONNECTED & & glfwJoystickIsGamepad ( jid ) ) {
joysticks [ jid ] = 1 ;
pick_new_controller ( ) ;
} else if ( event = = GLFW_DISCONNECTED ) {
joysticks [ jid ] = 0 ;
if ( jid = = current_controller ) {
DEBUG ( " Controller Disconnected " ) ;
pick_new_controller ( ) ;
}
}
}
// Release all keys/buttons when interaction is disabled and vice versa.
static void emit_events_after_is_interactable_change ( ) {
if ( is_running ) {
for ( int i = GLFW_KEY_SPACE ; i < = GLFW_KEY_LAST ; i + + ) {
int state = glfwGetKey ( glfw_window , i ) ;
if ( state = = GLFW_PRESS ) {
glfw_key_raw ( i , glfwGetKeyScancode ( i ) , is_interactable ? GLFW_PRESS : GLFW_RELEASE , 0 ) ;
}
}
for ( int i = GLFW_MOUSE_BUTTON_1 ; i < = GLFW_MOUSE_BUTTON_LAST ; i + + ) {
int state = glfwGetMouseButton ( glfw_window , i ) ;
if ( state = = GLFW_PRESS ) {
glfw_click_raw ( i , is_interactable ? GLFW_PRESS : GLFW_RELEASE ) ;
}
}
}
}
# endif
2021-09-12 03:18:12 +00:00
2024-03-19 04:25:50 +00:00
// Enable/Disable Raw Mouse Motion
2022-03-07 01:07:49 +00:00
void media_set_raw_mouse_motion_enabled ( int enabled ) {
raw_mouse_motion_enabled = enabled ;
# ifndef MCPI_HEADLESS_MODE
if ( is_running ) {
glfwSetInputMode ( glfw_window , GLFW_RAW_MOUSE_MOTION , GLFW_FALSE ) ;
}
2022-03-14 23:09:25 +00:00
# endif
2022-03-07 01:07:49 +00:00
if ( ! raw_mouse_motion_enabled ) {
2022-04-15 01:12:42 +00:00
WARN ( " Raw mouse motion has been DISABLED, this IS NOT recommended, and should only ever be used on systems that don't support or have broken raw mouse motion. " ) ;
2022-03-07 01:07:49 +00:00
}
}
2021-11-14 04:29:48 +00:00
// Disable V-Sync
static int disable_vsync = 0 ;
void media_disable_vsync ( ) {
disable_vsync = 1 ;
# ifndef MCPI_HEADLESS_MODE
if ( is_running ) {
glfwSwapInterval ( 0 ) ;
}
2022-03-14 23:09:25 +00:00
# endif
2021-11-14 04:29:48 +00:00
}
2022-07-16 02:08:12 +00:00
// Force EGL
static int force_egl = 0 ;
void media_force_egl ( ) {
if ( force_egl = = - 1 ) {
IMPOSSIBLE ( ) ;
}
force_egl = 1 ;
}
2021-09-12 03:18:12 +00:00
// Init Media Layer
2022-05-29 22:44:27 +00:00
# define GL_VERSION 0x1f02
typedef const unsigned char * ( * glGetString_t ) ( unsigned int name ) ;
2021-06-17 21:32:24 +00:00
void SDL_WM_SetCaption ( const char * title , __attribute__ ( ( unused ) ) const char * icon ) {
2021-09-12 03:18:12 +00:00
// Don't Enable GLFW In Headless Mode
2021-08-27 02:52:18 +00:00
# ifndef MCPI_HEADLESS_MODE
2021-09-12 03:18:12 +00:00
// Init GLFW
2021-06-17 21:32:24 +00:00
glfwSetErrorCallback ( glfw_error ) ;
if ( ! glfwInit ( ) ) {
2022-04-15 01:12:42 +00:00
ERR ( " Unable To Initialize GLFW " ) ;
2021-06-17 21:32:24 +00:00
}
2022-05-29 22:44:27 +00:00
// Create OpenGL ES Context
2021-06-17 21:32:24 +00:00
glfwWindowHint ( GLFW_CLIENT_API , GLFW_OPENGL_ES_API ) ;
2022-05-29 22:44:27 +00:00
# ifdef MCPI_USE_GLES1_COMPATIBILITY_LAYER
2023-09-08 01:38:14 +00:00
glfwWindowHint ( GLFW_CONTEXT_VERSION_MAJOR , 2 ) ;
2022-05-29 22:44:27 +00:00
glfwWindowHint ( GLFW_CONTEXT_VERSION_MINOR , 0 ) ;
# else
2021-06-17 21:32:24 +00:00
glfwWindowHint ( GLFW_CONTEXT_VERSION_MAJOR , 1 ) ;
glfwWindowHint ( GLFW_CONTEXT_VERSION_MINOR , 1 ) ;
2022-05-29 22:44:27 +00:00
# endif
2022-07-16 02:08:12 +00:00
// Use EGL
if ( force_egl ) {
glfwWindowHint ( GLFW_CONTEXT_CREATION_API , GLFW_EGL_CONTEXT_API ) ;
}
force_egl = - 1 ;
2021-06-17 21:32:24 +00:00
// Extra Settings
glfwWindowHint ( GLFW_AUTO_ICONIFY , GLFW_FALSE ) ;
glfwWindowHint ( GLFW_ALPHA_BITS , 0 ) ; // Fix Transparent Window On Wayland
2022-07-30 02:13:03 +00:00
// App ID
glfwWindowHintString ( GLFW_X11_CLASS_NAME , MCPI_APP_ID ) ;
glfwWindowHintString ( GLFW_WAYLAND_APP_ID , MCPI_APP_ID ) ;
2021-06-17 21:32:24 +00:00
2022-05-14 02:36:12 +00:00
// Create Window
2021-06-17 21:32:24 +00:00
glfw_window = glfwCreateWindow ( DEFAULT_WIDTH , DEFAULT_HEIGHT , title , NULL , NULL ) ;
if ( ! glfw_window ) {
2022-04-15 01:12:42 +00:00
ERR ( " Unable To Create GLFW Window " ) ;
2021-06-17 21:32:24 +00:00
}
2022-05-14 02:36:12 +00:00
// Event Handlers
2021-06-17 21:32:24 +00:00
glfwSetKeyCallback ( glfw_window , glfw_key ) ;
glfwSetCharCallback ( glfw_window , glfw_char ) ;
glfwSetCursorPosCallback ( glfw_window , glfw_motion ) ;
glfwSetMouseButtonCallback ( glfw_window , glfw_click ) ;
glfwSetScrollCallback ( glfw_window , glfw_scroll ) ;
2022-10-01 05:37:20 +00:00
// Setup Controller Support
find_controllers ( ) ;
glfwSetJoystickCallback ( glfw_joystick ) ;
2022-05-14 02:36:12 +00:00
// Make Window Context Current
2021-06-17 21:32:24 +00:00
glfwMakeContextCurrent ( glfw_window ) ;
2023-08-05 02:52:30 +00:00
// Setup Compatibility Layer
# ifdef MCPI_USE_GLES1_COMPATIBILITY_LAYER
extern void init_gles_compatibility_layer ( ) ;
init_gles_compatibility_layer ( ) ;
# endif
2021-09-12 03:18:12 +00:00
2022-05-29 22:44:27 +00:00
// Debug
glGetString_t glGetString = ( glGetString_t ) glfwGetProcAddress ( " glGetString " ) ;
DEBUG ( " Using %s " , ( * glGetString ) ( GL_VERSION ) ) ;
2021-09-12 03:18:12 +00:00
// Init OpenAL
_media_audio_init ( ) ;
2022-03-14 23:09:25 +00:00
# else
2021-06-17 21:32:24 +00:00
( void ) title ; // Mark As Used
2022-03-14 23:09:25 +00:00
# endif
2021-09-12 03:18:12 +00:00
// Set State
is_running = 1 ;
2021-11-14 04:29:48 +00:00
// Update State
update_cursor ( ) ;
if ( disable_vsync ) {
media_disable_vsync ( ) ;
}
2022-05-29 22:44:27 +00:00
// Always Cleanup Media Layer
atexit ( media_cleanup ) ;
2021-06-17 21:32:24 +00:00
}
void media_swap_buffers ( ) {
2021-08-27 02:52:18 +00:00
# ifndef MCPI_HEADLESS_MODE
2021-06-17 21:32:24 +00:00
// Don't Swap Buffers In A Context-Less Window
glfwSwapBuffers ( glfw_window ) ;
2022-03-14 23:09:25 +00:00
# endif
2021-06-17 21:32:24 +00:00
}
2021-09-12 03:18:12 +00:00
// Fullscreen Not Needed In Headless Mode
2021-08-27 02:52:18 +00:00
# ifndef MCPI_HEADLESS_MODE
2021-06-17 21:32:24 +00:00
static int is_fullscreen = 0 ;
// Old Size And Position To Use When Exiting Fullscreen
static int old_width = - 1 ;
static int old_height = - 1 ;
static int old_x = - 1 ;
static int old_y = - 1 ;
// Toggle Fullscreen
void media_toggle_fullscreen ( ) {
if ( is_fullscreen ) {
glfwSetWindowMonitor ( glfw_window , NULL , old_x , old_y , old_width , old_height , GLFW_DONT_CARE ) ;
old_width = - 1 ;
old_height = - 1 ;
old_x = - 1 ;
old_y = - 1 ;
} else {
glfwGetWindowSize ( glfw_window , & old_width , & old_height ) ;
glfwGetWindowPos ( glfw_window , & old_x , & old_y ) ;
GLFWmonitor * monitor = glfwGetPrimaryMonitor ( ) ;
const GLFWvidmode * mode = glfwGetVideoMode ( monitor ) ;
glfwSetWindowMonitor ( glfw_window , monitor , 0 , 0 , mode - > width , mode - > height , GLFW_DONT_CARE ) ;
}
is_fullscreen = ! is_fullscreen ;
}
2022-03-14 23:09:25 +00:00
# else
2021-06-17 21:32:24 +00:00
void media_toggle_fullscreen ( ) {
}
2022-03-14 23:09:25 +00:00
# endif
2021-06-17 21:32:24 +00:00
// Intercept SDL Events
void _media_handle_SDL_PollEvent ( ) {
2021-09-12 03:18:12 +00:00
// GLFW And Audio Are Disabled Disabled In Headless Mode
2021-08-27 02:52:18 +00:00
# ifndef MCPI_HEADLESS_MODE
2021-06-17 21:32:24 +00:00
// Process GLFW Events
glfwPollEvents ( ) ;
2022-10-01 05:37:20 +00:00
// Controller
update_controller_state ( ) ;
2021-06-17 21:32:24 +00:00
// Close Window
if ( glfwWindowShouldClose ( glfw_window ) ) {
SDL_Event event ;
event . type = SDL_QUIT ;
SDL_PushEvent ( & event ) ;
glfwSetWindowShouldClose ( glfw_window , GLFW_FALSE ) ;
}
2022-03-14 23:09:25 +00:00
# endif
2021-06-17 21:32:24 +00:00
}
2021-09-12 03:18:12 +00:00
// Cleanup Media Layer
2021-06-17 21:32:24 +00:00
void media_cleanup ( ) {
2021-09-12 03:18:12 +00:00
if ( is_running ) {
// GLFW And Audio Are Disabled In Headless Mode
2021-08-27 02:52:18 +00:00
# ifndef MCPI_HEADLESS_MODE
2021-09-12 20:38:41 +00:00
// Ignore GLFW Errors During Termination
glfwSetErrorCallback ( NULL ) ;
2021-09-12 03:18:12 +00:00
// Terminate GLFW
glfwDestroyWindow ( glfw_window ) ;
glfwTerminate ( ) ;
// Cleanup OpenAL
_media_audio_cleanup ( ) ;
2022-03-14 23:09:25 +00:00
# endif
2021-09-12 03:18:12 +00:00
// Update State
is_running = 0 ;
}
2021-06-17 21:32:24 +00:00
}
2021-06-19 23:07:09 +00:00
// Update GLFW Cursor State (Client Only)
2021-11-14 04:29:48 +00:00
static void update_cursor ( ) {
2021-08-27 02:52:18 +00:00
# ifndef MCPI_HEADLESS_MODE
2021-11-14 04:29:48 +00:00
if ( is_running ) {
// Get New State
int new_cursor_visible = is_interactable ? cursor_visible : 1 ;
int new_cursor_grabbed = is_interactable ? cursor_grabbed : 0 ;
// Store Old Mode
int old_mode = glfwGetInputMode ( glfw_window , GLFW_CURSOR ) ;
// Handle Cursor Visibility
int new_mode ;
if ( ! new_cursor_visible ) {
if ( new_cursor_grabbed ) {
new_mode = GLFW_CURSOR_DISABLED ;
} else {
new_mode = GLFW_CURSOR_HIDDEN ;
}
2021-06-19 23:07:09 +00:00
} else {
2021-11-14 04:29:48 +00:00
new_mode = GLFW_CURSOR_NORMAL ;
2021-06-19 23:07:09 +00:00
}
2021-11-14 04:29:48 +00:00
if ( new_mode ! = old_mode ) {
// Ignore Relative Cursor Motion When Locking
if ( new_mode = = GLFW_CURSOR_DISABLED & & old_mode ! = GLFW_CURSOR_DISABLED ) {
ignore_relative_motion = 1 ;
}
// Set New Mode
glfwSetInputMode ( glfw_window , GLFW_CURSOR , new_mode ) ;
// Handle Cursor Lock/Unlock
2022-10-01 05:37:20 +00:00
if ( ( new_mode = = GLFW_CURSOR_DISABLED & & old_mode ! = GLFW_CURSOR_DISABLED ) | | ( new_mode ! = GLFW_CURSOR_DISABLED & & old_mode = = GLFW_CURSOR_DISABLED ) ) {
2021-11-14 04:29:48 +00:00
// Use Raw Mouse Motion
2022-10-01 05:37:20 +00:00
if ( raw_mouse_motion_enabled ) {
glfwSetInputMode ( glfw_window , GLFW_RAW_MOUSE_MOTION , new_mode = = GLFW_CURSOR_DISABLED ? GLFW_TRUE : GLFW_FALSE ) ;
}
// Request Focus
if ( ! glfwGetWindowAttrib ( glfw_window , GLFW_FOCUSED ) ) {
glfwRequestWindowAttention ( glfw_window ) ;
}
2021-11-14 04:29:48 +00:00
}
// Reset Mouse Position When Unlocking
if ( new_mode ! = GLFW_CURSOR_DISABLED & & old_mode = = GLFW_CURSOR_DISABLED ) {
double cursor_x ;
double cursor_y ;
glfwGetCursorPos ( glfw_window , & cursor_x , & cursor_y ) ;
glfw_motion ( glfw_window , cursor_x , cursor_y ) ;
}
2021-06-19 23:07:09 +00:00
}
}
2022-03-14 23:09:25 +00:00
# endif
2021-06-19 23:07:09 +00:00
}
2021-06-17 21:32:24 +00:00
// Fix SDL Cursor Visibility/Grabbing
SDL_GrabMode SDL_WM_GrabInput ( SDL_GrabMode mode ) {
2021-06-19 23:07:09 +00:00
if ( mode = = SDL_GRAB_QUERY ) {
// Query
return cursor_grabbed ? SDL_GRAB_ON : SDL_GRAB_OFF ;
} else if ( mode = = SDL_GRAB_ON ) {
// Store State
cursor_grabbed = 1 ;
} else if ( mode = = SDL_GRAB_OFF ) {
// Store State
cursor_grabbed = 0 ;
2021-06-17 21:32:24 +00:00
}
2021-06-19 23:07:09 +00:00
// Update Cursor GLFW State (Client Only)
2021-11-14 04:29:48 +00:00
update_cursor ( ) ;
2021-06-19 23:07:09 +00:00
// Return
return mode ;
2021-06-17 21:32:24 +00:00
}
// Stub SDL Cursor Visibility
int SDL_ShowCursor ( int toggle ) {
2021-06-19 23:07:09 +00:00
if ( toggle = = SDL_QUERY ) {
// Query
return cursor_visible ? SDL_ENABLE : SDL_DISABLE ;
} else if ( toggle = = SDL_ENABLE ) {
// Store State
cursor_visible = 1 ;
} else if ( toggle = = SDL_DISABLE ) {
// Store State
cursor_visible = 0 ;
}
// Update Cursor GLFW State (Client Only)
2021-11-14 04:29:48 +00:00
update_cursor ( ) ;
2021-06-19 23:07:09 +00:00
// Return
return toggle ;
2021-06-17 21:32:24 +00:00
}
// Get Framebuffer Size
void media_get_framebuffer_size ( int * width , int * height ) {
2021-08-27 02:52:18 +00:00
# ifndef MCPI_HEADLESS_MODE
2021-06-17 21:32:24 +00:00
if ( glfw_window ! = NULL ) {
glfwGetFramebufferSize ( glfw_window , width , height ) ;
return ;
}
2022-03-14 23:09:25 +00:00
# endif
2021-06-17 21:32:24 +00:00
* width = DEFAULT_WIDTH ;
* height = DEFAULT_HEIGHT ;
}