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..515c9b321
--- /dev/null
+++ b/plugins/protocols/shortcuts-inhibit.cpp
@@ -0,0 +1,181 @@
+#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
+#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/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()