summaryrefslogtreecommitdiff
path: root/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness
diff options
context:
space:
mode:
authorNippy <nippy@rp1.hu>2026-05-09 13:33:09 +0200
committerNippy <nippy@rp1.hu>2026-05-09 13:33:09 +0200
commita2b924d7e9492b978ad6dc33b6c1ffcb304b4bc5 (patch)
tree1a8769217f84bfe9d6216fafaadaf8cdd876d457 /raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness
parent34b9c34f982b2596cfa9e368a2d7f74e9a17477c (diff)
downloadRaveOS-PKGBUILD-a2b924d7e9492b978ad6dc33b6c1ffcb304b4bc5.tar.gz
RaveOS-PKGBUILD-a2b924d7e9492b978ad6dc33b6c1ffcb304b4bc5.zip
raveos update
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness')
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/ddc.go510
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/ddc_filter.go135
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/ddc_filter_test.go122
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/ddc_test.go135
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/handlers.go124
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/logind.go66
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/logind_test.go95
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/manager.go389
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/manager_test.go11
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/sysfs.go267
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/sysfs_logind_test.go278
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/sysfs_test.go180
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/types.go184
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/udev.go237
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/udev_test.go260
15 files changed, 0 insertions, 2993 deletions
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/ddc.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/ddc.go
deleted file mode 100644
index 00cf2d9..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/ddc.go
+++ /dev/null
@@ -1,510 +0,0 @@
-package brightness
-
-import (
- "encoding/binary"
- "fmt"
- "math"
- "os"
- "strings"
- "syscall"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "golang.org/x/sys/unix"
-)
-
-const (
- I2C_SLAVE = 0x0703
- DDCCI_ADDR = 0x37
- DDCCI_VCP_GET = 0x01
- DDCCI_VCP_SET = 0x03
- VCP_BRIGHTNESS = 0x10
- DDC_SOURCE_ADDR = 0x51
-)
-
-func NewDDCBackend() (*DDCBackend, error) {
- b := &DDCBackend{
- scanInterval: 30 * time.Second,
- debounceTimers: make(map[string]*time.Timer),
- debouncePending: make(map[string]ddcPendingSet),
- }
-
- if err := b.scanI2CDevices(); err != nil {
- return nil, err
- }
-
- return b, nil
-}
-
-func (b *DDCBackend) scanI2CDevices() error {
- return b.scanI2CDevicesInternal(false)
-}
-
-func (b *DDCBackend) ForceRescan() error {
- return b.scanI2CDevicesInternal(true)
-}
-
-func (b *DDCBackend) scanI2CDevicesInternal(force bool) error {
- b.scanMutex.Lock()
- defer b.scanMutex.Unlock()
-
- if !force && time.Since(b.lastScan) < b.scanInterval {
- return nil
- }
-
- activeBuses := make(map[int]bool)
-
- for i := 0; i < 32; i++ {
- busPath := fmt.Sprintf("/dev/i2c-%d", i)
- if _, err := os.Stat(busPath); os.IsNotExist(err) {
- continue
- }
-
- if isIgnorableI2CBus(i) {
- log.Debugf("Skipping ignorable i2c-%d", i)
- continue
- }
-
- activeBuses[i] = true
- id := fmt.Sprintf("ddc:i2c-%d", i)
-
- dev, err := b.probeDDCDevice(i)
- if err != nil || dev == nil {
- continue
- }
-
- dev.id = id
- b.devices.Store(id, dev)
- log.Debugf("found DDC device on i2c-%d", i)
- }
-
- b.devices.Range(func(id string, dev *ddcDevice) bool {
- if !activeBuses[dev.bus] {
- b.devices.Delete(id)
- log.Debugf("removed DDC device %s (bus no longer exists)", id)
- }
- return true
- })
-
- b.lastScan = time.Now()
-
- return nil
-}
-
-func (b *DDCBackend) probeDDCDevice(bus int) (*ddcDevice, error) {
- busPath := fmt.Sprintf("/dev/i2c-%d", bus)
-
- fd, err := syscall.Open(busPath, syscall.O_RDWR, 0)
- if err != nil {
- return nil, err
- }
- defer syscall.Close(fd)
-
- if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), I2C_SLAVE, uintptr(DDCCI_ADDR)); errno != 0 {
- return nil, errno
- }
-
- dummy := make([]byte, 32)
- syscall.Read(fd, dummy) //nolint:errcheck
-
- writebuf := []byte{0x00}
- n, err := syscall.Write(fd, writebuf)
- if err == nil && n == len(writebuf) {
- name := b.getDDCName(bus)
- dev := &ddcDevice{
- bus: bus,
- addr: DDCCI_ADDR,
- name: name,
- }
- b.readInitialBrightness(fd, dev)
- return dev, nil
- }
-
- readbuf := make([]byte, 4)
- n, err = syscall.Read(fd, readbuf)
- if err != nil || n == 0 {
- return nil, fmt.Errorf("x37 unresponsive")
- }
-
- name := b.getDDCName(bus)
-
- dev := &ddcDevice{
- bus: bus,
- addr: DDCCI_ADDR,
- name: name,
- }
- b.readInitialBrightness(fd, dev)
- return dev, nil
-}
-
-func (b *DDCBackend) getDDCName(bus int) string {
- sysfsPath := fmt.Sprintf("/sys/class/i2c-adapter/i2c-%d/name", bus)
- data, err := os.ReadFile(sysfsPath)
- if err != nil {
- return fmt.Sprintf("I2C-%d", bus)
- }
-
- name := strings.TrimSpace(string(data))
- if name == "" {
- name = fmt.Sprintf("I2C-%d", bus)
- }
-
- return name
-}
-
-func (b *DDCBackend) readInitialBrightness(fd int, dev *ddcDevice) {
- cap, err := b.getVCPFeature(fd, VCP_BRIGHTNESS)
- if err != nil {
- log.Debugf("failed to read initial brightness for %s: %v", dev.name, err)
- return
- }
-
- dev.max = cap.max
- dev.lastBrightness = cap.current
- log.Debugf("initialized %s with brightness %d/%d", dev.name, cap.current, cap.max)
-}
-
-func (b *DDCBackend) GetDevices() ([]Device, error) {
- if err := b.scanI2CDevices(); err != nil {
- log.Debugf("DDC scan error: %v", err)
- }
-
- devices := make([]Device, 0)
-
- b.devices.Range(func(id string, dev *ddcDevice) bool {
- devices = append(devices, Device{
- Class: ClassDDC,
- ID: id,
- Name: dev.name,
- Current: dev.lastBrightness,
- Max: dev.max,
- CurrentPercent: dev.lastBrightness,
- Backend: "ddc",
- })
- return true
- })
-
- return devices, nil
-}
-
-func (b *DDCBackend) SetBrightness(id string, value int, exponential bool, callback func()) error {
- return b.SetBrightnessWithExponent(id, value, exponential, 1.2, callback)
-}
-
-func (b *DDCBackend) SetBrightnessWithExponent(id string, value int, exponential bool, exponent float64, callback func()) error {
- _, ok := b.devices.Load(id)
-
- if !ok {
- if err := b.scanI2CDevicesInternal(true); err != nil {
- log.Debugf("rescan failed for %s: %v", id, err)
- }
- _, ok = b.devices.Load(id)
- }
-
- if !ok {
- return fmt.Errorf("device not found: %s", id)
- }
-
- if value < 0 {
- return fmt.Errorf("value out of range: %d", value)
- }
-
- b.debounceMutex.Lock()
- b.debouncePending[id] = ddcPendingSet{
- percent: value,
- callback: callback,
- }
-
- if timer, exists := b.debounceTimers[id]; exists {
- timer.Reset(200 * time.Millisecond)
- } else {
- b.debounceWg.Add(1)
- b.debounceTimers[id] = time.AfterFunc(200*time.Millisecond, func() {
- defer b.debounceWg.Done()
- b.debounceMutex.Lock()
- pending, exists := b.debouncePending[id]
- if exists {
- delete(b.debouncePending, id)
- }
- b.debounceMutex.Unlock()
-
- if !exists {
- return
- }
-
- err := b.setBrightnessImmediateWithExponent(id, pending.percent)
- if err != nil {
- log.Debugf("Failed to set brightness for %s: %v", id, err)
- }
-
- if pending.callback != nil {
- pending.callback()
- }
- })
- }
- b.debounceMutex.Unlock()
-
- return nil
-}
-
-func (b *DDCBackend) setBrightnessImmediateWithExponent(id string, value int) error {
- dev, ok := b.devices.Load(id)
-
- if !ok {
- if err := b.scanI2CDevicesInternal(true); err != nil {
- log.Debugf("rescan failed for %s: %v", id, err)
- }
- dev, ok = b.devices.Load(id)
- }
-
- if !ok {
- return fmt.Errorf("device not found: %s", id)
- }
-
- busPath := fmt.Sprintf("/dev/i2c-%d", dev.bus)
-
- if _, err := os.Stat(busPath); os.IsNotExist(err) {
- b.devices.Delete(id)
- log.Debugf("removed stale DDC device %s (bus no longer exists)", id)
- return fmt.Errorf("device disconnected: %s", id)
- }
-
- fd, err := syscall.Open(busPath, syscall.O_RDWR, 0)
- if err != nil {
- b.devices.Delete(id)
- log.Debugf("removed DDC device %s (open failed: %v)", id, err)
- return fmt.Errorf("open i2c device: %w", err)
- }
- defer syscall.Close(fd)
-
- if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), I2C_SLAVE, uintptr(dev.addr)); errno != 0 {
- return fmt.Errorf("set i2c slave addr: %w", errno)
- }
-
- max := dev.max
- if max == 0 {
- cap, err := b.getVCPFeature(fd, VCP_BRIGHTNESS)
- if err != nil {
- return fmt.Errorf("get current capability: %w", err)
- }
- max = cap.max
- dev.max = max
- b.devices.Store(id, dev)
- }
-
- if err := b.setVCPFeature(fd, VCP_BRIGHTNESS, value); err != nil {
- return fmt.Errorf("set vcp feature: %w", err)
- }
-
- log.Debugf("set %s to %d/%d", id, value, max)
-
- dev.max = max
- dev.lastBrightness = value
- b.devices.Store(id, dev)
-
- return nil
-}
-
-func (b *DDCBackend) getVCPFeature(fd int, vcp byte) (*ddcCapability, error) {
- for flushTry := 0; flushTry < 3; flushTry++ {
- dummy := make([]byte, 32)
- n, _ := syscall.Read(fd, dummy)
- if n == 0 {
- break
- }
- time.Sleep(20 * time.Millisecond)
- }
-
- data := []byte{
- DDCCI_VCP_GET,
- vcp,
- }
-
- payload := []byte{
- DDC_SOURCE_ADDR,
- byte(len(data)) | 0x80,
- }
- payload = append(payload, data...)
- payload = append(payload, ddcciChecksum(payload))
-
- n, err := syscall.Write(fd, payload)
- if err != nil || n != len(payload) {
- return nil, fmt.Errorf("write i2c: %w", err)
- }
-
- time.Sleep(50 * time.Millisecond)
-
- pollFds := []unix.PollFd{
- {
- Fd: int32(fd),
- Events: unix.POLLIN,
- },
- }
-
- pollTimeout := 200
- pollResult, err := unix.Poll(pollFds, pollTimeout)
- if err != nil {
- return nil, fmt.Errorf("poll i2c: %w", err)
- }
- if pollResult == 0 {
- return nil, fmt.Errorf("poll timeout after %dms", pollTimeout)
- }
- if pollFds[0].Revents&unix.POLLIN == 0 {
- return nil, fmt.Errorf("poll returned but POLLIN not set")
- }
-
- response := make([]byte, 12)
- n, err = syscall.Read(fd, response)
- if err != nil || n < 8 {
- return nil, fmt.Errorf("read i2c: %w", err)
- }
-
- if response[0] != 0x6E || response[2] != 0x02 {
- return nil, fmt.Errorf("invalid ddc response")
- }
-
- resultCode := response[3]
- if resultCode != 0x00 {
- return nil, fmt.Errorf("vcp feature not supported")
- }
-
- responseVCP := response[4]
- if responseVCP != vcp {
- return nil, fmt.Errorf("vcp mismatch: wanted 0x%02x, got 0x%02x", vcp, responseVCP)
- }
-
- maxHigh := response[6]
- maxLow := response[7]
- currentHigh := response[8]
- currentLow := response[9]
-
- max := int(binary.BigEndian.Uint16([]byte{maxHigh, maxLow}))
- current := int(binary.BigEndian.Uint16([]byte{currentHigh, currentLow}))
-
- return &ddcCapability{
- vcp: vcp,
- max: max,
- current: current,
- }, nil
-}
-
-func ddcciChecksum(payload []byte) byte {
- sum := byte(0x6E)
- for _, b := range payload {
- sum ^= b
- }
- return sum
-}
-
-func (b *DDCBackend) setVCPFeature(fd int, vcp byte, value int) error {
- data := []byte{
- DDCCI_VCP_SET,
- vcp,
- byte(value >> 8),
- byte(value & 0xFF),
- }
-
- payload := []byte{
- DDC_SOURCE_ADDR,
- byte(len(data)) | 0x80,
- }
- payload = append(payload, data...)
- payload = append(payload, ddcciChecksum(payload))
-
- if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), I2C_SLAVE, uintptr(DDCCI_ADDR)); errno != 0 {
- return fmt.Errorf("set i2c slave for write: %w", errno)
- }
-
- n, err := syscall.Write(fd, payload)
- if err != nil || n != len(payload) {
- return fmt.Errorf("write i2c: wrote %d/%d: %w", n, len(payload), err)
- }
-
- time.Sleep(50 * time.Millisecond)
-
- return nil
-}
-
-func (b *DDCBackend) percentToValue(percent int, max int, exponential bool) int {
- const minValue = 1
-
- if percent == 0 {
- return minValue
- }
-
- usableRange := max - minValue
- var value int
-
- if exponential {
- const exponent = 2.0
- normalizedPercent := float64(percent) / 100.0
- hardwarePercent := math.Pow(normalizedPercent, 1.0/exponent)
- value = minValue + int(math.Round(hardwarePercent*float64(usableRange)))
- } else {
- value = minValue + ((percent - 1) * usableRange / 99)
- }
-
- if value < minValue {
- value = minValue
- }
- if value > max {
- value = max
- }
-
- return value
-}
-
-func (b *DDCBackend) valueToPercent(value int, max int, exponential bool) int {
- const minValue = 1
-
- if max == 0 {
- return 0
- }
-
- if value <= minValue {
- return 1
- }
-
- usableRange := max - minValue
- if usableRange == 0 {
- return 100
- }
-
- var percent int
-
- if exponential {
- const exponent = 2.0
- linearPercent := 1 + ((value - minValue) * 99 / usableRange)
- normalizedLinear := float64(linearPercent) / 100.0
- expPercent := math.Pow(normalizedLinear, exponent)
- percent = int(math.Round(expPercent * 100.0))
- } else {
- percent = 1 + ((value - minValue) * 99 / usableRange)
- }
-
- if percent > 100 {
- percent = 100
- }
- if percent < 1 {
- percent = 1
- }
-
- return percent
-}
-
-func (b *DDCBackend) WaitPending() {
- done := make(chan struct{})
- go func() {
- b.debounceWg.Wait()
- close(done)
- }()
-
- select {
- case <-done:
- case <-time.After(5 * time.Second):
- log.Debug("WaitPending timed out waiting for DDC writes")
- }
-}
-
-func (b *DDCBackend) Close() {
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/ddc_filter.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/ddc_filter.go
deleted file mode 100644
index 12fc535..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/ddc_filter.go
+++ /dev/null
@@ -1,135 +0,0 @@
-package brightness
-
-import (
- "fmt"
- "os"
- "path/filepath"
- "strconv"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
-)
-
-// isIgnorableI2CBus checks if an I2C bus should be skipped during DDC probing.
-// Based on ddcutil's sysfs_is_ignorable_i2c_device() (sysfs_base.c:1441)
-func isIgnorableI2CBus(busno int) bool {
- name := getI2CDeviceSysfsName(busno)
- driver := getI2CSysfsDriver(busno)
-
- if name != "" && isIgnorableI2CDeviceName(name, driver) {
- log.Debugf("i2c-%d: ignoring '%s' (driver: %s)", busno, name, driver)
- return true
- }
-
- // Only probe display adapters (0x03xxxx) and docking stations (0x0axxxx)
- class := getI2CDeviceSysfsClass(busno)
- if class != 0 {
- classHigh := class & 0xFFFF0000
- ignorable := (classHigh != 0x030000 && classHigh != 0x0A0000)
- if ignorable {
- log.Debugf("i2c-%d: ignoring class 0x%08x", busno, class)
- }
- return ignorable
- }
-
- return false
-}
-
-// Based on ddcutil's ignorable_i2c_device_sysfs_name() (sysfs_base.c:1408)
-func isIgnorableI2CDeviceName(name, driver string) bool {
- ignorablePrefixes := []string{
- "SMBus",
- "Synopsys DesignWare",
- "soc:i2cdsi",
- "smu",
- "mac-io",
- "u4",
- "AMDGPU SMU", // AMD Navi2+ - probing hangs GPU
- }
-
- for _, prefix := range ignorablePrefixes {
- if strings.HasPrefix(name, prefix) {
- return true
- }
- }
-
- // nouveau driver: only nvkm-* buses are valid
- if driver == "nouveau" && !strings.HasPrefix(name, "nvkm-") {
- return true
- }
-
- return false
-}
-
-// Based on ddcutil's get_i2c_device_sysfs_name() (sysfs_base.c:1175)
-func getI2CDeviceSysfsName(busno int) string {
- path := fmt.Sprintf("/sys/bus/i2c/devices/i2c-%d/name", busno)
- data, err := os.ReadFile(path)
- if err != nil {
- return ""
- }
- return strings.TrimSpace(string(data))
-}
-
-// Based on ddcutil's get_i2c_device_sysfs_class() (sysfs_base.c:1380)
-func getI2CDeviceSysfsClass(busno int) uint32 {
- classPath := fmt.Sprintf("/sys/bus/i2c/devices/i2c-%d/device/class", busno)
- data, err := os.ReadFile(classPath)
- if err != nil {
- classPath = fmt.Sprintf("/sys/bus/i2c/devices/i2c-%d/device/device/device/class", busno)
- data, err = os.ReadFile(classPath)
- if err != nil {
- return 0
- }
- }
-
- classStr := strings.TrimSpace(string(data))
- classStr = strings.TrimPrefix(classStr, "0x")
-
- class, err := strconv.ParseUint(classStr, 16, 32)
- if err != nil {
- return 0
- }
-
- return uint32(class)
-}
-
-// Based on ddcutil's get_i2c_sysfs_driver_by_busno() (sysfs_base.c:1284)
-func getI2CSysfsDriver(busno int) string {
- devicePath := fmt.Sprintf("/sys/bus/i2c/devices/i2c-%d", busno)
- adapterPath, err := findI2CAdapter(devicePath)
- if err != nil {
- return ""
- }
-
- driverLink := filepath.Join(adapterPath, "driver")
- target, err := os.Readlink(driverLink)
- if err != nil {
- return ""
- }
-
- return filepath.Base(target)
-}
-
-func findI2CAdapter(devicePath string) (string, error) {
- currentPath := devicePath
-
- for depth := 0; depth < 10; depth++ {
- if _, err := os.Stat(filepath.Join(currentPath, "name")); err == nil {
- return currentPath, nil
- }
-
- deviceLink := filepath.Join(currentPath, "device")
- target, err := os.Readlink(deviceLink)
- if err != nil {
- break
- }
-
- if !filepath.IsAbs(target) {
- target = filepath.Join(filepath.Dir(currentPath), target)
- }
- currentPath = filepath.Clean(target)
- }
-
- return "", fmt.Errorf("could not find adapter for %s", devicePath)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/ddc_filter_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/ddc_filter_test.go
deleted file mode 100644
index dc3751f..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/ddc_filter_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package brightness
-
-import (
- "testing"
-)
-
-func TestIsIgnorableI2CDeviceName(t *testing.T) {
- tests := []struct {
- name string
- deviceName string
- driver string
- want bool
- }{
- {
- name: "AMDGPU SMU should be ignored",
- deviceName: "AMDGPU SMU",
- driver: "amdgpu",
- want: true,
- },
- {
- name: "SMBus should be ignored",
- deviceName: "SMBus I801 adapter",
- driver: "",
- want: true,
- },
- {
- name: "Synopsys DesignWare should be ignored",
- deviceName: "Synopsys DesignWare I2C adapter",
- driver: "",
- want: true,
- },
- {
- name: "smu prefix should be ignored (Mac G5)",
- deviceName: "smu-i2c-controller",
- driver: "",
- want: true,
- },
- {
- name: "Regular NVIDIA DDC should not be ignored",
- deviceName: "NVIDIA i2c adapter 1",
- driver: "nvidia",
- want: false,
- },
- {
- name: "nouveau nvkm bus should not be ignored",
- deviceName: "nvkm-0000:01:00.0-bus-0000",
- driver: "nouveau",
- want: false,
- },
- {
- name: "nouveau non-nvkm bus should be ignored",
- deviceName: "nouveau-other-bus",
- driver: "nouveau",
- want: true,
- },
- {
- name: "Regular AMD display adapter should not be ignored",
- deviceName: "AMDGPU DM i2c hw bus 0",
- driver: "amdgpu",
- want: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got := isIgnorableI2CDeviceName(tt.deviceName, tt.driver)
- if got != tt.want {
- t.Errorf("isIgnorableI2CDeviceName(%q, %q) = %v, want %v",
- tt.deviceName, tt.driver, got, tt.want)
- }
- })
- }
-}
-
-func TestClassFiltering(t *testing.T) {
- tests := []struct {
- name string
- class uint32
- want bool
- }{
- {
- name: "Display adapter class should not be ignored",
- class: 0x030000,
- want: false,
- },
- {
- name: "Docking station class should not be ignored",
- class: 0x0a0000,
- want: false,
- },
- {
- name: "Display adapter with subclass should not be ignored",
- class: 0x030001,
- want: false,
- },
- {
- name: "SMBus class should be ignored",
- class: 0x0c0500,
- want: true,
- },
- {
- name: "Bridge class should be ignored",
- class: 0x060400,
- want: true,
- },
- {
- name: "Generic system peripheral should be ignored",
- class: 0x088000,
- want: true,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- classHigh := tt.class & 0xFFFF0000
- ignorable := (classHigh != 0x030000 && classHigh != 0x0A0000)
- if ignorable != tt.want {
- t.Errorf("class 0x%08x: ignorable = %v, want %v", tt.class, ignorable, tt.want)
- }
- })
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/ddc_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/ddc_test.go
deleted file mode 100644
index 7f85510..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/ddc_test.go
+++ /dev/null
@@ -1,135 +0,0 @@
-package brightness
-
-import (
- "testing"
-)
-
-func TestDDCBackend_PercentConversions(t *testing.T) {
- tests := []struct {
- name string
- max int
- percent int
- wantValue int
- }{
- {
- name: "0% should map to minValue=1",
- max: 100,
- percent: 0,
- wantValue: 1,
- },
- {
- name: "1% should be 1",
- max: 100,
- percent: 1,
- wantValue: 1,
- },
- {
- name: "50% should be ~50",
- max: 100,
- percent: 50,
- wantValue: 50,
- },
- {
- name: "100% should be max",
- max: 100,
- percent: 100,
- wantValue: 100,
- },
- }
-
- b := &DDCBackend{}
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got := b.percentToValue(tt.percent, tt.max, false)
- diff := got - tt.wantValue
- if diff < 0 {
- diff = -diff
- }
- if diff > 1 {
- t.Errorf("percentToValue() = %v, want %v (±1)", got, tt.wantValue)
- }
- })
- }
-}
-
-func TestDDCBackend_ValueToPercent(t *testing.T) {
- tests := []struct {
- name string
- max int
- value int
- wantPercent int
- tolerance int
- }{
- {
- name: "zero value should be 1%",
- max: 100,
- value: 0,
- wantPercent: 1,
- tolerance: 0,
- },
- {
- name: "min value should be 1%",
- max: 100,
- value: 1,
- wantPercent: 1,
- tolerance: 0,
- },
- {
- name: "mid value should be ~50%",
- max: 100,
- value: 50,
- wantPercent: 50,
- tolerance: 2,
- },
- {
- name: "max value should be 100%",
- max: 100,
- value: 100,
- wantPercent: 100,
- tolerance: 0,
- },
- }
-
- b := &DDCBackend{}
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got := b.valueToPercent(tt.value, tt.max, false)
- diff := got - tt.wantPercent
- if diff < 0 {
- diff = -diff
- }
- if diff > tt.tolerance {
- t.Errorf("valueToPercent() = %v, want %v (±%d)", got, tt.wantPercent, tt.tolerance)
- }
- })
- }
-}
-
-func TestDDCBackend_RoundTrip(t *testing.T) {
- b := &DDCBackend{}
-
- tests := []struct {
- name string
- max int
- percent int
- }{
- {"1%", 100, 1},
- {"25%", 100, 25},
- {"50%", 100, 50},
- {"75%", 100, 75},
- {"100%", 100, 100},
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- value := b.percentToValue(tt.percent, tt.max, false)
- gotPercent := b.valueToPercent(value, tt.max, false)
-
- if diff := tt.percent - gotPercent; diff < -1 || diff > 1 {
- t.Errorf("round trip failed: wanted %d%%, got %d%% (value=%d)", tt.percent, gotPercent, value)
- }
- })
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/handlers.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/handlers.go
deleted file mode 100644
index c71163e..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/handlers.go
+++ /dev/null
@@ -1,124 +0,0 @@
-package brightness
-
-import (
- "encoding/json"
- "fmt"
- "net"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/params"
-)
-
-func HandleRequest(conn net.Conn, req models.Request, m *Manager) {
- switch req.Method {
- case "brightness.getState":
- handleGetState(conn, req, m)
- case "brightness.setBrightness":
- handleSetBrightness(conn, req, m)
- case "brightness.increment":
- handleIncrement(conn, req, m)
- case "brightness.decrement":
- handleDecrement(conn, req, m)
- case "brightness.rescan":
- handleRescan(conn, req, m)
- case "brightness.subscribe":
- handleSubscribe(conn, req, m)
- default:
- models.RespondError(conn, req.ID, "unknown method: "+req.Method)
- }
-}
-
-func handleGetState(conn net.Conn, req models.Request, m *Manager) {
- models.Respond(conn, req.ID, m.GetState())
-}
-
-func handleSetBrightness(conn net.Conn, req models.Request, m *Manager) {
- device, err := params.String(req.Params, "device")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- percent, err := params.Int(req.Params, "percent")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- exponential := params.BoolOpt(req.Params, "exponential", false)
- exponent := params.FloatOpt(req.Params, "exponent", 1.2)
-
- if err := m.SetBrightnessWithExponent(device, percent, exponential, exponent); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, m.GetState())
-}
-
-func handleIncrement(conn net.Conn, req models.Request, m *Manager) {
- device, err := params.String(req.Params, "device")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- step := params.IntOpt(req.Params, "step", 10)
- exponential := params.BoolOpt(req.Params, "exponential", false)
- exponent := params.FloatOpt(req.Params, "exponent", 1.2)
-
- if err := m.IncrementBrightnessWithExponent(device, step, exponential, exponent); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, m.GetState())
-}
-
-func handleDecrement(conn net.Conn, req models.Request, m *Manager) {
- device, err := params.String(req.Params, "device")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- step := params.IntOpt(req.Params, "step", 10)
- exponential := params.BoolOpt(req.Params, "exponential", false)
- exponent := params.FloatOpt(req.Params, "exponent", 1.2)
-
- if err := m.IncrementBrightnessWithExponent(device, -step, exponential, exponent); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, m.GetState())
-}
-
-func handleRescan(conn net.Conn, req models.Request, m *Manager) {
- m.Rescan()
- models.Respond(conn, req.ID, m.GetState())
-}
-
-func handleSubscribe(conn net.Conn, req models.Request, m *Manager) {
- clientID := fmt.Sprintf("brightness-%d", req.ID)
-
- ch := m.Subscribe(clientID)
- defer m.Unsubscribe(clientID)
-
- initialState := m.GetState()
- if err := json.NewEncoder(conn).Encode(models.Response[State]{
- ID: req.ID,
- Result: &initialState,
- }); err != nil {
- return
- }
-
- for state := range ch {
- if err := json.NewEncoder(conn).Encode(models.Response[State]{
- ID: req.ID,
- Result: &state,
- }); err != nil {
- return
- }
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/logind.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/logind.go
deleted file mode 100644
index 8449ab5..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/logind.go
+++ /dev/null
@@ -1,66 +0,0 @@
-package brightness
-
-import (
- "fmt"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/godbus/dbus/v5"
-)
-
-type DBusConn interface {
- Object(dest string, path dbus.ObjectPath) dbus.BusObject
- Close() error
-}
-
-type LogindBackend struct {
- conn DBusConn
-}
-
-func NewLogindBackend() (*LogindBackend, error) {
- conn, err := dbus.ConnectSystemBus()
- if err != nil {
- return nil, fmt.Errorf("connect to system bus: %w", err)
- }
-
- obj := conn.Object("org.freedesktop.login1", "/org/freedesktop/login1/session/auto")
- call := obj.Call("org.freedesktop.DBus.Peer.Ping", 0)
- if call.Err != nil {
- conn.Close()
- return nil, fmt.Errorf("logind not available: %w", call.Err)
- }
-
- conn.Close()
-
- return &LogindBackend{}, nil
-}
-
-func NewLogindBackendWithConn(conn DBusConn) *LogindBackend {
- return &LogindBackend{
- conn: conn,
- }
-}
-
-func (b *LogindBackend) SetBrightness(subsystem, name string, brightness uint32) error {
- if b.conn == nil {
- conn, err := dbus.ConnectSystemBus()
- if err != nil {
- return fmt.Errorf("connect to system bus: %w", err)
- }
- b.conn = conn
- }
-
- obj := b.conn.Object("org.freedesktop.login1", "/org/freedesktop/login1/session/auto")
- call := obj.Call("org.freedesktop.login1.Session.SetBrightness", 0, subsystem, name, brightness)
- if call.Err != nil {
- return fmt.Errorf("dbus call failed: %w", call.Err)
- }
-
- log.Debugf("logind: set %s/%s to %d", subsystem, name, brightness)
- return nil
-}
-
-func (b *LogindBackend) Close() {
- if b.conn != nil {
- b.conn.Close()
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/logind_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/logind_test.go
deleted file mode 100644
index 3d4c2ee..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/logind_test.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package brightness
-
-import (
- "errors"
- "testing"
-
- mocks_brightness "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/brightness"
- mock_dbus "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/github.com/godbus/dbus/v5"
- "github.com/godbus/dbus/v5"
- "github.com/stretchr/testify/mock"
-)
-
-func TestLogindBackend_SetBrightness_Success(t *testing.T) {
- mockConn := mocks_brightness.NewMockDBusConn(t)
- mockObj := mock_dbus.NewMockBusObject(t)
-
- backend := NewLogindBackendWithConn(mockConn)
-
- mockConn.EXPECT().
- Object("org.freedesktop.login1", dbus.ObjectPath("/org/freedesktop/login1/session/auto")).
- Return(mockObj).
- Once()
-
- mockObj.EXPECT().
- Call("org.freedesktop.login1.Session.SetBrightness", dbus.Flags(0), "backlight", "nvidia_0", uint32(75)).
- Return(&dbus.Call{Err: nil}).
- Once()
-
- err := backend.SetBrightness("backlight", "nvidia_0", 75)
- if err != nil {
- t.Errorf("SetBrightness() error = %v, want nil", err)
- }
-}
-
-func TestLogindBackend_SetBrightness_DBusError(t *testing.T) {
- mockConn := mocks_brightness.NewMockDBusConn(t)
- mockObj := mock_dbus.NewMockBusObject(t)
-
- backend := NewLogindBackendWithConn(mockConn)
-
- mockConn.EXPECT().
- Object("org.freedesktop.login1", dbus.ObjectPath("/org/freedesktop/login1/session/auto")).
- Return(mockObj).
- Once()
-
- dbusErr := errors.New("permission denied")
- mockObj.EXPECT().
- Call("org.freedesktop.login1.Session.SetBrightness", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
- Return(&dbus.Call{Err: dbusErr}).
- Once()
-
- err := backend.SetBrightness("backlight", "test_device", 50)
- if err == nil {
- t.Error("SetBrightness() error = nil, want error")
- }
-}
-
-func TestLogindBackend_SetBrightness_LEDDevice(t *testing.T) {
- mockConn := mocks_brightness.NewMockDBusConn(t)
- mockObj := mock_dbus.NewMockBusObject(t)
-
- backend := NewLogindBackendWithConn(mockConn)
-
- mockConn.EXPECT().
- Object("org.freedesktop.login1", dbus.ObjectPath("/org/freedesktop/login1/session/auto")).
- Return(mockObj).
- Once()
-
- mockObj.EXPECT().
- Call("org.freedesktop.login1.Session.SetBrightness", dbus.Flags(0), "leds", "test_led", uint32(128)).
- Return(&dbus.Call{Err: nil}).
- Once()
-
- err := backend.SetBrightness("leds", "test_led", 128)
- if err != nil {
- t.Errorf("SetBrightness() error = %v, want nil", err)
- }
-}
-
-func TestLogindBackend_Close(t *testing.T) {
- mockConn := mocks_brightness.NewMockDBusConn(t)
- backend := NewLogindBackendWithConn(mockConn)
-
- mockConn.EXPECT().
- Close().
- Return(nil).
- Once()
-
- backend.Close()
-}
-
-func TestLogindBackend_Close_NilConn(t *testing.T) {
- backend := &LogindBackend{conn: nil}
- backend.Close()
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/manager.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/manager.go
deleted file mode 100644
index 4cf7dd4..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/manager.go
+++ /dev/null
@@ -1,389 +0,0 @@
-package brightness
-
-import (
- "fmt"
- "sort"
- "strings"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
-)
-
-func NewManager() (*Manager, error) {
- return NewManagerWithOptions(false)
-}
-
-func NewManagerWithOptions(exponential bool) (*Manager, error) {
- m := &Manager{
- stopChan: make(chan struct{}),
- exponential: exponential,
- }
-
- go m.initLogind()
- go m.initSysfs()
- go m.initDDC()
-
- return m, nil
-}
-
-func (m *Manager) initLogind() {
- log.Debug("Initializing logind backend...")
- logind, err := NewLogindBackend()
- if err != nil {
- log.Infof("Logind backend not available: %v", err)
- log.Info("Will use direct sysfs access for brightness control")
- return
- }
-
- m.logindBackend = logind
- m.logindReady = true
- log.Info("Logind backend initialized - will use for brightness control")
-}
-
-func (m *Manager) initSysfs() {
- log.Debug("Initializing sysfs backend...")
- sysfs, err := NewSysfsBackend()
- if err != nil {
- log.Warnf("Failed to initialize sysfs backend: %v", err)
- return
- }
-
- devices, err := sysfs.GetDevices()
- if err != nil {
- log.Warnf("Failed to get initial sysfs devices: %v", err)
- m.sysfsBackend = sysfs
- m.sysfsReady = true
- m.updateState()
- m.initUdev()
- return
- }
-
- log.Infof("Sysfs backend initialized with %d devices", len(devices))
- for _, d := range devices {
- log.Debugf(" - %s: %s (%d%%)", d.ID, d.Name, d.CurrentPercent)
- }
-
- m.sysfsBackend = sysfs
- m.sysfsReady = true
- m.updateState()
- m.initUdev()
-}
-
-func (m *Manager) initUdev() {
- m.udevMonitor = NewUdevMonitor(m)
-}
-
-func (m *Manager) initDDC() {
- ddc, err := NewDDCBackend()
- if err != nil {
- log.Debugf("Failed to initialize DDC backend: %v", err)
- return
- }
-
- m.ddcBackend = ddc
- m.ddcReady = true
- log.Info("DDC backend initialized")
-
- m.updateState()
-}
-
-func (m *Manager) Rescan() {
- log.Debug("Rescanning brightness devices...")
-
- if m.ddcReady && m.ddcBackend != nil {
- if err := m.ddcBackend.ForceRescan(); err != nil {
- log.Debugf("DDC force rescan failed: %v", err)
- }
- }
-
- if m.sysfsReady && m.sysfsBackend != nil {
- if err := m.sysfsBackend.Rescan(); err != nil {
- log.Debugf("Sysfs rescan failed: %v", err)
- }
- }
-
- m.updateState()
-}
-
-func sortDevices(devices []Device) {
- sort.Slice(devices, func(i, j int) bool {
- classOrder := map[DeviceClass]int{
- ClassBacklight: 0,
- ClassDDC: 1,
- ClassLED: 2,
- }
-
- orderI := classOrder[devices[i].Class]
- orderJ := classOrder[devices[j].Class]
-
- if orderI != orderJ {
- return orderI < orderJ
- }
-
- return devices[i].Name < devices[j].Name
- })
-}
-
-func stateChanged(old, new State) bool {
- if len(old.Devices) != len(new.Devices) {
- return true
- }
-
- oldMap := make(map[string]Device)
- for _, d := range old.Devices {
- oldMap[d.ID] = d
- }
-
- for _, newDev := range new.Devices {
- oldDev, exists := oldMap[newDev.ID]
- if !exists {
- return true
- }
- if oldDev.Current != newDev.Current || oldDev.Max != newDev.Max {
- return true
- }
- }
-
- return false
-}
-
-func (m *Manager) updateState() {
- allDevices := make([]Device, 0)
-
- if m.sysfsReady && m.sysfsBackend != nil {
- devices, err := m.sysfsBackend.GetDevices()
- if err != nil {
- log.Debugf("Failed to get sysfs devices: %v", err)
- }
- if err == nil {
- allDevices = append(allDevices, devices...)
- }
- }
-
- if m.ddcReady && m.ddcBackend != nil {
- devices, err := m.ddcBackend.GetDevices()
- if err != nil {
- log.Debugf("Failed to get DDC devices: %v", err)
- }
- if err == nil {
- allDevices = append(allDevices, devices...)
- }
- }
-
- sortDevices(allDevices)
-
- m.stateMutex.Lock()
- oldState := m.state
- newState := State{Devices: allDevices}
-
- if !stateChanged(oldState, newState) {
- m.stateMutex.Unlock()
- return
- }
-
- m.state = newState
- m.stateMutex.Unlock()
- log.Debugf("State changed, notifying subscribers")
- m.NotifySubscribers()
-}
-
-func (m *Manager) SetBrightness(deviceID string, percent int) error {
- return m.SetBrightnessWithMode(deviceID, percent, m.exponential)
-}
-
-func (m *Manager) SetBrightnessWithMode(deviceID string, percent int, exponential bool) error {
- return m.SetBrightnessWithExponent(deviceID, percent, exponential, 1.2)
-}
-
-func (m *Manager) SetBrightnessWithExponent(deviceID string, percent int, exponential bool, exponent float64) error {
- if percent < 0 {
- return fmt.Errorf("percent out of range: %d", percent)
- }
-
- log.Debugf("SetBrightness: %s to %d%%", deviceID, percent)
-
- m.stateMutex.Lock()
- currentState := m.state
- var found bool
- var deviceClass DeviceClass
- var deviceIndex int
-
- log.Debugf("Current state has %d devices", len(currentState.Devices))
-
- for i, dev := range currentState.Devices {
- if dev.ID == deviceID {
- found = true
- deviceClass = dev.Class
- deviceIndex = i
- break
- }
- }
-
- if !found {
- m.stateMutex.Unlock()
- log.Debugf("Device not found in state: %s", deviceID)
- return fmt.Errorf("device not found: %s", deviceID)
- }
-
- newDevices := make([]Device, len(currentState.Devices))
- copy(newDevices, currentState.Devices)
- newDevices[deviceIndex].CurrentPercent = percent
- m.state = State{Devices: newDevices}
- m.stateMutex.Unlock()
-
- var err error
- if deviceClass == ClassDDC {
- log.Debugf("Calling DDC backend for %s", deviceID)
- err = m.ddcBackend.SetBrightnessWithExponent(deviceID, percent, exponential, exponent, func() {
- m.updateState()
- m.debouncedBroadcast(deviceID)
- })
- } else if m.logindReady && m.logindBackend != nil {
- log.Debugf("Calling logind backend for %s", deviceID)
- err = m.setViaSysfsWithLogindWithExponent(deviceID, percent, exponential, exponent)
- } else {
- log.Debugf("Calling sysfs backend for %s", deviceID)
- err = m.sysfsBackend.SetBrightnessWithExponent(deviceID, percent, exponential, exponent)
- }
-
- if err != nil {
- m.updateState()
- return fmt.Errorf("failed to set brightness: %w", err)
- }
-
- if deviceClass != ClassDDC {
- log.Debugf("Queueing broadcast for %s", deviceID)
- m.debouncedBroadcast(deviceID)
- }
- return nil
-}
-
-func (m *Manager) IncrementBrightness(deviceID string, step int) error {
- return m.IncrementBrightnessWithMode(deviceID, step, m.exponential)
-}
-
-func (m *Manager) IncrementBrightnessWithMode(deviceID string, step int, exponential bool) error {
- return m.IncrementBrightnessWithExponent(deviceID, step, exponential, 1.2)
-}
-
-func (m *Manager) IncrementBrightnessWithExponent(deviceID string, step int, exponential bool, exponent float64) error {
- m.stateMutex.RLock()
- currentState := m.state
- m.stateMutex.RUnlock()
-
- var currentPercent int
- var found bool
-
- for _, dev := range currentState.Devices {
- if dev.ID == deviceID {
- currentPercent = dev.CurrentPercent
- found = true
- break
- }
- }
-
- if !found {
- return fmt.Errorf("device not found: %s", deviceID)
- }
-
- newPercent := currentPercent + step
- if newPercent > 100 {
- newPercent = 100
- }
- if newPercent < 0 {
- newPercent = 0
- }
-
- return m.SetBrightnessWithExponent(deviceID, newPercent, exponential, exponent)
-}
-
-func (m *Manager) DecrementBrightness(deviceID string, step int) error {
- return m.IncrementBrightness(deviceID, -step)
-}
-
-func (m *Manager) setViaSysfsWithLogindWithExponent(deviceID string, percent int, exponential bool, exponent float64) error {
- parts := strings.SplitN(deviceID, ":", 2)
- if len(parts) != 2 {
- return fmt.Errorf("invalid device id: %s", deviceID)
- }
-
- subsystem := parts[0]
- name := parts[1]
-
- dev, err := m.sysfsBackend.GetDevice(deviceID)
- if err != nil {
- return err
- }
-
- value := m.sysfsBackend.PercentToValueWithExponent(percent, dev, exponential, exponent)
-
- if m.logindBackend == nil {
- return m.sysfsBackend.SetBrightnessWithExponent(deviceID, percent, exponential, exponent)
- }
-
- err = m.logindBackend.SetBrightness(subsystem, name, uint32(value))
- if err != nil {
- log.Debugf("logind SetBrightness failed, falling back to direct sysfs: %v", err)
- return m.sysfsBackend.SetBrightnessWithExponent(deviceID, percent, exponential, exponent)
- }
-
- log.Debugf("set %s to %d%% (%d/%d) via logind", deviceID, percent, value, dev.maxBrightness)
- return nil
-}
-
-func (m *Manager) debouncedBroadcast(deviceID string) {
- m.broadcastMutex.Lock()
- defer m.broadcastMutex.Unlock()
-
- m.broadcastPending = true
- m.pendingDeviceID = deviceID
-
- if m.broadcastTimer == nil {
- m.broadcastTimer = time.AfterFunc(150*time.Millisecond, func() {
- m.broadcastMutex.Lock()
- pending := m.broadcastPending
- deviceID := m.pendingDeviceID
- m.broadcastPending = false
- m.pendingDeviceID = ""
- m.broadcastMutex.Unlock()
-
- if !pending || deviceID == "" {
- return
- }
-
- m.broadcastDeviceUpdate(deviceID)
- })
- } else {
- m.broadcastTimer.Reset(150 * time.Millisecond)
- }
-}
-
-func (m *Manager) broadcastDeviceUpdate(deviceID string) {
- m.stateMutex.RLock()
- var targetDevice *Device
- for _, dev := range m.state.Devices {
- if dev.ID == deviceID {
- devCopy := dev
- targetDevice = &devCopy
- break
- }
- }
- m.stateMutex.RUnlock()
-
- if targetDevice == nil {
- log.Debugf("Device not found for broadcast: %s", deviceID)
- return
- }
-
- update := DeviceUpdate{Device: *targetDevice}
-
- log.Debugf("Broadcasting device update: %s at %d%%", deviceID, targetDevice.CurrentPercent)
-
- m.updateSubscribers.Range(func(key string, ch chan DeviceUpdate) bool {
- select {
- case ch <- update:
- default:
- }
- return true
- })
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/manager_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/manager_test.go
deleted file mode 100644
index a8ae0f8..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/manager_test.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package brightness
-
-import (
- "testing"
-)
-
-// Manager tests can be added here as needed
-func TestManager_Placeholder(t *testing.T) {
- // Placeholder test to keep the test file valid
- t.Skip("No tests implemented yet")
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/sysfs.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/sysfs.go
deleted file mode 100644
index 1ff38f9..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/sysfs.go
+++ /dev/null
@@ -1,267 +0,0 @@
-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) Rescan() error {
- return b.scanDevices()
-}
-
-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, 0o644); 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
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/sysfs_logind_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/sysfs_logind_test.go
deleted file mode 100644
index 6ba84b8..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/sysfs_logind_test.go
+++ /dev/null
@@ -1,278 +0,0 @@
-package brightness
-
-import (
- "os"
- "path/filepath"
- "testing"
-
- mocks_brightness "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/brightness"
- mock_dbus "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/github.com/godbus/dbus/v5"
- "github.com/godbus/dbus/v5"
- "github.com/stretchr/testify/mock"
-)
-
-func TestManager_SetBrightness_LogindSuccess(t *testing.T) {
- tmpDir := t.TempDir()
-
- backlightDir := filepath.Join(tmpDir, "backlight", "test_backlight")
- if err := os.MkdirAll(backlightDir, 0o755); err != nil {
- t.Fatal(err)
- }
- if err := os.WriteFile(filepath.Join(backlightDir, "max_brightness"), []byte("100\n"), 0o644); err != nil {
- t.Fatal(err)
- }
- if err := os.WriteFile(filepath.Join(backlightDir, "brightness"), []byte("50\n"), 0o644); err != nil {
- t.Fatal(err)
- }
-
- mockConn := mocks_brightness.NewMockDBusConn(t)
- mockObj := mock_dbus.NewMockBusObject(t)
-
- mockLogind := NewLogindBackendWithConn(mockConn)
-
- sysfs := &SysfsBackend{
- basePath: tmpDir,
- classes: []string{"backlight"},
- }
-
- if err := sysfs.scanDevices(); err != nil {
- t.Fatal(err)
- }
-
- m := &Manager{
- logindBackend: mockLogind,
- sysfsBackend: sysfs,
- logindReady: true,
- sysfsReady: true,
- stopChan: make(chan struct{}),
- }
-
- m.state = State{
- Devices: []Device{
- {
- Class: ClassBacklight,
- ID: "backlight:test_backlight",
- Name: "test_backlight",
- Current: 50,
- Max: 100,
- CurrentPercent: 50,
- Backend: "sysfs",
- },
- },
- }
-
- mockConn.EXPECT().
- Object("org.freedesktop.login1", dbus.ObjectPath("/org/freedesktop/login1/session/auto")).
- Return(mockObj).
- Once()
-
- mockObj.EXPECT().
- Call("org.freedesktop.login1.Session.SetBrightness", mock.Anything, "backlight", "test_backlight", uint32(75)).
- Return(&dbus.Call{Err: nil}).
- Once()
-
- err := m.SetBrightness("backlight:test_backlight", 75)
- if err != nil {
- t.Errorf("SetBrightness() with logind error = %v, want nil", err)
- }
-
- data, _ := os.ReadFile(filepath.Join(backlightDir, "brightness"))
- if string(data) == "75\n" {
- t.Error("Direct sysfs write occurred when logind should have been used")
- }
-}
-
-func TestManager_SetBrightness_LogindFailsFallbackToSysfs(t *testing.T) {
- tmpDir := t.TempDir()
-
- backlightDir := filepath.Join(tmpDir, "backlight", "test_backlight")
- if err := os.MkdirAll(backlightDir, 0o755); err != nil {
- t.Fatal(err)
- }
- if err := os.WriteFile(filepath.Join(backlightDir, "max_brightness"), []byte("100\n"), 0o644); err != nil {
- t.Fatal(err)
- }
- if err := os.WriteFile(filepath.Join(backlightDir, "brightness"), []byte("50\n"), 0o644); err != nil {
- t.Fatal(err)
- }
-
- mockConn := mocks_brightness.NewMockDBusConn(t)
- mockObj := mock_dbus.NewMockBusObject(t)
-
- mockLogind := NewLogindBackendWithConn(mockConn)
-
- sysfs := &SysfsBackend{
- basePath: tmpDir,
- classes: []string{"backlight"},
- }
-
- if err := sysfs.scanDevices(); err != nil {
- t.Fatal(err)
- }
-
- m := &Manager{
- logindBackend: mockLogind,
- sysfsBackend: sysfs,
- logindReady: true,
- sysfsReady: true,
- stopChan: make(chan struct{}),
- }
-
- m.state = State{
- Devices: []Device{
- {
- Class: ClassBacklight,
- ID: "backlight:test_backlight",
- Name: "test_backlight",
- Current: 50,
- Max: 100,
- CurrentPercent: 50,
- Backend: "sysfs",
- },
- },
- }
-
- mockConn.EXPECT().
- Object("org.freedesktop.login1", dbus.ObjectPath("/org/freedesktop/login1/session/auto")).
- Return(mockObj).
- Once()
-
- mockObj.EXPECT().
- Call("org.freedesktop.login1.Session.SetBrightness", mock.Anything, "backlight", "test_backlight", mock.Anything).
- Return(&dbus.Call{Err: dbus.ErrMsgNoObject}).
- Once()
-
- err := m.SetBrightness("backlight:test_backlight", 75)
- if err != nil {
- t.Errorf("SetBrightness() with fallback error = %v, want nil", err)
- }
-
- data, _ := os.ReadFile(filepath.Join(backlightDir, "brightness"))
- brightness := string(data)
- if brightness != "75" {
- t.Errorf("Fallback sysfs write did not occur, got brightness = %q, want %q", brightness, "75")
- }
-}
-
-func TestManager_SetBrightness_NoLogind(t *testing.T) {
- tmpDir := t.TempDir()
-
- backlightDir := filepath.Join(tmpDir, "backlight", "test_backlight")
- if err := os.MkdirAll(backlightDir, 0o755); err != nil {
- t.Fatal(err)
- }
- if err := os.WriteFile(filepath.Join(backlightDir, "max_brightness"), []byte("100\n"), 0o644); err != nil {
- t.Fatal(err)
- }
- if err := os.WriteFile(filepath.Join(backlightDir, "brightness"), []byte("50\n"), 0o644); err != nil {
- t.Fatal(err)
- }
-
- sysfs := &SysfsBackend{
- basePath: tmpDir,
- classes: []string{"backlight"},
- }
-
- if err := sysfs.scanDevices(); err != nil {
- t.Fatal(err)
- }
-
- m := &Manager{
- logindBackend: nil,
- sysfsBackend: sysfs,
- logindReady: false,
- sysfsReady: true,
- stopChan: make(chan struct{}),
- }
-
- m.state = State{
- Devices: []Device{
- {
- Class: ClassBacklight,
- ID: "backlight:test_backlight",
- Name: "test_backlight",
- Current: 50,
- Max: 100,
- CurrentPercent: 50,
- Backend: "sysfs",
- },
- },
- }
-
- err := m.SetBrightness("backlight:test_backlight", 75)
- if err != nil {
- t.Errorf("SetBrightness() without logind error = %v, want nil", err)
- }
-
- data, _ := os.ReadFile(filepath.Join(backlightDir, "brightness"))
- brightness := string(data)
- if brightness != "75" {
- t.Errorf("Direct sysfs write = %q, want %q", brightness, "75")
- }
-}
-
-func TestManager_SetBrightness_LEDWithLogind(t *testing.T) {
- tmpDir := t.TempDir()
-
- ledsDir := filepath.Join(tmpDir, "leds", "test_led")
- if err := os.MkdirAll(ledsDir, 0o755); err != nil {
- t.Fatal(err)
- }
- if err := os.WriteFile(filepath.Join(ledsDir, "max_brightness"), []byte("255\n"), 0o644); err != nil {
- t.Fatal(err)
- }
- if err := os.WriteFile(filepath.Join(ledsDir, "brightness"), []byte("128\n"), 0o644); err != nil {
- t.Fatal(err)
- }
-
- mockConn := mocks_brightness.NewMockDBusConn(t)
- mockObj := mock_dbus.NewMockBusObject(t)
-
- mockLogind := NewLogindBackendWithConn(mockConn)
-
- sysfs := &SysfsBackend{
- basePath: tmpDir,
- classes: []string{"leds"},
- }
-
- if err := sysfs.scanDevices(); err != nil {
- t.Fatal(err)
- }
-
- m := &Manager{
- logindBackend: mockLogind,
- sysfsBackend: sysfs,
- logindReady: true,
- sysfsReady: true,
- stopChan: make(chan struct{}),
- }
-
- m.state = State{
- Devices: []Device{
- {
- Class: ClassLED,
- ID: "leds:test_led",
- Name: "test_led",
- Current: 128,
- Max: 255,
- CurrentPercent: 50,
- Backend: "sysfs",
- },
- },
- }
-
- mockConn.EXPECT().
- Object("org.freedesktop.login1", dbus.ObjectPath("/org/freedesktop/login1/session/auto")).
- Return(mockObj).
- Once()
-
- mockObj.EXPECT().
- Call("org.freedesktop.login1.Session.SetBrightness", mock.Anything, "leds", "test_led", uint32(0)).
- Return(&dbus.Call{Err: nil}).
- Once()
-
- err := m.SetBrightness("leds:test_led", 0)
- if err != nil {
- t.Errorf("SetBrightness() LED with logind error = %v, want nil", err)
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/sysfs_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/sysfs_test.go
deleted file mode 100644
index 8061513..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/sysfs_test.go
+++ /dev/null
@@ -1,180 +0,0 @@
-package brightness
-
-import (
- "os"
- "path/filepath"
- "testing"
-)
-
-func TestSysfsBackend_PercentConversions(t *testing.T) {
- tests := []struct {
- name string
- device *sysfsDevice
- percent int
- wantValue int
- tolerance int
- }{
- {
- name: "backlight 0% should be minValue=1",
- device: &sysfsDevice{maxBrightness: 100, minValue: 1, class: ClassBacklight},
- percent: 0,
- wantValue: 1,
- tolerance: 0,
- },
- {
- name: "backlight 1% should be minValue=1",
- device: &sysfsDevice{maxBrightness: 100, minValue: 1, class: ClassBacklight},
- percent: 1,
- wantValue: 1,
- tolerance: 0,
- },
- {
- name: "backlight 50% should be ~50",
- device: &sysfsDevice{maxBrightness: 100, minValue: 1, class: ClassBacklight},
- percent: 50,
- wantValue: 50,
- tolerance: 1,
- },
- {
- name: "backlight 100% should be max",
- device: &sysfsDevice{maxBrightness: 100, minValue: 1, class: ClassBacklight},
- percent: 100,
- wantValue: 100,
- tolerance: 0,
- },
- {
- name: "led 0% should be 0",
- device: &sysfsDevice{maxBrightness: 255, minValue: 0, class: ClassLED},
- percent: 0,
- wantValue: 0,
- tolerance: 0,
- },
- {
- name: "led 1% should be ~2-3",
- device: &sysfsDevice{maxBrightness: 255, minValue: 0, class: ClassLED},
- percent: 1,
- wantValue: 2,
- tolerance: 3,
- },
- {
- name: "led 50% should be ~127",
- device: &sysfsDevice{maxBrightness: 255, minValue: 0, class: ClassLED},
- percent: 50,
- wantValue: 127,
- tolerance: 2,
- },
- {
- name: "led 100% should be max",
- device: &sysfsDevice{maxBrightness: 255, minValue: 0, class: ClassLED},
- percent: 100,
- wantValue: 255,
- tolerance: 0,
- },
- }
-
- b := &SysfsBackend{}
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got := b.PercentToValue(tt.percent, tt.device, false)
- diff := got - tt.wantValue
- if diff < 0 {
- diff = -diff
- }
- if diff > tt.tolerance {
- t.Errorf("percentToValue() = %v, want %v (±%d)", got, tt.wantValue, tt.tolerance)
- }
-
- gotPercent := b.ValueToPercent(got, tt.device, false)
- if tt.percent > 1 && gotPercent == 0 {
- t.Errorf("valueToPercent() returned 0 for non-zero input (percent=%d, got value=%d)", tt.percent, got)
- }
- })
- }
-}
-
-func TestSysfsBackend_ValueToPercent(t *testing.T) {
- tests := []struct {
- name string
- device *sysfsDevice
- value int
- wantPercent int
- }{
- {
- name: "backlight min value",
- device: &sysfsDevice{maxBrightness: 100, minValue: 1, class: ClassBacklight},
- value: 1,
- wantPercent: 1,
- },
- {
- name: "backlight max value",
- device: &sysfsDevice{maxBrightness: 100, minValue: 1, class: ClassBacklight},
- value: 100,
- wantPercent: 100,
- },
- {
- name: "led zero",
- device: &sysfsDevice{maxBrightness: 255, minValue: 0, class: ClassLED},
- value: 0,
- wantPercent: 0,
- },
- }
-
- b := &SysfsBackend{}
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got := b.ValueToPercent(tt.value, tt.device, false)
- if got != tt.wantPercent {
- t.Errorf("valueToPercent() = %v, want %v", got, tt.wantPercent)
- }
- })
- }
-}
-
-func TestSysfsBackend_ScanDevices(t *testing.T) {
- tmpDir := t.TempDir()
-
- backlightDir := filepath.Join(tmpDir, "backlight", "test_backlight")
- if err := os.MkdirAll(backlightDir, 0o755); err != nil {
- t.Fatal(err)
- }
-
- if err := os.WriteFile(filepath.Join(backlightDir, "max_brightness"), []byte("100\n"), 0o644); err != nil {
- t.Fatal(err)
- }
- if err := os.WriteFile(filepath.Join(backlightDir, "brightness"), []byte("50\n"), 0o644); err != nil {
- t.Fatal(err)
- }
-
- ledsDir := filepath.Join(tmpDir, "leds", "test_led")
- if err := os.MkdirAll(ledsDir, 0o755); err != nil {
- t.Fatal(err)
- }
-
- if err := os.WriteFile(filepath.Join(ledsDir, "max_brightness"), []byte("255\n"), 0o644); err != nil {
- t.Fatal(err)
- }
- if err := os.WriteFile(filepath.Join(ledsDir, "brightness"), []byte("128\n"), 0o644); err != nil {
- t.Fatal(err)
- }
-
- b := &SysfsBackend{
- basePath: tmpDir,
- classes: []string{"backlight", "leds"},
- }
-
- if err := b.scanDevices(); err != nil {
- t.Fatalf("scanDevices() error = %v", err)
- }
-
- backlightID := "backlight:test_backlight"
- if _, ok := b.deviceCache.Load(backlightID); !ok {
- t.Errorf("backlight device not found")
- }
-
- ledID := "leds:test_led"
- if _, ok := b.deviceCache.Load(ledID); !ok {
- t.Errorf("LED device not found")
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/types.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/types.go
deleted file mode 100644
index 81e00cf..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/types.go
+++ /dev/null
@@ -1,184 +0,0 @@
-package brightness
-
-import (
- "sync"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
-)
-
-type DeviceClass string
-
-const (
- ClassBacklight DeviceClass = "backlight"
- ClassLED DeviceClass = "leds"
- ClassDDC DeviceClass = "ddc"
-)
-
-type Device struct {
- Class DeviceClass `json:"class"`
- ID string `json:"id"`
- Name string `json:"name"`
- Current int `json:"current"`
- Max int `json:"max"`
- CurrentPercent int `json:"currentPercent"`
- Backend string `json:"backend"`
-}
-
-type State struct {
- Devices []Device `json:"devices"`
-}
-
-type DeviceUpdate struct {
- Device Device `json:"device"`
-}
-
-type Manager struct {
- logindBackend *LogindBackend
- sysfsBackend *SysfsBackend
- ddcBackend *DDCBackend
- udevMonitor *UdevMonitor
-
- logindReady bool
- sysfsReady bool
- ddcReady bool
-
- exponential bool
-
- stateMutex sync.RWMutex
- state State
-
- subscribers syncmap.Map[string, chan State]
- updateSubscribers syncmap.Map[string, chan DeviceUpdate]
-
- broadcastMutex sync.Mutex
- broadcastTimer *time.Timer
- broadcastPending bool
- pendingDeviceID string
-
- stopChan chan struct{}
-}
-
-type SysfsBackend struct {
- basePath string
- classes []string
-
- deviceCache syncmap.Map[string, *sysfsDevice]
-}
-
-type sysfsDevice struct {
- class DeviceClass
- id string
- name string
- maxBrightness int
- minValue int
-}
-
-type DDCBackend struct {
- devices syncmap.Map[string, *ddcDevice]
-
- scanMutex sync.Mutex
- lastScan time.Time
- scanInterval time.Duration
-
- debounceMutex sync.Mutex
- debounceTimers map[string]*time.Timer
- debouncePending map[string]ddcPendingSet
- debounceWg sync.WaitGroup
-}
-
-type ddcPendingSet struct {
- percent int
- callback func()
-}
-
-type ddcDevice struct {
- bus int
- addr int
- id string
- name string
- max int
- lastBrightness int
-}
-
-type ddcCapability struct {
- vcp byte
- max int
- current int
-}
-
-func (m *Manager) Subscribe(id string) chan State {
- ch := make(chan State, 16)
-
- m.subscribers.Store(id, ch)
-
- return ch
-}
-
-func (m *Manager) Unsubscribe(id string) {
-
- if val, ok := m.subscribers.LoadAndDelete(id); ok {
- close(val)
-
- }
-
-}
-
-func (m *Manager) SubscribeUpdates(id string) chan DeviceUpdate {
- ch := make(chan DeviceUpdate, 16)
- m.updateSubscribers.Store(id, ch)
- return ch
-}
-
-func (m *Manager) UnsubscribeUpdates(id string) {
- if val, ok := m.updateSubscribers.LoadAndDelete(id); ok {
- close(val)
- }
-}
-
-func (m *Manager) NotifySubscribers() {
- m.stateMutex.RLock()
- state := m.state
- m.stateMutex.RUnlock()
-
- m.subscribers.Range(func(key string, ch chan State) bool {
- select {
- case ch <- state:
- default:
- }
- return true
- })
-}
-
-func (m *Manager) GetState() State {
- m.stateMutex.RLock()
- defer m.stateMutex.RUnlock()
- return m.state
-}
-
-func (m *Manager) Close() {
- close(m.stopChan)
-
- m.subscribers.Range(func(key string, ch chan State) bool {
- close(ch)
- m.subscribers.Delete(key)
- return true
- })
- m.updateSubscribers.Range(func(key string, ch chan DeviceUpdate) bool {
- close(ch)
- m.updateSubscribers.Delete(key)
- return true
- })
-
- if m.udevMonitor != nil {
- m.udevMonitor.Close()
- }
-
- if m.logindBackend != nil {
- m.logindBackend.Close()
- }
-
- if m.ddcBackend != nil {
- m.ddcBackend.Close()
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/udev.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/udev.go
deleted file mode 100644
index b7f02a8..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/udev.go
+++ /dev/null
@@ -1,237 +0,0 @@
-package brightness
-
-import (
- "os"
- "path/filepath"
- "strconv"
- "strings"
- "sync"
- "syscall"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/pilebones/go-udev/netlink"
-)
-
-const (
- udevRecvBufSize = 8 * 1024 * 1024
- udevMaxRetries = 5
- udevBaseDelay = 2 * time.Second
- udevMaxDelay = 60 * time.Second
-)
-
-type UdevMonitor struct {
- stop chan struct{}
- rescanMutex sync.Mutex
- rescanTimer *time.Timer
- rescanPending bool
-}
-
-func NewUdevMonitor(manager *Manager) *UdevMonitor {
- m := &UdevMonitor{
- stop: make(chan struct{}),
- }
-
- go m.run(manager)
- return m
-}
-
-func (m *UdevMonitor) run(manager *Manager) {
- matcher := &netlink.RuleDefinitions{
- Rules: []netlink.RuleDefinition{
- {Env: map[string]string{"SUBSYSTEM": "backlight"}},
- {Env: map[string]string{"SUBSYSTEM": "drm"}},
- {Env: map[string]string{"SUBSYSTEM": "i2c"}},
- },
- }
- if err := matcher.Compile(); err != nil {
- log.Errorf("Failed to compile udev matcher: %v", err)
- return
- }
-
- failures := 0
- for {
- if err := m.monitorLoop(manager, matcher); err != nil {
- log.Errorf("Udev monitor error: %v", err)
- }
-
- select {
- case <-m.stop:
- return
- default:
- }
-
- failures++
- if failures > udevMaxRetries {
- log.Errorf("Udev monitor exceeded %d retries, giving up", udevMaxRetries)
- return
- }
-
- delay := min(udevBaseDelay*time.Duration(1<<(failures-1)), udevMaxDelay)
- log.Infof("Udev monitor reconnecting in %v (attempt %d/%d)", delay, failures, udevMaxRetries)
-
- select {
- case <-m.stop:
- return
- case <-time.After(delay):
- }
- }
-}
-
-func (m *UdevMonitor) monitorLoop(manager *Manager, matcher *netlink.RuleDefinitions) error {
- conn := &netlink.UEventConn{}
- if err := conn.Connect(netlink.UdevEvent); err != nil {
- return err
- }
- defer conn.Close()
-
- if err := syscall.SetsockoptInt(conn.Fd, syscall.SOL_SOCKET, syscall.SO_RCVBUF, udevRecvBufSize); err != nil {
- log.Warnf("Failed to set udev socket receive buffer: %v", err)
- }
-
- events := make(chan netlink.UEvent)
- errs := make(chan error)
- conn.Monitor(events, errs, matcher)
-
- log.Info("Udev monitor started for backlight/drm/i2c events")
-
- for {
- select {
- case <-m.stop:
- return nil
- case err := <-errs:
- return err
- case event := <-events:
- m.handleEvent(manager, event)
- }
- }
-}
-
-func (m *UdevMonitor) handleEvent(manager *Manager, event netlink.UEvent) {
- subsystem := event.Env["SUBSYSTEM"]
- devpath := event.Env["DEVPATH"]
-
- if subsystem == "" || devpath == "" {
- return
- }
-
- sysname := filepath.Base(devpath)
- action := string(event.Action)
-
- switch subsystem {
- case "drm", "i2c":
- m.handleDisplayEvent(manager, action, subsystem, sysname)
- case "backlight":
- m.handleBacklightEvent(manager, action, sysname)
- }
-}
-
-func (m *UdevMonitor) handleDisplayEvent(manager *Manager, action, subsystem, sysname string) {
- switch action {
- case "add", "remove", "change":
- log.Debugf("Udev %s event: %s:%s - queueing DDC rescan", action, subsystem, sysname)
- m.debouncedRescan(manager)
- }
-}
-
-func (m *UdevMonitor) debouncedRescan(manager *Manager) {
- m.rescanMutex.Lock()
- defer m.rescanMutex.Unlock()
-
- m.rescanPending = true
-
- if m.rescanTimer != nil {
- m.rescanTimer.Reset(2 * time.Second)
- return
- }
-
- m.rescanTimer = time.AfterFunc(2*time.Second, func() {
- m.rescanMutex.Lock()
- pending := m.rescanPending
- m.rescanPending = false
- m.rescanMutex.Unlock()
-
- if !pending {
- return
- }
-
- log.Debug("Executing debounced DDC rescan")
- manager.Rescan()
- })
-}
-
-func (m *UdevMonitor) handleBacklightEvent(manager *Manager, action, sysname string) {
- switch action {
- case "change":
- m.handleChange(manager, "backlight", sysname)
- case "add", "remove":
- log.Debugf("Udev %s event: backlight:%s - triggering rescan", action, sysname)
- manager.Rescan()
- }
-}
-
-func (m *UdevMonitor) handleChange(manager *Manager, subsystem, sysname string) {
- deviceID := subsystem + ":" + sysname
-
- if manager.sysfsBackend == nil {
- return
- }
-
- brightnessPath := filepath.Join(manager.sysfsBackend.basePath, subsystem, sysname, "brightness")
- data, err := os.ReadFile(brightnessPath)
- if err != nil {
- log.Debugf("Udev change event for %s but failed to read brightness: %v", deviceID, err)
- return
- }
-
- brightness, err := strconv.Atoi(strings.TrimSpace(string(data)))
- if err != nil {
- log.Debugf("Failed to parse brightness for %s: %v", deviceID, err)
- return
- }
-
- manager.handleUdevBrightnessChange(deviceID, brightness)
-}
-
-func (m *UdevMonitor) Close() {
- close(m.stop)
-}
-
-func (m *Manager) handleUdevBrightnessChange(deviceID string, rawBrightness int) {
- if m.sysfsBackend == nil {
- return
- }
-
- dev, err := m.sysfsBackend.GetDevice(deviceID)
- if err != nil {
- log.Debugf("Udev event for unknown device %s: %v", deviceID, err)
- return
- }
-
- percent := m.sysfsBackend.ValueToPercent(rawBrightness, dev, false)
-
- m.stateMutex.Lock()
- var found bool
- for i, d := range m.state.Devices {
- if d.ID != deviceID {
- continue
- }
- found = true
- if d.Current == rawBrightness {
- m.stateMutex.Unlock()
- return
- }
- m.state.Devices[i].Current = rawBrightness
- m.state.Devices[i].CurrentPercent = percent
- break
- }
- m.stateMutex.Unlock()
-
- if !found {
- log.Debugf("Udev event for device not in state: %s", deviceID)
- return
- }
-
- log.Debugf("Udev brightness change: %s -> %d (%d%%)", deviceID, rawBrightness, percent)
- m.broadcastDeviceUpdate(deviceID)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/udev_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/udev_test.go
deleted file mode 100644
index a5c406b..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/brightness/udev_test.go
+++ /dev/null
@@ -1,260 +0,0 @@
-package brightness
-
-import (
- "os"
- "path/filepath"
- "testing"
- "time"
-
- "github.com/pilebones/go-udev/netlink"
-)
-
-func setupTestManager(t *testing.T) (*Manager, string) {
- tmpDir := t.TempDir()
-
- backlightDir := filepath.Join(tmpDir, "backlight", "intel_backlight")
- if err := os.MkdirAll(backlightDir, 0o755); err != nil {
- t.Fatal(err)
- }
- if err := os.WriteFile(filepath.Join(backlightDir, "max_brightness"), []byte("1000\n"), 0o644); err != nil {
- t.Fatal(err)
- }
- if err := os.WriteFile(filepath.Join(backlightDir, "brightness"), []byte("500\n"), 0o644); err != nil {
- t.Fatal(err)
- }
-
- sysfs := &SysfsBackend{
- basePath: tmpDir,
- classes: []string{"backlight"},
- }
- if err := sysfs.scanDevices(); err != nil {
- t.Fatal(err)
- }
-
- m := &Manager{
- sysfsBackend: sysfs,
- sysfsReady: true,
- stopChan: make(chan struct{}),
- }
-
- m.state = State{
- Devices: []Device{
- {
- Class: ClassBacklight,
- ID: "backlight:intel_backlight",
- Name: "intel_backlight",
- Current: 500,
- Max: 1000,
- CurrentPercent: 50,
- Backend: "sysfs",
- },
- },
- }
-
- return m, tmpDir
-}
-
-func TestHandleUdevBrightnessChange_UpdatesState(t *testing.T) {
- m, _ := setupTestManager(t)
-
- m.handleUdevBrightnessChange("backlight:intel_backlight", 750)
-
- state := m.GetState()
- if len(state.Devices) != 1 {
- t.Fatalf("expected 1 device, got %d", len(state.Devices))
- }
-
- dev := state.Devices[0]
- if dev.Current != 750 {
- t.Errorf("expected Current=750, got %d", dev.Current)
- }
- if dev.CurrentPercent != 75 {
- t.Errorf("expected CurrentPercent=75, got %d", dev.CurrentPercent)
- }
-}
-
-func TestHandleUdevBrightnessChange_NoChangeWhenSameValue(t *testing.T) {
- m, _ := setupTestManager(t)
-
- updateCh := m.SubscribeUpdates("test")
- defer m.UnsubscribeUpdates("test")
-
- m.handleUdevBrightnessChange("backlight:intel_backlight", 500)
-
- select {
- case <-updateCh:
- t.Error("should not broadcast when brightness unchanged")
- case <-time.After(50 * time.Millisecond):
- }
-}
-
-func TestHandleUdevBrightnessChange_BroadcastsOnChange(t *testing.T) {
- m, _ := setupTestManager(t)
-
- updateCh := m.SubscribeUpdates("test")
- defer m.UnsubscribeUpdates("test")
-
- m.handleUdevBrightnessChange("backlight:intel_backlight", 750)
-
- select {
- case update := <-updateCh:
- if update.Device.Current != 750 {
- t.Errorf("broadcast had wrong Current: got %d, want 750", update.Device.Current)
- }
- case <-time.After(100 * time.Millisecond):
- t.Error("expected broadcast on brightness change")
- }
-}
-
-func TestHandleUdevBrightnessChange_UnknownDevice(t *testing.T) {
- m, _ := setupTestManager(t)
-
- m.handleUdevBrightnessChange("backlight:unknown_device", 500)
-
- state := m.GetState()
- if len(state.Devices) != 1 {
- t.Errorf("state should be unchanged, got %d devices", len(state.Devices))
- }
-}
-
-func TestHandleUdevBrightnessChange_NilSysfsBackend(t *testing.T) {
- m := &Manager{
- sysfsBackend: nil,
- stopChan: make(chan struct{}),
- }
-
- m.handleUdevBrightnessChange("backlight:test", 500)
-}
-
-func TestHandleUdevBrightnessChange_DeviceNotInState(t *testing.T) {
- m, _ := setupTestManager(t)
-
- m.sysfsBackend.deviceCache.Store("backlight:other_device", &sysfsDevice{
- class: ClassBacklight,
- id: "backlight:other_device",
- name: "other_device",
- maxBrightness: 100,
- minValue: 1,
- })
-
- m.handleUdevBrightnessChange("backlight:other_device", 50)
-
- state := m.GetState()
- for _, d := range state.Devices {
- if d.ID == "backlight:other_device" {
- t.Error("device should not be added to state via udev change event")
- }
- }
-}
-
-func TestHandleEvent_ChangeAction(t *testing.T) {
- m, tmpDir := setupTestManager(t)
- um := &UdevMonitor{stop: make(chan struct{})}
-
- brightnessPath := filepath.Join(tmpDir, "backlight", "intel_backlight", "brightness")
- if err := os.WriteFile(brightnessPath, []byte("800\n"), 0o644); err != nil {
- t.Fatal(err)
- }
-
- event := netlink.UEvent{
- Action: netlink.CHANGE,
- Env: map[string]string{
- "SUBSYSTEM": "backlight",
- "DEVPATH": "/devices/pci0000:00/0000:00:02.0/drm/card0/card0-eDP-1/intel_backlight",
- },
- }
-
- um.handleEvent(m, event)
-
- state := m.GetState()
- if state.Devices[0].Current != 800 {
- t.Errorf("expected Current=800 after change event, got %d", state.Devices[0].Current)
- }
-}
-
-func TestHandleEvent_MissingEnvVars(t *testing.T) {
- m, _ := setupTestManager(t)
- um := &UdevMonitor{stop: make(chan struct{})}
-
- event := netlink.UEvent{
- Action: netlink.CHANGE,
- Env: map[string]string{},
- }
-
- um.handleEvent(m, event)
-
- state := m.GetState()
- if state.Devices[0].Current != 500 {
- t.Error("state should be unchanged with missing env vars")
- }
-}
-
-func TestHandleEvent_MissingSubsystem(t *testing.T) {
- m, _ := setupTestManager(t)
- um := &UdevMonitor{stop: make(chan struct{})}
-
- event := netlink.UEvent{
- Action: netlink.CHANGE,
- Env: map[string]string{
- "DEVPATH": "/devices/foo/bar",
- },
- }
-
- um.handleEvent(m, event)
-
- state := m.GetState()
- if state.Devices[0].Current != 500 {
- t.Error("state should be unchanged with missing SUBSYSTEM")
- }
-}
-
-func TestHandleChange_BrightnessFileNotFound(t *testing.T) {
- m, _ := setupTestManager(t)
- um := &UdevMonitor{stop: make(chan struct{})}
-
- um.handleChange(m, "backlight", "nonexistent_device")
-
- state := m.GetState()
- if state.Devices[0].Current != 500 {
- t.Error("state should be unchanged when brightness file not found")
- }
-}
-
-func TestHandleChange_InvalidBrightnessValue(t *testing.T) {
- m, tmpDir := setupTestManager(t)
- um := &UdevMonitor{stop: make(chan struct{})}
-
- brightnessPath := filepath.Join(tmpDir, "backlight", "intel_backlight", "brightness")
- if err := os.WriteFile(brightnessPath, []byte("not_a_number\n"), 0o644); err != nil {
- t.Fatal(err)
- }
-
- um.handleChange(m, "backlight", "intel_backlight")
-
- state := m.GetState()
- if state.Devices[0].Current != 500 {
- t.Error("state should be unchanged with invalid brightness value")
- }
-}
-
-func TestUdevMonitor_Close(t *testing.T) {
- um := &UdevMonitor{stop: make(chan struct{})}
-
- um.Close()
-
- select {
- case <-um.stop:
- default:
- t.Error("stop channel should be closed")
- }
-}
-
-func TestHandleChange_NilSysfsBackend(t *testing.T) {
- m := &Manager{
- sysfsBackend: nil,
- stopChan: make(chan struct{}),
- }
- um := &UdevMonitor{stop: make(chan struct{})}
-
- um.handleChange(m, "backlight", "test_device")
-}