mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-15 07:44:01 -04:00
- Quick actions are reorderable with button color roles and optional power accents - Updated dank-qml-common
1489 lines
63 KiB
QML
1489 lines
63 KiB
QML
pragma ComponentBehavior: Bound
|
|
|
|
import QtQuick
|
|
import QtQuick.Effects
|
|
import QtQuick.Layouts
|
|
import QtQuick.Window
|
|
import Quickshell
|
|
import qs.Common
|
|
import qs.Modals
|
|
import qs.Modules.Notifications as Notifications
|
|
import qs.Services
|
|
import qs.Widgets
|
|
import qs.DankCommon.Session
|
|
import "../../Common/KeyUtils.js" as KeyUtils
|
|
|
|
Item {
|
|
id: root
|
|
readonly property var log: Log.scoped("LockScreenContent")
|
|
|
|
function encodeFileUrl(path) {
|
|
if (!path)
|
|
return "";
|
|
return "file://" + path.split('/').map(s => encodeURIComponent(s)).join('/');
|
|
}
|
|
|
|
property string passwordBuffer: ""
|
|
property bool demoMode: false
|
|
property var pam: demoPam
|
|
property string screenName: ""
|
|
property bool unlocking: false
|
|
property string pamState: ""
|
|
property bool lockerReadySent: false
|
|
property bool lockerReadyArmed: false
|
|
property var sessionLock: null
|
|
readonly property bool hasCustomWallpaper: SettingsData.lockScreenWallpaperPath !== ""
|
|
readonly property string lockFontFamily: SettingsData.lockScreenFontFamily
|
|
|
|
component ClockDigitText: StyledText {
|
|
font.pixelSize: LockMetrics.clockSize
|
|
font.weight: Theme.fontWeight
|
|
color: Theme.lockScreenContentColor
|
|
horizontalAlignment: Text.AlignHCenter
|
|
font.family: root.lockFontFamily !== "" ? root.lockFontFamily : resolvedFontFamily
|
|
}
|
|
|
|
signal unlockRequested
|
|
signal passwordEdited(string text)
|
|
|
|
function resetLockState() {
|
|
lockerReadySent = false;
|
|
lockerReadyArmed = true;
|
|
unlocking = false;
|
|
pamState = "";
|
|
if (pam)
|
|
pam.lockMessage = "";
|
|
}
|
|
|
|
function focusPasswordField() {
|
|
if (demoMode || !passwordField)
|
|
return;
|
|
passwordField.forceActiveFocus();
|
|
}
|
|
|
|
function currentAuthFeedbackText() {
|
|
if (!pam)
|
|
return "";
|
|
if (pam.u2fState === "insert" && !pam.u2fPending)
|
|
return I18n.tr("Insert your security key...");
|
|
if (pam.u2fState === "waiting" && !pam.u2fPending)
|
|
return I18n.tr("Touch your security key...");
|
|
if (pam.lockMessage && pam.lockMessage.length > 0)
|
|
return pam.lockMessage;
|
|
if (root.pamState === "error")
|
|
return I18n.tr("Authentication error - try again");
|
|
if (root.pamState === "max")
|
|
return I18n.tr("Too many attempts - locked out");
|
|
if (root.pamState === "fail")
|
|
return I18n.tr("Incorrect password - try again");
|
|
if (pam.fprint.status === "disabled")
|
|
return "";
|
|
if (pam.fprintState === "error")
|
|
return I18n.tr("Fingerprint error");
|
|
if (pam.fprintState === "max")
|
|
return I18n.tr("Maximum fingerprint attempts reached. Please use password.");
|
|
if (pam.fprintState === "fail")
|
|
return I18n.tr("Fingerprint not recognized (%1/%2). Please try again or use password.", "lock screen message, %1 is attempts used, %2 is max attempts").arg(pam.fprint.tries).arg(SettingsData.maxFprintTries);
|
|
return "";
|
|
}
|
|
|
|
function authFeedbackIsHint() {
|
|
return pam && (pam.u2fState === "waiting" || pam.u2fState === "insert") && !pam.u2fPending;
|
|
}
|
|
|
|
function canStartSecurityKeyUnlock() {
|
|
return !demoMode && pam && pam.u2f && pam.u2f.available && SettingsData.enableU2f && SettingsData.u2fMode === "or" && !pam.passwd.active && !pam.u2f.active && !pam.u2fPending && !root.unlocking;
|
|
}
|
|
|
|
function triggerSecurityKeyUnlock() {
|
|
if (!canStartSecurityKeyUnlock())
|
|
return;
|
|
passwordField.clear();
|
|
pam.u2f.startForAlternativeAuth();
|
|
}
|
|
|
|
function securityKeyShortcutMatches(event) {
|
|
return SettingsData.lockScreenSecurityKeyShortcutEnabled && KeyUtils.eventMatchesCombo(event, SettingsData.lockScreenSecurityKeyShortcut);
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
WeatherService.addRef();
|
|
UserInfoService.getUserInfo();
|
|
|
|
lockerReadyArmed = true;
|
|
}
|
|
|
|
Component.onDestruction: {
|
|
WeatherService.removeRef();
|
|
}
|
|
|
|
function sendLockerReadyOnce() {
|
|
if (root.demoMode)
|
|
return;
|
|
if (lockerReadySent)
|
|
return;
|
|
if (root.unlocking)
|
|
return;
|
|
lockerReadySent = true;
|
|
if (SessionService.loginctlAvailable && DMSService.apiVersion >= 2) {
|
|
DMSService.sendRequest("loginctl.lockerReady", null, resp => {
|
|
if (resp?.error)
|
|
log.warn("lockerReady failed:", resp.error);
|
|
else
|
|
log.debug("lockerReady sent (afterAnimating/afterRendering)");
|
|
});
|
|
}
|
|
}
|
|
|
|
function maybeSend() {
|
|
if (!lockerReadyArmed)
|
|
return;
|
|
if (root.unlocking)
|
|
return;
|
|
if (!root.visible || root.opacity <= 0)
|
|
return;
|
|
if (root.sessionLock && !root.sessionLock.secure)
|
|
return;
|
|
Qt.callLater(() => {
|
|
if (root.visible && root.opacity > 0 && !root.unlocking)
|
|
sendLockerReadyOnce();
|
|
});
|
|
}
|
|
|
|
Connections {
|
|
target: root.sessionLock
|
|
enabled: target !== null
|
|
function onSecureChanged() {
|
|
root.maybeSend();
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: root.Window.window
|
|
enabled: target !== null
|
|
|
|
function onAfterAnimating() {
|
|
maybeSend();
|
|
}
|
|
function onAfterRendering() {
|
|
maybeSend();
|
|
}
|
|
}
|
|
|
|
onVisibleChanged: maybeSend()
|
|
onOpacityChanged: maybeSend()
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
color: SettingsData.effectiveWallpaperBackgroundColor
|
|
}
|
|
|
|
Loader {
|
|
anchors.fill: parent
|
|
active: {
|
|
if (root.hasCustomWallpaper)
|
|
return false;
|
|
var currentWallpaper = SessionData.getMonitorWallpaper(screenName);
|
|
return !currentWallpaper || (currentWallpaper && currentWallpaper.startsWith("#"));
|
|
}
|
|
asynchronous: true
|
|
|
|
sourceComponent: DankBackdrop {
|
|
screenName: root.screenName
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
id: wallpaperBackground
|
|
anchors.fill: parent
|
|
|
|
readonly property string wallpaperSource: {
|
|
if (root.hasCustomWallpaper)
|
|
return root.encodeFileUrl(SettingsData.lockScreenWallpaperPath);
|
|
var w = SessionData.getMonitorWallpaper(screenName);
|
|
return (w && !w.startsWith("#")) ? encodeFileUrl(w) : "";
|
|
}
|
|
readonly property string fillModeName: {
|
|
if (SettingsData.lockScreenWallpaperFillMode !== "")
|
|
return SettingsData.lockScreenWallpaperFillMode;
|
|
return root.hasCustomWallpaper ? "Fill" : SessionData.getMonitorWallpaperFillMode(root.screenName);
|
|
}
|
|
|
|
readonly property real screenScale: CompositorService.getScreenScale(Quickshell.screens.find(s => s.name === root.screenName) ?? null)
|
|
readonly property size decodeSize: Qt.size(Math.round(width * screenScale), Math.round(height * screenScale))
|
|
|
|
active: wallpaperSource !== "" && width > 0 && height > 0
|
|
asynchronous: false
|
|
|
|
sourceComponent: fillModeName === "Scrolling" ? scrollWallpaperComp : plainWallpaperComp
|
|
|
|
layer.enabled: true
|
|
layer.effect: MultiEffect {
|
|
autoPaddingEnabled: false
|
|
blurEnabled: true
|
|
blur: Theme.lockScreenBlur
|
|
blurMax: Theme.lockScreenBlurMax
|
|
blurMultiplier: 1
|
|
}
|
|
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
duration: LockMetrics.effectsDuration
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: Theme.expressiveCurves.expressiveEffects
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: plainWallpaperComp
|
|
Image {
|
|
source: wallpaperBackground.wallpaperSource
|
|
sourceSize: wallpaperBackground.decodeSize
|
|
fillMode: Theme.getFillMode(wallpaperBackground.fillModeName)
|
|
smooth: true
|
|
cache: true
|
|
asynchronous: false
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: scrollWallpaperComp
|
|
Item {
|
|
Image {
|
|
id: scrollSource
|
|
anchors.fill: parent
|
|
visible: false
|
|
source: wallpaperBackground.wallpaperSource
|
|
sourceSize: wallpaperBackground.decodeSize
|
|
asynchronous: false
|
|
cache: true
|
|
}
|
|
|
|
ShaderEffectSource {
|
|
id: scrollSrc
|
|
sourceItem: scrollSource
|
|
hideSource: true
|
|
live: false
|
|
}
|
|
|
|
ShaderEffect {
|
|
anchors.fill: parent
|
|
|
|
readonly property var scrollPos: SessionData.getMonitorScrollPosition(screenName)
|
|
|
|
property variant source1: scrollSrc
|
|
property variant source2: scrollSrc
|
|
property real progress: 0.0
|
|
property real fillMode: Theme.getShaderFillMode(wallpaperBackground.fillModeName)
|
|
property real scrollX: scrollPos.scrollX
|
|
property real scrollY: scrollPos.scrollY
|
|
property real imageWidth1: scrollSource.implicitWidth > 0 ? scrollSource.implicitWidth : 1
|
|
property real imageHeight1: scrollSource.implicitHeight > 0 ? scrollSource.implicitHeight : 1
|
|
property real imageWidth2: imageWidth1
|
|
property real imageHeight2: imageHeight1
|
|
property real screenWidth: width > 0 ? width : 1
|
|
property real screenHeight: height > 0 ? height : 1
|
|
property vector4d fillColor: Qt.vector4d(0, 0, 0, 1)
|
|
|
|
fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/wp_fade.frag.qsb")
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
color: Theme.screenOffColor
|
|
opacity: Theme.lockScreenScrimAlpha
|
|
}
|
|
|
|
SystemClock {
|
|
id: systemClock
|
|
|
|
precision: SystemClock.Seconds
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
color: "transparent"
|
|
|
|
Item {
|
|
id: clockContainer
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.bottom: parent.verticalCenter
|
|
anchors.bottomMargin: LockMetrics.fieldHeight
|
|
width: parent.width
|
|
height: verticalClock.visible ? verticalClock.implicitHeight : clockText.implicitHeight
|
|
visible: SettingsData.lockScreenShowTime
|
|
|
|
Row {
|
|
id: clockText
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.top: parent.top
|
|
spacing: 0
|
|
visible: SettingsData.lockScreenClockStyle !== "vertical"
|
|
|
|
property string fullTimeStr: {
|
|
const format = SettingsData.getEffectiveTimeFormat();
|
|
return systemClock.date.toLocaleTimeString(Qt.locale(), format);
|
|
}
|
|
property var timeParts: fullTimeStr.split(':')
|
|
property string hours: timeParts[0] || ""
|
|
property string minutes: timeParts[1] || ""
|
|
property string secondsWithAmPm: timeParts.length > 2 ? timeParts[2] : ""
|
|
property string seconds: secondsWithAmPm.replace(/\s*(AM|PM|am|pm)$/i, '')
|
|
property string ampm: {
|
|
const match = fullTimeStr.match(/\s*(AM|PM|am|pm)$/i);
|
|
return match ? match[0].trim() : "";
|
|
}
|
|
property bool hasSeconds: timeParts.length > 2
|
|
|
|
ClockDigitText {
|
|
width: clockText.hours.length > 1 ? LockMetrics.clockDigitWidth : 0
|
|
text: clockText.hours.length > 1 ? clockText.hours[0] : ""
|
|
}
|
|
|
|
ClockDigitText {
|
|
width: LockMetrics.clockDigitWidth
|
|
text: clockText.hours.length > 1 ? clockText.hours[1] : clockText.hours.length > 0 ? clockText.hours[0] : ""
|
|
}
|
|
|
|
ClockDigitText {
|
|
text: ":"
|
|
}
|
|
|
|
ClockDigitText {
|
|
width: LockMetrics.clockDigitWidth
|
|
text: clockText.minutes.length > 0 ? clockText.minutes[0] : ""
|
|
}
|
|
|
|
ClockDigitText {
|
|
width: LockMetrics.clockDigitWidth
|
|
text: clockText.minutes.length > 1 ? clockText.minutes[1] : ""
|
|
}
|
|
|
|
ClockDigitText {
|
|
text: clockText.hasSeconds ? ":" : ""
|
|
visible: clockText.hasSeconds
|
|
}
|
|
|
|
ClockDigitText {
|
|
width: LockMetrics.clockDigitWidth
|
|
text: clockText.hasSeconds && clockText.seconds.length > 0 ? clockText.seconds[0] : ""
|
|
visible: clockText.hasSeconds
|
|
}
|
|
|
|
ClockDigitText {
|
|
width: LockMetrics.clockDigitWidth
|
|
text: clockText.hasSeconds && clockText.seconds.length > 1 ? clockText.seconds[1] : ""
|
|
visible: clockText.hasSeconds
|
|
}
|
|
|
|
ClockDigitText {
|
|
width: Theme.iconSizeSmall
|
|
text: " "
|
|
visible: clockText.ampm !== ""
|
|
}
|
|
|
|
ClockDigitText {
|
|
text: clockText.ampm
|
|
visible: clockText.ampm !== ""
|
|
}
|
|
}
|
|
|
|
Column {
|
|
id: verticalClock
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.top: parent.top
|
|
spacing: -Theme.spacingS
|
|
visible: SettingsData.lockScreenClockStyle === "vertical"
|
|
|
|
Row {
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
spacing: 0
|
|
|
|
ClockDigitText {
|
|
width: clockText.hours.length > 1 ? LockMetrics.clockDigitWidth : 0
|
|
text: clockText.hours.length > 1 ? clockText.hours[0] : ""
|
|
}
|
|
ClockDigitText {
|
|
width: LockMetrics.clockDigitWidth
|
|
text: clockText.hours.length > 1 ? clockText.hours[1] : clockText.hours.length > 0 ? clockText.hours[0] : ""
|
|
}
|
|
}
|
|
|
|
Row {
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
spacing: 0
|
|
|
|
ClockDigitText {
|
|
width: LockMetrics.clockDigitWidth
|
|
text: clockText.minutes.length > 0 ? clockText.minutes[0] : ""
|
|
}
|
|
ClockDigitText {
|
|
width: LockMetrics.clockDigitWidth
|
|
text: clockText.minutes.length > 1 ? clockText.minutes[1] : ""
|
|
}
|
|
}
|
|
|
|
StyledText {
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
text: clockText.hasSeconds ? clockText.seconds + (clockText.ampm !== "" ? " " + clockText.ampm : "") : clockText.ampm
|
|
font.pixelSize: Theme.fontSizeDisplay
|
|
font.weight: Theme.fontWeight
|
|
font.family: root.lockFontFamily !== "" ? root.lockFontFamily : resolvedFontFamily
|
|
color: Theme.lockScreenContentColor
|
|
visible: clockText.hasSeconds || clockText.ampm !== ""
|
|
}
|
|
}
|
|
}
|
|
|
|
StyledText {
|
|
id: dateText
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.top: clockContainer.bottom
|
|
anchors.topMargin: Theme.spacingXS
|
|
visible: SettingsData.lockScreenShowDate
|
|
text: {
|
|
if (SettingsData.lockDateFormat && SettingsData.lockDateFormat.length > 0) {
|
|
return systemClock.date.toLocaleDateString(I18n.locale(), SettingsData.lockDateFormat);
|
|
}
|
|
return systemClock.date.toLocaleDateString(I18n.locale(), Locale.LongFormat);
|
|
}
|
|
font.pixelSize: Theme.fontSizeXLarge
|
|
font.family: root.lockFontFamily !== "" ? root.lockFontFamily : resolvedFontFamily
|
|
color: Theme.lockScreenContentColor
|
|
opacity: 1
|
|
}
|
|
|
|
Item {
|
|
id: lockNotificationPanel
|
|
|
|
readonly property int notificationMode: SettingsData.lockScreenNotificationMode
|
|
readonly property var notifications: NotificationService.groupedNotifications
|
|
readonly property int totalCount: {
|
|
let count = 0;
|
|
for (const group of notifications) {
|
|
count += group.count || 0;
|
|
}
|
|
return count;
|
|
}
|
|
readonly property bool hasNotifications: totalCount > 0
|
|
readonly property var appNameGroups: {
|
|
const groups = {};
|
|
for (const group of notifications) {
|
|
const appName = (group.appName || "Unknown").toLowerCase();
|
|
if (!groups[appName]) {
|
|
groups[appName] = {
|
|
appName: group.appName || I18n.tr("Unknown"),
|
|
count: 0,
|
|
latestNotification: group.latestNotification
|
|
};
|
|
}
|
|
groups[appName].count += group.count || 0;
|
|
if (group.latestNotification && (!groups[appName].latestNotification || group.latestNotification.time > groups[appName].latestNotification.time)) {
|
|
groups[appName].latestNotification = group.latestNotification;
|
|
}
|
|
}
|
|
return Object.values(groups).sort((a, b) => b.count - a.count);
|
|
}
|
|
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.top: capsLockRow.bottom
|
|
anchors.topMargin: Theme.spacingM
|
|
width: Math.min(LockMetrics.notificationCardWidth, parent.width - Theme.spacingXL * 2)
|
|
height: notificationMode === 0 || !hasNotifications ? 0 : contentLoader.height
|
|
visible: notificationMode > 0 && hasNotifications
|
|
clip: true
|
|
|
|
Loader {
|
|
id: contentLoader
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
active: lockNotificationPanel.notificationMode > 0 && lockNotificationPanel.hasNotifications
|
|
sourceComponent: lockNotificationPanel.notificationMode === 1 ? countOnlyComponent : notificationListComponent
|
|
}
|
|
|
|
Component {
|
|
id: countOnlyComponent
|
|
|
|
LockNotificationCard {
|
|
width: parent.width
|
|
height: Theme.listItemHeight
|
|
color: Theme.notificationFloatingSurface
|
|
Accessible.name: countLabel.text
|
|
|
|
Row {
|
|
anchors.centerIn: parent
|
|
spacing: Theme.spacingS
|
|
|
|
DankIcon {
|
|
name: "notifications"
|
|
size: Theme.iconSize
|
|
color: Theme.onSurfaceVariant
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
|
|
StyledText {
|
|
id: countLabel
|
|
text: lockNotificationPanel.totalCount === 1 ? I18n.tr("1 notification") : I18n.tr("%1 notifications").arg(lockNotificationPanel.totalCount)
|
|
font.pixelSize: Theme.fontSizeMedium
|
|
color: Theme.onSurface
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: notificationListComponent
|
|
|
|
DankFlickable {
|
|
width: parent.width
|
|
height: Math.min(notificationColumn.implicitHeight, LockMetrics.notificationMaxHeight, Math.max(0, root.height - lockNotificationPanel.y - Theme.spacingXL))
|
|
contentHeight: notificationColumn.implicitHeight
|
|
clip: true
|
|
|
|
Column {
|
|
id: notificationColumn
|
|
width: parent.width
|
|
spacing: Theme.groupedListGap
|
|
|
|
Repeater {
|
|
id: notificationRepeater
|
|
model: lockNotificationPanel.appNameGroups.slice(0, LockMetrics.notificationLimit)
|
|
|
|
Notifications.NotificationCard {
|
|
required property var modelData
|
|
required property int index
|
|
width: notificationColumn.width
|
|
notificationData: modelData.latestNotification
|
|
groupCount: modelData.count
|
|
interactive: false
|
|
headerOnly: lockNotificationPanel.notificationMode === 2
|
|
showActions: false
|
|
showDismiss: false
|
|
showClose: false
|
|
showTime: !headerOnly
|
|
animateHeight: false
|
|
firstInGroup: index === 0
|
|
lastInGroup: index === notificationRepeater.count - 1
|
|
}
|
|
}
|
|
|
|
StyledText {
|
|
width: parent.width
|
|
visible: lockNotificationPanel.appNameGroups.length > LockMetrics.notificationLimit
|
|
text: I18n.tr("+ %1 more", "lock screen notification list overflow, %1 is a count of hidden apps").arg(lockNotificationPanel.appNameGroups.length - LockMetrics.notificationLimit)
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: Theme.lockScreenContentColor
|
|
horizontalAlignment: Text.AlignHCenter
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ColumnLayout {
|
|
id: passwordLayout
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.top: dateText.visible ? dateText.bottom : clockContainer.bottom
|
|
anchors.topMargin: Theme.spacingL
|
|
spacing: Theme.spacingM
|
|
width: Math.min(LockMetrics.passwordRowWidth, parent.width - Theme.spacingXL * 2)
|
|
|
|
RowLayout {
|
|
LayoutMirroring.enabled: I18n.isRtl
|
|
LayoutMirroring.childrenInherit: true
|
|
spacing: Theme.spacingM
|
|
Layout.fillWidth: true
|
|
|
|
DankCircularImage {
|
|
Layout.preferredWidth: LockMetrics.avatarSize
|
|
ringWidth: Theme.avatarRingWidth
|
|
ringColor: Theme.avatarRingColor
|
|
Layout.preferredHeight: LockMetrics.fieldHeight
|
|
imageSource: {
|
|
if (PortalService.profileImage === "")
|
|
return "";
|
|
if (PortalService.profileImage.startsWith("/"))
|
|
return encodeFileUrl(PortalService.profileImage);
|
|
return PortalService.profileImage;
|
|
}
|
|
fallbackIcon: "material:person"
|
|
visible: SettingsData.lockScreenShowProfileImage
|
|
}
|
|
|
|
Rectangle {
|
|
id: passwordBox
|
|
|
|
property bool showPassword: false
|
|
property real errorOffset: 0
|
|
readonly property bool focusRingShown: passwordField.activeFocus && Theme.focusRingWidth > 0
|
|
transform: Translate {
|
|
x: Math.max(-LockMetrics.shakeDistance, Math.min(LockMetrics.shakeDistance, passwordBox.errorOffset))
|
|
}
|
|
|
|
Layout.fillWidth: true
|
|
Layout.preferredHeight: LockMetrics.fieldHeight
|
|
radius: Theme.fullRadius(width, height)
|
|
color: Theme.cardSurface
|
|
border.width: focusRingShown ? Math.max(Theme.outlineWidth, Theme.focusRingWidth) : Theme.layerOutlineWidth
|
|
border.color: focusRingShown ? Theme.focusRingColor : Theme.outlineMedium
|
|
Accessible.name: I18n.tr("Password")
|
|
visible: SettingsData.lockScreenShowPasswordField || root.passwordBuffer.length > 0
|
|
|
|
Item {
|
|
id: lockIconContainer
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: Theme.spacingM
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
width: Theme.iconSizeSmall
|
|
height: Theme.iconSizeSmall
|
|
|
|
DankIcon {
|
|
id: lockIcon
|
|
|
|
anchors.centerIn: parent
|
|
name: {
|
|
if (pam.u2fPending)
|
|
return "passkey";
|
|
switch (pam.fprint.status) {
|
|
case "max":
|
|
case "stopped":
|
|
return "fingerprint_off";
|
|
case "active":
|
|
return "fingerprint";
|
|
case "retrying":
|
|
return "hourglass_empty";
|
|
}
|
|
if (pam.u2f.active)
|
|
return "passkey";
|
|
return "lock";
|
|
}
|
|
size: Theme.iconSizeSmall
|
|
color: {
|
|
if (pam.fprint.tries >= SettingsData.maxFprintTries)
|
|
return Theme.error;
|
|
if (pam.u2fState !== "")
|
|
return Theme.tertiary;
|
|
return passwordField.activeFocus ? Theme.primary : Theme.surfaceVariantText;
|
|
}
|
|
opacity: pam.passwd.active ? 0 : 1
|
|
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
duration: LockMetrics.effectsDuration
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: Theme.expressiveCurves.expressiveEffects
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FocusScope {
|
|
id: passwordField
|
|
|
|
KeyNavigation.tab: virtualKeyboardButton.visible ? virtualKeyboardButton : powerButton
|
|
KeyNavigation.backtab: powerButton
|
|
Accessible.role: Accessible.EditableText
|
|
Accessible.name: I18n.tr("Password")
|
|
|
|
readonly property string text: root.passwordBuffer
|
|
property int cursorPosition: text.length
|
|
|
|
signal accepted
|
|
|
|
function clampCursorPosition() {
|
|
cursorPosition = Math.max(0, Math.min(cursorPosition, text.length));
|
|
}
|
|
|
|
function clear() {
|
|
root.passwordEdited("");
|
|
cursorPosition = 0;
|
|
}
|
|
|
|
function insertText(value) {
|
|
if (value.length === 0)
|
|
return;
|
|
clampCursorPosition();
|
|
const pos = cursorPosition;
|
|
root.passwordEdited(text.slice(0, pos) + value + text.slice(pos));
|
|
cursorPosition = pos + value.length;
|
|
}
|
|
|
|
function backspace() {
|
|
clampCursorPosition();
|
|
if (cursorPosition === 0)
|
|
return;
|
|
const pos = cursorPosition;
|
|
root.passwordEdited(text.slice(0, pos - 1) + text.slice(pos));
|
|
cursorPosition = pos - 1;
|
|
}
|
|
|
|
function deleteForward() {
|
|
clampCursorPosition();
|
|
if (cursorPosition === text.length)
|
|
return;
|
|
const pos = cursorPosition;
|
|
root.passwordEdited(text.slice(0, pos) + text.slice(pos + 1));
|
|
cursorPosition = pos;
|
|
}
|
|
|
|
function deleteToLineStart() {
|
|
clampCursorPosition();
|
|
if (cursorPosition === 0)
|
|
return;
|
|
root.passwordEdited(text.slice(cursorPosition));
|
|
cursorPosition = 0;
|
|
}
|
|
|
|
function deleteToLineEnd() {
|
|
clampCursorPosition();
|
|
if (cursorPosition === text.length)
|
|
return;
|
|
root.passwordEdited(text.slice(0, cursorPosition));
|
|
}
|
|
|
|
function deleteWordBackward() {
|
|
clampCursorPosition();
|
|
if (cursorPosition === 0)
|
|
return;
|
|
let pos = cursorPosition;
|
|
while (pos > 0 && text.charAt(pos - 1) === " ")
|
|
pos--;
|
|
while (pos > 0 && text.charAt(pos - 1) !== " ")
|
|
pos--;
|
|
root.passwordEdited(text.slice(0, pos) + text.slice(cursorPosition));
|
|
cursorPosition = pos;
|
|
}
|
|
|
|
function isPrintableText(value) {
|
|
if (value.length === 0)
|
|
return false;
|
|
const code = value.charCodeAt(0);
|
|
return code >= 0x20 && code !== 0x7f;
|
|
}
|
|
|
|
anchors.fill: parent
|
|
anchors.leftMargin: lockIconContainer.width + Theme.spacingM * 2
|
|
anchors.rightMargin: {
|
|
let margin = Theme.spacingM;
|
|
if (loadingSpinner.visible) {
|
|
margin += loadingSpinner.width;
|
|
}
|
|
if (enterButton.visible) {
|
|
margin += enterButton.width + Theme.spacingXXS;
|
|
}
|
|
if (securityKeyButton.visible) {
|
|
margin += securityKeyButton.width;
|
|
}
|
|
if (virtualKeyboardButton.visible) {
|
|
margin += virtualKeyboardButton.width;
|
|
}
|
|
if (revealButton.visible) {
|
|
margin += revealButton.width;
|
|
}
|
|
return margin;
|
|
}
|
|
opacity: 0
|
|
focus: true
|
|
enabled: !demoMode
|
|
activeFocusOnTab: !demoMode
|
|
onTextChanged: cursorPosition = text.length
|
|
onAccepted: {
|
|
if (!demoMode && !root.unlocking && !pam.passwd.active && !pam.u2fPending) {
|
|
pam.passwd.start();
|
|
}
|
|
}
|
|
Keys.onPressed: event => handleKey(event)
|
|
|
|
function handleKey(event) {
|
|
if (demoMode) {
|
|
return;
|
|
}
|
|
|
|
root.pam.retryFprintOnActivity();
|
|
|
|
if (root.unlocking) {
|
|
event.accepted = true;
|
|
return;
|
|
}
|
|
|
|
if (event.key === Qt.Key_Escape) {
|
|
if (keyboardController.isKeyboardActive) {
|
|
keyboardController.hide();
|
|
event.accepted = true;
|
|
return;
|
|
}
|
|
if (pam.u2fPending) {
|
|
pam.cancelU2fPending();
|
|
event.accepted = true;
|
|
return;
|
|
}
|
|
clear();
|
|
event.accepted = true;
|
|
return;
|
|
}
|
|
|
|
if (pam.passwd.active) {
|
|
log.debug("PAM is active, ignoring input");
|
|
event.accepted = true;
|
|
return;
|
|
}
|
|
|
|
if ((event.modifiers & Qt.ControlModifier) && !(event.modifiers & (Qt.AltModifier | Qt.MetaModifier))) {
|
|
if (securityKeyShortcutMatches(event) && canStartSecurityKeyUnlock()) {
|
|
triggerSecurityKeyUnlock();
|
|
event.accepted = true;
|
|
return;
|
|
}
|
|
|
|
switch (event.key) {
|
|
case Qt.Key_A:
|
|
cursorPosition = 0;
|
|
event.accepted = true;
|
|
return;
|
|
case Qt.Key_E:
|
|
cursorPosition = text.length;
|
|
event.accepted = true;
|
|
return;
|
|
case Qt.Key_B:
|
|
clampCursorPosition();
|
|
cursorPosition = Math.max(0, cursorPosition - 1);
|
|
event.accepted = true;
|
|
return;
|
|
case Qt.Key_F:
|
|
clampCursorPosition();
|
|
cursorPosition = Math.min(text.length, cursorPosition + 1);
|
|
event.accepted = true;
|
|
return;
|
|
case Qt.Key_U:
|
|
deleteToLineStart();
|
|
event.accepted = true;
|
|
return;
|
|
case Qt.Key_K:
|
|
deleteToLineEnd();
|
|
event.accepted = true;
|
|
return;
|
|
case Qt.Key_W:
|
|
case Qt.Key_Backspace:
|
|
deleteWordBackward();
|
|
event.accepted = true;
|
|
return;
|
|
case Qt.Key_H:
|
|
backspace();
|
|
event.accepted = true;
|
|
return;
|
|
case Qt.Key_D:
|
|
deleteForward();
|
|
event.accepted = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
switch (event.key) {
|
|
case Qt.Key_Return:
|
|
case Qt.Key_Enter:
|
|
accepted();
|
|
event.accepted = true;
|
|
return;
|
|
case Qt.Key_Backspace:
|
|
backspace();
|
|
event.accepted = true;
|
|
return;
|
|
case Qt.Key_Delete:
|
|
deleteForward();
|
|
event.accepted = true;
|
|
return;
|
|
case Qt.Key_Left:
|
|
clampCursorPosition();
|
|
cursorPosition = Math.max(0, cursorPosition - 1);
|
|
event.accepted = true;
|
|
return;
|
|
case Qt.Key_Right:
|
|
clampCursorPosition();
|
|
cursorPosition = Math.min(text.length, cursorPosition + 1);
|
|
event.accepted = true;
|
|
return;
|
|
case Qt.Key_Home:
|
|
cursorPosition = 0;
|
|
event.accepted = true;
|
|
return;
|
|
case Qt.Key_End:
|
|
cursorPosition = text.length;
|
|
event.accepted = true;
|
|
return;
|
|
}
|
|
|
|
if (isPrintableText(event.text)) {
|
|
insertText(event.text);
|
|
event.accepted = true;
|
|
}
|
|
}
|
|
|
|
// IME commits use a hidden password input: https://github.com/AvengeMedia/DankMaterialShell/issues/2950
|
|
TextInput {
|
|
id: imeCommitSink
|
|
|
|
focus: true
|
|
width: Theme.dividerWidth
|
|
height: 1
|
|
opacity: 0
|
|
cursorDelegate: Item {}
|
|
echoMode: TextInput.Password
|
|
inputMethodHints: Qt.ImhHiddenText | Qt.ImhSensitiveData | Qt.ImhNoPredictiveText | Qt.ImhNoAutoUppercase
|
|
KeyNavigation.tab: virtualKeyboardButton.visible ? virtualKeyboardButton : powerButton
|
|
KeyNavigation.backtab: powerButton
|
|
Keys.onPressed: event => {
|
|
passwordField.handleKey(event);
|
|
if (!event.accepted && (event.modifiers & (Qt.ControlModifier | Qt.AltModifier | Qt.MetaModifier)))
|
|
event.accepted = true;
|
|
}
|
|
onTextChanged: {
|
|
if (text.length === 0)
|
|
return;
|
|
const committed = text;
|
|
text = "";
|
|
if (demoMode || root.unlocking || pam.passwd.active)
|
|
return;
|
|
passwordField.insertText(committed);
|
|
}
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
if (!demoMode) {
|
|
forceActiveFocus();
|
|
}
|
|
}
|
|
|
|
onVisibleChanged: {
|
|
if (visible && !demoMode) {
|
|
forceActiveFocus();
|
|
}
|
|
}
|
|
}
|
|
|
|
KeyboardController {
|
|
id: keyboardController
|
|
target: passwordField
|
|
rootObject: root
|
|
expressive: true
|
|
}
|
|
|
|
StyledText {
|
|
id: placeholder
|
|
|
|
anchors.left: lockIconContainer.right
|
|
anchors.leftMargin: Theme.spacingM
|
|
anchors.right: (revealButton.visible ? revealButton.left : (virtualKeyboardButton.visible ? virtualKeyboardButton.left : (securityKeyButton.visible ? securityKeyButton.left : (enterButton.visible ? enterButton.left : (loadingSpinner.visible ? loadingSpinner.left : parent.right)))))
|
|
anchors.rightMargin: Theme.spacingXXS
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
text: {
|
|
if (demoMode) {
|
|
return "";
|
|
}
|
|
if (root.unlocking) {
|
|
return I18n.tr("Unlocking...", "lock screen status text while unlocking");
|
|
}
|
|
if (pam.u2fPending) {
|
|
if (pam.u2fState === "insert")
|
|
return I18n.tr("Insert your security key...");
|
|
return I18n.tr("Touch your security key...");
|
|
}
|
|
if (pam.passwd.active) {
|
|
return I18n.tr("Authenticating...", "lock screen status text while the password is checked");
|
|
}
|
|
return I18n.tr("Password", "lock screen password field placeholder") + "…";
|
|
}
|
|
color: root.unlocking ? Theme.primary : (pam.passwd.active ? Theme.primary : Theme.outline)
|
|
font.pixelSize: Theme.fontSizeMedium
|
|
opacity: (demoMode || root.passwordBuffer.length === 0) ? 1 : 0
|
|
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
duration: LockMetrics.effectsDuration
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: Theme.expressiveCurves.expressiveEffects
|
|
}
|
|
}
|
|
|
|
Behavior on color {
|
|
ColorAnimation {
|
|
duration: LockMetrics.effectsDuration
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: Theme.expressiveCurves.expressiveEffects
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
id: passwordViewport
|
|
|
|
property real scrollX: 0
|
|
property real scrollY: 0
|
|
|
|
function followCursor() {
|
|
const rect = passwordDisplay.cursorRectangle;
|
|
const contentWidth = passwordDisplay.contentWidth + passwordCursor.width;
|
|
const contentHeight = passwordDisplay.contentHeight;
|
|
scrollX = followAxis(scrollX, rect.x, rect.x + passwordCursor.width, contentWidth, width);
|
|
scrollY = contentHeight < height ? (height - contentHeight) / 2 : followAxis(scrollY, rect.y, rect.y + rect.height, contentHeight, height);
|
|
}
|
|
|
|
function followAxis(offset, start, end, contentSize, viewSize) {
|
|
if (contentSize <= viewSize)
|
|
return 0;
|
|
if (start + offset < 0)
|
|
return -start;
|
|
if (end + offset > viewSize)
|
|
return viewSize - end;
|
|
return Math.max(viewSize - contentSize, Math.min(0, offset));
|
|
}
|
|
|
|
anchors.left: lockIconContainer.right
|
|
anchors.leftMargin: Theme.spacingM
|
|
anchors.right: (revealButton.visible ? revealButton.left : (virtualKeyboardButton.visible ? virtualKeyboardButton.left : (securityKeyButton.visible ? securityKeyButton.left : (enterButton.visible ? enterButton.left : (loadingSpinner.visible ? loadingSpinner.left : parent.right)))))
|
|
anchors.rightMargin: Theme.spacingXXS
|
|
anchors.top: parent.top
|
|
anchors.bottom: parent.bottom
|
|
anchors.topMargin: Theme.spacingXS
|
|
anchors.bottomMargin: Theme.spacingXS
|
|
clip: true
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
enabled: !root.demoMode
|
|
onClicked: passwordField.forceActiveFocus()
|
|
}
|
|
|
|
onWidthChanged: followCursor()
|
|
onHeightChanged: followCursor()
|
|
|
|
TextEdit {
|
|
id: passwordDisplay
|
|
LayoutMirroring.enabled: false
|
|
|
|
x: passwordViewport.scrollX
|
|
y: passwordViewport.scrollY
|
|
width: wrapMode === TextEdit.NoWrap ? implicitWidth : passwordViewport.width
|
|
readOnly: true
|
|
activeFocusOnPress: false
|
|
selectByMouse: false
|
|
wrapMode: passwordBox.showPassword ? TextEdit.Wrap : TextEdit.NoWrap
|
|
text: {
|
|
if (demoMode) {
|
|
return "••••••••";
|
|
}
|
|
if (passwordBox.showPassword) {
|
|
return root.passwordBuffer;
|
|
}
|
|
return "•".repeat(root.passwordBuffer.length);
|
|
}
|
|
color: Theme.surfaceText
|
|
font.family: Theme.fontFamily
|
|
font.weight: Theme.fontWeight
|
|
font.pixelSize: passwordBox.showPassword ? Theme.fontSizeMedium : Theme.fontSizeLarge
|
|
opacity: (demoMode || root.passwordBuffer.length > 0) ? 1 : 0
|
|
|
|
onTextChanged: cursorPosition = passwordField.cursorPosition
|
|
onCursorRectangleChanged: passwordViewport.followCursor()
|
|
onContentWidthChanged: passwordViewport.followCursor()
|
|
onContentHeightChanged: passwordViewport.followCursor()
|
|
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
duration: LockMetrics.effectsDuration
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: Theme.expressiveCurves.expressiveEffects
|
|
}
|
|
}
|
|
}
|
|
|
|
DankTextCursor {
|
|
id: passwordCursor
|
|
|
|
x: passwordDisplay.x + passwordDisplay.cursorRectangle.x
|
|
y: passwordDisplay.y + passwordDisplay.cursorRectangle.y
|
|
height: passwordDisplay.cursorRectangle.height
|
|
shown: !demoMode && passwordField.activeFocus && !pam.passwd.active && !pam.u2fPending && !root.unlocking
|
|
|
|
readonly property int fieldCursorPosition: passwordField.cursorPosition
|
|
readonly property string fieldText: passwordField.text
|
|
|
|
onFieldCursorPositionChanged: {
|
|
passwordDisplay.cursorPosition = fieldCursorPosition;
|
|
resetBlink();
|
|
}
|
|
|
|
onFieldTextChanged: resetBlink()
|
|
}
|
|
}
|
|
|
|
LockActionButton {
|
|
id: revealButton
|
|
|
|
activeFocusOnTab: false
|
|
Accessible.name: parent.showPassword ? I18n.tr("Hide password") : I18n.tr("Show password")
|
|
|
|
anchors.right: virtualKeyboardButton.visible ? virtualKeyboardButton.left : (securityKeyButton.visible ? securityKeyButton.left : (enterButton.visible ? enterButton.left : (loadingSpinner.visible ? loadingSpinner.left : parent.right)))
|
|
anchors.rightMargin: 0
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
iconName: parent.showPassword ? "visibility_off" : "visibility"
|
|
buttonSize: Theme.buttonHeightXS
|
|
visible: !demoMode && root.passwordBuffer.length > 0 && !pam.passwd.active && !root.unlocking
|
|
enabled: visible
|
|
onClicked: parent.showPassword = !parent.showPassword
|
|
}
|
|
LockActionButton {
|
|
id: securityKeyButton
|
|
|
|
activeFocusOnTab: false
|
|
|
|
anchors.right: enterButton.visible ? enterButton.left : (loadingSpinner.visible ? loadingSpinner.left : parent.right)
|
|
anchors.rightMargin: 0
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
iconName: "passkey"
|
|
buttonSize: Theme.buttonHeightXS
|
|
visible: root.canStartSecurityKeyUnlock()
|
|
enabled: visible
|
|
tooltipText: SettingsData.lockScreenSecurityKeyShortcutEnabled ? I18n.tr("Security key (%1)", "lock screen security key button tooltip with shortcut").arg(SettingsData.lockScreenSecurityKeyShortcut) : I18n.tr("Security key", "lock screen security key button tooltip")
|
|
onClicked: root.triggerSecurityKeyUnlock()
|
|
}
|
|
LockActionButton {
|
|
id: virtualKeyboardButton
|
|
Keys.onEscapePressed: {
|
|
keyboardController.hide();
|
|
passwordField.forceActiveFocus();
|
|
}
|
|
|
|
KeyNavigation.tab: powerButton
|
|
KeyNavigation.backtab: passwordField
|
|
Accessible.name: I18n.tr("On-screen keyboard")
|
|
|
|
anchors.right: securityKeyButton.visible ? securityKeyButton.left : (enterButton.visible ? enterButton.left : (loadingSpinner.visible ? loadingSpinner.left : parent.right))
|
|
anchors.rightMargin: securityKeyButton.visible || enterButton.visible ? 0 : Theme.spacingS
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
iconName: "keyboard"
|
|
buttonSize: Theme.buttonHeightXS
|
|
visible: !demoMode && !pam.passwd.active && !root.unlocking && !pam.u2fPending
|
|
enabled: visible
|
|
onClicked: {
|
|
if (keyboardController.isKeyboardActive) {
|
|
keyboardController.hide();
|
|
} else {
|
|
keyboardController.show();
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: loadingSpinner
|
|
|
|
anchors.right: enterButton.visible ? enterButton.left : parent.right
|
|
anchors.rightMargin: Theme.spacingM
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
width: Theme.iconSize
|
|
height: Theme.iconSize
|
|
radius: Theme.fullRadius(width, height)
|
|
color: "transparent"
|
|
visible: !demoMode && (pam.passwd.active || root.unlocking)
|
|
|
|
DankIcon {
|
|
anchors.centerIn: parent
|
|
name: "check_circle"
|
|
size: Theme.iconSizeSmall
|
|
color: Theme.primary
|
|
visible: root.unlocking
|
|
|
|
opacity: root.unlocking ? 1 : 0
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
duration: LockMetrics.effectsDuration
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: Theme.expressiveCurves.expressiveEffects
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
anchors.fill: parent
|
|
visible: pam.passwd.active && !root.unlocking
|
|
|
|
Rectangle {
|
|
width: Theme.iconSizeSmall
|
|
height: Theme.iconSizeSmall
|
|
radius: Theme.fullRadius(width, height)
|
|
anchors.centerIn: parent
|
|
color: "transparent"
|
|
border.color: Theme.primarySelected
|
|
border.width: Theme.outlineWidthFocused
|
|
}
|
|
|
|
Rectangle {
|
|
width: Theme.iconSizeSmall
|
|
height: Theme.iconSizeSmall
|
|
radius: Theme.fullRadius(width, height)
|
|
anchors.centerIn: parent
|
|
color: "transparent"
|
|
border.color: Theme.primary
|
|
border.width: Theme.outlineWidthFocused
|
|
|
|
Rectangle {
|
|
width: parent.width
|
|
height: parent.height / 2
|
|
anchors.top: parent.top
|
|
anchors.topMargin: -Theme.outlineWidth
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
color: Theme.cardSurface
|
|
}
|
|
|
|
RotationAnimator on rotation {
|
|
running: pam.passwd.active && !root.unlocking
|
|
loops: Animation.Infinite
|
|
duration: Anims.durLong
|
|
from: 0
|
|
to: 360
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
LockActionButton {
|
|
id: enterButton
|
|
|
|
activeFocusOnTab: false
|
|
Accessible.name: I18n.tr("Unlock", "verb, lock screen submit button")
|
|
|
|
anchors.right: parent.right
|
|
anchors.rightMargin: Theme.spacingXXS
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
iconName: "keyboard_return"
|
|
buttonSize: Theme.buttonHeightXS
|
|
visible: (demoMode || (!pam.passwd.active && !root.unlocking && !pam.u2fPending))
|
|
enabled: !demoMode
|
|
onClicked: {
|
|
if (!demoMode && !root.unlocking && !pam.u2fPending) {
|
|
pam.passwd.start();
|
|
}
|
|
}
|
|
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
duration: LockMetrics.effectsDuration
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: Theme.expressiveCurves.expressiveEffects
|
|
}
|
|
}
|
|
}
|
|
|
|
Behavior on border.color {
|
|
ColorAnimation {
|
|
duration: LockMetrics.effectsDuration
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: Theme.expressiveCurves.expressiveEffects
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
StyledText {
|
|
id: authFeedbackText
|
|
|
|
Layout.fillWidth: true
|
|
Layout.preferredHeight: text.length > 0 ? Math.min(implicitHeight, Math.ceil(Theme.fontSizeSmall * 4.5)) : 0
|
|
text: root.currentAuthFeedbackText()
|
|
color: root.authFeedbackIsHint() ? Theme.outline : Theme.error
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
horizontalAlignment: Text.AlignHCenter
|
|
wrapMode: Text.WordWrap
|
|
maximumLineCount: 3
|
|
elide: Text.ElideRight
|
|
opacity: text.length > 0 ? 1 : 0
|
|
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
duration: LockMetrics.effectsDuration
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: Theme.expressiveCurves.expressiveEffects
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Row {
|
|
id: capsLockRow
|
|
anchors.top: passwordLayout.bottom
|
|
anchors.topMargin: Theme.spacingS
|
|
anchors.horizontalCenter: passwordLayout.horizontalCenter
|
|
spacing: Theme.spacingXS
|
|
opacity: DMSService.capsLockState ? 1 : 0
|
|
|
|
DankIcon {
|
|
name: "shift_lock"
|
|
size: Theme.iconSizeSmall
|
|
color: Theme.error
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
|
|
StyledText {
|
|
text: I18n.tr("Caps Lock is on")
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: Theme.error
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
duration: LockMetrics.effectsDuration
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: Theme.expressiveCurves.expressiveEffects
|
|
}
|
|
}
|
|
}
|
|
|
|
StyledText {
|
|
anchors.top: parent.top
|
|
anchors.left: parent.left
|
|
anchors.margins: Theme.spacingXL
|
|
text: I18n.tr("DEMO MODE - Click anywhere to exit")
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: Theme.lockScreenContentColor
|
|
opacity: Theme.pendingOpacity
|
|
visible: demoMode
|
|
}
|
|
|
|
LockStatusRow {
|
|
anchors.top: parent.top
|
|
anchors.right: parent.right
|
|
anchors.margins: Theme.spacingXL
|
|
visible: SettingsData.lockScreenShowSystemIcons
|
|
interactive: !root.demoMode
|
|
showMediaPlayer: SettingsData.lockScreenShowMediaPlayer
|
|
showWeather: SettingsData.lockScreenShowWeather
|
|
useFahrenheit: SettingsData.useFahrenheit
|
|
}
|
|
|
|
LockActionButton {
|
|
id: powerButton
|
|
KeyNavigation.tab: passwordField
|
|
KeyNavigation.backtab: virtualKeyboardButton.visible ? virtualKeyboardButton : passwordField
|
|
Accessible.name: I18n.tr("Power Options")
|
|
anchors.bottom: parent.bottom
|
|
anchors.left: parent.left
|
|
anchors.margins: Theme.spacingXL
|
|
visible: SettingsData.lockScreenShowPowerActions
|
|
iconName: "power_settings_new"
|
|
iconColor: Theme.onSecondaryContainer
|
|
backgroundColor: Theme.secondaryContainer
|
|
radius: pressed ? Theme.cornerRadiusS : Theme.fullRadius(width, height)
|
|
buttonSize: Theme.buttonHeightM
|
|
onClicked: {
|
|
if (demoMode) {
|
|
log.debug("Demo: Power Menu");
|
|
} else {
|
|
powerMenu.show();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Pam {
|
|
id: demoPam
|
|
lockSecured: false
|
|
}
|
|
|
|
Connections {
|
|
target: root.pam
|
|
|
|
function onUnlockRequested() {
|
|
root.unlocking = true;
|
|
lockerReadyArmed = false;
|
|
passwordField.clear();
|
|
root.unlockRequested();
|
|
}
|
|
|
|
function onStateChanged() {
|
|
root.pamState = root.pam.state;
|
|
if (root.pam.state === "")
|
|
return;
|
|
root.unlocking = false;
|
|
errorShake.restart();
|
|
placeholderDelay.restart();
|
|
passwordField.clear();
|
|
}
|
|
|
|
function onU2fPendingChanged() {
|
|
if (!root.pam.u2fPending)
|
|
return;
|
|
passwordField.clear();
|
|
if (keyboardController.isKeyboardActive)
|
|
keyboardController.hide();
|
|
}
|
|
|
|
function onUnlockInProgressChanged() {
|
|
if (!root.pam.unlockInProgress && root.unlocking)
|
|
root.unlocking = false;
|
|
}
|
|
}
|
|
|
|
SequentialAnimation {
|
|
id: errorShake
|
|
NumberAnimation {
|
|
target: passwordBox
|
|
property: "errorOffset"
|
|
to: LockMetrics.shakeDistance
|
|
duration: LockMetrics.shakeDuration / 3
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: Theme.expressiveCurves.expressiveFastSpatial
|
|
}
|
|
NumberAnimation {
|
|
target: passwordBox
|
|
property: "errorOffset"
|
|
to: -LockMetrics.shakeDistance
|
|
duration: LockMetrics.shakeDuration / 3
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: Theme.expressiveCurves.expressiveFastSpatial
|
|
}
|
|
NumberAnimation {
|
|
target: passwordBox
|
|
property: "errorOffset"
|
|
to: 0
|
|
duration: LockMetrics.shakeDuration / 3
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: Theme.expressiveCurves.expressiveFastSpatial
|
|
}
|
|
}
|
|
|
|
Timer {
|
|
id: placeholderDelay
|
|
|
|
interval: 4000
|
|
onTriggered: root.pamState = ""
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
enabled: demoMode
|
|
onClicked: root.unlockRequested()
|
|
}
|
|
|
|
LockPowerMenu {
|
|
id: powerMenu
|
|
expressive: true
|
|
showLogout: true
|
|
onClosed: {
|
|
if (!demoMode && passwordField && passwordField.forceActiveFocus) {
|
|
Qt.callLater(() => passwordField.forceActiveFocus());
|
|
}
|
|
}
|
|
onSwitchUserRequested: {
|
|
switchUserPicker.showFromLockScreen();
|
|
}
|
|
}
|
|
|
|
SwitchUserModal {
|
|
id: switchUserPicker
|
|
}
|
|
}
|