1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-05 21:15:38 -05:00

dwl: add dwl/MangoWC support

- Requires dms api v12
- Tags/Workspace support
- MangoWC launcher logo
- dpms off/on support
- logout support
This commit is contained in:
bbedward
2025-10-29 12:39:31 -04:00
parent 76b168020c
commit aede6b064a
14 changed files with 408 additions and 34 deletions

View File

@@ -24,6 +24,7 @@ assignees: ""
- [ ] niri - [ ] niri
- [ ] Hyprland - [ ] Hyprland
- [ ] dwl (MangoWC)
- [ ] Other (specify) - [ ] Other (specify)
## Distribution ## Distribution

View File

@@ -21,6 +21,7 @@ Is this feature specific to one compositor?
- [ ] All compositors - [ ] All compositors
- [ ] niri - [ ] niri
- [ ] Hyprland - [ ] Hyprland
- [ ] dwl (MangoWC)
## Proposed Solution ## Proposed Solution

View File

@@ -10,6 +10,7 @@ assignees: ""
- [ ] niri - [ ] niri
- [ ] Hyprland - [ ] Hyprland
- [ ] dwl (MangoWC)
- [ ] other - [ ] other
## Distribution ## Distribution

View File

@@ -580,6 +580,11 @@ Item {
} }
return monitorWorkspaces.sort((a, b) => a.id - b.id) return monitorWorkspaces.sort((a, b) => a.id - b.id)
} else if (CompositorService.isDwl) {
if (!DwlService.dwlAvailable || DwlService.tagCount === 0) {
return Array.from({length: 9}, (_, i) => i)
}
return Array.from({length: DwlService.tagCount}, (_, i) => i)
} }
return [1] return [1]
} }
@@ -595,6 +600,12 @@ Item {
const monitors = Hyprland.monitors?.values || [] const monitors = Hyprland.monitors?.values || []
const currentMonitor = monitors.find(monitor => monitor.name === barWindow.screenName) const currentMonitor = monitors.find(monitor => monitor.name === barWindow.screenName)
return currentMonitor?.activeWorkspace?.id ?? 1 return currentMonitor?.activeWorkspace?.id ?? 1
} else if (CompositorService.isDwl) {
if (!DwlService.dwlAvailable) return 0
const outputState = DwlService.getOutputState(barWindow.screenName)
if (!outputState || !outputState.tags) return 0
const activeTags = DwlService.getActiveTags(barWindow.screenName)
return activeTags.length > 0 ? activeTags[0] : 0
} }
return 1 return 1
} }
@@ -623,6 +634,15 @@ Item {
if (nextIndex !== validIndex) { if (nextIndex !== validIndex) {
Hyprland.dispatch(`workspace ${realWorkspaces[nextIndex].id}`) Hyprland.dispatch(`workspace ${realWorkspaces[nextIndex].id}`)
} }
} else if (CompositorService.isDwl) {
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) {
DwlService.switchToTag(barWindow.screenName, realWorkspaces[nextIndex])
}
} }
} }

View File

@@ -37,7 +37,7 @@ BasePill {
} }
IconImage { IconImage {
visible: SettingsData.launcherLogoMode === "compositor" visible: SettingsData.launcherLogoMode === "compositor" && (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl)
anchors.centerIn: parent anchors.centerIn: parent
width: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset) width: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset)
height: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset) height: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset)
@@ -48,6 +48,8 @@ BasePill {
return "file://" + Theme.shellDir + "/assets/niri.svg" return "file://" + Theme.shellDir + "/assets/niri.svg"
} else if (CompositorService.isHyprland) { } else if (CompositorService.isHyprland) {
return "file://" + Theme.shellDir + "/assets/hyprland.svg" return "file://" + Theme.shellDir + "/assets/hyprland.svg"
} else if (CompositorService.isDwl) {
return "file://" + Theme.shellDir + "/assets/mango.png"
} }
return "" return ""
} }

View File

