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

feat(Hyprland): Introduce Lua support for Hyprland configurations

- Note: We do not convert your existing conf configs to lua. This update only reflects DMS defaults state
- Updated README.md to reflect changes
- Updated Keyboard shortcut support
This commit is contained in:
purian23
2026-05-18 13:06:58 -04:00
parent 8dd891f93a
commit 0b55bf5dac
48 changed files with 3756 additions and 1057 deletions
+57 -69
View File
@@ -14,10 +14,10 @@ Singleton {
readonly property string configDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation))
readonly property string hyprDmsDir: configDir + "/hypr/dms"
readonly property string outputsPath: hyprDmsDir + "/outputs.conf"
readonly property string layoutPath: hyprDmsDir + "/layout.conf"
readonly property string cursorPath: hyprDmsDir + "/cursor.conf"
readonly property string windowrulesPath: hyprDmsDir + "/windowrules.conf"
readonly property string outputsPath: hyprDmsDir + "/outputs.lua"
readonly property string layoutPath: hyprDmsDir + "/layout.lua"
readonly property string cursorPath: hyprDmsDir + "/cursor.lua"
readonly property string windowrulesPath: hyprDmsDir + "/windowrules.lua"
property int _lastGapValue: -1
@@ -31,7 +31,7 @@ Singleton {
function ensureWindowrulesConfig() {
Proc.runCommand("hypr-ensure-windowrules", ["sh", "-c", `mkdir -p "${hyprDmsDir}" && [ ! -f "${windowrulesPath}" ] && touch "${windowrulesPath}" || true`], (output, exitCode) => {
if (exitCode !== 0)
log.warn("Failed to ensure windowrules.conf:", output);
log.warn("Failed to ensure windowrules.lua:", output);
});
}
@@ -62,6 +62,18 @@ Singleton {
return outputName;
}
function luaQuoted(str) {
return JSON.stringify(String(str ?? ""));
}
function forceFlagValue(value) {
if (value === true)
return 1;
if (value === false)
return -1;
return Number(value);
}
function generateOutputsConfig(outputsData, hyprlandSettings, callback) {
if (!outputsData || Object.keys(outputsData).length === 0) {
if (callback)
@@ -70,8 +82,7 @@ Singleton {
}
const settings = hyprlandSettings || SettingsData.hyprlandOutputSettings;
let lines = ["# Auto-generated by DMS - do not edit manually", ""];
let monitorv2Blocks = [];
let lines = ["-- Auto-generated by DMS do not edit manually", ""];
for (const outputName in outputsData) {
const output = outputsData[outputName];
@@ -82,7 +93,7 @@ Singleton {
const outputSettings = settings[identifier] || {};
if (outputSettings.disabled) {
lines.push("monitor = " + identifier + ", disable");
lines.push(`hl.monitor({ output = ${luaQuoted(identifier)}, disabled = true })`);
continue;
}
@@ -98,68 +109,42 @@ Singleton {
const position = x + "x" + y;
const scale = output.logical?.scale ?? 1.0;
let monitorLine = "monitor = " + identifier + ", " + resolution + ", " + position + ", " + scale;
const parts = [`output = ${luaQuoted(identifier)}`, `mode = ${luaQuoted(resolution)}`, `position = ${luaQuoted(position)}`, `scale = ${Number(scale)}`];
const transform = transformToHyprland(output.logical?.transform ?? "Normal");
if (transform !== 0)
monitorLine += ", transform, " + transform;
parts.push(`transform = ${transform}`);
if (output.vrr_supported) {
const vrrMode = outputSettings.vrrFullscreenOnly ? 2 : (output.vrr_enabled ? 1 : 0);
monitorLine += ", vrr, " + vrrMode;
parts.push(`vrr = ${vrrMode}`);
}
if (output.mirror && output.mirror.length > 0)
monitorLine += ", mirror, " + output.mirror;
parts.push(`mirror = ${luaQuoted(output.mirror)}`);
if (outputSettings.bitdepth && outputSettings.bitdepth !== 8)
monitorLine += ", bitdepth, " + outputSettings.bitdepth;
parts.push(`bitdepth = ${Number(outputSettings.bitdepth)}`);
if (outputSettings.colorManagement && outputSettings.colorManagement !== "auto")
monitorLine += ", cm, " + outputSettings.colorManagement;
parts.push(`cm = ${luaQuoted(outputSettings.colorManagement)}`);
if (outputSettings.sdrBrightness !== undefined && outputSettings.sdrBrightness !== 1.0)
monitorLine += ", sdrbrightness, " + outputSettings.sdrBrightness;
parts.push(`sdrbrightness = ${Number(outputSettings.sdrBrightness)}`);
if (outputSettings.sdrSaturation !== undefined && outputSettings.sdrSaturation !== 1.0)
monitorLine += ", sdrsaturation, " + outputSettings.sdrSaturation;
parts.push(`sdrsaturation = ${Number(outputSettings.sdrSaturation)}`);
lines.push(monitorLine);
if (outputSettings.supportsWideColor !== undefined)
parts.push(`supports_wide_color = ${forceFlagValue(outputSettings.supportsWideColor)}`);
const needsMonitorv2 = outputSettings.supportsHdr || outputSettings.supportsWideColor || outputSettings.sdrMinLuminance !== undefined || outputSettings.sdrMaxLuminance !== undefined || outputSettings.minLuminance !== undefined || outputSettings.maxLuminance !== undefined || outputSettings.maxAvgLuminance !== undefined;
if (outputSettings.supportsHdr !== undefined)
parts.push(`supports_hdr = ${forceFlagValue(outputSettings.supportsHdr)}`);
if (needsMonitorv2) {
let block = "monitorv2 {\n";
block += " output = " + identifier + "\n";
if (outputSettings.supportsWideColor)
block += " supports_wide_color = true\n";
if (outputSettings.supportsHdr)
block += " supports_hdr = true\n";
if (outputSettings.sdrMinLuminance !== undefined)
block += " sdr_min_luminance = " + outputSettings.sdrMinLuminance + "\n";
if (outputSettings.sdrMaxLuminance !== undefined)
block += " sdr_max_luminance = " + outputSettings.sdrMaxLuminance + "\n";
if (outputSettings.minLuminance !== undefined)
block += " min_luminance = " + outputSettings.minLuminance + "\n";
if (outputSettings.maxLuminance !== undefined)
block += " max_luminance = " + outputSettings.maxLuminance + "\n";
if (outputSettings.maxAvgLuminance !== undefined)
block += " max_avg_luminance = " + outputSettings.maxAvgLuminance + "\n";
block += "}";
monitorv2Blocks.push(block);
}
}
if (monitorv2Blocks.length > 0) {
lines.push("");
for (const block of monitorv2Blocks)
lines.push(block);
lines.push("hl.monitor({ " + parts.join(", ") + " })");
}
lines.push("");
const content = lines.join("\n");
Proc.runCommand("hypr-write-outputs", ["sh", "-c", `mkdir -p "${hyprDmsDir}" && cat > "${outputsPath}" << 'EOF'\n${content}EOF`], (output, exitCode) => {
@@ -196,17 +181,18 @@ Singleton {
const gaps = (typeof SettingsData !== "undefined" && SettingsData.hyprlandLayoutGapsOverride >= 0) ? SettingsData.hyprlandLayoutGapsOverride : defaultGaps;
const borderSize = (typeof SettingsData !== "undefined" && SettingsData.hyprlandLayoutBorderSize >= 0) ? SettingsData.hyprlandLayoutBorderSize : defaultBorderSize;
let content = `# Auto-generated by DMS - do not edit manually
let content = `-- Auto-generated by DMS do not edit manually
general {
gaps_in = ${gaps}
gaps_out = ${gaps}
border_size = ${borderSize}
}
decoration {
rounding = ${cornerRadius}
}
hl.config({
general = {
gaps_in = ${gaps},
gaps_out = ${gaps},
border_size = ${borderSize},
},
decoration = {
rounding = ${cornerRadius},
},
})
`;
Proc.runCommand("hypr-write-layout", ["sh", "-c", `mkdir -p "${hyprDmsDir}" && cat > "${layoutPath}" << 'EOF'\n${content}EOF`], (output, exitCode) => {
@@ -271,7 +257,7 @@ decoration {
const settings = typeof SettingsData !== "undefined" ? SettingsData.cursorSettings : null;
if (!settings) {
Proc.runCommand("hypr-write-cursor", ["sh", "-c", `mkdir -p "${hyprDmsDir}" && : > "${cursorPath}"`], (output, exitCode) => {
Proc.runCommand("hypr-write-cursor", ["sh", "-c", `mkdir -p "${hyprDmsDir}" && printf '%s\\n' "-- Auto-generated by DMS — do not edit manually" "" > "${cursorPath}"`], (output, exitCode) => {
if (exitCode !== 0)
log.warn("Failed to write cursor config:", output);
});
@@ -289,32 +275,34 @@ decoration {
const hasCursorSettings = hideOnKeyPress || hideOnTouch || inactiveTimeout > 0;
if (!hasTheme && !hasNonDefaultSize && !hasCursorSettings) {
Proc.runCommand("hypr-write-cursor", ["sh", "-c", `mkdir -p "${hyprDmsDir}" && : > "${cursorPath}"`], (output, exitCode) => {
Proc.runCommand("hypr-write-cursor", ["sh", "-c", `mkdir -p "${hyprDmsDir}" && printf '%s\\n' "-- Auto-generated by DMS — do not edit manually" "" > "${cursorPath}"`], (output, exitCode) => {
if (exitCode !== 0)
log.warn("Failed to write cursor config:", output);
});
return;
}
let lines = ["# Auto-generated by DMS - do not edit manually", ""];
let lines = ["-- Auto-generated by DMS do not edit manually", ""];
if (hasTheme) {
lines.push(`env = HYPRCURSOR_THEME,${themeName}`);
lines.push(`env = XCURSOR_THEME,${themeName}`);
lines.push(`hl.env("HYPRCURSOR_THEME", ${luaQuoted(themeName)})`);
lines.push(`hl.env("XCURSOR_THEME", ${luaQuoted(themeName)})`);
}
lines.push(`env = HYPRCURSOR_SIZE,${size}`);
lines.push(`env = XCURSOR_SIZE,${size}`);
lines.push(`hl.env("HYPRCURSOR_SIZE", ${luaQuoted(String(size))})`);
lines.push(`hl.env("XCURSOR_SIZE", ${luaQuoted(String(size))})`);
if (hasCursorSettings) {
lines.push("");
lines.push("cursor {");
lines.push("hl.config({");
lines.push("\tcursor = {");
if (hideOnKeyPress)
lines.push(" hide_on_key_press = true");
lines.push("\t\thide_on_key_press = true,");
if (hideOnTouch)
lines.push(" hide_on_touch = true");
lines.push("\t\thide_on_touch = true,");
if (inactiveTimeout > 0)
lines.push(` inactive_timeout = ${inactiveTimeout}`);
lines.push("}");
lines.push(`\t\tinactive_timeout = ${inactiveTimeout},`);
lines.push("\t},");
lines.push("})");
}
lines.push("");
+58 -8
View File
@@ -7,6 +7,7 @@ import Quickshell
import Quickshell.Io
import qs.Common
import qs.Services
import "../Common/ConfigIncludeResolve.js" as ConfigIncludeResolve
import "../Common/KeybindActions.js" as Actions
Singleton {
@@ -82,6 +83,7 @@ Singleton {
case "niri":
return compositorConfigDir + "/dms/binds.kdl";
case "hyprland":
return compositorConfigDir + "/dms/binds.lua";
case "mangowc":
return compositorConfigDir + "/dms/binds.conf";
default:
@@ -93,7 +95,7 @@ Singleton {
case "niri":
return compositorConfigDir + "/config.kdl";
case "hyprland":
return compositorConfigDir + "/hyprland.conf";
return compositorConfigDir + "/hyprland.lua";
case "mangowc":
return compositorConfigDir + "/config.conf";
default:
@@ -247,8 +249,8 @@ Singleton {
root.lastError = "";
root.dmsBindsIncluded = true;
root.dmsBindsFixed();
const bindsFile = root.currentProvider === "niri" ? "dms/binds.kdl" : "dms/binds.conf";
ToastService.showInfo(I18n.tr("Binds include added"), I18n.tr("%1 is now included in config").arg(bindsFile), "", "keybinds");
const bindsRel = root.currentProvider === "niri" ? "dms/binds.kdl" : root.currentProvider === "hyprland" ? "dms/binds.lua" : "dms/binds.conf";
ToastService.showInfo(I18n.tr("Binds include added"), I18n.tr("%1 is now included in config").arg(bindsRel), "", "keybinds");
Qt.callLater(root.forceReload);
}
}
@@ -262,13 +264,36 @@ Singleton {
let script;
switch (currentProvider) {
case "niri":
script = `mkdir -p "${compositorConfigDir}/dms" && touch "${compositorConfigDir}/dms/binds.kdl" && cp "${mainConfigPath}" "${backupPath}" && echo 'include "dms/binds.kdl"' >> "${mainConfigPath}"`;
script = ConfigIncludeResolve.buildRepairScript({
configFile: mainConfigPath,
backupFile: backupPath,
fragmentFile: compositorConfigDir + "/dms/binds.kdl",
grepPattern: 'include.*"dms/binds.kdl"',
includeLine: 'include "dms/binds.kdl"'
});
break;
case "hyprland":
script = `mkdir -p "${compositorConfigDir}/dms" && touch "${compositorConfigDir}/dms/binds.conf" && cp "${mainConfigPath}" "${backupPath}" && echo 'source = ./dms/binds.conf' >> "${mainConfigPath}"`;
script = ConfigIncludeResolve.buildRepairScript({
configFile: mainConfigPath,
backupFile: backupPath,
fragmentFiles: [compositorConfigDir + "/dms/binds.lua", compositorConfigDir + "/dms/binds-user.lua"],
includes: [{
grepPattern: "dms.binds",
includeLine: "require(\"dms.binds\")"
}, {
grepPattern: "dms.binds-user",
includeLine: "require(\"dms.binds-user\")"
}]
});
break;
case "mangowc":
script = `mkdir -p "${compositorConfigDir}/dms" && touch "${compositorConfigDir}/dms/binds.conf" && cp "${mainConfigPath}" "${backupPath}" && echo 'source = ./dms/binds.conf' >> "${mainConfigPath}"`;
script = ConfigIncludeResolve.buildRepairScript({
configFile: mainConfigPath,
backupFile: backupPath,
fragmentFile: compositorConfigDir + "/dms/binds.conf",
grepPattern: "source.*dms/binds.conf",
includeLine: "source = ./dms/binds.conf"
});
break;
default:
fixing = false;
@@ -321,6 +346,7 @@ Singleton {
"statusMessage": status.statusMessage ?? ""
};
}
_maybeWarnHyprlandLegacyConf();
if (!_rawData?.binds) {
_allBinds = {};
@@ -365,10 +391,13 @@ Singleton {
for (var i = 0; i < binds.length; i++) {
const bind = binds[i];
const action = bind.action || "";
const sourceStr = bind.source || "config";
const keyData = {
"key": bind.key || "",
"source": bind.source || "config",
"isOverride": bind.source === "dms",
"source": sourceStr,
"isOverride": sourceStr === "dms",
"isDMSManaged": sourceStr === "dms" || sourceStr === "dms-default",
"hasDefault": bind.hasDefault === true,
"cooldownMs": bind.cooldownMs || 0,
"flags": bind.flags || "",
"allowWhenLocked": bind.allowWhenLocked || false,
@@ -456,6 +485,19 @@ Singleton {
_pendingSavedKey = bindData.key;
}
property bool _hyprlandLegacyWarnShown: false
function _maybeWarnHyprlandLegacyConf() {
if (_hyprlandLegacyWarnShown)
return;
if (currentProvider !== "hyprland")
return;
if (!dmsStatus.exists || dmsStatus.included)
return;
_hyprlandLegacyWarnShown = true;
ToastService.showWarning(I18n.tr("Hyprland config still uses hyprlang"), I18n.tr("DMS Settings now writes Lua. Edits won't apply until you migrate."), "dms setup", "hyprland-migration");
}
function removeBind(key) {
if (!key)
return;
@@ -464,6 +506,14 @@ Singleton {
bindRemoved(key);
}
function resetBind(key) {
if (!key)
return;
removeProcess.command = ["dms", "keybinds", "reset", currentProvider, key];
removeProcess.running = true;
bindRemoved(key);
}
function isDmsAction(action) {
return Actions.isDmsAction(action);
}