/* KWin - the KDE window manager This file is part of the KDE project. SPDX-FileCopyrightText: 1999, 2000 Matthias Ettrich SPDX-FileCopyrightText: 2003 Lubos Lunak SPDX-FileCopyrightText: 2009 Lucas Murray SPDX-FileCopyrightText: 2019 Vlad Zahorodnii SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef KWIN_WORKSPACE_H #define KWIN_WORKSPACE_H // kwin #include "options.h" #include "sm.h" #include "utils/common.h" // Qt #include #include #include // std #include #include class KConfig; class KConfigGroup; class KStartupInfo; class KStartupInfoData; class KStartupInfoId; namespace KWin { namespace Xcb { class Tree; class Window; } class AbstractClient; class AbstractOutput; class ColorMapper; class Compositor; class Deleted; class Group; class InternalClient; class KillWindow; class ShortcutDialog; class Toplevel; class Unmanaged; class UserActionsMenu; class VirtualDesktop; class X11Client; class X11EventFilter; enum class Predicate; class KWIN_EXPORT Workspace : public QObject { Q_OBJECT public: explicit Workspace(); ~Workspace() override; static Workspace *self() { return _self; } bool workspaceEvent(xcb_generic_event_t *); bool workspaceEvent(QEvent *); bool hasClient(const AbstractClient *); /** * @brief Finds the first Client matching the condition expressed by passed in @p func. * * Internally findClient uses the std::find_if algorithm and that determines how the function * needs to be implemented. An example usage for finding a Client with a matching windowId * @code * xcb_window_t w; // our test window * X11Client *client = findClient([w](const X11Client *c) -> bool { * return c->window() == w; * }); * @endcode * * For the standard cases of matching the window id with one of the Client's windows use * the simplified overload method findClient(Predicate, xcb_window_t). Above example * can be simplified to: * @code * xcb_window_t w; // our test window * X11Client *client = findClient(Predicate::WindowMatch, w); * @endcode * * @param func Unary function that accepts a X11Client *as argument and * returns a value convertible to bool. The value returned indicates whether the * X11Client *is considered a match in the context of this function. * The function shall not modify its argument. * This can either be a function pointer or a function object. * @return KWin::X11Client *The found Client or @c null * @see findClient(Predicate, xcb_window_t) */ X11Client *findClient(std::function func) const; AbstractClient *findAbstractClient(std::function func) const; AbstractClient *findAbstractClient(const QUuid &internalId) const; /** * @brief Finds the Client matching the given match @p predicate for the given window. * * @param predicate Which window should be compared * @param w The window id to test against * @return KWin::X11Client *The found Client or @c null * @see findClient(std::function) */ X11Client *findClient(Predicate predicate, xcb_window_t w) const; void forEachClient(std::function func); void forEachAbstractClient(std::function func); Unmanaged *findUnmanaged(std::function func) const; /** * @brief Finds the Unmanaged with the given window id. * * @param w The window id to search for * @return KWin::Unmanaged* Found Unmanaged or @c null if there is no Unmanaged with given Id. */ Unmanaged *findUnmanaged(xcb_window_t w) const; void forEachUnmanaged(std::function func); Toplevel *findToplevel(std::function func) const; void forEachToplevel(std::function func); Toplevel *findToplevel(const QUuid &internalId) const; /** * @brief Finds a Toplevel for the internal window @p w. * * Internal window means a window created by KWin itself. On X11 this is an Unmanaged * and mapped by the window id, on Wayland a XdgShellClient mapped on the internal window id. * * @returns Toplevel */ Toplevel *findInternal(QWindow *w) const; QRect clientArea(clientAreaOption, const AbstractOutput *output, const VirtualDesktop *desktop) const; QRect clientArea(clientAreaOption, const Toplevel *window) const; QRect clientArea(clientAreaOption, const Toplevel *window, const AbstractOutput *output) const; QRect clientArea(clientAreaOption, const Toplevel *window, const QPoint &pos) const; /** * Returns the geometry of this Workspace, i.e. the bounding rectangle of all outputs. */ QRect geometry() const; QRegion restrictedMoveArea(const VirtualDesktop *desktop, StrutAreas areas = StrutAreaAll) const; bool initializing() const; AbstractOutput *activeOutput() const; void setActiveOutput(AbstractOutput *output); void setActiveOutput(const QPoint &pos); /** * Returns the active client, i.e. the client that has the focus (or None * if no client has the focus) */ AbstractClient *activeClient() const; /** * Client that was activated, but it's not yet really activeClient(), because * we didn't process yet the matching FocusIn event. Used mostly in focus * stealing prevention code. */ AbstractClient *mostRecentlyActivatedClient() const; AbstractClient *clientUnderMouse(AbstractOutput *output) const; void activateClient(AbstractClient *, bool force = false); bool requestFocus(AbstractClient *c, bool force = false); enum ActivityFlag { ActivityFocus = 1 << 0, // focus the window ActivityFocusForce = 1 << 1 | ActivityFocus, // focus even if Dock etc. ActivityRaise = 1 << 2 // raise the window }; Q_DECLARE_FLAGS(ActivityFlags, ActivityFlag) bool takeActivity(AbstractClient *c, ActivityFlags flags); bool allowClientActivation(const AbstractClient *c, xcb_timestamp_t time = -1U, bool focus_in = false, bool ignore_desktop = false); bool restoreFocus(); void gotFocusIn(const AbstractClient *); void setShouldGetFocus(AbstractClient *); bool activateNextClient(AbstractClient *c); bool focusChangeEnabled() { return block_focus == 0; } /** * Indicates that the client c is being moved or resized by the user. */ void setMoveResizeClient(AbstractClient *c); QRect adjustClientArea(AbstractClient *client, const QRect &area) const; QPoint adjustClientPosition(AbstractClient *c, QPoint pos, bool unrestricted, double snapAdjust = 1.0); QRect adjustClientSize(AbstractClient *c, QRect moveResizeGeom, Gravity gravity); void raiseClient(AbstractClient *c, bool nogroup = false); void lowerClient(AbstractClient *c, bool nogroup = false); void raiseClientRequest(AbstractClient *c, NET::RequestSource src = NET::FromApplication, xcb_timestamp_t timestamp = 0); void lowerClientRequest(X11Client *c, NET::RequestSource src, xcb_timestamp_t timestamp); void lowerClientRequest(AbstractClient *c); void restackClientUnderActive(AbstractClient *); void restack(AbstractClient *c, AbstractClient *under, bool force = false); void raiseOrLowerClient(AbstractClient *); void resetUpdateToolWindowsTimer(); void restoreSessionStackingOrder(X11Client *c); void updateStackingOrder(bool propagate_new_clients = false); void forceRestacking(); void constrain(AbstractClient *below, AbstractClient *above); void unconstrain(AbstractClient *below, AbstractClient *above); void clientHidden(AbstractClient *); void clientAttentionChanged(AbstractClient *c, bool set); /** * @return List of clients currently managed by Workspace */ const QList &clientList() const { return m_x11Clients; } /** * @return List of unmanaged "clients" currently registered in Workspace */ const QList &unmanagedList() const { return m_unmanaged; } /** * @return List of deleted "clients" currently managed by Workspace */ const QList &deletedList() const { return deleted; } /** * @returns List of all clients (either X11 or Wayland) currently managed by Workspace */ const QList allClientList() const { return m_allClients; } /** * @returns List of all internal clients currently managed by Workspace */ const QList &internalClients() const { return m_internalClients; } void stackScreenEdgesUnderOverrideRedirect(); SessionManager *sessionManager() const; public: QPoint cascadeOffset(const AbstractClient *c) const; private: QTimer *m_quickTileCombineTimer; QuickTileMode m_lastTilingMode; //------------------------------------------------- // Unsorted public: bool isOnCurrentHead(); // True when performing Workspace::updateClientArea(). // The calls below are valid only in that case. bool inUpdateClientArea() const; QRegion previousRestrictedMoveArea(const VirtualDesktop *desktop, StrutAreas areas = StrutAreaAll) const; QHash previousScreenSizes() const; int oldDisplayWidth() const; int oldDisplayHeight() const; /** * Returns the list of clients sorted in stacking order, with topmost client * at the last position */ const QList &stackingOrder() const; QList xStackingOrder() const; QList unconstrainedStackingOrder() const; QList ensureStackingOrder(const QList &clients) const; QList ensureStackingOrder(const QList &clients) const; AbstractClient *topClientOnDesktop(VirtualDesktop *desktop, AbstractOutput *output = nullptr, bool unconstrained = false, bool only_normal = true) const; AbstractClient *findDesktop(bool topmost, VirtualDesktop *desktop) const; void sendClientToDesktop(AbstractClient *c, int desktop, bool dont_activate); void windowToPreviousDesktop(AbstractClient *c); void windowToNextDesktop(AbstractClient *c); void sendClientToOutput(AbstractClient *client, AbstractOutput *output); void addManualOverlay(xcb_window_t id) { manual_overlays << id; } void removeManualOverlay(xcb_window_t id) { manual_overlays.removeOne(id); } /** * Shows the menu operations menu for the client and makes it active if * it's not already. */ void showWindowMenu(const QRect &pos, AbstractClient *cl); const UserActionsMenu *userActionsMenu() const { return m_userActionsMenu; } void showApplicationMenu(const QRect &pos, AbstractClient *c, int actionId); void updateMinimizedOfTransients(AbstractClient *); void updateOnAllDesktopsOfTransients(AbstractClient *); void checkTransients(xcb_window_t w); SessionInfo *takeSessionInfo(X11Client *); // D-Bus interface QString supportInformation() const; AbstractOutput *nextOutput(AbstractOutput *reference) const; AbstractOutput *previousOutput(AbstractOutput *reference) const; void switchToOutput(AbstractOutput *output); /** * Set "Show Desktop" status * * @param showing @c true to show the desktop, @c false to restore the window positions * @param animated @c true if the "Show Desktop Animation" should be played, otherwise @c false */ void setShowingDesktop(bool showing, bool animated = true); bool showingDesktop() const; void removeX11Client(X11Client *); // Only called from X11Client::destroyClient() or X11Client::releaseWindow() void setActiveClient(AbstractClient *); Group *findGroup(xcb_window_t leader) const; void addGroup(Group *group); void removeGroup(Group *group); Group *findClientLeaderGroup(const X11Client *c) const; int unconstainedStackingOrderIndex(const X11Client *c) const; void removeUnmanaged(Unmanaged *); // Only called from Unmanaged::release() void removeDeleted(Deleted *); void addDeleted(Deleted *, Toplevel *); bool checkStartupNotification(xcb_window_t w, KStartupInfoId &id, KStartupInfoData &data); void focusToNull(); // SELI TODO: Public? void clientShortcutUpdated(AbstractClient *c); bool shortcutAvailable(const QKeySequence &cut, AbstractClient *ignore = nullptr) const; bool globalShortcutsDisabled() const; void disableGlobalShortcutsForClient(bool disable); void setWasUserInteraction(); bool wasUserInteraction() const; int packPositionLeft(const AbstractClient *client, int oldX, bool leftEdge) const; int packPositionRight(const AbstractClient *client, int oldX, bool rightEdge) const; int packPositionUp(const AbstractClient *client, int oldY, bool topEdge) const; int packPositionDown(const AbstractClient *client, int oldY, bool bottomEdge) const; void cancelDelayFocus(); void requestDelayFocus(AbstractClient *); /** * updates the mouse position to track whether a focus follow mouse focus change was caused by * an actual mouse move * is esp. called on enter/motion events of inactive windows * since an active window doesn't receive mouse events, it must also be invoked if a (potentially) * active window might be moved/resize away from the cursor (causing a leave event) */ void updateFocusMousePosition(const QPoint &pos); QPoint focusMousePosition() const; /** * Returns a client that is currently being moved or resized by the user. * * If none of clients is being moved or resized, @c null will be returned. */ AbstractClient *moveResizeClient() { return movingClient; } void markXStackingOrderAsDirty(); void quickTileWindow(QuickTileMode mode); enum Direction { DirectionNorth, DirectionEast, DirectionSouth, DirectionWest }; void switchWindow(Direction direction); ShortcutDialog *shortcutDialog() const { return client_keys_dialog; } /** * Adds the internal client to Workspace. * * This method will be called by InternalClient when it's mapped. * * @see internalClientAdded * @internal */ void addInternalClient(InternalClient *client); /** * Removes the internal client from Workspace. * * This method is meant to be called only by InternalClient. * * @see internalClientRemoved * @internal */ void removeInternalClient(InternalClient *client); /** * @internal * Used by session management */ void setInitialDesktop(int desktop); public Q_SLOTS: void performWindowOperation(KWin::AbstractClient *c, Options::WindowOperation op); // Keybindings // void slotSwitchToWindow( int ); void slotWindowToDesktop(VirtualDesktop *desktop); // void slotWindowToListPosition( int ); void slotSwitchToScreen(); void slotWindowToScreen(); void slotSwitchToNextScreen(); void slotWindowToNextScreen(); void slotSwitchToPrevScreen(); void slotWindowToPrevScreen(); void slotToggleShowDesktop(); void slotWindowMaximize(); void slotWindowMaximizeVertical(); void slotWindowMaximizeHorizontal(); void slotWindowMinimize(); void slotWindowShade(); void slotWindowRaise(); void slotWindowLower(); void slotWindowRaiseOrLower(); void slotActivateAttentionWindow(); void slotWindowCenter(); void slotWindowMoveLeft(); void slotWindowMoveRight(); void slotWindowMoveUp(); void slotWindowMoveDown(); void slotWindowExpandHorizontal(); void slotWindowExpandVertical(); void slotWindowShrinkHorizontal(); void slotWindowShrinkVertical(); void slotIncreaseWindowOpacity(); void slotLowerWindowOpacity(); void slotWindowOperations(); void slotWindowClose(); void slotWindowMove(); void slotWindowResize(); void slotWindowAbove(); void slotWindowBelow(); void slotWindowOnAllDesktops(); void slotWindowFullScreen(); void slotWindowNoBorder(); void slotWindowToNextDesktop(); void slotWindowToPreviousDesktop(); void slotWindowToDesktopRight(); void slotWindowToDesktopLeft(); void slotWindowToDesktopUp(); void slotWindowToDesktopDown(); void reconfigure(); void slotReconfigure(); void slotKillWindow(); void slotSetupWindowShortcut(); void setupWindowShortcutDone(bool); void updateClientArea(); private Q_SLOTS: void desktopResized(); void selectWmInputEventMask(); void slotUpdateToolWindows(); void delayFocus(); void slotReloadConfig(); void updateCurrentActivity(const QString &new_activity); // virtual desktop handling void slotCurrentDesktopChanged(uint oldDesktop, uint newDesktop); void slotCurrentDesktopChanging(uint currentDesktop, QPointF delta); void slotCurrentDesktopChangingCancelled(); void slotDesktopAdded(VirtualDesktop *desktop); void slotDesktopRemoved(VirtualDesktop *desktop); void slotOutputEnabled(AbstractOutput *output); void slotOutputDisabled(AbstractOutput *output); Q_SIGNALS: /** * Emitted after the Workspace has setup the complete initialization process. * This can be used to connect to for performing post-workspace initialization. */ void workspaceInitialized(); void geometryChanged(); // Signals required for the scripting interface void desktopPresenceChanged(KWin::AbstractClient *, int); void currentActivityChanged(); void currentDesktopChanged(int, KWin::AbstractClient *); void currentDesktopChanging(uint currentDesktop, QPointF delta, KWin::AbstractClient*);//for realtime animations void currentDesktopChangingCancelled(); void clientAdded(KWin::AbstractClient *); void clientRemoved(KWin::AbstractClient *); void clientActivated(KWin::AbstractClient *); void clientDemandsAttentionChanged(KWin::AbstractClient *, bool); void clientMinimizedChanged(KWin::AbstractClient *); void groupAdded(KWin::Group *); void unmanagedAdded(KWin::Unmanaged *); void unmanagedRemoved(KWin::Unmanaged *); void deletedRemoved(KWin::Deleted *); void configChanged(); void showingDesktopChanged(bool showing, bool animated); /** * This signal is emitted when the stacking order changed, i.e. a window is risen * or lowered */ void stackingOrderChanged(); /** * This signal is emitted whenever an internal client is created. */ void internalClientAdded(KWin::InternalClient *client); /** * This signal is emitted whenever an internal client gets removed. */ void internalClientRemoved(KWin::InternalClient *client); private: void init(); void initializeX11(); void cleanupX11(); void initShortcuts(); template void initShortcut(const QString &actionName, const QString &description, const QKeySequence &shortcut, Slot slot, const QVariant &data = QVariant()); template void initShortcut(const QString &actionName, const QString &description, const QKeySequence &shortcut, T *receiver, Slot slot, const QVariant &data = QVariant()); void setupWindowShortcut(AbstractClient *c); bool switchWindow(AbstractClient *c, Direction direction, QPoint curPos, VirtualDesktop *desktop); void propagateClients(bool propagate_new_clients); // Called only from updateStackingOrder QList constrainedStackingOrder(); void raiseClientWithinApplication(AbstractClient *c); void lowerClientWithinApplication(AbstractClient *c); bool allowFullClientRaising(const AbstractClient *c, xcb_timestamp_t timestamp); void blockStackingUpdates(bool block); void updateToolWindows(bool also_hide); void fixPositionAfterCrash(xcb_window_t w, const xcb_get_geometry_reply_t *geom); void saveOldScreenSizes(); void addToStack(Toplevel *toplevel); void replaceInStack(Toplevel *original, Deleted *deleted); void removeFromStack(Toplevel *toplevel); /// This is the right way to create a new client X11Client *createClient(xcb_window_t w, bool is_mapped); void setupClientConnections(AbstractClient *client); void addClient(X11Client *c); Unmanaged *createUnmanaged(xcb_window_t w); void addUnmanaged(Unmanaged *c); void addShellClient(AbstractClient *client); void removeShellClient(AbstractClient *client); //--------------------------------------------------------------------- void closeActivePopup(); void updateClientVisibilityOnDesktopChange(VirtualDesktop *newDesktop); void activateClientOnNewDesktop(VirtualDesktop *desktop); AbstractClient *findClientToActivateOnDesktop(VirtualDesktop *desktop); void removeAbstractClient(AbstractClient *client); struct Constraint { Toplevel *below; Toplevel *above; // All constraints above our "below" window QList parents; // All constraints below our "above" window QList children; // Used to prevent cycles. bool enqueued = false; }; QList m_constraints; QWidget *active_popup; AbstractClient *active_popup_client; int m_initialDesktop; void updateXStackingOrder(); void updateTabbox(); AbstractOutput *m_activeOutput = nullptr; AbstractClient *active_client; AbstractClient *last_active_client; AbstractClient *movingClient; // Delay(ed) window focus timer and client QTimer *delayFocusTimer; AbstractClient *delayfocus_client; QPoint focusMousePos; QList m_x11Clients; QList m_allClients; QList m_unmanaged; QList deleted; QList m_internalClients; QList unconstrained_stacking_order; // Topmost last QList stacking_order; // Topmost last QVector manual_overlays; // Topmost last bool force_restacking; QList x_stacking; // From XQueryTree() std::unique_ptr m_xStackingQueryTree; bool m_xStackingDirty = false; QList should_get_focus; // Last is most recent QList attention_chain; bool showing_desktop; QList groups; bool was_user_interaction; QScopedPointer m_wasUserInteractionFilter; int block_focus; /** * Holds the menu containing the user actions which is shown * on e.g. right click the window decoration. */ UserActionsMenu *m_userActionsMenu; void modalActionsSwitch(bool enabled); ShortcutDialog *client_keys_dialog; AbstractClient *client_keys_client; bool global_shortcuts_disabled_for_client; // Timer to collect requests for 'reconfigure' QTimer reconfigureTimer; QTimer updateToolWindowsTimer; static Workspace *_self; bool workspaceInit; QScopedPointer m_startup; QScopedPointer m_colorMapper; QHash m_workAreas; QHash m_restrictedAreas; QHash> m_screenAreas; QRect m_geometry; QHash m_oldScreenGeometries; QSize olddisplaysize; // previous sizes od displayWidth()/displayHeight() QHash m_oldRestrictedAreas; bool m_inUpdateClientArea = false; int set_active_client_recursion; int block_stacking_updates; // When > 0, stacking updates are temporarily disabled bool blocked_propagating_new_clients; // Propagate also new clients after enabling stacking updates? QScopedPointer m_nullFocus; friend class StackingUpdatesBlocker; QScopedPointer m_windowKiller; QScopedPointer m_movingClientFilter; QScopedPointer m_syncAlarmFilter; SessionManager *m_sessionManager; private: friend bool performTransiencyCheck(); friend Workspace *workspace(); }; /** * Helper for Workspace::blockStackingUpdates() being called in pairs (True/false) */ class StackingUpdatesBlocker { public: explicit StackingUpdatesBlocker(Workspace *w) : ws(w) { ws->blockStackingUpdates(true); } ~StackingUpdatesBlocker() { ws->blockStackingUpdates(false); } private: Workspace *ws; }; class ColorMapper : public QObject { Q_OBJECT public: ColorMapper(QObject *parent); ~ColorMapper() override; public Q_SLOTS: void update(); private: xcb_colormap_t m_default; xcb_colormap_t m_installed; }; //--------------------------------------------------------- // Unsorted inline bool Workspace::initializing() const { return workspaceInit; } inline AbstractClient *Workspace::activeClient() const { return active_client; } inline AbstractClient *Workspace::mostRecentlyActivatedClient() const { return should_get_focus.count() > 0 ? should_get_focus.last() : active_client; } inline void Workspace::addGroup(Group *group) { Q_EMIT groupAdded(group); groups.append(group); } inline void Workspace::removeGroup(Group *group) { groups.removeAll(group); } inline const QList &Workspace::stackingOrder() const { // TODO: Q_ASSERT( block_stacking_updates == 0 ); return stacking_order; } inline bool Workspace::wasUserInteraction() const { return was_user_interaction; } inline SessionManager *Workspace::sessionManager() const { return m_sessionManager; } inline bool Workspace::showingDesktop() const { return showing_desktop; } inline bool Workspace::globalShortcutsDisabled() const { return global_shortcuts_disabled_for_client; } inline void Workspace::forceRestacking() { force_restacking = true; StackingUpdatesBlocker blocker(this); // Do restacking if not blocked } inline void Workspace::updateFocusMousePosition(const QPoint &pos) { focusMousePos = pos; } inline QPoint Workspace::focusMousePosition() const { return focusMousePos; } inline void Workspace::forEachClient(std::function func) { std::for_each(m_x11Clients.constBegin(), m_x11Clients.constEnd(), func); } inline void Workspace::forEachUnmanaged(std::function func) { std::for_each(m_unmanaged.constBegin(), m_unmanaged.constEnd(), func); } inline Workspace *workspace() { return Workspace::_self; } } // namespace Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::Workspace::ActivityFlags) #endif