diff --git a/src/backends/meta-idle-monitor-private.h b/src/backends/meta-idle-monitor-private.h
index d498f13f9..497d0486d 100644
--- a/src/backends/meta-idle-monitor-private.h
+++ b/src/backends/meta-idle-monitor-private.h
@@ -36,6 +36,7 @@ typedef struct
guint64 timeout_msec;
int idle_source_id;
GSource *timeout_source;
+ gboolean ignore_inhibitors;
} MetaIdleMonitorWatch;
struct _MetaIdleMonitor
@@ -56,4 +57,11 @@ struct _MetaIdleMonitorClass
void meta_idle_monitor_reset_idletime (MetaIdleMonitor *monitor);
+guint meta_idle_monitor_add_idle_watch_full (MetaIdleMonitor *monitor,
+ guint64 interval_msec,
+ gboolean ignore_inhibitors,
+ MetaIdleMonitorWatchFunc callback,
+ gpointer user_data,
+ GDestroyNotify notify);
+
#endif /* META_IDLE_MONITOR_PRIVATE_H */
diff --git a/src/backends/meta-idle-monitor.c b/src/backends/meta-idle-monitor.c
index f2c6c75db..82e9d113f 100644
--- a/src/backends/meta-idle-monitor.c
+++ b/src/backends/meta-idle-monitor.c
@@ -181,7 +181,7 @@ update_inhibited_watch (gpointer key,
if (!watch->timeout_source)
return;
- if (monitor->inhibited)
+ if (monitor->inhibited && !watch->ignore_inhibitors)
{
g_source_set_ready_time (watch->timeout_source, -1);
}
@@ -337,6 +337,7 @@ make_watch (MetaIdleMonitor *monitor,
watch->user_data = user_data;
watch->notify = notify;
watch->timeout_msec = timeout_msec;
+ watch->ignore_inhibitors = FALSE;
if (timeout_msec != 0)
{
@@ -498,7 +499,7 @@ meta_idle_monitor_reset_idletime (MetaIdleMonitor *monitor)
}
else
{
- if (monitor->inhibited)
+ if (monitor->inhibited && !watch->ignore_inhibitors)
{
g_source_set_ready_time (watch->timeout_source, -1);
}
@@ -513,3 +514,33 @@ meta_idle_monitor_reset_idletime (MetaIdleMonitor *monitor)
g_list_free (watch_ids);
}
+
+guint
+meta_idle_monitor_add_idle_watch_full (MetaIdleMonitor *monitor,
+ guint64 interval_msec,
+ gboolean ignore_inhibitors,
+ MetaIdleMonitorWatchFunc callback,
+ gpointer user_data,
+ GDestroyNotify notify)
+{
+ MetaIdleMonitorWatch *watch;
+
+ g_return_val_if_fail (META_IS_IDLE_MONITOR (monitor), 0);
+ g_return_val_if_fail (interval_msec > 0, 0);
+
+ watch = make_watch (monitor,
+ interval_msec,
+ callback,
+ user_data,
+ notify);
+
+ watch->ignore_inhibitors = ignore_inhibitors;
+
+ if (monitor->inhibited && ignore_inhibitors && watch->timeout_source)
+ {
+ g_source_set_ready_time (watch->timeout_source,
+ monitor->last_event_time +
+ watch->timeout_msec * 1000);
+ }
+ return watch->id;
+}
diff --git a/src/meson.build b/src/meson.build
index 796282e86..fa15dc4ba 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -524,6 +524,8 @@ if have_wayland
'wayland/meta-wayland-layer-shell.c',
'wayland/meta-wayland-layer-shell.h',
'wayland/meta-wayland.h',
+ 'wayland/meta-wayland-idle-notify.h',
+ 'wayland/meta-wayland-idle-notify.c',
'wayland/meta-wayland-idle-inhibit.c',
'wayland/meta-wayland-idle-inhibit.h',
'wayland/meta-wayland-inhibit-shortcuts.c',
@@ -858,6 +860,7 @@ if have_wayland
['xapp-shell', 'private', ],
['xwayland-keyboard-grab', 'unstable', 'v1', ],
['wayland-drm', 'private', ],
+ ['ext-idle-notify', 'staging', 'v1', ],
]
if have_wayland_eglstream
wayland_eglstream_protocols_dir = wayland_eglstream_protocols_dep.get_variable(pkgconfig: 'pkgdatadir')
diff --git a/src/wayland/meta-wayland-idle-notify.c b/src/wayland/meta-wayland-idle-notify.c
new file mode 100644
index 000000000..bd047ff2a
--- /dev/null
+++ b/src/wayland/meta-wayland-idle-notify.c
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2026 Linux Mint
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see .
+ */
+
+#include "config.h"
+
+#include "wayland/meta-wayland-idle-notify.h"
+
+#include "backends/meta-idle-monitor-private.h"
+#include "wayland/meta-wayland-private.h"
+#include "wayland/meta-wayland-versions.h"
+
+#include "ext-idle-notify-v1-server-protocol.h"
+
+typedef struct _MetaWaylandIdleNotification
+{
+ struct wl_resource *resource;
+
+ MetaIdleMonitor *monitor;
+
+ guint idle_watch_id;
+ guint active_watch_id;
+
+ guint timeout;
+
+ gboolean ignore_inhibitors;
+} MetaWaylandIdleNotification;
+
+static void idle_trigger_cb (MetaIdleMonitor *monitor,
+ guint id,
+ gpointer user_data);
+
+static void
+active_trigger_cb (MetaIdleMonitor *monitor,
+ guint id,
+ gpointer user_data)
+{
+ MetaWaylandIdleNotification *notification = user_data;
+
+ notification->active_watch_id = 0;
+
+ ext_idle_notification_v1_send_resumed (notification->resource);
+
+ notification->idle_watch_id =
+ meta_idle_monitor_add_idle_watch_full (notification->monitor,
+ notification->timeout,
+ notification->ignore_inhibitors,
+ idle_trigger_cb,
+ notification,
+ NULL);
+}
+
+static void
+idle_trigger_cb (MetaIdleMonitor *monitor,
+ guint id,
+ gpointer user_data)
+{
+ MetaWaylandIdleNotification *notification = user_data;
+
+ meta_idle_monitor_remove_watch (notification->monitor, id);
+ notification->idle_watch_id = 0;
+
+ ext_idle_notification_v1_send_idled (notification->resource);
+
+ notification->active_watch_id =
+ meta_idle_monitor_add_user_active_watch (notification->monitor,
+ active_trigger_cb,
+ notification,
+ NULL);
+}
+
+static void
+ext_idle_notification_destructor (struct wl_resource *resource)
+{
+ MetaWaylandIdleNotification *notification = wl_resource_get_user_data (resource);
+
+ if (notification->idle_watch_id)
+ meta_idle_monitor_remove_watch (notification->monitor,
+ notification->idle_watch_id);
+
+ if (notification->active_watch_id)
+ meta_idle_monitor_remove_watch (notification->monitor,
+ notification->active_watch_id);
+
+ g_free (notification);
+}
+
+static void
+ext_idle_notification_destroy (struct wl_client *client,
+ struct wl_resource *resource)
+{
+ wl_resource_destroy (resource);
+}
+
+static const struct ext_idle_notification_v1_interface ext_idle_notification_v1_impl = {
+ ext_idle_notification_destroy,
+};
+
+static void
+ext_idle_notification_create (struct wl_client *client,
+ struct wl_resource *manager_resource,
+ uint32_t id,
+ uint32_t timeout,
+ struct wl_resource *seat_resource,
+ gboolean ignore_inhibitors)
+{
+ MetaWaylandSeat *seat = wl_resource_get_user_data (seat_resource);
+ MetaWaylandIdleNotification *notification;
+ uint32_t version;
+ ClutterInputDevice *pointer_device = NULL;
+
+ notification = g_new0 (MetaWaylandIdleNotification, 1);
+ notification->timeout = timeout;
+ notification->ignore_inhibitors = ignore_inhibitors;
+
+ if (seat && seat->pointer)
+ pointer_device = seat->pointer->device;
+
+ if (pointer_device)
+ {
+ MetaBackend *backend = meta_get_backend ();
+ notification->monitor = meta_backend_get_idle_monitor (backend, pointer_device);
+ }
+ else
+ {
+ notification->monitor = meta_idle_monitor_get_core ();
+ }
+
+ version = wl_resource_get_version (manager_resource);
+ notification->resource = wl_resource_create (client,
+ &ext_idle_notification_v1_interface,
+ version,
+ id);
+
+ wl_resource_set_implementation (notification->resource,
+ &ext_idle_notification_v1_impl,
+ notification,
+ ext_idle_notification_destructor);
+
+ notification->idle_watch_id =
+ meta_idle_monitor_add_idle_watch_full (notification->monitor,
+ notification->timeout,
+ notification->ignore_inhibitors,
+ idle_trigger_cb,
+ notification,
+ NULL);
+}
+
+static void
+ext_idle_notifier_get_idle_notification (struct wl_client *client,
+ struct wl_resource *resource,
+ uint32_t id,
+ uint32_t timeout,
+ struct wl_resource *seat)
+{
+ ext_idle_notification_create (client, resource,
+ id, timeout,
+ seat, FALSE);
+}
+
+static void
+ext_idle_notifier_get_input_idle_notification (struct wl_client *client,
+ struct wl_resource *resource,
+ uint32_t id,
+ uint32_t timeout,
+ struct wl_resource *seat)
+{
+ ext_idle_notification_create (client, resource,
+ id, timeout,
+ seat, TRUE);
+}
+
+static void
+ext_idle_notifier_destroy (struct wl_client *client,
+ struct wl_resource *resource)
+{
+ wl_resource_destroy (resource);
+}
+
+static const struct ext_idle_notifier_v1_interface ext_idle_notifier_v1_impl = {
+ ext_idle_notifier_destroy,
+ ext_idle_notifier_get_idle_notification,
+ ext_idle_notifier_get_input_idle_notification,
+};
+
+static void
+bind_ext_idle_notifier (struct wl_client *client,
+ void *data,
+ uint32_t version,
+ uint32_t id)
+{
+ struct wl_resource *resource;
+
+ resource = wl_resource_create (client,
+ &ext_idle_notifier_v1_interface,
+ version,
+ id);
+ wl_resource_set_implementation (resource,
+ &ext_idle_notifier_v1_impl,
+ NULL,
+ NULL);
+}
+
+void
+meta_wayland_idle_notify_init (MetaWaylandCompositor *compositor)
+{
+ if (wl_global_create (compositor->wayland_display,
+ &ext_idle_notifier_v1_interface,
+ META_EXT_IDLE_NOTIFY_V1_VERSION,
+ NULL,
+ bind_ext_idle_notifier) == NULL)
+ g_error ("Failed to register a global ext-idle-notify object");
+}
diff --git a/src/wayland/meta-wayland-idle-notify.h b/src/wayland/meta-wayland-idle-notify.h
new file mode 100644
index 000000000..4b9f3d04c
--- /dev/null
+++ b/src/wayland/meta-wayland-idle-notify.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2026 Linux Mint
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see .
+ */
+
+#ifndef META_WAYLAND_EXT_IDLE_NOTIFY_H
+#define META_WAYLAND_EXT_IDLE_NOTIFY_H
+
+#include "wayland/meta-wayland-types.h"
+
+void meta_wayland_idle_notify_init (MetaWaylandCompositor *compositor);
+
+#endif
diff --git a/src/wayland/meta-wayland-versions.h b/src/wayland/meta-wayland-versions.h
index b4b3ef2f7..6283bbac0 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_EXT_IDLE_NOTIFY_V1_VERSION 1
#define META_ZWP_IDLE_INHIBIT_V1_VERSION 1
#define META_ZXDG_EXPORTER_V1_VERSION 1
#define META_ZXDG_IMPORTER_V1_VERSION 1
diff --git a/src/wayland/meta-wayland.c b/src/wayland/meta-wayland.c
index f0eba38ed..7bd756c74 100644
--- a/src/wayland/meta-wayland.c
+++ b/src/wayland/meta-wayland.c
@@ -39,6 +39,7 @@
#include "wayland/meta-wayland-drm.h"
#include "wayland/meta-wayland-egl-stream.h"
#include "wayland/meta-wayland-idle-inhibit.h"
+#include "wayland/meta-wayland-idle-notify.h"
#include "wayland/meta-wayland-inhibit-shortcuts-dialog.h"
#include "wayland/meta-wayland-inhibit-shortcuts.h"
#include "wayland/meta-wayland-legacy-xdg-foreign.h"
@@ -478,6 +479,7 @@ meta_wayland_compositor_setup (MetaWaylandCompositor *wayland_compositor)
meta_wayland_input_method_manager_init (compositor);
meta_wayland_virtual_keyboard_manager_init (compositor);
meta_wayland_activation_init (compositor);
+ meta_wayland_idle_notify_init (compositor);
meta_wayland_idle_inhibit_init (compositor);
meta_wayland_init_xdg_wm_dialog (compositor);
meta_wayland_xdg_toplevel_tag_init (compositor);