@@ -28,11 +28,14 @@ Item {
_desktopEntriesUpdateTrigger++ _desktopEntriesUpdateTrigger++
} }
} }
property int currentWorkspace: { property int currentWorkspace: {
if (CompositorService.isNiri) { if (CompositorService.isNiri) {
return getNiriActiveWorkspace() return getNiriActiveWorkspace()
} else if (CompositorService.isHyprland) { } else if (CompositorService.isHyprland) {
return getHyprlandActiveWorkspace() return getHyprlandActiveWorkspace()
} else if (CompositorService.isDwl) {
return getDwlActiveTag()
} }
return 1 return 1
} }
@@ -47,6 +50,10 @@ Item {
const filteredList = baseList.filter(ws => ws.id > -1) const filteredList = baseList.filter(ws => ws.id > -1)
return SettingsData.showWorkspacePadding ? padWorkspaces(filteredList) : filteredList return SettingsData.showWorkspacePadding ? padWorkspaces(filteredList) : filteredList
} }
if (CompositorService.isDwl) {
const baseList = getDwlTags()
return SettingsData.showWorkspacePadding ? padWorkspaces(baseList) : baseList
}
return [1] return [1]
} }
@@ -69,15 +76,30 @@ Item {
targetWorkspaceId = workspace.id targetWorkspaceId = workspace.id
} else if (CompositorService.isHyprland) { } else if (CompositorService.isHyprland) {
targetWorkspaceId = ws.id !== undefined ? ws.id : ws targetWorkspaceId = ws.id !== undefined ? ws.id : ws
} else if (CompositorService.isDwl) {
if (typeof ws !== "object" || ws.tag === undefined) {
return []
}
targetWorkspaceId = ws.tag
} else { } else {
return [] return []
} }
const wins = CompositorService.isNiri ? (NiriService.windows || []) : CompositorService.sortedToplevels const wins = CompositorService.isNiri ? (NiriService.windows || []) : CompositorService.sortedToplevels
const byApp = {} const byApp = {}
const isActiveWs = CompositorService.isNiri ? NiriService.allWorkspaces.some(ws => ws.id === targetWorkspaceId && ws.is_active) : targetWorkspaceId === root.currentWorkspace let isActiveWs = false
if (CompositorService.isNiri) {
isActiveWs = NiriService.allWorkspaces.some(ws => ws.id === targetWorkspaceId && ws.is_active)
} else if (CompositorService.isDwl) {
const output = DwlService.getOutputState(root.screenName)
if (output && output.tags) {
const tag = output.tags.find(t => t.tag === targetWorkspaceId)
isActiveWs = tag ? (tag.state === 1) : false
}
} else {
isActiveWs = targetWorkspaceId === root.currentWorkspace
}
wins.forEach((w, i) => { wins.forEach((w, i) => {
if (!w) { if (!w) {
@@ -128,10 +150,14 @@ Item {
function padWorkspaces(list) { function padWorkspaces(list) {
const padded = list.slice() const padded = list.slice()
const placeholder = CompositorService.isHyprland ? { let placeholder
"id": -1, if (CompositorService.isHyprland) {
"name": "" placeholder = {"id": -1, "name": ""}
} : -1 } else if (CompositorService.isDwl) {
placeholder = {"tag": -1}
} else {
placeholder = -1
}
while (padded.length < 3) { while (padded.length < 3) {
padded.push(placeholder) padded.push(placeholder)
} }
@@ -199,7 +225,6 @@ Item {
return Hyprland.focusedWorkspace ? Hyprland.focusedWorkspace.id : 1 return Hyprland.focusedWorkspace ? Hyprland.focusedWorkspace.id : 1
} }
// Find the monitor object for this screen
const monitors = Hyprland.monitors?.values || [] const monitors = Hyprland.monitors?.values || []
const currentMonitor = monitors.find(monitor => monitor.name === root.screenName) const currentMonitor = monitors.find(monitor => monitor.name === root.screenName)
@@ -207,10 +232,41 @@ Item {
return 1 return 1
} }
// Use the monitor's active workspace ID (like original config)
return currentMonitor.activeWorkspace?.id ?? 1 return currentMonitor.activeWorkspace?.id ?? 1
} }
function getDwlTags() {
if (!DwlService.dwlAvailable) {
return [{"tag": 0}, {"tag": 1}]
}
const output = DwlService.getOutputState(root.screenName)
if (!output || !output.tags || output.tags.length === 0) {
const tagCount = DwlService.tagCount || 9
const tags = []
for (let i = 0; i < tagCount; i++) {
tags.push({"tag": i})
}
return tags
}
return output.tags.map(tag => ({"tag": tag.tag, "state": tag.state, "clients": tag.clients, "focused": tag.focused}))
}
function getDwlActiveTag() {
if (!DwlService.dwlAvailable) {
return 0
}
const output = DwlService.getOutputState(root.screenName)
if (!output || !output.tags) {
return 0
}
const activeTag = output.tags.find(tag => tag.state === 1)
return activeTag ? activeTag.tag : 0
}
readonly property real padding: Math.max(Theme.spacingXS, Theme.spacingS * (widgetHeight / 30)) readonly property real padding: Math.max(Theme.spacingXS, Theme.spacingS * (widgetHeight / 30))
readonly property real visualWidth: isVertical ? widgetHeight : (workspaceRow.implicitWidth + padding * 2) readonly property real visualWidth: isVertical ? widgetHeight : (workspaceRow.implicitWidth + padding * 2)
readonly property real visualHeight: isVertical ? (workspaceRow.implicitHeight + padding * 2) : widgetHeight readonly property real visualHeight: isVertical ? (workspaceRow.implicitHeight + padding * 2) : widgetHeight
@@ -219,6 +275,8 @@ Item {
return root.workspaceList.filter(ws => { return root.workspaceList.filter(ws => {
if (CompositorService.isHyprland) { if (CompositorService.isHyprland) {
return ws && ws.id !== -1 return ws && ws.id !== -1
} else if (CompositorService.isDwl) {
return ws && ws.tag !== -1
} }
return ws !== -1 return ws !== -1
}) })
@@ -255,12 +313,27 @@ Item {
} }
Hyprland.dispatch(`workspace ${realWorkspaces[nextIndex].id}`) Hyprland.dispatch(`workspace ${realWorkspaces[nextIndex].id}`)
} else if (CompositorService.isDwl) {
const realWorkspaces = getRealWorkspaces()
if (realWorkspaces.length < 2) {
return
}
const currentIndex = realWorkspaces.findIndex(ws => ws.tag === root.currentWorkspace)
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) {
return
}
DwlService.switchToTag(root.screenName, realWorkspaces[nextIndex].tag)
} }
} }
width: isVertical ? barThickness : visualWidth width: isVertical ? barThickness : visualWidth
height: isVertical ? visualHeight : barThickness height: isVertical ? visualHeight : barThickness
visible: CompositorService.isNiri || CompositorService.isHyprland visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl
Rectangle { Rectangle {
id: visualBackground id: visualBackground
@@ -303,12 +376,16 @@ Item {
property bool isActive: { property bool isActive: {
if (CompositorService.isHyprland) { if (CompositorService.isHyprland) {
return modelData && modelData.id === root.currentWorkspace return modelData && modelData.id === root.currentWorkspace
} else if (CompositorService.isDwl) {
return modelData && modelData.tag === root.currentWorkspace
} }
return modelData === root.currentWorkspace return modelData === root.currentWorkspace
} }
property bool isPlaceholder: { property bool isPlaceholder: {
if (CompositorService.isHyprland) { if (CompositorService.isHyprland) {
return modelData && modelData.id === -1 return modelData && modelData.id === -1
} else if (CompositorService.isDwl) {
return modelData && modelData.tag === -1
} }
return modelData === -1 return modelData === -1
} }
@@ -319,9 +396,10 @@ Item {
property bool isUrgent: { property bool isUrgent: {
if (CompositorService.isHyprland) { if (CompositorService.isHyprland) {
return modelData?.urgent ?? false return modelData?.urgent ?? false
} } else if (CompositorService.isNiri) {
if (CompositorService.isNiri) {
return loadedIsUrgent return loadedIsUrgent
} else if (CompositorService.isDwl) {
return modelData?.state === 2
} }
return false return false
} }
@@ -372,6 +450,8 @@ Item {
NiriService.switchToWorkspace(modelData - 1) NiriService.switchToWorkspace(modelData - 1)
} else if (CompositorService.isHyprland && modelData?.id) { } else if (CompositorService.isHyprland && modelData?.id) {
Hyprland.dispatch(`workspace ${modelData.id}`) Hyprland.dispatch(`workspace ${modelData.id}`)
} else if (CompositorService.isDwl && modelData?.tag !== undefined) {
DwlService.switchToTag(root.screenName, modelData.tag)
} }
} }
} }
@@ -394,6 +474,8 @@ Item {
wsData = NiriService.allWorkspaces.find(ws => ws.idx + 1 === modelData && ws.output === root.screenName) || null; wsData = NiriService.allWorkspaces.find(ws => ws.idx + 1 === modelData && ws.output === root.screenName) || null;
} else if (CompositorService.isHyprland) { } else if (CompositorService.isHyprland) {
wsData = modelData; wsData = modelData;
} else if (CompositorService.isDwl) {
wsData = modelData;
} }
delegateRoot.loadedWorkspaceData = wsData; delegateRoot.loadedWorkspaceData = wsData;
delegateRoot.loadedIsUrgent = wsData?.is_urgent ?? false; delegateRoot.loadedIsUrgent = wsData?.is_urgent ?? false;
@@ -406,7 +488,11 @@ Item {
delegateRoot.loadedHasIcon = icData !== null; delegateRoot.loadedHasIcon = icData !== null;
if (SettingsData.showWorkspaceApps) { if (SettingsData.showWorkspaceApps) {
delegateRoot.loadedIcons = root.getWorkspaceIcons(CompositorService.isHyprland ? modelData : (modelData === -1 ? null : modelData)); if (CompositorService.isDwl) {
delegateRoot.loadedIcons = root.getWorkspaceIcons(modelData);
} else {
delegateRoot.loadedIcons = root.getWorkspaceIcons(CompositorService.isHyprland ? modelData : (modelData === -1 ? null : modelData));
}
} else { } else {
delegateRoot.loadedIcons = []; delegateRoot.loadedIcons = [];
} }
@@ -651,11 +737,25 @@ Item {
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
text: { text: {
const isPlaceholder = CompositorService.isHyprland ? (modelData?.id === -1) : (modelData === -1) let isPlaceholder
if (CompositorService.isHyprland) {
isPlaceholder = modelData?.id === -1
} else if (CompositorService.isDwl) {
isPlaceholder = modelData?.tag === -1
} else {
isPlaceholder = modelData === -1
}
if (isPlaceholder) { if (isPlaceholder) {
return index + 1 return index + 1
} }
return CompositorService.isHyprland ? (modelData?.id || "") : (modelData - 1);
if (CompositorService.isHyprland) {
return modelData?.id || ""
} else if (CompositorService.isDwl) {
return (modelData?.tag !== undefined) ? (modelData.tag + 1) : ""
}
return modelData - 1
} }
color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
font.pixelSize: Theme.barTextSize(barThickness) font.pixelSize: Theme.barTextSize(barThickness)
@@ -683,6 +783,11 @@ Item {
function onShowWorkspaceAppsChanged() { delegateRoot.updateAllData() } function onShowWorkspaceAppsChanged() { delegateRoot.updateAllData() }
function onWorkspaceNameIconsChanged() { delegateRoot.updateAllData() } function onWorkspaceNameIconsChanged() { delegateRoot.updateAllData() }
} }
Connections {
target: DwlService
enabled: CompositorService.isDwl
function onStateChanged() { delegateRoot.updateAllData() }
}
} }
} }
} }

