mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-13 00:42:49 -05:00
switch hto monorepo structure
This commit is contained in:
272
backend/internal/server/brightness/sysfs.go
Normal file
272
backend/internal/server/brightness/sysfs.go
Normal file
@@ -0,0 +1,272 @@
|
||||
package brightness
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/backend/internal/log"
|
||||
)
|
||||
|
||||
func NewSysfsBackend() (*SysfsBackend, error) {
|
||||
b := &SysfsBackend{
|
||||
basePath: "/sys/class",
|
||||
classes: []string{"backlight", "leds"},
|
||||
deviceCache: make(map[string]*sysfsDevice),
|
||||
}
|
||||
|
||||
if err := b.scanDevices(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (b *SysfsBackend) scanDevices() error {
|
||||
b.deviceCacheMutex.Lock()
|
||||
defer b.deviceCacheMutex.Unlock()
|
||||
|
||||
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[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) {
|
||||
b.deviceCacheMutex.RLock()
|
||||
defer b.deviceCacheMutex.RUnlock()
|
||||
|
||||
devices := make([]Device, 0, len(b.deviceCache))
|
||||
|
||||
for _, dev := range b.deviceCache {
|
||||
if shouldSuppressDevice(dev.name) {
|
||||
continue
|
||||
}
|
||||
|
||||
parts := strings.SplitN(dev.id, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
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)
|
||||
continue
|
||||
}
|
||||
|
||||
current, err := strconv.Atoi(strings.TrimSpace(string(brightnessData)))
|
||||
if err != nil {
|
||||
log.Debugf("failed to parse brightness for %s: %v", dev.id, err)
|
||||
continue
|
||||
}
|
||||
|
||||
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 devices, nil
|
||||
}
|
||||
|
||||
func (b *SysfsBackend) GetDevice(id string) (*sysfsDevice, error) {
|
||||
b.deviceCacheMutex.RLock()
|
||||
defer b.deviceCacheMutex.RUnlock()
|
||||
|
||||
dev, ok := b.deviceCache[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
|
||||
}
|
||||
Reference in New Issue
Block a user