From 3b6951bf1e03569332394842946d1f4e6c0db172 Mon Sep 17 00:00:00 2001 From: Devin Lin Date: Wed, 2 Jul 2025 10:27:33 -0400 Subject: [PATCH] panel: Overlay over lockscreen Overlay the shell's status panel and quicksettings panel over the lockscreen, instead of rendering a second copy in the lockscreen theme. This will allow us to improve the lockscreen loading speed. Key changes: - Overlay quicksettings window and the status bar over the lockscreen when it is shown - Refactor the top panel's showing logic to be cleaner (as it supports various overlay modes over fullscreen apps already) - Implement lockscreen support to the status bar and quicksettings panel in the to panel - Forward quicksettings panel requests for "unlock" over DBus to the lockscreen - Add "raiselockscreen" QML plugin to easily request a window to be raised over the lockscreen Notes: - Now that we are sharing the quicksettings panel from the shell, notifications that are already there will be shown on the lockscreen (compared to right now, where only new notifications would be shown) Depends on: - https://invent.kde.org/plasma/plasma-workspace/-/merge_requests/2339 - https://invent.kde.org/plasma/kscreenlocker/-/merge_requests/283 - https://invent.kde.org/plasma/kwin/-/merge_requests/7839 Implements: https://invent.kde.org/plasma/plasma-mobile/-/issues/199 ![Screencast_20250612_013325](/uploads/49e9981cb863056b4c0c46a144e5ee7d/Screencast_20250612_013325.webm) --- CMakeLists.txt | 9 + components/CMakeLists.txt | 1 + .../notifications/NotificationsWidget.qml | 7 +- .../mobileshellstate/lockscreendbusclient.cpp | 2 - .../mobileshellstate/shelldbusclient.cpp | 6 + components/mobileshellstate/shelldbusclient.h | 2 + .../mobileshellstate/shelldbusobject.cpp | 5 + components/mobileshellstate/shelldbusobject.h | 2 + .../raiselockscreenplugin/CMakeLists.txt | 29 ++ .../raiselockscreenplugin/raiselockscreen.cpp | 140 ++++++++ .../raiselockscreenplugin/raiselockscreen.h | 49 +++ components/raiselockscreenplugin/utils.h | 12 + containments/panel/CMakeLists.txt | 5 + containments/panel/qml/LockscreenOverlay.qml | 57 ++++ containments/panel/qml/StatusBarWrapper.qml | 100 ++++++ containments/panel/qml/StatusPanel.qml | 185 +++++++++++ containments/panel/qml/main.qml | 243 +++----------- containments/taskpanel/qml/main.qml | 2 +- shell/contents/lockscreen/HeaderComponent.qml | 145 --------- shell/contents/lockscreen/LockScreen.qml | 306 ++++++++---------- tests/ActionDrawerTest.qml | 2 +- tests/LockScreenTest.qml | 45 ++- 22 files changed, 823 insertions(+), 531 deletions(-) create mode 100644 components/raiselockscreenplugin/CMakeLists.txt create mode 100644 components/raiselockscreenplugin/raiselockscreen.cpp create mode 100644 components/raiselockscreenplugin/raiselockscreen.h create mode 100644 components/raiselockscreenplugin/utils.h create mode 100644 containments/panel/qml/LockscreenOverlay.qml create mode 100644 containments/panel/qml/StatusBarWrapper.qml create mode 100644 containments/panel/qml/StatusPanel.qml delete mode 100644 shell/contents/lockscreen/HeaderComponent.qml diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a2ab6e0..52024e9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,7 @@ include(ECMMarkNonGuiExecutable) include(ECMGenerateHeaders) include(ECMFindQmlModule) include(ECMGenerateQmlTypes) +include(ECMConfiguredInstall) include(ECMFindQmlModule) include(GenerateExportHeader) include(KDEGitCommitHooks) @@ -100,6 +101,14 @@ find_package(KWin ${PROJECT_DEP_VERSION} REQUIRED COMPONENTS ) find_package(LayerShellQt REQUIRED) +find_package(Wayland REQUIRED) +find_package(PlasmaWaylandProtocols 1.8 CONFIG) +set_package_properties(PlasmaWaylandProtocols PROPERTIES + TYPE REQUIRED + PURPOSE "Collection of Plasma-specific Wayland protocols" + URL "https://invent.kde.org/libraries/plasma-wayland-protocols/" +) + find_package(LibKWorkspace CONFIG REQUIRED) find_package(Libudev REQUIRED) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 8930097f..32be1037 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -13,3 +13,4 @@ add_subdirectory(shellsettingsplugin) add_subdirectory(wallpaperimageplugin) add_subdirectory(dpmsplugin) add_subdirectory(screenbrightnessplugin) +add_subdirectory(raiselockscreenplugin) diff --git a/components/mobileshell/qml/widgets/notifications/NotificationsWidget.qml b/components/mobileshell/qml/widgets/notifications/NotificationsWidget.qml index e1c0e3d6..41fb1773 100644 --- a/components/mobileshell/qml/widgets/notifications/NotificationsWidget.qml +++ b/components/mobileshell/qml/widgets/notifications/NotificationsWidget.qml @@ -111,7 +111,10 @@ Item { * Run pending action that was pending for authentication when unlockRequested() was emitted. */ function runPendingAction() { - list.pendingNotificationWithAction.runPendingAction(); + if (list.pendingNotificationWithAction) { + list.pendingNotificationWithAction.runPendingAction(); + list.pendingNotificationWithAction = null; + } } /** @@ -175,7 +178,7 @@ Item { currentIndex: 0 - property var pendingNotificationWithAction + property NotificationItem pendingNotificationWithAction: null readonly property int animationDuration: ShellSettings.Settings.animationsEnabled ? Kirigami.Units.longDuration : 0 diff --git a/components/mobileshellstate/lockscreendbusclient.cpp b/components/mobileshellstate/lockscreendbusclient.cpp index 81bf09d3..6e446717 100644 --- a/components/mobileshellstate/lockscreendbusclient.cpp +++ b/components/mobileshellstate/lockscreendbusclient.cpp @@ -16,8 +16,6 @@ LockscreenDBusClient::LockscreenDBusClient(QObject *parent) QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("GetActive")); - const QDBusReply response = QDBusConnection::sessionBus().call(request); - QDBusConnection::sessionBus().callWithCallback(request, this, SLOT(slotLockscreenActiveChanged(bool)), SLOT(dbusError(QDBusError))); QDBusConnection::sessionBus().connect(QStringLiteral("org.freedesktop.ScreenSaver"), diff --git a/components/mobileshellstate/shelldbusclient.cpp b/components/mobileshellstate/shelldbusclient.cpp index c933ae02..e8c3a9dd 100644 --- a/components/mobileshellstate/shelldbusclient.cpp +++ b/components/mobileshellstate/shelldbusclient.cpp @@ -50,6 +50,7 @@ void ShellDBusClient::connectSignals() connect(m_interface, &OrgKdePlasmashellInterface::openHomeScreenRequested, this, &ShellDBusClient::openHomeScreenRequested); connect(m_interface, &OrgKdePlasmashellInterface::resetHomeScreenPositionRequested, this, &ShellDBusClient::resetHomeScreenPositionRequested); connect(m_interface, &OrgKdePlasmashellInterface::showVolumeOSDRequested, this, &ShellDBusClient::showVolumeOSDRequested); + connect(m_interface, &OrgKdePlasmashellInterface::openLockScreenKeypadRequested, this, &ShellDBusClient::openLockScreenKeypadRequested); updateDoNotDisturb(); updateIsTaskSwitcherVisible(); @@ -151,6 +152,11 @@ void ShellDBusClient::showVolumeOSD() m_interface->showVolumeOSD(); } +void ShellDBusClient::openLockScreenKeypad() +{ + m_interface->openLockScreenKeypad(); +} + void ShellDBusClient::updatePanelState() { auto reply = m_interface->panelState(); diff --git a/components/mobileshellstate/shelldbusclient.h b/components/mobileshellstate/shelldbusclient.h index e3818aab..4f36a174 100644 --- a/components/mobileshellstate/shelldbusclient.h +++ b/components/mobileshellstate/shelldbusclient.h @@ -54,6 +54,7 @@ public: Q_INVOKABLE void openHomeScreen(); Q_INVOKABLE void resetHomeScreenPosition(); Q_INVOKABLE void showVolumeOSD(); + Q_INVOKABLE void openLockScreenKeypad(); Q_SIGNALS: void panelStateChanged(); @@ -68,6 +69,7 @@ Q_SIGNALS: void openHomeScreenRequested(); void resetHomeScreenPositionRequested(); void showVolumeOSDRequested(); + void openLockScreenKeypadRequested(); private Q_SLOTS: void updateDoNotDisturb(); diff --git a/components/mobileshellstate/shelldbusobject.cpp b/components/mobileshellstate/shelldbusobject.cpp index 3697cb0f..1a279968 100644 --- a/components/mobileshellstate/shelldbusobject.cpp +++ b/components/mobileshellstate/shelldbusobject.cpp @@ -150,3 +150,8 @@ void ShellDBusObject::showVolumeOSD() { Q_EMIT showVolumeOSDRequested(); } + +void ShellDBusObject::openLockScreenKeypad() +{ + Q_EMIT openLockScreenKeypadRequested(); +} diff --git a/components/mobileshellstate/shelldbusobject.h b/components/mobileshellstate/shelldbusobject.h index 5d0055ce..b39f1cbf 100644 --- a/components/mobileshellstate/shelldbusobject.h +++ b/components/mobileshellstate/shelldbusobject.h @@ -38,6 +38,7 @@ Q_SIGNALS: Q_SCRIPTABLE void openHomeScreenRequested(); Q_SCRIPTABLE void resetHomeScreenPositionRequested(); Q_SCRIPTABLE void showVolumeOSDRequested(); + Q_SCRIPTABLE void openLockScreenKeypadRequested(); public Q_SLOTS: Q_SCRIPTABLE bool doNotDisturb(); @@ -70,6 +71,7 @@ public Q_SLOTS: Q_SCRIPTABLE void openHomeScreen(); Q_SCRIPTABLE void resetHomeScreenPosition(); Q_SCRIPTABLE void showVolumeOSD(); + Q_SCRIPTABLE void openLockScreenKeypad(); private: bool m_initialized{false}; diff --git a/components/raiselockscreenplugin/CMakeLists.txt b/components/raiselockscreenplugin/CMakeLists.txt new file mode 100644 index 00000000..42be9344 --- /dev/null +++ b/components/raiselockscreenplugin/CMakeLists.txt @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: 2025 Devin Lin +# SPDX-License-Identifier: LGPL-2.0-or-later + +ecm_add_qml_module(raiselockscreenplugin URI org.kde.plasma.private.mobileshell.raiselockscreenplugin GENERATE_PLUGIN_SOURCE) +target_sources(raiselockscreenplugin PRIVATE + raiselockscreen.cpp + utils.h +) + +target_link_libraries(raiselockscreenplugin PRIVATE + Qt::Qml + Qt::Quick + Qt::WaylandClient + Qt::WaylandClientPrivate + KF6::Service + KF6::Package + KF6::I18n + KF6::ConfigCore + KF6::Service + KF6::WindowSystem + Wayland::Client +) + +qt6_generate_wayland_protocol_client_sources(raiselockscreenplugin FILES + ${PLASMA_WAYLAND_PROTOCOLS_DIR}/kde-lockscreen-overlay-v1.xml) + +ecm_finalize_qml_module(raiselockscreenplugin) + + diff --git a/components/raiselockscreenplugin/raiselockscreen.cpp b/components/raiselockscreenplugin/raiselockscreen.cpp new file mode 100644 index 00000000..f5b01502 --- /dev/null +++ b/components/raiselockscreenplugin/raiselockscreen.cpp @@ -0,0 +1,140 @@ +// SPDX-FileCopyrightText: 2025 Devin Lin +// SPDX-License-Identifier: LGPL-2.0-or-later + +#include "raiselockscreen.h" +#include "utils.h" + +#include +#include +#include + +#include +#include + +#include "qwayland-kde-lockscreen-overlay-v1.h" + +class WaylandAboveLockscreen : public QWaylandClientExtensionTemplate, public QtWayland::kde_lockscreen_overlay_v1 +{ +public: + WaylandAboveLockscreen() + : QWaylandClientExtensionTemplate(1) + { + initialize(); + } +}; + +RaiseLockscreen::RaiseLockscreen(QObject *parent) + : QObject{parent} + , m_implementation(std::make_unique()) +{ + QObject::connect(KWaylandExtras::self(), &KWaylandExtras::xdgActivationTokenArrived, this, [this](int serial, const QString &token) { + if (!m_window || serial != m_serial) { + return; + } + + qCDebug(LOGGING_CATEGORY) << "XDG activation token arrived, activating window:" << m_window; + // Activate window over lockscreen once we have activation token + KWindowSystem::setCurrentXdgActivationToken(token); + KWindowSystem::activateWindow(m_window); + }); +} + +RaiseLockscreen::~RaiseLockscreen() +{ +} + +QWindow *RaiseLockscreen::window() const +{ + return m_window; +} + +void RaiseLockscreen::setWindow(QWindow *window) +{ + m_window = window; + Q_EMIT windowChanged(); +} + +bool RaiseLockscreen::initialized() const +{ + return m_initialized; +} + +void RaiseLockscreen::setInitialized(bool initialized) +{ + m_initialized = initialized; + Q_EMIT initializedChanged(); +} + +void RaiseLockscreen::initializeOverlay(QQuickWindow *window) +{ + if (!window || window == m_window) { + return; + } + + setWindow(window); + setOverlay(); + + // also re-set the overlay when the compositor gets restarted + connect(m_implementation.get(), &WaylandAboveLockscreen::activeChanged, this, &RaiseLockscreen::setOverlay); +} + +void RaiseLockscreen::setOverlay() +{ + if (!m_implementation->isActive()) { + setInitialized(false); + qCWarning(LOGGING_CATEGORY) << "Unable to set overlay: wayland protocol is not active"; + return; + } + auto waylandWindow = m_window->nativeInterface(); + if (!waylandWindow) { + // Add event filter to listen for when wayland window appears, and try again + m_window->installEventFilter(this); + setInitialized(false); + qCWarning(LOGGING_CATEGORY) << "Unable to set overlay: unable to get wayland window"; + return; + } + + // Listen to when new surface roles are created, and re-allow again. + // This can happen when a window is hidden, and then shown again (same surface, different surface role) + connect(waylandWindow, &QNativeInterface::Private::QWaylandWindow::surfaceRoleCreated, this, [this, waylandWindow]() { + m_implementation->allow(waylandWindow->surface()); + setInitialized(true); + qCDebug(LOGGING_CATEGORY) << "Initialized overlay successfully"; + }); + + if (waylandWindow->surface()) { + m_implementation->allow(waylandWindow->surface()); + setInitialized(true); + qCDebug(LOGGING_CATEGORY) << "Initialized overlay successfully"; + } +} + +bool RaiseLockscreen::eventFilter(QObject *watched, QEvent *event) +{ + auto window = qobject_cast(watched); + if (window && event->type() == QEvent::PlatformSurface) { + auto surfaceEvent = static_cast(event); + if (surfaceEvent->surfaceEventType() == QPlatformSurfaceEvent::SurfaceCreated) { + m_window->removeEventFilter(this); + setOverlay(); + } + } + return false; +} + +void RaiseLockscreen::raiseOverlay() +{ + if (!m_window) { + qCWarning(LOGGING_CATEGORY) << "Unable to raise overlay: no window set"; + return; + } + + if (!m_initialized) { + qCWarning(LOGGING_CATEGORY) << "Unable to raise overlay: window is not initialized for lockscreen overlaying, trying anyway..."; + } + + m_serial = KWaylandExtras::lastInputSerial(m_window); + + qCDebug(LOGGING_CATEGORY) << "Attempting to raise overlay: " << m_window << m_initialized; + KWaylandExtras::requestXdgActivationToken(m_window, m_serial, QStringLiteral("org.kde.plasmashell.desktop")); +} diff --git a/components/raiselockscreenplugin/raiselockscreen.h b/components/raiselockscreenplugin/raiselockscreen.h new file mode 100644 index 00000000..67a53ae5 --- /dev/null +++ b/components/raiselockscreenplugin/raiselockscreen.h @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2025 Devin Lin +// SPDX-License-Identifier: LGPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "qqml.h" + +class WaylandAboveLockscreen; + +/** + * A plugin to implement raising windows over the lockscreen. + */ +class RaiseLockscreen : public QObject +{ + Q_OBJECT + Q_PROPERTY(QWindow *window READ window WRITE setWindow NOTIFY windowChanged) + Q_PROPERTY(bool initialized READ initialized NOTIFY initializedChanged) + QML_ELEMENT + +public: + RaiseLockscreen(QObject *parent = nullptr); + ~RaiseLockscreen() override; + + QWindow *window() const; + void setWindow(QWindow *window); + + bool initialized() const; + + Q_INVOKABLE void initializeOverlay(QQuickWindow *window); + Q_INVOKABLE void raiseOverlay(); + +Q_SIGNALS: + void windowChanged(); + void initializedChanged(); + +private: + void setInitialized(bool initialized); + void setOverlay(); + bool eventFilter(QObject *watched, QEvent *event) override; + + bool m_initialized = false; + QWindow *m_window = nullptr; + int m_serial = 0; + std::unique_ptr m_implementation; +}; diff --git a/components/raiselockscreenplugin/utils.h b/components/raiselockscreenplugin/utils.h new file mode 100644 index 00000000..980a878f --- /dev/null +++ b/components/raiselockscreenplugin/utils.h @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2025 Devin Lin +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +static const QLoggingCategory &LOGGING_CATEGORY() +{ + static const QLoggingCategory category("raiselockscreenplugin"); + return category; +} diff --git a/containments/panel/CMakeLists.txt b/containments/panel/CMakeLists.txt index f292eeeb..47fe3a93 100644 --- a/containments/panel/CMakeLists.txt +++ b/containments/panel/CMakeLists.txt @@ -1,11 +1,15 @@ # SPDX-FileCopyrightText: 2017 Marco Martin # SPDX-FileCopyrightText: 2021 Aleix Pol # SPDX-FileCopyrightText: 2020-2021 Nicolas Fella +# SPDX-FileCopyrightText: 2022 Alexey Andreyev # SPDX-License-Identifier: GPL-2.0-or-later plasma_add_applet(org.kde.plasma.mobile.panel QML_SOURCES qml/main.qml + qml/LockscreenOverlay.qml + qml/StatusBarWrapper.qml + qml/StatusPanel.qml CPP_SOURCES phonepanel.cpp ) @@ -16,4 +20,5 @@ target_link_libraries(org.kde.plasma.mobile.panel PRIVATE Plasma::Plasma KF6::I18n KF6::Service + KF6::WindowSystem ) diff --git a/containments/panel/qml/LockscreenOverlay.qml b/containments/panel/qml/LockscreenOverlay.qml new file mode 100644 index 00000000..7dccce79 --- /dev/null +++ b/containments/panel/qml/LockscreenOverlay.qml @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: 2025 Devin Lin +// SPDX-License-Identifier: GPL-2.0-or-later + +import QtQuick +import QtQuick.Layouts +import QtQuick.Window + +import org.kde.plasma.private.mobileshell as MobileShell +import org.kde.plasma.private.mobileshell.state as MobileShellState +import org.kde.plasma.private.mobileshell.raiselockscreenplugin as RaiseLockscreenPlugin + +import org.kde.layershell 1.0 as LayerShell + +// Raise panel window over the lockscreen when it is shown +QtObject { + id: root + required property var window + + onWindowChanged: { + // Window.window may start out null, we need to wait for it to exist + if (root.window && !raiseLockscreen.initialized) { + initializeLockscreenOverlay(); + } + } + + function raiseOverlay() { + if (MobileShellState.LockscreenDBusClient.lockscreenActive) { + console.log('Raising top panel over the lockscreen'); + raiseLockscreen.raiseOverlay(); + } + } + + function initializeLockscreenOverlay() { + if (!root.window) { + return; + } + + raiseLockscreen.initializeOverlay(root.window); + + // Raise panel if lockscreen is already active + raiseOverlay(); + } + + // Raise panel over the lockscreen when it is enabled + readonly property var raiseLockscreen: RaiseLockscreenPlugin.RaiseLockscreen { + id: raiseLockscreen + Component.onCompleted: root.initializeLockscreenOverlay() + } + + readonly property Connections lockscreenConnections: Connections { + target: MobileShellState.LockscreenDBusClient + + function onLockscreenLocked() { + root.raiseOverlay(); + } + } +} \ No newline at end of file diff --git a/containments/panel/qml/StatusBarWrapper.qml b/containments/panel/qml/StatusBarWrapper.qml new file mode 100644 index 00000000..995926a6 --- /dev/null +++ b/containments/panel/qml/StatusBarWrapper.qml @@ -0,0 +1,100 @@ +// SPDX-FileCopyrightText: 2021-2025 Devin Lin +// SPDX-License-Identifier: GPL-2.0-or-later + +import QtQuick +import QtQuick.Layouts + +import org.kde.kirigami as Kirigami + +import org.kde.plasma.plasmoid + +import org.kde.plasma.private.mobileshell as MobileShell +import org.kde.plasma.private.mobileshell.state as MobileShellState +import org.kde.plasma.private.mobileshell.windowplugin as WindowPlugin + +Item { + id: root + + // The full intended height of the status panel. + required property real statusPanelHeight + + // Whether the background should be transparent, with content using a complementary theme on top. + required property bool transparentBackground + + // Request the panel itself to reapply settings (ex. for updating touch area). + signal updatePanelPropertiesRequested() + + + Kirigami.Theme.colorSet: transparentBackground ? Kirigami.Theme.Complementary : Kirigami.Theme.Header + Kirigami.Theme.inherit: false + + property real offset: 0 + + MobileShell.StatusBar { + id: topPanel + anchors.fill: parent + + showSecondRow: false + showTime: !MobileShellState.LockscreenDBusClient.lockscreenActive // Don't show time on the lockscreen, since we already have a massive clock + + showDropShadow: root.transparentBackground + backgroundColor: { + if (root.transparentBackground) { + return "transparent"; + } + + if (state == "default") { + return Kirigami.Theme.backgroundColor; + } else { + return Qt.rgba(Kirigami.Theme.backgroundColor.r, Kirigami.Theme.backgroundColor.g, Kirigami.Theme.backgroundColor.b, 0.95); + } + } + + transform: [ + Translate { + y: root.offset + } + ] + } + + states: [ + State { + // Default panel state, which is shown in the UI. + name: "default" + PropertyChanges { + target: root; offset: 0 + } + }, + State { + // Panel is forced to be visible and overlaid over content (will be automatically hidden after a duration). + name: "visible" + PropertyChanges { + target: root; offset: 0 + } + }, + State { + // Panel is hidden and requires a gesture to be shown. + name: "hidden" + PropertyChanges { + target: root; offset: -root.statusPanelHeight + } + } + ] + + transitions: Transition { + SequentialAnimation { + ParallelAnimation { + PropertyAnimation { + properties: "offset" + easing.type: root.state === "hidden" ? Easing.InExpo : Easing.OutExpo + duration: Kirigami.Units.longDuration + } + } + ScriptAction { + script: { + root.updatePanelPropertiesRequested(); + } + } + } + } +} \ No newline at end of file diff --git a/containments/panel/qml/StatusPanel.qml b/containments/panel/qml/StatusPanel.qml new file mode 100644 index 00000000..4d6caec0 --- /dev/null +++ b/containments/panel/qml/StatusPanel.qml @@ -0,0 +1,185 @@ +// SPDX-FileCopyrightText: 2021-2023 Devin Lin +// SPDX-FileCopyrightText: 2015 Marco Martin +// SPDX-License-Identifier: GPL-2.0-or-later + +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQml.Models + +import org.kde.kirigami as Kirigami + +import org.kde.plasma.plasmoid +import org.kde.plasma.core as PlasmaCore + +import org.kde.plasma.private.mobileshell as MobileShell +import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings +import org.kde.plasma.private.mobileshell.state as MobileShellState +import org.kde.plasma.private.mobileshell.windowplugin as WindowPlugin + +import org.kde.taskmanager as TaskManager +import org.kde.notificationmanager as NotificationManager +import org.kde.layershell 1.0 as LayerShell + +Item { + id: root + + // The base containment item + property ContainmentItem containmentItem + +//BEGIN API implementation + + Connections { + target: MobileShellState.ShellDBusClient + + function onOpenActionDrawerRequested() { + drawer.actionDrawer.open(); + } + + function onCloseActionDrawerRequested() { + drawer.actionDrawer.close(); + } + } + + Binding { + target: MobileShellState.ShellDBusClient + property: "isActionDrawerOpen" + value: drawer.visible + } + +//END API implementation + + // Startup feedback fill animation + MobileShell.StartupFeedbackPanelFill { + id: startupFeedbackColorAnimation + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + + fullHeight: containmentItem.height + screen: Plasmoid.screen + maximizedTracker: containmentItem.windowMaximizedTracker + + visible: !containmentItem.fullscreen + } + + // Status bar component + StatusBarWrapper { + id: statusBarWrapper + anchors.fill: parent + + statusPanelHeight: MobileShell.Constants.topPanelHeight + transparentBackground: { + // If we are over the lockscreen, always have a transparent background. + if (MobileShellState.LockscreenDBusClient.lockscreenActive) { + return true; + } + + return !containmentItem.showingApp && !containmentItem.fullscreen; + } + + state: { + // If we are on the lockscreen, always show the status panel. + if (MobileShellState.LockscreenDBusClient.lockscreenActive) { + return "default"; + } + + return MobileShellState.ShellDBusClient.panelState; + } + onStateChanged: { + if (state != "hidden") { + containmentItem.setWindowProperties(); + hiddenTimer.restart(); + } + } + + onUpdatePanelPropertiesRequested: containmentItem.setWindowProperties() + + // Hide status bar panel if it is visible for 3 seconds (in forced "visible" mode). + Timer { + id: hiddenTimer + running: false + interval: 3000 + onTriggered: { + if (statusBarWrapper.state == "visible") { + MobileShellState.ShellDBusClient.panelState = "hidden"; + } + } + } + } + + // Swiping area for swipe-down drawer + MobileShell.ActionDrawerOpenSurface { + id: swipeArea + actionDrawer: drawer.actionDrawer + anchors.fill: parent + + readonly property alias drawerVisible: drawer.visible + readonly property alias offset: drawer.actionDrawer.offset + + // if in a fullscreen app, the panels are visible, and the action drawer is opened + // set the panels to a hidden state + onDrawerVisibleChanged: { + if (statusBarWrapper.state == "visible") { + MobileShellState.ShellDBusClient.panelState = "hidden"; + } + } + } + + // Swipe-down drawer component + MobileShell.ActionDrawerWindow { + id: drawer + + onVisibleChanged: { + if (visible && MobileShellState.LockscreenDBusClient.lockscreenActive) { + // This works as long the wayland surface is the same (no window.close(), just window.visible = false) + lockScreenOverlay.raiseOverlay(); + } + } + + LockscreenOverlay { + id: lockScreenOverlay + window: drawer + } + + actionDrawer.restrictedPermissions: MobileShellState.LockscreenDBusClient.lockscreenActive + + actionDrawer.notificationSettings: NotificationManager.Settings {} + actionDrawer.notificationModel: NotificationManager.Notifications { + showExpired: true + showDismissed: true + showJobs: drawer.actionDrawer.notificationSettings.jobsInNotifications + sortMode: NotificationManager.Notifications.SortByTypeAndUrgency + groupMode: NotificationManager.Notifications.GroupApplicationsFlat + groupLimit: 2 + expandUnread: true + blacklistedDesktopEntries: drawer.actionDrawer.notificationSettings.historyBlacklistedApplications + blacklistedNotifyRcNames: drawer.actionDrawer.notificationSettings.historyBlacklistedServices + urgencies: { + var urgencies = NotificationManager.Notifications.CriticalUrgency + | NotificationManager.Notifications.NormalUrgency; + if (drawer.actionDrawer.notificationSettings.lowPriorityHistory) { + urgencies |= NotificationManager.Notifications.LowUrgency; + } + return urgencies; + } + } + + Connections { + target: drawer.actionDrawer + + function onPermissionsRequested() { + MobileShellState.ShellDBusClient.openLockScreenKeypad(); + } + } + + Connections { + target: MobileShellState.LockscreenDBusClient + + function onLockscreenUnlocked() { + // Run pending actions after the lockscreen gets unlocked + drawer.actionDrawer.runPendingNotificationAction(); + } + } + } +} diff --git a/containments/panel/qml/main.qml b/containments/panel/qml/main.qml index 27266cf9..fb6e0961 100644 --- a/containments/panel/qml/main.qml +++ b/containments/panel/qml/main.qml @@ -11,7 +11,6 @@ import org.kde.kirigami as Kirigami import org.kde.plasma.plasmoid import org.kde.plasma.core as PlasmaCore -import org.kde.plasma.components 3.0 as PlasmaComponents import org.kde.plasma.private.mobileshell as MobileShell import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings @@ -24,249 +23,97 @@ import org.kde.layershell 1.0 as LayerShell ContainmentItem { id: root - Plasmoid.backgroundHints: PlasmaCore.Types.NoBackground - Plasmoid.status: PlasmaCore.Types.PassiveStatus // ensure that the panel never takes focus away from the running app - // filled in by the shell (Panel.qml) with the plasma-workspace PanelView + Plasmoid.backgroundHints: PlasmaCore.Types.NoBackground + Plasmoid.status: PlasmaCore.Types.PassiveStatus // Ensure that the panel never takes focus away from the running app + + // Filled in by the shell (Panel.qml) with the plasma-workspace PanelView property var panel: null onPanelChanged: setWindowProperties() - MobileShell.HapticsEffect { - id: haptics + // Whether the startup feedback is showing + readonly property bool showingStartupFeedback: MobileShellState.ShellDBusObject.startupFeedbackModel.activeWindowIsStartupFeedback + + // Whether an app is maximized and showing (does not include startup feedback) + readonly property bool showingApp: windowMaximizedTracker.showingWindow && !showingStartupFeedback + + // Whether the currently showing app is in "fullscreen" + readonly property bool fullscreen: { + if (windowMaximizedTracker.isCurrentWindowFullscreen) { + return true; + } + + // The "autoHidePanelsEnabled" settings option treats every app as a fullscreen window + return (ShellSettings.Settings.autoHidePanelsEnabled && showingApp); + } + onFullscreenChanged: { + MobileShellState.ShellDBusClient.panelState = fullscreen ? "hidden" : "default"; } - readonly property real statusPanelHeight: MobileShell.Constants.topPanelHeight - readonly property real intendedWindowThickness: statusPanelHeight + property WindowPlugin.WindowMaximizedTracker windowMaximizedTracker: WindowPlugin.WindowMaximizedTracker { + id: windowMaximizedTracker + screenGeometry: Plasmoid.containment.screenGeometry - // use a timer so we don't have to maximize for every single pixel - // - improves performance if the shell is run in a window, and can be resized - Timer { - id: maximizeTimer - running: false - interval: 100 - onTriggered: root.panel.maximize() + onShowingWindowChanged: { + // Hide panel when we open the task switcher and an app is "fullscreen" + if (windowMaximizedTracker.showingWindow + && MobileShellState.ShellDBusClient.isTaskSwitcherVisible + && (ShellSettings.Settings.autoHidePanelsEnabled || fullscreen)) { + MobileShellState.ShellDBusClient.panelState = "hidden"; + } + } } function setWindowProperties() { if (root.panel) { root.panel.floating = false; root.panel.maximize(); // maximize first, then we can apply offsets (otherwise they are overridden) - root.panel.thickness = statusPanelHeight; + root.panel.thickness = MobileShell.Constants.topPanelHeight; root.panel.visibilityMode = ShellSettings.Settings.autoHidePanelsEnabled ? 3 : 0; MobileShell.ShellUtil.setWindowLayer(root.panel, LayerShell.Window.LayerOverlay) root.updateTouchArea(); } } - // update the touch area when hidden to minimize the space the panel takes for touch input + // Update the touch area when hidden to minimize the space the panel takes for touch input function updateTouchArea() { const hiddenTouchAreaThickness = Kirigami.Units.gridUnit; - if (statusPanel.state == "hidden") { + if (MobileShellState.ShellDBusClient.panelState == "hidden") { MobileShell.ShellUtil.setInputRegion(root.panel, Qt.rect(0, 0, root.panel.width, hiddenTouchAreaThickness)); } else { MobileShell.ShellUtil.setInputRegion(root.panel, Qt.rect(0, 0, 0, 0)); } } - Connections { - target: ShellSettings.Settings - - function onAutoHidePanelsEnabled() { - root.setWindowProperties(); - } + // Overlay the panel over the lockscreen when brought up + LockscreenOverlay { + window: root.Window.window } - // only opaque if there are no maximized windows on this screen - readonly property bool showingStartupFeedback: MobileShellState.ShellDBusObject.startupFeedbackModel.activeWindowIsStartupFeedback && startupFeedbackColorAnimation.visible && windowMaximizedTracker.windowCount === 1 - readonly property bool showingApp: windowMaximizedTracker.showingWindow && !showingStartupFeedback - readonly property color backgroundColor: topPanel.colorScopeColor - readonly property alias isCurrentWindowFullscreen: windowMaximizedTracker.isCurrentWindowFullscreen - readonly property bool fullscreen: isCurrentWindowFullscreen || (ShellSettings.Settings.autoHidePanelsEnabled && showingApp) - onFullscreenChanged: { - MobileShellState.ShellDBusClient.panelState = fullscreen ? "hidden" : "default"; - } - - WindowPlugin.WindowMaximizedTracker { - id: windowMaximizedTracker - screenGeometry: Plasmoid.containment.screenGeometry - - onShowingWindowChanged: { - if (windowMaximizedTracker.showingWindow && MobileShellState.ShellDBusClient.isTaskSwitcherVisible && (ShellSettings.Settings.autoHidePanelsEnabled || fullscreen)) { - MobileShellState.ShellDBusClient.panelState = "hidden"; - statusPanel.offset = -root.statusPanelHeight; - } - } - } - - // enforce thickness + // Enforce thickness of panel Binding { target: panel // assumed to be plasma-workspace "PanelView" component property: "thickness" value: MobileShell.Constants.topPanelHeight } -//BEGIN API implementation - Connections { - target: MobileShellState.ShellDBusClient + target: ShellSettings.Settings - function onOpenActionDrawerRequested() { - drawer.actionDrawer.open(); - } - - function onCloseActionDrawerRequested() { - drawer.actionDrawer.close(); + function onAutoHidePanelsEnabledChanged() { + root.setWindowProperties(); } } - Binding { - target: MobileShellState.ShellDBusClient - property: "isActionDrawerOpen" - value: drawer.visible - } - -//END API implementation - Component.onCompleted: { root.setWindowProperties(); } - MobileShell.StartupFeedbackPanelFill { - id: startupFeedbackColorAnimation - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - - fullHeight: root.height - screen: Plasmoid.screen - maximizedTracker: windowMaximizedTracker - - visible: !root.fullscreen - } - - Rectangle { + // Visual panel component + StatusPanel { id: statusPanel anchors.fill: parent - Kirigami.Theme.colorSet: root.showingApp ? Kirigami.Theme.Header : Kirigami.Theme.Complementary - Kirigami.Theme.inherit: false - - color: statusPanel.state == "default" && (root.showingApp || root.fullscreen) ? Kirigami.Theme.backgroundColor : "transparent" - - property real offset: 0 - - // top panel component - MobileShell.StatusBar { - id: topPanel - anchors.fill: parent - - showDropShadow: !root.showingApp - backgroundColor: statusPanel.state != "default" && root.showingApp ? Qt.rgba(Kirigami.Theme.backgroundColor.r, Kirigami.Theme.backgroundColor.g, Kirigami.Theme.backgroundColor.b, 0.95) : "transparent" - - transform: [ - Translate { - y: statusPanel.offset - } - ] - } - - state: MobileShellState.ShellDBusClient.panelState - onStateChanged: { - if (statusPanel.state != "hidden") { - root.setWindowProperties(); - hiddenTimer.restart(); - } - } - - Timer { - id: hiddenTimer - running: false - interval: 3000 - onTriggered: { - if (statusPanel.state == "visible") { - MobileShellState.ShellDBusClient.panelState = "hidden"; - } - } - } - - states: [ - State { - name: "default" - PropertyChanges { - target: statusPanel; offset: 0 - } - }, - State { - name: "visible" - PropertyChanges { - target: statusPanel; offset: 0 - } - }, - State { - name: "hidden" - PropertyChanges { - target: statusPanel; offset: -root.statusPanelHeight - } - } - ] - - transitions: Transition { - SequentialAnimation { - ParallelAnimation { - PropertyAnimation { - properties: "offset"; easing.type: statusPanel.state === "hidden" ? Easing.InExpo : Easing.OutExpo; duration: Kirigami.Units.longDuration - } - } - ScriptAction { - script: { - root.setWindowProperties(); - } - } - } - } - } - - // swiping area for swipe-down drawer - MobileShell.ActionDrawerOpenSurface { - id: swipeArea - actionDrawer: drawer.actionDrawer - anchors.fill: parent - - readonly property alias drawerVisible: drawer.visible - readonly property alias offset: drawer.actionDrawer.offset - - // if in a fullscreen app, the panels are visible, and the action drawer is opened - // set the panels to a hidden state - onDrawerVisibleChanged: { - if (statusPanel.state == "visible") { - MobileShellState.ShellDBusClient.panelState = "hidden"; - } - } - } - - // swipe-down drawer component - MobileShell.ActionDrawerWindow { - id: drawer - - actionDrawer.notificationSettings: NotificationManager.Settings {} - actionDrawer.notificationModel: NotificationManager.Notifications { - showExpired: true - showDismissed: true - showJobs: drawer.actionDrawer.notificationSettings.jobsInNotifications - sortMode: NotificationManager.Notifications.SortByTypeAndUrgency - groupMode: NotificationManager.Notifications.GroupApplicationsFlat - groupLimit: 2 - expandUnread: true - blacklistedDesktopEntries: drawer.actionDrawer.notificationSettings.historyBlacklistedApplications - blacklistedNotifyRcNames: drawer.actionDrawer.notificationSettings.historyBlacklistedServices - urgencies: { - var urgencies = NotificationManager.Notifications.CriticalUrgency - | NotificationManager.Notifications.NormalUrgency; - if (drawer.actionDrawer.notificationSettings.lowPriorityHistory) { - urgencies |= NotificationManager.Notifications.LowUrgency; - } - return urgencies; - } - } + containmentItem: root } } diff --git a/containments/taskpanel/qml/main.qml b/containments/taskpanel/qml/main.qml index 9bf3c4da..888708ec 100644 --- a/containments/taskpanel/qml/main.qml +++ b/containments/taskpanel/qml/main.qml @@ -128,7 +128,7 @@ ContainmentItem { Connections { target: ShellSettings.Settings - function onAutoHidePanelsEnabled() { + function onAutoHidePanelsEnabledChanged() { root.setWindowProperties(); } } diff --git a/shell/contents/lockscreen/HeaderComponent.qml b/shell/contents/lockscreen/HeaderComponent.qml deleted file mode 100644 index 8844e8f3..00000000 --- a/shell/contents/lockscreen/HeaderComponent.qml +++ /dev/null @@ -1,145 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2024 Devin Lin - * SPDX-License-Identifier: GPL-2.0-or-later - */ - -import QtQuick 2.12 -import QtQuick.Controls 2.15 -import QtQuick.Layouts 1.1 - -import org.kde.kirigami as Kirigami -import org.kde.plasma.private.mobileshell as MobileShell -import org.kde.plasma.components 3.0 as PC3 - -import org.kde.notificationmanager as NotificationManager - -Item { - id: root - required property real openFactor - required property real statusBarHeight - - property var notificationsModel: [] - - readonly property bool actionDrawerVisible: swipeArea.actionDrawer.intendedToBeVisible - - signal passwordRequested() - - // The status bar and quicksettings take a while to load, don't pause initial lockscreen loading for it - Timer { - id: loadTimer - running: true - repeat: false - onTriggered: { - statusBarLoader.active = true - actionDrawerLoader.active = true - } - } - - // Add loading indicator when status bar has not loaded yet - PC3.BusyIndicator { - id: statusBarLoadingIndication - anchors.top: parent.top - anchors.right: parent.right - anchors.topMargin: Kirigami.Units.smallSpacing - anchors.rightMargin: Kirigami.Units.smallSpacing - visible: statusBarLoader.status != Loader.Ready - - implicitHeight: root.statusBarHeight - implicitWidth: root.statusBarHeight - - Kirigami.Theme.inherit: false - Kirigami.Theme.colorSet: Kirigami.Theme.Complementary - } - - // Status bar - Loader { - id: statusBarLoader - active: false - asynchronous: true - visible: status == Loader.Ready - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - height: root.statusBarHeight - - sourceComponent: MobileShell.StatusBar { - id: statusBar - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - height: root.statusBarHeight - - Kirigami.Theme.inherit: false - Kirigami.Theme.colorSet: Kirigami.Theme.Complementary - - backgroundColor: "transparent" - - showSecondRow: false - showDropShadow: true - showTime: false - disableSystemTray: true // prevent SIGABRT, since loading the system tray on the lockscreen leads to bad... things - } - } - - // Drag down gesture to open action drawer - MobileShell.ActionDrawerOpenSurface { - id: swipeArea - actionDrawer: actionDrawerLoader.item ? actionDrawerLoader.item.actionDrawer : null - - anchors.fill: statusBarLoader - } - - // Dynamically load on swipe-down to avoid having to load at start - Loader { - id: actionDrawerLoader - active: false - asynchronous: true - visible: status == Loader.Ready - - anchors.fill: parent - - sourceComponent: Item { - property var actionDrawer: drawer - - // Action drawer component - MobileShell.ActionDrawer { - id: drawer - anchors.fill: parent - - visible: offset !== 0 - restrictedPermissions: true - - notificationSettings: NotificationManager.Settings {} - notificationModel: root.notificationsModel - notificationModelType: MobileShell.NotificationsModelType.WatchedNotificationsModel - - property bool requestNotificationAction: false - - // notification button clicked, requesting auth - onPermissionsRequested: { - requestNotificationAction = true; - drawer.close(); - root.passwordRequested(); - } - } - - // listen to authentication events - Connections { - target: authenticator - function onSucceeded() { - // run pending action if successfully unlocked - if (drawer.requestNotificationAction) { - drawer.runPendingAction(); - drawer.requestNotificationAction = false; - } - } - function onFailed() { - drawer.requestNotificationAction = false; - } - } - } - } -} diff --git a/shell/contents/lockscreen/LockScreen.qml b/shell/contents/lockscreen/LockScreen.qml index 06bf7dfb..d66a2558 100644 --- a/shell/contents/lockscreen/LockScreen.qml +++ b/shell/contents/lockscreen/LockScreen.qml @@ -9,6 +9,7 @@ import QtQuick.Layouts import org.kde.plasma.core as PlasmaCore import org.kde.notificationmanager as Notifications import org.kde.plasma.private.mobileshell as MobileShell +import org.kde.plasma.private.mobileshell.state as MobileShellState import org.kde.plasma.private.mobileshell.dpmsplugin as DPMS import org.kde.plasma.components 3.0 as PC3 import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings @@ -30,7 +31,7 @@ Item { readonly property bool isWidescreen: root.height < Kirigami.Units.gridUnit * 25 && (root.height < root.width * 0.75) property bool notificationsShown: false - property var passwordBar: flickableLoader.item ? flickableLoader.item.flickable.passwordBar : null + property var passwordBar: flickable.passwordBar Component.onCompleted: { forceActiveFocus(); @@ -38,12 +39,18 @@ Item { // Listen for keyboard events, and focus on input area Keys.onPressed: (event) => { - if (flickableLoader.item) { - root.lockScreenState.isKeyboardMode = true; - flickableLoader.item.flickable.goToOpenPosition(); - passwordBar.textField.forceActiveFocus(); + root.lockScreenState.isKeyboardMode = true; + flickable.goToOpenPosition(); + passwordBar.textField.forceActiveFocus(); - passwordBar.keyPress(event.text); + passwordBar.keyPress(event.text); + } + + Connections { + target: MobileShellState.ShellDBusClient + + function onOpenLockScreenKeypadRequested() { + flickable.goToOpenPosition(); } } @@ -63,7 +70,7 @@ Item { sourceComponent: WallpaperBlur { source: wallpaper - opacity: flickableLoader.item ? flickableLoader.item.flickable.openFactor : 0 + opacity: flickable.openFactor } } @@ -72,8 +79,8 @@ Item { // Ensure keypad is opened when password is updated (ex. keyboard) function onPasswordChanged() { - if (root.lockScreenState.password !== "" && flickableLoader.item) { - flickableLoader.item.flickable.goToOpenPosition(); + if (root.lockScreenState.password !== "") { + flickable.goToOpenPosition(); } } } @@ -84,191 +91,130 @@ Item { onDpmsTurnedOff: (screen) => { if (screen.name === Screen.name) { - if (flickableLoader.item) { - flickableLoader.item.flickable.goToClosePosition(); - } + flickable.goToClosePosition(); lockScreenState.resetPassword(); } } } + // Container for lockscreen contents Item { id: lockscreenContainer anchors.fill: parent - // Header bar and action drawer - HeaderComponent { - id: headerBar - z: 1 + FlickContainer { + id: flickable anchors.fill: parent - statusBarHeight: MobileShell.Constants.topPanelHeight - openFactor: flickableLoader.item ? flickableLoader.item.flickable.openFactor : 0 + property alias passwordBar: keypad.passwordBar + + // Speed up animation when passwordless + animationDuration: root.lockScreenState.canBeUnlocked ? 400 : 800 + + // Distance to swipe to fully open keypad + keypadHeight: Kirigami.Units.gridUnit * 20 + + Component.onCompleted: { + // Go to closed position when loaded + flickable.position = 0; + flickable.goToClosePosition(); + } + + // Unlock lockscreen if it's already unlocked and keypad is opened + onOpened: { + if (root.lockScreenState.canBeUnlocked) { + Qt.quit(); + } + } + + // Unlock lockscreen if it's already unlocked and keypad is open + Connections { + target: root.lockScreenState + function onCanBeUnlockedChanged() { + if (root.lockScreenState.canBeUnlocked && flickable.openFactor > 0.8) { + Qt.quit(); + } + } + } + + // Clear entered password after closing keypad + onOpenFactorChanged: { + if (flickable.openFactor < 0.1 && !flickable.movingUp) { + root.passwordBar.clear(); + } + } + + // scroll up icon + BottomIconIndicator { + id: scrollUpIconLoader + lockScreenState: root.lockScreenState + opacity: Math.max(0, 1 - flickable.openFactor * 2) + + anchors.bottom: parent.bottom + anchors.bottomMargin: Kirigami.Units.gridUnit + flickable.position * 0.1 + anchors.horizontalCenter: parent.horizontalCenter + } + + Rectangle { + id: keypadScrim + anchors.fill: parent + visible: opacity > 0 + opacity: flickable.openFactor + color: Qt.rgba(0, 0, 0, 0.5) + } + + MouseArea { + // Disable "double tap to lock" to avoid accidental locking + // when the keypad is open, and the user is typing their password. + enabled: flickable.openFactor < 0.1 + anchors.fill: parent + + onDoubleClicked: (mouse) => { + if (ShellSettings.KWinSettings.doubleTapWakeup) { + deviceLock.triggerLock(); + } + } + + MobileShell.DeviceLock { + id: deviceLock + } + } + + Keypad { + id: keypad + visible: !root.lockScreenState.canBeUnlocked // don't show for passwordless login + anchors.fill: parent + openProgress: flickable.openFactor + lockScreenState: root.lockScreenState + + // only show in last 50% of anim + opacity: (flickable.openFactor - 0.5) * 2 + transform: Translate { y: (flickable.keypadHeight - flickable.position) * 0.1 } + } + } + + LockScreenContent { + id: lockScreenContent + + isVertical: !root.isWidescreen + opacity: Math.max(0, 1 - flickable.openFactor * 2) + transform: [ + Scale { + origin.x: lockScreenContent.width / 2 + origin.y: lockScreenContent.height / 2 + yScale: 1 - (flickable.openFactor * 2) * 0.1 + xScale: 1 - (flickable.openFactor * 2) * 0.1 + } + ] + + lockScreenState: root.lockScreenState notificationsModel: root.notifModel - onPasswordRequested: root.askPassword() - } + onNotificationsShownChanged: root.notificationsShown = notificationsShown + onPasswordRequested: flickable.goToOpenPosition() - // Add loading indicator when status bar has not loaded yet - PC3.BusyIndicator { - id: flickableLoadingBusyIndicator - anchors.centerIn: parent - visible: flickableLoader.status != Loader.Ready + scrollLock: flickable.openFactor > 0.2 + z: scrollLock ? -1 : 0 - implicitHeight: Kirigami.Units.iconSizes.huge - implicitWidth: Kirigami.Units.iconSizes.huge - - Kirigami.Theme.inherit: false - Kirigami.Theme.colorSet: Kirigami.Theme.Complementary - } - - // Load flickable async - Loader { - id: flickableLoader - - active: false - asynchronous: true - opacity: status == Loader.Ready ? 1 : 0 - visible: opacity > 0 anchors.fill: parent - - Behavior on opacity { - NumberAnimation {} - } - - // This take a while to load, don't pause initial lockscreen and wallpaper loading for it - Timer { - id: loadTimer - running: true - repeat: false - onTriggered: { - flickableLoader.active = true - } - } - - // Container for lockscreen contents - sourceComponent: Item { - id: item - property alias flickable: flickable - FlickContainer { - id: flickable - anchors.fill: parent - property alias passwordBar: keypad.passwordBar - - // Speed up animation when passwordless - animationDuration: root.lockScreenState.canBeUnlocked ? 400 : 800 - - // Distance to swipe to fully open keypad - keypadHeight: Kirigami.Units.gridUnit * 20 - - Component.onCompleted: { - // Go to closed position when loaded - flickable.position = 0; - flickable.goToClosePosition(); - } - - // Unlock lockscreen if it's already unlocked and keypad is opened - onOpened: { - if (root.lockScreenState.canBeUnlocked) { - Qt.quit(); - } - } - - // Unlock lockscreen if it's already unlocked and keypad is open - Connections { - target: root.lockScreenState - function onCanBeUnlockedChanged() { - if (root.lockScreenState.canBeUnlocked && flickable.openFactor > 0.8) { - Qt.quit(); - } - } - } - - // Clear entered password after closing keypad - onOpenFactorChanged: { - if (flickable.openFactor < 0.1 && !flickable.movingUp) { - root.passwordBar.clear(); - } - } - - // scroll up icon - BottomIconIndicator { - id: scrollUpIconLoader - lockScreenState: root.lockScreenState - opacity: Math.max(0, 1 - flickable.openFactor * 2) - - anchors.bottom: parent.bottom - anchors.bottomMargin: Kirigami.Units.gridUnit + flickable.position * 0.1 - anchors.horizontalCenter: parent.horizontalCenter - } - - Rectangle { - id: keypadScrim - anchors.fill: parent - visible: opacity > 0 - opacity: flickable.openFactor - color: Qt.rgba(0, 0, 0, 0.5) - } - - MouseArea { - // Disable "double tap to lock" to avoid accidental locking - // when the keypad is open, and the user is typing their password. - enabled: flickable.openFactor < 0.1 - anchors.fill: parent - - onDoubleClicked: (mouse) => { - if (ShellSettings.KWinSettings.doubleTapWakeup) { - deviceLock.triggerLock(); - } - } - - MobileShell.DeviceLock { - id: deviceLock - } - } - - Keypad { - id: keypad - visible: !root.lockScreenState.canBeUnlocked // don't show for passwordless login - anchors.fill: parent - openProgress: flickable.openFactor - lockScreenState: root.lockScreenState - - // only show in last 50% of anim - opacity: (flickable.openFactor - 0.5) * 2 - transform: Translate { y: (flickable.keypadHeight - flickable.position) * 0.1 } - } - } - - LockScreenContent { - id: lockScreenContent - - isVertical: !root.isWidescreen - opacity: Math.max(0, 1 - flickable.openFactor * 2) - transform: [ - Scale { - origin.x: lockScreenContent.width / 2 - origin.y: lockScreenContent.height / 2 - yScale: 1 - (flickable.openFactor * 2) * 0.1 - xScale: 1 - (flickable.openFactor * 2) * 0.1 - } - ] - - lockScreenState: root.lockScreenState - notificationsModel: root.notifModel - onNotificationsShownChanged: root.notificationsShown = notificationsShown - onPasswordRequested: flickable.goToOpenPosition() - - scrollLock: headerBar.actionDrawerVisible || (flickableLoader.item ? flickableLoader.item.flickable.openFactor > 0.2 : false) - z: scrollLock ? -1 : 0 - - anchors { - //topMargin: headerBar.statusBarHeight - top: item.top - bottom: item.bottom - left: item.left - right: item.right - } - } - } } } } diff --git a/tests/ActionDrawerTest.qml b/tests/ActionDrawerTest.qml index d439d883..434f2982 100644 --- a/tests/ActionDrawerTest.qml +++ b/tests/ActionDrawerTest.qml @@ -40,7 +40,7 @@ ApplicationWindow { anchors.left: parent.left anchors.right: parent.right - height: Kirigami.Units.gridUnit * 1.25 + height: MobileShell.Constants.topPanelHeight Kirigami.Theme.inherit: false Kirigami.Theme.colorSet: Kirigami.Theme.Complementary diff --git a/tests/LockScreenTest.qml b/tests/LockScreenTest.qml index 37be1253..831c2213 100644 --- a/tests/LockScreenTest.qml +++ b/tests/LockScreenTest.qml @@ -1,11 +1,13 @@ -// SPDX-FileCopyrightText: 2022 Devin LIn +// SPDX-FileCopyrightText: 2022 Devin Lin // SPDX-License-Identifier: LGPL-2.0-or-later import QtQuick 2.15 import QtQuick.Controls 2.15 +import org.kde.kirigami as Kirigami import org.kde.plasma.components 3.0 as PC3 import org.kde.plasma.private.mobileshell as MobileShell +import org.kde.notificationmanager as NotificationManager import "../shell/contents/lockscreen" as LockScreen @@ -61,9 +63,48 @@ ApplicationWindow { } } - // component to test + // Component to test LockScreen.LockScreen { anchors.fill: parent } + + // Simulate "overlaid" status bar and quick settings panel + MobileShell.StatusBar { + id: statusBar + z: 1 + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + height: Kirigami.Units.gridUnit * 1.25 + + Kirigami.Theme.inherit: false + Kirigami.Theme.colorSet: Kirigami.Theme.Complementary + + backgroundColor: "transparent" + + showSecondRow: false + showDropShadow: true + showTime: true + disableSystemTray: true // prevent SIGABRT, since loading the system tray leads to bad... things + } + + MobileShell.ActionDrawerOpenSurface { + anchors.fill: statusBar + actionDrawer: drawer + z: 1 + } + + MobileShell.ActionDrawer { + id: drawer + z: 1 + anchors.fill: parent + visible: offset !== 0 + + notificationSettings: NotificationManager.Settings {} + notificationModelType: MobileShell.NotificationsModelType.WatchedNotificationsModel + notificationModel: NotificationManager.WatchedNotificationsModel {} + } }