File indexing completed on 2024-04-14 05:24:38

0001 /*
0002     SPDX-FileCopyrightText: 2016 Smith AR <audoban@openmailbox.org>
0003     SPDX-FileCopyrightText: 2016 Michail Vourlakos <mvourlakos@gmail.com>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "globalshortcuts.h"
0009 
0010 // local
0011 #include "modifiertracker.h"
0012 #include "shortcutstracker.h"
0013 #include "../lattecorona.h"
0014 #include "../layout/centrallayout.h"
0015 #include "../layouts/manager.h"
0016 #include "../layouts/synchronizer.h"
0017 #include "../settings/settingsdialog/settingsdialog.h"
0018 #include "../settings/universalsettings.h"
0019 #include "../view/containmentinterface.h"
0020 #include "../view/view.h"
0021 
0022 // C++
0023 #include <array>
0024 
0025 // Qt
0026 #include <QAction>
0027 #include <QDebug>
0028 #include <QQuickItem>
0029 #include <QMetaMethod>
0030 #include <QX11Info>
0031 
0032 // KDE
0033 #include <KActionCollection>
0034 #include <KGlobalAccel>
0035 #include <KLocalizedString>
0036 #include <KPluginMetaData>
0037 
0038 // Plasma
0039 #include <Plasma/Applet>
0040 #include <Plasma/Containment>
0041 
0042 
0043 namespace Latte {
0044 
0045 GlobalShortcuts::GlobalShortcuts(QObject *parent)
0046     : QObject(parent)
0047 {
0048     m_corona = qobject_cast<Latte::Corona *>(parent);
0049     m_modifierTracker = new ShortcutsPart::ModifierTracker(this);
0050     m_shortcutsTracker = new ShortcutsPart::ShortcutsTracker(this);
0051 
0052     if (m_corona) {
0053         init();
0054     }
0055 
0056     m_hideViewsTimer.setSingleShot(true);
0057 
0058     if (QX11Info::isPlatformX11()) {
0059         //in X11 the timer is a poller that checks to see if the modifier keys
0060         //from user global shortcut have been released
0061         m_hideViewsTimer.setInterval(300);
0062     } else {
0063         //on wayland in acting just as simple timer that hides the view afterwards
0064         m_hideViewsTimer.setInterval(2500);
0065     }
0066 
0067     connect(&m_hideViewsTimer, &QTimer::timeout, this, &GlobalShortcuts::hideViewsTimerSlot);
0068 }
0069 
0070 GlobalShortcuts::~GlobalShortcuts()
0071 {
0072     if (m_modifierTracker) {
0073         m_modifierTracker->deleteLater();
0074     }
0075 
0076     if (m_shortcutsTracker) {
0077         m_shortcutsTracker->deleteLater();
0078     }
0079 }
0080 
0081 void GlobalShortcuts::init()
0082 {
0083     KActionCollection *generalActions = new KActionCollection(m_corona);
0084 
0085     //show-hide the main view in the primary screen
0086     QAction *showAction = generalActions->addAction(QStringLiteral("show latte view"));
0087     showAction->setText(i18n("Show Latte Dock/Panel"));
0088     showAction->setShortcut(QKeySequence(Qt::META + '`'));
0089     KGlobalAccel::setGlobalShortcut(showAction, QKeySequence(Qt::META + '`'));
0090     connect(showAction, &QAction::triggered, this, [this]() {
0091         showViews();
0092     });
0093 
0094     //show-cycle between Latte settings windows
0095     QAction *settingsAction = generalActions->addAction(QStringLiteral("show view settings"));
0096     settingsAction->setText(i18n("Cycle Through Dock/Panel Settings Windows"));
0097     KGlobalAccel::setGlobalShortcut(settingsAction, QKeySequence(Qt::META + Qt::Key_A));
0098     connect(settingsAction, &QAction::triggered, this, [this] {
0099         m_modifierTracker->cancelMetaPressed();
0100         showSettings();
0101     });
0102 
0103     //show the layouts editor
0104     QAction *layoutsAction = generalActions->addAction(QStringLiteral("show latte global settings"));
0105     layoutsAction->setText(i18n("Show Latte Global Settings"));
0106     layoutsAction->setShortcut(QKeySequence());
0107     KGlobalAccel::setGlobalShortcut(layoutsAction, QKeySequence());
0108     connect(layoutsAction, &QAction::triggered, this, [this]() {
0109         m_modifierTracker->cancelMetaPressed();
0110         m_corona->layoutsManager()->showLatteSettingsDialog(Settings::Dialog::PreferencesPage, true);
0111     });
0112 
0113     KActionCollection *taskbarActions = new KActionCollection(m_corona);
0114 
0115     //activate actions [1-9]
0116     for (int i = 1; i < 10; ++i) {
0117         const int entryNumber = i;
0118         const Qt::Key key = static_cast<Qt::Key>(Qt::Key_0 + i);
0119 
0120         QAction *action = taskbarActions->addAction(QStringLiteral("activate entry %1").arg(QString::number(entryNumber)));
0121         action->setText(i18n("Activate Entry %1", entryNumber));
0122         action->setShortcut(QKeySequence(Qt::META + key));
0123         KGlobalAccel::setGlobalShortcut(action, QKeySequence(Qt::META + key));
0124         connect(action, &QAction::triggered, this, [this, i] {
0125             // qDebug() << "meta action...";
0126             m_modifierTracker->cancelMetaPressed();
0127             activateEntry(i, static_cast<Qt::Key>(Qt::META));
0128         });
0129     }
0130 
0131     //! Array that is used to register correctly actions for task index>=10 and <19
0132     std::array<Qt::Key, 10> keysAboveTen{  Qt::Key_0,  Qt::Key_Z,  Qt::Key_X, Qt::Key_C, Qt::Key_V, Qt::Key_B, Qt::Key_N, Qt::Key_M, Qt::Key_Comma, Qt::Key_Period };
0133 
0134     //activate actions [10-19]
0135     for (int i = 10; i < 20; ++i) {
0136         QAction *action = taskbarActions->addAction(QStringLiteral("activate entry %1").arg(QString::number(i)));
0137         action->setText(i18n("Activate Entry %1", i));
0138         action->setShortcut(QKeySequence(Qt::META + keysAboveTen[i - 10]));
0139         KGlobalAccel::setGlobalShortcut(action, QKeySequence(Qt::META + keysAboveTen[i - 10]));
0140         connect(action, &QAction::triggered, this, [this, i] {
0141             m_modifierTracker->cancelMetaPressed();
0142             activateEntry(i, static_cast<Qt::Key>(Qt::META));
0143         });
0144     }
0145 
0146     //new instance actions [1-9]
0147     for (int i = 1; i < 10; ++i) {
0148         const int entryNumber = i;
0149         const Qt::Key key = static_cast<Qt::Key>(Qt::Key_0 + i);
0150 
0151         QAction *action = taskbarActions->addAction(QStringLiteral("new instance for entry %1").arg(QString::number(entryNumber)));
0152         action->setText(i18n("New Instance for Entry %1", entryNumber));
0153         KGlobalAccel::setGlobalShortcut(action, QKeySequence(Qt::META + Qt::CTRL + key));
0154         connect(action, &QAction::triggered, this, [this, i] {
0155             // qDebug() << "meta + ctrl + action...";
0156             m_modifierTracker->cancelMetaPressed();
0157             activateEntry(i, static_cast<Qt::Key>(Qt::CTRL));
0158         });
0159     }
0160 
0161     //new instance actions [10-19]
0162     for (int i = 10; i < 20; ++i) {
0163         QAction *action = taskbarActions->addAction(QStringLiteral("new instance for entry %1").arg(QString::number(i)));
0164         action->setText(i18n("New Instance for Entry %1", i));
0165         KGlobalAccel::setGlobalShortcut(action, QKeySequence(Qt::META + Qt::CTRL + keysAboveTen[i - 10]));
0166         connect(action, &QAction::triggered, this, [this, i] {
0167             m_modifierTracker->cancelMetaPressed();
0168             activateEntry(i, static_cast<Qt::Key>(Qt::CTRL));
0169         });
0170     }
0171 
0172     m_singleMetaAction = new QAction(this);
0173     m_singleMetaAction->setShortcut(QKeySequence(Qt::META));
0174 
0175     connect(m_corona->universalSettings(), &UniversalSettings::metaPressAndHoldEnabledChanged , this, [&]() {
0176         if (!m_corona->universalSettings()->metaPressAndHoldEnabled()) {
0177             m_modifierTracker->blockModifierTracking(Qt::Key_Super_L);
0178             m_modifierTracker->blockModifierTracking(Qt::Key_Super_R);
0179         } else {
0180             m_modifierTracker->unblockModifierTracking(Qt::Key_Super_L);
0181             m_modifierTracker->unblockModifierTracking(Qt::Key_Super_R);
0182         }
0183     });
0184 
0185     //display shortcut badges while holding Meta
0186     connect(m_modifierTracker, &ShortcutsPart::ModifierTracker::metaModifierPressed, this, [&]() {
0187         m_metaShowedViews = true;
0188         showViews();
0189     });
0190 }
0191 
0192 ShortcutsPart::ShortcutsTracker *GlobalShortcuts::shortcutsTracker() const
0193 {
0194     return m_shortcutsTracker;
0195 }
0196 
0197 Latte::View *GlobalShortcuts::highestApplicationLauncherView(const QList<Latte::View *> &views) const
0198 {
0199     if (views.isEmpty()) {
0200         return nullptr;
0201     }
0202 
0203     Latte::View *highestPriorityView{nullptr};
0204 
0205     for (const auto view : views) {
0206         if (view->extendedInterface()->applicationLauncherHasGlobalShortcut()) {
0207             highestPriorityView = view;
0208             break;
0209         }
0210     }
0211 
0212     if (!highestPriorityView) {
0213         for (const auto view : views) {
0214             if (view->extendedInterface()->containsApplicationLauncher()) {
0215                 highestPriorityView = view;
0216                 break;
0217             }
0218         }
0219     }
0220 
0221     return highestPriorityView;
0222 }
0223 
0224 //! Activate launcher menu through dbus interface
0225 void GlobalShortcuts::activateLauncherMenu()
0226 {
0227     if (m_metaShowedViews) {
0228         return;
0229     }
0230 
0231     QList<Latte::View *> sortedViews = m_corona->layoutsManager()->synchronizer()->sortedCurrentViews();
0232 
0233     Latte::View *highestPriorityView = highestApplicationLauncherView(sortedViews);
0234 
0235     if (highestPriorityView) {
0236         if (!highestPriorityView->visibility()->isShownFully() && highestPriorityView->extendedInterface()->applicationLauncherInPopup()) {
0237             m_lastInvokedAction = m_singleMetaAction;
0238 
0239             //! wait for view to fully shown
0240             connect(highestPriorityView->visibility(), &Latte::ViewPart::VisibilityManager::isShownFullyChanged, this, [&, highestPriorityView](){
0241                 if (highestPriorityView->visibility()->isShownFully()) {
0242                     highestPriorityView->extendedInterface()->toggleAppletExpanded(highestPriorityView->extendedInterface()->applicationLauncherId());
0243                     //!remove that signal tracking
0244                     disconnect(highestPriorityView->visibility(), &Latte::ViewPart::VisibilityManager::isShownFullyChanged, this, nullptr);
0245                 }
0246             });
0247 
0248             //! That signal is removed from Latte::View only when the popup is really shown!
0249             highestPriorityView->visibility()->addBlockHidingEvent(SHORTCUTBLOCKHIDINGTYPE);
0250 
0251         } else {
0252             highestPriorityView->extendedInterface()->toggleAppletExpanded(highestPriorityView->extendedInterface()->applicationLauncherId());
0253         }
0254     }
0255 }
0256 
0257 bool GlobalShortcuts::activatePlasmaTaskManager(const Latte::View *view, int index, Qt::Key modifier, bool *delayedExecution)
0258 {
0259     bool activation{modifier == static_cast<Qt::Key>(Qt::META)};
0260     bool newInstance{!activation};
0261 
0262     if (!view->visibility()->isShownFully()) {
0263         //! wait for view to fully shown
0264         connect(view->visibility(), &Latte::ViewPart::VisibilityManager::isShownFullyChanged, this, [&, view, index, activation](){
0265             if (view->visibility()->isShownFully()) {
0266                 if (activation) {
0267                     view->extendedInterface()->activatePlasmaTask(index);
0268                 } else {
0269                     view->extendedInterface()->newInstanceForPlasmaTask(index);
0270                 }
0271                 //!remove that signal tracking
0272                 disconnect(view->visibility(), &Latte::ViewPart::VisibilityManager::isShownFullyChanged, this, nullptr);
0273             }
0274         });
0275 
0276         *delayedExecution = true;
0277 
0278         return true;
0279     } else {
0280         *delayedExecution = false;
0281 
0282         return (activation ? view->extendedInterface()->activatePlasmaTask(index) : view->extendedInterface()->newInstanceForPlasmaTask(index));
0283     }
0284 }
0285 
0286 bool GlobalShortcuts::activateLatteEntry(Latte::View *view, int index, Qt::Key modifier, bool *delayedExecution)
0287 {
0288     bool activation{modifier == static_cast<Qt::Key>(Qt::META)};
0289     bool newInstance{!activation};
0290 
0291     int appletId = view->extendedInterface()->appletIdForVisualIndex(index);
0292     bool hasPopUp {(appletId>-1 && view->extendedInterface()->appletIsExpandable(appletId))};
0293 
0294     if (!view->visibility()->isShownFully() && hasPopUp) {
0295         //! wait for view to fully shown
0296         connect(view->visibility(), &Latte::ViewPart::VisibilityManager::isShownFullyChanged, this, [&, view, index, activation](){
0297             if (view->visibility()->isShownFully()) {
0298                 if (activation) {
0299                     view->extendedInterface()->activateEntry(index);
0300                 } else {
0301                     view->extendedInterface()->newInstanceForEntry(index);
0302                 }
0303                 //!remove that signal tracking
0304                 disconnect(view->visibility(), &Latte::ViewPart::VisibilityManager::isShownFullyChanged, this, nullptr);
0305             }
0306         });
0307 
0308         *delayedExecution = true;
0309 
0310         return true;
0311     } else {
0312         *delayedExecution = false;
0313 
0314         return (activation ? view->extendedInterface()->activateEntry(index) : view->extendedInterface()->newInstanceForEntry(index));
0315     }
0316 }
0317 
0318 bool GlobalShortcuts::activateEntryForView(Latte::View *view, int index, Qt::Key modifier)
0319 {
0320     if (!view) {
0321         return false;
0322     }
0323 
0324     bool delayed{false};
0325 
0326     bool executed = ((!view->extendedInterface()->hasLatteTasks() && view->extendedInterface()->hasPlasmaTasks()
0327                       && activatePlasmaTaskManager(view, index, modifier, &delayed))
0328                      || activateLatteEntry(view, index, modifier, &delayed));
0329 
0330     if (executed) {
0331         if (!m_hideViews.contains(view)) {
0332             m_hideViews.append(view);
0333         }
0334 
0335         if (delayed) {
0336             view->visibility()->addBlockHidingEvent(SHORTCUTBLOCKHIDINGTYPE);
0337             m_hideViewsTimer.start();
0338         }
0339 
0340         return true;
0341     }
0342 
0343     return false;
0344 }
0345 
0346 //! Activate task manager entry
0347 void GlobalShortcuts::activateEntry(int index, Qt::Key modifier)
0348 {
0349     m_lastInvokedAction = dynamic_cast<QAction *>(sender());
0350 
0351     QList<Latte::View *> sortedViews = m_corona->layoutsManager()->synchronizer()->sortedCurrentViews();
0352 
0353     Latte::View *highest{nullptr};
0354 
0355     for (const auto view : sortedViews) {
0356         if (view->isPreferredForShortcuts()) {
0357             highest = view;
0358             break;
0359         }
0360     }
0361 
0362     if (highest) {
0363         activateEntryForView(highest, index, modifier);
0364     } else {
0365         for (const auto view : sortedViews) {
0366             if (activateEntryForView(view, index, modifier)) {
0367                 return;
0368             }
0369         }
0370     }
0371 }
0372 
0373 //! update badge for specific view item
0374 void GlobalShortcuts::updateViewItemBadge(QString identifier, QString value)
0375 {
0376     QList<Latte::View *> views = m_corona->layoutsManager()->synchronizer()->currentViews();
0377 
0378     // update badges in all Latte Tasks plasmoids
0379     for (const auto &view : views) {
0380         view->extendedInterface()->updateBadgeForLatteTask(identifier, value);
0381     }
0382 }
0383 
0384 void GlobalShortcuts::showViews()
0385 {
0386     m_lastInvokedAction = dynamic_cast<QAction *>(sender());
0387     if (!m_lastInvokedAction) {
0388         // when holding Meta
0389         m_lastInvokedAction = m_singleMetaAction;
0390     }
0391 
0392     QList<Latte::View *> sortedViews = m_corona->layoutsManager()->synchronizer()->sortedCurrentViews();
0393 
0394     Latte::View *viewWithTasks{nullptr};
0395     Latte::View *viewWithMeta{nullptr};
0396 
0397     bool hasPreferredForShortcutsView{false};
0398 
0399     for (const auto view : sortedViews) {
0400         if (view->isPreferredForShortcuts()) {
0401             hasPreferredForShortcutsView = true;
0402             break;
0403         }
0404     }
0405 
0406     for(const auto view : sortedViews) {
0407         if (!viewWithTasks
0408                 && (!hasPreferredForShortcutsView || view->isPreferredForShortcuts())) {
0409             viewWithTasks = view;
0410             break;
0411         }
0412     }
0413 
0414     if (m_corona->universalSettings()->kwin_metaForwardedToLatte()) {
0415         viewWithMeta = highestApplicationLauncherView(sortedViews);
0416     }
0417 
0418     bool viewFound{false};
0419 
0420     if (!m_hideViewsTimer.isActive()) {
0421         m_hideViews.clear();
0422     }
0423 
0424     //! show view that contains tasks plasmoid
0425     if (viewWithTasks) {
0426         viewFound = true;
0427 
0428         bool showMeta = (viewWithMeta && (viewWithMeta == viewWithTasks));
0429 
0430         viewWithTasks->extendedInterface()->showShortcutBadges(true, showMeta);
0431 
0432         if (!m_hideViewsTimer.isActive()) {
0433             m_hideViews.append(viewWithTasks);
0434             viewWithTasks->visibility()->addBlockHidingEvent(SHORTCUTBLOCKHIDINGTYPE);
0435         }
0436     }
0437 
0438     //! show view that contains only meta
0439     if (viewWithMeta && viewWithMeta != viewWithTasks && viewWithMeta->extendedInterface()->showOnlyMeta()) {
0440         viewFound = true;
0441 
0442         if (!m_hideViewsTimer.isActive()) {
0443             m_hideViews.append(viewWithMeta);
0444             viewWithMeta->visibility()->addBlockHidingEvent(SHORTCUTBLOCKHIDINGTYPE);
0445         }
0446     }
0447 
0448     //! show all the rest views that contain plasma shortcuts
0449     QList<Latte::View *> viewsWithShortcuts = m_corona->layoutsManager()->synchronizer()->currentViewsWithPlasmaShortcuts();
0450 
0451     if (viewsWithShortcuts.count() > 0) {
0452         viewFound = true;
0453 
0454         if (!m_hideViewsTimer.isActive()) {
0455             for(const auto view : viewsWithShortcuts) {
0456                 if (view != viewWithTasks && view != viewWithMeta) {
0457                     if (view->extendedInterface()->showShortcutBadges(false, false)) {
0458                         m_hideViews.append(view);
0459                         view->visibility()->addBlockHidingEvent(SHORTCUTBLOCKHIDINGTYPE);
0460                     }
0461                 }
0462             }
0463         }
0464     }
0465 
0466     if (viewFound) {
0467         if (!m_hideViewsTimer.isActive()) {
0468             m_hideViewsTimer.start();
0469         } else {
0470             m_hideViewsTimer.stop();
0471             hideViewsTimerSlot();
0472         }
0473     }
0474 }
0475 
0476 bool GlobalShortcuts::viewsToHideAreValid()
0477 {
0478     for(const auto view : m_hideViews) {
0479         if (!m_corona->layoutsManager()->synchronizer()->latteViewExists(view)) {
0480             return false;
0481         }
0482 
0483     }
0484 
0485     return true;
0486 }
0487 
0488 void GlobalShortcuts::showSettings()
0489 {
0490     QList<Latte::View *> sortedViews = m_corona->layoutsManager()->synchronizer()->sortedCurrentOriginalViews();
0491 
0492     //! find which is the next view to show its settings
0493     if (sortedViews.count() > 0) {
0494         int openSettings = -1;
0495 
0496         //! find last view that showed its config view
0497         for (int i = 0; i < sortedViews.size(); ++i) {
0498             for (auto currentLayout : m_corona->layoutsManager()->currentLayouts()) {
0499                 if (sortedViews[i] == currentLayout->lastConfigViewFor()) {
0500                     openSettings = i;
0501                     break;
0502                 }
0503             }
0504 
0505             if (openSettings >= 0) {
0506                 break;
0507             }
0508         }
0509 
0510         if (openSettings >= 0 && sortedViews.count() > 1) {
0511             //! check if there is a view with opened settings window
0512             openSettings = sortedViews[openSettings]->settingsWindowIsShown() ? openSettings + 1 : openSettings;
0513 
0514             if (openSettings >= sortedViews.size()) {
0515                 openSettings = 0;
0516             }
0517 
0518             sortedViews[openSettings]->showSettingsWindow();
0519         } else {
0520             sortedViews[0]->showSettingsWindow();
0521         }
0522     }
0523 }
0524 
0525 void GlobalShortcuts::hideViewsTimerSlot()
0526 {
0527     if (!m_lastInvokedAction || m_hideViews.count() == 0) {
0528         return;
0529     }
0530 
0531     auto initParameters = [this]() {
0532         m_lastInvokedAction = Q_NULLPTR;
0533 
0534         if (viewsToHideAreValid()) {
0535             for(const auto latteView : m_hideViews) {
0536                 latteView->visibility()->removeBlockHidingEvent(SHORTCUTBLOCKHIDINGTYPE);
0537                 latteView->extendedInterface()->hideShortcutBadges();
0538 
0539                 if (latteView->visibility()->isSidebar() && !latteView->visibility()->isHidden()) {
0540                     latteView->visibility()->toggleHiddenState();
0541                 }
0542             }
0543         }
0544 
0545         m_hideViews.clear();
0546         m_metaShowedViews = false;
0547     };
0548 
0549     // qDebug() << "MEMORY ::: " << m_hideViews.count() << " _ " << m_viewItemsCalled.count() << " _ " << m_showShortcutBadgesMethods.count();
0550 
0551     if (QX11Info::isPlatformX11()) {
0552         if (!m_modifierTracker->sequenceModifierPressed(m_lastInvokedAction->shortcut())) {
0553             initParameters();
0554 
0555             return;
0556         } else {
0557             m_hideViewsTimer.start();
0558         }
0559     } else {
0560         // TODO: This is needs to be fixed in wayland
0561         initParameters();
0562     }
0563 }
0564 
0565 }
0566 
0567 #include "moc_globalshortcuts.cpp"