1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-08-01 19:18:28 -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.Layouts
import Quickshell
import Quickshell.Hyprland
import qs.Common
@@ -80,7 +79,6 @@ Item {
readonly property int displayWorkspaceCount: displayedWorkspaceIds.length
readonly property int effectiveColumns: SettingsData.overviewColumns
readonly property int effectiveRows: Math.max(SettingsData.overviewRows, Math.ceil(displayWorkspaceCount / effectiveColumns))
function getWorkspaceMonitorName(workspaceId) {
if (!allWorkspaces || !workspaceId)
@@ -107,21 +105,53 @@ Item {
}
}
function getWorkspaceViewportBounds(workspaceId) {
function monitorIpcForWorkspace(workspaceId) {
const workspace = allWorkspaces?.find(ws => ws?.id === workspaceId);
const mon = workspace?.monitor?.lastIpcObject || monitor?.lastIpcObject || {};
const reserved = mon.reserved || [0, 0, 0, 0];
return workspace?.monitor?.lastIpcObject ?? monitor?.lastIpcObject ?? null;
}
const x = (mon.x ?? 0) + (reserved[0] ?? 0);
const y = (mon.y ?? 0) + (reserved[1] ?? 0);
const width = Math.max((mon.width ?? monitorPhysicalWidth) - (reserved[0] ?? 0) - (reserved[2] ?? 0), 1);
const height = Math.max((mon.height ?? monitorPhysicalHeight) - (reserved[1] ?? 0) - (reserved[3] ?? 0), 1);
const scale = Math.min(root.workspaceImplicitWidth / width, root.workspaceImplicitHeight / height);
function monitorLogicalSize(ipc) {
if (!ipc || !ipc.width || !ipc.height)
return {
"width": monitorPhysicalWidth,
"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 {
"x": x,
"y": y,
"scale": scale
"scale": Math.min(cellWidth / width, cellHeight / height)
};
}
@@ -143,6 +173,56 @@ Item {
property int draggingFromWorkspace: -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
implicitHeight: overviewBackground.implicitHeight + Theme.spacingL * 2
@@ -166,8 +246,8 @@ Item {
anchors.fill: parent
anchors.margins: Theme.spacingL
implicitWidth: workspaceColumnLayout.implicitWidth + padding * 2
implicitHeight: workspaceColumnLayout.implicitHeight + padding * 2
implicitWidth: workspaceGrid.implicitWidth + padding * 2
implicitHeight: workspaceGrid.implicitHeight + padding * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainer
@@ -182,84 +262,78 @@ Item {
shadowEnabled: Theme.elevationEnabled
}
ColumnLayout {
id: workspaceColumnLayout
Item {
id: workspaceGrid
z: root.workspaceZ
anchors.centerIn: parent
spacing: workspaceSpacing
implicitWidth: root.gridLayout.width
implicitHeight: root.gridLayout.height
Repeater {
model: root.effectiveRows
delegate: RowLayout {
id: row
property int rowIndex: index
spacing: workspaceSpacing
model: root.gridLayout.cells.length
Repeater {
model: root.effectiveColumns
Rectangle {
id: workspace
property int colIndex: index
property int workspaceIndex: 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 var workspaceObj: (workspaceExists && Hyprland.workspaces?.values) ? Hyprland.workspaces.values.find(ws => ws?.id === workspaceValue) : null
property bool isActive: workspaceObj?.active ?? false
property bool isOnThisMonitor: (workspaceObj && root.monitor) ? (workspaceObj.monitor?.name === root.monitor.name) : true
property bool hasWindows: (workspaceValue > 0) ? root.workspaceHasWindows(workspaceValue) : false
property string workspaceMonitorName: (workspaceValue > 0) ? root.getWorkspaceMonitorName(workspaceValue) : ""
property color defaultWorkspaceColor: workspaceExists ? Theme.surfaceContainer : Theme.withAlpha(Theme.surfaceContainer, 0.3)
property color hoveredWorkspaceColor: Qt.lighter(defaultWorkspaceColor, 1.1)
property color hoveredBorderColor: Theme.surfaceVariant
property bool hoveredWhileDragging: false
property bool shouldShowActiveIndicator: isActive && isOnThisMonitor && hasWindows
Rectangle {
id: workspace
required property int index
readonly property var cell: root.gridLayout.cells[index] ?? null
property int workspaceValue: cell?.id ?? -1
property bool workspaceExists: (root.allWorkspaceIds && workspaceValue > 0) ? root.allWorkspaceIds.includes(workspaceValue) : false
property var workspaceObj: (workspaceExists && Hyprland.workspaces?.values) ? Hyprland.workspaces.values.find(ws => ws?.id === workspaceValue) : null
property bool isActive: workspaceObj?.active ?? false
property bool isOnThisMonitor: (workspaceObj && root.monitor) ? (workspaceObj.monitor?.name === root.monitor.name) : true
property bool hasWindows: (workspaceValue > 0) ? root.workspaceHasWindows(workspaceValue) : false
property color defaultWorkspaceColor: workspaceExists ? Theme.surfaceContainer : Theme.withAlpha(Theme.surfaceContainer, 0.3)
property color hoveredWorkspaceColor: Qt.lighter(defaultWorkspaceColor, 1.1)
property color hoveredBorderColor: Theme.surfaceVariant
property bool hoveredWhileDragging: false
property bool shouldShowActiveIndicator: isActive && isOnThisMonitor && hasWindows
visible: workspaceValue !== -1
visible: workspaceValue !== -1
implicitWidth: root.workspaceImplicitWidth
implicitHeight: root.workspaceImplicitHeight
color: hoveredWhileDragging ? hoveredWorkspaceColor : defaultWorkspaceColor
radius: Theme.cornerRadius
border.width: 2
border.color: hoveredWhileDragging ? hoveredBorderColor : (shouldShowActiveIndicator ? root.activeBorderColor : Theme.withAlpha(root.activeBorderColor, 0))
x: cell?.x ?? 0
y: cell?.y ?? 0
width: cell?.width ?? 0
height: cell?.height ?? 0
color: hoveredWhileDragging ? hoveredWorkspaceColor : defaultWorkspaceColor
radius: Theme.cornerRadius
border.width: 2
border.color: hoveredWhileDragging ? hoveredBorderColor : (shouldShowActiveIndicator ? root.activeBorderColor : Theme.withAlpha(root.activeBorderColor, 0))
StyledText {
anchors.centerIn: parent
text: workspaceValue
font.pixelSize: Theme.fontSizeXLarge * 6
font.weight: Font.DemiBold
color: Theme.withAlpha(Theme.surfaceText, workspaceExists ? 0.2 : 0.1)
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
StyledText {
anchors.centerIn: parent
text: workspace.workspaceValue
font.pixelSize: Theme.fontSizeXLarge * 6
font.weight: Font.DemiBold
color: Theme.withAlpha(Theme.surfaceText, workspace.workspaceExists ? 0.2 : 0.1)
horizontalAlignment: Text.AlignHCenter
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 {
id: workspaceArea
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onClicked: {
if (root.draggingTargetWorkspace === -1) {
root.overviewOpen = false;
HyprlandService.focusWorkspace(workspaceValue);
}
}
}
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;
}
}
DropArea {
anchors.fill: parent
onEntered: {
root.draggingTargetWorkspace = workspace.workspaceValue;
if (root.draggingFromWorkspace == root.draggingTargetWorkspace)
return;
workspace.hoveredWhileDragging = true;
}
onExited: {
workspace.hoveredWhileDragging = false;
if (root.draggingTargetWorkspace == workspace.workspaceValue)
root.draggingTargetWorkspace = -1;
}
}
}
@@ -269,8 +343,8 @@ Item {
Item {
id: windowSpace
anchors.centerIn: parent
implicitWidth: workspaceColumnLayout.implicitWidth
implicitHeight: workspaceColumnLayout.implicitHeight
implicitWidth: workspaceGrid.implicitWidth
implicitHeight: workspaceGrid.implicitHeight
Repeater {
model: ScriptModel {
@@ -306,41 +380,21 @@ Item {
overviewOpen: root.overviewOpen
readonly property int windowWorkspaceId: modelData?.workspace?.id ?? -1
function getWorkspaceIndex() {
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)
readonly property var workspaceCell: root.cellForWorkspace(windowWorkspaceId)
readonly property var workspaceBounds: root.getWorkspaceViewportBounds(windowWorkspaceId, workspaceCell.width, workspaceCell.height)
toplevel: modelData
scale: root.scale
monitorDpr: root.dpr
availableWorkspaceWidth: root.workspaceImplicitWidth
availableWorkspaceHeight: root.workspaceImplicitHeight
availableWorkspaceWidth: workspaceCell.width
availableWorkspaceHeight: workspaceCell.height
contentOriginX: workspaceBounds.x
contentOriginY: workspaceBounds.y
contentScale: workspaceBounds.scale
widgetMonitorId: root.monitor.id
xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex
yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex
xOffset: workspaceCell.x
yOffset: workspaceCell.y
z: atInitPosition ? root.windowZ : root.windowDraggingZ
property bool atInitPosition: (initX == x && initY == y)
@@ -409,54 +463,44 @@ Item {
Item {
id: monitorLabelSpace
anchors.centerIn: parent
implicitWidth: workspaceColumnLayout.implicitWidth
implicitHeight: workspaceColumnLayout.implicitHeight
implicitWidth: workspaceGrid.implicitWidth
implicitHeight: workspaceGrid.implicitHeight
z: root.monitorLabelZ
Repeater {
model: root.effectiveRows
model: root.gridLayout.cells.length
delegate: Item {
id: labelRow
property int rowIndex: index
y: (root.workspaceImplicitHeight + workspaceSpacing) * rowIndex
width: parent.width
height: root.workspaceImplicitHeight
id: labelItem
required property int index
readonly property var cell: root.gridLayout.cells[index] ?? null
property int workspaceValue: cell?.id ?? -1
property bool workspaceExists: (root.allWorkspaceIds && workspaceValue > 0) ? root.allWorkspaceIds.includes(workspaceValue) : false
property string workspaceMonitorName: (workspaceValue > 0) ? root.getWorkspaceMonitorName(workspaceValue) : ""
Repeater {
model: root.effectiveColumns
delegate: Item {
id: labelItem
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: cell?.x ?? 0
y: cell?.y ?? 0
width: cell?.width ?? 0
height: cell?.height ?? 0
x: (root.workspaceImplicitWidth + workspaceSpacing) * colIndex
width: root.workspaceImplicitWidth
height: root.workspaceImplicitHeight
Rectangle {
anchors.right: parent.right
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 {
anchors.right: parent.right
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 !== ""
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
}
}
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
// compositor quickshell is actually connected to. Env vars like
// Primary detection asks the kernel which process owns the $WAYLAND_DISPLAY
// socket — the compositor quickshell is actually connected to. Env vars like
// 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() {
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) => {
const match = output ? output.match(/users:\(\("([^"]+)"/) : null;
const comm = match ? match[1].toLowerCase() : "";
const comm = (exitCode === 0 && output) ? output.trim().toLowerCase() : "";
const name = _compositorNameFromComm(comm);
if (name) {
_applyCompositor(name);