File indexing completed on 2024-04-28 05:30:30

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