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)