1
0
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:
bbedward
2026-07-19 10:31:48 -04:00
parent 069df80b22
commit 90291bd627
46 changed files with 3168 additions and 209 deletions
@@ -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)
}
+15 -45
View File
@@ -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)
}
@@ -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{}),
}
+15 -4
View File
@@ -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 {
@@ -33,7 +33,7 @@ func setupTestManager(t *testing.T) (*Manager, string) {
m := &Manager{
sysfsBackend: sysfs,
sysfsReady: true,
nativeReady: true,
stopChan: make(chan struct{}),
}