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:
144
core/internal/server/dwl/handlers.go
Normal file
144
core/internal/server/dwl/handlers.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package dwl
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"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"`
|
||||
}
|
||||
|
||||
func HandleRequest(conn net.Conn, req Request, manager *Manager) {
|
||||
if manager == nil {
|
||||
models.RespondError(conn, req.ID, "dwl manager not initialized")
|
||||
return
|
||||
}
|
||||
|
||||
switch req.Method {
|
||||
case "dwl.getState":
|
||||
handleGetState(conn, req, manager)
|
||||
case "dwl.setTags":
|
||||
handleSetTags(conn, req, manager)
|
||||
case "dwl.setClientTags":
|
||||
handleSetClientTags(conn, req, manager)
|
||||
case "dwl.setLayout":
|
||||
handleSetLayout(conn, req, manager)
|
||||
case "dwl.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 handleSetTags(conn net.Conn, req Request, manager *Manager) {
|
||||
output, ok := req.Params["output"].(string)
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'output' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
tagmask, ok := req.Params["tagmask"].(float64)
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'tagmask' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
toggleTagset, ok := req.Params["toggleTagset"].(float64)
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'toggleTagset' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
if err := manager.SetTags(output, uint32(tagmask), uint32(toggleTagset)); err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "tags set"})
|
||||
}
|
||||
|
||||
func handleSetClientTags(conn net.Conn, req Request, manager *Manager) {
|
||||
output, ok := req.Params["output"].(string)
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'output' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
andTags, ok := req.Params["andTags"].(float64)
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'andTags' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
xorTags, ok := req.Params["xorTags"].(float64)
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'xorTags' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
if err := manager.SetClientTags(output, uint32(andTags), uint32(xorTags)); err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "client tags set"})
|
||||
}
|
||||
|
||||
func handleSetLayout(conn net.Conn, req Request, manager *Manager) {
|
||||
output, ok := req.Params["output"].(string)
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'output' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
index, ok := req.Params["index"].(float64)
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'index' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
if err := manager.SetLayout(output, uint32(index)); err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "layout set"})
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
539
core/internal/server/dwl/manager.go
Normal file
539
core/internal/server/dwl/manager.go
Normal file
@@ -0,0 +1,539 @@
|
||||
package dwl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
wlclient "github.com/yaslama/go-wayland/wayland/client"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/dwl_ipc"
|
||||
)
|
||||
|
||||
func NewManager(display *wlclient.Display) (*Manager, error) {
|
||||
m := &Manager{
|
||||
display: display,
|
||||
outputs: make(map[uint32]*outputState),
|
||||
cmdq: make(chan cmd, 128),
|
||||
outputSetupReq: make(chan uint32, 16),
|
||||
stopChan: make(chan struct{}),
|
||||
subscribers: make(map[string]chan State),
|
||||
dirty: make(chan struct{}, 1),
|
||||
layouts: make([]string, 0),
|
||||
}
|
||||
|
||||
if err := m.setupRegistry(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m.updateState()
|
||||
|
||||
m.notifierWg.Add(1)
|
||||
go m.notifier()
|
||||
|
||||
m.wg.Add(1)
|
||||
go m.waylandActor()
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *Manager) post(fn func()) {
|
||||
select {
|
||||
case m.cmdq <- cmd{fn: fn}:
|
||||
default:
|
||||
log.Warn("DWL actor command queue full, dropping command")
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) waylandActor() {
|
||||
defer m.wg.Done()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-m.stopChan:
|
||||
return
|
||||
case c := <-m.cmdq:
|
||||
c.fn()
|
||||
case outputID := <-m.outputSetupReq:
|
||||
m.outputsMutex.RLock()
|
||||
out, exists := m.outputs[outputID]
|
||||
m.outputsMutex.RUnlock()
|
||||
|
||||
if !exists {
|
||||
log.Warnf("DWL: Output %d no longer exists, skipping setup", outputID)
|
||||
continue
|
||||
}
|
||||
|
||||
if out.ipcOutput != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
mgr, ok := m.manager.(*dwl_ipc.ZdwlIpcManagerV2)
|
||||
if !ok || mgr == nil {
|
||||
log.Errorf("DWL: Manager not available for output %d setup", outputID)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Infof("DWL: Setting up ipcOutput for dynamically added output %d", outputID)
|
||||
if err := m.setupOutput(mgr, out.output); err != nil {
|
||||
log.Errorf("DWL: Failed to setup output %d: %v", outputID, err)
|
||||
} else {
|
||||
m.updateState()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) setupRegistry() error {
|
||||
log.Info("DWL: 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
|
||||
|
||||
outputs := make([]*wlclient.Output, 0)
|
||||
outputRegNames := make(map[uint32]uint32)
|
||||
var dwlMgr *dwl_ipc.ZdwlIpcManagerV2
|
||||
|
||||
registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) {
|
||||
switch e.Interface {
|
||||
case dwl_ipc.ZdwlIpcManagerV2InterfaceName:
|
||||
log.Infof("DWL: found %s", dwl_ipc.ZdwlIpcManagerV2InterfaceName)
|
||||
manager := dwl_ipc.NewZdwlIpcManagerV2(ctx)
|
||||
version := e.Version
|
||||
if version > 1 {
|
||||
version = 1
|
||||
}
|
||||
if err := registry.Bind(e.Name, e.Interface, version, manager); err == nil {
|
||||
dwlMgr = manager
|
||||
log.Info("DWL: manager bound successfully")
|
||||
} else {
|
||||
log.Errorf("DWL: failed to bind manager: %v", err)
|
||||
}
|
||||
case "wl_output":
|
||||
log.Debugf("DWL: found wl_output (name=%d)", e.Name)
|
||||
output := wlclient.NewOutput(ctx)
|
||||
|
||||
outState := &outputState{
|
||||
registryName: e.Name,
|
||||
output: output,
|
||||
tags: make([]TagState, 0),
|
||||
}
|
||||
|
||||
output.SetNameHandler(func(ev wlclient.OutputNameEvent) {
|
||||
log.Debugf("DWL: Output name: %s (registry=%d)", ev.Name, e.Name)
|
||||
outState.name = ev.Name
|
||||
})
|
||||
|
||||
output.SetDescriptionHandler(func(ev wlclient.OutputDescriptionEvent) {
|
||||
log.Debugf("DWL: Output description: %s", ev.Description)
|
||||
})
|
||||
|
||||
version := e.Version
|
||||
if version > 4 {
|
||||
version = 4
|
||||
}
|
||||
if err := registry.Bind(e.Name, e.Interface, version, output); err == nil {
|
||||
outputID := output.ID()
|
||||
outState.id = outputID
|
||||
log.Infof("DWL: Bound wl_output id=%d registry_name=%d", outputID, e.Name)
|
||||
outputs = append(outputs, output)
|
||||
outputRegNames[outputID] = e.Name
|
||||
|
||||
m.outputsMutex.Lock()
|
||||
m.outputs[outputID] = outState
|
||||
m.outputsMutex.Unlock()
|
||||
|
||||
if m.manager != nil {
|
||||
select {
|
||||
case m.outputSetupReq <- outputID:
|
||||
log.Debugf("DWL: Queued setup for output %d", outputID)
|
||||
default:
|
||||
log.Warnf("DWL: Setup queue full, output %d will not be initialized", outputID)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Errorf("DWL: Failed to bind wl_output: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
registry.SetGlobalRemoveHandler(func(e wlclient.RegistryGlobalRemoveEvent) {
|
||||
m.post(func() {
|
||||
m.outputsMutex.Lock()
|
||||
var outToRelease *outputState
|
||||
for id, out := range m.outputs {
|
||||
if out.registryName == e.Name {
|
||||
log.Infof("DWL: Output %d removed", id)
|
||||
outToRelease = out
|
||||
delete(m.outputs, id)
|
||||
break
|
||||
}
|
||||
}
|
||||
m.outputsMutex.Unlock()
|
||||
|
||||
if outToRelease != nil {
|
||||
if ipcOut, ok := outToRelease.ipcOutput.(*dwl_ipc.ZdwlIpcOutputV2); ok && ipcOut != nil {
|
||||
m.wlMutex.Lock()
|
||||
ipcOut.Release()
|
||||
m.wlMutex.Unlock()
|
||||
log.Debugf("DWL: Released ipcOutput for removed output %d", outToRelease.id)
|
||||
}
|
||||
m.updateState()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
if err := m.display.Roundtrip(); err != nil {
|
||||
return fmt.Errorf("first roundtrip failed: %w", err)
|
||||
}
|
||||
if err := m.display.Roundtrip(); err != nil {
|
||||
return fmt.Errorf("second roundtrip failed: %w", err)
|
||||
}
|
||||
|
||||
if dwlMgr == nil {
|
||||
log.Info("DWL: manager not found in registry")
|
||||
return fmt.Errorf("dwl_ipc_manager_v2 not available")
|
||||
}
|
||||
|
||||
dwlMgr.SetTagsHandler(func(e dwl_ipc.ZdwlIpcManagerV2TagsEvent) {
|
||||
log.Infof("DWL: Tags count: %d", e.Amount)
|
||||
m.tagCount = e.Amount
|
||||
m.updateState()
|
||||
})
|
||||
|
||||
dwlMgr.SetLayoutHandler(func(e dwl_ipc.ZdwlIpcManagerV2LayoutEvent) {
|
||||
log.Infof("DWL: Layout: %s", e.Name)
|
||||
m.layouts = append(m.layouts, e.Name)
|
||||
m.updateState()
|
||||
})
|
||||
|
||||
m.manager = dwlMgr
|
||||
|
||||
for _, output := range outputs {
|
||||
if err := m.setupOutput(dwlMgr, output); err != nil {
|
||||
log.Warnf("DWL: Failed to setup output %d: %v", output.ID(), err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := m.display.Roundtrip(); err != nil {
|
||||
return fmt.Errorf("final roundtrip failed: %w", err)
|
||||
}
|
||||
|
||||
log.Info("DWL: registry setup complete")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) setupOutput(manager *dwl_ipc.ZdwlIpcManagerV2, output *wlclient.Output) error {
|
||||
m.wlMutex.Lock()
|
||||
ipcOutput, err := manager.GetOutput(output)
|
||||
m.wlMutex.Unlock()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get dwl output: %w", err)
|
||||
}
|
||||
|
||||
m.outputsMutex.Lock()
|
||||
outState, exists := m.outputs[output.ID()]
|
||||
if !exists {
|
||||
m.outputsMutex.Unlock()
|
||||
return fmt.Errorf("output state not found for id %d", output.ID())
|
||||
}
|
||||
outState.ipcOutput = ipcOutput
|
||||
m.outputsMutex.Unlock()
|
||||
|
||||
ipcOutput.SetActiveHandler(func(e dwl_ipc.ZdwlIpcOutputV2ActiveEvent) {
|
||||
outState.active = e.Active
|
||||
})
|
||||
|
||||
ipcOutput.SetTagHandler(func(e dwl_ipc.ZdwlIpcOutputV2TagEvent) {
|
||||
updated := false
|
||||
for i, tag := range outState.tags {
|
||||
if tag.Tag == e.Tag {
|
||||
outState.tags[i] = TagState{
|
||||
Tag: e.Tag,
|
||||
State: e.State,
|
||||
Clients: e.Clients,
|
||||
Focused: e.Focused,
|
||||
}
|
||||
updated = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !updated {
|
||||
outState.tags = append(outState.tags, TagState{
|
||||
Tag: e.Tag,
|
||||
State: e.State,
|
||||
Clients: e.Clients,
|
||||
Focused: e.Focused,
|
||||
})
|
||||
}
|
||||
|
||||
m.updateState()
|
||||
})
|
||||
|
||||
ipcOutput.SetLayoutHandler(func(e dwl_ipc.ZdwlIpcOutputV2LayoutEvent) {
|
||||
outState.layout = e.Layout
|
||||
})
|
||||
|
||||
ipcOutput.SetTitleHandler(func(e dwl_ipc.ZdwlIpcOutputV2TitleEvent) {
|
||||
outState.title = e.Title
|
||||
})
|
||||
|
||||
ipcOutput.SetAppidHandler(func(e dwl_ipc.ZdwlIpcOutputV2AppidEvent) {
|
||||
outState.appID = e.Appid
|
||||
})
|
||||
|
||||
ipcOutput.SetLayoutSymbolHandler(func(e dwl_ipc.ZdwlIpcOutputV2LayoutSymbolEvent) {
|
||||
outState.layoutSymbol = e.Layout
|
||||
})
|
||||
|
||||
ipcOutput.SetFrameHandler(func(e dwl_ipc.ZdwlIpcOutputV2FrameEvent) {
|
||||
m.updateState()
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) updateState() {
|
||||
m.outputsMutex.RLock()
|
||||
outputs := make(map[string]*OutputState)
|
||||
activeOutput := ""
|
||||
|
||||
for _, out := range m.outputs {
|
||||
name := out.name
|
||||
if name == "" {
|
||||
name = fmt.Sprintf("output-%d", out.id)
|
||||
}
|
||||
|
||||
tagsCopy := make([]TagState, len(out.tags))
|
||||
copy(tagsCopy, out.tags)
|
||||
|
||||
outputs[name] = &OutputState{
|
||||
Name: name,
|
||||
Active: out.active,
|
||||
Tags: tagsCopy,
|
||||
Layout: out.layout,
|
||||
LayoutSymbol: out.layoutSymbol,
|
||||
Title: out.title,
|
||||
AppID: out.appID,
|
||||
}
|
||||
|
||||
if out.active != 0 {
|
||||
activeOutput = name
|
||||
}
|
||||
}
|
||||
m.outputsMutex.RUnlock()
|
||||
|
||||
newState := State{
|
||||
Outputs: outputs,
|
||||
TagCount: m.tagCount,
|
||||
Layouts: m.layouts,
|
||||
ActiveOutput: activeOutput,
|
||||
}
|
||||
|
||||
m.stateMutex.Lock()
|
||||
m.state = &newState
|
||||
m.stateMutex.Unlock()
|
||||
|
||||
m.notifySubscribers()
|
||||
}
|
||||
|
||||
func (m *Manager) notifier() {
|
||||
defer m.notifierWg.Done()
|
||||
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("DWL: subscriber channel full, dropping update")
|
||||
}
|
||||
}
|
||||
m.subMutex.RUnlock()
|
||||
|
||||
stateCopy := currentState
|
||||
m.lastNotified = &stateCopy
|
||||
pending = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) ensureOutputSetup(out *outputState) error {
|
||||
if out.ipcOutput != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("output not yet initialized - setup in progress, retry in a moment")
|
||||
}
|
||||
|
||||
func (m *Manager) SetTags(outputName string, tagmask uint32, toggleTagset uint32) error {
|
||||
m.outputsMutex.RLock()
|
||||
|
||||
availableOutputs := make([]string, 0, len(m.outputs))
|
||||
var targetOut *outputState
|
||||
for _, out := range m.outputs {
|
||||
name := out.name
|
||||
if name == "" {
|
||||
name = fmt.Sprintf("output-%d", out.id)
|
||||
}
|
||||
availableOutputs = append(availableOutputs, name)
|
||||
if name == outputName {
|
||||
targetOut = out
|
||||
break
|
||||
}
|
||||
}
|
||||
m.outputsMutex.RUnlock()
|
||||
|
||||
if targetOut == nil {
|
||||
return fmt.Errorf("output not found: %s (available: %v)", outputName, availableOutputs)
|
||||
}
|
||||
|
||||
if err := m.ensureOutputSetup(targetOut); err != nil {
|
||||
return fmt.Errorf("failed to setup output %s: %w", outputName, err)
|
||||
}
|
||||
|
||||
ipcOut, ok := targetOut.ipcOutput.(*dwl_ipc.ZdwlIpcOutputV2)
|
||||
if !ok {
|
||||
return fmt.Errorf("output %s has invalid ipcOutput type", outputName)
|
||||
}
|
||||
|
||||
m.wlMutex.Lock()
|
||||
err := ipcOut.SetTags(tagmask, toggleTagset)
|
||||
m.wlMutex.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Manager) SetClientTags(outputName string, andTags uint32, xorTags uint32) error {
|
||||
m.outputsMutex.RLock()
|
||||
|
||||
var targetOut *outputState
|
||||
for _, out := range m.outputs {
|
||||
name := out.name
|
||||
if name == "" {
|
||||
name = fmt.Sprintf("output-%d", out.id)
|
||||
}
|
||||
if name == outputName {
|
||||
targetOut = out
|
||||
break
|
||||
}
|
||||
}
|
||||
m.outputsMutex.RUnlock()
|
||||
|
||||
if targetOut == nil {
|
||||
return fmt.Errorf("output not found: %s", outputName)
|
||||
}
|
||||
|
||||
if err := m.ensureOutputSetup(targetOut); err != nil {
|
||||
return fmt.Errorf("failed to setup output %s: %w", outputName, err)
|
||||
}
|
||||
|
||||
ipcOut, ok := targetOut.ipcOutput.(*dwl_ipc.ZdwlIpcOutputV2)
|
||||
if !ok {
|
||||
return fmt.Errorf("output %s has invalid ipcOutput type", outputName)
|
||||
}
|
||||
|
||||
m.wlMutex.Lock()
|
||||
err := ipcOut.SetClientTags(andTags, xorTags)
|
||||
m.wlMutex.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Manager) SetLayout(outputName string, index uint32) error {
|
||||
m.outputsMutex.RLock()
|
||||
|
||||
var targetOut *outputState
|
||||
for _, out := range m.outputs {
|
||||
name := out.name
|
||||
if name == "" {
|
||||
name = fmt.Sprintf("output-%d", out.id)
|
||||
}
|
||||
if name == outputName {
|
||||
targetOut = out
|
||||
break
|
||||
}
|
||||
}
|
||||
m.outputsMutex.RUnlock()
|
||||
|
||||
if targetOut == nil {
|
||||
return fmt.Errorf("output not found: %s", outputName)
|
||||
}
|
||||
|
||||
if err := m.ensureOutputSetup(targetOut); err != nil {
|
||||
return fmt.Errorf("failed to setup output %s: %w", outputName, err)
|
||||
}
|
||||
|
||||
ipcOut, ok := targetOut.ipcOutput.(*dwl_ipc.ZdwlIpcOutputV2)
|
||||
if !ok {
|
||||
return fmt.Errorf("output %s has invalid ipcOutput type", outputName)
|
||||
}
|
||||
|
||||
m.wlMutex.Lock()
|
||||
err := ipcOut.SetLayout(index)
|
||||
m.wlMutex.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
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.outputsMutex.Lock()
|
||||
for _, out := range m.outputs {
|
||||
if ipcOut, ok := out.ipcOutput.(*dwl_ipc.ZdwlIpcOutputV2); ok {
|
||||
ipcOut.Release()
|
||||
}
|
||||
}
|
||||
m.outputs = make(map[uint32]*outputState)
|
||||
m.outputsMutex.Unlock()
|
||||
|
||||
if mgr, ok := m.manager.(*dwl_ipc.ZdwlIpcManagerV2); ok {
|
||||
mgr.Release()
|
||||
}
|
||||
}
|
||||
169
core/internal/server/dwl/types.go
Normal file
169
core/internal/server/dwl/types.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package dwl
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
wlclient "github.com/yaslama/go-wayland/wayland/client"
|
||||
)
|
||||
|
||||
type TagState struct {
|
||||
Tag uint32 `json:"tag"`
|
||||
State uint32 `json:"state"`
|
||||
Clients uint32 `json:"clients"`
|
||||
Focused uint32 `json:"focused"`
|
||||
}
|
||||
|
||||
type OutputState struct {
|
||||
Name string `json:"name"`
|
||||
Active uint32 `json:"active"`
|
||||
Tags []TagState `json:"tags"`
|
||||
Layout uint32 `json:"layout"`
|
||||
LayoutSymbol string `json:"layoutSymbol"`
|
||||
Title string `json:"title"`
|
||||
AppID string `json:"appId"`
|
||||
}
|
||||
|
||||
type State struct {
|
||||
Outputs map[string]*OutputState `json:"outputs"`
|
||||
TagCount uint32 `json:"tagCount"`
|
||||
Layouts []string `json:"layouts"`
|
||||
ActiveOutput string `json:"activeOutput"`
|
||||
}
|
||||
|
||||
type cmd struct {
|
||||
fn func()
|
||||
}
|
||||
|
||||
type Manager struct {
|
||||
display *wlclient.Display
|
||||
registry *wlclient.Registry
|
||||
manager interface{}
|
||||
|
||||
outputs map[uint32]*outputState
|
||||
outputsMutex sync.RWMutex
|
||||
|
||||
tagCount uint32
|
||||
layouts []string
|
||||
|
||||
wlMutex sync.Mutex
|
||||
cmdq chan cmd
|
||||
outputSetupReq chan uint32
|
||||
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
|
||||
}
|
||||
|
||||
type outputState struct {
|
||||
id uint32
|
||||
registryName uint32
|
||||
output *wlclient.Output
|
||||
ipcOutput interface{}
|
||||
name string
|
||||
active uint32
|
||||
tags []TagState
|
||||
layout uint32
|
||||
layoutSymbol string
|
||||
title string
|
||||
appID string
|
||||
}
|
||||
|
||||
func (m *Manager) GetState() State {
|
||||
m.stateMutex.RLock()
|
||||
defer m.stateMutex.RUnlock()
|
||||
if m.state == nil {
|
||||
return State{
|
||||
Outputs: make(map[string]*OutputState),
|
||||
Layouts: []string{},
|
||||
TagCount: 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 stateChanged(old, new *State) bool {
|
||||
if old == nil || new == nil {
|
||||
return true
|
||||
}
|
||||
if old.TagCount != new.TagCount {
|
||||
return true
|
||||
}
|
||||
if len(old.Layouts) != len(new.Layouts) {
|
||||
return true
|
||||
}
|
||||
if old.ActiveOutput != new.ActiveOutput {
|
||||
return true
|
||||
}
|
||||
if len(old.Outputs) != len(new.Outputs) {
|
||||
return true
|
||||
}
|
||||
|
||||
for name, newOut := range new.Outputs {
|
||||
oldOut, exists := old.Outputs[name]
|
||||
if !exists {
|
||||
return true
|
||||
}
|
||||
if oldOut.Active != newOut.Active {
|
||||
return true
|
||||
}
|
||||
if oldOut.Layout != newOut.Layout {
|
||||
return true
|
||||
}
|
||||
if oldOut.LayoutSymbol != newOut.LayoutSymbol {
|
||||
return true
|
||||
}
|
||||
if oldOut.Title != newOut.Title {
|
||||
return true
|
||||
}
|
||||
if oldOut.AppID != newOut.AppID {
|
||||
return true
|
||||
}
|
||||
if len(oldOut.Tags) != len(newOut.Tags) {
|
||||
return true
|
||||
}
|
||||
for i, newTag := range newOut.Tags {
|
||||
if i >= len(oldOut.Tags) {
|
||||
return true
|
||||
}
|
||||
oldTag := oldOut.Tags[i]
|
||||
if oldTag.Tag != newTag.Tag || oldTag.State != newTag.State ||
|
||||
oldTag.Clients != newTag.Clients || oldTag.Focused != newTag.Focused {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user