File indexing completed on 2024-04-28 16:49:00

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 &timestamp)
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