1096 lines
28 KiB
C++
1096 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;
|
|
bool readonly = flags & P_FILE_READONLY;
|
|
|
|
const char *mode = "rb+";
|
|
if(readonly)
|
|
mode = "rb";
|
|
file->handle = fopen(filename, mode);
|
|
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;
|
|
}
|