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);