1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-05 21:15:38 -05:00
Files
DankMaterialShell/core/internal/server/wlroutput/handlers.go
2025-12-01 11:04:37 -05:00

279 lines
7.3 KiB
Go

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]any `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)")
})
headsByName := make(map[string]*headState)
m.heads.Range(func(key uint32, head *headState) bool {
if !head.finished {
headsByName[head.name] = head
}
return true
})
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 {
mode, exists := m.modes.Load(*headCfg.ModeID)
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
}