diff --git a/CHANGES b/CHANGES index 98f29e11..8b228a5b 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,12 @@ +Louvre (2.7.0-1) + + # New Protocols + + * XDG Activation: See LActivationTokenManager and LActivationToken for details. + + -- Eduardo Hopperdietzel Thu, 22 Aug 2024 20:00:51 -0400 + + Louvre (2.6.0-1) # New Protocols diff --git a/README.md b/README.md index 4cba5cac..8345dfe5 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Louvre is released under the MIT license. - Current Louvre version. + Current Louvre version.

@@ -47,6 +47,7 @@ Fortunately, Louvre simplifies this intricate process by handling all the comple ## 🧩 Protocols * Wayland +* XDG Activation * XDG Shell * XDG Decoration * XDG Output @@ -157,7 +158,6 @@ Similarly as with CPU consumption, we can observe that Louvre uses fewer GPU res ## 🔨 Upcoming Features * Wlr Output Management -* XDG activation Protocol * Cursor Shape Protocol * DRM Overlay Planes Control * DRM Synchronization Object diff --git a/VERSION b/VERSION index e70b4523..24ba9a38 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.6.0 +2.7.0 diff --git a/doxygen/md/tutorial/tmp.md b/doxygen/md/tutorial/tmp.md index c6498a3d..41955e25 100644 --- a/doxygen/md/tutorial/tmp.md +++ b/doxygen/md/tutorial/tmp.md @@ -32,6 +32,7 @@ Here are some links to the C++ API documentation to help you navigate it: - [Session Lock Manager](@ref lsessionlockmanager_detailed) and [role.](@ref lsessionlockrole_detailed) - [Toplevel](@ref ltoplevelrole_detailed) and [Popup](@ref ldnd_detailed) surfaces. - [Wlr Layer Shell](@ref llayerrole_detailed) and [Exclusive Zones.](@ref lexclusivezone_detailed) +- [XDG Activation Tokens.](@ref lactivationtokenmanager_detailed) - [Foreign Toplevel Window Management.](@ref lforeigntoplevelcontroller_detailed) - [Foreign Toplevel List.](@ref Louvre::LToplevelRole::foreignHandleFilter) - [Idle Listeners and Inhibitors.](@ref lidlelistener_detailed) \ No newline at end of file diff --git a/src/lib/core/LActivationToken.cpp b/src/lib/core/LActivationToken.cpp new file mode 100644 index 00000000..4315fba2 --- /dev/null +++ b/src/lib/core/LActivationToken.cpp @@ -0,0 +1,18 @@ +#include +#include +#include + +using namespace Louvre; + +const std::unordered_map::iterator LActivationToken::destroy() const noexcept +{ + auto &tokens { Louvre::activationTokenManager()->m_tokens }; + return tokens.erase(tokens.find(*m_key)); +} + +LActivationToken::LActivationToken(LClient *creator, LSurface *origin, LEvent *triggeringEvent, std::string &&toActivateAppId) noexcept : + m_creator(creator), + m_origin(origin), + m_triggeringEvent(triggeringEvent), + m_toActivateAppId(toActivateAppId) +{} diff --git a/src/lib/core/LActivationToken.h b/src/lib/core/LActivationToken.h new file mode 100644 index 00000000..2c47ff87 --- /dev/null +++ b/src/lib/core/LActivationToken.h @@ -0,0 +1,140 @@ +#ifndef LACTIVATIONTOKEN_H +#define LACTIVATIONTOKEN_H + +#include +#include +#include +#include +#include + +/** + * @brief Token for activating surfaces + * + * This class represents a token used in LActivationTokenManager::activateSurfaceRequest(). + * + * For more details, refer to the LActivationTokenManager class documentation. + */ +class Louvre::LActivationToken final : public LObject +{ +public: + + /** + * @brief The client that requested the token creation. + * + * @note This may, but does not necessarily, refer to the same client that requests surface activation. + * + * @return A valid pointer during LActivationTokenManager::createTokenRequest(), but it may return `nullptr` + * if accessed later, for example, if the client has disconnected. + */ + LClient *creator() const noexcept + { + return m_creator.get(); + } + + /** + * @brief The surface of the client that created the token. + * + * This refers to the surface where the triggeringEvent() was sent. + * + * @note This is different from the surface intended to be activated. + * + * @warning Clients are not obligated to provide this hint, so this method may return `nullptr`. + */ + LSurface *origin() const noexcept + { + return m_origin.get(); + } + + /** + * @brief Triggering event + * + * Clients typically request token creation in response to an input event. The default implementation of + * LActivationTokenManager::createTokenRequest() only accepts requests containing a valid triggeringEvent(). + * + * @see origin() + * + * @warning This value is optional, if the client does not specify a triggering event, this method returns `nullptr`. + */ + const LEvent *triggeringEvent() const noexcept + { + return m_triggeringEvent.get(); + } + + /** + * @brief Number of times the token has been used to activate a surface. + * + * Incremented by 1 before each LActivationTokenManager::activateSurfaceRequest(). + */ + UInt32 timesUsed() const noexcept + { + return m_timesUsed; + } + + /** + * @brief Time the token was created. + * + * @see LActivationTokenManager::destroyTokensOlderThanMs(). + * + * @return The time point when the token was created. + */ + const std::chrono::time_point &created() const noexcept + { + return m_created; + } + + /** + * @brief The app ID of the client to be activated. + * + * This value is optional, if the token creator does not assign an app ID, this method returns an empty string. + */ + const std::string &toActivateAppId() const noexcept + { + return m_toActivateAppId; + } + + /** + * @brief The unique and random token string generated by Louvre. + * + * @return The token string. + */ + const std::string &token() const noexcept + { + return *m_key; + } + + /** + * @brief Invalidates and destroys the token. + * + * Removes it from LActivationTokenManager::tokens(). + * + * Once destroyed, no client will be able to use it to activate surfaces. + * + * @return An iterator to the next element in LActivationTokenManager::tokens(). + */ + const std::unordered_map::iterator destroy() const noexcept; + + LActivationToken(const LActivationToken&) = delete; + LActivationToken &operator=(const LActivationToken&) = delete; + LActivationToken(LActivationToken&&) = default; + +private: + friend class Protocols::XdgActivation::RXdgActivationToken; + + LActivationToken( + LClient *creator, + LSurface *origin, + LEvent *triggeringEvent, + std::string &&toActivateAppId) noexcept; + + LWeak m_creator; + LWeak m_origin; + std::unique_ptr m_triggeringEvent; + std::string m_toActivateAppId; + std::chrono::time_point m_created { + std::chrono::steady_clock::now() + }; + UInt32 m_timesUsed { 0 }; + const std::string *m_key { nullptr }; +}; + +#endif // LACTIVATIONTOKEN_H diff --git a/src/lib/core/LActivationTokenManager.cpp b/src/lib/core/LActivationTokenManager.cpp new file mode 100644 index 00000000..ba91ddbb --- /dev/null +++ b/src/lib/core/LActivationTokenManager.cpp @@ -0,0 +1,29 @@ +#include +#include + +using namespace Louvre; + +LActivationTokenManager::LActivationTokenManager(const void *params) noexcept : LFactoryObject(FactoryObjectType) +{ + assert(params != nullptr && "Invalid parameter passed to LActivationTokenManager constructor."); + LActivationTokenManager**ptr { (LActivationTokenManager**) params }; + assert(*ptr == nullptr && *ptr == Louvre::activationTokenManager() && "Only a single LActivationTokenManager instance can exist."); + *ptr = this; +} + +void LActivationTokenManager::destroyTokensOlderThanMs(UInt32 ms) +{ + if (ms == 0) + { + m_tokens.clear(); + return; + } + + for (auto it = m_tokens.begin(); it != m_tokens.end();) + { + if (std::chrono::duration_cast(std::chrono::steady_clock::now() - (*it).second.created()).count() > ms) + it = m_tokens.erase(it); + else + it++; + } +} diff --git a/src/lib/core/LActivationTokenManager.h b/src/lib/core/LActivationTokenManager.h new file mode 100644 index 00000000..52108853 --- /dev/null +++ b/src/lib/core/LActivationTokenManager.h @@ -0,0 +1,144 @@ +#ifndef LACTIVATIONTOKENMANAGER_H +#define LACTIVATIONTOKENMANAGER_H + +#include +#include + +/** + * @brief Activation Token Manager + * + * @anchor lactivationtokenmanager_detailed + * + * The [XDG Activation](https://wayland.app/protocols/xdg-activation-v1) protocol allows clients to pass focus to another client's surface. + * + * The protocol functions as follows: + * + * 1. The "activator" client requests a new token from the compositor via the createTokenRequest() method. + * During the request, the client can provide additional (optional) information, such as the application ID of the "target" client + * (LActivationToken::toActivateAppId()), the event that triggered the request (LActivationToken::triggeringEvent()), + * and the surface that received the triggering event (LActivationToken::origin()). + * + * 2. If the request is accepted, the compositor generates an activation token (a unique and random string) + * and sends it to the client. The token is stored in the tokens() map. + * + * 3. The "activator" client then passes the token to the "target" client via its own communication channel (e.g., D-Bus, the **XDG_ACTIVATION_TOKEN** environment variable). + * + * 4. The "target" client can use the token to activate one of its surfaces by invoking the activateSurfaceRequest() method. + * + * The default implementations of createTokenRequest() and activateSurfaceRequest() only accept requests that provide a valid triggering event and + * destroy the token after it is used, as well as those that haven't been used in the last 10 seconds. + * This is for security reasons, tokens can remain valid indefinitely if desired. + * + * There is a unique instance of this class, created within LCompositor::createObjectRequest(), + * which can be accessed globally via Louvre::activationTokenManager(). + * + * To disable this protocol, remove its global from LCompositor::createGlobalsRequest() + * + * @see LActivationToken + */ +class Louvre::LActivationTokenManager : public LFactoryObject +{ +public: + static constexpr LFactoryObject::Type FactoryObjectType = LFactoryObject::Type::LActivationTokenManager; + + /** + * @brief LActivationTokenManager class constructor. + * + * There is only one instance of this class, which can be accessed globally via activationTokenManager(). + * + * @param params Internal parameters provided in LCompositor::createObjectRequest(). + */ + LActivationTokenManager(const void *params) noexcept; + + /** + * @brief Destructor of the LActivationTokenManager class. + * + * Invoked after LCompositor::onAnticipatedObjectDestruction(). + */ + ~LActivationTokenManager() = default; + + LCLASS_NO_COPY(LActivationTokenManager) + + /** + * @brief Retrieve available tokens. + * + * Provides all currently available tokens, which clients can use to activate surfaces. + * + * To expire tokens, use LActivationToken::destroy() or the destroyTokensOlderThanMs() method. + */ + const std::unordered_map &tokens() const noexcept + { + return m_tokens; + } + + /** + * @brief Retrieve the current token. + * + * This method provides access to the token used during createTokenRequest() or activateSurfaceRequest() requests. + * If neither request is in progress, this method returns `nullptr`. + * + * The token is not provided as an argument to those requests because the default implementation of activateSurfaceRequest() + * triggers LToplevelRole::activateRequest() if the surface has a toplevel role. This way, the token can be accessed + * from LToplevelRole::activateRequest() if needed. + */ + LActivationToken *token() const noexcept + { + return m_token.get(); + } + + /** + * @brief Destroy tokens older than the specified number of milliseconds. + * + * The default implementation of activateSurfaceRequest() uses this method to destroy all tokens older than 10 seconds + * to prevent unnecessary tokens from remaining active. + * + * @see LActivationToken::destroy() and LActivationToken::created(). + */ + void destroyTokensOlderThanMs(UInt32 ms); + + /// @name Virtual Methods + /// @{ + + /** + * @brief Request to create an activation token. + * + * This request is triggered each time a client wants to create a new activation token. + * The activation token data can be accessed via token(). If the token is denied, `token()->destroy()` must be called, + * otherwise, the token will be retained in the tokens() map, and clients will be allowed to use it for activateSurfaceRequest(). + * + * The default implementation only accepts tokens generated by the currently focused client. + * + * #### Default implementation + * @snippet LActivationTokenManagerDefault.cpp createTokenRequest + */ + virtual void createTokenRequest(); + + /** + * @brief Request to activate a surface. + * + * This request indicates that a client wants to activate one of its surfaces and is only triggered if the client + * provides one of the tokens(), which can be accessed during the request with token(). + * + * The default implementation verifies if the token is recent and destroys it immediately after use, + * along with any tokens older than 10 seconds. + * + * If the token is not destroyed, LActivationToken::timesUsed() is incremented by 1. + * + * @param surface The surface to activate. + * + * #### Default implementation + * @snippet LActivationTokenManagerDefault.cpp activateSurfaceRequest + */ + virtual void activateSurfaceRequest(LSurface *surface); + + /// @} + +private: + friend class Protocols::XdgActivation::GXdgActivation; + friend class Protocols::XdgActivation::RXdgActivationToken; + friend class LActivationToken; + std::unordered_map m_tokens; + LWeak m_token; +}; + +#endif // LACTIVATIONTOKENMANAGER_H diff --git a/src/lib/core/LClient.cpp b/src/lib/core/LClient.cpp index 64768947..e0fd84b5 100644 --- a/src/lib/core/LClient.cpp +++ b/src/lib/core/LClient.cpp @@ -256,6 +256,11 @@ const std::vector &LClient::idleInhibitManag return imp()->idleInhibitManagerGlobals; } +const std::vector &LClient::xdgActivationGlobals() const noexcept +{ + return imp()->xdgActivationGlobals; +} + const LClient::EventHistory &LClient::eventHistory() const noexcept { return imp()->eventHistory; diff --git a/src/lib/core/LClient.h b/src/lib/core/LClient.h index 50470bb7..ad3ec40d 100644 --- a/src/lib/core/LClient.h +++ b/src/lib/core/LClient.h @@ -410,6 +410,13 @@ class Louvre::LClient : public LFactoryObject */ const std::vector &idleInhibitManagerGlobals() const noexcept; + /** + * Resources created when the client binds to the + * [xdg_activation_v1](https://wayland.app/protocols/xdg-activation-v1#xdg_activation_v1) global + * of the XDG Activation protocol. + */ + const std::vector &xdgActivationGlobals() const noexcept; + LPRIVATE_IMP_UNIQUE(LClient) }; diff --git a/src/lib/core/LCompositor.cpp b/src/lib/core/LCompositor.cpp index 224e6ab1..a6205b43 100644 --- a/src/lib/core/LCompositor.cpp +++ b/src/lib/core/LCompositor.cpp @@ -52,6 +52,11 @@ LSessionLockManager *Louvre::sessionLockManager() noexcept return s_compositor->imp()->sessionLockManager; } +LActivationTokenManager *Louvre::activationTokenManager() noexcept +{ + return s_compositor->imp()->activationTokenManager; +} + void LCompositor::removeGlobal(LGlobal *global) noexcept { if (global->m_removed) @@ -439,6 +444,11 @@ LSessionLockManager *LCompositor::sessionLockManager() const noexcept return imp()->sessionLockManager; } +LActivationTokenManager *LCompositor::activationTokenManager() const noexcept +{ + return imp()->activationTokenManager; +} + void LCompositor::repaintAllOutputs() noexcept { for (LOutput *o : imp()->outputs) diff --git a/src/lib/core/LCompositor.h b/src/lib/core/LCompositor.h index 1a83b53c..0b7b7da3 100644 --- a/src/lib/core/LCompositor.h +++ b/src/lib/core/LCompositor.h @@ -160,11 +160,16 @@ class Louvre::LCompositor * @brief Provides access to the session lock manager. * * The session lock manager allows you to handle client requests to lock the user session and display arbitrary content. - * - * @return A pointer to the LSessionLockManager instance. */ LSessionLockManager *sessionLockManager() const noexcept; + /** + * @brief Provides access to the activation token manager. + * + * The activation token manager allows you to handle client requests to activate surfaces of other clients. + */ + LActivationTokenManager *activationTokenManager() const noexcept; + /** * @brief Gets a list of all surfaces created by clients. * diff --git a/src/lib/core/LFactoryObject.h b/src/lib/core/LFactoryObject.h index 863b9afd..ac677167 100644 --- a/src/lib/core/LFactoryObject.h +++ b/src/lib/core/LFactoryObject.h @@ -94,7 +94,10 @@ class Louvre::LFactoryObject : public LObject LDND, /// Represents the LSessionLockManager class. - LSessionLockManager + LSessionLockManager, + + /// Represents the LActivationTokenManager class. + LActivationTokenManager }; /** diff --git a/src/lib/core/LNamespaces.h b/src/lib/core/LNamespaces.h index 9270c48a..aae82835 100644 --- a/src/lib/core/LNamespaces.h +++ b/src/lib/core/LNamespaces.h @@ -14,6 +14,7 @@ #define LOUVRE_WL_SEAT_VERSION 9 #define LOUVRE_WL_OUTPUT_VERSION 4 #define LOUVRE_WL_SUBCOMPOSITOR_VERSION 1 +#define LOUVRE_XDG_ACTIVATION_VERSION 1 #define LOUVRE_XDG_WM_BASE_VERSION 6 #define LOUVRE_XDG_DECORATION_MANAGER_VERSION 1 #define LOUVRE_XDG_OUTPUT_MANAGER_VERSION 3 @@ -99,6 +100,8 @@ namespace Louvre class LSurface; class LTexture; class LScreenshotRequest; + class LActivationTokenManager; + class LActivationToken; class LPainter; class LRenderBuffer; @@ -324,6 +327,13 @@ namespace Louvre class RCallback; } + namespace XdgActivation + { + class GXdgActivation; + + class RXdgActivationToken; + }; + namespace XdgShell { class GXdgWmBase; @@ -511,10 +521,15 @@ namespace Louvre * @brief Gets the compositor's session lock manager. * * @warning Must be accessed within or after the LCompositor::initialized() event. - * - * @return A pointer to the LSessionLockManager instance. */ LSessionLockManager *sessionLockManager() noexcept; + + /** + * @brief Provides access to the activation token manager. + * + * @warning Must be accessed within or after the LCompositor::initialized() event. + */ + LActivationTokenManager *activationTokenManager() noexcept; }; #endif // LNAMESPACES_H diff --git a/src/lib/core/LSeat.h b/src/lib/core/LSeat.h index a01c2abe..23dfb109 100644 --- a/src/lib/core/LSeat.h +++ b/src/lib/core/LSeat.h @@ -77,10 +77,10 @@ class Louvre::LSeat : public LFactoryObject /** * @brief Sets a hint about the user's idle state. * - * Resetting all idle listener timers manually with `LIdleListeners::resetTimer()` each time an event occurs isn't very CPU-friendly, + * Resetting all idle listener timers manually with LIdleListeners::resetTimer() each time an event occurs isn't very CPU-friendly, * as multiple events can be triggered in a single main loop iteration. * - * Instead, by using this method (which only updates a boolean variable), we can ask Louvre to update all timers only once at the end of an iteration. + * Instead, by using this method (which only updates a boolean variable), we can ask Louvre to update all timers only once at the end of a main loop iteration. * * @note The value is automatically set to `true` at the start of each iteration. * @@ -359,6 +359,8 @@ class Louvre::LSeat : public LFactoryObject * @param event Opaque handle to the native backend event. * When using the Libinput backend it corresponds to a [libinput_event](https://wayland.freedesktop.org/libinput/doc/latest/api/structlibinput__event.html) struct * + * @note Use LCompositor::inputBackendId() to determine which input backend is in use. + * * #### Default implementation * @snippet LSeatDefault.cpp nativeInputEvent */ diff --git a/src/lib/core/LSessionLockManager.h b/src/lib/core/LSessionLockManager.h index 6a705eda..9474f035 100644 --- a/src/lib/core/LSessionLockManager.h +++ b/src/lib/core/LSessionLockManager.h @@ -34,7 +34,7 @@ class Louvre::LSessionLockManager : public LFactoryObject * * There is only one instance of LSessionLockManager, which can be accessed from LCompositor::sessionLockManager(). * - * @param params Internal library parameters provided in the virtual LCompositor::createSessionLockManagerRequest() constructor. + * @param params Internal parameters provided in LCompositor::createObjectRequest(). */ LSessionLockManager(const void *params) noexcept; diff --git a/src/lib/core/default/LActivationTokenManagerDefault.cpp b/src/lib/core/default/LActivationTokenManagerDefault.cpp new file mode 100644 index 00000000..f011313e --- /dev/null +++ b/src/lib/core/default/LActivationTokenManagerDefault.cpp @@ -0,0 +1,79 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Louvre; + +//! [createTokenRequest] +void LActivationTokenManager::createTokenRequest() +{ + // Deny requests not originated from a recent user event + + bool allowed { false }; + + if (token()->triggeringEvent()) + { + switch (token()->triggeringEvent()->type()) + { + case LEvent::Type::Pointer: + allowed = seat()->pointer()->focus() && seat()->pointer()->focus()->client() == token()->creator(); + break; + case LEvent::Type::Keyboard: + allowed = seat()->keyboard()->focus() && seat()->keyboard()->focus()->client() == token()->creator(); + break; + case LEvent::Type::Touch: + const auto &touchPoints { seat()->touch()->touchPoints() }; + allowed = std::any_of(touchPoints.begin(), touchPoints.end(), [this](LTouchPoint *tp) { return tp->surface() && tp->surface()->client() == token()->creator();}); + break; + } + } + + if (!allowed) + token()->destroy(); + + destroyTokensOlderThanMs(10000); +} +//! [createTokenRequest] + +//! [activateSurfaceRequest] +void LActivationTokenManager::activateSurfaceRequest(LSurface *surface) +{ + bool allowed { false }; + + if (token()->creator() && token()->triggeringEvent() && sessionLockManager()->state() == LSessionLockManager::Unlocked) + { + switch (token()->triggeringEvent()->type()) + { + case LEvent::Type::Pointer: + allowed = seat()->pointer()->focus() && seat()->pointer()->focus()->client() == token()->creator(); + break; + case LEvent::Type::Keyboard: + allowed = seat()->keyboard()->focus() && seat()->keyboard()->focus()->client() == token()->creator(); + break; + case LEvent::Type::Touch: + const auto &touchPoints { seat()->touch()->touchPoints() }; + allowed = std::any_of(touchPoints.begin(), touchPoints.end(), [this](LTouchPoint *tp) { return tp->surface() && tp->surface()->client() == token()->creator();}); + break; + } + } + + if (allowed) + { + if (surface->toplevel()) + surface->toplevel()->activateRequest(); + else + { + seat()->keyboard()->setFocus(surface); + surface->raise(); + } + } + + token()->destroy(); + destroyTokensOlderThanMs(10000); +} +//! [activateSurfaceRequest] diff --git a/src/lib/core/default/LCompositorDefault.cpp b/src/lib/core/default/LCompositorDefault.cpp index 3ae9034b..db33c782 100644 --- a/src/lib/core/default/LCompositorDefault.cpp +++ b/src/lib/core/default/LCompositorDefault.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -127,6 +128,9 @@ bool LCompositor::createGlobalsRequest() // Allows clients to request inhibition of the compositor's idle state createGlobal(); + // Allows clients to activate other client's surfaces + createGlobal(); + return true; } //! [createGlobalsRequest] diff --git a/src/lib/core/default/LSeatDefault.cpp b/src/lib/core/default/LSeatDefault.cpp index 78b8f5cd..c7859ae2 100644 --- a/src/lib/core/default/LSeatDefault.cpp +++ b/src/lib/core/default/LSeatDefault.cpp @@ -102,18 +102,19 @@ void LSeat::onEvent(const LEvent &event) L_UNUSED(event) /* - * Resetting all timers each time an event occurs is not CPU-friendly, - * as multiple events can be triggered in a single main loop iteration. - * Instead, using the option below is more efficient. - * * for (const LIdleListener *idleListener : idleListeners()) * idleListener->resetTimer(); + * + * Resetting all timers each time an event occurs is not CPU-friendly, + * as multiple events can be triggered in a single main loop iteration. + * Instead, using the option below (setIsUserIdleHint()) is more efficient. */ /* * Setting this flag to false indicates the user wasn't idle during * this main loop iteration. If that's the case, Louvre will reset * all timers only once at the end of the iteration. + * The flag is automatically set to true again afterwards. */ setIsUserIdleHint(false); } diff --git a/src/lib/core/default/LToplevelRoleDefault.cpp b/src/lib/core/default/LToplevelRoleDefault.cpp index 7d7c7aa0..c59447a6 100644 --- a/src/lib/core/default/LToplevelRoleDefault.cpp +++ b/src/lib/core/default/LToplevelRoleDefault.cpp @@ -300,7 +300,8 @@ void LToplevelRole::unsetMinimizedRequest() //! [activateRequest] void LToplevelRole::activateRequest() { - /* This request is always triggered by a foreign client */ + /* This request is always triggered by a foreign client or + * by the default implementation of LActivationTokenManager::activateSurfaceRequest() */ configureState(pendingConfiguration().state | Activated); } diff --git a/src/lib/core/private/LClientPrivate.h b/src/lib/core/private/LClientPrivate.h index ff612bf2..0e55929b 100644 --- a/src/lib/core/private/LClientPrivate.h +++ b/src/lib/core/private/LClientPrivate.h @@ -55,6 +55,7 @@ class LClient::LClientPrivate std::vector contentTypeManagerGlobals; std::vector idleNotifierGlobals; std::vector idleInhibitManagerGlobals; + std::vector xdgActivationGlobals; bool destroyed { false }; }; diff --git a/src/lib/core/private/LCompositorPrivate.cpp b/src/lib/core/private/LCompositorPrivate.cpp index f96cc293..6293faa7 100644 --- a/src/lib/core/private/LCompositorPrivate.cpp +++ b/src/lib/core/private/LCompositorPrivate.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -162,6 +163,7 @@ bool LCompositor::LCompositorPrivate::initWayland() clientConnectedListener.notify = &clientConnectedEvent; wl_display_add_client_created_listener(display, &clientConnectedListener); LFactory::createObject(&sessionLockManager); + LFactory::createObject(&activationTokenManager); wl_display_set_global_filter(display, [](const wl_client *client, const wl_global *global, void */*data*/) -> bool { LGlobal *lGlobal { nullptr }; @@ -209,6 +211,13 @@ void LCompositor::LCompositorPrivate::unitWayland() display = nullptr; } + if (activationTokenManager) + { + compositor()->onAnticipatedObjectDestruction(activationTokenManager); + delete activationTokenManager; + activationTokenManager = nullptr; + } + if (sessionLockManager) { compositor()->onAnticipatedObjectDestruction(sessionLockManager); diff --git a/src/lib/core/private/LCompositorPrivate.h b/src/lib/core/private/LCompositorPrivate.h index 974e10d4..5490d58a 100644 --- a/src/lib/core/private/LCompositorPrivate.h +++ b/src/lib/core/private/LCompositorPrivate.h @@ -43,6 +43,7 @@ LPRIVATE_CLASS(LCompositor) #define LEV_WAYLAND 3 epoll_event events[4]; // [0] Unlock [1] Libseat [2] Aux [3] Wayland LSessionLockManager *sessionLockManager { nullptr }; + LActivationTokenManager *activationTokenManager { nullptr }; void unitWayland(); bool surfacesListChanged { false }; diff --git a/src/lib/core/roles/LToplevelRole.h b/src/lib/core/roles/LToplevelRole.h index 3bde0ced..7479d344 100644 --- a/src/lib/core/roles/LToplevelRole.h +++ b/src/lib/core/roles/LToplevelRole.h @@ -45,11 +45,12 @@ * * @note Minimized is not a toplevel @ref State and can be applied immediately. * - * @section foreign-controller Foreign Toplevel Controllers + * @section foreign-controller Foreign Toplevel Controllers and XDG Activation Tokens * * Requests, including those previously mentioned as well as `unsetMinimizedRequest()`, `activateRequest()`, and `closeRequest()`, can be triggered by external clients - * through the [Wlr Foreign Toplevel Management](https://wayland.app/protocols/wlr-foreign-toplevel-management-unstable-v1) protocol. For a comprehensive understanding - * of this functionality, please refer to the LForeignToplevelController class documentation. + * through the [Wlr Foreign Toplevel Management](https://wayland.app/protocols/wlr-foreign-toplevel-management-unstable-v1) and + * the [XDG Activation](https://wayland.app/protocols/xdg-activation-v1) protocols. For a comprehensive understanding of this functionality, please refer to the + * LForeignToplevelController and LActivationTokenManager classes documentation. * * @section toplevel-sessions Interactive Sessions * @@ -910,7 +911,7 @@ class Louvre::LToplevelRole : public LBaseSurfaceRole /** * @brief Request to activate. * - * Triggered by a requesterController() expecting the compositor to configure the toplevel with the @ref Activated state. + * Triggered by a an LActivationTokenManager::token() or requesterController() expecting the compositor to configure the toplevel with the @ref Activated state. * * @see configureState(), requesterController() and LForeignToplevelController. * diff --git a/src/lib/protocols/XdgActivation/GXdgActivation.cpp b/src/lib/protocols/XdgActivation/GXdgActivation.cpp new file mode 100644 index 00000000..ce9eb155 --- /dev/null +++ b/src/lib/protocols/XdgActivation/GXdgActivation.cpp @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include +#include +#include + +using namespace Louvre; +using namespace Louvre::Protocols::XdgActivation; + +static const struct xdg_activation_v1_interface imp +{ + .destroy = &GXdgActivation::destroy, + .get_activation_token = &GXdgActivation::get_activation_token, + .activate = &GXdgActivation::activate, +}; + +void GXdgActivation::bind(wl_client *client, void */*data*/, UInt32 version, UInt32 id) noexcept +{ + new GXdgActivation(client, version, id); +} + +Int32 GXdgActivation::maxVersion() noexcept +{ + return LOUVRE_XDG_ACTIVATION_VERSION; +} + +const wl_interface *GXdgActivation::interface() noexcept +{ + return &xdg_activation_v1_interface; +} + +GXdgActivation::GXdgActivation( + wl_client *client, + Int32 version, + UInt32 id) noexcept + :LResource + ( + client, + interface(), + version, + id, + &imp + ) +{ + this->client()->imp()->xdgActivationGlobals.emplace_back(this); +} + +GXdgActivation::~GXdgActivation() noexcept +{ + LVectorRemoveOneUnordered(client()->imp()->xdgActivationGlobals, this); +} + +void GXdgActivation::destroy(wl_client */*client*/, wl_resource *resource) noexcept +{ + wl_resource_destroy(resource); +} + +void GXdgActivation::get_activation_token(wl_client */*client*/, wl_resource *resource, UInt32 id) noexcept +{ + auto *res { static_cast(wl_resource_get_user_data(resource)) }; + new RXdgActivationToken(res, id); +} + +void GXdgActivation::activate(wl_client */*client*/, wl_resource */*resource*/, const char *token, wl_resource *surface) +{ + const std::string tokenString { token }; + auto it { activationTokenManager()->m_tokens.find(tokenString) }; + + if (it == activationTokenManager()->m_tokens.end()) + return; + + auto *surfaceRes { static_cast(wl_resource_get_user_data(surface)) }; + activationTokenManager()->m_token.reset(&it->second); + activationTokenManager()->activateSurfaceRequest(surfaceRes->surface()); + activationTokenManager()->m_token.reset(); +} diff --git a/src/lib/protocols/XdgActivation/GXdgActivation.h b/src/lib/protocols/XdgActivation/GXdgActivation.h new file mode 100644 index 00000000..6c1147fb --- /dev/null +++ b/src/lib/protocols/XdgActivation/GXdgActivation.h @@ -0,0 +1,18 @@ +#ifndef GXDGACTIVATION_H +#define GXDGACTIVATION_H + +#include + +class Louvre::Protocols::XdgActivation::GXdgActivation final : public LResource +{ +public: + static void destroy(wl_client *client, wl_resource *resource) noexcept; + static void get_activation_token(wl_client *client, wl_resource *resource, UInt32 id) noexcept; + static void activate(wl_client *client, wl_resource *, const char *token, wl_resource *surface); +private: + LGLOBAL_INTERFACE + GXdgActivation(wl_client *client, Int32 version, UInt32 id) noexcept; + ~GXdgActivation() noexcept; +}; + +#endif // GXDGACTIVATION_H diff --git a/src/lib/protocols/XdgActivation/RXdgActivationToken.cpp b/src/lib/protocols/XdgActivation/RXdgActivationToken.cpp new file mode 100644 index 00000000..a21febbe --- /dev/null +++ b/src/lib/protocols/XdgActivation/RXdgActivationToken.cpp @@ -0,0 +1,155 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Louvre; +using namespace Louvre::Protocols::XdgActivation; + +static const struct xdg_activation_token_v1_interface imp +{ + .set_serial = &RXdgActivationToken::set_serial, + .set_app_id = &RXdgActivationToken::set_app_id, + .set_surface = &RXdgActivationToken::set_surface, + .commit = &RXdgActivationToken::commit, + .destroy = &RXdgActivationToken::destroy +}; + +RXdgActivationToken::RXdgActivationToken( + GXdgActivation *xdgActivationRes, + UInt32 id + ) noexcept + :LResource + ( + xdgActivationRes->client(), + &xdg_activation_token_v1_interface, + xdgActivationRes->version(), + id, + &imp + ) +{} + +/******************** REQUESTS ********************/ + +void RXdgActivationToken::set_serial(wl_client */*client*/, wl_resource *resource, UInt32 serial, wl_resource */*seat*/) +{ + auto &res { *static_cast(wl_resource_get_user_data(resource)) }; + + if (res.m_commited) + { + wl_resource_post_error(res.resource(), XDG_ACTIVATION_TOKEN_V1_ERROR_ALREADY_USED, "Duplicated commit."); + return; + } + + res.m_serial = serial; +} + +void RXdgActivationToken::set_app_id(wl_client */*client*/, wl_resource *resource, const char *app_id) +{ + auto &res { *static_cast(wl_resource_get_user_data(resource)) }; + + if (res.m_commited) + { + wl_resource_post_error(res.resource(), XDG_ACTIVATION_TOKEN_V1_ERROR_ALREADY_USED, "Duplicated commit."); + return; + } + + res.m_appId = app_id; +} + +void RXdgActivationToken::set_surface(wl_client */*client*/, wl_resource *resource, wl_resource *surface) +{ + auto &res { *static_cast(wl_resource_get_user_data(resource)) }; + + if (res.m_commited) + { + wl_resource_post_error(res.resource(), XDG_ACTIVATION_TOKEN_V1_ERROR_ALREADY_USED, "Duplicated commit."); + return; + } + + auto &surfaceRes { *static_cast(wl_resource_get_user_data(surface)) }; + res.m_surface.reset(surfaceRes.surface()); +} + +static std::string randomToken() noexcept +{ + static bool seeded { false }; + static UInt32 n { 0 }; + n++; + + if (!seeded) + { + srand(LTime::ms()); + seeded = true; + } + + static std::string dic { "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" }; + static char random[17]; + + retry: + + for (int i = 0; i < 16; i++) + random[i] = dic[rand() % dic.length()]; + + random[16] = '\0'; + + std::string token = std::to_string(n) + "_" + random; + + if (activationTokenManager()->tokens().contains(token)) + goto retry; + + return token; +} + +void RXdgActivationToken::commit(wl_client */*client*/, wl_resource *resource) +{ + auto &res { *static_cast(wl_resource_get_user_data(resource)) }; + + if (res.m_commited) + { + wl_resource_post_error(res.resource(), XDG_ACTIVATION_TOKEN_V1_ERROR_ALREADY_USED, "Duplicated commit."); + return; + } + + res.m_commited = true; + + const LEvent *event { res.client()->findEventBySerial(res.m_serial) }; + auto pair = activationTokenManager()->m_tokens.emplace( + randomToken(), + LActivationToken( + res.client(), + res.m_surface, + event ? event->copy() : nullptr, + std::move(res.m_appId))).first; + + pair->second.m_key = &pair->first; + activationTokenManager()->m_token.reset(&pair->second); + activationTokenManager()->createTokenRequest(); + + // Request accepted + if (activationTokenManager()->token()) + { + activationTokenManager()->m_token.reset(); + res.done(pair->first); + } + // The user denied the request (destroyed the token) + else + res.done("INVALID_TOKEN"); +} + +void RXdgActivationToken::destroy(wl_client */*client*/, wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +/******************** EVENTS ********************/ + +void RXdgActivationToken::done(const std::string &token) noexcept +{ + xdg_activation_token_v1_send_done(resource(), token.c_str()); +} + diff --git a/src/lib/protocols/XdgActivation/RXdgActivationToken.h b/src/lib/protocols/XdgActivation/RXdgActivationToken.h new file mode 100644 index 00000000..32998ef6 --- /dev/null +++ b/src/lib/protocols/XdgActivation/RXdgActivationToken.h @@ -0,0 +1,35 @@ +#ifndef RXDGACTIVATIONTOKEN_H +#define RXDGACTIVATIONTOKEN_H + +#include +#include +#include + +class Louvre::Protocols::XdgActivation::RXdgActivationToken final : public LResource +{ +public: + + /******************** REQUESTS ********************/ + + static void set_serial(wl_client *client, wl_resource *resource, UInt32 serial, wl_resource *seat); + static void set_app_id(wl_client *client, wl_resource *resource, const char *app_id); + static void set_surface(wl_client *client, wl_resource *resource, wl_resource *surface); + static void commit(wl_client *client, wl_resource *resource); + static void destroy(wl_client *client, wl_resource *resource); + + /******************** EVENTS ********************/ + + void done(const std::string &token) noexcept; + +private: + friend class Louvre::Protocols::XdgActivation::GXdgActivation; + RXdgActivationToken(GXdgActivation *xdgActivationRes, + UInt32 id) noexcept; + ~RXdgActivationToken() = default; + LWeak m_surface; + std::string m_appId; + UInt32 m_serial { 0 }; + bool m_commited { false }; +}; + +#endif // RXDGACTIVATIONTOKEN_H diff --git a/src/lib/protocols/XdgActivation/xdg-activation-v1.c b/src/lib/protocols/XdgActivation/xdg-activation-v1.c new file mode 100644 index 00000000..9e6cfc58 --- /dev/null +++ b/src/lib/protocols/XdgActivation/xdg-activation-v1.c @@ -0,0 +1,84 @@ +/* Generated by wayland-scanner 1.22.0 */ + +/* + * Copyright © 2020 Aleix Pol Gonzalez + * Copyright © 2020 Carlos Garnacho + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include "wayland-util.h" + +#ifndef __has_attribute +# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ +#endif + +#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) +#define WL_PRIVATE __attribute__ ((visibility("hidden"))) +#else +#define WL_PRIVATE +#endif + +extern const struct wl_interface wl_seat_interface; +extern const struct wl_interface wl_surface_interface; +extern const struct wl_interface xdg_activation_token_v1_interface; + +static const struct wl_interface *xdg_activation_v1_types[] = { + NULL, + &xdg_activation_token_v1_interface, + NULL, + &wl_surface_interface, + NULL, + &wl_seat_interface, + &wl_surface_interface, +}; + +static const struct wl_message xdg_activation_v1_requests[] = { + { "destroy", "", xdg_activation_v1_types + 0 }, + { "get_activation_token", "n", xdg_activation_v1_types + 1 }, + { "activate", "so", xdg_activation_v1_types + 2 }, +}; + +WL_PRIVATE const struct wl_interface xdg_activation_v1_interface = { + "xdg_activation_v1", 1, + 3, xdg_activation_v1_requests, + 0, NULL, +}; + +static const struct wl_message xdg_activation_token_v1_requests[] = { + { "set_serial", "uo", xdg_activation_v1_types + 4 }, + { "set_app_id", "s", xdg_activation_v1_types + 0 }, + { "set_surface", "o", xdg_activation_v1_types + 6 }, + { "commit", "", xdg_activation_v1_types + 0 }, + { "destroy", "", xdg_activation_v1_types + 0 }, +}; + +static const struct wl_message xdg_activation_token_v1_events[] = { + { "done", "s", xdg_activation_v1_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface xdg_activation_token_v1_interface = { + "xdg_activation_token_v1", 1, + 5, xdg_activation_token_v1_requests, + 1, xdg_activation_token_v1_events, +}; + diff --git a/src/lib/protocols/XdgActivation/xdg-activation-v1.h b/src/lib/protocols/XdgActivation/xdg-activation-v1.h new file mode 100644 index 00000000..51c9e318 --- /dev/null +++ b/src/lib/protocols/XdgActivation/xdg-activation-v1.h @@ -0,0 +1,338 @@ +/* Generated by wayland-scanner 1.22.0 */ + +#ifndef XDG_ACTIVATION_V1_SERVER_PROTOCOL_H +#define XDG_ACTIVATION_V1_SERVER_PROTOCOL_H + +#include +#include +#include "wayland-server.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct wl_client; +struct wl_resource; + +/** + * @page page_xdg_activation_v1 The xdg_activation_v1 protocol + * Protocol for requesting activation of surfaces + * + * @section page_desc_xdg_activation_v1 Description + * + * The way for a client to pass focus to another toplevel is as follows. + * + * The client that intends to activate another toplevel uses the + * xdg_activation_v1.get_activation_token request to get an activation token. + * This token is then forwarded to the client, which is supposed to activate + * one of its surfaces, through a separate band of communication. + * + * One established way of doing this is through the XDG_ACTIVATION_TOKEN + * environment variable of a newly launched child process. The child process + * should unset the environment variable again right after reading it out in + * order to avoid propagating it to other child processes. + * + * Another established way exists for Applications implementing the D-Bus + * interface org.freedesktop.Application, which should get their token under + * activation-token on their platform_data. + * + * In general activation tokens may be transferred across clients through + * means not described in this protocol. + * + * The client to be activated will then pass the token + * it received to the xdg_activation_v1.activate request. The compositor can + * then use this token to decide how to react to the activation request. + * + * The token the activating client gets may be ineffective either already at + * the time it receives it, for example if it was not focused, for focus + * stealing prevention. The activating client will have no way to discover + * the validity of the token, and may still forward it to the to be activated + * client. + * + * The created activation token may optionally get information attached to it + * that can be used by the compositor to identify the application that we + * intend to activate. This can for example be used to display a visual hint + * about what application is being started. + * + * Warning! The protocol described in this file is currently in the testing + * phase. Backward compatible changes may be added together with the + * corresponding interface version bump. Backward incompatible changes can + * only be done by creating a new major version of the extension. + * + * @section page_ifaces_xdg_activation_v1 Interfaces + * - @subpage page_iface_xdg_activation_v1 - interface for activating surfaces + * - @subpage page_iface_xdg_activation_token_v1 - an exported activation handle + * @section page_copyright_xdg_activation_v1 Copyright + *
+ *
+ * Copyright © 2020 Aleix Pol Gonzalez 
+ * Copyright © 2020 Carlos Garnacho 
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * 
+ */ +struct wl_seat; +struct wl_surface; +struct xdg_activation_token_v1; +struct xdg_activation_v1; + +#ifndef XDG_ACTIVATION_V1_INTERFACE +#define XDG_ACTIVATION_V1_INTERFACE +/** + * @page page_iface_xdg_activation_v1 xdg_activation_v1 + * @section page_iface_xdg_activation_v1_desc Description + * + * A global interface used for informing the compositor about applications + * being activated or started, or for applications to request to be + * activated. + * @section page_iface_xdg_activation_v1_api API + * See @ref iface_xdg_activation_v1. + */ +/** + * @defgroup iface_xdg_activation_v1 The xdg_activation_v1 interface + * + * A global interface used for informing the compositor about applications + * being activated or started, or for applications to request to be + * activated. + */ +extern const struct wl_interface xdg_activation_v1_interface; +#endif +#ifndef XDG_ACTIVATION_TOKEN_V1_INTERFACE +#define XDG_ACTIVATION_TOKEN_V1_INTERFACE +/** + * @page page_iface_xdg_activation_token_v1 xdg_activation_token_v1 + * @section page_iface_xdg_activation_token_v1_desc Description + * + * An object for setting up a token and receiving a token handle that can + * be passed as an activation token to another client. + * + * The object is created using the xdg_activation_v1.get_activation_token + * request. This object should then be populated with the app_id, surface + * and serial information and committed. The compositor shall then issue a + * done event with the token. In case the request's parameters are invalid, + * the compositor will provide an invalid token. + * @section page_iface_xdg_activation_token_v1_api API + * See @ref iface_xdg_activation_token_v1. + */ +/** + * @defgroup iface_xdg_activation_token_v1 The xdg_activation_token_v1 interface + * + * An object for setting up a token and receiving a token handle that can + * be passed as an activation token to another client. + * + * The object is created using the xdg_activation_v1.get_activation_token + * request. This object should then be populated with the app_id, surface + * and serial information and committed. The compositor shall then issue a + * done event with the token. In case the request's parameters are invalid, + * the compositor will provide an invalid token. + */ +extern const struct wl_interface xdg_activation_token_v1_interface; +#endif + +/** + * @ingroup iface_xdg_activation_v1 + * @struct xdg_activation_v1_interface + */ +struct xdg_activation_v1_interface { + /** + * destroy the xdg_activation object + * + * Notify the compositor that the xdg_activation object will no + * longer be used. + * + * The child objects created via this interface are unaffected and + * should be destroyed separately. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * requests a token + * + * Creates an xdg_activation_token_v1 object that will provide + * the initiating client with a unique token for this activation. + * This token should be offered to the clients to be activated. + */ + void (*get_activation_token)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id); + /** + * notify new interaction being available + * + * Requests surface activation. It's up to the compositor to + * display this information as desired, for example by placing the + * surface above the rest. + * + * The compositor may know who requested this by checking the + * activation token and might decide not to follow through with the + * activation if it's considered unwanted. + * + * Compositors can ignore unknown activation tokens when an invalid + * token is passed. + * @param token the activation token of the initiating client + * @param surface the wl_surface to activate + */ + void (*activate)(struct wl_client *client, + struct wl_resource *resource, + const char *token, + struct wl_resource *surface); +}; + + +/** + * @ingroup iface_xdg_activation_v1 + */ +#define XDG_ACTIVATION_V1_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_activation_v1 + */ +#define XDG_ACTIVATION_V1_GET_ACTIVATION_TOKEN_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_activation_v1 + */ +#define XDG_ACTIVATION_V1_ACTIVATE_SINCE_VERSION 1 + +#ifndef XDG_ACTIVATION_TOKEN_V1_ERROR_ENUM +#define XDG_ACTIVATION_TOKEN_V1_ERROR_ENUM +enum xdg_activation_token_v1_error { + /** + * The token has already been used previously + */ + XDG_ACTIVATION_TOKEN_V1_ERROR_ALREADY_USED = 0, +}; +#endif /* XDG_ACTIVATION_TOKEN_V1_ERROR_ENUM */ + +/** + * @ingroup iface_xdg_activation_token_v1 + * @struct xdg_activation_token_v1_interface + */ +struct xdg_activation_token_v1_interface { + /** + * specifies the seat and serial of the activating event + * + * Provides information about the seat and serial event that + * requested the token. + * + * The serial can come from an input or focus event. For instance, + * if a click triggers the launch of a third-party client, the + * launcher client should send a set_serial request with the serial + * and seat from the wl_pointer.button event. + * + * Some compositors might refuse to activate toplevels when the + * token doesn't have a valid and recent enough event serial. + * + * Must be sent before commit. This information is optional. + * @param serial the serial of the event that triggered the activation + * @param seat the wl_seat of the event + */ + void (*set_serial)(struct wl_client *client, + struct wl_resource *resource, + uint32_t serial, + struct wl_resource *seat); + /** + * specifies the application being activated + * + * The requesting client can specify an app_id to associate the + * token being created with it. + * + * Must be sent before commit. This information is optional. + * @param app_id the application id of the client being activated. + */ + void (*set_app_id)(struct wl_client *client, + struct wl_resource *resource, + const char *app_id); + /** + * specifies the surface requesting activation + * + * This request sets the surface requesting the activation. Note, + * this is different from the surface that will be activated. + * + * Some compositors might refuse to activate toplevels when the + * token doesn't have a requesting surface. + * + * Must be sent before commit. This information is optional. + * @param surface the requesting surface + */ + void (*set_surface)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surface); + /** + * issues the token request + * + * Requests an activation token based on the different parameters + * that have been offered through set_serial, set_surface and + * set_app_id. + */ + void (*commit)(struct wl_client *client, + struct wl_resource *resource); + /** + * destroy the xdg_activation_token_v1 object + * + * Notify the compositor that the xdg_activation_token_v1 object + * will no longer be used. The received token stays valid. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); +}; + +#define XDG_ACTIVATION_TOKEN_V1_DONE 0 + +/** + * @ingroup iface_xdg_activation_token_v1 + */ +#define XDG_ACTIVATION_TOKEN_V1_DONE_SINCE_VERSION 1 + +/** + * @ingroup iface_xdg_activation_token_v1 + */ +#define XDG_ACTIVATION_TOKEN_V1_SET_SERIAL_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_activation_token_v1 + */ +#define XDG_ACTIVATION_TOKEN_V1_SET_APP_ID_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_activation_token_v1 + */ +#define XDG_ACTIVATION_TOKEN_V1_SET_SURFACE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_activation_token_v1 + */ +#define XDG_ACTIVATION_TOKEN_V1_COMMIT_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_activation_token_v1 + */ +#define XDG_ACTIVATION_TOKEN_V1_DESTROY_SINCE_VERSION 1 + +/** + * @ingroup iface_xdg_activation_token_v1 + * Sends an done event to the client owning the resource. + * @param resource_ The client's resource + * @param token the exported activation token + */ +static inline void +xdg_activation_token_v1_send_done(struct wl_resource *resource_, const char *token) +{ + wl_resource_post_event(resource_, XDG_ACTIVATION_TOKEN_V1_DONE, token); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/lib/protocols/XdgActivation/xdg-activation-v1.xml b/src/lib/protocols/XdgActivation/xdg-activation-v1.xml new file mode 100644 index 00000000..9adcc274 --- /dev/null +++ b/src/lib/protocols/XdgActivation/xdg-activation-v1.xml @@ -0,0 +1,200 @@ + + + + + Copyright © 2020 Aleix Pol Gonzalez <aleixpol@kde.org> + Copyright © 2020 Carlos Garnacho <carlosg@gnome.org> + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + The way for a client to pass focus to another toplevel is as follows. + + The client that intends to activate another toplevel uses the + xdg_activation_v1.get_activation_token request to get an activation token. + This token is then forwarded to the client, which is supposed to activate + one of its surfaces, through a separate band of communication. + + One established way of doing this is through the XDG_ACTIVATION_TOKEN + environment variable of a newly launched child process. The child process + should unset the environment variable again right after reading it out in + order to avoid propagating it to other child processes. + + Another established way exists for Applications implementing the D-Bus + interface org.freedesktop.Application, which should get their token under + activation-token on their platform_data. + + In general activation tokens may be transferred across clients through + means not described in this protocol. + + The client to be activated will then pass the token + it received to the xdg_activation_v1.activate request. The compositor can + then use this token to decide how to react to the activation request. + + The token the activating client gets may be ineffective either already at + the time it receives it, for example if it was not focused, for focus + stealing prevention. The activating client will have no way to discover + the validity of the token, and may still forward it to the to be activated + client. + + The created activation token may optionally get information attached to it + that can be used by the compositor to identify the application that we + intend to activate. This can for example be used to display a visual hint + about what application is being started. + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. + + + + + A global interface used for informing the compositor about applications + being activated or started, or for applications to request to be + activated. + + + + + Notify the compositor that the xdg_activation object will no longer be + used. + + The child objects created via this interface are unaffected and should + be destroyed separately. + + + + + + Creates an xdg_activation_token_v1 object that will provide + the initiating client with a unique token for this activation. This + token should be offered to the clients to be activated. + + + + + + + + Requests surface activation. It's up to the compositor to display + this information as desired, for example by placing the surface above + the rest. + + The compositor may know who requested this by checking the activation + token and might decide not to follow through with the activation if it's + considered unwanted. + + Compositors can ignore unknown activation tokens when an invalid + token is passed. + + + + + + + + + An object for setting up a token and receiving a token handle that can + be passed as an activation token to another client. + + The object is created using the xdg_activation_v1.get_activation_token + request. This object should then be populated with the app_id, surface + and serial information and committed. The compositor shall then issue a + done event with the token. In case the request's parameters are invalid, + the compositor will provide an invalid token. + + + + + + + + + Provides information about the seat and serial event that requested the + token. + + The serial can come from an input or focus event. For instance, if a + click triggers the launch of a third-party client, the launcher client + should send a set_serial request with the serial and seat from the + wl_pointer.button event. + + Some compositors might refuse to activate toplevels when the token + doesn't have a valid and recent enough event serial. + + Must be sent before commit. This information is optional. + + + + + + + + The requesting client can specify an app_id to associate the token + being created with it. + + Must be sent before commit. This information is optional. + + + + + + + This request sets the surface requesting the activation. Note, this is + different from the surface that will be activated. + + Some compositors might refuse to activate toplevels when the token + doesn't have a requesting surface. + + Must be sent before commit. This information is optional. + + + + + + + Requests an activation token based on the different parameters that + have been offered through set_serial, set_surface and set_app_id. + + + + + + The 'done' event contains the unique token of this activation request + and notifies that the provider is done. + + + + + + + Notify the compositor that the xdg_activation_token_v1 object will no + longer be used. The received token stays valid. + + + + diff --git a/src/meson.build b/src/meson.build index d32cad98..d3ed76ba 100644 --- a/src/meson.build +++ b/src/meson.build @@ -90,6 +90,7 @@ endforeach globals = [ 'LinuxDMABuf', 'Wayland', + 'XdgActivation', 'XdgDecoration', 'XdgShell', 'XdgOutput',