diff --git a/core/internal/server/extworkspace/manager.go b/core/internal/server/extworkspace/manager.go index c0b06ee9..49231f82 100644 --- a/core/internal/server/extworkspace/manager.go +++ b/core/internal/server/extworkspace/manager.go @@ -2,6 +2,7 @@ package extworkspace import ( "fmt" + "sync" "time" "github.com/AvengeMedia/DankMaterialShell/core/internal/log" @@ -9,6 +10,45 @@ import ( wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client" ) +func CheckCapability() bool { + display, err := wlclient.Connect("") + if err != nil { + return false + } + defer display.Destroy() + + registry, err := display.GetRegistry() + if err != nil { + return false + } + + found := false + var mu sync.Mutex + done := make(chan struct{}) + + registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) { + if e.Interface == ext_workspace.ExtWorkspaceManagerV1InterfaceName { + mu.Lock() + found = true + mu.Unlock() + } + }) + + go func() { + for i := 0; i < 10 && !found; i++ { + if err := display.Context().Dispatch(); err != nil { + break + } + time.Sleep(10 * time.Millisecond) + } + registry.Destroy() + close(done) + }() + + <-done + return found +} + func NewManager(display *wlclient.Display) (*Manager, error) { m := &Manager{ display: display, diff --git a/core/internal/server/router.go b/core/internal/server/router.go index 836f177f..07c5da65 100644 --- a/core/internal/server/router.go +++ b/core/internal/server/router.go @@ -140,8 +140,20 @@ func RouteRequest(conn net.Conn, req models.Request) { if strings.HasPrefix(req.Method, "extworkspace.") { if extWorkspaceManager == nil { - models.RespondError(conn, req.ID, "extworkspace manager not initialized") - return + if extWorkspaceAvailable.Load() { + extWorkspaceInitMutex.Lock() + if extWorkspaceManager == nil { + if err := InitializeExtWorkspaceManager(); err != nil { + extWorkspaceInitMutex.Unlock() + models.RespondError(conn, req.ID, "extworkspace manager not available") + return + } + } + extWorkspaceInitMutex.Unlock() + } else { + models.RespondError(conn, req.ID, "extworkspace manager not initialized") + return + } } extWorkspaceReq := extworkspace.Request{ ID: req.ID, diff --git a/core/internal/server/server.go b/core/internal/server/server.go index b735b152..b2b5e926 100644 --- a/core/internal/server/server.go +++ b/core/internal/server/server.go @@ -63,6 +63,8 @@ var wlContext *wlcontext.SharedContext var capabilitySubscribers syncmap.Map[string, chan ServerInfo] var cupsSubscribers syncmap.Map[string, bool] var cupsSubscriberCount atomic.Int32 +var extWorkspaceAvailable atomic.Bool +var extWorkspaceInitMutex sync.Mutex func getSocketDir() string { if runtime := os.Getenv("XDG_RUNTIME_DIR"); runtime != "" { @@ -361,7 +363,7 @@ func getCapabilities() Capabilities { caps = append(caps, "dwl") } - if extWorkspaceManager != nil { + if extWorkspaceAvailable.Load() { caps = append(caps, "extworkspace") } @@ -411,7 +413,7 @@ func getServerInfo() ServerInfo { caps = append(caps, "dwl") } - if extWorkspaceManager != nil { + if extWorkspaceAvailable.Load() { caps = append(caps, "extworkspace") } @@ -810,12 +812,14 @@ func handleSubscribe(conn net.Conn, req models.Request) { } if shouldSubscribe("extworkspace") { - if extWorkspaceManager == nil { - if err := InitializeExtWorkspaceManager(); err != nil { - log.Warnf("Failed to initialize ExtWorkspace manager for subscription: %v", err) - } else { - notifyCapabilityChange() + if extWorkspaceManager == nil && extWorkspaceAvailable.Load() { + extWorkspaceInitMutex.Lock() + if extWorkspaceManager == nil { + if err := InitializeExtWorkspaceManager(); err != nil { + log.Warnf("Failed to initialize ExtWorkspace manager for subscription: %v", err) + } } + extWorkspaceInitMutex.Unlock() } if extWorkspaceManager != nil { @@ -1248,6 +1252,14 @@ func Start(printDocs bool) error { log.Debugf("DWL manager unavailable: %v", err) } + if extworkspace.CheckCapability() { + extWorkspaceAvailable.Store(true) + log.Info("ExtWorkspace capability detected and will be available on subscription") + } else { + log.Debug("ExtWorkspace capability not available") + extWorkspaceAvailable.Store(false) + } + if err := InitializeWlrOutputManager(); err != nil { log.Debugf("WlrOutput manager unavailable: %v", err) } diff --git a/quickshell/Modules/DankBar/Widgets/WorkspaceSwitcher.qml b/quickshell/Modules/DankBar/Widgets/WorkspaceSwitcher.qml index f392479c..d2c624ef 100644 --- a/quickshell/Modules/DankBar/Widgets/WorkspaceSwitcher.qml +++ b/quickshell/Modules/DankBar/Widgets/WorkspaceSwitcher.qml @@ -316,12 +316,19 @@ Item { return [{"id": "1", "name": "1", "active": false}] } - const visible = group.workspaces.filter(ws => !ws.hidden).sort((a, b) => { - const coordsA = a.coordinates || [0, 0] - const coordsB = b.coordinates || [0, 0] - if (coordsA[0] !== coordsB[0]) return coordsA[0] - coordsB[0] - return coordsA[1] - coordsB[1] - }).map(ws => ({ + let visible = group.workspaces.filter(ws => !ws.hidden) + + const hasValidCoordinates = visible.some(ws => ws.coordinates && ws.coordinates.length > 0) + if (hasValidCoordinates) { + visible = visible.sort((a, b) => { + const coordsA = a.coordinates || [0, 0] + const coordsB = b.coordinates || [0, 0] + if (coordsA[0] !== coordsB[0]) return coordsA[0] - coordsB[0] + return coordsA[1] - coordsB[1] + }) + } + + visible = visible.map(ws => ({ id: ws.id, name: ws.name, coordinates: ws.coordinates, @@ -350,7 +357,7 @@ Item { function getRealWorkspaces() { return root.workspaceList.filter(ws => { - if (useExtWorkspace) return ws && ws.id !== "" && !ws.hidden + if (useExtWorkspace) return ws && (ws.id !== "" || ws.name !== "") && !ws.hidden if (CompositorService.isHyprland) return ws && ws.id !== -1 if (CompositorService.isDwl) return ws && ws.tag !== -1 if (CompositorService.isSway) return ws && ws.num !== -1 @@ -893,7 +900,7 @@ Item { if (isPlaceholder) return index + 1 - if (root.useExtWorkspace) return modelData?.name || modelData?.id || "" + if (root.useExtWorkspace) return index + 1 if (CompositorService.isHyprland) return modelData?.id || "" if (CompositorService.isDwl) return (modelData?.tag !== undefined) ? (modelData.tag + 1) : "" if (CompositorService.isSway) return modelData?.num || "" diff --git a/quickshell/Services/ExtWorkspaceService.qml b/quickshell/Services/ExtWorkspaceService.qml index ac9e8f45..36a739a2 100644 --- a/quickshell/Services/ExtWorkspaceService.qml +++ b/quickshell/Services/ExtWorkspaceService.qml @@ -46,8 +46,17 @@ Singleton { const hasExtWorkspace = DMSService.capabilities.includes("extworkspace") if (hasExtWorkspace && !extWorkspaceAvailable) { + if (typeof CompositorService !== "undefined") { + const useExtWorkspace = !CompositorService.isNiri && !CompositorService.isHyprland && !CompositorService.isDwl && !CompositorService.isSway + if (!useExtWorkspace) { + console.info("ExtWorkspaceService: ext-workspace available but compositor has native support") + extWorkspaceAvailable = false + return + } + } extWorkspaceAvailable = true console.info("ExtWorkspaceService: ext-workspace capability detected") + DMSService.addSubscription("extworkspace") requestState() } else if (!hasExtWorkspace) { extWorkspaceAvailable = false @@ -181,12 +190,17 @@ Singleton { function getVisibleWorkspaces(outputName) { const workspaces = getWorkspacesForOutput(outputName) - const visible = workspaces.filter(ws => !ws.hidden).sort((a, b) => { - const coordsA = a.coordinates || [0, 0] - const coordsB = b.coordinates || [0, 0] - if (coordsA[0] !== coordsB[0]) return coordsA[0] - coordsB[0] - return coordsA[1] - coordsB[1] - }) + let visible = workspaces.filter(ws => !ws.hidden) + + const hasValidCoordinates = visible.some(ws => ws.coordinates && ws.coordinates.length > 0) + if (hasValidCoordinates) { + visible = visible.sort((a, b) => { + const coordsA = a.coordinates || [0, 0] + const coordsB = b.coordinates || [0, 0] + if (coordsA[0] !== coordsB[0]) return coordsA[0] - coordsB[0] + return coordsA[1] - coordsB[1] + }) + } const cacheKey = outputName if (!_cachedWorkspaces[cacheKey]) {