mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-11 07:52:50 -05:00
rename backend to core
This commit is contained in:
152
core/internal/server/extworkspace/handlers.go
Normal file
152
core/internal/server/extworkspace/handlers.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package extworkspace
|
||||
|
||||
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, "extworkspace manager not initialized")
|
||||
return
|
||||
}
|
||||
|
||||
switch req.Method {
|
||||
case "extworkspace.getState":
|
||||
handleGetState(conn, req, manager)
|
||||
case "extworkspace.activateWorkspace":
|
||||
handleActivateWorkspace(conn, req, manager)
|
||||
case "extworkspace.deactivateWorkspace":
|
||||
handleDeactivateWorkspace(conn, req, manager)
|
||||
case "extworkspace.removeWorkspace":
|
||||
handleRemoveWorkspace(conn, req, manager)
|
||||
case "extworkspace.createWorkspace":
|
||||
handleCreateWorkspace(conn, req, manager)
|
||||
case "extworkspace.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 handleActivateWorkspace(conn net.Conn, req Request, manager *Manager) {
|
||||
groupID, ok := req.Params["groupID"].(string)
|
||||
if !ok {
|
||||
groupID = ""
|
||||
}
|
||||
|
||||
workspaceID, ok := req.Params["workspaceID"].(string)
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'workspaceID' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
if err := manager.ActivateWorkspace(groupID, workspaceID); err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "workspace activated"})
|
||||
}
|
||||
|
||||
func handleDeactivateWorkspace(conn net.Conn, req Request, manager *Manager) {
|
||||
groupID, ok := req.Params["groupID"].(string)
|
||||
if !ok {
|
||||
groupID = ""
|
||||
}
|
||||
|
||||
workspaceID, ok := req.Params["workspaceID"].(string)
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'workspaceID' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
if err := manager.DeactivateWorkspace(groupID, workspaceID); err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "workspace deactivated"})
|
||||
}
|
||||
|
||||
func handleRemoveWorkspace(conn net.Conn, req Request, manager *Manager) {
|
||||
groupID, ok := req.Params["groupID"].(string)
|
||||
if !ok {
|
||||
groupID = ""
|
||||
}
|
||||
|
||||
workspaceID, ok := req.Params["workspaceID"].(string)
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'workspaceID' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
if err := manager.RemoveWorkspace(groupID, workspaceID); err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "workspace removed"})
|
||||
}
|
||||
|
||||
func handleCreateWorkspace(conn net.Conn, req Request, manager *Manager) {
|
||||
groupID, ok := req.Params["groupID"].(string)
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'groupID' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
workspaceName, ok := req.Params["name"].(string)
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'name' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
if err := manager.CreateWorkspace(groupID, workspaceName); err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "workspace create requested"})
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
566
core/internal/server/extworkspace/manager.go
Normal file
566
core/internal/server/extworkspace/manager.go
Normal file
@@ -0,0 +1,566 @@
|
||||
package extworkspace
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/ext_workspace"
|
||||
wlclient "github.com/yaslama/go-wayland/wayland/client"
|
||||
)
|
||||
|
||||
func NewManager(display *wlclient.Display) (*Manager, error) {
|
||||
m := &Manager{
|
||||
display: display,
|
||||
outputs: make(map[uint32]*wlclient.Output),
|
||||
outputNames: make(map[uint32]string),
|
||||
groups: make(map[uint32]*workspaceGroupState),
|
||||
workspaces: make(map[uint32]*workspaceState),
|
||||
cmdq: make(chan cmd, 128),
|
||||
stopChan: make(chan struct{}),
|
||||
subscribers: make(map[string]chan State),
|
||||
dirty: make(chan struct{}, 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("ExtWorkspace 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) setupRegistry() error {
|
||||
log.Info("ExtWorkspace: 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 == "wl_output" {
|
||||
output := wlclient.NewOutput(ctx)
|
||||
if err := registry.Bind(e.Name, e.Interface, 4, output); err == nil {
|
||||
outputID := output.ID()
|
||||
|
||||
output.SetNameHandler(func(ev wlclient.OutputNameEvent) {
|
||||
m.outputsMutex.Lock()
|
||||
m.outputNames[outputID] = ev.Name
|
||||
m.outputsMutex.Unlock()
|
||||
log.Debugf("ExtWorkspace: Output %d (%s) name received", outputID, ev.Name)
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if e.Interface == ext_workspace.ExtWorkspaceManagerV1InterfaceName {
|
||||
log.Infof("ExtWorkspace: found %s", ext_workspace.ExtWorkspaceManagerV1InterfaceName)
|
||||
manager := ext_workspace.NewExtWorkspaceManagerV1(ctx)
|
||||
version := e.Version
|
||||
if version > 1 {
|
||||
version = 1
|
||||
}
|
||||
|
||||
manager.SetWorkspaceGroupHandler(func(e ext_workspace.ExtWorkspaceManagerV1WorkspaceGroupEvent) {
|
||||
m.handleWorkspaceGroup(e)
|
||||
})
|
||||
|
||||
manager.SetWorkspaceHandler(func(e ext_workspace.ExtWorkspaceManagerV1WorkspaceEvent) {
|
||||
m.handleWorkspace(e)
|
||||
})
|
||||
|
||||
manager.SetDoneHandler(func(e ext_workspace.ExtWorkspaceManagerV1DoneEvent) {
|
||||
log.Debug("ExtWorkspace: done event received")
|
||||
m.post(func() {
|
||||
m.updateState()
|
||||
})
|
||||
})
|
||||
|
||||
manager.SetFinishedHandler(func(e ext_workspace.ExtWorkspaceManagerV1FinishedEvent) {
|
||||
log.Info("ExtWorkspace: finished event received")
|
||||
})
|
||||
|
||||
if err := registry.Bind(e.Name, e.Interface, version, manager); err == nil {
|
||||
m.manager = manager
|
||||
log.Info("ExtWorkspace: manager bound successfully")
|
||||
} else {
|
||||
log.Errorf("ExtWorkspace: failed to bind manager: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
log.Info("ExtWorkspace: registry setup complete (events will be processed async)")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) handleWorkspaceGroup(e ext_workspace.ExtWorkspaceManagerV1WorkspaceGroupEvent) {
|
||||
handle := e.WorkspaceGroup
|
||||
groupID := handle.ID()
|
||||
|
||||
log.Debugf("ExtWorkspace: New workspace group (id=%d)", groupID)
|
||||
|
||||
group := &workspaceGroupState{
|
||||
id: groupID,
|
||||
handle: handle,
|
||||
outputIDs: make(map[uint32]bool),
|
||||
workspaceIDs: make([]uint32, 0),
|
||||
}
|
||||
|
||||
m.groupsMutex.Lock()
|
||||
m.groups[groupID] = group
|
||||
m.groupsMutex.Unlock()
|
||||
|
||||
handle.SetCapabilitiesHandler(func(e ext_workspace.ExtWorkspaceGroupHandleV1CapabilitiesEvent) {
|
||||
log.Debugf("ExtWorkspace: Group %d capabilities: %d", groupID, e.Capabilities)
|
||||
})
|
||||
|
||||
handle.SetOutputEnterHandler(func(e ext_workspace.ExtWorkspaceGroupHandleV1OutputEnterEvent) {
|
||||
outputID := e.Output.ID()
|
||||
log.Debugf("ExtWorkspace: Group %d output enter (output=%d)", groupID, outputID)
|
||||
|
||||
group.outputIDs[outputID] = true
|
||||
|
||||
m.post(func() {
|
||||
m.updateState()
|
||||
})
|
||||
})
|
||||
|
||||
handle.SetOutputLeaveHandler(func(e ext_workspace.ExtWorkspaceGroupHandleV1OutputLeaveEvent) {
|
||||
outputID := e.Output.ID()
|
||||
log.Debugf("ExtWorkspace: Group %d output leave (output=%d)", groupID, outputID)
|
||||
delete(group.outputIDs, outputID)
|
||||
m.post(func() {
|
||||
m.updateState()
|
||||
})
|
||||
})
|
||||
|
||||
handle.SetWorkspaceEnterHandler(func(e ext_workspace.ExtWorkspaceGroupHandleV1WorkspaceEnterEvent) {
|
||||
workspaceID := e.Workspace.ID()
|
||||
log.Debugf("ExtWorkspace: Group %d workspace enter (workspace=%d)", groupID, workspaceID)
|
||||
|
||||
m.workspacesMutex.Lock()
|
||||
if ws, exists := m.workspaces[workspaceID]; exists {
|
||||
ws.groupID = groupID
|
||||
}
|
||||
m.workspacesMutex.Unlock()
|
||||
|
||||
group.workspaceIDs = append(group.workspaceIDs, workspaceID)
|
||||
m.post(func() {
|
||||
m.updateState()
|
||||
})
|
||||
})
|
||||
|
||||
handle.SetWorkspaceLeaveHandler(func(e ext_workspace.ExtWorkspaceGroupHandleV1WorkspaceLeaveEvent) {
|
||||
workspaceID := e.Workspace.ID()
|
||||
log.Debugf("ExtWorkspace: Group %d workspace leave (workspace=%d)", groupID, workspaceID)
|
||||
|
||||
m.workspacesMutex.Lock()
|
||||
if ws, exists := m.workspaces[workspaceID]; exists {
|
||||
ws.groupID = 0
|
||||
}
|
||||
m.workspacesMutex.Unlock()
|
||||
|
||||
for i, id := range group.workspaceIDs {
|
||||
if id == workspaceID {
|
||||
group.workspaceIDs = append(group.workspaceIDs[:i], group.workspaceIDs[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
m.post(func() {
|
||||
m.updateState()
|
||||
})
|
||||
})
|
||||
|
||||
handle.SetRemovedHandler(func(e ext_workspace.ExtWorkspaceGroupHandleV1RemovedEvent) {
|
||||
log.Debugf("ExtWorkspace: Group %d removed", groupID)
|
||||
group.removed = true
|
||||
|
||||
m.groupsMutex.Lock()
|
||||
delete(m.groups, groupID)
|
||||
m.groupsMutex.Unlock()
|
||||
|
||||
m.post(func() {
|
||||
m.wlMutex.Lock()
|
||||
handle.Destroy()
|
||||
m.wlMutex.Unlock()
|
||||
|
||||
m.updateState()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (m *Manager) handleWorkspace(e ext_workspace.ExtWorkspaceManagerV1WorkspaceEvent) {
|
||||
handle := e.Workspace
|
||||
workspaceID := handle.ID()
|
||||
|
||||
log.Debugf("ExtWorkspace: New workspace (proxy_id=%d)", workspaceID)
|
||||
|
||||
ws := &workspaceState{
|
||||
id: workspaceID,
|
||||
handle: handle,
|
||||
coordinates: make([]uint32, 0),
|
||||
}
|
||||
|
||||
m.workspacesMutex.Lock()
|
||||
m.workspaces[workspaceID] = ws
|
||||
m.workspacesMutex.Unlock()
|
||||
|
||||
handle.SetIdHandler(func(e ext_workspace.ExtWorkspaceHandleV1IdEvent) {
|
||||
log.Debugf("ExtWorkspace: Workspace %d id: %s", workspaceID, e.Id)
|
||||
ws.workspaceID = e.Id
|
||||
m.post(func() {
|
||||
m.updateState()
|
||||
})
|
||||
})
|
||||
|
||||
handle.SetNameHandler(func(e ext_workspace.ExtWorkspaceHandleV1NameEvent) {
|
||||
log.Debugf("ExtWorkspace: Workspace %d name: %s", workspaceID, e.Name)
|
||||
ws.name = e.Name
|
||||
m.post(func() {
|
||||
m.updateState()
|
||||
})
|
||||
})
|
||||
|
||||
handle.SetCoordinatesHandler(func(e ext_workspace.ExtWorkspaceHandleV1CoordinatesEvent) {
|
||||
coords := make([]uint32, 0)
|
||||
for i := 0; i < len(e.Coordinates); i += 4 {
|
||||
if i+4 <= len(e.Coordinates) {
|
||||
val := uint32(e.Coordinates[i]) |
|
||||
uint32(e.Coordinates[i+1])<<8 |
|
||||
uint32(e.Coordinates[i+2])<<16 |
|
||||
uint32(e.Coordinates[i+3])<<24
|
||||
coords = append(coords, val)
|
||||
}
|
||||
}
|
||||
log.Debugf("ExtWorkspace: Workspace %d coordinates: %v", workspaceID, coords)
|
||||
ws.coordinates = coords
|
||||
m.post(func() {
|
||||
m.updateState()
|
||||
})
|
||||
})
|
||||
|
||||
handle.SetStateHandler(func(e ext_workspace.ExtWorkspaceHandleV1StateEvent) {
|
||||
log.Debugf("ExtWorkspace: Workspace %d state: %d", workspaceID, e.State)
|
||||
ws.state = e.State
|
||||
m.post(func() {
|
||||
m.updateState()
|
||||
})
|
||||
})
|
||||
|
||||
handle.SetCapabilitiesHandler(func(e ext_workspace.ExtWorkspaceHandleV1CapabilitiesEvent) {
|
||||
log.Debugf("ExtWorkspace: Workspace %d capabilities: %d", workspaceID, e.Capabilities)
|
||||
})
|
||||
|
||||
handle.SetRemovedHandler(func(e ext_workspace.ExtWorkspaceHandleV1RemovedEvent) {
|
||||
log.Debugf("ExtWorkspace: Workspace %d removed", workspaceID)
|
||||
ws.removed = true
|
||||
|
||||
m.workspacesMutex.Lock()
|
||||
delete(m.workspaces, workspaceID)
|
||||
m.workspacesMutex.Unlock()
|
||||
|
||||
m.post(func() {
|
||||
m.wlMutex.Lock()
|
||||
handle.Destroy()
|
||||
m.wlMutex.Unlock()
|
||||
|
||||
m.updateState()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (m *Manager) updateState() {
|
||||
m.groupsMutex.RLock()
|
||||
m.workspacesMutex.RLock()
|
||||
|
||||
groups := make([]*WorkspaceGroup, 0)
|
||||
|
||||
for _, group := range m.groups {
|
||||
if group.removed {
|
||||
continue
|
||||
}
|
||||
|
||||
outputs := make([]string, 0)
|
||||
for outputID := range group.outputIDs {
|
||||
m.outputsMutex.RLock()
|
||||
name := m.outputNames[outputID]
|
||||
m.outputsMutex.RUnlock()
|
||||
if name != "" {
|
||||
outputs = append(outputs, name)
|
||||
} else {
|
||||
outputs = append(outputs, fmt.Sprintf("output-%d", outputID))
|
||||
}
|
||||
}
|
||||
|
||||
workspaces := make([]*Workspace, 0)
|
||||
for _, wsID := range group.workspaceIDs {
|
||||
ws, exists := m.workspaces[wsID]
|
||||
if !exists || ws.removed {
|
||||
continue
|
||||
}
|
||||
|
||||
workspace := &Workspace{
|
||||
ID: ws.workspaceID,
|
||||
Name: ws.name,
|
||||
Coordinates: ws.coordinates,
|
||||
State: ws.state,
|
||||
Active: ws.state&uint32(ext_workspace.ExtWorkspaceHandleV1StateActive) != 0,
|
||||
Urgent: ws.state&uint32(ext_workspace.ExtWorkspaceHandleV1StateUrgent) != 0,
|
||||
Hidden: ws.state&uint32(ext_workspace.ExtWorkspaceHandleV1StateHidden) != 0,
|
||||
}
|
||||
workspaces = append(workspaces, workspace)
|
||||
}
|
||||
|
||||
groupState := &WorkspaceGroup{
|
||||
ID: fmt.Sprintf("group-%d", group.id),
|
||||
Outputs: outputs,
|
||||
Workspaces: workspaces,
|
||||
}
|
||||
groups = append(groups, groupState)
|
||||
}
|
||||
|
||||
m.workspacesMutex.RUnlock()
|
||||
m.groupsMutex.RUnlock()
|
||||
|
||||
newState := State{
|
||||
Groups: groups,
|
||||
}
|
||||
|
||||
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("ExtWorkspace: subscriber channel full, dropping update")
|
||||
}
|
||||
}
|
||||
m.subMutex.RUnlock()
|
||||
|
||||
stateCopy := currentState
|
||||
m.lastNotified = &stateCopy
|
||||
pending = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) ActivateWorkspace(groupID, workspaceID string) error {
|
||||
m.workspacesMutex.RLock()
|
||||
defer m.workspacesMutex.RUnlock()
|
||||
|
||||
var targetGroupID uint32
|
||||
if groupID != "" {
|
||||
var parsedID uint32
|
||||
if _, err := fmt.Sscanf(groupID, "group-%d", &parsedID); err == nil {
|
||||
targetGroupID = parsedID
|
||||
}
|
||||
}
|
||||
|
||||
for _, ws := range m.workspaces {
|
||||
if targetGroupID != 0 && ws.groupID != targetGroupID {
|
||||
continue
|
||||
}
|
||||
if ws.workspaceID == workspaceID || ws.name == workspaceID {
|
||||
m.wlMutex.Lock()
|
||||
err := ws.handle.Activate()
|
||||
if err == nil {
|
||||
err = m.manager.Commit()
|
||||
}
|
||||
m.wlMutex.Unlock()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("workspace not found: %s in group %s", workspaceID, groupID)
|
||||
}
|
||||
|
||||
func (m *Manager) DeactivateWorkspace(groupID, workspaceID string) error {
|
||||
m.workspacesMutex.RLock()
|
||||
defer m.workspacesMutex.RUnlock()
|
||||
|
||||
var targetGroupID uint32
|
||||
if groupID != "" {
|
||||
var parsedID uint32
|
||||
if _, err := fmt.Sscanf(groupID, "group-%d", &parsedID); err == nil {
|
||||
targetGroupID = parsedID
|
||||
}
|
||||
}
|
||||
|
||||
for _, ws := range m.workspaces {
|
||||
if targetGroupID != 0 && ws.groupID != targetGroupID {
|
||||
continue
|
||||
}
|
||||
if ws.workspaceID == workspaceID || ws.name == workspaceID {
|
||||
m.wlMutex.Lock()
|
||||
err := ws.handle.Deactivate()
|
||||
if err == nil {
|
||||
err = m.manager.Commit()
|
||||
}
|
||||
m.wlMutex.Unlock()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("workspace not found: %s in group %s", workspaceID, groupID)
|
||||
}
|
||||
|
||||
func (m *Manager) RemoveWorkspace(groupID, workspaceID string) error {
|
||||
m.workspacesMutex.RLock()
|
||||
defer m.workspacesMutex.RUnlock()
|
||||
|
||||
var targetGroupID uint32
|
||||
if groupID != "" {
|
||||
var parsedID uint32
|
||||
if _, err := fmt.Sscanf(groupID, "group-%d", &parsedID); err == nil {
|
||||
targetGroupID = parsedID
|
||||
}
|
||||
}
|
||||
|
||||
for _, ws := range m.workspaces {
|
||||
if targetGroupID != 0 && ws.groupID != targetGroupID {
|
||||
continue
|
||||
}
|
||||
if ws.workspaceID == workspaceID || ws.name == workspaceID {
|
||||
m.wlMutex.Lock()
|
||||
err := ws.handle.Remove()
|
||||
if err == nil {
|
||||
err = m.manager.Commit()
|
||||
}
|
||||
m.wlMutex.Unlock()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("workspace not found: %s in group %s", workspaceID, groupID)
|
||||
}
|
||||
|
||||
func (m *Manager) CreateWorkspace(groupID, workspaceName string) error {
|
||||
m.groupsMutex.RLock()
|
||||
defer m.groupsMutex.RUnlock()
|
||||
|
||||
for _, group := range m.groups {
|
||||
if fmt.Sprintf("group-%d", group.id) == groupID {
|
||||
m.wlMutex.Lock()
|
||||
err := group.handle.CreateWorkspace(workspaceName)
|
||||
if err == nil {
|
||||
err = m.manager.Commit()
|
||||
}
|
||||
m.wlMutex.Unlock()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("workspace group not found: %s", groupID)
|
||||
}
|
||||
|
||||
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.workspacesMutex.Lock()
|
||||
for _, ws := range m.workspaces {
|
||||
if ws.handle != nil {
|
||||
ws.handle.Destroy()
|
||||
}
|
||||
}
|
||||
m.workspaces = make(map[uint32]*workspaceState)
|
||||
m.workspacesMutex.Unlock()
|
||||
|
||||
m.groupsMutex.Lock()
|
||||
for _, group := range m.groups {
|
||||
if group.handle != nil {
|
||||
group.handle.Destroy()
|
||||
}
|
||||
}
|
||||
m.groups = make(map[uint32]*workspaceGroupState)
|
||||
m.groupsMutex.Unlock()
|
||||
|
||||
if m.manager != nil {
|
||||
m.manager.Stop()
|
||||
}
|
||||
}
|
||||
175
core/internal/server/extworkspace/types.go
Normal file
175
core/internal/server/extworkspace/types.go
Normal file
@@ -0,0 +1,175 @@
|
||||
package extworkspace
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/ext_workspace"
|
||||
wlclient "github.com/yaslama/go-wayland/wayland/client"
|
||||
)
|
||||
|
||||
type Workspace struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Coordinates []uint32 `json:"coordinates"`
|
||||
State uint32 `json:"state"`
|
||||
Active bool `json:"active"`
|
||||
Urgent bool `json:"urgent"`
|
||||
Hidden bool `json:"hidden"`
|
||||
}
|
||||
|
||||
type WorkspaceGroup struct {
|
||||
ID string `json:"id"`
|
||||
Outputs []string `json:"outputs"`
|
||||
Workspaces []*Workspace `json:"workspaces"`
|
||||
}
|
||||
|
||||
type State struct {
|
||||
Groups []*WorkspaceGroup `json:"groups"`
|
||||
}
|
||||
|
||||
type cmd struct {
|
||||
fn func()
|
||||
}
|
||||
|
||||
type Manager struct {
|
||||
display *wlclient.Display
|
||||
registry *wlclient.Registry
|
||||
manager *ext_workspace.ExtWorkspaceManagerV1
|
||||
|
||||
outputsMutex sync.RWMutex
|
||||
outputs map[uint32]*wlclient.Output
|
||||
outputNames map[uint32]string
|
||||
|
||||
groupsMutex sync.RWMutex
|
||||
groups map[uint32]*workspaceGroupState
|
||||
|
||||
workspacesMutex sync.RWMutex
|
||||
workspaces map[uint32]*workspaceState
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
type workspaceGroupState struct {
|
||||
id uint32
|
||||
handle *ext_workspace.ExtWorkspaceGroupHandleV1
|
||||
outputIDs map[uint32]bool
|
||||
workspaceIDs []uint32
|
||||
removed bool
|
||||
}
|
||||
|
||||
type workspaceState struct {
|
||||
id uint32
|
||||
handle *ext_workspace.ExtWorkspaceHandleV1
|
||||
workspaceID string
|
||||
name string
|
||||
coordinates []uint32
|
||||
state uint32
|
||||
groupID uint32
|
||||
removed bool
|
||||
}
|
||||
|
||||
func (m *Manager) GetState() State {
|
||||
m.stateMutex.RLock()
|
||||
defer m.stateMutex.RUnlock()
|
||||
if m.state == nil {
|
||||
return State{
|
||||
Groups: []*WorkspaceGroup{},
|
||||
}
|
||||
}
|
||||
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 len(old.Groups) != len(new.Groups) {
|
||||
return true
|
||||
}
|
||||
|
||||
for i, newGroup := range new.Groups {
|
||||
if i >= len(old.Groups) {
|
||||
return true
|
||||
}
|
||||
oldGroup := old.Groups[i]
|
||||
if oldGroup.ID != newGroup.ID {
|
||||
return true
|
||||
}
|
||||
if len(oldGroup.Outputs) != len(newGroup.Outputs) {
|
||||
return true
|
||||
}
|
||||
for j, newOutput := range newGroup.Outputs {
|
||||
if j >= len(oldGroup.Outputs) {
|
||||
return true
|
||||
}
|
||||
if oldGroup.Outputs[j] != newOutput {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if len(oldGroup.Workspaces) != len(newGroup.Workspaces) {
|
||||
return true
|
||||
}
|
||||
for j, newWs := range newGroup.Workspaces {
|
||||
if j >= len(oldGroup.Workspaces) {
|
||||
return true
|
||||
}
|
||||
oldWs := oldGroup.Workspaces[j]
|
||||
if oldWs.ID != newWs.ID || oldWs.Name != newWs.Name || oldWs.State != newWs.State {
|
||||
return true
|
||||
}
|
||||
if oldWs.Active != newWs.Active || oldWs.Urgent != newWs.Urgent || oldWs.Hidden != newWs.Hidden {
|
||||
return true
|
||||
}
|
||||
if len(oldWs.Coordinates) != len(newWs.Coordinates) {
|
||||
return true
|
||||
}
|
||||
for k, coord := range newWs.Coordinates {
|
||||
if k >= len(oldWs.Coordinates) {
|
||||
return true
|
||||
}
|
||||
if oldWs.Coordinates[k] != coord {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user