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