feat(hyprland): add native tiling layout comp settings

This commit is contained in:
purian23
2026-09-17 21:30:41 -04:00
parent c4b1f4b880
commit 29463bbf27
6 changed files with 460 additions and 36 deletions
+12
View File
@@ -192,6 +192,18 @@ Singleton {
property int hyprlandLayoutRadiusOverride: Spec.SPEC.hyprlandLayoutRadiusOverride.def
property int hyprlandLayoutBorderSize: Spec.SPEC.hyprlandLayoutBorderSize.def
property bool hyprlandResizeOnBorder: Spec.SPEC.hyprlandResizeOnBorder.def
property string hyprlandTilingLayout: Spec.SPEC.hyprlandTilingLayout.def
property bool hyprlandDwindlePreserveSplit: Spec.SPEC.hyprlandDwindlePreserveSplit.def
property bool hyprlandDwindleSmartSplit: Spec.SPEC.hyprlandDwindleSmartSplit.def
property int hyprlandDwindleForceSplit: Spec.SPEC.hyprlandDwindleForceSplit.def
property string hyprlandMasterOrientation: Spec.SPEC.hyprlandMasterOrientation.def
property string hyprlandMasterNewStatus: Spec.SPEC.hyprlandMasterNewStatus.def
property bool hyprlandMasterNewOnTop: Spec.SPEC.hyprlandMasterNewOnTop.def
property int hyprlandMasterSize: Spec.SPEC.hyprlandMasterSize.def
property string hyprlandScrollingDirection: Spec.SPEC.hyprlandScrollingDirection.def
property int hyprlandScrollingColumnWidth: Spec.SPEC.hyprlandScrollingColumnWidth.def
property bool hyprlandScrollingFullscreenOneColumn: Spec.SPEC.hyprlandScrollingFullscreenOneColumn.def
property bool hyprlandScrollingFollowFocus: Spec.SPEC.hyprlandScrollingFollowFocus.def
property int mangoLayoutGapsOverride: Spec.SPEC.mangoLayoutGapsOverride.def
property int mangoLayoutGapsOutOverride: Spec.SPEC.mangoLayoutGapsOutOverride.def
property int mangoLayoutRadiusOverride: Spec.SPEC.mangoLayoutRadiusOverride.def
@@ -105,6 +105,54 @@ var LOCAL_SPEC = {
def: false,
onChange: "updateCompositorLayout"
},
hyprlandTilingLayout: {
def: "",
onChange: "updateCompositorLayout"
},
hyprlandDwindlePreserveSplit: {
def: false,
onChange: "updateCompositorLayout"
},
hyprlandDwindleSmartSplit: {
def: false,
onChange: "updateCompositorLayout"
},
hyprlandDwindleForceSplit: {
def: 0,
onChange: "updateCompositorLayout"
},
hyprlandMasterOrientation: {
def: "left",
onChange: "updateCompositorLayout"
},
hyprlandMasterNewStatus: {
def: "slave",
onChange: "updateCompositorLayout"
},
hyprlandMasterNewOnTop: {
def: false,
onChange: "updateCompositorLayout"
},
hyprlandMasterSize: {
def: 55,
onChange: "updateCompositorLayout"
},
hyprlandScrollingDirection: {
def: "right",
onChange: "updateCompositorLayout"
},
hyprlandScrollingColumnWidth: {
def: 50,
onChange: "updateCompositorLayout"
},
hyprlandScrollingFullscreenOneColumn: {
def: true,
onChange: "updateCompositorLayout"
},
hyprlandScrollingFollowFocus: {
def: true,
onChange: "updateCompositorLayout"
},
mangoLayoutGapsOverride: {
def: -1,
onChange: "updateCompositorLayout"
@@ -221,6 +221,157 @@ awk '$1 == "xray" { print FILENAME ":" FNR; exit }' $files 2>/dev/null`;
}
}
SettingsCard {
id: hyprTilingCard
width: parent.width
tags: ["hyprland", "layout", "tiling", "dwindle", "master", "scrolling", "general:layout"]
title: I18n.tr("Tiling layout")
settingKey: "hyprlandTilingLayout"
iconName: "view_quilt"
visible: CompositorService.isHyprland
readonly property var layoutIds: ["", "dwindle", "master", "scrolling"]
SettingsButtonGroupRow {
tags: ["hyprland", "layout", "tiling", "dwindle", "master", "scrolling"]
settingKey: "hyprlandTilingLayout"
text: I18n.tr("Layout")
model: [I18n.tr("Off"), I18n.tr("Dwindle", "Hyprland tiling layout name"), I18n.tr("Master", "Hyprland tiling layout name"), I18n.tr("Scrolling", "Hyprland tiling layout name")]
currentIndex: Math.max(0, hyprTilingCard.layoutIds.indexOf(SettingsData.hyprlandTilingLayout))
onSelectionChanged: (index, selected) => {
if (!selected)
return;
SettingsData.set("hyprlandTilingLayout", hyprTilingCard.layoutIds[index]);
}
}
SettingsToggleRow {
tags: ["hyprland", "dwindle", "preserve", "split"]
settingKey: "hyprlandDwindlePreserveSplit"
visible: SettingsData.hyprlandTilingLayout === "dwindle"
text: I18n.tr("Preserve split")
description: I18n.tr("Split direction stays fixed when the container resizes")
checked: SettingsData.hyprlandDwindlePreserveSplit
onToggled: checked => SettingsData.set("hyprlandDwindlePreserveSplit", checked)
}
SettingsToggleRow {
tags: ["hyprland", "dwindle", "smart", "split", "cursor"]
settingKey: "hyprlandDwindleSmartSplit"
visible: SettingsData.hyprlandTilingLayout === "dwindle"
text: I18n.tr("Smart split")
description: I18n.tr("Split direction follows the cursor position in the window")
checked: SettingsData.hyprlandDwindleSmartSplit
onToggled: checked => SettingsData.set("hyprlandDwindleSmartSplit", checked)
}
SettingsButtonGroupRow {
tags: ["hyprland", "dwindle", "force", "split", "direction"]
settingKey: "hyprlandDwindleForceSplit"
visible: SettingsData.hyprlandTilingLayout === "dwindle"
text: I18n.tr("Force split")
model: [I18n.tr("Follow mouse"), I18n.tr("Left"), I18n.tr("Right")]
currentIndex: SettingsData.hyprlandDwindleForceSplit
onSelectionChanged: (index, selected) => {
if (!selected)
return;
SettingsData.set("hyprlandDwindleForceSplit", index);
}
}
SettingsDropdownRow {
readonly property var ids: ["left", "right", "top", "bottom", "center"]
tags: ["hyprland", "master", "orientation", "position"]
settingKey: "hyprlandMasterOrientation"
visible: SettingsData.hyprlandTilingLayout === "master"
text: I18n.tr("Master position")
options: [I18n.tr("Left"), I18n.tr("Right"), I18n.tr("Top"), I18n.tr("Bottom"), I18n.tr("Center")]
currentValue: options[Math.max(0, ids.indexOf(SettingsData.hyprlandMasterOrientation))]
onValueChanged: value => SettingsData.set("hyprlandMasterOrientation", ids[Math.max(0, options.indexOf(value))])
}
SettingsButtonGroupRow {
readonly property var ids: ["slave", "master", "inherit"]
tags: ["hyprland", "master", "new", "window", "status", "slave"]
settingKey: "hyprlandMasterNewStatus"
visible: SettingsData.hyprlandTilingLayout === "master"
text: I18n.tr("New windows")
model: [I18n.tr("Stack"), I18n.tr("Master", "Hyprland tiling layout name"), I18n.tr("Inherit")]
currentIndex: Math.max(0, ids.indexOf(SettingsData.hyprlandMasterNewStatus))
onSelectionChanged: (index, selected) => {
if (!selected)
return;
SettingsData.set("hyprlandMasterNewStatus", ids[index]);
}
}
SettingsToggleRow {
tags: ["hyprland", "master", "new", "top", "stack"]
settingKey: "hyprlandMasterNewOnTop"
visible: SettingsData.hyprlandTilingLayout === "master"
text: I18n.tr("New windows on top of the stack")
checked: SettingsData.hyprlandMasterNewOnTop
onToggled: checked => SettingsData.set("hyprlandMasterNewOnTop", checked)
}
SettingsSliderRow {
tags: ["hyprland", "master", "size", "mfact", "ratio"]
settingKey: "hyprlandMasterSize"
visible: SettingsData.hyprlandTilingLayout === "master"
text: I18n.tr("Master size")
value: SettingsData.hyprlandMasterSize
minimum: 10
maximum: 90
unit: "%"
onSliderValueChanged: newValue => SettingsData.set("hyprlandMasterSize", newValue)
}
SettingsButtonGroupRow {
readonly property var ids: ["right", "left", "down", "up"]
tags: ["hyprland", "scrolling", "direction"]
settingKey: "hyprlandScrollingDirection"
visible: SettingsData.hyprlandTilingLayout === "scrolling"
text: I18n.tr("Direction")
model: [I18n.tr("Right"), I18n.tr("Left"), I18n.tr("Down"), I18n.tr("Up")]
currentIndex: Math.max(0, ids.indexOf(SettingsData.hyprlandScrollingDirection))
onSelectionChanged: (index, selected) => {
if (!selected)
return;
SettingsData.set("hyprlandScrollingDirection", ids[index]);
}
}
SettingsSliderRow {
tags: ["hyprland", "scrolling", "column", "width"]
settingKey: "hyprlandScrollingColumnWidth"
visible: SettingsData.hyprlandTilingLayout === "scrolling"
text: I18n.tr("Column Width")
value: SettingsData.hyprlandScrollingColumnWidth
minimum: 10
maximum: 100
unit: "%"
onSliderValueChanged: newValue => SettingsData.set("hyprlandScrollingColumnWidth", newValue)
}
SettingsToggleRow {
tags: ["hyprland", "scrolling", "fullscreen", "single", "column"]
settingKey: "hyprlandScrollingFullscreenOneColumn"
visible: SettingsData.hyprlandTilingLayout === "scrolling"
text: I18n.tr("Fullscreen single column")
checked: SettingsData.hyprlandScrollingFullscreenOneColumn
onToggled: checked => SettingsData.set("hyprlandScrollingFullscreenOneColumn", checked)
}
SettingsToggleRow {
tags: ["hyprland", "scrolling", "follow", "focus", "scroll"]
settingKey: "hyprlandScrollingFollowFocus"
visible: SettingsData.hyprlandTilingLayout === "scrolling"
text: I18n.tr("Follow focus")
checked: SettingsData.hyprlandScrollingFollowFocus
onToggled: checked => SettingsData.set("hyprlandScrollingFollowFocus", checked)
}
}
SettingsCard {
width: parent.width
tags: ["hyprland", "layout", "gaps", "radius", "window", "border", "rounding"]
@@ -234,7 +385,6 @@ awk '$1 == "xray" { print FILENAME ":" FNR; exit }' $files 2>/dev/null`;
settingKey: "hyprlandLayoutGapsMode"
resetKeys: ["hyprlandLayoutGapsOverride"]
text: I18n.tr("Gaps")
description: I18n.tr("Auto follows bar spacing, Off keeps your %1 config").arg("Hyprland")
model: [I18n.tr("Auto"), I18n.tr("Custom"), I18n.tr("Off")]
currentIndex: {
if (SettingsData.hyprlandLayoutGapsOverride === -2)
+24 -6
View File
@@ -406,20 +406,25 @@ Singleton {
if (frameEnabled && SettingsData.frameMode !== "connected")
xrayNamespaces.push("dms:frame");
const tilingLayout = typeof SettingsData !== "undefined" ? SettingsData.hyprlandTilingLayout : "";
const generalLines = [];
if (manageGaps)
generalLines.push(`gaps_in = ${gapsIn},`, `gaps_out = ${gapsOut},`);
generalLines.push(`border_size = ${borderSize},`, `resize_on_border = ${resizeOnBorder},`);
if (tilingLayout)
generalLines.push(`layout = ${luaString(tilingLayout)},`);
const sections = [`\tgeneral = {\n${generalLines.map(l => "\t\t" + l).join("\n")}\n\t},`];
const tilingLines = tilingLayoutLines(tilingLayout);
if (tilingLines.length)
sections.push(`\t${tilingLayout} = {\n${tilingLines.map(l => "\t\t" + l).join("\n")}\n\t},`);
sections.push(`\tdecoration = {\n\t\trounding = ${cornerRadius},\n\t},`);
let content = `-- Auto-generated by DMS — do not edit manually
hl.config({
general = {
${generalLines.map(l => "\t\t" + l).join("\n")}
},
decoration = {
rounding = ${cornerRadius},
},
${sections.join("\n")}
})
`;
@@ -469,6 +474,19 @@ hl.layer_rule({
});
}
function tilingLayoutLines(layout) {
switch (layout) {
case "dwindle":
return [`preserve_split = ${SettingsData.hyprlandDwindlePreserveSplit},`, `smart_split = ${SettingsData.hyprlandDwindleSmartSplit},`, `force_split = ${SettingsData.hyprlandDwindleForceSplit},`];
case "master":
return [`orientation = ${luaString(SettingsData.hyprlandMasterOrientation)},`, `new_status = ${luaString(SettingsData.hyprlandMasterNewStatus)},`, `new_on_top = ${SettingsData.hyprlandMasterNewOnTop},`, `mfact = ${SettingsData.hyprlandMasterSize / 100},`];
case "scrolling":
return [`direction = ${luaString(SettingsData.hyprlandScrollingDirection)},`, `column_width = ${SettingsData.hyprlandScrollingColumnWidth / 100},`, `fullscreen_on_one_column = ${SettingsData.hyprlandScrollingFullscreenOneColumn},`, `follow_focus = ${SettingsData.hyprlandScrollingFollowFocus},`];
default:
return [];
}
}
function generateCursorConfig() {
if (!CompositorService.isHyprland)
return;
@@ -25,6 +25,7 @@ ALLOWED = [
{"PIN", "Pin"}, # WPS PIN acronym vs the verb "pin"
{"Device", "device"}, # label vs inline generic-noun fallback
{"Until %1", "until %1"}, # sentence-initial vs mid-sentence position
{"Up", "up"}, # scroll direction vs uptime prefix
{"Custom Color", "Custom color"}, # dms-plugins checkout, not this repo
{"Media Player", "Media player"}, # dms-plugins checkout, not this repo
]
@@ -10599,6 +10599,67 @@
],
"conditionKey": "layoutCapable"
},
{
"section": "hyprlandScrollingColumnWidth",
"label": "Column Width",
"tabIndex": 37,
"category": "System & integrations",
"keywords": [
"column",
"hyprland",
"integrations",
"scrolling",
"system",
"width"
],
"conditionKey": "layoutCapable"
},
{
"section": "hyprlandScrollingDirection",
"label": "Direction",
"tabIndex": 37,
"category": "System & integrations",
"keywords": [
"direction",
"hyprland",
"integrations",
"scrolling",
"system"
],
"conditionKey": "layoutCapable"
},
{
"section": "hyprlandScrollingFollowFocus",
"label": "Follow focus",
"tabIndex": 37,
"category": "System & integrations",
"keywords": [
"focus",
"follow",
"hyprland",
"integrations",
"scroll",
"scrolling",
"system"
],
"conditionKey": "layoutCapable"
},
{
"section": "hyprlandDwindleForceSplit",
"label": "Force split",
"tabIndex": 37,
"category": "System & integrations",
"keywords": [
"direction",
"dwindle",
"force",
"hyprland",
"integrations",
"split",
"system"
],
"conditionKey": "layoutCapable"
},
{
"section": "hyprlandLayoutBarXrayEnabled",
"label": "Frame xray",
@@ -10651,35 +10712,37 @@
"description": "Blur against the wallpaper even with xray off",
"conditionKey": "isNiri"
},
{
"section": "hyprlandScrollingFullscreenOneColumn",
"label": "Fullscreen single column",
"tabIndex": 37,
"category": "System & integrations",
"keywords": [
"column",
"fullscreen",
"hyprland",
"integrations",
"scrolling",
"single",
"system"
],
"conditionKey": "layoutCapable"
},
{
"section": "hyprlandLayoutGapsMode",
"label": "Gaps",
"tabIndex": 37,
"category": "System & integrations",
"keywords": [
"auto",
"config",
"follows",
"gap",
"gaps",
"hyprland",
"inner",
"integrations",
"keeps",
"margin",
"margins",
"outer",
"override",
"padding",
"panel",
"spacing",
"statusbar",
"system",
"taskbar",
"topbar",
"unmanaged"
],
"description": "Auto follows bar spacing, Off keeps your %1 config",
"conditionKey": "layoutCapable"
},
{
@@ -10783,32 +10846,30 @@
"tabIndex": 37,
"category": "System & integrations",
"keywords": [
"auto",
"background",
"beneath",
"bg",
"blurred",
"border",
"config",
"follows",
"gap",
"desktop",
"gaps",
"hyprland",
"image",
"integrations",
"keeps",
"layout",
"margin",
"margins",
"overrides",
"padding",
"panel",
"picture",
"radius",
"rounding",
"spacing",
"statusbar",
"show",
"surfaces",
"system",
"taskbar",
"topbar",
"window"
"wallpaper",
"window",
"windows"
],
"icon": "crop_square",
"description": "Auto follows bar spacing, Off keeps your %1 config",
"description": "Blurred surfaces show the wallpaper, not the windows beneath",
"conditionKey": "isHyprland"
},
{
@@ -10879,6 +10940,72 @@
"description": "Auto follows bar spacing, Off keeps your %1 config",
"conditionKey": "isNiri"
},
{
"section": "hyprlandMasterOrientation",
"label": "Master position",
"tabIndex": 37,
"category": "System & integrations",
"keywords": [
"hyprland",
"integrations",
"master",
"orientation",
"position",
"system"
],
"conditionKey": "layoutCapable"
},
{
"section": "hyprlandMasterSize",
"label": "Master size",
"tabIndex": 37,
"category": "System & integrations",
"keywords": [
"hyprland",
"integrations",
"master",
"mfact",
"ratio",
"size",
"system"
],
"conditionKey": "layoutCapable"
},
{
"section": "hyprlandMasterNewStatus",
"label": "New windows",
"tabIndex": 37,
"category": "System & integrations",
"keywords": [
"hyprland",
"integrations",
"master",
"new",
"slave",
"status",
"system",
"window",
"windows"
],
"conditionKey": "layoutCapable"
},
{
"section": "hyprlandMasterNewOnTop",
"label": "New windows on top of the stack",
"tabIndex": 37,
"category": "System & integrations",
"keywords": [
"hyprland",
"integrations",
"master",
"new",
"stack",
"system",
"top",
"windows"
],
"conditionKey": "layoutCapable"
},
{
"section": "compositorNotifications",
"label": "Notifications",
@@ -11001,6 +11128,27 @@
"icon": "overview",
"conditionKey": "isNiri"
},
{
"section": "hyprlandDwindlePreserveSplit",
"label": "Preserve split",
"tabIndex": 37,
"category": "System & integrations",
"keywords": [
"container",
"direction",
"dwindle",
"fixed",
"hyprland",
"integrations",
"preserve",
"resizes",
"split",
"stays",
"system"
],
"description": "Split direction stays fixed when the container resizes",
"conditionKey": "layoutCapable"
},
{
"section": "hyprlandResizeOnBorder",
"label": "Resize on border",
@@ -11032,6 +11180,53 @@
],
"conditionKey": "layoutCapable"
},
{
"section": "hyprlandDwindleSmartSplit",
"label": "Smart split",
"tabIndex": 37,
"category": "System & integrations",
"keywords": [
"cursor",
"direction",
"dwindle",
"follows",
"hyprland",
"integrations",
"position",
"smart",
"split",
"system",
"window"
],
"description": "Split direction follows the cursor position in the window",
"conditionKey": "layoutCapable"
},
{
"section": "hyprlandTilingLayout",
"label": "Tiling layout",
"tabIndex": 37,
"category": "System & integrations",
"keywords": [
"container",
"direction",
"dwindle",
"fixed",
"general:layout",
"hyprland",
"integrations",
"layout",
"master",
"resizes",
"scrolling",
"split",
"stays",
"system",
"tiling"
],
"icon": "view_quilt",
"description": "Split direction stays fixed when the container resizes",
"conditionKey": "isHyprland"
},
{
"section": "niriLayoutGapsOverride",
"label": "Window gaps",