File indexing completed on 2024-11-10 04:57:12
0001 /* 0002 SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "windowvieweffect.h" 0008 #include "effect/effecthandler.h" 0009 #include "windowview1adaptor.h" 0010 #include "windowviewconfig.h" 0011 0012 #include <QAction> 0013 #include <QQuickItem> 0014 #include <QTimer> 0015 0016 #include <KGlobalAccel> 0017 #include <KLocalizedString> 0018 0019 namespace KWin 0020 { 0021 0022 static const QString s_dbusServiceName = QStringLiteral("org.kde.KWin.Effect.WindowView1"); 0023 static const QString s_dbusObjectPath = QStringLiteral("/org/kde/KWin/Effect/WindowView1"); 0024 0025 WindowViewEffect::WindowViewEffect() 0026 : m_shutdownTimer(new QTimer(this)) 0027 , m_exposeAction(new QAction(this)) 0028 , m_exposeAllAction(new QAction(this)) 0029 , m_exposeClassAction(new QAction(this)) 0030 , m_exposeClassCurrentDesktopAction(new QAction(this)) 0031 { 0032 qmlRegisterUncreatableType<WindowViewEffect>("org.kde.KWin.Effect.WindowView", 1, 0, "WindowView", QStringLiteral("WindowView cannot be created in QML")); 0033 WindowViewConfig::instance(effects->config()); 0034 new WindowView1Adaptor(this); 0035 0036 QDBusConnection::sessionBus().registerObject(s_dbusObjectPath, this); 0037 QDBusConnection::sessionBus().registerService(s_dbusServiceName); 0038 0039 m_shutdownTimer->setSingleShot(true); 0040 connect(m_shutdownTimer, &QTimer::timeout, this, &WindowViewEffect::realDeactivate); 0041 connect(effects, &EffectsHandler::screenAboutToLock, this, &WindowViewEffect::realDeactivate); 0042 0043 setSource(QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kwin/effects/windowview/qml/main.qml")))); 0044 0045 m_exposeAction->setObjectName(QStringLiteral("Expose")); 0046 m_exposeAction->setText(i18n("Toggle Present Windows (Current desktop)")); 0047 KGlobalAccel::self()->setDefaultShortcut(m_exposeAction, QList<QKeySequence>() << (Qt::CTRL | Qt::Key_F9)); 0048 KGlobalAccel::self()->setShortcut(m_exposeAction, QList<QKeySequence>() << (Qt::CTRL | Qt::Key_F9)); 0049 m_shortcut = KGlobalAccel::self()->shortcut(m_exposeAction); 0050 connect(m_exposeAction, &QAction::triggered, this, [this]() { 0051 toggleMode(ModeCurrentDesktop); 0052 }); 0053 0054 m_exposeAllAction->setObjectName(QStringLiteral("ExposeAll")); 0055 m_exposeAllAction->setText(i18n("Toggle Present Windows (All desktops)")); 0056 KGlobalAccel::self()->setDefaultShortcut(m_exposeAllAction, QList<QKeySequence>() << (Qt::CTRL | Qt::Key_F10) << Qt::Key_LaunchC); 0057 KGlobalAccel::self()->setShortcut(m_exposeAllAction, QList<QKeySequence>() << (Qt::CTRL | Qt::Key_F10) << Qt::Key_LaunchC); 0058 m_shortcutAll = KGlobalAccel::self()->shortcut(m_exposeAllAction); 0059 connect(m_exposeAllAction, &QAction::triggered, this, [this]() { 0060 toggleMode(ModeAllDesktops); 0061 }); 0062 0063 m_exposeClassAction->setObjectName(QStringLiteral("ExposeClass")); 0064 m_exposeClassAction->setText(i18n("Toggle Present Windows (Window class)")); 0065 KGlobalAccel::self()->setDefaultShortcut(m_exposeClassAction, QList<QKeySequence>() << (Qt::CTRL | Qt::Key_F7)); 0066 KGlobalAccel::self()->setShortcut(m_exposeClassAction, QList<QKeySequence>() << (Qt::CTRL | Qt::Key_F7)); 0067 m_shortcutClass = KGlobalAccel::self()->shortcut(m_exposeClassAction); 0068 connect(m_exposeClassAction, &QAction::triggered, this, [this]() { 0069 toggleMode(ModeWindowClass); 0070 }); 0071 0072 m_exposeClassCurrentDesktopAction->setObjectName(QStringLiteral("ExposeClassCurrentDesktop")); 0073 m_exposeClassCurrentDesktopAction->setText(i18n("Toggle Present Windows (Window class on current desktop)")); 0074 KGlobalAccel::self()->setDefaultShortcut(m_exposeClassCurrentDesktopAction, QList<QKeySequence>()); // no default shortcut 0075 KGlobalAccel::self()->setShortcut(m_exposeClassCurrentDesktopAction, QList<QKeySequence>()); 0076 m_shortcutClassCurrentDesktop = KGlobalAccel::self()->shortcut(m_exposeClassCurrentDesktopAction); 0077 connect(m_exposeClassCurrentDesktopAction, &QAction::triggered, this, [this]() { 0078 toggleMode(ModeWindowClassCurrentDesktop); 0079 }); 0080 0081 connect(KGlobalAccel::self(), &KGlobalAccel::globalShortcutChanged, this, [this](QAction *action, const QKeySequence &seq) { 0082 if (action->objectName() == QStringLiteral("Expose")) { 0083 m_shortcut.clear(); 0084 m_shortcut.append(seq); 0085 } else if (action->objectName() == QStringLiteral("ExposeAll")) { 0086 m_shortcutAll.clear(); 0087 m_shortcutAll.append(seq); 0088 } else if (action->objectName() == QStringLiteral("ExposeClass")) { 0089 m_shortcutClass.clear(); 0090 m_shortcutClass.append(seq); 0091 } else if (action->objectName() == QStringLiteral("ExposeClassCurrentDesktop")) { 0092 m_shortcutClassCurrentDesktop.clear(); 0093 m_shortcutClassCurrentDesktop.append(seq); 0094 } 0095 }); 0096 0097 m_realtimeToggleAction = new QAction(this); 0098 connect(m_realtimeToggleAction, &QAction::triggered, this, [this]() { 0099 if (m_status == Status::Deactivating) { 0100 if (m_partialActivationFactor < 0.5) { 0101 deactivate(animationDuration()); 0102 } else { 0103 cancelPartialDeactivate(); 0104 } 0105 } else if (m_status == Status::Activating) { 0106 if (m_partialActivationFactor > 0.5) { 0107 activate(); 0108 } else { 0109 cancelPartialActivate(); 0110 } 0111 } 0112 }); 0113 0114 reconfigure(ReconfigureAll); 0115 } 0116 0117 WindowViewEffect::~WindowViewEffect() 0118 { 0119 QDBusConnection::sessionBus().unregisterService(s_dbusServiceName); 0120 QDBusConnection::sessionBus().unregisterObject(s_dbusObjectPath); 0121 } 0122 0123 int WindowViewEffect::animationDuration() const 0124 { 0125 return m_animationDuration; 0126 } 0127 0128 void WindowViewEffect::setAnimationDuration(int duration) 0129 { 0130 if (m_animationDuration != duration) { 0131 m_animationDuration = duration; 0132 Q_EMIT animationDurationChanged(); 0133 } 0134 } 0135 0136 int WindowViewEffect::layout() const 0137 { 0138 return m_layout; 0139 } 0140 0141 void WindowViewEffect::setLayout(int layout) 0142 { 0143 if (m_layout != layout) { 0144 m_layout = layout; 0145 Q_EMIT layoutChanged(); 0146 } 0147 } 0148 0149 bool WindowViewEffect::ignoreMinimized() const 0150 { 0151 return WindowViewConfig::ignoreMinimized(); 0152 } 0153 0154 int WindowViewEffect::requestedEffectChainPosition() const 0155 { 0156 return 70; 0157 } 0158 0159 void WindowViewEffect::reconfigure(ReconfigureFlags) 0160 { 0161 WindowViewConfig::self()->read(); 0162 setAnimationDuration(animationTime(300)); 0163 setLayout(WindowViewConfig::layoutMode()); 0164 0165 for (ElectricBorder border : std::as_const(m_borderActivate)) { 0166 effects->unreserveElectricBorder(border, this); 0167 } 0168 for (ElectricBorder border : std::as_const(m_borderActivateAll)) { 0169 effects->unreserveElectricBorder(border, this); 0170 } 0171 0172 m_borderActivate.clear(); 0173 m_borderActivateAll.clear(); 0174 m_borderActivateClass.clear(); 0175 0176 const auto borderActivate = WindowViewConfig::borderActivate(); 0177 for (int i : borderActivate) { 0178 m_borderActivate.append(ElectricBorder(i)); 0179 effects->reserveElectricBorder(ElectricBorder(i), this); 0180 } 0181 const auto activateAll = WindowViewConfig::borderActivateAll(); 0182 for (int i : activateAll) { 0183 m_borderActivateAll.append(ElectricBorder(i)); 0184 effects->reserveElectricBorder(ElectricBorder(i), this); 0185 } 0186 const auto activateClass = WindowViewConfig::borderActivateClass(); 0187 for (int i : activateClass) { 0188 m_borderActivateClass.append(ElectricBorder(i)); 0189 effects->reserveElectricBorder(ElectricBorder(i), this); 0190 } 0191 const auto activateClassCurrentDesktop = WindowViewConfig::borderActivateClassCurrentDesktop(); 0192 for (int i : activateClassCurrentDesktop) { 0193 m_borderActivateClassCurrentDesktop.append(ElectricBorder(i)); 0194 effects->reserveElectricBorder(ElectricBorder(i), this); 0195 } 0196 0197 auto touchCallback = [this](ElectricBorder border, const QPointF &deltaProgress, const Output *screen) { 0198 if (m_status == Status::Active) { 0199 return; 0200 } 0201 if (m_touchBorderActivate.contains(border)) { 0202 setMode(ModeCurrentDesktop); 0203 } else if (m_touchBorderActivateAll.contains(border)) { 0204 setMode(ModeAllDesktops); 0205 } else if (m_touchBorderActivateClass.contains(border)) { 0206 setMode(ModeWindowClass); 0207 } else if (m_touchBorderActivateClassCurrentDesktop.contains(border)) { 0208 setMode(ModeWindowClassCurrentDesktop); 0209 } 0210 const int maxDelta = 500; // Arbitrary logical pixels value seems to behave better than scaledScreenSize 0211 if (border == ElectricTop || border == ElectricBottom) { 0212 partialActivate(std::min(1.0, std::abs(deltaProgress.y()) / maxDelta)); 0213 } else { 0214 partialActivate(std::min(1.0, std::abs(deltaProgress.x()) / maxDelta)); 0215 } 0216 }; 0217 0218 QList<int> touchActivateBorders = WindowViewConfig::touchBorderActivate(); 0219 for (const int &border : touchActivateBorders) { 0220 m_touchBorderActivate.append(ElectricBorder(border)); 0221 effects->registerRealtimeTouchBorder(ElectricBorder(border), m_realtimeToggleAction, touchCallback); 0222 } 0223 touchActivateBorders = WindowViewConfig::touchBorderActivateAll(); 0224 for (const int &border : touchActivateBorders) { 0225 m_touchBorderActivateAll.append(ElectricBorder(border)); 0226 effects->registerRealtimeTouchBorder(ElectricBorder(border), m_realtimeToggleAction, touchCallback); 0227 } 0228 touchActivateBorders = WindowViewConfig::touchBorderActivateClass(); 0229 for (const int &border : touchActivateBorders) { 0230 m_touchBorderActivateClass.append(ElectricBorder(border)); 0231 effects->registerRealtimeTouchBorder(ElectricBorder(border), m_realtimeToggleAction, touchCallback); 0232 } 0233 touchActivateBorders = WindowViewConfig::touchBorderActivateClassCurrentDesktop(); 0234 for (const int &border : touchActivateBorders) { 0235 m_touchBorderActivateClassCurrentDesktop.append(ElectricBorder(border)); 0236 effects->registerRealtimeTouchBorder(ElectricBorder(border), m_realtimeToggleAction, touchCallback); 0237 } 0238 } 0239 0240 void WindowViewEffect::grabbedKeyboardEvent(QKeyEvent *e) 0241 { 0242 if (e->type() == QEvent::KeyPress) { 0243 // check for global shortcuts 0244 // HACK: keyboard grab disables the global shortcuts so we have to check for global shortcut (bug 156155) 0245 if (m_mode == ModeCurrentDesktop && m_shortcut.contains(e->key() | e->modifiers())) { 0246 toggleMode(ModeCurrentDesktop); 0247 return; 0248 } else if (m_mode == ModeAllDesktops && m_shortcutAll.contains(e->key() | e->modifiers())) { 0249 toggleMode(ModeAllDesktops); 0250 return; 0251 } else if (m_mode == ModeWindowClass && m_shortcutClass.contains(e->key() | e->modifiers())) { 0252 toggleMode(ModeWindowClass); 0253 return; 0254 } else if (m_mode == ModeWindowClassCurrentDesktop && m_shortcutClassCurrentDesktop.contains(e->key() | e->modifiers())) { 0255 toggleMode(ModeWindowClassCurrentDesktop); 0256 return; 0257 } else if (e->key() == Qt::Key_Escape) { 0258 deactivate(animationDuration()); 0259 } 0260 } 0261 QuickSceneEffect::grabbedKeyboardEvent(e); 0262 } 0263 0264 qreal WindowViewEffect::partialActivationFactor() const 0265 { 0266 return m_partialActivationFactor; 0267 } 0268 0269 void WindowViewEffect::setPartialActivationFactor(qreal factor) 0270 { 0271 if (m_partialActivationFactor != factor) { 0272 m_partialActivationFactor = factor; 0273 Q_EMIT partialActivationFactorChanged(); 0274 } 0275 } 0276 0277 bool WindowViewEffect::gestureInProgress() const 0278 { 0279 return m_gestureInProgress; 0280 } 0281 0282 void WindowViewEffect::setGestureInProgress(bool gesture) 0283 { 0284 if (m_gestureInProgress != gesture) { 0285 m_gestureInProgress = gesture; 0286 Q_EMIT gestureInProgressChanged(); 0287 } 0288 } 0289 0290 void WindowViewEffect::activate(const QStringList &windowIds) 0291 { 0292 setMode(ModeWindowGroup); 0293 QList<QUuid> internalIds; 0294 internalIds.reserve(windowIds.count()); 0295 for (const QString &windowId : windowIds) { 0296 if (const auto window = effects->findWindow(QUuid(windowId))) { 0297 internalIds.append(window->internalId()); 0298 continue; 0299 } 0300 0301 // On X11, the task manager can pass a list with X11 ids. 0302 bool ok; 0303 if (const long legacyId = windowId.toLong(&ok); ok) { 0304 if (const auto window = effects->findWindow(legacyId)) { 0305 internalIds.append(window->internalId()); 0306 } 0307 } 0308 } 0309 if (!internalIds.isEmpty()) { 0310 setSelectedIds(internalIds); 0311 m_searchText = QString(); 0312 setRunning(true); 0313 } 0314 } 0315 0316 void WindowViewEffect::activate() 0317 { 0318 if (effects->isScreenLocked()) { 0319 return; 0320 } 0321 0322 m_status = Status::Active; 0323 setSelectedIds(QList<QUuid>()); 0324 0325 setGestureInProgress(false); 0326 setPartialActivationFactor(0); 0327 0328 // This one should be the last. 0329 m_searchText = QString(); 0330 setRunning(true); 0331 } 0332 0333 void WindowViewEffect::partialActivate(qreal factor) 0334 { 0335 if (effects->isScreenLocked()) { 0336 return; 0337 } 0338 0339 m_status = Status::Activating; 0340 0341 setPartialActivationFactor(factor); 0342 setGestureInProgress(true); 0343 0344 // This one should be the last. 0345 m_searchText = QString(); 0346 setRunning(true); 0347 } 0348 0349 void WindowViewEffect::cancelPartialActivate() 0350 { 0351 deactivate(animationDuration()); 0352 } 0353 0354 void WindowViewEffect::deactivate(int timeout) 0355 { 0356 const auto screens = effects->screens(); 0357 for (const auto screen : screens) { 0358 if (QuickSceneView *view = viewForScreen(screen)) { 0359 QMetaObject::invokeMethod(view->rootItem(), "stop"); 0360 } 0361 } 0362 m_shutdownTimer->start(timeout); 0363 0364 setGestureInProgress(false); 0365 setPartialActivationFactor(0.0); 0366 } 0367 0368 void WindowViewEffect::partialDeactivate(qreal factor) 0369 { 0370 m_status = Status::Deactivating; 0371 0372 setPartialActivationFactor(1.0 - factor); 0373 setGestureInProgress(true); 0374 } 0375 0376 void WindowViewEffect::cancelPartialDeactivate() 0377 { 0378 activate(); 0379 } 0380 0381 void WindowViewEffect::realDeactivate() 0382 { 0383 setRunning(false); 0384 m_status = Status::Inactive; 0385 } 0386 0387 void WindowViewEffect::setMode(WindowViewEffect::PresentWindowsMode mode) 0388 { 0389 if (mode == m_mode) { 0390 return; 0391 } 0392 0393 if (mode != ModeWindowGroup) { 0394 setSelectedIds(QList<QUuid>()); 0395 } 0396 0397 m_mode = mode; 0398 Q_EMIT modeChanged(); 0399 } 0400 0401 void WindowViewEffect::toggleMode(PresentWindowsMode mode) 0402 { 0403 if (!isRunning()) { 0404 setMode(mode); 0405 activate(); 0406 } else { 0407 if (m_mode != mode) { 0408 setMode(mode); 0409 } else { 0410 deactivate(animationDuration()); 0411 } 0412 } 0413 } 0414 0415 WindowViewEffect::PresentWindowsMode WindowViewEffect::mode() const 0416 { 0417 return m_mode; 0418 } 0419 0420 bool WindowViewEffect::borderActivated(ElectricBorder border) 0421 { 0422 if (effects->activeFullScreenEffect() && effects->activeFullScreenEffect() != this) { 0423 return true; 0424 } 0425 0426 if (m_borderActivate.contains(border)) { 0427 toggleMode(ModeCurrentDesktop); 0428 } else if (m_borderActivateAll.contains(border)) { 0429 toggleMode(ModeAllDesktops); 0430 } else if (m_borderActivateClass.contains(border)) { 0431 toggleMode(ModeWindowClass); 0432 } else if (m_touchBorderActivateClassCurrentDesktop.contains(border)) { 0433 toggleMode(ModeWindowClassCurrentDesktop); 0434 } else { 0435 return false; 0436 } 0437 0438 return true; 0439 } 0440 0441 void WindowViewEffect::setSelectedIds(const QList<QUuid> &ids) 0442 { 0443 if (m_windowIds != ids) { 0444 m_windowIds = ids; 0445 Q_EMIT selectedIdsChanged(); 0446 } 0447 } 0448 0449 } // namespace KWin 0450 0451 #include "moc_windowvieweffect.cpp"