GHOST/Wayland: support window activation

Calling render (for example) with an existing window open now activates
the window on Wayland. Tested to work on GNOME & KDE.

Use the xdg-activation protocol which typically brings the
window to the foreground.

Partially resolves #102985.
This commit is contained in:
Campbell Barton
2023-03-28 16:05:34 +11:00
parent 8132b4a927
commit 3071ec486b
4 changed files with 156 additions and 1 deletions

View File

@@ -398,6 +398,10 @@ elseif(WITH_GHOST_X11 OR WITH_GHOST_WAYLAND)
generate_protocol_bindings(
"${WAYLAND_PROTOCOLS_DIR}/unstable/xdg-output/xdg-output-unstable-v1.xml"
)
# `xdg-activation`.
generate_protocol_bindings(
"${WAYLAND_PROTOCOLS_DIR}/staging/xdg-activation/xdg-activation-v1.xml"
)
# Pointer-constraints.
generate_protocol_bindings(
"${WAYLAND_PROTOCOLS_DIR}/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml"

View File

@@ -59,6 +59,7 @@
#include <primary-selection-unstable-v1-client-protocol.h>
#include <relative-pointer-unstable-v1-client-protocol.h>
#include <tablet-unstable-v2-client-protocol.h>
#include <xdg-activation-v1-client-protocol.h>
#include <xdg-output-unstable-v1-client-protocol.h>
/* Decorations `xdg_decor`. */
@@ -934,6 +935,7 @@ struct GWL_Display {
struct zwp_tablet_manager_v2 *wp_tablet_manager = nullptr;
struct zwp_relative_pointer_manager_v1 *wp_relative_pointer_manager = nullptr;
struct zwp_primary_selection_device_manager_v1 *wp_primary_selection_device_manager = nullptr;
struct xdg_activation_v1 *xdg_activation_manager = nullptr;
struct zwp_pointer_constraints_v1 *wp_pointer_constraints = nullptr;
struct zwp_pointer_gestures_v1 *wp_pointer_gestures = nullptr;
@@ -5161,6 +5163,24 @@ static void gwl_registry_wp_pointer_gestures_remove(GWL_Display *display,
*value_p = nullptr;
}
/* #GWL_Display.xdg_activation */
static void gwl_registry_xdg_activation_add(GWL_Display *display,
const GWL_RegisteryAdd_Params *params)
{
display->xdg_activation_manager = static_cast<xdg_activation_v1 *>(
wl_registry_bind(display->wl_registry, params->name, &xdg_activation_v1_interface, 1));
gwl_registry_entry_add(display, params, nullptr);
}
static void gwl_registry_xdg_activation_remove(GWL_Display *display,
void * /*user_data*/,
const bool /*on_exit*/)
{
struct xdg_activation_v1 **value_p = &display->xdg_activation_manager;
xdg_activation_v1_destroy(*value_p);
*value_p = nullptr;
}
/* #GWL_Display.wp_primary_selection_device_manager */
static void gwl_registry_wp_primary_selection_device_manager_add(
@@ -5264,6 +5284,12 @@ static const GWL_RegistryHandler gwl_registry_handlers[] = {
nullptr,
gwl_registry_wp_pointer_gestures_remove,
},
{
&xdg_activation_v1_interface.name,
gwl_registry_xdg_activation_add,
nullptr,
gwl_registry_xdg_activation_remove,
},
/* Display outputs. */
{
&wl_output_interface.name,
@@ -6823,6 +6849,11 @@ struct zwp_primary_selection_device_manager_v1 *GHOST_SystemWayland::wp_primary_
return display_->wp_primary_selection_device_manager;
}
struct xdg_activation_v1 *GHOST_SystemWayland::xdg_activation_manager()
{
return display_->xdg_activation_manager;
}
struct zwp_pointer_gestures_v1 *GHOST_SystemWayland::wp_pointer_gestures()
{
return display_->wp_pointer_gestures;
@@ -6934,6 +6965,16 @@ void GHOST_SystemWayland::seat_active_set(const struct GWL_Seat *seat)
gwl_display_seat_active_set(display_, seat);
}
struct wl_seat *GHOST_SystemWayland::wl_seat_active_get_with_input_serial(uint32_t &serial)
{
GWL_Seat *seat = gwl_display_seat_active_get(display_);
if (seat) {
serial = seat->data_source_serial;
return seat->wl_seat;
}
return nullptr;
}
bool GHOST_SystemWayland::window_surface_unref(const wl_surface *wl_surface)
{
bool changed = false;

View File

@@ -179,6 +179,7 @@ class GHOST_SystemWayland : public GHOST_System {
struct wl_display *wl_display();
struct wl_compositor *wl_compositor();
struct zwp_primary_selection_device_manager_v1 *wp_primary_selection_manager();
struct xdg_activation_v1 *xdg_activation_manager();
struct zwp_pointer_gestures_v1 *wp_pointer_gestures();
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
@@ -205,6 +206,8 @@ class GHOST_SystemWayland : public GHOST_System {
/** Set this seat to be active. */
void seat_active_set(const struct GWL_Seat *seat);
struct wl_seat *wl_seat_active_get_with_input_serial(uint32_t &serial);
/**
* Clear all references to this output.
*

View File

@@ -35,6 +35,7 @@
#endif
/* Generated by `wayland-scanner`. */
#include <xdg-activation-v1-client-protocol.h>
#include <xdg-decoration-unstable-v1-client-protocol.h>
#include <xdg-shell-client-protocol.h>
@@ -43,6 +44,8 @@
/* Logging, use `ghost.wl.*` prefix. */
#include "CLG_log.h"
static const struct xdg_activation_token_v1_listener *xdg_activation_listener_get();
static constexpr size_t base_dpi = 96;
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
@@ -111,6 +114,9 @@ struct GWL_Window {
*/
wl_fixed_t scale_fractional = 0;
/** A temporary token used for the window to be notified of of it's activation. */
struct xdg_activation_token_v1 *xdg_activation_token = nullptr;
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
WGL_LibDecor_Window *libdecor = nullptr;
#endif
@@ -281,6 +287,61 @@ static bool gwl_window_state_set(GWL_Window *win, const GHOST_TWindowState state
/** \} */
/* -------------------------------------------------------------------- */
/** \name Internal #GWL_Window Activation
* \{ */
/**
* Unlike #GHOST_WindowWayland::activate which responds to WAYLAND having set the window active.
* This function makes a request to WAYLAND for the window to become active.
*
* \note The request may be ignored and may not work on all compositors.
*/
static void gwl_window_activate(GWL_Window *win)
{
GHOST_SystemWayland *system = win->ghost_system;
struct xdg_activation_v1 *activation_manager = system->xdg_activation_manager();
if (UNLIKELY(activation_manager == nullptr)) {
return;
}
if (win->xdg_activation_token) {
/* We're about to overwrite this with a new request. */
xdg_activation_token_v1_destroy(win->xdg_activation_token);
}
win->xdg_activation_token = xdg_activation_v1_get_activation_token(activation_manager);
xdg_activation_token_v1_add_listener(
win->xdg_activation_token, xdg_activation_listener_get(), win);
xdg_activation_token_v1_set_app_id(win->xdg_activation_token, GHOST_SystemWayland::xdg_app_id());
/* The serial of the input device requesting activation. */
{
uint32_t serial = 0;
struct wl_seat *seat = system->wl_seat_active_get_with_input_serial(serial);
if (seat) {
xdg_activation_token_v1_set_serial(win->xdg_activation_token, serial, seat);
}
}
/* The surface of the window requesting activation. */
{
GHOST_WindowWayland *ghost_window_active = static_cast<GHOST_WindowWayland *>(
system->getWindowManager()->getActiveWindow());
if (ghost_window_active) {
struct wl_surface *surface = ghost_window_active->wl_surface();
if (surface) {
xdg_activation_token_v1_set_surface(win->xdg_activation_token, surface);
}
}
}
xdg_activation_token_v1_commit(win->xdg_activation_token);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Internal #GWL_Window Pending Actions
* \{ */
@@ -521,6 +582,39 @@ static const xdg_toplevel_listener xdg_toplevel_listener = {
/** \} */
/* -------------------------------------------------------------------- */
/** \name Listener (XDG Activation), #xdg_activation_v1_interface
*
* Used by #gwl_window_activate.
* \{ */
static void xdg_activation_handle_done(void *data,
struct xdg_activation_token_v1 *xdg_activation_token_v1,
const char *token)
{
GWL_Window *win = static_cast<GWL_Window *>(data);
if (xdg_activation_token_v1 != win->xdg_activation_token) {
return;
}
GHOST_SystemWayland *system = win->ghost_system;
struct xdg_activation_v1 *activation_manager = system->xdg_activation_manager();
xdg_activation_v1_activate(activation_manager, token, win->wl_surface);
xdg_activation_token_v1_destroy(win->xdg_activation_token);
win->xdg_activation_token = nullptr;
}
static const struct xdg_activation_token_v1_listener xdg_activation_listener = {
/*done*/ xdg_activation_handle_done,
};
static const struct xdg_activation_token_v1_listener *xdg_activation_listener_get()
{
return &xdg_activation_listener;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Listener (LibDecor Frame), #libdecor_frame_interface
* \{ */
@@ -1060,6 +1154,11 @@ GHOST_WindowWayland::~GHOST_WindowWayland()
wl_egl_window_destroy(window_->egl_window);
if (window_->xdg_activation_token) {
xdg_activation_token_v1_destroy(window_->xdg_activation_token);
window_->xdg_activation_token = nullptr;
}
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
if (use_libdecor) {
gwl_libdecor_window_destroy(window_->libdecor);
@@ -1123,8 +1222,16 @@ GHOST_TSuccess GHOST_WindowWayland::invalidate()
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_WindowWayland::setOrder(GHOST_TWindowOrder /*order*/)
GHOST_TSuccess GHOST_WindowWayland::setOrder(GHOST_TWindowOrder order)
{
/* NOTE(@ideasman42): only activation is supported (on X11 & Cocoa for e.g.)
* both activation and raising is performed. Since WAYLAND only supports activation,
* do that as the compositor will likely raise the window as well.
* Although it's not ideal that raising does something but lowering a window is ignored. */
if (order == GHOST_kWindowOrderTop) {
gwl_window_activate(window_);
}
return GHOST_kSuccess;
}