Fix window chrome positioning on Sway
Improves focused-window geometry so chrome placement stays stable across workspaces and floating containers. WindowService now exposes focused workspace X/Y, resolves workspace by name, and publishes those offsets with focused geometry. The Sway backend now tracks the active floating frame through recursion and consistently derives frame/deco/content rects from that frame. QML chrome placement was updated to use workspace-relative coordinates and simpler direct margins (removing interpolation behaviors), with safer title-bar height handling for vertical centering.
This commit is contained in:
@@ -156,6 +156,16 @@ int WindowService::focusedWindowTitleBarHeight() const
|
||||
return m_focusedWindowTitleBarHeight;
|
||||
}
|
||||
|
||||
int WindowService::focusedWorkspaceX() const
|
||||
{
|
||||
return m_focusedWorkspaceX;
|
||||
}
|
||||
|
||||
int WindowService::focusedWorkspaceY() const
|
||||
{
|
||||
return m_focusedWorkspaceY;
|
||||
}
|
||||
|
||||
bool WindowService::focusWindow(const QString &windowId)
|
||||
{
|
||||
return m_backend->focusWindow(parseWindowId(windowId));
|
||||
@@ -333,6 +343,8 @@ void WindowService::publishFocusedGeometry(const WindowInfo *focused)
|
||||
int decoHeight = 0;
|
||||
int titleBarY = 0;
|
||||
int titleBarHeight = 0;
|
||||
int workspaceX = 0;
|
||||
int workspaceY = 0;
|
||||
bool fullscreen = false;
|
||||
|
||||
if (focused && !focused->minimized) {
|
||||
@@ -349,13 +361,19 @@ void WindowService::publishFocusedGeometry(const WindowInfo *focused)
|
||||
titleBarHeight = focused->decoHeight;
|
||||
if (titleBarHeight <= 0)
|
||||
titleBarHeight = focused->contentY;
|
||||
if (titleBarHeight <= 0)
|
||||
if (titleBarHeight < 16 || titleBarHeight > 48)
|
||||
titleBarHeight = 32;
|
||||
|
||||
if (focused->decoHeight > 0 || focused->contentY > 0)
|
||||
titleBarY = focused->y + focused->decoY;
|
||||
else
|
||||
titleBarY = focused->y - titleBarHeight;
|
||||
// Frame rect from the floating container starts at the title bar.
|
||||
titleBarY = focused->y + focused->decoY;
|
||||
|
||||
const WorkspaceInfo *workspace = m_workspaces->workspaceByName(focused->workspace);
|
||||
if (!workspace)
|
||||
workspace = m_workspaces->focusedWorkspace();
|
||||
if (workspace) {
|
||||
workspaceX = workspace->x;
|
||||
workspaceY = workspace->y;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_focusedWindowFullscreen != fullscreen) {
|
||||
@@ -372,7 +390,9 @@ void WindowService::publishFocusedGeometry(const WindowInfo *focused)
|
||||
|| m_focusedWindowDecoWidth != decoWidth
|
||||
|| m_focusedWindowDecoHeight != decoHeight
|
||||
|| m_focusedWindowTitleBarY != titleBarY
|
||||
|| m_focusedWindowTitleBarHeight != titleBarHeight) {
|
||||
|| m_focusedWindowTitleBarHeight != titleBarHeight
|
||||
|| m_focusedWorkspaceX != workspaceX
|
||||
|| m_focusedWorkspaceY != workspaceY) {
|
||||
m_focusedWindowX = x;
|
||||
m_focusedWindowY = y;
|
||||
m_focusedWindowWidth = width;
|
||||
@@ -383,6 +403,8 @@ void WindowService::publishFocusedGeometry(const WindowInfo *focused)
|
||||
m_focusedWindowDecoHeight = decoHeight;
|
||||
m_focusedWindowTitleBarY = titleBarY;
|
||||
m_focusedWindowTitleBarHeight = titleBarHeight;
|
||||
m_focusedWorkspaceX = workspaceX;
|
||||
m_focusedWorkspaceY = workspaceY;
|
||||
emit focusedWindowGeometryChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@ class WindowService : public QObject
|
||||
Q_PROPERTY(int focusedWindowDecoHeight READ focusedWindowDecoHeight NOTIFY focusedWindowGeometryChanged)
|
||||
Q_PROPERTY(int focusedWindowTitleBarY READ focusedWindowTitleBarY NOTIFY focusedWindowGeometryChanged)
|
||||
Q_PROPERTY(int focusedWindowTitleBarHeight READ focusedWindowTitleBarHeight NOTIFY focusedWindowGeometryChanged)
|
||||
Q_PROPERTY(int focusedWorkspaceX READ focusedWorkspaceX NOTIFY focusedWindowGeometryChanged)
|
||||
Q_PROPERTY(int focusedWorkspaceY READ focusedWorkspaceY NOTIFY focusedWindowGeometryChanged)
|
||||
|
||||
public:
|
||||
enum SnapEdge {
|
||||
@@ -73,6 +75,8 @@ public:
|
||||
int focusedWindowDecoHeight() const;
|
||||
int focusedWindowTitleBarY() const;
|
||||
int focusedWindowTitleBarHeight() const;
|
||||
int focusedWorkspaceX() const;
|
||||
int focusedWorkspaceY() const;
|
||||
|
||||
Q_INVOKABLE bool focusWindow(const QString &windowId);
|
||||
Q_INVOKABLE bool closeWindow(const QString &windowId);
|
||||
@@ -124,5 +128,7 @@ private:
|
||||
int m_focusedWindowDecoHeight = 0;
|
||||
int m_focusedWindowTitleBarY = 0;
|
||||
int m_focusedWindowTitleBarHeight = 0;
|
||||
int m_focusedWorkspaceX = 0;
|
||||
int m_focusedWorkspaceY = 0;
|
||||
QTimer *m_geometryTimer = nullptr;
|
||||
};
|
||||
|
||||
@@ -82,3 +82,14 @@ const WorkspaceInfo *WorkspaceModel::focusedWorkspace() const
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const WorkspaceInfo *WorkspaceModel::workspaceByName(const QString &name) const
|
||||
{
|
||||
if (name.isEmpty())
|
||||
return nullptr;
|
||||
for (const WorkspaceInfo &workspace : m_workspaces) {
|
||||
if (workspace.name == name)
|
||||
return &workspace;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ public:
|
||||
void setWorkspaces(QVector<WorkspaceInfo> workspaces);
|
||||
const WorkspaceInfo *workspaceAt(int row) const;
|
||||
const WorkspaceInfo *focusedWorkspace() const;
|
||||
const WorkspaceInfo *workspaceByName(const QString &name) const;
|
||||
|
||||
signals:
|
||||
void countChanged();
|
||||
|
||||
@@ -66,38 +66,16 @@ bool nodeLooksLikeView(const QJsonObject &node)
|
||||
return !properties.isEmpty();
|
||||
}
|
||||
|
||||
void applyFrameGeometry(WindowInfo &window, const QJsonObject &node, const QJsonObject &parent)
|
||||
void applyFrameGeometry(WindowInfo &window, const QJsonObject &node, const QJsonObject &frame)
|
||||
{
|
||||
QJsonObject rect = node.value(QStringLiteral("rect")).toObject();
|
||||
QJsonObject deco = node.value(QStringLiteral("deco_rect")).toObject();
|
||||
QJsonObject content = node.value(QStringLiteral("window_rect")).toObject();
|
||||
const bool useFrame = !frame.isEmpty()
|
||||
&& (frame.value(QStringLiteral("type")).toString() == QLatin1String("floating_con")
|
||||
|| nodeIsFloating(frame));
|
||||
const QJsonObject &source = useFrame ? frame : node;
|
||||
|
||||
int decoHeight = deco.value(QStringLiteral("height")).toInt();
|
||||
int contentY = content.value(QStringLiteral("y")).toInt();
|
||||
|
||||
// Floating views live inside a floating_con whose rect includes Sway's
|
||||
// server-side title bar. The child view rect is the client content rect.
|
||||
// Always use the wrapper while it exists so refreshes cannot make chrome
|
||||
// jump between frame and content coordinates.
|
||||
if (nodeIsFloating(parent)) {
|
||||
rect = parent.value(QStringLiteral("rect")).toObject();
|
||||
deco = parent.value(QStringLiteral("deco_rect")).toObject();
|
||||
content = parent.value(QStringLiteral("window_rect")).toObject();
|
||||
decoHeight = deco.value(QStringLiteral("height")).toInt();
|
||||
contentY = content.value(QStringLiteral("y")).toInt();
|
||||
} else 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;
|
||||
}
|
||||
}
|
||||
const QJsonObject rect = source.value(QStringLiteral("rect")).toObject();
|
||||
const QJsonObject deco = source.value(QStringLiteral("deco_rect")).toObject();
|
||||
const QJsonObject content = source.value(QStringLiteral("window_rect")).toObject();
|
||||
|
||||
window.x = rect.value(QStringLiteral("x")).toInt();
|
||||
window.y = rect.value(QStringLiteral("y")).toInt();
|
||||
@@ -106,9 +84,9 @@ void applyFrameGeometry(WindowInfo &window, const QJsonObject &node, const QJson
|
||||
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.decoHeight = deco.value(QStringLiteral("height")).toInt();
|
||||
window.contentX = content.value(QStringLiteral("x")).toInt();
|
||||
window.contentY = contentY;
|
||||
window.contentY = content.value(QStringLiteral("y")).toInt();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -530,7 +508,7 @@ bool SwayWindowBackend::isShellChrome(const QJsonObject &node)
|
||||
}
|
||||
|
||||
void SwayWindowBackend::collect(const QJsonObject &node,
|
||||
const QJsonObject &parent,
|
||||
const QJsonObject &frame,
|
||||
const QString &workspace,
|
||||
const QString &output,
|
||||
bool onScratchpad,
|
||||
@@ -568,6 +546,10 @@ void SwayWindowBackend::collect(const QJsonObject &node,
|
||||
}
|
||||
}
|
||||
|
||||
QJsonObject nextFrame = frame;
|
||||
if (type == QLatin1String("floating_con"))
|
||||
nextFrame = node;
|
||||
|
||||
if (nodeLooksLikeView(node) && !isShellChrome(node)) {
|
||||
WindowInfo window;
|
||||
window.id = jsonId(node.value(QStringLiteral("id")));
|
||||
@@ -580,18 +562,18 @@ void SwayWindowBackend::collect(const QJsonObject &node,
|
||||
window.workspace = nextWorkspace;
|
||||
window.output = nextOutput;
|
||||
window.focused = node.value(QStringLiteral("focused")).toBool();
|
||||
window.floating = nodeIsFloating(node) || nodeIsFloating(parent);
|
||||
window.floating = nodeIsFloating(node) || nodeIsFloating(nextFrame);
|
||||
window.fullscreen = node.value(QStringLiteral("fullscreen_mode")).toInt() > 0;
|
||||
window.minimized = nextScratchpad;
|
||||
applyFrameGeometry(window, node, parent);
|
||||
applyFrameGeometry(window, node, nextFrame);
|
||||
windows->append(window);
|
||||
}
|
||||
|
||||
const QJsonArray nodes = node.value(QStringLiteral("nodes")).toArray();
|
||||
for (const QJsonValue &child : nodes)
|
||||
collect(child.toObject(), node, nextWorkspace, nextOutput, nextScratchpad, windows, workspaces);
|
||||
collect(child.toObject(), nextFrame, nextWorkspace, nextOutput, nextScratchpad, windows, workspaces);
|
||||
|
||||
const QJsonArray floating = node.value(QStringLiteral("floating_nodes")).toArray();
|
||||
for (const QJsonValue &child : floating)
|
||||
collect(child.toObject(), node, nextWorkspace, nextOutput, nextScratchpad, windows, workspaces);
|
||||
collect(child.toObject(), nextFrame, nextWorkspace, nextOutput, nextScratchpad, windows, workspaces);
|
||||
}
|
||||
|
||||
@@ -44,7 +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 QJsonObject &frame,
|
||||
const QString &workspace,
|
||||
const QString &output,
|
||||
bool onScratchpad,
|
||||
|
||||
Reference in New Issue
Block a user