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"