revolver

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

xcb_impl.c (42632B)


      1 //////////////////////////////////////////////////////////////////
      2 // xcb_impl.c
      3 //
      4 // on linux, requires
      5 //    -lxcb -lxcb-xkb
      6 // and for opengl:
      7 //    -lEGL -lGL
      8 
      9 #include <xcb/xcb.h>
     10 #include <xcb/xkb.h>
     11 
     12 #include "../../external/xkb-keysym-utf.c"
     13 
     14 #define RV_XKB_DEBUG 0
     15 
     16 
     17 //////////////////////////////////////////////////////////////////
     18 // Window
     19 
     20 struct rv_window_handle_t {
     21     rv_window_handle_t* next;
     22     xcb_window_t window_id;
     23     rv_vec2 last_size;
     24     xcb_intern_atom_reply_t* close_reply;
     25     void* render;
     26     bool32 sent_initial_events;
     27 
     28     bool32 vsync;
     29 };
     30 
     31 //////////////////////////////////////////////////////////////////
     32 // Context
     33 
     34 #define KEYSYM_LOOKUP_SIZE 6
     35 
     36 RV_INTERNAL struct {
     37     bool32 is_init;
     38     xcb_connection_t* conn;
     39 
     40     rv_window_handle_t* window_list;
     41 
     42     const xcb_query_extension_reply_t *xkb_extension_reply;
     43 
     44     struct {
     45         xcb_keysym_t keysyms[KEYSYM_LOOKUP_SIZE];
     46         s32 num_groups;
     47         u8 kt_index[4];
     48     } keycode_converter_list[256];
     49 
     50     u8 mods;
     51     u8 group;
     52 
     53     struct {
     54         u32 mask;
     55         struct {
     56             bool32 active;
     57             u32 mask;
     58             s32 level;
     59         } entries[64];
     60         s32 entries_size;
     61     } type_map[64];
     62     s32 type_map_size;
     63 } xcb_ctx;
     64 
     65 #define xcb_maybe_window(w) ((w) ? (w) : (xcb_ctx.window_list))
     66 
     67 
     68 //////////////////////////////////////////////////////////////////
     69 // EGL
     70 
     71 #if RV_RENDER_OPENGL
     72 
     73 #include <EGL/egl.h>
     74 #include <EGL/eglext.h>
     75 
     76 RV_INTERNAL struct {
     77     bool32 is_init;
     78     EGLContext* egl_context;
     79     EGLDisplay egl_display;
     80     EGLConfig config;
     81 } egl_context;
     82 
     83 
     84 RV_GLOBAL void* egl_attach(xcb_window_t window, xcb_connection_t* connection, bool32 vsync)
     85 {
     86     { // make egl context
     87         if (!egl_context.is_init) {
     88             egl_context.is_init = true;
     89 
     90             { // egl_display
     91                 PFNEGLGETPLATFORMDISPLAYEXTPROC GetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT");
     92                 // initialize EGL
     93                 {
     94                     egl_context.egl_display = GetPlatformDisplayEXT(EGL_PLATFORM_XCB_EXT, connection,
     95                                                                     (const EGLint[]){
     96                                                                         EGL_PLATFORM_XCB_SCREEN_EXT,
     97                                                                         0, // This is a screenp that you can get from 2nd argument of xcb_connect
     98                                                                         EGL_NONE,
     99                                                                     });
    100                     rv_assert(egl_context.egl_display != EGL_NO_DISPLAY);
    101 
    102                     EGLint major, minor;
    103                     if (!eglInitialize(egl_context.egl_display, &major, &minor))
    104                     {
    105                         rv_abort_msg(1, "Cannot initialize EGL display");
    106                     }
    107                     if (major < 1 || (major == 1 && minor < 5))
    108                     {
    109                         rv_abort_msg(1, "EGL version 1.5 or higher required");
    110                     }
    111                 }
    112             }
    113 
    114             // choose OpenGL API for EGL, by default it uses OpenGL ES
    115             EGLBoolean ok = eglBindAPI(EGL_OPENGL_API);
    116             rv_assert(ok);
    117 
    118             { // egl_context
    119                 // choose EGL configuration
    120                 {
    121                     EGLint attr[] =
    122                         {
    123                             EGL_SURFACE_TYPE,      EGL_WINDOW_BIT,
    124                             EGL_CONFORMANT,        EGL_OPENGL_BIT,
    125                             EGL_RENDERABLE_TYPE,   EGL_OPENGL_BIT,
    126                             EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER,
    127 
    128                             EGL_RED_SIZE,      8,
    129                             EGL_GREEN_SIZE,    8,
    130                             EGL_BLUE_SIZE,     8,
    131                             EGL_DEPTH_SIZE,   24,
    132                             EGL_STENCIL_SIZE,  8,
    133 
    134                             // uncomment for multisampled framebuffer
    135                             //EGL_SAMPLE_BUFFERS, 1,
    136                             //EGL_SAMPLES,        4, // 4x MSAA
    137 
    138                             EGL_NONE,
    139                         };
    140 
    141                     EGLint count;
    142                     if (!eglChooseConfig(egl_context.egl_display, attr, &egl_context.config, 1, &count) || count != 1)
    143                     {
    144                         rv_abort_msg(1, "Cannot choose EGL config");
    145                     }
    146                 }
    147 
    148                 {
    149                     EGLint attr[] =
    150                         {
    151                             EGL_CONTEXT_MAJOR_VERSION, 3,
    152                             EGL_CONTEXT_MINOR_VERSION, 3,
    153                             EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
    154                             // ask for debug context for non "Release" builds
    155                             // this is so we can enable debug callback
    156                             EGL_CONTEXT_OPENGL_DEBUG, EGL_TRUE,
    157                             EGL_NONE,
    158                         };
    159 
    160                     egl_context.egl_context = eglCreateContext(egl_context.egl_display, egl_context.config, EGL_NO_CONTEXT, attr);
    161                     if (egl_context.egl_context == EGL_NO_CONTEXT)
    162                     {
    163                         rv_abort_msg(1, "Cannot create EGL context, OpenGL 4.5 not supported?");
    164                     }
    165                 }
    166             }
    167         }
    168     }
    169 
    170 
    171     EGLSurface* surface;
    172     { // surface
    173         EGLint attr[] = {
    174             EGL_GL_COLORSPACE, EGL_GL_COLORSPACE_LINEAR, // or use EGL_GL_COLORSPACE_SRGB for sRGB framebuffer
    175             EGL_RENDER_BUFFER, EGL_BACK_BUFFER,
    176             EGL_NONE,
    177         };
    178 
    179         surface = eglCreateWindowSurface(egl_context.egl_display, egl_context.config, window, attr);
    180         if (surface == EGL_NO_SURFACE) {
    181             rv_abort_msg(1, "Cannot create EGL surface");
    182         }
    183     }
    184 
    185     eglMakeCurrent(egl_context.egl_display, surface, surface, egl_context.egl_context);
    186 
    187     if (vsync)
    188         eglSwapInterval(egl_context.egl_display, 1);
    189     else
    190         eglSwapInterval(egl_context.egl_display, 0);
    191 
    192     { // load opengl functions
    193         RV_LOCAL_PERSIST bool32 glad_loaded = false;
    194         if (!glad_loaded) {
    195             glad_loaded = true;
    196             gladLoadGLLoader((GLADloadproc)eglGetProcAddress);
    197         }
    198     }
    199 
    200     return surface;
    201 }
    202 
    203 RV_GLOBAL void rv_window_render_begin(rv_window_handle_t* window)
    204 {
    205     rv_window_handle_t* w = xcb_maybe_window(window);
    206     if (w) {
    207         EGLSurface* surface = w->render;
    208         eglMakeCurrent(egl_context.egl_display, surface, surface, egl_context.egl_context);
    209     }
    210 }
    211 
    212 RV_GLOBAL void  rv_window_render_end(rv_window_handle_t* window)
    213 {
    214     rv_window_handle_t* w = xcb_maybe_window(window);
    215     if (w) {
    216         EGLSurface* surface = w->render;
    217         eglSwapBuffers(egl_context.egl_display, surface);
    218     }
    219     xcb_flush(xcb_ctx.conn);
    220 }
    221 
    222 #else // RV_RENDER_OPENGL
    223 
    224 RV_GLOBAL void rv_window_render_begin(rv_window_handle_t* window)
    225 {
    226 
    227 }
    228 
    229 RV_GLOBAL void  rv_window_render_end(rv_window_handle_t* window)
    230 {
    231 
    232 }
    233 
    234 #endif // !RV_RENDER_OPENGL
    235 
    236 
    237 //////////////////////////////////////////////////////////////////
    238 // Context Creation
    239 
    240 u32 mods_from_real_virtual(u32 rmod, u32 vmod_low, u32 vmod_high) {
    241     return (rmod) | (vmod_low << 8) | (vmod_high << 16);
    242 }
    243 
    244 #define XKB_COMPONENTS_MASK                     \
    245     (XCB_XKB_MAP_PART_KEY_TYPES |               \
    246      XCB_XKB_MAP_PART_KEY_SYMS  )
    247 
    248 RV_INTERNAL void make_xcb_context(void) {
    249     if (xcb_ctx.is_init) return;
    250     xcb_ctx.is_init = true;
    251 
    252     xcb_ctx.conn = xcb_connect(NULL, NULL);
    253 
    254     { // xkb
    255 
    256         // NOTE(Samdal):
    257         // we only set up the keyboard map once
    258         // this does not support changing the keyboard layout during runtime...
    259         // do we care?
    260 
    261         // NOTE(Samdal):
    262         // Sources
    263         // https://gist.github.com/bluetech/6038239
    264         // https://www.cl.cam.ac.uk/~mgk25/ucs/keysymdef.h
    265         // https://www.x.org/docs/XKB/XKBlib.pdf
    266         // https://tronche.com/gui/x/xlib/
    267 
    268         { // set up xkb extension
    269             {
    270                 const xcb_query_extension_reply_t *reply;
    271 
    272                 xcb_ctx.xkb_extension_reply = xcb_get_extension_data(xcb_ctx.conn, &xcb_xkb_id);
    273                 if (!xcb_ctx.xkb_extension_reply)
    274                     rv_abort_msg(1, "no XKB in X server");
    275             }
    276 
    277             {
    278                 xcb_xkb_use_extension_cookie_t cookie;
    279                 xcb_xkb_use_extension_reply_t *reply = NULL;
    280 
    281                 cookie = xcb_xkb_use_extension(xcb_ctx.conn,
    282                                                XCB_XKB_MAJOR_VERSION,
    283                                                XCB_XKB_MINOR_VERSION);
    284                 reply = xcb_xkb_use_extension_reply(xcb_ctx.conn, cookie, NULL);
    285                 if (!reply)
    286                     rv_abort_msg(1, "couldn't use XKB extension");
    287 
    288                 if (!reply->supported)
    289                     rv_abort_msg(1, "the XKB extension is not supported in X server");
    290             }
    291         }
    292 
    293 
    294         xcb_xkb_get_map_reply_t *reply = NULL;
    295 
    296         {
    297             xcb_xkb_get_map_cookie_t cookie;
    298             xcb_generic_error_t *error = NULL;
    299 
    300             // Send the XkbGetMap request.
    301             cookie = xcb_xkb_get_map(xcb_ctx.conn,
    302                                      XCB_XKB_ID_USE_CORE_KBD,
    303                                      XKB_COMPONENTS_MASK,
    304                                      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
    305             reply = xcb_xkb_get_map_reply(xcb_ctx.conn, cookie, &error);
    306             if (!reply || error)
    307                 rv_abort_msg(1, "couldn't get get_map reply: error_code %d", error->error_code);
    308 
    309             if ((reply->present & XKB_COMPONENTS_MASK) != XKB_COMPONENTS_MASK)
    310                 rv_abort_msg(1, "didn't get all components");
    311 
    312             free(error);
    313         }
    314 
    315 
    316         // Get the map itself, with all the details.
    317         xcb_xkb_get_map_map_t map;
    318         {
    319             void *buffer;
    320 
    321             buffer = xcb_xkb_get_map_map(reply);
    322             xcb_xkb_get_map_map_unpack(buffer,
    323                                        reply->nTypes,
    324                                        reply->nKeySyms,
    325                                        reply->nKeyActions,
    326                                        reply->totalActions,
    327                                        reply->totalKeyBehaviors,
    328                                        reply->nVModMapKeys,
    329                                        reply->totalKeyExplicit,
    330                                        reply->totalModMapKeys,
    331                                        reply->totalVModMapKeys,
    332                                        reply->present,
    333                                        &map);
    334         }
    335 
    336         rv_mem_zero(xcb_ctx.keycode_converter_list, sizeof(xcb_ctx.keycode_converter_list));
    337 
    338         // Get types
    339         {
    340             s32 length = xcb_xkb_get_map_map_types_rtrn_length(reply, &map);
    341 
    342             rv_assert(length <= rv_array_size(xcb_ctx.type_map));
    343             length = rv_min(length, rv_array_size(xcb_ctx.type_map));
    344             xcb_ctx.type_map_size = length;
    345 
    346             xcb_xkb_key_type_iterator_t iter = xcb_xkb_get_map_map_types_rtrn_iterator(reply, &map);
    347 
    348             for (int i = 0; i < length; i++) {
    349                 xcb_xkb_key_type_t *type = iter.data;
    350 
    351                 xcb_ctx.type_map[i].mask = type->mods_mask;
    352 #if RV_XKB_DEBUG
    353                 printf("type (%d):\n", i);
    354 #endif
    355 
    356                 {
    357                     s32 length2 = xcb_xkb_key_type_map_length(type);
    358 
    359                     rv_assert(length2 <= rv_array_size(xcb_ctx.type_map[i].entries));
    360                     length2 = rv_min(length2, rv_array_size(xcb_ctx.type_map[i].entries));
    361                     xcb_ctx.type_map[i].entries_size = length2;
    362 
    363                     xcb_xkb_kt_map_entry_iterator_t iter2 = xcb_xkb_key_type_map_iterator(type);
    364 
    365                     for (int j = 0; j < length2; j++) {
    366                         xcb_xkb_kt_map_entry_t *entry = iter2.data;
    367 
    368 
    369 #if RV_XKB_DEBUG
    370                         printf("\tentry(%d) (active: %d | mask: %d | lvl: %d):\n", j,
    371                                entry->active,
    372                                entry->mods_mask,
    373                                entry->level
    374                             );
    375 #endif
    376                         xcb_ctx.type_map[i].entries[j].active = entry->active;
    377                         xcb_ctx.type_map[i].entries[j].mask = entry->mods_mask;
    378                         xcb_ctx.type_map[i].entries[j].level = entry->level;
    379 
    380                         xcb_xkb_kt_map_entry_next(&iter2);
    381                     }
    382                 }
    383 
    384                 xcb_xkb_key_type_next(&iter);
    385             }
    386         }
    387 
    388         { // map keysyms
    389             int length;
    390             xcb_xkb_key_sym_map_iterator_t iter;
    391 
    392             length = xcb_xkb_get_map_map_syms_rtrn_length(reply, &map);
    393             iter = xcb_xkb_get_map_map_syms_rtrn_iterator(reply, &map);
    394 
    395             rv_assert(length <= 256);
    396             length = rv_min(length, 256);
    397             for (int i = 0; i < length; i++) {
    398                 xcb_xkb_key_sym_map_t *sym_map = iter.data;
    399 
    400                 s32 num_groups = sym_map->groupInfo;
    401                 xcb_ctx.keycode_converter_list[i + reply->firstKeySym].num_groups = num_groups;
    402                 for (s32 g = 0; g < rv_min(4, num_groups); g++) {
    403                     xcb_ctx.keycode_converter_list[i + reply->firstKeySym].kt_index[g] = sym_map->kt_index[g];
    404                 }
    405 
    406 #if RV_XKB_DEBUG
    407                 printf("ksyms(%d) [%d]=>[%d, %d, %d, %d]:\n", i, sym_map->groupInfo,
    408                        sym_map->kt_index[0],
    409                        sym_map->kt_index[1],
    410                        sym_map->kt_index[2],
    411                        sym_map->kt_index[3]
    412                     );
    413 #endif
    414 
    415                 int length2;
    416                 xcb_keysym_t *syms;
    417 
    418                 length2 = xcb_xkb_key_sym_map_syms_length(sym_map);
    419                 syms = xcb_xkb_key_sym_map_syms(sym_map);
    420 
    421                 rv_assert(length2 <= KEYSYM_LOOKUP_SIZE);
    422                 length2 = rv_min(length2, KEYSYM_LOOKUP_SIZE);
    423                 for (int j = 0; j < length2; j++) {
    424                     xcb_keysym_t keysym = syms[j];
    425 
    426 #if RV_XKB_DEBUG
    427                     printf("\tsym(%d): 0x%x\n", j, keysym);
    428 #endif
    429 
    430                     xcb_ctx.keycode_converter_list[i + reply->firstKeySym].keysyms[j] = keysym;
    431                 }
    432 
    433                 xcb_xkb_key_sym_map_next(&iter);
    434             }
    435         }
    436 
    437         { // add xkb events
    438             xcb_void_cookie_t cookie = xcb_xkb_select_events(xcb_ctx.conn,
    439                                                              XCB_XKB_ID_USE_CORE_KBD,
    440                                                              XCB_XKB_EVENT_TYPE_STATE_NOTIFY,
    441                                                              0,
    442                                                              XCB_XKB_EVENT_TYPE_STATE_NOTIFY,
    443                                                              0xff,
    444                                                              0xff,
    445                                                              NULL);
    446         }
    447 
    448         {
    449             xcb_xkb_get_state_cookie_t cookie = xcb_xkb_get_state(xcb_ctx.conn, XCB_XKB_ID_USE_CORE_KBD);
    450             xcb_xkb_get_state_reply_t* reply = xcb_xkb_get_state_reply(xcb_ctx.conn, cookie, NULL);
    451 
    452             xcb_ctx.mods = reply->mods;
    453             xcb_ctx.group = reply->group;
    454 
    455             free(reply);
    456         }
    457 
    458     }
    459 
    460 }
    461 
    462 RV_INTERNAL xcb_keysym_t keycode_to_keysym(u8 keycode, u32 mods)
    463 {
    464     s32 level = 0;
    465     { // look up key level
    466 
    467         s32 group = rv_min(xcb_ctx.keycode_converter_list[keycode].num_groups, xcb_ctx.group);
    468 
    469         if (xcb_ctx.keycode_converter_list[keycode].num_groups) {
    470             s32 key_type = xcb_ctx.keycode_converter_list[keycode].kt_index[group];
    471 
    472             mods &= xcb_ctx.type_map[key_type].mask;
    473             for (s32 e = 0; e < xcb_ctx.type_map[key_type].entries_size; e++) {
    474                 if (xcb_ctx.type_map[key_type].entries[e].active &&
    475                     xcb_ctx.type_map[key_type].entries[e].mask == mods) {
    476                     level = xcb_ctx.type_map[key_type].entries[e].level;
    477                     break;
    478                 }
    479             }
    480         }
    481     }
    482 
    483     return xcb_ctx.keycode_converter_list[keycode].keysyms[level];
    484 }
    485 
    486 //////////////////////////////////////////////////////////////////
    487 // window creation/destruction
    488 
    489 RV_GLOBAL rv_window_handle_t* rv_create_window(rv_window_desc_t desc)
    490 {
    491     make_xcb_context();
    492 
    493     rv_window_handle_t* res = rv_mem_global_alloc(rv_window_handle_t, 1);
    494 
    495     { // create the window
    496 
    497         // get the first screen
    498         xcb_screen_t* screen = xcb_setup_roots_iterator(xcb_get_setup(xcb_ctx.conn)).data;
    499 
    500         // set up masks
    501         uint32_t             mask = 0;
    502         uint32_t             values[2];
    503         res->window_id = xcb_generate_id(xcb_ctx.conn);
    504 
    505         mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
    506 
    507         values[0] = screen->black_pixel;
    508         values[1] = XCB_EVENT_MASK_EXPOSURE     | XCB_EVENT_MASK_STRUCTURE_NOTIFY |
    509                     XCB_EVENT_MASK_KEY_PRESS    | XCB_EVENT_MASK_KEY_RELEASE      |
    510                     XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE   |
    511                     XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW     ;
    512 
    513 
    514         // default window size
    515         rv_vec2 size = desc.rect.wh;
    516         if (size.x == 0) size.x = 150;
    517         if (size.y == 0) size.y = 150;
    518 
    519         xcb_create_window(xcb_ctx.conn,        // connection
    520                           XCB_COPY_FROM_PARENT,          // depth
    521                           res->window_id,                // window Id
    522                           screen->root,                  // parent window
    523                           desc.rect.x, desc.rect.y,      // x, y
    524                           size.x, size.y,                // width, height
    525                           0,                             // border_width
    526                           XCB_WINDOW_CLASS_INPUT_OUTPUT, // class
    527                           screen->root_visual,           // visual
    528                           mask, values);                 // masks
    529 
    530         // map the window on the screen
    531         xcb_map_window(xcb_ctx.conn, res->window_id);
    532     }
    533 
    534     { // change window title
    535         xcb_intern_atom_cookie_t utf8_string_cookie = xcb_intern_atom(xcb_ctx.conn, 0, 11, "UTF8_STRING");
    536         xcb_intern_atom_reply_t* utf8_string_reply  = xcb_intern_atom_reply(xcb_ctx.conn, utf8_string_cookie, 0);
    537         xcb_atom_t m_utf8_string_atom = utf8_string_reply->atom;
    538         free(utf8_string_reply);
    539 
    540         xcb_change_property(xcb_ctx.conn, XCB_PROP_MODE_REPLACE, res->window_id,
    541                             XCB_ATOM_WM_NAME, m_utf8_string_atom, 8,
    542                             desc.name.len, desc.name.str);
    543     }
    544 
    545     { // setup close handler event
    546         xcb_intern_atom_cookie_t protocolCookie = xcb_intern_atom_unchecked(xcb_ctx.conn, 1, 12, "WM_PROTOCOLS");
    547         xcb_intern_atom_reply_t* protocolReply = xcb_intern_atom_reply(xcb_ctx.conn, protocolCookie, 0);
    548         xcb_intern_atom_cookie_t closeCookie = xcb_intern_atom_unchecked(xcb_ctx.conn, 0, 16, "WM_DELETE_WINDOW");
    549         res->close_reply = xcb_intern_atom_reply(xcb_ctx.conn, closeCookie, 0);
    550         xcb_change_property(xcb_ctx.conn, XCB_PROP_MODE_REPLACE, res->window_id, protocolReply->atom, 4, 32, 1, &(res->close_reply->atom));
    551 
    552         free(protocolReply);
    553     }
    554 
    555     if (desc.attach_render) {
    556 #if RV_RENDER_OPENGL
    557         res->render = egl_attach(res->window_id, xcb_ctx.conn, desc.vsync);
    558 #endif
    559     }
    560 
    561     res->vsync = desc.vsync;
    562 
    563     xcb_flush(xcb_ctx.conn);
    564 
    565     xcb_generic_event_t *e;
    566     while ((e = xcb_wait_for_event(xcb_ctx.conn))) {
    567         bool32 done = false;
    568 
    569         if ((e->response_type & ~0x80) == XCB_EXPOSE) {
    570             done = true;
    571         }
    572 
    573         free(e);
    574 
    575         if (done) break;
    576     }
    577 
    578     {
    579         xcb_get_geometry_cookie_t geom_cookie = xcb_get_geometry(xcb_ctx.conn, res->window_id);
    580         xcb_get_geometry_reply_t* reply = xcb_get_geometry_reply(xcb_ctx.conn, geom_cookie, NULL);
    581         if (reply) {
    582             res->last_size = rv_v2(reply->width, reply->height);
    583             free(reply);
    584         }
    585     }
    586 
    587     rv_llist_stack_push(xcb_ctx.window_list, res);
    588 
    589     return res;
    590 }
    591 
    592 RV_GLOBAL void rv_destroy_window(rv_window_handle_t** handle)
    593 {
    594     if (!*handle) {
    595         rv_unreachable();
    596         return;
    597     }
    598 
    599 #if RV_RENDER_OPENGL
    600     {
    601         eglMakeCurrent(egl_context.egl_display, NULL, NULL, egl_context.egl_context);
    602         EGLSurface* surface = handle[0]->render;
    603         eglDestroySurface(egl_context.egl_display, surface);
    604     }
    605 #endif
    606 
    607     free(handle[0]->close_reply);
    608 
    609     xcb_destroy_window(xcb_ctx.conn, handle[0]->window_id);
    610 
    611     xcb_flush(xcb_ctx.conn);
    612 
    613     for (rv_window_handle_t** window_iter = &xcb_ctx.window_list; *window_iter; window_iter = &window_iter[0]->next) {
    614         if (*window_iter == *handle) {
    615             *window_iter = handle[0]->next;
    616             break;
    617         }
    618     }
    619 
    620     rv_mem_global_alloc_free(*handle, sizeof(**handle));
    621     *handle = 0;
    622 }
    623 
    624 rv_window_handle_t* xcb_get_window(xcb_window_t window) {
    625     for (rv_window_handle_t* w = xcb_ctx.window_list; w; w = w->next) {
    626         if (window == w->window_id) {
    627             return w;
    628         }
    629     }
    630     return NULL;
    631 }
    632 
    633 //////////////////////////////////////////////////////////////////
    634 // event polling
    635 
    636 RV_GLOBAL rv_event_t* rv_get_events(rv_arena* arena, rv_event_flags_t flags)
    637 {
    638     xcb_flush(xcb_ctx.conn);
    639 
    640     rv_event_t* res_first = NULL;
    641     rv_event_t* res_last = NULL;
    642 
    643     // force send initial events...
    644     for (rv_window_handle_t* w = xcb_ctx.window_list; w; w = w->next) {
    645         if (!w->sent_initial_events) {
    646             w->sent_initial_events = true;
    647             rv_event_t* n;
    648 
    649             n = rv_push_compound(arena, rv_event_t, {.type = RV_EVENT_WINDOW_RESIZE, .window_resize = w});
    650             rv_llist_queue_push(res_first, res_last, n);
    651 
    652             n = rv_push_compound(arena, rv_event_t, {.type = RV_EVENT_KEY_PRESS, .key_press.mods = xcb_ctx.mods, .key_press.group = xcb_ctx.group});
    653             rv_llist_queue_push(res_first, res_last, n);
    654         }
    655     }
    656 
    657     xcb_generic_event_t *event;
    658     while ((event = xcb_poll_for_event(xcb_ctx.conn))) {
    659         rv_event_t new_ev = {0};
    660 
    661         bool32 is_press = false;
    662 
    663         switch (event->response_type & ~0x80) {
    664         case XCB_EXPOSE:           {
    665             xcb_flush(xcb_ctx.conn);
    666         } break;
    667 
    668         case XCB_CLIENT_MESSAGE: {
    669             for (rv_window_handle_t* w = xcb_ctx.window_list; w; w = w->next) {
    670                 if (((xcb_client_message_event_t*)event)->data.data32[0] == w->close_reply->atom) {
    671                     new_ev.type = RV_EVENT_WINDOW_CLOSE;
    672                     new_ev.window_close = w;
    673                     break;
    674                 }
    675             }
    676         } break;
    677 
    678             // TODO: handle closing
    679 
    680         case XCB_KEY_PRESS:        {
    681             _Static_assert(sizeof(xcb_key_release_event_t) == sizeof(xcb_key_press_event_t), "press/release were not the same");
    682             is_press = true;
    683             case XCB_KEY_RELEASE:;
    684 
    685                 if (is_press) new_ev.type = RV_EVENT_KEY_PRESS;
    686                 else          new_ev.type = RV_EVENT_KEY_RELEASE;
    687 
    688                 xcb_key_press_event_t* key_event = (xcb_key_press_event_t*)event;
    689 
    690                 u8 keycode = key_event->detail;
    691 
    692                 new_ev.key_press.window = xcb_get_window(key_event->event);
    693 
    694                 xcb_keysym_t keysym = keycode_to_keysym(keycode, xcb_ctx.mods);
    695 
    696                 // TODO(Samdal): look up conversion map for multi-keys and dead keys
    697                 { // convert (some) dead keys to real keys...
    698                     switch (keysym) {
    699                     case 0xfe50: { keysym = 0x0060; } break;
    700                     case 0xfe51: { keysym = 0x00b4; } break;
    701                     case 0xfe57: { keysym = 0x00a8; } break;
    702                     case 0xfe52: { keysym = 0x005e; } break;
    703                     case 0xfe53: { keysym = 0x007e; } break;
    704                     }
    705                 }
    706 
    707                 rv_keycode_t kc = RV_KEYCODE_INVALID;
    708                 { // convert to RV keycode
    709                     switch (keysym) {
    710                     case 0x0020: { kc = RV_KEYCODE_SPACE;           } break;
    711                     case 0x0027: { kc = RV_KEYCODE_APOSTROPHE;      } break;
    712                     case 0x002c: { kc = RV_KEYCODE_COMMA;           } break;
    713                     case 0x002d: { kc = RV_KEYCODE_MINUS;           } break;
    714                     case 0x002e: { kc = RV_KEYCODE_PERIOD;          } break;
    715                     case 0x002f: { kc = RV_KEYCODE_SLASH;           } break;
    716                     case 0x0030: { kc = RV_KEYCODE_0;               } break;
    717                     case 0x0031: { kc = RV_KEYCODE_1;               } break;
    718                     case 0x0032: { kc = RV_KEYCODE_2;               } break;
    719                     case 0x0033: { kc = RV_KEYCODE_3;               } break;
    720                     case 0x0034: { kc = RV_KEYCODE_4;               } break;
    721                     case 0x0035: { kc = RV_KEYCODE_5;               } break;
    722                     case 0x0036: { kc = RV_KEYCODE_6;               } break;
    723                     case 0x0037: { kc = RV_KEYCODE_7;               } break;
    724                     case 0x0038: { kc = RV_KEYCODE_8;               } break;
    725                     case 0x0039: { kc = RV_KEYCODE_9;               } break;
    726                     case 0x003b: { kc = RV_KEYCODE_SEMICOLON;       } break;
    727                     case 0x003d: { kc = RV_KEYCODE_EQUAL;           } break;
    728 
    729                         // capital letters
    730                     case 0x0041: { kc = RV_KEYCODE_A;               } break;
    731                     case 0x0042: { kc = RV_KEYCODE_B;               } break;
    732                     case 0x0043: { kc = RV_KEYCODE_C;               } break;
    733                     case 0x0044: { kc = RV_KEYCODE_D;               } break;
    734                     case 0x0045: { kc = RV_KEYCODE_E;               } break;
    735                     case 0x0046: { kc = RV_KEYCODE_F;               } break;
    736                     case 0x0047: { kc = RV_KEYCODE_G;               } break;
    737                     case 0x0048: { kc = RV_KEYCODE_H;               } break;
    738                     case 0x0049: { kc = RV_KEYCODE_I;               } break;
    739                     case 0x004a: { kc = RV_KEYCODE_J;               } break;
    740                     case 0x004b: { kc = RV_KEYCODE_K;               } break;
    741                     case 0x004c: { kc = RV_KEYCODE_L;               } break;
    742                     case 0x004d: { kc = RV_KEYCODE_M;               } break;
    743                     case 0x004e: { kc = RV_KEYCODE_N;               } break;
    744                     case 0x004f: { kc = RV_KEYCODE_O;               } break;
    745                     case 0x0050: { kc = RV_KEYCODE_P;               } break;
    746                     case 0x0051: { kc = RV_KEYCODE_Q;               } break;
    747                     case 0x0052: { kc = RV_KEYCODE_R;               } break;
    748                     case 0x0053: { kc = RV_KEYCODE_S;               } break;
    749                     case 0x0054: { kc = RV_KEYCODE_T;               } break;
    750                     case 0x0055: { kc = RV_KEYCODE_U;               } break;
    751                     case 0x0056: { kc = RV_KEYCODE_V;               } break;
    752                     case 0x0057: { kc = RV_KEYCODE_W;               } break;
    753                     case 0x0058: { kc = RV_KEYCODE_X;               } break;
    754                     case 0x0059: { kc = RV_KEYCODE_Y;               } break;
    755                     case 0x005a: { kc = RV_KEYCODE_Z;               } break;
    756 
    757                         // small letters
    758                     case 0x0061: { kc = RV_KEYCODE_A;               } break;
    759                     case 0x0062: { kc = RV_KEYCODE_B;               } break;
    760                     case 0x0063: { kc = RV_KEYCODE_C;               } break;
    761                     case 0x0064: { kc = RV_KEYCODE_D;               } break;
    762                     case 0x0065: { kc = RV_KEYCODE_E;               } break;
    763                     case 0x0066: { kc = RV_KEYCODE_F;               } break;
    764                     case 0x0067: { kc = RV_KEYCODE_G;               } break;
    765                     case 0x0068: { kc = RV_KEYCODE_H;               } break;
    766                     case 0x0069: { kc = RV_KEYCODE_I;               } break;
    767                     case 0x006a: { kc = RV_KEYCODE_J;               } break;
    768                     case 0x006b: { kc = RV_KEYCODE_K;               } break;
    769                     case 0x006c: { kc = RV_KEYCODE_L;               } break;
    770                     case 0x006d: { kc = RV_KEYCODE_M;               } break;
    771                     case 0x006e: { kc = RV_KEYCODE_N;               } break;
    772                     case 0x006f: { kc = RV_KEYCODE_O;               } break;
    773                     case 0x0070: { kc = RV_KEYCODE_P;               } break;
    774                     case 0x0071: { kc = RV_KEYCODE_Q;               } break;
    775                     case 0x0072: { kc = RV_KEYCODE_R;               } break;
    776                     case 0x0073: { kc = RV_KEYCODE_S;               } break;
    777                     case 0x0074: { kc = RV_KEYCODE_T;               } break;
    778                     case 0x0075: { kc = RV_KEYCODE_U;               } break;
    779                     case 0x0076: { kc = RV_KEYCODE_V;               } break;
    780                     case 0x0077: { kc = RV_KEYCODE_W;               } break;
    781                     case 0x0078: { kc = RV_KEYCODE_X;               } break;
    782                     case 0x0079: { kc = RV_KEYCODE_Y;               } break;
    783                     case 0x007a: { kc = RV_KEYCODE_Z;               } break;
    784 
    785                     case 0x007c: { kc = RV_KEYCODE_BAR;             } break;
    786                     case 0x005b: { kc = RV_KEYCODE_LEFT_BRACKET;    } break;
    787                     case 0x005c: { kc = RV_KEYCODE_BACKSLASH;       } break;
    788                     case 0x005d: { kc = RV_KEYCODE_RIGHT_BRACKET;   } break;
    789                     case 0x0060: { kc = RV_KEYCODE_GRAVE_ACCENT;    } break; // ascii grave
    790                     case 0x00b4: { kc = RV_KEYCODE_ACUTE_ACCENT;    } break; // ascii acute
    791                     case 0x00a8: { kc = RV_KEYCODE_DIAERESIS;       } break; // ascii diaeresis
    792                     case 0x005e: { kc = RV_KEYCODE_CIRCUMFLEX;       } break; // ascii circumflex
    793                     case 0x007e: { kc = RV_KEYCODE_TILDE;           } break; // ascii tilde
    794                         //case 0x0000: { kc = RV_KEYCODE_WORLD_1;         } break;
    795                         //case 0x0000: { kc = RV_KEYCODE_WORLD_2;         } break;
    796                     case 0xff1b: { kc = RV_KEYCODE_ESC;             } break;
    797                     case 0xff0d: { kc = RV_KEYCODE_ENTER;           } break;
    798                     case 0xff09: { kc = RV_KEYCODE_TAB;             } break;
    799                     case 0xff08: { kc = RV_KEYCODE_BACKSPACE;       } break;
    800 
    801                         // nav cluster
    802                     case 0xff63: { kc = RV_KEYCODE_INSERT;          } break;
    803                     case 0xffff: { kc = RV_KEYCODE_DELETE;          } break;
    804                     case 0xff53: { kc = RV_KEYCODE_RIGHT;           } break;
    805                     case 0xff51: { kc = RV_KEYCODE_LEFT;            } break;
    806                     case 0xff54: { kc = RV_KEYCODE_DOWN;            } break;
    807                     case 0xff52: { kc = RV_KEYCODE_UP;              } break;
    808                     case 0xff55: { kc = RV_KEYCODE_PAGE_UP;         } break;
    809                     case 0xff56: { kc = RV_KEYCODE_PAGE_DOWN;       } break;
    810                     case 0xff50: { kc = RV_KEYCODE_HOME;            } break;
    811                     case 0xff57: { kc = RV_KEYCODE_END;             } break;
    812                         // non-numlock nav
    813                     case 0xff9e: { kc = RV_KEYCODE_INSERT;          } break;
    814                     case 0xff9f: { kc = RV_KEYCODE_DELETE;          } break;
    815                     case 0xff98: { kc = RV_KEYCODE_RIGHT;           } break;
    816                     case 0xff96: { kc = RV_KEYCODE_LEFT;            } break;
    817                     case 0xff99: { kc = RV_KEYCODE_DOWN;            } break;
    818                     case 0xff97: { kc = RV_KEYCODE_UP;              } break;
    819                     case 0xff9a: { kc = RV_KEYCODE_PAGE_UP;         } break;
    820                     case 0xff9b: { kc = RV_KEYCODE_PAGE_DOWN;       } break;
    821                     case 0xff95: { kc = RV_KEYCODE_HOME;            } break;
    822                     case 0xff9c: { kc = RV_KEYCODE_END;             } break;
    823 
    824                     case 0xffe5: { kc = RV_KEYCODE_CAPS_LOCK;       } break;
    825                     case 0xff14: { kc = RV_KEYCODE_SCROLL_LOCK;     } break;
    826                     case 0xff7f: { kc = RV_KEYCODE_NUM_LOCK;        } break;
    827                     case 0xff61: { kc = RV_KEYCODE_PRINT_SCREEN;    } break;
    828                     case 0xff13: { kc = RV_KEYCODE_PAUSE;           } break;
    829                     case 0xffbe: { kc = RV_KEYCODE_F1;              } break;
    830                     case 0xffbf: { kc = RV_KEYCODE_F2;              } break;
    831                     case 0xffc0: { kc = RV_KEYCODE_F3;              } break;
    832                     case 0xffc1: { kc = RV_KEYCODE_F4;              } break;
    833                     case 0xffc2: { kc = RV_KEYCODE_F5;              } break;
    834                     case 0xffc3: { kc = RV_KEYCODE_F6;              } break;
    835                     case 0xffc4: { kc = RV_KEYCODE_F7;              } break;
    836                     case 0xffc5: { kc = RV_KEYCODE_F8;              } break;
    837                     case 0xffc6: { kc = RV_KEYCODE_F9;              } break;
    838                     case 0xffc7: { kc = RV_KEYCODE_F10;             } break;
    839                     case 0xffc8: { kc = RV_KEYCODE_F11;             } break;
    840                     case 0xffc9: { kc = RV_KEYCODE_F12;             } break;
    841                     case 0xffca: { kc = RV_KEYCODE_F13;             } break;
    842                     case 0xffcb: { kc = RV_KEYCODE_F14;             } break;
    843                     case 0xffcc: { kc = RV_KEYCODE_F15;             } break;
    844                     case 0xffcd: { kc = RV_KEYCODE_F16;             } break;
    845                     case 0xffce: { kc = RV_KEYCODE_F17;             } break;
    846                     case 0xffcf: { kc = RV_KEYCODE_F18;             } break;
    847                     case 0xffd0: { kc = RV_KEYCODE_F19;             } break;
    848                     case 0xffd1: { kc = RV_KEYCODE_F20;             } break;
    849                     case 0xffd2: { kc = RV_KEYCODE_F21;             } break;
    850                     case 0xffd3: { kc = RV_KEYCODE_F22;             } break;
    851                     case 0xffd4: { kc = RV_KEYCODE_F23;             } break;
    852                     case 0xffd5: { kc = RV_KEYCODE_F24;             } break;
    853                     case 0xffd6: { kc = RV_KEYCODE_F25;             } break;
    854                     case 0xffb0: { kc = RV_KEYCODE_KP_0;            } break;
    855                     case 0xffb1: { kc = RV_KEYCODE_KP_1;            } break;
    856                     case 0xffb2: { kc = RV_KEYCODE_KP_2;            } break;
    857                     case 0xffb3: { kc = RV_KEYCODE_KP_3;            } break;
    858                     case 0xffb4: { kc = RV_KEYCODE_KP_4;            } break;
    859                     case 0xffb5: { kc = RV_KEYCODE_KP_5;            } break;
    860                     case 0xffb6: { kc = RV_KEYCODE_KP_6;            } break;
    861                     case 0xffb7: { kc = RV_KEYCODE_KP_7;            } break;
    862                     case 0xffb8: { kc = RV_KEYCODE_KP_8;            } break;
    863                     case 0xffb9: { kc = RV_KEYCODE_KP_9;            } break;
    864                     case 0xffae: { kc = RV_KEYCODE_KP_DECIMAL;      } break;
    865                     case 0xffaf: { kc = RV_KEYCODE_KP_DIVIDE;       } break;
    866                     case 0xffaa: { kc = RV_KEYCODE_KP_MULTIPLY;     } break;
    867                     case 0xffad: { kc = RV_KEYCODE_KP_SUBTRACT;     } break;
    868                     case 0xffab: { kc = RV_KEYCODE_KP_ADD;          } break;
    869                     case 0xff8d: { kc = RV_KEYCODE_KP_ENTER;        } break; // same as normal enter...
    870                     case 0xffbd: { kc = RV_KEYCODE_KP_EQUAL;        } break;
    871                     case 0xffe1: { kc = RV_KEYCODE_LEFT_SHIFT;      } break;
    872                     case 0xffe3: { kc = RV_KEYCODE_LEFT_CONTROL;    } break;
    873                     case 0xffe9: { kc = RV_KEYCODE_LEFT_ALT;        } break;
    874                     case 0xffeb: { kc = RV_KEYCODE_LEFT_SUPER;      } break;
    875                     case 0xffe2: { kc = RV_KEYCODE_RIGHT_SHIFT;     } break;
    876                     case 0xffe4: { kc = RV_KEYCODE_RIGHT_CONTROL;   } break;
    877                     case 0xffea: { kc = RV_KEYCODE_RIGHT_ALT;       } break;
    878                     case 0xfe03: { kc = RV_KEYCODE_RIGHT_ALT;       } break; // ISO level 3 shift
    879                     case 0xffec: { kc = RV_KEYCODE_RIGHT_SUPER;     } break;
    880                     case 0xff67: { kc = RV_KEYCODE_MENU;            } break;
    881                     default:     { kc = RV_KEYCODE_INVALID;         } break;
    882                     }
    883                 }
    884                 new_ev.key_press.keycode = kc;
    885                 new_ev.key_press.mods = xcb_ctx.mods;
    886                 new_ev.key_press.group = xcb_ctx.group;
    887                 new_ev.key_press.native_keycode = keycode;
    888 
    889                 {
    890                     char buf[XKB_KEYSYM_UTF8_MAX_SIZE];
    891                     s32 len = xkb_keysym_to_utf8(keysym, buf, sizeof(buf));
    892                     if (len) {
    893                         new_ev.key_press.string = rv_str8_from_cstr(buf);
    894                         new_ev.key_press.string = rv_str8_copy(arena, new_ev.key_press.string);
    895                         if (new_ev.key_press.string.len == 1) {
    896                             if (new_ev.key_press.string.str[0] == '\r') {
    897                                 new_ev.key_press.string.str[0] = '\n';
    898                             }
    899                         }
    900                     }
    901                 }
    902 
    903                 // invalidate event if nothing gave any results
    904                 if (new_ev.key_press.keycode == RV_KEYCODE_INVALID && new_ev.key_press.string.len == 0) {
    905                     new_ev.type = RV_EVENT_INVALID;
    906                 }
    907         } break;
    908         case XCB_BUTTON_PRESS:     {
    909             is_press = true;
    910         case XCB_BUTTON_RELEASE:
    911             if (is_press) new_ev.type = RV_EVENT_BUTTON_PRESS;
    912             else          new_ev.type = RV_EVENT_BUTTON_RELEASE;
    913             xcb_button_press_event_t* button_event = (xcb_button_press_event_t*)event;
    914 
    915             // remap buttons from x11
    916             switch (button_event->detail) {
    917             case 0:
    918             case 1:
    919                 new_ev.button_press.button = 0;
    920                 break;
    921             case 2:
    922                 new_ev.button_press.button = 1;
    923                 break;
    924             case 3:
    925                 new_ev.button_press.button = 2;
    926                 break;
    927             default:
    928                 new_ev.button_press.button = button_event->detail - 1;
    929                 break;
    930             }
    931 
    932             new_ev.button_press.window = xcb_get_window(button_event->event);
    933         } break;
    934         case XCB_CONFIGURE_NOTIFY: {
    935             xcb_configure_notify_event_t* conf_event = (xcb_configure_notify_event_t*)event;
    936             rv_window_handle_t* w = xcb_get_window(conf_event->event);
    937             new_ev.window_resize = w;
    938             if (w) {
    939                 if (w->last_size.x != conf_event->width || w->last_size.y != conf_event->height) {
    940                     w->last_size = rv_v2(conf_event->width, conf_event->height);
    941                     new_ev.type = RV_EVENT_WINDOW_RESIZE;
    942                 }
    943             }
    944             xcb_flush(xcb_ctx.conn);
    945         } break;
    946         case XCB_ENTER_NOTIFY:     {
    947             xcb_enter_notify_event_t* enter_event = (xcb_leave_notify_event_t*)event;
    948             new_ev.type = RV_EVENT_WINDOW_ENTER;
    949             new_ev.window_enter = xcb_get_window(enter_event->event);
    950         } break;
    951         case XCB_LEAVE_NOTIFY:     {
    952             xcb_leave_notify_event_t* leave_event = (xcb_leave_notify_event_t*)event;
    953             new_ev.type = RV_EVENT_WINDOW_LEAVE;
    954             new_ev.window_leave = xcb_get_window(leave_event->event);
    955         } break;
    956 
    957     case XCB_MOTION_NOTIFY:
    958     case XCB_FOCUS_IN:
    959     case XCB_FOCUS_OUT:
    960     case XCB_GRAPHICS_EXPOSURE:
    961     case XCB_NO_EXPOSURE:
    962     case XCB_VISIBILITY_NOTIFY:
    963     case XCB_CREATE_NOTIFY:
    964     case XCB_DESTROY_NOTIFY:
    965     case XCB_UNMAP_NOTIFY:
    966     case XCB_MAP_NOTIFY:
    967     case XCB_MAP_REQUEST:
    968     case XCB_REPARENT_NOTIFY:
    969     case XCB_CONFIGURE_REQUEST:
    970     case XCB_GRAVITY_NOTIFY:
    971     case XCB_RESIZE_REQUEST:
    972     case XCB_CIRCULATE_NOTIFY:
    973     case XCB_CIRCULATE_REQUEST:
    974     case XCB_PROPERTY_NOTIFY:
    975     case XCB_COLORMAP_NOTIFY:
    976     {
    977         printf("unhandled event (one of above in source code): %d\n", event->response_type & ~0x80);
    978     } break;
    979     case XCB_GE_GENERIC: {
    980         // GE generic event...
    981     } break;
    982     case 0: {
    983         puts("X11 error!");
    984     } break;
    985 
    986         default:                   {
    987             if (xcb_ctx.xkb_extension_reply->first_event == (event->response_type & ~0x80)) {
    988 
    989                 xcb_xkb_state_notify_event_t *state = (xcb_xkb_state_notify_event_t *)event;
    990                 xcb_ctx.mods = state->mods;
    991                 xcb_ctx.group = state->group;
    992 
    993             } else {
    994                 printf("unhandled event: %d\n", event->response_type & ~0x80);
    995             }
    996         } break;
    997         }
    998 
    999         if (new_ev.type != RV_EVENT_INVALID) {
   1000             rv_event_t* n = rv_push_copy(arena, rv_event_t, &new_ev);
   1001             rv_llist_queue_push(res_first, res_last, n);
   1002         }
   1003 
   1004         free(event);
   1005     }
   1006 
   1007     xcb_flush(xcb_ctx.conn);
   1008 
   1009     rv_keyboard_context_update(&rv_keyboard_context, res_first);
   1010 
   1011     return res_first;
   1012 }
   1013 
   1014 //////////////////////////////////////////////////////////////////
   1015 // Querying
   1016 
   1017 RV_GLOBAL rv_vec2 rv_window_size(rv_window_handle_t* window)
   1018 {
   1019     return xcb_maybe_window(window)->last_size;
   1020 }
   1021 
   1022 RV_GLOBAL rv_vec2 rv_window_mouse_pos(rv_window_handle_t* window)
   1023 {
   1024     rv_vec2 ret = {0};
   1025     xcb_query_pointer_cookie_t cookie = xcb_query_pointer(xcb_ctx.conn, xcb_maybe_window(window)->window_id);
   1026     xcb_query_pointer_reply_t* reply = xcb_query_pointer_reply(xcb_ctx.conn, cookie, NULL);
   1027     if (reply) {
   1028         ret = rv_v2(
   1029             reply->win_x,
   1030             reply->win_y,
   1031             );
   1032         free(reply);
   1033     }
   1034     return ret;
   1035 }