From 0f11aa037ad8765abf66e0c90052f9e4c37d56db Mon Sep 17 00:00:00 2001
From: Michael Weiser <michael.weiser@gmx.de>
Date: Thu, 12 Mar 2020 22:10:04 +0100
Subject: [PATCH] commands: Add per-view shortcuts_inhibitor command

Add a separate per-view shortcuts_inhibitor command that can be used
with criteria to override the per-seat defaults. This allows to e.g.
disable shortcuts inhibiting globally but enable it for specific,
known-good virtualization and remote desktop software or, alternatively,
to blacklist that one slightly broken piece of software that just
doesn't seem to get it right but insists on trying.

Add a flag to sway_view and handling logic in the input manager that
respects that flag if configured but falls back to per-seat config
otherwise. Add the actual command but with just enable and disable
subcommands since there's no value in duplicating the per-seat
activate/deactivate/toggle logic here. Split the inhibitor retrieval
helper in two so we can use the backend half in the command to retrieve
inhibitors for a specific surface and not just the currently focused
one. Extend the manual page with documentation of the command and
references to its per-seat sibling and usefulness with criteria.

Signed-off-by: Michael Weiser <michael.weiser@gmx.de>
---
 include/sway/commands.h             |  1 +
 include/sway/input/seat.h           |  8 +++++
 include/sway/tree/view.h            |  2 ++
 sway/commands.c                     |  1 +
 sway/commands/shortcuts_inhibitor.c | 49 +++++++++++++++++++++++++++++
 sway/input/input-manager.c          | 22 ++++++++++---
 sway/input/seat.c                   | 16 +++++++---
 sway/meson.build                    |  1 +
 sway/sway.5.scd                     | 11 +++++++
 sway/tree/view.c                    |  1 +
 10 files changed, 103 insertions(+), 9 deletions(-)
 create mode 100644 sway/commands/shortcuts_inhibitor.c

diff --git a/include/sway/commands.h b/include/sway/commands.h
index 3fde0893a..4a2f8c20d 100644
--- a/include/sway/commands.h
+++ b/include/sway/commands.h
@@ -164,6 +164,7 @@ sway_cmd cmd_resize;
 sway_cmd cmd_scratchpad;
 sway_cmd cmd_seamless_mouse;
 sway_cmd cmd_set;
+sway_cmd cmd_shortcuts_inhibitor;
 sway_cmd cmd_show_marks;
 sway_cmd cmd_smart_borders;
 sway_cmd cmd_smart_gaps;
