diff --git a/src/wayland/CMakeLists.txt b/src/wayland/CMakeLists.txt
index 1d6543e..ea2a5d8 100644
--- a/src/wayland/CMakeLists.txt
+++ b/src/wayland/CMakeLists.txt
@@ -114,6 +114,9 @@ if (HYPRLAND)
add_subdirectory(hyprland)
endif()
+add_subdirectory(idle_inhibit)
+list(APPEND WAYLAND_MODULES Quickshell.Wayland._IdleInhibitor)
+
# widgets for qmenu
target_link_libraries(quickshell-wayland PRIVATE
Qt::Quick Qt::Widgets Qt::WaylandClient Qt::WaylandClientPrivate
diff --git a/src/wayland/idle_inhibit/CMakeLists.txt b/src/wayland/idle_inhibit/CMakeLists.txt
new file mode 100644
index 0000000..040e10f
--- /dev/null
+++ b/src/wayland/idle_inhibit/CMakeLists.txt
@@ -0,0 +1,25 @@
+qt_add_library(quickshell-wayland-idle-inhibit STATIC
+ proto.cpp
+ inhibitor.cpp
+)
+
+qt_add_qml_module(quickshell-wayland-idle-inhibit
+ URI Quickshell.Wayland._IdleInhibitor
+ VERSION 0.1
+ DEPENDENCIES QtQuick
+)
+
+install_qml_module(quickshell-wayland-idle-inhibit)
+
+qs_add_module_deps_light(quickshell-wayland-idle-inhibit Quickshell)
+
+wl_proto(wlp-idle-inhibit idle-inhibit-unstable-v1 "${CMAKE_CURRENT_SOURCE_DIR}")
+
+target_link_libraries(quickshell-wayland-idle-inhibit PRIVATE
+ Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client
+ wlp-idle-inhibit
+)
+
+qs_module_pch(quickshell-wayland-idle-inhibit SET large)
+
+target_link_libraries(quickshell PRIVATE quickshell-wayland-idle-inhibitplugin)
diff --git a/src/wayland/idle_inhibit/idle-inhibit-unstable-v1.xml b/src/wayland/idle_inhibit/idle-inhibit-unstable-v1.xml
new file mode 100644
index 0000000..9c06cdc
--- /dev/null
+++ b/src/wayland/idle_inhibit/idle-inhibit-unstable-v1.xml
@@ -0,0 +1,83 @@
+
+
+
+
+ Copyright © 2015 Samsung Electronics Co., Ltd
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+
+
+
+
+ This interface permits inhibiting the idle behavior such as screen
+ blanking, locking, and screensaving. The client binds the idle manager
+ globally, then creates idle-inhibitor objects for each surface.
+
+ Warning! The protocol described in this file is experimental and
+ backward incompatible changes may be made. Backward compatible changes
+ may be added together with the corresponding interface version bump.
+ Backward incompatible changes are done by bumping the version number in
+ the protocol and interface names and resetting the interface version.
+ Once the protocol is to be declared stable, the 'z' prefix and the
+ version number in the protocol and interface names are removed and the
+ interface version number is reset.
+
+
+
+
+ Destroy the inhibit manager.
+
+
+
+
+
+ Create a new inhibitor object associated with the given surface.
+
+
+
+
+
+
+
+
+
+ An idle inhibitor prevents the output that the associated surface is
+ visible on from being set to a state where it is not visually usable due
+ to lack of user interaction (e.g. blanked, dimmed, locked, set to power
+ save, etc.) Any screensaver processes are also blocked from displaying.
+
+ If the surface is destroyed, unmapped, becomes occluded, loses
+ visibility, or otherwise becomes not visually relevant for the user, the
+ idle inhibitor will not be honored by the compositor; if the surface
+ subsequently regains visibility the inhibitor takes effect once again.
+ Likewise, the inhibitor isn't honored if the system was already idled at
+ the time the inhibitor was established, although if the system later
+ de-idles and re-idles the inhibitor will take effect.
+
+
+
+
+ Remove the inhibitor effect from the associated wl_surface.
+
+
+
+
+
diff --git a/src/wayland/idle_inhibit/inhibitor.cpp b/src/wayland/idle_inhibit/inhibitor.cpp
new file mode 100644
index 0000000..f576722
--- /dev/null
+++ b/src/wayland/idle_inhibit/inhibitor.cpp
@@ -0,0 +1,136 @@
+#include "inhibitor.hpp"
+
+#include
+#include
+#include
+#include
+
+#include "../../window/proxywindow.hpp"
+#include "../../window/windowinterface.hpp"
+#include "proto.hpp"
+
+namespace qs::wayland::idle_inhibit {
+using QtWaylandClient::QWaylandWindow;
+
+IdleInhibitor::IdleInhibitor() {
+ this->bBoundWindow.setBinding([this] { return this->bEnabled ? this->bWindowObject : nullptr; });
+}
+
+QObject* IdleInhibitor::window() const { return this->bWindowObject; }
+
+void IdleInhibitor::setWindow(QObject* window) {
+ if (window == this->bWindowObject) return;
+
+ auto* proxyWindow = qobject_cast(window);
+
+ if (proxyWindow == nullptr) {
+ if (auto* iface = qobject_cast(window)) {
+ proxyWindow = iface->proxyWindow();
+ }
+ }
+
+ this->bWindowObject = proxyWindow ? window : nullptr;
+}
+
+void IdleInhibitor::boundWindowChanged() {
+ auto* window = this->bBoundWindow.value();
+ auto* proxyWindow = qobject_cast(window);
+
+ if (proxyWindow == nullptr) {
+ if (auto* iface = qobject_cast(window)) {
+ proxyWindow = iface->proxyWindow();
+ }
+ }
+
+ if (proxyWindow == this->proxyWindow) return;
+
+ if (this->mWaylandWindow) {
+ QObject::disconnect(this->mWaylandWindow, nullptr, this, nullptr);
+ this->mWaylandWindow = nullptr;
+ this->onWaylandSurfaceDestroyed();
+ }
+
+ if (this->proxyWindow) {
+ QObject::disconnect(this->proxyWindow, nullptr, this, nullptr);
+ this->proxyWindow = nullptr;
+ }
+
+ if (proxyWindow) {
+ this->proxyWindow = proxyWindow;
+
+ QObject::connect(proxyWindow, &QObject::destroyed, this, &IdleInhibitor::onWindowDestroyed);
+
+ QObject::connect(
+ proxyWindow,
+ &ProxyWindowBase::backerVisibilityChanged,
+ this,
+ &IdleInhibitor::onWindowVisibilityChanged
+ );
+
+ this->onWindowVisibilityChanged();
+ }
+
+ emit this->windowChanged();
+}
+
+void IdleInhibitor::onWindowDestroyed() {
+ this->proxyWindow = nullptr;
+ this->onWaylandSurfaceDestroyed();
+ this->bWindowObject = nullptr;
+}
+
+void IdleInhibitor::onWindowVisibilityChanged() {
+ if (!this->proxyWindow->isVisibleDirect()) return;
+
+ auto* window = this->proxyWindow->backingWindow();
+ if (!window->handle()) window->create();
+
+ auto* waylandWindow = dynamic_cast(window->handle());
+ if (waylandWindow == this->mWaylandWindow) return;
+ this->mWaylandWindow = waylandWindow;
+
+ QObject::connect(
+ waylandWindow,
+ &QObject::destroyed,
+ this,
+ &IdleInhibitor::onWaylandWindowDestroyed
+ );
+
+ QObject::connect(
+ waylandWindow,
+ &QWaylandWindow::surfaceCreated,
+ this,
+ &IdleInhibitor::onWaylandSurfaceCreated
+ );
+
+ QObject::connect(
+ waylandWindow,
+ &QWaylandWindow::surfaceDestroyed,
+ this,
+ &IdleInhibitor::onWaylandSurfaceDestroyed
+ );
+
+ if (waylandWindow->surface()) this->onWaylandSurfaceCreated();
+}
+
+void IdleInhibitor::onWaylandWindowDestroyed() { this->mWaylandWindow = nullptr; }
+
+void IdleInhibitor::onWaylandSurfaceCreated() {
+ auto* manager = impl::IdleInhibitManager::instance();
+
+ if (!manager) {
+ qWarning() << "Cannot enable idle inhibitor as idle-inhibit-unstable-v1 is not supported by "
+ "the current compositor.";
+ return;
+ }
+
+ delete this->inhibitor;
+ this->inhibitor = manager->createIdleInhibitor(this->mWaylandWindow);
+}
+
+void IdleInhibitor::onWaylandSurfaceDestroyed() {
+ delete this->inhibitor;
+ this->inhibitor = nullptr;
+}
+
+} // namespace qs::wayland::idle_inhibit
diff --git a/src/wayland/idle_inhibit/inhibitor.hpp b/src/wayland/idle_inhibit/inhibitor.hpp
new file mode 100644
index 0000000..3d16bd4
--- /dev/null
+++ b/src/wayland/idle_inhibit/inhibitor.hpp
@@ -0,0 +1,72 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+
+#include "../../window/proxywindow.hpp"
+#include "proto.hpp"
+
+namespace qs::wayland::idle_inhibit {
+
+///! Prevents a wayland session from idling
+/// If an idle daemon is running, it may perform actions such as locking the screen
+/// or putting the computer to sleep.
+///
+/// An idle inhibitor prevents a wayland session from being marked as idle, if compositor
+/// defined heuristics determine the window the inhibitor is attached to is important.
+///
+/// A compositor will usually consider a @@Quickshell.PanelWindow or
+/// a focused @@Quickshell.FloatingWindow to be important.
+///
+/// > [!NOTE] Using an idle inhibitor requires the compositor support the [idle-inhibit-unstable-v1] protocol.
+///
+/// [idle-inhibit-unstable-v1]: https://wayland.app/protocols/idle-inhibit-unstable-v1
+class IdleInhibitor: public QObject {
+ Q_OBJECT;
+ QML_ELEMENT;
+ // clang-format off
+ /// If the idle inhibitor should be enabled. Defaults to false.
+ Q_PROPERTY(bool enabled READ default WRITE default NOTIFY enabledChanged BINDABLE bindableEnabled);
+ /// The window to associate the idle inhibitor with. This may be used by the compositor
+ /// to determine if the inhibitor should be respected.
+ ///
+ /// Must be set to a non null value to enable the inhibitor.
+ Q_PROPERTY(QObject* window READ window WRITE setWindow NOTIFY windowChanged);
+ // clang-format on
+
+public:
+ IdleInhibitor();
+
+ [[nodiscard]] QObject* window() const;
+ void setWindow(QObject* window);
+
+ [[nodiscard]] QBindable bindableEnabled() { return &this->bEnabled; }
+
+signals:
+ void enabledChanged();
+ void windowChanged();
+
+private slots:
+ void onWindowDestroyed();
+ void onWindowVisibilityChanged();
+ void onWaylandWindowDestroyed();
+ void onWaylandSurfaceCreated();
+ void onWaylandSurfaceDestroyed();
+
+private:
+ void boundWindowChanged();
+ ProxyWindowBase* proxyWindow = nullptr;
+ QtWaylandClient::QWaylandWindow* mWaylandWindow = nullptr;
+
+ impl::IdleInhibitor* inhibitor = nullptr;
+
+ // clang-format off
+ Q_OBJECT_BINDABLE_PROPERTY(IdleInhibitor, bool, bEnabled, &IdleInhibitor::enabledChanged);
+ Q_OBJECT_BINDABLE_PROPERTY(IdleInhibitor, QObject*, bWindowObject, &IdleInhibitor::windowChanged);
+ Q_OBJECT_BINDABLE_PROPERTY(IdleInhibitor, QObject*, bBoundWindow, &IdleInhibitor::boundWindowChanged);
+ // clang-format on
+};
+
+} // namespace qs::wayland::idle_inhibit
diff --git a/src/wayland/idle_inhibit/proto.cpp b/src/wayland/idle_inhibit/proto.cpp
new file mode 100644
index 0000000..0caab91
--- /dev/null
+++ b/src/wayland/idle_inhibit/proto.cpp
@@ -0,0 +1,23 @@
+#include "proto.hpp"
+
+#include
+#include
+
+namespace qs::wayland::idle_inhibit::impl {
+
+IdleInhibitManager::IdleInhibitManager(): QWaylandClientExtensionTemplate(1) { this->initialize(); }
+
+IdleInhibitManager* IdleInhibitManager::instance() {
+ static auto* instance = new IdleInhibitManager(); // NOLINT
+ return instance->isInitialized() ? instance : nullptr;
+}
+
+IdleInhibitor* IdleInhibitManager::createIdleInhibitor(QtWaylandClient::QWaylandWindow* surface) {
+ return new IdleInhibitor(this->create_inhibitor(surface->surface()));
+}
+
+IdleInhibitor::~IdleInhibitor() {
+ if (this->isInitialized()) this->destroy();
+}
+
+} // namespace qs::wayland::idle_inhibit::impl
diff --git a/src/wayland/idle_inhibit/proto.hpp b/src/wayland/idle_inhibit/proto.hpp
new file mode 100644
index 0000000..c797c33
--- /dev/null
+++ b/src/wayland/idle_inhibit/proto.hpp
@@ -0,0 +1,34 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+
+#include "wayland-idle-inhibit-unstable-v1-client-protocol.h"
+
+namespace qs::wayland::idle_inhibit::impl {
+
+class IdleInhibitor;
+
+class IdleInhibitManager
+ : public QWaylandClientExtensionTemplate
+ , public QtWayland::zwp_idle_inhibit_manager_v1 {
+public:
+ explicit IdleInhibitManager();
+
+ IdleInhibitor* createIdleInhibitor(QtWaylandClient::QWaylandWindow* surface);
+
+ static IdleInhibitManager* instance();
+};
+
+class IdleInhibitor: public QtWayland::zwp_idle_inhibitor_v1 {
+public:
+ explicit IdleInhibitor(::zwp_idle_inhibitor_v1* inhibitor)
+ : QtWayland::zwp_idle_inhibitor_v1(inhibitor) {}
+
+ ~IdleInhibitor() override;
+ Q_DISABLE_COPY_MOVE(IdleInhibitor);
+};
+
+} // namespace qs::wayland::idle_inhibit::impl
diff --git a/src/wayland/module.md b/src/wayland/module.md
index b9f8f59..622346a 100644
--- a/src/wayland/module.md
+++ b/src/wayland/module.md
@@ -5,5 +5,6 @@ headers = [
"session_lock.hpp",
"toplevel_management/qml.hpp",
"screencopy/view.hpp",
+ "idle_inhibit/inhibitor.hpp",
]
-----