diff --git a/core/cmd/dms/commands_config.go b/core/cmd/dms/commands_config.go index ccb49e39..2eace0d1 100644 --- a/core/cmd/dms/commands_config.go +++ b/core/cmd/dms/commands_config.go @@ -72,7 +72,7 @@ func runResolveInclude(cmd *cobra.Command, args []string) { result, err = checkHyprlandInclude(filename) case "niri": result, err = checkNiriInclude(filename) - case "mangowc", "dwl", "mango": + case "mangowc", "mango": result, err = checkMangoWCInclude(filename) default: log.Fatalf("Unknown compositor: %s", compositor) diff --git a/core/cmd/dms/commands_screenshot.go b/core/cmd/dms/commands_screenshot.go index 909b6ded..262fc59b 100644 --- a/core/cmd/dms/commands_screenshot.go +++ b/core/cmd/dms/commands_screenshot.go @@ -39,7 +39,7 @@ Modes: full - Capture the focused output all - Capture all outputs combined output - Capture a specific output by name - window - Capture the focused window (Hyprland/DWL) + window - Capture the focused window (Hyprland/Mango) last - Capture the last selected region Output format (--format): @@ -97,7 +97,7 @@ If no previous region exists, falls back to interactive selection.`, var ssWindowCmd = &cobra.Command{ Use: "window", Short: "Capture the focused window", - Long: `Capture the currently focused window. Supported on Hyprland and DWL.`, + Long: `Capture the currently focused window. Supported on Hyprland and Mango.`, Run: runScreenshotWindow, } diff --git a/core/internal/proto/dwl_ipc/dwl_ipc.go b/core/internal/proto/dwl_ipc/dwl_ipc.go deleted file mode 100644 index fc0e50bc..00000000 --- a/core/internal/proto/dwl_ipc/dwl_ipc.go +++ /dev/null @@ -1,791 +0,0 @@ -// Generated by go-wayland-scanner -// https://github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/cmd/go-wayland-scanner -// XML file : internal/proto/xml/dwl-ipc-unstable-v2.xml -// -// dwl_ipc_unstable_v2 Protocol Copyright: - -package dwl_ipc - -import "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client" - -// ZdwlIpcManagerV2InterfaceName 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 ZdwlIpcManagerV2InterfaceName = "zdwl_ipc_manager_v2" - -// ZdwlIpcManagerV2 : manage dwl state -// -// This interface is exposed as a global in wl_registry. -// -// Clients can use this interface to get a dwl_ipc_output. -// After binding the client will recieve the dwl_ipc_manager.tags and dwl_ipc_manager.layout events. -// The dwl_ipc_manager.tags and dwl_ipc_manager.layout events expose tags and layouts to the client. -type ZdwlIpcManagerV2 struct { - client.BaseProxy - tagsHandler ZdwlIpcManagerV2TagsHandlerFunc - layoutHandler ZdwlIpcManagerV2LayoutHandlerFunc -} - -// NewZdwlIpcManagerV2 : manage dwl state -// -// This interface is exposed as a global in wl_registry. -// -// Clients can use this interface to get a dwl_ipc_output. -// After binding the client will recieve the dwl_ipc_manager.tags and dwl_ipc_manager.layout events. -// The dwl_ipc_manager.tags and dwl_ipc_manager.layout events expose tags and layouts to the client. -func NewZdwlIpcManagerV2(ctx *client.Context) *ZdwlIpcManagerV2 { - zdwlIpcManagerV2 := &ZdwlIpcManagerV2{} - ctx.Register(zdwlIpcManagerV2) - return zdwlIpcManagerV2 -} - -// Release : release dwl_ipc_manager -// -// Indicates that the client will not the dwl_ipc_manager object anymore. -// Objects created through this instance are not affected. -func (i *ZdwlIpcManagerV2) Release() 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 -} - -// GetOutput : get a dwl_ipc_outout for a wl_output -// -// Get a dwl_ipc_outout for the specified wl_output. -func (i *ZdwlIpcManagerV2) GetOutput(output *client.Output) (*ZdwlIpcOutputV2, error) { - id := NewZdwlIpcOutputV2(i.Context()) - const opcode = 1 - const _reqBufLen = 8 + 4 + 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], id.ID()) - l += 4 - client.PutUint32(_reqBuf[l:l+4], output.ID()) - l += 4 - err := i.Context().WriteMsg(_reqBuf[:], nil) - return id, err -} - -// ZdwlIpcManagerV2TagsEvent : Announces tag amount -// -// This event is sent after binding. -// A roundtrip after binding guarantees the client recieved all tags. -type ZdwlIpcManagerV2TagsEvent struct { - Amount uint32 -} -type ZdwlIpcManagerV2TagsHandlerFunc func(ZdwlIpcManagerV2TagsEvent) - -// SetTagsHandler : sets handler for ZdwlIpcManagerV2TagsEvent -func (i *ZdwlIpcManagerV2) SetTagsHandler(f ZdwlIpcManagerV2TagsHandlerFunc) { - i.tagsHandler = f -} - -// ZdwlIpcManagerV2LayoutEvent : Announces a layout -// -// This event is sent after binding. -// A roundtrip after binding guarantees the client recieved all layouts. -type ZdwlIpcManagerV2LayoutEvent struct { - Name string -} -type ZdwlIpcManagerV2LayoutHandlerFunc func(ZdwlIpcManagerV2LayoutEvent) - -// SetLayoutHandler : sets handler for ZdwlIpcManagerV2LayoutEvent -func (i *ZdwlIpcManagerV2) SetLayoutHandler(f ZdwlIpcManagerV2LayoutHandlerFunc) { - i.layoutHandler = f -} - -func (i *ZdwlIpcManagerV2) Dispatch(opcode uint32, fd int, data []byte) { - switch opcode { - case 0: - if i.tagsHandler == nil { - return - } - var e ZdwlIpcManagerV2TagsEvent - l := 0 - e.Amount = client.Uint32(data[l : l+4]) - l += 4 - - i.tagsHandler(e) - case 1: - if i.layoutHandler == nil { - return - } - var e ZdwlIpcManagerV2LayoutEvent - 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.layoutHandler(e) - } -} - -// ZdwlIpcOutputV2InterfaceName 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 ZdwlIpcOutputV2InterfaceName = "zdwl_ipc_output_v2" - -// ZdwlIpcOutputV2 : control dwl output -// -// Observe and control a dwl output. -// -// Events are double-buffered: -// Clients should cache events and redraw when a dwl_ipc_output.frame event is sent. -// -// Request are not double-buffered: -// The compositor will update immediately upon request. -type ZdwlIpcOutputV2 struct { - client.BaseProxy - toggleVisibilityHandler ZdwlIpcOutputV2ToggleVisibilityHandlerFunc - activeHandler ZdwlIpcOutputV2ActiveHandlerFunc - tagHandler ZdwlIpcOutputV2TagHandlerFunc - layoutHandler ZdwlIpcOutputV2LayoutHandlerFunc - titleHandler ZdwlIpcOutputV2TitleHandlerFunc - appidHandler ZdwlIpcOutputV2AppidHandlerFunc - layoutSymbolHandler ZdwlIpcOutputV2LayoutSymbolHandlerFunc - frameHandler ZdwlIpcOutputV2FrameHandlerFunc - fullscreenHandler ZdwlIpcOutputV2FullscreenHandlerFunc - floatingHandler ZdwlIpcOutputV2FloatingHandlerFunc - xHandler ZdwlIpcOutputV2XHandlerFunc - yHandler ZdwlIpcOutputV2YHandlerFunc - widthHandler ZdwlIpcOutputV2WidthHandlerFunc - heightHandler ZdwlIpcOutputV2HeightHandlerFunc - lastLayerHandler ZdwlIpcOutputV2LastLayerHandlerFunc - kbLayoutHandler ZdwlIpcOutputV2KbLayoutHandlerFunc - keymodeHandler ZdwlIpcOutputV2KeymodeHandlerFunc - scalefactorHandler ZdwlIpcOutputV2ScalefactorHandlerFunc -} - -// NewZdwlIpcOutputV2 : control dwl output -// -// Observe and control a dwl output. -// -// Events are double-buffered: -// Clients should cache events and redraw when a dwl_ipc_output.frame event is sent. -// -// Request are not double-buffered: -// The compositor will update immediately upon request. -func NewZdwlIpcOutputV2(ctx *client.Context) *ZdwlIpcOutputV2 { - zdwlIpcOutputV2 := &ZdwlIpcOutputV2{} - ctx.Register(zdwlIpcOutputV2) - return zdwlIpcOutputV2 -} - -// Release : release dwl_ipc_outout -// -// Indicates to that the client no longer needs this dwl_ipc_output. -func (i *ZdwlIpcOutputV2) Release() 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 -} - -// SetTags : Set the active tags of this output -// -// tagmask: bitmask of the tags that should be set. -// toggleTagset: toggle the selected tagset, zero for invalid, nonzero for valid. -func (i *ZdwlIpcOutputV2) SetTags(tagmask, toggleTagset uint32) error { - const opcode = 1 - const _reqBufLen = 8 + 4 + 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], uint32(tagmask)) - l += 4 - client.PutUint32(_reqBuf[l:l+4], uint32(toggleTagset)) - l += 4 - err := i.Context().WriteMsg(_reqBuf[:], nil) - return err -} - -// SetClientTags : Set the tags of the focused client. -// -// The tags are updated as follows: -// new_tags = (current_tags AND and_tags) XOR xor_tags -func (i *ZdwlIpcOutputV2) SetClientTags(andTags, xorTags uint32) error { - const opcode = 2 - const _reqBufLen = 8 + 4 + 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], uint32(andTags)) - l += 4 - client.PutUint32(_reqBuf[l:l+4], uint32(xorTags)) - l += 4 - err := i.Context().WriteMsg(_reqBuf[:], nil) - return err -} - -// SetLayout : Set the layout of this output -// -// index: index of a layout recieved by dwl_ipc_manager.layout -func (i *ZdwlIpcOutputV2) SetLayout(index uint32) 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], uint32(index)) - l += 4 - err := i.Context().WriteMsg(_reqBuf[:], nil) - return err -} - -// Quit : Quit mango -// This request allows clients to instruct the compositor to quit mango. -func (i *ZdwlIpcOutputV2) Quit() 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 -} - -// SendDispatch : Set the active tags of this output -// -// dispatch: dispatch name. -// arg1: arg1. -// arg2: arg2. -// arg3: arg3. -// arg4: arg4. -// arg5: arg5. -func (i *ZdwlIpcOutputV2) SendDispatch(dispatch, arg1, arg2, arg3, arg4, arg5 string) error { - const opcode = 5 - dispatchLen := client.PaddedLen(len(dispatch) + 1) - arg1Len := client.PaddedLen(len(arg1) + 1) - arg2Len := client.PaddedLen(len(arg2) + 1) - arg3Len := client.PaddedLen(len(arg3) + 1) - arg4Len := client.PaddedLen(len(arg4) + 1) - arg5Len := client.PaddedLen(len(arg5) + 1) - _reqBufLen := 8 + (4 + dispatchLen) + (4 + arg1Len) + (4 + arg2Len) + (4 + arg3Len) + (4 + arg4Len) + (4 + arg5Len) - _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+dispatchLen)], dispatch) - l += (4 + dispatchLen) - client.PutString(_reqBuf[l:l+(4+arg1Len)], arg1) - l += (4 + arg1Len) - client.PutString(_reqBuf[l:l+(4+arg2Len)], arg2) - l += (4 + arg2Len) - client.PutString(_reqBuf[l:l+(4+arg3Len)], arg3) - l += (4 + arg3Len) - client.PutString(_reqBuf[l:l+(4+arg4Len)], arg4) - l += (4 + arg4Len) - client.PutString(_reqBuf[l:l+(4+arg5Len)], arg5) - l += (4 + arg5Len) - err := i.Context().WriteMsg(_reqBuf, nil) - return err -} - -type ZdwlIpcOutputV2TagState uint32 - -// ZdwlIpcOutputV2TagState : -const ( - // ZdwlIpcOutputV2TagStateNone : no state - ZdwlIpcOutputV2TagStateNone ZdwlIpcOutputV2TagState = 0 - // ZdwlIpcOutputV2TagStateActive : tag is active - ZdwlIpcOutputV2TagStateActive ZdwlIpcOutputV2TagState = 1 - // ZdwlIpcOutputV2TagStateUrgent : tag has at least one urgent client - ZdwlIpcOutputV2TagStateUrgent ZdwlIpcOutputV2TagState = 2 -) - -func (e ZdwlIpcOutputV2TagState) Name() string { - switch e { - case ZdwlIpcOutputV2TagStateNone: - return "none" - case ZdwlIpcOutputV2TagStateActive: - return "active" - case ZdwlIpcOutputV2TagStateUrgent: - return "urgent" - default: - return "" - } -} - -func (e ZdwlIpcOutputV2TagState) Value() string { - switch e { - case ZdwlIpcOutputV2TagStateNone: - return "0" - case ZdwlIpcOutputV2TagStateActive: - return "1" - case ZdwlIpcOutputV2TagStateUrgent: - return "2" - default: - return "" - } -} - -func (e ZdwlIpcOutputV2TagState) String() string { - return e.Name() + "=" + e.Value() -} - -// ZdwlIpcOutputV2ToggleVisibilityEvent : Toggle client visibilty -// -// Indicates the client should hide or show themselves. -// If the client is visible then hide, if hidden then show. -type ZdwlIpcOutputV2ToggleVisibilityEvent struct{} -type ZdwlIpcOutputV2ToggleVisibilityHandlerFunc func(ZdwlIpcOutputV2ToggleVisibilityEvent) - -// SetToggleVisibilityHandler : sets handler for ZdwlIpcOutputV2ToggleVisibilityEvent -func (i *ZdwlIpcOutputV2) SetToggleVisibilityHandler(f ZdwlIpcOutputV2ToggleVisibilityHandlerFunc) { - i.toggleVisibilityHandler = f -} - -// ZdwlIpcOutputV2ActiveEvent : Update the selected output. -// -// Indicates if the output is active. Zero is invalid, nonzero is valid. -type ZdwlIpcOutputV2ActiveEvent struct { - Active uint32 -} -type ZdwlIpcOutputV2ActiveHandlerFunc func(ZdwlIpcOutputV2ActiveEvent) - -// SetActiveHandler : sets handler for ZdwlIpcOutputV2ActiveEvent -func (i *ZdwlIpcOutputV2) SetActiveHandler(f ZdwlIpcOutputV2ActiveHandlerFunc) { - i.activeHandler = f -} - -// ZdwlIpcOutputV2TagEvent : Update the state of a tag. -// -// Indicates that a tag has been updated. -type ZdwlIpcOutputV2TagEvent struct { - Tag uint32 - State uint32 - Clients uint32 - Focused uint32 -} -type ZdwlIpcOutputV2TagHandlerFunc func(ZdwlIpcOutputV2TagEvent) - -// SetTagHandler : sets handler for ZdwlIpcOutputV2TagEvent -func (i *ZdwlIpcOutputV2) SetTagHandler(f ZdwlIpcOutputV2TagHandlerFunc) { - i.tagHandler = f -} - -// ZdwlIpcOutputV2LayoutEvent : Update the layout. -// -// Indicates a new layout is selected. -type ZdwlIpcOutputV2LayoutEvent struct { - Layout uint32 -} -type ZdwlIpcOutputV2LayoutHandlerFunc func(ZdwlIpcOutputV2LayoutEvent) - -// SetLayoutHandler : sets handler for ZdwlIpcOutputV2LayoutEvent -func (i *ZdwlIpcOutputV2) SetLayoutHandler(f ZdwlIpcOutputV2LayoutHandlerFunc) { - i.layoutHandler = f -} - -// ZdwlIpcOutputV2TitleEvent : Update the title. -// -// Indicates the title has changed. -type ZdwlIpcOutputV2TitleEvent struct { - Title string -} -type ZdwlIpcOutputV2TitleHandlerFunc func(ZdwlIpcOutputV2TitleEvent) - -// SetTitleHandler : sets handler for ZdwlIpcOutputV2TitleEvent -func (i *ZdwlIpcOutputV2) SetTitleHandler(f ZdwlIpcOutputV2TitleHandlerFunc) { - i.titleHandler = f -} - -// ZdwlIpcOutputV2AppidEvent : Update the appid. -// -// Indicates the appid has changed. -type ZdwlIpcOutputV2AppidEvent struct { - Appid string -} -type ZdwlIpcOutputV2AppidHandlerFunc func(ZdwlIpcOutputV2AppidEvent) - -// SetAppidHandler : sets handler for ZdwlIpcOutputV2AppidEvent -func (i *ZdwlIpcOutputV2) SetAppidHandler(f ZdwlIpcOutputV2AppidHandlerFunc) { - i.appidHandler = f -} - -// ZdwlIpcOutputV2LayoutSymbolEvent : Update the current layout symbol -// -// Indicates the layout has changed. Since layout symbols are dynamic. -// As opposed to the zdwl_ipc_manager.layout event, this should take precendence when displaying. -// You can ignore the zdwl_ipc_output.layout event. -type ZdwlIpcOutputV2LayoutSymbolEvent struct { - Layout string -} -type ZdwlIpcOutputV2LayoutSymbolHandlerFunc func(ZdwlIpcOutputV2LayoutSymbolEvent) - -// SetLayoutSymbolHandler : sets handler for ZdwlIpcOutputV2LayoutSymbolEvent -func (i *ZdwlIpcOutputV2) SetLayoutSymbolHandler(f ZdwlIpcOutputV2LayoutSymbolHandlerFunc) { - i.layoutSymbolHandler = f -} - -// ZdwlIpcOutputV2FrameEvent : The update sequence is done. -// -// Indicates that a sequence of status updates have finished and the client should redraw. -type ZdwlIpcOutputV2FrameEvent struct{} -type ZdwlIpcOutputV2FrameHandlerFunc func(ZdwlIpcOutputV2FrameEvent) - -// SetFrameHandler : sets handler for ZdwlIpcOutputV2FrameEvent -func (i *ZdwlIpcOutputV2) SetFrameHandler(f ZdwlIpcOutputV2FrameHandlerFunc) { - i.frameHandler = f -} - -// ZdwlIpcOutputV2FullscreenEvent : Update fullscreen status -// -// Indicates if the selected client on this output is fullscreen. -type ZdwlIpcOutputV2FullscreenEvent struct { - IsFullscreen uint32 -} -type ZdwlIpcOutputV2FullscreenHandlerFunc func(ZdwlIpcOutputV2FullscreenEvent) - -// SetFullscreenHandler : sets handler for ZdwlIpcOutputV2FullscreenEvent -func (i *ZdwlIpcOutputV2) SetFullscreenHandler(f ZdwlIpcOutputV2FullscreenHandlerFunc) { - i.fullscreenHandler = f -} - -// ZdwlIpcOutputV2FloatingEvent : Update the floating status -// -// Indicates if the selected client on this output is floating. -type ZdwlIpcOutputV2FloatingEvent struct { - IsFloating uint32 -} -type ZdwlIpcOutputV2FloatingHandlerFunc func(ZdwlIpcOutputV2FloatingEvent) - -// SetFloatingHandler : sets handler for ZdwlIpcOutputV2FloatingEvent -func (i *ZdwlIpcOutputV2) SetFloatingHandler(f ZdwlIpcOutputV2FloatingHandlerFunc) { - i.floatingHandler = f -} - -// ZdwlIpcOutputV2XEvent : Update the x coordinates -// -// Indicates if x coordinates of the selected client. -type ZdwlIpcOutputV2XEvent struct { - X int32 -} -type ZdwlIpcOutputV2XHandlerFunc func(ZdwlIpcOutputV2XEvent) - -// SetXHandler : sets handler for ZdwlIpcOutputV2XEvent -func (i *ZdwlIpcOutputV2) SetXHandler(f ZdwlIpcOutputV2XHandlerFunc) { - i.xHandler = f -} - -// ZdwlIpcOutputV2YEvent : Update the y coordinates -// -// Indicates if y coordinates of the selected client. -type ZdwlIpcOutputV2YEvent struct { - Y int32 -} -type ZdwlIpcOutputV2YHandlerFunc func(ZdwlIpcOutputV2YEvent) - -// SetYHandler : sets handler for ZdwlIpcOutputV2YEvent -func (i *ZdwlIpcOutputV2) SetYHandler(f ZdwlIpcOutputV2YHandlerFunc) { - i.yHandler = f -} - -// ZdwlIpcOutputV2WidthEvent : Update the width -// -// Indicates if width of the selected client. -type ZdwlIpcOutputV2WidthEvent struct { - Width int32 -} -type ZdwlIpcOutputV2WidthHandlerFunc func(ZdwlIpcOutputV2WidthEvent) - -// SetWidthHandler : sets handler for ZdwlIpcOutputV2WidthEvent -func (i *ZdwlIpcOutputV2) SetWidthHandler(f ZdwlIpcOutputV2WidthHandlerFunc) { - i.widthHandler = f -} - -// ZdwlIpcOutputV2HeightEvent : Update the height -// -// Indicates if height of the selected client. -type ZdwlIpcOutputV2HeightEvent struct { - Height int32 -} -type ZdwlIpcOutputV2HeightHandlerFunc func(ZdwlIpcOutputV2HeightEvent) - -// SetHeightHandler : sets handler for ZdwlIpcOutputV2HeightEvent -func (i *ZdwlIpcOutputV2) SetHeightHandler(f ZdwlIpcOutputV2HeightHandlerFunc) { - i.heightHandler = f -} - -// ZdwlIpcOutputV2LastLayerEvent : last map layer. -// -// last map layer. -type ZdwlIpcOutputV2LastLayerEvent struct { - LastLayer string -} -type ZdwlIpcOutputV2LastLayerHandlerFunc func(ZdwlIpcOutputV2LastLayerEvent) - -// SetLastLayerHandler : sets handler for ZdwlIpcOutputV2LastLayerEvent -func (i *ZdwlIpcOutputV2) SetLastLayerHandler(f ZdwlIpcOutputV2LastLayerHandlerFunc) { - i.lastLayerHandler = f -} - -// ZdwlIpcOutputV2KbLayoutEvent : current keyboard layout. -// -// current keyboard layout. -type ZdwlIpcOutputV2KbLayoutEvent struct { - KbLayout string -} -type ZdwlIpcOutputV2KbLayoutHandlerFunc func(ZdwlIpcOutputV2KbLayoutEvent) - -// SetKbLayoutHandler : sets handler for ZdwlIpcOutputV2KbLayoutEvent -func (i *ZdwlIpcOutputV2) SetKbLayoutHandler(f ZdwlIpcOutputV2KbLayoutHandlerFunc) { - i.kbLayoutHandler = f -} - -// ZdwlIpcOutputV2KeymodeEvent : current keybind mode. -// -// current keybind mode. -type ZdwlIpcOutputV2KeymodeEvent struct { - Keymode string -} -type ZdwlIpcOutputV2KeymodeHandlerFunc func(ZdwlIpcOutputV2KeymodeEvent) - -// SetKeymodeHandler : sets handler for ZdwlIpcOutputV2KeymodeEvent -func (i *ZdwlIpcOutputV2) SetKeymodeHandler(f ZdwlIpcOutputV2KeymodeHandlerFunc) { - i.keymodeHandler = f -} - -// ZdwlIpcOutputV2ScalefactorEvent : scale factor of monitor. -// -// scale factor of monitor. -type ZdwlIpcOutputV2ScalefactorEvent struct { - Scalefactor uint32 -} -type ZdwlIpcOutputV2ScalefactorHandlerFunc func(ZdwlIpcOutputV2ScalefactorEvent) - -// SetScalefactorHandler : sets handler for ZdwlIpcOutputV2ScalefactorEvent -func (i *ZdwlIpcOutputV2) SetScalefactorHandler(f ZdwlIpcOutputV2ScalefactorHandlerFunc) { - i.scalefactorHandler = f -} - -func (i *ZdwlIpcOutputV2) Dispatch(opcode uint32, fd int, data []byte) { - switch opcode { - case 0: - if i.toggleVisibilityHandler == nil { - return - } - var e ZdwlIpcOutputV2ToggleVisibilityEvent - - i.toggleVisibilityHandler(e) - case 1: - if i.activeHandler == nil { - return - } - var e ZdwlIpcOutputV2ActiveEvent - l := 0 - e.Active = client.Uint32(data[l : l+4]) - l += 4 - - i.activeHandler(e) - case 2: - if i.tagHandler == nil { - return - } - var e ZdwlIpcOutputV2TagEvent - l := 0 - e.Tag = client.Uint32(data[l : l+4]) - l += 4 - e.State = client.Uint32(data[l : l+4]) - l += 4 - e.Clients = client.Uint32(data[l : l+4]) - l += 4 - e.Focused = client.Uint32(data[l : l+4]) - l += 4 - - i.tagHandler(e) - case 3: - if i.layoutHandler == nil { - return - } - var e ZdwlIpcOutputV2LayoutEvent - l := 0 - e.Layout = client.Uint32(data[l : l+4]) - l += 4 - - i.layoutHandler(e) - case 4: - if i.titleHandler == nil { - return - } - var e ZdwlIpcOutputV2TitleEvent - l := 0 - titleLen := client.PaddedLen(int(client.Uint32(data[l : l+4]))) - l += 4 - e.Title = client.String(data[l : l+titleLen]) - l += titleLen - - i.titleHandler(e) - case 5: - if i.appidHandler == nil { - return - } - var e ZdwlIpcOutputV2AppidEvent - l := 0 - appidLen := client.PaddedLen(int(client.Uint32(data[l : l+4]))) - l += 4 - e.Appid = client.String(data[l : l+appidLen]) - l += appidLen - - i.appidHandler(e) - case 6: - if i.layoutSymbolHandler == nil { - return - } - var e ZdwlIpcOutputV2LayoutSymbolEvent - l := 0 - layoutLen := client.PaddedLen(int(client.Uint32(data[l : l+4]))) - l += 4 - e.Layout = client.String(data[l : l+layoutLen]) - l += layoutLen - - i.layoutSymbolHandler(e) - case 7: - if i.frameHandler == nil { - return - } - var e ZdwlIpcOutputV2FrameEvent - - i.frameHandler(e) - case 8: - if i.fullscreenHandler == nil { - return - } - var e ZdwlIpcOutputV2FullscreenEvent - l := 0 - e.IsFullscreen = client.Uint32(data[l : l+4]) - l += 4 - - i.fullscreenHandler(e) - case 9: - if i.floatingHandler == nil { - return - } - var e ZdwlIpcOutputV2FloatingEvent - l := 0 - e.IsFloating = client.Uint32(data[l : l+4]) - l += 4 - - i.floatingHandler(e) - case 10: - if i.xHandler == nil { - return - } - var e ZdwlIpcOutputV2XEvent - l := 0 - e.X = int32(client.Uint32(data[l : l+4])) - l += 4 - - i.xHandler(e) - case 11: - if i.yHandler == nil { - return - } - var e ZdwlIpcOutputV2YEvent - l := 0 - e.Y = int32(client.Uint32(data[l : l+4])) - l += 4 - - i.yHandler(e) - case 12: - if i.widthHandler == nil { - return - } - var e ZdwlIpcOutputV2WidthEvent - l := 0 - e.Width = int32(client.Uint32(data[l : l+4])) - l += 4 - - i.widthHandler(e) - case 13: - if i.heightHandler == nil { - return - } - var e ZdwlIpcOutputV2HeightEvent - l := 0 - e.Height = int32(client.Uint32(data[l : l+4])) - l += 4 - - i.heightHandler(e) - case 14: - if i.lastLayerHandler == nil { - return - } - var e ZdwlIpcOutputV2LastLayerEvent - l := 0 - lastLayerLen := client.PaddedLen(int(client.Uint32(data[l : l+4]))) - l += 4 - e.LastLayer = client.String(data[l : l+lastLayerLen]) - l += lastLayerLen - - i.lastLayerHandler(e) - case 15: - if i.kbLayoutHandler == nil { - return - } - var e ZdwlIpcOutputV2KbLayoutEvent - l := 0 - kbLayoutLen := client.PaddedLen(int(client.Uint32(data[l : l+4]))) - l += 4 - e.KbLayout = client.String(data[l : l+kbLayoutLen]) - l += kbLayoutLen - - i.kbLayoutHandler(e) - case 16: - if i.keymodeHandler == nil { - return - } - var e ZdwlIpcOutputV2KeymodeEvent - l := 0 - keymodeLen := client.PaddedLen(int(client.Uint32(data[l : l+4]))) - l += 4 - e.Keymode = client.String(data[l : l+keymodeLen]) - l += keymodeLen - - i.keymodeHandler(e) - case 17: - if i.scalefactorHandler == nil { - return - } - var e ZdwlIpcOutputV2ScalefactorEvent - l := 0 - e.Scalefactor = client.Uint32(data[l : l+4]) - l += 4 - - i.scalefactorHandler(e) - } -} diff --git a/core/internal/screenshot/compositor.go b/core/internal/screenshot/compositor.go index 9bd5f0ec..1f107d23 100644 --- a/core/internal/screenshot/compositor.go +++ b/core/internal/screenshot/compositor.go @@ -6,7 +6,6 @@ import ( "os" "os/exec" - "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/dwl_ipc" "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_output_management" wlhelpers "github.com/AvengeMedia/DankMaterialShell/core/internal/wayland/client" "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client" @@ -19,9 +18,9 @@ const ( CompositorHyprland CompositorSway CompositorNiri - CompositorDWL CompositorScroll CompositorMiracle + CompositorMango ) var detectedCompositor Compositor = -1 @@ -36,8 +35,14 @@ func DetectCompositor() Compositor { swaySocket := os.Getenv("SWAYSOCK") scrollSocket := os.Getenv("SCROLLSOCK") miracleSocket := os.Getenv("MIRACLESOCK") + mangoSocket := os.Getenv("MANGO_INSTANCE_SIGNATURE") switch { + case mangoSocket != "": + if _, err := os.Stat(mangoSocket); err == nil { + detectedCompositor = CompositorMango + return detectedCompositor + } case niriSocket != "": if _, err := os.Stat(niriSocket); err == nil { detectedCompositor = CompositorNiri @@ -63,66 +68,29 @@ func DetectCompositor() Compositor { return detectedCompositor } - if detectDWLProtocol() { - detectedCompositor = CompositorDWL - return detectedCompositor - } - detectedCompositor = CompositorUnknown return detectedCompositor } -func detectDWLProtocol() bool { - display, err := client.Connect("") - if err != nil { - return false - } - ctx := display.Context() - defer ctx.Close() - - registry, err := display.GetRegistry() - if err != nil { - return false - } - - found := false - registry.SetGlobalHandler(func(e client.RegistryGlobalEvent) { - if e.Interface == dwl_ipc.ZdwlIpcManagerV2InterfaceName { - found = true - } - }) - - if err := wlhelpers.Roundtrip(display, ctx); err != nil { - return false - } - - return found -} - -func SetCompositorDWL() { - detectedCompositor = CompositorDWL -} - type WindowGeometry struct { - X int32 - Y int32 - Width int32 - Height int32 - Output string - Scale float64 - OutputX int32 - OutputY int32 - OutputTransform int32 + X int32 + Y int32 + Width int32 + Height int32 + Output string + Scale float64 + OutputX int32 + OutputY int32 } func GetActiveWindow() (*WindowGeometry, error) { switch DetectCompositor() { case CompositorHyprland: return getHyprlandActiveWindow() - case CompositorDWL: - return getDWLActiveWindow() + case CompositorMango: + return getMangoActiveWindow() default: - return nil, fmt.Errorf("window capture requires Hyprland or DWL") + return nil, fmt.Errorf("window capture requires Hyprland or Mango") } } @@ -285,6 +253,93 @@ func getMiracleFocusedMonitor() string { return "" } +type mangoMonitor struct { + Name string `json:"name"` + Active bool `json:"active"` + X int32 `json:"x"` + Y int32 `json:"y"` + Scale float64 `json:"scale"` +} + +func getMangoMonitors() []mangoMonitor { + output, err := exec.Command("mmsg", "get", "all-monitors").Output() + if err != nil { + return nil + } + + var data struct { + Monitors []mangoMonitor `json:"monitors"` + } + if err := json.Unmarshal(output, &data); err != nil { + return nil + } + return data.Monitors +} + +func getMangoFocusedMonitor() string { + for _, m := range getMangoMonitors() { + if m.Active { + return m.Name + } + } + return "" +} + +type mangoClient struct { + Monitor string `json:"monitor"` + IsFocused bool `json:"is_focused"` + X int32 `json:"x"` + Y int32 `json:"y"` + Width int32 `json:"width"` + Height int32 `json:"height"` +} + +func getMangoActiveWindow() (*WindowGeometry, error) { + output, err := exec.Command("mmsg", "get", "all-clients").Output() + if err != nil { + return nil, fmt.Errorf("mmsg get all-clients: %w", err) + } + + var data struct { + Clients []mangoClient `json:"clients"` + } + if err := json.Unmarshal(output, &data); err != nil { + return nil, fmt.Errorf("parse all-clients: %w", err) + } + + for _, c := range data.Clients { + if !c.IsFocused { + continue + } + if c.Width <= 0 || c.Height <= 0 { + return nil, fmt.Errorf("no active window") + } + + geom := &WindowGeometry{ + X: c.X, + Y: c.Y, + Width: c.Width, + Height: c.Height, + Output: c.Monitor, + Scale: 1.0, + } + for _, m := range getMangoMonitors() { + if m.Name != c.Monitor { + continue + } + geom.OutputX = m.X + geom.OutputY = m.Y + if m.Scale > 0 { + geom.Scale = m.Scale + } + break + } + return geom, nil + } + + return nil, fmt.Errorf("no focused window") +} + type niriWorkspace struct { Output string `json:"output"` IsFocused bool `json:"is_focused"` @@ -309,121 +364,6 @@ func getNiriFocusedMonitor() string { return "" } -var dwlActiveOutput string - -func SetDWLActiveOutput(name string) { - dwlActiveOutput = name -} - -func getDWLFocusedMonitor() string { - if dwlActiveOutput != "" { - return dwlActiveOutput - } - return queryDWLActiveOutput() -} - -func queryDWLActiveOutput() string { - display, err := client.Connect("") - if err != nil { - return "" - } - ctx := display.Context() - defer ctx.Close() - - registry, err := display.GetRegistry() - if err != nil { - return "" - } - - var dwlManager *dwl_ipc.ZdwlIpcManagerV2 - outputs := make(map[uint32]*client.Output) - - registry.SetGlobalHandler(func(e client.RegistryGlobalEvent) { - switch e.Interface { - case dwl_ipc.ZdwlIpcManagerV2InterfaceName: - mgr := dwl_ipc.NewZdwlIpcManagerV2(ctx) - if err := registry.Bind(e.Name, e.Interface, e.Version, mgr); err == nil { - dwlManager = mgr - } - case client.OutputInterfaceName: - out := client.NewOutput(ctx) - version := e.Version - if version > 4 { - version = 4 - } - if err := registry.Bind(e.Name, e.Interface, version, out); err == nil { - outputs[e.Name] = out - } - } - }) - - if err := wlhelpers.Roundtrip(display, ctx); err != nil { - return "" - } - - if dwlManager == nil || len(outputs) == 0 { - return "" - } - - outputNames := make(map[uint32]string) - for name, out := range outputs { - n := name - out.SetNameHandler(func(e client.OutputNameEvent) { - outputNames[n] = e.Name - }) - } - - if err := wlhelpers.Roundtrip(display, ctx); err != nil { - return "" - } - - type outputState struct { - name string - active bool - gotFrame bool - } - states := make(map[uint32]*outputState) - - for name, out := range outputs { - dwlOut, err := dwlManager.GetOutput(out) - if err != nil { - continue - } - state := &outputState{name: outputNames[name]} - states[name] = state - - dwlOut.SetActiveHandler(func(e dwl_ipc.ZdwlIpcOutputV2ActiveEvent) { - state.active = e.Active != 0 - }) - dwlOut.SetFrameHandler(func(e dwl_ipc.ZdwlIpcOutputV2FrameEvent) { - state.gotFrame = true - }) - } - - allFramesReceived := func() bool { - for _, s := range states { - if !s.gotFrame { - return false - } - } - return true - } - - for !allFramesReceived() { - if err := ctx.Dispatch(); err != nil { - return "" - } - } - - for _, state := range states { - if state.active { - return state.name - } - } - - return "" -} - func GetFocusedMonitor() string { switch DetectCompositor() { case CompositorHyprland: @@ -436,8 +376,8 @@ func GetFocusedMonitor() string { return getMiracleFocusedMonitor() case CompositorNiri: return getNiriFocusedMonitor() - case CompositorDWL: - return getDWLFocusedMonitor() + case CompositorMango: + return getMangoFocusedMonitor() } return "" } @@ -534,161 +474,3 @@ func getAllOutputInfos() map[string]*outputInfo { } return result } - -func getOutputInfo(outputName string) (*outputInfo, bool) { - infos := getAllOutputInfos() - if infos == nil { - return nil, false - } - info, ok := infos[outputName] - return info, ok -} - -func getDWLActiveWindow() (*WindowGeometry, error) { - display, err := client.Connect("") - if err != nil { - return nil, fmt.Errorf("connect: %w", err) - } - ctx := display.Context() - defer ctx.Close() - - registry, err := display.GetRegistry() - if err != nil { - return nil, fmt.Errorf("get registry: %w", err) - } - - var dwlManager *dwl_ipc.ZdwlIpcManagerV2 - outputs := make(map[uint32]*client.Output) - - registry.SetGlobalHandler(func(e client.RegistryGlobalEvent) { - switch e.Interface { - case dwl_ipc.ZdwlIpcManagerV2InterfaceName: - mgr := dwl_ipc.NewZdwlIpcManagerV2(ctx) - if err := registry.Bind(e.Name, e.Interface, e.Version, mgr); err == nil { - dwlManager = mgr - } - case client.OutputInterfaceName: - out := client.NewOutput(ctx) - version := e.Version - if version > 4 { - version = 4 - } - if err := registry.Bind(e.Name, e.Interface, version, out); err == nil { - outputs[e.Name] = out - } - } - }) - - if err := wlhelpers.Roundtrip(display, ctx); err != nil { - return nil, fmt.Errorf("roundtrip: %w", err) - } - - if dwlManager == nil { - return nil, fmt.Errorf("dwl_ipc_manager not available") - } - - if len(outputs) == 0 { - return nil, fmt.Errorf("no outputs found") - } - - outputNames := make(map[uint32]string) - for name, out := range outputs { - n := name - out.SetNameHandler(func(e client.OutputNameEvent) { - outputNames[n] = e.Name - }) - } - - if err := wlhelpers.Roundtrip(display, ctx); err != nil { - return nil, fmt.Errorf("roundtrip: %w", err) - } - - type dwlOutputState struct { - output *dwl_ipc.ZdwlIpcOutputV2 - name string - active bool - x, y int32 - w, h int32 - scalefactor uint32 - gotFrame bool - } - - dwlOutputs := make(map[uint32]*dwlOutputState) - for name, out := range outputs { - dwlOut, err := dwlManager.GetOutput(out) - if err != nil { - continue - } - state := &dwlOutputState{output: dwlOut, name: outputNames[name]} - dwlOutputs[name] = state - - dwlOut.SetActiveHandler(func(e dwl_ipc.ZdwlIpcOutputV2ActiveEvent) { - state.active = e.Active != 0 - }) - dwlOut.SetXHandler(func(e dwl_ipc.ZdwlIpcOutputV2XEvent) { - state.x = e.X - }) - dwlOut.SetYHandler(func(e dwl_ipc.ZdwlIpcOutputV2YEvent) { - state.y = e.Y - }) - dwlOut.SetWidthHandler(func(e dwl_ipc.ZdwlIpcOutputV2WidthEvent) { - state.w = e.Width - }) - dwlOut.SetHeightHandler(func(e dwl_ipc.ZdwlIpcOutputV2HeightEvent) { - state.h = e.Height - }) - dwlOut.SetScalefactorHandler(func(e dwl_ipc.ZdwlIpcOutputV2ScalefactorEvent) { - state.scalefactor = e.Scalefactor - }) - dwlOut.SetFrameHandler(func(e dwl_ipc.ZdwlIpcOutputV2FrameEvent) { - state.gotFrame = true - }) - } - - allFramesReceived := func() bool { - for _, s := range dwlOutputs { - if !s.gotFrame { - return false - } - } - return true - } - - for !allFramesReceived() { - if err := ctx.Dispatch(); err != nil { - return nil, fmt.Errorf("dispatch: %w", err) - } - } - - for _, state := range dwlOutputs { - if !state.active { - continue - } - if state.w <= 0 || state.h <= 0 { - return nil, fmt.Errorf("no active window") - } - scale := float64(state.scalefactor) / 100.0 - if scale <= 0 { - scale = 1.0 - } - - geom := &WindowGeometry{ - X: state.x, - Y: state.y, - Width: state.w, - Height: state.h, - Output: state.name, - Scale: scale, - } - - if info, ok := getOutputInfo(state.name); ok { - geom.OutputX = info.x - geom.OutputY = info.y - geom.OutputTransform = info.transform - } - - return geom, nil - } - - return nil, fmt.Errorf("no active output found") -} diff --git a/core/internal/screenshot/screenshot.go b/core/internal/screenshot/screenshot.go index 42d3404b..71b7fe59 100644 --- a/core/internal/screenshot/screenshot.go +++ b/core/internal/screenshot/screenshot.go @@ -156,14 +156,14 @@ func (s *Screenshoter) captureWindow() (*CaptureResult, error) { switch DetectCompositor() { case CompositorHyprland: return s.captureAndCrop(output, region) - case CompositorDWL: - return s.captureDWLWindow(output, region, geom) + case CompositorMango: + return s.captureMangoWindow(output, region, geom) default: return s.captureRegionOnOutput(output, region) } } -func (s *Screenshoter) captureDWLWindow(output *WaylandOutput, region Region, geom *WindowGeometry) (*CaptureResult, error) { +func (s *Screenshoter) captureMangoWindow(output *WaylandOutput, region Region, geom *WindowGeometry) (*CaptureResult, error) { result, err := s.captureWholeOutput(output) if err != nil { return nil, err @@ -628,7 +628,7 @@ func (s *Screenshoter) captureRegionOnOutput(output *WaylandOutput, region Regio w := int32(float64(region.Width) * scale) h := int32(float64(region.Height) * scale) - if DetectCompositor() == CompositorDWL { + if DetectCompositor() == CompositorMango { scaledOutW := int32(float64(output.width) * scale) scaledOutH := int32(float64(output.height) * scale) if localX >= scaledOutW { diff --git a/core/internal/server/dwl/handlers.go b/core/internal/server/dwl/handlers.go deleted file mode 100644 index e83cc848..00000000 --- a/core/internal/server/dwl/handlers.go +++ /dev/null @@ -1,138 +0,0 @@ -package dwl - -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, "dwl manager not initialized") - return - } - - switch req.Method { - case "dwl.getState": - handleGetState(conn, req, manager) - case "dwl.setTags": - handleSetTags(conn, req, manager) - case "dwl.setClientTags": - handleSetClientTags(conn, req, manager) - case "dwl.setLayout": - handleSetLayout(conn, req, manager) - case "dwl.subscribe": - handleSubscribe(conn, req, manager) - default: - models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method)) - } -} - -func handleGetState(conn net.Conn, req models.Request, manager *Manager) { - state := manager.GetState() - models.Respond(conn, req.ID, state) -} - -func handleSetTags(conn net.Conn, req models.Request, manager *Manager) { - output, ok := models.Get[string](req, "output") - if !ok { - models.RespondError(conn, req.ID, "missing or invalid 'output' parameter") - return - } - - tagmask, ok := models.Get[float64](req, "tagmask") - if !ok { - models.RespondError(conn, req.ID, "missing or invalid 'tagmask' parameter") - return - } - - toggleTagset, ok := models.Get[float64](req, "toggleTagset") - if !ok { - models.RespondError(conn, req.ID, "missing or invalid 'toggleTagset' parameter") - return - } - - if err := manager.SetTags(output, uint32(tagmask), uint32(toggleTagset)); err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - - models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "tags set"}) -} - -func handleSetClientTags(conn net.Conn, req models.Request, manager *Manager) { - output, ok := models.Get[string](req, "output") - if !ok { - models.RespondError(conn, req.ID, "missing or invalid 'output' parameter") - return - } - - andTags, ok := models.Get[float64](req, "andTags") - if !ok { - models.RespondError(conn, req.ID, "missing or invalid 'andTags' parameter") - return - } - - xorTags, ok := models.Get[float64](req, "xorTags") - if !ok { - models.RespondError(conn, req.ID, "missing or invalid 'xorTags' parameter") - return - } - - if err := manager.SetClientTags(output, uint32(andTags), uint32(xorTags)); err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - - models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "client tags set"}) -} - -func handleSetLayout(conn net.Conn, req models.Request, manager *Manager) { - output, ok := models.Get[string](req, "output") - if !ok { - models.RespondError(conn, req.ID, "missing or invalid 'output' parameter") - return - } - - index, ok := models.Get[float64](req, "index") - if !ok { - models.RespondError(conn, req.ID, "missing or invalid 'index' parameter") - return - } - - if err := manager.SetLayout(output, uint32(index)); err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - - models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "layout set"}) -} - -func handleSubscribe(conn net.Conn, req 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/dwl/manager.go b/core/internal/server/dwl/manager.go deleted file mode 100644 index 0d21afa0..00000000 --- a/core/internal/server/dwl/manager.go +++ /dev/null @@ -1,522 +0,0 @@ -package dwl - -import ( - "fmt" - "time" - - wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client" - - "github.com/AvengeMedia/DankMaterialShell/core/internal/log" - "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/dwl_ipc" -) - -func NewManager(display wlclient.WaylandDisplay) (*Manager, error) { - m := &Manager{ - display: display, - ctx: display.Context(), - cmdq: make(chan cmd, 128), - outputSetupReq: make(chan uint32, 16), - stopChan: make(chan struct{}), - - dirty: make(chan struct{}, 1), - layouts: make([]string, 0), - } - - if err := m.setupRegistry(); err != nil { - return nil, err - } - - m.updateState() - - m.notifierWg.Add(1) - go m.notifier() - - m.wg.Add(1) - go m.waylandActor() - - return m, nil -} - -func (m *Manager) post(fn func()) { - select { - case m.cmdq <- cmd{fn: fn}: - default: - log.Warn("DWL actor command queue full, dropping command") - } -} - -func (m *Manager) waylandActor() { - defer m.wg.Done() - - for { - select { - case <-m.stopChan: - return - case c := <-m.cmdq: - c.fn() - case outputID := <-m.outputSetupReq: - out, exists := m.outputs.Load(outputID) - if !exists { - log.Warnf("DWL: Output %d no longer exists, skipping setup", outputID) - continue - } - - if out.ipcOutput != nil { - continue - } - - mgr, ok := m.manager.(*dwl_ipc.ZdwlIpcManagerV2) - if !ok || mgr == nil { - log.Errorf("DWL: Manager not available for output %d setup", outputID) - continue - } - - log.Infof("DWL: Setting up ipcOutput for dynamically added output %d", outputID) - if err := m.setupOutput(mgr, out.output); err != nil { - log.Errorf("DWL: Failed to setup output %d: %v", outputID, err) - } else { - m.updateState() - } - } - } -} - -func (m *Manager) setupRegistry() error { - log.Info("DWL: starting registry setup") - - registry, err := m.display.GetRegistry() - if err != nil { - return fmt.Errorf("failed to get registry: %w", err) - } - m.registry = registry - - outputs := make([]*wlclient.Output, 0) - outputRegNames := make(map[uint32]uint32) - var dwlMgr *dwl_ipc.ZdwlIpcManagerV2 - - registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) { - switch e.Interface { - case dwl_ipc.ZdwlIpcManagerV2InterfaceName: - log.Infof("DWL: found %s", dwl_ipc.ZdwlIpcManagerV2InterfaceName) - manager := dwl_ipc.NewZdwlIpcManagerV2(m.ctx) - version := e.Version - if version > 2 { - version = 2 - } - if err := registry.Bind(e.Name, e.Interface, version, manager); err == nil { - dwlMgr = manager - log.Info("DWL: manager bound successfully") - - // Set handlers immediately after binding, before roundtrips - manager.SetTagsHandler(func(e dwl_ipc.ZdwlIpcManagerV2TagsEvent) { - log.Infof("DWL: Tags count: %d", e.Amount) - m.tagCount = e.Amount - m.updateState() - }) - - manager.SetLayoutHandler(func(e dwl_ipc.ZdwlIpcManagerV2LayoutEvent) { - log.Infof("DWL: Layout: %s", e.Name) - m.layouts = append(m.layouts, e.Name) - m.updateState() - }) - } else { - log.Errorf("DWL: failed to bind manager: %v", err) - } - case "wl_output": - log.Debugf("DWL: found wl_output (name=%d)", e.Name) - output := wlclient.NewOutput(m.ctx) - - outState := &outputState{ - registryName: e.Name, - output: output, - tags: make([]TagState, 0), - } - - output.SetNameHandler(func(ev wlclient.OutputNameEvent) { - log.Debugf("DWL: Output name: %s (registry=%d)", ev.Name, e.Name) - outState.name = ev.Name - }) - - output.SetDescriptionHandler(func(ev wlclient.OutputDescriptionEvent) { - log.Debugf("DWL: Output description: %s", ev.Description) - }) - - version := e.Version - if version > 4 { - version = 4 - } - if err := registry.Bind(e.Name, e.Interface, version, output); err == nil { - outputID := output.ID() - outState.id = outputID - log.Infof("DWL: Bound wl_output id=%d registry_name=%d", outputID, e.Name) - outputs = append(outputs, output) - outputRegNames[outputID] = e.Name - - m.outputs.Store(outputID, outState) - - if m.manager != nil { - select { - case m.outputSetupReq <- outputID: - log.Debugf("DWL: Queued setup for output %d", outputID) - default: - log.Warnf("DWL: Setup queue full, output %d will not be initialized", outputID) - } - } - } else { - log.Errorf("DWL: Failed to bind wl_output: %v", err) - } - } - }) - - registry.SetGlobalRemoveHandler(func(e wlclient.RegistryGlobalRemoveEvent) { - m.post(func() { - var outToRelease *outputState - m.outputs.Range(func(id uint32, out *outputState) bool { - if out.registryName == e.Name { - log.Infof("DWL: Output %d removed", id) - outToRelease = out - m.outputs.Delete(id) - return false - } - return true - }) - - if outToRelease != nil { - if ipcOut, ok := outToRelease.ipcOutput.(*dwl_ipc.ZdwlIpcOutputV2); ok && ipcOut != nil { - m.wlMutex.Lock() - ipcOut.Release() - m.wlMutex.Unlock() - log.Debugf("DWL: Released ipcOutput for removed output %d", outToRelease.id) - } - m.updateState() - } - }) - }) - - if err := m.display.Roundtrip(); err != nil { - return fmt.Errorf("first roundtrip failed: %w", err) - } - if err := m.display.Roundtrip(); err != nil { - return fmt.Errorf("second roundtrip failed: %w", err) - } - - if dwlMgr == nil { - log.Info("DWL: manager not found in registry") - return fmt.Errorf("dwl_ipc_manager_v2 not available") - } - - m.manager = dwlMgr - - for _, output := range outputs { - if err := m.setupOutput(dwlMgr, output); err != nil { - log.Warnf("DWL: Failed to setup output %d: %v", output.ID(), err) - } - } - - if err := m.display.Roundtrip(); err != nil { - return fmt.Errorf("final roundtrip failed: %w", err) - } - - log.Info("DWL: registry setup complete") - return nil -} - -func (m *Manager) setupOutput(manager *dwl_ipc.ZdwlIpcManagerV2, output *wlclient.Output) error { - m.wlMutex.Lock() - ipcOutput, err := manager.GetOutput(output) - m.wlMutex.Unlock() - if err != nil { - return fmt.Errorf("failed to get dwl output: %w", err) - } - - outState, exists := m.outputs.Load(output.ID()) - if !exists { - return fmt.Errorf("output state not found for id %d", output.ID()) - } - outState.ipcOutput = ipcOutput - - ipcOutput.SetActiveHandler(func(e dwl_ipc.ZdwlIpcOutputV2ActiveEvent) { - outState.active = e.Active - }) - - ipcOutput.SetTagHandler(func(e dwl_ipc.ZdwlIpcOutputV2TagEvent) { - updated := false - for i, tag := range outState.tags { - if tag.Tag == e.Tag { - outState.tags[i] = TagState{ - Tag: e.Tag, - State: e.State, - Clients: e.Clients, - Focused: e.Focused, - } - updated = true - break - } - } - - if !updated { - outState.tags = append(outState.tags, TagState{ - Tag: e.Tag, - State: e.State, - Clients: e.Clients, - Focused: e.Focused, - }) - } - - m.updateState() - }) - - ipcOutput.SetLayoutHandler(func(e dwl_ipc.ZdwlIpcOutputV2LayoutEvent) { - outState.layout = e.Layout - }) - - ipcOutput.SetTitleHandler(func(e dwl_ipc.ZdwlIpcOutputV2TitleEvent) { - outState.title = e.Title - }) - - ipcOutput.SetAppidHandler(func(e dwl_ipc.ZdwlIpcOutputV2AppidEvent) { - outState.appID = e.Appid - }) - - ipcOutput.SetLayoutSymbolHandler(func(e dwl_ipc.ZdwlIpcOutputV2LayoutSymbolEvent) { - outState.layoutSymbol = e.Layout - }) - - ipcOutput.SetKbLayoutHandler(func(e dwl_ipc.ZdwlIpcOutputV2KbLayoutEvent) { - outState.kbLayout = e.KbLayout - }) - - ipcOutput.SetKeymodeHandler(func(e dwl_ipc.ZdwlIpcOutputV2KeymodeEvent) { - outState.keymode = e.Keymode - }) - - ipcOutput.SetFrameHandler(func(e dwl_ipc.ZdwlIpcOutputV2FrameEvent) { - m.updateState() - }) - - return nil -} - -func (m *Manager) updateState() { - outputs := make(map[string]*OutputState) - activeOutput := "" - - m.outputs.Range(func(key uint32, out *outputState) bool { - name := out.name - if name == "" { - name = fmt.Sprintf("output-%d", out.id) - } - - tagsCopy := make([]TagState, len(out.tags)) - copy(tagsCopy, out.tags) - - outputs[name] = &OutputState{ - Name: name, - Active: out.active, - Tags: tagsCopy, - Layout: out.layout, - LayoutSymbol: out.layoutSymbol, - Title: out.title, - AppID: out.appID, - KbLayout: out.kbLayout, - Keymode: out.keymode, - } - - if out.active != 0 { - activeOutput = name - } - return true - }) - - newState := State{ - Outputs: outputs, - TagCount: m.tagCount, - Layouts: m.layouts, - ActiveOutput: activeOutput, - } - - m.stateMutex.Lock() - m.state = &newState - m.stateMutex.Unlock() - - m.notifySubscribers() -} - -func (m *Manager) notifier() { - defer m.notifierWg.Done() - const minGap = 100 * time.Millisecond - timer := time.NewTimer(minGap) - timer.Stop() - var pending bool - - for { - select { - case <-m.stopChan: - timer.Stop() - return - case <-m.dirty: - if pending { - continue - } - pending = true - timer.Reset(minGap) - case <-timer.C: - if !pending { - continue - } - - 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("DWL: subscriber channel full, dropping update") - } - return true - }) - - stateCopy := currentState - m.lastNotified = &stateCopy - pending = false - } - } -} - -func (m *Manager) ensureOutputSetup(out *outputState) error { - if out.ipcOutput != nil { - return nil - } - - return fmt.Errorf("output not yet initialized - setup in progress, retry in a moment") -} - -func (m *Manager) SetTags(outputName string, tagmask uint32, toggleTagset uint32) error { - availableOutputs := make([]string, 0) - var targetOut *outputState - m.outputs.Range(func(key uint32, out *outputState) bool { - name := out.name - if name == "" { - name = fmt.Sprintf("output-%d", out.id) - } - availableOutputs = append(availableOutputs, name) - if name == outputName { - targetOut = out - return false - } - return true - }) - - if targetOut == nil { - return fmt.Errorf("output not found: %s (available: %v)", outputName, availableOutputs) - } - - if err := m.ensureOutputSetup(targetOut); err != nil { - return fmt.Errorf("failed to setup output %s: %w", outputName, err) - } - - ipcOut, ok := targetOut.ipcOutput.(*dwl_ipc.ZdwlIpcOutputV2) - if !ok { - return fmt.Errorf("output %s has invalid ipcOutput type", outputName) - } - - m.wlMutex.Lock() - err := ipcOut.SetTags(tagmask, toggleTagset) - m.wlMutex.Unlock() - return err -} - -func (m *Manager) SetClientTags(outputName string, andTags uint32, xorTags uint32) error { - var targetOut *outputState - m.outputs.Range(func(key uint32, out *outputState) bool { - name := out.name - if name == "" { - name = fmt.Sprintf("output-%d", out.id) - } - if name == outputName { - targetOut = out - return false - } - return true - }) - - if targetOut == nil { - return fmt.Errorf("output not found: %s", outputName) - } - - if err := m.ensureOutputSetup(targetOut); err != nil { - return fmt.Errorf("failed to setup output %s: %w", outputName, err) - } - - ipcOut, ok := targetOut.ipcOutput.(*dwl_ipc.ZdwlIpcOutputV2) - if !ok { - return fmt.Errorf("output %s has invalid ipcOutput type", outputName) - } - - m.wlMutex.Lock() - err := ipcOut.SetClientTags(andTags, xorTags) - m.wlMutex.Unlock() - return err -} - -func (m *Manager) SetLayout(outputName string, index uint32) error { - var targetOut *outputState - m.outputs.Range(func(key uint32, out *outputState) bool { - name := out.name - if name == "" { - name = fmt.Sprintf("output-%d", out.id) - } - if name == outputName { - targetOut = out - return false - } - return true - }) - - if targetOut == nil { - return fmt.Errorf("output not found: %s", outputName) - } - - if err := m.ensureOutputSetup(targetOut); err != nil { - return fmt.Errorf("failed to setup output %s: %w", outputName, err) - } - - ipcOut, ok := targetOut.ipcOutput.(*dwl_ipc.ZdwlIpcOutputV2) - if !ok { - return fmt.Errorf("output %s has invalid ipcOutput type", outputName) - } - - m.wlMutex.Lock() - err := ipcOut.SetLayout(index) - m.wlMutex.Unlock() - return err -} - -func (m *Manager) Close() { - close(m.stopChan) - m.wg.Wait() - m.notifierWg.Wait() - - m.subscribers.Range(func(key string, ch chan State) bool { - close(ch) - m.subscribers.Delete(key) - return true - }) - - m.outputs.Range(func(key uint32, out *outputState) bool { - if ipcOut, ok := out.ipcOutput.(*dwl_ipc.ZdwlIpcOutputV2); ok { - ipcOut.Release() - } - m.outputs.Delete(key) - return true - }) - - if mgr, ok := m.manager.(*dwl_ipc.ZdwlIpcManagerV2); ok { - mgr.Release() - } -} diff --git a/core/internal/server/dwl/manager_test.go b/core/internal/server/dwl/manager_test.go deleted file mode 100644 index 13af1379..00000000 --- a/core/internal/server/dwl/manager_test.go +++ /dev/null @@ -1,366 +0,0 @@ -package dwl - -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{TagCount: 9} - assert.True(t, stateChanged(s, nil)) - assert.True(t, stateChanged(nil, s)) -} - -func TestStateChanged_TagCountDiffers(t *testing.T) { - a := &State{TagCount: 9, Outputs: make(map[string]*OutputState), Layouts: []string{}} - b := &State{TagCount: 10, Outputs: make(map[string]*OutputState), Layouts: []string{}} - assert.True(t, stateChanged(a, b)) -} - -func TestStateChanged_LayoutLengthDiffers(t *testing.T) { - a := &State{TagCount: 9, Layouts: []string{"tile"}, Outputs: make(map[string]*OutputState)} - b := &State{TagCount: 9, Layouts: []string{"tile", "monocle"}, Outputs: make(map[string]*OutputState)} - assert.True(t, stateChanged(a, b)) -} - -func TestStateChanged_ActiveOutputDiffers(t *testing.T) { - a := &State{TagCount: 9, ActiveOutput: "eDP-1", Outputs: make(map[string]*OutputState), Layouts: []string{}} - b := &State{TagCount: 9, ActiveOutput: "HDMI-A-1", Outputs: make(map[string]*OutputState), Layouts: []string{}} - assert.True(t, stateChanged(a, b)) -} - -func TestStateChanged_OutputCountDiffers(t *testing.T) { - a := &State{ - TagCount: 9, - Outputs: map[string]*OutputState{"eDP-1": {}}, - Layouts: []string{}, - } - b := &State{ - TagCount: 9, - Outputs: map[string]*OutputState{}, - Layouts: []string{}, - } - assert.True(t, stateChanged(a, b)) -} - -func TestStateChanged_OutputFieldsDiffer(t *testing.T) { - a := &State{ - TagCount: 9, - Layouts: []string{}, - Outputs: map[string]*OutputState{ - "eDP-1": {Active: 1, Layout: 0, Title: "Firefox"}, - }, - } - b := &State{ - TagCount: 9, - Layouts: []string{}, - Outputs: map[string]*OutputState{ - "eDP-1": {Active: 0, Layout: 0, Title: "Firefox"}, - }, - } - assert.True(t, stateChanged(a, b)) - - b.Outputs["eDP-1"].Active = 1 - b.Outputs["eDP-1"].Layout = 1 - assert.True(t, stateChanged(a, b)) - - b.Outputs["eDP-1"].Layout = 0 - b.Outputs["eDP-1"].Title = "Code" - assert.True(t, stateChanged(a, b)) -} - -func TestStateChanged_TagsDiffer(t *testing.T) { - a := &State{ - TagCount: 9, - Layouts: []string{}, - Outputs: map[string]*OutputState{ - "eDP-1": {Tags: []TagState{{Tag: 1, State: 1, Clients: 2, Focused: 1}}}, - }, - } - b := &State{ - TagCount: 9, - Layouts: []string{}, - Outputs: map[string]*OutputState{ - "eDP-1": {Tags: []TagState{{Tag: 1, State: 2, Clients: 2, Focused: 1}}}, - }, - } - assert.True(t, stateChanged(a, b)) - - b.Outputs["eDP-1"].Tags[0].State = 1 - b.Outputs["eDP-1"].Tags[0].Clients = 3 - assert.True(t, stateChanged(a, b)) -} - -func TestStateChanged_Equal(t *testing.T) { - a := &State{ - TagCount: 9, - ActiveOutput: "eDP-1", - Layouts: []string{"tile", "monocle"}, - Outputs: map[string]*OutputState{ - "eDP-1": { - Name: "eDP-1", - Active: 1, - Layout: 0, - LayoutSymbol: "[]=", - Title: "Firefox", - AppID: "firefox", - KbLayout: "us", - Keymode: "", - Tags: []TagState{{Tag: 1, State: 1, Clients: 2, Focused: 1}}, - }, - }, - } - b := &State{ - TagCount: 9, - ActiveOutput: "eDP-1", - Layouts: []string{"tile", "monocle"}, - Outputs: map[string]*OutputState{ - "eDP-1": { - Name: "eDP-1", - Active: 1, - Layout: 0, - LayoutSymbol: "[]=", - Title: "Firefox", - AppID: "firefox", - KbLayout: "us", - Keymode: "", - Tags: []TagState{{Tag: 1, State: 1, Clients: 2, Focused: 1}}, - }, - }, - } - assert.False(t, stateChanged(a, b)) -} - -func TestManager_ConcurrentGetState(t *testing.T) { - m := &Manager{ - state: &State{ - TagCount: 9, - Layouts: []string{"tile"}, - Outputs: map[string]*OutputState{"eDP-1": {Name: "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.TagCount - _ = s.Outputs - } - }() - } - - 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{ - TagCount: uint32(j % 10), - Layouts: []string{"tile", "monocle"}, - Outputs: map[string]*OutputState{"eDP-1": {Active: uint32(j % 2)}}, - } - 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_SyncmapOutputsConcurrentAccess(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 := &outputState{ - id: key, - name: "test-output", - active: uint32(j % 2), - tags: []TagState{{Tag: uint32(j), State: 1}}, - } - m.outputs.Store(key, state) - - if loaded, ok := m.outputs.Load(key); ok { - assert.Equal(t, key, loaded.id) - } - - m.outputs.Range(func(k uint32, v *outputState) bool { - _ = v.name - _ = v.active - return true - }) - } - - m.outputs.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.Outputs) - assert.NotNil(t, s.Layouts) - assert.Equal(t, uint32(0), s.TagCount) -} - -func TestTagState_Fields(t *testing.T) { - tag := TagState{ - Tag: 1, - State: 2, - Clients: 3, - Focused: 1, - } - - assert.Equal(t, uint32(1), tag.Tag) - assert.Equal(t, uint32(2), tag.State) - assert.Equal(t, uint32(3), tag.Clients) - assert.Equal(t, uint32(1), tag.Focused) -} - -func TestOutputState_Fields(t *testing.T) { - out := OutputState{ - Name: "eDP-1", - Active: 1, - Tags: []TagState{{Tag: 1}}, - Layout: 0, - LayoutSymbol: "[]=", - Title: "Firefox", - AppID: "firefox", - KbLayout: "us", - Keymode: "", - } - - assert.Equal(t, "eDP-1", out.Name) - assert.Equal(t, uint32(1), out.Active) - assert.Len(t, out.Tags, 1) - assert.Equal(t, "[]=", out.LayoutSymbol) -} - -func TestStateChanged_NewOutputAppears(t *testing.T) { - a := &State{ - TagCount: 9, - Layouts: []string{}, - Outputs: map[string]*OutputState{ - "eDP-1": {Name: "eDP-1"}, - }, - } - b := &State{ - TagCount: 9, - Layouts: []string{}, - Outputs: map[string]*OutputState{ - "eDP-1": {Name: "eDP-1"}, - "HDMI-A-1": {Name: "HDMI-A-1"}, - }, - } - assert.True(t, stateChanged(a, b)) -} - -func TestStateChanged_TagsLengthDiffers(t *testing.T) { - a := &State{ - TagCount: 9, - Layouts: []string{}, - Outputs: map[string]*OutputState{ - "eDP-1": {Tags: []TagState{{Tag: 1}}}, - }, - } - b := &State{ - TagCount: 9, - Layouts: []string{}, - Outputs: map[string]*OutputState{ - "eDP-1": {Tags: []TagState{{Tag: 1}, {Tag: 2}}}, - }, - } - assert.True(t, stateChanged(a, b)) -} - -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/dwl/types.go b/core/internal/server/dwl/types.go deleted file mode 100644 index 2b175377..00000000 --- a/core/internal/server/dwl/types.go +++ /dev/null @@ -1,176 +0,0 @@ -package dwl - -import ( - "sync" - - wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap" -) - -type TagState struct { - Tag uint32 `json:"tag"` - State uint32 `json:"state"` - Clients uint32 `json:"clients"` - Focused uint32 `json:"focused"` -} - -type OutputState struct { - Name string `json:"name"` - Active uint32 `json:"active"` - Tags []TagState `json:"tags"` - Layout uint32 `json:"layout"` - LayoutSymbol string `json:"layoutSymbol"` - Title string `json:"title"` - AppID string `json:"appId"` - KbLayout string `json:"kbLayout"` - Keymode string `json:"keymode"` -} - -type State struct { - Outputs map[string]*OutputState `json:"outputs"` - TagCount uint32 `json:"tagCount"` - Layouts []string `json:"layouts"` - ActiveOutput string `json:"activeOutput"` -} - -type cmd struct { - fn func() -} - -type Manager struct { - display wlclient.WaylandDisplay - ctx *wlclient.Context - registry *wlclient.Registry - manager any - - outputs syncmap.Map[uint32, *outputState] - - tagCount uint32 - layouts []string - - wlMutex sync.Mutex - cmdq chan cmd - outputSetupReq chan uint32 - 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 outputState struct { - id uint32 - registryName uint32 - output *wlclient.Output - ipcOutput any - name string - active uint32 - tags []TagState - layout uint32 - layoutSymbol string - title string - appID string - kbLayout string - keymode string -} - -func (m *Manager) GetState() State { - m.stateMutex.RLock() - defer m.stateMutex.RUnlock() - if m.state == nil { - return State{ - Outputs: make(map[string]*OutputState), - Layouts: []string{}, - TagCount: 0, - } - } - stateCopy := *m.state - return stateCopy -} - -func (m *Manager) Subscribe(id string) chan State { - ch := make(chan State, 64) - - m.subscribers.Store(id, ch) - - return ch -} - -func (m *Manager) Unsubscribe(id string) { - if val, ok := m.subscribers.LoadAndDelete(id); ok { - close(val) - } -} - -func (m *Manager) notifySubscribers() { - select { - case m.dirty <- struct{}{}: - default: - } -} - -func stateChanged(old, new *State) bool { - if old == nil || new == nil { - return true - } - if old.TagCount != new.TagCount { - return true - } - if len(old.Layouts) != len(new.Layouts) { - return true - } - if old.ActiveOutput != new.ActiveOutput { - return true - } - if len(old.Outputs) != len(new.Outputs) { - return true - } - - for name, newOut := range new.Outputs { - oldOut, exists := old.Outputs[name] - if !exists { - return true - } - if oldOut.Active != newOut.Active { - return true - } - if oldOut.Layout != newOut.Layout { - return true - } - if oldOut.LayoutSymbol != newOut.LayoutSymbol { - return true - } - if oldOut.Title != newOut.Title { - return true - } - if oldOut.AppID != newOut.AppID { - return true - } - if oldOut.KbLayout != newOut.KbLayout { - return true - } - if oldOut.Keymode != newOut.Keymode { - return true - } - if len(oldOut.Tags) != len(newOut.Tags) { - return true - } - for i, newTag := range newOut.Tags { - if i >= len(oldOut.Tags) { - return true - } - oldTag := oldOut.Tags[i] - if oldTag.Tag != newTag.Tag || oldTag.State != newTag.State || - oldTag.Clients != newTag.Clients || oldTag.Focused != newTag.Focused { - return true - } - } - } - - return false -} diff --git a/core/internal/server/router.go b/core/internal/server/router.go index 988b8b28..df7fee2e 100644 --- a/core/internal/server/router.go +++ b/core/internal/server/router.go @@ -11,7 +11,6 @@ import ( "github.com/AvengeMedia/DankMaterialShell/core/internal/server/clipboard" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/cups" 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/freedesktop" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/location" @@ -125,15 +124,6 @@ func RouteRequest(conn net.Conn, req models.Request) { return } - if strings.HasPrefix(req.Method, "dwl.") { - if dwlManager == nil { - models.RespondError(conn, req.ID, "dwl manager not initialized") - return - } - dwl.HandleRequest(conn, req, dwlManager) - return - } - if strings.HasPrefix(req.Method, "brightness.") { if brightnessManager == nil { models.RespondError(conn, req.ID, "brightness manager not initialized") diff --git a/core/internal/server/server.go b/core/internal/server/server.go index b74c17ef..0d025dea 100644 --- a/core/internal/server/server.go +++ b/core/internal/server/server.go @@ -22,7 +22,6 @@ import ( "github.com/AvengeMedia/DankMaterialShell/core/internal/server/clipboard" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/cups" 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/freedesktop" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/location" @@ -39,7 +38,7 @@ import ( "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap" ) -const APIVersion = 24 +const APIVersion = 25 var CLIVersion = "dev" @@ -66,7 +65,6 @@ var bluezManager *bluez.Manager var appPickerManager *apppicker.Manager var cupsManager *cups.Manager var tailscaleManager *tailscale.Manager -var dwlManager *dwl.Manager var brightnessManager *brightness.Manager var wlrOutputManager *wlroutput.Manager var evdevManager *evdev.Manager @@ -252,30 +250,6 @@ func InitializeCupsManager() error { return nil } -func InitializeDwlManager() error { - log.Info("Attempting to initialize DWL IPC...") - - 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 := dwl.NewManager(wlContext.Display()) - if err != nil { - log.Debug("Failed to initialize dwl manager: %v", err) - return err - } - - dwlManager = manager - - log.Info("DWL IPC initialized successfully") - return nil -} - func InitializeBrightnessManager() error { manager, err := brightness.NewManager() if err != nil { @@ -468,10 +442,6 @@ func getCapabilities() Capabilities { caps = append(caps, "tailscale") } - if dwlManager != nil { - caps = append(caps, "dwl") - } - if brightnessManager != nil { caps = append(caps, "brightness") } @@ -538,10 +508,6 @@ func getServerInfo() ServerInfo { caps = append(caps, "tailscale") } - if dwlManager != nil { - caps = append(caps, "dwl") - } - if brightnessManager != nil { caps = append(caps, "brightness") } @@ -1046,38 +1012,6 @@ func handleSubscribe(conn net.Conn, req models.Request) { }() } - if shouldSubscribe("dwl") && dwlManager != nil { - wg.Add(1) - dwlChan := dwlManager.Subscribe(clientID + "-dwl") - go func() { - defer wg.Done() - defer dwlManager.Unsubscribe(clientID + "-dwl") - - initialState := dwlManager.GetState() - select { - case eventChan <- ServiceEvent{Service: "dwl", Data: initialState}: - case <-stopChan: - return - } - - for { - select { - case state, ok := <-dwlChan: - if !ok { - return - } - select { - case eventChan <- ServiceEvent{Service: "dwl", Data: state}: - case <-stopChan: - return - } - case <-stopChan: - return - } - } - }() - } - if shouldSubscribe("brightness") && brightnessManager != nil { wg.Add(2) brightnessStateChan := brightnessManager.Subscribe(clientID + "-brightness-state") @@ -1333,9 +1267,6 @@ func cleanupManagers() { if cupsManager != nil { cupsManager.Close() } - if dwlManager != nil { - dwlManager.Close() - } if brightnessManager != nil { brightnessManager.Close() } @@ -1502,19 +1433,6 @@ func Start(printDocs bool) error { log.Info(" cups.resumePrinter - Resume printer (params: printerName)") log.Info(" cups.cancelJob - Cancel job (params: printerName, jobID)") log.Info(" cups.purgeJobs - Cancel all jobs (params: printerName)") - log.Info("DWL:") - log.Info(" dwl.getState - Get current dwl state (tags, windows, layouts, keyboard)") - log.Info(" dwl.setTags - Set active tags (params: output, tagmask, toggleTagset)") - log.Info(" dwl.setClientTags - Set focused client tags (params: output, andTags, xorTags)") - log.Info(" dwl.setLayout - Set layout (params: output, index)") - log.Info(" dwl.subscribe - Subscribe to dwl state changes (streaming)") - log.Info(" Output state includes:") - log.Info(" - tags : Tag states (active, clients, focused)") - log.Info(" - layoutSymbol : Current layout name") - log.Info(" - title : Focused window title") - log.Info(" - appId : Focused window app ID") - log.Info(" - kbLayout : Current keyboard layout") - log.Info(" - keymode : Current keybind mode") log.Info("Brightness:") log.Info(" brightness.getState - Get current brightness state for all devices") log.Info(" brightness.setBrightness - Set device brightness (params: device, percent)") @@ -1691,10 +1609,6 @@ func Start(printDocs bool) error { log.Debugf("AppPicker manager unavailable: %v", err) } - if err := InitializeDwlManager(); err != nil { - log.Debugf("DWL manager unavailable: %v", err) - } - if err := InitializeWlrOutputManager(); err != nil { log.Debugf("WlrOutput manager unavailable: %v", err) } diff --git a/quickshell/Common/SettingsData.qml b/quickshell/Common/SettingsData.qml index 60b62363..f88c3100 100644 --- a/quickshell/Common/SettingsData.qml +++ b/quickshell/Common/SettingsData.qml @@ -489,9 +489,6 @@ Singleton { "hideOnTouch": false, "inactiveTimeout": 0 }, - "dwl": { - "cursorHideTimeout": 0 - }, "mango": { "cursorHideTimeout": 0 } @@ -1224,8 +1221,6 @@ Singleton { NiriService.generateNiriLayoutConfig(); if (CompositorService.isHyprland && typeof HyprlandService !== "undefined") HyprlandService.generateLayoutConfig(); - if (CompositorService.isDwl && typeof DwlService !== "undefined") - DwlService.generateLayoutConfig(); if (CompositorService.isMango && typeof MangoService !== "undefined") MangoService.generateLayoutConfig(); } @@ -2451,10 +2446,6 @@ Singleton { HyprlandService.generateCursorConfig(); return; } - if (CompositorService.isDwl && typeof DwlService !== "undefined") { - DwlService.generateCursorConfig(); - return; - } if (CompositorService.isMango && typeof MangoService !== "undefined") { MangoService.generateCursorConfig(); return; diff --git a/quickshell/DMSShellIPC.qml b/quickshell/DMSShellIPC.qml index 1586ad02..c94e0115 100644 --- a/quickshell/DMSShellIPC.qml +++ b/quickshell/DMSShellIPC.qml @@ -337,9 +337,6 @@ Item { const focusedWs = I3.workspaces.values.find(ws => ws.focused === true); return focusedWs?.monitor?.name || ""; } - if (CompositorService.isDwl && DwlService.activeOutput) { - return DwlService.activeOutput; - } if (CompositorService.isMango && MangoService.activeOutput) { return MangoService.activeOutput; } diff --git a/quickshell/Modals/Greeter/GreeterCompletePage.qml b/quickshell/Modals/Greeter/GreeterCompletePage.qml index a468a16e..45bb3cb1 100644 --- a/quickshell/Modals/Greeter/GreeterCompletePage.qml +++ b/quickshell/Modals/Greeter/GreeterCompletePage.qml @@ -320,8 +320,6 @@ Item { url = "https://danklinux.com/docs/dankmaterialshell/compositors#dms-keybindings"; else if (CompositorService.isHyprland) url = "https://danklinux.com/docs/dankmaterialshell/compositors#dms-keybindings-1"; - else if (CompositorService.isDwl) - url = "https://danklinux.com/docs/dankmaterialshell/compositors#dms-keybindings-2"; else if (CompositorService.isMango) url = "https://danklinux.com/docs/dankmaterialshell/compositors#dms-keybindings-2"; Qt.openUrlExternally(url); diff --git a/quickshell/Modals/Greeter/GreeterWelcomePage.qml b/quickshell/Modals/Greeter/GreeterWelcomePage.qml index 233f88b3..9470b42f 100644 --- a/quickshell/Modals/Greeter/GreeterWelcomePage.qml +++ b/quickshell/Modals/Greeter/GreeterWelcomePage.qml @@ -130,7 +130,7 @@ Item { title: I18n.tr("Multi-Monitor", "greeter feature card title") description: I18n.tr("Per-screen config", "greeter feature card description") onClicked: { - const hasDisplayConfig = CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango; + const hasDisplayConfig = CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango; PopoutService.openSettingsWithTab(hasDisplayConfig ? "display_config" : "display_widgets"); } } diff --git a/quickshell/Modules/DankBar/DankBar.qml b/quickshell/Modules/DankBar/DankBar.qml index 6c971505..cb8692f3 100644 --- a/quickshell/Modules/DankBar/DankBar.qml +++ b/quickshell/Modules/DankBar/DankBar.qml @@ -108,8 +108,6 @@ Item { } else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) { const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true); focusedScreenName = focusedWs?.monitor?.name || ""; - } else if (CompositorService.isDwl && DwlService.activeOutput) { - focusedScreenName = DwlService.activeOutput; } else if (CompositorService.isMango && MangoService.activeOutput) { focusedScreenName = MangoService.activeOutput; } @@ -139,8 +137,6 @@ Item { } else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) { const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true); focusedScreenName = focusedWs?.monitor?.name || ""; - } else if (CompositorService.isDwl && DwlService.activeOutput) { - focusedScreenName = DwlService.activeOutput; } else if (CompositorService.isMango && MangoService.activeOutput) { focusedScreenName = MangoService.activeOutput; } diff --git a/quickshell/Modules/DankBar/DankBarContent.qml b/quickshell/Modules/DankBar/DankBarContent.qml index 9888d297..ef3c914d 100644 --- a/quickshell/Modules/DankBar/DankBarContent.qml +++ b/quickshell/Modules/DankBar/DankBarContent.qml @@ -29,7 +29,6 @@ Item { readonly property real _frameEdgeFloorInset: (SettingsData.frameEnabled && _usesFrameBarChrome) ? Math.max(0, SettingsData.frameThickness - _edgeBaseMargin) : 0 readonly property bool _barIsVertical: _hasBarWindow ? barWindow.isVertical : false readonly property string _barScreenName: _hasBarWindow ? (barWindow.screenName || "") : "" - readonly property var dwlSvc: CompositorService.isMango ? MangoService : DwlService readonly property bool hasAdjacentTopBarLive: _hasBarWindow && barWindow.hasAdjacentTopBar readonly property bool hasAdjacentBottomBarLive: _hasBarWindow && barWindow.hasAdjacentBottomBar readonly property bool hasAdjacentLeftBarLive: _hasBarWindow && barWindow.hasAdjacentLeftBar @@ -190,16 +189,16 @@ Item { } return monitorWorkspaces.sort((a, b) => a.id - b.id); - } else if (CompositorService.isDwl || CompositorService.isMango) { - if (!dwlSvc.available) { + } else if (CompositorService.isMango) { + if (!MangoService.available) { return [0]; } if (SettingsData.dwlShowAllTags) { return Array.from({ - length: dwlSvc.tagCount + length: MangoService.tagCount }, (_, i) => i); } - return dwlSvc.getVisibleTags(screenName); + return MangoService.getVisibleTags(screenName); } else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) { const workspaces = I3.workspaces?.values || []; if (workspaces.length === 0) @@ -235,13 +234,13 @@ Item { const monitors = Hyprland.monitors?.values || []; const currentMonitor = monitors.find(monitor => monitor.name === screenName); return currentMonitor?.activeWorkspace?.id ?? 1; - } else if (CompositorService.isDwl || CompositorService.isMango) { - if (!dwlSvc.available) + } else if (CompositorService.isMango) { + if (!MangoService.available) return 0; - const outputState = dwlSvc.getOutputState(screenName); + const outputState = MangoService.getOutputState(screenName); if (!outputState || !outputState.tags) return 0; - const activeTags = dwlSvc.getActiveTags(screenName); + const activeTags = MangoService.getActiveTags(screenName); return activeTags.length > 0 ? activeTags[0] : 0; } else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) { if (!screenName || SettingsData.workspaceFollowFocus) { @@ -283,14 +282,14 @@ Item { if (nextIndex !== validIndex) { HyprlandService.focusWorkspace(realWorkspaces[nextIndex].id); } - } else if (CompositorService.isDwl || CompositorService.isMango) { + } else if (CompositorService.isMango) { const currentTag = getCurrentWorkspace(); const currentIndex = realWorkspaces.findIndex(tag => tag === currentTag); const validIndex = currentIndex === -1 ? 0 : currentIndex; const nextIndex = direction > 0 ? Math.min(validIndex + 1, realWorkspaces.length - 1) : Math.max(validIndex - 1, 0); if (nextIndex !== validIndex) { - dwlSvc.switchToTag(_barScreenName, realWorkspaces[nextIndex]); + MangoService.switchToTag(_barScreenName, realWorkspaces[nextIndex]); } } else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) { const currentWs = getCurrentWorkspace(); diff --git a/quickshell/Modules/DankBar/Popouts/DWLLayoutPopout.qml b/quickshell/Modules/DankBar/Popouts/DWLLayoutPopout.qml index 57e0dfd5..344f6fb9 100644 --- a/quickshell/Modules/DankBar/Popouts/DWLLayoutPopout.qml +++ b/quickshell/Modules/DankBar/Popouts/DWLLayoutPopout.qml @@ -10,9 +10,7 @@ DankPopout { property var triggerScreen: null - // mango shares dwl's layout model; route to the right service. - readonly property bool isDwlLike: CompositorService.isDwl || CompositorService.isMango - readonly property var dwlSvc: CompositorService.isMango ? MangoService : DwlService + readonly property bool isMango: CompositorService.isMango function setTriggerPosition(x, y, width, section, screen, barPosition, barThickness, barSpacing, barConfig) { triggerX = x; @@ -37,8 +35,8 @@ DankPopout { onScreenChanged: updateOutputState() function updateOutputState() { - if (screen && root.dwlSvc.available) { - outputState = root.dwlSvc.getOutputState(screen.name); + if (screen && MangoService.available) { + outputState = MangoService.getOutputState(screen.name); } else { outputState = null; } @@ -84,7 +82,7 @@ DankPopout { } Connections { - target: DwlService + target: MangoService function onStateChanged() { updateOutputState(); } @@ -219,7 +217,7 @@ DankPopout { spacing: Theme.spacingS Repeater { - model: root.dwlSvc.layouts + model: MangoService.layouts delegate: Rectangle { required property string modelData @@ -273,11 +271,11 @@ DankPopout { if (!root.triggerScreen) { return; } - if (!root.dwlSvc.available) { + if (!MangoService.available) { return; } - root.dwlSvc.setLayout(root.triggerScreen.name, index); + MangoService.setLayout(root.triggerScreen.name, index); root.close(); } } diff --git a/quickshell/Modules/DankBar/WidgetHost.qml b/quickshell/Modules/DankBar/WidgetHost.qml index 3a0fefb5..4b23054a 100644 --- a/quickshell/Modules/DankBar/WidgetHost.qml +++ b/quickshell/Modules/DankBar/WidgetHost.qml @@ -282,7 +282,7 @@ Loader { "cpuTemp": dgopAvailable, "gpuTemp": dgopAvailable, "network_speed_monitor": dgopAvailable, - "layout": (CompositorService.isDwl && DwlService.dwlAvailable) || (CompositorService.isMango && MangoService.available) + "layout": CompositorService.isMango && MangoService.available }; return widgetVisibility[widgetId] ?? true; diff --git a/quickshell/Modules/DankBar/Widgets/DWLLayout.qml b/quickshell/Modules/DankBar/Widgets/DWLLayout.qml index 95738e6c..d9b92e40 100644 --- a/quickshell/Modules/DankBar/Widgets/DWLLayout.qml +++ b/quickshell/Modules/DankBar/Widgets/DWLLayout.qml @@ -13,12 +13,11 @@ BasePill { signal toggleLayoutPopup // mango shares dwl's tag/layout model; route to the right service. - readonly property bool isDwlLike: CompositorService.isDwl || CompositorService.isMango - readonly property var dwlSvc: CompositorService.isMango ? MangoService : DwlService + readonly property bool isMango: CompositorService.isMango - visible: layout.isDwlLike && layout.dwlSvc.available + visible: layout.isMango && MangoService.available - property var outputState: parentScreen ? layout.dwlSvc.getOutputState(parentScreen.name) : null + property var outputState: parentScreen ? MangoService.getOutputState(parentScreen.name) : null property string currentLayoutSymbol: outputState?.layoutSymbol || "" property int currentLayoutIndex: outputState?.layout || 0 @@ -41,9 +40,9 @@ BasePill { } Connections { - target: layout.dwlSvc + target: MangoService function onStateChanged() { - outputState = parentScreen ? layout.dwlSvc.getOutputState(parentScreen.name) : null; + outputState = parentScreen ? MangoService.getOutputState(parentScreen.name) : null; } } @@ -101,13 +100,13 @@ BasePill { } onRightClicked: { - if (!parentScreen || !layout.dwlSvc.available || layout.dwlSvc.layouts.length === 0) { + if (!parentScreen || !MangoService.available || MangoService.layouts.length === 0) { return; } const currentIndex = layout.currentLayoutIndex; - const nextIndex = (currentIndex + 1) % layout.dwlSvc.layouts.length; + const nextIndex = (currentIndex + 1) % MangoService.layouts.length; - layout.dwlSvc.setLayout(parentScreen.name, nextIndex); + MangoService.setLayout(parentScreen.name, nextIndex); } } diff --git a/quickshell/Modules/DankBar/Widgets/KeyboardLayoutName.qml b/quickshell/Modules/DankBar/Widgets/KeyboardLayoutName.qml index af9240a9..2fbcdf7d 100644 --- a/quickshell/Modules/DankBar/Widgets/KeyboardLayoutName.qml +++ b/quickshell/Modules/DankBar/Widgets/KeyboardLayoutName.qml @@ -112,8 +112,6 @@ BasePill { property string currentLayout: { if (CompositorService.isNiri) { return NiriService.getCurrentKeyboardLayoutName(); - } else if (CompositorService.isDwl) { - return DwlService.currentKeyboardLayout; } else if (CompositorService.isMango) { return MangoService.currentKeyboardLayout; } @@ -209,8 +207,6 @@ BasePill { NiriService.cycleKeyboardLayout(); } else if (CompositorService.isHyprland) { Quickshell.execDetached(["hyprctl", "switchxkblayout", root.hyprlandKeyboard, "next"]); - } else if (CompositorService.isDwl) { - Quickshell.execDetached(["mmsg", "dispatch", "switch_keyboard_layout"]); } else if (CompositorService.isMango) { MangoService.cycleKeyboardLayout(); } diff --git a/quickshell/Modules/DankBar/Widgets/LauncherButton.qml b/quickshell/Modules/DankBar/Widgets/LauncherButton.qml index 80d3fdb8..d5f6e570 100644 --- a/quickshell/Modules/DankBar/Widgets/LauncherButton.qml +++ b/quickshell/Modules/DankBar/Widgets/LauncherButton.qml @@ -55,7 +55,7 @@ BasePill { } IconImage { - visible: SettingsData.launcherLogoMode === "compositor" && (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle || CompositorService.isLabwc) + visible: SettingsData.launcherLogoMode === "compositor" && (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle || CompositorService.isLabwc) anchors.centerIn: parent width: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale) height: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale) @@ -66,8 +66,6 @@ BasePill { return "file://" + Theme.shellDir + "/assets/niri.svg"; } else if (CompositorService.isHyprland) { return "file://" + Theme.shellDir + "/assets/hyprland.svg"; - } else if (CompositorService.isDwl) { - return "file://" + Theme.shellDir + "/assets/mango.png"; } else if (CompositorService.isMango) { return "file://" + Theme.shellDir + "/assets/mango.png"; } else if (CompositorService.isSway) { diff --git a/quickshell/Modules/DankBar/Widgets/WorkspaceSwitcher.qml b/quickshell/Modules/DankBar/Widgets/WorkspaceSwitcher.qml index 311db408..73a256a1 100644 --- a/quickshell/Modules/DankBar/Widgets/WorkspaceSwitcher.qml +++ b/quickshell/Modules/DankBar/Widgets/WorkspaceSwitcher.qml @@ -22,10 +22,7 @@ Item { property var hyprlandOverviewLoader: null property var parentScreen: null - // mango shares dwl's tag model; route to the right service so one set of - // branches serves both. - readonly property bool isDwlLike: CompositorService.isDwl || CompositorService.isMango - readonly property var dwlSvc: CompositorService.isMango ? MangoService : DwlService + readonly property bool isMango: CompositorService.isMango readonly property real _leftMargin: { if (isVertical) @@ -80,9 +77,8 @@ Item { return NiriService.currentOutput || root.screenName; case "hyprland": return Hyprland.focusedWorkspace?.monitor?.name || root.screenName; - case "dwl": case "mango": - return root.dwlSvc.activeOutput || root.screenName; + return MangoService.activeOutput || root.screenName; case "sway": case "scroll": case "miracle": @@ -101,7 +97,6 @@ Item { switch (CompositorService.compositor) { case "niri": case "hyprland": - case "dwl": case "mango": case "sway": case "scroll": @@ -128,7 +123,6 @@ Item { return getNiriActiveWorkspace(); case "hyprland": return getHyprlandActiveWorkspace(); - case "dwl": case "mango": const activeTags = getDwlActiveTags(); return activeTags.length > 0 ? activeTags[0] : -1; @@ -141,7 +135,7 @@ Item { } } property var dwlActiveTags: { - if (root.isDwlLike) { + if (root.isMango) { return getDwlActiveTags(); } return []; @@ -160,9 +154,6 @@ Item { case "hyprland": baseList = getHyprlandWorkspaces(); break; - case "dwl": - baseList = getDwlTags(); - break; case "mango": if (root.mangoOverviewActive) return []; @@ -302,7 +293,7 @@ Item { } } else if (CompositorService.isHyprland) { targetWorkspaceId = ws.id !== undefined ? ws.id : ws; - } else if (root.isDwlLike) { + } else if (root.isMango) { if (typeof ws !== "object" || ws.tag === undefined) { return []; } @@ -322,8 +313,8 @@ Item { } else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) { const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true); isActiveWs = focusedWs ? (focusedWs.num === targetWorkspaceId) : false; - } else if (root.isDwlLike) { - const output = root.dwlSvc.getOutputState(root.effectiveScreenName); + } else if (root.isMango) { + const output = MangoService.getOutputState(root.effectiveScreenName); if (output && output.tags) { const tag = output.tags.find(t => t.tag === targetWorkspaceId); isActiveWs = tag ? (tag.state === 1) : false; @@ -411,7 +402,7 @@ Item { "id": -1, "name": "" }; - } else if (root.isDwlLike) { + } else if (root.isMango) { placeholder = { "tag": -1 }; @@ -493,11 +484,11 @@ Item { } function getDwlTags() { - if (!root.dwlSvc.available) + if (!MangoService.available) return []; const targetScreen = root.effectiveScreenName; - const output = root.dwlSvc.getOutputState(targetScreen); + const output = MangoService.getOutputState(targetScreen); if (!output || !output.tags || output.tags.length === 0) return []; @@ -510,7 +501,7 @@ Item { })); } - const visibleTagIndices = root.dwlSvc.getVisibleTags(targetScreen); + const visibleTagIndices = MangoService.getVisibleTags(targetScreen); return visibleTagIndices.map(tagIndex => { const tagData = output.tags.find(t => t.tag === tagIndex); return { @@ -523,10 +514,10 @@ Item { } function getDwlActiveTags() { - if (!root.dwlSvc.available) + if (!MangoService.available) return []; - return root.dwlSvc.getActiveTags(root.effectiveScreenName); + return MangoService.getActiveTags(root.effectiveScreenName); } function getExtWorkspaceWorkspaces() { @@ -577,7 +568,7 @@ Item { return ws && ws.idx !== -1; if (CompositorService.isHyprland) return ws && ws.id !== -1; - if (root.isDwlLike) + if (root.isMango) return ws && ws.tag !== -1; if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) return ws && ws.num !== -1; @@ -605,10 +596,9 @@ Item { HyprlandService.focusWorkspace(data.id); } break; - case "dwl": case "mango": if (data.tag !== undefined) - root.dwlSvc.switchToTag(root.screenName, data.tag); + MangoService.switchToTag(root.screenName, data.tag); break; case "sway": case "scroll": @@ -694,7 +684,7 @@ Item { } HyprlandService.focusWorkspace(realWorkspaces[nextIndex].id); - } else if (root.isDwlLike) { + } else if (root.isMango) { const realWorkspaces = getRealWorkspaces(); if (realWorkspaces.length < 2) { return; @@ -708,7 +698,7 @@ Item { return; } - root.dwlSvc.switchToTag(root.screenName, realWorkspaces[nextIndex].tag); + MangoService.switchToTag(root.screenName, realWorkspaces[nextIndex].tag); } else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) { const realWorkspaces = getRealWorkspaces(); if (realWorkspaces.length < 2) { @@ -736,7 +726,7 @@ Item { return (modelData?.idx !== undefined && modelData?.idx !== -1) ? modelData.idx : ""; if (CompositorService.isHyprland) return modelData?.id || ""; - if (root.isDwlLike) + if (root.isMango) return (modelData?.tag !== undefined) ? (modelData.tag + 1) : ""; if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) return modelData?.num || ""; @@ -751,7 +741,7 @@ Item { isPlaceholder = modelData?.idx === -1; } else if (CompositorService.isHyprland) { isPlaceholder = modelData?.id === -1; - } else if (root.isDwlLike) { + } else if (root.isMango) { isPlaceholder = modelData?.tag === -1; } else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) { isPlaceholder = modelData?.num === -1; @@ -786,7 +776,7 @@ Item { return getWorkspaceIndexFallback(modelData, index); } - readonly property bool hasNativeWorkspaceSupport: CompositorService.isNiri || CompositorService.isHyprland || root.isDwlLike || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle + readonly property bool hasNativeWorkspaceSupport: CompositorService.isNiri || CompositorService.isHyprland || root.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle readonly property bool hasWorkspaces: getRealWorkspaces().length > 0 readonly property bool shouldShow: hasNativeWorkspaceSupport || (useExtWorkspace && hasWorkspaces) @@ -1051,7 +1041,7 @@ Item { return !!(modelData && modelData.idx === root.currentWorkspace); if (CompositorService.isHyprland) return !!(modelData && modelData.id === root.currentWorkspace); - if (root.isDwlLike) + if (root.isMango) return !!(modelData && root.dwlActiveTags.includes(modelData.tag)); if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) return !!(modelData && modelData.num === root.currentWorkspace); @@ -1060,7 +1050,7 @@ Item { property bool isOccupied: { if (CompositorService.isHyprland) return Array.from(Hyprland.toplevels?.values || []).some(tl => tl.workspace?.id === modelData?.id); - if (root.isDwlLike) + if (root.isMango) return modelData.clients > 0; if (CompositorService.isNiri) { const workspace = NiriService.allWorkspaces.find(ws => ws.idx + 1 === modelData && ws.output === root.effectiveScreenName); @@ -1075,7 +1065,7 @@ Item { return !!(modelData && modelData.idx === -1); if (CompositorService.isHyprland) return !!(modelData && modelData.id === -1); - if (root.isDwlLike) + if (root.isMango) return !!(modelData && modelData.tag === -1); if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) return !!(modelData && modelData.num === -1); @@ -1092,7 +1082,7 @@ Item { return modelData?.urgent ?? false; if (CompositorService.isNiri) return loadedIsUrgent; - if (root.isDwlLike) + if (root.isMango) return modelData?.state === 2; if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) return loadedIsUrgent; @@ -1120,7 +1110,7 @@ Item { targetWorkspaceId = modelData?.id; } else if (CompositorService.isHyprland) { targetWorkspaceId = modelData?.id; - } else if (root.isDwlLike) { + } else if (root.isMango) { targetWorkspaceId = modelData?.tag; } else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) { targetWorkspaceId = modelData?.num; @@ -1383,8 +1373,8 @@ Item { } } else if (CompositorService.isHyprland && modelData?.id) { HyprlandService.focusWorkspace(modelData.id); - } else if (root.isDwlLike && modelData?.tag !== undefined) { - root.dwlSvc.switchToTag(root.screenName, modelData.tag); + } else if (root.isMango && modelData?.tag !== undefined) { + MangoService.switchToTag(root.screenName, modelData.tag); } else if ((CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) && modelData?.num) { try { I3.dispatch(`workspace number ${modelData.num}`); @@ -1395,8 +1385,8 @@ Item { NiriService.toggleOverview(); } else if (CompositorService.isHyprland && root.hyprlandOverviewLoader?.item) { root.hyprlandOverviewLoader.item.overviewOpen = !root.hyprlandOverviewLoader.item.overviewOpen; - } else if (root.isDwlLike && modelData?.tag !== undefined) { - root.dwlSvc.toggleTag(root.screenName, modelData.tag); + } else if (root.isMango && modelData?.tag !== undefined) { + MangoService.toggleTag(root.screenName, modelData.tag); } } } @@ -1420,7 +1410,7 @@ Item { wsData = modelData || null; } else if (CompositorService.isHyprland) { wsData = modelData; - } else if (root.isDwlLike) { + } else if (root.isMango) { wsData = modelData; } else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) { wsData = modelData; @@ -1434,7 +1424,7 @@ Item { } if (SettingsData.showWorkspaceApps) { - if (root.isDwlLike || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) { + if (root.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) { delegateRoot.loadedIcons = root.getWorkspaceIcons(modelData); } else if (CompositorService.isNiri) { delegateRoot.loadedIcons = root.getWorkspaceIcons(isPlaceholder ? null : modelData); @@ -1994,8 +1984,8 @@ Item { } } Connections { - target: root.dwlSvc - enabled: root.isDwlLike + target: MangoService + enabled: root.isMango function onStateChanged() { delegateRoot.updateAllData(); } diff --git a/quickshell/Modules/DankDash/Overview/UserInfoCard.qml b/quickshell/Modules/DankDash/Overview/UserInfoCard.qml index 9f5c019b..5858f269 100644 --- a/quickshell/Modules/DankDash/Overview/UserInfoCard.qml +++ b/quickshell/Modules/DankDash/Overview/UserInfoCard.qml @@ -67,9 +67,6 @@ Card { return I18n.tr("on Niri"); if (CompositorService.isHyprland) return I18n.tr("on Hyprland"); - // technically they might not be on mangowc, but its what we support in the docs - if (CompositorService.isDwl) - return I18n.tr("on MangoWC"); if (CompositorService.isMango) return I18n.tr("on MangoWC"); if (CompositorService.isSway) @@ -101,9 +98,7 @@ Card { } StyledText { - text: DgopService.shortUptime - ? I18n.tr("up") + DgopService.shortUptime.slice(2) - : I18n.tr("up") + text: DgopService.shortUptime ? I18n.tr("up") + DgopService.shortUptime.slice(2) : I18n.tr("up") font.pixelSize: Theme.fontSizeSmall color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) anchors.verticalCenter: parent.verticalCenter diff --git a/quickshell/Modules/Dock/DockLauncherButton.qml b/quickshell/Modules/Dock/DockLauncherButton.qml index 9380c2ec..b5d93ad4 100644 --- a/quickshell/Modules/Dock/DockLauncherButton.qml +++ b/quickshell/Modules/Dock/DockLauncherButton.qml @@ -236,7 +236,7 @@ Item { } IconImage { - visible: SettingsData.dockLauncherLogoMode === "compositor" && (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle || CompositorService.isLabwc) + visible: SettingsData.dockLauncherLogoMode === "compositor" && (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle || CompositorService.isLabwc) anchors.centerIn: parent width: actualIconSize + SettingsData.dockLauncherLogoSizeOffset height: actualIconSize + SettingsData.dockLauncherLogoSizeOffset @@ -247,8 +247,6 @@ Item { return "file://" + Theme.shellDir + "/assets/niri.svg"; } else if (CompositorService.isHyprland) { return "file://" + Theme.shellDir + "/assets/hyprland.svg"; - } else if (CompositorService.isDwl) { - return "file://" + Theme.shellDir + "/assets/mango.png"; } else if (CompositorService.isMango) { return "file://" + Theme.shellDir + "/assets/mango.png"; } else if (CompositorService.isSway) { diff --git a/quickshell/Modules/Settings/AboutTab.qml b/quickshell/Modules/Settings/AboutTab.qml index c71f1ece..bb37b3ef 100644 --- a/quickshell/Modules/Settings/AboutTab.qml +++ b/quickshell/Modules/Settings/AboutTab.qml @@ -15,7 +15,7 @@ Item { property bool isSway: CompositorService.isSway property bool isScroll: CompositorService.isScroll property bool isMiracle: CompositorService.isMiracle - property bool isDwl: CompositorService.isDwl || CompositorService.isMango + property bool isMango: CompositorService.isMango property bool isLabwc: CompositorService.isLabwc property string compositorName: { @@ -27,7 +27,7 @@ Item { return "scroll"; if (isMiracle) return "miracle"; - if (isDwl) + if (isMango) return "mangowc"; if (isLabwc) return "labwc"; @@ -43,7 +43,7 @@ Item { return "/assets/sway.svg"; if (isMiracle) return "/assets/miraclewm.svg"; - if (isDwl) + if (isMango) return "/assets/mango.png"; if (isLabwc) return "/assets/labwc.png"; @@ -59,7 +59,7 @@ Item { return "https://github.com/dawsers/scroll"; if (isMiracle) return "https://github.com/miracle-wm-org/miracle-wm"; - if (isDwl) + if (isMango) return "https://github.com/DreamMaoMao/mangowc"; if (isLabwc) return "https://labwc.github.io/"; @@ -75,7 +75,7 @@ Item { return I18n.tr("Scroll GitHub"); if (isMiracle) return I18n.tr("Scroll GitHub"); - if (isDwl) + if (isMango) return I18n.tr("mangowc GitHub"); if (isLabwc) return I18n.tr("LabWC Website"); @@ -88,7 +88,7 @@ Item { property string compositorDiscordUrl: { if (isHyprland) return "https://discord.com/invite/hQ9XvMUjjr"; - if (isDwl) + if (isMango) return "https://discord.gg/CPjbDxesh5"; return ""; } @@ -96,7 +96,7 @@ Item { property string compositorDiscordTooltip: { if (isHyprland) return I18n.tr("Hyprland Discord Server"); - if (isDwl) + if (isMango) return I18n.tr("mangowc Discord Server"); return ""; } @@ -107,9 +107,9 @@ Item { property string ircUrl: "https://web.libera.chat/gamja/?channels=#labwc" property string ircTooltip: I18n.tr("LabWC IRC Channel") - property bool showMatrix: isNiri && !isHyprland && !isSway && !isScroll && !isMiracle && !isDwl && !isLabwc - property bool showCompositorDiscord: isHyprland || isDwl - property bool showReddit: isNiri && !isHyprland && !isSway && !isScroll && !isMiracle && !isDwl && !isLabwc + property bool showMatrix: isNiri && !isHyprland && !isSway && !isScroll && !isMiracle && !isMango && !isLabwc + property bool showCompositorDiscord: isHyprland || isMango + property bool showReddit: isNiri && !isHyprland && !isSway && !isScroll && !isMiracle && !isMango && !isLabwc property bool showIrc: isLabwc DankFlickable { diff --git a/quickshell/Modules/Settings/CompositorLayoutTab.qml b/quickshell/Modules/Settings/CompositorLayoutTab.qml index 98add06b..bf6a5420 100644 --- a/quickshell/Modules/Settings/CompositorLayoutTab.qml +++ b/quickshell/Modules/Settings/CompositorLayoutTab.qml @@ -229,7 +229,7 @@ Item { title: I18n.tr("MangoWC Layout Overrides") settingKey: "mangoLayout" iconName: "crop_square" - visible: CompositorService.isDwl || CompositorService.isMango + visible: CompositorService.isMango SettingsToggleRow { tags: ["mangowc", "mango", "gaps", "override"] diff --git a/quickshell/Modules/Settings/DisplayConfig/DisplayConfigState.qml b/quickshell/Modules/Settings/DisplayConfig/DisplayConfigState.qml index bd976700..5b8ac166 100644 --- a/quickshell/Modules/Settings/DisplayConfig/DisplayConfigState.qml +++ b/quickshell/Modules/Settings/DisplayConfig/DisplayConfigState.qml @@ -1023,7 +1023,6 @@ Singleton { return parseNiriOutputs(content); case "hyprland": return parseHyprlandOutputs(content); - case "dwl": case "mango": return parseMangoOutputs(content); default: @@ -1362,7 +1361,6 @@ Singleton { "grepPattern": "dms.outputs", "includeLine": "require(\"dms.outputs\")" }; - case "dwl": case "mango": return { "configFile": configDir + "/mango/config.conf", @@ -1377,7 +1375,7 @@ Singleton { function checkIncludeStatus() { const compositor = CompositorService.compositor; - if (compositor !== "niri" && compositor !== "hyprland" && compositor !== "dwl" && compositor !== "mango") { + if (compositor !== "niri" && compositor !== "hyprland" && compositor !== "mango") { includeStatus = { "exists": false, "included": false, @@ -1388,8 +1386,7 @@ Singleton { } const filename = (compositor === "niri") ? "outputs.kdl" : ((compositor === "hyprland") ? "outputs.lua" : "outputs.conf"); - // mango and dwl both use outputs.conf under ~/.config/mango - const compositorArg = (compositor === "dwl" || compositor === "mango") ? "mangowc" : compositor; + const compositorArg = (compositor === "mango") ? "mangowc" : compositor; checkingInclude = true; Proc.runCommand("check-outputs-include", ["dms", "config", "resolve-include", compositorArg, filename], (output, exitCode) => { @@ -1589,9 +1586,6 @@ Singleton { case "mango": MangoService.generateOutputsConfig(outputsData, finish); break; - case "dwl": - DwlService.generateOutputsConfig(outputsData, finish); - break; default: WlrOutputService.applyOutputsConfig(outputsData, outputs); finish(true); diff --git a/quickshell/Modules/Settings/DisplayConfig/OutputCard.qml b/quickshell/Modules/Settings/DisplayConfig/OutputCard.qml index 7be85d34..134d6a2e 100644 --- a/quickshell/Modules/Settings/DisplayConfig/OutputCard.qml +++ b/quickshell/Modules/Settings/DisplayConfig/OutputCard.qml @@ -317,7 +317,7 @@ StyledRect { DankToggle { width: parent.width text: I18n.tr("Variable Refresh Rate") - visible: root.isConnected && !root.isDisabled && !CompositorService.isDwl && !CompositorService.isMango && !CompositorService.isHyprland && !CompositorService.isNiri && (DisplayConfigState.outputs[root.outputName]?.vrr_supported ?? false) + visible: root.isConnected && !root.isDisabled && !CompositorService.isMango && !CompositorService.isHyprland && !CompositorService.isNiri && (DisplayConfigState.outputs[root.outputName]?.vrr_supported ?? false) checked: { const pendingVrr = DisplayConfigState.getPendingValue(root.outputName, "vrr"); if (pendingVrr !== undefined) diff --git a/quickshell/Modules/Settings/DisplayConfigTab.qml b/quickshell/Modules/Settings/DisplayConfigTab.qml index 0bdde3ad..0ea55055 100644 --- a/quickshell/Modules/Settings/DisplayConfigTab.qml +++ b/quickshell/Modules/Settings/DisplayConfigTab.qml @@ -500,7 +500,7 @@ Item { Column { id: displayFormatColumn - visible: !CompositorService.isDwl && !CompositorService.isMango + visible: !CompositorService.isMango spacing: Theme.spacingXS anchors.verticalCenter: parent.verticalCenter diff --git a/quickshell/Modules/Settings/DockTab.qml b/quickshell/Modules/Settings/DockTab.qml index 6d91880f..d60f8b16 100644 --- a/quickshell/Modules/Settings/DockTab.qml +++ b/quickshell/Modules/Settings/DockTab.qml @@ -282,8 +282,6 @@ Item { modes.push("niri"); } else if (CompositorService.isHyprland) { modes.push("Hyprland"); - } else if (CompositorService.isDwl) { - modes.push("mango"); } else if (CompositorService.isMango) { modes.push("mango"); } else if (CompositorService.isSway) { diff --git a/quickshell/Modules/Settings/LauncherTab.qml b/quickshell/Modules/Settings/LauncherTab.qml index 1671c79b..9f22d827 100644 --- a/quickshell/Modules/Settings/LauncherTab.qml +++ b/quickshell/Modules/Settings/LauncherTab.qml @@ -304,8 +304,6 @@ Item { modes.push("niri"); } else if (CompositorService.isHyprland) { modes.push("Hyprland"); - } else if (CompositorService.isDwl) { - modes.push("mango"); } else if (CompositorService.isMango) { modes.push("mango"); } else if (CompositorService.isSway) { diff --git a/quickshell/Modules/Settings/ThemeColorsTab.qml b/quickshell/Modules/Settings/ThemeColorsTab.qml index beae61c6..0a644b38 100644 --- a/quickshell/Modules/Settings/ThemeColorsTab.qml +++ b/quickshell/Modules/Settings/ThemeColorsTab.qml @@ -48,7 +48,6 @@ Item { "grepPattern": "dms.cursor", "includeLine": "require(\"dms.cursor\")" }; - case "dwl": case "mango": return { "configFile": configDir + "/mango/config.conf", @@ -63,7 +62,7 @@ Item { function checkCursorIncludeStatus() { const compositor = CompositorService.compositor; - if (compositor !== "niri" && compositor !== "hyprland" && compositor !== "dwl" && compositor !== "mango") { + if (compositor !== "niri" && compositor !== "hyprland" && compositor !== "mango") { cursorIncludeStatus = { "exists": false, "included": false, @@ -74,7 +73,7 @@ Item { } const filename = (compositor === "niri") ? "cursor.kdl" : ((compositor === "hyprland") ? "cursor.lua" : "cursor.conf"); - const compositorArg = (compositor === "dwl" || compositor === "mango") ? "mangowc" : compositor; + const compositorArg = (compositor === "mango") ? "mangowc" : compositor; checkingCursorInclude = true; Proc.runCommand("check-cursor-include", ["dms", "config", "resolve-include", compositorArg, filename], (output, exitCode) => { @@ -194,7 +193,7 @@ Item { themeColorsTab.templateDetection = JSON.parse(output.trim()); } catch (e) {} }); - if (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango) + if (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango) checkCursorIncludeStatus(); } @@ -2016,7 +2015,7 @@ Item { title: I18n.tr("Cursor Theme") settingKey: "cursorTheme" iconName: "mouse" - visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango + visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango Column { width: parent.width @@ -2181,8 +2180,6 @@ Item { return SettingsData.cursorSettings.niri?.hideAfterInactiveMs || 0; if (CompositorService.isHyprland) return SettingsData.cursorSettings.hyprland?.inactiveTimeout || 0; - if (CompositorService.isDwl) - return SettingsData.cursorSettings.dwl?.cursorHideTimeout || 0; if (CompositorService.isMango) return SettingsData.cursorSettings.mango?.cursorHideTimeout || 0; return 0; @@ -2201,10 +2198,6 @@ Item { if (!updated.hyprland) updated.hyprland = {}; updated.hyprland.inactiveTimeout = newValue; - } else if (CompositorService.isDwl) { - if (!updated.dwl) - updated.dwl = {}; - updated.dwl.cursorHideTimeout = newValue; } else if (CompositorService.isMango) { if (!updated.mango) updated.mango = {}; diff --git a/quickshell/Modules/Settings/WidgetsTab.qml b/quickshell/Modules/Settings/WidgetsTab.qml index df072047..26058ace 100644 --- a/quickshell/Modules/Settings/WidgetsTab.qml +++ b/quickshell/Modules/Settings/WidgetsTab.qml @@ -37,10 +37,10 @@ Item { { "id": "layout", "text": I18n.tr("Layout"), - "description": I18n.tr("Display and switch DWL layouts"), + "description": I18n.tr("Display and switch MangoWC layouts"), "icon": "view_quilt", - "enabled": (CompositorService.isDwl && DwlService.dwlAvailable) || (CompositorService.isMango && MangoService.available), - "warning": CompositorService.isMango ? (!MangoService.available ? I18n.tr("DWL service not available") : undefined) : (!CompositorService.isDwl ? I18n.tr("Requires DWL compositor") : (!DwlService.dwlAvailable ? I18n.tr("DWL service not available") : undefined)) + "enabled": CompositorService.isMango && MangoService.available, + "warning": !CompositorService.isMango ? I18n.tr("Requires MangoWC compositor") : (!MangoService.available ? I18n.tr("Mango service not available") : undefined) }, { "id": "launcherButton", diff --git a/quickshell/Modules/Settings/WorkspaceAppearanceCard.qml b/quickshell/Modules/Settings/WorkspaceAppearanceCard.qml index 8f0e3e44..d0a99d33 100644 --- a/quickshell/Modules/Settings/WorkspaceAppearanceCard.qml +++ b/quickshell/Modules/Settings/WorkspaceAppearanceCard.qml @@ -51,7 +51,7 @@ SettingsCard { SettingsButtonGroupRow { text: I18n.tr("Occupied Color") model: ["none", "sec", "s", "sc", "sch", "schh"] - visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango + visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango buttonHeight: 22 minButtonWidth: 36 buttonPadding: Theme.spacingS @@ -87,7 +87,7 @@ SettingsCard { height: 1 color: Theme.outline opacity: 0.15 - visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango + visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango } SettingsButtonGroupRow { @@ -124,12 +124,12 @@ SettingsCard { height: 1 color: Theme.outline opacity: 0.15 - visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle + visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle } SettingsButtonGroupRow { text: I18n.tr("Urgent Color") - visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle + visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle model: ["err", "pri", "sec", "s", "sc"] buttonHeight: 22 minButtonWidth: 36 diff --git a/quickshell/Modules/Settings/WorkspacesTab.qml b/quickshell/Modules/Settings/WorkspacesTab.qml index 2582825d..e439d6a9 100644 --- a/quickshell/Modules/Settings/WorkspacesTab.qml +++ b/quickshell/Modules/Settings/WorkspacesTab.qml @@ -153,7 +153,7 @@ Item { text: I18n.tr("Follow Monitor Focus") description: I18n.tr("Show workspaces of the currently focused monitor") checked: SettingsData.workspaceFollowFocus - visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle + visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle onToggled: checked => SettingsData.set("workspaceFollowFocus", checked) } @@ -193,7 +193,7 @@ Item { text: I18n.tr("Show All Tags") description: I18n.tr("Show all 9 tags instead of only occupied tags") checked: SettingsData.dwlShowAllTags - visible: CompositorService.isDwl || CompositorService.isMango + visible: CompositorService.isMango onToggled: checked => SettingsData.set("dwlShowAllTags", checked) } } diff --git a/quickshell/Services/CompositorService.qml b/quickshell/Services/CompositorService.qml index 4dfd209d..f93cb041 100644 --- a/quickshell/Services/CompositorService.qml +++ b/quickshell/Services/CompositorService.qml @@ -15,7 +15,6 @@ Singleton { property bool isHyprland: false property bool isNiri: false - property bool isDwl: false property bool isMango: false property bool isSway: false property bool isScroll: false @@ -97,12 +96,6 @@ Singleton { return hyprlandMonitor.scale; } - if (isDwl && screen) { - const dwlScale = DwlService.getOutputScale(screen.name); - if (dwlScale !== undefined && dwlScale > 0) - return dwlScale; - } - if (isMango && screen) { const mangoScale = MangoService.getOutputScale(screen.name); if (mangoScale !== undefined && mangoScale > 0) @@ -121,9 +114,7 @@ Singleton { else if (isSway || isScroll || isMiracle) { const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true); screenName = focusedWs?.monitor?.name || ""; - } else if (isDwl && DwlService.activeOutput) - screenName = DwlService.activeOutput; - else if (isMango && MangoService.activeOutput) + } else if (isMango && MangoService.activeOutput) screenName = MangoService.activeOutput; if (!screenName) @@ -192,19 +183,9 @@ Singleton { Qt.callLater(() => { NiriService.generateNiriLayoutConfig(); HyprlandService.generateLayoutConfig(); - DwlService.generateLayoutConfig(); }); } - Connections { - target: DwlService - function onStateChanged() { - if (isDwl && !isHyprland && !isNiri) { - scheduleSort(); - } - } - } - Connections { target: MangoService function onStateChanged() { @@ -271,13 +252,7 @@ Singleton { function _specialWorkspaceNameFromMonitor(monitor) { if (!monitor) return ""; - const candidates = [ - monitor.activeSpecialWorkspace?.name, - monitor.specialWorkspace?.name, - monitor.lastIpcObject?.specialWorkspace?.name, - monitor.lastIpcObject?.specialWorkspace, - monitor.lastIpcObject?.activeSpecialWorkspace?.name - ]; + const candidates = [monitor.activeSpecialWorkspace?.name, monitor.specialWorkspace?.name, monitor.lastIpcObject?.specialWorkspace?.name, monitor.lastIpcObject?.specialWorkspace, monitor.lastIpcObject?.activeSpecialWorkspace?.name]; for (let i = 0; i < candidates.length; i++) { const normalized = _normalizeSpecialWorkspaceName(candidates[i]); if (normalized) @@ -860,7 +835,6 @@ Singleton { Qt.callLater(() => { NiriService.generateNiriLayoutConfig(); HyprlandService.generateLayoutConfig(); - DwlService.generateLayoutConfig(); MangoService.generateLayoutConfig(); }); } @@ -870,7 +844,6 @@ Singleton { if (mangoSignature && mangoSignature.length > 0) { isHyprland = false; isNiri = false; - isDwl = false; isMango = true; isSway = false; isScroll = false; @@ -884,7 +857,6 @@ Singleton { if (hyprlandSignature && hyprlandSignature.length > 0 && !niriSocket && !swaySocket && !scrollSocket && !miracleSocket && !labwcPid) { isHyprland = true; isNiri = false; - isDwl = false; isMango = false; isSway = false; isScroll = false; @@ -900,7 +872,6 @@ Singleton { if (exitCode === 0) { isNiri = true; isHyprland = false; - isDwl = false; isMango = false; isSway = false; isScroll = false; @@ -919,7 +890,6 @@ Singleton { if (exitCode === 0) { isNiri = false; isHyprland = false; - isDwl = false; isSway = true; isScroll = false; isMiracle = false; @@ -936,7 +906,6 @@ Singleton { if (exitCode === 0) { isNiri = false; isHyprland = false; - isDwl = false; isMango = false; isSway = false; isScroll = false; @@ -954,7 +923,6 @@ Singleton { if (exitCode === 0) { isNiri = false; isHyprland = false; - isDwl = false; isMango = false; isSway = false; isScroll = true; @@ -970,7 +938,6 @@ Singleton { if (labwcPid && labwcPid.length > 0) { isHyprland = false; isNiri = false; - isDwl = false; isMango = false; isSway = false; isScroll = false; @@ -981,45 +948,15 @@ Singleton { return; } - if (DMSService.dmsAvailable) { - Qt.callLater(checkForDwl); - } else { - isHyprland = false; - isNiri = false; - isDwl = false; - isMango = false; - isSway = false; - isScroll = false; - isMiracle = false; - isLabwc = false; - compositor = "unknown"; - log.warn("No compositor detected"); - } - } - - Connections { - target: DMSService - function onCapabilitiesReceived() { - if (!isHyprland && !isNiri && !isDwl && !isMango && !isLabwc) { - checkForDwl(); - } - } - } - - function checkForDwl() { - if (isMango) - return; - if (DMSService.apiVersion >= 12 && DMSService.capabilities.includes("dwl")) { - isHyprland = false; - isNiri = false; - isDwl = true; - isSway = false; - isScroll = false; - isMiracle = false; - isLabwc = false; - compositor = "dwl"; - log.info("Detected DWL via DMS capability"); - } + isHyprland = false; + isNiri = false; + isMango = false; + isSway = false; + isScroll = false; + isMiracle = false; + isLabwc = false; + compositor = "unknown"; + log.warn("No compositor detected"); } function powerOffMonitors() { @@ -1027,8 +964,6 @@ Singleton { return NiriService.powerOffMonitors(); if (isHyprland) return HyprlandService.dpmsOff(); - if (isDwl) - return _dwlPowerOffMonitors(); if (isMango) return MangoService.powerOffMonitors(); if (isSway || isScroll || isMiracle) { @@ -1048,8 +983,6 @@ Singleton { return NiriService.powerOnMonitors(); if (isHyprland) return HyprlandService.dpmsOn(); - if (isDwl) - return _dwlPowerOnMonitors(); if (isMango) return MangoService.powerOnMonitors(); if (isSway || isScroll || isMiracle) { @@ -1063,32 +996,4 @@ Singleton { } log.warn("Cannot power on monitors, unknown compositor"); } - - function _dwlPowerOffMonitors() { - if (!Quickshell.screens || Quickshell.screens.length === 0) { - log.warn("No screens available for DWL power off"); - return; - } - - for (let i = 0; i < Quickshell.screens.length; i++) { - const screen = Quickshell.screens[i]; - if (screen && screen.name) { - Quickshell.execDetached(["mmsg", "dispatch", "disable_monitor," + screen.name]); - } - } - } - - function _dwlPowerOnMonitors() { - if (!Quickshell.screens || Quickshell.screens.length === 0) { - log.warn("No screens available for DWL power on"); - return; - } - - for (let i = 0; i < Quickshell.screens.length; i++) { - const screen = Quickshell.screens[i]; - if (screen && screen.name) { - Quickshell.execDetached(["mmsg", "dispatch", "enable_monitor," + screen.name]); - } - } - } } diff --git a/quickshell/Services/DMSService.qml b/quickshell/Services/DMSService.qml index 35ae37c0..a3091478 100644 --- a/quickshell/Services/DMSService.qml +++ b/quickshell/Services/DMSService.qml @@ -49,7 +49,6 @@ Singleton { signal capabilitiesReceived signal credentialsRequest(var data) signal bluetoothPairingRequest(var data) - signal dwlStateUpdate(var data) signal brightnessStateUpdate(var data) signal brightnessDeviceUpdate(var device) signal wlrOutputStateUpdate(var data) @@ -68,7 +67,7 @@ Singleton { property bool screensaverInhibited: false property var screensaverInhibitors: [] - property var activeSubscriptions: ["network", "network.credentials", "loginctl", "freedesktop", "freedesktop.screensaver", "gamma", "theme.auto", "bluetooth", "bluetooth.pairing", "dwl", "brightness", "wlroutput", "evdev", "browser", "dbus", "clipboard", "location", "sysupdate"] + property var activeSubscriptions: ["network", "network.credentials", "loginctl", "freedesktop", "freedesktop.screensaver", "gamma", "theme.auto", "bluetooth", "bluetooth.pairing", "brightness", "wlroutput", "evdev", "browser", "dbus", "clipboard", "location", "sysupdate"] Component.onCompleted: { if (socketPath && socketPath.length > 0) { @@ -286,7 +285,7 @@ Singleton { function removeSubscription(service) { if (activeSubscriptions.includes("all")) { - const allServices = ["network", "loginctl", "freedesktop", "gamma", "bluetooth", "dwl", "brightness", "browser", "location"]; + const allServices = ["network", "loginctl", "freedesktop", "gamma", "bluetooth", "brightness", "browser", "location"]; const filtered = allServices.filter(s => s !== service); subscribe(filtered); } else { @@ -308,7 +307,7 @@ Singleton { excludeServices = [excludeServices]; } - const allServices = ["network", "loginctl", "freedesktop", "gamma", "theme.auto", "bluetooth", "cups", "dwl", "brightness", "browser", "dbus", "location"]; + const allServices = ["network", "loginctl", "freedesktop", "gamma", "theme.auto", "bluetooth", "cups", "brightness", "browser", "dbus", "location"]; const filtered = allServices.filter(s => !excludeServices.includes(s)); subscribe(filtered); } @@ -354,8 +353,6 @@ Singleton { bluetoothPairingRequest(data); } else if (service === "cups") { cupsStateUpdate(data); - } else if (service === "dwl") { - dwlStateUpdate(data); } else if (service === "brightness") { brightnessStateUpdate(data); } else if (service === "brightness.update") { diff --git a/quickshell/Services/DwlService.qml b/quickshell/Services/DwlService.qml deleted file mode 100644 index f9023b4a..00000000 --- a/quickshell/Services/DwlService.qml +++ /dev/null @@ -1,461 +0,0 @@ -pragma Singleton -pragma ComponentBehavior: Bound - -import QtCore -import QtQuick -import Quickshell -import Quickshell.Io -import qs.Common -import qs.Services - -Singleton { - id: root - readonly property var log: Log.scoped("DwlService") - - readonly property string configDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation)) - readonly property string mangoDmsDir: configDir + "/mango/dms" - readonly property string outputsPath: mangoDmsDir + "/outputs.conf" - readonly property string layoutPath: mangoDmsDir + "/layout.conf" - readonly property string cursorPath: mangoDmsDir + "/cursor.conf" - - property int _lastGapValue: -1 - - property bool dwlAvailable: false - // Alias so consumers can treat DwlService/MangoService uniformly via `.available`. - readonly property bool available: dwlAvailable - property var outputs: ({}) - property var tagCount: 9 - property var layouts: [] - property string activeOutput: "" - property var outputScales: ({}) - property string currentKeyboardLayout: { - if (!outputs || !activeOutput) - return ""; - const output = outputs[activeOutput]; - return (output && output.kbLayout) || ""; - } - - signal stateChanged - - Connections { - target: SettingsData - function onBarConfigsChanged() { - if (!CompositorService.isDwl) - return; - const newGaps = Math.max(4, (SettingsData.barConfigs[0]?.spacing ?? 4)); - if (newGaps === root._lastGapValue) - return; - root._lastGapValue = newGaps; - generateLayoutConfig(); - } - } - - Connections { - target: CompositorService - function onIsDwlChanged() { - if (CompositorService.isDwl) - generateLayoutConfig(); - } - } - - Connections { - target: DMSService - function onCapabilitiesReceived() { - checkCapabilities(); - } - function onConnectionStateChanged() { - if (DMSService.isConnected) { - checkCapabilities(); - } else { - dwlAvailable = false; - } - } - function onDwlStateUpdate(data) { - if (dwlAvailable) { - handleStateUpdate(data); - } - } - } - - Component.onCompleted: { - if (DMSService.dmsAvailable) - checkCapabilities(); - if (dwlAvailable) - refreshOutputScales(); - if (CompositorService.isDwl) - Qt.callLater(generateLayoutConfig); - } - - function checkCapabilities() { - if (!DMSService.capabilities || !Array.isArray(DMSService.capabilities)) { - dwlAvailable = false; - return; - } - - const hasDwl = DMSService.capabilities.includes("dwl"); - if (hasDwl && !dwlAvailable) { - dwlAvailable = true; - log.info("DWL capability detected"); - requestState(); - refreshOutputScales(); - } else if (!hasDwl) { - dwlAvailable = false; - } - } - - function requestState() { - if (!DMSService.isConnected || !dwlAvailable) { - return; - } - - DMSService.sendRequest("dwl.getState", null, response => { - if (response.result) { - handleStateUpdate(response.result); - } - }); - } - - function handleStateUpdate(state) { - outputs = state.outputs || {}; - tagCount = state.tagCount || 9; - layouts = state.layouts || []; - activeOutput = state.activeOutput || ""; - stateChanged(); - } - - function setTags(outputName, tagmask, toggleTagset) { - if (!DMSService.isConnected || !dwlAvailable) { - return; - } - - DMSService.sendRequest("dwl.setTags", { - "output": outputName, - "tagmask": tagmask, - "toggleTagset": toggleTagset - }, response => { - if (response.error) { - log.warn("setTags error:", response.error); - } - }); - } - - function setClientTags(outputName, andTags, xorTags) { - if (!DMSService.isConnected || !dwlAvailable) { - return; - } - - DMSService.sendRequest("dwl.setClientTags", { - "output": outputName, - "andTags": andTags, - "xorTags": xorTags - }, response => { - if (response.error) { - log.warn("setClientTags error:", response.error); - } - }); - } - - function setLayout(outputName, index) { - if (!DMSService.isConnected || !dwlAvailable) { - return; - } - - DMSService.sendRequest("dwl.setLayout", { - "output": outputName, - "index": index - }, response => { - if (response.error) { - log.warn("setLayout error:", response.error); - } - }); - } - - function getOutputState(outputName) { - if (!outputs || !outputs[outputName]) { - return null; - } - return outputs[outputName]; - } - - function getActiveTags(outputName) { - const output = getOutputState(outputName); - if (!output || !output.tags) { - return []; - } - return output.tags.filter(tag => tag.state === 1).map(tag => tag.tag); - } - - function getTagsWithClients(outputName) { - const output = getOutputState(outputName); - if (!output || !output.tags) { - return []; - } - return output.tags.filter(tag => tag.clients > 0).map(tag => tag.tag); - } - - function getUrgentTags(outputName) { - const output = getOutputState(outputName); - if (!output || !output.tags) { - return []; - } - return output.tags.filter(tag => tag.state === 2).map(tag => tag.tag); - } - - function switchToTag(outputName, tagIndex) { - const tagmask = 1 << tagIndex; - setTags(outputName, tagmask, 0); - } - - function toggleTag(outputName, tagIndex) { - const output = getOutputState(outputName); - if (!output || !output.tags) { - log.debug("toggleTag: no output or tags for", outputName); - return; - } - - let currentMask = 0; - output.tags.forEach(tag => { - if (tag.state === 1) { - currentMask |= (1 << tag.tag); - } - }); - - const clickedMask = 1 << tagIndex; - const newMask = currentMask ^ clickedMask; - - log.debug("toggleTag:", outputName, "tag:", tagIndex, "currentMask:", currentMask.toString(2), "clickedMask:", clickedMask.toString(2), "newMask:", newMask.toString(2)); - - if (newMask === 0) { - log.debug("toggleTag: newMask is 0, switching to tag", tagIndex); - setTags(outputName, 1 << tagIndex, 0); - } else { - log.debug("toggleTag: setting combined mask", newMask); - setTags(outputName, newMask, 0); - } - } - - function quit() { - Quickshell.execDetached(["mmsg", "dispatch", "quit"]); - } - - Process { - id: scaleQueryProcess - command: ["mmsg", "get", "all-monitors"] - running: false - - stdout: StdioCollector { - onStreamFinished: { - try { - const newScales = {}; - const data = JSON.parse(text.trim()); - const monitors = data.monitors || []; - for (const mon of monitors) { - if (mon.name && typeof mon.scale === "number" && mon.scale > 0) { - newScales[mon.name] = mon.scale; - } - } - outputScales = newScales; - } catch (e) { - log.warn("Failed to parse mmsg output:", e); - } - } - } - - onExited: exitCode => { - if (exitCode !== 0) { - log.warn("mmsg failed with exit code:", exitCode); - } - } - } - - function refreshOutputScales() { - if (!dwlAvailable) - return; - scaleQueryProcess.running = true; - } - - function getOutputScale(outputName) { - return outputScales[outputName]; - } - - function getVisibleTags(outputName) { - const output = getOutputState(outputName); - if (!output || !output.tags) { - return []; - } - - const visibleTags = new Set(); - - output.tags.forEach(tag => { - if (tag.state === 1 || tag.clients > 0) { - visibleTags.add(tag.tag); - } - }); - - return Array.from(visibleTags).sort((a, b) => a - b); - } - - function generateOutputsConfig(outputsData, callback) { - if (!outputsData || Object.keys(outputsData).length === 0) { - if (callback) - callback(false); - return; - } - let lines = ["# Auto-generated by DMS - do not edit manually", ""]; - - for (const outputName in outputsData) { - const output = outputsData[outputName]; - if (!output) - continue; - let width = 1920; - let height = 1080; - let refreshRate = 60; - if (output.modes && output.current_mode !== undefined) { - const mode = output.modes[output.current_mode]; - if (mode) { - width = mode.width || 1920; - height = mode.height || 1080; - refreshRate = Math.round((mode.refresh_rate || 60000) / 1000); - } - } - - const x = output.logical?.x ?? 0; - const y = output.logical?.y ?? 0; - const scale = output.logical?.scale ?? 1.0; - const transform = transformToMango(output.logical?.transform ?? "Normal"); - const vrr = output.vrr_enabled ? 1 : 0; - - const rule = ["name:^" + outputName + "$", "width:" + width, "height:" + height, "refresh:" + refreshRate, "x:" + x, "y:" + y, "scale:" + scale, "rr:" + transform, "vrr:" + vrr].join(","); - - lines.push("monitorrule=" + rule); - } - - lines.push(""); - - const content = lines.join("\n"); - - Proc.runCommand("mango-write-outputs", ["sh", "-c", `mkdir -p "${mangoDmsDir}" && cat > "${outputsPath}" << 'EOF'\n${content}EOF`], (output, exitCode) => { - if (exitCode !== 0) { - log.warn("Failed to write outputs config:", output); - if (callback) - callback(false); - return; - } - log.info("Generated outputs config at", outputsPath); - if (CompositorService.isDwl) - reloadConfig(); - if (callback) - callback(true); - }); - } - - function reloadConfig() { - Proc.runCommand("mango-reload", ["mmsg", "dispatch", "reload_config"], (output, exitCode) => { - if (exitCode !== 0) - log.warn("mmsg reload_config failed:", output); - }); - } - - function generateLayoutConfig() { - if (!CompositorService.isDwl) - return; - - const defaultRadius = typeof SettingsData !== "undefined" ? SettingsData.cornerRadius : 12; - const defaultGaps = typeof SettingsData !== "undefined" ? Math.max(4, (SettingsData.barConfigs[0]?.spacing ?? 4)) : 4; - const defaultBorderSize = 2; - - const cornerRadius = (typeof SettingsData !== "undefined" && SettingsData.mangoLayoutRadiusOverride >= 0) ? SettingsData.mangoLayoutRadiusOverride : defaultRadius; - const gaps = (typeof SettingsData !== "undefined" && SettingsData.mangoLayoutGapsOverride >= 0) ? SettingsData.mangoLayoutGapsOverride : defaultGaps; - const borderSize = (typeof SettingsData !== "undefined" && SettingsData.mangoLayoutBorderSize >= 0) ? SettingsData.mangoLayoutBorderSize : defaultBorderSize; - - let content = `# Auto-generated by DMS - do not edit manually -border_radius=${cornerRadius} -gappih=${gaps} -gappiv=${gaps} -gappoh=${gaps} -gappov=${gaps} -borderpx=${borderSize} -`; - - Proc.runCommand("mango-write-layout", ["sh", "-c", `mkdir -p "${mangoDmsDir}" && cat > "${layoutPath}" << 'EOF'\n${content}EOF`], (output, exitCode) => { - if (exitCode !== 0) { - log.warn("Failed to write layout config:", output); - return; - } - log.info("Generated layout config at", layoutPath); - reloadConfig(); - }); - } - - function transformToMango(transform) { - switch (transform) { - case "Normal": - return 0; - case "90": - return 1; - case "180": - return 2; - case "270": - return 3; - case "Flipped": - return 4; - case "Flipped90": - return 5; - case "Flipped180": - return 6; - case "Flipped270": - return 7; - default: - return 0; - } - } - - function generateCursorConfig() { - if (!CompositorService.isDwl) - return; - - log.debug("Generating cursor config..."); - - const settings = typeof SettingsData !== "undefined" ? SettingsData.cursorSettings : null; - if (!settings) { - Proc.runCommand("mango-write-cursor", ["sh", "-c", `mkdir -p "${mangoDmsDir}" && : > "${cursorPath}"`], (output, exitCode) => { - if (exitCode !== 0) - log.warn("Failed to write cursor config:", output); - }); - return; - } - - const themeName = settings.theme === "System Default" ? (SettingsData.systemDefaultCursorTheme || "") : settings.theme; - const size = settings.size || 24; - const hideTimeout = settings.dwl?.cursorHideTimeout || 0; - - const isDefaultConfig = !themeName && size === 24 && hideTimeout === 0; - if (isDefaultConfig) { - Proc.runCommand("mango-write-cursor", ["sh", "-c", `mkdir -p "${mangoDmsDir}" && : > "${cursorPath}"`], (output, exitCode) => { - if (exitCode !== 0) - log.warn("Failed to write cursor config:", output); - }); - return; - } - - let content = `# Auto-generated by DMS - do not edit manually -cursor_size=${size}`; - - if (themeName) - content += `\ncursor_theme=${themeName}`; - - if (hideTimeout > 0) - content += `\ncursor_hide_timeout=${hideTimeout}`; - - content += `\n`; - - Proc.runCommand("mango-write-cursor", ["sh", "-c", `mkdir -p "${mangoDmsDir}" && cat > "${cursorPath}" << 'EOF'\n${content}EOF`], (output, exitCode) => { - if (exitCode !== 0) { - log.warn("Failed to write cursor config:", output); - return; - } - log.info("Generated cursor config at", cursorPath); - reloadConfig(); - }); - } -} diff --git a/quickshell/Services/KeybindsService.qml b/quickshell/Services/KeybindsService.qml index ca6cb162..f0a8e94e 100644 --- a/quickshell/Services/KeybindsService.qml +++ b/quickshell/Services/KeybindsService.qml @@ -14,13 +14,13 @@ Singleton { id: root readonly property var log: Log.scoped("KeybindsService") - property bool available: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango + property bool available: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango property string currentProvider: { if (CompositorService.isNiri) return "niri"; if (CompositorService.isHyprland) return "hyprland"; - if (CompositorService.isDwl || CompositorService.isMango) + if (CompositorService.isMango) return "mangowc"; return ""; } @@ -30,7 +30,7 @@ Singleton { return "niri"; if (CompositorService.isHyprland) return "hyprland"; - if (CompositorService.isDwl || CompositorService.isMango) + if (CompositorService.isMango) return "mangowc"; return ""; } @@ -290,13 +290,16 @@ Singleton { configFile: mainConfigPath, backupFile: backupPath, fragmentFiles: [compositorConfigDir + "/dms/binds.lua", compositorConfigDir + "/dms/binds-user.lua"], - includes: [{ + includes: [ + { grepPattern: "dms.binds", includeLine: "require(\"dms.binds\")" - }, { + }, + { grepPattern: "dms.binds-user", includeLine: "require(\"dms.binds-user\")" - }] + } + ] }); break; case "mangowc": diff --git a/quickshell/Services/MangoService.qml b/quickshell/Services/MangoService.qml index bcc796a4..795ab00b 100644 --- a/quickshell/Services/MangoService.qml +++ b/quickshell/Services/MangoService.qml @@ -10,9 +10,8 @@ import qs.Services // Native MangoWM IPC client. mango advertises a JSON-over-Unix-socket protocol // via MANGO_INSTANCE_SIGNATURE; each connection issues one `watch ` verb -// and gets a full JSON snapshot followed by newline-delimited updates. Replaces -// the legacy dwl-ipc-v2 path (DwlService) for mango, exposing a -// DwlService-compatible tag API plus a per-client window list. +// and gets a full JSON snapshot followed by newline-delimited updates. Exposes +// a dwl-style tag API plus a per-client window list. Singleton { id: root readonly property var log: Log.scoped("MangoService") @@ -219,7 +218,7 @@ Singleton { root.windows = data.clients; } - // ── DwlService-compatible tag API ────────────────────────────────────── + // ── Tag API (dwl-style tag model) ────────────────────────────────────── function getOutputState(outputName) { return (outputs && outputs[outputName]) ? outputs[outputName] : null; diff --git a/quickshell/Services/SessionService.qml b/quickshell/Services/SessionService.qml index b29b42fc..83c78965 100644 --- a/quickshell/Services/SessionService.qml +++ b/quickshell/Services/SessionService.qml @@ -4,7 +4,6 @@ pragma ComponentBehavior: Bound import QtQuick import Quickshell import Quickshell.Io -import Quickshell.Hyprland import Quickshell.I3 import qs.Common import qs.Services @@ -314,11 +313,6 @@ Singleton { return; } - if (CompositorService.isDwl) { - DwlService.quit(); - return; - } - if (CompositorService.isMango) { MangoService.quit(); return; diff --git a/quickshell/Services/SettingsSearchService.qml b/quickshell/Services/SettingsSearchService.qml index b1287a57..e031ada4 100644 --- a/quickshell/Services/SettingsSearchService.qml +++ b/quickshell/Services/SettingsSearchService.qml @@ -35,7 +35,6 @@ Singleton { readonly property var conditionMap: ({ "isNiri": () => CompositorService.isNiri, "isHyprland": () => CompositorService.isHyprland, - "isDwl": () => CompositorService.isDwl, "isMango": () => CompositorService.isMango, "keybindsAvailable": () => KeybindsService.available, "soundsAvailable": () => AudioService.soundsAvailable, diff --git a/quickshell/Services/WlrOutputService.qml b/quickshell/Services/WlrOutputService.qml index 270b2de4..0d1b11c5 100644 --- a/quickshell/Services/WlrOutputService.qml +++ b/quickshell/Services/WlrOutputService.qml @@ -279,7 +279,7 @@ Singleton { } // High-level apply matching the generateOutputsConfig() pattern used by - // NiriService, HyprlandService and DwlService. Instead of writing a + // NiriService, HyprlandService and MangoService. Instead of writing a // config file, the changes are applied directly via the // wlr-output-management protocol. function applyOutputsConfig(outputsData, connectedOutputs) { diff --git a/scripts/format-staged.py b/scripts/format-staged.py index dd989450..b36cca81 100755 --- a/scripts/format-staged.py +++ b/scripts/format-staged.py @@ -195,6 +195,23 @@ def apply_edits(text, edits): return text +def start_client(qmlls, root): + client = LspClient([qmlls]) + client.request("initialize", { + "processId": os.getpid(), + "rootUri": root.as_uri(), + "workspaceFolders": [{"uri": root.as_uri(), "name": root.name}], + "capabilities": { + "textDocument": { + "formatting": {"dynamicRegistration": False}, + "synchronization": {"dynamicRegistration": False}, + }, + }, + }) + client.notify("initialized", {}) + return client + + def main(): root = repo_root() files = staged_qml_files(root) @@ -219,24 +236,11 @@ def main(): if not qmllint: print("warning: qmllint with --json not found; skipping unused-import checks", file=sys.stderr) - client = LspClient([qmlls]) + client = start_client(qmlls, root) changed = 0 skipped = 0 unused_by_file = {} try: - client.request("initialize", { - "processId": os.getpid(), - "rootUri": root.as_uri(), - "workspaceFolders": [{"uri": root.as_uri(), "name": root.name}], - "capabilities": { - "textDocument": { - "formatting": {"dynamicRegistration": False}, - "synchronization": {"dynamicRegistration": False}, - }, - }, - }) - client.notify("initialized", {}) - for file in files: rel = file.relative_to(root) print(f" {rel} ... ", end="", flush=True) @@ -258,16 +262,21 @@ def main(): "textDocument": {"uri": uri}, "options": {"tabSize": TAB_SIZE, "insertSpaces": True}, }) - except RuntimeError as exc: - # qmlls (qmlformat's DOM) refuses some valid files — notably - # "Cannot format invalid documents!" on constructs qmllint - # accepts. Don't let one file's formatter bug abort the commit. + except (RuntimeError, OSError) as exc: + # qmlls (qmlformat's DOM) chokes on some valid files: it refuses + # them ("Cannot format invalid documents!") or outright crashes + # (e.g. SIGABRT on a function declaration inside a property + # binding). Don't let one file's formatter bug abort the commit. + skipped += 1 + if client.proc.poll() is not None: + print("skipped (qmlls crashed on this file; restarting it)") + client = start_client(qmlls, root) + continue client.notify("textDocument/didClose", {"textDocument": {"uri": uri}}) if "invalid document" in str(exc).lower(): - print("skipped (qmlls rejected as invalid;") + print("skipped (qmlls rejected as invalid)") else: print(f"skipped ({exc})") - skipped += 1 continue client.notify("textDocument/didClose", {"textDocument": {"uri": uri}})