#include "platform.h" #include #include #include #include #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; }