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..d87beee53 100644 --- a/plugins/protocols/xdg-activation.cpp +++ b/plugins/protocols/xdg-activation.cpp @@ -7,21 +7,36 @@ #include #include #include +#include #include "config.h" 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(); + xdg_activation_token_destroy.disconnect(); + last_token = nullptr; + } bool is_unloadable() override { @@ -29,36 +44,96 @@ 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); + + 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 + + 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) + auto toplevel = wf::toplevel_cast(view); + if (!toplevel) + { + LOGE("Could not get toplevel view"); + return; + } + + LOGD("Activating view"); + wf::get_core().default_wm->focus_request(toplevel); + }); + + xdg_activation_new_token.set_callback([this] (void *data) { - LOGE("Could not get toplevel view"); - return; - } + auto token = static_cast(data); + 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"); + return; + } - if (!event->token->seat) + 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 + return; + } + + // update our token and connect its destroy signal + last_token = token; + xdg_activation_token_destroy.disconnect(); + xdg_activation_token_destroy.connect(&token->events.destroy); + }); + + xdg_activation_token_destroy.set_callback([this] (void *data) { - LOGI("Denying focus request, seat wasn't supplied"); - return; - } + last_token = nullptr; - LOGI("Activating view"); - wf::get_core().default_wm->focus_request(toplevel); + xdg_activation_token_destroy.disconnect(); + }); + + 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; + wf::wl_listener_wrapper xdg_activation_token_destroy; + 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);