diff --git a/components/mobileshell/qml/components/StartupFeedback.qml b/components/mobileshell/qml/components/StartupFeedback.qml index 54e4262e..5b28f9bf 100644 --- a/components/mobileshell/qml/components/StartupFeedback.qml +++ b/components/mobileshell/qml/components/StartupFeedback.qml @@ -22,10 +22,6 @@ MouseArea { // use mousearea to ensure clicks don't go behind visible: false property alias backgroundColor: background.color - Kirigami.ImageColors { - id: colorGenerator - source: icon.source - } function open(splashIcon, title, x, y, sourceIconSize, color) { iconParent.scale = sourceIconSize/iconParent.width; @@ -83,6 +79,11 @@ MouseArea { // use mousearea to ensure clicks don't go behind background.state = "closed"; } } + + Kirigami.ImageColors { + id: colorGenerator + source: icon.source + } Item { id: backgroundParent diff --git a/components/mobileshell/qml/homescreen/HomeScreen.qml b/components/mobileshell/qml/homescreen/HomeScreen.qml index 6b23d138..5eefc409 100644 --- a/components/mobileshell/qml/homescreen/HomeScreen.qml +++ b/components/mobileshell/qml/homescreen/HomeScreen.qml @@ -243,5 +243,12 @@ Item { id: startupFeedback z: 999999 anchors.fill: parent + + // if the startup feedback closes, clear the shell's stored launching app + onVisibleChanged: { + if (!visible) { + MobileShell.ShellUtil.clearLaunchingApp(); + } + } } } diff --git a/components/mobileshell/qml/statusbar/ClockText.qml b/components/mobileshell/qml/statusbar/ClockText.qml index ada27f11..25bb802e 100644 --- a/components/mobileshell/qml/statusbar/ClockText.qml +++ b/components/mobileshell/qml/statusbar/ClockText.qml @@ -27,7 +27,7 @@ PlasmaComponents.Label { TapHandler { onTapped: { - MobileShell.ShellUtil.launchApp("org.kde.kclock"); + MobileShell.ShellUtil.launchApp("org.kde.kclock.desktop"); } } } diff --git a/components/mobileshell/qml/widgets/krunner/KRunnerWidget.qml b/components/mobileshell/qml/widgets/krunner/KRunnerWidget.qml index 48aea31a..6afd424d 100644 --- a/components/mobileshell/qml/widgets/krunner/KRunnerWidget.qml +++ b/components/mobileshell/qml/widgets/krunner/KRunnerWidget.qml @@ -11,7 +11,6 @@ import QtQuick.Controls 2.15 as Controls import QtQuick.Layouts 1.15 import QtGraphicalEffects 1.15 -import org.kde.plasma.plasmoid 2.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 3.0 as PlasmaComponents import org.kde.plasma.extras 2.0 as PlasmaExtras diff --git a/components/mobileshell/shellutil.cpp b/components/mobileshell/shellutil.cpp index ccbe2a0f..735f271a 100644 --- a/components/mobileshell/shellutil.cpp +++ b/components/mobileshell/shellutil.cpp @@ -7,12 +7,13 @@ */ #include "shellutil.h" +#include "windowutil.h" #include #include -#include #include #include +#include #include #include @@ -24,6 +25,7 @@ ShellUtil::ShellUtil(QObject *parent) : QObject{parent} + , m_launchingApp{nullptr} { m_localeConfig = KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::SimpleConfig); m_localeConfigWatcher = KConfigWatcher::create(m_localeConfig); @@ -77,13 +79,54 @@ bool ShellUtil::isSystem24HourFormat() return timeFormat == QStringLiteral(FORMAT24H); } -void ShellUtil::launchApp(const QString &app) +void ShellUtil::launchApp(const QString &storageId) { - const KService::Ptr appService = KService::serviceByDesktopName(app); - if (!appService) { - qWarning() << "Could not find" << app; + // try to activate a running window first + auto windows = WindowUtil::instance()->windowsFromStorageId(storageId); + + if (!windows.empty()) { + windows[0]->requestActivate(); return; } - auto job = new KIO::ApplicationLauncherJob(appService, this); + + // now try launching the window + KService::Ptr service = KService::serviceByStorageId(storageId); + if (!service) { + qWarning() << "Could not find" << storageId; + return; + } + + auto job = new KIO::ApplicationLauncherJob(service, this); + job->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled)); job->start(); + + setLaunchingApp(job); +} + +bool ShellUtil::isLaunchingApp() +{ + return m_launchingApp != nullptr; +} + +void ShellUtil::setLaunchingApp(KIO::ApplicationLauncherJob *launcherJob) +{ + m_launchingApp = launcherJob; + connect(launcherJob, &KIO::ApplicationLauncherJob::result, this, [this](auto *job) { + m_launchingAppPids = m_launchingApp->pids(); + }); + Q_EMIT isLaunchingAppChanged(); +} + +void ShellUtil::cancelLaunchingApp() +{ + for (auto pid : m_launchingAppPids) { + QProcess::execute("kill", {QString::number(pid)}); + } + clearLaunchingApp(); +} + +void ShellUtil::clearLaunchingApp() +{ + m_launchingApp = nullptr; + Q_EMIT isLaunchingAppChanged(); } diff --git a/components/mobileshell/shellutil.h b/components/mobileshell/shellutil.h index af4a676e..d6b426c5 100644 --- a/components/mobileshell/shellutil.h +++ b/components/mobileshell/shellutil.h @@ -11,6 +11,7 @@ #include #include +#include #include /** @@ -22,7 +23,7 @@ class ShellUtil : public QObject { Q_OBJECT Q_PROPERTY(bool isSystem24HourFormat READ isSystem24HourFormat NOTIFY isSystem24HourFormatChanged) - Q_PROPERTY(bool launchingApp READ isLaunchingApp NOTIFY isLaunchingAppChanged) + Q_PROPERTY(bool isLaunchingApp READ isLaunchingApp NOTIFY isLaunchingAppChanged) public: ShellUtil(QObject *parent = nullptr); @@ -52,21 +53,44 @@ public: Q_INVOKABLE void executeCommand(const QString &command); /** - * Launch an application by name. + * Launch an application by name. Sets the internal "launched app" state. * - * @param app The name of the application to launch. + * @param storageId The storage id of the application to launch. */ - Q_INVOKABLE void launchApp(const QString &app); + Q_INVOKABLE void launchApp(const QString &storageId); /** * Whether the system is using 24 hour format. */ Q_INVOKABLE bool isSystem24HourFormat(); + /** + * Whether an application is being launched. + */ + Q_INVOKABLE bool isLaunchingApp(); + + /** + * Cancels an application launch by running `kill pid` for every associated pid of the launching app. + */ + Q_INVOKABLE void cancelLaunchingApp(); + + /** + * Clears the currently stored launching app. + * + * This should be called if the application window finally shows. + */ + Q_INVOKABLE void clearLaunchingApp(); + Q_SIGNALS: void isSystem24HourFormatChanged(); + void isLaunchingAppChanged(); private: + void setLaunchingApp(KIO::ApplicationLauncherJob *launcherJob); + KConfigWatcher::Ptr m_localeConfigWatcher; KSharedConfig::Ptr m_localeConfig; + + KIO::ApplicationLauncherJob *m_launchingApp; + QVector m_launchingAppPids; }; diff --git a/components/mobileshell/windowutil.cpp b/components/mobileshell/windowutil.cpp index ab1362dd..f0a71872 100644 --- a/components/mobileshell/windowutil.cpp +++ b/components/mobileshell/windowutil.cpp @@ -75,6 +75,7 @@ void WindowUtil::initWayland() connect(m_windowManagement, &KWayland::Client::PlasmaWindowManagement::windowCreated, this, [this](KWayland::Client::PlasmaWindow *window) { Q_EMIT windowCreated(window); }); + connect(m_windowManagement, &KWayland::Client::PlasmaWindowManagement::windowCreated, this, &WindowUtil::windowCreatedSlot); connect(m_windowManagement, &PlasmaWindowManagement::showingDesktopChanged, this, &WindowUtil::updateShowingDesktop); connect(m_windowManagement, &PlasmaWindowManagement::activeWindowChanged, m_activeWindowTimer, qOverload<>(&QTimer::start)); @@ -222,3 +223,34 @@ void WindowUtil::forgetActiveWindow() m_activeWindow.clear(); Q_EMIT hasCloseableActiveWindowChanged(); } + +QList WindowUtil::windowsFromStorageId(const QString &storageId) const +{ + if (!m_windows.contains(storageId)) { + return {}; + } + return m_windows[storageId]; +} + +void WindowUtil::windowCreatedSlot(KWayland::Client::PlasmaWindow *window) +{ + QString storageId = window->appId() + QStringLiteral(".desktop"); + + // ignore empty windows + if (storageId == ".desktop" || storageId == "org.kde.plasmashell.desktop") { + return; + } + + if (!m_windows.contains(storageId)) { + m_windows[storageId] = {}; + } + m_windows[storageId].push_back(window); + + // listen for window close + connect(window, &KWayland::Client::PlasmaWindow::unmapped, this, [this, storageId]() { + m_windows.remove(storageId); + Q_EMIT windowChanged(storageId); + }); + + Q_EMIT windowChanged(storageId); +} diff --git a/components/mobileshell/windowutil.h b/components/mobileshell/windowutil.h index 1858982f..b4a7c175 100644 --- a/components/mobileshell/windowutil.h +++ b/components/mobileshell/windowutil.h @@ -67,6 +67,11 @@ public: */ bool hasCloseableActiveWindow() const; + /** + * Get the list of windows associated to a storage id. + */ + QList windowsFromStorageId(const QString &storageId) const; + /** * Close the current active window. */ @@ -99,11 +104,13 @@ Q_SIGNALS: void hasCloseableActiveWindowChanged(); void activeWindowChanged(); void activeWindowIsShellChanged(); + void windowChanged(QString storageId); // emitted on window open or close private Q_SLOTS: void updateActiveWindowIsShell(); void forgetActiveWindow(); void updateShowingDesktop(bool showing); + void windowCreatedSlot(KWayland::Client::PlasmaWindow *window); private: void initWayland(); @@ -117,4 +124,6 @@ private: bool m_allWindowsMinimized = true; bool m_allWindowsMinimizedExcludingShell = true; bool m_activeWindowIsShell = false; + + QHash> m_windows; // }; diff --git a/containments/taskpanel/package/contents/ui/NavigationPanelComponent.qml b/containments/taskpanel/package/contents/ui/NavigationPanelComponent.qml index f5220490..dc48aac2 100644 --- a/containments/taskpanel/package/contents/ui/NavigationPanelComponent.qml +++ b/containments/taskpanel/package/contents/ui/NavigationPanelComponent.qml @@ -82,7 +82,7 @@ MobileShell.NavigationPanel { rightAction: MobileShell.NavigationPanelAction { id: closeAppAction - enabled: Keyboards.KWinVirtualKeyboard.visible || root.taskSwitcher.visible || MobileShell.WindowUtil.hasCloseableActiveWindow + enabled: Keyboards.KWinVirtualKeyboard.visible || root.taskSwitcher.visible || MobileShell.WindowUtil.hasCloseableActiveWindow || MobileShell.ShellUtil.isLaunchingApp iconSource: Keyboards.KWinVirtualKeyboard.visible ? "go-down-symbolic" : "mobile-close-app" // mobile-close-app (from plasma-frameworks) seems to have less margins than icons from breeze-icons iconSizeFactor: Keyboards.KWinVirtualKeyboard.visible ? 1 : 0.75 @@ -101,6 +101,12 @@ MobileShell.NavigationPanel { if (root.taskSwitcher.tasksModel.activeTask !== 0) { root.taskSwitcher.tasksModel.requestClose(root.taskSwitcher.tasksModel.activeTask); } + MobileShell.HomeScreenControls.closeAppLaunchAnimation(); + } else if (MobileShell.ShellUtil.isLaunchingApp) { + + // cancel the launching of the app + MobileShell.HomeScreenControls.closeAppLaunchAnimation(); + MobileShell.ShellUtil.cancelLaunchingApp(); } } }