From 832f61659779d1fac95e9db7b07cba429131dda8 Mon Sep 17 00:00:00 2001 From: fuleyi Date: Wed, 29 Apr 2026 14:10:54 +0800 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=85=89?= =?UTF-8?q?=E6=84=9F=E4=BA=AE=E5=BA=A6=E8=B0=83=E8=8A=82=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增AutoBrightnessManager自动亮度管理器,基于环境光传感器数据自动调节屏幕亮度 新增SensorProxyClient传感器客户端,通过net.hadess.SensorProxy D-Bus接口获取环境光数据 新增BrightnessTransition亮度渐变管理器,支持可配置的渐变时长和步进间隔 在Manager中集成自动亮度和渐变管理器,支持启停、暂停恢复、配置变更等状态管理 新增DSettings配置项,支持敏感度、变化阈值、轮询间隔、手动调节策略等参数 新增导出方法和D-Bus接口,支持外部控制自动亮度启停和属性查询 Log: 新增光感亮度调节功能 --- display1/auto_brightness.go | 1076 +++++++++++++ display1/brightness.go | 47 +- display1/brightness_transition.go | 422 +++++ display1/display.go | 10 +- .../auto_backlight/auto_brightness_design.md | 1406 +++++++++++++++++ ...77\347\224\250\346\214\207\345\215\227.md" | 393 +++++ display1/exported_methods_auto.go | 5 + display1/main.go | 3 +- display1/manager.go | 174 +- display1/manager_ifc.go | 19 +- display1/manager_lid.go | 4 +- display1/sensor_proxy.go | 440 ++++++ .../org.deepin.Display.AutoBrightness.json | 69 + misc/dsg-configs/org.deepin.Display.json | 27 + 14 files changed, 4079 insertions(+), 16 deletions(-) create mode 100644 display1/auto_brightness.go create mode 100644 display1/brightness_transition.go create mode 100644 display1/docs/auto_backlight/auto_brightness_design.md create mode 100644 "display1/docs/auto_backlight/\350\207\252\345\212\250\344\272\256\345\272\246\345\212\237\350\203\275\344\275\277\347\224\250\346\214\207\345\215\227.md" create mode 100644 display1/sensor_proxy.go create mode 100644 misc/dsg-configs/org.deepin.Display.AutoBrightness.json diff --git a/display1/auto_brightness.go b/display1/auto_brightness.go new file mode 100644 index 000000000..11aacba2d --- /dev/null +++ b/display1/auto_brightness.go @@ -0,0 +1,1076 @@ +// SPDX-FileCopyrightText: 2022 - 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later +package display1 + +import ( + "errors" + "fmt" + "math" + "sync" + "time" + + "github.com/godbus/dbus/v5" + configManager "github.com/linuxdeepin/go-dbus-factory/org.desktopspec.ConfigManager" +) + +// AutoBrightnessConfig 自动亮度配置结构体 +type AutoBrightnessConfig struct { + Enabled bool `json:"enabled"` // 是否启用自动亮度 + Sensitivity float64 `json:"sensitivity"` // 敏感度 (0.1-3.0) + PollingInterval int `json:"polling_interval"` // 轮询间隔(秒) (1-60) + ChangeThreshold float64 `json:"change_threshold"` // 变化阈值 (1.0-50.0) + ManualOverrideDuration int `json:"manual_override_duration"` // 手动调节暂停时间(秒) (60-1800) + ManualAdjustDisablesAutoMode bool `json:"manual_adjust_disables_auto_mode"` // 手动调节是否禁用自动模式 + UseTransition bool `json:"use_transition"` // 自动调节时是否使用渐变效果 +} + +// DefaultAutoBrightnessConfig 默认自动亮度配置 +var DefaultAutoBrightnessConfig = AutoBrightnessConfig{ + Enabled: false, + Sensitivity: 0.5, + PollingInterval: 5, + ChangeThreshold: 20.0, + ManualOverrideDuration: 300, + ManualAdjustDisablesAutoMode: true, + UseTransition: true, +} + +// DSettings键名已在manager.go中定义 +// AutoBrightnessManager 自动亮度管理器 +type AutoBrightnessManager struct { + // 依赖注入 - 复用现有Manager + manager *Manager + // 独立组件 + sensorClient *SensorProxyClient + // 配置管理 + config AutoBrightnessConfig + configManager configManager.Manager + sysBus *dbus.Conn + // 状态管理 + enabled bool + supported bool + manualOverride time.Time + lastLightLevel int + lastAdjustTime time.Time + lastBrightness float64 + // 轮询控制 + pollInterval time.Duration + stopChan chan struct{} // 停止信号(通过写入来停止goroutine) + ticker *time.Ticker + pollingWg sync.WaitGroup // 等待轮询 goroutine 退出 + // 同步控制 + mutex sync.RWMutex + // 运行状态 + running bool + polling bool // 标记是否有轮询 goroutine 在运行 + // 系统调整标志(用于区分系统自动调整和用户手动调整) + systemAdjusting bool + // 重试和降级配置 + maxRetries int + retryInterval time.Duration + gracefulDegradation bool +} + +// NewAutoBrightnessManager 创建新的自动亮度管理器 +func NewAutoBrightnessManager() *AutoBrightnessManager { + return &AutoBrightnessManager{ + lastLightLevel: -1, // 初始化为无效值 + lastBrightness: -1, // 初始化为无效值 + maxRetries: 3, + retryInterval: time.Second * 2, + gracefulDegradation: true, + stopChan: make(chan struct{}), // 初始化时创建 + } +} + +// Initialize 初始化自动亮度管理器 +func (abm *AutoBrightnessManager) Initialize(manager *Manager) error { + abm.mutex.Lock() + defer abm.mutex.Unlock() + if manager == nil { + return errors.New("manager cannot be nil") + } + abm.manager = manager + abm.sysBus = manager.sysBus + logger.Info("[AutoBrightness] Initializing AutoBrightnessManager") + // 初始化配置管理器 + err := abm.initConfigManager() + if err != nil { + logger.Warning("[AutoBrightness] Failed to initialize config manager:", err) + } + // 检查是否有内置显示器 + builtinMonitor := abm.manager.getBuiltinMonitor() + if builtinMonitor == nil { + abm.supported = false + return fmt.Errorf("no builtin monitor found (total monitors: %d)", len(abm.manager.getConnectedMonitors())) + } + // 检查是否可以设置亮度 + canSet, _ := abm.manager.CanSetBrightness(builtinMonitor.Name) + if !canSet { + abm.supported = false + return fmt.Errorf("cannot set brightness for builtin monitor: %s", builtinMonitor.Name) + } + // 创建传感器客户端(但不连接) + abm.sensorClient = NewSensorProxyClient(abm.manager.sysBus) + // 检查传感器是否可用(不连接,只检查服务是否存在) + // 实际的连接和传感器声明将在Start()时进行 + err = abm.checkSensorAvailability() + if err != nil { + abm.supported = false + logger.Warning("[AutoBrightness] Sensor not available:", err) + return fmt.Errorf("sensor not available: %w", err) + } + abm.supported = true + // 设置服务状态变化回调(在连接前设置,这样Start时就可以使用) + abm.sensorClient.SetServiceChangeCallback(abm.onServiceChange) + // 加载配置 + err = abm.loadConfig() + if err != nil { + logger.Warning("[AutoBrightness] Failed to load config, using default:", err) + abm.config = DefaultAutoBrightnessConfig + } + abm.pollInterval = time.Duration(abm.config.PollingInterval) * time.Second + logger.Info("[AutoBrightness] AutoBrightnessManager initialized successfully") + return nil +} + +// Start 启动自动亮度功能 +func (abm *AutoBrightnessManager) Start() error { + abm.mutex.Lock() + defer abm.mutex.Unlock() + if !abm.supported { + return errors.New("auto brightness not supported") + } + abm.manualOverride = time.Time{} + if abm.running { + return nil + } + if !abm.config.Enabled { + return nil + } + // 连接传感器服务 + err := abm.sensorClient.Connect() + if err != nil { + logger.Warning("[AutoBrightness] Failed to connect to sensor proxy:", err) + return fmt.Errorf("failed to connect to sensor proxy: %w", err) + } + // 声明环境光传感器(带重试机制) + err = abm.claimLightWithRetry() + if err != nil { + logger.Warning("[AutoBrightness] Failed to claim light sensor after retries:", err) + abm.sensorClient.Disconnect() + return fmt.Errorf("failed to claim light sensor: %w", err) + } + abm.running = true + abm.enabled = true + // 不明确在哪种情况下被启用,因此需要判断当前状态 + // 不需要处理lidclose, 当前的实现中,如果处在lidclose时,因为光感值长期不变,不产生实际影响 + if abm.manager.powerSaving || !abm.manager.sessionActive { + logger.Warningf("[AutoBrightness] Started but powerSaving [%v], session active [%v]", abm.manager.powerSaving, abm.manager.sessionActive) + } else { + // 启动轮询定时器 + abm.startPolling() + } + logger.Info("[AutoBrightness] AutoBrightnessManager started successfully") + return nil +} + +// Stop 停止自动亮度功能 +func (abm *AutoBrightnessManager) Stop() error { + abm.mutex.Lock() + defer abm.mutex.Unlock() + if !abm.running { + return nil + } + // 停止轮询定时器 + abm.stopPolling() + abm.restoreSavedBrightness() + // 释放环境光传感器 + if abm.sensorClient != nil { + err := abm.sensorClient.ReleaseLight() + if err != nil { + logger.Warning("[AutoBrightness] Failed to release light sensor:", err) + } + // 断开传感器连接 + err = abm.sensorClient.Disconnect() + if err != nil { + logger.Warning("[AutoBrightness] Failed to disconnect from sensor proxy:", err) + } + } + abm.running = false + abm.enabled = false + logger.Info("[AutoBrightness] AutoBrightnessManager stopped successfully") + return nil +} + +// Cleanup 清理资源 +func (abm *AutoBrightnessManager) Cleanup() error { + abm.mutex.Lock() + defer abm.mutex.Unlock() + var cleanupErrors []error + // 停止运行 + if abm.running { + abm.stopPolling() + abm.running = false + } + // 断开传感器连接 + if abm.sensorClient != nil { + err := abm.sensorClient.Disconnect() + if err != nil { + logger.Warning("[AutoBrightness] Failed to disconnect sensor client:", err) + cleanupErrors = append(cleanupErrors, err) + } + abm.sensorClient = nil + } + // 注意:stopChan 和 transitionStop 不需要关闭,它们会一直存在直到对象被GC + logger.Info("[AutoBrightness] AutoBrightnessManager cleaned up") + if len(cleanupErrors) > 0 { + return cleanupErrors[0] // 返回第一个错误 + } + return nil +} + +// SetEnabled 设置启用状态 +func (abm *AutoBrightnessManager) SetEnabled(enabled bool) error { + abm.mutex.Lock() + if !abm.supported { + abm.mutex.Unlock() + return errors.New("[AutoBrightness] Not supported") + } + // 更新配置 + abm.config.Enabled = enabled + needStart := enabled && !abm.running + needStop := !enabled && abm.running + abm.mutex.Unlock() + // 保存配置 + err := abm.saveConfig() + if err != nil { + logger.Warning("[AutoBrightness] Failed to save config:", err) + } + // 在锁外执行启动/停止操作 + if needStart { + return abm.Start() + } else if needStop { + return abm.Stop() + } + return nil +} + +// setSystemAdjusting 设置系统调整标志--用于节能模式或类似功能 +func (abm *AutoBrightnessManager) setSystemAdjusting(adjusting bool) { + abm.mutex.Lock() + defer abm.mutex.Unlock() + abm.systemAdjusting = adjusting + logger.Debugf("[AutoBrightness] System adjusting flag set to: %v", adjusting) +} + +// OnManualBrightnessChange 处理手动亮度调节 +func (abm *AutoBrightnessManager) OnManualBrightnessChange() { + abm.mutex.Lock() + defer abm.mutex.Unlock() + // 如果是系统自动调整(如节能模式),忽略此次调用 + if abm.systemAdjusting { + logger.Debug("[AutoBrightness] Ignoring brightness change from system adjustment") + return + } + if !abm.running || !abm.polling { + logger.Info("[AutoBrightness] Manual brightness change") + return + } + // 检查配置:手动调节是否禁用自动模式 + if abm.config.ManualAdjustDisablesAutoMode { + logger.Info("[AutoBrightness] Manual brightness change detected, disabling auto brightness mode") + // 禁用自动亮度功能 + abm.config.Enabled = false + // 异步保存配置并停止功能 + go func() { + err := abm.saveConfig() + if err != nil { + logger.Warning("[AutoBrightness] Failed to save config:", err) + } + // 停止自动亮度功能 + err = abm.Stop() + if err != nil { + logger.Warning("[AutoBrightness] Failed to stop auto brightness:", err) + } + // 更新 Manager 属性 + if abm.manager != nil { + abm.manager.setPropAutoBrightnessEnabled(false) + } + }() + return + } + // 默认行为:临时暂停 + // 记录手动调节时间 + abm.manualOverride = time.Now() + logger.Infof("[AutoBrightness] Manual brightness change detected, pausing auto adjustment for %d seconds", abm.config.ManualOverrideDuration) + // 释放传感器,停止收集亮度数据 + if abm.sensorClient != nil && abm.sensorClient.IsClaimed() { + err := abm.sensorClient.ReleaseLight() + if err != nil { + logger.Warning("[AutoBrightness] Failed to release light sensor on manual override:", err) + } + } +} + +// isManualOverrideActive 检查手动调节是否仍在生效 +func (abm *AutoBrightnessManager) isManualOverrideActive() bool { + abm.mutex.RLock() + defer abm.mutex.RUnlock() + if abm.manualOverride.IsZero() { + return false + } + duration := time.Duration(abm.config.ManualOverrideDuration) * time.Second + return time.Since(abm.manualOverride) < duration +} + +// OnSystemSleep 处理系统休眠 +func (abm *AutoBrightnessManager) hold() { + abm.mutex.Lock() + defer abm.mutex.Unlock() + if !abm.running { + return + } + // 暂停轮询 + abm.stopPolling() +} + +// OnSystemWakeup 处理系统唤醒 +func (abm *AutoBrightnessManager) resume() { + abm.mutex.Lock() + defer abm.mutex.Unlock() + if !abm.running { + return + } + // 恢复轮询 + abm.startPolling() +} + +// OnConfigChanged 处理配置变更 +func (abm *AutoBrightnessManager) OnConfigChanged(config AutoBrightnessConfig) { + abm.mutex.Lock() + oldEnabled := abm.config.Enabled + oldSensitivity := abm.config.Sensitivity + abm.config = config + abm.pollInterval = time.Duration(config.PollingInterval) * time.Second + needStart := config.Enabled && !oldEnabled && !abm.running + needStop := !config.Enabled && oldEnabled && abm.running + needRestart := config.Enabled == oldEnabled && abm.running + // 检查是否需要立即应用新配置(仅敏感度变化需要立即调整) + // ChangeThreshold 只是判断门槛,不影响亮度计算,无需立即调整 + sensitivityChanged := oldSensitivity != config.Sensitivity + needImmediateAdjust := sensitivityChanged && abm.running && config.Enabled + // 如果需要重启轮询(配置变更但状态未变),在持有锁的情况下重启 + if needRestart { + abm.stopPolling() + abm.startPolling() + } + abm.mutex.Unlock() + // 在锁外执行状态变更操作 + if needStart { + abm.Start() + } else if needStop { + abm.Stop() + } + // 如果敏感度变化,立即触发一次亮度调整 + if needImmediateAdjust { + logger.Infof("[AutoBrightness] Sensitivity changed from %.2f to %.2f, triggering immediate adjustment", oldSensitivity, config.Sensitivity) + go abm.adjustBrightnessOnce() + } + logger.Infof("[AutoBrightness] Config updated: enabled=%v, sensitivity=%.2f, polling=%ds, threshold=%.1f", + config.Enabled, config.Sensitivity, config.PollingInterval, config.ChangeThreshold) +} + +// IsSupported 检查是否支持自动亮度 +func (abm *AutoBrightnessManager) IsSupported() bool { + abm.mutex.RLock() + defer abm.mutex.RUnlock() + return abm.supported +} + +// IsEnabled 检查是否启用 +func (abm *AutoBrightnessManager) IsEnabled() bool { + abm.mutex.RLock() + defer abm.mutex.RUnlock() + return abm.enabled +} + +// GetConfig 获取当前配置 +func (abm *AutoBrightnessManager) GetConfig() AutoBrightnessConfig { + abm.mutex.RLock() + defer abm.mutex.RUnlock() + return abm.config +} + +// GetStatus 获取当前状态信息 +func (abm *AutoBrightnessManager) GetStatus() map[string]interface{} { + abm.mutex.RLock() + defer abm.mutex.RUnlock() + status := map[string]interface{}{ + "supported": abm.supported, + "enabled": abm.enabled, + "running": abm.running, + "last_light_level": abm.lastLightLevel, + "last_brightness": abm.lastBrightness, + "manual_override": !abm.manualOverride.IsZero(), + "service_available": false, + "sensor_claimed": false, + } + if abm.sensorClient != nil { + status["service_available"] = abm.sensorClient.IsServiceAvailable() + status["sensor_claimed"] = abm.sensorClient.IsClaimed() + } + if !abm.manualOverride.IsZero() { + duration := time.Duration(abm.config.ManualOverrideDuration) * time.Second + remaining := duration - time.Since(abm.manualOverride) + if remaining > 0 { + status["manual_override_remaining"] = remaining.Seconds() + } + } + return status +} + +// GetManualOverrideRemaining 获取手动调节暂停剩余时间(秒) +func (abm *AutoBrightnessManager) GetManualOverrideRemaining() float64 { + abm.mutex.RLock() + defer abm.mutex.RUnlock() + if abm.manualOverride.IsZero() { + return 0 + } + duration := time.Duration(abm.config.ManualOverrideDuration) * time.Second + elapsed := time.Since(abm.manualOverride) + remaining := duration - elapsed + if remaining <= 0 { + return 0 + } + return remaining.Seconds() +} + +// claimLightWithRetry 带重试机制的传感器声明 +func (abm *AutoBrightnessManager) claimLightWithRetry() error { + var lastErr error + for i := 0; i < abm.maxRetries; i++ { + err := abm.sensorClient.ClaimLight() + if err == nil { + return nil + } + lastErr = err + if i < abm.maxRetries-1 { + time.Sleep(abm.retryInterval) + } + } + return lastErr +} + +// gracefulDegradeService 优雅降级服务 +// 注意:此函数假设调用者已经持有锁(如果需要的话) +func (abm *AutoBrightnessManager) gracefulDegradeService() { + // 停止轮询但保持基本功能 + abm.stopPolling() + // 释放传感器资源 + if abm.sensorClient != nil { + err := abm.sensorClient.ReleaseLight() + if err != nil { + logger.Warning("[AutoBrightness] Failed to release light sensor during degradation:", err) + } + } + abm.running = false + abm.supported = false +} + +// recoverService 尝试恢复服务 +func (abm *AutoBrightnessManager) recoverService() error { + // 重新初始化传感器连接 + if abm.sensorClient != nil { + err := abm.sensorClient.Connect() + if err != nil { + return fmt.Errorf("failed to reconnect sensor during recovery: %w", err) + } + hasLight, err := abm.sensorClient.HasAmbientLight() + if err != nil || !hasLight { + return fmt.Errorf("ambient light sensor not available during recovery: %w", err) + } + abm.supported = true + if abm.config.Enabled { + return abm.Start() + } + } + return nil +} + +// loadConfig 加载配置 +// 注意:此函数假设调用者已经持有锁(如果需要修改 abm.config) +func (abm *AutoBrightnessManager) loadConfig() error { + config, err := abm.getConfig() + if err != nil { + return err + } + abm.config = config + return nil +} + +// startPolling 启动轮询定时器 +// 注意:此函数假设调用者已经持有锁(用于访问 abm.ticker 和 abm.pollInterval) +func (abm *AutoBrightnessManager) startPolling() { + logger.Debug("auto brightness polling start") + // 如果已经在运行,先停止 + if abm.polling { + abm.stopPolling() + } + pollInterval := abm.pollInterval + // 创建新的 ticker + abm.ticker = time.NewTicker(pollInterval) + abm.polling = true + // 增加 WaitGroup 计数 + abm.pollingWg.Add(1) + go func() { + defer abm.pollingWg.Done() + // 立即执行一次 + abm.pollLightLevel() + // 轮询循环 + for { + select { + case <-abm.ticker.C: + abm.pollLightLevel() + case <-abm.stopChan: + logger.Debug("auto brightness polling goroutine received stop signal") + return + } + } + }() +} + +// stopPolling 停止轮询定时器 +// 注意:此函数假设调用者已经持有锁(用于访问 abm.ticker) +// 重要:此函数是幂等的,可以安全地多次调用 +func (abm *AutoBrightnessManager) stopPolling() { + logger.Debug("auto brightness polling stop") + if !abm.polling { + return + } + if abm.ticker != nil { + abm.ticker.Stop() + abm.ticker = nil + } + // 发送停止信号给 goroutine(非阻塞) + select { + case abm.stopChan <- struct{}{}: + logger.Debug("auto brightness stop signal sent") + default: + // channel 已满,说明已经有停止信号在等待,无需重复发送 + logger.Debug("auto brightness stop signal already pending") + } + abm.polling = false + // 释放锁后等待 goroutine 退出 + // 注意:这里需要临时释放锁,避免死锁 + abm.mutex.Unlock() + abm.pollingWg.Wait() + abm.mutex.Lock() + // 清空 stopChan 中可能残留的信号 + select { + case <-abm.stopChan: + default: + } +} + +// pollLightLevel 轮询环境光强度 +func (abm *AutoBrightnessManager) pollLightLevel() { + // 检查是否正在运行 + abm.mutex.Lock() + if !abm.running { + abm.mutex.Unlock() + return + } + // 检查是否在手动调节暂停期间 + inManualOverride := abm.isInManualOverride() + abm.mutex.Unlock() + if inManualOverride { + return + } + if abm.sensorClient == nil { + logger.Warning("[AutoBrightness] SensorClient is nil") + return + } + // 如果不在手动调节期,确保传感器已被claim + if !abm.sensorClient.IsClaimed() { + err := abm.sensorClient.ClaimLight() + if err != nil { + logger.Warning("[AutoBrightness] Failed to re-claim light sensor:", err) + return + } + logger.Info("[AutoBrightness] Re-claimed light sensor after manual override period") + } + // 从传感器获取光照强度 + lightLevel, err := abm.sensorClient.GetLightLevel() + if err != nil { + logger.Warningf("[AutoBrightness] Failed to get light level: %v", err) + return + } + abm.processLightChange(lightLevel) +} + +// onServiceChange 服务状态变化回调 +func (abm *AutoBrightnessManager) onServiceChange(available bool) { + abm.mutex.Lock() + defer abm.mutex.Unlock() + if available { + logger.Info("[AutoBrightness] SensorProxy service became available") + // 服务恢复,重新检查传感器 + if abm.sensorClient != nil { + hasLight, err := abm.sensorClient.HasAmbientLight() + if err == nil && hasLight { + abm.supported = true + if abm.config.Enabled && !abm.running { + abm.mutex.Unlock() + abm.Start() + abm.mutex.Lock() + } + } + } + } else { + logger.Warning("[AutoBrightness] SensorProxy service became unavailable") + // 服务不可用,停止功能 + if abm.running { + abm.stopPolling() + abm.running = false + abm.enabled = false + } + abm.supported = false + } + // 更新Manager属性 + if abm.manager != nil { + abm.manager.setPropAutoBrightnessSupported(abm.supported) + } +} + +// adjustBrightnessOnce 立即执行一次亮度调整(用于配置变化时) +func (abm *AutoBrightnessManager) adjustBrightnessOnce() { + abm.mutex.Lock() + if !abm.running || !abm.enabled { + abm.mutex.Unlock() + return + } + // 使用上一次的环境光值重新计算亮度 + lastLight := abm.lastLightLevel + // 如果没有历史光照数据,先读取一次 + if lastLight == 0 { + abm.mutex.Unlock() + abm.pollLightLevel() + return + } + // 临时重置状态,让 shouldAdjustBrightness 的检查通过 + savedLightLevel := abm.lastLightLevel + savedAdjustTime := abm.lastAdjustTime + abm.lastLightLevel = -1 // 设为负数,跳过环境光变化检查 + abm.lastAdjustTime = time.Time{} // 设为零值,跳过频率检查 + abm.mutex.Unlock() + // 使用历史光照值重新计算并应用亮度 + abm.processLightChange(lastLight) + // 恢复状态(如果调整成功,lastLightLevel 和 lastAdjustTime 会被更新) + abm.mutex.Lock() + if abm.lastLightLevel < 0 { + abm.lastLightLevel = savedLightLevel + } + if abm.lastAdjustTime.IsZero() { + abm.lastAdjustTime = savedAdjustTime + } + abm.mutex.Unlock() +} + +// processLightChange 处理环境光变化 +func (abm *AutoBrightnessManager) processLightChange(lightLevel int) { + abm.mutex.Lock() + if !abm.running { + abm.mutex.Unlock() + return + } + // 计算目标亮度 + targetBrightness := abm.calculateTargetBrightness(lightLevel) + // 检查是否应该调节亮度(包含所有阈值和频率控制逻辑) + if !abm.shouldAdjustBrightness(lightLevel, targetBrightness) { + abm.mutex.Unlock() + return + } + // 释放锁后再设置亮度(避免在渐变时持有锁) + abm.mutex.Unlock() + // 设置亮度(不重试,下次轮询会自动重试) + err := abm.setBrightness(targetBrightness) + if err != nil { + logger.Warningf("[AutoBrightness] Failed to set brightness (light=%d, target=%.1f%%): %v", + lightLevel, targetBrightness*100, err) + return + } + // 更新状态 + abm.mutex.Lock() + now := time.Now() + abm.lastLightLevel = lightLevel + abm.lastBrightness = targetBrightness + abm.lastAdjustTime = now + abm.mutex.Unlock() + logger.Infof("[AutoBrightness] Brightness adjusted: light=%d lux -> brightness=%.1f%%", lightLevel, targetBrightness*100) +} + +// isInManualOverride 检查是否在手动调节暂停期间 +// 注意:此函数需要读取 abm.manualOverride 和 abm.config,调用者应该持有至少读锁 +func (abm *AutoBrightnessManager) isInManualOverride() bool { + if abm.manualOverride.IsZero() { + return false + } + duration := time.Duration(abm.config.ManualOverrideDuration) * time.Second + return time.Since(abm.manualOverride) < duration +} + +// calculateTargetBrightness 计算目标亮度 +func (abm *AutoBrightnessManager) calculateTargetBrightness(lightLevel int) float64 { + // 应用敏感度调整 + adjustedLevel := float64(lightLevel) * abm.config.Sensitivity + // 简单线性映射 (后续可扩展为更复杂的曲线) + // 假设环境光范围 0-1024,映射到亮度 0.0-1.0,通过敏感度调整变化速率 + brightness := adjustedLevel / 1024.0 + // 约束到有效范围 + if brightness < 0.0 { + brightness = 0.0 + } else if brightness > 1.0 { + brightness = 1.0 + } + // 设置最小亮度,避免屏幕过暗 + minBrightness := 0.1 // 10% 最小亮度 + if brightness < minBrightness { + brightness = minBrightness + } + return brightness +} + +// shouldAdjustBrightness 检查是否应该调节亮度(变化阈值和频率控制) +// 注意:此函数需要读取多个字段,调用者应该持有至少读锁 +func (abm *AutoBrightnessManager) shouldAdjustBrightness(lightLevel int, targetBrightness float64) bool { + now := time.Now() + // 检查是否在手动调节暂停期间 + if abm.isInManualOverride() { + return false + } + // 检查环境光变化阈值 + if abm.lastLightLevel >= 0 { + lightChange := math.Abs(float64(lightLevel - abm.lastLightLevel)) + if lightChange < abm.config.ChangeThreshold { + return false + } + } + // 检查调节频率限制 + if !abm.lastAdjustTime.IsZero() { + timeSinceLastAdjust := now.Sub(abm.lastAdjustTime) + minInterval := time.Duration(abm.config.PollingInterval) * time.Second + if timeSinceLastAdjust < minInterval { + return false + } + } + // 检查亮度变化是否足够大 + if abm.lastBrightness >= 0 { + brightnessChange := math.Abs(targetBrightness - abm.lastBrightness) + minBrightnessChange := 0.05 // 5% 最小变化 + if brightnessChange < minBrightnessChange { + return false + } + } + return true +} + +// setBrightness 设置亮度(自动调节专用,不触发手动调节检测) +func (abm *AutoBrightnessManager) setBrightness(value float64) error { + // 使用内置显示器 + builtinMonitor := abm.manager.getBuiltinMonitor() + if builtinMonitor == nil { + return errors.New("no builtin monitor") + } + // 根据配置决定是否使用渐变 + abm.mutex.RLock() + useTransition := abm.config.UseTransition + abm.mutex.RUnlock() + var err error + if useTransition { + // 使用渐变效果(即使全局渐变功能关闭) + if abm.manager.brightnessTransition != nil { + // 使用 SetBrightness 强制启用渐变,忽略全局 enabled 标志 + err = abm.manager.brightnessTransition.SetBrightness(builtinMonitor.Name, value, true) + if err == nil { + // 渐变会在完成时自动同步属性,这里不需要再次同步 + return nil + } + // 如果渐变失败,回退到直接设置 + logger.Warning("[AutoBrightness] Transition failed, fallback to direct set:", err) + return err + } + } + // 不强制使用渐变 + err = abm.manager.setBrightness(builtinMonitor.Name, value) + if err != nil { + logger.Warning("[AutoBrightness] Failed to set brightness:", err) + } + // 不论是否设置失败,均应当向外同步亮度 + abm.manager.syncPropBrightness() + return err +} + +// restoreSavedBrightness 恢复配置中保存的亮度 +// 注意:此函数假设调用者已经持有锁 +func (abm *AutoBrightnessManager) restoreSavedBrightness() { + // 使用内置显示器 + builtinMonitor := abm.manager.getBuiltinMonitor() + if builtinMonitor == nil { + return + } + // 从配置中获取保存的亮度 + savedBrightness := abm.manager.getMonitorBrightness(builtinMonitor.Name) + if savedBrightness < 0 { + savedBrightness = 1.0 // 默认亮度 + } + // 恢复亮度(自动处理渐变和属性同步) + err := abm.manager.setBrightnessAndSync(builtinMonitor.Name, savedBrightness) + if err != nil { + logger.Warning("[AutoBrightness] Failed to restore brightness:", err) + return + } + logger.Infof("[AutoBrightness] Restored saved brightness: %.1f%%", savedBrightness*100) +} + +// checkSensorAvailability 检查传感器是否可用(不连接) +func (abm *AutoBrightnessManager) checkSensorAvailability() error { + // 临时连接以检查传感器 + err := abm.sensorClient.Connect() + if err != nil { + return fmt.Errorf("failed to connect to sensor proxy: %w", err) + } + // 检查是否有环境光传感器 + hasLight, err := abm.sensorClient.HasAmbientLight() + if err != nil { + abm.sensorClient.Disconnect() + return fmt.Errorf("failed to check ambient light sensor: %w", err) + } + if !hasLight { + abm.sensorClient.Disconnect() + return errors.New("no ambient light sensor available") + } + // 检查完成后立即断开连接 + // 实际使用时会在Start()中重新连接 + abm.sensorClient.Disconnect() + return nil +} + +// 配置管理方法 +// Validate 验证配置参数的有效性 +func (config *AutoBrightnessConfig) Validate() error { + if config.Sensitivity < 0.1 { + return errors.New("sensitivity too small") + } + if config.PollingInterval < 1 { + return errors.New("polling interval too small") + } + if config.ChangeThreshold < 1.0 { + return errors.New("change threshold too small") + } + if config.ManualOverrideDuration < 1 { + return errors.New("manual override duration to small") + } + return nil +} + +// initConfigManager 初始化配置管理器 +func (abm *AutoBrightnessManager) initConfigManager() error { + ds := configManager.NewConfigManager(abm.sysBus) + configPath, err := ds.AcquireManager(0, DSettingsAutoBrightnessAppID, DSettingsAutoBrightnessName, "") + if err != nil || configPath == "" { + return fmt.Errorf("failed to acquire config manager: %w", err) + } + abm.configManager, err = configManager.NewManager(abm.sysBus, configPath) + if err != nil { + return fmt.Errorf("failed to create config manager: %w", err) + } + // 监听配置变更 + abm.configManager.InitSignalExt(abm.manager.sysSigLoop, true) + _, err = abm.configManager.ConnectValueChanged(func(key string) { + abm.onConfigFileChanged() + }) + if err != nil { + return fmt.Errorf("failed to connect value changed: %w", err) + } + logger.Info("[AutoBrightness] Config manager initialized successfully") + return nil +} + +// onConfigFileChanged 配置文件变更回调 +func (abm *AutoBrightnessManager) onConfigFileChanged() { + config, err := abm.getConfig() + if err != nil { + logger.Warning("[AutoBrightness] Failed to reload config:", err) + return + } + logger.Info("[AutoBrightness] Config file changed, reloading configuration") + // 更新 Manager 的属性 + abm.manager.setPropAutoBrightnessEnabled(config.Enabled) + // 通知配置变更 + abm.OnConfigChanged(config) +} + +// getConfig 获取自动亮度配置 +func (abm *AutoBrightnessManager) getConfig() (AutoBrightnessConfig, error) { + if abm.configManager == nil { + logger.Warning("[AutoBrightness] Config manager is nil, using default config") + return DefaultAutoBrightnessConfig, nil + } + var config AutoBrightnessConfig + // Enabled + if val, err := abm.configManager.Value(0, DSettingsKeyABEnabled); err == nil { + if b, ok := val.Value().(bool); ok { + config.Enabled = b + } else { + config.Enabled = DefaultAutoBrightnessConfig.Enabled + } + } else { + config.Enabled = DefaultAutoBrightnessConfig.Enabled + } + // Sensitivity + if val, err := abm.configManager.Value(0, DSettingsKeyABSensitivity); err == nil { + switch v := val.Value().(type) { + case float64: + config.Sensitivity = v + case int64: + config.Sensitivity = float64(v) + default: + logger.Warningf("[AutoBrightness] Invalid type for sensitivity: %T", v) + config.Sensitivity = DefaultAutoBrightnessConfig.Sensitivity + } + } else { + logger.Warning("[AutoBrightness] Config convert faild, using default sensitivity") + config.Sensitivity = DefaultAutoBrightnessConfig.Sensitivity + } + // ChangeThreshold + if val, err := abm.configManager.Value(0, DSettingsKeyABChangeThreshold); err == nil { + switch v := val.Value().(type) { + case float64: + config.ChangeThreshold = v + case int64: + config.ChangeThreshold = float64(v) + default: + logger.Warningf("[AutoBrightness] Invalid type for changeThreshold: %T", v) + config.ChangeThreshold = DefaultAutoBrightnessConfig.ChangeThreshold + } + } else { + logger.Warning("[AutoBrightness] Config convert faild, using default changeThreshold") + config.ChangeThreshold = DefaultAutoBrightnessConfig.ChangeThreshold + } + // PollingInterval + if val, err := abm.configManager.Value(0, DSettingsKeyABPollingInterval); err == nil { + switch v := val.Value().(type) { + case int64: + config.PollingInterval = int(v) + case float64: + config.PollingInterval = int(v) + default: + config.PollingInterval = DefaultAutoBrightnessConfig.PollingInterval + } + } else { + logger.Warning("[AutoBrightness] Config convert faild, using default pollingInterval") + config.PollingInterval = DefaultAutoBrightnessConfig.PollingInterval + } + // ManualOverrideDuration + if val, err := abm.configManager.Value(0, DSettingsKeyABManualOverride); err == nil { + switch v := val.Value().(type) { + case int64: + config.ManualOverrideDuration = int(v) + case float64: + config.ManualOverrideDuration = int(v) + default: + config.ManualOverrideDuration = DefaultAutoBrightnessConfig.ManualOverrideDuration + } + } else { + logger.Warning("[AutoBrightness] Config convert faild, using default manualOverrideDuration") + config.ManualOverrideDuration = DefaultAutoBrightnessConfig.ManualOverrideDuration + } + // ManualAdjustDisablesAutoMode + if val, err := abm.configManager.Value(0, DSettingsKeyABManualAdjustDisablesAutoMode); err == nil { + if b, ok := val.Value().(bool); ok { + config.ManualAdjustDisablesAutoMode = b + } else { + config.ManualAdjustDisablesAutoMode = DefaultAutoBrightnessConfig.ManualAdjustDisablesAutoMode + } + } else { + logger.Warning("[AutoBrightness] Config convert faild, using default manualAdjustDisablesAutoMode") + config.ManualAdjustDisablesAutoMode = DefaultAutoBrightnessConfig.ManualAdjustDisablesAutoMode + } + // UseTransition + if val, err := abm.configManager.Value(0, DSettingsKeyABUseTransition); err == nil { + if b, ok := val.Value().(bool); ok { + config.UseTransition = b + } else { + config.UseTransition = DefaultAutoBrightnessConfig.UseTransition + } + } else { + logger.Warning("[AutoBrightness] Config convert faild, using default useTransition") + config.UseTransition = DefaultAutoBrightnessConfig.UseTransition + } + // 验证配置有效性 + logger.Debugf("[AutoBrightness] Apply config: %v", config) + err := config.Validate() + if err != nil { + logger.Warning("[AutoBrightness] Invalid config, using default:", err) + return DefaultAutoBrightnessConfig, nil + } + return config, nil +} + +// saveConfig 保存自动亮度配置 +func (abm *AutoBrightnessManager) saveConfig() error { + if abm.configManager == nil { + return errors.New("config manager is nil") + } + // 验证配置有效性 + err := abm.config.Validate() + if err != nil { + return err + } + // 保存各个配置项 + err = setGlobalDconfValue(DSettingsAutoBrightnessAppID, DSettingsAutoBrightnessName, "", + DSettingsKeyABEnabled, dbus.MakeVariant(abm.config.Enabled)) + if err != nil { + return err + } + err = setGlobalDconfValue(DSettingsAutoBrightnessAppID, DSettingsAutoBrightnessName, "", + DSettingsKeyABSensitivity, dbus.MakeVariant(abm.config.Sensitivity)) + if err != nil { + return err + } + err = setGlobalDconfValue(DSettingsAutoBrightnessAppID, DSettingsAutoBrightnessName, "", + DSettingsKeyABChangeThreshold, dbus.MakeVariant(abm.config.ChangeThreshold)) + if err != nil { + return err + } + err = setGlobalDconfValue(DSettingsAutoBrightnessAppID, DSettingsAutoBrightnessName, "", + DSettingsKeyABPollingInterval, dbus.MakeVariant(abm.config.PollingInterval)) + if err != nil { + return err + } + err = setGlobalDconfValue(DSettingsAutoBrightnessAppID, DSettingsAutoBrightnessName, "", + DSettingsKeyABManualOverride, dbus.MakeVariant(abm.config.ManualOverrideDuration)) + if err != nil { + return err + } + err = setGlobalDconfValue(DSettingsAutoBrightnessAppID, DSettingsAutoBrightnessName, "", + DSettingsKeyABManualAdjustDisablesAutoMode, dbus.MakeVariant(abm.config.ManualAdjustDisablesAutoMode)) + if err != nil { + return err + } + err = setGlobalDconfValue(DSettingsAutoBrightnessAppID, DSettingsAutoBrightnessName, "", + DSettingsKeyABUseTransition, dbus.MakeVariant(abm.config.UseTransition)) + if err != nil { + return err + } + return nil +} + +// https://gerrit.uniontech.com/plugins/gitiles/startdde/+/2295aa3ceaae739b0afb12c38b8c879f449631e7 +// https://gerrit.uniontech.com/plugins/gitiles/startdde/+/5ee59c1117d2d8dffc3e4d3e70009693e73ed576 +// https://gerrit.uniontech.com/plugins/gitiles/startdde/+/c99dee92d7bbec19a9140b77d1ffed8c9fa06106 +// https://gerrit.uniontech.com/plugins/gitiles/startdde/+/b2772f4cb6c7418a2f4e8307fcf4ae5ee6f81e2f +// https://gerrit.uniontech.com/plugins/gitiles/startdde/+/020e8d3486d195b6f40a543141c216e164176b01 +// https://gerrit.uniontech.com/plugins/gitiles/startdde/+/7a546becfbf065a22534d44264760d2e18e1e248 diff --git a/display1/brightness.go b/display1/brightness.go index 78c1dd8f4..62ff47b14 100644 --- a/display1/brightness.go +++ b/display1/brightness.go @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2018 - 2022 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2018 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -131,7 +131,7 @@ func (m *Manager) initBrightness() { m.Brightness = brightnessTable } -func (m *Manager) getBrightnessSetter() int { +func (m *Manager) getSetterConfig() int { // NOTE: 特殊处理龙芯笔记本亮度设置问题 blDir := "/sys/class/backlight/loongson" _, err := os.Stat(blDir) @@ -151,6 +151,24 @@ func (m *Manager) getBrightnessSetter() int { return int(v.Value().(int64)) } +// createBrightnessSetter 创建亮度设置闭包函数,用于亮度渐变管理器 +func (m *Manager) createBrightnessSetter(monitor *Monitor) func(float64) error { + temperature := m.getColorTemperatureValue() + if !isValidColorTempValue(int32(temperature)) { + temperature = defaultTemperatureManual + } + + isBuiltin := m.isBuiltinMonitor(monitor.Name) + setterConfig := m.getSetterConfig() + + logger.Debugf("Create brightness setter config %d for monitor %s, isBuiltin: %v, temperature: %v", setterConfig, monitor.Name, isBuiltin, temperature) + + // 返回一个只接收亮度值参数的闭包 + return func(brightnessValue float64) error { + return brightness.Set(brightnessValue, temperature, setterConfig, isBuiltin, monitor.ID, m.xConn) + } +} + // see also: gnome-desktop/libgnome-desktop/gnome-rr.c // // '_gnome_rr_output_name_is_builtin_display' @@ -181,15 +199,21 @@ func (m *Manager) isBuiltinMonitor(name string) bool { return false } -func (m *Manager) setMonitorBrightness(monitor *Monitor, brightnessValue float64, temperature int) error { - if !isValidColorTempValue(int32(temperature)) { - temperature = defaultTemperatureManual +func (m *Manager) setMonitorBrightness(monitor *Monitor, brightnessValue float64, forceTransition bool) error { + setter := m.createBrightnessSetter(monitor) + if setter == nil { + return fmt.Errorf("failed to create brightness setter for monitor %s", monitor.Name) } - isBuiltin := m.isBuiltinMonitor(monitor.Name) - err := brightness.Set(brightnessValue, temperature, m.getBrightnessSetter(), isBuiltin, - monitor.ID, m.xConn) - return err + logger.Debug("setMonitorBrightness reality value:", brightnessValue) + + if m.brightnessTransition != nil { + // TODO : 我们可以优化setter的使用来避免频繁create,但是需要处理屏幕插拔或其它变动信号 + m.brightnessTransition.SetBrightnessSetter(monitor.Name, setter) + return m.brightnessTransition.SetBrightness(monitor.Name, brightnessValue, forceTransition) + } else { + return setter(brightnessValue) + } } func (m *Manager) setBrightnessAux(fake bool, name string, value float64) error { @@ -205,12 +229,11 @@ func (m *Manager) setBrightnessAux(fake bool, name string, value float64) error value = math.Round(value*1000) / 1000 // 通过该方法,用来对亮度值(亮度值范围为0-1)四舍五入保留小数点后三位有效数字 if !fake && enabled { - temperature := m.getColorTemperatureValue() // 保持最小亮度,不能全黑 if value <= 0.1 { value = 0.1 } - err := m.setMonitorBrightness(monitor, value, temperature) + err := m.setMonitorBrightness(monitor, value, false) if err != nil { logger.Warningf("failed to set brightness for %s: %v", name, err) return err @@ -230,6 +253,8 @@ func (m *Manager) setBrightnessAndSync(name string, value float64) error { err := m.setBrightness(name, value) if err == nil { m.syncPropBrightness() + // 通知自动亮度管理器手动调节 + m.notifyManualBrightnessChange() } return err } diff --git a/display1/brightness_transition.go b/display1/brightness_transition.go new file mode 100644 index 000000000..fc9cd11ec --- /dev/null +++ b/display1/brightness_transition.go @@ -0,0 +1,422 @@ +// SPDX-FileCopyrightText: 2022 - 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later +package display1 + +import ( + "math" + "sync" + "time" + + "github.com/godbus/dbus/v5" + configManager "github.com/linuxdeepin/go-dbus-factory/org.desktopspec.ConfigManager" + "github.com/linuxdeepin/go-lib/dbusutil" +) + +// transitionState 单个显示器的渐变状态 +type transitionState struct { + mu sync.Mutex // 保护 running 和 currentValue + running bool // 是否正在执行渐变 + currentValue float64 // 当前渐变的实时亮度值 + stopCh chan struct{} // 停止信号 + wg sync.WaitGroup // 等待渐变完成 + setter func(float64) error +} + +const ( + defaultDuration = 4 + defaultStepInterval = 100 + durationMin = 1 + stepIntervalMin = 20 +) + +// BrightnessTransition 亮度渐变管理器 +type BrightnessTransition struct { + manager *Manager + // 渐变配置 + enabled bool + duration int // 从0%到100%的渐变时长(秒) + stepInterval int // 步进间隔(毫秒) + // 每个显示器的渐变状态 + states map[string]*transitionState + // 配置管理器 + cfgManager configManager.Manager + mu sync.Mutex // 保护配置和状态 +} + +// NewBrightnessTransition 创建亮度渐变管理器 +func NewBrightnessTransition(manager *Manager) *BrightnessTransition { + return &BrightnessTransition{ + manager: manager, + enabled: false, + duration: defaultDuration, + stepInterval: defaultStepInterval, + states: make(map[string]*transitionState), + } +} +func (bt *BrightnessTransition) SetBrightnessSetter(monitorName string, setter func(float64) error) { + // 初始化配置管理器 + state := bt.getState(monitorName) + state.setter = setter +} + +// getState 获取或创建显示器的渐变状态 +func (bt *BrightnessTransition) getState(monitorName string) *transitionState { + bt.mu.Lock() + defer bt.mu.Unlock() + state, exists := bt.states[monitorName] + if !exists { + state = &transitionState{ + stopCh: make(chan struct{}, 1), + } + bt.states[monitorName] = state + } + return state +} + +// SetEnabled 设置是否启用渐变 +func (bt *BrightnessTransition) SetEnabled(enabled bool) { + bt.mu.Lock() + defer bt.mu.Unlock() + bt.enabled = enabled +} + +// IsEnabled 获取是否启用渐变 +func (bt *BrightnessTransition) IsEnabled() bool { + bt.mu.Lock() + defer bt.mu.Unlock() + return bt.enabled +} + +// SetDuration 设置渐变时长(秒) +func (bt *BrightnessTransition) SetDuration(duration int) { + bt.mu.Lock() + defer bt.mu.Unlock() + if duration >= durationMin { + bt.duration = duration + } else { + logger.Warningf("[BrightnessTransition] duration must be >= %d, got %d", durationMin, duration) + } +} + +// SetStepInterval 设置步进间隔(毫秒) +func (bt *BrightnessTransition) SetStepInterval(interval int) { + bt.mu.Lock() + defer bt.mu.Unlock() + if interval >= stepIntervalMin { + bt.stepInterval = interval + } else { + logger.Warningf("[BrightnessTransition] stepInterval must be >= %d, got %d", stepIntervalMin, interval) + } +} + +// IsRunning 检查是否正在执行渐变 +func (bt *BrightnessTransition) IsRunning() bool { + bt.mu.Lock() + defer bt.mu.Unlock() + for _, state := range bt.states { + if state.running { + return true + } + } + return false +} + +// Stop 停止所有渐变(公开方法) +func (bt *BrightnessTransition) Stop() { + bt.mu.Lock() + states := make([]*transitionState, 0, len(bt.states)) + for _, state := range bt.states { + states = append(states, state) + } + bt.mu.Unlock() + for _, state := range states { + bt.stopState(state) + } +} + +// stopState 停止指定显示器的渐变 +func (bt *BrightnessTransition) stopState(state *transitionState) { + if state == nil { + return + } + state.mu.Lock() + isRunning := state.running + state.mu.Unlock() + if !isRunning { + return + } + // 发送停止信号(非阻塞) + select { + case state.stopCh <- struct{}{}: + default: + } + // 等待渐变完成 + state.wg.Wait() + // 清空可能残留的停止信号 + select { + case <-state.stopCh: + default: + } +} + +// SetBrightness 使用渐变效果设置亮度 +func (bt *BrightnessTransition) SetBrightness(monitorName string, targetValue float64, forceTransition bool) error { + return bt.setBrightnessInternal(monitorName, targetValue, forceTransition) +} + +// setBrightnessInternal 内部方法,实现渐变逻辑 +// forceTransition: 是否强制使用渐变(忽略 enabled 标志) +func (bt *BrightnessTransition) setBrightnessInternal(monitorName string, targetValue float64, forceTransition bool) error { + bt.mu.Lock() + enabled := bt.enabled + duration := bt.duration + stepInterval := bt.stepInterval + bt.mu.Unlock() + state := bt.getState(monitorName) + brightnessSetter := state.setter + if brightnessSetter == nil { + logger.Warningf("No setter for monitro %v brightness transition", monitorName) + return nil + } + // 如果未启用渐变且不是强制渐变,直接设置 + if !enabled && !forceTransition { + return brightnessSetter(targetValue) + } + // 获取当前亮度:如果正在渐变,使用实时值 + var currentBrightness float64 + var hasCurrentValue bool + state.mu.Lock() + if state.running { + currentBrightness = state.currentValue + hasCurrentValue = true + } + state.mu.Unlock() + // 如果没有实时值,从 Manager 获取 + if !hasCurrentValue { + currentBrightness = bt.manager.getMonitorBrightness(monitorName) + if currentBrightness < 0 { + currentBrightness = 0.5 // 如果无法获取,使用默认值 + } + } + // 计算亮度差值 + delta := targetValue - currentBrightness + if math.Abs(delta) < 0.001 { + return nil // 无需调整(差值太小) + } + // 停止该显示器之前的渐变(如果有) + bt.stopState(state) + // 计算渐变参数 + // duration 是 0-100% 的时间,实际时间按比例计算 + actualDuration := time.Duration(float64(duration)*math.Abs(delta)*1000) * time.Millisecond + // 计算步进参数 + stepIntervalDuration := time.Duration(stepInterval) * time.Millisecond + // 如果渐变时间太短(小于 2 个步进周期),直接设置 + // 这样可以避免过短的渐变(例如:默认配置下小于 200ms 的渐变) + minDuration := stepIntervalDuration * 2 + if actualDuration < minDuration { + // 变化太小,直接设置 + return brightnessSetter(targetValue) + } + totalSteps := int(actualDuration / stepIntervalDuration) + if totalSteps < 2 { + totalSteps = 2 + } + stepSize := delta / float64(totalSteps) + // 标记渐变开始 + state.mu.Lock() + state.running = true + state.currentValue = currentBrightness + state.mu.Unlock() + state.wg.Add(1) + // 启动渐变 goroutine + go func() { + defer state.wg.Done() + defer func() { + state.mu.Lock() + state.running = false + state.mu.Unlock() + }() + currentValue := currentBrightness + ticker := time.NewTicker(stepIntervalDuration) + defer ticker.Stop() + for i := 0; i < totalSteps; i++ { + // 先检查停止信号(优先级更高) + select { + case <-state.stopCh: + // 更新最终的实时值 + state.mu.Lock() + state.currentValue = currentValue + state.mu.Unlock() + logger.Debugf("[BrightnessTransition] %s transition stopped at %.1f%%", monitorName, currentValue*100) + // 即使被停止,也同步最后的亮度值 + return + default: + } + // 计算下一个亮度值 + currentValue += stepSize + // 确保不超出范围 + if (stepSize > 0 && currentValue > targetValue) || (stepSize < 0 && currentValue < targetValue) { + currentValue = targetValue + } + // 更新实时亮度值 + state.mu.Lock() + state.currentValue = currentValue + state.mu.Unlock() + // 设置亮度(只更新底层硬件,不同步属性) + err := brightnessSetter(currentValue) + if err != nil { + logger.Warningf("[BrightnessTransition] Failed to set brightness during transition: %v", err) + // 失败时也要同步一次,确保属性与实际状态一致 + return + } + // 如果已经到达目标值,同步属性并结束 + if currentValue == targetValue { + logger.Debugf("[BrightnessTransition] %s transition completed: %.1f%% -> %.1f%%", + monitorName, currentBrightness*100, targetValue*100) + return + } + // 如果不是最后一步,等待下一个步进时间或停止信号 + if i < totalSteps-1 { + select { + case <-state.stopCh: + // 更新最终的实时值(已经在上面更新过了) + state.mu.Lock() + state.currentValue = currentValue + state.mu.Unlock() + logger.Debugf("[BrightnessTransition] %s transition stopped at %.1f%%", monitorName, currentValue*100) + // 即使被停止,也同步最后的亮度值 + return + case <-ticker.C: + // 继续下一次循环 + } + } + } + // 确保最终值精确 + if currentValue != targetValue { + err := brightnessSetter(targetValue) + if err != nil { + logger.Warningf("[BrightnessTransition] Failed to set final brightness: %v", err) + } + state.mu.Lock() + state.currentValue = targetValue + state.mu.Unlock() + } + logger.Debugf("[BrightnessTransition] %s transition completed: %.1f%% -> %.1f%%", + monitorName, currentBrightness*100, targetValue*100) + }() + return nil +} + +// LoadConfig 从配置文件加载渐变配置 +func (bt *BrightnessTransition) LoadConfig(sysBus *dbus.Conn) error { + logger.Debug("[BrightnessTransition] Loading config") + // 获取配置管理器 - 使用 Display 配置文件,而不是 AutoBrightness 配置文件 + ds := configManager.NewConfigManager(sysBus) + configPath, err := ds.AcquireManager(0, DSettingsAppID, DSettingsDisplayName, "") + if err != nil || configPath == "" { + logger.Warning("[BrightnessTransition] Failed to acquire config manager:", err) + return err + } + cfgManager, err := configManager.NewManager(sysBus, configPath) + if err != nil { + logger.Warning("[BrightnessTransition] Failed to create config manager:", err) + return err + } + bt.mu.Lock() + bt.cfgManager = cfgManager + bt.mu.Unlock() + // 读取配置 + enabled := true + duration := 4 + stepInterval := 100 + if val, err := cfgManager.Value(0, DSettingsKeyABTransitionEnabled); err == nil { + enabled = val.Value().(bool) + } else { + logger.Warning("[BrightnessTransition] Failed to read transition-enabled:", err) + } + if val, err := cfgManager.Value(0, DSettingsKeyABTransitionDuration); err == nil { + switch v := val.Value().(type) { + case int64: + duration = int(v) + case float64: + duration = int(v) + } + } else { + logger.Warning("[BrightnessTransition] Failed to read transition-duration:", err) + } + if val, err := cfgManager.Value(0, DSettingsKeyABTransitionStepInterval); err == nil { + switch v := val.Value().(type) { + case int64: + stepInterval = int(v) + case float64: + stepInterval = int(v) + } + } else { + logger.Warning("[BrightnessTransition] Failed to read transition-step-interval:", err) + } + // 应用配置 + bt.SetEnabled(enabled) + bt.SetDuration(duration) + bt.SetStepInterval(stepInterval) + logger.Infof("[BrightnessTransition] Config loaded: enabled=%v, duration=%ds, stepInterval=%dms", + enabled, duration, stepInterval) + return nil +} + +// WatchConfigChanges 监听配置变化 +func (bt *BrightnessTransition) WatchConfigChanges(sysSigLoop *dbusutil.SignalLoop) error { + bt.mu.Lock() + cfgManager := bt.cfgManager + bt.mu.Unlock() + if cfgManager == nil { + return nil + } + cfgManager.InitSignalExt(sysSigLoop, true) + _, err := cfgManager.ConnectValueChanged(func(key string) { + switch key { + case DSettingsKeyABTransitionEnabled: + if val, err := cfgManager.Value(0, key); err == nil { + enabled := val.Value().(bool) + bt.SetEnabled(enabled) + logger.Info("[BrightnessTransition] Enabled changed:", enabled) + } else { + logger.Warning("[BrightnessTransition] Failed to read transition-enabled on change:", err) + } + case DSettingsKeyABTransitionDuration: + if val, err := cfgManager.Value(0, key); err == nil { + var duration int + switch v := val.Value().(type) { + case int64: + duration = int(v) + case float64: + duration = int(v) + } + bt.SetDuration(duration) + logger.Info("[BrightnessTransition] Duration changed:", duration) + } else { + logger.Warning("[BrightnessTransition] Failed to read transition-duration on change:", err) + } + case DSettingsKeyABTransitionStepInterval: + if val, err := cfgManager.Value(0, key); err == nil { + var interval int + switch v := val.Value().(type) { + case int64: + interval = int(v) + case float64: + interval = int(v) + } + bt.SetStepInterval(interval) + logger.Info("[BrightnessTransition] Step interval changed:", interval) + } else { + logger.Warning("[BrightnessTransition] Failed to read transition-step-interval on change:", err) + } + } + }) + if err != nil { + logger.Warning("[BrightnessTransition] Failed to watch config changes:", err) + return err + } + logger.Debug("[BrightnessTransition] Config change watcher started") + return nil +} diff --git a/display1/display.go b/display1/display.go index 798265bba..daa2eee87 100644 --- a/display1/display.go +++ b/display1/display.go @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2022 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -93,3 +93,11 @@ func StartPart2() error { func SetLogLevel(level log.Priority) { logger.SetLogLevel(level) } + +// Cleanup 清理display模块资源 +func Cleanup() { + if _dpy != nil { + _dpy.cleanupAutoBrightness() + logger.Info("Display module cleaned up") + } +} diff --git a/display1/docs/auto_backlight/auto_brightness_design.md b/display1/docs/auto_backlight/auto_brightness_design.md new file mode 100644 index 000000000..b0fef9632 --- /dev/null +++ b/display1/docs/auto_backlight/auto_brightness_design.md @@ -0,0 +1,1406 @@ +# 自动亮度调节功能概要设计文档 + +## 目录 + +1. [功能概述](#1-功能概述) + - 1.1 系统架构图 +2. [核心组件](#2-核心组件) + - 2.1 AutoBrightnessManager + - 2.2 SensorProxyClient + - 2.3 BrightnessTransition + - 2.4 配置管理 +3. [状态机](#3-状态机) + - 3.1 自动亮度状态转换 +4. [数据结构](#4-数据结构) + - 4.1 AutoBrightnessConfig + - 4.2 AutoBrightnessManager 状态字段 + - 4.3 BrightnessTransition 数据结构 +5. [核心流程](#5-核心流程) + - 5.1 初始化流程 + - 5.2 启动流程 + - 5.3 轮询流程 + - 5.4 停止流程 +6. [关键算法](#6-关键算法) + - 6.1 亮度计算算法 + - 6.2 调节判断逻辑 + - 6.3 渐变算法 + - 6.4 完整数据流图 +7. [亮度渐变机制](#7-亮度渐变机制) + - 7.1 渐变流程 + - 7.2 渐变控制 + - 7.3 渐变优化 + - 7.4 自动亮度与渐变集成 +8. [手动调节处理](#8-手动调节处理) + - 8.1 两种模式 + - 8.2 手动调节处理时序 + - 8.3 系统调整标志 +9. [配置管理](#9-配置管理) + - 9.1 DSettings 集成 + - 9.2 配置项 + - 9.3 动态更新 +10. [异常处理](#10-异常处理) + - 10.1 重试机制 + - 10.2 优雅降级 + - 10.3 服务恢复 +11. [并发控制](#11-并发控制) + - 11.1 锁策略 + - 11.2 Goroutine 管理 +12. [系统集成](#12-系统集成) + - 12.1 与 Display Manager 集成 + - 12.2 电源管理集成 + - 12.3 DBus 接口 +13. [依赖关系](#13-依赖关系) + - 13.1 外部依赖 + - 13.2 内部依赖 +14. [测试要点](#14-测试要点) + - 14.1 功能测试 + - 14.2 异常测试 + - 14.3 性能测试 +15. [未来扩展](#15-未来扩展) + - 15.1 算法优化 + - 15.2 功能增强 + - 15.3 性能优化 +16. [典型使用场景](#16-典型使用场景) + - 16.1 场景一:用户启用自动亮度 + - 16.2 场景二:环境光变化触发调节 + - 16.3 场景三:手动调节后的行为 + - 16.4 场景四:系统休眠与唤醒 +17. [注意事项](#17-注意事项) + +--- + +## 1. 功能概述 + +自动亮度调节功能通过环境光传感器自动调整显示器亮度,提升用户体验并节省电能。该功能集成在 StartDDE 的 display 模块中,支持灵活的配置和优雅的降级处理。 + +### 1.1 系统架构图 + +```mermaid +graph TB + subgraph "用户层" + User[用户] + ControlCenter[控制中心] + end + + subgraph "StartDDE Display 模块" + Manager[Display Manager] + ABM[AutoBrightnessManager] + BT[BrightnessTransition] + Backlight[Backlight 控制] + end + + subgraph "配置层" + DConf[DSettings/DConf] + end + + subgraph "系统服务" + SensorProxy[iio-sensor-proxy] + Hardware[环境光传感器硬件] + end + + subgraph "DBus 通信" + DBus[DBus 总线] + end + + User -->|手动调节| ControlCenter + ControlCenter -->|DBus 调用| Manager + Manager -->|管理| ABM + Manager -->|使用| BT + Manager -->|控制| Backlight + + ABM -->|读取配置| DConf + ABM -->|保存配置| DConf + BT -->|读取配置| DConf + + ABM -->|DBus 调用| SensorProxy + SensorProxy -->|读取| Hardware + + ABM -->|调用| BT + BT -->|设置亮度| Backlight + ABM -->|设置亮度| Backlight + + Manager -.->|属性通知| DBus + ControlCenter -.->|监听属性| DBus + + style ABM fill:#e1f5ff + style BT fill:#e1f5ff + style Manager fill:#fff4e1 +``` + +## 2. 核心组件 + +### 2.1 AutoBrightnessManager + +自动亮度管理器,负责整个功能的生命周期管理。 + +**主要职责:** +- 初始化和资源管理 +- 配置加载和持久化 +- 传感器数据采集和处理 +- 亮度计算和应用 +- 状态监控和异常处理 + +### 2.2 SensorProxyClient + +传感器代理客户端,封装与 iio-sensor-proxy 服务的交互。 + +**主要功能:** +- 连接/断开传感器服务 +- 声明/释放环境光传感器 +- 读取光照强度数据 +- 监听服务状态变化 + +### 2.3 BrightnessTransition + +亮度渐变管理器,提供平滑的亮度过渡效果。 + +**主要功能:** +- 渐变效果的启用/禁用控制 +- 渐变参数配置(时长、步进间隔) +- 多显示器独立渐变状态管理 +- 渐变过程的启动、停止和中断处理 +- 实时亮度值跟踪 + +### 2.4 配置管理 + +基于 DSettings (DConfig) 的配置系统,支持动态配置更新。 + +## 3. 状态机 + +### 3.1 自动亮度状态转换 + +```mermaid +stateDiagram-v2 + [*] --> 未初始化 + + 未初始化 --> 初始化中: Initialize() + + 初始化中 --> 不支持: 检查失败
(无显示器/传感器) + 初始化中 --> 已初始化未启用: 检查成功
Enabled=false + 初始化中 --> 已初始化已启用: 检查成功
Enabled=true + + 不支持 --> [*]: 功能不可用 + + 已初始化未启用 --> 启动中: SetEnabled(true) + 已初始化已启用 --> 启动中: Start() + + 启动中 --> 运行中: 传感器声明成功
轮询启动 + 启动中 --> 已初始化未启用: 启动失败 + + 运行中 --> 手动暂停: 用户手动调节
(临时暂停模式) + 运行中 --> 停止中: SetEnabled(false) + 运行中 --> 休眠暂停: hold() + 运行中 --> 服务异常: 传感器服务不可用 + + 手动暂停 --> 运行中: 超时恢复 + 手动暂停 --> 停止中: SetEnabled(false) + + 休眠暂停 --> 运行中: resume() + 休眠暂停 --> 停止中: SetEnabled(false) + + 服务异常 --> 不支持: 服务持续不可用 + 服务异常 --> 运行中: 服务恢复
自动重连 + + 停止中 --> 已初始化未启用: 停止完成 + + 已初始化未启用 --> 启动中: SetEnabled(true) + 已初始化已启用 --> 停止中: SetEnabled(false) + + note right of 运行中 + - 轮询传感器 + - 计算亮度 + - 应用调节 + end note + + note right of 手动暂停 + - 释放传感器 + - 停止调节 + - 计时等待 + end note + + note right of 休眠暂停 + - 停止轮询 + - 保持状态 + - 等待唤醒 + end note +``` + +## 4. 数据结构 + +### 4.1 AutoBrightnessConfig + +```go +type AutoBrightnessConfig struct { + Enabled bool // 是否启用 + Sensitivity float64 // 敏感度 (0.1-3.0) + PollingInterval int // 轮询间隔(秒) (1-60) + ChangeThreshold float64 // 变化阈值 (1.0-50.0) + ManualOverrideDuration int // 手动调节暂停时间(秒) (60-1800) + ManualAdjustDisablesAutoMode bool // 手动调节是否禁用自动模式 + UseTransition bool // 是否使用渐变效果 +} +``` + +### 4.2 AutoBrightnessManager 状态字段 + +- **依赖注入:** manager (复用 display.Manager) +- **独立组件:** sensorClient, configManager +- **配置状态:** config, enabled, supported +- **运行状态:** running, polling, systemAdjusting +- **历史数据:** lastLightLevel, lastBrightness, lastAdjustTime +- **手动控制:** manualOverride (时间戳) +- **轮询控制:** ticker, stopChan, pollingWg + +### 4.3 BrightnessTransition 数据结构 + +**配置字段:** +- `enabled`: 是否启用渐变 +- `duration`: 从 0% 到 100% 的渐变时长(秒) +- `stepInterval`: 步进间隔(毫秒) + +**状态管理:** +```go +type transitionState struct { + running bool // 是否正在执行渐变 + currentValue float64 // 当前渐变的实时亮度值 + stopCh chan struct{} // 停止信号通道 + wg sync.WaitGroup // 等待渐变完成 +} +``` + +**多显示器支持:** +- `states map[string]*transitionState`: 每个显示器独立的渐变状态 + +## 5. 核心流程 + +### 5.1 初始化流程 + +```mermaid +flowchart TD + A[Initialize] --> B{检查内置显示器} + B -->|不存在| C[返回错误: 无内置显示器] + B -->|存在| D{检查亮度调节支持} + D -->|不支持| E[返回错误: 不支持亮度调节] + D -->|支持| F[创建传感器客户端] + F --> G[检查传感器可用性] + G -->|不可用| H[返回错误: 传感器不可用] + G -->|可用| I[初始化配置管理器] + I --> J[加载配置] + J -->|失败| K[使用默认配置] + J -->|成功| L[设置服务状态回调] + K --> L + L --> M[标记为已支持] + M --> N[初始化完成] +``` + +### 5.2 启动流程 + +```mermaid +sequenceDiagram + participant User as 用户/系统 + participant ABM as AutoBrightnessManager + participant Sensor as SensorProxyClient + participant Poller as 轮询器 + + User->>ABM: Start() + ABM->>ABM: 检查支持状态 + alt 不支持 + ABM-->>User: 返回错误 + else 支持 + ABM->>ABM: 检查配置是否启用 + alt 未启用 + ABM-->>User: 返回 nil + else 已启用 + ABM->>Sensor: Connect() + Sensor-->>ABM: 连接成功 + ABM->>Sensor: ClaimLight() (带重试) + loop 最多3次 + Sensor->>Sensor: 尝试声明传感器 + alt 成功 + Sensor-->>ABM: 声明成功 + else 失败且未达重试上限 + Sensor->>Sensor: 等待2秒 + end + end + alt 声明失败 + ABM->>Sensor: Disconnect() + ABM-->>User: 返回错误 + else 声明成功 + ABM->>Poller: startPolling() + Poller->>Poller: 创建 ticker + Poller->>Poller: 启动 goroutine + Poller->>Poller: 立即执行一次采集 + ABM->>ABM: 更新运行状态 + ABM-->>User: 启动成功 + end + end + end +``` + +### 5.3 轮询流程 + +```mermaid +flowchart TD + A[定时器触发] --> B[pollLightLevel] + B --> C{检查运行状态} + C -->|未运行| D[返回] + C -->|运行中| E{检查手动调节暂停期} + E -->|暂停中| D + E -->|未暂停| F{传感器已声明?} + F -->|否| G[重新声明传感器] + G -->|失败| D + G -->|成功| H[获取光照强度] + F -->|是| H + H -->|失败| D + H -->|成功| I[processLightChange] + + I --> J[计算目标亮度] + J --> K{shouldAdjustBrightness} + + K --> L{手动调节暂停?} + L -->|是| M[不调节] + L -->|否| N{环境光变化 >= 阈值?} + N -->|否| M + N -->|是| O{距上次调节 >= 间隔?} + O -->|否| M + O -->|是| P{亮度变化 >= 5%?} + P -->|否| M + P -->|是| Q[应用亮度] + + Q --> R{使用渐变?} + R -->|是| S[BrightnessTransition.SetBrightnessForced] + R -->|否| T[setBrightnessRaw] + S --> U[更新历史状态] + T --> U + U --> V[记录光照/亮度/时间] + V --> D + M --> D +``` + +### 5.4 停止流程 + +```mermaid +sequenceDiagram + participant User as 用户/系统 + participant ABM as AutoBrightnessManager + participant Poller as 轮询器 + participant Sensor as SensorProxyClient + participant Display as 显示器 + + User->>ABM: Stop() + ABM->>ABM: 检查运行状态 + alt 未运行 + ABM-->>User: 返回 nil + else 运行中 + ABM->>Poller: stopPolling() + Poller->>Poller: 停止 ticker + Poller->>Poller: 发送停止信号 + Poller->>Poller: 等待 goroutine 退出 + Poller-->>ABM: 停止完成 + + ABM->>Display: restoreSavedBrightness() + Display->>Display: 获取保存的亮度 + Display->>Display: 应用亮度 + Display-->>ABM: 恢复完成 + + ABM->>Sensor: ReleaseLight() + Sensor-->>ABM: 释放完成 + + ABM->>Sensor: Disconnect() + Sensor-->>ABM: 断开完成 + + ABM->>ABM: 更新运行状态 + ABM-->>User: 停止成功 + end +``` + +## 6. 关键算法 + +### 6.1 亮度计算算法 + +``` +目标亮度 = min(max((光照强度 × 敏感度) / 255, 0.1), 1.0) +``` + +**说明:** +- 线性映射:光照强度 0-255 lux → 亮度 0.0-1.0 +- 敏感度调整:支持 0.1-3.0 倍率 +- 最小亮度保护:不低于 10%,避免屏幕过暗 + +### 6.2 调节判断逻辑 + +满足以下所有条件才执行调节: + +1. **不在手动调节暂停期** +2. **环境光变化超过阈值**:`|当前光照 - 上次光照| >= ChangeThreshold` +3. **距上次调节时间足够**:`当前时间 - 上次调节时间 >= PollingInterval` +4. **亮度变化足够大**:`|目标亮度 - 当前亮度| >= 5%` + +### 6.3 渐变算法 + +**基本原理:** +将亮度变化分解为多个小步进,在一定时间内逐步完成。 + +**参数计算:** +``` +实际渐变时长 = 配置时长 × |亮度差值| +步进次数 = 实际渐变时长 / 步进间隔 +每步变化量 = 亮度差值 / 步进次数 +``` + +**示例:** +- 配置时长:4 秒(0-100% 的时间) +- 步进间隔:100 毫秒 +- 亮度变化:30% → 80%(差值 50%) +- 实际时长:4 × 0.5 = 2 秒 +- 步进次数:2000ms / 100ms = 20 步 +- 每步变化:0.5 / 20 = 0.025 (2.5%) + +**优化策略:** +- 最小渐变时长:2 × 步进间隔(避免过短渐变) +- 变化太小时直接设置(< 0.1%) +- 支持中途停止和新渐变覆盖 + +### 6.4 完整数据流图 + +```mermaid +flowchart TB + Start([定时器触发]) --> A[读取环境光传感器] + A -->|光照强度 lux| B[应用敏感度系数] + B -->|调整后光照| C[线性映射到 0.0-1.0] + C -->|原始亮度| D[应用最小亮度保护] + D -->|目标亮度| E{检查调节条件} + + E -->|手动暂停中| End1([跳过]) + E -->|光照变化 < 阈值| End1 + E -->|时间间隔不足| End1 + E -->|亮度变化 < 5%| End1 + E -->|所有条件满足| F{使用渐变?} + + F -->|否| G[直接设置亮度] + F -->|是| H[计算渐变参数] + + H --> I[实际时长 = 配置时长 × 差值] + I --> J[步进次数 = 时长 / 间隔] + J --> K{时长 >= 最小值?} + + K -->|否| G + K -->|是| L[启动渐变 goroutine] + + L --> M[循环步进] + M --> N[更新实时亮度] + N --> O[调用硬件接口] + O --> P{到达目标?} + + P -->|否| Q[等待步进间隔] + Q --> M + P -->|是| R[同步 DBus 属性] + + G --> S[调用硬件接口] + S --> R + + R --> T[更新历史状态] + T --> End2([完成]) + + style A fill:#e1f5ff + style B fill:#e1f5ff + style C fill:#e1f5ff + style D fill:#e1f5ff + style H fill:#fff4e1 + style L fill:#fff4e1 + style M fill:#fff4e1 +``` + +## 7. 亮度渐变机制 + +### 7.1 渐变流程 + +```mermaid +flowchart TD + A[SetBrightness] --> B{检查启用状态} + B -->|未启用且非强制| C[直接设置亮度] + B -->|已启用或强制| D[获取当前亮度] + + D --> E{正在渐变?} + E -->|是| F[使用实时亮度值] + E -->|否| G[从 Manager 获取] + + F --> H[计算亮度差值] + G --> H + + H --> I{差值 < 0.001?} + I -->|是| J[无需调整,返回] + I -->|否| K[停止之前的渐变] + + K --> L[计算渐变参数] + L --> M[实际时长 = 配置时长 × |差值|] + M --> N[步进次数 = 实际时长 / 步进间隔] + N --> O[每步变化 = 差值 / 步进次数] + + O --> P{实际时长 < 最小时长?} + P -->|是| C + P -->|否| Q[标记渐变开始] + + Q --> R[启动 goroutine] + R --> S[返回不等待] + + R --> T[渐变循环] + T --> U{收到停止信号?} + U -->|是| V[更新实时值] + U -->|否| W[计算下一个亮度] + + W --> X[更新实时值] + X --> Y[setBrightnessRaw] + Y --> Z{到达目标值?} + Z -->|是| AA[同步 DBus 属性] + Z -->|否| AB{最后一步?} + + AB -->|否| AC[等待步进间隔或停止信号] + AC --> U + AB -->|是| AA + + V --> AA + AA --> AD[标记渐变结束] + AD --> AE[goroutine 退出] +``` + +### 7.2 渐变控制 + +```mermaid +sequenceDiagram + participant Caller as 调用者 + participant BT as BrightnessTransition + participant State as transitionState + participant Worker as 渐变 Goroutine + participant HW as 硬件 + + Note over Caller,HW: 场景1: 启动新渐变 + Caller->>BT: SetBrightness(monitor, 0.8) + BT->>BT: 获取当前亮度 0.3 + BT->>State: 检查是否正在渐变 + State-->>BT: running = false + BT->>BT: 计算参数 (差值0.5, 20步) + BT->>State: 标记 running = true + BT->>State: wg.Add(1) + BT->>Worker: 启动 goroutine + BT-->>Caller: 立即返回 + + loop 20次步进 + Worker->>State: 更新 currentValue + Worker->>HW: setBrightnessRaw + Worker->>Worker: 等待100ms + end + Worker->>HW: 同步 DBus 属性 + Worker->>State: 标记 running = false + Worker->>State: wg.Done() + + Note over Caller,HW: 场景2: 中断现有渐变 + Caller->>BT: SetBrightness(monitor, 0.5) + BT->>State: 检查是否正在渐变 + State-->>BT: running = true, currentValue = 0.6 + BT->>State: 发送停止信号 + State->>Worker: stopCh <- signal + Worker->>Worker: 收到停止信号 + Worker->>State: 更新最终 currentValue + Worker->>HW: 同步 DBus 属性 + Worker->>State: wg.Done() + BT->>State: wg.Wait() 等待退出 + BT->>BT: 使用 currentValue 0.6 作为起点 + BT->>Worker: 启动新渐变 (0.6 -> 0.5) + BT-->>Caller: 立即返回 +``` + +**启动渐变:** +- 标记 `running = true` +- 增加 WaitGroup 计数 +- 启动独立 goroutine 执行 + +**停止渐变:** +- 发送停止信号到 `stopCh` +- 等待 WaitGroup 完成 +- 清空残留信号 + +**中断处理:** +- 新渐变会先停止旧渐变 +- 使用实时亮度值作为起点 +- 确保平滑过渡 + +### 7.3 渐变优化 + +**性能优化:** +- 每个显示器独立渐变状态 +- 非阻塞启动(立即返回) +- 只在完成时同步一次属性 + +**用户体验优化:** +- 变化太小时直接设置(< 0.1%) +- 渐变时长按比例缩放 +- 最小渐变时长保护(200ms) + +**资源管理:** +- 使用 WaitGroup 确保 goroutine 正确退出 +- 停止信号使用缓冲通道避免阻塞 +- 清理残留信号防止误触发 + +### 7.4 自动亮度与渐变集成 + +**两种调用方式:** + +1. **SetBrightness(常规)** + - 检查全局 `enabled` 标志 + - 未启用时直接设置亮度 + +2. **SetBrightnessForced(强制)** + - 忽略全局 `enabled` 标志 + - 自动亮度专用,确保渐变生效 + +**配置独立性:** +- 自动亮度有独立的 `UseTransition` 配置 +- 可以在全局渐变关闭时仍使用渐变 +- 通过 `SetBrightnessForced` 实现 + +**属性同步:** +- 渐变过程中不同步属性(减少信号) +- 只在渐变完成或停止时同步一次 +- 失败时立即同步确保一致性 + +## 8. 手动调节处理 + +### 8.1 两种模式 + +```mermaid +stateDiagram-v2 + [*] --> 自动调节运行中 + + 自动调节运行中 --> 检查配置: 用户手动调节亮度 + + 检查配置 --> 临时暂停模式: ManualAdjustDisablesAutoMode = false + 检查配置 --> 完全禁用模式: ManualAdjustDisablesAutoMode = true + + 临时暂停模式 --> 记录暂停时间 + 记录暂停时间 --> 释放传感器 + 释放传感器 --> 暂停状态 + + 暂停状态 --> 检查超时: 每次轮询检查 + 检查超时 --> 暂停状态: 未超时 + 检查超时 --> 重新声明传感器: 超时 + 重新声明传感器 --> 自动调节运行中 + + 完全禁用模式 --> 更新配置Enabled=false + 更新配置Enabled=false --> 停止功能 + 停止功能 --> 已禁用状态 + + 已禁用状态 --> 自动调节运行中: 用户手动启用 +``` + +**模式一:临时暂停(默认)** +- 手动调节后暂停自动调节指定时间 +- 暂停期间释放传感器资源 +- 超时后自动恢复 + +**模式二:完全禁用** +- 手动调节后永久禁用自动亮度 +- 更新配置并停止功能 +- 需用户手动重新启用 + +### 8.2 手动调节处理时序 + +```mermaid +sequenceDiagram + participant User as 用户 + participant Manager as Display Manager + participant ABM as AutoBrightnessManager + participant Sensor as SensorProxyClient + participant Config as 配置系统 + + Note over User,Config: 场景1: 临时暂停模式 + User->>Manager: 手动调节亮度 + Manager->>Manager: setBrightness() + Manager->>ABM: OnManualBrightnessChange() + ABM->>ABM: 检查 systemAdjusting + alt systemAdjusting = true + ABM->>ABM: 忽略(系统调整) + else systemAdjusting = false + ABM->>ABM: 检查配置模式 + alt 临时暂停模式 + ABM->>ABM: 记录 manualOverride 时间 + ABM->>Sensor: ReleaseLight() + Note over ABM: 暂停 300 秒 + ABM->>ABM: 轮询时检查超时 + ABM->>Sensor: ClaimLight() (超时后) + ABM->>ABM: 恢复自动调节 + end + end + + Note over User,Config: 场景2: 完全禁用模式 + User->>Manager: 手动调节亮度 + Manager->>Manager: setBrightness() + Manager->>ABM: OnManualBrightnessChange() + ABM->>ABM: 检查配置模式 + alt 完全禁用模式 + ABM->>Config: 保存 Enabled = false + ABM->>ABM: Stop() + ABM->>Manager: setPropAutoBrightnessEnabled(false) + end +``` + +### 8.3 系统调整标志 + +通过 `systemAdjusting` 标志区分系统自动调整(如节能模式)和用户手动调整,避免误判。 + +```mermaid +sequenceDiagram + participant Power as 电源管理 + participant Manager as Display Manager + participant ABM as AutoBrightnessManager + + Note over Power,ABM: 系统调整场景(节能模式) + Power->>Manager: 节能模式降低亮度 + Manager->>ABM: setSystemAdjusting(true) + Manager->>Manager: setBrightness(0.3) + Manager->>ABM: OnManualBrightnessChange() + ABM->>ABM: 检查 systemAdjusting = true + ABM->>ABM: 忽略此次调用 + Manager->>ABM: setSystemAdjusting(false) + + Note over Power,ABM: 用户手动调整场景 + Power->>Manager: 用户拖动亮度滑块 + Manager->>Manager: setBrightness(0.8) + Manager->>ABM: OnManualBrightnessChange() + ABM->>ABM: 检查 systemAdjusting = false + ABM->>ABM: 执行暂停或禁用逻辑 +``` + +## 9. 配置管理 + +### 9.1 DSettings 集成 + +- **AppID:** `org.deepin.startdde` +- **配置名:** `org.deepin.startdde.display` +- **配置键前缀:** `autobrightness-*` + +### 9.2 配置项 + +**自动亮度配置:** + +| 配置键 | 类型 | 默认值 | 说明 | +|--------|------|--------|------| +| enabled | bool | false | 是否启用 | +| sensitivity | float64 | 0.5 | 敏感度 | +| polling-interval | int | 3 | 轮询间隔(秒) | +| change-threshold | float64 | 20.0 | 变化阈值 | +| manual-override-duration | int | 300 | 手动暂停时间(秒) | +| manual-adjust-disables-auto-mode | bool | true | 手动调节是否禁用 | +| use-transition | bool | true | 是否使用渐变 | + +**渐变效果配置:** + +| 配置键 | 类型 | 默认值 | 说明 | +|--------|------|--------|------| +| transition-enabled | bool | true | 全局渐变开关 | +| transition-duration | int | 4 | 0-100% 渐变时长(秒) | +| transition-step-interval | int | 100 | 步进间隔(毫秒) | + +### 9.3 动态更新 + +监听配置文件变化,自动重新加载并应用新配置。敏感度变化时立即触发一次亮度调整。 + +```mermaid +sequenceDiagram + participant User as 用户/控制中心 + participant DConf as DSettings/DConf + participant ABM as AutoBrightnessManager + participant BT as BrightnessTransition + participant Poller as 轮询器 + + Note over User,Poller: 自动亮度配置变更 + User->>DConf: 修改配置 (如 sensitivity) + DConf->>ABM: ValueChanged 信号 + ABM->>DConf: 读取新配置 + DConf-->>ABM: 返回配置值 + ABM->>ABM: OnConfigChanged() + + alt 启用状态变化 + ABM->>ABM: Start() 或 Stop() + else 轮询参数变化 + ABM->>Poller: stopPolling() + ABM->>Poller: startPolling() + else 敏感度变化 + ABM->>ABM: adjustBrightnessOnce() + Note over ABM: 使用新敏感度立即调整 + end + + Note over User,Poller: 渐变配置变更 + User->>DConf: 修改渐变配置 + DConf->>BT: ValueChanged 信号 + BT->>DConf: 读取新配置 + DConf-->>BT: 返回配置值 + + alt transition-enabled 变化 + BT->>BT: SetEnabled(newValue) + else transition-duration 变化 + BT->>BT: SetDuration(newValue) + else transition-step-interval 变化 + BT->>BT: SetStepInterval(newValue) + end + + Note over BT: 新配置立即生效
下次调节时使用 +``` + +## 10. 异常处理 + +### 10.1 重试机制 + +- 传感器声明失败:最多重试 3 次,间隔 2 秒 +- 亮度设置失败:下次轮询自动重试 + +### 10.2 优雅降级 + +- 传感器服务不可用:停止功能但不崩溃 +- 配置加载失败:使用默认配置 +- 内置显示器不存在:标记为不支持 + +### 10.3 服务恢复 + +监听 iio-sensor-proxy 服务状态,服务恢复后自动重新初始化。 + +```mermaid +sequenceDiagram + participant Sensor as iio-sensor-proxy + participant Client as SensorProxyClient + participant ABM as AutoBrightnessManager + participant Manager as Display Manager + + Note over Sensor,Manager: 服务异常场景 + Sensor->>Sensor: 服务崩溃/重启 + Sensor->>Client: NameOwnerChanged 信号 + Client->>Client: 检测到服务不可用 + Client->>ABM: onServiceChange(false) + ABM->>ABM: stopPolling() + ABM->>ABM: 标记 supported = false + ABM->>Manager: setPropAutoBrightnessSupported(false) + + Note over Sensor,Manager: 服务恢复场景 + Sensor->>Sensor: 服务恢复 + Sensor->>Client: NameOwnerChanged 信号 + Client->>Client: 检测到服务可用 + Client->>ABM: onServiceChange(true) + ABM->>Client: HasAmbientLight() + Client-->>ABM: true + ABM->>ABM: 标记 supported = true + ABM->>Manager: setPropAutoBrightnessSupported(true) + + alt 配置已启用 + ABM->>ABM: Start() + ABM->>Client: Connect() + ABM->>Client: ClaimLight() + ABM->>ABM: startPolling() + Note over ABM: 自动恢复功能 + end +``` + +## 11. 并发控制 + +### 11.1 锁策略 + +- 使用 `sync.RWMutex` 保护共享状态 +- 读多写少场景使用读锁 +- 避免在持有锁时执行耗时操作 + +```mermaid +graph TD + A[公共方法调用] --> B{需要修改状态?} + B -->|是| C[获取写锁 Lock] + B -->|否| D[获取读锁 RLock] + + C --> E{需要耗时操作?} + E -->|是| F[释放锁] + E -->|否| G[执行操作] + + F --> H[执行耗时操作
如 Start/Stop] + H --> I[必要时重新获取锁] + + G --> J[释放写锁 Unlock] + D --> K[读取状态] + K --> L[释放读锁 RUnlock] + + I --> J + J --> M[返回] + L --> M +``` + +### 11.2 Goroutine 管理 + +- 轮询 goroutine:通过 `stopChan` 和 `WaitGroup` 安全退出 +- 配置更新回调:异步执行,避免阻塞 +- 幂等停止:`stopPolling()` 可安全多次调用 + +```mermaid +sequenceDiagram + participant Main as 主线程 + participant Poller as 轮询 Goroutine + participant Worker as 渐变 Goroutine + + Note over Main,Worker: Goroutine 生命周期管理 + + Main->>Main: startPolling() + Main->>Main: wg.Add(1) + Main->>Poller: 启动 goroutine + Main->>Main: 返回(不等待) + + loop 轮询循环 + Poller->>Poller: 等待 ticker 或 stopChan + alt 收到 ticker + Poller->>Poller: pollLightLevel() + else 收到 stopChan + Poller->>Poller: wg.Done() + Poller->>Poller: 退出 + end + end + + Main->>Main: stopPolling() + Main->>Poller: stopChan <- signal + Main->>Main: 释放锁 + Main->>Main: wg.Wait() + Note over Main: 等待 goroutine 退出 + Main->>Main: 重新获取锁 + Main->>Main: 清空 stopChan + + Note over Main,Worker: 渐变 Goroutine 管理 + Main->>Main: SetBrightness() + Main->>Main: state.wg.Add(1) + Main->>Worker: 启动 goroutine + Main->>Main: 返回(不等待) + + Worker->>Worker: 执行渐变 + alt 完成 + Worker->>Worker: state.wg.Done() + else 被停止 + Worker->>Worker: state.wg.Done() + end + + Main->>Main: stopState() + Main->>Worker: stopCh <- signal + Main->>Main: state.wg.Wait() + Note over Main: 等待渐变完成 +``` + +## 12. 系统集成 + +### 12.1 与 Display Manager 集成 + +- 复用 Manager 的显示器管理能力 +- 复用 BrightnessTransition 渐变功能 +- 同步更新 DBus 属性 + +```mermaid +graph LR + ABM[AutoBrightnessManager] -->|依赖注入| Manager[Display Manager] + ABM -->|调用| BT[BrightnessTransition] + ABM -->|调用| getBuiltinMonitor + ABM -->|调用| canSetBrightness + ABM -->|调用| setBrightnessRaw + ABM -->|调用| syncPropBrightness + + Manager -->|创建| ABM + Manager -->|初始化| BT + Manager -->|暴露| DBusAPI[DBus API] + + BT -->|调用| setBrightnessRaw + BT -->|调用| syncPropBrightness +``` + +### 12.2 电源管理集成 + +- 支持系统休眠/唤醒事件 +- `hold()`: 休眠前暂停轮询 +- `resume()`: 唤醒后恢复轮询 + +```mermaid +sequenceDiagram + participant PM as 电源管理 + participant Manager as Display Manager + participant ABM as AutoBrightnessManager + participant Poller as 轮询器 + participant Display as 显示器 + + Note over PM,Display: 系统休眠场景 + PM->>Manager: PrepareForSleep(true) + Manager->>ABM: hold() + ABM->>ABM: 设置 systemAdjusting = true + ABM->>Poller: stopPolling() + Poller->>Poller: 停止 ticker + Poller->>Poller: 等待 goroutine 退出 + Note over ABM: 保持运行状态
但停止轮询 + + Note over PM,Display: 系统唤醒场景 + PM->>Manager: PrepareForSleep(false) + Manager->>ABM: resume() + ABM->>Poller: startPolling() + Poller->>Poller: 创建新 ticker + Poller->>Poller: 启动 goroutine + Poller->>Poller: 立即采集一次 + ABM->>ABM: 清除 systemAdjusting + Note over ABM: 恢复正常运行 + + Note over PM,Display: 亮度变化不触发手动调节检测 + ABM->>Display: 调整亮度 + Display->>Manager: 亮度变化通知 + Manager->>ABM: OnManualBrightnessChange() + ABM->>ABM: 检查 systemAdjusting + Note over ABM: systemAdjusting = true
忽略此次调用 +``` + +### 12.3 DBus 接口 + +通过 Manager 暴露以下属性和方法: +- `AutoBrightnessSupported` (只读) +- `AutoBrightnessEnabled` (读写) +- 配置相关的 Get/Set 方法 + +```mermaid +sequenceDiagram + participant App as 应用程序 + participant DBus as DBus + participant Manager as Display Manager + participant ABM as AutoBrightnessManager + + Note over App,ABM: 查询支持状态 + App->>DBus: Get AutoBrightnessSupported + DBus->>Manager: 读取属性 + Manager->>ABM: IsSupported() + ABM-->>Manager: true/false + Manager-->>DBus: 返回值 + DBus-->>App: true/false + + Note over App,ABM: 启用自动亮度 + App->>DBus: Set AutoBrightnessEnabled = true + DBus->>Manager: 设置属性 + Manager->>ABM: SetEnabled(true) + ABM->>ABM: Start() + ABM-->>Manager: 成功 + Manager->>DBus: PropertiesChanged 信号 + DBus->>App: 属性变化通知 + + Note over App,ABM: 查询状态信息 + App->>DBus: Call GetStatus() + DBus->>Manager: 调用方法 + Manager->>ABM: GetStatus() + ABM-->>Manager: 状态 map + Manager-->>DBus: 返回状态 + DBus-->>App: 状态信息 +``` + +## 13. 依赖关系 + +### 13.1 外部依赖 + +- **iio-sensor-proxy:** 提供环境光传感器数据 +- **DSettings/DConfig:** 配置管理 +- **DBus:** 进程间通信 + +### 13.2 内部依赖 + +- **display.Manager:** 显示器管理和亮度控制 +- **BrightnessTransition:** 亮度渐变效果 +- **backlight:** 底层亮度控制 + +## 14. 测试要点 + +### 14.1 功能测试 + +- 基本启停流程 +- 配置加载和保存 +- 亮度计算准确性 +- 手动调节处理 + +```mermaid +graph TD + subgraph "基本功能测试" + T1[初始化测试] --> T1A{检查支持状态} + T1A -->|支持| T1B[验证 supported = true] + T1A -->|不支持| T1C[验证 supported = false] + + T2[启动测试] --> T2A[调用 Start] + T2A --> T2B{检查传感器} + T2B -->|成功| T2C[验证 running = true] + T2B -->|失败| T2D[验证错误处理] + + T3[轮询测试] --> T3A[模拟光照变化] + T3A --> T3B[等待轮询周期] + T3B --> T3C[验证亮度调整] + + T4[停止测试] --> T4A[调用 Stop] + T4A --> T4B[验证资源释放] + T4B --> T4C[验证 running = false] + end + + subgraph "手动调节测试" + T5[临时暂停模式] --> T5A[手动调节亮度] + T5A --> T5B[验证暂停状态] + T5B --> T5C[等待超时] + T5C --> T5D[验证自动恢复] + + T6[完全禁用模式] --> T6A[修改配置] + T6A --> T6B[手动调节亮度] + T6B --> T6C[验证功能停止] + T6C --> T6D[验证配置更新] + end + + subgraph "渐变测试" + T7[渐变效果] --> T7A[设置目标亮度] + T7A --> T7B[验证渐变启动] + T7B --> T7C[监控步进过程] + T7C --> T7D[验证到达目标] + + T8[渐变中断] --> T8A[启动渐变] + T8A --> T8B[发起新渐变] + T8B --> T8C[验证旧渐变停止] + T8C --> T8D[验证新渐变启动] + end +``` + +### 14.2 异常测试 + +- 传感器服务不可用 +- 配置文件损坏 +- 并发访问 +- 资源泄漏 + +```mermaid +graph TD + subgraph "异常场景测试" + E1[传感器服务异常] --> E1A[停止 iio-sensor-proxy] + E1A --> E1B[验证服务不可用检测] + E1B --> E1C[验证优雅降级] + E1C --> E1D[重启服务] + E1D --> E1E[验证自动恢复] + + E2[配置异常] --> E2A[损坏配置文件] + E2A --> E2B[尝试加载配置] + E2B --> E2C[验证使用默认配置] + + E3[并发测试] --> E3A[多线程调用] + E3A --> E3B[验证无死锁] + E3B --> E3C[验证数据一致性] + + E4[资源泄漏] --> E4A[反复启停] + E4A --> E4B[监控 goroutine 数量] + E4B --> E4C[监控内存使用] + E4C --> E4D[验证资源正确释放] + end + + subgraph "边界条件测试" + B1[极端光照值] --> B1A[测试 0 lux] + B1A --> B1B[测试 255 lux] + B1B --> B1C[测试超出范围值] + + B2[极端配置] --> B2A[最小轮询间隔] + B2A --> B2B[最大敏感度] + B2B --> B2C[最小阈值] + + B3[快速变化] --> B3A[光照快速波动] + B3A --> B3B[验证防抖动] + B3B --> B3C[验证调节频率限制] + end +``` + +### 14.3 性能测试 + +- CPU 占用率 +- 内存使用 +- 响应延迟 +- 长时间运行稳定性 + +```mermaid +graph LR + subgraph "性能指标" + P1[CPU 占用] --> P1A[空闲时 < 0.5%] + P1A --> P1B[调节时 < 2%] + + P2[内存使用] --> P2A[基础内存 < 5MB] + P2A --> P2B[无内存泄漏] + + P3[响应延迟] --> P3A[光照变化到调节 < 5s] + P3A --> P3B[配置变更生效 < 1s] + + P4[稳定性] --> P4A[24小时运行测试] + P4A --> P4B[无崩溃无异常] + end + + subgraph "压力测试" + S1[高频调节] --> S1A[每秒变化光照] + S1A --> S1B[验证系统稳定] + + S2[长时间运行] --> S2A[连续运行7天] + S2A --> S2B[监控资源使用] + S2B --> S2C[验证无退化] + + S3[并发压力] --> S3A[多线程频繁调用] + S3A --> S3B[验证锁性能] + S3B --> S3C[验证无竞争条件] + end +``` + +## 15. 未来扩展 + +### 15.1 算法优化 + +- 支持非线性亮度曲线 +- 机器学习自适应调节 +- 时间段相关的亮度策略 + +### 15.2 功能增强 + +- 多显示器独立控制 +- 色温自动调节 +- 用户习惯学习 + +### 15.3 性能优化 + +- 事件驱动替代轮询 +- 智能采样频率调整 +- 更精细的电源管理 + +## 16. 典型使用场景 + +### 16.1 场景一:用户启用自动亮度 + +```mermaid +sequenceDiagram + participant User as 用户 + participant CC as 控制中心 + participant Manager as Display Manager + participant ABM as AutoBrightnessManager + participant Sensor as 传感器 + participant Display as 显示器 + + User->>CC: 打开自动亮度开关 + CC->>Manager: SetAutoBrightnessEnabled(true) + Manager->>ABM: SetEnabled(true) + ABM->>ABM: 保存配置 + ABM->>ABM: Start() + ABM->>Sensor: 连接并声明传感器 + Sensor-->>ABM: 成功 + ABM->>ABM: 启动轮询 + + loop 每3秒 + ABM->>Sensor: 读取光照强度 + Sensor-->>ABM: 150 lux + ABM->>ABM: 计算目标亮度 58% + ABM->>Display: 渐变调整到 58% + Display->>Display: 平滑过渡 + end + + Manager->>CC: PropertiesChanged + CC->>User: 显示已启用状态 +``` + +### 16.2 场景二:环境光变化触发调节 + +```mermaid +sequenceDiagram + participant Env as 环境 + participant Sensor as 传感器 + participant ABM as AutoBrightnessManager + participant Display as 显示器 + participant User as 用户 + + Note over Env: 室内光线变暗 + Env->>Sensor: 光照从 200 lux 降至 50 lux + + ABM->>Sensor: 轮询读取 + Sensor-->>ABM: 50 lux + ABM->>ABM: 计算目标亮度 + Note over ABM: 当前 78% -> 目标 29%
变化 49% > 阈值 20% + + ABM->>ABM: shouldAdjustBrightness() + ABM->>ABM: 所有条件满足 + + ABM->>Display: SetBrightnessForced(0.29) + Note over Display: 启动渐变
2秒内从 78% -> 29% + + loop 20步,每步100ms + Display->>Display: 亮度 -= 2.45% + end + + Display->>User: 屏幕亮度平滑降低 + Note over User: 感觉舒适,无闪烁 +``` + +### 16.3 场景三:手动调节后的行为 + +```mermaid +sequenceDiagram + participant User as 用户 + participant CC as 控制中心 + participant Manager as Display Manager + participant ABM as AutoBrightnessManager + participant Sensor as 传感器 + + Note over ABM: 自动亮度运行中 + + User->>CC: 手动拖动亮度滑块到 90% + CC->>Manager: SetBrightness(0.9) + Manager->>Manager: setBrightness() + Manager->>ABM: OnManualBrightnessChange() + + alt 临时暂停模式 + ABM->>ABM: 记录暂停时间 + ABM->>Sensor: ReleaseLight() + Note over ABM: 暂停 300 秒 + + Note over ABM,Sensor: 5分钟后 + ABM->>ABM: 检查超时 + ABM->>Sensor: ClaimLight() + ABM->>ABM: 恢复自动调节 + Note over User: 自动亮度重新生效 + + else 完全禁用模式 + ABM->>ABM: 保存 Enabled = false + ABM->>ABM: Stop() + ABM->>Sensor: 释放并断开 + Manager->>CC: PropertiesChanged + CC->>User: 显示已禁用状态 + Note over User: 需手动重新启用 + end +``` + +### 16.4 场景四:系统休眠与唤醒 + +```mermaid +sequenceDiagram + participant PM as 电源管理 + participant Manager as Display Manager + participant ABM as AutoBrightnessManager + participant Sensor as 传感器 + participant Display as 显示器 + + Note over PM: 用户合上笔记本 + PM->>Manager: PrepareForSleep(true) + Manager->>ABM: hold() + ABM->>ABM: systemAdjusting = true + ABM->>ABM: stopPolling() + Note over ABM: 保持连接但停止轮询 + + Note over PM: 系统休眠... + + Note over PM: 用户打开笔记本 + PM->>Manager: PrepareForSleep(false) + Manager->>ABM: resume() + ABM->>ABM: startPolling() + ABM->>Sensor: 立即读取光照 + Sensor-->>ABM: 当前光照值 + ABM->>Display: 调整到合适亮度 + ABM->>ABM: systemAdjusting = false + Note over ABM: 恢复正常运行 +``` + +## 17. 注意事项 + +1. **线程安全:** 所有公共方法都需要考虑并发访问 +2. **资源管理:** 确保传感器资源正确释放 +3. **用户体验:** 避免频繁调节造成闪烁 +4. **电源效率:** 合理设置轮询间隔 +5. **降级策略:** 功能不可用时不影响系统稳定性 + diff --git "a/display1/docs/auto_backlight/\350\207\252\345\212\250\344\272\256\345\272\246\345\212\237\350\203\275\344\275\277\347\224\250\346\214\207\345\215\227.md" "b/display1/docs/auto_backlight/\350\207\252\345\212\250\344\272\256\345\272\246\345\212\237\350\203\275\344\275\277\347\224\250\346\214\207\345\215\227.md" new file mode 100644 index 000000000..0618bc460 --- /dev/null +++ "b/display1/docs/auto_backlight/\350\207\252\345\212\250\344\272\256\345\272\246\345\212\237\350\203\275\344\275\277\347\224\250\346\214\207\345\215\227.md" @@ -0,0 +1,393 @@ +# 自动亮度功能使用指南 + +## 功能介绍 + +自动亮度功能可以根据周围环境的光线强度,自动调整笔记本电脑或一体机屏幕的亮度,让您的眼睛更舒适,同时节省电量。 + +## 配置工具说明 + +本功能使用 `dde-dconfig` 工具进行配置管理。`dde-dconfig` 是 Deepin 桌面环境的统一配置管理工具。 + +**基本语法**: +```bash +dde-dconfig -a <应用ID> -r <资源名称> -k <配置键> --set -v <值> # 设置配置 +dde-dconfig -a <应用ID> -r <资源名称> -k <配置键> --get # 查询配置 +``` + +**自动亮度配置参数**: +- 应用ID:`org.deepin.startdde` +- 资源名称:`org.deepin.Display.AutoBrightness` +- 配置键:`enabled`, `sensitivity`, `polling-interval`, `change-threshold`, `manual-override-duration` + +## 使用前准备 + +### 检查设备是否支持 + +您的设备需要满足以下条件: + +1. **有环境光传感器**(大部分现代笔记本都有) +2. **是内置屏幕**(外接显示器不支持) +3. **系统服务正常** + +### 快速检查方法 + +打开终端,输入以下命令检查: + +```bash +# 检查是否支持自动亮度 +dbus-send --session --print-reply --dest=com.deepin.daemon.Display \ + /com/deepin/daemon/Display org.freedesktop.DBus.Properties.Get \ + string:com.deepin.daemon.Display string:AutoBrightnessSupported +``` + +如果返回 `boolean true`,说明您的设备支持此功能。 + +## 基本使用 + +### 启用自动亮度 + +```bash +# 启用自动亮度 +dbus-send --session --dest=com.deepin.daemon.Display \ + /com/deepin/daemon/Display com.deepin.daemon.Display.SetAutoBrightnessEnabled \ + boolean:true +``` + +### 关闭自动亮度 + +```bash +# 关闭自动亮度 +dbus-send --session --dest=com.deepin.daemon.Display \ + /com/deepin/daemon/Display com.deepin.daemon.Display.SetAutoBrightnessEnabled \ + boolean:false +``` + +### 查看当前状态 + +```bash +# 查看是否已启用 +dbus-send --session --print-reply --dest=com.deepin.daemon.Display \ + /com/deepin/daemon/Display org.freedesktop.DBus.Properties.Get \ + string:com.deepin.daemon.Display string:AutoBrightnessEnabled + +# 查看当前环境光强度 +dbus-send --session --print-reply --dest=com.deepin.daemon.Display \ + /com/deepin/daemon/Display org.freedesktop.DBus.Properties.Get \ + string:com.deepin.daemon.Display string:CurrentLightLevel +``` + +## 个性化设置 + +### 设置参数说明 + +| 设置项 | 配置键 | 说明 | 取值范围 | 推荐值 | +|-------|--------|------|---------|--------| +| **启用状态** | enabled | 是否启用自动亮度功能 | true/false | true | +| **敏感度** | sensitivity | 控制亮度调节的敏感程度,值越大亮度变化越明显 | 0.1-3.0 | 0.5(标准)| +| **检测间隔** | polling-interval | 多久检测一次环境光(秒) | 1-60秒 | 3秒(标准)| +| **变化阈值** | change-threshold | 环境光变化多少才调节亮度(绝对值) | 1.0-50.0 | 20.0(标准)| +| **暂停时间** | manual-override-duration | 手动调节后暂停自动调节的时间(秒) | 60-1800秒 | 300秒(5分钟)| + +### 单独设置配置项 + +每个配置项都可以使用 `dde-dconfig` 命令单独设置: + +```bash +# 设置敏感度 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.5 + +# 设置检测间隔(秒) +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 3 + +# 设置变化阈值 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 20.0 + +# 设置手动调节暂停时间(秒) +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 300 +``` + +### 查询单个配置项 + +```bash +# 查询敏感度 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --get + +# 查询检测间隔 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --get + +# 查询变化阈值 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --get + +# 查询暂停时间 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k manual-override-duration --get + +# 查询所有配置 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness --list +``` + +### 常用配置示例 + +**注意**:配置修改后会立即生效,无需重启服务。 + +#### 标准配置(推荐) +```bash +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.5 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 3 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 20.0 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 300 +``` + +#### 快速响应配置(适合移动办公) +```bash +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.8 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 2 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 15.0 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 180 +``` + +#### 稳定配置(适合固定办公) +```bash +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.4 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 5 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 25.0 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 600 +``` + +#### 节能配置(最大化省电) +```bash +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.3 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 10 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 30.0 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 900 +``` + +## 使用技巧 + +### 1. 手动调节与自动调节的配合 + +- **手动调节后**:系统会自动暂停5分钟,避免冲突 +- **立即恢复**:关闭后重新开启自动亮度即可立即恢复 +- **调整暂停时间**:可以修改 `manual_override_duration` 参数 + +### 2. 不同环境的优化 + +**明亮办公室**: +- 敏感度:0.4-0.6 +- 检测间隔:5-10秒 +- 变化阈值:20.0-30.0 + +**昏暗环境**: +- 敏感度:0.6-0.8 +- 检测间隔:3-5秒 +- 变化阈值:15.0-20.0 + +**户外使用**: +- 敏感度:0.8-1.2 +- 检测间隔:2-3秒 +- 变化阈值:10.0-15.0 + +### 3. 省电优化 + +如果您更关心电池续航: +- 增加检测间隔(10-30秒) +- 提高变化阈值(25.0-40.0) +- 降低敏感度(0.3-0.5) + +## 常见问题解决 + +### 问题1:功能不可用 + +**现象**:显示不支持自动亮度 + +**解决方法**: +```bash +# 检查传感器服务 +sudo systemctl status iio-sensor-proxy + +# 如果服务未运行,启动它 +sudo systemctl start iio-sensor-proxy +sudo systemctl enable iio-sensor-proxy +``` + +### 问题2:亮度不调节 + +**现象**:环境光变化但屏幕亮度不变 + +**解决方法**: +1. 检查是否在手动调节暂停期间 +2. 降低变化阈值 +3. 重新启用功能 + +```bash +# 方法1:降低变化阈值(更敏感) +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 10.0 + +# 方法2:重新启用(清除暂停状态) +dbus-send --session --dest=com.deepin.daemon.Display \ + /com/deepin/daemon/Display com.deepin.daemon.Display.SetAutoBrightnessEnabled \ + boolean:false +dbus-send --session --dest=com.deepin.daemon.Display \ + /com/deepin/daemon/Display com.deepin.daemon.Display.SetAutoBrightnessEnabled \ + boolean:true +``` + +### 问题3:调节太频繁 + +**现象**:屏幕亮度变化太频繁,影响使用 + +**解决方法**: +```bash +# 方法1:提高变化阈值(降低敏感度) +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 30.0 + +# 方法2:增加检测间隔 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 10 + +# 方法3:使用完整稳定配置 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.4 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 5 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 25.0 +``` + +### 问题4:反应太慢 + +**现象**:环境光变化后很久才调节亮度 + +**解决方法**: +```bash +# 方法1:降低变化阈值(更敏感) +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 10.0 + +# 方法2:减少检测间隔 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 2 + +# 方法3:使用完整快速响应配置 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.8 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 2 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 15.0 +``` + +## 高级功能 + +### 查看详细状态 + +```bash +# 查看所有配置项 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness --list + +# 查看单个配置项 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k enabled --get +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --get +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --get +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --get +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k manual-override-duration --get + +# 查看运行状态(通过 D-Bus) +dbus-send --session --print-reply --dest=com.deepin.daemon.Display \ + /com/deepin/daemon/Display org.freedesktop.DBus.Properties.Get \ + string:com.deepin.daemon.Display string:AutoBrightnessSupported + +dbus-send --session --print-reply --dest=com.deepin.daemon.Display \ + /com/deepin/daemon/Display org.freedesktop.DBus.Properties.Get \ + string:com.deepin.daemon.Display string:AutoBrightnessEnabled +``` + +### 监控环境光变化 + +```bash +# 实时监控环境光强度变化 +watch -n 1 'dbus-send --session --print-reply --dest=com.deepin.daemon.Display \ + /com/deepin/daemon/Display org.freedesktop.DBus.Properties.Get \ + string:com.deepin.daemon.Display string:CurrentLightLevel' +``` + +### 查看日志 + +如果遇到问题,可以查看系统日志: + +```bash +# 查看自动亮度相关日志 +journalctl --user -u startdde -f | grep AutoBrightness + +# 查看传感器服务日志 +journalctl -u iio-sensor-proxy -f +``` + +## 注意事项 + +1. **仅限内置屏幕**:外接显示器不支持此功能 +2. **传感器位置**:不要遮挡设备上的光线传感器 +3. **电量消耗**:频繁检测会增加电量消耗,建议合理设置检测间隔 +4. **手动优先**:手动调节亮度后会自动暂停功能一段时间 +5. **重启保持**:设置会自动保存,重启后依然有效 +6. **敏感度说明**:敏感度参数控制环境光到屏幕亮度的映射关系,值越大屏幕亮度变化越明显。推荐范围 0.3-1.0,默认 0.5 + +## 配置参数详解 + +### 敏感度 (sensitivity) + +敏感度控制环境光强度到屏幕亮度的转换比例: + +- **0.1-0.3**:低敏感度,适合希望屏幕亮度变化较小的用户 +- **0.4-0.6**:标准敏感度,适合大多数使用场景(推荐) +- **0.7-1.0**:高敏感度,适合需要明显亮度变化的场景 +- **1.1-3.0**:超高敏感度,仅在特殊场景使用 + +**计算公式**:屏幕亮度 = (环境光强度 × 敏感度) / 255 + +### 检测间隔 (polling_interval) + +控制多久检测一次环境光强度: + +- **1-2秒**:快速响应,适合移动办公,但会增加电量消耗 +- **3-5秒**:标准响应,平衡响应速度和电量消耗(推荐) +- **6-10秒**:慢速响应,适合固定办公环境 +- **11-60秒**:省电模式,适合电池续航优先的场景 + +### 变化阈值 (change_threshold) + +环境光强度变化超过此值才会触发亮度调节: + +- **1.0-10.0**:低阈值,对环境光变化敏感,调节频繁 +- **11.0-25.0**:标准阈值,适合大多数场景(推荐) +- **26.0-50.0**:高阈值,只在环境光明显变化时才调节 + +**注意**:阈值过低会导致频繁调节,阈值过高会导致响应迟钝 + +### 暂停时间 (manual_override_duration) + +手动调节亮度后,自动调节功能暂停的时间: + +- **60-180秒**:短暂停,适合频繁切换环境的场景 +- **181-600秒**:标准暂停,适合大多数场景(推荐 300秒) +- **601-1800秒**:长暂停,适合希望手动控制优先的场景 + +## 推荐设置 + +### 日常办公 +```bash +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.5 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 3 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 20.0 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 300 +``` + +### 移动办公 +```bash +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.8 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 2 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 15.0 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 180 +``` + +### 省电模式 +```bash +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.3 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 10 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 30.0 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 600 +``` + +--- + +**提示**:如果您不熟悉命令行操作,建议使用系统设置界面进行配置(如果可用)。如有问题,可以随时关闭此功能,不会影响正常的手动亮度调节。 diff --git a/display1/exported_methods_auto.go b/display1/exported_methods_auto.go index e1eabc163..366b8e0eb 100644 --- a/display1/exported_methods_auto.go +++ b/display1/exported_methods_auto.go @@ -94,6 +94,11 @@ func (v *Manager) GetExportedMethods() dbusutil.ExportedMethods { Fn: v.SetAndSaveBrightness, InArgs: []string{"outputName", "value"}, }, + { + Name: "SetAutoBrightnessEnabled", + Fn: v.SetAutoBrightnessEnabled, + InArgs: []string{"enabled"}, + }, { Name: "SetBrightness", Fn: v.SetBrightness, diff --git a/display1/main.go b/display1/main.go index 6c6f5466e..695a44294 100644 --- a/display1/main.go +++ b/display1/main.go @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2025 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -108,6 +108,7 @@ func isInVM() (bool, error) { } func (*daemon) Stop() error { + Cleanup() distory() return nil } diff --git a/display1/manager.go b/display1/manager.go index a88af1891..8bb069eea 100644 --- a/display1/manager.go +++ b/display1/manager.go @@ -89,6 +89,22 @@ const ( DSettingsKeyRotateScreenTimeDelay = "rotateScreenTimeDelay" DSettingsKeyCustomDisplayMode = "customDisplayMode" + // 渐变亮度配置 + DSettingsKeyABTransitionEnabled = "transition-enabled" + DSettingsKeyABTransitionDuration = "transition-duration" + DSettingsKeyABTransitionStepInterval = "transition-step-interval" + + // 自动亮度配置(独立配置文件) + DSettingsAutoBrightnessAppID = "org.deepin.dde.daemon" + DSettingsAutoBrightnessName = "org.deepin.Display.AutoBrightness" + DSettingsKeyABEnabled = "enabled" + DSettingsKeyABSensitivity = "sensitivity" + DSettingsKeyABChangeThreshold = "change-threshold" + DSettingsKeyABPollingInterval = "polling-interval" + DSettingsKeyABManualOverride = "manual-override-duration" + DSettingsKeyABManualAdjustDisablesAutoMode = "manual-adjust-disables-auto-mode" + DSettingsKeyABUseTransition = "use-transition" + customModeDelim = "+" monitorsIdDelimiter = "," defaultTemperatureMode = ColorTemperatureModeNone @@ -237,6 +253,18 @@ type Manager struct { xsManager xs.XSettings isVM bool + + // 自动亮度相关属性 + AutoBrightnessEnabled bool `prop:"access:rw"` + AutoBrightnessSupported bool `prop:"access:r"` + + // 自动亮度管理器 + autoBrightnessManager *AutoBrightnessManager + + // 亮度渐变管理器 + brightnessTransition *BrightnessTransition + + powerSaving bool } type monitorSizeInfo struct { @@ -382,8 +410,10 @@ func newManager(service *dbusutil.Service) *Manager { m.sessionActive = active if !active { + m.holdAutoBrightness() return } + m.resumeAutoBrightness() if m.newSysCfg != nil { m.handleSysConfigUpdated(m.newSysCfg) m.newSysCfg = nil @@ -1194,6 +1224,11 @@ func (m *Manager) init() { // 埋点:启动时记录屏幕信息 m.logDisplayScreenEvent() + + go func() { + m.initBrightnessTransition() + m.initAutoBrightness() + }() } // 过滤掉部分模式,尽量不过滤掉 saveMode。 @@ -3304,7 +3339,6 @@ func (m *Manager) tryToChangeScaleFactor(monitorWidth, monitorHeight uint16) { } } - // logDisplayScreenEvent records display screen information for event logging func (m *Manager) logDisplayScreenEvent() { monitors := m.getConnectedMonitors() @@ -3323,3 +3357,141 @@ func (m *Manager) logDisplayScreenEvent() { LogOnStartup(screenCount, displayMode, primaryMonitor) } +// getMonitorBrightness 获取显示器当前亮度 +func (m *Manager) getMonitorBrightness(name string) float64 { + m.PropsMu.RLock() + defer m.PropsMu.RUnlock() + + if v, ok := m.Brightness[name]; ok { + return v + } + return -1 // 返回 -1 表示无法获取 +} + +// 手动添加自动亮度相关的属性设置方法 +func (m *Manager) setPropAutoBrightnessEnabled(value bool) (changed bool) { + if m.AutoBrightnessEnabled != value { + m.AutoBrightnessEnabled = value + m.emitPropChangedAutoBrightnessEnabled(value) + return true + } + return false +} + +func (m *Manager) emitPropChangedAutoBrightnessEnabled(value bool) error { + return m.service.EmitPropertyChanged(m, "AutoBrightnessEnabled", value) +} + +func (m *Manager) setPropAutoBrightnessSupported(value bool) (changed bool) { + if m.AutoBrightnessSupported != value { + m.AutoBrightnessSupported = value + m.emitPropChangedAutoBrightnessSupported(value) + return true + } + return false +} + +func (m *Manager) emitPropChangedAutoBrightnessSupported(value bool) error { + return m.service.EmitPropertyChanged(m, "AutoBrightnessSupported", value) +} + +// 自动亮度管理器集成方法 + +// initAutoBrightness 初始化自动亮度管理器 +func (m *Manager) initAutoBrightness() { + logger.Debug("Initializing auto brightness manager") + + m.autoBrightnessManager = NewAutoBrightnessManager() + err := m.autoBrightnessManager.Initialize(m) + if err != nil { + logger.Info("Auto brightness not supported:", err) + m.setPropAutoBrightnessSupported(false) + m.setPropAutoBrightnessEnabled(false) + return + } + + // 设置支持状态 + m.setPropAutoBrightnessSupported(m.autoBrightnessManager.IsSupported()) + + // 获取配置(已在 Initialize 中加载) + config, err := m.autoBrightnessManager.getConfig() + if err != nil { + logger.Warning("Failed to get auto brightness config:", err) + config = DefaultAutoBrightnessConfig + } + + // 设置启用状态属性 + m.setPropAutoBrightnessEnabled(config.Enabled) + + // 如果配置启用且支持,则启动 + if config.Enabled && m.autoBrightnessManager.IsSupported() { + err = m.autoBrightnessManager.Start() + if err != nil { + logger.Warning("Failed to start auto brightness manager:", err) + } + } + + logger.Info("Auto brightness manager initialized successfully") +} + +// initBrightnessTransition 初始化亮度渐变管理器 +func (m *Manager) initBrightnessTransition() { + logger.Debug("Initializing brightness transition manager") + + // 创建亮度渐变管理器 + m.brightnessTransition = NewBrightnessTransition(m) + + // 加载配置 + err := m.brightnessTransition.LoadConfig(m.sysBus) + if err != nil { + logger.Warning("Failed to load brightness transition config:", err) + return + } + + // 监听配置变化 + err = m.brightnessTransition.WatchConfigChanges(m.sysSigLoop) + if err != nil { + logger.Warning("Failed to watch brightness transition config changes:", err) + } + + logger.Info("Brightness transition manager initialized successfully") +} + +// notifyManualBrightnessChange 通知手动亮度调节 +func (m *Manager) notifyManualBrightnessChange() { + if m.autoBrightnessManager != nil { + m.autoBrightnessManager.OnManualBrightnessChange() + } +} + +// setSystemAdjusting 设置系统调整标志(用于区分系统自动调整和用户手动调整) +func (m *Manager) setSystemAdjusting(adjusting bool) { + if m.autoBrightnessManager != nil { + m.autoBrightnessManager.setSystemAdjusting(adjusting) + } +} + +// holdAutoBrightness 通知系统休眠 +func (m *Manager) holdAutoBrightness() { + if m.autoBrightnessManager != nil { + m.autoBrightnessManager.hold() + } +} + +// resumeAutoBrightness 通知系统唤醒 +func (m *Manager) resumeAutoBrightness() { + if m.autoBrightnessManager != nil { + m.autoBrightnessManager.resume() + } +} + +// cleanupAutoBrightness 清理自动亮度资源 +func (m *Manager) cleanupAutoBrightness() { + if m.autoBrightnessManager != nil { + err := m.autoBrightnessManager.Cleanup() + if err != nil { + logger.Warning("Failed to cleanup auto brightness manager:", err) + } + m.autoBrightnessManager = nil + } +} diff --git a/display1/manager_ifc.go b/display1/manager_ifc.go index 0afde107e..fd22bfb5b 100644 --- a/display1/manager_ifc.go +++ b/display1/manager_ifc.go @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2022 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -114,6 +114,23 @@ func (m *Manager) ChangeBrightness(raised bool) *dbus.Error { return dbusutil.ToError(err) } +// SetAutoBrightnessEnabled 设置自动亮度启用状态 +func (m *Manager) SetAutoBrightnessEnabled(enabled bool) *dbus.Error { + logger.Debug("dbus call SetAutoBrightnessEnabled", enabled) + if m.autoBrightnessManager == nil { + return dbusutil.ToError(errors.New("auto brightness not supported")) + } + + err := m.autoBrightnessManager.SetEnabled(enabled) + if err != nil { + return dbusutil.ToError(err) + } + + m.setPropAutoBrightnessEnabled(enabled) + + return nil +} + func (m *Manager) GetBrightness() (map[string]float64, *dbus.Error) { m.PropsMu.RLock() defer m.PropsMu.RUnlock() diff --git a/display1/manager_lid.go b/display1/manager_lid.go index cc6b11fd5..001e7e3bb 100644 --- a/display1/manager_lid.go +++ b/display1/manager_lid.go @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2025 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -42,12 +42,14 @@ func (m *Manager) initLidSwitch() { sysPower.InitSignalExt(m.sysSigLoop, true) sysPower.ConnectLidClosed(func() { logger.Warning("lid closed signal") + m.holdAutoBrightness() m.handleLidSwitch(sysPower, func(closed bool) { m.setLidClosed(closed) }) }) sysPower.ConnectLidOpened(func() { logger.Warning("lid open signal") + m.resumeAutoBrightness() m.handleLidSwitch(sysPower, func(closed bool) { m.setLidClosed(closed) }) diff --git a/display1/sensor_proxy.go b/display1/sensor_proxy.go new file mode 100644 index 000000000..c2ba49203 --- /dev/null +++ b/display1/sensor_proxy.go @@ -0,0 +1,440 @@ +// SPDX-FileCopyrightText: 2022 - 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later +package display1 + +import ( + "errors" + "fmt" + "sync" + "time" + + "github.com/godbus/dbus/v5" +) + +const ( + // net.hadess.SensorProxy D-Bus 接口常量 + hadessProxyService = "net.hadess.SensorProxy" + hadessProxyObjectPath = "/net/hadess/SensorProxy" + hadessProxyInterface = "net.hadess.SensorProxy" + // 属性名称 + propHasAmbientLight = "HasAmbientLight" + propLightLevel = "LightLevel" + propLightLevelUnit = "LightLevelUnit" + // 方法名称 + methodClaimLight = "ClaimLight" + methodReleaseLight = "ReleaseLight" + // 信号名称 + signalPropertiesChanged = "org.freedesktop.DBus.Properties.PropertiesChanged" +) + +// SensorProxyClient 环境光传感器D-Bus客户端 +type SensorProxyClient struct { + conn *dbus.Conn + sensorProxy dbus.BusObject + hasAmbientLight bool + claimed bool + // 事件处理 + signalChan chan *dbus.Signal + onServiceChange func(bool) + // 同步控制 + mutex sync.Mutex + // 服务监控 + serviceAvailable bool + ownerWatcher chan *dbus.Signal + // 错误处理 + maxRetries int + retryDelay time.Duration + filterValue int // 滤波值 + filterFactor float64 // 滤波参数默认0.2,暂时没有必要进行配置 +} + +// NewSensorProxyClient 创建新的传感器代理客户端 +func NewSensorProxyClient(conn *dbus.Conn) *SensorProxyClient { + return &SensorProxyClient{ + conn: conn, + signalChan: make(chan *dbus.Signal, 10), + ownerWatcher: make(chan *dbus.Signal, 10), + maxRetries: 3, + retryDelay: time.Millisecond * 500, + filterValue: 0, + filterFactor: 0.2, + } +} + +// Connect 连接到SensorProxy服务 +func (c *SensorProxyClient) Connect() error { + c.mutex.Lock() + defer c.mutex.Unlock() + if c.conn == nil { + return errors.New("D-Bus connection is nil") + } + // 创建D-Bus对象 + c.sensorProxy = c.conn.Object(hadessProxyService, hadessProxyObjectPath) + // 检查服务是否可用(带重试机制) + err := c.checkServiceAvailableWithRetry() + if err != nil { + c.serviceAvailable = false + return fmt.Errorf("SensorProxy service not available after retries: %w", err) + } + c.serviceAvailable = true + // 检查是否有环境光传感器 + hasLight, err := c.hasAmbientLightInternal() + if err != nil { + return fmt.Errorf("failed to check ambient light sensor: %w", err) + } + c.hasAmbientLight = hasLight + if !hasLight { + return errors.New("no ambient light sensor available") + } + // 启动信号监听 + c.startSignalWatching() + // 启动服务监控 + c.startServiceWatching() + return nil +} + +// Disconnect 断开连接 +func (c *SensorProxyClient) Disconnect() error { + c.mutex.Lock() + defer c.mutex.Unlock() + // 释放环境光传感器 + if c.claimed { + err := c.releaseLightInternal() + if err != nil { + logger.Warning("[SensorProxy] Failed to release light sensor:", err) + } + } + // 停止信号监听 + c.stopSignalWatching() + // 停止服务监控 + c.stopServiceWatching() + c.filterValue = 0 + c.sensorProxy = nil + c.serviceAvailable = false + c.hasAmbientLight = false + return nil +} + +// ClaimLight 声明对环境光传感器的使用 +func (c *SensorProxyClient) ClaimLight() error { + logger.Debug("[SensorProxy] Claiming light sensor") + c.mutex.Lock() + defer c.mutex.Unlock() + if !c.serviceAvailable { + return errors.New("SensorProxy service not available") + } + if !c.hasAmbientLight { + return errors.New("no ambient light sensor") + } + if c.claimed { + logger.Debug("[SensorProxy] Light sensor already claimed") + return nil + } + err := c.claimLightWithRetry() + if err != nil { + return fmt.Errorf("failed to claim light sensor after retries: %w", err) + } + c.claimed = true + return nil +} + +// ReleaseLight 释放环境光传感器 +func (c *SensorProxyClient) ReleaseLight() error { + logger.Debug("[SensorProxy] Releasing light sensor") + c.mutex.Lock() + defer c.mutex.Unlock() + if !c.claimed { + logger.Debug("[SensorProxy] Light sensor not claimed") + return nil + } + err := c.releaseLightInternal() + if err != nil { + logger.Warning("[SensorProxy] Failed to release light sensor:", err) + return err + } + c.claimed = false + c.filterValue = 0 + return nil +} + +// GetLightLevel 获取当前环境光强度(返回缓存值的平均值并清空缓存) +func (c *SensorProxyClient) GetLightLevel() (int, error) { + c.mutex.Lock() + defer c.mutex.Unlock() + if !c.serviceAvailable { + return 0, errors.New("SensorProxy service not available") + } + if !c.hasAmbientLight { + return 0, errors.New("no ambient light sensor") + } + if !c.claimed { + return 0, errors.New("light sensor not claimed") + } + // 如果缓存为空,返回上一次的平均值 + if c.filterValue == 0 { + // 没有任何数据可用 + return 0, errors.New("no light level data available") + } + return c.filterValue, nil +} + +// HasAmbientLight 检查是否有环境光传感器 +func (c *SensorProxyClient) HasAmbientLight() (bool, error) { + c.mutex.Lock() + defer c.mutex.Unlock() + if !c.serviceAvailable { + return false, errors.New("SensorProxy service not available") + } + return c.hasAmbientLightInternal() +} + +// SetServiceChangeCallback 设置服务状态变化回调 +func (c *SensorProxyClient) SetServiceChangeCallback(callback func(bool)) { + c.mutex.Lock() + defer c.mutex.Unlock() + c.onServiceChange = callback +} + +// IsServiceAvailable 检查服务是否可用 +func (c *SensorProxyClient) IsServiceAvailable() bool { + c.mutex.Lock() + defer c.mutex.Unlock() + return c.serviceAvailable +} + +// IsClaimed 检查是否已声明传感器 +func (c *SensorProxyClient) IsClaimed() bool { + c.mutex.Lock() + defer c.mutex.Unlock() + return c.claimed +} + +// 内部方法 - 不加锁 +// checkServiceAvailableWithRetry 带重试机制检查服务是否可用 +func (c *SensorProxyClient) checkServiceAvailableWithRetry() error { + var lastErr error + for i := 0; i < c.maxRetries; i++ { + err := c.checkServiceAvailable() + if err == nil { + return nil + } + lastErr = err + if i < c.maxRetries-1 { + time.Sleep(c.retryDelay) + } + } + return lastErr +} + +// claimLightWithRetry 带重试机制声明环境光传感器 +func (c *SensorProxyClient) claimLightWithRetry() error { + var lastErr error + for i := 0; i < c.maxRetries; i++ { + err := c.claimLightInternal() + if err == nil { + return nil + } + lastErr = err + if i < c.maxRetries-1 { + time.Sleep(c.retryDelay) + } + } + return lastErr +} + +// checkServiceAvailable 检查服务是否可用 +func (c *SensorProxyClient) checkServiceAvailable() error { + // 尝试调用一个简单的属性获取来检查服务是否可用 + _, err := c.sensorProxy.GetProperty(hadessProxyInterface + "." + propHasAmbientLight) + return err +} + +// hasAmbientLightInternal 内部检查环境光传感器 +func (c *SensorProxyClient) hasAmbientLightInternal() (bool, error) { + variant, err := c.sensorProxy.GetProperty(hadessProxyInterface + "." + propHasAmbientLight) + if err != nil { + return false, err + } + hasLight, ok := variant.Value().(bool) + if !ok { + return false, errors.New("invalid HasAmbientLight type") + } + return hasLight, nil +} + +// claimLightInternal 内部声明环境光传感器 +func (c *SensorProxyClient) claimLightInternal() error { + call := c.sensorProxy.Call(hadessProxyInterface+"."+methodClaimLight, 0) + return call.Err +} + +// releaseLightInternal 内部释放环境光传感器 +func (c *SensorProxyClient) releaseLightInternal() error { + call := c.sensorProxy.Call(hadessProxyInterface+"."+methodReleaseLight, 0) + return call.Err +} + +// startSignalWatching 启动信号监听 +func (c *SensorProxyClient) startSignalWatching() { + // 添加属性变化信号监听,只监听特定服务的特定对象路径 + matchRule := "type='signal'," + + "sender='" + hadessProxyService + "'," + + "interface='org.freedesktop.DBus.Properties'," + + "member='PropertiesChanged'," + + "path='" + hadessProxyObjectPath + "'," + + "arg0='" + hadessProxyInterface + "'" + err := c.conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, matchRule).Err + if err != nil { + logger.Warning("[SensorProxy] Failed to add PropertiesChanged signal match:", err) + return + } + // 启动信号处理协程 + go c.handleSignals() +} + +// stopSignalWatching 停止信号监听 +func (c *SensorProxyClient) stopSignalWatching() { + // 移除信号监听 + matchRule := "type='signal'," + + "sender='" + hadessProxyService + "'," + + "interface='org.freedesktop.DBus.Properties'," + + "member='PropertiesChanged'," + + "path='" + hadessProxyObjectPath + "'," + + "arg0='" + hadessProxyInterface + "'" + err := c.conn.BusObject().Call("org.freedesktop.DBus.RemoveMatch", 0, matchRule).Err + if err != nil { + logger.Warning("[SensorProxy] Failed to remove PropertiesChanged signal match:", err) + } +} + +// startServiceWatching 启动服务监控 +func (c *SensorProxyClient) startServiceWatching() { + // 监听特定服务的所有者变化 + matchRule := "type='signal'," + + "sender='org.freedesktop.DBus'," + + "interface='org.freedesktop.DBus'," + + "member='NameOwnerChanged'," + + "path='/org/freedesktop/DBus'," + + "arg0='" + hadessProxyService + "'" + err := c.conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, matchRule).Err + if err != nil { + logger.Warning("[SensorProxy] Failed to add NameOwnerChanged signal match:", err) + return + } + // 启动服务监控协程 + go c.handleServiceChanges() +} + +// stopServiceWatching 停止服务监控 +func (c *SensorProxyClient) stopServiceWatching() { + // 移除服务监听 + matchRule := "type='signal'," + + "sender='org.freedesktop.DBus'," + + "interface='org.freedesktop.DBus'," + + "member='NameOwnerChanged'," + + "path='/org/freedesktop/DBus'," + + "arg0='" + hadessProxyService + "'" + err := c.conn.BusObject().Call("org.freedesktop.DBus.RemoveMatch", 0, matchRule).Err + if err != nil { + logger.Warning("[SensorProxy] Failed to remove NameOwnerChanged signal match:", err) + } +} + +// handleSignals 处理D-Bus信号 +func (c *SensorProxyClient) handleSignals() { + c.conn.Signal(c.signalChan) + for signal := range c.signalChan { + if signal.Name == "org.freedesktop.DBus.Properties.PropertiesChanged" { + c.handlePropertiesChanged(signal) + } + } +} + +// handleServiceChanges 处理服务状态变化 +func (c *SensorProxyClient) handleServiceChanges() { + c.conn.Signal(c.ownerWatcher) + for signal := range c.ownerWatcher { + if signal.Name == "org.freedesktop.DBus.NameOwnerChanged" { + c.handleNameOwnerChanged(signal) + } + } +} + +// handlePropertiesChanged 处理属性变化信号 +func (c *SensorProxyClient) handlePropertiesChanged(signal *dbus.Signal) { + if len(signal.Body) < 2 { + logger.Warning("[SensorProxy] Invalid PropertiesChanged signal body") + return + } + // 第二个参数是变化的属性映射 + changedProps, ok := signal.Body[1].(map[string]dbus.Variant) + if !ok { + logger.Warning("[SensorProxy] Invalid PropertiesChanged signal format") + return + } + c.mutex.Lock() + defer c.mutex.Unlock() + // 检查LightLevel属性变化(当claimed后会收到此信号) + if lightLevelVariant, exists := changedProps[propLightLevel]; exists { + if lightLevel, ok := lightLevelVariant.Value().(float64); ok { + // 只有在claimed状态下才添加到缓存 + if c.claimed { + c.lightValueFilter(int(lightLevel)) + } + } else { + logger.Warning("[SensorProxy] Failed to convert LightLevel value to float") + } + } +} + +// handleNameOwnerChanged 处理服务所有者变化 +func (c *SensorProxyClient) handleNameOwnerChanged(signal *dbus.Signal) { + if len(signal.Body) < 3 { + logger.Warning("[SensorProxy] Invalid NameOwnerChanged signal body") + return + } + serviceName, ok := signal.Body[0].(string) + if !ok || serviceName != hadessProxyService { + return + } + newOwner, ok := signal.Body[2].(string) + if !ok { + return + } + // 服务状态变化 + serviceAvailable := newOwner != "" + c.mutex.Lock() + oldAvailable := c.serviceAvailable + c.serviceAvailable = serviceAvailable + if !serviceAvailable { + // 服务不可用,重置状态 + c.claimed = false + c.hasAmbientLight = false + } else if !oldAvailable { + // 服务重新可用,重新检查传感器 + go func() { + time.Sleep(100 * time.Millisecond) // 等待服务完全启动 + hasLight, err := c.HasAmbientLight() + if err == nil { + c.mutex.Lock() + c.hasAmbientLight = hasLight + c.mutex.Unlock() + } + }() + } + callback := c.onServiceChange + c.mutex.Unlock() + // 调用服务状态变化回调 + if callback != nil { + go callback(serviceAvailable) + } +} +func (c *SensorProxyClient) lightValueFilter(newValue int) { + if c.filterValue == 0 { + c.filterValue = newValue + return + } + filtered := c.filterFactor*float64(c.filterValue) + (1.0-c.filterFactor)*float64(newValue) + c.filterValue = int(filtered) +} diff --git a/misc/dsg-configs/org.deepin.Display.AutoBrightness.json b/misc/dsg-configs/org.deepin.Display.AutoBrightness.json new file mode 100644 index 000000000..3718d1ca8 --- /dev/null +++ b/misc/dsg-configs/org.deepin.Display.AutoBrightness.json @@ -0,0 +1,69 @@ +{ + "magic": "dsg.config.meta", + "version": "1.0", + "contents": { + "enabled": { + "name": "Auto Brightness Enabled", + "description": "启用基于环境光传感器的自动亮度调节", + "value": true, + "visibility": "private", + "permissions": "readwrite", + "serial": 0, + "flags": [] + }, + "sensitivity": { + "name": "Auto Brightness Sensitivity", + "description": "自动亮度调节的敏感度,值越大时调整范围越大", + "value": 0.5, + "visibility": "private", + "permissions": "readwrite", + "serial": 0, + "flags": [] + }, + "change-threshold": { + "name": "Light Change Threshold", + "description": "触发亮度调节的环境光变化阈值", + "value": 31.0, + "visibility": "private", + "permissions": "readwrite", + "serial": 0, + "flags": [] + }, + "polling-interval": { + "name": "Polling Interval", + "description": "环境光采样间隔(秒)", + "value": 2, + "visibility": "private", + "permissions": "readwrite", + "serial": 0, + "flags": [] + }, + "manual-override-duration": { + "name": "Manual Override Duration", + "description": "手动调节后暂停自动调节的时长(秒)", + "value": 300, + "visibility": "private", + "permissions": "readwrite", + "serial": 0, + "flags": [] + }, + "manual-adjust-disables-auto-mode": { + "name": "Manual adjust disables auto mode", + "description": "手动调节后是否立即停止功能", + "value": true, + "visibility": "private", + "permissions": "readwrite", + "serial": 0, + "flags": [] + }, + "use-transition": { + "name": "Use Transition for Auto Brightness", + "description": "自动亮度调节时是否使用渐变效果(即使全局渐变功能关闭)", + "value": true, + "visibility": "private", + "permissions": "readwrite", + "serial": 0, + "flags": [] + } + } +} \ No newline at end of file diff --git a/misc/dsg-configs/org.deepin.Display.json b/misc/dsg-configs/org.deepin.Display.json index 79d9ead62..70795f3f8 100644 --- a/misc/dsg-configs/org.deepin.Display.json +++ b/misc/dsg-configs/org.deepin.Display.json @@ -177,6 +177,33 @@ "description[zh_CN]": "自定义显示模式类型(范围:1-2)", "permissions": "readwrite", "visibility": "public" + }, + "transition-enabled": { + "value": true, + "serial": 0, + "flags": [], + "name": "Brightness Transition Enabled", + "description": "是否启用亮度渐变效果", + "permissions": "readwrite", + "visibility": "private" + }, + "transition-duration": { + "value": 4, + "serial": 0, + "flags": [], + "name": "Brightness Transition Duration", + "description": "从 0% 到 100% 完整渐变所需的时间(秒)", + "permissions": "readwrite", + "visibility": "private" + }, + "transition-step-interval": { + "value": 100, + "serial": 0, + "flags": [], + "name": "Brightness Transition Step Interval", + "description": "渐变过程中每次调整亮度的时间间隔(毫秒)", + "permissions": "readwrite", + "visibility": "private" } } } From f35b1d1658c400c254d4fdf545829ed81bd96b8b Mon Sep 17 00:00:00 2001 From: fuleyi Date: Thu, 7 May 2026 11:24:03 +0800 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20=E9=BB=98=E8=AE=A4=E4=BA=AE?= =?UTF-8?q?=E5=BA=A6=E6=9B=B2=E7=BA=BF=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 提供内置屏默认亮度曲线配置功能,重构CurveManager管理亮度曲线配置和计算。 包含对AI代码与设计的重构。 Change-Id: Icc3d68def8d6366f422c829703ac4ae1803a4d83 --- display1/brightness/brightness.go | 9 +- display1/brightness/curve.go | 490 +++++++++++++++++++++++ display1/display_dbusutil.go | 13 + display1/manager.go | 141 ++++++- misc/dsg-configs/org.deepin.Display.json | 72 ++++ 5 files changed, 722 insertions(+), 3 deletions(-) create mode 100644 display1/brightness/curve.go diff --git a/display1/brightness/brightness.go b/display1/brightness/brightness.go index a2368f385..5915b843b 100644 --- a/display1/brightness/brightness.go +++ b/display1/brightness/brightness.go @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2018 - 2022 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2018 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -218,6 +218,13 @@ func setBacklight(value float64, output randr.Output, conn *x.Conn) error { func _setBacklight(value float64, controller *displayBl.Controller) error { br := int32(float64(controller.MaxBrightness) * value) + + v, ok := GetBacklightCurveValue(value, controller) + if ok { + logger.Debugf("Brightness curve value: %v", v) + br = v + } + const backlightTypeDisplay = 1 fmt.Printf("help set brightness %q max %v value %v br %v\n", controller.Name, controller.MaxBrightness, value, br) diff --git a/display1/brightness/curve.go b/display1/brightness/curve.go new file mode 100644 index 000000000..0ab05e750 --- /dev/null +++ b/display1/brightness/curve.go @@ -0,0 +1,490 @@ +// SPDX-FileCopyrightText: 2018 - 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package brightness + +import ( + "encoding/json" + "fmt" + "math" + "strings" + "sync" + + displayBl "github.com/linuxdeepin/go-lib/backlight/display" +) + +const ( + DefaultScale = 100 +) + +// 亮度曲线点 +type BrightnessPoint struct { + Percentage int32 `json:"percentage"` // 亮度百分比 (0-100) + Value int32 `json:"value"` // 对应的亮度百分比 (0-100),表示最大亮度的百分比 +} + +// 自定义亮度曲线配置 +type BrightnessCurveConfig struct { + EDID string `json:"edid,omitempty"` // 屏幕EDID标识(可选,默认曲线不需要) + CurvePoints []BrightnessPoint `json:"curve_points"` // 亮度曲线点 + MaxLimit int32 `json:"max_limit"` // 最大亮度限制百分比 (0-100),表示最大亮度的百分比 + MaxScale int32 `json:"max_scale"` // 告知外部曲线最大比例 +} + +// CurveManager 管理亮度曲线的配置和计算 +type CurveManager struct { + mu sync.RWMutex // 统一的锁保护所有状态 + + // 亮度限制功能使用的硬件信息 + configBoardName string + deviceBoardName string + + // 亮度限制功能开关与缩放值 + maxBrightnessUnlimited bool + maxScale int32 + + // flm机型定制曲线 + flmCurveFunc func(percentage float64, maxBrightness int) int32 + + // 默认曲线函数 + defaultCurveFunc func(percentage float64, maxBrightness int) int32 + + // 受限曲线 + // 配置上可能会有多项,但只应用第一个匹配上的,因为多控制器可能导致亮度设置错乱 + limitedCurveFunc func(percentage float64) int32 + + // 曲线类型 + curveType string + + // FLM曲线参数 + backLightMinValue int32 + backlightMidValue int32 +} + +// 全局 CurveManager 实例 +var _curveManager = &CurveManager{ + maxBrightnessUnlimited: true, + maxScale: DefaultScale, + flmCurveFunc: nil, + defaultCurveFunc: nil, + limitedCurveFunc: nil, +} + +// InitFlmCurves 初始化FLM曲线 +func InitFlmCurves(backLightMinValue int32, backlightMidValue int32) { + _curveManager.initFlmCurves(backLightMinValue, backlightMidValue) +} + +func SetCustomBrightnessCurves(jsonStr string) { + _curveManager.setCustomBrightnessCurves(jsonStr) +} + +func SetMaxBrightnessUnlimited(enabled bool) { + _curveManager.setMaxBrightnessUnlimited(enabled) +} + +func GetBacklightCurveValue(v float64, c *displayBl.Controller) (int32, bool) { + return _curveManager.getCurveValue(v, c) +} + +func SetDeviceBoardName(boardName string) { + _curveManager.setDeviceBoardName(boardName) +} + +// SetDefaultBrightnessCurve 设置默认亮度曲线(不依赖硬件信息) +func SetDefaultBrightnessCurve(jsonStr string) { + _curveManager.setDefaultBrightnessCurve(jsonStr) +} + +func IsDeviceSupported() bool { + return _curveManager.isDeviceSupported() +} + +func IsMaxLimitCurveSupported() bool { + return _curveManager.isMaxLimitCurveSupported() +} + +func GetCurrentMaxScale() int32 { + return _curveManager.getCurrentMaxScale() +} + +func SetCurveType(curveType string) { + _curveManager.setCurveType(curveType) +} + +// interpolateCurveValue 对曲线点进行线性插值计算 +// percentage: 输入百分比 (0-100) +// curvePoints: 曲线点数组 +// 返回插值后的曲线值(百分比形式) +func interpolateCurveValue(percentage float64, curvePoints []BrightnessPoint) float64 { + if percentage <= 0 { + return float64(curvePoints[0].Value) + } + + if percentage >= 100 { + return float64(curvePoints[len(curvePoints)-1].Value) + } + + // 线性插值 + curveValue := float64(curvePoints[len(curvePoints)-1].Value) + for i := 0; i < len(curvePoints)-1; i++ { + p1, p2 := curvePoints[i], curvePoints[i+1] + if percentage >= float64(p1.Percentage) && percentage <= float64(p2.Percentage) { + x1, x2 := float64(p1.Percentage), float64(p2.Percentage) + y1, y2 := float64(p1.Value), float64(p2.Value) + if x2 == x1 { + curveValue = y1 + } else { + curveValue = y1 + (y2-y1)*(percentage-x1)/(x2-x1) + } + break + } + } + + return curveValue +} + +func matchControllerByEDID(controller *displayBl.Controller, configEDID string) bool { + if configEDID == "" || controller.DeviceEDID == nil { + return false + } + + // 厂商把产品型号作为字符串存于edid中,可以直接读到 + s := string(controller.DeviceEDID) + // 检查配置中的EDID标识是否包含在完整的EDID中 + return strings.Contains(strings.ToLower(s), strings.ToLower(configEDID)) +} + +// 配置验证 +func validateBrightnessCurve(curve BrightnessCurveConfig) error { + if curve.MaxLimit < 0 { + return fmt.Errorf("max_limit must be over 0, got: %d", curve.MaxLimit) + } + + if len(curve.CurvePoints) == 0 { + return fmt.Errorf("curve_points cannot be empty") + } + + // 验证曲线点非递减 + for i := 1; i < len(curve.CurvePoints); i++ { + if curve.CurvePoints[i].Percentage < curve.CurvePoints[i-1].Percentage { + return fmt.Errorf("curve points must be in non-descending order") + } + } + + return nil +} + +func (cm *CurveManager) initFlmCurves(backLightMinValue int32, backlightMidValue int32) { + cm.mu.Lock() + defer cm.mu.Unlock() + + // 保存FLM曲线参数 + cm.backLightMinValue = backLightMinValue + cm.backlightMidValue = backlightMidValue + + // 生成FLM曲线 + cm.generateFlmCurveLocked(backLightMinValue, backlightMidValue) +} + +// generateFlmCurveLocked 生成FLM曲线函数(全局唯一) +// 注意:调用此方法前必须持有写锁 +func (cm *CurveManager) generateFlmCurveLocked(backLightMinValue int32, backlightMidValue int32) { + logger.Debugf("Generating FLM curve function with minValue=%d, midValue=%d", backLightMinValue, backlightMidValue) + + const ( + x1 = 10.0 + power = 2.2 + ) + + y1 := float64(backLightMinValue) + x2 := 100.0 + + // FLM曲线函数:根据百分比和最大亮度计算实际亮度值 + cm.flmCurveFunc = func(percentage float64, maxBrightness int) int32 { + y2 := float64(maxBrightness) + base := x2 - x1 + yAdjusted := y2 - y1 + a := yAdjusted / math.Pow(base, power) + + // 2.2次函数 + func2p2 := func(x float64) int32 { + return int32(a*math.Pow(x-x1, power) + y1) + } + + // 线性函数 + k := (float64(func2p2(float64(backlightMidValue))) - y1) / float64(backlightMidValue-x1) + b := y1 - k*x1 + linearFunc := func(x float64) int32 { + return int32(k*x + b) + } + + // 分段计算 + if percentage <= x1 { + return backLightMinValue + } else if percentage > x1 && percentage <= float64(backlightMidValue) { + return linearFunc(percentage) + } else if percentage > float64(backlightMidValue) && percentage <= x2 { + return func2p2(percentage) + } + return int32(maxBrightness) + } + + logger.Debug("FLM curve function generated") +} + +// generateDefaultCurveFuncLocked 生成默认曲线函数(全局唯一,类似FLM曲线) +// 注意:调用此方法前必须持有写锁 +func (cm *CurveManager) generateDefaultCurveFuncLocked(curve BrightnessCurveConfig) { + logger.Debugf("Generating default curve function with %d points", len(curve.CurvePoints)) + + // 默认曲线函数:根据百分比和最大亮度计算实际亮度值 + cm.defaultCurveFunc = func(percentage float64, maxBrightness int) int32 { + // 使用统一的插值函数 + curveValue := interpolateCurveValue(percentage, curve.CurvePoints) + + // 默认曲线不使用 max_limit,曲线点已经定义了完整的映射关系 + + // 转换为实际亮度值 + return int32(math.Round(curveValue * float64(maxBrightness) / 100.0)) + } + + logger.Debug("Default curve function generated") +} + +// setCustomBrightnessCurves 设置自定义亮度曲线配置(内部方法) +func (cm *CurveManager) setCustomBrightnessCurves(jsonStr string) { + cm.mu.Lock() + defer cm.mu.Unlock() + + cm.limitedCurveFunc = nil + + // 解析配置 + err := cm.parseCustomBrightnessCurves(jsonStr) + if err != nil { + logger.Debugf("Failed to parse custom brightness curves: %v", err) + return + } + + logger.Info("Custom brightness curves set successfully") +} + +// setMaxBrightnessUnlimited 设置最大亮度不受限制(内部方法) +func (cm *CurveManager) setMaxBrightnessUnlimited(enabled bool) { + cm.mu.Lock() + defer cm.mu.Unlock() + + cm.maxBrightnessUnlimited = enabled + logger.Infof("Set max brightness unlimited: %v", enabled) +} + +// isMaxBrightnessUnlimited 检查是否启用最大亮度不受限制(内部方法) +func (cm *CurveManager) isMaxBrightnessUnlimited() bool { + return cm.maxBrightnessUnlimited +} + +// ParseCustomBrightnessCurves 解析自定义亮度曲线配置 +func (cm *CurveManager) parseCustomBrightnessCurves(jsonStr string) error { + var config struct { + BoardName string `json:"boardName"` + Curves []BrightnessCurveConfig `json:"curves"` + } + + // Remove any backslash characters from the input JSON string + jsonStr = strings.ReplaceAll(jsonStr, "\\", "") + + logger.Debugf("Parsing custom brightness curves: %s", jsonStr) + + if err := json.Unmarshal([]byte(jsonStr), &config); err != nil { + return fmt.Errorf("Custom curve failed to parse JSON: %v", err) + } + + // 存储 boardName + cm.mu.Lock() + cm.configBoardName = config.BoardName + cm.mu.Unlock() + + if config.Curves == nil { + return fmt.Errorf("Custom curve json contains nothing") + } + + ctlrs, err := displayBl.List() + if err != nil { + return fmt.Errorf("Custom curve failed to list controllers: %v", err) + } + + for _, curve := range config.Curves { + // 验证 EDID 字段不能为空 + if curve.EDID == "" { + logger.Debug("Skipping curve config with empty EDID") + continue + } + + for _, controller := range ctlrs { + if matchControllerByEDID(controller, curve.EDID) { + logger.Infof("Found matching controller for EDID %s: %s", curve.EDID, controller.Name) + + if err := validateBrightnessCurve(curve); err == nil { + cm.limitedCurveFunc = cm.generateCustomCurveFunc(curve, int32(controller.MaxBrightness)) + cm.maxScale = curve.MaxScale + return nil + } + } + } + } + + logger.Debug("Custom curve config missed all controller") + return nil +} + +func (cm *CurveManager) generateCustomCurveFunc(curve BrightnessCurveConfig, controllerMaxBrightness int32) func(float64) int32 { + return func(percentage float64) int32 { + // 使用统一的插值函数 + curveValue := interpolateCurveValue(percentage, curve.CurvePoints) + + // 应用max_limit限制 + // 注意:这里直接读取全局 _curveManager 的状态 + cm.mu.RLock() + unlimited := cm.maxBrightnessUnlimited + cm.mu.RUnlock() + + if !unlimited && curve.MaxLimit > 0 && curve.MaxLimit < 100 { + curveValue = curveValue * float64(curve.MaxLimit) / 100.0 + } + + // 转换为实际亮度值 + return int32(math.Round(curveValue * float64(controllerMaxBrightness) / 100.0)) + } +} + +// 根据当前设置亮度百分比与控制器,返回由曲线控制的实际亮度 +func (cm *CurveManager) getCurveValue(v float64, c *displayBl.Controller) (int32, bool) { + // 将v转为合适的值,注意精度,不要过早转为整数 + vv := 100 * v + + if cm.curveType == "flm" { + if cm.flmCurveFunc == nil { + logger.Warning("FLM curve function is not initialized") + return int32(v * float64(c.MaxBrightness)), false + } + + return cm.flmCurveFunc(vv, c.MaxBrightness), true + } + + // 如果存在自定义曲线,检查适用性 + if !cm.isDeviceSupportedLocked() || cm.maxBrightnessUnlimited || cm.limitedCurveFunc == nil { + logger.Debugf("Limited curve not working now, maxBrightnessUnlimited=%v, func exist=%v", cm.maxBrightnessUnlimited, cm.limitedCurveFunc != nil) + } else { + return cm.limitedCurveFunc(vv), true + } + + if cm.defaultCurveFunc != nil { + return cm.defaultCurveFunc(vv, c.MaxBrightness), true + } + + // 默认无处理的值 + return int32(v * float64(c.MaxBrightness)), false +} + +// setDeviceBoardName 设置设备板名(内部方法) +func (cm *CurveManager) setDeviceBoardName(boardName string) { + cm.mu.Lock() + defer cm.mu.Unlock() + cm.deviceBoardName = boardName +} + +// setDefaultBrightnessCurve 设置默认亮度曲线(内部方法) +func (cm *CurveManager) setDefaultBrightnessCurve(jsonStr string) { + cm.mu.Lock() + defer cm.mu.Unlock() + + cm.defaultCurveFunc = nil + + err := cm.parseDefaultBrightnessCurve(jsonStr) + if err != nil { + logger.Warningf("Failed to parse default brightness curve: %v", err) + return + } + + logger.Info("Default brightness curve set successfully") +} + +// ParseDefaultBrightnessCurve 解析默认亮度曲线配置 +func (cm *CurveManager) parseDefaultBrightnessCurve(jsonStr string) error { + // Remove any backslash characters from the input JSON string + jsonStr = strings.ReplaceAll(jsonStr, "\\", "") + + logger.Debugf("Parsing default brightness curve: %s", jsonStr) + + var curve BrightnessCurveConfig + if err := json.Unmarshal([]byte(jsonStr), &curve); err != nil { + return fmt.Errorf("failed to parse JSON: %v", err) + } + + // 验证曲线配置 + if err := validateBrightnessCurve(curve); err != nil { + return fmt.Errorf("invalid curve config: %v", err) + } + + cm.generateDefaultCurveFuncLocked(curve) + + logger.Infof("Default brightness curve parsed successfully: %d points", len(curve.CurvePoints)) + + return nil +} + +// isDeviceSupported 检查设备是否支持(内部方法) +func (cm *CurveManager) isDeviceSupported() bool { + cm.mu.RLock() + defer cm.mu.RUnlock() + return cm.isDeviceSupportedLocked() +} + +// isDeviceSupportedLocked 检查设备是否支持(需要持有锁) +func (cm *CurveManager) isDeviceSupportedLocked() bool { + if cm.configBoardName != "" && !strings.Contains(strings.ToLower(cm.configBoardName), strings.ToLower(cm.deviceBoardName)) { + return false + } + + return true +} + +// isMaxLimitCurveSupported 检查是否支持最大亮度限制曲线(内部方法) +func (cm *CurveManager) isMaxLimitCurveSupported() bool { + cm.mu.RLock() + defer cm.mu.RUnlock() + + if !cm.isDeviceSupportedLocked() { + return false + } + + // 检查是否有任何控制器在自定义曲线映射中存在 + return cm.limitedCurveFunc != nil +} + +// getCurrentMaxScale 获取当前最大缩放值(内部方法) +func (cm *CurveManager) getCurrentMaxScale() int32 { + cm.mu.RLock() + defer cm.mu.RUnlock() + + // 如果没有自定义曲线配置,返回默认值 100(表示100%) + if cm.limitedCurveFunc == nil || !cm.isDeviceSupportedLocked() { + return DefaultScale + } + + if cm.maxScale > DefaultScale { + return cm.maxScale + } + + // 如果没有设置 MaxScale,返回默认值 100(表示100%) + return DefaultScale +} + +// setCurveType 设置曲线类型(内部方法) +func (cm *CurveManager) setCurveType(curveType string) { + cm.mu.Lock() + defer cm.mu.Unlock() + cm.curveType = curveType + logger.Infof("Set curve type: %s", curveType) +} diff --git a/display1/display_dbusutil.go b/display1/display_dbusutil.go index 318891cf6..7b3363f82 100644 --- a/display1/display_dbusutil.go +++ b/display1/display_dbusutil.go @@ -170,6 +170,19 @@ func (v *Manager) emitPropChangedMaxBacklightBrightness(value uint32) error { return v.service.EmitPropertyChanged(v, "MaxBacklightBrightness", value) } +func (v *Manager) setPropCurveMaxScale(value int32) (changed bool) { + if v.CurveMaxScale != value { + v.CurveMaxScale = value + v.emitPropChangedCurveMaxScale(value) + return true + } + return false +} + +func (v *Manager) emitPropChangedCurveMaxScale(value int32) error { + return v.service.EmitPropertyChanged(v, "CurveMaxScale", value) +} + func (v *Manager) setPropColorTemperatureEnabled(value bool) (changed bool) { if v.ColorTemperatureEnabled != value { v.ColorTemperatureEnabled = value diff --git a/display1/manager.go b/display1/manager.go index 8bb069eea..a73e17329 100644 --- a/display1/manager.go +++ b/display1/manager.go @@ -89,6 +89,16 @@ const ( DSettingsKeyRotateScreenTimeDelay = "rotateScreenTimeDelay" DSettingsKeyCustomDisplayMode = "customDisplayMode" + // 亮度曲线配置 + DSettingsKeyBacklightCurveType = "backlight-curve-type" + DSettingsKeyBacklightMinValue = "backlight-curve-min-value" + DSettingsKeyBacklightMidValue = "backlight-curve-mid-value" + DSettingsKeyBrightnessPercentage = "brightness-percentage" + DSettingsKeyCanSetBrightnessDelay = "can-set-brightness-delay-interval" + DSettingsKeyCustomBrightnessCurves = "custom-brightness-curves" + DSettingsKeyDefaultBrightnessCurve = "default-brightness-curve" + DSettingsKeyMaxBrightnessUnlimited = "max-brightness-unlimited" + // 渐变亮度配置 DSettingsKeyABTransitionEnabled = "transition-enabled" DSettingsKeyABTransitionDuration = "transition-duration" @@ -255,8 +265,9 @@ type Manager struct { isVM bool // 自动亮度相关属性 - AutoBrightnessEnabled bool `prop:"access:rw"` - AutoBrightnessSupported bool `prop:"access:r"` + AutoBrightnessEnabled bool `prop:"access:rw"` + AutoBrightnessSupported bool `prop:"access:r"` + CurveMaxScale int32 `prop:"access:r"` // 自动亮度管理器 autoBrightnessManager *AutoBrightnessManager @@ -264,6 +275,11 @@ type Manager struct { // 亮度渐变管理器 brightnessTransition *BrightnessTransition + // 背光曲线配置 + backlightCurveType string + backlightMinValue int32 + backlightMidValue int32 + powerSaving bool } @@ -494,6 +510,18 @@ func (m *Manager) initDConfig(sysBus *dbus.Conn) { m.getCurrentCustomId() case DSettingsKeyRotateScreenTimeDelay: m.getRotateScreenTimeDelay() + case DSettingsKeyCustomBrightnessCurves: + m.getCustomBrightnessCurves() + case DSettingsKeyDefaultBrightnessCurve: + m.getDefaultBrightnessCurve() + case DSettingsKeyBacklightMinValue, DSettingsKeyBacklightMidValue: + m.getBacklightMinValue() + m.getBacklightMidValue() + brightness.InitFlmCurves(m.backlightMinValue, m.backlightMidValue) + case DSettingsKeyBacklightCurveType: + m.getBacklightCurveType() + case DSettingsKeyMaxBrightnessUnlimited: + m.getMaxBrightnessUnlimited() default: break } @@ -510,6 +538,14 @@ func (m *Manager) loadInitialConfigValues() { m.getCurrentCustomId() m.getRotateScreenTimeDelay() // ColorTemperatureManual will be loaded from user config via applyColorTempConfig() + m.getBacklightCurveType() + m.getBacklightMinValue() + m.getBacklightMidValue() + m.getCustomBrightnessCurves() + m.getDefaultBrightnessCurve() + m.getMaxBrightnessUnlimited() + // 初始化FLM曲线 + brightness.InitFlmCurves(m.backlightMinValue, m.backlightMidValue) } func (m *Manager) getDefaultTemperatureManual() { @@ -629,6 +665,107 @@ func (m *Manager) getBrightness() string { return v.Value().(string) } +func (m *Manager) getBacklightCurveType() { + v, err := m.displayConfigMgr.Value(0, DSettingsKeyBacklightCurveType) + if err != nil { + logger.Warning(err) + m.backlightCurveType = "default" + brightness.SetCurveType("default") + return + } + m.backlightCurveType = v.Value().(string) + logger.Info("Backlight Curve Type:", m.backlightCurveType) + brightness.SetCurveType(m.backlightCurveType) +} + +func (m *Manager) getBacklightMinValue() { + v, err := m.displayConfigMgr.Value(0, DSettingsKeyBacklightMinValue) + if err != nil { + logger.Warning(err) + m.backlightMinValue = 4 + return + } + switch vType := v.Value().(type) { + case int64: + m.backlightMinValue = int32(vType) + case float64: + m.backlightMinValue = int32(vType) + default: + m.backlightMinValue = 4 + } + logger.Info("Backlight min value:", m.backlightMinValue) +} + +func (m *Manager) getBacklightMidValue() { + v, err := m.displayConfigMgr.Value(0, DSettingsKeyBacklightMidValue) + if err != nil { + logger.Warning(err) + m.backlightMidValue = 50 + return + } + switch vType := v.Value().(type) { + case int64: + m.backlightMidValue = int32(vType) + case float64: + m.backlightMidValue = int32(vType) + default: + m.backlightMidValue = 50 + } + logger.Info("Backlight mid value:", m.backlightMidValue) +} + +func (m *Manager) getCustomBrightnessCurves() { + v, err := m.displayConfigMgr.Value(0, DSettingsKeyCustomBrightnessCurves) + if err != nil { + logger.Warning(err) + return + } + + jsonStr, ok := v.Value().(string) + if !ok { + logger.Warning("Custom brightness curves configuration is not a string") + return + } + + brightness.SetCustomBrightnessCurves(jsonStr) + // 更新当前最大缩放值 + curveMaxScale := brightness.GetCurrentMaxScale() + + m.PropsMu.Lock() + m.setPropCurveMaxScale(curveMaxScale) + m.PropsMu.Unlock() +} + +func (m *Manager) getDefaultBrightnessCurve() { + v, err := m.displayConfigMgr.Value(0, DSettingsKeyDefaultBrightnessCurve) + if err != nil { + logger.Warning(err) + return + } + + jsonStr, ok := v.Value().(string) + if !ok { + logger.Warning("Default brightness curve configuration is not a string") + return + } + + brightness.SetDefaultBrightnessCurve(jsonStr) +} + +func (m *Manager) getMaxBrightnessUnlimited() { + v, err := m.displayConfigMgr.Value(0, DSettingsKeyMaxBrightnessUnlimited) + if err != nil { + logger.Warning(err) + return + } + enabled, ok := v.Value().(bool) + if !ok { + logger.Warning("Max brightness unlimited configuration is not a bool") + return + } + brightness.SetMaxBrightnessUnlimited(enabled) +} + // 初始化系统级 display 服务的信号处理 func (m *Manager) initSysDisplay() { m.sysDisplay.InitSignalExt(m.sysSigLoop, true) diff --git a/misc/dsg-configs/org.deepin.Display.json b/misc/dsg-configs/org.deepin.Display.json index 70795f3f8..58abb139e 100644 --- a/misc/dsg-configs/org.deepin.Display.json +++ b/misc/dsg-configs/org.deepin.Display.json @@ -204,6 +204,78 @@ "description": "渐变过程中每次调整亮度的时间间隔(毫秒)", "permissions": "readwrite", "visibility": "private" + }, + "backlight-curve-type": { + "value": "default", + "serial": 0, + "flags": ["global"], + "name": "backlightCurveType", + "description": "背光曲线类型", + "permissions": "readonly", + "visibility": "private" + }, + "backlight-curve-min-value": { + "value": 4, + "serial": 0, + "flags": ["global"], + "name": "backlightMinValue", + "description": "背光曲线起始点", + "permissions": "readonly", + "visibility": "private" + }, + "backlight-curve-mid-value": { + "value": 50, + "serial": 0, + "flags": ["global"], + "name": "backlightMidValue", + "description": "背光曲线中间点", + "permissions": "readonly", + "visibility": "private" + }, + "brightness-percentage": { + "value": 100, + "serial": 0, + "flags": ["global"], + "name": "brightnessPercentage", + "description": "实际亮度百分比, 取值范围:[50, 100]", + "permissions": "readwrite", + "visibility": "private" + }, + "can-set-brightness-delay-interval": { + "value": 0, + "serial": 0, + "flags": ["global"], + "name": "canSetBrightnessDelayInterval", + "description": "新增显示器延时获取是否支持亮度调节", + "permissions": "readwrite", + "visibility": "private" + }, + "max-brightness-unlimited": { + "value": false, + "serial": 0, + "flags": ["global"], + "name": "maxBrightnessUnlimited", + "description": "内置屏幕最大亮度不受限制开关", + "permissions": "readwrite", + "visibility": "private" + }, + "custom-brightness-curves": { + "value": "", + "serial": 0, + "flags": ["global"], + "name": "customBrightnessCurves", + "description": "内置屏亮度曲线与最大限制", + "permissions": "readwrite", + "visibility": "private" + }, + "default-brightness-curve": { + "value": "", + "serial": 0, + "flags": ["global"], + "name": "defaultBrightnessCurve", + "description": "默认亮度曲线配置(不依赖硬件信息)", + "permissions": "readwrite", + "visibility": "private" } } } From da746ab6802ca1b0b9fff87cfc5e6e813f94d33a Mon Sep 17 00:00:00 2001 From: fuleyi Date: Thu, 7 May 2026 13:17:02 +0800 Subject: [PATCH 3/8] =?UTF-8?q?feat:=20=E5=A2=9E=E5=BC=BA=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E4=BA=AE=E5=BA=A6=E5=8A=9F=E8=83=BD=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=9B=B2=E7=BA=BF=E6=98=A0=E5=B0=84=E5=92=8C=E5=8D=A1=E5=B0=94?= =?UTF-8?q?=E6=9B=BC=E6=BB=A4=E6=B3=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 添加自动亮度曲线配置支持,允许自定义环境光到亮度的映射关系 2. 引入卡尔曼滤波器处理环境光传感器数据,减少噪声干扰 3. 新增亮度变化阈值配置,避免微小亮度波动触发调节 4. 优化传感器数据处理流程,支持推送模式和缓存机制 5. 重构代码结构,分离滤波逻辑和亮度计算逻辑 Log: 自动亮度功能新增曲线映射和智能滤波,调节更平滑精准 Change-Id: I344f167fdb553e7cda137a78951bb6c764312aad --- display1/auto_brightness.go | 331 ++++++++++++++---- display1/brightness/curve.go | 154 ++++++++ display1/brightness/kalman_filter.go | 300 ++++++++++++++++ display1/manager.go | 7 + display1/sensor_proxy.go | 158 +++++++-- .../org.deepin.Display.AutoBrightness.json | 53 ++- 6 files changed, 893 insertions(+), 110 deletions(-) create mode 100644 display1/brightness/kalman_filter.go diff --git a/display1/auto_brightness.go b/display1/auto_brightness.go index 11aacba2d..9e9193c56 100644 --- a/display1/auto_brightness.go +++ b/display1/auto_brightness.go @@ -12,6 +12,8 @@ import ( "github.com/godbus/dbus/v5" configManager "github.com/linuxdeepin/go-dbus-factory/org.desktopspec.ConfigManager" + + "github.com/linuxdeepin/dde-daemon/display1/brightness" ) // AutoBrightnessConfig 自动亮度配置结构体 @@ -20,9 +22,13 @@ type AutoBrightnessConfig struct { Sensitivity float64 `json:"sensitivity"` // 敏感度 (0.1-3.0) PollingInterval int `json:"polling_interval"` // 轮询间隔(秒) (1-60) ChangeThreshold float64 `json:"change_threshold"` // 变化阈值 (1.0-50.0) + BrightnessChangeThreshold float64 `json:"brightness_change_threshold"` // 亮度变化阈值 (0.01-1.0) ManualOverrideDuration int `json:"manual_override_duration"` // 手动调节暂停时间(秒) (60-1800) ManualAdjustDisablesAutoMode bool `json:"manual_adjust_disables_auto_mode"` // 手动调节是否禁用自动模式 UseTransition bool `json:"use_transition"` // 自动调节时是否使用渐变效果 + KalmanProcessNoise float64 `json:"kalman_process_noise"` // 卡尔曼滤波器过程噪声协方差 Q + KalmanMeasurementNoise float64 `json:"kalman_measurement_noise"` // 卡尔曼滤波器测量噪声协方差 R + KalmanWindowSize int `json:"kalman_window_size"` // 卡尔曼滤波器窗口大小 } // DefaultAutoBrightnessConfig 默认自动亮度配置 @@ -31,41 +37,47 @@ var DefaultAutoBrightnessConfig = AutoBrightnessConfig{ Sensitivity: 0.5, PollingInterval: 5, ChangeThreshold: 20.0, + BrightnessChangeThreshold: 0.01, ManualOverrideDuration: 300, ManualAdjustDisablesAutoMode: true, UseTransition: true, + KalmanProcessNoise: 0.01, + KalmanMeasurementNoise: 0.1, + KalmanWindowSize: 10, } // DSettings键名已在manager.go中定义 // AutoBrightnessManager 自动亮度管理器 type AutoBrightnessManager struct { - // 依赖注入 - 复用现有Manager manager *Manager - // 独立组件 + sensorClient *SensorProxyClient - // 配置管理 + config AutoBrightnessConfig configManager configManager.Manager sysBus *dbus.Conn - // 状态管理 + enabled bool supported bool manualOverride time.Time lastLightLevel int lastAdjustTime time.Time lastBrightness float64 - // 轮询控制 + pollInterval time.Duration - stopChan chan struct{} // 停止信号(通过写入来停止goroutine) + stopChan chan struct{} ticker *time.Ticker - pollingWg sync.WaitGroup // 等待轮询 goroutine 退出 + pollingWg sync.WaitGroup + + kalmanFilter *brightness.AdaptiveKalmanFilter + + systemAdjusting bool + running bool + polling bool + // 同步控制 mutex sync.RWMutex - // 运行状态 - running bool - polling bool // 标记是否有轮询 goroutine 在运行 - // 系统调整标志(用于区分系统自动调整和用户手动调整) - systemAdjusting bool + // 重试和降级配置 maxRetries int retryInterval time.Duration @@ -86,51 +98,68 @@ func NewAutoBrightnessManager() *AutoBrightnessManager { // Initialize 初始化自动亮度管理器 func (abm *AutoBrightnessManager) Initialize(manager *Manager) error { - abm.mutex.Lock() - defer abm.mutex.Unlock() if manager == nil { return errors.New("manager cannot be nil") } + + logger.Info("[AutoBrightness] Initializing AutoBrightnessManager") + + abm.mutex.Lock() abm.manager = manager abm.sysBus = manager.sysBus - logger.Info("[AutoBrightness] Initializing AutoBrightnessManager") - // 初始化配置管理器 + abm.mutex.Unlock() + err := abm.initConfigManager() if err != nil { logger.Warning("[AutoBrightness] Failed to initialize config manager:", err) } - // 检查是否有内置显示器 - builtinMonitor := abm.manager.getBuiltinMonitor() + + builtinMonitor := manager.getBuiltinMonitor() if builtinMonitor == nil { + abm.mutex.Lock() abm.supported = false - return fmt.Errorf("no builtin monitor found (total monitors: %d)", len(abm.manager.getConnectedMonitors())) + abm.mutex.Unlock() + return fmt.Errorf("no builtin monitor found (total monitors: %d)", len(manager.getConnectedMonitors())) } - // 检查是否可以设置亮度 - canSet, _ := abm.manager.CanSetBrightness(builtinMonitor.Name) + + canSet, _ := manager.CanSetBrightness(builtinMonitor.Name) if !canSet { + abm.mutex.Lock() abm.supported = false + abm.mutex.Unlock() return fmt.Errorf("cannot set brightness for builtin monitor: %s", builtinMonitor.Name) } - // 创建传感器客户端(但不连接) - abm.sensorClient = NewSensorProxyClient(abm.manager.sysBus) - // 检查传感器是否可用(不连接,只检查服务是否存在) - // 实际的连接和传感器声明将在Start()时进行 + + sensorClient := NewSensorProxyClient(manager.sysBus) + + abm.mutex.Lock() + abm.sensorClient = sensorClient + abm.mutex.Unlock() + err = abm.checkSensorAvailability() if err != nil { + abm.mutex.Lock() abm.supported = false + abm.mutex.Unlock() logger.Warning("[AutoBrightness] Sensor not available:", err) return fmt.Errorf("sensor not available: %w", err) } - abm.supported = true - // 设置服务状态变化回调(在连接前设置,这样Start时就可以使用) - abm.sensorClient.SetServiceChangeCallback(abm.onServiceChange) - // 加载配置 - err = abm.loadConfig() + + sensorClient.SetServiceChangeCallback(abm.onServiceChange) + sensorClient.SetLightLevelChangeCallback(abm.onLightLevelChange) + + abm.mutex.Lock() + config, err := abm.getConfig() if err != nil { logger.Warning("[AutoBrightness] Failed to load config, using default:", err) - abm.config = DefaultAutoBrightnessConfig + config = DefaultAutoBrightnessConfig } + abm.config = config + abm.supported = true + abm.applyKalmanFilterConfig() abm.pollInterval = time.Duration(abm.config.PollingInterval) * time.Second + abm.mutex.Unlock() + logger.Info("[AutoBrightness] AutoBrightnessManager initialized successfully") return nil } @@ -357,23 +386,25 @@ func (abm *AutoBrightnessManager) OnConfigChanged(config AutoBrightnessConfig) { needStart := config.Enabled && !oldEnabled && !abm.running needStop := !config.Enabled && oldEnabled && abm.running needRestart := config.Enabled == oldEnabled && abm.running - // 检查是否需要立即应用新配置(仅敏感度变化需要立即调整) - // ChangeThreshold 只是判断门槛,不影响亮度计算,无需立即调整 + sensitivityChanged := oldSensitivity != config.Sensitivity needImmediateAdjust := sensitivityChanged && abm.running && config.Enabled - // 如果需要重启轮询(配置变更但状态未变),在持有锁的情况下重启 + + abm.applyKalmanFilterConfig() + if needRestart { abm.stopPolling() abm.startPolling() } + abm.mutex.Unlock() - // 在锁外执行状态变更操作 + if needStart { abm.Start() } else if needStop { abm.Stop() } - // 如果敏感度变化,立即触发一次亮度调整 + if needImmediateAdjust { logger.Infof("[AutoBrightness] Sensitivity changed from %.2f to %.2f, triggering immediate adjustment", oldSensitivity, config.Sensitivity) go abm.adjustBrightnessOnce() @@ -382,6 +413,25 @@ func (abm *AutoBrightnessManager) OnConfigChanged(config AutoBrightnessConfig) { config.Enabled, config.Sensitivity, config.PollingInterval, config.ChangeThreshold) } +// applyKalmanFilterConfig 应用卡尔曼滤波器配置 +func (abm *AutoBrightnessManager) applyKalmanFilterConfig() { + if abm.kalmanFilter == nil { + abm.kalmanFilter = brightness.NewAdaptiveKalmanFilter( + abm.config.KalmanProcessNoise, + abm.config.KalmanMeasurementNoise, + abm.config.KalmanWindowSize, + ) + logger.Debug("[AutoBrightness] Kalman filter created with config params") + return + } + + abm.kalmanFilter.SetProcessNoise(abm.config.KalmanProcessNoise) + abm.kalmanFilter.SetMeasurementNoise(abm.config.KalmanMeasurementNoise) + abm.kalmanFilter.SetWindowSize(abm.config.KalmanWindowSize) + logger.Debugf("[AutoBrightness] Kalman filter params updated: Q=%.4f, R=%.4f, window=%d", + abm.config.KalmanProcessNoise, abm.config.KalmanMeasurementNoise, abm.config.KalmanWindowSize) +} + // IsSupported 检查是否支持自动亮度 func (abm *AutoBrightnessManager) IsSupported() bool { abm.mutex.RLock() @@ -601,36 +651,34 @@ func (abm *AutoBrightnessManager) pollLightLevel() { } logger.Info("[AutoBrightness] Re-claimed light sensor after manual override period") } - // 从传感器获取光照强度 - lightLevel, err := abm.sensorClient.GetLightLevel() + // 从传感器获取缓存的原始光照强度 + rawLightLevel, err := abm.sensorClient.GetCachedLightLevel() if err != nil { - logger.Warningf("[AutoBrightness] Failed to get light level: %v", err) + logger.Debugf("[AutoBrightness] Failed to get cached light level: %v", err) return } - abm.processLightChange(lightLevel) + + // 处理原始值(包括滤波和亮度调整) + abm.processLightChange(rawLightLevel) } // onServiceChange 服务状态变化回调 func (abm *AutoBrightnessManager) onServiceChange(available bool) { + var shouldStart bool + abm.mutex.Lock() - defer abm.mutex.Unlock() + if available { logger.Info("[AutoBrightness] SensorProxy service became available") - // 服务恢复,重新检查传感器 if abm.sensorClient != nil { hasLight, err := abm.sensorClient.HasAmbientLight() if err == nil && hasLight { abm.supported = true - if abm.config.Enabled && !abm.running { - abm.mutex.Unlock() - abm.Start() - abm.mutex.Lock() - } + shouldStart = abm.config.Enabled && !abm.running } } } else { logger.Warning("[AutoBrightness] SensorProxy service became unavailable") - // 服务不可用,停止功能 if abm.running { abm.stopPolling() abm.running = false @@ -638,10 +686,31 @@ func (abm *AutoBrightnessManager) onServiceChange(available bool) { } abm.supported = false } - // 更新Manager属性 + if abm.manager != nil { abm.manager.setPropAutoBrightnessSupported(abm.supported) } + + abm.mutex.Unlock() + + if shouldStart { + abm.Start() + } +} + +// onLightLevelChange 光照值变化回调(推送模式) +func (abm *AutoBrightnessManager) onLightLevelChange(rawLightLevel int) { + abm.mutex.Lock() + running := abm.running + polling := abm.polling + inManualOverride := abm.isInManualOverride() + abm.mutex.Unlock() + + if !running || !polling || inManualOverride { + return + } + + abm.processLightChange(rawLightLevel) } // adjustBrightnessOnce 立即执行一次亮度调整(用于配置变化时) @@ -678,37 +747,56 @@ func (abm *AutoBrightnessManager) adjustBrightnessOnce() { abm.mutex.Unlock() } -// processLightChange 处理环境光变化 -func (abm *AutoBrightnessManager) processLightChange(lightLevel int) { +// processLightChange 处理环境光变化(包括卡尔曼滤波和亮度调整) +func (abm *AutoBrightnessManager) processLightChange(rawLightLevel int) { abm.mutex.Lock() + if !abm.running { abm.mutex.Unlock() return } - // 计算目标亮度 - targetBrightness := abm.calculateTargetBrightness(lightLevel) + + // 应用卡尔曼滤波器处理原始光值 + if abm.kalmanFilter == nil { + logger.Warning("[AutoBrightness] Kalman filter is nil, skipping light change processing") + abm.mutex.Unlock() + return + } + + estimate := abm.kalmanFilter.Update(float64(rawLightLevel)) + filteredLightLevel := int(estimate) + logger.Infof("[AutoBrightness] Kalman filter: raw=%d lux -> filtered=%d lux", rawLightLevel, filteredLightLevel) + + // 计算目标亮度(使用滤波后的值) + targetBrightness := abm.calculateTargetBrightness(filteredLightLevel) + // 检查是否应该调节亮度(包含所有阈值和频率控制逻辑) - if !abm.shouldAdjustBrightness(lightLevel, targetBrightness) { + if !abm.shouldAdjustBrightness(filteredLightLevel, targetBrightness) { abm.mutex.Unlock() return } + // 释放锁后再设置亮度(避免在渐变时持有锁) abm.mutex.Unlock() + // 设置亮度(不重试,下次轮询会自动重试) err := abm.setBrightness(targetBrightness) if err != nil { - logger.Warningf("[AutoBrightness] Failed to set brightness (light=%d, target=%.1f%%): %v", - lightLevel, targetBrightness*100, err) + logger.Warningf("[AutoBrightness] Failed to set brightness (raw=%d, filtered=%d, target=%.1f%%): %v", + rawLightLevel, filteredLightLevel, targetBrightness*100, err) return } + // 更新状态 abm.mutex.Lock() now := time.Now() - abm.lastLightLevel = lightLevel + abm.lastLightLevel = filteredLightLevel abm.lastBrightness = targetBrightness abm.lastAdjustTime = now abm.mutex.Unlock() - logger.Infof("[AutoBrightness] Brightness adjusted: light=%d lux -> brightness=%.1f%%", lightLevel, targetBrightness*100) + + logger.Infof("[AutoBrightness] Brightness adjusted: raw=%d lux -> filtered=%d lux -> brightness=%.1f%%", + rawLightLevel, filteredLightLevel, targetBrightness*100) } // isInManualOverride 检查是否在手动调节暂停期间 @@ -723,17 +811,38 @@ func (abm *AutoBrightnessManager) isInManualOverride() bool { // calculateTargetBrightness 计算目标亮度 func (abm *AutoBrightnessManager) calculateTargetBrightness(lightLevel int) float64 { + // 优先使用曲线配置 + if brightness.HasAutoBrightnessCurve() { + br := brightness.GetAutoBrightnessValue(lightLevel) + if br >= 0 { + // 约束到有效范围 + if br < 0.0 { + br = 0.0 + } else if br > 1.0 { + br = 1.0 + } + // 设置最小亮度,避免屏幕过暗 + minBrightness := 0.1 // 10% 最小亮度 + if br < minBrightness { + br = minBrightness + } + return br + } + } + // 应用敏感度调整 adjustedLevel := float64(lightLevel) * abm.config.Sensitivity - // 简单线性映射 (后续可扩展为更复杂的曲线) - // 假设环境光范围 0-1024,映射到亮度 0.0-1.0,通过敏感度调整变化速率 + + // 简单线性映射 brightness := adjustedLevel / 1024.0 + // 约束到有效范围 if brightness < 0.0 { brightness = 0.0 } else if brightness > 1.0 { brightness = 1.0 } + // 设置最小亮度,避免屏幕过暗 minBrightness := 0.1 // 10% 最小亮度 if brightness < minBrightness { @@ -742,14 +851,10 @@ func (abm *AutoBrightnessManager) calculateTargetBrightness(lightLevel int) floa return brightness } -// shouldAdjustBrightness 检查是否应该调节亮度(变化阈值和频率控制) -// 注意:此函数需要读取多个字段,调用者应该持有至少读锁 +// shouldAdjustBrightness 检查是否应该调节亮度(变化阈值和频率控制) func (abm *AutoBrightnessManager) shouldAdjustBrightness(lightLevel int, targetBrightness float64) bool { now := time.Now() - // 检查是否在手动调节暂停期间 - if abm.isInManualOverride() { - return false - } + // 检查环境光变化阈值 if abm.lastLightLevel >= 0 { lightChange := math.Abs(float64(lightLevel - abm.lastLightLevel)) @@ -757,6 +862,7 @@ func (abm *AutoBrightnessManager) shouldAdjustBrightness(lightLevel int, targetB return false } } + // 检查调节频率限制 if !abm.lastAdjustTime.IsZero() { timeSinceLastAdjust := now.Sub(abm.lastAdjustTime) @@ -765,11 +871,11 @@ func (abm *AutoBrightnessManager) shouldAdjustBrightness(lightLevel int, targetB return false } } + // 检查亮度变化是否足够大 if abm.lastBrightness >= 0 { brightnessChange := math.Abs(targetBrightness - abm.lastBrightness) - minBrightnessChange := 0.05 // 5% 最小变化 - if brightnessChange < minBrightnessChange { + if brightnessChange < abm.config.BrightnessChangeThreshold { return false } } @@ -870,7 +976,16 @@ func (config *AutoBrightnessConfig) Validate() error { return errors.New("change threshold too small") } if config.ManualOverrideDuration < 1 { - return errors.New("manual override duration to small") + return errors.New("manual override duration too small") + } + if config.KalmanProcessNoise <= 0 || config.KalmanProcessNoise > 100 { + return errors.New("kalman process noise must be positive and less than 100") + } + if config.KalmanMeasurementNoise <= 0 || config.KalmanMeasurementNoise > 100 { + return errors.New("kalman measurement noise must be positive and less than 100") + } + if config.KalmanWindowSize < 2 || config.KalmanWindowSize > 100 { + return errors.New("kalman window size must be between 2 and 100") } return nil } @@ -959,6 +1074,15 @@ func (abm *AutoBrightnessManager) getConfig() (AutoBrightnessConfig, error) { logger.Warning("[AutoBrightness] Config convert faild, using default changeThreshold") config.ChangeThreshold = DefaultAutoBrightnessConfig.ChangeThreshold } + + // BrightnessChangeThreshold + if val, err := abm.configManager.Value(0, DSettingsKeyABBrightnessChangeThreshold); err == nil { + config.BrightnessChangeThreshold = val.Value().(float64) + } else { + logger.Warning("[AutoBrightness] Config convert faild, using default brightnessChangeThreshold") + config.BrightnessChangeThreshold = DefaultAutoBrightnessConfig.BrightnessChangeThreshold + } + // PollingInterval if val, err := abm.configManager.Value(0, DSettingsKeyABPollingInterval); err == nil { switch v := val.Value().(type) { @@ -1009,6 +1133,75 @@ func (abm *AutoBrightnessManager) getConfig() (AutoBrightnessConfig, error) { logger.Warning("[AutoBrightness] Config convert faild, using default useTransition") config.UseTransition = DefaultAutoBrightnessConfig.UseTransition } + + // Curve + if val, err := abm.configManager.Value(0, DSettingsKeyABCurve); err == nil { + itemList, ok := val.Value().([]dbus.Variant) + if ok && len(itemList) > 0 { + var points []brightness.AutoBrightnessCurvePoint + for _, item := range itemList { + pointMap, ok := item.Value().(map[string]dbus.Variant) + if !ok { + continue + } + var point brightness.AutoBrightnessCurvePoint + if luxVal, ok := pointMap["lux"]; ok { + switch v := luxVal.Value().(type) { + case int64: + point.Lux = int(v) + case float64: + point.Lux = int(v) + } + } + if brVal, ok := pointMap["br"]; ok { + switch v := brVal.Value().(type) { + case int64: + point.Br = float64(v) + case float64: + point.Br = v + } + } + points = append(points, point) + } + brightness.SetAutoBrightnessCurveFromPoints(points) + } else { + logger.Debug("[AutoBrightness] Curve config is empty, using default linear mapping") + } + } else { + logger.Debug("[AutoBrightness] No curve config found, using default linear mapping") + } + + // KalmanProcessNoise + if val, err := abm.configManager.Value(0, DSettingsKeyABKalmanProcessNoise); err == nil { + config.KalmanProcessNoise = val.Value().(float64) + } else { + logger.Warning("[AutoBrightness] Config convert failed, using default kalmanProcessNoise") + config.KalmanProcessNoise = DefaultAutoBrightnessConfig.KalmanProcessNoise + } + + // KalmanMeasurementNoise + if val, err := abm.configManager.Value(0, DSettingsKeyABKalmanMeasurementNoise); err == nil { + config.KalmanMeasurementNoise = val.Value().(float64) + } else { + logger.Warning("[AutoBrightness] Config convert failed, using default kalmanMeasurementNoise") + config.KalmanMeasurementNoise = DefaultAutoBrightnessConfig.KalmanMeasurementNoise + } + + // KalmanWindowSize + if val, err := abm.configManager.Value(0, DSettingsKeyABKalmanWindowSize); err == nil { + switch v := val.Value().(type) { + case int64: + config.KalmanWindowSize = int(v) + case float64: + config.KalmanWindowSize = int(v) + default: + config.KalmanWindowSize = DefaultAutoBrightnessConfig.KalmanWindowSize + } + } else { + logger.Warning("[AutoBrightness] Config convert failed, using default kalmanWindowSize") + config.KalmanWindowSize = DefaultAutoBrightnessConfig.KalmanWindowSize + } + // 验证配置有效性 logger.Debugf("[AutoBrightness] Apply config: %v", config) err := config.Validate() diff --git a/display1/brightness/curve.go b/display1/brightness/curve.go index 0ab05e750..e09b1129d 100644 --- a/display1/brightness/curve.go +++ b/display1/brightness/curve.go @@ -24,6 +24,12 @@ type BrightnessPoint struct { Value int32 `json:"value"` // 对应的亮度百分比 (0-100),表示最大亮度的百分比 } +// AutoBrightnessCurvePoint 自动亮度曲线控制点 +type AutoBrightnessCurvePoint struct { + Lux int `json:"lux"` // 光感值 + Br float64 `json:"br"` // 亮度百分比 (0-100) +} + // 自定义亮度曲线配置 type BrightnessCurveConfig struct { EDID string `json:"edid,omitempty"` // 屏幕EDID标识(可选,默认曲线不需要) @@ -60,6 +66,10 @@ type CurveManager struct { // FLM曲线参数 backLightMinValue int32 backlightMidValue int32 + + // 自动亮度曲线 + autoBrightnessCurve []AutoBrightnessCurvePoint + autoBrightnessCurveFunc func(lux int) float64 } // 全局 CurveManager 实例 @@ -488,3 +498,147 @@ func (cm *CurveManager) setCurveType(curveType string) { cm.curveType = curveType logger.Infof("Set curve type: %s", curveType) } + +// SetAutoBrightnessCurve 设置自动亮度曲线配置(JSON字符串) +func SetAutoBrightnessCurve(jsonStr string) { + _curveManager.setAutoBrightnessCurve(jsonStr) +} + +// SetAutoBrightnessCurveFromPoints 设置自动亮度曲线配置(数组) +func SetAutoBrightnessCurveFromPoints(points []AutoBrightnessCurvePoint) { + _curveManager.setAutoBrightnessCurveFromPoints(points) +} + +// GetAutoBrightnessValue 根据光照值获取目标亮度百分比 (0-1) +func GetAutoBrightnessValue(lux int) float64 { + return _curveManager.getAutoBrightnessValue(lux) +} + +// HasAutoBrightnessCurve 检查是否配置了自动亮度曲线 +func HasAutoBrightnessCurve() bool { + return _curveManager.hasAutoBrightnessCurve() +} + +// setAutoBrightnessCurve 设置自动亮度曲线(内部方法,从JSON字符串解析) +func (cm *CurveManager) setAutoBrightnessCurve(jsonStr string) { + cm.mu.Lock() + defer cm.mu.Unlock() + + cm.autoBrightnessCurveFunc = nil + cm.autoBrightnessCurve = nil + + if jsonStr == "" { + logger.Info("Auto brightness curve cleared") + return + } + + jsonStr = strings.ReplaceAll(jsonStr, "\\", "") + logger.Debugf("Parsing auto brightness curve: %s", jsonStr) + + var points []AutoBrightnessCurvePoint + if err := json.Unmarshal([]byte(jsonStr), &points); err != nil { + logger.Warningf("Failed to parse auto brightness curve: %v", err) + return + } + + cm.setAutoBrightnessCurveFromPointsLocked(points) +} + +// setAutoBrightnessCurveFromPoints 设置自动亮度曲线(内部方法,从数组) +func (cm *CurveManager) setAutoBrightnessCurveFromPoints(points []AutoBrightnessCurvePoint) { + cm.mu.Lock() + defer cm.mu.Unlock() + cm.setAutoBrightnessCurveFromPointsLocked(points) +} + +// setAutoBrightnessCurveFromPointsLocked 设置自动亮度曲线(内部方法,需持有锁) +func (cm *CurveManager) setAutoBrightnessCurveFromPointsLocked(points []AutoBrightnessCurvePoint) { + cm.autoBrightnessCurveFunc = nil + cm.autoBrightnessCurve = nil + + if len(points) == 0 { + logger.Info("Auto brightness curve cleared") + return + } + + if err := validateAutoBrightnessCurve(points); err != nil { + logger.Warningf("Invalid auto brightness curve: %v", err) + return + } + + cm.autoBrightnessCurve = points + cm.autoBrightnessCurveFunc = cm.generateAutoBrightnessCurveFunc(points) + logger.Infof("Auto brightness curve set successfully with %d points", len(points)) +} + +// validateAutoBrightnessCurve 验证自动亮度曲线配置 +func validateAutoBrightnessCurve(points []AutoBrightnessCurvePoint) error { + if len(points) == 0 { + return fmt.Errorf("curve points cannot be empty") + } + + for i, p := range points { + if p.Br < 0 || p.Br > 100 { + return fmt.Errorf("brightness at index %d must be between 0 and 100, got: %f", i, p.Br) + } + if i > 0 && p.Lux <= points[i-1].Lux { + return fmt.Errorf("lux values must be in ascending order at index %d", i) + } + } + + return nil +} + +// generateAutoBrightnessCurveFunc 生成自动亮度曲线函数 +func (cm *CurveManager) generateAutoBrightnessCurveFunc(points []AutoBrightnessCurvePoint) func(lux int) float64 { + return func(lux int) float64 { + if len(points) == 0 { + return -1 + } + + if len(points) == 1 { + return points[0].Br / 100.0 + } + + luxFloat := float64(lux) + + if luxFloat <= float64(points[0].Lux) { + return points[0].Br / 100.0 + } + + if luxFloat >= float64(points[len(points)-1].Lux) { + return points[len(points)-1].Br / 100.0 + } + + for i := 0; i < len(points)-1; i++ { + p1, p2 := points[i], points[i+1] + if luxFloat >= float64(p1.Lux) && luxFloat <= float64(p2.Lux) { + x1, x2 := float64(p1.Lux), float64(p2.Lux) + y1, y2 := p1.Br, p2.Br + br := y1 + (y2-y1)*(luxFloat-x1)/(x2-x1) + return br / 100.0 + } + } + + return points[len(points)-1].Br / 100.0 + } +} + +// getAutoBrightnessValue 根据光照值获取目标亮度百分比 (0-1) +func (cm *CurveManager) getAutoBrightnessValue(lux int) float64 { + cm.mu.RLock() + defer cm.mu.RUnlock() + + if cm.autoBrightnessCurveFunc == nil { + return -1 + } + + return cm.autoBrightnessCurveFunc(lux) +} + +// hasAutoBrightnessCurve 检查是否配置了自动亮度曲线 +func (cm *CurveManager) hasAutoBrightnessCurve() bool { + cm.mu.RLock() + defer cm.mu.RUnlock() + return cm.autoBrightnessCurveFunc != nil +} diff --git a/display1/brightness/kalman_filter.go b/display1/brightness/kalman_filter.go new file mode 100644 index 000000000..167e47d07 --- /dev/null +++ b/display1/brightness/kalman_filter.go @@ -0,0 +1,300 @@ +// SPDX-FileCopyrightText: 2022 - 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package brightness + +import ( + "math" +) + +// 卡尔曼滤波器默认参数 +const ( + DefaultProcessNoiseQ = 0.5 // 过程噪声协方差(系统模型的不确定性) + DefaultMeasurementNoiseR = 0.02 // 测量噪声协方差(传感器噪声) + DefaultWindowSize = 5 // 自适应滤波器窗口大小 + DefaultVarianceScale = 0.1 // 方差缩放系数(用于自适应调整R) +) + +// KalmanFilter1D 一维卡尔曼滤波器 +// 用于传感器数据的平滑和噪声抑制 +type KalmanFilter1D struct { + // 系统状态 + xEst float64 // 估计值 + PEst float64 // 估计协方差 + + // 系统模型参数 + F float64 // 状态转移矩阵(通常为1,假设状态不变) + H float64 // 观测矩阵(通常为1) + Q float64 // 过程噪声协方差 + R float64 // 测量噪声协方差 + + // 初始化标志 + initialized bool +} + +// NewKalmanFilter1D 创建一维卡尔曼滤波器 +// q: 过程噪声协方差(系统模型的不确定性) +// r: 测量噪声协方差(传感器噪声) +// initialValue: 初始估计值 +func NewKalmanFilter1D(q, r, initialValue float64) *KalmanFilter1D { + return &KalmanFilter1D{ + F: 1.0, // 假设状态不变 + H: 1.0, // 直接观测 + Q: q, + R: r, + xEst: initialValue, + PEst: 1.0, + initialized: false, + } +} + +// Update 更新滤波器 +// measurement: 测量值 +// 返回: 滤波后的估计值 +func (kf *KalmanFilter1D) Update(measurement float64) float64 { + if !kf.initialized { + kf.xEst = measurement + kf.PEst = 1.0 + kf.initialized = true + logger.Debugf("[AutoBrightness::KalmanFilter] Initialized with measurement: %d", int(measurement)) + return kf.xEst + } + + // 记录输入值 + logger.Debugf("[AutoBrightness::KalmanFilter] Input measurement: %d, previous estimate: %d", int(measurement), int(kf.xEst)) + + // 预测步骤 + xPred := kf.F * kf.xEst // 状态预测 + pPred := kf.F*kf.PEst*kf.F + kf.Q // 协方差预测 + + // 更新步骤 + y := measurement - kf.H*xPred // 测量残差 + s := kf.H*pPred*kf.H + kf.R // 残差协方差 + k := pPred * kf.H / s // 卡尔曼增益 + + // 状态更新 + oldEstimate := kf.xEst + kf.xEst = xPred + k*y + kf.PEst = (1.0 - k*kf.H) * pPred + + // 记录输出值 + logger.Debugf("[AutoBrightness::KalmanFilter] Output estimate: %d (change: %.2f, gain: %.4f)", + int(kf.xEst), kf.xEst-oldEstimate, k) + + return kf.xEst +} + +// Reset 重置滤波器 +func (kf *KalmanFilter1D) Reset() { + kf.initialized = false + kf.PEst = 1.0 +} + +// GetEstimate 获取当前估计值 +func (kf *KalmanFilter1D) GetEstimate() float64 { + return kf.xEst +} + +// GetCovariance 获取当前协方差 +func (kf *KalmanFilter1D) GetCovariance() float64 { + return kf.PEst +} + +// SetProcessNoise 设置过程噪声协方差 +func (kf *KalmanFilter1D) SetProcessNoise(q float64) { + kf.Q = q +} + +// SetMeasurementNoise 设置测量噪声协方差 +func (kf *KalmanFilter1D) SetMeasurementNoise(r float64) { + kf.R = r +} + +// AdaptiveKalmanFilter 自适应卡尔曼滤波器 +// 根据测量值的方差自动调整噪声参数 +type AdaptiveKalmanFilter struct { + *KalmanFilter1D + window []float64 // 测量值窗口 + windowSize int // 窗口大小 + measurementVariance float64 // 测量方差 +} + +// NewAdaptiveKalmanFilter 创建自适应卡尔曼滤波器 +// q: 过程噪声协方差 +// r: 测量噪声协方差 +// window: 滑动窗口大小(用于计算方差) +func NewAdaptiveKalmanFilter(q, r float64, window int) *AdaptiveKalmanFilter { + return &AdaptiveKalmanFilter{ + KalmanFilter1D: NewKalmanFilter1D(q, r, 0.0), + window: make([]float64, 0, window), + windowSize: window, + } +} + +// NewDefaultAdaptiveKalmanFilter 使用默认参数创建自适应卡尔曼滤波器 +func NewDefaultAdaptiveKalmanFilter() *AdaptiveKalmanFilter { + return NewAdaptiveKalmanFilter(DefaultProcessNoiseQ, DefaultMeasurementNoiseR, DefaultWindowSize) +} + +// Update 更新滤波器(自适应版本) +func (akf *AdaptiveKalmanFilter) Update(measurement float64) float64 { + // 添加到窗口 + akf.window = append(akf.window, measurement) + if len(akf.window) > akf.windowSize { + akf.window = akf.window[1:] + } + + logger.Debugf("[AutoBrightness::AdaptiveKalmanFilter] Window size: %d/%d, measurement: %d", + len(akf.window), akf.windowSize, int(measurement)) + + // 计算窗口内测量值的方差 + if len(akf.window) >= 2 { + var sum float64 + for _, val := range akf.window { + sum += val + } + mean := sum / float64(len(akf.window)) + + var variance float64 + for _, val := range akf.window { + diff := val - mean + variance += diff * diff + } + akf.measurementVariance = variance / float64(len(akf.window)) + + logger.Debugf("[AutoBrightness::AdaptiveKalmanFilter] Window stats: mean=%.2f, variance=%.4f", + mean, akf.measurementVariance) + + // 根据测量方差自适应调整测量噪声 + // 方差越大,测量噪声越大,越信任估计值 + if akf.measurementVariance > 0 { + oldR := akf.R + newR := akf.measurementVariance * 0.1 + akf.SetMeasurementNoise(newR) + logger.Debugf("[AutoBrightness::AdaptiveKalmanFilter] Adjusted measurement noise: R=%.4f -> %.4f", + oldR, newR) + } + } + + // 调用基类的更新方法 + return akf.KalmanFilter1D.Update(measurement) +} + +// Reset 重置滤波器 +func (akf *AdaptiveKalmanFilter) Reset() { + akf.KalmanFilter1D.Reset() + akf.window = akf.window[:0] + akf.measurementVariance = 0 +} + +// GetMeasurementVariance 获取当前测量方差 +func (akf *AdaptiveKalmanFilter) GetMeasurementVariance() float64 { + return akf.measurementVariance +} + +// GetWindowSize 获取窗口大小 +func (akf *AdaptiveKalmanFilter) GetWindowSize() int { + return len(akf.window) +} + +// IsInitialized 检查滤波器是否已初始化(是否收到过数据) +func (akf *AdaptiveKalmanFilter) IsInitialized() bool { + return akf.initialized +} + +// SetWindowSize 设置窗口大小 +func (akf *AdaptiveKalmanFilter) SetWindowSize(size int) { + if size < 2 { + size = 2 + } + akf.windowSize = size + // 如果当前窗口超过新大小,截断 + if len(akf.window) > akf.windowSize { + akf.window = akf.window[len(akf.window)-akf.windowSize:] + } +} + +// ExponentialMovingAverage 指数移动平均滤波器(保留用于对比) +type ExponentialMovingAverage struct { + alpha float64 // 平滑系数 + lastValue float64 // 上一次的值 + initialized bool +} + +// NewExponentialMovingAverage 创建指数移动平均滤波器 +// alpha: 平滑系数 (0-1),值越大对新数据响应越快 +func NewExponentialMovingAverage(alpha float64) *ExponentialMovingAverage { + return &ExponentialMovingAverage{ + alpha: alpha, + } +} + +// Update 更新滤波器 +func (ema *ExponentialMovingAverage) Update(value float64) float64 { + if !ema.initialized { + ema.lastValue = value + ema.initialized = true + return value + } + + ema.lastValue = ema.alpha*value + (1-ema.alpha)*ema.lastValue + return ema.lastValue +} + +// Reset 重置滤波器 +func (ema *ExponentialMovingAverage) Reset() { + ema.initialized = false +} + +// GetValue 获取当前值 +func (ema *ExponentialMovingAverage) GetValue() float64 { + return ema.lastValue +} + +// MovingAverage 移动平均滤波器 +type MovingAverage struct { + window []float64 + windowSize int + sum float64 +} + +// NewMovingAverage 创建移动平均滤波器 +func NewMovingAverage(windowSize int) *MovingAverage { + return &MovingAverage{ + window: make([]float64, 0, windowSize), + windowSize: windowSize, + } +} + +// Update 更新滤波器 +func (ma *MovingAverage) Update(value float64) float64 { + ma.window = append(ma.window, value) + ma.sum += value + + if len(ma.window) > ma.windowSize { + ma.sum -= ma.window[0] + ma.window = ma.window[1:] + } + + return ma.sum / float64(len(ma.window)) +} + +// Reset 重置滤波器 +func (ma *MovingAverage) Reset() { + ma.window = ma.window[:0] + ma.sum = 0 +} + +// GetValue 获取当前平均值 +func (ma *MovingAverage) GetValue() float64 { + if len(ma.window) == 0 { + return 0 + } + return ma.sum / float64(len(ma.window)) +} + +// clampFloat64 将值限制在指定范围内 +func clampFloat64(value, min, max float64) float64 { + return math.Max(min, math.Min(max, value)) +} diff --git a/display1/manager.go b/display1/manager.go index a73e17329..9ea0f5837 100644 --- a/display1/manager.go +++ b/display1/manager.go @@ -114,6 +114,13 @@ const ( DSettingsKeyABManualOverride = "manual-override-duration" DSettingsKeyABManualAdjustDisablesAutoMode = "manual-adjust-disables-auto-mode" DSettingsKeyABUseTransition = "use-transition" + DSettingsKeyABBrightnessChangeThreshold = "brightness-change-threshold" + DSettingsKeyABCurve = "lux-brightness-curve" + + // 卡尔曼滤波器配置 + DSettingsKeyABKalmanProcessNoise = "kalman-process-noise" + DSettingsKeyABKalmanMeasurementNoise = "kalman-measurement-noise" + DSettingsKeyABKalmanWindowSize = "kalman-window-size" customModeDelim = "+" monitorsIdDelimiter = "," diff --git a/display1/sensor_proxy.go b/display1/sensor_proxy.go index c2ba49203..a42b4bec0 100644 --- a/display1/sensor_proxy.go +++ b/display1/sensor_proxy.go @@ -29,36 +29,43 @@ const ( ) // SensorProxyClient 环境光传感器D-Bus客户端 +// 职责:负责从光感服务获取数据并缓存,不负责滤波处理 type SensorProxyClient struct { conn *dbus.Conn sensorProxy dbus.BusObject hasAmbientLight bool claimed bool + // 事件处理 - signalChan chan *dbus.Signal - onServiceChange func(bool) + signalChan chan *dbus.Signal + onServiceChange func(bool) + onLightLevelChange func(int) + // 同步控制 mutex sync.Mutex + // 服务监控 serviceAvailable bool ownerWatcher chan *dbus.Signal + // 错误处理 - maxRetries int - retryDelay time.Duration - filterValue int // 滤波值 - filterFactor float64 // 滤波参数默认0.2,暂时没有必要进行配置 + maxRetries int + retryDelay time.Duration + + // 光感数据缓存 + lastLightLevel int // 最后一次光感值(lux) + lastLightLevelTime time.Time // 最后更新时间 } // NewSensorProxyClient 创建新的传感器代理客户端 func NewSensorProxyClient(conn *dbus.Conn) *SensorProxyClient { return &SensorProxyClient{ - conn: conn, - signalChan: make(chan *dbus.Signal, 10), - ownerWatcher: make(chan *dbus.Signal, 10), - maxRetries: 3, - retryDelay: time.Millisecond * 500, - filterValue: 0, - filterFactor: 0.2, + conn: conn, + signalChan: make(chan *dbus.Signal, 10), + ownerWatcher: make(chan *dbus.Signal, 10), + maxRetries: 3, + retryDelay: time.Millisecond * 500, + lastLightLevel: -1, } } @@ -109,10 +116,11 @@ func (c *SensorProxyClient) Disconnect() error { c.stopSignalWatching() // 停止服务监控 c.stopServiceWatching() - c.filterValue = 0 + c.sensorProxy = nil c.serviceAvailable = false c.hasAmbientLight = false + c.lastLightLevel = -1 return nil } @@ -154,39 +162,60 @@ func (c *SensorProxyClient) ReleaseLight() error { return err } c.claimed = false - c.filterValue = 0 + c.lastLightLevel = -1 return nil } -// GetLightLevel 获取当前环境光强度(返回缓存值的平均值并清空缓存) -func (c *SensorProxyClient) GetLightLevel() (int, error) { +// GetCachedLightLevel 获取缓存的环境光强度(返回原始值,不滤波) +func (c *SensorProxyClient) GetCachedLightLevel() (int, error) { c.mutex.Lock() - defer c.mutex.Unlock() if !c.serviceAvailable { + c.mutex.Unlock() return 0, errors.New("SensorProxy service not available") } + if !c.hasAmbientLight { + c.mutex.Unlock() return 0, errors.New("no ambient light sensor") } + if !c.claimed { + c.mutex.Unlock() return 0, errors.New("light sensor not claimed") } - // 如果缓存为空,返回上一次的平均值 - if c.filterValue == 0 { - // 没有任何数据可用 + + needInit := c.lastLightLevel < 0 + c.mutex.Unlock() + + if needInit { + c.mutex.Lock() + sensorProxy := c.sensorProxy + c.mutex.Unlock() + c.initializeCacheFromPropertyWithProxy(sensorProxy) + } + + c.mutex.Lock() + defer c.mutex.Unlock() + + if c.lastLightLevel < 0 { return 0, errors.New("no light level data available") } - return c.filterValue, nil + + logger.Debugf("[AutoBrightness::GetCachedLightLevel] Returning cached raw light level: %d lux", c.lastLightLevel) + return c.lastLightLevel, nil } // HasAmbientLight 检查是否有环境光传感器 func (c *SensorProxyClient) HasAmbientLight() (bool, error) { c.mutex.Lock() - defer c.mutex.Unlock() if !c.serviceAvailable { + c.mutex.Unlock() return false, errors.New("SensorProxy service not available") } - return c.hasAmbientLightInternal() + sensorProxy := c.sensorProxy + c.mutex.Unlock() + + return c.hasAmbientLightInternalWithProxy(sensorProxy) } // SetServiceChangeCallback 设置服务状态变化回调 @@ -196,6 +225,13 @@ func (c *SensorProxyClient) SetServiceChangeCallback(callback func(bool)) { c.onServiceChange = callback } +// SetLightLevelChangeCallback 设置光照值变化回调 +func (c *SensorProxyClient) SetLightLevelChangeCallback(callback func(int)) { + c.mutex.Lock() + defer c.mutex.Unlock() + c.onLightLevelChange = callback +} + // IsServiceAvailable 检查服务是否可用 func (c *SensorProxyClient) IsServiceAvailable() bool { c.mutex.Lock() @@ -250,9 +286,13 @@ func (c *SensorProxyClient) checkServiceAvailable() error { return err } -// hasAmbientLightInternal 内部检查环境光传感器 -func (c *SensorProxyClient) hasAmbientLightInternal() (bool, error) { - variant, err := c.sensorProxy.GetProperty(hadessProxyInterface + "." + propHasAmbientLight) +// hasAmbientLightInternalWithProxy 内部检查环境光传感器(不加锁版本) +func (c *SensorProxyClient) hasAmbientLightInternalWithProxy(sensorProxy dbus.BusObject) (bool, error) { + if sensorProxy == nil { + return false, errors.New("sensor proxy is nil") + } + + variant, err := sensorProxy.GetProperty(hadessProxyInterface + "." + propHasAmbientLight) if err != nil { return false, err } @@ -263,6 +303,11 @@ func (c *SensorProxyClient) hasAmbientLightInternal() (bool, error) { return hasLight, nil } +// hasAmbientLightInternal 内部检查环境光传感器 +func (c *SensorProxyClient) hasAmbientLightInternal() (bool, error) { + return c.hasAmbientLightInternalWithProxy(c.sensorProxy) +} + // claimLightInternal 内部声明环境光传感器 func (c *SensorProxyClient) claimLightInternal() error { call := c.sensorProxy.Call(hadessProxyInterface+"."+methodClaimLight, 0) @@ -367,19 +412,20 @@ func (c *SensorProxyClient) handlePropertiesChanged(signal *dbus.Signal) { logger.Warning("[SensorProxy] Invalid PropertiesChanged signal body") return } - // 第二个参数是变化的属性映射 + changedProps, ok := signal.Body[1].(map[string]dbus.Variant) if !ok { logger.Warning("[SensorProxy] Invalid PropertiesChanged signal format") return } - c.mutex.Lock() - defer c.mutex.Unlock() - // 检查LightLevel属性变化(当claimed后会收到此信号) + if lightLevelVariant, exists := changedProps[propLightLevel]; exists { if lightLevel, ok := lightLevelVariant.Value().(float64); ok { - // 只有在claimed状态下才添加到缓存 - if c.claimed { + c.mutex.Lock() + claimed := c.claimed + c.mutex.Unlock() + + if claimed { c.lightValueFilter(int(lightLevel)) } } else { @@ -431,10 +477,48 @@ func (c *SensorProxyClient) handleNameOwnerChanged(signal *dbus.Signal) { } } func (c *SensorProxyClient) lightValueFilter(newValue int) { - if c.filterValue == 0 { - c.filterValue = newValue + logger.Infof("[AutoBrightness::RawLightSensor] Raw light sensor value: %d lux", newValue) + + c.mutex.Lock() + c.lastLightLevel = newValue + c.lastLightLevelTime = time.Now() + callback := c.onLightLevelChange + c.mutex.Unlock() + + logger.Debugf("[AutoBrightness::CachedLightValue] Cached raw light value: %d lux", newValue) + + if callback != nil { + go callback(newValue) + } +} + +// initializeCacheFromPropertyWithProxy 从 D-Bus 属性读取当前光照值并缓存(不加锁版本) +func (c *SensorProxyClient) initializeCacheFromPropertyWithProxy(sensorProxy dbus.BusObject) { + if sensorProxy == nil { return } - filtered := c.filterFactor*float64(c.filterValue) + (1.0-c.filterFactor)*float64(newValue) - c.filterValue = int(filtered) + + variant, err := sensorProxy.GetProperty(hadessProxyInterface + "." + propLightLevel) + if err != nil { + logger.Warning("[AutoBrightness::LightSensor] Failed to get LightLevel property:", err) + return + } + + lightLevel, ok := variant.Value().(float64) + if !ok { + logger.Warning("[AutoBrightness::LightSensor] Invalid LightLevel property type") + return + } + + if lightLevel <= 0 { + logger.Debug("[AutoBrightness::LightSensor] LightLevel property is zero or negative, skipping cache") + return + } + + c.mutex.Lock() + c.lastLightLevel = int(lightLevel) + c.lastLightLevelTime = time.Now() + c.mutex.Unlock() + + logger.Infof("[AutoBrightness::LightSensor] Cached raw light value from property: %d lux", int(lightLevel)) } diff --git a/misc/dsg-configs/org.deepin.Display.AutoBrightness.json b/misc/dsg-configs/org.deepin.Display.AutoBrightness.json index 3718d1ca8..f15632719 100644 --- a/misc/dsg-configs/org.deepin.Display.AutoBrightness.json +++ b/misc/dsg-configs/org.deepin.Display.AutoBrightness.json @@ -23,7 +23,16 @@ "change-threshold": { "name": "Light Change Threshold", "description": "触发亮度调节的环境光变化阈值", - "value": 31.0, + "value": 20.0, + "visibility": "private", + "permissions": "readwrite", + "serial": 0, + "flags": [] + }, + "brightness-change-threshold": { + "name": "Brightness Change Threshold", + "description": "触发亮度调节的亮度变化阈值(0.01表示1%)", + "value": 0.05, "visibility": "private", "permissions": "readwrite", "serial": 0, @@ -48,14 +57,32 @@ "flags": [] }, "manual-adjust-disables-auto-mode": { - "name": "Manual adjust disables auto mode", - "description": "手动调节后是否立即停止功能", + "name": "Manual Adjust Disables Auto Mode", + "description": "手动调节是否禁用自动模式", "value": true, "visibility": "private", "permissions": "readwrite", "serial": 0, "flags": [] }, + "kalman-process-noise": { + "name": "Kalman Filter Process Noise Q", + "description": "卡尔曼滤波器过程噪声协方差(Q),值越大对环境光变化响应越快", + "value": 0.5, + "visibility": "private", + "permissions": "readwrite", + "serial": 0, + "flags": [] + }, + "kalman-measurement-noise": { + "name": "Kalman Filter Measurement Noise R", + "description": "卡尔曼滤波器测量噪声协方差(R),值越大滤波效果越强", + "value": 0.1, + "visibility": "private", + "permissions": "readwrite", + "serial": 0, + "flags": [] + }, "use-transition": { "name": "Use Transition for Auto Brightness", "description": "自动亮度调节时是否使用渐变效果(即使全局渐变功能关闭)", @@ -64,6 +91,24 @@ "permissions": "readwrite", "serial": 0, "flags": [] + }, + "lux-brightness-curve": { + "name": "Lux to Brightness Curve", + "description": "环境光到亮度的映射曲线配置", + "value": [], + "visibility": "private", + "permissions": "readwrite", + "serial": 0, + "flags": [] + }, + "kalman-window-size": { + "name": "Kalman Filter Window Size", + "description": "自适应卡尔曼滤波器滑动窗口大小,用于计算测量方差", + "value": 5, + "visibility": "private", + "permissions": "readwrite", + "serial": 0, + "flags": [] } } -} \ No newline at end of file +} From 57199d78ff37c4d77811bd460d082c0e5dad041e Mon Sep 17 00:00:00 2001 From: fuleyi Date: Thu, 7 May 2026 13:37:32 +0800 Subject: [PATCH 4/8] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E4=BA=AE=E5=BA=A6=E5=8D=A1=E5=B0=94=E6=9B=BC=E6=BB=A4?= =?UTF-8?q?=E6=B3=A2=E5=99=A8=E5=8F=82=E6=95=B0=E5=B9=B6=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=A1=A5=E5=81=BF=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 调整卡尔曼滤波器参数:过程噪声0.01→0.8,测量噪声0.1→0.05,窗口大小10→3 2. 新增传感器数据补偿机制,在数据超时1秒时主动补偿卡尔曼滤波器 3. 添加补偿定时器管理逻辑,在自动亮度启停、暂停恢复等状态变更时同步管理 4. 新增重置历史状态方法,确保手动调节后能立即响应 5. 更新DSettings配置文件中的默认参数值 Log: 优化自动亮度调节的响应性和准确性 Change-Id: I19c1ece035dff6d91348b626466bffcec3c28733 --- display1/auto_brightness.go | 181 +++++++++++++++--- .../org.deepin.Display.AutoBrightness.json | 6 +- 2 files changed, 158 insertions(+), 29 deletions(-) diff --git a/display1/auto_brightness.go b/display1/auto_brightness.go index 9e9193c56..0421d2022 100644 --- a/display1/auto_brightness.go +++ b/display1/auto_brightness.go @@ -16,6 +16,12 @@ import ( "github.com/linuxdeepin/dde-daemon/display1/brightness" ) +const ( + compensationInterval = 300 * time.Millisecond + sensorDataTimeout = 1 * time.Second + compensationThreshold = 5.0 +) + // AutoBrightnessConfig 自动亮度配置结构体 type AutoBrightnessConfig struct { Enabled bool `json:"enabled"` // 是否启用自动亮度 @@ -41,9 +47,9 @@ var DefaultAutoBrightnessConfig = AutoBrightnessConfig{ ManualOverrideDuration: 300, ManualAdjustDisablesAutoMode: true, UseTransition: true, - KalmanProcessNoise: 0.01, - KalmanMeasurementNoise: 0.1, - KalmanWindowSize: 10, + KalmanProcessNoise: 0.8, + KalmanMeasurementNoise: 0.05, + KalmanWindowSize: 3, } // DSettings键名已在manager.go中定义 @@ -75,6 +81,11 @@ type AutoBrightnessManager struct { running bool polling bool + lastSensorDataTime time.Time + compensationTicker *time.Ticker + compensationStopCh chan struct{} + compensationWg sync.WaitGroup + // 同步控制 mutex sync.RWMutex @@ -167,40 +178,55 @@ func (abm *AutoBrightnessManager) Initialize(manager *Manager) error { // Start 启动自动亮度功能 func (abm *AutoBrightnessManager) Start() error { abm.mutex.Lock() - defer abm.mutex.Unlock() if !abm.supported { + abm.mutex.Unlock() return errors.New("auto brightness not supported") } + + abm.resetHistoryState() abm.manualOverride = time.Time{} + if abm.running { + abm.mutex.Unlock() return nil } + if !abm.config.Enabled { + abm.mutex.Unlock() return nil } - // 连接传感器服务 - err := abm.sensorClient.Connect() + + sensorClient := abm.sensorClient + manager := abm.manager + abm.mutex.Unlock() + + err := sensorClient.Connect() if err != nil { logger.Warning("[AutoBrightness] Failed to connect to sensor proxy:", err) return fmt.Errorf("failed to connect to sensor proxy: %w", err) } - // 声明环境光传感器(带重试机制) + err = abm.claimLightWithRetry() if err != nil { logger.Warning("[AutoBrightness] Failed to claim light sensor after retries:", err) - abm.sensorClient.Disconnect() + sensorClient.Disconnect() return fmt.Errorf("failed to claim light sensor: %w", err) } + + abm.mutex.Lock() abm.running = true abm.enabled = true - // 不明确在哪种情况下被启用,因此需要判断当前状态 - // 不需要处理lidclose, 当前的实现中,如果处在lidclose时,因为光感值长期不变,不产生实际影响 - if abm.manager.powerSaving || !abm.manager.sessionActive { - logger.Warningf("[AutoBrightness] Started but powerSaving [%v], session active [%v]", abm.manager.powerSaving, abm.manager.sessionActive) - } else { - // 启动轮询定时器 + abm.lastSensorDataTime = time.Now() + powerSaving := manager.powerSaving + sessionActive := manager.sessionActive + if !powerSaving && sessionActive { abm.startPolling() + abm.startCompensationTimer() + } else { + logger.Warningf("[AutoBrightness] Started but powerSaving [%v], session active [%v]", powerSaving, sessionActive) } + abm.mutex.Unlock() + logger.Info("[AutoBrightness] AutoBrightnessManager started successfully") return nil } @@ -208,27 +234,32 @@ func (abm *AutoBrightnessManager) Start() error { // Stop 停止自动亮度功能 func (abm *AutoBrightnessManager) Stop() error { abm.mutex.Lock() - defer abm.mutex.Unlock() + if !abm.running { + abm.mutex.Unlock() return nil } - // 停止轮询定时器 + abm.stopPolling() + abm.stopCompensationTimer() abm.restoreSavedBrightness() - // 释放环境光传感器 + if abm.sensorClient != nil { err := abm.sensorClient.ReleaseLight() if err != nil { logger.Warning("[AutoBrightness] Failed to release light sensor:", err) } - // 断开传感器连接 + err = abm.sensorClient.Disconnect() if err != nil { logger.Warning("[AutoBrightness] Failed to disconnect from sensor proxy:", err) } } + abm.running = false abm.enabled = false + abm.mutex.Unlock() + logger.Info("[AutoBrightness] AutoBrightnessManager stopped successfully") return nil } @@ -241,6 +272,7 @@ func (abm *AutoBrightnessManager) Cleanup() error { // 停止运行 if abm.running { abm.stopPolling() + abm.stopCompensationTimer() abm.running = false } // 断开传感器连接 @@ -331,7 +363,7 @@ func (abm *AutoBrightnessManager) OnManualBrightnessChange() { return } // 默认行为:临时暂停 - // 记录手动调节时间 + abm.resetHistoryState() abm.manualOverride = time.Now() logger.Infof("[AutoBrightness] Manual brightness change detected, pausing auto adjustment for %d seconds", abm.config.ManualOverrideDuration) // 释放传感器,停止收集亮度数据 @@ -354,26 +386,37 @@ func (abm *AutoBrightnessManager) isManualOverrideActive() bool { return time.Since(abm.manualOverride) < duration } -// OnSystemSleep 处理系统休眠 +// resetHistoryState 重置历史状态,使下次轮询能立即触发亮度调节 +// 注意:此函数假设调用者已经持有锁 +func (abm *AutoBrightnessManager) resetHistoryState() { + abm.lastLightLevel = -1 + abm.lastBrightness = -1 + abm.lastAdjustTime = time.Time{} +} + +// hold 暂停自动亮度调节 func (abm *AutoBrightnessManager) hold() { abm.mutex.Lock() defer abm.mutex.Unlock() if !abm.running { return } - // 暂停轮询 + abm.stopPolling() + abm.stopCompensationTimer() } -// OnSystemWakeup 处理系统唤醒 +// resume 恢复自动亮度调节 func (abm *AutoBrightnessManager) resume() { abm.mutex.Lock() defer abm.mutex.Unlock() if !abm.running { return } - // 恢复轮询 + + abm.resetHistoryState() abm.startPolling() + abm.startCompensationTimer() } // OnConfigChanged 处理配置变更 @@ -394,7 +437,9 @@ func (abm *AutoBrightnessManager) OnConfigChanged(config AutoBrightnessConfig) { if needRestart { abm.stopPolling() + abm.stopCompensationTimer() abm.startPolling() + abm.startCompensationTimer() } abm.mutex.Unlock() @@ -514,11 +559,11 @@ func (abm *AutoBrightnessManager) claimLightWithRetry() error { } // gracefulDegradeService 优雅降级服务 -// 注意:此函数假设调用者已经持有锁(如果需要的话) +// 注意:此函数假设调用者已经持有锁 func (abm *AutoBrightnessManager) gracefulDegradeService() { - // 停止轮询但保持基本功能 abm.stopPolling() - // 释放传感器资源 + abm.stopCompensationTimer() + if abm.sensorClient != nil { err := abm.sensorClient.ReleaseLight() if err != nil { @@ -681,6 +726,7 @@ func (abm *AutoBrightnessManager) onServiceChange(available bool) { logger.Warning("[AutoBrightness] SensorProxy service became unavailable") if abm.running { abm.stopPolling() + abm.stopCompensationTimer() abm.running = false abm.enabled = false } @@ -704,6 +750,7 @@ func (abm *AutoBrightnessManager) onLightLevelChange(rawLightLevel int) { running := abm.running polling := abm.polling inManualOverride := abm.isInManualOverride() + abm.lastSensorDataTime = time.Now() abm.mutex.Unlock() if !running || !polling || inManualOverride { @@ -799,6 +846,88 @@ func (abm *AutoBrightnessManager) processLightChange(rawLightLevel int) { rawLightLevel, filteredLightLevel, targetBrightness*100) } +// needCompensation checks if compensation is needed and returns the sensor value +// Note: caller must hold abm.mutex +func (abm *AutoBrightnessManager) needCompensation() (bool, int) { + if abm.lastSensorDataTime.IsZero() { + return false, 0 + } + + if time.Since(abm.lastSensorDataTime) < sensorDataTimeout { + return false, 0 + } + + if abm.kalmanFilter == nil { + return false, 0 + } + + sensorValue, err := abm.sensorClient.GetCachedLightLevel() + if err != nil { + return false, 0 + } + + filterOutput := abm.kalmanFilter.GetEstimate() + diff := math.Abs(filterOutput - float64(sensorValue)) + + return diff > compensationThreshold, sensorValue +} + +func (abm *AutoBrightnessManager) compensationTick() { + abm.mutex.Lock() + if !abm.running || !abm.polling { + abm.mutex.Unlock() + return + } + inManualOverride := abm.isInManualOverride() + needComp, sensorValue := abm.needCompensation() + abm.mutex.Unlock() + + if inManualOverride || !needComp { + return + } + + logger.Infof("[AutoBrightness] Compensation: feeding sensor value %d lux to filter", sensorValue) + abm.processLightChange(sensorValue) +} + +func (abm *AutoBrightnessManager) startCompensationTimer() { + if abm.compensationTicker != nil { + return + } + abm.compensationTicker = time.NewTicker(compensationInterval) + abm.compensationStopCh = make(chan struct{}) + abm.compensationWg.Add(1) + + go func() { + defer abm.compensationWg.Done() + for { + select { + case <-abm.compensationTicker.C: + abm.compensationTick() + case <-abm.compensationStopCh: + return + } + } + }() +} + +func (abm *AutoBrightnessManager) stopCompensationTimer() { + if abm.compensationTicker == nil { + return + } + + abm.compensationTicker.Stop() + abm.compensationTicker = nil + if abm.compensationStopCh != nil { + close(abm.compensationStopCh) + abm.compensationStopCh = nil + } + + abm.mutex.Unlock() + abm.compensationWg.Wait() + abm.mutex.Lock() +} + // isInManualOverride 检查是否在手动调节暂停期间 // 注意:此函数需要读取 abm.manualOverride 和 abm.config,调用者应该持有至少读锁 func (abm *AutoBrightnessManager) isInManualOverride() bool { diff --git a/misc/dsg-configs/org.deepin.Display.AutoBrightness.json b/misc/dsg-configs/org.deepin.Display.AutoBrightness.json index f15632719..ea6024913 100644 --- a/misc/dsg-configs/org.deepin.Display.AutoBrightness.json +++ b/misc/dsg-configs/org.deepin.Display.AutoBrightness.json @@ -68,7 +68,7 @@ "kalman-process-noise": { "name": "Kalman Filter Process Noise Q", "description": "卡尔曼滤波器过程噪声协方差(Q),值越大对环境光变化响应越快", - "value": 0.5, + "value": 0.8, "visibility": "private", "permissions": "readwrite", "serial": 0, @@ -77,7 +77,7 @@ "kalman-measurement-noise": { "name": "Kalman Filter Measurement Noise R", "description": "卡尔曼滤波器测量噪声协方差(R),值越大滤波效果越强", - "value": 0.1, + "value": 0.05, "visibility": "private", "permissions": "readwrite", "serial": 0, @@ -104,7 +104,7 @@ "kalman-window-size": { "name": "Kalman Filter Window Size", "description": "自适应卡尔曼滤波器滑动窗口大小,用于计算测量方差", - "value": 5, + "value": 3, "visibility": "private", "permissions": "readwrite", "serial": 0, From 18191c6c9f494d1f494983c0f3bfa12434abc348 Mon Sep 17 00:00:00 2001 From: fuleyi Date: Thu, 7 May 2026 13:51:08 +0800 Subject: [PATCH 5/8] =?UTF-8?q?docs:=20=E6=9B=B4=E6=96=B0=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E4=BA=AE=E5=BA=A6=E8=AE=BE=E8=AE=A1=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E5=92=8C=E4=BD=BF=E7=94=A8=E6=8C=87=E5=8D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 更新自动亮度设计文档,增加卡尔曼滤波器、亮度曲线管理和补偿机制等新功能 2. 更新自动亮度使用指南,补充新增配置参数和高级功能说明 3. 在 .gitignore 中添加 AGENTS.md 文件忽略规则 Change-Id: I946a1335a6b8d500e290581a950f483f999e446f --- .gitignore | 8 +- docs/auto_backlight/auto_brightness_design.md | 787 ++++++++++++++++++ ...77\347\224\250\346\214\207\345\215\227.md" | 421 ++++++++++ 3 files changed, 1215 insertions(+), 1 deletion(-) create mode 100644 docs/auto_backlight/auto_brightness_design.md create mode 100644 "docs/auto_backlight/\350\207\252\345\212\250\344\272\256\345\272\246\345\212\237\350\203\275\344\275\277\347\224\250\346\214\207\345\215\227.md" diff --git a/.gitignore b/.gitignore index 98a9a2581..115998fdb 100644 --- a/.gitignore +++ b/.gitignore @@ -49,4 +49,10 @@ vendor/ a.out misc/polkit-action/*.policy -coverage.csv \ No newline at end of file +coverage.csv + +# AI +.cursor/ +.kiro/ +*/AGENTS.md +AGENTS.md \ No newline at end of file diff --git a/docs/auto_backlight/auto_brightness_design.md b/docs/auto_backlight/auto_brightness_design.md new file mode 100644 index 000000000..78e5176ac --- /dev/null +++ b/docs/auto_backlight/auto_brightness_design.md @@ -0,0 +1,787 @@ +# 自动亮度调节功能概要设计文档 + +## 目录 + +1. [功能概述](#1-功能概述) + - 1.1 系统架构图 +2. [核心组件](#2-核心组件) + - 2.1 AutoBrightnessManager + - 2.2 SensorProxyClient + - 2.3 BrightnessTransition + - 2.4 CurveManager + - 2.5 配置管理 +3. [状态机](#3-状态机) + - 3.1 自动亮度状态转换 +4. [数据结构](#4-数据结构) + - 4.1 AutoBrightnessConfig + - 4.2 AutoBrightnessManager 状态字段 + - 4.3 BrightnessTransition 数据结构 +5. [核心流程](#5-核心流程) + - 5.1 初始化流程 + - 5.2 启动流程 + - 5.3 轮询流程 + - 5.4 停止流程 +6. [关键算法](#6-关键算法) + - 6.1 亮度计算算法 + - 6.2 卡尔曼滤波器 + - 6.3 调节判断逻辑 + - 6.4 渐变算法 + - 6.5 补偿机制 +7. [亮度渐变机制](#7-亮度渐变机制) + - 7.1 渐变流程 + - 7.2 渐变控制 + - 7.3 渐变优化 +8. [手动调节处理](#8-手动调节处理) + - 8.1 两种模式 + - 8.2 手动调节处理时序 + - 8.3 系统调整标志 +9. [配置管理](#9-配置管理) + - 9.1 DSettings 集成 + - 9.2 配置项 + - 9.3 动态更新 +10. [异常处理](#10-异常处理) + - 10.1 重试机制 + - 10.2 优雅降级 + - 10.3 服务恢复 +11. [并发控制](#11-并发控制) + - 11.1 锁策略 + - 11.2 Goroutine 管理 +12. [系统集成](#12-系统集成) + - 12.1 与 Display Manager 集成 + - 12.2 电源管理集成 + - 12.3 DBus 接口 +13. [依赖关系](#13-依赖关系) + - 13.1 外部依赖 + - 13.2 内部依赖 +14. [测试要点](#14-测试要点) +15. [注意事项](#15-注意事项) + +--- + +## 1. 功能概述 + +自动亮度调节功能通过环境光传感器自动调整显示器亮度,提升用户体验并节省电能。该功能集成在 StartDDE 的 display 模块中,支持灵活的配置和优雅的降级处理。 + +### 1.1 系统架构图 + +```mermaid +graph TB + subgraph "用户层" + User[用户] + ControlCenter[控制中心] + end + + subgraph "StartDDE Display 模块" + Manager[Display Manager] + ABM[AutoBrightnessManager] + BT[BrightnessTransition] + CM[CurveManager] + KF[AdaptiveKalmanFilter] + Backlight[Backlight 控制] + end + + subgraph "配置层" + DConf[DSettings/DConfig] + end + + subgraph "系统服务" + SensorProxy[iio-sensor-proxy] + Hardware[环境光传感器硬件] + end + + subgraph "DBus 通信" + DBus[DBus 总线] + end + + User -->|手动调节| ControlCenter + ControlCenter -->|DBus 调用| Manager + Manager -->|管理| ABM + Manager -->|使用| BT + Manager -->|控制| Backlight + + ABM -->|读取配置| DConf + ABM -->|保存配置| DConf + BT -->|读取配置| DConf + CM -->|读取曲线配置| DConf + + ABM -->|DBus 调用| SensorProxy + SensorProxy -->|读取| Hardware + + ABM -->|滤波处理| KF + ABM -->|查询曲线| CM + ABM -->|调用| BT + BT -->|设置亮度| Backlight + ABM -->|设置亮度| Backlight + + Manager -.->|属性通知| DBus + ControlCenter -.->|监听属性| DBus + + style ABM fill:#e1f5ff + style BT fill:#e1f5ff + style KF fill:#fff4e1 + style CM fill:#fff4e1 + style Manager fill:#fff4e1 +``` + +## 2. 核心组件 + +### 2.1 AutoBrightnessManager + +自动亮度管理器,负责整个功能的生命周期管理。 + +**主要职责:** +- 初始化和资源管理 +- 配置加载和持久化 +- 传感器数据采集和滤波处理 +- 亮度计算和应用 +- 状态监控和异常处理 +- 补偿机制管理 + +### 2.2 SensorProxyClient + +传感器代理客户端,封装与 iio-sensor-proxy 服务的交互。 + +**主要功能:** +- 连接/断开传感器服务 +- 声明/释放环境光传感器 +- 读取光照强度数据(原始值缓存) +- 监听服务状态变化 +- 监听光照值变化(推送模式) + +### 2.3 BrightnessTransition + +亮度渐变管理器,提供平滑的亮度过渡效果。 + +**主要功能:** +- 渐变效果的启用/禁用控制 +- 渐变参数配置(时长、步进间隔) +- 多显示器独立渐变状态管理 +- 渐变过程的启动、停止和中断处理 + +### 2.4 CurveManager + +曲线管理器,管理亮度曲线的配置和计算。 + +**主要功能:** +- FLM 机型定制曲线 +- 默认亮度曲线 +- 自定义亮度曲线(按 EDID 匹配) +- 自动亮度曲线(光照-亮度映射) +- 最大亮度限制 + +### 2.5 配置管理 + +基于 DSettings (DConfig) 的配置系统,支持动态配置更新。 + +## 3. 状态机 + +### 3.1 自动亮度状态转换 + +```mermaid +stateDiagram-v2 + [*] --> 未初始化 + + 未初始化 --> 初始化中: Initialize() + + 初始化中 --> 不支持: 检查失败
(无显示器/传感器) + 初始化中 --> 已初始化未启用: 检查成功
Enabled=false + 初始化中 --> 已初始化已启用: 检查成功
Enabled=true + + 不支持 --> [*]: 功能不可用 + + 已初始化未启用 --> 启动中: SetEnabled(true) + 已初始化已启用 --> 启动中: Start() + + 启动中 --> 运行中: 传感器声明成功
轮询启动 + 启动中 --> 已初始化未启用: 启动失败 + + 运行中 --> 手动暂停: 用户手动调节
(临时暂停模式) + 运行中 --> 停止中: SetEnabled(false) + 运行中 --> 休眠暂停: hold() + 运行中 --> 服务异常: 传感器服务不可用 + + 手动暂停 --> 运行中: 超时恢复 + 手动暂停 --> 停止中: SetEnabled(false) + + 休眠暂停 --> 运行中: resume() + 休眠暂停 --> 停止中: SetEnabled(false) + + 服务异常 --> 不支持: 服务持续不可用 + 服务异常 --> 运行中: 服务恢复
自动重连 + + 停止中 --> 已初始化未启用: 停止完成 + + 已初始化未启用 --> 启动中: SetEnabled(true) + 已初始化已启用 --> 停止中: SetEnabled(false) + + note right of 运行中 + - 轮询传感器 + - 卡尔曼滤波 + - 计算亮度 + - 应用调节 + - 补偿机制 + end note +``` + +## 4. 数据结构 + +### 4.1 AutoBrightnessConfig + +```go +type AutoBrightnessConfig struct { + Enabled bool // 是否启用 + Sensitivity float64 // 敏感度 (0.1-3.0) + PollingInterval int // 轮询间隔(秒) (1-60) + ChangeThreshold float64 // 环境光变化阈值 (1.0-50.0) + BrightnessChangeThreshold float64 // 亮度变化阈值 (0.01-1.0) + ManualOverrideDuration int // 手动调节暂停时间(秒) (60-1800) + ManualAdjustDisablesAutoMode bool // 手动调节是否禁用自动模式 + UseTransition bool // 是否使用渐变效果 + KalmanProcessNoise float64 // 卡尔曼滤波器过程噪声协方差 Q + KalmanMeasurementNoise float64 // 卡尔曼滤波器测量噪声协方差 R + KalmanWindowSize int // 卡尔曼滤波器窗口大小 +} +``` + +**默认值:** +```go +DefaultAutoBrightnessConfig = AutoBrightnessConfig{ + Enabled: false, + Sensitivity: 0.5, + PollingInterval: 5, + ChangeThreshold: 20.0, + BrightnessChangeThreshold: 0.01, + ManualOverrideDuration: 300, + ManualAdjustDisablesAutoMode: true, + UseTransition: true, + KalmanProcessNoise: 0.8, + KalmanMeasurementNoise: 0.05, + KalmanWindowSize: 3, +} +``` + +### 4.2 AutoBrightnessManager 状态字段 + +- **依赖注入:** manager (复用 display.Manager) +- **独立组件:** sensorClient, configManager, kalmanFilter +- **配置状态:** config, enabled, supported +- **运行状态:** running, polling, systemAdjusting +- **历史数据:** lastLightLevel, lastBrightness, lastAdjustTime +- **手动控制:** manualOverride (时间戳) +- **轮询控制:** ticker, stopChan, pollingWg +- **补偿机制:** lastSensorDataTime, compensationTicker, compensationStopCh, compensationWg + +### 4.3 BrightnessTransition 数据结构 + +**配置字段:** +- `enabled`: 是否启用渐变 +- `duration`: 从 0% 到 100% 的渐变时长(秒) +- `stepInterval`: 步进间隔(毫秒) + +**状态管理:** +```go +type transitionState struct { + running bool // 是否正在执行渐变 + currentValue float64 // 当前渐变的实时亮度值 + stopCh chan struct{} // 停止信号通道 + wg sync.WaitGroup // 等待渐变完成 +} +``` + +## 5. 核心流程 + +### 5.1 初始化流程 + +```mermaid +flowchart TD + A[Initialize] --> B{检查内置显示器} + B -->|不存在| C[返回错误: 无内置显示器] + B -->|存在| D{检查亮度调节支持} + D -->|不支持| E[返回错误: 不支持亮度调节] + D -->|支持| F[创建传感器客户端] + F --> G[检查传感器可用性] + G -->|不可用| H[返回错误: 传感器不可用] + G -->|可用| I[初始化配置管理器] + I --> J[加载配置] + J -->|失败| K[使用默认配置] + J -->|成功| L[设置服务状态回调] + K --> L + L --> M[初始化卡尔曼滤波器] + M --> N[标记为已支持] + N --> O[初始化完成] +``` + +### 5.2 启动流程 + +```mermaid +sequenceDiagram + participant User as 用户/系统 + participant ABM as AutoBrightnessManager + participant Sensor as SensorProxyClient + participant Poller as 轮询器 + participant Comp as 补偿定时器 + + User->>ABM: Start() + ABM->>ABM: 检查支持状态 + alt 不支持 + ABM-->>User: 返回错误 + else 支持 + ABM->>ABM: 检查配置是否启用 + alt 未启用 + ABM-->>User: 返回 nil + else 已启用 + ABM->>Sensor: Connect() + Sensor-->>ABM: 连接成功 + ABM->>Sensor: ClaimLight() (带重试) + alt 声明失败 + ABM->>Sensor: Disconnect() + ABM-->>User: 返回错误 + else 声明成功 + ABM->>ABM: 更新运行状态 + ABM->>ABM: 记录 lastSensorDataTime + ABM->>Poller: startPolling() + ABM->>Comp: startCompensationTimer() + ABM-->>User: 启动成功 + end + end + end +``` + +### 5.3 轮询流程 + +```mermaid +flowchart TD + A[定时器触发] --> B[pollLightLevel] + B --> C{检查运行状态} + C -->|未运行| D[返回] + C -->|运行中| E{检查手动调节暂停期} + E -->|暂停中| D + E -->|未暂停| F{传感器已声明?} + F -->|否| G[重新声明传感器] + G -->|失败| D + G -->|成功| H[获取缓存光照强度] + F -->|是| H + H -->|失败| D + H -->|成功| I[processLightChange] + + I --> J[卡尔曼滤波处理] + J --> K[计算目标亮度] + K --> L{shouldAdjustBrightness} + + L --> M{手动调节暂停?} + M -->|是| N[不调节] + M -->|否| O{环境光变化 >= 阈值?} + O -->|否| N + O -->|是| P{距上次调节 >= 间隔?} + P -->|否| N + P -->|是| Q{亮度变化 >= 阈值?} + Q -->|否| N + Q -->|是| R[应用亮度] + + R --> S{使用渐变?} + S -->|是| T[BrightnessTransition.SetBrightnessForced] + S -->|否| U[setBrightnessRaw] + T --> V[更新历史状态] + U --> V + V --> W[记录光照/亮度/时间] + W --> D + N --> D +``` + +### 5.4 停止流程 + +```mermaid +sequenceDiagram + participant User as 用户/系统 + participant ABM as AutoBrightnessManager + participant Poller as 轮询器 + participant Comp as 补偿定时器 + participant Sensor as SensorProxyClient + participant Display as 显示器 + + User->>ABM: Stop() + ABM->>ABM: 检查运行状态 + alt 未运行 + ABM-->>User: 返回 nil + else 运行中 + ABM->>Poller: stopPolling() + Poller->>Poller: 停止 ticker + Poller->>Poller: 发送停止信号 + Poller->>Poller: 等待 goroutine 退出 + Poller-->>ABM: 停止完成 + + ABM->>Comp: stopCompensationTimer() + Comp-->>ABM: 停止完成 + + ABM->>Display: restoreSavedBrightness() + Display->>Display: 获取保存的亮度 + Display->>Display: 应用亮度 + Display-->>ABM: 恢复完成 + + ABM->>Sensor: ReleaseLight() + Sensor-->>ABM: 释放完成 + + ABM->>Sensor: Disconnect() + Sensor-->>ABM: 断开完成 + + ABM->>ABM: 更新运行状态 + ABM-->>User: 停止成功 + end +``` + +## 6. 关键算法 + +### 6.1 亮度计算算法 + +**优先级:** +1. **曲线配置**:如果配置了 `lux-brightness-curve`,使用曲线映射 +2. **线性映射**:使用敏感度参数进行线性计算 + +**曲线映射:** +```go +// 配置格式 +type AutoBrightnessCurvePoint struct { + Lux int // 光感值 + Br float64 // 亮度百分比 (0-100) +} + +// 线性插值计算 +func interpolate(lux int, points []AutoBrightnessCurvePoint) float64 { + // 找到 lux 所在的区间,进行线性插值 +} +``` + +**线性映射(默认):** +``` +目标亮度 = min(max((光照强度 × 敏感度) / 1024.0, 0.1), 1.0) +``` + +**说明:** +- 环境光范围:0-1024 lux +- 敏感度调整:支持 0.1-3.0 倍率 +- 最小亮度保护:不低于 10%,避免屏幕过暗 + +### 6.2 卡尔曼滤波器 + +自适应卡尔曼滤波器用于平滑传感器数据,减少噪声影响。 + +**核心结构:** +```go +type AdaptiveKalmanFilter struct { + *KalmanFilter1D + window []float64 // 测量值窗口 + windowSize int // 窗口大小 + measurementVariance float64 // 测量方差 +} +``` + +**工作原理:** +1. 维护一个滑动窗口存储最近的测量值 +2. 计算窗口内测量值的方差 +3. 根据方差自适应调整测量噪声参数 R +4. 方差越大,R 越大,越信任估计值而非测量值 + +**参数说明:** +- **Q (过程噪声)**: 系统模型的不确定性,默认 0.8 +- **R (测量噪声)**: 传感器噪声,默认 0.05,会自适应调整 +- **窗口大小**: 用于计算方差,默认 3 + +**数据流:** +``` +原始光照值 -> 卡尔曼滤波器 -> 滤波后光照值 -> 亮度计算 +``` + +### 6.3 调节判断逻辑 + +满足以下所有条件才执行调节: + +1. **不在手动调节暂停期** +2. **环境光变化超过阈值**:`|当前光照 - 上次光照| >= ChangeThreshold` +3. **距上次调节时间足够**:`当前时间 - 上次调节时间 >= PollingInterval` +4. **亮度变化足够大**:`|目标亮度 - 当前亮度| >= BrightnessChangeThreshold` + +### 6.4 渐变算法 + +**基本原理:** +将亮度变化分解为多个小步进,在一定时间内逐步完成。 + +**参数计算:** +``` +实际渐变时长 = 配置时长 × |亮度差值| +步进次数 = 实际渐变时长 / 步进间隔 +每步变化量 = 亮度差值 / 步进次数 +``` + +### 6.5 补偿机制 + +当传感器数据超时(1秒未更新)时,主动补偿以确保滤波器持续工作。 + +**补偿条件:** +1. `lastSensorDataTime` 超过 1 秒 +2. 滤波器输出值与当前传感器值差异超过阈值(5.0) + +**补偿流程:** +```go +func compensationTick() { + if needCompensation() { + // 主动读取传感器值并处理 + processLightChange(sensorValue) + } +} +``` + +## 7. 亮度渐变机制 + +### 7.1 渐变流程 + +```mermaid +flowchart TD + A[SetBrightness] --> B{检查启用状态} + B -->|未启用且非强制| C[直接设置亮度] + B -->|已启用或强制| D[获取当前亮度] + + D --> E{正在渐变?} + E -->|是| F[使用实时亮度值] + E -->|否| G[从 Manager 获取] + + F --> H[计算亮度差值] + G --> H + + H --> I{差值 < 0.001?} + I -->|是| J[无需调整,返回] + I -->|否| K[停止之前的渐变] + + K --> L[计算渐变参数] + L --> M[实际时长 = 配置时长 × |差值|] + M --> N[步进次数 = 实际时长 / 步进间隔] + + N --> O{实际时长 < 最小时长?} + O -->|是| C + O -->|否| P[启动渐变 goroutine] + + P --> Q[返回不等待] +``` + +### 7.2 渐变控制 + +**启动渐变:** +- 标记 `running = true` +- 增加 WaitGroup 计数 +- 启动独立 goroutine 执行 + +**停止渐变:** +- 发送停止信号到 `stopCh` +- 等待 WaitGroup 完成 +- 清空残留信号 + +**中断处理:** +- 新渐变会先停止旧渐变 +- 使用实时亮度值作为起点 +- 确保平滑过渡 + +### 7.3 渐变优化 + +**性能优化:** +- 每个显示器独立渐变状态 +- 非阻塞启动(立即返回) +- 只在完成时同步一次属性 + +**用户体验优化:** +- 变化太小时直接设置(< 0.1%) +- 渐变时长按比例缩放 +- 最小渐变时长保护(200ms) + +## 8. 手动调节处理 + +### 8.1 两种模式 + +**模式一:临时暂停(默认,ManualAdjustDisablesAutoMode = false)** +- 手动调节后暂停自动调节指定时间 +- 暂停期间释放传感器资源 +- 超时后自动恢复 + +**模式二:完全禁用(ManualAdjustDisablesAutoMode = true)** +- 手动调节后永久禁用自动亮度 +- 更新配置并停止功能 +- 需用户手动重新启用 + +### 8.2 手动调节处理时序 + +```mermaid +sequenceDiagram + participant User as 用户 + participant Manager as Display Manager + participant ABM as AutoBrightnessManager + participant Sensor as SensorProxyClient + participant Config as 配置系统 + + User->>Manager: 手动调节亮度 + Manager->>Manager: setBrightness() + Manager->>ABM: OnManualBrightnessChange() + ABM->>ABM: 检查 systemAdjusting + alt systemAdjusting = true + ABM->>ABM: 忽略(系统调整) + else systemAdjusting = false + ABM->>ABM: 检查配置模式 + alt 临时暂停模式 + ABM->>ABM: 记录 manualOverride 时间 + ABM->>Sensor: ReleaseLight() + Note over ABM: 暂停指定时间 + ABM->>Sensor: ClaimLight() (超时后) + ABM->>ABM: 恢复自动调节 + else 完全禁用模式 + ABM->>Config: 保存 Enabled = false + ABM->>ABM: Stop() + ABM->>Manager: setPropAutoBrightnessEnabled(false) + end + end +``` + +### 8.3 系统调整标志 + +通过 `systemAdjusting` 标志区分系统自动调整(如节能模式)和用户手动调整,避免误判。 + +## 9. 配置管理 + +### 9.1 DSettings 集成 + +- **AppID:** `org.deepin.dde-daemon` +- **配置名:** `org.deepin.Display.AutoBrightness` + +### 9.2 配置项 + +**自动亮度配置:** + +| 配置键 | 类型 | 默认值 | 说明 | +|--------|------|--------|------| +| enabled | bool | false | 是否启用 | +| sensitivity | float64 | 0.5 | 敏感度 (0.1-3.0) | +| polling-interval | int | 5 | 轮询间隔(秒) | +| change-threshold | float64 | 20.0 | 环境光变化阈值 | +| brightness-change-threshold | float64 | 0.01 | 亮度变化阈值 | +| manual-override-duration | int | 300 | 手动暂停时间(秒) | +| manual-adjust-disables-auto-mode | bool | true | 手动调节是否禁用 | +| use-transition | bool | true | 是否使用渐变 | +| lux-brightness-curve | array | [] | 光照-亮度曲线 | +| kalman-process-noise | float64 | 0.8 | 卡尔曼过程噪声 Q | +| kalman-measurement-noise | float64 | 0.05 | 卡尔曼测量噪声 R | +| kalman-window-size | int | 3 | 卡尔曼窗口大小 | + +**渐变效果配置:** + +| 配置键 | 类型 | 默认值 | 说明 | +|--------|------|--------|------| +| transition-enabled | bool | true | 全局渐变开关 | +| transition-duration | int | 4 | 0-100% 渐变时长(秒) | +| transition-step-interval | int | 100 | 步进间隔(毫秒) | + +### 9.3 动态更新 + +监听配置文件变化,自动重新加载并应用新配置。敏感度变化时立即触发一次亮度调整。 + +## 10. 异常处理 + +### 10.1 重试机制 + +- 传感器声明失败:最多重试 3 次,间隔 2 秒 +- 亮度设置失败:下次轮询自动重试 +- 服务连接失败:带重试机制检查 + +### 10.2 优雅降级 + +- 传感器服务不可用:停止功能但不崩溃 +- 配置加载失败:使用默认配置 +- 内置显示器不存在:标记为不支持 + +### 10.3 服务恢复 + +监听 iio-sensor-proxy 服务状态,服务恢复后自动重新初始化。 + +## 11. 并发控制 + +### 11.1 锁策略 + +- 使用 `sync.RWMutex` 保护共享状态 +- 读多写少场景使用读锁 +- 避免在持有锁时执行耗时操作 +- 停止轮询时临时释放锁等待 goroutine 退出 + +### 11.2 Goroutine 管理 + +- **轮询 goroutine**: 通过 `stopChan` 和 `WaitGroup` 安全退出 +- **补偿 goroutine**: 通过 `compensationStopCh` 和 `compensationWg` 安全退出 +- **渐变 goroutine**: 每个显示器独立管理 +- **幂等停止**: 停止方法可安全多次调用 + +## 12. 系统集成 + +### 12.1 与 Display Manager 集成 + +- 复用 Manager 的显示器管理能力 +- 复用 BrightnessTransition 渐变功能 +- 同步更新 DBus 属性 + +### 12.2 电源管理集成 + +- 支持系统休眠/唤醒事件 +- `hold()`: 休眠前暂停轮询和补偿 +- `resume()`: 唤醒后恢复轮询和补偿 + +### 12.3 DBus 接口 + +通过 Manager 暴露以下属性和方法: +- `AutoBrightnessSupported` (只读) +- `AutoBrightnessEnabled` (读写) +- `CurrentLightLevel` (只读) +- 配置相关的 Get/Set 方法 + +## 13. 依赖关系 + +### 13.1 外部依赖 + +- **iio-sensor-proxy:** 提供环境光传感器数据 +- **DSettings/DConfig:** 配置管理 +- **DBus:** 进程间通信 + +### 13.2 内部依赖 + +- **display.Manager:** 显示器管理和亮度控制 +- **BrightnessTransition:** 亮度渐变效果 +- **CurveManager:** 亮度曲线管理 +- **AdaptiveKalmanFilter:** 传感器数据滤波 +- **backlight:** 底层亮度控制 + +## 14. 测试要点 + +### 14.1 功能测试 + +- 基本启停流程 +- 配置加载和保存 +- 亮度计算准确性(曲线和线性映射) +- 卡尔曼滤波器效果 +- 手动调节处理 +- 补偿机制 + +### 14.2 异常测试 + +- 传感器服务不可用 +- 配置文件损坏 +- 并发访问 +- 资源泄漏 + +### 14.3 性能测试 + +- CPU 占用率 +- 内存使用 +- 响应延迟 +- 长时间运行稳定性 + +## 15. 注意事项 + +1. **线程安全:** 所有公共方法都需要考虑并发访问 +2. **资源管理:** 确保传感器资源和 goroutine 正确释放 +3. **用户体验:** 避免频繁调节造成闪烁 +4. **电源效率:** 合理设置轮询间隔 +5. **降级策略:** 功能不可用时不影响系统稳定性 +6. **滤波器参数:** 卡尔曼滤波器参数需要根据实际传感器特性调整 +7. **曲线配置:** 光照-亮度曲线配置优先于线性映射 diff --git "a/docs/auto_backlight/\350\207\252\345\212\250\344\272\256\345\272\246\345\212\237\350\203\275\344\275\277\347\224\250\346\214\207\345\215\227.md" "b/docs/auto_backlight/\350\207\252\345\212\250\344\272\256\345\272\246\345\212\237\350\203\275\344\275\277\347\224\250\346\214\207\345\215\227.md" new file mode 100644 index 000000000..d03cd0a17 --- /dev/null +++ "b/docs/auto_backlight/\350\207\252\345\212\250\344\272\256\345\272\246\345\212\237\350\203\275\344\275\277\347\224\250\346\214\207\345\215\227.md" @@ -0,0 +1,421 @@ +# 自动亮度功能使用指南 + +## 功能介绍 + +自动亮度功能可以根据周围环境的光线强度,自动调整笔记本电脑或一体机屏幕的亮度,让您的眼睛更舒适,同时节省电量。 + +## 配置工具说明 + +本功能使用 `dde-dconfig` 工具进行配置管理。`dde-dconfig` 是 Deepin 桌面环境的统一配置管理工具。 + +**基本语法**: +```bash +dde-dconfig -a <应用ID> -r <资源名称> -k <配置键> --set -v <值> # 设置配置 +dde-dconfig -a <应用ID> -r <资源名称> -k <配置键> --get # 查询配置 +``` + +**自动亮度配置参数**: +- 应用ID:`org.deepin.dde-daemon` +- 资源名称:`org.deepin.Display.AutoBrightness` +- 配置键:`enabled`, `sensitivity`, `polling-interval`, `change-threshold`, `manual-override-duration`, `brightness-change-threshold`, `kalman-process-noise`, `kalman-measurement-noise`, `kalman-window-size` + +## 使用前准备 + +### 检查设备是否支持 + +您的设备需要满足以下条件: + +1. **有环境光传感器**(大部分现代笔记本都有) +2. **是内置屏幕**(外接显示器不支持) +3. **系统服务正常** + +### 快速检查方法 + +打开终端,输入以下命令检查: + +```bash +# 检查是否支持自动亮度 +dbus-send --session --print-reply --dest=com.deepin.daemon.Display \ + /com/deepin/daemon/Display org.freedesktop.DBus.Properties.Get \ + string:com.deepin.daemon.Display string:AutoBrightnessSupported +``` + +如果返回 `boolean true`,说明您的设备支持此功能。 + +## 基本使用 + +### 启用自动亮度 + +```bash +# 启用自动亮度 +dbus-send --session --dest=com.deepin.daemon.Display \ + /com/deepin/daemon/Display com.deepin.daemon.Display.SetAutoBrightnessEnabled \ + boolean:true +``` + +### 关闭自动亮度 + +```bash +# 关闭自动亮度 +dbus-send --session --dest=com.deepin.daemon.Display \ + /com/deepin/daemon/Display com.deepin.daemon.Display.SetAutoBrightnessEnabled \ + boolean:false +``` + +### 查看当前状态 + +```bash +# 查看是否已启用 +dbus-send --session --print-reply --dest=com.deepin.daemon.Display \ + /com/deepin/daemon/Display org.freedesktop.DBus.Properties.Get \ + string:com.deepin.daemon.Display string:AutoBrightnessEnabled + +# 查看当前环境光强度 +dbus-send --session --print-reply --dest=com.deepin.daemon.Display \ + /com/deepin/daemon/Display org.freedesktop.DBus.Properties.Get \ + string:com.deepin.daemon.Display string:CurrentLightLevel +``` + +## 个性化设置 + +### 设置参数说明 + +| 设置项 | 配置键 | 说明 | 取值范围 | 默认值 | +|-------|--------|------|---------|--------| +| **启用状态** | enabled | 是否启用自动亮度功能 | true/false | false | +| **敏感度** | sensitivity | 控制亮度调节的敏感程度,值越大亮度变化越明显 | 0.1-3.0 | 0.5 | +| **检测间隔** | polling-interval | 多久检测一次环境光(秒) | 1-60秒 | 5秒 | +| **环境光变化阈值** | change-threshold | 环境光变化多少才调节亮度(绝对值) | 1.0-50.0 | 20.0 | +| **亮度变化阈值** | brightness-change-threshold | 亮度变化多少才执行调节 | 0.01-1.0 | 0.01 | +| **暂停时间** | manual-override-duration | 手动调节后暂停自动调节的时间(秒) | 60-1800秒 | 300秒 | +| **手动调节禁用模式** | manual-adjust-disables-auto-mode | 手动调节后是否完全禁用自动亮度 | true/false | true | +| **渐变效果** | use-transition | 自动调节时是否使用渐变效果 | true/false | true | +| **卡尔曼过程噪声** | kalman-process-noise | 传感器数据滤波器参数(系统模型不确定性) | 0.01-100 | 0.8 | +| **卡尔曼测量噪声** | kalman-measurement-noise | 传感器数据滤波器参数(传感器噪声) | 0.01-100 | 0.05 | +| **卡尔曼窗口大小** | kalman-window-size | 传感器数据滤波器窗口大小 | 2-100 | 3 | + +### 单独设置配置项 + +每个配置项都可以使用 `dde-dconfig` 命令单独设置: + +```bash +# 设置敏感度 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.5 + +# 设置检测间隔(秒) +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 5 + +# 设置环境光变化阈值 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 20.0 + +# 设置亮度变化阈值 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k brightness-change-threshold --set -v 0.01 + +# 设置手动调节暂停时间(秒) +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 300 + +# 设置卡尔曼滤波器参数(高级) +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k kalman-process-noise --set -v 0.8 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k kalman-measurement-noise --set -v 0.05 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k kalman-window-size --set -v 3 +``` + +### 查询单个配置项 + +```bash +# 查询敏感度 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k sensitivity --get + +# 查询检测间隔 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k polling-interval --get + +# 查询变化阈值 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k change-threshold --get + +# 查询暂停时间 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k manual-override-duration --get + +# 查询所有配置 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness --list +``` + +### 常用配置示例 + +**注意**:配置修改后会立即生效,无需重启服务。 + +#### 标准配置(推荐) +```bash +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.5 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 5 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 20.0 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 300 +``` + +#### 快速响应配置(适合移动办公) +```bash +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.8 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 2 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 15.0 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 180 +``` + +#### 稳定配置(适合固定办公) +```bash +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.4 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 10 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 25.0 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 600 +``` + +#### 节能配置(最大化省电) +```bash +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.3 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 15 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 30.0 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 900 +``` + +## 使用技巧 + +### 1. 手动调节与自动调节的配合 + +- **手动调节后**:系统会自动暂停(或完全禁用,取决于配置) +- **立即恢复**:关闭后重新开启自动亮度即可立即恢复 +- **调整暂停时间**:可以修改 `manual-override-duration` 参数 + +### 2. 不同环境的优化 + +**明亮办公室**: +- 敏感度:0.4-0.6 +- 检测间隔:5-10秒 +- 变化阈值:20.0-30.0 + +**昏暗环境**: +- 敏感度:0.6-0.8 +- 检测间隔:3-5秒 +- 变化阈值:15.0-20.0 + +**户外使用**: +- 敏感度:0.8-1.2 +- 检测间隔:2-3秒 +- 变化阈值:10.0-15.0 + +### 3. 省电优化 + +如果您更关心电池续航: +- 增加检测间隔(10-30秒) +- 提高变化阈值(25.0-40.0) +- 降低敏感度(0.3-0.5) + +## 常见问题解决 + +### 问题1:功能不可用 + +**现象**:显示不支持自动亮度 + +**解决方法**: +```bash +# 检查传感器服务 +sudo systemctl status iio-sensor-proxy + +# 如果服务未运行,启动它 +sudo systemctl start iio-sensor-proxy +sudo systemctl enable iio-sensor-proxy +``` + +### 问题2:亮度不调节 + +**现象**:环境光变化但屏幕亮度不变 + +**解决方法**: +1. 检查是否在手动调节暂停期间 +2. 降低变化阈值 +3. 重新启用功能 + +```bash +# 方法1:降低变化阈值(更敏感) +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 10.0 + +# 方法2:重新启用(清除暂停状态) +dbus-send --session --dest=com.deepin.daemon.Display \ + /com/deepin/daemon/Display com.deepin.daemon.Display.SetAutoBrightnessEnabled \ + boolean:false +dbus-send --session --dest=com.deepin.daemon.Display \ + /com/deepin/daemon/Display com.deepin.daemon.Display.SetAutoBrightnessEnabled \ + boolean:true +``` + +### 问题3:调节太频繁 + +**现象**:屏幕亮度变化太频繁,影响使用 + +**解决方法**: +```bash +# 方法1:提高变化阈值(降低敏感度) +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 30.0 + +# 方法2:增加检测间隔 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 10 + +# 方法3:使用完整稳定配置 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.4 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 10 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 25.0 +``` + +### 问题4:反应太慢 + +**现象**:环境光变化后很久才调节亮度 + +**解决方法**: +```bash +# 方法1:降低变化阈值(更敏感) +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 10.0 + +# 方法2:减少检测间隔 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 2 + +# 方法3:使用完整快速响应配置 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.8 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 2 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 15.0 +``` + +### 问题5:亮度跳变严重 + +**现象**:亮度变化不平滑,有跳变 + +**解决方法**: +```bash +# 调整卡尔曼滤波器参数,使数据更平滑 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k kalman-process-noise --set -v 0.5 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k kalman-measurement-noise --set -v 0.1 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k kalman-window-size --set -v 5 +``` + +## 高级功能 + +### 查看详细状态 + +```bash +# 查看所有配置项 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness --list + +# 查看单个配置项 +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k enabled --get +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k sensitivity --get +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k polling-interval --get +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k change-threshold --get +dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k manual-override-duration --get + +# 查看运行状态(通过 D-Bus) +dbus-send --session --print-reply --dest=com.deepin.daemon.Display \ + /com/deepin/daemon/Display org.freedesktop.DBus.Properties.Get \ + string:com.deepin.daemon.Display string:AutoBrightnessSupported + +dbus-send --session --print-reply --dest=com.deepin.daemon.Display \ + /com/deepin/daemon/Display org.freedesktop.DBus.Properties.Get \ + string:com.deepin.daemon.Display string:AutoBrightnessEnabled +``` + +### 监控环境光变化 + +```bash +# 实时监控环境光强度变化 +watch -n 1 'dbus-send --session --print-reply --dest=com.deepin.daemon.Display \ + /com/deepin/daemon/Display org.freedesktop.DBus.Properties.Get \ + string:com.deepin.daemon.Display string:CurrentLightLevel' +``` + +### 查看日志 + +如果遇到问题,可以查看系统日志: + +```bash +# 查看自动亮度相关日志 +journalctl --user -u dde-daemon -f | grep AutoBrightness + +# 查看传感器服务日志 +journalctl -u iio-sensor-proxy -f +``` + +## 配置参数详解 + +### 敏感度 (sensitivity) + +敏感度控制环境光强度到屏幕亮度的转换比例: + +- **0.1-0.3**:低敏感度,适合希望屏幕亮度变化较小的用户 +- **0.4-0.6**:标准敏感度,适合大多数使用场景(推荐) +- **0.7-1.0**:高敏感度,适合需要明显亮度变化的场景 +- **1.1-3.0**:超高敏感度,仅在特殊场景使用 + +**计算公式**(默认线性映射):屏幕亮度 = (环境光强度 × 敏感度) / 1024.0 + +### 检测间隔 (polling_interval) + +控制多久检测一次环境光强度: + +- **1-2秒**:快速响应,适合移动办公,但会增加电量消耗 +- **3-5秒**:标准响应,平衡响应速度和电量消耗(推荐) +- **6-10秒**:慢速响应,适合固定办公环境 +- **11-60秒**:省电模式,适合电池续航优先的场景 + +### 环境光变化阈值 (change_threshold) + +环境光强度变化超过此值才会触发亮度调节: + +- **1.0-10.0**:低阈值,对环境光变化敏感,调节频繁 +- **11.0-25.0**:标准阈值,适合大多数场景(推荐) +- **26.0-50.0**:高阈值,只在环境光明显变化时才调节 + +**注意**:阈值过低会导致频繁调节,阈值过高会导致响应迟钝 + +### 亮度变化阈值 (brightness_change_threshold) + +目标亮度与当前亮度差值超过此值才执行调节: + +- **0.01**:默认值,1% 的亮度变化就调节 +- **0.05**:5% 的亮度变化才调节 +- **0.10**:10% 的亮度变化才调节 + +### 暂停时间 (manual_override_duration) + +手动调节亮度后,自动调节功能暂停的时间: + +- **60-180秒**:短暂停,适合频繁切换环境的场景 +- **181-600秒**:标准暂停,适合大多数场景(推荐 300秒) +- **601-1800秒**:长暂停,适合希望手动控制优先的场景 + +### 卡尔曼滤波器参数(高级) + +卡尔曼滤波器用于平滑传感器数据,减少噪声影响: + +- **kalman-process-noise (Q)**: 过程噪声,表示系统模型的不确定性 + - 较小值(0.1-0.5):更信任模型,数据更平滑但响应慢 + - 较大值(0.5-2.0):更信任测量,响应快但可能不平滑 + - 默认值:0.8 + +- **kalman-measurement-noise (R)**: 测量噪声,表示传感器噪声 + - 较小值(0.01-0.05):更信任传感器,响应快 + - 较大值(0.05-0.5):更信任估计值,更平滑 + - 默认值:0.05(会自适应调整) + +- **kalman-window-size**: 滑动窗口大小,用于计算方差 + - 较小值(2-5):快速响应变化 + - 较大值(5-10):更稳定的估计 + - 默认值:3 + +## 注意事项 + +1. **仅限内置屏幕**:外接显示器不支持此功能 +2. **传感器位置**:不要遮挡设备上的光线传感器 +3. **电量消耗**:频繁检测会增加电量消耗,建议合理设置检测间隔 +4. **手动优先**:手动调节亮度后会自动暂停功能或禁用功能 +5. **重启保持**:设置会自动保存,重启后依然有效 +6. **敏感度说明**:敏感度参数控制环境光到屏幕亮度的映射关系,值越大屏幕亮度变化越明显。推荐范围 0.3-1.0,默认 0.5 +7. **卡尔曼滤波器**:高级参数,普通用户无需调整。如果亮度跳变严重,可尝试调整 + +--- + +**提示**:如果您不熟悉命令行操作,建议使用系统设置界面进行配置(如果可用)。如有问题,可以随时关闭此功能,不会影响正常的手动亮度调节。 From 3ee61a5b8af9f5f2145b28771aeeb468e054c56a Mon Sep 17 00:00:00 2001 From: fuleyi Date: Thu, 7 May 2026 13:59:29 +0800 Subject: [PATCH 6/8] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E4=BA=AE=E5=BA=A6=E7=AE=A1=E7=90=86=E5=99=A8=E7=9A=84?= =?UTF-8?q?=E8=BD=AE=E8=AF=A2=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 移除了基于定时器的轮询机制,改为完全依赖传感器事件驱动 2. 删除了 pollInterval、stopChan、ticker、pollingWg 等轮询相关字段 3. 移除了 startPolling()、stopPolling()、pollLightLevel() 等轮询方法 4. 重构了补偿定时器逻辑,使其更简洁高效 5. 简化了状态管理,移除了 polling 状态字段 6. 优化了 adjustBrightnessOnce() 方法,直接从传感器获取当前光照值 7. manager.go 新增 isPowerSaving()、scheduleSystemAdjustingClear() 方法 Change-Id: I883ad02422f7aca35cd95374807350dae4635daa --- display1/auto_brightness.go | 283 ++++++++++++++---------------------- display1/manager.go | 31 +++- 2 files changed, 143 insertions(+), 171 deletions(-) diff --git a/display1/auto_brightness.go b/display1/auto_brightness.go index 0421d2022..54520dca4 100644 --- a/display1/auto_brightness.go +++ b/display1/auto_brightness.go @@ -70,16 +70,10 @@ type AutoBrightnessManager struct { lastAdjustTime time.Time lastBrightness float64 - pollInterval time.Duration - stopChan chan struct{} - ticker *time.Ticker - pollingWg sync.WaitGroup - kalmanFilter *brightness.AdaptiveKalmanFilter systemAdjusting bool running bool - polling bool lastSensorDataTime time.Time compensationTicker *time.Ticker @@ -98,12 +92,11 @@ type AutoBrightnessManager struct { // NewAutoBrightnessManager 创建新的自动亮度管理器 func NewAutoBrightnessManager() *AutoBrightnessManager { return &AutoBrightnessManager{ - lastLightLevel: -1, // 初始化为无效值 - lastBrightness: -1, // 初始化为无效值 + lastLightLevel: -1, + lastBrightness: -1, maxRetries: 3, retryInterval: time.Second * 2, gracefulDegradation: true, - stopChan: make(chan struct{}), // 初始化时创建 } } @@ -168,7 +161,6 @@ func (abm *AutoBrightnessManager) Initialize(manager *Manager) error { abm.config = config abm.supported = true abm.applyKalmanFilterConfig() - abm.pollInterval = time.Duration(abm.config.PollingInterval) * time.Second abm.mutex.Unlock() logger.Info("[AutoBrightness] AutoBrightnessManager initialized successfully") @@ -217,16 +209,22 @@ func (abm *AutoBrightnessManager) Start() error { abm.running = true abm.enabled = true abm.lastSensorDataTime = time.Now() - powerSaving := manager.powerSaving + powerSaving := manager.isPowerSaving() sessionActive := manager.sessionActive if !powerSaving && sessionActive { - abm.startPolling() abm.startCompensationTimer() } else { logger.Warningf("[AutoBrightness] Started but powerSaving [%v], session active [%v]", powerSaving, sessionActive) } abm.mutex.Unlock() + if !powerSaving && sessionActive { + go func() { + time.Sleep(time.Second) + abm.adjustBrightnessOnce() + }() + } + logger.Info("[AutoBrightness] AutoBrightnessManager started successfully") return nil } @@ -240,7 +238,6 @@ func (abm *AutoBrightnessManager) Stop() error { return nil } - abm.stopPolling() abm.stopCompensationTimer() abm.restoreSavedBrightness() @@ -271,7 +268,6 @@ func (abm *AutoBrightnessManager) Cleanup() error { var cleanupErrors []error // 停止运行 if abm.running { - abm.stopPolling() abm.stopCompensationTimer() abm.running = false } @@ -335,7 +331,7 @@ func (abm *AutoBrightnessManager) OnManualBrightnessChange() { logger.Debug("[AutoBrightness] Ignoring brightness change from system adjustment") return } - if !abm.running || !abm.polling { + if !abm.running { logger.Info("[AutoBrightness] Manual brightness change") return } @@ -375,18 +371,7 @@ func (abm *AutoBrightnessManager) OnManualBrightnessChange() { } } -// isManualOverrideActive 检查手动调节是否仍在生效 -func (abm *AutoBrightnessManager) isManualOverrideActive() bool { - abm.mutex.RLock() - defer abm.mutex.RUnlock() - if abm.manualOverride.IsZero() { - return false - } - duration := time.Duration(abm.config.ManualOverrideDuration) * time.Second - return time.Since(abm.manualOverride) < duration -} - -// resetHistoryState 重置历史状态,使下次轮询能立即触发亮度调节 +// resetHistoryState 重置历史状态,使下次能立即触发亮度调节 // 注意:此函数假设调用者已经持有锁 func (abm *AutoBrightnessManager) resetHistoryState() { abm.lastLightLevel = -1 @@ -402,21 +387,25 @@ func (abm *AutoBrightnessManager) hold() { return } - abm.stopPolling() abm.stopCompensationTimer() } -// resume 恢复自动亮度调节 func (abm *AutoBrightnessManager) resume() { abm.mutex.Lock() - defer abm.mutex.Unlock() + if !abm.running { + abm.mutex.Unlock() return } abm.resetHistoryState() - abm.startPolling() abm.startCompensationTimer() + abm.mutex.Unlock() + + go func() { + time.Sleep(time.Second) + abm.adjustBrightnessOnce() + }() } // OnConfigChanged 处理配置变更 @@ -425,23 +414,15 @@ func (abm *AutoBrightnessManager) OnConfigChanged(config AutoBrightnessConfig) { oldEnabled := abm.config.Enabled oldSensitivity := abm.config.Sensitivity abm.config = config - abm.pollInterval = time.Duration(config.PollingInterval) * time.Second + needStart := config.Enabled && !oldEnabled && !abm.running needStop := !config.Enabled && oldEnabled && abm.running - needRestart := config.Enabled == oldEnabled && abm.running sensitivityChanged := oldSensitivity != config.Sensitivity needImmediateAdjust := sensitivityChanged && abm.running && config.Enabled abm.applyKalmanFilterConfig() - if needRestart { - abm.stopPolling() - abm.stopCompensationTimer() - abm.startPolling() - abm.startCompensationTimer() - } - abm.mutex.Unlock() if needStart { @@ -454,8 +435,9 @@ func (abm *AutoBrightnessManager) OnConfigChanged(config AutoBrightnessConfig) { logger.Infof("[AutoBrightness] Sensitivity changed from %.2f to %.2f, triggering immediate adjustment", oldSensitivity, config.Sensitivity) go abm.adjustBrightnessOnce() } - logger.Infof("[AutoBrightness] Config updated: enabled=%v, sensitivity=%.2f, polling=%ds, threshold=%.1f", - config.Enabled, config.Sensitivity, config.PollingInterval, config.ChangeThreshold) + + logger.Infof("[AutoBrightness] Config updated: enabled=%v, sensitivity=%.2f, threshold=%.1f", + config.Enabled, config.Sensitivity, config.ChangeThreshold) } // applyKalmanFilterConfig 应用卡尔曼滤波器配置 @@ -561,7 +543,6 @@ func (abm *AutoBrightnessManager) claimLightWithRetry() error { // gracefulDegradeService 优雅降级服务 // 注意:此函数假设调用者已经持有锁 func (abm *AutoBrightnessManager) gracefulDegradeService() { - abm.stopPolling() abm.stopCompensationTimer() if abm.sensorClient != nil { @@ -605,108 +586,6 @@ func (abm *AutoBrightnessManager) loadConfig() error { return nil } -// startPolling 启动轮询定时器 -// 注意:此函数假设调用者已经持有锁(用于访问 abm.ticker 和 abm.pollInterval) -func (abm *AutoBrightnessManager) startPolling() { - logger.Debug("auto brightness polling start") - // 如果已经在运行,先停止 - if abm.polling { - abm.stopPolling() - } - pollInterval := abm.pollInterval - // 创建新的 ticker - abm.ticker = time.NewTicker(pollInterval) - abm.polling = true - // 增加 WaitGroup 计数 - abm.pollingWg.Add(1) - go func() { - defer abm.pollingWg.Done() - // 立即执行一次 - abm.pollLightLevel() - // 轮询循环 - for { - select { - case <-abm.ticker.C: - abm.pollLightLevel() - case <-abm.stopChan: - logger.Debug("auto brightness polling goroutine received stop signal") - return - } - } - }() -} - -// stopPolling 停止轮询定时器 -// 注意:此函数假设调用者已经持有锁(用于访问 abm.ticker) -// 重要:此函数是幂等的,可以安全地多次调用 -func (abm *AutoBrightnessManager) stopPolling() { - logger.Debug("auto brightness polling stop") - if !abm.polling { - return - } - if abm.ticker != nil { - abm.ticker.Stop() - abm.ticker = nil - } - // 发送停止信号给 goroutine(非阻塞) - select { - case abm.stopChan <- struct{}{}: - logger.Debug("auto brightness stop signal sent") - default: - // channel 已满,说明已经有停止信号在等待,无需重复发送 - logger.Debug("auto brightness stop signal already pending") - } - abm.polling = false - // 释放锁后等待 goroutine 退出 - // 注意:这里需要临时释放锁,避免死锁 - abm.mutex.Unlock() - abm.pollingWg.Wait() - abm.mutex.Lock() - // 清空 stopChan 中可能残留的信号 - select { - case <-abm.stopChan: - default: - } -} - -// pollLightLevel 轮询环境光强度 -func (abm *AutoBrightnessManager) pollLightLevel() { - // 检查是否正在运行 - abm.mutex.Lock() - if !abm.running { - abm.mutex.Unlock() - return - } - // 检查是否在手动调节暂停期间 - inManualOverride := abm.isInManualOverride() - abm.mutex.Unlock() - if inManualOverride { - return - } - if abm.sensorClient == nil { - logger.Warning("[AutoBrightness] SensorClient is nil") - return - } - // 如果不在手动调节期,确保传感器已被claim - if !abm.sensorClient.IsClaimed() { - err := abm.sensorClient.ClaimLight() - if err != nil { - logger.Warning("[AutoBrightness] Failed to re-claim light sensor:", err) - return - } - logger.Info("[AutoBrightness] Re-claimed light sensor after manual override period") - } - // 从传感器获取缓存的原始光照强度 - rawLightLevel, err := abm.sensorClient.GetCachedLightLevel() - if err != nil { - logger.Debugf("[AutoBrightness] Failed to get cached light level: %v", err) - return - } - - // 处理原始值(包括滤波和亮度调整) - abm.processLightChange(rawLightLevel) -} - // onServiceChange 服务状态变化回调 func (abm *AutoBrightnessManager) onServiceChange(available bool) { var shouldStart bool @@ -725,7 +604,6 @@ func (abm *AutoBrightnessManager) onServiceChange(available bool) { } else { logger.Warning("[AutoBrightness] SensorProxy service became unavailable") if abm.running { - abm.stopPolling() abm.stopCompensationTimer() abm.running = false abm.enabled = false @@ -748,12 +626,15 @@ func (abm *AutoBrightnessManager) onServiceChange(available bool) { func (abm *AutoBrightnessManager) onLightLevelChange(rawLightLevel int) { abm.mutex.Lock() running := abm.running - polling := abm.polling inManualOverride := abm.isInManualOverride() abm.lastSensorDataTime = time.Now() abm.mutex.Unlock() - if !running || !polling || inManualOverride { + if !running || inManualOverride { + return + } + + if abm.manager != nil && abm.manager.isPowerSaving() { return } @@ -767,23 +648,29 @@ func (abm *AutoBrightnessManager) adjustBrightnessOnce() { abm.mutex.Unlock() return } - // 使用上一次的环境光值重新计算亮度 - lastLight := abm.lastLightLevel - // 如果没有历史光照数据,先读取一次 - if lastLight == 0 { - abm.mutex.Unlock() - abm.pollLightLevel() + + sensorClient := abm.sensorClient + abm.mutex.Unlock() + + if sensorClient == nil { + return + } + + rawLightLevel, err := sensorClient.GetCachedLightLevel() + if err != nil { + logger.Warning("[AutoBrightness] Failed to get light level for immediate adjust:", err) return } - // 临时重置状态,让 shouldAdjustBrightness 的检查通过 + + abm.mutex.Lock() savedLightLevel := abm.lastLightLevel savedAdjustTime := abm.lastAdjustTime - abm.lastLightLevel = -1 // 设为负数,跳过环境光变化检查 - abm.lastAdjustTime = time.Time{} // 设为零值,跳过频率检查 + abm.lastLightLevel = -1 + abm.lastAdjustTime = time.Time{} abm.mutex.Unlock() - // 使用历史光照值重新计算并应用亮度 - abm.processLightChange(lastLight) - // 恢复状态(如果调整成功,lastLightLevel 和 lastAdjustTime 会被更新) + + abm.processLightChange(rawLightLevel) + abm.mutex.Lock() if abm.lastLightLevel < 0 { abm.lastLightLevel = savedLightLevel @@ -803,6 +690,11 @@ func (abm *AutoBrightnessManager) processLightChange(rawLightLevel int) { return } + if abm.manager != nil && abm.manager.isPowerSaving() { + abm.mutex.Unlock() + return + } + // 应用卡尔曼滤波器处理原始光值 if abm.kalmanFilter == nil { logger.Warning("[AutoBrightness] Kalman filter is nil, skipping light change processing") @@ -834,7 +726,7 @@ func (abm *AutoBrightnessManager) processLightChange(rawLightLevel int) { return } - // 更新状态 + // 设置成功后更新状态 abm.mutex.Lock() now := time.Now() abm.lastLightLevel = filteredLightLevel @@ -846,9 +738,14 @@ func (abm *AutoBrightnessManager) processLightChange(rawLightLevel int) { rawLightLevel, filteredLightLevel, targetBrightness*100) } -// needCompensation checks if compensation is needed and returns the sensor value -// Note: caller must hold abm.mutex -func (abm *AutoBrightnessManager) needCompensation() (bool, int) { +func (abm *AutoBrightnessManager) needCompensationWithClient(sensorClient *SensorProxyClient) (bool, int) { + if sensorClient == nil { + return false, 0 + } + + abm.mutex.RLock() + defer abm.mutex.RUnlock() + if abm.lastSensorDataTime.IsZero() { return false, 0 } @@ -861,7 +758,7 @@ func (abm *AutoBrightnessManager) needCompensation() (bool, int) { return false, 0 } - sensorValue, err := abm.sensorClient.GetCachedLightLevel() + sensorValue, err := sensorClient.GetCachedLightLevel() if err != nil { return false, 0 } @@ -872,22 +769,62 @@ func (abm *AutoBrightnessManager) needCompensation() (bool, int) { return diff > compensationThreshold, sensorValue } +func (abm *AutoBrightnessManager) ensureSensorClaimed(sensorClient *SensorProxyClient) bool { + if sensorClient == nil { + return false + } + if !sensorClient.IsClaimed() { + err := sensorClient.ClaimLight() + if err != nil { + logger.Warning("[AutoBrightness] Failed to re-claim light sensor:", err) + return false + } + logger.Info("[AutoBrightness] Re-claimed light sensor after manual override period") + abm.mutex.Lock() + abm.resetHistoryState() + abm.mutex.Unlock() + return true + } + return false +} + func (abm *AutoBrightnessManager) compensationTick() { abm.mutex.Lock() - if !abm.running || !abm.polling { + if !abm.running { abm.mutex.Unlock() return } inManualOverride := abm.isInManualOverride() - needComp, sensorValue := abm.needCompensation() + sensorClient := abm.sensorClient abm.mutex.Unlock() - if inManualOverride || !needComp { + if inManualOverride { + return + } + + if abm.manager != nil && abm.manager.isPowerSaving() { + return + } + + if sensorClient == nil { return } - logger.Infof("[AutoBrightness] Compensation: feeding sensor value %d lux to filter", sensorValue) - abm.processLightChange(sensorValue) + justReclaimed := abm.ensureSensorClaimed(sensorClient) + if justReclaimed { + rawLightLevel, err := sensorClient.GetCachedLightLevel() + if err == nil { + logger.Infof("[AutoBrightness] Triggering adjustment after sensor reclaimed: %d lux", rawLightLevel) + abm.processLightChange(rawLightLevel) + return + } + } + + needComp, sensorValue := abm.needCompensationWithClient(sensorClient) + if needComp { + logger.Infof("[AutoBrightness] Compensation: feeding sensor value %d lux", sensorValue) + abm.processLightChange(sensorValue) + } } func (abm *AutoBrightnessManager) startCompensationTimer() { @@ -911,6 +848,12 @@ func (abm *AutoBrightnessManager) startCompensationTimer() { }() } +// stopCompensationTimer 停止补偿定时器并等待补偿 goroutine 退出 +// 注意:调用者必须持有 abm.mutex 锁 +// +// 为什么需要 Unlock/Wait/Lock: +// compensationTick() 开头需要获取 mutex,如果调用者持有锁直接 Wait() 会死锁。 +// 因此必须先 Unlock 让 goroutine 能获取锁并检测到停止信号,Wait 等待其退出后,再 Lock 恢复锁状态。 func (abm *AutoBrightnessManager) stopCompensationTimer() { if abm.compensationTicker == nil { return diff --git a/display1/manager.go b/display1/manager.go index 9ea0f5837..3b26fad99 100644 --- a/display1/manager.go +++ b/display1/manager.go @@ -287,7 +287,9 @@ type Manager struct { backlightMinValue int32 backlightMidValue int32 - powerSaving bool + powerSaving bool + systemAdjustingTimer *time.Timer + systemAdjustingTimerMu sync.Mutex } type monitorSizeInfo struct { @@ -3609,6 +3611,12 @@ func (m *Manager) notifyManualBrightnessChange() { } // setSystemAdjusting 设置系统调整标志(用于区分系统自动调整和用户手动调整) +func (m *Manager) isPowerSaving() bool { + m.PropsMu.RLock() + defer m.PropsMu.RUnlock() + return m.powerSaving +} + func (m *Manager) setSystemAdjusting(adjusting bool) { if m.autoBrightnessManager != nil { m.autoBrightnessManager.setSystemAdjusting(adjusting) @@ -3629,8 +3637,29 @@ func (m *Manager) resumeAutoBrightness() { } } +// scheduleSystemAdjustingClear 延迟清除系统调整标志 +func (m *Manager) scheduleSystemAdjustingClear(delay time.Duration) { + m.systemAdjustingTimerMu.Lock() + defer m.systemAdjustingTimerMu.Unlock() + + if m.systemAdjustingTimer != nil { + m.systemAdjustingTimer.Stop() + } + m.systemAdjustingTimer = time.AfterFunc(delay, func() { + m.setSystemAdjusting(false) + logger.Debug("system adjusting flag cleared after delay") + }) +} + // cleanupAutoBrightness 清理自动亮度资源 func (m *Manager) cleanupAutoBrightness() { + m.systemAdjustingTimerMu.Lock() + if m.systemAdjustingTimer != nil { + m.systemAdjustingTimer.Stop() + m.systemAdjustingTimer = nil + } + m.systemAdjustingTimerMu.Unlock() + if m.autoBrightnessManager != nil { err := m.autoBrightnessManager.Cleanup() if err != nil { From 8de5d8d3ca0fc0350aa3d815e275cc617b759bb5 Mon Sep 17 00:00:00 2001 From: fuleyi Date: Thu, 7 May 2026 14:00:54 +0800 Subject: [PATCH 7/8] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E4=BA=AE=E5=BA=A6=E8=B0=83=E8=8A=82=E5=93=8D=E5=BA=94=E9=80=9F?= =?UTF-8?q?=E5=BA=A6=E5=92=8C=E5=87=86=E7=A1=AE=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 将补偿间隔从300毫秒缩短到150毫秒,提高亮度调节响应速度 2. 在resetHistoryState中增加卡尔曼滤波器重置和传感器数据时间重置 3. 移除resume方法中延迟1秒后执行adjustBrightnessOnce的逻辑 4. 将needCompensationWithClient中的GetCachedLightLevel改为GetLightLevel 5. 在RefreshBrightness方法中添加自动亮度启用时的跳过逻辑 6. 在SensorProxyClient中添加GetLightLevel方法,支持直接读取实时环境光强度 Change-Id: I00296ee3c0f0d776159566305909f35a23114b0f --- display1/auto_brightness.go | 13 ++++++------- display1/manager_ifc.go | 4 ++++ display1/sensor_proxy.go | 38 +++++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/display1/auto_brightness.go b/display1/auto_brightness.go index 54520dca4..7a4194c6d 100644 --- a/display1/auto_brightness.go +++ b/display1/auto_brightness.go @@ -17,7 +17,7 @@ import ( ) const ( - compensationInterval = 300 * time.Millisecond + compensationInterval = 150 * time.Millisecond sensorDataTimeout = 1 * time.Second compensationThreshold = 5.0 ) @@ -377,6 +377,10 @@ func (abm *AutoBrightnessManager) resetHistoryState() { abm.lastLightLevel = -1 abm.lastBrightness = -1 abm.lastAdjustTime = time.Time{} + abm.lastSensorDataTime = time.Time{} + if abm.kalmanFilter != nil { + abm.kalmanFilter.Reset() + } } // hold 暂停自动亮度调节 @@ -401,11 +405,6 @@ func (abm *AutoBrightnessManager) resume() { abm.resetHistoryState() abm.startCompensationTimer() abm.mutex.Unlock() - - go func() { - time.Sleep(time.Second) - abm.adjustBrightnessOnce() - }() } // OnConfigChanged 处理配置变更 @@ -758,7 +757,7 @@ func (abm *AutoBrightnessManager) needCompensationWithClient(sensorClient *Senso return false, 0 } - sensorValue, err := sensorClient.GetCachedLightLevel() + sensorValue, err := sensorClient.GetLightLevel() if err != nil { return false, 0 } diff --git a/display1/manager_ifc.go b/display1/manager_ifc.go index fd22bfb5b..2273be9ed 100644 --- a/display1/manager_ifc.go +++ b/display1/manager_ifc.go @@ -176,6 +176,10 @@ func (m *Manager) DeleteCustomMode(name string) *dbus.Error { // RefreshBrightness 重置亮度,主要被 session/power 模块调用。从配置恢复亮度。 func (m *Manager) RefreshBrightness() *dbus.Error { logger.Debug("dbus call RefreshBrightness") + if m.AutoBrightnessEnabled { + logger.Debug("auto brightness enabled, skip RefreshBrightness") + return nil + } monitors := m.getConnectedMonitors() monitorsId := monitors.getMonitorsId() configs := m.getSuitableSysMonitorConfigs(m.DisplayMode, monitorsId, monitors) diff --git a/display1/sensor_proxy.go b/display1/sensor_proxy.go index a42b4bec0..74d1330ef 100644 --- a/display1/sensor_proxy.go +++ b/display1/sensor_proxy.go @@ -205,6 +205,44 @@ func (c *SensorProxyClient) GetCachedLightLevel() (int, error) { return c.lastLightLevel, nil } +// GetLightLevel 获取实时的环境光强度(从 D-Bus 属性读取) +func (c *SensorProxyClient) GetLightLevel() (int, error) { + c.mutex.Lock() + if !c.serviceAvailable { + c.mutex.Unlock() + return 0, errors.New("SensorProxy service not available") + } + + if !c.hasAmbientLight { + c.mutex.Unlock() + return 0, errors.New("no ambient light sensor") + } + + if !c.claimed { + c.mutex.Unlock() + return 0, errors.New("light sensor not claimed") + } + + sensorProxy := c.sensorProxy + c.mutex.Unlock() + + if sensorProxy == nil { + return 0, errors.New("sensor proxy is nil") + } + + variant, err := sensorProxy.GetProperty(hadessProxyInterface + "." + propLightLevel) + if err != nil { + return 0, fmt.Errorf("failed to get LightLevel property: %w", err) + } + + lightLevel, ok := variant.Value().(float64) + if !ok { + return 0, errors.New("invalid LightLevel property type") + } + + return int(lightLevel), nil +} + // HasAmbientLight 检查是否有环境光传感器 func (c *SensorProxyClient) HasAmbientLight() (bool, error) { c.mutex.Lock() From 5a07ab828e36c0f655f2f02f1d0b0a1a3ee99596 Mon Sep 17 00:00:00 2001 From: fuleyi Date: Wed, 13 May 2026 13:19:26 +0800 Subject: [PATCH 8/8] refactor: optimize light sensor interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Refactor SensorProxyClient to use go-dbus-factory generated proxy instead of raw D-Bus calls 2. Move BrightnessTransition to brightness package as unified TransitionManager with TransitionExecutor 3. Separate setBrightness and setBrightnessWithTransition methods for clearer API 4. Add setColorTemperature method for independent color temperature control 5. Fix potential deadlocks in AutoBrightnessManager.OnManualBrightnessChange by releasing mutex before async operations 6. Fix setColorTempOneShot to read brightness from system config instead of monitor cache 7. Add power mode change listener to properly handle auto brightness during power saving 8. Add backlight controller caching and curve inverse function for percentage calculation 9. Remove unused functions: gracefulDegradeService, recoverService, loadConfig 10. Update transition config from time-based to step-percent based approach 11. Change default values: auto brightness and transition both disabled by default Influence: 1. Test auto brightness enable/disable functionality 2. Verify light sensor data acquisition works correctly with new proxy 3. Test brightness transition effects with different step-percent configurations 4. Verify manual brightness adjustment properly pauses auto brightness 5. Test color temperature changes without affecting brightness 6. Verify no deadlocks occur during rapid brightness changes 7. Test power saving mode transition with auto brightness enabled 8. Verify system wake from sleep properly restores auto brightness state 9. Test monitor hot-plug scenarios with transition enabled 10. Verify backlight and gamma brightness types work correctly refactor: 优化光感接口 1. 重构 SensorProxyClient,使用 go-dbus-factory 生成的代理替代原始 D-Bus 调用 2. 将 BrightnessTransition 移至 brightness 包,重构为统一的 TransitionManager 和 TransitionExecutor 3. 分离 setBrightness 和 setBrightnessWithTransition 方法,使 API 更清晰 4. 新增 setColorTemperature 方法,实现独立的色温控制 5. 修复 AutoBrightnessManager.OnManualBrightnessChange 中的潜在死锁,在 异步操作前释放互斥锁 6. 修复 setColorTempOneShot 从系统配置读取亮度而非显示器缓存 7. 新增电源模式变化监听,正确处理节能模式下的自动亮度 8. 新增背光控制器缓存和曲线逆函数计算百分比 9. 移除未使用的函数:gracefulDegradeService、recoverService、loadConfig 10. 将过渡配置从基于时间改为基于步进百分比的方式 11. 更改默认值:自动亮度和过渡默认均关闭 Influence: 1. 测试自动亮度开启/关闭功能 2. 验证新代理下光感数据获取正常工作 3. 测试不同步进百分比配置下的亮度过渡效果 4. 验证手动调节亮度正确暂停自动亮度 5. 测试色温变化不影响亮度 6. 验证快速亮度调节时不发生死锁 7. 测试开启自动亮度时的节能模式切换 8. 验证系统从睡眠唤醒正确恢复自动亮度状态 9. 测试开启过渡时的显示器热插拔场景 10. 验证背光和 Gamma 亮度类型正常工作 PMS: BUG-358023 Change-Id: I610f8dc65f0558b4727275c7124a92ee3ae6f631 --- display1/auto_brightness.go | 253 ++- display1/brightness.go | 183 ++- display1/brightness/brightness.go | 135 +- display1/brightness/brightness_transition.go | 597 +++++++ display1/brightness/colortemp.go | 15 +- display1/brightness/curve.go | 115 +- display1/brightness/kalman_filter.go | 140 +- display1/brightness_transition.go | 422 ----- display1/color_temp.go | 22 +- .../auto_backlight/auto_brightness_design.md | 1406 ----------------- ...77\347\224\250\346\214\207\345\215\227.md" | 393 ----- display1/manager.go | 292 +++- display1/manager_ifc.go | 11 + display1/manager_lid.go | 42 +- display1/monitor.go | 11 +- display1/sensor_proxy.go | 335 ++-- docs/auto_backlight/auto_brightness_design.md | 1013 +++++++++--- ...77\347\224\250\346\214\207\345\215\227.md" | 202 +-- go.mod | 4 +- go.sum | 8 +- .../org.deepin.Display.AutoBrightness.json | 2 +- misc/dsg-configs/org.deepin.Display.json | 17 +- 22 files changed, 2458 insertions(+), 3160 deletions(-) create mode 100644 display1/brightness/brightness_transition.go delete mode 100644 display1/brightness_transition.go delete mode 100644 display1/docs/auto_backlight/auto_brightness_design.md delete mode 100644 "display1/docs/auto_backlight/\350\207\252\345\212\250\344\272\256\345\272\246\345\212\237\350\203\275\344\275\277\347\224\250\346\214\207\345\215\227.md" diff --git a/display1/auto_brightness.go b/display1/auto_brightness.go index 7a4194c6d..7d748e9c9 100644 --- a/display1/auto_brightness.go +++ b/display1/auto_brightness.go @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 - 2026 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later package display1 @@ -8,10 +8,12 @@ import ( "fmt" "math" "sync" + "sync/atomic" "time" "github.com/godbus/dbus/v5" configManager "github.com/linuxdeepin/go-dbus-factory/org.desktopspec.ConfigManager" + sensorproxy "github.com/linuxdeepin/go-dbus-factory/system/net.hadess.sensorproxy" "github.com/linuxdeepin/dde-daemon/display1/brightness" ) @@ -20,6 +22,8 @@ const ( compensationInterval = 150 * time.Millisecond sensorDataTimeout = 1 * time.Second compensationThreshold = 5.0 + maxSensorLux = 1024.0 // 传感器最大勒克斯值 + minBrightnessLimit = 0.1 // 最小亮度限制 (10%) ) // AutoBrightnessConfig 自动亮度配置结构体 @@ -74,6 +78,7 @@ type AutoBrightnessManager struct { systemAdjusting bool running bool + stopping int32 // atomic: 1 when Stop is in progress lastSensorDataTime time.Time compensationTicker *time.Ticker @@ -134,7 +139,8 @@ func (abm *AutoBrightnessManager) Initialize(manager *Manager) error { return fmt.Errorf("cannot set brightness for builtin monitor: %s", builtinMonitor.Name) } - sensorClient := NewSensorProxyClient(manager.sysBus) + sensorProxy := sensorproxy.NewSensorProxy(manager.sysBus) + sensorClient := NewSensorProxyClient(sensorProxy, manager.dbusDaemon) abm.mutex.Lock() abm.sensorClient = sensorClient @@ -183,6 +189,12 @@ func (abm *AutoBrightnessManager) Start() error { return nil } + // 如果正在执行 Stop,不启动新实例 + if atomic.LoadInt32(&abm.stopping) != 0 { + abm.mutex.Unlock() + return errors.New("auto brightness is stopping") + } + if !abm.config.Enabled { abm.mutex.Unlock() return nil @@ -192,7 +204,7 @@ func (abm *AutoBrightnessManager) Start() error { manager := abm.manager abm.mutex.Unlock() - err := sensorClient.Connect() + err := sensorClient.Connect(manager.sysSigLoop) if err != nil { logger.Warning("[AutoBrightness] Failed to connect to sensor proxy:", err) return fmt.Errorf("failed to connect to sensor proxy: %w", err) @@ -238,7 +250,10 @@ func (abm *AutoBrightnessManager) Stop() error { return nil } + // 标记正在停止,防止窗口期内 Start() 创建新 ticker + atomic.StoreInt32(&abm.stopping, 1) abm.stopCompensationTimer() + atomic.StoreInt32(&abm.stopping, 0) abm.restoreSavedBrightness() if abm.sensorClient != nil { @@ -263,28 +278,23 @@ func (abm *AutoBrightnessManager) Stop() error { // Cleanup 清理资源 func (abm *AutoBrightnessManager) Cleanup() error { + // Stop 会处理 running 状态下的完整清理(补偿定时器、亮度恢复、传感器释放与断开) + if err := abm.Stop(); err != nil { + logger.Warning("[AutoBrightness] Stop during cleanup failed:", err) + } + abm.mutex.Lock() defer abm.mutex.Unlock() - var cleanupErrors []error - // 停止运行 - if abm.running { - abm.stopCompensationTimer() - abm.running = false - } - // 断开传感器连接 + + // Stop 在 running=false 时不会断开连接,需确保 sensorClient 被释放 if abm.sensorClient != nil { - err := abm.sensorClient.Disconnect() - if err != nil { + if err := abm.sensorClient.Disconnect(); err != nil { logger.Warning("[AutoBrightness] Failed to disconnect sensor client:", err) - cleanupErrors = append(cleanupErrors, err) } abm.sensorClient = nil } - // 注意:stopChan 和 transitionStop 不需要关闭,它们会一直存在直到对象被GC + logger.Info("[AutoBrightness] AutoBrightnessManager cleaned up") - if len(cleanupErrors) > 0 { - return cleanupErrors[0] // 返回第一个错误 - } return nil } @@ -325,21 +335,23 @@ func (abm *AutoBrightnessManager) setSystemAdjusting(adjusting bool) { // OnManualBrightnessChange 处理手动亮度调节 func (abm *AutoBrightnessManager) OnManualBrightnessChange() { abm.mutex.Lock() - defer abm.mutex.Unlock() // 如果是系统自动调整(如节能模式),忽略此次调用 if abm.systemAdjusting { logger.Debug("[AutoBrightness] Ignoring brightness change from system adjustment") + abm.mutex.Unlock() return } if !abm.running { logger.Info("[AutoBrightness] Manual brightness change") + abm.mutex.Unlock() return } // 检查配置:手动调节是否禁用自动模式 if abm.config.ManualAdjustDisablesAutoMode { - logger.Info("[AutoBrightness] Manual brightness change detected, disabling auto brightness mode") - // 禁用自动亮度功能 abm.config.Enabled = false + manager := abm.manager + abm.mutex.Unlock() + logger.Info("[AutoBrightness] Manual brightness change detected, disabling auto brightness mode") // 异步保存配置并停止功能 go func() { err := abm.saveConfig() @@ -352,19 +364,22 @@ func (abm *AutoBrightnessManager) OnManualBrightnessChange() { logger.Warning("[AutoBrightness] Failed to stop auto brightness:", err) } // 更新 Manager 属性 - if abm.manager != nil { - abm.manager.setPropAutoBrightnessEnabled(false) + if manager != nil { + manager.setPropAutoBrightnessEnabled(false) } }() return } - // 默认行为:临时暂停 abm.resetHistoryState() abm.manualOverride = time.Now() - logger.Infof("[AutoBrightness] Manual brightness change detected, pausing auto adjustment for %d seconds", abm.config.ManualOverrideDuration) - // 释放传感器,停止收集亮度数据 - if abm.sensorClient != nil && abm.sensorClient.IsClaimed() { - err := abm.sensorClient.ReleaseLight() + overrideDuration := abm.config.ManualOverrideDuration + sensorClient := abm.sensorClient + abm.mutex.Unlock() + + logger.Infof("[AutoBrightness] Manual brightness change detected, pausing auto adjustment for %d seconds", overrideDuration) + + if sensorClient != nil && sensorClient.IsClaimed() { + err := sensorClient.ReleaseLight() if err != nil { logger.Warning("[AutoBrightness] Failed to release light sensor on manual override:", err) } @@ -539,52 +554,6 @@ func (abm *AutoBrightnessManager) claimLightWithRetry() error { return lastErr } -// gracefulDegradeService 优雅降级服务 -// 注意:此函数假设调用者已经持有锁 -func (abm *AutoBrightnessManager) gracefulDegradeService() { - abm.stopCompensationTimer() - - if abm.sensorClient != nil { - err := abm.sensorClient.ReleaseLight() - if err != nil { - logger.Warning("[AutoBrightness] Failed to release light sensor during degradation:", err) - } - } - abm.running = false - abm.supported = false -} - -// recoverService 尝试恢复服务 -func (abm *AutoBrightnessManager) recoverService() error { - // 重新初始化传感器连接 - if abm.sensorClient != nil { - err := abm.sensorClient.Connect() - if err != nil { - return fmt.Errorf("failed to reconnect sensor during recovery: %w", err) - } - hasLight, err := abm.sensorClient.HasAmbientLight() - if err != nil || !hasLight { - return fmt.Errorf("ambient light sensor not available during recovery: %w", err) - } - abm.supported = true - if abm.config.Enabled { - return abm.Start() - } - } - return nil -} - -// loadConfig 加载配置 -// 注意:此函数假设调用者已经持有锁(如果需要修改 abm.config) -func (abm *AutoBrightnessManager) loadConfig() error { - config, err := abm.getConfig() - if err != nil { - return err - } - abm.config = config - return nil -} - // onServiceChange 服务状态变化回调 func (abm *AutoBrightnessManager) onServiceChange(available bool) { var shouldStart bool @@ -714,6 +683,9 @@ func (abm *AutoBrightnessManager) processLightChange(rawLightLevel int) { return } + // 记录时间戳(在释放锁之前),用于频率控制 + now := time.Now() + // 释放锁后再设置亮度(避免在渐变时持有锁) abm.mutex.Unlock() @@ -727,7 +699,6 @@ func (abm *AutoBrightnessManager) processLightChange(rawLightLevel int) { // 设置成功后更新状态 abm.mutex.Lock() - now := time.Now() abm.lastLightLevel = filteredLightLevel abm.lastBrightness = targetBrightness abm.lastAdjustTime = now @@ -839,7 +810,13 @@ func (abm *AutoBrightnessManager) startCompensationTimer() { for { select { case <-abm.compensationTicker.C: - abm.compensationTick() + // 检查停止信号,避免在持有锁的情况下调用 compensationTick() + select { + case <-abm.compensationStopCh: + return + default: + abm.compensationTick() + } case <-abm.compensationStopCh: return } @@ -849,25 +826,23 @@ func (abm *AutoBrightnessManager) startCompensationTimer() { // stopCompensationTimer 停止补偿定时器并等待补偿 goroutine 退出 // 注意:调用者必须持有 abm.mutex 锁 -// -// 为什么需要 Unlock/Wait/Lock: -// compensationTick() 开头需要获取 mutex,如果调用者持有锁直接 Wait() 会死锁。 -// 因此必须先 Unlock 让 goroutine 能获取锁并检测到停止信号,Wait 等待其退出后,再 Lock 恢复锁状态。 func (abm *AutoBrightnessManager) stopCompensationTimer() { if abm.compensationTicker == nil { return } + // 先停止 ticker,防止新的 tick 事件 abm.compensationTicker.Stop() abm.compensationTicker = nil + + // 关闭停止通道,通知 goroutine 退出 if abm.compensationStopCh != nil { close(abm.compensationStopCh) abm.compensationStopCh = nil } - abm.mutex.Unlock() + // 等待 goroutine 退出 abm.compensationWg.Wait() - abm.mutex.Lock() } // isInManualOverride 检查是否在手动调节暂停期间 @@ -887,15 +862,12 @@ func (abm *AutoBrightnessManager) calculateTargetBrightness(lightLevel int) floa br := brightness.GetAutoBrightnessValue(lightLevel) if br >= 0 { // 约束到有效范围 - if br < 0.0 { - br = 0.0 - } else if br > 1.0 { + if br > 1.0 { br = 1.0 } // 设置最小亮度,避免屏幕过暗 - minBrightness := 0.1 // 10% 最小亮度 - if br < minBrightness { - br = minBrightness + if br < minBrightnessLimit { + br = minBrightnessLimit } return br } @@ -905,7 +877,7 @@ func (abm *AutoBrightnessManager) calculateTargetBrightness(lightLevel int) floa adjustedLevel := float64(lightLevel) * abm.config.Sensitivity // 简单线性映射 - brightness := adjustedLevel / 1024.0 + brightness := adjustedLevel / maxSensorLux // 约束到有效范围 if brightness < 0.0 { @@ -915,9 +887,8 @@ func (abm *AutoBrightnessManager) calculateTargetBrightness(lightLevel int) floa } // 设置最小亮度,避免屏幕过暗 - minBrightness := 0.1 // 10% 最小亮度 - if brightness < minBrightness { - brightness = minBrightness + if brightness < minBrightnessLimit { + brightness = minBrightnessLimit } return brightness } @@ -955,6 +926,9 @@ func (abm *AutoBrightnessManager) shouldAdjustBrightness(lightLevel int, targetB // setBrightness 设置亮度(自动调节专用,不触发手动调节检测) func (abm *AutoBrightnessManager) setBrightness(value float64) error { + if abm.manager == nil { + return errors.New("manager is nil") + } // 使用内置显示器 builtinMonitor := abm.manager.getBuiltinMonitor() if builtinMonitor == nil { @@ -967,15 +941,17 @@ func (abm *AutoBrightnessManager) setBrightness(value float64) error { var err error if useTransition { // 使用渐变效果(即使全局渐变功能关闭) - if abm.manager.brightnessTransition != nil { - // 使用 SetBrightness 强制启用渐变,忽略全局 enabled 标志 - err = abm.manager.brightnessTransition.SetBrightness(builtinMonitor.Name, value, true) + if abm.manager.transitionManager != nil { + // 使用 setBrightnessWithTransition 强制启用渐变,忽略全局 enabled 标志 + err = abm.manager.setBrightnessWithTransition(builtinMonitor.Name, value) + + abm.manager.syncPropBrightness() + if err == nil { - // 渐变会在完成时自动同步属性,这里不需要再次同步 return nil } - // 如果渐变失败,回退到直接设置 - logger.Warning("[AutoBrightness] Transition failed, fallback to direct set:", err) + // 渐变失败,返回错误 + logger.Warning("[AutoBrightness] Transition failed:", err) return err } } @@ -990,19 +966,14 @@ func (abm *AutoBrightnessManager) setBrightness(value float64) error { } // restoreSavedBrightness 恢复配置中保存的亮度 -// 注意:此函数假设调用者已经持有锁 func (abm *AutoBrightnessManager) restoreSavedBrightness() { - // 使用内置显示器 builtinMonitor := abm.manager.getBuiltinMonitor() if builtinMonitor == nil { return } // 从配置中获取保存的亮度 - savedBrightness := abm.manager.getMonitorBrightness(builtinMonitor.Name) - if savedBrightness < 0 { - savedBrightness = 1.0 // 默认亮度 - } - // 恢复亮度(自动处理渐变和属性同步) + savedBrightness := abm.manager.getDefaultMonitorBrightness(builtinMonitor.Name) + err := abm.manager.setBrightnessAndSync(builtinMonitor.Name, savedBrightness) if err != nil { logger.Warning("[AutoBrightness] Failed to restore brightness:", err) @@ -1014,23 +985,21 @@ func (abm *AutoBrightnessManager) restoreSavedBrightness() { // checkSensorAvailability 检查传感器是否可用(不连接) func (abm *AutoBrightnessManager) checkSensorAvailability() error { // 临时连接以检查传感器 - err := abm.sensorClient.Connect() + err := abm.sensorClient.Connect(abm.manager.sysSigLoop) if err != nil { return fmt.Errorf("failed to connect to sensor proxy: %w", err) } + // 检查完成后立即断开连接 + // 实际使用时会在Start()中重新连接 + defer abm.sensorClient.Disconnect() // 检查是否有环境光传感器 hasLight, err := abm.sensorClient.HasAmbientLight() if err != nil { - abm.sensorClient.Disconnect() return fmt.Errorf("failed to check ambient light sensor: %w", err) } if !hasLight { - abm.sensorClient.Disconnect() return errors.New("no ambient light sensor available") } - // 检查完成后立即断开连接 - // 实际使用时会在Start()中重新连接 - abm.sensorClient.Disconnect() return nil } @@ -1127,7 +1096,7 @@ func (abm *AutoBrightnessManager) getConfig() (AutoBrightnessConfig, error) { config.Sensitivity = DefaultAutoBrightnessConfig.Sensitivity } } else { - logger.Warning("[AutoBrightness] Config convert faild, using default sensitivity") + logger.Warning("[AutoBrightness] Config convert failed, using default sensitivity") config.Sensitivity = DefaultAutoBrightnessConfig.Sensitivity } // ChangeThreshold @@ -1142,15 +1111,23 @@ func (abm *AutoBrightnessManager) getConfig() (AutoBrightnessConfig, error) { config.ChangeThreshold = DefaultAutoBrightnessConfig.ChangeThreshold } } else { - logger.Warning("[AutoBrightness] Config convert faild, using default changeThreshold") + logger.Warning("[AutoBrightness] Config convert failed, using default changeThreshold") config.ChangeThreshold = DefaultAutoBrightnessConfig.ChangeThreshold } // BrightnessChangeThreshold if val, err := abm.configManager.Value(0, DSettingsKeyABBrightnessChangeThreshold); err == nil { - config.BrightnessChangeThreshold = val.Value().(float64) + switch v := val.Value().(type) { + case float64: + config.BrightnessChangeThreshold = v + case int64: + config.BrightnessChangeThreshold = float64(v) + default: + logger.Warningf("[AutoBrightness] Invalid type for brightnessChangeThreshold: %T", v) + config.BrightnessChangeThreshold = DefaultAutoBrightnessConfig.BrightnessChangeThreshold + } } else { - logger.Warning("[AutoBrightness] Config convert faild, using default brightnessChangeThreshold") + logger.Warning("[AutoBrightness] Config convert failed, using default brightnessChangeThreshold") config.BrightnessChangeThreshold = DefaultAutoBrightnessConfig.BrightnessChangeThreshold } @@ -1165,7 +1142,7 @@ func (abm *AutoBrightnessManager) getConfig() (AutoBrightnessConfig, error) { config.PollingInterval = DefaultAutoBrightnessConfig.PollingInterval } } else { - logger.Warning("[AutoBrightness] Config convert faild, using default pollingInterval") + logger.Warning("[AutoBrightness] Config convert failed, using default pollingInterval") config.PollingInterval = DefaultAutoBrightnessConfig.PollingInterval } // ManualOverrideDuration @@ -1179,7 +1156,7 @@ func (abm *AutoBrightnessManager) getConfig() (AutoBrightnessConfig, error) { config.ManualOverrideDuration = DefaultAutoBrightnessConfig.ManualOverrideDuration } } else { - logger.Warning("[AutoBrightness] Config convert faild, using default manualOverrideDuration") + logger.Warning("[AutoBrightness] Config convert failed, using default manualOverrideDuration") config.ManualOverrideDuration = DefaultAutoBrightnessConfig.ManualOverrideDuration } // ManualAdjustDisablesAutoMode @@ -1190,7 +1167,7 @@ func (abm *AutoBrightnessManager) getConfig() (AutoBrightnessConfig, error) { config.ManualAdjustDisablesAutoMode = DefaultAutoBrightnessConfig.ManualAdjustDisablesAutoMode } } else { - logger.Warning("[AutoBrightness] Config convert faild, using default manualAdjustDisablesAutoMode") + logger.Warning("[AutoBrightness] Config convert failed, using default manualAdjustDisablesAutoMode") config.ManualAdjustDisablesAutoMode = DefaultAutoBrightnessConfig.ManualAdjustDisablesAutoMode } // UseTransition @@ -1201,7 +1178,7 @@ func (abm *AutoBrightnessManager) getConfig() (AutoBrightnessConfig, error) { config.UseTransition = DefaultAutoBrightnessConfig.UseTransition } } else { - logger.Warning("[AutoBrightness] Config convert faild, using default useTransition") + logger.Warning("[AutoBrightness] Config convert failed, using default useTransition") config.UseTransition = DefaultAutoBrightnessConfig.UseTransition } @@ -1244,7 +1221,15 @@ func (abm *AutoBrightnessManager) getConfig() (AutoBrightnessConfig, error) { // KalmanProcessNoise if val, err := abm.configManager.Value(0, DSettingsKeyABKalmanProcessNoise); err == nil { - config.KalmanProcessNoise = val.Value().(float64) + switch v := val.Value().(type) { + case float64: + config.KalmanProcessNoise = v + case int64: + config.KalmanProcessNoise = float64(v) + default: + logger.Warningf("[AutoBrightness] Invalid type for kalmanProcessNoise: %T", v) + config.KalmanProcessNoise = DefaultAutoBrightnessConfig.KalmanProcessNoise + } } else { logger.Warning("[AutoBrightness] Config convert failed, using default kalmanProcessNoise") config.KalmanProcessNoise = DefaultAutoBrightnessConfig.KalmanProcessNoise @@ -1252,7 +1237,15 @@ func (abm *AutoBrightnessManager) getConfig() (AutoBrightnessConfig, error) { // KalmanMeasurementNoise if val, err := abm.configManager.Value(0, DSettingsKeyABKalmanMeasurementNoise); err == nil { - config.KalmanMeasurementNoise = val.Value().(float64) + switch v := val.Value().(type) { + case float64: + config.KalmanMeasurementNoise = v + case int64: + config.KalmanMeasurementNoise = float64(v) + default: + logger.Warningf("[AutoBrightness] Invalid type for kalmanMeasurementNoise: %T", v) + config.KalmanMeasurementNoise = DefaultAutoBrightnessConfig.KalmanMeasurementNoise + } } else { logger.Warning("[AutoBrightness] Config convert failed, using default kalmanMeasurementNoise") config.KalmanMeasurementNoise = DefaultAutoBrightnessConfig.KalmanMeasurementNoise @@ -1288,44 +1281,48 @@ func (abm *AutoBrightnessManager) saveConfig() error { if abm.configManager == nil { return errors.New("config manager is nil") } + // 在锁内复制配置,避免数据竞争 + abm.mutex.RLock() + config := abm.config + abm.mutex.RUnlock() // 验证配置有效性 - err := abm.config.Validate() + err := config.Validate() if err != nil { return err } // 保存各个配置项 err = setGlobalDconfValue(DSettingsAutoBrightnessAppID, DSettingsAutoBrightnessName, "", - DSettingsKeyABEnabled, dbus.MakeVariant(abm.config.Enabled)) + DSettingsKeyABEnabled, dbus.MakeVariant(config.Enabled)) if err != nil { return err } err = setGlobalDconfValue(DSettingsAutoBrightnessAppID, DSettingsAutoBrightnessName, "", - DSettingsKeyABSensitivity, dbus.MakeVariant(abm.config.Sensitivity)) + DSettingsKeyABSensitivity, dbus.MakeVariant(config.Sensitivity)) if err != nil { return err } err = setGlobalDconfValue(DSettingsAutoBrightnessAppID, DSettingsAutoBrightnessName, "", - DSettingsKeyABChangeThreshold, dbus.MakeVariant(abm.config.ChangeThreshold)) + DSettingsKeyABChangeThreshold, dbus.MakeVariant(config.ChangeThreshold)) if err != nil { return err } err = setGlobalDconfValue(DSettingsAutoBrightnessAppID, DSettingsAutoBrightnessName, "", - DSettingsKeyABPollingInterval, dbus.MakeVariant(abm.config.PollingInterval)) + DSettingsKeyABPollingInterval, dbus.MakeVariant(config.PollingInterval)) if err != nil { return err } err = setGlobalDconfValue(DSettingsAutoBrightnessAppID, DSettingsAutoBrightnessName, "", - DSettingsKeyABManualOverride, dbus.MakeVariant(abm.config.ManualOverrideDuration)) + DSettingsKeyABManualOverride, dbus.MakeVariant(config.ManualOverrideDuration)) if err != nil { return err } err = setGlobalDconfValue(DSettingsAutoBrightnessAppID, DSettingsAutoBrightnessName, "", - DSettingsKeyABManualAdjustDisablesAutoMode, dbus.MakeVariant(abm.config.ManualAdjustDisablesAutoMode)) + DSettingsKeyABManualAdjustDisablesAutoMode, dbus.MakeVariant(config.ManualAdjustDisablesAutoMode)) if err != nil { return err } err = setGlobalDconfValue(DSettingsAutoBrightnessAppID, DSettingsAutoBrightnessName, "", - DSettingsKeyABUseTransition, dbus.MakeVariant(abm.config.UseTransition)) + DSettingsKeyABUseTransition, dbus.MakeVariant(config.UseTransition)) if err != nil { return err } diff --git a/display1/brightness.go b/display1/brightness.go index 62ff47b14..14229ee56 100644 --- a/display1/brightness.go +++ b/display1/brightness.go @@ -5,7 +5,6 @@ package display1 import ( - "encoding/json" "fmt" "math" "os" @@ -45,6 +44,11 @@ func (m *Manager) saveBrightnessInCfg(valueMap map[string]float64) error { } if config.UUID == monitor.uuid { + other := monitors.GetByUuidAndName(config.UUID, config.Name) + if other != nil && other != monitor { + // 存在其他的名字和UUID都对应配置的显示器,不要改该配置 + continue + } config.Name = name config.Brightness = v } @@ -110,25 +114,16 @@ func (m *Manager) changeBrightness(raised bool) error { return nil } -func (m *Manager) getSavedBrightnessTable() (map[string]float64, error) { - value := m.getBrightness() - if value == "" { - return nil, nil - } - var result map[string]float64 - err := json.Unmarshal([]byte(value), &result) - if err != nil { - return nil, err - } - return result, nil -} - func (m *Manager) initBrightness() { - brightnessTable, err := m.getSavedBrightnessTable() - if err != nil { - logger.Warning(err) + m.Brightness = make(map[string]float64) + monitors := m.getConnectedMonitors() + monitorsId := monitors.getMonitorsId() + configs := m.getSuitableSysMonitorConfigs(m.DisplayMode, monitorsId, monitors) + for _, config := range configs { + if config.Enabled { + m.Brightness[config.Name] = config.Brightness + } } - m.Brightness = brightnessTable } func (m *Manager) getSetterConfig() int { @@ -151,24 +146,6 @@ func (m *Manager) getSetterConfig() int { return int(v.Value().(int64)) } -// createBrightnessSetter 创建亮度设置闭包函数,用于亮度渐变管理器 -func (m *Manager) createBrightnessSetter(monitor *Monitor) func(float64) error { - temperature := m.getColorTemperatureValue() - if !isValidColorTempValue(int32(temperature)) { - temperature = defaultTemperatureManual - } - - isBuiltin := m.isBuiltinMonitor(monitor.Name) - setterConfig := m.getSetterConfig() - - logger.Debugf("Create brightness setter config %d for monitor %s, isBuiltin: %v, temperature: %v", setterConfig, monitor.Name, isBuiltin, temperature) - - // 返回一个只接收亮度值参数的闭包 - return func(brightnessValue float64) error { - return brightness.Set(brightnessValue, temperature, setterConfig, isBuiltin, monitor.ID, m.xConn) - } -} - // see also: gnome-desktop/libgnome-desktop/gnome-rr.c // // '_gnome_rr_output_name_is_builtin_display' @@ -200,26 +177,84 @@ func (m *Manager) isBuiltinMonitor(name string) bool { } func (m *Manager) setMonitorBrightness(monitor *Monitor, brightnessValue float64, forceTransition bool) error { + logger.Debug("setMonitorBrightness reality value:", brightnessValue) + + // 使用统一过渡管理器 + if m.transitionManager != nil { + return m.transitionManager.SetBrightness(monitor.Name, brightnessValue, forceTransition) + } + + // 降级:直接设置亮度 setter := m.createBrightnessSetter(monitor) if setter == nil { return fmt.Errorf("failed to create brightness setter for monitor %s", monitor.Name) } + return setter(brightnessValue) +} - logger.Debug("setMonitorBrightness reality value:", brightnessValue) +func (m *Manager) createBrightnessSetter(monitor *Monitor) func(float64) error { + isBuiltin := m.isBuiltinMonitor(monitor.Name) + _uuid := monitor.uuid + if _useWayland { + _uuid = monitor.uuidV0 + } + + // 获取当前色温值,用于 gamma 设置路径 + temperature := m.getColorTemperatureValue() + + setter := m.getSetterConfig() + + var setterFunc func(float64) error - if m.brightnessTransition != nil { - // TODO : 我们可以优化setter的使用来避免频繁create,但是需要处理屏幕插拔或其它变动信号 - m.brightnessTransition.SetBrightnessSetter(monitor.Name, setter) - return m.brightnessTransition.SetBrightness(monitor.Name, brightnessValue, forceTransition) - } else { - return setter(brightnessValue) + switch setter { + case brightness.BrightnessSetterBacklight: + setterFunc = func(brightnessValue float64) error { + return brightness.SetBacklight(brightnessValue) + } + case brightness.BrightnessSetterAuto: + if isBuiltin && brightness.SupportBacklight() { + setterFunc = func(brightnessValue float64) error { + return brightness.SetBacklight(brightnessValue) + } + } else { + setterFunc = func(brightnessValue float64) error { + return brightness.SetOutputGama(brightnessValue, temperature, monitor.ID, m.xConn, _uuid) + } + } + default: // BrightnessSetterGamma + setterFunc = func(brightnessValue float64) error { + return brightness.SetOutputGama(brightnessValue, temperature, monitor.ID, m.xConn, _uuid) + } } + + return setterFunc } -func (m *Manager) setBrightnessAux(fake bool, name string, value float64) error { +// setColorTemperature 设置色温(通过 gamma) +func (m *Manager) setColorTemperature(monitor *Monitor, brightnessVal float64) error { + temperature := m.getColorTemperatureValue() + logger.Debug("setColorTemperature", monitor.Name, temperature) + + isBuiltin := m.isBuiltinMonitor(monitor.Name) + _uuid := monitor.uuid + if _useWayland { + _uuid = monitor.uuidV0 + } + + // 内建显示器使用背光时,色温通过 gamma 设置(亮度为1) + if isBuiltin && brightness.SupportBacklight() { + brightnessVal = 1 + } + + return brightness.SetOutputGama(brightnessVal, temperature, monitor.ID, m.xConn, _uuid) +} + +func (m *Manager) setBrightness(name string, value float64) error { + logger.Debug("Starting brightness setting", name, value) monitors := m.getConnectedMonitors() monitor := monitors.GetByName(name) if monitor == nil { + logger.Debug("Monitor not found:", name) return InvalidOutputNameError{Name: name} } @@ -228,11 +263,14 @@ func (m *Manager) setBrightnessAux(fake bool, name string, value float64) error monitor.PropsMu.RUnlock() value = math.Round(value*1000) / 1000 // 通过该方法,用来对亮度值(亮度值范围为0-1)四舍五入保留小数点后三位有效数字 - if !fake && enabled { + if enabled { // 保持最小亮度,不能全黑 if value <= 0.1 { value = 0.1 + } else if value > 1 { + value = 1 } + err := m.setMonitorBrightness(monitor, value, false) if err != nil { logger.Warningf("failed to set brightness for %s: %v", name, err) @@ -242,19 +280,62 @@ func (m *Manager) setBrightnessAux(fake bool, name string, value float64) error monitor.setPropBrightnessWithLock(value) - return nil -} + logger.Debug("end set brightness", name, value) -func (m *Manager) setBrightness(name string, value float64) error { - return m.setBrightnessAux(false, name, value) + return nil } func (m *Manager) setBrightnessAndSync(name string, value float64) error { err := m.setBrightness(name, value) if err == nil { m.syncPropBrightness() - // 通知自动亮度管理器手动调节 - m.notifyManualBrightnessChange() } return err } + +// setBrightnessWithTransition 使用渐变效果设置亮度(强制启用渐变) +func (m *Manager) setBrightnessWithTransition(name string, value float64) error { + logger.Debug("Starting brightness setting with transition", name, value) + monitors := m.getConnectedMonitors() + monitor := monitors.GetByName(name) + if monitor == nil { + logger.Debug("Monitor not found:", name) + return InvalidOutputNameError{Name: name} + } + + monitor.PropsMu.RLock() + enabled := monitor.Enabled + monitor.PropsMu.RUnlock() + + value = math.Round(value*1000) / 1000 + if enabled { + if value <= 0.1 { + value = 0.1 + } else if value > 1 { + value = 1 + } + + err := m.setMonitorBrightness(monitor, value, true) + if err != nil { + logger.Warningf("failed to set brightness for %s: %v", name, err) + return err + } + } + + monitor.setPropBrightnessWithLock(value) + + logger.Debug("end set brightness with transition", name, value) + + return nil +} + +// getDefaultMonitorBrightness 获取默认显示器亮度(带 fallback 逻辑) +func (m *Manager) getDefaultMonitorBrightness(name string) float64 { + if v, ok := m.Brightness[name]; ok { + return v + } + if v, ok := m.Brightness["default"]; ok { + return v + } + return 1 +} diff --git a/display1/brightness/brightness.go b/display1/brightness/brightness.go index 5915b843b..2618a9bc5 100644 --- a/display1/brightness/brightness.go +++ b/display1/brightness/brightness.go @@ -7,12 +7,12 @@ package brightness import ( "fmt" "math" + "sync" "github.com/godbus/dbus/v5" backlight "github.com/linuxdeepin/go-dbus-factory/system/org.deepin.dde.backlighthelper1" displayBl "github.com/linuxdeepin/go-lib/backlight/display" "github.com/linuxdeepin/go-lib/log" - "github.com/linuxdeepin/go-lib/multierr" x "github.com/linuxdeepin/go-x11-client" "github.com/linuxdeepin/go-x11-client/ext/randr" ) @@ -42,52 +42,18 @@ func InitBacklightHelper() { helper = backlight.NewBacklight(sysBus) } -func Set(brightness float64, temperature int, setter int, isBuiltin bool, outputId uint32, conn *x.Conn) error { - if brightness < 0 { - brightness = 0 - } else if brightness > 1 { - brightness = 1 - } - +// SetOutputGama 设置 Gamma 亮度和色温 +func SetOutputGama(brightness float64, temperature int, outputId uint32, conn *x.Conn, uuid string) error { output := randr.Output(outputId) + return setOutputCrtcGamma(gammaSetting{ + brightness: brightness, + temperature: temperature, + }, output, conn) +} - // 亮度和色温分开设置,亮度用背光,色温用 gamma - setBlGamma := func() error { - var errs error - err := setBacklight(brightness, output, conn) - if err != nil { - errs = multierr.Append(errs, err) - } - - err = setOutputCrtcGamma(gammaSetting{ - brightness: 1, - temperature: temperature, - }, output, conn) - if err != nil { - errs = multierr.Append(errs, err) - } - return errs - } - - // 亮度和色温都用 gamma 值设置 - setGamma := func() error { - return setOutputCrtcGamma(gammaSetting{ - brightness: brightness, - temperature: temperature, - }, output, conn) - } - - setFn := setGamma - switch setter { - case BrightnessSetterBacklight: - setFn = setBlGamma - case BrightnessSetterAuto: - if isBuiltin && supportBacklight() { - setFn = setBlGamma - } - //case BrightnessSetterGamma - } - return setFn() +// SupportBacklight 检查是否支持背光调节 +func SupportBacklight() bool { + return supportBacklight() } // unused function @@ -206,16 +172,6 @@ func init() { } } -func setBacklight(value float64, output randr.Output, conn *x.Conn) error { - for _, controller := range controllers { - err := _setBacklight(value, controller) - if err != nil { - fmt.Printf("WARN: failed to set backlight %s: %v", controller.Name, err) - } - } - return nil -} - func _setBacklight(value float64, controller *displayBl.Controller) error { br := int32(float64(controller.MaxBrightness) * value) @@ -230,3 +186,72 @@ func _setBacklight(value float64, controller *displayBl.Controller) error { controller.Name, controller.MaxBrightness, value, br) return helper.SetBrightness(0, backlightTypeDisplay, controller.Name, br) } + +// backlightControllerCache 背光控制器缓存 +var ( + cachedBacklightControllers displayBl.Controllers + backlightControllersMu sync.RWMutex +) + +// getBacklightControllers 获取缓存的背光控制器列表 +func getBacklightControllers() displayBl.Controllers { + backlightControllersMu.RLock() + if len(cachedBacklightControllers) > 0 { + defer backlightControllersMu.RUnlock() + return cachedBacklightControllers + } + backlightControllersMu.RUnlock() + + backlightControllersMu.Lock() + defer backlightControllersMu.Unlock() + + // 双重检查 + if len(cachedBacklightControllers) > 0 { + return cachedBacklightControllers + } + + var err error + cachedBacklightControllers, err = displayBl.List() + if err != nil { + logger.Warningf("Failed to list backlight controllers: %v", err) + return nil + } + return cachedBacklightControllers +} + +// SetBacklight 设置背光亮度(供 TransitionManager 回调使用) +func SetBacklight(brightness float64) error { + controllers := getBacklightControllers() + if len(controllers) == 0 { + return fmt.Errorf("no backlight controllers available") + } + + for _, controller := range controllers { + err := _setBacklight(brightness, controller) + if err != nil { + logger.Warningf("Failed to set backlight %s: %v", controller.Name, err) + } + } + return nil +} + +// GetBacklightCurrentValue 获取当前背光亮度百分比 (0.0 - 1.0) +func GetBacklightCurrentValue() (float64, error) { + controllers := getBacklightControllers() + if len(controllers) == 0 { + return 0.5, fmt.Errorf("no backlight controllers available") + } + + // 使用第一个控制器的当前亮度 + controller := controllers[0] + currentBrightness, err := controller.GetActualBrightness() + if err != nil { + return 0.5, fmt.Errorf("failed to get current brightness: %v", err) + } + + if controller.MaxBrightness <= 0 { + return 0.5, fmt.Errorf("invalid max brightness: %d", controller.MaxBrightness) + } + + return float64(currentBrightness) / float64(controller.MaxBrightness), nil +} diff --git a/display1/brightness/brightness_transition.go b/display1/brightness/brightness_transition.go new file mode 100644 index 000000000..452ccacab --- /dev/null +++ b/display1/brightness/brightness_transition.go @@ -0,0 +1,597 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package brightness + +import ( + "context" + "fmt" + "sync" + "time" +) + +// BrightnessType 亮度类型 +type BrightnessType int + +const ( + // TypeUnknown 未知类型 + TypeUnknown BrightnessType = iota + // TypeBacklight 背光调节 + TypeBacklight + // TypeGamma Gamma 调节 + TypeGamma +) + +const ( + // DefaultMinStepInterval 默认最小步进间隔 + DefaultMinStepInterval = 100 * time.Millisecond + + // epsilon 用于浮点数比较的极小值 + epsilon = 1e-6 +) + +// TransitionConfig 统一过渡配置 +type TransitionConfig struct { + // 是否启用过渡 + Enabled bool + + // 总调节时长(毫秒) + Duration time.Duration + + // 步进百分比(如 0.1 表示 0.1%) + StepPercent float64 + + // 最小步进间隔(默认100ms) + MinStepInterval time.Duration +} + +// DefaultTransitionConfig 默认过渡配置 +func DefaultTransitionConfig() TransitionConfig { + return TransitionConfig{ + Enabled: true, + Duration: 4000 * time.Millisecond, + StepPercent: 1, // 1% 步进 + MinStepInterval: DefaultMinStepInterval, // 默认 100ms + } +} + +// Validate 验证配置有效性 +func (c *TransitionConfig) Validate() error { + if c.Duration <= 0 { + c.Duration = 4000 * time.Millisecond + } + + if c.StepPercent <= 0 { + c.StepPercent = 1 + } + + // 确保最小步进间隔不小于默认值 + if c.MinStepInterval < 0 { + c.MinStepInterval = DefaultMinStepInterval + } + + return nil +} + +// transitionTask 过渡任务 +type transitionTask struct { + ctx context.Context + cancel context.CancelFunc + target float64 + startValue float64 + config TransitionConfig +} + +// TransitionExecutor 统一过渡执行器 +type TransitionExecutor struct { + mu sync.Mutex + + // 显示器名称 + monitorName string + + // 亮度类型 + brightnessType BrightnessType + + // 当前正在进行的调节任务 + currentTask *transitionTask + + // 当前实时亮度百分比 + currentPercent float64 + hasCurrentPercent bool + + // 亮度设置函数(百分比) + setterFunc func(percent float64) error + + // 获取当前亮度百分比函数 + getterFunc func() (float64, error) + + // 配置参数 + config TransitionConfig + + // goroutine 追踪 + wg sync.WaitGroup +} + +// NewTransitionExecutor 创建新的过渡执行器 +func NewTransitionExecutor(monitorName string, brightnessType BrightnessType, setter func(float64) error, getter func() (float64, error), config TransitionConfig) *TransitionExecutor { + config.Validate() + + return &TransitionExecutor{ + monitorName: monitorName, + brightnessType: brightnessType, + setterFunc: setter, + getterFunc: getter, + config: config, + } +} + +// SetBrightness 设置亮度(百分比,0.0 - 1.0) +func (e *TransitionExecutor) SetBrightness(targetPercent float64) error { + return e.SetBrightnessWithForce(targetPercent, false) +} + +// SetBrightnessWithForce 设置亮度,支持强制过渡 +func (e *TransitionExecutor) SetBrightnessWithForce(targetPercent float64, forceTransition bool) error { + e.mu.Lock() + defer e.mu.Unlock() + + // 如果过渡未启用且不是强制过渡,直接设置 + if !e.config.Enabled && !forceTransition { + return e.setterFunc(targetPercent) + } + + // 获取当前亮度百分比 + var currentPercent float64 + var err error + + // 如果正在过渡,使用实时值 + if e.hasCurrentPercent && e.currentTask != nil { + currentPercent = e.currentPercent + } else { + currentPercent, err = e.getterFunc() + if err != nil { + logger.Warningf("[%s] Failed to get current brightness: %v", e.monitorName, err) + return e.setterFunc(targetPercent) + } + } + + // 如果目标值等于当前值,不需要调节 + if abs(currentPercent-targetPercent) < epsilon { + return nil + } + + // 取消当前正在进行的任务 + if e.currentTask != nil { + e.cancelTask(e.currentTask) + e.currentTask = nil + } + + // 创建新的调节任务 + ctx, cancel := e.createContext() + task := &transitionTask{ + ctx: ctx, + cancel: cancel, + target: targetPercent, + startValue: currentPercent, + config: e.config, + } + + e.currentTask = task + e.currentPercent = currentPercent + e.hasCurrentPercent = true + + // 启动过渡协程 + e.wg.Add(1) + go func() { + defer e.wg.Done() + e.runTransition(task) + }() + + return nil +} + +// createContext 创建上下文 +func (e *TransitionExecutor) createContext() (context.Context, context.CancelFunc) { + return context.WithCancel(context.Background()) +} + +// cancelTask 取消任务 +func (e *TransitionExecutor) cancelTask(task *transitionTask) { + if task != nil && task.cancel != nil { + task.cancel() + } +} + +// isTaskCancelled 检查任务是否被取消 +func (e *TransitionExecutor) isTaskCancelled(ctx context.Context) bool { + select { + case <-ctx.Done(): + return true + default: + return false + } +} + +// abs 计算绝对值 +func abs(x float64) float64 { + if x < 0 { + return -x + } + return x +} + +// runTransition 执行过渡 +func (e *TransitionExecutor) runTransition(task *transitionTask) { + defer func() { + e.mu.Lock() + if e.currentTask == task { + e.currentTask = nil + } + e.mu.Unlock() + }() + + // 如果差值很小,直接设置目标值 + if abs(task.target-task.startValue) < epsilon { + err := e.setterFunc(task.target) + if err != nil { + logger.Warningf("[%s] Failed to set brightness %.2f%%: %v", e.monitorName, task.target*100, err) + } + e.mu.Lock() + e.currentPercent = task.target + e.hasCurrentPercent = false + e.mu.Unlock() + return + } + + // 计算过渡参数 + totalSteps, stepSize, stepInterval := e.calculateTransitionParams(task) + + if totalSteps == 0 { + return + } + + logger.Debugf("[%s] Start %s transition: %.2f%% -> %.2f%%, steps: %d, step size: %.4f%%, interval: %v", + e.monitorName, e.typeName(), task.startValue*100, task.target*100, totalSteps, stepSize*100, stepInterval) + + // 执行等间隔过渡 + currentPercent := task.startValue + ticker := time.NewTicker(stepInterval) + defer ticker.Stop() + + for step := 1; step <= totalSteps; step++ { + // 检查是否被取消 + if e.isTaskCancelled(task.ctx) { + logger.Debugf("[%s] Transition cancelled at %.2f%%", e.monitorName, currentPercent*100) + return + } + + // 计算当前步骤的目标百分比 + currentPercent += stepSize + + // 确保不超过最终目标值 + if (stepSize > 0 && currentPercent > task.target) || (stepSize < 0 && currentPercent < task.target) { + currentPercent = task.target + } + + // 设置亮度 + err := e.setterFunc(currentPercent) + if err != nil { + logger.Warningf("[%s] Failed to set brightness %.2f%%: %v", e.monitorName, currentPercent*100, err) + return + } + + // 更新实时值 + e.mu.Lock() + e.currentPercent = currentPercent + e.mu.Unlock() + + // 如果达到目标值,结束过渡 + if abs(currentPercent-task.target) < epsilon { + logger.Debugf("[%s] Transition completed: %.2f%% -> %.2f%%", e.monitorName, task.startValue*100, task.target*100) + e.mu.Lock() + e.hasCurrentPercent = false + e.mu.Unlock() + return + } + + // 等待下一个步进时间 + if step < totalSteps { + select { + case <-task.ctx.Done(): + logger.Debugf("[%s] Transition cancelled at %.2f%%", e.monitorName, currentPercent*100) + return + case <-ticker.C: + // 继续下一步 + } + } + } + + logger.Debugf("[%s] Transition completed: %.2f%% -> %.2f%%", e.monitorName, task.startValue*100, task.target*100) + e.mu.Lock() + e.hasCurrentPercent = false + e.mu.Unlock() +} + +// calculateTransitionParams 计算过渡参数 +func (e *TransitionExecutor) calculateTransitionParams(task *transitionTask) (totalSteps int, stepSize float64, stepInterval time.Duration) { + deltaPercent := task.target - task.startValue + if abs(deltaPercent) < epsilon { + return 0, 0, 0 + } + + // 计算总步数:总百分比变化 / 步进百分比 + // 例如:变化 50%,步进 0.1%,则步数 = 50 / 0.1 = 500 步 + totalSteps = int(abs(deltaPercent*100) / task.config.StepPercent) + if totalSteps <= 0 { + totalSteps = 1 + } + + // 计算每步的百分比变化 + stepSize = deltaPercent / float64(totalSteps) + + // 计算每步的时间间隔 + stepInterval = task.config.Duration / time.Duration(totalSteps) + + // 确保间隔不小于最小间隔 + // 如果间隔太小,则根据最小间隔重新计算步数 + if stepInterval < task.config.MinStepInterval { + totalSteps = int(task.config.Duration / task.config.MinStepInterval) + if totalSteps <= 0 { + totalSteps = 1 + } + stepSize = deltaPercent / float64(totalSteps) + stepInterval = task.config.MinStepInterval + } + return totalSteps, stepSize, stepInterval +} + +// UpdateConfig 更新配置 +func (e *TransitionExecutor) UpdateConfig(config TransitionConfig) { + e.mu.Lock() + defer e.mu.Unlock() + + if e.currentTask != nil && (e.config.Duration != config.Duration) { + e.cancelTask(e.currentTask) + e.currentTask = nil + logger.Debugf("[%s] Config updated, cancelled current transition", e.monitorName) + } + + config.Validate() + e.config = config +} + +// Stop 停止当前过渡并等待 goroutine 退出 +func (e *TransitionExecutor) Stop() { + e.mu.Lock() + if e.currentTask != nil { + e.cancelTask(e.currentTask) + e.currentTask = nil + } + e.hasCurrentPercent = false + e.mu.Unlock() + + // 等待 goroutine 退出(需要在锁外等待,否则死锁) + e.wg.Wait() +} + +// IsRunning 检查是否正在执行过渡 +func (e *TransitionExecutor) IsRunning() bool { + e.mu.Lock() + defer e.mu.Unlock() + return e.currentTask != nil +} + +// typeName 获取类型名称 +func (e *TransitionExecutor) typeName() string { + switch e.brightnessType { + case TypeBacklight: + return "Backlight" + case TypeGamma: + return "Gamma" + default: + return "Unknown" + } +} + +// TransitionManager 统一过渡管理器 +// 按显示器名称管理所有显示器的亮度过渡 +type TransitionManager struct { + mu sync.Mutex + + // 执行器映射(按显示器名称) + executors map[string]*TransitionExecutor + + // 配置 + config TransitionConfig + + // 获取显示器亮度类型的回调 + getBrightnessTypeFunc func(monitorName string) BrightnessType + + // 设置 Backlight 亮度的回调(百分比) + setBacklightFunc func(percent float64) error + + // 获取 Backlight 当前亮度的回调(百分比) + getBacklightFunc func() (float64, error) + + // 设置 Gamma 亮度的回调 + setGammaFunc func(monitorName string, percent float64) error + + // 获取 Gamma 当前亮度的回调 + getGammaFunc func(monitorName string) (float64, error) +} + +// NewTransitionManager 创建统一过渡管理器 +func NewTransitionManager() *TransitionManager { + return &TransitionManager{ + executors: make(map[string]*TransitionExecutor), + config: DefaultTransitionConfig(), + } +} + +// SetEnabled 设置是否启用过渡 +func (m *TransitionManager) SetEnabled(enabled bool) { + m.mu.Lock() + defer m.mu.Unlock() + m.config.Enabled = enabled + for _, executor := range m.executors { + executor.UpdateConfig(m.config) + } +} + +// SetDuration 设置过渡时长(毫秒) +func (m *TransitionManager) SetDuration(durationMs int) { + m.mu.Lock() + defer m.mu.Unlock() + m.config.Duration = time.Duration(durationMs) * time.Millisecond + for _, executor := range m.executors { + executor.UpdateConfig(m.config) + } +} + +// SetStepPercent 设置步进百分比 +func (m *TransitionManager) SetStepPercent(stepPercent float64) { + m.mu.Lock() + defer m.mu.Unlock() + m.config.StepPercent = stepPercent + for _, executor := range m.executors { + executor.UpdateConfig(m.config) + } +} + +// SetMinStepInterval 设置最小步进间隔(毫秒) +func (m *TransitionManager) SetMinStepInterval(intervalMs int) { + m.mu.Lock() + defer m.mu.Unlock() + m.config.MinStepInterval = time.Duration(intervalMs) * time.Millisecond + if m.config.MinStepInterval < 0 { + m.config.MinStepInterval = DefaultMinStepInterval + } + for _, executor := range m.executors { + executor.UpdateConfig(m.config) + } +} + +// SetGetBrightnessTypeFunc 设置获取亮度类型的回调 +func (m *TransitionManager) SetGetBrightnessTypeFunc(fn func(monitorName string) BrightnessType) { + m.mu.Lock() + defer m.mu.Unlock() + m.getBrightnessTypeFunc = fn +} + +// SetBacklightFuncs 设置 Backlight 回调 +func (m *TransitionManager) SetBacklightFuncs( + setFunc func(percent float64) error, + getFunc func() (float64, error), +) { + m.mu.Lock() + defer m.mu.Unlock() + m.setBacklightFunc = setFunc + m.getBacklightFunc = getFunc +} + +// SetGammaFuncs 设置 Gamma 回调 +func (m *TransitionManager) SetGammaFuncs( + setFunc func(monitorName string, percent float64) error, + getFunc func(monitorName string) (float64, error), +) { + m.mu.Lock() + defer m.mu.Unlock() + m.setGammaFunc = setFunc + m.getGammaFunc = getFunc +} + +// SetBrightness 设置指定显示器的亮度(百分比,0.0 - 1.0) +func (m *TransitionManager) SetBrightness(monitorName string, targetPercent float64, forceTransition bool) error { + m.mu.Lock() + executor, exists := m.executors[monitorName] + config := m.config + getBrightnessType := m.getBrightnessTypeFunc + m.mu.Unlock() + + // 如果执行器不存在,创建新的 + if !exists { + var brightnessType BrightnessType = TypeGamma // 默认 Gamma + if getBrightnessType != nil { + brightnessType = getBrightnessType(monitorName) + } + + executor = m.createExecutor(monitorName, brightnessType, config) + if executor == nil { + return fmt.Errorf("failed to create executor for monitor %s", monitorName) + } + + m.mu.Lock() + m.executors[monitorName] = executor + m.mu.Unlock() + } + + return executor.SetBrightnessWithForce(targetPercent, forceTransition) +} + +// createExecutor 创建执行器 +func (m *TransitionManager) createExecutor(monitorName string, brightnessType BrightnessType, config TransitionConfig) *TransitionExecutor { + m.mu.Lock() + setBacklight := m.setBacklightFunc + getBacklight := m.getBacklightFunc + setGamma := m.setGammaFunc + getGamma := m.getGammaFunc + m.mu.Unlock() + + switch brightnessType { + case TypeBacklight: + setter := func(percent float64) error { + if setBacklight == nil { + return fmt.Errorf("backlight setter not configured") + } + return setBacklight(percent) + } + + getter := func() (float64, error) { + if getBacklight == nil { + return 0.5, fmt.Errorf("backlight getter not configured") + } + return getBacklight() + } + + return NewTransitionExecutor(monitorName, TypeBacklight, setter, getter, config) + + default: // TypeGamma + setter := func(percent float64) error { + if setGamma != nil { + return setGamma(monitorName, percent) + } + return fmt.Errorf("gamma setter not configured") + } + + getter := func() (float64, error) { + if getGamma == nil { + return 0.5, fmt.Errorf("gamma getter not configured") + } + return getGamma(monitorName) + } + + return NewTransitionExecutor(monitorName, TypeGamma, setter, getter, config) + } +} + +// Stop 停止所有过渡 +func (m *TransitionManager) Stop() { + m.mu.Lock() + executors := make([]*TransitionExecutor, 0, len(m.executors)) + for _, executor := range m.executors { + executors = append(executors, executor) + } + m.mu.Unlock() + + for _, executor := range executors { + executor.Stop() + } +} + +// GetConfig 获取当前配置 +func (m *TransitionManager) GetConfig() TransitionConfig { + m.mu.Lock() + defer m.mu.Unlock() + return m.config +} diff --git a/display1/brightness/colortemp.go b/display1/brightness/colortemp.go index 00909b5a5..6a0be3cc2 100644 --- a/display1/brightness/colortemp.go +++ b/display1/brightness/colortemp.go @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2022 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -271,8 +271,17 @@ type gammaSetting struct { } func fillColorRamp(gammaR, gammaG, gammaB []uint16, setting gammaSetting) { - alpha := float64(setting.temperature%100) / 100.0 - tempIndex := ((setting.temperature - 1000) / 100) * 3 + temperature := setting.temperature + if temperature < 1000 { + temperature = 1000 + } + maxTemp := 1000 + (len(blackBodyColor)/3-1)*100 + if temperature > maxTemp { + temperature = maxTemp + } + + alpha := float64(temperature%100) / 100.0 + tempIndex := ((temperature - 1000) / 100) * 3 // Approximate white point whitePoint := interpolateColor(alpha, blackBodyColor[tempIndex:tempIndex+3], diff --git a/display1/brightness/curve.go b/display1/brightness/curve.go index e09b1129d..892409d48 100644 --- a/display1/brightness/curve.go +++ b/display1/brightness/curve.go @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2018 - 2026 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -311,9 +311,7 @@ func (cm *CurveManager) parseCustomBrightnessCurves(jsonStr string) error { } // 存储 boardName - cm.mu.Lock() cm.configBoardName = config.BoardName - cm.mu.Unlock() if config.Curves == nil { return fmt.Errorf("Custom curve json contains nothing") @@ -615,6 +613,9 @@ func (cm *CurveManager) generateAutoBrightnessCurveFunc(points []AutoBrightnessC if luxFloat >= float64(p1.Lux) && luxFloat <= float64(p2.Lux) { x1, x2 := float64(p1.Lux), float64(p2.Lux) y1, y2 := p1.Br, p2.Br + if math.Abs(x1-x2) < 1e-6 { + return y1 / 100.0 + } br := y1 + (y2-y1)*(luxFloat-x1)/(x2-x1) return br / 100.0 } @@ -642,3 +643,111 @@ func (cm *CurveManager) hasAutoBrightnessCurve() bool { defer cm.mu.RUnlock() return cm.autoBrightnessCurveFunc != nil } + +// GetBacklightCurvePercent 返回当前控制器的实际亮度值对应的百分比 +func GetBacklightCurvePercent(c *displayBl.Controller) float64 { + return _curveManager.getBacklightCurvePercent(c) +} + +// 返回当前控制器的实际亮度值对应的百分比 +func (cm *CurveManager) getBacklightCurvePercent(c *displayBl.Controller) float64 { + currentBrightness, err := c.GetBrightness() + if err != nil { + return 1.0 + } + // 其他曲线需根据曲线类型进行逆函数查找 + if cm.curveType == "flm" { + if cm.flmCurveFunc == nil { + logger.Warning("FLM curve function is not initialized") + return float64(currentBrightness) / float64(c.MaxBrightness) + } + + return findBrightnessPercentByCurve(int32(currentBrightness), cm.flmCurveFunc, cm.backLightMinValue, cm.backlightMidValue, int32(c.MaxBrightness)) + } + return float64(currentBrightness) / float64(c.MaxBrightness) +} + +// findBrightnessPercentByCurve 通过背光曲线逆函数查找对应的百分比值 +func findBrightnessPercentByCurve(targetBrightness int32, curveFunc func(float64, int) int32, backLightMinValue int32, backlightMidValue int32, maxBrightness int32) float64 { + const ( + x1 = 10.0 + x2 = 100.0 + maxIterations = 50 + tolerance = 0.01 + ) + + // 边界条件检查 + if targetBrightness <= backLightMinValue { + return x1 + } + if targetBrightness >= maxBrightness { + return x2 + } + + // 计算中间点的亮度值,用于确定搜索范围 + midBrightness := curveFunc(float64(backlightMidValue), int(maxBrightness)) + + // 根据目标亮度值确定搜索范围 + var left, right float64 + if targetBrightness <= midBrightness { + left = x1 + right = float64(backlightMidValue) + } else { + left = float64(backlightMidValue) + right = x2 + } + + // 预计算边界值,避免重复计算 + leftBrightness := curveFunc(left, int(maxBrightness)) + rightBrightness := curveFunc(right, int(maxBrightness)) + + if leftBrightness == targetBrightness { + return left + } + if rightBrightness == targetBrightness { + return right + } + + // 使用二分查找法查找对应的百分比值 + iterations := 0 + var bestResult float64 + var bestDiff int32 = maxBrightness + + for right-left > tolerance && iterations < maxIterations { + mid := (left + right) / 2 + calculatedBrightness := curveFunc(mid, int(maxBrightness)) + + diff := calculatedBrightness - targetBrightness + if diff < 0 { + diff = -diff + } + + if diff < bestDiff { + bestDiff = diff + bestResult = mid + } + + if calculatedBrightness < targetBrightness { + left = mid + } else if calculatedBrightness > targetBrightness { + right = mid + } else { + return mid + } + iterations++ + } + + result := bestResult + if result < x1 { + result = x1 + } else if result > x2 { + result = x2 + } + + if iterations >= maxIterations { + logger.Debugf("findBrightnessPercentByCurve: reached max iterations (%d), target: %d, result: %f", + maxIterations, targetBrightness, result) + } + + return result +} diff --git a/display1/brightness/kalman_filter.go b/display1/brightness/kalman_filter.go index 167e47d07..3935d2d03 100644 --- a/display1/brightness/kalman_filter.go +++ b/display1/brightness/kalman_filter.go @@ -1,11 +1,11 @@ -// SPDX-FileCopyrightText: 2022 - 2026 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later package brightness import ( - "math" + "sync" ) // 卡尔曼滤波器默认参数 @@ -19,6 +19,8 @@ const ( // KalmanFilter1D 一维卡尔曼滤波器 // 用于传感器数据的平滑和噪声抑制 type KalmanFilter1D struct { + mu sync.Mutex + // 系统状态 xEst float64 // 估计值 PEst float64 // 估计协方差 @@ -53,6 +55,13 @@ func NewKalmanFilter1D(q, r, initialValue float64) *KalmanFilter1D { // measurement: 测量值 // 返回: 滤波后的估计值 func (kf *KalmanFilter1D) Update(measurement float64) float64 { + kf.mu.Lock() + defer kf.mu.Unlock() + return kf.updateUnlocked(measurement) +} + +// updateUnlocked 内部无锁版本,供 AdaptiveKalmanFilter 调用(调用者已持有锁) +func (kf *KalmanFilter1D) updateUnlocked(measurement float64) float64 { if !kf.initialized { kf.xEst = measurement kf.PEst = 1.0 @@ -87,33 +96,55 @@ func (kf *KalmanFilter1D) Update(measurement float64) float64 { // Reset 重置滤波器 func (kf *KalmanFilter1D) Reset() { + kf.mu.Lock() + defer kf.mu.Unlock() kf.initialized = false kf.PEst = 1.0 } // GetEstimate 获取当前估计值 func (kf *KalmanFilter1D) GetEstimate() float64 { + kf.mu.Lock() + defer kf.mu.Unlock() return kf.xEst } // GetCovariance 获取当前协方差 func (kf *KalmanFilter1D) GetCovariance() float64 { + kf.mu.Lock() + defer kf.mu.Unlock() return kf.PEst } // SetProcessNoise 设置过程噪声协方差 func (kf *KalmanFilter1D) SetProcessNoise(q float64) { + kf.mu.Lock() + defer kf.mu.Unlock() kf.Q = q } // SetMeasurementNoise 设置测量噪声协方差 func (kf *KalmanFilter1D) SetMeasurementNoise(r float64) { + kf.mu.Lock() + defer kf.mu.Unlock() + kf.R = r +} + +// setMeasurementNoiseUnlocked 无锁版本,供已持有 KalmanFilter1D.mu 的调用者使用 +func (kf *KalmanFilter1D) setMeasurementNoiseUnlocked(r float64) { kf.R = r } +// resetUnlocked 无锁重置版本,供已持有 KalmanFilter1D.mu 的调用者使用 +func (kf *KalmanFilter1D) resetUnlocked() { + kf.initialized = false + kf.PEst = 1.0 +} + // AdaptiveKalmanFilter 自适应卡尔曼滤波器 // 根据测量值的方差自动调整噪声参数 type AdaptiveKalmanFilter struct { + mu sync.Mutex *KalmanFilter1D window []float64 // 测量值窗口 windowSize int // 窗口大小 @@ -139,6 +170,9 @@ func NewDefaultAdaptiveKalmanFilter() *AdaptiveKalmanFilter { // Update 更新滤波器(自适应版本) func (akf *AdaptiveKalmanFilter) Update(measurement float64) float64 { + akf.mu.Lock() + defer akf.mu.Unlock() + // 添加到窗口 akf.window = append(akf.window, measurement) if len(akf.window) > akf.windowSize { @@ -171,40 +205,50 @@ func (akf *AdaptiveKalmanFilter) Update(measurement float64) float64 { if akf.measurementVariance > 0 { oldR := akf.R newR := akf.measurementVariance * 0.1 - akf.SetMeasurementNoise(newR) + akf.KalmanFilter1D.setMeasurementNoiseUnlocked(newR) logger.Debugf("[AutoBrightness::AdaptiveKalmanFilter] Adjusted measurement noise: R=%.4f -> %.4f", oldR, newR) } } - // 调用基类的更新方法 - return akf.KalmanFilter1D.Update(measurement) + // 调用基类的无锁更新方法(已持有 akf.mu,避免死锁) + return akf.KalmanFilter1D.updateUnlocked(measurement) } // Reset 重置滤波器 func (akf *AdaptiveKalmanFilter) Reset() { - akf.KalmanFilter1D.Reset() + akf.mu.Lock() + defer akf.mu.Unlock() + akf.KalmanFilter1D.resetUnlocked() akf.window = akf.window[:0] akf.measurementVariance = 0 } // GetMeasurementVariance 获取当前测量方差 func (akf *AdaptiveKalmanFilter) GetMeasurementVariance() float64 { + akf.mu.Lock() + defer akf.mu.Unlock() return akf.measurementVariance } // GetWindowSize 获取窗口大小 func (akf *AdaptiveKalmanFilter) GetWindowSize() int { + akf.mu.Lock() + defer akf.mu.Unlock() return len(akf.window) } // IsInitialized 检查滤波器是否已初始化(是否收到过数据) func (akf *AdaptiveKalmanFilter) IsInitialized() bool { + akf.mu.Lock() + defer akf.mu.Unlock() return akf.initialized } // SetWindowSize 设置窗口大小 func (akf *AdaptiveKalmanFilter) SetWindowSize(size int) { + akf.mu.Lock() + defer akf.mu.Unlock() if size < 2 { size = 2 } @@ -214,87 +258,3 @@ func (akf *AdaptiveKalmanFilter) SetWindowSize(size int) { akf.window = akf.window[len(akf.window)-akf.windowSize:] } } - -// ExponentialMovingAverage 指数移动平均滤波器(保留用于对比) -type ExponentialMovingAverage struct { - alpha float64 // 平滑系数 - lastValue float64 // 上一次的值 - initialized bool -} - -// NewExponentialMovingAverage 创建指数移动平均滤波器 -// alpha: 平滑系数 (0-1),值越大对新数据响应越快 -func NewExponentialMovingAverage(alpha float64) *ExponentialMovingAverage { - return &ExponentialMovingAverage{ - alpha: alpha, - } -} - -// Update 更新滤波器 -func (ema *ExponentialMovingAverage) Update(value float64) float64 { - if !ema.initialized { - ema.lastValue = value - ema.initialized = true - return value - } - - ema.lastValue = ema.alpha*value + (1-ema.alpha)*ema.lastValue - return ema.lastValue -} - -// Reset 重置滤波器 -func (ema *ExponentialMovingAverage) Reset() { - ema.initialized = false -} - -// GetValue 获取当前值 -func (ema *ExponentialMovingAverage) GetValue() float64 { - return ema.lastValue -} - -// MovingAverage 移动平均滤波器 -type MovingAverage struct { - window []float64 - windowSize int - sum float64 -} - -// NewMovingAverage 创建移动平均滤波器 -func NewMovingAverage(windowSize int) *MovingAverage { - return &MovingAverage{ - window: make([]float64, 0, windowSize), - windowSize: windowSize, - } -} - -// Update 更新滤波器 -func (ma *MovingAverage) Update(value float64) float64 { - ma.window = append(ma.window, value) - ma.sum += value - - if len(ma.window) > ma.windowSize { - ma.sum -= ma.window[0] - ma.window = ma.window[1:] - } - - return ma.sum / float64(len(ma.window)) -} - -// Reset 重置滤波器 -func (ma *MovingAverage) Reset() { - ma.window = ma.window[:0] - ma.sum = 0 -} - -// GetValue 获取当前平均值 -func (ma *MovingAverage) GetValue() float64 { - if len(ma.window) == 0 { - return 0 - } - return ma.sum / float64(len(ma.window)) -} - -// clampFloat64 将值限制在指定范围内 -func clampFloat64(value, min, max float64) float64 { - return math.Max(min, math.Min(max, value)) -} diff --git a/display1/brightness_transition.go b/display1/brightness_transition.go deleted file mode 100644 index fc9cd11ec..000000000 --- a/display1/brightness_transition.go +++ /dev/null @@ -1,422 +0,0 @@ -// SPDX-FileCopyrightText: 2022 - 2026 UnionTech Software Technology Co., Ltd. -// -// SPDX-License-Identifier: GPL-3.0-or-later -package display1 - -import ( - "math" - "sync" - "time" - - "github.com/godbus/dbus/v5" - configManager "github.com/linuxdeepin/go-dbus-factory/org.desktopspec.ConfigManager" - "github.com/linuxdeepin/go-lib/dbusutil" -) - -// transitionState 单个显示器的渐变状态 -type transitionState struct { - mu sync.Mutex // 保护 running 和 currentValue - running bool // 是否正在执行渐变 - currentValue float64 // 当前渐变的实时亮度值 - stopCh chan struct{} // 停止信号 - wg sync.WaitGroup // 等待渐变完成 - setter func(float64) error -} - -const ( - defaultDuration = 4 - defaultStepInterval = 100 - durationMin = 1 - stepIntervalMin = 20 -) - -// BrightnessTransition 亮度渐变管理器 -type BrightnessTransition struct { - manager *Manager - // 渐变配置 - enabled bool - duration int // 从0%到100%的渐变时长(秒) - stepInterval int // 步进间隔(毫秒) - // 每个显示器的渐变状态 - states map[string]*transitionState - // 配置管理器 - cfgManager configManager.Manager - mu sync.Mutex // 保护配置和状态 -} - -// NewBrightnessTransition 创建亮度渐变管理器 -func NewBrightnessTransition(manager *Manager) *BrightnessTransition { - return &BrightnessTransition{ - manager: manager, - enabled: false, - duration: defaultDuration, - stepInterval: defaultStepInterval, - states: make(map[string]*transitionState), - } -} -func (bt *BrightnessTransition) SetBrightnessSetter(monitorName string, setter func(float64) error) { - // 初始化配置管理器 - state := bt.getState(monitorName) - state.setter = setter -} - -// getState 获取或创建显示器的渐变状态 -func (bt *BrightnessTransition) getState(monitorName string) *transitionState { - bt.mu.Lock() - defer bt.mu.Unlock() - state, exists := bt.states[monitorName] - if !exists { - state = &transitionState{ - stopCh: make(chan struct{}, 1), - } - bt.states[monitorName] = state - } - return state -} - -// SetEnabled 设置是否启用渐变 -func (bt *BrightnessTransition) SetEnabled(enabled bool) { - bt.mu.Lock() - defer bt.mu.Unlock() - bt.enabled = enabled -} - -// IsEnabled 获取是否启用渐变 -func (bt *BrightnessTransition) IsEnabled() bool { - bt.mu.Lock() - defer bt.mu.Unlock() - return bt.enabled -} - -// SetDuration 设置渐变时长(秒) -func (bt *BrightnessTransition) SetDuration(duration int) { - bt.mu.Lock() - defer bt.mu.Unlock() - if duration >= durationMin { - bt.duration = duration - } else { - logger.Warningf("[BrightnessTransition] duration must be >= %d, got %d", durationMin, duration) - } -} - -// SetStepInterval 设置步进间隔(毫秒) -func (bt *BrightnessTransition) SetStepInterval(interval int) { - bt.mu.Lock() - defer bt.mu.Unlock() - if interval >= stepIntervalMin { - bt.stepInterval = interval - } else { - logger.Warningf("[BrightnessTransition] stepInterval must be >= %d, got %d", stepIntervalMin, interval) - } -} - -// IsRunning 检查是否正在执行渐变 -func (bt *BrightnessTransition) IsRunning() bool { - bt.mu.Lock() - defer bt.mu.Unlock() - for _, state := range bt.states { - if state.running { - return true - } - } - return false -} - -// Stop 停止所有渐变(公开方法) -func (bt *BrightnessTransition) Stop() { - bt.mu.Lock() - states := make([]*transitionState, 0, len(bt.states)) - for _, state := range bt.states { - states = append(states, state) - } - bt.mu.Unlock() - for _, state := range states { - bt.stopState(state) - } -} - -// stopState 停止指定显示器的渐变 -func (bt *BrightnessTransition) stopState(state *transitionState) { - if state == nil { - return - } - state.mu.Lock() - isRunning := state.running - state.mu.Unlock() - if !isRunning { - return - } - // 发送停止信号(非阻塞) - select { - case state.stopCh <- struct{}{}: - default: - } - // 等待渐变完成 - state.wg.Wait() - // 清空可能残留的停止信号 - select { - case <-state.stopCh: - default: - } -} - -// SetBrightness 使用渐变效果设置亮度 -func (bt *BrightnessTransition) SetBrightness(monitorName string, targetValue float64, forceTransition bool) error { - return bt.setBrightnessInternal(monitorName, targetValue, forceTransition) -} - -// setBrightnessInternal 内部方法,实现渐变逻辑 -// forceTransition: 是否强制使用渐变(忽略 enabled 标志) -func (bt *BrightnessTransition) setBrightnessInternal(monitorName string, targetValue float64, forceTransition bool) error { - bt.mu.Lock() - enabled := bt.enabled - duration := bt.duration - stepInterval := bt.stepInterval - bt.mu.Unlock() - state := bt.getState(monitorName) - brightnessSetter := state.setter - if brightnessSetter == nil { - logger.Warningf("No setter for monitro %v brightness transition", monitorName) - return nil - } - // 如果未启用渐变且不是强制渐变,直接设置 - if !enabled && !forceTransition { - return brightnessSetter(targetValue) - } - // 获取当前亮度:如果正在渐变,使用实时值 - var currentBrightness float64 - var hasCurrentValue bool - state.mu.Lock() - if state.running { - currentBrightness = state.currentValue - hasCurrentValue = true - } - state.mu.Unlock() - // 如果没有实时值,从 Manager 获取 - if !hasCurrentValue { - currentBrightness = bt.manager.getMonitorBrightness(monitorName) - if currentBrightness < 0 { - currentBrightness = 0.5 // 如果无法获取,使用默认值 - } - } - // 计算亮度差值 - delta := targetValue - currentBrightness - if math.Abs(delta) < 0.001 { - return nil // 无需调整(差值太小) - } - // 停止该显示器之前的渐变(如果有) - bt.stopState(state) - // 计算渐变参数 - // duration 是 0-100% 的时间,实际时间按比例计算 - actualDuration := time.Duration(float64(duration)*math.Abs(delta)*1000) * time.Millisecond - // 计算步进参数 - stepIntervalDuration := time.Duration(stepInterval) * time.Millisecond - // 如果渐变时间太短(小于 2 个步进周期),直接设置 - // 这样可以避免过短的渐变(例如:默认配置下小于 200ms 的渐变) - minDuration := stepIntervalDuration * 2 - if actualDuration < minDuration { - // 变化太小,直接设置 - return brightnessSetter(targetValue) - } - totalSteps := int(actualDuration / stepIntervalDuration) - if totalSteps < 2 { - totalSteps = 2 - } - stepSize := delta / float64(totalSteps) - // 标记渐变开始 - state.mu.Lock() - state.running = true - state.currentValue = currentBrightness - state.mu.Unlock() - state.wg.Add(1) - // 启动渐变 goroutine - go func() { - defer state.wg.Done() - defer func() { - state.mu.Lock() - state.running = false - state.mu.Unlock() - }() - currentValue := currentBrightness - ticker := time.NewTicker(stepIntervalDuration) - defer ticker.Stop() - for i := 0; i < totalSteps; i++ { - // 先检查停止信号(优先级更高) - select { - case <-state.stopCh: - // 更新最终的实时值 - state.mu.Lock() - state.currentValue = currentValue - state.mu.Unlock() - logger.Debugf("[BrightnessTransition] %s transition stopped at %.1f%%", monitorName, currentValue*100) - // 即使被停止,也同步最后的亮度值 - return - default: - } - // 计算下一个亮度值 - currentValue += stepSize - // 确保不超出范围 - if (stepSize > 0 && currentValue > targetValue) || (stepSize < 0 && currentValue < targetValue) { - currentValue = targetValue - } - // 更新实时亮度值 - state.mu.Lock() - state.currentValue = currentValue - state.mu.Unlock() - // 设置亮度(只更新底层硬件,不同步属性) - err := brightnessSetter(currentValue) - if err != nil { - logger.Warningf("[BrightnessTransition] Failed to set brightness during transition: %v", err) - // 失败时也要同步一次,确保属性与实际状态一致 - return - } - // 如果已经到达目标值,同步属性并结束 - if currentValue == targetValue { - logger.Debugf("[BrightnessTransition] %s transition completed: %.1f%% -> %.1f%%", - monitorName, currentBrightness*100, targetValue*100) - return - } - // 如果不是最后一步,等待下一个步进时间或停止信号 - if i < totalSteps-1 { - select { - case <-state.stopCh: - // 更新最终的实时值(已经在上面更新过了) - state.mu.Lock() - state.currentValue = currentValue - state.mu.Unlock() - logger.Debugf("[BrightnessTransition] %s transition stopped at %.1f%%", monitorName, currentValue*100) - // 即使被停止,也同步最后的亮度值 - return - case <-ticker.C: - // 继续下一次循环 - } - } - } - // 确保最终值精确 - if currentValue != targetValue { - err := brightnessSetter(targetValue) - if err != nil { - logger.Warningf("[BrightnessTransition] Failed to set final brightness: %v", err) - } - state.mu.Lock() - state.currentValue = targetValue - state.mu.Unlock() - } - logger.Debugf("[BrightnessTransition] %s transition completed: %.1f%% -> %.1f%%", - monitorName, currentBrightness*100, targetValue*100) - }() - return nil -} - -// LoadConfig 从配置文件加载渐变配置 -func (bt *BrightnessTransition) LoadConfig(sysBus *dbus.Conn) error { - logger.Debug("[BrightnessTransition] Loading config") - // 获取配置管理器 - 使用 Display 配置文件,而不是 AutoBrightness 配置文件 - ds := configManager.NewConfigManager(sysBus) - configPath, err := ds.AcquireManager(0, DSettingsAppID, DSettingsDisplayName, "") - if err != nil || configPath == "" { - logger.Warning("[BrightnessTransition] Failed to acquire config manager:", err) - return err - } - cfgManager, err := configManager.NewManager(sysBus, configPath) - if err != nil { - logger.Warning("[BrightnessTransition] Failed to create config manager:", err) - return err - } - bt.mu.Lock() - bt.cfgManager = cfgManager - bt.mu.Unlock() - // 读取配置 - enabled := true - duration := 4 - stepInterval := 100 - if val, err := cfgManager.Value(0, DSettingsKeyABTransitionEnabled); err == nil { - enabled = val.Value().(bool) - } else { - logger.Warning("[BrightnessTransition] Failed to read transition-enabled:", err) - } - if val, err := cfgManager.Value(0, DSettingsKeyABTransitionDuration); err == nil { - switch v := val.Value().(type) { - case int64: - duration = int(v) - case float64: - duration = int(v) - } - } else { - logger.Warning("[BrightnessTransition] Failed to read transition-duration:", err) - } - if val, err := cfgManager.Value(0, DSettingsKeyABTransitionStepInterval); err == nil { - switch v := val.Value().(type) { - case int64: - stepInterval = int(v) - case float64: - stepInterval = int(v) - } - } else { - logger.Warning("[BrightnessTransition] Failed to read transition-step-interval:", err) - } - // 应用配置 - bt.SetEnabled(enabled) - bt.SetDuration(duration) - bt.SetStepInterval(stepInterval) - logger.Infof("[BrightnessTransition] Config loaded: enabled=%v, duration=%ds, stepInterval=%dms", - enabled, duration, stepInterval) - return nil -} - -// WatchConfigChanges 监听配置变化 -func (bt *BrightnessTransition) WatchConfigChanges(sysSigLoop *dbusutil.SignalLoop) error { - bt.mu.Lock() - cfgManager := bt.cfgManager - bt.mu.Unlock() - if cfgManager == nil { - return nil - } - cfgManager.InitSignalExt(sysSigLoop, true) - _, err := cfgManager.ConnectValueChanged(func(key string) { - switch key { - case DSettingsKeyABTransitionEnabled: - if val, err := cfgManager.Value(0, key); err == nil { - enabled := val.Value().(bool) - bt.SetEnabled(enabled) - logger.Info("[BrightnessTransition] Enabled changed:", enabled) - } else { - logger.Warning("[BrightnessTransition] Failed to read transition-enabled on change:", err) - } - case DSettingsKeyABTransitionDuration: - if val, err := cfgManager.Value(0, key); err == nil { - var duration int - switch v := val.Value().(type) { - case int64: - duration = int(v) - case float64: - duration = int(v) - } - bt.SetDuration(duration) - logger.Info("[BrightnessTransition] Duration changed:", duration) - } else { - logger.Warning("[BrightnessTransition] Failed to read transition-duration on change:", err) - } - case DSettingsKeyABTransitionStepInterval: - if val, err := cfgManager.Value(0, key); err == nil { - var interval int - switch v := val.Value().(type) { - case int64: - interval = int(v) - case float64: - interval = int(v) - } - bt.SetStepInterval(interval) - logger.Info("[BrightnessTransition] Step interval changed:", interval) - } else { - logger.Warning("[BrightnessTransition] Failed to read transition-step-interval on change:", err) - } - } - }) - if err != nil { - logger.Warning("[BrightnessTransition] Failed to watch config changes:", err) - return err - } - logger.Debug("[BrightnessTransition] Config change watcher started") - return nil -} diff --git a/display1/color_temp.go b/display1/color_temp.go index f69d991ba..f996277d6 100644 --- a/display1/color_temp.go +++ b/display1/color_temp.go @@ -435,15 +435,31 @@ func (m *Manager) setColorTempOneShot() { defer _setColorTempMu.Unlock() monitors := m.getConnectedMonitors() + // bug204347 部分场景下monitor.Brightness为默认值 从系统配置中获取亮度 + monitorsId := monitors.getMonitorsId() + configs := m.getSuitableSysMonitorConfigs(m.DisplayMode, monitorsId, monitors) for _, monitor := range monitors { monitor.PropsMu.RLock() br := monitor.Brightness name := monitor.Name monitor.PropsMu.RUnlock() - err := m.setBrightness(name, br) - if err != nil { - logger.Warning(err) + var config *SysMonitorConfig + for _, c := range configs { + if c.Name == name { + config = c + break + } + } + if config != nil { + br = config.Brightness + } + + if canSet, _ := m.CanSetBrightness(name); canSet && br > 0 && monitor.Enabled { + err := m.setColorTemperature(monitor, br) + if err != nil { + logger.Warning(err) + } } } } diff --git a/display1/docs/auto_backlight/auto_brightness_design.md b/display1/docs/auto_backlight/auto_brightness_design.md deleted file mode 100644 index b0fef9632..000000000 --- a/display1/docs/auto_backlight/auto_brightness_design.md +++ /dev/null @@ -1,1406 +0,0 @@ -# 自动亮度调节功能概要设计文档 - -## 目录 - -1. [功能概述](#1-功能概述) - - 1.1 系统架构图 -2. [核心组件](#2-核心组件) - - 2.1 AutoBrightnessManager - - 2.2 SensorProxyClient - - 2.3 BrightnessTransition - - 2.4 配置管理 -3. [状态机](#3-状态机) - - 3.1 自动亮度状态转换 -4. [数据结构](#4-数据结构) - - 4.1 AutoBrightnessConfig - - 4.2 AutoBrightnessManager 状态字段 - - 4.3 BrightnessTransition 数据结构 -5. [核心流程](#5-核心流程) - - 5.1 初始化流程 - - 5.2 启动流程 - - 5.3 轮询流程 - - 5.4 停止流程 -6. [关键算法](#6-关键算法) - - 6.1 亮度计算算法 - - 6.2 调节判断逻辑 - - 6.3 渐变算法 - - 6.4 完整数据流图 -7. [亮度渐变机制](#7-亮度渐变机制) - - 7.1 渐变流程 - - 7.2 渐变控制 - - 7.3 渐变优化 - - 7.4 自动亮度与渐变集成 -8. [手动调节处理](#8-手动调节处理) - - 8.1 两种模式 - - 8.2 手动调节处理时序 - - 8.3 系统调整标志 -9. [配置管理](#9-配置管理) - - 9.1 DSettings 集成 - - 9.2 配置项 - - 9.3 动态更新 -10. [异常处理](#10-异常处理) - - 10.1 重试机制 - - 10.2 优雅降级 - - 10.3 服务恢复 -11. [并发控制](#11-并发控制) - - 11.1 锁策略 - - 11.2 Goroutine 管理 -12. [系统集成](#12-系统集成) - - 12.1 与 Display Manager 集成 - - 12.2 电源管理集成 - - 12.3 DBus 接口 -13. [依赖关系](#13-依赖关系) - - 13.1 外部依赖 - - 13.2 内部依赖 -14. [测试要点](#14-测试要点) - - 14.1 功能测试 - - 14.2 异常测试 - - 14.3 性能测试 -15. [未来扩展](#15-未来扩展) - - 15.1 算法优化 - - 15.2 功能增强 - - 15.3 性能优化 -16. [典型使用场景](#16-典型使用场景) - - 16.1 场景一:用户启用自动亮度 - - 16.2 场景二:环境光变化触发调节 - - 16.3 场景三:手动调节后的行为 - - 16.4 场景四:系统休眠与唤醒 -17. [注意事项](#17-注意事项) - ---- - -## 1. 功能概述 - -自动亮度调节功能通过环境光传感器自动调整显示器亮度,提升用户体验并节省电能。该功能集成在 StartDDE 的 display 模块中,支持灵活的配置和优雅的降级处理。 - -### 1.1 系统架构图 - -```mermaid -graph TB - subgraph "用户层" - User[用户] - ControlCenter[控制中心] - end - - subgraph "StartDDE Display 模块" - Manager[Display Manager] - ABM[AutoBrightnessManager] - BT[BrightnessTransition] - Backlight[Backlight 控制] - end - - subgraph "配置层" - DConf[DSettings/DConf] - end - - subgraph "系统服务" - SensorProxy[iio-sensor-proxy] - Hardware[环境光传感器硬件] - end - - subgraph "DBus 通信" - DBus[DBus 总线] - end - - User -->|手动调节| ControlCenter - ControlCenter -->|DBus 调用| Manager - Manager -->|管理| ABM - Manager -->|使用| BT - Manager -->|控制| Backlight - - ABM -->|读取配置| DConf - ABM -->|保存配置| DConf - BT -->|读取配置| DConf - - ABM -->|DBus 调用| SensorProxy - SensorProxy -->|读取| Hardware - - ABM -->|调用| BT - BT -->|设置亮度| Backlight - ABM -->|设置亮度| Backlight - - Manager -.->|属性通知| DBus - ControlCenter -.->|监听属性| DBus - - style ABM fill:#e1f5ff - style BT fill:#e1f5ff - style Manager fill:#fff4e1 -``` - -## 2. 核心组件 - -### 2.1 AutoBrightnessManager - -自动亮度管理器,负责整个功能的生命周期管理。 - -**主要职责:** -- 初始化和资源管理 -- 配置加载和持久化 -- 传感器数据采集和处理 -- 亮度计算和应用 -- 状态监控和异常处理 - -### 2.2 SensorProxyClient - -传感器代理客户端,封装与 iio-sensor-proxy 服务的交互。 - -**主要功能:** -- 连接/断开传感器服务 -- 声明/释放环境光传感器 -- 读取光照强度数据 -- 监听服务状态变化 - -### 2.3 BrightnessTransition - -亮度渐变管理器,提供平滑的亮度过渡效果。 - -**主要功能:** -- 渐变效果的启用/禁用控制 -- 渐变参数配置(时长、步进间隔) -- 多显示器独立渐变状态管理 -- 渐变过程的启动、停止和中断处理 -- 实时亮度值跟踪 - -### 2.4 配置管理 - -基于 DSettings (DConfig) 的配置系统,支持动态配置更新。 - -## 3. 状态机 - -### 3.1 自动亮度状态转换 - -```mermaid -stateDiagram-v2 - [*] --> 未初始化 - - 未初始化 --> 初始化中: Initialize() - - 初始化中 --> 不支持: 检查失败
(无显示器/传感器) - 初始化中 --> 已初始化未启用: 检查成功
Enabled=false - 初始化中 --> 已初始化已启用: 检查成功
Enabled=true - - 不支持 --> [*]: 功能不可用 - - 已初始化未启用 --> 启动中: SetEnabled(true) - 已初始化已启用 --> 启动中: Start() - - 启动中 --> 运行中: 传感器声明成功
轮询启动 - 启动中 --> 已初始化未启用: 启动失败 - - 运行中 --> 手动暂停: 用户手动调节
(临时暂停模式) - 运行中 --> 停止中: SetEnabled(false) - 运行中 --> 休眠暂停: hold() - 运行中 --> 服务异常: 传感器服务不可用 - - 手动暂停 --> 运行中: 超时恢复 - 手动暂停 --> 停止中: SetEnabled(false) - - 休眠暂停 --> 运行中: resume() - 休眠暂停 --> 停止中: SetEnabled(false) - - 服务异常 --> 不支持: 服务持续不可用 - 服务异常 --> 运行中: 服务恢复
自动重连 - - 停止中 --> 已初始化未启用: 停止完成 - - 已初始化未启用 --> 启动中: SetEnabled(true) - 已初始化已启用 --> 停止中: SetEnabled(false) - - note right of 运行中 - - 轮询传感器 - - 计算亮度 - - 应用调节 - end note - - note right of 手动暂停 - - 释放传感器 - - 停止调节 - - 计时等待 - end note - - note right of 休眠暂停 - - 停止轮询 - - 保持状态 - - 等待唤醒 - end note -``` - -## 4. 数据结构 - -### 4.1 AutoBrightnessConfig - -```go -type AutoBrightnessConfig struct { - Enabled bool // 是否启用 - Sensitivity float64 // 敏感度 (0.1-3.0) - PollingInterval int // 轮询间隔(秒) (1-60) - ChangeThreshold float64 // 变化阈值 (1.0-50.0) - ManualOverrideDuration int // 手动调节暂停时间(秒) (60-1800) - ManualAdjustDisablesAutoMode bool // 手动调节是否禁用自动模式 - UseTransition bool // 是否使用渐变效果 -} -``` - -### 4.2 AutoBrightnessManager 状态字段 - -- **依赖注入:** manager (复用 display.Manager) -- **独立组件:** sensorClient, configManager -- **配置状态:** config, enabled, supported -- **运行状态:** running, polling, systemAdjusting -- **历史数据:** lastLightLevel, lastBrightness, lastAdjustTime -- **手动控制:** manualOverride (时间戳) -- **轮询控制:** ticker, stopChan, pollingWg - -### 4.3 BrightnessTransition 数据结构 - -**配置字段:** -- `enabled`: 是否启用渐变 -- `duration`: 从 0% 到 100% 的渐变时长(秒) -- `stepInterval`: 步进间隔(毫秒) - -**状态管理:** -```go -type transitionState struct { - running bool // 是否正在执行渐变 - currentValue float64 // 当前渐变的实时亮度值 - stopCh chan struct{} // 停止信号通道 - wg sync.WaitGroup // 等待渐变完成 -} -``` - -**多显示器支持:** -- `states map[string]*transitionState`: 每个显示器独立的渐变状态 - -## 5. 核心流程 - -### 5.1 初始化流程 - -```mermaid -flowchart TD - A[Initialize] --> B{检查内置显示器} - B -->|不存在| C[返回错误: 无内置显示器] - B -->|存在| D{检查亮度调节支持} - D -->|不支持| E[返回错误: 不支持亮度调节] - D -->|支持| F[创建传感器客户端] - F --> G[检查传感器可用性] - G -->|不可用| H[返回错误: 传感器不可用] - G -->|可用| I[初始化配置管理器] - I --> J[加载配置] - J -->|失败| K[使用默认配置] - J -->|成功| L[设置服务状态回调] - K --> L - L --> M[标记为已支持] - M --> N[初始化完成] -``` - -### 5.2 启动流程 - -```mermaid -sequenceDiagram - participant User as 用户/系统 - participant ABM as AutoBrightnessManager - participant Sensor as SensorProxyClient - participant Poller as 轮询器 - - User->>ABM: Start() - ABM->>ABM: 检查支持状态 - alt 不支持 - ABM-->>User: 返回错误 - else 支持 - ABM->>ABM: 检查配置是否启用 - alt 未启用 - ABM-->>User: 返回 nil - else 已启用 - ABM->>Sensor: Connect() - Sensor-->>ABM: 连接成功 - ABM->>Sensor: ClaimLight() (带重试) - loop 最多3次 - Sensor->>Sensor: 尝试声明传感器 - alt 成功 - Sensor-->>ABM: 声明成功 - else 失败且未达重试上限 - Sensor->>Sensor: 等待2秒 - end - end - alt 声明失败 - ABM->>Sensor: Disconnect() - ABM-->>User: 返回错误 - else 声明成功 - ABM->>Poller: startPolling() - Poller->>Poller: 创建 ticker - Poller->>Poller: 启动 goroutine - Poller->>Poller: 立即执行一次采集 - ABM->>ABM: 更新运行状态 - ABM-->>User: 启动成功 - end - end - end -``` - -### 5.3 轮询流程 - -```mermaid -flowchart TD - A[定时器触发] --> B[pollLightLevel] - B --> C{检查运行状态} - C -->|未运行| D[返回] - C -->|运行中| E{检查手动调节暂停期} - E -->|暂停中| D - E -->|未暂停| F{传感器已声明?} - F -->|否| G[重新声明传感器] - G -->|失败| D - G -->|成功| H[获取光照强度] - F -->|是| H - H -->|失败| D - H -->|成功| I[processLightChange] - - I --> J[计算目标亮度] - J --> K{shouldAdjustBrightness} - - K --> L{手动调节暂停?} - L -->|是| M[不调节] - L -->|否| N{环境光变化 >= 阈值?} - N -->|否| M - N -->|是| O{距上次调节 >= 间隔?} - O -->|否| M - O -->|是| P{亮度变化 >= 5%?} - P -->|否| M - P -->|是| Q[应用亮度] - - Q --> R{使用渐变?} - R -->|是| S[BrightnessTransition.SetBrightnessForced] - R -->|否| T[setBrightnessRaw] - S --> U[更新历史状态] - T --> U - U --> V[记录光照/亮度/时间] - V --> D - M --> D -``` - -### 5.4 停止流程 - -```mermaid -sequenceDiagram - participant User as 用户/系统 - participant ABM as AutoBrightnessManager - participant Poller as 轮询器 - participant Sensor as SensorProxyClient - participant Display as 显示器 - - User->>ABM: Stop() - ABM->>ABM: 检查运行状态 - alt 未运行 - ABM-->>User: 返回 nil - else 运行中 - ABM->>Poller: stopPolling() - Poller->>Poller: 停止 ticker - Poller->>Poller: 发送停止信号 - Poller->>Poller: 等待 goroutine 退出 - Poller-->>ABM: 停止完成 - - ABM->>Display: restoreSavedBrightness() - Display->>Display: 获取保存的亮度 - Display->>Display: 应用亮度 - Display-->>ABM: 恢复完成 - - ABM->>Sensor: ReleaseLight() - Sensor-->>ABM: 释放完成 - - ABM->>Sensor: Disconnect() - Sensor-->>ABM: 断开完成 - - ABM->>ABM: 更新运行状态 - ABM-->>User: 停止成功 - end -``` - -## 6. 关键算法 - -### 6.1 亮度计算算法 - -``` -目标亮度 = min(max((光照强度 × 敏感度) / 255, 0.1), 1.0) -``` - -**说明:** -- 线性映射:光照强度 0-255 lux → 亮度 0.0-1.0 -- 敏感度调整:支持 0.1-3.0 倍率 -- 最小亮度保护:不低于 10%,避免屏幕过暗 - -### 6.2 调节判断逻辑 - -满足以下所有条件才执行调节: - -1. **不在手动调节暂停期** -2. **环境光变化超过阈值**:`|当前光照 - 上次光照| >= ChangeThreshold` -3. **距上次调节时间足够**:`当前时间 - 上次调节时间 >= PollingInterval` -4. **亮度变化足够大**:`|目标亮度 - 当前亮度| >= 5%` - -### 6.3 渐变算法 - -**基本原理:** -将亮度变化分解为多个小步进,在一定时间内逐步完成。 - -**参数计算:** -``` -实际渐变时长 = 配置时长 × |亮度差值| -步进次数 = 实际渐变时长 / 步进间隔 -每步变化量 = 亮度差值 / 步进次数 -``` - -**示例:** -- 配置时长:4 秒(0-100% 的时间) -- 步进间隔:100 毫秒 -- 亮度变化:30% → 80%(差值 50%) -- 实际时长:4 × 0.5 = 2 秒 -- 步进次数:2000ms / 100ms = 20 步 -- 每步变化:0.5 / 20 = 0.025 (2.5%) - -**优化策略:** -- 最小渐变时长:2 × 步进间隔(避免过短渐变) -- 变化太小时直接设置(< 0.1%) -- 支持中途停止和新渐变覆盖 - -### 6.4 完整数据流图 - -```mermaid -flowchart TB - Start([定时器触发]) --> A[读取环境光传感器] - A -->|光照强度 lux| B[应用敏感度系数] - B -->|调整后光照| C[线性映射到 0.0-1.0] - C -->|原始亮度| D[应用最小亮度保护] - D -->|目标亮度| E{检查调节条件} - - E -->|手动暂停中| End1([跳过]) - E -->|光照变化 < 阈值| End1 - E -->|时间间隔不足| End1 - E -->|亮度变化 < 5%| End1 - E -->|所有条件满足| F{使用渐变?} - - F -->|否| G[直接设置亮度] - F -->|是| H[计算渐变参数] - - H --> I[实际时长 = 配置时长 × 差值] - I --> J[步进次数 = 时长 / 间隔] - J --> K{时长 >= 最小值?} - - K -->|否| G - K -->|是| L[启动渐变 goroutine] - - L --> M[循环步进] - M --> N[更新实时亮度] - N --> O[调用硬件接口] - O --> P{到达目标?} - - P -->|否| Q[等待步进间隔] - Q --> M - P -->|是| R[同步 DBus 属性] - - G --> S[调用硬件接口] - S --> R - - R --> T[更新历史状态] - T --> End2([完成]) - - style A fill:#e1f5ff - style B fill:#e1f5ff - style C fill:#e1f5ff - style D fill:#e1f5ff - style H fill:#fff4e1 - style L fill:#fff4e1 - style M fill:#fff4e1 -``` - -## 7. 亮度渐变机制 - -### 7.1 渐变流程 - -```mermaid -flowchart TD - A[SetBrightness] --> B{检查启用状态} - B -->|未启用且非强制| C[直接设置亮度] - B -->|已启用或强制| D[获取当前亮度] - - D --> E{正在渐变?} - E -->|是| F[使用实时亮度值] - E -->|否| G[从 Manager 获取] - - F --> H[计算亮度差值] - G --> H - - H --> I{差值 < 0.001?} - I -->|是| J[无需调整,返回] - I -->|否| K[停止之前的渐变] - - K --> L[计算渐变参数] - L --> M[实际时长 = 配置时长 × |差值|] - M --> N[步进次数 = 实际时长 / 步进间隔] - N --> O[每步变化 = 差值 / 步进次数] - - O --> P{实际时长 < 最小时长?} - P -->|是| C - P -->|否| Q[标记渐变开始] - - Q --> R[启动 goroutine] - R --> S[返回不等待] - - R --> T[渐变循环] - T --> U{收到停止信号?} - U -->|是| V[更新实时值] - U -->|否| W[计算下一个亮度] - - W --> X[更新实时值] - X --> Y[setBrightnessRaw] - Y --> Z{到达目标值?} - Z -->|是| AA[同步 DBus 属性] - Z -->|否| AB{最后一步?} - - AB -->|否| AC[等待步进间隔或停止信号] - AC --> U - AB -->|是| AA - - V --> AA - AA --> AD[标记渐变结束] - AD --> AE[goroutine 退出] -``` - -### 7.2 渐变控制 - -```mermaid -sequenceDiagram - participant Caller as 调用者 - participant BT as BrightnessTransition - participant State as transitionState - participant Worker as 渐变 Goroutine - participant HW as 硬件 - - Note over Caller,HW: 场景1: 启动新渐变 - Caller->>BT: SetBrightness(monitor, 0.8) - BT->>BT: 获取当前亮度 0.3 - BT->>State: 检查是否正在渐变 - State-->>BT: running = false - BT->>BT: 计算参数 (差值0.5, 20步) - BT->>State: 标记 running = true - BT->>State: wg.Add(1) - BT->>Worker: 启动 goroutine - BT-->>Caller: 立即返回 - - loop 20次步进 - Worker->>State: 更新 currentValue - Worker->>HW: setBrightnessRaw - Worker->>Worker: 等待100ms - end - Worker->>HW: 同步 DBus 属性 - Worker->>State: 标记 running = false - Worker->>State: wg.Done() - - Note over Caller,HW: 场景2: 中断现有渐变 - Caller->>BT: SetBrightness(monitor, 0.5) - BT->>State: 检查是否正在渐变 - State-->>BT: running = true, currentValue = 0.6 - BT->>State: 发送停止信号 - State->>Worker: stopCh <- signal - Worker->>Worker: 收到停止信号 - Worker->>State: 更新最终 currentValue - Worker->>HW: 同步 DBus 属性 - Worker->>State: wg.Done() - BT->>State: wg.Wait() 等待退出 - BT->>BT: 使用 currentValue 0.6 作为起点 - BT->>Worker: 启动新渐变 (0.6 -> 0.5) - BT-->>Caller: 立即返回 -``` - -**启动渐变:** -- 标记 `running = true` -- 增加 WaitGroup 计数 -- 启动独立 goroutine 执行 - -**停止渐变:** -- 发送停止信号到 `stopCh` -- 等待 WaitGroup 完成 -- 清空残留信号 - -**中断处理:** -- 新渐变会先停止旧渐变 -- 使用实时亮度值作为起点 -- 确保平滑过渡 - -### 7.3 渐变优化 - -**性能优化:** -- 每个显示器独立渐变状态 -- 非阻塞启动(立即返回) -- 只在完成时同步一次属性 - -**用户体验优化:** -- 变化太小时直接设置(< 0.1%) -- 渐变时长按比例缩放 -- 最小渐变时长保护(200ms) - -**资源管理:** -- 使用 WaitGroup 确保 goroutine 正确退出 -- 停止信号使用缓冲通道避免阻塞 -- 清理残留信号防止误触发 - -### 7.4 自动亮度与渐变集成 - -**两种调用方式:** - -1. **SetBrightness(常规)** - - 检查全局 `enabled` 标志 - - 未启用时直接设置亮度 - -2. **SetBrightnessForced(强制)** - - 忽略全局 `enabled` 标志 - - 自动亮度专用,确保渐变生效 - -**配置独立性:** -- 自动亮度有独立的 `UseTransition` 配置 -- 可以在全局渐变关闭时仍使用渐变 -- 通过 `SetBrightnessForced` 实现 - -**属性同步:** -- 渐变过程中不同步属性(减少信号) -- 只在渐变完成或停止时同步一次 -- 失败时立即同步确保一致性 - -## 8. 手动调节处理 - -### 8.1 两种模式 - -```mermaid -stateDiagram-v2 - [*] --> 自动调节运行中 - - 自动调节运行中 --> 检查配置: 用户手动调节亮度 - - 检查配置 --> 临时暂停模式: ManualAdjustDisablesAutoMode = false - 检查配置 --> 完全禁用模式: ManualAdjustDisablesAutoMode = true - - 临时暂停模式 --> 记录暂停时间 - 记录暂停时间 --> 释放传感器 - 释放传感器 --> 暂停状态 - - 暂停状态 --> 检查超时: 每次轮询检查 - 检查超时 --> 暂停状态: 未超时 - 检查超时 --> 重新声明传感器: 超时 - 重新声明传感器 --> 自动调节运行中 - - 完全禁用模式 --> 更新配置Enabled=false - 更新配置Enabled=false --> 停止功能 - 停止功能 --> 已禁用状态 - - 已禁用状态 --> 自动调节运行中: 用户手动启用 -``` - -**模式一:临时暂停(默认)** -- 手动调节后暂停自动调节指定时间 -- 暂停期间释放传感器资源 -- 超时后自动恢复 - -**模式二:完全禁用** -- 手动调节后永久禁用自动亮度 -- 更新配置并停止功能 -- 需用户手动重新启用 - -### 8.2 手动调节处理时序 - -```mermaid -sequenceDiagram - participant User as 用户 - participant Manager as Display Manager - participant ABM as AutoBrightnessManager - participant Sensor as SensorProxyClient - participant Config as 配置系统 - - Note over User,Config: 场景1: 临时暂停模式 - User->>Manager: 手动调节亮度 - Manager->>Manager: setBrightness() - Manager->>ABM: OnManualBrightnessChange() - ABM->>ABM: 检查 systemAdjusting - alt systemAdjusting = true - ABM->>ABM: 忽略(系统调整) - else systemAdjusting = false - ABM->>ABM: 检查配置模式 - alt 临时暂停模式 - ABM->>ABM: 记录 manualOverride 时间 - ABM->>Sensor: ReleaseLight() - Note over ABM: 暂停 300 秒 - ABM->>ABM: 轮询时检查超时 - ABM->>Sensor: ClaimLight() (超时后) - ABM->>ABM: 恢复自动调节 - end - end - - Note over User,Config: 场景2: 完全禁用模式 - User->>Manager: 手动调节亮度 - Manager->>Manager: setBrightness() - Manager->>ABM: OnManualBrightnessChange() - ABM->>ABM: 检查配置模式 - alt 完全禁用模式 - ABM->>Config: 保存 Enabled = false - ABM->>ABM: Stop() - ABM->>Manager: setPropAutoBrightnessEnabled(false) - end -``` - -### 8.3 系统调整标志 - -通过 `systemAdjusting` 标志区分系统自动调整(如节能模式)和用户手动调整,避免误判。 - -```mermaid -sequenceDiagram - participant Power as 电源管理 - participant Manager as Display Manager - participant ABM as AutoBrightnessManager - - Note over Power,ABM: 系统调整场景(节能模式) - Power->>Manager: 节能模式降低亮度 - Manager->>ABM: setSystemAdjusting(true) - Manager->>Manager: setBrightness(0.3) - Manager->>ABM: OnManualBrightnessChange() - ABM->>ABM: 检查 systemAdjusting = true - ABM->>ABM: 忽略此次调用 - Manager->>ABM: setSystemAdjusting(false) - - Note over Power,ABM: 用户手动调整场景 - Power->>Manager: 用户拖动亮度滑块 - Manager->>Manager: setBrightness(0.8) - Manager->>ABM: OnManualBrightnessChange() - ABM->>ABM: 检查 systemAdjusting = false - ABM->>ABM: 执行暂停或禁用逻辑 -``` - -## 9. 配置管理 - -### 9.1 DSettings 集成 - -- **AppID:** `org.deepin.startdde` -- **配置名:** `org.deepin.startdde.display` -- **配置键前缀:** `autobrightness-*` - -### 9.2 配置项 - -**自动亮度配置:** - -| 配置键 | 类型 | 默认值 | 说明 | -|--------|------|--------|------| -| enabled | bool | false | 是否启用 | -| sensitivity | float64 | 0.5 | 敏感度 | -| polling-interval | int | 3 | 轮询间隔(秒) | -| change-threshold | float64 | 20.0 | 变化阈值 | -| manual-override-duration | int | 300 | 手动暂停时间(秒) | -| manual-adjust-disables-auto-mode | bool | true | 手动调节是否禁用 | -| use-transition | bool | true | 是否使用渐变 | - -**渐变效果配置:** - -| 配置键 | 类型 | 默认值 | 说明 | -|--------|------|--------|------| -| transition-enabled | bool | true | 全局渐变开关 | -| transition-duration | int | 4 | 0-100% 渐变时长(秒) | -| transition-step-interval | int | 100 | 步进间隔(毫秒) | - -### 9.3 动态更新 - -监听配置文件变化,自动重新加载并应用新配置。敏感度变化时立即触发一次亮度调整。 - -```mermaid -sequenceDiagram - participant User as 用户/控制中心 - participant DConf as DSettings/DConf - participant ABM as AutoBrightnessManager - participant BT as BrightnessTransition - participant Poller as 轮询器 - - Note over User,Poller: 自动亮度配置变更 - User->>DConf: 修改配置 (如 sensitivity) - DConf->>ABM: ValueChanged 信号 - ABM->>DConf: 读取新配置 - DConf-->>ABM: 返回配置值 - ABM->>ABM: OnConfigChanged() - - alt 启用状态变化 - ABM->>ABM: Start() 或 Stop() - else 轮询参数变化 - ABM->>Poller: stopPolling() - ABM->>Poller: startPolling() - else 敏感度变化 - ABM->>ABM: adjustBrightnessOnce() - Note over ABM: 使用新敏感度立即调整 - end - - Note over User,Poller: 渐变配置变更 - User->>DConf: 修改渐变配置 - DConf->>BT: ValueChanged 信号 - BT->>DConf: 读取新配置 - DConf-->>BT: 返回配置值 - - alt transition-enabled 变化 - BT->>BT: SetEnabled(newValue) - else transition-duration 变化 - BT->>BT: SetDuration(newValue) - else transition-step-interval 变化 - BT->>BT: SetStepInterval(newValue) - end - - Note over BT: 新配置立即生效
下次调节时使用 -``` - -## 10. 异常处理 - -### 10.1 重试机制 - -- 传感器声明失败:最多重试 3 次,间隔 2 秒 -- 亮度设置失败:下次轮询自动重试 - -### 10.2 优雅降级 - -- 传感器服务不可用:停止功能但不崩溃 -- 配置加载失败:使用默认配置 -- 内置显示器不存在:标记为不支持 - -### 10.3 服务恢复 - -监听 iio-sensor-proxy 服务状态,服务恢复后自动重新初始化。 - -```mermaid -sequenceDiagram - participant Sensor as iio-sensor-proxy - participant Client as SensorProxyClient - participant ABM as AutoBrightnessManager - participant Manager as Display Manager - - Note over Sensor,Manager: 服务异常场景 - Sensor->>Sensor: 服务崩溃/重启 - Sensor->>Client: NameOwnerChanged 信号 - Client->>Client: 检测到服务不可用 - Client->>ABM: onServiceChange(false) - ABM->>ABM: stopPolling() - ABM->>ABM: 标记 supported = false - ABM->>Manager: setPropAutoBrightnessSupported(false) - - Note over Sensor,Manager: 服务恢复场景 - Sensor->>Sensor: 服务恢复 - Sensor->>Client: NameOwnerChanged 信号 - Client->>Client: 检测到服务可用 - Client->>ABM: onServiceChange(true) - ABM->>Client: HasAmbientLight() - Client-->>ABM: true - ABM->>ABM: 标记 supported = true - ABM->>Manager: setPropAutoBrightnessSupported(true) - - alt 配置已启用 - ABM->>ABM: Start() - ABM->>Client: Connect() - ABM->>Client: ClaimLight() - ABM->>ABM: startPolling() - Note over ABM: 自动恢复功能 - end -``` - -## 11. 并发控制 - -### 11.1 锁策略 - -- 使用 `sync.RWMutex` 保护共享状态 -- 读多写少场景使用读锁 -- 避免在持有锁时执行耗时操作 - -```mermaid -graph TD - A[公共方法调用] --> B{需要修改状态?} - B -->|是| C[获取写锁 Lock] - B -->|否| D[获取读锁 RLock] - - C --> E{需要耗时操作?} - E -->|是| F[释放锁] - E -->|否| G[执行操作] - - F --> H[执行耗时操作
如 Start/Stop] - H --> I[必要时重新获取锁] - - G --> J[释放写锁 Unlock] - D --> K[读取状态] - K --> L[释放读锁 RUnlock] - - I --> J - J --> M[返回] - L --> M -``` - -### 11.2 Goroutine 管理 - -- 轮询 goroutine:通过 `stopChan` 和 `WaitGroup` 安全退出 -- 配置更新回调:异步执行,避免阻塞 -- 幂等停止:`stopPolling()` 可安全多次调用 - -```mermaid -sequenceDiagram - participant Main as 主线程 - participant Poller as 轮询 Goroutine - participant Worker as 渐变 Goroutine - - Note over Main,Worker: Goroutine 生命周期管理 - - Main->>Main: startPolling() - Main->>Main: wg.Add(1) - Main->>Poller: 启动 goroutine - Main->>Main: 返回(不等待) - - loop 轮询循环 - Poller->>Poller: 等待 ticker 或 stopChan - alt 收到 ticker - Poller->>Poller: pollLightLevel() - else 收到 stopChan - Poller->>Poller: wg.Done() - Poller->>Poller: 退出 - end - end - - Main->>Main: stopPolling() - Main->>Poller: stopChan <- signal - Main->>Main: 释放锁 - Main->>Main: wg.Wait() - Note over Main: 等待 goroutine 退出 - Main->>Main: 重新获取锁 - Main->>Main: 清空 stopChan - - Note over Main,Worker: 渐变 Goroutine 管理 - Main->>Main: SetBrightness() - Main->>Main: state.wg.Add(1) - Main->>Worker: 启动 goroutine - Main->>Main: 返回(不等待) - - Worker->>Worker: 执行渐变 - alt 完成 - Worker->>Worker: state.wg.Done() - else 被停止 - Worker->>Worker: state.wg.Done() - end - - Main->>Main: stopState() - Main->>Worker: stopCh <- signal - Main->>Main: state.wg.Wait() - Note over Main: 等待渐变完成 -``` - -## 12. 系统集成 - -### 12.1 与 Display Manager 集成 - -- 复用 Manager 的显示器管理能力 -- 复用 BrightnessTransition 渐变功能 -- 同步更新 DBus 属性 - -```mermaid -graph LR - ABM[AutoBrightnessManager] -->|依赖注入| Manager[Display Manager] - ABM -->|调用| BT[BrightnessTransition] - ABM -->|调用| getBuiltinMonitor - ABM -->|调用| canSetBrightness - ABM -->|调用| setBrightnessRaw - ABM -->|调用| syncPropBrightness - - Manager -->|创建| ABM - Manager -->|初始化| BT - Manager -->|暴露| DBusAPI[DBus API] - - BT -->|调用| setBrightnessRaw - BT -->|调用| syncPropBrightness -``` - -### 12.2 电源管理集成 - -- 支持系统休眠/唤醒事件 -- `hold()`: 休眠前暂停轮询 -- `resume()`: 唤醒后恢复轮询 - -```mermaid -sequenceDiagram - participant PM as 电源管理 - participant Manager as Display Manager - participant ABM as AutoBrightnessManager - participant Poller as 轮询器 - participant Display as 显示器 - - Note over PM,Display: 系统休眠场景 - PM->>Manager: PrepareForSleep(true) - Manager->>ABM: hold() - ABM->>ABM: 设置 systemAdjusting = true - ABM->>Poller: stopPolling() - Poller->>Poller: 停止 ticker - Poller->>Poller: 等待 goroutine 退出 - Note over ABM: 保持运行状态
但停止轮询 - - Note over PM,Display: 系统唤醒场景 - PM->>Manager: PrepareForSleep(false) - Manager->>ABM: resume() - ABM->>Poller: startPolling() - Poller->>Poller: 创建新 ticker - Poller->>Poller: 启动 goroutine - Poller->>Poller: 立即采集一次 - ABM->>ABM: 清除 systemAdjusting - Note over ABM: 恢复正常运行 - - Note over PM,Display: 亮度变化不触发手动调节检测 - ABM->>Display: 调整亮度 - Display->>Manager: 亮度变化通知 - Manager->>ABM: OnManualBrightnessChange() - ABM->>ABM: 检查 systemAdjusting - Note over ABM: systemAdjusting = true
忽略此次调用 -``` - -### 12.3 DBus 接口 - -通过 Manager 暴露以下属性和方法: -- `AutoBrightnessSupported` (只读) -- `AutoBrightnessEnabled` (读写) -- 配置相关的 Get/Set 方法 - -```mermaid -sequenceDiagram - participant App as 应用程序 - participant DBus as DBus - participant Manager as Display Manager - participant ABM as AutoBrightnessManager - - Note over App,ABM: 查询支持状态 - App->>DBus: Get AutoBrightnessSupported - DBus->>Manager: 读取属性 - Manager->>ABM: IsSupported() - ABM-->>Manager: true/false - Manager-->>DBus: 返回值 - DBus-->>App: true/false - - Note over App,ABM: 启用自动亮度 - App->>DBus: Set AutoBrightnessEnabled = true - DBus->>Manager: 设置属性 - Manager->>ABM: SetEnabled(true) - ABM->>ABM: Start() - ABM-->>Manager: 成功 - Manager->>DBus: PropertiesChanged 信号 - DBus->>App: 属性变化通知 - - Note over App,ABM: 查询状态信息 - App->>DBus: Call GetStatus() - DBus->>Manager: 调用方法 - Manager->>ABM: GetStatus() - ABM-->>Manager: 状态 map - Manager-->>DBus: 返回状态 - DBus-->>App: 状态信息 -``` - -## 13. 依赖关系 - -### 13.1 外部依赖 - -- **iio-sensor-proxy:** 提供环境光传感器数据 -- **DSettings/DConfig:** 配置管理 -- **DBus:** 进程间通信 - -### 13.2 内部依赖 - -- **display.Manager:** 显示器管理和亮度控制 -- **BrightnessTransition:** 亮度渐变效果 -- **backlight:** 底层亮度控制 - -## 14. 测试要点 - -### 14.1 功能测试 - -- 基本启停流程 -- 配置加载和保存 -- 亮度计算准确性 -- 手动调节处理 - -```mermaid -graph TD - subgraph "基本功能测试" - T1[初始化测试] --> T1A{检查支持状态} - T1A -->|支持| T1B[验证 supported = true] - T1A -->|不支持| T1C[验证 supported = false] - - T2[启动测试] --> T2A[调用 Start] - T2A --> T2B{检查传感器} - T2B -->|成功| T2C[验证 running = true] - T2B -->|失败| T2D[验证错误处理] - - T3[轮询测试] --> T3A[模拟光照变化] - T3A --> T3B[等待轮询周期] - T3B --> T3C[验证亮度调整] - - T4[停止测试] --> T4A[调用 Stop] - T4A --> T4B[验证资源释放] - T4B --> T4C[验证 running = false] - end - - subgraph "手动调节测试" - T5[临时暂停模式] --> T5A[手动调节亮度] - T5A --> T5B[验证暂停状态] - T5B --> T5C[等待超时] - T5C --> T5D[验证自动恢复] - - T6[完全禁用模式] --> T6A[修改配置] - T6A --> T6B[手动调节亮度] - T6B --> T6C[验证功能停止] - T6C --> T6D[验证配置更新] - end - - subgraph "渐变测试" - T7[渐变效果] --> T7A[设置目标亮度] - T7A --> T7B[验证渐变启动] - T7B --> T7C[监控步进过程] - T7C --> T7D[验证到达目标] - - T8[渐变中断] --> T8A[启动渐变] - T8A --> T8B[发起新渐变] - T8B --> T8C[验证旧渐变停止] - T8C --> T8D[验证新渐变启动] - end -``` - -### 14.2 异常测试 - -- 传感器服务不可用 -- 配置文件损坏 -- 并发访问 -- 资源泄漏 - -```mermaid -graph TD - subgraph "异常场景测试" - E1[传感器服务异常] --> E1A[停止 iio-sensor-proxy] - E1A --> E1B[验证服务不可用检测] - E1B --> E1C[验证优雅降级] - E1C --> E1D[重启服务] - E1D --> E1E[验证自动恢复] - - E2[配置异常] --> E2A[损坏配置文件] - E2A --> E2B[尝试加载配置] - E2B --> E2C[验证使用默认配置] - - E3[并发测试] --> E3A[多线程调用] - E3A --> E3B[验证无死锁] - E3B --> E3C[验证数据一致性] - - E4[资源泄漏] --> E4A[反复启停] - E4A --> E4B[监控 goroutine 数量] - E4B --> E4C[监控内存使用] - E4C --> E4D[验证资源正确释放] - end - - subgraph "边界条件测试" - B1[极端光照值] --> B1A[测试 0 lux] - B1A --> B1B[测试 255 lux] - B1B --> B1C[测试超出范围值] - - B2[极端配置] --> B2A[最小轮询间隔] - B2A --> B2B[最大敏感度] - B2B --> B2C[最小阈值] - - B3[快速变化] --> B3A[光照快速波动] - B3A --> B3B[验证防抖动] - B3B --> B3C[验证调节频率限制] - end -``` - -### 14.3 性能测试 - -- CPU 占用率 -- 内存使用 -- 响应延迟 -- 长时间运行稳定性 - -```mermaid -graph LR - subgraph "性能指标" - P1[CPU 占用] --> P1A[空闲时 < 0.5%] - P1A --> P1B[调节时 < 2%] - - P2[内存使用] --> P2A[基础内存 < 5MB] - P2A --> P2B[无内存泄漏] - - P3[响应延迟] --> P3A[光照变化到调节 < 5s] - P3A --> P3B[配置变更生效 < 1s] - - P4[稳定性] --> P4A[24小时运行测试] - P4A --> P4B[无崩溃无异常] - end - - subgraph "压力测试" - S1[高频调节] --> S1A[每秒变化光照] - S1A --> S1B[验证系统稳定] - - S2[长时间运行] --> S2A[连续运行7天] - S2A --> S2B[监控资源使用] - S2B --> S2C[验证无退化] - - S3[并发压力] --> S3A[多线程频繁调用] - S3A --> S3B[验证锁性能] - S3B --> S3C[验证无竞争条件] - end -``` - -## 15. 未来扩展 - -### 15.1 算法优化 - -- 支持非线性亮度曲线 -- 机器学习自适应调节 -- 时间段相关的亮度策略 - -### 15.2 功能增强 - -- 多显示器独立控制 -- 色温自动调节 -- 用户习惯学习 - -### 15.3 性能优化 - -- 事件驱动替代轮询 -- 智能采样频率调整 -- 更精细的电源管理 - -## 16. 典型使用场景 - -### 16.1 场景一:用户启用自动亮度 - -```mermaid -sequenceDiagram - participant User as 用户 - participant CC as 控制中心 - participant Manager as Display Manager - participant ABM as AutoBrightnessManager - participant Sensor as 传感器 - participant Display as 显示器 - - User->>CC: 打开自动亮度开关 - CC->>Manager: SetAutoBrightnessEnabled(true) - Manager->>ABM: SetEnabled(true) - ABM->>ABM: 保存配置 - ABM->>ABM: Start() - ABM->>Sensor: 连接并声明传感器 - Sensor-->>ABM: 成功 - ABM->>ABM: 启动轮询 - - loop 每3秒 - ABM->>Sensor: 读取光照强度 - Sensor-->>ABM: 150 lux - ABM->>ABM: 计算目标亮度 58% - ABM->>Display: 渐变调整到 58% - Display->>Display: 平滑过渡 - end - - Manager->>CC: PropertiesChanged - CC->>User: 显示已启用状态 -``` - -### 16.2 场景二:环境光变化触发调节 - -```mermaid -sequenceDiagram - participant Env as 环境 - participant Sensor as 传感器 - participant ABM as AutoBrightnessManager - participant Display as 显示器 - participant User as 用户 - - Note over Env: 室内光线变暗 - Env->>Sensor: 光照从 200 lux 降至 50 lux - - ABM->>Sensor: 轮询读取 - Sensor-->>ABM: 50 lux - ABM->>ABM: 计算目标亮度 - Note over ABM: 当前 78% -> 目标 29%
变化 49% > 阈值 20% - - ABM->>ABM: shouldAdjustBrightness() - ABM->>ABM: 所有条件满足 - - ABM->>Display: SetBrightnessForced(0.29) - Note over Display: 启动渐变
2秒内从 78% -> 29% - - loop 20步,每步100ms - Display->>Display: 亮度 -= 2.45% - end - - Display->>User: 屏幕亮度平滑降低 - Note over User: 感觉舒适,无闪烁 -``` - -### 16.3 场景三:手动调节后的行为 - -```mermaid -sequenceDiagram - participant User as 用户 - participant CC as 控制中心 - participant Manager as Display Manager - participant ABM as AutoBrightnessManager - participant Sensor as 传感器 - - Note over ABM: 自动亮度运行中 - - User->>CC: 手动拖动亮度滑块到 90% - CC->>Manager: SetBrightness(0.9) - Manager->>Manager: setBrightness() - Manager->>ABM: OnManualBrightnessChange() - - alt 临时暂停模式 - ABM->>ABM: 记录暂停时间 - ABM->>Sensor: ReleaseLight() - Note over ABM: 暂停 300 秒 - - Note over ABM,Sensor: 5分钟后 - ABM->>ABM: 检查超时 - ABM->>Sensor: ClaimLight() - ABM->>ABM: 恢复自动调节 - Note over User: 自动亮度重新生效 - - else 完全禁用模式 - ABM->>ABM: 保存 Enabled = false - ABM->>ABM: Stop() - ABM->>Sensor: 释放并断开 - Manager->>CC: PropertiesChanged - CC->>User: 显示已禁用状态 - Note over User: 需手动重新启用 - end -``` - -### 16.4 场景四:系统休眠与唤醒 - -```mermaid -sequenceDiagram - participant PM as 电源管理 - participant Manager as Display Manager - participant ABM as AutoBrightnessManager - participant Sensor as 传感器 - participant Display as 显示器 - - Note over PM: 用户合上笔记本 - PM->>Manager: PrepareForSleep(true) - Manager->>ABM: hold() - ABM->>ABM: systemAdjusting = true - ABM->>ABM: stopPolling() - Note over ABM: 保持连接但停止轮询 - - Note over PM: 系统休眠... - - Note over PM: 用户打开笔记本 - PM->>Manager: PrepareForSleep(false) - Manager->>ABM: resume() - ABM->>ABM: startPolling() - ABM->>Sensor: 立即读取光照 - Sensor-->>ABM: 当前光照值 - ABM->>Display: 调整到合适亮度 - ABM->>ABM: systemAdjusting = false - Note over ABM: 恢复正常运行 -``` - -## 17. 注意事项 - -1. **线程安全:** 所有公共方法都需要考虑并发访问 -2. **资源管理:** 确保传感器资源正确释放 -3. **用户体验:** 避免频繁调节造成闪烁 -4. **电源效率:** 合理设置轮询间隔 -5. **降级策略:** 功能不可用时不影响系统稳定性 - diff --git "a/display1/docs/auto_backlight/\350\207\252\345\212\250\344\272\256\345\272\246\345\212\237\350\203\275\344\275\277\347\224\250\346\214\207\345\215\227.md" "b/display1/docs/auto_backlight/\350\207\252\345\212\250\344\272\256\345\272\246\345\212\237\350\203\275\344\275\277\347\224\250\346\214\207\345\215\227.md" deleted file mode 100644 index 0618bc460..000000000 --- "a/display1/docs/auto_backlight/\350\207\252\345\212\250\344\272\256\345\272\246\345\212\237\350\203\275\344\275\277\347\224\250\346\214\207\345\215\227.md" +++ /dev/null @@ -1,393 +0,0 @@ -# 自动亮度功能使用指南 - -## 功能介绍 - -自动亮度功能可以根据周围环境的光线强度,自动调整笔记本电脑或一体机屏幕的亮度,让您的眼睛更舒适,同时节省电量。 - -## 配置工具说明 - -本功能使用 `dde-dconfig` 工具进行配置管理。`dde-dconfig` 是 Deepin 桌面环境的统一配置管理工具。 - -**基本语法**: -```bash -dde-dconfig -a <应用ID> -r <资源名称> -k <配置键> --set -v <值> # 设置配置 -dde-dconfig -a <应用ID> -r <资源名称> -k <配置键> --get # 查询配置 -``` - -**自动亮度配置参数**: -- 应用ID:`org.deepin.startdde` -- 资源名称:`org.deepin.Display.AutoBrightness` -- 配置键:`enabled`, `sensitivity`, `polling-interval`, `change-threshold`, `manual-override-duration` - -## 使用前准备 - -### 检查设备是否支持 - -您的设备需要满足以下条件: - -1. **有环境光传感器**(大部分现代笔记本都有) -2. **是内置屏幕**(外接显示器不支持) -3. **系统服务正常** - -### 快速检查方法 - -打开终端,输入以下命令检查: - -```bash -# 检查是否支持自动亮度 -dbus-send --session --print-reply --dest=com.deepin.daemon.Display \ - /com/deepin/daemon/Display org.freedesktop.DBus.Properties.Get \ - string:com.deepin.daemon.Display string:AutoBrightnessSupported -``` - -如果返回 `boolean true`,说明您的设备支持此功能。 - -## 基本使用 - -### 启用自动亮度 - -```bash -# 启用自动亮度 -dbus-send --session --dest=com.deepin.daemon.Display \ - /com/deepin/daemon/Display com.deepin.daemon.Display.SetAutoBrightnessEnabled \ - boolean:true -``` - -### 关闭自动亮度 - -```bash -# 关闭自动亮度 -dbus-send --session --dest=com.deepin.daemon.Display \ - /com/deepin/daemon/Display com.deepin.daemon.Display.SetAutoBrightnessEnabled \ - boolean:false -``` - -### 查看当前状态 - -```bash -# 查看是否已启用 -dbus-send --session --print-reply --dest=com.deepin.daemon.Display \ - /com/deepin/daemon/Display org.freedesktop.DBus.Properties.Get \ - string:com.deepin.daemon.Display string:AutoBrightnessEnabled - -# 查看当前环境光强度 -dbus-send --session --print-reply --dest=com.deepin.daemon.Display \ - /com/deepin/daemon/Display org.freedesktop.DBus.Properties.Get \ - string:com.deepin.daemon.Display string:CurrentLightLevel -``` - -## 个性化设置 - -### 设置参数说明 - -| 设置项 | 配置键 | 说明 | 取值范围 | 推荐值 | -|-------|--------|------|---------|--------| -| **启用状态** | enabled | 是否启用自动亮度功能 | true/false | true | -| **敏感度** | sensitivity | 控制亮度调节的敏感程度,值越大亮度变化越明显 | 0.1-3.0 | 0.5(标准)| -| **检测间隔** | polling-interval | 多久检测一次环境光(秒) | 1-60秒 | 3秒(标准)| -| **变化阈值** | change-threshold | 环境光变化多少才调节亮度(绝对值) | 1.0-50.0 | 20.0(标准)| -| **暂停时间** | manual-override-duration | 手动调节后暂停自动调节的时间(秒) | 60-1800秒 | 300秒(5分钟)| - -### 单独设置配置项 - -每个配置项都可以使用 `dde-dconfig` 命令单独设置: - -```bash -# 设置敏感度 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.5 - -# 设置检测间隔(秒) -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 3 - -# 设置变化阈值 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 20.0 - -# 设置手动调节暂停时间(秒) -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 300 -``` - -### 查询单个配置项 - -```bash -# 查询敏感度 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --get - -# 查询检测间隔 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --get - -# 查询变化阈值 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --get - -# 查询暂停时间 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k manual-override-duration --get - -# 查询所有配置 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness --list -``` - -### 常用配置示例 - -**注意**:配置修改后会立即生效,无需重启服务。 - -#### 标准配置(推荐) -```bash -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.5 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 3 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 20.0 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 300 -``` - -#### 快速响应配置(适合移动办公) -```bash -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.8 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 2 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 15.0 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 180 -``` - -#### 稳定配置(适合固定办公) -```bash -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.4 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 5 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 25.0 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 600 -``` - -#### 节能配置(最大化省电) -```bash -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.3 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 10 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 30.0 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 900 -``` - -## 使用技巧 - -### 1. 手动调节与自动调节的配合 - -- **手动调节后**:系统会自动暂停5分钟,避免冲突 -- **立即恢复**:关闭后重新开启自动亮度即可立即恢复 -- **调整暂停时间**:可以修改 `manual_override_duration` 参数 - -### 2. 不同环境的优化 - -**明亮办公室**: -- 敏感度:0.4-0.6 -- 检测间隔:5-10秒 -- 变化阈值:20.0-30.0 - -**昏暗环境**: -- 敏感度:0.6-0.8 -- 检测间隔:3-5秒 -- 变化阈值:15.0-20.0 - -**户外使用**: -- 敏感度:0.8-1.2 -- 检测间隔:2-3秒 -- 变化阈值:10.0-15.0 - -### 3. 省电优化 - -如果您更关心电池续航: -- 增加检测间隔(10-30秒) -- 提高变化阈值(25.0-40.0) -- 降低敏感度(0.3-0.5) - -## 常见问题解决 - -### 问题1:功能不可用 - -**现象**:显示不支持自动亮度 - -**解决方法**: -```bash -# 检查传感器服务 -sudo systemctl status iio-sensor-proxy - -# 如果服务未运行,启动它 -sudo systemctl start iio-sensor-proxy -sudo systemctl enable iio-sensor-proxy -``` - -### 问题2:亮度不调节 - -**现象**:环境光变化但屏幕亮度不变 - -**解决方法**: -1. 检查是否在手动调节暂停期间 -2. 降低变化阈值 -3. 重新启用功能 - -```bash -# 方法1:降低变化阈值(更敏感) -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 10.0 - -# 方法2:重新启用(清除暂停状态) -dbus-send --session --dest=com.deepin.daemon.Display \ - /com/deepin/daemon/Display com.deepin.daemon.Display.SetAutoBrightnessEnabled \ - boolean:false -dbus-send --session --dest=com.deepin.daemon.Display \ - /com/deepin/daemon/Display com.deepin.daemon.Display.SetAutoBrightnessEnabled \ - boolean:true -``` - -### 问题3:调节太频繁 - -**现象**:屏幕亮度变化太频繁,影响使用 - -**解决方法**: -```bash -# 方法1:提高变化阈值(降低敏感度) -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 30.0 - -# 方法2:增加检测间隔 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 10 - -# 方法3:使用完整稳定配置 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.4 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 5 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 25.0 -``` - -### 问题4:反应太慢 - -**现象**:环境光变化后很久才调节亮度 - -**解决方法**: -```bash -# 方法1:降低变化阈值(更敏感) -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 10.0 - -# 方法2:减少检测间隔 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 2 - -# 方法3:使用完整快速响应配置 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.8 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 2 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 15.0 -``` - -## 高级功能 - -### 查看详细状态 - -```bash -# 查看所有配置项 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness --list - -# 查看单个配置项 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k enabled --get -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --get -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --get -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --get -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k manual-override-duration --get - -# 查看运行状态(通过 D-Bus) -dbus-send --session --print-reply --dest=com.deepin.daemon.Display \ - /com/deepin/daemon/Display org.freedesktop.DBus.Properties.Get \ - string:com.deepin.daemon.Display string:AutoBrightnessSupported - -dbus-send --session --print-reply --dest=com.deepin.daemon.Display \ - /com/deepin/daemon/Display org.freedesktop.DBus.Properties.Get \ - string:com.deepin.daemon.Display string:AutoBrightnessEnabled -``` - -### 监控环境光变化 - -```bash -# 实时监控环境光强度变化 -watch -n 1 'dbus-send --session --print-reply --dest=com.deepin.daemon.Display \ - /com/deepin/daemon/Display org.freedesktop.DBus.Properties.Get \ - string:com.deepin.daemon.Display string:CurrentLightLevel' -``` - -### 查看日志 - -如果遇到问题,可以查看系统日志: - -```bash -# 查看自动亮度相关日志 -journalctl --user -u startdde -f | grep AutoBrightness - -# 查看传感器服务日志 -journalctl -u iio-sensor-proxy -f -``` - -## 注意事项 - -1. **仅限内置屏幕**:外接显示器不支持此功能 -2. **传感器位置**:不要遮挡设备上的光线传感器 -3. **电量消耗**:频繁检测会增加电量消耗,建议合理设置检测间隔 -4. **手动优先**:手动调节亮度后会自动暂停功能一段时间 -5. **重启保持**:设置会自动保存,重启后依然有效 -6. **敏感度说明**:敏感度参数控制环境光到屏幕亮度的映射关系,值越大屏幕亮度变化越明显。推荐范围 0.3-1.0,默认 0.5 - -## 配置参数详解 - -### 敏感度 (sensitivity) - -敏感度控制环境光强度到屏幕亮度的转换比例: - -- **0.1-0.3**:低敏感度,适合希望屏幕亮度变化较小的用户 -- **0.4-0.6**:标准敏感度,适合大多数使用场景(推荐) -- **0.7-1.0**:高敏感度,适合需要明显亮度变化的场景 -- **1.1-3.0**:超高敏感度,仅在特殊场景使用 - -**计算公式**:屏幕亮度 = (环境光强度 × 敏感度) / 255 - -### 检测间隔 (polling_interval) - -控制多久检测一次环境光强度: - -- **1-2秒**:快速响应,适合移动办公,但会增加电量消耗 -- **3-5秒**:标准响应,平衡响应速度和电量消耗(推荐) -- **6-10秒**:慢速响应,适合固定办公环境 -- **11-60秒**:省电模式,适合电池续航优先的场景 - -### 变化阈值 (change_threshold) - -环境光强度变化超过此值才会触发亮度调节: - -- **1.0-10.0**:低阈值,对环境光变化敏感,调节频繁 -- **11.0-25.0**:标准阈值,适合大多数场景(推荐) -- **26.0-50.0**:高阈值,只在环境光明显变化时才调节 - -**注意**:阈值过低会导致频繁调节,阈值过高会导致响应迟钝 - -### 暂停时间 (manual_override_duration) - -手动调节亮度后,自动调节功能暂停的时间: - -- **60-180秒**:短暂停,适合频繁切换环境的场景 -- **181-600秒**:标准暂停,适合大多数场景(推荐 300秒) -- **601-1800秒**:长暂停,适合希望手动控制优先的场景 - -## 推荐设置 - -### 日常办公 -```bash -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.5 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 3 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 20.0 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 300 -``` - -### 移动办公 -```bash -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.8 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 2 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 15.0 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 180 -``` - -### 省电模式 -```bash -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.3 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 10 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 30.0 -dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 600 -``` - ---- - -**提示**:如果您不熟悉命令行操作,建议使用系统设置界面进行配置(如果可用)。如有问题,可以随时关闭此功能,不会影响正常的手动亮度调节。 diff --git a/display1/manager.go b/display1/manager.go index 3b26fad99..2f50bc79c 100644 --- a/display1/manager.go +++ b/display1/manager.go @@ -90,19 +90,20 @@ const ( DSettingsKeyCustomDisplayMode = "customDisplayMode" // 亮度曲线配置 - DSettingsKeyBacklightCurveType = "backlight-curve-type" - DSettingsKeyBacklightMinValue = "backlight-curve-min-value" - DSettingsKeyBacklightMidValue = "backlight-curve-mid-value" - DSettingsKeyBrightnessPercentage = "brightness-percentage" - DSettingsKeyCanSetBrightnessDelay = "can-set-brightness-delay-interval" - DSettingsKeyCustomBrightnessCurves = "custom-brightness-curves" - DSettingsKeyDefaultBrightnessCurve = "default-brightness-curve" - DSettingsKeyMaxBrightnessUnlimited = "max-brightness-unlimited" - - // 渐变亮度配置 - DSettingsKeyABTransitionEnabled = "transition-enabled" - DSettingsKeyABTransitionDuration = "transition-duration" - DSettingsKeyABTransitionStepInterval = "transition-step-interval" + DSettingsKeyBacklightCurveType = "backlight-curve-type" + DSettingsKeyBacklightMinValue = "backlight-curve-min-value" + DSettingsKeyBacklightMidValue = "backlight-curve-mid-value" + DSettingsKeyBrightnessPercentage = "brightness-percentage" + DSettingsKeyCanSetBrightnessDelay = "can-set-brightness-delay-interval" + DSettingsKeyCustomBrightnessCurves = "custom-brightness-curves" + DSettingsKeyDefaultBrightnessCurve = "default-brightness-curve" + DSettingsKeyMaxBrightnessUnlimited = "max-brightness-unlimited" + + // 统一亮度过渡配置 + DSettingsKeyTransitionEnabled = "transition-enabled" + DSettingsKeyTransitionDuration = "transition-duration" + DSettingsKeyTransitionStepPercent = "transition-step-percent" + DSettingsKeyTransitionMinStepInterval = "transition-min-step-interval" // 自动亮度配置(独立配置文件) DSettingsAutoBrightnessAppID = "org.deepin.dde.daemon" @@ -118,7 +119,7 @@ const ( DSettingsKeyABCurve = "lux-brightness-curve" // 卡尔曼滤波器配置 - DSettingsKeyABKalmanProcessNoise = "kalman-process-noise" + DSettingsKeyABKalmanProcessNoise = "kalman-process-noise" DSettingsKeyABKalmanMeasurementNoise = "kalman-measurement-noise" DSettingsKeyABKalmanWindowSize = "kalman-window-size" @@ -196,8 +197,9 @@ type Manager struct { debugOpts debugOptions redshiftRunner *redshiftRunner - sessionActive bool - newSysCfg *SysRootConfig + sessionActive bool + sessionActiveMu sync.RWMutex + newSysCfg *SysRootConfig cursorShowed bool // dconfig com.deepin.Display @@ -272,15 +274,22 @@ type Manager struct { isVM bool // 自动亮度相关属性 - AutoBrightnessEnabled bool `prop:"access:rw"` - AutoBrightnessSupported bool `prop:"access:r"` - CurveMaxScale int32 `prop:"access:r"` + AutoBrightnessEnabled bool `prop:"access:rw"` + AutoBrightnessSupported bool `prop:"access:r"` + CurveMaxScale int32 `prop:"access:r"` // 自动亮度管理器 autoBrightnessManager *AutoBrightnessManager - // 亮度渐变管理器 - brightnessTransition *BrightnessTransition + // 统一亮度过渡管理器 + transitionManager *brightness.TransitionManager + + // 统一亮度过渡配置 + transitionMu sync.RWMutex + transitionEnabled bool + transitionDuration int // 毫秒 + transitionStepPercent float64 // 步进百分比 + transitionMinStepInterval int // 最小步进间隔(毫秒) // 背光曲线配置 backlightCurveType string @@ -384,7 +393,9 @@ func newManager(service *dbusutil.Service) *Manager { loginManager.InitSignalExt(sysSigLoop, true) /* 当系统从待机或者休眠状态唤醒时,需要重新获取当前屏幕的状态 */ _, err = loginManager.ConnectPrepareForSleep(func(isSleep bool) { - if !isSleep { + if isSleep { + m.holdAutoBrightness() + } else { logger.Info("system Wakeup, need reacquire screen status", isSleep) m.initScreenRotation() @@ -394,6 +405,7 @@ func newManager(service *dbusutil.Service) *Manager { if error != nil { logger.Warning("Cancel wm blackscreen failed", error) } + m.resumeAutoBrightness() } }) @@ -433,7 +445,9 @@ func newManager(service *dbusutil.Service) *Manager { } logger.Debug("session active changed", active) + m.sessionActiveMu.Lock() m.sessionActive = active + m.sessionActiveMu.Unlock() if !active { m.holdAutoBrightness() return @@ -457,7 +471,9 @@ func newManager(service *dbusutil.Service) *Manager { logger.Warningf("prop active ConnectChanged failed! %v", err) } + m.sessionActiveMu.Lock() m.sessionActive, err = selfSession.Active().Get(0) + m.sessionActiveMu.Unlock() if err != nil { logger.Warning(err) } @@ -531,6 +547,42 @@ func (m *Manager) initDConfig(sysBus *dbus.Conn) { m.getBacklightCurveType() case DSettingsKeyMaxBrightnessUnlimited: m.getMaxBrightnessUnlimited() + case DSettingsKeyTransitionEnabled, + DSettingsKeyTransitionDuration, + DSettingsKeyTransitionStepPercent, + DSettingsKeyTransitionMinStepInterval: + m.transitionMu.Lock() + // 重新读取所有过渡配置 + if v, err := _dsConfigManager.Value(0, DSettingsKeyTransitionEnabled); err == nil { + m.transitionEnabled = v.Value().(bool) + } + if v, err := _dsConfigManager.Value(0, DSettingsKeyTransitionDuration); err == nil { + switch val := v.Value().(type) { + case int64: + m.transitionDuration = int(val) + case float64: + m.transitionDuration = int(val) + } + } + if v, err := _dsConfigManager.Value(0, DSettingsKeyTransitionStepPercent); err == nil { + m.transitionStepPercent = v.Value().(float64) + } + if v, err := _dsConfigManager.Value(0, DSettingsKeyTransitionMinStepInterval); err == nil { + switch val := v.Value().(type) { + case int64: + m.transitionMinStepInterval = int(val) + case float64: + m.transitionMinStepInterval = int(val) + } + } + m.transitionMu.Unlock() + // 更新统一 TransitionManager 配置 + if m.transitionManager != nil { + m.transitionManager.SetEnabled(m.transitionEnabled) + m.transitionManager.SetDuration(m.transitionDuration) + m.transitionManager.SetStepPercent(m.transitionStepPercent) + m.transitionManager.SetMinStepInterval(m.transitionMinStepInterval) + } default: break } @@ -794,7 +846,10 @@ func (m *Manager) initSysDisplay() { logger.Debug("get new sysConfig:", spew.Sdump(newSysConfig)) } - if !m.sessionActive { + m.sessionActiveMu.RLock() + sessionActive := m.sessionActive + m.sessionActiveMu.RUnlock() + if !sessionActive { m.newSysCfg = newSysConfig return } @@ -877,6 +932,14 @@ func (m *Manager) handleSysConfigUpdated(newSysConfig *SysRootConfig) { go func() { for _, config := range newMonitorCfgs { if config.Enabled { + // 如果开启了自动亮度且当前处理的是内置显示器,则跳过应用旧的手动亮度 + builtin := m.getBuiltinMonitor() + if builtin != nil && builtin.Name == config.Name && m.AutoBrightnessEnabled && m.autoBrightnessManager != nil { + logger.Debugf("Auto brightness enabled, skip manual brightness setup for %s and trigger adjust", config.Name) + m.autoBrightnessManager.adjustBrightnessOnce() + continue + } + err := m.setBrightness(config.Name, config.Brightness) if err != nil { logger.Warning(err) @@ -1372,7 +1435,8 @@ func (m *Manager) init() { m.logDisplayScreenEvent() go func() { - m.initBrightnessTransition() + m.initTransitionConfig() + m.initTransitionManager() m.initAutoBrightness() }() } @@ -2449,6 +2513,15 @@ func (m *Manager) applySysMonitorConfigs(mode byte, monitorsId monitorsId, monit // TODO:色温和设置亮度都调用了setBrightness的接口,逻辑重复了,需要优化。 for _, config := range configs { if config.Enabled { + // 如果开启了自动亮度且当前处理的是内置显示器,则跳过应用旧的手动亮度 + // 并触发一次自动亮度调节 + builtin := m.getBuiltinMonitor() + if builtin != nil && builtin.Name == config.Name && m.AutoBrightnessEnabled && m.autoBrightnessManager != nil { + logger.Debugf("Auto brightness enabled, skip manual brightness setup for %s and trigger adjust", config.Name) + m.autoBrightnessManager.adjustBrightnessOnce() + continue + } + err := m.setBrightness(config.Name, config.Brightness) if err != nil { logger.Warningf("call setBrightness err: %v, config.Name: %s", err, config.Name) @@ -3580,27 +3653,154 @@ func (m *Manager) initAutoBrightness() { logger.Info("Auto brightness manager initialized successfully") } -// initBrightnessTransition 初始化亮度渐变管理器 -func (m *Manager) initBrightnessTransition() { - logger.Debug("Initializing brightness transition manager") +// initTransitionManager 初始化亮度过渡管理器 +func (m *Manager) initTransitionManager() { + logger.Info("Initializing unified transition manager") + + // 创建统一过渡管理器 + m.transitionManager = brightness.NewTransitionManager() + m.transitionManager.SetEnabled(m.transitionEnabled) + m.transitionManager.SetDuration(m.transitionDuration) + m.transitionManager.SetStepPercent(m.transitionStepPercent) + m.transitionManager.SetMinStepInterval(m.transitionMinStepInterval) + + // 设置亮度类型判断回调 + m.transitionManager.SetGetBrightnessTypeFunc(func(monitorName string) brightness.BrightnessType { + setter := m.getSetterConfig() + isBuiltin := m.isBuiltinMonitor(monitorName) + + switch setter { + case brightness.BrightnessSetterBacklight: + return brightness.TypeBacklight + case brightness.BrightnessSetterAuto: + if isBuiltin { + return brightness.TypeBacklight + } + return brightness.TypeGamma + default: // BrightnessSetterGamma + return brightness.TypeGamma + } + }) + + // 设置 Backlight 回调 + m.transitionManager.SetBacklightFuncs( + func(percent float64) error { + return brightness.SetBacklight(percent) + }, + func() (float64, error) { + return brightness.GetBacklightCurrentValue() + }, + ) + + // 设置 Gamma 回调 + m.transitionManager.SetGammaFuncs( + func(monitorName string, percent float64) error { + temperature := m.getColorTemperatureValue() + monitors := m.getConnectedMonitors() + monitor := monitors.GetByName(monitorName) + if monitor == nil { + return fmt.Errorf("monitor not found: %s", monitorName) + } + _uuid := monitor.uuid + if _useWayland { + _uuid = monitor.uuidV0 + } + return brightness.SetOutputGama(percent, temperature, monitor.ID, m.xConn, _uuid) + }, + func(monitorName string) (float64, error) { + currentBrightness := m.getMonitorBrightness(monitorName) + if currentBrightness < 0 { + return 0.5, nil + } + return currentBrightness, nil + }, + ) + + logger.Infof("Unified transition manager initialized: enabled=%v, duration=%dms, stepPercent=%.2f%%, minInterval=%dms", + m.transitionEnabled, m.transitionDuration, m.transitionStepPercent, m.transitionMinStepInterval) +} + +// initTransitionConfig 初始化亮度过渡配置 +func (m *Manager) initTransitionConfig() { + m.transitionMu.Lock() + defer m.transitionMu.Unlock() - // 创建亮度渐变管理器 - m.brightnessTransition = NewBrightnessTransition(m) + // 默认配置 + m.transitionEnabled = false + m.transitionDuration = 4000 + m.transitionStepPercent = 1.0 + m.transitionMinStepInterval = 100 - // 加载配置 - err := m.brightnessTransition.LoadConfig(m.sysBus) - if err != nil { - logger.Warning("Failed to load brightness transition config:", err) - return + // 从 DSettings 读取配置 + getTransitionEnabled := func() { + v, err := _dsConfigManager.Value(0, DSettingsKeyTransitionEnabled) + if err != nil { + logger.Warning(err) + m.transitionEnabled = false + return + } + m.transitionEnabled = v.Value().(bool) + logger.Info("Transition enabled:", m.transitionEnabled) } - // 监听配置变化 - err = m.brightnessTransition.WatchConfigChanges(m.sysSigLoop) - if err != nil { - logger.Warning("Failed to watch brightness transition config changes:", err) + getTransitionDuration := func() { + v, err := _dsConfigManager.Value(0, DSettingsKeyTransitionDuration) + if err != nil { + logger.Warning(err) + m.transitionDuration = 4000 + return + } + switch val := v.Value().(type) { + case int64: + m.transitionDuration = int(val) + case float64: + m.transitionDuration = int(val) + default: + m.transitionDuration = 4000 + } + logger.Info("Transition duration:", m.transitionDuration, "ms") } - logger.Info("Brightness transition manager initialized successfully") + getTransitionStepPercent := func() { + v, err := _dsConfigManager.Value(0, DSettingsKeyTransitionStepPercent) + if err != nil { + logger.Warning(err) + m.transitionStepPercent = 1.0 + return + } + switch val := v.Value().(type) { + case int64: + m.transitionStepPercent = float64(val) + case float64: + m.transitionStepPercent = val + default: + m.transitionStepPercent = 1.0 + } + logger.Info("Transition step percent:", m.transitionStepPercent) + } + + getTransitionMinStepInterval := func() { + v, err := _dsConfigManager.Value(0, DSettingsKeyTransitionMinStepInterval) + if err != nil { + logger.Warning(err) + m.transitionMinStepInterval = 100 + return + } + switch val := v.Value().(type) { + case int64: + m.transitionMinStepInterval = int(val) + case float64: + m.transitionMinStepInterval = int(val) + default: + m.transitionMinStepInterval = 100 + } + logger.Info("Transition min step interval:", m.transitionMinStepInterval, "ms") + } + + getTransitionEnabled() + getTransitionDuration() + getTransitionStepPercent() + getTransitionMinStepInterval() } // notifyManualBrightnessChange 通知手动亮度调节 @@ -3617,12 +3817,6 @@ func (m *Manager) isPowerSaving() bool { return m.powerSaving } -func (m *Manager) setSystemAdjusting(adjusting bool) { - if m.autoBrightnessManager != nil { - m.autoBrightnessManager.setSystemAdjusting(adjusting) - } -} - // holdAutoBrightness 通知系统休眠 func (m *Manager) holdAutoBrightness() { if m.autoBrightnessManager != nil { @@ -3637,11 +3831,17 @@ func (m *Manager) resumeAutoBrightness() { } } +// setSystemAdjusting 设置系统调整标志(用于区分系统自动调整和用户手动调整) +func (m *Manager) setSystemAdjusting(adjusting bool) { + if m.autoBrightnessManager != nil { + m.autoBrightnessManager.setSystemAdjusting(adjusting) + } +} + // scheduleSystemAdjustingClear 延迟清除系统调整标志 func (m *Manager) scheduleSystemAdjustingClear(delay time.Duration) { m.systemAdjustingTimerMu.Lock() defer m.systemAdjustingTimerMu.Unlock() - if m.systemAdjustingTimer != nil { m.systemAdjustingTimer.Stop() } diff --git a/display1/manager_ifc.go b/display1/manager_ifc.go index 2273be9ed..303c2ada3 100644 --- a/display1/manager_ifc.go +++ b/display1/manager_ifc.go @@ -111,6 +111,10 @@ func (m *Manager) AssociateTouchByUUID(outputName, touchUUID string) *dbus.Error func (m *Manager) ChangeBrightness(raised bool) *dbus.Error { logger.Debug("dbus call ChangeBrightness", raised) err := m.changeBrightness(raised) + if err == nil { + // 通知自动亮度管理器手动调节 + m.notifyManualBrightnessChange() + } return dbusutil.ToError(err) } @@ -213,6 +217,9 @@ func (m *Manager) SetAndSaveBrightness(outputName string, value float64) *dbus.E return dbusutil.ToError(err) } + // 通知自动亮度管理器手动调节 + m.notifyManualBrightnessChange() + err = m.saveBrightnessInCfg(map[string]float64{ outputName: value, }) @@ -240,6 +247,10 @@ func (m *Manager) SetBrightness(outputName string, value float64) *dbus.Error { logger.Warning(err) return dbusutil.ToError(err) } + + // 通知自动亮度管理器手动调节 + m.notifyManualBrightnessChange() + return nil } diff --git a/display1/manager_lid.go b/display1/manager_lid.go index 001e7e3bb..3c4cdbd65 100644 --- a/display1/manager_lid.go +++ b/display1/manager_lid.go @@ -6,6 +6,7 @@ package display1 import ( "fmt" + "time" configManager "github.com/linuxdeepin/go-dbus-factory/org.desktopspec.ConfigManager" syspower "github.com/linuxdeepin/go-dbus-factory/system/org.deepin.dde.power1" @@ -25,6 +26,46 @@ const ( func (m *Manager) initLidSwitch() { logger.Debug("init lid switch") sysPower := syspower.NewPower(m.sysBus) + sysPower.InitSignalExt(m.sysSigLoop, true) + + // 初始化电源模式状态 + powerMode, err := sysPower.Mode().Get(0) + if err != nil { + m.PropsMu.Lock() + m.powerSaving = false + m.PropsMu.Unlock() + logger.Warning("failed to get system power mode:", err) + } else { + m.PropsMu.Lock() + m.powerSaving = powerMode == "powersave" + m.PropsMu.Unlock() + logger.Info("Initial power mode:", powerMode, "powerSaving:", m.powerSaving) + } + + // 监听电源模式变化 + err = sysPower.Mode().ConnectChanged(func(hasValue bool, value string) { + if !hasValue { + return + } + + logger.Debug("system power mode changed:", value) + m.setSystemAdjusting(true) + m.scheduleSystemAdjustingClear(500 * time.Millisecond) + m.PropsMu.Lock() + m.powerSaving = value == "powersave" + isPowerSaving := m.powerSaving + m.PropsMu.Unlock() + + if isPowerSaving { + m.holdAutoBrightness() + } else { + m.resumeAutoBrightness() + } + }) + if err != nil { + logger.Warning("failed to connect system power mode change:", err) + } + hasLid, err := sysPower.HasLidSwitch().Get(0) if err != nil { logger.Warningf("failed to get lid switch info: %v", err) @@ -39,7 +80,6 @@ func (m *Manager) initLidSwitch() { m.builtinMonitor.lidClosed = closed } }) - sysPower.InitSignalExt(m.sysSigLoop, true) sysPower.ConnectLidClosed(func() { logger.Warning("lid closed signal") m.holdAutoBrightness() diff --git a/display1/monitor.go b/display1/monitor.go index f18ad21c5..3e77bf4e9 100644 --- a/display1/monitor.go +++ b/display1/monitor.go @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2022 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -546,6 +546,15 @@ func (monitors Monitors) GetByUuid(uuid string) *Monitor { return nil } +func (monitors Monitors) GetByUuidAndName(uuid, name string) *Monitor { + for _, monitor := range monitors { + if monitor.Name == name && monitor.getUuids().Contains(uuid) { + return monitor + } + } + return nil +} + const fillModeKeyDelimiter = ":" func (m *Monitor) generateFillModeKey() string { diff --git a/display1/sensor_proxy.go b/display1/sensor_proxy.go index 74d1330ef..dcb6f6062 100644 --- a/display1/sensor_proxy.go +++ b/display1/sensor_proxy.go @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 - 2026 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later package display1 @@ -10,34 +10,21 @@ import ( "time" "github.com/godbus/dbus/v5" -) - -const ( - // net.hadess.SensorProxy D-Bus 接口常量 - hadessProxyService = "net.hadess.SensorProxy" - hadessProxyObjectPath = "/net/hadess/SensorProxy" - hadessProxyInterface = "net.hadess.SensorProxy" - // 属性名称 - propHasAmbientLight = "HasAmbientLight" - propLightLevel = "LightLevel" - propLightLevelUnit = "LightLevelUnit" - // 方法名称 - methodClaimLight = "ClaimLight" - methodReleaseLight = "ReleaseLight" - // 信号名称 - signalPropertiesChanged = "org.freedesktop.DBus.Properties.PropertiesChanged" + sensorproxy "github.com/linuxdeepin/go-dbus-factory/system/net.hadess.sensorproxy" + ofdbus "github.com/linuxdeepin/go-dbus-factory/system/org.freedesktop.dbus" + "github.com/linuxdeepin/go-lib/dbusutil" + "github.com/linuxdeepin/go-lib/dbusutil/proxy" ) // SensorProxyClient 环境光传感器D-Bus客户端 // 职责:负责从光感服务获取数据并缓存,不负责滤波处理 type SensorProxyClient struct { - conn *dbus.Conn - sensorProxy dbus.BusObject + sensorProxy sensorproxy.SensorProxy + dbusDaemon ofdbus.DBus hasAmbientLight bool claimed bool // 事件处理 - signalChan chan *dbus.Signal onServiceChange func(bool) onLightLevelChange func(int) @@ -46,7 +33,7 @@ type SensorProxyClient struct { // 服务监控 serviceAvailable bool - ownerWatcher chan *dbus.Signal + serviceSigLoop *dbusutil.SignalLoop // 服务监控的 SignalLoop // 错误处理 maxRetries int @@ -58,11 +45,10 @@ type SensorProxyClient struct { } // NewSensorProxyClient 创建新的传感器代理客户端 -func NewSensorProxyClient(conn *dbus.Conn) *SensorProxyClient { +func NewSensorProxyClient(proxy sensorproxy.SensorProxy, dbusDaemon ofdbus.DBus) *SensorProxyClient { return &SensorProxyClient{ - conn: conn, - signalChan: make(chan *dbus.Signal, 10), - ownerWatcher: make(chan *dbus.Signal, 10), + sensorProxy: proxy, + dbusDaemon: dbusDaemon, maxRetries: 3, retryDelay: time.Millisecond * 500, lastLightLevel: -1, @@ -70,14 +56,14 @@ func NewSensorProxyClient(conn *dbus.Conn) *SensorProxyClient { } // Connect 连接到SensorProxy服务 -func (c *SensorProxyClient) Connect() error { +func (c *SensorProxyClient) Connect(sigLoop *dbusutil.SignalLoop) error { c.mutex.Lock() defer c.mutex.Unlock() - if c.conn == nil { - return errors.New("D-Bus connection is nil") + if c.sensorProxy == nil { + return errors.New("SensorProxy is nil") } - // 创建D-Bus对象 - c.sensorProxy = c.conn.Object(hadessProxyService, hadessProxyObjectPath) + // 初始化信号处理 + c.sensorProxy.InitSignalExt(sigLoop, true) // 检查服务是否可用(带重试机制) err := c.checkServiceAvailableWithRetry() if err != nil { @@ -94,9 +80,9 @@ func (c *SensorProxyClient) Connect() error { if !hasLight { return errors.New("no ambient light sensor available") } - // 启动信号监听 + // 订阅属性变化信号(LightLevel 等) c.startSignalWatching() - // 启动服务监控 + // 订阅服务所有者变化信号 c.startServiceWatching() return nil } @@ -111,13 +97,17 @@ func (c *SensorProxyClient) Disconnect() error { if err != nil { logger.Warning("[SensorProxy] Failed to release light sensor:", err) } + c.claimed = false } - // 停止信号监听 - c.stopSignalWatching() - // 停止服务监控 - c.stopServiceWatching() + // 停止服务监控的 SignalLoop + if c.serviceSigLoop != nil { + c.serviceSigLoop.Stop() + c.serviceSigLoop = nil + } + // 移除所有信号处理器 + c.sensorProxy.RemoveHandler(proxy.RemoveAllHandlers) + c.dbusDaemon.RemoveHandler(proxy.RemoveAllHandlers) - c.sensorProxy = nil c.serviceAvailable = false c.hasAmbientLight = false c.lastLightLevel = -1 @@ -188,10 +178,7 @@ func (c *SensorProxyClient) GetCachedLightLevel() (int, error) { c.mutex.Unlock() if needInit { - c.mutex.Lock() - sensorProxy := c.sensorProxy - c.mutex.Unlock() - c.initializeCacheFromPropertyWithProxy(sensorProxy) + c.initializeCacheFromProperty() } c.mutex.Lock() @@ -222,24 +209,13 @@ func (c *SensorProxyClient) GetLightLevel() (int, error) { c.mutex.Unlock() return 0, errors.New("light sensor not claimed") } - - sensorProxy := c.sensorProxy c.mutex.Unlock() - if sensorProxy == nil { - return 0, errors.New("sensor proxy is nil") - } - - variant, err := sensorProxy.GetProperty(hadessProxyInterface + "." + propLightLevel) + lightLevel, err := c.sensorProxy.LightLevel().Get(0) if err != nil { return 0, fmt.Errorf("failed to get LightLevel property: %w", err) } - lightLevel, ok := variant.Value().(float64) - if !ok { - return 0, errors.New("invalid LightLevel property type") - } - return int(lightLevel), nil } @@ -250,10 +226,9 @@ func (c *SensorProxyClient) HasAmbientLight() (bool, error) { c.mutex.Unlock() return false, errors.New("SensorProxy service not available") } - sensorProxy := c.sensorProxy c.mutex.Unlock() - return c.hasAmbientLightInternalWithProxy(sensorProxy) + return c.hasAmbientLightInternal() } // SetServiceChangeCallback 设置服务状态变化回调 @@ -289,7 +264,7 @@ func (c *SensorProxyClient) IsClaimed() bool { func (c *SensorProxyClient) checkServiceAvailableWithRetry() error { var lastErr error for i := 0; i < c.maxRetries; i++ { - err := c.checkServiceAvailable() + _, err := c.hasAmbientLightInternal() if err == nil { return nil } @@ -317,201 +292,99 @@ func (c *SensorProxyClient) claimLightWithRetry() error { return lastErr } -// checkServiceAvailable 检查服务是否可用 -func (c *SensorProxyClient) checkServiceAvailable() error { - // 尝试调用一个简单的属性获取来检查服务是否可用 - _, err := c.sensorProxy.GetProperty(hadessProxyInterface + "." + propHasAmbientLight) - return err -} - -// hasAmbientLightInternalWithProxy 内部检查环境光传感器(不加锁版本) -func (c *SensorProxyClient) hasAmbientLightInternalWithProxy(sensorProxy dbus.BusObject) (bool, error) { - if sensorProxy == nil { - return false, errors.New("sensor proxy is nil") - } - - variant, err := sensorProxy.GetProperty(hadessProxyInterface + "." + propHasAmbientLight) +// hasAmbientLightInternal 内部检查环境光传感器 +func (c *SensorProxyClient) hasAmbientLightInternal() (bool, error) { + hasLight, err := c.sensorProxy.HasAmbientLight().Get(0) if err != nil { return false, err } - hasLight, ok := variant.Value().(bool) - if !ok { - return false, errors.New("invalid HasAmbientLight type") - } return hasLight, nil } -// hasAmbientLightInternal 内部检查环境光传感器 -func (c *SensorProxyClient) hasAmbientLightInternal() (bool, error) { - return c.hasAmbientLightInternalWithProxy(c.sensorProxy) -} - // claimLightInternal 内部声明环境光传感器 func (c *SensorProxyClient) claimLightInternal() error { - call := c.sensorProxy.Call(hadessProxyInterface+"."+methodClaimLight, 0) - return call.Err + return c.sensorProxy.ClaimLight(0) } // releaseLightInternal 内部释放环境光传感器 func (c *SensorProxyClient) releaseLightInternal() error { - call := c.sensorProxy.Call(hadessProxyInterface+"."+methodReleaseLight, 0) - return call.Err + return c.sensorProxy.ReleaseLight(0) } -// startSignalWatching 启动信号监听 +// startSignalWatching 通过 proxy.Object 订阅属性变化信号 func (c *SensorProxyClient) startSignalWatching() { - // 添加属性变化信号监听,只监听特定服务的特定对象路径 - matchRule := "type='signal'," + - "sender='" + hadessProxyService + "'," + - "interface='org.freedesktop.DBus.Properties'," + - "member='PropertiesChanged'," + - "path='" + hadessProxyObjectPath + "'," + - "arg0='" + hadessProxyInterface + "'" - err := c.conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, matchRule).Err - if err != nil { - logger.Warning("[SensorProxy] Failed to add PropertiesChanged signal match:", err) - return - } - // 启动信号处理协程 - go c.handleSignals() -} - -// stopSignalWatching 停止信号监听 -func (c *SensorProxyClient) stopSignalWatching() { - // 移除信号监听 - matchRule := "type='signal'," + - "sender='" + hadessProxyService + "'," + - "interface='org.freedesktop.DBus.Properties'," + - "member='PropertiesChanged'," + - "path='" + hadessProxyObjectPath + "'," + - "arg0='" + hadessProxyInterface + "'" - err := c.conn.BusObject().Call("org.freedesktop.DBus.RemoveMatch", 0, matchRule).Err - if err != nil { - logger.Warning("[SensorProxy] Failed to remove PropertiesChanged signal match:", err) - } -} - -// startServiceWatching 启动服务监控 -func (c *SensorProxyClient) startServiceWatching() { - // 监听特定服务的所有者变化 - matchRule := "type='signal'," + - "sender='org.freedesktop.DBus'," + - "interface='org.freedesktop.DBus'," + - "member='NameOwnerChanged'," + - "path='/org/freedesktop/DBus'," + - "arg0='" + hadessProxyService + "'" - err := c.conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, matchRule).Err - if err != nil { - logger.Warning("[SensorProxy] Failed to add NameOwnerChanged signal match:", err) - return - } - // 启动服务监控协程 - go c.handleServiceChanges() -} - -// stopServiceWatching 停止服务监控 -func (c *SensorProxyClient) stopServiceWatching() { - // 移除服务监听 - matchRule := "type='signal'," + - "sender='org.freedesktop.DBus'," + - "interface='org.freedesktop.DBus'," + - "member='NameOwnerChanged'," + - "path='/org/freedesktop/DBus'," + - "arg0='" + hadessProxyService + "'" - err := c.conn.BusObject().Call("org.freedesktop.DBus.RemoveMatch", 0, matchRule).Err - if err != nil { - logger.Warning("[SensorProxy] Failed to remove NameOwnerChanged signal match:", err) - } -} - -// handleSignals 处理D-Bus信号 -func (c *SensorProxyClient) handleSignals() { - c.conn.Signal(c.signalChan) - for signal := range c.signalChan { - if signal.Name == "org.freedesktop.DBus.Properties.PropertiesChanged" { - c.handlePropertiesChanged(signal) - } - } -} - -// handleServiceChanges 处理服务状态变化 -func (c *SensorProxyClient) handleServiceChanges() { - c.conn.Signal(c.ownerWatcher) - for signal := range c.ownerWatcher { - if signal.Name == "org.freedesktop.DBus.NameOwnerChanged" { - c.handleNameOwnerChanged(signal) - } - } -} - -// handlePropertiesChanged 处理属性变化信号 -func (c *SensorProxyClient) handlePropertiesChanged(signal *dbus.Signal) { - if len(signal.Body) < 2 { - logger.Warning("[SensorProxy] Invalid PropertiesChanged signal body") - return - } - - changedProps, ok := signal.Body[1].(map[string]dbus.Variant) - if !ok { - logger.Warning("[SensorProxy] Invalid PropertiesChanged signal format") - return - } - - if lightLevelVariant, exists := changedProps[propLightLevel]; exists { - if lightLevel, ok := lightLevelVariant.Value().(float64); ok { + _, err := c.sensorProxy.ConnectPropertiesChanged( + func(interfaceName string, changedProperties map[string]dbus.Variant, + invalidatedProperties []string) { + lightLevelVariant, exists := changedProperties["LightLevel"] + if !exists { + return + } + lightLevel, ok := lightLevelVariant.Value().(float64) + if !ok { + logger.Warning("[SensorProxy] Failed to convert LightLevel value to float") + return + } c.mutex.Lock() claimed := c.claimed c.mutex.Unlock() - if claimed { c.lightValueFilter(int(lightLevel)) } - } else { - logger.Warning("[SensorProxy] Failed to convert LightLevel value to float") - } + }) + if err != nil { + logger.Warning("[SensorProxy] Failed to connect PropertiesChanged signal:", err) } } -// handleNameOwnerChanged 处理服务所有者变化 -func (c *SensorProxyClient) handleNameOwnerChanged(signal *dbus.Signal) { - if len(signal.Body) < 3 { - logger.Warning("[SensorProxy] Invalid NameOwnerChanged signal body") - return - } - serviceName, ok := signal.Body[0].(string) - if !ok || serviceName != hadessProxyService { - return +// startServiceWatching 通过 ofdbus.DBus 订阅服务所有者变化信号 +func (c *SensorProxyClient) startServiceWatching() { + // 如果已有 sigLoop,先停止它 + if c.serviceSigLoop != nil { + c.serviceSigLoop.Stop() + c.serviceSigLoop = nil } - newOwner, ok := signal.Body[2].(string) - if !ok { + + systemBus, err := dbus.SystemBus() + if err != nil { + logger.Warning("[SensorProxy] Failed to connect to system bus for service watching:", err) return } - // 服务状态变化 - serviceAvailable := newOwner != "" - c.mutex.Lock() - oldAvailable := c.serviceAvailable - c.serviceAvailable = serviceAvailable - if !serviceAvailable { - // 服务不可用,重置状态 - c.claimed = false - c.hasAmbientLight = false - } else if !oldAvailable { - // 服务重新可用,重新检查传感器 - go func() { - time.Sleep(100 * time.Millisecond) // 等待服务完全启动 - hasLight, err := c.HasAmbientLight() - if err == nil { - c.mutex.Lock() - c.hasAmbientLight = hasLight - c.mutex.Unlock() + sigLoop := dbusutil.NewSignalLoop(systemBus, 10) + sigLoop.Start() + c.serviceSigLoop = sigLoop + c.dbusDaemon.InitSignalExt(sigLoop, true) + _, err = c.dbusDaemon.ConnectNameOwnerChanged( + func(name, oldOwner, newOwner string) { + if name != "net.hadess.SensorProxy" { + return } - }() - } - callback := c.onServiceChange - c.mutex.Unlock() - // 调用服务状态变化回调 - if callback != nil { - go callback(serviceAvailable) + serviceAvailable := newOwner != "" + c.mutex.Lock() + oldAvailable := c.serviceAvailable + c.serviceAvailable = serviceAvailable + if !serviceAvailable { + c.claimed = false + c.hasAmbientLight = false + } else if !oldAvailable { + go func() { + time.Sleep(100 * time.Millisecond) + hasLight, err := c.HasAmbientLight() + if err == nil { + c.mutex.Lock() + c.hasAmbientLight = hasLight + c.mutex.Unlock() + } + }() + } + callback := c.onServiceChange + c.mutex.Unlock() + if callback != nil { + go callback(serviceAvailable) + } + }) + if err != nil { + logger.Warning("[SensorProxy] Failed to connect NameOwnerChanged signal:", err) } } func (c *SensorProxyClient) lightValueFilter(newValue int) { @@ -530,24 +403,14 @@ func (c *SensorProxyClient) lightValueFilter(newValue int) { } } -// initializeCacheFromPropertyWithProxy 从 D-Bus 属性读取当前光照值并缓存(不加锁版本) -func (c *SensorProxyClient) initializeCacheFromPropertyWithProxy(sensorProxy dbus.BusObject) { - if sensorProxy == nil { - return - } - - variant, err := sensorProxy.GetProperty(hadessProxyInterface + "." + propLightLevel) +// initializeCacheFromProperty 从 D-Bus 属性读取当前光照值并缓存 +func (c *SensorProxyClient) initializeCacheFromProperty() { + lightLevel, err := c.sensorProxy.LightLevel().Get(0) if err != nil { logger.Warning("[AutoBrightness::LightSensor] Failed to get LightLevel property:", err) return } - lightLevel, ok := variant.Value().(float64) - if !ok { - logger.Warning("[AutoBrightness::LightSensor] Invalid LightLevel property type") - return - } - if lightLevel <= 0 { logger.Debug("[AutoBrightness::LightSensor] LightLevel property is zero or negative, skipping cache") return diff --git a/docs/auto_backlight/auto_brightness_design.md b/docs/auto_backlight/auto_brightness_design.md index 78e5176ac..b0fef9632 100644 --- a/docs/auto_backlight/auto_brightness_design.md +++ b/docs/auto_backlight/auto_brightness_design.md @@ -8,8 +8,7 @@ - 2.1 AutoBrightnessManager - 2.2 SensorProxyClient - 2.3 BrightnessTransition - - 2.4 CurveManager - - 2.5 配置管理 + - 2.4 配置管理 3. [状态机](#3-状态机) - 3.1 自动亮度状态转换 4. [数据结构](#4-数据结构) @@ -23,14 +22,14 @@ - 5.4 停止流程 6. [关键算法](#6-关键算法) - 6.1 亮度计算算法 - - 6.2 卡尔曼滤波器 - - 6.3 调节判断逻辑 - - 6.4 渐变算法 - - 6.5 补偿机制 + - 6.2 调节判断逻辑 + - 6.3 渐变算法 + - 6.4 完整数据流图 7. [亮度渐变机制](#7-亮度渐变机制) - 7.1 渐变流程 - 7.2 渐变控制 - 7.3 渐变优化 + - 7.4 自动亮度与渐变集成 8. [手动调节处理](#8-手动调节处理) - 8.1 两种模式 - 8.2 手动调节处理时序 @@ -54,7 +53,19 @@ - 13.1 外部依赖 - 13.2 内部依赖 14. [测试要点](#14-测试要点) -15. [注意事项](#15-注意事项) + - 14.1 功能测试 + - 14.2 异常测试 + - 14.3 性能测试 +15. [未来扩展](#15-未来扩展) + - 15.1 算法优化 + - 15.2 功能增强 + - 15.3 性能优化 +16. [典型使用场景](#16-典型使用场景) + - 16.1 场景一:用户启用自动亮度 + - 16.2 场景二:环境光变化触发调节 + - 16.3 场景三:手动调节后的行为 + - 16.4 场景四:系统休眠与唤醒 +17. [注意事项](#17-注意事项) --- @@ -75,13 +86,11 @@ graph TB Manager[Display Manager] ABM[AutoBrightnessManager] BT[BrightnessTransition] - CM[CurveManager] - KF[AdaptiveKalmanFilter] Backlight[Backlight 控制] end subgraph "配置层" - DConf[DSettings/DConfig] + DConf[DSettings/DConf] end subgraph "系统服务" @@ -102,13 +111,10 @@ graph TB ABM -->|读取配置| DConf ABM -->|保存配置| DConf BT -->|读取配置| DConf - CM -->|读取曲线配置| DConf ABM -->|DBus 调用| SensorProxy SensorProxy -->|读取| Hardware - ABM -->|滤波处理| KF - ABM -->|查询曲线| CM ABM -->|调用| BT BT -->|设置亮度| Backlight ABM -->|设置亮度| Backlight @@ -118,8 +124,6 @@ graph TB style ABM fill:#e1f5ff style BT fill:#e1f5ff - style KF fill:#fff4e1 - style CM fill:#fff4e1 style Manager fill:#fff4e1 ``` @@ -132,10 +136,9 @@ graph TB **主要职责:** - 初始化和资源管理 - 配置加载和持久化 -- 传感器数据采集和滤波处理 +- 传感器数据采集和处理 - 亮度计算和应用 - 状态监控和异常处理 -- 补偿机制管理 ### 2.2 SensorProxyClient @@ -144,9 +147,8 @@ graph TB **主要功能:** - 连接/断开传感器服务 - 声明/释放环境光传感器 -- 读取光照强度数据(原始值缓存) +- 读取光照强度数据 - 监听服务状态变化 -- 监听光照值变化(推送模式) ### 2.3 BrightnessTransition @@ -157,19 +159,9 @@ graph TB - 渐变参数配置(时长、步进间隔) - 多显示器独立渐变状态管理 - 渐变过程的启动、停止和中断处理 +- 实时亮度值跟踪 -### 2.4 CurveManager - -曲线管理器,管理亮度曲线的配置和计算。 - -**主要功能:** -- FLM 机型定制曲线 -- 默认亮度曲线 -- 自定义亮度曲线(按 EDID 匹配) -- 自动亮度曲线(光照-亮度映射) -- 最大亮度限制 - -### 2.5 配置管理 +### 2.4 配置管理 基于 DSettings (DConfig) 的配置系统,支持动态配置更新。 @@ -216,10 +208,20 @@ stateDiagram-v2 note right of 运行中 - 轮询传感器 - - 卡尔曼滤波 - 计算亮度 - 应用调节 - - 补偿机制 + end note + + note right of 手动暂停 + - 释放传感器 + - 停止调节 + - 计时等待 + end note + + note right of 休眠暂停 + - 停止轮询 + - 保持状态 + - 等待唤醒 end note ``` @@ -232,44 +234,22 @@ type AutoBrightnessConfig struct { Enabled bool // 是否启用 Sensitivity float64 // 敏感度 (0.1-3.0) PollingInterval int // 轮询间隔(秒) (1-60) - ChangeThreshold float64 // 环境光变化阈值 (1.0-50.0) - BrightnessChangeThreshold float64 // 亮度变化阈值 (0.01-1.0) + ChangeThreshold float64 // 变化阈值 (1.0-50.0) ManualOverrideDuration int // 手动调节暂停时间(秒) (60-1800) ManualAdjustDisablesAutoMode bool // 手动调节是否禁用自动模式 UseTransition bool // 是否使用渐变效果 - KalmanProcessNoise float64 // 卡尔曼滤波器过程噪声协方差 Q - KalmanMeasurementNoise float64 // 卡尔曼滤波器测量噪声协方差 R - KalmanWindowSize int // 卡尔曼滤波器窗口大小 -} -``` - -**默认值:** -```go -DefaultAutoBrightnessConfig = AutoBrightnessConfig{ - Enabled: false, - Sensitivity: 0.5, - PollingInterval: 5, - ChangeThreshold: 20.0, - BrightnessChangeThreshold: 0.01, - ManualOverrideDuration: 300, - ManualAdjustDisablesAutoMode: true, - UseTransition: true, - KalmanProcessNoise: 0.8, - KalmanMeasurementNoise: 0.05, - KalmanWindowSize: 3, } ``` ### 4.2 AutoBrightnessManager 状态字段 - **依赖注入:** manager (复用 display.Manager) -- **独立组件:** sensorClient, configManager, kalmanFilter +- **独立组件:** sensorClient, configManager - **配置状态:** config, enabled, supported - **运行状态:** running, polling, systemAdjusting - **历史数据:** lastLightLevel, lastBrightness, lastAdjustTime - **手动控制:** manualOverride (时间戳) - **轮询控制:** ticker, stopChan, pollingWg -- **补偿机制:** lastSensorDataTime, compensationTicker, compensationStopCh, compensationWg ### 4.3 BrightnessTransition 数据结构 @@ -288,6 +268,9 @@ type transitionState struct { } ``` +**多显示器支持:** +- `states map[string]*transitionState`: 每个显示器独立的渐变状态 + ## 5. 核心流程 ### 5.1 初始化流程 @@ -306,9 +289,8 @@ flowchart TD J -->|失败| K[使用默认配置] J -->|成功| L[设置服务状态回调] K --> L - L --> M[初始化卡尔曼滤波器] - M --> N[标记为已支持] - N --> O[初始化完成] + L --> M[标记为已支持] + M --> N[初始化完成] ``` ### 5.2 启动流程 @@ -319,7 +301,6 @@ sequenceDiagram participant ABM as AutoBrightnessManager participant Sensor as SensorProxyClient participant Poller as 轮询器 - participant Comp as 补偿定时器 User->>ABM: Start() ABM->>ABM: 检查支持状态 @@ -333,14 +314,23 @@ sequenceDiagram ABM->>Sensor: Connect() Sensor-->>ABM: 连接成功 ABM->>Sensor: ClaimLight() (带重试) + loop 最多3次 + Sensor->>Sensor: 尝试声明传感器 + alt 成功 + Sensor-->>ABM: 声明成功 + else 失败且未达重试上限 + Sensor->>Sensor: 等待2秒 + end + end alt 声明失败 ABM->>Sensor: Disconnect() ABM-->>User: 返回错误 else 声明成功 - ABM->>ABM: 更新运行状态 - ABM->>ABM: 记录 lastSensorDataTime ABM->>Poller: startPolling() - ABM->>Comp: startCompensationTimer() + Poller->>Poller: 创建 ticker + Poller->>Poller: 启动 goroutine + Poller->>Poller: 立即执行一次采集 + ABM->>ABM: 更新运行状态 ABM-->>User: 启动成功 end end @@ -359,33 +349,32 @@ flowchart TD E -->|未暂停| F{传感器已声明?} F -->|否| G[重新声明传感器] G -->|失败| D - G -->|成功| H[获取缓存光照强度] + G -->|成功| H[获取光照强度] F -->|是| H H -->|失败| D H -->|成功| I[processLightChange] - I --> J[卡尔曼滤波处理] - J --> K[计算目标亮度] - K --> L{shouldAdjustBrightness} - - L --> M{手动调节暂停?} - M -->|是| N[不调节] - M -->|否| O{环境光变化 >= 阈值?} - O -->|否| N - O -->|是| P{距上次调节 >= 间隔?} - P -->|否| N - P -->|是| Q{亮度变化 >= 阈值?} - Q -->|否| N - Q -->|是| R[应用亮度] - - R --> S{使用渐变?} - S -->|是| T[BrightnessTransition.SetBrightnessForced] - S -->|否| U[setBrightnessRaw] - T --> V[更新历史状态] - U --> V - V --> W[记录光照/亮度/时间] - W --> D - N --> D + I --> J[计算目标亮度] + J --> K{shouldAdjustBrightness} + + K --> L{手动调节暂停?} + L -->|是| M[不调节] + L -->|否| N{环境光变化 >= 阈值?} + N -->|否| M + N -->|是| O{距上次调节 >= 间隔?} + O -->|否| M + O -->|是| P{亮度变化 >= 5%?} + P -->|否| M + P -->|是| Q[应用亮度] + + Q --> R{使用渐变?} + R -->|是| S[BrightnessTransition.SetBrightnessForced] + R -->|否| T[setBrightnessRaw] + S --> U[更新历史状态] + T --> U + U --> V[记录光照/亮度/时间] + V --> D + M --> D ``` ### 5.4 停止流程 @@ -395,7 +384,6 @@ sequenceDiagram participant User as 用户/系统 participant ABM as AutoBrightnessManager participant Poller as 轮询器 - participant Comp as 补偿定时器 participant Sensor as SensorProxyClient participant Display as 显示器 @@ -410,9 +398,6 @@ sequenceDiagram Poller->>Poller: 等待 goroutine 退出 Poller-->>ABM: 停止完成 - ABM->>Comp: stopCompensationTimer() - Comp-->>ABM: 停止完成 - ABM->>Display: restoreSavedBrightness() Display->>Display: 获取保存的亮度 Display->>Display: 应用亮度 @@ -433,74 +418,25 @@ sequenceDiagram ### 6.1 亮度计算算法 -**优先级:** -1. **曲线配置**:如果配置了 `lux-brightness-curve`,使用曲线映射 -2. **线性映射**:使用敏感度参数进行线性计算 - -**曲线映射:** -```go -// 配置格式 -type AutoBrightnessCurvePoint struct { - Lux int // 光感值 - Br float64 // 亮度百分比 (0-100) -} - -// 线性插值计算 -func interpolate(lux int, points []AutoBrightnessCurvePoint) float64 { - // 找到 lux 所在的区间,进行线性插值 -} -``` - -**线性映射(默认):** ``` -目标亮度 = min(max((光照强度 × 敏感度) / 1024.0, 0.1), 1.0) +目标亮度 = min(max((光照强度 × 敏感度) / 255, 0.1), 1.0) ``` **说明:** -- 环境光范围:0-1024 lux +- 线性映射:光照强度 0-255 lux → 亮度 0.0-1.0 - 敏感度调整:支持 0.1-3.0 倍率 - 最小亮度保护:不低于 10%,避免屏幕过暗 -### 6.2 卡尔曼滤波器 - -自适应卡尔曼滤波器用于平滑传感器数据,减少噪声影响。 - -**核心结构:** -```go -type AdaptiveKalmanFilter struct { - *KalmanFilter1D - window []float64 // 测量值窗口 - windowSize int // 窗口大小 - measurementVariance float64 // 测量方差 -} -``` - -**工作原理:** -1. 维护一个滑动窗口存储最近的测量值 -2. 计算窗口内测量值的方差 -3. 根据方差自适应调整测量噪声参数 R -4. 方差越大,R 越大,越信任估计值而非测量值 - -**参数说明:** -- **Q (过程噪声)**: 系统模型的不确定性,默认 0.8 -- **R (测量噪声)**: 传感器噪声,默认 0.05,会自适应调整 -- **窗口大小**: 用于计算方差,默认 3 - -**数据流:** -``` -原始光照值 -> 卡尔曼滤波器 -> 滤波后光照值 -> 亮度计算 -``` - -### 6.3 调节判断逻辑 +### 6.2 调节判断逻辑 满足以下所有条件才执行调节: 1. **不在手动调节暂停期** 2. **环境光变化超过阈值**:`|当前光照 - 上次光照| >= ChangeThreshold` 3. **距上次调节时间足够**:`当前时间 - 上次调节时间 >= PollingInterval` -4. **亮度变化足够大**:`|目标亮度 - 当前亮度| >= BrightnessChangeThreshold` +4. **亮度变化足够大**:`|目标亮度 - 当前亮度| >= 5%` -### 6.4 渐变算法 +### 6.3 渐变算法 **基本原理:** 将亮度变化分解为多个小步进,在一定时间内逐步完成。 @@ -512,22 +448,67 @@ type AdaptiveKalmanFilter struct { 每步变化量 = 亮度差值 / 步进次数 ``` -### 6.5 补偿机制 +**示例:** +- 配置时长:4 秒(0-100% 的时间) +- 步进间隔:100 毫秒 +- 亮度变化:30% → 80%(差值 50%) +- 实际时长:4 × 0.5 = 2 秒 +- 步进次数:2000ms / 100ms = 20 步 +- 每步变化:0.5 / 20 = 0.025 (2.5%) -当传感器数据超时(1秒未更新)时,主动补偿以确保滤波器持续工作。 +**优化策略:** +- 最小渐变时长:2 × 步进间隔(避免过短渐变) +- 变化太小时直接设置(< 0.1%) +- 支持中途停止和新渐变覆盖 -**补偿条件:** -1. `lastSensorDataTime` 超过 1 秒 -2. 滤波器输出值与当前传感器值差异超过阈值(5.0) +### 6.4 完整数据流图 -**补偿流程:** -```go -func compensationTick() { - if needCompensation() { - // 主动读取传感器值并处理 - processLightChange(sensorValue) - } -} +```mermaid +flowchart TB + Start([定时器触发]) --> A[读取环境光传感器] + A -->|光照强度 lux| B[应用敏感度系数] + B -->|调整后光照| C[线性映射到 0.0-1.0] + C -->|原始亮度| D[应用最小亮度保护] + D -->|目标亮度| E{检查调节条件} + + E -->|手动暂停中| End1([跳过]) + E -->|光照变化 < 阈值| End1 + E -->|时间间隔不足| End1 + E -->|亮度变化 < 5%| End1 + E -->|所有条件满足| F{使用渐变?} + + F -->|否| G[直接设置亮度] + F -->|是| H[计算渐变参数] + + H --> I[实际时长 = 配置时长 × 差值] + I --> J[步进次数 = 时长 / 间隔] + J --> K{时长 >= 最小值?} + + K -->|否| G + K -->|是| L[启动渐变 goroutine] + + L --> M[循环步进] + M --> N[更新实时亮度] + N --> O[调用硬件接口] + O --> P{到达目标?} + + P -->|否| Q[等待步进间隔] + Q --> M + P -->|是| R[同步 DBus 属性] + + G --> S[调用硬件接口] + S --> R + + R --> T[更新历史状态] + T --> End2([完成]) + + style A fill:#e1f5ff + style B fill:#e1f5ff + style C fill:#e1f5ff + style D fill:#e1f5ff + style H fill:#fff4e1 + style L fill:#fff4e1 + style M fill:#fff4e1 ``` ## 7. 亮度渐变机制 @@ -554,16 +535,81 @@ flowchart TD K --> L[计算渐变参数] L --> M[实际时长 = 配置时长 × |差值|] M --> N[步进次数 = 实际时长 / 步进间隔] + N --> O[每步变化 = 差值 / 步进次数] + + O --> P{实际时长 < 最小时长?} + P -->|是| C + P -->|否| Q[标记渐变开始] - N --> O{实际时长 < 最小时长?} - O -->|是| C - O -->|否| P[启动渐变 goroutine] + Q --> R[启动 goroutine] + R --> S[返回不等待] - P --> Q[返回不等待] + R --> T[渐变循环] + T --> U{收到停止信号?} + U -->|是| V[更新实时值] + U -->|否| W[计算下一个亮度] + + W --> X[更新实时值] + X --> Y[setBrightnessRaw] + Y --> Z{到达目标值?} + Z -->|是| AA[同步 DBus 属性] + Z -->|否| AB{最后一步?} + + AB -->|否| AC[等待步进间隔或停止信号] + AC --> U + AB -->|是| AA + + V --> AA + AA --> AD[标记渐变结束] + AD --> AE[goroutine 退出] ``` ### 7.2 渐变控制 +```mermaid +sequenceDiagram + participant Caller as 调用者 + participant BT as BrightnessTransition + participant State as transitionState + participant Worker as 渐变 Goroutine + participant HW as 硬件 + + Note over Caller,HW: 场景1: 启动新渐变 + Caller->>BT: SetBrightness(monitor, 0.8) + BT->>BT: 获取当前亮度 0.3 + BT->>State: 检查是否正在渐变 + State-->>BT: running = false + BT->>BT: 计算参数 (差值0.5, 20步) + BT->>State: 标记 running = true + BT->>State: wg.Add(1) + BT->>Worker: 启动 goroutine + BT-->>Caller: 立即返回 + + loop 20次步进 + Worker->>State: 更新 currentValue + Worker->>HW: setBrightnessRaw + Worker->>Worker: 等待100ms + end + Worker->>HW: 同步 DBus 属性 + Worker->>State: 标记 running = false + Worker->>State: wg.Done() + + Note over Caller,HW: 场景2: 中断现有渐变 + Caller->>BT: SetBrightness(monitor, 0.5) + BT->>State: 检查是否正在渐变 + State-->>BT: running = true, currentValue = 0.6 + BT->>State: 发送停止信号 + State->>Worker: stopCh <- signal + Worker->>Worker: 收到停止信号 + Worker->>State: 更新最终 currentValue + Worker->>HW: 同步 DBus 属性 + Worker->>State: wg.Done() + BT->>State: wg.Wait() 等待退出 + BT->>BT: 使用 currentValue 0.6 作为起点 + BT->>Worker: 启动新渐变 (0.6 -> 0.5) + BT-->>Caller: 立即返回 +``` + **启动渐变:** - 标记 `running = true` - 增加 WaitGroup 计数 @@ -591,16 +637,68 @@ flowchart TD - 渐变时长按比例缩放 - 最小渐变时长保护(200ms) +**资源管理:** +- 使用 WaitGroup 确保 goroutine 正确退出 +- 停止信号使用缓冲通道避免阻塞 +- 清理残留信号防止误触发 + +### 7.4 自动亮度与渐变集成 + +**两种调用方式:** + +1. **SetBrightness(常规)** + - 检查全局 `enabled` 标志 + - 未启用时直接设置亮度 + +2. **SetBrightnessForced(强制)** + - 忽略全局 `enabled` 标志 + - 自动亮度专用,确保渐变生效 + +**配置独立性:** +- 自动亮度有独立的 `UseTransition` 配置 +- 可以在全局渐变关闭时仍使用渐变 +- 通过 `SetBrightnessForced` 实现 + +**属性同步:** +- 渐变过程中不同步属性(减少信号) +- 只在渐变完成或停止时同步一次 +- 失败时立即同步确保一致性 + ## 8. 手动调节处理 ### 8.1 两种模式 -**模式一:临时暂停(默认,ManualAdjustDisablesAutoMode = false)** +```mermaid +stateDiagram-v2 + [*] --> 自动调节运行中 + + 自动调节运行中 --> 检查配置: 用户手动调节亮度 + + 检查配置 --> 临时暂停模式: ManualAdjustDisablesAutoMode = false + 检查配置 --> 完全禁用模式: ManualAdjustDisablesAutoMode = true + + 临时暂停模式 --> 记录暂停时间 + 记录暂停时间 --> 释放传感器 + 释放传感器 --> 暂停状态 + + 暂停状态 --> 检查超时: 每次轮询检查 + 检查超时 --> 暂停状态: 未超时 + 检查超时 --> 重新声明传感器: 超时 + 重新声明传感器 --> 自动调节运行中 + + 完全禁用模式 --> 更新配置Enabled=false + 更新配置Enabled=false --> 停止功能 + 停止功能 --> 已禁用状态 + + 已禁用状态 --> 自动调节运行中: 用户手动启用 +``` + +**模式一:临时暂停(默认)** - 手动调节后暂停自动调节指定时间 - 暂停期间释放传感器资源 - 超时后自动恢复 -**模式二:完全禁用(ManualAdjustDisablesAutoMode = true)** +**模式二:完全禁用** - 手动调节后永久禁用自动亮度 - 更新配置并停止功能 - 需用户手动重新启用 @@ -615,6 +713,7 @@ sequenceDiagram participant Sensor as SensorProxyClient participant Config as 配置系统 + Note over User,Config: 场景1: 临时暂停模式 User->>Manager: 手动调节亮度 Manager->>Manager: setBrightness() Manager->>ABM: OnManualBrightnessChange() @@ -626,27 +725,59 @@ sequenceDiagram alt 临时暂停模式 ABM->>ABM: 记录 manualOverride 时间 ABM->>Sensor: ReleaseLight() - Note over ABM: 暂停指定时间 + Note over ABM: 暂停 300 秒 + ABM->>ABM: 轮询时检查超时 ABM->>Sensor: ClaimLight() (超时后) ABM->>ABM: 恢复自动调节 - else 完全禁用模式 - ABM->>Config: 保存 Enabled = false - ABM->>ABM: Stop() - ABM->>Manager: setPropAutoBrightnessEnabled(false) end end + + Note over User,Config: 场景2: 完全禁用模式 + User->>Manager: 手动调节亮度 + Manager->>Manager: setBrightness() + Manager->>ABM: OnManualBrightnessChange() + ABM->>ABM: 检查配置模式 + alt 完全禁用模式 + ABM->>Config: 保存 Enabled = false + ABM->>ABM: Stop() + ABM->>Manager: setPropAutoBrightnessEnabled(false) + end ``` ### 8.3 系统调整标志 通过 `systemAdjusting` 标志区分系统自动调整(如节能模式)和用户手动调整,避免误判。 +```mermaid +sequenceDiagram + participant Power as 电源管理 + participant Manager as Display Manager + participant ABM as AutoBrightnessManager + + Note over Power,ABM: 系统调整场景(节能模式) + Power->>Manager: 节能模式降低亮度 + Manager->>ABM: setSystemAdjusting(true) + Manager->>Manager: setBrightness(0.3) + Manager->>ABM: OnManualBrightnessChange() + ABM->>ABM: 检查 systemAdjusting = true + ABM->>ABM: 忽略此次调用 + Manager->>ABM: setSystemAdjusting(false) + + Note over Power,ABM: 用户手动调整场景 + Power->>Manager: 用户拖动亮度滑块 + Manager->>Manager: setBrightness(0.8) + Manager->>ABM: OnManualBrightnessChange() + ABM->>ABM: 检查 systemAdjusting = false + ABM->>ABM: 执行暂停或禁用逻辑 +``` + ## 9. 配置管理 ### 9.1 DSettings 集成 -- **AppID:** `org.deepin.dde-daemon` -- **配置名:** `org.deepin.Display.AutoBrightness` +- **AppID:** `org.deepin.startdde` +- **配置名:** `org.deepin.startdde.display` +- **配置键前缀:** `autobrightness-*` ### 9.2 配置项 @@ -655,17 +786,12 @@ sequenceDiagram | 配置键 | 类型 | 默认值 | 说明 | |--------|------|--------|------| | enabled | bool | false | 是否启用 | -| sensitivity | float64 | 0.5 | 敏感度 (0.1-3.0) | -| polling-interval | int | 5 | 轮询间隔(秒) | -| change-threshold | float64 | 20.0 | 环境光变化阈值 | -| brightness-change-threshold | float64 | 0.01 | 亮度变化阈值 | +| sensitivity | float64 | 0.5 | 敏感度 | +| polling-interval | int | 3 | 轮询间隔(秒) | +| change-threshold | float64 | 20.0 | 变化阈值 | | manual-override-duration | int | 300 | 手动暂停时间(秒) | | manual-adjust-disables-auto-mode | bool | true | 手动调节是否禁用 | | use-transition | bool | true | 是否使用渐变 | -| lux-brightness-curve | array | [] | 光照-亮度曲线 | -| kalman-process-noise | float64 | 0.8 | 卡尔曼过程噪声 Q | -| kalman-measurement-noise | float64 | 0.05 | 卡尔曼测量噪声 R | -| kalman-window-size | int | 3 | 卡尔曼窗口大小 | **渐变效果配置:** @@ -679,13 +805,54 @@ sequenceDiagram 监听配置文件变化,自动重新加载并应用新配置。敏感度变化时立即触发一次亮度调整。 +```mermaid +sequenceDiagram + participant User as 用户/控制中心 + participant DConf as DSettings/DConf + participant ABM as AutoBrightnessManager + participant BT as BrightnessTransition + participant Poller as 轮询器 + + Note over User,Poller: 自动亮度配置变更 + User->>DConf: 修改配置 (如 sensitivity) + DConf->>ABM: ValueChanged 信号 + ABM->>DConf: 读取新配置 + DConf-->>ABM: 返回配置值 + ABM->>ABM: OnConfigChanged() + + alt 启用状态变化 + ABM->>ABM: Start() 或 Stop() + else 轮询参数变化 + ABM->>Poller: stopPolling() + ABM->>Poller: startPolling() + else 敏感度变化 + ABM->>ABM: adjustBrightnessOnce() + Note over ABM: 使用新敏感度立即调整 + end + + Note over User,Poller: 渐变配置变更 + User->>DConf: 修改渐变配置 + DConf->>BT: ValueChanged 信号 + BT->>DConf: 读取新配置 + DConf-->>BT: 返回配置值 + + alt transition-enabled 变化 + BT->>BT: SetEnabled(newValue) + else transition-duration 变化 + BT->>BT: SetDuration(newValue) + else transition-step-interval 变化 + BT->>BT: SetStepInterval(newValue) + end + + Note over BT: 新配置立即生效
下次调节时使用 +``` + ## 10. 异常处理 ### 10.1 重试机制 - 传感器声明失败:最多重试 3 次,间隔 2 秒 - 亮度设置失败:下次轮询自动重试 -- 服务连接失败:带重试机制检查 ### 10.2 优雅降级 @@ -697,6 +864,41 @@ sequenceDiagram 监听 iio-sensor-proxy 服务状态,服务恢复后自动重新初始化。 +```mermaid +sequenceDiagram + participant Sensor as iio-sensor-proxy + participant Client as SensorProxyClient + participant ABM as AutoBrightnessManager + participant Manager as Display Manager + + Note over Sensor,Manager: 服务异常场景 + Sensor->>Sensor: 服务崩溃/重启 + Sensor->>Client: NameOwnerChanged 信号 + Client->>Client: 检测到服务不可用 + Client->>ABM: onServiceChange(false) + ABM->>ABM: stopPolling() + ABM->>ABM: 标记 supported = false + ABM->>Manager: setPropAutoBrightnessSupported(false) + + Note over Sensor,Manager: 服务恢复场景 + Sensor->>Sensor: 服务恢复 + Sensor->>Client: NameOwnerChanged 信号 + Client->>Client: 检测到服务可用 + Client->>ABM: onServiceChange(true) + ABM->>Client: HasAmbientLight() + Client-->>ABM: true + ABM->>ABM: 标记 supported = true + ABM->>Manager: setPropAutoBrightnessSupported(true) + + alt 配置已启用 + ABM->>ABM: Start() + ABM->>Client: Connect() + ABM->>Client: ClaimLight() + ABM->>ABM: startPolling() + Note over ABM: 自动恢复功能 + end +``` + ## 11. 并发控制 ### 11.1 锁策略 @@ -704,14 +906,84 @@ sequenceDiagram - 使用 `sync.RWMutex` 保护共享状态 - 读多写少场景使用读锁 - 避免在持有锁时执行耗时操作 -- 停止轮询时临时释放锁等待 goroutine 退出 + +```mermaid +graph TD + A[公共方法调用] --> B{需要修改状态?} + B -->|是| C[获取写锁 Lock] + B -->|否| D[获取读锁 RLock] + + C --> E{需要耗时操作?} + E -->|是| F[释放锁] + E -->|否| G[执行操作] + + F --> H[执行耗时操作
如 Start/Stop] + H --> I[必要时重新获取锁] + + G --> J[释放写锁 Unlock] + D --> K[读取状态] + K --> L[释放读锁 RUnlock] + + I --> J + J --> M[返回] + L --> M +``` ### 11.2 Goroutine 管理 -- **轮询 goroutine**: 通过 `stopChan` 和 `WaitGroup` 安全退出 -- **补偿 goroutine**: 通过 `compensationStopCh` 和 `compensationWg` 安全退出 -- **渐变 goroutine**: 每个显示器独立管理 -- **幂等停止**: 停止方法可安全多次调用 +- 轮询 goroutine:通过 `stopChan` 和 `WaitGroup` 安全退出 +- 配置更新回调:异步执行,避免阻塞 +- 幂等停止:`stopPolling()` 可安全多次调用 + +```mermaid +sequenceDiagram + participant Main as 主线程 + participant Poller as 轮询 Goroutine + participant Worker as 渐变 Goroutine + + Note over Main,Worker: Goroutine 生命周期管理 + + Main->>Main: startPolling() + Main->>Main: wg.Add(1) + Main->>Poller: 启动 goroutine + Main->>Main: 返回(不等待) + + loop 轮询循环 + Poller->>Poller: 等待 ticker 或 stopChan + alt 收到 ticker + Poller->>Poller: pollLightLevel() + else 收到 stopChan + Poller->>Poller: wg.Done() + Poller->>Poller: 退出 + end + end + + Main->>Main: stopPolling() + Main->>Poller: stopChan <- signal + Main->>Main: 释放锁 + Main->>Main: wg.Wait() + Note over Main: 等待 goroutine 退出 + Main->>Main: 重新获取锁 + Main->>Main: 清空 stopChan + + Note over Main,Worker: 渐变 Goroutine 管理 + Main->>Main: SetBrightness() + Main->>Main: state.wg.Add(1) + Main->>Worker: 启动 goroutine + Main->>Main: 返回(不等待) + + Worker->>Worker: 执行渐变 + alt 完成 + Worker->>Worker: state.wg.Done() + else 被停止 + Worker->>Worker: state.wg.Done() + end + + Main->>Main: stopState() + Main->>Worker: stopCh <- signal + Main->>Main: state.wg.Wait() + Note over Main: 等待渐变完成 +``` ## 12. 系统集成 @@ -721,20 +993,104 @@ sequenceDiagram - 复用 BrightnessTransition 渐变功能 - 同步更新 DBus 属性 +```mermaid +graph LR + ABM[AutoBrightnessManager] -->|依赖注入| Manager[Display Manager] + ABM -->|调用| BT[BrightnessTransition] + ABM -->|调用| getBuiltinMonitor + ABM -->|调用| canSetBrightness + ABM -->|调用| setBrightnessRaw + ABM -->|调用| syncPropBrightness + + Manager -->|创建| ABM + Manager -->|初始化| BT + Manager -->|暴露| DBusAPI[DBus API] + + BT -->|调用| setBrightnessRaw + BT -->|调用| syncPropBrightness +``` + ### 12.2 电源管理集成 - 支持系统休眠/唤醒事件 -- `hold()`: 休眠前暂停轮询和补偿 -- `resume()`: 唤醒后恢复轮询和补偿 +- `hold()`: 休眠前暂停轮询 +- `resume()`: 唤醒后恢复轮询 + +```mermaid +sequenceDiagram + participant PM as 电源管理 + participant Manager as Display Manager + participant ABM as AutoBrightnessManager + participant Poller as 轮询器 + participant Display as 显示器 + + Note over PM,Display: 系统休眠场景 + PM->>Manager: PrepareForSleep(true) + Manager->>ABM: hold() + ABM->>ABM: 设置 systemAdjusting = true + ABM->>Poller: stopPolling() + Poller->>Poller: 停止 ticker + Poller->>Poller: 等待 goroutine 退出 + Note over ABM: 保持运行状态
但停止轮询 + + Note over PM,Display: 系统唤醒场景 + PM->>Manager: PrepareForSleep(false) + Manager->>ABM: resume() + ABM->>Poller: startPolling() + Poller->>Poller: 创建新 ticker + Poller->>Poller: 启动 goroutine + Poller->>Poller: 立即采集一次 + ABM->>ABM: 清除 systemAdjusting + Note over ABM: 恢复正常运行 + + Note over PM,Display: 亮度变化不触发手动调节检测 + ABM->>Display: 调整亮度 + Display->>Manager: 亮度变化通知 + Manager->>ABM: OnManualBrightnessChange() + ABM->>ABM: 检查 systemAdjusting + Note over ABM: systemAdjusting = true
忽略此次调用 +``` ### 12.3 DBus 接口 通过 Manager 暴露以下属性和方法: - `AutoBrightnessSupported` (只读) - `AutoBrightnessEnabled` (读写) -- `CurrentLightLevel` (只读) - 配置相关的 Get/Set 方法 +```mermaid +sequenceDiagram + participant App as 应用程序 + participant DBus as DBus + participant Manager as Display Manager + participant ABM as AutoBrightnessManager + + Note over App,ABM: 查询支持状态 + App->>DBus: Get AutoBrightnessSupported + DBus->>Manager: 读取属性 + Manager->>ABM: IsSupported() + ABM-->>Manager: true/false + Manager-->>DBus: 返回值 + DBus-->>App: true/false + + Note over App,ABM: 启用自动亮度 + App->>DBus: Set AutoBrightnessEnabled = true + DBus->>Manager: 设置属性 + Manager->>ABM: SetEnabled(true) + ABM->>ABM: Start() + ABM-->>Manager: 成功 + Manager->>DBus: PropertiesChanged 信号 + DBus->>App: 属性变化通知 + + Note over App,ABM: 查询状态信息 + App->>DBus: Call GetStatus() + DBus->>Manager: 调用方法 + Manager->>ABM: GetStatus() + ABM-->>Manager: 状态 map + Manager-->>DBus: 返回状态 + DBus-->>App: 状态信息 +``` + ## 13. 依赖关系 ### 13.1 外部依赖 @@ -747,8 +1103,6 @@ sequenceDiagram - **display.Manager:** 显示器管理和亮度控制 - **BrightnessTransition:** 亮度渐变效果 -- **CurveManager:** 亮度曲线管理 -- **AdaptiveKalmanFilter:** 传感器数据滤波 - **backlight:** 底层亮度控制 ## 14. 测试要点 @@ -757,10 +1111,54 @@ sequenceDiagram - 基本启停流程 - 配置加载和保存 -- 亮度计算准确性(曲线和线性映射) -- 卡尔曼滤波器效果 +- 亮度计算准确性 - 手动调节处理 -- 补偿机制 + +```mermaid +graph TD + subgraph "基本功能测试" + T1[初始化测试] --> T1A{检查支持状态} + T1A -->|支持| T1B[验证 supported = true] + T1A -->|不支持| T1C[验证 supported = false] + + T2[启动测试] --> T2A[调用 Start] + T2A --> T2B{检查传感器} + T2B -->|成功| T2C[验证 running = true] + T2B -->|失败| T2D[验证错误处理] + + T3[轮询测试] --> T3A[模拟光照变化] + T3A --> T3B[等待轮询周期] + T3B --> T3C[验证亮度调整] + + T4[停止测试] --> T4A[调用 Stop] + T4A --> T4B[验证资源释放] + T4B --> T4C[验证 running = false] + end + + subgraph "手动调节测试" + T5[临时暂停模式] --> T5A[手动调节亮度] + T5A --> T5B[验证暂停状态] + T5B --> T5C[等待超时] + T5C --> T5D[验证自动恢复] + + T6[完全禁用模式] --> T6A[修改配置] + T6A --> T6B[手动调节亮度] + T6B --> T6C[验证功能停止] + T6C --> T6D[验证配置更新] + end + + subgraph "渐变测试" + T7[渐变效果] --> T7A[设置目标亮度] + T7A --> T7B[验证渐变启动] + T7B --> T7C[监控步进过程] + T7C --> T7D[验证到达目标] + + T8[渐变中断] --> T8A[启动渐变] + T8A --> T8B[发起新渐变] + T8B --> T8C[验证旧渐变停止] + T8C --> T8D[验证新渐变启动] + end +``` ### 14.2 异常测试 @@ -769,6 +1167,44 @@ sequenceDiagram - 并发访问 - 资源泄漏 +```mermaid +graph TD + subgraph "异常场景测试" + E1[传感器服务异常] --> E1A[停止 iio-sensor-proxy] + E1A --> E1B[验证服务不可用检测] + E1B --> E1C[验证优雅降级] + E1C --> E1D[重启服务] + E1D --> E1E[验证自动恢复] + + E2[配置异常] --> E2A[损坏配置文件] + E2A --> E2B[尝试加载配置] + E2B --> E2C[验证使用默认配置] + + E3[并发测试] --> E3A[多线程调用] + E3A --> E3B[验证无死锁] + E3B --> E3C[验证数据一致性] + + E4[资源泄漏] --> E4A[反复启停] + E4A --> E4B[监控 goroutine 数量] + E4B --> E4C[监控内存使用] + E4C --> E4D[验证资源正确释放] + end + + subgraph "边界条件测试" + B1[极端光照值] --> B1A[测试 0 lux] + B1A --> B1B[测试 255 lux] + B1B --> B1C[测试超出范围值] + + B2[极端配置] --> B2A[最小轮询间隔] + B2A --> B2B[最大敏感度] + B2B --> B2C[最小阈值] + + B3[快速变化] --> B3A[光照快速波动] + B3A --> B3B[验证防抖动] + B3B --> B3C[验证调节频率限制] + end +``` + ### 14.3 性能测试 - CPU 占用率 @@ -776,12 +1212,195 @@ sequenceDiagram - 响应延迟 - 长时间运行稳定性 -## 15. 注意事项 +```mermaid +graph LR + subgraph "性能指标" + P1[CPU 占用] --> P1A[空闲时 < 0.5%] + P1A --> P1B[调节时 < 2%] + + P2[内存使用] --> P2A[基础内存 < 5MB] + P2A --> P2B[无内存泄漏] + + P3[响应延迟] --> P3A[光照变化到调节 < 5s] + P3A --> P3B[配置变更生效 < 1s] + + P4[稳定性] --> P4A[24小时运行测试] + P4A --> P4B[无崩溃无异常] + end + + subgraph "压力测试" + S1[高频调节] --> S1A[每秒变化光照] + S1A --> S1B[验证系统稳定] + + S2[长时间运行] --> S2A[连续运行7天] + S2A --> S2B[监控资源使用] + S2B --> S2C[验证无退化] + + S3[并发压力] --> S3A[多线程频繁调用] + S3A --> S3B[验证锁性能] + S3B --> S3C[验证无竞争条件] + end +``` + +## 15. 未来扩展 + +### 15.1 算法优化 + +- 支持非线性亮度曲线 +- 机器学习自适应调节 +- 时间段相关的亮度策略 + +### 15.2 功能增强 + +- 多显示器独立控制 +- 色温自动调节 +- 用户习惯学习 + +### 15.3 性能优化 + +- 事件驱动替代轮询 +- 智能采样频率调整 +- 更精细的电源管理 + +## 16. 典型使用场景 + +### 16.1 场景一:用户启用自动亮度 + +```mermaid +sequenceDiagram + participant User as 用户 + participant CC as 控制中心 + participant Manager as Display Manager + participant ABM as AutoBrightnessManager + participant Sensor as 传感器 + participant Display as 显示器 + + User->>CC: 打开自动亮度开关 + CC->>Manager: SetAutoBrightnessEnabled(true) + Manager->>ABM: SetEnabled(true) + ABM->>ABM: 保存配置 + ABM->>ABM: Start() + ABM->>Sensor: 连接并声明传感器 + Sensor-->>ABM: 成功 + ABM->>ABM: 启动轮询 + + loop 每3秒 + ABM->>Sensor: 读取光照强度 + Sensor-->>ABM: 150 lux + ABM->>ABM: 计算目标亮度 58% + ABM->>Display: 渐变调整到 58% + Display->>Display: 平滑过渡 + end + + Manager->>CC: PropertiesChanged + CC->>User: 显示已启用状态 +``` + +### 16.2 场景二:环境光变化触发调节 + +```mermaid +sequenceDiagram + participant Env as 环境 + participant Sensor as 传感器 + participant ABM as AutoBrightnessManager + participant Display as 显示器 + participant User as 用户 + + Note over Env: 室内光线变暗 + Env->>Sensor: 光照从 200 lux 降至 50 lux + + ABM->>Sensor: 轮询读取 + Sensor-->>ABM: 50 lux + ABM->>ABM: 计算目标亮度 + Note over ABM: 当前 78% -> 目标 29%
变化 49% > 阈值 20% + + ABM->>ABM: shouldAdjustBrightness() + ABM->>ABM: 所有条件满足 + + ABM->>Display: SetBrightnessForced(0.29) + Note over Display: 启动渐变
2秒内从 78% -> 29% + + loop 20步,每步100ms + Display->>Display: 亮度 -= 2.45% + end + + Display->>User: 屏幕亮度平滑降低 + Note over User: 感觉舒适,无闪烁 +``` + +### 16.3 场景三:手动调节后的行为 + +```mermaid +sequenceDiagram + participant User as 用户 + participant CC as 控制中心 + participant Manager as Display Manager + participant ABM as AutoBrightnessManager + participant Sensor as 传感器 + + Note over ABM: 自动亮度运行中 + + User->>CC: 手动拖动亮度滑块到 90% + CC->>Manager: SetBrightness(0.9) + Manager->>Manager: setBrightness() + Manager->>ABM: OnManualBrightnessChange() + + alt 临时暂停模式 + ABM->>ABM: 记录暂停时间 + ABM->>Sensor: ReleaseLight() + Note over ABM: 暂停 300 秒 + + Note over ABM,Sensor: 5分钟后 + ABM->>ABM: 检查超时 + ABM->>Sensor: ClaimLight() + ABM->>ABM: 恢复自动调节 + Note over User: 自动亮度重新生效 + + else 完全禁用模式 + ABM->>ABM: 保存 Enabled = false + ABM->>ABM: Stop() + ABM->>Sensor: 释放并断开 + Manager->>CC: PropertiesChanged + CC->>User: 显示已禁用状态 + Note over User: 需手动重新启用 + end +``` + +### 16.4 场景四:系统休眠与唤醒 + +```mermaid +sequenceDiagram + participant PM as 电源管理 + participant Manager as Display Manager + participant ABM as AutoBrightnessManager + participant Sensor as 传感器 + participant Display as 显示器 + + Note over PM: 用户合上笔记本 + PM->>Manager: PrepareForSleep(true) + Manager->>ABM: hold() + ABM->>ABM: systemAdjusting = true + ABM->>ABM: stopPolling() + Note over ABM: 保持连接但停止轮询 + + Note over PM: 系统休眠... + + Note over PM: 用户打开笔记本 + PM->>Manager: PrepareForSleep(false) + Manager->>ABM: resume() + ABM->>ABM: startPolling() + ABM->>Sensor: 立即读取光照 + Sensor-->>ABM: 当前光照值 + ABM->>Display: 调整到合适亮度 + ABM->>ABM: systemAdjusting = false + Note over ABM: 恢复正常运行 +``` + +## 17. 注意事项 1. **线程安全:** 所有公共方法都需要考虑并发访问 -2. **资源管理:** 确保传感器资源和 goroutine 正确释放 +2. **资源管理:** 确保传感器资源正确释放 3. **用户体验:** 避免频繁调节造成闪烁 4. **电源效率:** 合理设置轮询间隔 5. **降级策略:** 功能不可用时不影响系统稳定性 -6. **滤波器参数:** 卡尔曼滤波器参数需要根据实际传感器特性调整 -7. **曲线配置:** 光照-亮度曲线配置优先于线性映射 + diff --git "a/docs/auto_backlight/\350\207\252\345\212\250\344\272\256\345\272\246\345\212\237\350\203\275\344\275\277\347\224\250\346\214\207\345\215\227.md" "b/docs/auto_backlight/\350\207\252\345\212\250\344\272\256\345\272\246\345\212\237\350\203\275\344\275\277\347\224\250\346\214\207\345\215\227.md" index d03cd0a17..0618bc460 100644 --- "a/docs/auto_backlight/\350\207\252\345\212\250\344\272\256\345\272\246\345\212\237\350\203\275\344\275\277\347\224\250\346\214\207\345\215\227.md" +++ "b/docs/auto_backlight/\350\207\252\345\212\250\344\272\256\345\272\246\345\212\237\350\203\275\344\275\277\347\224\250\346\214\207\345\215\227.md" @@ -15,9 +15,9 @@ dde-dconfig -a <应用ID> -r <资源名称> -k <配置键> --get # 查 ``` **自动亮度配置参数**: -- 应用ID:`org.deepin.dde-daemon` +- 应用ID:`org.deepin.startdde` - 资源名称:`org.deepin.Display.AutoBrightness` -- 配置键:`enabled`, `sensitivity`, `polling-interval`, `change-threshold`, `manual-override-duration`, `brightness-change-threshold`, `kalman-process-noise`, `kalman-measurement-noise`, `kalman-window-size` +- 配置键:`enabled`, `sensitivity`, `polling-interval`, `change-threshold`, `manual-override-duration` ## 使用前准备 @@ -80,19 +80,13 @@ dbus-send --session --print-reply --dest=com.deepin.daemon.Display \ ### 设置参数说明 -| 设置项 | 配置键 | 说明 | 取值范围 | 默认值 | +| 设置项 | 配置键 | 说明 | 取值范围 | 推荐值 | |-------|--------|------|---------|--------| -| **启用状态** | enabled | 是否启用自动亮度功能 | true/false | false | -| **敏感度** | sensitivity | 控制亮度调节的敏感程度,值越大亮度变化越明显 | 0.1-3.0 | 0.5 | -| **检测间隔** | polling-interval | 多久检测一次环境光(秒) | 1-60秒 | 5秒 | -| **环境光变化阈值** | change-threshold | 环境光变化多少才调节亮度(绝对值) | 1.0-50.0 | 20.0 | -| **亮度变化阈值** | brightness-change-threshold | 亮度变化多少才执行调节 | 0.01-1.0 | 0.01 | -| **暂停时间** | manual-override-duration | 手动调节后暂停自动调节的时间(秒) | 60-1800秒 | 300秒 | -| **手动调节禁用模式** | manual-adjust-disables-auto-mode | 手动调节后是否完全禁用自动亮度 | true/false | true | -| **渐变效果** | use-transition | 自动调节时是否使用渐变效果 | true/false | true | -| **卡尔曼过程噪声** | kalman-process-noise | 传感器数据滤波器参数(系统模型不确定性) | 0.01-100 | 0.8 | -| **卡尔曼测量噪声** | kalman-measurement-noise | 传感器数据滤波器参数(传感器噪声) | 0.01-100 | 0.05 | -| **卡尔曼窗口大小** | kalman-window-size | 传感器数据滤波器窗口大小 | 2-100 | 3 | +| **启用状态** | enabled | 是否启用自动亮度功能 | true/false | true | +| **敏感度** | sensitivity | 控制亮度调节的敏感程度,值越大亮度变化越明显 | 0.1-3.0 | 0.5(标准)| +| **检测间隔** | polling-interval | 多久检测一次环境光(秒) | 1-60秒 | 3秒(标准)| +| **变化阈值** | change-threshold | 环境光变化多少才调节亮度(绝对值) | 1.0-50.0 | 20.0(标准)| +| **暂停时间** | manual-override-duration | 手动调节后暂停自动调节的时间(秒) | 60-1800秒 | 300秒(5分钟)| ### 单独设置配置项 @@ -100,43 +94,35 @@ dbus-send --session --print-reply --dest=com.deepin.daemon.Display \ ```bash # 设置敏感度 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.5 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.5 # 设置检测间隔(秒) -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 5 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 3 -# 设置环境光变化阈值 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 20.0 - -# 设置亮度变化阈值 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k brightness-change-threshold --set -v 0.01 +# 设置变化阈值 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 20.0 # 设置手动调节暂停时间(秒) -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 300 - -# 设置卡尔曼滤波器参数(高级) -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k kalman-process-noise --set -v 0.8 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k kalman-measurement-noise --set -v 0.05 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k kalman-window-size --set -v 3 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 300 ``` ### 查询单个配置项 ```bash # 查询敏感度 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k sensitivity --get +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --get # 查询检测间隔 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k polling-interval --get +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --get # 查询变化阈值 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k change-threshold --get +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --get # 查询暂停时间 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k manual-override-duration --get +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k manual-override-duration --get # 查询所有配置 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness --list +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness --list ``` ### 常用配置示例 @@ -145,43 +131,43 @@ dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness --list #### 标准配置(推荐) ```bash -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.5 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 5 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 20.0 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 300 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.5 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 3 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 20.0 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 300 ``` #### 快速响应配置(适合移动办公) ```bash -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.8 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 2 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 15.0 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 180 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.8 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 2 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 15.0 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 180 ``` #### 稳定配置(适合固定办公) ```bash -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.4 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 10 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 25.0 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 600 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.4 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 5 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 25.0 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 600 ``` #### 节能配置(最大化省电) ```bash -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.3 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 15 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 30.0 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 900 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.3 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 10 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 30.0 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 900 ``` ## 使用技巧 ### 1. 手动调节与自动调节的配合 -- **手动调节后**:系统会自动暂停(或完全禁用,取决于配置) +- **手动调节后**:系统会自动暂停5分钟,避免冲突 - **立即恢复**:关闭后重新开启自动亮度即可立即恢复 -- **调整暂停时间**:可以修改 `manual-override-duration` 参数 +- **调整暂停时间**:可以修改 `manual_override_duration` 参数 ### 2. 不同环境的优化 @@ -234,7 +220,7 @@ sudo systemctl enable iio-sensor-proxy ```bash # 方法1:降低变化阈值(更敏感) -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 10.0 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 10.0 # 方法2:重新启用(清除暂停状态) dbus-send --session --dest=com.deepin.daemon.Display \ @@ -252,15 +238,15 @@ dbus-send --session --dest=com.deepin.daemon.Display \ **解决方法**: ```bash # 方法1:提高变化阈值(降低敏感度) -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 30.0 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 30.0 # 方法2:增加检测间隔 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 10 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 10 # 方法3:使用完整稳定配置 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.4 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 10 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 25.0 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.4 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 5 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 25.0 ``` ### 问题4:反应太慢 @@ -270,27 +256,15 @@ dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k cha **解决方法**: ```bash # 方法1:降低变化阈值(更敏感) -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 10.0 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 10.0 # 方法2:减少检测间隔 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 2 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 2 # 方法3:使用完整快速响应配置 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.8 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 2 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 15.0 -``` - -### 问题5:亮度跳变严重 - -**现象**:亮度变化不平滑,有跳变 - -**解决方法**: -```bash -# 调整卡尔曼滤波器参数,使数据更平滑 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k kalman-process-noise --set -v 0.5 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k kalman-measurement-noise --set -v 0.1 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k kalman-window-size --set -v 5 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.8 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 2 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 15.0 ``` ## 高级功能 @@ -299,14 +273,14 @@ dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k kal ```bash # 查看所有配置项 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness --list +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness --list # 查看单个配置项 -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k enabled --get -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k sensitivity --get -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k polling-interval --get -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k change-threshold --get -dde-dconfig -a org.deepin.dde-daemon -r org.deepin.Display.AutoBrightness -k manual-override-duration --get +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k enabled --get +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --get +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --get +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --get +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k manual-override-duration --get # 查看运行状态(通过 D-Bus) dbus-send --session --print-reply --dest=com.deepin.daemon.Display \ @@ -333,12 +307,21 @@ watch -n 1 'dbus-send --session --print-reply --dest=com.deepin.daemon.Display \ ```bash # 查看自动亮度相关日志 -journalctl --user -u dde-daemon -f | grep AutoBrightness +journalctl --user -u startdde -f | grep AutoBrightness # 查看传感器服务日志 journalctl -u iio-sensor-proxy -f ``` +## 注意事项 + +1. **仅限内置屏幕**:外接显示器不支持此功能 +2. **传感器位置**:不要遮挡设备上的光线传感器 +3. **电量消耗**:频繁检测会增加电量消耗,建议合理设置检测间隔 +4. **手动优先**:手动调节亮度后会自动暂停功能一段时间 +5. **重启保持**:设置会自动保存,重启后依然有效 +6. **敏感度说明**:敏感度参数控制环境光到屏幕亮度的映射关系,值越大屏幕亮度变化越明显。推荐范围 0.3-1.0,默认 0.5 + ## 配置参数详解 ### 敏感度 (sensitivity) @@ -350,7 +333,7 @@ journalctl -u iio-sensor-proxy -f - **0.7-1.0**:高敏感度,适合需要明显亮度变化的场景 - **1.1-3.0**:超高敏感度,仅在特殊场景使用 -**计算公式**(默认线性映射):屏幕亮度 = (环境光强度 × 敏感度) / 1024.0 +**计算公式**:屏幕亮度 = (环境光强度 × 敏感度) / 255 ### 检测间隔 (polling_interval) @@ -361,7 +344,7 @@ journalctl -u iio-sensor-proxy -f - **6-10秒**:慢速响应,适合固定办公环境 - **11-60秒**:省电模式,适合电池续航优先的场景 -### 环境光变化阈值 (change_threshold) +### 变化阈值 (change_threshold) 环境光强度变化超过此值才会触发亮度调节: @@ -371,14 +354,6 @@ journalctl -u iio-sensor-proxy -f **注意**:阈值过低会导致频繁调节,阈值过高会导致响应迟钝 -### 亮度变化阈值 (brightness_change_threshold) - -目标亮度与当前亮度差值超过此值才执行调节: - -- **0.01**:默认值,1% 的亮度变化就调节 -- **0.05**:5% 的亮度变化才调节 -- **0.10**:10% 的亮度变化才调节 - ### 暂停时间 (manual_override_duration) 手动调节亮度后,自动调节功能暂停的时间: @@ -387,34 +362,31 @@ journalctl -u iio-sensor-proxy -f - **181-600秒**:标准暂停,适合大多数场景(推荐 300秒) - **601-1800秒**:长暂停,适合希望手动控制优先的场景 -### 卡尔曼滤波器参数(高级) - -卡尔曼滤波器用于平滑传感器数据,减少噪声影响: - -- **kalman-process-noise (Q)**: 过程噪声,表示系统模型的不确定性 - - 较小值(0.1-0.5):更信任模型,数据更平滑但响应慢 - - 较大值(0.5-2.0):更信任测量,响应快但可能不平滑 - - 默认值:0.8 - -- **kalman-measurement-noise (R)**: 测量噪声,表示传感器噪声 - - 较小值(0.01-0.05):更信任传感器,响应快 - - 较大值(0.05-0.5):更信任估计值,更平滑 - - 默认值:0.05(会自适应调整) +## 推荐设置 -- **kalman-window-size**: 滑动窗口大小,用于计算方差 - - 较小值(2-5):快速响应变化 - - 较大值(5-10):更稳定的估计 - - 默认值:3 +### 日常办公 +```bash +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.5 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 3 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 20.0 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 300 +``` -## 注意事项 +### 移动办公 +```bash +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.8 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 2 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 15.0 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 180 +``` -1. **仅限内置屏幕**:外接显示器不支持此功能 -2. **传感器位置**:不要遮挡设备上的光线传感器 -3. **电量消耗**:频繁检测会增加电量消耗,建议合理设置检测间隔 -4. **手动优先**:手动调节亮度后会自动暂停功能或禁用功能 -5. **重启保持**:设置会自动保存,重启后依然有效 -6. **敏感度说明**:敏感度参数控制环境光到屏幕亮度的映射关系,值越大屏幕亮度变化越明显。推荐范围 0.3-1.0,默认 0.5 -7. **卡尔曼滤波器**:高级参数,普通用户无需调整。如果亮度跳变严重,可尝试调整 +### 省电模式 +```bash +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k sensitivity --set -v 0.3 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k polling-interval --set -v 10 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k change-threshold --set -v 30.0 +dde-dconfig -a org.deepin.startdde -r org.deepin.Display.AutoBrightness -k manual-override-duration --set -v 600 +``` --- diff --git a/go.mod b/go.mod index fa7129b7f..0e242f9c6 100644 --- a/go.mod +++ b/go.mod @@ -8,8 +8,8 @@ require ( github.com/fsnotify/fsnotify v1.8.0 github.com/godbus/dbus/v5 v5.1.0 github.com/jouyouyun/hardware v0.1.8 - github.com/linuxdeepin/dde-api v0.0.0-20260310032929-7f0ab8f52e1b - github.com/linuxdeepin/go-dbus-factory v0.0.0-20260227070938-bcb8d12841ab + github.com/linuxdeepin/dde-api v0.0.0-20260511093853-07ca3f2f1232 + github.com/linuxdeepin/go-dbus-factory v0.0.0-20260513063723-8eea4924a64a github.com/linuxdeepin/go-gir v0.0.0-20251204113853-1873b5530f50 github.com/linuxdeepin/go-lib v0.0.0-20260205120541-a1f572ce1442 github.com/linuxdeepin/go-x11-client v0.0.0-20240415051504-c8e43d028ff9 diff --git a/go.sum b/go.sum index 91968dcd0..763114867 100644 --- a/go.sum +++ b/go.sum @@ -28,10 +28,10 @@ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/linuxdeepin/dde-api v0.0.0-20260310032929-7f0ab8f52e1b h1:MkrAGimPW17PAShqzLQ8JURvyZ+UvzbnZuM+XCijAtQ= -github.com/linuxdeepin/dde-api v0.0.0-20260310032929-7f0ab8f52e1b/go.mod h1:etFJ3bz0/U3wklOLPLn7A4DM5VBQJ4yHhHaWpCEzzV8= -github.com/linuxdeepin/go-dbus-factory v0.0.0-20260227070938-bcb8d12841ab h1:tzTj6afLE47xQ8oRdeH/xBwgc1Jfo6loOCZyAVO21/4= -github.com/linuxdeepin/go-dbus-factory v0.0.0-20260227070938-bcb8d12841ab/go.mod h1:dfpixHkqiijg3P7w5QArkMyC7+vlHcutN5R6zHIn8is= +github.com/linuxdeepin/dde-api v0.0.0-20260511093853-07ca3f2f1232 h1:1jSUHQ4FlFGhhhLiCzbXvcH+JIW92gGLWOQExbl/6SI= +github.com/linuxdeepin/dde-api v0.0.0-20260511093853-07ca3f2f1232/go.mod h1:etFJ3bz0/U3wklOLPLn7A4DM5VBQJ4yHhHaWpCEzzV8= +github.com/linuxdeepin/go-dbus-factory v0.0.0-20260513063723-8eea4924a64a h1:1N9cq5hB4mlbUf7YJSZ9+3fb9VffUSxOXxndWlpONAE= +github.com/linuxdeepin/go-dbus-factory v0.0.0-20260513063723-8eea4924a64a/go.mod h1:dfpixHkqiijg3P7w5QArkMyC7+vlHcutN5R6zHIn8is= github.com/linuxdeepin/go-gir v0.0.0-20250812023606-b28aaee32ac9/go.mod h1:a0tox5vepTQu5iO6rdKc4diGT+fkyXZlRROM8ULEvaI= github.com/linuxdeepin/go-gir v0.0.0-20251204113853-1873b5530f50 h1:CLVQdE+YgfvHD2EadN/0hPIhCbbu5cdGyDu4brW9pyw= github.com/linuxdeepin/go-gir v0.0.0-20251204113853-1873b5530f50/go.mod h1:a0tox5vepTQu5iO6rdKc4diGT+fkyXZlRROM8ULEvaI= diff --git a/misc/dsg-configs/org.deepin.Display.AutoBrightness.json b/misc/dsg-configs/org.deepin.Display.AutoBrightness.json index ea6024913..116bf1457 100644 --- a/misc/dsg-configs/org.deepin.Display.AutoBrightness.json +++ b/misc/dsg-configs/org.deepin.Display.AutoBrightness.json @@ -5,7 +5,7 @@ "enabled": { "name": "Auto Brightness Enabled", "description": "启用基于环境光传感器的自动亮度调节", - "value": true, + "value": false, "visibility": "private", "permissions": "readwrite", "serial": 0, diff --git a/misc/dsg-configs/org.deepin.Display.json b/misc/dsg-configs/org.deepin.Display.json index 58abb139e..df77f781c 100644 --- a/misc/dsg-configs/org.deepin.Display.json +++ b/misc/dsg-configs/org.deepin.Display.json @@ -179,7 +179,7 @@ "visibility": "public" }, "transition-enabled": { - "value": true, + "value": false, "serial": 0, "flags": [], "name": "Brightness Transition Enabled", @@ -188,11 +188,22 @@ "visibility": "private" }, "transition-duration": { - "value": 4, + "value": 4000, "serial": 0, "flags": [], "name": "Brightness Transition Duration", - "description": "从 0% 到 100% 完整渐变所需的时间(秒)", + "description": "从 0% 到 100% 完整渐变所需的时间(毫秒)", + "permissions": "readwrite", + "visibility": "private" + }, + "transition-step-percent": { + "value": 1, + "serial": 0, + "flags": [], + "name": "Transition Step Percent", + "name[zh_CN]": "过渡步进百分比", + "description": "Step percentage for brightness transition (e.g., 1 means 1%)", + "description[zh_CN]": "亮度过渡步进百分比(如 1 表示每次调节 1%)", "permissions": "readwrite", "visibility": "private" },