View File

@@ -87,9 +87,14 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
model: { model: {
const modes = [I18n.tr("Apps Icon"), I18n.tr("OS Logo")] const modes = [I18n.tr("Apps Icon"), I18n.tr("OS Logo")]
if (CompositorService.isNiri || CompositorService.isHyprland) { if (CompositorService.isNiri) {
const compositorName = CompositorService.isNiri ? "niri" : "Hyprland" modes.push("niri")
modes.push(compositorName) } else if (CompositorService.isHyprland) {
modes.push("Hyprland")
} else if (CompositorService.isDwl) {
modes.push("mango")
} else {
modes.push(I18n.tr("Compositor"))
} }
modes.push(I18n.tr("Custom")) modes.push(I18n.tr("Custom"))
return modes return modes
@@ -97,12 +102,8 @@ Item {
currentIndex: { currentIndex: {
if (SettingsData.launcherLogoMode === "apps") return 0 if (SettingsData.launcherLogoMode === "apps") return 0
if (SettingsData.launcherLogoMode === "os") return 1 if (SettingsData.launcherLogoMode === "os") return 1
if (SettingsData.launcherLogoMode === "compositor") { if (SettingsData.launcherLogoMode === "compositor") return 2
return (CompositorService.isNiri || CompositorService.isHyprland) ? 2 : -1 if (SettingsData.launcherLogoMode === "custom") return 3
}
if (SettingsData.launcherLogoMode === "custom") {
return (CompositorService.isNiri || CompositorService.isHyprland) ? 3 : 2
}
return 0 return 0
} }
onSelectionChanged: (index, selected) => { onSelectionChanged: (index, selected) => {
@@ -111,13 +112,9 @@ Item {
SettingsData.setLauncherLogoMode("apps") SettingsData.setLauncherLogoMode("apps")
} else if (index === 1) { } else if (index === 1) {
SettingsData.setLauncherLogoMode("os") SettingsData.setLauncherLogoMode("os")
} else if (CompositorService.isNiri || CompositorService.isHyprland) {
if (index === 2) {
SettingsData.setLauncherLogoMode("compositor")
} else if (index === 3) {
SettingsData.setLauncherLogoMode("custom")
}
} else if (index === 2) { } else if (index === 2) {
SettingsData.setLauncherLogoMode("compositor")
} else if (index === 3) {
SettingsData.setLauncherLogoMode("custom") SettingsData.setLauncherLogoMode("custom")
} }
} }

View File

@@ -12,7 +12,7 @@
</div> </div>
A modern Wayland desktop shell built with [Quickshell](https://quickshell.org/) and [Go](https://go.dev/). Optimized for the [niri](https://github.com/YaLTeR/niri) and [Hyprland](https://hyprland.org/) compositors. A modern Wayland desktop shell built with [Quickshell](https://quickshell.org/) and [Go](https://go.dev/). Optimized for the [niri](https://github.com/YaLTeR/niri), [Hyprland](https://hyprland.org/), and [dwl/mangowc](https://github.com/DreamMaoMao/mangowc) compositors.
Features notifications, app launcher, wallpaper customization, and fully customizable with [plugins](https://github.com/AvengeMedia/dms-plugin-registry). Features notifications, app launcher, wallpaper customization, and fully customizable with [plugins](https://github.com/AvengeMedia/dms-plugin-registry).

View File

@@ -12,6 +12,7 @@ Singleton {
property bool isHyprland: false property bool isHyprland: false
property bool isNiri: false property bool isNiri: false
property bool isDwl: false
property string compositor: "unknown" property string compositor: "unknown"
readonly property string hyprlandSignature: Quickshell.env("HYPRLAND_INSTANCE_SIGNATURE") readonly property string hyprlandSignature: Quickshell.env("HYPRLAND_INSTANCE_SIGNATURE")
@@ -87,6 +88,15 @@ Singleton {
Qt.callLater(() => NiriService.generateNiriLayoutConfig()) Qt.callLater(() => NiriService.generateNiriLayoutConfig())
} }
Connections {
target: DwlService
function onStateChanged() {
if (isDwl && !isHyprland && !isNiri) {
scheduleSort()
}
}
}
function computeSortedToplevels() { function computeSortedToplevels() {
if (!ToplevelManager.toplevels || !ToplevelManager.toplevels.values) if (!ToplevelManager.toplevels || !ToplevelManager.toplevels.values)
return [] return []
@@ -331,6 +341,7 @@ Singleton {
if (hyprlandSignature && hyprlandSignature.length > 0) { if (hyprlandSignature && hyprlandSignature.length > 0) {
isHyprland = true isHyprland = true
isNiri = false isNiri = false
isDwl = false
compositor = "hyprland" compositor = "hyprland"
console.info("CompositorService: Detected Hyprland") console.info("CompositorService: Detected Hyprland")
try { try {
@@ -344,6 +355,7 @@ Singleton {
if (exitCode === 0) { if (exitCode === 0) {
isNiri = true isNiri = true
isHyprland = false isHyprland = false
isDwl = false
compositor = "niri" compositor = "niri"
console.info("CompositorService: Detected Niri with socket:", niriSocket) console.info("CompositorService: Detected Niri with socket:", niriSocket)
NiriService.generateNiriBinds() NiriService.generateNiriBinds()
@@ -351,27 +363,82 @@ Singleton {
} else { } else {
isHyprland = false isHyprland = false
isNiri = true isNiri = true
isDwl = false
compositor = "niri" compositor = "niri"
console.warn("CompositorService: Niri socket check failed, defaulting to Niri anyway") console.warn("CompositorService: Niri socket check failed, defaulting to Niri anyway")
} }
}, 0) }, 0)
} else { } else {
if (DMSService.dmsAvailable) {
Qt.callLater(checkForDwl)
} else {
isHyprland = false
isNiri = false
isDwl = false
compositor = "unknown"
console.warn("CompositorService: No compositor detected")
}
}
}
Connections {
target: DMSService
function onCapabilitiesReceived() {
if (!isHyprland && !isNiri && !isDwl) {
checkForDwl()
}
}
}
function checkForDwl() {
if (DMSService.apiVersion >= 12 && DMSService.capabilities.includes("dwl")) {
isHyprland = false isHyprland = false
isNiri = false isNiri = false
compositor = "unknown" isDwl = true
console.warn("CompositorService: No compositor detected") compositor = "dwl"
console.info("CompositorService: Detected DWL via DMS capability")
} }
} }
function powerOffMonitors() { function powerOffMonitors() {
if (isNiri) return NiriService.powerOffMonitors() if (isNiri) return NiriService.powerOffMonitors()
if (isHyprland) return Hyprland.dispatch("dpms off") if (isHyprland) return Hyprland.dispatch("dpms off")
if (isDwl) return _dwlPowerOffMonitors()
console.warn("CompositorService: Cannot power off monitors, unknown compositor") console.warn("CompositorService: Cannot power off monitors, unknown compositor")
} }
function powerOnMonitors() { function powerOnMonitors() {
if (isNiri) return NiriService.powerOnMonitors() if (isNiri) return NiriService.powerOnMonitors()
if (isHyprland) return Hyprland.dispatch("dpms on") if (isHyprland) return Hyprland.dispatch("dpms on")
if (isDwl) return _dwlPowerOnMonitors()
console.warn("CompositorService: Cannot power on monitors, unknown compositor") console.warn("CompositorService: Cannot power on monitors, unknown compositor")
} }
function _dwlPowerOffMonitors() {
if (!Quickshell.screens || Quickshell.screens.length === 0) {
console.warn("CompositorService: 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(["wlr-randr", "--output", screen.name, "--off"])
}
}
}
function _dwlPowerOnMonitors() {
if (!Quickshell.screens || Quickshell.screens.length === 0) {
console.warn("CompositorService: 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(["wlr-randr", "--output", screen.name, "--on"])
}
}
}
} }

View File

@@ -42,6 +42,7 @@ Singleton {
signal capabilitiesReceived() signal capabilitiesReceived()
signal credentialsRequest(var data) signal credentialsRequest(var data)
signal bluetoothPairingRequest(var data) signal bluetoothPairingRequest(var data)
signal dwlStateUpdate(var data)
Component.onCompleted: { Component.onCompleted: {
if (socketPath && socketPath.length > 0) { if (socketPath && socketPath.length > 0) {
@@ -266,6 +267,8 @@ Singleton {
} }
} else if (service === "bluetooth.pairing") { } else if (service === "bluetooth.pairing") {
bluetoothPairingRequest(data) bluetoothPairingRequest(data)
} else if (service === "dwl") {
dwlStateUpdate(data)
} }
} }

170
Services/DwlService.qml Normal file
View File

@@ -0,0 +1,170 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
Singleton {
id: root
property bool dwlAvailable: false
property var outputs: ({})
property var tagCount: 0
property var layouts: []
property string activeOutput: ""
signal stateChanged()
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()
}
}
function checkCapabilities() {
if (!DMSService.capabilities || !Array.isArray(DMSService.capabilities)) {
dwlAvailable = false
return
}
const hasDwl = DMSService.capabilities.includes("dwl")
if (hasDwl && !dwlAvailable) {
dwlAvailable = true
console.info("DwlService: DWL capability detected")
requestState()
} 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 || 0
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) {
console.warn("DwlService: 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) {
console.warn("DwlService: 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) {
console.warn("DwlService: 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 tagmask = 1 << tagIndex
setTags(outputName, tagmask, 1)
}
function quit() {
Quickshell.execDetached(["mmsg", "-d", "quit"])
}
}

View File

@@ -184,7 +184,11 @@ Singleton {
return return
} }
// Hyprland fallback if (CompositorService.isDwl) {
DwlService.quit()
return
}
Hyprland.dispatch("exit") Hyprland.dispatch("exit")
} else { } else {
Quickshell.execDetached(["sh", "-c", SettingsData.customPowerActionLogout]) Quickshell.execDetached(["sh", "-c", SettingsData.customPowerActionLogout])

3
assets/danklogo.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" fill="#fff">
<path fill-rule="evenodd" style="shape-rendering:geometricPrecision" d="M580 70q8-3 15 2l15 58 20 60 20 50q8 7 30 12l40 13 35 13q19 9 10 17l-145 2-160-2h-80l-35 13q-6 8 5 12l55-10q-1-8 5-8l110 2 30 10 40-4 10-4 100-2q8 0 8 6t-6 8v22l-4 30-8 25q-7 11-30 15l40 15 70 15q21 7 20 22 1 14-20 18l-130 2-5 18-10 40-5 40q-7 31-30 55a48 48 0 0 1-40 13l-70-28-80-45-60-45-12-30-10-60-6-60q0-21 6-50l-73-2q-19-3-13-18l28-15 60-20 15-40 25-95 10-40q8-24 30-25l80 7h40l25-6Zm-135 130 95 2 90 8 12 30-32-2-90-3-110 3-50 6-15-29 25-9Zm120 135q8-4 13 3l17 27 15 20q15 15 45 30l50 17 65 16q15 6 15 17 0 5-15 5l-130-5-60-10-60-3q-15 0-28-6-6-6-4-18l12-18 30-25 15-25Zm-143 0q3 5 6 20l7 25q8 15 30 18 5 0 5 2-15 3-28-5l-12-17-8-30Zm283-3v20l-5 23-10 15q-8 8-28 8 0-3 8-4l20-8 8-16Zm-375 218 30 50 45 70 40 60 35 70 35 55 20-55 10-60-15-30 15-20 15-15 30 20-5 25 15 50 15 60 7-50 3-60v-120l10 10 15 15 25 25 35 30 40 25-5 25-25 50-25 45-30 50-35 55-35 40-20 5-35-25-70-55-80-60-70-55-80-65-60-40 20-15 50-30 35-25 5-15 15-25Zm238-182q4 3 4 8-3 3-9 0Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
assets/mango.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB