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"