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", ] -----