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

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 1999, 2000 Matthias Ettrich <ettrich@kde.org>
0006     SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org>
0007     SPDX-FileCopyrightText: 2022 Natalie Clarius <natalie_clarius@yahoo.de>
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 
0012 /*
0013 
0014  This file contains things relevant to direct user actions, such as
0015  responses to global keyboard shortcuts, or selecting actions
0016  from the window operations menu.
0017 
0018 */
0019 
0020 ///////////////////////////////////////////////////////////////////////////////
0021 // NOTE: if you change the menu, keep
0022 //       plasma-desktop/applets/taskmanager/package/contents/ui/ContextMenu.qml
0023 //       in sync
0024 //////////////////////////////////////////////////////////////////////////////
0025 
0026 #include <config-kwin.h>
0027 
0028 #include "core/output.h"
0029 #include "cursor.h"
0030 #include "input.h"
0031 #include "options.h"
0032 #include "scripting/scripting.h"
0033 #include "useractions.h"
0034 #include "virtualdesktops.h"
0035 #include "workspace.h"
0036 #include "x11window.h"
0037 
0038 #if KWIN_BUILD_ACTIVITIES
0039 #include "activities.h"
0040 #include <PlasmaActivities/Info>
0041 #endif
0042 #include "appmenu.h"
0043 
0044 #include <KProcess>
0045 
0046 #include <QAction>
0047 #include <QCheckBox>
0048 #include <QPushButton>
0049 
0050 #include <KGlobalAccel>
0051 #include <KLazyLocalizedString>
0052 #include <KLocalizedString>
0053 #include <QAction>
0054 #include <QActionGroup>
0055 #include <QMenu>
0056 #include <QRegularExpression>
0057 #include <kauthorized.h>
0058 #include <kconfig.h>
0059 
0060 #include "killwindow.h"
0061 #if KWIN_BUILD_TABBOX
0062 #include "tabbox/tabbox.h"
0063 #endif
0064 
0065 namespace KWin
0066 {
0067 
0068 UserActionsMenu::UserActionsMenu(QObject *parent)
0069     : QObject(parent)
0070     , m_menu(nullptr)
0071     , m_desktopMenu(nullptr)
0072     , m_multipleDesktopsMenu(nullptr)
0073     , m_screenMenu(nullptr)
0074     , m_activityMenu(nullptr)
0075     , m_scriptsMenu(nullptr)
0076     , m_resizeOperation(nullptr)
0077     , m_moveOperation(nullptr)
0078     , m_maximizeOperation(nullptr)
0079     , m_shadeOperation(nullptr)
0080     , m_keepAboveOperation(nullptr)
0081     , m_keepBelowOperation(nullptr)
0082     , m_fullScreenOperation(nullptr)
0083     , m_noBorderOperation(nullptr)
0084     , m_minimizeOperation(nullptr)
0085     , m_closeOperation(nullptr)
0086     , m_shortcutOperation(nullptr)
0087 {
0088 }
0089 
0090 UserActionsMenu::~UserActionsMenu()
0091 {
0092     discard();
0093 }
0094 
0095 bool UserActionsMenu::isShown() const
0096 {
0097     return m_menu && m_menu->isVisible();
0098 }
0099 
0100 bool UserActionsMenu::hasWindow() const
0101 {
0102     return m_window && isShown();
0103 }
0104 
0105 void UserActionsMenu::close()
0106 {
0107     if (!m_menu) {
0108         return;
0109     }
0110     m_menu->close();
0111 }
0112 
0113 bool UserActionsMenu::isMenuWindow(const Window *window) const
0114 {
0115     return window && window == m_window;
0116 }
0117 
0118 void UserActionsMenu::show(const QRect &pos, Window *window)
0119 {
0120     Q_ASSERT(window);
0121     QPointer<Window> windowPtr(window);
0122     // Presumably window will never be nullptr,
0123     // but play it safe and make sure not to crash.
0124     if (windowPtr.isNull()) {
0125         return;
0126     }
0127     if (isShown()) { // recursion
0128         return;
0129     }
0130     if (windowPtr->isDesktop() || windowPtr->isDock()) {
0131         return;
0132     }
0133     if (!KAuthorized::authorizeAction(QStringLiteral("kwin_rmb"))) {
0134         return;
0135     }
0136     m_window = windowPtr;
0137     init();
0138     m_menu->popup(pos.bottomLeft());
0139 }
0140 
0141 void UserActionsMenu::grabInput()
0142 {
0143     m_menu->windowHandle()->setMouseGrabEnabled(true);
0144     m_menu->windowHandle()->setKeyboardGrabEnabled(true);
0145 }
0146 
0147 void UserActionsMenu::helperDialog(const QString &message)
0148 {
0149     QStringList args;
0150     QString type;
0151     auto shortcut = [](const QString &name) {
0152         QAction *action = Workspace::self()->findChild<QAction *>(name);
0153         Q_ASSERT(action != nullptr);
0154         const auto shortcuts = KGlobalAccel::self()->shortcut(action);
0155         return QStringLiteral("%1 (%2)").arg(action->text(), shortcuts.isEmpty() ? QString() : shortcuts.first().toString(QKeySequence::NativeText));
0156     };
0157     if (message == QStringLiteral("noborderaltf3")) {
0158         args << QStringLiteral("--msgbox") << i18n("You have selected to show a window without its border.\n"
0159                                                    "Without the border, you will not be able to enable the border "
0160                                                    "again using the mouse: use the window operations menu instead, "
0161                                                    "activated using the %1 keyboard shortcut.",
0162                                                    shortcut(QStringLiteral("Window Operations Menu")));
0163         type = QStringLiteral("altf3warning");
0164     } else if (message == QLatin1String("fullscreenaltf3")) {
0165         args << QStringLiteral("--msgbox") << i18n("You have selected to show a window in fullscreen mode.\n"
0166                                                    "If the application itself does not have an option to turn the fullscreen "
0167                                                    "mode off you will not be able to disable it "
0168                                                    "again using the mouse: use the window operations menu instead, "
0169                                                    "activated using the %1 keyboard shortcut.",
0170                                                    shortcut(QStringLiteral("Window Operations Menu")));
0171         type = QStringLiteral("altf3warning");
0172     } else {
0173         Q_UNREACHABLE();
0174     }
0175     if (!type.isEmpty()) {
0176         KConfig cfg(QStringLiteral("kwin_dialogsrc"));
0177         KConfigGroup cg(&cfg, QStringLiteral("Notification Messages")); // Depends on KMessageBox
0178         if (!cg.readEntry(type, true)) {
0179             return;
0180         }
0181         args << QStringLiteral("--dontagain") << QLatin1String("kwin_dialogsrc:") + type;
0182     }
0183     KProcess::startDetached(QStringLiteral("kdialog"), args);
0184 }
0185 
0186 void UserActionsMenu::init()
0187 {
0188     if (m_menu) {
0189         return;
0190     }
0191     m_menu = new QMenu;
0192     connect(m_menu, &QMenu::aboutToShow, this, &UserActionsMenu::menuAboutToShow);
0193 
0194     // the toplevel menu gets closed before a submenu's action is invoked
0195     connect(m_menu, &QMenu::aboutToHide, this, &UserActionsMenu::menuAboutToHide, Qt::QueuedConnection);
0196     connect(m_menu, &QMenu::triggered, this, &UserActionsMenu::slotWindowOperation);
0197 
0198     QMenu *advancedMenu = new QMenu(m_menu);
0199     connect(advancedMenu, &QMenu::aboutToShow, this, [this, advancedMenu]() {
0200         if (m_window) {
0201             advancedMenu->setPalette(m_window->palette());
0202         }
0203     });
0204 
0205     auto setShortcut = [](QAction *action, const QString &actionName) {
0206         const auto shortcuts = KGlobalAccel::self()->shortcut(Workspace::self()->findChild<QAction *>(actionName));
0207         if (!shortcuts.isEmpty()) {
0208             action->setShortcut(shortcuts.first());
0209         }
0210     };
0211 
0212     m_moveOperation = advancedMenu->addAction(i18n("&Move"));
0213     m_moveOperation->setIcon(QIcon::fromTheme(QStringLiteral("transform-move")));
0214     setShortcut(m_moveOperation, QStringLiteral("Window Move"));
0215     m_moveOperation->setData(Options::UnrestrictedMoveOp);
0216 
0217     m_resizeOperation = advancedMenu->addAction(i18n("&Resize"));
0218     m_resizeOperation->setIcon(QIcon::fromTheme(QStringLiteral("transform-scale")));
0219     setShortcut(m_resizeOperation, QStringLiteral("Window Resize"));
0220     m_resizeOperation->setData(Options::ResizeOp);
0221 
0222     m_keepAboveOperation = advancedMenu->addAction(i18n("Keep &Above Others"));
0223     m_keepAboveOperation->setIcon(QIcon::fromTheme(QStringLiteral("window-keep-above")));
0224     setShortcut(m_keepAboveOperation, QStringLiteral("Window Above Other Windows"));
0225     m_keepAboveOperation->setCheckable(true);
0226     m_keepAboveOperation->setData(Options::KeepAboveOp);
0227 
0228     m_keepBelowOperation = advancedMenu->addAction(i18n("Keep &Below Others"));
0229     m_keepBelowOperation->setIcon(QIcon::fromTheme(QStringLiteral("window-keep-below")));
0230     setShortcut(m_keepBelowOperation, QStringLiteral("Window Below Other Windows"));
0231     m_keepBelowOperation->setCheckable(true);
0232     m_keepBelowOperation->setData(Options::KeepBelowOp);
0233 
0234     m_fullScreenOperation = advancedMenu->addAction(i18n("&Fullscreen"));
0235     m_fullScreenOperation->setIcon(QIcon::fromTheme(QStringLiteral("view-fullscreen")));
0236     setShortcut(m_fullScreenOperation, QStringLiteral("Window Fullscreen"));
0237     m_fullScreenOperation->setCheckable(true);
0238     m_fullScreenOperation->setData(Options::FullScreenOp);
0239 
0240     m_shadeOperation = advancedMenu->addAction(i18n("&Shade"));
0241     m_shadeOperation->setIcon(QIcon::fromTheme(QStringLiteral("window-shade")));
0242     setShortcut(m_shadeOperation, QStringLiteral("Window Shade"));
0243     m_shadeOperation->setCheckable(true);
0244     m_shadeOperation->setData(Options::ShadeOp);
0245 
0246     m_noBorderOperation = advancedMenu->addAction(i18n("&No Titlebar and Frame"));
0247     m_noBorderOperation->setIcon(QIcon::fromTheme(QStringLiteral("edit-none-border")));
0248     setShortcut(m_noBorderOperation, QStringLiteral("Window No Border"));
0249     m_noBorderOperation->setCheckable(true);
0250     m_noBorderOperation->setData(Options::NoBorderOp);
0251 
0252     advancedMenu->addSeparator();
0253 
0254     m_shortcutOperation = advancedMenu->addAction(i18n("Set Window Short&cut…"));
0255     m_shortcutOperation->setIcon(QIcon::fromTheme(QStringLiteral("configure-shortcuts")));
0256     setShortcut(m_shortcutOperation, QStringLiteral("Setup Window Shortcut"));
0257     m_shortcutOperation->setData(Options::SetupWindowShortcutOp);
0258 
0259 #if KWIN_BUILD_KCMS
0260     QAction *action = advancedMenu->addAction(i18n("Configure Special &Window Settings…"));
0261     action->setIcon(QIcon::fromTheme(QStringLiteral("preferences-system-windows-actions")));
0262     action->setData(Options::WindowRulesOp);
0263     m_rulesOperation = action;
0264 
0265     action = advancedMenu->addAction(i18n("Configure S&pecial Application Settings…"));
0266     action->setIcon(QIcon::fromTheme(QStringLiteral("preferences-system-windows-actions")));
0267     action->setData(Options::ApplicationRulesOp);
0268     m_applicationRulesOperation = action;
0269 #endif
0270 
0271     m_maximizeOperation = m_menu->addAction(i18n("Ma&ximize"));
0272     m_maximizeOperation->setIcon(QIcon::fromTheme(QStringLiteral("window-maximize")));
0273     setShortcut(m_maximizeOperation, QStringLiteral("Window Maximize"));
0274     m_maximizeOperation->setCheckable(true);
0275     m_maximizeOperation->setData(Options::MaximizeOp);
0276 
0277     m_minimizeOperation = m_menu->addAction(i18n("Mi&nimize"));
0278     m_minimizeOperation->setIcon(QIcon::fromTheme(QStringLiteral("window-minimize")));
0279     setShortcut(m_minimizeOperation, QStringLiteral("Window Minimize"));
0280     m_minimizeOperation->setData(Options::MinimizeOp);
0281 
0282     QAction *overflowAction = m_menu->addMenu(advancedMenu);
0283     overflowAction->setText(i18n("&More Actions"));
0284     overflowAction->setIcon(QIcon::fromTheme(QStringLiteral("overflow-menu")));
0285 
0286     m_menu->addSeparator();
0287 
0288     m_closeOperation = m_menu->addAction(i18n("&Close"));
0289     m_closeOperation->setIcon(QIcon::fromTheme(QStringLiteral("window-close")));
0290     setShortcut(m_closeOperation, QStringLiteral("Window Close"));
0291     m_closeOperation->setData(Options::CloseOp);
0292 }
0293 
0294 void UserActionsMenu::discard()
0295 {
0296     delete m_menu;
0297     m_menu = nullptr;
0298     m_desktopMenu = nullptr;
0299     m_multipleDesktopsMenu = nullptr;
0300     m_screenMenu = nullptr;
0301     m_activityMenu = nullptr;
0302     m_scriptsMenu = nullptr;
0303 }
0304 
0305 void UserActionsMenu::menuAboutToShow()
0306 {
0307     if (m_window.isNull() || !m_menu) {
0308         return;
0309     }
0310 
0311     m_window->blockActivityUpdates(true);
0312 
0313     if (VirtualDesktopManager::self()->count() == 1) {
0314         delete m_desktopMenu;
0315         m_desktopMenu = nullptr;
0316         delete m_multipleDesktopsMenu;
0317         m_multipleDesktopsMenu = nullptr;
0318     } else {
0319         initDesktopPopup();
0320     }
0321     if (workspace()->outputs().count() == 1 || (!m_window->isMovable() && !m_window->isMovableAcrossScreens())) {
0322         delete m_screenMenu;
0323         m_screenMenu = nullptr;
0324     } else {
0325         initScreenPopup();
0326     }
0327 
0328     m_menu->setPalette(m_window->palette());
0329     m_resizeOperation->setEnabled(m_window->isResizable());
0330     m_moveOperation->setEnabled(m_window->isMovableAcrossScreens());
0331     m_maximizeOperation->setEnabled(m_window->isMaximizable());
0332     m_maximizeOperation->setChecked(m_window->maximizeMode() == MaximizeFull);
0333     m_shadeOperation->setEnabled(m_window->isShadeable());
0334     m_shadeOperation->setChecked(m_window->shadeMode() != ShadeNone);
0335     m_keepAboveOperation->setChecked(m_window->keepAbove());
0336     m_keepBelowOperation->setChecked(m_window->keepBelow());
0337     m_fullScreenOperation->setEnabled(m_window->isFullScreenable());
0338     m_fullScreenOperation->setChecked(m_window->isFullScreen());
0339     m_noBorderOperation->setEnabled(m_window->userCanSetNoBorder());
0340     m_noBorderOperation->setChecked(m_window->noBorder());
0341     m_minimizeOperation->setEnabled(m_window->isMinimizable());
0342     m_closeOperation->setEnabled(m_window->isCloseable());
0343     m_shortcutOperation->setEnabled(m_window->rules()->checkShortcut(QString()).isNull());
0344 
0345     // drop the existing scripts menu
0346     delete m_scriptsMenu;
0347     m_scriptsMenu = nullptr;
0348     // ask scripts whether they want to add entries for the given window
0349     QList<QAction *> scriptActions = Scripting::self()->actionsForUserActionMenu(m_window.data(), m_scriptsMenu);
0350     if (!scriptActions.isEmpty()) {
0351         m_scriptsMenu = new QMenu(m_menu);
0352         m_scriptsMenu->setPalette(m_window->palette());
0353         m_scriptsMenu->addActions(scriptActions);
0354 
0355         QAction *action = m_scriptsMenu->menuAction();
0356         // set it as the first item after desktop
0357         m_menu->insertAction(m_closeOperation, action);
0358         action->setText(i18n("&Extensions"));
0359     }
0360 
0361     if (m_rulesOperation) {
0362         m_rulesOperation->setEnabled(m_window->supportsWindowRules());
0363     }
0364     if (m_applicationRulesOperation) {
0365         m_applicationRulesOperation->setEnabled(m_window->supportsWindowRules());
0366     }
0367 
0368     showHideActivityMenu();
0369 }
0370 
0371 void UserActionsMenu::menuAboutToHide()
0372 {
0373     if (m_window) {
0374         m_window->blockActivityUpdates(false);
0375         m_window.clear();
0376     }
0377 }
0378 
0379 void UserActionsMenu::showHideActivityMenu()
0380 {
0381 #if KWIN_BUILD_ACTIVITIES
0382     if (!Workspace::self()->activities()) {
0383         return;
0384     }
0385     const QStringList &openActivities_ = Workspace::self()->activities()->running();
0386     qCDebug(KWIN_CORE) << "activities:" << openActivities_.size();
0387     if (openActivities_.size() < 2) {
0388         delete m_activityMenu;
0389         m_activityMenu = nullptr;
0390     } else {
0391         initActivityPopup();
0392     }
0393 #endif
0394 }
0395 
0396 void UserActionsMenu::initDesktopPopup()
0397 {
0398     if (kwinApp()->operationMode() == Application::OperationModeWaylandOnly || kwinApp()->operationMode() == Application::OperationModeXwayland) {
0399         if (m_multipleDesktopsMenu) {
0400             return;
0401         }
0402 
0403         m_multipleDesktopsMenu = new QMenu(m_menu);
0404         connect(m_multipleDesktopsMenu, &QMenu::aboutToShow, this, &UserActionsMenu::multipleDesktopsPopupAboutToShow);
0405 
0406         QAction *action = m_multipleDesktopsMenu->menuAction();
0407         // set it as the first item
0408         m_menu->insertAction(m_maximizeOperation, action);
0409         action->setText(i18n("&Desktops"));
0410         action->setIcon(QIcon::fromTheme(QStringLiteral("virtual-desktops")));
0411 
0412     } else {
0413         if (m_desktopMenu) {
0414             return;
0415         }
0416 
0417         m_desktopMenu = new QMenu(m_menu);
0418         connect(m_desktopMenu, &QMenu::aboutToShow, this, &UserActionsMenu::desktopPopupAboutToShow);
0419 
0420         QAction *action = m_desktopMenu->menuAction();
0421         // set it as the first item
0422         m_menu->insertAction(m_maximizeOperation, action);
0423         action->setText(i18n("Move to &Desktop"));
0424         action->setIcon(QIcon::fromTheme(QStringLiteral("virtual-desktops")));
0425     }
0426 }
0427 
0428 void UserActionsMenu::initScreenPopup()
0429 {
0430     if (m_screenMenu) {
0431         return;
0432     }
0433 
0434     m_screenMenu = new QMenu(m_menu);
0435     connect(m_screenMenu, &QMenu::aboutToShow, this, &UserActionsMenu::screenPopupAboutToShow);
0436 
0437     QAction *action = m_screenMenu->menuAction();
0438     // set it as the first item after desktop
0439     m_menu->insertAction(m_activityMenu ? m_activityMenu->menuAction() : m_minimizeOperation, action);
0440     action->setText(i18n("Move to &Screen"));
0441     action->setIcon(QIcon::fromTheme(QStringLiteral("computer")));
0442 }
0443 
0444 void UserActionsMenu::initActivityPopup()
0445 {
0446     if (m_activityMenu) {
0447         return;
0448     }
0449 
0450     m_activityMenu = new QMenu(m_menu);
0451     connect(m_activityMenu, &QMenu::aboutToShow, this, &UserActionsMenu::activityPopupAboutToShow);
0452 
0453     QAction *action = m_activityMenu->menuAction();
0454     // set it as the first item
0455     m_menu->insertAction(m_maximizeOperation, action);
0456     action->setText(i18n("Show in &Activities"));
0457     action->setIcon(QIcon::fromTheme(QStringLiteral("activities")));
0458 }
0459 
0460 void UserActionsMenu::desktopPopupAboutToShow()
0461 {
0462     if (!m_desktopMenu) {
0463         return;
0464     }
0465     const VirtualDesktopManager *vds = VirtualDesktopManager::self();
0466 
0467     m_desktopMenu->clear();
0468     if (m_window) {
0469         m_desktopMenu->setPalette(m_window->palette());
0470     }
0471     QActionGroup *group = new QActionGroup(m_desktopMenu);
0472 
0473     QAction *action = m_desktopMenu->addAction(i18n("Move &To Current Desktop"));
0474     action->setEnabled(m_window && (m_window->isOnAllDesktops() || !m_window->isOnDesktop(vds->currentDesktop())));
0475     connect(action, &QAction::triggered, this, [this]() {
0476         if (!m_window) {
0477             return;
0478         }
0479         VirtualDesktopManager *vds = VirtualDesktopManager::self();
0480         workspace()->sendWindowToDesktops(m_window, {vds->currentDesktop()}, false);
0481     });
0482 
0483     action = m_desktopMenu->addAction(i18n("&All Desktops"));
0484     connect(action, &QAction::triggered, this, [this]() {
0485         if (m_window) {
0486             m_window->setOnAllDesktops(!m_window->isOnAllDesktops());
0487         }
0488     });
0489     action->setCheckable(true);
0490     if (m_window && m_window->isOnAllDesktops()) {
0491         action->setChecked(true);
0492     }
0493     group->addAction(action);
0494 
0495     m_desktopMenu->addSeparator();
0496 
0497     const uint BASE = 10;
0498 
0499     const auto desktops = vds->desktops();
0500     for (VirtualDesktop *desktop : desktops) {
0501         const uint legacyId = desktop->x11DesktopNumber();
0502 
0503         QString basic_name(QStringLiteral("%1  %2"));
0504         if (legacyId < BASE) {
0505             basic_name.prepend(QLatin1Char('&'));
0506         }
0507         action = m_desktopMenu->addAction(basic_name.arg(legacyId).arg(desktop->name().replace(QLatin1Char('&'), QStringLiteral("&&"))));
0508         connect(action, &QAction::triggered, this, [this, desktop]() {
0509             if (m_window) {
0510                 workspace()->sendWindowToDesktops(m_window, {desktop}, false);
0511             }
0512         });
0513         action->setCheckable(true);
0514         group->addAction(action);
0515 
0516         if (m_window && !m_window->isOnAllDesktops() && m_window->isOnDesktop(desktop)) {
0517             action->setChecked(true);
0518         }
0519     }
0520 
0521     m_desktopMenu->addSeparator();
0522 
0523     action = m_desktopMenu->addAction(i18nc("Create a new desktop and move the window there", "&New Desktop"));
0524     action->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
0525     connect(action, &QAction::triggered, this, [this]() {
0526         if (!m_window) {
0527             return;
0528         }
0529         VirtualDesktopManager *vds = VirtualDesktopManager::self();
0530         VirtualDesktop *desktop = vds->createVirtualDesktop(vds->count());
0531         if (desktop) {
0532             workspace()->sendWindowToDesktops(m_window, {desktop}, false);
0533         }
0534     });
0535     action->setEnabled(vds->count() < vds->maximum());
0536 }
0537 
0538 void UserActionsMenu::multipleDesktopsPopupAboutToShow()
0539 {
0540     if (!m_multipleDesktopsMenu) {
0541         return;
0542     }
0543     VirtualDesktopManager *vds = VirtualDesktopManager::self();
0544 
0545     m_multipleDesktopsMenu->clear();
0546     if (m_window) {
0547         m_multipleDesktopsMenu->setPalette(m_window->palette());
0548     }
0549 
0550     QAction *action = m_multipleDesktopsMenu->addAction(i18n("&All Desktops"));
0551     connect(action, &QAction::triggered, this, [this]() {
0552         if (m_window) {
0553             m_window->setOnAllDesktops(!m_window->isOnAllDesktops());
0554         }
0555     });
0556     action->setCheckable(true);
0557     if (m_window && m_window->isOnAllDesktops()) {
0558         action->setChecked(true);
0559     }
0560 
0561     m_multipleDesktopsMenu->addSeparator();
0562 
0563     const uint BASE = 10;
0564 
0565     const auto desktops = vds->desktops();
0566     for (VirtualDesktop *desktop : desktops) {
0567         const uint legacyId = desktop->x11DesktopNumber();
0568 
0569         QString basic_name(QStringLiteral("%1  %2"));
0570         if (legacyId < BASE) {
0571             basic_name.prepend(QLatin1Char('&'));
0572         }
0573 
0574         QAction *action = m_multipleDesktopsMenu->addAction(basic_name.arg(legacyId).arg(desktop->name().replace(QLatin1Char('&'), QStringLiteral("&&"))));
0575         connect(action, &QAction::triggered, this, [this, desktop]() {
0576             if (m_window) {
0577                 if (m_window->desktops().contains(desktop)) {
0578                     m_window->leaveDesktop(desktop);
0579                 } else {
0580                     m_window->enterDesktop(desktop);
0581                 }
0582             }
0583         });
0584         action->setCheckable(true);
0585         if (m_window && !m_window->isOnAllDesktops() && m_window->isOnDesktop(desktop)) {
0586             action->setChecked(true);
0587         }
0588     }
0589 
0590     m_multipleDesktopsMenu->addSeparator();
0591 
0592     for (VirtualDesktop *desktop : desktops) {
0593         const uint legacyId = desktop->x11DesktopNumber();
0594         QString name = i18n("Move to %1 %2", legacyId, desktop->name());
0595         QAction *action = m_multipleDesktopsMenu->addAction(name);
0596         connect(action, &QAction::triggered, this, [this, desktop]() {
0597             if (m_window) {
0598                 m_window->setDesktops({desktop});
0599             }
0600         });
0601     }
0602 
0603     m_multipleDesktopsMenu->addSeparator();
0604 
0605     bool allowNewDesktops = vds->count() < vds->maximum();
0606 
0607     action = m_multipleDesktopsMenu->addAction(i18nc("Create a new desktop and add the window to that desktop", "Add to &New Desktop"));
0608     connect(action, &QAction::triggered, this, [this, vds]() {
0609         if (!m_window) {
0610             return;
0611         }
0612         VirtualDesktop *desktop = vds->createVirtualDesktop(vds->count());
0613         if (desktop) {
0614             m_window->enterDesktop(desktop);
0615         }
0616     });
0617     action->setEnabled(allowNewDesktops);
0618 
0619     action = m_multipleDesktopsMenu->addAction(i18nc("Create a new desktop and move the window to that desktop", "Move to New Desktop"));
0620     connect(action, &QAction::triggered, this, [this, vds]() {
0621         if (!m_window) {
0622             return;
0623         }
0624         VirtualDesktop *desktop = vds->createVirtualDesktop(vds->count());
0625         if (desktop) {
0626             m_window->setDesktops({desktop});
0627         }
0628     });
0629     action->setEnabled(allowNewDesktops);
0630 }
0631 
0632 void UserActionsMenu::screenPopupAboutToShow()
0633 {
0634     if (!m_screenMenu) {
0635         return;
0636     }
0637     m_screenMenu->clear();
0638 
0639     if (!m_window) {
0640         return;
0641     }
0642     m_screenMenu->setPalette(m_window->palette());
0643     QActionGroup *group = new QActionGroup(m_screenMenu);
0644 
0645     const auto outputs = workspace()->outputs();
0646     for (int i = 0; i < outputs.count(); ++i) {
0647         Output *output = outputs[i];
0648         // assumption: there are not more than 9 screens attached.
0649         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)",
0650                                                         "Screen &%1 (%2)", (i + 1), output->name()));
0651         connect(action, &QAction::triggered, this, [this, output]() {
0652             workspace()->sendWindowToOutput(m_window, output);
0653         });
0654         action->setCheckable(true);
0655         if (m_window && output == m_window->output()) {
0656             action->setChecked(true);
0657         }
0658         group->addAction(action);
0659     }
0660 }
0661 
0662 void UserActionsMenu::activityPopupAboutToShow()
0663 {
0664     if (!m_activityMenu) {
0665         return;
0666     }
0667 
0668 #if KWIN_BUILD_ACTIVITIES
0669     if (!Workspace::self()->activities()) {
0670         return;
0671     }
0672     m_activityMenu->clear();
0673     if (m_window) {
0674         m_activityMenu->setPalette(m_window->palette());
0675     }
0676     QAction *action = m_activityMenu->addAction(i18n("&All Activities"));
0677     action->setCheckable(true);
0678     connect(action, &QAction::triggered, this, [this]() {
0679         if (m_window) {
0680             m_window->setOnAllActivities(!m_window->isOnAllActivities());
0681         }
0682     });
0683     static QPointer<QActionGroup> allActivitiesGroup;
0684     if (!allActivitiesGroup) {
0685         allActivitiesGroup = new QActionGroup(m_activityMenu);
0686     }
0687     allActivitiesGroup->addAction(action);
0688 
0689     if (m_window && m_window->isOnAllActivities()) {
0690         action->setChecked(true);
0691     }
0692     m_activityMenu->addSeparator();
0693 
0694     const auto activities = Workspace::self()->activities()->running();
0695     for (const QString &id : activities) {
0696         KActivities::Info activity(id);
0697         QString name = activity.name();
0698         name.replace('&', "&&");
0699         auto action = m_activityMenu->addAction(name);
0700         action->setCheckable(true);
0701         const QString icon = activity.icon();
0702         if (!icon.isEmpty()) {
0703             action->setIcon(QIcon::fromTheme(icon));
0704         }
0705         m_activityMenu->addAction(action);
0706         connect(action, &QAction::triggered, this, [this, id]() {
0707             if (m_window) {
0708                 Workspace::self()->activities()->toggleWindowOnActivity(m_window, id, false);
0709             }
0710         });
0711 
0712         if (m_window && !m_window->isOnAllActivities() && m_window->isOnActivity(id)) {
0713             action->setChecked(true);
0714         }
0715     }
0716 
0717     m_activityMenu->addSeparator();
0718     for (const QString &id : activities) {
0719         const KActivities::Info activity(id);
0720         if (m_window->activities().size() == 1 && m_window->activities().front() == id) {
0721             // no need to show a button that doesn't do anything
0722             continue;
0723         }
0724         const QString name = i18n("Move to %1", activity.name().replace('&', "&&"));
0725         const auto action = m_activityMenu->addAction(name);
0726         if (const QString icon = activity.icon(); !icon.isEmpty()) {
0727             action->setIcon(QIcon::fromTheme(icon));
0728         }
0729         connect(action, &QAction::triggered, this, [this, id] {
0730             m_window->setOnActivities({id});
0731         });
0732         m_activityMenu->addAction(action);
0733     }
0734 #endif
0735 }
0736 
0737 void UserActionsMenu::slotWindowOperation(QAction *action)
0738 {
0739     if (!action->data().isValid()) {
0740         return;
0741     }
0742 
0743     Options::WindowOperation op = static_cast<Options::WindowOperation>(action->data().toInt());
0744     QPointer<Window> c = m_window ? m_window : QPointer<Window>(Workspace::self()->activeWindow());
0745     if (c.isNull()) {
0746         return;
0747     }
0748     QString type;
0749     switch (op) {
0750     case Options::FullScreenOp:
0751         if (!c->isFullScreen() && c->isFullScreenable()) {
0752             type = QStringLiteral("fullscreenaltf3");
0753         }
0754         break;
0755     case Options::NoBorderOp:
0756         if (!c->noBorder() && c->userCanSetNoBorder()) {
0757             type = QStringLiteral("noborderaltf3");
0758         }
0759         break;
0760     default:
0761         break;
0762     }
0763     if (!type.isEmpty()) {
0764         helperDialog(type);
0765     }
0766     // need to delay performing the window operation as we need to have the
0767     // user actions menu closed before we destroy the decoration. Otherwise Qt crashes
0768     QMetaObject::invokeMethod(
0769         workspace(), [c, op]() {
0770             workspace()->performWindowOperation(c, op);
0771         },
0772         Qt::QueuedConnection);
0773 }
0774 
0775 //****************************************
0776 // ShortcutDialog
0777 //****************************************
0778 ShortcutDialog::ShortcutDialog(const QKeySequence &cut)
0779     : _shortcut(cut)
0780 {
0781     m_ui.setupUi(this);
0782     m_ui.keySequenceEdit->setKeySequence(cut);
0783     m_ui.warning->hide();
0784 
0785     // Listen to changed shortcuts
0786     connect(m_ui.keySequenceEdit, &QKeySequenceEdit::editingFinished, this, &ShortcutDialog::keySequenceChanged);
0787     connect(m_ui.clearButton, &QToolButton::clicked, this, [this] {
0788         _shortcut = QKeySequence();
0789     });
0790     m_ui.keySequenceEdit->setFocus();
0791 
0792     setWindowFlags(Qt::Popup | Qt::X11BypassWindowManagerHint);
0793 }
0794 
0795 void ShortcutDialog::accept()
0796 {
0797     QKeySequence seq = shortcut();
0798     if (!seq.isEmpty()) {
0799         if (seq[0] == QKeyCombination(Qt::Key_Escape)) {
0800             reject();
0801             return;
0802         }
0803         if (seq[0] == QKeyCombination(Qt::Key_Space) || seq[0].keyboardModifiers() == Qt::NoModifier) {
0804             // clear
0805             m_ui.keySequenceEdit->clear();
0806             QDialog::accept();
0807             return;
0808         }
0809     }
0810     QDialog::accept();
0811 }
0812 
0813 void ShortcutDialog::done(int r)
0814 {
0815     QDialog::done(r);
0816     Q_EMIT dialogDone(r == Accepted);
0817 }
0818 
0819 void ShortcutDialog::keySequenceChanged()
0820 {
0821     activateWindow(); // where is the kbd focus lost? cause of popup state?
0822     QKeySequence seq = m_ui.keySequenceEdit->keySequence();
0823     if (_shortcut == seq) {
0824         return; // don't try to update the same
0825     }
0826 
0827     if (seq.isEmpty()) { // clear
0828         _shortcut = seq;
0829         return;
0830     }
0831     if (seq.count() > 1) {
0832         seq = QKeySequence(seq[0]);
0833         m_ui.keySequenceEdit->setKeySequence(seq);
0834     }
0835 
0836     // Check if the key sequence is used currently
0837     QString sc = seq.toString();
0838     // NOTICE - seq.toString() & the entries in "conflicting" randomly get invalidated after the next call (if no sc has been set & conflicting isn't empty?!)
0839     QList<KGlobalShortcutInfo> conflicting = KGlobalAccel::globalShortcutsByKey(seq);
0840     if (!conflicting.isEmpty()) {
0841         const KGlobalShortcutInfo &conflict = conflicting.at(0);
0842         m_ui.warning->setText(i18nc("'%1' is a keyboard shortcut like 'ctrl+w'",
0843                                     "<b>%1</b> is already in use", sc));
0844         m_ui.warning->setToolTip(i18nc("keyboard shortcut '%1' is used by action '%2' in application '%3'",
0845                                        "<b>%1</b> is used by %2 in %3", sc, conflict.friendlyName(), conflict.componentFriendlyName()));
0846         m_ui.warning->show();
0847         m_ui.keySequenceEdit->setKeySequence(shortcut());
0848     } else if (seq != _shortcut) {
0849         m_ui.warning->hide();
0850         if (QPushButton *ok = m_ui.buttonBox->button(QDialogButtonBox::Ok)) {
0851             ok->setFocus();
0852         }
0853     }
0854 
0855     _shortcut = seq;
0856 }
0857 
0858 QKeySequence ShortcutDialog::shortcut() const
0859 {
0860     return _shortcut;
0861 }
0862 
0863 //****************************************
0864 // Workspace
0865 //****************************************
0866 
0867 void Workspace::slotIncreaseWindowOpacity()
0868 {
0869     if (!m_activeWindow) {
0870         return;
0871     }
0872     m_activeWindow->setOpacity(std::min(m_activeWindow->opacity() + 0.05, 1.0));
0873 }
0874 
0875 void Workspace::slotLowerWindowOpacity()
0876 {
0877     if (!m_activeWindow) {
0878         return;
0879     }
0880     m_activeWindow->setOpacity(std::max(m_activeWindow->opacity() - 0.05, 0.05));
0881 }
0882 
0883 void Workspace::closeActivePopup()
0884 {
0885     if (active_popup) {
0886         active_popup->close();
0887         active_popup = nullptr;
0888         m_activePopupWindow = nullptr;
0889     }
0890     m_userActionsMenu->close();
0891 }
0892 
0893 template<typename Slot>
0894 void Workspace::initShortcut(const QString &actionName, const QString &description, const QKeySequence &shortcut, Slot slot)
0895 {
0896     initShortcut(actionName, description, shortcut, this, slot);
0897 }
0898 
0899 template<typename T, typename Slot>
0900 void Workspace::initShortcut(const QString &actionName, const QString &description, const QKeySequence &shortcut, T *receiver, Slot slot)
0901 {
0902     QAction *a = new QAction(this);
0903     a->setProperty("componentName", QStringLiteral("kwin"));
0904     a->setObjectName(actionName);
0905     a->setText(description);
0906     KGlobalAccel::self()->setDefaultShortcut(a, QList<QKeySequence>() << shortcut);
0907     KGlobalAccel::self()->setShortcut(a, QList<QKeySequence>() << shortcut);
0908     connect(a, &QAction::triggered, receiver, slot);
0909 }
0910 
0911 /**
0912  * Creates the global accel object \c keys.
0913  */
0914 void Workspace::initShortcuts()
0915 {
0916     // The first argument to initShortcut() is the id for the shortcut in the user's
0917     // config file, while the second argumewnt is its user-visible text.
0918     // Normally these should be identical, but if the user-visible text for a new
0919     // shortcut that you're adding is super long, it is permissible to use a shorter
0920     // string for name.
0921 
0922     // PLEASE NOTE: Never change the ID of an existing shortcut! It will cause users'
0923     // custom shortcuts to be lost. Instead, only change the description
0924 
0925     initShortcut("Window Operations Menu", i18n("Window Operations Menu"),
0926                  Qt::ALT | Qt::Key_F3, &Workspace::slotWindowOperations);
0927     initShortcut("Window Close", i18n("Close Window"),
0928                  Qt::ALT | Qt::Key_F4, &Workspace::slotWindowClose);
0929     initShortcut("Window Maximize", i18n("Maximize Window"),
0930                  Qt::META | Qt::Key_PageUp, &Workspace::slotWindowMaximize);
0931     initShortcut("Window Maximize Vertical", i18n("Maximize Window Vertically"),
0932                  0, &Workspace::slotWindowMaximizeVertical);
0933     initShortcut("Window Maximize Horizontal", i18n("Maximize Window Horizontally"),
0934                  0, &Workspace::slotWindowMaximizeHorizontal);
0935     initShortcut("Window Minimize", i18n("Minimize Window"),
0936                  Qt::META | Qt::Key_PageDown, &Workspace::slotWindowMinimize);
0937     initShortcut("Window Shade", i18n("Shade Window"),
0938                  0, &Workspace::slotWindowShade);
0939     initShortcut("Window Move", i18n("Move Window"),
0940                  0, &Workspace::slotWindowMove);
0941     initShortcut("Window Resize", i18n("Resize Window"),
0942                  0, &Workspace::slotWindowResize);
0943     initShortcut("Window Raise", i18n("Raise Window"),
0944                  0, &Workspace::slotWindowRaise);
0945     initShortcut("Window Lower", i18n("Lower Window"),
0946                  0, &Workspace::slotWindowLower);
0947     initShortcut("Toggle Window Raise/Lower", i18n("Toggle Window Raise/Lower"),
0948                  0, &Workspace::slotWindowRaiseOrLower);
0949     initShortcut("Window Fullscreen", i18n("Make Window Fullscreen"),
0950                  0, &Workspace::slotWindowFullScreen);
0951     initShortcut("Window No Border", i18n("Toggle Window Titlebar and Frame"),
0952                  0, &Workspace::slotWindowNoBorder);
0953     initShortcut("Window Above Other Windows", i18n("Keep Window Above Others"),
0954                  0, &Workspace::slotWindowAbove);
0955     initShortcut("Window Below Other Windows", i18n("Keep Window Below Others"),
0956                  0, &Workspace::slotWindowBelow);
0957     initShortcut("Activate Window Demanding Attention", i18n("Activate Window Demanding Attention"),
0958                  Qt::META | Qt::CTRL | Qt::Key_A, &Workspace::slotActivateAttentionWindow);
0959     initShortcut("Setup Window Shortcut", i18n("Setup Window Shortcut"),
0960                  0, &Workspace::slotSetupWindowShortcut);
0961     initShortcut("Window Move Center", i18n("Move Window to the Center"), 0,
0962                  &Workspace::slotWindowCenter);
0963     initShortcut("Window Pack Right", i18n("Move Window Right"),
0964                  0, &Workspace::slotWindowMoveRight);
0965     initShortcut("Window Pack Left", i18n("Move Window Left"),
0966                  0, &Workspace::slotWindowMoveLeft);
0967     initShortcut("Window Pack Up", i18n("Move Window Up"),
0968                  0, &Workspace::slotWindowMoveUp);
0969     initShortcut("Window Pack Down", i18n("Move Window Down"),
0970                  0, &Workspace::slotWindowMoveDown);
0971     initShortcut("Window Grow Horizontal", i18n("Expand Window Horizontally"),
0972                  0, &Workspace::slotWindowExpandHorizontal);
0973     initShortcut("Window Grow Vertical", i18n("Expand Window Vertically"),
0974                  0, &Workspace::slotWindowExpandVertical);
0975     initShortcut("Window Shrink Horizontal", i18n("Shrink Window Horizontally"),
0976                  0, &Workspace::slotWindowShrinkHorizontal);
0977     initShortcut("Window Shrink Vertical", i18n("Shrink Window Vertically"),
0978                  0, &Workspace::slotWindowShrinkVertical);
0979     initShortcut("Window Quick Tile Left", i18n("Quick Tile Window to the Left"),
0980                  Qt::META | Qt::Key_Left, std::bind(&Workspace::quickTileWindow, this, QuickTileFlag::Left));
0981     initShortcut("Window Quick Tile Right", i18n("Quick Tile Window to the Right"),
0982                  Qt::META | Qt::Key_Right, std::bind(&Workspace::quickTileWindow, this, QuickTileFlag::Right));
0983     initShortcut("Window Quick Tile Top", i18n("Quick Tile Window to the Top"),
0984                  Qt::META | Qt::Key_Up, std::bind(&Workspace::quickTileWindow, this, QuickTileFlag::Top));
0985     initShortcut("Window Quick Tile Bottom", i18n("Quick Tile Window to the Bottom"),
0986                  Qt::META | Qt::Key_Down, std::bind(&Workspace::quickTileWindow, this, QuickTileFlag::Bottom));
0987     initShortcut("Window Quick Tile Top Left", i18n("Quick Tile Window to the Top Left"),
0988                  0, std::bind(&Workspace::quickTileWindow, this, QuickTileFlag::Top | QuickTileFlag::Left));
0989     initShortcut("Window Quick Tile Bottom Left", i18n("Quick Tile Window to the Bottom Left"),
0990                  0, std::bind(&Workspace::quickTileWindow, this, QuickTileFlag::Bottom | QuickTileFlag::Left));
0991     initShortcut("Window Quick Tile Top Right", i18n("Quick Tile Window to the Top Right"),
0992                  0, std::bind(&Workspace::quickTileWindow, this, QuickTileFlag::Top | QuickTileFlag::Right));
0993     initShortcut("Window Quick Tile Bottom Right", i18n("Quick Tile Window to the Bottom Right"),
0994                  0, std::bind(&Workspace::quickTileWindow, this, QuickTileFlag::Bottom | QuickTileFlag::Right));
0995     initShortcut("Switch Window Up", i18n("Switch to Window Above"),
0996                  Qt::META | Qt::ALT | Qt::Key_Up, std::bind(static_cast<void (Workspace::*)(Direction)>(&Workspace::switchWindow), this, DirectionNorth));
0997     initShortcut("Switch Window Down", i18n("Switch to Window Below"),
0998                  Qt::META | Qt::ALT | Qt::Key_Down, std::bind(static_cast<void (Workspace::*)(Direction)>(&Workspace::switchWindow), this, DirectionSouth));
0999     initShortcut("Switch Window Right", i18n("Switch to Window to the Right"),
1000                  Qt::META | Qt::ALT | Qt::Key_Right, std::bind(static_cast<void (Workspace::*)(Direction)>(&Workspace::switchWindow), this, DirectionEast));
1001     initShortcut("Switch Window Left", i18n("Switch to Window to the Left"),
1002                  Qt::META | Qt::ALT | Qt::Key_Left, std::bind(static_cast<void (Workspace::*)(Direction)>(&Workspace::switchWindow), this, DirectionWest));
1003     initShortcut("Increase Opacity", i18n("Increase Opacity of Active Window by 5%"),
1004                  0, &Workspace::slotIncreaseWindowOpacity);
1005     initShortcut("Decrease Opacity", i18n("Decrease Opacity of Active Window by 5%"),
1006                  0, &Workspace::slotLowerWindowOpacity);
1007 
1008     initShortcut("Window On All Desktops", i18n("Keep Window on All Desktops"),
1009                  0, &Workspace::slotWindowOnAllDesktops);
1010 
1011     VirtualDesktopManager *vds = VirtualDesktopManager::self();
1012     for (uint i = 0; i < vds->maximum(); ++i) {
1013         auto handler = [this, i]() {
1014             const QList<VirtualDesktop *> desktops = VirtualDesktopManager::self()->desktops();
1015             if (i < uint(desktops.count())) {
1016                 slotWindowToDesktop(desktops[i]);
1017             }
1018         };
1019         initShortcut(QStringLiteral("Window to Desktop %1").arg(i + 1), i18n("Window to Desktop %1", i + 1), 0, handler);
1020     }
1021     initShortcut("Window to Next Desktop", i18n("Window to Next Desktop"), 0, &Workspace::slotWindowToNextDesktop);
1022     initShortcut("Window to Previous Desktop", i18n("Window to Previous Desktop"), 0, &Workspace::slotWindowToPreviousDesktop);
1023     initShortcut("Window One Desktop to the Right", i18n("Window One Desktop to the Right"),
1024                  Qt::META | Qt::CTRL | Qt::SHIFT | Qt::Key_Right, &Workspace::slotWindowToDesktopRight);
1025     initShortcut("Window One Desktop to the Left", i18n("Window One Desktop to the Left"),
1026                  Qt::META | Qt::CTRL | Qt::SHIFT | Qt::Key_Left, &Workspace::slotWindowToDesktopLeft);
1027     initShortcut("Window One Desktop Up", i18n("Window One Desktop Up"),
1028                  Qt::META | Qt::CTRL | Qt::SHIFT | Qt::Key_Up, &Workspace::slotWindowToDesktopUp);
1029     initShortcut("Window One Desktop Down", i18n("Window One Desktop Down"),
1030                  Qt::META | Qt::CTRL | Qt::SHIFT | Qt::Key_Down, &Workspace::slotWindowToDesktopDown);
1031 
1032     for (int i = 0; i < 8; ++i) {
1033         initShortcut(QStringLiteral("Window to Screen %1").arg(i), i18n("Move Window to Screen %1", i), 0, [this, i]() {
1034             Output *output = outputs().value(i);
1035             if (output) {
1036                 slotWindowToScreen(output);
1037             }
1038         });
1039     }
1040     initShortcut("Window to Next Screen", i18n("Move Window to Next Screen"),
1041                  Qt::META | Qt::SHIFT | Qt::Key_Right, &Workspace::slotWindowToNextScreen);
1042     initShortcut("Window to Previous Screen", i18n("Move Window to Previous Screen"),
1043                  Qt::META | Qt::SHIFT | Qt::Key_Left, &Workspace::slotWindowToPrevScreen);
1044     initShortcut("Window One Screen to the Right", i18n("Move Window One Screen to the Right"),
1045                  0, &Workspace::slotWindowToRightScreen);
1046     initShortcut("Window One Screen to the Left", i18n("Move Window One Screen to the Left"),
1047                  0, &Workspace::slotWindowToLeftScreen);
1048     initShortcut("Window One Screen Up", i18n("Move Window One Screen Up"),
1049                  0, &Workspace::slotWindowToAboveScreen);
1050     initShortcut("Window One Screen Down", i18n("Move Window One Screen Down"),
1051                  0, &Workspace::slotWindowToBelowScreen);
1052 
1053     for (int i = 0; i < 8; ++i) {
1054         initShortcut(QStringLiteral("Switch to Screen %1").arg(i), i18n("Switch to Screen %1", i), 0, [this, i]() {
1055             Output *output = outputs().value(i);
1056             if (output) {
1057                 slotSwitchToScreen(output);
1058             }
1059         });
1060     }
1061     initShortcut("Switch to Next Screen", i18n("Switch to Next Screen"), 0, &Workspace::slotSwitchToNextScreen);
1062     initShortcut("Switch to Previous Screen", i18n("Switch to Previous Screen"), 0, &Workspace::slotSwitchToPrevScreen);
1063     initShortcut("Switch to Screen to the Right", i18n("Switch to Screen to the Right"),
1064                  0, &Workspace::slotSwitchToRightScreen);
1065     initShortcut("Switch to Screen to the Left", i18n("Switch to Screen to the Left"),
1066                  0, &Workspace::slotSwitchToLeftScreen);
1067     initShortcut("Switch to Screen Above", i18n("Switch to Screen Above"),
1068                  0, &Workspace::slotSwitchToAboveScreen);
1069     initShortcut("Switch to Screen Below", i18n("Switch to Screen Below"),
1070                  0, &Workspace::slotSwitchToBelowScreen);
1071 
1072     initShortcut("Show Desktop", i18n("Peek at Desktop"),
1073                  Qt::META | Qt::Key_D, &Workspace::slotToggleShowDesktop);
1074 
1075     initShortcut("Kill Window", i18n("Kill Window"), Qt::META | Qt::CTRL | Qt::Key_Escape, &Workspace::slotKillWindow);
1076 
1077 #if KWIN_BUILD_TABBOX
1078     m_tabbox->initShortcuts();
1079 #endif
1080     vds->initShortcuts();
1081     m_userActionsMenu->discard(); // so that it's recreated next time
1082 }
1083 
1084 void Workspace::setupWindowShortcut(Window *window)
1085 {
1086     Q_ASSERT(m_windowKeysDialog == nullptr);
1087     // TODO: PORT ME (KGlobalAccel related)
1088     // keys->setEnabled( false );
1089     // disable_shortcuts_keys->setEnabled( false );
1090     // client_keys->setEnabled( false );
1091     m_windowKeysDialog = new ShortcutDialog(window->shortcut());
1092     m_windowKeysWindow = window;
1093     connect(m_windowKeysDialog, &ShortcutDialog::dialogDone, this, &Workspace::setupWindowShortcutDone);
1094     QRect r = clientArea(ScreenArea, window).toRect();
1095     QSize size = m_windowKeysDialog->sizeHint();
1096     QPointF pos(window->frameGeometry().left() + window->frameMargins().left(),
1097                 window->frameGeometry().top() + window->frameMargins().top());
1098     if (pos.x() + size.width() >= r.right()) {
1099         pos.setX(r.right() - size.width());
1100     }
1101     if (pos.y() + size.height() >= r.bottom()) {
1102         pos.setY(r.bottom() - size.height());
1103     }
1104     m_windowKeysDialog->move(pos.toPoint());
1105     m_windowKeysDialog->show();
1106     active_popup = m_windowKeysDialog;
1107     m_activePopupWindow = window;
1108 }
1109 
1110 void Workspace::setupWindowShortcutDone(bool ok)
1111 {
1112     //    keys->setEnabled( true );
1113     //    disable_shortcuts_keys->setEnabled( true );
1114     //    client_keys->setEnabled( true );
1115     if (ok) {
1116         m_windowKeysWindow->setShortcut(m_windowKeysDialog->shortcut().toString());
1117     }
1118     closeActivePopup();
1119     m_windowKeysDialog->deleteLater();
1120     m_windowKeysDialog = nullptr;
1121     m_windowKeysWindow = nullptr;
1122     if (m_activeWindow) {
1123         m_activeWindow->takeFocus();
1124     }
1125 }
1126 
1127 void Workspace::windowShortcutUpdated(Window *window)
1128 {
1129     QString key = QStringLiteral("_k_session:%1").arg(window->internalId().toString());
1130     QAction *action = findChild<QAction *>(key);
1131     if (!window->shortcut().isEmpty()) {
1132         if (action == nullptr) { // new shortcut
1133             action = new QAction(this);
1134             connect(window, &Window::closed, action, [action]() {
1135                 KGlobalAccel::self()->removeAllShortcuts(action);
1136                 delete action;
1137             });
1138 
1139             action->setProperty("componentName", QStringLiteral("kwin"));
1140             action->setObjectName(key);
1141             action->setText(i18n("Activate Window (%1)", window->caption()));
1142             connect(action, &QAction::triggered, window, std::bind(&Workspace::activateWindow, this, window, true));
1143         }
1144 
1145         // no autoloading, since it's configured explicitly here and is not meant to be reused
1146         // (the key is the window id anyway, which is kind of random)
1147         KGlobalAccel::self()->setShortcut(action, QList<QKeySequence>() << window->shortcut(),
1148                                           KGlobalAccel::NoAutoloading);
1149         action->setEnabled(true);
1150     } else {
1151         KGlobalAccel::self()->removeAllShortcuts(action);
1152         delete action;
1153     }
1154 }
1155 
1156 void Workspace::performWindowOperation(Window *window, Options::WindowOperation op)
1157 {
1158     if (!window) {
1159         return;
1160     }
1161     if (op == Options::MoveOp || op == Options::UnrestrictedMoveOp) {
1162         Cursors::self()->mouse()->setPos(window->frameGeometry().center());
1163     }
1164     if (op == Options::ResizeOp || op == Options::UnrestrictedResizeOp) {
1165         Cursors::self()->mouse()->setPos(window->frameGeometry().bottomRight());
1166     }
1167     switch (op) {
1168     case Options::MoveOp:
1169         window->performMouseCommand(Options::MouseMove, Cursors::self()->mouse()->pos());
1170         break;
1171     case Options::UnrestrictedMoveOp:
1172         window->performMouseCommand(Options::MouseUnrestrictedMove, Cursors::self()->mouse()->pos());
1173         break;
1174     case Options::ResizeOp:
1175         window->performMouseCommand(Options::MouseResize, Cursors::self()->mouse()->pos());
1176         break;
1177     case Options::UnrestrictedResizeOp:
1178         window->performMouseCommand(Options::MouseUnrestrictedResize, Cursors::self()->mouse()->pos());
1179         break;
1180     case Options::CloseOp:
1181         QMetaObject::invokeMethod(window, &Window::closeWindow, Qt::QueuedConnection);
1182         break;
1183     case Options::MaximizeOp:
1184         window->maximize(window->maximizeMode() == MaximizeFull
1185                              ? MaximizeRestore
1186                              : MaximizeFull);
1187         takeActivity(window, ActivityFocus | ActivityRaise);
1188         break;
1189     case Options::HMaximizeOp:
1190         window->maximize(window->maximizeMode() ^ MaximizeHorizontal);
1191         takeActivity(window, ActivityFocus | ActivityRaise);
1192         break;
1193     case Options::VMaximizeOp:
1194         window->maximize(window->maximizeMode() ^ MaximizeVertical);
1195         takeActivity(window, ActivityFocus | ActivityRaise);
1196         break;
1197     case Options::RestoreOp:
1198         window->maximize(MaximizeRestore);
1199         takeActivity(window, ActivityFocus | ActivityRaise);
1200         break;
1201     case Options::MinimizeOp:
1202         window->setMinimized(true);
1203         break;
1204     case Options::ShadeOp:
1205         window->performMouseCommand(Options::MouseShade, Cursors::self()->mouse()->pos());
1206         break;
1207     case Options::OnAllDesktopsOp:
1208         window->setOnAllDesktops(!window->isOnAllDesktops());
1209         break;
1210     case Options::FullScreenOp:
1211         window->setFullScreen(!window->isFullScreen());
1212         break;
1213     case Options::NoBorderOp:
1214         if (window->userCanSetNoBorder()) {
1215             window->setNoBorder(!window->noBorder());
1216         }
1217         break;
1218     case Options::KeepAboveOp: {
1219         StackingUpdatesBlocker blocker(this);
1220         bool was = window->keepAbove();
1221         window->setKeepAbove(!window->keepAbove());
1222         if (was && !window->keepAbove()) {
1223             raiseWindow(window);
1224         }
1225         break;
1226     }
1227     case Options::KeepBelowOp: {
1228         StackingUpdatesBlocker blocker(this);
1229         bool was = window->keepBelow();
1230         window->setKeepBelow(!window->keepBelow());
1231         if (was && !window->keepBelow()) {
1232             lowerWindow(window);
1233         }
1234         break;
1235     }
1236     case Options::OperationsOp:
1237         window->performMouseCommand(Options::MouseShade, Cursors::self()->mouse()->pos());
1238         break;
1239     case Options::WindowRulesOp:
1240         m_rulebook->edit(window, false);
1241         break;
1242     case Options::ApplicationRulesOp:
1243         m_rulebook->edit(window, true);
1244         break;
1245     case Options::SetupWindowShortcutOp:
1246         setupWindowShortcut(window);
1247         break;
1248     case Options::LowerOp:
1249         lowerWindow(window);
1250         break;
1251     case Options::NoOp:
1252         break;
1253     }
1254 }
1255 
1256 void Workspace::slotActivateAttentionWindow()
1257 {
1258     if (attention_chain.count() > 0) {
1259         activateWindow(attention_chain.first());
1260     }
1261 }
1262 
1263 #define USABLE_ACTIVE_WINDOW (m_activeWindow && !(m_activeWindow->isDesktop() || m_activeWindow->isDock()))
1264 
1265 void Workspace::slotWindowToDesktop(VirtualDesktop *desktop)
1266 {
1267     if (USABLE_ACTIVE_WINDOW) {
1268         sendWindowToDesktops(m_activeWindow, {desktop}, true);
1269     }
1270 }
1271 
1272 static bool screenSwitchImpossible()
1273 {
1274     if (!options->activeMouseScreen()) {
1275         return false;
1276     }
1277     QStringList args;
1278     args << QStringLiteral("--passivepopup") << i18n("The window manager is configured to consider the screen with the mouse on it as active one.\n"
1279                                                      "Therefore it is not possible to switch to a screen explicitly.")
1280          << QStringLiteral("20");
1281     KProcess::startDetached(QStringLiteral("kdialog"), args);
1282     return true;
1283 }
1284 
1285 void Workspace::slotSwitchToScreen(Output *output)
1286 {
1287     if (!screenSwitchImpossible()) {
1288         switchToOutput(output);
1289     }
1290 }
1291 
1292 void Workspace::slotSwitchToLeftScreen()
1293 {
1294     if (!screenSwitchImpossible()) {
1295         switchToOutput(findOutput(activeOutput(), Direction::DirectionWest, true));
1296     }
1297 }
1298 
1299 void Workspace::slotSwitchToRightScreen()
1300 {
1301     if (!screenSwitchImpossible()) {
1302         switchToOutput(findOutput(activeOutput(), Direction::DirectionEast, true));
1303     }
1304 }
1305 
1306 void Workspace::slotSwitchToAboveScreen()
1307 {
1308     if (!screenSwitchImpossible()) {
1309         switchToOutput(findOutput(activeOutput(), Direction::DirectionNorth, true));
1310     }
1311 }
1312 
1313 void Workspace::slotSwitchToBelowScreen()
1314 {
1315     if (!screenSwitchImpossible()) {
1316         switchToOutput(findOutput(activeOutput(), Direction::DirectionSouth, true));
1317     }
1318 }
1319 
1320 void Workspace::slotSwitchToPrevScreen()
1321 {
1322     if (!screenSwitchImpossible()) {
1323         switchToOutput(findOutput(activeOutput(), Direction::DirectionPrev, true));
1324     }
1325 }
1326 
1327 void Workspace::slotSwitchToNextScreen()
1328 {
1329     if (!screenSwitchImpossible()) {
1330         switchToOutput(findOutput(activeOutput(), Direction::DirectionNext, true));
1331     }
1332 }
1333 
1334 void Workspace::slotWindowToScreen(Output *output)
1335 {
1336     if (USABLE_ACTIVE_WINDOW) {
1337         sendWindowToOutput(m_activeWindow, output);
1338     }
1339 }
1340 
1341 void Workspace::slotWindowToLeftScreen()
1342 {
1343     if (USABLE_ACTIVE_WINDOW) {
1344         sendWindowToOutput(m_activeWindow, findOutput(m_activeWindow->output(), Direction::DirectionWest, true));
1345     }
1346 }
1347 
1348 void Workspace::slotWindowToRightScreen()
1349 {
1350     if (USABLE_ACTIVE_WINDOW) {
1351         sendWindowToOutput(m_activeWindow, findOutput(m_activeWindow->output(), Direction::DirectionEast, true));
1352     }
1353 }
1354 
1355 void Workspace::slotWindowToAboveScreen()
1356 {
1357     if (USABLE_ACTIVE_WINDOW) {
1358         sendWindowToOutput(m_activeWindow, findOutput(m_activeWindow->output(), Direction::DirectionNorth, true));
1359     }
1360 }
1361 
1362 void Workspace::slotWindowToBelowScreen()
1363 {
1364     if (USABLE_ACTIVE_WINDOW) {
1365         sendWindowToOutput(m_activeWindow, findOutput(m_activeWindow->output(), Direction::DirectionSouth, true));
1366     }
1367 }
1368 
1369 void Workspace::slotWindowToPrevScreen()
1370 {
1371     if (USABLE_ACTIVE_WINDOW) {
1372         sendWindowToOutput(m_activeWindow, findOutput(m_activeWindow->output(), Direction::DirectionPrev, true));
1373     }
1374 }
1375 
1376 void Workspace::slotWindowToNextScreen()
1377 {
1378     if (USABLE_ACTIVE_WINDOW) {
1379         sendWindowToOutput(m_activeWindow, findOutput(m_activeWindow->output(), Direction::DirectionNext, true));
1380     }
1381 }
1382 
1383 /**
1384  * Maximizes the active window.
1385  */
1386 void Workspace::slotWindowMaximize()
1387 {
1388     if (USABLE_ACTIVE_WINDOW) {
1389         performWindowOperation(m_activeWindow, Options::MaximizeOp);
1390     }
1391 }
1392 
1393 /**
1394  * Maximizes the active window vertically.
1395  */
1396 void Workspace::slotWindowMaximizeVertical()
1397 {
1398     if (USABLE_ACTIVE_WINDOW) {
1399         performWindowOperation(m_activeWindow, Options::VMaximizeOp);
1400     }
1401 }
1402 
1403 /**
1404  * Maximizes the active window horiozontally.
1405  */
1406 void Workspace::slotWindowMaximizeHorizontal()
1407 {
1408     if (USABLE_ACTIVE_WINDOW) {
1409         performWindowOperation(m_activeWindow, Options::HMaximizeOp);
1410     }
1411 }
1412 
1413 /**
1414  * Minimizes the active window.
1415  */
1416 void Workspace::slotWindowMinimize()
1417 {
1418     if (USABLE_ACTIVE_WINDOW) {
1419         performWindowOperation(m_activeWindow, Options::MinimizeOp);
1420     }
1421 }
1422 
1423 /**
1424  * Shades/unshades the active window respectively.
1425  */
1426 void Workspace::slotWindowShade()
1427 {
1428     if (USABLE_ACTIVE_WINDOW) {
1429         performWindowOperation(m_activeWindow, Options::ShadeOp);
1430     }
1431 }
1432 
1433 /**
1434  * Raises the active window.
1435  */
1436 void Workspace::slotWindowRaise()
1437 {
1438     if (USABLE_ACTIVE_WINDOW) {
1439         raiseWindow(m_activeWindow);
1440     }
1441 }
1442 
1443 /**
1444  * Lowers the active window.
1445  */
1446 void Workspace::slotWindowLower()
1447 {
1448     if (USABLE_ACTIVE_WINDOW) {
1449         lowerWindow(m_activeWindow);
1450         // As this most likely makes the window no longer visible change the
1451         // keyboard focus to the next available window.
1452         // activateNextWindow( c ); // Doesn't work when we lower a child window
1453         if (m_activeWindow->isActive() && options->focusPolicyIsReasonable()) {
1454             if (options->isNextFocusPrefersMouse()) {
1455                 Window *next = windowUnderMouse(m_activeWindow->output());
1456                 if (next && next != m_activeWindow) {
1457                     requestFocus(next, false);
1458                 }
1459             } else {
1460                 activateWindow(topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop()));
1461             }
1462         }
1463     }
1464 }
1465 
1466 /**
1467  * Does a toggle-raise-and-lower on the active window.
1468  */
1469 void Workspace::slotWindowRaiseOrLower()
1470 {
1471     if (USABLE_ACTIVE_WINDOW) {
1472         raiseOrLowerWindow(m_activeWindow);
1473     }
1474 }
1475 
1476 void Workspace::slotWindowOnAllDesktops()
1477 {
1478     if (USABLE_ACTIVE_WINDOW) {
1479         m_activeWindow->setOnAllDesktops(!m_activeWindow->isOnAllDesktops());
1480     }
1481 }
1482 
1483 void Workspace::slotWindowFullScreen()
1484 {
1485     if (USABLE_ACTIVE_WINDOW) {
1486         performWindowOperation(m_activeWindow, Options::FullScreenOp);
1487     }
1488 }
1489 
1490 void Workspace::slotWindowNoBorder()
1491 {
1492     if (USABLE_ACTIVE_WINDOW) {
1493         performWindowOperation(m_activeWindow, Options::NoBorderOp);
1494     }
1495 }
1496 
1497 void Workspace::slotWindowAbove()
1498 {
1499     if (USABLE_ACTIVE_WINDOW) {
1500         performWindowOperation(m_activeWindow, Options::KeepAboveOp);
1501     }
1502 }
1503 
1504 void Workspace::slotWindowBelow()
1505 {
1506     if (USABLE_ACTIVE_WINDOW) {
1507         performWindowOperation(m_activeWindow, Options::KeepBelowOp);
1508     }
1509 }
1510 void Workspace::slotSetupWindowShortcut()
1511 {
1512     if (USABLE_ACTIVE_WINDOW) {
1513         performWindowOperation(m_activeWindow, Options::SetupWindowShortcutOp);
1514     }
1515 }
1516 
1517 /**
1518  * Toggles show desktop.
1519  */
1520 void Workspace::slotToggleShowDesktop()
1521 {
1522     setShowingDesktop(!showingDesktop());
1523 }
1524 
1525 void windowToDesktop(Window *window, VirtualDesktopManager::Direction direction)
1526 {
1527     VirtualDesktopManager *vds = VirtualDesktopManager::self();
1528     Workspace *ws = Workspace::self();
1529     // TODO: why is options->isRollOverDesktops() not honored?
1530     const auto desktop = vds->inDirection(nullptr, direction, true);
1531     if (window && !window->isDesktop()
1532         && !window->isDock()) {
1533         ws->setMoveResizeWindow(window);
1534         vds->setCurrent(desktop);
1535         ws->setMoveResizeWindow(nullptr);
1536     }
1537 }
1538 
1539 /**
1540  * Moves the active window to the next desktop.
1541  */
1542 void Workspace::slotWindowToNextDesktop()
1543 {
1544     if (USABLE_ACTIVE_WINDOW) {
1545         windowToNextDesktop(m_activeWindow);
1546     }
1547 }
1548 
1549 void Workspace::windowToNextDesktop(Window *window)
1550 {
1551     windowToDesktop(window, VirtualDesktopManager::Direction::Next);
1552 }
1553 
1554 /**
1555  * Moves the active window to the previous desktop.
1556  */
1557 void Workspace::slotWindowToPreviousDesktop()
1558 {
1559     if (USABLE_ACTIVE_WINDOW) {
1560         windowToPreviousDesktop(m_activeWindow);
1561     }
1562 }
1563 
1564 void Workspace::windowToPreviousDesktop(Window *window)
1565 {
1566     windowToDesktop(window, VirtualDesktopManager::Direction::Previous);
1567 }
1568 
1569 void activeWindowToDesktop(VirtualDesktopManager::Direction direction)
1570 {
1571     VirtualDesktopManager *vds = VirtualDesktopManager::self();
1572     Workspace *ws = Workspace::self();
1573     VirtualDesktop *current = vds->currentDesktop();
1574     VirtualDesktop *newCurrent = VirtualDesktopManager::self()->inDirection(current, direction, options->isRollOverDesktops());
1575     if (newCurrent == current) {
1576         return;
1577     }
1578     ws->setMoveResizeWindow(ws->activeWindow());
1579     vds->setCurrent(newCurrent);
1580     ws->setMoveResizeWindow(nullptr);
1581 }
1582 
1583 void Workspace::slotWindowToDesktopRight()
1584 {
1585     if (USABLE_ACTIVE_WINDOW) {
1586         activeWindowToDesktop(VirtualDesktopManager::Direction::Right);
1587     }
1588 }
1589 
1590 void Workspace::slotWindowToDesktopLeft()
1591 {
1592     if (USABLE_ACTIVE_WINDOW) {
1593         activeWindowToDesktop(VirtualDesktopManager::Direction::Left);
1594     }
1595 }
1596 
1597 void Workspace::slotWindowToDesktopUp()
1598 {
1599     if (USABLE_ACTIVE_WINDOW) {
1600         activeWindowToDesktop(VirtualDesktopManager::Direction::Up);
1601     }
1602 }
1603 
1604 void Workspace::slotWindowToDesktopDown()
1605 {
1606     if (USABLE_ACTIVE_WINDOW) {
1607         activeWindowToDesktop(VirtualDesktopManager::Direction::Down);
1608     }
1609 }
1610 
1611 /**
1612  * Kill Window feature, similar to xkill.
1613  */
1614 void Workspace::slotKillWindow()
1615 {
1616     if (!m_windowKiller) {
1617         m_windowKiller = std::make_unique<KillWindow>();
1618     }
1619     m_windowKiller->start();
1620 }
1621 
1622 /**
1623  * Switches to the nearest window in given direction.
1624  */
1625 void Workspace::switchWindow(Direction direction)
1626 {
1627     if (!m_activeWindow) {
1628         return;
1629     }
1630     Window *window = m_activeWindow;
1631     VirtualDesktop *desktop = VirtualDesktopManager::self()->currentDesktop();
1632 
1633     // Centre of the active window
1634     QPoint curPos(window->x() + window->width() / 2, window->y() + window->height() / 2);
1635 
1636     if (!switchWindow(window, direction, curPos, desktop)) {
1637         auto opposite = [&] {
1638             switch (direction) {
1639             case DirectionNorth:
1640                 return QPoint(curPos.x(), geometry().height());
1641             case DirectionSouth:
1642                 return QPoint(curPos.x(), 0);
1643             case DirectionEast:
1644                 return QPoint(0, curPos.y());
1645             case DirectionWest:
1646                 return QPoint(geometry().width(), curPos.y());
1647             default:
1648                 Q_UNREACHABLE();
1649             }
1650         };
1651 
1652         switchWindow(window, direction, opposite(), desktop);
1653     }
1654 }
1655 
1656 bool Workspace::switchWindow(Window *window, Direction direction, QPoint curPos, VirtualDesktop *desktop)
1657 {
1658     Window *switchTo = nullptr;
1659     int bestScore = 0;
1660 
1661     QList<Window *> clist = stackingOrder();
1662     for (auto i = clist.rbegin(); i != clist.rend(); ++i) {
1663         auto other = *i;
1664         if (!other->isClient()) {
1665             continue;
1666         }
1667         if (other->wantsTabFocus() && *i != window && other->isOnDesktop(desktop) && !other->isMinimized() && (*i)->isOnCurrentActivity()) {
1668             // Centre of the other window
1669             const QPoint otherCenter(other->x() + other->width() / 2, other->y() + other->height() / 2);
1670 
1671             int distance;
1672             int offset;
1673             switch (direction) {
1674             case DirectionNorth:
1675                 distance = curPos.y() - otherCenter.y();
1676                 offset = std::abs(otherCenter.x() - curPos.x());
1677                 break;
1678             case DirectionEast:
1679                 distance = otherCenter.x() - curPos.x();
1680                 offset = std::abs(otherCenter.y() - curPos.y());
1681                 break;
1682             case DirectionSouth:
1683                 distance = otherCenter.y() - curPos.y();
1684                 offset = std::abs(otherCenter.x() - curPos.x());
1685                 break;
1686             case DirectionWest:
1687                 distance = curPos.x() - otherCenter.x();
1688                 offset = std::abs(otherCenter.y() - curPos.y());
1689                 break;
1690             default:
1691                 distance = -1;
1692                 offset = -1;
1693             }
1694 
1695             if (distance > 0) {
1696                 // Inverse score
1697                 int score = distance + offset + ((offset * offset) / distance);
1698                 if (score < bestScore || !switchTo) {
1699                     switchTo = other;
1700                     bestScore = score;
1701                 }
1702             }
1703         }
1704     }
1705     if (switchTo) {
1706         activateWindow(switchTo);
1707     }
1708 
1709     return switchTo;
1710 }
1711 
1712 /**
1713  * Shows the window operations popup menu for the active window.
1714  */
1715 void Workspace::slotWindowOperations()
1716 {
1717     if (!m_activeWindow) {
1718         return;
1719     }
1720     const QPoint pos(m_activeWindow->frameGeometry().left() + m_activeWindow->frameMargins().left(),
1721                      m_activeWindow->frameGeometry().top() + m_activeWindow->frameMargins().top());
1722     showWindowMenu(QRect(pos, pos), m_activeWindow);
1723 }
1724 
1725 void Workspace::showWindowMenu(const QRect &pos, Window *window)
1726 {
1727     m_userActionsMenu->show(pos, window);
1728 }
1729 
1730 void Workspace::showApplicationMenu(const QRect &pos, Window *window, int actionId)
1731 {
1732     Workspace::self()->applicationMenu()->showApplicationMenu(window->pos().toPoint() + pos.bottomLeft(), window, actionId);
1733 }
1734 
1735 /**
1736  * Closes the active window.
1737  */
1738 void Workspace::slotWindowClose()
1739 {
1740     // TODO: why?
1741     //     if ( tab_box->isVisible())
1742     //         return;
1743     if (USABLE_ACTIVE_WINDOW) {
1744         performWindowOperation(m_activeWindow, Options::CloseOp);
1745     }
1746 }
1747 
1748 /**
1749  * Starts keyboard move mode for the active window.
1750  */
1751 void Workspace::slotWindowMove()
1752 {
1753     if (USABLE_ACTIVE_WINDOW) {
1754         performWindowOperation(m_activeWindow, Options::UnrestrictedMoveOp);
1755     }
1756 }
1757 
1758 /**
1759  * Starts keyboard resize mode for the active window.
1760  */
1761 void Workspace::slotWindowResize()
1762 {
1763     if (USABLE_ACTIVE_WINDOW) {
1764         performWindowOperation(m_activeWindow, Options::UnrestrictedResizeOp);
1765     }
1766 }
1767 
1768 #undef USABLE_ACTIVE_WINDOW
1769 
1770 void Window::setShortcut(const QString &_cut)
1771 {
1772     QString cut = rules()->checkShortcut(_cut);
1773     auto updateShortcut = [this](const QKeySequence &cut = QKeySequence()) {
1774         if (_shortcut == cut) {
1775             return;
1776         }
1777         _shortcut = cut;
1778         setShortcutInternal();
1779     };
1780     if (cut.isEmpty()) {
1781         updateShortcut();
1782         return;
1783     }
1784     if (cut == shortcut().toString()) {
1785         return; // no change
1786     }
1787     // Format:
1788     // base+(abcdef)<space>base+(abcdef)
1789     // E.g. Alt+Ctrl+(ABCDEF);Meta+X,Meta+(ABCDEF)
1790     if (!cut.contains(QLatin1Char('(')) && !cut.contains(QLatin1Char(')')) && !cut.contains(QLatin1String(" - "))) {
1791         if (workspace()->shortcutAvailable(cut, this)) {
1792             updateShortcut(QKeySequence(cut));
1793         } else {
1794             updateShortcut();
1795         }
1796         return;
1797     }
1798     static const QRegularExpression reg(QStringLiteral("(.*\\+)\\((.*)\\)"));
1799     QList<QKeySequence> keys;
1800     const QStringList groups = cut.split(QStringLiteral(" - "));
1801     for (auto it = groups.begin(); it != groups.end(); ++it) {
1802         const QRegularExpressionMatch match = reg.match(*it);
1803         if (match.hasMatch()) {
1804             const QString base = match.captured(1);
1805             const QString list = match.captured(2);
1806             for (int i = 0; i < list.length(); ++i) {
1807                 QKeySequence c(base + list[i]);
1808                 if (!c.isEmpty()) {
1809                     keys.append(c);
1810                 }
1811             }
1812         } else {
1813             // regexp doesn't match, so it should be a normal shortcut
1814             QKeySequence c(*it);
1815             if (!c.isEmpty()) {
1816                 keys.append(c);
1817             }
1818         }
1819     }
1820     for (auto it = keys.constBegin(); it != keys.cend(); ++it) {
1821         if (_shortcut == *it) { // current one is in the list
1822             return;
1823         }
1824     }
1825     for (auto it = keys.cbegin(); it != keys.cend(); ++it) {
1826         if (workspace()->shortcutAvailable(*it, this)) {
1827             updateShortcut(*it);
1828             return;
1829         }
1830     }
1831     updateShortcut();
1832 }
1833 
1834 void Window::setShortcutInternal()
1835 {
1836     updateCaption();
1837     workspace()->windowShortcutUpdated(this);
1838 }
1839 
1840 void X11Window::setShortcutInternal()
1841 {
1842     updateCaption();
1843 #if 0
1844     workspace()->windowShortcutUpdated(this);
1845 #else
1846     // Workaround for kwin<->kglobalaccel deadlock, when KWin has X grab and the kded
1847     // kglobalaccel module tries to create the key grab. KWin should preferably grab
1848     // they keys itself anyway :(.
1849     QTimer::singleShot(0, this, std::bind(&Workspace::windowShortcutUpdated, workspace(), this));
1850 #endif
1851 }
1852 
1853 bool Workspace::shortcutAvailable(const QKeySequence &cut, Window *ignore) const
1854 {
1855     if (ignore && cut == ignore->shortcut()) {
1856         return true;
1857     }
1858 
1859     // Check if the shortcut is already registered
1860     const QList<KGlobalShortcutInfo> registeredShortcuts = KGlobalAccel::globalShortcutsByKey(cut);
1861     for (const auto &shortcut : registeredShortcuts) {
1862         // Only return "not available" if is not a window activation shortcut, as it may be no longer valid
1863         if (!shortcut.uniqueName().startsWith(QStringLiteral("_k_session:"))) {
1864             return false;
1865         }
1866     }
1867     // Check now conflicts with activation shortcuts for current windows
1868     for (const auto window : std::as_const(m_windows)) {
1869         if (window != ignore && window->shortcut() == cut) {
1870             return false;
1871         }
1872     }
1873     return true;
1874 }
1875 
1876 } // namespace
1877 
1878 #include "moc_useractions.cpp"