//======================================================================== // GLFW 3.4 Wayland - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2014 Jonas Ã…dahl // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== // It is fine to use C99 in this file because it will not be built with VS //======================================================================== #define _GNU_SOURCE #include "internal.h" #include "backend_utils.h" #include "wl_client_side_decorations.h" #include "linux_desktop_settings.h" #include "../kitty/monotonic.h" #include "wl_text_input.h" #include "wayland-text-input-unstable-v3-client-protocol.h" #include "wayland-pointer-gestures-unstable-v1-client-protocol.h" #include #include #include #include #include #include #include #include #include // errno.h needed for BSD code paths #include // Needed for the BTN_* defines #ifdef __has_include #if __has_include() #include #elif __has_include() #include #endif #else #include #endif #define debug debug_rendering #define x window->wl.allCursorPosX #define y window->wl.allCursorPosY static _GLFWwindow* get_window_from_surface(struct wl_surface* surface) { if (!surface) return NULL; _GLFWwindow *ans = wl_surface_get_user_data(surface); if (ans) { const _GLFWwindow *w = _glfw.windowListHead; while (w) { if (w == ans) return ans; w = w->next; } } return NULL; } static void pointerHandleEnter( void* data UNUSED, struct wl_pointer* pointer UNUSED, uint32_t serial, struct wl_surface* surface, wl_fixed_t sx, wl_fixed_t sy ) { _GLFWwindow* window = get_window_from_surface(surface); if (!window) return; _glfw.wl.serial = serial; _glfw.wl.input_serial = serial; _glfw.wl.pointer_serial = serial; _glfw.wl.pointer_enter_serial = serial; _glfw.wl.pointerFocus = window; window->wl.allCursorPosX = wl_fixed_to_double(sx); window->wl.allCursorPosY = wl_fixed_to_double(sy); if (surface != window->wl.surface) { csd_handle_pointer_event(window, -2, -2, surface); } else { window->wl.decorations.focus = CENTRAL_WINDOW; window->wl.hovered = true; window->wl.cursorPosX = x; window->wl.cursorPosY = y; _glfwPlatformSetCursor(window, window->wl.currentCursor); _glfwInputCursorEnter(window, true); } } static void pointerHandleLeave(void* data UNUSED, struct wl_pointer* pointer UNUSED, uint32_t serial, struct wl_surface* surface) { _GLFWwindow* window = _glfw.wl.pointerFocus; if (!window) return; _glfw.wl.serial = serial; _glfw.wl.pointerFocus = NULL; if (window->wl.surface == surface) { window->wl.hovered = false; _glfwInputCursorEnter(window, false); _glfw.wl.cursorPreviousShape = GLFW_INVALID_CURSOR; } else csd_handle_pointer_event(window, -3, -3, surface); } static void pointerHandleMotion(void* data UNUSED, struct wl_pointer* pointer UNUSED, uint32_t time UNUSED, wl_fixed_t sx, wl_fixed_t sy) { _GLFWwindow* window = _glfw.wl.pointerFocus; if (!window || window->cursorMode == GLFW_CURSOR_DISABLED) return; window->wl.allCursorPosX = wl_fixed_to_double(sx); window->wl.allCursorPosY = wl_fixed_to_double(sy); if (window->wl.decorations.focus != CENTRAL_WINDOW) { csd_handle_pointer_event(window, -1, -1, NULL); } else { window->wl.cursorPosX = x; window->wl.cursorPosY = y; _glfwInputCursorPos(window, x, y); _glfw.wl.cursorPreviousShape = GLFW_INVALID_CURSOR; } } static void pointerHandleButton(void* data UNUSED, struct wl_pointer* pointer UNUSED, uint32_t serial, uint32_t time UNUSED, uint32_t button, uint32_t state) { glfw_cancel_momentum_scroll(); _glfw.wl.serial = serial; _glfw.wl.input_serial = serial; _glfw.wl.pointer_serial = serial; _GLFWwindow* window = _glfw.wl.pointerFocus; if (!window) return; if (window->wl.decorations.focus != CENTRAL_WINDOW) { csd_handle_pointer_event(window, button, state, NULL); return; } /* Makes left, right and middle 0, 1 and 2. Overall order follows evdev * codes. */ int glfwButton = button - BTN_LEFT; _glfwInputMouseClick( window, glfwButton, state == WL_POINTER_BUTTON_STATE_PRESSED ? GLFW_PRESS : GLFW_RELEASE, _glfw.wl.xkb.states.modifiers); } #undef x #undef y #define info (window->wl.pointer_curr_axis_info) static void pointer_handle_axis_common(enum _GLFWWaylandAxisEvent type, uint32_t axis, wl_fixed_t value) { _GLFWwindow* window = _glfw.wl.pointerFocus; if (!window || window->wl.decorations.focus != CENTRAL_WINDOW) return; float fval = (float) wl_fixed_to_double(value); #define CASE(type, type_const, axis, fval) \ case type_const: \ if (info.type.axis##_axis_type == AXIS_EVENT_UNKNOWN) { \ info.type.axis##_axis_type = type_const; info.type.axis = 0.f; } \ info.type.axis += fval; break; if (window) { switch ((enum wl_pointer_axis)axis) { case WL_POINTER_AXIS_VERTICAL_SCROLL: switch (type) { case AXIS_EVENT_UNKNOWN: break; CASE(discrete, AXIS_EVENT_DISCRETE, y, -fval); // wheel event CASE(discrete, AXIS_EVENT_VALUE120, y, -fval); // wheel event higher res than plain discrete CASE(continuous, AXIS_EVENT_CONTINUOUS, y, -fval); // touchpad, etc. high res } break; case WL_POINTER_AXIS_HORIZONTAL_SCROLL: switch (type) { case AXIS_EVENT_UNKNOWN: break; CASE(discrete, AXIS_EVENT_DISCRETE, x, fval); // wheel event CASE(discrete, AXIS_EVENT_VALUE120, x, fval); // wheel event higher res than plain discrete CASE(continuous, AXIS_EVENT_CONTINUOUS, x, fval); // touchpad, etc. high res } break; } } #undef CASE } static void pointer_handle_axis(void *data UNUSED, struct wl_pointer *pointer UNUSED, uint32_t time, uint32_t axis, wl_fixed_t value) { _GLFWwindow* window = _glfw.wl.pointerFocus; if (!window) return; switch (axis) { case WL_POINTER_AXIS_VERTICAL_SCROLL: if (!info.y_start_time) info.y_start_time = ms_to_monotonic_t(time); break; case WL_POINTER_AXIS_HORIZONTAL_SCROLL: if (!info.x_start_time) info.x_start_time = ms_to_monotonic_t(time); break; } pointer_handle_axis_common(AXIS_EVENT_CONTINUOUS, axis, value); } static void pointer_handle_frame(void *data UNUSED, struct wl_pointer *pointer UNUSED) { _GLFWwindow* window = _glfw.wl.pointerFocus; if (!window) return; GLFWScrollEvent ev = {.keyboard_modifiers=_glfw.wl.xkb.states.modifiers}; bool found = false; if (info.discrete.y_axis_type != AXIS_EVENT_UNKNOWN) { ev.unscaled.y = info.discrete.y; if (info.discrete.y_axis_type == AXIS_EVENT_VALUE120) ev.offset_type = GLFW_SCROLL_OFFEST_V120; found = true; } else if (info.continuous.y_axis_type != AXIS_EVENT_UNKNOWN) { ev.offset_type = GLFW_SCROLL_OFFEST_HIGHRES; ev.unscaled.y = info.continuous.y; found = true; } if (info.discrete.x_axis_type != AXIS_EVENT_UNKNOWN) { ev.unscaled.x = info.discrete.x; if (info.discrete.x_axis_type == AXIS_EVENT_VALUE120) ev.offset_type = GLFW_SCROLL_OFFEST_V120; found = true; } else if (info.continuous.x_axis_type != AXIS_EVENT_UNKNOWN) { ev.offset_type = GLFW_SCROLL_OFFEST_HIGHRES; ev.unscaled.x = info.continuous.x; found = true; } bool stopped = info.y_stop_received || info.x_stop_received; if (!found && stopped) ev.offset_type = window->wl.prev_frame_offset_type; ev.unscaled.x *= -1; const double scale = ev.offset_type == GLFW_SCROLL_OFFEST_HIGHRES ? _glfwWaylandWindowScale(window) : 1; ev.x_offset = scale * ev.unscaled.x; ev.y_offset = scale * ev.unscaled.y; glfw_handle_scroll_event_for_momentum( window, &ev, stopped, info.source_type == WL_POINTER_AXIS_SOURCE_FINGER); window->wl.prev_frame_offset_type = ev.offset_type; /* clear pointer_curr_axis_info for next frame */ memset(&info, 0, sizeof(info)); } static void pointer_handle_axis_source(void* data UNUSED, struct wl_pointer* pointer UNUSED, uint32_t source) { _GLFWwindow* window = _glfw.wl.pointerFocus; if (!window) return; info.source_type = source; } static void pointer_handle_axis_stop(void *data UNUSED, struct wl_pointer *wl_pointer UNUSED, uint32_t time UNUSED, uint32_t axis) { _GLFWwindow* window = _glfw.wl.pointerFocus; if (!window) return; switch (axis) { case WL_POINTER_AXIS_VERTICAL_SCROLL: info.y_stop_received = true; info.y_stop_time = ms_to_monotonic_t(time); break; case WL_POINTER_AXIS_HORIZONTAL_SCROLL: info.x_stop_received = true; info.x_stop_time = ms_to_monotonic_t(time); break; } } static void pointer_handle_axis_discrete(void *data UNUSED, struct wl_pointer *pointer UNUSED, uint32_t axis, int32_t discrete) { pointer_handle_axis_common(AXIS_EVENT_DISCRETE, axis, wl_fixed_from_int(discrete)); } static void pointer_handle_axis_value120(void *data UNUSED, struct wl_pointer *pointer UNUSED, uint32_t axis, int32_t value120) { pointer_handle_axis_common(AXIS_EVENT_VALUE120, axis, wl_fixed_from_int(value120)); } static void pointer_handle_axis_relative_direction(void *data UNUSED, struct wl_pointer *pointer UNUSED, uint32_t axis UNUSED, uint32_t axis_relative_direction UNUSED) { } #undef info static const struct wl_pointer_listener pointerListener = { .enter = pointerHandleEnter, .leave = pointerHandleLeave, .motion = pointerHandleMotion, .button = pointerHandleButton, .axis = pointer_handle_axis, .frame = pointer_handle_frame, .axis_source = pointer_handle_axis_source, .axis_stop = pointer_handle_axis_stop, .axis_discrete = pointer_handle_axis_discrete, #ifdef WL_POINTER_AXIS_VALUE120_SINCE_VERSION .axis_value120 = pointer_handle_axis_value120, #endif #ifdef WL_POINTER_AXIS_RELATIVE_DIRECTION_SINCE_VERSION .axis_relative_direction = pointer_handle_axis_relative_direction, #endif }; static void keyboardHandleKeymap(void* data UNUSED, struct wl_keyboard* keyboard UNUSED, uint32_t format, int fd, uint32_t size) { char* mapStr; if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { _glfwInputError(GLFW_PLATFORM_ERROR, "Unknown keymap format: %u", format); close(fd); return; } mapStr = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); if (mapStr == MAP_FAILED) { close(fd); _glfwInputError(GLFW_PLATFORM_ERROR, "Mapping of keymap file descriptor failed: %u", format); return; } glfw_xkb_compile_keymap(&_glfw.wl.xkb, mapStr); munmap(mapStr, size); close(fd); } static bool needs_synthetic_key_repeat(void) { return _glfw.wl.keyboardRepeatRate > 0 && !_glfw.wl.has_key_repeat_events; } static void start_key_repeat_timer(bool initial) { #ifdef HAS_TIMER_FD (void)initial; struct itimerspec new_value = {.it_value={.tv_nsec = _glfw.wl.keyboardRepeatDelay}, .it_interval={.tv_nsec = (s_to_monotonic_t(1ll) / (monotonic_t)_glfw.wl.keyboardRepeatRate)}}; if (_glfw.wl.eventLoopData.key_repeat_fd > -1) timerfd_settime( _glfw.wl.eventLoopData.key_repeat_fd, 0, &new_value, NULL); #else monotonic_t interval = _glfw.wl.keyboardRepeatDelay; if (!initial) interval = (s_to_monotonic_t(1ll) / (monotonic_t)_glfw.wl.keyboardRepeatRate); changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, interval); toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 1); #endif } static void stop_key_repeat_timer(void) { #ifdef HAS_TIMER_FD struct itimerspec new_value = {0}; if (_glfw.wl.eventLoopData.key_repeat_fd > -1) timerfd_settime(_glfw.wl.eventLoopData.key_repeat_fd, 0, &new_value, NULL); #else toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 0); #endif } #ifndef HAS_TIMER_FD static void send_key_repeat_timer_event(id_type timer_id UNUSED, void *data UNUSED) { char b = 1; b += write(_glfw.wl.eventLoopData.key_repeat_fds[1], &b, 1); if (needs_synthetic_key_repeat()) start_key_repeat_timer(false); } #endif static void keyboardHandleEnter(void* data UNUSED, struct wl_keyboard* keyboard UNUSED, uint32_t serial, struct wl_surface* surface, struct wl_array* keys) { _GLFWwindow* window = get_window_from_surface(surface); if (!window) return; _glfw.wl.serial = serial; _glfw.wl.input_serial = serial; _glfw.wl.keyboard_enter_serial = serial; _glfw.wl.keyboardFocusId = window->id; _glfwInputWindowFocus(window, true); uint32_t* key; if (keys && _glfw.wl.keyRepeatInfo.key) { wl_array_for_each(key, keys) { if (*key == _glfw.wl.keyRepeatInfo.key) { if (needs_synthetic_key_repeat()) start_key_repeat_timer(true); break; } } } } static void keyboardHandleLeave(void* data UNUSED, struct wl_keyboard* keyboard UNUSED, uint32_t serial, struct wl_surface* surface UNUSED) { _GLFWwindow* window = _glfwWindowForId(_glfw.wl.keyboardFocusId); if (!window) return; _glfw.wl.serial = serial; _glfw.wl.keyboardFocusId = 0; _glfwInputWindowFocus(window, false); stop_key_repeat_timer(); } static void keyboardHandleKey(void* data UNUSED, struct wl_keyboard* keyboard UNUSED, uint32_t serial, uint32_t time UNUSED, uint32_t key, uint32_t state) { glfw_cancel_momentum_scroll(); _GLFWwindow* window = _glfwWindowForId(_glfw.wl.keyboardFocusId); if (!window) return; int action = GLFW_PRESS; switch (state) { case WL_KEYBOARD_KEY_STATE_PRESSED: action = GLFW_PRESS; break; case WL_KEYBOARD_KEY_STATE_RELEASED: action = GLFW_RELEASE; break; #ifdef WL_KEYBOARD_KEY_STATE_REPEATED_SINCE_VERSION case WL_KEYBOARD_KEY_STATE_REPEATED: action = GLFW_REPEAT; break; #endif } _glfw.wl.serial = serial; _glfw.wl.input_serial = serial; glfw_xkb_handle_key_event(window, &_glfw.wl.xkb, key, action); if (action == GLFW_PRESS && needs_synthetic_key_repeat() && glfw_xkb_should_repeat(&_glfw.wl.xkb, key)) { _glfw.wl.keyRepeatInfo.key = key; _glfw.wl.keyRepeatInfo.keyboardFocusId = window->id; start_key_repeat_timer(true); } else if (action == GLFW_RELEASE && key == _glfw.wl.keyRepeatInfo.key) { _glfw.wl.keyRepeatInfo.key = 0; stop_key_repeat_timer(); } } static void keyboardHandleModifiers(void* data UNUSED, struct wl_keyboard* keyboard UNUSED, uint32_t serial, uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group) { _glfw.wl.serial = serial; _glfw.wl.input_serial = serial; glfw_xkb_update_modifiers(&_glfw.wl.xkb, modsDepressed, modsLatched, modsLocked, 0, 0, group); } static void keyboardHandleRepeatInfo(void* data UNUSED, struct wl_keyboard* keyboard, int32_t rate, int32_t delay) { if (keyboard != _glfw.wl.keyboard) return; _glfw.wl.keyboardRepeatRate = rate; _glfw.wl.keyboardRepeatDelay = ms_to_monotonic_t(delay); } static const struct wl_keyboard_listener keyboardListener = { keyboardHandleKeymap, keyboardHandleEnter, keyboardHandleLeave, keyboardHandleKey, keyboardHandleModifiers, keyboardHandleRepeatInfo, }; static void holdGestureHandleBegin(void *data UNUSED, struct zwp_pointer_gesture_hold_v1 *gesture UNUSED, uint32_t serial UNUSED, uint32_t time UNUSED, struct wl_surface *surface UNUSED, uint32_t fingers UNUSED) { glfw_cancel_momentum_scroll(); } static void holdGestureHandleEnd(void *data UNUSED, struct zwp_pointer_gesture_hold_v1 *gesture UNUSED, uint32_t serial UNUSED, uint32_t time UNUSED, int32_t cancelled UNUSED) {} static const struct zwp_pointer_gesture_hold_v1_listener holdGestureListener = { .begin = holdGestureHandleBegin, .end = holdGestureHandleEnd, }; static void seatHandleCapabilities(void* data UNUSED, struct wl_seat* seat, enum wl_seat_capability caps) { if ((caps & WL_SEAT_CAPABILITY_POINTER) && !_glfw.wl.pointer) { _glfw.wl.pointer = wl_seat_get_pointer(seat); wl_pointer_add_listener(_glfw.wl.pointer, &pointerListener, NULL); if (_glfw.wl.wp_cursor_shape_manager_v1) { if (_glfw.wl.wp_cursor_shape_device_v1) wp_cursor_shape_device_v1_destroy(_glfw.wl.wp_cursor_shape_device_v1); _glfw.wl.wp_cursor_shape_device_v1 = NULL; _glfw.wl.wp_cursor_shape_device_v1 = wp_cursor_shape_manager_v1_get_pointer(_glfw.wl.wp_cursor_shape_manager_v1, _glfw.wl.pointer); } if (_glfw.wl.pointer_gestures) { _glfw.wl.pointer_gesture_hold = zwp_pointer_gestures_v1_get_hold_gesture(_glfw.wl.pointer_gestures, _glfw.wl.pointer); zwp_pointer_gesture_hold_v1_add_listener(_glfw.wl.pointer_gesture_hold, &holdGestureListener, NULL); } } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && _glfw.wl.pointer) { if (_glfw.wl.wp_cursor_shape_device_v1) wp_cursor_shape_device_v1_destroy(_glfw.wl.wp_cursor_shape_device_v1); _glfw.wl.wp_cursor_shape_device_v1 = NULL; if (_glfw.wl.pointer_gesture_hold) { zwp_pointer_gesture_hold_v1_destroy(_glfw.wl.pointer_gesture_hold); _glfw.wl.pointer_gesture_hold = NULL; } wl_pointer_destroy(_glfw.wl.pointer); _glfw.wl.pointer = NULL; if (_glfw.wl.cursorAnimationTimer) toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 0); } if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !_glfw.wl.keyboard) { _glfw.wl.keyboard = wl_seat_get_keyboard(seat); wl_keyboard_add_listener(_glfw.wl.keyboard, &keyboardListener, NULL); } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && _glfw.wl.keyboard) { wl_keyboard_destroy(_glfw.wl.keyboard); _glfw.wl.keyboard = NULL; _glfw.wl.keyboardFocusId = 0; stop_key_repeat_timer(); } } static void seatHandleName(void* data UNUSED, struct wl_seat* seat UNUSED, const char* name UNUSED) { } static const struct wl_seat_listener seatListener = { seatHandleCapabilities, seatHandleName, }; static void wmBaseHandlePing(void* data UNUSED, struct xdg_wm_base* wmBase, uint32_t serial) { xdg_wm_base_pong(wmBase, serial); } static const struct xdg_wm_base_listener wmBaseListener = { wmBaseHandlePing }; static void extBackgroundEffectHandleCapabilities(void* data UNUSED, struct ext_background_effect_manager_v1* manager UNUSED, uint32_t flags) { _glfw.wl.ext_background_effect_capabilities = flags; } static const struct ext_background_effect_manager_v1_listener extBackgroundEffectManagerListener = { extBackgroundEffectHandleCapabilities }; static void registryHandleGlobal(void* data UNUSED, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { #define is(x) strcmp(interface, x##_interface.name) == 0 if (is(wl_compositor)) { #ifdef WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION _glfw.wl.compositorVersion = MIN(WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION, (int)version); _glfw.wl.has_preferred_buffer_scale = _glfw.wl.compositorVersion >= WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION; #else _glfw.wl.compositorVersion = MIN(3, (int)version); #endif _glfw.wl.compositor = wl_registry_bind(registry, name, &wl_compositor_interface, _glfw.wl.compositorVersion); } else if (is(wl_subcompositor)) { _glfw.wl.subcompositor = wl_registry_bind(registry, name, &wl_subcompositor_interface, 1); } else if (is(wl_shm)) { _glfw.wl.shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); } else if (is(wl_output)) { _glfwAddOutputWayland(name, version); } else if (is(wl_seat)) { if (!_glfw.wl.seat) { _glfw.wl.has_key_repeat_events = false; #if defined(WL_KEYBOARD_KEY_STATE_REPEATED_SINCE_VERSION) _glfw.wl.seatVersion = MIN(WL_KEYBOARD_KEY_STATE_REPEATED_SINCE_VERSION, (int)version); _glfw.wl.has_key_repeat_events = _glfw.wl.seatVersion >= WL_KEYBOARD_KEY_STATE_REPEATED_SINCE_VERSION; #elif defined(WL_POINTER_AXIS_RELATIVE_DIRECTION_SINCE_VERSION) _glfw.wl.seatVersion = MIN(WL_POINTER_AXIS_RELATIVE_DIRECTION_SINCE_VERSION, (int)version); #elif defined(WL_POINTER_AXIS_VALUE120_SINCE_VERSION) _glfw.wl.seatVersion = MIN(WL_POINTER_AXIS_VALUE120_SINCE_VERSION, (int)version); #else _glfw.wl.seatVersion = MIN(WL_POINTER_AXIS_DISCRETE_SINCE_VERSION, version); #endif _glfw.wl.seat = wl_registry_bind(registry, name, &wl_seat_interface, _glfw.wl.seatVersion); wl_seat_add_listener(_glfw.wl.seat, &seatListener, NULL); } if (_glfw.wl.seat) { if (_glfw.wl.dataDeviceManager && !_glfw.wl.dataDevice) _glfwSetupWaylandDataDevice(); if (_glfw.wl.primarySelectionDeviceManager && !_glfw.wl.primarySelectionDevice) { _glfwSetupWaylandPrimarySelectionDevice(); } } } else if (is(xdg_wm_base)) { _glfw.wl.xdg_wm_base_version = 1; #ifdef XDG_TOPLEVEL_STATE_SUSPENDED_SINCE_VERSION _glfw.wl.xdg_wm_base_version = MIN(XDG_TOPLEVEL_STATE_SUSPENDED_SINCE_VERSION, (int)version); #elif defined(XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION) _glfw.wl.xdg_wm_base_version = MIN(XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION, (int)version); #endif _glfw.wl.wmBase = wl_registry_bind(registry, name, &xdg_wm_base_interface, _glfw.wl.xdg_wm_base_version); xdg_wm_base_add_listener(_glfw.wl.wmBase, &wmBaseListener, NULL); } else if (is(zxdg_decoration_manager_v1)) { _glfw.wl.decorationManager = wl_registry_bind(registry, name, &zxdg_decoration_manager_v1_interface, 1); } else if (is(zwp_relative_pointer_manager_v1)) { _glfw.wl.relativePointerManager = wl_registry_bind(registry, name, &zwp_relative_pointer_manager_v1_interface, 1); } else if (is(zwp_pointer_constraints_v1)) { _glfw.wl.pointerConstraints = wl_registry_bind(registry, name, &zwp_pointer_constraints_v1_interface, 1); } else if (is(zwp_text_input_manager_v3)) { _glfwWaylandBindTextInput(registry, name); } else if (is(wl_data_device_manager)) { _glfw.wl.dataDeviceManager = wl_registry_bind(registry, name, &wl_data_device_manager_interface, 3); if (_glfw.wl.seat && _glfw.wl.dataDeviceManager && !_glfw.wl.dataDevice) { _glfwSetupWaylandDataDevice(); } } else if (is(zwp_primary_selection_device_manager_v1)) { _glfw.wl.primarySelectionDeviceManager = wl_registry_bind(registry, name, &zwp_primary_selection_device_manager_v1_interface, 1); if (_glfw.wl.seat && _glfw.wl.primarySelectionDeviceManager && !_glfw.wl.primarySelectionDevice) { _glfwSetupWaylandPrimarySelectionDevice(); } } else if (is(wp_single_pixel_buffer_manager_v1)) { _glfw.wl.wp_single_pixel_buffer_manager_v1 = wl_registry_bind(registry, name, &wp_single_pixel_buffer_manager_v1_interface, 1); } else if (is(xdg_activation_v1)) { _glfw.wl.xdg_activation_v1 = wl_registry_bind(registry, name, &xdg_activation_v1_interface, 1); } else if (is(wp_cursor_shape_manager_v1)) { _glfw.wl.wp_cursor_shape_manager_v1 = wl_registry_bind(registry, name, &wp_cursor_shape_manager_v1_interface, 1); } else if (is(wp_fractional_scale_manager_v1)) { _glfw.wl.wp_fractional_scale_manager_v1 = wl_registry_bind(registry, name, &wp_fractional_scale_manager_v1_interface, 1); } else if (is(wp_viewporter)) { _glfw.wl.wp_viewporter = wl_registry_bind(registry, name, &wp_viewporter_interface, 1); } else if (is(org_kde_kwin_blur_manager)) { _glfw.wl.org_kde_kwin_blur_manager = wl_registry_bind(registry, name, &org_kde_kwin_blur_manager_interface, 1); } else if (is(ext_background_effect_manager_v1)) { _glfw.wl.ext_background_effect_manager_v1 = wl_registry_bind(registry, name, &ext_background_effect_manager_v1_interface, 1); ext_background_effect_manager_v1_add_listener(_glfw.wl.ext_background_effect_manager_v1, &extBackgroundEffectManagerListener, NULL); } else if (is(zwlr_layer_shell_v1)) { if (version >= 4) { _glfw.wl.zwlr_layer_shell_v1_version = version; _glfw.wl.zwlr_layer_shell_v1 = wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, version); } } else if (is(zwp_idle_inhibit_manager_v1)) { _glfw.wl.idle_inhibit_manager = wl_registry_bind(registry, name, &zwp_idle_inhibit_manager_v1_interface, 1); } else if (is(zwp_keyboard_shortcuts_inhibit_manager_v1)) { _glfw.wl.keyboard_shortcuts_inhibit_manager = wl_registry_bind(registry, name, &zwp_keyboard_shortcuts_inhibit_manager_v1_interface, 1); } else if (is(zwp_pointer_gestures_v1)) { if (version >= 3) _glfw.wl.pointer_gestures = wl_registry_bind(registry, name, &zwp_pointer_gestures_v1_interface, 3); } else if (is(xdg_toplevel_icon_manager_v1)) { _glfw.wl.xdg_toplevel_icon_manager_v1 = wl_registry_bind(registry, name, &xdg_toplevel_icon_manager_v1_interface, 1); } else if (is(xdg_system_bell_v1)) { _glfw.wl.xdg_system_bell_v1 = wl_registry_bind(registry, name, &xdg_system_bell_v1_interface, 1); } else if (is(xdg_toplevel_tag_manager_v1)) { _glfw.wl.xdg_toplevel_tag_manager_v1 = wl_registry_bind(registry, name, &xdg_toplevel_tag_manager_v1_interface, 1); } else if (is(xdg_toplevel_drag_manager_v1)) { _glfw.wl.xdg_toplevel_drag_manager_v1 = wl_registry_bind(registry, name, &xdg_toplevel_drag_manager_v1_interface, 1); } #undef is } static void registryHandleGlobalRemove(void *data UNUSED, struct wl_registry *registry UNUSED, uint32_t name) { _GLFWmonitor* monitor; for (int i = 0; i < _glfw.monitorCount; ++i) { monitor = _glfw.monitors[i]; if (monitor->wl.name == name) { for (_GLFWwindow *window = _glfw.windowListHead; window; window = window->next) { for (int m = window->wl.monitorsCount - 1; m >= 0; m--) { if (window->wl.monitors[m] == monitor) { remove_i_from_array(window->wl.monitors, m, window->wl.monitorsCount); } } } _glfwInputMonitor(monitor, GLFW_DISCONNECTED, 0); return; } } } static const struct wl_registry_listener registryListener = { registryHandleGlobal, registryHandleGlobalRemove }; GLFWAPI GLFWColorScheme glfwGetCurrentSystemColorTheme(bool query_if_unintialized) { return glfw_current_system_color_theme(query_if_unintialized); } static pid_t get_socket_peer_pid(int fd) { (void)fd; #ifdef __linux__ struct ucred ucred; socklen_t len = sizeof(struct ucred); return (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == -1) ? -1 : ucred.pid; #elif defined(LOCAL_PEERCRED) && defined(XUCRED_VERSION) struct xucred peercred; socklen_t peercredlen = sizeof(peercred); return (getsockopt(c->fd, LOCAL_PEERCRED, 1, (void *)&peercred, &peercredlen) == 0 && peercred.cr_version == XUCRED_VERSION) ? peercred.cr_pid : -1; #elif defined(LOCAL_PEERPID) pid_t pid; socklen_t pid_size = sizeof(pid); return getsockopt(client, SOL_LOCAL, LOCAL_PEERPID, &pid, &pid_size) == -1 ? -1 : pid; #else errno = ENOSYS; return -1; #endif } GLFWAPI pid_t glfwWaylandCompositorPID(void) { if (!_glfw.wl.display) return -1; int fd = wl_display_get_fd(_glfw.wl.display); if (fd < 0) return -1; return get_socket_peer_pid(fd); } const char* _glfwWaylandCompositorName(void) { static bool probed = false; if (!probed) { probed = true; static const size_t sz = 1024; _glfw.wl.compositor_name = malloc(sz); if (!_glfw.wl.compositor_name) return ""; char *ans = _glfw.wl.compositor_name; ans[0] = 0; pid_t cpid = glfwWaylandCompositorPID(); if (cpid < 0) return ans; snprintf(ans, sz, "/proc/%d/cmdline", cpid); int fd = open(ans, O_RDONLY | O_CLOEXEC); if (fd < 0) { ans[0] = 0; } else { ssize_t n; while (true) { n = read(fd, ans, sz-1); if (n < 0 && errno == EINTR) continue; close(fd); break; } ans[n < 0 ? 0 : n] = 0; } } return _glfw.wl.compositor_name ? _glfw.wl.compositor_name : ""; } ////////////////////////////////////////////////////////////////////////// ////// GLFW platform API ////// ////////////////////////////////////////////////////////////////////////// static const char* get_compositor_missing_capabilities(void) { #define C(title, x) if (!_glfw.wl.x) p += snprintf(p, sizeof(buf) - (p - buf), "%s ", #title); static char buf[512]; char *p = buf; *p = 0; C(viewporter, wp_viewporter); C(fractional_scale, wp_fractional_scale_manager_v1); if (!_glfw.wl.org_kde_kwin_blur_manager && !_glfw.wl.ext_background_effect_manager_v1) p += snprintf(p, sizeof(buf) - (p - buf), "%s ", "blur"); C(server_side_decorations, decorationManager); C(cursor_shape, wp_cursor_shape_manager_v1); C(layer_shell, zwlr_layer_shell_v1); C(single_pixel_buffer, wp_single_pixel_buffer_manager_v1); C(preferred_scale, has_preferred_buffer_scale); C(idle_inhibit, idle_inhibit_manager); C(icon, xdg_toplevel_icon_manager_v1); C(bell, xdg_system_bell_v1); C(window-tag, xdg_toplevel_tag_manager_v1); C(keyboard_shortcuts_inhibit, keyboard_shortcuts_inhibit_manager); C(key-repeat, has_key_repeat_events); C(top_level_drag, xdg_toplevel_drag_manager_v1); C(pointer_gestures, pointer_gestures); #define P(x) p += snprintf(p, sizeof(buf) - (p - buf), "%s ", x); if (_glfw.wl.xdg_wm_base_version < 6) P("window-state-suspended"); if (_glfw.wl.xdg_wm_base_version < 5) P("window-capabilities"); #undef P #undef C while (p > buf && (p - 1)[0] == ' ') { p--; *p = 0; } return buf; } GLFWAPI const char* glfwWaylandMissingCapabilities(void) { return get_compositor_missing_capabilities(); } int _glfwPlatformInit(bool *supports_window_occlusion) { int i; _GLFWmonitor* monitor; _glfw.wl.cursor.handle = _glfw_dlopen("libwayland-cursor.so.0"); if (!_glfw.wl.cursor.handle) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Failed to open libwayland-cursor"); return false; } glfw_dlsym(_glfw.wl.cursor.theme_load, _glfw.wl.cursor.handle, "wl_cursor_theme_load"); glfw_dlsym(_glfw.wl.cursor.theme_destroy, _glfw.wl.cursor.handle, "wl_cursor_theme_destroy"); glfw_dlsym(_glfw.wl.cursor.theme_get_cursor, _glfw.wl.cursor.handle, "wl_cursor_theme_get_cursor"); glfw_dlsym(_glfw.wl.cursor.image_get_buffer, _glfw.wl.cursor.handle, "wl_cursor_image_get_buffer"); _glfw.wl.egl.handle = _glfw_dlopen("libwayland-egl.so.1"); if (!_glfw.wl.egl.handle) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Failed to open libwayland-egl"); return false; } glfw_dlsym(_glfw.wl.egl.window_create, _glfw.wl.egl.handle, "wl_egl_window_create"); glfw_dlsym(_glfw.wl.egl.window_destroy, _glfw.wl.egl.handle, "wl_egl_window_destroy"); glfw_dlsym(_glfw.wl.egl.window_resize, _glfw.wl.egl.handle, "wl_egl_window_resize"); _glfw.wl.display = wl_display_connect(NULL); if (!_glfw.wl.display) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Failed to connect to display"); return false; } if (!initPollData(&_glfw.wl.eventLoopData, wl_display_get_fd(_glfw.wl.display))) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Failed to initialize event loop data"); } glfw_dbus_init(&_glfw.wl.dbus, &_glfw.wl.eventLoopData); glfw_initialize_desktop_settings(); #ifndef HAS_TIMER_FD _glfw.wl.keyRepeatInfo.keyRepeatTimer = addTimer(&_glfw.wl.eventLoopData, "wayland-key-repeat", ms_to_monotonic_t(500ll), 0, true, send_key_repeat_timer_event, NULL, NULL); #endif _glfw.wl.cursorAnimationTimer = addTimer(&_glfw.wl.eventLoopData, "wayland-cursor-animation", ms_to_monotonic_t(500ll), 0, true, animateCursorImage, NULL, NULL); _glfw.wl.registry = wl_display_get_registry(_glfw.wl.display); wl_registry_add_listener(_glfw.wl.registry, ®istryListener, NULL); if (!glfw_xkb_create_context(&_glfw.wl.xkb)) return false; // Sync so we got all registry objects wl_display_roundtrip(_glfw.wl.display); _glfwWaylandInitTextInput(); // Sync so we got all initial output events wl_display_roundtrip(_glfw.wl.display); for (i = 0; i < _glfw.monitorCount; ++i) { monitor = _glfw.monitors[i]; if (monitor->widthMM <= 0 || monitor->heightMM <= 0) { // If Wayland does not provide a physical size, assume the default 96 DPI monitor->widthMM = (int) (monitor->modes[monitor->wl.currentMode].width * 25.4f / 96.f); monitor->heightMM = (int) (monitor->modes[monitor->wl.currentMode].height * 25.4f / 96.f); } } if (!_glfw.wl.wmBase) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Failed to find xdg-shell in your compositor"); return false; } if (_glfw.wl.shm) { _glfw.wl.cursorSurface = wl_compositor_create_surface(_glfw.wl.compositor); } else { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Failed to find Wayland SHM"); return false; } if (_glfw.hints.init.debugRendering) { const char *mc = get_compositor_missing_capabilities(); if (mc && mc[0]) debug("Compositor missing capabilities: %s\n", mc); } *supports_window_occlusion = _glfw.wl.xdg_wm_base_version > 5; return true; } void _glfwPlatformTerminate(void) { if (_glfw.wl.activation_requests.array) { for (size_t i=0; i < _glfw.wl.activation_requests.sz; i++) { glfw_wl_xdg_activation_request *r = _glfw.wl.activation_requests.array + i; if (r->callback) r->callback(NULL, NULL, r->callback_data); xdg_activation_token_v1_destroy(r->token); } free(_glfw.wl.activation_requests.array); } _glfwTerminateEGL(); if (_glfw.wl.egl.handle) { _glfw_dlclose(_glfw.wl.egl.handle); _glfw.wl.egl.handle = NULL; } glfw_xkb_release(&_glfw.wl.xkb); glfw_dbus_terminate(&_glfw.wl.dbus); glfw_wlc_destroy(); if (_glfw.wl.cursor.handle) { _glfw_dlclose(_glfw.wl.cursor.handle); _glfw.wl.cursor.handle = NULL; } if (_glfw.wl.cursorSurface) wl_surface_destroy(_glfw.wl.cursorSurface); if (_glfw.wl.subcompositor) wl_subcompositor_destroy(_glfw.wl.subcompositor); if (_glfw.wl.compositor) wl_compositor_destroy(_glfw.wl.compositor); if (_glfw.wl.shm) wl_shm_destroy(_glfw.wl.shm); if (_glfw.wl.decorationManager) zxdg_decoration_manager_v1_destroy(_glfw.wl.decorationManager); if (_glfw.wl.wmBase) xdg_wm_base_destroy(_glfw.wl.wmBase); if (_glfw.wl.pointer) wl_pointer_destroy(_glfw.wl.pointer); if (_glfw.wl.keyboard) wl_keyboard_destroy(_glfw.wl.keyboard); if (_glfw.wl.seat) wl_seat_destroy(_glfw.wl.seat); if (_glfw.wl.relativePointerManager) zwp_relative_pointer_manager_v1_destroy(_glfw.wl.relativePointerManager); if (_glfw.wl.pointerConstraints) zwp_pointer_constraints_v1_destroy(_glfw.wl.pointerConstraints); _glfwWaylandDestroyTextInput(); if (_glfw.wl.dataSourceForClipboard) wl_data_source_destroy(_glfw.wl.dataSourceForClipboard); if (_glfw.wl.dataSourceForPrimarySelection) zwp_primary_selection_source_v1_destroy(_glfw.wl.dataSourceForPrimarySelection); for (size_t doi=0; doi < arraysz(_glfw.wl.untyped_data_offers); doi++) { if (_glfw.wl.untyped_data_offers[doi].id) { destroy_data_offer(&_glfw.wl.untyped_data_offers[doi]); } } if (_glfw.wl.primary_data_offer.id) destroy_data_offer(&_glfw.wl.primary_data_offer); if (_glfw.wl.clipboard_data_offer.id) destroy_data_offer(&_glfw.wl.clipboard_data_offer); if (_glfw.wl.drop_data_offer.id) destroy_data_offer(&_glfw.wl.drop_data_offer); if (_glfw.wl.dataDevice) wl_data_device_destroy(_glfw.wl.dataDevice); if (_glfw.wl.dataDeviceManager) wl_data_device_manager_destroy(_glfw.wl.dataDeviceManager); if (_glfw.wl.primarySelectionDevice) zwp_primary_selection_device_v1_destroy(_glfw.wl.primarySelectionDevice); if (_glfw.wl.primarySelectionDeviceManager) zwp_primary_selection_device_manager_v1_destroy(_glfw.wl.primarySelectionDeviceManager); if (_glfw.wl.xdg_activation_v1) xdg_activation_v1_destroy(_glfw.wl.xdg_activation_v1); if (_glfw.wl.xdg_toplevel_icon_manager_v1) xdg_toplevel_icon_manager_v1_destroy(_glfw.wl.xdg_toplevel_icon_manager_v1); if (_glfw.wl.xdg_system_bell_v1) xdg_system_bell_v1_destroy(_glfw.wl.xdg_system_bell_v1); if (_glfw.wl.xdg_toplevel_tag_manager_v1) xdg_toplevel_tag_manager_v1_destroy(_glfw.wl.xdg_toplevel_tag_manager_v1); if (_glfw.wl.xdg_toplevel_drag_manager_v1) xdg_toplevel_drag_manager_v1_destroy(_glfw.wl.xdg_toplevel_drag_manager_v1); if (_glfw.wl.wp_single_pixel_buffer_manager_v1) wp_single_pixel_buffer_manager_v1_destroy(_glfw.wl.wp_single_pixel_buffer_manager_v1); if (_glfw.wl.wp_cursor_shape_manager_v1) wp_cursor_shape_manager_v1_destroy(_glfw.wl.wp_cursor_shape_manager_v1); if (_glfw.wl.wp_viewporter) wp_viewporter_destroy(_glfw.wl.wp_viewporter); if (_glfw.wl.wp_fractional_scale_manager_v1) wp_fractional_scale_manager_v1_destroy(_glfw.wl.wp_fractional_scale_manager_v1); if (_glfw.wl.org_kde_kwin_blur_manager) org_kde_kwin_blur_manager_destroy(_glfw.wl.org_kde_kwin_blur_manager); if (_glfw.wl.ext_background_effect_manager_v1) ext_background_effect_manager_v1_destroy(_glfw.wl.ext_background_effect_manager_v1); if (_glfw.wl.zwlr_layer_shell_v1) zwlr_layer_shell_v1_destroy(_glfw.wl.zwlr_layer_shell_v1); if (_glfw.wl.idle_inhibit_manager) zwp_idle_inhibit_manager_v1_destroy(_glfw.wl.idle_inhibit_manager); if (_glfw.wl.keyboard_shortcuts_inhibit_manager) zwp_keyboard_shortcuts_inhibit_manager_v1_destroy(_glfw.wl.keyboard_shortcuts_inhibit_manager); if (_glfw.wl.pointer_gesture_hold) zwp_pointer_gesture_hold_v1_destroy(_glfw.wl.pointer_gesture_hold); if (_glfw.wl.pointer_gestures) zwp_pointer_gestures_v1_release(_glfw.wl.pointer_gestures); if (_glfw.wl.registry) wl_registry_destroy(_glfw.wl.registry); if (_glfw.wl.display) { wl_display_flush(_glfw.wl.display); wl_display_disconnect(_glfw.wl.display); _glfw.wl.display = NULL; } finalizePollData(&_glfw.wl.eventLoopData); if (_glfw.wl.compositor_name) { free(_glfw.wl.compositor_name); _glfw.wl.compositor_name = NULL; } } #define GLFW_LOOP_BACKEND wl #include "main_loop.h" const char* _glfwPlatformGetVersionString(void) { (void)keep_going; return _GLFW_VERSION_NUMBER " Wayland EGL OSMesa" #if defined(_POSIX_TIMERS) && defined(_POSIX_MONOTONIC_CLOCK) " clock_gettime" #else " gettimeofday" #endif " evdev" #if defined(_GLFW_BUILD_DLL) " shared" #endif ; }