Files
Server_Monitor/code/linux_platform.cpp
2023-09-26 19:40:16 +02:00

1093 lines
28 KiB
C++

#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;
file->handle = fopen(filename, "rb+");
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;
}