1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-06-08 04:09:15 -04:00

feat(mango): first-class MangoWM support across DMS, dankinstaller & UI tools

- Bring up Mango to parity with niri/hyprland via a native JSON-IPC w/Native MangoServic., replaces the legacy dwl/`mmsg` path and recent breaking changes
- Dankinstall: mango supported installer, config/binds templates, and packaging (Arch AUR, Fedora Terra auto-enable, Gentoo GURU)
- Window rules: Go provider + CLI + Settings GUI editor
- Keybinds + config reload on edit (mmsg dispatch reload_config)
- Misc new supported options in DMS settings
This commit is contained in:
purian23
2026-06-04 18:45:04 -04:00
parent 4181343ef3
commit 8eb23bcc29
63 changed files with 2282 additions and 301 deletions
+1 -1
View File
@@ -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
property bool isDwl: CompositorService.isDwl || CompositorService.isMango
property bool isLabwc: CompositorService.isLabwc
property string compositorName: {
+2 -2
View File
@@ -659,7 +659,7 @@ Item {
SettingsToggleRow {
width: parent.width - parent.leftPadding
visible: CompositorService.isNiri || CompositorService.isHyprland
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango
text: I18n.tr("Hide When Windows Open")
description: I18n.tr("Show the bar only when no windows are open")
checked: selectedBarConfig?.showOnWindowsOpen ?? false
@@ -1144,7 +1144,7 @@ Item {
iconName: "fit_screen"
title: I18n.tr("Maximize Detection")
description: I18n.tr("Remove gaps and border when windows are maximized")
visible: selectedBarConfig?.enabled && (CompositorService.isNiri || CompositorService.isHyprland)
visible: selectedBarConfig?.enabled && (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango)
checked: selectedBarConfig?.maximizeDetection ?? true
onToggled: checked => SettingsData.updateBarConfig(selectedBarId, {
maximizeDetection: checked
@@ -158,12 +158,14 @@ Singleton {
const compositorDirs = {
"niri": configDir + "/niri/dms/profiles",
"hyprland": configDir + "/hypr/dms/profiles",
"dwl": configDir + "/mango/dms/profiles"
"dwl": configDir + "/mango/dms/profiles",
"mango": configDir + "/mango/dms/profiles"
};
const compositorExts = {
"niri": ".kdl",
"hyprland": ".conf",
"dwl": ".conf"
"dwl": ".conf",
"mango": ".conf"
};
const tasks = [];
@@ -542,6 +544,14 @@ Singleton {
onWriteFailed();
});
break;
case "mango":
MangoService.generateOutputsConfig(outputsData, success => {
if (success)
onWriteSuccess();
else
onWriteFailed();
});
break;
case "dwl":
DwlService.generateOutputsConfig(outputsData, success => {
if (success)
@@ -1032,6 +1042,7 @@ Singleton {
case "hyprland":
return parseHyprlandOutputs(content);
case "dwl":
case "mango":
return parseMangoOutputs(content);
default:
return {};
@@ -1302,7 +1313,7 @@ Singleton {
params[pair.substring(0, colonIdx).trim()] = pair.substring(colonIdx + 1).trim();
}
const name = params.name;
const name = (params.name || "").replace(/^\^/, "").replace(/\$$/, "");
if (!name)
continue;
@@ -1370,6 +1381,7 @@ Singleton {
"includeLine": "require(\"dms.outputs\")"
};
case "dwl":
case "mango":
return {
"configFile": configDir + "/mango/config.conf",
"outputsFile": configDir + "/mango/dms/outputs.conf",
@@ -1383,7 +1395,7 @@ Singleton {
function checkIncludeStatus() {
const compositor = CompositorService.compositor;
if (compositor !== "niri" && compositor !== "hyprland" && compositor !== "dwl") {
if (compositor !== "niri" && compositor !== "hyprland" && compositor !== "dwl" && compositor !== "mango") {
includeStatus = {
"exists": false,
"included": false,
@@ -1394,7 +1406,8 @@ Singleton {
}
const filename = (compositor === "niri") ? "outputs.kdl" : ((compositor === "hyprland") ? "outputs.lua" : "outputs.conf");
const compositorArg = (compositor === "dwl") ? "mangowc" : compositor;
// mango and dwl both use outputs.conf under ~/.config/mango
const compositorArg = (compositor === "dwl" || compositor === "mango") ? "mangowc" : compositor;
checkingInclude = true;
Proc.runCommand("check-outputs-include", ["dms", "config", "resolve-include", compositorArg, filename], (output, exitCode) => {
@@ -1569,6 +1582,9 @@ Singleton {
}
HyprlandService.generateOutputsConfig(outputsData, buildMergedHyprlandSettings());
break;
case "mango":
MangoService.generateOutputsConfig(outputsData);
break;
case "dwl":
DwlService.generateOutputsConfig(outputsData);
break;
@@ -317,7 +317,7 @@ StyledRect {
DankToggle {
width: parent.width
text: I18n.tr("Variable Refresh Rate")
visible: root.isConnected && !root.isDisabled && !CompositorService.isDwl && !CompositorService.isHyprland && !CompositorService.isNiri && (DisplayConfigState.outputs[root.outputName]?.vrr_supported ?? false)
visible: root.isConnected && !root.isDisabled && !CompositorService.isDwl && !CompositorService.isMango && !CompositorService.isHyprland && !CompositorService.isNiri && (DisplayConfigState.outputs[root.outputName]?.vrr_supported ?? false)
checked: {
const pendingVrr = DisplayConfigState.getPendingValue(root.outputName, "vrr");
if (pendingVrr !== undefined)
@@ -500,7 +500,7 @@ Item {
Column {
id: displayFormatColumn
visible: !CompositorService.isDwl
visible: !CompositorService.isDwl && !CompositorService.isMango
spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
+3 -1
View File
@@ -70,7 +70,7 @@ Item {
text: I18n.tr("Intelligent Auto-hide")
description: I18n.tr("Show dock when floating windows don't overlap its area")
checked: SettingsData.dockSmartAutoHide
visible: SettingsData.showDock && (CompositorService.isNiri || CompositorService.isHyprland)
visible: SettingsData.showDock && (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango)
onToggled: checked => {
if (checked && SettingsData.dockAutoHide) {
SettingsData.set("dockAutoHide", false);
@@ -284,6 +284,8 @@ Item {
modes.push("Hyprland");
} else if (CompositorService.isDwl) {
modes.push("mango");
} else if (CompositorService.isMango) {
modes.push("mango");
} else if (CompositorService.isSway) {
modes.push("Sway");
} else if (CompositorService.isScroll) {
@@ -306,6 +306,8 @@ Item {
modes.push("Hyprland");
} else if (CompositorService.isDwl) {
modes.push("mango");
} else if (CompositorService.isMango) {
modes.push("mango");
} else if (CompositorService.isSway) {
modes.push("Sway");
} else if (CompositorService.isScroll) {
+12 -5
View File
@@ -49,6 +49,7 @@ Item {
"includeLine": "require(\"dms.cursor\")"
};
case "dwl":
case "mango":
return {
"configFile": configDir + "/mango/config.conf",
"cursorFile": configDir + "/mango/dms/cursor.conf",
@@ -62,7 +63,7 @@ Item {
function checkCursorIncludeStatus() {
const compositor = CompositorService.compositor;
if (compositor !== "niri" && compositor !== "hyprland" && compositor !== "dwl") {
if (compositor !== "niri" && compositor !== "hyprland" && compositor !== "dwl" && compositor !== "mango") {
cursorIncludeStatus = {
"exists": false,
"included": false,
@@ -73,7 +74,7 @@ Item {
}
const filename = (compositor === "niri") ? "cursor.kdl" : ((compositor === "hyprland") ? "cursor.lua" : "cursor.conf");
const compositorArg = (compositor === "dwl") ? "mangowc" : compositor;
const compositorArg = (compositor === "dwl" || compositor === "mango") ? "mangowc" : compositor;
checkingCursorInclude = true;
Proc.runCommand("check-cursor-include", ["dms", "config", "resolve-include", compositorArg, filename], (output, exitCode) => {
@@ -193,7 +194,7 @@ Item {
themeColorsTab.templateDetection = JSON.parse(output.trim());
} catch (e) {}
});
if (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl)
if (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango)
checkCursorIncludeStatus();
}
@@ -2177,7 +2178,7 @@ Item {
title: I18n.tr("MangoWC Layout Overrides")
settingKey: "mangoLayout"
iconName: "crop_square"
visible: CompositorService.isDwl
visible: CompositorService.isDwl || CompositorService.isMango
SettingsToggleRow {
tab: "theme"
@@ -2334,7 +2335,7 @@ Item {
title: I18n.tr("Cursor Theme")
settingKey: "cursorTheme"
iconName: "mouse"
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango
Column {
width: parent.width
@@ -2490,6 +2491,8 @@ Item {
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;
}
minimum: 0
@@ -2510,6 +2513,10 @@ Item {
if (!updated.dwl)
updated.dwl = {};
updated.dwl.cursorHideTimeout = newValue;
} else if (CompositorService.isMango) {
if (!updated.mango)
updated.mango = {};
updated.mango.cursorHideTimeout = newValue;
}
SettingsData.set("cursorSettings", updated);
}
+2 -2
View File
@@ -37,8 +37,8 @@ Item {
"text": I18n.tr("Layout"),
"description": I18n.tr("Display and switch DWL layouts"),
"icon": "view_quilt",
"enabled": CompositorService.isDwl && DwlService.dwlAvailable,
"warning": !CompositorService.isDwl ? I18n.tr("Requires DWL compositor") : (!DwlService.dwlAvailable ? I18n.tr("DWL service not available") : undefined)
"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))
},
{
"id": "launcherButton",
+23 -9
View File
@@ -30,6 +30,7 @@ Item {
property var externalRules: []
property var activeWindows: getActiveWindows()
property string expandedExternalId: ""
readonly property string dmsRulesFileName: CompositorService.isNiri ? "dms/windowrules.kdl" : CompositorService.isMango ? "dms/windowrules.conf" : "dms/windowrules.lua"
readonly property var matchLabels: ({
"appId": I18n.tr("App ID"),
@@ -166,6 +167,13 @@ Item {
"grepPattern": "dms.windowrules",
"includeLine": "require(\"dms.windowrules\")"
};
case "mango":
return {
"configFile": configDir + "/mango/config.conf",
"rulesFile": configDir + "/mango/dms/windowrules.conf",
"grepPattern": "dms/windowrules.conf",
"includeLine": "source=./dms/windowrules.conf"
};
default:
return null;
}
@@ -173,7 +181,7 @@ Item {
function loadWindowRules() {
const compositor = CompositorService.compositor;
if (compositor !== "niri" && compositor !== "hyprland") {
if (compositor !== "niri" && compositor !== "hyprland" && compositor !== "mango") {
windowRules = [];
externalRules = [];
return;
@@ -211,11 +219,13 @@ Item {
return;
}
const compositor = CompositorService.compositor;
if (compositor !== "niri" && compositor !== "hyprland")
if (compositor !== "niri" && compositor !== "hyprland" && compositor !== "mango")
return;
Proc.runCommand("remove-windowrule", ["dms", "config", "windowrules", "remove", compositor, ruleId], (output, exitCode) => {
if (exitCode === 0) {
if (CompositorService.isMango)
MangoService.reloadConfig();
loadWindowRules();
rulesChanged();
}
@@ -231,7 +241,7 @@ Item {
return;
const compositor = CompositorService.compositor;
if (compositor !== "niri" && compositor !== "hyprland")
if (compositor !== "niri" && compositor !== "hyprland" && compositor !== "mango")
return;
let ids = windowRules.map(r => r.id);
@@ -240,6 +250,8 @@ Item {
Proc.runCommand("reorder-windowrules", ["dms", "config", "windowrules", "reorder", compositor, JSON.stringify(ids)], (output, exitCode) => {
if (exitCode === 0) {
if (CompositorService.isMango)
MangoService.reloadConfig();
loadWindowRules();
rulesChanged();
}
@@ -248,7 +260,7 @@ Item {
function checkWindowRulesIncludeStatus() {
const compositor = CompositorService.compositor;
if (compositor !== "niri" && compositor !== "hyprland") {
if (compositor !== "niri" && compositor !== "hyprland" && compositor !== "mango") {
windowRulesIncludeStatus = {
"exists": false,
"included": false,
@@ -258,7 +270,7 @@ Item {
return;
}
const filename = (compositor === "niri") ? "windowrules.kdl" : "windowrules.lua";
const filename = (compositor === "niri") ? "windowrules.kdl" : (compositor === "mango") ? "windowrules.conf" : "windowrules.lua";
checkingInclude = true;
Proc.runCommand("check-windowrules-include", ["dms", "config", "resolve-include", compositor, filename], (output, exitCode) => {
checkingInclude = false;
@@ -306,6 +318,8 @@ Item {
fixingInclude = false;
if (exitCode !== 0)
return;
if (CompositorService.isMango)
MangoService.reloadConfig();
checkWindowRulesIncludeStatus();
loadWindowRules();
});
@@ -358,7 +372,7 @@ Item {
}
Component.onCompleted: {
if (CompositorService.isNiri || CompositorService.isHyprland) {
if (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango) {
checkWindowRulesIncludeStatus();
loadWindowRules();
}
@@ -415,7 +429,7 @@ Item {
}
StyledText {
text: I18n.tr("Define rules for window behavior. Saves to %1").arg(CompositorService.isNiri ? "dms/windowrules.kdl" : "dms/windowrules.lua")
text: I18n.tr("Define rules for window behavior. Saves to %1").arg(root.dmsRulesFileName)
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
@@ -489,7 +503,7 @@ Item {
color: (showLegacy || showError || showSetup) ? Theme.withAlpha(Theme.warning, 0.15) : "transparent"
border.color: (showLegacy || showError || showSetup) ? Theme.withAlpha(Theme.warning, 0.3) : "transparent"
border.width: 1
visible: (showLegacy || showError || showSetup) && !root.checkingInclude && (CompositorService.isNiri || CompositorService.isHyprland)
visible: (showLegacy || showError || showSetup) && !root.checkingInclude && (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango)
Row {
id: warningSection
@@ -519,7 +533,7 @@ Item {
}
StyledText {
readonly property string rulesFile: CompositorService.isNiri ? "dms/windowrules.kdl" : "dms/windowrules.lua"
readonly property string rulesFile: root.dmsRulesFileName
text: warningBox.showLegacy ? I18n.tr("This install is still using hyprland.conf. Run dms setup to migrate before editing window rules in Settings.") : (warningBox.showSetup ? I18n.tr("Click 'Setup' to create %1 and add include to your compositor config.").arg(rulesFile) : I18n.tr("%1 exists but is not included. Window rules won't apply.").arg(rulesFile))
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
@@ -59,7 +59,7 @@ Item {
text: I18n.tr("Show Workspace Apps")
description: I18n.tr("Display application icons in workspace indicators")
checked: SettingsData.showWorkspaceApps
visible: CompositorService.isNiri || CompositorService.isHyprland
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango
onToggled: checked => SettingsData.set("showWorkspaceApps", checked)
}
@@ -151,7 +151,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.isSway || CompositorService.isScroll || CompositorService.isMiracle
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle
onToggled: checked => SettingsData.set("workspaceFollowFocus", checked)
}
@@ -161,7 +161,7 @@ Item {
text: I18n.tr("Show Occupied Workspaces Only")
description: I18n.tr("Display only workspaces that contain windows")
checked: SettingsData.showOccupiedWorkspacesOnly
visible: CompositorService.isNiri || CompositorService.isHyprland
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango
onToggled: checked => SettingsData.set("showOccupiedWorkspacesOnly", checked)
}
@@ -171,7 +171,7 @@ Item {
text: I18n.tr("Reverse Scrolling Direction")
description: I18n.tr("Reverse workspace switch direction when scrolling over the bar")
checked: SettingsData.reverseScrolling
visible: CompositorService.isNiri || CompositorService.isHyprland
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango
onToggled: checked => SettingsData.set("reverseScrolling", checked)
}
@@ -191,7 +191,7 @@ Item {
text: I18n.tr("Show All Tags")
description: I18n.tr("Show all 9 tags instead of only occupied tags (DWL only)")
checked: SettingsData.dwlShowAllTags
visible: CompositorService.isDwl
visible: CompositorService.isDwl || CompositorService.isMango
onToggled: checked => SettingsData.set("dwlShowAllTags", checked)
}
}
@@ -243,7 +243,7 @@ Item {
SettingsButtonGroupRow {
text: I18n.tr("Occupied Color")
model: ["none", "sec", "s", "sc", "sch", "schh"]
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango
buttonHeight: 22
minButtonWidth: 36
buttonPadding: Theme.spacingS
@@ -279,7 +279,7 @@ Item {
height: 1
color: Theme.outline
opacity: 0.15
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango
}
SettingsButtonGroupRow {
@@ -316,12 +316,12 @@ Item {
height: 1
color: Theme.outline
opacity: 0.15
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle
}
SettingsButtonGroupRow {
text: I18n.tr("Urgent Color")
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle
model: ["err", "pri", "sec", "s", "sc"]
buttonHeight: 22
minButtonWidth: 36