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