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 ×tamp)
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