Wayland: use "XDG activation" protocol to support a window activating another window.
Build and Test / build-linux (push) Canceled after 0s
Build and Test / build-wayland (push) Canceled after 0s
Build and Test / build-wayland-arm64 (push) Canceled after 0s
Build and Test / build-macos (push) Canceled after 0s
Build and Test / build-windows (push) Canceled after 0s
Build and Test / build-windows-arm (push) Canceled after 0s

See
https://stackoverflow.com/questions/75861687/how-to-request-a-window-be-activated-with-the-wayland-protocol
explaining the "XDG activation" protocol is necessary and able to programmatically
activate a window.
The effect of the new code is visible with FLTK test program windowfocus.
This commit is contained in:
ManoloFLTK
2026-07-21 15:57:21 +02:00
parent 7e99bee32b
commit e5de7162a1
8 changed files with 110 additions and 10 deletions
+6
View File
@@ -318,6 +318,12 @@ if(UNIX)
else()
set(HAVE_XDG_DIALOG 0)
endif()
if(EXISTS ${PROTOCOLS}/staging/xdg-activation/xdg-activation-v1.xml)
set(HAVE_XDG_ACTIVATION 1)
message(STATUS "Found dev files for Wayland protocol 'XDG activation'")
else()
set(HAVE_XDG_ACTIVATION 0)
endif()
if(EXISTS ${PROTOCOLS}/staging/cursor-shape/cursor-shape-v1.xml AND
EXISTS ${PROTOCOLS}/stable/tablet/tablet-v2.xml)
set(HAVE_CURSOR_SHAPE 1)
+7
View File
@@ -356,3 +356,10 @@
*/
#cmakedefine01 HAVE_CURSOR_SHAPE
/*
* Can we use the "XDG activation" Wayland protocol?
*/
#cmakedefine01 HAVE_XDG_ACTIVATION
+4 -1
View File
@@ -101,7 +101,8 @@ beginning with \c wl_, \c xkb_ or \c libdecor_ and belong to the \c libwayland-c
\c libwayland-cursor.so, \c libxkbcommon.so and \c libdecor.so shared libraries.
FLTK calls also several other functions which operate other used Wayland protocols.
These have names beginning with \c xdg_, \c zwp_text_input_v3, \c zxdg_toplevel_decoration_,
\c gtk_shell1_, \c gtk_surface1_, \c wp_cursor_shape_ and \c zwp_tablet_v2.
\c gtk_shell1_, \c gtk_surface1_, \c wp_cursor_shape_, \c xdg_activation_ and
\c zwp_tablet_v2.
FLTK never writes itself to the socket; this is done inside the functions just mentioned.
- Compositor to client: the callback function \c wld_socket_callback() runs when there are
data to read in the socket; it calls \c libdecor_dispatch() which reads available data from
@@ -161,6 +162,7 @@ wayland-scanner client-header $PROTOCOLS/stable/xdg-shell/xdg-shell.xml xdg-shel
\endcode
Similar operations are performed for FLTK to use protocols
<a href=https://wayland.app/protocols/xdg-dialog-v1>XDG dialog windows</a>,
<a href=https://wayland.app/protocols/xdg-activation-v1>XDG activation</a>,
<a href=https://wayland.app/protocols/xdg-decoration-unstable-v1>XDG decoration</a>,
<a href=https://wayland.app/protocols/cursor-shape-v1>Cursor shape</a>,
<a href=https://wayland.app/protocols/text-input-unstable-v3>Text input</a>,
@@ -332,6 +334,7 @@ Here is the list of the \c interface value for all protocols and system features
wl_data_device | support of copy/paste/drag-n-drop
wl_output | received once for each display
xdg_wm_base | create mapped windows
xdg_activation_v1 | programmatically activate a window
xdg_wm_dialog_v1 | create dialogs related to other windows
wp_cursor_shape_manager_v1 | set cursor images using enumerated cursors
gtk_shell1 | indicates Mutter is in use + titlebar gestures
+13
View File
@@ -817,6 +817,19 @@ if(UNIX AND FLTK_USE_WAYLAND)
list(APPEND STATIC_FILES "xdg-shell-protocol.c")
list(APPEND SHARED_FILES "xdg-shell-protocol.c")
if(HAVE_XDG_ACTIVATION)
set(INFILE ${PROTOCOLS}/staging/xdg-activation/xdg-activation-v1.xml)
add_custom_command(
OUTPUT xdg-activation-protocol.c xdg-activation-client-protocol.h
COMMAND wayland-scanner private-code ${INFILE} xdg-activation-protocol.c
COMMAND wayland-scanner client-header ${INFILE} xdg-activation-client-protocol.h
DEPENDS ${INFILE}
VERBATIM
)
list(APPEND STATIC_FILES "xdg-activation-protocol.c")
list(APPEND SHARED_FILES "xdg-activation-protocol.c")
endif()
if(NOT USE_SYSTEM_LIBDECOR)
set(INFILE ${PROTOCOLS}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml)
fl_check_required_file(STOP_REQUIRED ${INFILE})
@@ -110,6 +110,9 @@ public:
#if HAVE_XDG_DIALOG
struct xdg_wm_dialog_v1 *xdg_wm_dialog;
#endif
#if HAVE_XDG_ACTIVATION
struct xdg_activation_v1 *xdg_activation;
#endif
#if HAVE_CURSOR_SHAPE
struct wp_cursor_shape_manager_v1 *wp_cursor_shape_manager;
struct wp_cursor_shape_device_v1 *wp_cursor_shape_device;
@@ -39,6 +39,9 @@
#if HAVE_XDG_DIALOG
# include "xdg-dialog-client-protocol.h"
#endif
#if HAVE_XDG_ACTIVATION
# include "xdg-activation-client-protocol.h"
#endif
#if HAVE_CURSOR_SHAPE
# include "cursor-shape-client-protocol.h"
# include "tablet-client-protocol.h"
@@ -1393,6 +1396,11 @@ static void registry_handle_global(void *user_data, struct wl_registry *wl_regis
scr_driver->xdg_wm_dialog = (struct xdg_wm_dialog_v1 *)
wl_registry_bind(wl_registry, id, &xdg_wm_dialog_v1_interface, 1);
#endif // HAVE_XDG_DIALOG
#if HAVE_XDG_ACTIVATION
} else if (strcmp(interface, xdg_activation_v1_interface.name) == 0) {
scr_driver->xdg_activation = (struct xdg_activation_v1 *)
wl_registry_bind(wl_registry, id, &xdg_activation_v1_interface, 1);
#endif // HAVE_XDG_ACTIVATION
#if HAVE_CURSOR_SHAPE
} else if (strcmp(interface, wp_cursor_shape_manager_v1_interface.name) == 0) {
scr_driver->wp_cursor_shape_manager = (struct wp_cursor_shape_manager_v1 *)
@@ -1460,6 +1468,9 @@ Fl_Wayland_Screen_Driver::Fl_Wayland_Screen_Driver() : Fl_Unix_Screen_Driver() {
#if HAVE_XDG_DIALOG
xdg_wm_dialog = NULL;
#endif
#if HAVE_XDG_ACTIVATION
xdg_activation = NULL;
#endif
#if HAVE_CURSOR_SHAPE
wp_cursor_shape_manager = NULL;
wp_cursor_shape_device = NULL;
@@ -1636,6 +1647,12 @@ void Fl_Wayland_Screen_Driver::close_display() {
xdg_wm_dialog = NULL;
}
#endif // HAVE_XDG_DIALOG
#if HAVE_XDG_ACTIVATION
if (xdg_activation) {
xdg_activation_v1_destroy(xdg_activation);
xdg_activation = NULL;
}
#endif // HAVE_XDG_ACTIVATION
#if HAVE_CURSOR_SHAPE
if (wp_cursor_shape_device ) {
wp_cursor_shape_device_v1_destroy(wp_cursor_shape_device);
@@ -64,6 +64,7 @@ private:
struct gl_start_support *gl_start_support_; // for support of gl_start/gl_finish
bool is_popup_window_;
bool previous_border_; // records whether window before being turned fullscreen had a border
void activate_(Fl_Window *);
public:
inline Fl_Cursor standard_cursor() { return standard_cursor_; }
bool in_handle_configure; // distinguish OS and user window resize
@@ -23,6 +23,9 @@
#include <wayland-cursor.h>
#include "../../../libdecor/build/fl_libdecor.h"
#include "xdg-shell-client-protocol.h"
#if HAVE_XDG_ACTIVATION
# include "xdg-activation-client-protocol.h"
#endif
#include "gtk-shell-client-protocol.h"
#if HAVE_XDG_DIALOG
# include "xdg-dialog-client-protocol.h"
@@ -164,15 +167,12 @@ void Fl_Wayland_Window_Driver::take_focus()
struct wld_window *w = fl_wl_xid(pWindow);
if (w) {
Fl_Window *old_first = Fl::first_window();
struct wld_window *first_xid = (old_first ? fl_wl_xid(old_first->top_window()) : NULL);
if (first_xid && first_xid != w && xdg_toplevel()) {
// this will move the target window to the front
Fl_Wayland_Window_Driver *top_dr =
Fl_Wayland_Window_Driver::driver(old_first->top_window());
xdg_toplevel_set_parent(xdg_toplevel(), top_dr->xdg_toplevel());
// this will remove the parent-child relationship
xdg_toplevel_set_parent(xdg_toplevel(), NULL);
wl_surface_commit(w->wl_surface);
if (old_first) {
if (old_first->parent()) old_first = old_first->top_window();
if (old_first != pWindow && xdg_toplevel()) {
// let the current active window activate this window
Fl_Wayland_Window_Driver::driver(old_first)->activate_(pWindow);
}
}
// this sets the first window
fl_wl_find(w);
@@ -2203,3 +2203,53 @@ void Fl_Wayland_Window_Driver::un_maximize() {
if (xid->kind == DECORATED) libdecor_frame_unset_maximized(xid->frame);
else Fl_Window_Driver::un_maximize();
}
#if HAVE_XDG_ACTIVATION
static void xdg_activation_done(void *data,
struct xdg_activation_token_v1 *xdg_activation_token_v1,
const char *token) {
Fl_Wayland_Screen_Driver *screen_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver();
xdg_activation_v1_activate(screen_driver->xdg_activation, token, (struct wl_surface*)data);
xdg_activation_token_v1_destroy(xdg_activation_token_v1);
}
static struct xdg_activation_token_v1_listener xdg_activation_token_listener = {
.done = xdg_activation_done
};
#endif // HAVE_XDG_ACTIVATION
/* This function lets one toplevel window, currently active, activate
another toplevel window given in its target parameter.
Wayland requires the "XDG activation" protocol for that.
This function also implements a fallback method used in absence of this protocol
which brings the target to front but doesn't activate it.
*/
void Fl_Wayland_Window_Driver::activate_(Fl_Window *target) {
struct wl_surface *target_surface = fl_wl_xid(target)->wl_surface;
#if HAVE_XDG_ACTIVATION
Fl_Wayland_Screen_Driver *screen_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver();
if (screen_driver->xdg_activation) {
struct xdg_activation_token_v1 *token = xdg_activation_v1_get_activation_token(screen_driver->xdg_activation);
const char *appid = pWindow->xclass() ? pWindow->xclass() : get_prog_name();
xdg_activation_token_v1_set_app_id(token, appid);
xdg_activation_token_v1_set_surface(token, fl_wl_xid(pWindow)->wl_surface);
xdg_activation_token_v1_set_serial(token, screen_driver->seat->serial,
screen_driver->seat->wl_seat);
xdg_activation_token_v1_add_listener(token, &xdg_activation_token_listener, target_surface);
xdg_activation_token_v1_commit(token);
} else
#endif // HAVE_XDG_ACTIVATION
{ // Fallback in absence of support of the adequate Wayland protocol.
// this will move the target window to the front
Fl_Wayland_Window_Driver *top_dr = Fl_Wayland_Window_Driver::driver(target);
xdg_toplevel_set_parent(top_dr->xdg_toplevel(), xdg_toplevel());
// this will remove the parent-child relationship
xdg_toplevel_set_parent(top_dr->xdg_toplevel(), NULL);
wl_surface_commit(target_surface);
}
}