2023-09-26 19:40:16 +02:00
# include "platform.h"
# include <assert.h>
# include <string.h>
# include <stdlib.h>
# include <time.h>
# include "unistd.h"
# include "X11/Xlib.h"
# include "X11/extensions/XInput2.h"
# include "X11/keysymdef.h"
# include "GL/glx.h"
# include "signal.h"
# include "pulse/pulseaudio.h"
# include "semaphore.h"
# include "lib/queue.h"
# include "debug/logger.h"
# ifndef SWAP_INTERVAL
# define SWAP_INTERVAL -1
# endif
// Platform state
static Display * p_display = NULL ;
static Window p_root_window ;
static Window p_window ;
static GLXDrawable p_glx_drawable ;
static u32 p_width ;
static u32 p_height ;
static bool p_mouse_grabbed ;
static bool p_window_has_focus ;
static bool p_cursor_is_inside_window ;
static void p_mouse_grab_internal ( bool grab ) ;
static Cursor p_empty_cursor ;
static int p_xi2_opcode ;
static XIM p_xim ;
static XIC p_xic ;
static QUEUE_TYPE ( Event ) p_event_queue ;
static void linux_signal_handler ( s32 signal )
{
switch ( signal )
{
case SIGINT :
{
Event e ;
e . type = EVENT_QUIT ;
if ( Queue_Size ( p_event_queue ) > = Queue_Capacity ( p_event_queue ) )
LOG ( LOG_ERROR , " Event queue full. Dropping oldest event after receiving signal %d " , signal ) ;
Queue_Push ( p_event_queue , e ) ;
} break ;
default :
{
LOG ( LOG_WARNING , " Unmanaged linux signal received: %d " , signal ) ;
}
}
}
static pa_threaded_mainloop * p_audio_mainloop ;
static pa_mainloop_api * p_audio_mainloop_api ;
static pa_context * p_audio_context ;
static sem_t p_audio_context_ready ;
static pa_context_state_t p_audio_context_state ;
static pa_stream * p_audio_stream ;
static void * p_audio_data ;
static p_audio_callback p_audio_cb ;
static u32 _p_audio_sample_rate ;
static void pa_context_cb ( pa_context * c , void * userdata )
{
p_audio_context_state = pa_context_get_state ( c ) ;
s32 status = sem_post ( & p_audio_context_ready ) ;
assert ( status = = 0 ) ;
}
static pa_context_state_t wait_pa_context_state ( )
{
s32 status ;
status = sem_wait ( & p_audio_context_ready ) ;
assert ( status = = 0 ) ;
sem_destroy ( & p_audio_context_ready ) ;
return p_audio_context_state ;
}
static void p_audio_write_cb ( pa_stream * stream , long unsigned success , void * userdata )
{
s32 status ;
void * data ;
u64 n_bytes ;
status = pa_stream_begin_write ( stream , & data , & n_bytes ) ;
if ( status ! = 0 | | data = = NULL )
{
// Pulseadio allocation failed. Alloc our own memory
n_bytes = 16 * 1024 ;
if ( ! p_audio_data )
p_audio_data = p_alloc ( n_bytes ) ;
data = p_audio_data ;
}
p_audio_buffer buffer ;
buffer . samples = ( p_audio_sample * ) data ;
buffer . size = n_bytes / sizeof ( p_audio_sample ) ;
if ( p_audio_cb )
p_audio_cb ( & buffer ) ;
n_bytes = buffer . size * sizeof ( p_audio_sample ) ;
pa_stream_write ( stream , data , n_bytes , NULL , 0 , PA_SEEK_RELATIVE ) ;
}
// Generic platform initialization
void p_init ( bool capture_os_signals )
{
p_width = 1280 ;
p_height = 720 ;
p_mouse_grabbed = false ;
p_window_has_focus = false ;
p_cursor_is_inside_window = false ;
// Events
p_event_queue = Queue_Alloc ( p_alloc , Event , 32 ) ;
if ( capture_os_signals )
{
struct sigaction sa ;
memset ( & sa , 0 , sizeof ( struct sigaction ) ) ;
sa . sa_handler = linux_signal_handler ;
int status = sigaction ( SIGINT , & sa , NULL ) ;
assert ( status = = 0 ) ;
}
// Audio
{
_p_audio_sample_rate = 44100 ;
p_audio_data = NULL ;
p_audio_cb = NULL ;
s32 status ;
p_audio_mainloop = pa_threaded_mainloop_new ( ) ;
assert ( p_audio_mainloop ! = NULL ) ;
status = pa_threaded_mainloop_start ( p_audio_mainloop ) ;
assert ( status = = 0 ) ;
pa_threaded_mainloop_lock ( p_audio_mainloop ) ;
p_audio_mainloop_api = pa_threaded_mainloop_get_api ( p_audio_mainloop ) ;
assert ( p_audio_mainloop_api ! = NULL ) ;
p_audio_context = pa_context_new ( p_audio_mainloop_api , " Piuma " ) ;
assert ( p_audio_context ! = NULL ) ;
status = pa_context_connect ( p_audio_context , NULL , PA_CONTEXT_NOFLAGS , NULL ) ;
assert ( status = = 0 ) ;
status = sem_init ( & p_audio_context_ready , 0 , 0 ) ;
assert ( status = = 0 ) ;
pa_context_set_state_callback ( p_audio_context , pa_context_cb , NULL ) ;
pa_threaded_mainloop_unlock ( p_audio_mainloop ) ;
pa_context_state_t context_state = PA_CONTEXT_UNCONNECTED ;
while ( context_state = = PA_CONTEXT_UNCONNECTED | | context_state = = PA_CONTEXT_CONNECTING | | context_state = = PA_CONTEXT_AUTHORIZING | | context_state = = PA_CONTEXT_SETTING_NAME )
{
context_state = wait_pa_context_state ( ) ;
}
pa_threaded_mainloop_lock ( p_audio_mainloop ) ;
assert ( context_state = = PA_CONTEXT_READY ) ;
pa_sample_spec ss ;
ss . format = PA_SAMPLE_FLOAT32LE ;
ss . rate = _p_audio_sample_rate ;
ss . channels = 2 ;
p_audio_stream = pa_stream_new ( p_audio_context , " Piuma audio " , & ss , NULL ) ;
assert ( p_audio_stream ! = NULL ) ;
pa_stream_set_write_callback ( p_audio_stream , p_audio_write_cb , NULL ) ;
status = pa_stream_connect_playback ( p_audio_stream , NULL , NULL , PA_STREAM_NOFLAGS , NULL , NULL ) ;
pa_threaded_mainloop_unlock ( p_audio_mainloop ) ;
}
}
void p_deinit ( )
{
Queue_Free ( p_free , p_event_queue ) ;
// Audio
pa_stream_disconnect ( p_audio_stream ) ;
pa_context_disconnect ( p_audio_context ) ;
pa_threaded_mainloop_stop ( p_audio_mainloop ) ;
pa_threaded_mainloop_free ( p_audio_mainloop ) ;
if ( p_audio_data )
p_free ( p_audio_data ) ;
}
// Memory
void * p_alloc ( u64 size )
{
return malloc ( size ) ;
}
void * p_realloc ( void * ptr , u64 new_size )
{
return realloc ( ptr , new_size ) ;
}
void p_free ( void * ptr )
{
free ( ptr ) ;
}
// File IO
bool p_file_init ( p_file * file , const char * filename , b32 flags )
{
bool create_flag_set = flags & P_FILE_CREATE_IF_NOT_EXISTS ;
2024-09-24 16:45:04 +02:00
bool readonly = flags & P_FILE_READONLY ;
2023-09-26 19:40:16 +02:00
2024-09-24 16:45:04 +02:00
const char * mode = " rb+ " ;
if ( readonly )
mode = " rb " ;
file - > handle = fopen ( filename , mode ) ;
2023-09-26 19:40:16 +02:00
if ( create_flag_set & & file - > handle = = NULL ) // @Robustness: check errno
{
file - > handle = fopen ( filename , " wb+ " ) ;
}
if ( file - > handle = = NULL )
return false ;
return true ;
}
u64 p_file_size ( p_file * file )
{
assert ( file - > handle ! = NULL ) ;
int status ;
u64 saved_pos = ftell ( file - > handle ) ;
status = fseek ( file - > handle , 0 , SEEK_END ) ;
assert ( status = = 0 ) ;
u64 size = ftell ( file - > handle ) ;
// Restore saved state
status = fseek ( file - > handle , saved_pos , SEEK_SET ) ;
assert ( status = = 0 ) ;
return size ;
}
bool p_file_read ( p_file * file , Buffer * buf , u64 max_size )
{
assert ( file - > handle ! = NULL ) ;
int status ;
u64 read ;
status = fseek ( file - > handle , 0 , SEEK_SET ) ;
assert ( status = = 0 ) ;
read = fread ( buf - > data , 1 , max_size , file - > handle ) ;
buf - > size = read ;
if ( feof ( file - > handle ) | | read = = max_size )
{
// File completely read successfully
return true ;
}
return false ;
}
bool p_file_write ( p_file * file , u8 * data , u64 size )
{
assert ( file - > handle ! = NULL ) ;
int status ;
u64 written ;
status = fseek ( file - > handle , 0 , SEEK_SET ) ;
assert ( status = = 0 ) ;
written = fwrite ( data , 1 , size , file - > handle ) ;
if ( written = = size )
{
return true ;
}
return false ;
}
void p_file_deinit ( p_file * file )
{
assert ( file - > handle ! = NULL ) ;
int res = fclose ( file - > handle ) ;
assert ( res = = 0 ) ;
}
// Timers
f64 p_time ( ) // Returns seconds
{
f64 result ;
timespec ts ;
clock_gettime ( CLOCK_MONOTONIC_RAW , & ts ) ;
result = ( ( f64 ) ts . tv_sec ) + ( ( f64 ) ts . tv_nsec * 1e-9 ) ;
return result ;
}
void p_wait ( f64 milliseconds )
{
int status ;
u64 useconds = milliseconds * 1000 ;
status = usleep ( useconds ) ;
assert ( status = = 0 ) ;
}
// Windowing
/*
Related documentation available at :
- X11 : https : //x.org/releases/current/doc/libX11/libX11/libX11.html
- GLX : https : //khronos.org/registry/OpenGL/specs/gl/glx1.4.pdf
Before starting to code , I suggest you take a look to the X11 documentation first
and then take a look to at least the GLX intro .
When you need to use a function from the X11 docs , look in the GLX spec to see if
a replacement function is provided .
*/
static int p_fb_config_attributes [ ] =
{
// GLX_BUFFER_SIZE, 32, // RGBA 8-bit each
GLX_DOUBLEBUFFER , True ,
GLX_DEPTH_SIZE , 24 ,
GLX_DRAWABLE_TYPE , GLX_WINDOW_BIT | GLX_PIXMAP_BIT ,
GLX_X_RENDERABLE , True ,
GLX_X_VISUAL_TYPE , GLX_TRUE_COLOR ,
None
} ;
static int p_gl_attributes [ ] =
{
GLX_CONTEXT_MAJOR_VERSION_ARB , 4 ,
GLX_CONTEXT_MINOR_VERSION_ARB , 3 ,
GLX_CONTEXT_PROFILE_MASK_ARB , GLX_CONTEXT_CORE_PROFILE_BIT_ARB ,
None
} ;
static u32 p_x11_event_mask = ExposureMask | FocusChangeMask |
//ResizeRedirectMask |
KeyPressMask | KeyReleaseMask |
ButtonPressMask | ButtonReleaseMask |
PointerMotionMask ;
void p_window_open ( )
{
int p_default_screen ;
XVisualInfo * p_visual_info ;
Colormap p_color_map ;
GLXFBConfig p_fb_config ;
GLXWindow p_glx_window ;
GLXContext p_glx_context ;
/* Open a connection to X11 server*/
{
p_display = XOpenDisplay ( NULL ) ;
assert ( p_display ! = NULL ) ;
p_default_screen = XDefaultScreen ( p_display ) ;
p_root_window = XDefaultRootWindow ( p_display ) ;
}
/* Get a FBConfig with the right parameters */
{
int n_fb_config ;
GLXFBConfig * fb_config_list ;
fb_config_list = glXChooseFBConfig ( p_display , p_default_screen , p_fb_config_attributes , & n_fb_config ) ;
assert ( fb_config_list ! = NULL ) ;
p_fb_config = * fb_config_list ; // Get first matching FBConfig
XFree ( fb_config_list ) ;
}
/* Create a window */
{
// @Robustness: manage errors for CreateWindow and MapWindow
// Visual info struct
p_visual_info = glXGetVisualFromFBConfig ( p_display , p_fb_config ) ;
assert ( p_visual_info ! = NULL ) ;
p_color_map = XCreateColormap ( p_display , p_root_window , p_visual_info - > visual , AllocNone ) ;
// X11 Window struct
XSetWindowAttributes win_attr ;
win_attr . colormap = p_color_map ;
win_attr . event_mask = ExposureMask | FocusChangeMask |
//ResizeRedirectMask |
KeyPressMask | KeyReleaseMask |
ButtonPressMask | ButtonReleaseMask |
PointerMotionMask |
EnterWindowMask | LeaveWindowMask ;
p_window = XCreateWindow ( p_display , p_root_window , 10 , 10 , p_width , p_height , 0 , p_visual_info - > depth , InputOutput , p_visual_info - > visual , CWColormap | CWEventMask , & win_attr ) ;
// GLX Window struct
p_glx_window = glXCreateWindow ( p_display , p_fb_config , p_window , NULL ) ;
XMapWindow ( p_display , p_window ) ;
XFree ( p_visual_info ) ;
p_window_has_focus = true ;
}
// Create empty cursor for the times when mouse is grabbed
{
char data [ 1 ] = { 0 } ;
Pixmap pixmap ;
XColor color ;
pixmap = XCreateBitmapFromData ( p_display , p_window , data , 1 , 1 ) ;
color . red = color . green = color . blue = 0 ;
p_empty_cursor = XCreatePixmapCursor ( p_display , pixmap , pixmap , & color , & color , 0 , 0 ) ;
XFreePixmap ( p_display , pixmap ) ;
}
// Initialize XInput2
{
// http://who-t.blogspot.com/2009/05/xi2-recipes-part-1.html
int event , error ;
int major , minor ;
if ( ! XQueryExtension ( p_display , " XInputExtension " , & p_xi2_opcode , & event , & error ) )
{
LOG ( LOG_WARNING , " X Input extension not available. " ) ;
}
major = 2 ;
minor = 2 ;
if ( XIQueryVersion ( p_display , & major , & minor ) = = BadRequest )
{
LOG ( LOG_WARNING , " XInput2 not available. Server supports %d.%d " , major , minor ) ;
}
}
// XIM (X Input Method)
{
p_xim = XOpenIM ( p_display , NULL , NULL , NULL ) ;
if ( p_xim = = NULL )
LOG ( LOG_ERROR , " Cannot open XIM (input method) " ) ;
p_xic = XCreateIC ( p_xim , XNInputStyle , ( XIMPreeditNothing | XIMStatusNothing ) , XNClientWindow , p_window , NULL ) ;
if ( p_xic = = NULL )
LOG ( LOG_ERROR , " Cannot create XIC (input context) " ) ;
XSetICFocus ( p_xic ) ;
//void XUnsetICFocus(XIC ic);
//char *XmbResetIC(XIC ic);
}
/* GLX extensions */
/* OpenGL Context and Drawable*/
{
// @Robustness: Check GLX extension string to see if this function is available.
p_glx_context = glXCreateContextAttribsARB ( p_display , p_fb_config , NULL , GL_TRUE , p_gl_attributes ) ;
glXMakeCurrent ( p_display , p_window , p_glx_context ) ;
p_glx_drawable = glXGetCurrentDrawable ( ) ;
assert ( p_glx_drawable ! = None ) ;
}
/* VSync */
{
// @Robustness: Check GLX extension string to see if this function is available.
glXSwapIntervalEXT ( p_display , p_glx_drawable , SWAP_INTERVAL ) ;
}
XFlush ( p_display ) ;
}
void p_window_name ( char * name )
{
XStoreName ( p_display , p_window , name ) ;
XFlush ( p_display ) ;
}
void p_window_resize ( u32 width , u32 height , bool fullscreen )
{
// @Feature: fullscreen support
XResizeWindow ( p_display , p_window , width , height ) ;
XFlush ( p_display ) ; // Does not resize without flush
}
// You shouldn't call this for each frame. Cache the data or just look at system events.
void p_window_dimensions ( u32 * width , u32 * height )
{
Window root ;
s32 x , y ;
u32 border_w , border_h ;
// Other parameters cannot be null
XGetGeometry ( p_display , p_glx_drawable , & root , & x , & y , & p_width , & p_height , & border_w , & border_h ) ;
* width = p_width ;
* height = p_height ;
}
void p_window_close ( )
{
XDestroyIC ( p_xic ) ;
XCloseIM ( p_xim ) ;
XCloseDisplay ( p_display ) ;
p_display = NULL ;
}
// Graphics
void p_graphics_swap_interval ( s32 frames )
{
// -1 for Adaptive Sync, 0 for no interval, >0 to wait 'frames' frames between each swap
glXSwapIntervalEXT ( p_display , p_glx_drawable , frames ) ;
}
void p_graphics_swap ( )
{
glXSwapBuffers ( p_display , p_window ) ;
}
# define CASE_STRING(str) case str: return #str;
static const char * x11_event_type_str ( s32 type )
{
switch ( type )
{
CASE_STRING ( MotionNotify )
CASE_STRING ( ButtonPress )
CASE_STRING ( ButtonRelease )
CASE_STRING ( ColormapNotify )
CASE_STRING ( EnterNotify )
CASE_STRING ( LeaveNotify )
CASE_STRING ( Expose )
CASE_STRING ( GraphicsExpose )
CASE_STRING ( NoExpose )
CASE_STRING ( FocusIn )
CASE_STRING ( FocusOut )
CASE_STRING ( KeymapNotify )
CASE_STRING ( KeyPress )
CASE_STRING ( KeyRelease )
CASE_STRING ( PropertyNotify )
CASE_STRING ( ResizeRequest )
CASE_STRING ( CirculateNotify )
CASE_STRING ( ConfigureNotify )
CASE_STRING ( DestroyNotify )
CASE_STRING ( GravityNotify )
CASE_STRING ( MapNotify )
CASE_STRING ( ReparentNotify )
CASE_STRING ( UnmapNotify )
CASE_STRING ( CirculateRequest )
CASE_STRING ( ConfigureRequest )
CASE_STRING ( MapRequest )
CASE_STRING ( ClientMessage )
CASE_STRING ( MappingNotify )
CASE_STRING ( SelectionClear )
CASE_STRING ( SelectionNotify )
CASE_STRING ( SelectionRequest )
CASE_STRING ( VisibilityNotify )
default : return " Unknown X11 type " ;
}
}
static Key_Code map_x11_button ( u32 button )
{
switch ( button )
{
case 1 : return KEY_MOUSE_LEFT ;
case 2 : return KEY_MOUSE_MIDDLE ;
case 3 : return KEY_MOUSE_RIGHT ;
case 4 : return KEY_MOUSE_WHEEL_UP ;
case 5 : return KEY_MOUSE_WHEEL_DOWN ;
case 8 : return KEY_MOUSE_4 ;
case 9 : return KEY_MOUSE_5 ;
}
return KEY_UNKNOWN ;
}
static Key_Code map_x11_keycode ( u32 keycode )
{
//LOG(LOG_DEBUG, "Keycode %u", keycode);
// @Performance: a lot of this codes are sequential. Check with other OSs if that's the case. If it is, we can have faster mapping by subtracting and adding an offset.
KeySym keysym = XKeycodeToKeysym ( p_display , keycode , 0 ) ;
switch ( keysym )
{
case XK_Return : return KEY_ENTER ;
case XK_ISO_Enter : return KEY_ENTER ;
case XK_Escape : return KEY_ESCAPE ;
case XK_BackSpace : return KEY_BACKSPACE ;
case XK_Tab : return KEY_TAB ;
case XK_space : return KEY_SPACE ;
case XK_exclam : return KEY_EXCLAMATION ;
case XK_quotedbl : return KEY_DOUBLE_QUOTE ;
case XK_numbersign : return KEY_HASH ;
case XK_percent : return KEY_PERCENT ;
case XK_dollar : return KEY_DOLLAR ;
case XK_ampersand : return KEY_AMPERSAND ;
case XK_apostrophe : return KEY_SINGLE_QUOTE ;
case XK_parenleft : return KEY_LEFT_PARENTHESIS ;
case XK_parenright : return KEY_RIGHT_PARENTHESIS ;
case XK_asterisk : return KEY_ASTERISK ;
case XK_plus : return KEY_PLUS ;
case XK_comma : return KEY_COMMA ;
case XK_minus : return KEY_MINUS ;
case XK_period : return KEY_PERIOD ;
case XK_slash : return KEY_SLASH ;
case XK_0 : return KEY_0 ;
case XK_1 : return KEY_1 ;
case XK_2 : return KEY_2 ;
case XK_3 : return KEY_3 ;
case XK_4 : return KEY_4 ;
case XK_5 : return KEY_5 ;
case XK_6 : return KEY_6 ;
case XK_7 : return KEY_7 ;
case XK_8 : return KEY_8 ;
case XK_9 : return KEY_9 ;
case XK_colon : return KEY_COLON ;
case XK_semicolon : return KEY_SEMICOLON ;
case XK_less : return KEY_LESS ;
case XK_equal : return KEY_EQUALS ;
case XK_greater : return KEY_GREATER ;
case XK_question : return KEY_QUESTION ;
case XK_at : return KEY_AT ;
case XK_bracketleft : return KEY_LEFT_BRACKET ;
case XK_bracketright : return KEY_RIGHT_BRACKET ;
case XK_backslash : return KEY_BACKSLASH ;
case XK_asciicircum : return KEY_CARET ;
case XK_underscore : return KEY_UNDERSCORE ;
case XK_grave : return KEY_BACKQUOTE ;
case XK_a : return KEY_A ;
case XK_b : return KEY_B ;
case XK_c : return KEY_C ;
case XK_d : return KEY_D ;
case XK_e : return KEY_E ;
case XK_f : return KEY_F ;
case XK_g : return KEY_G ;
case XK_h : return KEY_H ;
case XK_i : return KEY_I ;
case XK_j : return KEY_J ;
case XK_k : return KEY_K ;
case XK_l : return KEY_L ;
case XK_m : return KEY_M ;
case XK_n : return KEY_N ;
case XK_o : return KEY_O ;
case XK_p : return KEY_P ;
case XK_q : return KEY_Q ;
case XK_r : return KEY_R ;
case XK_s : return KEY_S ;
case XK_t : return KEY_T ;
case XK_u : return KEY_U ;
case XK_v : return KEY_V ;
case XK_w : return KEY_W ;
case XK_x : return KEY_X ;
case XK_y : return KEY_Y ;
case XK_z : return KEY_Z ;
case XK_Caps_Lock : return KEY_CAPSLOCK ;
case XK_F1 : return KEY_F1 ;
case XK_F2 : return KEY_F2 ;
case XK_F3 : return KEY_F3 ;
case XK_F4 : return KEY_F4 ;
case XK_F5 : return KEY_F5 ;
case XK_F6 : return KEY_F6 ;
case XK_F7 : return KEY_F7 ;
case XK_F8 : return KEY_F8 ;
case XK_F9 : return KEY_F9 ;
case XK_F10 : return KEY_F10 ;
case XK_F11 : return KEY_F11 ;
case XK_F12 : return KEY_F12 ;
case XK_Print : return KEY_PRINTSCREEN ;
case XK_Scroll_Lock : return KEY_SCROLLLOCK ;
case XK_Pause : return KEY_PAUSE ;
case XK_Insert : return KEY_INSERT ;
case XK_Delete : return KEY_DELETE ;
case XK_Home : return KEY_HOME ;
case XK_End : return KEY_END ;
case XK_Page_Up : return KEY_PAGEUP ;
case XK_Page_Down : return KEY_PAGEDOWN ;
case XK_Up : return KEY_ARROW_UP ;
case XK_Down : return KEY_ARROW_DOWN ;
case XK_Left : return KEY_ARROW_LEFT ;
case XK_Right : return KEY_ARROW_RIGHT ;
case XK_Num_Lock : return KEY_NUMLOCK ;
case XK_KP_Divide : return KEY_PAD_DIVIDE ;
case XK_KP_Multiply : return KEY_PAD_MULTIPLY ;
case XK_KP_Subtract : return KEY_PAD_MINUS ;
case XK_KP_Add : return KEY_PAD_PLUS ;
case XK_KP_Enter : return KEY_PAD_ENTER ;
case XK_KP_1 : return KEY_PAD_1 ;
case XK_KP_2 : return KEY_PAD_2 ;
case XK_KP_3 : return KEY_PAD_3 ;
case XK_KP_4 : return KEY_PAD_4 ;
case XK_KP_5 : return KEY_PAD_5 ;
case XK_KP_6 : return KEY_PAD_6 ;
case XK_KP_7 : return KEY_PAD_7 ;
case XK_KP_8 : return KEY_PAD_8 ;
case XK_KP_9 : return KEY_PAD_9 ;
case XK_KP_0 : return KEY_PAD_0 ;
case XK_KP_Separator : return KEY_PAD_PERIOD ;
case XK_KP_End : return KEY_PAD_1 ;
case XK_KP_Down : return KEY_PAD_2 ;
case XK_KP_Page_Down : return KEY_PAD_3 ;
case XK_KP_Left : return KEY_PAD_4 ;
case XK_KP_Begin : return KEY_PAD_5 ;
case XK_KP_Right : return KEY_PAD_6 ;
case XK_KP_Home : return KEY_PAD_7 ;
case XK_KP_Up : return KEY_PAD_8 ;
case XK_KP_Page_Up : return KEY_PAD_9 ;
case XK_KP_Insert : return KEY_PAD_0 ;
case XK_KP_Delete : return KEY_PAD_PERIOD ;
case XK_Control_L : return KEY_LEFT_CTRL ;
case XK_Control_R : return KEY_RIGHT_CTRL ;
case XK_Shift_L : return KEY_LEFT_SHIFT ;
case XK_Shift_R : return KEY_RIGHT_SHIFT ;
case XK_Alt_L : return KEY_LEFT_ALT ;
case XK_Alt_R : return KEY_RIGHT_ALT ;
default : {
LOG ( LOG_WARNING , " Keycode not mapped: %u 0x%X - sym %u 0x%X " , keycode , keycode , keysym , keysym ) ;
return KEY_UNKNOWN ;
}
}
}
static void p_events_process ( )
{
while ( Queue_Size ( p_event_queue ) < Queue_Capacity ( p_event_queue ) & & XEventsQueued ( p_display , QueuedAfterReading ) )
{
XEvent e ;
XNextEvent ( p_display , & e ) ;
//if(e.type != 6) LOG(LOG_DEBUG, "X11 event: %d - %s", e.type, x11_event_type_str(e.type));
// @Feature: xinput2, gamepads
switch ( e . type )
{
case MotionNotify :
{
XMotionEvent m = e . xmotion ;
Event event ;
event . type = EVENT_MOUSE_MOVE ;
event . mouse_move . relative = false ;
event . mouse_move . position . x = m . x ;
event . mouse_move . position . y = m . y ;
Queue_Push ( p_event_queue , event ) ;
// Do things
bool is_grab_position = ( m . x = = p_width / 2 ) & & ( m . y = = p_height / 2 ) ;
if ( p_mouse_grabbed & & p_window_has_focus & & ! is_grab_position )
XWarpPointer ( p_display , None , p_window , 0 , 0 , 0 , 0 , p_width / 2 , p_height / 2 ) ;
} break ;
case GenericEvent :
{
bool is_xi2_cookie = ( e . xcookie . extension = = p_xi2_opcode ) ;
if ( is_xi2_cookie )
{
if ( XGetEventData ( p_display , & e . xcookie ) )
{
static int a = 0 ;
switch ( e . xcookie . evtype )
{
case XI_RawMotion :
{
if ( p_mouse_grabbed & & p_window_has_focus )
{
XIRawEvent * re = ( XIRawEvent * ) e . xcookie . data ;
if ( ( 1 < re - > valuators . mask_len * 8 ) & & XIMaskIsSet ( re - > valuators . mask , 0 ) & & XIMaskIsSet ( re - > valuators . mask , 1 ) )
{
Event event ;
event . type = EVENT_MOUSE_MOVE ;
event . mouse_move . relative = true ;
event . mouse_move . position . x = re - > raw_values [ 0 ] ;
event . mouse_move . position . y = re - > raw_values [ 1 ] ;
Queue_Push ( p_event_queue , event ) ;
}
}
} break ;
case XI_RawButtonPress :
{
} break ;
case XI_RawButtonRelease :
{
} break ;
}
}
XFreeEventData ( p_display , & e . xcookie ) ;
}
} break ;
case ButtonPress :
{
XButtonEvent b = e . xbutton ;
Event event ;
event . type = EVENT_KEY ;
event . key . pressed = true ;
event . key . key_code = map_x11_button ( b . button ) ;
Queue_Push ( p_event_queue , event ) ;
} break ;
case ButtonRelease :
{
XButtonEvent b = e . xbutton ;
Event event ;
event . type = EVENT_KEY ;
event . key . pressed = false ;
event . key . key_code = map_x11_button ( b . button ) ;
Queue_Push ( p_event_queue , event ) ;
} break ;
case KeyPress :
{
XKeyEvent k = e . xkey ;
Event event ;
event . type = EVENT_KEY ;
event . key . pressed = true ;
event . key . key_code = map_x11_keycode ( k . keycode ) ;
Queue_Push ( p_event_queue , event ) ;
if ( ! XFilterEvent ( & e , p_window ) )
{
char text [ 64 ] ;
KeySym keysym ;
static Status status = 0 ;
int text_length = Xutf8LookupString ( p_xic , & k , text , 64 , & keysym , & status ) ;
//LOG(LOG_DEBUG, "X11 UTF8 lookup (length %d): %.*s, (%u)", text_length, text_length, text, text[0]);
if ( text_length > 0 )
{
Event event ;
event . type = EVENT_TEXT ;
memcpy ( event . text . data , text , text_length ) ;
event . text . data [ text_length ] = ' \0 ' ;
if ( event . text . data [ 0 ] = = ' \r ' )
event . text . data [ 0 ] = ' \n ' ;
Queue_Push ( p_event_queue , event ) ;
}
}
} break ;
case KeyRelease :
{
XKeyEvent k = e . xkey ;
// Check for repeated keypress
if ( XEventsQueued ( p_display , QueuedAfterReading ) )
{
XEvent next_e ;
XPeekEvent ( p_display , & next_e ) ;
if ( next_e . type = = KeyPress & & next_e . xkey . time = = k . time & & next_e . xkey . keycode = = k . keycode )
{
// Repeated keypress. Key wasn't actually released.
// It's the thing that the OS does when keep a key pressed and it prints multiple characters.
// Repeated keypresses are sent as a KeyRelease, immediatly followed by a KeyPress with the same time.
// So we eat both this KeyRelease and the next KeyPress
XNextEvent ( p_display , & e ) ;
// We still use this event for text input
if ( ! XFilterEvent ( & e , p_window ) )
{
XKeyEvent k = e . xkey ;
char text [ 64 ] ;
KeySym keysym ;
static Status status = 0 ;
int text_length = Xutf8LookupString ( p_xic , & k , text , 64 , & keysym , & status ) ;
//LOG(LOG_DEBUG, "X11 UTF8 lookup (length %d): %.*s", text_length, text_length, text);
if ( text_length > 0 )
{
Event event ;
event . type = EVENT_TEXT ;
memcpy ( event . text . data , text , text_length ) ;
event . text . data [ text_length ] = ' \0 ' ;
if ( event . text . data [ 0 ] = = ' \r ' )
event . text . data [ 0 ] = ' \n ' ;
Queue_Push ( p_event_queue , event ) ;
}
}
break ;
}
}
Event event ;
event . type = EVENT_KEY ;
event . key . pressed = false ;
event . key . key_code = map_x11_keycode ( k . keycode ) ;
Queue_Push ( p_event_queue , event ) ;
} break ;
case FocusIn :
{
//XFocusChangeEvent fc = e.xfocus;
Event event ;
event . type = EVENT_FOCUS ;
Queue_Push ( p_event_queue , event ) ;
// Grab pointer if it was grabbed before (before leaving the window, now we are coming back to it
p_window_has_focus = true ;
if ( p_mouse_grabbed & & p_cursor_is_inside_window )
p_mouse_grab_internal ( p_mouse_grabbed ) ;
} break ;
case FocusOut :
{
//XFocusChangeEvent fc = e.xfocus;
Event event ;
event . type = EVENT_UNFOCUS ;
Queue_Push ( p_event_queue , event ) ;
// Ungrab pointer when leaving the window (minimize, alt+tab, ...)
p_window_has_focus = false ;
if ( p_mouse_grabbed )
p_mouse_grab_internal ( false ) ;
} break ;
case EnterNotify :
{
// Add to event queue -> no
// Grab pointer if needed
p_cursor_is_inside_window = true ;
if ( p_mouse_grabbed & & p_window_has_focus )
p_mouse_grab_internal ( p_mouse_grabbed ) ;
} break ;
case LeaveNotify :
{
// Add to event queue -> no
// Ungrab pointer if needed
p_cursor_is_inside_window = false ;
if ( p_mouse_grabbed & & p_window_has_focus )
p_mouse_grab_internal ( false ) ;
} break ;
case Expose :
{
XExposeEvent ex = e . xexpose ;
Event event ;
event . type = EVENT_RESIZE ;
u32 new_width = ex . x + ex . width ;
u32 new_height = ex . y + ex . height ;
event . resize . width = new_width ;
event . resize . height = new_height ;
p_width = new_width ;
p_height = new_height ;
//LOG(LOG_DEBUG, "Expose event - x: %d y: %d width: %d height: %d count: %d", ex.x, ex.y, ex.width, ex.height, ex.count);
Queue_Push ( p_event_queue , event ) ;
} break ;
default :
LOG ( LOG_DEBUG , " Unrecognized X11 event: %d - %s " , e . type , x11_event_type_str ( e . type ) ) ;
// Discard unrecognized event
}
}
}
// Input
static void p_mouse_grab_internal ( bool grab )
{
if ( grab )
{
// Set event mask
XIEventMask xi2_event_mask ;
unsigned char mask [ 4 ] = { 0 , 0 , 0 , 0 } ;
xi2_event_mask . deviceid = XIAllMasterDevices ;
xi2_event_mask . mask_len = sizeof ( mask ) ;
xi2_event_mask . mask = mask ;
XISetMask ( mask , XI_RawMotion ) ;
XISetMask ( mask , XI_RawButtonPress ) ;
XISetMask ( mask , XI_RawButtonRelease ) ;
XISelectEvents ( p_display , p_root_window , & xi2_event_mask , 1 ) ;
// Cursor grabbing and hiding
# if 1 // In XInput 2.0 we will not get raw events if we grab the pointer. In XInput 2.2 we get raw events even when we grab the pointer, as expected.
unsigned event_mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask | EnterWindowMask | LeaveWindowMask ;
XGrabPointer ( p_display , p_window , False , event_mask , GrabModeAsync , GrabModeAsync , None , None , CurrentTime ) ;
# endif
XDefineCursor ( p_display , p_window , p_empty_cursor ) ;
XWarpPointer ( p_display , None , p_window , 0 , 0 , 0 , 0 , p_width / 2 , p_height / 2 ) ;
}
else
{
// Clear event mask
XIEventMask event_mask ;
unsigned char mask [ 1 ] = { 0 } ;
event_mask . deviceid = XIAllMasterDevices ;
event_mask . mask_len = 0 ;
event_mask . mask = mask ;
XISelectEvents ( p_display , p_root_window , & event_mask , 1 ) ;
// Undo Cursor grabbing and hiding
XUngrabPointer ( p_display , CurrentTime ) ;
XDefineCursor ( p_display , p_window , None ) ;
}
}
void p_mouse_grab ( bool grab )
{
if ( grab ! = p_mouse_grabbed )
{
p_mouse_grabbed = grab ;
p_mouse_grab_internal ( grab ) ;
}
}
bool p_next_event ( Event * e )
{
// If not in cache, process events until you find one
if ( Queue_Size ( p_event_queue ) = = 0 )
{
p_events_process ( ) ;
}
// If an event was found, return it
if ( Queue_Size ( p_event_queue ) > 0 )
{
* e = Queue_Pop ( p_event_queue ) ;
return true ;
}
return false ;
}
// Audio
void p_audio_register_data_callback ( p_audio_callback cb )
{
p_audio_cb = cb ;
}
u32 p_audio_sample_rate ( )
{
return _p_audio_sample_rate ;
}