File indexing completed on 2024-04-28 05:30:30

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