1 /*
2     KWin - the KDE window manager
3     This file is part of the KDE project.
4 
5     SPDX-FileCopyrightText: 2011 Arthur Arlt <a.arlt@stud.uni-heidelberg.de>
6     SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
7 
8     Since the functionality provided in this class has been moved from
9     class Workspace, it is not clear who exactly has written the code.
10     The list below contains the copyright holders of the class Workspace.
11 
12     SPDX-FileCopyrightText: 1999, 2000 Matthias Ettrich <ettrich@kde.org>
13     SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org>
14     SPDX-FileCopyrightText: 2009 Lucas Murray <lmurray@undefinedfire.com>
15 
16     SPDX-License-Identifier: GPL-2.0-or-later
17 */
18 
19 #include "screenedge.h"
20 
21 // KWin
22 #include "abstract_output.h"
23 #include "gestures.h"
24 #include <x11client.h>
25 #include "cursor.h"
26 #include "main.h"
27 #include "platform.h"
28 #include "utils.h"
29 #include <workspace.h>
30 #include "virtualdesktops.h"
31 // DBus generated
32 #include "screenlocker_interface.h"
33 // frameworks
34 #include <KConfigGroup>
35 // Qt
36 #include <QAction>
37 #include <QMouseEvent>
38 #include <QSharedPointer>
39 #include <QTimer>
40 #include <QTextStream>
41 #include <QDBusInterface>
42 #include <QDBusPendingCall>
43 #include <QWidget>
44 
45 namespace KWin {
46 
47 // Mouse should not move more than this many pixels
48 static const int DISTANCE_RESET = 30;
49 
50 // How large the touch target of the area recognizing touch gestures is
51 static const int TOUCH_TARGET = 3;
52 
53 // How far the user needs to swipe before triggering an action.
54 static const int MINIMUM_DELTA = 44;
55 
Edge(ScreenEdges * parent)56 Edge::Edge(ScreenEdges *parent)
57     : QObject(parent)
58     , m_edges(parent)
59     , m_border(ElectricNone)
60     , m_action(ElectricActionNone)
61     , m_reserved(0)
62     , m_approaching(false)
63     , m_lastApproachingFactor(0)
64     , m_blocked(false)
65     , m_pushBackBlocked(false)
66     , m_client(nullptr)
67     , m_gesture(new SwipeGesture(this))
68 {
69     m_gesture->setMinimumFingerCount(1);
70     m_gesture->setMaximumFingerCount(1);
71     connect(m_gesture, &Gesture::triggered, this,
72         [this] {
73             stopApproaching();
74             if (m_client) {
75                 m_client->showOnScreenEdge();
76                 unreserve();
77                 return;
78             }
79             handleTouchAction();
80             handleTouchCallback();
81         }, Qt::QueuedConnection
82     );
83     connect(m_gesture, &SwipeGesture::started, this, &Edge::startApproaching);
84     connect(m_gesture, &SwipeGesture::cancelled, this, &Edge::stopApproaching);
85     connect(m_gesture, &SwipeGesture::progress, this,
86         [this] (qreal progress) {
87             int factor = progress * 256.0f;
88             if (m_lastApproachingFactor != factor) {
89                 m_lastApproachingFactor = factor;
90                 Q_EMIT approaching(border(), m_lastApproachingFactor/256.0f, m_approachGeometry);
91             }
92         }
93     );
94     connect(this, &Edge::activatesForTouchGestureChanged, this,
95         [this] {
96             if (isReserved()) {
97                 if (activatesForTouchGesture()) {
98                     m_edges->gestureRecognizer()->registerGesture(m_gesture);
99                 } else {
100                     m_edges->gestureRecognizer()->unregisterGesture(m_gesture);
101                 }
102             }
103         }
104     );
105 }
106 
~Edge()107 Edge::~Edge()
108 {
109 }
110 
reserve()111 void Edge::reserve()
112 {
113     m_reserved++;
114     if (m_reserved == 1) {
115         // got activated
116         activate();
117     }
118 }
119 
reserve(QObject * object,const char * slot)120 void Edge::reserve(QObject *object, const char *slot)
121 {
122     connect(object, &QObject::destroyed, this, qOverload<QObject *>(&Edge::unreserve));
123     m_callBacks.insert(object, QByteArray(slot));
124     reserve();
125 }
126 
reserveTouchCallBack(QAction * action)127 void Edge::reserveTouchCallBack(QAction *action)
128 {
129     if (m_touchActions.contains(action)) {
130         return;
131     }
132     connect(action, &QAction::destroyed, this,
133         [this, action] {
134             unreserveTouchCallBack(action);
135         }
136     );
137     m_touchActions << action;
138     reserve();
139 }
140 
unreserveTouchCallBack(QAction * action)141 void Edge::unreserveTouchCallBack(QAction *action)
142 {
143     auto it = std::find_if(m_touchActions.begin(), m_touchActions.end(), [action] (QAction *a) { return a == action; });
144     if (it != m_touchActions.end()) {
145         m_touchActions.erase(it);
146         unreserve();
147     }
148 }
149 
unreserve()150 void Edge::unreserve()
151 {
152     m_reserved--;
153     if (m_reserved == 0) {
154         // got deactivated
155         stopApproaching();
156         deactivate();
157     }
158 }
unreserve(QObject * object)159 void Edge::unreserve(QObject *object)
160 {
161     if (m_callBacks.contains(object)) {
162         m_callBacks.remove(object);
163         disconnect(object, &QObject::destroyed, this, qOverload<QObject *>(&Edge::unreserve));
164         unreserve();
165     }
166 }
167 
activatesForPointer() const168 bool Edge::activatesForPointer() const
169 {
170     if (m_client) {
171         return true;
172     }
173     if (m_edges->isDesktopSwitching()) {
174         return true;
175     }
176     if (m_edges->isDesktopSwitchingMovingClients()) {
177         auto c = Workspace::self()->moveResizeClient();
178         if (c && !c->isInteractiveResize()) {
179             return true;
180         }
181     }
182     if (!m_callBacks.isEmpty()) {
183         return true;
184     }
185     if (m_action != ElectricActionNone) {
186         return true;
187     }
188     return false;
189 }
190 
activatesForTouchGesture() const191 bool Edge::activatesForTouchGesture() const
192 {
193     if (!isScreenEdge()) {
194         return false;
195     }
196     if (m_blocked) {
197         return false;
198     }
199     if (m_client) {
200         return true;
201     }
202     if (m_touchAction != ElectricActionNone) {
203         return true;
204     }
205     if (!m_touchActions.isEmpty()) {
206         return true;
207     }
208     return false;
209 }
210 
triggersFor(const QPoint & cursorPos) const211 bool Edge::triggersFor(const QPoint &cursorPos) const
212 {
213     if (isBlocked()) {
214         return false;
215     }
216     if (!activatesForPointer()) {
217         return false;
218     }
219     if (!m_geometry.contains(cursorPos)) {
220         return false;
221     }
222     if (isLeft() && cursorPos.x() != m_geometry.x()) {
223         return false;
224     }
225     if (isRight() && cursorPos.x() != (m_geometry.x() + m_geometry.width() -1)) {
226         return false;
227     }
228     if (isTop() && cursorPos.y() != m_geometry.y()) {
229         return false;
230     }
231     if (isBottom() && cursorPos.y() != (m_geometry.y() + m_geometry.height() -1)) {
232         return false;
233     }
234     return true;
235 }
236 
check(const QPoint & cursorPos,const QDateTime & triggerTime,bool forceNoPushBack)237 bool Edge::check(const QPoint &cursorPos, const QDateTime &triggerTime, bool forceNoPushBack)
238 {
239     if (!triggersFor(cursorPos)) {
240         return false;
241     }
242     if (m_lastTrigger.isValid() && // still in cooldown
243         m_lastTrigger.msecsTo(triggerTime) < edges()->reActivationThreshold() - edges()->timeThreshold()) {
244         return false;
245     }
246     // no pushback so we have to activate at once
247     bool directActivate = forceNoPushBack || edges()->cursorPushBackDistance().isNull() || m_client;
248     if (directActivate || canActivate(cursorPos, triggerTime)) {
249         markAsTriggered(cursorPos, triggerTime);
250         handle(cursorPos);
251         return true;
252     } else {
253         pushCursorBack(cursorPos);
254         m_triggeredPoint = cursorPos;
255     }
256     return false;
257 }
258 
markAsTriggered(const QPoint & cursorPos,const QDateTime & triggerTime)259 void Edge::markAsTriggered(const QPoint &cursorPos, const QDateTime &triggerTime)
260 {
261     m_lastTrigger = triggerTime;
262     m_lastReset = QDateTime(); // invalidate
263     m_triggeredPoint = cursorPos;
264 }
265 
canActivate(const QPoint & cursorPos,const QDateTime & triggerTime)266 bool Edge::canActivate(const QPoint &cursorPos, const QDateTime &triggerTime)
267 {
268     // we check whether either the timer has explicitly been invalidated (successful trigger) or is
269     // bigger than the reactivation threshold (activation "aborted", usually due to moving away the cursor
270     // from the corner after successful activation)
271     // either condition means that "this is the first event in a new attempt"
272     if (!m_lastReset.isValid() || m_lastReset.msecsTo(triggerTime) > edges()->reActivationThreshold()) {
273         m_lastReset = triggerTime;
274         return false;
275     }
276     if (m_lastTrigger.isValid() && m_lastTrigger.msecsTo(triggerTime) < edges()->reActivationThreshold() - edges()->timeThreshold()) {
277         return false;
278     }
279     if (m_lastReset.msecsTo(triggerTime) < edges()->timeThreshold()) {
280         return false;
281     }
282     // does the check on position make any sense at all?
283     if ((cursorPos - m_triggeredPoint).manhattanLength() > DISTANCE_RESET) {
284         return false;
285     }
286     return true;
287 }
288 
handle(const QPoint & cursorPos)289 void Edge::handle(const QPoint &cursorPos)
290 {
291     AbstractClient *movingClient = Workspace::self()->moveResizeClient();
292     if ((edges()->isDesktopSwitchingMovingClients() && movingClient && !movingClient->isInteractiveResize()) ||
293         (edges()->isDesktopSwitching() && isScreenEdge())) {
294         // always switch desktops in case:
295         // moving a Client and option for switch on client move is enabled
296         // or switch on screen edge is enabled
297         switchDesktop(cursorPos);
298         return;
299     }
300     if (movingClient) {
301         // if we are moving a window we don't want to trigger the actions. This just results in
302         // problems, e.g. Desktop Grid activated or screen locker activated which just cannot
303         // work as we hold a grab.
304         return;
305     }
306 
307     if (m_client) {
308         pushCursorBack(cursorPos);
309         m_client->showOnScreenEdge();
310         unreserve();
311         return;
312     }
313 
314     if (handlePointerAction() || handleByCallback()) {
315         pushCursorBack(cursorPos);
316         return;
317     }
318     if (edges()->isDesktopSwitching() && isCorner()) {
319         // try again desktop switching for the corner
320         switchDesktop(cursorPos);
321     }
322 }
323 
handleAction(ElectricBorderAction action)324 bool Edge::handleAction(ElectricBorderAction action)
325 {
326     switch (action) {
327     case ElectricActionShowDesktop: {
328         Workspace::self()->setShowingDesktop(!Workspace::self()->showingDesktop());
329         return true;
330     }
331     case ElectricActionLockScreen: { // Lock the screen
332         OrgFreedesktopScreenSaverInterface interface(QStringLiteral("org.freedesktop.ScreenSaver"),
333                                                      QStringLiteral("/ScreenSaver"),
334                                                      QDBusConnection::sessionBus());
335         if (interface.isValid()) {
336             interface.Lock();
337         }
338         return true;
339     }
340     case ElectricActionKRunner: { // open krunner
341         QDBusConnection::sessionBus().asyncCall(
342             QDBusMessage::createMethodCall(QStringLiteral("org.kde.krunner"),
343                                            QStringLiteral("/App"),
344                                            QStringLiteral("org.kde.krunner.App"),
345                                            QStringLiteral("display")
346             )
347         );
348         return true;
349     }
350     case ElectricActionActivityManager: { // open activity manager
351         QDBusConnection::sessionBus().asyncCall(
352             QDBusMessage::createMethodCall(QStringLiteral("org.kde.plasmashell"),
353                                            QStringLiteral("/PlasmaShell"),
354                                            QStringLiteral("org.kde.PlasmaShell"),
355                                            QStringLiteral("toggleActivityManager")
356             )
357         );
358         return true;
359     }
360     case ElectricActionApplicationLauncher: {
361         QDBusConnection::sessionBus().asyncCall(
362             QDBusMessage::createMethodCall(QStringLiteral("org.kde.plasmashell"),
363                                            QStringLiteral("/PlasmaShell"),
364                                            QStringLiteral("org.kde.PlasmaShell"),
365                                            QStringLiteral("activateLauncherMenu")
366             )
367         );
368         return true;
369     }
370     default:
371         return false;
372     }
373 }
374 
handleByCallback()375 bool Edge::handleByCallback()
376 {
377     if (m_callBacks.isEmpty()) {
378         return false;
379     }
380     for (QHash<QObject *, QByteArray>::iterator it = m_callBacks.begin();
381         it != m_callBacks.end();
382         ++it) {
383         bool retVal = false;
384         QMetaObject::invokeMethod(it.key(), it.value().constData(), Q_RETURN_ARG(bool, retVal), Q_ARG(ElectricBorder, m_border));
385         if (retVal) {
386             return true;
387         }
388     }
389     return false;
390 }
391 
handleTouchCallback()392 void Edge::handleTouchCallback()
393 {
394     if (m_touchActions.isEmpty()) {
395         return;
396     }
397     m_touchActions.first()->trigger();
398 }
399 
switchDesktop(const QPoint & cursorPos)400 void Edge::switchDesktop(const QPoint &cursorPos)
401 {
402     QPoint pos(cursorPos);
403     VirtualDesktopManager *vds = VirtualDesktopManager::self();
404     VirtualDesktop *oldDesktop = vds->currentDesktop();
405     VirtualDesktop *desktop = oldDesktop;
406     const int OFFSET = 2;
407     if (isLeft()) {
408         const VirtualDesktop *interimDesktop = desktop;
409         desktop = vds->toLeft(desktop, vds->isNavigationWrappingAround());
410         if (desktop != interimDesktop) {
411             pos.setX(workspace()->geometry().width() - 1 - OFFSET);
412         }
413     } else if (isRight()) {
414         const VirtualDesktop *interimDesktop = desktop;
415         desktop = vds->toRight(desktop, vds->isNavigationWrappingAround());
416         if (desktop != interimDesktop) {
417             pos.setX(OFFSET);
418         }
419     }
420     if (isTop()) {
421         const VirtualDesktop *interimDesktop = desktop;
422         desktop = vds->above(desktop, vds->isNavigationWrappingAround());
423         if (desktop != interimDesktop) {
424             pos.setY(workspace()->geometry().height() - 1 - OFFSET);
425         }
426     } else if (isBottom()) {
427         const VirtualDesktop *interimDesktop = desktop;
428         desktop = vds->below(desktop, vds->isNavigationWrappingAround());
429         if (desktop != interimDesktop) {
430             pos.setY(OFFSET);
431         }
432     }
433 #ifndef KWIN_UNIT_TEST
434     if (AbstractClient *c = Workspace::self()->moveResizeClient()) {
435         const QVector<VirtualDesktop *> desktops{desktop};
436         if (c->rules()->checkDesktops(desktops) != desktops) {
437             // user attempts to move a client to another desktop where it is ruleforced to not be
438             return;
439         }
440     }
441 #endif
442     vds->setCurrent(desktop);
443     if (vds->currentDesktop() != oldDesktop) {
444         m_pushBackBlocked = true;
445         Cursors::self()->mouse()->setPos(pos);
446         QSharedPointer<QMetaObject::Connection> me(new QMetaObject::Connection);
447         *me = QObject::connect(QCoreApplication::eventDispatcher(),
448                                &QAbstractEventDispatcher::aboutToBlock, this,
449                                [this, me](){
450             QObject::disconnect(*me);
451             const_cast<QSharedPointer<QMetaObject::Connection>*>(&me)->reset(nullptr);
452             m_pushBackBlocked = false;
453         }
454         );
455     }
456 }
457 
pushCursorBack(const QPoint & cursorPos)458 void Edge::pushCursorBack(const QPoint &cursorPos)
459 {
460     if (m_pushBackBlocked)
461         return;
462     int x = cursorPos.x();
463     int y = cursorPos.y();
464     const QSize &distance = edges()->cursorPushBackDistance();
465     if (isLeft()) {
466         x += distance.width();
467     }
468     if (isRight()) {
469         x -= distance.width();
470     }
471     if (isTop()) {
472         y += distance.height();
473     }
474     if (isBottom()) {
475         y -= distance.height();
476     }
477     Cursors::self()->mouse()->setPos(x, y);
478 }
479 
setGeometry(const QRect & geometry)480 void Edge::setGeometry(const QRect &geometry)
481 {
482     if (m_geometry == geometry) {
483         return;
484     }
485     m_geometry = geometry;
486     int x = m_geometry.x();
487     int y = m_geometry.y();
488     int width = m_geometry.width();
489     int height = m_geometry.height();
490     const int offset = m_edges->cornerOffset();
491     if (isCorner()) {
492         if (isRight()) {
493             x = x + width - offset;
494         }
495         if (isBottom()) {
496             y = y + height - offset;
497         }
498         width = offset;
499         height = offset;
500     } else {
501         if (isLeft()) {
502             y += offset;
503             width = offset;
504             height = height - offset * 2;
505         } else if (isRight()) {
506             x = x + width - offset;
507             y += offset;
508             width = offset;
509             height = height - offset * 2;
510         } else if (isTop()) {
511             x += offset;
512             width = width - offset * 2;
513             height = offset;
514         } else if (isBottom()) {
515             x += offset;
516             y = y + height - offset;
517             width = width - offset * 2;
518             height = offset;
519         }
520     }
521     m_approachGeometry = QRect(x, y, width, height);
522     doGeometryUpdate();
523 
524     if (isScreenEdge()) {
525         const AbstractOutput *output = kwinApp()->platform()->outputAt(m_geometry.center());
526         m_gesture->setStartGeometry(m_geometry);
527         m_gesture->setMinimumDelta(QSizeF(MINIMUM_DELTA, MINIMUM_DELTA) / output->scale());
528     }
529 }
530 
checkBlocking()531 void Edge::checkBlocking()
532 {
533     if (isCorner()) {
534         return;
535     }
536     bool newValue = false;
537     if (AbstractClient *client = Workspace::self()->activeClient()) {
538         newValue = client->isFullScreen() && client->frameGeometry().contains(m_geometry.center());
539     }
540     if (newValue == m_blocked) {
541         return;
542     }
543     const bool wasTouch = activatesForTouchGesture();
544     m_blocked = newValue;
545     if (wasTouch != activatesForTouchGesture()) {
546         Q_EMIT activatesForTouchGestureChanged();
547     }
548     doUpdateBlocking();
549 }
550 
doUpdateBlocking()551 void Edge::doUpdateBlocking()
552 {
553 }
554 
doGeometryUpdate()555 void Edge::doGeometryUpdate()
556 {
557 }
558 
activate()559 void Edge::activate()
560 {
561     if (activatesForTouchGesture()) {
562         m_edges->gestureRecognizer()->registerGesture(m_gesture);
563     }
564     doActivate();
565 }
566 
doActivate()567 void Edge::doActivate()
568 {
569 }
570 
deactivate()571 void Edge::deactivate()
572 {
573     m_edges->gestureRecognizer()->unregisterGesture(m_gesture);
574     doDeactivate();
575 }
576 
doDeactivate()577 void Edge::doDeactivate()
578 {
579 }
580 
startApproaching()581 void Edge::startApproaching()
582 {
583     if (m_approaching) {
584         return;
585     }
586     m_approaching = true;
587     doStartApproaching();
588     m_lastApproachingFactor = 0;
589     Q_EMIT approaching(border(), 0.0, m_approachGeometry);
590 }
591 
doStartApproaching()592 void Edge::doStartApproaching()
593 {
594 }
595 
stopApproaching()596 void Edge::stopApproaching()
597 {
598     if (!m_approaching) {
599         return;
600     }
601     m_approaching = false;
602     doStopApproaching();
603     m_lastApproachingFactor = 0;
604     Q_EMIT approaching(border(), 0.0, m_approachGeometry);
605 }
606 
doStopApproaching()607 void Edge::doStopApproaching()
608 {
609 }
610 
updateApproaching(const QPoint & point)611 void Edge::updateApproaching(const QPoint &point)
612 {
613     if (approachGeometry().contains(point)) {
614         int factor = 0;
615         const int edgeDistance = m_edges->cornerOffset();
616         auto cornerDistance = [=](const QPoint &corner) {
617             return qMax(qAbs(corner.x() - point.x()), qAbs(corner.y() - point.y()));
618         };
619         switch (border()) {
620         case ElectricTopLeft:
621             factor = (cornerDistance(approachGeometry().topLeft())<<8) / edgeDistance;
622             break;
623         case ElectricTopRight:
624             factor = (cornerDistance(approachGeometry().topRight())<<8) / edgeDistance;
625             break;
626         case ElectricBottomRight:
627             factor = (cornerDistance(approachGeometry().bottomRight())<<8) / edgeDistance;
628             break;
629         case ElectricBottomLeft:
630             factor = (cornerDistance(approachGeometry().bottomLeft())<<8) / edgeDistance;
631             break;
632         case ElectricTop:
633             factor = (qAbs(point.y() - approachGeometry().y())<<8) / edgeDistance;
634             break;
635         case ElectricRight:
636             factor = (qAbs(point.x() - approachGeometry().right())<<8) / edgeDistance;
637             break;
638         case ElectricBottom:
639             factor = (qAbs(point.y() - approachGeometry().bottom())<<8) / edgeDistance;
640             break;
641         case ElectricLeft:
642             factor = (qAbs(point.x() - approachGeometry().x())<<8) / edgeDistance;
643             break;
644         default:
645             break;
646         }
647         factor = 256 - factor;
648         if (m_lastApproachingFactor != factor) {
649             m_lastApproachingFactor = factor;
650             Q_EMIT approaching(border(), m_lastApproachingFactor/256.0f, m_approachGeometry);
651         }
652     } else {
653         stopApproaching();
654     }
655 }
656 
window() const657 quint32 Edge::window() const
658 {
659     return 0;
660 }
661 
approachWindow() const662 quint32 Edge::approachWindow() const
663 {
664     return 0;
665 }
666 
setBorder(ElectricBorder border)667 void Edge::setBorder(ElectricBorder border)
668 {
669     m_border = border;
670     switch (m_border) {
671     case ElectricTop:
672         m_gesture->setDirection(SwipeGesture::Direction::Down);
673         break;
674     case ElectricRight:
675         m_gesture->setDirection(SwipeGesture::Direction::Left);
676         break;
677     case ElectricBottom:
678         m_gesture->setDirection(SwipeGesture::Direction::Up);
679         break;
680     case ElectricLeft:
681         m_gesture->setDirection(SwipeGesture::Direction::Right);
682         break;
683     default:
684         break;
685     }
686 }
687 
setTouchAction(ElectricBorderAction action)688 void Edge::setTouchAction(ElectricBorderAction action) {
689     const bool wasTouch = activatesForTouchGesture();
690     m_touchAction = action;
691     if (wasTouch != activatesForTouchGesture()) {
692         Q_EMIT activatesForTouchGestureChanged();
693     }
694 }
695 
setClient(AbstractClient * client)696 void Edge::setClient(AbstractClient *client)
697 {
698     const bool wasTouch = activatesForTouchGesture();
699     m_client = client;
700     if (wasTouch != activatesForTouchGesture()) {
701         Q_EMIT activatesForTouchGestureChanged();
702     }
703 }
704 
705 /**********************************************************
706  * ScreenEdges
707  *********************************************************/
KWIN_SINGLETON_FACTORY(ScreenEdges)708 KWIN_SINGLETON_FACTORY(ScreenEdges)
709 
710 ScreenEdges::ScreenEdges(QObject *parent)
711     : QObject(parent)
712     , m_desktopSwitching(false)
713     , m_desktopSwitchingMovingClients(false)
714     , m_timeThreshold(0)
715     , m_reactivateThreshold(0)
716     , m_virtualDesktopLayout({})
717     , m_actionTopLeft(ElectricActionNone)
718     , m_actionTop(ElectricActionNone)
719     , m_actionTopRight(ElectricActionNone)
720     , m_actionRight(ElectricActionNone)
721     , m_actionBottomRight(ElectricActionNone)
722     , m_actionBottom(ElectricActionNone)
723     , m_actionBottomLeft(ElectricActionNone)
724     , m_actionLeft(ElectricActionNone)
725     , m_gestureRecognizer(new GestureRecognizer(this))
726 {
727     // TODO: Maybe calculate the corner offset based on font metrics instead?
728     const auto outputs = kwinApp()->platform()->enabledOutputs();
729     if (!outputs.isEmpty()) {
730         const QSize size = outputs[0]->geometry().size();
731         const QSizeF physicalSize = outputs[0]->physicalSize();
732 
733         const int physicalDpiX = size.width() / physicalSize.width() * qreal(25.4);
734         const int physicalDpiY = size.height() / physicalSize.height() * qreal(25.4);
735 
736         m_cornerOffset = (physicalDpiX + physicalDpiY + 5) / 6;
737     }
738 
739     connect(workspace(), &Workspace::clientRemoved, this, &ScreenEdges::deleteEdgeForClient);
740 }
741 
~ScreenEdges()742 ScreenEdges::~ScreenEdges()
743 {
744     s_self = nullptr;
745 }
746 
init()747 void ScreenEdges::init()
748 {
749     reconfigure();
750     updateLayout();
751     recreateEdges();
752 }
electricBorderAction(const QString & name)753 static ElectricBorderAction electricBorderAction(const QString& name)
754 {
755     QString lowerName = name.toLower();
756     if (lowerName == QStringLiteral("showdesktop")) {
757         return ElectricActionShowDesktop;
758     } else if (lowerName == QStringLiteral("lockscreen")) {
759         return ElectricActionLockScreen;
760     } else if (lowerName == QLatin1String("krunner")) {
761         return ElectricActionKRunner;
762     } else if (lowerName == QLatin1String("activitymanager")) {
763         return ElectricActionActivityManager;
764     } else if (lowerName == QLatin1String("applicationlauncher")) {
765         return ElectricActionApplicationLauncher;
766     }
767     return ElectricActionNone;
768 }
769 
reconfigure()770 void ScreenEdges::reconfigure()
771 {
772     if (!m_config) {
773         return;
774     }
775     // TODO: migrate settings to a group ScreenEdges
776     KConfigGroup windowsConfig = m_config->group("Windows");
777     setTimeThreshold(windowsConfig.readEntry("ElectricBorderDelay", 150));
778     setReActivationThreshold(qMax(timeThreshold() + 50, windowsConfig.readEntry("ElectricBorderCooldown", 350)));
779     int desktopSwitching = windowsConfig.readEntry("ElectricBorders", static_cast<int>(ElectricDisabled));
780     if (desktopSwitching == ElectricDisabled) {
781         setDesktopSwitching(false);
782         setDesktopSwitchingMovingClients(false);
783     } else if (desktopSwitching == ElectricMoveOnly) {
784         setDesktopSwitching(false);
785         setDesktopSwitchingMovingClients(true);
786     } else if (desktopSwitching == ElectricAlways) {
787         setDesktopSwitching(true);
788         setDesktopSwitchingMovingClients(true);
789     }
790     const int pushBack = windowsConfig.readEntry("ElectricBorderPushbackPixels", 1);
791     m_cursorPushBackDistance = QSize(pushBack, pushBack);
792 
793     KConfigGroup borderConfig = m_config->group("ElectricBorders");
794     setActionForBorder(ElectricTopLeft,     &m_actionTopLeft,
795                        electricBorderAction(borderConfig.readEntry("TopLeft", "None")));
796     setActionForBorder(ElectricTop,         &m_actionTop,
797                        electricBorderAction(borderConfig.readEntry("Top", "None")));
798     setActionForBorder(ElectricTopRight,    &m_actionTopRight,
799                        electricBorderAction(borderConfig.readEntry("TopRight", "None")));
800     setActionForBorder(ElectricRight,       &m_actionRight,
801                        electricBorderAction(borderConfig.readEntry("Right", "None")));
802     setActionForBorder(ElectricBottomRight, &m_actionBottomRight,
803                        electricBorderAction(borderConfig.readEntry("BottomRight", "None")));
804     setActionForBorder(ElectricBottom,      &m_actionBottom,
805                        electricBorderAction(borderConfig.readEntry("Bottom", "None")));
806     setActionForBorder(ElectricBottomLeft,  &m_actionBottomLeft,
807                        electricBorderAction(borderConfig.readEntry("BottomLeft", "None")));
808     setActionForBorder(ElectricLeft,        &m_actionLeft,
809                        electricBorderAction(borderConfig.readEntry("Left", "None")));
810 
811     borderConfig = m_config->group("TouchEdges");
812     setActionForTouchBorder(ElectricTop, electricBorderAction(borderConfig.readEntry("Top", "None")));
813     setActionForTouchBorder(ElectricRight, electricBorderAction(borderConfig.readEntry("Right", "None")));
814     setActionForTouchBorder(ElectricBottom, electricBorderAction(borderConfig.readEntry("Bottom", "None")));
815     setActionForTouchBorder(ElectricLeft, electricBorderAction(borderConfig.readEntry("Left", "None")));
816 }
817 
setActionForBorder(ElectricBorder border,ElectricBorderAction * oldValue,ElectricBorderAction newValue)818 void ScreenEdges::setActionForBorder(ElectricBorder border, ElectricBorderAction *oldValue, ElectricBorderAction newValue)
819 {
820     if (*oldValue == newValue) {
821         return;
822     }
823     if (*oldValue == ElectricActionNone) {
824         // have to reserve
825         for (auto it = m_edges.begin(); it != m_edges.end(); ++it) {
826             if ((*it)->border() == border) {
827                 (*it)->reserve();
828             }
829         }
830     }
831     if (newValue == ElectricActionNone) {
832         // have to unreserve
833         for (auto it = m_edges.begin(); it != m_edges.end(); ++it) {
834             if ((*it)->border() == border) {
835                 (*it)->unreserve();
836             }
837         }
838     }
839     *oldValue = newValue;
840     // update action on all Edges for given border
841     for (auto it = m_edges.begin(); it != m_edges.end(); ++it) {
842         if ((*it)->border() == border) {
843             (*it)->setAction(newValue);
844         }
845     }
846 }
847 
setActionForTouchBorder(ElectricBorder border,ElectricBorderAction newValue)848 void ScreenEdges::setActionForTouchBorder(ElectricBorder border, ElectricBorderAction newValue)
849 {
850     auto it = m_touchActions.find(border);
851     ElectricBorderAction oldValue = ElectricActionNone;
852     if (it != m_touchActions.end()) {
853         oldValue = it.value();
854     }
855     if (oldValue == newValue) {
856         return;
857     }
858     if (oldValue == ElectricActionNone) {
859         // have to reserve
860         for (auto it = m_edges.begin(); it != m_edges.end(); ++it) {
861             if ((*it)->border() == border) {
862                 (*it)->reserve();
863             }
864         }
865     }
866     if (newValue == ElectricActionNone) {
867         // have to unreserve
868         for (auto it = m_edges.begin(); it != m_edges.end(); ++it) {
869             if ((*it)->border() == border) {
870                 (*it)->unreserve();
871             }
872         }
873 
874         m_touchActions.erase(it);
875     } else {
876         m_touchActions.insert(border, newValue);
877     }
878     // update action on all Edges for given border
879     for (auto it = m_edges.begin(); it != m_edges.end(); ++it) {
880         if ((*it)->border() == border) {
881             (*it)->setTouchAction(newValue);
882         }
883     }
884 }
885 
updateLayout()886 void ScreenEdges::updateLayout()
887 {
888     const QSize desktopMatrix = VirtualDesktopManager::self()->grid().size();
889     Qt::Orientations newLayout = {};
890     if (desktopMatrix.width() > 1) {
891         newLayout |= Qt::Horizontal;
892     }
893     if (desktopMatrix.height() > 1) {
894         newLayout |= Qt::Vertical;
895     }
896     if (newLayout == m_virtualDesktopLayout) {
897         return;
898     }
899     if (isDesktopSwitching()) {
900         reserveDesktopSwitching(false, m_virtualDesktopLayout);
901     }
902     m_virtualDesktopLayout = newLayout;
903     if (isDesktopSwitching()) {
904         reserveDesktopSwitching(true, m_virtualDesktopLayout);
905     }
906 }
907 
isLeftScreen(const QRect & screen,const QRect & fullArea)908 static bool isLeftScreen(const QRect &screen, const QRect &fullArea)
909 {
910     const auto outputs = kwinApp()->platform()->enabledOutputs();
911     if (outputs.count() == 1) {
912         return true;
913     }
914     if (screen.x() == fullArea.x()) {
915         return true;
916     }
917     // If any other screen has a right edge against our left edge, then this screen is not a left screen
918     for (const AbstractOutput *output : outputs) {
919         const QRect otherGeo = output->geometry();
920         if (otherGeo == screen) {
921             // that's our screen to test
922             continue;
923         }
924         if (screen.x() == otherGeo.x() + otherGeo.width()
925             && screen.y() < otherGeo.y() + otherGeo.height()
926             && screen.y() + screen.height() > otherGeo.y()) {
927             // There is a screen to the left
928             return false;
929         }
930     }
931     // No screen exists to the left, so this is a left screen
932     return true;
933 }
934 
isRightScreen(const QRect & screen,const QRect & fullArea)935 static bool isRightScreen(const QRect &screen, const QRect &fullArea)
936 {
937     const auto outputs = kwinApp()->platform()->enabledOutputs();
938     if (outputs.count() == 1) {
939         return true;
940     }
941     if (screen.x() + screen.width() == fullArea.x() + fullArea.width()) {
942         return true;
943     }
944     // If any other screen has any left edge against any of our right edge, then this screen is not a right screen
945     for (const AbstractOutput *output : outputs) {
946         const QRect otherGeo = output->geometry();
947         if (otherGeo == screen) {
948             // that's our screen to test
949             continue;
950         }
951         if (screen.x() + screen.width() == otherGeo.x()
952             && screen.y() < otherGeo.y() + otherGeo.height()
953             && screen.y() + screen.height() > otherGeo.y()) {
954             // There is a screen to the right
955             return false;
956         }
957     }
958     // No screen exists to the right, so this is a right screen
959     return true;
960 }
961 
isTopScreen(const QRect & screen,const QRect & fullArea)962 static bool isTopScreen(const QRect &screen, const QRect &fullArea)
963 {
964     const auto outputs = kwinApp()->platform()->enabledOutputs();
965     if (outputs.count() == 1) {
966         return true;
967     }
968     if (screen.y() == fullArea.y()) {
969         return true;
970     }
971     // If any other screen has any bottom edge against any of our top edge, then this screen is not a top screen
972     for (const AbstractOutput *output : outputs) {
973         const QRect otherGeo = output->geometry();
974         if (otherGeo == screen) {
975             // that's our screen to test
976             continue;
977         }
978         if (screen.y() == otherGeo.y() + otherGeo.height()
979             && screen.x() < otherGeo.x() + otherGeo.width()
980             && screen.x() + screen.width() > otherGeo.x()) {
981             // There is a screen to the top
982             return false;
983         }
984     }
985     // No screen exists to the top, so this is a top screen
986     return true;
987 }
988 
isBottomScreen(const QRect & screen,const QRect & fullArea)989 static bool isBottomScreen(const QRect &screen, const QRect &fullArea)
990 {
991     const auto outputs = kwinApp()->platform()->enabledOutputs();
992     if (outputs.count() == 1) {
993         return true;
994     }
995     if (screen.y() + screen.height() == fullArea.y() + fullArea.height()) {
996         return true;
997     }
998     // If any other screen has any top edge against any of our bottom edge, then this screen is not a bottom screen
999     for (const AbstractOutput *output : outputs) {
1000         const QRect otherGeo = output->geometry();
1001         if (otherGeo == screen) {
1002             // that's our screen to test
1003             continue;
1004         }
1005         if (screen.y() + screen.height() == otherGeo.y()
1006             && screen.x() < otherGeo.x() + otherGeo.width()
1007             && screen.x() + screen.width() > otherGeo.x()) {
1008             // There is a screen to the bottom
1009             return false;
1010         }
1011     }
1012     // No screen exists to the bottom, so this is a bottom screen
1013     return true;
1014 }
1015 
recreateEdges()1016 void ScreenEdges::recreateEdges()
1017 {
1018     QList<Edge*> oldEdges(m_edges);
1019     m_edges.clear();
1020     const QRect fullArea = workspace()->geometry();
1021     QRegion processedRegion;
1022 
1023     const auto outputs = kwinApp()->platform()->enabledOutputs();
1024     for (const AbstractOutput *output : outputs) {
1025         const QRegion screen = QRegion(output->geometry()).subtracted(processedRegion);
1026         processedRegion += screen;
1027         for (const QRect &screenPart : screen) {
1028             if (isLeftScreen(screenPart, fullArea)) {
1029                 // left most screen
1030                 createVerticalEdge(ElectricLeft, screenPart, fullArea);
1031             }
1032             if (isRightScreen(screenPart, fullArea)) {
1033                 // right most screen
1034                 createVerticalEdge(ElectricRight, screenPart, fullArea);
1035             }
1036             if (isTopScreen(screenPart, fullArea)) {
1037                 // top most screen
1038                 createHorizontalEdge(ElectricTop, screenPart, fullArea);
1039             }
1040             if (isBottomScreen(screenPart, fullArea)) {
1041                 // bottom most screen
1042                 createHorizontalEdge(ElectricBottom, screenPart, fullArea);
1043             }
1044         }
1045     }
1046     // copy over the effect/script reservations from the old edges
1047     for (auto it = m_edges.begin(); it != m_edges.end(); ++it) {
1048         Edge *edge = *it;
1049         for (auto oldIt = oldEdges.constBegin();
1050                 oldIt != oldEdges.constEnd();
1051                 ++oldIt) {
1052             Edge *oldEdge = *oldIt;
1053             if (oldEdge->client()) {
1054                 // show the client again and don't recreate the edge
1055                 oldEdge->client()->showOnScreenEdge();
1056                 continue;
1057             }
1058             if (oldEdge->border() != edge->border()) {
1059                 continue;
1060             }
1061             const QHash<QObject *, QByteArray> &callbacks = oldEdge->callBacks();
1062             for (QHash<QObject *, QByteArray>::const_iterator callback = callbacks.begin();
1063                     callback != callbacks.end();
1064                     ++callback) {
1065                 edge->reserve(callback.key(), callback.value().constData());
1066             }
1067             const auto touchCallBacks = oldEdge->touchCallBacks();
1068             for (auto a : touchCallBacks) {
1069                 edge->reserveTouchCallBack(a);
1070             }
1071         }
1072     }
1073     qDeleteAll(oldEdges);
1074 }
1075 
createVerticalEdge(ElectricBorder border,const QRect & screen,const QRect & fullArea)1076 void ScreenEdges::createVerticalEdge(ElectricBorder border, const QRect &screen, const QRect &fullArea)
1077 {
1078     if (border != ElectricRight && border != KWin::ElectricLeft) {
1079         return;
1080     }
1081     int y = screen.y();
1082     int height = screen.height();
1083     const int x = (border == ElectricLeft) ? screen.x() : screen.x() + screen.width() - TOUCH_TARGET;
1084     if (isTopScreen(screen, fullArea)) {
1085         // also top most screen
1086         height -= m_cornerOffset;
1087         y += m_cornerOffset;
1088         // create top left/right edge
1089         const ElectricBorder edge = (border == ElectricLeft) ? ElectricTopLeft : ElectricTopRight;
1090         m_edges << createEdge(edge, x, screen.y(), TOUCH_TARGET, TOUCH_TARGET);
1091     }
1092     if (isBottomScreen(screen, fullArea)) {
1093         // also bottom most screen
1094         height -= m_cornerOffset;
1095         // create bottom left/right edge
1096         const ElectricBorder edge = (border == ElectricLeft) ? ElectricBottomLeft : ElectricBottomRight;
1097         m_edges << createEdge(edge, x, screen.y() + screen.height() - TOUCH_TARGET, TOUCH_TARGET, TOUCH_TARGET);
1098     }
1099     if (height <= m_cornerOffset) {
1100         // An overlap with another output is near complete. We ignore this border.
1101         return;
1102     }
1103     m_edges << createEdge(border, x, y, TOUCH_TARGET, height);
1104 }
1105 
createHorizontalEdge(ElectricBorder border,const QRect & screen,const QRect & fullArea)1106 void ScreenEdges::createHorizontalEdge(ElectricBorder border, const QRect &screen, const QRect &fullArea)
1107 {
1108     if (border != ElectricTop && border != ElectricBottom) {
1109         return;
1110     }
1111     int x = screen.x();
1112     int width = screen.width();
1113     if (isLeftScreen(screen, fullArea)) {
1114         // also left most - adjust only x and width
1115         x += m_cornerOffset;
1116         width -= m_cornerOffset;
1117     }
1118     if (isRightScreen(screen, fullArea)) {
1119         // also right most edge
1120         width -= m_cornerOffset;
1121     }
1122     if (width <= m_cornerOffset) {
1123         // An overlap with another output is near complete. We ignore this border.
1124         return;
1125     }
1126     const int y = (border == ElectricTop) ? screen.y() : screen.y() + screen.height() - TOUCH_TARGET;
1127     m_edges << createEdge(border, x, y, width, TOUCH_TARGET);
1128 }
1129 
createEdge(ElectricBorder border,int x,int y,int width,int height,bool createAction)1130 Edge *ScreenEdges::createEdge(ElectricBorder border, int x, int y, int width, int height, bool createAction)
1131 {
1132     Edge *edge = kwinApp()->platform()->createScreenEdge(this);
1133     // Edges can not have negative size.
1134     Q_ASSERT(width >= 0);
1135     Q_ASSERT(height >= 0);
1136 
1137     edge->setBorder(border);
1138     edge->setGeometry(QRect(x, y, width, height));
1139     if (createAction) {
1140         const ElectricBorderAction action = actionForEdge(edge);
1141         if (action != KWin::ElectricActionNone) {
1142             edge->reserve();
1143             edge->setAction(action);
1144         }
1145         const ElectricBorderAction touchAction = actionForTouchEdge(edge);
1146         if (touchAction != KWin::ElectricActionNone) {
1147             edge->reserve();
1148             edge->setTouchAction(touchAction);
1149         }
1150     }
1151     if (isDesktopSwitching()) {
1152         if (edge->isCorner()) {
1153             edge->reserve();
1154         } else {
1155             if ((m_virtualDesktopLayout & Qt::Horizontal) && (edge->isLeft() || edge->isRight())) {
1156                 edge->reserve();
1157             }
1158             if ((m_virtualDesktopLayout & Qt::Vertical) && (edge->isTop() || edge->isBottom())) {
1159                 edge->reserve();
1160             }
1161         }
1162     }
1163     connect(edge, &Edge::approaching, this, &ScreenEdges::approaching);
1164     if (edge->isScreenEdge()) {
1165         connect(this, &ScreenEdges::checkBlocking, edge, &Edge::checkBlocking);
1166     }
1167     return edge;
1168 }
1169 
actionForEdge(Edge * edge) const1170 ElectricBorderAction ScreenEdges::actionForEdge(Edge *edge) const
1171 {
1172     switch (edge->border()) {
1173     case ElectricTopLeft:
1174         return m_actionTopLeft;
1175     case ElectricTop:
1176         return m_actionTop;
1177     case ElectricTopRight:
1178         return m_actionTopRight;
1179     case ElectricRight:
1180         return m_actionRight;
1181     case ElectricBottomRight:
1182         return m_actionBottomRight;
1183     case ElectricBottom:
1184         return m_actionBottom;
1185     case ElectricBottomLeft:
1186         return m_actionBottomLeft;
1187     case ElectricLeft:
1188         return m_actionLeft;
1189     default:
1190         // fall through
1191         break;
1192     }
1193     return ElectricActionNone;
1194 }
1195 
actionForTouchEdge(Edge * edge) const1196 ElectricBorderAction ScreenEdges::actionForTouchEdge(Edge *edge) const
1197 {
1198     auto it = m_touchActions.find(edge->border());
1199     if (it != m_touchActions.end()) {
1200         return it.value();
1201     }
1202     return ElectricActionNone;
1203 }
1204 
reserveDesktopSwitching(bool isToReserve,Qt::Orientations o)1205 void ScreenEdges::reserveDesktopSwitching(bool isToReserve, Qt::Orientations o)
1206 {
1207     if (!o)
1208         return;
1209     for (auto it = m_edges.begin(); it != m_edges.end(); ++it) {
1210         Edge *edge = *it;
1211         if (edge->isCorner()) {
1212             isToReserve ? edge->reserve() : edge->unreserve();
1213         } else {
1214             if ((m_virtualDesktopLayout & Qt::Horizontal) && (edge->isLeft() || edge->isRight())) {
1215                 isToReserve ? edge->reserve() : edge->unreserve();
1216             }
1217             if ((m_virtualDesktopLayout & Qt::Vertical) && (edge->isTop() || edge->isBottom())) {
1218                 isToReserve ? edge->reserve() : edge->unreserve();
1219             }
1220         }
1221     }
1222 }
1223 
reserve(ElectricBorder border,QObject * object,const char * slot)1224 void ScreenEdges::reserve(ElectricBorder border, QObject *object, const char *slot)
1225 {
1226     for (auto it = m_edges.begin(); it != m_edges.end(); ++it) {
1227         if ((*it)->border() == border) {
1228             (*it)->reserve(object, slot);
1229         }
1230     }
1231 }
1232 
unreserve(ElectricBorder border,QObject * object)1233 void ScreenEdges::unreserve(ElectricBorder border, QObject *object)
1234 {
1235     for (auto it = m_edges.begin(); it != m_edges.end(); ++it) {
1236         if ((*it)->border() == border) {
1237             (*it)->unreserve(object);
1238         }
1239     }
1240 }
1241 
reserve(AbstractClient * client,ElectricBorder border)1242 void ScreenEdges::reserve(AbstractClient *client, ElectricBorder border)
1243 {
1244     bool hadBorder = false;
1245     auto it = m_edges.begin();
1246     while (it != m_edges.end()) {
1247         if ((*it)->client() == client) {
1248             hadBorder = true;
1249             delete *it;
1250             it = m_edges.erase(it);
1251         } else {
1252             it++;
1253         }
1254     }
1255 
1256     if (border != ElectricNone) {
1257         createEdgeForClient(client, border);
1258     } else {
1259         if (hadBorder) // show again
1260             client->showOnScreenEdge();
1261     }
1262 }
1263 
reserveTouch(ElectricBorder border,QAction * action)1264 void ScreenEdges::reserveTouch(ElectricBorder border, QAction *action)
1265 {
1266     for (auto it = m_edges.begin(); it != m_edges.end(); ++it) {
1267         if ((*it)->border() == border) {
1268             (*it)->reserveTouchCallBack(action);
1269         }
1270     }
1271 }
1272 
unreserveTouch(ElectricBorder border,QAction * action)1273 void ScreenEdges::unreserveTouch(ElectricBorder border, QAction *action)
1274 {
1275     for (auto it = m_edges.begin(); it != m_edges.end(); ++it) {
1276         if ((*it)->border() == border) {
1277             (*it)->unreserveTouchCallBack(action);
1278         }
1279     }
1280 }
1281 
createEdgeForClient(AbstractClient * client,ElectricBorder border)1282 void ScreenEdges::createEdgeForClient(AbstractClient *client, ElectricBorder border)
1283 {
1284     int y = 0;
1285     int x = 0;
1286     int width = 0;
1287     int height = 0;
1288     const QRect geo = client->frameGeometry();
1289     const QRect fullArea = workspace()->geometry();
1290 
1291     const auto outputs = kwinApp()->platform()->enabledOutputs();
1292     for (const AbstractOutput *output : outputs) {
1293         const QRect screen = output->geometry();
1294         if (!screen.contains(geo)) {
1295             // ignoring Clients having a geometry overlapping with multiple screens
1296             // this would make the code more complex. If it's needed in future it can be added
1297             continue;
1298         }
1299         const bool bordersTop = (screen.y() == geo.y());
1300         const bool bordersLeft = (screen.x() == geo.x());
1301         const bool bordersBottom = (screen.y() + screen.height() == geo.y() + geo.height());
1302         const bool bordersRight = (screen.x() + screen.width() == geo.x() + geo.width());
1303 
1304         if (bordersTop && border == ElectricTop) {
1305             if (!isTopScreen(screen, fullArea)) {
1306                 continue;
1307             }
1308             y = geo.y();
1309             x = geo.x();
1310             height = 1;
1311             width = geo.width();
1312             break;
1313         }
1314         if (bordersBottom && border == ElectricBottom) {
1315             if (!isBottomScreen(screen, fullArea)) {
1316                 continue;
1317             }
1318             y = geo.y() + geo.height() - 1;
1319             x = geo.x();
1320             height = 1;
1321             width = geo.width();
1322             break;
1323         }
1324         if (bordersLeft && border == ElectricLeft) {
1325             if (!isLeftScreen(screen, fullArea)) {
1326                 continue;
1327             }
1328             x = geo.x();
1329             y = geo.y();
1330             width = 1;
1331             height = geo.height();
1332             break;
1333         }
1334         if (bordersRight && border == ElectricRight) {
1335             if (!isRightScreen(screen, fullArea)) {
1336                 continue;
1337             }
1338             x = geo.x() + geo.width() - 1;
1339             y = geo.y();
1340             width = 1;
1341             height = geo.height();
1342             break;
1343         }
1344     }
1345 
1346     if (width > 0 && height > 0) {
1347         Edge *edge = createEdge(border, x, y, width, height, false);
1348         edge->setClient(client);
1349         m_edges.append(edge);
1350         edge->reserve();
1351     } else {
1352         // we could not create an edge window, so don't allow the window to hide
1353         client->showOnScreenEdge();
1354     }
1355 }
1356 
deleteEdgeForClient(AbstractClient * c)1357 void ScreenEdges::deleteEdgeForClient(AbstractClient* c)
1358 {
1359     auto it = m_edges.begin();
1360     while (it != m_edges.end()) {
1361         if ((*it)->client() == c) {
1362             delete *it;
1363             it = m_edges.erase(it);
1364         } else {
1365             it++;
1366         }
1367     }
1368 }
1369 
check(const QPoint & pos,const QDateTime & now,bool forceNoPushBack)1370 void ScreenEdges::check(const QPoint &pos, const QDateTime &now, bool forceNoPushBack)
1371 {
1372     bool activatedForClient = false;
1373     for (auto it = m_edges.begin(); it != m_edges.end(); ++it) {
1374         if (!(*it)->isReserved()) {
1375             continue;
1376         }
1377         if (!(*it)->activatesForPointer()) {
1378             continue;
1379         }
1380         if ((*it)->approachGeometry().contains(pos)) {
1381             (*it)->startApproaching();
1382         }
1383         if ((*it)->client() != nullptr && activatedForClient) {
1384             (*it)->markAsTriggered(pos, now);
1385             continue;
1386         }
1387         if ((*it)->check(pos, now, forceNoPushBack)) {
1388             if ((*it)->client()) {
1389                 activatedForClient = true;
1390             }
1391         }
1392     }
1393 }
1394 
isEntered(QMouseEvent * event)1395 bool ScreenEdges::isEntered(QMouseEvent *event)
1396 {
1397     if (event->type() != QEvent::MouseMove) {
1398         return false;
1399     }
1400     bool activated = false;
1401     bool activatedForClient = false;
1402     for (auto it = m_edges.begin(); it != m_edges.end(); ++it) {
1403         Edge *edge = *it;
1404         if (!edge->isReserved()) {
1405             continue;
1406         }
1407         if (!edge->activatesForPointer()) {
1408             continue;
1409         }
1410         if (edge->approachGeometry().contains(event->globalPos())) {
1411             if (!edge->isApproaching()) {
1412                 edge->startApproaching();
1413             } else {
1414                 edge->updateApproaching(event->globalPos());
1415             }
1416         } else {
1417             if (edge->isApproaching()) {
1418                 edge->stopApproaching();
1419             }
1420         }
1421         if (edge->geometry().contains(event->globalPos())) {
1422             if (edge->check(event->globalPos(), QDateTime::fromMSecsSinceEpoch(event->timestamp(), Qt::UTC))) {
1423                 if (edge->client()) {
1424                     activatedForClient = true;
1425                 }
1426             }
1427         }
1428     }
1429     if (activatedForClient) {
1430         for (auto it = m_edges.constBegin(); it != m_edges.constEnd(); ++it) {
1431             if ((*it)->client()) {
1432                 (*it)->markAsTriggered(event->globalPos(), QDateTime::fromMSecsSinceEpoch(event->timestamp(), Qt::UTC));
1433             }
1434         }
1435     }
1436     return activated;
1437 }
1438 
handleEnterNotifiy(xcb_window_t window,const QPoint & point,const QDateTime & timestamp)1439 bool ScreenEdges::handleEnterNotifiy(xcb_window_t window, const QPoint &point, const QDateTime &timestamp)
1440 {
1441     bool activated = false;
1442     bool activatedForClient = false;
1443     for (auto it = m_edges.begin(); it != m_edges.end(); ++it) {
1444         Edge *edge = *it;
1445         if (!edge || edge->window() == XCB_WINDOW_NONE) {
1446             continue;
1447         }
1448         if (!edge->isReserved()) {
1449             continue;
1450         }
1451         if (!edge->activatesForPointer()) {
1452             continue;
1453         }
1454         if (edge->window() == window) {
1455             if (edge->check(point, timestamp)) {
1456                 if ((*it)->client()) {
1457                     activatedForClient = true;
1458                 }
1459             }
1460             activated = true;
1461             break;
1462         }
1463         if (edge->approachWindow() == window) {
1464             edge->startApproaching();
1465             // TODO: if it's a corner, it should also trigger for other windows
1466             return true;
1467         }
1468     }
1469     if (activatedForClient) {
1470         for (auto it = m_edges.constBegin(); it != m_edges.constEnd(); ++it) {
1471             if ((*it)->client()) {
1472                 (*it)->markAsTriggered(point, timestamp);
1473             }
1474         }
1475     }
1476     return activated;
1477 }
1478 
handleDndNotify(xcb_window_t window,const QPoint & point)1479 bool ScreenEdges::handleDndNotify(xcb_window_t window, const QPoint &point)
1480 {
1481     for (auto it = m_edges.begin(); it != m_edges.end(); ++it) {
1482         Edge *edge = *it;
1483         if (!edge || edge->window() == XCB_WINDOW_NONE) {
1484             continue;
1485         }
1486         if (edge->isReserved() && edge->window() == window) {
1487             updateXTime();
1488             edge->check(point, QDateTime::fromMSecsSinceEpoch(xTime(), Qt::UTC), true);
1489             return true;
1490         }
1491     }
1492     return false;
1493 }
1494 
ensureOnTop()1495 void ScreenEdges::ensureOnTop()
1496 {
1497     Xcb::restackWindowsWithRaise(windows());
1498 }
1499 
windows() const1500 QVector< xcb_window_t > ScreenEdges::windows() const
1501 {
1502     QVector<xcb_window_t> wins;
1503     for (auto it = m_edges.constBegin();
1504             it != m_edges.constEnd();
1505             ++it) {
1506         Edge *edge = *it;
1507         xcb_window_t w = edge->window();
1508         if (w != XCB_WINDOW_NONE) {
1509             wins.append(w);
1510         }
1511         // TODO:  lambda
1512         w = edge->approachWindow();
1513         if (w != XCB_WINDOW_NONE) {
1514             wins.append(w);
1515         }
1516     }
1517     return wins;
1518 }
1519 
1520 } //namespace
1521