diff --git a/src/meson.build b/src/meson.build
index 10fb80dc5..d8b532bda 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -517,6 +517,8 @@ if have_wayland
'wayland/meta-wayland-gtk-shell.c',
'wayland/meta-wayland-gtk-shell.h',
'wayland/meta-wayland.h',
+ 'wayland/meta-wayland-idle-inhibit.c',
+ 'wayland/meta-wayland-idle-inhibit.h',
'wayland/meta-wayland-inhibit-shortcuts.c',
'wayland/meta-wayland-inhibit-shortcuts-dialog.c',
'wayland/meta-wayland-inhibit-shortcuts-dialog.h',
@@ -799,6 +801,7 @@ if have_wayland
# - protocol version (if stability is 'unstable')
wayland_protocols = [
['gtk-shell', 'private', ],
+ ['idle-inhibit', 'unstable', 'v1', ],
['keyboard-shortcuts-inhibit', 'unstable', 'v1', ],
['linux-dmabuf', 'unstable', 'v1', ],
['pointer-constraints', 'unstable', 'v1', ],
diff --git a/src/wayland/meta-wayland-idle-inhibit.c b/src/wayland/meta-wayland-idle-inhibit.c
new file mode 100644
index 000000000..7811fffa9
--- /dev/null
+++ b/src/wayland/meta-wayland-idle-inhibit.c
@@ -0,0 +1,400 @@
+/*
+ * Copyright (C) 2021 SUSE Software Solutions Germany GmbH
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ */
+
+#include "config.h"
+
+#include "wayland/meta-wayland-idle-inhibit.h"
+
+#include
+
+#include "backends/meta-backend-private.h"
+#include "backends/meta-logical-monitor.h"
+#include "backends/meta-settings-private.h"
+#include "wayland/meta-wayland-private.h"
+#include "wayland/meta-wayland-versions.h"
+
+#include "idle-inhibit-unstable-v1-server-protocol.h"
+
+typedef enum _IdleState
+{
+ IDLE_STATE_INITIALIZING,
+ IDLE_STATE_UNINHIBITED,
+ IDLE_STATE_INHIBITING,
+ IDLE_STATE_INHIBITED,
+ IDLE_STATE_UNINHIBITING,
+} IdleState;
+
+struct _MetaWaylandIdleInhibitor
+{
+ GDBusProxy *session_proxy;
+ struct wl_resource *resource;
+
+ MetaSurfaceActor *actor;
+ gulong is_obscured_changed_handler;
+ gulong actor_destroyed_handler_id;
+
+ MetaWaylandSurface *surface;
+ gulong surface_destroy_handler_id;
+ gulong actor_changed_handler_id;
+
+ uint32_t cookie;
+ IdleState state;
+};
+
+typedef struct _MetaWaylandIdleInhibitor MetaWaylandIdleInhibitor;
+
+static void update_inhibition (MetaWaylandIdleInhibitor *inhibitor);
+
+static void
+meta_wayland_inhibitor_free (MetaWaylandIdleInhibitor *inhibitor)
+{
+ g_clear_signal_handler (&inhibitor->is_obscured_changed_handler,
+ inhibitor->actor);
+ g_clear_signal_handler (&inhibitor->actor_destroyed_handler_id,
+ inhibitor->actor);
+ g_clear_signal_handler (&inhibitor->actor_changed_handler_id,
+ inhibitor->surface);
+ g_clear_signal_handler (&inhibitor->surface_destroy_handler_id,
+ inhibitor->surface);
+
+ g_free (inhibitor);
+}
+
+static void
+inhibit_completed (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ MetaWaylandIdleInhibitor *inhibitor = user_data;
+ g_autoptr (GVariant) ret = NULL;
+ g_autoptr (GError) error = NULL;
+
+ ret = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error);
+ if (!ret)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Failed to inhibit: %s", error->message);
+ return;
+ }
+
+ g_warn_if_fail (inhibitor->state == IDLE_STATE_INHIBITING);
+
+ g_variant_get (ret, "(u)", &inhibitor->cookie);
+ inhibitor->state = IDLE_STATE_INHIBITED;
+
+ update_inhibition (inhibitor);
+}
+
+static void
+uninhibit_completed (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ MetaWaylandIdleInhibitor *inhibitor = user_data;
+ g_autoptr (GVariant) ret = NULL;
+ g_autoptr (GError) error = NULL;
+
+ ret = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error);
+ if (!ret)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Failed to uninhibit: %s", error->message);
+ return;
+ }
+
+ if (!inhibitor)
+ return;
+
+ g_warn_if_fail (inhibitor->state == IDLE_STATE_UNINHIBITING);
+ inhibitor->state = IDLE_STATE_UNINHIBITED;
+
+ update_inhibition (inhibitor);
+}
+
+static void
+update_inhibition (MetaWaylandIdleInhibitor *inhibitor)
+{
+ gboolean should_inhibit;
+
+ if (!inhibitor->session_proxy)
+ return;
+
+ if (!inhibitor->surface ||
+ !inhibitor->resource ||
+ !inhibitor->actor)
+ {
+ should_inhibit = FALSE;
+ }
+ else
+ {
+ if (meta_surface_actor_is_obscured (inhibitor->actor))
+ should_inhibit = FALSE;
+ else
+ should_inhibit = TRUE;
+ }
+
+ switch (inhibitor->state)
+ {
+ case IDLE_STATE_INITIALIZING:
+ case IDLE_STATE_UNINHIBITED:
+ if (!inhibitor->resource)
+ {
+ meta_wayland_inhibitor_free (inhibitor);
+ return;
+ }
+
+ if (!should_inhibit)
+ return;
+
+ break;
+ case IDLE_STATE_INHIBITED:
+ if (should_inhibit)
+ return;
+ break;
+ case IDLE_STATE_INHIBITING:
+ case IDLE_STATE_UNINHIBITING:
+ /* Update inhibition after current asynchronous call completes. */
+ return;
+ }
+
+ if (should_inhibit)
+ {
+ g_dbus_proxy_call (G_DBUS_PROXY (inhibitor->session_proxy),
+ "Inhibit",
+ g_variant_new ("(ss)", "muffin", "idle-inhibit"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ inhibit_completed,
+ inhibitor);
+ inhibitor->state = IDLE_STATE_INHIBITING;
+ }
+ else
+ {
+ g_dbus_proxy_call (G_DBUS_PROXY (inhibitor->session_proxy),
+ "UnInhibit",
+ g_variant_new ("(u)", inhibitor->cookie),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ uninhibit_completed,
+ inhibitor);
+ inhibitor->state = IDLE_STATE_UNINHIBITING;
+ }
+}
+
+static void
+is_obscured_changed (MetaSurfaceActor *actor,
+ GParamSpec *pspec,
+ MetaWaylandIdleInhibitor *inhibitor)
+{
+ update_inhibition (inhibitor);
+}
+
+static void
+inhibitor_proxy_completed (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ MetaWaylandIdleInhibitor *inhibitor = user_data;
+ GDBusProxy *proxy;
+ g_autoptr (GError) error = NULL;
+
+ proxy = g_dbus_proxy_new_finish (res, &error);
+ if (!proxy)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_warning ("Failed to obtain org.freedesktop.ScreenSaver proxy: %s",
+ error->message);
+ }
+ return;
+ }
+
+ inhibitor->session_proxy = proxy;
+ inhibitor->state = IDLE_STATE_UNINHIBITED;
+
+ update_inhibition (inhibitor);
+}
+
+static void
+idle_inhibit_destroy (struct wl_client *client,
+ struct wl_resource *resource)
+{
+ wl_resource_destroy (resource);
+}
+
+static void
+idle_inhibitor_destructor (struct wl_resource *resource)
+{
+ MetaWaylandIdleInhibitor *inhibitor = wl_resource_get_user_data (resource);
+
+ switch (inhibitor->state)
+ {
+ case IDLE_STATE_UNINHIBITED:
+ meta_wayland_inhibitor_free (inhibitor);
+ return;
+ case IDLE_STATE_INITIALIZING:
+ case IDLE_STATE_INHIBITED:
+ case IDLE_STATE_INHIBITING:
+ case IDLE_STATE_UNINHIBITING:
+ inhibitor->resource = NULL;
+ break;
+ }
+
+ update_inhibition (inhibitor);
+}
+
+static void
+on_surface_destroyed (MetaWaylandSurface *surface,
+ MetaWaylandIdleInhibitor *inhibitor)
+{
+ g_clear_signal_handler (&inhibitor->is_obscured_changed_handler,
+ inhibitor->actor);
+ g_clear_signal_handler (&inhibitor->actor_destroyed_handler_id,
+ inhibitor->actor);
+ inhibitor->actor = NULL;
+ g_clear_signal_handler (&inhibitor->actor_changed_handler_id,
+ inhibitor->surface);
+ g_clear_signal_handler (&inhibitor->surface_destroy_handler_id,
+ inhibitor->surface);
+ inhibitor->surface = NULL;
+}
+
+static void
+on_actor_destroyed (MetaSurfaceActor *actor,
+ MetaWaylandIdleInhibitor *inhibitor)
+{
+ g_warn_if_fail (actor == inhibitor->actor);
+
+ g_clear_signal_handler (&inhibitor->is_obscured_changed_handler, actor);
+ g_clear_signal_handler (&inhibitor->actor_destroyed_handler_id, actor);
+ inhibitor->actor = NULL;
+}
+
+static void
+attach_actor (MetaWaylandIdleInhibitor *inhibitor)
+{
+ inhibitor->actor = meta_wayland_surface_get_actor (inhibitor->surface);
+
+ if (inhibitor->actor)
+ {
+ inhibitor->is_obscured_changed_handler =
+ g_signal_connect (inhibitor->actor, "notify::is-obscured",
+ G_CALLBACK (is_obscured_changed), inhibitor);
+ inhibitor->actor_destroyed_handler_id =
+ g_signal_connect (inhibitor->actor, "destroy",
+ G_CALLBACK (on_actor_destroyed), inhibitor);
+ }
+}
+
+static void
+on_actor_changed (MetaWaylandSurface *surface,
+ MetaWaylandIdleInhibitor *inhibitor)
+{
+ g_clear_signal_handler (&inhibitor->is_obscured_changed_handler,
+ inhibitor->actor);
+ g_clear_signal_handler (&inhibitor->actor_destroyed_handler_id,
+ inhibitor->actor);
+ attach_actor (inhibitor);
+}
+
+static const struct zwp_idle_inhibitor_v1_interface meta_wayland_idle_inhibitor_interface =
+{
+ idle_inhibit_destroy,
+};
+
+static void
+idle_inhibit_manager_create_inhibitor (struct wl_client *client,
+ struct wl_resource *resource,
+ uint32_t id,
+ struct wl_resource *surface_resource)
+{
+ MetaWaylandSurface *surface = wl_resource_get_user_data (surface_resource);
+ MetaWaylandIdleInhibitor *inhibitor;
+ struct wl_resource *inhibitor_resource;
+
+ inhibitor_resource = wl_resource_create (client,
+ &zwp_idle_inhibitor_v1_interface,
+ wl_resource_get_version (resource),
+ id);
+
+ inhibitor = g_new0 (MetaWaylandIdleInhibitor, 1);
+ inhibitor->surface = surface;
+ inhibitor->resource = inhibitor_resource;
+
+ attach_actor (inhibitor);
+
+ inhibitor->actor_changed_handler_id =
+ g_signal_connect (surface, "actor-changed",
+ G_CALLBACK (on_actor_changed), inhibitor);
+ inhibitor->surface_destroy_handler_id =
+ g_signal_connect (surface, "destroy",
+ G_CALLBACK (on_surface_destroyed), inhibitor);
+
+ g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ "org.freedesktop.ScreenSaver",
+ "/org/freedesktop/ScreenSaver",
+ "org.freedesktop.ScreenSaver",
+ NULL,
+ inhibitor_proxy_completed,
+ inhibitor);
+
+ wl_resource_set_implementation (inhibitor_resource,
+ &meta_wayland_idle_inhibitor_interface,
+ inhibitor,
+ idle_inhibitor_destructor);
+}
+
+
+static const struct zwp_idle_inhibit_manager_v1_interface meta_wayland_idle_inhibit_manager_interface =
+{
+ idle_inhibit_destroy,
+ idle_inhibit_manager_create_inhibitor,
+};
+
+static void
+bind_idle_inhibit (struct wl_client *client,
+ void *data,
+ uint32_t version,
+ uint32_t id)
+{
+ struct wl_resource *resource;
+
+ resource = wl_resource_create (client,
+ &zwp_idle_inhibit_manager_v1_interface,
+ version, id);
+
+ wl_resource_set_implementation (resource,
+ &meta_wayland_idle_inhibit_manager_interface,
+ NULL, NULL);
+}
+
+gboolean
+meta_wayland_idle_inhibit_init (MetaWaylandCompositor *compositor)
+{
+ if (wl_global_create (compositor->wayland_display,
+ &zwp_idle_inhibit_manager_v1_interface,
+ META_ZWP_IDLE_INHIBIT_V1_VERSION,
+ NULL,
+ bind_idle_inhibit) == NULL)
+ return FALSE;
+ return TRUE;
+}
diff --git a/src/wayland/meta-wayland-idle-inhibit.h b/src/wayland/meta-wayland-idle-inhibit.h
new file mode 100644
index 000000000..21773c9fb
--- /dev/null
+++ b/src/wayland/meta-wayland-idle-inhibit.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2021 SUSE Software Solutions Germany GmbH
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ */
+
+#pragma once
+
+#include
+
+#include "wayland/meta-wayland-types.h"
+
+gboolean meta_wayland_idle_inhibit_init (MetaWaylandCompositor *compositor);
diff --git a/src/wayland/meta-wayland-versions.h b/src/wayland/meta-wayland-versions.h
index 6d2dbd7bc..150190c60 100644
--- a/src/wayland/meta-wayland-versions.h
+++ b/src/wayland/meta-wayland-versions.h
@@ -44,6 +44,7 @@
#define META_GTK_SHELL1_VERSION 6
#define META_WL_SUBCOMPOSITOR_VERSION 1
#define META_ZWP_POINTER_GESTURES_V1_VERSION 3
+#define META_ZWP_IDLE_INHIBIT_V1_VERSION 1
#define META_ZXDG_EXPORTER_V1_VERSION 1
#define META_ZXDG_IMPORTER_V1_VERSION 1
#define META_ZXDG_EXPORTER_V2_VERSION 1
diff --git a/src/wayland/meta-wayland.c b/src/wayland/meta-wayland.c
index aeb39ec5d..7b21ddefa 100644
--- a/src/wayland/meta-wayland.c
+++ b/src/wayland/meta-wayland.c
@@ -35,6 +35,7 @@
#include "wayland/meta-wayland-data-device.h"
#include "wayland/meta-wayland-dma-buf.h"
#include "wayland/meta-wayland-egl-stream.h"
+#include "wayland/meta-wayland-idle-inhibit.h"
#include "wayland/meta-wayland-inhibit-shortcuts-dialog.h"
#include "wayland/meta-wayland-inhibit-shortcuts.h"
#include "wayland/meta-wayland-legacy-xdg-foreign.h"
@@ -441,6 +442,7 @@ meta_wayland_compositor_setup (MetaWaylandCompositor *wayland_compositor)
meta_wayland_surface_inhibit_shortcuts_dialog_init ();
meta_wayland_text_input_init (compositor);
meta_wayland_activation_init (compositor);
+ meta_wayland_idle_inhibit_init (compositor);
meta_wayland_init_xdg_wm_dialog (compositor);
/* Xwayland specific protocol, needs to be filtered out for all other clients */