diff --git a/include/sway/input/seat.h b/include/sway/input/seat.h
index fa232aa24..6a46fa917 100644
--- a/include/sway/input/seat.h
+++ b/include/sway/input/seat.h
@@ -294,6 +294,14 @@ void seatop_render(struct sway_seat *seat, struct sway_output *output,
 
 bool seatop_allows_set_cursor(struct sway_seat *seat);
 
+/**
+ * Returns the keyboard shortcuts inhibitor that applies to the given surface
+ * or NULL if none exists.
+ */
+struct sway_keyboard_shortcuts_inhibitor *
+keyboard_shortcuts_inhibitor_get_for_surface(const struct sway_seat *seat,
+		const struct wlr_surface *surface);
+
 /**
  * Returns the keyboard shortcuts inhibitor that applies to the currently
  * focused surface of a seat or NULL if none exists.
diff --git a/include/sway/tree/view.h b/include/sway/tree/view.h
index 4d3532d27..9230f456c 100644
--- a/include/sway/tree/view.h
+++ b/include/sway/tree/view.h
@@ -110,6 +110,8 @@ struct sway_view {
 	struct wl_listener surface_new_subsurface;
 
 	int max_render_time; // In milliseconds
+
+	enum seat_config_shortcuts_inhibit shortcuts_inhibit;
 };
 
 struct sway_xdg_shell_view {
diff --git a/sway/commands.c b/sway/commands.c
index 6a56ff5a9..afe05b26e 100644
--- a/sway/commands.c
+++ b/sway/commands.c
@@ -127,6 +127,7 @@ static struct cmd_handler command_handlers[] = {
 	{ "rename", cmd_rename },
 	{ "resize", cmd_resize },
 	{ "scratchpad", cmd_scratchpad },
+	{ "shortcuts_inhibitor", cmd_shortcuts_inhibitor },
 	{ "split", cmd_split },
 	{ "splith", cmd_splith },
 	{ "splitt", cmd_splitt },
diff --git a/sway/commands/shortcuts_inhibitor.c b/sway/commands/shortcuts_inhibitor.c
new file mode 100644
index 000000000..ffa1a5c99
--- /dev/null
+++ b/sway/commands/shortcuts_inhibitor.c
@@ -0,0 +1,49 @@
+#include <string.h>
+#include "log.h"
+#include "sway/commands.h"
+#include "sway/config.h"
+#include "sway/input/seat.h"
+#include "sway/tree/container.h"
+#include "sway/tree/view.h"
+
+struct cmd_results *cmd_shortcuts_inhibitor(int argc, char **argv) {
+	struct cmd_results *error = NULL;
+	if ((error = checkarg(argc, "shortcuts_inhibitor", EXPECTED_EQUAL_TO, 1))) {
+		return error;
+	}
+
+	struct sway_container *con = config->handler_context.container;
+	if (!con || !con->view) {
+		return cmd_results_new(CMD_INVALID,
+				"Only views can have shortcuts inhibitors");
+	}
+
+	struct sway_view *view = con->view;
+	if (strcmp(argv[0], "enable") == 0) {
+		view->shortcuts_inhibit = SHORTCUTS_INHIBIT_ENABLE;
+	} else if (strcmp(argv[0], "disable") == 0) {
+		view->shortcuts_inhibit = SHORTCUTS_INHIBIT_DISABLE;
+
+		struct sway_seat *seat = NULL;
+		wl_list_for_each(seat, &server.input->seats, link) {
+			struct sway_keyboard_shortcuts_inhibitor *sway_inhibitor =
+				keyboard_shortcuts_inhibitor_get_for_surface(
+						seat, view->surface);
+			if (!sway_inhibitor) {
+				continue;
+			}
+
+			wlr_keyboard_shortcuts_inhibitor_v1_deactivate(
+					sway_inhibitor->inhibitor);
+			sway_log(SWAY_DEBUG, "Deactivated keyboard shortcuts "
+					"inhibitor for seat %s on view",
+					seat->wlr_seat->name);
+
+		}
+	} else {
+		return cmd_results_new(CMD_INVALID,
+				"Expected `shortcuts_inhibitor enable|disable`");
+	}
+
+	return cmd_results_new(CMD_SUCCESS, NULL);
+}
diff --git a/sway/input/input-manager.c b/sway/input/input-manager.c
index 243f860ba..dc07cbf06 100644
--- a/sway/input/input-manager.c
+++ b/sway/input/input-manager.c
@@ -15,6 +15,7 @@
 #include "sway/input/cursor.h"
 #include "sway/ipc-server.h"
 #include "sway/server.h"
+#include "sway/tree/view.h"
 #include "stringop.h"
 #include "list.h"
 #include "log.h"
@@ -333,12 +334,25 @@ static void handle_keyboard_shortcuts_inhibit_new_inhibitor(
 	struct sway_seat *seat = inhibitor->seat->data;
 	wl_list_insert(&seat->keyboard_shortcuts_inhibitors, &sway_inhibitor->link);
 
-	struct seat_config *config = seat_get_config(seat);
-	if (!config) {
-		config = seat_get_config_by_name("*");
+	// per-view, seat-agnostic config via criteria
+	struct sway_view *view = view_from_wlr_surface(inhibitor->surface);
+	enum seat_config_shortcuts_inhibit inhibit = SHORTCUTS_INHIBIT_DEFAULT;
+	if (view) {
+		inhibit = view->shortcuts_inhibit;
 	}
 
-	if (config && config->shortcuts_inhibit == SHORTCUTS_INHIBIT_DISABLE) {
+	if (inhibit == SHORTCUTS_INHIBIT_DEFAULT) {
+		struct seat_config *config = seat_get_config(seat);
+		if (!config) {
+			config = seat_get_config_by_name("*");
+		}
+
+		if (config) {
+			inhibit = config->shortcuts_inhibit;
+		}
+	}
+
+	if (inhibit == SHORTCUTS_INHIBIT_DISABLE) {
 		/**
 		 * Here we deny to honour the inhibitor by never sending the
 		 * activate signal. We can not, however, destroy the inhibitor
diff --git a/sway/input/seat.c b/sway/input/seat.c
index aa46940d6..a4e06c57a 100644
--- a/sway/input/seat.c
+++ b/sway/input/seat.c
@@ -1499,16 +1499,22 @@ bool seatop_allows_set_cursor(struct sway_seat *seat) {
 }
 
 struct sway_keyboard_shortcuts_inhibitor *
-keyboard_shortcuts_inhibitor_get_for_focused_surface(
-		const struct sway_seat *seat) {
-	struct wlr_surface *focused_surface =
-		seat->wlr_seat->keyboard_state.focused_surface;
+keyboard_shortcuts_inhibitor_get_for_surface(
+		const struct sway_seat *seat,
+		const struct wlr_surface *surface) {
 	struct sway_keyboard_shortcuts_inhibitor *sway_inhibitor = NULL;
 	wl_list_for_each(sway_inhibitor, &seat->keyboard_shortcuts_inhibitors, link) {
-		if (sway_inhibitor->inhibitor->surface == focused_surface) {
+		if (sway_inhibitor->inhibitor->surface == surface) {
 			return sway_inhibitor;
 		}
 	}
 
 	return NULL;
 }
+
+struct sway_keyboard_shortcuts_inhibitor *
+keyboard_shortcuts_inhibitor_get_for_focused_surface(
+		const struct sway_seat *seat) {
+	return keyboard_shortcuts_inhibitor_get_for_surface(seat,
+		seat->wlr_seat->keyboard_state.focused_surface);
+}
diff --git a/sway/meson.build b/sway/meson.build
index 226e6458f..d71846a48 100644
--- a/sway/meson.build
+++ b/sway/meson.build
@@ -99,6 +99,7 @@ sway_sources = files(
 	'commands/seat/xcursor_theme.c',
 	'commands/set.c',
 	'commands/show_marks.c',
+	'commands/shortcuts_inhibitor.c',
 	'commands/smart_borders.c',
 	'commands/smart_gaps.c',
 	'commands/split.c',
diff --git a/sway/sway.5.scd b/sway/sway.5.scd
index febf749fb..9e42d8972 100644
--- a/sway/sway.5.scd
+++ b/sway/sway.5.scd
@@ -304,6 +304,17 @@ set|plus|minus <amount>
 	Shows a window from the scratchpad. Repeatedly using this command will
 	cycle through the windows in the scratchpad.
 
+*shortcuts inhibitor* enable|disable
+	Enables or disables the ability of clients to inhibit keyboard
+	shortcuts for a view. This is primarily useful for virtualization and
+	remote desktop software. It affects either the currently focused view
+	or a set of views selected by criteria. Subcommand _disable_
+	additionally deactivates any active inhibitors for the given view(s).
+	Criteria are particularly useful with the *for_window* command to
+	configure a class of views differently from the per-seat defaults
+	established by the *seat* subcommand of the same name. See
+	*sway-input*(5) for more ways to affect inhibitors.
+
 *split* vertical|v|horizontal|h|toggle|t
 	Splits the current container, vertically or horizontally. When _toggle_ is
 	specified, the current container is split opposite to the parent
diff --git a/sway/tree/view.c b/sway/tree/view.c
index de1e936aa..2b4b6c09b 100644
--- a/sway/tree/view.c
+++ b/sway/tree/view.c
@@ -36,6 +36,7 @@ void view_init(struct sway_view *view, enum sway_view_type type,
 	view->impl = impl;
 	view->executed_criteria = create_list();
 	view->allow_request_urgent = true;
+	view->shortcuts_inhibit = SHORTCUTS_INHIBIT_DEFAULT;
 	wl_signal_init(&view->events.unmap);
 }