Skip to content

Commit

Permalink
Add XDG activation protocol
Browse files Browse the repository at this point in the history
  • Loading branch information
ehopperdietzel committed Aug 23, 2024
1 parent 557a0d3 commit 8a3ddc0
Show file tree
Hide file tree
Showing 32 changed files with 1,414 additions and 20 deletions.
9 changes: 9 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
Louvre (2.7.0-1)

# New Protocols

* XDG Activation: See LActivationTokenManager and LActivationToken for details.

-- Eduardo Hopperdietzel <[email protected]> Thu, 22 Aug 2024 20:00:51 -0400


Louvre (2.6.0-1)

# New Protocols
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="Louvre is released under the MIT license." />
</a>
<a href="https://github.com/CuarzoSoftware/Louvre">
<img src="https://img.shields.io/badge/version-2.6.0-brightgreen" alt="Current Louvre version." />
<img src="https://img.shields.io/badge/version-2.7.0-brightgreen" alt="Current Louvre version." />
</a>
</p>

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.6.0
2.7.0
1 change: 1 addition & 0 deletions doxygen/md/tutorial/tmp.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
18 changes: 18 additions & 0 deletions src/lib/core/LActivationToken.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#include <LActivationTokenManager.h>
#include <LClient.h>
#include <LSurface.h>

using namespace Louvre;

const std::unordered_map<std::string, LActivationToken>::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)
{}
140 changes: 140 additions & 0 deletions src/lib/core/LActivationToken.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
#ifndef LACTIVATIONTOKEN_H
#define LACTIVATIONTOKEN_H

#include <LObject.h>
#include <LWeak.h>
#include <LEvent.h>
#include <memory>
#include <chrono>

/**
* @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<std::chrono::steady_clock> &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<std::string, LActivationToken>::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<LClient> m_creator;
LWeak<LSurface> m_origin;
std::unique_ptr<LEvent> m_triggeringEvent;
std::string m_toActivateAppId;
std::chrono::time_point<std::chrono::steady_clock> m_created {
std::chrono::steady_clock::now()
};
UInt32 m_timesUsed { 0 };
const std::string *m_key { nullptr };
};

#endif // LACTIVATIONTOKEN_H
29 changes: 29 additions & 0 deletions src/lib/core/LActivationTokenManager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#include <LActivationTokenManager.h>
#include <cassert>

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::milliseconds>(std::chrono::steady_clock::now() - (*it).second.created()).count() > ms)
it = m_tokens.erase(it);
else
it++;
}
}
144 changes: 144 additions & 0 deletions src/lib/core/LActivationTokenManager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#ifndef LACTIVATIONTOKENMANAGER_H
#define LACTIVATIONTOKENMANAGER_H

#include <LFactoryObject.h>
#include <LActivationToken.h>

/**
* @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<std::string, LActivationToken> &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<std::string, LActivationToken> m_tokens;
LWeak<LActivationToken> m_token;
};

#endif // LACTIVATIONTOKENMANAGER_H
5 changes: 5 additions & 0 deletions src/lib/core/LClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,11 @@ const std::vector<IdleInhibit::GIdleInhibitManager *> &LClient::idleInhibitManag
return imp()->idleInhibitManagerGlobals;
}

const std::vector<XdgActivation::GXdgActivation *> &LClient::xdgActivationGlobals() const noexcept
{
return imp()->xdgActivationGlobals;
}

const LClient::EventHistory &LClient::eventHistory() const noexcept
{
return imp()->eventHistory;
Expand Down
Loading

0 comments on commit 8a3ddc0

Please sign in to comment.