diff --git a/core/internal/proto/ext_workspace/workspace.go b/core/internal/proto/ext_workspace/workspace.go deleted file mode 100644 index c76ab982..00000000 --- a/core/internal/proto/ext_workspace/workspace.go +++ /dev/null @@ -1,1040 +0,0 @@ -// Generated by go-wayland-scanner -// https://github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/cmd/go-wayland-scanner -// XML file : ext-workspace-v1.xml -// -// ext_workspace_v1 Protocol Copyright: -// -// Copyright © 2019 Christopher Billington -// Copyright © 2020 Ilia Bozhinov -// Copyright © 2022 Victoria Brekenfeld -// -// Permission to use, copy, modify, distribute, and sell this -// software and its documentation for any purpose is hereby granted -// without fee, provided that the above copyright notice appear in -// all copies and that both that copyright notice and this permission -// notice appear in supporting documentation, and that the name of -// the copyright holders not be used in advertising or publicity -// pertaining to distribution of the software without specific, -// written prior permission. The copyright holders make no -// representations about the suitability of this software for any -// purpose. It is provided "as is" without express or implied -// warranty. -// -// THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS -// SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -// FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY -// SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN -// AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, -// ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -// THIS SOFTWARE. - -package ext_workspace - -import ( - "reflect" - "unsafe" - - "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap" -) - -// registerServerProxy registers a proxy with a server-assigned ID. -// This is necessary because go-wayland-scanner doesn't properly handle new_id arguments in events. -// In requests (like DWL), the client creates the ID via NewXxx(ctx) which calls ctx.Register(). -// In events (like ext-workspace), the server creates the ID and sends it, requiring manual registration. -// The Context.objects map is private with no public API for server IDs, requiring reflection. -func registerServerProxy(ctx *client.Context, proxy client.Proxy, serverID uint32) { - defer func() { - if r := recover(); r != nil { - return - } - }() - - ctxVal := reflect.ValueOf(ctx) - if ctxVal.Kind() != reflect.Ptr || ctxVal.IsNil() { - return - } - - ctxElem := ctxVal.Elem() - objectsField := ctxElem.FieldByName("objects") - if !objectsField.IsValid() { - return - } - - objectsMapPtr := unsafe.Pointer(objectsField.UnsafeAddr()) - objectsMap := (*syncmap.Map[uint32, client.Proxy])(objectsMapPtr) - objectsMap.Store(serverID, proxy) -} - -// ExtWorkspaceManagerV1InterfaceName is the name of the interface as it appears in the [client.Registry]. -// It can be used to match the [client.RegistryGlobalEvent.Interface] in the -// [Registry.SetGlobalHandler] and can be used in [Registry.Bind] if this applies. -const ExtWorkspaceManagerV1InterfaceName = "ext_workspace_manager_v1" - -// ExtWorkspaceManagerV1 : list and control workspaces -// -// Workspaces, also called virtual desktops, are groups of surfaces. A -// compositor with a concept of workspaces may only show some such groups of -// surfaces (those of 'active' workspaces) at a time. 'Activating' a -// workspace is a request for the compositor to display that workspace's -// surfaces as normal, whereas the compositor may hide or otherwise -// de-emphasise surfaces that are associated only with 'inactive' workspaces. -// Workspaces are grouped by which sets of outputs they correspond to, and -// may contain surfaces only from those outputs. In this way, it is possible -// for each output to have its own set of workspaces, or for all outputs (or -// any other arbitrary grouping) to share workspaces. Compositors may -// optionally conceptually arrange each group of workspaces in an -// N-dimensional grid. -// -// The purpose of this protocol is to enable the creation of taskbars and -// docks by providing them with a list of workspaces and their properties, -// and allowing them to activate and deactivate workspaces. -// -// After a client binds the ext_workspace_manager_v1, each workspace will be -// sent via the workspace event. -type ExtWorkspaceManagerV1 struct { - client.BaseProxy - workspaceGroupHandler ExtWorkspaceManagerV1WorkspaceGroupHandlerFunc - workspaceHandler ExtWorkspaceManagerV1WorkspaceHandlerFunc - doneHandler ExtWorkspaceManagerV1DoneHandlerFunc - finishedHandler ExtWorkspaceManagerV1FinishedHandlerFunc -} - -// NewExtWorkspaceManagerV1 : list and control workspaces -// -// Workspaces, also called virtual desktops, are groups of surfaces. A -// compositor with a concept of workspaces may only show some such groups of -// surfaces (those of 'active' workspaces) at a time. 'Activating' a -// workspace is a request for the compositor to display that workspace's -// surfaces as normal, whereas the compositor may hide or otherwise -// de-emphasise surfaces that are associated only with 'inactive' workspaces. -// Workspaces are grouped by which sets of outputs they correspond to, and -// may contain surfaces only from those outputs. In this way, it is possible -// for each output to have its own set of workspaces, or for all outputs (or -// any other arbitrary grouping) to share workspaces. Compositors may -// optionally conceptually arrange each group of workspaces in an -// N-dimensional grid. -// -// The purpose of this protocol is to enable the creation of taskbars and -// docks by providing them with a list of workspaces and their properties, -// and allowing them to activate and deactivate workspaces. -// -// After a client binds the ext_workspace_manager_v1, each workspace will be -// sent via the workspace event. -func NewExtWorkspaceManagerV1(ctx *client.Context) *ExtWorkspaceManagerV1 { - extWorkspaceManagerV1 := &ExtWorkspaceManagerV1{} - ctx.Register(extWorkspaceManagerV1) - return extWorkspaceManagerV1 -} - -// Commit : all requests about the workspaces have been sent -// -// The client must send this request after it has finished sending other -// requests. The compositor must process a series of requests preceding a -// commit request atomically. -// -// This allows changes to the workspace properties to be seen as atomic, -// even if they happen via multiple events, and even if they involve -// multiple ext_workspace_handle_v1 objects, for example, deactivating one -// workspace and activating another. -func (i *ExtWorkspaceManagerV1) Commit() error { - const opcode = 0 - const _reqBufLen = 8 - var _reqBuf [_reqBufLen]byte - l := 0 - client.PutUint32(_reqBuf[l:4], i.ID()) - l += 4 - client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff)) - l += 4 - err := i.Context().WriteMsg(_reqBuf[:], nil) - return err -} - -// Stop : stop sending events -// -// Indicates the client no longer wishes to receive events for new -// workspace groups. However the compositor may emit further workspace -// events, until the finished event is emitted. The compositor is expected -// to send the finished event eventually once the stop request has been processed. -// -// The client must not send any requests after this one, doing so will raise a wl_display -// invalid_object error. -func (i *ExtWorkspaceManagerV1) Stop() error { - const opcode = 1 - const _reqBufLen = 8 - var _reqBuf [_reqBufLen]byte - l := 0 - client.PutUint32(_reqBuf[l:4], i.ID()) - l += 4 - client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff)) - l += 4 - err := i.Context().WriteMsg(_reqBuf[:], nil) - return err -} - -func (i *ExtWorkspaceManagerV1) Destroy() error { - i.MarkZombie() - return nil -} - -// ExtWorkspaceManagerV1WorkspaceGroupEvent : a workspace group has been created -// -// This event is emitted whenever a new workspace group has been created. -// -// All initial details of the workspace group (outputs) will be -// sent immediately after this event via the corresponding events in -// ext_workspace_group_handle_v1 and ext_workspace_handle_v1. -type ExtWorkspaceManagerV1WorkspaceGroupEvent struct { - WorkspaceGroup *ExtWorkspaceGroupHandleV1 -} -type ExtWorkspaceManagerV1WorkspaceGroupHandlerFunc func(ExtWorkspaceManagerV1WorkspaceGroupEvent) - -// SetWorkspaceGroupHandler : sets handler for ExtWorkspaceManagerV1WorkspaceGroupEvent -func (i *ExtWorkspaceManagerV1) SetWorkspaceGroupHandler(f ExtWorkspaceManagerV1WorkspaceGroupHandlerFunc) { - i.workspaceGroupHandler = f -} - -// ExtWorkspaceManagerV1WorkspaceEvent : workspace has been created -// -// This event is emitted whenever a new workspace has been created. -// -// All initial details of the workspace (name, coordinates, state) will -// be sent immediately after this event via the corresponding events in -// ext_workspace_handle_v1. -// -// Workspaces start off unassigned to any workspace group. -type ExtWorkspaceManagerV1WorkspaceEvent struct { - Workspace *ExtWorkspaceHandleV1 -} -type ExtWorkspaceManagerV1WorkspaceHandlerFunc func(ExtWorkspaceManagerV1WorkspaceEvent) - -// SetWorkspaceHandler : sets handler for ExtWorkspaceManagerV1WorkspaceEvent -func (i *ExtWorkspaceManagerV1) SetWorkspaceHandler(f ExtWorkspaceManagerV1WorkspaceHandlerFunc) { - i.workspaceHandler = f -} - -// ExtWorkspaceManagerV1DoneEvent : all information about the workspaces and workspace groups has been sent -// -// This event is sent after all changes in all workspaces and workspace groups have been -// sent. -// -// This allows changes to one or more ext_workspace_group_handle_v1 -// properties and ext_workspace_handle_v1 properties -// to be seen as atomic, even if they happen via multiple events. -// In particular, an output moving from one workspace group to -// another sends an output_enter event and an output_leave event to the two -// ext_workspace_group_handle_v1 objects in question. The compositor sends -// the done event only after updating the output information in both -// workspace groups. -type ExtWorkspaceManagerV1DoneEvent struct{} -type ExtWorkspaceManagerV1DoneHandlerFunc func(ExtWorkspaceManagerV1DoneEvent) - -// SetDoneHandler : sets handler for ExtWorkspaceManagerV1DoneEvent -func (i *ExtWorkspaceManagerV1) SetDoneHandler(f ExtWorkspaceManagerV1DoneHandlerFunc) { - i.doneHandler = f -} - -// ExtWorkspaceManagerV1FinishedEvent : the compositor has finished with the workspace_manager -// -// This event indicates that the compositor is done sending events to the -// ext_workspace_manager_v1. The server will destroy the object -// immediately after sending this request. -type ExtWorkspaceManagerV1FinishedEvent struct{} -type ExtWorkspaceManagerV1FinishedHandlerFunc func(ExtWorkspaceManagerV1FinishedEvent) - -// SetFinishedHandler : sets handler for ExtWorkspaceManagerV1FinishedEvent -func (i *ExtWorkspaceManagerV1) SetFinishedHandler(f ExtWorkspaceManagerV1FinishedHandlerFunc) { - i.finishedHandler = f -} - -func (i *ExtWorkspaceManagerV1) Dispatch(opcode uint32, fd int, data []byte) { - switch opcode { - case 0: - if i.workspaceGroupHandler == nil { - return - } - var e ExtWorkspaceManagerV1WorkspaceGroupEvent - l := 0 - objectID := client.Uint32(data[l : l+4]) - proxy := i.Context().GetProxy(objectID) - if proxy != nil && !proxy.IsZombie() { - e.WorkspaceGroup = proxy.(*ExtWorkspaceGroupHandleV1) - } else { - groupHandle := &ExtWorkspaceGroupHandleV1{} - groupHandle.SetContext(i.Context()) - groupHandle.SetID(objectID) - registerServerProxy(i.Context(), groupHandle, objectID) - e.WorkspaceGroup = groupHandle - } - l += 4 - - i.workspaceGroupHandler(e) - case 1: - if i.workspaceHandler == nil { - return - } - var e ExtWorkspaceManagerV1WorkspaceEvent - l := 0 - objectID := client.Uint32(data[l : l+4]) - proxy := i.Context().GetProxy(objectID) - if proxy != nil && !proxy.IsZombie() { - e.Workspace = proxy.(*ExtWorkspaceHandleV1) - } else { - wsHandle := &ExtWorkspaceHandleV1{} - wsHandle.SetContext(i.Context()) - wsHandle.SetID(objectID) - registerServerProxy(i.Context(), wsHandle, objectID) - e.Workspace = wsHandle - } - l += 4 - - i.workspaceHandler(e) - case 2: - if i.doneHandler == nil { - return - } - var e ExtWorkspaceManagerV1DoneEvent - - i.doneHandler(e) - case 3: - if i.finishedHandler == nil { - return - } - var e ExtWorkspaceManagerV1FinishedEvent - - i.finishedHandler(e) - } -} - -// ExtWorkspaceGroupHandleV1InterfaceName is the name of the interface as it appears in the [client.Registry]. -// It can be used to match the [client.RegistryGlobalEvent.Interface] in the -// [Registry.SetGlobalHandler] and can be used in [Registry.Bind] if this applies. -const ExtWorkspaceGroupHandleV1InterfaceName = "ext_workspace_group_handle_v1" - -// ExtWorkspaceGroupHandleV1 : a workspace group assigned to a set of outputs -// -// A ext_workspace_group_handle_v1 object represents a workspace group -// that is assigned a set of outputs and contains a number of workspaces. -// -// The set of outputs assigned to the workspace group is conveyed to the client via -// output_enter and output_leave events, and its workspaces are conveyed with -// workspace events. -// -// For example, a compositor which has a set of workspaces for each output may -// advertise a workspace group (and its workspaces) per output, whereas a compositor -// where a workspace spans all outputs may advertise a single workspace group for all -// outputs. -type ExtWorkspaceGroupHandleV1 struct { - client.BaseProxy - capabilitiesHandler ExtWorkspaceGroupHandleV1CapabilitiesHandlerFunc - outputEnterHandler ExtWorkspaceGroupHandleV1OutputEnterHandlerFunc - outputLeaveHandler ExtWorkspaceGroupHandleV1OutputLeaveHandlerFunc - workspaceEnterHandler ExtWorkspaceGroupHandleV1WorkspaceEnterHandlerFunc - workspaceLeaveHandler ExtWorkspaceGroupHandleV1WorkspaceLeaveHandlerFunc - removedHandler ExtWorkspaceGroupHandleV1RemovedHandlerFunc -} - -// NewExtWorkspaceGroupHandleV1 : a workspace group assigned to a set of outputs -// -// A ext_workspace_group_handle_v1 object represents a workspace group -// that is assigned a set of outputs and contains a number of workspaces. -// -// The set of outputs assigned to the workspace group is conveyed to the client via -// output_enter and output_leave events, and its workspaces are conveyed with -// workspace events. -// -// For example, a compositor which has a set of workspaces for each output may -// advertise a workspace group (and its workspaces) per output, whereas a compositor -// where a workspace spans all outputs may advertise a single workspace group for all -// outputs. -func NewExtWorkspaceGroupHandleV1(ctx *client.Context) *ExtWorkspaceGroupHandleV1 { - extWorkspaceGroupHandleV1 := &ExtWorkspaceGroupHandleV1{} - ctx.Register(extWorkspaceGroupHandleV1) - return extWorkspaceGroupHandleV1 -} - -// CreateWorkspace : create a new workspace -// -// Request that the compositor create a new workspace with the given name -// and assign it to this group. -// -// There is no guarantee that the compositor will create a new workspace, -// or that the created workspace will have the provided name. -func (i *ExtWorkspaceGroupHandleV1) CreateWorkspace(workspace string) error { - const opcode = 0 - workspaceLen := client.PaddedLen(len(workspace) + 1) - _reqBufLen := 8 + (4 + workspaceLen) - _reqBuf := make([]byte, _reqBufLen) - l := 0 - client.PutUint32(_reqBuf[l:4], i.ID()) - l += 4 - client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff)) - l += 4 - client.PutString(_reqBuf[l:l+(4+workspaceLen)], workspace) - l += (4 + workspaceLen) - err := i.Context().WriteMsg(_reqBuf, nil) - return err -} - -// Destroy : destroy the ext_workspace_group_handle_v1 object -// -// Destroys the ext_workspace_group_handle_v1 object. -// -// This request should be send either when the client does not want to -// use the workspace group object any more or after the removed event to finalize -// the destruction of the object. -func (i *ExtWorkspaceGroupHandleV1) Destroy() error { - defer i.MarkZombie() - const opcode = 1 - const _reqBufLen = 8 - var _reqBuf [_reqBufLen]byte - l := 0 - client.PutUint32(_reqBuf[l:4], i.ID()) - l += 4 - client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff)) - l += 4 - err := i.Context().WriteMsg(_reqBuf[:], nil) - return err -} - -type ExtWorkspaceGroupHandleV1GroupCapabilities uint32 - -// ExtWorkspaceGroupHandleV1GroupCapabilities : -const ( - // ExtWorkspaceGroupHandleV1GroupCapabilitiesCreateWorkspace : create_workspace request is available - ExtWorkspaceGroupHandleV1GroupCapabilitiesCreateWorkspace ExtWorkspaceGroupHandleV1GroupCapabilities = 1 -) - -func (e ExtWorkspaceGroupHandleV1GroupCapabilities) Name() string { - switch e { - case ExtWorkspaceGroupHandleV1GroupCapabilitiesCreateWorkspace: - return "create_workspace" - default: - return "" - } -} - -func (e ExtWorkspaceGroupHandleV1GroupCapabilities) Value() string { - switch e { - case ExtWorkspaceGroupHandleV1GroupCapabilitiesCreateWorkspace: - return "1" - default: - return "" - } -} - -func (e ExtWorkspaceGroupHandleV1GroupCapabilities) String() string { - return e.Name() + "=" + e.Value() -} - -// ExtWorkspaceGroupHandleV1CapabilitiesEvent : compositor capabilities -// -// This event advertises the capabilities supported by the compositor. If -// a capability isn't supported, clients should hide or disable the UI -// elements that expose this functionality. For instance, if the -// compositor doesn't advertise support for creating workspaces, a button -// triggering the create_workspace request should not be displayed. -// -// The compositor will ignore requests it doesn't support. For instance, -// a compositor which doesn't advertise support for creating workspaces will ignore -// create_workspace requests. -// -// Compositors must send this event once after creation of an -// ext_workspace_group_handle_v1. When the capabilities change, compositors -// must send this event again. -type ExtWorkspaceGroupHandleV1CapabilitiesEvent struct { - Capabilities uint32 -} -type ExtWorkspaceGroupHandleV1CapabilitiesHandlerFunc func(ExtWorkspaceGroupHandleV1CapabilitiesEvent) - -// SetCapabilitiesHandler : sets handler for ExtWorkspaceGroupHandleV1CapabilitiesEvent -func (i *ExtWorkspaceGroupHandleV1) SetCapabilitiesHandler(f ExtWorkspaceGroupHandleV1CapabilitiesHandlerFunc) { - i.capabilitiesHandler = f -} - -// ExtWorkspaceGroupHandleV1OutputEnterEvent : output assigned to workspace group -// -// This event is emitted whenever an output is assigned to the workspace -// group or a new `wl_output` object is bound by the client, which was already -// assigned to this workspace_group. -type ExtWorkspaceGroupHandleV1OutputEnterEvent struct { - Output *client.Output -} -type ExtWorkspaceGroupHandleV1OutputEnterHandlerFunc func(ExtWorkspaceGroupHandleV1OutputEnterEvent) - -// SetOutputEnterHandler : sets handler for ExtWorkspaceGroupHandleV1OutputEnterEvent -func (i *ExtWorkspaceGroupHandleV1) SetOutputEnterHandler(f ExtWorkspaceGroupHandleV1OutputEnterHandlerFunc) { - i.outputEnterHandler = f -} - -// ExtWorkspaceGroupHandleV1OutputLeaveEvent : output removed from workspace group -// -// This event is emitted whenever an output is removed from the workspace -// group. -type ExtWorkspaceGroupHandleV1OutputLeaveEvent struct { - Output *client.Output -} -type ExtWorkspaceGroupHandleV1OutputLeaveHandlerFunc func(ExtWorkspaceGroupHandleV1OutputLeaveEvent) - -// SetOutputLeaveHandler : sets handler for ExtWorkspaceGroupHandleV1OutputLeaveEvent -func (i *ExtWorkspaceGroupHandleV1) SetOutputLeaveHandler(f ExtWorkspaceGroupHandleV1OutputLeaveHandlerFunc) { - i.outputLeaveHandler = f -} - -// ExtWorkspaceGroupHandleV1WorkspaceEnterEvent : workspace added to workspace group -// -// This event is emitted whenever a workspace is assigned to this group. -// A workspace may only ever be assigned to a single group at a single point -// in time, but can be re-assigned during it's lifetime. -type ExtWorkspaceGroupHandleV1WorkspaceEnterEvent struct { - Workspace *ExtWorkspaceHandleV1 -} -type ExtWorkspaceGroupHandleV1WorkspaceEnterHandlerFunc func(ExtWorkspaceGroupHandleV1WorkspaceEnterEvent) - -// SetWorkspaceEnterHandler : sets handler for ExtWorkspaceGroupHandleV1WorkspaceEnterEvent -func (i *ExtWorkspaceGroupHandleV1) SetWorkspaceEnterHandler(f ExtWorkspaceGroupHandleV1WorkspaceEnterHandlerFunc) { - i.workspaceEnterHandler = f -} - -// ExtWorkspaceGroupHandleV1WorkspaceLeaveEvent : workspace removed from workspace group -// -// This event is emitted whenever a workspace is removed from this group. -type ExtWorkspaceGroupHandleV1WorkspaceLeaveEvent struct { - Workspace *ExtWorkspaceHandleV1 -} -type ExtWorkspaceGroupHandleV1WorkspaceLeaveHandlerFunc func(ExtWorkspaceGroupHandleV1WorkspaceLeaveEvent) - -// SetWorkspaceLeaveHandler : sets handler for ExtWorkspaceGroupHandleV1WorkspaceLeaveEvent -func (i *ExtWorkspaceGroupHandleV1) SetWorkspaceLeaveHandler(f ExtWorkspaceGroupHandleV1WorkspaceLeaveHandlerFunc) { - i.workspaceLeaveHandler = f -} - -// ExtWorkspaceGroupHandleV1RemovedEvent : this workspace group has been removed -// -// This event is send when the group associated with the ext_workspace_group_handle_v1 -// has been removed. After sending this request the compositor will immediately consider -// the object inert. Any requests will be ignored except the destroy request. -// It is guaranteed there won't be any more events referencing this -// ext_workspace_group_handle_v1. -// -// The compositor must remove all workspaces belonging to a workspace group -// via a workspace_leave event before removing the workspace group. -type ExtWorkspaceGroupHandleV1RemovedEvent struct{} -type ExtWorkspaceGroupHandleV1RemovedHandlerFunc func(ExtWorkspaceGroupHandleV1RemovedEvent) - -// SetRemovedHandler : sets handler for ExtWorkspaceGroupHandleV1RemovedEvent -func (i *ExtWorkspaceGroupHandleV1) SetRemovedHandler(f ExtWorkspaceGroupHandleV1RemovedHandlerFunc) { - i.removedHandler = f -} - -func (i *ExtWorkspaceGroupHandleV1) Dispatch(opcode uint32, fd int, data []byte) { - switch opcode { - case 0: - if i.capabilitiesHandler == nil { - return - } - var e ExtWorkspaceGroupHandleV1CapabilitiesEvent - l := 0 - e.Capabilities = client.Uint32(data[l : l+4]) - l += 4 - - i.capabilitiesHandler(e) - case 1: - if i.outputEnterHandler == nil { - return - } - var e ExtWorkspaceGroupHandleV1OutputEnterEvent - l := 0 - e.Output = i.Context().GetProxy(client.Uint32(data[l : l+4])).(*client.Output) - l += 4 - - i.outputEnterHandler(e) - case 2: - if i.outputLeaveHandler == nil { - return - } - var e ExtWorkspaceGroupHandleV1OutputLeaveEvent - l := 0 - e.Output = i.Context().GetProxy(client.Uint32(data[l : l+4])).(*client.Output) - l += 4 - - i.outputLeaveHandler(e) - case 3: - if i.workspaceEnterHandler == nil { - return - } - var e ExtWorkspaceGroupHandleV1WorkspaceEnterEvent - l := 0 - e.Workspace = i.Context().GetProxy(client.Uint32(data[l : l+4])).(*ExtWorkspaceHandleV1) - l += 4 - - i.workspaceEnterHandler(e) - case 4: - if i.workspaceLeaveHandler == nil { - return - } - var e ExtWorkspaceGroupHandleV1WorkspaceLeaveEvent - l := 0 - e.Workspace = i.Context().GetProxy(client.Uint32(data[l : l+4])).(*ExtWorkspaceHandleV1) - l += 4 - - i.workspaceLeaveHandler(e) - case 5: - if i.removedHandler == nil { - return - } - var e ExtWorkspaceGroupHandleV1RemovedEvent - - i.removedHandler(e) - } -} - -// ExtWorkspaceHandleV1InterfaceName is the name of the interface as it appears in the [client.Registry]. -// It can be used to match the [client.RegistryGlobalEvent.Interface] in the -// [Registry.SetGlobalHandler] and can be used in [Registry.Bind] if this applies. -const ExtWorkspaceHandleV1InterfaceName = "ext_workspace_handle_v1" - -// ExtWorkspaceHandleV1 : a workspace handing a group of surfaces -// -// A ext_workspace_handle_v1 object represents a workspace that handles a -// group of surfaces. -// -// Each workspace has: -// - a name, conveyed to the client with the name event -// - potentially an id conveyed with the id event -// - a list of states, conveyed to the client with the state event -// - and optionally a set of coordinates, conveyed to the client with the -// coordinates event -// -// The client may request that the compositor activate or deactivate the workspace. -// -// Each workspace can belong to only a single workspace group. -// Depending on the compositor policy, there might be workspaces with -// the same name in different workspace groups, but these workspaces are still -// separate (e.g. one of them might be active while the other is not). -type ExtWorkspaceHandleV1 struct { - client.BaseProxy - idHandler ExtWorkspaceHandleV1IdHandlerFunc - nameHandler ExtWorkspaceHandleV1NameHandlerFunc - coordinatesHandler ExtWorkspaceHandleV1CoordinatesHandlerFunc - stateHandler ExtWorkspaceHandleV1StateHandlerFunc - capabilitiesHandler ExtWorkspaceHandleV1CapabilitiesHandlerFunc - removedHandler ExtWorkspaceHandleV1RemovedHandlerFunc -} - -// NewExtWorkspaceHandleV1 : a workspace handing a group of surfaces -// -// A ext_workspace_handle_v1 object represents a workspace that handles a -// group of surfaces. -// -// Each workspace has: -// - a name, conveyed to the client with the name event -// - potentially an id conveyed with the id event -// - a list of states, conveyed to the client with the state event -// - and optionally a set of coordinates, conveyed to the client with the -// coordinates event -// -// The client may request that the compositor activate or deactivate the workspace. -// -// Each workspace can belong to only a single workspace group. -// Depending on the compositor policy, there might be workspaces with -// the same name in different workspace groups, but these workspaces are still -// separate (e.g. one of them might be active while the other is not). -func NewExtWorkspaceHandleV1(ctx *client.Context) *ExtWorkspaceHandleV1 { - extWorkspaceHandleV1 := &ExtWorkspaceHandleV1{} - ctx.Register(extWorkspaceHandleV1) - return extWorkspaceHandleV1 -} - -// Destroy : destroy the ext_workspace_handle_v1 object -// -// Destroys the ext_workspace_handle_v1 object. -// -// This request should be made either when the client does not want to -// use the workspace object any more or after the remove event to finalize -// the destruction of the object. -func (i *ExtWorkspaceHandleV1) Destroy() error { - defer i.MarkZombie() - const opcode = 0 - const _reqBufLen = 8 - var _reqBuf [_reqBufLen]byte - l := 0 - client.PutUint32(_reqBuf[l:4], i.ID()) - l += 4 - client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff)) - l += 4 - err := i.Context().WriteMsg(_reqBuf[:], nil) - return err -} - -// Activate : activate the workspace -// -// Request that this workspace be activated. -// -// There is no guarantee the workspace will be actually activated, and -// behaviour may be compositor-dependent. For example, activating a -// workspace may or may not deactivate all other workspaces in the same -// group. -func (i *ExtWorkspaceHandleV1) Activate() error { - const opcode = 1 - const _reqBufLen = 8 - var _reqBuf [_reqBufLen]byte - l := 0 - client.PutUint32(_reqBuf[l:4], i.ID()) - l += 4 - client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff)) - l += 4 - err := i.Context().WriteMsg(_reqBuf[:], nil) - return err -} - -// Deactivate : deactivate the workspace -// -// Request that this workspace be deactivated. -// -// There is no guarantee the workspace will be actually deactivated. -func (i *ExtWorkspaceHandleV1) Deactivate() error { - const opcode = 2 - const _reqBufLen = 8 - var _reqBuf [_reqBufLen]byte - l := 0 - client.PutUint32(_reqBuf[l:4], i.ID()) - l += 4 - client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff)) - l += 4 - err := i.Context().WriteMsg(_reqBuf[:], nil) - return err -} - -// Assign : assign workspace to group -// -// Requests that this workspace is assigned to the given workspace group. -// -// There is no guarantee the workspace will be assigned. -func (i *ExtWorkspaceHandleV1) Assign(workspaceGroup *ExtWorkspaceGroupHandleV1) error { - const opcode = 3 - const _reqBufLen = 8 + 4 - var _reqBuf [_reqBufLen]byte - l := 0 - client.PutUint32(_reqBuf[l:4], i.ID()) - l += 4 - client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff)) - l += 4 - client.PutUint32(_reqBuf[l:l+4], workspaceGroup.ID()) - l += 4 - err := i.Context().WriteMsg(_reqBuf[:], nil) - return err -} - -// Remove : remove the workspace -// -// Request that this workspace be removed. -// -// There is no guarantee the workspace will be actually removed. -func (i *ExtWorkspaceHandleV1) Remove() error { - const opcode = 4 - const _reqBufLen = 8 - var _reqBuf [_reqBufLen]byte - l := 0 - client.PutUint32(_reqBuf[l:4], i.ID()) - l += 4 - client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff)) - l += 4 - err := i.Context().WriteMsg(_reqBuf[:], nil) - return err -} - -type ExtWorkspaceHandleV1State uint32 - -// ExtWorkspaceHandleV1State : types of states on the workspace -// -// The different states that a workspace can have. -const ( - // ExtWorkspaceHandleV1StateActive : the workspace is active - ExtWorkspaceHandleV1StateActive ExtWorkspaceHandleV1State = 1 - // ExtWorkspaceHandleV1StateUrgent : the workspace requests attention - ExtWorkspaceHandleV1StateUrgent ExtWorkspaceHandleV1State = 2 - ExtWorkspaceHandleV1StateHidden ExtWorkspaceHandleV1State = 4 -) - -func (e ExtWorkspaceHandleV1State) Name() string { - switch e { - case ExtWorkspaceHandleV1StateActive: - return "active" - case ExtWorkspaceHandleV1StateUrgent: - return "urgent" - case ExtWorkspaceHandleV1StateHidden: - return "hidden" - default: - return "" - } -} - -func (e ExtWorkspaceHandleV1State) Value() string { - switch e { - case ExtWorkspaceHandleV1StateActive: - return "1" - case ExtWorkspaceHandleV1StateUrgent: - return "2" - case ExtWorkspaceHandleV1StateHidden: - return "4" - default: - return "" - } -} - -func (e ExtWorkspaceHandleV1State) String() string { - return e.Name() + "=" + e.Value() -} - -type ExtWorkspaceHandleV1WorkspaceCapabilities uint32 - -// ExtWorkspaceHandleV1WorkspaceCapabilities : -const ( - // ExtWorkspaceHandleV1WorkspaceCapabilitiesActivate : activate request is available - ExtWorkspaceHandleV1WorkspaceCapabilitiesActivate ExtWorkspaceHandleV1WorkspaceCapabilities = 1 - // ExtWorkspaceHandleV1WorkspaceCapabilitiesDeactivate : deactivate request is available - ExtWorkspaceHandleV1WorkspaceCapabilitiesDeactivate ExtWorkspaceHandleV1WorkspaceCapabilities = 2 - // ExtWorkspaceHandleV1WorkspaceCapabilitiesRemove : remove request is available - ExtWorkspaceHandleV1WorkspaceCapabilitiesRemove ExtWorkspaceHandleV1WorkspaceCapabilities = 4 - // ExtWorkspaceHandleV1WorkspaceCapabilitiesAssign : assign request is available - ExtWorkspaceHandleV1WorkspaceCapabilitiesAssign ExtWorkspaceHandleV1WorkspaceCapabilities = 8 -) - -func (e ExtWorkspaceHandleV1WorkspaceCapabilities) Name() string { - switch e { - case ExtWorkspaceHandleV1WorkspaceCapabilitiesActivate: - return "activate" - case ExtWorkspaceHandleV1WorkspaceCapabilitiesDeactivate: - return "deactivate" - case ExtWorkspaceHandleV1WorkspaceCapabilitiesRemove: - return "remove" - case ExtWorkspaceHandleV1WorkspaceCapabilitiesAssign: - return "assign" - default: - return "" - } -} - -func (e ExtWorkspaceHandleV1WorkspaceCapabilities) Value() string { - switch e { - case ExtWorkspaceHandleV1WorkspaceCapabilitiesActivate: - return "1" - case ExtWorkspaceHandleV1WorkspaceCapabilitiesDeactivate: - return "2" - case ExtWorkspaceHandleV1WorkspaceCapabilitiesRemove: - return "4" - case ExtWorkspaceHandleV1WorkspaceCapabilitiesAssign: - return "8" - default: - return "" - } -} - -func (e ExtWorkspaceHandleV1WorkspaceCapabilities) String() string { - return e.Name() + "=" + e.Value() -} - -// ExtWorkspaceHandleV1IdEvent : workspace id -// -// If this event is emitted, it will be send immediately after the -// ext_workspace_handle_v1 is created or when an id is assigned to -// a workspace (at most once during it's lifetime). -// -// An id will never change during the lifetime of the `ext_workspace_handle_v1` -// and is guaranteed to be unique during it's lifetime. -// -// Ids are not human-readable and shouldn't be displayed, use `name` for that purpose. -// -// Compositors are expected to only send ids for workspaces likely stable across multiple -// sessions and can be used by clients to store preferences for workspaces. Workspaces without -// ids should be considered temporary and any data associated with them should be deleted once -// the respective object is lost. -type ExtWorkspaceHandleV1IdEvent struct { - Id string -} -type ExtWorkspaceHandleV1IdHandlerFunc func(ExtWorkspaceHandleV1IdEvent) - -// SetIdHandler : sets handler for ExtWorkspaceHandleV1IdEvent -func (i *ExtWorkspaceHandleV1) SetIdHandler(f ExtWorkspaceHandleV1IdHandlerFunc) { - i.idHandler = f -} - -// ExtWorkspaceHandleV1NameEvent : workspace name changed -// -// This event is emitted immediately after the ext_workspace_handle_v1 is -// created and whenever the name of the workspace changes. -// -// A name is meant to be human-readable and can be displayed to a user. -// Unlike the id it is neither stable nor unique. -type ExtWorkspaceHandleV1NameEvent struct { - Name string -} -type ExtWorkspaceHandleV1NameHandlerFunc func(ExtWorkspaceHandleV1NameEvent) - -// SetNameHandler : sets handler for ExtWorkspaceHandleV1NameEvent -func (i *ExtWorkspaceHandleV1) SetNameHandler(f ExtWorkspaceHandleV1NameHandlerFunc) { - i.nameHandler = f -} - -// ExtWorkspaceHandleV1CoordinatesEvent : workspace coordinates changed -// -// This event is used to organize workspaces into an N-dimensional grid -// within a workspace group, and if supported, is emitted immediately after -// the ext_workspace_handle_v1 is created and whenever the coordinates of -// the workspace change. Compositors may not send this event if they do not -// conceptually arrange workspaces in this way. If compositors simply -// number workspaces, without any geometric interpretation, they may send -// 1D coordinates, which clients should not interpret as implying any -// geometry. Sending an empty array means that the compositor no longer -// orders the workspace geometrically. -// -// Coordinates have an arbitrary number of dimensions N with an uint32 -// position along each dimension. By convention if N > 1, the first -// dimension is X, the second Y, the third Z, and so on. The compositor may -// chose to utilize these events for a more novel workspace layout -// convention, however. No guarantee is made about the grid being filled or -// bounded; there may be a workspace at coordinate 1 and another at -// coordinate 1000 and none in between. Within a workspace group, however, -// workspaces must have unique coordinates of equal dimensionality. -type ExtWorkspaceHandleV1CoordinatesEvent struct { - Coordinates []byte -} -type ExtWorkspaceHandleV1CoordinatesHandlerFunc func(ExtWorkspaceHandleV1CoordinatesEvent) - -// SetCoordinatesHandler : sets handler for ExtWorkspaceHandleV1CoordinatesEvent -func (i *ExtWorkspaceHandleV1) SetCoordinatesHandler(f ExtWorkspaceHandleV1CoordinatesHandlerFunc) { - i.coordinatesHandler = f -} - -// ExtWorkspaceHandleV1StateEvent : the state of the workspace changed -// -// This event is emitted immediately after the ext_workspace_handle_v1 is -// created and each time the workspace state changes, either because of a -// compositor action or because of a request in this protocol. -// -// Missing states convey the opposite meaning, e.g. an unset active bit -// means the workspace is currently inactive. -type ExtWorkspaceHandleV1StateEvent struct { - State uint32 -} -type ExtWorkspaceHandleV1StateHandlerFunc func(ExtWorkspaceHandleV1StateEvent) - -// SetStateHandler : sets handler for ExtWorkspaceHandleV1StateEvent -func (i *ExtWorkspaceHandleV1) SetStateHandler(f ExtWorkspaceHandleV1StateHandlerFunc) { - i.stateHandler = f -} - -// ExtWorkspaceHandleV1CapabilitiesEvent : compositor capabilities -// -// This event advertises the capabilities supported by the compositor. If -// a capability isn't supported, clients should hide or disable the UI -// elements that expose this functionality. For instance, if the -// compositor doesn't advertise support for removing workspaces, a button -// triggering the remove request should not be displayed. -// -// The compositor will ignore requests it doesn't support. For instance, -// a compositor which doesn't advertise support for remove will ignore -// remove requests. -// -// Compositors must send this event once after creation of an -// ext_workspace_handle_v1 . When the capabilities change, compositors -// must send this event again. -type ExtWorkspaceHandleV1CapabilitiesEvent struct { - Capabilities uint32 -} -type ExtWorkspaceHandleV1CapabilitiesHandlerFunc func(ExtWorkspaceHandleV1CapabilitiesEvent) - -// SetCapabilitiesHandler : sets handler for ExtWorkspaceHandleV1CapabilitiesEvent -func (i *ExtWorkspaceHandleV1) SetCapabilitiesHandler(f ExtWorkspaceHandleV1CapabilitiesHandlerFunc) { - i.capabilitiesHandler = f -} - -// ExtWorkspaceHandleV1RemovedEvent : this workspace has been removed -// -// This event is send when the workspace associated with the ext_workspace_handle_v1 -// has been removed. After sending this request, the compositor will immediately consider -// the object inert. Any requests will be ignored except the destroy request. -// -// It is guaranteed there won't be any more events referencing this -// ext_workspace_handle_v1. -// -// The compositor must only remove a workspaces not currently belonging to any -// workspace_group. -type ExtWorkspaceHandleV1RemovedEvent struct{} -type ExtWorkspaceHandleV1RemovedHandlerFunc func(ExtWorkspaceHandleV1RemovedEvent) - -// SetRemovedHandler : sets handler for ExtWorkspaceHandleV1RemovedEvent -func (i *ExtWorkspaceHandleV1) SetRemovedHandler(f ExtWorkspaceHandleV1RemovedHandlerFunc) { - i.removedHandler = f -} - -func (i *ExtWorkspaceHandleV1) Dispatch(opcode uint32, fd int, data []byte) { - switch opcode { - case 0: - if i.idHandler == nil { - return - } - var e ExtWorkspaceHandleV1IdEvent - l := 0 - idLen := client.PaddedLen(int(client.Uint32(data[l : l+4]))) - l += 4 - e.Id = client.String(data[l : l+idLen]) - l += idLen - - i.idHandler(e) - case 1: - if i.nameHandler == nil { - return - } - var e ExtWorkspaceHandleV1NameEvent - l := 0 - nameLen := client.PaddedLen(int(client.Uint32(data[l : l+4]))) - l += 4 - e.Name = client.String(data[l : l+nameLen]) - l += nameLen - - i.nameHandler(e) - case 2: - if i.coordinatesHandler == nil { - return - } - var e ExtWorkspaceHandleV1CoordinatesEvent - l := 0 - coordinatesLen := int(client.Uint32(data[l : l+4])) - l += 4 - e.Coordinates = make([]byte, coordinatesLen) - copy(e.Coordinates, data[l:l+coordinatesLen]) - l += coordinatesLen - - i.coordinatesHandler(e) - case 3: - if i.stateHandler == nil { - return - } - var e ExtWorkspaceHandleV1StateEvent - l := 0 - e.State = client.Uint32(data[l : l+4]) - l += 4 - - i.stateHandler(e) - case 4: - if i.capabilitiesHandler == nil { - return - } - var e ExtWorkspaceHandleV1CapabilitiesEvent - l := 0 - e.Capabilities = client.Uint32(data[l : l+4]) - l += 4 - - i.capabilitiesHandler(e) - case 5: - if i.removedHandler == nil { - return - } - var e ExtWorkspaceHandleV1RemovedEvent - - i.removedHandler(e) - } -} diff --git a/core/internal/proto/xml/ext-workspace-v1.xml b/core/internal/proto/xml/ext-workspace-v1.xml deleted file mode 100644 index 134b729b..00000000 --- a/core/internal/proto/xml/ext-workspace-v1.xml +++ /dev/null @@ -1,422 +0,0 @@ - - - - Copyright © 2019 Christopher Billington - Copyright © 2020 Ilia Bozhinov - Copyright © 2022 Victoria Brekenfeld - - Permission to use, copy, modify, distribute, and sell this - software and its documentation for any purpose is hereby granted - without fee, provided that the above copyright notice appear in - all copies and that both that copyright notice and this permission - notice appear in supporting documentation, and that the name of - the copyright holders not be used in advertising or publicity - pertaining to distribution of the software without specific, - written prior permission. The copyright holders make no - representations about the suitability of this software for any - purpose. It is provided "as is" without express or implied - warranty. - - THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS - SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND - FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY - SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, - ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF - THIS SOFTWARE. - - - - - Workspaces, also called virtual desktops, are groups of surfaces. A - compositor with a concept of workspaces may only show some such groups of - surfaces (those of 'active' workspaces) at a time. 'Activating' a - workspace is a request for the compositor to display that workspace's - surfaces as normal, whereas the compositor may hide or otherwise - de-emphasise surfaces that are associated only with 'inactive' workspaces. - Workspaces are grouped by which sets of outputs they correspond to, and - may contain surfaces only from those outputs. In this way, it is possible - for each output to have its own set of workspaces, or for all outputs (or - any other arbitrary grouping) to share workspaces. Compositors may - optionally conceptually arrange each group of workspaces in an - N-dimensional grid. - - The purpose of this protocol is to enable the creation of taskbars and - docks by providing them with a list of workspaces and their properties, - and allowing them to activate and deactivate workspaces. - - After a client binds the ext_workspace_manager_v1, each workspace will be - sent via the workspace event. - - - - - This event is emitted whenever a new workspace group has been created. - - All initial details of the workspace group (outputs) will be - sent immediately after this event via the corresponding events in - ext_workspace_group_handle_v1 and ext_workspace_handle_v1. - - - - - - - This event is emitted whenever a new workspace has been created. - - All initial details of the workspace (name, coordinates, state) will - be sent immediately after this event via the corresponding events in - ext_workspace_handle_v1. - - Workspaces start off unassigned to any workspace group. - - - - - - - The client must send this request after it has finished sending other - requests. The compositor must process a series of requests preceding a - commit request atomically. - - This allows changes to the workspace properties to be seen as atomic, - even if they happen via multiple events, and even if they involve - multiple ext_workspace_handle_v1 objects, for example, deactivating one - workspace and activating another. - - - - - - This event is sent after all changes in all workspaces and workspace groups have been - sent. - - This allows changes to one or more ext_workspace_group_handle_v1 - properties and ext_workspace_handle_v1 properties - to be seen as atomic, even if they happen via multiple events. - In particular, an output moving from one workspace group to - another sends an output_enter event and an output_leave event to the two - ext_workspace_group_handle_v1 objects in question. The compositor sends - the done event only after updating the output information in both - workspace groups. - - - - - - This event indicates that the compositor is done sending events to the - ext_workspace_manager_v1. The server will destroy the object - immediately after sending this request. - - - - - - Indicates the client no longer wishes to receive events for new - workspace groups. However the compositor may emit further workspace - events, until the finished event is emitted. The compositor is expected - to send the finished event eventually once the stop request has been processed. - - The client must not send any requests after this one, doing so will raise a wl_display - invalid_object error. - - - - - - - - A ext_workspace_group_handle_v1 object represents a workspace group - that is assigned a set of outputs and contains a number of workspaces. - - The set of outputs assigned to the workspace group is conveyed to the client via - output_enter and output_leave events, and its workspaces are conveyed with - workspace events. - - For example, a compositor which has a set of workspaces for each output may - advertise a workspace group (and its workspaces) per output, whereas a compositor - where a workspace spans all outputs may advertise a single workspace group for all - outputs. - - - - - - - - - This event advertises the capabilities supported by the compositor. If - a capability isn't supported, clients should hide or disable the UI - elements that expose this functionality. For instance, if the - compositor doesn't advertise support for creating workspaces, a button - triggering the create_workspace request should not be displayed. - - The compositor will ignore requests it doesn't support. For instance, - a compositor which doesn't advertise support for creating workspaces will ignore - create_workspace requests. - - Compositors must send this event once after creation of an - ext_workspace_group_handle_v1. When the capabilities change, compositors - must send this event again. - - - - - - - This event is emitted whenever an output is assigned to the workspace - group or a new `wl_output` object is bound by the client, which was already - assigned to this workspace_group. - - - - - - - This event is emitted whenever an output is removed from the workspace - group. - - - - - - - This event is emitted whenever a workspace is assigned to this group. - A workspace may only ever be assigned to a single group at a single point - in time, but can be re-assigned during it's lifetime. - - - - - - - This event is emitted whenever a workspace is removed from this group. - - - - - - - This event is send when the group associated with the ext_workspace_group_handle_v1 - has been removed. After sending this request the compositor will immediately consider - the object inert. Any requests will be ignored except the destroy request. - It is guaranteed there won't be any more events referencing this - ext_workspace_group_handle_v1. - - The compositor must remove all workspaces belonging to a workspace group - via a workspace_leave event before removing the workspace group. - - - - - - Request that the compositor create a new workspace with the given name - and assign it to this group. - - There is no guarantee that the compositor will create a new workspace, - or that the created workspace will have the provided name. - - - - - - - Destroys the ext_workspace_group_handle_v1 object. - - This request should be send either when the client does not want to - use the workspace group object any more or after the removed event to finalize - the destruction of the object. - - - - - - - A ext_workspace_handle_v1 object represents a workspace that handles a - group of surfaces. - - Each workspace has: - - a name, conveyed to the client with the name event - - potentially an id conveyed with the id event - - a list of states, conveyed to the client with the state event - - and optionally a set of coordinates, conveyed to the client with the - coordinates event - - The client may request that the compositor activate or deactivate the workspace. - - Each workspace can belong to only a single workspace group. - Depending on the compositor policy, there might be workspaces with - the same name in different workspace groups, but these workspaces are still - separate (e.g. one of them might be active while the other is not). - - - - - If this event is emitted, it will be send immediately after the - ext_workspace_handle_v1 is created or when an id is assigned to - a workspace (at most once during it's lifetime). - - An id will never change during the lifetime of the `ext_workspace_handle_v1` - and is guaranteed to be unique during it's lifetime. - - Ids are not human-readable and shouldn't be displayed, use `name` for that purpose. - - Compositors are expected to only send ids for workspaces likely stable across multiple - sessions and can be used by clients to store preferences for workspaces. Workspaces without - ids should be considered temporary and any data associated with them should be deleted once - the respective object is lost. - - - - - - - This event is emitted immediately after the ext_workspace_handle_v1 is - created and whenever the name of the workspace changes. - - A name is meant to be human-readable and can be displayed to a user. - Unlike the id it is neither stable nor unique. - - - - - - - This event is used to organize workspaces into an N-dimensional grid - within a workspace group, and if supported, is emitted immediately after - the ext_workspace_handle_v1 is created and whenever the coordinates of - the workspace change. Compositors may not send this event if they do not - conceptually arrange workspaces in this way. If compositors simply - number workspaces, without any geometric interpretation, they may send - 1D coordinates, which clients should not interpret as implying any - geometry. Sending an empty array means that the compositor no longer - orders the workspace geometrically. - - Coordinates have an arbitrary number of dimensions N with an uint32 - position along each dimension. By convention if N > 1, the first - dimension is X, the second Y, the third Z, and so on. The compositor may - chose to utilize these events for a more novel workspace layout - convention, however. No guarantee is made about the grid being filled or - bounded; there may be a workspace at coordinate 1 and another at - coordinate 1000 and none in between. Within a workspace group, however, - workspaces must have unique coordinates of equal dimensionality. - - - - - - - The different states that a workspace can have. - - - - - - - The workspace is not visible in its workspace group, and clients - attempting to visualize the compositor workspace state should not - display such workspaces. - - - - - - - This event is emitted immediately after the ext_workspace_handle_v1 is - created and each time the workspace state changes, either because of a - compositor action or because of a request in this protocol. - - Missing states convey the opposite meaning, e.g. an unset active bit - means the workspace is currently inactive. - - - - - - - - - - - - - - This event advertises the capabilities supported by the compositor. If - a capability isn't supported, clients should hide or disable the UI - elements that expose this functionality. For instance, if the - compositor doesn't advertise support for removing workspaces, a button - triggering the remove request should not be displayed. - - The compositor will ignore requests it doesn't support. For instance, - a compositor which doesn't advertise support for remove will ignore - remove requests. - - Compositors must send this event once after creation of an - ext_workspace_handle_v1 . When the capabilities change, compositors - must send this event again. - - - - - - - This event is send when the workspace associated with the ext_workspace_handle_v1 - has been removed. After sending this request, the compositor will immediately consider - the object inert. Any requests will be ignored except the destroy request. - - It is guaranteed there won't be any more events referencing this - ext_workspace_handle_v1. - - The compositor must only remove a workspaces not currently belonging to any - workspace_group. - - - - - - Destroys the ext_workspace_handle_v1 object. - - This request should be made either when the client does not want to - use the workspace object any more or after the remove event to finalize - the destruction of the object. - - - - - - Request that this workspace be activated. - - There is no guarantee the workspace will be actually activated, and - behaviour may be compositor-dependent. For example, activating a - workspace may or may not deactivate all other workspaces in the same - group. - - - - - - Request that this workspace be deactivated. - - There is no guarantee the workspace will be actually deactivated. - - - - - - Requests that this workspace is assigned to the given workspace group. - - There is no guarantee the workspace will be assigned. - - - - - - - Request that this workspace be removed. - - There is no guarantee the workspace will be actually removed. - - - - diff --git a/core/internal/server/extworkspace/handlers.go b/core/internal/server/extworkspace/handlers.go deleted file mode 100644 index 3f7e17fd..00000000 --- a/core/internal/server/extworkspace/handlers.go +++ /dev/null @@ -1,134 +0,0 @@ -package extworkspace - -import ( - "encoding/json" - "fmt" - "net" - - "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" -) - -type SuccessResult struct { - Success bool `json:"success"` - Message string `json:"message"` -} - -func HandleRequest(conn net.Conn, req models.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 models.Request, manager *Manager) { - state := manager.GetState() - models.Respond(conn, req.ID, state) -} - -func handleActivateWorkspace(conn net.Conn, req models.Request, manager *Manager) { - groupID := models.GetOr(req, "groupID", "") - workspaceID, ok := models.Get[string](req, "workspaceID") - 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 models.Request, manager *Manager) { - groupID := models.GetOr(req, "groupID", "") - workspaceID, ok := models.Get[string](req, "workspaceID") - 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 models.Request, manager *Manager) { - groupID := models.GetOr(req, "groupID", "") - workspaceID, ok := models.Get[string](req, "workspaceID") - 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 models.Request, manager *Manager) { - groupID, ok := models.Get[string](req, "groupID") - if !ok { - models.RespondError(conn, req.ID, "missing or invalid 'groupID' parameter") - return - } - - workspaceName, ok := models.Get[string](req, "name") - 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 models.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 - } - } -} diff --git a/core/internal/server/extworkspace/manager.go b/core/internal/server/extworkspace/manager.go deleted file mode 100644 index b791b393..00000000 --- a/core/internal/server/extworkspace/manager.go +++ /dev/null @@ -1,598 +0,0 @@ -package extworkspace - -import ( - "fmt" - "time" - - "github.com/AvengeMedia/DankMaterialShell/core/internal/log" - "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/ext_workspace" - 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 - } - defer registry.Destroy() - - found := false - - registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) { - if e.Interface == ext_workspace.ExtWorkspaceManagerV1InterfaceName { - found = true - } - }) - - // Roundtrip to ensure all registry events are processed - if err := display.Roundtrip(); err != nil { - return false - } - - return found -} - -func NewManager(display wlclient.WaylandDisplay) (*Manager, error) { - m := &Manager{ - display: display, - ctx: display.Context(), - cmdq: make(chan cmd, 128), - stopChan: make(chan struct{}), - - 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") - - 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(m.ctx) - if err := registry.Bind(e.Name, e.Interface, 4, output); err == nil { - outputID := output.ID() - - output.SetNameHandler(func(ev wlclient.OutputNameEvent) { - m.outputNames.Store(outputID, ev.Name) - log.Debugf("ExtWorkspace: Output %d (%s) name received", outputID, ev.Name) - m.post(func() { - m.updateState() - }) - }) - } - return - } - - if e.Interface == ext_workspace.ExtWorkspaceManagerV1InterfaceName { - log.Infof("ExtWorkspace: found %s", ext_workspace.ExtWorkspaceManagerV1InterfaceName) - manager := ext_workspace.NewExtWorkspaceManagerV1(m.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.groups.Store(groupID, group) - - 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) - - m.post(func() { - group.outputIDs[outputID] = true - m.updateState() - }) - }) - - handle.SetOutputLeaveHandler(func(e ext_workspace.ExtWorkspaceGroupHandleV1OutputLeaveEvent) { - outputID := e.Output.ID() - log.Debugf("ExtWorkspace: Group %d output leave (output=%d)", groupID, outputID) - m.post(func() { - delete(group.outputIDs, outputID) - 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.post(func() { - if ws, ok := m.workspaces.Load(workspaceID); ok { - ws.groupID = groupID - } - - group.workspaceIDs = append(group.workspaceIDs, workspaceID) - 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.post(func() { - if ws, ok := m.workspaces.Load(workspaceID); ok { - ws.groupID = 0 - } - - for i, id := range group.workspaceIDs { - if id == workspaceID { - group.workspaceIDs = append(group.workspaceIDs[:i], group.workspaceIDs[i+1:]...) - break - } - } - m.updateState() - }) - }) - - handle.SetRemovedHandler(func(e ext_workspace.ExtWorkspaceGroupHandleV1RemovedEvent) { - log.Debugf("ExtWorkspace: Group %d removed", groupID) - - m.post(func() { - group.removed = true - - m.groups.Delete(groupID) - - 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.workspaces.Store(workspaceID, ws) - - handle.SetIdHandler(func(e ext_workspace.ExtWorkspaceHandleV1IdEvent) { - log.Debugf("ExtWorkspace: Workspace %d id: %s", workspaceID, e.Id) - m.post(func() { - ws.workspaceID = e.Id - m.updateState() - }) - }) - - handle.SetNameHandler(func(e ext_workspace.ExtWorkspaceHandleV1NameEvent) { - log.Debugf("ExtWorkspace: Workspace %d name: %s", workspaceID, e.Name) - m.post(func() { - ws.name = e.Name - 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) - m.post(func() { - ws.coordinates = coords - m.updateState() - }) - }) - - handle.SetStateHandler(func(e ext_workspace.ExtWorkspaceHandleV1StateEvent) { - log.Debugf("ExtWorkspace: Workspace %d state: %d", workspaceID, e.State) - m.post(func() { - ws.state = e.State - 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) - - m.post(func() { - ws.removed = true - - m.workspaces.Delete(workspaceID) - - m.wlMutex.Lock() - handle.Destroy() - m.wlMutex.Unlock() - - m.updateState() - }) - }) -} - -func (m *Manager) updateState() { - groups := make([]*WorkspaceGroup, 0) - - m.groups.Range(func(key uint32, group *workspaceGroupState) bool { - if group.removed { - return true - } - - outputs := make([]string, 0) - for outputID := range group.outputIDs { - if name, ok := m.outputNames.Load(outputID); ok && name != "" { - outputs = append(outputs, name) - } - } - - workspaces := make([]*Workspace, 0) - for _, wsID := range group.workspaceIDs { - ws, exists := m.workspaces.Load(wsID) - if !exists { - continue - } - if 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) - return true - }) - - 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 - } - - currentState := m.GetState() - - if m.lastNotified != nil && !stateChanged(m.lastNotified, ¤tState) { - pending = false - continue - } - - m.subscribers.Range(func(key string, ch chan State) bool { - select { - case ch <- currentState: - default: - log.Warn("ExtWorkspace: subscriber channel full, dropping update") - } - return true - }) - - stateCopy := currentState - m.lastNotified = &stateCopy - pending = false - } - } -} - -func (m *Manager) ActivateWorkspace(groupID, workspaceID string) error { - errChan := make(chan error, 1) - - m.post(func() { - var targetGroupID uint32 - if groupID != "" { - var parsedID uint32 - if _, err := fmt.Sscanf(groupID, "group-%d", &parsedID); err == nil { - targetGroupID = parsedID - } - } - - var found bool - m.workspaces.Range(func(key uint32, ws *workspaceState) bool { - if targetGroupID != 0 && ws.groupID != targetGroupID { - return true - } - if ws.workspaceID == workspaceID || ws.name == workspaceID { - m.wlMutex.Lock() - err := ws.handle.Activate() - if err == nil { - err = m.manager.Commit() - } - m.wlMutex.Unlock() - errChan <- err - found = true - return false - } - return true - }) - - if !found { - errChan <- fmt.Errorf("workspace not found: %s in group %s", workspaceID, groupID) - } - }) - - return <-errChan -} - -func (m *Manager) DeactivateWorkspace(groupID, workspaceID string) error { - errChan := make(chan error, 1) - - m.post(func() { - var targetGroupID uint32 - if groupID != "" { - var parsedID uint32 - if _, err := fmt.Sscanf(groupID, "group-%d", &parsedID); err == nil { - targetGroupID = parsedID - } - } - - var found bool - m.workspaces.Range(func(key uint32, ws *workspaceState) bool { - if targetGroupID != 0 && ws.groupID != targetGroupID { - return true - } - if ws.workspaceID == workspaceID || ws.name == workspaceID { - m.wlMutex.Lock() - err := ws.handle.Deactivate() - if err == nil { - err = m.manager.Commit() - } - m.wlMutex.Unlock() - errChan <- err - found = true - return false - } - return true - }) - - if !found { - errChan <- fmt.Errorf("workspace not found: %s in group %s", workspaceID, groupID) - } - }) - - return <-errChan -} - -func (m *Manager) RemoveWorkspace(groupID, workspaceID string) error { - errChan := make(chan error, 1) - - m.post(func() { - var targetGroupID uint32 - if groupID != "" { - var parsedID uint32 - if _, err := fmt.Sscanf(groupID, "group-%d", &parsedID); err == nil { - targetGroupID = parsedID - } - } - - var found bool - m.workspaces.Range(func(key uint32, ws *workspaceState) bool { - if targetGroupID != 0 && ws.groupID != targetGroupID { - return true - } - if ws.workspaceID == workspaceID || ws.name == workspaceID { - m.wlMutex.Lock() - err := ws.handle.Remove() - if err == nil { - err = m.manager.Commit() - } - m.wlMutex.Unlock() - errChan <- err - found = true - return false - } - return true - }) - - if !found { - errChan <- fmt.Errorf("workspace not found: %s in group %s", workspaceID, groupID) - } - }) - - return <-errChan -} - -func (m *Manager) CreateWorkspace(groupID, workspaceName string) error { - errChan := make(chan error, 1) - - m.post(func() { - var found bool - m.groups.Range(func(key uint32, group *workspaceGroupState) bool { - 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() - errChan <- err - found = true - return false - } - return true - }) - - if !found { - errChan <- fmt.Errorf("workspace group not found: %s", groupID) - } - }) - - return <-errChan -} - -func (m *Manager) Close() { - close(m.stopChan) - m.wg.Wait() - m.notifierWg.Wait() - - m.subscribers.Range(func(key string, ch chan State) bool { - close(ch) - m.subscribers.Delete(key) - return true - }) - - m.workspaces.Range(func(key uint32, ws *workspaceState) bool { - if ws.handle != nil { - ws.handle.Destroy() - } - m.workspaces.Delete(key) - return true - }) - - m.groups.Range(func(key uint32, group *workspaceGroupState) bool { - if group.handle != nil { - group.handle.Destroy() - } - m.groups.Delete(key) - return true - }) - - if m.manager != nil { - m.manager.Stop() - } -} diff --git a/core/internal/server/extworkspace/manager_test.go b/core/internal/server/extworkspace/manager_test.go deleted file mode 100644 index b20d4576..00000000 --- a/core/internal/server/extworkspace/manager_test.go +++ /dev/null @@ -1,392 +0,0 @@ -package extworkspace - -import ( - "errors" - "sync" - "testing" - "time" - - "github.com/stretchr/testify/assert" - - mocks_wlclient "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/wlclient" -) - -func TestStateChanged_BothNil(t *testing.T) { - assert.True(t, stateChanged(nil, nil)) -} - -func TestStateChanged_OneNil(t *testing.T) { - s := &State{Groups: []*WorkspaceGroup{}} - assert.True(t, stateChanged(s, nil)) - assert.True(t, stateChanged(nil, s)) -} - -func TestStateChanged_GroupCountDiffers(t *testing.T) { - a := &State{Groups: []*WorkspaceGroup{{ID: "group-1"}}} - b := &State{Groups: []*WorkspaceGroup{}} - assert.True(t, stateChanged(a, b)) -} - -func TestStateChanged_GroupIDDiffers(t *testing.T) { - a := &State{Groups: []*WorkspaceGroup{{ID: "group-1", Outputs: []string{}, Workspaces: []*Workspace{}}}} - b := &State{Groups: []*WorkspaceGroup{{ID: "group-2", Outputs: []string{}, Workspaces: []*Workspace{}}}} - assert.True(t, stateChanged(a, b)) -} - -func TestStateChanged_OutputCountDiffers(t *testing.T) { - a := &State{Groups: []*WorkspaceGroup{{ID: "group-1", Outputs: []string{"eDP-1"}, Workspaces: []*Workspace{}}}} - b := &State{Groups: []*WorkspaceGroup{{ID: "group-1", Outputs: []string{}, Workspaces: []*Workspace{}}}} - assert.True(t, stateChanged(a, b)) -} - -func TestStateChanged_OutputNameDiffers(t *testing.T) { - a := &State{Groups: []*WorkspaceGroup{{ID: "group-1", Outputs: []string{"eDP-1"}, Workspaces: []*Workspace{}}}} - b := &State{Groups: []*WorkspaceGroup{{ID: "group-1", Outputs: []string{"HDMI-A-1"}, Workspaces: []*Workspace{}}}} - assert.True(t, stateChanged(a, b)) -} - -func TestStateChanged_WorkspaceCountDiffers(t *testing.T) { - a := &State{Groups: []*WorkspaceGroup{{ - ID: "group-1", - Outputs: []string{}, - Workspaces: []*Workspace{{ID: "1", Name: "ws1"}}, - }}} - b := &State{Groups: []*WorkspaceGroup{{ - ID: "group-1", - Outputs: []string{}, - Workspaces: []*Workspace{}, - }}} - assert.True(t, stateChanged(a, b)) -} - -func TestStateChanged_WorkspaceFieldsDiffer(t *testing.T) { - a := &State{Groups: []*WorkspaceGroup{{ - ID: "group-1", - Outputs: []string{}, - Workspaces: []*Workspace{{ - ID: "1", Name: "ws1", State: 0, Active: false, Urgent: false, Hidden: false, - }}, - }}} - b := &State{Groups: []*WorkspaceGroup{{ - ID: "group-1", - Outputs: []string{}, - Workspaces: []*Workspace{{ - ID: "2", Name: "ws1", State: 0, Active: false, Urgent: false, Hidden: false, - }}, - }}} - assert.True(t, stateChanged(a, b)) - - b.Groups[0].Workspaces[0].ID = "1" - b.Groups[0].Workspaces[0].Name = "ws2" - assert.True(t, stateChanged(a, b)) - - b.Groups[0].Workspaces[0].Name = "ws1" - b.Groups[0].Workspaces[0].State = 1 - assert.True(t, stateChanged(a, b)) - - b.Groups[0].Workspaces[0].State = 0 - b.Groups[0].Workspaces[0].Active = true - assert.True(t, stateChanged(a, b)) - - b.Groups[0].Workspaces[0].Active = false - b.Groups[0].Workspaces[0].Urgent = true - assert.True(t, stateChanged(a, b)) - - b.Groups[0].Workspaces[0].Urgent = false - b.Groups[0].Workspaces[0].Hidden = true - assert.True(t, stateChanged(a, b)) -} - -func TestStateChanged_WorkspaceCoordinatesDiffer(t *testing.T) { - a := &State{Groups: []*WorkspaceGroup{{ - ID: "group-1", - Outputs: []string{}, - Workspaces: []*Workspace{{ - ID: "1", Name: "ws1", Coordinates: []uint32{0, 0}, - }}, - }}} - b := &State{Groups: []*WorkspaceGroup{{ - ID: "group-1", - Outputs: []string{}, - Workspaces: []*Workspace{{ - ID: "1", Name: "ws1", Coordinates: []uint32{1, 0}, - }}, - }}} - assert.True(t, stateChanged(a, b)) - - b.Groups[0].Workspaces[0].Coordinates = []uint32{0} - assert.True(t, stateChanged(a, b)) -} - -func TestStateChanged_Equal(t *testing.T) { - a := &State{Groups: []*WorkspaceGroup{{ - ID: "group-1", - Outputs: []string{"eDP-1", "HDMI-A-1"}, - Workspaces: []*Workspace{ - {ID: "1", Name: "ws1", Coordinates: []uint32{0, 0}, State: 1, Active: true}, - {ID: "2", Name: "ws2", Coordinates: []uint32{1, 0}, State: 0, Active: false}, - }, - }}} - b := &State{Groups: []*WorkspaceGroup{{ - ID: "group-1", - Outputs: []string{"eDP-1", "HDMI-A-1"}, - Workspaces: []*Workspace{ - {ID: "1", Name: "ws1", Coordinates: []uint32{0, 0}, State: 1, Active: true}, - {ID: "2", Name: "ws2", Coordinates: []uint32{1, 0}, State: 0, Active: false}, - }, - }}} - assert.False(t, stateChanged(a, b)) -} - -func TestManager_ConcurrentGetState(t *testing.T) { - m := &Manager{ - state: &State{ - Groups: []*WorkspaceGroup{{ID: "group-1", Outputs: []string{"eDP-1"}}}, - }, - } - - var wg sync.WaitGroup - const goroutines = 50 - const iterations = 100 - - for i := 0; i < goroutines/2; i++ { - wg.Add(1) - go func() { - defer wg.Done() - for j := 0; j < iterations; j++ { - s := m.GetState() - _ = s.Groups - } - }() - } - - for i := 0; i < goroutines/2; i++ { - wg.Add(1) - go func(i int) { - defer wg.Done() - for j := 0; j < iterations; j++ { - m.stateMutex.Lock() - m.state = &State{ - Groups: []*WorkspaceGroup{{ID: "group-1", Outputs: []string{"eDP-1"}}}, - } - m.stateMutex.Unlock() - } - }(i) - } - - wg.Wait() -} - -func TestManager_ConcurrentSubscriberAccess(t *testing.T) { - m := &Manager{ - stopChan: make(chan struct{}), - dirty: make(chan struct{}, 1), - } - - var wg sync.WaitGroup - const goroutines = 20 - - for i := 0; i < goroutines; i++ { - wg.Add(1) - go func(id int) { - defer wg.Done() - subID := string(rune('a' + id)) - ch := m.Subscribe(subID) - assert.NotNil(t, ch) - time.Sleep(time.Millisecond) - m.Unsubscribe(subID) - }(i) - } - - wg.Wait() -} - -func TestManager_SyncmapGroupsConcurrentAccess(t *testing.T) { - m := &Manager{} - - var wg sync.WaitGroup - const goroutines = 30 - const iterations = 50 - - for i := 0; i < goroutines; i++ { - wg.Add(1) - go func(id int) { - defer wg.Done() - key := uint32(id) - - for j := 0; j < iterations; j++ { - state := &workspaceGroupState{ - id: key, - outputIDs: map[uint32]bool{1: true}, - workspaceIDs: []uint32{uint32(j)}, - } - m.groups.Store(key, state) - - if loaded, ok := m.groups.Load(key); ok { - assert.Equal(t, key, loaded.id) - } - - m.groups.Range(func(k uint32, v *workspaceGroupState) bool { - _ = v.id - _ = v.outputIDs - return true - }) - } - - m.groups.Delete(key) - }(i) - } - - wg.Wait() -} - -func TestManager_SyncmapWorkspacesConcurrentAccess(t *testing.T) { - m := &Manager{} - - var wg sync.WaitGroup - const goroutines = 30 - const iterations = 50 - - for i := 0; i < goroutines; i++ { - wg.Add(1) - go func(id int) { - defer wg.Done() - key := uint32(id) - - for j := 0; j < iterations; j++ { - state := &workspaceState{ - id: key, - workspaceID: "ws-1", - name: "workspace", - state: uint32(j % 4), - coordinates: []uint32{uint32(j), 0}, - } - m.workspaces.Store(key, state) - - if loaded, ok := m.workspaces.Load(key); ok { - assert.Equal(t, key, loaded.id) - } - - m.workspaces.Range(func(k uint32, v *workspaceState) bool { - _ = v.name - _ = v.state - return true - }) - } - - m.workspaces.Delete(key) - }(i) - } - - wg.Wait() -} - -func TestManager_SyncmapOutputNamesConcurrentAccess(t *testing.T) { - m := &Manager{} - - var wg sync.WaitGroup - const goroutines = 30 - const iterations = 50 - - for i := 0; i < goroutines; i++ { - wg.Add(1) - go func(id int) { - defer wg.Done() - key := uint32(id) - - for j := 0; j < iterations; j++ { - m.outputNames.Store(key, "eDP-1") - - if loaded, ok := m.outputNames.Load(key); ok { - assert.NotEmpty(t, loaded) - } - - m.outputNames.Range(func(k uint32, v string) bool { - _ = v - return true - }) - } - - m.outputNames.Delete(key) - }(i) - } - - wg.Wait() -} - -func TestManager_NotifySubscribersNonBlocking(t *testing.T) { - m := &Manager{ - dirty: make(chan struct{}, 1), - } - - for i := 0; i < 10; i++ { - m.notifySubscribers() - } - - assert.Len(t, m.dirty, 1) -} - -func TestManager_PostQueueFull(t *testing.T) { - m := &Manager{ - cmdq: make(chan cmd, 2), - stopChan: make(chan struct{}), - } - - m.post(func() {}) - m.post(func() {}) - m.post(func() {}) - m.post(func() {}) - - assert.Len(t, m.cmdq, 2) -} - -func TestManager_GetStateNilState(t *testing.T) { - m := &Manager{} - - s := m.GetState() - assert.NotNil(t, s.Groups) - assert.Empty(t, s.Groups) -} - -func TestWorkspace_Fields(t *testing.T) { - ws := Workspace{ - ID: "ws-1", - Name: "workspace 1", - Coordinates: []uint32{0, 0}, - State: 1, - Active: true, - Urgent: false, - Hidden: false, - } - - assert.Equal(t, "ws-1", ws.ID) - assert.Equal(t, "workspace 1", ws.Name) - assert.True(t, ws.Active) - assert.False(t, ws.Urgent) - assert.False(t, ws.Hidden) -} - -func TestWorkspaceGroup_Fields(t *testing.T) { - group := WorkspaceGroup{ - ID: "group-1", - Outputs: []string{"eDP-1", "HDMI-A-1"}, - Workspaces: []*Workspace{ - {ID: "ws-1", Name: "workspace 1"}, - }, - } - - assert.Equal(t, "group-1", group.ID) - assert.Len(t, group.Outputs, 2) - assert.Len(t, group.Workspaces, 1) -} - -func TestNewManager_GetRegistryError(t *testing.T) { - mockDisplay := mocks_wlclient.NewMockWaylandDisplay(t) - - mockDisplay.EXPECT().Context().Return(nil) - mockDisplay.EXPECT().GetRegistry().Return(nil, errors.New("failed to get registry")) - - _, err := NewManager(mockDisplay) - assert.Error(t, err) - assert.Contains(t, err.Error(), "failed to get registry") -} diff --git a/core/internal/server/extworkspace/types.go b/core/internal/server/extworkspace/types.go deleted file mode 100644 index 903a1cb6..00000000 --- a/core/internal/server/extworkspace/types.go +++ /dev/null @@ -1,169 +0,0 @@ -package extworkspace - -import ( - "sync" - - "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/ext_workspace" - wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap" -) - -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.WaylandDisplay - ctx *wlclient.Context - registry *wlclient.Registry - manager *ext_workspace.ExtWorkspaceManagerV1 - - outputNames syncmap.Map[uint32, string] - - groups syncmap.Map[uint32, *workspaceGroupState] - - workspaces syncmap.Map[uint32, *workspaceState] - - wlMutex sync.Mutex - cmdq chan cmd - stopChan chan struct{} - wg sync.WaitGroup - - subscribers syncmap.Map[string, chan State] - 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.subscribers.Store(id, ch) - - return ch -} - -func (m *Manager) Unsubscribe(id string) { - if ch, ok := m.subscribers.LoadAndDelete(id); ok { - close(ch) - } -} - -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 -} diff --git a/core/internal/server/router.go b/core/internal/server/router.go index a0543801..e0f97b80 100644 --- a/core/internal/server/router.go +++ b/core/internal/server/router.go @@ -13,7 +13,6 @@ import ( serverDbus "github.com/AvengeMedia/DankMaterialShell/core/internal/server/dbus" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/dwl" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/evdev" - "github.com/AvengeMedia/DankMaterialShell/core/internal/server/extworkspace" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/freedesktop" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/location" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/loginctl" @@ -138,27 +137,6 @@ func RouteRequest(conn net.Conn, req models.Request) { return } - if strings.HasPrefix(req.Method, "extworkspace.") { - if extWorkspaceManager == nil { - 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 - } - } - extworkspace.HandleRequest(conn, req, extWorkspaceManager) - return - } - if strings.HasPrefix(req.Method, "wlroutput.") { if wlrOutputManager == nil { models.RespondError(conn, req.ID, "wlroutput manager not initialized") diff --git a/core/internal/server/server.go b/core/internal/server/server.go index e78acac5..2bfbb250 100644 --- a/core/internal/server/server.go +++ b/core/internal/server/server.go @@ -24,7 +24,6 @@ import ( serverDbus "github.com/AvengeMedia/DankMaterialShell/core/internal/server/dbus" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/dwl" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/evdev" - "github.com/AvengeMedia/DankMaterialShell/core/internal/server/extworkspace" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/freedesktop" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/location" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/loginctl" @@ -68,7 +67,6 @@ var appPickerManager *apppicker.Manager var cupsManager *cups.Manager var tailscaleManager *tailscale.Manager var dwlManager *dwl.Manager -var extWorkspaceManager *extworkspace.Manager var brightnessManager *brightness.Manager var wlrOutputManager *wlroutput.Manager var evdevManager *evdev.Manager @@ -86,8 +84,6 @@ const dbusClientID = "dms-dbus-client" 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 != "" { @@ -293,30 +289,6 @@ func InitializeBrightnessManager() error { return nil } -func InitializeExtWorkspaceManager() error { - log.Info("Attempting to initialize ExtWorkspace...") - - if wlContext == nil { - ctx, err := wlcontext.New() - if err != nil { - log.Errorf("Failed to create shared Wayland context: %v", err) - return err - } - wlContext = ctx - } - - manager, err := extworkspace.NewManager(wlContext.Display()) - if err != nil { - log.Debug("Failed to initialize extworkspace manager: %v", err) - return err - } - - extWorkspaceManager = manager - - log.Info("ExtWorkspace initialized successfully") - return nil -} - func InitializeWlrOutputManager() error { log.Info("Attempting to initialize WlrOutput management...") @@ -499,10 +471,6 @@ func getCapabilities() Capabilities { caps = append(caps, "dwl") } - if extWorkspaceAvailable.Load() { - caps = append(caps, "extworkspace") - } - if brightnessManager != nil { caps = append(caps, "brightness") } @@ -573,10 +541,6 @@ func getServerInfo() ServerInfo { caps = append(caps, "dwl") } - if extWorkspaceAvailable.Load() { - caps = append(caps, "extworkspace") - } - if brightnessManager != nil { caps = append(caps, "brightness") } @@ -1113,50 +1077,6 @@ func handleSubscribe(conn net.Conn, req models.Request) { }() } - if shouldSubscribe("extworkspace") { - 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 { - wg.Add(1) - extWorkspaceChan := extWorkspaceManager.Subscribe(clientID + "-extworkspace") - go func() { - defer wg.Done() - defer extWorkspaceManager.Unsubscribe(clientID + "-extworkspace") - - initialState := extWorkspaceManager.GetState() - select { - case eventChan <- ServiceEvent{Service: "extworkspace", Data: initialState}: - case <-stopChan: - return - } - - for { - select { - case state, ok := <-extWorkspaceChan: - if !ok { - return - } - select { - case eventChan <- ServiceEvent{Service: "extworkspace", Data: state}: - case <-stopChan: - return - } - case <-stopChan: - return - } - } - }() - } - } - if shouldSubscribe("brightness") && brightnessManager != nil { wg.Add(2) brightnessStateChan := brightnessManager.Subscribe(clientID + "-brightness-state") @@ -1415,9 +1335,6 @@ func cleanupManagers() { if dwlManager != nil { dwlManager.Close() } - if extWorkspaceManager != nil { - extWorkspaceManager.Close() - } if brightnessManager != nil { brightnessManager.Close() } @@ -1597,13 +1514,6 @@ func Start(printDocs bool) error { log.Info(" - appId : Focused window app ID") log.Info(" - kbLayout : Current keyboard layout") log.Info(" - keymode : Current keybind mode") - log.Info("ExtWorkspace:") - log.Info(" extworkspace.getState - Get current workspace state (groups, workspaces)") - log.Info(" extworkspace.activateWorkspace - Activate workspace (params: groupID, workspaceID)") - log.Info(" extworkspace.deactivateWorkspace - Deactivate workspace (params: groupID, workspaceID)") - log.Info(" extworkspace.removeWorkspace - Remove workspace (params: groupID, workspaceID)") - log.Info(" extworkspace.createWorkspace - Create workspace (params: groupID, name)") - log.Info(" extworkspace.subscribe - Subscribe to workspace state changes (streaming)") log.Info("Brightness:") log.Info(" brightness.getState - Get current brightness state for all devices") log.Info(" brightness.setBrightness - Set device brightness (params: device, percent)") @@ -1784,14 +1694,6 @@ 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 490b3c34..12c14860 100644 --- a/quickshell/Modules/DankBar/Widgets/WorkspaceSwitcher.qml +++ b/quickshell/Modules/DankBar/Widgets/WorkspaceSwitcher.qml @@ -4,6 +4,7 @@ import Quickshell import Quickshell.Widgets import Quickshell.Hyprland import Quickshell.I3 +import Quickshell.WindowManager import qs.Common import qs.Services import qs.Widgets @@ -86,7 +87,22 @@ Item { } } - readonly property bool useExtWorkspace: DMSService.forceExtWorkspace || (!CompositorService.isNiri && !CompositorService.isHyprland && !CompositorService.isDwl && !CompositorService.isSway && !CompositorService.isScroll && !CompositorService.isMiracle && ExtWorkspaceService.extWorkspaceAvailable) + readonly property var extProjection: (useExtWorkspace && parentScreen) ? WindowManager.screenProjection(parentScreen) : null + readonly property bool useExtWorkspace: { + if (Quickshell.env("DMS_FORCE_EXTWS") === "1") + return (WindowManager.windowsets?.length ?? 0) > 0; + switch (CompositorService.compositor) { + case "niri": + case "hyprland": + case "dwl": + case "sway": + case "scroll": + case "miracle": + return false; + default: + return (WindowManager.windowsets?.length ?? 0) > 0; + } + } Connections { target: DesktopEntries @@ -361,7 +377,7 @@ Item { "id": "", "name": "", "active": false, - "hidden": true + "_placeholder": true }; } else if (CompositorService.isNiri) { placeholder = { @@ -493,33 +509,21 @@ Item { } function getExtWorkspaceWorkspaces() { - const groups = ExtWorkspaceService.groups; - if (!ExtWorkspaceService.extWorkspaceAvailable || groups.length === 0) { - return [ - { - "id": "1", - "name": "1", - "active": false - } - ]; - } + const fallback = [ + { + "id": "1", + "name": "1", + "active": false + } + ]; + if (!extProjection) + return fallback; - const group = groups.find(g => g.outputs && g.outputs.includes(root.screenName)); - if (!group || !group.workspaces) { - return [ - { - "id": "1", - "name": "1", - "active": false - } - ]; - } - - let visible = group.workspaces.filter(ws => !ws.hidden); + let visible = extProjection.windowsets.filter(ws => ws.shouldDisplay); const hasValidCoordinates = visible.some(ws => ws.coordinates && ws.coordinates.length > 0); if (hasValidCoordinates) { - visible = visible.sort((a, b) => { + visible = visible.slice().sort((a, b) => { const coordsA = a.coordinates || [0, 0]; const coordsB = b.coordinates || [0, 0]; if (coordsA[0] !== coordsB[0]) @@ -528,33 +532,14 @@ Item { }); } - visible = visible.map(ws => ({ - id: ws.id, - name: ws.name, - coordinates: ws.coordinates, - state: ws.state, - active: ws.active, - urgent: ws.urgent, - hidden: ws.hidden, - groupID: group.id - })); - - return visible.length > 0 ? visible : [ - { - "id": "1", - "name": "1", - "active": false - } - ]; + return visible.length > 0 ? visible : fallback; } function getExtWorkspaceActiveWorkspace() { - if (!ExtWorkspaceService.extWorkspaceAvailable) { - return 1; - } - - const activeWs = ExtWorkspaceService.getActiveWorkspaceForOutput(root.screenName); - return activeWs ? (activeWs.id || activeWs.name || "1") : "1"; + if (!extProjection) + return ""; + const activeWs = extProjection.windowsets.find(ws => ws.active); + return activeWs ? (activeWs.id || activeWs.name || "") : ""; } readonly property real dpr: parentScreen ? CompositorService.getScreenScale(parentScreen) : 1 @@ -566,7 +551,7 @@ Item { function getRealWorkspaces() { return root.workspaceList.filter(ws => { if (useExtWorkspace) - return ws && (ws.id !== "" || ws.name !== "") && !ws.hidden; + return ws && !ws._placeholder; if (CompositorService.isNiri) return ws && ws.idx !== -1; if (CompositorService.isHyprland) @@ -583,8 +568,9 @@ Item { if (!data) return; - if (root.useExtWorkspace && (data.id || data.name)) { - ExtWorkspaceService.activateWorkspace(data.id || data.name, data.groupID || ""); + if (root.useExtWorkspace) { + if (typeof data.activate === "function") + data.activate(); return; } @@ -649,7 +635,8 @@ Item { } const nextWorkspace = realWorkspaces[nextIndex]; - ExtWorkspaceService.activateWorkspace(nextWorkspace.id || nextWorkspace.name, nextWorkspace.groupID || ""); + if (typeof nextWorkspace.activate === "function") + nextWorkspace.activate(); } else if (CompositorService.isNiri) { const realWorkspaces = getRealWorkspaces(); if (realWorkspaces.length < 2) { @@ -1013,7 +1000,7 @@ Item { } property bool isPlaceholder: { if (root.useExtWorkspace) - return !!(modelData && modelData.hidden); + return !!(modelData && modelData._placeholder); if (CompositorService.isNiri) return !!(modelData && modelData.idx === -1); if (CompositorService.isHyprland) @@ -1313,8 +1300,9 @@ Item { return; if (mouse.button === Qt.LeftButton) { - if (root.useExtWorkspace && (modelData?.id || modelData?.name)) { - ExtWorkspaceService.activateWorkspace(modelData.id || modelData.name, modelData.groupID || ""); + if (root.useExtWorkspace) { + if (typeof modelData?.activate === "function") + modelData.activate(); } else if (CompositorService.isNiri) { if (modelData && modelData.id !== undefined) { NiriService.switchToWorkspace(modelData.id); @@ -1941,9 +1929,9 @@ Item { } } Connections { - target: ExtWorkspaceService + target: WindowManager enabled: root.useExtWorkspace - function onStateChanged() { + function onWindowsetsChanged() { delegateRoot.updateAllData(); } } @@ -1952,9 +1940,6 @@ Item { } Component.onCompleted: { - if (useExtWorkspace && !DMSService.activeSubscriptions.includes("extworkspace")) { - DMSService.addSubscription("extworkspace"); - } _updateBlurRegistration(); } diff --git a/quickshell/Services/DMSService.qml b/quickshell/Services/DMSService.qml index c0305316..35ae37c0 100644 --- a/quickshell/Services/DMSService.qml +++ b/quickshell/Services/DMSService.qml @@ -23,7 +23,6 @@ Singleton { property bool isConnected: false property bool isConnecting: false property bool subscribeConnected: false - readonly property bool forceExtWorkspace: false readonly property string socketPath: Quickshell.env("DMS_SOCKET") @@ -53,7 +52,6 @@ Singleton { signal dwlStateUpdate(var data) signal brightnessStateUpdate(var data) signal brightnessDeviceUpdate(var device) - signal extWorkspaceStateUpdate(var data) signal wlrOutputStateUpdate(var data) signal evdevStateUpdate(var data) signal gammaStateUpdate(var data) @@ -288,7 +286,7 @@ Singleton { function removeSubscription(service) { if (activeSubscriptions.includes("all")) { - const allServices = ["network", "loginctl", "freedesktop", "gamma", "bluetooth", "dwl", "brightness", "extworkspace", "browser", "location"]; + const allServices = ["network", "loginctl", "freedesktop", "gamma", "bluetooth", "dwl", "brightness", "browser", "location"]; const filtered = allServices.filter(s => s !== service); subscribe(filtered); } else { @@ -310,7 +308,7 @@ Singleton { excludeServices = [excludeServices]; } - const allServices = ["network", "loginctl", "freedesktop", "gamma", "theme.auto", "bluetooth", "cups", "dwl", "brightness", "extworkspace", "browser", "dbus", "location"]; + const allServices = ["network", "loginctl", "freedesktop", "gamma", "theme.auto", "bluetooth", "cups", "dwl", "brightness", "browser", "dbus", "location"]; const filtered = allServices.filter(s => !excludeServices.includes(s)); subscribe(filtered); } @@ -364,8 +362,6 @@ Singleton { if (data.device) { brightnessDeviceUpdate(data.device); } - } else if (service === "extworkspace") { - extWorkspaceStateUpdate(data); } else if (service === "wlroutput") { wlrOutputStateUpdate(data); } else if (service === "evdev") { @@ -752,12 +748,6 @@ Singleton { }); } - function renameWorkspace(name, callback) { - sendRequest("extworkspace.renameWorkspace", { - "name": name - }, callback); - } - function sysupdateGetState(callback) { sendRequest("sysupdate.getState", null, callback); } diff --git a/quickshell/Services/ExtWorkspaceService.qml b/quickshell/Services/ExtWorkspaceService.qml deleted file mode 100644 index 5b4fc654..00000000 --- a/quickshell/Services/ExtWorkspaceService.qml +++ /dev/null @@ -1,279 +0,0 @@ -pragma Singleton -pragma ComponentBehavior: Bound - -import QtQuick -import Quickshell -import qs.Services - -Singleton { - id: root - readonly property var log: Log.scoped("ExtWorkspaceService") - - property bool extWorkspaceAvailable: false - property var groups: [] - property var _cachedWorkspaces: ({}) - - signal stateChanged - - Connections { - target: DMSService - function onCapabilitiesReceived() { - checkCapabilities(); - } - function onConnectionStateChanged() { - if (DMSService.isConnected) { - checkCapabilities(); - } else { - extWorkspaceAvailable = false; - } - } - function onExtWorkspaceStateUpdate(data) { - if (extWorkspaceAvailable) { - handleStateUpdate(data); - } - } - } - - Component.onCompleted: { - if (DMSService.dmsAvailable) { - checkCapabilities(); - } - } - - function checkCapabilities() { - if (!DMSService.capabilities || !Array.isArray(DMSService.capabilities)) { - extWorkspaceAvailable = false; - return; - } - - const hasExtWorkspace = DMSService.capabilities.includes("extworkspace"); - if (hasExtWorkspace && !extWorkspaceAvailable) { - if (typeof CompositorService !== "undefined") { - const useExtWorkspace = DMSService.forceExtWorkspace || (!CompositorService.isNiri && !CompositorService.isHyprland && !CompositorService.isDwl && !CompositorService.isSway && !CompositorService.isScroll && !CompositorService.isMiracle); - if (!useExtWorkspace) { - log.info("ext-workspace available but compositor has native support"); - extWorkspaceAvailable = false; - return; - } - } - extWorkspaceAvailable = true; - log.info("ext-workspace capability detected"); - DMSService.addSubscription("extworkspace"); - requestState(); - } else if (!hasExtWorkspace) { - extWorkspaceAvailable = false; - } - } - - function requestState() { - if (!DMSService.isConnected || !extWorkspaceAvailable) { - return; - } - - DMSService.sendRequest("extworkspace.getState", null, response => { - if (response.result) { - handleStateUpdate(response.result); - } - }); - } - - function handleStateUpdate(state) { - groups = state.groups || []; - if (groups.length === 0) { - log.warn("Received empty workspace groups from backend"); - } else { - log.debug("Updated with", groups.length, "workspace groups"); - } - stateChanged(); - } - - function activateWorkspace(workspaceID, groupID = "") { - if (!DMSService.isConnected || !extWorkspaceAvailable) { - return; - } - - DMSService.sendRequest("extworkspace.activateWorkspace", { - "workspaceID": workspaceID, - "groupID": groupID - }, response => { - if (response.error) { - log.warn("activateWorkspace error:", response.error); - } - }); - } - - function deactivateWorkspace(workspaceID, groupID = "") { - if (!DMSService.isConnected || !extWorkspaceAvailable) { - return; - } - - DMSService.sendRequest("extworkspace.deactivateWorkspace", { - "workspaceID": workspaceID, - "groupID": groupID - }, response => { - if (response.error) { - log.warn("deactivateWorkspace error:", response.error); - } - }); - } - - function removeWorkspace(workspaceID, groupID = "") { - if (!DMSService.isConnected || !extWorkspaceAvailable) { - return; - } - - DMSService.sendRequest("extworkspace.removeWorkspace", { - "workspaceID": workspaceID, - "groupID": groupID - }, response => { - if (response.error) { - log.warn("removeWorkspace error:", response.error); - } - }); - } - - function createWorkspace(groupID, name) { - if (!DMSService.isConnected || !extWorkspaceAvailable) { - return; - } - - DMSService.sendRequest("extworkspace.createWorkspace", { - "groupID": groupID, - "name": name - }, response => { - if (response.error) { - log.warn("createWorkspace error:", response.error); - } - }); - } - - function getGroupForOutput(outputName) { - for (const group of groups) { - if (group.outputs && group.outputs.includes(outputName)) { - return group; - } - } - return null; - } - - function getWorkspacesForOutput(outputName) { - const group = getGroupForOutput(outputName); - return group ? (group.workspaces || []) : []; - } - - function getActiveWorkspaces() { - const active = []; - for (const group of groups) { - if (!group.workspaces) - continue; - for (const ws of group.workspaces) { - if (ws.active) { - active.push({ - workspace: ws, - group: group, - outputs: group.outputs || [] - }); - } - } - } - return active; - } - - function getActiveWorkspaceForOutput(outputName) { - const group = getGroupForOutput(outputName); - if (!group || !group.workspaces) - return null; - - for (const ws of group.workspaces) { - if (ws.active) { - return ws; - } - } - return null; - } - - function getVisibleWorkspaces(outputName) { - const workspaces = getWorkspacesForOutput(outputName); - 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]) { - _cachedWorkspaces[cacheKey] = { - workspaces: [], - lastNames: [] - }; - } - - const cache = _cachedWorkspaces[cacheKey]; - const currentNames = visible.map(ws => ws.name || ws.id); - const namesChanged = JSON.stringify(cache.lastNames) !== JSON.stringify(currentNames); - - if (namesChanged || cache.workspaces.length !== visible.length) { - cache.workspaces = visible.map(ws => ({ - id: ws.id, - name: ws.name, - coordinates: ws.coordinates, - state: ws.state, - active: ws.active, - urgent: ws.urgent, - hidden: ws.hidden - })); - cache.lastNames = currentNames; - return cache.workspaces; - } - - for (let i = 0; i < visible.length; i++) { - const src = visible[i]; - const dst = cache.workspaces[i]; - dst.id = src.id; - dst.name = src.name; - dst.coordinates = src.coordinates; - dst.state = src.state; - dst.active = src.active; - dst.urgent = src.urgent; - dst.hidden = src.hidden; - } - - return cache.workspaces; - } - - function getUrgentWorkspaces() { - const urgent = []; - for (const group of groups) { - if (!group.workspaces) - continue; - for (const ws of group.workspaces) { - if (ws.urgent) { - urgent.push({ - workspace: ws, - group: group, - outputs: group.outputs || [] - }); - } - } - } - return urgent; - } - - function switchToWorkspace(outputName, workspaceName) { - const workspaces = getWorkspacesForOutput(outputName); - for (const ws of workspaces) { - if (ws.name === workspaceName || ws.id === workspaceName) { - activateWorkspace(ws.name || ws.id); - return; - } - } - log.warn("workspace not found:", workspaceName); - } -}