#define AL_LOG_SECTION "x11" #include #include "window_x11.h" #include "common.h" #include "poll_common.h" #include "keys.h" static Atom ATOM_WM_DELETE_WINDOW; #define XWIN_ALL_DESKTOPS 0xFFFFFFFF #define _NET_WM_STATE_REMOVE 0 #define _NET_WM_STATE_ADD 1 #define _NET_WM_STATE_TOGGLE 2 // @TODO: When to XFlush(), XSync(), or do nothing. f64 stl_scale_scroll(f64 v, f64 factor) { return v / factor; } static struct stl_x11_global global = { 0 }; void stl_set_output_discovered_callback(void (*output_discovered)(void *, struct stl_monitor *), void *userdata) { global.output_discovered = output_discovered; global.userdata = userdata; } bool stl_global_init(bool skip_graphics) { global.x11_d = XOpenDisplay(NULL); if (!global.x11_d) { log_error("Couldn't open display."); return false; } if (!skip_graphics) { #if defined STELA_API_VULKAN global.vk_extensions = (const char **)al_calloc(2, sizeof(const char *const *)); global.vk_extensions[0] = "VK_KHR_surface"; global.vk_extensions[1] = "VK_KHR_xlib_surface"; #elif defined STELA_API_OPENGL if (!stl_maybe_load_egl()) return false; global.egl.display = eglGetDisplay((EGLNativeDisplayType)global.x11_d); if (!global.egl.display) { log_error("Failed to get X11-native EGL display."); return false; } if (!stl_load_egl(&global.egl)) return false; #endif } else { #if defined STELA_API_VULKAN global.vk_extensions = NULL; #elif defined STELA_API_OPENGL global.egl.display = EGL_NO_DISPLAY; #endif } global.cursors[STELA_CURSOR_NORMAL] = None; char *cursor_theme = XcursorGetTheme(global.x11_d); s32 cursor_size = XcursorGetDefaultSize(global.x11_d); XcursorImage *image; if ((image = XcursorLibraryLoadImage("crosshair", cursor_theme, cursor_size))) { global.cursors[STELA_CURSOR_CROSSHAIR] = XcursorImageLoadCursor(global.x11_d, image); XcursorImageDestroy(image); } if ((image = XcursorLibraryLoadImage("pointer", cursor_theme, cursor_size))) { global.cursors[STELA_CURSOR_MOVE] = XcursorImageLoadCursor(global.x11_d, image); XcursorImageDestroy(image); } s32 monitor_count; Window root = DefaultRootWindow(global.x11_d); XRRMonitorInfo *info = XRRGetMonitors(global.x11_d, root, 0, &monitor_count); al_array_init(global.monitors); al_array_reserve(global.monitors, monitor_count); for (s32 i = 0; i < monitor_count; i++) { struct stl_monitor *monitor = al_alloc_object(struct stl_monitor); char *name = XGetAtomName(global.x11_d, info[i].name); size_t length = al_strlen(name) + 1; if (length >= STELA_MAX_MONITOR_NAME) { length = STELA_MAX_MONITOR_NAME - 1; monitor->name[length] = '\0'; } al_memcpy(monitor->name, name, length); free(name); monitor->scale = 1; al_assert(info[i].width > 0 && info[i].height > 0); monitor->width = (u32)info[i].width; monitor->height = (u32)info[i].height; al_array_push(global.monitors, monitor); if (global.output_discovered) { global.output_discovered(global.userdata, monitor); } } XRRFreeMonitors(info); global.xkb_desc = XkbGetMap(global.x11_d, 0, XkbUseCoreKbd); XkbGetNames(global.x11_d, XkbKeyNamesMask | XkbKeyAliasesMask, global.xkb_desc); return true; } s32 stl_global_get_poll_fd() { return ConnectionNumber(global.x11_d); } void stl_global_process_events() { al_assert(false); } s32 stl_swallow_main(u32 argc, str *argv, s32 (*main)(u32, str *, void *), void *extra) { return main(argc, argv, extra); } void stl_global_close(void) { if (!global.x11_d) return; #if defined STELA_API_VULKAN if (global.vk_extensions) al_free(global.vk_extensions); #elif defined STELA_API_OPENGL stl_terminate_egl(&global.egl); #endif XkbFreeNames(global.xkb_desc, XkbKeyNamesMask, True); XkbFreeKeyboard(global.xkb_desc, 0, True); for (u32 i = 0; i < global.monitors.count; i++) { struct stl_monitor *monitor = al_array_at(global.monitors, i); al_free(monitor); } al_array_free(global.monitors); XCloseDisplay(global.x11_d); } #ifdef STELA_API_OPENGL static bool x11_egl_init(struct stl_window_x11 *xw) { EGLConfig config; if (!stl_init_egl_context(&xw->egl, &global.egl, 0, &config)) { return false; } xw->w.get_gl_proc_address = eglGetProcAddress; const EGLint surface_attr[] = { EGL_RENDER_BUFFER, EGL_BACK_BUFFER, EGL_NONE }; xw->egl.surface = eglCreateWindowSurface(xw->egl.display, config, (EGLNativeWindowType)xw->window, surface_attr); return true; } #endif // https://github.com/glfw/glfw/blob/7ef6efeb662e0833645e4b03b910e054fc7d8a85/src/x11_window.c#L54 #define MWM_HINTS_DECORATIONS 2 #define MWM_DECOR_ALL 1 static void set_decorations(struct stl_window_x11 *xw, bool enabled) { Atom motif_wm_hints = XInternAtom(global.x11_d, "_MOTIF_WM_HINTS", False); if (motif_wm_hints) { struct { ulong flags; ulong functions; ulong decorations; long input_mode; ulong status; } hints = { 0 }; hints.flags = MWM_HINTS_DECORATIONS; hints.decorations = enabled ? MWM_DECOR_ALL : 0; XChangeProperty(global.x11_d, xw->window, motif_wm_hints, motif_wm_hints, 32, PropModeReplace, (u8 *)&hints, sizeof(hints) / sizeof(long)); } } static bool window_x11_create_window(struct stl_window *window, u32 width, u32 height, u32 flags, const char *monitor, const char *name, const char *app_id) { struct stl_window_x11 *xw = (struct stl_window_x11 *)window; (void)monitor; XSetWindowAttributes swa; swa.event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | ExposureMask | StructureNotifyMask | PropertyChangeMask | VisibilityChangeMask; Window root = DefaultRootWindow(global.x11_d); xw->window = XCreateWindow(global.x11_d, root, 0, 0, width, height, 0, CopyFromParent, InputOutput, CopyFromParent, CWEventMask, &swa); ATOM_WM_DELETE_WINDOW = XInternAtom(global.x11_d, "WM_DELETE_WINDOW", False); XSetWMProtocols(global.x11_d, xw->window, &ATOM_WM_DELETE_WINDOW, 1); XStoreName(global.x11_d, xw->window, name); XClassHint *class_hint = XAllocClassHint(); class_hint->res_name = (char *)name; class_hint->res_class = (char *)app_id; XSetClassHint(global.x11_d, xw->window, class_hint); XFree(class_hint); XSetWindowAttributes xattr; xattr.override_redirect = False; XChangeWindowAttributes(global.x11_d, xw->window, CWOverrideRedirect, &xattr); XWindowAttributes gwa; XGetWindowAttributes(global.x11_d, xw->window, &gwa); al_assert(gwa.width > 0 && gwa.height > 0); stl_window_created(&xw->w, (u32)gwa.width, (u32)gwa.height, flags); if (flags & STELA_WINDOW_WALLPAPER) { Atom net_wm_type = XInternAtom(global.x11_d, "_NET_WM_WINDOW_TYPE", False); if (net_wm_type) { //Atom window_type = XInternAtom(global.x11_d, "_NET_WM_WINDOW_TYPE_DESKTOP", False); Atom window_type = XInternAtom(global.x11_d, "_NET_WM_WINDOW_TYPE_SPLASH", False); XChangeProperty(global.x11_d, xw->window, net_wm_type, XA_ATOM, 32, PropModeReplace, (u8 *)&window_type, 1); } set_decorations(xw, false); Atom net_wm_state = XInternAtom(global.x11_d, "_NET_WM_STATE", False); if (net_wm_state) { Atom net_wm_below = XInternAtom(global.x11_d, "_NET_WM_STATE_BELOW", False); XChangeProperty(global.x11_d, xw->window, net_wm_state, XA_ATOM, 32, PropModeAppend, (u8 *)&net_wm_below, 1); Atom net_wm_sticky = XInternAtom(global.x11_d, "_NET_WM_STATE_STICKY", False); XChangeProperty(global.x11_d, xw->window, net_wm_state, XA_ATOM, 32, PropModeAppend, (u8 *)&net_wm_sticky, 1); } Atom net_wm_desktop = XInternAtom(global.x11_d, "_NET_WM_DESKTOP", False); if (net_wm_desktop) { ulong all_desktops = XWIN_ALL_DESKTOPS; XChangeProperty(global.x11_d, xw->window, net_wm_desktop, XA_CARDINAL, 32, PropModeReplace, (u8 *)&all_desktops, 1); } } xw->cursor_hidden = false; XMapWindow(global.x11_d, xw->window); XFlush(global.x11_d); if (flags & STELA_WINDOW_WALLPAPER) { // Enable click-through by making the window shape 0x0. Window win = xw->window; while (win != None) { Region region; if ((region = XCreateRegion())) { XShapeCombineRegion(global.x11_d, xw->window, ShapeInput, 0, 0, region, ShapeSet); XDestroyRegion(region); } Window parent, *children = NULL; u32 num_children; if (XQueryTree(global.x11_d, win, &root, &parent, &children, &num_children) && children) { XFree(children); } win = (parent == root) ? None : parent; } XFlush(global.x11_d); } #ifdef STELA_API_OPENGL if (!x11_egl_init(xw)) { return false; } stl_egl_make_current(&xw->egl); stl_egl_swap_interval(&xw->egl, (flags & STELA_WINDOW_VSYNC) ? 1 : 0); stl_egl_release_current(&xw->egl); #endif xw->fds[0].fd = ConnectionNumber(global.x11_d); xw->fds[0].events = POLLIN; #if defined STELA_POLL_INLINE && defined STELA_PAUSE xw->fds[1].fd = nn_eventfd(0, 0); xw->fds[1].events = POLLIN; #endif return true; } static void window_x11_resize_internal(struct stl_window *window, u32 width, u32 height) { (void)window; (void)width; (void)height; } static void window_x11_resize(struct stl_window *window, u32 width, u32 height) { struct stl_window_x11 *xw = (struct stl_window_x11 *)window; XMoveResizeWindow(global.x11_d, xw->window, 0, 0, width, height); } static void window_x11_toggle_fullscreen(struct stl_window *window) { struct stl_window_x11 *xw = (struct stl_window_x11 *)window; XEvent event; event.xclient.type = ClientMessage; event.xclient.serial = 0; event.xclient.send_event = True; event.xclient.display = global.x11_d; event.xclient.window = xw->window; event.xclient.message_type = XInternAtom(global.x11_d, "_NET_WM_STATE", False); event.xclient.format = 32; event.xclient.data.l[1] = XInternAtom(global.x11_d, "_NET_WM_STATE_FULLSCREEN", False); event.xclient.data.l[2] = 0; event.xclient.data.l[3] = 0; event.xclient.data.l[4] = 0; if (!xw->w.fullscreen) { event.xclient.data.l[0] = _NET_WM_STATE_ADD; } else { event.xclient.data.l[0] = _NET_WM_STATE_REMOVE; } Window root = DefaultRootWindow(global.x11_d); XSendEvent(global.x11_d, root, False, SubstructureRedirectMask | SubstructureNotifyMask, &event); XFlush(global.x11_d); xw->w.fullscreen = !xw->w.fullscreen; } static void window_x11_set_cursor(struct stl_window *window, u8 shape) { struct stl_window_x11 *xw = (struct stl_window_x11 *)window; if (shape == STELA_CURSOR_HIDDEN) { if (!xw->cursor_hidden) { xw->cursor_hidden = true; XFixesHideCursor(global.x11_d, xw->window); XFlush(global.x11_d); } return; } if (xw->cursor_hidden) { xw->cursor_hidden = false; XFixesShowCursor(global.x11_d, xw->window); } XSetWindowAttributes swa; swa.cursor = global.cursors[shape]; XChangeWindowAttributes(global.x11_d, xw->window, CWCursor, &swa); XFlush(global.x11_d); } static void window_x11_set_decorations(struct stl_window *window, bool enabled) { struct stl_window_x11 *xw = (struct stl_window_x11 *)window; // On Xwayland this won't apply until interacting with the window; untested otherwise. set_decorations(xw, enabled); XFlush(global.x11_d); } #ifdef STELA_POLL_INLINE static void window_x11_poll(struct stl_window *window, bool block) { struct stl_window_x11 *xw = (struct stl_window_x11 *)window; stl_poll_common(xw->fds, block); } #ifdef STELA_PAUSE static void window_x11_wake(struct stl_window *window) { struct stl_window_x11 *xw = (struct stl_window_x11 *)window; stl_wake_common(xw->fds); } #endif #endif static u16 keysym_to_key(KeySym sym) { // https://github.com/glfw/glfw/blob/7ef6efeb662e0833645e4b03b910e054fc7d8a85/src/x11_init.c#L47 switch (sym) { case XK_KP_0: return STELA_KEY_KP_0; case XK_KP_1: return STELA_KEY_KP_1; case XK_KP_2: return STELA_KEY_KP_2; case XK_KP_3: return STELA_KEY_KP_3; case XK_KP_4: return STELA_KEY_KP_4; case XK_KP_5: return STELA_KEY_KP_5; case XK_KP_6: return STELA_KEY_KP_6; case XK_KP_7: return STELA_KEY_KP_7; case XK_KP_8: return STELA_KEY_KP_8; case XK_KP_9: return STELA_KEY_KP_9; case XK_KP_Separator: case XK_KP_Decimal: return STELA_KEY_KP_DECIMAL; case XK_KP_Equal: return STELA_KEY_KP_EQUAL; case XK_KP_Enter: return STELA_KEY_KP_RETURN; case XK_Escape: return STELA_KEY_ESCAPE; case XK_Tab: return STELA_KEY_TAB; case XK_Shift_L: return STELA_KEY_LEFT_SHIFT; case XK_Shift_R: return STELA_KEY_RIGHT_SHIFT; case XK_Control_L: return STELA_KEY_LEFT_CONTROL; case XK_Control_R: return STELA_KEY_RIGHT_CONTROL; case XK_Meta_L: case XK_Alt_L: return STELA_KEY_LEFT_ALT; case XK_Mode_switch: case XK_ISO_Level3_Shift: case XK_Meta_R: case XK_Alt_R: return STELA_KEY_RIGHT_ALT; case XK_Super_L: return STELA_KEY_LEFT_SUPER; case XK_Super_R: return STELA_KEY_RIGHT_SUPER; case XK_Menu: return STELA_KEY_MENU; case XK_Num_Lock: return STELA_KEY_NUM_LOCK; case XK_Caps_Lock: return STELA_KEY_CAPS_LOCK; case XK_Print: return STELA_KEY_PRINT_SCREEN; case XK_Scroll_Lock: return STELA_KEY_SCROLL_LOCK; case XK_Pause: return STELA_KEY_PAUSE; case XK_Delete: return STELA_KEY_DELETE; case XK_BackSpace: return STELA_KEY_BACKSPACE; case XK_Return: return STELA_KEY_RETURN; case XK_Home: return STELA_KEY_HOME; case XK_End: return STELA_KEY_END; case XK_Page_Up: return STELA_KEY_PAGE_UP; case XK_Page_Down: return STELA_KEY_PAGE_DOWN; case XK_Insert: return STELA_KEY_INSERT; case XK_Left: return STELA_KEY_LEFT; case XK_Right: return STELA_KEY_RIGHT; case XK_Down: return STELA_KEY_DOWN; case XK_Up: return STELA_KEY_UP; case XK_F1: return STELA_KEY_F1; case XK_F2: return STELA_KEY_F2; case XK_F3: return STELA_KEY_F3; case XK_F4: return STELA_KEY_F4; case XK_F5: return STELA_KEY_F5; case XK_F6: return STELA_KEY_F6; case XK_F7: return STELA_KEY_F7; case XK_F8: return STELA_KEY_F8; case XK_F9: return STELA_KEY_F9; case XK_F10: return STELA_KEY_F10; case XK_F11: return STELA_KEY_F11; case XK_F12: return STELA_KEY_F12; case XK_F13: return STELA_KEY_F13; case XK_F14: return STELA_KEY_F14; case XK_F15: return STELA_KEY_F15; case XK_F16: return STELA_KEY_F16; case XK_F17: return STELA_KEY_F17; case XK_F18: return STELA_KEY_F18; case XK_F19: return STELA_KEY_F19; case XK_F20: return STELA_KEY_F20; case XK_F21: return STELA_KEY_F21; case XK_F22: return STELA_KEY_F22; case XK_F23: return STELA_KEY_F23; case XK_F24: return STELA_KEY_F24; case XK_KP_Divide: return STELA_KEY_KP_DIVIDE; case XK_KP_Multiply: return STELA_KEY_KP_MULTIPLY; case XK_KP_Subtract: return STELA_KEY_KP_SUBTRACT; case XK_KP_Add: return STELA_KEY_KP_ADD; case XK_KP_Insert: return STELA_KEY_KP_0; case XK_KP_End: return STELA_KEY_KP_1; case XK_KP_Down: return STELA_KEY_KP_2; case XK_KP_Page_Down: return STELA_KEY_KP_3; case XK_KP_Left: return STELA_KEY_KP_4; case XK_KP_Right: return STELA_KEY_KP_6; case XK_KP_Home: return STELA_KEY_KP_7; case XK_KP_Up: return STELA_KEY_KP_8; case XK_KP_Page_Up: return STELA_KEY_KP_9; case XK_a: return STELA_KEY_A; case XK_b: return STELA_KEY_B; case XK_c: return STELA_KEY_C; case XK_d: return STELA_KEY_D; case XK_e: return STELA_KEY_E; case XK_f: return STELA_KEY_F; case XK_g: return STELA_KEY_G; case XK_h: return STELA_KEY_H; case XK_i: return STELA_KEY_I; case XK_j: return STELA_KEY_J; case XK_k: return STELA_KEY_K; case XK_l: return STELA_KEY_L; case XK_m: return STELA_KEY_M; case XK_n: return STELA_KEY_N; case XK_o: return STELA_KEY_O; case XK_p: return STELA_KEY_P; case XK_q: return STELA_KEY_Q; case XK_r: return STELA_KEY_R; case XK_s: return STELA_KEY_S; case XK_t: return STELA_KEY_T; case XK_u: return STELA_KEY_U; case XK_v: return STELA_KEY_V; case XK_w: return STELA_KEY_W; case XK_x: return STELA_KEY_X; case XK_y: return STELA_KEY_Y; case XK_z: return STELA_KEY_Z; case XK_1: return STELA_KEY_1; case XK_2: return STELA_KEY_2; case XK_3: return STELA_KEY_3; case XK_4: return STELA_KEY_4; case XK_5: return STELA_KEY_5; case XK_6: return STELA_KEY_6; case XK_7: return STELA_KEY_7; case XK_8: return STELA_KEY_8; case XK_9: return STELA_KEY_9; case XK_0: return STELA_KEY_0; case XK_space: return STELA_KEY_SPACE; case XK_minus: return STELA_KEY_MINUS; case XK_equal: return STELA_KEY_EQUAL; case XK_bracketleft: return STELA_KEY_LEFT_BRACKET; case XK_bracketright: return STELA_KEY_RIGHT_BRACKET; case XK_backslash: return STELA_KEY_BACKSLASH; case XK_semicolon: return STELA_KEY_SEMICOLON; case XK_apostrophe: return STELA_KEY_APOSTROPHE; case XK_grave: return STELA_KEY_GRAVE_ACCENT; case XK_comma: return STELA_KEY_COMMA; case XK_period: return STELA_KEY_PERIOD; case XK_slash: return STELA_KEY_SLASH; default: return STELA_KEY_NONE; } } #define _NET_WM_MOVERESIZE_MOVE 8 static void handle_next_xevent(struct stl_window_x11 *xw) { XEvent xev; XNextEvent(global.x11_d, &xev); switch (xev.type) { case ClientMessage: if ((Atom)xev.xclient.data.l[0] == ATOM_WM_DELETE_WINDOW) { stl_window_send_should_close_event(&xw->w); } break; case ConfigureNotify: al_assert(xev.xconfigure.width > 0 && xev.xconfigure.height > 0); xw->w.width = (u32)xev.xconfigure.width; xw->w.height = (u32)xev.xconfigure.height; stl_window_send_resize_event(&xw->w, xw->w.width, xw->w.height); break; case KeyPress: case KeyRelease: { KeySym sym = XkbKeycodeToKeysym(global.x11_d, xev.xkey.keycode, 0, 0);//(xev.xkey.state & ShiftMask) ? 1 : 0); if (sym != NoSymbol) { u8 mapped_state = xev.type == KeyPress ? STELA_BUTTON_PRESSED : STELA_BUTTON_RELEASED; u16 mapped_key = keysym_to_key(sym); stl_window_send_key_immediate_event(&xw->w, mapped_state, mapped_key); stl_window_send_key_event(&xw->w, mapped_state, mapped_key); } break; } case ButtonPress: if (xev.xbutton.button == Button1) { stl_window_send_mouse_button_event(&xw->w, STELA_BUTTON_PRESSED, STELA_MOUSE1); } else if (xev.xbutton.button == Button3) { #ifdef STELA_MOUSE2_DRAG XEvent event; event.xclient.type = ClientMessage; event.xclient.serial = 0; event.xclient.send_event = True; event.xclient.display = global.x11_d; event.xclient.window = xw->window; // https://specifications.freedesktop.org/wm/1.5/ar01s04.html#id-1.5.4 event.xclient.message_type = XInternAtom(global.x11_d, "_NET_WM_MOVERESIZE", False); event.xclient.format = 32; event.xclient.data.l[0] = xev.xbutton.x_root; event.xclient.data.l[1] = xev.xbutton.y_root; event.xclient.data.l[2] = _NET_WM_MOVERESIZE_MOVE; event.xclient.data.l[3] = xev.xbutton.button; // https://specifications.freedesktop.org/wm/1.5/ar01s09.html#sourceindication event.xclient.data.l[4] = 1; // Source indication of "normal applications". Window root = DefaultRootWindow(global.x11_d); XSendEvent(global.x11_d, root, False, SubstructureRedirectMask | SubstructureNotifyMask, &event); XFlush(global.x11_d); #endif stl_window_send_mouse_button_event(&xw->w, STELA_BUTTON_PRESSED, STELA_MOUSE2); } else if (xev.xbutton.button == Button2) { stl_window_send_mouse_button_event(&xw->w, STELA_BUTTON_PRESSED, STELA_MOUSE3); } else if (xev.xbutton.button == Button4) { stl_window_send_scroll_event(&xw->w, 1.f); } else if (xev.xbutton.button == Button5) { stl_window_send_scroll_event(&xw->w, -1.f); } break; case ButtonRelease: if (xev.xbutton.button == Button1) { stl_window_send_mouse_button_event(&xw->w, STELA_BUTTON_RELEASED, STELA_MOUSE1); } else if (xev.xbutton.button == Button3) { stl_window_send_mouse_button_event(&xw->w, STELA_BUTTON_RELEASED, STELA_MOUSE2); } else if (xev.xbutton.button == Button2) { stl_window_send_mouse_button_event(&xw->w, STELA_BUTTON_RELEASED, STELA_MOUSE3); } break; case MotionNotify: stl_window_send_pointer_pos_event(&xw->w, (f64)xev.xmotion.x, (f64)xev.xmotion.y); break; case MapNotify: break; } } static void window_x11_process_events(struct stl_window *window) { struct stl_window_x11 *xw = (struct stl_window_x11 *)window; if (stl_process_events_common(&xw->w, xw->fds)) { // It appears for some reason POLLIN doesn't get set even when there are events. } XPending(global.x11_d); while (QLength(global.x11_d) > 0) { handle_next_xevent(xw); } } #ifdef STELA_PAUSE static bool window_x11_should_refresh(struct stl_window *window) { return stl_should_refresh_common(window); } #endif static void window_x11_free(struct stl_window **window) { struct stl_window_x11 *xw = (struct stl_window_x11 *)*window; XUnmapWindow(global.x11_d, xw->window); XDestroyWindow(global.x11_d, xw->window); #ifdef STELA_API_OPENGL if (stl_egl_is_ready(&xw->egl)) stl_egl_destroy_context(&xw->egl); #endif al_free(xw); *window = NULL; } #if defined STELA_API_VULKAN static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL window_x11_get_vk_proc_address(VkInstance inst, const char *name) { return stl_get_vk_proc_address(inst, name); } static VkResult window_x11_vk_create_surface(void *window, VkInstance inst, VkSurfaceKHR *surface) { struct stl_window_x11 *xw = (struct stl_window_x11 *)window; VkXlibSurfaceCreateInfoKHR info = { .sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, .dpy = global.x11_d, .window = xw->window }; VkResult ret = vkCreateXlibSurfaceKHR(inst, &info, NULL, surface); if (ret != VK_SUCCESS) { log_error("Failed to create Wayland Vulkan surface."); } return ret; } static VkResult window_x11_vk_destroy_surface(VkInstance inst, VkSurfaceKHR surface) { return stl_vk_destroy_surface(inst, surface); } static const char *const *window_x11_vk_get_extensions(u32 *num) { *num = 2; return global.vk_extensions; } #elif defined STELA_API_OPENGL static bool window_x11_gl_loader_load(void *window) { struct stl_window_x11 *xw = (struct stl_window_x11 *)window; return stl_gl_loader_load(xw->w.get_gl_proc_address); } static bool window_x11_gl_make_current(void *window) { struct stl_window_x11 *xw = (struct stl_window_x11 *)window; return stl_egl_make_current(&xw->egl); } static void window_x11_gl_release_current(void *window) { struct stl_window_x11 *xw = (struct stl_window_x11 *)window; stl_egl_release_current(&xw->egl); } static void window_x11_gl_swap_buffers(void *window) { struct stl_window_x11 *xw = (struct stl_window_x11 *)window; stl_egl_swap_buffers(&xw->egl); } #endif struct stl_window *stl_window_create(void) { log_info("Creating X11 window."); struct stl_window_x11 *xw = al_alloc_object(struct stl_window_x11); stl_window_init(&xw->w); xw->w.fullscreen = false; xw->w.create_window = window_x11_create_window; xw->w.resize_internal = window_x11_resize_internal; xw->w.resize = window_x11_resize; xw->w.toggle_fullscreen = window_x11_toggle_fullscreen; xw->w.set_cursor = window_x11_set_cursor; xw->w.set_decorations = window_x11_set_decorations; #ifdef STELA_POLL_INLINE xw->w.poll = window_x11_poll; #ifdef STELA_PAUSE xw->w.wake = window_x11_wake; #endif #endif xw->w.process_events = window_x11_process_events; #ifdef STELA_PAUSE xw->w.should_refresh = window_x11_should_refresh; #endif xw->w.free = window_x11_free; #if defined STELA_API_VULKAN xw->w.get_vk_proc_address = window_x11_get_vk_proc_address; xw->w.vk_create_surface = window_x11_vk_create_surface; xw->w.vk_destroy_surface = window_x11_vk_destroy_surface; xw->w.vk_get_extensions = window_x11_vk_get_extensions; #elif defined STELA_API_OPENGL xw->w.get_gl_proc_address = NULL; xw->w.gl_loader_load = window_x11_gl_loader_load; xw->w.gl_make_current = window_x11_gl_make_current; xw->w.gl_release_current = window_x11_gl_release_current; xw->w.gl_swap_buffers = window_x11_gl_swap_buffers; #endif return (struct stl_window *)xw; }