From ca9798f55b4c5f05e192758d2c58180c70f268b4 Mon Sep 17 00:00:00 2001 From: fuleyi Date: Thu, 30 Jul 2026 14:39:43 +0800 Subject: [PATCH] fix: support ambient light automatic brightness MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Add an ambient brightness service backed by iio-sensor-proxy 2. Convert lux samples into stable brightness recommendations with filtering, hysteresis, and debounce 3. Coordinate sensor lifecycle with lid, sleep, session, service, and configuration state Influence: 1. Publish automatic brightness state and recommendations through org.deepin.dde.AmbientBrightness1 2. Prevent power-saving and manual brightness paths from conflicting with ambient brightness 3. Verify sensor lifecycle and brightness policy with unit tests fix: 支持环境光自动亮度调节 1. 新增基于iio-sensor-proxy的环境光亮度服务 2. 通过滤波、滞回和防抖将lux样本转换为稳定的亮度推荐值 3. 根据合盖、休眠、会话、传感器服务及配置状态管理光感生命周期 Influence: 1. 通过org.deepin.dde.AmbientBrightness1发布自动亮度状态及推荐值 2. 避免省电及手动亮度调节路径与环境光自动亮度冲突 3. 通过单元测试验证光感生命周期及亮度策略 PMS: BUG-372191 --- CMakeLists.txt | 3 +- debian/control | 2 + src/plugin-qt/CMakeLists.txt | 1 + .../ambient-brightness/CMakeLists.txt | 68 ++ src/plugin-qt/ambient-brightness/README.md | 244 +++++++ .../ambientbrightnesslogging.cpp | 6 + .../ambientbrightnesslogging.h | 8 + .../ambientbrightnessmodel.cpp | 113 ++++ .../ambientbrightnessmodel.h | 59 ++ .../ambientbrightnesspolicy.h | 36 + .../ambientbrightnesspolicyfactory.cpp | 166 +++++ .../ambientbrightnesspolicyfactory.h | 35 + .../ambientbrightnessservice.cpp | 620 ++++++++++++++++++ .../ambientbrightnessservice.h | 109 +++ .../ambientlightlifecyclestate.h | 22 + .../ambient-brightness/brightnesscurve.cpp | 64 ++ .../ambient-brightness/brightnesscurve.h | 42 ++ ....deepin.dde.daemon.ambient-brightness.json | 105 +++ .../ambient-brightness/continuous/README.md | 254 +++++++ .../continuousambientlightpolicy.cpp | 437 ++++++++++++ .../continuous/continuousambientlightpolicy.h | 140 ++++ .../misc/ambient-brightness.service | 4 + .../misc/plugin-ambient-brightness.json | 12 + src/plugin-qt/ambient-brightness/plugin.cpp | 34 + .../ambient-brightness/tests/CMakeLists.txt | 25 + .../tests/tst_ambientbrightnesspolicy.cpp | 188 ++++++ src/plugin-qt/power/powerconstants.h | 1 - src/plugin-qt/power/session/powermanager.cpp | 3 - src/plugin-qt/power/session/powermanager.h | 5 - .../tools/dde-shortcut-tool/constant.h | 1 - .../dde-shortcut-tool/displaycontroller.cpp | 21 +- 31 files changed, 2797 insertions(+), 31 deletions(-) create mode 100644 src/plugin-qt/ambient-brightness/CMakeLists.txt create mode 100644 src/plugin-qt/ambient-brightness/README.md create mode 100644 src/plugin-qt/ambient-brightness/ambientbrightnesslogging.cpp create mode 100644 src/plugin-qt/ambient-brightness/ambientbrightnesslogging.h create mode 100644 src/plugin-qt/ambient-brightness/ambientbrightnessmodel.cpp create mode 100644 src/plugin-qt/ambient-brightness/ambientbrightnessmodel.h create mode 100644 src/plugin-qt/ambient-brightness/ambientbrightnesspolicy.h create mode 100644 src/plugin-qt/ambient-brightness/ambientbrightnesspolicyfactory.cpp create mode 100644 src/plugin-qt/ambient-brightness/ambientbrightnesspolicyfactory.h create mode 100644 src/plugin-qt/ambient-brightness/ambientbrightnessservice.cpp create mode 100644 src/plugin-qt/ambient-brightness/ambientbrightnessservice.h create mode 100644 src/plugin-qt/ambient-brightness/ambientlightlifecyclestate.h create mode 100644 src/plugin-qt/ambient-brightness/brightnesscurve.cpp create mode 100644 src/plugin-qt/ambient-brightness/brightnesscurve.h create mode 100644 src/plugin-qt/ambient-brightness/configs/org.deepin.dde.daemon.ambient-brightness.json create mode 100644 src/plugin-qt/ambient-brightness/continuous/README.md create mode 100644 src/plugin-qt/ambient-brightness/continuous/continuousambientlightpolicy.cpp create mode 100644 src/plugin-qt/ambient-brightness/continuous/continuousambientlightpolicy.h create mode 100644 src/plugin-qt/ambient-brightness/misc/ambient-brightness.service create mode 100644 src/plugin-qt/ambient-brightness/misc/plugin-ambient-brightness.json create mode 100644 src/plugin-qt/ambient-brightness/plugin.cpp create mode 100644 src/plugin-qt/ambient-brightness/tests/CMakeLists.txt create mode 100644 src/plugin-qt/ambient-brightness/tests/tst_ambientbrightnesspolicy.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 38b60ccc..24e1ca87 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +# SPDX-FileCopyrightText: 2023-2026 UnionTech Software Technology Co., Ltd. # # SPDX-License-Identifier: LGPL-3.0-or-later cmake_minimum_required(VERSION 3.16) @@ -12,6 +12,7 @@ if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) endif() include(GNUInstallDirs) +include(CTest) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") # 设置二进制输出目录 方便调试 diff --git a/debian/control b/debian/control index 8d12dc47..da52e5be 100644 --- a/debian/control +++ b/debian/control @@ -37,6 +37,8 @@ Depends: ${misc:Depends}, deepin-service-manager (>> 1.0.21), dbus +Recommends: + iio-sensor-proxy Breaks: dde-daemon (<< 6.1.94) Replaces: dde-daemon (<< 6.1.94) Description: deepin desktop-environment plugins service diff --git a/src/plugin-qt/CMakeLists.txt b/src/plugin-qt/CMakeLists.txt index 0c185d96..828cbc6d 100644 --- a/src/plugin-qt/CMakeLists.txt +++ b/src/plugin-qt/CMakeLists.txt @@ -6,5 +6,6 @@ add_subdirectory("thememanager") add_subdirectory("wallpapercache") add_subdirectory("wallpaperslideshow") add_subdirectory("xsettings") +add_subdirectory("ambient-brightness") add_subdirectory("power") add_subdirectory("shortcut") diff --git a/src/plugin-qt/ambient-brightness/CMakeLists.txt b/src/plugin-qt/ambient-brightness/CMakeLists.txt new file mode 100644 index 00000000..d12e4355 --- /dev/null +++ b/src/plugin-qt/ambient-brightness/CMakeLists.txt @@ -0,0 +1,68 @@ +# SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +# SPDX-License-Identifier: LGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.16) +project(plugin-ambient-brightness LANGUAGES CXX) + +include(GNUInstallDirs) +if(NOT DEFINED QT_VERSION_MAJOR) + set(QT_VERSION_MAJOR 6) +endif() +if(NOT DEFINED DTK_VERSION_MAJOR) + set(DTK_VERSION_MAJOR 6) +endif() + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(PLUGIN_NAME "plugin-ambient-brightness") + +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core DBus) +find_package(Dtk${DTK_VERSION_MAJOR} REQUIRED COMPONENTS Core DConfig) + +set(CMAKE_AUTOMOC ON) + +add_library(${PLUGIN_NAME} MODULE + plugin.cpp + ambientbrightnessservice.cpp + ambientbrightnessservice.h + ambientlightlifecyclestate.h + ambientbrightnesspolicyfactory.cpp + ambientbrightnesspolicyfactory.h + ambientbrightnesspolicy.h + ambientbrightnessmodel.cpp + ambientbrightnessmodel.h + continuous/continuousambientlightpolicy.cpp + continuous/continuousambientlightpolicy.h + brightnesscurve.cpp + brightnesscurve.h + ambientbrightnesslogging.cpp + ambientbrightnesslogging.h +) + +target_include_directories(${PLUGIN_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + +target_link_libraries(${PLUGIN_NAME} PRIVATE + Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::DBus + Dtk${DTK_VERSION_MAJOR}::Core +) + +install(TARGETS ${PLUGIN_NAME} + DESTINATION ${CMAKE_INSTALL_LIBDIR}/deepin-service-manager/ +) +install(FILES misc/plugin-ambient-brightness.json + DESTINATION ${CMAKE_INSTALL_DATADIR}/deepin-service-manager/user/ +) +install(FILES misc/ambient-brightness.service + DESTINATION ${CMAKE_INSTALL_DATADIR}/dbus-1/services/ +) + +install(FILES configs/org.deepin.dde.daemon.ambient-brightness.json + DESTINATION ${CMAKE_INSTALL_DATADIR}/dsg/configs/org.deepin.dde.daemon/ +) + + +if(BUILD_TESTING) + add_subdirectory(tests) +endif() diff --git a/src/plugin-qt/ambient-brightness/README.md b/src/plugin-qt/ambient-brightness/README.md new file mode 100644 index 00000000..05d303a3 --- /dev/null +++ b/src/plugin-qt/ambient-brightness/README.md @@ -0,0 +1,244 @@ +# ambient-brightness + +`ambient-brightness` 是 DDE 会话级自动亮度推荐插件。它从 `iio-sensor-proxy` 获取环境光照度(lux),经过平滑、滞回、防抖和亮度映射后,对外发布 `[0.0, 1.0]` 范围的推荐亮度。 + +本模块只负责计算和发布推荐值,不直接修改显示器亮度。Display 服务或其他消费者决定是否以及如何应用该推荐值。 + +## 模块功能 + +- 监听 system bus 上的 `net.hadess.SensorProxy`,读取环境光传感器数据。 +- 校验、缓存并处理 lux 样本,抑制噪声、短时遮挡和阈值附近抖动。 +- 支持连续曲线 lux 到亮度映射,使用 log1p 空间分段线性插值。 +- 在 session bus 上发布推荐亮度和传感器状态。 +- 监听传感器服务注册、注销,支持传感器服务重启和热插拔。 +- 监听本模块 DConfig 的 `ambientLightAdjustBrightness`:关闭时 `ReleaseLight`,开启时重新初始化光感。 +- 监听合盖/开盖和休眠/唤醒事件,在不可用阶段停止光感,恢复后按运行条件重新初始化。 +- 监听算法 DConfig,运行时重建策略并应用新配置,无需重启插件。 +- 在没有新传感器事件时,通过单次定时器完成 debounce 到期确认,不伪造传感器样本。 + +## 外部接口 + +### 传感器输入 + +| 项目 | 值 | +| --- | --- | +| Bus | system bus | +| Service | `net.hadess.SensorProxy` | +| Object path | `/net/hadess/SensorProxy` | +| Interface | `net.hadess.SensorProxy` | +| 输入属性 | `LightLevel` | +| 变化信号 | `org.freedesktop.DBus.Properties.PropertiesChanged` | + +只有自动亮度已开启、上盖打开、系统未休眠且当前登录会话处于前台(`login1.Session.Active=true`)时才会连接传感器。初始化时检查 `HasAmbientLight` 和 `LightLevelUnit`,先订阅 `PropertiesChanged` 再调用 `ClaimLight`,优先使用 Claim 期间或之后收到的首个有效 `LightLevel`;若 2 秒内没有收到信号,则延迟读取一次 `LightLevel` 属性作为兜底。停止时调用 `ReleaseLight` 并清空策略状态。这样可以避免把 Claim 时刻尚未刷新的缓存值或默认值用于初始化。 + +### 推荐值输出 + +| 项目 | 值 | +| --- | --- | +| Bus | session bus | +| Service | `org.deepin.dde.AmbientBrightness1` | +| Object path | `/org/deepin/dde/AmbientBrightness1` | +| Interface | `org.deepin.dde.AmbientBrightness1` | + +对外属性: + +| 属性 | 类型 | 含义 | +| --- | --- | --- | +| `Supported` | `bool` | 当前是否已连接并成功声明环境光传感器 | +| `State` | `string` | 当前状态:`Unavailable`、`Disabled`、`WaitingForSample` 或 `Active` | +| `RecommendedBrightness` | `double` | 推荐亮度,范围 `[0.0, 1.0]` | + +属性变化通过标准 `PropertiesChanged` 信号发布。 + +每次重新 Claim、策略重建或从 `Disabled` 恢复后,首个有效 Recommendation 都会重新发布,即使数值与停止前相同;消费者不需要依赖旧推荐值推断是否应重新应用亮度。 + +## 总体原理 + +```mermaid +flowchart LR + Sensor["iio-sensor-proxy\nLightLevel"] + Service["AmbientBrightnessService\n传感器、DConfig、定时器、D-Bus"] + Model["AmbientBrightnessModel\n状态机与策略适配"] + Factory["PolicyFactory\n解析配置并创建策略"] + Policy["ContinuousAmbientLightPolicy\n样本处理与亮度推荐"] + Output["AmbientBrightness1\nRecommendedBrightness"] + Config["DConfig"] + Runtime["Enabled / Lid / Sleep"] + + Sensor -->|PropertiesChanged| Service + Service -->|submitSample / tick| Model + Model -->|update / tick| Policy + Policy -->|Recommendation| Model + Model -->|Qt signal| Service + Service -->|PropertiesChanged| Output + Config --> Service + Runtime -->|stop / init| Service + Config --> Factory + Factory -->|replace policy| Model +``` + +模块分为四层: + +1. **插件入口层**:由 `deepin-service-manager` 加载和销毁服务实例。 +2. **服务层**:处理 D-Bus、传感器生命周期、DConfig 和评估定时器。 +3. **模型层**:维护对外状态,持有并驱动当前算法策略。 +4. **算法层**:只处理带单调时间戳的 lux 样本,返回可选的亮度推荐。 + +这种分层使算法不依赖 D-Bus;单元测试可以直接向策略或 Model 提交样本。 + +## 运行流程 + +### 1. 插件启动 + +1. `deepin-service-manager` 调用 `DSMRegister`。 +2. 创建 `AmbientBrightnessService` 并调用 `initialize()`。 +3. Service 初始化算法 DConfig 和光感生命周期控制,在 session bus 导出 D-Bus 对象。 +4. 创建 `QDBusServiceWatcher`,监听 `net.hadess.SensorProxy` 注册和注销。 +5. 自动亮度开启、上盖打开、系统已唤醒且传感器服务存在时,立即初始化光感。 + +### 2. 光感启动与停止 + +光感只有在以下条件同时成立时运行: + +- `ambientLightAdjustBrightness=true` +- 上盖处于打开状态 +- 系统不在休眠过程中(通过 `login1.Manager.PrepareForSleep` 监听) +- 当前登录会话处于前台(通过 `login1.Session.Active` 监听) + +初始化光感时,Service 创建 `QDBusInterface` 并校验 ALS 能力和 lux 单位。随后先订阅 `PropertiesChanged`,再调用 `ClaimLight`,以免漏掉驱动在 Claim 调用期间同步上报的样本。Claim 成功后,首个有效 `LightLevel` 信号作为初始样本;若 2 秒内没有收到信号,则读取一次此时的 `LightLevel` 属性兜底,以覆盖真实首帧等于代理缓存值、重复 Claim 或已有其他客户端占用时不产生变化信号的情况。策略已经被 stop 流程重置,因此首个有效样本会立即生成推荐亮度。 + +任一运行条件失效,或传感器服务注销时,Service 停止评估定时器、调用 `ReleaseLight`、清空最近样本,并让 Model 进入 `Unavailable`。 + +### 3. 样本处理 + +1. Service 收到 `LightLevel`,使用 `QElapsedTimer` 添加单调时间戳。 +2. Model 调用当前策略的 `update()`。 +3. 策略完成样本校验、平滑、转换判定和亮度映射。 +4. 没有满足转换条件时返回 `std::nullopt`,对外属性不变。 +5. 产生 `Recommendation` 时,Model 更新 `RecommendedBrightness`,状态进入 `Active`。 +6. Service 将 Model 信号转换为 D-Bus `PropertiesChanged`。 + +### 4. 无新样本时的定时确认 + +策略通过 `nextEvaluationDelayMs()` 告诉 Service 下一次需要复算的时间。Service 使用 single-shot `QTimer` 调用 Model 的 `tick()`: + +- `tick()` 只推进时间并确认 debounce 是否到期。 +- 不向环形缓冲区插入虚拟样本。 +- 若仍需等待,Service 再次设置下一次 single-shot 定时器。 + +### 5. 配置热更新 + +DConfig 发生变化时,Service 会: + +1. 停止旧评估定时器。 +2. 通过 Factory 创建带新配置的策略。 +3. 让 Model 替换并重置旧策略。 +4. 如果已有有效的最近 lux,立即重新提交该样本;否则等待下一次传感器上报。 + +因此配置切换不会保留旧算法内部状态,也不需要重启服务。 + +### 6. 光感生命周期 + +| 事件 | 动作 | +| --- | --- | +| 用户关闭自动亮度 | stop:`ReleaseLight`,停止定时器并重置策略 | +| 用户重新开启自动亮度 | init:若上盖打开且系统已唤醒,则先监听再 `ClaimLight`,等待首个有效 `LightLevel`;2 秒无信号时延迟读取属性兜底 | +| 合盖 | stop | +| 开盖 | init;仍处于休眠或自动亮度关闭时保持停止 | +| 进入待机、休眠 | stop | +| 待机、休眠唤醒 | init;上盖仍关闭、会话非前台或自动亮度关闭时保持停止 | +| 会话切到后台 | stop | +| 会话切回前台 | init;上盖关闭或自动亮度关闭时保持停止 | + +四个条件由 `AmbientLightLifecycleState` 统一判断。多个停止原因可能重叠,只有自动亮度开启、上盖打开、系统唤醒且会话前台后才会重新 Claim,避免单一事件单独解除另一个阻断条件。 + +## 状态机 + +```mermaid +stateDiagram-v2 + [*] --> Unavailable + Unavailable --> WaitingForSample: 运行条件满足并 ClaimLight 成功 + WaitingForSample --> Active: Claim 后首个有效 LightLevel(或超时兜底值)产生 Recommendation + Active --> WaitingForSample: 算法配置更新并重建策略 + WaitingForSample --> Unavailable: 关闭自动亮度、合盖、休眠、会话非前台或传感器断开 + Active --> Unavailable: 关闭自动亮度、合盖、休眠、会话非前台或传感器断开 +``` + +无效样本会被策略拒绝,不会使 `WaitingForSample` 错误进入 `Active`。 + +## 类与文件职责 + +| 类、结构或入口 | 文件 | 职责 | +| --- | --- | --- | +| `DSMRegister` / `DSMUnRegister` | `plugin.cpp` | 插件 ABI 入口。创建、初始化和销毁全局 `AmbientBrightnessService` 实例,处理重复注册和初始化失败。 | +| `AmbientBrightnessService` | `ambientbrightnessservice.h/cpp` | 模块编排层。导出 D-Bus 属性;按自动亮度开关、盖子和休眠状态执行光感 stop/init;声明和释放传感器;监听服务上下线及 `LightLevel`;管理定时器、DConfig、策略重建和属性发布。它不实现亮度算法。 | +| `AmbientLightLifecycleState` | `ambientlightlifecyclestate.h` | 保存自动亮度开关、盖子、休眠和会话前台四个独立条件;仅在开关开启、上盖打开、系统唤醒且会话前台时允许 Claim ALS。 | +| `AmbientBrightnessModel` | `ambientbrightnessmodel.h/cpp` | Qt 状态适配层。独占一个 `AmbientBrightnessPolicy`;把 `submitSample()`、`tick()` 转发给策略;维护 `Supported`、`State` 和推荐亮度;将策略结果转换为 Qt 信号。它不访问 D-Bus 或 DConfig。 | +| `AmbientBrightnessPolicy` | `ambientbrightnesspolicy.h` | 与 Qt、D-Bus 无关的算法抽象接口,定义 `update()`、`tick()`、`reset()` 和 `nextEvaluationDelayMs()`,使 Model 不依赖具体算法。 | +| `SensorSample` | `ambientbrightnesspolicy.h` | 策略输入,包含 lux、单调时间戳和样本来源。 | +| `Recommendation` | `ambientbrightnesspolicy.h` | 策略输出,包含 raw、fast、slow、stable lux 和最终亮度,便于状态更新、日志及调试。 | +| `BrightnessAlgorithm` | `ambientbrightnesspolicyfactory.h` | 算法类型枚举。当前只有 `Continuous`,保留作为以后新增算法的扩展点。 | +| Policy Factory 函数 | `ambientbrightnesspolicyfactory.h/cpp` | 解析 `algorithm` 和连续策略 DConfig;校验 JSON 曲线及数值范围;创建配置完整的策略。非法字段保留内置默认值,未知算法回退为 `continuous`。 | +| `ContinuousPolicyConfig` | `continuous/continuousambientlightpolicy.h` | 连续策略的完整配置值对象,包括窗口、debounce、滞回、推荐死区和曲线。 | +| `ContinuousAmbientLightPolicy` | `continuous/continuousambientlightpolicy.h/cpp` | 当前唯一算法实现。负责样本校验和缓存、fast/slow 加权或 rawLux 模式、亮暗滞回、方向 debounce、曲线映射、推荐值死区及下一次评估时间计算。 | +| `ContinuousAmbientLightPolicy::AmbientLightRingBuffer` | `continuous/continuousambientlightpolicy.h/cpp` | 策略内部固定容量环形缓冲区,按时间保存 lux,覆盖最旧样本并裁剪窗口外数据。仅供策略内部使用。 | +| `CurvePoint` | `brightnesscurve.h` | 连续曲线的 lux/brightness 控制点。 | +| `BrightnessCurve` | `brightnesscurve.h/cpp` | 校验曲线控制点,并在 `log1p(lux)` 空间执行分段线性插值;低于或高于曲线范围时使用端点亮度。 | +| `logAmbientBrightness` | `ambientbrightnesslogging.h/cpp` | 模块统一日志分类。 | + +其他文件: + +| 文件 | 用途 | +| --- | --- | +| `configs/org.deepin.dde.daemon.ambient-brightness.json` | DConfig 元数据、默认值、范围和字段说明。 | +| `misc/plugin-ambient-brightness.json` | `deepin-service-manager` 插件描述。 | +| `misc/ambient-brightness.service` | D-Bus 服务激活描述。 | + +## 当前算法概览 + +当前只实现了 `continuous` 策略,在 `log1p(lux)` 空间通过分段线性插值将 lux 映射到亮度。 + +策略支持两种输入处理模式: + +| 模式 | 说明 | +| --- | --- | +| `useWeightedWindows=true` | 使用 fast/slow 时间加权窗口;fast 用于快速响应变亮,变暗要求 fast 和 slow 均满足条件。 | +| `useWeightedWindows=false` | 直接使用最新 rawLux,主要依靠滞回和 debounce 抗抖,适合低频传感器。 | + +所有模式都经过方向滞回、变亮/变暗 debounce 和 `minimumRecommendationDelta` 推荐值死区。 + +算法实现和内部数据结构见: + +- [`continuous/continuousambientlightpolicy.h`](continuous/continuousambientlightpolicy.h) +- [`continuous/continuousambientlightpolicy.cpp`](continuous/continuousambientlightpolicy.cpp) +- [`brightnesscurve.h`](brightnesscurve.h) +- [`brightnesscurve.cpp`](brightnesscurve.cpp) + +## 配置 + +DConfig 标识: + +```text +App ID: org.deepin.dde.daemon +Resource ID: org.deepin.dde.daemon.ambient-brightness +``` + +用户可配置参数: + +- `continuousLuxCurve`:lux-亮度映射曲线。 +- `brightenDebounceMs`:变亮防抖时间(默认 2000ms)。 +- `darkenDebounceMs`:变暗防抖时间(默认 2000ms)。 + +其他参数(加权窗口、滞回比例、推荐值死区等)由代码内置默认值,不对外暴露。 + +字段默认值、有效范围、JSON 格式和生效条件以 [`configs/org.deepin.dde.daemon.ambient-brightness.json`](configs/org.deepin.dde.daemon.ambient-brightness.json) 为准。 + +光感运行开关与算法参数使用同一 DConfig Resource: + +```text +App ID: org.deepin.dde.daemon +Resource ID: org.deepin.dde.daemon.ambient-brightness +Key: ambientLightAdjustBrightness +``` + +该值变为 `false` 时立即 stop;变为 `true` 时在盖子和休眠条件允许的情况下 init。 diff --git a/src/plugin-qt/ambient-brightness/ambientbrightnesslogging.cpp b/src/plugin-qt/ambient-brightness/ambientbrightnesslogging.cpp new file mode 100644 index 00000000..4474639a --- /dev/null +++ b/src/plugin-qt/ambient-brightness/ambientbrightnesslogging.cpp @@ -0,0 +1,6 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "ambientbrightnesslogging.h" + +Q_LOGGING_CATEGORY(logAmbientBrightness, "dde.ambientbrightness") diff --git a/src/plugin-qt/ambient-brightness/ambientbrightnesslogging.h b/src/plugin-qt/ambient-brightness/ambientbrightnesslogging.h new file mode 100644 index 00000000..e57b7515 --- /dev/null +++ b/src/plugin-qt/ambient-brightness/ambientbrightnesslogging.h @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include + +Q_DECLARE_LOGGING_CATEGORY(logAmbientBrightness) diff --git a/src/plugin-qt/ambient-brightness/ambientbrightnessmodel.cpp b/src/plugin-qt/ambient-brightness/ambientbrightnessmodel.cpp new file mode 100644 index 00000000..06b8a0d2 --- /dev/null +++ b/src/plugin-qt/ambient-brightness/ambientbrightnessmodel.cpp @@ -0,0 +1,113 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "ambientbrightnessmodel.h" + +#include + +namespace dde::ambient_brightness { + +AmbientBrightnessModel::AmbientBrightnessModel(std::unique_ptr policy, + QObject *parent) + : QObject(parent) + , m_policy(std::move(policy)) +{ + Q_ASSERT(m_policy); +} + +void AmbientBrightnessModel::waitForSample() +{ + m_policy->reset(); + m_haveRecommendation = false; + setSupported(true); + setState(QStringLiteral("WaitingForSample")); +} + +void AmbientBrightnessModel::makeUnavailable() +{ + m_policy->reset(); + m_haveRecommendation = false; + setSupported(false); + setState(QStringLiteral("Unavailable")); +} + +void AmbientBrightnessModel::setDisabled() +{ + m_policy->reset(); + m_haveRecommendation = false; + setSupported(true); + setState(QStringLiteral("Disabled")); +} + +void AmbientBrightnessModel::submitSample(double lux, + double monotonicTimestampMs, + SensorSample::Source source) +{ + const auto recommendation = m_policy->update({ lux, monotonicTimestampMs, source }); + if (!recommendation) { + return; + } + + + // +1.0 偏移:qFuzzyCompare 对 0 附近的值不可靠(brightness 合法值为 0), + // 偏移到 [1,2] 区间使其进入 qFuzzyCompare 的有效比较范围。 + if (!m_haveRecommendation + || !qFuzzyCompare(m_recommendedBrightness + 1.0, recommendation->brightness + 1.0)) { + m_recommendedBrightness = recommendation->brightness; + m_haveRecommendation = true; + qCDebug(logAmbientBrightness) + << "recommendation changed: stableLux=" << recommendation->stableLux + << "brightness=" << m_recommendedBrightness; + Q_EMIT recommendedBrightnessChanged(m_recommendedBrightness); + } + setState(QStringLiteral("Active")); +} + +void AmbientBrightnessModel::tick(double monotonicTimestampMs) +{ + const auto recommendation = m_policy->tick(monotonicTimestampMs); + if (!recommendation) + return; + + + // 同上:+1.0 偏移以正确处理 brightness=0 的边界。 + if (!m_haveRecommendation + || !qFuzzyCompare(m_recommendedBrightness + 1.0, recommendation->brightness + 1.0)) { + m_recommendedBrightness = recommendation->brightness; + m_haveRecommendation = true; + qCDebug(logAmbientBrightness) + << "timer recommendation changed: stableLux=" << recommendation->stableLux + << "brightness=" << m_recommendedBrightness; + Q_EMIT recommendedBrightnessChanged(m_recommendedBrightness); + } + setState(QStringLiteral("Active")); +} + +void AmbientBrightnessModel::setPolicy(std::unique_ptr policy) +{ + Q_ASSERT(policy); + m_policy = std::move(policy); + m_policy->reset(); + m_haveRecommendation = false; + setState(m_supported ? QStringLiteral("WaitingForSample") : QStringLiteral("Unavailable")); +} + +void AmbientBrightnessModel::setSupported(bool value) +{ + if (m_supported == value) + return; + qCDebug(logAmbientBrightness) << "supported changed:" << m_supported << "->" << value; + m_supported = value; + Q_EMIT supportedChanged(value); +} + +void AmbientBrightnessModel::setState(const QString &value) +{ + if (m_state == value) + return; + qCDebug(logAmbientBrightness) << "state changed:" << m_state << "->" << value; + m_state = value; + Q_EMIT stateChanged(value); +} + +} // namespace dde::ambient_brightness diff --git a/src/plugin-qt/ambient-brightness/ambientbrightnessmodel.h b/src/plugin-qt/ambient-brightness/ambientbrightnessmodel.h new file mode 100644 index 00000000..7358f941 --- /dev/null +++ b/src/plugin-qt/ambient-brightness/ambientbrightnessmodel.h @@ -0,0 +1,59 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include "ambientbrightnesslogging.h" +#include "ambientbrightnesspolicy.h" + +#include +#include + +#include + +namespace dde::ambient_brightness { + +/// Qt 适配层:把可替换的纯算法策略包装成 QObject, +/// 对外暴露 D-Bus 可订阅的属性和信号。 +class AmbientBrightnessModel : public QObject +{ + Q_OBJECT + +public: + explicit AmbientBrightnessModel(std::unique_ptr policy, + QObject *parent = nullptr); + + bool supported() const { return m_supported; } + + QString state() const { return m_state; } + + double recommendedBrightness() const { return m_recommendedBrightness; } + + double nextEvaluationDelayMs() const { return m_policy->nextEvaluationDelayMs(); } + + void waitForSample(); + void makeUnavailable(); + void setDisabled(); + void submitSample(double lux, + double monotonicTimestampMs, + SensorSample::Source source = SensorSample::Source::RealSensor); + void tick(double monotonicTimestampMs); + void setPolicy(std::unique_ptr policy); + +Q_SIGNALS: + void supportedChanged(bool value); + void stateChanged(const QString &value); + void recommendedBrightnessChanged(double value); + +private: + void setSupported(bool value); + void setState(const QString &value); + + std::unique_ptr m_policy; + bool m_supported = false; + QString m_state = QStringLiteral("Unavailable"); + double m_recommendedBrightness = 0.0; + bool m_haveRecommendation = false; +}; + +} // namespace dde::ambient_brightness diff --git a/src/plugin-qt/ambient-brightness/ambientbrightnesspolicy.h b/src/plugin-qt/ambient-brightness/ambientbrightnesspolicy.h new file mode 100644 index 00000000..fe8ff67f --- /dev/null +++ b/src/plugin-qt/ambient-brightness/ambientbrightnesspolicy.h @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include + +namespace dde::ambient_brightness { + +struct SensorSample { + double lux = 0.0; + double monotonicTimestampMs = 0.0; + enum class Source { RealSensor, HeldEvaluation } source = Source::RealSensor; +}; + +struct Recommendation { + double rawLux = 0.0; + double fastLux = 0.0; + double slowLux = 0.0; + double stableLux = 0.0; + double brightness = 0.0; +}; + +class AmbientBrightnessPolicy { +public: + virtual ~AmbientBrightnessPolicy() = default; + + virtual std::optional update(const SensorSample &sample) = 0; + + /// 无新传感器事件时的时间推进;只做候选确认/超时,不新增样本。 + virtual std::optional tick(double monotonicTimestampMs) = 0; + virtual void reset() = 0; + virtual double nextEvaluationDelayMs() const = 0; +}; + +} // namespace dde::ambient_brightness diff --git a/src/plugin-qt/ambient-brightness/ambientbrightnesspolicyfactory.cpp b/src/plugin-qt/ambient-brightness/ambientbrightnesspolicyfactory.cpp new file mode 100644 index 00000000..42e0233f --- /dev/null +++ b/src/plugin-qt/ambient-brightness/ambientbrightnesspolicyfactory.cpp @@ -0,0 +1,166 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "ambientbrightnesspolicyfactory.h" + +#include "continuous/continuousambientlightpolicy.h" + +#include + +#include +#include +#include +#include + +#include + +namespace dde::ambient_brightness { +namespace { + +constexpr auto kContinuousMappingModeKey = "continuousMappingMode"; +constexpr auto kUseWeightedWindowsKey = "useWeightedWindows"; +constexpr auto kAmbientLightHorizonMsKey = "ambientLightHorizonMs"; +constexpr auto kFastLightHorizonMsKey = "fastLightHorizonMs"; +constexpr auto kContinuousLuxCurveKey = "continuousLuxCurve"; +constexpr auto kStepHysteresisRatioKey = "stepHysteresisRatio"; +constexpr auto kBrightenDebounceMsKey = "brightenDebounceMs"; +constexpr auto kDarkenDebounceMsKey = "darkenDebounceMs"; + +std::optional readFiniteDouble(Dtk::Core::DConfig *config, const char *key) +{ + bool ok = false; + const double value = config->value(QString::fromLatin1(key)).toDouble(&ok); + if (!ok || !std::isfinite(value)) + return std::nullopt; + return value; +} + +std::vector parseCurvePoints(const QVariant &raw) +{ + // DConfig 可能以两种形式返回 continuousLuxCurve: + // 1. QString —— JSON 文本(dde-dconfig set 写入的字符串值) + // 2. QVariantList —— 已解析的列表(DConfig 后端将 JSON 数组展开为 QVariantList) + std::vector points; + QJsonArray array; + if (raw.typeId() == QMetaType::QString) { + const QJsonDocument doc = QJsonDocument::fromJson(raw.toString().toUtf8()); + if (!doc.isArray()) + return {}; + array = doc.array(); + } else if (raw.typeId() == QMetaType::QVariantList) { + array = QJsonArray::fromVariantList(raw.toList()); + } else { + return {}; + } + points.reserve(array.size()); + for (const auto &value : array) { + if (!value.isObject()) + return {}; + const QJsonObject object = value.toObject(); + const QJsonValue lux = object.value(QStringLiteral("lux")); + const QJsonValue brightness = object.value(QStringLiteral("brightness")); + if (!lux.isDouble() || !brightness.isDouble()) + return {}; + points.push_back({ lux.toDouble(), brightness.toDouble() }); + } + return points; +} + +ContinuousPolicyConfig buildContinuousConfig(Dtk::Core::DConfig *config) +{ + ContinuousPolicyConfig cfg; + if (!config) + return cfg; + + const QVariant windowFlag = config->value(QString::fromLatin1(kUseWeightedWindowsKey)); + if (windowFlag.isValid() && windowFlag.canConvert()) + cfg.useWeightedWindows = windowFlag.toBool(); + + const auto ambientHorizon = readFiniteDouble(config, kAmbientLightHorizonMsKey); + const auto fastHorizon = readFiniteDouble(config, kFastLightHorizonMsKey); + const double candidateAmbient = + ambientHorizon && *ambientHorizon > 0.0 && *ambientHorizon <= cfg.weightingIntercept + ? *ambientHorizon + : cfg.ambientLightHorizonMs; + const double candidateFast = + fastHorizon && *fastHorizon > 0.0 ? *fastHorizon : cfg.fastLightHorizonMs; + if (candidateFast <= candidateAmbient) { + cfg.ambientLightHorizonMs = candidateAmbient; + cfg.fastLightHorizonMs = candidateFast; + } + + if (const auto value = readFiniteDouble(config, kBrightenDebounceMsKey); + value && *value >= 0.0) { + cfg.brightenDebounceMs = *value; + } + if (const auto value = readFiniteDouble(config, kDarkenDebounceMsKey); value && *value >= 0.0) { + cfg.darkenDebounceMs = *value; + } + if (const auto value = readFiniteDouble(config, kStepHysteresisRatioKey); + value && *value >= 0.5 && *value <= 1.0) { + cfg.stepHysteresisRatio = *value; + } + + const QString mode = config->value(QString::fromLatin1(kContinuousMappingModeKey)).toString(); + cfg.useStepsMode = (mode != QLatin1String("curve")); + + auto curve = parseCurvePoints(config->value(QString::fromLatin1(kContinuousLuxCurveKey))); + if (!BrightnessCurve::isValid(curve)) + return cfg; + + cfg.curve = curve; + if (cfg.useStepsMode) { + // 将曲线控制点转换为档位数据 + std::vector steps; + for (const auto &p : curve) + steps.push_back({ p.lux, p.brightness }); + if (ContinuousAmbientLightPolicy::isValidLuxSteps(steps)) + cfg.luxSteps = std::move(steps); + } + + return cfg; +} + +} // namespace + +std::optional parseBrightnessAlgorithm(const QString &value) +{ + if (value.compare(QStringLiteral("continuous"), Qt::CaseInsensitive) == 0) + return BrightnessAlgorithm::Continuous; + return std::nullopt; +} + +QString brightnessAlgorithmName(BrightnessAlgorithm algorithm) +{ + switch (algorithm) { + case BrightnessAlgorithm::Continuous: + return QStringLiteral("continuous"); + } + return QStringLiteral("continuous"); +} + +std::unique_ptr +createAmbientBrightnessPolicy(BrightnessAlgorithm algorithm) +{ + switch (algorithm) { + case BrightnessAlgorithm::Continuous: + return std::make_unique(ContinuousPolicyConfig{}); + } + return std::make_unique(ContinuousPolicyConfig{}); +} + +std::unique_ptr +createAmbientBrightnessPolicy(BrightnessAlgorithm algorithm, Dtk::Core::DConfig *config) +{ + switch (algorithm) { + case BrightnessAlgorithm::Continuous: { + auto cfg = buildContinuousConfig(config); + if (!ContinuousAmbientLightPolicy::isValidConfig(cfg)) + cfg = ContinuousPolicyConfig{}; + return std::make_unique(cfg); + } + } + return std::make_unique(ContinuousPolicyConfig{}); +} + +} // namespace dde::ambient_brightness diff --git a/src/plugin-qt/ambient-brightness/ambientbrightnesspolicyfactory.h b/src/plugin-qt/ambient-brightness/ambientbrightnesspolicyfactory.h new file mode 100644 index 00000000..538e23ef --- /dev/null +++ b/src/plugin-qt/ambient-brightness/ambientbrightnesspolicyfactory.h @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include "ambientbrightnesspolicy.h" + +#include +#include +#include + +namespace Dtk::Core { +class DConfig; +} + +namespace dde::ambient_brightness { + +/// 亮度算法标识。当前仅 continuous;保留枚举与 Factory 扩展点, +/// 未来新增算法时在此添加枚举值并在 Factory 注册创建分支。 +enum class BrightnessAlgorithm { + Continuous, +}; + +std::optional parseBrightnessAlgorithm(const QString &value); +QString brightnessAlgorithmName(BrightnessAlgorithm algorithm); + +/// 创建默认配置的策略实例(测试与回退路径)。 +std::unique_ptr createAmbientBrightnessPolicy(BrightnessAlgorithm algorithm); + +/// 创建策略并从 DConfig 注入运行时配置。config 为空时等价于无配置版本。 +/// 配置读取失败/字段非法时各策略自行回退到默认值,保证可用。 +std::unique_ptr createAmbientBrightnessPolicy(BrightnessAlgorithm algorithm, + Dtk::Core::DConfig *config); + +} // namespace dde::ambient_brightness diff --git a/src/plugin-qt/ambient-brightness/ambientbrightnessservice.cpp b/src/plugin-qt/ambient-brightness/ambientbrightnessservice.cpp new file mode 100644 index 00000000..f2c2c2d1 --- /dev/null +++ b/src/plugin-qt/ambient-brightness/ambientbrightnessservice.cpp @@ -0,0 +1,620 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "ambientbrightnessservice.h" +#include "ambientbrightnesslogging.h" +#include "ambientbrightnesspolicyfactory.h" +#include "continuous/continuousambientlightpolicy.h" + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace dde::ambient_brightness { +namespace { + +constexpr auto kSensorService = "net.hadess.SensorProxy"; +constexpr auto kSensorPath = "/net/hadess/SensorProxy"; +constexpr auto kSensorInterface = "net.hadess.SensorProxy"; +constexpr auto kPropertiesInterface = "org.freedesktop.DBus.Properties"; +constexpr auto kObjectPath = "/org/deepin/dde/AmbientBrightness1"; +constexpr auto kConfigAppId = "org.deepin.dde.daemon"; +constexpr auto kConfigName = "org.deepin.dde.daemon.ambient-brightness"; +constexpr auto kAmbientLightAdjustBrightnessKey = "ambientLightAdjustBrightness"; +constexpr auto kPowerService = "org.deepin.dde.Power1"; +constexpr auto kPowerPath = "/org/deepin/dde/Power1"; +constexpr auto kPowerInterface = "org.deepin.dde.Power1"; + +constexpr auto kUseWeightedWindowsKey = "useWeightedWindows"; +constexpr auto kAmbientLightHorizonMsKey = "ambientLightHorizonMs"; +constexpr auto kFastLightHorizonMsKey = "fastLightHorizonMs"; +constexpr auto kContinuousMappingModeKey = "continuousMappingMode"; +constexpr auto kContinuousLuxCurveKey = "continuousLuxCurve"; +constexpr auto kStepHysteresisRatioKey = "stepHysteresisRatio"; +constexpr auto kBrightenDebounceMsKey = "brightenDebounceMs"; +constexpr auto kDarkenDebounceMsKey = "darkenDebounceMs"; +constexpr int kInitialSampleTimeoutMs = 2000; +constexpr int kRuntimeRecoveryRefreshDelayMs = 1000; + +bool variantToValidLux(const QVariant &value, double *lux) +{ + bool ok = false; + const double converted = value.toDouble(&ok); + if (!ok || !std::isfinite(converted) || converted < 0.0) + return false; + *lux = converted; + return true; +} + +} // namespace + +AmbientBrightnessService::AmbientBrightnessService(QDBusConnection connection, QObject *parent) + : QObject(parent) + , m_connection(std::move(connection)) + , m_model(createAmbientBrightnessPolicy(BrightnessAlgorithm::Continuous)) +{ + connect(&m_model, &AmbientBrightnessModel::supportedChanged, this, [this](bool value) { + Q_EMIT supportedChanged(value); + publishPropertyChange(QStringLiteral("Supported"), value); + }); + connect(&m_model, &AmbientBrightnessModel::stateChanged, this, [this](const QString &value) { + Q_EMIT stateChanged(value); + publishPropertyChange(QStringLiteral("State"), value); + }); + connect(&m_model, &AmbientBrightnessModel::recommendedBrightnessChanged, this, + [this](double value) { + Q_EMIT recommendedBrightnessChanged(value); + publishPropertyChange(QStringLiteral("RecommendedBrightness"), value); + }); + m_monotonicClock.start(); +} + +AmbientBrightnessService::~AmbientBrightnessService() +{ + disconnectSensor(); +} + +bool AmbientBrightnessService::initialize() +{ + initAlgorithmConfig(); + initRuntimeControl(); + if (!m_connection.registerObject(QString::fromLatin1(kObjectPath), this, + QDBusConnection::ExportAllProperties + | QDBusConnection::ExportAllSignals + | QDBusConnection::ExportAllSlots)) { + qCWarning(logAmbientBrightness) << "Failed to register D-Bus object" + << m_connection.lastError().message(); + return false; + } + + m_watcher = new QDBusServiceWatcher(QString::fromLatin1(kSensorService), + QDBusConnection::systemBus(), + QDBusServiceWatcher::WatchForRegistration + | QDBusServiceWatcher::WatchForUnregistration, + this); + connect(m_watcher, &QDBusServiceWatcher::serviceRegistered, + this, &AmbientBrightnessService::onSensorServiceRegistered); + connect(m_watcher, &QDBusServiceWatcher::serviceUnregistered, + this, &AmbientBrightnessService::onSensorServiceUnregistered); + + m_runtimeReady = true; + refreshSensorConnection(); + return true; +} + +void AmbientBrightnessService::onSensorServiceRegistered() +{ + refreshSensorConnection(); +} + +void AmbientBrightnessService::onSensorServiceUnregistered() +{ + disconnectSensor(); +} + +void AmbientBrightnessService::onLidClosed() +{ + qCDebug(logAmbientBrightness) << "lid closed; suspending sensor"; + m_lifecycle.lidClosed = true; + refreshSensorConnection(); +} + +void AmbientBrightnessService::onLidOpened() +{ + const int delayMs = darkenDebounceDelayMs(); + qCDebug(logAmbientBrightness) << "lid opened; reconnect delay=" << delayMs << "ms"; + m_lifecycle.lidClosed = false; + // 开盖后的 lux 可能持续抖动;复用变暗防抖时间,在此期间不重新 Claim,也不计算推荐亮度。 + scheduleDelayedRefresh(delayMs); +} + +int AmbientBrightnessService::darkenDebounceDelayMs() const +{ + double delayMs = ContinuousPolicyConfig{}.darkenDebounceMs; + if (m_config) { + bool ok = false; + const double configured = + m_config->value(QString::fromLatin1(kDarkenDebounceMsKey)).toDouble(&ok); + if (ok && std::isfinite(configured) && configured >= 0.0) + delayMs = configured; + } + return static_cast( + std::min(std::ceil(delayMs), static_cast(std::numeric_limits::max()))); +} + +void AmbientBrightnessService::scheduleDelayedRefresh(int delayMs) +{ + if (!m_wakeRefreshTimer) { + m_wakeRefreshTimer = new QTimer(this); + m_wakeRefreshTimer->setSingleShot(true); + connect(m_wakeRefreshTimer, &QTimer::timeout, this, [this]() { + refreshSensorConnection(); + }); + } + if (m_wakeRefreshTimer->isActive() && m_wakeRefreshTimer->remainingTime() >= delayMs) + return; + // 多个恢复事件可能连续到达,后续事件不能缩短已经安排的稳定等待时间。 + m_wakeRefreshTimer->start(delayMs); +} + +void AmbientBrightnessService::onPrepareForSleep(bool beforeSleep) +{ + qCDebug(logAmbientBrightness) << "prepare for sleep=" << beforeSleep; + m_lifecycle.sleeping = beforeSleep; + if (!beforeSleep) { + scheduleDelayedRefresh(kRuntimeRecoveryRefreshDelayMs); + } else { + refreshSensorConnection(); + } +} + +void AmbientBrightnessService::onSessionActiveChanged(bool active) +{ + qCDebug(logAmbientBrightness) << "session active=" << active; + m_lifecycle.sessionActive = active; + if (!active) + refreshSensorConnection(); + else + scheduleDelayedRefresh(kRuntimeRecoveryRefreshDelayMs); +} + +void AmbientBrightnessService::onAutomaticBrightnessEnabledChanged(bool enabled) +{ + if (m_lifecycle.enabled == enabled) + return; + qCDebug(logAmbientBrightness) << "configured enabled=" << enabled; + m_lifecycle.enabled = enabled; + Q_EMIT enabledChanged(enabled); + publishPropertyChange(QStringLiteral("Enabled"), enabled); + if (!enabled) { + stopInitialSampleWait(); + if (m_evaluationTimer) + m_evaluationTimer->stop(); + if (m_sensor && m_claimed) + m_sensor->call(QStringLiteral("ReleaseLight")); + m_claimed = false; + m_haveSample = false; + m_model.setDisabled(); + } else { + refreshSensorConnection(); + } +} + +void AmbientBrightnessService::Enable(bool active) +{ + qCDebug(logAmbientBrightness) << "enable requested=" << active; + m_lifecycle.enabled = active; + Q_EMIT enabledChanged(active); + publishPropertyChange(QStringLiteral("Enabled"), active); + if (m_config && m_config->isValid()) + m_config->setValue(QString::fromLatin1(kAmbientLightAdjustBrightnessKey), active); + + if (!active) { + // 关闭:释放传感器但保留 Supported=true,状态变为 Disabled + stopInitialSampleWait(); + if (m_evaluationTimer) + m_evaluationTimer->stop(); + if (m_sensor && m_claimed) + m_sensor->call(QStringLiteral("ReleaseLight")); + m_claimed = false; + m_haveSample = false; + m_model.setDisabled(); + } else { + refreshSensorConnection(); + } +} + +void AmbientBrightnessService::onPropertiesChanged(const QString &interface, + const QVariantMap &changed, + const QStringList &) +{ + if ((!m_claimed && !m_claimInProgress) || !m_lifecycle.shouldRun()) + return; + if (interface != QLatin1String(kSensorInterface)) + return; + const auto it = changed.constFind(QStringLiteral("LightLevel")); + if (it == changed.cend()) + return; + double lux = 0.0; + if (!variantToValidLux(*it, &lux)) { + qCWarning(logAmbientBrightness) << "invalid sensor LightLevel=" << *it; + return; + } + if (m_claimInProgress) { + m_pendingInitialLux = lux; + m_havePendingInitialSample = true; + return; + } + if (m_waitingForInitialSample) { + stopInitialSampleWait(); + } + processLux(lux); +} + +void AmbientBrightnessService::connectSensor() +{ + disconnectSensor(); + auto systemBus = QDBusConnection::systemBus(); + m_sensor = new QDBusInterface(QString::fromLatin1(kSensorService), + QString::fromLatin1(kSensorPath), + QString::fromLatin1(kSensorInterface), + systemBus, this); + if (!m_sensor->isValid()) { + qCWarning(logAmbientBrightness) << "sensor interface invalid"; + delete m_sensor; + m_sensor = nullptr; + m_model.makeUnavailable(); + return; + } + + const bool hasAmbientLight = m_sensor->property("HasAmbientLight").toBool(); + const QString unit = m_sensor->property("LightLevelUnit").toString(); + if (!hasAmbientLight || (!unit.isEmpty() && unit != QLatin1String("lux"))) { + delete m_sensor; + m_sensor = nullptr; + m_model.makeUnavailable(); + return; + } + + // iio-sensor-proxy 的部分驱动会在 ClaimLight 调用期间立即上报首帧, + // 因此必须先订阅 PropertiesChanged,再执行 ClaimLight。 + const bool signalConnected = systemBus.connect( + QString::fromLatin1(kSensorService), QString::fromLatin1(kSensorPath), + QString::fromLatin1(kPropertiesInterface), QStringLiteral("PropertiesChanged"), + this, SLOT(onPropertiesChanged(QString,QVariantMap,QStringList))); + if (!signalConnected) { + qCWarning(logAmbientBrightness) << "failed to subscribe to sensor PropertiesChanged" + << systemBus.lastError().message(); + delete m_sensor; + m_sensor = nullptr; + m_model.makeUnavailable(); + return; + } + + m_waitingForInitialSample = true; + m_claimInProgress = true; + m_havePendingInitialSample = false; + const QDBusReply claimReply = m_sensor->call(QStringLiteral("ClaimLight")); + m_claimInProgress = false; + if (!claimReply.isValid()) { + qCWarning(logAmbientBrightness) << "ClaimLight failed" << claimReply.error().message(); + stopInitialSampleWait(); + systemBus.disconnect(QString::fromLatin1(kSensorService), + QString::fromLatin1(kSensorPath), + QString::fromLatin1(kPropertiesInterface), + QStringLiteral("PropertiesChanged"), this, + SLOT(onPropertiesChanged(QString,QVariantMap,QStringList))); + delete m_sensor; + m_sensor = nullptr; + m_model.makeUnavailable(); + return; + } + m_claimed = true; + m_haveSample = false; + m_model.waitForSample(); + qCDebug(logAmbientBrightness) << "sensor claimed: unit=" << unit + << "pending initial sample=" << m_havePendingInitialSample; + if (m_havePendingInitialSample) { + const double pendingLux = m_pendingInitialLux; + stopInitialSampleWait(); + processLux(pendingLux); + } else { + startInitialSampleTimeout(); + } +} + +void AmbientBrightnessService::disconnectSensor() +{ + if (m_sensor || m_claimed) + qCDebug(logAmbientBrightness) << "disconnecting sensor: claimed=" << m_claimed; + QDBusConnection::systemBus().disconnect(QString::fromLatin1(kSensorService), + QString::fromLatin1(kSensorPath), + QString::fromLatin1(kPropertiesInterface), + QStringLiteral("PropertiesChanged"), this, + SLOT(onPropertiesChanged(QString,QVariantMap,QStringList))); + if (m_sensor && m_claimed) + m_sensor->call(QStringLiteral("ReleaseLight")); + m_claimed = false; + m_claimInProgress = false; + stopInitialSampleWait(); + delete m_sensor; + m_sensor = nullptr; + if (m_evaluationTimer) + m_evaluationTimer->stop(); + m_haveSample = false; + m_model.makeUnavailable(); +} + +void AmbientBrightnessService::initRuntimeControl() +{ + auto systemBus = QDBusConnection::systemBus(); + systemBus.connect(QString::fromLatin1(kPowerService), + QString::fromLatin1(kPowerPath), + QString::fromLatin1(kPowerInterface), + QStringLiteral("LidClosed"), this, SLOT(onLidClosed())); + systemBus.connect(QString::fromLatin1(kPowerService), + QString::fromLatin1(kPowerPath), + QString::fromLatin1(kPowerInterface), + QStringLiteral("LidOpened"), this, SLOT(onLidOpened())); + + // login1: PrepareForSleep (system bus) + constexpr auto kLogin1Service = "org.freedesktop.login1"; + constexpr auto kLogin1Path = "/org/freedesktop/login1"; + constexpr auto kLogin1Manager = "org.freedesktop.login1.Manager"; + systemBus.connect(QString::fromLatin1(kLogin1Service), + QString::fromLatin1(kLogin1Path), + QString::fromLatin1(kLogin1Manager), + QStringLiteral("PrepareForSleep"), this, + SLOT(onPrepareForSleep(bool))); + + // login1: Session.Active (system bus) + initLogin1Session(); + + QDBusInterface power(QString::fromLatin1(kPowerService), + QString::fromLatin1(kPowerPath), + QString::fromLatin1(kPowerInterface), systemBus); + if (power.isValid()) { + const QVariant lidClosed = power.property("LidClosed"); + if (lidClosed.isValid() && lidClosed.canConvert()) + m_lifecycle.lidClosed = lidClosed.toBool(); + } + +} + +void AmbientBrightnessService::initLogin1Session() +{ + constexpr auto kLogin1Service = "org.freedesktop.login1"; + constexpr auto kLogin1Path = "/org/freedesktop/login1"; + constexpr auto kLogin1Manager = "org.freedesktop.login1.Manager"; + + const QString sessionId = qEnvironmentVariable("XDG_SESSION_ID"); + if (sessionId.isEmpty()) { + qCWarning(logAmbientBrightness) + << "XDG_SESSION_ID empty; skipping login1 Session.Active monitoring"; + return; + } + + QDBusInterface manager(QString::fromLatin1(kLogin1Service), + QString::fromLatin1(kLogin1Path), + QString::fromLatin1(kLogin1Manager), + QDBusConnection::systemBus()); + QDBusReply reply = + manager.call(QStringLiteral("GetSession"), sessionId); + if (!reply.isValid()) { + qCWarning(logAmbientBrightness) + << "GetSession failed for" << sessionId + << reply.error().message(); + return; + } + m_login1SessionPath = reply.value().path(); + + // 读取当前 Session.Active + QDBusInterface session(QString::fromLatin1(kLogin1Service), + m_login1SessionPath, + QStringLiteral("org.freedesktop.DBus.Properties"), + QDBusConnection::systemBus()); + QDBusReply activeReply = session.call( + QStringLiteral("Get"), + QStringLiteral("org.freedesktop.login1.Session"), + QStringLiteral("Active")); + if (activeReply.isValid() && activeReply.value().canConvert()) { + m_lifecycle.sessionActive = activeReply.value().toBool(); + } + + // 监听 PropertiesChanged + auto systemBus = QDBusConnection::systemBus(); + systemBus.connect( + QString::fromLatin1(kLogin1Service), m_login1SessionPath, + QStringLiteral("org.freedesktop.DBus.Properties"), + QStringLiteral("PropertiesChanged"), this, + SLOT(onSessionPropertiesChanged(QString,QVariantMap,QStringList))); +} + +void AmbientBrightnessService::onSessionPropertiesChanged( + const QString &interface, const QVariantMap &changed, const QStringList &) +{ + if (interface != QLatin1String("org.freedesktop.login1.Session")) + return; + const auto it = changed.constFind(QStringLiteral("Active")); + if (it == changed.cend()) + return; + onSessionActiveChanged(it->toBool()); +} + +void AmbientBrightnessService::refreshSensorConnection() +{ + if (!m_runtimeReady) + return; + + if (!m_lifecycle.shouldRun()) { + if (m_sensor || m_claimed) + disconnectSensor(); + else + m_model.makeUnavailable(); + return; + } + + if (m_claimed) + return; + + auto *interface = QDBusConnection::systemBus().interface(); + if (!interface) { + m_model.makeUnavailable(); + return; + } + const auto registered = interface->isServiceRegistered(QString::fromLatin1(kSensorService)); + if (registered.isValid() && registered.value()) + connectSensor(); + else + m_model.makeUnavailable(); +} + +void AmbientBrightnessService::startInitialSampleTimeout() +{ + if (!m_initialSampleTimer) { + m_initialSampleTimer = new QTimer(this); + m_initialSampleTimer->setSingleShot(true); + connect(m_initialSampleTimer, &QTimer::timeout, this, + &AmbientBrightnessService::onInitialSampleTimeout); + } + m_initialSampleTimer->start(kInitialSampleTimeoutMs); +} + +void AmbientBrightnessService::stopInitialSampleWait() +{ + m_waitingForInitialSample = false; + m_havePendingInitialSample = false; + if (m_initialSampleTimer) + m_initialSampleTimer->stop(); +} + +void AmbientBrightnessService::onInitialSampleTimeout() +{ + if (!m_waitingForInitialSample || !m_claimed || !m_sensor + || !m_lifecycle.shouldRun()) { + return; + } + + double lux = 0.0; + const QVariant lightLevel = m_sensor->property("LightLevel"); + if (!variantToValidLux(lightLevel, &lux)) { + qCWarning(logAmbientBrightness) + << "no post-claim LightLevel signal and delayed property read failed" + << m_sensor->lastError().message(); + return; + } + + stopInitialSampleWait(); + processLux(lux); +} + +void AmbientBrightnessService::processLux(double lux) +{ + if (!std::isfinite(lux) || lux < 0.0) { + qCWarning(logAmbientBrightness) << "ignoring invalid lux=" << lux; + return; + } + m_lastLux = lux; + m_haveSample = true; + m_model.submitSample(lux, static_cast(m_monotonicClock.elapsed())); + + const double delayMs = m_model.nextEvaluationDelayMs(); + if (delayMs > 0.0) + armEvaluationTimer(delayMs); + else if (m_evaluationTimer) + m_evaluationTimer->stop(); +} + + +void AmbientBrightnessService::armEvaluationTimer(double delayMs) +{ + if (!m_evaluationTimer) { + m_evaluationTimer = new QTimer(this); + m_evaluationTimer->setSingleShot(true); + connect(m_evaluationTimer, &QTimer::timeout, this, + &AmbientBrightnessService::onEvaluationTimerElapsed); + } + m_evaluationTimer->start(std::max(1, static_cast(std::ceil(delayMs)))); +} + +void AmbientBrightnessService::onEvaluationTimerElapsed() +{ + if (!m_claimed || !m_lifecycle.shouldRun()) + return; + const double now = static_cast(m_monotonicClock.elapsed()); + m_model.tick(now); + + const double delayMs = m_model.nextEvaluationDelayMs(); + if (delayMs > 0.0) + armEvaluationTimer(delayMs); + else if (m_evaluationTimer) + m_evaluationTimer->stop(); +} + +void AmbientBrightnessService::publishPropertyChange(const QString &name, const QVariant &value) +{ + QVariantMap changed{{name, value}}; + auto signal = QDBusMessage::createSignal(QString::fromLatin1(kObjectPath), + QString::fromLatin1(kPropertiesInterface), + QStringLiteral("PropertiesChanged")); + signal << QStringLiteral("org.deepin.dde.AmbientBrightness1") << changed << QStringList{}; + m_connection.send(signal); +} + +void AmbientBrightnessService::initAlgorithmConfig() +{ + m_config = Dtk::Core::DConfig::create(QString::fromLatin1(kConfigAppId), + QString::fromLatin1(kConfigName), {}, this); + if (!m_config) { + qCWarning(logAmbientBrightness) << "failed to create algorithm DConfig; using continuous"; + return; + } + + // 读取光感开关初始值 + const QVariant enabled = m_config->value( + QString::fromLatin1(kAmbientLightAdjustBrightnessKey)); + if (enabled.isValid() && enabled.canConvert()) + m_lifecycle.enabled = enabled.toBool(); + + rebuildCurrentPolicy(); + connect(m_config, &Dtk::Core::DConfig::valueChanged, this, [this](const QString &key) { + if (key == QLatin1String(kAmbientLightAdjustBrightnessKey)) { + onAutomaticBrightnessEnabledChanged( + m_config->value(key).toBool()); + } else if (key == QLatin1String(kContinuousMappingModeKey) + || key == QLatin1String(kUseWeightedWindowsKey) + || key == QLatin1String(kAmbientLightHorizonMsKey) + || key == QLatin1String(kFastLightHorizonMsKey) + || key == QLatin1String(kContinuousLuxCurveKey) + || key == QLatin1String(kStepHysteresisRatioKey) + || key == QLatin1String(kBrightenDebounceMsKey) + || key == QLatin1String(kDarkenDebounceMsKey)) { + rebuildCurrentPolicy(); + } + }); +} + +void AmbientBrightnessService::rebuildCurrentPolicy() +{ + if (m_evaluationTimer) + m_evaluationTimer->stop(); + + auto policy = createAmbientBrightnessPolicy(BrightnessAlgorithm::Continuous, m_config); + m_model.setPolicy(std::move(policy)); + if (!m_claimed) + return; + if (m_haveSample) + processLux(m_lastLux); + else + m_model.waitForSample(); +} + +} // namespace dde::ambient_brightness diff --git a/src/plugin-qt/ambient-brightness/ambientbrightnessservice.h b/src/plugin-qt/ambient-brightness/ambientbrightnessservice.h new file mode 100644 index 00000000..013521d2 --- /dev/null +++ b/src/plugin-qt/ambient-brightness/ambientbrightnessservice.h @@ -0,0 +1,109 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include "ambientbrightnessmodel.h" +#include "ambientbrightnesspolicyfactory.h" +#include "ambientlightlifecyclestate.h" + +#include +#include +#include +#include +#include + +class QDBusInterface; +class QDBusServiceWatcher; + +namespace Dtk::Core { +class DConfig; +} + +namespace dde::ambient_brightness { + +/// D-Bus 服务:连接 iio-sensor-proxy,驱动 AmbientBrightnessModel, +/// 对外暴露 org.deepin.dde.AmbientBrightness1 接口。 +class AmbientBrightnessService : public QObject { + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.deepin.dde.AmbientBrightness1") + Q_PROPERTY(bool Supported READ supported NOTIFY supportedChanged) + Q_PROPERTY(QString State READ state NOTIFY stateChanged) + Q_PROPERTY(bool Enabled READ enabled NOTIFY enabledChanged) + Q_PROPERTY(double RecommendedBrightness READ recommendedBrightness NOTIFY recommendedBrightnessChanged) + +public: + explicit AmbientBrightnessService(QDBusConnection connection, QObject *parent = nullptr); + ~AmbientBrightnessService() override; + + bool initialize(); + + bool supported() const { return m_model.supported(); } + QString state() const { return m_model.state(); } + bool enabled() const { return m_lifecycle.enabled; } + double recommendedBrightness() const { return m_model.recommendedBrightness(); } + +public Q_SLOTS: + void Enable(bool active); + +Q_SIGNALS: + void supportedChanged(bool value); + void stateChanged(const QString &value); + void enabledChanged(bool value); + void recommendedBrightnessChanged(double value); + +private Q_SLOTS: + void onSensorServiceRegistered(); + void onSensorServiceUnregistered(); + void onLidClosed(); + void onLidOpened(); + void onPrepareForSleep(bool beforeSleep); + void onSessionActiveChanged(bool active); + void onSessionPropertiesChanged(const QString &interface, + const QVariantMap &changed, + const QStringList &); + void onAutomaticBrightnessEnabledChanged(bool enabled); + void onPropertiesChanged(const QString &interface, + const QVariantMap &changed, + const QStringList &invalidated); + +private: + void connectSensor(); + void disconnectSensor(); + void initRuntimeControl(); + void initLogin1Session(); + int darkenDebounceDelayMs() const; + void scheduleDelayedRefresh(int delayMs); + void refreshSensorConnection(); + void startInitialSampleTimeout(); + void stopInitialSampleWait(); + void onInitialSampleTimeout(); + void processLux(double lux); + void publishPropertyChange(const QString &name, const QVariant &value); + void armEvaluationTimer(double delayMs); + void onEvaluationTimerElapsed(); + void initAlgorithmConfig(); + void rebuildCurrentPolicy(); + + QDBusConnection m_connection; + QDBusInterface *m_sensor = nullptr; + QDBusServiceWatcher *m_watcher = nullptr; + Dtk::Core::DConfig *m_config = nullptr; + AmbientBrightnessModel m_model; + QElapsedTimer m_monotonicClock; + bool m_claimed = false; + QTimer *m_evaluationTimer = nullptr; + double m_lastLux = 0.0; + bool m_haveSample = false; + AmbientLightLifecycleState m_lifecycle; + bool m_runtimeReady = false; + QString m_login1SessionPath; + QTimer *m_wakeRefreshTimer = nullptr; + QTimer *m_initialSampleTimer = nullptr; + bool m_waitingForInitialSample = false; + bool m_claimInProgress = false; + bool m_havePendingInitialSample = false; + double m_pendingInitialLux = 0.0; +}; + +} // namespace dde::ambient_brightness diff --git a/src/plugin-qt/ambient-brightness/ambientlightlifecyclestate.h b/src/plugin-qt/ambient-brightness/ambientlightlifecyclestate.h new file mode 100644 index 00000000..72a59f03 --- /dev/null +++ b/src/plugin-qt/ambient-brightness/ambientlightlifecyclestate.h @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +namespace dde::ambient_brightness { + +/// 决定环境光传感器是否应处于 Claim 状态。 +/// 自动亮度开关、合盖和休眠是相互独立的阻断条件;只有全部允许时才运行。 +struct AmbientLightLifecycleState { + bool enabled = true; + bool lidClosed = false; + bool sleeping = false; + bool sessionActive = true; + + constexpr bool shouldRun() const noexcept + { + return enabled && !lidClosed && !sleeping && sessionActive; + } +}; + +} // namespace dde::ambient_brightness diff --git a/src/plugin-qt/ambient-brightness/brightnesscurve.cpp b/src/plugin-qt/ambient-brightness/brightnesscurve.cpp new file mode 100644 index 00000000..93476e89 --- /dev/null +++ b/src/plugin-qt/ambient-brightness/brightnesscurve.cpp @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "brightnesscurve.h" + +#include +#include + +namespace dde::ambient_brightness { + +BrightnessCurve::BrightnessCurve(std::vector points) +{ + if (isValid(points)) + m_points = std::move(points); +} + +bool BrightnessCurve::isValid(const std::vector &points) +{ + if (points.size() < 2) + return false; + for (size_t i = 0; i < points.size(); ++i) { + const auto &p = points[i]; + if (!std::isfinite(p.lux) || !std::isfinite(p.brightness) + || p.lux < 0.0 || p.brightness < 0.0 || p.brightness > 1.0) { + return false; + } + if (i > 0 && (p.lux <= points[i - 1].lux + || p.brightness < points[i - 1].brightness)) { + return false; + } + } + return true; +} + +bool BrightnessCurve::set(std::vector points) +{ + if (!isValid(points)) + return false; + m_points = std::move(points); + return true; +} + +double BrightnessCurve::map(double lux) const +{ + if (m_points.empty()) + return 0.0; + if (lux <= m_points.front().lux) + return m_points.front().brightness; + if (lux >= m_points.back().lux) + return m_points.back().brightness; + + const auto upper = std::upper_bound(m_points.begin(), m_points.end(), lux, + [](double v, const CurvePoint &p) { + return v < p.lux; + }); + const auto &high = *upper; + const auto &low = *(upper - 1); + const double lowLog = std::log1p(low.lux); + const double highLog = std::log1p(high.lux); + const double ratio = (std::log1p(lux) - lowLog) / (highLog - lowLog); + return low.brightness + ratio * (high.brightness - low.brightness); +} + +} // namespace dde::ambient_brightness diff --git a/src/plugin-qt/ambient-brightness/brightnesscurve.h b/src/plugin-qt/ambient-brightness/brightnesscurve.h new file mode 100644 index 00000000..75b58c66 --- /dev/null +++ b/src/plugin-qt/ambient-brightness/brightnesscurve.h @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include + +namespace dde::ambient_brightness { + +/// lux → 亮度曲线控制点。 +struct CurvePoint { + double lux = 0.0; + double brightness = 0.0; // [0.0, 1.0] +}; + +/// 配置驱动的 lux → 亮度映射。 +/// +/// 在 log1p(lux) 空间做分段线性插值: +/// - 低于第一个控制点:返回第一个亮度 +/// - 高于最后一个控制点:返回最后一个亮度 +/// - 中间:log1p 空间线性插值 +class BrightnessCurve { +public: + BrightnessCurve() = default; + explicit BrightnessCurve(std::vector points); + + /// lux → brightness。 + double map(double lux) const; + + /// 校验曲线是否有效(lux 严格递增, brightness 单调不降, 范围 [0,1])。 + static bool isValid(const std::vector &points); + + /// 替换曲线。无效则返回 false 且不修改。 + bool set(std::vector points); + + const std::vector &points() const { return m_points; } + +private: + std::vector m_points; +}; + +} // namespace dde::ambient_brightness diff --git a/src/plugin-qt/ambient-brightness/configs/org.deepin.dde.daemon.ambient-brightness.json b/src/plugin-qt/ambient-brightness/configs/org.deepin.dde.daemon.ambient-brightness.json new file mode 100644 index 00000000..17dbd491 --- /dev/null +++ b/src/plugin-qt/ambient-brightness/configs/org.deepin.dde.daemon.ambient-brightness.json @@ -0,0 +1,105 @@ +{ + "magic": "dsg.config.meta", + "version": "1.0", + "contents": { + "ambientLightAdjustBrightness": { + "value": true, + "serial": 0, + "flags": [], + "name": "Ambient light auto brightness", + "name[zh_CN]": "环境光自动调节亮度", + "description": "Boolean. Enable or disable ambient light auto brightness. When enabled, the service claims the sensor, reads lux, and publishes recommended brightness. When disabled, the service releases the sensor claim and stops recommendation.", + "permissions": "readwrite", + "visibility": "public", + "description[zh_CN]": "布尔值。启用或禁用环境光自动亮度。启用时,服务声明传感器、读取 lux 并发布推荐亮度。禁用时,服务释放传感器声明并停止推荐。" + }, + "continuousMappingMode": { + "value": "steps", + "serial": 0, + "flags": [], + "name": "Mapping mode", + "name[zh_CN]": "映射模式", + "description": "Mapping mode enum. steps: lux falls into a step band and outputs the fixed brightness of that band (default). curve: interpolate continuously between control points in log1p(lux) space.", + "permissions": "readwrite", + "visibility": "public", + "description[zh_CN]": "映射模式。steps:lux 落入某个档位区间时输出该区间固定亮度(默认)。curve:在 log1p(lux) 空间对控制点做连续插值。" + }, + "useWeightedWindows": { + "value": false, + "serial": 0, + "flags": [], + "name": "Use weighted windows", + "name[zh_CN]": "启用双窗口加权", + "description": "Boolean. true: smooth sensor data with fast/slow time-weighted windows. false: use the latest raw lux and rely on debounce plus hysteresis; recommended for low-frequency sensors such as an 800 ms reporting interval.", + "permissions": "readwrite", + "visibility": "public", + "description[zh_CN]": "布尔值。true:使用 fast/slow 时间加权窗口平滑传感器数据,此时 ambientLightHorizonMs 和 fastLightHorizonMs 生效。false:直接使用最新 rawLux,依靠 debounce 和滞回防抖,适合约 800ms 上报一次的低频传感器。" + }, + "ambientLightHorizonMs": { + "value": 10000, + "serial": 0, + "flags": [], + "name": "Slow window horizon", + "name[zh_CN]": "慢窗口时长", + "description": "Slow weighted-window duration in milliseconds. Effective only when useWeightedWindows=true. Valid range: (0, 10000], and the value must be greater than or equal to fastLightHorizonMs. A larger value is steadier but responds more slowly.", + "permissions": "readwrite", + "visibility": "public", + "description[zh_CN]": "慢加权窗口时长,单位毫秒。仅 useWeightedWindows=true 时生效。有效范围:(0, 10000],且必须大于或等于 fastLightHorizonMs。值越大越稳定,但响应越慢。" + }, + "fastLightHorizonMs": { + "value": 1000, + "serial": 0, + "flags": [], + "name": "Fast window horizon", + "name[zh_CN]": "快窗口时长", + "description": "Fast weighted-window duration in milliseconds. Effective only when useWeightedWindows=true. Valid range: (0, ambientLightHorizonMs]. A smaller value responds faster but provides less smoothing.", + "permissions": "readwrite", + "visibility": "public", + "description[zh_CN]": "快加权窗口时长,单位毫秒。仅 useWeightedWindows=true 时生效。有效范围:(0, ambientLightHorizonMs]。值越小响应越快,但平滑能力越弱。" + }, + "continuousLuxCurve": { + "value": [{"lux":20,"brightness":0.2},{"lux":120,"brightness":0.4},{"lux":220,"brightness":0.6},{"lux":320,"brightness":0.8},{"lux":650,"brightness":0.9},{"lux":2000,"brightness":1.0}], + "serial": 0, + "flags": [], + "name": "Lux-brightness curve", + "name[zh_CN]": "lux-亮度曲线", + "description": "JSON array of {lux, brightness} control points. At least two points are required; lux must be finite, non-negative, and strictly increasing; brightness must be within [0,1] and non-decreasing. In steps mode, adjacent lux midpoints define initial neutral boundaries and each point supplies a fixed brightness; directional thresholds then apply stepHysteresisRatio. In curve mode, interpolation is linear in log1p(lux) space. Invalid data falls back to the built-in default.", + "permissions": "readwrite", + "visibility": "public", + "description[zh_CN]": "由 {lux, brightness} 控制点组成的 JSON 数组。至少需要两个点;lux 必须是有限、非负且严格递增的数值;brightness 必须位于 [0,1] 且单调不降。steps 模式下相邻 lux 的中点作为初始中性边界,每个控制点提供固定亮度,运行时再应用 stepHysteresisRatio 双向阈值。curve 模式下在 log1p(lux) 空间线性插值。配置无效时回退到内置默认值。" + }, + "stepHysteresisRatio": { + "value": 0.6, + "serial": 0, + "flags": [], + "name": "Step hysteresis ratio", + "name[zh_CN]": "档位滞回比例", + "description": "Effective only in steps mode. Valid range: [0.5, 1.0]. For adjacent lux points a= 0. Larger values reject short flashes but respond more slowly.", + "permissions": "readwrite", + "visibility": "public", + "description[zh_CN]": "变亮防抖时间,单位毫秒。lux 必须连续高于变亮阈值达到该时长,才发布更亮的推荐值。有效范围:>= 0。值越大越能过滤短暂闪光,但响应越慢。" + }, + "darkenDebounceMs": { + "value": 2000, + "serial": 0, + "flags": [], + "name": "Darkening debounce", + "name[zh_CN]": "变暗防抖时间", + "description": "Milliseconds that lux must remain continuously below the darkening threshold before publishing a darker recommendation. Valid range: >= 0. Larger values reject brief sensor occlusion but respond more slowly.", + "permissions": "readwrite", + "visibility": "public", + "description[zh_CN]": "变暗防抖时间,单位毫秒。lux 必须连续低于变暗阈值达到该时长,才发布更暗的推荐值。有效范围:>= 0。值越大越能过滤短暂遮挡,但响应越慢。" + } + } +} diff --git a/src/plugin-qt/ambient-brightness/continuous/README.md b/src/plugin-qt/ambient-brightness/continuous/README.md new file mode 100644 index 00000000..e06dc514 --- /dev/null +++ b/src/plugin-qt/ambient-brightness/continuous/README.md @@ -0,0 +1,254 @@ +# ContinuousAmbientLightPolicy + +`ContinuousAmbientLightPolicy` 是当前模块唯一的环境光策略实现,位于: + +- [`continuousambientlightpolicy.h`](continuousambientlightpolicy.h) +- [`continuousambientlightpolicy.cpp`](continuousambientlightpolicy.cpp) + +它接收带单调时间戳的 lux 样本,维护环境光状态,并在需要时返回一个 `Recommendation`。策略本身不依赖 Qt D-Bus,不负责读取 DConfig,也不直接设置显示器亮度。 + +## 处理目标 + +策略需要同时解决两个问题: + +1. **输入稳定性**:过滤非法值、传感器噪声、短时遮挡和阈值附近抖动。 +2. **亮度映射**:把稳定后的环境光 lux 转成 `[0.0, 1.0]` 的推荐亮度。 + +处理链路如下: + +```mermaid +flowchart LR + A[SensorSample] --> B[输入校验] + B --> C[环形缓存] + C --> D{输入模式} + D -->|加权窗口| E[fast / slow lux] + D -->|rawLux| F[最新样本] + E --> G[滞回与 debounce] + F --> G + G --> H{映射模式} + H -->|steps| I[档位映射] + H -->|curve| J[log1p 曲线插值] + I --> K[推荐值死区] + J --> K + K --> L[Recommendation 或无输出] +``` + +## 输入和输出 + +### 输入:`SensorSample` + +```cpp +struct SensorSample { + double lux; + double monotonicTimestampMs; + Source source; +}; +``` + +策略处理输入时遵循以下规则: + +- lux 和时间戳必须是有限数值,lux 必须大于等于 `0`。 +- 超过 `maxSensorLux`(默认 `100000`)的有限 lux 会饱和到上限,使强光环境稳定输出曲线末端亮度。 +- 初始化后,时间戳必须严格晚于上一条样本。 + +非法样本不会进入缓存,也不会改变策略状态。 + +### 输出:`Recommendation` + +```cpp +struct Recommendation { + double rawLux; + double fastLux; + double slowLux; + double stableLux; + double brightness; +}; +``` + +`brightness` 是 Model 和 Service 最终对外发布的值;其余字段用于策略状态、日志和调试。 + +策略返回 `std::nullopt` 表示当前没有新的推荐值,不代表样本处理失败。 + +## 内部状态 + +- `AmbientLightRingBuffer`:保存最近一段时间的 lux 和时间戳。 +- `m_fastAmbientLux` / `m_slowAmbientLux`:快、慢窗口结果。 +- `m_stableLux`:最近一次被策略接受的稳定 lux。 +- `m_lastPublishedBrightness`:最近一次通过推荐值死区并发布的亮度。 +- `m_lastTimestampMs`:最近一条传感器样本时间。 +- `m_lastEvaluationTimestampMs`:最近一次 `update()` 或 `tick()` 的评估时间。 + +`reset()` 会清空缓存、稳定值、档位和已发布亮度。算法配置重建,或 Service 因关闭自动亮度、合盖、休眠而 stop 光感时都会执行 reset;重新启用、开盖或唤醒后,Service 会先订阅 `PropertiesChanged` 再重新 Claim,优先把 Claim 期间或之后收到的首个有效 `LightLevel` 作为初始样本。若 2 秒内没有收到变化信号,则延迟读取一次 `LightLevel` 属性作为兜底,避免使用 Claim 时刻可能尚未刷新的缓存值。 + +## 样本缓存 + +环形缓存的初始容量根据慢窗口计算,至少保留 8 个样本: + +```text +initialCapacity = max(8, ambientLightHorizonMs / 100) +``` + +每次 `update()` 会先裁剪慢窗口之外的数据,同时保留计算窗口边界所需的前一个样本;如果窗口内的有效样本超过初始容量,缓存按两倍容量增长,不会因采样频率高于 10Hz 而覆盖仍在窗口内的样本。扩容后仍按逻辑顺序访问,正常写入不移动已有数据。 + +## 两种输入处理模式 + +### 1. fast/slow 加权窗口:`useWeightedWindows=true` + +策略分别在 `fastLightHorizonMs`(默认 1000ms)和 `ambientLightHorizonMs`(默认 10000ms)内计算时间加权平均值: + +- `fastLux`:窗口短,响应更快。 +- `slowLux`:窗口长,变化更稳定。 +- 变亮主要由 `fastLux` 驱动。 +- 变暗同时要求 `slowLux` 和 `fastLux` 都达到暗化条件,避免短暂变暗造成亮度快速下降。 + +权重按样本覆盖的时间区间计算。实现使用权重积分函数: + +$$ +F(x)=x(0.5x+I) +$$ + +其中 $I$ 是 `weightingIntercept`(默认 10000),某个样本区间的权重为: + +$$ +w_i=F(x_{end})-F(x_{start}) +$$ + +最终窗口值为: + +$$ +L_window = (Σ w_i × lux_i) / Σ w_i +$$ + +越接近当前时刻的区间权重越大。实现还使用 100ms 的预测尾段,使最新样本在评估时仍有有效覆盖区间。 + +该模式适合采样频率较高、窗口内有多个样本的传感器。 + +### 2. 原始样本模式:`useWeightedWindows=false` + +关闭窗口计算后: + +- `rawLux` 使用最近一条有效样本。 +- 变亮和变暗的触发值都基于 `rawLux`。 +- 仍然保留滞回、debounce 和推荐值死区。 + +该模式适合约 800ms 或更低频率的传感器。低频采样时,窗口内样本太少,slow 窗口可能残留旧值,导致响应变慢。 + +## 初始化 + +第一条有效样本不会经过方向滞回和 debounce,而是直接建立初始状态: + +- 加权模式:`stableLux = fastLux`。 +- rawLux 模式:`stableLux = rawLux`。 +- 随后按当前 steps 或 curve 模式计算亮度。 + +初始化会产生第一条 `Recommendation`,让上层尽快得到有效亮度。 + +## 滞回 + +初始化完成后,策略以 `stableLux` 为基准建立两个方向不同的阈值。 + +变亮阈值: + +$$ +T_{bright}=stableLux+\max(minimumHysteresisLux,\ stableLux\times brightenHysteresisRatio) +$$ + +变暗阈值: + +$$ +T_{dark}=stableLux-\max(minimumHysteresisLux,\ stableLux\times darkenHysteresisRatio) +$$ + +默认比例均为 `0.15`,最小 lux 滞回为 `5.0`。lux 必须先离开当前稳定值一段距离,才会进入对应方向的候选转换。 + +## debounce + +变亮和变暗分别配置: + +- `brightenDebounceMs`,默认 2000ms。 +- `darkenDebounceMs`,默认 2000ms。 + +实现采用“最早连续达到或越过阈值时间”语义: + +1. 找到最近一次连续达到或越过方向阈值的样本区间。 +2. 计算该连续区间的起始时间。 +3. 起始时间加上对应方向的 debounce 时长,得到候选转换时间。 +4. 中间出现回到阈值内的样本时,本次连续越界失效,需要重新计时。 + +加权模式下,窗口值负责判断当前是否达到转换条件,缓存中的原始样本时间用于判断连续越界时长。 + +策略通过 `nextEvaluationDelayMs()` 把候选转换时间交给 Service。没有新传感器事件时,Service 调用 `tick()` 推进时间;`tick()` 不会向缓存插入虚拟样本。 + +## lux 到亮度映射 + +配置控制点必须满足:至少两个点;lux 有限、非负且严格递增;brightness 位于 `[0,1]` 且单调不降。配置无效时 Factory 使用内置回退曲线。 + +### steps 模式 + +每个控制点对应一个固定亮度档位。首次分类使用相邻 lux 控制点的中点作为中性边界,避免 reset 前后同一 lux 在滞回区间外落入不同档位。对于相邻控制点 $a +#include +#include + +namespace dde::ambient_brightness { +namespace { +constexpr double kPredictionTimeMs = 100.0; +constexpr double kEvaluationIntervalMs = 100.0; + +std::vector luxStepsFromCurve(const std::vector &curve) +{ + std::vector steps; + steps.reserve(curve.size()); + for (const auto &point : curve) + steps.push_back({ point.lux, point.brightness }); + return steps; +} +} // namespace + +ContinuousAmbientLightPolicy::AmbientLightRingBuffer::AmbientLightRingBuffer(size_t capacity) + : m_capacity(capacity > 0 ? capacity : 1) + , m_lux(m_capacity, 0.0) + , m_time(m_capacity, 0.0) +{ +} + +void ContinuousAmbientLightPolicy::AmbientLightRingBuffer::clear() +{ + m_count = 0; + m_start = 0; +} + +size_t ContinuousAmbientLightPolicy::AmbientLightRingBuffer::size() const +{ + return m_count; +} + +double ContinuousAmbientLightPolicy::AmbientLightRingBuffer::luxAt(size_t index) const +{ + return m_lux[(m_start + index) % m_capacity]; +} + +double ContinuousAmbientLightPolicy::AmbientLightRingBuffer::timeAt(size_t index) const +{ + return m_time[(m_start + index) % m_capacity]; +} + +void ContinuousAmbientLightPolicy::AmbientLightRingBuffer::push(double timeMs, double lux) +{ + if (m_count == m_capacity) { + const size_t newCapacity = m_capacity * 2; + std::vector grownLux(newCapacity, 0.0); + std::vector grownTime(newCapacity, 0.0); + for (size_t i = 0; i < m_count; ++i) { + grownLux[i] = luxAt(i); + grownTime[i] = timeAt(i); + } + m_lux = std::move(grownLux); + m_time = std::move(grownTime); + m_capacity = newCapacity; + m_start = 0; + } + + const size_t pos = (m_start + m_count) % m_capacity; + m_time[pos] = timeMs; + m_lux[pos] = lux; + ++m_count; +} + +void ContinuousAmbientLightPolicy::AmbientLightRingBuffer::prune(double minTimeMs) +{ + while (m_count > 1 && timeAt(1) <= minTimeMs) { + m_start = (m_start + 1) % m_capacity; + --m_count; + } +} + +ContinuousAmbientLightPolicy::ContinuousAmbientLightPolicy(const ContinuousPolicyConfig &config) + : m_buffer(1) +{ + setConfig(config); +} + +bool ContinuousAmbientLightPolicy::isValidConfig(const ContinuousPolicyConfig &c) +{ + if (!std::isfinite(c.maxSensorLux) || c.maxSensorLux <= 0.0 + || !std::isfinite(c.ambientLightHorizonMs) || c.ambientLightHorizonMs <= 0.0 + || !std::isfinite(c.fastLightHorizonMs) || c.fastLightHorizonMs <= 0.0 + || c.fastLightHorizonMs > c.ambientLightHorizonMs || !std::isfinite(c.weightingIntercept) + || c.weightingIntercept < c.ambientLightHorizonMs + || !std::isfinite(c.brightenHysteresisRatio) || c.brightenHysteresisRatio < 0.0 + || !std::isfinite(c.darkenHysteresisRatio) || c.darkenHysteresisRatio < 0.0 + || !std::isfinite(c.minimumHysteresisLux) || c.minimumHysteresisLux < 0.0 + || !std::isfinite(c.brightenDebounceMs) || c.brightenDebounceMs < 0.0 + || !std::isfinite(c.darkenDebounceMs) || c.darkenDebounceMs < 0.0 + || !std::isfinite(c.minimumRecommendationDelta) || c.minimumRecommendationDelta < 0.0 + || c.minimumRecommendationDelta > 1.0) { + return false; + } + if (c.useStepsMode) { + const bool validMapping = + c.luxSteps.empty() ? BrightnessCurve::isValid(c.curve) : isValidLuxSteps(c.luxSteps); + return validMapping && std::isfinite(c.stepHysteresisRatio) && c.stepHysteresisRatio >= 0.5 + && c.stepHysteresisRatio <= 1.0; + } + return BrightnessCurve::isValid(c.curve); +} + +bool ContinuousAmbientLightPolicy::setConfig(const ContinuousPolicyConfig &config) +{ + if (!isValidConfig(config)) + return false; + + m_curve = BrightnessCurve(config.curve); + m_maxSensorLux = config.maxSensorLux; + m_ambientLightHorizonMs = config.ambientLightHorizonMs; + m_fastLightHorizonMs = config.fastLightHorizonMs; + m_weightingIntercept = config.weightingIntercept; + m_brightenHysteresisRatio = config.brightenHysteresisRatio; + m_darkenHysteresisRatio = config.darkenHysteresisRatio; + m_minimumHysteresisLux = config.minimumHysteresisLux; + m_brightenDebounceMs = config.brightenDebounceMs; + m_darkenDebounceMs = config.darkenDebounceMs; + m_minimumRecommendationDelta = config.minimumRecommendationDelta; + m_useWeightedWindows = config.useWeightedWindows; + m_useStepsMode = config.useStepsMode; + m_steps = config.luxSteps; + if (m_useStepsMode && m_steps.empty()) + m_steps = luxStepsFromCurve(config.curve); + m_stepHysteresisRatio = config.stepHysteresisRatio; + if (m_useStepsMode) + computeStepThresholds(); + m_buffer = AmbientLightRingBuffer( + std::max(8, static_cast(m_ambientLightHorizonMs / 100.0))); + reset(); + return true; +} + +void ContinuousAmbientLightPolicy::reset() +{ + m_buffer.clear(); + m_initialized = false; + m_lastTimestampMs = -1.0; + m_lastEvaluationTimestampMs = -1.0; + m_fastAmbientLux = 0.0; + m_slowAmbientLux = 0.0; + m_stableLux = 0.0; + m_currentStepIndex = 0; + m_lastPublishedBrightness.reset(); +} + +std::optional ContinuousAmbientLightPolicy::update(const SensorSample &sample) +{ + if (!std::isfinite(sample.lux) || !std::isfinite(sample.monotonicTimestampMs) + || sample.lux < 0.0 + || (m_initialized && sample.monotonicTimestampMs <= m_lastTimestampMs)) { + return std::nullopt; + } + + const double lux = std::min(sample.lux, m_maxSensorLux); + m_buffer.prune(sample.monotonicTimestampMs - m_ambientLightHorizonMs); + m_buffer.push(sample.monotonicTimestampMs, lux); + m_lastTimestampMs = sample.monotonicTimestampMs; + m_lastEvaluationTimestampMs = sample.monotonicTimestampMs; + return evaluate(sample.monotonicTimestampMs); +} + +std::optional ContinuousAmbientLightPolicy::tick(double monotonicTimestampMs) +{ + if (!m_initialized || m_buffer.size() == 0 || !std::isfinite(monotonicTimestampMs) + || monotonicTimestampMs < m_lastEvaluationTimestampMs) { + return std::nullopt; + } + m_lastEvaluationTimestampMs = monotonicTimestampMs; + m_buffer.prune(monotonicTimestampMs - m_ambientLightHorizonMs); + return evaluate(monotonicTimestampMs); +} + +std::optional ContinuousAmbientLightPolicy::evaluate(double nowMs) +{ + const double rawLux = m_buffer.size() > 0 ? m_buffer.luxAt(m_buffer.size() - 1) : 0.0; + + if (m_useWeightedWindows) { + m_fastAmbientLux = calculateWeightedAmbientLux(nowMs, m_fastLightHorizonMs); + m_slowAmbientLux = calculateWeightedAmbientLux(nowMs, m_ambientLightHorizonMs); + } + + if (!m_initialized) { + m_initialized = true; + m_stableLux = m_useWeightedWindows ? m_fastAmbientLux : rawLux; + if (m_useStepsMode && !m_steps.empty()) + m_currentStepIndex = findStepForLux(m_stableLux); + const double brightness = mapLuxToBrightness(m_stableLux); + m_lastPublishedBrightness = brightness; + return Recommendation{ rawLux, + m_fastAmbientLux, + m_slowAmbientLux, + m_stableLux, + brightness }; + } + + const double brightenTransition = nextBrighteningTransitionMs(nowMs); + const double darkenTransition = nextDarkeningTransitionMs(nowMs); + const double triggerLux = m_useWeightedWindows ? m_fastAmbientLux : rawLux; + const double darkTriggerLux = m_useWeightedWindows ? m_slowAmbientLux : rawLux; + const bool brightReady = triggerLux >= brighteningThresholdLux() && brightenTransition <= nowMs; + const bool darkReady = darkTriggerLux <= darkeningThresholdLux() + && triggerLux <= darkeningThresholdLux() && darkenTransition <= nowMs; + + if (!brightReady && !darkReady) + return std::nullopt; + + const double previousLux = m_stableLux; + m_stableLux = triggerLux; + if (m_useStepsMode && !m_steps.empty()) { + const double brightenTargetLux = + m_useWeightedWindows ? (triggerLux > previousLux * 2.0 ? rawLux : triggerLux) : rawLux; + const double darkenTargetLux = + m_useWeightedWindows ? (triggerLux < previousLux * 0.5 ? rawLux : triggerLux) : rawLux; + m_currentStepIndex = brightReady ? findTargetStepForBrightening(brightenTargetLux) + : findTargetStepForDarkening(darkenTargetLux); + } + const double brightness = mapLuxToBrightness(m_stableLux); + if (m_lastPublishedBrightness + && std::abs(brightness - *m_lastPublishedBrightness) < m_minimumRecommendationDelta) { + return std::nullopt; + } + + m_lastPublishedBrightness = brightness; + return Recommendation{ rawLux, m_fastAmbientLux, m_slowAmbientLux, m_stableLux, brightness }; +} + +double ContinuousAmbientLightPolicy::nextEvaluationDelayMs() const +{ + if (!m_initialized || m_buffer.size() == 0) + return 0.0; + + const bool supportsBrightening = latestSampleSupportsBrightening(); + const bool supportsDarkening = latestSampleSupportsDarkening(); + if (!supportsBrightening && !supportsDarkening) + return 0.0; + + const double nowMs = m_lastEvaluationTimestampMs; + double next = std::numeric_limits::infinity(); + if (supportsBrightening) + next = std::min(next, nextBrighteningTransitionMs(nowMs)); + if (supportsDarkening) + next = std::min(next, nextDarkeningTransitionMs(nowMs)); + if (next <= nowMs) + return kEvaluationIntervalMs; + return next - nowMs; +} + +double ContinuousAmbientLightPolicy::brighteningThresholdLux() const +{ + if (m_useStepsMode && !m_steps.empty()) { + if (m_currentStepIndex < m_stepThresholds.size()) + return m_stepThresholds[m_currentStepIndex].increaseThreshold; + return std::numeric_limits::infinity(); + } + return m_stableLux + std::max(m_minimumHysteresisLux, m_stableLux * m_brightenHysteresisRatio); +} + +double ContinuousAmbientLightPolicy::darkeningThresholdLux() const +{ + if (m_useStepsMode && !m_steps.empty()) { + if (m_currentStepIndex < m_stepThresholds.size()) + return m_stepThresholds[m_currentStepIndex].decreaseThreshold; + return 0.0; + } + return m_stableLux - std::max(m_minimumHysteresisLux, m_stableLux * m_darkenHysteresisRatio); +} + +bool ContinuousAmbientLightPolicy::latestSampleSupportsBrightening() const +{ + if (m_buffer.size() == 0 || (m_useStepsMode && m_currentStepIndex + 1 >= m_steps.size())) { + return false; + } + return m_buffer.luxAt(m_buffer.size() - 1) >= brighteningThresholdLux(); +} + +bool ContinuousAmbientLightPolicy::latestSampleSupportsDarkening() const +{ + if (m_buffer.size() == 0 || (m_useStepsMode && m_currentStepIndex == 0)) { + return false; + } + return m_buffer.luxAt(m_buffer.size() - 1) <= darkeningThresholdLux(); +} + +double ContinuousAmbientLightPolicy::nextBrighteningTransitionMs(double nowMs) const +{ + if (!latestSampleSupportsBrightening()) + return nowMs + m_brightenDebounceMs; + + double earliest = nowMs; + for (size_t i = m_buffer.size(); i-- > 0;) { + if (m_buffer.luxAt(i) < brighteningThresholdLux()) + break; + earliest = m_buffer.timeAt(i); + } + return earliest + m_brightenDebounceMs; +} + +double ContinuousAmbientLightPolicy::nextDarkeningTransitionMs(double nowMs) const +{ + if (!latestSampleSupportsDarkening()) + return nowMs + m_darkenDebounceMs; + + double earliest = nowMs; + for (size_t i = m_buffer.size(); i-- > 0;) { + if (m_buffer.luxAt(i) > darkeningThresholdLux()) + break; + earliest = m_buffer.timeAt(i); + } + return earliest + m_darkenDebounceMs; +} + +double ContinuousAmbientLightPolicy::calculateWeightedAmbientLux(double nowMs, + double horizonMs) const +{ + const size_t n = m_buffer.size(); + if (n == 0) + return 0.0; + + const double horizonStart = nowMs - horizonMs; + size_t start = 0; + for (size_t i = 0; i + 1 < n; ++i) { + if (m_buffer.timeAt(i + 1) <= horizonStart) + start = i + 1; + else + break; + } + + double sum = 0.0; + double totalWeight = 0.0; + double endDelta = kPredictionTimeMs; + for (size_t i = n; i > start; --i) { + const size_t index = i - 1; + double eventTime = m_buffer.timeAt(index); + if (index == start && eventTime < horizonStart) + eventTime = horizonStart; + const double startDelta = eventTime - nowMs; + const double weight = weightIntegral(endDelta) - weightIntegral(startDelta); + sum += m_buffer.luxAt(index) * weight; + totalWeight += weight; + endDelta = startDelta; + } + return totalWeight > 0.0 ? sum / totalWeight : m_buffer.luxAt(n - 1); +} + +double ContinuousAmbientLightPolicy::weightIntegral(double x) const +{ + return x * (x * 0.5 + m_weightingIntercept); +} + +double ContinuousAmbientLightPolicy::mapLuxToBrightness(double lux) const +{ + if (m_useStepsMode && !m_steps.empty()) + return m_steps[m_currentStepIndex].brightness; + return std::round(std::clamp(m_curve.map(lux), 0.0, 1.0) * 100.0) / 100.0; +} + +bool ContinuousAmbientLightPolicy::isValidLuxSteps(const std::vector &steps) +{ + if (steps.size() < 1) + return false; + for (size_t i = 0; i < steps.size(); ++i) { + const auto &s = steps[i]; + if (!std::isfinite(s.lux) || !std::isfinite(s.brightness) || s.lux < 0.0 + || s.brightness < 0.0 || s.brightness > 1.0) + return false; + if (i > 0 && (s.lux <= steps[i - 1].lux || s.brightness < steps[i - 1].brightness)) + return false; + } + return true; +} + +void ContinuousAmbientLightPolicy::computeStepThresholds() +{ + const size_t n = m_steps.size(); + m_stepThresholds.resize(n); + const double ratio = m_stepHysteresisRatio; + + for (size_t i = 0; i < n; ++i) { + if (i < n - 1) { + const double gap = m_steps[i + 1].lux - m_steps[i].lux; + m_stepThresholds[i].increaseThreshold = m_steps[i].lux + ratio * gap; + } else { + m_stepThresholds[i].increaseThreshold = std::numeric_limits::infinity(); + } + if (i > 0) { + const double gap = m_steps[i].lux - m_steps[i - 1].lux; + m_stepThresholds[i].decreaseThreshold = m_steps[i].lux - ratio * gap; + } else { + m_stepThresholds[i].decreaseThreshold = 0.0; + } + } +} + +size_t ContinuousAmbientLightPolicy::findStepForLux(double lux) const +{ + size_t index = 0; + for (size_t i = 1; i < m_steps.size(); ++i) { + const double neutralBoundary = + m_steps[i - 1].lux + 0.5 * (m_steps[i].lux - m_steps[i - 1].lux); + if (lux >= neutralBoundary) + index = i; + else + break; + } + return index; +} + +size_t ContinuousAmbientLightPolicy::findTargetStepForBrightening(double lux) const +{ + size_t index = m_currentStepIndex; + while (index + 1 < m_steps.size() && lux >= m_stepThresholds[index].increaseThreshold) + ++index; + return index; +} + +size_t ContinuousAmbientLightPolicy::findTargetStepForDarkening(double lux) const +{ + size_t index = m_currentStepIndex; + while (index > 0 && lux <= m_stepThresholds[index].decreaseThreshold) + --index; + return index; +} + +} // namespace dde::ambient_brightness diff --git a/src/plugin-qt/ambient-brightness/continuous/continuousambientlightpolicy.h b/src/plugin-qt/ambient-brightness/continuous/continuousambientlightpolicy.h new file mode 100644 index 00000000..8fbaf583 --- /dev/null +++ b/src/plugin-qt/ambient-brightness/continuous/continuousambientlightpolicy.h @@ -0,0 +1,140 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include "../ambientbrightnesspolicy.h" +#include "brightnesscurve.h" + +#include +#include +#include +#include + +namespace dde::ambient_brightness { + +/// 档位模式的 lux-亮度映射点。 +struct LuxStep +{ + double lux = 0.0; + double brightness = 0.0; +}; + +struct ContinuousPolicyConfig +{ + double maxSensorLux = 100000.0; + double ambientLightHorizonMs = 10000.0; + double fastLightHorizonMs = 1000.0; + double weightingIntercept = 10000.0; + + double brightenHysteresisRatio = 0.15; + double darkenHysteresisRatio = 0.15; + double minimumHysteresisLux = 5.0; + + double brightenDebounceMs = 2000.0; + double darkenDebounceMs = 2000.0; + + double minimumRecommendationDelta = 0.02; + + bool useWeightedWindows = false; + + // 映射模式:steps = 档位输出,curve = log1p 插值 + bool useStepsMode = true; + + // 档位模式参数。为空时由 curve 自动生成,保证内置回退配置始终可用。 + std::vector luxSteps; + double stepHysteresisRatio = 0.6; + + std::vector curve = { { 20.0, 0.20 }, { 120.0, 0.40 }, { 220.0, 0.60 }, + { 320.0, 0.80 }, { 650.0, 0.90 }, { 2000.0, 1.00 } }; +}; + +/// AOSP AutomaticBrightnessController 风格的连续策略。 +/// 使用时间加权 fast/slow ambient lux、亮暗滞回和最早连续越界时间 debounce。 +class ContinuousAmbientLightPolicy final : public AmbientBrightnessPolicy +{ +public: + explicit ContinuousAmbientLightPolicy(const ContinuousPolicyConfig &config); + + std::optional update(const SensorSample &sample) override; + std::optional tick(double monotonicTimestampMs) override; + void reset() override; + bool setConfig(const ContinuousPolicyConfig &config); + double nextEvaluationDelayMs() const override; + + static bool isValidConfig(const ContinuousPolicyConfig &config); + static bool isValidLuxSteps(const std::vector &steps); + +private: + struct AmbientLightRingBuffer + { + explicit AmbientLightRingBuffer(size_t capacity); + + void clear(); + size_t size() const; + double luxAt(size_t index) const; + double timeAt(size_t index) const; + void push(double timeMs, double lux); + void prune(double minTimeMs); + + size_t m_capacity = 1; + size_t m_count = 0; + size_t m_start = 0; + std::vector m_lux; + std::vector m_time; + }; + + std::optional evaluate(double monotonicTimestampMs); + double calculateWeightedAmbientLux(double nowMs, double horizonMs) const; + double weightIntegral(double x) const; + double mapLuxToBrightness(double lux) const; + double brighteningThresholdLux() const; + double darkeningThresholdLux() const; + double nextBrighteningTransitionMs(double nowMs) const; + double nextDarkeningTransitionMs(double nowMs) const; + bool latestSampleSupportsBrightening() const; + bool latestSampleSupportsDarkening() const; + + // 档位模式相关 + struct StepThresholds + { + double increaseThreshold = std::numeric_limits::infinity(); + double decreaseThreshold = 0.0; + }; + + size_t findStepForLux(double lux) const; + size_t findTargetStepForBrightening(double lux) const; + size_t findTargetStepForDarkening(double lux) const; + void computeStepThresholds(); + + std::vector m_steps; + std::vector m_stepThresholds; + double m_stepHysteresisRatio = 0.6; + size_t m_currentStepIndex = 0; + bool m_useStepsMode = true; + + BrightnessCurve m_curve; + AmbientLightRingBuffer m_buffer; + + double m_maxSensorLux = 0.0; + double m_ambientLightHorizonMs = 0.0; + double m_fastLightHorizonMs = 0.0; + double m_weightingIntercept = 1.0; + double m_brightenHysteresisRatio = 0.0; + double m_darkenHysteresisRatio = 0.0; + double m_minimumHysteresisLux = 0.0; + double m_brightenDebounceMs = 0.0; + double m_darkenDebounceMs = 0.0; + double m_minimumRecommendationDelta = 0.0; + bool m_useWeightedWindows = false; + + bool m_initialized = false; + double m_lastTimestampMs = -1.0; + double m_lastEvaluationTimestampMs = -1.0; + double m_fastAmbientLux = 0.0; + double m_slowAmbientLux = 0.0; + double m_stableLux = 0.0; + std::optional m_lastPublishedBrightness; +}; + +} // namespace dde::ambient_brightness diff --git a/src/plugin-qt/ambient-brightness/misc/ambient-brightness.service b/src/plugin-qt/ambient-brightness/misc/ambient-brightness.service new file mode 100644 index 00000000..62ec3576 --- /dev/null +++ b/src/plugin-qt/ambient-brightness/misc/ambient-brightness.service @@ -0,0 +1,4 @@ +[D-BUS Service] +Name=org.deepin.dde.AmbientBrightness1 +Exec=/usr/bin/deepin-service-manager -n org.deepin.dde.AmbientBrightness1 +SystemdService=org.deepin.dde.AmbientBrightness1.service diff --git a/src/plugin-qt/ambient-brightness/misc/plugin-ambient-brightness.json b/src/plugin-qt/ambient-brightness/misc/plugin-ambient-brightness.json new file mode 100644 index 00000000..de17edca --- /dev/null +++ b/src/plugin-qt/ambient-brightness/misc/plugin-ambient-brightness.json @@ -0,0 +1,12 @@ +{ + "name": "org.deepin.dde.AmbientBrightness1", + "libPath": "libplugin-ambient-brightness.so", + "group": "dde", + "startType": "Resident", + "pluginType": "qt", + "policy": [ + { + "path": "/org/deepin/dde/AmbientBrightness1" + } + ] +} diff --git a/src/plugin-qt/ambient-brightness/plugin.cpp b/src/plugin-qt/ambient-brightness/plugin.cpp new file mode 100644 index 00000000..c73724d0 --- /dev/null +++ b/src/plugin-qt/ambient-brightness/plugin.cpp @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "ambientbrightnessservice.h" + +#include + +using dde::ambient_brightness::AmbientBrightnessService; + +static AmbientBrightnessService *g_service = nullptr; + +extern "C" int DSMRegister(const char *, void *data) +{ + auto *connection = reinterpret_cast(data); + if (!connection) + return -1; + // 防御重复注册:先释放可能存在的旧实例,避免覆盖全局指针时泄漏。 + delete g_service; + g_service = nullptr; + g_service = new AmbientBrightnessService(*connection); + if (!g_service->initialize()) { + delete g_service; + g_service = nullptr; + return -1; + } + return 0; +} + +extern "C" int DSMUnRegister(const char *, void *) +{ + delete g_service; + g_service = nullptr; + return 0; +} diff --git a/src/plugin-qt/ambient-brightness/tests/CMakeLists.txt b/src/plugin-qt/ambient-brightness/tests/CMakeLists.txt new file mode 100644 index 00000000..86a0097f --- /dev/null +++ b/src/plugin-qt/ambient-brightness/tests/CMakeLists.txt @@ -0,0 +1,25 @@ +# SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +# SPDX-License-Identifier: LGPL-3.0-or-later + +find_package(Qt6 REQUIRED COMPONENTS Test) + +add_executable(tst-ambientbrightnesspolicy + tst_ambientbrightnesspolicy.cpp + ../ambientbrightnessservice.cpp + ../ambientbrightnesspolicyfactory.cpp + ../ambientbrightnessmodel.cpp + ../ambientbrightnesslogging.cpp + ../brightnesscurve.cpp + ../continuous/continuousambientlightpolicy.cpp +) + +target_include_directories(tst-ambientbrightnesspolicy PRIVATE ..) + +target_link_libraries(tst-ambientbrightnesspolicy PRIVATE + Qt6::Core + Qt6::DBus + Dtk6::Core + Qt6::Test +) + +add_test(NAME ambient-brightness-policy COMMAND tst-ambientbrightnesspolicy) diff --git a/src/plugin-qt/ambient-brightness/tests/tst_ambientbrightnesspolicy.cpp b/src/plugin-qt/ambient-brightness/tests/tst_ambientbrightnesspolicy.cpp new file mode 100644 index 00000000..1607a977 --- /dev/null +++ b/src/plugin-qt/ambient-brightness/tests/tst_ambientbrightnesspolicy.cpp @@ -0,0 +1,188 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "ambientbrightnessmodel.h" +#include "ambientbrightnessservice.h" +#include "continuous/continuousambientlightpolicy.h" + +#include + +using namespace dde::ambient_brightness; + +namespace { +ContinuousPolicyConfig stepConfig() +{ + ContinuousPolicyConfig config; + config.useStepsMode = true; + config.luxSteps = { { 0.0, 0.1 }, { 10.0, 0.2 }, { 30.0, 0.3 } }; + return config; +} +} // namespace + +class AmbientBrightnessPolicyTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void defaultConfigProducesRecommendation(); + void invalidConfigKeepsActiveConfiguration(); + void coldAndWarmStepClassificationAgree(); + void exactThresholdCompletesDebounce(); + void terminalStepsDoNotScheduleEvaluation(); + void overRangeLuxSaturates(); + void weightedWindowRetainsHighFrequencySamples(); + void modelRepublishesAfterReset(); + void lidOpenUsesDarkenDebounceDelay(); + void recoveryEventDoesNotShortenLidDelay(); +}; + +void AmbientBrightnessPolicyTest::initTestCase() +{ + QLoggingCategory::setFilterRules(QStringLiteral("dde.ambientbrightness=false")); +} + +void AmbientBrightnessPolicyTest::defaultConfigProducesRecommendation() +{ + const ContinuousPolicyConfig config; + QVERIFY(ContinuousAmbientLightPolicy::isValidConfig(config)); + + ContinuousAmbientLightPolicy policy(config); + const auto recommendation = policy.update({ 10.0, 0.0 }); + QVERIFY(recommendation.has_value()); + QCOMPARE(recommendation->brightness, 0.2); +} + +void AmbientBrightnessPolicyTest::invalidConfigKeepsActiveConfiguration() +{ + ContinuousAmbientLightPolicy policy(stepConfig()); + auto invalid = stepConfig(); + invalid.maxSensorLux = 0.0; + QVERIFY(!policy.setConfig(invalid)); + + const auto recommendation = policy.update({ 9.0, 0.0 }); + QVERIFY(recommendation.has_value()); + QCOMPARE(recommendation->brightness, 0.2); +} + +void AmbientBrightnessPolicyTest::coldAndWarmStepClassificationAgree() +{ + const auto config = stepConfig(); + ContinuousAmbientLightPolicy cold(config); + const auto coldRecommendation = cold.update({ 9.0, 0.0 }); + QVERIFY(coldRecommendation.has_value()); + + ContinuousAmbientLightPolicy warm(config); + QVERIFY(warm.update({ 0.0, 0.0 }).has_value()); + QVERIFY(!warm.update({ 9.0, 1000.0 }).has_value()); + const auto warmRecommendation = warm.tick(3000.0); + QVERIFY(warmRecommendation.has_value()); + + QCOMPARE(coldRecommendation->brightness, 0.2); + QCOMPARE(warmRecommendation->brightness, coldRecommendation->brightness); +} + +void AmbientBrightnessPolicyTest::exactThresholdCompletesDebounce() +{ + ContinuousAmbientLightPolicy policy(stepConfig()); + QVERIFY(policy.update({ 0.0, 0.0 }).has_value()); + QVERIFY(!policy.update({ 6.0, 1000.0 }).has_value()); + QCOMPARE(policy.nextEvaluationDelayMs(), 2000.0); + QVERIFY(!policy.tick(2999.0).has_value()); + + const auto recommendation = policy.tick(3000.0); + QVERIFY(recommendation.has_value()); + QCOMPARE(recommendation->brightness, 0.2); +} + +void AmbientBrightnessPolicyTest::terminalStepsDoNotScheduleEvaluation() +{ + ContinuousAmbientLightPolicy darkest(stepConfig()); + QVERIFY(darkest.update({ 0.0, 0.0 }).has_value()); + QCOMPARE(darkest.nextEvaluationDelayMs(), 0.0); + + ContinuousAmbientLightPolicy brightest(stepConfig()); + QVERIFY(brightest.update({ 100.0, 0.0 }).has_value()); + QCOMPARE(brightest.nextEvaluationDelayMs(), 0.0); +} + +void AmbientBrightnessPolicyTest::overRangeLuxSaturates() +{ + ContinuousAmbientLightPolicy policy(ContinuousPolicyConfig{}); + const auto recommendation = policy.update({ 200000.0, 0.0 }); + QVERIFY(recommendation.has_value()); + QCOMPARE(recommendation->rawLux, 100000.0); + QCOMPARE(recommendation->brightness, 1.0); +} + +void AmbientBrightnessPolicyTest::weightedWindowRetainsHighFrequencySamples() +{ + ContinuousPolicyConfig config; + config.useStepsMode = false; + config.useWeightedWindows = true; + config.ambientLightHorizonMs = 1000.0; + config.fastLightHorizonMs = 1000.0; + config.brightenHysteresisRatio = 0.0; + config.darkenHysteresisRatio = 0.0; + config.minimumHysteresisLux = 0.0; + config.brightenDebounceMs = 0.0; + config.darkenDebounceMs = 0.0; + config.minimumRecommendationDelta = 0.0; + + ContinuousAmbientLightPolicy policy(config); + std::optional recommendation; + for (int i = 0; i <= 100; ++i) { + const double lux = i < 50 ? 0.0 : 100.0; + recommendation = policy.update({ lux, static_cast(i * 10) }); + } + + QVERIFY(recommendation.has_value()); + QVERIFY(recommendation->slowLux > 50.0); + QVERIFY(recommendation->slowLux < 90.0); +} + +void AmbientBrightnessPolicyTest::modelRepublishesAfterReset() +{ + auto policy = std::make_unique(stepConfig()); + AmbientBrightnessModel model(std::move(policy)); + QSignalSpy spy(&model, &AmbientBrightnessModel::recommendedBrightnessChanged); + + model.waitForSample(); + model.submitSample(9.0, 0.0); + QCOMPARE(spy.count(), 1); + QCOMPARE(model.state(), QStringLiteral("Active")); + + model.setDisabled(); + model.waitForSample(); + model.submitSample(9.0, 1.0); + QCOMPARE(spy.count(), 2); + QCOMPARE(model.state(), QStringLiteral("Active")); +} + +void AmbientBrightnessPolicyTest::lidOpenUsesDarkenDebounceDelay() +{ + AmbientBrightnessService service(QDBusConnection::sessionBus()); + QVERIFY(QMetaObject::invokeMethod(&service, "onLidOpened", Qt::DirectConnection)); + + const auto timers = service.findChildren(); + QCOMPARE(timers.size(), 1); + QVERIFY(timers.constFirst()->isSingleShot()); + QVERIFY(timers.constFirst()->isActive()); + QCOMPARE(timers.constFirst()->interval(), 2000); +} + +void AmbientBrightnessPolicyTest::recoveryEventDoesNotShortenLidDelay() +{ + AmbientBrightnessService service(QDBusConnection::sessionBus()); + QVERIFY(QMetaObject::invokeMethod(&service, "onLidOpened", Qt::DirectConnection)); + QVERIFY(QMetaObject::invokeMethod(&service, "onSessionActiveChanged", Qt::DirectConnection, + Q_ARG(bool, true))); + + const auto timers = service.findChildren(); + QCOMPARE(timers.size(), 1); + QCOMPARE(timers.constFirst()->interval(), 2000); +} + +QTEST_GUILESS_MAIN(AmbientBrightnessPolicyTest) + +#include "tst_ambientbrightnesspolicy.moc" diff --git a/src/plugin-qt/power/powerconstants.h b/src/plugin-qt/power/powerconstants.h index 1d37b6d8..8d20684d 100644 --- a/src/plugin-qt/power/powerconstants.h +++ b/src/plugin-qt/power/powerconstants.h @@ -44,7 +44,6 @@ namespace PowerDConfig { 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"; diff --git a/src/plugin-qt/power/session/powermanager.cpp b/src/plugin-qt/power/session/powermanager.cpp index 53c693bb..4c922dfa 100644 --- a/src/plugin-qt/power/session/powermanager.cpp +++ b/src/plugin-qt/power/session/powermanager.cpp @@ -63,7 +63,6 @@ DEF_SETTER_PERSIST(bool, LowPowerNotifyEnable, lowPowerNotifyEnable, lowPowerNot 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) @@ -615,8 +614,6 @@ void PowerManager::initDConfig() [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, diff --git a/src/plugin-qt/power/session/powermanager.h b/src/plugin-qt/power/session/powermanager.h index 76ca740c..dda5eaf0 100644 --- a/src/plugin-qt/power/session/powermanager.h +++ b/src/plugin-qt/power/session/powermanager.h @@ -97,7 +97,6 @@ class PowerManager : public QObject 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) @@ -167,8 +166,6 @@ public Q_SLOTS: 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); @@ -257,7 +254,6 @@ private Q_SLOTS: void lowPowerNotifyThresholdChanged(); void lowPowerAutoSleepThresholdChanged(); void lowPowerActionChanged(); - void ambientLightAdjustBrightnessChanged(); void scheduledShutdownStateChanged(); void shutdownTimeChanged(); void shutdownRepetitionChanged(); @@ -318,7 +314,6 @@ private Q_SLOTS: 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; diff --git a/src/plugin-qt/shortcut/tools/dde-shortcut-tool/constant.h b/src/plugin-qt/shortcut/tools/dde-shortcut-tool/constant.h index ead4f7ab..f875f0a3 100644 --- a/src/plugin-qt/shortcut/tools/dde-shortcut-tool/constant.h +++ b/src/plugin-qt/shortcut/tools/dde-shortcut-tool/constant.h @@ -78,7 +78,6 @@ constexpr const char *KEY_LINE_POWER_PRESS_POWER_BTN_ACTION = "linePowerPressPow constexpr const char *KEY_SCREEN_BLACK_LOCK = "screenBlackLock"; constexpr const char *KEY_HIGH_PERFORMANCE_ENABLED = "highPerformanceEnabled"; constexpr const char *KEY_SLEEP_LOCK = "sleepLock"; -constexpr const char *KEY_AMBIENT_LIGHT_ADJUST_BRIGHTNESS = "ambientLightAdjustBrightness"; // Other keys constexpr const char *KEY_UPPER_LAYER_WLAN = "upperLayerWlan"; diff --git a/src/plugin-qt/shortcut/tools/dde-shortcut-tool/displaycontroller.cpp b/src/plugin-qt/shortcut/tools/dde-shortcut-tool/displaycontroller.cpp index 9f8b0fdd..e6d40fc6 100644 --- a/src/plugin-qt/shortcut/tools/dde-shortcut-tool/displaycontroller.cpp +++ b/src/plugin-qt/shortcut/tools/dde-shortcut-tool/displaycontroller.cpp @@ -11,12 +11,9 @@ #include #include -#include #include #include -DCORE_USE_NAMESPACE - DisplayController::DisplayController(QObject *parent) : BaseController(parent) , m_displayInterface(nullptr) @@ -112,31 +109,15 @@ bool DisplayController::changeBrightness(bool raised) return false; } - auto *powerConfig = DConfig::create("org.deepin.dde.daemon", "org.deepin.dde.daemon.power", "", this); - if (!powerConfig->isValid()) { - qWarning() << "daemon power config is not valid"; - return false; - } - - // Check if ambient light auto-adjustment is enabled, disable it first if so - QVariant autoAdjustValue = powerConfig->value("ambientLightAdjustBrightness"); - if (autoAdjustValue.toBool()) { - powerConfig->setValue("ambientLightAdjustBrightness", false); - qDebug() << "Disabled ambient light auto brightness adjustment"; - } - // Call Display1's ChangeBrightness method directly QDBusReply reply = m_displayInterface->call("ChangeBrightness", raised); if (!reply.isValid()) { qWarning() << "Failed to change brightness:" << reply.error().message(); return false; } - + qDebug() << "Changed brightness:" << (raised ? "up" : "down"); showOSD(raised ? "BrightnessUp" : "BrightnessDown"); - - powerConfig->deleteLater(); - return true; }