1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-11 07:52:50 -05:00
Files
DankMaterialShell/core/internal/server/wlroutput/manager.go
2025-11-12 23:12:31 -05:00

512 lines
11 KiB
Go

package wlroutput
import (
"fmt"
"time"
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_output_management"
wlclient "github.com/yaslama/go-wayland/wayland/client"
)
func NewManager(display *wlclient.Display) (*Manager, error) {
m := &Manager{
display: display,
heads: make(map[uint32]*headState),
modes: make(map[uint32]*modeState),
cmdq: make(chan cmd, 128),
stopChan: make(chan struct{}),
subscribers: make(map[string]chan State),
dirty: make(chan struct{}, 1),
fatalError: make(chan error, 1),
}
m.wg.Add(1)
go m.waylandActor()
if err := m.setupRegistry(); err != nil {
close(m.stopChan)
m.wg.Wait()
return nil, err
}
m.updateState()
m.notifierWg.Add(1)
go m.notifier()
return m, nil
}
func (m *Manager) post(fn func()) {
select {
case m.cmdq <- cmd{fn: fn}:
default:
log.Warn("WlrOutput actor command queue full, dropping command")
}
}
func (m *Manager) waylandActor() {
defer m.wg.Done()
defer func() {
if r := recover(); r != nil {
err := fmt.Errorf("waylandActor panic: %v", r)
log.Errorf("WlrOutput: %v", err)
select {
case m.fatalError <- err:
default:
}
select {
case <-m.stopChan:
default:
close(m.stopChan)
}
}
}()
for {
select {
case <-m.stopChan:
return
case c := <-m.cmdq:
func() {
defer func() {
if r := recover(); r != nil {
log.Errorf("WlrOutput: command execution panic: %v", r)
}
}()
c.fn()
}()
}
}
}
func (m *Manager) setupRegistry() error {
log.Info("WlrOutput: starting registry setup")
ctx := m.display.Context()
registry, err := m.display.GetRegistry()
if err != nil {
return fmt.Errorf("failed to get registry: %w", err)
}
m.registry = registry
registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) {
if e.Interface == wlr_output_management.ZwlrOutputManagerV1InterfaceName {
log.Infof("WlrOutput: found %s", wlr_output_management.ZwlrOutputManagerV1InterfaceName)
manager := wlr_output_management.NewZwlrOutputManagerV1(ctx)
version := e.Version
if version > 4 {
version = 4
}
manager.SetHeadHandler(func(e wlr_output_management.ZwlrOutputManagerV1HeadEvent) {
m.handleHead(e)
})
manager.SetDoneHandler(func(e wlr_output_management.ZwlrOutputManagerV1DoneEvent) {
log.Debugf("WlrOutput: done event received (serial=%d)", e.Serial)
m.serial = e.Serial
m.post(func() {
m.updateState()
})
})
manager.SetFinishedHandler(func(e wlr_output_management.ZwlrOutputManagerV1FinishedEvent) {
log.Info("WlrOutput: finished event received")
})
if err := registry.Bind(e.Name, e.Interface, version, manager); err == nil {
m.manager = manager
log.Info("WlrOutput: manager bound successfully")
} else {
log.Errorf("WlrOutput: failed to bind manager: %v", err)
}
}
})
log.Info("WlrOutput: registry setup complete (events will be processed async)")
return nil
}
func (m *Manager) handleHead(e wlr_output_management.ZwlrOutputManagerV1HeadEvent) {
handle := e.Head
headID := handle.ID()
log.Debugf("WlrOutput: New head (id=%d)", headID)
head := &headState{
id: headID,
handle: handle,
modeIDs: make([]uint32, 0),
}
m.headsMutex.Lock()
m.heads[headID] = head
m.headsMutex.Unlock()
handle.SetNameHandler(func(e wlr_output_management.ZwlrOutputHeadV1NameEvent) {
log.Debugf("WlrOutput: Head %d name: %s", headID, e.Name)
head.name = e.Name
m.post(func() {
m.updateState()
})
})
handle.SetDescriptionHandler(func(e wlr_output_management.ZwlrOutputHeadV1DescriptionEvent) {
log.Debugf("WlrOutput: Head %d description: %s", headID, e.Description)
head.description = e.Description
m.post(func() {
m.updateState()
})
})
handle.SetPhysicalSizeHandler(func(e wlr_output_management.ZwlrOutputHeadV1PhysicalSizeEvent) {
log.Debugf("WlrOutput: Head %d physical size: %dx%d", headID, e.Width, e.Height)
head.physicalWidth = e.Width
head.physicalHeight = e.Height
m.post(func() {
m.updateState()
})
})
handle.SetModeHandler(func(e wlr_output_management.ZwlrOutputHeadV1ModeEvent) {
m.handleMode(headID, e)
})
handle.SetEnabledHandler(func(e wlr_output_management.ZwlrOutputHeadV1EnabledEvent) {
log.Debugf("WlrOutput: Head %d enabled: %d", headID, e.Enabled)
head.enabled = e.Enabled != 0
m.post(func() {
m.updateState()
})
})
handle.SetCurrentModeHandler(func(e wlr_output_management.ZwlrOutputHeadV1CurrentModeEvent) {
modeID := e.Mode.ID()
log.Debugf("WlrOutput: Head %d current mode: %d", headID, modeID)
head.currentModeID = modeID
m.post(func() {
m.updateState()
})
})
handle.SetPositionHandler(func(e wlr_output_management.ZwlrOutputHeadV1PositionEvent) {
log.Debugf("WlrOutput: Head %d position: %d,%d", headID, e.X, e.Y)
head.x = e.X
head.y = e.Y
m.post(func() {
m.updateState()
})
})
handle.SetTransformHandler(func(e wlr_output_management.ZwlrOutputHeadV1TransformEvent) {
log.Debugf("WlrOutput: Head %d transform: %d", headID, e.Transform)
head.transform = e.Transform
m.post(func() {
m.updateState()
})
})
handle.SetScaleHandler(func(e wlr_output_management.ZwlrOutputHeadV1ScaleEvent) {
log.Debugf("WlrOutput: Head %d scale: %f", headID, e.Scale)
head.scale = e.Scale
m.post(func() {
m.updateState()
})
})
handle.SetMakeHandler(func(e wlr_output_management.ZwlrOutputHeadV1MakeEvent) {
log.Debugf("WlrOutput: Head %d make: %s", headID, e.Make)
head.make = e.Make
m.post(func() {
m.updateState()
})
})
handle.SetModelHandler(func(e wlr_output_management.ZwlrOutputHeadV1ModelEvent) {
log.Debugf("WlrOutput: Head %d model: %s", headID, e.Model)
head.model = e.Model
m.post(func() {
m.updateState()
})
})
handle.SetSerialNumberHandler(func(e wlr_output_management.ZwlrOutputHeadV1SerialNumberEvent) {
log.Debugf("WlrOutput: Head %d serial: %s", headID, e.SerialNumber)
head.serialNumber = e.SerialNumber
m.post(func() {
m.updateState()
})
})
handle.SetAdaptiveSyncHandler(func(e wlr_output_management.ZwlrOutputHeadV1AdaptiveSyncEvent) {
log.Debugf("WlrOutput: Head %d adaptive sync: %d", headID, e.State)
head.adaptiveSync = e.State
m.post(func() {
m.updateState()
})
})
handle.SetFinishedHandler(func(e wlr_output_management.ZwlrOutputHeadV1FinishedEvent) {
log.Debugf("WlrOutput: Head %d finished", headID)
head.finished = true
m.headsMutex.Lock()
delete(m.heads, headID)
m.headsMutex.Unlock()
m.post(func() {
m.wlMutex.Lock()
handle.Release()
m.wlMutex.Unlock()
m.updateState()
})
})
}
func (m *Manager) handleMode(headID uint32, e wlr_output_management.ZwlrOutputHeadV1ModeEvent) {
handle := e.Mode
modeID := handle.ID()
log.Debugf("WlrOutput: Head %d new mode (id=%d)", headID, modeID)
mode := &modeState{
id: modeID,
handle: handle,
}
m.modesMutex.Lock()
m.modes[modeID] = mode
m.modesMutex.Unlock()
m.headsMutex.Lock()
if head, ok := m.heads[headID]; ok {
head.modeIDs = append(head.modeIDs, modeID)
}
m.headsMutex.Unlock()
handle.SetSizeHandler(func(e wlr_output_management.ZwlrOutputModeV1SizeEvent) {
log.Debugf("WlrOutput: Mode %d size: %dx%d", modeID, e.Width, e.Height)
mode.width = e.Width
mode.height = e.Height
m.post(func() {
m.updateState()
})
})
handle.SetRefreshHandler(func(e wlr_output_management.ZwlrOutputModeV1RefreshEvent) {
log.Debugf("WlrOutput: Mode %d refresh: %d", modeID, e.Refresh)
mode.refresh = e.Refresh
m.post(func() {
m.updateState()
})
})
handle.SetPreferredHandler(func(e wlr_output_management.ZwlrOutputModeV1PreferredEvent) {
log.Debugf("WlrOutput: Mode %d preferred", modeID)
mode.preferred = true
m.post(func() {
m.updateState()
})
})
handle.SetFinishedHandler(func(e wlr_output_management.ZwlrOutputModeV1FinishedEvent) {
log.Debugf("WlrOutput: Mode %d finished", modeID)
mode.finished = true
m.modesMutex.Lock()
delete(m.modes, modeID)
m.modesMutex.Unlock()
m.post(func() {
m.wlMutex.Lock()
handle.Release()
m.wlMutex.Unlock()
m.updateState()
})
})
}
func (m *Manager) updateState() {
m.headsMutex.RLock()
m.modesMutex.RLock()
outputs := make([]Output, 0)
for _, head := range m.heads {
if head.finished {
continue
}
modes := make([]OutputMode, 0)
var currentMode *OutputMode
for _, modeID := range head.modeIDs {
mode, exists := m.modes[modeID]
if !exists || mode.finished {
continue
}
outMode := OutputMode{
Width: mode.width,
Height: mode.height,
Refresh: mode.refresh,
Preferred: mode.preferred,
ID: modeID,
}
modes = append(modes, outMode)
if modeID == head.currentModeID {
currentMode = &outMode
}
}
output := Output{
Name: head.name,
Description: head.description,
Make: head.make,
Model: head.model,
SerialNumber: head.serialNumber,
PhysicalWidth: head.physicalWidth,
PhysicalHeight: head.physicalHeight,
Enabled: head.enabled,
X: head.x,
Y: head.y,
Transform: head.transform,
Scale: head.scale,
CurrentMode: currentMode,
Modes: modes,
AdaptiveSync: head.adaptiveSync,
ID: head.id,
}
outputs = append(outputs, output)
}
m.modesMutex.RUnlock()
m.headsMutex.RUnlock()
newState := State{
Outputs: outputs,
Serial: m.serial,
}
m.stateMutex.Lock()
m.state = &newState
m.stateMutex.Unlock()
m.notifySubscribers()
}
func (m *Manager) notifier() {
defer m.notifierWg.Done()
defer func() {
if r := recover(); r != nil {
err := fmt.Errorf("notifier panic: %v", r)
log.Errorf("WlrOutput: %v", err)
select {
case m.fatalError <- err:
default:
}
select {
case <-m.stopChan:
default:
close(m.stopChan)
}
}
}()
const minGap = 100 * time.Millisecond
timer := time.NewTimer(minGap)
timer.Stop()
var pending bool
for {
select {
case <-m.stopChan:
timer.Stop()
return
case <-m.dirty:
if pending {
continue
}
pending = true
timer.Reset(minGap)
case <-timer.C:
if !pending {
continue
}
m.subMutex.RLock()
subCount := len(m.subscribers)
m.subMutex.RUnlock()
if subCount == 0 {
pending = false
continue
}
currentState := m.GetState()
if m.lastNotified != nil && !stateChanged(m.lastNotified, &currentState) {
pending = false
continue
}
m.subMutex.RLock()
for _, ch := range m.subscribers {
select {
case ch <- currentState:
default:
log.Warn("WlrOutput: subscriber channel full, dropping update")
}
}
m.subMutex.RUnlock()
stateCopy := currentState
m.lastNotified = &stateCopy
pending = false
}
}
}
func (m *Manager) Close() {
close(m.stopChan)
m.wg.Wait()
m.notifierWg.Wait()
m.subMutex.Lock()
for _, ch := range m.subscribers {
close(ch)
}
m.subscribers = make(map[string]chan State)
m.subMutex.Unlock()
m.modesMutex.Lock()
for _, mode := range m.modes {
if mode.handle != nil {
mode.handle.Release()
}
}
m.modes = make(map[uint32]*modeState)
m.modesMutex.Unlock()
m.headsMutex.Lock()
for _, head := range m.heads {
if head.handle != nil {
head.handle.Release()
}
}
m.heads = make(map[uint32]*headState)
m.headsMutex.Unlock()
if m.manager != nil {
m.manager.Stop()
}
}