diff --git a/client.h b/client.h index 063a2d2dd..1c4f6a973 100644 --- a/client.h +++ b/client.h @@ -1,759 +1,756 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_CLIENT_H #define KWIN_CLIENT_H // kwin #include "options.h" #include "rules.h" #include "tabgroup.h" #include "abstract_client.h" #include "xcbutils.h" // Qt #include #include #include #include #include // X #include // TODO: Cleanup the order of things in this .h file class QTimer; class KStartupInfoData; class KStartupInfoId; struct xcb_sync_alarm_notify_event_t; namespace KWin { /** * @brief Defines Predicates on how to search for a Client. * * Used by Workspace::findClient. */ enum class Predicate { WindowMatch, WrapperIdMatch, FrameIdMatch, InputIdMatch }; class KWIN_EXPORT Client : public AbstractClient { Q_OBJECT /** * By how much the window wishes to grow/shrink at least. Usually QSize(1,1). * MAY BE DISOBEYED BY THE WM! It's only for information, do NOT rely on it at all. * The value is evaluated each time the getter is called. * Because of that no changed signal is provided. */ Q_PROPERTY(QSize basicUnit READ basicUnit) /** * The "Window Tabs" Group this Client belongs to. **/ Q_PROPERTY(KWin::TabGroup* tabGroup READ tabGroup NOTIFY tabGroupChanged SCRIPTABLE false) /** * A client can block compositing. That is while the Client is alive and the state is set, * Compositing is suspended and is resumed when there are no Clients blocking compositing any * more. * * This is actually set by a window property, unfortunately not used by the target application * group. For convenience it's exported as a property to the scripts. * * Use with care! **/ Q_PROPERTY(bool blocksCompositing READ isBlockingCompositing WRITE setBlockingCompositing NOTIFY blockingCompositingChanged) /** * Whether the Client uses client side window decorations. * Only GTK+ are detected. **/ Q_PROPERTY(bool clientSideDecorated READ isClientSideDecorated NOTIFY clientSideDecoratedChanged) public: explicit Client(); xcb_window_t wrapperId() const; xcb_window_t inputId() const { return m_decoInputExtent; } virtual xcb_window_t frameId() const override; bool isTransient() const override; bool groupTransient() const; bool wasOriginallyGroupTransient() const; QList mainClients() const override; // Call once before loop , is not indirect bool hasTransient(const AbstractClient* c, bool indirect) const override; void checkTransient(xcb_window_t w); AbstractClient* findModal(bool allow_itself = false) override; const Group* group() const; Group* group(); void checkGroup(Group* gr = NULL, bool force = false); void changeClientLeaderGroup(Group* gr); const WindowRules* rules() const override; void removeRule(Rules* r); void setupWindowRules(bool ignore_temporary); void applyWindowRules(); void updateWindowRules(Rules::Types selection) override; void updateFullscreenMonitors(NETFullscreenMonitors topology); bool hasNETSupport() const; QSize minSize() const override; QSize maxSize() const override; QSize basicUnit() const; virtual QSize clientSize() const; QPoint inputPos() const { return input_offset; } // Inside of geometry() bool windowEvent(xcb_generic_event_t *e); void syncEvent(xcb_sync_alarm_notify_event_t* e); NET::WindowType windowType(bool direct = false, int supported_types = 0) const; bool manage(xcb_window_t w, bool isMapped); void releaseWindow(bool on_shutdown = false); void destroyClient(); virtual QStringList activities() const; void setOnActivity(const QString &activity, bool enable); void setOnAllActivities(bool set) override; void setOnActivities(QStringList newActivitiesList); void updateActivities(bool includeTransients); void blockActivityUpdates(bool b = true) override; /// Is not minimized and not hidden. I.e. normally visible on some virtual desktop. bool isShown(bool shaded_is_shown) const override; bool isHiddenInternal() const override; // For compositing ShadeMode shadeMode() const override; // Prefer isShade() void setShade(ShadeMode mode) override; bool isShadeable() const override; bool isMaximizable() const override; QRect geometryRestore() const override; MaximizeMode maximizeMode() const override; bool isMinimizable() const override; QRect iconGeometry() const override; void setFullScreen(bool set, bool user = true) override; bool isFullScreen() const override; bool isFullScreenable() const override; bool isFullScreenable(bool fullscreen_hack) const; bool userCanSetFullScreen() const override; QRect geometryFSRestore() const { return geom_fs_restore; // Only for session saving } int fullScreenMode() const { return fullscreen_mode; // only for session saving } bool noBorder() const override; void setNoBorder(bool set) override; bool userCanSetNoBorder() const override; void checkNoBorder(); int sessionStackingOrder() const; // Auxiliary functions, depend on the windowType bool wantsInput() const override; bool isResizable() const override; bool isMovable() const override; bool isMovableAcrossScreens() const override; bool isCloseable() const override; ///< May be closed by the user (May have a close button) void takeFocus() override; void updateDecoration(bool check_workspace_pos, bool force = false) override; void updateShape(); using AbstractClient::setGeometry; void setGeometry(int x, int y, int w, int h, ForceGeometry_t force = NormalGeometrySet) override; /// plainResize() simply resizes void plainResize(int w, int h, ForceGeometry_t force = NormalGeometrySet); void plainResize(const QSize& s, ForceGeometry_t force = NormalGeometrySet); /// resizeWithChecks() resizes according to gravity, and checks workarea position using AbstractClient::resizeWithChecks; void resizeWithChecks(int w, int h, ForceGeometry_t force = NormalGeometrySet) override; void resizeWithChecks(int w, int h, xcb_gravity_t gravity, ForceGeometry_t force = NormalGeometrySet); void resizeWithChecks(const QSize& s, xcb_gravity_t gravity, ForceGeometry_t force = NormalGeometrySet); QSize sizeForClientSize(const QSize&, Sizemode mode = SizemodeAny, bool noframe = false) const override; bool providesContextHelp() const override; Options::WindowOperation mouseButtonToWindowOperation(Qt::MouseButtons button); bool performMouseCommand(Options::MouseCommand, const QPoint& globalPos) override; QRect adjustedClientArea(const QRect& desktop, const QRect& area) const; xcb_colormap_t colormap() const; /// Updates visibility depending on being shaded, virtual desktop, etc. void updateVisibility(); /// Hides a client - Basically like minimize, but without effects, it's simply hidden void hideClient(bool hide) override; bool hiddenPreview() const; ///< Window is mapped in order to get a window pixmap virtual bool setupCompositing(); void finishCompositing(ReleaseReason releaseReason = ReleaseReason::Release) override; void setBlockingCompositing(bool block); inline bool isBlockingCompositing() { return blocks_compositing; } QString caption(bool full = true, bool stripped = false) const override; using AbstractClient::keyPressEvent; void keyPressEvent(uint key_code, xcb_timestamp_t time); // FRAME ?? void updateMouseGrab() override; xcb_window_t moveResizeGrabWindow() const; const QPoint calculateGravitation(bool invert, int gravity = 0) const; // FRAME public? void NETMoveResize(int x_root, int y_root, NET::Direction direction); void NETMoveResizeWindow(int flags, int x, int y, int width, int height); void restackWindow(xcb_window_t above, int detail, NET::RequestSource source, xcb_timestamp_t timestamp, bool send_event = false); void gotPing(xcb_timestamp_t timestamp); void updateUserTime(xcb_timestamp_t time = XCB_TIME_CURRENT_TIME); xcb_timestamp_t userTime() const override; bool hasUserTimeSupport() const; /// Does 'delete c;' static void deleteClient(Client* c); static bool belongToSameApplication(const Client* c1, const Client* c2, bool active_hack = false); static bool sameAppWindowRoleMatch(const Client* c1, const Client* c2, bool active_hack); void killWindow() override; void toggleShade(); void showContextHelp() override; void cancelShadeHoverTimer(); void checkActiveModal(); StrutRect strutRect(StrutArea area) const; StrutRects strutRects() const; bool hasStrut() const override; // Tabbing functions TabGroup* tabGroup() const override; // Returns a pointer to client_group Q_INVOKABLE inline bool tabBefore(Client *other, bool activate) { return tabTo(other, false, activate); } Q_INVOKABLE inline bool tabBehind(Client *other, bool activate) { return tabTo(other, true, activate); } /** * Syncs the *dynamic* @param property @param fromThisClient or the @link currentTab() to * all members of the @link tabGroup() (if there is one) * * eg. if you call: * client->setProperty("kwin_tiling_floats", true); * client->syncTabGroupFor("kwin_tiling_floats", true) * all clients in this tabGroup will have ::property("kwin_tiling_floats").toBool() == true * * WARNING: non dynamic properties are ignored - you're not supposed to alter/update such explicitly */ Q_INVOKABLE void syncTabGroupFor(QString property, bool fromThisClient = false); Q_INVOKABLE bool untab(const QRect &toGeometry = QRect(), bool clientRemoved = false) override; /** * Set tab group - this is to be invoked by TabGroup::add/remove(client) and NO ONE ELSE */ void setTabGroup(TabGroup* group); /* * If shown is true the client is mapped and raised, if false * the client is unmapped and hidden, this function is called * when the tabbing group of the client switches its visible * client. */ void setClientShown(bool shown); /* * When a click is done in the decoration and it calls the group * to change the visible client it starts to move-resize the new * client, this function stops it. */ bool isCurrentTab() const override; /** * Whether or not the window has a strut that expands through the invisible area of * an xinerama setup where the monitors are not the same resolution. */ bool hasOffscreenXineramaStrut() const; // Decorations <-> Effects QRect decorationRect() const; QRect transparentRect() const; bool isClientSideDecorated() const; bool wantsShadowToBeRendered() const override; void layoutDecorationRects(QRect &left, QRect &top, QRect &right, QRect &bottom) const override; Xcb::Property fetchFirstInTabBox() const; void readFirstInTabBox(Xcb::Property &property); void updateFirstInTabBox(); Xcb::StringProperty fetchColorScheme() const; void readColorScheme(Xcb::StringProperty &property); void updateColorScheme(); //sets whether the client should be faked as being on all activities (and be shown during session save) void setSessionActivityOverride(bool needed); virtual bool isClient() const; template void print(T &stream) const; void cancelFocusOutTimer(); /** * Restores the Client after it had been hidden due to show on screen edge functionality. * In addition the property gets deleted so that the Client knows that it is visible again. **/ void showOnScreenEdge() override; Xcb::StringProperty fetchApplicationMenuServiceName() const; void readApplicationMenuServiceName(Xcb::StringProperty &property); void checkApplicationMenuServiceName(); Xcb::StringProperty fetchApplicationMenuObjectPath() const; void readApplicationMenuObjectPath(Xcb::StringProperty &property); void checkApplicationMenuObjectPath(); static void cleanupX11(); public Q_SLOTS: void closeWindow() override; void updateCaption(); void evaluateWindowRules(); private Q_SLOTS: void shadeHover(); void shadeUnhover(); private: // Use Workspace::createClient() virtual ~Client(); ///< Use destroyClient() or releaseWindow() // Handlers for X11 events bool mapRequestEvent(xcb_map_request_event_t *e); void unmapNotifyEvent(xcb_unmap_notify_event_t *e); void destroyNotifyEvent(xcb_destroy_notify_event_t *e); void configureRequestEvent(xcb_configure_request_event_t *e); virtual void propertyNotifyEvent(xcb_property_notify_event_t *e) override; void clientMessageEvent(xcb_client_message_event_t *e) override; void enterNotifyEvent(xcb_enter_notify_event_t *e); void leaveNotifyEvent(xcb_leave_notify_event_t *e); void focusInEvent(xcb_focus_in_event_t *e); void focusOutEvent(xcb_focus_out_event_t *e); virtual void damageNotifyEvent(); bool buttonPressEvent(xcb_window_t w, int button, int state, int x, int y, int x_root, int y_root, xcb_timestamp_t time = XCB_CURRENT_TIME); bool buttonReleaseEvent(xcb_window_t w, int button, int state, int x, int y, int x_root, int y_root); bool motionNotifyEvent(xcb_window_t w, int state, int x, int y, int x_root, int y_root); Client* findAutogroupCandidate() const; protected: virtual void debug(QDebug& stream) const; void addDamage(const QRegion &damage) override; bool belongsToSameApplication(const AbstractClient *other, bool active_hack) const override; void doSetActive() override; void doSetKeepAbove() override; void doSetKeepBelow() override; void doSetDesktop(int desktop, int was_desk) override; void doMinimize() override; void doSetSkipPager() override; void doSetSkipTaskbar() override; bool belongsToDesktop() const override; bool isActiveFullScreen() const override; void setGeometryRestore(const QRect &geo) override; void updateTabGroupStates(TabGroup::States states) override; void doMove(int x, int y) override; bool doStartMoveResize() override; void doPerformMoveResize() override; bool isWaitingForMoveResizeSync() const override; void doResizeSync() override; QSize resizeIncrements() const override; bool acceptsFocus() const override; -private Q_SLOTS: - void delayedSetShortcut(); - //Signals for the scripting interface //Signals make an excellent way for communication //in between objects as compared to simple function //calls Q_SIGNALS: void clientManaging(KWin::Client*); void clientFullScreenSet(KWin::Client*, bool, bool); /** * Emitted whenever the Client's TabGroup changed. That is whenever the Client is moved to * another group, but not when a Client gets added or removed to the Client's ClientGroup. **/ void tabGroupChanged(); /** * Emitted whenever the Client want to show it menu */ void showRequest(); /** * Emitted whenever the Client's menu is closed */ void menuHidden(); /** * Emitted whenever the Client's menu is available **/ void appMenuAvailable(); /** * Emitted whenever the Client's menu is unavailable */ void appMenuUnavailable(); /** * Emitted whenever the Client's block compositing state changes. **/ void blockingCompositingChanged(KWin::Client *client); void clientSideDecoratedChanged(); private: void exportMappingState(int s); // ICCCM 4.1.3.1, 4.1.4, NETWM 2.5.1 bool isManaged() const; ///< Returns false if this client is not yet managed void updateAllowedActions(bool force = false); QRect fullscreenMonitorsArea(NETFullscreenMonitors topology) const; void changeMaximize(bool horizontal, bool vertical, bool adjust) override; int checkFullScreenHack(const QRect& geom) const; // 0 - None, 1 - One xinerama screen, 2 - Full area void updateFullScreenHack(const QRect& geom); void getWmNormalHints(); void getMotifHints(); void getIcons(); void fetchName(); void fetchIconicName(); QString readName() const; void setCaption(const QString& s, bool force = false); bool hasTransientInternal(const Client* c, bool indirect, ConstClientList& set) const; void finishWindowRules(); void setShortcutInternal() override; void configureRequest(int value_mask, int rx, int ry, int rw, int rh, int gravity, bool from_tool); NETExtendedStrut strut() const; int checkShadeGeometry(int w, int h); void getSyncCounter(); void sendSyncRequest(); void leaveMoveResize() override; void positionGeometryTip() override; void grabButton(int mod); void ungrabButton(int mod); void resizeDecoration(); void createDecoration(const QRect &oldgeom); void pingWindow(); void killProcess(bool ask, xcb_timestamp_t timestamp = XCB_TIME_CURRENT_TIME); void updateUrgency(); static void sendClientMessage(xcb_window_t w, xcb_atom_t a, xcb_atom_t protocol, uint32_t data1 = 0, uint32_t data2 = 0, uint32_t data3 = 0, xcb_timestamp_t timestamp = xTime()); void embedClient(xcb_window_t w, xcb_visualid_t visualid, xcb_colormap_t colormap, uint8_t depth); void detectNoBorder(); Xcb::Property fetchGtkFrameExtents() const; void readGtkFrameExtents(Xcb::Property &prop); void detectGtkFrameExtents(); void destroyDecoration() override; void updateFrameExtents(); void internalShow(); void internalHide(); void internalKeep(); void map(); void unmap(); void updateHiddenPreview(); void updateInputShape(); xcb_timestamp_t readUserTimeMapTimestamp(const KStartupInfoId* asn_id, const KStartupInfoData* asn_data, bool session) const; xcb_timestamp_t readUserCreationTime() const; void startupIdChanged(); void updateInputWindow(); bool tabTo(Client *other, bool behind, bool activate); Xcb::Property fetchShowOnScreenEdge() const; void readShowOnScreenEdge(Xcb::Property &property); /** * Reads the property and creates/destroys the screen edge if required * and shows/hides the client. **/ void updateShowOnScreenEdge(); Xcb::Window m_client; Xcb::Window m_wrapper; Xcb::Window m_frame; QStringList activityList; int m_activityUpdatesBlocked; bool m_blockedActivityUpdatesRequireTransients; Xcb::Window m_moveResizeGrabWindow; bool move_resize_has_keyboard_grab; bool m_managed; Xcb::GeometryHints m_geometryHints; void sendSyntheticConfigureNotify(); enum MappingState { Withdrawn, ///< Not handled, as per ICCCM WithdrawnState Mapped, ///< The frame is mapped Unmapped, ///< The frame is not mapped Kept ///< The frame should be unmapped, but is kept (For compositing) }; MappingState mapping_state; Xcb::TransientFor fetchTransient() const; void readTransientProperty(Xcb::TransientFor &transientFor); void readTransient(); xcb_window_t verifyTransientFor(xcb_window_t transient_for, bool set); void addTransient(AbstractClient* cl) override; void removeTransient(AbstractClient* cl) override; void removeFromMainClients(); void cleanGrouping(); void checkGroupTransients(); void setTransient(xcb_window_t new_transient_for_id); xcb_window_t m_transientForId; xcb_window_t m_originalTransientForId; ShadeMode shade_mode; Client *shade_below; uint deleting : 1; ///< True when doing cleanup and destroying the client Xcb::MotifHints m_motif; uint hidden : 1; ///< Forcibly hidden by calling hide() uint noborder : 1; uint app_noborder : 1; ///< App requested no border via window type, shape extension, etc. uint ignore_focus_stealing : 1; ///< Don't apply focus stealing prevention to this client bool blocks_compositing; WindowRules client_rules; // DON'T reorder - Saved to config files !!! enum FullScreenMode { FullScreenNone, FullScreenNormal, FullScreenHack ///< Non-NETWM fullscreen (noborder and size of desktop) }; FullScreenMode fullscreen_mode; MaximizeMode max_mode; QRect geom_restore; QRect geom_fs_restore; QTimer* shadeHoverTimer; xcb_colormap_t m_colormap; QString cap_normal, cap_iconic, cap_suffix, cap_deco; Group* in_group; TabGroup* tab_group; QTimer* ping_timer; qint64 m_killHelperPID; xcb_timestamp_t m_pingTimestamp; xcb_timestamp_t m_userTime; NET::Actions allowed_actions; QSize client_size; bool shade_geometry_change; struct { xcb_sync_counter_t counter; xcb_sync_int64_t value; xcb_sync_alarm_t alarm; xcb_timestamp_t lastTimestamp; QTimer *timeout, *failsafeTimeout; bool isPending; } syncRequest; static bool check_active_modal; ///< \see Client::checkActiveModal() int sm_stacking_order; friend struct ResetupRulesProcedure; friend bool performTransiencyCheck(); Xcb::StringProperty fetchActivities() const; void readActivities(Xcb::StringProperty &property); void checkActivities(); bool activitiesDefined; //whether the x property was actually set bool sessionActivityOverride; bool needsXWindowMove; Xcb::Window m_decoInputExtent; QPoint input_offset; QTimer *m_focusOutTimer; QList m_connections; bool m_clientSideDecorated; QMetaObject::Connection m_edgeRemoveConnection; QMetaObject::Connection m_edgeGeometryTrackingConnection; }; inline xcb_window_t Client::wrapperId() const { return m_wrapper; } inline bool Client::isClientSideDecorated() const { return m_clientSideDecorated; } inline bool Client::groupTransient() const { return m_transientForId == rootWindow(); } // Needed because verifyTransientFor() may set transient_for_id to root window, // if the original value has a problem (window doesn't exist, etc.) inline bool Client::wasOriginallyGroupTransient() const { return m_originalTransientForId == rootWindow(); } inline bool Client::isTransient() const { return m_transientForId != XCB_WINDOW_NONE; } inline const Group* Client::group() const { return in_group; } inline Group* Client::group() { return in_group; } inline TabGroup* Client::tabGroup() const { return tab_group; } inline bool Client::isShown(bool shaded_is_shown) const { return !isMinimized() && (!isShade() || shaded_is_shown) && !hidden && (!tabGroup() || tabGroup()->current() == this); } inline bool Client::isHiddenInternal() const { return hidden; } inline ShadeMode Client::shadeMode() const { return shade_mode; } inline QRect Client::geometryRestore() const { return geom_restore; } inline void Client::setGeometryRestore(const QRect &geo) { geom_restore = geo; } inline MaximizeMode Client::maximizeMode() const { return max_mode; } inline bool Client::isFullScreen() const { return fullscreen_mode != FullScreenNone; } inline bool Client::hasNETSupport() const { return info->hasNETSupport(); } inline xcb_colormap_t Client::colormap() const { return m_colormap; } inline int Client::sessionStackingOrder() const { return sm_stacking_order; } inline bool Client::isManaged() const { return m_managed; } inline QSize Client::clientSize() const { return client_size; } inline void Client::plainResize(const QSize& s, ForceGeometry_t force) { plainResize(s.width(), s.height(), force); } inline void Client::resizeWithChecks(int w, int h, AbstractClient::ForceGeometry_t force) { resizeWithChecks(w, h, XCB_GRAVITY_BIT_FORGET, force); } inline void Client::resizeWithChecks(const QSize& s, xcb_gravity_t gravity, ForceGeometry_t force) { resizeWithChecks(s.width(), s.height(), gravity, force); } inline bool Client::hasUserTimeSupport() const { return info->userTime() != -1U; } inline const WindowRules* Client::rules() const { return &client_rules; } inline xcb_window_t Client::moveResizeGrabWindow() const { return m_moveResizeGrabWindow; } inline void Client::removeRule(Rules* rule) { client_rules.remove(rule); } inline bool Client::hiddenPreview() const { return mapping_state == Kept; } template inline void Client::print(T &stream) const { stream << "\'ID:" << window() << ";WMCLASS:" << resourceClass() << ":" << resourceName() << ";Caption:" << caption() << "\'"; } } // namespace Q_DECLARE_METATYPE(KWin::Client*) Q_DECLARE_METATYPE(QList) #endif diff --git a/useractions.cpp b/useractions.cpp index c3a0360dc..62052baa3 100644 --- a/useractions.cpp +++ b/useractions.cpp @@ -1,1871 +1,1862 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ /* This file contains things relevant to direct user actions, such as responses to global keyboard shortcuts, or selecting actions from the window operations menu. */ /////////////////////////////////////////////////////////////////////////////// // NOTE: if you change the menu, keep kde-workspace/libs/taskmanager/taskactions.cpp in sync ////////////////////////////////////////////////////////////////////////////// #include "useractions.h" #include "cursor.h" #include "client.h" #include "input.h" #include "workspace.h" #include "effects.h" #include "platform.h" #include "screens.h" #include "virtualdesktops.h" #include "scripting/scripting.h" #ifdef KWIN_BUILD_ACTIVITIES #include "activities.h" #include #endif #include "appmenu.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "killwindow.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox.h" #endif namespace KWin { UserActionsMenu::UserActionsMenu(QObject *parent) : QObject(parent) , m_menu(NULL) , m_desktopMenu(NULL) , m_screenMenu(NULL) , m_activityMenu(NULL) , m_addTabsMenu(NULL) , m_switchToTabMenu(NULL) , m_scriptsMenu(NULL) , m_resizeOperation(NULL) , m_moveOperation(NULL) , m_maximizeOperation(NULL) , m_shadeOperation(NULL) , m_keepAboveOperation(NULL) , m_keepBelowOperation(NULL) , m_fullScreenOperation(NULL) , m_noBorderOperation(NULL) , m_minimizeOperation(NULL) , m_closeOperation(NULL) , m_removeFromTabGroup(NULL) , m_closeTabGroup(NULL) , m_client(QWeakPointer()) { } UserActionsMenu::~UserActionsMenu() { discard(); } bool UserActionsMenu::isShown() const { return m_menu && m_menu->isVisible(); } bool UserActionsMenu::hasClient() const { return !m_client.isNull() && isShown(); } void UserActionsMenu::close() { if (!m_menu) { return; } m_menu->close(); m_client.clear(); } bool UserActionsMenu::isMenuClient(const AbstractClient *c) const { if (!c || m_client.isNull()) { return false; } return c == m_client.data(); } void UserActionsMenu::show(const QRect &pos, const QWeakPointer &cl) { if (!KAuthorized::authorizeAction(QStringLiteral("kwin_rmb"))) return; if (cl.isNull()) return; if (isShown()) // recursion return; if (cl.data()->isDesktop() || cl.data()->isDock()) return; m_client = cl; init(); Workspace *ws = Workspace::self(); int x = pos.left(); int y = pos.bottom(); const bool needsPopup = kwinApp()->shouldUseWaylandForCompositing(); if (y == pos.top()) { m_client.data()->blockActivityUpdates(true); if (needsPopup) { m_menu->popup(QPoint(x, y)); } else { m_menu->exec(QPoint(x, y)); } if (!m_client.isNull()) m_client.data()->blockActivityUpdates(false); } else { m_client.data()->blockActivityUpdates(true); QRect area = ws->clientArea(ScreenArea, QPoint(x, y), VirtualDesktopManager::self()->current()); menuAboutToShow(); // needed for sizeHint() to be correct :-/ int popupHeight = m_menu->sizeHint().height(); if (y + popupHeight < area.height()) { if (needsPopup) { m_menu->popup(QPoint(x, y)); } else { m_menu->exec(QPoint(x, y)); } } else { if (needsPopup) { m_menu->popup(QPoint(x, pos.top() - popupHeight)); } else { m_menu->exec(QPoint(x, pos.top() - popupHeight)); } } if (!m_client.isNull()) m_client.data()->blockActivityUpdates(true); } } void UserActionsMenu::grabInput() { m_menu->windowHandle()->setMouseGrabEnabled(true); m_menu->windowHandle()->setKeyboardGrabEnabled(true); } void UserActionsMenu::helperDialog(const QString& message, const QWeakPointer &c) { QStringList args; QString type; auto shortcut = [](const QString &name) { QAction* action = Workspace::self()->findChild(name); assert(action != NULL); const auto shortcuts = KGlobalAccel::self()->shortcut(action); return QStringLiteral("%1 (%2)").arg(action->text()) .arg(shortcuts.isEmpty() ? QString() : shortcuts.first().toString(QKeySequence::NativeText)); }; if (message == QStringLiteral("noborderaltf3")) { args << QStringLiteral("--msgbox") << i18n( "You have selected to show a window without its border.\n" "Without the border, you will not be able to enable the border " "again using the mouse: use the window operations menu instead, " "activated using the %1 keyboard shortcut.", shortcut(QStringLiteral("Window Operations Menu"))); type = QStringLiteral("altf3warning"); } else if (message == QLatin1String("fullscreenaltf3")) { args << QStringLiteral("--msgbox") << i18n( "You have selected to show a window in fullscreen mode.\n" "If the application itself does not have an option to turn the fullscreen " "mode off you will not be able to disable it " "again using the mouse: use the window operations menu instead, " "activated using the %1 keyboard shortcut.", shortcut(QStringLiteral("Window Operations Menu"))); type = QStringLiteral("altf3warning"); } else abort(); if (!type.isEmpty()) { KConfig cfg(QStringLiteral("kwin_dialogsrc")); KConfigGroup cg(&cfg, "Notification Messages"); // Depends on KMessageBox if (!cg.readEntry(type, true)) return; args << QStringLiteral("--dontagain") << QLatin1String("kwin_dialogsrc:") + type; } if (!c.isNull()) args << QStringLiteral("--embed") << QString::number(c.data()->windowId()); QtConcurrent::run([args]() { KProcess::startDetached(QStringLiteral("kdialog"), args); }); } QStringList configModules(bool controlCenter) { QStringList args; args << QStringLiteral("kwindecoration"); if (controlCenter) args << QStringLiteral("kwinoptions"); else if (KAuthorized::authorizeControlModule(QStringLiteral("kde-kwinoptions.desktop"))) args << QStringLiteral("kwinactions") << QStringLiteral("kwinfocus") << QStringLiteral("kwinmoving") << QStringLiteral("kwinadvanced") << QStringLiteral("kwinrules") << QStringLiteral("kwincompositing") << QStringLiteral("kwineffects") #ifdef KWIN_BUILD_TABBOX << QStringLiteral("kwintabbox") #endif << QStringLiteral("kwinscreenedges") << QStringLiteral("kwinscripts") ; return args; } void UserActionsMenu::init() { if (m_menu) { return; } m_menu = new QMenu; connect(m_menu, &QMenu::aboutToShow, this, &UserActionsMenu::menuAboutToShow); connect(m_menu, &QMenu::triggered, this, &UserActionsMenu::slotWindowOperation, Qt::QueuedConnection); QMenu *advancedMenu = new QMenu(m_menu); connect(advancedMenu, &QMenu::aboutToShow, [this, advancedMenu]() { advancedMenu->setPalette(m_client.data()->palette()); }); auto setShortcut = [](QAction *action, const QString &actionName) { const auto shortcuts = KGlobalAccel::self()->shortcut(Workspace::self()->findChild(actionName)); if (!shortcuts.isEmpty()) { action->setShortcut(shortcuts.first()); } }; m_moveOperation = advancedMenu->addAction(i18n("&Move")); m_moveOperation->setIcon(QIcon::fromTheme(QStringLiteral("transform-move"))); setShortcut(m_moveOperation, QStringLiteral("Window Move")); m_moveOperation->setData(Options::UnrestrictedMoveOp); m_resizeOperation = advancedMenu->addAction(i18n("&Resize")); setShortcut(m_resizeOperation, QStringLiteral("Window Resize")); m_resizeOperation->setData(Options::ResizeOp); m_keepAboveOperation = advancedMenu->addAction(i18n("Keep &Above Others")); m_keepAboveOperation->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); setShortcut(m_keepAboveOperation, QStringLiteral("Window Above Other Windows")); m_keepAboveOperation->setCheckable(true); m_keepAboveOperation->setData(Options::KeepAboveOp); m_keepBelowOperation = advancedMenu->addAction(i18n("Keep &Below Others")); m_keepBelowOperation->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); setShortcut(m_keepBelowOperation, QStringLiteral("Window Below Other Windows")); m_keepBelowOperation->setCheckable(true); m_keepBelowOperation->setData(Options::KeepBelowOp); m_fullScreenOperation = advancedMenu->addAction(i18n("&Fullscreen")); m_fullScreenOperation->setIcon(QIcon::fromTheme(QStringLiteral("view-fullscreen"))); setShortcut(m_fullScreenOperation, QStringLiteral("Window Fullscreen")); m_fullScreenOperation->setCheckable(true); m_fullScreenOperation->setData(Options::FullScreenOp); m_shadeOperation = advancedMenu->addAction(i18n("&Shade")); setShortcut(m_shadeOperation, QStringLiteral("Window Shade")); m_shadeOperation->setCheckable(true); m_shadeOperation->setData(Options::ShadeOp); m_noBorderOperation = advancedMenu->addAction(i18n("&No Border")); setShortcut(m_noBorderOperation, QStringLiteral("Window No Border")); m_noBorderOperation->setCheckable(true); m_noBorderOperation->setData(Options::NoBorderOp); advancedMenu->addSeparator(); m_shortcutOperation = advancedMenu->addAction(i18n("Window Short&cut...")); m_shortcutOperation->setIcon(QIcon::fromTheme(QStringLiteral("configure-shortcuts"))); setShortcut(m_shortcutOperation, QStringLiteral("Setup Window Shortcut")); m_shortcutOperation->setData(Options::SetupWindowShortcutOp); QAction *action = advancedMenu->addAction(i18n("Special &Window Settings...")); action->setIcon(QIcon::fromTheme(QStringLiteral("preferences-system-windows-actions"))); action->setData(Options::WindowRulesOp); action = advancedMenu->addAction(i18n("S&pecial Application Settings...")); action->setIcon(QIcon::fromTheme(QStringLiteral("preferences-system-windows-actions"))); action->setData(Options::ApplicationRulesOp); if (!kwinApp()->config()->isImmutable() && !KAuthorized::authorizeControlModules(configModules(true)).isEmpty()) { advancedMenu->addSeparator(); action = advancedMenu->addAction(i18nc("Entry in context menu of window decoration to open the configuration module of KWin", "Window Manager S&ettings...")); action->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); connect(action, &QAction::triggered, this, [this]() { // opens the KWin configuration QStringList args; args << QStringLiteral("--icon") << QStringLiteral("preferences-system-windows"); const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kservices5/kwinfocus.desktop")); if (!path.isEmpty()) { args << QStringLiteral("--desktopfile") << path; } args << configModules(false); QProcess *p = new Process(this); p->setArguments(args); p->setProcessEnvironment(kwinApp()->processStartupEnvironment()); p->setProgram(QStringLiteral("kcmshell5")); connect(p, static_cast(&QProcess::finished), p, &QProcess::deleteLater); connect(p, static_cast(&QProcess::error), this, [p] (QProcess::ProcessError e) { if (e == QProcess::FailedToStart) { qCDebug(KWIN_CORE) << "Failed to start kcmshell5"; } } ); p->start(); } ); } m_minimizeOperation = m_menu->addAction(i18n("Mi&nimize")); setShortcut(m_minimizeOperation, QStringLiteral("Window Minimize")); m_minimizeOperation->setData(Options::MinimizeOp); m_maximizeOperation = m_menu->addAction(i18n("Ma&ximize")); setShortcut(m_maximizeOperation, QStringLiteral("Window Maximize")); m_maximizeOperation->setCheckable(true); m_maximizeOperation->setData(Options::MaximizeOp); m_menu->addSeparator(); // Actions for window tabbing if (false) { m_removeFromTabGroup = m_menu->addAction(i18n("&Untab")); setShortcut(m_removeFromTabGroup, QStringLiteral("Untab")); m_removeFromTabGroup->setData(Options::RemoveTabFromGroupOp); m_closeTabGroup = m_menu->addAction(i18n("Close Entire &Group")); m_closeTabGroup->setIcon(QIcon::fromTheme(QStringLiteral("window-close"))); setShortcut(m_closeTabGroup, QStringLiteral("Close TabGroup")); m_closeTabGroup->setData(Options::CloseTabGroupOp); m_menu->addSeparator(); } m_menu->addSeparator(); action = m_menu->addMenu(advancedMenu); action->setText(i18n("&More Actions")); m_menu->addSeparator(); m_closeOperation = m_menu->addAction(i18n("&Close")); m_closeOperation->setIcon(QIcon::fromTheme(QStringLiteral("window-close"))); setShortcut(m_closeOperation, QStringLiteral("Window Close")); m_closeOperation->setData(Options::CloseOp); } void UserActionsMenu::discard() { delete m_menu; m_menu = NULL; m_desktopMenu = NULL; m_screenMenu = NULL; m_activityMenu = NULL; m_switchToTabMenu = NULL; m_addTabsMenu = NULL; m_scriptsMenu = NULL; } void UserActionsMenu::menuAboutToShow() { if (m_client.isNull() || !m_menu) return; if (VirtualDesktopManager::self()->count() == 1) { delete m_desktopMenu; m_desktopMenu = 0; } else { initDesktopPopup(); } if (screens()->count() == 1 || (!m_client.data()->isMovable() && !m_client.data()->isMovableAcrossScreens())) { delete m_screenMenu; m_screenMenu = NULL; } else { initScreenPopup(); } m_menu->setPalette(m_client.data()->palette()); m_resizeOperation->setEnabled(m_client.data()->isResizable()); m_moveOperation->setEnabled(m_client.data()->isMovableAcrossScreens()); m_maximizeOperation->setEnabled(m_client.data()->isMaximizable()); m_maximizeOperation->setChecked(m_client.data()->maximizeMode() == MaximizeFull); m_shadeOperation->setEnabled(m_client.data()->isShadeable()); m_shadeOperation->setChecked(m_client.data()->shadeMode() != ShadeNone); m_keepAboveOperation->setChecked(m_client.data()->keepAbove()); m_keepBelowOperation->setChecked(m_client.data()->keepBelow()); m_fullScreenOperation->setEnabled(m_client.data()->userCanSetFullScreen()); m_fullScreenOperation->setChecked(m_client.data()->isFullScreen()); m_noBorderOperation->setEnabled(m_client.data()->userCanSetNoBorder()); m_noBorderOperation->setChecked(m_client.data()->noBorder()); m_minimizeOperation->setEnabled(m_client.data()->isMinimizable()); m_closeOperation->setEnabled(m_client.data()->isCloseable()); m_shortcutOperation->setEnabled(m_client.data()->rules()->checkShortcut(QString()).isNull()); if (false) { initTabbingPopups(); m_addTabsMenu->setPalette(m_client.data()->palette()); } else { delete m_addTabsMenu; m_addTabsMenu = 0; } // drop the existing scripts menu delete m_scriptsMenu; m_scriptsMenu = NULL; // ask scripts whether they want to add entries for the given Client QList scriptActions = Scripting::self()->actionsForUserActionMenu(m_client.data(), m_scriptsMenu); if (!scriptActions.isEmpty()) { m_scriptsMenu = new QMenu(m_menu); m_scriptsMenu->setPalette(m_client.data()->palette()); m_scriptsMenu->addActions(scriptActions); QAction *action = m_scriptsMenu->menuAction(); // set it as the first item after desktop m_menu->insertAction(m_closeOperation, action); action->setText(i18n("&Extensions")); } showHideActivityMenu(); } void UserActionsMenu::showHideActivityMenu() { #ifdef KWIN_BUILD_ACTIVITIES if (!Activities::self()) { return; } const QStringList &openActivities_ = Activities::self()->running(); qCDebug(KWIN_CORE) << "activities:" << openActivities_.size(); if (openActivities_.size() < 2) { delete m_activityMenu; m_activityMenu = 0; } else { initActivityPopup(); } #endif } void UserActionsMenu::selectPopupClientTab(QAction* action) { if (!(!m_client.isNull() && m_client.data()->tabGroup()) || !action->data().isValid()) return; if (Client *other = action->data().value()) { m_client.data()->tabGroup()->setCurrent(other); return; } // failed conversion, try "1" & "2", being prev and next int direction = action->data().toInt(); if (direction == 1) m_client.data()->tabGroup()->activatePrev(); else if (direction == 2) m_client.data()->tabGroup()->activateNext(); } static QString shortCaption(const QString &s) { if (s.length() < 64) return s; QString ss = s; return ss.replace(32,s.length()-64, QStringLiteral("...")); } void UserActionsMenu::rebuildTabListPopup() { Q_ASSERT(m_switchToTabMenu); m_switchToTabMenu->clear(); // whatever happens "0x1" and "0x2" are no heap positions ;-) m_switchToTabMenu->addAction(i18nc("Switch to tab -> Previous", "Previous"))->setData(1); m_switchToTabMenu->addAction(i18nc("Switch to tab -> Next", "Next"))->setData(2); m_switchToTabMenu->addSeparator(); for (QList::const_iterator i = m_client.data()->tabGroup()->clients().constBegin(), end = m_client.data()->tabGroup()->clients().constEnd(); i != end; ++i) { if ((*i)->noBorder() || *i == m_client.data()->tabGroup()->current()) continue; // cannot tab there anyway m_switchToTabMenu->addAction(shortCaption((*i)->caption()))->setData(QVariant::fromValue(*i)); } } void UserActionsMenu::entabPopupClient(QAction* action) { if (m_client.isNull() || !action->data().isValid()) return; Client *other = dynamic_cast(action->data().value()); if (!Workspace::self()->clientList().contains(other)) // might have been lost betwenn pop-up and selection return; Client *c = dynamic_cast(m_client.data()); if (!c) { return; } c->tabBehind(other, true); if (options->focusPolicyIsReasonable()) Workspace::self()->requestFocus(c); } void UserActionsMenu::rebuildTabGroupPopup() { Q_ASSERT(m_addTabsMenu); m_addTabsMenu->clear(); QList handled; const ClientList &clientList = Workspace::self()->clientList(); for (QList::const_iterator i = clientList.constBegin(), end = clientList.constEnd(); i != end; ++i) { if (*i == m_client.data() || (*i)->noBorder()) continue; m_addTabsMenu->addAction(shortCaption((*i)->caption()))->setData(QVariant::fromValue(*i)); } if (m_addTabsMenu->actions().isEmpty()) m_addTabsMenu->addAction(i18nc("There's no window available to be attached as tab to this one", "None available"))->setEnabled(false); } void UserActionsMenu::initTabbingPopups() { bool needTabManagers = false; if (m_client.data()->tabGroup() && m_client.data()->tabGroup()->count() > 1) { needTabManagers = true; if (!m_switchToTabMenu) { m_switchToTabMenu = new QMenu(i18n("Switch to Tab"), m_menu); connect(m_switchToTabMenu, &QMenu::triggered, this, &UserActionsMenu::selectPopupClientTab); connect(m_switchToTabMenu, &QMenu::aboutToShow, this, &UserActionsMenu::rebuildTabListPopup); m_menu->insertMenu(m_removeFromTabGroup, m_switchToTabMenu); } } else { delete m_switchToTabMenu; m_switchToTabMenu = 0; } if (!m_addTabsMenu) { m_addTabsMenu = new QMenu(i18n("&Attach as tab to"), m_menu); connect(m_addTabsMenu, &QMenu::triggered, this, &UserActionsMenu::entabPopupClient); connect(m_addTabsMenu, &QMenu::aboutToShow, this, &UserActionsMenu::rebuildTabGroupPopup); m_menu->insertMenu(m_removeFromTabGroup, m_addTabsMenu); } m_addTabsMenu->menuAction()->setEnabled(!m_client.data()->isFullScreen()); m_removeFromTabGroup->setVisible(needTabManagers); m_closeTabGroup->setVisible(needTabManagers); } void UserActionsMenu::initDesktopPopup() { if (m_desktopMenu) return; m_desktopMenu = new QMenu(m_menu); connect(m_desktopMenu, &QMenu::triggered, this, &UserActionsMenu::slotSendToDesktop); connect(m_desktopMenu, &QMenu::aboutToShow, this, &UserActionsMenu::desktopPopupAboutToShow); QAction *action = m_desktopMenu->menuAction(); // set it as the first item m_menu->insertAction(m_minimizeOperation, action); action->setText(i18n("Move To &Desktop")); } void UserActionsMenu::initScreenPopup() { if (m_screenMenu) { return; } m_screenMenu = new QMenu(m_menu); connect(m_screenMenu, &QMenu::triggered, this, &UserActionsMenu::slotSendToScreen); connect(m_screenMenu, &QMenu::aboutToShow, this, &UserActionsMenu::screenPopupAboutToShow); QAction *action = m_screenMenu->menuAction(); // set it as the first item after desktop m_menu->insertAction(m_activityMenu ? m_activityMenu->menuAction() : m_minimizeOperation, action); action->setText(i18n("Move To &Screen")); } void UserActionsMenu::initActivityPopup() { if (m_activityMenu) return; m_activityMenu = new QMenu(m_menu); connect(m_activityMenu, &QMenu::triggered, this, &UserActionsMenu::slotToggleOnActivity); connect(m_activityMenu, &QMenu::aboutToShow, this, &UserActionsMenu::activityPopupAboutToShow); QAction *action = m_activityMenu->menuAction(); // set it as the first item m_menu->insertAction(m_minimizeOperation, action); action->setText(i18n("Ac&tivities")); //FIXME is that a good string? } void UserActionsMenu::desktopPopupAboutToShow() { if (!m_desktopMenu) return; const VirtualDesktopManager *vds = VirtualDesktopManager::self(); m_desktopMenu->clear(); m_desktopMenu->setPalette(m_client.data()->palette()); QActionGroup *group = new QActionGroup(m_desktopMenu); QAction *action = m_desktopMenu->addAction(i18n("&All Desktops")); action->setData(0); action->setCheckable(true); group->addAction(action); if (!m_client.isNull() && m_client.data()->isOnAllDesktops()) action->setChecked(true); m_desktopMenu->addSeparator(); const uint BASE = 10; for (uint i = 1; i <= vds->count(); ++i) { QString basic_name(QStringLiteral("%1 %2")); if (i < BASE) { basic_name.prepend(QLatin1Char('&')); } action = m_desktopMenu->addAction(basic_name.arg(i).arg(vds->name(i).replace(QLatin1Char('&'), QStringLiteral("&&")))); action->setData(i); action->setCheckable(true); group->addAction(action); if (!m_client.isNull() && !m_client.data()->isOnAllDesktops() && m_client.data()->isOnDesktop(i)) action->setChecked(true); } m_desktopMenu->addSeparator(); action = m_desktopMenu->addAction(i18nc("Create a new desktop and move there the window", "&New Desktop")); action->setData(vds->count() + 1); if (vds->count() >= vds->maximum()) action->setEnabled(false); } void UserActionsMenu::screenPopupAboutToShow() { if (!m_screenMenu) { return; } m_screenMenu->clear(); m_screenMenu->setPalette(m_client.data()->palette()); QActionGroup *group = new QActionGroup(m_screenMenu); for (int i = 0; icount(); ++i) { // assumption: there are not more than 9 screens attached. QAction *action = m_screenMenu->addAction(i18nc("@item:inmenu List of all Screens to send a window to. First argument is a number, second the output identifier. E.g. Screen 1 (HDMI1)", "Screen &%1 (%2)", (i+1), screens()->name(i))); action->setData(i); action->setCheckable(true); if (!m_client.isNull() && i == m_client.data()->screen()) { action->setChecked(true); } group->addAction(action); } } void UserActionsMenu::activityPopupAboutToShow() { if (!m_activityMenu) return; #ifdef KWIN_BUILD_ACTIVITIES if (!Activities::self()) { return; } m_activityMenu->clear(); m_activityMenu->setPalette(m_client.data()->palette()); QAction *action = m_activityMenu->addAction(i18n("&All Activities")); action->setData(QString()); action->setCheckable(true); static QPointer allActivitiesGroup; if (!allActivitiesGroup) { allActivitiesGroup = new QActionGroup(m_activityMenu); } allActivitiesGroup->addAction(action); if (!m_client.isNull() && m_client.data()->isOnAllActivities()) action->setChecked(true); m_activityMenu->addSeparator(); foreach (const QString &id, Activities::self()->running()) { KActivities::Info activity(id); QString name = activity.name(); name.replace('&', "&&"); QWidgetAction *action = new QWidgetAction(m_activityMenu); QCheckBox *box = new QCheckBox(name, m_activityMenu); action->setDefaultWidget(box); const QString icon = activity.icon(); if (!icon.isEmpty()) box->setIcon(QIcon::fromTheme(icon)); box->setBackgroundRole(m_activityMenu->backgroundRole()); box->setForegroundRole(m_activityMenu->foregroundRole()); box->setPalette(m_activityMenu->palette()); connect(box, &QCheckBox::clicked, action, &QAction::triggered); m_activityMenu->addAction(action); action->setData(id); if (!m_client.isNull() && !m_client.data()->isOnAllActivities() && m_client.data()->isOnActivity(id)) box->setChecked(true); } #endif } void UserActionsMenu::slotWindowOperation(QAction *action) { if (!action->data().isValid()) return; Options::WindowOperation op = static_cast< Options::WindowOperation >(action->data().toInt()); QWeakPointer c = (!m_client.isNull()) ? m_client : QWeakPointer(Workspace::self()->activeClient()); if (c.isNull()) return; QString type; switch(op) { case Options::FullScreenOp: if (!c.data()->isFullScreen() && c.data()->userCanSetFullScreen()) type = QStringLiteral("fullscreenaltf3"); break; case Options::NoBorderOp: if (!c.data()->noBorder() && c.data()->userCanSetNoBorder()) type = QStringLiteral("noborderaltf3"); break; default: break; }; if (!type.isEmpty()) helperDialog(type, c); // need to delay performing the window operation as we need to have the // user actions menu closed before we destroy the decoration. Otherwise Qt crashes qRegisterMetaType(); QMetaObject::invokeMethod(workspace(), "performWindowOperation", Qt::QueuedConnection, Q_ARG(KWin::AbstractClient*, c.data()), Q_ARG(Options::WindowOperation, op)); } void UserActionsMenu::slotSendToDesktop(QAction *action) { bool ok = false; uint desk = action->data().toUInt(&ok); if (!ok) { return; } if (m_client.isNull()) return; Workspace *ws = Workspace::self(); VirtualDesktopManager *vds = VirtualDesktopManager::self(); if (desk == 0) { // the 'on_all_desktops' menu entry m_client.data()->setOnAllDesktops(!m_client.data()->isOnAllDesktops()); return; } else if (desk > vds->count()) { vds->setCount(desk); } ws->sendClientToDesktop(m_client.data(), desk, false); } void UserActionsMenu::slotSendToScreen(QAction *action) { const int screen = action->data().toInt(); if (m_client.isNull()) { return; } if (screen >= screens()->count()) { return; } Workspace::self()->sendClientToScreen(m_client.data(), screen); } void UserActionsMenu::slotToggleOnActivity(QAction *action) { #ifdef KWIN_BUILD_ACTIVITIES if (!Activities::self()) { return; } QString activity = action->data().toString(); if (m_client.isNull()) return; if (activity.isEmpty()) { // the 'on_all_activities' menu entry m_client.data()->setOnAllActivities(!m_client.data()->isOnAllActivities()); return; } Client *c = dynamic_cast(m_client.data()); if (!c) { return; } Activities::self()->toggleClientOnActivity(c, activity, false); if (m_activityMenu && m_activityMenu->isVisible() && m_activityMenu->actions().count()) { const bool isOnAll = m_client.data()->isOnAllActivities(); m_activityMenu->actions().at(0)->setChecked(isOnAll); if (isOnAll) { // toggleClientOnActivity interprets "on all" as "on none" and // susequent toggling ("off") would move the client to only that activity. // bug #330838 -> set all but "on all" off to "force proper usage" for (int i = 1; i < m_activityMenu->actions().count(); ++i) { if (QWidgetAction *qwa = qobject_cast(m_activityMenu->actions().at(i))) { if (QCheckBox *qcb = qobject_cast(qwa->defaultWidget())) { qcb->setChecked(false); } } } } } #else Q_UNUSED(action) #endif } //**************************************** // ShortcutDialog //**************************************** ShortcutDialog::ShortcutDialog(const QKeySequence& cut) : _shortcut(cut) { m_ui.setupUi(this); m_ui.keySequenceEdit->setKeySequence(cut); m_ui.warning->hide(); // Listen to changed shortcuts connect(m_ui.keySequenceEdit, &QKeySequenceEdit::editingFinished, this, &ShortcutDialog::keySequenceChanged); connect(m_ui.clearButton, &QToolButton::clicked, [this]{ _shortcut = QKeySequence(); }); m_ui.keySequenceEdit->setFocus(); setWindowFlags(Qt::Popup | Qt::X11BypassWindowManagerHint); } void ShortcutDialog::accept() { QKeySequence seq = shortcut(); if (!seq.isEmpty()) { if (seq[0] == Qt::Key_Escape) { reject(); return; } if (seq[0] == Qt::Key_Space || (seq[0] & Qt::KeyboardModifierMask) == 0) { // clear m_ui.keySequenceEdit->clear(); QDialog::accept(); return; } } QDialog::accept(); } void ShortcutDialog::done(int r) { QDialog::done(r); emit dialogDone(r == Accepted); } void ShortcutDialog::keySequenceChanged() { activateWindow(); // where is the kbd focus lost? cause of popup state? QKeySequence seq = m_ui.keySequenceEdit->keySequence(); if (_shortcut == seq) return; // don't try to update the same if (seq.isEmpty()) { // clear _shortcut = seq; return; } if (seq.count() > 1) { seq = QKeySequence(seq[0]); m_ui.keySequenceEdit->setKeySequence(seq); } // Check if the key sequence is used currently QString sc = seq.toString(); // NOTICE - seq.toString() & the entries in "conflicting" randomly get invalidated after the next call (if no sc has been set & conflicting isn't empty?!) QList conflicting = KGlobalAccel::getGlobalShortcutsByKey(seq); if (!conflicting.isEmpty()) { const KGlobalShortcutInfo &conflict = conflicting.at(0); m_ui.warning->setText(i18nc("'%1' is a keyboard shortcut like 'ctrl+w'", "%1 is already in use", sc)); m_ui.warning->setToolTip(i18nc("keyboard shortcut '%1' is used by action '%2' in application '%3'", "%1 is used by %2 in %3", sc, conflict.friendlyName(), conflict.componentFriendlyName())); m_ui.warning->show(); m_ui.keySequenceEdit->setKeySequence(shortcut()); } else if (seq != _shortcut) { m_ui.warning->hide(); if (QPushButton *ok = m_ui.buttonBox->button(QDialogButtonBox::Ok)) ok->setFocus(); } _shortcut = seq; } QKeySequence ShortcutDialog::shortcut() const { return _shortcut; } //**************************************** // Workspace //**************************************** void Workspace::slotIncreaseWindowOpacity() { if (!active_client) { return; } active_client->setOpacity(qMin(active_client->opacity() + 0.05, 1.0)); } void Workspace::slotLowerWindowOpacity() { if (!active_client) { return; } active_client->setOpacity(qMax(active_client->opacity() - 0.05, 0.05)); } void Workspace::closeActivePopup() { if (active_popup) { active_popup->close(); active_popup = NULL; active_popup_client = NULL; } m_userActionsMenu->close(); } template void Workspace::initShortcut(const QString &actionName, const QString &description, const QKeySequence &shortcut, Slot slot, const QVariant &data) { QAction *a = new QAction(this); a->setProperty("componentName", QStringLiteral(KWIN_NAME)); a->setObjectName(actionName); a->setText(description); if (data.isValid()) { a->setData(data); } KGlobalAccel::self()->setDefaultShortcut(a, QList() << shortcut); KGlobalAccel::self()->setShortcut(a, QList() << shortcut); input()->registerShortcut(shortcut, a, this, slot); } /*! Create the global accel object \c keys. */ void Workspace::initShortcuts() { #define IN_KWIN #include "kwinbindings.cpp" #ifdef KWIN_BUILD_TABBOX TabBox::TabBox::self()->initShortcuts(); #endif VirtualDesktopManager::self()->initShortcuts(); m_userActionsMenu->discard(); // so that it's recreated next time } void Workspace::setupWindowShortcut(AbstractClient* c) { assert(client_keys_dialog == NULL); // TODO: PORT ME (KGlobalAccel related) //keys->setEnabled( false ); //disable_shortcuts_keys->setEnabled( false ); //client_keys->setEnabled( false ); client_keys_dialog = new ShortcutDialog(c->shortcut()); client_keys_client = c; connect(client_keys_dialog, &ShortcutDialog::dialogDone, this, &Workspace::setupWindowShortcutDone); QRect r = clientArea(ScreenArea, c); QSize size = client_keys_dialog->sizeHint(); QPoint pos = c->pos() + c->clientPos(); if (pos.x() + size.width() >= r.right()) pos.setX(r.right() - size.width()); if (pos.y() + size.height() >= r.bottom()) pos.setY(r.bottom() - size.height()); client_keys_dialog->move(pos); client_keys_dialog->show(); active_popup = client_keys_dialog; active_popup_client = c; } void Workspace::setupWindowShortcutDone(bool ok) { // keys->setEnabled( true ); // disable_shortcuts_keys->setEnabled( true ); // client_keys->setEnabled( true ); if (ok) client_keys_client->setShortcut(client_keys_dialog->shortcut().toString()); closeActivePopup(); client_keys_dialog->deleteLater(); client_keys_dialog = NULL; client_keys_client = NULL; if (active_client) active_client->takeFocus(); } void Workspace::clientShortcutUpdated(AbstractClient* c) { QString key = QStringLiteral("_k_session:%1").arg(c->window()); QAction* action = findChild(key); if (!c->shortcut().isEmpty()) { if (action == NULL) { // new shortcut action = new QAction(this); kwinApp()->platform()->setupActionForGlobalAccel(action); action->setProperty("componentName", QStringLiteral(KWIN_NAME)); action->setObjectName(key); action->setText(i18n("Activate Window (%1)", c->caption())); - connect(action, &QAction::triggered, c, - [c]() { - workspace()->activateClient(c, true); - } - ); + connect(action, &QAction::triggered, c, std::bind(&Workspace::activateClient, this, c, true)); } // no autoloading, since it's configured explicitly here and is not meant to be reused // (the key is the window id anyway, which is kind of random) KGlobalAccel::self()->setShortcut(action, QList() << c->shortcut(), KGlobalAccel::NoAutoloading); action->setEnabled(true); } else { KGlobalAccel::self()->removeAllShortcuts(action); delete action; } } void Workspace::performWindowOperation(AbstractClient* c, Options::WindowOperation op) { if (!c) return; if (op == Options::MoveOp || op == Options::UnrestrictedMoveOp) Cursor::setPos(c->geometry().center()); if (op == Options::ResizeOp || op == Options::UnrestrictedResizeOp) Cursor::setPos(c->geometry().bottomRight()); switch(op) { case Options::MoveOp: c->performMouseCommand(Options::MouseMove, Cursor::pos()); break; case Options::UnrestrictedMoveOp: c->performMouseCommand(Options::MouseUnrestrictedMove, Cursor::pos()); break; case Options::ResizeOp: c->performMouseCommand(Options::MouseResize, Cursor::pos()); break; case Options::UnrestrictedResizeOp: c->performMouseCommand(Options::MouseUnrestrictedResize, Cursor::pos()); break; case Options::CloseOp: QMetaObject::invokeMethod(c, "closeWindow", Qt::QueuedConnection); break; case Options::MaximizeOp: c->maximize(c->maximizeMode() == MaximizeFull ? MaximizeRestore : MaximizeFull); break; case Options::HMaximizeOp: c->maximize(c->maximizeMode() ^ MaximizeHorizontal); break; case Options::VMaximizeOp: c->maximize(c->maximizeMode() ^ MaximizeVertical); break; case Options::RestoreOp: c->maximize(MaximizeRestore); break; case Options::MinimizeOp: c->minimize(); break; case Options::ShadeOp: c->performMouseCommand(Options::MouseShade, Cursor::pos()); break; case Options::OnAllDesktopsOp: c->setOnAllDesktops(!c->isOnAllDesktops()); break; case Options::FullScreenOp: c->setFullScreen(!c->isFullScreen(), true); break; case Options::NoBorderOp: c->setNoBorder(!c->noBorder()); break; case Options::KeepAboveOp: { StackingUpdatesBlocker blocker(this); bool was = c->keepAbove(); c->setKeepAbove(!c->keepAbove()); if (was && !c->keepAbove()) raiseClient(c); break; } case Options::KeepBelowOp: { StackingUpdatesBlocker blocker(this); bool was = c->keepBelow(); c->setKeepBelow(!c->keepBelow()); if (was && !c->keepBelow()) lowerClient(c); break; } case Options::OperationsOp: c->performMouseCommand(Options::MouseShade, Cursor::pos()); break; case Options::WindowRulesOp: RuleBook::self()->edit(c, false); break; case Options::ApplicationRulesOp: RuleBook::self()->edit(c, true); break; case Options::SetupWindowShortcutOp: setupWindowShortcut(c); break; case Options::LowerOp: lowerClient(c); break; case Options::TabDragOp: // Handled by decoration itself case Options::NoOp: break; case Options::RemoveTabFromGroupOp: if (c->untab(c->geometry().translated(cascadeOffset(c))) && options->focusPolicyIsReasonable()) takeActivity(c, ActivityFocus | ActivityRaise); break; case Options::ActivateNextTabOp: if (c->tabGroup()) c->tabGroup()->activateNext(); break; case Options::ActivatePreviousTabOp: if (c->tabGroup()) c->tabGroup()->activatePrev(); break; case Options::CloseTabGroupOp: c->tabGroup()->closeAll(); break; } } /** * Called by the decoration in the new API to determine what buttons the user has configured for * window tab dragging and the operations menu. */ Options::WindowOperation Client::mouseButtonToWindowOperation(Qt::MouseButtons button) { Options::MouseCommand com = Options::MouseNothing; bool active = isActive(); if (!wantsInput()) // we cannot be active, use it anyway active = true; if (button == Qt::LeftButton) com = active ? options->commandActiveTitlebar1() : options->commandInactiveTitlebar1(); else if (button == Qt::MidButton) com = active ? options->commandActiveTitlebar2() : options->commandInactiveTitlebar2(); else if (button == Qt::RightButton) com = active ? options->commandActiveTitlebar3() : options->commandInactiveTitlebar3(); // TODO: Complete the list if (com == Options::MouseDragTab) return Options::TabDragOp; if (com == Options::MouseOperationsMenu) return Options::OperationsOp; return Options::NoOp; } /*! Performs a mouse command on this client (see options.h) */ bool Client::performMouseCommand(Options::MouseCommand command, const QPoint &globalPos) { bool replay = false; switch(command) { case Options::MouseShade : toggleShade(); cancelShadeHoverTimer(); break; case Options::MouseSetShade: setShade(ShadeNormal); cancelShadeHoverTimer(); break; case Options::MouseUnsetShade: setShade(ShadeNone); cancelShadeHoverTimer(); break; default: return AbstractClient::performMouseCommand(command, globalPos); } return replay; } void Workspace::slotActivateAttentionWindow() { if (attention_chain.count() > 0) activateClient(attention_chain.first()); } static uint senderValue(QObject *sender) { QAction *act = qobject_cast(sender); bool ok = false; uint i = -1; if (act) i = act->data().toUInt(&ok); if (ok) return i; return -1; } #define USABLE_ACTIVE_CLIENT (active_client && !(active_client->isDesktop() || active_client->isDock())) void Workspace::slotWindowToDesktop(uint i) { if (USABLE_ACTIVE_CLIENT) { if (i < 1) return; if (i >= 1 && i <= VirtualDesktopManager::self()->count()) sendClientToDesktop(active_client, i, true); } } static bool screenSwitchImpossible() { if (!screens()->isCurrentFollowsMouse()) return false; QStringList args; args << QStringLiteral("--passivepopup") << i18n("The window manager is configured to consider the screen with the mouse on it as active one.\n" "Therefore it is not possible to switch to a screen explicitly.") << QStringLiteral("20"); KProcess::startDetached(QStringLiteral("kdialog"), args); return true; } void Workspace::slotSwitchToScreen() { if (screenSwitchImpossible()) return; const int i = senderValue(sender()); if (i > -1) setCurrentScreen(i); } void Workspace::slotSwitchToNextScreen() { if (screenSwitchImpossible()) return; setCurrentScreen((screens()->current() + 1) % screens()->count()); } void Workspace::slotSwitchToPrevScreen() { if (screenSwitchImpossible()) return; setCurrentScreen((screens()->current() + screens()->count() - 1) % screens()->count()); } void Workspace::slotWindowToScreen() { if (USABLE_ACTIVE_CLIENT) { const int i = senderValue(sender()); if (i < 0) return; if (i >= 0 && i <= screens()->count()) { sendClientToScreen(active_client, i); } } } void Workspace::slotWindowToNextScreen() { if (USABLE_ACTIVE_CLIENT) sendClientToScreen(active_client, (active_client->screen() + 1) % screens()->count()); } void Workspace::slotWindowToPrevScreen() { if (USABLE_ACTIVE_CLIENT) sendClientToScreen(active_client, (active_client->screen() + screens()->count() - 1) % screens()->count()); } /*! Maximizes the popup client */ void Workspace::slotWindowMaximize() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::MaximizeOp); } /*! Maximizes the popup client vertically */ void Workspace::slotWindowMaximizeVertical() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::VMaximizeOp); } /*! Maximizes the popup client horiozontally */ void Workspace::slotWindowMaximizeHorizontal() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::HMaximizeOp); } /*! Minimizes the popup client */ void Workspace::slotWindowMinimize() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::MinimizeOp); } /*! Shades/unshades the popup client respectively */ void Workspace::slotWindowShade() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::ShadeOp); } /*! Raises the popup client */ void Workspace::slotWindowRaise() { if (USABLE_ACTIVE_CLIENT) raiseClient(active_client); } /*! Lowers the popup client */ void Workspace::slotWindowLower() { if (USABLE_ACTIVE_CLIENT) { lowerClient(active_client); // As this most likely makes the window no longer visible change the // keyboard focus to the next available window. //activateNextClient( c ); // Doesn't work when we lower a child window if (active_client->isActive() && options->focusPolicyIsReasonable()) { if (options->isNextFocusPrefersMouse()) { AbstractClient *next = clientUnderMouse(active_client->screen()); if (next && next != active_client) requestFocus(next, false); } else { activateClient(topClientOnDesktop(VirtualDesktopManager::self()->current(), -1)); } } } } /*! Does a toggle-raise-and-lower on the popup client; */ void Workspace::slotWindowRaiseOrLower() { if (USABLE_ACTIVE_CLIENT) raiseOrLowerClient(active_client); } void Workspace::slotWindowOnAllDesktops() { if (USABLE_ACTIVE_CLIENT) active_client->setOnAllDesktops(!active_client->isOnAllDesktops()); } void Workspace::slotWindowFullScreen() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::FullScreenOp); } void Workspace::slotWindowNoBorder() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::NoBorderOp); } void Workspace::slotWindowAbove() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::KeepAboveOp); } void Workspace::slotWindowBelow() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::KeepBelowOp); } void Workspace::slotSetupWindowShortcut() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::SetupWindowShortcutOp); } /*! Toggles show desktop */ void Workspace::slotToggleShowDesktop() { setShowingDesktop(!showingDesktop()); } template void windowToDesktop(AbstractClient *c) { VirtualDesktopManager *vds = VirtualDesktopManager::self(); Workspace *ws = Workspace::self(); Direction functor; // TODO: why is options->isRollOverDesktops() not honored? const auto desktop = functor(nullptr, true); if (c && !c->isDesktop() && !c->isDock()) { ws->setClientIsMoving(c); vds->setCurrent(desktop); ws->setClientIsMoving(NULL); } } /*! Move window to next desktop */ void Workspace::slotWindowToNextDesktop() { if (USABLE_ACTIVE_CLIENT) windowToNextDesktop(active_client); } void Workspace::windowToNextDesktop(AbstractClient* c) { windowToDesktop(c); } /*! Move window to previous desktop */ void Workspace::slotWindowToPreviousDesktop() { if (USABLE_ACTIVE_CLIENT) windowToPreviousDesktop(active_client); } void Workspace::windowToPreviousDesktop(AbstractClient* c) { windowToDesktop(c); } template void activeClientToDesktop() { VirtualDesktopManager *vds = VirtualDesktopManager::self(); Workspace *ws = Workspace::self(); const int current = vds->current(); Direction functor; const int d = functor(current, options->isRollOverDesktops()); if (d == current) { return; } ws->setClientIsMoving(ws->activeClient()); vds->setCurrent(d); ws->setClientIsMoving(NULL); } void Workspace::slotWindowToDesktopRight() { if (USABLE_ACTIVE_CLIENT) { activeClientToDesktop(); } } void Workspace::slotWindowToDesktopLeft() { if (USABLE_ACTIVE_CLIENT) { activeClientToDesktop(); } } void Workspace::slotWindowToDesktopUp() { if (USABLE_ACTIVE_CLIENT) { activeClientToDesktop(); } } void Workspace::slotWindowToDesktopDown() { if (USABLE_ACTIVE_CLIENT) { activeClientToDesktop(); } } void Workspace::slotActivateNextTab() { if (active_client && active_client->tabGroup()) active_client->tabGroup()->activateNext(); } void Workspace::slotActivatePrevTab() { if (active_client && active_client->tabGroup()) active_client->tabGroup()->activatePrev(); } void Workspace::slotUntab() { if (active_client) active_client->untab(active_client->geometry().translated(cascadeOffset(active_client))); } /*! Kill Window feature, similar to xkill */ void Workspace::slotKillWindow() { if (m_windowKiller.isNull()) { m_windowKiller.reset(new KillWindow()); } m_windowKiller->start(); } /*! Switches to the nearest window in given direction */ void Workspace::switchWindow(Direction direction) { if (!active_client) return; AbstractClient *c = active_client; int desktopNumber = c->isOnAllDesktops() ? VirtualDesktopManager::self()->current() : c->desktop(); // Centre of the active window QPoint curPos(c->pos().x() + c->geometry().width() / 2, c->pos().y() + c->geometry().height() / 2); if (!switchWindow(c, direction, curPos, desktopNumber)) { auto opposite = [&] { switch(direction) { case DirectionNorth: return QPoint(curPos.x(), screens()->geometry().height()); case DirectionSouth: return QPoint(curPos.x(), 0); case DirectionEast: return QPoint(0, curPos.y()); case DirectionWest: return QPoint(screens()->geometry().width(), curPos.y()); default: Q_UNREACHABLE(); } }; switchWindow(c, direction, opposite(), desktopNumber); } } bool Workspace::switchWindow(AbstractClient *c, Direction direction, QPoint curPos, int d) { AbstractClient *switchTo = nullptr; int bestScore = 0; ToplevelList clist = stackingOrder(); for (ToplevelList::Iterator i = clist.begin(); i != clist.end(); ++i) { auto client = qobject_cast(*i); if (!client) { continue; } if (client->wantsTabFocus() && *i != c && client->isOnDesktop(d) && !client->isMinimized() && (*i)->isOnCurrentActivity()) { // Centre of the other window QPoint other(client->pos().x() + client->geometry().width() / 2, client->pos().y() + client->geometry().height() / 2); int distance; int offset; switch(direction) { case DirectionNorth: distance = curPos.y() - other.y(); offset = qAbs(other.x() - curPos.x()); break; case DirectionEast: distance = other.x() - curPos.x(); offset = qAbs(other.y() - curPos.y()); break; case DirectionSouth: distance = other.y() - curPos.y(); offset = qAbs(other.x() - curPos.x()); break; case DirectionWest: distance = curPos.x() - other.x(); offset = qAbs(other.y() - curPos.y()); break; default: distance = -1; offset = -1; } if (distance > 0) { // Inverse score int score = distance + offset + ((offset * offset) / distance); if (score < bestScore || !switchTo) { switchTo = client; bestScore = score; } } } } if (switchTo) { if (switchTo->tabGroup()) switchTo = switchTo->tabGroup()->current(); activateClient(switchTo); } return switchTo; } /*! Shows the window operations popup menu for the activeClient() */ void Workspace::slotWindowOperations() { if (!active_client) return; QPoint pos = active_client->pos() + active_client->clientPos(); showWindowMenu(QRect(pos, pos), active_client); } void Workspace::showWindowMenu(const QRect &pos, AbstractClient* cl) { m_userActionsMenu->show(pos, cl); } void Workspace::showApplicationMenu(const QRect &pos, AbstractClient *c, int actionId) { ApplicationMenu::self()->showApplicationMenu(c->geometry().topLeft() + pos.bottomLeft(), c, actionId); } /*! Closes the popup client */ void Workspace::slotWindowClose() { // TODO: why? // if ( tab_box->isVisible()) // return; if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::CloseOp); } /*! Starts keyboard move mode for the popup client */ void Workspace::slotWindowMove() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::UnrestrictedMoveOp); } /*! Starts keyboard resize mode for the popup client */ void Workspace::slotWindowResize() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::UnrestrictedResizeOp); } void Workspace::slotInvertScreen() { using namespace Xcb::RandR; bool succeeded = false; if (Xcb::Extensions::self()->isRandrAvailable()) { ScreenResources res((active_client && active_client->window() != XCB_WINDOW_NONE) ? active_client->window() : rootWindow()); if (!res.isNull()) { for (int j = 0; j < res->num_crtcs; ++j) { auto crtc = res.crtcs()[j]; CrtcGamma gamma(crtc); if (gamma.isNull()) { continue; } if (gamma->size) { qCDebug(KWIN_CORE) << "inverting screen using xcb_randr_set_crtc_gamma"; const int half = gamma->size / 2 + 1; uint16_t *red = gamma.red(); uint16_t *green = gamma.green(); uint16_t *blue = gamma.blue(); for (int i = 0; i < half; ++i) { auto invert = [&gamma, i](uint16_t *ramp) { qSwap(ramp[i], ramp[gamma->size - 1 - i]); }; invert(red); invert(green); invert(blue); } xcb_randr_set_crtc_gamma(connection(), crtc, gamma->size, red, green, blue); succeeded = true; } } } } if (succeeded) return; //BEGIN effect plugin inversion - atm only works with OpenGL and has an overhead to it if (effects) { if (Effect *inverter = static_cast(effects)->provides(Effect::ScreenInversion)) { qCDebug(KWIN_CORE) << "inverting screen using Effect plugin"; QMetaObject::invokeMethod(inverter, "toggleScreenInversion", Qt::DirectConnection); } } if (!succeeded) qCDebug(KWIN_CORE) << "sorry - neither Xrandr, nor XF86VidModeSetGammaRamp worked and there's no inversion supplying effect plugin either"; } #undef USABLE_ACTIVE_CLIENT void AbstractClient::setShortcut(const QString& _cut) { QString cut = rules()->checkShortcut(_cut); auto updateShortcut = [this](const QKeySequence &cut = QKeySequence()) { if (_shortcut == cut) return; _shortcut = cut; setShortcutInternal(); }; if (cut.isEmpty()) { updateShortcut(); return; } if (cut == shortcut().toString()) { return; // no change } // Format: // base+(abcdef)base+(abcdef) // E.g. Alt+Ctrl+(ABCDEF);Meta+X,Meta+(ABCDEF) if (!cut.contains(QLatin1Char('(')) && !cut.contains(QLatin1Char(')')) && !cut.contains(QLatin1String(" - "))) { if (workspace()->shortcutAvailable(cut, this)) updateShortcut(QKeySequence(cut)); else updateShortcut(); return; } QList< QKeySequence > keys; QStringList groups = cut.split(QStringLiteral(" - ")); for (QStringList::ConstIterator it = groups.constBegin(); it != groups.constEnd(); ++it) { QRegExp reg(QStringLiteral("(.*\\+)\\((.*)\\)")); if (reg.indexIn(*it) > -1) { QString base = reg.cap(1); QString list = reg.cap(2); for (int i = 0; i < list.length(); ++i) { QKeySequence c(base + list[ i ]); if (!c.isEmpty()) keys.append(c); } } else { // regexp doesn't match, so it should be a normal shortcut QKeySequence c(*it); if (!c.isEmpty()) { keys.append(c); } } } for (auto it = keys.constBegin(); it != keys.constEnd(); ++it) { if (_shortcut == *it) // current one is in the list return; } for (auto it = keys.constBegin(); it != keys.constEnd(); ++it) { if (workspace()->shortcutAvailable(*it, this)) { updateShortcut(*it); return; } } updateShortcut(); } void AbstractClient::setShortcutInternal() { workspace()->clientShortcutUpdated(this); } void Client::setShortcutInternal() { updateCaption(); #if 0 workspace()->clientShortcutUpdated(this); #else // Workaround for kwin<->kglobalaccel deadlock, when KWin has X grab and the kded // kglobalaccel module tries to create the key grab. KWin should preferably grab // they keys itself anyway :(. - QTimer::singleShot(0, this, SLOT(delayedSetShortcut())); + QTimer::singleShot(0, this, std::bind(&Workspace::clientShortcutUpdated, workspace(), this)); #endif } -void Client::delayedSetShortcut() -{ - workspace()->clientShortcutUpdated(this); -} - bool Workspace::shortcutAvailable(const QKeySequence &cut, AbstractClient* ignore) const { if (ignore && cut == ignore->shortcut()) return true; if (!KGlobalAccel::getGlobalShortcutsByKey(cut).isEmpty()) { return false; } for (auto it = m_allClients.constBegin(); it != m_allClients.constEnd(); ++it) { if ((*it) != ignore && (*it)->shortcut() == cut) return false; } return true; } } // namespace