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