diff --git a/metadata/meson.build b/metadata/meson.build index 740b0b236..c035f62e2 100644 --- a/metadata/meson.build +++ b/metadata/meson.build @@ -37,6 +37,7 @@ install_data('workarounds.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('wrot.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('zoom.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('scale-title-filter.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) +install_data('shortcuts-inhibit.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('wsets.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('wayfire-shell.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('xdg-activation.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) diff --git a/metadata/shortcuts-inhibit.xml b/metadata/shortcuts-inhibit.xml new file mode 100644 index 000000000..b113816a8 --- /dev/null +++ b/metadata/shortcuts-inhibit.xml @@ -0,0 +1,20 @@ + + + + <_short>Keyboard Shortcuts Inhibit Protocol + <_long>An implementation of the keyboard-shortcuts-inhibit-v1 protocol. + Utility + + + + + + diff --git a/plugins/protocols/meson.build b/plugins/protocols/meson.build index cf4759ae4..ce60a3622 100644 --- a/plugins/protocols/meson.build +++ b/plugins/protocols/meson.build @@ -1,5 +1,5 @@ protocol_plugins = [ - 'foreign-toplevel', 'gtk-shell', 'wayfire-shell', 'xdg-activation', + 'foreign-toplevel', 'gtk-shell', 'wayfire-shell', 'xdg-activation', 'shortcuts-inhibit' ] all_include_dirs = [wayfire_api_inc, wayfire_conf_inc, plugins_common_inc] diff --git a/plugins/protocols/shortcuts-inhibit.cpp b/plugins/protocols/shortcuts-inhibit.cpp new file mode 100644 index 000000000..a2c2a5234 --- /dev/null +++ b/plugins/protocols/shortcuts-inhibit.cpp @@ -0,0 +1,180 @@ +#include "wayfire/core.hpp" +#include "wayfire/option-wrapper.hpp" +#include "wayfire/plugins/common/shared-core-data.hpp" +#include "wayfire/signal-definitions.hpp" +#include "wayfire/signal-provider.hpp" +#include "wayfire/util.hpp" +#include "wayfire/seat.hpp" +#include "wayfire/view.hpp" +#include "wayfire/matcher.hpp" +#include "wayfire/bindings-repository.hpp" +#include +#include +#include + +class wayfire_shortcuts_inhibit : public wf::plugin_interface_t +{ + public: + void init() override + { + inhibit_manager = wlr_keyboard_shortcuts_inhibit_v1_create(wf::get_core().display); + + keyboard_inhibit_new.set_callback([&] (void *data) + { + auto wlr_inhibitor = (struct wlr_keyboard_shortcuts_inhibitor_v1*)data; + if (inhibitors.count(wlr_inhibitor->surface)) + { + LOGE("Duplicate inhibitors for one surface not supported!"); + return; + } + + inhibitors[wlr_inhibitor->surface] = std::make_unique(); + auto& inhibitor = inhibitors[wlr_inhibitor->surface]; + + inhibitor->inhibitor = wlr_inhibitor; + inhibitor->on_destroy.set_callback([=] (auto) + { + deactivate_for_surface(wlr_inhibitor->surface); + this->inhibitors.erase(wlr_inhibitor->surface); + }); + inhibitor->on_destroy.connect(&wlr_inhibitor->events.destroy); + check_inhibit(wf::get_core().seat->get_active_node()); + }); + keyboard_inhibit_new.connect(&inhibit_manager->events.new_inhibitor); + wf::get_core().connect(&on_kb_focus_change); + wf::get_core().connect(&on_view_mapped); + wf::get_core().connect(&on_key_press); + } + + void fini() override + {} + + void check_inhibit(wf::scene::node_ptr focus) + { + auto focus_view = focus ? wf::node_to_view(focus) : nullptr; + wlr_surface *new_focus = focus_view ? focus_view->get_keyboard_focus_surface() : nullptr; + if (!inhibitors.count(new_focus)) + { + new_focus = nullptr; + } + + if (new_focus == last_focus) + { + return; + } + + deactivate_for_surface(last_focus); + activate_for_surface(new_focus); + } + + bool is_unloadable() override + { + return false; + } + + private: + wlr_keyboard_shortcuts_inhibit_manager_v1 *inhibit_manager; + wf::wl_listener_wrapper keyboard_inhibit_new; + wf::view_matcher_t inhibit_by_default{"shortcuts-inhibit/inhibit_by_default"}; + + struct inhibitor_t + { + bool active = false; + wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor; + wf::wl_listener_wrapper on_destroy; + }; + + std::map> inhibitors; + wlr_surface *last_focus = nullptr; + + void activate_for_surface(wlr_surface *surface) + { + if (!surface) + { + return; + } + + auto& inhibitor = inhibitors[surface]; + if (!inhibitor->active) + { + LOGD("Activating inhibitor for surface ", surface); + wf::get_core().bindings->set_enabled(false); + + if (inhibitor->inhibitor) + { + wlr_keyboard_shortcuts_inhibitor_v1_activate(inhibitor->inhibitor); + } + + inhibitor->active = true; + } + + last_focus = surface; + } + + void deactivate_for_surface(wlr_surface *surface) + { + if (!surface) + { + return; + } + + auto& inhibitor = inhibitors[surface]; + if (inhibitor->active) + { + LOGD("Deactivating inhibitor for surface ", surface); + wf::get_core().bindings->set_enabled(true); + + if (inhibitor->inhibitor) + { + wlr_keyboard_shortcuts_inhibitor_v1_deactivate(inhibitor->inhibitor); + } + + inhibitor->active = false; + } + + last_focus = nullptr; + } + + wf::signal::connection_t on_kb_focus_change = + [=] (wf::keyboard_focus_changed_signal *ev) + { + check_inhibit(ev->new_focus); + }; + + wf::signal::connection_t on_view_mapped = [=] (wf::view_mapped_signal *ev) + { + if (inhibit_by_default.matches(ev->view) && ev->view->get_keyboard_focus_surface()) + { + auto surface = ev->view->get_keyboard_focus_surface(); + inhibitors[surface] = std::make_unique(); + auto& inhibitor = inhibitors[surface]; + + inhibitor->inhibitor = nullptr; + inhibitor->on_destroy.set_callback([this, surface] (auto) + { + deactivate_for_surface(surface); + this->inhibitors.erase(surface); + }); + inhibitor->on_destroy.connect(&surface->events.destroy); + check_inhibit(wf::get_core().seat->get_active_node()); + } + }; + + wf::option_wrapper_t break_grab_key{"shortcuts-inhibit/break_grab"}; + + wf::signal::connection_t> on_key_press = + [=] (wf::input_event_signal *ev) + { + auto break_key = break_grab_key.value(); + + if ((ev->event->state == WL_KEYBOARD_KEY_STATE_PRESSED) && + (wf::get_core().seat->get_keyboard_modifiers() == break_key.get_modifiers()) && + (ev->event->keycode == break_key.get_key())) + { + LOGD("Force-break active inhibitor"); + deactivate_for_surface(last_focus); + } + }; +}; + +DECLARE_WAYFIRE_PLUGIN(wayfire_shortcuts_inhibit); diff --git a/proto/meson.build b/proto/meson.build index e3d3b7aec..4da01bf58 100644 --- a/proto/meson.build +++ b/proto/meson.build @@ -29,6 +29,7 @@ server_protocols = [ [wl_protocol_dir, 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml'], [wl_protocol_dir, 'unstable/relative-pointer/relative-pointer-unstable-v1.xml'], [wl_protocol_dir, 'unstable/tablet/tablet-unstable-v2.xml'], + [wl_protocol_dir, 'unstable/keyboard-shortcuts-inhibit/keyboard-shortcuts-inhibit-unstable-v1.xml'], 'wayfire-shell-unstable-v2.xml', 'gtk-shell.xml', 'wlr-layer-shell-unstable-v1.xml', diff --git a/src/api/wayfire/bindings-repository.hpp b/src/api/wayfire/bindings-repository.hpp index b0c8e407a..6b1b5eb3e 100644 --- a/src/api/wayfire/bindings-repository.hpp +++ b/src/api/wayfire/bindings-repository.hpp @@ -59,6 +59,11 @@ class bindings_repository_t /** Erase binding of any type by callback */ void rem_binding(void *callback); + /** + * Enable or disable the repository. The state is reference-counted and starts at 1 (enabled). + */ + void set_enabled(bool enabled); + struct impl; std::unique_ptr priv; }; diff --git a/src/api/wayfire/nonstd/wlroots-full.hpp b/src/api/wayfire/nonstd/wlroots-full.hpp index 396d7a1ac..ff6eae231 100644 --- a/src/api/wayfire/nonstd/wlroots-full.hpp +++ b/src/api/wayfire/nonstd/wlroots-full.hpp @@ -37,6 +37,7 @@ extern "C" #include #include #include +#include // Shells #if __has_include() diff --git a/src/api/wayfire/plugin.hpp b/src/api/wayfire/plugin.hpp index cb3bcaeeb..85ffffbc8 100644 --- a/src/api/wayfire/plugin.hpp +++ b/src/api/wayfire/plugin.hpp @@ -108,7 +108,7 @@ class plugin_interface_t using wayfire_plugin_load_func = wf::plugin_interface_t * (*)(); /** The version of Wayfire's API/ABI */ -constexpr uint32_t WAYFIRE_API_ABI_VERSION = 2023'10'05; +constexpr uint32_t WAYFIRE_API_ABI_VERSION = 2023'10'21; /** * Each plugin must also provide a function which returns the Wayfire API/ABI diff --git a/src/api/wayfire/seat.hpp b/src/api/wayfire/seat.hpp index 746113e35..22c44390c 100644 --- a/src/api/wayfire/seat.hpp +++ b/src/api/wayfire/seat.hpp @@ -54,6 +54,11 @@ class seat_t */ void set_active_node(wf::scene::node_ptr node); + /** + * Get the node which has current keyboard focus. + */ + wf::scene::node_ptr get_active_node(); + /** * Try to focus the given view. This may not work if another view or a node requests a higher focus * importance. diff --git a/src/core/seat/bindings-repository-impl.hpp b/src/core/seat/bindings-repository-impl.hpp index ea7bc26da..06cc3ae6f 100644 --- a/src/core/seat/bindings-repository-impl.hpp +++ b/src/core/seat/bindings-repository-impl.hpp @@ -16,7 +16,13 @@ struct wf::bindings_repository_t::impl { this->idle_recreate_hotspots.run_once([=] () { - hotspot_mgr.update_hotspots(activators); + if (enabled > 0) + { + hotspot_mgr.update_hotspots(activators); + } else + { + hotspot_mgr.update_hotspots({}); + } }); } @@ -33,4 +39,5 @@ struct wf::bindings_repository_t::impl }; wf::wl_idle_call idle_recreate_hotspots; + int enabled = 1; }; diff --git a/src/core/seat/bindings-repository.cpp b/src/core/seat/bindings-repository.cpp index 08b39f369..4626f0e72 100644 --- a/src/core/seat/bindings-repository.cpp +++ b/src/core/seat/bindings-repository.cpp @@ -49,6 +49,11 @@ void wf::bindings_repository_t::add_activator( bool wf::bindings_repository_t::handle_key(const wf::keybinding_t& pressed, uint32_t mod_binding_key) { + if (!priv->enabled) + { + return false; + } + std::vector> callbacks; for (auto& binding : this->priv->keys) { @@ -101,6 +106,11 @@ bool wf::bindings_repository_t::handle_key(const wf::keybinding_t& pressed, bool wf::bindings_repository_t::handle_axis(uint32_t modifiers, wlr_pointer_axis_event *ev) { + if (!priv->enabled) + { + return false; + } + std::vector callbacks; for (auto& binding : this->priv->axes) @@ -121,6 +131,11 @@ bool wf::bindings_repository_t::handle_axis(uint32_t modifiers, bool wf::bindings_repository_t::handle_button(const wf::buttonbinding_t& pressed) { + if (!priv->enabled) + { + return false; + } + std::vector> callbacks; for (auto& binding : this->priv->buttons) { @@ -165,6 +180,11 @@ bool wf::bindings_repository_t::handle_button(const wf::buttonbinding_t& pressed void wf::bindings_repository_t::handle_gesture(const wf::touchgesture_t& gesture) { + if (!priv->enabled) + { + return; + } + std::vector> callbacks; for (auto& binding : this->priv->activators) { @@ -218,3 +238,9 @@ void wf::bindings_repository_t::rem_binding(void *callback) priv->recreate_hotspots(); } } + +void wf::bindings_repository_t::set_enabled(bool enabled) +{ + priv->enabled += (enabled ? 1 : -1); + priv->recreate_hotspots(); +} diff --git a/src/core/seat/hotspot-manager.hpp b/src/core/seat/hotspot-manager.hpp index 0f460c424..1a89aa23d 100644 --- a/src/core/seat/hotspot-manager.hpp +++ b/src/core/seat/hotspot-manager.hpp @@ -87,6 +87,7 @@ class hotspot_manager_t void update_hotspots(const container_t& activators); private: + bool enabled = true; std::vector> hotspots; }; } diff --git a/src/core/seat/seat.cpp b/src/core/seat/seat.cpp index f1fe0e89a..58fe550de 100644 --- a/src/core/seat/seat.cpp +++ b/src/core/seat/seat.cpp @@ -42,6 +42,11 @@ void wf::seat_t::set_active_node(wf::scene::node_ptr node) priv->set_keyboard_focus(focus.node ? focus.node->shared_from_this() : nullptr); } +wf::scene::node_ptr wf::seat_t::get_active_node() +{ + return priv->keyboard_focus; +} + void wf::seat_t::focus_output(wf::output_t *wo) { if (priv->active_output == wo)