File indexing completed on 2024-10-06 13:22:28

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