Warning, file /plasma/kwin/src/screenedge.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 KWin - the KDE window manager 0003 This file is part of the KDE project. 0004 0005 SPDX-FileCopyrightText: 2011 Arthur Arlt <a.arlt@stud.uni-heidelberg.de> 0006 SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org> 0007 0008 Since the functionality provided in this class has been moved from 0009 class Workspace, it is not clear who exactly has written the code. 0010 The list below contains the copyright holders of the class Workspace. 0011 0012 SPDX-FileCopyrightText: 1999, 2000 Matthias Ettrich <ettrich@kde.org> 0013 SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org> 0014 SPDX-FileCopyrightText: 2009 Lucas Murray <lmurray@undefinedfire.com> 0015 0016 SPDX-License-Identifier: GPL-2.0-or-later 0017 */ 0018 0019 #include "screenedge.h" 0020 0021 #include <config-kwin.h> 0022 0023 #include "core/output.h" 0024 #include "cursor.h" 0025 #include "effects.h" 0026 #include "gestures.h" 0027 #include "main.h" 0028 #include "utils/common.h" 0029 #include "virtualdesktops.h" 0030 #include <workspace.h> 0031 #include <x11window.h> 0032 // DBus generated 0033 #if KWIN_BUILD_SCREENLOCKER 0034 #include "screenlocker_interface.h" 0035 #endif 0036 // frameworks 0037 #include <KConfigGroup> 0038 // Qt 0039 #include <QAbstractEventDispatcher> 0040 #include <QAction> 0041 #include <QDBusInterface> 0042 #include <QDBusPendingCall> 0043 #include <QFontDatabase> 0044 #include <QFontMetrics> 0045 #include <QMouseEvent> 0046 #include <QTextStream> 0047 #include <QTimer> 0048 #include <QWidget> 0049 0050 namespace KWin 0051 { 0052 0053 // Mouse should not move more than this many pixels 0054 static const int DISTANCE_RESET = 30; 0055 0056 // How large the touch target of the area recognizing touch gestures is 0057 static const int TOUCH_TARGET = 3; 0058 0059 // How far the user needs to swipe before triggering an action. 0060 static const int MINIMUM_DELTA = 44; 0061 0062 TouchCallback::TouchCallback(QAction *touchUpAction, TouchCallback::CallbackFunction progressCallback) 0063 : m_touchUpAction(touchUpAction) 0064 , m_progressCallback(progressCallback) 0065 { 0066 } 0067 0068 TouchCallback::~TouchCallback() 0069 { 0070 } 0071 0072 QAction *TouchCallback::touchUpAction() const 0073 { 0074 return m_touchUpAction; 0075 } 0076 0077 void TouchCallback::progressCallback(ElectricBorder border, const QPointF &deltaProgress, Output *output) const 0078 { 0079 if (m_progressCallback) { 0080 m_progressCallback(border, deltaProgress, output); 0081 } 0082 } 0083 0084 bool TouchCallback::hasProgressCallback() const 0085 { 0086 return m_progressCallback != nullptr; 0087 } 0088 0089 Edge::Edge(ScreenEdges *parent) 0090 : m_edges(parent) 0091 , m_border(ElectricNone) 0092 , m_action(ElectricActionNone) 0093 , m_reserved(0) 0094 , m_approaching(false) 0095 , m_lastApproachingFactor(0) 0096 , m_blocked(false) 0097 , m_pushBackBlocked(false) 0098 , m_client(nullptr) 0099 , m_output(nullptr) 0100 , m_gesture(std::make_unique<SwipeGesture>()) 0101 { 0102 m_gesture->setMinimumFingerCount(1); 0103 m_gesture->setMaximumFingerCount(1); 0104 connect( 0105 m_gesture.get(), &Gesture::triggered, this, [this]() { 0106 stopApproaching(); 0107 if (m_client) { 0108 m_client->showOnScreenEdge(); 0109 unreserve(); 0110 return; 0111 } 0112 handleTouchAction(); 0113 handleTouchCallback(); 0114 }, 0115 Qt::QueuedConnection); 0116 connect(m_gesture.get(), &SwipeGesture::started, this, &Edge::startApproaching); 0117 connect(m_gesture.get(), &SwipeGesture::cancelled, this, &Edge::stopApproaching); 0118 connect(m_gesture.get(), &SwipeGesture::cancelled, this, [this]() { 0119 if (!m_touchCallbacks.isEmpty() && m_touchCallbacks.constFirst().hasProgressCallback()) { 0120 handleTouchCallback(); 0121 } 0122 }); 0123 connect(m_gesture.get(), &SwipeGesture::progress, this, [this](qreal progress) { 0124 int factor = progress * 256.0f; 0125 if (m_lastApproachingFactor != factor) { 0126 m_lastApproachingFactor = factor; 0127 Q_EMIT approaching(border(), m_lastApproachingFactor / 256.0f, m_approachGeometry); 0128 } 0129 }); 0130 connect(m_gesture.get(), &SwipeGesture::deltaProgress, this, [this](const QPointF &progressDelta) { 0131 if (!m_touchCallbacks.isEmpty()) { 0132 m_touchCallbacks.constFirst().progressCallback(border(), progressDelta, m_output); 0133 } 0134 }); 0135 connect(this, &Edge::activatesForTouchGestureChanged, this, [this]() { 0136 if (isReserved()) { 0137 if (activatesForTouchGesture()) { 0138 m_edges->gestureRecognizer()->registerSwipeGesture(m_gesture.get()); 0139 } else { 0140 m_edges->gestureRecognizer()->unregisterSwipeGesture(m_gesture.get()); 0141 } 0142 } 0143 }); 0144 } 0145 0146 Edge::~Edge() 0147 { 0148 stopApproaching(); 0149 } 0150 0151 void Edge::reserve() 0152 { 0153 m_reserved++; 0154 if (m_reserved == 1) { 0155 // got activated 0156 activate(); 0157 } 0158 } 0159 0160 void Edge::reserve(QObject *object, const char *slot) 0161 { 0162 connect(object, &QObject::destroyed, this, qOverload<QObject *>(&Edge::unreserve)); 0163 m_callBacks.insert(object, QByteArray(slot)); 0164 reserve(); 0165 } 0166 0167 void Edge::reserveTouchCallBack(QAction *action, TouchCallback::CallbackFunction callback) 0168 { 0169 if (std::any_of(m_touchCallbacks.constBegin(), m_touchCallbacks.constEnd(), [action](const TouchCallback &c) { 0170 return c.touchUpAction() == action; 0171 })) { 0172 return; 0173 } 0174 reserveTouchCallBack(TouchCallback(action, callback)); 0175 } 0176 0177 void Edge::reserveTouchCallBack(const TouchCallback &callback) 0178 { 0179 if (std::any_of(m_touchCallbacks.constBegin(), m_touchCallbacks.constEnd(), [callback](const TouchCallback &c) { 0180 return c.touchUpAction() == callback.touchUpAction(); 0181 })) { 0182 return; 0183 } 0184 connect(callback.touchUpAction(), &QAction::destroyed, this, [this, callback]() { 0185 unreserveTouchCallBack(callback.touchUpAction()); 0186 }); 0187 m_touchCallbacks << callback; 0188 reserve(); 0189 } 0190 0191 void Edge::unreserveTouchCallBack(QAction *action) 0192 { 0193 auto it = std::find_if(m_touchCallbacks.begin(), m_touchCallbacks.end(), [action](const TouchCallback &c) { 0194 return c.touchUpAction() == action; 0195 }); 0196 if (it != m_touchCallbacks.end()) { 0197 m_touchCallbacks.erase(it); 0198 unreserve(); 0199 } 0200 } 0201 0202 void Edge::unreserve() 0203 { 0204 m_reserved--; 0205 if (m_reserved == 0) { 0206 // got deactivated 0207 stopApproaching(); 0208 deactivate(); 0209 } 0210 } 0211 void Edge::unreserve(QObject *object) 0212 { 0213 if (m_callBacks.remove(object) > 0) { 0214 disconnect(object, &QObject::destroyed, this, qOverload<QObject *>(&Edge::unreserve)); 0215 unreserve(); 0216 } 0217 } 0218 0219 bool Edge::activatesForPointer() const 0220 { 0221 if (m_client) { 0222 return true; 0223 } 0224 if (m_edges->isDesktopSwitching()) { 0225 return true; 0226 } 0227 if (m_edges->isDesktopSwitchingMovingClients()) { 0228 auto c = Workspace::self()->moveResizeWindow(); 0229 if (c && !c->isInteractiveResize()) { 0230 return true; 0231 } 0232 } 0233 if (!m_callBacks.isEmpty()) { 0234 return true; 0235 } 0236 if (m_action != ElectricActionNone) { 0237 return true; 0238 } 0239 return false; 0240 } 0241 0242 bool Edge::activatesForTouchGesture() const 0243 { 0244 if (!isScreenEdge()) { 0245 return false; 0246 } 0247 if (m_blocked) { 0248 return false; 0249 } 0250 if (m_client) { 0251 return true; 0252 } 0253 if (m_touchAction != ElectricActionNone) { 0254 return true; 0255 } 0256 if (!m_touchCallbacks.isEmpty()) { 0257 return true; 0258 } 0259 return false; 0260 } 0261 0262 bool Edge::triggersFor(const QPoint &cursorPos) const 0263 { 0264 if (isBlocked()) { 0265 return false; 0266 } 0267 if (!activatesForPointer()) { 0268 return false; 0269 } 0270 if (!m_geometry.contains(cursorPos)) { 0271 return false; 0272 } 0273 if (isLeft() && cursorPos.x() != m_geometry.x()) { 0274 return false; 0275 } 0276 if (isRight() && cursorPos.x() != (m_geometry.x() + m_geometry.width() - 1)) { 0277 return false; 0278 } 0279 if (isTop() && cursorPos.y() != m_geometry.y()) { 0280 return false; 0281 } 0282 if (isBottom() && cursorPos.y() != (m_geometry.y() + m_geometry.height() - 1)) { 0283 return false; 0284 } 0285 return true; 0286 } 0287 0288 bool Edge::check(const QPoint &cursorPos, const QDateTime &triggerTime, bool forceNoPushBack) 0289 { 0290 if (!triggersFor(cursorPos)) { 0291 return false; 0292 } 0293 if (m_lastTrigger.isValid() && // still in cooldown 0294 m_lastTrigger.msecsTo(triggerTime) < edges()->reActivationThreshold() - edges()->timeThreshold()) { 0295 // Reset the time, so the user has to actually keep the mouse still for this long to retrigger 0296 m_lastTrigger = triggerTime; 0297 return false; 0298 } 0299 // no pushback so we have to activate at once 0300 bool directActivate = forceNoPushBack || edges()->cursorPushBackDistance().isNull() || m_client; 0301 if (directActivate || canActivate(cursorPos, triggerTime)) { 0302 markAsTriggered(cursorPos, triggerTime); 0303 handle(cursorPos); 0304 return true; 0305 } else { 0306 pushCursorBack(cursorPos); 0307 m_triggeredPoint = cursorPos; 0308 } 0309 return false; 0310 } 0311 0312 void Edge::markAsTriggered(const QPoint &cursorPos, const QDateTime &triggerTime) 0313 { 0314 m_lastTrigger = triggerTime; 0315 m_lastReset = QDateTime(); // invalidate 0316 m_triggeredPoint = cursorPos; 0317 } 0318 0319 bool Edge::canActivate(const QPoint &cursorPos, const QDateTime &triggerTime) 0320 { 0321 // we check whether either the timer has explicitly been invalidated (successful trigger) or is 0322 // bigger than the reactivation threshold (activation "aborted", usually due to moving away the cursor 0323 // from the corner after successful activation) 0324 // either condition means that "this is the first event in a new attempt" 0325 if (!m_lastReset.isValid() || m_lastReset.msecsTo(triggerTime) > edges()->reActivationThreshold()) { 0326 m_lastReset = triggerTime; 0327 return false; 0328 } 0329 if (m_lastTrigger.isValid() && m_lastTrigger.msecsTo(triggerTime) < edges()->reActivationThreshold() - edges()->timeThreshold()) { 0330 return false; 0331 } 0332 if (m_lastReset.msecsTo(triggerTime) < edges()->timeThreshold()) { 0333 return false; 0334 } 0335 // does the check on position make any sense at all? 0336 if ((cursorPos - m_triggeredPoint).manhattanLength() > DISTANCE_RESET) { 0337 return false; 0338 } 0339 return true; 0340 } 0341 0342 void Edge::handle(const QPoint &cursorPos) 0343 { 0344 Window *movingClient = Workspace::self()->moveResizeWindow(); 0345 if ((edges()->isDesktopSwitchingMovingClients() && movingClient && !movingClient->isInteractiveResize()) || (edges()->isDesktopSwitching() && isScreenEdge())) { 0346 // always switch desktops in case: 0347 // moving a Client and option for switch on client move is enabled 0348 // or switch on screen edge is enabled 0349 switchDesktop(cursorPos); 0350 return; 0351 } 0352 if (movingClient) { 0353 // if we are moving a window we don't want to trigger the actions. This just results in 0354 // problems, e.g. Desktop Grid activated or screen locker activated which just cannot 0355 // work as we hold a grab. 0356 return; 0357 } 0358 0359 if (m_client) { 0360 pushCursorBack(cursorPos); 0361 m_client->showOnScreenEdge(); 0362 unreserve(); 0363 return; 0364 } 0365 0366 if (handlePointerAction() || handleByCallback()) { 0367 pushCursorBack(cursorPos); 0368 return; 0369 } 0370 if (edges()->isDesktopSwitching() && isCorner()) { 0371 // try again desktop switching for the corner 0372 switchDesktop(cursorPos); 0373 } 0374 } 0375 0376 bool Edge::handleAction(ElectricBorderAction action) 0377 { 0378 switch (action) { 0379 case ElectricActionShowDesktop: { 0380 Workspace::self()->setShowingDesktop(!Workspace::self()->showingDesktop()); 0381 return true; 0382 } 0383 case ElectricActionLockScreen: { // Lock the screen 0384 #if KWIN_BUILD_SCREENLOCKER 0385 OrgFreedesktopScreenSaverInterface interface(QStringLiteral("org.freedesktop.ScreenSaver"), 0386 QStringLiteral("/ScreenSaver"), 0387 QDBusConnection::sessionBus()); 0388 if (interface.isValid()) { 0389 interface.Lock(); 0390 } 0391 return true; 0392 #else 0393 return false; 0394 #endif 0395 } 0396 case ElectricActionKRunner: { // open krunner 0397 QDBusConnection::sessionBus().asyncCall( 0398 QDBusMessage::createMethodCall(QStringLiteral("org.kde.krunner"), 0399 QStringLiteral("/App"), 0400 QStringLiteral("org.kde.krunner.App"), 0401 QStringLiteral("display"))); 0402 return true; 0403 } 0404 case ElectricActionActivityManager: { // open activity manager 0405 QDBusConnection::sessionBus().asyncCall( 0406 QDBusMessage::createMethodCall(QStringLiteral("org.kde.plasmashell"), 0407 QStringLiteral("/PlasmaShell"), 0408 QStringLiteral("org.kde.PlasmaShell"), 0409 QStringLiteral("toggleActivityManager"))); 0410 return true; 0411 } 0412 case ElectricActionApplicationLauncher: { 0413 QDBusConnection::sessionBus().asyncCall( 0414 QDBusMessage::createMethodCall(QStringLiteral("org.kde.plasmashell"), 0415 QStringLiteral("/PlasmaShell"), 0416 QStringLiteral("org.kde.PlasmaShell"), 0417 QStringLiteral("activateLauncherMenu"))); 0418 return true; 0419 } 0420 default: 0421 return false; 0422 } 0423 } 0424 0425 bool Edge::handleByCallback() 0426 { 0427 if (m_callBacks.isEmpty()) { 0428 return false; 0429 } 0430 for (auto it = m_callBacks.begin(); it != m_callBacks.end(); ++it) { 0431 bool retVal = false; 0432 QMetaObject::invokeMethod(it.key(), it.value().constData(), Q_RETURN_ARG(bool, retVal), Q_ARG(ElectricBorder, m_border)); 0433 if (retVal) { 0434 return true; 0435 } 0436 } 0437 return false; 0438 } 0439 0440 void Edge::handleTouchCallback() 0441 { 0442 if (!m_touchCallbacks.isEmpty()) { 0443 m_touchCallbacks.constFirst().touchUpAction()->trigger(); 0444 } 0445 } 0446 0447 void Edge::switchDesktop(const QPoint &cursorPos) 0448 { 0449 QPoint pos(cursorPos); 0450 VirtualDesktopManager *vds = VirtualDesktopManager::self(); 0451 VirtualDesktop *oldDesktop = vds->currentDesktop(); 0452 VirtualDesktop *desktop = oldDesktop; 0453 const int OFFSET = 2; 0454 if (isLeft()) { 0455 const VirtualDesktop *interimDesktop = desktop; 0456 desktop = vds->toLeft(desktop, vds->isNavigationWrappingAround()); 0457 if (desktop != interimDesktop) { 0458 pos.setX(workspace()->geometry().width() - 1 - OFFSET); 0459 } 0460 } else if (isRight()) { 0461 const VirtualDesktop *interimDesktop = desktop; 0462 desktop = vds->toRight(desktop, vds->isNavigationWrappingAround()); 0463 if (desktop != interimDesktop) { 0464 pos.setX(OFFSET); 0465 } 0466 } 0467 if (isTop()) { 0468 const VirtualDesktop *interimDesktop = desktop; 0469 desktop = vds->above(desktop, vds->isNavigationWrappingAround()); 0470 if (desktop != interimDesktop) { 0471 pos.setY(workspace()->geometry().height() - 1 - OFFSET); 0472 } 0473 } else if (isBottom()) { 0474 const VirtualDesktop *interimDesktop = desktop; 0475 desktop = vds->below(desktop, vds->isNavigationWrappingAround()); 0476 if (desktop != interimDesktop) { 0477 pos.setY(OFFSET); 0478 } 0479 } 0480 if (Window *c = Workspace::self()->moveResizeWindow()) { 0481 const QVector<VirtualDesktop *> desktops{desktop}; 0482 if (c->rules()->checkDesktops(desktops) != desktops) { 0483 // user attempts to move a client to another desktop where it is ruleforced to not be 0484 return; 0485 } 0486 } 0487 vds->setCurrent(desktop); 0488 if (vds->currentDesktop() != oldDesktop) { 0489 m_pushBackBlocked = true; 0490 Cursors::self()->mouse()->setPos(pos); 0491 QMetaObject::Connection *me = new QMetaObject::Connection(); 0492 *me = QObject::connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, this, [this, me]() { 0493 QObject::disconnect(*me); 0494 delete me; 0495 m_pushBackBlocked = false; 0496 }); 0497 } 0498 } 0499 0500 void Edge::pushCursorBack(const QPoint &cursorPos) 0501 { 0502 if (m_pushBackBlocked) { 0503 return; 0504 } 0505 int x = cursorPos.x(); 0506 int y = cursorPos.y(); 0507 const QSize &distance = edges()->cursorPushBackDistance(); 0508 if (isLeft()) { 0509 x += distance.width(); 0510 } 0511 if (isRight()) { 0512 x -= distance.width(); 0513 } 0514 if (isTop()) { 0515 y += distance.height(); 0516 } 0517 if (isBottom()) { 0518 y -= distance.height(); 0519 } 0520 Cursors::self()->mouse()->setPos(x, y); 0521 } 0522 0523 void Edge::setGeometry(const QRect &geometry) 0524 { 0525 if (m_geometry == geometry) { 0526 return; 0527 } 0528 m_geometry = geometry; 0529 int x = m_geometry.x(); 0530 int y = m_geometry.y(); 0531 int width = m_geometry.width(); 0532 int height = m_geometry.height(); 0533 const int offset = m_edges->cornerOffset(); 0534 if (isCorner()) { 0535 if (isRight()) { 0536 x = x + width - offset; 0537 } 0538 if (isBottom()) { 0539 y = y + height - offset; 0540 } 0541 width = offset; 0542 height = offset; 0543 } else { 0544 if (isLeft()) { 0545 y += offset; 0546 width = offset; 0547 height = height - offset * 2; 0548 } else if (isRight()) { 0549 x = x + width - offset; 0550 y += offset; 0551 width = offset; 0552 height = height - offset * 2; 0553 } else if (isTop()) { 0554 x += offset; 0555 width = width - offset * 2; 0556 height = offset; 0557 } else if (isBottom()) { 0558 x += offset; 0559 y = y + height - offset; 0560 width = width - offset * 2; 0561 height = offset; 0562 } 0563 } 0564 m_approachGeometry = QRect(x, y, width, height); 0565 doGeometryUpdate(); 0566 0567 if (isScreenEdge()) { 0568 const Output *output = workspace()->outputAt(m_geometry.center()); 0569 m_gesture->setStartGeometry(m_geometry); 0570 m_gesture->setMinimumDelta(QPointF(MINIMUM_DELTA, MINIMUM_DELTA) / output->scale()); 0571 } 0572 } 0573 0574 void Edge::checkBlocking() 0575 { 0576 Window *client = Workspace::self()->activeWindow(); 0577 const bool newValue = !m_edges->remainActiveOnFullscreen() && client && client->isFullScreen() && client->frameGeometry().contains(m_geometry.center()) && !(effects && effects->hasActiveFullScreenEffect()); 0578 if (newValue == m_blocked) { 0579 return; 0580 } 0581 const bool wasTouch = activatesForTouchGesture(); 0582 m_blocked = newValue; 0583 if (m_blocked && m_approaching) { 0584 stopApproaching(); 0585 } 0586 if (wasTouch != activatesForTouchGesture()) { 0587 Q_EMIT activatesForTouchGestureChanged(); 0588 } 0589 doUpdateBlocking(); 0590 } 0591 0592 void Edge::doUpdateBlocking() 0593 { 0594 } 0595 0596 void Edge::doGeometryUpdate() 0597 { 0598 } 0599 0600 void Edge::activate() 0601 { 0602 if (activatesForTouchGesture()) { 0603 m_edges->gestureRecognizer()->registerSwipeGesture(m_gesture.get()); 0604 } 0605 doActivate(); 0606 } 0607 0608 void Edge::doActivate() 0609 { 0610 } 0611 0612 void Edge::deactivate() 0613 { 0614 m_edges->gestureRecognizer()->unregisterSwipeGesture(m_gesture.get()); 0615 doDeactivate(); 0616 } 0617 0618 void Edge::doDeactivate() 0619 { 0620 } 0621 0622 void Edge::startApproaching() 0623 { 0624 if (m_approaching) { 0625 return; 0626 } 0627 m_approaching = true; 0628 doStartApproaching(); 0629 m_lastApproachingFactor = 0; 0630 Q_EMIT approaching(border(), 0.0, m_approachGeometry); 0631 } 0632 0633 void Edge::doStartApproaching() 0634 { 0635 } 0636 0637 void Edge::stopApproaching() 0638 { 0639 if (!m_approaching) { 0640 return; 0641 } 0642 m_approaching = false; 0643 doStopApproaching(); 0644 m_lastApproachingFactor = 0; 0645 Q_EMIT approaching(border(), 0.0, m_approachGeometry); 0646 } 0647 0648 void Edge::doStopApproaching() 0649 { 0650 } 0651 0652 void Edge::updateApproaching(const QPoint &point) 0653 { 0654 if (approachGeometry().contains(point)) { 0655 int factor = 0; 0656 const int edgeDistance = m_edges->cornerOffset(); 0657 auto cornerDistance = [=](const QPoint &corner) { 0658 return std::max(std::abs(corner.x() - point.x()), std::abs(corner.y() - point.y())); 0659 }; 0660 switch (border()) { 0661 case ElectricTopLeft: 0662 factor = (cornerDistance(approachGeometry().topLeft()) << 8) / edgeDistance; 0663 break; 0664 case ElectricTopRight: 0665 factor = (cornerDistance(approachGeometry().topRight()) << 8) / edgeDistance; 0666 break; 0667 case ElectricBottomRight: 0668 factor = (cornerDistance(approachGeometry().bottomRight()) << 8) / edgeDistance; 0669 break; 0670 case ElectricBottomLeft: 0671 factor = (cornerDistance(approachGeometry().bottomLeft()) << 8) / edgeDistance; 0672 break; 0673 case ElectricTop: 0674 factor = (std::abs(point.y() - approachGeometry().y()) << 8) / edgeDistance; 0675 break; 0676 case ElectricRight: 0677 factor = (std::abs(point.x() - approachGeometry().right()) << 8) / edgeDistance; 0678 break; 0679 case ElectricBottom: 0680 factor = (std::abs(point.y() - approachGeometry().bottom()) << 8) / edgeDistance; 0681 break; 0682 case ElectricLeft: 0683 factor = (std::abs(point.x() - approachGeometry().x()) << 8) / edgeDistance; 0684 break; 0685 default: 0686 break; 0687 } 0688 factor = 256 - factor; 0689 if (m_lastApproachingFactor != factor) { 0690 m_lastApproachingFactor = factor; 0691 Q_EMIT approaching(border(), m_lastApproachingFactor / 256.0f, m_approachGeometry); 0692 } 0693 } else { 0694 stopApproaching(); 0695 } 0696 } 0697 0698 quint32 Edge::window() const 0699 { 0700 return 0; 0701 } 0702 0703 quint32 Edge::approachWindow() const 0704 { 0705 return 0; 0706 } 0707 0708 void Edge::setBorder(ElectricBorder border) 0709 { 0710 m_border = border; 0711 switch (m_border) { 0712 case ElectricTop: 0713 m_gesture->setDirection(SwipeGesture::Direction::Down); 0714 break; 0715 case ElectricRight: 0716 m_gesture->setDirection(SwipeGesture::Direction::Left); 0717 break; 0718 case ElectricBottom: 0719 m_gesture->setDirection(SwipeGesture::Direction::Up); 0720 break; 0721 case ElectricLeft: 0722 m_gesture->setDirection(SwipeGesture::Direction::Right); 0723 break; 0724 default: 0725 break; 0726 } 0727 } 0728 0729 void Edge::setTouchAction(ElectricBorderAction action) 0730 { 0731 const bool wasTouch = activatesForTouchGesture(); 0732 m_touchAction = action; 0733 if (wasTouch != activatesForTouchGesture()) { 0734 Q_EMIT activatesForTouchGestureChanged(); 0735 } 0736 } 0737 0738 void Edge::setClient(Window *client) 0739 { 0740 const bool wasTouch = activatesForTouchGesture(); 0741 m_client = client; 0742 if (wasTouch != activatesForTouchGesture()) { 0743 Q_EMIT activatesForTouchGestureChanged(); 0744 } 0745 } 0746 0747 void Edge::setOutput(Output *output) 0748 { 0749 m_output = output; 0750 } 0751 0752 Output *Edge::output() const 0753 { 0754 return m_output; 0755 } 0756 0757 /********************************************************** 0758 * ScreenEdges 0759 *********************************************************/ 0760 0761 ScreenEdges::ScreenEdges() 0762 : m_desktopSwitching(false) 0763 , m_desktopSwitchingMovingClients(false) 0764 , m_timeThreshold(0) 0765 , m_reactivateThreshold(0) 0766 , m_virtualDesktopLayout({}) 0767 , m_actionTopLeft(ElectricActionNone) 0768 , m_actionTop(ElectricActionNone) 0769 , m_actionTopRight(ElectricActionNone) 0770 , m_actionRight(ElectricActionNone) 0771 , m_actionBottomRight(ElectricActionNone) 0772 , m_actionBottom(ElectricActionNone) 0773 , m_actionBottomLeft(ElectricActionNone) 0774 , m_actionLeft(ElectricActionNone) 0775 , m_gestureRecognizer(new GestureRecognizer(this)) 0776 { 0777 const int gridUnit = QFontMetrics(QFontDatabase::systemFont(QFontDatabase::GeneralFont)).boundingRect(QLatin1Char('M')).height(); 0778 m_cornerOffset = 4 * gridUnit; 0779 0780 connect(workspace(), &Workspace::windowRemoved, this, &ScreenEdges::deleteEdgeForClient); 0781 } 0782 0783 void ScreenEdges::init() 0784 { 0785 reconfigure(); 0786 updateLayout(); 0787 recreateEdges(); 0788 } 0789 static ElectricBorderAction electricBorderAction(const QString &name) 0790 { 0791 QString lowerName = name.toLower(); 0792 if (lowerName == QStringLiteral("showdesktop")) { 0793 return ElectricActionShowDesktop; 0794 } else if (lowerName == QStringLiteral("lockscreen")) { 0795 return ElectricActionLockScreen; 0796 } else if (lowerName == QLatin1String("krunner")) { 0797 return ElectricActionKRunner; 0798 } else if (lowerName == QLatin1String("activitymanager")) { 0799 return ElectricActionActivityManager; 0800 } else if (lowerName == QLatin1String("applicationlauncher")) { 0801 return ElectricActionApplicationLauncher; 0802 } 0803 return ElectricActionNone; 0804 } 0805 0806 void ScreenEdges::reconfigure() 0807 { 0808 if (!m_config) { 0809 return; 0810 } 0811 KConfigGroup screenEdgesConfig = m_config->group("ScreenEdges"); 0812 setRemainActiveOnFullscreen(screenEdgesConfig.readEntry("RemainActiveOnFullscreen", false)); 0813 0814 // TODO: migrate settings to a group ScreenEdges 0815 KConfigGroup windowsConfig = m_config->group("Windows"); 0816 setTimeThreshold(windowsConfig.readEntry("ElectricBorderDelay", 150)); 0817 setReActivationThreshold(std::max(timeThreshold() + 50, windowsConfig.readEntry("ElectricBorderCooldown", 350))); 0818 int desktopSwitching = windowsConfig.readEntry("ElectricBorders", static_cast<int>(ElectricDisabled)); 0819 if (desktopSwitching == ElectricDisabled) { 0820 setDesktopSwitching(false); 0821 setDesktopSwitchingMovingClients(false); 0822 } else if (desktopSwitching == ElectricMoveOnly) { 0823 setDesktopSwitching(false); 0824 setDesktopSwitchingMovingClients(true); 0825 } else if (desktopSwitching == ElectricAlways) { 0826 setDesktopSwitching(true); 0827 setDesktopSwitchingMovingClients(true); 0828 } 0829 const int pushBack = windowsConfig.readEntry("ElectricBorderPushbackPixels", 1); 0830 m_cursorPushBackDistance = QSize(pushBack, pushBack); 0831 0832 KConfigGroup borderConfig = m_config->group("ElectricBorders"); 0833 setActionForBorder(ElectricTopLeft, &m_actionTopLeft, 0834 electricBorderAction(borderConfig.readEntry("TopLeft", "None"))); 0835 setActionForBorder(ElectricTop, &m_actionTop, 0836 electricBorderAction(borderConfig.readEntry("Top", "None"))); 0837 setActionForBorder(ElectricTopRight, &m_actionTopRight, 0838 electricBorderAction(borderConfig.readEntry("TopRight", "None"))); 0839 setActionForBorder(ElectricRight, &m_actionRight, 0840 electricBorderAction(borderConfig.readEntry("Right", "None"))); 0841 setActionForBorder(ElectricBottomRight, &m_actionBottomRight, 0842 electricBorderAction(borderConfig.readEntry("BottomRight", "None"))); 0843 setActionForBorder(ElectricBottom, &m_actionBottom, 0844 electricBorderAction(borderConfig.readEntry("Bottom", "None"))); 0845 setActionForBorder(ElectricBottomLeft, &m_actionBottomLeft, 0846 electricBorderAction(borderConfig.readEntry("BottomLeft", "None"))); 0847 setActionForBorder(ElectricLeft, &m_actionLeft, 0848 electricBorderAction(borderConfig.readEntry("Left", "None"))); 0849 0850 borderConfig = m_config->group("TouchEdges"); 0851 setActionForTouchBorder(ElectricTop, electricBorderAction(borderConfig.readEntry("Top", "None"))); 0852 setActionForTouchBorder(ElectricRight, electricBorderAction(borderConfig.readEntry("Right", "None"))); 0853 setActionForTouchBorder(ElectricBottom, electricBorderAction(borderConfig.readEntry("Bottom", "None"))); 0854 setActionForTouchBorder(ElectricLeft, electricBorderAction(borderConfig.readEntry("Left", "None"))); 0855 } 0856 0857 void ScreenEdges::setActionForBorder(ElectricBorder border, ElectricBorderAction *oldValue, ElectricBorderAction newValue) 0858 { 0859 if (*oldValue == newValue) { 0860 return; 0861 } 0862 if (*oldValue == ElectricActionNone) { 0863 // have to reserve 0864 for (const auto &edge : m_edges) { 0865 if (edge->border() == border) { 0866 edge->reserve(); 0867 } 0868 } 0869 } 0870 if (newValue == ElectricActionNone) { 0871 // have to unreserve 0872 for (const auto &edge : m_edges) { 0873 if (edge->border() == border) { 0874 edge->unreserve(); 0875 } 0876 } 0877 } 0878 *oldValue = newValue; 0879 // update action on all Edges for given border 0880 for (const auto &edge : m_edges) { 0881 if (edge->border() == border) { 0882 edge->setAction(newValue); 0883 } 0884 } 0885 } 0886 0887 void ScreenEdges::setActionForTouchBorder(ElectricBorder border, ElectricBorderAction newValue) 0888 { 0889 auto it = m_touchCallbacks.find(border); 0890 ElectricBorderAction oldValue = ElectricActionNone; 0891 if (it != m_touchCallbacks.end()) { 0892 oldValue = it.value(); 0893 } 0894 if (oldValue == newValue) { 0895 return; 0896 } 0897 if (oldValue == ElectricActionNone) { 0898 // have to reserve 0899 for (const auto &edge : m_edges) { 0900 if (edge->border() == border) { 0901 edge->reserve(); 0902 } 0903 } 0904 } 0905 if (newValue == ElectricActionNone) { 0906 // have to unreserve 0907 for (const auto &edge : m_edges) { 0908 if (edge->border() == border) { 0909 edge->unreserve(); 0910 } 0911 } 0912 0913 m_touchCallbacks.erase(it); 0914 } else { 0915 m_touchCallbacks.insert(border, newValue); 0916 } 0917 // update action on all Edges for given border 0918 for (const auto &edge : m_edges) { 0919 if (edge->border() == border) { 0920 edge->setTouchAction(newValue); 0921 } 0922 } 0923 } 0924 0925 void ScreenEdges::updateLayout() 0926 { 0927 const QSize desktopMatrix = VirtualDesktopManager::self()->grid().size(); 0928 Qt::Orientations newLayout = {}; 0929 if (desktopMatrix.width() > 1) { 0930 newLayout |= Qt::Horizontal; 0931 } 0932 if (desktopMatrix.height() > 1) { 0933 newLayout |= Qt::Vertical; 0934 } 0935 if (newLayout == m_virtualDesktopLayout) { 0936 return; 0937 } 0938 if (isDesktopSwitching()) { 0939 reserveDesktopSwitching(false, m_virtualDesktopLayout); 0940 } 0941 m_virtualDesktopLayout = newLayout; 0942 if (isDesktopSwitching()) { 0943 reserveDesktopSwitching(true, m_virtualDesktopLayout); 0944 } 0945 } 0946 0947 static bool isLeftScreen(const QRect &screen, const QRect &fullArea) 0948 { 0949 const auto outputs = workspace()->outputs(); 0950 if (outputs.count() == 1) { 0951 return true; 0952 } 0953 if (screen.x() == fullArea.x()) { 0954 return true; 0955 } 0956 // If any other screen has a right edge against our left edge, then this screen is not a left screen 0957 for (const Output *output : outputs) { 0958 const QRect otherGeo = output->geometry(); 0959 if (otherGeo == screen) { 0960 // that's our screen to test 0961 continue; 0962 } 0963 if (screen.x() == otherGeo.x() + otherGeo.width() 0964 && screen.y() < otherGeo.y() + otherGeo.height() 0965 && screen.y() + screen.height() > otherGeo.y()) { 0966 // There is a screen to the left 0967 return false; 0968 } 0969 } 0970 // No screen exists to the left, so this is a left screen 0971 return true; 0972 } 0973 0974 static bool isRightScreen(const QRect &screen, const QRect &fullArea) 0975 { 0976 const auto outputs = workspace()->outputs(); 0977 if (outputs.count() == 1) { 0978 return true; 0979 } 0980 if (screen.x() + screen.width() == fullArea.x() + fullArea.width()) { 0981 return true; 0982 } 0983 // If any other screen has any left edge against any of our right edge, then this screen is not a right screen 0984 for (const Output *output : outputs) { 0985 const QRect otherGeo = output->geometry(); 0986 if (otherGeo == screen) { 0987 // that's our screen to test 0988 continue; 0989 } 0990 if (screen.x() + screen.width() == otherGeo.x() 0991 && screen.y() < otherGeo.y() + otherGeo.height() 0992 && screen.y() + screen.height() > otherGeo.y()) { 0993 // There is a screen to the right 0994 return false; 0995 } 0996 } 0997 // No screen exists to the right, so this is a right screen 0998 return true; 0999 } 1000 1001 static bool isTopScreen(const QRect &screen, const QRect &fullArea) 1002 { 1003 const auto outputs = workspace()->outputs(); 1004 if (outputs.count() == 1) { 1005 return true; 1006 } 1007 if (screen.y() == fullArea.y()) { 1008 return true; 1009 } 1010 // If any other screen has any bottom edge against any of our top edge, then this screen is not a top screen 1011 for (const Output *output : outputs) { 1012 const QRect otherGeo = output->geometry(); 1013 if (otherGeo == screen) { 1014 // that's our screen to test 1015 continue; 1016 } 1017 if (screen.y() == otherGeo.y() + otherGeo.height() 1018 && screen.x() < otherGeo.x() + otherGeo.width() 1019 && screen.x() + screen.width() > otherGeo.x()) { 1020 // There is a screen to the top 1021 return false; 1022 } 1023 } 1024 // No screen exists to the top, so this is a top screen 1025 return true; 1026 } 1027 1028 static bool isBottomScreen(const QRect &screen, const QRect &fullArea) 1029 { 1030 const auto outputs = workspace()->outputs(); 1031 if (outputs.count() == 1) { 1032 return true; 1033 } 1034 if (screen.y() + screen.height() == fullArea.y() + fullArea.height()) { 1035 return true; 1036 } 1037 // If any other screen has any top edge against any of our bottom edge, then this screen is not a bottom screen 1038 for (const Output *output : outputs) { 1039 const QRect otherGeo = output->geometry(); 1040 if (otherGeo == screen) { 1041 // that's our screen to test 1042 continue; 1043 } 1044 if (screen.y() + screen.height() == otherGeo.y() 1045 && screen.x() < otherGeo.x() + otherGeo.width() 1046 && screen.x() + screen.width() > otherGeo.x()) { 1047 // There is a screen to the bottom 1048 return false; 1049 } 1050 } 1051 // No screen exists to the bottom, so this is a bottom screen 1052 return true; 1053 } 1054 1055 bool ScreenEdges::remainActiveOnFullscreen() const 1056 { 1057 return m_remainActiveOnFullscreen; 1058 } 1059 1060 void ScreenEdges::recreateEdges() 1061 { 1062 std::vector<std::unique_ptr<Edge>> oldEdges = std::move(m_edges); 1063 m_edges.clear(); 1064 const QRect fullArea = workspace()->geometry(); 1065 QRegion processedRegion; 1066 1067 const auto outputs = workspace()->outputs(); 1068 for (Output *output : outputs) { 1069 const QRegion screen = QRegion(output->geometry()).subtracted(processedRegion); 1070 processedRegion += screen; 1071 for (const QRect &screenPart : screen) { 1072 if (isLeftScreen(screenPart, fullArea)) { 1073 // left most screen 1074 createVerticalEdge(ElectricLeft, screenPart, fullArea, output); 1075 } 1076 if (isRightScreen(screenPart, fullArea)) { 1077 // right most screen 1078 createVerticalEdge(ElectricRight, screenPart, fullArea, output); 1079 } 1080 if (isTopScreen(screenPart, fullArea)) { 1081 // top most screen 1082 createHorizontalEdge(ElectricTop, screenPart, fullArea, output); 1083 } 1084 if (isBottomScreen(screenPart, fullArea)) { 1085 // bottom most screen 1086 createHorizontalEdge(ElectricBottom, screenPart, fullArea, output); 1087 } 1088 } 1089 } 1090 // copy over the effect/script reservations from the old edges 1091 for (const auto &edge : m_edges) { 1092 for (const auto &oldEdge : oldEdges) { 1093 if (oldEdge->client()) { 1094 // show the client again and don't recreate the edge 1095 oldEdge->client()->showOnScreenEdge(); 1096 continue; 1097 } 1098 if (oldEdge->border() != edge->border()) { 1099 continue; 1100 } 1101 const QHash<QObject *, QByteArray> &callbacks = oldEdge->callBacks(); 1102 for (auto callback = callbacks.begin(); callback != callbacks.end(); callback++) { 1103 edge->reserve(callback.key(), callback.value().constData()); 1104 } 1105 const auto touchCallBacks = oldEdge->touchCallBacks(); 1106 for (auto c : touchCallBacks) { 1107 edge->reserveTouchCallBack(c); 1108 } 1109 } 1110 } 1111 } 1112 1113 void ScreenEdges::createVerticalEdge(ElectricBorder border, const QRect &screen, const QRect &fullArea, Output *output) 1114 { 1115 if (border != ElectricRight && border != KWin::ElectricLeft) { 1116 return; 1117 } 1118 int y = screen.y(); 1119 int height = screen.height(); 1120 const int x = (border == ElectricLeft) ? screen.x() : screen.x() + screen.width() - TOUCH_TARGET; 1121 if (isTopScreen(screen, fullArea)) { 1122 // also top most screen 1123 height -= m_cornerOffset; 1124 y += m_cornerOffset; 1125 // create top left/right edge 1126 const ElectricBorder edge = (border == ElectricLeft) ? ElectricTopLeft : ElectricTopRight; 1127 m_edges.push_back(createEdge(edge, x, screen.y(), TOUCH_TARGET, TOUCH_TARGET, output)); 1128 } 1129 if (isBottomScreen(screen, fullArea)) { 1130 // also bottom most screen 1131 height -= m_cornerOffset; 1132 // create bottom left/right edge 1133 const ElectricBorder edge = (border == ElectricLeft) ? ElectricBottomLeft : ElectricBottomRight; 1134 m_edges.push_back(createEdge(edge, x, screen.y() + screen.height() - TOUCH_TARGET, TOUCH_TARGET, TOUCH_TARGET, output)); 1135 } 1136 if (height <= m_cornerOffset) { 1137 // An overlap with another output is near complete. We ignore this border. 1138 return; 1139 } 1140 m_edges.push_back(createEdge(border, x, y, TOUCH_TARGET, height, output)); 1141 } 1142 1143 void ScreenEdges::createHorizontalEdge(ElectricBorder border, const QRect &screen, const QRect &fullArea, Output *output) 1144 { 1145 if (border != ElectricTop && border != ElectricBottom) { 1146 return; 1147 } 1148 int x = screen.x(); 1149 int width = screen.width(); 1150 if (isLeftScreen(screen, fullArea)) { 1151 // also left most - adjust only x and width 1152 x += m_cornerOffset; 1153 width -= m_cornerOffset; 1154 } 1155 if (isRightScreen(screen, fullArea)) { 1156 // also right most edge 1157 width -= m_cornerOffset; 1158 } 1159 if (width <= m_cornerOffset) { 1160 // An overlap with another output is near complete. We ignore this border. 1161 return; 1162 } 1163 const int y = (border == ElectricTop) ? screen.y() : screen.y() + screen.height() - TOUCH_TARGET; 1164 m_edges.push_back(createEdge(border, x, y, width, TOUCH_TARGET, output)); 1165 } 1166 1167 std::unique_ptr<Edge> ScreenEdges::createEdge(ElectricBorder border, int x, int y, int width, int height, Output *output, bool createAction) 1168 { 1169 std::unique_ptr<Edge> edge = kwinApp()->createScreenEdge(this); 1170 // Edges can not have negative size. 1171 Q_ASSERT(width >= 0); 1172 Q_ASSERT(height >= 0); 1173 1174 edge->setBorder(border); 1175 edge->setGeometry(QRect(x, y, width, height)); 1176 edge->setOutput(output); 1177 if (createAction) { 1178 const ElectricBorderAction action = actionForEdge(edge.get()); 1179 if (action != KWin::ElectricActionNone) { 1180 edge->reserve(); 1181 edge->setAction(action); 1182 } 1183 const ElectricBorderAction touchAction = actionForTouchEdge(edge.get()); 1184 if (touchAction != KWin::ElectricActionNone) { 1185 edge->reserve(); 1186 edge->setTouchAction(touchAction); 1187 } 1188 } 1189 if (isDesktopSwitching()) { 1190 if (edge->isCorner()) { 1191 edge->reserve(); 1192 } else { 1193 if ((m_virtualDesktopLayout & Qt::Horizontal) && (edge->isLeft() || edge->isRight())) { 1194 edge->reserve(); 1195 } 1196 if ((m_virtualDesktopLayout & Qt::Vertical) && (edge->isTop() || edge->isBottom())) { 1197 edge->reserve(); 1198 } 1199 } 1200 } 1201 connect(edge.get(), &Edge::approaching, this, &ScreenEdges::approaching); 1202 connect(this, &ScreenEdges::checkBlocking, edge.get(), &Edge::checkBlocking); 1203 return edge; 1204 } 1205 1206 ElectricBorderAction ScreenEdges::actionForEdge(Edge *edge) const 1207 { 1208 switch (edge->border()) { 1209 case ElectricTopLeft: 1210 return m_actionTopLeft; 1211 case ElectricTop: 1212 return m_actionTop; 1213 case ElectricTopRight: 1214 return m_actionTopRight; 1215 case ElectricRight: 1216 return m_actionRight; 1217 case ElectricBottomRight: 1218 return m_actionBottomRight; 1219 case ElectricBottom: 1220 return m_actionBottom; 1221 case ElectricBottomLeft: 1222 return m_actionBottomLeft; 1223 case ElectricLeft: 1224 return m_actionLeft; 1225 default: 1226 // fall through 1227 break; 1228 } 1229 return ElectricActionNone; 1230 } 1231 1232 ElectricBorderAction ScreenEdges::actionForTouchEdge(Edge *edge) const 1233 { 1234 auto it = m_touchCallbacks.find(edge->border()); 1235 if (it != m_touchCallbacks.end()) { 1236 return it.value(); 1237 } 1238 return ElectricActionNone; 1239 } 1240 1241 ElectricBorderAction ScreenEdges::actionForTouchBorder(ElectricBorder border) const 1242 { 1243 return m_touchCallbacks.value(border); 1244 } 1245 1246 void ScreenEdges::reserveDesktopSwitching(bool isToReserve, Qt::Orientations o) 1247 { 1248 if (!o) { 1249 return; 1250 } 1251 for (const auto &edge : m_edges) { 1252 if (edge->isCorner()) { 1253 isToReserve ? edge->reserve() : edge->unreserve(); 1254 } else { 1255 if ((m_virtualDesktopLayout & Qt::Horizontal) && (edge->isLeft() || edge->isRight())) { 1256 isToReserve ? edge->reserve() : edge->unreserve(); 1257 } 1258 if ((m_virtualDesktopLayout & Qt::Vertical) && (edge->isTop() || edge->isBottom())) { 1259 isToReserve ? edge->reserve() : edge->unreserve(); 1260 } 1261 } 1262 } 1263 } 1264 1265 void ScreenEdges::reserve(ElectricBorder border, QObject *object, const char *slot) 1266 { 1267 for (const auto &edge : m_edges) { 1268 if (edge->border() == border) { 1269 edge->reserve(object, slot); 1270 } 1271 } 1272 } 1273 1274 void ScreenEdges::unreserve(ElectricBorder border, QObject *object) 1275 { 1276 for (const auto &edge : m_edges) { 1277 if (edge->border() == border) { 1278 edge->unreserve(object); 1279 } 1280 } 1281 } 1282 1283 void ScreenEdges::reserve(Window *client, ElectricBorder border) 1284 { 1285 const auto it = std::remove_if(m_edges.begin(), m_edges.end(), [client](const auto &edge) { 1286 return edge->client() == client; 1287 }); 1288 const bool hadBorder = it != m_edges.end(); 1289 m_edges.erase(it, m_edges.end()); 1290 1291 if (border != ElectricNone) { 1292 createEdgeForClient(client, border); 1293 } else { 1294 if (hadBorder) { // show again 1295 client->showOnScreenEdge(); 1296 } 1297 } 1298 } 1299 1300 void ScreenEdges::reserveTouch(ElectricBorder border, QAction *action, TouchCallback::CallbackFunction callback) 1301 { 1302 for (const auto &edge : m_edges) { 1303 if (edge->border() == border) { 1304 edge->reserveTouchCallBack(action, callback); 1305 } 1306 } 1307 } 1308 1309 void ScreenEdges::unreserveTouch(ElectricBorder border, QAction *action) 1310 { 1311 for (const auto &edge : m_edges) { 1312 if (edge->border() == border) { 1313 edge->unreserveTouchCallBack(action); 1314 } 1315 } 1316 } 1317 1318 void ScreenEdges::createEdgeForClient(Window *client, ElectricBorder border) 1319 { 1320 int y = 0; 1321 int x = 0; 1322 int width = 0; 1323 int height = 0; 1324 const QRect geo = client->frameGeometry().toRect(); 1325 const QRect fullArea = workspace()->geometry(); 1326 1327 const auto outputs = workspace()->outputs(); 1328 Output *foundOutput = nullptr; 1329 for (Output *output : outputs) { 1330 foundOutput = output; 1331 const QRect screen = output->geometry(); 1332 if (!screen.contains(geo)) { 1333 // ignoring Clients having a geometry overlapping with multiple screens 1334 // this would make the code more complex. If it's needed in future it can be added 1335 continue; 1336 } 1337 const bool bordersTop = (screen.y() == geo.y()); 1338 const bool bordersLeft = (screen.x() == geo.x()); 1339 const bool bordersBottom = (screen.y() + screen.height() == geo.y() + geo.height()); 1340 const bool bordersRight = (screen.x() + screen.width() == geo.x() + geo.width()); 1341 1342 if (bordersTop && border == ElectricTop) { 1343 if (!isTopScreen(screen, fullArea)) { 1344 continue; 1345 } 1346 y = geo.y(); 1347 x = geo.x(); 1348 height = 1; 1349 width = geo.width(); 1350 break; 1351 } 1352 if (bordersBottom && border == ElectricBottom) { 1353 if (!isBottomScreen(screen, fullArea)) { 1354 continue; 1355 } 1356 y = geo.y() + geo.height() - 1; 1357 x = geo.x(); 1358 height = 1; 1359 width = geo.width(); 1360 break; 1361 } 1362 if (bordersLeft && border == ElectricLeft) { 1363 if (!isLeftScreen(screen, fullArea)) { 1364 continue; 1365 } 1366 x = geo.x(); 1367 y = geo.y(); 1368 width = 1; 1369 height = geo.height(); 1370 break; 1371 } 1372 if (bordersRight && border == ElectricRight) { 1373 if (!isRightScreen(screen, fullArea)) { 1374 continue; 1375 } 1376 x = geo.x() + geo.width() - 1; 1377 y = geo.y(); 1378 width = 1; 1379 height = geo.height(); 1380 break; 1381 } 1382 } 1383 1384 if (width > 0 && height > 0) { 1385 m_edges.push_back(createEdge(border, x, y, width, height, foundOutput, false)); 1386 Edge *edge = m_edges.back().get(); 1387 edge->setClient(client); 1388 edge->reserve(); 1389 } else { 1390 // we could not create an edge window, so don't allow the window to hide 1391 client->showOnScreenEdge(); 1392 } 1393 } 1394 1395 void ScreenEdges::deleteEdgeForClient(Window *window) 1396 { 1397 const auto it = std::remove_if(m_edges.begin(), m_edges.end(), [window](const auto &edge) { 1398 return edge->client() == window; 1399 }); 1400 m_edges.erase(it, m_edges.end()); 1401 } 1402 1403 void ScreenEdges::check(const QPoint &pos, const QDateTime &now, bool forceNoPushBack) 1404 { 1405 bool activatedForClient = false; 1406 for (const auto &edge : m_edges) { 1407 if (!edge->isReserved() || edge->isBlocked()) { 1408 continue; 1409 } 1410 if (!edge->activatesForPointer()) { 1411 continue; 1412 } 1413 if (edge->approachGeometry().contains(pos)) { 1414 edge->startApproaching(); 1415 } 1416 if (edge->client() != nullptr && activatedForClient) { 1417 edge->markAsTriggered(pos, now); 1418 continue; 1419 } 1420 if (edge->check(pos, now, forceNoPushBack)) { 1421 if (edge->client()) { 1422 activatedForClient = true; 1423 } 1424 } 1425 } 1426 } 1427 1428 bool ScreenEdges::isEntered(QMouseEvent *event) 1429 { 1430 if (event->type() != QEvent::MouseMove) { 1431 return false; 1432 } 1433 bool activated = false; 1434 bool activatedForClient = false; 1435 for (const auto &edge : m_edges) { 1436 if (!edge->isReserved() || edge->isBlocked()) { 1437 continue; 1438 } 1439 if (!edge->activatesForPointer()) { 1440 continue; 1441 } 1442 if (edge->approachGeometry().contains(event->globalPos())) { 1443 if (!edge->isApproaching()) { 1444 edge->startApproaching(); 1445 } else { 1446 edge->updateApproaching(event->globalPos()); 1447 } 1448 } else { 1449 if (edge->isApproaching()) { 1450 edge->stopApproaching(); 1451 } 1452 } 1453 if (edge->geometry().contains(event->globalPos())) { 1454 if (edge->check(event->globalPos(), QDateTime::fromMSecsSinceEpoch(event->timestamp(), Qt::UTC))) { 1455 if (edge->client()) { 1456 activatedForClient = true; 1457 } 1458 } 1459 } 1460 } 1461 if (activatedForClient) { 1462 for (const auto &edge : m_edges) { 1463 if (edge) { 1464 edge->markAsTriggered(event->globalPos(), QDateTime::fromMSecsSinceEpoch(event->timestamp(), Qt::UTC)); 1465 } 1466 } 1467 } 1468 return activated; 1469 } 1470 1471 bool ScreenEdges::handleEnterNotifiy(xcb_window_t window, const QPoint &point, const QDateTime ×tamp) 1472 { 1473 bool activated = false; 1474 bool activatedForClient = false; 1475 for (const auto &edge : m_edges) { 1476 if (!edge || edge->window() == XCB_WINDOW_NONE) { 1477 continue; 1478 } 1479 if (!edge->isReserved() || edge->isBlocked()) { 1480 continue; 1481 } 1482 if (!edge->activatesForPointer()) { 1483 continue; 1484 } 1485 if (edge->window() == window) { 1486 if (edge->check(point, timestamp)) { 1487 if (edge->client()) { 1488 activatedForClient = true; 1489 } 1490 } 1491 activated = true; 1492 break; 1493 } 1494 if (edge->approachWindow() == window) { 1495 edge->startApproaching(); 1496 // TODO: if it's a corner, it should also trigger for other windows 1497 return true; 1498 } 1499 } 1500 if (activatedForClient) { 1501 for (const auto &edge : m_edges) { 1502 if (edge->client()) { 1503 edge->markAsTriggered(point, timestamp); 1504 } 1505 } 1506 } 1507 return activated; 1508 } 1509 1510 bool ScreenEdges::handleDndNotify(xcb_window_t window, const QPoint &point) 1511 { 1512 for (const auto &edge : m_edges) { 1513 if (!edge || edge->window() == XCB_WINDOW_NONE) { 1514 continue; 1515 } 1516 if (edge->isReserved() && edge->window() == window) { 1517 kwinApp()->updateXTime(); 1518 edge->check(point, QDateTime::fromMSecsSinceEpoch(xTime(), Qt::UTC), true); 1519 return true; 1520 } 1521 } 1522 return false; 1523 } 1524 1525 void ScreenEdges::ensureOnTop() 1526 { 1527 Xcb::restackWindowsWithRaise(windows()); 1528 } 1529 1530 QVector<xcb_window_t> ScreenEdges::windows() const 1531 { 1532 QVector<xcb_window_t> wins; 1533 for (const auto &edge : m_edges) { 1534 xcb_window_t w = edge->window(); 1535 if (w != XCB_WINDOW_NONE) { 1536 wins.append(w); 1537 } 1538 // TODO: lambda 1539 w = edge->approachWindow(); 1540 if (w != XCB_WINDOW_NONE) { 1541 wins.append(w); 1542 } 1543 } 1544 return wins; 1545 } 1546 1547 void ScreenEdges::setRemainActiveOnFullscreen(bool remainActive) 1548 { 1549 m_remainActiveOnFullscreen = remainActive; 1550 } 1551 1552 const std::vector<std::unique_ptr<Edge>> &ScreenEdges::edges() const 1553 { 1554 return m_edges; 1555 } 1556 1557 } // namespace