mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-13 00:42:49 -05:00
rename backend to core
This commit is contained in:
281
core/internal/server/wlroutput/handlers.go
Normal file
281
core/internal/server/wlroutput/handlers.go
Normal file
@@ -0,0 +1,281 @@
|
||||
package wlroutput
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_output_management"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||
)
|
||||
|
||||
type Request struct {
|
||||
ID int `json:"id,omitempty"`
|
||||
Method string `json:"method"`
|
||||
Params map[string]interface{} `json:"params,omitempty"`
|
||||
}
|
||||
|
||||
type SuccessResult struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type HeadConfig struct {
|
||||
Name string `json:"name"`
|
||||
Enabled bool `json:"enabled"`
|
||||
ModeID *uint32 `json:"modeId,omitempty"`
|
||||
CustomMode *struct {
|
||||
Width int32 `json:"width"`
|
||||
Height int32 `json:"height"`
|
||||
Refresh int32 `json:"refresh"`
|
||||
} `json:"customMode,omitempty"`
|
||||
Position *struct{ X, Y int32 } `json:"position,omitempty"`
|
||||
Transform *int32 `json:"transform,omitempty"`
|
||||
Scale *float64 `json:"scale,omitempty"`
|
||||
AdaptiveSync *uint32 `json:"adaptiveSync,omitempty"`
|
||||
}
|
||||
|
||||
type ConfigurationRequest struct {
|
||||
Heads []HeadConfig `json:"heads"`
|
||||
Test bool `json:"test"`
|
||||
}
|
||||
|
||||
func HandleRequest(conn net.Conn, req Request, manager *Manager) {
|
||||
if manager == nil {
|
||||
models.RespondError(conn, req.ID, "wlroutput manager not initialized")
|
||||
return
|
||||
}
|
||||
|
||||
switch req.Method {
|
||||
case "wlroutput.getState":
|
||||
handleGetState(conn, req, manager)
|
||||
case "wlroutput.applyConfiguration":
|
||||
handleApplyConfiguration(conn, req, manager, false)
|
||||
case "wlroutput.testConfiguration":
|
||||
handleApplyConfiguration(conn, req, manager, true)
|
||||
case "wlroutput.subscribe":
|
||||
handleSubscribe(conn, req, manager)
|
||||
default:
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
|
||||
}
|
||||
}
|
||||
|
||||
func handleGetState(conn net.Conn, req Request, manager *Manager) {
|
||||
state := manager.GetState()
|
||||
models.Respond(conn, req.ID, state)
|
||||
}
|
||||
|
||||
func handleApplyConfiguration(conn net.Conn, req Request, manager *Manager, test bool) {
|
||||
headsParam, ok := req.Params["heads"]
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing 'heads' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
headsJSON, err := json.Marshal(headsParam)
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, "invalid 'heads' parameter format")
|
||||
return
|
||||
}
|
||||
|
||||
var heads []HeadConfig
|
||||
if err := json.Unmarshal(headsJSON, &heads); err != nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("invalid heads configuration: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if err := manager.ApplyConfiguration(heads, test); err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
msg := "configuration applied"
|
||||
if test {
|
||||
msg = "configuration test succeeded"
|
||||
}
|
||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: msg})
|
||||
}
|
||||
|
||||
func handleSubscribe(conn net.Conn, req Request, manager *Manager) {
|
||||
clientID := fmt.Sprintf("client-%p", conn)
|
||||
stateChan := manager.Subscribe(clientID)
|
||||
defer manager.Unsubscribe(clientID)
|
||||
|
||||
initialState := manager.GetState()
|
||||
if err := json.NewEncoder(conn).Encode(models.Response[State]{
|
||||
ID: req.ID,
|
||||
Result: &initialState,
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for state := range stateChan {
|
||||
if err := json.NewEncoder(conn).Encode(models.Response[State]{
|
||||
Result: &state,
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) ApplyConfiguration(heads []HeadConfig, test bool) error {
|
||||
if m.manager == nil {
|
||||
return fmt.Errorf("output manager not initialized")
|
||||
}
|
||||
|
||||
resultChan := make(chan error, 1)
|
||||
|
||||
m.post(func() {
|
||||
m.wlMutex.Lock()
|
||||
defer m.wlMutex.Unlock()
|
||||
|
||||
config, err := m.manager.CreateConfiguration(m.serial)
|
||||
if err != nil {
|
||||
resultChan <- fmt.Errorf("failed to create configuration: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
statusChan := make(chan error, 1)
|
||||
|
||||
config.SetSucceededHandler(func(e wlr_output_management.ZwlrOutputConfigurationV1SucceededEvent) {
|
||||
log.Info("WlrOutput: configuration succeeded")
|
||||
statusChan <- nil
|
||||
})
|
||||
|
||||
config.SetFailedHandler(func(e wlr_output_management.ZwlrOutputConfigurationV1FailedEvent) {
|
||||
log.Warn("WlrOutput: configuration failed")
|
||||
statusChan <- fmt.Errorf("compositor rejected configuration")
|
||||
})
|
||||
|
||||
config.SetCancelledHandler(func(e wlr_output_management.ZwlrOutputConfigurationV1CancelledEvent) {
|
||||
log.Warn("WlrOutput: configuration cancelled")
|
||||
statusChan <- fmt.Errorf("configuration cancelled (outdated serial)")
|
||||
})
|
||||
|
||||
m.headsMutex.RLock()
|
||||
headsByName := make(map[string]*headState)
|
||||
for _, head := range m.heads {
|
||||
if !head.finished {
|
||||
headsByName[head.name] = head
|
||||
}
|
||||
}
|
||||
m.headsMutex.RUnlock()
|
||||
|
||||
for _, headCfg := range heads {
|
||||
head, exists := headsByName[headCfg.Name]
|
||||
if !exists {
|
||||
config.Destroy()
|
||||
resultChan <- fmt.Errorf("head not found: %s", headCfg.Name)
|
||||
return
|
||||
}
|
||||
|
||||
if !headCfg.Enabled {
|
||||
if err := config.DisableHead(head.handle); err != nil {
|
||||
config.Destroy()
|
||||
resultChan <- fmt.Errorf("failed to disable head %s: %w", headCfg.Name, err)
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
headConfig, err := config.EnableHead(head.handle)
|
||||
if err != nil {
|
||||
config.Destroy()
|
||||
resultChan <- fmt.Errorf("failed to enable head %s: %w", headCfg.Name, err)
|
||||
return
|
||||
}
|
||||
|
||||
if headCfg.ModeID != nil {
|
||||
m.modesMutex.RLock()
|
||||
mode, exists := m.modes[*headCfg.ModeID]
|
||||
m.modesMutex.RUnlock()
|
||||
|
||||
if !exists {
|
||||
config.Destroy()
|
||||
resultChan <- fmt.Errorf("mode not found: %d", *headCfg.ModeID)
|
||||
return
|
||||
}
|
||||
|
||||
if err := headConfig.SetMode(mode.handle); err != nil {
|
||||
config.Destroy()
|
||||
resultChan <- fmt.Errorf("failed to set mode for %s: %w", headCfg.Name, err)
|
||||
return
|
||||
}
|
||||
} else if headCfg.CustomMode != nil {
|
||||
if err := headConfig.SetCustomMode(
|
||||
headCfg.CustomMode.Width,
|
||||
headCfg.CustomMode.Height,
|
||||
headCfg.CustomMode.Refresh,
|
||||
); err != nil {
|
||||
config.Destroy()
|
||||
resultChan <- fmt.Errorf("failed to set custom mode for %s: %w", headCfg.Name, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if headCfg.Position != nil {
|
||||
if err := headConfig.SetPosition(headCfg.Position.X, headCfg.Position.Y); err != nil {
|
||||
config.Destroy()
|
||||
resultChan <- fmt.Errorf("failed to set position for %s: %w", headCfg.Name, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if headCfg.Transform != nil {
|
||||
if err := headConfig.SetTransform(*headCfg.Transform); err != nil {
|
||||
config.Destroy()
|
||||
resultChan <- fmt.Errorf("failed to set transform for %s: %w", headCfg.Name, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if headCfg.Scale != nil {
|
||||
if err := headConfig.SetScale(*headCfg.Scale); err != nil {
|
||||
config.Destroy()
|
||||
resultChan <- fmt.Errorf("failed to set scale for %s: %w", headCfg.Name, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if headCfg.AdaptiveSync != nil {
|
||||
if err := headConfig.SetAdaptiveSync(*headCfg.AdaptiveSync); err != nil {
|
||||
config.Destroy()
|
||||
resultChan <- fmt.Errorf("failed to set adaptive sync for %s: %w", headCfg.Name, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var applyErr error
|
||||
if test {
|
||||
applyErr = config.Test()
|
||||
} else {
|
||||
applyErr = config.Apply()
|
||||
}
|
||||
|
||||
if applyErr != nil {
|
||||
config.Destroy()
|
||||
action := "apply"
|
||||
if test {
|
||||
action = "test"
|
||||
}
|
||||
resultChan <- fmt.Errorf("failed to %s configuration: %w", action, applyErr)
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case err := <-statusChan:
|
||||
config.Destroy()
|
||||
resultChan <- err
|
||||
case <-time.After(5 * time.Second):
|
||||
config.Destroy()
|
||||
resultChan <- fmt.Errorf("timeout waiting for configuration response")
|
||||
}
|
||||
}()
|
||||
})
|
||||
|
||||
return <-resultChan
|
||||
}
|
||||
511
core/internal/server/wlroutput/manager.go
Normal file
511
core/internal/server/wlroutput/manager.go
Normal file
@@ -0,0 +1,511 @@
|
||||
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, ¤tState) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
191
core/internal/server/wlroutput/types.go
Normal file
191
core/internal/server/wlroutput/types.go
Normal file
@@ -0,0 +1,191 @@
|
||||
package wlroutput
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_output_management"
|
||||
wlclient "github.com/yaslama/go-wayland/wayland/client"
|
||||
)
|
||||
|
||||
type OutputMode struct {
|
||||
Width int32 `json:"width"`
|
||||
Height int32 `json:"height"`
|
||||
Refresh int32 `json:"refresh"`
|
||||
Preferred bool `json:"preferred"`
|
||||
ID uint32 `json:"id"`
|
||||
}
|
||||
|
||||
type Output struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Make string `json:"make"`
|
||||
Model string `json:"model"`
|
||||
SerialNumber string `json:"serialNumber"`
|
||||
PhysicalWidth int32 `json:"physicalWidth"`
|
||||
PhysicalHeight int32 `json:"physicalHeight"`
|
||||
Enabled bool `json:"enabled"`
|
||||
X int32 `json:"x"`
|
||||
Y int32 `json:"y"`
|
||||
Transform int32 `json:"transform"`
|
||||
Scale float64 `json:"scale"`
|
||||
CurrentMode *OutputMode `json:"currentMode"`
|
||||
Modes []OutputMode `json:"modes"`
|
||||
AdaptiveSync uint32 `json:"adaptiveSync"`
|
||||
ID uint32 `json:"id"`
|
||||
}
|
||||
|
||||
type State struct {
|
||||
Outputs []Output `json:"outputs"`
|
||||
Serial uint32 `json:"serial"`
|
||||
}
|
||||
|
||||
type cmd struct {
|
||||
fn func()
|
||||
}
|
||||
|
||||
type Manager struct {
|
||||
display *wlclient.Display
|
||||
registry *wlclient.Registry
|
||||
manager *wlr_output_management.ZwlrOutputManagerV1
|
||||
|
||||
headsMutex sync.RWMutex
|
||||
heads map[uint32]*headState
|
||||
|
||||
modesMutex sync.RWMutex
|
||||
modes map[uint32]*modeState
|
||||
|
||||
serial uint32
|
||||
|
||||
wlMutex sync.Mutex
|
||||
cmdq chan cmd
|
||||
stopChan chan struct{}
|
||||
wg sync.WaitGroup
|
||||
|
||||
subscribers map[string]chan State
|
||||
subMutex sync.RWMutex
|
||||
dirty chan struct{}
|
||||
notifierWg sync.WaitGroup
|
||||
lastNotified *State
|
||||
|
||||
stateMutex sync.RWMutex
|
||||
state *State
|
||||
|
||||
fatalError chan error
|
||||
}
|
||||
|
||||
type headState struct {
|
||||
id uint32
|
||||
handle *wlr_output_management.ZwlrOutputHeadV1
|
||||
name string
|
||||
description string
|
||||
make string
|
||||
model string
|
||||
serialNumber string
|
||||
physicalWidth int32
|
||||
physicalHeight int32
|
||||
enabled bool
|
||||
x int32
|
||||
y int32
|
||||
transform int32
|
||||
scale float64
|
||||
currentModeID uint32
|
||||
modeIDs []uint32
|
||||
adaptiveSync uint32
|
||||
finished bool
|
||||
}
|
||||
|
||||
type modeState struct {
|
||||
id uint32
|
||||
handle *wlr_output_management.ZwlrOutputModeV1
|
||||
width int32
|
||||
height int32
|
||||
refresh int32
|
||||
preferred bool
|
||||
finished bool
|
||||
}
|
||||
|
||||
func (m *Manager) GetState() State {
|
||||
m.stateMutex.RLock()
|
||||
defer m.stateMutex.RUnlock()
|
||||
if m.state == nil {
|
||||
return State{
|
||||
Outputs: []Output{},
|
||||
Serial: 0,
|
||||
}
|
||||
}
|
||||
stateCopy := *m.state
|
||||
return stateCopy
|
||||
}
|
||||
|
||||
func (m *Manager) Subscribe(id string) chan State {
|
||||
ch := make(chan State, 64)
|
||||
m.subMutex.Lock()
|
||||
m.subscribers[id] = ch
|
||||
m.subMutex.Unlock()
|
||||
return ch
|
||||
}
|
||||
|
||||
func (m *Manager) Unsubscribe(id string) {
|
||||
m.subMutex.Lock()
|
||||
if ch, ok := m.subscribers[id]; ok {
|
||||
close(ch)
|
||||
delete(m.subscribers, id)
|
||||
}
|
||||
m.subMutex.Unlock()
|
||||
}
|
||||
|
||||
func (m *Manager) notifySubscribers() {
|
||||
select {
|
||||
case m.dirty <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) FatalError() <-chan error {
|
||||
return m.fatalError
|
||||
}
|
||||
|
||||
func stateChanged(old, new *State) bool {
|
||||
if old == nil || new == nil {
|
||||
return true
|
||||
}
|
||||
if old.Serial != new.Serial {
|
||||
return true
|
||||
}
|
||||
if len(old.Outputs) != len(new.Outputs) {
|
||||
return true
|
||||
}
|
||||
for i := range new.Outputs {
|
||||
if i >= len(old.Outputs) {
|
||||
return true
|
||||
}
|
||||
oldOut := &old.Outputs[i]
|
||||
newOut := &new.Outputs[i]
|
||||
if oldOut.Name != newOut.Name || oldOut.Enabled != newOut.Enabled {
|
||||
return true
|
||||
}
|
||||
if oldOut.X != newOut.X || oldOut.Y != newOut.Y {
|
||||
return true
|
||||
}
|
||||
if oldOut.Transform != newOut.Transform || oldOut.Scale != newOut.Scale {
|
||||
return true
|
||||
}
|
||||
if oldOut.AdaptiveSync != newOut.AdaptiveSync {
|
||||
return true
|
||||
}
|
||||
if (oldOut.CurrentMode == nil) != (newOut.CurrentMode == nil) {
|
||||
return true
|
||||
}
|
||||
if oldOut.CurrentMode != nil && newOut.CurrentMode != nil {
|
||||
if oldOut.CurrentMode.Width != newOut.CurrentMode.Width ||
|
||||
oldOut.CurrentMode.Height != newOut.CurrentMode.Height ||
|
||||
oldOut.CurrentMode.Refresh != newOut.CurrentMode.Refresh {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if len(oldOut.Modes) != len(newOut.Modes) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user