diff --git a/debian/control b/debian/control index 275e8ce..5ff0caa 100644 --- a/debian/control +++ b/debian/control @@ -6,6 +6,10 @@ Build-Depends: debhelper (>= 11), pkg-config, cmake, qt6-base-dev, + qt6-tools-dev-tools, + qt6-wayland-dev, + qt6-wayland-private-dev, + qt6-wayland-dev-tools, libsystemd-dev, libpcap-dev, libnet-dev, @@ -15,6 +19,9 @@ Build-Depends: debhelper (>= 11), libx11-dev, libxcb-randr0-dev, libxcb1-dev, + treeland-protocols, + wlr-protocols, + wayland-protocols, Standards-Version: 4.1.3 Homepage: https://github.com/linuxdeepin diff --git a/src/plugin-qt/CMakeLists.txt b/src/plugin-qt/CMakeLists.txt index 4d571df..a1c632b 100644 --- a/src/plugin-qt/CMakeLists.txt +++ b/src/plugin-qt/CMakeLists.txt @@ -5,4 +5,5 @@ add_subdirectory("thememanager") add_subdirectory("wallpapercache") add_subdirectory("wallpaperslideshow") -add_subdirectory("xsettings") \ No newline at end of file +add_subdirectory("xsettings") +add_subdirectory("power") \ No newline at end of file diff --git a/src/plugin-qt/power/CMakeLists.txt b/src/plugin-qt/power/CMakeLists.txt new file mode 100644 index 0000000..7f41238 --- /dev/null +++ b/src/plugin-qt/power/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +# +# SPDX-License-Identifier: LGPL-3.0-or-later + +add_subdirectory(session) +# TODO: system-level power is provided by dde-daemon's dde-system-daemon +# for both X11 and Wayland. Remove this when unified. +# add_subdirectory(system) diff --git a/src/plugin-qt/power/powerconstants.h b/src/plugin-qt/power/powerconstants.h new file mode 100644 index 0000000..1d37b6d --- /dev/null +++ b/src/plugin-qt/power/powerconstants.h @@ -0,0 +1,113 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +namespace PowerDConfig { + inline constexpr auto kAppId = "org.deepin.dde.daemon"; + inline constexpr auto kPowerName = "org.deepin.dde.daemon.power"; + + inline constexpr auto kLinePowerScreensaverDelay = "linePowerScreensaverDelay"; + inline constexpr auto kLinePowerScreenBlackDelay = "linePowerScreenBlackDelay"; + inline constexpr auto kLinePowerSleepDelay = "linePowerSleepDelay"; + inline constexpr auto kLinePowerLockDelay = "linePowerLockDelay"; + inline constexpr auto kLinePowerLidClosedAction = "linePowerLidClosedAction"; + inline constexpr auto kLinePowerPressPowerButton = "linePowerPressPowerButton"; + + inline constexpr auto kBatteryScreensaverDelay = "batteryScreensaverDelay"; + inline constexpr auto kBatteryScreenBlackDelay = "batteryScreenBlackDelay"; + inline constexpr auto kBatterySleepDelay = "batterySleepDelay"; + inline constexpr auto kBatteryLockDelay = "batteryLockDelay"; + inline constexpr auto kBatteryLidClosedAction = "batteryLidClosedAction"; + inline constexpr auto kBatteryPressPowerButton = "batteryPressPowerButton"; + + inline constexpr auto kScreenBlackLock = "screenBlackLock"; + inline constexpr auto kSleepLock = "sleepLock"; + inline constexpr auto kPowerButtonPressedExec = "powerButtonPressedExec"; + + inline constexpr auto kLowPowerNotifyEnable = "lowPowerNotifyEnable"; + inline constexpr auto kLowPowerNotifyThreshold = "lowPowerNotifyThreshold"; + inline constexpr auto kUsePercentageForPolicy = "usePercentageForPolicy"; + inline constexpr auto kPercentageAction = "percentageAction"; + inline constexpr auto kTimeToEmptyLow = "timeToEmptyLow"; + inline constexpr auto kTimeToEmptyDanger = "timeToEmptyDanger"; + inline constexpr auto kTimeToEmptyCritical = "timeToEmptyCritical"; + inline constexpr auto kTimeToEmptyAction = "timeToEmptyAction"; + inline constexpr auto kLowPowerAction = "lowPowerAction"; + inline constexpr auto kPowerSavingModeBrightnessDropPercent = "powerSavingModeBrightnessDropPercent"; + inline constexpr auto kPowerSavingModeEnabled = "powerSavingModeEnabled"; + inline constexpr auto kPowerSavingModeAuto = "powerSavingModeAuto"; + inline constexpr auto kPowerSavingModeAutoWhenBatteryLow = "powerSavingModeAutoWhenBatteryLow"; + inline constexpr auto kPowerSavingModeAutoBatteryPercent = "powerSavingModeAutoBatteryPercent"; + inline constexpr auto kLastMode = "lastMode"; + inline constexpr auto kMode = "mode"; + inline constexpr auto kAdjustBrightnessEnabled = "adjustBrightnessEnabled"; + inline constexpr auto kHighPerformanceEnabled = "highPerformanceEnabled"; + inline constexpr auto kAmbientLightAdjustBrightness = "ambientLightAdjustBrightness"; + + inline constexpr auto kScheduledShutdownState = "scheduledShutdownState"; + inline constexpr auto kShutdownTime = "shutdownTime"; + inline constexpr auto kShutdownRepetition = "shutdownRepetition"; + inline constexpr auto kCustomShutdownWeekDays = "customShutdownWeekDays"; + inline constexpr auto kShutdownCountdown = "shutdownCountdown"; + inline constexpr auto kNextShutdownTime = "nextShutdownTime"; +} + +namespace PowerDBus { + inline constexpr auto kService = "org.deepin.dde.Power1"; + inline constexpr auto kPath = "/org/deepin/dde/Power1"; + inline constexpr auto kInterface = "org.deepin.dde.Power1"; + + inline constexpr auto kLogin1Service = "org.freedesktop.login1"; + inline constexpr auto kLogin1Path = "/org/freedesktop/login1"; + inline constexpr auto kLogin1Manager = "org.freedesktop.login1.Manager"; + inline constexpr auto kLogin1Session = "org.freedesktop.login1.Session"; + + inline constexpr auto kSessionManager = "org.deepin.dde.SessionManager1"; + inline constexpr auto kSessionPath = "/org/deepin/dde/SessionManager1"; + + inline constexpr auto kSessionWatcher = "org.deepin.dde.SessionWatcher1"; + inline constexpr auto kSessionWatcherPath = "/org/deepin/dde/SessionWatcher1"; + + inline constexpr auto kDaemonService = "org.deepin.dde.Daemon1"; + inline constexpr auto kDaemonPath = "/org/deepin/dde/Daemon1"; + + inline constexpr auto kLockFront = "org.deepin.dde.LockFront1"; + inline constexpr auto kLockFrontPath = "/org/deepin/dde/LockFront1"; + + inline constexpr auto kShutdownFront = "org.deepin.dde.ShutdownFront1"; + inline constexpr auto kShutdownPath = "/org/deepin/dde/ShutdownFront1"; + + inline constexpr auto kScreensaver = "com.deepin.ScreenSaver"; + inline constexpr auto kScreensaverPath = "/com/deepin/ScreenSaver"; + + inline constexpr auto kDisplay = "org.deepin.dde.Display1"; + inline constexpr auto kDisplayPath = "/org/deepin/dde/Display1"; + + inline constexpr auto kBlackScreen = "org.deepin.dde.BlackScreen1"; + inline constexpr auto kBlackScreenPath = "/org/deepin/dde/BlackScreen1"; + + inline constexpr auto kNotifications = "org.freedesktop.Notifications"; + inline constexpr auto kNotificationsPath = "/org/freedesktop/Notifications"; + + inline constexpr auto kFreedesktopDBus = "org.freedesktop.DBus"; + inline constexpr auto kFreedesktopPath = "/org/freedesktop/DBus"; + + inline constexpr auto kUPowerService = "org.freedesktop.UPower"; + inline constexpr auto kUPowerPath = "/org/freedesktop/UPower"; + + inline constexpr auto kCalendarService = "com.deepin.dataserver.Calendar"; + inline constexpr auto kCalendarPath = "/com/deepin/dataserver/Calendar/HuangLi"; + inline constexpr auto kCalendarIface = "com.deepin.dataserver.Calendar.HuangLi"; +} + +namespace PowerFS { + inline constexpr auto kCpuSysfsDir = "/sys/devices/system/cpu"; + inline constexpr auto kCpuGovernorFmt = "/sys/devices/system/cpu/%1/cpufreq/scaling_governor"; + inline constexpr auto kCpuBoostPath = "/sys/devices/system/cpu/cpufreq/boost"; + inline constexpr auto kLidStatePath = "/proc/acpi/button/lid/LID/state"; + inline constexpr auto kDpmsStateFile = "/tmp/dpms-state"; + inline constexpr auto kNoSuspendFile = "/etc/deepin/no_suspend"; + inline constexpr auto kLowPowerCmd = "/usr/lib/deepin-daemon/dde-lowpower"; +} diff --git a/src/plugin-qt/power/session/CMakeLists.txt b/src/plugin-qt/power/session/CMakeLists.txt new file mode 100644 index 0000000..9009a3d --- /dev/null +++ b/src/plugin-qt/power/session/CMakeLists.txt @@ -0,0 +1,103 @@ +# SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +# +# SPDX-License-Identifier: LGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.16) + +set(BIN_NAME "plugin-power-session") +project(${BIN_NAME}) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +include(GNUInstallDirs) + +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core DBus Gui WaylandClient LinguistTools) +find_package(Dtk${DTK_VERSION_MAJOR} REQUIRED COMPONENTS Core) +find_package(PkgConfig REQUIRED) +pkg_check_modules(WAYLAND_PROTOCOLS REQUIRED wayland-protocols) +pkg_check_modules(WLR_PROTOCOLS REQUIRED wlr-protocols) +pkg_check_modules(TREELAND_PROTOCOLS REQUIRED treeland-protocols) +pkg_get_variable(WAYLAND_PROTOCOLS_DATADIR wayland-protocols pkgdatadir) +pkg_get_variable(WLR_PROTOCOLS_DATADIR wlr-protocols pkgdatadir) +pkg_get_variable(TREELAND_PROTOCOLS_DATADIR treeland-protocols pkgdatadir) + +set(_src_idle + idle/idlewatcher_wl.cpp + idle/idlewatcher_x11.cpp +) +set(_src_screen + screen/screencontroller.cpp + screen/screencontroller_wl.cpp + screen/screencontroller_x11.cpp +) +set(_src_core + plugin-power-session.cpp + powermanager.cpp + sessiondbusproxy.cpp + powersaveplan.cpp + lidswitchhandler.cpp + sleepinhibitor.cpp + lowpowermanager.cpp +) +set(_src_headers + idle/idlewatcher.h + idle/idlewatcher_wl.h + screen/screencontroller.h + screen/screencontroller_wl.h + powersaveplan.h + lidswitchhandler.h + sleepinhibitor.h + lowpowermanager.h + powermanager.h + sessiondbusproxy.h +) + +file(GLOB TS_FILES translations/*.ts) + +add_library(${BIN_NAME} MODULE + ${_src_idle} ${_src_screen} ${_src_core} ${_src_headers} +) + +qt6_generate_wayland_protocol_client_sources(${BIN_NAME} + FILES + ${WAYLAND_PROTOCOLS_DATADIR}/staging/ext-idle-notify/ext-idle-notify-v1.xml + ${WLR_PROTOCOLS_DATADIR}/unstable/wlr-output-power-management-unstable-v1.xml + ${TREELAND_PROTOCOLS_DATADIR}/treeland-output-manager-v1.xml +) + +qt_add_translations(${BIN_NAME} + TS_FILES ${TS_FILES} + QM_FILES_OUTPUT_VARIABLE QM_FILES + LUPDATE_OPTIONS -no-ui-lines -locations none +) + + +target_include_directories(${BIN_NAME} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} +) + +target_link_libraries(${BIN_NAME} PRIVATE + Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::DBus + Qt${QT_VERSION_MAJOR}::Gui + Qt${QT_VERSION_MAJOR}::WaylandClient + Dtk${DTK_VERSION_MAJOR}::Core +) + +install(TARGETS ${BIN_NAME} +DESTINATION ${CMAKE_INSTALL_LIBDIR}/deepin-service-manager/ +) + +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/misc/plugin-power-session.json +DESTINATION ${CMAKE_INSTALL_FULL_DATADIR}/deepin-service-manager/user/ +) + +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/misc/dbus/org.deepin.dde.Power1.service +DESTINATION ${CMAKE_INSTALL_FULL_DATADIR}/dbus-1/services/ +) + +install(FILES ${QM_FILES} +DESTINATION ${CMAKE_INSTALL_DATADIR}/${BIN_NAME}/translations +) diff --git a/src/plugin-qt/power/session/idle/idlewatcher.h b/src/plugin-qt/power/session/idle/idlewatcher.h new file mode 100644 index 0000000..7e76341 --- /dev/null +++ b/src/plugin-qt/power/session/idle/idlewatcher.h @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include +#include + +/** + * @brief Abstract interface for user idle detection. + * + * X11 implementation: uses org.freedesktop.ScreenSaver D-Bus signals. + * Wayland implementation: uses ext-idle-notify-v1 protocol. + */ +class IdleWatcher : public QObject +{ + Q_OBJECT + +public: + explicit IdleWatcher(QObject *parent = nullptr) : QObject(parent) {} + ~IdleWatcher() override = default; + + /// Whether the underlying backend initialized successfully. + virtual bool isValid() const = 0; + + /// Set the idle timeout in seconds. Re-creates the notification object + /// with the new timeout when the backend supports it. + virtual void setTimeout(uint32_t timeoutSec) = 0; + + /// Simulate user activity — resets the idle timer. + virtual void simulateActivity() = 0; + + /// Elapsed idle time in milliseconds (0 if not idle). + virtual uint32_t idleTimeMs() const = 0; + + /// Whether the user is currently idle. + virtual bool isIdle() const = 0; + +Q_SIGNALS: + /// Emitted when the user becomes idle. + void idled(); + + /// Emitted when the user resumes activity. + void resumed(); +}; diff --git a/src/plugin-qt/power/session/idle/idlewatcher_wl.cpp b/src/plugin-qt/power/session/idle/idlewatcher_wl.cpp new file mode 100644 index 0000000..371fc6c --- /dev/null +++ b/src/plugin-qt/power/session/idle/idlewatcher_wl.cpp @@ -0,0 +1,189 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "idlewatcher_wl.h" + +#include +#include + +Q_DECLARE_LOGGING_CATEGORY(logPowerSession) + +IdleNotification::IdleNotification(::ext_idle_notification_v1 *id) + : QtWayland::ext_idle_notification_v1(id) +{ +} + +IdleNotification::~IdleNotification() +{ + destroy(); +} + +void IdleNotification::ext_idle_notification_v1_idled() +{ + if (!m_active) + return; + + Q_EMIT idled(); +} + +void IdleNotification::ext_idle_notification_v1_resumed() +{ + if (!m_active) + return; + + Q_EMIT resumed(); +} + +IdleNotifier::IdleNotifier() + : QWaylandClientExtensionTemplate(1) +{ +} + +IdleNotifier::~IdleNotifier() +{ + if (isInitialized()) + destroy(); +} + +void IdleNotifier::instantiate() +{ + initialize(); +} + +std::unique_ptr +IdleNotifier::getIdleNotification(uint32_t timeout, wl_seat *seat) +{ + if (!isInitialized() || !seat) + return nullptr; + + auto *raw = get_idle_notification(timeout, seat); + if (!raw) { + qWarning(logPowerSession) << "[Power::IDLE] getIdleNotification: failed to create idle notification"; + return nullptr; + } + + return std::make_unique(raw); +} + +WaylandIdleWatcher::WaylandIdleWatcher(QObject *parent) + : IdleWatcher(parent) +{ + auto *app = qGuiApp; + if (!app) { + qWarning(logPowerSession) << "[Power::WL] Idle: no QGuiApplication"; + return; + } + + auto *wlApp = + app->nativeInterface(); + + if (!wlApp) { + qWarning(logPowerSession) << "[Power::WL] Idle: no QWaylandApplication"; + return; + } + + m_seat = wlApp->seat(); + if (!m_seat) { + qWarning(logPowerSession) << "[Power::WL] Idle: no wl_seat"; + return; + } + + m_notifier.reset(new IdleNotifier); + m_notifier->instantiate(); + + if (!m_notifier->isInitialized()) { + qWarning(logPowerSession) << "[Power::WL] Idle: notifier init failed"; + return; + } + + switchToNotification(m_timeoutSec); + m_valid = true; +} + +WaylandIdleWatcher::~WaylandIdleWatcher() +{ + if (m_activeNotification) + m_activeNotification->disconnect(this); + m_notificationCache.clear(); + m_notifier.reset(); +} + +void WaylandIdleWatcher::switchToNotification(uint32_t timeoutSec) +{ + if (!m_notifier || !m_seat || timeoutSec == 0) + return; + + // 1. 断开旧 notification + if (m_activeNotification) { + m_activeNotification->setActive(false); + m_activeNotification->disconnect(this); + m_activeNotification = nullptr; + } + + // 2. 缓存命中则复用, 否则从 notifier 创建新 notification 并缓存 + auto it = m_notificationCache.find(timeoutSec); + if (it == m_notificationCache.end()) { + auto notif = m_notifier->getIdleNotification(timeoutSec * 1000, m_seat); + if (!notif) { + qWarning(logPowerSession) << "[Power::IDLE] switchToNotification: failed, timeout=" + << timeoutSec << "s"; + return; + } + qDebug(logPowerSession) << "[Power::IDLE] create & cache notification for timeout=" << timeoutSec << "s"; + auto [inserted, _] = m_notificationCache.emplace(timeoutSec, std::move(notif)); + it = inserted; + } + + // 3. 激活新的 notification + m_activeNotification = it->second.get(); + m_activeNotification->setActive(true); + + m_isIdle = false; + m_idleTimer.invalidate(); + + connect(m_activeNotification, &IdleNotification::idled, this, [this]() { + if (m_isIdle) + return; + + m_isIdle = true; + m_idleTimer.start(); + + Q_EMIT idled(); + }); + + connect(m_activeNotification, &IdleNotification::resumed, this, [this]() { + if (!m_isIdle) + return; + + m_isIdle = false; + m_idleTimer.invalidate(); + + Q_EMIT resumed(); + }); +} + +void WaylandIdleWatcher::setTimeout(uint32_t s) +{ + if (m_timeoutSec == s) + return; + + m_timeoutSec = s; + switchToNotification(s); +} + +void WaylandIdleWatcher::simulateActivity() +{ + // unused +} + +uint32_t WaylandIdleWatcher::idleTimeMs() const +{ + if (!m_isIdle) + return 0; + + if (!m_idleTimer.isValid()) + return 0; + + return static_cast(m_idleTimer.elapsed()); +} diff --git a/src/plugin-qt/power/session/idle/idlewatcher_wl.h b/src/plugin-qt/power/session/idle/idlewatcher_wl.h new file mode 100644 index 0000000..2961913 --- /dev/null +++ b/src/plugin-qt/power/session/idle/idlewatcher_wl.h @@ -0,0 +1,72 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include "idlewatcher.h" + +#include +#include +#include + +#include +#include +#include "qwayland-ext-idle-notify-v1.h" + +struct wl_seat; + +class IdleNotification : public QObject, public QtWayland::ext_idle_notification_v1 +{ + Q_OBJECT +public: + explicit IdleNotification(::ext_idle_notification_v1 *id); + ~IdleNotification() override; + void setActive(bool on) { m_active = on; } + bool isActive() const { return m_active; } +Q_SIGNALS: + void idled(); + void resumed(); +protected: + void ext_idle_notification_v1_idled() override; + void ext_idle_notification_v1_resumed() override; +private: + bool m_active = true; +}; + +class IdleNotifier : public QWaylandClientExtensionTemplate + , public QtWayland::ext_idle_notifier_v1 +{ + Q_OBJECT +public: + IdleNotifier(); + ~IdleNotifier() override; + void instantiate(); + std::unique_ptr getIdleNotification(uint32_t timeout, wl_seat *seat); +}; + +class WaylandIdleWatcher : public IdleWatcher +{ + Q_OBJECT +public: + explicit WaylandIdleWatcher(QObject *parent = nullptr); + ~WaylandIdleWatcher() override; + + bool isValid() const override { return m_valid; } + void setTimeout(uint32_t timeoutSec) override; + void simulateActivity() override; + uint32_t idleTimeMs() const override; + bool isIdle() const override { return m_isIdle; } + +private: + void switchToNotification(uint32_t timeoutSec); + + std::unique_ptr m_notifier; + std::unordered_map> m_notificationCache; + IdleNotification *m_activeNotification = nullptr; + wl_seat *m_seat = nullptr; + QElapsedTimer m_idleTimer; + uint32_t m_timeoutSec = 300; + bool m_valid = false; + bool m_isIdle = false; +}; diff --git a/src/plugin-qt/power/session/idle/idlewatcher_x11.cpp b/src/plugin-qt/power/session/idle/idlewatcher_x11.cpp new file mode 100644 index 0000000..4cea4dc --- /dev/null +++ b/src/plugin-qt/power/session/idle/idlewatcher_x11.cpp @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "idlewatcher.h" + +class X11IdleWatcher : public IdleWatcher +{ + Q_OBJECT +public: + using IdleWatcher::IdleWatcher; + bool isValid() const override { return false; } + void setTimeout(uint32_t) override {} + void simulateActivity() override {} + uint32_t idleTimeMs() const override { return 0; } + bool isIdle() const override { return false; } +}; + +#include "idlewatcher_x11.moc" diff --git a/src/plugin-qt/power/session/lidswitchhandler.cpp b/src/plugin-qt/power/session/lidswitchhandler.cpp new file mode 100644 index 0000000..8331915 --- /dev/null +++ b/src/plugin-qt/power/session/lidswitchhandler.cpp @@ -0,0 +1,97 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "lidswitchhandler.h" +#include "idle/idlewatcher.h" +#include "powermanager.h" +#include "../powerconstants.h" + +#include +#include +#include +#include + +Q_DECLARE_LOGGING_CATEGORY(logPowerSession) + +using namespace PowerDBus; + +LidSwitchHandler::LidSwitchHandler(PowerManager *manager, QObject *parent) + : QObject(parent), m_manager(manager) +{ + m_debounce = new QTimer(this); + m_debounce->setSingleShot(true); + m_debounce->setInterval(1500); + + connect(m_debounce, &QTimer::timeout, this, [this]() { + doLidStateChanged(m_pendingOpen); + }); + + auto bus = QDBusConnection::systemBus(); + bus.connect(kService, kPath, kInterface, "LidClosed", + this, SLOT(onLidClosed())); + bus.connect(kService, kPath, kInterface, "LidOpened", + this, SLOT(onLidOpened())); +} + +void LidSwitchHandler::onLidClosed() +{ + qDebug(logPowerSession) << "Lid closed"; + m_pendingOpen = false; + m_debounce->start(); +} + +void LidSwitchHandler::onLidOpened() +{ + qDebug(logPowerSession) << "Lid opened"; + m_pendingOpen = true; + m_debounce->start(); +} + +void LidSwitchHandler::doLidStateChanged(bool opened) +{ + if (!m_manager) return; + + if (!opened) { + // 合盖 + m_manager->SetPrepareSuspend(PS_LidClose); + bool onBattery = m_manager->onBattery(); + int32_t action = onBattery ? m_manager->batteryLidClosedAction() + : m_manager->linePowerLidClosedAction(); + qDebug(logPowerSession) << "Lid closed, onBattery=" << onBattery << " action=" << action; + + switch (action) { + case PA_Shutdown: + m_manager->doShutdown(); + break; + case PA_Suspend: + m_manager->doSuspend(); + break; + case PA_Hibernate: + m_manager->doHibernate(); + break; + case PA_TurnOffScreen: + m_manager->doTurnOffScreen(); + break; + case PA_Lock: + m_manager->doLock(); + break; + case PA_DoNothing: + return; + default: + break; + } + + if (action != PA_TurnOffScreen) + m_manager->setBlackScreenActive(true); + } else { + if (m_manager->useWayland()) + m_manager->SetPrepareSuspend(PS_Resume); + + if (auto *iw = m_manager->idleWatcher()) + iw->simulateActivity(); + + m_manager->setBlackScreenActive(false); + m_manager->setDPMSModeOn(); + } +} diff --git a/src/plugin-qt/power/session/lidswitchhandler.h b/src/plugin-qt/power/session/lidswitchhandler.h new file mode 100644 index 0000000..d43e651 --- /dev/null +++ b/src/plugin-qt/power/session/lidswitchhandler.h @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include + +class PowerManager; +class QTimer; + +class LidSwitchHandler : public QObject { + Q_OBJECT +public: + explicit LidSwitchHandler(PowerManager *manager, QObject *parent = nullptr); + +private Q_SLOTS: + void onLidClosed(); + void onLidOpened(); + +private: + void doLidStateChanged(bool opened); + QTimer *m_debounce = nullptr; + bool m_pendingOpen = true; + PowerManager *m_manager = nullptr; +}; diff --git a/src/plugin-qt/power/session/lowpowermanager.cpp b/src/plugin-qt/power/session/lowpowermanager.cpp new file mode 100644 index 0000000..e12e4bc --- /dev/null +++ b/src/plugin-qt/power/session/lowpowermanager.cpp @@ -0,0 +1,253 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "lowpowermanager.h" +#include "powermanager.h" +#include "../powerconstants.h" + +#include +#include +#include +#include + +Q_DECLARE_LOGGING_CATEGORY(logPowerSession) + +using namespace PowerFS; +using namespace PowerDConfig; + +LowPowerManager::LowPowerManager(PowerManager *powerManager, QObject *parent) + : QObject(parent), m_powerManager(powerManager) +{ + m_countTicker = new QTimer(this); + m_countTicker->setInterval(1000); + + connect(m_countTicker, &QTimer::timeout, this, [this]() { + m_count++; + if (!m_powerManager) { + disableTicker(); + return; + } + + if (m_count == 3) { + if (m_powerManager->sleepLock()) + lockWaitShow(5000, false); + } else if (m_count == 4) { + showLowPower(); + } else if (m_count >= 5) { + disableTicker(); + if (m_powerManager->lowPowerAction() == 1) + m_powerManager->doHibernate(); + else + m_powerManager->doSuspend(); + } + }); + + connect(m_powerManager, &PowerManager::batteryPercentageChanged, + this, &LowPowerManager::updateWarnLevel); + connect(m_powerManager, &PowerManager::onBatteryChanged, + this, &LowPowerManager::updateWarnLevel); +} + +void LowPowerManager::initConfig(Dtk::Core::DConfig *config) +{ + m_config = config; + if (!m_config) return; + + connect(m_config, &Dtk::Core::DConfig::valueChanged, + this, &LowPowerManager::onConfigChanged); + + m_usePercentageForPolicy = m_config->value(kUsePercentageForPolicy, true).toBool(); + m_lowPowerNotifyThreshold = m_config->value(kLowPowerNotifyThreshold, 0).toInt(); + m_percentageAction = m_config->value(kPercentageAction, 0).toInt(); + m_timeToEmptyLow = static_cast(m_config->value(kTimeToEmptyLow, 0).toLongLong()); + m_timeToEmptyDanger = static_cast(m_config->value(kTimeToEmptyDanger, 0).toLongLong()); + m_timeToEmptyCritical = static_cast(m_config->value(kTimeToEmptyCritical, 0).toLongLong()); + m_timeToEmptyAction = static_cast(m_config->value(kTimeToEmptyAction, 0).toLongLong()); +} + +void LowPowerManager::onConfigChanged(const QString &key) +{ + if (!m_config) return; + + if (key == QLatin1String(kUsePercentageForPolicy)) + m_usePercentageForPolicy = m_config->value(kUsePercentageForPolicy, true).toBool(); + else if (key == QLatin1String(kLowPowerNotifyThreshold)) + m_lowPowerNotifyThreshold = m_config->value(kLowPowerNotifyThreshold, 0).toInt(); + else if (key == QLatin1String(kPercentageAction)) + m_percentageAction = m_config->value(kPercentageAction, 0).toInt(); + else if (key == QLatin1String(kTimeToEmptyLow)) + m_timeToEmptyLow = static_cast(m_config->value(kTimeToEmptyLow, 0).toLongLong()); + else if (key == QLatin1String(kTimeToEmptyDanger)) + m_timeToEmptyDanger = static_cast(m_config->value(kTimeToEmptyDanger, 0).toLongLong()); + else if (key == QLatin1String(kTimeToEmptyCritical)) + m_timeToEmptyCritical = static_cast(m_config->value(kTimeToEmptyCritical, 0).toLongLong()); + else if (key == QLatin1String(kTimeToEmptyAction)) + m_timeToEmptyAction = static_cast(m_config->value(kTimeToEmptyAction, 0).toLongLong()); + else + return; + + updateWarnLevel(); +} + +uint LowPowerManager::getWarnLevel(double percentage, quint64 timeToEmpty) +{ + if (!m_powerManager || !m_powerManager->onBattery()) + return None; + + if (m_usePercentageForPolicy) { + if (percentage == 0.0) + return None; + + if (percentage <= m_lowPowerNotifyThreshold) { + if (m_percentageAction > 0 && percentage <= m_percentageAction) + return Action; + if (percentage <= 10.0) + return Critical; + if (percentage <= 15.0) + return Danger; + if (percentage <= 20.0) + return Low; + if (percentage <= 25.0) + return Remind; + return None; + } + + return None; + } else { + if (timeToEmpty > m_timeToEmptyLow || timeToEmpty == 0) + return None; + if (timeToEmpty > m_timeToEmptyDanger) + return Low; + if (timeToEmpty > m_timeToEmptyCritical) + return Danger; + if (timeToEmpty > m_timeToEmptyAction) + return Critical; + return Action; + } +} + +void LowPowerManager::updateWarnLevel() +{ + if (!m_powerManager || !m_powerManager->onBattery()) { + if (m_currentLevel != None) { + m_currentLevel = None; + handleLevelChanged(None); + } + disableTicker(); + closeLowPower(); + return; + } + + double pct = 100.0; + auto bat = m_powerManager->batteryPercentage(); + if (!bat.isEmpty()) + pct = bat.first(); + + quint64 tte = m_powerManager->batteryTimeToEmpty(); + + uint newLevel = getWarnLevel(pct, tte); + if (newLevel == m_currentLevel) + return; + + m_currentLevel = newLevel; + handleLevelChanged(newLevel); +} + +void LowPowerManager::handleLevelChanged(uint level) +{ + qDebug(logPowerSession) << "Battery level changed: " << level; + disableTicker(); + + switch (level) { + case Action: { + if (m_powerManager && m_powerManager->scheduledShutdownState()) + m_powerManager->scheduledShutdown(SchedInit); // matches Go: m.scheduledShutdown(Init) + playBatterySound(); + sendNotify(tr("Battery critically low")); + startCountTicker(); + break; + } + case Critical: + case Danger: + case Low: + case Remind: + playBatterySound(); + sendNotify(tr("Battery low, please plug in")); + break; + case None: { + closeLowPower(); + if (m_powerManager) { + m_powerManager->closeNotify(); + if (m_powerManager->scheduledShutdownState()) + m_powerManager->scheduledShutdown(SchedInit); + } + break; + } + } +} + +void LowPowerManager::startCountTicker() +{ + m_count = 0; + m_countTicker->start(); +} + +void LowPowerManager::lockWaitShow(int timeoutMs, bool autoStartAuth) +{ + if (!m_powerManager) return; + + m_powerManager->doLock(autoStartAuth); + + auto *pollTimer = new QTimer(this); + auto *endTimer = new QTimer(this); + endTimer->setSingleShot(true); + + connect(pollTimer, &QTimer::timeout, this, [pollTimer, endTimer]() { + QDBusInterface sm(PowerDBus::kSessionManager, PowerDBus::kSessionPath, + PowerDBus::kSessionManager, QDBusConnection::sessionBus()); + if (sm.isValid() && sm.property("Locked").toBool()) { + pollTimer->stop(); + endTimer->stop(); + pollTimer->deleteLater(); + endTimer->deleteLater(); + } + }); + + connect(endTimer, &QTimer::timeout, this, [pollTimer, endTimer]() { + pollTimer->stop(); + pollTimer->deleteLater(); + endTimer->deleteLater(); + }); + + pollTimer->start(300); + endTimer->start(timeoutMs); +} + +void LowPowerManager::playBatterySound() +{ + if (!QProcess::startDetached("paplay", {"/usr/share/sounds/deepin/stereo/battery-low.ogg"})) + qWarning(logPowerSession) << "Failed to play battery sound"; +} + +void LowPowerManager::disableTicker() +{ + m_countTicker->stop(); +} + +void LowPowerManager::sendNotify(const QString &body) +{ + m_powerManager->sendNotify("", body); +} + +void LowPowerManager::showLowPower() +{ + if (!QProcess::startDetached(kLowPowerCmd, {"--raise"})) + qWarning(logPowerSession) << "Failed to start dde-lowpower --raise"; +} + +void LowPowerManager::closeLowPower() +{ + if (!QProcess::startDetached(kLowPowerCmd, {"--quit"})) + qWarning(logPowerSession) << "Failed to start dde-lowpower --quit"; +} diff --git a/src/plugin-qt/power/session/lowpowermanager.h b/src/plugin-qt/power/session/lowpowermanager.h new file mode 100644 index 0000000..0b65755 --- /dev/null +++ b/src/plugin-qt/power/session/lowpowermanager.h @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include +#include +#include + +class PowerManager; + +class LowPowerManager : public QObject { + Q_OBJECT +public: + LowPowerManager(PowerManager *powerManager, QObject *parent = nullptr); + enum Level { None = 0, Remind, Low, Danger, Critical, Action }; + + void initConfig(Dtk::Core::DConfig *config); + +private Q_SLOTS: + void updateWarnLevel(); + void onConfigChanged(const QString &key); + +private: + void handleLevelChanged(uint level); + void disableTicker(); + uint getWarnLevel(double percentage, quint64 timeToEmpty); + void startCountTicker(); + void sendNotify(const QString &body); + void showLowPower(); + void closeLowPower(); + void lockWaitShow(int timeoutMs, bool autoStartAuth); + void playBatterySound(); + + QTimer *m_countTicker = nullptr; + int m_count = 0; + uint m_currentLevel = 0; + + Dtk::Core::DConfig *m_config = nullptr; + bool m_usePercentageForPolicy = true; + quint64 m_timeToEmptyLow = 0; + quint64 m_timeToEmptyDanger = 0; + quint64 m_timeToEmptyCritical = 0; + quint64 m_timeToEmptyAction = 0; + int m_percentageAction = 0; + int m_lowPowerNotifyThreshold = 0; + PowerManager *m_powerManager = nullptr; +}; diff --git a/src/plugin-qt/power/session/misc/dbus/org.deepin.dde.Power1.service b/src/plugin-qt/power/session/misc/dbus/org.deepin.dde.Power1.service new file mode 100644 index 0000000..55ca94f --- /dev/null +++ b/src/plugin-qt/power/session/misc/dbus/org.deepin.dde.Power1.service @@ -0,0 +1,4 @@ +[D-BUS Service] +Name=org.deepin.dde.Power1 +Exec=/usr/bin/deepin-service-manager -n org.deepin.dde.Power1 +SystemdService=org.deepin.dde.Power1.service diff --git a/src/plugin-qt/power/session/misc/org.deepin.dde.Power1.service b/src/plugin-qt/power/session/misc/org.deepin.dde.Power1.service new file mode 100644 index 0000000..46125c5 --- /dev/null +++ b/src/plugin-qt/power/session/misc/org.deepin.dde.Power1.service @@ -0,0 +1,11 @@ +[Unit] +Description=org.deepin.dde.Power1 (Session) +RefuseManualStart=no +RefuseManualStop=no + +[Service] +Type=dbus +BusName=org.deepin.dde.Power1 +ExecStart=/usr/bin/deepin-service-manager -n org.deepin.dde.Power1 +Restart=on-failure +Slice=session.slice diff --git a/src/plugin-qt/power/session/misc/plugin-power-session.json b/src/plugin-qt/power/session/misc/plugin-power-session.json new file mode 100644 index 0000000..354e9a9 --- /dev/null +++ b/src/plugin-qt/power/session/misc/plugin-power-session.json @@ -0,0 +1,12 @@ +{ + "name": "org.deepin.dde.Power1", + "libPath": "libplugin-power-session.so", + "group": "dde", + "startType": "Resident", + "pluginType": "qt", + "policy": [ + { + "path": "/org/deepin/dde/Power1" + } + ] +} diff --git a/src/plugin-qt/power/session/plugin-power-session.cpp b/src/plugin-qt/power/session/plugin-power-session.cpp new file mode 100644 index 0000000..007ae74 --- /dev/null +++ b/src/plugin-qt/power/session/plugin-power-session.cpp @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "powermanager.h" + +#include +#include +#include +#include +#include + +static PowerManager *g_mgr = nullptr; +static QTranslator *g_trans = nullptr; + +extern "C" int DSMRegister(const char *name, void *data) +{ + if (qEnvironmentVariable("XDG_SESSION_TYPE") != QLatin1String("wayland")) + return 0; + + g_trans = new QTranslator; + const QString &path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, + QStringLiteral("plugin-power-session/translations"), + QStandardPaths::LocateDirectory); + if (g_trans->load(QLocale::system(), "plugin-power-session", "_", path)) { + QCoreApplication::installTranslator(g_trans); + } + + auto conn = reinterpret_cast(data); + g_mgr = new PowerManager(conn, QString::fromLatin1(name)); + if (!g_mgr->initialize()) { delete g_mgr; g_mgr = nullptr; return -1; } + return 0; +} + +extern "C" int DSMUnRegister(const char *, void *) +{ + if (g_trans) { + QCoreApplication::removeTranslator(g_trans); + delete g_trans; + g_trans = nullptr; + } + delete g_mgr; g_mgr = nullptr; + return 0; +} diff --git a/src/plugin-qt/power/session/powermanager.cpp b/src/plugin-qt/power/session/powermanager.cpp new file mode 100644 index 0000000..e48e2c1 --- /dev/null +++ b/src/plugin-qt/power/session/powermanager.cpp @@ -0,0 +1,1004 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "powermanager.h" +#include "idle/idlewatcher.h" +#include "idle/idlewatcher_wl.h" +#include "screen/screencontroller.h" +#include "screen/screencontroller_wl.h" +#include "powersaveplan.h" +#include "lidswitchhandler.h" +#include "sleepinhibitor.h" +#include "sessiondbusproxy.h" +#include "../powerconstants.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace PowerDBus; +using namespace PowerDConfig; +using namespace PowerFS; +using namespace Dtk::Core; + +Q_LOGGING_CATEGORY(logPowerSession, "dde.power.session") + +#define DEF_SETTER_PERSIST(T, Suffix, member, signal, dkey) \ + void PowerManager::set##Suffix(T v) { \ + if (m_##member != v) { m_##member = v; Q_EMIT signal(); \ + if (m_config) m_config->setValue(dkey, QVariant::fromValue(v)); } } + +DEF_SETTER_PERSIST(int, LinePowerScreensaverDelay, linePowerScreensaverDelay, linePowerScreensaverDelayChanged, kLinePowerScreensaverDelay) +DEF_SETTER_PERSIST(int, LinePowerScreenBlackDelay, linePowerScreenBlackDelay, linePowerScreenBlackDelayChanged, kLinePowerScreenBlackDelay) +DEF_SETTER_PERSIST(int, LinePowerSleepDelay, linePowerSleepDelay, linePowerSleepDelayChanged, kLinePowerSleepDelay) +DEF_SETTER_PERSIST(int, LinePowerLockDelay, linePowerLockDelay, linePowerLockDelayChanged, kLinePowerLockDelay) +DEF_SETTER_PERSIST(int, BatteryScreensaverDelay, batteryScreensaverDelay, batteryScreensaverDelayChanged, kBatteryScreensaverDelay) +DEF_SETTER_PERSIST(int, BatteryScreenBlackDelay, batteryScreenBlackDelay, batteryScreenBlackDelayChanged, kBatteryScreenBlackDelay) +DEF_SETTER_PERSIST(int, BatterySleepDelay, batterySleepDelay, batterySleepDelayChanged, kBatterySleepDelay) +DEF_SETTER_PERSIST(int, BatteryLockDelay, batteryLockDelay, batteryLockDelayChanged, kBatteryLockDelay) +DEF_SETTER_PERSIST(bool, ScreenBlackLock, screenBlackLock, screenBlackLockChanged, kScreenBlackLock) +DEF_SETTER_PERSIST(bool, SleepLock, sleepLock, sleepLockChanged, kSleepLock) +DEF_SETTER_PERSIST(int, LinePowerLidClosedAction, linePowerLidClosedAction, linePowerLidClosedActionChanged, kLinePowerLidClosedAction) +DEF_SETTER_PERSIST(int, BatteryLidClosedAction, batteryLidClosedAction, batteryLidClosedActionChanged, kBatteryLidClosedAction) +DEF_SETTER_PERSIST(int, LinePowerPressPowerBtnAction, linePowerPressPowerBtnAction, linePowerPressPowerBtnActionChanged, kLinePowerPressPowerButton) +DEF_SETTER_PERSIST(int, BatteryPressPowerBtnAction, batteryPressPowerBtnAction, batteryPressPowerBtnActionChanged, kBatteryPressPowerButton) +DEF_SETTER_PERSIST(bool, LowPowerNotifyEnable, lowPowerNotifyEnable, lowPowerNotifyEnableChanged, kLowPowerNotifyEnable) +DEF_SETTER_PERSIST(int, LowPowerNotifyThreshold, lowPowerNotifyThreshold, lowPowerNotifyThresholdChanged, kLowPowerNotifyThreshold) +DEF_SETTER_PERSIST(int, LowPowerAutoSleepThreshold, lowPowerAutoSleepThreshold, lowPowerAutoSleepThresholdChanged, kPercentageAction) +DEF_SETTER_PERSIST(int, LowPowerAction, lowPowerAction, lowPowerActionChanged, kLowPowerAction) +DEF_SETTER_PERSIST(bool, AmbientLightAdjustBrightness, ambientLightAdjustBrightness, ambientLightAdjustBrightnessChanged, kAmbientLightAdjustBrightness) +DEF_SETTER_PERSIST(bool, ScheduledShutdownState, scheduledShutdownState, scheduledShutdownStateChanged, kScheduledShutdownState) +DEF_SETTER_PERSIST(int, ShutdownRepetition, shutdownRepetition, shutdownRepetitionChanged, kShutdownRepetition) + +PowerManager::PowerManager(QDBusConnection *conn, const QString &svc, QObject *parent) + : QObject(parent), m_conn(conn) +{ + Q_UNUSED(svc); + m_useWayland = (qEnvironmentVariable("XDG_SESSION_TYPE") == QLatin1String("wayland")); + + qRegisterMetaType("BatteryIsPresentMap"); + qRegisterMetaType("BatteryPercentageMap"); + qRegisterMetaType("BatteryStateMap"); + + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + + QDBusMetaType::registerCustomType(QMetaType::fromType(), "a{sb}"); + QDBusMetaType::registerCustomType(QMetaType::fromType(), "a{sd}"); + QDBusMetaType::registerCustomType(QMetaType::fromType(), "a{su}"); +} + +PowerManager::~PowerManager() +{ + if (m_sleepInhibitor) m_sleepInhibitor->unblock(); +} + +bool PowerManager::initialize() +{ + m_proxy = new SessionDBusProxy(this); + m_idleWatcher = createIdleWatcher(); + m_screenCtrl = createScreenController(); + + initDConfig(); + + m_powerSavePlan = new PowerSavePlan(this); + m_lidSwitch = new LidSwitchHandler(this); + m_sleepInhibitor = new SleepInhibitor(this); + m_lowPowerMgr = new LowPowerManager(this, this); + m_lowPowerMgr->initConfig(m_config); + + if (m_idleWatcher) { + connect(m_idleWatcher, &IdleWatcher::idled, m_powerSavePlan, &PowerSavePlan::HandleIdleOn); + connect(m_idleWatcher, &IdleWatcher::resumed, m_powerSavePlan, &PowerSavePlan::HandleIdleOff); + } + connect(this, &PowerManager::onBatteryChanged, this, [this]() { m_powerSavePlan->Reset(); }); + + connect(this, &PowerManager::linePowerScreensaverDelayChanged, this, &PowerManager::onLinePowerDelayChanged); + connect(this, &PowerManager::linePowerScreenBlackDelayChanged, this, &PowerManager::onLinePowerDelayChanged); + connect(this, &PowerManager::linePowerLockDelayChanged, this, &PowerManager::onLinePowerDelayChanged); + connect(this, &PowerManager::linePowerSleepDelayChanged, this, &PowerManager::onLinePowerDelayChanged); + connect(this, &PowerManager::batteryScreensaverDelayChanged, this, &PowerManager::onBatteryDelayChanged); + connect(this, &PowerManager::batteryScreenBlackDelayChanged, this, &PowerManager::onBatteryDelayChanged); + connect(this, &PowerManager::batteryLockDelayChanged, this, &PowerManager::onBatteryDelayChanged); + connect(this, &PowerManager::batterySleepDelayChanged, this, &PowerManager::onBatteryDelayChanged); + + initBatteryWatcher(); + initSleepWatcher(); + initLogindInhibit(); + initScheduledShutdown(); + + m_powerSavePlan->Start(); + + if (!m_conn->registerObject(kPath, this, + QDBusConnection::ExportAllSlots | + QDBusConnection::ExportAllSignals | + QDBusConnection::ExportAllProperties)) { + qWarning(logPowerSession) << "Failed to register D-Bus object:" << m_conn->lastError().message(); + return false; + } + + // ExportAllProperties 只提供 Get/Set/GetAll 访问,不会自动把 NOTIFY 信号 + // 转成 org.freedesktop.DBus.Properties.PropertiesChanged。 + // 用 notifyPropertyChanged 槽函数统一桥接所有 Q_PROPERTY(NOTIFY) 到标准 D-Bus 属性变更通知。 + const QMetaObject *mo = metaObject(); + int slotIdx = mo->indexOfSlot("notifyPropertyChanged()"); + if (slotIdx >= 0) { + QMetaMethod slot = mo->method(slotIdx); + for (int i = mo->propertyOffset(); i < mo->propertyCount(); ++i) { + QMetaProperty prop = mo->property(i); + if (!prop.hasNotifySignal()) + continue; + connect(this, prop.notifySignal(), this, slot); + } + } + + return true; +} + +void PowerManager::onLinePowerDelayChanged() +{ + if (!m_onBattery) { + m_powerSavePlan->OnLinePower(); + } +} + +void PowerManager::onBatteryDelayChanged() +{ + if (m_onBattery) { + m_powerSavePlan->OnBattery(); + } +} + +void PowerManager::initBatteryWatcher() +{ + connect(m_proxy, &SessionDBusProxy::OnBatteryChanged, this, &PowerManager::handleOnBatteryChanged); + connect(m_proxy, &SessionDBusProxy::HasLidSwitchChanged, this, &PowerManager::handleHasLidSwitchChanged); + connect(m_proxy, &SessionDBusProxy::HasBatteryChanged, this, &PowerManager::handleHasBatteryChanged); + connect(m_proxy, &SessionDBusProxy::BatteryPercentageChanged, this, &PowerManager::handleBatteryPercentageChanged); + connect(m_proxy, &SessionDBusProxy::BatteryStatusChanged, this, &PowerManager::handleBatteryStatusChanged); + connect(m_proxy, &SessionDBusProxy::BatteryTimeToEmptyChanged, this, &PowerManager::handleBatteryTimeToEmptyChanged); + connect(m_proxy, &SessionDBusProxy::IsHighPerformanceSupportedChanged, this, &PowerManager::handleIsHighPerformanceSupportedChanged); + connect(m_proxy, &SessionDBusProxy::PowerSavingModeEnabledChanged, this, &PowerManager::handlePowerSavingModeEnabledChanged); + connect(m_proxy, &SessionDBusProxy::PowerSavingModeBrightnessDropPercentChanged, this, &PowerManager::handlePowerSavingModeBrightnessDropPercentChanged); + + QTimer::singleShot(1000, this, [this]() { + refreshBatteryInfo(); + // 拉取 PSM 初始值 (因为 system 端 registerObject 在 DConfig init 之后, + // 初始 NOTIFY 信号丢失, DDBusInterface 的 GetAll 也不可靠) + if (m_powerSavePlan) { + m_powerSavePlan->onPowerSavingModeEnabledChanged(m_proxy->powerSavingModeEnabled()); + m_powerSavePlan->onBrightnessDropPercentChanged(m_proxy->powerSavingModeBrightnessDropPercent()); + } + }); +} + +void PowerManager::initSleepWatcher() +{ + if (m_sleepInhibitor) { + connect(m_sleepInhibitor, &SleepInhibitor::aboutToSleep, + this, [this]() { handleBeforeSleep(true); }); + connect(m_sleepInhibitor, &SleepInhibitor::wokeUp, + this, &PowerManager::handleWakeup); + } +} + +void PowerManager::refreshBatteryInfo() +{ + bool hasBattery = m_proxy->hasBattery(); + if (hasBattery) { + m_batteryIsPresent["Display"] = true; + m_batteryPercentage["Display"] = m_proxy->batteryPercentage(); + m_batteryState["Display"] = m_proxy->batteryStatus(); + m_batteryTimeToEmpty = m_proxy->batteryTimeToEmpty(); + Q_EMIT batteryIsPresentChanged(); + Q_EMIT batteryPercentageChanged(); + Q_EMIT batteryStateChanged(); + } + + bool onBattery = m_proxy->onBattery(); + if (onBattery != m_onBattery) { + m_onBattery = onBattery; + Q_EMIT onBatteryChanged(); + } + + bool hasLid = m_proxy->hasLidSwitch(); + if (hasLid != m_lidIsPresent) { + m_lidIsPresent = hasLid; + Q_EMIT lidIsPresentChanged(); + } + + bool hps = m_proxy->isHighPerformanceSupported(); + if (m_config) { + bool e = m_config->value(PowerDConfig::kHighPerformanceEnabled).toBool(); + hps = hps && e; + } + if (hps != m_isHighPerformanceSupported) { + m_isHighPerformanceSupported = hps; + Q_EMIT isHighPerformanceSupportedChanged(); + } +} + +void PowerManager::handleOnBatteryChanged(bool value) +{ + if (value != m_onBattery) { + m_onBattery = value; + Q_EMIT onBatteryChanged(); + } +} + +void PowerManager::handleHasLidSwitchChanged(bool value) +{ + if (value != m_lidIsPresent) { + m_lidIsPresent = value; + Q_EMIT lidIsPresentChanged(); + } +} + +void PowerManager::handleHasBatteryChanged(bool value) +{ + m_batteryIsPresent["Display"] = value; + Q_EMIT batteryIsPresentChanged(); +} + +void PowerManager::handleBatteryPercentageChanged(double value) +{ + m_batteryPercentage["Display"] = value; + Q_EMIT batteryPercentageChanged(); +} + +void PowerManager::handleBatteryStatusChanged(uint value) +{ + m_batteryState["Display"] = value; + Q_EMIT batteryStateChanged(); +} + +void PowerManager::handleBatteryTimeToEmptyChanged(quint64 value) +{ + m_batteryTimeToEmpty = value; +} + +void PowerManager::handleIsHighPerformanceSupportedChanged(bool value) +{ + if (m_config) { + bool e = m_config->value(PowerDConfig::kHighPerformanceEnabled).toBool(); + value = value && e; + } + if (value != m_isHighPerformanceSupported) { + m_isHighPerformanceSupported = value; + Q_EMIT isHighPerformanceSupportedChanged(); + } +} + +void PowerManager::handlePowerSavingModeEnabledChanged(bool enabled) +{ + if (m_powerSavePlan) + m_powerSavePlan->onPowerSavingModeEnabledChanged(enabled); +} + +void PowerManager::handlePowerSavingModeBrightnessDropPercentChanged(uint value) +{ + if (m_powerSavePlan) + m_powerSavePlan->onBrightnessDropPercentChanged(value); +} + +void PowerManager::initLogindInhibit() +{ + auto fd = m_proxy->inhibit( + "handle-power-key:handle-lid-switch:handle-suspend-key", + PowerDBus::kService, "handling key press and lid switch close", "block"); + m_inhibitFd = fd.isValid() ? dup(fd.fileDescriptor()) : -1; + + connect(m_proxy, &SessionDBusProxy::login1OwnerChanged, + this, &PowerManager::onLogin1OwnerChanged); +} + +void PowerManager::onLogin1OwnerChanged(const QString &name, const QString &, + const QString &newOwner) +{ + if (name != QLatin1String(PowerDBus::kLogin1Service) || newOwner.isEmpty()) return; + if (m_inhibitFd >= 0) { ::close(m_inhibitFd); m_inhibitFd = -1; } + initLogindInhibit(); +} + +void PowerManager::notifyPropertyChanged() +{ + int sigIdx = senderSignalIndex(); + if (sigIdx < 0) + return; + + const QMetaObject *mo = metaObject(); + for (int i = mo->propertyOffset(); i < mo->propertyCount(); ++i) { + QMetaProperty prop = mo->property(i); + if (!prop.hasNotifySignal() || !prop.isReadable()) + continue; + if (prop.notifySignal().methodIndex() != sigIdx) + continue; + + QDBusMessage msg = QDBusMessage::createSignal( + kPath, + QStringLiteral("org.freedesktop.DBus.Properties"), + QStringLiteral("PropertiesChanged")); + msg << QLatin1String(kInterface); + QVariantMap changed; + changed[QString::fromLatin1(prop.name())] = prop.read(this); + msg << changed; + msg << QStringList(); + m_conn->send(msg); + return; + } +} + +static QString currentSessionId() +{ + QString sid = qEnvironmentVariable("XDG_SESSION_ID"); + if (sid.isEmpty()) sid = QString::number(getpid()); + return sid; +} + +void PowerManager::handleBeforeSleep(bool) +{ + qDebug(logPowerSession) << "System is going to sleep, prepare suspend state:" << m_prepareSuspendState; + m_prepareSuspendState = PS_Sleeping; + setBlackScreenActive(true); + if (m_useWayland && m_sleepLock) { + m_proxy->lockSession(currentSessionId()); + } +} + +void PowerManager::handleWakeup() +{ + qDebug(logPowerSession) << "System woke up"; + m_prepareSuspendState = PS_Resume; + m_delayInActive = true; + QTimer::singleShot(m_delayWakeupInterval * 1000, this, [this]() { + m_delayInActive = false; + setBlackScreenActive(false); + }); + setDPMSModeOn(); + if (m_powerSavePlan) m_powerSavePlan->HandleIdleOff(); + if (m_scheduledShutdownState) scheduledShutdown(SchedInit); +} + +void PowerManager::Reset() +{ + if (!m_config) return; + static const char *keys[] = { + kLinePowerScreenBlackDelay, kLinePowerSleepDelay, kLinePowerLockDelay, + kLinePowerLidClosedAction, kLinePowerPressPowerButton, + kBatteryScreenBlackDelay, kBatterySleepDelay, kBatteryLockDelay, + kBatteryLidClosedAction, kBatteryPressPowerButton, + kScreenBlackLock, kSleepLock, kPowerButtonPressedExec, + kLowPowerNotifyEnable, kLowPowerNotifyThreshold, + kPercentageAction, kPowerSavingModeBrightnessDropPercent, + }; + for (auto k : keys) { + if (m_config->value(k).isValid()) { + m_config->reset(k); + } + } + if (m_powerSavePlan) m_powerSavePlan->Reset(); +} + +void PowerManager::SetPrepareSuspend(int state) +{ + m_prepareSuspendState = static_cast(state); +} + +void PowerManager::TurnOffScreen() +{ + setDPMSModeOff(); +} + +void PowerManager::TurnOnScreen() +{ + setDPMSModeOn(); +} + +bool PowerManager::shouldIgnoreIdleOn() const +{ + return m_prepareSuspendState > PS_Finish; +} + +void PowerManager::doSuspend() +{ + qInfo(logPowerSession) << "Requesting suspend, canSuspend=" << canSuspend(); + if (!canSuspend()) + return; + + if (!m_useWayland) { + m_proxy->requestSuspendByFront(); + return; + } else { + m_proxy->requestSuspend(); + } +} + +void PowerManager::doSuspendByFront() { + qInfo(logPowerSession) << "Requesting suspend by front, canSuspend=" << canSuspend(); + if (canSuspend()) { + m_proxy->requestSuspendByFront(); + } +} + +void PowerManager::doShutdown() { + qInfo(logPowerSession) << "Requesting shutdown"; + m_proxy->requestShutdown(); +} + +void PowerManager::doHibernate() { + qInfo(logPowerSession) << "Requesting hibernate, canHibernate=" << canHibernate(); + if (canHibernate()) { + m_proxy->requestHibernate(); + } +} + +void PowerManager::doTurnOffScreen() +{ + qInfo(logPowerSession) << "Turning off screen"; + if (m_screenBlackLock) { + doLock(true); + } + QTimer::singleShot(500, this, [this]() { setDPMSModeOff(); }); +} + +void PowerManager::doLock(bool autoStartAuth) +{ + qInfo(logPowerSession) << "Locking session"; + if (m_useWayland) { + m_proxy->lockSession(currentSessionId()); + return; + } + m_proxy->showLockAuth(autoStartAuth); +} + +void PowerManager::setDPMSModeOn() { + qInfo(logPowerSession) << "Setting DPMS mode to on"; + if (m_screenCtrl) { + m_screenCtrl->setAllModes(ScreenController::On); + } +} + +void PowerManager::setDPMSModeOff() +{ + qInfo(logPowerSession) << "Setting DPMS mode to off"; + if (m_screenCtrl) { + m_screenCtrl->setAllModes(ScreenController::Off); + } + + const auto dpmsPath = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation) + + QStringLiteral("/dpms-state"); + QFile f(dpmsPath); + if (f.open(QIODevice::WriteOnly)) { + f.write("1"); + f.close(); + } +} + +void PowerManager::setBlackScreenActive(bool active) +{ + if (m_delayInActive || !m_sleepLock) { + return; + } + + m_proxy->setBlackScreenActive(active); +} + +bool PowerManager::canSuspend() const { + return m_proxy->canSuspend(); +} + +bool PowerManager::canHibernate() const { + return m_proxy->canHibernate(); +} + +void PowerManager::sendNotify(const QString &s, const QString &b) +{ + if (!m_lowPowerNotifyEnable) return; + m_proxy->notify(0, "dde-control-center", "notification-battery-low", + s, b, QStringList(), QVariantMap(), -1); +} + +void PowerManager::setDisplayBrightness(const QMap &t) +{ + for (auto it = t.begin(); it != t.end(); ++it) { + m_proxy->setBrightness(it.key(), it.value()); + } +} + +void PowerManager::setAndSaveDisplayBrightness(const QMap &t) +{ + for (auto it = t.begin(); it != t.end(); ++it) { + m_proxy->setAndSaveBrightness(it.key(), it.value()); + } +} + +// TODO: x11 +IdleWatcher *PowerManager::createIdleWatcher() +{ + return m_useWayland ? new WaylandIdleWatcher(this) : nullptr; +} + +// TODO x11 +ScreenController *PowerManager::createScreenController() +{ + return m_useWayland ? new WaylandScreenController(this) : nullptr; +} + +void PowerManager::recalculateScheduledShutdown() +{ + qDebug(logPowerSession) << "Recalculating scheduled shutdown, current nextShutdownTime=" << m_nextShutdownTime; + m_nextShutdownTime = getNextShutdownTime(0); + + if (m_config) + m_config->setValue(PowerDConfig::kNextShutdownTime, m_nextShutdownTime); + scheduledShutdown(SchedInit); +} + +void PowerManager::initDConfig() +{ + m_config = DConfig::create(PowerDConfig::kAppId, PowerDConfig::kPowerName, "", this); + if (!m_config) + return; + + using Setter = std::function; + using Hook = std::function; + struct Binding { const char *key; Setter apply; Hook onChange = {}; }; + + std::vector bindings = { + // ── Line power delays ── + { PowerDConfig::kLinePowerScreensaverDelay, + [this](const QVariant &v) { setLinePowerScreensaverDelay(v.toInt()); } }, + { PowerDConfig::kLinePowerScreenBlackDelay, + [this](const QVariant &v) { setLinePowerScreenBlackDelay(v.toInt()); } }, + { PowerDConfig::kLinePowerSleepDelay, + [this](const QVariant &v) { setLinePowerSleepDelay(v.toInt()); } }, + { PowerDConfig::kLinePowerLockDelay, + [this](const QVariant &v) { setLinePowerLockDelay(v.toInt()); } }, + + // ── Battery delays ── + { PowerDConfig::kBatteryScreensaverDelay, + [this](const QVariant &v) { setBatteryScreensaverDelay(v.toInt()); } }, + { PowerDConfig::kBatteryScreenBlackDelay, + [this](const QVariant &v) { setBatteryScreenBlackDelay(v.toInt()); } }, + { PowerDConfig::kBatterySleepDelay, + [this](const QVariant &v) { setBatterySleepDelay(v.toInt()); } }, + { PowerDConfig::kBatteryLockDelay, + [this](const QVariant &v) { setBatteryLockDelay(v.toInt()); } }, + + // ── Locks ── + { PowerDConfig::kScreenBlackLock, + [this](const QVariant &v) { setScreenBlackLock(v.toBool()); } }, + { PowerDConfig::kSleepLock, + [this](const QVariant &v) { setSleepLock(v.toBool()); } }, + + // ── Actions ── + { PowerDConfig::kLinePowerLidClosedAction, + [this](const QVariant &v) { setLinePowerLidClosedAction(v.toInt()); } }, + { PowerDConfig::kBatteryLidClosedAction, + [this](const QVariant &v) { setBatteryLidClosedAction(v.toInt()); } }, + { PowerDConfig::kLinePowerPressPowerButton, + [this](const QVariant &v) { setLinePowerPressPowerBtnAction(v.toInt()); } }, + { PowerDConfig::kBatteryPressPowerButton, + [this](const QVariant &v) { setBatteryPressPowerBtnAction(v.toInt()); } }, + + // ── Low power ── + { PowerDConfig::kLowPowerNotifyEnable, + [this](const QVariant &v) { setLowPowerNotifyEnable(v.toBool()); } }, + { PowerDConfig::kLowPowerNotifyThreshold, + [this](const QVariant &v) { setLowPowerNotifyThreshold(v.toInt()); } }, + { PowerDConfig::kPercentageAction, + [this](const QVariant &v) { setLowPowerAutoSleepThreshold(v.toInt()); } }, + { PowerDConfig::kLowPowerAction, + [this](const QVariant &v) { setLowPowerAction(v.toInt()); } }, + { PowerDConfig::kAmbientLightAdjustBrightness, + [this](const QVariant &v) { setAmbientLightAdjustBrightness(v.toBool()); } }, + + // ── Scheduled shutdown ── + { PowerDConfig::kScheduledShutdownState, + [this](const QVariant &v) { setScheduledShutdownState(v.toBool()); }, + [this]() { + if (m_scheduledShutdownState) { + recalculateScheduledShutdown(); + } else { + scheduledShutdown(SchedCancel); + } + } + }, + { PowerDConfig::kShutdownTime, + [this](const QVariant &v) { setShutdownTime(v.toString()); }, + [this]() { + if (m_scheduledShutdownState) { + recalculateScheduledShutdown(); + } + } + }, + { PowerDConfig::kShutdownRepetition, + [this](const QVariant &v) { setShutdownRepetition(v.toInt()); }, + [this]() { + if (m_scheduledShutdownState) { + recalculateScheduledShutdown(); + } + } + }, + { PowerDConfig::kCustomShutdownWeekDays, + [this](const QVariant &v) { setCustomShutdownWeekDays(v.toByteArray()); }, + [this]() { + if (m_scheduledShutdownState) { + recalculateScheduledShutdown(); + } + } + }, + { PowerDConfig::kShutdownCountdown, + [this](const QVariant &v) { m_shutdownCountdown = v.toInt(); } }, + { PowerDConfig::kNextShutdownTime, + [this](const QVariant &v) { m_nextShutdownTime = v.toLongLong(); } }, + }; + + for (const auto &b : bindings) + b.apply(m_config->value(QLatin1String(b.key))); + + connect(m_config, &DConfig::valueChanged, this, + [this, bindings = std::move(bindings)](const QString &k) { + QVariant v = m_config->value(k); + qDebug(logPowerSession) << "DConfig value changed:" << k << "=" << v; + + for (const auto &b : bindings) { + if (k == QLatin1String(b.key)) { + b.apply(v); + if (b.onChange) + b.onChange(); + return; + } + } + }); + + qInfo(logPowerSession) << "initial load: ss=" << m_linePowerScreensaverDelay + << " black=" << m_linePowerScreenBlackDelay + << " sleep=" << m_linePowerSleepDelay + << " lock=" << m_linePowerLockDelay; +} + +void PowerManager::setShutdownTime(const QString &v) +{ + if (m_shutdownTime != v) { + m_shutdownTime = v; + Q_EMIT shutdownTimeChanged(); + if (m_config) m_config->setValue(kShutdownTime, v); + } +} + +void PowerManager::setCustomShutdownWeekDays(const QByteArray &v) +{ + if (m_customShutdownWeekDays != v) { + m_customShutdownWeekDays = v; Q_EMIT customShutdownWeekDaysChanged(); + if (m_config) m_config->setValue(kCustomShutdownWeekDays, QVariant(v)); + } +} + +void PowerManager::closeNotify() +{ + if (m_shutdownNotifyId == 0) return; + m_proxy->closeNotification(m_shutdownNotifyId); + m_shutdownNotifyId = 0; +} + +void PowerManager::onNotifyActionInvoked(uint id, const QString &actionKey) +{ + qDebug(logPowerSession) << "Notification action invoked: id=" << id << " key=" << actionKey; + if (id != m_shutdownNotifyId) return; + int nextStatus; + if (actionKey == "cancel") { + nextStatus = SchedCancel; + } else if (actionKey == "shutdown") { + nextStatus = SchedShutdown; + } else { + nextStatus = SchedCancel; + } + scheduledShutdown(nextStatus); +} + +void PowerManager::initScheduledShutdown() +{ + qInfo(logPowerSession) << "Scheduled shutdown init: state=" << m_scheduledShutdownState + << " time=" << m_shutdownTime + << " repetition=" << m_shutdownRepetition + << " countdown=" << m_shutdownCountdown; + m_shutdownTimer = new QTimer(this); + m_shutdownTimer->setSingleShot(true); + m_countdownTimer = new QTimer(this); + + connect(m_proxy, &SessionDBusProxy::notifyActionInvoked, + this, &PowerManager::onNotifyActionInvoked); + connect(m_proxy, &SessionDBusProxy::timeUpdate, + this, &PowerManager::onSystemTimeChanged); + connect(m_proxy, &SessionDBusProxy::SessionActiveChanged, + this, &PowerManager::onSessionActiveChanged); + + if (m_scheduledShutdownState) { + if (m_nextShutdownTime == 0) { + m_nextShutdownTime = getNextShutdownTime(0); + if (m_config) m_config->setValue(kNextShutdownTime, m_nextShutdownTime); + } + scheduledShutdown(SchedInit); + } +} + +void PowerManager::onSystemTimeChanged() +{ + if (!m_scheduledShutdownState) + return; + + m_nextShutdownTime = getNextShutdownTime(0); + if (m_config) m_config->setValue(kNextShutdownTime, m_nextShutdownTime); + scheduledShutdown(SchedInit); +} + +void PowerManager::onSessionActiveChanged() +{ + if (!m_scheduledShutdownState) + return; + + scheduledShutdown(SchedInit); +} + +void PowerManager::doAutoShutdown() +{ + qInfo(logPowerSession) << "Performing auto shutdown"; + closeNotify(); + m_proxy->requestShutdown(); +} + +void PowerManager::shutdownCountdownNotify(int count, bool playSound) +{ + QString body = tr("The system will shut down automatically after %1 s").arg(count); + QString title = tr("Scheduled Shutdown"); + QStringList actions = {"cancel", tr("Cancel"), "shutdown", tr("Shut down")}; + QVariantMap hints = { + {"x-deepin-PlaySound", playSound}, + {"urgency", 2}, + {"x-deepin-ShowInNotifyCenter", false}, + {"x-deepin-ClickToDisappear", false}, + {"x-deepin-DisappearAfterLock", false}, + }; + + m_shutdownNotifyId = m_proxy->notify(m_shutdownNotifyId, "dde-control-center", + "preferences-system", + title, body, actions, hints, -1); +} + +void PowerManager::scheduledShutdown(int state) +{ + qInfo(logPowerSession) << "Scheduled shutdown state change: pre=" << m_shutdownStatus + << " next=" << state + << " nextTime=" << QDateTime::fromSecsSinceEpoch(m_nextShutdownTime).toString("yyyy-MM-dd hh:mm:ss") + << " rep=" << m_shutdownRepetition + << " cnt=" << m_shutdownCountdown; + + if (!m_shutdownTimer) return; + if (m_shutdownTimer->isActive()) m_shutdownTimer->stop(); + if (m_countdownTimer && m_countdownTimer->isActive()) m_countdownTimer->stop(); + + // Check session active status (match Go: !isSessionActive || m.WarnLevel == WarnLevelAction) + bool isSessionActive = m_proxy->sessionActive(); + + if (!isSessionActive || m_warnLevel == LowPowerManager::Action) { + closeNotify(); + return; + } + + if (!m_scheduledShutdownState && state == SchedInit) { + return; + } + + if (state != SchedInit && state == m_shutdownStatus) { + return; + } + + if (m_nextShutdownTime == 0) { + return; + } + + QDateTime next = QDateTime::fromSecsSinceEpoch(m_nextShutdownTime); + QDateTime now = QDateTime::currentDateTime(); + + switch (state) { + case SchedInit: { + if (m_shutdownStatus >= SchedCountdowning) { + closeNotify(); + } + if (!m_scheduledShutdownState) return; + m_shutdownStatus = SchedInit; + + qint64 diffMins = now.secsTo(next) / 60; + int nextStatus; + if (diffMins < 0) { + nextStatus = SchedTimeout; + } else if (diffMins == 0) { + nextStatus = SchedCountdowning; + } else if (now.secsTo(next) <= m_shutdownCountdown) { + nextStatus = SchedCountdowning; + } else { + nextStatus = SchedWaitingToNotify; + } + scheduledShutdown(nextStatus); + break; + } + case SchedWaitingToNotify: { + m_shutdownStatus = SchedWaitingToNotify; + QDateTime notifyAt = next.addSecs(-m_shutdownCountdown); + qint64 msToNotify = now.msecsTo(notifyAt); + if (msToNotify < 0) msToNotify = 0; + m_shutdownTimer->start(msToNotify); + disconnect(m_shutdownTimer, &QTimer::timeout, this, nullptr); + connect(m_shutdownTimer, &QTimer::timeout, this, [this]() { + scheduledShutdown(SchedCountdowning); + }); + break; + } + case SchedCountdowning: { + m_shutdownStatus = SchedCountdowning; + int remaining = m_shutdownCountdown + 1; + shutdownCountdownNotify(remaining, true); + + // Use member m_countdownTimer (matching Go: m.shutdownTimer in countdown goroutine) + // The timer is stopped at the top of this function on any state transition + disconnect(m_countdownTimer, &QTimer::timeout, this, nullptr); + connect(m_countdownTimer, &QTimer::timeout, this, [this, remaining]() mutable { + remaining--; + if (remaining <= 0) { + m_countdownTimer->stop(); + scheduledShutdown(SchedShutdown); + } else { + shutdownCountdownNotify(remaining, false); + } + }); + m_countdownTimer->start(1000); + break; + } + case SchedShutdown: + case SchedCancel: + case SchedTimeout: { + if (state == SchedTimeout) { + closeNotify(); + } + m_shutdownStatus = state; + + if (m_shutdownRepetition == RepOnce) { + m_scheduledShutdownState = false; + m_nextShutdownTime = 0; + if (m_config) { + m_config->setValue(kNextShutdownTime, 0); + m_config->setValue(kScheduledShutdownState, false); + } + Q_EMIT scheduledShutdownStateChanged(); + } else { + m_nextShutdownTime = getNextShutdownTime(m_nextShutdownTime); + if (m_config) m_config->setValue(kNextShutdownTime, m_nextShutdownTime); + + qint64 t = now.secsTo(next) / 60; + if (t > 0) { + QTimer::singleShot(10000, this, [this]() { scheduledShutdown(SchedInit); }); + } else { + QTimer::singleShot(m_shutdownCountdown * 1000, this, [this]() { scheduledShutdown(SchedInit); }); + } + } + + if (state == SchedShutdown) { + doAutoShutdown(); + } + break; + } + } +} + +bool PowerManager::isWorkday(const QDateTime &date) const +{ + int year = date.date().year(); + int month = date.date().month(); + QString reply = m_proxy->getFestivalMonth(year, month); + if (reply.isEmpty()) { + int dow = date.date().dayOfWeek(); + return dow != Qt::Saturday && dow != Qt::Sunday; + } + + QJsonDocument doc = QJsonDocument::fromJson(reply.toUtf8()); + if (!doc.isArray() || doc.array().isEmpty()) { + int dow = date.date().dayOfWeek(); + return dow != Qt::Saturday && dow != Qt::Sunday; + } + QJsonObject root = doc.array().first().toObject(); + QJsonArray list = root["List"].toArray(); + if (list.isEmpty()) { + int dow = date.date().dayOfWeek(); + return dow != Qt::Saturday && dow != Qt::Sunday; + } + + QString dateStr1 = date.toString("yyyy-M-d"); + QString dateStr2 = date.toString("yyyy-MM-dd"); + for (const auto &item : list) { + QJsonObject obj = item.toObject(); + if (obj["Date"].toString() == dateStr1 || obj["Date"].toString() == dateStr2) { + return obj["Status"].toInt() == 2; + } + } + int dow = date.date().dayOfWeek(); + return dow != Qt::Saturday && dow != Qt::Sunday; +} + +bool PowerManager::isCustomDay(const QDateTime &date) const +{ + int dow = date.date().dayOfWeek(); + for (int i = 0; i < m_customShutdownWeekDays.size(); ++i) { + if (m_customShutdownWeekDays[i] - '0' == dow) + return true; + } + return false; +} + +qint64 PowerManager::getNextShutdownTime(qint64 baseTime) const +{ + auto getNextTime = [this](qint64 bt) -> QDateTime { + QDateTime baseDate = QDateTime::fromSecsSinceEpoch(bt); + QDateTime now = QDateTime::currentDateTime(); + QTime targetTime = QTime::fromString(m_shutdownTime, "hh:mm"); + QDateTime target = QDateTime(now.date(), targetTime); + + if (now.secsTo(target) / 60 < 0) { // 已经过去时间了 + target = target.addDays(1); + } + + if (baseDate.secsTo(target) / 60 <= 0) { // + target = target.addDays(1); + } + return target; + }; + + QDateTime targetTime; + switch (m_shutdownRepetition) { + case RepOnce: + case RepEveryday: + targetTime = getNextTime(baseTime); + break; + case RepWorkdays: { + targetTime = getNextTime(baseTime); + for (int i = 0; i <= 366; ++i) { + if (i == 366) return 0; + if (isWorkday(targetTime)) break; + targetTime = targetTime.addDays(1); + } + break; + } + case RepCustom: { + targetTime = getNextTime(baseTime); + for (int i = 0; i <= 7; ++i) { + if (i == 7) return 0; + if (isCustomDay(targetTime)) break; + targetTime = targetTime.addDays(1); + } + break; + } + default: + targetTime = getNextTime(baseTime); + break; + } + + return targetTime.toSecsSinceEpoch(); +} diff --git a/src/plugin-qt/power/session/powermanager.h b/src/plugin-qt/power/session/powermanager.h new file mode 100644 index 0000000..76ca740 --- /dev/null +++ b/src/plugin-qt/power/session/powermanager.h @@ -0,0 +1,342 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include "lowpowermanager.h" + +#include +#include +#include + +using BatteryPercentageMap = QMap; +using BatteryStateMap = QMap; +using BatteryIsPresentMap = QMap; + +Q_DECLARE_METATYPE(BatteryPercentageMap) +Q_DECLARE_METATYPE(BatteryStateMap) +Q_DECLARE_METATYPE(BatteryIsPresentMap) + +class QDBusConnection; +class QTimer; +class IdleWatcher; +class ScreenController; +class PowerHelper; +class PowerSavePlan; +class LidSwitchHandler; +class SleepInhibitor; +class SessionDBusProxy; + +enum SchedState { + SchedInit = 0, + SchedWaitingToNotify = 1, + SchedCountdowning = 2, + SchedShutdown = 3, + SchedCancel = 4, + SchedTimeout = 5, +}; + +enum RepetitionMode { + RepOnce = 0, + RepEveryday = 1, + RepWorkdays = 2, + RepCustom = 3, +}; + +enum PrepareSuspendState { + PS_Normal = 0, // 正常/空闲 + PS_LidClose = 3, // 合盖进入挂起流程 + PS_Finish = 4, // 阈值:> PS_Finish 表示处于休眠/唤醒过渡中,应忽略 idle 事件 + PS_Resume = 5, // 从挂起中唤醒 / 开盖恢复 + PS_Sleeping = 6, // 即将进入休眠 (handleBeforeSleep) +}; + +enum PowerAction { + PA_Shutdown = 0, // 关机 + PA_Suspend = 1, // 休眠 + PA_Hibernate = 2, // 休眠 + PA_TurnOffScreen = 3, // 关闭显示器 + PA_Lock = 4, // 锁屏 + PA_DoNothing = 5, // 无操作 +}; + +class PowerManager : public QObject +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.deepin.dde.Power1") + + Q_PROPERTY(bool OnBattery READ onBattery NOTIFY onBatteryChanged) + Q_PROPERTY(bool LidIsPresent READ lidIsPresent NOTIFY lidIsPresentChanged) + Q_PROPERTY(BatteryIsPresentMap BatteryIsPresent READ batteryIsPresent NOTIFY batteryIsPresentChanged) + Q_PROPERTY(BatteryPercentageMap BatteryPercentage READ batteryPercentage NOTIFY batteryPercentageChanged) + Q_PROPERTY(BatteryStateMap BatteryState READ batteryState NOTIFY batteryStateChanged) + Q_PROPERTY(bool HasAmbientLightSensor READ hasAmbientLightSensor NOTIFY hasAmbientLightSensorChanged) + Q_PROPERTY(uint WarnLevel READ warnLevel NOTIFY warnLevelChanged) + Q_PROPERTY(bool IsHighPerformanceSupported READ isHighPerformanceSupported NOTIFY isHighPerformanceSupportedChanged) + + Q_PROPERTY(int LinePowerScreensaverDelay READ linePowerScreensaverDelay WRITE setLinePowerScreensaverDelay NOTIFY linePowerScreensaverDelayChanged) + Q_PROPERTY(int LinePowerScreenBlackDelay READ linePowerScreenBlackDelay WRITE setLinePowerScreenBlackDelay NOTIFY linePowerScreenBlackDelayChanged) + Q_PROPERTY(int LinePowerSleepDelay READ linePowerSleepDelay WRITE setLinePowerSleepDelay NOTIFY linePowerSleepDelayChanged) + Q_PROPERTY(int LinePowerLockDelay READ linePowerLockDelay WRITE setLinePowerLockDelay NOTIFY linePowerLockDelayChanged) + + Q_PROPERTY(int BatteryScreensaverDelay READ batteryScreensaverDelay WRITE setBatteryScreensaverDelay NOTIFY batteryScreensaverDelayChanged) + Q_PROPERTY(int BatteryScreenBlackDelay READ batteryScreenBlackDelay WRITE setBatteryScreenBlackDelay NOTIFY batteryScreenBlackDelayChanged) + Q_PROPERTY(int BatterySleepDelay READ batterySleepDelay WRITE setBatterySleepDelay NOTIFY batterySleepDelayChanged) + Q_PROPERTY(int BatteryLockDelay READ batteryLockDelay WRITE setBatteryLockDelay NOTIFY batteryLockDelayChanged) + + Q_PROPERTY(bool ScreenBlackLock READ screenBlackLock WRITE setScreenBlackLock NOTIFY screenBlackLockChanged) + Q_PROPERTY(bool SleepLock READ sleepLock WRITE setSleepLock NOTIFY sleepLockChanged) + + Q_PROPERTY(int LinePowerLidClosedAction READ linePowerLidClosedAction WRITE setLinePowerLidClosedAction NOTIFY linePowerLidClosedActionChanged) + Q_PROPERTY(int BatteryLidClosedAction READ batteryLidClosedAction WRITE setBatteryLidClosedAction NOTIFY batteryLidClosedActionChanged) + Q_PROPERTY(int LinePowerPressPowerBtnAction READ linePowerPressPowerBtnAction WRITE setLinePowerPressPowerBtnAction NOTIFY linePowerPressPowerBtnActionChanged) + Q_PROPERTY(int BatteryPressPowerBtnAction READ batteryPressPowerBtnAction WRITE setBatteryPressPowerBtnAction NOTIFY batteryPressPowerBtnActionChanged) + + Q_PROPERTY(bool LowPowerNotifyEnable READ lowPowerNotifyEnable WRITE setLowPowerNotifyEnable NOTIFY lowPowerNotifyEnableChanged) + Q_PROPERTY(int LowPowerNotifyThreshold READ lowPowerNotifyThreshold WRITE setLowPowerNotifyThreshold NOTIFY lowPowerNotifyThresholdChanged) + Q_PROPERTY(int LowPowerAutoSleepThreshold READ lowPowerAutoSleepThreshold WRITE setLowPowerAutoSleepThreshold NOTIFY lowPowerAutoSleepThresholdChanged) + Q_PROPERTY(int LowPowerAction READ lowPowerAction WRITE setLowPowerAction NOTIFY lowPowerActionChanged) + Q_PROPERTY(bool AmbientLightAdjustBrightness READ ambientLightAdjustBrightness WRITE setAmbientLightAdjustBrightness NOTIFY ambientLightAdjustBrightnessChanged) + + Q_PROPERTY(bool ScheduledShutdownState READ scheduledShutdownState WRITE setScheduledShutdownState NOTIFY scheduledShutdownStateChanged) + Q_PROPERTY(QString ShutdownTime READ shutdownTime WRITE setShutdownTime NOTIFY shutdownTimeChanged) + Q_PROPERTY(int ShutdownRepetition READ shutdownRepetition WRITE setShutdownRepetition NOTIFY shutdownRepetitionChanged) + Q_PROPERTY(QByteArray CustomShutdownWeekDays READ customShutdownWeekDays WRITE setCustomShutdownWeekDays NOTIFY customShutdownWeekDaysChanged) + +public: + explicit PowerManager(QDBusConnection *conn, const QString &serviceName, + QObject *parent = nullptr); + ~PowerManager() override; + + bool initialize(); + +public Q_SLOTS: + void Reset(); + void SetPrepareSuspend(int state); + void TurnOffScreen(); + void TurnOnScreen(); + + bool onBattery() const { return m_onBattery; } + bool lidIsPresent() const { return m_lidIsPresent; } + BatteryIsPresentMap batteryIsPresent() const { return m_batteryIsPresent; } + BatteryPercentageMap batteryPercentage() const { return m_batteryPercentage; } + BatteryStateMap batteryState() const { return m_batteryState; } + quint64 batteryTimeToEmpty() const { return m_batteryTimeToEmpty; } + bool hasAmbientLightSensor() const { return m_hasAmbientLightSensor; } + uint warnLevel() const { return m_warnLevel; } + bool isHighPerformanceSupported() const { return m_isHighPerformanceSupported; } + + int linePowerScreensaverDelay() const { return m_linePowerScreensaverDelay; } + void setLinePowerScreensaverDelay(int v); + int linePowerScreenBlackDelay() const { return m_linePowerScreenBlackDelay; } + void setLinePowerScreenBlackDelay(int v); + int linePowerSleepDelay() const { return m_linePowerSleepDelay; } + void setLinePowerSleepDelay(int v); + int linePowerLockDelay() const { return m_linePowerLockDelay; } + void setLinePowerLockDelay(int v); + + int batteryScreensaverDelay() const { return m_batteryScreensaverDelay; } + void setBatteryScreensaverDelay(int v); + int batteryScreenBlackDelay() const { return m_batteryScreenBlackDelay; } + void setBatteryScreenBlackDelay(int v); + int batterySleepDelay() const { return m_batterySleepDelay; } + void setBatterySleepDelay(int v); + int batteryLockDelay() const { return m_batteryLockDelay; } + void setBatteryLockDelay(int v); + + bool screenBlackLock() const { return m_screenBlackLock; } + void setScreenBlackLock(bool v); + bool sleepLock() const { return m_sleepLock; } + void setSleepLock(bool v); + + int linePowerLidClosedAction() const { return m_linePowerLidClosedAction; } + void setLinePowerLidClosedAction(int v); + int batteryLidClosedAction() const { return m_batteryLidClosedAction; } + void setBatteryLidClosedAction(int v); + int linePowerPressPowerBtnAction() const { return m_linePowerPressPowerBtnAction; } + void setLinePowerPressPowerBtnAction(int v); + int batteryPressPowerBtnAction() const { return m_batteryPressPowerBtnAction; } + void setBatteryPressPowerBtnAction(int v); + + bool lowPowerNotifyEnable() const { return m_lowPowerNotifyEnable; } + void setLowPowerNotifyEnable(bool v); + int lowPowerNotifyThreshold() const { return m_lowPowerNotifyThreshold; } + void setLowPowerNotifyThreshold(int v); + int lowPowerAutoSleepThreshold() const { return m_lowPowerAutoSleepThreshold; } + void setLowPowerAutoSleepThreshold(int v); + int lowPowerAction() const { return m_lowPowerAction; } + void setLowPowerAction(int v); + bool ambientLightAdjustBrightness() const { return m_ambientLightAdjustBrightness; } + void setAmbientLightAdjustBrightness(bool v); + + bool scheduledShutdownState() const { return m_scheduledShutdownState; } + void setScheduledShutdownState(bool v); + QString shutdownTime() const { return m_shutdownTime; } + void setShutdownTime(const QString &v); + int shutdownRepetition() const { return m_shutdownRepetition; } + void setShutdownRepetition(int v); + QByteArray customShutdownWeekDays() const { return m_customShutdownWeekDays; } + void setCustomShutdownWeekDays(const QByteArray &v); + +public: + // ── Accessors for submodules ────────────────────────── + IdleWatcher *idleWatcher() const { return m_idleWatcher; } + ScreenController *screenController() const { return m_screenCtrl; } + bool useWayland() const { return m_useWayland; } + bool shouldIgnoreIdleOn() const; + int prepareSuspendState() const { return static_cast(m_prepareSuspendState); } + + void doSuspend(); + void doShutdown(); + void doHibernate(); + void doTurnOffScreen(); + void doLock(bool autoStartAuth = true); + bool canSuspend() const; + void setDPMSModeOn(); + void setDPMSModeOff(); + bool canHibernate() const; + void handleBeforeSleep(bool beforeSleep); + void handleWakeup(); + void sendNotify(const QString &summary, const QString &body); + void setDisplayBrightness(const QMap &table); + void setAndSaveDisplayBrightness(const QMap &table); + void setBlackScreenActive(bool active); + void initBatteryWatcher(); + void initSleepWatcher(); + void initLogindInhibit(); + void scheduledShutdown(int state); + void closeNotify(); + +private Q_SLOTS: + void onLogin1OwnerChanged(const QString &name, const QString &oldOwner, + const QString &newOwner); + void onNotifyActionInvoked(uint id, const QString &actionKey); + void onSystemTimeChanged(); + void onSessionActiveChanged(); + + void onLinePowerDelayChanged(); + void onBatteryDelayChanged(); + + void handleOnBatteryChanged(bool value); + void handleHasLidSwitchChanged(bool value); + void handleHasBatteryChanged(bool value); + void handleBatteryPercentageChanged(double value); + void handleBatteryStatusChanged(uint value); + void handleBatteryTimeToEmptyChanged(quint64 value); + void handleIsHighPerformanceSupportedChanged(bool value); + void handlePowerSavingModeEnabledChanged(bool value); + void handlePowerSavingModeBrightnessDropPercentChanged(uint value); + void refreshBatteryInfo(); + void notifyPropertyChanged(); + +Q_SIGNALS: + void onBatteryChanged(); + void lidIsPresentChanged(); + void batteryIsPresentChanged(); + void batteryPercentageChanged(); + void batteryStateChanged(); + void hasAmbientLightSensorChanged(); + void warnLevelChanged(); + void isHighPerformanceSupportedChanged(); + void linePowerScreensaverDelayChanged(); + void linePowerScreenBlackDelayChanged(); + void linePowerSleepDelayChanged(); + void linePowerLockDelayChanged(); + void batteryScreensaverDelayChanged(); + void batteryScreenBlackDelayChanged(); + void batterySleepDelayChanged(); + void batteryLockDelayChanged(); + void screenBlackLockChanged(); + void sleepLockChanged(); + void linePowerLidClosedActionChanged(); + void batteryLidClosedActionChanged(); + void linePowerPressPowerBtnActionChanged(); + void batteryPressPowerBtnActionChanged(); + void lowPowerNotifyEnableChanged(); + void lowPowerNotifyThresholdChanged(); + void lowPowerAutoSleepThresholdChanged(); + void lowPowerActionChanged(); + void ambientLightAdjustBrightnessChanged(); + void scheduledShutdownStateChanged(); + void shutdownTimeChanged(); + void shutdownRepetitionChanged(); + void customShutdownWeekDaysChanged(); + +private: + void doSuspendByFront(); + +private: + IdleWatcher *createIdleWatcher(); + ScreenController *createScreenController(); + void initDConfig(); + void initSubmodules(); + void recalculateScheduledShutdown(); + qint64 getNextShutdownTime(qint64 baseTime) const; + bool isWorkday(const QDateTime &date) const; + bool isCustomDay(const QDateTime &date) const; + void shutdownCountdownNotify(int count, bool playSound); + void doAutoShutdown(); + void initScheduledShutdown(); + + QDBusConnection *m_conn = nullptr; + + SessionDBusProxy *m_proxy = nullptr; + IdleWatcher *m_idleWatcher = nullptr; + ScreenController *m_screenCtrl = nullptr; + Dtk::Core::DConfig *m_config = nullptr; + PowerSavePlan *m_powerSavePlan = nullptr; + LidSwitchHandler *m_lidSwitch = nullptr; + SleepInhibitor *m_sleepInhibitor = nullptr; + LowPowerManager *m_lowPowerMgr = nullptr; + + bool m_useWayland = false; + bool m_onBattery = false; + bool m_lidIsPresent = false; + BatteryIsPresentMap m_batteryIsPresent; + BatteryPercentageMap m_batteryPercentage; + BatteryStateMap m_batteryState; + quint64 m_batteryTimeToEmpty = 0; + bool m_hasAmbientLightSensor = false; + uint m_warnLevel = 0; + bool m_isHighPerformanceSupported = false; + int m_linePowerScreensaverDelay = 0; + int m_linePowerScreenBlackDelay = 0; + int m_linePowerSleepDelay = 0; + int m_linePowerLockDelay = 0; + int m_batteryScreensaverDelay = 0; + int m_batteryScreenBlackDelay = 0; + int m_batterySleepDelay = 0; + int m_batteryLockDelay = 0; + bool m_screenBlackLock = false; + bool m_sleepLock = false; + int m_linePowerLidClosedAction = 0; + int m_batteryLidClosedAction = 0; + int m_linePowerPressPowerBtnAction = 0; + int m_batteryPressPowerBtnAction = 0; + bool m_lowPowerNotifyEnable = true; + int m_lowPowerNotifyThreshold = 0; + int m_lowPowerAutoSleepThreshold = 0; + int m_lowPowerAction = 0; + bool m_ambientLightAdjustBrightness = false; + bool m_scheduledShutdownState = false; + QString m_shutdownTime; + int m_shutdownRepetition = 0; + QByteArray m_customShutdownWeekDays; + + PrepareSuspendState m_prepareSuspendState = PS_Normal; + + bool m_screensaverWasRunning = false; + bool m_screensaverLockAtAwake = false; + bool m_screensaverStateCaptured = false; + bool m_delayInActive = false; + int m_delayWakeupInterval = 2; + int m_inhibitFd = -1; + + QTimer *m_shutdownTimer = nullptr; + QTimer *m_countdownTimer = nullptr; + int m_shutdownStatus = SchedInit; + int m_shutdownCountdown = 60; + uint m_shutdownNotifyId = 0; + qint64 m_nextShutdownTime = 0; +}; diff --git a/src/plugin-qt/power/session/powersaveplan.cpp b/src/plugin-qt/power/session/powersaveplan.cpp new file mode 100644 index 0000000..96d0606 --- /dev/null +++ b/src/plugin-qt/power/session/powersaveplan.cpp @@ -0,0 +1,359 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "powersaveplan.h" +#include "powermanager.h" +#include "idle/idlewatcher.h" +#include "screen/screencontroller.h" +#include "../powerconstants.h" + +#include +#include +#include + +Q_DECLARE_LOGGING_CATEGORY(logPowerSession) + +using namespace PowerDBus; +using namespace PowerFS; + +static bool canAdd(const QString &type, int delay, + const QVector &tasks) +{ + if (tasks.isEmpty()) + return true; + if (type == QLatin1String("sleep")) + return true; + if (type == QLatin1String("screenSaverStart")) { + int min = tasks.first().delay; + for (const auto &t : tasks) + if (t.delay < min) min = t.delay; + return delay <= min; + } + if (type == QLatin1String("screenBlack")) { + if (delay < tasks.first().delay) + return true; + if (delay == tasks.first().delay && tasks.last().name == QLatin1String("lock")) + return true; + return false; + } + return false; +} + +PowerSavePlan::PowerSavePlan(PowerManager *powerManager, QObject *parent) + : QObject(parent), m_powerManager(powerManager) +{ + +} + +void PowerSavePlan::Start() +{ + Reset(); +} + +void PowerSavePlan::Reset() +{ + if (!m_powerManager) + return; + + if (m_powerManager->onBattery()) + OnBattery(); + else + OnLinePower(); +} + +void PowerSavePlan::OnBattery() +{ + if (!m_powerManager) + return; + + Update(m_powerManager->batteryScreensaverDelay(), m_powerManager->batteryLockDelay(), + m_powerManager->batteryScreenBlackDelay(), m_powerManager->batterySleepDelay()); +} + +void PowerSavePlan::OnLinePower() +{ + if (!m_powerManager) + return; + + Update(m_powerManager->linePowerScreensaverDelay(), m_powerManager->linePowerLockDelay(), + m_powerManager->linePowerScreenBlackDelay(), m_powerManager->linePowerSleepDelay()); +} + +void PowerSavePlan::Update(int screenSaverStartDelay, int lockDelay, + int screenBlackDelay, int sleepDelay) +{ + interruptTasks(); + m_metaTasks.clear(); + + qInfo(logPowerSession) << "Updating PowerSavePlan: screenSaverStartDelay=" << screenSaverStartDelay + << " lockDelay=" << lockDelay + << " screenBlackDelay=" << screenBlackDelay + << " sleepDelay=" << sleepDelay; + + if (sleepDelay > 0 && canAdd("sleep", sleepDelay, m_metaTasks)) { + m_metaTasks.append({sleepDelay, 0, "sleep", [this]{ + m_powerManager->doSuspend(); + }}); + } + + if (screenSaverStartDelay > 0 && canAdd("screenSaverStart", screenSaverStartDelay, m_metaTasks)) { + m_metaTasks.append({screenSaverStartDelay, 0, "screenSaverStart", [this]{ + startScreensaver(); + }}); + } + + if (lockDelay > 0) { + m_metaTasks.append({lockDelay, 0, "lock", [this]{ + if (m_powerManager) { + m_powerManager->doLock(); + } + }}); + } + + if (screenBlackDelay > 0 && canAdd("screenBlack", screenBlackDelay, m_metaTasks)) { + m_metaTasks.append({screenBlackDelay, 0, "screenBlack", [this]{ + screenBlack(); + }}); + } + + int min = 0; + for (const auto &t : m_metaTasks) { + if (t.delay < min || min == 0) { + min = t.delay; + } + } + + setScreenSaverTimeout(min); + + for (auto &t : m_metaTasks) { + int nSecs = t.delay - min; + t.realDelay = nSecs > 0 ? nSecs * 1000 : 1; + } + + if (m_isIdle) { + HandleIdleOn(); + } +} + +void PowerSavePlan::HandleIdleOn() +{ + qDebug(logPowerSession) << "HandleIdleOn"; + + if (!m_powerManager) { + return; + } + + if (m_powerManager->shouldIgnoreIdleOn()) { + qDebug(logPowerSession) << "shouldIgnoreIdleOn is true, ignoring idle on event"; + return; + } + + m_isIdle = true; + + for (const auto &t : m_metaTasks) { + scheduleTask(t); + } + + if (QFile::exists(kNoSuspendFile) && m_powerManager->screenBlackLock()) + m_powerManager->screenController()->setAllModes(ScreenController::On); +} + +void PowerSavePlan::HandleIdleOff() +{ + qDebug(logPowerSession) << "HandleIdleOff"; + m_isIdle = false; + interruptTasks(); + if (!m_powerManager) return; + if (m_powerManager->screenController()) { + m_powerManager->screenController()->setAllModes(ScreenController::On); + } + resetBrightness(); +} + +void PowerSavePlan::scheduleTask(const MetaTask &t) +{ + qDebug(logPowerSession) << "Scheduling task" << t.name << "to run in" << t.realDelay << "ms"; + auto *timer = new QTimer(this); + timer->setSingleShot(true); + connect(timer, &QTimer::timeout, this, t.fn); + timer->start(t.realDelay > 0 ? t.realDelay : 100); + m_timers.append(timer); +} + +void PowerSavePlan::interruptTasks() +{ + for (auto *t : m_timers) { + t->stop(); + t->deleteLater(); + } + m_timers.clear(); +} + +void PowerSavePlan::setScreenSaverTimeout(int seconds) +{ + if (!m_powerManager) + return; + auto *iw = m_powerManager->idleWatcher(); + if (iw && m_powerManager->useWayland()) { + iw->setTimeout(static_cast(seconds)); + return; + } + + // FIXME(mhduiy): delete or use in x11? + // QDBusInterface iface(kScreensaver, kScreensaverPath, kScreensaver, + // QDBusConnection::sessionBus()); + // iface.call("SetTimeout", static_cast(seconds), 0u, false); +} + +void PowerSavePlan::startScreensaver() +{ + if (m_powerManager->useWayland()) { + // Wayland no need to call start + return; + } + + if (qEnvironmentVariable("DESKTOP_CAN_SCREENSAVER") == "N") + return; + + if (!m_allowScreenSaver) + return; + + QDBusInterface iface(kScreensaver, kScreensaverPath, kScreensaver, + QDBusConnection::sessionBus()); + iface.call("Start"); + m_screensaverRunning = true; +} + +void PowerSavePlan::stopScreensaver() +{ + if (!m_screensaverRunning) + return; + QDBusInterface iface(kScreensaver, kScreensaverPath, kScreensaver, + QDBusConnection::sessionBus()); + iface.call("Stop"); + m_screensaverRunning = false; +} + +void PowerSavePlan::screenBlack() +{ + qDebug(logPowerSession) << "Blackening screen"; + if (!m_powerManager) { + return; + } + saveCurrentBrightness(); + if (auto *sc = m_powerManager->screenController()) + sc->setAllModes(ScreenController::Off); + if (m_powerManager->screenBlackLock()) { + QTimer::singleShot(200, this, [this]() { + if (m_powerManager) { + m_powerManager->doLock(); + } + }); + } +} + +void PowerSavePlan::onPowerSavingModeEnabledChanged(bool enabled) +{ + m_psmEnabled = enabled; + qDebug(logPowerSession) << "PowerSavingModeEnabled changed to" << enabled << ", drop=" << m_psmDrop; + + if (enabled) + applyBrightnessDrop(); + else + resetBrightness(); +} + +void PowerSavePlan::onBrightnessDropPercentChanged(uint value) +{ + m_psmDrop = value; + qDebug(logPowerSession) << "BrightnessDropPercent changed to" << value << ", enabled=" << m_psmEnabled ; + + if (m_psmEnabled) + applyBrightnessDrop(); +} + +void PowerSavePlan::saveCurrentBrightness() +{ + m_oldBrightness.clear(); + if (!m_powerManager) return; + + auto *sc = m_powerManager->screenController(); + if (!sc || !sc->supportsBrightness()) { + m_oldBrightness["default"] = 1.0; // legacy placeholder + return; + } + + int n = sc->outputCount(); + for (int i = 0; i < n; ++i) { + double b = sc->brightness(i); + if (b >= 0.0) { + m_oldBrightness[QString::number(i)] = b; + qDebug(logPowerSession) << "saveBrightness: output" << i << "= " << b; + } + } + + if (m_oldBrightness.isEmpty()) + m_oldBrightness["default"] = 1.0; +} + +void PowerSavePlan::resetBrightness() +{ + qDebug(logPowerSession) << "Resetting brightness to original values"; + + if (!m_powerManager) { + m_oldBrightness.clear(); + return; + } + + auto *sc = m_powerManager->screenController(); + if (!sc || !sc->supportsBrightness()) { + m_oldBrightness.clear(); + return; + } + + for (auto it = m_oldBrightness.cbegin(); it != m_oldBrightness.cend(); ++it) { + if (it.key() == QLatin1String("default")) + continue; + bool ok = false; + int idx = it.key().toInt(&ok); + if (ok) { + sc->setBrightness(idx, it.value()); + qDebug(logPowerSession) << "Restored brightness: output" << idx << "->" << it.value(); + } + } + m_oldBrightness.clear(); +} + +void PowerSavePlan::applyBrightnessDrop() +{ + if (!m_powerManager) return; + + auto *sc = m_powerManager->screenController(); + if (!sc || !sc->supportsBrightness()) { + qWarning(logPowerSession) << "applyBrightnessDrop: brightness not supported"; + return; + } + + // Save original brightness if we haven't already + if (m_oldBrightness.isEmpty()) + saveCurrentBrightness(); + + int n = sc->outputCount(); + double ratio = 1.0 - static_cast(m_psmDrop) / 100.0; + ratio = std::clamp(ratio, 0.1, 1.0); // never drop below 10% + + qDebug(logPowerSession) << "applyBrightnessDrop: ratio=" << ratio << ", outputs=" << n << ", oldBrightness=" << m_oldBrightness; + + for (int i = 0; i < n; ++i) { + QString key = QString::number(i); + double original = m_oldBrightness.value(key, -1.0); + if (original < 0.0) continue; + + double target = original * ratio; + target = std::clamp(target, 10.0, 100.0); + + sc->setBrightness(i, target); + qDebug(logPowerSession) << "drop brightness: output" << i << ": " << original << " -> " << target; + } +} diff --git a/src/plugin-qt/power/session/powersaveplan.h b/src/plugin-qt/power/session/powersaveplan.h new file mode 100644 index 0000000..f9a72e6 --- /dev/null +++ b/src/plugin-qt/power/session/powersaveplan.h @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +class PowerManager; +class ScreenController; + +class PowerSavePlan : public QObject { + Q_OBJECT +public: + struct MetaTask { + int delay = 0; + int realDelay = 0; + QString name; + std::function fn; + }; + + PowerSavePlan(PowerManager *powerManager, QObject *parent = nullptr); + + void Start(); + void Reset(); + void Update(int screenSaverStartDelay, int lockDelay, + int screenBlackDelay, int sleepDelay); + void HandleIdleOn(); + void HandleIdleOff(); + void OnBattery(); + void OnLinePower(); + + void onPowerSavingModeEnabledChanged(bool enabled); + void onBrightnessDropPercentChanged(uint value); + +private: + void startScreensaver(); + void stopScreensaver(); + void screenBlack(); + void interruptTasks(); + void setScreenSaverTimeout(int seconds); + void saveCurrentBrightness(); + void resetBrightness(); + void applyBrightnessDrop(); + void scheduleTask(const MetaTask &t); + + QVector m_metaTasks; + QVector m_timers; + QMap m_oldBrightness; + bool m_screensaverRunning = false; + bool m_isIdle = false; + bool m_allowScreenSaver = true; + bool m_psmEnabled = false; + uint m_psmDrop = 0; + bool m_lockFired = false; + PowerManager *m_powerManager = nullptr; +}; diff --git a/src/plugin-qt/power/session/screen/screencontroller.cpp b/src/plugin-qt/power/session/screen/screencontroller.cpp new file mode 100644 index 0000000..d06c2bf --- /dev/null +++ b/src/plugin-qt/power/session/screen/screencontroller.cpp @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "screencontroller.h" + +void ScreenController::setAllModes(Mode m) +{ + for (int i = 0; i < outputCount(); ++i) + setMode(i, m); +} + +bool ScreenController::isAllOff() const +{ + for (int i = 0; i < outputCount(); ++i) { + if (mode(i) != Off) + return false; + } + return outputCount() > 0; +} diff --git a/src/plugin-qt/power/session/screen/screencontroller.h b/src/plugin-qt/power/session/screen/screencontroller.h new file mode 100644 index 0000000..d22d201 --- /dev/null +++ b/src/plugin-qt/power/session/screen/screencontroller.h @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include +#include + +/** + * @brief Abstract interface for output power management (DPMS equivalent). + * + * X11 implementation: uses DPMS Extension. + * Wayland implementation: uses wlr-output-power-management-unstable-v1. + */ +class ScreenController : public QObject +{ + Q_OBJECT + +public: + enum Mode { Off = 0, On = 1 }; + + explicit ScreenController(QObject *parent = nullptr) : QObject(parent) {} + ~ScreenController() override = default; + + /// Whether the underlying backend initialized successfully. + virtual bool isValid() const = 0; + + /// Number of managed outputs. + virtual int outputCount() const = 0; + + /// Current power mode of output @p index. + virtual Mode mode(int index) const = 0; + + /// Set power mode for output @p index. + virtual void setMode(int index, Mode m) = 0; + + /// Convenience: set the same mode on every output. + void setAllModes(Mode m); + + /// True when all outputs are Off. + bool isAllOff() const; + + // ── Brightness (Treeland protocol, 0.0–100.0) ─────────────── + /// Whether brightness control is supported by this backend. + virtual bool supportsBrightness() const { return false; } + + /// Current brightness of output @p index (0.0–100.0), or -1.0 if unsupported. + virtual double brightness(int index) const { Q_UNUSED(index); return -1.0; } + + /// Set brightness for output @p index (0.0–100.0). + virtual void setBrightness(int index, double value) { Q_UNUSED(index); Q_UNUSED(value); } + +Q_SIGNALS: + /// Emitted when the power mode of an output changes. + void modeChanged(int index, ScreenController::Mode mode); + + /// Emitted when the brightness of an output changes. + void brightnessChanged(int index, double value); +}; diff --git a/src/plugin-qt/power/session/screen/screencontroller_wl.cpp b/src/plugin-qt/power/session/screen/screencontroller_wl.cpp new file mode 100644 index 0000000..c6ae9ec --- /dev/null +++ b/src/plugin-qt/power/session/screen/screencontroller_wl.cpp @@ -0,0 +1,319 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "screencontroller_wl.h" + +#include +#include +#include +#include + +#include + +Q_DECLARE_LOGGING_CATEGORY(logPowerSession) + +OutputPower::OutputPower(::zwlr_output_power_v1 *obj) + : QtWayland::zwlr_output_power_v1(obj) +{ + +} + +OutputPower::~OutputPower() +{ + destroy(); +} + +void OutputPower::zwlr_output_power_v1_mode(uint32_t mode) +{ + Q_EMIT modeChanged(mode); +} + +void OutputPower::zwlr_output_power_v1_failed() +{ + +} + +OutputPowerManager::OutputPowerManager() + : QWaylandClientExtensionTemplate(1) +{ + +} + +OutputPowerManager::~OutputPowerManager() +{ + if (isInitialized()) + destroy(); +} + +void OutputPowerManager::instantiate() +{ + initialize(); +} + +std::unique_ptr OutputPowerManager::getOutputPower(wl_output *output) +{ + if (!isInitialized() || !output) + return nullptr; + + auto *raw = get_output_power(output); + if (!raw) + return nullptr; + + return std::make_unique(raw); +} + + +OutputColorControl::OutputColorControl(::treeland_output_color_control_v1 *obj) + : QtWayland::treeland_output_color_control_v1(obj) +{ + +} + +OutputColorControl::~OutputColorControl() +{ + destroy(); +} + +void OutputColorControl::treeland_output_color_control_v1_brightness(wl_fixed_t brightness) +{ + m_brightness = wl_fixed_to_double(brightness); + Q_EMIT brightnessChanged(m_brightness); +} + +void OutputColorControl::treeland_output_color_control_v1_color_temperature(uint32_t) +{ + +} + +void OutputColorControl::treeland_output_color_control_v1_result(uint32_t) +{ + +} + +TreeLandOutputMgr::TreeLandOutputMgr() + : QWaylandClientExtensionTemplate(2) +{ + +} + +TreeLandOutputMgr::~TreeLandOutputMgr() +{ + if (isInitialized()) + destroy(); +} + +void TreeLandOutputMgr::instantiate() +{ + initialize(); +} + +OutputColorControl *TreeLandOutputMgr::getColorControl(wl_output *output) +{ + if (!isInitialized() || !output) + return nullptr; + + auto *raw = get_color_control(output); + if (!raw) + return nullptr; + + return new OutputColorControl(raw); +} + + +WaylandScreenController::WaylandScreenController(QObject *parent) + : ScreenController(parent) +{ + auto *wlApp = qGuiApp->nativeInterface(); + if (!wlApp) { + qWarning(logPowerSession) << "[Power::WL] Scrn: no QWaylandApplication"; + return; + } + + m_display = wlApp->display(); + if (!m_display) { + qWarning(logPowerSession) << "[Power::WL] Scrn: no wl_display"; + return; + } + + m_manager.reset(new OutputPowerManager); + m_manager->instantiate(); + if (!m_manager->isInitialized()) { + qWarning(logPowerSession) << "[Power::WL] Scrn: manager init failed"; + return; + } + + m_treeLandMgr.reset(new TreeLandOutputMgr); + m_treeLandMgr->instantiate(); + if (m_treeLandMgr->isInitialized()) { + wl_display_roundtrip(m_display); + m_brightnessAvailable = true; + } else { + qWarning(logPowerSession) << "[Power::WL] Scrn: treeland brightness NOT available"; + } + + discoverOutputs(); + m_valid = true; +} + +WaylandScreenController::~WaylandScreenController() +{ + for (auto *anim : m_brightnessAnims) { + anim->stop(); + anim->deleteLater(); + } + m_brightnessAnims.clear(); + for (auto &out : m_outputs) { + if (out.wlOutput) + wl_output_destroy(out.wlOutput); + } + m_outputs.clear(); + m_treeLandMgr.reset(); + m_manager.reset(); +} + +static void scOutputGlobal(void *data, wl_registry *r, uint32_t name, + const char *iface, uint32_t) +{ + auto *sc = static_cast(data); + if (std::strcmp(iface, wl_output_interface.name) == 0) { + auto *wlo = static_cast( + wl_registry_bind(r, name, &wl_output_interface, 1)); + WaylandScreenController::Output out; + out.registryName = name; + out.wlOutput = wlo; + out.power = sc->m_manager->getOutputPower(wlo); + if (out.power) { + QObject::connect(out.power.get(), &OutputPower::modeChanged, + sc, [sc, idx = int(sc->m_outputs.size())](uint32_t m) { + if (idx < int(sc->m_outputs.size())) { + sc->m_outputs[idx].currentMode = m; + Q_EMIT sc->modeChanged(idx, m == 0 ? ScreenController::Off : ScreenController::On); + } + }); + } + if (sc->m_brightnessAvailable) { + out.colorControl.reset(sc->m_treeLandMgr->getColorControl(wlo)); + if (out.colorControl) { + QObject::connect(out.colorControl.get(), &OutputColorControl::brightnessChanged, + sc, [sc, idx = int(sc->m_outputs.size())](double v) { + if (idx < int(sc->m_outputs.size())) + Q_EMIT sc->brightnessChanged(idx, v); + }); + } + } + sc->m_outputs.push_back(std::move(out)); + } +} + +static void scOutputRemove(void *data, wl_registry *, uint32_t name) +{ + auto *sc = static_cast(data); + auto &outs = sc->m_outputs; + for (auto it = outs.begin(); it != outs.end(); ++it) { + if (it->registryName == name) { + int idx = int(it - outs.begin()); + if (auto *anim = sc->m_brightnessAnims.take(idx)) { + anim->stop(); + anim->deleteLater(); + } + if (it->wlOutput) + wl_output_destroy(it->wlOutput); + outs.erase(it); + break; + } + } +} + +static const wl_registry_listener kOutputListener = {scOutputGlobal, scOutputRemove}; + +void WaylandScreenController::discoverOutputs() +{ + auto *reg = wl_display_get_registry(m_display); + wl_registry_add_listener(reg, &kOutputListener, this); + wl_display_roundtrip(m_display); + wl_display_roundtrip(m_display); + wl_registry_destroy(reg); +} + +ScreenController::Mode WaylandScreenController::mode(int index) const +{ + if (index < 0 || size_t(index) >= m_outputs.size()) + return On; + + return m_outputs[size_t(index)].currentMode == 0 ? Off : On; +} + +void WaylandScreenController::setMode(int index, Mode m) +{ + if (index < 0 || size_t(index) >= m_outputs.size()) return; + auto &out = m_outputs[size_t(index)]; + uint32_t wm = m == Off ? 0 : 1; + if (out.power) { + out.power->set_mode(wm); + wl_display_flush(m_display); + } + out.currentMode = wm; + Q_EMIT modeChanged(index, m); +} + +double WaylandScreenController::brightness(int index) const +{ + if (index < 0 || size_t(index) >= m_outputs.size()) { + qWarning(logPowerSession) << "[Power::WL] brightness(" << index << "): out of range (total=" << int(m_outputs.size()) << ")"; + return -1.0; + } + auto &out = m_outputs[size_t(index)]; + if (out.colorControl) { + return out.colorControl->currentBrightness(); + } + return -1.0; +} + +void WaylandScreenController::setBrightness(int index, double value) +{ + if (index < 0 || size_t(index) >= m_outputs.size()) { + qWarning(logPowerSession) << "[Power::WL] setBrightness(" << index << ", " << value << "): out of range (total=" << int(m_outputs.size()) << ")"; + return; + } + + auto &out = m_outputs[size_t(index)]; + if (!out.colorControl) { + qWarning(logPowerSession) << "[Power::WL] setBrightness(" << index << ", " << value << "): no colorControl"; + return; + } + + double current = out.colorControl->currentBrightness(); + if (current < 0.0 || std::abs(current - value) < 0.5) { + out.colorControl->set_brightness(wl_fixed_from_double(value)); + out.colorControl->commit(); + wl_display_flush(m_display); + return; + } + + if (auto *old = m_brightnessAnims.take(index)) { + old->disconnect(this); + old->stop(); + old->deleteLater(); + } + + auto *anim = new QVariantAnimation(this); + anim->setDuration(400); + anim->setStartValue(current); + anim->setEndValue(value); + anim->setEasingCurve(QEasingCurve::InOutCubic); + + connect(anim, &QVariantAnimation::valueChanged, this, + [this, index](const QVariant &v) { + if (index < 0 || size_t(index) >= m_outputs.size()) return; + auto &o = m_outputs[size_t(index)]; + if (o.colorControl) { + o.colorControl->set_brightness(wl_fixed_from_double(v.toDouble())); + o.colorControl->commit(); + wl_display_flush(m_display); + } + }); + + m_brightnessAnims[index] = anim; + anim->start(); +} diff --git a/src/plugin-qt/power/session/screen/screencontroller_wl.h b/src/plugin-qt/power/session/screen/screencontroller_wl.h new file mode 100644 index 0000000..9cef395 --- /dev/null +++ b/src/plugin-qt/power/session/screen/screencontroller_wl.h @@ -0,0 +1,114 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include "screencontroller.h" + +#include +#include +#include +#include +#include + +#include + +#include "qwayland-treeland-output-manager-v1.h" +#include "qwayland-wlr-output-power-management-unstable-v1.h" + +struct wl_display; +struct wl_output; + +class OutputPower : public QObject, public QtWayland::zwlr_output_power_v1 +{ + Q_OBJECT +public: + explicit OutputPower(::zwlr_output_power_v1 *obj); + ~OutputPower() override; +Q_SIGNALS: + void modeChanged(uint32_t mode); +protected: + void zwlr_output_power_v1_mode(uint32_t mode) override; + void zwlr_output_power_v1_failed() override; +}; + +class OutputPowerManager : public QWaylandClientExtensionTemplate + , public QtWayland::zwlr_output_power_manager_v1 +{ + Q_OBJECT +public: + OutputPowerManager(); + ~OutputPowerManager() override; + void instantiate(); + std::unique_ptr getOutputPower(wl_output *output); +}; + +class OutputColorControl : public QObject, public QtWayland::treeland_output_color_control_v1 +{ + Q_OBJECT +public: + explicit OutputColorControl(::treeland_output_color_control_v1 *obj); + ~OutputColorControl() override; + + double currentBrightness() const { return m_brightness; } + +Q_SIGNALS: + void brightnessChanged(double value); + +protected: + void treeland_output_color_control_v1_brightness(wl_fixed_t brightness) override; + void treeland_output_color_control_v1_color_temperature(uint32_t temperature) override; + void treeland_output_color_control_v1_result(uint32_t success) override; + +private: + double m_brightness = 100.0; +}; + +class TreeLandOutputMgr : public QWaylandClientExtensionTemplate + , public QtWayland::treeland_output_manager_v1 +{ + Q_OBJECT +public: + TreeLandOutputMgr(); + ~TreeLandOutputMgr() override; + void instantiate(); + OutputColorControl *getColorControl(wl_output *output); +}; + +class WaylandScreenController : public ScreenController +{ + Q_OBJECT +public: + struct Output { + uint32_t registryName = 0; + wl_output *wlOutput = nullptr; + std::unique_ptr power; + std::unique_ptr colorControl; + uint32_t currentMode = 1; + }; + + explicit WaylandScreenController(QObject *parent = nullptr); + ~WaylandScreenController() override; + + bool isValid() const override { return m_valid; } + int outputCount() const override { return int(m_outputs.size()); } + Mode mode(int index) const override; + void setMode(int index, Mode m) override; + + bool supportsBrightness() const override { return m_brightnessAvailable; } + double brightness(int index) const override; + void setBrightness(int index, double value) override; + + wl_display *m_display = nullptr; + std::unique_ptr m_manager; + std::unique_ptr m_treeLandMgr; + std::vector m_outputs; + QHash m_brightnessAnims; + bool m_brightnessAvailable = false; + +private: + void discoverOutputs(); + + bool m_valid = false; +}; diff --git a/src/plugin-qt/power/session/screen/screencontroller_x11.cpp b/src/plugin-qt/power/session/screen/screencontroller_x11.cpp new file mode 100644 index 0000000..964b256 --- /dev/null +++ b/src/plugin-qt/power/session/screen/screencontroller_x11.cpp @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "screencontroller.h" + +class X11ScreenController : public ScreenController +{ + Q_OBJECT +public: + using ScreenController::ScreenController; + bool isValid() const override { return false; } + int outputCount() const override { return 0; } + Mode mode(int) const override { return On; } + void setMode(int, Mode) override {} +}; + +#include "screencontroller_x11.moc" diff --git a/src/plugin-qt/power/session/sessiondbusproxy.cpp b/src/plugin-qt/power/session/sessiondbusproxy.cpp new file mode 100644 index 0000000..52db99b --- /dev/null +++ b/src/plugin-qt/power/session/sessiondbusproxy.cpp @@ -0,0 +1,206 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "sessiondbusproxy.h" +#include "../powerconstants.h" + +#include +#include +#include + +using namespace PowerDBus; + +SessionDBusProxy::SessionDBusProxy(QObject *parent) + : QObject(parent) + , m_powerInter(new DDBusInterface( + PowerDBus::kService, PowerDBus::kPath, PowerDBus::kInterface, + QDBusConnection::systemBus(), this)) + , m_sessionManagerInter(new DDBusInterface( + kSessionManager, kSessionPath, kSessionManager, + QDBusConnection::sessionBus(), this)) + , m_shutdownFrontInter(new DDBusInterface( + kShutdownFront, kShutdownPath, kShutdownFront, + QDBusConnection::sessionBus(), this)) + , m_login1Inter(new DDBusInterface( + kLogin1Service, kLogin1Path, kLogin1Manager, + QDBusConnection::systemBus(), this)) + , m_lockFrontInter(new DDBusInterface( + kLockFront, kLockFrontPath, kLockFront, + QDBusConnection::sessionBus(), this)) + , m_blackScreenInter(new DDBusInterface( + kBlackScreen, kBlackScreenPath, kBlackScreen, + QDBusConnection::sessionBus(), this)) + , m_displayInter(new DDBusInterface( + kDisplay, kDisplayPath, kDisplay, + QDBusConnection::sessionBus(), this)) + , m_notificationsInter(new DDBusInterface( + kNotifications, kNotificationsPath, kNotifications, + QDBusConnection::sessionBus(), this)) + , m_sessionWatcherInter(new DDBusInterface( + kSessionWatcher, kSessionWatcherPath, kSessionWatcher, + QDBusConnection::sessionBus(), this)) + , m_calendarInter(new DDBusInterface( + kCalendarService, kCalendarPath, kCalendarIface, + QDBusConnection::sessionBus(), this)) + , m_timedateInter(new DDBusInterface( + "org.deepin.dde.Timedate1", "/org/deepin/dde/Timedate1", + "org.deepin.dde.Timedate1", QDBusConnection::sessionBus(), this)) + , m_freedesktopDBusInter(new DDBusInterface( + kFreedesktopDBus, kFreedesktopPath, kFreedesktopDBus, + QDBusConnection::systemBus(), this)) +{ + QDBusConnection::sessionBus().connect( + m_notificationsInter->service(), m_notificationsInter->path(), + m_notificationsInter->interface(), + "ActionInvoked", this, SIGNAL(notifyActionInvoked(uint,QString))); + + QDBusConnection::sessionBus().connect( + m_timedateInter->service(), m_timedateInter->path(), + m_timedateInter->interface(), + "TimeUpdate", this, SIGNAL(timeUpdate())); + + QDBusConnection::systemBus().connect( + m_freedesktopDBusInter->service(), m_freedesktopDBusInter->path(), + m_freedesktopDBusInter->interface(), + "NameOwnerChanged", this, SIGNAL(login1OwnerChanged(QString,QString,QString))); +} + +bool SessionDBusProxy::onBattery() const +{ + return m_powerInter->property("OnBattery").toBool(); +} + +bool SessionDBusProxy::hasLidSwitch() const +{ + return m_powerInter->property("HasLidSwitch").toBool(); +} + +bool SessionDBusProxy::hasBattery() const +{ + return m_powerInter->property("HasBattery").toBool(); +} + +double SessionDBusProxy::batteryPercentage() const +{ + return m_powerInter->property("BatteryPercentage").toDouble(); +} + +uint SessionDBusProxy::batteryStatus() const +{ + return m_powerInter->property("BatteryStatus").toUInt(); +} + +quint64 SessionDBusProxy::batteryTimeToEmpty() const +{ + return m_powerInter->property("BatteryTimeToEmpty").toULongLong(); +} + +bool SessionDBusProxy::isHighPerformanceSupported() const +{ + return m_powerInter->property("IsHighPerformanceSupported").toBool(); +} + +bool SessionDBusProxy::powerSavingModeEnabled() const +{ + return m_powerInter->property("PowerSavingModeEnabled").toBool(); +} + +uint SessionDBusProxy::powerSavingModeBrightnessDropPercent() const +{ + return m_powerInter->property("PowerSavingModeBrightnessDropPercent").toUInt(); +} + +bool SessionDBusProxy::sessionActive() const +{ + return m_sessionWatcherInter->property("IsActive").toBool(); +} + +void SessionDBusProxy::requestSuspend() +{ + m_sessionManagerInter->asyncCall("RequestSuspend"); +} + +void SessionDBusProxy::requestShutdown() +{ + m_sessionManagerInter->asyncCall("RequestShutdown"); +} + +void SessionDBusProxy::requestHibernate() +{ + m_sessionManagerInter->asyncCall("RequestHibernate"); +} + +bool SessionDBusProxy::canSuspend() +{ + QDBusReply r = m_sessionManagerInter->call("CanSuspend"); + return r.isValid() && r.value(); +} + +bool SessionDBusProxy::canHibernate() +{ + QDBusReply r = m_sessionManagerInter->call("CanHibernate"); + return r.isValid() && r.value(); +} + +void SessionDBusProxy::requestSuspendByFront() +{ + m_shutdownFrontInter->asyncCall("Suspend"); +} + +void SessionDBusProxy::showLockAuth(bool autoStart) +{ + m_lockFrontInter->call("ShowAuth", autoStart); +} + +void SessionDBusProxy::lockSession(const QString &sessionId) +{ + QDBusReply r = m_login1Inter->call("LockSession", sessionId); + if (!r.isValid()) + qWarning("[Proxy] LockSession(%s) failed: %s", qPrintable(sessionId), qPrintable(r.error().message())); +} + +QDBusUnixFileDescriptor SessionDBusProxy::inhibit(const QString &what, const QString &who, + const QString &why, const QString &mode) +{ + QDBusReply r = m_login1Inter->call("Inhibit", what, who, why, mode); + if (r.isValid()) + return r.value(); + qWarning("[Proxy] Inhibit failed: %s", qPrintable(r.error().message())); + return {}; +} + +void SessionDBusProxy::setBlackScreenActive(bool active) +{ + m_blackScreenInter->asyncCall("setActive", active); +} + +void SessionDBusProxy::setBrightness(const QString &monitor, double value) +{ + m_displayInter->asyncCall("SetBrightness", monitor, value); +} + +void SessionDBusProxy::setAndSaveBrightness(const QString &monitor, double value) +{ + m_displayInter->asyncCall("SetAndSaveBrightness", monitor, value); +} + +uint SessionDBusProxy::notify(uint replaceId, const QString &appName, const QString &icon, + const QString &title, const QString &body, + const QStringList &actions, const QVariantMap &hints, int timeout) +{ + QDBusReply r = m_notificationsInter->call( + "Notify", appName, replaceId, icon, title, body, actions, hints, timeout); + return r.isValid() ? r.value() : 0; +} + +void SessionDBusProxy::closeNotification(uint id) +{ + m_notificationsInter->asyncCall("CloseNotification", id); +} + +QString SessionDBusProxy::getFestivalMonth(int year, int month) +{ + QDBusReply r = m_calendarInter->call("getFestivalMonth", year, month); + return r.isValid() ? r.value() : QString(); +} diff --git a/src/plugin-qt/power/session/sessiondbusproxy.h b/src/plugin-qt/power/session/sessiondbusproxy.h new file mode 100644 index 0000000..1fd3049 --- /dev/null +++ b/src/plugin-qt/power/session/sessiondbusproxy.h @@ -0,0 +1,115 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include +#include +#include + +using Dtk::Core::DDBusInterface; + +class SessionDBusProxy : public QObject { + Q_OBJECT + +public: + explicit SessionDBusProxy(QObject *parent = nullptr); + + // ── Power (system bus) — DDBusInterface 自动转发 PropertiesChanged ── + Q_PROPERTY(bool OnBattery READ onBattery NOTIFY OnBatteryChanged) + bool onBattery() const; + + Q_PROPERTY(bool HasLidSwitch READ hasLidSwitch NOTIFY HasLidSwitchChanged) + bool hasLidSwitch() const; + + Q_PROPERTY(bool HasBattery READ hasBattery NOTIFY HasBatteryChanged) + bool hasBattery() const; + + Q_PROPERTY(double batteryPercentage READ batteryPercentage NOTIFY BatteryPercentageChanged) + double batteryPercentage() const; + + Q_PROPERTY(uint batteryStatus READ batteryStatus NOTIFY BatteryStatusChanged) + uint batteryStatus() const; + + Q_PROPERTY(quint64 batteryTimeToEmpty READ batteryTimeToEmpty NOTIFY BatteryTimeToEmptyChanged) + quint64 batteryTimeToEmpty() const; + + Q_PROPERTY(bool IsHighPerformanceSupported READ isHighPerformanceSupported NOTIFY IsHighPerformanceSupportedChanged) + bool isHighPerformanceSupported() const; + + Q_PROPERTY(bool PowerSavingModeEnabled READ powerSavingModeEnabled NOTIFY PowerSavingModeEnabledChanged) + bool powerSavingModeEnabled() const; + + Q_PROPERTY(uint PowerSavingModeBrightnessDropPercent READ powerSavingModeBrightnessDropPercent NOTIFY PowerSavingModeBrightnessDropPercentChanged) + uint powerSavingModeBrightnessDropPercent() const; + + // ── SessionWatcher ── + Q_PROPERTY(bool SessionActive READ sessionActive NOTIFY SessionActiveChanged) + bool sessionActive() const; + + // ── SessionManager ── + void requestSuspend(); + void requestShutdown(); + void requestHibernate(); + bool canSuspend(); + bool canHibernate(); + + // ── ShutdownFront ── + void requestSuspendByFront(); + + // ── LockFront ── + void showLockAuth(bool autoStart); + + // ── Login1 ── + void lockSession(const QString &sessionId); + QDBusUnixFileDescriptor inhibit(const QString &what, const QString &who, + const QString &why, const QString &mode); + + // ── BlackScreen ── + void setBlackScreenActive(bool active); + + // ── Display ── + void setBrightness(const QString &monitor, double value); + void setAndSaveBrightness(const QString &monitor, double value); + + // ── Notifications ── + uint notify(uint replaceId, const QString &appName, const QString &icon, + const QString &title, const QString &body, + const QStringList &actions, const QVariantMap &hints, int timeout); + void closeNotification(uint id); + + // ── Calendar ── + QString getFestivalMonth(int year, int month); + +signals: + // DDBusInterface 自动转发:属性名 + Changed + void OnBatteryChanged(bool value); + void HasLidSwitchChanged(bool value); + void HasBatteryChanged(bool value); + void BatteryPercentageChanged(double value); + void BatteryStatusChanged(uint value); + void BatteryTimeToEmptyChanged(quint64 value); + void IsHighPerformanceSupportedChanged(bool value); + void PowerSavingModeEnabledChanged(bool value); + void PowerSavingModeBrightnessDropPercentChanged(uint value); + void SessionActiveChanged(bool value); + + void notifyActionInvoked(uint id, const QString &actionKey); + void timeUpdate(); + void login1OwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner); + +private: + DDBusInterface *m_powerInter; + DDBusInterface *m_sessionManagerInter; + DDBusInterface *m_shutdownFrontInter; + DDBusInterface *m_login1Inter; + DDBusInterface *m_lockFrontInter; + DDBusInterface *m_blackScreenInter; + DDBusInterface *m_displayInter; + DDBusInterface *m_notificationsInter; + DDBusInterface *m_sessionWatcherInter; + DDBusInterface *m_calendarInter; + DDBusInterface *m_timedateInter; + DDBusInterface *m_freedesktopDBusInter; +}; diff --git a/src/plugin-qt/power/session/sleepinhibitor.cpp b/src/plugin-qt/power/session/sleepinhibitor.cpp new file mode 100644 index 0000000..f6d799c --- /dev/null +++ b/src/plugin-qt/power/session/sleepinhibitor.cpp @@ -0,0 +1,80 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "sleepinhibitor.h" +#include "../powerconstants.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace PowerDBus; + +SleepInhibitor::SleepInhibitor(QObject *parent) + : QObject(parent) +{ + inhibit(); + QDBusConnection::systemBus().connect( + kDaemonService, kDaemonPath, kDaemonService, "HandleForSleep", + this, SLOT(handleSleep(bool))); + QDBusConnection::systemBus().connect( + kFreedesktopDBus, kFreedesktopPath, kFreedesktopDBus, "NameOwnerChanged", + this, SLOT(onNameOwnerChanged(QString, QString, QString))); +} + +SleepInhibitor::~SleepInhibitor() +{ + unblock(); +} + +void SleepInhibitor::handleSleep(bool beforeSleep) +{ + if (beforeSleep) { + unblock(); + Q_EMIT aboutToSleep(); + } else { + Q_EMIT wokeUp(); + block(); + } +} + +void SleepInhibitor::onNameOwnerChanged(const QString &name, + const QString &, + const QString &newOwner) +{ + if (name == QLatin1String(kLogin1Service) && !newOwner.isEmpty()) + inhibit(); +} + +void SleepInhibitor::inhibit() +{ + if (m_fd >= 0) + return; + QDBusInterface iface(kLogin1Service, kLogin1Path, kLogin1Manager, + QDBusConnection::systemBus()); + QDBusReply reply = + iface.call("Inhibit", "sleep", kService, "run screen lock", "delay"); + if (reply.isValid()) { + m_fd = dup(reply.value().fileDescriptor()); + if (m_fd < 0) + qWarning("[SleepInhibitor] dup failed: %s", strerror(errno)); + } +} + +void SleepInhibitor::block() +{ + inhibit(); +} + +void SleepInhibitor::unblock() +{ + if (m_fd >= 0) { + ::close(m_fd); + m_fd = -1; + } +} diff --git a/src/plugin-qt/power/session/sleepinhibitor.h b/src/plugin-qt/power/session/sleepinhibitor.h new file mode 100644 index 0000000..c4b3c09 --- /dev/null +++ b/src/plugin-qt/power/session/sleepinhibitor.h @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include + +class SleepInhibitor : public QObject { + Q_OBJECT +public: + explicit SleepInhibitor(QObject *parent = nullptr); + ~SleepInhibitor() override; + + void block(); + void unblock(); + +Q_SIGNALS: + void aboutToSleep(); + void wokeUp(); + +private Q_SLOTS: + void handleSleep(bool beforeSleep); + void onNameOwnerChanged(const QString &name, const QString &, + const QString &newOwner); + +private: + void inhibit(); + int m_fd = -1; +}; diff --git a/src/plugin-qt/power/session/translations/plugin-power-session_az.ts b/src/plugin-qt/power/session/translations/plugin-power-session_az.ts new file mode 100644 index 0000000..518eac3 --- /dev/null +++ b/src/plugin-qt/power/session/translations/plugin-power-session_az.ts @@ -0,0 +1,34 @@ + + + + + LowPowerManager + + Battery critically low + + + + Battery low, please plug in + + + + + PowerManager + + The system will shut down automatically after %1 s + + + + Scheduled Shutdown + + + + Cancel + + + + Shut down + + + + diff --git a/src/plugin-qt/power/session/translations/plugin-power-session_bo.ts b/src/plugin-qt/power/session/translations/plugin-power-session_bo.ts new file mode 100644 index 0000000..2accfe7 --- /dev/null +++ b/src/plugin-qt/power/session/translations/plugin-power-session_bo.ts @@ -0,0 +1,34 @@ + + + + + LowPowerManager + + Battery critically low + + + + Battery low, please plug in + + + + + PowerManager + + The system will shut down automatically after %1 s + + + + Scheduled Shutdown + + + + Cancel + + + + Shut down + + + + diff --git a/src/plugin-qt/power/session/translations/plugin-power-session_ca.ts b/src/plugin-qt/power/session/translations/plugin-power-session_ca.ts new file mode 100644 index 0000000..3276eca --- /dev/null +++ b/src/plugin-qt/power/session/translations/plugin-power-session_ca.ts @@ -0,0 +1,34 @@ + + + + + LowPowerManager + + Battery critically low + + + + Battery low, please plug in + + + + + PowerManager + + The system will shut down automatically after %1 s + + + + Scheduled Shutdown + + + + Cancel + + + + Shut down + + + + diff --git a/src/plugin-qt/power/session/translations/plugin-power-session_en.ts b/src/plugin-qt/power/session/translations/plugin-power-session_en.ts new file mode 100644 index 0000000..33810df --- /dev/null +++ b/src/plugin-qt/power/session/translations/plugin-power-session_en.ts @@ -0,0 +1,34 @@ + + + + + LowPowerManager + + Battery critically low + + + + Battery low, please plug in + + + + + PowerManager + + The system will shut down automatically after %1 s + + + + Scheduled Shutdown + + + + Cancel + + + + Shut down + + + + diff --git a/src/plugin-qt/power/session/translations/plugin-power-session_en_US.ts b/src/plugin-qt/power/session/translations/plugin-power-session_en_US.ts new file mode 100644 index 0000000..d69a770 --- /dev/null +++ b/src/plugin-qt/power/session/translations/plugin-power-session_en_US.ts @@ -0,0 +1,34 @@ + + + + + LowPowerManager + + Battery critically low + + + + Battery low, please plug in + + + + + PowerManager + + The system will shut down automatically after %1 s + + + + Scheduled Shutdown + + + + Cancel + + + + Shut down + + + + diff --git a/src/plugin-qt/power/session/translations/plugin-power-session_es.ts b/src/plugin-qt/power/session/translations/plugin-power-session_es.ts new file mode 100644 index 0000000..c47b916 --- /dev/null +++ b/src/plugin-qt/power/session/translations/plugin-power-session_es.ts @@ -0,0 +1,34 @@ + + + + + LowPowerManager + + Battery critically low + + + + Battery low, please plug in + + + + + PowerManager + + The system will shut down automatically after %1 s + + + + Scheduled Shutdown + + + + Cancel + + + + Shut down + + + + diff --git a/src/plugin-qt/power/session/translations/plugin-power-session_fi.ts b/src/plugin-qt/power/session/translations/plugin-power-session_fi.ts new file mode 100644 index 0000000..e85bcc4 --- /dev/null +++ b/src/plugin-qt/power/session/translations/plugin-power-session_fi.ts @@ -0,0 +1,34 @@ + + + + + LowPowerManager + + Battery critically low + + + + Battery low, please plug in + + + + + PowerManager + + The system will shut down automatically after %1 s + + + + Scheduled Shutdown + + + + Cancel + + + + Shut down + + + + diff --git a/src/plugin-qt/power/session/translations/plugin-power-session_fr.ts b/src/plugin-qt/power/session/translations/plugin-power-session_fr.ts new file mode 100644 index 0000000..ae60744 --- /dev/null +++ b/src/plugin-qt/power/session/translations/plugin-power-session_fr.ts @@ -0,0 +1,34 @@ + + + + + LowPowerManager + + Battery critically low + + + + Battery low, please plug in + + + + + PowerManager + + The system will shut down automatically after %1 s + + + + Scheduled Shutdown + + + + Cancel + + + + Shut down + + + + diff --git a/src/plugin-qt/power/session/translations/plugin-power-session_hu.ts b/src/plugin-qt/power/session/translations/plugin-power-session_hu.ts new file mode 100644 index 0000000..3ec590b --- /dev/null +++ b/src/plugin-qt/power/session/translations/plugin-power-session_hu.ts @@ -0,0 +1,34 @@ + + + + + LowPowerManager + + Battery critically low + + + + Battery low, please plug in + + + + + PowerManager + + The system will shut down automatically after %1 s + + + + Scheduled Shutdown + + + + Cancel + + + + Shut down + + + + diff --git a/src/plugin-qt/power/session/translations/plugin-power-session_it.ts b/src/plugin-qt/power/session/translations/plugin-power-session_it.ts new file mode 100644 index 0000000..41584ad --- /dev/null +++ b/src/plugin-qt/power/session/translations/plugin-power-session_it.ts @@ -0,0 +1,34 @@ + + + + + LowPowerManager + + Battery critically low + + + + Battery low, please plug in + + + + + PowerManager + + The system will shut down automatically after %1 s + + + + Scheduled Shutdown + + + + Cancel + + + + Shut down + + + + diff --git a/src/plugin-qt/power/session/translations/plugin-power-session_ja.ts b/src/plugin-qt/power/session/translations/plugin-power-session_ja.ts new file mode 100644 index 0000000..92df2f4 --- /dev/null +++ b/src/plugin-qt/power/session/translations/plugin-power-session_ja.ts @@ -0,0 +1,34 @@ + + + + + LowPowerManager + + Battery critically low + + + + Battery low, please plug in + + + + + PowerManager + + The system will shut down automatically after %1 s + + + + Scheduled Shutdown + + + + Cancel + + + + Shut down + + + + diff --git a/src/plugin-qt/power/session/translations/plugin-power-session_ko.ts b/src/plugin-qt/power/session/translations/plugin-power-session_ko.ts new file mode 100644 index 0000000..81025d9 --- /dev/null +++ b/src/plugin-qt/power/session/translations/plugin-power-session_ko.ts @@ -0,0 +1,34 @@ + + + + + LowPowerManager + + Battery critically low + + + + Battery low, please plug in + + + + + PowerManager + + The system will shut down automatically after %1 s + + + + Scheduled Shutdown + + + + Cancel + + + + Shut down + + + + diff --git a/src/plugin-qt/power/session/translations/plugin-power-session_nb_NO.ts b/src/plugin-qt/power/session/translations/plugin-power-session_nb_NO.ts new file mode 100644 index 0000000..738a922 --- /dev/null +++ b/src/plugin-qt/power/session/translations/plugin-power-session_nb_NO.ts @@ -0,0 +1,34 @@ + + + + + LowPowerManager + + Battery critically low + + + + Battery low, please plug in + + + + + PowerManager + + The system will shut down automatically after %1 s + + + + Scheduled Shutdown + + + + Cancel + + + + Shut down + + + + diff --git a/src/plugin-qt/power/session/translations/plugin-power-session_pl.ts b/src/plugin-qt/power/session/translations/plugin-power-session_pl.ts new file mode 100644 index 0000000..ab1e395 --- /dev/null +++ b/src/plugin-qt/power/session/translations/plugin-power-session_pl.ts @@ -0,0 +1,34 @@ + + + + + LowPowerManager + + Battery critically low + + + + Battery low, please plug in + + + + + PowerManager + + The system will shut down automatically after %1 s + + + + Scheduled Shutdown + + + + Cancel + + + + Shut down + + + + diff --git a/src/plugin-qt/power/session/translations/plugin-power-session_pt_BR.ts b/src/plugin-qt/power/session/translations/plugin-power-session_pt_BR.ts new file mode 100644 index 0000000..a816d45 --- /dev/null +++ b/src/plugin-qt/power/session/translations/plugin-power-session_pt_BR.ts @@ -0,0 +1,34 @@ + + + + + LowPowerManager + + Battery critically low + + + + Battery low, please plug in + + + + + PowerManager + + The system will shut down automatically after %1 s + + + + Scheduled Shutdown + + + + Cancel + + + + Shut down + + + + diff --git a/src/plugin-qt/power/session/translations/plugin-power-session_ru.ts b/src/plugin-qt/power/session/translations/plugin-power-session_ru.ts new file mode 100644 index 0000000..f4ca3f7 --- /dev/null +++ b/src/plugin-qt/power/session/translations/plugin-power-session_ru.ts @@ -0,0 +1,34 @@ + + + + + LowPowerManager + + Battery critically low + + + + Battery low, please plug in + + + + + PowerManager + + The system will shut down automatically after %1 s + + + + Scheduled Shutdown + + + + Cancel + + + + Shut down + + + + diff --git a/src/plugin-qt/power/session/translations/plugin-power-session_uk.ts b/src/plugin-qt/power/session/translations/plugin-power-session_uk.ts new file mode 100644 index 0000000..0044fe0 --- /dev/null +++ b/src/plugin-qt/power/session/translations/plugin-power-session_uk.ts @@ -0,0 +1,34 @@ + + + + + LowPowerManager + + Battery critically low + + + + Battery low, please plug in + + + + + PowerManager + + The system will shut down automatically after %1 s + + + + Scheduled Shutdown + + + + Cancel + + + + Shut down + + + + diff --git a/src/plugin-qt/power/session/translations/plugin-power-session_zh_CN.ts b/src/plugin-qt/power/session/translations/plugin-power-session_zh_CN.ts new file mode 100644 index 0000000..6fe0250 --- /dev/null +++ b/src/plugin-qt/power/session/translations/plugin-power-session_zh_CN.ts @@ -0,0 +1,34 @@ + + + + + LowPowerManager + + Battery critically low + 电池电量耗尽 + + + Battery low, please plug in + 电池电量低,请连接电源 + + + + PowerManager + + The system will shut down automatically after %1 s + 系统将在%1秒后自动关机 + + + Scheduled Shutdown + 定时关机 + + + Cancel + 取消 + + + Shut down + 关机 + + + diff --git a/src/plugin-qt/power/session/translations/plugin-power-session_zh_HK.ts b/src/plugin-qt/power/session/translations/plugin-power-session_zh_HK.ts new file mode 100644 index 0000000..ea70c93 --- /dev/null +++ b/src/plugin-qt/power/session/translations/plugin-power-session_zh_HK.ts @@ -0,0 +1,34 @@ + + + + + LowPowerManager + + Battery critically low + 電池電量耗盡 + + + Battery low, please plug in + 電池電量低,請連接電源 + + + + PowerManager + + The system will shut down automatically after %1 s + 系統將在%1秒後自動關機 + + + Scheduled Shutdown + 定時關機 + + + Cancel + 取消 + + + Shut down + 關機 + + + \ No newline at end of file diff --git a/src/plugin-qt/power/session/translations/plugin-power-session_zh_TW.ts b/src/plugin-qt/power/session/translations/plugin-power-session_zh_TW.ts new file mode 100644 index 0000000..120ed7c --- /dev/null +++ b/src/plugin-qt/power/session/translations/plugin-power-session_zh_TW.ts @@ -0,0 +1,34 @@ + + + + + LowPowerManager + + Battery critically low + 電池電量耗盡 + + + Battery low, please plug in + 電池電量低,請連線電源 + + + + PowerManager + + The system will shut down automatically after %1 s + 系統將在%1秒後自動關機 + + + Scheduled Shutdown + 定時關機 + + + Cancel + 取消 + + + Shut down + 關機 + + + \ No newline at end of file diff --git a/src/plugin-qt/power/system/CMakeLists.txt b/src/plugin-qt/power/system/CMakeLists.txt new file mode 100644 index 0000000..fb2bc42 --- /dev/null +++ b/src/plugin-qt/power/system/CMakeLists.txt @@ -0,0 +1,47 @@ +# SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +# +# SPDX-License-Identifier: LGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.16) + +set(BIN_NAME "plugin-power-system") +project(${BIN_NAME}) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +include(GNUInstallDirs) + +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core DBus) +find_package(Dtk${DTK_VERSION_MAJOR} REQUIRED COMPONENTS Core) +find_package(PkgConfig REQUIRED) +pkg_check_modules(UDEV REQUIRED libudev) + +file(GLOB_RECURSE SRCS "*.cpp" "*.h") + +add_library(${BIN_NAME} MODULE ${SRCS}) + +target_include_directories(${BIN_NAME} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${UDEV_INCLUDE_DIRS} +) + +target_link_libraries(${BIN_NAME} PRIVATE + Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::DBus + Dtk${DTK_VERSION_MAJOR}::Core + ${UDEV_LIBRARIES} +) + +install(TARGETS ${BIN_NAME} + DESTINATION ${CMAKE_INSTALL_LIBDIR}/deepin-service-manager/ +) +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/misc/plugin-power-system.json + DESTINATION ${CMAKE_INSTALL_FULL_DATADIR}/deepin-service-manager/system/ +) +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/misc/org.deepin.dde.Power1.service + DESTINATION lib/systemd/system/ +) +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/misc/dbus/org.deepin.dde.Power1.service + DESTINATION ${CMAKE_INSTALL_FULL_DATADIR}/dbus-1/system-services/ +) diff --git a/src/plugin-qt/power/system/batterymanager.cpp b/src/plugin-qt/power/system/batterymanager.cpp new file mode 100644 index 0000000..c2b1088 --- /dev/null +++ b/src/plugin-qt/power/system/batterymanager.cpp @@ -0,0 +1,212 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "batterymanager.h" +#include "powermanager.h" +#include "../powerconstants.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +Q_DECLARE_LOGGING_CATEGORY(logPowerSystem) + +using namespace PowerDBus; + +BatteryManager::BatteryManager(SystemPowerManager *mgr, QObject *parent) + : QObject(parent) + , m_mgr(mgr) +{ + probe(); + initUdev(); + + m_batteryPollTimer = new QTimer(this); + m_batteryPollTimer->setInterval(60000); + connect(m_batteryPollTimer, &QTimer::timeout, this, &BatteryManager::pollBattery); + m_batteryPollTimer->start(); +} + +BatteryManager::~BatteryManager() +{ + if (m_udevMon) { + udev_monitor_unref(m_udevMon); + m_udevMon = nullptr; + } + if (m_udev) { + udev_unref(m_udev); + m_udev = nullptr; + } +} + +void BatteryManager::probe() +{ + QDir ps("/sys/class/power_supply"); + for (const auto &entry : ps.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { + QFile tf("/sys/class/power_supply/" + entry + "/type"); + if (tf.open(QIODevice::ReadOnly)) { + if (tf.readAll().trimmed() == "Battery") { m_hasBattery = true; break; } + } + } + if (m_hasBattery) { m_mgr->updateHasBattery(true); pollBattery(); } + // AC initial state will be picked up by udev on next event, + // or by the fallback poll if no udev events arrive. +} + +void BatteryManager::pollBattery() +{ + if (!m_hasBattery) { + QDir ps("/sys/class/power_supply"); + for (const auto &entry : ps.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { + QFile tf("/sys/class/power_supply/" + entry + "/type"); + if (tf.open(QIODevice::ReadOnly) && tf.readAll().trimmed() == "Battery") + { m_hasBattery = true; m_mgr->updateHasBattery(true); break; } + } + } + if (!m_hasBattery) return; + + double pct = 100.0; uint status = 0; quint64 tte = 0; double cap = 100.0; + QDir ps("/sys/class/power_supply"); + for (const auto &entry : ps.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { + QFile tf("/sys/class/power_supply/" + entry + "/type"); + if (!tf.open(QIODevice::ReadOnly) || tf.readAll().trimmed() != "Battery") continue; + auto readInt = [&](const QString &f) -> int { + QFile ff("/sys/class/power_supply/" + entry + "/" + f); + if (ff.open(QIODevice::ReadOnly)) return ff.readAll().trimmed().toInt(); + return 0; + }; + QFile cf("/sys/class/power_supply/" + entry + "/capacity"); + if (cf.open(QIODevice::ReadOnly)) { pct = cf.readAll().trimmed().toInt(); cf.close(); } + QFile sf("/sys/class/power_supply/" + entry + "/status"); + if (sf.open(QIODevice::ReadOnly)) { + QString s = sf.readAll().trimmed(); sf.close(); + if (s == "Charging") status = 1; + else if (s == "Discharging") { status = 2; int pw = readInt("power_now"); + tte = pw > 0 ? static_cast(readInt("energy_now")) * 3600 / pw : 0; } + else if (s == "Full") status = 4; + } + int ef = readInt("energy_full"), efd = readInt("energy_full_design"); + if (efd > 0) cap = ef * 100.0 / efd; + break; + } + bool chg = (pct != m_percentage || status != m_status || tte != m_timeToEmpty || cap != m_capacity); + m_percentage = pct; m_status = status; m_timeToEmpty = tte; m_capacity = cap; + if (chg) { m_mgr->updateBatteryInfo(pct, status, tte, m_timeToFull, cap); Q_EMIT batteryChanged(); } +} + +// ── udev-based AC / battery monitoring ────────────────────────── + +void BatteryManager::initUdev() +{ + m_udev = udev_new(); + if (!m_udev) { + qWarning(logPowerSystem) << "udev_new failed"; + return; + } + + m_udevMon = udev_monitor_new_from_netlink(m_udev, "udev"); + if (!m_udevMon) { + qWarning(logPowerSystem) << "udev_monitor_new_from_netlink failed"; + return; + } + + if (udev_monitor_filter_add_match_subsystem_devtype(m_udevMon, "power_supply", nullptr) < 0) { + qWarning(logPowerSystem) << "udev_monitor_filter_add_match failed"; + return; + } + + if (udev_monitor_enable_receiving(m_udevMon) < 0) { + qWarning(logPowerSystem) << "udev_monitor_enable_receiving failed"; + return; + } + + int fd = udev_monitor_get_fd(m_udevMon); + if (fd < 0) { + qWarning(logPowerSystem) << "udev_monitor_get_fd failed"; + return; + } + + m_udevNotifier = new QSocketNotifier(fd, QSocketNotifier::Read, this); + connect(m_udevNotifier, &QSocketNotifier::activated, this, &BatteryManager::onUdevEvent); +} + +void BatteryManager::onUdevEvent() +{ + if (!m_udevMon) return; + + auto *dev = udev_monitor_receive_device(m_udevMon); + if (!dev) return; + + const char *action = udev_device_get_action(dev); + const char *type = udev_device_get_sysattr_value(dev, "type"); + + if (!action || !type) { + udev_device_unref(dev); + return; + } + + if (strcmp(action, "change") == 0) { + if (strcmp(type, "Mains") == 0 || strcmp(type, "USB") == 0) { + refreshACFromUdev(dev); + scheduleBatteryRefreshAfterAC(); + } else if (strcmp(type, "Battery") == 0) { + refreshBatteryFromUdev(dev); + } + } else if (strcmp(action, "add") == 0) { + if (strcmp(type, "Battery") == 0) { + m_hasBattery = true; + m_mgr->updateHasBattery(true); + pollBattery(); + } + } else if (strcmp(action, "remove") == 0) { + if (strcmp(type, "Battery") == 0) { + // re-probe to see if any batteries remain + m_hasBattery = false; + QDir ps("/sys/class/power_supply"); + for (const auto &entry : ps.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { + QFile tf("/sys/class/power_supply/" + entry + "/type"); + if (tf.open(QIODevice::ReadOnly) && tf.readAll().trimmed() == "Battery") { + m_hasBattery = true; + break; + } + } + if (!m_hasBattery) + m_mgr->updateHasBattery(false); + } + } + + udev_device_unref(dev); +} + +void BatteryManager::refreshACFromUdev(struct udev_device *dev) +{ + const char *online = udev_device_get_sysattr_value(dev, "online"); + bool onBatt = !(online && strcmp(online, "1") == 0); + if (onBatt != m_onBattery) { + m_onBattery = onBatt; + Q_EMIT onBatteryChanged(onBatt); + } +} + +void BatteryManager::refreshBatteryFromUdev(struct udev_device *) +{ + // udev notified us of a battery change; full poll picks up all values + pollBattery(); +} + +// AC 变更后, 在 1s, 3s, 5s, 10s, 15s, ... 60s 递进刷新电池 +void BatteryManager::scheduleBatteryRefreshAfterAC() +{ + static const int delays[] = {1000, 3000, 5000, 10000, 15000, 20000, + 25000, 30000, 35000, 40000, 45000, 50000, + 55000, 60000}; + for (int d : delays) + QTimer::singleShot(d, this, &BatteryManager::pollBattery); +} diff --git a/src/plugin-qt/power/system/batterymanager.h b/src/plugin-qt/power/system/batterymanager.h new file mode 100644 index 0000000..c9f0d0d --- /dev/null +++ b/src/plugin-qt/power/system/batterymanager.h @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: LGPL-3.0-or-later +#pragma once +#include + +struct udev; +struct udev_monitor; +class QSocketNotifier; +class QTimer; +class SystemPowerManager; + +class BatteryManager : public QObject { + Q_OBJECT +public: + explicit BatteryManager(SystemPowerManager *mgr, QObject *parent = nullptr); + ~BatteryManager() override; + void probe(); + bool hasBattery() const { return m_hasBattery; } + +Q_SIGNALS: + void batteryChanged(); + void onBatteryChanged(bool onBattery); + +private: + void pollBattery(); + void initUdev(); + void onUdevEvent(); + void refreshACFromUdev(struct udev_device *dev); + void refreshBatteryFromUdev(struct udev_device *dev); + void scheduleBatteryRefreshAfterAC(); + + SystemPowerManager *m_mgr = nullptr; + bool m_hasBattery = false; + bool m_onBattery = false; + double m_percentage = 100.0; + uint m_status = 0; + quint64 m_timeToEmpty = 0; + quint64 m_timeToFull = 0; + double m_capacity = 100.0; + + struct udev *m_udev = nullptr; + struct udev_monitor *m_udevMon = nullptr; + QSocketNotifier *m_udevNotifier = nullptr; + QTimer *m_batteryPollTimer = nullptr; +}; diff --git a/src/plugin-qt/power/system/misc/dbus/org.deepin.dde.Power1.service b/src/plugin-qt/power/system/misc/dbus/org.deepin.dde.Power1.service new file mode 100644 index 0000000..2880344 --- /dev/null +++ b/src/plugin-qt/power/system/misc/dbus/org.deepin.dde.Power1.service @@ -0,0 +1,5 @@ +[D-BUS Service] +Name=org.deepin.dde.Power1 +Exec=/usr/bin/deepin-service-manager -n org.deepin.dde.Power1 +User=deepin-daemon +SystemdService=org.deepin.dde.Power1.service diff --git a/src/plugin-qt/power/system/misc/org.deepin.dde.Power1.service b/src/plugin-qt/power/system/misc/org.deepin.dde.Power1.service new file mode 100644 index 0000000..e0fba96 --- /dev/null +++ b/src/plugin-qt/power/system/misc/org.deepin.dde.Power1.service @@ -0,0 +1,11 @@ +[Unit] +Description=org.deepin.dde.Power1 (System) +RefuseManualStart=no +RefuseManualStop=no + +[Service] +Type=dbus +BusName=org.deepin.dde.Power1 +ExecStart=/usr/bin/deepin-service-manager -n org.deepin.dde.Power1 +Restart=on-failure +Slice=system.slice diff --git a/src/plugin-qt/power/system/misc/plugin-power-system.json b/src/plugin-qt/power/system/misc/plugin-power-system.json new file mode 100644 index 0000000..e0ee714 --- /dev/null +++ b/src/plugin-qt/power/system/misc/plugin-power-system.json @@ -0,0 +1,12 @@ +{ + "name": "org.deepin.dde.Power1", + "libPath": "libplugin-power-system.so", + "group": "dde", + "startType": "Resident", + "pluginType": "qt", + "policy": [ + { + "path": "/org/deepin/dde/Power1" + } + ] +} diff --git a/src/plugin-qt/power/system/plugin-power-system.cpp b/src/plugin-qt/power/system/plugin-power-system.cpp new file mode 100644 index 0000000..25c5a5c --- /dev/null +++ b/src/plugin-qt/power/system/plugin-power-system.cpp @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "powermanager.h" +#include + +static SystemPowerManager *g_mgr = nullptr; + +extern "C" int DSMRegister(const char *name, void *data) +{ + auto conn = reinterpret_cast(data); + g_mgr = new SystemPowerManager(conn, QString::fromLatin1(name)); + return g_mgr->initialize() ? 0 : -1; +} + +extern "C" int DSMUnRegister(const char *, void *) +{ + delete g_mgr; + g_mgr = nullptr; + return 0; +} diff --git a/src/plugin-qt/power/system/powermanager.cpp b/src/plugin-qt/power/system/powermanager.cpp new file mode 100644 index 0000000..5c10e19 --- /dev/null +++ b/src/plugin-qt/power/system/powermanager.cpp @@ -0,0 +1,373 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "powermanager.h" +#include "batterymanager.h" +#include "systemdbusproxy.h" +#include "../powerconstants.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace PowerDBus; +using namespace PowerFS; +using namespace PowerDConfig; + +Q_LOGGING_CATEGORY(logPowerSystem, "dde.power.system") + +SystemPowerManager::SystemPowerManager(QDBusConnection *conn, const QString &svc, + QObject *p) + : QObject(p) + , m_conn(conn) +{ + Q_UNUSED(svc); +} + +bool SystemPowerManager::initialize() +{ + bool ok = m_conn->registerObject(kPath, this, + QDBusConnection::ExportAllSlots | + QDBusConnection::ExportAllSignals | + QDBusConnection::ExportAllProperties); + if (!ok) { + qWarning(logPowerSystem) << "registerObject failed"; + return false; + } + + initLidSwitch(); + initPowerSavingDConfig(); + initCpuGovernor(); + + auto *battery = new BatteryManager(this, this); + connect(battery, &BatteryManager::onBatteryChanged, this, [this](bool onBatt) { + qDebug(logPowerSystem) << "onBatteryChanged:" << onBatt << " prev=" << m_onBattery; + if (m_onBattery != onBatt) { + m_onBattery = onBatt; + Q_EMIT onBatteryChanged(); + recalcBatteryLow(); + updatePowerMode(false); + } + }); + connect(battery, &BatteryManager::batteryChanged, this, [battery]() { + battery->probe(); + }); + + return true; +} + +void SystemPowerManager::initLidSwitch() +{ + SystemDBusProxy proxy(this); + QString chassis = proxy.chassis(); + if (chassis != "laptop" && chassis != "convertible") + return; + + m_hasLidSwitch = proxy.lidIsPresent(); + if (!m_hasLidSwitch) { + // 后备: 尝试 /proc/acpi/button/lid/LID/state + QFile f(kLidStatePath); + if (f.exists()) { + m_hasLidSwitch = true; + } + } + + if (!m_hasLidSwitch) + return; + + Q_EMIT hasLidSwitchChanged(); + + // 读取初始状态 + QDBusInterface upower(kUPowerService, kUPowerPath, "org.freedesktop.DBus.Properties", + QDBusConnection::systemBus()); + if (upower.isValid()) { + QDBusReply reply = upower.call("Get", kUPowerService, "LidIsClosed"); + if (reply.isValid()) { + bool closed = reply.value().toBool(); + m_lidClosed = closed; + Q_EMIT lidClosedChanged(); + handleLidSwitchEvent(closed); + } + } + + // 持续监听 UPower 的 PropertiesChanged 信号,确保每次合盖/开盖都能收到通知 + QDBusConnection::systemBus().connect( + kUPowerService, kUPowerPath, + "org.freedesktop.DBus.Properties", "PropertiesChanged", + this, SLOT(onUPowerPropertiesChanged(QString,QVariantMap,QStringList))); +} + +void SystemPowerManager::onUPowerPropertiesChanged(const QString &interface, + const QVariantMap &changed, + const QStringList &) +{ + if (interface != QLatin1String(kUPowerService)) + return; + + if (changed.contains("LidIsClosed")) { + bool closed = changed.value("LidIsClosed").toBool(); + if (m_lidClosed != closed) { + m_lidClosed = closed; + Q_EMIT lidClosedChanged(); + } + handleLidSwitchEvent(closed); + } +} + +void SystemPowerManager::handleLidSwitchEvent(bool closed) +{ + qDebug(logPowerSystem) << "handleLidSwitchEvent: closed=" << closed; + if (closed) { + Q_EMIT LidClosed(); + } else { + Q_EMIT LidOpened(); + } +} + +void SystemPowerManager::initPowerSavingDConfig() +{ + m_config = Dtk::Core::DConfig::create(kAppId, kPowerName, "", this); + if (!m_config) return; + + auto load = [this](const QString &k) { + QVariant v = m_config->value(k); + qDebug(logPowerSystem) << "DConfig load: key=" << k << " value=" << v; + if (k == QLatin1String(kPowerSavingModeEnabled)) + setPowerSavingModeEnabled(v.toBool()); + else if (k == QLatin1String(kPowerSavingModeAuto)) + setPowerSavingModeAuto(v.toBool()); + else if (k == QLatin1String(kPowerSavingModeAutoWhenBatteryLow)) + setPowerSavingModeAutoWhenBatteryLow(v.toBool()); + else if (k == QLatin1String(kPowerSavingModeBrightnessDropPercent)) + setPowerSavingModeBrightnessDropPercent(v.toUInt()); + else if (k == QLatin1String(kPowerSavingModeAutoBatteryPercent)) + setPowerSavingModeAutoBatteryPercent(v.toUInt()); + else if (k == QLatin1String(kMode)) { + QString m = v.toString(); + static const QStringList valid = {"balance", "powersave", "performance"}; + if (valid.contains(m) && !m.isEmpty()) + setMode(m); + } + else if (k == QLatin1String(kLastMode)) { + QString lm = v.toString(); + if (!lm.isEmpty()) m_lastMode = lm; + } + }; + + load(kPowerSavingModeEnabled); + load(kMode); + load(kPowerSavingModeAuto); + load(kPowerSavingModeAutoWhenBatteryLow); + load(kPowerSavingModeBrightnessDropPercent); + load(kPowerSavingModeAutoBatteryPercent); + load(kLastMode); + + updatePowerMode(true); + + connect(m_config, &Dtk::Core::DConfig::valueChanged, this, + [this, load](const QString &key) { + qDebug(logPowerSystem) << "DConfig valueChanged: key=" << key; + load(key); + if (key == QLatin1String(kMode)) { + QString newMode = m_config->value(key).toString(); + setMode(newMode); + return; + } + if (key == QLatin1String(kPowerSavingModeAutoBatteryPercent)) { + recalcBatteryLow(); + } + if (key == QLatin1String(kPowerSavingModeAutoWhenBatteryLow) + || key == QLatin1String(kPowerSavingModeAutoBatteryPercent)) { + recalcBatteryLow(); + updatePowerMode(false); + } + }); +} + +void SystemPowerManager::initCpuGovernor() +{ + QFile gf("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor"); + if (gf.open(QIODevice::ReadOnly)) { + QString gov = gf.readAll().trimmed(); + gf.close(); + if (!gov.isEmpty()) + setCpuGovernor(gov); + } + + QFile af("/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors"); + if (af.open(QIODevice::ReadOnly)) { + QStringList avail = QString(af.readAll()).split(' ', Qt::SkipEmptyParts); + af.close(); + bool hp = avail.contains("performance") || avail.contains("ondemand"); + bool ps = avail.contains("powersave") || avail.contains("ondemand"); + if (m_hpSupported != hp) { + m_hpSupported = hp; + Q_EMIT isHighPerformanceSupportedChanged(); + } + + if (m_psSupported != ps) { + m_psSupported = ps; + Q_EMIT isPowerSaveSupportedChanged(); + } + } + + QFile bf("/sys/devices/system/cpu/cpufreq/boost"); + if (bf.open(QIODevice::ReadOnly)) { + setCpuBoost(bf.readAll().trimmed() == "1"); + bf.close(); + } +} + +void SystemPowerManager::updateHasBattery(bool has) +{ + if (m_hasBattery != has) { + m_hasBattery = has; + Q_EMIT hasBatteryChanged(); + } +} + +void SystemPowerManager::updateBatteryInfo(double pct, uint status, + quint64 tte, quint64 ttf, double cap) +{ + if (m_batteryPercentage != pct) { + qWarning(logPowerSystem) << "batteryPercentage changed:" << m_batteryPercentage << "→" << pct; + m_batteryPercentage = pct; + Q_EMIT batteryPercentageChanged(); + recalcBatteryLow(); + updatePowerMode(false); + } + + if (m_batteryStatus != status) { + m_batteryStatus = status; + Q_EMIT batteryStatusChanged(); + } + + if (m_batteryTimeToEmpty != tte) + { + m_batteryTimeToEmpty = tte; + Q_EMIT batteryTimeToEmptyChanged(); + } + + if (m_batteryTimeToFull != ttf) { + m_batteryTimeToFull = ttf; + Q_EMIT batteryTimeToFullChanged(); + } + if (m_batteryCapacity != cap) { + m_batteryCapacity = cap; + Q_EMIT batteryCapacityChanged(); + } +} + +QList SystemPowerManager::GetBatteries() { + return {}; +} + +void SystemPowerManager::Refresh() +{ + RefreshBatteries(); + RefreshMains(); +} + +void SystemPowerManager::RefreshBatteries() +{ + +} + +void SystemPowerManager::RefreshMains() +{ + +} + +void SystemPowerManager::setMode(const QString &v) +{ + static const QStringList valid = {"balance", "powersave", "performance"}; + if (!valid.contains(v)) { + qWarning(logPowerSystem) << "invalid mode: " << v; + return; + } + + if (m_mode == v) { + qWarning(logPowerSystem) << "setMode: same mode " << v << ", skip"; + return; + } + m_mode = v; + Q_EMIT modeChanged(); + + QString dspc; + if (v == "performance") { + dspc = "performance"; + } else if (v == "powersave") { + dspc = "saving"; + } else { + dspc = "balance"; + } + + if (!QProcess::startDetached("/usr/sbin/deepin-power-control", {"set", dspc})) + qWarning(logPowerSystem) << "Failed to start deepin-power-control set" << dspc; + + setPowerSavingModeEnabled(v == "powersave"); + + if (m_lastMode != v && v != "powersave") { + m_lastMode = v; + if (m_config) m_config->setValue(kLastMode, v); + } +} + +void SystemPowerManager::SetCpuGovernor(const QString &gov) +{ + setCpuGovernor(gov); +} + +void SystemPowerManager::SetCpuBoost(bool on) +{ + setCpuBoost(on); +} + +void SystemPowerManager::LockCpuFreq(const QString &gov, int lockTime) +{ + Q_UNUSED(gov); + Q_UNUSED(lockTime); +} + +void SystemPowerManager::recalcBatteryLow() +{ + bool old = m_batteryLow; + m_batteryLow = m_onBattery && m_batteryPercentage > 0 + && m_batteryPercentage <= static_cast(m_psmAutoPct); + qDebug(logPowerSystem) << "recalcBatteryLow:" << old << "→" << m_batteryLow + << "(OnBattery=" << m_onBattery + << " pct=" << m_batteryPercentage + << " threshold=" << m_psmAutoPct << ")"; +} + +void SystemPowerManager::updatePowerMode(bool init) +{ + bool enablePowerSave = m_psmAuto && m_onBattery; + bool enableLowPower = m_psmAutoLow && m_batteryLow; + + qDebug(logPowerSystem) << "updatePowerMode: init=" << init + << " PSMAuto=" << m_psmAuto + << " OnBattery=" << m_onBattery + << " PSMAutoLow=" << m_psmAutoLow + << " BatteryLow=" << m_batteryLow + << " currentMode=" << m_mode + << " lastMode=" << m_lastMode; + + if (!m_psmAuto && !m_psmAutoLow && !init) { + qDebug(logPowerSystem) << " → both auto off, restoring lastMode:" << m_lastMode; + setMode(m_lastMode); + return; + } + + QString target = init ? m_mode : m_lastMode; + if (enablePowerSave || enableLowPower) + target = "powersave"; + qDebug(logPowerSystem) << " → setMode(" << target << ")"; + setMode(target); +} diff --git a/src/plugin-qt/power/system/powermanager.h b/src/plugin-qt/power/system/powermanager.h new file mode 100644 index 0000000..6a8793d --- /dev/null +++ b/src/plugin-qt/power/system/powermanager.h @@ -0,0 +1,146 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later +#pragma once +#include +#include +#include +#include +#include + +class SystemPowerManager : public QObject { + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.deepin.dde.Power1") + + Q_PROPERTY(bool OnBattery READ onBattery NOTIFY onBatteryChanged) + Q_PROPERTY(bool HasBattery READ hasBattery NOTIFY hasBatteryChanged) + Q_PROPERTY(bool HasLidSwitch READ hasLidSwitch NOTIFY hasLidSwitchChanged) + Q_PROPERTY(bool LidClosed READ lidClosed NOTIFY lidClosedChanged) + Q_PROPERTY(double BatteryPercentage READ batteryPercentage NOTIFY batteryPercentageChanged) + Q_PROPERTY(uint BatteryStatus READ batteryStatus NOTIFY batteryStatusChanged) + Q_PROPERTY(quint64 BatteryTimeToEmpty READ batteryTimeToEmpty NOTIFY batteryTimeToEmptyChanged) + Q_PROPERTY(quint64 BatteryTimeToFull READ batteryTimeToFull NOTIFY batteryTimeToFullChanged) + Q_PROPERTY(double BatteryCapacity READ batteryCapacity NOTIFY batteryCapacityChanged) + Q_PROPERTY(bool PowerSavingModeEnabled READ powerSavingModeEnabled + WRITE setPowerSavingModeEnabled NOTIFY powerSavingModeEnabledChanged) + Q_PROPERTY(bool PowerSavingModeAuto READ powerSavingModeAuto + WRITE setPowerSavingModeAuto NOTIFY powerSavingModeAutoChanged) + Q_PROPERTY(bool PowerSavingModeAutoWhenBatteryLow READ powerSavingModeAutoWhenBatteryLow + WRITE setPowerSavingModeAutoWhenBatteryLow NOTIFY powerSavingModeAutoWhenBatteryLowChanged) + Q_PROPERTY(uint PowerSavingModeBrightnessDropPercent READ powerSavingModeBrightnessDropPercent + WRITE setPowerSavingModeBrightnessDropPercent NOTIFY powerSavingModeBrightnessDropPercentChanged) + Q_PROPERTY(uint PowerSavingModeAutoBatteryPercent READ powerSavingModeAutoBatteryPercent + WRITE setPowerSavingModeAutoBatteryPercent NOTIFY powerSavingModeAutoBatteryPercentChanged) + Q_PROPERTY(QString Mode READ mode WRITE setMode NOTIFY modeChanged) + Q_PROPERTY(bool IsHighPerformanceSupported READ isHighPerformanceSupported NOTIFY isHighPerformanceSupportedChanged) + Q_PROPERTY(bool IsPowerSaveSupported READ isPowerSaveSupported NOTIFY isPowerSaveSupportedChanged) + Q_PROPERTY(QString CpuGovernor READ cpuGovernor WRITE setCpuGovernor NOTIFY cpuGovernorChanged) + Q_PROPERTY(bool CpuBoost READ cpuBoost WRITE setCpuBoost NOTIFY cpuBoostChanged) + +public: + explicit SystemPowerManager(QDBusConnection *conn, const QString &svc, QObject *p = nullptr); + bool initialize(); + + void updateHasBattery(bool has); + void updateBatteryInfo(double pct, uint status, quint64 tte, quint64 ttf, double cap); + void initLidSwitch(); + void initPowerSavingDConfig(); + void initCpuGovernor(); + +private Q_SLOTS: + void onUPowerPropertiesChanged(const QString &interface, + const QVariantMap &changed, + const QStringList &invalidated); + + bool onBattery() const { return m_onBattery; } + bool hasBattery() const { return m_hasBattery; } + bool hasLidSwitch() const { return m_hasLidSwitch; } + bool lidClosed() const { return m_lidClosed; } + double batteryPercentage() const { return m_batteryPercentage; } + uint batteryStatus() const { return m_batteryStatus; } + quint64 batteryTimeToEmpty() const { return m_batteryTimeToEmpty; } + quint64 batteryTimeToFull() const { return m_batteryTimeToFull; } + double batteryCapacity() const { return m_batteryCapacity; } + bool powerSavingModeEnabled() const { return m_psmEnabled; } + void setPowerSavingModeEnabled(bool v) { if (m_psmEnabled != v) { m_psmEnabled = v; Q_EMIT powerSavingModeEnabledChanged(); } } + bool powerSavingModeAuto() const { return m_psmAuto; } + void setPowerSavingModeAuto(bool v) { if (m_psmAuto != v) { m_psmAuto = v; Q_EMIT powerSavingModeAutoChanged(); } } + bool powerSavingModeAutoWhenBatteryLow() const { return m_psmAutoLow; } + void setPowerSavingModeAutoWhenBatteryLow(bool v) { if (m_psmAutoLow != v) { m_psmAutoLow = v; Q_EMIT powerSavingModeAutoWhenBatteryLowChanged(); } } + uint powerSavingModeBrightnessDropPercent() const { return m_psmDrop; } + void setPowerSavingModeBrightnessDropPercent(uint v) { if (m_psmDrop != v) { m_psmDrop = v; Q_EMIT powerSavingModeBrightnessDropPercentChanged(); } } + uint powerSavingModeAutoBatteryPercent() const { return m_psmAutoPct; } + void setPowerSavingModeAutoBatteryPercent(uint v) { if (m_psmAutoPct != v) { m_psmAutoPct = v; Q_EMIT powerSavingModeAutoBatteryPercentChanged(); } } + QString mode() const { return m_mode; } + void setMode(const QString &v); + QString lastMode() const { return m_lastMode; } + bool batteryLow() const { return m_batteryLow; } + bool isHighPerformanceSupported() const { return m_hpSupported; } + bool isPowerSaveSupported() const { return m_psSupported; } + QString cpuGovernor() const { return m_governor; } + void setCpuGovernor(const QString &v) { if (m_governor != v) { m_governor = v; Q_EMIT cpuGovernorChanged(); } } + bool cpuBoost() const { return m_boost; } + void setCpuBoost(bool v) { if (m_boost != v) { m_boost = v; Q_EMIT cpuBoostChanged(); } } + +public Q_SLOTS: + QList GetBatteries(); + void Refresh(); + void RefreshBatteries(); + void RefreshMains(); + void SetCpuGovernor(const QString &gov); + void SetCpuBoost(bool on); + void LockCpuFreq(const QString &gov, int lockTime); + +Q_SIGNALS: + void onBatteryChanged(); + void hasBatteryChanged(); + void hasLidSwitchChanged(); + void lidClosedChanged(); + void LidClosed(); + void LidOpened(); + void batteryPercentageChanged(); + void batteryStatusChanged(); + void batteryTimeToEmptyChanged(); + void batteryTimeToFullChanged(); + void batteryCapacityChanged(); + void powerSavingModeEnabledChanged(); + void powerSavingModeAutoChanged(); + void powerSavingModeAutoWhenBatteryLowChanged(); + void powerSavingModeBrightnessDropPercentChanged(); + void powerSavingModeAutoBatteryPercentChanged(); + void modeChanged(); + void isHighPerformanceSupportedChanged(); + void isPowerSaveSupportedChanged(); + void cpuGovernorChanged(); + void cpuBoostChanged(); + +private: + void handleLidSwitchEvent(bool closed); + void updatePowerMode(bool init = false); + void recalcBatteryLow(); + + QDBusConnection *m_conn; + + bool m_onBattery = false; + bool m_hasBattery = false; + bool m_hasLidSwitch = false; + bool m_lidClosed = false; + double m_batteryPercentage = 100.0; + uint m_batteryStatus = 0; + quint64 m_batteryTimeToEmpty = 0; + quint64 m_batteryTimeToFull = 0; + double m_batteryCapacity = 100.0; + bool m_psmEnabled = false; + bool m_psmAuto = false; + bool m_psmAutoLow = false; + uint m_psmDrop = 0; + uint m_psmAutoPct = 20; + QString m_mode = "balance"; + QString m_lastMode = "balance"; + bool m_batteryLow = false; + QString m_governor = "powersave"; + bool m_boost = true; + bool m_hpSupported = true; + bool m_psSupported = true; + Dtk::Core::DConfig *m_config = nullptr; +}; diff --git a/src/plugin-qt/power/system/systemdbusproxy.cpp b/src/plugin-qt/power/system/systemdbusproxy.cpp new file mode 100644 index 0000000..794ea70 --- /dev/null +++ b/src/plugin-qt/power/system/systemdbusproxy.cpp @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "systemdbusproxy.h" +#include "../powerconstants.h" + +#include +#include +#include +#include + +using namespace PowerDBus; + +SystemDBusProxy::SystemDBusProxy(QObject *parent) + : QObject(parent) +{ +} + +QString SystemDBusProxy::chassis() const +{ + QDBusInterface iface("org.freedesktop.hostname1", + "/org/freedesktop/hostname1", + "org.freedesktop.hostname1", + QDBusConnection::systemBus()); + return iface.property("Chassis").toString(); +} + +bool SystemDBusProxy::lidIsPresent() const +{ + QDBusInterface iface(kUPowerService, kUPowerPath, kUPowerService, + QDBusConnection::systemBus()); + return iface.property("LidIsPresent").toBool(); +} + +bool SystemDBusProxy::lidIsClosed() const +{ + QDBusInterface iface(kUPowerService, kUPowerPath, kUPowerService, + QDBusConnection::systemBus()); + return iface.property("LidIsClosed").toBool(); +} diff --git a/src/plugin-qt/power/system/systemdbusproxy.h b/src/plugin-qt/power/system/systemdbusproxy.h new file mode 100644 index 0000000..59e3ab9 --- /dev/null +++ b/src/plugin-qt/power/system/systemdbusproxy.h @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later +#pragma once +#include + +class QDBusConnection; + +class SystemDBusProxy : public QObject { + Q_OBJECT +public: + explicit SystemDBusProxy(QObject *parent = nullptr); + + QString chassis() const; + bool lidIsPresent() const; + bool lidIsClosed() const; +};