#include static const char* X11ConvertKeyPhysicalIntoXkbName(key Key) { switch (Key) { case Key_Escape: return "ESC"; case Key_Grave: return "TLDE"; case Key_1: return "AE01"; case Key_2: return "AE02"; case Key_3: return "AE03"; case Key_4: return "AE04"; case Key_5: return "AE05"; case Key_6: return "AE06"; case Key_7: return "AE07"; case Key_8: return "AE08"; case Key_9: return "AE09"; case Key_0: return "AE10"; case Key_Minus: return "AE11"; case Key_Equal: return "AE12"; case Key_Backspace: return "BKSP"; case Key_Tab: return "TAB"; case Key_Q: return "AD01"; case Key_W: return "AD02"; case Key_E: return "AD03"; case Key_R: return "AD04"; case Key_T: return "AD05"; case Key_Y: return "AD06"; case Key_U: return "AD07"; case Key_I: return "AD08"; case Key_O: return "AD09"; case Key_P: return "AD10"; case Key_LeftBracket: return "AD11"; case Key_RightBracket: return "AD12"; case Key_Enter: return "RTRN"; case Key_CapsLock: return "CAPS"; case Key_A: return "AC01"; case Key_S: return "AC02"; case Key_D: return "AC03"; case Key_F: return "AC04"; case Key_G: return "AC05"; case Key_H: return "AC06"; case Key_J: return "AC07"; case Key_K: return "AC08"; case Key_L: return "AC09"; case Key_Semicolon: return "AC10"; case Key_Apostrophe: return "AC11"; case Key_LeftShift: return "LFSH"; case Key_Z: return "AB01"; case Key_X: return "AB02"; case Key_C: return "AB03"; case Key_V: return "AB04"; case Key_B: return "AB05"; case Key_N: return "AB06"; case Key_M: return "AB07"; case Key_Comma: return "AB08"; case Key_Period: return "AB09"; case Key_Slash: return "AB10"; case Key_RightShift: return "RTSH"; case Key_LeftCtrl: return "LCTL"; case Key_LeftAlt: return "LALT"; case Key_Space: return "SPCE"; case Key_RightAlt: return "RALT"; case Key_RightCtrl: return "RCTL"; case Key_ArrowLeft: return "LEFT"; case Key_ArrowRight: return "RGHT"; case Key_ArrowUp: return "UP"; case Key_ArrowDown: return "DOWN"; case Key_Home: return "HOME"; case Key_End: return "END"; case Key_PageUp: return "PGUP"; case Key_PageDown: return "PGDN"; case Key_Insert: return "INS"; case Key_Delete: return "DELE"; case Key_F1: return "FK01"; case Key_F2: return "FK02"; case Key_F3: return "FK03"; case Key_F4: return "FK04"; case Key_F5: return "FK05"; case Key_F6: return "FK06"; case Key_F7: return "FK07"; case Key_F8: return "FK08"; case Key_F9: return "FK09"; case Key_F10: return "FK10"; case Key_F11: return "FK11"; case Key_F12: return "FK12"; } ASSERT(false); return {}; } #define NYLA_X11_ATOMS(X) \ X(compound_text, COMPOUND_TEXT) \ X(wm_delete_window, WM_DELETE_WINDOW) \ X(wm_protocols, WM_PROTOCOLS) \ X(wm_name, WM_NAME) \ X(wm_state, WM_STATE) \ X(wm_take_focus, WM_TAKE_FOCUS) \ X(net_wm_name, _NET_WM_NAME) \ X(net_wm_pid, _NET_WM_PID) \ X(net_supported, _NET_SUPPORTED) \ X(net_supporting_wm_check, _NET_SUPPORTING_WM_CHECK) \ X(net_wm_moveresize, _NET_WM_MOVERESIZE) \ X(utf8_string, UTF8_STRING) \ X(clipboard, CLIPBOARD) \ X(targets, TARGETS) struct x11_atoms { #define X(varname, atomname) xcb_atom_t varname; NYLA_X11_ATOMS(X) #undef X }; struct x11 { xcb_connection_t* Conn; xcb_screen_t* Screen; uint32_t extensionXInput2MajorOpCode; uint32_t extensionXRandRFirstEvent; int ScreenIndex; xcb_window_t Window; xcb_get_geometry_reply_t WindowGeometry; x11_atoms Atoms; xcb_keycode_t keyCodes[KEY_COUNT]; xcb_errors_context_t* errorsContext; xkb_context* xkbCtx; xkb_keymap* xkbKeymap; xkb_state* xkbState; }; static x11* X11; static void WinOpen() { uint32_t eventMask = XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY; bool overrideRedirect = false; X11->Window = xcb_generate_id(X11->Conn); uint32_t values[2] { overrideRedirect, eventMask }; xcb_create_window(X11->Conn, XCB_COPY_FROM_PARENT, X11->Window, X11->Screen->root, 0, 0, 1200, 800, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, X11->Screen->root_visual, XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK, values); xcb_get_geometry_reply_t* windowGeometry = xcb_get_geometry_reply(X11->Conn, xcb_get_geometry(X11->Conn, X11->Window), nullptr); X11->WindowGeometry = *windowGeometry; free(windowGeometry); xcb_map_window(X11->Conn, X11->Window); xcb_flush(X11->Conn); } static void WinGetSize(uint32_t* width, uint32_t* height) { *width = X11->WindowGeometry.width; *height = X11->WindowGeometry.height; } static bool ProcessXEvent(xcb_generic_event_t* event, window_event* outEvent) { uint8_t eventType = event->response_type & 0x7F; switch (eventType) { case XCB_KEY_PRESS: { xcb_key_press_event_t* keypress = (xcb_key_press_event_t*)event; outEvent->type = WindowEvent_KeyDown; for (uint32_t i = 1; i < KEY_COUNT; ++i) { if (X11->keyCodes[i] == keypress->detail) { outEvent->Key = (key)i; return true; } } return false; } case XCB_KEY_RELEASE: { xcb_key_release_event_t* keyrelease = (xcb_key_release_event_t*)event; outEvent->type = WindowEvent_KeyUp; for (uint32_t i = 1; i < KEY_COUNT; ++i) { if (X11->keyCodes[i] == keyrelease->detail) { outEvent->Key = (key)i; return true; } } return false; } case XCB_CONFIGURE_NOTIFY: { xcb_configure_notify_event_t* configurenotify = (xcb_configure_notify_event_t*)event; xcb_get_geometry_reply_t* newGeom = xcb_get_geometry_reply(X11->Conn, xcb_get_geometry(X11->Conn, X11->Window), nullptr); bool windowResized = newGeom->width != X11->WindowGeometry.width || newGeom->height != X11->WindowGeometry.height; X11->WindowGeometry = *newGeom; free(newGeom); if (windowResized) { outEvent->type = WindowEvent_Resize; return true; } return false; } case XCB_EXPOSE: { outEvent->type = WindowEvent_Repaint; return true; } case XCB_CLIENT_MESSAGE: { xcb_client_message_event_t* clientmessage = (xcb_client_message_event_t*)event; if (clientmessage->format == 32 && clientmessage->type == X11->Atoms.wm_protocols && clientmessage->data.data32[0] == X11->Atoms.wm_delete_window ) { outEvent->type = WindowEvent_Quit; return true; } return false; } default: return false; } } static bool WinPollEvent(window_event* outEvent) { for (;;) { if (xcb_connection_has_error(X11->Conn)) { outEvent->type = WindowEvent_Quit; return true; } xcb_generic_event_t* event = xcb_poll_for_event(X11->Conn); if (!event) return false; bool done = ProcessXEvent(event, outEvent); free(event); if (done) return true; } } static void Init0() { ASSERT(sysconf(_SC_PAGESIZE) == MEMPAGE_SIZE); } static xcb_atom_t X11InternAtom(byteview name, bool onlyIfExists) { xcb_intern_atom_reply_t* reply = xcb_intern_atom_reply(X11->Conn, xcb_intern_atom(X11->Conn, onlyIfExists, name.size, (char*)name.data), nullptr); ASSERT(reply); xcb_atom_t ret = reply->atom; free(reply); return ret; } static void Init1() { X11 = PUSH_ALLOC(&PermanentAlloc, x11, 1); { X11->Conn = xcb_connect(nullptr, &X11->ScreenIndex); ASSERT(!xcb_connection_has_error(X11->Conn), "could not connect to X server"); ASSERT(X11->Screen = xcb_aux_get_screen(X11->Conn, X11->ScreenIndex)); #define X(varname, atomname) X11->Atoms.varname = X11InternAtom(#atomname##_s, false); NYLA_X11_ATOMS(X) #undef X } { xcb_errors_context_new(X11->Conn, &X11->errorsContext); } { uint16_t majorXkbVersionOut, minorXkbVersionOut; uint8_t baseEventOut, baseErrorOut; ASSERT(xkb_x11_setup_xkb_extension(X11->Conn, XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION, XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS, &majorXkbVersionOut, &minorXkbVersionOut, &baseEventOut, &baseErrorOut), "could not set up xkb extension"); xcb_generic_error_t* err = nullptr; xcb_xkb_per_client_flags_reply_t* reply = xcb_xkb_per_client_flags_reply(X11->Conn, xcb_xkb_per_client_flags(X11->Conn, XCB_XKB_ID_USE_CORE_KBD, XCB_XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT, XCB_XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT, 0, 0, 0), &err); if (!reply || err) ASSERT(false && "could not set up detectable autorepeat"); } { X11->xkbCtx = xkb_context_new(XKB_CONTEXT_NO_FLAGS); ASSERT(X11->xkbCtx); int32_t deviceId = xkb_x11_get_core_keyboard_device_id(X11->Conn); ASSERT(deviceId != -1); X11->xkbKeymap = xkb_x11_keymap_new_from_device(X11->xkbCtx, X11->Conn, deviceId, XKB_KEYMAP_COMPILE_NO_FLAGS); ASSERT(X11->xkbKeymap); X11->xkbState = xkb_x11_state_new_from_device(X11->xkbKeymap, X11->Conn, deviceId); ASSERT(X11->xkbState); for (uint32_t i = 1; i < KEY_COUNT; ++i) { key Key = (key)i; const char* xkbName = X11ConvertKeyPhysicalIntoXkbName(Key); xkb_keycode_t keycode = xkb_keymap_key_by_name(X11->xkbKeymap, xkbName); ASSERT(xkb_keycode_is_legal_x11(keycode)); X11->keyCodes[i] = keycode; } } { const xcb_query_extension_reply_t* ext = xcb_get_extension_data(X11->Conn, &xcb_input_id); ASSERT(ext && ext->present, "could not set up XI2 extension"); struct { xcb_input_event_mask_t eventMask; uint32_t maskBits; } mask { }; mask.eventMask.deviceid = XCB_INPUT_DEVICE_ALL_MASTER; mask.eventMask.mask_len = 1; mask.maskBits = XCB_INPUT_XI_EVENT_MASK_RAW_MOTION | XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_PRESS; xcb_generic_error_t* error = xcb_request_check(X11->Conn, xcb_input_xi_select_events_checked(X11->Conn, X11->Screen->root, 1, &mask.eventMask)); ASSERT(!error, "could not setup XI2 extension, error: %s", xcb_errors_get_name_for_error(X11->errorsContext, error->error_code, nullptr)); X11->extensionXInput2MajorOpCode = ext->major_opcode; } } static void TearDown() { } static void X11SendClientMessage32(xcb_window_t window, xcb_atom_t type, xcb_atom_t arg1, uint32_t arg2, uint32_t arg3, uint32_t arg4) { const xcb_client_message_event_t event = { .response_type = XCB_CLIENT_MESSAGE, .format = 32, .window = window, .type = type, .data = { .data32 = { arg1, arg2, arg3, arg4 } }, }; xcb_send_event(X11->Conn, false, window, XCB_EVENT_MASK_NO_EVENT, (const char*)&event); } static void X11SendWmTakeFocus(xcb_window_t window, uint32_t time) { X11SendClientMessage32(window, X11->Atoms.wm_protocols, X11->Atoms.wm_take_focus, time, 0, 0); } static void X11SendWmDeleteWindow(xcb_window_t window) { X11SendClientMessage32(window, X11->Atoms.wm_protocols, X11->Atoms.wm_delete_window, XCB_CURRENT_TIME, 0, 0); } static void X11SendConfigureNotify(xcb_window_t window, xcb_window_t parent, int16_t x, int16_t y, uint16_t width, uint16_t height, uint16_t borderWidth) { (void)parent; const xcb_configure_notify_event_t event = { .response_type = XCB_CONFIGURE_NOTIFY, .event = window, .window = window, .x = x, .y = y, .width = width, .height = height, .border_width = borderWidth, }; xcb_send_event(X11->Conn, false, window, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (const char*)&event); } static uint32_t X11KeyPhysicalToKeyCode(key Key) { return X11->keyCodes[(uint32_t)Key]; } static bool X11KeyCodeToKeyPhysical(uint32_t keyCode, key* outKey) { for (uint32_t i = 1; i < KEY_COUNT; ++i) { if (X11->keyCodes[i] == keyCode) { *outKey = (key)i; return true; } } return false; } static void X11RandRInit() { const xcb_query_extension_reply_t* ext = xcb_get_extension_data(X11->Conn, &xcb_randr_id); if (!ext || !ext->present) { X11->extensionXRandRFirstEvent = 0; return; } X11->extensionXRandRFirstEvent = ext->first_event; xcb_randr_select_input(X11->Conn, X11->Screen->root, XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE | XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE | XCB_RANDR_NOTIFY_MASK_CRTC_CHANGE); } static uint32_t X11RandRGetEventOffset() { return X11->extensionXRandRFirstEvent; } static void X11RefreshKeyboardMapping() { const int32_t deviceId = xkb_x11_get_core_keyboard_device_id(X11->Conn); if (deviceId == -1) return; xkb_keymap_unref(X11->xkbKeymap); X11->xkbKeymap = xkb_x11_keymap_new_from_device(X11->xkbCtx, X11->Conn, deviceId, XKB_KEYMAP_COMPILE_NO_FLAGS); if (!X11->xkbKeymap) return; xkb_state_unref(X11->xkbState); X11->xkbState = xkb_x11_state_new_from_device(X11->xkbKeymap, X11->Conn, deviceId); for (uint32_t i = 1; i < KEY_COUNT; ++i) { const key k = (key)i; const char* xkbName = X11ConvertKeyPhysicalIntoXkbName(k); const xkb_keycode_t keycode = xkb_keymap_key_by_name(X11->xkbKeymap, xkbName); if (xkb_keycode_is_legal_x11(keycode)) X11->keyCodes[i] = keycode; } }