summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/egl_common.c19
-rw-r--r--src/vk_common.c54
-rw-r--r--src/vk_common.h5
-rw-r--r--src/window.c13
-rw-r--r--src/window.h17
-rw-r--r--src/window_glfw.c50
-rw-r--r--src/window_glfw.h2
-rw-r--r--src/window_wayland.c150
-rw-r--r--src/window_wayland.h15
-rw-r--r--src/window_win32.c100
-rw-r--r--src/window_x11.c400
-rw-r--r--src/window_x11.h22
12 files changed, 627 insertions, 220 deletions
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 <al/log.h>
+
+#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 <vulkan/vulkan.h>
#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 <linux/input.h>
#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 <X11/Xlib.h>
#include <X11/Xatom.h>
-#include <X11/extensions/Xrandr.h>
#include <X11/extensions/shape.h>
+#include <X11/extensions/Xrandr.h>
+#include <X11/extensions/Xfixes.h>
+#include <X11/cursorfont.h>
+#include <X11/Xcursor/Xcursor.h>
+#include <X11/XKBlib.h>
#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