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

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