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/display1/auto_brightness.go b/display1/auto_brightness.go new file mode 100644 index 000000000..7d748e9c9 --- /dev/null +++ b/display1/auto_brightness.go @@ -0,0 +1,1337 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later +package display1 + +import ( + "errors" + "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" +) + +const ( + compensationInterval = 150 * time.Millisecond + sensorDataTimeout = 1 * time.Second + compensationThreshold = 5.0 + maxSensorLux = 1024.0 // 传感器最大勒克斯值 + minBrightnessLimit = 0.1 // 最小亮度限制 (10%) +) + +// 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) + 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 默认自动亮度配置 +var 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, +} + +// DSettings键名已在manager.go中定义 +// AutoBrightnessManager 自动亮度管理器 +type AutoBrightnessManager struct { + 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 + + kalmanFilter *brightness.AdaptiveKalmanFilter + + systemAdjusting bool + running bool + stopping int32 // atomic: 1 when Stop is in progress + + lastSensorDataTime time.Time + compensationTicker *time.Ticker + compensationStopCh chan struct{} + compensationWg sync.WaitGroup + + // 同步控制 + mutex sync.RWMutex + + // 重试和降级配置 + 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, + } +} + +// Initialize 初始化自动亮度管理器 +func (abm *AutoBrightnessManager) Initialize(manager *Manager) error { + 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 + abm.mutex.Unlock() + + err := abm.initConfigManager() + if err != nil { + logger.Warning("[AutoBrightness] Failed to initialize config manager:", err) + } + + builtinMonitor := manager.getBuiltinMonitor() + if builtinMonitor == nil { + abm.mutex.Lock() + abm.supported = false + abm.mutex.Unlock() + return fmt.Errorf("no builtin monitor found (total monitors: %d)", len(manager.getConnectedMonitors())) + } + + 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) + } + + sensorProxy := sensorproxy.NewSensorProxy(manager.sysBus) + sensorClient := NewSensorProxyClient(sensorProxy, manager.dbusDaemon) + + 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) + } + + 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) + config = DefaultAutoBrightnessConfig + } + abm.config = config + abm.supported = true + abm.applyKalmanFilterConfig() + abm.mutex.Unlock() + + logger.Info("[AutoBrightness] AutoBrightnessManager initialized successfully") + return nil +} + +// Start 启动自动亮度功能 +func (abm *AutoBrightnessManager) Start() error { + abm.mutex.Lock() + 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 + } + + // 如果正在执行 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 + } + + sensorClient := abm.sensorClient + manager := abm.manager + abm.mutex.Unlock() + + 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) + } + + err = abm.claimLightWithRetry() + if err != nil { + logger.Warning("[AutoBrightness] Failed to claim light sensor after retries:", err) + sensorClient.Disconnect() + return fmt.Errorf("failed to claim light sensor: %w", err) + } + + abm.mutex.Lock() + abm.running = true + abm.enabled = true + abm.lastSensorDataTime = time.Now() + powerSaving := manager.isPowerSaving() + sessionActive := manager.sessionActive + if !powerSaving && sessionActive { + 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 +} + +// Stop 停止自动亮度功能 +func (abm *AutoBrightnessManager) Stop() error { + abm.mutex.Lock() + + if !abm.running { + abm.mutex.Unlock() + return nil + } + + // 标记正在停止,防止窗口期内 Start() 创建新 ticker + atomic.StoreInt32(&abm.stopping, 1) + abm.stopCompensationTimer() + atomic.StoreInt32(&abm.stopping, 0) + 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 +} + +// 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() + + // Stop 在 running=false 时不会断开连接,需确保 sensorClient 被释放 + if abm.sensorClient != nil { + if err := abm.sensorClient.Disconnect(); err != nil { + logger.Warning("[AutoBrightness] Failed to disconnect sensor client:", err) + } + abm.sensorClient = nil + } + + logger.Info("[AutoBrightness] AutoBrightnessManager cleaned up") + 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() + // 如果是系统自动调整(如节能模式),忽略此次调用 + 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 { + 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() + 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 manager != nil { + manager.setPropAutoBrightnessEnabled(false) + } + }() + return + } + abm.resetHistoryState() + abm.manualOverride = time.Now() + 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) + } + } +} + +// resetHistoryState 重置历史状态,使下次能立即触发亮度调节 +// 注意:此函数假设调用者已经持有锁 +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 暂停自动亮度调节 +func (abm *AutoBrightnessManager) hold() { + abm.mutex.Lock() + defer abm.mutex.Unlock() + if !abm.running { + return + } + + abm.stopCompensationTimer() +} + +func (abm *AutoBrightnessManager) resume() { + abm.mutex.Lock() + + if !abm.running { + abm.mutex.Unlock() + return + } + + abm.resetHistoryState() + abm.startCompensationTimer() + abm.mutex.Unlock() +} + +// OnConfigChanged 处理配置变更 +func (abm *AutoBrightnessManager) OnConfigChanged(config AutoBrightnessConfig) { + abm.mutex.Lock() + oldEnabled := abm.config.Enabled + oldSensitivity := abm.config.Sensitivity + abm.config = config + + needStart := config.Enabled && !oldEnabled && !abm.running + needStop := !config.Enabled && oldEnabled && abm.running + + sensitivityChanged := oldSensitivity != config.Sensitivity + needImmediateAdjust := sensitivityChanged && abm.running && config.Enabled + + abm.applyKalmanFilterConfig() + + 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, threshold=%.1f", + config.Enabled, config.Sensitivity, 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() + 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 +} + +// onServiceChange 服务状态变化回调 +func (abm *AutoBrightnessManager) onServiceChange(available bool) { + var shouldStart bool + + abm.mutex.Lock() + + 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 + shouldStart = abm.config.Enabled && !abm.running + } + } + } else { + logger.Warning("[AutoBrightness] SensorProxy service became unavailable") + if abm.running { + abm.stopCompensationTimer() + abm.running = false + abm.enabled = false + } + abm.supported = false + } + + 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 + inManualOverride := abm.isInManualOverride() + abm.lastSensorDataTime = time.Now() + abm.mutex.Unlock() + + if !running || inManualOverride { + return + } + + if abm.manager != nil && abm.manager.isPowerSaving() { + return + } + + abm.processLightChange(rawLightLevel) +} + +// adjustBrightnessOnce 立即执行一次亮度调整(用于配置变化时) +func (abm *AutoBrightnessManager) adjustBrightnessOnce() { + abm.mutex.Lock() + if !abm.running || !abm.enabled { + abm.mutex.Unlock() + return + } + + 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 + } + + abm.mutex.Lock() + savedLightLevel := abm.lastLightLevel + savedAdjustTime := abm.lastAdjustTime + abm.lastLightLevel = -1 + abm.lastAdjustTime = time.Time{} + abm.mutex.Unlock() + + abm.processLightChange(rawLightLevel) + + 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(rawLightLevel int) { + abm.mutex.Lock() + + if !abm.running { + abm.mutex.Unlock() + 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") + 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(filteredLightLevel, targetBrightness) { + abm.mutex.Unlock() + return + } + + // 记录时间戳(在释放锁之前),用于频率控制 + now := time.Now() + + // 释放锁后再设置亮度(避免在渐变时持有锁) + abm.mutex.Unlock() + + // 设置亮度(不重试,下次轮询会自动重试) + err := abm.setBrightness(targetBrightness) + if err != nil { + logger.Warningf("[AutoBrightness] Failed to set brightness (raw=%d, filtered=%d, target=%.1f%%): %v", + rawLightLevel, filteredLightLevel, targetBrightness*100, err) + return + } + + // 设置成功后更新状态 + abm.mutex.Lock() + abm.lastLightLevel = filteredLightLevel + abm.lastBrightness = targetBrightness + abm.lastAdjustTime = now + abm.mutex.Unlock() + + logger.Infof("[AutoBrightness] Brightness adjusted: raw=%d lux -> filtered=%d lux -> brightness=%.1f%%", + rawLightLevel, filteredLightLevel, targetBrightness*100) +} + +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 + } + + if time.Since(abm.lastSensorDataTime) < sensorDataTimeout { + return false, 0 + } + + if abm.kalmanFilter == nil { + return false, 0 + } + + sensorValue, err := sensorClient.GetLightLevel() + if err != nil { + return false, 0 + } + + filterOutput := abm.kalmanFilter.GetEstimate() + diff := math.Abs(filterOutput - float64(sensorValue)) + + 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.mutex.Unlock() + return + } + inManualOverride := abm.isInManualOverride() + sensorClient := abm.sensorClient + abm.mutex.Unlock() + + if inManualOverride { + return + } + + if abm.manager != nil && abm.manager.isPowerSaving() { + return + } + + if sensorClient == nil { + return + } + + 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() { + 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: + // 检查停止信号,避免在持有锁的情况下调用 compensationTick() + select { + case <-abm.compensationStopCh: + return + default: + abm.compensationTick() + } + case <-abm.compensationStopCh: + return + } + } + }() +} + +// stopCompensationTimer 停止补偿定时器并等待补偿 goroutine 退出 +// 注意:调用者必须持有 abm.mutex 锁 +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 + } + + // 等待 goroutine 退出 + abm.compensationWg.Wait() +} + +// 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 { + // 优先使用曲线配置 + if brightness.HasAutoBrightnessCurve() { + br := brightness.GetAutoBrightnessValue(lightLevel) + if br >= 0 { + // 约束到有效范围 + if br > 1.0 { + br = 1.0 + } + // 设置最小亮度,避免屏幕过暗 + if br < minBrightnessLimit { + br = minBrightnessLimit + } + return br + } + } + + // 应用敏感度调整 + adjustedLevel := float64(lightLevel) * abm.config.Sensitivity + + // 简单线性映射 + brightness := adjustedLevel / maxSensorLux + + // 约束到有效范围 + if brightness < 0.0 { + brightness = 0.0 + } else if brightness > 1.0 { + brightness = 1.0 + } + + // 设置最小亮度,避免屏幕过暗 + if brightness < minBrightnessLimit { + brightness = minBrightnessLimit + } + return brightness +} + +// shouldAdjustBrightness 检查是否应该调节亮度(变化阈值和频率控制) +func (abm *AutoBrightnessManager) shouldAdjustBrightness(lightLevel int, targetBrightness float64) bool { + now := time.Now() + + // 检查环境光变化阈值 + 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) + if brightnessChange < abm.config.BrightnessChangeThreshold { + return false + } + } + return true +} + +// 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 { + return errors.New("no builtin monitor") + } + // 根据配置决定是否使用渐变 + abm.mutex.RLock() + useTransition := abm.config.UseTransition + abm.mutex.RUnlock() + var err error + if useTransition { + // 使用渐变效果(即使全局渐变功能关闭) + 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:", 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.getDefaultMonitorBrightness(builtinMonitor.Name) + + 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(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 { + return fmt.Errorf("failed to check ambient light sensor: %w", err) + } + if !hasLight { + return errors.New("no ambient light sensor available") + } + 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 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 +} + +// 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 failed, 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 failed, using default changeThreshold") + config.ChangeThreshold = DefaultAutoBrightnessConfig.ChangeThreshold + } + + // BrightnessChangeThreshold + if val, err := abm.configManager.Value(0, DSettingsKeyABBrightnessChangeThreshold); err == nil { + 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 failed, using default brightnessChangeThreshold") + config.BrightnessChangeThreshold = DefaultAutoBrightnessConfig.BrightnessChangeThreshold + } + + // 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 failed, 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 failed, 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 failed, 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 failed, 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 { + 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 + } + + // KalmanMeasurementNoise + if val, err := abm.configManager.Value(0, DSettingsKeyABKalmanMeasurementNoise); err == nil { + 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 + } + + // 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() + 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") + } + // 在锁内复制配置,避免数据竞争 + abm.mutex.RLock() + config := abm.config + abm.mutex.RUnlock() + // 验证配置有效性 + err := config.Validate() + if err != nil { + return err + } + // 保存各个配置项 + err = setGlobalDconfValue(DSettingsAutoBrightnessAppID, DSettingsAutoBrightnessName, "", + DSettingsKeyABEnabled, dbus.MakeVariant(config.Enabled)) + if err != nil { + return err + } + err = setGlobalDconfValue(DSettingsAutoBrightnessAppID, DSettingsAutoBrightnessName, "", + DSettingsKeyABSensitivity, dbus.MakeVariant(config.Sensitivity)) + if err != nil { + return err + } + err = setGlobalDconfValue(DSettingsAutoBrightnessAppID, DSettingsAutoBrightnessName, "", + DSettingsKeyABChangeThreshold, dbus.MakeVariant(config.ChangeThreshold)) + if err != nil { + return err + } + err = setGlobalDconfValue(DSettingsAutoBrightnessAppID, DSettingsAutoBrightnessName, "", + DSettingsKeyABPollingInterval, dbus.MakeVariant(config.PollingInterval)) + if err != nil { + return err + } + err = setGlobalDconfValue(DSettingsAutoBrightnessAppID, DSettingsAutoBrightnessName, "", + DSettingsKeyABManualOverride, dbus.MakeVariant(config.ManualOverrideDuration)) + if err != nil { + return err + } + err = setGlobalDconfValue(DSettingsAutoBrightnessAppID, DSettingsAutoBrightnessName, "", + DSettingsKeyABManualAdjustDisablesAutoMode, dbus.MakeVariant(config.ManualAdjustDisablesAutoMode)) + if err != nil { + return err + } + err = setGlobalDconfValue(DSettingsAutoBrightnessAppID, DSettingsAutoBrightnessName, "", + DSettingsKeyABUseTransition, dbus.MakeVariant(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..14229ee56 100644 --- a/display1/brightness.go +++ b/display1/brightness.go @@ -1,11 +1,10 @@ -// 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 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,28 +114,19 @@ 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) getBrightnessSetter() int { +func (m *Manager) getSetterConfig() int { // NOTE: 特殊处理龙芯笔记本亮度设置问题 blDir := "/sys/class/backlight/loongson" _, err := os.Stat(blDir) @@ -181,21 +176,85 @@ 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 { + 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) +} + +func (m *Manager) createBrightnessSetter(monitor *Monitor) func(float64) error { isBuiltin := m.isBuiltinMonitor(monitor.Name) - err := brightness.Set(brightnessValue, temperature, m.getBrightnessSetter(), isBuiltin, - monitor.ID, m.xConn) - return err + _uuid := monitor.uuid + if _useWayland { + _uuid = monitor.uuidV0 + } + + // 获取当前色温值,用于 gamma 设置路径 + temperature := m.getColorTemperatureValue() + + setter := m.getSetterConfig() + + var setterFunc func(float64) error + + 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} } @@ -204,13 +263,15 @@ 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 { - temperature := m.getColorTemperatureValue() + if enabled { // 保持最小亮度,不能全黑 if value <= 0.1 { value = 0.1 + } else if value > 1 { + value = 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 @@ -219,11 +280,9 @@ 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 { @@ -233,3 +292,50 @@ func (m *Manager) setBrightnessAndSync(name string, value float64) error { } 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 a2368f385..2618a9bc5 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 @@ -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,20 +172,86 @@ func init() { } } -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) + 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(value, controller) + err := _setBacklight(brightness, controller) if err != nil { - fmt.Printf("WARN: failed to set backlight %s: %v", controller.Name, err) + logger.Warningf("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) - const backlightTypeDisplay = 1 - fmt.Printf("help set brightness %q max %v value %v br %v\n", - controller.Name, controller.MaxBrightness, value, br) - return helper.SetBrightness(0, backlightTypeDisplay, controller.Name, br) +// 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 new file mode 100644 index 000000000..892409d48 --- /dev/null +++ b/display1/brightness/curve.go @@ -0,0 +1,753 @@ +// SPDX-FileCopyrightText: 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),表示最大亮度的百分比 +} + +// AutoBrightnessCurvePoint 自动亮度曲线控制点 +type AutoBrightnessCurvePoint struct { + Lux int `json:"lux"` // 光感值 + Br float64 `json:"br"` // 亮度百分比 (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 + + // 自动亮度曲线 + autoBrightnessCurve []AutoBrightnessCurvePoint + autoBrightnessCurveFunc func(lux int) float64 +} + +// 全局 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.configBoardName = config.BoardName + + 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) +} + +// 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 + if math.Abs(x1-x2) < 1e-6 { + return y1 / 100.0 + } + 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 +} + +// 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 new file mode 100644 index 000000000..3935d2d03 --- /dev/null +++ b/display1/brightness/kalman_filter.go @@ -0,0 +1,260 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package brightness + +import ( + "sync" +) + +// 卡尔曼滤波器默认参数 +const ( + DefaultProcessNoiseQ = 0.5 // 过程噪声协方差(系统模型的不确定性) + DefaultMeasurementNoiseR = 0.02 // 测量噪声协方差(传感器噪声) + DefaultWindowSize = 5 // 自适应滤波器窗口大小 + DefaultVarianceScale = 0.1 // 方差缩放系数(用于自适应调整R) +) + +// KalmanFilter1D 一维卡尔曼滤波器 +// 用于传感器数据的平滑和噪声抑制 +type KalmanFilter1D struct { + mu sync.Mutex + + // 系统状态 + 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 { + 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 + 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.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 // 窗口大小 + 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.mu.Lock() + defer akf.mu.Unlock() + + // 添加到窗口 + 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.KalmanFilter1D.setMeasurementNoiseUnlocked(newR) + logger.Debugf("[AutoBrightness::AdaptiveKalmanFilter] Adjusted measurement noise: R=%.4f -> %.4f", + oldR, newR) + } + } + + // 调用基类的无锁更新方法(已持有 akf.mu,避免死锁) + return akf.KalmanFilter1D.updateUnlocked(measurement) +} + +// Reset 重置滤波器 +func (akf *AdaptiveKalmanFilter) 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 + } + akf.windowSize = size + // 如果当前窗口超过新大小,截断 + if len(akf.window) > akf.windowSize { + akf.window = akf.window[len(akf.window)-akf.windowSize:] + } +} 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/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/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/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..2f50bc79c 100644 --- a/display1/manager.go +++ b/display1/manager.go @@ -89,6 +89,40 @@ 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" + + // 统一亮度过渡配置 + DSettingsKeyTransitionEnabled = "transition-enabled" + DSettingsKeyTransitionDuration = "transition-duration" + DSettingsKeyTransitionStepPercent = "transition-step-percent" + DSettingsKeyTransitionMinStepInterval = "transition-min-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" + DSettingsKeyABBrightnessChangeThreshold = "brightness-change-threshold" + DSettingsKeyABCurve = "lux-brightness-curve" + + // 卡尔曼滤波器配置 + DSettingsKeyABKalmanProcessNoise = "kalman-process-noise" + DSettingsKeyABKalmanMeasurementNoise = "kalman-measurement-noise" + DSettingsKeyABKalmanWindowSize = "kalman-window-size" + customModeDelim = "+" monitorsIdDelimiter = "," defaultTemperatureMode = ColorTemperatureModeNone @@ -163,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 @@ -237,6 +272,33 @@ type Manager struct { xsManager xs.XSettings isVM bool + + // 自动亮度相关属性 + AutoBrightnessEnabled bool `prop:"access:rw"` + AutoBrightnessSupported bool `prop:"access:r"` + CurveMaxScale int32 `prop:"access:r"` + + // 自动亮度管理器 + autoBrightnessManager *AutoBrightnessManager + + // 统一亮度过渡管理器 + transitionManager *brightness.TransitionManager + + // 统一亮度过渡配置 + transitionMu sync.RWMutex + transitionEnabled bool + transitionDuration int // 毫秒 + transitionStepPercent float64 // 步进百分比 + transitionMinStepInterval int // 最小步进间隔(毫秒) + + // 背光曲线配置 + backlightCurveType string + backlightMinValue int32 + backlightMidValue int32 + + powerSaving bool + systemAdjustingTimer *time.Timer + systemAdjustingTimerMu sync.Mutex } type monitorSizeInfo struct { @@ -331,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() @@ -341,6 +405,7 @@ func newManager(service *dbusutil.Service) *Manager { if error != nil { logger.Warning("Cancel wm blackscreen failed", error) } + m.resumeAutoBrightness() } }) @@ -380,10 +445,14 @@ 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 } + m.resumeAutoBrightness() if m.newSysCfg != nil { m.handleSysConfigUpdated(m.newSysCfg) m.newSysCfg = nil @@ -402,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) } @@ -464,6 +535,54 @@ 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() + 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 } @@ -480,6 +599,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() { @@ -599,6 +726,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) @@ -618,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 } @@ -701,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) @@ -1194,6 +1433,12 @@ func (m *Manager) init() { // 埋点:启动时记录屏幕信息 m.logDisplayScreenEvent() + + go func() { + m.initTransitionConfig() + m.initTransitionManager() + m.initAutoBrightness() + }() } // 过滤掉部分模式,尽量不过滤掉 saveMode。 @@ -2268,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) @@ -3304,7 +3558,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 +3576,295 @@ 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") +} + +// 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.transitionEnabled = false + m.transitionDuration = 4000 + m.transitionStepPercent = 1.0 + m.transitionMinStepInterval = 100 + + // 从 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) + } + + 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") + } + + 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 通知手动亮度调节 +func (m *Manager) notifyManualBrightnessChange() { + if m.autoBrightnessManager != nil { + m.autoBrightnessManager.OnManualBrightnessChange() + } +} + +// setSystemAdjusting 设置系统调整标志(用于区分系统自动调整和用户手动调整) +func (m *Manager) isPowerSaving() bool { + m.PropsMu.RLock() + defer m.PropsMu.RUnlock() + return m.powerSaving +} + +// holdAutoBrightness 通知系统休眠 +func (m *Manager) holdAutoBrightness() { + if m.autoBrightnessManager != nil { + m.autoBrightnessManager.hold() + } +} + +// resumeAutoBrightness 通知系统唤醒 +func (m *Manager) resumeAutoBrightness() { + if m.autoBrightnessManager != nil { + m.autoBrightnessManager.resume() + } +} + +// 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() + } + 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 { + 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..303c2ada3 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 @@ -111,9 +111,30 @@ 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) } +// 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() @@ -159,6 +180,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) @@ -192,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, }) @@ -219,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 cc6b11fd5..3c4cdbd65 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 @@ -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,15 +80,16 @@ func (m *Manager) initLidSwitch() { m.builtinMonitor.lidClosed = closed } }) - 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/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 new file mode 100644 index 000000000..dcb6f6062 --- /dev/null +++ b/display1/sensor_proxy.go @@ -0,0 +1,425 @@ +// SPDX-FileCopyrightText: 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" + 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 { + sensorProxy sensorproxy.SensorProxy + dbusDaemon ofdbus.DBus + hasAmbientLight bool + claimed bool + + // 事件处理 + onServiceChange func(bool) + onLightLevelChange func(int) + + // 同步控制 + mutex sync.Mutex + + // 服务监控 + serviceAvailable bool + serviceSigLoop *dbusutil.SignalLoop // 服务监控的 SignalLoop + + // 错误处理 + maxRetries int + retryDelay time.Duration + + // 光感数据缓存 + lastLightLevel int // 最后一次光感值(lux) + lastLightLevelTime time.Time // 最后更新时间 +} + +// NewSensorProxyClient 创建新的传感器代理客户端 +func NewSensorProxyClient(proxy sensorproxy.SensorProxy, dbusDaemon ofdbus.DBus) *SensorProxyClient { + return &SensorProxyClient{ + sensorProxy: proxy, + dbusDaemon: dbusDaemon, + maxRetries: 3, + retryDelay: time.Millisecond * 500, + lastLightLevel: -1, + } +} + +// Connect 连接到SensorProxy服务 +func (c *SensorProxyClient) Connect(sigLoop *dbusutil.SignalLoop) error { + c.mutex.Lock() + defer c.mutex.Unlock() + if c.sensorProxy == nil { + return errors.New("SensorProxy is nil") + } + // 初始化信号处理 + c.sensorProxy.InitSignalExt(sigLoop, true) + // 检查服务是否可用(带重试机制) + 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") + } + // 订阅属性变化信号(LightLevel 等) + 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.claimed = false + } + // 停止服务监控的 SignalLoop + if c.serviceSigLoop != nil { + c.serviceSigLoop.Stop() + c.serviceSigLoop = nil + } + // 移除所有信号处理器 + c.sensorProxy.RemoveHandler(proxy.RemoveAllHandlers) + c.dbusDaemon.RemoveHandler(proxy.RemoveAllHandlers) + + c.serviceAvailable = false + c.hasAmbientLight = false + c.lastLightLevel = -1 + 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.lastLightLevel = -1 + return nil +} + +// GetCachedLightLevel 获取缓存的环境光强度(返回原始值,不滤波) +func (c *SensorProxyClient) GetCachedLightLevel() (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") + } + + needInit := c.lastLightLevel < 0 + c.mutex.Unlock() + + if needInit { + c.initializeCacheFromProperty() + } + + c.mutex.Lock() + defer c.mutex.Unlock() + + if c.lastLightLevel < 0 { + return 0, errors.New("no light level data available") + } + + logger.Debugf("[AutoBrightness::GetCachedLightLevel] Returning cached raw light level: %d lux", c.lastLightLevel) + 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") + } + c.mutex.Unlock() + + lightLevel, err := c.sensorProxy.LightLevel().Get(0) + if err != nil { + return 0, fmt.Errorf("failed to get LightLevel property: %w", err) + } + + return int(lightLevel), nil +} + +// HasAmbientLight 检查是否有环境光传感器 +func (c *SensorProxyClient) HasAmbientLight() (bool, error) { + c.mutex.Lock() + if !c.serviceAvailable { + c.mutex.Unlock() + return false, errors.New("SensorProxy service not available") + } + c.mutex.Unlock() + + return c.hasAmbientLightInternal() +} + +// SetServiceChangeCallback 设置服务状态变化回调 +func (c *SensorProxyClient) SetServiceChangeCallback(callback func(bool)) { + c.mutex.Lock() + defer c.mutex.Unlock() + 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() + 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.hasAmbientLightInternal() + 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 +} + +// hasAmbientLightInternal 内部检查环境光传感器 +func (c *SensorProxyClient) hasAmbientLightInternal() (bool, error) { + hasLight, err := c.sensorProxy.HasAmbientLight().Get(0) + if err != nil { + return false, err + } + return hasLight, nil +} + +// claimLightInternal 内部声明环境光传感器 +func (c *SensorProxyClient) claimLightInternal() error { + return c.sensorProxy.ClaimLight(0) +} + +// releaseLightInternal 内部释放环境光传感器 +func (c *SensorProxyClient) releaseLightInternal() error { + return c.sensorProxy.ReleaseLight(0) +} + +// startSignalWatching 通过 proxy.Object 订阅属性变化信号 +func (c *SensorProxyClient) startSignalWatching() { + _, 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)) + } + }) + if err != nil { + logger.Warning("[SensorProxy] Failed to connect PropertiesChanged signal:", err) + } +} + +// startServiceWatching 通过 ofdbus.DBus 订阅服务所有者变化信号 +func (c *SensorProxyClient) startServiceWatching() { + // 如果已有 sigLoop,先停止它 + if c.serviceSigLoop != nil { + c.serviceSigLoop.Stop() + c.serviceSigLoop = nil + } + + systemBus, err := dbus.SystemBus() + if err != nil { + logger.Warning("[SensorProxy] Failed to connect to system bus for service watching:", err) + return + } + 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 + } + 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) { + 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) + } +} + +// 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 + } + + 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/docs/auto_backlight/auto_brightness_design.md b/docs/auto_backlight/auto_brightness_design.md new file mode 100644 index 000000000..b0fef9632 --- /dev/null +++ b/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/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..0618bc460 --- /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,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/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 new file mode 100644 index 000000000..116bf1457 --- /dev/null +++ b/misc/dsg-configs/org.deepin.Display.AutoBrightness.json @@ -0,0 +1,114 @@ +{ + "magic": "dsg.config.meta", + "version": "1.0", + "contents": { + "enabled": { + "name": "Auto Brightness Enabled", + "description": "启用基于环境光传感器的自动亮度调节", + "value": false, + "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": 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, + "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": [] + }, + "kalman-process-noise": { + "name": "Kalman Filter Process Noise Q", + "description": "卡尔曼滤波器过程噪声协方差(Q),值越大对环境光变化响应越快", + "value": 0.8, + "visibility": "private", + "permissions": "readwrite", + "serial": 0, + "flags": [] + }, + "kalman-measurement-noise": { + "name": "Kalman Filter Measurement Noise R", + "description": "卡尔曼滤波器测量噪声协方差(R),值越大滤波效果越强", + "value": 0.05, + "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": [] + }, + "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": 3, + "visibility": "private", + "permissions": "readwrite", + "serial": 0, + "flags": [] + } + } +} diff --git a/misc/dsg-configs/org.deepin.Display.json b/misc/dsg-configs/org.deepin.Display.json index 79d9ead62..df77f781c 100644 --- a/misc/dsg-configs/org.deepin.Display.json +++ b/misc/dsg-configs/org.deepin.Display.json @@ -177,6 +177,116 @@ "description[zh_CN]": "自定义显示模式类型(范围:1-2)", "permissions": "readwrite", "visibility": "public" + }, + "transition-enabled": { + "value": false, + "serial": 0, + "flags": [], + "name": "Brightness Transition Enabled", + "description": "是否启用亮度渐变效果", + "permissions": "readwrite", + "visibility": "private" + }, + "transition-duration": { + "value": 4000, + "serial": 0, + "flags": [], + "name": "Brightness Transition Duration", + "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" + }, + "transition-step-interval": { + "value": 100, + "serial": 0, + "flags": [], + "name": "Brightness Transition Step Interval", + "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" } } }