2024-06-15 08:52:15 -04:00
# include <ctime>
# include <unistd.h>
# include <SDL/SDL.h>
# include <libreborn/libreborn.h>
# define GLFW_INCLUDE_NONE
# include <GLFW/glfw3.h>
# include <media-layer/core.h>
# include <media-layer/internal.h>
# include "audio/engine.h"
// Allow Disabling Interaction
static void update_cursor ( ) ;
static int is_interactable = 1 ;
2024-08-21 18:19:07 -04:00
void media_set_interactable ( const int toggle ) {
2024-06-15 08:52:15 -04:00
if ( bool ( toggle ) ! = is_interactable ) {
is_interactable = toggle ;
update_cursor ( ) ;
}
}
// Store Cursor State
static bool cursor_grabbed = false ;
static bool cursor_visible = true ;
// Track If Raw Mouse Motion Is Enabled
static bool raw_mouse_motion_enabled = true ;
// Window
static GLFWwindow * glfw_window = nullptr ;
// Handle GLFW Error
static void glfw_error ( __attribute__ ( ( unused ) ) int error , const char * description ) {
WARN ( " GLFW Error: %s " , description ) ;
}
// Convert GLFW Key To SDL Key
static SDLKey glfw_key_to_sdl_key ( const 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 ;
// Drop Item
case GLFW_KEY_Q :
return SDLK_q ;
// Toolbar
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 ;
case GLFW_KEY_9 :
return SDLK_9 ;
case GLFW_KEY_0 :
return SDLK_0 ;
// 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 ;
case GLFW_KEY_DELETE :
return SDLK_DELETE ;
// Fullscreen
case GLFW_KEY_F11 :
return SDLK_F11 ;
// Screenshot
case GLFW_KEY_F2 :
return SDLK_F2 ;
2024-07-05 02:33:29 -04:00
// Debug
case GLFW_KEY_F3 :
return SDLK_F3 ;
2024-06-15 08:52:15 -04:00
// 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 ;
// Unknown
default :
return SDLK_UNKNOWN ;
}
}
// Convert GLFW Key Modifier To SDL Key Modifier
static SDLMod glfw_modifier_to_sdl_modifier ( const int mods ) {
int 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 SDLMod ( ret ) ;
}
// Pass Key Presses To SDL
static void glfw_key_raw ( int key , int scancode , int action , int mods ) {
SDL_Event event1 ;
bool up = action = = GLFW_RELEASE ;
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 ) ;
}
static void glfw_key ( __attribute__ ( ( unused ) ) GLFWwindow * window , const int key , const int scancode , const int action , const int mods ) {
if ( is_interactable ) {
glfw_key_raw ( key , scancode , action , mods ) ;
}
}
// Pass Text To Minecraft
static void character_event ( char c ) {
if ( ! is_interactable ) {
return ;
}
// SDL_UserEvent Is Never Used In MCPI, So It Is Repurposed For Character Events
SDL_Event event ;
event . type = SDL_USEREVENT ;
event . user . code = USER_EVENT_CHARACTER ;
event . user . data1 = ( int ) c ;
SDL_PushEvent ( & event ) ;
}
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
}
}
static void glfw_char ( __attribute__ ( ( unused ) ) GLFWwindow * window , const unsigned int codepoint ) {
// Convert
size_t str_size = 4 /* Maximum UTF-8 character size */ + 1 /* NULL-terminator */ ;
char str [ str_size ] = { } ;
codepoint_to_utf8 ( ( unsigned char * ) str , codepoint ) ;
char * cp437_str = to_cp437 ( str ) ;
// Send Event
for ( int i = 0 ; cp437_str [ i ] ! = ' \0 ' ; i + + ) {
character_event ( cp437_str [ i ] ) ;
}
// Free
free ( cp437_str ) ;
}
// Last Mouse Location
static double last_mouse_x = 0 ;
static double last_mouse_y = 0 ;
// Ignore Relative Cursor Motion
static bool ignore_relative_motion = false ;
// Convert Screen Coordinates To Pixels
static void convert_to_pixels ( GLFWwindow * window , double * xpos , double * ypos ) {
// Skip If Cursor Is Grabbed
if ( cursor_grabbed & & raw_mouse_motion_enabled ) {
return ;
}
// 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
const double width_ratio = ( ( double ) framebuffer_width ) / ( ( double ) window_width ) ;
const double height_ratio = ( ( double ) framebuffer_height ) / ( ( double ) window_height ) ;
// Multiply
* xpos * = width_ratio ;
* ypos * = height_ratio ;
}
// Pass Mouse Movement To SDL
static void glfw_motion ( __attribute__ ( ( unused ) ) GLFWwindow * window , double xpos , double ypos ) {
convert_to_pixels ( window , & xpos , & ypos ) ;
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 = false ;
last_mouse_x = xpos ;
last_mouse_y = ypos ;
}
// Create And Push SDL Mouse Click Event
static void click_event ( int button , bool up ) {
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 ) ;
}
// Pass Mouse Click To SDL
static void glfw_click_raw ( const int button , const int action ) {
const bool up = action = = GLFW_RELEASE ;
const 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 ) ;
}
static void glfw_click ( __attribute__ ( ( unused ) ) GLFWwindow * window , const int button , const int action , __attribute__ ( ( unused ) ) int mods ) {
if ( is_interactable ) {
glfw_click_raw ( button , action ) ;
}
}
// Pass Mouse Scroll To SDL
static void glfw_scroll ( __attribute__ ( ( unused ) ) GLFWwindow * window , __attribute__ ( ( unused ) ) double xoffset , double yoffset ) {
if ( is_interactable & & yoffset ! = 0 ) {
2024-09-22 19:28:51 -04:00
const int sdl_button = yoffset > 0 ? SDL_BUTTON_WHEELUP : SDL_BUTTON_WHEELDOWN ;
2024-06-15 08:52:15 -04:00
click_event ( sdl_button , false ) ;
click_event ( sdl_button , true ) ;
}
}
// Enable/Disable Raw Mouse Motion
void media_set_raw_mouse_motion_enabled ( const int enabled ) {
raw_mouse_motion_enabled = enabled ;
if ( glfw_window ) {
glfwSetInputMode ( glfw_window , GLFW_RAW_MOUSE_MOTION , GLFW_FALSE ) ;
}
if ( ! raw_mouse_motion_enabled ) {
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. " ) ;
}
}
// Disable V-Sync
static int disable_vsync = 0 ;
void media_disable_vsync ( ) {
disable_vsync = 1 ;
if ( glfw_window ) {
glfwSwapInterval ( 0 ) ;
}
}
// Force EGL
static int force_egl = 0 ;
void media_force_egl ( ) {
if ( force_egl = = - 1 ) {
IMPOSSIBLE ( ) ;
}
force_egl = 1 ;
}
// Init Media Layer
# define GL_VERSION 0x1f02
typedef const char * ( * glGetString_t ) ( unsigned int name ) ;
void SDL_WM_SetCaption ( const char * title , __attribute__ ( ( unused ) ) const char * icon ) {
// Disable In Headless Mode
if ( reborn_is_headless ( ) ) {
return ;
}
// Init GLFW
reborn_check_display ( ) ;
glfwSetErrorCallback ( glfw_error ) ;
if ( ! glfwInit ( ) ) {
ERR ( " Unable To Initialize GLFW " ) ;
}
2024-10-05 23:52:44 -04:00
// Create OpenGL Context
2024-06-15 08:52:15 -04:00
glfwWindowHint ( GLFW_CONTEXT_VERSION_MAJOR , 1 ) ;
2024-10-05 23:52:44 -04:00
glfwWindowHint ( GLFW_CONTEXT_VERSION_MINOR , 5 ) ;
2024-06-15 08:52:15 -04:00
// Use EGL
if ( force_egl ) {
glfwWindowHint ( GLFW_CONTEXT_CREATION_API , GLFW_EGL_CONTEXT_API ) ;
}
force_egl = - 1 ;
// Extra Settings
glfwWindowHint ( GLFW_AUTO_ICONIFY , GLFW_FALSE ) ;
glfwWindowHint ( GLFW_ALPHA_BITS , 0 ) ; // Fix Transparent Window On Wayland
// App ID
glfwWindowHintString ( GLFW_X11_CLASS_NAME , MCPI_APP_ID ) ;
glfwWindowHintString ( GLFW_WAYLAND_APP_ID , MCPI_APP_ID ) ;
// Create Window
2024-09-22 19:28:51 -04:00
glfw_window = glfwCreateWindow ( DEFAULT_WIDTH , DEFAULT_HEIGHT , title , nullptr , nullptr ) ;
2024-06-15 08:52:15 -04:00
if ( ! glfw_window ) {
ERR ( " Unable To Create GLFW Window " ) ;
}
// Event Handlers
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 ) ;
// Make Window Context Current
glfwMakeContextCurrent ( glfw_window ) ;
// Debug
2024-08-22 23:43:32 -04:00
const glGetString_t glGetString = ( glGetString_t ) glfwGetProcAddress ( " glGetString " ) ;
2024-10-05 23:52:44 -04:00
DEBUG ( " Using OpenGL %s " , ( * glGetString ) ( GL_VERSION ) ) ;
2024-06-15 08:52:15 -04:00
// Init OpenAL
_media_audio_init ( ) ;
// Update State
update_cursor ( ) ;
if ( disable_vsync ) {
media_disable_vsync ( ) ;
}
// Always Cleanup Media Layer
atexit ( media_cleanup ) ;
}
void media_swap_buffers ( ) {
if ( glfw_window ) {
glfwSwapBuffers ( glfw_window ) ;
}
}
// Track Fullscreen
static bool is_fullscreen = false ;
// 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 ( glfw_window ) {
if ( is_fullscreen ) {
glfwSetWindowMonitor ( glfw_window , nullptr , 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 ;
}
}
// Intercept SDL Events
void _media_handle_SDL_PollEvent ( ) {
if ( glfw_window ) {
// Process GLFW Events
glfwPollEvents ( ) ;
// Close Window
if ( glfwWindowShouldClose ( glfw_window ) ) {
SDL_Event event ;
event . type = SDL_QUIT ;
SDL_PushEvent ( & event ) ;
glfwSetWindowShouldClose ( glfw_window , GLFW_FALSE ) ;
}
}
}
// Cleanup Media Layer
void media_cleanup ( ) {
if ( glfw_window ) {
// Ignore GLFW Errors During Termination
glfwSetErrorCallback ( nullptr ) ;
// Terminate GLFW
glfwDestroyWindow ( glfw_window ) ;
glfwTerminate ( ) ;
// Cleanup OpenAL
_media_audio_cleanup ( ) ;
2024-07-05 02:33:29 -04:00
// Mark As Stopped
glfw_window = nullptr ;
2024-06-15 08:52:15 -04:00
}
}
// Update GLFW Cursor State (Client Only)
static void update_cursor ( ) {
if ( glfw_window ) {
// Get New State
const bool new_cursor_visible = is_interactable ? cursor_visible : true ;
const bool new_cursor_grabbed = is_interactable ? cursor_grabbed : false ;
// Store Old Mode
const 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 ;
}
} else {
new_mode = GLFW_CURSOR_NORMAL ;
}
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 = true ;
}
// Set New Mode
glfwSetInputMode ( glfw_window , GLFW_CURSOR , new_mode ) ;
// Handle Cursor Lock/Unlock
if ( ( new_mode = = GLFW_CURSOR_DISABLED & & old_mode ! = GLFW_CURSOR_DISABLED ) | | ( new_mode ! = GLFW_CURSOR_DISABLED & & old_mode = = GLFW_CURSOR_DISABLED ) ) {
// Use Raw Mouse Motion
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 ) ;
}
}
// 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 ) ;
}
}
}
}
// Fix SDL Cursor Visibility/Grabbing
SDL_GrabMode SDL_WM_GrabInput ( const SDL_GrabMode mode ) {
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 = true ;
} else if ( mode = = SDL_GRAB_OFF ) {
// Store State
cursor_grabbed = false ;
}
// Update Cursor GLFW State (Client Only)
update_cursor ( ) ;
// Return
return mode ;
}
// Stub SDL Cursor Visibility
int SDL_ShowCursor ( const int toggle ) {
if ( toggle = = SDL_QUERY ) {
// Query
return cursor_visible ? SDL_ENABLE : SDL_DISABLE ;
} else if ( toggle = = SDL_ENABLE ) {
// Store State
cursor_visible = true ;
} else if ( toggle = = SDL_DISABLE ) {
// Store State
cursor_visible = false ;
}
// Update Cursor GLFW State (Client Only)
update_cursor ( ) ;
// Return
return toggle ;
}
// Get Framebuffer Size
void media_get_framebuffer_size ( int * width , int * height ) {
if ( glfw_window ) {
glfwGetFramebufferSize ( glfw_window , width , height ) ;
} else {
* width = DEFAULT_WIDTH ;
* height = DEFAULT_HEIGHT ;
}
2024-07-10 23:15:58 -04:00
}
// Check OpenGL Extension
int media_has_extension ( const char * name ) {
if ( glfw_window ) {
return glfwExtensionSupported ( name ) ;
} else {
return 0 ;
}
2024-07-05 02:33:29 -04:00
}