You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
latte-dock/app/shortcuts/globalshortcuts.cpp

568 lines
19 KiB
C++

/*
SPDX-FileCopyrightText: 2016 Smith AR <audoban@openmailbox.org>
SPDX-FileCopyrightText: 2016 Michail Vourlakos <mvourlakos@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "globalshortcuts.h"
// local
#include "modifiertracker.h"
#include "shortcutstracker.h"
#include "../lattecorona.h"
#include "../layout/centrallayout.h"
#include "../layouts/manager.h"
#include "../layouts/synchronizer.h"
#include "../settings/settingsdialog/settingsdialog.h"
#include "../settings/universalsettings.h"
#include "../view/containmentinterface.h"
#include "../view/view.h"
// C++
#include <array>
// Qt
#include <QAction>
#include <QDebug>
#include <QQuickItem>
#include <QMetaMethod>
#include <QX11Info>
// KDE
#include <KActionCollection>
#include <KGlobalAccel>
#include <KLocalizedString>
#include <KPluginMetaData>
// Plasma
#include <Plasma/Applet>
#include <Plasma/Containment>
namespace Latte {
GlobalShortcuts::GlobalShortcuts(QObject *parent)
: QObject(parent)
{
m_corona = qobject_cast<Latte::Corona *>(parent);
m_modifierTracker = new ShortcutsPart::ModifierTracker(this);
m_shortcutsTracker = new ShortcutsPart::ShortcutsTracker(this);
if (m_corona) {
init();
}
m_hideViewsTimer.setSingleShot(true);
if (QX11Info::isPlatformX11()) {
//in X11 the timer is a poller that checks to see if the modifier keys
//from user global shortcut have been released
m_hideViewsTimer.setInterval(300);
} else {
//on wayland in acting just as simple timer that hides the view afterwards
m_hideViewsTimer.setInterval(2500);
}
connect(&m_hideViewsTimer, &QTimer::timeout, this, &GlobalShortcuts::hideViewsTimerSlot);
}
GlobalShortcuts::~GlobalShortcuts()
{
if (m_modifierTracker) {
m_modifierTracker->deleteLater();
}
if (m_shortcutsTracker) {
m_shortcutsTracker->deleteLater();
}
}
void GlobalShortcuts::init()
{
KActionCollection *generalActions = new KActionCollection(m_corona);
//show-hide the main view in the primary screen
QAction *showAction = generalActions->addAction(QStringLiteral("show latte view"));
showAction->setText(i18n("Show Latte Dock/Panel"));
showAction->setShortcut(QKeySequence(Qt::META + '`'));
KGlobalAccel::setGlobalShortcut(showAction, QKeySequence(Qt::META + '`'));
connect(showAction, &QAction::triggered, this, [this]() {
showViews();
});
//show-cycle between Latte settings windows
QAction *settingsAction = generalActions->addAction(QStringLiteral("show view settings"));
settingsAction->setText(i18n("Cycle Through Dock/Panel Settings Windows"));
KGlobalAccel::setGlobalShortcut(settingsAction, QKeySequence(Qt::META + Qt::Key_A));
connect(settingsAction, &QAction::triggered, this, [this] {
m_modifierTracker->cancelMetaPressed();
showSettings();
});
//show the layouts editor
QAction *layoutsAction = generalActions->addAction(QStringLiteral("show latte global settings"));
layoutsAction->setText(i18n("Show Latte Global Settings"));
layoutsAction->setShortcut(QKeySequence());
KGlobalAccel::setGlobalShortcut(layoutsAction, QKeySequence());
connect(layoutsAction, &QAction::triggered, this, [this]() {
m_modifierTracker->cancelMetaPressed();
m_corona->layoutsManager()->showLatteSettingsDialog(Settings::Dialog::PreferencesPage, true);
});
KActionCollection *taskbarActions = new KActionCollection(m_corona);
//activate actions [1-9]
for (int i = 1; i < 10; ++i) {
const int entryNumber = i;
const Qt::Key key = static_cast<Qt::Key>(Qt::Key_0 + i);
QAction *action = taskbarActions->addAction(QStringLiteral("activate entry %1").arg(QString::number(entryNumber)));
action->setText(i18n("Activate Entry %1", entryNumber));
action->setShortcut(QKeySequence(Qt::META + key));
KGlobalAccel::setGlobalShortcut(action, QKeySequence(Qt::META + key));
connect(action, &QAction::triggered, this, [this, i] {
// qDebug() << "meta action...";
m_modifierTracker->cancelMetaPressed();
activateEntry(i, static_cast<Qt::Key>(Qt::META));
});
}
//! Array that is used to register correctly actions for task index>=10 and <19
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 };
//activate actions [10-19]
for (int i = 10; i < 20; ++i) {
QAction *action = taskbarActions->addAction(QStringLiteral("activate entry %1").arg(QString::number(i)));
action->setText(i18n("Activate Entry %1", i));
action->setShortcut(QKeySequence(Qt::META + keysAboveTen[i - 10]));
KGlobalAccel::setGlobalShortcut(action, QKeySequence(Qt::META + keysAboveTen[i - 10]));
connect(action, &QAction::triggered, this, [this, i] {
m_modifierTracker->cancelMetaPressed();
activateEntry(i, static_cast<Qt::Key>(Qt::META));
});
}
//new instance actions [1-9]
for (int i = 1; i < 10; ++i) {
const int entryNumber = i;
const Qt::Key key = static_cast<Qt::Key>(Qt::Key_0 + i);
QAction *action = taskbarActions->addAction(QStringLiteral("new instance for entry %1").arg(QString::number(entryNumber)));
action->setText(i18n("New Instance for Entry %1", entryNumber));
KGlobalAccel::setGlobalShortcut(action, QKeySequence(Qt::META + Qt::CTRL + key));
connect(action, &QAction::triggered, this, [this, i] {
// qDebug() << "meta + ctrl + action...";
m_modifierTracker->cancelMetaPressed();
activateEntry(i, static_cast<Qt::Key>(Qt::CTRL));
});
}
//new instance actions [10-19]
for (int i = 10; i < 20; ++i) {
QAction *action = taskbarActions->addAction(QStringLiteral("new instance for entry %1").arg(QString::number(i)));
action->setText(i18n("New Instance for Entry %1", i));
KGlobalAccel::setGlobalShortcut(action, QKeySequence(Qt::META + Qt::CTRL + keysAboveTen[i - 10]));
connect(action, &QAction::triggered, this, [this, i] {
m_modifierTracker->cancelMetaPressed();
activateEntry(i, static_cast<Qt::Key>(Qt::CTRL));
});
}
m_singleMetaAction = new QAction(this);
m_singleMetaAction->setShortcut(QKeySequence(Qt::META));
connect(m_corona->universalSettings(), &UniversalSettings::metaPressAndHoldEnabledChanged , this, [&]() {
if (!m_corona->universalSettings()->metaPressAndHoldEnabled()) {
m_modifierTracker->blockModifierTracking(Qt::Key_Super_L);
m_modifierTracker->blockModifierTracking(Qt::Key_Super_R);
} else {
m_modifierTracker->unblockModifierTracking(Qt::Key_Super_L);
m_modifierTracker->unblockModifierTracking(Qt::Key_Super_R);
}
});
//display shortcut badges while holding Meta
connect(m_modifierTracker, &ShortcutsPart::ModifierTracker::metaModifierPressed, this, [&]() {
m_metaShowedViews = true;
showViews();
});
}
ShortcutsPart::ShortcutsTracker *GlobalShortcuts::shortcutsTracker() const
{
return m_shortcutsTracker;
}
Latte::View *GlobalShortcuts::highestApplicationLauncherView(const QList<Latte::View *> &views) const
{
if (views.isEmpty()) {
return nullptr;
}
Latte::View *highestPriorityView{nullptr};
for (const auto view : views) {
if (view->extendedInterface()->applicationLauncherHasGlobalShortcut()) {
highestPriorityView = view;
break;
}
}
if (!highestPriorityView) {
for (const auto view : views) {
if (view->extendedInterface()->containsApplicationLauncher()) {
highestPriorityView = view;
break;
}
}
}
return highestPriorityView;
}
//! Activate launcher menu through dbus interface
void GlobalShortcuts::activateLauncherMenu()
{
if (m_metaShowedViews) {
return;
}
QList<Latte::View *> sortedViews = m_corona->layoutsManager()->synchronizer()->sortedCurrentViews();
Latte::View *highestPriorityView = highestApplicationLauncherView(sortedViews);
if (highestPriorityView) {
if (!highestPriorityView->visibility()->isShownFully() && highestPriorityView->extendedInterface()->applicationLauncherInPopup()) {
m_lastInvokedAction = m_singleMetaAction;
//! wait for view to fully shown
connect(highestPriorityView->visibility(), &Latte::ViewPart::VisibilityManager::isShownFullyChanged, this, [&, highestPriorityView](){
if (highestPriorityView->visibility()->isShownFully()) {
highestPriorityView->extendedInterface()->toggleAppletExpanded(highestPriorityView->extendedInterface()->applicationLauncherId());
//!remove that signal tracking
disconnect(highestPriorityView->visibility(), &Latte::ViewPart::VisibilityManager::isShownFullyChanged, this, nullptr);
}
});
//! That signal is removed from Latte::View only when the popup is really shown!
highestPriorityView->visibility()->addBlockHidingEvent(SHORTCUTBLOCKHIDINGTYPE);
} else {
highestPriorityView->extendedInterface()->toggleAppletExpanded(highestPriorityView->extendedInterface()->applicationLauncherId());
}
}
}
bool GlobalShortcuts::activatePlasmaTaskManager(const Latte::View *view, int index, Qt::Key modifier, bool *delayedExecution)
{
bool activation{modifier == static_cast<Qt::Key>(Qt::META)};
bool newInstance{!activation};
if (!view->visibility()->isShownFully()) {
//! wait for view to fully shown
connect(view->visibility(), &Latte::ViewPart::VisibilityManager::isShownFullyChanged, this, [&, view, index, activation](){
if (view->visibility()->isShownFully()) {
if (activation) {
view->extendedInterface()->activatePlasmaTask(index);
} else {
view->extendedInterface()->newInstanceForPlasmaTask(index);
}
//!remove that signal tracking
disconnect(view->visibility(), &Latte::ViewPart::VisibilityManager::isShownFullyChanged, this, nullptr);
}
});
*delayedExecution = true;
return true;
} else {
*delayedExecution = false;
return (activation ? view->extendedInterface()->activatePlasmaTask(index) : view->extendedInterface()->newInstanceForPlasmaTask(index));
}
}
bool GlobalShortcuts::activateLatteEntry(Latte::View *view, int index, Qt::Key modifier, bool *delayedExecution)
{
bool activation{modifier == static_cast<Qt::Key>(Qt::META)};
bool newInstance{!activation};
int appletId = view->extendedInterface()->appletIdForVisualIndex(index);
bool hasPopUp {(appletId>-1 && view->extendedInterface()->appletIsExpandable(appletId))};
if (!view->visibility()->isShownFully() && hasPopUp) {
//! wait for view to fully shown
connect(view->visibility(), &Latte::ViewPart::VisibilityManager::isShownFullyChanged, this, [&, view, index, activation](){
if (view->visibility()->isShownFully()) {
if (activation) {
view->extendedInterface()->activateEntry(index);
} else {
view->extendedInterface()->newInstanceForEntry(index);
}
//!remove that signal tracking
disconnect(view->visibility(), &Latte::ViewPart::VisibilityManager::isShownFullyChanged, this, nullptr);
}
});
*delayedExecution = true;
return true;
} else {
*delayedExecution = false;
return (activation ? view->extendedInterface()->activateEntry(index) : view->extendedInterface()->newInstanceForEntry(index));
}
}
bool GlobalShortcuts::activateEntryForView(Latte::View *view, int index, Qt::Key modifier)
{
if (!view) {
return false;
}
bool delayed{false};
bool executed = ((!view->extendedInterface()->hasLatteTasks() && view->extendedInterface()->hasPlasmaTasks()
&& activatePlasmaTaskManager(view, index, modifier, &delayed))
|| activateLatteEntry(view, index, modifier, &delayed));
if (executed) {
if (!m_hideViews.contains(view)) {
m_hideViews.append(view);
}
if (delayed) {
view->visibility()->addBlockHidingEvent(SHORTCUTBLOCKHIDINGTYPE);
m_hideViewsTimer.start();
}
return true;
}
return false;
}
//! Activate task manager entry
void GlobalShortcuts::activateEntry(int index, Qt::Key modifier)
{
m_lastInvokedAction = dynamic_cast<QAction *>(sender());
QList<Latte::View *> sortedViews = m_corona->layoutsManager()->synchronizer()->sortedCurrentViews();
Latte::View *highest{nullptr};
for (const auto view : sortedViews) {
if (view->isPreferredForShortcuts()) {
highest = view;
break;
}
}
if (highest) {
activateEntryForView(highest, index, modifier);
} else {
for (const auto view : sortedViews) {
if (activateEntryForView(view, index, modifier)) {
return;
}
}
}
}
//! update badge for specific view item
void GlobalShortcuts::updateViewItemBadge(QString identifier, QString value)
{
QList<Latte::View *> views = m_corona->layoutsManager()->synchronizer()->currentViews();
// update badges in all Latte Tasks plasmoids
for (const auto &view : views) {
view->extendedInterface()->updateBadgeForLatteTask(identifier, value);
}
}
void GlobalShortcuts::showViews()
{
m_lastInvokedAction = dynamic_cast<QAction *>(sender());
if (!m_lastInvokedAction) {
// when holding Meta
m_lastInvokedAction = m_singleMetaAction;
}
QList<Latte::View *> sortedViews = m_corona->layoutsManager()->synchronizer()->sortedCurrentViews();
Latte::View *viewWithTasks{nullptr};
Latte::View *viewWithMeta{nullptr};
bool hasPreferredForShortcutsView{false};
for (const auto view : sortedViews) {
if (view->isPreferredForShortcuts()) {
hasPreferredForShortcutsView = true;
break;
}
}
for(const auto view : sortedViews) {
if (!viewWithTasks
&& (!hasPreferredForShortcutsView || view->isPreferredForShortcuts())) {
viewWithTasks = view;
break;
}
}
if (m_corona->universalSettings()->kwin_metaForwardedToLatte()) {
viewWithMeta = highestApplicationLauncherView(sortedViews);
}
bool viewFound{false};
if (!m_hideViewsTimer.isActive()) {
m_hideViews.clear();
}
//! show view that contains tasks plasmoid
if (viewWithTasks) {
viewFound = true;
bool showMeta = (viewWithMeta && (viewWithMeta == viewWithTasks));
viewWithTasks->extendedInterface()->showShortcutBadges(true, showMeta);
if (!m_hideViewsTimer.isActive()) {
m_hideViews.append(viewWithTasks);
viewWithTasks->visibility()->addBlockHidingEvent(SHORTCUTBLOCKHIDINGTYPE);
}
}
//! show view that contains only meta
if (viewWithMeta && viewWithMeta != viewWithTasks && viewWithMeta->extendedInterface()->showOnlyMeta()) {
viewFound = true;
if (!m_hideViewsTimer.isActive()) {
m_hideViews.append(viewWithMeta);
viewWithMeta->visibility()->addBlockHidingEvent(SHORTCUTBLOCKHIDINGTYPE);
}
}
//! show all the rest views that contain plasma shortcuts
QList<Latte::View *> viewsWithShortcuts = m_corona->layoutsManager()->synchronizer()->currentViewsWithPlasmaShortcuts();
if (viewsWithShortcuts.count() > 0) {
viewFound = true;
if (!m_hideViewsTimer.isActive()) {
for(const auto view : viewsWithShortcuts) {
if (view != viewWithTasks && view != viewWithMeta) {
if (view->extendedInterface()->showShortcutBadges(false, false)) {
m_hideViews.append(view);
view->visibility()->addBlockHidingEvent(SHORTCUTBLOCKHIDINGTYPE);
}
}
}
}
}
if (viewFound) {
if (!m_hideViewsTimer.isActive()) {
m_hideViewsTimer.start();
} else {
m_hideViewsTimer.stop();
hideViewsTimerSlot();
}
}
}
bool GlobalShortcuts::viewsToHideAreValid()
{
for(const auto view : m_hideViews) {
if (!m_corona->layoutsManager()->synchronizer()->latteViewExists(view)) {
return false;
}
}
return true;
}
void GlobalShortcuts::showSettings()
{
fix #96,FEATURE:AllScreens and AllSecondaryScreens --This is a HUGE FEATURE and so important for multi-screens users. It is introduced as one single commit because it reimplements plenty of infrastructure changes and it will be easier to identify newly introduced bugs. --Users can now choose for their docks and panels to belong at various screen groups. The first two screen groups introduced are AllScreens and AllSecondayScreens. In the future it might be possible to provide CustomScreensGroup that the user will be able to define specific screens in which a dock or panel should be always present. --Current solution specifies an Original dock or panel and clones/copies itself automatically to other screens. So docks and panels in other screens are just real docks and panels that reference themselves to original docks and panels. --Clones are destroyed during layout startup and are automaticaly recreated. It is suggested to export your layouts through the official Layouts Editor in order to share them because in that case clones are not included in the new generated layout file. If in any case you do not this and you share your layout with any previous versions then your clones will just appear as separate docks and panels that belong to specific screens. --Automatic syncing was introduced in order to keep up-to-date the configuration of Original docks and panels with their referenced Clones. --Automatic syncing currently works for all docks and panels settings, for all normal applets configurations and for all subcontaiments configuration such as systrays. --Automatic syncing does not work for applets inside subcontainments such as Group Plasmoid. In such case it is suggested to configure your applets inside your Group Plasmoid in the original dock or panel and afterwards to trigger a recreation for the relevant clones --Manual recreation of clones is easily possible by just choosing the dock or panel to be OnPrimary or OnSpecificScreen and rechoosing afterwards the AllScreensGroup or AllSecondaryScreensGroup
3 years ago
QList<Latte::View *> sortedViews = m_corona->layoutsManager()->synchronizer()->sortedCurrentOriginalViews();
//! find which is the next view to show its settings
if (sortedViews.count() > 0) {
int openSettings = -1;
//! find last view that showed its config view
for (int i = 0; i < sortedViews.size(); ++i) {
for (auto currentLayout : m_corona->layoutsManager()->currentLayouts()) {
if (sortedViews[i] == currentLayout->lastConfigViewFor()) {
openSettings = i;
break;
}
}
if (openSettings >= 0) {
break;
}
}
if (openSettings >= 0 && sortedViews.count() > 1) {
//! check if there is a view with opened settings window
openSettings = sortedViews[openSettings]->settingsWindowIsShown() ? openSettings + 1 : openSettings;
if (openSettings >= sortedViews.size()) {
openSettings = 0;
}
sortedViews[openSettings]->showSettingsWindow();
} else {
sortedViews[0]->showSettingsWindow();
}
}
}
void GlobalShortcuts::hideViewsTimerSlot()
{
if (!m_lastInvokedAction || m_hideViews.count() == 0) {
return;
}
auto initParameters = [this]() {
m_lastInvokedAction = Q_NULLPTR;
if (viewsToHideAreValid()) {
for(const auto latteView : m_hideViews) {
latteView->visibility()->removeBlockHidingEvent(SHORTCUTBLOCKHIDINGTYPE);
latteView->extendedInterface()->hideShortcutBadges();
if (latteView->visibility()->isSidebar() && !latteView->visibility()->isHidden()) {
latteView->visibility()->toggleHiddenState();
}
}
}
m_hideViews.clear();
m_metaShowedViews = false;
};
// qDebug() << "MEMORY ::: " << m_hideViews.count() << " _ " << m_viewItemsCalled.count() << " _ " << m_showShortcutBadgesMethods.count();
if (QX11Info::isPlatformX11()) {
if (!m_modifierTracker->sequenceModifierPressed(m_lastInvokedAction->shortcut())) {
initParameters();
return;
} else {
m_hideViewsTimer.start();
}
} else {
// TODO: This is needs to be fixed in wayland
initParameters();
}
}
}
#include "moc_globalshortcuts.cpp"