diff --git a/metadata/xdg-activation.xml b/metadata/xdg-activation.xml
index 27193249e..6eb8c5514 100644
--- a/metadata/xdg-activation.xml
+++ b/metadata/xdg-activation.xml
@@ -2,7 +2,23 @@
<_short>XDG Activation Protocol
- <_long>An implementation of the xdg-activation-v1 protocol.
+ <_long>An implementation of the xdg-activation-v1 protocol. This allows the active app to pass the focus to a different view in the same or a different app.
Utility
+
+
+
diff --git a/plugins/protocols/xdg-activation.cpp b/plugins/protocols/xdg-activation.cpp
index 8f41770a3..0b6149cd9 100644
--- a/plugins/protocols/xdg-activation.cpp
+++ b/plugins/protocols/xdg-activation.cpp
@@ -12,16 +12,29 @@
class wayfire_xdg_activation_protocol_impl : public wf::plugin_interface_t
{
public:
+ wayfire_xdg_activation_protocol_impl()
+ {
+ set_callbacks();
+ }
+
void init() override
{
xdg_activation = wlr_xdg_activation_v1_create(wf::get_core().display);
- xdg_activation_request_activate.notify = xdg_activation_handle_request_activate;
+ if (timeout >= 0)
+ {
+ xdg_activation->token_timeout_msec = 1000 * timeout;
+ }
- wl_signal_add(&xdg_activation->events.request_activate, &xdg_activation_request_activate);
+ xdg_activation_request_activate.connect(&xdg_activation->events.request_activate);
+ xdg_activation_new_token.connect(&xdg_activation->events.new_token);
}
void fini() override
- {}
+ {
+ xdg_activation_request_activate.disconnect();
+ xdg_activation_new_token.disconnect();
+ last_token = nullptr;
+ }
bool is_unloadable() override
{
@@ -29,36 +42,105 @@ class wayfire_xdg_activation_protocol_impl : public wf::plugin_interface_t
}
private:
- static void xdg_activation_handle_request_activate(struct wl_listener *listener, void *data)
+ void set_callbacks()
{
- auto event = static_cast(data);
-
- wayfire_view view = wf::wl_surface_to_wayfire_view(event->surface->resource);
- if (!view)
+ xdg_activation_request_activate.set_callback([this] (void *data)
{
- LOGE("Could not get view");
- return;
- }
+ auto event = static_cast(data);
- auto toplevel = wf::toplevel_cast(view);
- if (!toplevel)
- {
- LOGE("Could not get toplevel view");
- return;
- }
+ if (!event->token->seat)
+ {
+ LOGI("Denying focus request, token was rejected at creation");
+ return;
+ }
+
+ if (only_last_token && (event->token != last_token))
+ {
+ LOGI("Denying focus request, token is expired");
+ return;
+ }
+
+ last_token = nullptr; // avoid reusing the same token
- if (!event->token->seat)
+ wayfire_view view = wf::wl_surface_to_wayfire_view(event->surface->resource);
+ if (!view)
+ {
+ LOGE("Could not get view");
+ return;
+ }
+
+ auto toplevel = wf::toplevel_cast(view);
+ if (!toplevel)
+ {
+ LOGE("Could not get toplevel view");
+ return;
+ }
+
+ LOGI("Activating view");
+ wf::get_core().default_wm->focus_request(toplevel);
+ });
+
+ xdg_activation_new_token.set_callback([this] (void *data)
{
- LOGI("Denying focus request, seat wasn't supplied");
- return;
- }
+ auto token = static_cast(data);
+ bool reject_token = false;
+ if (!token->seat)
+ {
+ // note: for a valid seat, wlroots already checks that the serial is valid
+ LOGI("Not registering activation token, seat was not supplied");
+ reject_token = true;
+ }
+
+ if (check_surface && !token->surface)
+ {
+ // note: for a valid surface, wlroots already checks that this is the active surface
+ LOGI("Not registering activation token, surface was not supplied");
+ token->seat = nullptr; // this will ensure that this token will be rejected later
+ reject_token = true;
+ }
+
+ if (reject_token)
+ {
+ if (token == last_token)
+ {
+ /* corner case: (1) we created a valid token, storing it in last_token (2) it was freed by
+ * wlroots without using it (3) a new token is created that is allocated the same memory.
+ * In this case, we explicitly mark it as invalid. In other cases, we do not touch
+ * last_token, since it can be a valid one and we don't want to cancel it because of a
+ * rejected request.
+ */
+ last_token = nullptr;
+ }
+
+ // we will reject this token also in the activate callback
+ return;
+ }
- LOGI("Activating view");
- wf::get_core().default_wm->focus_request(toplevel);
+ last_token = token; // update our token
+ });
+
+ timeout.set_callback(timeout_changed);
}
+ wf::config::option_base_t::updated_callback_t timeout_changed =
+ [this] ()
+ {
+ if (xdg_activation && (timeout >= 0))
+ {
+ xdg_activation->token_timeout_msec = 1000 * timeout;
+ }
+ };
+
struct wlr_xdg_activation_v1 *xdg_activation;
- struct wl_listener xdg_activation_request_activate;
+ wf::wl_listener_wrapper xdg_activation_request_activate;
+ wf::wl_listener_wrapper xdg_activation_new_token;
+ /* last valid token generated -- might be stale if it was destroyed by wlroots, should not be
+ * dereferenced, only compared to other tokens */
+ struct wlr_xdg_activation_token_v1 *last_token = nullptr;
+
+ wf::option_wrapper_t check_surface{"xdg-activation/check_surface"};
+ wf::option_wrapper_t only_last_token{"xdg-activation/only_last_request"};
+ wf::option_wrapper_t timeout{"xdg-activation/timeout"};
};
DECLARE_WAYFIRE_PLUGIN(wayfire_xdg_activation_protocol_impl);