diff options
| author | 2025-06-23 17:35:34 -0400 | |
|---|---|---|
| committer | 2025-06-23 17:35:34 -0400 | |
| commit | 01637283b33d7fe4921e9347ada867e44b29edba (patch) | |
| tree | bda468becec64b5d2930c80910905c567020851a | |
| parent | 234e3cb390c7bb149ddd281d695d81a368e1a441 (diff) | |
| download | stela-01637283b33d7fe4921e9347ada867e44b29edba.tar.gz stela-01637283b33d7fe4921e9347ada867e44b29edba.tar.bz2 stela-01637283b33d7fe4921e9347ada867e44b29edba.zip | |
Internally track modifier keys, Wayland Vulkan
- Add set_cursor().
- Various Wayland cleanups.
Signed-off-by: Andrew Opalach <andrew@akon.city>
| -rw-r--r-- | src/egl_common.c | 8 | ||||
| -rw-r--r-- | src/gl_versions.h | 25 | ||||
| -rw-r--r-- | src/keys.h | 61 | ||||
| -rw-r--r-- | src/poll_common.c | 4 | ||||
| -rw-r--r-- | src/poll_common.h | 2 | ||||
| -rw-r--r-- | src/window.c | 61 | ||||
| -rw-r--r-- | src/window.h | 15 | ||||
| -rw-r--r-- | src/window_glfw.c | 20 | ||||
| -rw-r--r-- | src/window_wayland.c | 306 | ||||
| -rw-r--r-- | src/window_wayland.h | 28 | ||||
| -rw-r--r-- | src/window_win32.c | 160 | ||||
| -rw-r--r-- | src/window_win32.h | 1 | ||||
| -rw-r--r-- | src/window_x11.c | 8 | ||||
| -rw-r--r-- | subprojects/libalabaster.wrap | 2 | ||||
| -rw-r--r-- | subprojects/libnaunet.wrap | 2 | ||||
| -rw-r--r-- | tests/window.c | 4 |
16 files changed, 496 insertions, 211 deletions
diff --git a/src/egl_common.c b/src/egl_common.c index 7ed6a62..606cc67 100644 --- a/src/egl_common.c +++ b/src/egl_common.c @@ -57,7 +57,9 @@ bool stl_load_egl(struct stl_egl_global *global) void stl_terminate_egl(struct stl_egl_global *global) { if (global->display != EGL_NO_DISPLAY) { - eglTerminate(global->display); + if (!eglTerminate(global->display)) { + log_warn("Failed to eglTerminate()."); + } } } @@ -69,9 +71,7 @@ static EGLint match_config_to_visual(EGLDisplay display, EGLint visual_id, EGLCo if (!eglGetConfigAttrib(display, configs[i], EGL_NATIVE_VISUAL_ID, &id)) { continue; } - if (id == visual_id) { - return i; - } + if (id == visual_id) return i; } return -1; } diff --git a/src/gl_versions.h b/src/gl_versions.h index e6c6ab2..0182317 100644 --- a/src/gl_versions.h +++ b/src/gl_versions.h @@ -59,28 +59,3 @@ }; #endif #endif - -/* -#define PASTE_GL_VERSIONS() static struct { \ - s32 api; \ - s32 type; \ - s32 major, minor; \ - s32 glsl_ver; \ - s32 profile; \ -} stl_gl_vers[] = { \ - { STELA_GL_API, STELA_GL_BIT, 4, 6, 460, STELA_GL_CORE_PROFILE_BIT }, \ - { STELA_GL_API, STELA_GL_BIT, 4, 5, 450, STELA_GL_CORE_PROFILE_BIT }, \ - { STELA_GL_API, STELA_GL_BIT, 4, 4, 440, STELA_GL_CORE_PROFILE_BIT }, \ - { STELA_GL_API, STELA_GL_BIT, 4, 0, 400, STELA_GL_CORE_PROFILE_BIT }, \ - { STELA_GL_API, STELA_GL_BIT, 3, 3, 330, STELA_GL_CORE_PROFILE_BIT }, \ - { STELA_GL_API, STELA_GL_BIT, 3, 2, 150, STELA_GL_CORE_PROFILE_BIT }, \ - { STELA_GLES_API, STELA_GLES3_BIT, 3, 2, 320, 0 }, \ - { STELA_GL_API, STELA_GL_BIT, 3, 1, 140, 0 }, \ - { STELA_GLES_API, STELA_GLES3_BIT, 3, 1, 310, 0 }, \ - { STELA_GL_API, STELA_GL_BIT, 3, 0, 130, 0 }, \ - { STELA_GLES_API, STELA_GLES3_BIT, 3, 0, 300, 0 }, \ - { STELA_GLES_API, STELA_GLES2_BIT, 2, 0, 100, 0 }, \ - { STELA_GL_API, STELA_GL_BIT, 2, 1, 120, 0 }, \ - { STELA_GL_API, STELA_GL_BIT, 2, 0, 110, 0 } \ -}; -*/ @@ -1,6 +1,39 @@ #pragma once -// https://github.com/glfw/glfw/blob/e7ea71be039836da3a98cea55ae5569cb5eb885c/include/STELA/glfw3.h#L395C1-L519C50 +#include <al/types.h> +#include <al/lib.h> + +// Expected behavior of modifiers: +// - Window keeps an internal state of all currently held modifiers. +// - On window focus enter, if modifier is held, send BUTTON_PRESSED event. +// - If a modifier was held on enter, it must still receive a BUTTON_RELEASED +// event when the key is released. +// - On window focus exit, send BUTTON_RELEASED for every active modifier. +enum { + STELA_MOD_NONE = 0, + STELA_MOD_LCONTROL = 1, + STELA_MOD_RCONTROL = 1 << 1, + STELA_MOD_LSHIFT = 1 << 2, + STELA_MOD_RSHIFT = 1 << 3, + STELA_MOD_LALT = 1 << 4, + STELA_MOD_RALT = 1 << 5 +}; + +AL_IGNORE_WARNING("-Wunused-variable") + +static u32 stl_all_modifiers[] = { + STELA_MOD_LCONTROL, STELA_MOD_RCONTROL, + STELA_MOD_LSHIFT, STELA_MOD_RSHIFT, + STELA_MOD_LALT, STELA_MOD_RALT, + STELA_MOD_NONE +}; + +AL_IGNORE_WARNING_END + +// https://github.com/glfw/glfw/blob/e7ea71be039836da3a98cea55ae5569cb5eb885c/include/GLFW/glfw3.h#L395C1-L519C50 +// See window_glfw.c:key_callback() for ommited keys. + +#define STELA_KEY_NONE 0 /* Printable keys */ #define STELA_KEY_0 48 @@ -129,3 +162,29 @@ #define STELA_KEY_MENU 348 #define STELA_KEY_LAST STELA_KEY_MENU + +static inline u16 stl_mod_to_key(u32 mod) +{ + switch (mod) { + case STELA_MOD_LCONTROL: return STELA_KEY_LEFT_CONTROL; + case STELA_MOD_RCONTROL: return STELA_KEY_RIGHT_CONTROL; + case STELA_MOD_LSHIFT: return STELA_KEY_LEFT_SHIFT; + case STELA_MOD_RSHIFT: return STELA_KEY_RIGHT_SHIFT; + case STELA_MOD_LALT: return STELA_KEY_LEFT_ALT; + case STELA_MOD_RALT: return STELA_KEY_RIGHT_ALT; + } + al_assert_and_return(STELA_KEY_NONE); +} + +static inline u32 stl_key_to_mod(u16 key) +{ + switch (key) { + case STELA_KEY_LEFT_CONTROL: return STELA_MOD_LCONTROL; + case STELA_KEY_RIGHT_CONTROL: return STELA_MOD_RCONTROL; + case STELA_KEY_LEFT_SHIFT: return STELA_MOD_LSHIFT; + case STELA_KEY_RIGHT_SHIFT: return STELA_MOD_RSHIFT; + case STELA_KEY_LEFT_ALT: return STELA_MOD_LALT; + case STELA_KEY_RIGHT_ALT: return STELA_MOD_RALT; + } + return STELA_MOD_NONE; +} diff --git a/src/poll_common.c b/src/poll_common.c index a79e0de..c5d43c4 100644 --- a/src/poll_common.c +++ b/src/poll_common.c @@ -5,7 +5,7 @@ #include "poll_common.h" #ifdef STELA_POLL_INLINE -bool stl_poll_common(struct nn_pollfd *fds, bool block) +bool stl_poll_common(struct stl_window *window, struct nn_pollfd *fds, bool block) { fds[0].revents = 0; #ifdef STELA_PAUSE @@ -30,7 +30,7 @@ bool stl_poll_common(struct nn_pollfd *fds, bool block) return false; } - return true; + return !window->closed; } #ifdef STELA_PAUSE void stl_wake_common(struct stl_window *window, struct nn_pollfd *fds) diff --git a/src/poll_common.h b/src/poll_common.h index 0efa500..3ad6d5e 100644 --- a/src/poll_common.h +++ b/src/poll_common.h @@ -5,7 +5,7 @@ #include "window.h" #ifdef STELA_POLL_INLINE -bool stl_poll_common(struct nn_pollfd *fds, bool block); +bool stl_poll_common(struct stl_window *window, struct nn_pollfd *fds, bool block); #ifdef STELA_PAUSE void stl_wake_common(struct stl_window *window, struct nn_pollfd *fds); #endif diff --git a/src/window.c b/src/window.c index 08dc625..034f401 100644 --- a/src/window.c +++ b/src/window.c @@ -2,6 +2,7 @@ #include <al/log.h> #include "window.h" +#include "keys.h" static bool nop_pointer_pos_callback(void *userdata, f64 x, f64 y) { @@ -44,6 +45,18 @@ static bool nop_key_callback(void *userdata, u8 state, u16 button) return false; } +static void nop_key_immediate_callback(void *userdata, u8 state, u16 button) +{ + (void)userdata; + (void)state; + (void)button; +} + +static void window_default_close(struct stl_window *window) +{ + window->closed = true; +} + void stl_window_init(struct stl_window *window) { window->pointer_pos_callback = nop_pointer_pos_callback; @@ -51,9 +64,13 @@ void stl_window_init(struct stl_window *window) window->scroll_callback = nop_scroll_callback; window->mouse_button_callback = nop_mouse_button_callback; window->key_callback = nop_key_callback; + window->key_immediate_callback = nop_key_immediate_callback; + window->close = window_default_close; #ifdef STELA_EVENT_BUFFER al_ring_buffer_init(&window->events, window->data, STELA_EVENTS_SIZE); #endif + window->closed = false; + window->held_modifiers = 0; #ifdef STELA_PAUSE window->pending_refresh = true; #endif @@ -164,8 +181,26 @@ void stl_window_send_mouse_button_event(struct stl_window *window, u8 state, u8 #endif } +#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. +#define STELA_CORRECT_MODIFIER_INCONSISTENCY +#endif + void stl_window_send_key_event(struct stl_window *window, u8 state, u16 button) { + u32 mod = stl_key_to_mod(button); +#ifdef STELA_CORRECT_MODIFIER_INCONSISTENCY + if (state == STELA_BUTTON_PRESSED) { + if (window->held_modifiers & mod) { + window->held_modifiers &= ~mod; + window->key_callback(window->userdata, STELA_BUTTON_RELEASED, button); + } + } else { + if (!(window->held_modifiers & mod)) return; + } +#endif + window->held_modifiers = (window->held_modifiers & ~mod) | (mod * (state == STELA_BUTTON_PRESSED)); #ifdef STELA_EVENT_BUFFER GET_EVENT_CHUNK(STELA_EVENT_KEY); ptr[1] = state; @@ -181,9 +216,20 @@ 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) { - if (window->key_immediate_callback) { - window->key_immediate_callback(window->userdata, state, button); + // Don't send modifiers on key immediate. + window->key_immediate_callback(window->userdata, state, button * (stl_key_to_mod(button) == STELA_MOD_NONE)); +} + +void stl_window_drop_modifiers(struct stl_window *window) +{ + u32 *mods = stl_all_modifiers; + while (window->held_modifiers) { + u32 mod = *mods++; + if (window->held_modifiers & mod) { + stl_window_send_key_event(window, STELA_BUTTON_RELEASED, stl_mod_to_key(mod)); + } } + al_assert(!window->held_modifiers); } void stl_window_send_should_close_event(struct stl_window *window) @@ -230,6 +276,17 @@ bool stl_window_read_events(struct stl_window *window) window->pointer_pos_callback(window->userdata, x, y); break; } + case STELA_EVENT_TOUCH: { + s32 index = ((s32 *)(&bc[1]))[0]; + u8 phase = bc[5]; + f64 x = ((f64 *)(&bc[6]))[0]; + f64 y = ((f64 *)(&bc[14]))[0]; +#ifdef STELA_PAUSE + window->pending_refresh |= +#endif + window->touch_callback(window->userdata, index, phase, x, y); + break; + } case STELA_EVENT_SCROLL: { f64 y = ((f64 *)(&bc[1]))[0]; #ifdef STELA_PAUSE diff --git a/src/window.h b/src/window.h index cd4e81f..b1b2425 100644 --- a/src/window.h +++ b/src/window.h @@ -22,8 +22,10 @@ #endif #if defined STELA_API_VULKAN -#ifdef NAUNET_ON_WINDOWS +#if defined STELA_WINDOW_WIN32 #define VK_USE_PLATFORM_WIN32_KHR +#elif defined STELA_WINDOW_WAYLAND +#define VK_USE_PLATFORM_WAYLAND_KHR #endif #include <vulkan/vulkan.h> #elif defined STELA_API_DX11 @@ -45,6 +47,12 @@ enum { STELA_WINDOW_WALLPAPER = 1 << 1 }; +enum { + STELA_CURSOR_NORMAL = 0, + STELA_CURSOR_CROSSHAIR, + STELA_CURSOR_HIDDEN +}; + #define STELA_MAX_MONITOR_NAME 255u struct stl_monitor { @@ -71,6 +79,7 @@ struct stl_window { void (*resize_internal)(struct stl_window *, u32, u32); void (*resize)(struct stl_window *, u32, u32); void (*toggle_fullscreen)(struct stl_window *); + void (*set_cursor)(struct stl_window *window, u8); void (*set_decorations)(struct stl_window *, bool); #ifdef STELA_POLL_INLINE bool (*poll)(struct stl_window *, bool); @@ -82,6 +91,7 @@ struct stl_window { #ifdef STELA_PAUSE bool (*should_refresh)(struct stl_window *); #endif + void (*close)(struct stl_window *window); void (*free)(struct stl_window **); // Context specific. #if defined STELA_API_VULKAN @@ -114,6 +124,8 @@ struct stl_window { void (*key_immediate_callback)(void *, u8, u16); void (*should_close_callback)(void *); void *userdata; + bool closed; + u32 held_modifiers; #ifdef STELA_PAUSE bool pending_refresh; #endif @@ -174,6 +186,7 @@ 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_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); void stl_window_send_should_close_event(struct stl_window *window); #ifdef STELA_EVENT_BUFFER bool stl_window_read_events(struct stl_window *window); diff --git a/src/window_glfw.c b/src/window_glfw.c index b7b3b30..0669bd4 100644 --- a/src/window_glfw.c +++ b/src/window_glfw.c @@ -107,6 +107,16 @@ static void cursor_position_callback(GLFWwindow *window, f64 xpos, f64 ypos) stl_window_send_pointer_pos_event(&glfw->w, xpos, ypos); } +// @TODO: Test behavior on x11, Windows? +static void cursor_enter_callback(GLFWwindow *window, s32 entered) +{ + struct stl_window_glfw *glfw = glfwGetWindowUserPointer(window); + if (entered) { + if (glfwGetKey(glfw->window, GLFW_KEY_LEFT_CONTROL)) { + } + } +} + static void scroll_callback(GLFWwindow *window, f64 xoffset, f64 yoffset) { struct stl_window_glfw *glfw = glfwGetWindowUserPointer(window); @@ -153,9 +163,9 @@ static void key_callback(GLFWwindow *window, s32 key, s32 scancode, s32 action, log_warn("Unmapped GLFW key pressed (%hu).", (u16)key); return; } - u8 key_state = action == GLFW_PRESS ? STELA_BUTTON_PRESSED : STELA_BUTTON_RELEASED; - stl_window_send_key_immediate_event(&glfw->w, key_state, (u16)key); - stl_window_send_key_event(&glfw->w, key_state, (u16)key); + u8 mapped_state = action == GLFW_PRESS ? STELA_BUTTON_PRESSED : STELA_BUTTON_RELEASED; + stl_window_send_key_immediate_event(&glfw->w, mapped_state, (u16)key); + stl_window_send_key_event(&glfw->w, mapped_state, (u16)key); } static void close_callback(GLFWwindow *window) @@ -170,8 +180,6 @@ static bool window_glfw_create_window(struct stl_window *window, u32 width, u32 struct stl_window_glfw *glfw = (struct stl_window_glfw *)window; (void)monitor; - al_assert(width >= STELA_MIN_WIDTH && height >= STELA_MIN_HEIGHT); - #if GLFW_VERSION_MAJOR > 3 || (GLFW_VERSION_MAJOR == 3 && GLFW_VERSION_MINOR >= 4) glfwWindowHintString(GLFW_WAYLAND_APP_ID, name); #endif @@ -205,6 +213,7 @@ static bool window_glfw_create_window(struct stl_window *window, u32 width, u32 stl_gl_vers[i].api == STELA_GL_API ? "GL" : "GLES", stl_gl_vers[i].major, stl_gl_vers[i].minor, stl_gl_vers[i].glsl_ver); #endif + al_assert(width >= STELA_MIN_WIDTH && height >= STELA_MIN_HEIGHT); glfw->window = glfwCreateWindow(width, height, name, NULL, global.master_window); #ifdef STELA_API_OPENGL if (glfw->window) break; @@ -258,6 +267,7 @@ static bool window_glfw_create_window(struct stl_window *window, u32 width, u32 glfwSetWindowSizeCallback(glfw->window, resize_callback); glfwSetWindowContentScaleCallback(glfw->window, content_scale_callback); glfwSetCursorPosCallback(glfw->window, cursor_position_callback); + glfwSetCursorEnterCallback(glfw->window, cursor_enter_callback); glfwSetScrollCallback(glfw->window, scroll_callback); glfwSetMouseButtonCallback(glfw->window, mouse_button_callback); glfwSetKeyCallback(glfw->window, key_callback); diff --git a/src/window_wayland.c b/src/window_wayland.c index 9479c70..ccf911c 100644 --- a/src/window_wayland.c +++ b/src/window_wayland.c @@ -1,12 +1,17 @@ #define AL_LOG_SECTION "wayland" #include <al/log.h> #include <al/lib.h> +#ifdef STELA_API_VULKAN +#include <dlfcn.h> +#endif #include "window_wayland.h" #include "common.h" #include "poll_common.h" #include "keys.h" +#define FRAME_NO_VALUE -DBL_MAX + static struct stl_wayland_global global = { 0 }; static void handle_output_geometry(void *data, struct wl_output *output, @@ -104,7 +109,7 @@ static void global_add(void *data, struct wl_registry *registry, u32 name, if (strcmp(interface, wl_output_interface.name) == 0) { struct stl_monitor_wayland *monitor = al_alloc_object(struct stl_monitor_wayland); al_array_push(global.monitors, (struct stl_monitor *)monitor); - monitor->output = wl_registry_bind(registry, name, &wl_output_interface, MIN(4u, version)); + monitor->output = (struct wl_output *)wl_registry_bind(registry, name, &wl_output_interface, MIN(4u, version)); wl_output_add_listener(monitor->output, &output_listener, monitor); } } @@ -266,7 +271,11 @@ bool stl_global_init(bool skip_graphics) global.display_fd = wl_display_get_fd(global.display); 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_wayland_surface"; +#elif defined STELA_API_OPENGL if (!stl_maybe_load_egl()) return false; global.egl.display = eglGetDisplay((EGLNativeDisplayType)global.display); if (!global.egl.display) { @@ -276,14 +285,14 @@ bool stl_global_init(bool skip_graphics) if (!stl_load_egl(&global.egl)) return false; #endif } else { -#ifdef STELA_API_OPENGL // So global_close() knows we skipped graphics. +#if defined STELA_API_VULKAN + global.vk_extensions = NULL; +#elif defined STELA_API_OPENGL global.egl.display = EGL_NO_DISPLAY; #endif } - global.active_surface = NULL; - al_array_init(global.monitors); global.registry = wl_display_get_registry(global.display); wl_registry_add_listener(global.registry, ®istry_listener, NULL); @@ -323,7 +332,9 @@ s32 stl_swallow_main(u32 argc, str *argv, s32 (*main)(u32, str *, void *), void void stl_global_close(void) { if (!global.display) 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 for (u32 i = 0; i < global.monitors.count; i++) { @@ -345,28 +356,50 @@ static const struct xdg_wm_base_listener xdg_wm_base_listener = { .ping = xdg_wm_base_ping }; +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_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: + wl_pointer_set_cursor(wl->cursor.pointer, wl->cursor.serial, NULL, 0, 0); + break; + } +} + 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)pointer; - global.active_surface = surface; - // @TODO: Hide cursor. - //wl_pointer_set_cursor(wl->pointer, serial, NULL, 0, 0); - if (wl->cursor_shape_device) { - wp_cursor_shape_device_v1_set_shape(wl->cursor_shape_device, serial, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT); - } - wl->pointer_x = wl_fixed_to_double(surface_x) * wl->w.scale; - wl->pointer_y = wl_fixed_to_double(surface_y) * wl->w.scale; + (void)surface; + al_assert(pointer == wl->pointer); + wl->cursor.serial = serial; + wl->cursor.pointer = pointer; + 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; } static void pointer_leave(void *data, struct wl_pointer *pointer, u32 serial, struct wl_surface *surface) { - (void)data; - (void)pointer; - (void)serial; + struct stl_window_wayland *wl = (struct stl_window_wayland *)data; (void)surface; + al_assert(pointer == wl->pointer); + wl->cursor.serial = serial; + wl->cursor.pointer = NULL; + wl->frame.pointer_x = FRAME_NO_VALUE; + wl->frame.pointer_y = FRAME_NO_VALUE; } static void pointer_motion(void *data, struct wl_pointer *pointer, @@ -375,13 +408,8 @@ static void pointer_motion(void *data, struct wl_pointer *pointer, struct stl_window_wayland *wl = (struct stl_window_wayland *)data; (void)pointer; (void)time; - - if (wl->surface != global.active_surface) { - return; - } - - wl->pointer_x += wl_fixed_to_double(surface_x) * wl->w.scale; - wl->pointer_y += wl_fixed_to_double(surface_y) * wl->w.scale; + 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; } static void pointer_button(void *data, struct wl_pointer *pointer, @@ -390,11 +418,6 @@ static void pointer_button(void *data, struct wl_pointer *pointer, struct stl_window_wayland *wl = (struct stl_window_wayland *)data; (void)pointer; (void)time; - - if (wl->surface != global.active_surface) { - return; - } - #define MOUSE1 0x110 #define MOUSE2 0x111 #define MOUSE3 0x112 @@ -433,15 +456,12 @@ static void pointer_axis(void *data, struct wl_pointer *pointer, struct stl_window_wayland *wl = (struct stl_window_wayland *)data; (void)pointer; (void)time; - - if (wl->surface != global.active_surface) { - return; - } - if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { - wl->scroll_y += wl_fixed_to_double(value); + if (wl->frame.scroll_y == FRAME_NO_VALUE) wl->frame.scroll_y = 0.0; + wl->frame.scroll_y += wl_fixed_to_double(value); } else if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) { - wl->scroll_x += wl_fixed_to_double(value); + if (wl->frame.scroll_x == FRAME_NO_VALUE) wl->frame.scroll_x = 0.0; + wl->frame.scroll_x += wl_fixed_to_double(value); } } @@ -449,20 +469,16 @@ static void pointer_frame(void *data, struct wl_pointer *pointer) { struct stl_window_wayland *wl = (struct stl_window_wayland *)data; (void)pointer; - - if (wl->surface != global.active_surface) { - return; + if (wl->frame.pointer_x != FRAME_NO_VALUE && wl->frame.pointer_y != FRAME_NO_VALUE) { + stl_window_send_pointer_pos_event(&wl->w, wl->frame.pointer_x, wl->frame.pointer_y); + wl->frame.pointer_x = FRAME_NO_VALUE; + wl->frame.pointer_y = FRAME_NO_VALUE; } - - if (wl->pointer_x || wl->pointer_y) { - stl_window_send_pointer_pos_event(&wl->w, wl->pointer_x, wl->pointer_y); - wl->pointer_x = 0.0; - wl->pointer_y = 0.0; - } - - if (wl->scroll_y) { - stl_window_send_scroll_event(&wl->w, wl->scroll_y); - wl->scroll_y = 0.0; + // If multiple pointer_axis() events can happen within a frame and + // resolve to 0.0, this will send a scroll event with y = 0.0. + if (wl->frame.scroll_y != FRAME_NO_VALUE) { + stl_window_send_scroll_event(&wl->w, wl->frame.scroll_y); + wl->frame.scroll_y = FRAME_NO_VALUE; } } @@ -539,37 +555,43 @@ static void keyboard_keymap(void *data, struct wl_keyboard *keyboard, static void keyboard_enter(void *data, struct wl_keyboard *keyboard, u32 serial, struct wl_surface *surface, struct wl_array *keys) { - (void)data; + struct stl_window_wayland *wl = (struct stl_window_wayland *)data; (void)keyboard; (void)serial; - (void)keys; - global.active_surface = surface; + (void)surface; + void *key; + wl_array_for_each(key, keys) { + u16 mapped_key = global.keycodes[*(u32 *)key]; + u32 mod = stl_key_to_mod(mapped_key); + if (mod == STELA_MOD_NONE) continue; + stl_window_send_key_event(&wl->w, STELA_BUTTON_PRESSED, mapped_key); + } } static void keyboard_leave(void *data, struct wl_keyboard *keyboard, u32 serial, struct wl_surface *surface) { - (void)data; + struct stl_window_wayland *wl = (struct stl_window_wayland *)data; (void)keyboard; (void)serial; (void)surface; + 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) { struct stl_window_wayland *wl = (struct stl_window_wayland *)data; - (void)serial; (void)keyboard; + (void)serial; (void)time; - - if (wl->surface != global.active_surface) { - return; - } - - u8 key_state = state == WL_KEYBOARD_KEY_STATE_PRESSED ? STELA_BUTTON_PRESSED : STELA_BUTTON_RELEASED; - stl_window_send_key_immediate_event(&wl->w, key_state, global.keycodes[key]); - stl_window_send_key_event(&wl->w, key_state, global.keycodes[key]); + u8 mapped_state = WL_TO_STELA_STATE(state); + 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); } static void keyboard_modifiers(void *data, struct wl_keyboard *keyboard, @@ -609,8 +631,8 @@ static void seat_capabilities(void *data, struct wl_seat *seat, u32 capabilities if (capabilities & WL_SEAT_CAPABILITY_POINTER) { if (!wl->pointer) { wl->pointer = wl_seat_get_pointer(wl->seat); - if (wl->cursor_shape) { - wl->cursor_shape_device = wp_cursor_shape_manager_v1_get_pointer(wl->cursor_shape, wl->pointer); + if (wl->cursor_shape_manager) { + wl->cursor_shape_device = wp_cursor_shape_manager_v1_get_pointer(wl->cursor_shape_manager, wl->pointer); } wl_pointer_add_listener(wl->pointer, &pointer_listener, wl); } @@ -651,19 +673,19 @@ static void context_global_add(void *data, struct wl_registry *registry, u32 nam { struct stl_window_wayland *wl = (struct stl_window_wayland *)data; if (strcmp(interface, wl_compositor_interface.name) == 0) { - wl->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, MIN(5u, version)); + wl->compositor = (struct wl_compositor *)wl_registry_bind(registry, name, &wl_compositor_interface, MIN(5u, version)); } else if (strcmp(interface, wl_seat_interface.name) == 0) { - wl->seat = wl_registry_bind(registry, name, &wl_seat_interface, MIN(8u, version)); + wl->seat = (struct wl_seat *)wl_registry_bind(registry, name, &wl_seat_interface, MIN(8u, version)); wl_seat_add_listener(wl->seat, &seat_listener, wl); } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { - wl->xdg_wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, MIN(2u, version)); + wl->xdg_wm_base = (struct xdg_wm_base *)wl_registry_bind(registry, name, &xdg_wm_base_interface, MIN(2u, version)); xdg_wm_base_add_listener(wl->xdg_wm_base, &xdg_wm_base_listener, wl); } else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) { - wl->zxdg_decoration_manager = wl_registry_bind(registry, name, &zxdg_decoration_manager_v1_interface, MIN(1u, version)); + wl->zxdg_decoration_manager = (struct zxdg_decoration_manager_v1 *)wl_registry_bind(registry, name, &zxdg_decoration_manager_v1_interface, MIN(1u, version)); } else if (strcmp(interface, wp_cursor_shape_manager_v1_interface.name) == 0) { - wl->cursor_shape = wl_registry_bind(registry, name, &wp_cursor_shape_manager_v1_interface, MIN(1u, version)); + wl->cursor_shape_manager = (struct wp_cursor_shape_manager_v1 *)wl_registry_bind(registry, name, &wp_cursor_shape_manager_v1_interface, MIN(1u, version)); } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { - wl->layer_shell = wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, MIN(4u, version)); + wl->layer_shell = (struct zwlr_layer_shell_v1 *)wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, MIN(4u, version)); } } @@ -733,6 +755,8 @@ static bool check_and_set_dimensions(struct stl_window_wayland *wl, s32 event_wi wl->w.width = width; wl->w.height = height; + wl->geom_width = event_width; + wl->geom_height = event_height; return true; } @@ -745,15 +769,16 @@ static void send_resize_event(struct stl_window_wayland *wl) static void handle_xdg_surface_configure(void *data, struct xdg_surface *surface, u32 serial) { struct stl_window_wayland *wl = (struct stl_window_wayland *)data; - if (wl->pending) { + al_assert(surface == wl->xdg_surface); + if (wl->pending_configure) { send_resize_event(wl); - wl->pending = false; + wl->pending_configure = false; } - xdg_surface_ack_configure(surface, serial); + xdg_surface_ack_configure(wl->xdg_surface, serial); } static const struct xdg_surface_listener xdg_surface_listener = { - .configure = handle_xdg_surface_configure, + .configure = handle_xdg_surface_configure }; static void handle_xdg_toplevel_configure(void *data, struct xdg_toplevel *toplevel, @@ -765,8 +790,10 @@ static void handle_xdg_toplevel_configure(void *data, struct xdg_toplevel *tople // It is expected that the first toplevel_configure() will give 0x0 dimensions. // We set the window dimensions with wl_egl_window_create() and the subsequent // toplevel_configure() event will come after the first swap_buffers(). + al_assert(!wl->pending_configure); if (check_and_set_dimensions(wl, width, height)) { - wl->pending = true; + xdg_surface_set_window_geometry(wl->xdg_surface, 0, 0, wl->geom_width, wl->geom_height); + wl->pending_configure = true; } } @@ -804,10 +831,11 @@ static void handle_layer_surface_configure(void *data, struct zwlr_layer_surface u32 serial, u32 width, u32 height) { struct stl_window_wayland *wl = (struct stl_window_wayland *)data; + al_assert(surface == wl->layer_surface); if (check_and_set_dimensions(wl, width, height)) { send_resize_event(wl); } - zwlr_layer_surface_v1_ack_configure(surface, serial); + zwlr_layer_surface_v1_ack_configure(wl->layer_surface, serial); } static void handle_layer_surface_closed(void *data, struct zwlr_layer_surface_v1 *surface) @@ -880,10 +908,11 @@ static bool create_toplevel(struct stl_window_wayland *wl, s32 scale, const char { wl->w.scale = scale; wl_surface_set_buffer_scale(wl->surface, scale); - //s32 geom_width = wl->w.width, geom_height = wl->w.height; + wl->geom_width = wl->w.width; + wl->geom_height = wl->w.height; wl->w.width *= scale; wl->w.height *= scale; - wl->pending = true; + wl->pending_configure = false; wl->xdg_surface = xdg_wm_base_get_xdg_surface(wl->xdg_wm_base, wl->surface); if (!wl->xdg_surface) { @@ -903,8 +932,13 @@ static bool create_toplevel(struct stl_window_wayland *wl, s32 scale, const char // If xdg_surface_set_window_geometry() is never set, it will be equal to the // entire surface and update on every commit. If we do set it, we need to manually // update it on any resize. Also, it's meant to be set to the unscaled width and height. - //xdg_surface_set_window_geometry(wl->xdg_surface, 0, 0, geom_width, geom_height); + // For our application, if using the event buffer, window events and rendering are + // on seperate threads. This means we have to manually control xdg surface geometry + // for responsiveness. Though there's currently a bug where toggling fullscreen + // 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; if (wl->zxdg_decoration_manager) { wl->zxdg_toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration( wl->zxdg_decoration_manager, wl->xdg_toplevel); @@ -935,6 +969,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->decorated = false; return true; } @@ -944,19 +979,21 @@ static bool window_wayland_create_window(struct stl_window *window, u32 width, u { struct stl_window_wayland *wl = (struct stl_window_wayland *)window; - al_assert(width >= STELA_MIN_WIDTH && height >= STELA_MIN_HEIGHT); - if (!create_context_internal(wl)) { goto err; } wl->surface = wl_compositor_create_surface(wl->compositor); - if (!wl->surface) goto err; + if (!wl->surface) { + log_error("Failed to create surface."); + goto err; + } wl_surface_add_listener(wl->surface, &surface_listener, wl); + al_assert(width >= STELA_MIN_WIDTH && height >= STELA_MIN_HEIGHT); stl_window_created(&wl->w, width, height, flags); - // @TODO: Test multi-monitor with 1 scaled. + // @TODO: Test multi-monitor with only 1 scaled. struct stl_monitor_wayland *selected = NULL; if (!(flags & STELA_WINDOW_WALLPAPER)) { selected = (struct stl_monitor_wayland *)al_array_at(global.monitors, 0); @@ -973,22 +1010,23 @@ static bool window_wayland_create_window(struct stl_window *window, u32 width, u } #ifdef STELA_API_OPENGL - if (!wl_egl_init(wl)) goto err; -#endif - - wl_surface_commit(wl->surface); - wl_display_roundtrip(wl->display); - -#ifdef STELA_API_OPENGL + if (!wl_egl_init(wl)) { + goto err; + } stl_egl_make_current(&wl->egl); stl_egl_swap_interval(&wl->egl, flags & STELA_WINDOW_VSYNC ? 1 : 0); stl_egl_release_current(&wl->egl); #endif - wl->pointer_x = 0.0; - wl->pointer_y = 0.0; - wl->scroll_x = 0.0; - wl->scroll_y = 0.0; + wl->cursor.serial = 0; + wl->cursor.pointer = NULL; + wl->frame.pointer_x = FRAME_NO_VALUE; + wl->frame.pointer_y = FRAME_NO_VALUE; + wl->frame.scroll_x = FRAME_NO_VALUE; + wl->frame.scroll_y = FRAME_NO_VALUE; + + wl_surface_commit(wl->surface); + wl_display_roundtrip(wl->display); return true; err: @@ -1009,11 +1047,6 @@ static void window_wayland_resize_internal(struct stl_window *window, u32 width, { struct stl_window_wayland *wl = (struct stl_window_wayland *)window; #ifdef STELA_API_OPENGL - //if (wl->xdg_surface) { - // u32 scale = wl->w.scale; - // s32 geom_width = wl->w.width / scale, geom_height = wl->w.height / scale; - // xdg_surface_set_window_geometry(wl->xdg_surface, 0, 0, geom_width, geom_height); - //} if (stl_egl_is_ready(&wl->egl)) { wl_egl_window_resize(wl->egl_window, width, height, 0, 0); } @@ -1027,12 +1060,21 @@ static void window_wayland_resize_internal(struct stl_window *window, u32 width, static void window_wayland_toggle_fullscreen(struct stl_window *window) { struct stl_window_wayland *wl = (struct stl_window_wayland *)window; - if (!wl->w.fullscreen) { + wl->w.fullscreen = !wl->w.fullscreen; + if (wl->w.fullscreen) { xdg_toplevel_set_fullscreen(wl->xdg_toplevel, NULL); } else { xdg_toplevel_unset_fullscreen(wl->xdg_toplevel); } - wl->w.fullscreen = !wl->w.fullscreen; +} + +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; + if (wl->cursor.pointer) { + set_cursor_internal(wl, wl->cursor_shape); + } } static void window_wayland_set_decorations(struct stl_window *window, bool enabled) @@ -1055,7 +1097,7 @@ static bool window_wayland_poll(struct stl_window *window, bool block) { struct stl_window_wayland *wl = (struct stl_window_wayland *)window; display_prepare_read(wl->display); - return stl_poll_common(wl->fds, block); + return stl_poll_common(&wl->w, wl->fds, block); } #ifdef STELA_PAUSE @@ -1110,7 +1152,7 @@ void window_wayland_free(struct stl_window **window) if (wl->registry) wl_registry_destroy(wl->registry); if (wl->seat) wl_seat_destroy(wl->seat); if (wl->pointer) wl_pointer_destroy(wl->pointer); - if (wl->cursor_shape) wp_cursor_shape_manager_v1_destroy(wl->cursor_shape); + if (wl->cursor_shape_manager) wp_cursor_shape_manager_v1_destroy(wl->cursor_shape_manager); if (wl->cursor_shape_device) wp_cursor_shape_device_v1_destroy(wl->cursor_shape_device); if (wl->keyboard) wl_keyboard_destroy(wl->keyboard); destroy_surface_internal(wl); @@ -1128,34 +1170,53 @@ 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) { - void *vk_module = dlopen("libvulkan.so.1", RTLD_LAZY | RTLD_LOCAL); - if (!vk_module) return NULL; - PFN_vkVoidFunction vk_get_proc_addr = dlsym(vk_module, "vkGetInstanceProcAddr"); + 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"); + 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_win32_get_vk_proc_address(VkInstance inst, const char *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)vk_get_proc_addr; + return (PFN_vkVoidFunction)get_vk_proc_address; } - return vk_get_proc_addr(inst, name); + return get_vk_proc_address(inst, name); } -static VkResult window_win32_vk_create_surface(void *window, VkInstance inst, VkSurfaceKHR *surface) +static VkResult window_wayland_vk_create_surface(void *window, VkInstance inst, VkSurfaceKHR *surface) { - struct stl_window_win32 *w32 = (struct stl_window_win32 *)window; - (void)w32; - (void)inst; - (void)surface; - return VK_ERROR_EXTENSION_NOT_PRESENT; + struct stl_window_wayland *wl = (struct stl_window_wayland *)window; + + VkWaylandSurfaceCreateInfoKHR info = { + .sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, + .display = wl->display, + .surface = wl->surface + }; + + PFN_vkCreateWaylandSurfaceKHR CreateWaylandSurfaceKHR; + CreateWaylandSurfaceKHR = (PFN_vkCreateWaylandSurfaceKHR)get_vk_proc_address(inst, "vkCreateWaylandSurfaceKHR"); + if (!CreateWaylandSurfaceKHR) { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } + + VkResult ret = vkCreateWaylandSurfaceKHR(inst, &info, NULL, surface); + if (ret != VK_SUCCESS) { + log_error("Failed to create Wayland Vulkan surface."); + } + return ret; } -static const char *const *window_win32_vk_get_extensions(u32 *num) +static const char *const *window_wayland_vk_get_extensions(u32 *num) { - (void)num; - return NULL; + *num = 2; + return global.vk_extensions; } #elif defined STELA_API_OPENGL static bool window_wayland_gl_loader_load(void *window) @@ -1193,6 +1254,7 @@ struct stl_window *stl_window_create(void) wl->w.resize = window_wayland_resize; wl->w.resize_internal = window_wayland_resize_internal; wl->w.toggle_fullscreen = window_wayland_toggle_fullscreen; + wl->w.set_cursor = window_wayland_set_cursor; wl->w.set_decorations = window_wayland_set_decorations; #ifdef STELA_POLL_INLINE wl->w.poll = window_wayland_poll; @@ -1205,7 +1267,11 @@ struct stl_window *stl_window_create(void) wl->w.should_refresh = window_wayland_should_refresh; #endif wl->w.free = window_wayland_free; -#ifdef STELA_API_OPENGL +#if defined STELA_API_VULKAN + wl->w.get_vk_proc_address = window_wayland_get_vk_proc_address; + wl->w.vk_create_surface = window_wayland_vk_create_surface; + wl->w.vk_get_extensions = window_wayland_vk_get_extensions; +#elif defined STELA_API_OPENGL wl->w.get_gl_proc_address = NULL; wl->w.gl_loader_load = window_wayland_gl_loader_load; wl->w.gl_make_current = window_wayland_gl_make_current; diff --git a/src/window_wayland.h b/src/window_wayland.h index e3b2c32..89643d5 100644 --- a/src/window_wayland.h +++ b/src/window_wayland.h @@ -25,9 +25,12 @@ struct stl_wayland_global { s32 display_fd; struct wl_registry *registry; u16 keycodes[STELA_WAYLAND_KEY_MAX]; - struct wl_surface *active_surface; 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; }; @@ -51,7 +54,7 @@ struct stl_window_wayland { struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; struct zxdg_decoration_manager_v1 *zxdg_decoration_manager; - struct wp_cursor_shape_manager_v1 *cursor_shape; + struct wp_cursor_shape_manager_v1 *cursor_shape_manager; struct wp_cursor_shape_device_v1 *cursor_shape_device; struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration; struct zwlr_layer_shell_v1 *layer_shell; @@ -64,12 +67,19 @@ struct stl_window_wayland { #else struct nn_pollfd fds[1]; #endif + bool pending_configure; + u8 cursor_shape; + struct { + u32 serial; + struct wl_pointer *pointer; + } cursor; bool decorated; - bool pending; - f64 pointer_x; - f64 pointer_y; - f64 last_pointer_x; - f64 last_pointer_y; - f64 scroll_x; - f64 scroll_y; + s32 geom_width; + s32 geom_height; + struct { + f64 pointer_x; + f64 pointer_y; + f64 scroll_x; + f64 scroll_y; + } frame; }; diff --git a/src/window_win32.c b/src/window_win32.c index f4baca8..6bb4945 100644 --- a/src/window_win32.c +++ b/src/window_win32.c @@ -58,11 +58,15 @@ struct the_baby { LPVOID lpParam; }; +// Service messages. #define CREATE_DANGEROUS_WINDOW (WM_USER + 0x1337) #define DESTROY_DANGEROUS_WINDOW (WM_USER + 0x1338) #define WAKE_WINDOW (WM_USER + 0x1339) #define EXIT_PROCESS (WM_USER + 0x133a) +// Window messages. +#define SET_CURSOR (WM_USER + 0x133b) + static DWORD MainThreadID; static struct stl_win32_global Global = { 0 }; @@ -230,12 +234,12 @@ static void set_keycodes_for_win32(void) Global.keycodes[VK_OEM_PLUS] = STELA_KEY_KP_EQUAL; Global.keycodes[VK_RETURN] = STELA_KEY_KP_RETURN; - Global.keycodes[VK_CONTROL] = STELA_KEY_LEFT_CONTROL; - Global.keycodes[VK_MENU] = STELA_KEY_LEFT_ALT; - Global.keycodes[VK_SHIFT] = STELA_KEY_LEFT_SHIFT; - Global.keycodes[VK_CONTROL] = STELA_KEY_RIGHT_CONTROL; - Global.keycodes[VK_MENU] = STELA_KEY_RIGHT_ALT; - Global.keycodes[VK_SHIFT] = STELA_KEY_RIGHT_SHIFT; + Global.keycodes[VK_LCONTROL] = STELA_KEY_LEFT_CONTROL; + Global.keycodes[VK_LMENU] = STELA_KEY_LEFT_ALT; + Global.keycodes[VK_LSHIFT] = STELA_KEY_LEFT_SHIFT; + Global.keycodes[VK_RCONTROL] = STELA_KEY_RIGHT_CONTROL; + Global.keycodes[VK_RMENU] = STELA_KEY_RIGHT_ALT; + Global.keycodes[VK_RSHIFT] = STELA_KEY_RIGHT_SHIFT; Global.keycodes[VK_LWIN] = STELA_KEY_LEFT_SUPER; Global.keycodes[VK_RWIN] = STELA_KEY_RIGHT_SUPER; } @@ -280,6 +284,9 @@ bool stl_global_init(bool skip_graphics) al_array_init(Global.monitors); EnumDisplayMonitors(NULL, NULL, MonitorInfoProc, 0); + Global.Cursors[STELA_CURSOR_NORMAL] = LoadCursor(NULL, IDC_ARROW); + Global.Cursors[STELA_CURSOR_CROSSHAIR] = LoadCursor(NULL, IDC_CROSS); + set_keycodes_for_win32(); return true; @@ -625,6 +632,9 @@ static const char *WndMessageString(UINT Message) } #endif +#define ASYNC_KEY_IS_DOWN(VK) \ + ((GetAsyncKeyState(VK) & 0x8000) != 0) + #define MESSAGE_TO_KEY_STATE(Message) \ ((Message == WM_KEYDOWN || Message == WM_SYSKEYDOWN) ? STELA_BUTTON_PRESSED : STELA_BUTTON_RELEASED) @@ -660,20 +670,10 @@ static LRESULT CALLBACK DisplayWndProc(HWND Window, UINT Message, WPARAM WParam, case WM_LBUTTONDOWN: { // https://learn.microsoft.com/en-us/windows/win32/learnwin32/mouse-movement SetCapture(w32->Window); - //SetClassLongPtr(w32->Window, GCLP_HCURSOR, (LONG_PTR)HCursor); - //SystemParametersInfo(SPI_SETCURSORS, 0, NULL, 0); // Reset system cursor state. - HCURSOR HCursor = LoadCursor(NULL, IDC_CROSS); - SetCursor(HCursor); - ShowCursor(TRUE); goto intercept; } case WM_LBUTTONUP: { ReleaseCapture(); - HCURSOR HCursor = LoadCursor(NULL, IDC_ARROW); - SetCursor(HCursor); - if (w32->w.fullscreen) { - while (ShowCursor(FALSE) >= 0) {} - } goto intercept; } case WM_RBUTTONDOWN: @@ -735,14 +735,56 @@ static LRESULT CALLBACK DisplayWndProc(HWND Window, UINT Message, WPARAM WParam, } case WM_MOUSEWHEEL: goto intercept; + case WM_ACTIVATE: { + // Do not use Wine behavior as a reference, it's too broken. + switch (LOWORD(WParam)) { + case WA_ACTIVE: + case WA_CLICKACTIVE: { + LParam = STELA_MOD_LCONTROL * ASYNC_KEY_IS_DOWN(VK_LCONTROL) | + STELA_MOD_RCONTROL * ASYNC_KEY_IS_DOWN(VK_RCONTROL) | + STELA_MOD_LSHIFT * ASYNC_KEY_IS_DOWN(VK_LSHIFT) | + STELA_MOD_RSHIFT * ASYNC_KEY_IS_DOWN(VK_RSHIFT) | + STELA_MOD_LALT * ASYNC_KEY_IS_DOWN(VK_LMENU) | + STELA_MOD_RALT * ASYNC_KEY_IS_DOWN(VK_RMENU); + break; + } + case WA_INACTIVE: { + break; + } + } + goto intercept; + } case WM_KEYDOWN: case WM_SYSKEYDOWN: case WM_KEYUP: - case WM_SYSKEYUP: + case WM_SYSKEYUP: { + // https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input + WORD Flags = HIWORD(LParam); + bool PreviouslyDown = (Flags & KF_REPEAT) == KF_REPEAT; + if (((Message == WM_KEYDOWN || Message == WM_SYSKEYDOWN) && PreviouslyDown) || + ((Message == WM_KEYUP || Message == WM_SYSKEYUP) && !PreviouslyDown)) { + break; // Repeat. + } + WORD Scancode = LOBYTE(Flags); + bool Extended = (LParam & KF_EXTENDED) == KF_EXTENDED; + if (Extended) Scancode = MAKEWORD(Scancode, 0xE0); + switch (WParam) { + case VK_SHIFT: + case VK_CONTROL: + case VK_MENU: + WParam = LOWORD(MapVirtualKey(Scancode, MAPVK_VSC_TO_VK_EX)); + break; + default: + break; + } stl_window_send_key_immediate_event(&w32->w, MESSAGE_TO_KEY_STATE(Message), Global.keycodes[WParam]); goto intercept; + } case WM_SIZE: goto intercept; + case SET_CURSOR: + SetCursor(Global.Cursors[WParam]); + return 0; default: break; } @@ -970,7 +1012,6 @@ static void window_win32_toggle_fullscreen(struct stl_window *window) w32->Prev.Width = Prev.right - Prev.left; w32->Prev.Height = Prev.bottom - Prev.top; set_window_decorations(w32, false); - while (ShowCursor(FALSE) >= 0) {} 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; @@ -979,7 +1020,6 @@ static void window_win32_toggle_fullscreen(struct stl_window *window) if (w32->UserDecorated) { set_window_decorations(w32, true); } - ShowCursor(TRUE); SetWindowPos(w32->Window, HWND_NOTOPMOST, X, Y, w32->Prev.Width, w32->Prev.Height, SWP_FRAMECHANGED); } w32->w.fullscreen = !w32->w.fullscreen; @@ -996,6 +1036,34 @@ 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; + } + } +} + +static void window_win32_set_cursor(struct stl_window *window, u8 shape) +{ + struct stl_window_win32 *w32 = (struct stl_window_win32 *)window; + SetCursorInternal(w32, shape); +} + static void win32_handle_message(struct stl_window_win32 *w32, MSG *Message) { switch (Message->message) { @@ -1064,19 +1132,32 @@ static void win32_handle_message(struct stl_window_win32 *w32, MSG *Message) case WM_MOUSEWHEEL: stl_window_send_scroll_event(&w32->w, -(f64)GET_WHEEL_DELTA_WPARAM(Message->wParam)); break; + case WM_ACTIVATE: { + switch (LOWORD(Message->wParam)) { + case WA_ACTIVE: + case WA_CLICKACTIVE: { + u32 *mods = stl_all_modifiers; + u32 mod; + while ((mod = *mods++) != STELA_MOD_NONE) { + if (Message->lParam & mod) { + stl_window_send_key_event(&w32->w, STELA_BUTTON_PRESSED, stl_mod_to_key(mod)); + } + } + break; + } + case WA_INACTIVE: { + stl_window_drop_modifiers(&w32->w); + break; + } + } + break; + } case WM_KEYDOWN: case WM_SYSKEYDOWN: case WM_KEYUP: - case WM_SYSKEYUP: { - /* https://github.com/glfw/glfw/blob/e7ea71be039836da3a98cea55ae5569cb5eb885c/src/win32_window.c#L717C11-L721C75 - s32 scancode = HIWORD(Message->lParam) & (KF_EXTENDED | 0xff); - if (!scancode) { - scancode = MapVirtualKeyW((UINT)Message->wParam, MAPVK_VK_TO_VSC); - } - */ + case WM_SYSKEYUP: stl_window_send_key_event(&w32->w, MESSAGE_TO_KEY_STATE(Message->message), Global.keycodes[Message->wParam]); break; - } case WM_SIZE: w32->w.width = LOWORD(Message->lParam); w32->w.height = HIWORD(Message->lParam); @@ -1142,8 +1223,14 @@ 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) { - HMODULE VKModule = LoadLibraryA("vulkan-1.dll"); + 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); } @@ -1166,13 +1253,17 @@ static VkResult window_win32_vk_create_surface(void *window, VkInstance inst, Vk return VK_ERROR_EXTENSION_NOT_PRESENT; } - VkWin32SurfaceCreateInfoKHR info = { 0 }; - info.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; - info.hinstance = Global.ModuleHandle; - info.hwnd = w32->Window; + VkWin32SurfaceCreateInfoKHR info = { + .sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, + .hinstance = Global.ModuleHandle, + .hwnd = w32->Window + }; - VkResult err = CreateWin32SurfaceKHR(inst, &info, NULL, surface); - return err; + VkResult ret = CreateWin32SurfaceKHR(inst, &info, NULL, surface); + if (ret != VK_SUCCESS) { + log_error("Failed to create Win32 Vulkan surface."); + } + return ret; } static const char *const *window_win32_vk_get_extensions(u32 *num) @@ -1225,6 +1316,7 @@ struct stl_window *stl_window_create(void) w32->w.resize = window_win32_resize; w32->w.toggle_fullscreen = window_win32_toggle_fullscreen; w32->w.set_decorations = window_win32_set_decorations; + w32->w.set_cursor = window_win32_set_cursor; #ifdef STELA_POLL_INLINE w32->w.poll = window_win32_poll; #ifdef STELA_PAUSE diff --git a/src/window_win32.h b/src/window_win32.h index 5fbb0f3..317916e 100644 --- a/src/window_win32.h +++ b/src/window_win32.h @@ -18,6 +18,7 @@ struct stl_win32_global { #ifdef STELA_API_VULKAN const char **VKExtensions; #endif + HCURSOR Cursors[STELA_CURSOR_HIDDEN]; u16 keycodes[STELA_WIN32_KEY_MAX]; array(struct stl_monitor *) monitors; u32 argc; diff --git a/src/window_x11.c b/src/window_x11.c index 413e9bd..2566fda 100644 --- a/src/window_x11.c +++ b/src/window_x11.c @@ -288,7 +288,7 @@ static void window_x11_toggle_fullscreen(struct stl_window *window) static bool window_x11_poll(struct stl_window *window, bool block) { struct stl_window_x11 *xw = (struct stl_window_x11 *)window; - return stl_poll_common(xw->fds, block); + return stl_poll_common(&xw->w, xw->fds, block); } #ifdef STELA_PAUSE @@ -318,9 +318,9 @@ static void handle_next_xevent(struct stl_window_x11 *xw) break; case KeyPress: case KeyRelease: { - u8 key_state = xev.type == KeyPress ? STELA_BUTTON_PRESSED : STELA_BUTTON_RELEASED; - stl_window_send_key_immediate_event(&xw->w, key_state, xev.xkey.keycode); - stl_window_send_key_event(&xw->w, key_state, xev.xkey.keycode); + 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); break; } case ButtonPress: diff --git a/subprojects/libalabaster.wrap b/subprojects/libalabaster.wrap index 81771ed..9a9cfc1 100644 --- a/subprojects/libalabaster.wrap +++ b/subprojects/libalabaster.wrap @@ -1,4 +1,4 @@ [wrap-git] url = https://git.akon.city/libalabaster -revision = b403770e7eb6e0a5a8ca14a24f91dbf214ffe917 +revision = 0470ca3416388e7e58b64f63bfe505d454f150b9 depth = 1 diff --git a/subprojects/libnaunet.wrap b/subprojects/libnaunet.wrap index 462c1a7..15dd478 100644 --- a/subprojects/libnaunet.wrap +++ b/subprojects/libnaunet.wrap @@ -1,4 +1,4 @@ [wrap-git] url = https://git.akon.city/libnaunet -revision = fc8df3fe9cd15fe7b0b86c6584e0b3fb487d6886 +revision = 3ea974dd51b1f01483bad6a4d115189928bbf4db depth = 1 diff --git a/tests/window.c b/tests/window.c index d334d36..e13f1fa 100644 --- a/tests/window.c +++ b/tests/window.c @@ -279,7 +279,9 @@ s32 window_system_main(u32 argc, str *argv, void *extra) s32 main(s32 argc, char *argv[]) { - if (!nn_common_init() || !stl_global_init(false)) { + if (!nn_common_init("Stella_main")) return EXIT_FAILURE; + if (!stl_global_init(false)) { + nn_common_close(); return EXIT_FAILURE; } |