Align window chrome with Sway title bars

Adds focused title-bar geometry to the window service and updates the Sway backend to derive frame data more reliably (including parent node fallback and content rect tracking for wrapped/floating views). Window chrome positioning now centers against the actual title bar instead of deco offsets, improving alignment. Also refreshes visual sizing/padding for traffic-light controls and Sway titlebar spacing to match the new chrome metrics.
This commit is contained in:
2026-08-26 22:45:50 +12:00
parent 4321209e92
commit 4d70014242
9 changed files with 100 additions and 33 deletions
@@ -66,6 +66,41 @@ bool nodeLooksLikeView(const QJsonObject &node)
return !properties.isEmpty();
}
void applyFrameGeometry(WindowInfo &window, const QJsonObject &node, const QJsonObject &parent)
{
QJsonObject rect = node.value(QStringLiteral("rect")).toObject();
QJsonObject deco = node.value(QStringLiteral("deco_rect")).toObject();
QJsonObject content = node.value(QStringLiteral("window_rect")).toObject();
int decoHeight = deco.value(QStringLiteral("height")).toInt();
int contentY = content.value(QStringLiteral("y")).toInt();
if (decoHeight <= 0 && contentY <= 0 && !parent.isEmpty()) {
const QJsonObject parentDeco = parent.value(QStringLiteral("deco_rect")).toObject();
const QJsonObject parentContent = parent.value(QStringLiteral("window_rect")).toObject();
const int parentDecoHeight = parentDeco.value(QStringLiteral("height")).toInt();
const int parentContentY = parentContent.value(QStringLiteral("y")).toInt();
if (parentDecoHeight > 0 || parentContentY > 0) {
rect = parent.value(QStringLiteral("rect")).toObject();
deco = parentDeco;
content = parentContent;
decoHeight = parentDecoHeight;
contentY = parentContentY;
}
}
window.x = rect.value(QStringLiteral("x")).toInt();
window.y = rect.value(QStringLiteral("y")).toInt();
window.width = rect.value(QStringLiteral("width")).toInt();
window.height = rect.value(QStringLiteral("height")).toInt();
window.decoX = deco.value(QStringLiteral("x")).toInt();
window.decoY = deco.value(QStringLiteral("y")).toInt();
window.decoWidth = deco.value(QStringLiteral("width")).toInt();
window.decoHeight = decoHeight;
window.contentX = content.value(QStringLiteral("x")).toInt();
window.contentY = contentY;
}
} // namespace
SwayWindowBackend::SwayWindowBackend(QObject *parent)
@@ -247,7 +282,7 @@ bool SwayWindowBackend::updateFocusedGeometry()
QVector<WindowInfo> windows;
QVector<WorkspaceInfo> workspaces;
collect(tree.object(), {}, {}, false, &windows, &workspaces);
collect(tree.object(), {}, {}, {}, false, &windows, &workspaces);
for (WindowInfo &window : windows) {
if (window.id != m_focusedWindowId)
@@ -263,6 +298,8 @@ bool SwayWindowBackend::updateFocusedGeometry()
&& cached.decoY == window.decoY
&& cached.decoWidth == window.decoWidth
&& cached.decoHeight == window.decoHeight
&& cached.contentX == window.contentX
&& cached.contentY == window.contentY
&& cached.fullscreen == window.fullscreen)
return false;
cached.x = window.x;
@@ -273,6 +310,8 @@ bool SwayWindowBackend::updateFocusedGeometry()
cached.decoY = window.decoY;
cached.decoWidth = window.decoWidth;
cached.decoHeight = window.decoHeight;
cached.contentX = window.contentX;
cached.contentY = window.contentY;
cached.fullscreen = window.fullscreen;
return true;
}
@@ -328,7 +367,7 @@ void SwayWindowBackend::refresh()
QVector<WindowInfo> windows;
QVector<WorkspaceInfo> workspaces;
collect(tree.object(), {}, {}, false, &windows, &workspaces);
collect(tree.object(), {}, {}, {}, false, &windows, &workspaces);
QHash<QString, int> counts;
qint64 focused = 0;
@@ -481,6 +520,7 @@ bool SwayWindowBackend::isShellChrome(const QJsonObject &node)
}
void SwayWindowBackend::collect(const QJsonObject &node,
const QJsonObject &parent,
const QString &workspace,
const QString &output,
bool onScratchpad,
@@ -530,27 +570,18 @@ void SwayWindowBackend::collect(const QJsonObject &node,
window.workspace = nextWorkspace;
window.output = nextOutput;
window.focused = node.value(QStringLiteral("focused")).toBool();
window.floating = nodeIsFloating(node);
window.floating = nodeIsFloating(node) || nodeIsFloating(parent);
window.fullscreen = node.value(QStringLiteral("fullscreen_mode")).toInt() > 0;
window.minimized = nextScratchpad;
const QJsonObject rect = node.value(QStringLiteral("rect")).toObject();
window.x = rect.value(QStringLiteral("x")).toInt();
window.y = rect.value(QStringLiteral("y")).toInt();
window.width = rect.value(QStringLiteral("width")).toInt();
window.height = rect.value(QStringLiteral("height")).toInt();
const QJsonObject deco = node.value(QStringLiteral("deco_rect")).toObject();
window.decoX = deco.value(QStringLiteral("x")).toInt();
window.decoY = deco.value(QStringLiteral("y")).toInt();
window.decoWidth = deco.value(QStringLiteral("width")).toInt();
window.decoHeight = deco.value(QStringLiteral("height")).toInt();
applyFrameGeometry(window, node, parent);
windows->append(window);
}
const QJsonArray nodes = node.value(QStringLiteral("nodes")).toArray();
for (const QJsonValue &child : nodes)
collect(child.toObject(), nextWorkspace, nextOutput, nextScratchpad, windows, workspaces);
collect(child.toObject(), node, nextWorkspace, nextOutput, nextScratchpad, windows, workspaces);
const QJsonArray floating = node.value(QStringLiteral("floating_nodes")).toArray();
for (const QJsonValue &child : floating)
collect(child.toObject(), nextWorkspace, nextOutput, nextScratchpad, windows, workspaces);
collect(child.toObject(), node, nextWorkspace, nextOutput, nextScratchpad, windows, workspaces);
}
@@ -44,6 +44,7 @@ private:
const WindowInfo *findWindow(qint64 windowId) const;
static bool isShellChrome(const QJsonObject &node);
static void collect(const QJsonObject &node,
const QJsonObject &parent,
const QString &workspace,
const QString &output,
bool onScratchpad,