1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-06 05:25:41 -05:00
Files
DankMaterialShell/core/internal/server/brightness/sysfs.go

264 lines
6.0 KiB
Go

package brightness
import (
"fmt"
"math"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
)
func NewSysfsBackend() (*SysfsBackend, error) {
b := &SysfsBackend{
basePath: "/sys/class",
classes: []string{"backlight", "leds"},
}
if err := b.scanDevices(); err != nil {
return nil, err
}
return b, nil
}
func (b *SysfsBackend) scanDevices() error {
for _, class := range b.classes {
classPath := filepath.Join(b.basePath, class)
entries, err := os.ReadDir(classPath)
if err != nil {
if os.IsNotExist(err) {
continue
}
return fmt.Errorf("read %s: %w", classPath, err)
}
for _, entry := range entries {
devicePath := filepath.Join(classPath, entry.Name())
stat, err := os.Stat(devicePath)
if err != nil || !stat.IsDir() {
continue
}
maxPath := filepath.Join(devicePath, "max_brightness")
maxData, err := os.ReadFile(maxPath)
if err != nil {
log.Debugf("skip %s/%s: no max_brightness", class, entry.Name())
continue
}
maxBrightness, err := strconv.Atoi(strings.TrimSpace(string(maxData)))
if err != nil || maxBrightness <= 0 {
log.Debugf("skip %s/%s: invalid max_brightness", class, entry.Name())
continue
}
deviceClass := ClassBacklight
minValue := 1
if class == "leds" {
deviceClass = ClassLED
minValue = 0
}
deviceID := fmt.Sprintf("%s:%s", class, entry.Name())
b.deviceCache.Store(deviceID, &sysfsDevice{
class: deviceClass,
id: deviceID,
name: entry.Name(),
maxBrightness: maxBrightness,
minValue: minValue,
})
log.Debugf("found %s device: %s (max=%d)", class, entry.Name(), maxBrightness)
}
}
return nil
}
func shouldSuppressDevice(name string) bool {
if strings.HasSuffix(name, "::lan") {
return true
}
keyboardLEDs := []string{
"::scrolllock",
"::capslock",
"::numlock",
"::kana",
"::compose",
}
for _, suffix := range keyboardLEDs {
if strings.HasSuffix(name, suffix) {
return true
}
}
return false
}
func (b *SysfsBackend) GetDevices() ([]Device, error) {
devices := make([]Device, 0)
b.deviceCache.Range(func(key string, dev *sysfsDevice) bool {
if shouldSuppressDevice(dev.name) {
return true
}
parts := strings.SplitN(dev.id, ":", 2)
if len(parts) != 2 {
return true
}
class := parts[0]
name := parts[1]
devicePath := filepath.Join(b.basePath, class, name)
brightnessPath := filepath.Join(devicePath, "brightness")
brightnessData, err := os.ReadFile(brightnessPath)
if err != nil {
log.Debugf("failed to read brightness for %s: %v", dev.id, err)
return true
}
current, err := strconv.Atoi(strings.TrimSpace(string(brightnessData)))
if err != nil {
log.Debugf("failed to parse brightness for %s: %v", dev.id, err)
return true
}
percent := b.ValueToPercent(current, dev, false)
devices = append(devices, Device{
Class: dev.class,
ID: dev.id,
Name: dev.name,
Current: current,
Max: dev.maxBrightness,
CurrentPercent: percent,
Backend: "sysfs",
})
return true
})
return devices, nil
}
func (b *SysfsBackend) GetDevice(id string) (*sysfsDevice, error) {
dev, ok := b.deviceCache.Load(id)
if !ok {
return nil, fmt.Errorf("device not found: %s", id)
}
return dev, nil
}
func (b *SysfsBackend) SetBrightness(id string, percent int, exponential bool) error {
return b.SetBrightnessWithExponent(id, percent, exponential, 1.2)
}
func (b *SysfsBackend) SetBrightnessWithExponent(id string, percent int, exponential bool, exponent float64) error {
dev, err := b.GetDevice(id)
if err != nil {
return err
}
if percent < 0 {
return fmt.Errorf("percent out of range: %d", percent)
}
value := b.PercentToValueWithExponent(percent, dev, exponential, exponent)
parts := strings.SplitN(id, ":", 2)
if len(parts) != 2 {
return fmt.Errorf("invalid device id: %s", id)
}
class := parts[0]
name := parts[1]
devicePath := filepath.Join(b.basePath, class, name)
brightnessPath := filepath.Join(devicePath, "brightness")
data := []byte(fmt.Sprintf("%d", value))
if err := os.WriteFile(brightnessPath, data, 0644); err != nil {
return fmt.Errorf("write brightness: %w", err)
}
log.Debugf("set %s to %d%% (%d/%d) via direct sysfs", id, percent, value, dev.maxBrightness)
return nil
}
func (b *SysfsBackend) PercentToValue(percent int, dev *sysfsDevice, exponential bool) int {
return b.PercentToValueWithExponent(percent, dev, exponential, 1.2)
}
func (b *SysfsBackend) PercentToValueWithExponent(percent int, dev *sysfsDevice, exponential bool, exponent float64) int {
if percent == 0 {
return dev.minValue
}
usableRange := dev.maxBrightness - dev.minValue
var value int
if exponential {
normalizedPercent := float64(percent-1) / 99.0
hardwarePercent := math.Pow(normalizedPercent, exponent)
value = dev.minValue + int(math.Round(hardwarePercent*float64(usableRange)))
} else {
value = dev.minValue + ((percent - 1) * usableRange / 99)
}
if value < dev.minValue {
value = dev.minValue
}
if value > dev.maxBrightness {
value = dev.maxBrightness
}
return value
}
func (b *SysfsBackend) ValueToPercent(value int, dev *sysfsDevice, exponential bool) int {
return b.ValueToPercentWithExponent(value, dev, exponential, 1.2)
}
func (b *SysfsBackend) ValueToPercentWithExponent(value int, dev *sysfsDevice, exponential bool, exponent float64) int {
if value <= dev.minValue {
if dev.minValue == 0 && value == 0 {
return 0
}
return 1
}
usableRange := dev.maxBrightness - dev.minValue
if usableRange == 0 {
return 100
}
var percent int
if exponential {
hardwarePercent := float64(value-dev.minValue) / float64(usableRange)
normalizedPercent := math.Pow(hardwarePercent, 1.0/exponent)
percent = 1 + int(math.Round(normalizedPercent*99.0))
} else {
percent = 1 + int(math.Round(float64(value-dev.minValue)*99.0/float64(usableRange)))
}
if percent > 100 {
percent = 100
}
if percent < 1 {
percent = 1
}
return percent
}