From 133eca8c1fc95c5b0d093ce7b7331aac42feac5d Mon Sep 17 00:00:00 2001 From: Andrew Opalach Date: Mon, 8 Sep 2025 13:27:24 -0400 Subject: Implement more of X11 backend, Vulkan cleanup - Conditional MOUSE2_DRAG. - Wayland fixes. Signed-off-by: Andrew Opalach --- meson.build | 6 +- src/egl_common.c | 19 +- src/vk_common.c | 54 +++ src/vk_common.h | 5 + src/window.c | 13 + src/window.h | 17 + src/window_glfw.c | 50 ++- src/window_glfw.h | 2 + src/window_wayland.c | 150 ++++---- src/window_wayland.h | 15 +- src/window_win32.c | 100 +++--- src/window_x11.c | 400 ++++++++++++++++++--- src/window_x11.h | 22 +- subprojects/glfw3.wrap | 4 +- subprojects/libalabaster.wrap | 3 +- subprojects/libnaunet.wrap | 3 +- .../packagefiles/vulkan-headers/meson.build | 2 +- subprojects/vulkan.wrap | 4 +- subprojects/wayland-protocols.wrap | 4 +- subprojects/wlr-protocols.wrap | 4 +- 20 files changed, 645 insertions(+), 232 deletions(-) create mode 100644 src/vk_common.c create mode 100644 src/vk_common.h diff --git a/meson.build b/meson.build index 1ba5efe..2070cda 100644 --- a/meson.build +++ b/meson.build @@ -96,6 +96,7 @@ elif get_option('api') in ['gl', 'gles'] elif get_option('api') == 'vulkan' vulkan = dependency('vulkan') vulkan_headers = vulkan.partial_dependency(includes: true, compile_args: true, link_args: true) + stela_src += ['src/vk_common.c'] stela_deps += [vulkan_headers] stela_args += ['-DSTELA_API_VULKAN'] elif get_option('api') == 'dx11' @@ -106,8 +107,11 @@ if get_option('window') == 'x11' x11 = dependency('x11') xext = dependency('xext') xrandr = dependency('xrandr') + xfixes = dependency('xfixes') + xcursor = dependency('xcursor') + xkb = dependency('xkbcommon') stela_src += ['src/window_x11.c'] - stela_deps += [x11, xext, xrandr] + stela_deps += [x11, xext, xrandr, xfixes, xcursor, xkb] stela_args += ['-DSTELA_WINDOW_X11'] elif get_option('window') == 'wayland' wl_client = dependency('wayland-client') diff --git a/src/egl_common.c b/src/egl_common.c index e3531c2..8f02afa 100644 --- a/src/egl_common.c +++ b/src/egl_common.c @@ -78,8 +78,6 @@ static EGLint match_config_to_visual(EGLDisplay display, EGLint visual_id, EGLCo static bool egl_choose_config(EGLDisplay display, const EGLint *attribs, EGLint visual_id, EGLConfig *config) { - EGLint index = -1; - EGLint count = 0; if (!eglGetConfigs(display, NULL, 0, &count) || count <= 0) { log_error("No configs to choose from."); @@ -90,15 +88,12 @@ static bool egl_choose_config(EGLDisplay display, const EGLint *attribs, EGLint EGLConfig *configs = (EGLConfig *)al_malloc(count * sizeof(EGLConfig)); if (!eglChooseConfig(display, attribs, configs, count, &matched) || !matched) { log_error("No configs with appropriate attributes."); - goto out; - } - - index = visual_id ? match_config_to_visual(display, visual_id, config, matched) : 0; - if (index != -1) { - *config = configs[index]; + al_free(configs); + return false; } -out: + EGLint index = visual_id ? match_config_to_visual(display, visual_id, config, matched) : 0; + if (index != -1) *config = configs[index]; al_free(configs); return index != -1; @@ -116,12 +111,17 @@ bool stl_init_egl_context(struct stl_egl *egl, struct stl_egl_global *global, EG }; const EGLint context_attr_gl[] = { +#ifdef AL_DEBUG + EGL_CONTEXT_OPENGL_DEBUG, EGL_TRUE, +#endif EGL_CONTEXT_MAJOR_VERSION, stl_gl_vers[i].major, EGL_CONTEXT_MINOR_VERSION, stl_gl_vers[i].minor, EGL_CONTEXT_OPENGL_PROFILE_MASK, stl_gl_vers[i].profile, EGL_NONE }; + STELA_LOG_GL_CONTEXT_CREATION(stl_gl_vers[i]); + const EGLint *context_attr = stl_gl_vers[i].api == STELA_GL_API ? context_attr_gl : context_attr_gles; @@ -151,7 +151,6 @@ bool stl_init_egl_context(struct stl_egl *egl, struct stl_egl_global *global, EG continue; } - STELA_LOG_GL_CONTEXT_CREATION(stl_gl_vers[i]); egl->context = eglCreateContext(egl->display, *config, EGL_NO_CONTEXT, context_attr); diff --git a/src/vk_common.c b/src/vk_common.c new file mode 100644 index 0000000..3b40f2b --- /dev/null +++ b/src/vk_common.c @@ -0,0 +1,54 @@ +#define AL_LOG_SECTION "vk_common" +#include + +#include "vk_common.h" + +static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL get_vk_proc_address(VkInstance inst, const char *name) +{ + PFN_vkGetInstanceProcAddr vk_get_proc_addr; +#ifdef NAUNET_ON_WINDOWS + const char *VKLib = "vulkan-1.dll"; + HMODULE VKModule = LoadLibraryA(VKLib); + if (!VKModule) { + log_error("Failed to load %s (0x%lx).", VKLib, GetLastError()); + return NULL; + } + vk_get_proc_addr = (void *)GetProcAddress(VKModule, "vkGetInstanceProcAddr"); +#else + /* + const char *vk_lib = "libvulkan.so.1"; + void *vk_module = dlopen(vk_lib, RTLD_LAZY | RTLD_LOCAL); + if (!vk_module) { + log_error("Failed to load %s.", vk_lib); + return NULL; + } + vk_get_proc_addr = (PFN_vkGetInstanceProcAddr)dlsym(vk_module, "vkGetInstanceProcAddr"); + dlclose(vk_module); + */ + vk_get_proc_addr = vkGetInstanceProcAddr; +#endif + if (!vk_get_proc_addr) return NULL; + return (PFN_vkVoidFunction)vk_get_proc_addr(inst, name); +} + +VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL stl_get_vk_proc_address(VkInstance inst, const char *name) +{ + // https://github.com/glfw/glfw/blob/e7ea71be039836da3a98cea55ae5569cb5eb885c/src/vulkan.c#L259 + if (strcmp(name, "vkGetInstanceProcAddr") == 0) { + return (PFN_vkVoidFunction)get_vk_proc_address; + } + return get_vk_proc_address(inst, name); +} + +VkResult stl_vk_destroy_surface(VkInstance inst, VkSurfaceKHR surface) +{ +#ifdef NAUNET_ON_WINDOWS + PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR; + vkDestroySurfaceKHR = (PFN_vkDestroySurfaceKHR)get_vk_proc_address(inst, "vkDestroySurfaceKHR"); + if (!vkDestroySurfaceKHR) { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +#endif + vkDestroySurfaceKHR(inst, surface, NULL); + return VK_SUCCESS; +} diff --git a/src/vk_common.h b/src/vk_common.h new file mode 100644 index 0000000..4fcfe14 --- /dev/null +++ b/src/vk_common.h @@ -0,0 +1,5 @@ +#include "window.h" + +VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL stl_get_vk_proc_address(VkInstance inst, const char *name); +VkResult stl_vk_create_surface(void *window, VkInstance inst, VkSurfaceKHR *surface); +VkResult stl_vk_destroy_surface(VkInstance inst, VkSurfaceKHR surface); diff --git a/src/window.c b/src/window.c index 034f401..0d448af 100644 --- a/src/window.c +++ b/src/window.c @@ -174,6 +174,7 @@ void stl_window_send_mouse_button_event(struct stl_window *window, u8 state, u8 ptr[2] = button; APPEND_EVENT_CHUNK(); #else + window->held_mouse_buttons = (window->held_mouse_buttons & ~button) | (button * (state == STELA_BUTTON_PRESSED)); #ifdef STELA_PAUSE window->pending_refresh |= #endif @@ -181,6 +182,18 @@ void stl_window_send_mouse_button_event(struct stl_window *window, u8 state, u8 #endif } +void stl_window_drop_mouse_buttons(struct stl_window *window) +{ + u8 *buttons = stl_all_mouse_buttons; + while (window->held_mouse_buttons) { + u8 button = *buttons++; + if (window->held_mouse_buttons & button) { + stl_window_send_mouse_button_event(window, STELA_BUTTON_RELEASED, button); + } + } + al_assert(!window->held_mouse_buttons); +} + #ifdef STELA_WINDOW_WIN32 // It's possible for a window to behave in a way that disallows // consistancy between BUTTON_PRESSED and BUTTON_RELEASED events. diff --git a/src/window.h b/src/window.h index e293a8f..f7f25e2 100644 --- a/src/window.h +++ b/src/window.h @@ -26,6 +26,8 @@ #define VK_USE_PLATFORM_WIN32_KHR #elif defined STELA_WINDOW_WAYLAND #define VK_USE_PLATFORM_WAYLAND_KHR +#elif defined STELA_WINDOW_X11 +#define VK_USE_PLATFORM_XLIB_KHR #endif #include #elif defined STELA_API_DX11 @@ -42,6 +44,9 @@ #define STELA_MIN_HEIGHT 16u #endif +// Drag window by clicking anywhere with MOUSE2. +#define STELA_MOUSE2_DRAG + enum { STELA_WINDOW_VSYNC = 1, STELA_WINDOW_WALLPAPER = 1 << 1 @@ -127,6 +132,7 @@ struct stl_window { void (*should_close_callback)(void *); void *userdata; bool closed; + u32 held_mouse_buttons; u32 held_modifiers; #ifdef STELA_PAUSE bool pending_refresh; @@ -161,6 +167,16 @@ enum { STELA_MOUSE3 = 1 << 2 }; +AL_IGNORE_WARNING("-Wunused-variable") + +static u8 stl_all_mouse_buttons[] = { + STELA_MOUSE1, + STELA_MOUSE2, + STELA_MOUSE3 +}; + +AL_IGNORE_WARNING_END + enum { STELA_TOUCH_HOVER = 0, STELA_TOUCH_BEGAN, @@ -187,6 +203,7 @@ void stl_window_send_pointer_pos_event(struct stl_window *window, f64 x, f64 y); void stl_window_send_touch_event(struct stl_window *window, s32 index, u8 phase, f64 x, f64 y); void stl_window_send_scroll_event(struct stl_window *window, f64 y); void stl_window_send_mouse_button_event(struct stl_window *window, u8 state, u8 button); +void stl_window_drop_mouse_buttons(struct stl_window *window); void stl_window_send_key_event(struct stl_window *window, u8 state, u16 button); void stl_window_send_key_immediate_event(struct stl_window *window, u8 state, u16 button); void stl_window_drop_modifiers(struct stl_window *window); diff --git a/src/window_glfw.c b/src/window_glfw.c index 14a3d73..f94c9c2 100644 --- a/src/window_glfw.c +++ b/src/window_glfw.c @@ -54,6 +54,10 @@ bool stl_global_init(bool skip_graphics) global.master_window = NULL; + global.cursors[STELA_CURSOR_NORMAL] = NULL; + global.cursors[STELA_CURSOR_MOVE] = glfwCreateStandardCursor(GLFW_HAND_CURSOR); + global.cursors[STELA_CURSOR_CROSSHAIR] = glfwCreateStandardCursor(GLFW_CROSSHAIR_CURSOR); + return true; } @@ -74,6 +78,12 @@ s32 stl_swallow_main(u32 argc, str *argv, s32 (*main)(u32, str *, void *), void void stl_global_close(void) { + if (global.cursors[STELA_CURSOR_MOVE]) { + glfwDestroyCursor(global.cursors[STELA_CURSOR_MOVE]); + } + if (global.cursors[STELA_CURSOR_CROSSHAIR]) { + glfwDestroyCursor(global.cursors[STELA_CURSOR_CROSSHAIR]); + } glfwTerminate(); } @@ -235,6 +245,12 @@ static bool window_glfw_create_window(struct stl_window *window, u32 width, u32 global.master_window = glfw->window; } + /* + if (glfwRawMouseMotionSupported()) { + glfwSetInputMode(glfw->window, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE); + } + */ + #if defined STELA_API_VULKAN (void)flags; #elif defined STELA_API_DX11 @@ -301,8 +317,8 @@ static GLFWmonitor *get_current_monitor(GLFWwindow *window, s32 wx, s32 wy) { #if GLFW_VERSION_MAJOR > 3 || (GLFW_VERSION_MAJOR == 3 && GLFW_VERSION_MINOR >= 4) if (glfwGetPlatform() == GLFW_PLATFORM_WAYLAND) { - // Calculating the current monitor doesn't work on Wayland - // because we can't know the position of the window. + // Calculating the current monitor doesn't work on Wayland because + // we can't know the position of the window. return glfwGetPrimaryMonitor(); } #endif @@ -358,16 +374,32 @@ static void window_glfw_toggle_fullscreen(struct stl_window *window) glfw->prev.width = glfw->w.width; glfw->prev.height = glfw->w.height; glfwSetWindowMonitor(glfw->window, monitor, 0, 0, - mode->width, mode->height, mode->refreshRate); - //glfwSetInputMode(glfw->window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); + mode->width, mode->height, GLFW_DONT_CARE); } else { glfwSetWindowMonitor(glfw->window, NULL, glfw->prev.x, glfw->prev.y, glfw->prev.width, glfw->prev.height, GLFW_DONT_CARE); - //glfwSetInputMode(glfw->window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); } glfw->w.fullscreen = !glfw->w.fullscreen; } +static void window_glfw_set_cursor(struct stl_window *window, u8 shape) +{ + struct stl_window_glfw *glfw = (struct stl_window_glfw *)window; + if (shape == STELA_CURSOR_HIDDEN) { + glfwSetCursor(glfw->window, NULL); + glfwSetInputMode(glfw->window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); + return; + } + glfwSetInputMode(glfw->window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + glfwSetCursor(glfw->window, global.cursors[shape]); +} + +static void window_glfw_set_decorations(struct stl_window *window, bool enabled) +{ + struct stl_window_glfw *glfw = (struct stl_window_glfw *)window; + glfwSetWindowAttrib(glfw->window, GLFW_DECORATED, enabled ? GLFW_TRUE : GLFW_FALSE); +} + #ifdef STELA_POLL_INLINE #ifdef STELA_EVENT_BUFFER #error "window_glfw_poll() is incompatible with event buffer mode." @@ -424,6 +456,11 @@ static VkResult window_glfw_vk_create_surface(void *window, VkInstance inst, VkS return glfwCreateWindowSurface(inst, glfw->window, NULL, surface); } +static VkResult window_glfw_vk_destroy_surface(VkInstance inst, VkSurfaceKHR surface) +{ + return stl_vk_destroy_surface(inst, surface); +} + static const char *const *window_glfw_vk_get_extensions(u32 *num) { return glfwGetRequiredInstanceExtensions(num); @@ -465,6 +502,8 @@ struct stl_window *stl_window_create(void) glfw->w.resize_internal = window_glfw_resize_internal; glfw->w.resize = window_glfw_resize; glfw->w.toggle_fullscreen = window_glfw_toggle_fullscreen; + glfw->w.set_cursor = window_glfw_set_cursor; + glfw->w.set_decorations = window_glfw_set_decorations; #ifdef STELA_POLL_INLINE glfw->w.poll = window_glfw_poll; #ifdef STELA_PAUSE @@ -479,6 +518,7 @@ struct stl_window *stl_window_create(void) #if defined STELA_API_VULKAN glfw->w.get_vk_proc_address = window_glfw_get_vk_proc_address; glfw->w.vk_create_surface = window_glfw_vk_create_surface; + glfw->w.vk_destroy_surface = window_glfw_vk_destroy_surface; glfw->w.vk_get_extensions = window_glfw_vk_get_extensions; #elif defined STELA_API_OPENGL glfw->w.get_gl_proc_address = glfwGetProcAddress; diff --git a/src/window_glfw.h b/src/window_glfw.h index ad64720..af149b5 100644 --- a/src/window_glfw.h +++ b/src/window_glfw.h @@ -2,6 +2,7 @@ #ifdef STELA_API_VULKAN #define GLFW_INCLUDE_VULKAN +#include "vk_common.h" #else #define GLFW_INCLUDE_NONE #endif @@ -21,6 +22,7 @@ struct stl_glfw_global { GLFWwindow *master_window; + GLFWcursor *cursors[STELA_CURSOR_HIDDEN]; }; struct stl_window_glfw { diff --git a/src/window_wayland.c b/src/window_wayland.c index 7598038..f4ceb90 100644 --- a/src/window_wayland.c +++ b/src/window_wayland.c @@ -361,42 +361,38 @@ static const struct xdg_wm_base_listener xdg_wm_base_listener = { .ping = xdg_wm_base_ping }; +static u32 cursors[] = { + [STELA_CURSOR_NORMAL] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT, + [STELA_CURSOR_MOVE] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE, + [STELA_CURSOR_CROSSHAIR] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR +}; + static void set_cursor_internal(struct stl_window_wayland *wl, u8 shape) { - switch (shape) { - case STELA_CURSOR_NORMAL: - if (wl->cursor_shape_device) { - wp_cursor_shape_device_v1_set_shape(wl->cursor_shape_device, - wl->cursor.serial, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT); - } - break; - case STELA_CURSOR_MOVE: - if (wl->cursor_shape_device) { - wp_cursor_shape_device_v1_set_shape(wl->cursor_shape_device, - wl->cursor.serial, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE); - } - break; - case STELA_CURSOR_CROSSHAIR: - if (wl->cursor_shape_device) { - wp_cursor_shape_device_v1_set_shape(wl->cursor_shape_device, - wl->cursor.serial, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR); - } - break; - case STELA_CURSOR_HIDDEN: + if (shape == STELA_CURSOR_HIDDEN) { wl_pointer_set_cursor(wl->cursor.pointer, wl->cursor.serial, NULL, 0, 0); - break; + } else if (wl->cursor_shape_device) { + wp_cursor_shape_device_v1_set_shape(wl->cursor_shape_device, wl->cursor.serial, cursors[shape]); } } + static void pointer_enter(void *data, struct wl_pointer *pointer, u32 serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y) { struct stl_window_wayland *wl = (struct stl_window_wayland *)data; (void)surface; al_assert(pointer == wl->pointer); +#ifdef STELA_MOUSE2_DRAG + if (wl->w.held_mouse_buttons & STELA_MOUSE2) { + al_assert(wl->cursor.grabbed_by_toplevel_move); + wl->cursor.grabbed_by_toplevel_move = false; + stl_window_send_mouse_button_event(&wl->w, STELA_BUTTON_RELEASED, STELA_MOUSE2); + } +#endif wl->cursor.serial = serial; wl->cursor.pointer = pointer; - set_cursor_internal(wl, wl->cursor_shape); + set_cursor_internal(wl, wl->cursor.shape); wl->frame.pointer_x = wl_fixed_to_double(surface_x) * wl->w.scale; wl->frame.pointer_y = wl_fixed_to_double(surface_y) * wl->w.scale; } @@ -407,6 +403,17 @@ static void pointer_leave(void *data, struct wl_pointer *pointer, struct stl_window_wayland *wl = (struct stl_window_wayland *)data; (void)surface; al_assert(pointer == wl->pointer); + // I don't know if Wayland is supposed to provide any guarantees about button press + // and release consistency. On Sway 1.11, if you hold MOUSE1 and go fullscreen it very + // commonly won't send a pointer_button(RELEASED) for that click. It will send just + // a pointer_leave() instead. Doing a standard drag off the window and release will track + // the pointer properly. + // Expected (... = any movement or amount of time): + // pointer_button(PRESSED) -> [off window] ... [release] -> pointer_button(RELEASED) -> pointer_leave() +#define STELA_WAYLAND_CORRECT_MOUSE_BUTTON_INCONSISTENCY +#ifdef STELA_WAYLAND_CORRECT_MOUSE_BUTTON_INCONSISTENCY + stl_window_drop_mouse_buttons(&wl->w); +#endif wl->cursor.serial = serial; wl->cursor.pointer = NULL; wl->frame.pointer_x = FRAME_NO_VALUE; @@ -432,32 +439,20 @@ static void pointer_button(void *data, struct wl_pointer *pointer, #define MOUSE1 0x110 #define MOUSE2 0x111 #define MOUSE3 0x112 - switch (button) { - case MOUSE1: - if (state == WL_POINTER_BUTTON_STATE_PRESSED) { - stl_window_send_mouse_button_event(&wl->w, STELA_BUTTON_PRESSED, STELA_MOUSE1); - } else if (state == WL_POINTER_BUTTON_STATE_RELEASED) { - stl_window_send_mouse_button_event(&wl->w, STELA_BUTTON_RELEASED, STELA_MOUSE1); - } - break; - case MOUSE2: - if (wl->xdg_toplevel && !wl->w.fullscreen) { - xdg_toplevel_move(wl->xdg_toplevel, wl->seat, serial); - } - if (state == WL_POINTER_BUTTON_STATE_PRESSED) { - stl_window_send_mouse_button_event(&wl->w, STELA_BUTTON_PRESSED, STELA_MOUSE2); - } else if (state == WL_POINTER_BUTTON_STATE_RELEASED) { - stl_window_send_mouse_button_event(&wl->w, STELA_BUTTON_RELEASED, STELA_MOUSE2); - } - break; - case MOUSE3: - if (state == WL_POINTER_BUTTON_STATE_PRESSED) { - stl_window_send_mouse_button_event(&wl->w, STELA_BUTTON_PRESSED, STELA_MOUSE3); - } else if (state == WL_POINTER_BUTTON_STATE_RELEASED) { - stl_window_send_mouse_button_event(&wl->w, STELA_BUTTON_RELEASED, STELA_MOUSE3); - } - break; +#ifdef STELA_MOUSE2_DRAG +#define CAN_TOPLEVEL_MOVE(wl) (wl->xdg_toplevel && !wl->w.fullscreen) + if (button == MOUSE2 && state == WL_POINTER_BUTTON_STATE_PRESSED && CAN_TOPLEVEL_MOVE(wl)) { + // We won't get a MOUSE2 released event after initiating a toplevel move. + // Instead we will get a pointer_enter() event when the user let's go of the button. + wl->cursor.grabbed_by_toplevel_move = true; + xdg_toplevel_move(wl->xdg_toplevel, wl->seat, serial); } +#else + (void)serial; +#endif + u8 mapped_state = (state == WL_POINTER_BUTTON_STATE_PRESSED) ? STELA_BUTTON_PRESSED : STELA_BUTTON_RELEASED; + u8 mapped_button = 1 << (button - MOUSE1); + stl_window_send_mouse_button_event(&wl->w, mapped_state, mapped_button); } static void pointer_axis(void *data, struct wl_pointer *pointer, @@ -588,9 +583,6 @@ static void keyboard_leave(void *data, struct wl_keyboard *keyboard, stl_window_drop_modifiers(&wl->w); } -#define WL_TO_STELA_STATE(state) \ - ((state == WL_KEYBOARD_KEY_STATE_PRESSED) ? STELA_BUTTON_PRESSED : STELA_BUTTON_RELEASED) - static void keyboard_key(void *data, struct wl_keyboard *keyboard, u32 serial, u32 time, u32 key, u32 state) { @@ -598,7 +590,7 @@ static void keyboard_key(void *data, struct wl_keyboard *keyboard, (void)keyboard; (void)serial; (void)time; - u8 mapped_state = WL_TO_STELA_STATE(state); + u8 mapped_state = (state == WL_KEYBOARD_KEY_STATE_PRESSED) ? STELA_BUTTON_PRESSED : STELA_BUTTON_RELEASED; u16 mapped_key = global.keycodes[key]; stl_window_send_key_immediate_event(&wl->w, mapped_state, mapped_key); stl_window_send_key_event(&wl->w, mapped_state, mapped_key); @@ -780,11 +772,18 @@ static void handle_xdg_surface_configure(void *data, struct xdg_surface *surface { struct stl_window_wayland *wl = (struct stl_window_wayland *)data; al_assert(surface == wl->xdg_surface); - if (wl->pending_configure) { + bool pending_configure = wl->pending_configure; + if (pending_configure) { send_resize_event(wl); wl->pending_configure = false; } xdg_surface_ack_configure(wl->xdg_surface, serial); + if (pending_configure) { + // I would assume this is supposed to go before ack_configure() but this is infinitely + // more responsive. Especially with Vulkan, while OpenGL seems to be more lenient about + // the order. Maybe this has the potential to display an incomplete or wrong sized frame? + xdg_surface_set_window_geometry(wl->xdg_surface, 0, 0, wl->geom_width, wl->geom_height); + } } static const struct xdg_surface_listener xdg_surface_listener = { @@ -802,7 +801,6 @@ static void handle_xdg_toplevel_configure(void *data, struct xdg_toplevel *tople // toplevel_configure() event will come after the first swap_buffers(). al_assert(!wl->pending_configure); if (check_and_set_dimensions(wl, width, height)) { - xdg_surface_set_window_geometry(wl->xdg_surface, 0, 0, wl->geom_width, wl->geom_height); wl->pending_configure = true; } } @@ -948,7 +946,11 @@ static bool create_toplevel(struct stl_window_wayland *wl, s32 scale, const char // misaligns the surface position within the window. xdg_surface_set_window_geometry(wl->xdg_surface, 0, 0, wl->geom_width, wl->geom_height); - wl->cursor_shape = STELA_CURSOR_NORMAL; + wl->cursor.shape = STELA_CURSOR_NORMAL; +#ifdef STELA_MOUSE2_DRAG + wl->cursor.grabbed_by_toplevel_move = false; +#endif + if (wl->zxdg_decoration_manager) { wl->zxdg_toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration( wl->zxdg_decoration_manager, wl->xdg_toplevel); @@ -979,7 +981,7 @@ static bool create_layer_shell(struct stl_window_wayland *wl, struct stl_monitor zwlr_layer_surface_v1_set_size(wl->layer_surface, wl->w.width, wl->w.height); zwlr_layer_surface_v1_set_anchor(wl->layer_surface, anchor); zwlr_layer_surface_v1_add_listener(wl->layer_surface, &layer_surface_listener, wl); - wl->cursor_shape = STELA_CURSOR_HIDDEN; + wl->cursor.shape = STELA_CURSOR_HIDDEN; wl->decorated = false; return true; } @@ -1082,9 +1084,9 @@ static void window_wayland_toggle_fullscreen(struct stl_window *window) static void window_wayland_set_cursor(struct stl_window *window, u8 shape) { struct stl_window_wayland *wl = (struct stl_window_wayland *)window; - wl->cursor_shape = shape; + wl->cursor.shape = shape; if (wl->cursor.pointer) { - set_cursor_internal(wl, wl->cursor_shape); + set_cursor_internal(wl, wl->cursor.shape); } } @@ -1179,29 +1181,9 @@ void window_wayland_free(struct stl_window **window) } #if defined STELA_API_VULKAN -static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL get_vk_proc_address(VkInstance inst, const char *name) -{ - /* - const char *vk_lib = "libvulkan.so.1"; - void *vk_module = dlopen(vk_lib, RTLD_LAZY | RTLD_LOCAL); - if (!vk_module) { - log_error("Failed to load %s.", vk_lib); - return NULL; - } - PFN_vkGetInstanceProcAddr vk_get_proc_addr = (PFN_vkGetInstanceProcAddr)dlsym(vk_module, "vkGetInstanceProcAddr"); - */ - PFN_vkGetInstanceProcAddr vk_get_proc_addr = vkGetInstanceProcAddr; - //dlclose(vk_module); - if (!vk_get_proc_addr) return NULL; - return (PFN_vkVoidFunction)vk_get_proc_addr(inst, name); -} - static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL window_wayland_get_vk_proc_address(VkInstance inst, const char *name) { - if (strcmp(name, "vkGetInstanceProcAddr") == 0) { - return (PFN_vkVoidFunction)get_vk_proc_address; - } - return get_vk_proc_address(inst, name); + return stl_get_vk_proc_address(inst, name); } static VkResult window_wayland_vk_create_surface(void *window, VkInstance inst, VkSurfaceKHR *surface) @@ -1216,7 +1198,7 @@ static VkResult window_wayland_vk_create_surface(void *window, VkInstance inst, /* PFN_vkCreateWaylandSurfaceKHR vkCreateWaylandSurfaceKHR; - vkCreateWaylandSurfaceKHR = (PFN_vkCreateWaylandSurfaceKHR)get_vk_proc_address(inst, "vkCreateWaylandSurfaceKHR"); + vkCreateWaylandSurfaceKHR = (PFN_vkCreateWaylandSurfaceKHR)stl_get_vk_proc_address(inst, "vkCreateWaylandSurfaceKHR"); if (!vkCreateWaylandSurfaceKHR) { return VK_ERROR_EXTENSION_NOT_PRESENT; } @@ -1231,15 +1213,7 @@ static VkResult window_wayland_vk_create_surface(void *window, VkInstance inst, static VkResult window_wayland_vk_destroy_surface(VkInstance inst, VkSurfaceKHR surface) { - /* - PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR; - vkDestroySurfaceKHR = (PFN_vkDestroySurfaceKHR)get_vk_proc_address(inst, "vkDestroySurfaceKHR"); - if (!vkDestroySurfaceKHR) { - return VK_ERROR_EXTENSION_NOT_PRESENT; - } - */ - vkDestroySurfaceKHR(inst, surface, NULL); - return VK_SUCCESS; + return stl_vk_destroy_surface(inst, surface); } static const char *const *window_wayland_vk_get_extensions(u32 *num) diff --git a/src/window_wayland.h b/src/window_wayland.h index 89643d5..09c3821 100644 --- a/src/window_wayland.h +++ b/src/window_wayland.h @@ -16,7 +16,11 @@ #include #include "window.h" +#if defined STELA_API_VULKAN +#include "vk_common.h" +#elif defined STELA_API_OPENGL #include "egl_common.h" +#endif #define STELA_WAYLAND_KEY_MAX (KEY_MEDIA + 1) @@ -61,21 +65,26 @@ struct stl_window_wayland { struct stl_monitor_wayland *monitor; struct zwlr_layer_surface_v1 *layer_surface; struct wl_egl_window *egl_window; +#if defined STELA_API_OPENGL struct stl_egl egl; +#endif #if defined STELA_POLL_INLINE && defined STELA_PAUSE struct nn_pollfd fds[2]; #else struct nn_pollfd fds[1]; #endif + s32 geom_width; + s32 geom_height; bool pending_configure; - u8 cursor_shape; struct { u32 serial; struct wl_pointer *pointer; + u8 shape; +#ifdef STELA_MOUSE2_DRAG + bool grabbed_by_toplevel_move; +#endif } cursor; bool decorated; - s32 geom_width; - s32 geom_height; struct { f64 pointer_x; f64 pointer_y; diff --git a/src/window_win32.c b/src/window_win32.c index 38cf6bf..791bacf 100644 --- a/src/window_win32.c +++ b/src/window_win32.c @@ -5,7 +5,9 @@ #include "common.h" #include "keys.h" -#ifdef STELA_API_OPENGL +#if defined STELA_API_VULKAN +#include "vk_common.h" +#elif defined STELA_API_OPENGL #include "gl_common.h" // https://registry.khronos.org/OpenGL/api/GL/wgl.h @@ -292,6 +294,7 @@ bool stl_global_init(bool skip_graphics) EnumDisplayMonitors(NULL, NULL, MonitorInfoProc, 0); Global.Cursors[STELA_CURSOR_NORMAL] = LoadCursor(NULL, IDC_ARROW); + Global.Cursors[STELA_CURSOR_MOVE] = LoadCursor(NULL, IDC_HAND); Global.Cursors[STELA_CURSOR_CROSSHAIR] = LoadCursor(NULL, IDC_CROSS); set_keycodes_for_win32(); @@ -685,6 +688,7 @@ static LRESULT CALLBACK DisplayWndProc(HWND Window, UINT Message, WPARAM WParam, } case WM_RBUTTONDOWN: case WM_NCRBUTTONDOWN: { +#ifdef STELA_MOUSE2_DRAG if (!Global.IsWine) { POINT Pos; Pos.x = GET_X_LPARAM(LParam); @@ -709,14 +713,22 @@ static LRESULT CALLBACK DisplayWndProc(HWND Window, UINT Message, WPARAM WParam, SendMessageW(w32->Window, WM_SYSCOMMAND, SC_MOVE + HTCAPTION, LParam); } break; +#else + goto intercept; +#endif } - case WM_RBUTTONUP: { + case WM_RBUTTONUP: + case WM_NCRBUTTONUP: { +#ifdef STELA_MOUSE2_DRAG if (!Global.IsWine) { w32->WindowMoving = FALSE; ReleaseCapture(); return Result; } break; +#else + goto intercept; +#endif } case WM_MBUTTONDOWN: case WM_MBUTTONUP: @@ -790,7 +802,15 @@ static LRESULT CALLBACK DisplayWndProc(HWND Window, UINT Message, WPARAM WParam, case WM_SIZE: goto intercept; case SET_CURSOR: - SetCursor(Global.Cursors[WParam]); + //SystemParametersInfo(SPI_SETCURSORS, 0, NULL, 0); // Reset system cursor state. + if (WParam == STELA_CURSOR_HIDDEN) { + while (ShowCursor(FALSE) >= 0) {} + } else { + HCURSOR Cursor = Global.Cursors[WParam]; + SetClassLongPtr(w32->Window, GCLP_HCURSOR, (LONG_PTR)Cursor); + SetCursor(Cursor); + ShowCursor(TRUE); + } return 0; default: break; @@ -987,7 +1007,7 @@ static struct stl_monitor_win32 *get_monitor_from_handle(HMONITOR Handle) return NULL; } -static void set_window_decorations(struct stl_window_win32 *w32, bool enabled) +static void set_decorations(struct stl_window_win32 *w32, bool enabled) { if (!enabled && w32->Decorated) { // https://stackoverflow.com/a/2400467 @@ -1017,14 +1037,14 @@ static void window_win32_toggle_fullscreen(struct stl_window *window) w32->Prev.Y = Prev.top; w32->Prev.Width = Prev.right - Prev.left; w32->Prev.Height = Prev.bottom - Prev.top; - set_window_decorations(w32, false); + set_decorations(w32, false); SetWindowPos(w32->Window, HWND_TOPMOST, Monitor->X, Monitor->Y, (s32)Monitor->M.width, (s32)Monitor->M.height, SWP_FRAMECHANGED); } else { struct stl_monitor_win32 *Monitor = w32->Prev.Monitor; s32 X = w32->Prev.X - Monitor->X; s32 Y = w32->Prev.Y - Monitor->Y; if (w32->UserDecorated) { - set_window_decorations(w32, true); + set_decorations(w32, true); } SetWindowPos(w32->Window, HWND_NOTOPMOST, X, Y, w32->Prev.Width, w32->Prev.Height, SWP_FRAMECHANGED); } @@ -1036,7 +1056,7 @@ static void window_win32_set_decorations(struct stl_window *window, bool enabled struct stl_window_win32 *w32 = (struct stl_window_win32 *)window; w32->UserDecorated = enabled; if (!w32->w.fullscreen) { - set_window_decorations(w32, enabled); + set_decorations(w32, enabled); UINT Flags = SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOSENDCHANGING | SWP_NOACTIVATE; SetWindowPos(w32->Window, 0, 0, 0, 0, 0, Flags); } @@ -1044,24 +1064,7 @@ static void window_win32_set_decorations(struct stl_window *window, bool enabled static void SetCursorInternal(struct stl_window_win32 *w32, u8 Shape) { - //SetClassLongPtr(w32->Window, GCLP_HCURSOR, (LONG_PTR)HCursor); - //SystemParametersInfo(SPI_SETCURSORS, 0, NULL, 0); // Reset system cursor state. - switch (Shape) { - case STELA_CURSOR_NORMAL: { - PostMessageW(w32->Window, SET_CURSOR, STELA_CURSOR_NORMAL, 0); - ShowCursor(TRUE); - break; - } - case STELA_CURSOR_CROSSHAIR: { - PostMessageW(w32->Window, SET_CURSOR, STELA_CURSOR_CROSSHAIR, 0); - ShowCursor(TRUE); - break; - } - case STELA_CURSOR_HIDDEN: { - while (ShowCursor(FALSE) >= 0) {} - break; - } - } + PostMessageW(w32->Window, SET_CURSOR, Shape, 0); } static void window_win32_set_cursor(struct stl_window *window, u8 shape) @@ -1107,12 +1110,26 @@ static void win32_handle_message(struct stl_window_win32 *w32, MSG *Message) stl_window_send_mouse_button_event(&w32->w, STELA_BUTTON_RELEASED, STELA_MOUSE1); break; } - case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: { + w32->MouseButtonMask |= STELA_MOUSE2; + stl_window_send_mouse_button_event(&w32->w, STELA_BUTTON_PRESSED, STELA_MOUSE2); + break; + } + case WM_RBUTTONUP: { + w32->MouseButtonMask &= ~STELA_MOUSE2; + stl_window_send_mouse_button_event(&w32->w, STELA_BUTTON_RELEASED, STELA_MOUSE2); + break; + } + case WM_MBUTTONDOWN: { + w32->MouseButtonMask |= STELA_MOUSE3; stl_window_send_mouse_button_event(&w32->w, STELA_BUTTON_PRESSED, STELA_MOUSE3); break; - case WM_MBUTTONUP: + } + case WM_MBUTTONUP: { + w32->MouseButtonMask &= ~STELA_MOUSE3; stl_window_send_mouse_button_event(&w32->w, STELA_BUTTON_RELEASED, STELA_MOUSE3); break; + } case WM_MOUSELEAVE: { /* if (w32->MouseButtonMask & STELA_MOUSE1) { @@ -1233,26 +1250,9 @@ static void window_win32_free(struct stl_window **window) } #if defined STELA_API_VULKAN -static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL GetVKProcAddress(VkInstance Inst, const char *Name) -{ - const char *VKLib = "vulkan-1.dll"; - HMODULE VKModule = LoadLibraryA(VKLib); - if (!VKModule) { - log_error("Failed to load %s (0x%lx).", VKLib, GetLastError()); - return NULL; - } - PFN_vkGetInstanceProcAddr GetInstanceProcAddr = (void *)GetProcAddress(VKModule, "vkGetInstanceProcAddr"); - if (!GetInstanceProcAddr) return NULL; - return (PFN_vkVoidFunction)GetInstanceProcAddr(Inst, Name); -} - static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL window_win32_get_vk_proc_address(VkInstance inst, const char *name) { - // https://github.com/glfw/glfw/blob/e7ea71be039836da3a98cea55ae5569cb5eb885c/src/vulkan.c#L259 - if (strcmp(name, "vkGetInstanceProcAddr") == 0) { - return (PFN_vkVoidFunction)GetVKProcAddress; - } - return GetVKProcAddress(inst, name); + return stl_get_vk_proc_address(inst, name); } static VkResult window_win32_vk_create_surface(void *window, VkInstance inst, VkSurfaceKHR *surface) @@ -1260,7 +1260,7 @@ static VkResult window_win32_vk_create_surface(void *window, VkInstance inst, Vk struct stl_window_win32 *w32 = (struct stl_window_win32 *)window; PFN_vkCreateWin32SurfaceKHR CreateWin32SurfaceKHR; - CreateWin32SurfaceKHR = (PFN_vkCreateWin32SurfaceKHR)GetVKProcAddress(inst, "vkCreateWin32SurfaceKHR"); + CreateWin32SurfaceKHR = (PFN_vkCreateWin32SurfaceKHR)stl_get_vk_proc_address(inst, "vkCreateWin32SurfaceKHR"); if (!CreateWin32SurfaceKHR) { return VK_ERROR_EXTENSION_NOT_PRESENT; } @@ -1280,13 +1280,7 @@ static VkResult window_win32_vk_create_surface(void *window, VkInstance inst, Vk static VkResult window_win32_vk_destroy_surface(VkInstance inst, VkSurfaceKHR surface) { - PFN_vkDestroySurfaceKHR DestroySurfaceKHR; - DestroySurfaceKHR = (PFN_vkDestroySurfaceKHR)GetVKProcAddress(inst, "vkDestroySurfaceKHR"); - if (!DestroySurfaceKHR) { - return VK_ERROR_EXTENSION_NOT_PRESENT; - } - DestroySurfaceKHR(inst, surface, NULL); - return VK_SUCCESS; + return stl_vk_destroy_surface(inst, surface); } static const char *const *window_win32_vk_get_extensions(u32 *num) diff --git a/src/window_x11.c b/src/window_x11.c index a1c56e9..4f399c2 100644 --- a/src/window_x11.c +++ b/src/window_x11.c @@ -4,6 +4,7 @@ #include "window_x11.h" #include "common.h" #include "poll_common.h" +#include "keys.h" static Atom ATOM_WM_DELETE_WINDOW; @@ -13,6 +14,8 @@ static Atom ATOM_WM_DELETE_WINDOW; #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; @@ -33,10 +36,13 @@ bool stl_global_init(bool skip_graphics) log_error("Couldn't open display."); return false; } - global.root = XRootWindow(global.x11_d, XDefaultScreen(global.x11_d)); if (!skip_graphics) { -#ifdef STELA_API_OPENGL +#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) { @@ -46,13 +52,29 @@ bool stl_global_init(bool skip_graphics) if (!stl_load_egl(&global.egl)) return false; #endif } else { -#ifdef STELA_API_OPENGL +#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; - XRRMonitorInfo *info = XRRGetMonitors(global.x11_d, global.root, 0, &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++) { @@ -76,6 +98,9 @@ bool stl_global_init(bool skip_graphics) } XRRFreeMonitors(info); + global.xkb_desc = XkbGetMap(global.x11_d, 0, XkbUseCoreKbd); + XkbGetNames(global.x11_d, XkbKeyNamesMask | XkbKeyAliasesMask, global.xkb_desc); + return true; } @@ -97,9 +122,13 @@ s32 stl_swallow_main(u32 argc, str *argv, s32 (*main)(u32, str *, void *), void void stl_global_close(void) { if (!global.x11_d) return; -#ifdef STELA_API_OPENGL +#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); @@ -129,6 +158,28 @@ static bool x11_egl_init(struct stl_window_x11 *xw) } #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, const char *monitor, const char *name, u32 flags) { @@ -140,13 +191,19 @@ static bool window_x11_create_window(struct stl_window *window, u32 width, u32 h PointerMotionMask | ExposureMask | StructureNotifyMask | PropertyChangeMask | VisibilityChangeMask; - xw->window = XCreateWindow(global.x11_d, global.root, 0, 0, width, height, 0, CopyFromParent, + 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 *)name; + XSetClassHint(global.x11_d, xw->window, class_hint); + XFree(class_hint); XSetWindowAttributes xattr; xattr.override_redirect = False; @@ -166,20 +223,7 @@ static bool window_x11_create_window(struct stl_window *window, u32 width, u32 h PropModeReplace, (u8 *)&window_type, 1); } - 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 = 2; - hints.decorations = 0; - XChangeProperty(global.x11_d, xw->window, motif_wm_hints, motif_wm_hints, 32, - PropModeReplace, (u8 *)&hints, sizeof(hints) / sizeof(long)); - } + set_decorations(xw, false); Atom net_wm_state = XInternAtom(global.x11_d, "_NET_WM_STATE", False); if (net_wm_state) { @@ -199,33 +243,12 @@ static bool window_x11_create_window(struct stl_window *window, u32 width, u32 h } } + xw->cursor_hidden = false; + XMapWindow(global.x11_d, xw->window); XFlush(global.x11_d); if (flags & STELA_WINDOW_WALLPAPER) { - Window root = DefaultRootWindow(global.x11_d); - - /* - 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[0] = _NET_WM_STATE_ADD; - event.xclient.data.l[1] = XInternAtom(global.x11_d, "_NET_WM_STATE_BELOW", False); - event.xclient.data.l[2] = 0; - event.xclient.data.l[3] = 0; - event.xclient.data.l[4] = 0; - XSendEvent(global.x11_d, root, False, SubstructureRedirectMask | SubstructureNotifyMask, &event); - - //event.xclient.data.l[1] = XInternAtom(global.x11_d, "_NET_WM_STATE_STICKY", False); - //XSendEvent(global.x11_d, root, False, SubstructureRedirectMask | SubstructureNotifyMask, &event); - */ - // Enable click-through by making the window shape 0x0. Window win = xw->window; while (win != None) { @@ -237,24 +260,24 @@ static bool window_x11_create_window(struct stl_window *window, u32 width, u32 h Window parent, *children = NULL; u32 num_children; - if (XQueryTree(global.x11_d, win, &root, &parent, &children, &num_children)) { - if (children) XFree(children); + if (XQueryTree(global.x11_d, win, &root, &parent, &children, &num_children) && children) { + XFree(children); } win = (parent == root) ? None : parent; } - // Not sure if this flush matters. XFlush(global.x11_d); } #ifdef STELA_API_OPENGL - if (!x11_egl_init(xw)) return false; -#endif - + 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; @@ -283,12 +306,58 @@ static void window_x11_resize(struct stl_window *window, u32 width, u32 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 bool window_x11_poll(struct stl_window *window, bool block) { @@ -305,6 +374,147 @@ static void window_x11_wake(struct stl_window *window) #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; @@ -323,16 +533,42 @@ static void handle_next_xevent(struct stl_window_x11 *xw) break; case KeyPress: case KeyRelease: { - u8 mapped_state = xev.type == KeyPress ? STELA_BUTTON_PRESSED : STELA_BUTTON_RELEASED; - stl_window_send_key_immediate_event(&xw->w, mapped_state, xev.xkey.keycode); - stl_window_send_key_event(&xw->w, mapped_state, xev.xkey.keycode); + 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 == Button2) { + } 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-spec/1.4/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; + event.xclient.data.l[1] = xev.xbutton.y; + event.xclient.data.l[2] = _NET_WM_MOVERESIZE_MOVE; + event.xclient.data.l[3] = 0; + // https://specifications.freedesktop.org/wm-spec/1.4/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) { @@ -342,8 +578,10 @@ static void handle_next_xevent(struct stl_window_x11 *xw) 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 == Button2) { + } 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: @@ -358,7 +596,7 @@ static void window_x11_process_events(struct stl_window *window) { struct stl_window_x11 *xw = (struct stl_window_x11 *)window; stl_process_events_common(xw->fds); - // It seems for some reason POLLIN doesn't get set even when there's events. + // It seems 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); @@ -375,12 +613,49 @@ static bool window_x11_should_refresh(struct stl_window *window) static void window_x11_free(struct stl_window **window) { struct stl_window_x11 *xw = (struct stl_window_x11 *)*window; - // @TODO: Cleanup x11 stuff. + 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_OPENGL +#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; @@ -408,7 +683,7 @@ static void window_x11_gl_swap_buffers(void *window) struct stl_window *stl_window_create(void) { - log_info("Creating X11 (EGL) window."); + 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; @@ -416,6 +691,8 @@ struct stl_window *stl_window_create(void) 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 @@ -427,7 +704,12 @@ struct stl_window *stl_window_create(void) xw->w.should_refresh = window_x11_should_refresh; #endif xw->w.free = window_x11_free; -#if defined STELA_API_OPENGL +#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; diff --git a/src/window_x11.h b/src/window_x11.h index 32749cd..b479096 100644 --- a/src/window_x11.h +++ b/src/window_x11.h @@ -5,17 +5,32 @@ #include #include -#include #include +#include +#include +#include +#include +#include #include "window.h" +#if defined STELA_API_VULKAN +#include "vk_common.h" +#elif defined STELA_API_OPENGL #include "egl_common.h" +#endif + +#define STELA_WAYLAND_KEY_MAX (KEY_MEDIA + 1) struct stl_x11_global { Display *x11_d; - Window root; + XkbDescPtr xkb_desc; + Cursor cursors[STELA_CURSOR_HIDDEN]; array(struct stl_monitor *) monitors; +#if defined STELA_API_VULKAN + const char **vk_extensions; +#elif defined STELA_API_OPENGL struct stl_egl_global egl; +#endif void (*output_discovered)(void *, struct stl_monitor *); void *userdata; }; @@ -23,7 +38,10 @@ struct stl_x11_global { struct stl_window_x11 { struct stl_window w; Window window; + bool cursor_hidden; +#if defined STELA_API_OPENGL struct stl_egl egl; +#endif #ifdef STELA_PAUSE struct nn_pollfd fds[2]; #else diff --git a/subprojects/glfw3.wrap b/subprojects/glfw3.wrap index 625b4a3..2e3e365 100644 --- a/subprojects/glfw3.wrap +++ b/subprojects/glfw3.wrap @@ -1,6 +1,6 @@ [wrap-git] -directory = glfw3-e7ea71b +directory = glfw3-8e15281 url = https://github.com/glfw/glfw.git -revision = e7ea71be039836da3a98cea55ae5569cb5eb885c +revision = 8e15281d34a8b9ee9271ccce38177a3d812456f8 depth = 1 method = cmake diff --git a/subprojects/libalabaster.wrap b/subprojects/libalabaster.wrap index 5457b48..e93a582 100644 --- a/subprojects/libalabaster.wrap +++ b/subprojects/libalabaster.wrap @@ -1,4 +1,5 @@ [wrap-git] +directory = libalabaster-ed6fc17 url = https://git.akon.city/libalabaster.git -revision = 492f4a38f7b7f32727ab678f333eff840d968d74 +revision = ed6fc176a1ef6afd43eaa198c43eb813c3a81d99 depth = 1 diff --git a/subprojects/libnaunet.wrap b/subprojects/libnaunet.wrap index 57a13ac..53670a7 100644 --- a/subprojects/libnaunet.wrap +++ b/subprojects/libnaunet.wrap @@ -1,4 +1,5 @@ [wrap-git] +directory = libnaunet-4aea807 url = https://git.akon.city/libnaunet.git -revision = ab75884371a3545f7548498549e2faebbc465ed6 +revision = 4aea807a167a894fd264718efc8c9fa8bced7dcb depth = 1 diff --git a/subprojects/packagefiles/vulkan-headers/meson.build b/subprojects/packagefiles/vulkan-headers/meson.build index 29018a8..2e9dc22 100644 --- a/subprojects/packagefiles/vulkan-headers/meson.build +++ b/subprojects/packagefiles/vulkan-headers/meson.build @@ -1,3 +1,3 @@ -project('vulkan', 'c', version: '1.4.313') +project('vulkan', 'c', version: '1.4.325') vulkan = declare_dependency(include_directories: include_directories('include')) meson.override_dependency('vulkan', vulkan) diff --git a/subprojects/vulkan.wrap b/subprojects/vulkan.wrap index 05b6b85..d8ccb21 100644 --- a/subprojects/vulkan.wrap +++ b/subprojects/vulkan.wrap @@ -1,7 +1,7 @@ [wrap-git] -directory = vulkan-headers-1.4.313 +directory = vulkan-headers-1.4.325 url = https://github.com/KhronosGroup/Vulkan-Headers.git -revision = v1.4.313 +revision = v1.4.325 depth = 1 patch_directory = vulkan-headers diff --git a/subprojects/wayland-protocols.wrap b/subprojects/wayland-protocols.wrap index 838ec24..fea39b9 100644 --- a/subprojects/wayland-protocols.wrap +++ b/subprojects/wayland-protocols.wrap @@ -1,5 +1,5 @@ [wrap-git] -directory = wayland-protocols-1.44 +directory = wayland-protocols-1.45 url = https://gitlab.freedesktop.org/wayland/wayland-protocols.git -revision = 1.44 +revision = 1.45 depth = 1 diff --git a/subprojects/wlr-protocols.wrap b/subprojects/wlr-protocols.wrap index a49790f..f3807f9 100644 --- a/subprojects/wlr-protocols.wrap +++ b/subprojects/wlr-protocols.wrap @@ -1,6 +1,6 @@ [wrap-git] -directory = wlr-protocols-2ec67eb +directory = wlr-protocols-92feabd url = https://gitlab.freedesktop.org/wlroots/wlr-protocols.git -revision = 2ec67ebd26b73bada12f3fa6afdd51563b656722 +revision = 92feabdfd77ab141ff76c3a9ce327dcbb8e57b1c depth = 1 patch_directory = wlr-protocols -- cgit v1.2.3-101-g0448