1 | /******************************************************************** |
2 | KWin - the KDE window manager |
3 | This file is part of the KDE project. |
4 | |
5 | Copyright (C) 2011 Arthur Arlt <a.arlt@stud.uni-heidelberg.de> |
6 | Copyright (C) 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 | Copyright (C) 1999, 2000 Matthias Ettrich <ettrich@kde.org> |
13 | Copyright (C) 2003 Lubos Lunak <l.lunak@kde.org> |
14 | Copyright (C) 2009 Lucas Murray <lmurray@undefinedfire.com> |
15 | |
16 | This program is free software; you can redistribute it and/or modify |
17 | it under the terms of the GNU General Public License as published by |
18 | the Free Software Foundation; either version 2 of the License, or |
19 | (at your option) any later version. |
20 | |
21 | This program is distributed in the hope that it will be useful, |
22 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
23 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
24 | GNU General Public License for more details. |
25 | |
26 | You should have received a copy of the GNU General Public License |
27 | along 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 | |
42 | namespace KWin { |
43 | |
44 | class ScreenEdges; |
45 | |
46 | class Edge : public QObject |
47 | { |
48 | Q_OBJECT |
49 | public: |
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 | |
69 | public 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(); |
78 | Q_SIGNALS: |
79 | void approaching(ElectricBorder border, qreal factor, const QRect &geometry); |
80 | protected: |
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(); |
91 | private: |
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 | |
113 | class WindowBasedEdge : public Edge |
114 | { |
115 | Q_OBJECT |
116 | public: |
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 | |
127 | protected: |
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 | |
135 | private: |
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 | */ |
184 | class 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) |
200 | public: |
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 | |
289 | public 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 | |
301 | Q_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 | |
310 | private: |
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 ×tamp); |
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 | |
349 | inline bool Edge::isBottom() const |
350 | { |
351 | return m_border == ElectricBottom || m_border == ElectricBottomLeft || m_border == ElectricBottomRight; |
352 | } |
353 | |
354 | inline bool Edge::isLeft() const |
355 | { |
356 | return m_border == ElectricLeft || m_border == ElectricTopLeft || m_border == ElectricBottomLeft; |
357 | } |
358 | |
359 | inline bool Edge::isRight() const |
360 | { |
361 | return m_border == ElectricRight || m_border == ElectricTopRight || m_border == ElectricBottomRight; |
362 | } |
363 | |
364 | inline bool Edge::isTop() const |
365 | { |
366 | return m_border == ElectricTop || m_border == ElectricTopLeft || m_border == ElectricTopRight; |
367 | } |
368 | |
369 | inline bool Edge::isCorner() const |
370 | { |
371 | return m_border == ElectricTopLeft |
372 | || m_border == ElectricTopRight |
373 | || m_border == ElectricBottomRight |
374 | || m_border == ElectricBottomLeft; |
375 | } |
376 | |
377 | inline bool Edge::isScreenEdge() const |
378 | { |
379 | return m_border == ElectricLeft |
380 | || m_border == ElectricRight |
381 | || m_border == ElectricTop |
382 | || m_border == ElectricBottom; |
383 | } |
384 | |
385 | inline bool Edge::isReserved() const |
386 | { |
387 | return m_reserved != 0; |
388 | } |
389 | |
390 | inline void Edge::setAction(ElectricBorderAction action) |
391 | { |
392 | m_action = action; |
393 | } |
394 | |
395 | inline void Edge::setBorder(ElectricBorder border) |
396 | { |
397 | m_border = border; |
398 | } |
399 | |
400 | inline ScreenEdges *Edge::edges() |
401 | { |
402 | return m_edges; |
403 | } |
404 | |
405 | inline const ScreenEdges *Edge::edges() const |
406 | { |
407 | return m_edges; |
408 | } |
409 | |
410 | inline const QRect &Edge::geometry() const |
411 | { |
412 | return m_geometry; |
413 | } |
414 | |
415 | inline const QRect &Edge::approachGeometry() const |
416 | { |
417 | return m_approachGeometry; |
418 | } |
419 | |
420 | inline ElectricBorder Edge::border() const |
421 | { |
422 | return m_border; |
423 | } |
424 | |
425 | inline const QHash< QObject *, QByteArray > &Edge::callBacks() const |
426 | { |
427 | return m_callBacks; |
428 | } |
429 | |
430 | inline bool Edge::isBlocked() const |
431 | { |
432 | return m_blocked; |
433 | } |
434 | |
435 | /********************************************************** |
436 | * Inlines WindowBasedEdge |
437 | *********************************************************/ |
438 | |
439 | inline xcb_window_t WindowBasedEdge::window() const |
440 | { |
441 | return m_window; |
442 | } |
443 | |
444 | inline xcb_window_t WindowBasedEdge::approachWindow() const |
445 | { |
446 | return m_approachWindow; |
447 | } |
448 | |
449 | /********************************************************** |
450 | * Inlines ScreenEdges |
451 | *********************************************************/ |
452 | inline void ScreenEdges::setConfig(KSharedConfig::Ptr config) |
453 | { |
454 | m_config = config; |
455 | } |
456 | |
457 | inline int ScreenEdges::cornerOffset() const { |
458 | return m_cornerOffset; |
459 | } |
460 | |
461 | inline const QSize &ScreenEdges::cursorPushBackDistance() const |
462 | { |
463 | return m_cursorPushBackDistance; |
464 | } |
465 | |
466 | inline bool ScreenEdges::isDesktopSwitching() const |
467 | { |
468 | return m_desktopSwitching; |
469 | } |
470 | |
471 | inline bool ScreenEdges::isDesktopSwitchingMovingClients() const |
472 | { |
473 | return m_desktopSwitchingMovingClients; |
474 | } |
475 | |
476 | inline int ScreenEdges::reActivationThreshold() const |
477 | { |
478 | return m_reactivateThreshold; |
479 | } |
480 | |
481 | inline int ScreenEdges::timeThreshold() const |
482 | { |
483 | return m_timeThreshold; |
484 | } |
485 | |
486 | inline void ScreenEdges::setCursorPushBackDistance(const QSize &distance) |
487 | { |
488 | m_cursorPushBackDistance = distance; |
489 | } |
490 | |
491 | inline 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 | |
500 | inline void ScreenEdges::setDesktopSwitchingMovingClients(bool enable) |
501 | { |
502 | m_desktopSwitchingMovingClients = enable; |
503 | } |
504 | |
505 | inline void ScreenEdges::setReActivationThreshold(int threshold) |
506 | { |
507 | m_reactivateThreshold = threshold; |
508 | } |
509 | |
510 | inline void ScreenEdges::setTimeThreshold(int threshold) |
511 | { |
512 | m_timeThreshold = threshold; |
513 | } |
514 | |
515 | #define ACTION( name ) \ |
516 | inline ElectricBorderAction ScreenEdges::name() const \ |
517 | { \ |
518 | return m_##name; \ |
519 | } |
520 | |
521 | ACTION(actionTopLeft) |
522 | ACTION(actionTop) |
523 | ACTION(actionTopRight) |
524 | ACTION(actionRight) |
525 | ACTION(actionBottomRight) |
526 | ACTION(actionBottom) |
527 | ACTION(actionBottomLeft) |
528 | ACTION(actionLeft) |
529 | |
530 | #undef ACTION |
531 | |
532 | } |
533 | #endif // KWIN_SCREENEDGE_H |
534 | |