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

hyprland: scale overview displays relative to their actual scale

port 1.5
This commit is contained in:
bbedward
2026-07-15 10:10:33 -04:00
parent 0440e40a82
commit 72a71cacba
2 changed files with 200 additions and 155 deletions
@@ -1,5 +1,4 @@
import QtQuick import QtQuick
import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Hyprland import Quickshell.Hyprland
import qs.Common import qs.Common
@@ -80,7 +79,6 @@ Item {
readonly property int displayWorkspaceCount: displayedWorkspaceIds.length readonly property int displayWorkspaceCount: displayedWorkspaceIds.length
readonly property int effectiveColumns: SettingsData.overviewColumns readonly property int effectiveColumns: SettingsData.overviewColumns
readonly property int effectiveRows: Math.max(SettingsData.overviewRows, Math.ceil(displayWorkspaceCount / effectiveColumns))
function getWorkspaceMonitorName(workspaceId) { function getWorkspaceMonitorName(workspaceId) {
if (!allWorkspaces || !workspaceId) if (!allWorkspaces || !workspaceId)
@@ -107,21 +105,53 @@ Item {
} }
} }
function getWorkspaceViewportBounds(workspaceId) { function monitorIpcForWorkspace(workspaceId) {
const workspace = allWorkspaces?.find(ws => ws?.id === workspaceId); const workspace = allWorkspaces?.find(ws => ws?.id === workspaceId);
const mon = workspace?.monitor?.lastIpcObject || monitor?.lastIpcObject || {}; return workspace?.monitor?.lastIpcObject ?? monitor?.lastIpcObject ?? null;
const reserved = mon.reserved || [0, 0, 0, 0]; }
const x = (mon.x ?? 0) + (reserved[0] ?? 0); function monitorLogicalSize(ipc) {
const y = (mon.y ?? 0) + (reserved[1] ?? 0); if (!ipc || !ipc.width || !ipc.height)
const width = Math.max((mon.width ?? monitorPhysicalWidth) - (reserved[0] ?? 0) - (reserved[2] ?? 0), 1); return {
const height = Math.max((mon.height ?? monitorPhysicalHeight) - (reserved[1] ?? 0) - (reserved[3] ?? 0), 1); "width": monitorPhysicalWidth,
const scale = Math.min(root.workspaceImplicitWidth / width, root.workspaceImplicitHeight / height); "height": monitorPhysicalHeight
};
const monScale = ipc.scale > 0 ? ipc.scale : 1;
const rotated = ((ipc.transform ?? 0) % 2) === 1;
return {
"width": (rotated ? ipc.height : ipc.width) / monScale,
"height": (rotated ? ipc.width : ipc.height) / monScale
};
}
function cellForWorkspace(workspaceId) {
const cell = gridLayout.cells.find(c => c.id === workspaceId);
if (cell)
return cell;
return {
"id": workspaceId,
"x": 0,
"y": 0,
"width": workspaceImplicitWidth,
"height": workspaceImplicitHeight
};
}
function getWorkspaceViewportBounds(workspaceId, cellWidth, cellHeight) {
const ipc = monitorIpcForWorkspace(workspaceId) ?? {};
const logical = monitorLogicalSize(ipc);
const reserved = ipc.reserved || [0, 0, 0, 0];
const x = (ipc.x ?? 0) + (reserved[0] ?? 0);
const y = (ipc.y ?? 0) + (reserved[1] ?? 0);
const width = Math.max(logical.width - (reserved[0] ?? 0) - (reserved[2] ?? 0), 1);
const height = Math.max(logical.height - (reserved[1] ?? 0) - (reserved[3] ?? 0), 1);
return { return {
"x": x, "x": x,
"y": y, "y": y,
"scale": scale "scale": Math.min(cellWidth / width, cellHeight / height)
}; };
} }
@@ -143,6 +173,56 @@ Item {
property int draggingFromWorkspace: -1 property int draggingFromWorkspace: -1
property int draggingTargetWorkspace: -1 property int draggingTargetWorkspace: -1
readonly property var gridLayout: {
const ids = displayedWorkspaceIds;
const columns = effectiveColumns;
if (!ids || ids.length === 0 || columns < 1)
return {
"cells": [],
"width": 0,
"height": 0
};
const rows = [];
for (let i = 0; i < ids.length; i += columns) {
rows.push(ids.slice(i, i + columns).map(id => {
const logical = monitorLogicalSize(monitorIpcForWorkspace(id));
return {
"id": id,
"width": logical.width * scale,
"height": logical.height * scale
};
}));
}
const rowWidth = row => row.reduce((acc, cell) => acc + cell.width, 0) + workspaceSpacing * (row.length - 1);
const totalWidth = rows.reduce((acc, row) => Math.max(acc, rowWidth(row)), 0);
const cells = [];
let cursorY = 0;
for (const row of rows) {
const rowHeight = row.reduce((acc, cell) => Math.max(acc, cell.height), 0);
let cursorX = (totalWidth - rowWidth(row)) / 2;
for (const cell of row) {
cells.push({
"id": cell.id,
"x": cursorX,
"y": cursorY + (rowHeight - cell.height) / 2,
"width": cell.width,
"height": cell.height
});
cursorX += cell.width + workspaceSpacing;
}
cursorY += rowHeight + workspaceSpacing;
}
return {
"cells": cells,
"width": totalWidth,
"height": cursorY - workspaceSpacing
};
}
implicitWidth: overviewBackground.implicitWidth + Theme.spacingL * 2 implicitWidth: overviewBackground.implicitWidth + Theme.spacingL * 2
implicitHeight: overviewBackground.implicitHeight + Theme.spacingL * 2 implicitHeight: overviewBackground.implicitHeight + Theme.spacingL * 2
@@ -166,8 +246,8 @@ Item {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingL anchors.margins: Theme.spacingL
implicitWidth: workspaceColumnLayout.implicitWidth + padding * 2 implicitWidth: workspaceGrid.implicitWidth + padding * 2
implicitHeight: workspaceColumnLayout.implicitHeight + padding * 2 implicitHeight: workspaceGrid.implicitHeight + padding * 2
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.surfaceContainer color: Theme.surfaceContainer
@@ -182,84 +262,78 @@ Item {
shadowEnabled: Theme.elevationEnabled shadowEnabled: Theme.elevationEnabled
} }
ColumnLayout { Item {
id: workspaceColumnLayout id: workspaceGrid
z: root.workspaceZ z: root.workspaceZ
anchors.centerIn: parent anchors.centerIn: parent
spacing: workspaceSpacing implicitWidth: root.gridLayout.width
implicitHeight: root.gridLayout.height
Repeater { Repeater {
model: root.effectiveRows model: root.gridLayout.cells.length
delegate: RowLayout {
id: row
property int rowIndex: index
spacing: workspaceSpacing
Repeater { Rectangle {
model: root.effectiveColumns id: workspace
Rectangle { required property int index
id: workspace readonly property var cell: root.gridLayout.cells[index] ?? null
property int colIndex: index property int workspaceValue: cell?.id ?? -1
property int workspaceIndex: rowIndex * root.effectiveColumns + colIndex property bool workspaceExists: (root.allWorkspaceIds && workspaceValue > 0) ? root.allWorkspaceIds.includes(workspaceValue) : false
property int workspaceValue: (root.displayedWorkspaceIds && workspaceIndex < root.displayedWorkspaceIds.length) ? root.displayedWorkspaceIds[workspaceIndex] : -1 property var workspaceObj: (workspaceExists && Hyprland.workspaces?.values) ? Hyprland.workspaces.values.find(ws => ws?.id === workspaceValue) : null
property bool workspaceExists: (root.allWorkspaceIds && workspaceValue > 0) ? root.allWorkspaceIds.includes(workspaceValue) : false property bool isActive: workspaceObj?.active ?? false
property var workspaceObj: (workspaceExists && Hyprland.workspaces?.values) ? Hyprland.workspaces.values.find(ws => ws?.id === workspaceValue) : null property bool isOnThisMonitor: (workspaceObj && root.monitor) ? (workspaceObj.monitor?.name === root.monitor.name) : true
property bool isActive: workspaceObj?.active ?? false property bool hasWindows: (workspaceValue > 0) ? root.workspaceHasWindows(workspaceValue) : false
property bool isOnThisMonitor: (workspaceObj && root.monitor) ? (workspaceObj.monitor?.name === root.monitor.name) : true property color defaultWorkspaceColor: workspaceExists ? Theme.surfaceContainer : Theme.withAlpha(Theme.surfaceContainer, 0.3)
property bool hasWindows: (workspaceValue > 0) ? root.workspaceHasWindows(workspaceValue) : false property color hoveredWorkspaceColor: Qt.lighter(defaultWorkspaceColor, 1.1)
property string workspaceMonitorName: (workspaceValue > 0) ? root.getWorkspaceMonitorName(workspaceValue) : "" property color hoveredBorderColor: Theme.surfaceVariant
property color defaultWorkspaceColor: workspaceExists ? Theme.surfaceContainer : Theme.withAlpha(Theme.surfaceContainer, 0.3) property bool hoveredWhileDragging: false
property color hoveredWorkspaceColor: Qt.lighter(defaultWorkspaceColor, 1.1) property bool shouldShowActiveIndicator: isActive && isOnThisMonitor && hasWindows
property color hoveredBorderColor: Theme.surfaceVariant
property bool hoveredWhileDragging: false
property bool shouldShowActiveIndicator: isActive && isOnThisMonitor && hasWindows
visible: workspaceValue !== -1 visible: workspaceValue !== -1
implicitWidth: root.workspaceImplicitWidth x: cell?.x ?? 0
implicitHeight: root.workspaceImplicitHeight y: cell?.y ?? 0
color: hoveredWhileDragging ? hoveredWorkspaceColor : defaultWorkspaceColor width: cell?.width ?? 0
radius: Theme.cornerRadius height: cell?.height ?? 0
border.width: 2 color: hoveredWhileDragging ? hoveredWorkspaceColor : defaultWorkspaceColor
border.color: hoveredWhileDragging ? hoveredBorderColor : (shouldShowActiveIndicator ? root.activeBorderColor : Theme.withAlpha(root.activeBorderColor, 0)) radius: Theme.cornerRadius
border.width: 2
border.color: hoveredWhileDragging ? hoveredBorderColor : (shouldShowActiveIndicator ? root.activeBorderColor : Theme.withAlpha(root.activeBorderColor, 0))
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
text: workspaceValue text: workspace.workspaceValue
font.pixelSize: Theme.fontSizeXLarge * 6 font.pixelSize: Theme.fontSizeXLarge * 6
font.weight: Font.DemiBold font.weight: Font.DemiBold
color: Theme.withAlpha(Theme.surfaceText, workspaceExists ? 0.2 : 0.1) color: Theme.withAlpha(Theme.surfaceText, workspace.workspaceExists ? 0.2 : 0.1)
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
}
MouseArea {
id: workspaceArea
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onClicked: {
if (root.draggingTargetWorkspace === -1) {
root.overviewOpen = false;
HyprlandService.focusWorkspace(workspace.workspaceValue);
} }
}
}
MouseArea { DropArea {
id: workspaceArea anchors.fill: parent
anchors.fill: parent onEntered: {
acceptedButtons: Qt.LeftButton root.draggingTargetWorkspace = workspace.workspaceValue;
onClicked: { if (root.draggingFromWorkspace == root.draggingTargetWorkspace)
if (root.draggingTargetWorkspace === -1) { return;
root.overviewOpen = false; workspace.hoveredWhileDragging = true;
HyprlandService.focusWorkspace(workspaceValue); }
} onExited: {
} workspace.hoveredWhileDragging = false;
} if (root.draggingTargetWorkspace == workspace.workspaceValue)
root.draggingTargetWorkspace = -1;
DropArea {
anchors.fill: parent
onEntered: {
root.draggingTargetWorkspace = workspaceValue;
if (root.draggingFromWorkspace == root.draggingTargetWorkspace)
return;
hoveredWhileDragging = true;
}
onExited: {
hoveredWhileDragging = false;
if (root.draggingTargetWorkspace == workspaceValue)
root.draggingTargetWorkspace = -1;
}
}
} }
} }
} }
@@ -269,8 +343,8 @@ Item {
Item { Item {
id: windowSpace id: windowSpace
anchors.centerIn: parent anchors.centerIn: parent
implicitWidth: workspaceColumnLayout.implicitWidth implicitWidth: workspaceGrid.implicitWidth
implicitHeight: workspaceColumnLayout.implicitHeight implicitHeight: workspaceGrid.implicitHeight
Repeater { Repeater {
model: ScriptModel { model: ScriptModel {
@@ -306,41 +380,21 @@ Item {
overviewOpen: root.overviewOpen overviewOpen: root.overviewOpen
readonly property int windowWorkspaceId: modelData?.workspace?.id ?? -1 readonly property int windowWorkspaceId: modelData?.workspace?.id ?? -1
readonly property var workspaceCell: root.cellForWorkspace(windowWorkspaceId)
function getWorkspaceIndex() { readonly property var workspaceBounds: root.getWorkspaceViewportBounds(windowWorkspaceId, workspaceCell.width, workspaceCell.height)
if (!root.displayedWorkspaceIds || root.displayedWorkspaceIds.length === 0)
return 0;
if (!windowWorkspaceId || windowWorkspaceId < 0)
return 0;
try {
for (let i = 0; i < root.displayedWorkspaceIds.length; i++) {
if (root.displayedWorkspaceIds[i] === windowWorkspaceId) {
return i;
}
}
return 0;
} catch (e) {
return 0;
}
}
readonly property int workspaceIndex: getWorkspaceIndex()
readonly property int workspaceColIndex: workspaceIndex % root.effectiveColumns
readonly property int workspaceRowIndex: Math.floor(workspaceIndex / root.effectiveColumns)
readonly property var workspaceBounds: root.getWorkspaceViewportBounds(windowWorkspaceId)
toplevel: modelData toplevel: modelData
scale: root.scale scale: root.scale
monitorDpr: root.dpr monitorDpr: root.dpr
availableWorkspaceWidth: root.workspaceImplicitWidth availableWorkspaceWidth: workspaceCell.width
availableWorkspaceHeight: root.workspaceImplicitHeight availableWorkspaceHeight: workspaceCell.height
contentOriginX: workspaceBounds.x contentOriginX: workspaceBounds.x
contentOriginY: workspaceBounds.y contentOriginY: workspaceBounds.y
contentScale: workspaceBounds.scale contentScale: workspaceBounds.scale
widgetMonitorId: root.monitor.id widgetMonitorId: root.monitor.id
xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex xOffset: workspaceCell.x
yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex yOffset: workspaceCell.y
z: atInitPosition ? root.windowZ : root.windowDraggingZ z: atInitPosition ? root.windowZ : root.windowDraggingZ
property bool atInitPosition: (initX == x && initY == y) property bool atInitPosition: (initX == x && initY == y)
@@ -409,54 +463,44 @@ Item {
Item { Item {
id: monitorLabelSpace id: monitorLabelSpace
anchors.centerIn: parent anchors.centerIn: parent
implicitWidth: workspaceColumnLayout.implicitWidth implicitWidth: workspaceGrid.implicitWidth
implicitHeight: workspaceColumnLayout.implicitHeight implicitHeight: workspaceGrid.implicitHeight
z: root.monitorLabelZ z: root.monitorLabelZ
Repeater { Repeater {
model: root.effectiveRows model: root.gridLayout.cells.length
delegate: Item { delegate: Item {
id: labelRow id: labelItem
property int rowIndex: index required property int index
y: (root.workspaceImplicitHeight + workspaceSpacing) * rowIndex readonly property var cell: root.gridLayout.cells[index] ?? null
width: parent.width property int workspaceValue: cell?.id ?? -1
height: root.workspaceImplicitHeight property bool workspaceExists: (root.allWorkspaceIds && workspaceValue > 0) ? root.allWorkspaceIds.includes(workspaceValue) : false
property string workspaceMonitorName: (workspaceValue > 0) ? root.getWorkspaceMonitorName(workspaceValue) : ""
Repeater { x: cell?.x ?? 0
model: root.effectiveColumns y: cell?.y ?? 0
delegate: Item { width: cell?.width ?? 0
id: labelItem height: cell?.height ?? 0
property int colIndex: index
property int workspaceIndex: labelRow.rowIndex * root.effectiveColumns + colIndex
property int workspaceValue: (root.displayedWorkspaceIds && workspaceIndex < root.displayedWorkspaceIds.length) ? root.displayedWorkspaceIds[workspaceIndex] : -1
property bool workspaceExists: (root.allWorkspaceIds && workspaceValue > 0) ? root.allWorkspaceIds.includes(workspaceValue) : false
property string workspaceMonitorName: (workspaceValue > 0) ? root.getWorkspaceMonitorName(workspaceValue) : ""
x: (root.workspaceImplicitWidth + workspaceSpacing) * colIndex Rectangle {
width: root.workspaceImplicitWidth anchors.right: parent.right
height: root.workspaceImplicitHeight anchors.top: parent.top
anchors.margins: Theme.spacingS
width: monitorNameText.contentWidth + Theme.spacingS * 2
height: monitorNameText.contentHeight + Theme.spacingXS * 2
radius: Theme.cornerRadius
color: Theme.surface
visible: labelItem.workspaceExists && labelItem.workspaceMonitorName !== ""
Rectangle { StyledText {
anchors.right: parent.right id: monitorNameText
anchors.top: parent.top anchors.centerIn: parent
anchors.margins: Theme.spacingS text: labelItem.workspaceMonitorName
width: monitorNameText.contentWidth + Theme.spacingS * 2 font.pixelSize: Theme.fontSizeSmall
height: monitorNameText.contentHeight + Theme.spacingXS * 2 font.weight: Font.Medium
radius: Theme.cornerRadius color: Theme.surfaceText
color: Theme.surface horizontalAlignment: Text.AlignHCenter
visible: labelItem.workspaceExists && labelItem.workspaceMonitorName !== "" verticalAlignment: Text.AlignVCenter
StyledText {
id: monitorNameText
anchors.centerIn: parent
text: labelItem.workspaceMonitorName
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
} }
} }
} }
+7 -6
View File
@@ -942,15 +942,16 @@ Singleton {
} }
} }
// Primary detection asks the OS which process owns $WAYLAND_DISPLAY — the // Primary detection asks the kernel which process owns the $WAYLAND_DISPLAY
// compositor quickshell is actually connected to. Env vars like // socket — the compositor quickshell is actually connected to. Env vars like
// HYPRLAND_INSTANCE_SIGNATURE / MANGO_INSTANCE_SIGNATURE can leak into the // HYPRLAND_INSTANCE_SIGNATURE / MANGO_INSTANCE_SIGNATURE can leak into the
// systemd user environment from previous sessions and lie. // systemd user environment from previous sessions and lie. Unset
// WAYLAND_DISPLAY falls back to "wayland-0", mirroring wl_display_connect.
// /proc/net/unix: field 6 is state (01 = listening), 7 inode, 8 bound path.
function detectCompositor() { function detectCompositor() {
const script = 'sock="$WAYLAND_DISPLAY"; case "$sock" in /*) ;; *) sock="$XDG_RUNTIME_DIR/$sock" ;; esac; ss -xlp 2>/dev/null | grep -F "$sock " | head -n1'; const script = 'sock="${WAYLAND_DISPLAY:-wayland-0}"; case "$sock" in /*) ;; *) sock="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/$sock" ;; esac; inode=$(awk -v p="$sock" \'$8 == p && $6 == 1 {print $7; exit}\' /proc/net/unix); [ -n "$inode" ] || exit 1; fd=$(find /proc/[0-9]*/fd/ -mindepth 1 -maxdepth 1 -lname "socket:\\[$inode\\]" 2>/dev/null | head -n1); [ -n "$fd" ] || exit 1; pid="${fd#/proc/}"; cat "/proc/${pid%%/*}/comm"';
Proc.runCommand("waylandSocketOwner", ["sh", "-c", script], (output, exitCode) => { Proc.runCommand("waylandSocketOwner", ["sh", "-c", script], (output, exitCode) => {
const match = output ? output.match(/users:\(\("([^"]+)"/) : null; const comm = (exitCode === 0 && output) ? output.trim().toLowerCase() : "";
const comm = match ? match[1].toLowerCase() : "";
const name = _compositorNameFromComm(comm); const name = _compositorNameFromComm(comm);
if (name) { if (name) {
_applyCompositor(name); _applyCompositor(name);