diff --git a/metadata/xdg-activation.xml b/metadata/xdg-activation.xml
index 6eb8c5514..71fe62a12 100644
--- a/metadata/xdg-activation.xml
+++ b/metadata/xdg-activation.xml
@@ -14,6 +14,11 @@
<_long>Whether to reject activation requests if a newer request has arrived since their creation.
false
+
+ <_short>Prevent stealing focus by an activation request
+ <_long>Whether to reject an activation request if the user interacted with a different view since its creation.
+ true
+
<_short>Timeout for activation (in seconds)
<_long>Focus requests will be ignored if at least this amount of time has elapsed between creating and using it.
diff --git a/plugins/protocols/xdg-activation.cpp b/plugins/protocols/xdg-activation.cpp
index d87beee53..5926b19af 100644
--- a/plugins/protocols/xdg-activation.cpp
+++ b/plugins/protocols/xdg-activation.cpp
@@ -36,6 +36,12 @@ class wayfire_xdg_activation_protocol_impl : public wf::plugin_interface_t
xdg_activation_new_token.disconnect();
xdg_activation_token_destroy.disconnect();
last_token = nullptr;
+ if (last_view)
+ {
+ last_view->disconnect(&on_view_unmapped);
+ last_view->disconnect(&on_view_deactivated);
+ last_view = nullptr;
+ }
}
bool is_unloadable() override
@@ -64,6 +70,17 @@ class wayfire_xdg_activation_protocol_impl : public wf::plugin_interface_t
last_token = nullptr; // avoid reusing the same token
+ if (last_view)
+ {
+ last_view->disconnect(&on_view_unmapped);
+ last_view->disconnect(&on_view_deactivated);
+ last_view = nullptr;
+ } else if (prevent_focus_stealing)
+ {
+ LOGI("Denying focus request, requesting view has been deactivated");
+ return;
+ }
+
wayfire_view view = wf::wl_surface_to_wayfire_view(event->surface->resource);
if (!view)
{
@@ -100,6 +117,26 @@ class wayfire_xdg_activation_protocol_impl : public wf::plugin_interface_t
return;
}
+ // unset any previously saved view
+ if (last_view)
+ {
+ last_view->disconnect(&on_view_unmapped);
+ last_view->disconnect(&on_view_deactivated);
+ last_view = nullptr;
+ }
+
+ wayfire_view view = token->surface ? wf::wl_surface_to_wayfire_view(
+ token->surface->resource) : nullptr;
+ if (view)
+ {
+ last_view = wf::toplevel_cast(view); // might return nullptr
+ if (last_view)
+ {
+ last_view->connect(&on_view_unmapped);
+ last_view->connect(&on_view_deactivated);
+ }
+ }
+
// update our token and connect its destroy signal
last_token = token;
xdg_activation_token_destroy.disconnect();
@@ -125,14 +162,43 @@ class wayfire_xdg_activation_protocol_impl : public wf::plugin_interface_t
}
};
+ wf::signal::connection_t on_view_unmapped = [this] (auto)
+ {
+ last_view->disconnect(&on_view_unmapped);
+ last_view->disconnect(&on_view_deactivated);
+ // handle the case when last_view was a dialog that is closed by user interaction
+ last_view = last_view->parent;
+ if (last_view)
+ {
+ last_view->connect(&on_view_unmapped);
+ last_view->connect(&on_view_deactivated);
+ }
+ };
+
+ wf::signal::connection_t on_view_deactivated = [this] (auto)
+ {
+ if (last_view->activated)
+ {
+ // could be a spurious event, e.g. activating the parent
+ // view after closing a dialog
+ return;
+ }
+
+ last_view->disconnect(&on_view_unmapped);
+ last_view->disconnect(&on_view_deactivated);
+ last_view = nullptr;
+ };
+
struct wlr_xdg_activation_v1 *xdg_activation;
wf::wl_listener_wrapper xdg_activation_request_activate;
wf::wl_listener_wrapper xdg_activation_new_token;
wf::wl_listener_wrapper xdg_activation_token_destroy;
struct wlr_xdg_activation_token_v1 *last_token = nullptr;
+ wayfire_toplevel_view last_view = nullptr; // view that created the token
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 prevent_focus_stealing{"xdg-activation/focus_stealing_prevention"};
wf::option_wrapper_t timeout{"xdg-activation/timeout"};
};