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 {} + } }