File indexing completed on 2024-04-28 16:49:00

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2011 Arthur Arlt <a.arlt@stud.uni-heidelberg.de>
0006     SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
0007 
0008     Since the functionality provided in this class has been moved from
0009     class Workspace, it is not clear who exactly has written the code.
0010     The list below contains the copyright holders of the class Workspace.
0011 
0012     SPDX-FileCopyrightText: 1999, 2000 Matthias Ettrich <ettrich@kde.org>
0013     SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org>
0014     SPDX-FileCopyrightText: 2009 Lucas Murray <lmurray@undefinedfire.com>
0015 
0016     SPDX-License-Identifier: GPL-2.0-or-later
0017 */
0018 
0019 #pragma once
0020 // KWin
0021 #include "kwinglobals.h"
0022 // KDE includes
0023 #include <KSharedConfig>
0024 // Qt
0025 #include <QDateTime>
0026 #include <QObject>
0027 #include <QRect>
0028 #include <QVector>
0029 #include <memory>
0030 
0031 class QAction;
0032 class QMouseEvent;
0033 
0034 namespace KWin
0035 {
0036 
0037 class Window;
0038 class Output;
0039 class GestureRecognizer;
0040 class ScreenEdges;
0041 class SwipeGesture;
0042 
0043 class TouchCallback
0044 {
0045 public:
0046     using CallbackFunction = std::function<void(ElectricBorder border, const QPointF &, Output *output)>;
0047     explicit TouchCallback(QAction *touchUpAction, TouchCallback::CallbackFunction progressCallback);
0048     ~TouchCallback();
0049 
0050     QAction *touchUpAction() const;
0051     void progressCallback(ElectricBorder border, const QPointF &deltaProgress, Output *output) const;
0052     bool hasProgressCallback() const;
0053 
0054 private:
0055     QAction *m_touchUpAction = nullptr;
0056     TouchCallback::CallbackFunction m_progressCallback;
0057 };
0058 
0059 class KWIN_EXPORT Edge : public QObject
0060 {
0061     Q_OBJECT
0062 public:
0063     explicit Edge(ScreenEdges *parent);
0064     ~Edge() override;
0065     bool isLeft() const;
0066     bool isTop() const;
0067     bool isRight() const;
0068     bool isBottom() const;
0069     bool isCorner() const;
0070     bool isScreenEdge() const;
0071     bool triggersFor(const QPoint &cursorPos) const;
0072     bool check(const QPoint &cursorPos, const QDateTime &triggerTime, bool forceNoPushBack = false);
0073     void markAsTriggered(const QPoint &cursorPos, const QDateTime &triggerTime);
0074     bool isReserved() const;
0075     const QRect &approachGeometry() const;
0076 
0077     ElectricBorder border() const;
0078     void reserve(QObject *object, const char *slot);
0079     const QHash<QObject *, QByteArray> &callBacks() const;
0080     void reserveTouchCallBack(QAction *action, TouchCallback::CallbackFunction callback = nullptr);
0081     void unreserveTouchCallBack(QAction *action);
0082     void startApproaching();
0083     void stopApproaching();
0084     bool isApproaching() const;
0085     void setClient(Window *client);
0086     Window *client() const;
0087     void setOutput(Output *output);
0088     Output *output() const;
0089     const QRect &geometry() const;
0090     void setTouchAction(ElectricBorderAction action);
0091 
0092     bool activatesForPointer() const;
0093     bool activatesForTouchGesture() const;
0094 
0095     /**
0096      * The window id of the native window representing the edge.
0097      * Default implementation returns @c 0, which means no window.
0098      */
0099     virtual quint32 window() const;
0100     /**
0101      * The approach window is a special window to notice when get close to the screen border but
0102      * not yet triggering the border.
0103      *
0104      * The default implementation returns @c 0, which means no window.
0105      */
0106     virtual quint32 approachWindow() const;
0107 
0108 public Q_SLOTS:
0109     void reserve();
0110     void unreserve();
0111     void unreserve(QObject *object);
0112     void setBorder(ElectricBorder border);
0113     void setAction(ElectricBorderAction action);
0114     void setGeometry(const QRect &geometry);
0115     void updateApproaching(const QPoint &point);
0116     void checkBlocking();
0117 Q_SIGNALS:
0118     void approaching(ElectricBorder border, qreal factor, const QRect &geometry);
0119     void activatesForTouchGestureChanged();
0120 
0121 protected:
0122     ScreenEdges *edges();
0123     const ScreenEdges *edges() const;
0124     bool isBlocked() const;
0125     virtual void doGeometryUpdate();
0126     virtual void doActivate();
0127     virtual void doDeactivate();
0128     virtual void doStartApproaching();
0129     virtual void doStopApproaching();
0130     virtual void doUpdateBlocking();
0131 
0132 private:
0133     void activate();
0134     void deactivate();
0135     bool canActivate(const QPoint &cursorPos, const QDateTime &triggerTime);
0136     void handle(const QPoint &cursorPos);
0137     bool handleAction(ElectricBorderAction action);
0138     bool handlePointerAction()
0139     {
0140         return handleAction(m_action);
0141     }
0142     bool handleTouchAction()
0143     {
0144         return handleAction(m_touchAction);
0145     }
0146     bool handleByCallback();
0147     void handleTouchCallback();
0148     void switchDesktop(const QPoint &cursorPos);
0149     void pushCursorBack(const QPoint &cursorPos);
0150     void reserveTouchCallBack(const TouchCallback &callback);
0151     QVector<TouchCallback> touchCallBacks() const
0152     {
0153         return m_touchCallbacks;
0154     }
0155     ScreenEdges *m_edges;
0156     ElectricBorder m_border;
0157     ElectricBorderAction m_action;
0158     ElectricBorderAction m_touchAction = ElectricActionNone;
0159     int m_reserved;
0160     QRect m_geometry;
0161     QRect m_approachGeometry;
0162     QDateTime m_lastTrigger;
0163     QDateTime m_lastReset;
0164     QPoint m_triggeredPoint;
0165     QHash<QObject *, QByteArray> m_callBacks;
0166     bool m_approaching;
0167     int m_lastApproachingFactor;
0168     bool m_blocked;
0169     bool m_pushBackBlocked;
0170     Window *m_client;
0171     Output *m_output;
0172     std::unique_ptr<SwipeGesture> m_gesture;
0173     QVector<TouchCallback> m_touchCallbacks;
0174     friend class ScreenEdges;
0175 };
0176 
0177 /**
0178  * @short Class for controlling screen edges.
0179  *
0180  * The screen edge functionality is split into three parts:
0181  * @li This manager class ScreenEdges
0182  * @li abstract class @ref Edge
0183  * @li specific implementation of @ref Edge, e.g. WindowBasedEdge
0184  *
0185  * The ScreenEdges creates an @ref Edge for each screen edge which is also an edge in the
0186  * combination of all screens. E.g. if there are two screens, no Edge is created between the screens,
0187  * but at all other edges even if the screens have a different dimension.
0188  *
0189  * In addition at each corner of the overall display geometry an one-pixel large @ref Edge is
0190  * created. No matter how many screens there are, there will only be exactly four of these corner
0191  * edges. This is motivated by Fitts's Law which show that it's easy to trigger such a corner, but
0192  * it would be very difficult to trigger a corner between two screens (one pixel target not visually
0193  * outlined).
0194  *
0195  * The ScreenEdges are used for one of the following functionality:
0196  * @li switch virtual desktop (see property @ref desktopSwitching)
0197  * @li switch virtual desktop when moving a window (see property @ref desktopSwitchingMovingClients)
0198  * @li trigger a pre-defined action (see properties @ref actionTop and similar)
0199  * @li trigger an externally configured action (e.g. Effect, Script, see @ref reserve, @ref unreserve)
0200  *
0201  * An @ref Edge is only active if there is at least one of the possible actions "reserved" for this
0202  * edge. The idea is to not block the screen edge if nothing could be triggered there, so that the
0203  * user e.g. can configure nothing on the top edge, which tends to interfere with full screen apps
0204  * having a hidden panel there. On X11 (currently only supported backend) the @ref Edge is
0205  * represented by a WindowBasedEdge which creates an input only window for the geometry and
0206  * reacts on enter notify events. If the edge gets reserved for the first time a window is created
0207  * and mapped, once the edge gets unreserved again, the window gets destroyed.
0208  *
0209  * When the mouse enters one of the screen edges the following values are used to determine whether
0210  * the action should be triggered or the cursor be pushed back
0211  * @li Time difference between two entering events is not larger than a certain threshold
0212  * @li Time difference between two entering events is larger than @ref timeThreshold
0213  * @li Time difference between two activations is larger than @ref reActivateThreshold
0214  * @li Distance between two enter events is not larger than a defined pixel distance
0215  * These checks are performed in @ref Edge
0216  *
0217  * @todo change way how Effects/Scripts can reserve an edge and are notified.
0218  */
0219 class KWIN_EXPORT ScreenEdges : public QObject
0220 {
0221     Q_OBJECT
0222     Q_PROPERTY(bool desktopSwitching READ isDesktopSwitching)
0223     Q_PROPERTY(bool desktopSwitchingMovingClients READ isDesktopSwitchingMovingClients)
0224     Q_PROPERTY(QSize cursorPushBackDistance READ cursorPushBackDistance)
0225     Q_PROPERTY(int timeThreshold READ timeThreshold)
0226     Q_PROPERTY(int reActivateThreshold READ reActivationThreshold)
0227     Q_PROPERTY(int actionTopLeft READ actionTopLeft)
0228     Q_PROPERTY(int actionTop READ actionTop)
0229     Q_PROPERTY(int actionTopRight READ actionTopRight)
0230     Q_PROPERTY(int actionRight READ actionRight)
0231     Q_PROPERTY(int actionBottomRight READ actionBottomRight)
0232     Q_PROPERTY(int actionBottom READ actionBottom)
0233     Q_PROPERTY(int actionBottomLeft READ actionBottomLeft)
0234     Q_PROPERTY(int actionLeft READ actionLeft)
0235 public:
0236     explicit ScreenEdges();
0237     /**
0238      * @internal
0239      */
0240     void setConfig(KSharedConfig::Ptr config);
0241     /**
0242      * Initialize the screen edges.
0243      * @internal
0244      */
0245     void init();
0246     /**
0247      * Check, if a screen edge is entered and trigger the appropriate action
0248      * if one is enabled for the current region and the timeout is satisfied
0249      * @param pos the position of the mouse pointer
0250      * @param now the time when the function is called
0251      * @param forceNoPushBack needs to be called to workaround some DnD clients, don't use unless you want to chek on a DnD event
0252      */
0253     void check(const QPoint &pos, const QDateTime &now, bool forceNoPushBack = false);
0254     /**
0255      * The (dpi dependent) length, reserved for the active corners of each edge - 1/3"
0256      */
0257     int cornerOffset() const;
0258     /**
0259      * Mark the specified screen edge as reserved. This method is provided for external activation
0260      * like effects and scripts. When the effect/script does no longer need the edge it is supposed
0261      * to call @ref unreserve.
0262      * @param border the screen edge to mark as reserved
0263      * @param object The object on which the @p callback needs to be invoked
0264      * @param callback The method name to be invoked - uses QMetaObject::invokeMethod
0265      * @see unreserve
0266      * @todo: add pointer to script/effect
0267      */
0268     void reserve(ElectricBorder border, QObject *object, const char *callback);
0269     /**
0270      * Mark the specified screen edge as unreserved. This method is provided for external activation
0271      * like effects and scripts. This method is only allowed to be called if @ref reserve had been
0272      * called before for the same @p border. An unbalanced calling of reserve/unreserve leads to the
0273      * edge never being active or never being able to deactivate again.
0274      * @param border the screen edge to mark as unreserved
0275      * @param object the object on which the callback had been invoked
0276      * @see reserve
0277      * @todo: add pointer to script/effect
0278      */
0279     void unreserve(ElectricBorder border, QObject *object);
0280     /**
0281      * Reserves an edge for the @p client. The idea behind this is to show the @p client if the
0282      * screen edge which the @p client borders gets triggered.
0283      *
0284      * When first called it is tried to create an Edge for the client. This is only done if the
0285      * client borders with a screen edge specified by @p border. If the client doesn't border the
0286      * screen edge, no Edge gets created and the client is shown again. Otherwise there would not
0287      * be a possibility to show the client again.
0288      *
0289      * On subsequent calls for the client no new Edge is created, but the existing one gets reused
0290      * and if the client is already hidden, the Edge gets reserved.
0291      *
0292      * Once the Edge for the client triggers, the client gets shown again and the Edge unreserved.
0293      * The idea is that the Edge can only get activated if the client is currently hidden.
0294      *
0295      * To make sure that the client can always be shown again the implementation also starts to
0296      * track geometry changes and shows the Client again. The same for screen geometry changes.
0297      *
0298      * The Edge gets automatically destroyed if the client gets released.
0299      * @param client The Client for which an Edge should be reserved
0300      * @param border The border which the client wants to use, only proper borders are supported (no corners)
0301      */
0302     void reserve(KWin::Window *client, ElectricBorder border);
0303 
0304     /**
0305      * Mark the specified screen edge as reserved for touch gestures. This method is provided for
0306      * external activation like effects and scripts.
0307      * When the effect/script does no longer need the edge it is supposed
0308      * to call @ref unreserveTouch.
0309      * @param border the screen edge to mark as reserved
0310      * @param action The action which gets triggered
0311      * @see unreserveTouch
0312      * @since 5.10
0313      */
0314     void reserveTouch(ElectricBorder border, QAction *action, TouchCallback::CallbackFunction callback = nullptr);
0315     /**
0316      * Unreserves the specified @p border from activating the @p action for touch gestures.
0317      * @see reserveTouch
0318      * @since 5.10
0319      */
0320     void unreserveTouch(ElectricBorder border, QAction *action);
0321 
0322     /**
0323      * Reserve desktop switching for screen edges, if @p isToReserve is @c true. Unreserve otherwise.
0324      * @param isToReserve indicated whether desktop switching should be reserved or unreseved
0325      * @param o Qt orientations
0326      */
0327     void reserveDesktopSwitching(bool isToReserve, Qt::Orientations o);
0328     /**
0329      * Raise electric border windows to the real top of the screen. We only need
0330      * to do this if an effect input window is active.
0331      */
0332     void ensureOnTop();
0333     bool isEntered(QMouseEvent *event);
0334 
0335     /**
0336      * Returns a QVector of all existing screen edge windows
0337      * @return all existing screen edge windows in a QVector
0338      */
0339     QVector<xcb_window_t> windows() const;
0340 
0341     bool isDesktopSwitching() const;
0342     bool isDesktopSwitchingMovingClients() const;
0343     const QSize &cursorPushBackDistance() const;
0344     /**
0345      * Minimum time between the push back of the cursor and the activation by re-entering the edge.
0346      */
0347     int timeThreshold() const;
0348     /**
0349      * Minimum time between triggers
0350      */
0351     int reActivationThreshold() const;
0352     ElectricBorderAction actionTopLeft() const;
0353     ElectricBorderAction actionTop() const;
0354     ElectricBorderAction actionTopRight() const;
0355     ElectricBorderAction actionRight() const;
0356     ElectricBorderAction actionBottomRight() const;
0357     ElectricBorderAction actionBottom() const;
0358     ElectricBorderAction actionBottomLeft() const;
0359     ElectricBorderAction actionLeft() const;
0360 
0361     ElectricBorderAction actionForTouchBorder(ElectricBorder border) const;
0362 
0363     GestureRecognizer *gestureRecognizer() const
0364     {
0365         return m_gestureRecognizer;
0366     }
0367 
0368     bool handleDndNotify(xcb_window_t window, const QPoint &point);
0369     bool handleEnterNotifiy(xcb_window_t window, const QPoint &point, const QDateTime &timestamp);
0370     bool remainActiveOnFullscreen() const;
0371     const std::vector<std::unique_ptr<Edge>> &edges() const;
0372 
0373 public Q_SLOTS:
0374     void reconfigure();
0375     /**
0376      * Updates the layout of virtual desktops and adjust the reserved borders in case of
0377      * virtual desktop switching on edges.
0378      */
0379     void updateLayout();
0380     /**
0381      * Recreates all edges e.g. after the screen size changes.
0382      */
0383     void recreateEdges();
0384 
0385 Q_SIGNALS:
0386     /**
0387      * Signal emitted during approaching of mouse towards @p border. The @p factor indicates how
0388      * far away the mouse is from the approaching area. The values are clamped into [0.0,1.0] with
0389      * @c 0.0 meaning far away from the border, @c 1.0 in trigger distance.
0390      */
0391     void approaching(ElectricBorder border, qreal factor, const QRect &geometry);
0392     void checkBlocking();
0393 
0394 private:
0395     enum {
0396         ElectricDisabled = 0,
0397         ElectricMoveOnly = 1,
0398         ElectricAlways = 2,
0399     };
0400     void setDesktopSwitching(bool enable);
0401     void setDesktopSwitchingMovingClients(bool enable);
0402     void setCursorPushBackDistance(const QSize &distance);
0403     void setTimeThreshold(int threshold);
0404     void setReActivationThreshold(int threshold);
0405     void createHorizontalEdge(ElectricBorder border, const QRect &screen, const QRect &fullArea, Output *output);
0406     void createVerticalEdge(ElectricBorder border, const QRect &screen, const QRect &fullArea, Output *output);
0407     std::unique_ptr<Edge> createEdge(ElectricBorder border, int x, int y, int width, int height, Output *output, bool createAction = true);
0408     void setActionForBorder(ElectricBorder border, ElectricBorderAction *oldValue, ElectricBorderAction newValue);
0409     void setActionForTouchBorder(ElectricBorder border, ElectricBorderAction newValue);
0410     void setRemainActiveOnFullscreen(bool remainActive);
0411     ElectricBorderAction actionForEdge(Edge *edge) const;
0412     ElectricBorderAction actionForTouchEdge(Edge *edge) const;
0413     void createEdgeForClient(Window *client, ElectricBorder border);
0414     void deleteEdgeForClient(Window *client);
0415     bool m_desktopSwitching;
0416     bool m_desktopSwitchingMovingClients;
0417     QSize m_cursorPushBackDistance;
0418     int m_timeThreshold;
0419     int m_reactivateThreshold;
0420     Qt::Orientations m_virtualDesktopLayout;
0421     std::vector<std::unique_ptr<Edge>> m_edges;
0422     KSharedConfig::Ptr m_config;
0423     ElectricBorderAction m_actionTopLeft;
0424     ElectricBorderAction m_actionTop;
0425     ElectricBorderAction m_actionTopRight;
0426     ElectricBorderAction m_actionRight;
0427     ElectricBorderAction m_actionBottomRight;
0428     ElectricBorderAction m_actionBottom;
0429     ElectricBorderAction m_actionBottomLeft;
0430     ElectricBorderAction m_actionLeft;
0431     QMap<ElectricBorder, ElectricBorderAction> m_touchCallbacks;
0432     int m_cornerOffset;
0433     GestureRecognizer *m_gestureRecognizer;
0434     bool m_remainActiveOnFullscreen = false;
0435 };
0436 
0437 /**********************************************************
0438  * Inlines Edge
0439  *********************************************************/
0440 
0441 inline bool Edge::isBottom() const
0442 {
0443     return m_border == ElectricBottom || m_border == ElectricBottomLeft || m_border == ElectricBottomRight;
0444 }
0445 
0446 inline bool Edge::isLeft() const
0447 {
0448     return m_border == ElectricLeft || m_border == ElectricTopLeft || m_border == ElectricBottomLeft;
0449 }
0450 
0451 inline bool Edge::isRight() const
0452 {
0453     return m_border == ElectricRight || m_border == ElectricTopRight || m_border == ElectricBottomRight;
0454 }
0455 
0456 inline bool Edge::isTop() const
0457 {
0458     return m_border == ElectricTop || m_border == ElectricTopLeft || m_border == ElectricTopRight;
0459 }
0460 
0461 inline bool Edge::isCorner() const
0462 {
0463     return m_border == ElectricTopLeft
0464         || m_border == ElectricTopRight
0465         || m_border == ElectricBottomRight
0466         || m_border == ElectricBottomLeft;
0467 }
0468 
0469 inline bool Edge::isScreenEdge() const
0470 {
0471     return m_border == ElectricLeft
0472         || m_border == ElectricRight
0473         || m_border == ElectricTop
0474         || m_border == ElectricBottom;
0475 }
0476 
0477 inline bool Edge::isReserved() const
0478 {
0479     return m_reserved != 0;
0480 }
0481 
0482 inline void Edge::setAction(ElectricBorderAction action)
0483 {
0484     m_action = action;
0485 }
0486 
0487 inline ScreenEdges *Edge::edges()
0488 {
0489     return m_edges;
0490 }
0491 
0492 inline const ScreenEdges *Edge::edges() const
0493 {
0494     return m_edges;
0495 }
0496 
0497 inline const QRect &Edge::geometry() const
0498 {
0499     return m_geometry;
0500 }
0501 
0502 inline const QRect &Edge::approachGeometry() const
0503 {
0504     return m_approachGeometry;
0505 }
0506 
0507 inline ElectricBorder Edge::border() const
0508 {
0509     return m_border;
0510 }
0511 
0512 inline const QHash<QObject *, QByteArray> &Edge::callBacks() const
0513 {
0514     return m_callBacks;
0515 }
0516 
0517 inline bool Edge::isBlocked() const
0518 {
0519     return m_blocked;
0520 }
0521 
0522 inline Window *Edge::client() const
0523 {
0524     return m_client;
0525 }
0526 
0527 inline bool Edge::isApproaching() const
0528 {
0529     return m_approaching;
0530 }
0531 
0532 /**********************************************************
0533  * Inlines ScreenEdges
0534  *********************************************************/
0535 inline void ScreenEdges::setConfig(KSharedConfig::Ptr config)
0536 {
0537     m_config = config;
0538 }
0539 
0540 inline int ScreenEdges::cornerOffset() const
0541 {
0542     return m_cornerOffset;
0543 }
0544 
0545 inline const QSize &ScreenEdges::cursorPushBackDistance() const
0546 {
0547     return m_cursorPushBackDistance;
0548 }
0549 
0550 inline bool ScreenEdges::isDesktopSwitching() const
0551 {
0552     return m_desktopSwitching;
0553 }
0554 
0555 inline bool ScreenEdges::isDesktopSwitchingMovingClients() const
0556 {
0557     return m_desktopSwitchingMovingClients;
0558 }
0559 
0560 inline int ScreenEdges::reActivationThreshold() const
0561 {
0562     return m_reactivateThreshold;
0563 }
0564 
0565 inline int ScreenEdges::timeThreshold() const
0566 {
0567     return m_timeThreshold;
0568 }
0569 
0570 inline void ScreenEdges::setCursorPushBackDistance(const QSize &distance)
0571 {
0572     m_cursorPushBackDistance = distance;
0573 }
0574 
0575 inline void ScreenEdges::setDesktopSwitching(bool enable)
0576 {
0577     if (enable == m_desktopSwitching) {
0578         return;
0579     }
0580     m_desktopSwitching = enable;
0581     reserveDesktopSwitching(enable, m_virtualDesktopLayout);
0582 }
0583 
0584 inline void ScreenEdges::setDesktopSwitchingMovingClients(bool enable)
0585 {
0586     m_desktopSwitchingMovingClients = enable;
0587 }
0588 
0589 inline void ScreenEdges::setReActivationThreshold(int threshold)
0590 {
0591     Q_ASSERT(threshold >= m_timeThreshold);
0592     m_reactivateThreshold = threshold;
0593 }
0594 
0595 inline void ScreenEdges::setTimeThreshold(int threshold)
0596 {
0597     m_timeThreshold = threshold;
0598 }
0599 
0600 #define ACTION(name)                                      \
0601     inline ElectricBorderAction ScreenEdges::name() const \
0602     {                                                     \
0603         return m_##name;                                  \
0604     }
0605 
0606 ACTION(actionTopLeft)
0607 ACTION(actionTop)
0608 ACTION(actionTopRight)
0609 ACTION(actionRight)
0610 ACTION(actionBottomRight)
0611 ACTION(actionBottom)
0612 ACTION(actionBottomLeft)
0613 ACTION(actionLeft)
0614 
0615 #undef ACTION
0616 
0617 }