fix sendToScreen/checkWorkspaceGeometry

Prime target is to preserve the in-screen
condition of client AND window.i[1]
Atm. when the client is fully in sight
(but the window is not) - regardless of snapping
or screen change - a workspace update (screen change,
resolution change, adding/removing a strutting panel)
would allow the client to partiall escape screen bounds.

This is changed so that if the client is fully in sight,
it's kept fully in sight (but not the decoration)
If the entire window was fully in sight, it's also kept
(as is right now)
The code handles inner screen edges (if the client was in sight,
the entire window will be if we'd bleed to the other screen)

[1] I'd say that handling the client is more relevant,
but foresee more complaints if the window wasn't handled anymore ;-)

During that, i stumbled across some other issues.
- when a window centered on one screen is moved to a screen smaller
  than the window, the window is shrinked to the dimensions of that
  screen and now randomly touches eg. left AND right edge. When
  moved back, the right and bottom edge were preferred
  (to the window was "moved" into the lower right corner).
  It's now kept centered.
- geom_restore was saved before keeping the window in the new
  screen area (causing accidental drops on screen changes)

BUG: 330968
REVIEW: 122517
FIXED-IN: 5.4

Prime target is to preserve the in-screen condition
of client AND window.[1]
Atm. when the client is fully in sight (but the window is not) -
regardless of snapping or screen change - a workspace update
(screen change, resolution change, adding/removing a strutting
panel) would allow the client to partiall escape screen bounds.

This is changed so that if the client is fully in sight,
it's kept fully in sight (but not the decoration)
If the entire window was fully in sight, it's also kept
(as is right now)
The code handles inner screen edges (if the client was in sight,
the entire window will be if we'd bleed to the other screen)

[1] I'd say that handling the client is more relevant,
but foresee more complaints if the window wasn't handled anymore ;-)

During that, i stumbled across some other issues.
- when a window centered on one screen is moved to a screen
smaller than the window, the window is shrinked to the dimensions
of that screen and now randomly touches eg. left AND right edge.
When moved back, the right and bottom edge were preferred
(to the window was "moved" into the lower right corner).
It's now kept centered.
- geom_restore was saved before keeping the window in the new
screen area (causing accidental drops on screen changes)

BUG: 330968
REVIEW: 116029
FIXED-IN: 5.3
This commit is contained in:
Thomas Lübking 2015-02-06 22:48:12 +01:00
parent d053c31571
commit 39c35f8f48
6 changed files with 119 additions and 76 deletions

View file

@ -290,7 +290,7 @@ public:
virtual const WindowRules* rules() const = 0;
virtual void takeFocus() = 0;
virtual bool wantsInput() const = 0;
virtual void checkWorkspacePosition(QRect oldGeometry = QRect(), int oldDesktop = -2) = 0;
virtual void checkWorkspacePosition(QRect oldGeometry = QRect(), int oldDesktop = -2, QRect oldClientGeometry = QRect()) = 0;
virtual xcb_timestamp_t userTime() const;
virtual void updateWindowRules(Rules::Types selection) = 0;

View file

@ -386,6 +386,7 @@ void Client::updateDecoration(bool check_workspace_pos, bool force)
((m_decoration == NULL && noBorder()) || (m_decoration != NULL && !noBorder())))
return;
QRect oldgeom = geometry();
QRect oldClientGeom = oldgeom.adjusted(borderLeft(), borderTop(), -borderRight(), -borderBottom());
blockGeometryUpdates(true);
if (force)
destroyDecoration();
@ -395,7 +396,7 @@ void Client::updateDecoration(bool check_workspace_pos, bool force)
destroyDecoration();
getShadow();
if (check_workspace_pos)
checkWorkspacePosition(oldgeom);
checkWorkspacePosition(oldgeom, -2, oldClientGeom);
updateInputWindow();
blockGeometryUpdates(false);
updateFrameExtents();

View file

@ -379,7 +379,7 @@ public:
void gotPing(xcb_timestamp_t timestamp);
void checkWorkspacePosition(QRect oldGeometry = QRect(), int oldDesktop = -2) override;
void checkWorkspacePosition(QRect oldGeometry = QRect(), int oldDesktop = -2, QRect oldClientGeometry = QRect()) override;
void updateUserTime(xcb_timestamp_t time = XCB_TIME_CURRENT_TIME);
xcb_timestamp_t userTime() const override;
bool hasUserTimeSupport() const;

View file

@ -1041,12 +1041,16 @@ bool Client::hasOffscreenXineramaStrut() const
return !region.isEmpty();
}
void Client::checkWorkspacePosition(QRect oldGeometry, int oldDesktop)
void Client::checkWorkspacePosition(QRect oldGeometry, int oldDesktop, QRect oldClientGeometry)
{
enum { Left = 0, Top, Right, Bottom };
const int border[4] = { borderLeft(), borderTop(), borderRight(), borderBottom() };
if( !oldGeometry.isValid())
oldGeometry = geometry();
if( oldDesktop == -2 )
oldDesktop = desktop();
if (!oldClientGeometry.isValid())
oldClientGeometry = oldGeometry.adjusted(border[Left], border[Top], -border[Right], -border[Bottom]);
if (isDesktop())
return;
if (isFullScreen()) {
@ -1115,55 +1119,38 @@ void Client::checkWorkspacePosition(QRect oldGeometry, int oldDesktop)
int bottomMax = screenArea.y() + screenArea.height();
int leftMax = screenArea.x();
QRect newGeom = geom_restore; // geometry();
QRect newClientGeom = newGeom.adjusted(border[Left], border[Top], -border[Right], -border[Bottom]);
const QRect newGeomTall = QRect(newGeom.x(), 0, newGeom.width(), displayHeight()); // Full screen height
const QRect newGeomWide = QRect(0, newGeom.y(), displayWidth(), newGeom.height()); // Full screen width
// Get the max strut point for each side where the window is (E.g. Highest point for
// the bottom struts bounded by the window's left and right sides).
if( workspace()->inUpdateClientArea()) {
// These 4 compute old bounds when the restricted areas themselves changed (Workspace::updateClientArea())
foreach (const QRect & r, workspace()->previousRestrictedMoveArea(oldDesktop, StrutAreaTop).rects()) {
QRect rect = r & oldGeomTall;
if (!rect.isEmpty())
oldTopMax = qMax(oldTopMax, rect.y() + rect.height());
}
foreach (const QRect & r, workspace()->previousRestrictedMoveArea(oldDesktop, StrutAreaRight).rects()) {
QRect rect = r & oldGeomWide;
if (!rect.isEmpty())
oldRightMax = qMin(oldRightMax, rect.x());
}
foreach (const QRect & r, workspace()->previousRestrictedMoveArea(oldDesktop, StrutAreaBottom).rects()) {
QRect rect = r & oldGeomTall;
if (!rect.isEmpty())
oldBottomMax = qMin(oldBottomMax, rect.y());
}
foreach (const QRect & r, workspace()->previousRestrictedMoveArea(oldDesktop, StrutAreaLeft).rects()) {
QRect rect = r & oldGeomWide;
if (!rect.isEmpty())
oldLeftMax = qMax(oldLeftMax, rect.x() + rect.width());
}
} else {
// These 4 compute old bounds when e.g. active desktop or screen changes
foreach (const QRect & r, workspace()->restrictedMoveArea(oldDesktop, StrutAreaTop).rects()) {
QRect rect = r & oldGeomTall;
if (!rect.isEmpty())
oldTopMax = qMax(oldTopMax, rect.y() + rect.height());
}
foreach (const QRect & r, workspace()->restrictedMoveArea(oldDesktop, StrutAreaRight).rects()) {
QRect rect = r & oldGeomWide;
if (!rect.isEmpty())
oldRightMax = qMin(oldRightMax, rect.x());
}
foreach (const QRect & r, workspace()->restrictedMoveArea(oldDesktop, StrutAreaBottom).rects()) {
QRect rect = r & oldGeomTall;
if (!rect.isEmpty())
oldBottomMax = qMin(oldBottomMax, rect.y());
}
foreach (const QRect & r, workspace()->restrictedMoveArea(oldDesktop, StrutAreaLeft).rects()) {
QRect rect = r & oldGeomWide;
if (!rect.isEmpty())
oldLeftMax = qMax(oldLeftMax, rect.x() + rect.width());
}
// These 4 compute old bounds ...
auto moveAreaFunc = workspace()->inUpdateClientArea() ?
&Workspace::previousRestrictedMoveArea : //... the restricted areas changed
&Workspace::restrictedMoveArea; //... when e.g. active desktop or screen changes
foreach (const QRect & r, (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaTop).rects()) {
QRect rect = r & oldGeomTall;
if (!rect.isEmpty())
oldTopMax = qMax(oldTopMax, rect.y() + rect.height());
}
foreach (const QRect & r, (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaRight).rects()) {
QRect rect = r & oldGeomWide;
if (!rect.isEmpty())
oldRightMax = qMin(oldRightMax, rect.x());
}
foreach (const QRect & r, (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaBottom).rects()) {
QRect rect = r & oldGeomTall;
if (!rect.isEmpty())
oldBottomMax = qMin(oldBottomMax, rect.y());
}
foreach (const QRect & r, (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaLeft).rects()) {
QRect rect = r & oldGeomWide;
if (!rect.isEmpty())
oldLeftMax = qMax(oldLeftMax, rect.x() + rect.width());
}
// These 4 compute new bounds
foreach (const QRect & r, workspace()->restrictedMoveArea(desktop(), StrutAreaTop).rects()) {
QRect rect = r & newGeomTall;
@ -1186,32 +1173,83 @@ void Client::checkWorkspacePosition(QRect oldGeometry, int oldDesktop)
leftMax = qMax(leftMax, rect.x() + rect.width());
}
// Check if the sides were inside or touching but are no longer
if ((oldGeometry.y() >= oldTopMax && newGeom.y() < topMax)
|| (oldGeometry.y() == oldTopMax && newGeom.y() != topMax)) {
// Top was inside or touching before but isn't anymore
newGeom.moveTop(qMax(topMax, screenArea.y()));
bool keep[4] = {false, false, false, false};
bool save[4] = {false, false, false, false};
int padding[4] = {0, 0, 0, 0};
if (oldGeometry.x() >= oldLeftMax)
save[Left] = newGeom.x() < leftMax;
if (oldGeometry.x() == oldLeftMax)
keep[Left] = newGeom.x() != leftMax;
else if (oldClientGeometry.x() == oldLeftMax && newClientGeom.x() != leftMax) {
padding[0] = border[Left];
keep[Left] = true;
}
if ((oldGeometry.y() + oldGeometry.height() <= oldBottomMax && newGeom.y() + newGeom.height() > bottomMax)
|| (oldGeometry.y() + oldGeometry.height() == oldBottomMax && newGeom.y() + newGeom.height() != bottomMax)) {
// Bottom was inside or touching before but isn't anymore
newGeom.moveBottom(qMin(bottomMax - 1, screenArea.bottom()));
// If the other side was inside make sure it still is afterwards (shrink appropriately)
if (oldGeometry.y() >= oldTopMax && newGeom.y() < topMax)
newGeom.setTop(qMax(topMax, screenArea.y()));
if (oldGeometry.y() >= oldTopMax)
save[Top] = newGeom.y() < topMax;
if (oldGeometry.y() == oldTopMax)
keep[Top] = newGeom.y() != topMax;
else if (oldClientGeometry.y() == oldTopMax && newClientGeom.y() != topMax) {
padding[1] = border[Left];
keep[Top] = true;
}
if ((oldGeometry.x() >= oldLeftMax && newGeom.x() < leftMax)
|| (oldGeometry.x() == oldLeftMax && newGeom.x() != leftMax)) {
// Left was inside or touching before but isn't anymore
newGeom.moveLeft(qMax(leftMax, screenArea.x()));
if (oldGeometry.right() <= oldRightMax - 1)
save[Right] = newGeom.right() > rightMax - 1;
if (oldGeometry.right() == oldRightMax - 1)
keep[Right] = newGeom.right() != rightMax - 1;
else if (oldClientGeometry.right() == oldRightMax - 1 && newClientGeom.right() != rightMax - 1) {
padding[2] = border[Right];
keep[Right] = true;
}
if ((oldGeometry.x() + oldGeometry.width() <= oldRightMax && newGeom.x() + newGeom.width() > rightMax)
|| (oldGeometry.x() + oldGeometry.width() == oldRightMax && newGeom.x() + newGeom.width() != rightMax)) {
// Right was inside or touching before but isn't anymore
newGeom.moveRight(qMin(rightMax - 1, screenArea.right()));
// If the other side was inside make sure it still is afterwards (shrink appropriately)
if (oldGeometry.x() >= oldLeftMax && newGeom.x() < leftMax)
newGeom.setLeft(qMax(leftMax, screenArea.x()));
if (oldGeometry.bottom() <= oldBottomMax - 1)
save[Bottom] = newGeom.bottom() > bottomMax - 1;
if (oldGeometry.bottom() == oldBottomMax - 1)
keep[Bottom] = newGeom.bottom() != bottomMax - 1;
else if (oldClientGeometry.bottom() == oldBottomMax - 1 && newClientGeom.bottom() != bottomMax - 1) {
padding[3] = border[Bottom];
keep[Bottom] = true;
}
// if randomly touches opposing edges, do not favor either
if (keep[Left] && keep[Right]) {
keep[Left] = keep[Right] = false;
padding[0] = padding[2] = 0;
}
if (keep[Top] && keep[Bottom]) {
keep[Top] = keep[Bottom] = false;
padding[1] = padding[3] = 0;
}
if (save[Left] || keep[Left])
newGeom.moveLeft(qMax(leftMax, screenArea.x()) - padding[0]);
if (padding[0] && screens()->intersecting(newGeom) > 1)
newGeom.moveLeft(newGeom.left() + padding[0]);
if (save[Top] || keep[Top])
newGeom.moveTop(qMax(topMax, screenArea.y()) - padding[1]);
if (padding[1] && screens()->intersecting(newGeom) > 1)
newGeom.moveTop(newGeom.top() + padding[1]);
if (save[Right] || keep[Right])
newGeom.moveRight(qMin(rightMax - 1, screenArea.right()) + padding[2]);
if (padding[2] && screens()->intersecting(newGeom) > 1)
newGeom.moveRight(newGeom.right() - padding[2]);
if (oldGeometry.x() >= oldLeftMax && newGeom.x() < leftMax)
newGeom.setLeft(qMax(leftMax, screenArea.x()));
else if (oldClientGeometry.x() >= oldLeftMax && newGeom.x() + border[Left] < leftMax) {
newGeom.setLeft(qMax(leftMax, screenArea.x()) - border[Left]);
if (screens()->intersecting(newGeom) > 1)
newGeom.setLeft(newGeom.left() + border[Left]);
}
if (save[Bottom] || keep[Bottom])
newGeom.moveBottom(qMin(bottomMax - 1, screenArea.bottom()) + padding[3]);
if (padding[3] && screens()->intersecting(newGeom) > 1)
newGeom.moveBottom(newGeom.bottom() - padding[3]);
if (oldGeometry.y() >= oldTopMax && newGeom.y() < topMax)
newGeom.setTop(qMax(topMax, screenArea.y()));
else if (oldClientGeometry.y() >= oldTopMax && newGeom.y() + border[Top] < topMax) {
newGeom.setTop(qMax(topMax, screenArea.y()) - border[Top]);
if (screens()->intersecting(newGeom) > 1)
newGeom.setTop(newGeom.top() + border[Top]);
}
checkOffscreenPosition(&newGeom, screenArea);
@ -3320,18 +3358,21 @@ void Client::sendToScreen(int newScreen)
center += screenArea.center();
newGeom.moveCenter(center);
setGeometry(newGeom);
// align geom_restore - checkWorkspacePosition operates on it
geom_restore = newGeom;
// If the window was inside the old screen area, explicitly make sure its inside also the new screen area.
// Calling checkWorkspacePosition() should ensure that, but when moving to a small screen the window could
// be big enough to overlap outside of the new screen area, making struts from other screens come into effect,
// which could alter the resulting geometry.
if (oldScreenArea.contains(oldGeom))
if (oldScreenArea.contains(oldGeom)) {
keepInArea(screenArea);
}
// align geom_restore - checkWorkspacePosition operates on it
geom_restore = geometry();
checkWorkspacePosition(oldGeom);
// re-align geom_restore to contrained geometry
// re-align geom_restore to constrained geometry
geom_restore = geometry();
// finally reset special states

View file

@ -236,10 +236,11 @@ QString ShellClient::caption(bool full, bool stripped) const
return m_shellSurface->title();
}
void ShellClient::checkWorkspacePosition(QRect oldGeometry, int oldDesktop)
void ShellClient::checkWorkspacePosition(QRect oldGeometry, int oldDesktop, QRect oldClientGeometry)
{
Q_UNUSED(oldGeometry)
Q_UNUSED(oldDesktop)
Q_UNUSED(oldClientGeometry)
}
void ShellClient::closeWindow()

View file

@ -60,7 +60,7 @@ public:
void blockActivityUpdates(bool b = true) override;
QString caption(bool full = true, bool stripped = false) const override;
void checkWorkspacePosition(QRect oldGeometry = QRect(), int oldDesktop = -2) override;
void checkWorkspacePosition(QRect oldGeometry = QRect(), int oldDesktop = -2, QRect oldClientGeometry = QRect()) override;
void closeWindow() override;
AbstractClient *findModal(bool allow_itself = false) override;
bool isCloseable() const override;