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 #ifndef KWIN_SCREENEDGE_H
20 #define KWIN_SCREENEDGE_H
21 // KWin
22 #include "kwinglobals.h"
23 // KDE includes
24 #include <KSharedConfig>
25 // Qt
26 #include <QObject>
27 #include <QVector>
28 #include <QDateTime>
29 #include <QRect>
30 
31 class QAction;
32 class QMouseEvent;
33 
34 namespace KWin {
35 
36 class AbstractClient;
37 class GestureRecognizer;
38 class ScreenEdges;
39 class SwipeGesture;
40 
41 class KWIN_EXPORT Edge : public QObject
42 {
43     Q_OBJECT
44 public:
45     explicit Edge(ScreenEdges *parent);
46     ~Edge() override;
47     bool isLeft() const;
48     bool isTop() const;
49     bool isRight() const;
50     bool isBottom() const;
51     bool isCorner() const;
52     bool isScreenEdge() const;
53     bool triggersFor(const QPoint &cursorPos) const;
54     bool check(const QPoint &cursorPos, const QDateTime &triggerTime, bool forceNoPushBack = false);
55     void markAsTriggered(const QPoint &cursorPos, const QDateTime &triggerTime);
56     bool isReserved() const;
57     const QRect &approachGeometry() const;
58 
59     ElectricBorder border() const;
60     void reserve(QObject *object, const char *slot);
61     const QHash<QObject *, QByteArray> &callBacks() const;
62     void reserveTouchCallBack(QAction *action);
63     void unreserveTouchCallBack(QAction *action);
touchCallBacks()64     QVector<QAction *> touchCallBacks() const {
65         return m_touchActions;
66     }
67     void startApproaching();
68     void stopApproaching();
69     bool isApproaching() const;
70     void setClient(AbstractClient *client);
71     AbstractClient *client() const;
72     const QRect &geometry() const;
73     void setTouchAction(ElectricBorderAction action);
74 
75     bool activatesForPointer() const;
76     bool activatesForTouchGesture() const;
77 
78     /**
79      * The window id of the native window representing the edge.
80      * Default implementation returns @c 0, which means no window.
81      */
82     virtual quint32 window() const;
83     /**
84      * The approach window is a special window to notice when get close to the screen border but
85      * not yet triggering the border.
86      *
87      * The default implementation returns @c 0, which means no window.
88      */
89     virtual quint32 approachWindow() const;
90 
91 public Q_SLOTS:
92     void reserve();
93     void unreserve();
94     void unreserve(QObject *object);
95     void setBorder(ElectricBorder border);
96     void setAction(ElectricBorderAction action);
97     void setGeometry(const QRect &geometry);
98     void updateApproaching(const QPoint &point);
99     void checkBlocking();
100 Q_SIGNALS:
101     void approaching(ElectricBorder border, qreal factor, const QRect &geometry);
102     void activatesForTouchGestureChanged();
103 protected:
104     ScreenEdges *edges();
105     const ScreenEdges *edges() const;
106     bool isBlocked() const;
107     virtual void doGeometryUpdate();
108     virtual void doActivate();
109     virtual void doDeactivate();
110     virtual void doStartApproaching();
111     virtual void doStopApproaching();
112     virtual void doUpdateBlocking();
113 private:
114     void activate();
115     void deactivate();
116     bool canActivate(const QPoint &cursorPos, const QDateTime &triggerTime);
117     void handle(const QPoint &cursorPos);
118     bool handleAction(ElectricBorderAction action);
handlePointerAction()119     bool handlePointerAction() {
120         return handleAction(m_action);
121     }
handleTouchAction()122     bool handleTouchAction() {
123         return handleAction(m_touchAction);
124     }
125     bool handleByCallback();
126     void handleTouchCallback();
127     void switchDesktop(const QPoint &cursorPos);
128     void pushCursorBack(const QPoint &cursorPos);
129     ScreenEdges *m_edges;
130     ElectricBorder m_border;
131     ElectricBorderAction m_action;
132     ElectricBorderAction m_touchAction = ElectricActionNone;
133     int m_reserved;
134     QRect m_geometry;
135     QRect m_approachGeometry;
136     QDateTime m_lastTrigger;
137     QDateTime m_lastReset;
138     QPoint m_triggeredPoint;
139     QHash<QObject *, QByteArray> m_callBacks;
140     bool m_approaching;
141     int m_lastApproachingFactor;
142     bool m_blocked;
143     bool m_pushBackBlocked;
144     AbstractClient *m_client;
145     SwipeGesture *m_gesture;
146     QVector<QAction *> m_touchActions;
147 };
148 
149 /**
150  * @short Class for controlling screen edges.
151  *
152  * The screen edge functionality is split into three parts:
153  * @li This manager class ScreenEdges
154  * @li abstract class @ref Edge
155  * @li specific implementation of @ref Edge, e.g. WindowBasedEdge
156  *
157  * The ScreenEdges creates an @ref Edge for each screen edge which is also an edge in the
158  * combination of all screens. E.g. if there are two screens, no Edge is created between the screens,
159  * but at all other edges even if the screens have a different dimension.
160  *
161  * In addition at each corner of the overall display geometry an one-pixel large @ref Edge is
162  * created. No matter how many screens there are, there will only be exactly four of these corner
163  * edges. This is motivated by Fitts's Law which show that it's easy to trigger such a corner, but
164  * it would be very difficult to trigger a corner between two screens (one pixel target not visually
165  * outlined).
166  *
167  * The ScreenEdges are used for one of the following functionality:
168  * @li switch virtual desktop (see property @ref desktopSwitching)
169  * @li switch virtual desktop when moving a window (see property @ref desktopSwitchingMovingClients)
170  * @li trigger a pre-defined action (see properties @ref actionTop and similar)
171  * @li trigger an externally configured action (e.g. Effect, Script, see @ref reserve, @ref unreserve)
172  *
173  * An @ref Edge is only active if there is at least one of the possible actions "reserved" for this
174  * edge. The idea is to not block the screen edge if nothing could be triggered there, so that the
175  * user e.g. can configure nothing on the top edge, which tends to interfere with full screen apps
176  * having a hidden panel there. On X11 (currently only supported backend) the @ref Edge is
177  * represented by a WindowBasedEdge which creates an input only window for the geometry and
178  * reacts on enter notify events. If the edge gets reserved for the first time a window is created
179  * and mapped, once the edge gets unreserved again, the window gets destroyed.
180  *
181  * When the mouse enters one of the screen edges the following values are used to determine whether
182  * the action should be triggered or the cursor be pushed back
183  * @li Time difference between two entering events is not larger than a certain threshold
184  * @li Time difference between two entering events is larger than @ref timeThreshold
185  * @li Time difference between two activations is larger than @ref reActivateThreshold
186  * @li Distance between two enter events is not larger than a defined pixel distance
187  * These checks are performed in @ref Edge
188  *
189  * @todo change way how Effects/Scripts can reserve an edge and are notified.
190  */
191 class KWIN_EXPORT ScreenEdges : public QObject
192 {
193     Q_OBJECT
194     Q_PROPERTY(bool desktopSwitching READ isDesktopSwitching)
195     Q_PROPERTY(bool desktopSwitchingMovingClients READ isDesktopSwitchingMovingClients)
196     Q_PROPERTY(QSize cursorPushBackDistance READ cursorPushBackDistance)
197     Q_PROPERTY(int timeThreshold READ timeThreshold)
198     Q_PROPERTY(int reActivateThreshold READ reActivationThreshold)
199     Q_PROPERTY(int actionTopLeft READ actionTopLeft)
200     Q_PROPERTY(int actionTop READ actionTop)
201     Q_PROPERTY(int actionTopRight READ actionTopRight)
202     Q_PROPERTY(int actionRight READ actionRight)
203     Q_PROPERTY(int actionBottomRight READ actionBottomRight)
204     Q_PROPERTY(int actionBottom READ actionBottom)
205     Q_PROPERTY(int actionBottomLeft READ actionBottomLeft)
206     Q_PROPERTY(int actionLeft READ actionLeft)
207 public:
208     ~ScreenEdges() override;
209     /**
210      * @internal
211      */
212     void setConfig(KSharedConfig::Ptr config);
213     /**
214      * Initialize the screen edges.
215      * @internal
216      */
217     void init();
218     /**
219      * Check, if a screen edge is entered and trigger the appropriate action
220      * if one is enabled for the current region and the timeout is satisfied
221      * @param pos the position of the mouse pointer
222      * @param now the time when the function is called
223      * @param forceNoPushBack needs to be called to workaround some DnD clients, don't use unless you want to chek on a DnD event
224      */
225     void check(const QPoint& pos, const QDateTime &now, bool forceNoPushBack = false);
226     /**
227      * The (dpi dependent) length, reserved for the active corners of each edge - 1/3"
228      */
229     int cornerOffset() const;
230     /**
231      * Mark the specified screen edge as reserved. This method is provided for external activation
232      * like effects and scripts. When the effect/script does no longer need the edge it is supposed
233      * to call @ref unreserve.
234      * @param border the screen edge to mark as reserved
235      * @param object The object on which the @p callback needs to be invoked
236      * @param callback The method name to be invoked - uses QMetaObject::invokeMethod
237      * @see unreserve
238      * @todo: add pointer to script/effect
239      */
240     void reserve(ElectricBorder border, QObject *object, const char *callback);
241     /**
242      * Mark the specified screen edge as unreserved. This method is provided for external activation
243      * like effects and scripts. This method is only allowed to be called if @ref reserve had been
244      * called before for the same @p border. An unbalanced calling of reserve/unreserve leads to the
245      * edge never being active or never being able to deactivate again.
246      * @param border the screen edge to mark as unreserved
247      * @param object the object on which the callback had been invoked
248      * @see reserve
249      * @todo: add pointer to script/effect
250      */
251     void unreserve(ElectricBorder border, QObject *object);
252     /**
253      * Reserves an edge for the @p client. The idea behind this is to show the @p client if the
254      * screen edge which the @p client borders gets triggered.
255      *
256      * When first called it is tried to create an Edge for the client. This is only done if the
257      * client borders with a screen edge specified by @p border. If the client doesn't border the
258      * screen edge, no Edge gets created and the client is shown again. Otherwise there would not
259      * be a possibility to show the client again.
260      *
261      * On subsequent calls for the client no new Edge is created, but the existing one gets reused
262      * and if the client is already hidden, the Edge gets reserved.
263      *
264      * Once the Edge for the client triggers, the client gets shown again and the Edge unreserved.
265      * The idea is that the Edge can only get activated if the client is currently hidden.
266      *
267      * To make sure that the client can always be shown again the implementation also starts to
268      * track geometry changes and shows the Client again. The same for screen geometry changes.
269      *
270      * The Edge gets automatically destroyed if the client gets released.
271      * @param client The Client for which an Edge should be reserved
272      * @param border The border which the client wants to use, only proper borders are supported (no corners)
273      */
274     void reserve(KWin::AbstractClient *client, ElectricBorder border);
275 
276     /**
277      * Mark the specified screen edge as reserved for touch gestures. This method is provided for
278      * external activation like effects and scripts.
279      * When the effect/script does no longer need the edge it is supposed
280      * to call @ref unreserveTouch.
281      * @param border the screen edge to mark as reserved
282      * @param action The action which gets triggered
283      * @see unreserveTouch
284      * @since 5.10
285      */
286     void reserveTouch(ElectricBorder border, QAction *action);
287     /**
288      * Unreserves the specified @p border from activating the @p action for touch gestures.
289      * @see reserveTouch
290      * @since 5.10
291      */
292     void unreserveTouch(ElectricBorder border, QAction *action);
293 
294     /**
295      * Reserve desktop switching for screen edges, if @p isToReserve is @c true. Unreserve otherwise.
296      * @param isToReserve indicated whether desktop switching should be reserved or unreseved
297      * @param o Qt orientations
298      */
299     void reserveDesktopSwitching(bool isToReserve, Qt::Orientations o);
300     /**
301      * Raise electric border windows to the real top of the screen. We only need
302      * to do this if an effect input window is active.
303      */
304     void ensureOnTop();
305     bool isEntered(QMouseEvent *event);
306 
307     /**
308      * Returns a QVector of all existing screen edge windows
309      * @return all existing screen edge windows in a QVector
310      */
311     QVector< xcb_window_t > windows() const;
312 
313     bool isDesktopSwitching() const;
314     bool isDesktopSwitchingMovingClients() const;
315     const QSize &cursorPushBackDistance() const;
316     /**
317      * Minimum time between the push back of the cursor and the activation by re-entering the edge.
318      */
319     int timeThreshold() const;
320     /**
321      * Minimum time between triggers
322      */
323     int reActivationThreshold() const;
324     ElectricBorderAction actionTopLeft() const;
325     ElectricBorderAction actionTop() const;
326     ElectricBorderAction actionTopRight() const;
327     ElectricBorderAction actionRight() const;
328     ElectricBorderAction actionBottomRight() const;
329     ElectricBorderAction actionBottom() const;
330     ElectricBorderAction actionBottomLeft() const;
331     ElectricBorderAction actionLeft() const;
332 
gestureRecognizer()333     GestureRecognizer *gestureRecognizer() const {
334         return m_gestureRecognizer;
335     }
336 
337     bool handleDndNotify(xcb_window_t window, const QPoint &point);
338     bool handleEnterNotifiy(xcb_window_t window, const QPoint &point, const QDateTime &timestamp);
339 
340 public Q_SLOTS:
341     void reconfigure();
342     /**
343      * Updates the layout of virtual desktops and adjust the reserved borders in case of
344      * virtual desktop switching on edges.
345      */
346     void updateLayout();
347     /**
348      * Recreates all edges e.g. after the screen size changes.
349      */
350     void recreateEdges();
351 
352 Q_SIGNALS:
353     /**
354      * Signal emitted during approaching of mouse towards @p border. The @p factor indicates how
355      * far away the mouse is from the approaching area. The values are clamped into [0.0,1.0] with
356      * @c 0.0 meaning far away from the border, @c 1.0 in trigger distance.
357      */
358     void approaching(ElectricBorder border, qreal factor, const QRect &geometry);
359     void checkBlocking();
360 
361 private:
362     enum {
363         ElectricDisabled = 0,
364         ElectricMoveOnly = 1,
365         ElectricAlways = 2,
366     };
367     void setDesktopSwitching(bool enable);
368     void setDesktopSwitchingMovingClients(bool enable);
369     void setCursorPushBackDistance(const QSize &distance);
370     void setTimeThreshold(int threshold);
371     void setReActivationThreshold(int threshold);
372     void createHorizontalEdge(ElectricBorder border, const QRect &screen, const QRect &fullArea);
373     void createVerticalEdge(ElectricBorder border, const QRect &screen, const QRect &fullArea);
374     Edge *createEdge(ElectricBorder border, int x, int y, int width, int height, bool createAction = true);
375     void setActionForBorder(ElectricBorder border, ElectricBorderAction *oldValue, ElectricBorderAction newValue);
376     void setActionForTouchBorder(ElectricBorder border, ElectricBorderAction newValue);
377     ElectricBorderAction actionForEdge(Edge *edge) const;
378     ElectricBorderAction actionForTouchEdge(Edge *edge) const;
379     void createEdgeForClient(AbstractClient *client, ElectricBorder border);
380     void deleteEdgeForClient(AbstractClient *client);
381     bool m_desktopSwitching;
382     bool m_desktopSwitchingMovingClients;
383     QSize m_cursorPushBackDistance;
384     int m_timeThreshold;
385     int m_reactivateThreshold;
386     Qt::Orientations m_virtualDesktopLayout;
387     QList<Edge*> m_edges;
388     KSharedConfig::Ptr m_config;
389     ElectricBorderAction m_actionTopLeft;
390     ElectricBorderAction m_actionTop;
391     ElectricBorderAction m_actionTopRight;
392     ElectricBorderAction m_actionRight;
393     ElectricBorderAction m_actionBottomRight;
394     ElectricBorderAction m_actionBottom;
395     ElectricBorderAction m_actionBottomLeft;
396     ElectricBorderAction m_actionLeft;
397     QMap<ElectricBorder, ElectricBorderAction> m_touchActions;
398     int m_cornerOffset;
399     GestureRecognizer *m_gestureRecognizer;
400 
401     KWIN_SINGLETON(ScreenEdges)
402 };
403 
404 /**********************************************************
405  * Inlines Edge
406  *********************************************************/
407 
isBottom()408 inline bool Edge::isBottom() const
409 {
410     return m_border == ElectricBottom || m_border == ElectricBottomLeft || m_border == ElectricBottomRight;
411 }
412 
isLeft()413 inline bool Edge::isLeft() const
414 {
415     return m_border == ElectricLeft || m_border == ElectricTopLeft || m_border == ElectricBottomLeft;
416 }
417 
isRight()418 inline bool Edge::isRight() const
419 {
420     return m_border == ElectricRight || m_border == ElectricTopRight || m_border == ElectricBottomRight;
421 }
422 
isTop()423 inline bool Edge::isTop() const
424 {
425     return m_border == ElectricTop || m_border == ElectricTopLeft || m_border == ElectricTopRight;
426 }
427 
isCorner()428 inline bool Edge::isCorner() const
429 {
430     return m_border == ElectricTopLeft
431         || m_border == ElectricTopRight
432         || m_border == ElectricBottomRight
433         || m_border == ElectricBottomLeft;
434 }
435 
isScreenEdge()436 inline bool Edge::isScreenEdge() const
437 {
438     return m_border == ElectricLeft
439         || m_border == ElectricRight
440         || m_border == ElectricTop
441         || m_border == ElectricBottom;
442 }
443 
isReserved()444 inline bool Edge::isReserved() const
445 {
446     return m_reserved != 0;
447 }
448 
setAction(ElectricBorderAction action)449 inline void Edge::setAction(ElectricBorderAction action)
450 {
451     m_action = action;
452 }
453 
edges()454 inline ScreenEdges *Edge::edges()
455 {
456     return m_edges;
457 }
458 
edges()459 inline const ScreenEdges *Edge::edges() const
460 {
461     return m_edges;
462 }
463 
geometry()464 inline const QRect &Edge::geometry() const
465 {
466     return m_geometry;
467 }
468 
approachGeometry()469 inline const QRect &Edge::approachGeometry() const
470 {
471     return m_approachGeometry;
472 }
473 
border()474 inline ElectricBorder Edge::border() const
475 {
476     return m_border;
477 }
478 
callBacks()479 inline const QHash< QObject *, QByteArray > &Edge::callBacks() const
480 {
481     return m_callBacks;
482 }
483 
isBlocked()484 inline bool Edge::isBlocked() const
485 {
486     return m_blocked;
487 }
488 
client()489 inline AbstractClient *Edge::client() const
490 {
491     return m_client;
492 }
493 
isApproaching()494 inline bool Edge::isApproaching() const
495 {
496     return m_approaching;
497 }
498 
499 /**********************************************************
500  * Inlines ScreenEdges
501  *********************************************************/
setConfig(KSharedConfig::Ptr config)502 inline void ScreenEdges::setConfig(KSharedConfig::Ptr config)
503 {
504     m_config = config;
505 }
506 
cornerOffset()507 inline int ScreenEdges::cornerOffset() const {
508     return m_cornerOffset;
509 }
510 
cursorPushBackDistance()511 inline const QSize &ScreenEdges::cursorPushBackDistance() const
512 {
513     return m_cursorPushBackDistance;
514 }
515 
isDesktopSwitching()516 inline bool ScreenEdges::isDesktopSwitching() const
517 {
518     return m_desktopSwitching;
519 }
520 
isDesktopSwitchingMovingClients()521 inline bool ScreenEdges::isDesktopSwitchingMovingClients() const
522 {
523     return m_desktopSwitchingMovingClients;
524 }
525 
reActivationThreshold()526 inline int ScreenEdges::reActivationThreshold() const
527 {
528     return m_reactivateThreshold;
529 }
530 
timeThreshold()531 inline int ScreenEdges::timeThreshold() const
532 {
533     return m_timeThreshold;
534 }
535 
setCursorPushBackDistance(const QSize & distance)536 inline void ScreenEdges::setCursorPushBackDistance(const QSize &distance)
537 {
538     m_cursorPushBackDistance = distance;
539 }
540 
setDesktopSwitching(bool enable)541 inline void ScreenEdges::setDesktopSwitching(bool enable)
542 {
543     if (enable == m_desktopSwitching) {
544         return;
545     }
546     m_desktopSwitching = enable;
547     reserveDesktopSwitching(enable, m_virtualDesktopLayout);
548 }
549 
setDesktopSwitchingMovingClients(bool enable)550 inline void ScreenEdges::setDesktopSwitchingMovingClients(bool enable)
551 {
552     m_desktopSwitchingMovingClients = enable;
553 }
554 
setReActivationThreshold(int threshold)555 inline void ScreenEdges::setReActivationThreshold(int threshold)
556 {
557     Q_ASSERT(threshold >= m_timeThreshold);
558     m_reactivateThreshold = threshold;
559 }
560 
setTimeThreshold(int threshold)561 inline void ScreenEdges::setTimeThreshold(int threshold)
562 {
563     m_timeThreshold = threshold;
564 }
565 
566 #define ACTION( name ) \
567 inline ElectricBorderAction ScreenEdges::name() const \
568 { \
569     return m_##name; \
570 }
571 
572 ACTION(actionTopLeft)
573 ACTION(actionTop)
574 ACTION(actionTopRight)
575 ACTION(actionRight)
576 ACTION(actionBottomRight)
577 ACTION(actionBottom)
578 ACTION(actionBottomLeft)
579 ACTION(actionLeft)
580 
581 #undef ACTION
582 
583 }
584 #endif // KWIN_SCREENEDGE_H
585