From 4d30ae55f3038c6ca75abf1da0a14ea313ded198 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 11 Sep 2022 09:33:41 +0530 Subject: [PATCH] Wayland: Mark windows in which a bell as urgent on compositors that support the xdg-activation protocol --- docs/changelog.rst | 3 ++ glfw/glfw3.h | 1 + glfw/wl_init.c | 13 ++++++++ glfw/wl_platform.h | 16 ++++++++++ glfw/wl_window.c | 78 +++++++++++++++++++++++++++++++++++++--------- 5 files changed, 96 insertions(+), 15 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index b40d926f9..caa7ede9d 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -38,6 +38,8 @@ Detailed list of changes 0.27.0 [future] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +- Wayland: Mark windows in which a bell as urgent on compositors that support the xdg-activation protocol + - Allow passing null bytes through the system clipboard (:iss:`5483`) - ssh kitten: Fix :envvar:`KITTY_PUBLIC_KEY` not being encoded properly when transmitting (:iss:`5496`) @@ -51,6 +53,7 @@ Detailed list of changes - Wayland: Fix for a bug preventing kitty from starting on Hyprland when using a non-unit scale (:iss:`5467`) + 0.26.2 [2022-09-05] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/glfw/glfw3.h b/glfw/glfw3.h index a19bf1a7c..863ae6040 100644 --- a/glfw/glfw3.h +++ b/glfw/glfw3.h @@ -1716,6 +1716,7 @@ typedef void (* GLFWjoystickfun)(int,int); typedef void (* GLFWuserdatafun)(unsigned long long, void*); typedef void (* GLFWtickcallback)(void*); +typedef void (* GLFWactivationcallback)(GLFWwindow *window, const char *token, void *data); typedef bool (* GLFWdrawtextfun)(GLFWwindow *window, const char *text, uint32_t fg, uint32_t bg, uint8_t *output_buf, size_t width, size_t height, float x_offset, float y_offset, size_t right_margin); typedef char* (* GLFWcurrentselectionfun)(void); typedef void (* GLFWclipboarddatafreefun)(void* data); diff --git a/glfw/wl_init.c b/glfw/wl_init.c index 48d96f51c..6bd8244b2 100644 --- a/glfw/wl_init.c +++ b/glfw/wl_init.c @@ -719,6 +719,9 @@ static void registryHandleGlobal(void* data UNUSED, _glfwSetupWaylandPrimarySelectionDevice(); } } + else if (strstr(interface, "xdg_activation_v1") != 0) { + _glfw.wl.xdg_activation_v1 = wl_registry_bind(registry, name, &xdg_activation_v1_interface, 1); + } } @@ -882,6 +885,14 @@ int _glfwPlatformInit(void) 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) { @@ -941,6 +952,8 @@ void _glfwPlatformTerminate(void) 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.registry) wl_registry_destroy(_glfw.wl.registry); if (_glfw.wl.display) diff --git a/glfw/wl_platform.h b/glfw/wl_platform.h index a8794d6c9..0bd59383c 100644 --- a/glfw/wl_platform.h +++ b/glfw/wl_platform.h @@ -58,7 +58,9 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR #include "wayland-pointer-constraints-unstable-v1-client-protocol.h" #include "wayland-idle-inhibit-unstable-v1-client-protocol.h" #include "wayland-primary-selection-unstable-v1-client-protocol.h" +#include "wayland-primary-selection-unstable-v1-client-protocol.h" #include "wl_text_input.h" +#include "wayland-xdg-activation-v1-client-protocol.h" #define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL) #define _glfw_dlclose(handle) dlclose(handle) @@ -124,6 +126,14 @@ typedef enum WaylandWindowState { TOPLEVEL_STATE_TILED_BOTTOM = 128, } WaylandWindowState; +typedef struct glfw_wl_xdg_activation_request { + GLFWid window_id; + GLFWactivationcallback callback; + void *callback_data; + uintptr_t request_id; + void *token; +} glfw_wl_xdg_activation_request; + static const WaylandWindowState TOPLEVEL_STATE_DOCKED = TOPLEVEL_STATE_MAXIMIZED | TOPLEVEL_STATE_FULLSCREEN | TOPLEVEL_STATE_TILED_TOP | TOPLEVEL_STATE_TILED_LEFT | TOPLEVEL_STATE_TILED_RIGHT | TOPLEVEL_STATE_TILED_BOTTOM; @@ -276,6 +286,7 @@ typedef struct _GLFWlibraryWayland struct zwp_primary_selection_device_manager_v1* primarySelectionDeviceManager; struct zwp_primary_selection_device_v1* primarySelectionDevice; struct zwp_primary_selection_source_v1* dataSourceForPrimarySelection; + struct xdg_activation_v1* xdg_activation_v1; int compositorVersion; int seatVersion; @@ -316,6 +327,11 @@ typedef struct _GLFWlibraryWayland PFN_wl_egl_window_resize window_resize; } egl; + struct { + glfw_wl_xdg_activation_request *array; + size_t capacity, sz; + } activation_requests; + EventLoopData eventLoopData; size_t dataOffersCounter; _GLFWWaylandDataOffer dataOffers[8]; diff --git a/glfw/wl_window.c b/glfw/wl_window.c index 4939906aa..2ec6a70fb 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -44,6 +44,59 @@ #define debug(...) if (_glfw.hints.init.debugRendering) fprintf(stderr, __VA_ARGS__); +static void +activation_token_done(void *data, struct xdg_activation_token_v1 *xdg_token, const char *token) { + 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->request_id == (uintptr_t)data) { + _GLFWwindow *window = _glfwWindowForId(r->window_id); + if (r->callback) r->callback((GLFWwindow*)window, token, r->callback_data); + remove_i_from_array(_glfw.wl.activation_requests.array, i, _glfw.wl.activation_requests.sz); + break; + } + } + xdg_activation_token_v1_destroy(xdg_token); +} + + +static const struct +xdg_activation_token_v1_listener activation_token_listener = { + .done = &activation_token_done, +}; + + +static bool +get_activation_token( + _GLFWwindow *window, uint32_t serial, GLFWactivationcallback cb, void *cb_data +) { +#define fail(msg) { _glfwInputError(GLFW_PLATFORM_ERROR, msg); if (cb) cb((GLFWwindow*)window, NULL, cb_data); return false; } + if (_glfw.wl.xdg_activation_v1 == NULL) fail("Wayland: activation requests not supported by this Wayland compositor"); + struct xdg_activation_token_v1 *token = xdg_activation_v1_get_activation_token(_glfw.wl.xdg_activation_v1); + if (token == NULL) fail("Wayland: failed to create activation request token"); + if (_glfw.wl.activation_requests.capacity < _glfw.wl.activation_requests.sz + 1) { + _glfw.wl.activation_requests.capacity = MAX(64u, _glfw.wl.activation_requests.capacity * 2); + _glfw.wl.activation_requests.array = realloc(_glfw.wl.activation_requests.array, _glfw.wl.activation_requests.capacity); + if (!_glfw.wl.activation_requests.array) { + _glfw.wl.activation_requests.capacity = 0; + fail("Wayland: Out of memory while allocation activation request"); + } + } + glfw_wl_xdg_activation_request *r = _glfw.wl.activation_requests.array + _glfw.wl.activation_requests.sz++; + memset(r, 0, sizeof(*r)); + static uintptr_t rq = 0; + r->window_id = window->id; + r->callback = cb; r->callback_data = cb_data; + r->request_id = ++rq; r->token = token; + if (serial != 0) + xdg_activation_token_v1_set_serial(token, serial, _glfw.wl.seat); + + xdg_activation_token_v1_set_surface(token, window->wl.surface); + xdg_activation_token_v1_add_listener(token, &activation_token_listener, (void*)r->request_id); + xdg_activation_token_v1_commit(token); + return true; +#undef fail +} + static struct wl_buffer* createShmBuffer(const GLFWimage* image, bool is_opaque, bool init_data) { struct wl_shm_pool* pool; @@ -1102,27 +1155,22 @@ void _glfwPlatformHideWindow(_GLFWwindow* window) window->wl.visible = false; } -void _glfwPlatformRequestWindowAttention(_GLFWwindow* window UNUSED) -{ - // TODO - static bool notified = false; - if (!notified) { - _glfwInputError(GLFW_FEATURE_UNIMPLEMENTED, - "Wayland: Window attention request not implemented yet"); - notified = true; +static void +request_attention(GLFWwindow *window, const char *token, void *data UNUSED) { + if (window && token && token[0]) xdg_activation_v1_activate(_glfw.wl.xdg_activation_v1, token, ((_GLFWwindow*)window)->wl.surface); +} + +void _glfwPlatformRequestWindowAttention(_GLFWwindow* window) { + 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->window_id == window->id && r->callback == request_attention) return; } + get_activation_token(window, 0, request_attention, NULL); } int _glfwPlatformWindowBell(_GLFWwindow* window UNUSED) { // TODO: Use an actual Wayland API to implement this when one becomes available - static char tty[L_ctermid + 1]; - int fd = open(ctermid(tty), O_WRONLY | O_CLOEXEC); - if (fd > -1) { - int ret = write(fd, "\x07", 1) == 1 ? true : false; - close(fd); - return ret; - } return false; }