diff --git a/intern/ghost/CMakeLists.txt b/intern/ghost/CMakeLists.txt index 1948a8a0147..64ce7923b13 100644 --- a/intern/ghost/CMakeLists.txt +++ b/intern/ghost/CMakeLists.txt @@ -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" diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index c6ac6a42e47..0e4279af2f4 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -59,6 +59,7 @@ #include #include #include +#include #include /* 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( + 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; diff --git a/intern/ghost/intern/GHOST_SystemWayland.h b/intern/ghost/intern/GHOST_SystemWayland.h index c4bab3ae8e3..f3b3b786a98 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.h +++ b/intern/ghost/intern/GHOST_SystemWayland.h @@ -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. * diff --git a/intern/ghost/intern/GHOST_WindowWayland.cpp b/intern/ghost/intern/GHOST_WindowWayland.cpp index 4b71d25a9b2..a0b2dae5b03 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.cpp +++ b/intern/ghost/intern/GHOST_WindowWayland.cpp @@ -35,6 +35,7 @@ #endif /* Generated by `wayland-scanner`. */ +#include #include #include @@ -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( + 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(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; }