diff --git a/include/sway/commands.h b/include/sway/commands.h
index 7533a14d3..9bd0f1cb5 100644
--- a/include/sway/commands.h
+++ b/include/sway/commands.h
@@ -136,6 +136,7 @@ sway_cmd cmd_fullscreen;
 sway_cmd cmd_gaps;
 sway_cmd cmd_hide_edge_borders;
 sway_cmd cmd_include;
+sway_cmd cmd_inhibit_idle;
 sway_cmd cmd_input;
 sway_cmd cmd_seat;
 sway_cmd cmd_ipc;
diff --git a/include/sway/desktop/idle_inhibit_v1.h b/include/sway/desktop/idle_inhibit_v1.h
index e5ed8a3de..4d4e59b0b 100644
--- a/include/sway/desktop/idle_inhibit_v1.h
+++ b/include/sway/desktop/idle_inhibit_v1.h
@@ -4,6 +4,14 @@
 #include <wlr/types/wlr_idle.h>
 #include "sway/server.h"
 
+enum sway_idle_inhibit_mode {
+	INHIBIT_IDLE_APPLICATION,  // Application set inhibitor (when visible)
+	INHIBIT_IDLE_FOCUS,  // User set inhibitor when focused
+	INHIBIT_IDLE_FULLSCREEN,  // User set inhibitor when fullscreen + visible
+	INHIBIT_IDLE_OPEN,  // User set inhibitor while open
+	INHIBIT_IDLE_VISIBLE  // User set inhibitor when visible
+};
+
 struct sway_idle_inhibit_manager_v1 {
 	struct wlr_idle_inhibit_manager_v1 *wlr_manager;
 	struct wl_listener new_idle_inhibitor_v1;
@@ -15,14 +23,24 @@ struct sway_idle_inhibit_manager_v1 {
 struct sway_idle_inhibitor_v1 {
 	struct sway_idle_inhibit_manager_v1 *manager;
 	struct sway_view *view;
+	enum sway_idle_inhibit_mode mode;
 
 	struct wl_list link;
 	struct wl_listener destroy;
 };
 
-void idle_inhibit_v1_check_active(
+void sway_idle_inhibit_v1_check_active(
 	struct sway_idle_inhibit_manager_v1 *manager);
 
+void sway_idle_inhibit_v1_user_inhibitor_register(struct sway_view *view,
+		enum sway_idle_inhibit_mode mode);
+
+struct sway_idle_inhibitor_v1 *sway_idle_inhibit_v1_user_inhibitor_for_view(
+		struct sway_view *view);
+
+void sway_idle_inhibit_v1_user_inhibitor_destroy(
+		struct sway_idle_inhibitor_v1 *inhibitor);
+
 struct sway_idle_inhibit_manager_v1 *sway_idle_inhibit_manager_v1_create(
 	struct wl_display *wl_display, struct wlr_idle *idle);
 #endif
diff --git a/sway/commands.c b/sway/commands.c
index 0d9460a21..abdaa3b87 100644
--- a/sway/commands.c
+++ b/sway/commands.c
@@ -112,6 +112,7 @@ static struct cmd_handler command_handlers[] = {
 	{ "exit", cmd_exit },
 	{ "floating", cmd_floating },
 	{ "fullscreen", cmd_fullscreen },
+	{ "inhibit_idle", cmd_inhibit_idle },
 	{ "kill", cmd_kill },
 	{ "layout", cmd_layout },
 	{ "mark", cmd_mark },
diff --git a/sway/commands/inhibit_idle.c b/sway/commands/inhibit_idle.c
new file mode 100644
index 000000000..aebc2bf9f
--- /dev/null
+++ b/sway/commands/inhibit_idle.c
@@ -0,0 +1,51 @@
+#include <string.h>
+#include "sway/commands.h"
+#include "sway/config.h"
+#include "sway/desktop/idle_inhibit_v1.h"
+#include "sway/tree/container.h"
+#include "sway/tree/view.h"
+
+struct cmd_results *cmd_inhibit_idle(int argc, char **argv) {
+	struct cmd_results *error = NULL;
+	if ((error = checkarg(argc, "inhibit_idle", 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 idle inhibitors");
+	}
+
+	bool clear = false;
+	enum sway_idle_inhibit_mode mode;
+	if (strcmp(argv[0], "focus") == 0) {
+		mode = INHIBIT_IDLE_FOCUS;
+	} else if (strcmp(argv[0], "fullscreen") == 0) {
+		mode = INHIBIT_IDLE_FULLSCREEN;
+	} else if (strcmp(argv[0], "open") == 0) {
+		mode = INHIBIT_IDLE_OPEN;
+	} else if (strcmp(argv[0], "none") == 0) {
+		clear = true;
+	} else if (strcmp(argv[0], "visible") == 0) {
+		mode = INHIBIT_IDLE_VISIBLE;
+	} else {
+		return cmd_results_new(CMD_INVALID,
+				"Expected `inhibit_idle focus|fullscreen|open|none|visible`");
+	}
+
+	struct sway_idle_inhibitor_v1 *inhibitor =
+		sway_idle_inhibit_v1_user_inhibitor_for_view(con->view);
+	if (inhibitor) {
+		if (clear) {
+			sway_idle_inhibit_v1_user_inhibitor_destroy(inhibitor);
+		} else {
+			inhibitor->mode = mode;
+			sway_idle_inhibit_v1_check_active(server.idle_inhibit_manager_v1);
+		}
+	} else if (!clear) {
+		sway_idle_inhibit_v1_user_inhibitor_register(con->view, mode);
+	}
+
+	return cmd_results_new(CMD_SUCCESS, NULL);
+}
diff --git a/sway/desktop/idle_inhibit_v1.c b/sway/desktop/idle_inhibit_v1.c
index 87b4ef43d..b981e5e5a 100644
--- a/sway/desktop/idle_inhibit_v1.c
+++ b/sway/desktop/idle_inhibit_v1.c
@@ -2,18 +2,24 @@
 #include <wlr/types/wlr_idle.h>
 #include "log.h"
 #include "sway/desktop/idle_inhibit_v1.h"
+#include "sway/input/seat.h"
+#include "sway/tree/container.h"
 #include "sway/tree/view.h"
 #include "sway/server.h"
 
 
+static void destroy_inhibitor(struct sway_idle_inhibitor_v1 *inhibitor) {
+	wl_list_remove(&inhibitor->link);
+	wl_list_remove(&inhibitor->destroy.link);
+	sway_idle_inhibit_v1_check_active(inhibitor->manager);
+	free(inhibitor);
+}
+
 static void handle_destroy(struct wl_listener *listener, void *data) {
 	struct sway_idle_inhibitor_v1 *inhibitor =
 		wl_container_of(listener, inhibitor, destroy);
 	sway_log(SWAY_DEBUG, "Sway idle inhibitor destroyed");
-	wl_list_remove(&inhibitor->link);
-	wl_list_remove(&inhibitor->destroy.link);
-	idle_inhibit_v1_check_active(inhibitor->manager);
-	free(inhibitor);
+	destroy_inhibitor(inhibitor);
 }
 
 void handle_idle_inhibitor_v1(struct wl_listener *listener, void *data) {
@@ -29,28 +35,93 @@ void handle_idle_inhibitor_v1(struct wl_listener *listener, void *data) {
 	}
 
 	inhibitor->manager = manager;
+	inhibitor->mode = INHIBIT_IDLE_APPLICATION;
 	inhibitor->view = view_from_wlr_surface(wlr_inhibitor->surface);
 	wl_list_insert(&manager->inhibitors, &inhibitor->link);
 
-
 	inhibitor->destroy.notify = handle_destroy;
 	wl_signal_add(&wlr_inhibitor->events.destroy, &inhibitor->destroy);
 
-	idle_inhibit_v1_check_active(manager);
+	sway_idle_inhibit_v1_check_active(manager);
 }
 
-void idle_inhibit_v1_check_active(
+void sway_idle_inhibit_v1_user_inhibitor_register(struct sway_view *view,
+		enum sway_idle_inhibit_mode mode) {
+	struct sway_idle_inhibitor_v1 *inhibitor =
+		calloc(1, sizeof(struct sway_idle_inhibitor_v1));
+	if (!inhibitor) {
+		return;
+	}
+
+	inhibitor->manager = server.idle_inhibit_manager_v1;
+	inhibitor->mode = mode;
+	inhibitor->view = view;
+	wl_list_insert(&inhibitor->manager->inhibitors, &inhibitor->link);
+
+	inhibitor->destroy.notify = handle_destroy;
+	wl_signal_add(&view->events.unmap, &inhibitor->destroy);
+
+	sway_idle_inhibit_v1_check_active(inhibitor->manager);
+}
+
+struct sway_idle_inhibitor_v1 *sway_idle_inhibit_v1_user_inhibitor_for_view(
+		struct sway_view *view) {
+	struct sway_idle_inhibitor_v1 *inhibitor;
+	wl_list_for_each(inhibitor, &server.idle_inhibit_manager_v1->inhibitors,
+			link) {
+		if (inhibitor->view == view &&
+				inhibitor->mode != INHIBIT_IDLE_APPLICATION) {
+			return inhibitor;
+		}
+	}
+	return NULL;
+}
+
+void sway_idle_inhibit_v1_user_inhibitor_destroy(
+		struct sway_idle_inhibitor_v1 *inhibitor) {
+	if (!inhibitor) {
+		return;
+	}
+	if (!sway_assert(inhibitor->mode != INHIBIT_IDLE_APPLICATION,
+				"User should not be able to destroy application inhibitor")) {
+		return;
+	}
+	destroy_inhibitor(inhibitor);
+}
+
+static bool check_active(struct sway_idle_inhibitor_v1 *inhibitor) {
+	switch (inhibitor->mode) {
+	case INHIBIT_IDLE_APPLICATION:
+		// If there is no view associated with the inhibitor, assume visible
+		return !inhibitor->view || view_is_visible(inhibitor->view);
+	case INHIBIT_IDLE_FOCUS:;
+		struct sway_seat *seat = NULL;
+		wl_list_for_each(seat, &server.input->seats, link) {
+			struct sway_container *con = seat_get_focused_container(seat);
+			if (con && con->view && con->view == inhibitor->view) {
+				return true;
+			}
+		}
+		return false;
+	case INHIBIT_IDLE_FULLSCREEN:
+		return inhibitor->view->container &&
+			container_is_fullscreen_or_child(inhibitor->view->container) &&
+			view_is_visible(inhibitor->view);
+	case INHIBIT_IDLE_OPEN:
+		// Inhibitor is destroyed on unmap so it must be open/mapped
+		return true;
+	case INHIBIT_IDLE_VISIBLE:
+		return view_is_visible(inhibitor->view);
+	}
+	return false;
+}
+
+void sway_idle_inhibit_v1_check_active(
 		struct sway_idle_inhibit_manager_v1 *manager) {
 	struct sway_idle_inhibitor_v1 *inhibitor;
 	bool inhibited = false;
 	wl_list_for_each(inhibitor, &manager->inhibitors, link) {
-		if (!inhibitor->view || !inhibitor->view->container) {
-			/* Cannot guess if view is visible so assume it is */
-			inhibited = true;
-			break;
-		}
-		if (view_is_visible(inhibitor->view)) {
-			inhibited = true;
+		if ((inhibited = check_active(inhibitor))) {
 			break;
 		}
 	}
diff --git a/sway/desktop/transaction.c b/sway/desktop/transaction.c
index e5f0ee3d1..51c6e7fca 100644
--- a/sway/desktop/transaction.c
+++ b/sway/desktop/transaction.c
@@ -349,7 +349,7 @@ static void transaction_progress_queue(void) {
 	list_del(server.transactions, 0);
 
 	if (!server.transactions->length) {
-		idle_inhibit_v1_check_active(server.idle_inhibit_manager_v1);
+		sway_idle_inhibit_v1_check_active(server.idle_inhibit_manager_v1);
 		return;
 	}
 
diff --git a/sway/meson.build b/sway/meson.build
index 9f79fb6ce..12b86efb8 100644
--- a/sway/meson.build
+++ b/sway/meson.build
@@ -63,6 +63,7 @@ sway_sources = files(
 	'commands/fullscreen.c',
 	'commands/gaps.c',
 	'commands/hide_edge_borders.c',
+	'commands/inhibit_idle.c',
 	'commands/kill.c',
 	'commands/mark.c',
 	'commands/opacity.c',
diff --git a/sway/sway.5.scd b/sway/sway.5.scd
index dbfeefe3c..1650cd60c 100644
--- a/sway/sway.5.scd
+++ b/sway/sway.5.scd
@@ -146,6 +146,18 @@ set|plus|minus <amount>
 	_right_, _bottom_, and _left_ or per direction with _horizontal_ and
 	_vertical_.
 
+*inhibit_idle* focus|fullscreen|open|none|visible
+	Set/unset an idle inhibitor for the view. _focus_ will inhibit idle when
+	the view is focused by any seat. _fullscreen_ will inhibit idle when the
+	view is fullscreen (or a descendant of a fullscreen container) and is
+	visible. _open_ will inhibit idle until the view is closed (or the
+	inhibitor is unset/changed). _visible_ will inhibit idle when the view is
+	visible on any output. _none_ will remove any existing idle inhibitor for
+	the view.
+
+	This can also be used with criteria to set an idle inhibitor for any
+	existing view or with _for_window_ to set idle inhibitors for future views.
+
 *layout* default|splith|splitv|stacking|tabbed
 	Sets the layout mode of the focused container.