mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-06 13:38:28 -04:00
freebsd: add initial support for running on FreeBSD, excluding Bluez,
ppd, and some things. Add a simple wpa_supplicant backend
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
package brightness
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||
"github.com/AvengeMedia/dankgo/syncmap"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// sys/sys/backlight.h: brightness is a 0-100 percent;
|
||||
// BACKLIGHTGETSTATUS/BACKLIGHTUPDATESTATUS = _IOWR('G', 0/1, struct
|
||||
// backlight_props{uint32 brightness; uint32 nlevels; uint32 levels[100]}).
|
||||
const (
|
||||
backlightDevDir = "/dev/backlight"
|
||||
backlightGetStatus = 0xc1984700
|
||||
backlightUpdateStatus = 0xc1984701
|
||||
)
|
||||
|
||||
type backlightProps struct {
|
||||
brightness uint32
|
||||
nlevels uint32
|
||||
levels [100]uint32
|
||||
}
|
||||
|
||||
type BacklightBackend struct {
|
||||
devices syncmap.Map[string, string]
|
||||
}
|
||||
|
||||
func NewBacklightBackend() (*BacklightBackend, error) {
|
||||
b := &BacklightBackend{}
|
||||
if err := b.scanDevices(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func isGenericBacklightName(name string) bool {
|
||||
rest, ok := strings.CutPrefix(name, "backlight")
|
||||
if !ok || rest == "" {
|
||||
return false
|
||||
}
|
||||
for _, r := range rest {
|
||||
if r < '0' || r > '9' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *BacklightBackend) scanDevices() error {
|
||||
entries, err := os.ReadDir(backlightDevDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read %s: %w", backlightDevDir, err)
|
||||
}
|
||||
|
||||
// backlight_register (sys/dev/backlight/backlight.c) publishes each unit
|
||||
// as backlight/backlightN plus a driver-named alias for the same cdev;
|
||||
// dedupe on the device number and keep the descriptive alias.
|
||||
names := make(map[uint64]string)
|
||||
for _, entry := range entries {
|
||||
var st unix.Stat_t
|
||||
if err := unix.Stat(filepath.Join(backlightDevDir, entry.Name()), &st); err != nil {
|
||||
continue
|
||||
}
|
||||
rdev := uint64(st.Rdev)
|
||||
current, exists := names[rdev]
|
||||
if exists && !isGenericBacklightName(current) {
|
||||
continue
|
||||
}
|
||||
names[rdev] = entry.Name()
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
b.devices.Store("backlight:"+name, filepath.Join(backlightDevDir, name))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func backlightIoctl(fd int, req uint, props *backlightProps) error {
|
||||
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(req), uintptr(unsafe.Pointer(props)))
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readBrightness(path string) (int, error) {
|
||||
fd, err := unix.Open(path, unix.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer unix.Close(fd)
|
||||
|
||||
var props backlightProps
|
||||
if err := backlightIoctl(fd, backlightGetStatus, &props); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int(props.brightness), nil
|
||||
}
|
||||
|
||||
func (b *BacklightBackend) Rescan() error {
|
||||
return b.scanDevices()
|
||||
}
|
||||
|
||||
func (b *BacklightBackend) GetDevices() ([]Device, error) {
|
||||
devices := make([]Device, 0)
|
||||
|
||||
b.devices.Range(func(id, path string) bool {
|
||||
brightness, err := readBrightness(path)
|
||||
if err != nil {
|
||||
log.Debugf("failed to read brightness for %s: %v", id, err)
|
||||
return true
|
||||
}
|
||||
|
||||
devices = append(devices, Device{
|
||||
Class: ClassBacklight,
|
||||
ID: id,
|
||||
Name: strings.TrimPrefix(id, "backlight:"),
|
||||
Current: brightness,
|
||||
Max: 100,
|
||||
CurrentPercent: brightness,
|
||||
Backend: "backlight",
|
||||
})
|
||||
return true
|
||||
})
|
||||
|
||||
return devices, nil
|
||||
}
|
||||
|
||||
func (b *BacklightBackend) SetBrightnessWithExponent(id string, percent int, exponential bool, exponent float64) error {
|
||||
if percent < 0 || percent > 100 {
|
||||
return fmt.Errorf("percent out of range: %d", percent)
|
||||
}
|
||||
|
||||
path, ok := b.devices.Load(id)
|
||||
if !ok {
|
||||
return fmt.Errorf("device not found: %s", id)
|
||||
}
|
||||
|
||||
value := percent
|
||||
switch {
|
||||
case percent == 0:
|
||||
value = 1
|
||||
case exponential:
|
||||
value = 1 + int(math.Round(math.Pow(float64(percent-1)/99.0, exponent)*99.0))
|
||||
}
|
||||
|
||||
fd, err := unix.Open(path, unix.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open %s: %w", path, err)
|
||||
}
|
||||
defer unix.Close(fd)
|
||||
|
||||
props := backlightProps{brightness: uint32(value)}
|
||||
if err := backlightIoctl(fd, backlightUpdateStatus, &props); err != nil {
|
||||
return fmt.Errorf("set brightness: %w", err)
|
||||
}
|
||||
|
||||
log.Debugf("set %s to %d%% (hw %d) via backlight(4)", id, percent, value)
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package brightness
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||
)
|
||||
|
||||
// devd(8) publishes device events on this SOCK_SEQPACKET socket, one
|
||||
// "!system=... subsystem=... type=..." record per packet.
|
||||
const devdSocketPath = "/var/run/devd.seqpacket.pipe"
|
||||
|
||||
const (
|
||||
devdMaxRetries = 5
|
||||
devdBaseDelay = 2 * time.Second
|
||||
devdMaxDelay = 60 * time.Second
|
||||
)
|
||||
|
||||
type DevdMonitor struct {
|
||||
stop chan struct{}
|
||||
rescanMutex sync.Mutex
|
||||
rescanTimer *time.Timer
|
||||
rescanPending bool
|
||||
}
|
||||
|
||||
func newDevdMonitor(manager *Manager) *DevdMonitor {
|
||||
m := &DevdMonitor{
|
||||
stop: make(chan struct{}),
|
||||
}
|
||||
|
||||
go m.run(manager)
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *DevdMonitor) run(manager *Manager) {
|
||||
failures := 0
|
||||
for {
|
||||
if err := m.monitorLoop(manager); err != nil {
|
||||
log.Errorf("Devd monitor error: %v", err)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-m.stop:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
failures++
|
||||
if failures > devdMaxRetries {
|
||||
log.Errorf("Devd monitor exceeded %d retries, giving up", devdMaxRetries)
|
||||
return
|
||||
}
|
||||
|
||||
delay := min(devdBaseDelay*time.Duration(1<<(failures-1)), devdMaxDelay)
|
||||
log.Infof("Devd monitor reconnecting in %v (attempt %d/%d)", delay, failures, devdMaxRetries)
|
||||
|
||||
select {
|
||||
case <-m.stop:
|
||||
return
|
||||
case <-time.After(delay):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *DevdMonitor) monitorLoop(manager *Manager) error {
|
||||
conn, err := net.Dial("unixpacket", devdSocketPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
go func() {
|
||||
select {
|
||||
case <-m.stop:
|
||||
conn.Close()
|
||||
case <-done:
|
||||
}
|
||||
}()
|
||||
|
||||
log.Info("Devd monitor started for backlight/drm events")
|
||||
|
||||
buf := make([]byte, 8192)
|
||||
for {
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
select {
|
||||
case <-m.stop:
|
||||
return nil
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
m.handleEvent(manager, string(buf[:n]))
|
||||
}
|
||||
}
|
||||
|
||||
func (m *DevdMonitor) handleEvent(manager *Manager, event string) {
|
||||
notification, ok := strings.CutPrefix(event, "!")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
fields := parseDevdEvent(notification)
|
||||
switch fields["system"] {
|
||||
case "DRM":
|
||||
m.debouncedRescan(manager)
|
||||
case "DEVFS":
|
||||
if fields["subsystem"] != "CDEV" {
|
||||
return
|
||||
}
|
||||
if !strings.HasPrefix(fields["cdev"], "backlight/") {
|
||||
return
|
||||
}
|
||||
m.debouncedRescan(manager)
|
||||
}
|
||||
}
|
||||
|
||||
func parseDevdEvent(s string) map[string]string {
|
||||
fields := make(map[string]string)
|
||||
for _, part := range strings.Fields(s) {
|
||||
k, v, ok := strings.Cut(part, "=")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
fields[k] = v
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
func (m *DevdMonitor) 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
|
||||
}
|
||||
|
||||
manager.Rescan()
|
||||
})
|
||||
}
|
||||
|
||||
func (m *DevdMonitor) Close() {
|
||||
close(m.stop)
|
||||
}
|
||||
@@ -20,7 +20,7 @@ func NewManagerWithOptions(exponential bool) (*Manager, error) {
|
||||
}
|
||||
|
||||
go m.initLogind()
|
||||
go m.initSysfs()
|
||||
go m.initNative()
|
||||
go m.initDDC()
|
||||
|
||||
return m, nil
|
||||
@@ -40,39 +40,6 @@ func (m *Manager) initLogind() {
|
||||
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 {
|
||||
@@ -96,9 +63,9 @@ func (m *Manager) Rescan() {
|
||||
}
|
||||
}
|
||||
|
||||
if m.sysfsReady && m.sysfsBackend != nil {
|
||||
if err := m.sysfsBackend.Rescan(); err != nil {
|
||||
log.Debugf("Sysfs rescan failed: %v", err)
|
||||
if m.nativeReady && m.nativeBackend != nil {
|
||||
if err := m.nativeBackend.Rescan(); err != nil {
|
||||
log.Debugf("Native backend rescan failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,10 +117,10 @@ func stateChanged(old, new State) bool {
|
||||
func (m *Manager) updateState() {
|
||||
allDevices := make([]Device, 0)
|
||||
|
||||
if m.sysfsReady && m.sysfsBackend != nil {
|
||||
devices, err := m.sysfsBackend.GetDevices()
|
||||
if m.nativeReady && m.nativeBackend != nil {
|
||||
devices, err := m.nativeBackend.GetDevices()
|
||||
if err != nil {
|
||||
log.Debugf("Failed to get sysfs devices: %v", err)
|
||||
log.Debugf("Failed to get native backend devices: %v", err)
|
||||
}
|
||||
if err == nil {
|
||||
allDevices = append(allDevices, devices...)
|
||||
@@ -232,18 +199,21 @@ func (m *Manager) SetBrightnessWithExponent(deviceID string, percent int, expone
|
||||
m.stateMutex.Unlock()
|
||||
|
||||
var err error
|
||||
if deviceClass == ClassDDC {
|
||||
switch {
|
||||
case 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 {
|
||||
case 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)
|
||||
case m.nativeBackend != nil:
|
||||
log.Debugf("Calling native backend for %s", deviceID)
|
||||
err = m.nativeBackend.SetBrightnessWithExponent(deviceID, percent, exponential, exponent)
|
||||
default:
|
||||
err = fmt.Errorf("no brightness backend for %s", deviceID)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package brightness
|
||||
|
||||
import (
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||
)
|
||||
|
||||
func (m *Manager) initNative() {
|
||||
log.Debug("Initializing backlight backend...")
|
||||
backend, err := NewBacklightBackend()
|
||||
if err != nil {
|
||||
log.Warnf("Failed to initialize backlight backend: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
devices, err := backend.GetDevices()
|
||||
if err == nil {
|
||||
log.Infof("Backlight backend initialized with %d devices", len(devices))
|
||||
}
|
||||
|
||||
m.nativeBackend = backend
|
||||
m.nativeReady = true
|
||||
m.updateState()
|
||||
m.monitor = newDevdMonitor(m)
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package brightness
|
||||
|
||||
import (
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||
)
|
||||
|
||||
func (m *Manager) initNative() {
|
||||
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.nativeBackend = sysfs
|
||||
m.nativeReady = true
|
||||
m.updateState()
|
||||
m.monitor = NewUdevMonitor(m)
|
||||
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.nativeBackend = sysfs
|
||||
m.nativeReady = true
|
||||
m.updateState()
|
||||
m.monitor = NewUdevMonitor(m)
|
||||
}
|
||||
+5
-4
@@ -43,7 +43,7 @@ func TestManager_SetBrightness_LogindSuccess(t *testing.T) {
|
||||
logindBackend: mockLogind,
|
||||
sysfsBackend: sysfs,
|
||||
logindReady: true,
|
||||
sysfsReady: true,
|
||||
nativeReady: true,
|
||||
stopChan: make(chan struct{}),
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ func TestManager_SetBrightness_LogindFailsFallbackToSysfs(t *testing.T) {
|
||||
logindBackend: mockLogind,
|
||||
sysfsBackend: sysfs,
|
||||
logindReady: true,
|
||||
sysfsReady: true,
|
||||
nativeReady: true,
|
||||
stopChan: make(chan struct{}),
|
||||
}
|
||||
|
||||
@@ -180,8 +180,9 @@ func TestManager_SetBrightness_NoLogind(t *testing.T) {
|
||||
m := &Manager{
|
||||
logindBackend: nil,
|
||||
sysfsBackend: sysfs,
|
||||
nativeBackend: sysfs,
|
||||
logindReady: false,
|
||||
sysfsReady: true,
|
||||
nativeReady: true,
|
||||
stopChan: make(chan struct{}),
|
||||
}
|
||||
|
||||
@@ -243,7 +244,7 @@ func TestManager_SetBrightness_LEDWithLogind(t *testing.T) {
|
||||
logindBackend: mockLogind,
|
||||
sysfsBackend: sysfs,
|
||||
logindReady: true,
|
||||
sysfsReady: true,
|
||||
nativeReady: true,
|
||||
stopChan: make(chan struct{}),
|
||||
}
|
||||
|
||||
@@ -33,14 +33,25 @@ type DeviceUpdate struct {
|
||||
Device Device `json:"device"`
|
||||
}
|
||||
|
||||
type Backend interface {
|
||||
Rescan() error
|
||||
GetDevices() ([]Device, error)
|
||||
SetBrightnessWithExponent(id string, percent int, exponential bool, exponent float64) error
|
||||
}
|
||||
|
||||
type deviceMonitor interface {
|
||||
Close()
|
||||
}
|
||||
|
||||
type Manager struct {
|
||||
logindBackend *LogindBackend
|
||||
sysfsBackend *SysfsBackend
|
||||
nativeBackend Backend
|
||||
ddcBackend *DDCBackend
|
||||
udevMonitor *UdevMonitor
|
||||
monitor deviceMonitor
|
||||
|
||||
logindReady bool
|
||||
sysfsReady bool
|
||||
nativeReady bool
|
||||
ddcReady bool
|
||||
|
||||
exponential bool
|
||||
@@ -170,8 +181,8 @@ func (m *Manager) Close() {
|
||||
return true
|
||||
})
|
||||
|
||||
if m.udevMonitor != nil {
|
||||
m.udevMonitor.Close()
|
||||
if m.monitor != nil {
|
||||
m.monitor.Close()
|
||||
}
|
||||
|
||||
if m.logindBackend != nil {
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ func setupTestManager(t *testing.T) (*Manager, string) {
|
||||
|
||||
m := &Manager{
|
||||
sysfsBackend: sysfs,
|
||||
sysfsReady: true,
|
||||
nativeReady: true,
|
||||
stopChan: make(chan struct{}),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user