diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -422,7 +422,6 @@ 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 --- a/abstract_client.cpp +++ b/abstract_client.cpp @@ -49,6 +49,11 @@ 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 @@ 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 @@ 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 --- 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 --- a/workspace.cpp +++ b/workspace.cpp @@ -1933,4 +1933,844 @@ (*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 --- a/x11client.cpp +++ b/x11client.cpp @@ -29,7 +29,9 @@ #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 @@ } } -} // 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