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

wayland: add wlr-output-management-unstable-v1 service + labwc info

This commit is contained in:
bbedward
2025-11-11 17:19:45 -05:00
parent 80e690f9fc
commit 695a75ea09
9 changed files with 374 additions and 23 deletions

View File

@@ -56,7 +56,7 @@ BasePill {
} }
IconImage { IconImage {
visible: SettingsData.launcherLogoMode === "compositor" && (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isSway) visible: SettingsData.launcherLogoMode === "compositor" && (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isSway || CompositorService.isLabwc)
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)
@@ -71,6 +71,8 @@ BasePill {
return "file://" + Theme.shellDir + "/assets/mango.png" return "file://" + Theme.shellDir + "/assets/mango.png"
} else if (CompositorService.isSway) { } else if (CompositorService.isSway) {
return "file://" + Theme.shellDir + "/assets/sway.svg" return "file://" + Theme.shellDir + "/assets/sway.svg"
} else if (CompositorService.isLabwc) {
return "file://" + Theme.shellDir + "/assets/labwc.png"
} }
return "" return ""
} }

View File

@@ -13,11 +13,13 @@ Item {
property bool isNiri: CompositorService.isNiri property bool isNiri: CompositorService.isNiri
property bool isSway: CompositorService.isSway property bool isSway: CompositorService.isSway
property bool isDwl: CompositorService.isDwl property bool isDwl: CompositorService.isDwl
property bool isLabwc: CompositorService.isLabwc
property string compositorName: { property string compositorName: {
if (isHyprland) return "hyprland" if (isHyprland) return "hyprland"
if (isSway) return "sway" if (isSway) return "sway"
if (isDwl) return "mangowc" if (isDwl) return "mangowc"
if (isLabwc) return "labwc"
return "niri" return "niri"
} }
@@ -25,6 +27,7 @@ Item {
if (isHyprland) return "/assets/hyprland.svg" if (isHyprland) return "/assets/hyprland.svg"
if (isSway) return "/assets/sway.svg" if (isSway) return "/assets/sway.svg"
if (isDwl) return "/assets/mango.png" if (isDwl) return "/assets/mango.png"
if (isLabwc) return "/assets/labwc.png"
return "/assets/niri.svg" return "/assets/niri.svg"
} }
@@ -32,6 +35,7 @@ Item {
if (isHyprland) return "https://hypr.land" if (isHyprland) return "https://hypr.land"
if (isSway) return "https://swaywm.org" if (isSway) return "https://swaywm.org"
if (isDwl) return "https://github.com/DreamMaoMao/mangowc" if (isDwl) return "https://github.com/DreamMaoMao/mangowc"
if (isLabwc) return "https://labwc.github.io/"
return "https://github.com/YaLTeR/niri" return "https://github.com/YaLTeR/niri"
} }
@@ -39,6 +43,7 @@ Item {
if (isHyprland) return "Hyprland Website" if (isHyprland) return "Hyprland Website"
if (isSway) return "Sway Website" if (isSway) return "Sway Website"
if (isDwl) return "mangowc GitHub" if (isDwl) return "mangowc GitHub"
if (isLabwc) return "LabWC Website"
return "niri GitHub" return "niri GitHub"
} }
@@ -60,9 +65,13 @@ Item {
property string redditUrl: "https://reddit.com/r/niri" property string redditUrl: "https://reddit.com/r/niri"
property string redditTooltip: "r/niri Subreddit" property string redditTooltip: "r/niri Subreddit"
property bool showMatrix: isNiri && !isHyprland && !isSway && !isDwl property string ircUrl: "https://web.libera.chat/gamja/?channels=#labwc"
property string ircTooltip: "LabWC IRC Channel"
property bool showMatrix: isNiri && !isHyprland && !isSway && !isDwl && !isLabwc
property bool showCompositorDiscord: isHyprland || isDwl property bool showCompositorDiscord: isHyprland || isDwl
property bool showReddit: isNiri && !isHyprland && !isSway && !isDwl property bool showReddit: isNiri && !isHyprland && !isSway && !isDwl && !isLabwc
property bool showIrc: isLabwc
DankFlickable { DankFlickable {
anchors.fill: parent anchors.fill: parent
@@ -153,6 +162,9 @@ Item {
if (showMatrix) { if (showMatrix) {
baseWidth += matrixButton.width + 4 baseWidth += matrixButton.width + 4
} }
if (showIrc) {
baseWidth += ircButton.width + Theme.spacingM
}
if (showCompositorDiscord) { if (showCompositorDiscord) {
baseWidth += compositorDiscordButton.width + Theme.spacingM baseWidth += compositorDiscordButton.width + Theme.spacingM
} }
@@ -232,11 +244,43 @@ Item {
} }
} }
Item {
id: ircButton
width: 24
height: 24
x: compositorButton.x + compositorButton.width + Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
visible: showIrc
property bool hovered: false
property string tooltipText: ircTooltip
DankIcon {
anchors.centerIn: parent
name: "forum"
size: 20
color: Theme.surfaceText
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onEntered: parent.hovered = true
onExited: parent.hovered = false
onClicked: Qt.openUrlExternally(ircUrl)
}
}
Item { Item {
id: dmsDiscordButton id: dmsDiscordButton
width: 20 width: 20
height: 20 height: 20
x: showMatrix ? matrixButton.x + matrixButton.width + Theme.spacingM : compositorButton.x + compositorButton.width + Theme.spacingM x: {
if (showMatrix) return matrixButton.x + matrixButton.width + Theme.spacingM
if (showIrc) return ircButton.x + ircButton.width + Theme.spacingM
return compositorButton.x + compositorButton.width + Theme.spacingM
}
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
property bool hovered: false property bool hovered: false
@@ -618,6 +662,7 @@ Item {
property var hoveredButton: { property var hoveredButton: {
if (compositorButton.hovered) return compositorButton if (compositorButton.hovered) return compositorButton
if (matrixButton.visible && matrixButton.hovered) return matrixButton if (matrixButton.visible && matrixButton.hovered) return matrixButton
if (ircButton.visible && ircButton.hovered) return ircButton
if (dmsDiscordButton.hovered) return dmsDiscordButton if (dmsDiscordButton.hovered) return dmsDiscordButton
if (compositorDiscordButton.visible && compositorDiscordButton.hovered) return compositorDiscordButton if (compositorDiscordButton.visible && compositorDiscordButton.hovered) return compositorDiscordButton
if (redditButton.visible && redditButton.hovered) return redditButton if (redditButton.visible && redditButton.hovered) return redditButton

View File

@@ -19,7 +19,7 @@
</div> </div>
DankMaterialShell is a complete desktop shell for [niri](https://github.com/YaLTeR/niri), [Hyprland](https://hypr.land), [MangoWC](https://github.com/DreamMaoMao/mangowc), [Sway](https://swaywm.org), and other Wayland compositors. It replaces waybar, swaylock, swayidle, mako, fuzzel, polkit, and everything else you'd normally stitch together to make a desktop - all in one cohesive package with a gorgeous interface. DankMaterialShell is a complete desktop shell for [niri](https://github.com/YaLTeR/niri), [Hyprland](https://hypr.land), [MangoWC](https://github.com/DreamMaoMao/mangowc), [Sway](https://swaywm.org), [labwc](https://labwc.github.io/), and other Wayland compositors. It replaces waybar, swaylock, swayidle, mako, fuzzel, polkit, and everything else you'd normally stitch together to make a desktop - all in one cohesive package with a gorgeous interface.
## Components ## Components
@@ -100,7 +100,7 @@ Endless customization with the [plugin registry](https://plugins.danklinux.com).
## Supported Compositors ## Supported Compositors
DankMaterialShell works best with **[niri](https://github.com/YaLTeR/niri)**, **[Hyprland](https://hyprland.org/)**, **[sway](https://swaywm.org/)**, and **[dwl/MangoWC](https://github.com/DreamMaoMao/mangowc)** - with full workspace switching, overview integration, and monitor management. DankMaterialShell works best with **[niri](https://github.com/YaLTeR/niri)**, **[Hyprland](https://hyprland.org/)**, **[sway](https://swaywm.org/)**, **[dwl/MangoWC](https://github.com/DreamMaoMao/mangowc)**, and **[labwc](https://labwc.github.io/)** - with full workspace switching, overview integration, and monitor management.
Other Wayland compositors work too, just with a reduced feature set. Other Wayland compositors work too, just with a reduced feature set.

View File

@@ -14,11 +14,13 @@ Singleton {
property bool isNiri: false property bool isNiri: false
property bool isDwl: false property bool isDwl: false
property bool isSway: false property bool isSway: false
property bool isLabwc: 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")
readonly property string niriSocket: Quickshell.env("NIRI_SOCKET") readonly property string niriSocket: Quickshell.env("NIRI_SOCKET")
readonly property string swaySocket: Quickshell.env("SWAYSOCK") readonly property string swaySocket: Quickshell.env("SWAYSOCK")
readonly property string labwcPid: Quickshell.env("LABWC_PID")
property bool useNiriSorting: isNiri && NiriService property bool useNiriSorting: isNiri && NiriService
property var sortedToplevels: [] property var sortedToplevels: []
@@ -33,6 +35,13 @@ Singleton {
return screen.devicePixelRatio || 1 return screen.devicePixelRatio || 1
} }
if (WlrOutputService.wlrOutputAvailable && screen) {
const wlrOutput = WlrOutputService.getOutput(screen.name)
if (wlrOutput?.enabled && wlrOutput.scale !== undefined && wlrOutput.scale > 0) {
return wlrOutput.scale
}
}
if (isNiri && screen) { if (isNiri && screen) {
const niriScale = NiriService.displayScales[screen.name] const niriScale = NiriService.displayScales[screen.name]
if (niriScale !== undefined) return niriScale if (niriScale !== undefined) return niriScale
@@ -343,6 +352,7 @@ Singleton {
isNiri = false isNiri = false
isDwl = false isDwl = false
isSway = false isSway = false
isLabwc = false
compositor = "hyprland" compositor = "hyprland"
console.info("CompositorService: Detected Hyprland") console.info("CompositorService: Detected Hyprland")
return return
@@ -355,6 +365,7 @@ Singleton {
isHyprland = false isHyprland = false
isDwl = false isDwl = false
isSway = false isSway = false
isLabwc = 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()
@@ -371,13 +382,25 @@ Singleton {
isHyprland = false isHyprland = false
isDwl = false isDwl = false
isSway = true isSway = true
isLabwc = false
compositor = "sway" compositor = "sway"
console.info("CompositorService: Detected Sway with socket:", swaySocket) console.info("CompositorService: Detected Sway with socket:", swaySocket)
} }
}, 0) }, 0)
return return
} }
if (labwcPid && labwcPid.length > 0) {
isHyprland = false
isNiri = false
isDwl = false
isSway = false
isLabwc = true
compositor = "labwc"
console.info("CompositorService: Detected LabWC with PID:", labwcPid)
return
}
if (DMSService.dmsAvailable) { if (DMSService.dmsAvailable) {
Qt.callLater(checkForDwl) Qt.callLater(checkForDwl)
} else { } else {
@@ -385,6 +408,7 @@ Singleton {
isNiri = false isNiri = false
isDwl = false isDwl = false
isSway = false isSway = false
isLabwc = false
compositor = "unknown" compositor = "unknown"
console.warn("CompositorService: No compositor detected") console.warn("CompositorService: No compositor detected")
} }
@@ -393,7 +417,7 @@ Singleton {
Connections { Connections {
target: DMSService target: DMSService
function onCapabilitiesReceived() { function onCapabilitiesReceived() {
if (!isHyprland && !isNiri && !isDwl) { if (!isHyprland && !isNiri && !isDwl && !isLabwc) {
checkForDwl() checkForDwl()
} }
} }
@@ -404,6 +428,8 @@ Singleton {
isHyprland = false isHyprland = false
isNiri = false isNiri = false
isDwl = true isDwl = true
isSway = false
isLabwc = false
compositor = "dwl" compositor = "dwl"
console.info("CompositorService: Detected DWL via DMS capability") console.info("CompositorService: Detected DWL via DMS capability")
} }

View File

@@ -1,6 +1,6 @@
pragma Singleton pragma Singleton
pragma ComponentBehavior pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell

View File

@@ -48,8 +48,9 @@ Singleton {
signal brightnessStateUpdate(var data) signal brightnessStateUpdate(var data)
signal brightnessDeviceUpdate(var device) signal brightnessDeviceUpdate(var device)
signal extWorkspaceStateUpdate(var data) signal extWorkspaceStateUpdate(var data)
signal wlrOutputStateUpdate(var data)
property var activeSubscriptions: ["network", "network.credentials", "loginctl", "freedesktop", "gamma", "bluetooth", "bluetooth.pairing", "dwl", "brightness"] property var activeSubscriptions: ["network", "network.credentials", "loginctl", "freedesktop", "gamma", "bluetooth", "bluetooth.pairing", "dwl", "brightness", "wlroutput"]
Component.onCompleted: { Component.onCompleted: {
if (socketPath && socketPath.length > 0) { if (socketPath && socketPath.length > 0) {
@@ -346,6 +347,8 @@ Singleton {
} }
} else if (service === "extworkspace") { } else if (service === "extworkspace") {
extWorkspaceStateUpdate(data) extWorkspaceStateUpdate(data)
} else if (service === "wlroutput") {
wlrOutputStateUpdate(data)
} }
} }

View File

@@ -0,0 +1,275 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
Singleton {
id: root
property bool wlrOutputAvailable: false
property var outputs: []
property int serial: 0
signal stateChanged
signal configurationApplied(bool success, string message)
Connections {
target: DMSService
function onCapabilitiesReceived() {
checkCapabilities()
}
function onConnectionStateChanged() {
if (DMSService.isConnected) {
checkCapabilities()
return
}
wlrOutputAvailable = false
}
function onWlrOutputStateUpdate(data) {
if (!wlrOutputAvailable) {
return
}
handleStateUpdate(data)
}
}
Component.onCompleted: {
if (!DMSService.dmsAvailable) {
return
}
checkCapabilities()
}
function checkCapabilities() {
if (!DMSService.capabilities || !Array.isArray(DMSService.capabilities)) {
wlrOutputAvailable = false
return
}
const hasWlrOutput = DMSService.capabilities.includes("wlroutput")
if (hasWlrOutput && !wlrOutputAvailable) {
wlrOutputAvailable = true
console.info("WlrOutputService: wlr-output-management capability detected")
requestState()
return
}
if (!hasWlrOutput) {
wlrOutputAvailable = false
}
}
function requestState() {
if (!DMSService.isConnected || !wlrOutputAvailable) {
return
}
DMSService.sendRequest("wlroutput.getState", null, response => {
if (!response.result) {
return
}
handleStateUpdate(response.result)
})
}
function handleStateUpdate(state) {
outputs = state.outputs || []
serial = state.serial || 0
if (outputs.length === 0) {
console.warn("WlrOutputService: Received empty outputs list")
} else {
console.log("WlrOutputService: Updated with", outputs.length, "outputs, serial:", serial)
outputs.forEach((output, index) => {
console.log("WlrOutputService: Output", index, "-", output.name,
"enabled:", output.enabled,
"mode:", output.currentMode ?
output.currentMode.width + "x" + output.currentMode.height + "@" +
(output.currentMode.refresh / 1000) + "Hz" : "none")
})
}
stateChanged()
}
function getOutput(name) {
for (const output of outputs) {
if (output.name === name) {
return output
}
}
return null
}
function getEnabledOutputs() {
return outputs.filter(output => output.enabled)
}
function applyConfiguration(heads, callback) {
if (!DMSService.isConnected || !wlrOutputAvailable) {
if (callback) {
callback(false, "Not connected")
}
return
}
console.log("WlrOutputService: Applying configuration for", heads.length, "outputs")
heads.forEach((head, index) => {
console.log("WlrOutputService: Head", index, "- name:", head.name,
"enabled:", head.enabled,
"modeId:", head.modeId,
"customMode:", JSON.stringify(head.customMode),
"position:", JSON.stringify(head.position),
"scale:", head.scale,
"transform:", head.transform,
"adaptiveSync:", head.adaptiveSync)
})
DMSService.sendRequest("wlroutput.applyConfiguration", {
"heads": heads
}, response => {
const success = !response.error
const message = response.error || response.result?.message || ""
if (response.error) {
console.warn("WlrOutputService: applyConfiguration error:", response.error)
} else {
console.log("WlrOutputService: Configuration applied successfully")
}
configurationApplied(success, message)
if (callback) {
callback(success, message)
}
})
}
function testConfiguration(heads, callback) {
if (!DMSService.isConnected || !wlrOutputAvailable) {
if (callback) {
callback(false, "Not connected")
}
return
}
console.log("WlrOutputService: Testing configuration for", heads.length, "outputs")
DMSService.sendRequest("wlroutput.testConfiguration", {
"heads": heads
}, response => {
const success = !response.error
const message = response.error || response.result?.message || ""
if (response.error) {
console.warn("WlrOutputService: testConfiguration error:", response.error)
} else {
console.log("WlrOutputService: Configuration test passed")
}
if (callback) {
callback(success, message)
}
})
}
function setOutputEnabled(outputName, enabled, callback) {
const output = getOutput(outputName)
if (!output) {
console.warn("WlrOutputService: Output not found:", outputName)
if (callback) {
callback(false, "Output not found")
}
return
}
const heads = [{
"name": outputName,
"enabled": enabled
}]
if (enabled && output.currentMode) {
heads[0].modeId = output.currentMode.id
}
applyConfiguration(heads, callback)
}
function setOutputMode(outputName, modeId, callback) {
const heads = [{
"name": outputName,
"enabled": true,
"modeId": modeId
}]
applyConfiguration(heads, callback)
}
function setOutputCustomMode(outputName, width, height, refresh, callback) {
const heads = [{
"name": outputName,
"enabled": true,
"customMode": {
"width": width,
"height": height,
"refresh": refresh
}
}]
applyConfiguration(heads, callback)
}
function setOutputPosition(outputName, x, y, callback) {
const heads = [{
"name": outputName,
"enabled": true,
"position": {
"x": x,
"y": y
}
}]
applyConfiguration(heads, callback)
}
function setOutputScale(outputName, scale, callback) {
const heads = [{
"name": outputName,
"enabled": true,
"scale": scale
}]
applyConfiguration(heads, callback)
}
function setOutputTransform(outputName, transform, callback) {
const heads = [{
"name": outputName,
"enabled": true,
"transform": transform
}]
applyConfiguration(heads, callback)
}
function setOutputAdaptiveSync(outputName, state, callback) {
const heads = [{
"name": outputName,
"enabled": true,
"adaptiveSync": state
}]
applyConfiguration(heads, callback)
}
function configureOutput(config, callback) {
const heads = [config]
applyConfiguration(heads, callback)
}
function configureMultipleOutputs(configs, callback) {
applyConfiguration(configs, callback)
}
}

BIN
assets/labwc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@@ -14,13 +14,13 @@
{ {
"term": "- Stateless System Monitoring", "term": "- Stateless System Monitoring",
"context": "- Stateless System Monitoring", "context": "- Stateless System Monitoring",
"reference": "Modules/Settings/AboutTab.qml:544", "reference": "Modules/Settings/AboutTab.qml:588",
"comment": "" "comment": ""
}, },
{ {
"term": "- Support Us With a Star ⭐", "term": "- Support Us With a Star ⭐",
"context": "- Support Us With a Star ⭐", "context": "- Support Us With a Star ⭐",
"reference": "Modules/Settings/AboutTab.qml:509", "reference": "Modules/Settings/AboutTab.qml:553",
"comment": "" "comment": ""
}, },
{ {
@@ -62,7 +62,7 @@
{ {
"term": "About", "term": "About",
"context": "About", "context": "About",
"reference": "Modals/Settings/SettingsSidebar.qml:44, Modules/Settings/AboutTab.qml:363", "reference": "Modals/Settings/SettingsSidebar.qml:44, Modules/Settings/AboutTab.qml:407",
"comment": "" "comment": ""
}, },
{ {
@@ -1010,7 +1010,7 @@
{ {
"term": "DMS out of date", "term": "DMS out of date",
"context": "DMS out of date", "context": "DMS out of date",
"reference": "Services/DMSService.qml:300", "reference": "Services/DMSService.qml:301",
"comment": "" "comment": ""
}, },
{ {
@@ -1262,7 +1262,7 @@
{ {
"term": "Donate on Ko-fi", "term": "Donate on Ko-fi",
"context": "Donate on Ko-fi", "context": "Donate on Ko-fi",
"reference": "Modules/Settings/AboutTab.qml:598", "reference": "Modules/Settings/AboutTab.qml:642",
"comment": "" "comment": ""
}, },
{ {
@@ -1688,7 +1688,7 @@
{ {
"term": "Github:", "term": "Github:",
"context": "Github:", "context": "Github:",
"reference": "Modules/Settings/AboutTab.qml:482", "reference": "Modules/Settings/AboutTab.qml:526",
"comment": "" "comment": ""
}, },
{ {
@@ -2798,7 +2798,7 @@
{ {
"term": "Plugins:", "term": "Plugins:",
"context": "Plugins:", "context": "Plugins:",
"reference": "Modules/Settings/AboutTab.qml:459", "reference": "Modules/Settings/AboutTab.qml:503",
"comment": "" "comment": ""
}, },
{ {
@@ -3026,7 +3026,7 @@
{ {
"term": "Resources", "term": "Resources",
"context": "Resources", "context": "Resources",
"reference": "Modules/Settings/AboutTab.qml:421", "reference": "Modules/Settings/AboutTab.qml:465",
"comment": "" "comment": ""
}, },
{ {
@@ -3500,7 +3500,7 @@
{ {
"term": "Support Development", "term": "Support Development",
"context": "Support Development", "context": "Support Development",
"reference": "Modules/Settings/AboutTab.qml:583", "reference": "Modules/Settings/AboutTab.qml:627",
"comment": "" "comment": ""
}, },
{ {
@@ -3578,7 +3578,7 @@
{ {
"term": "System Monitoring:", "term": "System Monitoring:",
"context": "System Monitoring:", "context": "System Monitoring:",
"reference": "Modules/Settings/AboutTab.qml:517", "reference": "Modules/Settings/AboutTab.qml:561",
"comment": "" "comment": ""
}, },
{ {
@@ -3728,7 +3728,7 @@
{ {
"term": "To update, run the following command:", "term": "To update, run the following command:",
"context": "To update, run the following command:", "context": "To update, run the following command:",
"reference": "Services/DMSService.qml:300", "reference": "Services/DMSService.qml:301",
"comment": "" "comment": ""
}, },
{ {
@@ -4052,7 +4052,7 @@
{ {
"term": "Website:", "term": "Website:",
"context": "Website:", "context": "Website:",
"reference": "Modules/Settings/AboutTab.qml:436", "reference": "Modules/Settings/AboutTab.qml:480",
"comment": "" "comment": ""
}, },
{ {