File indexing completed on 2024-12-08 13:22:04
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