diff --git a/CMakeLists.txt b/CMakeLists.txt
index 863e2378e6..bf19c4448e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -422,7 +422,6 @@ set(kwin_KDEINIT_SRCS
egl_context_attribute_builder.cpp
events.cpp
focuschain.cpp
- geometry.cpp
geometrytip.cpp
gestures.cpp
globalshortcuts.cpp
diff --git a/abstract_client.cpp b/abstract_client.cpp
index aa5b3c4d59..8100900d3d 100644
--- a/abstract_client.cpp
+++ b/abstract_client.cpp
@@ -49,6 +49,11 @@ along with this program. If not, see .
namespace KWin
{
+static inline int sign(int v)
+{
+ return (v > 0) - (v < 0);
+}
+
QHash> AbstractClient::s_palettes;
std::shared_ptr AbstractClient::s_defaultPalette;
@@ -752,11 +757,596 @@ QSize AbstractClient::minSize() const
return rules()->checkMinSize(QSize(0, 0));
}
+void AbstractClient::blockGeometryUpdates(bool block)
+{
+ if (block) {
+ if (m_blockGeometryUpdates == 0)
+ m_pendingGeometryUpdate = PendingGeometryNone;
+ ++m_blockGeometryUpdates;
+ } else {
+ if (--m_blockGeometryUpdates == 0) {
+ if (m_pendingGeometryUpdate != PendingGeometryNone) {
+ if (isShade())
+ setFrameGeometry(QRect(pos(), adjustedSize()), NormalGeometrySet);
+ else
+ setFrameGeometry(frameGeometry(), NormalGeometrySet);
+ m_pendingGeometryUpdate = PendingGeometryNone;
+ }
+ }
+ }
+}
+
+void AbstractClient::maximize(MaximizeMode m)
+{
+ setMaximize(m & MaximizeVertical, m & MaximizeHorizontal);
+}
+
+void AbstractClient::setMaximize(bool vertically, bool horizontally)
+{
+ // changeMaximize() flips the state, so change from set->flip
+ const MaximizeMode oldMode = maximizeMode();
+ changeMaximize(
+ oldMode & MaximizeHorizontal ? !horizontally : horizontally,
+ oldMode & MaximizeVertical ? !vertically : vertically,
+ false);
+ const MaximizeMode newMode = maximizeMode();
+ if (oldMode != newMode) {
+ emit clientMaximizedStateChanged(this, newMode);
+ emit clientMaximizedStateChanged(this, vertically, horizontally);
+ }
+}
+
+void AbstractClient::move(int x, int y, ForceGeometry_t force)
+{
+ // resuming geometry updates is handled only in setGeometry()
+ Q_ASSERT(pendingGeometryUpdate() == PendingGeometryNone || areGeometryUpdatesBlocked());
+ QPoint p(x, y);
+ if (!areGeometryUpdatesBlocked() && p != rules()->checkPosition(p)) {
+ qCDebug(KWIN_CORE) << "forced position fail:" << p << ":" << rules()->checkPosition(p);
+ }
+ if (force == NormalGeometrySet && m_frameGeometry.topLeft() == p)
+ return;
+ m_frameGeometry.moveTopLeft(p);
+ if (areGeometryUpdatesBlocked()) {
+ if (pendingGeometryUpdate() == PendingGeometryForced)
+ {} // maximum, nothing needed
+ else if (force == ForceGeometrySet)
+ setPendingGeometryUpdate(PendingGeometryForced);
+ else
+ setPendingGeometryUpdate(PendingGeometryNormal);
+ return;
+ }
+ doMove(x, y);
+ updateWindowRules(Rules::Position);
+ screens()->setCurrent(this);
+ workspace()->updateStackingOrder();
+ // client itself is not damaged
+ addRepaintDuringGeometryUpdates();
+ updateGeometryBeforeUpdateBlocking();
+ emit geometryChanged();
+}
+
+bool AbstractClient::startMoveResize()
+{
+ Q_ASSERT(!isMoveResize());
+ Q_ASSERT(QWidget::keyboardGrabber() == nullptr);
+ Q_ASSERT(QWidget::mouseGrabber() == nullptr);
+ stopDelayedMoveResize();
+ if (QApplication::activePopupWidget() != nullptr)
+ return false; // popups have grab
+ if (isFullScreen() && (screens()->count() < 2 || !isMovableAcrossScreens()))
+ return false;
+ if (!doStartMoveResize()) {
+ return false;
+ }
+
+ invalidateDecorationDoubleClickTimer();
+
+ setMoveResize(true);
+ workspace()->setMoveResizeClient(this);
+
+ const Position mode = moveResizePointerMode();
+ if (mode != PositionCenter) { // means "isResize()" but moveResizeMode = true is set below
+ if (maximizeMode() == MaximizeFull) { // partial is cond. reset in finishMoveResize
+ setGeometryRestore(frameGeometry()); // "restore" to current geometry
+ setMaximize(false, false);
+ }
+ }
+
+ if (quickTileMode() != QuickTileMode(QuickTileFlag::None) && mode != PositionCenter) { // Cannot use isResize() yet
+ // Exit quick tile mode when the user attempts to resize a tiled window
+ updateQuickTileMode(QuickTileFlag::None); // Do so without restoring original geometry
+ setGeometryRestore(frameGeometry());
+ emit quickTileModeChanged();
+ }
+
+ updateHaveResizeEffect();
+ updateInitialMoveResizeGeometry();
+ checkUnrestrictedMoveResize();
+ emit clientStartUserMovedResized(this);
+ if (ScreenEdges::self()->isDesktopSwitchingMovingClients())
+ ScreenEdges::self()->reserveDesktopSwitching(true, Qt::Vertical|Qt::Horizontal);
+ return true;
+}
+
+void AbstractClient::finishMoveResize(bool cancel)
+{
+ GeometryUpdatesBlocker blocker(this);
+ const bool wasResize = isResize(); // store across leaveMoveResize
+ leaveMoveResize();
+
+ if (cancel)
+ setFrameGeometry(initialMoveResizeGeometry());
+ else {
+ const QRect &moveResizeGeom = moveResizeGeometry();
+ if (wasResize) {
+ const bool restoreH = maximizeMode() == MaximizeHorizontal &&
+ moveResizeGeom.width() != initialMoveResizeGeometry().width();
+ const bool restoreV = maximizeMode() == MaximizeVertical &&
+ moveResizeGeom.height() != initialMoveResizeGeometry().height();
+ if (restoreH || restoreV) {
+ changeMaximize(restoreH, restoreV, false);
+ }
+ }
+ setFrameGeometry(moveResizeGeom);
+ }
+ checkScreen(); // needs to be done because clientFinishUserMovedResized has not yet re-activated online alignment
+ if (screen() != moveResizeStartScreen()) {
+ workspace()->sendClientToScreen(this, screen()); // checks rule validity
+ if (maximizeMode() != MaximizeRestore)
+ checkWorkspacePosition();
+ }
+
+ if (isElectricBorderMaximizing()) {
+ setQuickTileMode(electricBorderMode());
+ setElectricBorderMaximizing(false);
+ } else if (!cancel) {
+ QRect geom_restore = geometryRestore();
+ if (!(maximizeMode() & MaximizeHorizontal)) {
+ geom_restore.setX(frameGeometry().x());
+ geom_restore.setWidth(frameGeometry().width());
+ }
+ if (!(maximizeMode() & MaximizeVertical)) {
+ geom_restore.setY(frameGeometry().y());
+ geom_restore.setHeight(frameGeometry().height());
+ }
+ setGeometryRestore(geom_restore);
+ }
+// FRAME update();
+
+ emit clientFinishUserMovedResized(this);
+}
+
+// This function checks if it actually makes sense to perform a restricted move/resize.
+// If e.g. the titlebar is already outside of the workarea, there's no point in performing
+// a restricted move resize, because then e.g. resize would also move the window (#74555).
+// NOTE: Most of it is duplicated from handleMoveResize().
+void AbstractClient::checkUnrestrictedMoveResize()
+{
+ if (isUnrestrictedMoveResize())
+ return;
+ const QRect &moveResizeGeom = moveResizeGeometry();
+ QRect desktopArea = workspace()->clientArea(WorkArea, moveResizeGeom.center(), desktop());
+ int left_marge, right_marge, top_marge, bottom_marge, titlebar_marge;
+ // restricted move/resize - keep at least part of the titlebar always visible
+ // how much must remain visible when moved away in that direction
+ left_marge = qMin(100 + borderRight(), moveResizeGeom.width());
+ right_marge = qMin(100 + borderLeft(), moveResizeGeom.width());
+ // width/height change with opaque resizing, use the initial ones
+ titlebar_marge = initialMoveResizeGeometry().height();
+ top_marge = borderBottom();
+ bottom_marge = borderTop();
+ if (isResize()) {
+ if (moveResizeGeom.bottom() < desktopArea.top() + top_marge)
+ setUnrestrictedMoveResize(true);
+ if (moveResizeGeom.top() > desktopArea.bottom() - bottom_marge)
+ setUnrestrictedMoveResize(true);
+ if (moveResizeGeom.right() < desktopArea.left() + left_marge)
+ setUnrestrictedMoveResize(true);
+ if (moveResizeGeom.left() > desktopArea.right() - right_marge)
+ setUnrestrictedMoveResize(true);
+ if (!isUnrestrictedMoveResize() && moveResizeGeom.top() < desktopArea.top()) // titlebar mustn't go out
+ setUnrestrictedMoveResize(true);
+ }
+ if (isMove()) {
+ if (moveResizeGeom.bottom() < desktopArea.top() + titlebar_marge - 1)
+ setUnrestrictedMoveResize(true);
+ // no need to check top_marge, titlebar_marge already handles it
+ if (moveResizeGeom.top() > desktopArea.bottom() - bottom_marge + 1) // titlebar mustn't go out
+ setUnrestrictedMoveResize(true);
+ if (moveResizeGeom.right() < desktopArea.left() + left_marge)
+ setUnrestrictedMoveResize(true);
+ if (moveResizeGeom.left() > desktopArea.right() - right_marge)
+ setUnrestrictedMoveResize(true);
+ }
+}
+
+// When the user pressed mouse on the titlebar, don't activate move immediatelly,
+// since it may be just a click. Activate instead after a delay. Move used to be
+// activated only after moving by several pixels, but that looks bad.
+void AbstractClient::startDelayedMoveResize()
+{
+ Q_ASSERT(!m_moveResize.delayedTimer);
+ m_moveResize.delayedTimer = new QTimer(this);
+ m_moveResize.delayedTimer->setSingleShot(true);
+ connect(m_moveResize.delayedTimer, &QTimer::timeout, this,
+ [this]() {
+ Q_ASSERT(isMoveResizePointerButtonDown());
+ if (!startMoveResize()) {
+ setMoveResizePointerButtonDown(false);
+ }
+ updateCursor();
+ stopDelayedMoveResize();
+ }
+ );
+ m_moveResize.delayedTimer->start(QApplication::startDragTime());
+}
+
+void AbstractClient::stopDelayedMoveResize()
+{
+ delete m_moveResize.delayedTimer;
+ m_moveResize.delayedTimer = nullptr;
+}
+
void AbstractClient::updateMoveResize(const QPointF ¤tGlobalCursor)
{
handleMoveResize(pos(), currentGlobalCursor.toPoint());
}
+void AbstractClient::handleMoveResize(const QPoint &local, const QPoint &global)
+{
+ const QRect oldGeo = frameGeometry();
+ handleMoveResize(local.x(), local.y(), global.x(), global.y());
+ if (!isFullScreen() && isMove()) {
+ if (quickTileMode() != QuickTileMode(QuickTileFlag::None) && oldGeo != frameGeometry()) {
+ GeometryUpdatesBlocker blocker(this);
+ setQuickTileMode(QuickTileFlag::None);
+ const QRect &geom_restore = geometryRestore();
+ setMoveOffset(QPoint(double(moveOffset().x()) / double(oldGeo.width()) * double(geom_restore.width()),
+ double(moveOffset().y()) / double(oldGeo.height()) * double(geom_restore.height())));
+ if (rules()->checkMaximize(MaximizeRestore) == MaximizeRestore)
+ setMoveResizeGeometry(geom_restore);
+ handleMoveResize(local.x(), local.y(), global.x(), global.y()); // fix position
+ } else if (quickTileMode() == QuickTileMode(QuickTileFlag::None) && isResizable()) {
+ checkQuickTilingMaximizationZones(global.x(), global.y());
+ }
+ }
+}
+
+void AbstractClient::handleMoveResize(int x, int y, int x_root, int y_root)
+{
+ if (isWaitingForMoveResizeSync())
+ return; // we're still waiting for the client or the timeout
+
+ const Position mode = moveResizePointerMode();
+ if ((mode == PositionCenter && !isMovableAcrossScreens())
+ || (mode != PositionCenter && (isShade() || !isResizable())))
+ return;
+
+ if (!isMoveResize()) {
+ QPoint p(QPoint(x/* - padding_left*/, y/* - padding_top*/) - moveOffset());
+ if (p.manhattanLength() >= QApplication::startDragDistance()) {
+ if (!startMoveResize()) {
+ setMoveResizePointerButtonDown(false);
+ updateCursor();
+ return;
+ }
+ updateCursor();
+ } else
+ return;
+ }
+
+ // ShadeHover or ShadeActive, ShadeNormal was already avoided above
+ if (mode != PositionCenter && shadeMode() != ShadeNone)
+ setShade(ShadeNone);
+
+ QPoint globalPos(x_root, y_root);
+ // these two points limit the geometry rectangle, i.e. if bottomleft resizing is done,
+ // the bottomleft corner should be at is at (topleft.x(), bottomright().y())
+ QPoint topleft = globalPos - moveOffset();
+ QPoint bottomright = globalPos + invertedMoveOffset();
+ QRect previousMoveResizeGeom = moveResizeGeometry();
+
+ // TODO move whole group when moving its leader or when the leader is not mapped?
+
+ auto titleBarRect = [this](bool &transposed, int &requiredPixels) -> QRect {
+ const QRect &moveResizeGeom = moveResizeGeometry();
+ QRect r(moveResizeGeom);
+ r.moveTopLeft(QPoint(0,0));
+ switch (titlebarPosition()) {
+ default:
+ case PositionTop:
+ r.setHeight(borderTop());
+ break;
+ case PositionLeft:
+ r.setWidth(borderLeft());
+ transposed = true;
+ break;
+ case PositionBottom:
+ r.setTop(r.bottom() - borderBottom());
+ break;
+ case PositionRight:
+ r.setLeft(r.right() - borderRight());
+ transposed = true;
+ break;
+ }
+ // When doing a restricted move we must always keep 100px of the titlebar
+ // visible to allow the user to be able to move it again.
+ requiredPixels = qMin(100 * (transposed ? r.width() : r.height()),
+ moveResizeGeom.width() * moveResizeGeom.height());
+ return r;
+ };
+
+ bool update = false;
+ if (isResize()) {
+ QRect orig = initialMoveResizeGeometry();
+ Sizemode sizemode = SizemodeAny;
+ auto calculateMoveResizeGeom = [this, &topleft, &bottomright, &orig, &sizemode, &mode]() {
+ switch(mode) {
+ case PositionTopLeft:
+ setMoveResizeGeometry(QRect(topleft, orig.bottomRight()));
+ break;
+ case PositionBottomRight:
+ setMoveResizeGeometry(QRect(orig.topLeft(), bottomright));
+ break;
+ case PositionBottomLeft:
+ setMoveResizeGeometry(QRect(QPoint(topleft.x(), orig.y()), QPoint(orig.right(), bottomright.y())));
+ break;
+ case PositionTopRight:
+ setMoveResizeGeometry(QRect(QPoint(orig.x(), topleft.y()), QPoint(bottomright.x(), orig.bottom())));
+ break;
+ case PositionTop:
+ setMoveResizeGeometry(QRect(QPoint(orig.left(), topleft.y()), orig.bottomRight()));
+ sizemode = SizemodeFixedH; // try not to affect height
+ break;
+ case PositionBottom:
+ setMoveResizeGeometry(QRect(orig.topLeft(), QPoint(orig.right(), bottomright.y())));
+ sizemode = SizemodeFixedH;
+ break;
+ case PositionLeft:
+ setMoveResizeGeometry(QRect(QPoint(topleft.x(), orig.top()), orig.bottomRight()));
+ sizemode = SizemodeFixedW;
+ break;
+ case PositionRight:
+ setMoveResizeGeometry(QRect(orig.topLeft(), QPoint(bottomright.x(), orig.bottom())));
+ sizemode = SizemodeFixedW;
+ break;
+ case PositionCenter:
+ default:
+ abort();
+ break;
+ }
+ };
+
+ // first resize (without checking constrains), then snap, then check bounds, then check constrains
+ calculateMoveResizeGeom();
+ // adjust new size to snap to other windows/borders
+ setMoveResizeGeometry(workspace()->adjustClientSize(this, moveResizeGeometry(), mode));
+
+ if (!isUnrestrictedMoveResize()) {
+ // Make sure the titlebar isn't behind a restricted area. We don't need to restrict
+ // the other directions. If not visible enough, move the window to the closest valid
+ // point. We bruteforce this by slowly moving the window back to its previous position
+ QRegion availableArea(workspace()->clientArea(FullArea, -1, 0)); // On the screen
+ availableArea -= workspace()->restrictedMoveArea(desktop()); // Strut areas
+ bool transposed = false;
+ int requiredPixels;
+ QRect bTitleRect = titleBarRect(transposed, requiredPixels);
+ int lastVisiblePixels = -1;
+ QRect lastTry = moveResizeGeometry();
+ bool titleFailed = false;
+ for (;;) {
+ const QRect titleRect(bTitleRect.translated(moveResizeGeometry().topLeft()));
+ int visiblePixels = 0;
+ int realVisiblePixels = 0;
+ for (const QRect &rect : availableArea) {
+ const QRect r = rect & titleRect;
+ realVisiblePixels += r.width() * r.height();
+ if ((transposed && r.width() == titleRect.width()) || // Only the full size regions...
+ (!transposed && r.height() == titleRect.height())) // ...prevents long slim areas
+ visiblePixels += r.width() * r.height();
+ }
+
+ if (visiblePixels >= requiredPixels)
+ break; // We have reached a valid position
+
+ if (realVisiblePixels <= lastVisiblePixels) {
+ if (titleFailed && realVisiblePixels < lastVisiblePixels)
+ break; // we won't become better
+ else {
+ if (!titleFailed)
+ setMoveResizeGeometry(lastTry);
+ titleFailed = true;
+ }
+ }
+ lastVisiblePixels = realVisiblePixels;
+ QRect moveResizeGeom = moveResizeGeometry();
+ lastTry = moveResizeGeom;
+
+ // Not visible enough, move the window to the closest valid point. We bruteforce
+ // this by slowly moving the window back to its previous position.
+ // The geometry changes at up to two edges, the one with the title (if) shall take
+ // precedence. The opposing edge has no impact on visiblePixels and only one of
+ // the adjacent can alter at a time, ie. it's enough to ignore adjacent edges
+ // if the title edge altered
+ bool leftChanged = previousMoveResizeGeom.left() != moveResizeGeom.left();
+ bool rightChanged = previousMoveResizeGeom.right() != moveResizeGeom.right();
+ bool topChanged = previousMoveResizeGeom.top() != moveResizeGeom.top();
+ bool btmChanged = previousMoveResizeGeom.bottom() != moveResizeGeom.bottom();
+ auto fixChangedState = [titleFailed](bool &major, bool &counter, bool &ad1, bool &ad2) {
+ counter = false;
+ if (titleFailed)
+ major = false;
+ if (major)
+ ad1 = ad2 = false;
+ };
+ switch (titlebarPosition()) {
+ default:
+ case PositionTop:
+ fixChangedState(topChanged, btmChanged, leftChanged, rightChanged);
+ break;
+ case PositionLeft:
+ fixChangedState(leftChanged, rightChanged, topChanged, btmChanged);
+ break;
+ case PositionBottom:
+ fixChangedState(btmChanged, topChanged, leftChanged, rightChanged);
+ break;
+ case PositionRight:
+ fixChangedState(rightChanged, leftChanged, topChanged, btmChanged);
+ break;
+ }
+ if (topChanged)
+ moveResizeGeom.setTop(moveResizeGeom.y() + sign(previousMoveResizeGeom.y() - moveResizeGeom.y()));
+ else if (leftChanged)
+ moveResizeGeom.setLeft(moveResizeGeom.x() + sign(previousMoveResizeGeom.x() - moveResizeGeom.x()));
+ else if (btmChanged)
+ moveResizeGeom.setBottom(moveResizeGeom.bottom() + sign(previousMoveResizeGeom.bottom() - moveResizeGeom.bottom()));
+ else if (rightChanged)
+ moveResizeGeom.setRight(moveResizeGeom.right() + sign(previousMoveResizeGeom.right() - moveResizeGeom.right()));
+ else
+ break; // no position changed - that's certainly not good
+ setMoveResizeGeometry(moveResizeGeom);
+ }
+ }
+
+ // Always obey size hints, even when in "unrestricted" mode
+ QSize size = adjustedSize(moveResizeGeometry().size(), sizemode);
+ // the new topleft and bottomright corners (after checking size constrains), if they'll be needed
+ topleft = QPoint(moveResizeGeometry().right() - size.width() + 1, moveResizeGeometry().bottom() - size.height() + 1);
+ bottomright = QPoint(moveResizeGeometry().left() + size.width() - 1, moveResizeGeometry().top() + size.height() - 1);
+ orig = moveResizeGeometry();
+
+ // if aspect ratios are specified, both dimensions may change.
+ // Therefore grow to the right/bottom if needed.
+ // TODO it should probably obey gravity rather than always using right/bottom ?
+ if (sizemode == SizemodeFixedH)
+ orig.setRight(bottomright.x());
+ else if (sizemode == SizemodeFixedW)
+ orig.setBottom(bottomright.y());
+
+ calculateMoveResizeGeom();
+
+ if (moveResizeGeometry().size() != previousMoveResizeGeom.size())
+ update = true;
+ } else if (isMove()) {
+ Q_ASSERT(mode == PositionCenter);
+ if (!isMovable()) { // isMovableAcrossScreens() must have been true to get here
+ // Special moving of maximized windows on Xinerama screens
+ int screen = screens()->number(globalPos);
+ if (isFullScreen())
+ setMoveResizeGeometry(workspace()->clientArea(FullScreenArea, screen, 0));
+ else {
+ QRect moveResizeGeom = workspace()->clientArea(MaximizeArea, screen, 0);
+ QSize adjSize = adjustedSize(moveResizeGeom.size(), SizemodeMax);
+ if (adjSize != moveResizeGeom.size()) {
+ QRect r(moveResizeGeom);
+ moveResizeGeom.setSize(adjSize);
+ moveResizeGeom.moveCenter(r.center());
+ }
+ setMoveResizeGeometry(moveResizeGeom);
+ }
+ } else {
+ // first move, then snap, then check bounds
+ QRect moveResizeGeom = moveResizeGeometry();
+ moveResizeGeom.moveTopLeft(topleft);
+ moveResizeGeom.moveTopLeft(workspace()->adjustClientPosition(this, moveResizeGeom.topLeft(),
+ isUnrestrictedMoveResize()));
+ setMoveResizeGeometry(moveResizeGeom);
+
+ if (!isUnrestrictedMoveResize()) {
+ const QRegion strut = workspace()->restrictedMoveArea(desktop()); // Strut areas
+ QRegion availableArea(workspace()->clientArea(FullArea, -1, 0)); // On the screen
+ availableArea -= strut; // Strut areas
+ bool transposed = false;
+ int requiredPixels;
+ QRect bTitleRect = titleBarRect(transposed, requiredPixels);
+ for (;;) {
+ QRect moveResizeGeom = moveResizeGeometry();
+ const QRect titleRect(bTitleRect.translated(moveResizeGeom.topLeft()));
+ int visiblePixels = 0;
+ for (const QRect &rect : availableArea) {
+ const QRect r = rect & titleRect;
+ if ((transposed && r.width() == titleRect.width()) || // Only the full size regions...
+ (!transposed && r.height() == titleRect.height())) // ...prevents long slim areas
+ visiblePixels += r.width() * r.height();
+ }
+ if (visiblePixels >= requiredPixels)
+ break; // We have reached a valid position
+
+ // (esp.) if there're more screens with different struts (panels) it the titlebar
+ // will be movable outside the movearea (covering one of the panels) until it
+ // crosses the panel "too much" (not enough visiblePixels) and then stucks because
+ // it's usually only pushed by 1px to either direction
+ // so we first check whether we intersect suc strut and move the window below it
+ // immediately (it's still possible to hit the visiblePixels >= titlebarArea break
+ // by moving the window slightly downwards, but it won't stuck)
+ // see bug #274466
+ // and bug #301805 for why we can't just match the titlearea against the screen
+ if (screens()->count() > 1) { // optimization
+ // TODO: could be useful on partial screen struts (half-width panels etc.)
+ int newTitleTop = -1;
+ for (const QRect &r : strut) {
+ if (r.top() == 0 && r.width() > r.height() && // "top panel"
+ r.intersects(moveResizeGeom) && moveResizeGeom.top() < r.bottom()) {
+ newTitleTop = r.bottom() + 1;
+ break;
+ }
+ }
+ if (newTitleTop > -1) {
+ moveResizeGeom.moveTop(newTitleTop); // invalid position, possibly on screen change
+ setMoveResizeGeometry(moveResizeGeom);
+ break;
+ }
+ }
+
+ int dx = sign(previousMoveResizeGeom.x() - moveResizeGeom.x()),
+ dy = sign(previousMoveResizeGeom.y() - moveResizeGeom.y());
+ if (visiblePixels && dx) // means there's no full width cap -> favor horizontally
+ dy = 0;
+ else if (dy)
+ dx = 0;
+
+ // Move it back
+ moveResizeGeom.translate(dx, dy);
+ setMoveResizeGeometry(moveResizeGeom);
+
+ if (moveResizeGeom == previousMoveResizeGeom) {
+ break; // Prevent lockup
+ }
+ }
+ }
+ }
+ if (moveResizeGeometry().topLeft() != previousMoveResizeGeom.topLeft())
+ update = true;
+ } else
+ abort();
+
+ if (!update)
+ return;
+
+ if (isResize() && !haveResizeEffect()) {
+ doResizeSync();
+ } else
+ performMoveResize();
+
+ if (isMove()) {
+ ScreenEdges::self()->check(globalPos, QDateTime::fromMSecsSinceEpoch(xTime()));
+ }
+}
+
+void AbstractClient::performMoveResize()
+{
+ const QRect &moveResizeGeom = moveResizeGeometry();
+ if (isMove() || (isResize() && !haveResizeEffect())) {
+ setFrameGeometry(moveResizeGeom);
+ }
+ doPerformMoveResize();
+ if (isResize())
+ addRepaintFull();
+ positionGeometryTip();
+ emit clientStepUserMovedResized(this, moveResizeGeom);
+}
+
bool AbstractClient::hasStrut() const
{
return false;
@@ -2047,4 +2637,509 @@ QRect AbstractClient::clientRectToFrameRect(const QRect &rect) const
return QRect(position, size);
}
+void AbstractClient::setElectricBorderMode(QuickTileMode mode)
+{
+ if (mode != QuickTileMode(QuickTileFlag::Maximize)) {
+ // sanitize the mode, ie. simplify "invalid" combinations
+ if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Horizontal))
+ mode &= ~QuickTileMode(QuickTileFlag::Horizontal);
+ if ((mode & QuickTileFlag::Vertical) == QuickTileMode(QuickTileFlag::Vertical))
+ mode &= ~QuickTileMode(QuickTileFlag::Vertical);
+ }
+ m_electricMode = mode;
+}
+
+void AbstractClient::setElectricBorderMaximizing(bool maximizing)
+{
+ m_electricMaximizing = maximizing;
+ if (maximizing)
+ outline()->show(electricBorderMaximizeGeometry(Cursor::pos(), desktop()), moveResizeGeometry());
+ else
+ outline()->hide();
+ elevate(maximizing);
+}
+
+QRect AbstractClient::electricBorderMaximizeGeometry(QPoint pos, int desktop)
+{
+ if (electricBorderMode() == QuickTileMode(QuickTileFlag::Maximize)) {
+ if (maximizeMode() == MaximizeFull)
+ return geometryRestore();
+ else
+ return workspace()->clientArea(MaximizeArea, pos, desktop);
+ }
+
+ QRect ret = workspace()->clientArea(MaximizeArea, pos, desktop);
+ if (electricBorderMode() & QuickTileFlag::Left)
+ ret.setRight(ret.left()+ret.width()/2 - 1);
+ else if (electricBorderMode() & QuickTileFlag::Right)
+ ret.setLeft(ret.right()-(ret.width()-ret.width()/2) + 1);
+ if (electricBorderMode() & QuickTileFlag::Top)
+ ret.setBottom(ret.top()+ret.height()/2 - 1);
+ else if (electricBorderMode() & QuickTileFlag::Bottom)
+ ret.setTop(ret.bottom()-(ret.height()-ret.height()/2) + 1);
+
+ return ret;
+}
+
+void AbstractClient::setQuickTileMode(QuickTileMode mode, bool keyboard)
+{
+ // Only allow quick tile on a regular window.
+ if (!isResizable()) {
+ return;
+ }
+
+ workspace()->updateFocusMousePosition(Cursor::pos()); // may cause leave event
+
+ GeometryUpdatesBlocker blocker(this);
+
+ if (mode == QuickTileMode(QuickTileFlag::Maximize)) {
+ m_quickTileMode = int(QuickTileFlag::None);
+ if (maximizeMode() == MaximizeFull) {
+ setMaximize(false, false);
+ } else {
+ QRect prev_geom_restore = geometryRestore(); // setMaximize() would set moveResizeGeom as geom_restore
+ m_quickTileMode = int(QuickTileFlag::Maximize);
+ setMaximize(true, true);
+ QRect clientArea = workspace()->clientArea(MaximizeArea, this);
+ if (frameGeometry().top() != clientArea.top()) {
+ QRect r(frameGeometry());
+ r.moveTop(clientArea.top());
+ setFrameGeometry(r);
+ }
+ setGeometryRestore(prev_geom_restore);
+ }
+ emit quickTileModeChanged();
+ return;
+ }
+
+ // sanitize the mode, ie. simplify "invalid" combinations
+ if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Horizontal))
+ mode &= ~QuickTileMode(QuickTileFlag::Horizontal);
+ if ((mode & QuickTileFlag::Vertical) == QuickTileMode(QuickTileFlag::Vertical))
+ mode &= ~QuickTileMode(QuickTileFlag::Vertical);
+
+ setElectricBorderMode(mode); // used by ::electricBorderMaximizeGeometry(.)
+
+ // restore from maximized so that it is possible to tile maximized windows with one hit or by dragging
+ if (maximizeMode() != MaximizeRestore) {
+
+ if (mode != QuickTileMode(QuickTileFlag::None)) {
+ // decorations may turn off some borders when tiled
+ const ForceGeometry_t geom_mode = isDecorated() ? ForceGeometrySet : NormalGeometrySet;
+ m_quickTileMode = int(QuickTileFlag::None); // Temporary, so the maximize code doesn't get all confused
+
+ setMaximize(false, false);
+
+ setFrameGeometry(electricBorderMaximizeGeometry(keyboard ? frameGeometry().center() : Cursor::pos(), desktop()), geom_mode);
+ // Store the mode change
+ m_quickTileMode = mode;
+ } else {
+ m_quickTileMode = mode;
+ setMaximize(false, false);
+ }
+
+ emit quickTileModeChanged();
+
+ return;
+ }
+
+ if (mode != QuickTileMode(QuickTileFlag::None)) {
+ QPoint whichScreen = keyboard ? frameGeometry().center() : Cursor::pos();
+
+ // If trying to tile to the side that the window is already tiled to move the window to the next
+ // screen if it exists, otherwise toggle the mode (set QuickTileFlag::None)
+ if (quickTileMode() == mode) {
+ const int numScreens = screens()->count();
+ const int curScreen = screen();
+ int nextScreen = curScreen;
+ QVarLengthArray screens(numScreens);
+ for (int i = 0; i < numScreens; ++i) // Cache
+ screens[i] = Screens::self()->geometry(i);
+ for (int i = 0; i < numScreens; ++i) {
+
+ if (i == curScreen)
+ continue;
+
+ if (screens[i].bottom() <= screens[curScreen].top() || screens[i].top() >= screens[curScreen].bottom())
+ continue; // not in horizontal line
+
+ const int x = screens[i].center().x();
+ if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Left)) {
+ if (x >= screens[curScreen].center().x() || (curScreen != nextScreen && x <= screens[nextScreen].center().x()))
+ continue; // not left of current or more left then found next
+ } else if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Right)) {
+ if (x <= screens[curScreen].center().x() || (curScreen != nextScreen && x >= screens[nextScreen].center().x()))
+ continue; // not right of current or more right then found next
+ }
+
+ nextScreen = i;
+ }
+
+ if (nextScreen == curScreen) {
+ mode = QuickTileFlag::None; // No other screens, toggle tiling
+ } else {
+ // Move to other screen
+ setFrameGeometry(geometryRestore().translated(screens[nextScreen].topLeft() - screens[curScreen].topLeft()));
+ whichScreen = screens[nextScreen].center();
+
+ // Swap sides
+ if (mode & QuickTileFlag::Horizontal) {
+ mode = (~mode & QuickTileFlag::Horizontal) | (mode & QuickTileFlag::Vertical);
+ }
+ }
+ setElectricBorderMode(mode); // used by ::electricBorderMaximizeGeometry(.)
+ } else if (quickTileMode() == QuickTileMode(QuickTileFlag::None)) {
+ // Not coming out of an existing tile, not shifting monitors, we're setting a brand new tile.
+ // Store geometry first, so we can go out of this tile later.
+ setGeometryRestore(frameGeometry());
+ }
+
+ if (mode != QuickTileMode(QuickTileFlag::None)) {
+ m_quickTileMode = mode;
+ // decorations may turn off some borders when tiled
+ const ForceGeometry_t geom_mode = isDecorated() ? ForceGeometrySet : NormalGeometrySet;
+ // Temporary, so the maximize code doesn't get all confused
+ m_quickTileMode = int(QuickTileFlag::None);
+ setFrameGeometry(electricBorderMaximizeGeometry(whichScreen, desktop()), geom_mode);
+ }
+
+ // Store the mode change
+ m_quickTileMode = mode;
+ }
+
+ if (mode == QuickTileMode(QuickTileFlag::None)) {
+ m_quickTileMode = int(QuickTileFlag::None);
+ // Untiling, so just restore geometry, and we're done.
+ if (!geometryRestore().isValid()) // invalid if we started maximized and wait for placement
+ setGeometryRestore(frameGeometry());
+ // decorations may turn off some borders when tiled
+ const ForceGeometry_t geom_mode = isDecorated() ? ForceGeometrySet : NormalGeometrySet;
+ setFrameGeometry(geometryRestore(), geom_mode);
+ checkWorkspacePosition(); // Just in case it's a different screen
+ }
+ emit quickTileModeChanged();
+}
+
+void AbstractClient::sendToScreen(int newScreen)
+{
+ newScreen = rules()->checkScreen(newScreen);
+ if (isActive()) {
+ screens()->setCurrent(newScreen);
+ // might impact the layer of a fullscreen window
+ foreach (AbstractClient *cc, workspace()->allClientList()) {
+ if (cc->isFullScreen() && cc->screen() == newScreen) {
+ cc->updateLayer();
+ }
+ }
+ }
+ if (screen() == newScreen) // Don't use isOnScreen(), that's true even when only partially
+ return;
+
+ GeometryUpdatesBlocker blocker(this);
+
+ // operating on the maximized / quicktiled window would leave the old geom_restore behind,
+ // so we clear the state first
+ MaximizeMode maxMode = maximizeMode();
+ QuickTileMode qtMode = quickTileMode();
+ if (maxMode != MaximizeRestore)
+ maximize(MaximizeRestore);
+ if (qtMode != QuickTileMode(QuickTileFlag::None))
+ setQuickTileMode(QuickTileFlag::None, true);
+
+ QRect oldScreenArea = workspace()->clientArea(MaximizeArea, this);
+ QRect screenArea = workspace()->clientArea(MaximizeArea, newScreen, desktop());
+
+ // the window can have its center so that the position correction moves the new center onto
+ // the old screen, what will tile it where it is. Ie. the screen is not changed
+ // this happens esp. with electric border quicktiling
+ if (qtMode != QuickTileMode(QuickTileFlag::None))
+ keepInArea(oldScreenArea);
+
+ QRect oldGeom = frameGeometry();
+ QRect newGeom = oldGeom;
+ // move the window to have the same relative position to the center of the screen
+ // (i.e. one near the middle of the right edge will also end up near the middle of the right edge)
+ QPoint center = newGeom.center() - oldScreenArea.center();
+ center.setX(center.x() * screenArea.width() / oldScreenArea.width());
+ center.setY(center.y() * screenArea.height() / oldScreenArea.height());
+ center += screenArea.center();
+ newGeom.moveCenter(center);
+ setFrameGeometry(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)) {
+ keepInArea(screenArea);
+ }
+
+ // align geom_restore - checkWorkspacePosition operates on it
+ setGeometryRestore(frameGeometry());
+
+ checkWorkspacePosition(oldGeom);
+
+ // re-align geom_restore to constrained geometry
+ setGeometryRestore(frameGeometry());
+
+ // finally reset special states
+ // NOTICE that MaximizeRestore/QuickTileFlag::None checks are required.
+ // eg. setting QuickTileFlag::None would break maximization
+ if (maxMode != MaximizeRestore)
+ maximize(maxMode);
+ if (qtMode != QuickTileMode(QuickTileFlag::None) && qtMode != quickTileMode())
+ setQuickTileMode(qtMode, true);
+
+ auto tso = workspace()->ensureStackingOrder(transients());
+ for (auto it = tso.constBegin(), end = tso.constEnd(); it != end; ++it)
+ (*it)->sendToScreen(newScreen);
+}
+
+void AbstractClient::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 = frameGeometry();
+ if( oldDesktop == -2 )
+ oldDesktop = desktop();
+ if (!oldClientGeometry.isValid())
+ oldClientGeometry = oldGeometry.adjusted(border[Left], border[Top], -border[Right], -border[Bottom]);
+ if (isDesktop())
+ return;
+ if (isFullScreen()) {
+ QRect area = workspace()->clientArea(FullScreenArea, this);
+ if (frameGeometry() != area)
+ setFrameGeometry(area);
+ return;
+ }
+ if (isDock())
+ return;
+
+ if (maximizeMode() != MaximizeRestore) {
+ // TODO update geom_restore?
+ changeMaximize(false, false, true); // adjust size
+ const QRect screenArea = workspace()->clientArea(ScreenArea, this);
+ QRect geom = frameGeometry();
+ checkOffscreenPosition(&geom, screenArea);
+ setFrameGeometry(geom);
+ return;
+ }
+
+ if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) {
+ setFrameGeometry(electricBorderMaximizeGeometry(frameGeometry().center(), desktop()));
+ return;
+ }
+
+ // this can be true only if this window was mapped before KWin
+ // was started - in such case, don't adjust position to workarea,
+ // because the window already had its position, and if a window
+ // with a strut altering the workarea would be managed in initialization
+ // after this one, this window would be moved
+ if (!workspace() || workspace()->initializing())
+ return;
+
+ // If the window was touching an edge before but not now move it so it is again.
+ // Old and new maximums have different starting values so windows on the screen
+ // edge will move when a new strut is placed on the edge.
+ QRect oldScreenArea;
+ if( workspace()->inUpdateClientArea()) {
+ // we need to find the screen area as it was before the change
+ oldScreenArea = QRect( 0, 0, workspace()->oldDisplayWidth(), workspace()->oldDisplayHeight());
+ int distance = INT_MAX;
+ foreach(const QRect &r, workspace()->previousScreenSizes()) {
+ int d = r.contains( oldGeometry.center()) ? 0 : ( r.center() - oldGeometry.center()).manhattanLength();
+ if( d < distance ) {
+ distance = d;
+ oldScreenArea = r;
+ }
+ }
+ } else {
+ oldScreenArea = workspace()->clientArea(ScreenArea, oldGeometry.center(), oldDesktop);
+ }
+ const QRect oldGeomTall = QRect(oldGeometry.x(), oldScreenArea.y(), oldGeometry.width(), oldScreenArea.height()); // Full screen height
+ const QRect oldGeomWide = QRect(oldScreenArea.x(), oldGeometry.y(), oldScreenArea.width(), oldGeometry.height()); // Full screen width
+ int oldTopMax = oldScreenArea.y();
+ int oldRightMax = oldScreenArea.x() + oldScreenArea.width();
+ int oldBottomMax = oldScreenArea.y() + oldScreenArea.height();
+ int oldLeftMax = oldScreenArea.x();
+ const QRect screenArea = workspace()->clientArea(ScreenArea, geometryRestore().center(), desktop());
+ int topMax = screenArea.y();
+ int rightMax = screenArea.x() + screenArea.width();
+ int bottomMax = screenArea.y() + screenArea.height();
+ int leftMax = screenArea.x();
+ QRect newGeom = geometryRestore(); // geometry();
+ QRect newClientGeom = newGeom.adjusted(border[Left], border[Top], -border[Right], -border[Bottom]);
+ const QRect newGeomTall = QRect(newGeom.x(), screenArea.y(), newGeom.width(), screenArea.height()); // Full screen height
+ const QRect newGeomWide = QRect(screenArea.x(), newGeom.y(), screenArea.width(), 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).
+
+ // 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
+
+ for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaTop)) {
+ QRect rect = r & oldGeomTall;
+ if (!rect.isEmpty())
+ oldTopMax = qMax(oldTopMax, rect.y() + rect.height());
+ }
+ for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaRight)) {
+ QRect rect = r & oldGeomWide;
+ if (!rect.isEmpty())
+ oldRightMax = qMin(oldRightMax, rect.x());
+ }
+ for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaBottom)) {
+ QRect rect = r & oldGeomTall;
+ if (!rect.isEmpty())
+ oldBottomMax = qMin(oldBottomMax, rect.y());
+ }
+ for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaLeft)) {
+ QRect rect = r & oldGeomWide;
+ if (!rect.isEmpty())
+ oldLeftMax = qMax(oldLeftMax, rect.x() + rect.width());
+ }
+
+ // These 4 compute new bounds
+ for (const QRect &r : workspace()->restrictedMoveArea(desktop(), StrutAreaTop)) {
+ QRect rect = r & newGeomTall;
+ if (!rect.isEmpty())
+ topMax = qMax(topMax, rect.y() + rect.height());
+ }
+ for (const QRect &r : workspace()->restrictedMoveArea(desktop(), StrutAreaRight)) {
+ QRect rect = r & newGeomWide;
+ if (!rect.isEmpty())
+ rightMax = qMin(rightMax, rect.x());
+ }
+ for (const QRect &r : workspace()->restrictedMoveArea(desktop(), StrutAreaBottom)) {
+ QRect rect = r & newGeomTall;
+ if (!rect.isEmpty())
+ bottomMax = qMin(bottomMax, rect.y());
+ }
+ for (const QRect &r : workspace()->restrictedMoveArea(desktop(), StrutAreaLeft)) {
+ QRect rect = r & newGeomWide;
+ if (!rect.isEmpty())
+ leftMax = qMax(leftMax, rect.x() + rect.width());
+ }
+
+
+ // Check if the sides were inside or touching but are no longer
+ 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() >= 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.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.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);
+ // Obey size hints. TODO: We really should make sure it stays in the right place
+ if (!isShade())
+ newGeom.setSize(adjustedSize(newGeom.size()));
+
+ if (newGeom != frameGeometry())
+ setFrameGeometry(newGeom);
+}
+
+void AbstractClient::checkOffscreenPosition(QRect* geom, const QRect& screenArea)
+{
+ if (geom->left() > screenArea.right()) {
+ geom->moveLeft(screenArea.right() - screenArea.width()/4);
+ } else if (geom->right() < screenArea.left()) {
+ geom->moveRight(screenArea.left() + screenArea.width()/4);
+ }
+ if (geom->top() > screenArea.bottom()) {
+ geom->moveTop(screenArea.bottom() - screenArea.height()/4);
+ } else if (geom->bottom() < screenArea.top()) {
+ geom->moveBottom(screenArea.top() + screenArea.width()/4);
+ }
+}
+
+QSize AbstractClient::adjustedSize(const QSize& frame, Sizemode mode) const
+{
+ // first, get the window size for the given frame size s
+ QSize wsize = frameSizeToClientSize(frame);
+ if (wsize.isEmpty())
+ wsize = QSize(qMax(wsize.width(), 1), qMax(wsize.height(), 1));
+
+ return sizeForClientSize(wsize, mode, false);
+}
+
+// this helper returns proper size even if the window is shaded
+// see also the comment in X11Client::setGeometry()
+QSize AbstractClient::adjustedSize() const
+{
+ return sizeForClientSize(clientSize());
+}
+
}
diff --git a/geometry.cpp b/geometry.cpp
deleted file mode 100644
index cfd7e8bc6e..0000000000
--- a/geometry.cpp
+++ /dev/null
@@ -1,3406 +0,0 @@
-/********************************************************************
- KWin - the KDE window manager
- This file is part of the KDE project.
-
-Copyright (C) 1999, 2000 Matthias Ettrich
-Copyright (C) 2003 Lubos Lunak
-Copyright (C) 2009 Lucas Murray
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program. If not, see .
-*********************************************************************/
-
-/*
-
- This file contains things relevant to geometry, i.e. workspace size,
- window positions and window sizes.
-
-*/
-
-#include "x11client.h"
-#include "composite.h"
-#include "cursor.h"
-#include "netinfo.h"
-#include "workspace.h"
-
-#include "placement.h"
-#include "geometrytip.h"
-#include "rules.h"
-#include "screens.h"
-#include "effects.h"
-#include "screenedge.h"
-#include "internal_client.h"
-#include
-#include
-#include
-
-#include "outline.h"
-#include "xdgshellclient.h"
-#include "wayland_server.h"
-
-#include
-#include
-
-namespace KWin
-{
-
-static inline int sign(int v) {
- return (v > 0) - (v < 0);
-}
-
-//********************************************
-// Workspace
-//********************************************
-
-extern int screen_number;
-extern bool is_multihead;
-
-/**
- * Resizes the workspace after an XRANDR screen size change
- */
-void Workspace::desktopResized()
-{
- QRect geom = screens()->geometry();
- if (rootInfo()) {
- NETSize desktop_geometry;
- desktop_geometry.width = geom.width();
- desktop_geometry.height = geom.height();
- rootInfo()->setDesktopGeometry(desktop_geometry);
- }
-
- updateClientArea();
- saveOldScreenSizes(); // after updateClientArea(), so that one still uses the previous one
-
- // TODO: emit a signal instead and remove the deep function calls into edges and effects
- ScreenEdges::self()->recreateEdges();
-
- if (effects) {
- static_cast(effects)->desktopResized(geom.size());
- }
-}
-
-void Workspace::saveOldScreenSizes()
-{
- olddisplaysize = screens()->displaySize();
- oldscreensizes.clear();
- for( int i = 0;
- i < screens()->count();
- ++i )
- oldscreensizes.append( screens()->geometry( i ));
-}
-
-/**
- * Updates the current client areas according to the current clients.
- *
- * If the area changes or force is @c true, the new areas are propagated to the world.
- *
- * The client area is the area that is available for clients (that
- * which is not taken by windows like panels, the top-of-screen menu
- * etc).
- *
- * @see clientArea()
- */
-void Workspace::updateClientArea(bool force)
-{
- const Screens *s = Screens::self();
- int nscreens = s->count();
- const int numberOfDesktops = VirtualDesktopManager::self()->count();
- QVector< QRect > new_wareas(numberOfDesktops + 1);
- QVector< StrutRects > new_rmoveareas(numberOfDesktops + 1);
- QVector< QVector< QRect > > new_sareas(numberOfDesktops + 1);
- QVector< QRect > screens(nscreens);
- QRect desktopArea;
- for (int i = 0; i < nscreens; i++) {
- desktopArea |= s->geometry(i);
- }
- for (int iS = 0;
- iS < nscreens;
- iS ++) {
- screens [iS] = s->geometry(iS);
- }
- for (int i = 1;
- i <= numberOfDesktops;
- ++i) {
- new_wareas[ i ] = desktopArea;
- new_sareas[ i ].resize(nscreens);
- for (int iS = 0;
- iS < nscreens;
- iS ++)
- new_sareas[ i ][ iS ] = screens[ iS ];
- }
- for (auto it = clients.constBegin(); it != clients.constEnd(); ++it) {
- if (!(*it)->hasStrut())
- continue;
- QRect r = (*it)->adjustedClientArea(desktopArea, desktopArea);
- // sanity check that a strut doesn't exclude a complete screen geometry
- // this is a violation to EWMH, as KWin just ignores the strut
- for (int i = 0; i < Screens::self()->count(); i++) {
- if (!r.intersects(Screens::self()->geometry(i))) {
- qCDebug(KWIN_CORE) << "Adjusted client area would exclude a complete screen, ignore";
- r = desktopArea;
- break;
- }
- }
- StrutRects strutRegion = (*it)->strutRects();
- const QRect clientsScreenRect = KWin::screens()->geometry((*it)->screen());
- for (auto strut = strutRegion.begin(); strut != strutRegion.end(); strut++) {
- *strut = StrutRect((*strut).intersected(clientsScreenRect), (*strut).area());
- }
-
- // Ignore offscreen xinerama struts. These interfere with the larger monitors on the setup
- // and should be ignored so that applications that use the work area to work out where
- // windows can go can use the entire visible area of the larger monitors.
- // This goes against the EWMH description of the work area but it is a toss up between
- // having unusable sections of the screen (Which can be quite large with newer monitors)
- // or having some content appear offscreen (Relatively rare compared to other).
- bool hasOffscreenXineramaStrut = (*it)->hasOffscreenXineramaStrut();
-
- if ((*it)->isOnAllDesktops()) {
- for (int i = 1;
- i <= numberOfDesktops;
- ++i) {
- if (!hasOffscreenXineramaStrut)
- new_wareas[ i ] = new_wareas[ i ].intersected(r);
- new_rmoveareas[ i ] += strutRegion;
- for (int iS = 0;
- iS < nscreens;
- iS ++) {
- const auto geo = new_sareas[ i ][ iS ].intersected(
- (*it)->adjustedClientArea(desktopArea, screens[ iS ]));
- // ignore the geometry if it results in the screen getting removed completely
- if (!geo.isEmpty()) {
- new_sareas[ i ][ iS ] = geo;
- }
- }
- }
- } else {
- if (!hasOffscreenXineramaStrut)
- new_wareas[(*it)->desktop()] = new_wareas[(*it)->desktop()].intersected(r);
- new_rmoveareas[(*it)->desktop()] += strutRegion;
- for (int iS = 0;
- iS < nscreens;
- iS ++) {
-// qDebug() << "adjusting new_sarea: " << screens[ iS ];
- const auto geo = new_sareas[(*it)->desktop()][ iS ].intersected(
- (*it)->adjustedClientArea(desktopArea, screens[ iS ]));
- // ignore the geometry if it results in the screen getting removed completely
- if (!geo.isEmpty()) {
- new_sareas[(*it)->desktop()][ iS ] = geo;
- }
- }
- }
- }
- if (waylandServer()) {
- auto updateStrutsForWaylandClient = [&] (XdgShellClient *c) {
- // assuming that only docks have "struts" and that all docks have a strut
- if (!c->hasStrut()) {
- return;
- }
- auto margins = [c] (const QRect &geometry) {
- QMargins margins;
- if (!geometry.intersects(c->frameGeometry())) {
- return margins;
- }
- // figure out which areas of the overall screen setup it borders
- const bool left = c->frameGeometry().left() == geometry.left();
- const bool right = c->frameGeometry().right() == geometry.right();
- const bool top = c->frameGeometry().top() == geometry.top();
- const bool bottom = c->frameGeometry().bottom() == geometry.bottom();
- const bool horizontal = c->frameGeometry().width() >= c->frameGeometry().height();
- if (left && ((!top && !bottom) || !horizontal)) {
- margins.setLeft(c->frameGeometry().width());
- }
- if (right && ((!top && !bottom) || !horizontal)) {
- margins.setRight(c->frameGeometry().width());
- }
- if (top && ((!left && !right) || horizontal)) {
- margins.setTop(c->frameGeometry().height());
- }
- if (bottom && ((!left && !right) || horizontal)) {
- margins.setBottom(c->frameGeometry().height());
- }
- return margins;
- };
- auto marginsToStrutArea = [] (const QMargins &margins) {
- if (margins.left() != 0) {
- return StrutAreaLeft;
- }
- if (margins.right() != 0) {
- return StrutAreaRight;
- }
- if (margins.top() != 0) {
- return StrutAreaTop;
- }
- if (margins.bottom() != 0) {
- return StrutAreaBottom;
- }
- return StrutAreaInvalid;
- };
- const auto strut = margins(KWin::screens()->geometry(c->screen()));
- const StrutRects strutRegion = StrutRects{StrutRect(c->frameGeometry(), marginsToStrutArea(strut))};
- QRect r = desktopArea - margins(KWin::screens()->geometry());
- if (c->isOnAllDesktops()) {
- for (int i = 1; i <= numberOfDesktops; ++i) {
- new_wareas[ i ] = new_wareas[ i ].intersected(r);
- for (int iS = 0; iS < nscreens; ++iS) {
- new_sareas[ i ][ iS ] = new_sareas[ i ][ iS ].intersected(screens[iS] - margins(screens[iS]));
- }
- new_rmoveareas[ i ] += strutRegion;
- }
- } else {
- new_wareas[c->desktop()] = new_wareas[c->desktop()].intersected(r);
- for (int iS = 0; iS < nscreens; iS++) {
- new_sareas[c->desktop()][ iS ] = new_sareas[c->desktop()][ iS ].intersected(screens[iS] - margins(screens[iS]));
- }
- new_rmoveareas[ c->desktop() ] += strutRegion;
- }
- };
- const auto clients = waylandServer()->clients();
- for (auto c : clients) {
- updateStrutsForWaylandClient(c);
- }
- }
-#if 0
- for (int i = 1;
- i <= numberOfDesktops();
- ++i) {
- for (int iS = 0;
- iS < nscreens;
- iS ++)
- qCDebug(KWIN_CORE) << "new_sarea: " << new_sareas[ i ][ iS ];
- }
-#endif
-
- bool changed = force;
-
- if (screenarea.isEmpty())
- changed = true;
-
- for (int i = 1;
- !changed && i <= numberOfDesktops;
- ++i) {
- if (workarea[ i ] != new_wareas[ i ])
- changed = true;
- if (restrictedmovearea[ i ] != new_rmoveareas[ i ])
- changed = true;
- if (screenarea[ i ].size() != new_sareas[ i ].size())
- changed = true;
- for (int iS = 0;
- !changed && iS < nscreens;
- iS ++)
- if (new_sareas[ i ][ iS ] != screenarea [ i ][ iS ])
- changed = true;
- }
-
- if (changed) {
- workarea = new_wareas;
- oldrestrictedmovearea = restrictedmovearea;
- restrictedmovearea = new_rmoveareas;
- screenarea = new_sareas;
- if (rootInfo()) {
- NETRect r;
- for (int i = 1; i <= numberOfDesktops; i++) {
- r.pos.x = workarea[ i ].x();
- r.pos.y = workarea[ i ].y();
- r.size.width = workarea[ i ].width();
- r.size.height = workarea[ i ].height();
- rootInfo()->setWorkArea(i, r);
- }
- }
-
- for (auto it = m_allClients.constBegin();
- it != m_allClients.constEnd();
- ++it)
- (*it)->checkWorkspacePosition();
-
- oldrestrictedmovearea.clear(); // reset, no longer valid or needed
- }
-}
-
-void Workspace::updateClientArea()
-{
- updateClientArea(false);
-}
-
-
-/**
- * Returns the area available for clients. This is the desktop
- * geometry minus windows on the dock. Placement algorithms should
- * refer to this rather than Screens::geometry.
- */
-QRect Workspace::clientArea(clientAreaOption opt, int screen, int desktop) const
-{
- if (desktop == NETWinInfo::OnAllDesktops || desktop == 0)
- desktop = VirtualDesktopManager::self()->current();
- if (screen == -1)
- screen = screens()->current();
- const QSize displaySize = screens()->displaySize();
-
- QRect sarea, warea;
-
- if (is_multihead) {
- sarea = (!screenarea.isEmpty()
- && screen < screenarea[ desktop ].size()) // screens may be missing during KWin initialization or screen config changes
- ? screenarea[ desktop ][ screen_number ]
- : screens()->geometry(screen_number);
- warea = workarea[ desktop ].isNull()
- ? screens()->geometry(screen_number)
- : workarea[ desktop ];
- } else {
- sarea = (!screenarea.isEmpty()
- && screen < screenarea[ desktop ].size()) // screens may be missing during KWin initialization or screen config changes
- ? screenarea[ desktop ][ screen ]
- : screens()->geometry(screen);
- warea = workarea[ desktop ].isNull()
- ? QRect(0, 0, displaySize.width(), displaySize.height())
- : workarea[ desktop ];
- }
-
- switch(opt) {
- case MaximizeArea:
- case PlacementArea:
- return sarea;
- case MaximizeFullArea:
- case FullScreenArea:
- case MovementArea:
- case ScreenArea:
- if (is_multihead)
- return screens()->geometry(screen_number);
- else
- return screens()->geometry(screen);
- case WorkArea:
- if (is_multihead)
- return sarea;
- else
- return warea;
- case FullArea:
- return QRect(0, 0, displaySize.width(), displaySize.height());
- }
- abort();
-}
-
-
-QRect Workspace::clientArea(clientAreaOption opt, const QPoint& p, int desktop) const
-{
- return clientArea(opt, screens()->number(p), desktop);
-}
-
-QRect Workspace::clientArea(clientAreaOption opt, const AbstractClient* c) const
-{
- return clientArea(opt, c->frameGeometry().center(), c->desktop());
-}
-
-QRegion Workspace::restrictedMoveArea(int desktop, StrutAreas areas) const
-{
- if (desktop == NETWinInfo::OnAllDesktops || desktop == 0)
- desktop = VirtualDesktopManager::self()->current();
- QRegion region;
- foreach (const StrutRect & rect, restrictedmovearea[desktop])
- if (areas & rect.area())
- region += rect;
- return region;
-}
-
-bool Workspace::inUpdateClientArea() const
-{
- return !oldrestrictedmovearea.isEmpty();
-}
-
-QRegion Workspace::previousRestrictedMoveArea(int desktop, StrutAreas areas) const
-{
- if (desktop == NETWinInfo::OnAllDesktops || desktop == 0)
- desktop = VirtualDesktopManager::self()->current();
- QRegion region;
- foreach (const StrutRect & rect, oldrestrictedmovearea.at(desktop))
- if (areas & rect.area())
- region += rect;
- return region;
-}
-
-QVector< QRect > Workspace::previousScreenSizes() const
-{
- return oldscreensizes;
-}
-
-int Workspace::oldDisplayWidth() const
-{
- return olddisplaysize.width();
-}
-
-int Workspace::oldDisplayHeight() const
-{
- return olddisplaysize.height();
-}
-
-/**
- * Client \a c is moved around to position \a pos. This gives the
- * workspace the opportunity to interveniate and to implement
- * snap-to-windows functionality.
- *
- * The parameter \a snapAdjust is a multiplier used to calculate the
- * effective snap zones. When 1.0, it means that the snap zones will be
- * used without change.
- */
-QPoint Workspace::adjustClientPosition(AbstractClient* c, QPoint pos, bool unrestricted, double snapAdjust)
-{
- QSize borderSnapZone(options->borderSnapZone(), options->borderSnapZone());
- QRect maxRect;
- int guideMaximized = MaximizeRestore;
- if (c->maximizeMode() != MaximizeRestore) {
- maxRect = clientArea(MaximizeArea, pos + c->rect().center(), c->desktop());
- QRect geo = c->frameGeometry();
- if (c->maximizeMode() & MaximizeHorizontal && (geo.x() == maxRect.left() || geo.right() == maxRect.right())) {
- guideMaximized |= MaximizeHorizontal;
- borderSnapZone.setWidth(qMax(borderSnapZone.width() + 2, maxRect.width() / 16));
- }
- if (c->maximizeMode() & MaximizeVertical && (geo.y() == maxRect.top() || geo.bottom() == maxRect.bottom())) {
- guideMaximized |= MaximizeVertical;
- borderSnapZone.setHeight(qMax(borderSnapZone.height() + 2, maxRect.height() / 16));
- }
- }
-
- if (options->windowSnapZone() || !borderSnapZone.isNull() || options->centerSnapZone()) {
-
- const bool sOWO = options->isSnapOnlyWhenOverlapping();
- const int screen = screens()->number(pos + c->rect().center());
- if (maxRect.isNull())
- maxRect = clientArea(MovementArea, screen, c->desktop());
- const int xmin = maxRect.left();
- const int xmax = maxRect.right() + 1; //desk size
- const int ymin = maxRect.top();
- const int ymax = maxRect.bottom() + 1;
-
- const int cx(pos.x());
- const int cy(pos.y());
- const int cw(c->width());
- const int ch(c->height());
- const int rx(cx + cw);
- const int ry(cy + ch); //these don't change
-
- int nx(cx), ny(cy); //buffers
- int deltaX(xmax);
- int deltaY(ymax); //minimum distance to other clients
-
- int lx, ly, lrx, lry; //coords and size for the comparison client, l
-
- // border snap
- const int snapX = borderSnapZone.width() * snapAdjust; //snap trigger
- const int snapY = borderSnapZone.height() * snapAdjust;
- if (snapX || snapY) {
- QRect geo = c->frameGeometry();
- QMargins frameMargins = c->frameMargins();
-
- // snap to titlebar / snap to window borders on inner screen edges
- AbstractClient::Position titlePos = c->titlebarPosition();
- if (frameMargins.left() && (titlePos == AbstractClient::PositionLeft || (c->maximizeMode() & MaximizeHorizontal) ||
- screens()->intersecting(geo.translated(maxRect.x() - (frameMargins.left() + geo.x()), 0)) > 1)) {
- frameMargins.setLeft(0);
- }
- if (frameMargins.right() && (titlePos == AbstractClient::PositionRight || (c->maximizeMode() & MaximizeHorizontal) ||
- screens()->intersecting(geo.translated(maxRect.right() + frameMargins.right() - geo.right(), 0)) > 1)) {
- frameMargins.setRight(0);
- }
- if (frameMargins.top() && (titlePos == AbstractClient::PositionTop || (c->maximizeMode() & MaximizeVertical) ||
- screens()->intersecting(geo.translated(0, maxRect.y() - (frameMargins.top() + geo.y()))) > 1)) {
- frameMargins.setTop(0);
- }
- if (frameMargins.bottom() && (titlePos == AbstractClient::PositionBottom || (c->maximizeMode() & MaximizeVertical) ||
- screens()->intersecting(geo.translated(0, maxRect.bottom() + frameMargins.bottom() - geo.bottom())) > 1)) {
- frameMargins.setBottom(0);
- }
- if ((sOWO ? (cx < xmin) : true) && (qAbs(xmin - cx) < snapX)) {
- deltaX = xmin - cx;
- nx = xmin - frameMargins.left();
- }
- if ((sOWO ? (rx > xmax) : true) && (qAbs(rx - xmax) < snapX) && (qAbs(xmax - rx) < deltaX)) {
- deltaX = rx - xmax;
- nx = xmax - cw + frameMargins.right();
- }
-
- if ((sOWO ? (cy < ymin) : true) && (qAbs(ymin - cy) < snapY)) {
- deltaY = ymin - cy;
- ny = ymin - frameMargins.top();
- }
- if ((sOWO ? (ry > ymax) : true) && (qAbs(ry - ymax) < snapY) && (qAbs(ymax - ry) < deltaY)) {
- deltaY = ry - ymax;
- ny = ymax - ch + frameMargins.bottom();
- }
- }
-
- // windows snap
- int snap = options->windowSnapZone() * snapAdjust;
- if (snap) {
- for (auto l = m_allClients.constBegin(); l != m_allClients.constEnd(); ++l) {
- if ((*l) == c)
- continue;
- if ((*l)->isMinimized())
- continue; // is minimized
- if (!(*l)->isShown(false))
- continue;
- if (!((*l)->isOnDesktop(c->desktop()) || c->isOnDesktop((*l)->desktop())))
- continue; // wrong virtual desktop
- if (!(*l)->isOnCurrentActivity())
- continue; // wrong activity
- if ((*l)->isDesktop() || (*l)->isSplash())
- continue;
-
- lx = (*l)->x();
- ly = (*l)->y();
- lrx = lx + (*l)->width();
- lry = ly + (*l)->height();
-
- if (!(guideMaximized & MaximizeHorizontal) &&
- (((cy <= lry) && (cy >= ly)) || ((ry >= ly) && (ry <= lry)) || ((cy <= ly) && (ry >= lry)))) {
- if ((sOWO ? (cx < lrx) : true) && (qAbs(lrx - cx) < snap) && (qAbs(lrx - cx) < deltaX)) {
- deltaX = qAbs(lrx - cx);
- nx = lrx;
- }
- if ((sOWO ? (rx > lx) : true) && (qAbs(rx - lx) < snap) && (qAbs(rx - lx) < deltaX)) {
- deltaX = qAbs(rx - lx);
- nx = lx - cw;
- }
- }
-
- if (!(guideMaximized & MaximizeVertical) &&
- (((cx <= lrx) && (cx >= lx)) || ((rx >= lx) && (rx <= lrx)) || ((cx <= lx) && (rx >= lrx)))) {
- if ((sOWO ? (cy < lry) : true) && (qAbs(lry - cy) < snap) && (qAbs(lry - cy) < deltaY)) {
- deltaY = qAbs(lry - cy);
- ny = lry;
- }
- //if ( (qAbs( ry-ly ) < snap) && (qAbs( ry - ly ) < deltaY ))
- if ((sOWO ? (ry > ly) : true) && (qAbs(ry - ly) < snap) && (qAbs(ry - ly) < deltaY)) {
- deltaY = qAbs(ry - ly);
- ny = ly - ch;
- }
- }
-
- // Corner snapping
- if (!(guideMaximized & MaximizeVertical) && (nx == lrx || nx + cw == lx)) {
- if ((sOWO ? (ry > lry) : true) && (qAbs(lry - ry) < snap) && (qAbs(lry - ry) < deltaY)) {
- deltaY = qAbs(lry - ry);
- ny = lry - ch;
- }
- if ((sOWO ? (cy < ly) : true) && (qAbs(cy - ly) < snap) && (qAbs(cy - ly) < deltaY)) {
- deltaY = qAbs(cy - ly);
- ny = ly;
- }
- }
- if (!(guideMaximized & MaximizeHorizontal) && (ny == lry || ny + ch == ly)) {
- if ((sOWO ? (rx > lrx) : true) && (qAbs(lrx - rx) < snap) && (qAbs(lrx - rx) < deltaX)) {
- deltaX = qAbs(lrx - rx);
- nx = lrx - cw;
- }
- if ((sOWO ? (cx < lx) : true) && (qAbs(cx - lx) < snap) && (qAbs(cx - lx) < deltaX)) {
- deltaX = qAbs(cx - lx);
- nx = lx;
- }
- }
- }
- }
-
- // center snap
- snap = options->centerSnapZone() * snapAdjust; //snap trigger
- if (snap) {
- int diffX = qAbs((xmin + xmax) / 2 - (cx + cw / 2));
- int diffY = qAbs((ymin + ymax) / 2 - (cy + ch / 2));
- if (diffX < snap && diffY < snap && diffX < deltaX && diffY < deltaY) {
- // Snap to center of screen
- nx = (xmin + xmax) / 2 - cw / 2;
- ny = (ymin + ymax) / 2 - ch / 2;
- } else if (options->borderSnapZone()) {
- // Enhance border snap
- if ((nx == xmin || nx == xmax - cw) && diffY < snap && diffY < deltaY) {
- // Snap to vertical center on screen edge
- ny = (ymin + ymax) / 2 - ch / 2;
- } else if (((unrestricted ? ny == ymin : ny <= ymin) || ny == ymax - ch) &&
- diffX < snap && diffX < deltaX) {
- // Snap to horizontal center on screen edge
- nx = (xmin + xmax) / 2 - cw / 2;
- }
- }
- }
-
- pos = QPoint(nx, ny);
- }
- return pos;
-}
-
-QRect Workspace::adjustClientSize(AbstractClient* c, QRect moveResizeGeom, int mode)
-{
- //adapted from adjustClientPosition on 29May2004
- //this function is called when resizing a window and will modify
- //the new dimensions to snap to other windows/borders if appropriate
- if (options->windowSnapZone() || options->borderSnapZone()) { // || options->centerSnapZone )
- const bool sOWO = options->isSnapOnlyWhenOverlapping();
-
- const QRect maxRect = clientArea(MovementArea, c->rect().center(), c->desktop());
- const int xmin = maxRect.left();
- const int xmax = maxRect.right(); //desk size
- const int ymin = maxRect.top();
- const int ymax = maxRect.bottom();
-
- const int cx(moveResizeGeom.left());
- const int cy(moveResizeGeom.top());
- const int rx(moveResizeGeom.right());
- const int ry(moveResizeGeom.bottom());
-
- int newcx(cx), newcy(cy); //buffers
- int newrx(rx), newry(ry);
- int deltaX(xmax);
- int deltaY(ymax); //minimum distance to other clients
-
- int lx, ly, lrx, lry; //coords and size for the comparison client, l
-
- // border snap
- int snap = options->borderSnapZone(); //snap trigger
- if (snap) {
- deltaX = int(snap);
- deltaY = int(snap);
-
-#define SNAP_BORDER_TOP \
- if ((sOWO?(newcyymax):true) && (qAbs(ymax-newry)xmax):true) && (qAbs(xmax-newrx)windowSnapZone();
- if (snap) {
- deltaX = int(snap);
- deltaY = int(snap);
- for (auto l = m_allClients.constBegin(); l != m_allClients.constEnd(); ++l) {
- if ((*l)->isOnDesktop(VirtualDesktopManager::self()->current()) &&
- !(*l)->isMinimized()
- && (*l) != c) {
- lx = (*l)->x() - 1;
- ly = (*l)->y() - 1;
- lrx = (*l)->x() + (*l)->width();
- lry = (*l)->y() + (*l)->height();
-
-#define WITHIN_HEIGHT ((( newcy <= lry ) && ( newcy >= ly )) || \
- (( newry >= ly ) && ( newry <= lry )) || \
- (( newcy <= ly ) && ( newry >= lry )) )
-
-#define WITHIN_WIDTH ( (( cx <= lrx ) && ( cx >= lx )) || \
- (( rx >= lx ) && ( rx <= lrx )) || \
- (( cx <= lx ) && ( rx >= lrx )) )
-
-#define SNAP_WINDOW_TOP if ( (sOWO?(newcyly):true) \
- && WITHIN_WIDTH \
- && (qAbs( ly - newry ) < deltaY) ) { \
- deltaY = qAbs( ly - newry ); \
- newry=ly; \
-}
-
-#define SNAP_WINDOW_LEFT if ( (sOWO?(newcxlx):true) \
- && WITHIN_HEIGHT \
- && (qAbs( lx - newrx ) < deltaX)) \
-{ \
- deltaX = qAbs( lx - newrx ); \
- newrx=lx; \
-}
-
-#define SNAP_WINDOW_C_TOP if ( (sOWO?(newcylry):true) \
- && (newcx == lrx || newrx == lx) \
- && qAbs(lry-newry) < deltaY ) { \
- deltaY = qAbs( lry - newry - 1 ); \
- newry = lry - 1; \
-}
-
-#define SNAP_WINDOW_C_LEFT if ( (sOWO?(newcxlrx):true) \
- && (newcy == lry || newry == ly) \
- && qAbs(lrx-newrx) < deltaX ) { \
- deltaX = qAbs( lrx - newrx - 1 ); \
- newrx = lrx - 1; \
-}
-
- switch(mode) {
- case AbstractClient::PositionBottomRight:
- SNAP_WINDOW_BOTTOM
- SNAP_WINDOW_RIGHT
- SNAP_WINDOW_C_BOTTOM
- SNAP_WINDOW_C_RIGHT
- break;
- case AbstractClient::PositionRight:
- SNAP_WINDOW_RIGHT
- SNAP_WINDOW_C_RIGHT
- break;
- case AbstractClient::PositionBottom:
- SNAP_WINDOW_BOTTOM
- SNAP_WINDOW_C_BOTTOM
- break;
- case AbstractClient::PositionTopLeft:
- SNAP_WINDOW_TOP
- SNAP_WINDOW_LEFT
- SNAP_WINDOW_C_TOP
- SNAP_WINDOW_C_LEFT
- break;
- case AbstractClient::PositionLeft:
- SNAP_WINDOW_LEFT
- SNAP_WINDOW_C_LEFT
- break;
- case AbstractClient::PositionTop:
- SNAP_WINDOW_TOP
- SNAP_WINDOW_C_TOP
- break;
- case AbstractClient::PositionTopRight:
- SNAP_WINDOW_TOP
- SNAP_WINDOW_RIGHT
- SNAP_WINDOW_C_TOP
- SNAP_WINDOW_C_RIGHT
- break;
- case AbstractClient::PositionBottomLeft:
- SNAP_WINDOW_BOTTOM
- SNAP_WINDOW_LEFT
- SNAP_WINDOW_C_BOTTOM
- SNAP_WINDOW_C_LEFT
- break;
- default:
- abort();
- break;
- }
- }
- }
- }
-
- // center snap
- //snap = options->centerSnapZone;
- //if (snap)
- // {
- // // Don't resize snap to center as it interferes too much
- // // There are two ways of implementing this if wanted:
- // // 1) Snap only to the same points that the move snap does, and
- // // 2) Snap to the horizontal and vertical center lines of the screen
- // }
-
- moveResizeGeom = QRect(QPoint(newcx, newcy), QPoint(newrx, newry));
- }
- return moveResizeGeom;
-}
-
-/**
- * Marks the client as being moved or resized by the user.
- */
-void Workspace::setMoveResizeClient(AbstractClient *c)
-{
- Q_ASSERT(!c || !movingClient); // Catch attempts to move a second
- // window while still moving the first one.
- movingClient = c;
- if (movingClient)
- ++block_focus;
- else
- --block_focus;
-}
-
-// When kwin crashes, windows will not be gravitated back to their original position
-// and will remain offset by the size of the decoration. So when restarting, fix this
-// (the property with the size of the frame remains on the window after the crash).
-void Workspace::fixPositionAfterCrash(xcb_window_t w, const xcb_get_geometry_reply_t *geometry)
-{
- NETWinInfo i(connection(), w, rootWindow(), NET::WMFrameExtents, NET::Properties2());
- NETStrut frame = i.frameExtents();
-
- if (frame.left != 0 || frame.top != 0) {
- // left and top needed due to narrowing conversations restrictions in C++11
- const uint32_t left = frame.left;
- const uint32_t top = frame.top;
- const uint32_t values[] = { geometry->x - left, geometry->y - top };
- xcb_configure_window(connection(), w, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, values);
- }
-}
-
-//********************************************
-// Client
-//********************************************
-
-/**
- * Returns \a area with the client's strut taken into account.
- *
- * Used from Workspace in updateClientArea.
- */
-// TODO move to Workspace?
-
-QRect X11Client::adjustedClientArea(const QRect &desktopArea, const QRect& area) const
-{
- QRect r = area;
- NETExtendedStrut str = strut();
- QRect stareaL = QRect(
- 0,
- str . left_start,
- str . left_width,
- str . left_end - str . left_start + 1);
- QRect stareaR = QRect(
- desktopArea . right() - str . right_width + 1,
- str . right_start,
- str . right_width,
- str . right_end - str . right_start + 1);
- QRect stareaT = QRect(
- str . top_start,
- 0,
- str . top_end - str . top_start + 1,
- str . top_width);
- QRect stareaB = QRect(
- str . bottom_start,
- desktopArea . bottom() - str . bottom_width + 1,
- str . bottom_end - str . bottom_start + 1,
- str . bottom_width);
-
- QRect screenarea = workspace()->clientArea(ScreenArea, this);
- // HACK: workarea handling is not xinerama aware, so if this strut
- // reserves place at a xinerama edge that's inside the virtual screen,
- // ignore the strut for workspace setting.
- if (area == QRect(QPoint(0, 0), screens()->displaySize())) {
- if (stareaL.left() < screenarea.left())
- stareaL = QRect();
- if (stareaR.right() > screenarea.right())
- stareaR = QRect();
- if (stareaT.top() < screenarea.top())
- stareaT = QRect();
- if (stareaB.bottom() < screenarea.bottom())
- stareaB = QRect();
- }
- // Handle struts at xinerama edges that are inside the virtual screen.
- // They're given in virtual screen coordinates, make them affect only
- // their xinerama screen.
- stareaL.setLeft(qMax(stareaL.left(), screenarea.left()));
- stareaR.setRight(qMin(stareaR.right(), screenarea.right()));
- stareaT.setTop(qMax(stareaT.top(), screenarea.top()));
- stareaB.setBottom(qMin(stareaB.bottom(), screenarea.bottom()));
-
- if (stareaL . intersects(area)) {
-// qDebug() << "Moving left of: " << r << " to " << stareaL.right() + 1;
- r . setLeft(stareaL . right() + 1);
- }
- if (stareaR . intersects(area)) {
-// qDebug() << "Moving right of: " << r << " to " << stareaR.left() - 1;
- r . setRight(stareaR . left() - 1);
- }
- if (stareaT . intersects(area)) {
-// qDebug() << "Moving top of: " << r << " to " << stareaT.bottom() + 1;
- r . setTop(stareaT . bottom() + 1);
- }
- if (stareaB . intersects(area)) {
-// qDebug() << "Moving bottom of: " << r << " to " << stareaB.top() - 1;
- r . setBottom(stareaB . top() - 1);
- }
-
- return r;
-}
-
-NETExtendedStrut X11Client::strut() const
-{
- NETExtendedStrut ext = info->extendedStrut();
- NETStrut str = info->strut();
- const QSize displaySize = screens()->displaySize();
- if (ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0
- && (str.left != 0 || str.right != 0 || str.top != 0 || str.bottom != 0)) {
- // build extended from simple
- if (str.left != 0) {
- ext.left_width = str.left;
- ext.left_start = 0;
- ext.left_end = displaySize.height();
- }
- if (str.right != 0) {
- ext.right_width = str.right;
- ext.right_start = 0;
- ext.right_end = displaySize.height();
- }
- if (str.top != 0) {
- ext.top_width = str.top;
- ext.top_start = 0;
- ext.top_end = displaySize.width();
- }
- if (str.bottom != 0) {
- ext.bottom_width = str.bottom;
- ext.bottom_start = 0;
- ext.bottom_end = displaySize.width();
- }
- }
- return ext;
-}
-
-StrutRect X11Client::strutRect(StrutArea area) const
-{
- Q_ASSERT(area != StrutAreaAll); // Not valid
- const QSize displaySize = screens()->displaySize();
- NETExtendedStrut strutArea = strut();
- switch(area) {
- case StrutAreaTop:
- if (strutArea.top_width != 0)
- return StrutRect(QRect(
- strutArea.top_start, 0,
- strutArea.top_end - strutArea.top_start, strutArea.top_width
- ), StrutAreaTop);
- break;
- case StrutAreaRight:
- if (strutArea.right_width != 0)
- return StrutRect(QRect(
- displaySize.width() - strutArea.right_width, strutArea.right_start,
- strutArea.right_width, strutArea.right_end - strutArea.right_start
- ), StrutAreaRight);
- break;
- case StrutAreaBottom:
- if (strutArea.bottom_width != 0)
- return StrutRect(QRect(
- strutArea.bottom_start, displaySize.height() - strutArea.bottom_width,
- strutArea.bottom_end - strutArea.bottom_start, strutArea.bottom_width
- ), StrutAreaBottom);
- break;
- case StrutAreaLeft:
- if (strutArea.left_width != 0)
- return StrutRect(QRect(
- 0, strutArea.left_start,
- strutArea.left_width, strutArea.left_end - strutArea.left_start
- ), StrutAreaLeft);
- break;
- default:
- abort(); // Not valid
- }
- return StrutRect(); // Null rect
-}
-
-StrutRects X11Client::strutRects() const
-{
- StrutRects region;
- region += strutRect(StrutAreaTop);
- region += strutRect(StrutAreaRight);
- region += strutRect(StrutAreaBottom);
- region += strutRect(StrutAreaLeft);
- return region;
-}
-
-bool X11Client::hasStrut() const
-{
- NETExtendedStrut ext = strut();
- if (ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0)
- return false;
- return true;
-}
-
-bool X11Client::hasOffscreenXineramaStrut() const
-{
- // Get strut as a QRegion
- QRegion region;
- region += strutRect(StrutAreaTop);
- region += strutRect(StrutAreaRight);
- region += strutRect(StrutAreaBottom);
- region += strutRect(StrutAreaLeft);
-
- // Remove all visible areas so that only the invisible remain
- for (int i = 0; i < screens()->count(); i ++)
- region -= screens()->geometry(i);
-
- // If there's anything left then we have an offscreen strut
- return !region.isEmpty();
-}
-
-void AbstractClient::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 = frameGeometry();
- if( oldDesktop == -2 )
- oldDesktop = desktop();
- if (!oldClientGeometry.isValid())
- oldClientGeometry = oldGeometry.adjusted(border[Left], border[Top], -border[Right], -border[Bottom]);
- if (isDesktop())
- return;
- if (isFullScreen()) {
- QRect area = workspace()->clientArea(FullScreenArea, this);
- if (frameGeometry() != area)
- setFrameGeometry(area);
- return;
- }
- if (isDock())
- return;
-
- if (maximizeMode() != MaximizeRestore) {
- // TODO update geom_restore?
- changeMaximize(false, false, true); // adjust size
- const QRect screenArea = workspace()->clientArea(ScreenArea, this);
- QRect geom = frameGeometry();
- checkOffscreenPosition(&geom, screenArea);
- setFrameGeometry(geom);
- return;
- }
-
- if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) {
- setFrameGeometry(electricBorderMaximizeGeometry(frameGeometry().center(), desktop()));
- return;
- }
-
- // this can be true only if this window was mapped before KWin
- // was started - in such case, don't adjust position to workarea,
- // because the window already had its position, and if a window
- // with a strut altering the workarea would be managed in initialization
- // after this one, this window would be moved
- if (!workspace() || workspace()->initializing())
- return;
-
- // If the window was touching an edge before but not now move it so it is again.
- // Old and new maximums have different starting values so windows on the screen
- // edge will move when a new strut is placed on the edge.
- QRect oldScreenArea;
- if( workspace()->inUpdateClientArea()) {
- // we need to find the screen area as it was before the change
- oldScreenArea = QRect( 0, 0, workspace()->oldDisplayWidth(), workspace()->oldDisplayHeight());
- int distance = INT_MAX;
- foreach(const QRect &r, workspace()->previousScreenSizes()) {
- int d = r.contains( oldGeometry.center()) ? 0 : ( r.center() - oldGeometry.center()).manhattanLength();
- if( d < distance ) {
- distance = d;
- oldScreenArea = r;
- }
- }
- } else {
- oldScreenArea = workspace()->clientArea(ScreenArea, oldGeometry.center(), oldDesktop);
- }
- const QRect oldGeomTall = QRect(oldGeometry.x(), oldScreenArea.y(), oldGeometry.width(), oldScreenArea.height()); // Full screen height
- const QRect oldGeomWide = QRect(oldScreenArea.x(), oldGeometry.y(), oldScreenArea.width(), oldGeometry.height()); // Full screen width
- int oldTopMax = oldScreenArea.y();
- int oldRightMax = oldScreenArea.x() + oldScreenArea.width();
- int oldBottomMax = oldScreenArea.y() + oldScreenArea.height();
- int oldLeftMax = oldScreenArea.x();
- const QRect screenArea = workspace()->clientArea(ScreenArea, geometryRestore().center(), desktop());
- int topMax = screenArea.y();
- int rightMax = screenArea.x() + screenArea.width();
- int bottomMax = screenArea.y() + screenArea.height();
- int leftMax = screenArea.x();
- QRect newGeom = geometryRestore(); // geometry();
- QRect newClientGeom = newGeom.adjusted(border[Left], border[Top], -border[Right], -border[Bottom]);
- const QRect newGeomTall = QRect(newGeom.x(), screenArea.y(), newGeom.width(), screenArea.height()); // Full screen height
- const QRect newGeomWide = QRect(screenArea.x(), newGeom.y(), screenArea.width(), 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).
-
- // 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
-
- for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaTop)) {
- QRect rect = r & oldGeomTall;
- if (!rect.isEmpty())
- oldTopMax = qMax(oldTopMax, rect.y() + rect.height());
- }
- for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaRight)) {
- QRect rect = r & oldGeomWide;
- if (!rect.isEmpty())
- oldRightMax = qMin(oldRightMax, rect.x());
- }
- for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaBottom)) {
- QRect rect = r & oldGeomTall;
- if (!rect.isEmpty())
- oldBottomMax = qMin(oldBottomMax, rect.y());
- }
- for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaLeft)) {
- QRect rect = r & oldGeomWide;
- if (!rect.isEmpty())
- oldLeftMax = qMax(oldLeftMax, rect.x() + rect.width());
- }
-
- // These 4 compute new bounds
- for (const QRect &r : workspace()->restrictedMoveArea(desktop(), StrutAreaTop)) {
- QRect rect = r & newGeomTall;
- if (!rect.isEmpty())
- topMax = qMax(topMax, rect.y() + rect.height());
- }
- for (const QRect &r : workspace()->restrictedMoveArea(desktop(), StrutAreaRight)) {
- QRect rect = r & newGeomWide;
- if (!rect.isEmpty())
- rightMax = qMin(rightMax, rect.x());
- }
- for (const QRect &r : workspace()->restrictedMoveArea(desktop(), StrutAreaBottom)) {
- QRect rect = r & newGeomTall;
- if (!rect.isEmpty())
- bottomMax = qMin(bottomMax, rect.y());
- }
- for (const QRect &r : workspace()->restrictedMoveArea(desktop(), StrutAreaLeft)) {
- QRect rect = r & newGeomWide;
- if (!rect.isEmpty())
- leftMax = qMax(leftMax, rect.x() + rect.width());
- }
-
-
- // Check if the sides were inside or touching but are no longer
- 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() >= 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.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.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);
- // Obey size hints. TODO: We really should make sure it stays in the right place
- if (!isShade())
- newGeom.setSize(adjustedSize(newGeom.size()));
-
- if (newGeom != frameGeometry())
- setFrameGeometry(newGeom);
-}
-
-void AbstractClient::checkOffscreenPosition(QRect* geom, const QRect& screenArea)
-{
- if (geom->left() > screenArea.right()) {
- geom->moveLeft(screenArea.right() - screenArea.width()/4);
- } else if (geom->right() < screenArea.left()) {
- geom->moveRight(screenArea.left() + screenArea.width()/4);
- }
- if (geom->top() > screenArea.bottom()) {
- geom->moveTop(screenArea.bottom() - screenArea.height()/4);
- } else if (geom->bottom() < screenArea.top()) {
- geom->moveBottom(screenArea.top() + screenArea.width()/4);
- }
-}
-
-QSize AbstractClient::adjustedSize(const QSize& frame, Sizemode mode) const
-{
- // first, get the window size for the given frame size s
- QSize wsize = frameSizeToClientSize(frame);
- if (wsize.isEmpty())
- wsize = QSize(qMax(wsize.width(), 1), qMax(wsize.height(), 1));
-
- return sizeForClientSize(wsize, mode, false);
-}
-
-// this helper returns proper size even if the window is shaded
-// see also the comment in X11Client::setGeometry()
-QSize AbstractClient::adjustedSize() const
-{
- return sizeForClientSize(clientSize());
-}
-
-/**
- * Calculate the appropriate frame size for the given client size \a
- * wsize.
- *
- * \a wsize is adapted according to the window's size hints (minimum,
- * maximum and incremental size changes).
- */
-QSize X11Client::sizeForClientSize(const QSize& wsize, Sizemode mode, bool noframe) const
-{
- int w = wsize.width();
- int h = wsize.height();
- if (w < 1 || h < 1) {
- qCWarning(KWIN_CORE) << "sizeForClientSize() with empty size!" ;
- }
- if (w < 1) w = 1;
- if (h < 1) h = 1;
-
- // basesize, minsize, maxsize, paspect and resizeinc have all values defined,
- // even if they're not set in flags - see getWmNormalHints()
- QSize min_size = minSize();
- QSize max_size = maxSize();
- if (isDecorated()) {
- QSize decominsize(0, 0);
- QSize border_size(borderLeft() + borderRight(), borderTop() + borderBottom());
- if (border_size.width() > decominsize.width()) // just in case
- decominsize.setWidth(border_size.width());
- if (border_size.height() > decominsize.height())
- decominsize.setHeight(border_size.height());
- if (decominsize.width() > min_size.width())
- min_size.setWidth(decominsize.width());
- if (decominsize.height() > min_size.height())
- min_size.setHeight(decominsize.height());
- }
- w = qMin(max_size.width(), w);
- h = qMin(max_size.height(), h);
- w = qMax(min_size.width(), w);
- h = qMax(min_size.height(), h);
-
- int w1 = w;
- int h1 = h;
- int width_inc = m_geometryHints.resizeIncrements().width();
- int height_inc = m_geometryHints.resizeIncrements().height();
- int basew_inc = m_geometryHints.baseSize().width();
- int baseh_inc = m_geometryHints.baseSize().height();
- if (!m_geometryHints.hasBaseSize()) {
- basew_inc = m_geometryHints.minSize().width();
- baseh_inc = m_geometryHints.minSize().height();
- }
- w = int((w - basew_inc) / width_inc) * width_inc + basew_inc;
- h = int((h - baseh_inc) / height_inc) * height_inc + baseh_inc;
-// code for aspect ratios based on code from FVWM
- /*
- * The math looks like this:
- *
- * minAspectX dwidth maxAspectX
- * ---------- <= ------- <= ----------
- * minAspectY dheight maxAspectY
- *
- * If that is multiplied out, then the width and height are
- * invalid in the following situations:
- *
- * minAspectX * dheight > minAspectY * dwidth
- * maxAspectX * dheight < maxAspectY * dwidth
- *
- */
- if (m_geometryHints.hasAspect()) {
- double min_aspect_w = m_geometryHints.minAspect().width(); // use doubles, because the values can be MAX_INT
- double min_aspect_h = m_geometryHints.minAspect().height(); // and multiplying would go wrong otherwise
- double max_aspect_w = m_geometryHints.maxAspect().width();
- double max_aspect_h = m_geometryHints.maxAspect().height();
- // According to ICCCM 4.1.2.3 PMinSize should be a fallback for PBaseSize for size increments,
- // but not for aspect ratio. Since this code comes from FVWM, handles both at the same time,
- // and I have no idea how it works, let's hope nobody relies on that.
- const QSize baseSize = m_geometryHints.baseSize();
- w -= baseSize.width();
- h -= baseSize.height();
- int max_width = max_size.width() - baseSize.width();
- int min_width = min_size.width() - baseSize.width();
- int max_height = max_size.height() - baseSize.height();
- int min_height = min_size.height() - baseSize.height();
-#define ASPECT_CHECK_GROW_W \
- if ( min_aspect_w * h > min_aspect_h * w ) \
- { \
- int delta = int( min_aspect_w * h / min_aspect_h - w ) / width_inc * width_inc; \
- if ( w + delta <= max_width ) \
- w += delta; \
- }
-#define ASPECT_CHECK_SHRINK_H_GROW_W \
- if ( min_aspect_w * h > min_aspect_h * w ) \
- { \
- int delta = int( h - w * min_aspect_h / min_aspect_w ) / height_inc * height_inc; \
- if ( h - delta >= min_height ) \
- h -= delta; \
- else \
- { \
- int delta = int( min_aspect_w * h / min_aspect_h - w ) / width_inc * width_inc; \
- if ( w + delta <= max_width ) \
- w += delta; \
- } \
- }
-#define ASPECT_CHECK_GROW_H \
- if ( max_aspect_w * h < max_aspect_h * w ) \
- { \
- int delta = int( w * max_aspect_h / max_aspect_w - h ) / height_inc * height_inc; \
- if ( h + delta <= max_height ) \
- h += delta; \
- }
-#define ASPECT_CHECK_SHRINK_W_GROW_H \
- if ( max_aspect_w * h < max_aspect_h * w ) \
- { \
- int delta = int( w - max_aspect_w * h / max_aspect_h ) / width_inc * width_inc; \
- if ( w - delta >= min_width ) \
- w -= delta; \
- else \
- { \
- int delta = int( w * max_aspect_h / max_aspect_w - h ) / height_inc * height_inc; \
- if ( h + delta <= max_height ) \
- h += delta; \
- } \
- }
- switch(mode) {
- case SizemodeAny:
-#if 0 // make SizemodeAny equal to SizemodeFixedW - prefer keeping fixed width,
- // so that changing aspect ratio to a different value and back keeps the same size (#87298)
- {
- ASPECT_CHECK_SHRINK_H_GROW_W
- ASPECT_CHECK_SHRINK_W_GROW_H
- ASPECT_CHECK_GROW_H
- ASPECT_CHECK_GROW_W
- break;
- }
-#endif
- case SizemodeFixedW: {
- // the checks are order so that attempts to modify height are first
- ASPECT_CHECK_GROW_H
- ASPECT_CHECK_SHRINK_H_GROW_W
- ASPECT_CHECK_SHRINK_W_GROW_H
- ASPECT_CHECK_GROW_W
- break;
- }
- case SizemodeFixedH: {
- ASPECT_CHECK_GROW_W
- ASPECT_CHECK_SHRINK_W_GROW_H
- ASPECT_CHECK_SHRINK_H_GROW_W
- ASPECT_CHECK_GROW_H
- break;
- }
- case SizemodeMax: {
- // first checks that try to shrink
- ASPECT_CHECK_SHRINK_H_GROW_W
- ASPECT_CHECK_SHRINK_W_GROW_H
- ASPECT_CHECK_GROW_W
- ASPECT_CHECK_GROW_H
- break;
- }
- }
-#undef ASPECT_CHECK_SHRINK_H_GROW_W
-#undef ASPECT_CHECK_SHRINK_W_GROW_H
-#undef ASPECT_CHECK_GROW_W
-#undef ASPECT_CHECK_GROW_H
- w += baseSize.width();
- h += baseSize.height();
- }
- if (!rules()->checkStrictGeometry(!isFullScreen())) {
- // disobey increments and aspect by explicit rule
- w = w1;
- h = h1;
- }
-
- QSize size(w, h);
- if (!noframe) {
- size = clientSizeToFrameSize(size);
- }
- return rules()->checkSize(size);
-}
-
-/**
- * Gets the client's normal WM hints and reconfigures itself respectively.
- */
-void X11Client::getWmNormalHints()
-{
- const bool hadFixedAspect = m_geometryHints.hasAspect();
- // roundtrip to X server
- m_geometryHints.fetch();
- m_geometryHints.read();
-
- if (!hadFixedAspect && m_geometryHints.hasAspect()) {
- // align to eventual new contraints
- maximize(max_mode);
- }
- if (isManaged()) {
- // update to match restrictions
- QSize new_size = adjustedSize();
- if (new_size != size() && !isFullScreen()) {
- QRect origClientGeometry = m_clientGeometry;
- resizeWithChecks(new_size);
- if ((!isSpecialWindow() || isToolbar()) && !isFullScreen()) {
- // try to keep the window in its xinerama screen if possible,
- // if that fails at least keep it visible somewhere
- QRect area = workspace()->clientArea(MovementArea, this);
- if (area.contains(origClientGeometry))
- keepInArea(area);
- area = workspace()->clientArea(WorkArea, this);
- if (area.contains(origClientGeometry))
- keepInArea(area);
- }
- }
- }
- updateAllowedActions(); // affects isResizeable()
-}
-
-QSize X11Client::minSize() const
-{
- return rules()->checkMinSize(m_geometryHints.minSize());
-}
-
-QSize X11Client::maxSize() const
-{
- return rules()->checkMaxSize(m_geometryHints.maxSize());
-}
-
-QSize X11Client::basicUnit() const
-{
- return m_geometryHints.resizeIncrements();
-}
-
-/**
- * Auxiliary function to inform the client about the current window
- * configuration.
- */
-void X11Client::sendSyntheticConfigureNotify()
-{
- xcb_configure_notify_event_t c;
- memset(&c, 0, sizeof(c));
- c.response_type = XCB_CONFIGURE_NOTIFY;
- c.event = window();
- c.window = window();
- c.x = m_clientGeometry.x();
- c.y = m_clientGeometry.y();
- c.width = m_clientGeometry.width();
- c.height = m_clientGeometry.height();
- c.border_width = 0;
- c.above_sibling = XCB_WINDOW_NONE;
- c.override_redirect = 0;
- xcb_send_event(connection(), true, c.event, XCB_EVENT_MASK_STRUCTURE_NOTIFY, reinterpret_cast(&c));
- xcb_flush(connection());
-}
-
-QPoint X11Client::gravityAdjustment(xcb_gravity_t gravity) const
-{
- int dx = 0;
- int dy = 0;
-
- // dx, dy specify how the client window moves to make space for the frame.
- // In general we have to compute the reference point and from that figure
- // out how much we need to shift the client, however given that we ignore
- // the border width attribute and the extents of the server-side decoration
- // are known in advance, we can simplify the math quite a bit and express
- // the required window gravity adjustment in terms of border sizes.
- switch(gravity) {
- case XCB_GRAVITY_NORTH_WEST: // move down right
- default:
- dx = borderLeft();
- dy = borderTop();
- break;
- case XCB_GRAVITY_NORTH: // move right
- dx = 0;
- dy = borderTop();
- break;
- case XCB_GRAVITY_NORTH_EAST: // move down left
- dx = -borderRight();
- dy = borderTop();
- break;
- case XCB_GRAVITY_WEST: // move right
- dx = borderLeft();
- dy = 0;
- break;
- case XCB_GRAVITY_CENTER:
- dx = (borderLeft() - borderRight()) / 2;
- dy = (borderTop() - borderBottom()) / 2;
- break;
- case XCB_GRAVITY_STATIC: // don't move
- dx = 0;
- dy = 0;
- break;
- case XCB_GRAVITY_EAST: // move left
- dx = -borderRight();
- dy = 0;
- break;
- case XCB_GRAVITY_SOUTH_WEST: // move up right
- dx = borderLeft() ;
- dy = -borderBottom();
- break;
- case XCB_GRAVITY_SOUTH: // move up
- dx = 0;
- dy = -borderBottom();
- break;
- case XCB_GRAVITY_SOUTH_EAST: // move up left
- dx = -borderRight();
- dy = -borderBottom();
- break;
- }
-
- return QPoint(dx, dy);
-}
-
-const QPoint X11Client::calculateGravitation(bool invert) const
-{
- const QPoint adjustment = gravityAdjustment(m_geometryHints.windowGravity());
-
- // translate from client movement to frame movement
- const int dx = adjustment.x() - borderLeft();
- const int dy = adjustment.y() - borderTop();
-
- if (!invert)
- return QPoint(x() + dx, y() + dy);
- else
- return QPoint(x() - dx, y() - dy);
-}
-
-void X11Client::configureRequest(int value_mask, int rx, int ry, int rw, int rh, int gravity, bool from_tool)
-{
- const int configurePositionMask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y;
- const int configureSizeMask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
- const int configureGeometryMask = configurePositionMask | configureSizeMask;
-
- // "maximized" is a user setting -> we do not allow the client to resize itself
- // away from this & against the users explicit wish
- qCDebug(KWIN_CORE) << this << bool(value_mask & configureGeometryMask) <<
- bool(maximizeMode() & MaximizeVertical) <<
- bool(maximizeMode() & MaximizeHorizontal);
-
- // we want to (partially) ignore the request when the window is somehow maximized or quicktiled
- bool ignore = !app_noborder && (quickTileMode() != QuickTileMode(QuickTileFlag::None) || maximizeMode() != MaximizeRestore);
- // however, the user shall be able to force obedience despite and also disobedience in general
- ignore = rules()->checkIgnoreGeometry(ignore);
- if (!ignore) { // either we're not max'd / q'tiled or the user allowed the client to break that - so break it.
- updateQuickTileMode(QuickTileFlag::None);
- max_mode = MaximizeRestore;
- emit quickTileModeChanged();
- } else if (!app_noborder && quickTileMode() == QuickTileMode(QuickTileFlag::None) &&
- (maximizeMode() == MaximizeVertical || maximizeMode() == MaximizeHorizontal)) {
- // ignoring can be, because either we do, or the user does explicitly not want it.
- // for partially maximized windows we want to allow configures in the other dimension.
- // so we've to ask the user again - to know whether we just ignored for the partial maximization.
- // the problem here is, that the user can explicitly permit configure requests - even for maximized windows!
- // we cannot distinguish that from passing "false" for partially maximized windows.
- ignore = rules()->checkIgnoreGeometry(false);
- if (!ignore) { // the user is not interested, so we fix up dimensions
- if (maximizeMode() == MaximizeVertical)
- value_mask &= ~(XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_HEIGHT);
- if (maximizeMode() == MaximizeHorizontal)
- value_mask &= ~(XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_WIDTH);
- if (!(value_mask & configureGeometryMask)) {
- ignore = true; // the modification turned the request void
- }
- }
- }
-
- if (ignore) {
- qCDebug(KWIN_CORE) << "DENIED";
- return; // nothing to (left) to do for use - bugs #158974, #252314, #321491
- }
-
- qCDebug(KWIN_CORE) << "PERMITTED" << this << bool(value_mask & configureGeometryMask);
-
- if (gravity == 0) // default (nonsense) value for the argument
- gravity = m_geometryHints.windowGravity();
- if (value_mask & configurePositionMask) {
- QPoint new_pos = framePosToClientPos(pos());
- new_pos -= gravityAdjustment(xcb_gravity_t(gravity));
- if (value_mask & XCB_CONFIG_WINDOW_X) {
- new_pos.setX(rx);
- }
- if (value_mask & XCB_CONFIG_WINDOW_Y) {
- new_pos.setY(ry);
- }
- // clever(?) workaround for applications like xv that want to set
- // the location to the current location but miscalculate the
- // frame size due to kwin being a double-reparenting window
- // manager
- if (new_pos.x() == m_clientGeometry.x() && new_pos.y() == m_clientGeometry.y()
- && gravity == XCB_GRAVITY_NORTH_WEST && !from_tool) {
- new_pos.setX(x());
- new_pos.setY(y());
- }
- new_pos += gravityAdjustment(xcb_gravity_t(gravity));
- new_pos = clientPosToFramePos(new_pos);
-
- int nw = clientSize().width();
- int nh = clientSize().height();
- if (value_mask & XCB_CONFIG_WINDOW_WIDTH) {
- nw = rw;
- }
- if (value_mask & XCB_CONFIG_WINDOW_HEIGHT) {
- nh = rh;
- }
- QSize ns = sizeForClientSize(QSize(nw, nh)); // enforces size if needed
- new_pos = rules()->checkPosition(new_pos);
- int newScreen = screens()->number(QRect(new_pos, ns).center());
- if (newScreen != rules()->checkScreen(newScreen))
- return; // not allowed by rule
-
- QRect origClientGeometry = m_clientGeometry;
- GeometryUpdatesBlocker blocker(this);
- move(new_pos);
- plainResize(ns);
- QRect area = workspace()->clientArea(WorkArea, this);
- if (!from_tool && (!isSpecialWindow() || isToolbar()) && !isFullScreen()
- && area.contains(origClientGeometry))
- keepInArea(area);
-
- // this is part of the kicker-xinerama-hack... it should be
- // safe to remove when kicker gets proper ExtendedStrut support;
- // see Workspace::updateClientArea() and
- // X11Client::adjustedClientArea()
- if (hasStrut())
- workspace() -> updateClientArea();
- }
-
- if (value_mask & configureSizeMask && !(value_mask & configurePositionMask)) { // pure resize
- int nw = clientSize().width();
- int nh = clientSize().height();
- if (value_mask & XCB_CONFIG_WINDOW_WIDTH) {
- nw = rw;
- }
- if (value_mask & XCB_CONFIG_WINDOW_HEIGHT) {
- nh = rh;
- }
- QSize ns = sizeForClientSize(QSize(nw, nh));
-
- if (ns != size()) { // don't restore if some app sets its own size again
- QRect origClientGeometry = m_clientGeometry;
- GeometryUpdatesBlocker blocker(this);
- resizeWithChecks(ns, xcb_gravity_t(gravity));
- if (!from_tool && (!isSpecialWindow() || isToolbar()) && !isFullScreen()) {
- // try to keep the window in its xinerama screen if possible,
- // if that fails at least keep it visible somewhere
- QRect area = workspace()->clientArea(MovementArea, this);
- if (area.contains(origClientGeometry))
- keepInArea(area);
- area = workspace()->clientArea(WorkArea, this);
- if (area.contains(origClientGeometry))
- keepInArea(area);
- }
- }
- }
- geom_restore = frameGeometry();
- // No need to send synthetic configure notify event here, either it's sent together
- // with geometry change, or there's no need to send it.
- // Handling of the real ConfigureRequest event forces sending it, as there it's necessary.
-}
-
-void X11Client::resizeWithChecks(int w, int h, xcb_gravity_t gravity, ForceGeometry_t force)
-{
- Q_ASSERT(!shade_geometry_change);
- if (isShade()) {
- if (h == borderTop() + borderBottom()) {
- qCWarning(KWIN_CORE) << "Shaded geometry passed for size:" ;
- }
- }
- int newx = x();
- int newy = y();
- QRect area = workspace()->clientArea(WorkArea, this);
- // don't allow growing larger than workarea
- if (w > area.width())
- w = area.width();
- if (h > area.height())
- h = area.height();
- QSize tmp = adjustedSize(QSize(w, h)); // checks size constraints, including min/max size
- w = tmp.width();
- h = tmp.height();
- if (gravity == 0) {
- gravity = m_geometryHints.windowGravity();
- }
- switch(gravity) {
- case XCB_GRAVITY_NORTH_WEST: // top left corner doesn't move
- default:
- break;
- case XCB_GRAVITY_NORTH: // middle of top border doesn't move
- newx = (newx + width() / 2) - (w / 2);
- break;
- case XCB_GRAVITY_NORTH_EAST: // top right corner doesn't move
- newx = newx + width() - w;
- break;
- case XCB_GRAVITY_WEST: // middle of left border doesn't move
- newy = (newy + height() / 2) - (h / 2);
- break;
- case XCB_GRAVITY_CENTER: // middle point doesn't move
- newx = (newx + width() / 2) - (w / 2);
- newy = (newy + height() / 2) - (h / 2);
- break;
- case XCB_GRAVITY_STATIC: // top left corner of _client_ window doesn't move
- // since decoration doesn't change, equal to NorthWestGravity
- break;
- case XCB_GRAVITY_EAST: // // middle of right border doesn't move
- newx = newx + width() - w;
- newy = (newy + height() / 2) - (h / 2);
- break;
- case XCB_GRAVITY_SOUTH_WEST: // bottom left corner doesn't move
- newy = newy + height() - h;
- break;
- case XCB_GRAVITY_SOUTH: // middle of bottom border doesn't move
- newx = (newx + width() / 2) - (w / 2);
- newy = newy + height() - h;
- break;
- case XCB_GRAVITY_SOUTH_EAST: // bottom right corner doesn't move
- newx = newx + width() - w;
- newy = newy + height() - h;
- break;
- }
- setFrameGeometry(newx, newy, w, h, force);
-}
-
-// _NET_MOVERESIZE_WINDOW
-void X11Client::NETMoveResizeWindow(int flags, int x, int y, int width, int height)
-{
- int gravity = flags & 0xff;
- int value_mask = 0;
- if (flags & (1 << 8)) {
- value_mask |= XCB_CONFIG_WINDOW_X;
- }
- if (flags & (1 << 9)) {
- value_mask |= XCB_CONFIG_WINDOW_Y;
- }
- if (flags & (1 << 10)) {
- value_mask |= XCB_CONFIG_WINDOW_WIDTH;
- }
- if (flags & (1 << 11)) {
- value_mask |= XCB_CONFIG_WINDOW_HEIGHT;
- }
- configureRequest(value_mask, x, y, width, height, gravity, true);
-}
-
-bool X11Client::isMovable() const
-{
- if (!hasNETSupport() && !m_motif.move()) {
- return false;
- }
- if (isFullScreen())
- return false;
- if (isSpecialWindow() && !isSplash() && !isToolbar()) // allow moving of splashscreens :)
- return false;
- if (rules()->checkPosition(invalidPoint) != invalidPoint) // forced position
- return false;
- return true;
-}
-
-bool X11Client::isMovableAcrossScreens() const
-{
- if (!hasNETSupport() && !m_motif.move()) {
- return false;
- }
- if (isSpecialWindow() && !isSplash() && !isToolbar()) // allow moving of splashscreens :)
- return false;
- if (rules()->checkPosition(invalidPoint) != invalidPoint) // forced position
- return false;
- return true;
-}
-
-bool X11Client::isResizable() const
-{
- if (!hasNETSupport() && !m_motif.resize()) {
- return false;
- }
- if (isFullScreen())
- return false;
- if (isSpecialWindow() || isSplash() || isToolbar())
- return false;
- if (rules()->checkSize(QSize()).isValid()) // forced size
- return false;
- const Position mode = moveResizePointerMode();
- if ((mode == PositionTop || mode == PositionTopLeft || mode == PositionTopRight ||
- mode == PositionLeft || mode == PositionBottomLeft) && rules()->checkPosition(invalidPoint) != invalidPoint)
- return false;
-
- QSize min = minSize();
- QSize max = maxSize();
- return min.width() < max.width() || min.height() < max.height();
-}
-
-bool X11Client::isMaximizable() const
-{
- if (!isResizable() || isToolbar()) // SELI isToolbar() ?
- return false;
- if (rules()->checkMaximize(MaximizeRestore) == MaximizeRestore && rules()->checkMaximize(MaximizeFull) != MaximizeRestore)
- return true;
- return false;
-}
-
-
-/**
- * Reimplemented to inform the client about the new window position.
- */
-void X11Client::setFrameGeometry(int x, int y, int w, int h, ForceGeometry_t force)
-{
- // this code is also duplicated in X11Client::plainResize()
- // Ok, the shading geometry stuff. Generally, code doesn't care about shaded geometry,
- // simply because there are too many places dealing with geometry. Those places
- // ignore shaded state and use normal geometry, which they usually should get
- // from adjustedSize(). Such geometry comes here, and if the window is shaded,
- // the geometry is used only for client_size, since that one is not used when
- // shading. Then the frame geometry is adjusted for the shaded geometry.
- // This gets more complicated in the case the code does only something like
- // setGeometry( geometry()) - geometry() will return the shaded frame geometry.
- // Such code is wrong and should be changed to handle the case when the window is shaded,
- // for example using X11Client::clientSize()
-
- QRect frameGeometry(x, y, w, h);
- QRect bufferGeometry;
-
- if (shade_geometry_change)
- ; // nothing
- else if (isShade()) {
- if (frameGeometry.height() == borderTop() + borderBottom()) {
- qCDebug(KWIN_CORE) << "Shaded geometry passed for size:";
- } else {
- m_clientGeometry = frameRectToClientRect(frameGeometry);
- frameGeometry.setHeight(borderTop() + borderBottom());
- }
- } else {
- m_clientGeometry = frameRectToClientRect(frameGeometry);
- }
- if (isDecorated()) {
- bufferGeometry = frameGeometry;
- } else {
- bufferGeometry = m_clientGeometry;
- }
- if (!areGeometryUpdatesBlocked() && frameGeometry != rules()->checkGeometry(frameGeometry)) {
- qCDebug(KWIN_CORE) << "forced geometry fail:" << frameGeometry << ":" << rules()->checkGeometry(frameGeometry);
- }
- m_frameGeometry = frameGeometry;
- if (force == NormalGeometrySet && m_bufferGeometry == bufferGeometry && pendingGeometryUpdate() == PendingGeometryNone) {
- return;
- }
- m_bufferGeometry = bufferGeometry;
- if (areGeometryUpdatesBlocked()) {
- if (pendingGeometryUpdate() == PendingGeometryForced)
- {} // maximum, nothing needed
- else if (force == ForceGeometrySet)
- setPendingGeometryUpdate(PendingGeometryForced);
- else
- setPendingGeometryUpdate(PendingGeometryNormal);
- return;
- }
- updateServerGeometry();
- updateWindowRules(Rules::Position|Rules::Size);
-
- // keep track of old maximize mode
- // to detect changes
- screens()->setCurrent(this);
- workspace()->updateStackingOrder();
-
- // Need to regenerate decoration pixmaps when the buffer size is changed.
- if (bufferGeometryBeforeUpdateBlocking().size() != m_bufferGeometry.size()) {
- discardWindowPixmap();
- }
- emit geometryShapeChanged(this, frameGeometryBeforeUpdateBlocking());
- addRepaintDuringGeometryUpdates();
- updateGeometryBeforeUpdateBlocking();
- // TODO: this signal is emitted too often
- emit geometryChanged();
-}
-
-void X11Client::plainResize(int w, int h, ForceGeometry_t force)
-{
- QSize frameSize(w, h);
- QSize bufferSize;
-
- // this code is also duplicated in X11Client::setGeometry(), and it's also commented there
- if (shade_geometry_change)
- ; // nothing
- else if (isShade()) {
- if (frameSize.height() == borderTop() + borderBottom()) {
- qCDebug(KWIN_CORE) << "Shaded geometry passed for size:";
- } else {
- m_clientGeometry.setSize(frameSizeToClientSize(frameSize));
- frameSize.setHeight(borderTop() + borderBottom());
- }
- } else {
- m_clientGeometry.setSize(frameSizeToClientSize(frameSize));
- }
- if (isDecorated()) {
- bufferSize = frameSize;
- } else {
- bufferSize = m_clientGeometry.size();
- }
- if (!areGeometryUpdatesBlocked() && frameSize != rules()->checkSize(frameSize)) {
- qCDebug(KWIN_CORE) << "forced size fail:" << frameSize << ":" << rules()->checkSize(frameSize);
- }
- m_frameGeometry.setSize(frameSize);
- // resuming geometry updates is handled only in setGeometry()
- Q_ASSERT(pendingGeometryUpdate() == PendingGeometryNone || areGeometryUpdatesBlocked());
- if (force == NormalGeometrySet && m_bufferGeometry.size() == bufferSize) {
- return;
- }
- m_bufferGeometry.setSize(bufferSize);
- if (areGeometryUpdatesBlocked()) {
- if (pendingGeometryUpdate() == PendingGeometryForced)
- {} // maximum, nothing needed
- else if (force == ForceGeometrySet)
- setPendingGeometryUpdate(PendingGeometryForced);
- else
- setPendingGeometryUpdate(PendingGeometryNormal);
- return;
- }
- updateServerGeometry();
- updateWindowRules(Rules::Position|Rules::Size);
- screens()->setCurrent(this);
- workspace()->updateStackingOrder();
- if (bufferGeometryBeforeUpdateBlocking().size() != m_bufferGeometry.size()) {
- discardWindowPixmap();
- }
- emit geometryShapeChanged(this, frameGeometryBeforeUpdateBlocking());
- addRepaintDuringGeometryUpdates();
- updateGeometryBeforeUpdateBlocking();
- // TODO: this signal is emitted too often
- emit geometryChanged();
-}
-
-void X11Client::updateServerGeometry()
-{
- if (m_frame.geometry().size() != m_bufferGeometry.size() || pendingGeometryUpdate() == PendingGeometryForced) {
- resizeDecoration();
- m_frame.setGeometry(m_bufferGeometry);
- if (!isShade()) {
- QSize cs = clientSize();
- m_wrapper.setGeometry(QRect(clientPos(), cs));
- if (!isResize() || syncRequest.counter == XCB_NONE) {
- m_client.setGeometry(0, 0, cs.width(), cs.height());
- }
- // SELI - won't this be too expensive?
- // THOMAS - yes, but gtk+ clients will not resize without ...
- sendSyntheticConfigureNotify();
- }
- updateShape();
- } else {
- if (isMoveResize()) {
- if (compositing()) { // Defer the X update until we leave this mode
- needsXWindowMove = true;
- } else {
- m_frame.move(m_bufferGeometry.topLeft()); // sendSyntheticConfigureNotify() on finish shall be sufficient
- }
- } else {
- m_frame.move(m_bufferGeometry.topLeft());
- sendSyntheticConfigureNotify();
- }
- // Unconditionally move the input window: it won't affect rendering
- m_decoInputExtent.move(pos() + inputPos());
- }
-}
-
-/**
- * Reimplemented to inform the client about the new window position.
- */
-void AbstractClient::move(int x, int y, ForceGeometry_t force)
-{
- // resuming geometry updates is handled only in setGeometry()
- Q_ASSERT(pendingGeometryUpdate() == PendingGeometryNone || areGeometryUpdatesBlocked());
- QPoint p(x, y);
- if (!areGeometryUpdatesBlocked() && p != rules()->checkPosition(p)) {
- qCDebug(KWIN_CORE) << "forced position fail:" << p << ":" << rules()->checkPosition(p);
- }
- if (force == NormalGeometrySet && m_frameGeometry.topLeft() == p)
- return;
- m_frameGeometry.moveTopLeft(p);
- if (areGeometryUpdatesBlocked()) {
- if (pendingGeometryUpdate() == PendingGeometryForced)
- {} // maximum, nothing needed
- else if (force == ForceGeometrySet)
- setPendingGeometryUpdate(PendingGeometryForced);
- else
- setPendingGeometryUpdate(PendingGeometryNormal);
- return;
- }
- doMove(x, y);
- updateWindowRules(Rules::Position);
- screens()->setCurrent(this);
- workspace()->updateStackingOrder();
- // client itself is not damaged
- addRepaintDuringGeometryUpdates();
- updateGeometryBeforeUpdateBlocking();
- emit geometryChanged();
-}
-
-void AbstractClient::blockGeometryUpdates(bool block)
-{
- if (block) {
- if (m_blockGeometryUpdates == 0)
- m_pendingGeometryUpdate = PendingGeometryNone;
- ++m_blockGeometryUpdates;
- } else {
- if (--m_blockGeometryUpdates == 0) {
- if (m_pendingGeometryUpdate != PendingGeometryNone) {
- if (isShade())
- setFrameGeometry(QRect(pos(), adjustedSize()), NormalGeometrySet);
- else
- setFrameGeometry(frameGeometry(), NormalGeometrySet);
- m_pendingGeometryUpdate = PendingGeometryNone;
- }
- }
- }
-}
-
-void AbstractClient::maximize(MaximizeMode m)
-{
- setMaximize(m & MaximizeVertical, m & MaximizeHorizontal);
-}
-
-void AbstractClient::setMaximize(bool vertically, bool horizontally)
-{
- // changeMaximize() flips the state, so change from set->flip
- const MaximizeMode oldMode = maximizeMode();
- changeMaximize(
- oldMode & MaximizeHorizontal ? !horizontally : horizontally,
- oldMode & MaximizeVertical ? !vertically : vertically,
- false);
- const MaximizeMode newMode = maximizeMode();
- if (oldMode != newMode) {
- emit clientMaximizedStateChanged(this, newMode);
- emit clientMaximizedStateChanged(this, vertically, horizontally);
- }
-
-}
-
-static bool changeMaximizeRecursion = false;
-void X11Client::changeMaximize(bool horizontal, bool vertical, bool adjust)
-{
- if (changeMaximizeRecursion)
- return;
-
- if (!isResizable() || isToolbar()) // SELI isToolbar() ?
- return;
-
- QRect clientArea;
- if (isElectricBorderMaximizing())
- clientArea = workspace()->clientArea(MaximizeArea, Cursor::pos(), desktop());
- else
- clientArea = workspace()->clientArea(MaximizeArea, this);
-
- MaximizeMode old_mode = max_mode;
- // 'adjust == true' means to update the size only, e.g. after changing workspace size
- if (!adjust) {
- if (vertical)
- max_mode = MaximizeMode(max_mode ^ MaximizeVertical);
- if (horizontal)
- max_mode = MaximizeMode(max_mode ^ MaximizeHorizontal);
- }
-
- // if the client insist on a fix aspect ratio, we check whether the maximizing will get us
- // out of screen bounds and take that as a "full maximization with aspect check" then
- if (m_geometryHints.hasAspect() && // fixed aspect
- (max_mode == MaximizeVertical || max_mode == MaximizeHorizontal) && // ondimensional maximization
- rules()->checkStrictGeometry(true)) { // obey aspect
- const QSize minAspect = m_geometryHints.minAspect();
- const QSize maxAspect = m_geometryHints.maxAspect();
- if (max_mode == MaximizeVertical || (old_mode & MaximizeVertical)) {
- const double fx = minAspect.width(); // use doubles, because the values can be MAX_INT
- const double fy = maxAspect.height(); // use doubles, because the values can be MAX_INT
- if (fx*clientArea.height()/fy > clientArea.width()) // too big
- max_mode = old_mode & MaximizeHorizontal ? MaximizeRestore : MaximizeFull;
- } else { // max_mode == MaximizeHorizontal
- const double fx = maxAspect.width();
- const double fy = minAspect.height();
- if (fy*clientArea.width()/fx > clientArea.height()) // too big
- max_mode = old_mode & MaximizeVertical ? MaximizeRestore : MaximizeFull;
- }
- }
-
- max_mode = rules()->checkMaximize(max_mode);
- if (!adjust && max_mode == old_mode)
- return;
-
- GeometryUpdatesBlocker blocker(this);
-
- // maximing one way and unmaximizing the other way shouldn't happen,
- // so restore first and then maximize the other way
- if ((old_mode == MaximizeVertical && max_mode == MaximizeHorizontal)
- || (old_mode == MaximizeHorizontal && max_mode == MaximizeVertical)) {
- changeMaximize(false, false, false); // restore
- }
-
- // save sizes for restoring, if maximalizing
- QSize sz;
- if (isShade())
- sz = sizeForClientSize(clientSize());
- else
- sz = size();
-
- if (quickTileMode() == QuickTileMode(QuickTileFlag::None)) {
- if (!adjust && !(old_mode & MaximizeVertical)) {
- geom_restore.setTop(y());
- geom_restore.setHeight(sz.height());
- }
- if (!adjust && !(old_mode & MaximizeHorizontal)) {
- geom_restore.setLeft(x());
- geom_restore.setWidth(sz.width());
- }
- }
-
- // call into decoration update borders
- if (isDecorated() && decoration()->client() && !(options->borderlessMaximizedWindows() && max_mode == KWin::MaximizeFull)) {
- changeMaximizeRecursion = true;
- const auto c = decoration()->client().data();
- if ((max_mode & MaximizeVertical) != (old_mode & MaximizeVertical)) {
- emit c->maximizedVerticallyChanged(max_mode & MaximizeVertical);
- }
- if ((max_mode & MaximizeHorizontal) != (old_mode & MaximizeHorizontal)) {
- emit c->maximizedHorizontallyChanged(max_mode & MaximizeHorizontal);
- }
- if ((max_mode == MaximizeFull) != (old_mode == MaximizeFull)) {
- emit c->maximizedChanged(max_mode & MaximizeFull);
- }
- changeMaximizeRecursion = false;
- }
-
- if (options->borderlessMaximizedWindows()) {
- // triggers a maximize change.
- // The next setNoBorder interation will exit since there's no change but the first recursion pullutes the restore geometry
- changeMaximizeRecursion = true;
- setNoBorder(rules()->checkNoBorder(app_noborder || (m_motif.hasDecoration() && m_motif.noBorder()) || max_mode == MaximizeFull));
- changeMaximizeRecursion = false;
- }
-
- const ForceGeometry_t geom_mode = isDecorated() ? ForceGeometrySet : NormalGeometrySet;
-
- // Conditional quick tiling exit points
- if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) {
- if (old_mode == MaximizeFull &&
- !clientArea.contains(geom_restore.center())) {
- // Not restoring on the same screen
- // TODO: The following doesn't work for some reason
- //quick_tile_mode = QuickTileFlag::None; // And exit quick tile mode manually
- } else if ((old_mode == MaximizeVertical && max_mode == MaximizeRestore) ||
- (old_mode == MaximizeFull && max_mode == MaximizeHorizontal)) {
- // Modifying geometry of a tiled window
- updateQuickTileMode(QuickTileFlag::None); // Exit quick tile mode without restoring geometry
- }
- }
-
- switch(max_mode) {
-
- case MaximizeVertical: {
- if (old_mode & MaximizeHorizontal) { // actually restoring from MaximizeFull
- if (geom_restore.width() == 0 || !clientArea.contains(geom_restore.center())) {
- // needs placement
- plainResize(adjustedSize(QSize(width() * 2 / 3, clientArea.height()), SizemodeFixedH), geom_mode);
- Placement::self()->placeSmart(this, clientArea);
- } else {
- setFrameGeometry(QRect(QPoint(geom_restore.x(), clientArea.top()),
- adjustedSize(QSize(geom_restore.width(), clientArea.height()), SizemodeFixedH)), geom_mode);
- }
- } else {
- QRect r(x(), clientArea.top(), width(), clientArea.height());
- r.setTopLeft(rules()->checkPosition(r.topLeft()));
- r.setSize(adjustedSize(r.size(), SizemodeFixedH));
- setFrameGeometry(r, geom_mode);
- }
- info->setState(NET::MaxVert, NET::Max);
- break;
- }
-
- case MaximizeHorizontal: {
- if (old_mode & MaximizeVertical) { // actually restoring from MaximizeFull
- if (geom_restore.height() == 0 || !clientArea.contains(geom_restore.center())) {
- // needs placement
- plainResize(adjustedSize(QSize(clientArea.width(), height() * 2 / 3), SizemodeFixedW), geom_mode);
- Placement::self()->placeSmart(this, clientArea);
- } else {
- setFrameGeometry(QRect(QPoint(clientArea.left(), geom_restore.y()),
- adjustedSize(QSize(clientArea.width(), geom_restore.height()), SizemodeFixedW)), geom_mode);
- }
- } else {
- QRect r(clientArea.left(), y(), clientArea.width(), height());
- r.setTopLeft(rules()->checkPosition(r.topLeft()));
- r.setSize(adjustedSize(r.size(), SizemodeFixedW));
- setFrameGeometry(r, geom_mode);
- }
- info->setState(NET::MaxHoriz, NET::Max);
- break;
- }
-
- case MaximizeRestore: {
- QRect restore = frameGeometry();
- // when only partially maximized, geom_restore may not have the other dimension remembered
- if (old_mode & MaximizeVertical) {
- restore.setTop(geom_restore.top());
- restore.setBottom(geom_restore.bottom());
- }
- if (old_mode & MaximizeHorizontal) {
- restore.setLeft(geom_restore.left());
- restore.setRight(geom_restore.right());
- }
- if (!restore.isValid()) {
- QSize s = QSize(clientArea.width() * 2 / 3, clientArea.height() * 2 / 3);
- if (geom_restore.width() > 0)
- s.setWidth(geom_restore.width());
- if (geom_restore.height() > 0)
- s.setHeight(geom_restore.height());
- plainResize(adjustedSize(s));
- Placement::self()->placeSmart(this, clientArea);
- restore = frameGeometry();
- if (geom_restore.width() > 0)
- restore.moveLeft(geom_restore.x());
- if (geom_restore.height() > 0)
- restore.moveTop(geom_restore.y());
- geom_restore = restore; // relevant for mouse pos calculation, bug #298646
- }
- if (m_geometryHints.hasAspect()) {
- restore.setSize(adjustedSize(restore.size(), SizemodeAny));
- }
- setFrameGeometry(restore, geom_mode);
- if (!clientArea.contains(geom_restore.center())) // Not restoring to the same screen
- Placement::self()->place(this, clientArea);
- info->setState(NET::States(), NET::Max);
- updateQuickTileMode(QuickTileFlag::None);
- break;
- }
-
- case MaximizeFull: {
- QRect r(clientArea);
- r.setTopLeft(rules()->checkPosition(r.topLeft()));
- r.setSize(adjustedSize(r.size(), SizemodeMax));
- if (r.size() != clientArea.size()) { // to avoid off-by-one errors...
- if (isElectricBorderMaximizing() && r.width() < clientArea.width()) {
- r.moveLeft(qMax(clientArea.left(), Cursor::pos().x() - r.width()/2));
- r.moveRight(qMin(clientArea.right(), r.right()));
- } else {
- r.moveCenter(clientArea.center());
- const bool closeHeight = r.height() > 97*clientArea.height()/100;
- const bool closeWidth = r.width() > 97*clientArea.width() /100;
- const bool overHeight = r.height() > clientArea.height();
- const bool overWidth = r.width() > clientArea.width();
- if (closeWidth || closeHeight) {
- Position titlePos = titlebarPosition();
- const QRect screenArea = workspace()->clientArea(ScreenArea, clientArea.center(), desktop());
- if (closeHeight) {
- bool tryBottom = titlePos == PositionBottom;
- if ((overHeight && titlePos == PositionTop) ||
- screenArea.top() == clientArea.top())
- r.setTop(clientArea.top());
- else
- tryBottom = true;
- if (tryBottom &&
- (overHeight || screenArea.bottom() == clientArea.bottom()))
- r.setBottom(clientArea.bottom());
- }
- if (closeWidth) {
- bool tryLeft = titlePos == PositionLeft;
- if ((overWidth && titlePos == PositionRight) ||
- screenArea.right() == clientArea.right())
- r.setRight(clientArea.right());
- else
- tryLeft = true;
- if (tryLeft && (overWidth || screenArea.left() == clientArea.left()))
- r.setLeft(clientArea.left());
- }
- }
- }
- r.moveTopLeft(rules()->checkPosition(r.topLeft()));
- }
- setFrameGeometry(r, geom_mode);
- if (options->electricBorderMaximize() && r.top() == clientArea.top())
- updateQuickTileMode(QuickTileFlag::Maximize);
- else
- updateQuickTileMode(QuickTileFlag::None);
- info->setState(NET::Max, NET::Max);
- break;
- }
- default:
- break;
- }
-
- updateAllowedActions();
- updateWindowRules(Rules::MaximizeVert|Rules::MaximizeHoriz|Rules::Position|Rules::Size);
- emit quickTileModeChanged();
-}
-
-bool X11Client::userCanSetFullScreen() const
-{
- if (!isFullScreenable()) {
- return false;
- }
- return isNormalWindow() || isDialog();
-}
-
-void X11Client::setFullScreen(bool set, bool user)
-{
- set = rules()->checkFullScreen(set);
-
- const bool wasFullscreen = isFullScreen();
- if (wasFullscreen == set) {
- return;
- }
- if (user && !userCanSetFullScreen()) {
- return;
- }
-
- setShade(ShadeNone);
-
- if (wasFullscreen) {
- workspace()->updateFocusMousePosition(Cursor::pos()); // may cause leave event
- } else {
- geom_fs_restore = frameGeometry();
- }
-
- if (set) {
- m_fullscreenMode = FullScreenNormal;
- workspace()->raiseClient(this);
- } else {
- m_fullscreenMode = FullScreenNone;
- }
-
- StackingUpdatesBlocker blocker1(workspace());
- GeometryUpdatesBlocker blocker2(this);
-
- // active fullscreens get different layer
- workspace()->updateClientLayer(this);
-
- info->setState(isFullScreen() ? NET::FullScreen : NET::States(), NET::FullScreen);
- updateDecoration(false, false);
-
- if (set) {
- if (info->fullscreenMonitors().isSet()) {
- setFrameGeometry(fullscreenMonitorsArea(info->fullscreenMonitors()));
- } else {
- setFrameGeometry(workspace()->clientArea(FullScreenArea, this));
- }
- } else {
- Q_ASSERT(!geom_fs_restore.isNull());
- const int currentScreen = screen();
- setFrameGeometry(QRect(geom_fs_restore.topLeft(), adjustedSize(geom_fs_restore.size())));
- if(currentScreen != screen()) {
- workspace()->sendClientToScreen(this, currentScreen);
- }
- }
-
- updateWindowRules(Rules::Fullscreen | Rules::Position | Rules::Size);
- emit clientFullScreenSet(this, set, user);
- emit fullScreenChanged();
-}
-
-
-void X11Client::updateFullscreenMonitors(NETFullscreenMonitors topology)
-{
- int nscreens = screens()->count();
-
-// qDebug() << "incoming request with top: " << topology.top << " bottom: " << topology.bottom
-// << " left: " << topology.left << " right: " << topology.right
-// << ", we have: " << nscreens << " screens.";
-
- if (topology.top >= nscreens ||
- topology.bottom >= nscreens ||
- topology.left >= nscreens ||
- topology.right >= nscreens) {
- qCWarning(KWIN_CORE) << "fullscreenMonitors update failed. request higher than number of screens.";
- return;
- }
-
- info->setFullscreenMonitors(topology);
- if (isFullScreen())
- setFrameGeometry(fullscreenMonitorsArea(topology));
-}
-
-/**
- * Calculates the bounding rectangle defined by the 4 monitor indices indicating the
- * top, bottom, left, and right edges of the window when the fullscreen state is enabled.
- */
-QRect X11Client::fullscreenMonitorsArea(NETFullscreenMonitors requestedTopology) const
-{
- QRect top, bottom, left, right, total;
-
- top = screens()->geometry(requestedTopology.top);
- bottom = screens()->geometry(requestedTopology.bottom);
- left = screens()->geometry(requestedTopology.left);
- right = screens()->geometry(requestedTopology.right);
- total = top.united(bottom.united(left.united(right)));
-
-// qDebug() << "top: " << top << " bottom: " << bottom
-// << " left: " << left << " right: " << right;
-// qDebug() << "returning rect: " << total;
- return total;
-}
-
-static GeometryTip* geometryTip = nullptr;
-
-void X11Client::positionGeometryTip()
-{
- Q_ASSERT(isMove() || isResize());
- // Position and Size display
- if (effects && static_cast(effects)->provides(Effect::GeometryTip))
- return; // some effect paints this for us
- if (options->showGeometryTip()) {
- if (!geometryTip) {
- geometryTip = new GeometryTip(&m_geometryHints);
- }
- QRect wgeom(moveResizeGeometry()); // position of the frame, size of the window itself
- wgeom.setWidth(wgeom.width() - (width() - clientSize().width()));
- wgeom.setHeight(wgeom.height() - (height() - clientSize().height()));
- if (isShade())
- wgeom.setHeight(0);
- geometryTip->setGeometry(wgeom);
- if (!geometryTip->isVisible())
- geometryTip->show();
- geometryTip->raise();
- }
-}
-
-bool AbstractClient::startMoveResize()
-{
- Q_ASSERT(!isMoveResize());
- Q_ASSERT(QWidget::keyboardGrabber() == nullptr);
- Q_ASSERT(QWidget::mouseGrabber() == nullptr);
- stopDelayedMoveResize();
- if (QApplication::activePopupWidget() != nullptr)
- return false; // popups have grab
- if (isFullScreen() && (screens()->count() < 2 || !isMovableAcrossScreens()))
- return false;
- if (!doStartMoveResize()) {
- return false;
- }
-
- invalidateDecorationDoubleClickTimer();
-
- setMoveResize(true);
- workspace()->setMoveResizeClient(this);
-
- const Position mode = moveResizePointerMode();
- if (mode != PositionCenter) { // means "isResize()" but moveResizeMode = true is set below
- if (maximizeMode() == MaximizeFull) { // partial is cond. reset in finishMoveResize
- setGeometryRestore(frameGeometry()); // "restore" to current geometry
- setMaximize(false, false);
- }
- }
-
- if (quickTileMode() != QuickTileMode(QuickTileFlag::None) && mode != PositionCenter) { // Cannot use isResize() yet
- // Exit quick tile mode when the user attempts to resize a tiled window
- updateQuickTileMode(QuickTileFlag::None); // Do so without restoring original geometry
- setGeometryRestore(frameGeometry());
- emit quickTileModeChanged();
- }
-
- updateHaveResizeEffect();
- updateInitialMoveResizeGeometry();
- checkUnrestrictedMoveResize();
- emit clientStartUserMovedResized(this);
- if (ScreenEdges::self()->isDesktopSwitchingMovingClients())
- ScreenEdges::self()->reserveDesktopSwitching(true, Qt::Vertical|Qt::Horizontal);
- return true;
-}
-
-bool X11Client::doStartMoveResize()
-{
- bool has_grab = false;
- // This reportedly improves smoothness of the moveresize operation,
- // something with Enter/LeaveNotify events, looks like XFree performance problem or something *shrug*
- // (https://lists.kde.org/?t=107302193400001&r=1&w=2)
- QRect r = workspace()->clientArea(FullArea, this);
- m_moveResizeGrabWindow.create(r, XCB_WINDOW_CLASS_INPUT_ONLY, 0, nullptr, rootWindow());
- m_moveResizeGrabWindow.map();
- m_moveResizeGrabWindow.raise();
- updateXTime();
- const xcb_grab_pointer_cookie_t cookie = xcb_grab_pointer_unchecked(connection(), false, m_moveResizeGrabWindow,
- XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION |
- XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW,
- XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, m_moveResizeGrabWindow, Cursor::x11Cursor(cursor()), xTime());
- ScopedCPointer pointerGrab(xcb_grab_pointer_reply(connection(), cookie, nullptr));
- if (!pointerGrab.isNull() && pointerGrab->status == XCB_GRAB_STATUS_SUCCESS) {
- has_grab = true;
- }
- if (!has_grab && grabXKeyboard(frameId()))
- has_grab = move_resize_has_keyboard_grab = true;
- if (!has_grab) { // at least one grab is necessary in order to be able to finish move/resize
- m_moveResizeGrabWindow.reset();
- return false;
- }
- return true;
-}
-
-void AbstractClient::finishMoveResize(bool cancel)
-{
- GeometryUpdatesBlocker blocker(this);
- const bool wasResize = isResize(); // store across leaveMoveResize
- leaveMoveResize();
-
- if (cancel)
- setFrameGeometry(initialMoveResizeGeometry());
- else {
- const QRect &moveResizeGeom = moveResizeGeometry();
- if (wasResize) {
- const bool restoreH = maximizeMode() == MaximizeHorizontal &&
- moveResizeGeom.width() != initialMoveResizeGeometry().width();
- const bool restoreV = maximizeMode() == MaximizeVertical &&
- moveResizeGeom.height() != initialMoveResizeGeometry().height();
- if (restoreH || restoreV) {
- changeMaximize(restoreH, restoreV, false);
- }
- }
- setFrameGeometry(moveResizeGeom);
- }
- checkScreen(); // needs to be done because clientFinishUserMovedResized has not yet re-activated online alignment
- if (screen() != moveResizeStartScreen()) {
- workspace()->sendClientToScreen(this, screen()); // checks rule validity
- if (maximizeMode() != MaximizeRestore)
- checkWorkspacePosition();
- }
-
- if (isElectricBorderMaximizing()) {
- setQuickTileMode(electricBorderMode());
- setElectricBorderMaximizing(false);
- } else if (!cancel) {
- QRect geom_restore = geometryRestore();
- if (!(maximizeMode() & MaximizeHorizontal)) {
- geom_restore.setX(frameGeometry().x());
- geom_restore.setWidth(frameGeometry().width());
- }
- if (!(maximizeMode() & MaximizeVertical)) {
- geom_restore.setY(frameGeometry().y());
- geom_restore.setHeight(frameGeometry().height());
- }
- setGeometryRestore(geom_restore);
- }
-// FRAME update();
-
- emit clientFinishUserMovedResized(this);
-}
-
-void X11Client::leaveMoveResize()
-{
- if (needsXWindowMove) {
- // Do the deferred move
- m_frame.move(m_bufferGeometry.topLeft());
- needsXWindowMove = false;
- }
- if (!isResize())
- sendSyntheticConfigureNotify(); // tell the client about it's new final position
- if (geometryTip) {
- geometryTip->hide();
- delete geometryTip;
- geometryTip = nullptr;
- }
- if (move_resize_has_keyboard_grab)
- ungrabXKeyboard();
- move_resize_has_keyboard_grab = false;
- xcb_ungrab_pointer(connection(), xTime());
- m_moveResizeGrabWindow.reset();
- if (syncRequest.counter == XCB_NONE) // don't forget to sanitize since the timeout will no more fire
- syncRequest.isPending = false;
- delete syncRequest.timeout;
- syncRequest.timeout = nullptr;
- AbstractClient::leaveMoveResize();
-}
-
-// This function checks if it actually makes sense to perform a restricted move/resize.
-// If e.g. the titlebar is already outside of the workarea, there's no point in performing
-// a restricted move resize, because then e.g. resize would also move the window (#74555).
-// NOTE: Most of it is duplicated from handleMoveResize().
-void AbstractClient::checkUnrestrictedMoveResize()
-{
- if (isUnrestrictedMoveResize())
- return;
- const QRect &moveResizeGeom = moveResizeGeometry();
- QRect desktopArea = workspace()->clientArea(WorkArea, moveResizeGeom.center(), desktop());
- int left_marge, right_marge, top_marge, bottom_marge, titlebar_marge;
- // restricted move/resize - keep at least part of the titlebar always visible
- // how much must remain visible when moved away in that direction
- left_marge = qMin(100 + borderRight(), moveResizeGeom.width());
- right_marge = qMin(100 + borderLeft(), moveResizeGeom.width());
- // width/height change with opaque resizing, use the initial ones
- titlebar_marge = initialMoveResizeGeometry().height();
- top_marge = borderBottom();
- bottom_marge = borderTop();
- if (isResize()) {
- if (moveResizeGeom.bottom() < desktopArea.top() + top_marge)
- setUnrestrictedMoveResize(true);
- if (moveResizeGeom.top() > desktopArea.bottom() - bottom_marge)
- setUnrestrictedMoveResize(true);
- if (moveResizeGeom.right() < desktopArea.left() + left_marge)
- setUnrestrictedMoveResize(true);
- if (moveResizeGeom.left() > desktopArea.right() - right_marge)
- setUnrestrictedMoveResize(true);
- if (!isUnrestrictedMoveResize() && moveResizeGeom.top() < desktopArea.top()) // titlebar mustn't go out
- setUnrestrictedMoveResize(true);
- }
- if (isMove()) {
- if (moveResizeGeom.bottom() < desktopArea.top() + titlebar_marge - 1)
- setUnrestrictedMoveResize(true);
- // no need to check top_marge, titlebar_marge already handles it
- if (moveResizeGeom.top() > desktopArea.bottom() - bottom_marge + 1) // titlebar mustn't go out
- setUnrestrictedMoveResize(true);
- if (moveResizeGeom.right() < desktopArea.left() + left_marge)
- setUnrestrictedMoveResize(true);
- if (moveResizeGeom.left() > desktopArea.right() - right_marge)
- setUnrestrictedMoveResize(true);
- }
-}
-
-// When the user pressed mouse on the titlebar, don't activate move immediatelly,
-// since it may be just a click. Activate instead after a delay. Move used to be
-// activated only after moving by several pixels, but that looks bad.
-void AbstractClient::startDelayedMoveResize()
-{
- Q_ASSERT(!m_moveResize.delayedTimer);
- m_moveResize.delayedTimer = new QTimer(this);
- m_moveResize.delayedTimer->setSingleShot(true);
- connect(m_moveResize.delayedTimer, &QTimer::timeout, this,
- [this]() {
- Q_ASSERT(isMoveResizePointerButtonDown());
- if (!startMoveResize()) {
- setMoveResizePointerButtonDown(false);
- }
- updateCursor();
- stopDelayedMoveResize();
- }
- );
- m_moveResize.delayedTimer->start(QApplication::startDragTime());
-}
-
-void AbstractClient::stopDelayedMoveResize()
-{
- delete m_moveResize.delayedTimer;
- m_moveResize.delayedTimer = nullptr;
-}
-
-void AbstractClient::handleMoveResize(const QPoint &local, const QPoint &global)
-{
- const QRect oldGeo = frameGeometry();
- handleMoveResize(local.x(), local.y(), global.x(), global.y());
- if (!isFullScreen() && isMove()) {
- if (quickTileMode() != QuickTileMode(QuickTileFlag::None) && oldGeo != frameGeometry()) {
- GeometryUpdatesBlocker blocker(this);
- setQuickTileMode(QuickTileFlag::None);
- const QRect &geom_restore = geometryRestore();
- setMoveOffset(QPoint(double(moveOffset().x()) / double(oldGeo.width()) * double(geom_restore.width()),
- double(moveOffset().y()) / double(oldGeo.height()) * double(geom_restore.height())));
- if (rules()->checkMaximize(MaximizeRestore) == MaximizeRestore)
- setMoveResizeGeometry(geom_restore);
- handleMoveResize(local.x(), local.y(), global.x(), global.y()); // fix position
- } else if (quickTileMode() == QuickTileMode(QuickTileFlag::None) && isResizable()) {
- checkQuickTilingMaximizationZones(global.x(), global.y());
- }
- }
-}
-
-bool X11Client::isWaitingForMoveResizeSync() const
-{
- return syncRequest.isPending && isResize();
-}
-
-void AbstractClient::handleMoveResize(int x, int y, int x_root, int y_root)
-{
- if (isWaitingForMoveResizeSync())
- return; // we're still waiting for the client or the timeout
-
- const Position mode = moveResizePointerMode();
- if ((mode == PositionCenter && !isMovableAcrossScreens())
- || (mode != PositionCenter && (isShade() || !isResizable())))
- return;
-
- if (!isMoveResize()) {
- QPoint p(QPoint(x/* - padding_left*/, y/* - padding_top*/) - moveOffset());
- if (p.manhattanLength() >= QApplication::startDragDistance()) {
- if (!startMoveResize()) {
- setMoveResizePointerButtonDown(false);
- updateCursor();
- return;
- }
- updateCursor();
- } else
- return;
- }
-
- // ShadeHover or ShadeActive, ShadeNormal was already avoided above
- if (mode != PositionCenter && shadeMode() != ShadeNone)
- setShade(ShadeNone);
-
- QPoint globalPos(x_root, y_root);
- // these two points limit the geometry rectangle, i.e. if bottomleft resizing is done,
- // the bottomleft corner should be at is at (topleft.x(), bottomright().y())
- QPoint topleft = globalPos - moveOffset();
- QPoint bottomright = globalPos + invertedMoveOffset();
- QRect previousMoveResizeGeom = moveResizeGeometry();
-
- // TODO move whole group when moving its leader or when the leader is not mapped?
-
- auto titleBarRect = [this](bool &transposed, int &requiredPixels) -> QRect {
- const QRect &moveResizeGeom = moveResizeGeometry();
- QRect r(moveResizeGeom);
- r.moveTopLeft(QPoint(0,0));
- switch (titlebarPosition()) {
- default:
- case PositionTop:
- r.setHeight(borderTop());
- break;
- case PositionLeft:
- r.setWidth(borderLeft());
- transposed = true;
- break;
- case PositionBottom:
- r.setTop(r.bottom() - borderBottom());
- break;
- case PositionRight:
- r.setLeft(r.right() - borderRight());
- transposed = true;
- break;
- }
- // When doing a restricted move we must always keep 100px of the titlebar
- // visible to allow the user to be able to move it again.
- requiredPixels = qMin(100 * (transposed ? r.width() : r.height()),
- moveResizeGeom.width() * moveResizeGeom.height());
- return r;
- };
-
- bool update = false;
- if (isResize()) {
- QRect orig = initialMoveResizeGeometry();
- Sizemode sizemode = SizemodeAny;
- auto calculateMoveResizeGeom = [this, &topleft, &bottomright, &orig, &sizemode, &mode]() {
- switch(mode) {
- case PositionTopLeft:
- setMoveResizeGeometry(QRect(topleft, orig.bottomRight()));
- break;
- case PositionBottomRight:
- setMoveResizeGeometry(QRect(orig.topLeft(), bottomright));
- break;
- case PositionBottomLeft:
- setMoveResizeGeometry(QRect(QPoint(topleft.x(), orig.y()), QPoint(orig.right(), bottomright.y())));
- break;
- case PositionTopRight:
- setMoveResizeGeometry(QRect(QPoint(orig.x(), topleft.y()), QPoint(bottomright.x(), orig.bottom())));
- break;
- case PositionTop:
- setMoveResizeGeometry(QRect(QPoint(orig.left(), topleft.y()), orig.bottomRight()));
- sizemode = SizemodeFixedH; // try not to affect height
- break;
- case PositionBottom:
- setMoveResizeGeometry(QRect(orig.topLeft(), QPoint(orig.right(), bottomright.y())));
- sizemode = SizemodeFixedH;
- break;
- case PositionLeft:
- setMoveResizeGeometry(QRect(QPoint(topleft.x(), orig.top()), orig.bottomRight()));
- sizemode = SizemodeFixedW;
- break;
- case PositionRight:
- setMoveResizeGeometry(QRect(orig.topLeft(), QPoint(bottomright.x(), orig.bottom())));
- sizemode = SizemodeFixedW;
- break;
- case PositionCenter:
- default:
- abort();
- break;
- }
- };
-
- // first resize (without checking constrains), then snap, then check bounds, then check constrains
- calculateMoveResizeGeom();
- // adjust new size to snap to other windows/borders
- setMoveResizeGeometry(workspace()->adjustClientSize(this, moveResizeGeometry(), mode));
-
- if (!isUnrestrictedMoveResize()) {
- // Make sure the titlebar isn't behind a restricted area. We don't need to restrict
- // the other directions. If not visible enough, move the window to the closest valid
- // point. We bruteforce this by slowly moving the window back to its previous position
- QRegion availableArea(workspace()->clientArea(FullArea, -1, 0)); // On the screen
- availableArea -= workspace()->restrictedMoveArea(desktop()); // Strut areas
- bool transposed = false;
- int requiredPixels;
- QRect bTitleRect = titleBarRect(transposed, requiredPixels);
- int lastVisiblePixels = -1;
- QRect lastTry = moveResizeGeometry();
- bool titleFailed = false;
- for (;;) {
- const QRect titleRect(bTitleRect.translated(moveResizeGeometry().topLeft()));
- int visiblePixels = 0;
- int realVisiblePixels = 0;
- for (const QRect &rect : availableArea) {
- const QRect r = rect & titleRect;
- realVisiblePixels += r.width() * r.height();
- if ((transposed && r.width() == titleRect.width()) || // Only the full size regions...
- (!transposed && r.height() == titleRect.height())) // ...prevents long slim areas
- visiblePixels += r.width() * r.height();
- }
-
- if (visiblePixels >= requiredPixels)
- break; // We have reached a valid position
-
- if (realVisiblePixels <= lastVisiblePixels) {
- if (titleFailed && realVisiblePixels < lastVisiblePixels)
- break; // we won't become better
- else {
- if (!titleFailed)
- setMoveResizeGeometry(lastTry);
- titleFailed = true;
- }
- }
- lastVisiblePixels = realVisiblePixels;
- QRect moveResizeGeom = moveResizeGeometry();
- lastTry = moveResizeGeom;
-
- // Not visible enough, move the window to the closest valid point. We bruteforce
- // this by slowly moving the window back to its previous position.
- // The geometry changes at up to two edges, the one with the title (if) shall take
- // precedence. The opposing edge has no impact on visiblePixels and only one of
- // the adjacent can alter at a time, ie. it's enough to ignore adjacent edges
- // if the title edge altered
- bool leftChanged = previousMoveResizeGeom.left() != moveResizeGeom.left();
- bool rightChanged = previousMoveResizeGeom.right() != moveResizeGeom.right();
- bool topChanged = previousMoveResizeGeom.top() != moveResizeGeom.top();
- bool btmChanged = previousMoveResizeGeom.bottom() != moveResizeGeom.bottom();
- auto fixChangedState = [titleFailed](bool &major, bool &counter, bool &ad1, bool &ad2) {
- counter = false;
- if (titleFailed)
- major = false;
- if (major)
- ad1 = ad2 = false;
- };
- switch (titlebarPosition()) {
- default:
- case PositionTop:
- fixChangedState(topChanged, btmChanged, leftChanged, rightChanged);
- break;
- case PositionLeft:
- fixChangedState(leftChanged, rightChanged, topChanged, btmChanged);
- break;
- case PositionBottom:
- fixChangedState(btmChanged, topChanged, leftChanged, rightChanged);
- break;
- case PositionRight:
- fixChangedState(rightChanged, leftChanged, topChanged, btmChanged);
- break;
- }
- if (topChanged)
- moveResizeGeom.setTop(moveResizeGeom.y() + sign(previousMoveResizeGeom.y() - moveResizeGeom.y()));
- else if (leftChanged)
- moveResizeGeom.setLeft(moveResizeGeom.x() + sign(previousMoveResizeGeom.x() - moveResizeGeom.x()));
- else if (btmChanged)
- moveResizeGeom.setBottom(moveResizeGeom.bottom() + sign(previousMoveResizeGeom.bottom() - moveResizeGeom.bottom()));
- else if (rightChanged)
- moveResizeGeom.setRight(moveResizeGeom.right() + sign(previousMoveResizeGeom.right() - moveResizeGeom.right()));
- else
- break; // no position changed - that's certainly not good
- setMoveResizeGeometry(moveResizeGeom);
- }
- }
-
- // Always obey size hints, even when in "unrestricted" mode
- QSize size = adjustedSize(moveResizeGeometry().size(), sizemode);
- // the new topleft and bottomright corners (after checking size constrains), if they'll be needed
- topleft = QPoint(moveResizeGeometry().right() - size.width() + 1, moveResizeGeometry().bottom() - size.height() + 1);
- bottomright = QPoint(moveResizeGeometry().left() + size.width() - 1, moveResizeGeometry().top() + size.height() - 1);
- orig = moveResizeGeometry();
-
- // if aspect ratios are specified, both dimensions may change.
- // Therefore grow to the right/bottom if needed.
- // TODO it should probably obey gravity rather than always using right/bottom ?
- if (sizemode == SizemodeFixedH)
- orig.setRight(bottomright.x());
- else if (sizemode == SizemodeFixedW)
- orig.setBottom(bottomright.y());
-
- calculateMoveResizeGeom();
-
- if (moveResizeGeometry().size() != previousMoveResizeGeom.size())
- update = true;
- } else if (isMove()) {
- Q_ASSERT(mode == PositionCenter);
- if (!isMovable()) { // isMovableAcrossScreens() must have been true to get here
- // Special moving of maximized windows on Xinerama screens
- int screen = screens()->number(globalPos);
- if (isFullScreen())
- setMoveResizeGeometry(workspace()->clientArea(FullScreenArea, screen, 0));
- else {
- QRect moveResizeGeom = workspace()->clientArea(MaximizeArea, screen, 0);
- QSize adjSize = adjustedSize(moveResizeGeom.size(), SizemodeMax);
- if (adjSize != moveResizeGeom.size()) {
- QRect r(moveResizeGeom);
- moveResizeGeom.setSize(adjSize);
- moveResizeGeom.moveCenter(r.center());
- }
- setMoveResizeGeometry(moveResizeGeom);
- }
- } else {
- // first move, then snap, then check bounds
- QRect moveResizeGeom = moveResizeGeometry();
- moveResizeGeom.moveTopLeft(topleft);
- moveResizeGeom.moveTopLeft(workspace()->adjustClientPosition(this, moveResizeGeom.topLeft(),
- isUnrestrictedMoveResize()));
- setMoveResizeGeometry(moveResizeGeom);
-
- if (!isUnrestrictedMoveResize()) {
- const QRegion strut = workspace()->restrictedMoveArea(desktop()); // Strut areas
- QRegion availableArea(workspace()->clientArea(FullArea, -1, 0)); // On the screen
- availableArea -= strut; // Strut areas
- bool transposed = false;
- int requiredPixels;
- QRect bTitleRect = titleBarRect(transposed, requiredPixels);
- for (;;) {
- QRect moveResizeGeom = moveResizeGeometry();
- const QRect titleRect(bTitleRect.translated(moveResizeGeom.topLeft()));
- int visiblePixels = 0;
- for (const QRect &rect : availableArea) {
- const QRect r = rect & titleRect;
- if ((transposed && r.width() == titleRect.width()) || // Only the full size regions...
- (!transposed && r.height() == titleRect.height())) // ...prevents long slim areas
- visiblePixels += r.width() * r.height();
- }
- if (visiblePixels >= requiredPixels)
- break; // We have reached a valid position
-
- // (esp.) if there're more screens with different struts (panels) it the titlebar
- // will be movable outside the movearea (covering one of the panels) until it
- // crosses the panel "too much" (not enough visiblePixels) and then stucks because
- // it's usually only pushed by 1px to either direction
- // so we first check whether we intersect suc strut and move the window below it
- // immediately (it's still possible to hit the visiblePixels >= titlebarArea break
- // by moving the window slightly downwards, but it won't stuck)
- // see bug #274466
- // and bug #301805 for why we can't just match the titlearea against the screen
- if (screens()->count() > 1) { // optimization
- // TODO: could be useful on partial screen struts (half-width panels etc.)
- int newTitleTop = -1;
- for (const QRect &r : strut) {
- if (r.top() == 0 && r.width() > r.height() && // "top panel"
- r.intersects(moveResizeGeom) && moveResizeGeom.top() < r.bottom()) {
- newTitleTop = r.bottom() + 1;
- break;
- }
- }
- if (newTitleTop > -1) {
- moveResizeGeom.moveTop(newTitleTop); // invalid position, possibly on screen change
- setMoveResizeGeometry(moveResizeGeom);
- break;
- }
- }
-
- int dx = sign(previousMoveResizeGeom.x() - moveResizeGeom.x()),
- dy = sign(previousMoveResizeGeom.y() - moveResizeGeom.y());
- if (visiblePixels && dx) // means there's no full width cap -> favor horizontally
- dy = 0;
- else if (dy)
- dx = 0;
-
- // Move it back
- moveResizeGeom.translate(dx, dy);
- setMoveResizeGeometry(moveResizeGeom);
-
- if (moveResizeGeom == previousMoveResizeGeom) {
- break; // Prevent lockup
- }
- }
- }
- }
- if (moveResizeGeometry().topLeft() != previousMoveResizeGeom.topLeft())
- update = true;
- } else
- abort();
-
- if (!update)
- return;
-
- if (isResize() && !haveResizeEffect()) {
- doResizeSync();
- } else
- performMoveResize();
-
- if (isMove()) {
- ScreenEdges::self()->check(globalPos, QDateTime::fromMSecsSinceEpoch(xTime()));
- }
-}
-
-void X11Client::doResizeSync()
-{
- if (!syncRequest.timeout) {
- syncRequest.timeout = new QTimer(this);
- connect(syncRequest.timeout, &QTimer::timeout, this, &X11Client::performMoveResize);
- syncRequest.timeout->setSingleShot(true);
- }
- if (syncRequest.counter != XCB_NONE) {
- syncRequest.timeout->start(250);
- sendSyncRequest();
- } else { // for clients not supporting the XSYNC protocol, we
- syncRequest.isPending = true; // limit the resizes to 30Hz to take pointless load from X11
- syncRequest.timeout->start(33); // and the client, the mouse is still moved at full speed
- } // and no human can control faster resizes anyway
- const QRect moveResizeClientGeometry = frameRectToClientRect(moveResizeGeometry());
- m_client.setGeometry(0, 0, moveResizeClientGeometry.width(), moveResizeClientGeometry.height());
-}
-
-void AbstractClient::performMoveResize()
-{
- const QRect &moveResizeGeom = moveResizeGeometry();
- if (isMove() || (isResize() && !haveResizeEffect())) {
- setFrameGeometry(moveResizeGeom);
- }
- doPerformMoveResize();
- if (isResize())
- addRepaintFull();
- positionGeometryTip();
- emit clientStepUserMovedResized(this, moveResizeGeom);
-}
-
-void X11Client::doPerformMoveResize()
-{
- if (syncRequest.counter == XCB_NONE) // client w/o XSYNC support. allow the next resize event
- syncRequest.isPending = false; // NEVER do this for clients with a valid counter
- // (leads to sync request races in some clients)
-}
-
-void AbstractClient::setElectricBorderMode(QuickTileMode mode)
-{
- if (mode != QuickTileMode(QuickTileFlag::Maximize)) {
- // sanitize the mode, ie. simplify "invalid" combinations
- if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Horizontal))
- mode &= ~QuickTileMode(QuickTileFlag::Horizontal);
- if ((mode & QuickTileFlag::Vertical) == QuickTileMode(QuickTileFlag::Vertical))
- mode &= ~QuickTileMode(QuickTileFlag::Vertical);
- }
- m_electricMode = mode;
-}
-
-void AbstractClient::setElectricBorderMaximizing(bool maximizing)
-{
- m_electricMaximizing = maximizing;
- if (maximizing)
- outline()->show(electricBorderMaximizeGeometry(Cursor::pos(), desktop()), moveResizeGeometry());
- else
- outline()->hide();
- elevate(maximizing);
-}
-
-QRect AbstractClient::electricBorderMaximizeGeometry(QPoint pos, int desktop)
-{
- if (electricBorderMode() == QuickTileMode(QuickTileFlag::Maximize)) {
- if (maximizeMode() == MaximizeFull)
- return geometryRestore();
- else
- return workspace()->clientArea(MaximizeArea, pos, desktop);
- }
-
- QRect ret = workspace()->clientArea(MaximizeArea, pos, desktop);
- if (electricBorderMode() & QuickTileFlag::Left)
- ret.setRight(ret.left()+ret.width()/2 - 1);
- else if (electricBorderMode() & QuickTileFlag::Right)
- ret.setLeft(ret.right()-(ret.width()-ret.width()/2) + 1);
- if (electricBorderMode() & QuickTileFlag::Top)
- ret.setBottom(ret.top()+ret.height()/2 - 1);
- else if (electricBorderMode() & QuickTileFlag::Bottom)
- ret.setTop(ret.bottom()-(ret.height()-ret.height()/2) + 1);
-
- return ret;
-}
-
-void AbstractClient::setQuickTileMode(QuickTileMode mode, bool keyboard)
-{
- // Only allow quick tile on a regular window.
- if (!isResizable()) {
- return;
- }
-
- workspace()->updateFocusMousePosition(Cursor::pos()); // may cause leave event
-
- GeometryUpdatesBlocker blocker(this);
-
- if (mode == QuickTileMode(QuickTileFlag::Maximize)) {
- m_quickTileMode = int(QuickTileFlag::None);
- if (maximizeMode() == MaximizeFull) {
- setMaximize(false, false);
- } else {
- QRect prev_geom_restore = geometryRestore(); // setMaximize() would set moveResizeGeom as geom_restore
- m_quickTileMode = int(QuickTileFlag::Maximize);
- setMaximize(true, true);
- QRect clientArea = workspace()->clientArea(MaximizeArea, this);
- if (frameGeometry().top() != clientArea.top()) {
- QRect r(frameGeometry());
- r.moveTop(clientArea.top());
- setFrameGeometry(r);
- }
- setGeometryRestore(prev_geom_restore);
- }
- emit quickTileModeChanged();
- return;
- }
-
- // sanitize the mode, ie. simplify "invalid" combinations
- if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Horizontal))
- mode &= ~QuickTileMode(QuickTileFlag::Horizontal);
- if ((mode & QuickTileFlag::Vertical) == QuickTileMode(QuickTileFlag::Vertical))
- mode &= ~QuickTileMode(QuickTileFlag::Vertical);
-
- setElectricBorderMode(mode); // used by ::electricBorderMaximizeGeometry(.)
-
- // restore from maximized so that it is possible to tile maximized windows with one hit or by dragging
- if (maximizeMode() != MaximizeRestore) {
-
- if (mode != QuickTileMode(QuickTileFlag::None)) {
- // decorations may turn off some borders when tiled
- const ForceGeometry_t geom_mode = isDecorated() ? ForceGeometrySet : NormalGeometrySet;
- m_quickTileMode = int(QuickTileFlag::None); // Temporary, so the maximize code doesn't get all confused
-
- setMaximize(false, false);
-
- setFrameGeometry(electricBorderMaximizeGeometry(keyboard ? frameGeometry().center() : Cursor::pos(), desktop()), geom_mode);
- // Store the mode change
- m_quickTileMode = mode;
- } else {
- m_quickTileMode = mode;
- setMaximize(false, false);
- }
-
- emit quickTileModeChanged();
-
- return;
- }
-
- if (mode != QuickTileMode(QuickTileFlag::None)) {
- QPoint whichScreen = keyboard ? frameGeometry().center() : Cursor::pos();
-
- // If trying to tile to the side that the window is already tiled to move the window to the next
- // screen if it exists, otherwise toggle the mode (set QuickTileFlag::None)
- if (quickTileMode() == mode) {
- const int numScreens = screens()->count();
- const int curScreen = screen();
- int nextScreen = curScreen;
- QVarLengthArray screens(numScreens);
- for (int i = 0; i < numScreens; ++i) // Cache
- screens[i] = Screens::self()->geometry(i);
- for (int i = 0; i < numScreens; ++i) {
-
- if (i == curScreen)
- continue;
-
- if (screens[i].bottom() <= screens[curScreen].top() || screens[i].top() >= screens[curScreen].bottom())
- continue; // not in horizontal line
-
- const int x = screens[i].center().x();
- if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Left)) {
- if (x >= screens[curScreen].center().x() || (curScreen != nextScreen && x <= screens[nextScreen].center().x()))
- continue; // not left of current or more left then found next
- } else if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Right)) {
- if (x <= screens[curScreen].center().x() || (curScreen != nextScreen && x >= screens[nextScreen].center().x()))
- continue; // not right of current or more right then found next
- }
-
- nextScreen = i;
- }
-
- if (nextScreen == curScreen) {
- mode = QuickTileFlag::None; // No other screens, toggle tiling
- } else {
- // Move to other screen
- setFrameGeometry(geometryRestore().translated(screens[nextScreen].topLeft() - screens[curScreen].topLeft()));
- whichScreen = screens[nextScreen].center();
-
- // Swap sides
- if (mode & QuickTileFlag::Horizontal) {
- mode = (~mode & QuickTileFlag::Horizontal) | (mode & QuickTileFlag::Vertical);
- }
- }
- setElectricBorderMode(mode); // used by ::electricBorderMaximizeGeometry(.)
- } else if (quickTileMode() == QuickTileMode(QuickTileFlag::None)) {
- // Not coming out of an existing tile, not shifting monitors, we're setting a brand new tile.
- // Store geometry first, so we can go out of this tile later.
- setGeometryRestore(frameGeometry());
- }
-
- if (mode != QuickTileMode(QuickTileFlag::None)) {
- m_quickTileMode = mode;
- // decorations may turn off some borders when tiled
- const ForceGeometry_t geom_mode = isDecorated() ? ForceGeometrySet : NormalGeometrySet;
- // Temporary, so the maximize code doesn't get all confused
- m_quickTileMode = int(QuickTileFlag::None);
- setFrameGeometry(electricBorderMaximizeGeometry(whichScreen, desktop()), geom_mode);
- }
-
- // Store the mode change
- m_quickTileMode = mode;
- }
-
- if (mode == QuickTileMode(QuickTileFlag::None)) {
- m_quickTileMode = int(QuickTileFlag::None);
- // Untiling, so just restore geometry, and we're done.
- if (!geometryRestore().isValid()) // invalid if we started maximized and wait for placement
- setGeometryRestore(frameGeometry());
- // decorations may turn off some borders when tiled
- const ForceGeometry_t geom_mode = isDecorated() ? ForceGeometrySet : NormalGeometrySet;
- setFrameGeometry(geometryRestore(), geom_mode);
- checkWorkspacePosition(); // Just in case it's a different screen
- }
- emit quickTileModeChanged();
-}
-
-void AbstractClient::sendToScreen(int newScreen)
-{
- newScreen = rules()->checkScreen(newScreen);
- if (isActive()) {
- screens()->setCurrent(newScreen);
- // might impact the layer of a fullscreen window
- foreach (AbstractClient *cc, workspace()->allClientList()) {
- if (cc->isFullScreen() && cc->screen() == newScreen) {
- cc->updateLayer();
- }
- }
- }
- if (screen() == newScreen) // Don't use isOnScreen(), that's true even when only partially
- return;
-
- GeometryUpdatesBlocker blocker(this);
-
- // operating on the maximized / quicktiled window would leave the old geom_restore behind,
- // so we clear the state first
- MaximizeMode maxMode = maximizeMode();
- QuickTileMode qtMode = quickTileMode();
- if (maxMode != MaximizeRestore)
- maximize(MaximizeRestore);
- if (qtMode != QuickTileMode(QuickTileFlag::None))
- setQuickTileMode(QuickTileFlag::None, true);
-
- QRect oldScreenArea = workspace()->clientArea(MaximizeArea, this);
- QRect screenArea = workspace()->clientArea(MaximizeArea, newScreen, desktop());
-
- // the window can have its center so that the position correction moves the new center onto
- // the old screen, what will tile it where it is. Ie. the screen is not changed
- // this happens esp. with electric border quicktiling
- if (qtMode != QuickTileMode(QuickTileFlag::None))
- keepInArea(oldScreenArea);
-
- QRect oldGeom = frameGeometry();
- QRect newGeom = oldGeom;
- // move the window to have the same relative position to the center of the screen
- // (i.e. one near the middle of the right edge will also end up near the middle of the right edge)
- QPoint center = newGeom.center() - oldScreenArea.center();
- center.setX(center.x() * screenArea.width() / oldScreenArea.width());
- center.setY(center.y() * screenArea.height() / oldScreenArea.height());
- center += screenArea.center();
- newGeom.moveCenter(center);
- setFrameGeometry(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)) {
- keepInArea(screenArea);
- }
-
- // align geom_restore - checkWorkspacePosition operates on it
- setGeometryRestore(frameGeometry());
-
- checkWorkspacePosition(oldGeom);
-
- // re-align geom_restore to constrained geometry
- setGeometryRestore(frameGeometry());
-
- // finally reset special states
- // NOTICE that MaximizeRestore/QuickTileFlag::None checks are required.
- // eg. setting QuickTileFlag::None would break maximization
- if (maxMode != MaximizeRestore)
- maximize(maxMode);
- if (qtMode != QuickTileMode(QuickTileFlag::None) && qtMode != quickTileMode())
- setQuickTileMode(qtMode, true);
-
- auto tso = workspace()->ensureStackingOrder(transients());
- for (auto it = tso.constBegin(), end = tso.constEnd(); it != end; ++it)
- (*it)->sendToScreen(newScreen);
-}
-
-} // namespace
diff --git a/workspace.cpp b/workspace.cpp
index f434be5fa3..21f9a11f63 100644
--- a/workspace.cpp
+++ b/workspace.cpp
@@ -1938,4 +1938,844 @@ void Workspace::checkTransients(xcb_window_t w)
(*it)->checkTransient(w);
}
+/**
+ * Resizes the workspace after an XRANDR screen size change
+ */
+void Workspace::desktopResized()
+{
+ QRect geom = screens()->geometry();
+ if (rootInfo()) {
+ NETSize desktop_geometry;
+ desktop_geometry.width = geom.width();
+ desktop_geometry.height = geom.height();
+ rootInfo()->setDesktopGeometry(desktop_geometry);
+ }
+
+ updateClientArea();
+ saveOldScreenSizes(); // after updateClientArea(), so that one still uses the previous one
+
+ // TODO: emit a signal instead and remove the deep function calls into edges and effects
+ ScreenEdges::self()->recreateEdges();
+
+ if (effects) {
+ static_cast(effects)->desktopResized(geom.size());
+ }
+}
+
+void Workspace::saveOldScreenSizes()
+{
+ olddisplaysize = screens()->displaySize();
+ oldscreensizes.clear();
+ for( int i = 0;
+ i < screens()->count();
+ ++i )
+ oldscreensizes.append( screens()->geometry( i ));
+}
+
+/**
+ * Updates the current client areas according to the current clients.
+ *
+ * If the area changes or force is @c true, the new areas are propagated to the world.
+ *
+ * The client area is the area that is available for clients (that
+ * which is not taken by windows like panels, the top-of-screen menu
+ * etc).
+ *
+ * @see clientArea()
+ */
+void Workspace::updateClientArea(bool force)
+{
+ const Screens *s = Screens::self();
+ int nscreens = s->count();
+ const int numberOfDesktops = VirtualDesktopManager::self()->count();
+ QVector< QRect > new_wareas(numberOfDesktops + 1);
+ QVector< StrutRects > new_rmoveareas(numberOfDesktops + 1);
+ QVector< QVector< QRect > > new_sareas(numberOfDesktops + 1);
+ QVector< QRect > screens(nscreens);
+ QRect desktopArea;
+ for (int i = 0; i < nscreens; i++) {
+ desktopArea |= s->geometry(i);
+ }
+ for (int iS = 0;
+ iS < nscreens;
+ iS ++) {
+ screens [iS] = s->geometry(iS);
+ }
+ for (int i = 1;
+ i <= numberOfDesktops;
+ ++i) {
+ new_wareas[ i ] = desktopArea;
+ new_sareas[ i ].resize(nscreens);
+ for (int iS = 0;
+ iS < nscreens;
+ iS ++)
+ new_sareas[ i ][ iS ] = screens[ iS ];
+ }
+ for (auto it = clients.constBegin(); it != clients.constEnd(); ++it) {
+ if (!(*it)->hasStrut())
+ continue;
+ QRect r = (*it)->adjustedClientArea(desktopArea, desktopArea);
+ // sanity check that a strut doesn't exclude a complete screen geometry
+ // this is a violation to EWMH, as KWin just ignores the strut
+ for (int i = 0; i < Screens::self()->count(); i++) {
+ if (!r.intersects(Screens::self()->geometry(i))) {
+ qCDebug(KWIN_CORE) << "Adjusted client area would exclude a complete screen, ignore";
+ r = desktopArea;
+ break;
+ }
+ }
+ StrutRects strutRegion = (*it)->strutRects();
+ const QRect clientsScreenRect = KWin::screens()->geometry((*it)->screen());
+ for (auto strut = strutRegion.begin(); strut != strutRegion.end(); strut++) {
+ *strut = StrutRect((*strut).intersected(clientsScreenRect), (*strut).area());
+ }
+
+ // Ignore offscreen xinerama struts. These interfere with the larger monitors on the setup
+ // and should be ignored so that applications that use the work area to work out where
+ // windows can go can use the entire visible area of the larger monitors.
+ // This goes against the EWMH description of the work area but it is a toss up between
+ // having unusable sections of the screen (Which can be quite large with newer monitors)
+ // or having some content appear offscreen (Relatively rare compared to other).
+ bool hasOffscreenXineramaStrut = (*it)->hasOffscreenXineramaStrut();
+
+ if ((*it)->isOnAllDesktops()) {
+ for (int i = 1;
+ i <= numberOfDesktops;
+ ++i) {
+ if (!hasOffscreenXineramaStrut)
+ new_wareas[ i ] = new_wareas[ i ].intersected(r);
+ new_rmoveareas[ i ] += strutRegion;
+ for (int iS = 0;
+ iS < nscreens;
+ iS ++) {
+ const auto geo = new_sareas[ i ][ iS ].intersected(
+ (*it)->adjustedClientArea(desktopArea, screens[ iS ]));
+ // ignore the geometry if it results in the screen getting removed completely
+ if (!geo.isEmpty()) {
+ new_sareas[ i ][ iS ] = geo;
+ }
+ }
+ }
+ } else {
+ if (!hasOffscreenXineramaStrut)
+ new_wareas[(*it)->desktop()] = new_wareas[(*it)->desktop()].intersected(r);
+ new_rmoveareas[(*it)->desktop()] += strutRegion;
+ for (int iS = 0;
+ iS < nscreens;
+ iS ++) {
+// qDebug() << "adjusting new_sarea: " << screens[ iS ];
+ const auto geo = new_sareas[(*it)->desktop()][ iS ].intersected(
+ (*it)->adjustedClientArea(desktopArea, screens[ iS ]));
+ // ignore the geometry if it results in the screen getting removed completely
+ if (!geo.isEmpty()) {
+ new_sareas[(*it)->desktop()][ iS ] = geo;
+ }
+ }
+ }
+ }
+ if (waylandServer()) {
+ auto updateStrutsForWaylandClient = [&] (XdgShellClient *c) {
+ // assuming that only docks have "struts" and that all docks have a strut
+ if (!c->hasStrut()) {
+ return;
+ }
+ auto margins = [c] (const QRect &geometry) {
+ QMargins margins;
+ if (!geometry.intersects(c->frameGeometry())) {
+ return margins;
+ }
+ // figure out which areas of the overall screen setup it borders
+ const bool left = c->frameGeometry().left() == geometry.left();
+ const bool right = c->frameGeometry().right() == geometry.right();
+ const bool top = c->frameGeometry().top() == geometry.top();
+ const bool bottom = c->frameGeometry().bottom() == geometry.bottom();
+ const bool horizontal = c->frameGeometry().width() >= c->frameGeometry().height();
+ if (left && ((!top && !bottom) || !horizontal)) {
+ margins.setLeft(c->frameGeometry().width());
+ }
+ if (right && ((!top && !bottom) || !horizontal)) {
+ margins.setRight(c->frameGeometry().width());
+ }
+ if (top && ((!left && !right) || horizontal)) {
+ margins.setTop(c->frameGeometry().height());
+ }
+ if (bottom && ((!left && !right) || horizontal)) {
+ margins.setBottom(c->frameGeometry().height());
+ }
+ return margins;
+ };
+ auto marginsToStrutArea = [] (const QMargins &margins) {
+ if (margins.left() != 0) {
+ return StrutAreaLeft;
+ }
+ if (margins.right() != 0) {
+ return StrutAreaRight;
+ }
+ if (margins.top() != 0) {
+ return StrutAreaTop;
+ }
+ if (margins.bottom() != 0) {
+ return StrutAreaBottom;
+ }
+ return StrutAreaInvalid;
+ };
+ const auto strut = margins(KWin::screens()->geometry(c->screen()));
+ const StrutRects strutRegion = StrutRects{StrutRect(c->frameGeometry(), marginsToStrutArea(strut))};
+ QRect r = desktopArea - margins(KWin::screens()->geometry());
+ if (c->isOnAllDesktops()) {
+ for (int i = 1; i <= numberOfDesktops; ++i) {
+ new_wareas[ i ] = new_wareas[ i ].intersected(r);
+ for (int iS = 0; iS < nscreens; ++iS) {
+ new_sareas[ i ][ iS ] = new_sareas[ i ][ iS ].intersected(screens[iS] - margins(screens[iS]));
+ }
+ new_rmoveareas[ i ] += strutRegion;
+ }
+ } else {
+ new_wareas[c->desktop()] = new_wareas[c->desktop()].intersected(r);
+ for (int iS = 0; iS < nscreens; iS++) {
+ new_sareas[c->desktop()][ iS ] = new_sareas[c->desktop()][ iS ].intersected(screens[iS] - margins(screens[iS]));
+ }
+ new_rmoveareas[ c->desktop() ] += strutRegion;
+ }
+ };
+ const auto clients = waylandServer()->clients();
+ for (auto c : clients) {
+ updateStrutsForWaylandClient(c);
+ }
+ }
+#if 0
+ for (int i = 1;
+ i <= numberOfDesktops();
+ ++i) {
+ for (int iS = 0;
+ iS < nscreens;
+ iS ++)
+ qCDebug(KWIN_CORE) << "new_sarea: " << new_sareas[ i ][ iS ];
+ }
+#endif
+
+ bool changed = force;
+
+ if (screenarea.isEmpty())
+ changed = true;
+
+ for (int i = 1;
+ !changed && i <= numberOfDesktops;
+ ++i) {
+ if (workarea[ i ] != new_wareas[ i ])
+ changed = true;
+ if (restrictedmovearea[ i ] != new_rmoveareas[ i ])
+ changed = true;
+ if (screenarea[ i ].size() != new_sareas[ i ].size())
+ changed = true;
+ for (int iS = 0;
+ !changed && iS < nscreens;
+ iS ++)
+ if (new_sareas[ i ][ iS ] != screenarea [ i ][ iS ])
+ changed = true;
+ }
+
+ if (changed) {
+ workarea = new_wareas;
+ oldrestrictedmovearea = restrictedmovearea;
+ restrictedmovearea = new_rmoveareas;
+ screenarea = new_sareas;
+ if (rootInfo()) {
+ NETRect r;
+ for (int i = 1; i <= numberOfDesktops; i++) {
+ r.pos.x = workarea[ i ].x();
+ r.pos.y = workarea[ i ].y();
+ r.size.width = workarea[ i ].width();
+ r.size.height = workarea[ i ].height();
+ rootInfo()->setWorkArea(i, r);
+ }
+ }
+
+ for (auto it = m_allClients.constBegin();
+ it != m_allClients.constEnd();
+ ++it)
+ (*it)->checkWorkspacePosition();
+
+ oldrestrictedmovearea.clear(); // reset, no longer valid or needed
+ }
+}
+
+void Workspace::updateClientArea()
+{
+ updateClientArea(false);
+}
+
+
+/**
+ * Returns the area available for clients. This is the desktop
+ * geometry minus windows on the dock. Placement algorithms should
+ * refer to this rather than Screens::geometry.
+ */
+QRect Workspace::clientArea(clientAreaOption opt, int screen, int desktop) const
+{
+ if (desktop == NETWinInfo::OnAllDesktops || desktop == 0)
+ desktop = VirtualDesktopManager::self()->current();
+ if (screen == -1)
+ screen = screens()->current();
+ const QSize displaySize = screens()->displaySize();
+
+ QRect sarea, warea;
+
+ if (is_multihead) {
+ sarea = (!screenarea.isEmpty()
+ && screen < screenarea[ desktop ].size()) // screens may be missing during KWin initialization or screen config changes
+ ? screenarea[ desktop ][ screen_number ]
+ : screens()->geometry(screen_number);
+ warea = workarea[ desktop ].isNull()
+ ? screens()->geometry(screen_number)
+ : workarea[ desktop ];
+ } else {
+ sarea = (!screenarea.isEmpty()
+ && screen < screenarea[ desktop ].size()) // screens may be missing during KWin initialization or screen config changes
+ ? screenarea[ desktop ][ screen ]
+ : screens()->geometry(screen);
+ warea = workarea[ desktop ].isNull()
+ ? QRect(0, 0, displaySize.width(), displaySize.height())
+ : workarea[ desktop ];
+ }
+
+ switch(opt) {
+ case MaximizeArea:
+ case PlacementArea:
+ return sarea;
+ case MaximizeFullArea:
+ case FullScreenArea:
+ case MovementArea:
+ case ScreenArea:
+ if (is_multihead)
+ return screens()->geometry(screen_number);
+ else
+ return screens()->geometry(screen);
+ case WorkArea:
+ if (is_multihead)
+ return sarea;
+ else
+ return warea;
+ case FullArea:
+ return QRect(0, 0, displaySize.width(), displaySize.height());
+ }
+ abort();
+}
+
+
+QRect Workspace::clientArea(clientAreaOption opt, const QPoint& p, int desktop) const
+{
+ return clientArea(opt, screens()->number(p), desktop);
+}
+
+QRect Workspace::clientArea(clientAreaOption opt, const AbstractClient* c) const
+{
+ return clientArea(opt, c->frameGeometry().center(), c->desktop());
+}
+
+QRegion Workspace::restrictedMoveArea(int desktop, StrutAreas areas) const
+{
+ if (desktop == NETWinInfo::OnAllDesktops || desktop == 0)
+ desktop = VirtualDesktopManager::self()->current();
+ QRegion region;
+ foreach (const StrutRect & rect, restrictedmovearea[desktop])
+ if (areas & rect.area())
+ region += rect;
+ return region;
+}
+
+bool Workspace::inUpdateClientArea() const
+{
+ return !oldrestrictedmovearea.isEmpty();
+}
+
+QRegion Workspace::previousRestrictedMoveArea(int desktop, StrutAreas areas) const
+{
+ if (desktop == NETWinInfo::OnAllDesktops || desktop == 0)
+ desktop = VirtualDesktopManager::self()->current();
+ QRegion region;
+ foreach (const StrutRect & rect, oldrestrictedmovearea.at(desktop))
+ if (areas & rect.area())
+ region += rect;
+ return region;
+}
+
+QVector< QRect > Workspace::previousScreenSizes() const
+{
+ return oldscreensizes;
+}
+
+int Workspace::oldDisplayWidth() const
+{
+ return olddisplaysize.width();
+}
+
+int Workspace::oldDisplayHeight() const
+{
+ return olddisplaysize.height();
+}
+
+/**
+ * Client \a c is moved around to position \a pos. This gives the
+ * workspace the opportunity to interveniate and to implement
+ * snap-to-windows functionality.
+ *
+ * The parameter \a snapAdjust is a multiplier used to calculate the
+ * effective snap zones. When 1.0, it means that the snap zones will be
+ * used without change.
+ */
+QPoint Workspace::adjustClientPosition(AbstractClient* c, QPoint pos, bool unrestricted, double snapAdjust)
+{
+ QSize borderSnapZone(options->borderSnapZone(), options->borderSnapZone());
+ QRect maxRect;
+ int guideMaximized = MaximizeRestore;
+ if (c->maximizeMode() != MaximizeRestore) {
+ maxRect = clientArea(MaximizeArea, pos + c->rect().center(), c->desktop());
+ QRect geo = c->frameGeometry();
+ if (c->maximizeMode() & MaximizeHorizontal && (geo.x() == maxRect.left() || geo.right() == maxRect.right())) {
+ guideMaximized |= MaximizeHorizontal;
+ borderSnapZone.setWidth(qMax(borderSnapZone.width() + 2, maxRect.width() / 16));
+ }
+ if (c->maximizeMode() & MaximizeVertical && (geo.y() == maxRect.top() || geo.bottom() == maxRect.bottom())) {
+ guideMaximized |= MaximizeVertical;
+ borderSnapZone.setHeight(qMax(borderSnapZone.height() + 2, maxRect.height() / 16));
+ }
+ }
+
+ if (options->windowSnapZone() || !borderSnapZone.isNull() || options->centerSnapZone()) {
+
+ const bool sOWO = options->isSnapOnlyWhenOverlapping();
+ const int screen = screens()->number(pos + c->rect().center());
+ if (maxRect.isNull())
+ maxRect = clientArea(MovementArea, screen, c->desktop());
+ const int xmin = maxRect.left();
+ const int xmax = maxRect.right() + 1; //desk size
+ const int ymin = maxRect.top();
+ const int ymax = maxRect.bottom() + 1;
+
+ const int cx(pos.x());
+ const int cy(pos.y());
+ const int cw(c->width());
+ const int ch(c->height());
+ const int rx(cx + cw);
+ const int ry(cy + ch); //these don't change
+
+ int nx(cx), ny(cy); //buffers
+ int deltaX(xmax);
+ int deltaY(ymax); //minimum distance to other clients
+
+ int lx, ly, lrx, lry; //coords and size for the comparison client, l
+
+ // border snap
+ const int snapX = borderSnapZone.width() * snapAdjust; //snap trigger
+ const int snapY = borderSnapZone.height() * snapAdjust;
+ if (snapX || snapY) {
+ QRect geo = c->frameGeometry();
+ QMargins frameMargins = c->frameMargins();
+
+ // snap to titlebar / snap to window borders on inner screen edges
+ AbstractClient::Position titlePos = c->titlebarPosition();
+ if (frameMargins.left() && (titlePos == AbstractClient::PositionLeft || (c->maximizeMode() & MaximizeHorizontal) ||
+ screens()->intersecting(geo.translated(maxRect.x() - (frameMargins.left() + geo.x()), 0)) > 1)) {
+ frameMargins.setLeft(0);
+ }
+ if (frameMargins.right() && (titlePos == AbstractClient::PositionRight || (c->maximizeMode() & MaximizeHorizontal) ||
+ screens()->intersecting(geo.translated(maxRect.right() + frameMargins.right() - geo.right(), 0)) > 1)) {
+ frameMargins.setRight(0);
+ }
+ if (frameMargins.top() && (titlePos == AbstractClient::PositionTop || (c->maximizeMode() & MaximizeVertical) ||
+ screens()->intersecting(geo.translated(0, maxRect.y() - (frameMargins.top() + geo.y()))) > 1)) {
+ frameMargins.setTop(0);
+ }
+ if (frameMargins.bottom() && (titlePos == AbstractClient::PositionBottom || (c->maximizeMode() & MaximizeVertical) ||
+ screens()->intersecting(geo.translated(0, maxRect.bottom() + frameMargins.bottom() - geo.bottom())) > 1)) {
+ frameMargins.setBottom(0);
+ }
+ if ((sOWO ? (cx < xmin) : true) && (qAbs(xmin - cx) < snapX)) {
+ deltaX = xmin - cx;
+ nx = xmin - frameMargins.left();
+ }
+ if ((sOWO ? (rx > xmax) : true) && (qAbs(rx - xmax) < snapX) && (qAbs(xmax - rx) < deltaX)) {
+ deltaX = rx - xmax;
+ nx = xmax - cw + frameMargins.right();
+ }
+
+ if ((sOWO ? (cy < ymin) : true) && (qAbs(ymin - cy) < snapY)) {
+ deltaY = ymin - cy;
+ ny = ymin - frameMargins.top();
+ }
+ if ((sOWO ? (ry > ymax) : true) && (qAbs(ry - ymax) < snapY) && (qAbs(ymax - ry) < deltaY)) {
+ deltaY = ry - ymax;
+ ny = ymax - ch + frameMargins.bottom();
+ }
+ }
+
+ // windows snap
+ int snap = options->windowSnapZone() * snapAdjust;
+ if (snap) {
+ for (auto l = m_allClients.constBegin(); l != m_allClients.constEnd(); ++l) {
+ if ((*l) == c)
+ continue;
+ if ((*l)->isMinimized())
+ continue; // is minimized
+ if (!(*l)->isShown(false))
+ continue;
+ if (!((*l)->isOnDesktop(c->desktop()) || c->isOnDesktop((*l)->desktop())))
+ continue; // wrong virtual desktop
+ if (!(*l)->isOnCurrentActivity())
+ continue; // wrong activity
+ if ((*l)->isDesktop() || (*l)->isSplash())
+ continue;
+
+ lx = (*l)->x();
+ ly = (*l)->y();
+ lrx = lx + (*l)->width();
+ lry = ly + (*l)->height();
+
+ if (!(guideMaximized & MaximizeHorizontal) &&
+ (((cy <= lry) && (cy >= ly)) || ((ry >= ly) && (ry <= lry)) || ((cy <= ly) && (ry >= lry)))) {
+ if ((sOWO ? (cx < lrx) : true) && (qAbs(lrx - cx) < snap) && (qAbs(lrx - cx) < deltaX)) {
+ deltaX = qAbs(lrx - cx);
+ nx = lrx;
+ }
+ if ((sOWO ? (rx > lx) : true) && (qAbs(rx - lx) < snap) && (qAbs(rx - lx) < deltaX)) {
+ deltaX = qAbs(rx - lx);
+ nx = lx - cw;
+ }
+ }
+
+ if (!(guideMaximized & MaximizeVertical) &&
+ (((cx <= lrx) && (cx >= lx)) || ((rx >= lx) && (rx <= lrx)) || ((cx <= lx) && (rx >= lrx)))) {
+ if ((sOWO ? (cy < lry) : true) && (qAbs(lry - cy) < snap) && (qAbs(lry - cy) < deltaY)) {
+ deltaY = qAbs(lry - cy);
+ ny = lry;
+ }
+ //if ( (qAbs( ry-ly ) < snap) && (qAbs( ry - ly ) < deltaY ))
+ if ((sOWO ? (ry > ly) : true) && (qAbs(ry - ly) < snap) && (qAbs(ry - ly) < deltaY)) {
+ deltaY = qAbs(ry - ly);
+ ny = ly - ch;
+ }
+ }
+
+ // Corner snapping
+ if (!(guideMaximized & MaximizeVertical) && (nx == lrx || nx + cw == lx)) {
+ if ((sOWO ? (ry > lry) : true) && (qAbs(lry - ry) < snap) && (qAbs(lry - ry) < deltaY)) {
+ deltaY = qAbs(lry - ry);
+ ny = lry - ch;
+ }
+ if ((sOWO ? (cy < ly) : true) && (qAbs(cy - ly) < snap) && (qAbs(cy - ly) < deltaY)) {
+ deltaY = qAbs(cy - ly);
+ ny = ly;
+ }
+ }
+ if (!(guideMaximized & MaximizeHorizontal) && (ny == lry || ny + ch == ly)) {
+ if ((sOWO ? (rx > lrx) : true) && (qAbs(lrx - rx) < snap) && (qAbs(lrx - rx) < deltaX)) {
+ deltaX = qAbs(lrx - rx);
+ nx = lrx - cw;
+ }
+ if ((sOWO ? (cx < lx) : true) && (qAbs(cx - lx) < snap) && (qAbs(cx - lx) < deltaX)) {
+ deltaX = qAbs(cx - lx);
+ nx = lx;
+ }
+ }
+ }
+ }
+
+ // center snap
+ snap = options->centerSnapZone() * snapAdjust; //snap trigger
+ if (snap) {
+ int diffX = qAbs((xmin + xmax) / 2 - (cx + cw / 2));
+ int diffY = qAbs((ymin + ymax) / 2 - (cy + ch / 2));
+ if (diffX < snap && diffY < snap && diffX < deltaX && diffY < deltaY) {
+ // Snap to center of screen
+ nx = (xmin + xmax) / 2 - cw / 2;
+ ny = (ymin + ymax) / 2 - ch / 2;
+ } else if (options->borderSnapZone()) {
+ // Enhance border snap
+ if ((nx == xmin || nx == xmax - cw) && diffY < snap && diffY < deltaY) {
+ // Snap to vertical center on screen edge
+ ny = (ymin + ymax) / 2 - ch / 2;
+ } else if (((unrestricted ? ny == ymin : ny <= ymin) || ny == ymax - ch) &&
+ diffX < snap && diffX < deltaX) {
+ // Snap to horizontal center on screen edge
+ nx = (xmin + xmax) / 2 - cw / 2;
+ }
+ }
+ }
+
+ pos = QPoint(nx, ny);
+ }
+ return pos;
+}
+
+QRect Workspace::adjustClientSize(AbstractClient* c, QRect moveResizeGeom, int mode)
+{
+ //adapted from adjustClientPosition on 29May2004
+ //this function is called when resizing a window and will modify
+ //the new dimensions to snap to other windows/borders if appropriate
+ if (options->windowSnapZone() || options->borderSnapZone()) { // || options->centerSnapZone )
+ const bool sOWO = options->isSnapOnlyWhenOverlapping();
+
+ const QRect maxRect = clientArea(MovementArea, c->rect().center(), c->desktop());
+ const int xmin = maxRect.left();
+ const int xmax = maxRect.right(); //desk size
+ const int ymin = maxRect.top();
+ const int ymax = maxRect.bottom();
+
+ const int cx(moveResizeGeom.left());
+ const int cy(moveResizeGeom.top());
+ const int rx(moveResizeGeom.right());
+ const int ry(moveResizeGeom.bottom());
+
+ int newcx(cx), newcy(cy); //buffers
+ int newrx(rx), newry(ry);
+ int deltaX(xmax);
+ int deltaY(ymax); //minimum distance to other clients
+
+ int lx, ly, lrx, lry; //coords and size for the comparison client, l
+
+ // border snap
+ int snap = options->borderSnapZone(); //snap trigger
+ if (snap) {
+ deltaX = int(snap);
+ deltaY = int(snap);
+
+#define SNAP_BORDER_TOP \
+ if ((sOWO?(newcyymax):true) && (qAbs(ymax-newry)xmax):true) && (qAbs(xmax-newrx)windowSnapZone();
+ if (snap) {
+ deltaX = int(snap);
+ deltaY = int(snap);
+ for (auto l = m_allClients.constBegin(); l != m_allClients.constEnd(); ++l) {
+ if ((*l)->isOnDesktop(VirtualDesktopManager::self()->current()) &&
+ !(*l)->isMinimized()
+ && (*l) != c) {
+ lx = (*l)->x() - 1;
+ ly = (*l)->y() - 1;
+ lrx = (*l)->x() + (*l)->width();
+ lry = (*l)->y() + (*l)->height();
+
+#define WITHIN_HEIGHT ((( newcy <= lry ) && ( newcy >= ly )) || \
+ (( newry >= ly ) && ( newry <= lry )) || \
+ (( newcy <= ly ) && ( newry >= lry )) )
+
+#define WITHIN_WIDTH ( (( cx <= lrx ) && ( cx >= lx )) || \
+ (( rx >= lx ) && ( rx <= lrx )) || \
+ (( cx <= lx ) && ( rx >= lrx )) )
+
+#define SNAP_WINDOW_TOP if ( (sOWO?(newcyly):true) \
+ && WITHIN_WIDTH \
+ && (qAbs( ly - newry ) < deltaY) ) { \
+ deltaY = qAbs( ly - newry ); \
+ newry=ly; \
+}
+
+#define SNAP_WINDOW_LEFT if ( (sOWO?(newcxlx):true) \
+ && WITHIN_HEIGHT \
+ && (qAbs( lx - newrx ) < deltaX)) \
+{ \
+ deltaX = qAbs( lx - newrx ); \
+ newrx=lx; \
+}
+
+#define SNAP_WINDOW_C_TOP if ( (sOWO?(newcylry):true) \
+ && (newcx == lrx || newrx == lx) \
+ && qAbs(lry-newry) < deltaY ) { \
+ deltaY = qAbs( lry - newry - 1 ); \
+ newry = lry - 1; \
+}
+
+#define SNAP_WINDOW_C_LEFT if ( (sOWO?(newcxlrx):true) \
+ && (newcy == lry || newry == ly) \
+ && qAbs(lrx-newrx) < deltaX ) { \
+ deltaX = qAbs( lrx - newrx - 1 ); \
+ newrx = lrx - 1; \
+}
+
+ switch(mode) {
+ case AbstractClient::PositionBottomRight:
+ SNAP_WINDOW_BOTTOM
+ SNAP_WINDOW_RIGHT
+ SNAP_WINDOW_C_BOTTOM
+ SNAP_WINDOW_C_RIGHT
+ break;
+ case AbstractClient::PositionRight:
+ SNAP_WINDOW_RIGHT
+ SNAP_WINDOW_C_RIGHT
+ break;
+ case AbstractClient::PositionBottom:
+ SNAP_WINDOW_BOTTOM
+ SNAP_WINDOW_C_BOTTOM
+ break;
+ case AbstractClient::PositionTopLeft:
+ SNAP_WINDOW_TOP
+ SNAP_WINDOW_LEFT
+ SNAP_WINDOW_C_TOP
+ SNAP_WINDOW_C_LEFT
+ break;
+ case AbstractClient::PositionLeft:
+ SNAP_WINDOW_LEFT
+ SNAP_WINDOW_C_LEFT
+ break;
+ case AbstractClient::PositionTop:
+ SNAP_WINDOW_TOP
+ SNAP_WINDOW_C_TOP
+ break;
+ case AbstractClient::PositionTopRight:
+ SNAP_WINDOW_TOP
+ SNAP_WINDOW_RIGHT
+ SNAP_WINDOW_C_TOP
+ SNAP_WINDOW_C_RIGHT
+ break;
+ case AbstractClient::PositionBottomLeft:
+ SNAP_WINDOW_BOTTOM
+ SNAP_WINDOW_LEFT
+ SNAP_WINDOW_C_BOTTOM
+ SNAP_WINDOW_C_LEFT
+ break;
+ default:
+ abort();
+ break;
+ }
+ }
+ }
+ }
+
+ // center snap
+ //snap = options->centerSnapZone;
+ //if (snap)
+ // {
+ // // Don't resize snap to center as it interferes too much
+ // // There are two ways of implementing this if wanted:
+ // // 1) Snap only to the same points that the move snap does, and
+ // // 2) Snap to the horizontal and vertical center lines of the screen
+ // }
+
+ moveResizeGeom = QRect(QPoint(newcx, newcy), QPoint(newrx, newry));
+ }
+ return moveResizeGeom;
+}
+
+/**
+ * Marks the client as being moved or resized by the user.
+ */
+void Workspace::setMoveResizeClient(AbstractClient *c)
+{
+ Q_ASSERT(!c || !movingClient); // Catch attempts to move a second
+ // window while still moving the first one.
+ movingClient = c;
+ if (movingClient)
+ ++block_focus;
+ else
+ --block_focus;
+}
+
+// When kwin crashes, windows will not be gravitated back to their original position
+// and will remain offset by the size of the decoration. So when restarting, fix this
+// (the property with the size of the frame remains on the window after the crash).
+void Workspace::fixPositionAfterCrash(xcb_window_t w, const xcb_get_geometry_reply_t *geometry)
+{
+ NETWinInfo i(connection(), w, rootWindow(), NET::WMFrameExtents, NET::Properties2());
+ NETStrut frame = i.frameExtents();
+
+ if (frame.left != 0 || frame.top != 0) {
+ // left and top needed due to narrowing conversations restrictions in C++11
+ const uint32_t left = frame.left;
+ const uint32_t top = frame.top;
+ const uint32_t values[] = { geometry->x - left, geometry->y - top };
+ xcb_configure_window(connection(), w, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, values);
+ }
+}
+
} // namespace
diff --git a/x11client.cpp b/x11client.cpp
index 30e62711fe..3e645e84bd 100644
--- a/x11client.cpp
+++ b/x11client.cpp
@@ -29,7 +29,9 @@ along with this program. If not, see .
#include "composite.h"
#include "cursor.h"
#include "deleted.h"
+#include "effects.h"
#include "focuschain.h"
+#include "geometrytip.h"
#include "group.h"
#include "netinfo.h"
#include "screens.h"
@@ -3542,5 +3544,1404 @@ void X11Client::checkActiveModal()
}
}
-} // namespace
+/**
+ * Calculate the appropriate frame size for the given client size \a
+ * wsize.
+ *
+ * \a wsize is adapted according to the window's size hints (minimum,
+ * maximum and incremental size changes).
+ */
+QSize X11Client::sizeForClientSize(const QSize& wsize, Sizemode mode, bool noframe) const
+{
+ int w = wsize.width();
+ int h = wsize.height();
+ if (w < 1 || h < 1) {
+ qCWarning(KWIN_CORE) << "sizeForClientSize() with empty size!" ;
+ }
+ if (w < 1) w = 1;
+ if (h < 1) h = 1;
+ // basesize, minsize, maxsize, paspect and resizeinc have all values defined,
+ // even if they're not set in flags - see getWmNormalHints()
+ QSize min_size = minSize();
+ QSize max_size = maxSize();
+ if (isDecorated()) {
+ QSize decominsize(0, 0);
+ QSize border_size(borderLeft() + borderRight(), borderTop() + borderBottom());
+ if (border_size.width() > decominsize.width()) // just in case
+ decominsize.setWidth(border_size.width());
+ if (border_size.height() > decominsize.height())
+ decominsize.setHeight(border_size.height());
+ if (decominsize.width() > min_size.width())
+ min_size.setWidth(decominsize.width());
+ if (decominsize.height() > min_size.height())
+ min_size.setHeight(decominsize.height());
+ }
+ w = qMin(max_size.width(), w);
+ h = qMin(max_size.height(), h);
+ w = qMax(min_size.width(), w);
+ h = qMax(min_size.height(), h);
+
+ int w1 = w;
+ int h1 = h;
+ int width_inc = m_geometryHints.resizeIncrements().width();
+ int height_inc = m_geometryHints.resizeIncrements().height();
+ int basew_inc = m_geometryHints.baseSize().width();
+ int baseh_inc = m_geometryHints.baseSize().height();
+ if (!m_geometryHints.hasBaseSize()) {
+ basew_inc = m_geometryHints.minSize().width();
+ baseh_inc = m_geometryHints.minSize().height();
+ }
+ w = int((w - basew_inc) / width_inc) * width_inc + basew_inc;
+ h = int((h - baseh_inc) / height_inc) * height_inc + baseh_inc;
+// code for aspect ratios based on code from FVWM
+ /*
+ * The math looks like this:
+ *
+ * minAspectX dwidth maxAspectX
+ * ---------- <= ------- <= ----------
+ * minAspectY dheight maxAspectY
+ *
+ * If that is multiplied out, then the width and height are
+ * invalid in the following situations:
+ *
+ * minAspectX * dheight > minAspectY * dwidth
+ * maxAspectX * dheight < maxAspectY * dwidth
+ *
+ */
+ if (m_geometryHints.hasAspect()) {
+ double min_aspect_w = m_geometryHints.minAspect().width(); // use doubles, because the values can be MAX_INT
+ double min_aspect_h = m_geometryHints.minAspect().height(); // and multiplying would go wrong otherwise
+ double max_aspect_w = m_geometryHints.maxAspect().width();
+ double max_aspect_h = m_geometryHints.maxAspect().height();
+ // According to ICCCM 4.1.2.3 PMinSize should be a fallback for PBaseSize for size increments,
+ // but not for aspect ratio. Since this code comes from FVWM, handles both at the same time,
+ // and I have no idea how it works, let's hope nobody relies on that.
+ const QSize baseSize = m_geometryHints.baseSize();
+ w -= baseSize.width();
+ h -= baseSize.height();
+ int max_width = max_size.width() - baseSize.width();
+ int min_width = min_size.width() - baseSize.width();
+ int max_height = max_size.height() - baseSize.height();
+ int min_height = min_size.height() - baseSize.height();
+#define ASPECT_CHECK_GROW_W \
+ if ( min_aspect_w * h > min_aspect_h * w ) \
+ { \
+ int delta = int( min_aspect_w * h / min_aspect_h - w ) / width_inc * width_inc; \
+ if ( w + delta <= max_width ) \
+ w += delta; \
+ }
+#define ASPECT_CHECK_SHRINK_H_GROW_W \
+ if ( min_aspect_w * h > min_aspect_h * w ) \
+ { \
+ int delta = int( h - w * min_aspect_h / min_aspect_w ) / height_inc * height_inc; \
+ if ( h - delta >= min_height ) \
+ h -= delta; \
+ else \
+ { \
+ int delta = int( min_aspect_w * h / min_aspect_h - w ) / width_inc * width_inc; \
+ if ( w + delta <= max_width ) \
+ w += delta; \
+ } \
+ }
+#define ASPECT_CHECK_GROW_H \
+ if ( max_aspect_w * h < max_aspect_h * w ) \
+ { \
+ int delta = int( w * max_aspect_h / max_aspect_w - h ) / height_inc * height_inc; \
+ if ( h + delta <= max_height ) \
+ h += delta; \
+ }
+#define ASPECT_CHECK_SHRINK_W_GROW_H \
+ if ( max_aspect_w * h < max_aspect_h * w ) \
+ { \
+ int delta = int( w - max_aspect_w * h / max_aspect_h ) / width_inc * width_inc; \
+ if ( w - delta >= min_width ) \
+ w -= delta; \
+ else \
+ { \
+ int delta = int( w * max_aspect_h / max_aspect_w - h ) / height_inc * height_inc; \
+ if ( h + delta <= max_height ) \
+ h += delta; \
+ } \
+ }
+ switch(mode) {
+ case SizemodeAny:
+#if 0 // make SizemodeAny equal to SizemodeFixedW - prefer keeping fixed width,
+ // so that changing aspect ratio to a different value and back keeps the same size (#87298)
+ {
+ ASPECT_CHECK_SHRINK_H_GROW_W
+ ASPECT_CHECK_SHRINK_W_GROW_H
+ ASPECT_CHECK_GROW_H
+ ASPECT_CHECK_GROW_W
+ break;
+ }
+#endif
+ case SizemodeFixedW: {
+ // the checks are order so that attempts to modify height are first
+ ASPECT_CHECK_GROW_H
+ ASPECT_CHECK_SHRINK_H_GROW_W
+ ASPECT_CHECK_SHRINK_W_GROW_H
+ ASPECT_CHECK_GROW_W
+ break;
+ }
+ case SizemodeFixedH: {
+ ASPECT_CHECK_GROW_W
+ ASPECT_CHECK_SHRINK_W_GROW_H
+ ASPECT_CHECK_SHRINK_H_GROW_W
+ ASPECT_CHECK_GROW_H
+ break;
+ }
+ case SizemodeMax: {
+ // first checks that try to shrink
+ ASPECT_CHECK_SHRINK_H_GROW_W
+ ASPECT_CHECK_SHRINK_W_GROW_H
+ ASPECT_CHECK_GROW_W
+ ASPECT_CHECK_GROW_H
+ break;
+ }
+ }
+#undef ASPECT_CHECK_SHRINK_H_GROW_W
+#undef ASPECT_CHECK_SHRINK_W_GROW_H
+#undef ASPECT_CHECK_GROW_W
+#undef ASPECT_CHECK_GROW_H
+ w += baseSize.width();
+ h += baseSize.height();
+ }
+ if (!rules()->checkStrictGeometry(!isFullScreen())) {
+ // disobey increments and aspect by explicit rule
+ w = w1;
+ h = h1;
+ }
+
+ QSize size(w, h);
+ if (!noframe) {
+ size = clientSizeToFrameSize(size);
+ }
+ return rules()->checkSize(size);
+}
+
+/**
+ * Gets the client's normal WM hints and reconfigures itself respectively.
+ */
+void X11Client::getWmNormalHints()
+{
+ const bool hadFixedAspect = m_geometryHints.hasAspect();
+ // roundtrip to X server
+ m_geometryHints.fetch();
+ m_geometryHints.read();
+
+ if (!hadFixedAspect && m_geometryHints.hasAspect()) {
+ // align to eventual new constraints
+ maximize(max_mode);
+ }
+ if (isManaged()) {
+ // update to match restrictions
+ QSize new_size = adjustedSize();
+ if (new_size != size() && !isFullScreen()) {
+ QRect origClientGeometry = m_clientGeometry;
+ resizeWithChecks(new_size);
+ if ((!isSpecialWindow() || isToolbar()) && !isFullScreen()) {
+ // try to keep the window in its xinerama screen if possible,
+ // if that fails at least keep it visible somewhere
+ QRect area = workspace()->clientArea(MovementArea, this);
+ if (area.contains(origClientGeometry))
+ keepInArea(area);
+ area = workspace()->clientArea(WorkArea, this);
+ if (area.contains(origClientGeometry))
+ keepInArea(area);
+ }
+ }
+ }
+ updateAllowedActions(); // affects isResizeable()
+}
+
+QSize X11Client::minSize() const
+{
+ return rules()->checkMinSize(m_geometryHints.minSize());
+}
+
+QSize X11Client::maxSize() const
+{
+ return rules()->checkMaxSize(m_geometryHints.maxSize());
+}
+
+QSize X11Client::basicUnit() const
+{
+ return m_geometryHints.resizeIncrements();
+}
+
+/**
+ * Auxiliary function to inform the client about the current window
+ * configuration.
+ */
+void X11Client::sendSyntheticConfigureNotify()
+{
+ xcb_configure_notify_event_t c;
+ memset(&c, 0, sizeof(c));
+ c.response_type = XCB_CONFIGURE_NOTIFY;
+ c.event = window();
+ c.window = window();
+ c.x = m_clientGeometry.x();
+ c.y = m_clientGeometry.y();
+ c.width = m_clientGeometry.width();
+ c.height = m_clientGeometry.height();
+ c.border_width = 0;
+ c.above_sibling = XCB_WINDOW_NONE;
+ c.override_redirect = 0;
+ xcb_send_event(connection(), true, c.event, XCB_EVENT_MASK_STRUCTURE_NOTIFY, reinterpret_cast(&c));
+ xcb_flush(connection());
+}
+
+QPoint X11Client::gravityAdjustment(xcb_gravity_t gravity) const
+{
+ int dx = 0;
+ int dy = 0;
+
+ // dx, dy specify how the client window moves to make space for the frame.
+ // In general we have to compute the reference point and from that figure
+ // out how much we need to shift the client, however given that we ignore
+ // the border width attribute and the extents of the server-side decoration
+ // are known in advance, we can simplify the math quite a bit and express
+ // the required window gravity adjustment in terms of border sizes.
+ switch(gravity) {
+ case XCB_GRAVITY_NORTH_WEST: // move down right
+ default:
+ dx = borderLeft();
+ dy = borderTop();
+ break;
+ case XCB_GRAVITY_NORTH: // move right
+ dx = 0;
+ dy = borderTop();
+ break;
+ case XCB_GRAVITY_NORTH_EAST: // move down left
+ dx = -borderRight();
+ dy = borderTop();
+ break;
+ case XCB_GRAVITY_WEST: // move right
+ dx = borderLeft();
+ dy = 0;
+ break;
+ case XCB_GRAVITY_CENTER:
+ dx = (borderLeft() - borderRight()) / 2;
+ dy = (borderTop() - borderBottom()) / 2;
+ break;
+ case XCB_GRAVITY_STATIC: // don't move
+ dx = 0;
+ dy = 0;
+ break;
+ case XCB_GRAVITY_EAST: // move left
+ dx = -borderRight();
+ dy = 0;
+ break;
+ case XCB_GRAVITY_SOUTH_WEST: // move up right
+ dx = borderLeft() ;
+ dy = -borderBottom();
+ break;
+ case XCB_GRAVITY_SOUTH: // move up
+ dx = 0;
+ dy = -borderBottom();
+ break;
+ case XCB_GRAVITY_SOUTH_EAST: // move up left
+ dx = -borderRight();
+ dy = -borderBottom();
+ break;
+ }
+
+ return QPoint(dx, dy);
+}
+
+const QPoint X11Client::calculateGravitation(bool invert) const
+{
+ const QPoint adjustment = gravityAdjustment(m_geometryHints.windowGravity());
+
+ // translate from client movement to frame movement
+ const int dx = adjustment.x() - borderLeft();
+ const int dy = adjustment.y() - borderTop();
+
+ if (!invert)
+ return QPoint(x() + dx, y() + dy);
+ else
+ return QPoint(x() - dx, y() - dy);
+}
+
+void X11Client::configureRequest(int value_mask, int rx, int ry, int rw, int rh, int gravity, bool from_tool)
+{
+ const int configurePositionMask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y;
+ const int configureSizeMask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
+ const int configureGeometryMask = configurePositionMask | configureSizeMask;
+
+ // "maximized" is a user setting -> we do not allow the client to resize itself
+ // away from this & against the users explicit wish
+ qCDebug(KWIN_CORE) << this << bool(value_mask & configureGeometryMask) <<
+ bool(maximizeMode() & MaximizeVertical) <<
+ bool(maximizeMode() & MaximizeHorizontal);
+
+ // we want to (partially) ignore the request when the window is somehow maximized or quicktiled
+ bool ignore = !app_noborder && (quickTileMode() != QuickTileMode(QuickTileFlag::None) || maximizeMode() != MaximizeRestore);
+ // however, the user shall be able to force obedience despite and also disobedience in general
+ ignore = rules()->checkIgnoreGeometry(ignore);
+ if (!ignore) { // either we're not max'd / q'tiled or the user allowed the client to break that - so break it.
+ updateQuickTileMode(QuickTileFlag::None);
+ max_mode = MaximizeRestore;
+ emit quickTileModeChanged();
+ } else if (!app_noborder && quickTileMode() == QuickTileMode(QuickTileFlag::None) &&
+ (maximizeMode() == MaximizeVertical || maximizeMode() == MaximizeHorizontal)) {
+ // ignoring can be, because either we do, or the user does explicitly not want it.
+ // for partially maximized windows we want to allow configures in the other dimension.
+ // so we've to ask the user again - to know whether we just ignored for the partial maximization.
+ // the problem here is, that the user can explicitly permit configure requests - even for maximized windows!
+ // we cannot distinguish that from passing "false" for partially maximized windows.
+ ignore = rules()->checkIgnoreGeometry(false);
+ if (!ignore) { // the user is not interested, so we fix up dimensions
+ if (maximizeMode() == MaximizeVertical)
+ value_mask &= ~(XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_HEIGHT);
+ if (maximizeMode() == MaximizeHorizontal)
+ value_mask &= ~(XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_WIDTH);
+ if (!(value_mask & configureGeometryMask)) {
+ ignore = true; // the modification turned the request void
+ }
+ }
+ }
+
+ if (ignore) {
+ qCDebug(KWIN_CORE) << "DENIED";
+ return; // nothing to (left) to do for use - bugs #158974, #252314, #321491
+ }
+
+ qCDebug(KWIN_CORE) << "PERMITTED" << this << bool(value_mask & configureGeometryMask);
+
+ if (gravity == 0) // default (nonsense) value for the argument
+ gravity = m_geometryHints.windowGravity();
+ if (value_mask & configurePositionMask) {
+ QPoint new_pos = framePosToClientPos(pos());
+ new_pos -= gravityAdjustment(xcb_gravity_t(gravity));
+ if (value_mask & XCB_CONFIG_WINDOW_X) {
+ new_pos.setX(rx);
+ }
+ if (value_mask & XCB_CONFIG_WINDOW_Y) {
+ new_pos.setY(ry);
+ }
+ // clever(?) workaround for applications like xv that want to set
+ // the location to the current location but miscalculate the
+ // frame size due to kwin being a double-reparenting window
+ // manager
+ if (new_pos.x() == m_clientGeometry.x() && new_pos.y() == m_clientGeometry.y()
+ && gravity == XCB_GRAVITY_NORTH_WEST && !from_tool) {
+ new_pos.setX(x());
+ new_pos.setY(y());
+ }
+ new_pos += gravityAdjustment(xcb_gravity_t(gravity));
+ new_pos = clientPosToFramePos(new_pos);
+
+ int nw = clientSize().width();
+ int nh = clientSize().height();
+ if (value_mask & XCB_CONFIG_WINDOW_WIDTH) {
+ nw = rw;
+ }
+ if (value_mask & XCB_CONFIG_WINDOW_HEIGHT) {
+ nh = rh;
+ }
+ QSize ns = sizeForClientSize(QSize(nw, nh)); // enforces size if needed
+ new_pos = rules()->checkPosition(new_pos);
+ int newScreen = screens()->number(QRect(new_pos, ns).center());
+ if (newScreen != rules()->checkScreen(newScreen))
+ return; // not allowed by rule
+
+ QRect origClientGeometry = m_clientGeometry;
+ GeometryUpdatesBlocker blocker(this);
+ move(new_pos);
+ plainResize(ns);
+ QRect area = workspace()->clientArea(WorkArea, this);
+ if (!from_tool && (!isSpecialWindow() || isToolbar()) && !isFullScreen()
+ && area.contains(origClientGeometry))
+ keepInArea(area);
+
+ // this is part of the kicker-xinerama-hack... it should be
+ // safe to remove when kicker gets proper ExtendedStrut support;
+ // see Workspace::updateClientArea() and
+ // X11Client::adjustedClientArea()
+ if (hasStrut())
+ workspace() -> updateClientArea();
+ }
+
+ if (value_mask & configureSizeMask && !(value_mask & configurePositionMask)) { // pure resize
+ int nw = clientSize().width();
+ int nh = clientSize().height();
+ if (value_mask & XCB_CONFIG_WINDOW_WIDTH) {
+ nw = rw;
+ }
+ if (value_mask & XCB_CONFIG_WINDOW_HEIGHT) {
+ nh = rh;
+ }
+ QSize ns = sizeForClientSize(QSize(nw, nh));
+
+ if (ns != size()) { // don't restore if some app sets its own size again
+ QRect origClientGeometry = m_clientGeometry;
+ GeometryUpdatesBlocker blocker(this);
+ resizeWithChecks(ns, xcb_gravity_t(gravity));
+ if (!from_tool && (!isSpecialWindow() || isToolbar()) && !isFullScreen()) {
+ // try to keep the window in its xinerama screen if possible,
+ // if that fails at least keep it visible somewhere
+ QRect area = workspace()->clientArea(MovementArea, this);
+ if (area.contains(origClientGeometry))
+ keepInArea(area);
+ area = workspace()->clientArea(WorkArea, this);
+ if (area.contains(origClientGeometry))
+ keepInArea(area);
+ }
+ }
+ }
+ geom_restore = frameGeometry();
+ // No need to send synthetic configure notify event here, either it's sent together
+ // with geometry change, or there's no need to send it.
+ // Handling of the real ConfigureRequest event forces sending it, as there it's necessary.
+}
+
+void X11Client::resizeWithChecks(int w, int h, xcb_gravity_t gravity, ForceGeometry_t force)
+{
+ Q_ASSERT(!shade_geometry_change);
+ if (isShade()) {
+ if (h == borderTop() + borderBottom()) {
+ qCWarning(KWIN_CORE) << "Shaded geometry passed for size:" ;
+ }
+ }
+ int newx = x();
+ int newy = y();
+ QRect area = workspace()->clientArea(WorkArea, this);
+ // don't allow growing larger than workarea
+ if (w > area.width())
+ w = area.width();
+ if (h > area.height())
+ h = area.height();
+ QSize tmp = adjustedSize(QSize(w, h)); // checks size constraints, including min/max size
+ w = tmp.width();
+ h = tmp.height();
+ if (gravity == 0) {
+ gravity = m_geometryHints.windowGravity();
+ }
+ switch(gravity) {
+ case XCB_GRAVITY_NORTH_WEST: // top left corner doesn't move
+ default:
+ break;
+ case XCB_GRAVITY_NORTH: // middle of top border doesn't move
+ newx = (newx + width() / 2) - (w / 2);
+ break;
+ case XCB_GRAVITY_NORTH_EAST: // top right corner doesn't move
+ newx = newx + width() - w;
+ break;
+ case XCB_GRAVITY_WEST: // middle of left border doesn't move
+ newy = (newy + height() / 2) - (h / 2);
+ break;
+ case XCB_GRAVITY_CENTER: // middle point doesn't move
+ newx = (newx + width() / 2) - (w / 2);
+ newy = (newy + height() / 2) - (h / 2);
+ break;
+ case XCB_GRAVITY_STATIC: // top left corner of _client_ window doesn't move
+ // since decoration doesn't change, equal to NorthWestGravity
+ break;
+ case XCB_GRAVITY_EAST: // // middle of right border doesn't move
+ newx = newx + width() - w;
+ newy = (newy + height() / 2) - (h / 2);
+ break;
+ case XCB_GRAVITY_SOUTH_WEST: // bottom left corner doesn't move
+ newy = newy + height() - h;
+ break;
+ case XCB_GRAVITY_SOUTH: // middle of bottom border doesn't move
+ newx = (newx + width() / 2) - (w / 2);
+ newy = newy + height() - h;
+ break;
+ case XCB_GRAVITY_SOUTH_EAST: // bottom right corner doesn't move
+ newx = newx + width() - w;
+ newy = newy + height() - h;
+ break;
+ }
+ setFrameGeometry(newx, newy, w, h, force);
+}
+
+// _NET_MOVERESIZE_WINDOW
+void X11Client::NETMoveResizeWindow(int flags, int x, int y, int width, int height)
+{
+ int gravity = flags & 0xff;
+ int value_mask = 0;
+ if (flags & (1 << 8)) {
+ value_mask |= XCB_CONFIG_WINDOW_X;
+ }
+ if (flags & (1 << 9)) {
+ value_mask |= XCB_CONFIG_WINDOW_Y;
+ }
+ if (flags & (1 << 10)) {
+ value_mask |= XCB_CONFIG_WINDOW_WIDTH;
+ }
+ if (flags & (1 << 11)) {
+ value_mask |= XCB_CONFIG_WINDOW_HEIGHT;
+ }
+ configureRequest(value_mask, x, y, width, height, gravity, true);
+}
+
+bool X11Client::isMovable() const
+{
+ if (!hasNETSupport() && !m_motif.move()) {
+ return false;
+ }
+ if (isFullScreen())
+ return false;
+ if (isSpecialWindow() && !isSplash() && !isToolbar()) // allow moving of splashscreens :)
+ return false;
+ if (rules()->checkPosition(invalidPoint) != invalidPoint) // forced position
+ return false;
+ return true;
+}
+
+bool X11Client::isMovableAcrossScreens() const
+{
+ if (!hasNETSupport() && !m_motif.move()) {
+ return false;
+ }
+ if (isSpecialWindow() && !isSplash() && !isToolbar()) // allow moving of splashscreens :)
+ return false;
+ if (rules()->checkPosition(invalidPoint) != invalidPoint) // forced position
+ return false;
+ return true;
+}
+
+bool X11Client::isResizable() const
+{
+ if (!hasNETSupport() && !m_motif.resize()) {
+ return false;
+ }
+ if (isFullScreen())
+ return false;
+ if (isSpecialWindow() || isSplash() || isToolbar())
+ return false;
+ if (rules()->checkSize(QSize()).isValid()) // forced size
+ return false;
+ const Position mode = moveResizePointerMode();
+ if ((mode == PositionTop || mode == PositionTopLeft || mode == PositionTopRight ||
+ mode == PositionLeft || mode == PositionBottomLeft) && rules()->checkPosition(invalidPoint) != invalidPoint)
+ return false;
+
+ QSize min = minSize();
+ QSize max = maxSize();
+ return min.width() < max.width() || min.height() < max.height();
+}
+
+bool X11Client::isMaximizable() const
+{
+ if (!isResizable() || isToolbar()) // SELI isToolbar() ?
+ return false;
+ if (rules()->checkMaximize(MaximizeRestore) == MaximizeRestore && rules()->checkMaximize(MaximizeFull) != MaximizeRestore)
+ return true;
+ return false;
+}
+
+
+/**
+ * Reimplemented to inform the client about the new window position.
+ */
+void X11Client::setFrameGeometry(int x, int y, int w, int h, ForceGeometry_t force)
+{
+ // this code is also duplicated in X11Client::plainResize()
+ // Ok, the shading geometry stuff. Generally, code doesn't care about shaded geometry,
+ // simply because there are too many places dealing with geometry. Those places
+ // ignore shaded state and use normal geometry, which they usually should get
+ // from adjustedSize(). Such geometry comes here, and if the window is shaded,
+ // the geometry is used only for client_size, since that one is not used when
+ // shading. Then the frame geometry is adjusted for the shaded geometry.
+ // This gets more complicated in the case the code does only something like
+ // setGeometry( geometry()) - geometry() will return the shaded frame geometry.
+ // Such code is wrong and should be changed to handle the case when the window is shaded,
+ // for example using X11Client::clientSize()
+
+ QRect frameGeometry(x, y, w, h);
+ QRect bufferGeometry;
+
+ if (shade_geometry_change)
+ ; // nothing
+ else if (isShade()) {
+ if (frameGeometry.height() == borderTop() + borderBottom()) {
+ qCDebug(KWIN_CORE) << "Shaded geometry passed for size:";
+ } else {
+ m_clientGeometry = frameRectToClientRect(frameGeometry);
+ frameGeometry.setHeight(borderTop() + borderBottom());
+ }
+ } else {
+ m_clientGeometry = frameRectToClientRect(frameGeometry);
+ }
+ if (isDecorated()) {
+ bufferGeometry = frameGeometry;
+ } else {
+ bufferGeometry = m_clientGeometry;
+ }
+ if (!areGeometryUpdatesBlocked() && frameGeometry != rules()->checkGeometry(frameGeometry)) {
+ qCDebug(KWIN_CORE) << "forced geometry fail:" << frameGeometry << ":" << rules()->checkGeometry(frameGeometry);
+ }
+ m_frameGeometry = frameGeometry;
+ if (force == NormalGeometrySet && m_bufferGeometry == bufferGeometry && pendingGeometryUpdate() == PendingGeometryNone) {
+ return;
+ }
+ m_bufferGeometry = bufferGeometry;
+ if (areGeometryUpdatesBlocked()) {
+ if (pendingGeometryUpdate() == PendingGeometryForced)
+ {} // maximum, nothing needed
+ else if (force == ForceGeometrySet)
+ setPendingGeometryUpdate(PendingGeometryForced);
+ else
+ setPendingGeometryUpdate(PendingGeometryNormal);
+ return;
+ }
+ updateServerGeometry();
+ updateWindowRules(Rules::Position|Rules::Size);
+
+ // keep track of old maximize mode
+ // to detect changes
+ screens()->setCurrent(this);
+ workspace()->updateStackingOrder();
+
+ // Need to regenerate decoration pixmaps when the buffer size is changed.
+ if (bufferGeometryBeforeUpdateBlocking().size() != m_bufferGeometry.size()) {
+ discardWindowPixmap();
+ }
+ emit geometryShapeChanged(this, frameGeometryBeforeUpdateBlocking());
+ addRepaintDuringGeometryUpdates();
+ updateGeometryBeforeUpdateBlocking();
+ // TODO: this signal is emitted too often
+ emit geometryChanged();
+}
+
+void X11Client::plainResize(int w, int h, ForceGeometry_t force)
+{
+ QSize frameSize(w, h);
+ QSize bufferSize;
+
+ // this code is also duplicated in X11Client::setGeometry(), and it's also commented there
+ if (shade_geometry_change)
+ ; // nothing
+ else if (isShade()) {
+ if (frameSize.height() == borderTop() + borderBottom()) {
+ qCDebug(KWIN_CORE) << "Shaded geometry passed for size:";
+ } else {
+ m_clientGeometry.setSize(frameSizeToClientSize(frameSize));
+ frameSize.setHeight(borderTop() + borderBottom());
+ }
+ } else {
+ m_clientGeometry.setSize(frameSizeToClientSize(frameSize));
+ }
+ if (isDecorated()) {
+ bufferSize = frameSize;
+ } else {
+ bufferSize = m_clientGeometry.size();
+ }
+ if (!areGeometryUpdatesBlocked() && frameSize != rules()->checkSize(frameSize)) {
+ qCDebug(KWIN_CORE) << "forced size fail:" << frameSize << ":" << rules()->checkSize(frameSize);
+ }
+ m_frameGeometry.setSize(frameSize);
+ // resuming geometry updates is handled only in setGeometry()
+ Q_ASSERT(pendingGeometryUpdate() == PendingGeometryNone || areGeometryUpdatesBlocked());
+ if (force == NormalGeometrySet && m_bufferGeometry.size() == bufferSize) {
+ return;
+ }
+ m_bufferGeometry.setSize(bufferSize);
+ if (areGeometryUpdatesBlocked()) {
+ if (pendingGeometryUpdate() == PendingGeometryForced)
+ {} // maximum, nothing needed
+ else if (force == ForceGeometrySet)
+ setPendingGeometryUpdate(PendingGeometryForced);
+ else
+ setPendingGeometryUpdate(PendingGeometryNormal);
+ return;
+ }
+ updateServerGeometry();
+ updateWindowRules(Rules::Position|Rules::Size);
+ screens()->setCurrent(this);
+ workspace()->updateStackingOrder();
+ if (bufferGeometryBeforeUpdateBlocking().size() != m_bufferGeometry.size()) {
+ discardWindowPixmap();
+ }
+ emit geometryShapeChanged(this, frameGeometryBeforeUpdateBlocking());
+ addRepaintDuringGeometryUpdates();
+ updateGeometryBeforeUpdateBlocking();
+ // TODO: this signal is emitted too often
+ emit geometryChanged();
+}
+
+void X11Client::updateServerGeometry()
+{
+ if (m_frame.geometry().size() != m_bufferGeometry.size() || pendingGeometryUpdate() == PendingGeometryForced) {
+ resizeDecoration();
+ m_frame.setGeometry(m_bufferGeometry);
+ if (!isShade()) {
+ QSize cs = clientSize();
+ m_wrapper.setGeometry(QRect(clientPos(), cs));
+ if (!isResize() || syncRequest.counter == XCB_NONE) {
+ m_client.setGeometry(0, 0, cs.width(), cs.height());
+ }
+ // SELI - won't this be too expensive?
+ // THOMAS - yes, but gtk+ clients will not resize without ...
+ sendSyntheticConfigureNotify();
+ }
+ updateShape();
+ } else {
+ if (isMoveResize()) {
+ if (compositing()) { // Defer the X update until we leave this mode
+ needsXWindowMove = true;
+ } else {
+ m_frame.move(m_bufferGeometry.topLeft()); // sendSyntheticConfigureNotify() on finish shall be sufficient
+ }
+ } else {
+ m_frame.move(m_bufferGeometry.topLeft());
+ sendSyntheticConfigureNotify();
+ }
+ // Unconditionally move the input window: it won't affect rendering
+ m_decoInputExtent.move(pos() + inputPos());
+ }
+}
+
+static bool changeMaximizeRecursion = false;
+void X11Client::changeMaximize(bool horizontal, bool vertical, bool adjust)
+{
+ if (changeMaximizeRecursion)
+ return;
+
+ if (!isResizable() || isToolbar()) // SELI isToolbar() ?
+ return;
+
+ QRect clientArea;
+ if (isElectricBorderMaximizing())
+ clientArea = workspace()->clientArea(MaximizeArea, Cursor::pos(), desktop());
+ else
+ clientArea = workspace()->clientArea(MaximizeArea, this);
+
+ MaximizeMode old_mode = max_mode;
+ // 'adjust == true' means to update the size only, e.g. after changing workspace size
+ if (!adjust) {
+ if (vertical)
+ max_mode = MaximizeMode(max_mode ^ MaximizeVertical);
+ if (horizontal)
+ max_mode = MaximizeMode(max_mode ^ MaximizeHorizontal);
+ }
+
+ // if the client insist on a fix aspect ratio, we check whether the maximizing will get us
+ // out of screen bounds and take that as a "full maximization with aspect check" then
+ if (m_geometryHints.hasAspect() && // fixed aspect
+ (max_mode == MaximizeVertical || max_mode == MaximizeHorizontal) && // ondimensional maximization
+ rules()->checkStrictGeometry(true)) { // obey aspect
+ const QSize minAspect = m_geometryHints.minAspect();
+ const QSize maxAspect = m_geometryHints.maxAspect();
+ if (max_mode == MaximizeVertical || (old_mode & MaximizeVertical)) {
+ const double fx = minAspect.width(); // use doubles, because the values can be MAX_INT
+ const double fy = maxAspect.height(); // use doubles, because the values can be MAX_INT
+ if (fx*clientArea.height()/fy > clientArea.width()) // too big
+ max_mode = old_mode & MaximizeHorizontal ? MaximizeRestore : MaximizeFull;
+ } else { // max_mode == MaximizeHorizontal
+ const double fx = maxAspect.width();
+ const double fy = minAspect.height();
+ if (fy*clientArea.width()/fx > clientArea.height()) // too big
+ max_mode = old_mode & MaximizeVertical ? MaximizeRestore : MaximizeFull;
+ }
+ }
+
+ max_mode = rules()->checkMaximize(max_mode);
+ if (!adjust && max_mode == old_mode)
+ return;
+
+ GeometryUpdatesBlocker blocker(this);
+
+ // maximing one way and unmaximizing the other way shouldn't happen,
+ // so restore first and then maximize the other way
+ if ((old_mode == MaximizeVertical && max_mode == MaximizeHorizontal)
+ || (old_mode == MaximizeHorizontal && max_mode == MaximizeVertical)) {
+ changeMaximize(false, false, false); // restore
+ }
+
+ // save sizes for restoring, if maximalizing
+ QSize sz;
+ if (isShade())
+ sz = sizeForClientSize(clientSize());
+ else
+ sz = size();
+
+ if (quickTileMode() == QuickTileMode(QuickTileFlag::None)) {
+ if (!adjust && !(old_mode & MaximizeVertical)) {
+ geom_restore.setTop(y());
+ geom_restore.setHeight(sz.height());
+ }
+ if (!adjust && !(old_mode & MaximizeHorizontal)) {
+ geom_restore.setLeft(x());
+ geom_restore.setWidth(sz.width());
+ }
+ }
+
+ // call into decoration update borders
+ if (isDecorated() && decoration()->client() && !(options->borderlessMaximizedWindows() && max_mode == KWin::MaximizeFull)) {
+ changeMaximizeRecursion = true;
+ const auto c = decoration()->client().data();
+ if ((max_mode & MaximizeVertical) != (old_mode & MaximizeVertical)) {
+ emit c->maximizedVerticallyChanged(max_mode & MaximizeVertical);
+ }
+ if ((max_mode & MaximizeHorizontal) != (old_mode & MaximizeHorizontal)) {
+ emit c->maximizedHorizontallyChanged(max_mode & MaximizeHorizontal);
+ }
+ if ((max_mode == MaximizeFull) != (old_mode == MaximizeFull)) {
+ emit c->maximizedChanged(max_mode & MaximizeFull);
+ }
+ changeMaximizeRecursion = false;
+ }
+
+ if (options->borderlessMaximizedWindows()) {
+ // triggers a maximize change.
+ // The next setNoBorder interation will exit since there's no change but the first recursion pullutes the restore geometry
+ changeMaximizeRecursion = true;
+ setNoBorder(rules()->checkNoBorder(app_noborder || (m_motif.hasDecoration() && m_motif.noBorder()) || max_mode == MaximizeFull));
+ changeMaximizeRecursion = false;
+ }
+
+ const ForceGeometry_t geom_mode = isDecorated() ? ForceGeometrySet : NormalGeometrySet;
+
+ // Conditional quick tiling exit points
+ if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) {
+ if (old_mode == MaximizeFull &&
+ !clientArea.contains(geom_restore.center())) {
+ // Not restoring on the same screen
+ // TODO: The following doesn't work for some reason
+ //quick_tile_mode = QuickTileFlag::None; // And exit quick tile mode manually
+ } else if ((old_mode == MaximizeVertical && max_mode == MaximizeRestore) ||
+ (old_mode == MaximizeFull && max_mode == MaximizeHorizontal)) {
+ // Modifying geometry of a tiled window
+ updateQuickTileMode(QuickTileFlag::None); // Exit quick tile mode without restoring geometry
+ }
+ }
+
+ switch(max_mode) {
+
+ case MaximizeVertical: {
+ if (old_mode & MaximizeHorizontal) { // actually restoring from MaximizeFull
+ if (geom_restore.width() == 0 || !clientArea.contains(geom_restore.center())) {
+ // needs placement
+ plainResize(adjustedSize(QSize(width() * 2 / 3, clientArea.height()), SizemodeFixedH), geom_mode);
+ Placement::self()->placeSmart(this, clientArea);
+ } else {
+ setFrameGeometry(QRect(QPoint(geom_restore.x(), clientArea.top()),
+ adjustedSize(QSize(geom_restore.width(), clientArea.height()), SizemodeFixedH)), geom_mode);
+ }
+ } else {
+ QRect r(x(), clientArea.top(), width(), clientArea.height());
+ r.setTopLeft(rules()->checkPosition(r.topLeft()));
+ r.setSize(adjustedSize(r.size(), SizemodeFixedH));
+ setFrameGeometry(r, geom_mode);
+ }
+ info->setState(NET::MaxVert, NET::Max);
+ break;
+ }
+
+ case MaximizeHorizontal: {
+ if (old_mode & MaximizeVertical) { // actually restoring from MaximizeFull
+ if (geom_restore.height() == 0 || !clientArea.contains(geom_restore.center())) {
+ // needs placement
+ plainResize(adjustedSize(QSize(clientArea.width(), height() * 2 / 3), SizemodeFixedW), geom_mode);
+ Placement::self()->placeSmart(this, clientArea);
+ } else {
+ setFrameGeometry(QRect(QPoint(clientArea.left(), geom_restore.y()),
+ adjustedSize(QSize(clientArea.width(), geom_restore.height()), SizemodeFixedW)), geom_mode);
+ }
+ } else {
+ QRect r(clientArea.left(), y(), clientArea.width(), height());
+ r.setTopLeft(rules()->checkPosition(r.topLeft()));
+ r.setSize(adjustedSize(r.size(), SizemodeFixedW));
+ setFrameGeometry(r, geom_mode);
+ }
+ info->setState(NET::MaxHoriz, NET::Max);
+ break;
+ }
+
+ case MaximizeRestore: {
+ QRect restore = frameGeometry();
+ // when only partially maximized, geom_restore may not have the other dimension remembered
+ if (old_mode & MaximizeVertical) {
+ restore.setTop(geom_restore.top());
+ restore.setBottom(geom_restore.bottom());
+ }
+ if (old_mode & MaximizeHorizontal) {
+ restore.setLeft(geom_restore.left());
+ restore.setRight(geom_restore.right());
+ }
+ if (!restore.isValid()) {
+ QSize s = QSize(clientArea.width() * 2 / 3, clientArea.height() * 2 / 3);
+ if (geom_restore.width() > 0)
+ s.setWidth(geom_restore.width());
+ if (geom_restore.height() > 0)
+ s.setHeight(geom_restore.height());
+ plainResize(adjustedSize(s));
+ Placement::self()->placeSmart(this, clientArea);
+ restore = frameGeometry();
+ if (geom_restore.width() > 0)
+ restore.moveLeft(geom_restore.x());
+ if (geom_restore.height() > 0)
+ restore.moveTop(geom_restore.y());
+ geom_restore = restore; // relevant for mouse pos calculation, bug #298646
+ }
+ if (m_geometryHints.hasAspect()) {
+ restore.setSize(adjustedSize(restore.size(), SizemodeAny));
+ }
+ setFrameGeometry(restore, geom_mode);
+ if (!clientArea.contains(geom_restore.center())) // Not restoring to the same screen
+ Placement::self()->place(this, clientArea);
+ info->setState(NET::States(), NET::Max);
+ updateQuickTileMode(QuickTileFlag::None);
+ break;
+ }
+
+ case MaximizeFull: {
+ QRect r(clientArea);
+ r.setTopLeft(rules()->checkPosition(r.topLeft()));
+ r.setSize(adjustedSize(r.size(), SizemodeMax));
+ if (r.size() != clientArea.size()) { // to avoid off-by-one errors...
+ if (isElectricBorderMaximizing() && r.width() < clientArea.width()) {
+ r.moveLeft(qMax(clientArea.left(), Cursor::pos().x() - r.width()/2));
+ r.moveRight(qMin(clientArea.right(), r.right()));
+ } else {
+ r.moveCenter(clientArea.center());
+ const bool closeHeight = r.height() > 97*clientArea.height()/100;
+ const bool closeWidth = r.width() > 97*clientArea.width() /100;
+ const bool overHeight = r.height() > clientArea.height();
+ const bool overWidth = r.width() > clientArea.width();
+ if (closeWidth || closeHeight) {
+ Position titlePos = titlebarPosition();
+ const QRect screenArea = workspace()->clientArea(ScreenArea, clientArea.center(), desktop());
+ if (closeHeight) {
+ bool tryBottom = titlePos == PositionBottom;
+ if ((overHeight && titlePos == PositionTop) ||
+ screenArea.top() == clientArea.top())
+ r.setTop(clientArea.top());
+ else
+ tryBottom = true;
+ if (tryBottom &&
+ (overHeight || screenArea.bottom() == clientArea.bottom()))
+ r.setBottom(clientArea.bottom());
+ }
+ if (closeWidth) {
+ bool tryLeft = titlePos == PositionLeft;
+ if ((overWidth && titlePos == PositionRight) ||
+ screenArea.right() == clientArea.right())
+ r.setRight(clientArea.right());
+ else
+ tryLeft = true;
+ if (tryLeft && (overWidth || screenArea.left() == clientArea.left()))
+ r.setLeft(clientArea.left());
+ }
+ }
+ }
+ r.moveTopLeft(rules()->checkPosition(r.topLeft()));
+ }
+ setFrameGeometry(r, geom_mode);
+ if (options->electricBorderMaximize() && r.top() == clientArea.top())
+ updateQuickTileMode(QuickTileFlag::Maximize);
+ else
+ updateQuickTileMode(QuickTileFlag::None);
+ info->setState(NET::Max, NET::Max);
+ break;
+ }
+ default:
+ break;
+ }
+
+ updateAllowedActions();
+ updateWindowRules(Rules::MaximizeVert|Rules::MaximizeHoriz|Rules::Position|Rules::Size);
+ emit quickTileModeChanged();
+}
+
+bool X11Client::userCanSetFullScreen() const
+{
+ if (!isFullScreenable()) {
+ return false;
+ }
+ return isNormalWindow() || isDialog();
+}
+
+void X11Client::setFullScreen(bool set, bool user)
+{
+ set = rules()->checkFullScreen(set);
+
+ const bool wasFullscreen = isFullScreen();
+ if (wasFullscreen == set) {
+ return;
+ }
+ if (user && !userCanSetFullScreen()) {
+ return;
+ }
+
+ setShade(ShadeNone);
+
+ if (wasFullscreen) {
+ workspace()->updateFocusMousePosition(Cursor::pos()); // may cause leave event
+ } else {
+ geom_fs_restore = frameGeometry();
+ }
+
+ if (set) {
+ m_fullscreenMode = FullScreenNormal;
+ workspace()->raiseClient(this);
+ } else {
+ m_fullscreenMode = FullScreenNone;
+ }
+
+ StackingUpdatesBlocker blocker1(workspace());
+ GeometryUpdatesBlocker blocker2(this);
+
+ // active fullscreens get different layer
+ workspace()->updateClientLayer(this);
+
+ info->setState(isFullScreen() ? NET::FullScreen : NET::States(), NET::FullScreen);
+ updateDecoration(false, false);
+
+ if (set) {
+ if (info->fullscreenMonitors().isSet()) {
+ setFrameGeometry(fullscreenMonitorsArea(info->fullscreenMonitors()));
+ } else {
+ setFrameGeometry(workspace()->clientArea(FullScreenArea, this));
+ }
+ } else {
+ Q_ASSERT(!geom_fs_restore.isNull());
+ const int currentScreen = screen();
+ setFrameGeometry(QRect(geom_fs_restore.topLeft(), adjustedSize(geom_fs_restore.size())));
+ if(currentScreen != screen()) {
+ workspace()->sendClientToScreen(this, currentScreen);
+ }
+ }
+
+ updateWindowRules(Rules::Fullscreen | Rules::Position | Rules::Size);
+ emit clientFullScreenSet(this, set, user);
+ emit fullScreenChanged();
+}
+
+
+void X11Client::updateFullscreenMonitors(NETFullscreenMonitors topology)
+{
+ int nscreens = screens()->count();
+
+// qDebug() << "incoming request with top: " << topology.top << " bottom: " << topology.bottom
+// << " left: " << topology.left << " right: " << topology.right
+// << ", we have: " << nscreens << " screens.";
+
+ if (topology.top >= nscreens ||
+ topology.bottom >= nscreens ||
+ topology.left >= nscreens ||
+ topology.right >= nscreens) {
+ qCWarning(KWIN_CORE) << "fullscreenMonitors update failed. request higher than number of screens.";
+ return;
+ }
+
+ info->setFullscreenMonitors(topology);
+ if (isFullScreen())
+ setFrameGeometry(fullscreenMonitorsArea(topology));
+}
+
+/**
+ * Calculates the bounding rectangle defined by the 4 monitor indices indicating the
+ * top, bottom, left, and right edges of the window when the fullscreen state is enabled.
+ */
+QRect X11Client::fullscreenMonitorsArea(NETFullscreenMonitors requestedTopology) const
+{
+ QRect top, bottom, left, right, total;
+
+ top = screens()->geometry(requestedTopology.top);
+ bottom = screens()->geometry(requestedTopology.bottom);
+ left = screens()->geometry(requestedTopology.left);
+ right = screens()->geometry(requestedTopology.right);
+ total = top.united(bottom.united(left.united(right)));
+
+// qDebug() << "top: " << top << " bottom: " << bottom
+// << " left: " << left << " right: " << right;
+// qDebug() << "returning rect: " << total;
+ return total;
+}
+
+static GeometryTip* geometryTip = nullptr;
+
+void X11Client::positionGeometryTip()
+{
+ Q_ASSERT(isMove() || isResize());
+ // Position and Size display
+ if (effects && static_cast(effects)->provides(Effect::GeometryTip))
+ return; // some effect paints this for us
+ if (options->showGeometryTip()) {
+ if (!geometryTip) {
+ geometryTip = new GeometryTip(&m_geometryHints);
+ }
+ QRect wgeom(moveResizeGeometry()); // position of the frame, size of the window itself
+ wgeom.setWidth(wgeom.width() - (width() - clientSize().width()));
+ wgeom.setHeight(wgeom.height() - (height() - clientSize().height()));
+ if (isShade())
+ wgeom.setHeight(0);
+ geometryTip->setGeometry(wgeom);
+ if (!geometryTip->isVisible())
+ geometryTip->show();
+ geometryTip->raise();
+ }
+}
+
+bool X11Client::doStartMoveResize()
+{
+ bool has_grab = false;
+ // This reportedly improves smoothness of the moveresize operation,
+ // something with Enter/LeaveNotify events, looks like XFree performance problem or something *shrug*
+ // (https://lists.kde.org/?t=107302193400001&r=1&w=2)
+ QRect r = workspace()->clientArea(FullArea, this);
+ m_moveResizeGrabWindow.create(r, XCB_WINDOW_CLASS_INPUT_ONLY, 0, nullptr, rootWindow());
+ m_moveResizeGrabWindow.map();
+ m_moveResizeGrabWindow.raise();
+ updateXTime();
+ const xcb_grab_pointer_cookie_t cookie = xcb_grab_pointer_unchecked(connection(), false, m_moveResizeGrabWindow,
+ XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION |
+ XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW,
+ XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, m_moveResizeGrabWindow, Cursor::x11Cursor(cursor()), xTime());
+ ScopedCPointer pointerGrab(xcb_grab_pointer_reply(connection(), cookie, nullptr));
+ if (!pointerGrab.isNull() && pointerGrab->status == XCB_GRAB_STATUS_SUCCESS) {
+ has_grab = true;
+ }
+ if (!has_grab && grabXKeyboard(frameId()))
+ has_grab = move_resize_has_keyboard_grab = true;
+ if (!has_grab) { // at least one grab is necessary in order to be able to finish move/resize
+ m_moveResizeGrabWindow.reset();
+ return false;
+ }
+ return true;
+}
+
+void X11Client::leaveMoveResize()
+{
+ if (needsXWindowMove) {
+ // Do the deferred move
+ m_frame.move(m_bufferGeometry.topLeft());
+ needsXWindowMove = false;
+ }
+ if (!isResize())
+ sendSyntheticConfigureNotify(); // tell the client about it's new final position
+ if (geometryTip) {
+ geometryTip->hide();
+ delete geometryTip;
+ geometryTip = nullptr;
+ }
+ if (move_resize_has_keyboard_grab)
+ ungrabXKeyboard();
+ move_resize_has_keyboard_grab = false;
+ xcb_ungrab_pointer(connection(), xTime());
+ m_moveResizeGrabWindow.reset();
+ if (syncRequest.counter == XCB_NONE) // don't forget to sanitize since the timeout will no more fire
+ syncRequest.isPending = false;
+ delete syncRequest.timeout;
+ syncRequest.timeout = nullptr;
+ AbstractClient::leaveMoveResize();
+}
+
+bool X11Client::isWaitingForMoveResizeSync() const
+{
+ return syncRequest.isPending && isResize();
+}
+
+void X11Client::doResizeSync()
+{
+ if (!syncRequest.timeout) {
+ syncRequest.timeout = new QTimer(this);
+ connect(syncRequest.timeout, &QTimer::timeout, this, &X11Client::performMoveResize);
+ syncRequest.timeout->setSingleShot(true);
+ }
+ if (syncRequest.counter != XCB_NONE) {
+ syncRequest.timeout->start(250);
+ sendSyncRequest();
+ } else { // for clients not supporting the XSYNC protocol, we
+ syncRequest.isPending = true; // limit the resizes to 30Hz to take pointless load from X11
+ syncRequest.timeout->start(33); // and the client, the mouse is still moved at full speed
+ } // and no human can control faster resizes anyway
+ const QRect moveResizeClientGeometry = frameRectToClientRect(moveResizeGeometry());
+ m_client.setGeometry(0, 0, moveResizeClientGeometry.width(), moveResizeClientGeometry.height());
+}
+
+void X11Client::doPerformMoveResize()
+{
+ if (syncRequest.counter == XCB_NONE) // client w/o XSYNC support. allow the next resize event
+ syncRequest.isPending = false; // NEVER do this for clients with a valid counter
+ // (leads to sync request races in some clients)
+}
+
+/**
+ * Returns \a area with the client's strut taken into account.
+ *
+ * Used from Workspace in updateClientArea.
+ */
+// TODO move to Workspace?
+
+QRect X11Client::adjustedClientArea(const QRect &desktopArea, const QRect& area) const
+{
+ QRect r = area;
+ NETExtendedStrut str = strut();
+ QRect stareaL = QRect(
+ 0,
+ str . left_start,
+ str . left_width,
+ str . left_end - str . left_start + 1);
+ QRect stareaR = QRect(
+ desktopArea . right() - str . right_width + 1,
+ str . right_start,
+ str . right_width,
+ str . right_end - str . right_start + 1);
+ QRect stareaT = QRect(
+ str . top_start,
+ 0,
+ str . top_end - str . top_start + 1,
+ str . top_width);
+ QRect stareaB = QRect(
+ str . bottom_start,
+ desktopArea . bottom() - str . bottom_width + 1,
+ str . bottom_end - str . bottom_start + 1,
+ str . bottom_width);
+
+ QRect screenarea = workspace()->clientArea(ScreenArea, this);
+ // HACK: workarea handling is not xinerama aware, so if this strut
+ // reserves place at a xinerama edge that's inside the virtual screen,
+ // ignore the strut for workspace setting.
+ if (area == QRect(QPoint(0, 0), screens()->displaySize())) {
+ if (stareaL.left() < screenarea.left())
+ stareaL = QRect();
+ if (stareaR.right() > screenarea.right())
+ stareaR = QRect();
+ if (stareaT.top() < screenarea.top())
+ stareaT = QRect();
+ if (stareaB.bottom() < screenarea.bottom())
+ stareaB = QRect();
+ }
+ // Handle struts at xinerama edges that are inside the virtual screen.
+ // They're given in virtual screen coordinates, make them affect only
+ // their xinerama screen.
+ stareaL.setLeft(qMax(stareaL.left(), screenarea.left()));
+ stareaR.setRight(qMin(stareaR.right(), screenarea.right()));
+ stareaT.setTop(qMax(stareaT.top(), screenarea.top()));
+ stareaB.setBottom(qMin(stareaB.bottom(), screenarea.bottom()));
+
+ if (stareaL . intersects(area)) {
+// qDebug() << "Moving left of: " << r << " to " << stareaL.right() + 1;
+ r . setLeft(stareaL . right() + 1);
+ }
+ if (stareaR . intersects(area)) {
+// qDebug() << "Moving right of: " << r << " to " << stareaR.left() - 1;
+ r . setRight(stareaR . left() - 1);
+ }
+ if (stareaT . intersects(area)) {
+// qDebug() << "Moving top of: " << r << " to " << stareaT.bottom() + 1;
+ r . setTop(stareaT . bottom() + 1);
+ }
+ if (stareaB . intersects(area)) {
+// qDebug() << "Moving bottom of: " << r << " to " << stareaB.top() - 1;
+ r . setBottom(stareaB . top() - 1);
+ }
+
+ return r;
+}
+
+NETExtendedStrut X11Client::strut() const
+{
+ NETExtendedStrut ext = info->extendedStrut();
+ NETStrut str = info->strut();
+ const QSize displaySize = screens()->displaySize();
+ if (ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0
+ && (str.left != 0 || str.right != 0 || str.top != 0 || str.bottom != 0)) {
+ // build extended from simple
+ if (str.left != 0) {
+ ext.left_width = str.left;
+ ext.left_start = 0;
+ ext.left_end = displaySize.height();
+ }
+ if (str.right != 0) {
+ ext.right_width = str.right;
+ ext.right_start = 0;
+ ext.right_end = displaySize.height();
+ }
+ if (str.top != 0) {
+ ext.top_width = str.top;
+ ext.top_start = 0;
+ ext.top_end = displaySize.width();
+ }
+ if (str.bottom != 0) {
+ ext.bottom_width = str.bottom;
+ ext.bottom_start = 0;
+ ext.bottom_end = displaySize.width();
+ }
+ }
+ return ext;
+}
+
+StrutRect X11Client::strutRect(StrutArea area) const
+{
+ Q_ASSERT(area != StrutAreaAll); // Not valid
+ const QSize displaySize = screens()->displaySize();
+ NETExtendedStrut strutArea = strut();
+ switch(area) {
+ case StrutAreaTop:
+ if (strutArea.top_width != 0)
+ return StrutRect(QRect(
+ strutArea.top_start, 0,
+ strutArea.top_end - strutArea.top_start, strutArea.top_width
+ ), StrutAreaTop);
+ break;
+ case StrutAreaRight:
+ if (strutArea.right_width != 0)
+ return StrutRect(QRect(
+ displaySize.width() - strutArea.right_width, strutArea.right_start,
+ strutArea.right_width, strutArea.right_end - strutArea.right_start
+ ), StrutAreaRight);
+ break;
+ case StrutAreaBottom:
+ if (strutArea.bottom_width != 0)
+ return StrutRect(QRect(
+ strutArea.bottom_start, displaySize.height() - strutArea.bottom_width,
+ strutArea.bottom_end - strutArea.bottom_start, strutArea.bottom_width
+ ), StrutAreaBottom);
+ break;
+ case StrutAreaLeft:
+ if (strutArea.left_width != 0)
+ return StrutRect(QRect(
+ 0, strutArea.left_start,
+ strutArea.left_width, strutArea.left_end - strutArea.left_start
+ ), StrutAreaLeft);
+ break;
+ default:
+ abort(); // Not valid
+ }
+ return StrutRect(); // Null rect
+}
+
+StrutRects X11Client::strutRects() const
+{
+ StrutRects region;
+ region += strutRect(StrutAreaTop);
+ region += strutRect(StrutAreaRight);
+ region += strutRect(StrutAreaBottom);
+ region += strutRect(StrutAreaLeft);
+ return region;
+}
+
+bool X11Client::hasStrut() const
+{
+ NETExtendedStrut ext = strut();
+ if (ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0)
+ return false;
+ return true;
+}
+
+bool X11Client::hasOffscreenXineramaStrut() const
+{
+ // Get strut as a QRegion
+ QRegion region;
+ region += strutRect(StrutAreaTop);
+ region += strutRect(StrutAreaRight);
+ region += strutRect(StrutAreaBottom);
+ region += strutRect(StrutAreaLeft);
+
+ // Remove all visible areas so that only the invisible remain
+ for (int i = 0; i < screens()->count(); i ++)
+ region -= screens()->geometry(i);
+
+ // If there's anything left then we have an offscreen strut
+ return !region.isEmpty();
+}
+
+} // namespace