1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-06-08 04:09:15 -04:00
Files
DankMaterialShell/quickshell/Services/HyprlandService.qml
T

514 lines
18 KiB
QML

pragma Singleton
pragma ComponentBehavior: Bound
import QtCore
import QtQuick
import Quickshell
import Quickshell.Hyprland
import qs.Common
import qs.Services
Singleton {
id: root
readonly property var log: Log.scoped("HyprlandService")
readonly property string configDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation))
readonly property string hyprDmsDir: configDir + "/hypr/dms"
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"
readonly property bool luaConfigActive: CompositorService.isHyprland && (Hyprland.usingLua === true || luaConfigDetected)
property int _lastGapValue: -1
property bool luaConfigDetected: false
property bool luaConfigStatusReady: false
property bool luaConfigStatusLoading: false
property string luaConfigFormat: ""
onLuaConfigActiveChanged: {
if (luaConfigActive)
ensureDmsLuaConfigs();
}
Component.onCompleted: {
if (CompositorService.isHyprland) {
refreshLuaConfigStatus();
if (luaConfigActive)
ensureDmsLuaConfigs();
}
}
function ensureDmsLuaConfigs() {
Qt.callLater(generateLayoutConfig);
Qt.callLater(ensureWindowrulesConfig);
}
function ensureWindowrulesConfig() {
if (!canWriteLuaConfig("windowrules"))
return;
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.lua:", output);
});
}
Connections {
target: SettingsData
function onBarConfigsChanged() {
if (!CompositorService.isHyprland)
return;
const newGaps = Math.max(4, (SettingsData.barConfigs[0]?.spacing ?? 4));
if (newGaps === root._lastGapValue)
return;
root._lastGapValue = newGaps;
generateLayoutConfig();
}
}
Connections {
target: CompositorService
function onIsHyprlandChanged() {
if (CompositorService.isHyprland) {
refreshLuaConfigStatus();
if (luaConfigActive)
ensureDmsLuaConfigs();
return;
}
luaConfigDetected = false;
luaConfigStatusReady = false;
luaConfigStatusLoading = false;
luaConfigFormat = "";
}
}
function getOutputIdentifier(output, outputName) {
if (SettingsData.displayNameMode === "model" && output.make && output.model)
return ("desc:" + output.make + " " + output.model + " " + (output.serial || "Unknown")).replace(/,/g, "");
return outputName;
}
function luaQuoted(str) {
return JSON.stringify(String(str ?? ""));
}
function refreshLuaConfigStatus() {
if (!CompositorService.isHyprland) {
luaConfigDetected = false;
luaConfigStatusReady = false;
luaConfigStatusLoading = false;
luaConfigFormat = "";
return;
}
if (luaConfigStatusLoading)
return;
luaConfigStatusLoading = true;
Proc.runCommand("hypr-lua-config-status", ["dms", "config", "resolve-include", "hyprland", "outputs.lua"], (output, exitCode) => {
luaConfigStatusLoading = false;
luaConfigStatusReady = true;
if (exitCode !== 0) {
luaConfigDetected = false;
luaConfigFormat = "";
return;
}
try {
const status = JSON.parse(output.trim());
luaConfigFormat = status.configFormat ?? "";
luaConfigDetected = luaConfigFormat === "lua" && status.readOnly !== true;
} catch (e) {
luaConfigDetected = false;
luaConfigFormat = "";
}
});
}
function canWriteLuaConfig(name) {
if (luaConfigActive)
return true;
if (CompositorService.isHyprland && !luaConfigStatusReady && !luaConfigStatusLoading)
refreshLuaConfigStatus();
if (CompositorService.isHyprland && (luaConfigStatusLoading || !luaConfigStatusReady)) {
log.debug("Deferring Hyprland", name || "config", "Lua write until config format is known");
return false;
}
log.info("Skipping Hyprland", name || "config", "Lua write because the active Hyprland config is not Lua");
return false;
}
function forceFlagValue(value) {
if (value === true)
return 1;
if (value === false)
return -1;
return Number(value);
}
function generateOutputsConfig(outputsData, hyprlandSettings, callback) {
if (!canWriteLuaConfig("outputs")) {
if (callback)
callback(false);
return;
}
if (!outputsData || Object.keys(outputsData).length === 0) {
if (callback)
callback(false);
return;
}
const settings = hyprlandSettings || SettingsData.hyprlandOutputSettings;
let lines = ["-- Auto-generated by DMS — do not edit manually", ""];
for (const outputName in outputsData) {
const output = outputsData[outputName];
if (!output)
continue;
const identifier = getOutputIdentifier(output, outputName);
const outputSettings = settings[identifier] || {};
if (outputSettings.disabled) {
lines.push(`hl.monitor({ output = ${luaQuoted(identifier)}, disabled = true })`);
continue;
}
let resolution = "preferred";
if (output.modes && output.current_mode !== undefined) {
const mode = output.modes[output.current_mode];
if (mode)
resolution = mode.width + "x" + mode.height + "@" + (mode.refresh_rate / 1000).toFixed(3);
}
const x = output.logical?.x ?? 0;
const y = output.logical?.y ?? 0;
const position = x + "x" + y;
const scale = output.logical?.scale ?? 1.0;
const parts = [`output = ${luaQuoted(identifier)}`, `mode = ${luaQuoted(resolution)}`, `position = ${luaQuoted(position)}`, `scale = ${Number(scale)}`];
const transform = transformToHyprland(output.logical?.transform ?? "Normal");
if (transform !== 0)
parts.push(`transform = ${transform}`);
if (output.vrr_supported) {
const vrrMode = outputSettings.vrrFullscreenOnly ? 2 : (output.vrr_enabled ? 1 : 0);
parts.push(`vrr = ${vrrMode}`);
}
if (output.mirror && output.mirror.length > 0)
parts.push(`mirror = ${luaQuoted(output.mirror)}`);
if (outputSettings.bitdepth && outputSettings.bitdepth !== 8)
parts.push(`bitdepth = ${Number(outputSettings.bitdepth)}`);
if (outputSettings.colorManagement && outputSettings.colorManagement !== "auto")
parts.push(`cm = ${luaQuoted(outputSettings.colorManagement)}`);
if (outputSettings.sdrBrightness !== undefined && outputSettings.sdrBrightness !== 1.0)
parts.push(`sdrbrightness = ${Number(outputSettings.sdrBrightness)}`);
if (outputSettings.sdrSaturation !== undefined && outputSettings.sdrSaturation !== 1.0)
parts.push(`sdrsaturation = ${Number(outputSettings.sdrSaturation)}`);
if (outputSettings.supportsWideColor !== undefined)
parts.push(`supports_wide_color = ${forceFlagValue(outputSettings.supportsWideColor)}`);
if (outputSettings.supportsHdr !== undefined)
parts.push(`supports_hdr = ${forceFlagValue(outputSettings.supportsHdr)}`);
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) => {
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.isHyprland)
reloadConfig();
if (callback)
callback(true);
});
}
function reloadConfig() {
Proc.runCommand("hyprctl-reload", ["hyprctl", "reload"], (output, exitCode) => {
if (exitCode !== 0)
log.warn("hyprctl reload failed:", output);
});
}
function generateLayoutConfig() {
if (!CompositorService.isHyprland)
return;
if (!canWriteLuaConfig("layout"))
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.hyprlandLayoutRadiusOverride >= 0) ? SettingsData.hyprlandLayoutRadiusOverride : defaultRadius;
const gaps = (typeof SettingsData !== "undefined" && SettingsData.hyprlandLayoutGapsOverride >= 0) ? SettingsData.hyprlandLayoutGapsOverride : defaultGaps;
const borderSize = (typeof SettingsData !== "undefined" && SettingsData.hyprlandLayoutBorderSize >= 0) ? SettingsData.hyprlandLayoutBorderSize : defaultBorderSize;
const resizeOnBorder = (typeof SettingsData !== "undefined" && SettingsData.hyprlandResizeOnBorder) ? true : false;
let content = `-- Auto-generated by DMS do not edit manually
hl.config({
general = {
gaps_in = ${gaps},
gaps_out = ${gaps},
border_size = ${borderSize},
resize_on_border = ${resizeOnBorder},
},
decoration = {
rounding = ${cornerRadius},
},
})
`;
Proc.runCommand("hypr-write-layout", ["sh", "-c", `mkdir -p "${hyprDmsDir}" && 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 transformToHyprland(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 hyprlandToTransform(value) {
switch (value) {
case 0:
return "Normal";
case 1:
return "90";
case 2:
return "180";
case 3:
return "270";
case 4:
return "Flipped";
case 5:
return "Flipped90";
case 6:
return "Flipped180";
case 7:
return "Flipped270";
default:
return "Normal";
}
}
function generateCursorConfig() {
if (!CompositorService.isHyprland)
return;
if (!canWriteLuaConfig("cursor"))
return;
const settings = typeof SettingsData !== "undefined" ? SettingsData.cursorSettings : null;
if (!settings) {
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;
}
const themeName = settings.theme === "System Default" ? (SettingsData.systemDefaultCursorTheme || "") : settings.theme;
const size = settings.size || 24;
const hideOnKeyPress = settings.hyprland?.hideOnKeyPress || false;
const hideOnTouch = settings.hyprland?.hideOnTouch || false;
const inactiveTimeout = settings.hyprland?.inactiveTimeout || 0;
const hasTheme = themeName && themeName.length > 0;
const hasNonDefaultSize = size !== 24;
const hasCursorSettings = hideOnKeyPress || hideOnTouch || inactiveTimeout > 0;
if (!hasTheme && !hasNonDefaultSize && !hasCursorSettings) {
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", ""];
if (hasTheme) {
lines.push(`hl.env("HYPRCURSOR_THEME", ${luaQuoted(themeName)})`);
lines.push(`hl.env("XCURSOR_THEME", ${luaQuoted(themeName)})`);
}
lines.push(`hl.env("HYPRCURSOR_SIZE", ${luaQuoted(String(size))})`);
lines.push(`hl.env("XCURSOR_SIZE", ${luaQuoted(String(size))})`);
if (hasCursorSettings) {
lines.push("");
lines.push("hl.config({");
lines.push("\tcursor = {");
if (hideOnKeyPress)
lines.push("\t\thide_on_key_press = true,");
if (hideOnTouch)
lines.push("\t\thide_on_touch = true,");
if (inactiveTimeout > 0)
lines.push(`\t\tinactive_timeout = ${inactiveTimeout},`);
lines.push("\t},");
lines.push("})");
}
lines.push("");
const content = lines.join("\n");
Proc.runCommand("hypr-write-cursor", ["sh", "-c", `mkdir -p "${hyprDmsDir}" && cat > "${cursorPath}" << 'EOF'\n${content}EOF`], (output, exitCode) => {
if (exitCode !== 0) {
log.warn("Failed to write cursor config:", output);
return;
}
if (hasTheme)
Proc.runCommand("hyprctl-setcursor", ["hyprctl", "setcursor", themeName, String(size)], () => {});
reloadConfig();
});
}
function renameWorkspace(newName) {
if (!Hyprland.focusedWorkspace)
return;
const wsId = Hyprland.focusedWorkspace.id;
if (!wsId)
return;
const fullName = wsId + " " + newName;
if (luaConfigActive) {
Hyprland.dispatch(`hl.dsp.workspace.rename({ workspace = ${luaValue(wsId)}, name = ${luaString(fullName)} })`);
} else {
Hyprland.dispatch(`renameworkspace ${wsId} ${fullName}`);
}
}
function focusWorkspace(workspace) {
if (luaConfigActive) {
Hyprland.dispatch(`hl.dsp.focus({ workspace = ${luaValue(workspace)} })`);
} else {
Hyprland.dispatch(`workspace ${workspace}`);
}
}
function luaString(value) {
return `"${String(value ?? "").replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
}
function luaValue(value) {
const text = String(value ?? "");
return /^[-+]?\d+$/.test(text) ? text : luaString(text);
}
function windowSelector(windowAddress) {
if (!windowAddress)
return "";
const text = String(windowAddress);
if (text.startsWith("address:"))
return text;
return `address:${text.startsWith("0x") ? text : "0x" + text}`;
}
function focusWindow(windowAddress) {
const selector = windowSelector(windowAddress);
if (!selector)
return;
if (luaConfigActive) {
Hyprland.dispatch(`hl.dsp.focus({ window = ${luaString(selector)} })`);
} else {
Hyprland.dispatch(`focuswindow ${selector}`);
}
}
function closeWindow(windowAddress) {
const selector = windowSelector(windowAddress);
if (!selector)
return;
if (luaConfigActive) {
Hyprland.dispatch(`hl.dsp.window.close(${luaString(selector)})`);
} else {
Hyprland.dispatch(`closewindow ${selector}`);
}
}
function moveToWorkspace(workspace, windowAddress, follow = true) {
const selector = windowSelector(windowAddress);
if (!selector)
return;
if (luaConfigActive) {
Hyprland.dispatch(`hl.dsp.window.move({ workspace = ${luaValue(workspace)}, window = ${luaString(selector)}, follow = ${follow ? "true" : "false"} })`);
} else {
const dispatcher = follow ? "movetoworkspace" : "movetoworkspacesilent";
Hyprland.dispatch(`${dispatcher} ${workspace},${selector}`);
}
}
function toggleSpecial(specialName) {
if (luaConfigActive) {
Hyprland.dispatch(`hl.dsp.workspace.toggle_special(${luaString(specialName)})`);
} else {
Hyprland.dispatch("togglespecialworkspace " + specialName);
}
}
function exit() {
if (luaConfigActive) {
Hyprland.dispatch("hl.dsp.exit()");
} else {
Hyprland.dispatch("exit");
}
}
function dpmsOff() {
if (luaConfigActive) {
Hyprland.dispatch(`hl.dsp.dpms({ action = "disable" })`);
} else {
Hyprland.dispatch("dpms off");
}
}
function dpmsOn() {
if (luaConfigActive) {
Hyprland.dispatch(`hl.dsp.dpms({ action = "enable" })`);
} else {
Hyprland.dispatch("dpms on");
}
}
}