#define AL_LOG_SECTION "wayland" #include #include #include #include #include #ifdef STELA_API_VULKAN #include #endif #include "window_wayland.h" #include "common.h" #include "poll_common.h" #include "keys.h" #define ASSERT_MIN(v, max) (al_assert(v <= max), v) #define FRAME_NO_VALUE ((f64)0xffffffffffffffff) f64 stl_scale_scroll(f64 v, f64 factor) { return v / (15.0 * factor); } static struct stl_wayland_global global = { 0 }; static void handle_output_geometry(void *data, struct wl_output *output, s32 x, s32 y, s32 width_mm, s32 height_mm, s32 subpixel, const char *make, const char *model, s32 transform) { struct stl_monitor_wayland *monitor = (struct stl_monitor_wayland *)data; (void)output; (void)x; (void)y; (void)width_mm; (void)height_mm; (void)subpixel; (void)make; (void)model; switch (transform) { case WL_OUTPUT_TRANSFORM_90: case WL_OUTPUT_TRANSFORM_270: monitor->m.vertical = true; break; case WL_OUTPUT_TRANSFORM_NORMAL: case WL_OUTPUT_TRANSFORM_180: default: monitor->m.vertical = false; break; } } static void handle_output_mode(void *data, struct wl_output *output, u32 flags, s32 width, s32 height, s32 refresh) { struct stl_monitor_wayland *monitor = (struct stl_monitor_wayland *)data; (void)output; (void)flags; (void)refresh; al_assert(width > 0 && height > 0); if (monitor->m.vertical) { monitor->m.width = (u32)height; monitor->m.height = (u32)width; } else { monitor->m.width = (u32)width; monitor->m.height = (u32)height; } } static void handle_output_done(void *data, struct wl_output *output) { struct stl_monitor_wayland *monitor = (struct stl_monitor_wayland *)data; (void)output; if (global.output_discovered) { global.output_discovered(global.userdata, (struct stl_monitor *)monitor); } } static void handle_output_scale(void *data, struct wl_output *output, s32 factor) { struct stl_monitor_wayland *monitor = (struct stl_monitor_wayland *)data; (void)output; al_assert(factor > 0); monitor->m.scale = (u32)factor; } static void handle_output_description(void *data, struct wl_output *output, const char *description) { (void)data; (void)output; (void)description; } static void handle_output_name(void *data, struct wl_output *output, const char *name) { struct stl_monitor_wayland *monitor = (struct stl_monitor_wayland *)data; (void)output; size_t length = al_strlen(name) + 1; if (length >= STELA_MAX_MONITOR_NAME) { length = STELA_MAX_MONITOR_NAME - 1; monitor->m.name[length] = '\0'; } al_memcpy(monitor->m.name, name, length); } static const struct wl_output_listener output_listener = { .geometry = handle_output_geometry, .mode = handle_output_mode, .done = handle_output_done, .scale = handle_output_scale, .description = handle_output_description, .name = handle_output_name }; static void global_add(void *data, struct wl_registry *registry, u32 name, const char *interface, u32 version) { (void)data; if (al_strscmp(interface, "wl_output") == 0) { struct stl_monitor_wayland *monitor = al_alloc_object(struct stl_monitor_wayland); al_array_push(global.monitors, (struct stl_monitor *)monitor); monitor->output = (struct wl_output *)wl_registry_bind(registry, name, &wl_output_interface, ASSERT_MIN(4u, version)); wl_output_add_listener(monitor->output, &output_listener, monitor); } } static void global_remove(void *data, struct wl_registry *registry, u32 name) { (void)data; (void)registry; (void)name; } static const struct wl_registry_listener registry_listener = { .global = global_add, .global_remove = global_remove }; void stl_set_output_discovered_callback(void (*output_discovered)(void *, struct stl_monitor *), void *userdata) { global.output_discovered = output_discovered; global.userdata = userdata; } void stl_set_icon_data(u8 *data, u32 size) { global.icon.data = data; global.icon.size = size; } static void set_keycodes_for_wayland(void) { al_memset(global.keycodes, 0, sizeof(global.keycodes)); global.keycodes[KEY_0] = STELA_KEY_0; global.keycodes[KEY_1] = STELA_KEY_1; global.keycodes[KEY_2] = STELA_KEY_2; global.keycodes[KEY_3] = STELA_KEY_3; global.keycodes[KEY_4] = STELA_KEY_4; global.keycodes[KEY_5] = STELA_KEY_5; global.keycodes[KEY_6] = STELA_KEY_6; global.keycodes[KEY_7] = STELA_KEY_7; global.keycodes[KEY_8] = STELA_KEY_8; global.keycodes[KEY_9] = STELA_KEY_9; global.keycodes[KEY_A] = STELA_KEY_A; global.keycodes[KEY_B] = STELA_KEY_B; global.keycodes[KEY_C] = STELA_KEY_C; global.keycodes[KEY_D] = STELA_KEY_D; global.keycodes[KEY_E] = STELA_KEY_E; global.keycodes[KEY_F] = STELA_KEY_F; global.keycodes[KEY_G] = STELA_KEY_G; global.keycodes[KEY_H] = STELA_KEY_H; global.keycodes[KEY_I] = STELA_KEY_I; global.keycodes[KEY_J] = STELA_KEY_J; global.keycodes[KEY_K] = STELA_KEY_K; global.keycodes[KEY_L] = STELA_KEY_L; global.keycodes[KEY_M] = STELA_KEY_M; global.keycodes[KEY_N] = STELA_KEY_N; global.keycodes[KEY_O] = STELA_KEY_O; global.keycodes[KEY_P] = STELA_KEY_P; global.keycodes[KEY_Q] = STELA_KEY_Q; global.keycodes[KEY_R] = STELA_KEY_R; global.keycodes[KEY_S] = STELA_KEY_S; global.keycodes[KEY_T] = STELA_KEY_T; global.keycodes[KEY_U] = STELA_KEY_U; global.keycodes[KEY_V] = STELA_KEY_V; global.keycodes[KEY_W] = STELA_KEY_W; global.keycodes[KEY_Y] = STELA_KEY_Y; global.keycodes[KEY_X] = STELA_KEY_X; global.keycodes[KEY_Z] = STELA_KEY_Z; global.keycodes[KEY_SPACE] = STELA_KEY_SPACE; global.keycodes[KEY_APOSTROPHE] = STELA_KEY_APOSTROPHE; global.keycodes[KEY_COMMA] = STELA_KEY_COMMA; global.keycodes[KEY_DOT] = STELA_KEY_PERIOD; global.keycodes[KEY_SLASH] = STELA_KEY_SLASH; global.keycodes[KEY_BACKSLASH] = STELA_KEY_BACKSLASH; global.keycodes[KEY_GRAVE] = STELA_KEY_GRAVE_ACCENT; global.keycodes[KEY_MINUS] = STELA_KEY_MINUS; global.keycodes[KEY_LEFTBRACE] = STELA_KEY_LEFT_BRACKET; global.keycodes[KEY_RIGHTBRACE] = STELA_KEY_RIGHT_BRACKET; global.keycodes[KEY_SEMICOLON] = STELA_KEY_SEMICOLON; global.keycodes[KEY_EQUAL] = STELA_KEY_EQUAL; global.keycodes[KEY_ENTER] = STELA_KEY_RETURN; global.keycodes[KEY_BACKSPACE] = STELA_KEY_BACKSPACE; global.keycodes[KEY_TAB] = STELA_KEY_TAB; global.keycodes[KEY_ESC] = STELA_KEY_ESCAPE; global.keycodes[KEY_INSERT] = STELA_KEY_INSERT; global.keycodes[KEY_DELETE] = STELA_KEY_DELETE; global.keycodes[KEY_HOME] = STELA_KEY_HOME; global.keycodes[KEY_END] = STELA_KEY_END; global.keycodes[KEY_PAGEUP] = STELA_KEY_PAGE_UP; global.keycodes[KEY_PAGEDOWN] = STELA_KEY_PAGE_DOWN; global.keycodes[KEY_PRINT] = STELA_KEY_PRINT_SCREEN; global.keycodes[KEY_PAUSE] = STELA_KEY_PAUSE; global.keycodes[KEY_CAPSLOCK] = STELA_KEY_CAPS_LOCK; global.keycodes[KEY_SCROLLLOCK] = STELA_KEY_SCROLL_LOCK; global.keycodes[KEY_NUMLOCK] = STELA_KEY_NUM_LOCK; global.keycodes[KEY_LEFT] = STELA_KEY_LEFT; global.keycodes[KEY_RIGHT] = STELA_KEY_RIGHT; global.keycodes[KEY_UP] = STELA_KEY_UP; global.keycodes[KEY_DOWN] = STELA_KEY_DOWN; global.keycodes[KEY_F1] = STELA_KEY_F1; global.keycodes[KEY_F2] = STELA_KEY_F2; global.keycodes[KEY_F3] = STELA_KEY_F3; global.keycodes[KEY_F4] = STELA_KEY_F4; global.keycodes[KEY_F5] = STELA_KEY_F5; global.keycodes[KEY_F6] = STELA_KEY_F6; global.keycodes[KEY_F7] = STELA_KEY_F7; global.keycodes[KEY_F8] = STELA_KEY_F8; global.keycodes[KEY_F9] = STELA_KEY_F9; global.keycodes[KEY_F10] = STELA_KEY_F10; global.keycodes[KEY_F11] = STELA_KEY_F11; global.keycodes[KEY_F12] = STELA_KEY_F12; global.keycodes[KEY_F13] = STELA_KEY_F13; global.keycodes[KEY_F14] = STELA_KEY_F14; global.keycodes[KEY_F15] = STELA_KEY_F15; global.keycodes[KEY_F16] = STELA_KEY_F16; global.keycodes[KEY_F17] = STELA_KEY_F17; global.keycodes[KEY_F18] = STELA_KEY_F18; global.keycodes[KEY_F19] = STELA_KEY_F19; global.keycodes[KEY_F20] = STELA_KEY_F20; global.keycodes[KEY_F21] = STELA_KEY_F21; global.keycodes[KEY_F22] = STELA_KEY_F22; global.keycodes[KEY_F23] = STELA_KEY_F23; global.keycodes[KEY_F24] = STELA_KEY_F24; global.keycodes[KEY_KP0] = STELA_KEY_KP_0; global.keycodes[KEY_KP1] = STELA_KEY_KP_1; global.keycodes[KEY_KP2] = STELA_KEY_KP_2; global.keycodes[KEY_KP3] = STELA_KEY_KP_3; global.keycodes[KEY_KP4] = STELA_KEY_KP_4; global.keycodes[KEY_KP5] = STELA_KEY_KP_5; global.keycodes[KEY_KP6] = STELA_KEY_KP_6; global.keycodes[KEY_KP7] = STELA_KEY_KP_7; global.keycodes[KEY_KP8] = STELA_KEY_KP_8; global.keycodes[KEY_KP9] = STELA_KEY_KP_9; global.keycodes[KEY_KPPLUS] = STELA_KEY_KP_ADD; global.keycodes[KEY_KPMINUS] = STELA_KEY_KP_SUBTRACT; global.keycodes[KEY_KPASTERISK] = STELA_KEY_KP_MULTIPLY; global.keycodes[KEY_KPSLASH] = STELA_KEY_KP_DIVIDE; global.keycodes[KEY_KPDOT] = STELA_KEY_KP_DECIMAL; global.keycodes[KEY_KPEQUAL] = STELA_KEY_KP_EQUAL; global.keycodes[KEY_KPENTER] = STELA_KEY_KP_RETURN; global.keycodes[KEY_LEFTCTRL] = STELA_KEY_LEFT_CONTROL; global.keycodes[KEY_LEFTALT] = STELA_KEY_LEFT_ALT; global.keycodes[KEY_LEFTSHIFT] = STELA_KEY_LEFT_SHIFT; global.keycodes[KEY_RIGHTCTRL] = STELA_KEY_RIGHT_CONTROL; global.keycodes[KEY_RIGHTALT] = STELA_KEY_RIGHT_ALT; global.keycodes[KEY_RIGHTSHIFT] = STELA_KEY_RIGHT_SHIFT; global.keycodes[KEY_LEFTMETA] = STELA_KEY_LEFT_SUPER; global.keycodes[KEY_RIGHTMETA] = STELA_KEY_RIGHT_SUPER; global.keycodes[KEY_COMPOSE] = STELA_KEY_MENU; } static void assert_wl_interface_names() { al_assert(al_strscmp(wl_output_interface.name, "wl_output") == 0); al_assert(al_strscmp(wl_compositor_interface.name, "wl_compositor") == 0); al_assert(al_strscmp(wl_shm_interface.name, "wl_shm") == 0); al_assert(al_strscmp(wl_seat_interface.name, "wl_seat") == 0); al_assert(al_strscmp(xdg_wm_base_interface.name, "xdg_wm_base") == 0); al_assert(al_strscmp(zxdg_decoration_manager_v1_interface.name, "zxdg_decoration_manager_v1") == 0); al_assert(al_strscmp(wp_cursor_shape_manager_v1_interface.name, "wp_cursor_shape_manager_v1") == 0); al_assert(al_strscmp(xdg_toplevel_icon_manager_v1_interface.name, "xdg_toplevel_icon_manager_v1") == 0); } bool stl_global_init(bool skip_graphics) { assert_wl_interface_names(); global.display = wl_display_connect(NULL); if (!global.display) { log_error("Failed to connect to the display."); return false; } global.display_fd = wl_display_get_fd(global.display); if (!skip_graphics) { #if defined STELA_API_VULKAN global.vk_extensions = (const char **)al_calloc(2, sizeof(const char *const *)); global.vk_extensions[0] = "VK_KHR_surface"; global.vk_extensions[1] = "VK_KHR_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) { log_error("Failed to get wayland-native EGL display."); return false; } if (!stl_load_egl(&global.egl)) return false; #endif } else { // 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 } al_array_init(global.monitors); global.registry = wl_display_get_registry(global.display); wl_registry_add_listener(global.registry, ®istry_listener, NULL); wl_display_roundtrip(global.display); wl_display_roundtrip(global.display); set_keycodes_for_wayland(); return true; } s32 stl_global_get_poll_fd() { return global.display_fd; } static void display_prepare_read(struct wl_display *display) { while (wl_display_prepare_read(display) != 0) { wl_display_dispatch_pending(display); } wl_display_flush(display); } void stl_global_process_events() { display_prepare_read(global.display); wl_display_read_events(global.display); wl_display_dispatch_pending(global.display); } s32 stl_swallow_main(u32 argc, str *argv, s32 (*main)(u32, str *, void *), void *extra) { return main(argc, argv, extra); } void stl_global_close(void) { if (!global.display) return; #if defined STELA_API_VULKAN if (global.vk_extensions) al_free(global.vk_extensions); #elif defined STELA_API_OPENGL stl_terminate_egl(&global.egl); #endif for (u32 i = 0; i < global.monitors.count; i++) { struct stl_monitor_wayland *monitor = (struct stl_monitor_wayland *)al_array_at(global.monitors, i); if (monitor->output) wl_output_destroy(monitor->output); al_free(monitor); } al_array_free(global.monitors); wl_display_disconnect(global.display); } static void xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, u32 serial) { (void)data; xdg_wm_base_pong(shell, serial); } 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) { if (shape == STELA_CURSOR_HIDDEN) { wl_pointer_set_cursor(wl->cursor.pointer, wl->cursor.serial, NULL, 0, 0); } 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); 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) { 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; wl->frame.pointer_y = FRAME_NO_VALUE; } static void pointer_motion(void *data, struct wl_pointer *pointer, u32 time, wl_fixed_t surface_x, wl_fixed_t surface_y) { struct stl_window_wayland *wl = (struct stl_window_wayland *)data; (void)pointer; (void)time; 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, u32 serial, u32 time, u32 button, u32 state) { struct stl_window_wayland *wl = (struct stl_window_wayland *)data; (void)pointer; (void)time; #define MOUSE1 0x110 #define MOUSE2 0x111 #define MOUSE3 0x112 #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_button = 1 << (button - MOUSE1); u8 mapped_state = (state == WL_POINTER_BUTTON_STATE_PRESSED) ? STELA_BUTTON_PRESSED : STELA_BUTTON_RELEASED; stl_window_send_mouse_button_event(&wl->w, mapped_state, mapped_button); } static void pointer_axis(void *data, struct wl_pointer *pointer, u32 time, u32 axis, wl_fixed_t value) { struct stl_window_wayland *wl = (struct stl_window_wayland *)data; (void)pointer; (void)time; if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { 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) { if (wl->frame.scroll_x == FRAME_NO_VALUE) wl->frame.scroll_x = 0.0; wl->frame.scroll_x -= wl_fixed_to_double(value); } } static void pointer_frame(void *data, struct wl_pointer *pointer) { struct stl_window_wayland *wl = (struct stl_window_wayland *)data; (void)pointer; 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 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; } } static void pointer_axis_source(void *data, struct wl_pointer *pointer, u32 axis_source) { (void)data; (void)pointer; (void)axis_source; } static void pointer_axis_stop(void *data, struct wl_pointer *pointer, u32 time, u32 axis) { (void)data; (void)pointer; (void)time; (void)axis; } static void pointer_axis_discrete(void *data, struct wl_pointer *pointer, u32 axis, s32 discrete) { (void)data; (void)pointer; (void)axis; (void)discrete; } static const struct wl_pointer_listener pointer_listener = { .enter = pointer_enter, .leave = pointer_leave, .motion = pointer_motion, .button = pointer_button, .axis = pointer_axis, .frame = pointer_frame, .axis_source = pointer_axis_source, .axis_stop = pointer_axis_stop, .axis_discrete = pointer_axis_discrete }; static void keyboard_keymap(void *data, struct wl_keyboard *keyboard, u32 format, s32 fd, u32 size) { (void)data; (void)keyboard; (void)format; (void)fd; (void)size; } static void keyboard_enter(void *data, struct wl_keyboard *keyboard, u32 serial, struct wl_surface *surface, struct wl_array *keys) { struct stl_window_wayland *wl = (struct stl_window_wayland *)data; (void)keyboard; (void)serial; (void)surface; u32 *item; wl_array_for_each(item, keys) { u32 key = *item; al_assert(key <= STELA_WAYLAND_KEY_MAX); u16 mapped_key = global.keycodes[key]; if (stl_key_to_mod(mapped_key) == 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) { struct stl_window_wayland *wl = (struct stl_window_wayland *)data; (void)keyboard; (void)serial; (void)surface; stl_window_drop_modifiers(&wl->w); } 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)keyboard; (void)serial; (void)time; al_assert(key <= STELA_WAYLAND_KEY_MAX); u16 mapped_key = global.keycodes[key]; u8 mapped_state = (state == WL_KEYBOARD_KEY_STATE_PRESSED) ? STELA_BUTTON_PRESSED : STELA_BUTTON_RELEASED; 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, u32 serial, u32 mods_depressed, u32 mods_latched, u32 mods_locked, u32 group) { (void)data; (void)keyboard; (void)serial; (void)mods_depressed; (void)mods_latched; (void)mods_locked; (void)group; } static void keyboard_repeat(void *data, struct wl_keyboard *keyboard, s32 rate, s32 delay) { (void)data; (void)keyboard; (void)rate; (void)delay; } static const struct wl_keyboard_listener keyboard_listener = { .keymap = keyboard_keymap, .enter = keyboard_enter, .leave = keyboard_leave, .key = keyboard_key, .modifiers = keyboard_modifiers, .repeat_info = keyboard_repeat }; static void seat_capabilities(void *data, struct wl_seat *seat, u32 capabilities) { struct stl_window_wayland *wl = (struct stl_window_wayland *)data; al_assert(seat == wl->seat); if (capabilities & WL_SEAT_CAPABILITY_POINTER) { if (!wl->pointer) { wl->pointer = wl_seat_get_pointer(wl->seat); 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); } } else { if (wl->pointer) { // @TODO: Why or why not wl_keyboard/pointer_release()? wl_pointer_destroy(wl->pointer); wl->pointer = NULL; } } if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) { if (!wl->keyboard) { wl->keyboard = wl_seat_get_keyboard(wl->seat); wl_keyboard_add_listener(wl->keyboard, &keyboard_listener, wl); } } else { if (wl->keyboard) { wl_keyboard_destroy(wl->keyboard); wl->keyboard = NULL; } } } static void seat_name(void *data, struct wl_seat *seat, const char *name) { (void)data; (void)seat; (void)name; } static const struct wl_seat_listener seat_listener = { .capabilities = seat_capabilities, .name = seat_name }; #define SUPPORT_PREFERRED_BUFFER_SCALE static void context_global_add(void *data, struct wl_registry *registry, u32 name, const char *interface, u32 version) { struct stl_window_wayland *wl = (struct stl_window_wayland *)data; if (al_strscmp(interface, "wl_compositor") == 0) { #ifdef SUPPORT_PREFERRED_BUFFER_SCALE wl->compositor = (struct wl_compositor *)wl_registry_bind(registry, name, &wl_compositor_interface, ASSERT_MIN(6u, version)); #else wl->compositor = (struct wl_compositor *)wl_registry_bind(registry, name, &wl_compositor_interface, ASSERT_MIN(3u, version)); #endif } else if (al_strscmp(interface, "wl_shm") == 0) { wl->shm = (struct wl_shm *)wl_registry_bind(registry, name, &wl_shm_interface, ASSERT_MIN(1u, version)); } else if (al_strscmp(interface, "wl_seat") == 0) { wl->seat = (struct wl_seat *)wl_registry_bind(registry, name, &wl_seat_interface, ASSERT_MIN(5u, version)); wl_seat_add_listener(wl->seat, &seat_listener, wl); } else if (al_strscmp(interface, "xdg_wm_base") == 0) { wl->xdg_wm_base = (struct xdg_wm_base *)wl_registry_bind(registry, name, &xdg_wm_base_interface, ASSERT_MIN(1u, version)); xdg_wm_base_add_listener(wl->xdg_wm_base, &xdg_wm_base_listener, wl); } else if (al_strscmp(interface, "zxdg_decoration_manager_v1") == 0) { wl->zxdg_decoration_manager = (struct zxdg_decoration_manager_v1 *)wl_registry_bind(registry, name, &zxdg_decoration_manager_v1_interface, ASSERT_MIN(1u, version)); } else if (al_strscmp(interface, "wp_cursor_shape_manager_v1") == 0) { wl->cursor_shape_manager = (struct wp_cursor_shape_manager_v1 *)wl_registry_bind(registry, name, &wp_cursor_shape_manager_v1_interface, ASSERT_MIN(1u, version)); } else if (al_strscmp(interface, "xdg_toplevel_icon_manager_v1") == 0) { wl->icon.manager = (struct xdg_toplevel_icon_manager_v1 *)wl_registry_bind(registry, name, &xdg_toplevel_icon_manager_v1_interface, ASSERT_MIN(1u, version)); } else if (al_strscmp(interface, "zwlr_layer_shell_v1") == 0) { wl->layer_shell = (struct zwlr_layer_shell_v1 *)wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, ASSERT_MIN(3u, version)); } } static void context_global_remove(void *data, struct wl_registry *registry, u32 name) { (void)data; (void)registry; (void)name; } static const struct wl_registry_listener context_registry_listener = { .global = context_global_add, .global_remove = context_global_remove }; static void handle_surface_enter(void *data, struct wl_surface *surface, struct wl_output *output) { (void)data; (void)surface; (void)output; } static void handle_surface_leave(void *data, struct wl_surface *surface, struct wl_output *output) { (void)data; (void)surface; (void)output; } #ifdef SUPPORT_PREFERRED_BUFFER_SCALE static void preferred_buffer_scale(void *data, struct wl_surface *surface, s32 factor) { (void)data; (void)surface; (void)factor; } static void preferred_buffer_transform(void *data, struct wl_surface *surface, u32 transform) { (void)data; (void)surface; (void)transform; } #endif static const struct wl_surface_listener surface_listener = { .enter = handle_surface_enter, .leave = handle_surface_leave, #ifdef SUPPORT_PREFERRED_BUFFER_SCALE .preferred_buffer_scale = preferred_buffer_scale, .preferred_buffer_transform = preferred_buffer_transform #endif }; static bool check_and_set_dimensions(struct stl_window_wayland *wl, s32 event_width, s32 event_height) { if (event_width <= 0 || event_height <= 0) { return false; } u32 scale = wl->w.scale; u32 width = event_width * scale; u32 height = event_height * scale; if (width == wl->w.width && height == wl->w.height) { return false; } wl->w.width = width; wl->w.height = height; wl->geom_width = event_width; wl->geom_height = event_height; return true; } static void send_resize_event(struct stl_window_wayland *wl) { stl_window_send_resize_event(&wl->w, wl->w.width, wl->w.height); } static void handle_xdg_surface_configure(void *data, struct xdg_surface *surface, u32 serial) { struct stl_window_wayland *wl = (struct stl_window_wayland *)data; al_assert(surface == wl->xdg_surface); 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 = { .configure = handle_xdg_surface_configure }; static void handle_xdg_toplevel_configure(void *data, struct xdg_toplevel *toplevel, s32 width, s32 height, struct wl_array *states) { struct stl_window_wayland *wl = (struct stl_window_wayland *)data; (void)toplevel; (void)states; // 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_configure = true; } } static void handle_xdg_toplevel_close(void *data, struct xdg_toplevel *toplevel) { struct stl_window_wayland *wl = (struct stl_window_wayland *)data; (void)toplevel; stl_window_send_should_close_event(&wl->w); } static const struct xdg_toplevel_listener xdg_toplevel_listener = { .configure = handle_xdg_toplevel_configure, .close = handle_xdg_toplevel_close }; static void handle_layer_surface_configure(void *data, struct zwlr_layer_surface_v1 *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(wl->layer_surface, serial); } static void handle_layer_surface_closed(void *data, struct zwlr_layer_surface_v1 *surface) { struct stl_window_wayland *wl = (struct stl_window_wayland *)data; (void)surface; stl_window_send_should_close_event(&wl->w); } static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { .configure = handle_layer_surface_configure, .closed = handle_layer_surface_closed }; #ifdef STELA_API_OPENGL static bool wl_egl_init(struct stl_window_wayland *wl) { wl->egl_window = wl_egl_window_create(wl->surface, wl->w.width, wl->w.height); EGLConfig config; if (!stl_init_egl_context(&wl->egl, &global.egl, 0, &config)) { return false; } wl->w.get_gl_proc_address = eglGetProcAddress; wl->w.egl_display = wl->egl.display; wl->w.egl_context = wl->egl.context; const EGLint surface_attr[] = { EGL_RENDER_BUFFER, EGL_BACK_BUFFER, EGL_NONE }; wl->egl.surface = eglCreateWindowSurface(wl->egl.display, config, (EGLNativeWindowType)wl->egl_window, surface_attr); eglSurfaceAttrib(wl->egl.display, wl->egl.surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED); return true; } #endif static bool create_context_internal(struct stl_window_wayland *wl) { wl->display = global.display; wl->fds[0].fd = global.display_fd; wl->fds[0].events = POLLIN; #if defined STELA_POLL_INLINE && defined STELA_PAUSE wl->fds[1].fd = nn_eventfd(0, 0); wl->fds[1].events = POLLIN; #endif wl->registry = wl_display_get_registry(wl->display); wl_registry_add_listener(wl->registry, &context_registry_listener, wl); wl_display_roundtrip(wl->display); wl_display_roundtrip(wl->display); return true; } static struct stl_monitor_wayland *monitor_from_name(const char *name) { for (u32 i = 0; i < global.monitors.count; i++) { struct stl_monitor_wayland *monitor = (struct stl_monitor_wayland *)al_array_at(global.monitors, i); if (al_strncmp(monitor->m.name, name, STELA_MAX_MONITOR_NAME) == 0) { return monitor; } } return NULL; } // https://wayland-book.com/surfaces/shared-memory.html static s32 create_shm_file(char *name, char *suffix) { s32 retries = 100; do { al_random_hex_chars(suffix); --retries; s32 fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); if (fd >= 0) { shm_unlink(name); return fd; } } while (retries > 0 && errno == EEXIST); return -1; } static s32 allocate_shm_file(char *name, char *suffix, size_t size) { s32 fd = create_shm_file(name, suffix); if (fd < 0) return -1; s32 ret = 0; do { ret = ftruncate(fd, size); } while (ret < 0 && errno == EINTR); if (ret < 0) { close(fd); return -1; } return fd; } static struct xdg_toplevel_icon_v1 *create_icon(struct stl_window_wayland *wl, u32 size) { u32 stride = size * 4; u32 shm_pool_size = size * stride; char shm_name[] = "/wl_shm-XXXXXXXX"; s32 fd = allocate_shm_file(shm_name, shm_name + sizeof(shm_name) - 9, shm_pool_size); if (fd < 0) { log_error("Failed to allocate %s.", shm_name); return NULL; } u8 *data = mmap(NULL, shm_pool_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (!data) { log_error("mmap(%s) failed (%s).", shm_name, nn_strerror(errno)); return NULL; } wl->icon.pool = wl_shm_create_pool(wl->shm, fd, shm_pool_size); if (!wl->icon.pool) { log_error("Failed to create pool for %s.", shm_name); return NULL; } u32 format = WL_SHM_FORMAT_XRGB8888; wl->icon.buffer = wl_shm_pool_create_buffer(wl->icon.pool, 0, size, size, stride, format); if (!wl->icon.buffer) { log_error("Failed to create %ux%u(%u) buffer in %s.", size, size, format, shm_name); return NULL; } al_assert(global.icon.size <= shm_pool_size); al_memcpy(data, global.icon.data, global.icon.size); struct xdg_toplevel_icon_v1 *icon = xdg_toplevel_icon_manager_v1_create_icon(wl->icon.manager); if (!icon) { log_error("Failed to create icon."); return NULL; } xdg_toplevel_icon_v1_add_buffer(icon, wl->icon.buffer, 1); return icon; } static bool create_toplevel(struct stl_window_wayland *wl, s32 scale, const char *name, const char *app_id) { wl->w.scale = scale; wl_surface_set_buffer_scale(wl->surface, scale); wl->geom_width = wl->w.width; wl->geom_height = wl->w.height; wl->w.width *= scale; wl->w.height *= scale; wl->pending_configure = false; wl->xdg_surface = xdg_wm_base_get_xdg_surface(wl->xdg_wm_base, wl->surface); if (!wl->xdg_surface) { return false; } xdg_surface_add_listener(wl->xdg_surface, &xdg_surface_listener, wl); wl->xdg_toplevel = xdg_surface_get_toplevel(wl->xdg_surface); if (!wl->xdg_toplevel) { return false; } xdg_toplevel_add_listener(wl->xdg_toplevel, &xdg_toplevel_listener, wl); xdg_toplevel_set_title(wl->xdg_toplevel, name); xdg_toplevel_set_app_id(wl->xdg_toplevel, app_id); xdg_toplevel_set_min_size(wl->xdg_toplevel, STELA_MIN_WIDTH, STELA_MIN_HEIGHT); // 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. Note that it's meant to be set to the unscaled width and height. // For our application, if using the event buffer, window events and rendering are on // seperate threads. This means we must manually control the surface geometry for responsiveness. xdg_surface_set_window_geometry(wl->xdg_surface, 0, 0, wl->geom_width, wl->geom_height); if (wl->zxdg_decoration_manager) { wl->zxdg_toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration( wl->zxdg_decoration_manager, wl->xdg_toplevel); u32 mode = ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE; zxdg_toplevel_decoration_v1_set_mode(wl->zxdg_toplevel_decoration, mode); wl->decorated = true; } wl->cursor.shape = STELA_CURSOR_NORMAL; #ifdef STELA_MOUSE2_DRAG wl->cursor.grabbed_by_toplevel_move = false; #endif if (wl->icon.manager && global.icon.data) { wl->icon.icon = create_icon(wl, 256); if (wl->icon.icon) { xdg_toplevel_icon_manager_v1_set_icon(wl->icon.manager, wl->xdg_toplevel, wl->icon.icon); } } return true; } static bool create_layer_shell(struct stl_window_wayland *wl, struct stl_monitor_wayland *monitor, const char *name) { u32 scale = monitor->m.scale; wl->w.scale = scale; wl_surface_set_buffer_scale(wl->surface, scale); u32 surface = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM; u32 anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; wl->layer_surface = zwlr_layer_shell_v1_get_layer_surface( wl->layer_shell, wl->surface, monitor->output, surface, name); if (!wl->layer_surface) { return false; } zwlr_layer_surface_v1_set_margin(wl->layer_surface, 0, 0, 0, 0); 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_NORMAL; wl->decorated = false; return true; } static bool window_wayland_create_window(struct stl_window *window, u32 width, u32 height, u32 flags, const char *monitor, const char *name, const char *app_id) { struct stl_window_wayland *wl = (struct stl_window_wayland *)window; if (!create_context_internal(wl)) { goto err; } wl->surface = wl_compositor_create_surface(wl->compositor); 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 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); if (!create_toplevel(wl, selected ? selected->m.scale : 1, name, app_id)) { log_error("Failed to create toplevel surface."); goto err; } } else { selected = monitor_from_name(monitor); if (!selected || !create_layer_shell(wl, selected, name)) { log_error("Failed to create layer shell surface."); goto err; } } 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; // Keep this before egl_init() for optimal window creation speed. 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 return true; err: log_error("Failed to create window."); wl->w.free((struct stl_window **)&wl); return false; } static void window_wayland_resize(struct stl_window *window, u32 width, u32 height) { struct stl_window_wayland *wl = (struct stl_window_wayland *)window; (void)wl; (void)width; (void)height; } static void window_wayland_resize_internal(struct stl_window *window, u32 width, u32 height) { struct stl_window_wayland *wl = (struct stl_window_wayland *)window; #ifdef STELA_API_OPENGL if (stl_egl_is_ready(&wl->egl)) { wl_egl_window_resize(wl->egl_window, width, height, 0, 0); } #else (void)wl; (void)width; (void)height; #endif } static void window_wayland_toggle_fullscreen(struct stl_window *window) { struct stl_window_wayland *wl = (struct stl_window_wayland *)window; wl->w.fullscreen = !wl->w.fullscreen; // https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/377 if (wl->w.fullscreen) { xdg_toplevel_set_fullscreen(wl->xdg_toplevel, NULL); } else { xdg_toplevel_unset_fullscreen(wl->xdg_toplevel); } } 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) { struct stl_window_wayland *wl = (struct stl_window_wayland *)window; if (!wl->zxdg_toplevel_decoration) return; if (!enabled && wl->decorated) { u32 mode = ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; zxdg_toplevel_decoration_v1_set_mode(wl->zxdg_toplevel_decoration, mode); wl->decorated = false; } else if (enabled && !wl->decorated) { u32 mode = ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE; zxdg_toplevel_decoration_v1_set_mode(wl->zxdg_toplevel_decoration, mode); wl->decorated = true; } } #ifdef STELA_POLL_INLINE static void window_wayland_poll(struct stl_window *window, bool block) { struct stl_window_wayland *wl = (struct stl_window_wayland *)window; display_prepare_read(wl->display); stl_poll_common(wl->fds, block); } #ifdef STELA_PAUSE static void window_wayland_wake(struct stl_window *window) { struct stl_window_wayland *wl = (struct stl_window_wayland *)window; stl_wake_common(wl->fds); } #endif #endif static void window_wayland_process_events(struct stl_window *window) { struct stl_window_wayland *wl = (struct stl_window_wayland *)window; if (stl_process_events_common(&wl->w, wl->fds)) { wl_display_read_events(wl->display); wl_display_dispatch_pending(wl->display); } else { wl_display_cancel_read(wl->display); } } #ifdef STELA_PAUSE static bool window_wayland_should_refresh(struct stl_window *window) { return stl_should_refresh_common(window); } #endif static void destroy_surface_internal(struct stl_window_wayland *wl) { if (wl->xdg_surface) { if (wl->zxdg_toplevel_decoration) zxdg_toplevel_decoration_v1_destroy(wl->zxdg_toplevel_decoration); if (wl->xdg_toplevel) xdg_toplevel_destroy(wl->xdg_toplevel); xdg_surface_destroy(wl->xdg_surface); } else if (wl->layer_surface) { zwlr_layer_surface_v1_destroy(wl->layer_surface); } #ifdef STELA_API_OPENGL if (wl->egl_window) { stl_egl_destroy_surface(&wl->egl); wl_egl_window_destroy(wl->egl_window); } #endif if (wl->surface) wl_surface_destroy(wl->surface); } void window_wayland_free(struct stl_window **window) { struct stl_window_wayland *wl = (struct stl_window_wayland *)*window; if (!wl->display) return; 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_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->icon.manager) { if (wl->icon.icon) xdg_toplevel_icon_v1_destroy(wl->icon.icon); if (wl->icon.buffer) wl_buffer_destroy(wl->icon.buffer); if (wl->icon.pool) wl_shm_pool_destroy(wl->icon.pool); xdg_toplevel_icon_manager_v1_destroy(wl->icon.manager); } if (wl->keyboard) wl_keyboard_destroy(wl->keyboard); destroy_surface_internal(wl); if (wl->zxdg_decoration_manager) zxdg_decoration_manager_v1_destroy(wl->zxdg_decoration_manager); if (wl->xdg_wm_base) xdg_wm_base_destroy(wl->xdg_wm_base); if (wl->layer_shell) zwlr_layer_shell_v1_destroy(wl->layer_shell); if (wl->shm) wl_shm_destroy(wl->shm); if (wl->compositor) wl_compositor_destroy(wl->compositor); #ifdef STELA_API_OPENGL if (stl_egl_is_ready(&wl->egl)) stl_egl_destroy_context(&wl->egl); #endif al_free(wl); *window = NULL; } #if defined STELA_API_VULKAN static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL window_wayland_get_vk_proc_address(VkInstance inst, const char *name) { return stl_get_vk_proc_address(inst, name); } static VkResult window_wayland_vk_create_surface(void *window, VkInstance inst, VkSurfaceKHR *surface) { 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 pVkCreateWaylandSurfaceKHR; #ifdef VK_FORCE_DYNAMIC_LOAD pVkCreateWaylandSurfaceKHR = (PFN_vkCreateWaylandSurfaceKHR)stl_get_vk_proc_address(inst, "vkCreateWaylandSurfaceKHR"); if (!pVkCreateWaylandSurfaceKHR) { return VK_ERROR_EXTENSION_NOT_PRESENT; } #else pVkCreateWaylandSurfaceKHR = vkCreateWaylandSurfaceKHR; #endif VkResult ret = pVkCreateWaylandSurfaceKHR(inst, &info, NULL, surface); if (ret != VK_SUCCESS) { log_error("Failed to create Wayland Vulkan surface."); } return ret; } static VkResult window_wayland_vk_destroy_surface(VkInstance inst, VkSurfaceKHR surface) { return stl_vk_destroy_surface(inst, surface); } static const char *const *window_wayland_vk_get_extensions(u32 *num) { *num = 2; return global.vk_extensions; } #elif defined STELA_API_OPENGL static bool window_wayland_gl_loader_load(void *window) { struct stl_window_wayland *wl = (struct stl_window_wayland *)window; return stl_gl_loader_load(wl->w.get_gl_proc_address); } static bool window_wayland_gl_make_current(void *window) { struct stl_window_wayland *wl = (struct stl_window_wayland *)window; return stl_egl_make_current(&wl->egl); } static void window_wayland_gl_release_current(void *window) { struct stl_window_wayland *wl = (struct stl_window_wayland *)window; stl_egl_release_current(&wl->egl); } static void window_wayland_gl_swap_buffers(void *window) { struct stl_window_wayland *wl = (struct stl_window_wayland *)window; stl_egl_swap_buffers(&wl->egl); } #endif struct stl_window *stl_window_create(void) { log_info("Creating Wayland window."); struct stl_window_wayland *wl = al_alloc_object(struct stl_window_wayland); stl_window_init(&wl->w); wl->w.fullscreen = false; wl->w.create_window = window_wayland_create_window; 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; #ifdef STELA_PAUSE wl->w.wake = window_wayland_wake; #endif #endif wl->w.process_events = window_wayland_process_events; #ifdef STELA_PAUSE wl->w.should_refresh = window_wayland_should_refresh; #endif wl->w.free = window_wayland_free; #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_destroy_surface = window_wayland_vk_destroy_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; wl->w.gl_release_current = window_wayland_gl_release_current; wl->w.gl_swap_buffers = window_wayland_gl_swap_buffers; #else log_warn("Window created without graphics backend."); #endif return (struct stl_window *)wl; }