1/********************************************************************
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5Copyright (C) 2011 Arthur Arlt <a.arlt@stud.uni-heidelberg.de>
6Copyright (C) 2013 Martin Gräßlin <mgraesslin@kde.org>
7
8Since the functionality provided in this class has been moved from
9class Workspace, it is not clear who exactly has written the code.
10The list below contains the copyright holders of the class Workspace.
11
12Copyright (C) 1999, 2000 Matthias Ettrich <ettrich@kde.org>
13Copyright (C) 2003 Lubos Lunak <l.lunak@kde.org>
14Copyright (C) 2009 Lucas Murray <lmurray@undefinedfire.com>
15
16This program is free software; you can redistribute it and/or modify
17it under the terms of the GNU General Public License as published by
18the Free Software Foundation; either version 2 of the License, or
19(at your option) any later version.
20
21This program is distributed in the hope that it will be useful,
22but WITHOUT ANY WARRANTY; without even the implied warranty of
23MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24GNU General Public License for more details.
25
26You should have received a copy of the GNU General Public License
27along with this program. If not, see <http://www.gnu.org/licenses/>.
28*********************************************************************/
29
30#include "screenedge.h"
31
32// KWin
33#include "atoms.h"
34#include "client.h"
35#include "cursor.h"
36#include "effects.h"
37#include "screens.h"
38#include "utils.h"
39#include "workspace.h"
40#include "virtualdesktops.h"
41// Qt
42#include <QTimer>
43#include <QVector>
44#include <QTextStream>
45#include <QtDBus/QDBusInterface>
46#include <QtDBus/QDBusPendingCall>
47
48namespace KWin {
49
50// Mouse should not move more than this many pixels
51static const int DISTANCE_RESET = 30;
52
53Edge::Edge(ScreenEdges *parent)
54 : QObject(parent)
55 , m_edges(parent)
56 , m_border(ElectricNone)
57 , m_action(ElectricActionNone)
58 , m_reserved(0)
59 , m_approaching(false)
60 , m_lastApproachingFactor(0)
61 , m_blocked(false)
62{
63}
64
65Edge::~Edge()
66{
67}
68
69void Edge::reserve()
70{
71 m_reserved++;
72 if (m_reserved == 1) {
73 // got activated
74 activate();
75 }
76}
77
78void Edge::reserve(QObject *object, const char *slot)
79{
80 connect(object, SIGNAL(destroyed(QObject*)), SLOT(unreserve(QObject*)));
81 m_callBacks.insert(object, QByteArray(slot));
82 reserve();
83}
84
85void Edge::unreserve()
86{
87 m_reserved--;
88 if (m_reserved == 0) {
89 // got deactivated
90 deactivate();
91 }
92}
93void Edge::unreserve(QObject *object)
94{
95 if (m_callBacks.contains(object)) {
96 m_callBacks.remove(object);
97 disconnect(object, SIGNAL(destroyed(QObject*)), this, SLOT(unreserve(QObject*)));
98 unreserve();
99 }
100}
101
102bool Edge::triggersFor(const QPoint &cursorPos) const
103{
104 if (isBlocked()) {
105 return false;
106 }
107 if (!m_geometry.contains(cursorPos)) {
108 return false;
109 }
110 if (isLeft() && cursorPos.x() != m_geometry.x()) {
111 return false;
112 }
113 if (isRight() && cursorPos.x() != (m_geometry.x() + m_geometry.width() -1)) {
114 return false;
115 }
116 if (isTop() && cursorPos.y() != m_geometry.y()) {
117 return false;
118 }
119 if (isBottom() && cursorPos.y() != (m_geometry.y() + m_geometry.height() -1)) {
120 return false;
121 }
122 return true;
123}
124
125void Edge::check(const QPoint &cursorPos, const QDateTime &triggerTime, bool forceNoPushBack)
126{
127 if (!triggersFor(cursorPos)) {
128 return;
129 }
130 // no pushback so we have to activate at once
131 bool directActivate = forceNoPushBack || edges()->cursorPushBackDistance().isNull();
132 if (directActivate || canActivate(cursorPos, triggerTime)) {
133 m_lastTrigger = triggerTime;
134 m_lastReset = QDateTime(); // invalidate
135 handle(cursorPos);
136 } else {
137 pushCursorBack(cursorPos);
138 }
139 m_triggeredPoint = cursorPos;
140}
141
142bool Edge::canActivate(const QPoint &cursorPos, const QDateTime &triggerTime)
143{
144 // we check whether either the timer has explicitly been invalidated (successfull trigger) or is
145 // bigger than the reactivation threshold (activation "aborted", usually due to moving away the cursor
146 // from the corner after successfull activation)
147 // either condition means that "this is the first event in a new attempt"
148 if (!m_lastReset.isValid() || m_lastReset.msecsTo(triggerTime) > edges()->reActivationThreshold()) {
149 m_lastReset = triggerTime;
150 return false;
151 }
152 if (m_lastTrigger.msecsTo(triggerTime) < edges()->reActivationThreshold()) {
153 return false;
154 }
155 if (m_lastReset.msecsTo(triggerTime) < edges()->timeThreshold()) {
156 return false;
157 }
158 // does the check on position make any sense at all?
159 if ((cursorPos - m_triggeredPoint).manhattanLength() > DISTANCE_RESET) {
160 return false;
161 }
162 return true;
163}
164
165void Edge::handle(const QPoint &cursorPos)
166{
167 if ((edges()->isDesktopSwitchingMovingClients() && Workspace::self()->getMovingClient()) ||
168 (edges()->isDesktopSwitching() && isScreenEdge())) {
169 // always switch desktops in case:
170 // moving a Client and option for switch on client move is enabled
171 // or switch on screen edge is enabled
172 switchDesktop(cursorPos);
173 return;
174 }
175 if (Workspace::self()->getMovingClient()) {
176 // if we are moving a window we don't want to trigger the actions. This just results in
177 // problems, e.g. Desktop Grid activated or screen locker activated which just cannot
178 // work as we hold a grab.
179 return;
180 }
181 if (handleAction() || handleByCallback()) {
182 pushCursorBack(cursorPos);
183 return;
184 }
185 if (edges()->isDesktopSwitching() && isCorner()) {
186 // try again desktop switching for the corner
187 switchDesktop(cursorPos);
188 }
189}
190
191bool Edge::handleAction()
192{
193 switch (m_action) {
194 case ElectricActionDashboard: { // Display Plasma dashboard
195 QDBusInterface plasmaApp("org.kde.plasma-desktop", "/App");
196 plasmaApp.asyncCall("toggleDashboard");
197 return true;
198 }
199 case ElectricActionShowDesktop: {
200 Workspace::self()->setShowingDesktop(!Workspace::self()->showingDesktop());
201 return true;
202 }
203 case ElectricActionLockScreen: { // Lock the screen
204 QDBusInterface screenSaver("org.kde.screensaver", "/ScreenSaver");
205 screenSaver.asyncCall("Lock");
206 return true;
207 }
208 default:
209 return false;
210 }
211}
212
213bool Edge::handleByCallback()
214{
215 if (m_callBacks.isEmpty()) {
216 return false;
217 }
218 for (QHash<QObject *, QByteArray>::iterator it = m_callBacks.begin();
219 it != m_callBacks.end();
220 ++it) {
221 bool retVal = false;
222 QMetaObject::invokeMethod(it.key(), it.value().constData(), Q_RETURN_ARG(bool, retVal), Q_ARG(ElectricBorder, m_border));
223 if (retVal) {
224 return true;
225 }
226 }
227 return false;
228}
229
230void Edge::switchDesktop(const QPoint &cursorPos)
231{
232 QPoint pos(cursorPos);
233 VirtualDesktopManager *vds = VirtualDesktopManager::self();
234 const uint oldDesktop = vds->current();
235 uint desktop = oldDesktop;
236 const int OFFSET = 2;
237 if (isLeft()) {
238 const uint interimDesktop = desktop;
239 desktop = vds->toLeft(desktop, vds->isNavigationWrappingAround());
240 if (desktop != interimDesktop)
241 pos.setX(displayWidth() - 1 - OFFSET);
242 } else if (isRight()) {
243 const uint interimDesktop = desktop;
244 desktop = vds->toRight(desktop, vds->isNavigationWrappingAround());
245 if (desktop != interimDesktop)
246 pos.setX(OFFSET);
247 }
248 if (isTop()) {
249 const uint interimDesktop = desktop;
250 desktop = vds->above(desktop, vds->isNavigationWrappingAround());
251 if (desktop != interimDesktop)
252 pos.setY(displayHeight() - 1 - OFFSET);
253 } else if (isBottom()) {
254 const uint interimDesktop = desktop;
255 desktop = vds->below(desktop, vds->isNavigationWrappingAround());
256 if (desktop != interimDesktop)
257 pos.setY(OFFSET);
258 }
259 if (Client *c = Workspace::self()->getMovingClient()) {
260 if (c->rules()->checkDesktop(desktop) != int(desktop)) {
261 // user attempts to move a client to another desktop where it is ruleforced to not be
262 return;
263 }
264 }
265 vds->setCurrent(desktop);
266 if (vds->current() != oldDesktop) {
267 Cursor::setPos(pos);
268 }
269}
270
271void Edge::pushCursorBack(const QPoint &cursorPos)
272{
273 int x = cursorPos.x();
274 int y = cursorPos.y();
275 const QSize &distance = edges()->cursorPushBackDistance();
276 if (isLeft()) {
277 x += distance.width();
278 }
279 if (isRight()) {
280 x -= distance.width();
281 }
282 if (isTop()) {
283 y += distance.height();
284 }
285 if (isBottom()) {
286 y -= distance.height();
287 }
288 Cursor::setPos(x, y);
289}
290
291void Edge::setGeometry(const QRect &geometry)
292{
293 if (m_geometry == geometry) {
294 return;
295 }
296 m_geometry = geometry;
297 int x = m_geometry.x();
298 int y = m_geometry.y();
299 int width = m_geometry.width();
300 int height = m_geometry.height();
301 const int size = m_edges->cornerOffset();
302 if (isCorner()) {
303 if (isRight()) {
304 x = x - size +1;
305 }
306 if (isBottom()) {
307 y = y - size +1;
308 }
309 width = size;
310 height = size;
311 } else {
312 if (isLeft()) {
313 y += size + 1;
314 width = size;
315 height = height - size * 2;
316 } else if (isRight()) {
317 x = x - size + 1;
318 y += size;
319 width = size;
320 height = height - size * 2;
321 } else if (isTop()) {
322 x += size;
323 width = width - size * 2;
324 height = size;
325 } else if (isBottom()) {
326 x += size;
327 y = y - size +1;
328 width = width - size * 2;
329 height = size;
330 }
331 }
332 m_approachGeometry = QRect(x, y, width, height);
333 doGeometryUpdate();
334}
335
336void Edge::checkBlocking()
337{
338 if (isCorner()) {
339 return;
340 }
341 bool newValue = false;
342 if (Client *client = Workspace::self()->activeClient()) {
343 newValue = client->isFullScreen() && client->geometry().contains(m_geometry.center());
344 }
345 if (newValue == m_blocked) {
346 return;
347 }
348 m_blocked = newValue;
349 doUpdateBlocking();
350}
351
352void Edge::doUpdateBlocking()
353{
354}
355
356void Edge::doGeometryUpdate()
357{
358}
359
360void Edge::activate()
361{
362}
363
364void Edge::deactivate()
365{
366}
367
368void Edge::startApproaching()
369{
370 if (m_approaching) {
371 return;
372 }
373 m_approaching = true;
374 doStartApproaching();
375 m_lastApproachingFactor = 0;
376 emit approaching(border(), 0.0, m_approachGeometry);
377}
378
379void Edge::doStartApproaching()
380{
381}
382
383void Edge::stopApproaching()
384{
385 if (!m_approaching) {
386 return;
387 }
388 m_approaching = false;
389 doStopApproaching();
390 m_lastApproachingFactor = 0;
391 emit approaching(border(), 0.0, m_approachGeometry);
392}
393
394void Edge::doStopApproaching()
395{
396}
397
398void Edge::updateApproaching(const QPoint &point)
399{
400 if (approachGeometry().contains(point)) {
401 int factor = 0;
402 const int edgeDistance = m_edges->cornerOffset();
403 // manhattan length for our edge
404 const int cornerDistance = 2*edgeDistance;
405 switch (border()) {
406 case ElectricTopLeft:
407 factor = (point.manhattanLength()<<8) / cornerDistance;
408 break;
409 case ElectricTopRight:
410 factor = ((point - approachGeometry().topRight()).manhattanLength()<<8) / cornerDistance;
411 break;
412 case ElectricBottomRight:
413 factor = ((point - approachGeometry().bottomRight()).manhattanLength()<<8) / cornerDistance;
414 break;
415 case ElectricBottomLeft:
416 factor = ((point - approachGeometry().bottomLeft()).manhattanLength()<<8) / cornerDistance;
417 break;
418 case ElectricTop:
419 factor = (qAbs(point.y() - approachGeometry().y())<<8) / edgeDistance;
420 break;
421 case ElectricRight:
422 factor = (qAbs(point.x() - approachGeometry().right())<<8) / edgeDistance;
423 break;
424 case ElectricBottom:
425 factor = (qAbs(point.y() - approachGeometry().bottom())<<8) / edgeDistance;
426 break;
427 case ElectricLeft:
428 factor = (qAbs(point.x() - approachGeometry().x())<<8) / edgeDistance;
429 break;
430 default:
431 break;
432 }
433 factor = 256 - factor;
434 if (m_lastApproachingFactor != factor) {
435 m_lastApproachingFactor = factor;
436 emit approaching(border(), m_lastApproachingFactor/256.0f, m_approachGeometry);
437 }
438 } else {
439 stopApproaching();
440 }
441}
442
443/**********************************************************
444 * ScreenEdges
445 *********************************************************/
446WindowBasedEdge::WindowBasedEdge(ScreenEdges *parent)
447 : Edge(parent)
448 , m_window(XCB_WINDOW_NONE)
449 , m_approachWindow(XCB_WINDOW_NONE)
450{
451}
452
453WindowBasedEdge::~WindowBasedEdge()
454{
455}
456
457void WindowBasedEdge::activate()
458{
459 createWindow();
460 createApproachWindow();
461 doUpdateBlocking();
462}
463
464void WindowBasedEdge::deactivate()
465{
466 m_window.reset();
467 m_approachWindow.reset();
468}
469
470void WindowBasedEdge::createWindow()
471{
472 if (m_window.isValid()) {
473 return;
474 }
475 const uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
476 const uint32_t values[] = {
477 true,
478 XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW
479 };
480 m_window.create(geometry(), XCB_WINDOW_CLASS_INPUT_ONLY, mask, values);
481 m_window.map();
482 // Set XdndAware on the windows, so that DND enter events are received (#86998)
483 xcb_atom_t version = 4; // XDND version
484 xcb_change_property(connection(), XCB_PROP_MODE_REPLACE, m_window,
485 atoms->xdnd_aware, XCB_ATOM_ATOM, 32, 1, (unsigned char*)(&version));
486}
487
488void WindowBasedEdge::createApproachWindow()
489{
490 if (m_approachWindow.isValid()) {
491 return;
492 }
493 if (!approachGeometry().isValid()) {
494 return;
495 }
496 const uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
497 const uint32_t values[] = {
498 true,
499 XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW
500 };
501 m_approachWindow.create(approachGeometry(), XCB_WINDOW_CLASS_INPUT_ONLY, mask, values);
502 m_approachWindow.map();
503}
504
505void WindowBasedEdge::doGeometryUpdate()
506{
507 m_window.setGeometry(geometry());
508 m_approachWindow.setGeometry(approachGeometry());
509}
510
511void WindowBasedEdge::doStartApproaching()
512{
513 m_approachWindow.unmap();
514 Cursor *cursor = Cursor::self();
515 connect(cursor, SIGNAL(posChanged(QPoint)), SLOT(updateApproaching(QPoint)));
516 cursor->startMousePolling();
517}
518
519void WindowBasedEdge::doStopApproaching()
520{
521 Cursor *cursor = Cursor::self();
522 disconnect(cursor, SIGNAL(posChanged(QPoint)), this, SLOT(updateApproaching(QPoint)));
523 cursor->stopMousePolling();
524 m_approachWindow.map();
525}
526
527void WindowBasedEdge::doUpdateBlocking()
528{
529 if (!isReserved()) {
530 return;
531 }
532 if (isBlocked()) {
533 m_window.unmap();
534 m_approachWindow.unmap();
535 } else {
536 m_window.map();
537 m_approachWindow.map();
538 }
539}
540
541/**********************************************************
542 * ScreenEdges
543 *********************************************************/
544KWIN_SINGLETON_FACTORY(ScreenEdges)
545
546ScreenEdges::ScreenEdges(QObject *parent)
547 : QObject(parent)
548 , m_desktopSwitching(false)
549 , m_desktopSwitchingMovingClients(false)
550 , m_timeThreshold(0)
551 , m_reactivateThreshold(0)
552 , m_virtualDesktopLayout(0)
553 , m_actionTopLeft(ElectricActionNone)
554 , m_actionTop(ElectricActionNone)
555 , m_actionTopRight(ElectricActionNone)
556 , m_actionRight(ElectricActionNone)
557 , m_actionBottomRight(ElectricActionNone)
558 , m_actionBottom(ElectricActionNone)
559 , m_actionBottomLeft(ElectricActionNone)
560 , m_actionLeft(ElectricActionNone)
561{
562 QWidget w;
563 m_cornerOffset = (w.physicalDpiX() + w.physicalDpiY() + 5) / 6;
564}
565
566ScreenEdges::~ScreenEdges()
567{
568 s_self = NULL;
569}
570
571void ScreenEdges::init()
572{
573 reconfigure();
574 updateLayout();
575 recreateEdges();
576}
577static ElectricBorderAction electricBorderAction(const QString& name)
578{
579 QString lowerName = name.toLower();
580 if (lowerName == "dashboard") {
581 return ElectricActionDashboard;
582 } else if (lowerName == "showdesktop") {
583 return ElectricActionShowDesktop;
584 } else if (lowerName == "lockscreen") {
585 return ElectricActionLockScreen;
586 } else if (lowerName == "preventscreenlocking") {
587 return ElectricActionPreventScreenLocking;
588 }
589 return ElectricActionNone;
590}
591
592void ScreenEdges::reconfigure()
593{
594 if (!m_config) {
595 return;
596 }
597 // TODO: migrate settings to a group ScreenEdges
598 KConfigGroup windowsConfig = m_config->group("Windows");
599 setTimeThreshold(windowsConfig.readEntry("ElectricBorderDelay", 150));
600 setReActivationThreshold(qMax(timeThreshold() + 50, windowsConfig.readEntry("ElectricBorderCooldown", 350)));
601 int desktopSwitching = windowsConfig.readEntry("ElectricBorders", static_cast<int>(ElectricDisabled));
602 if (desktopSwitching == ElectricDisabled) {
603 setDesktopSwitching(false);
604 setDesktopSwitchingMovingClients(false);
605 } else if (desktopSwitching == ElectricMoveOnly) {
606 setDesktopSwitching(false);
607 setDesktopSwitchingMovingClients(true);
608 } else if (desktopSwitching == ElectricAlways) {
609 setDesktopSwitching(true);
610 setDesktopSwitchingMovingClients(true);
611 }
612 const int pushBack = windowsConfig.readEntry("ElectricBorderPushbackPixels", 1);
613 m_cursorPushBackDistance = QSize(pushBack, pushBack);
614
615 KConfigGroup borderConfig = m_config->group("ElectricBorders");
616 setActionForBorder(ElectricTopLeft, &m_actionTopLeft,
617 electricBorderAction(borderConfig.readEntry("TopLeft", "None")));
618 setActionForBorder(ElectricTop, &m_actionTop,
619 electricBorderAction(borderConfig.readEntry("Top", "None")));
620 setActionForBorder(ElectricTopRight, &m_actionTopRight,
621 electricBorderAction(borderConfig.readEntry("TopRight", "None")));
622 setActionForBorder(ElectricRight, &m_actionRight,
623 electricBorderAction(borderConfig.readEntry("Right", "None")));
624 setActionForBorder(ElectricBottomRight, &m_actionBottomRight,
625 electricBorderAction(borderConfig.readEntry("BottomRight", "None")));
626 setActionForBorder(ElectricBottom, &m_actionBottom,
627 electricBorderAction(borderConfig.readEntry("Bottom", "None")));
628 setActionForBorder(ElectricBottomLeft, &m_actionBottomLeft,
629 electricBorderAction(borderConfig.readEntry("BottomLeft", "None")));
630 setActionForBorder(ElectricLeft, &m_actionLeft,
631 electricBorderAction(borderConfig.readEntry("Left", "None")));
632}
633
634void ScreenEdges::setActionForBorder(ElectricBorder border, ElectricBorderAction *oldValue, ElectricBorderAction newValue)
635{
636 if (*oldValue == newValue) {
637 return;
638 }
639 if (*oldValue == ElectricActionNone) {
640 // have to reserve
641 for (QList<WindowBasedEdge*>::iterator it = m_edges.begin(); it != m_edges.end(); ++it) {
642 if ((*it)->border() == border) {
643 (*it)->reserve();
644 }
645 }
646 }
647 if (newValue == ElectricActionNone) {
648 // have to unreserve
649 for (QList<WindowBasedEdge*>::iterator it = m_edges.begin(); it != m_edges.end(); ++it) {
650 if ((*it)->border() == border) {
651 (*it)->unreserve();
652 }
653 }
654 }
655 *oldValue = newValue;
656 // update action on all Edges for given border
657 for (QList<WindowBasedEdge*>::iterator it = m_edges.begin(); it != m_edges.end(); ++it) {
658 if ((*it)->border() == border) {
659 (*it)->setAction(newValue);
660 }
661 }
662}
663
664void ScreenEdges::updateLayout()
665{
666 const QSize desktopMatrix = VirtualDesktopManager::self()->grid().size();
667 Qt::Orientations newLayout = 0;
668 if (desktopMatrix.width() > 1) {
669 newLayout |= Qt::Horizontal;
670 }
671 if (desktopMatrix.height() > 1) {
672 newLayout |= Qt::Vertical;
673 }
674 if (newLayout == m_virtualDesktopLayout) {
675 return;
676 }
677 if (isDesktopSwitching()) {
678 reserveDesktopSwitching(false, m_virtualDesktopLayout);
679 }
680 m_virtualDesktopLayout = newLayout;
681 if (isDesktopSwitching()) {
682 reserveDesktopSwitching(true, m_virtualDesktopLayout);
683 }
684}
685
686static bool isLeftScreen(const QRect &screen, const QRect &fullArea)
687{
688 if (screens()->count() == 1) {
689 return true;
690 }
691 if (screen.x() == fullArea.x()) {
692 return true;
693 }
694 // the screen is also on the left in case of a vertical layout with a second screen
695 // more to the left. In that case no screen ends left of screen's x coord
696 for (int i=0; i<screens()->count(); ++i) {
697 const QRect otherGeo = screens()->geometry(i);
698 if (otherGeo == screen) {
699 // that's our screen to test
700 continue;
701 }
702 if (otherGeo.x() + otherGeo.width() <= screen.x()) {
703 // other screen is completely in the left
704 return false;
705 }
706 }
707 // did not find a screen left of our current screen, so it is the left most
708 return true;
709}
710
711static bool isRightScreen(const QRect &screen, const QRect &fullArea)
712{
713 if (screens()->count() == 1) {
714 return true;
715 }
716 if (screen.x() + screen.width() == fullArea.x() + fullArea.width()) {
717 return true;
718 }
719 // the screen is also on the right in case of a vertical layout with a second screen
720 // more to the right. In that case no screen starts right of this screen
721 for (int i=0; i<screens()->count(); ++i) {
722 const QRect otherGeo = screens()->geometry(i);
723 if (otherGeo == screen) {
724 // that's our screen to test
725 continue;
726 }
727 if (otherGeo.x() >= screen.x() + screen.width()) {
728 // other screen is completely in the right
729 return false;
730 }
731 }
732 // did not find a screen right of our current screen, so it is the right most
733 return true;
734}
735
736static bool isTopScreen(const QRect &screen, const QRect &fullArea)
737{
738 if (screens()->count() == 1) {
739 return true;
740 }
741 if (screen.y() == fullArea.y()) {
742 return true;
743 }
744 // the screen is also top most in case of a horizontal layout with a second screen
745 // more to the top. In that case no screen ends above screen's y coord
746 for (int i=0; i<screens()->count(); ++i) {
747 const QRect otherGeo = screens()->geometry(i);
748 if (otherGeo == screen) {
749 // that's our screen to test
750 continue;
751 }
752 if (otherGeo.y() + otherGeo.height() <= screen.y()) {
753 // other screen is completely above
754 return false;
755 }
756 }
757 // did not find a screen above our current screen, so it is the top most
758 return true;
759}
760
761static bool isBottomScreen(const QRect &screen, const QRect &fullArea)
762{
763 if (screens()->count() == 1) {
764 return true;
765 }
766 if (screen.y() + screen.height() == fullArea.y() + fullArea.height()) {
767 return true;
768 }
769 // the screen is also bottom most in case of a horizontal layout with a second screen
770 // more below. In that case no screen starts below screen's y coord + height
771 for (int i=0; i<screens()->count(); ++i) {
772 const QRect otherGeo = screens()->geometry(i);
773 if (otherGeo == screen) {
774 // that's our screen to test
775 continue;
776 }
777 if (otherGeo.y() >= screen.y() + screen.height()) {
778 // other screen is completely below
779 return false;
780 }
781 }
782 // did not find a screen below our current screen, so it is the bottom most
783 return true;
784}
785
786void ScreenEdges::recreateEdges()
787{
788 QList<WindowBasedEdge*> oldEdges(m_edges);
789 m_edges.clear();
790 const QRect fullArea(0, 0, displayWidth(), displayHeight());
791 for (int i=0; i<screens()->count(); ++i) {
792 const QRect screen = screens()->geometry(i);
793 if (isLeftScreen(screen, fullArea)) {
794 // left most screen
795 createVerticalEdge(ElectricLeft, screen, fullArea);
796 }
797 if (isRightScreen(screen, fullArea)) {
798 // right most screen
799 createVerticalEdge(ElectricRight, screen, fullArea);
800 }
801 if (isTopScreen(screen, fullArea)) {
802 // top most screen
803 createHorizontalEdge(ElectricTop, screen, fullArea);
804 }
805 if (isBottomScreen(screen, fullArea)) {
806 // bottom most screen
807 createHorizontalEdge(ElectricBottom, screen, fullArea);
808 }
809 }
810 // copy over the effect/script reservations from the old edges
811 for (QList<WindowBasedEdge*>::iterator it = m_edges.begin(); it != m_edges.end(); ++it) {
812 WindowBasedEdge *edge = *it;
813 for (QList<WindowBasedEdge*>::const_iterator oldIt = oldEdges.constBegin();
814 oldIt != oldEdges.constEnd();
815 ++oldIt) {
816 WindowBasedEdge *oldEdge = *oldIt;
817 if (oldEdge->border() != edge->border()) {
818 continue;
819 }
820 const QHash<QObject *, QByteArray> &callbacks = oldEdge->callBacks();
821 for (QHash<QObject *, QByteArray>::const_iterator callback = callbacks.begin();
822 callback != callbacks.end();
823 ++callback) {
824 edge->reserve(callback.key(), callback.value().constData());
825 }
826 }
827 }
828 qDeleteAll(oldEdges);
829}
830
831void ScreenEdges::createVerticalEdge(ElectricBorder border, const QRect &screen, const QRect &fullArea)
832{
833 if (border != ElectricRight && border != KWin::ElectricLeft) {
834 return;
835 }
836 int y = screen.y();
837 int height = screen.height();
838 const int x = (border == ElectricLeft) ? screen.x() : screen.x() + screen.width() -1;
839 if (isTopScreen(screen, fullArea)) {
840 // also top most screen
841 height -= m_cornerOffset;
842 y += m_cornerOffset;
843 // create top left/right edge
844 const ElectricBorder edge = (border == ElectricLeft) ? ElectricTopLeft : ElectricTopRight;
845 m_edges << createEdge(edge, x, screen.y(), 1, 1);
846 }
847 if (isBottomScreen(screen, fullArea)) {
848 // also bottom most screen
849 height -= m_cornerOffset;
850 // create bottom left/right edge
851 const ElectricBorder edge = (border == ElectricLeft) ? ElectricBottomLeft : ElectricBottomRight;
852 m_edges << createEdge(edge, x, screen.y() + screen.height() -1, 1, 1);
853 }
854 // create border
855 m_edges << createEdge(border, x, y, 1, height);
856}
857
858void ScreenEdges::createHorizontalEdge(ElectricBorder border, const QRect &screen, const QRect &fullArea)
859{
860 if (border != ElectricTop && border != ElectricBottom) {
861 return;
862 }
863 int x = screen.x();
864 int width = screen.width();
865 if (isLeftScreen(screen, fullArea)) {
866 // also left most - adjust only x and width
867 x += m_cornerOffset;
868 width -= m_cornerOffset;
869 }
870 if (isRightScreen(screen, fullArea)) {
871 // also right most edge
872 width -= m_cornerOffset;
873 }
874 const int y = (border == ElectricTop) ? screen.y() : screen.y() + screen.height() - 1;
875 m_edges << createEdge(border, x, y, width, 1);
876}
877
878WindowBasedEdge *ScreenEdges::createEdge(ElectricBorder border, int x, int y, int width, int height)
879{
880 WindowBasedEdge *edge = new WindowBasedEdge(this);
881 edge->setBorder(border);
882 edge->setGeometry(QRect(x, y, width, height));
883 const ElectricBorderAction action = actionForEdge(edge);
884 if (action != KWin::ElectricActionNone) {
885 edge->reserve();
886 edge->setAction(action);
887 }
888 if (isDesktopSwitching()) {
889 if (edge->isCorner()) {
890 edge->reserve();
891 } else {
892 if ((m_virtualDesktopLayout & Qt::Horizontal) && (edge->isLeft() || edge->isRight())) {
893 edge->reserve();
894 }
895 if ((m_virtualDesktopLayout & Qt::Vertical) && (edge->isTop() || edge->isBottom())) {
896 edge->reserve();
897 }
898 }
899 }
900 connect(edge, SIGNAL(approaching(ElectricBorder,qreal,QRect)), SIGNAL(approaching(ElectricBorder,qreal,QRect)));
901 if (edge->isScreenEdge()) {
902 connect(this, SIGNAL(checkBlocking()), edge, SLOT(checkBlocking()));
903 }
904 return edge;
905}
906
907ElectricBorderAction ScreenEdges::actionForEdge(Edge *edge) const
908{
909 switch (edge->border()) {
910 case ElectricTopLeft:
911 return m_actionTopLeft;
912 case ElectricTop:
913 return m_actionTop;
914 case ElectricTopRight:
915 return m_actionTopRight;
916 case ElectricRight:
917 return m_actionRight;
918 case ElectricBottomRight:
919 return m_actionBottomRight;
920 case ElectricBottom:
921 return m_actionBottom;
922 case ElectricBottomLeft:
923 return m_actionBottomLeft;
924 case ElectricLeft:
925 return m_actionLeft;
926 default:
927 // fall through
928 break;
929 }
930 return ElectricActionNone;
931}
932
933void ScreenEdges::reserveDesktopSwitching(bool isToReserve, Qt::Orientations o)
934{
935 if (!o)
936 return;
937 for (QList<WindowBasedEdge*>::iterator it = m_edges.begin(); it != m_edges.end(); ++it) {
938 WindowBasedEdge *edge = *it;
939 if (edge->isCorner()) {
940 isToReserve ? edge->reserve() : edge->unreserve();
941 } else {
942 if ((m_virtualDesktopLayout & Qt::Horizontal) && (edge->isLeft() || edge->isRight())) {
943 isToReserve ? edge->reserve() : edge->unreserve();
944 }
945 if ((m_virtualDesktopLayout & Qt::Vertical) && (edge->isTop() || edge->isBottom())) {
946 isToReserve ? edge->reserve() : edge->unreserve();
947 }
948 }
949 }
950}
951
952void ScreenEdges::reserve(ElectricBorder border, QObject *object, const char *slot)
953{
954 for (QList<WindowBasedEdge*>::iterator it = m_edges.begin(); it != m_edges.end(); ++it) {
955 if ((*it)->border() == border) {
956 (*it)->reserve(object, slot);
957 }
958 }
959}
960
961void ScreenEdges::unreserve(ElectricBorder border, QObject *object)
962{
963 for (QList<WindowBasedEdge*>::iterator it = m_edges.begin(); it != m_edges.end(); ++it) {
964 if ((*it)->border() == border) {
965 (*it)->unreserve(object);
966 }
967 }
968}
969
970void ScreenEdges::check(const QPoint &pos, const QDateTime &now, bool forceNoPushBack)
971{
972 for (QList<WindowBasedEdge*>::iterator it = m_edges.begin(); it != m_edges.end(); ++it) {
973 (*it)->check(pos, now, forceNoPushBack);
974 }
975}
976
977bool ScreenEdges::isEntered(XEvent* e)
978{
979 if (e->type == EnterNotify) {
980 return handleEnterNotifiy(e->xcrossing.window,
981 QPoint(e->xcrossing.x_root, e->xcrossing.y_root),
982 QDateTime::fromMSecsSinceEpoch(e->xcrossing.time));
983 }
984 if (e->type == ClientMessage) {
985 if (e->xclient.message_type == atoms->xdnd_position) {
986 return handleDndNotify(e->xclient.window,
987 QPoint(e->xclient.data.l[2] >> 16, e->xclient.data.l[2] & 0xffff));
988 }
989 }
990 return false;
991}
992
993bool ScreenEdges::isEntered(xcb_generic_event_t *e)
994{
995 if (e->response_type == XCB_ENTER_NOTIFY) {
996 xcb_enter_notify_event_t *event = reinterpret_cast<xcb_enter_notify_event_t*>(e);
997 return handleEnterNotifiy(event->event,
998 QPoint(event->root_x, event->root_y),
999 QDateTime::fromMSecsSinceEpoch(event->time));
1000 }
1001 if (e->response_type == XCB_CLIENT_MESSAGE) {
1002 xcb_client_message_event_t *event = reinterpret_cast<xcb_client_message_event_t*>(e);
1003 return handleDndNotify(event->window,
1004 QPoint(event->data.data32[2] >> 16, event->data.data32[2] & 0xffff));
1005 }
1006 return false;
1007}
1008
1009bool ScreenEdges::handleEnterNotifiy(xcb_window_t window, const QPoint &point, const QDateTime &timestamp)
1010{
1011 for (QList<WindowBasedEdge*>::iterator it = m_edges.begin(); it != m_edges.end(); ++it) {
1012 WindowBasedEdge *edge = *it;
1013 if (!edge->isReserved()) {
1014 continue;
1015 }
1016 if (edge->window() == window) {
1017 edge->check(point, timestamp);
1018 return true;
1019 }
1020 if (edge->approachWindow() == window) {
1021 edge->startApproaching();
1022 // TODO: if it's a corner, it should also trigger for other windows
1023 return true;
1024 }
1025 }
1026 return false;
1027}
1028
1029bool ScreenEdges::handleDndNotify(xcb_window_t window, const QPoint &point)
1030{
1031 for (QList<WindowBasedEdge*>::iterator it = m_edges.begin(); it != m_edges.end(); ++it) {
1032 WindowBasedEdge *edge = *it;
1033 if (edge->isReserved() && edge->window() == window) {
1034 updateXTime();
1035 edge->check(point, QDateTime::fromMSecsSinceEpoch(xTime()), true);
1036 return true;
1037 }
1038 }
1039 return false;
1040}
1041
1042void ScreenEdges::ensureOnTop()
1043{
1044 Xcb::restackWindowsWithRaise(windows());
1045}
1046
1047QVector< xcb_window_t > ScreenEdges::windows() const
1048{
1049 QVector<xcb_window_t> wins;
1050 for (QList<WindowBasedEdge*>::const_iterator it = m_edges.constBegin();
1051 it != m_edges.constEnd();
1052 ++it) {
1053 xcb_window_t w = (*it)->window();
1054 if (w != XCB_WINDOW_NONE) {
1055 wins.append(w);
1056 }
1057 // TODO: lambda
1058 w = (*it)->approachWindow();
1059 if (w != XCB_WINDOW_NONE) {
1060 wins.append(w);
1061 }
1062 }
1063 return wins;
1064}
1065
1066} //namespace
1067