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