import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import qs.Commons import qs.Services.Compositor import qs.Services.Hardware import qs.Services.Keyboard import qs.Services.Location import qs.Services.Media import qs.Widgets import qs.Widgets.AudioSpectrum Item { id: root anchors.fill: parent required property var lockControl required property var batteryIndicator required property var keyboardLayout required property TextInput passwordInput // Whether to enable lock screen animations (smooth cursor blink). // Defaults to false to reduce GPU usage. Set Settings.data.general.lockScreenAnimations = true to restore. readonly property bool animationsEnabled: Settings.data.general.lockScreenAnimations || false Component.onCompleted: { if (Settings.data.general.autoStartAuth) { doUnlock(); } } function doUnlock() { if (lockControl) { lockControl.tryUnlock(); } } // Timer properties readonly property int timerDuration: Settings.data.general.lockScreenCountdownDuration property string pendingAction: "" property bool timerActive: false property int timeRemaining: 0 readonly property bool weatherReady: Settings.data.location.weatherEnabled && (LocationService.data.weather !== null) // Timer management functions function startTimer(action) { // Check if global countdown is disabled if (!Settings.data.general.enableLockScreenCountdown) { executeAction(action); return; } if (timerActive && pendingAction === action) { // Second click - execute immediately executeAction(action); return; } pendingAction = action; timeRemaining = timerDuration; timerActive = true; countdownTimer.start(); } function cancelTimer() { timerActive = false; pendingAction = ""; timeRemaining = 0; countdownTimer.stop(); } function executeAction(action) { // Stop timer but don't reset other properties yet countdownTimer.stop(); // Execute the action switch (action) { case "logout": CompositorService.logout(); break; case "suspend": CompositorService.suspend(); break; case "hibernate": CompositorService.hibernate(); break; case "reboot": CompositorService.reboot(); break; case "userspaceReboot": CompositorService.userspaceReboot(); break; case "shutdown": CompositorService.shutdown(); break; } // Reset timer state cancelTimer(); } // Countdown timer Timer { id: countdownTimer interval: 100 repeat: true onTriggered: { timeRemaining -= interval; if (timeRemaining <= 0) { executeAction(pendingAction); } } } // Compact status indicators container (compact mode only) Rectangle { width: { var hasBattery = batteryIndicator.isReady; var hasKeyboard = keyboardLayout.currentLayout !== "Unknown"; var hasCaps = LockKeysService.capsLockOn; var hasCapsSlot = hasBattery || hasKeyboard || hasCaps; var visibleCount = 0; if (hasBattery) visibleCount++; if (hasKeyboard) visibleCount++; if (hasCapsSlot) visibleCount++; if (visibleCount >= 3) { return 280; } else if (visibleCount === 2) { return 200; } else if (visibleCount === 1) { return 120; } else { return 0; } } height: 40 anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom anchors.bottomMargin: 96 + (Settings.data.general.compactLockScreen ? 116 : 220) topLeftRadius: Style.radiusL topRightRadius: Style.radiusL color: Color.mSurface visible: Settings.data.general.compactLockScreen && (batteryIndicator.isReady || keyboardLayout.currentLayout !== "Unknown" || LockKeysService.capsLockOn) RowLayout { id: compactStatusRow anchors.centerIn: parent spacing: Style.marginL // Battery indicator RowLayout { spacing: Style.marginS visible: batteryIndicator.isReady NIcon { icon: batteryIndicator.icon pointSize: Style.fontSizeM color: batteryIndicator.charging ? Color.mPrimary : Color.mOnSurfaceVariant } NText { text: Math.round(batteryIndicator.percent) + "%" color: Color.mOnSurfaceVariant pointSize: Style.fontSizeM } } // Keyboard layout indicator RowLayout { spacing: 6 visible: keyboardLayout.currentLayout !== "Unknown" NIcon { icon: "keyboard" pointSize: Style.fontSizeM color: Color.mOnSurfaceVariant } NText { text: keyboardLayout.currentLayout color: Color.mOnSurfaceVariant pointSize: Style.fontSizeM elide: Text.ElideRight } } // Caps Lock indicator RowLayout { spacing: 6 visible: batteryIndicator.isReady || keyboardLayout.currentLayout !== "Unknown" || LockKeysService.capsLockOn NIcon { icon: "lock" pointSize: Style.fontSizeM color: LockKeysService.capsLockOn ? Color.mPrimary : Qt.alpha(Color.mOnSurfaceVariant, 0.5) } NText { text: I18n.tr("bar.lock-keys.show-caps-lock-label") color: LockKeysService.capsLockOn ? Color.mOnSurfaceVariant : Qt.alpha(Color.mOnSurfaceVariant, 0.65) pointSize: Style.fontSizeM elide: Text.ElideRight } } } } // Bottom container with weather, password input and controls Rectangle { id: bottomContainer // Support for removing the session/power buttons at the bottom. readonly property int deltaY: Settings.data.general.showSessionButtonsOnLockScreen ? 0 : (Settings.data.general.compactLockScreen ? 36 : 48) + 14 height: { let calcHeight = Settings.data.general.compactLockScreen ? 120 : 220; if (!Settings.data.general.showSessionButtonsOnLockScreen) { calcHeight -= bottomContainer.deltaY; } return calcHeight; } anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom anchors.bottomMargin: 100 + bottomContainer.deltaY radius: Style.radiusL color: Color.mSurface width: Settings.data.general.showHibernateOnLockScreen ? 860 : 810 ColumnLayout { anchors.fill: parent anchors.margins: 14 spacing: Style.marginL // Top info row RowLayout { Layout.fillWidth: true Layout.preferredHeight: 65 spacing: Style.marginXL visible: !Settings.data.general.compactLockScreen // Media widget with visualizer Item { Layout.preferredWidth: Style.marginM visible: MediaService.currentPlayer && MediaService.canPlay } Rectangle { Layout.preferredWidth: 220 // Expand to take remaining space when weather is hidden Layout.fillWidth: !(Settings.data.location.weatherEnabled && LocationService.data.weather !== null) Layout.preferredHeight: 50 radius: Style.radiusL color: "transparent" clip: true visible: MediaService.currentPlayer && MediaService.canPlay Loader { anchors.fill: parent anchors.margins: 4 active: Settings.data.audio.visualizerType === "linear" z: 0 sourceComponent: NLinearSpectrum { anchors.fill: parent values: SpectrumService.values fillColor: Color.mPrimary opacity: 0.4 mirrored: Settings.data.audio.spectrumMirrored } } Loader { anchors.fill: parent anchors.margins: 4 active: Settings.data.audio.visualizerType === "mirrored" z: 0 sourceComponent: NMirroredSpectrum { anchors.fill: parent values: SpectrumService.values fillColor: Color.mPrimary opacity: 0.4 mirrored: Settings.data.audio.spectrumMirrored } } Loader { anchors.fill: parent anchors.margins: 4 active: Settings.data.audio.visualizerType === "wave" z: 0 sourceComponent: NWaveSpectrum { anchors.fill: parent values: SpectrumService.values fillColor: Color.mPrimary opacity: 0.4 mirrored: Settings.data.audio.spectrumMirrored } } RowLayout { anchors.fill: parent anchors.margins: 8 spacing: Style.marginM z: 1 Rectangle { Layout.preferredWidth: 34 Layout.preferredHeight: 34 radius: Math.min(Style.radiusL, width / 2) color: "transparent" clip: true NImageRounded { anchors.fill: parent anchors.margins: 2 radius: Math.min(Style.radiusL, width / 2) imagePath: MediaService.trackArtUrl fallbackIcon: "disc" fallbackIconSize: Style.fontSizeM borderColor: Color.mOutline borderWidth: Style.borderS } } ColumnLayout { Layout.fillWidth: true spacing: Style.marginXXS NText { text: MediaService.trackTitle || "No media" pointSize: Style.fontSizeM color: Color.mOnSurface Layout.fillWidth: true elide: Text.ElideRight } NText { text: MediaService.trackArtist || "" pointSize: Style.fontSizeM color: Color.mOnSurfaceVariant Layout.fillWidth: true elide: Text.ElideRight } } // Media controls (when enabled) RowLayout { spacing: Style.marginXS visible: Settings.data.general.enableLockScreenMediaControls Layout.alignment: Qt.AlignHCenter Rectangle { width: 28 height: 28 radius: Math.min(Style.radiusL, width / 2) color: prevButtonArea.containsMouse ? Color.mPrimary : Qt.alpha(Color.mOnSurface, 0.1) visible: MediaService.canGoPrevious NIcon { anchors.centerIn: parent icon: "media-prev" pointSize: Style.fontSizeM color: prevButtonArea.containsMouse ? Color.mOnPrimary : Color.mOnSurface Behavior on color { ColorAnimation { duration: Style.animationFast easing.type: Easing.OutCubic } } } MouseArea { id: prevButtonArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: MediaService.canGoPrevious ? MediaService.previous() : {} } Behavior on color { ColorAnimation { duration: Style.animationFast easing.type: Easing.OutCubic } } } Rectangle { width: 32 height: 32 radius: Math.min(Style.radiusL, width / 2) color: playPauseButtonArea.containsMouse ? Color.mPrimary : Qt.alpha(Color.mOnSurface, 0.15) visible: MediaService.canPlay || MediaService.canPause NIcon { anchors.centerIn: parent icon: MediaService.isPlaying ? "media-pause" : "media-play" pointSize: Style.fontSizeL color: playPauseButtonArea.containsMouse ? Color.mOnPrimary : Color.mOnSurface Behavior on color { ColorAnimation { duration: Style.animationFast easing.type: Easing.OutCubic } } } MouseArea { id: playPauseButtonArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: (MediaService.canPlay || MediaService.canPause) ? MediaService.playPause() : {} } Behavior on color { ColorAnimation { duration: Style.animationFast easing.type: Easing.OutCubic } } } Rectangle { width: 28 height: 28 radius: Math.min(Style.radiusL, width / 2) color: nextButtonArea.containsMouse ? Color.mPrimary : Qt.alpha(Color.mOnSurface, 0.1) visible: MediaService.canGoNext NIcon { anchors.centerIn: parent icon: "media-next" pointSize: Style.fontSizeM color: nextButtonArea.containsMouse ? Color.mOnPrimary : Color.mOnSurface Behavior on color { ColorAnimation { duration: Style.animationFast easing.type: Easing.OutCubic } } } MouseArea { id: nextButtonArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: MediaService.canGoNext ? MediaService.next() : {} } Behavior on color { ColorAnimation { duration: Style.animationFast easing.type: Easing.OutCubic } } } } } } Rectangle { Layout.preferredWidth: 1 Layout.fillHeight: true Layout.rightMargin: 4 color: Qt.alpha(Color.mOutline, 0.3) visible: MediaService.currentPlayer && MediaService.canPlay } Item { Layout.preferredWidth: Style.marginM visible: !(MediaService.currentPlayer && MediaService.canPlay) } // Current weather RowLayout { visible: Settings.data.location.weatherEnabled && LocationService.data.weather !== null Layout.preferredWidth: 180 spacing: Style.marginM Item { Layout.preferredWidth: lockMainWeatherIconSide Layout.preferredHeight: lockMainWeatherIconSide Layout.alignment: Qt.AlignVCenter readonly property int lockMainWeatherIconSide: Math.round(Style.fontSizeXXXL * Style.uiScaleRatio * 1.6) NIcon { visible: !LocationService.taliaWeatherMascotDayActive || !weatherReady anchors.centerIn: parent icon: weatherReady ? LocationService.weatherSymbolFromCode(LocationService.data.weather.current_weather.weathercode, LocationService.data.weather.current_weather.is_day) : "weather-cloud-off" pointSize: Style.fontSizeXXXL color: Color.mPrimary } Loader { active: LocationService.taliaWeatherMascotDayActive && weatherReady anchors.fill: parent asynchronous: true sourceComponent: Component { Image { anchors.fill: parent fillMode: Image.PreserveAspectFit smooth: true mipmap: true asynchronous: true source: Qt.resolvedUrl(LocationService.taliaWeatherImageFromCode(LocationService.data.weather.current_weather.weathercode, LocationService.data.weather.current_weather.is_day)) } } } } ColumnLayout { Layout.fillWidth: true spacing: Style.marginXXS RowLayout { Layout.fillWidth: true spacing: Style.marginL NText { text: { var temp = LocationService.data.weather.current_weather.temperature; var suffix = "C"; if (Settings.data.location.useFahrenheit) { temp = LocationService.celsiusToFahrenheit(temp); suffix = "F"; } temp = Math.round(temp); return temp + "°" + suffix; } pointSize: Style.fontSizeXL font.weight: Style.fontWeightBold color: Color.mOnSurface } NText { text: { var wind = LocationService.data.weather.current_weather.windspeed; var unit = "km/h"; if (Settings.data.location.useFahrenheit) { wind = wind * 0.621371; // Convert km/h to mph unit = "mph"; } wind = Math.round(wind); return wind + " " + unit; } pointSize: Style.fontSizeM color: Color.mOnSurfaceVariant } } RowLayout { Layout.fillWidth: true spacing: Style.marginM NText { text: Settings.data.location.name.split(",")[0] pointSize: Style.fontSizeM color: Color.mOnSurfaceVariant visible: !Settings.data.location.hideWeatherCityName } NText { text: (LocationService.data.weather.current && LocationService.data.weather.current.relativehumidity_2m) ? LocationService.data.weather.current.relativehumidity_2m + "% humidity" : "" pointSize: Style.fontSizeM color: Color.mOnSurfaceVariant } } } } // Forecast RowLayout { visible: Settings.data.location.weatherEnabled && LocationService.data.weather !== null Layout.preferredWidth: 260 Layout.rightMargin: 8 spacing: Style.marginXS Repeater { model: MediaService.currentPlayer && MediaService.canPlay ? 2 : 4 delegate: ColumnLayout { Layout.fillWidth: true spacing: Style.marginXXS + 1 NText { text: { var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/")); return I18n.locale.toString(weatherDate, "ddd"); } pointSize: Style.fontSizeM color: Color.mOnSurfaceVariant horizontalAlignment: Text.AlignHCenter Layout.fillWidth: true } Item { Layout.preferredWidth: lockForecastWeatherIconSide Layout.preferredHeight: lockForecastWeatherIconSide Layout.alignment: Qt.AlignHCenter readonly property int lockForecastWeatherIconSide: Math.round(Style.fontSizeXL * Style.uiScaleRatio * 1.6) NIcon { visible: !LocationService.taliaWeatherMascotDayActive anchors.centerIn: parent icon: LocationService.weatherSymbolFromCode(LocationService.data.weather.daily.weathercode[index]) pointSize: Style.fontSizeXL color: Color.mOnSurfaceVariant } Loader { active: LocationService.taliaWeatherMascotDayActive anchors.fill: parent asynchronous: true sourceComponent: Component { Image { anchors.fill: parent fillMode: Image.PreserveAspectFit smooth: true mipmap: true asynchronous: true source: Qt.resolvedUrl(LocationService.taliaWeatherImageFromCode(LocationService.data.weather.daily.weathercode[index])) } } } } NText { text: { var max = LocationService.data.weather.daily.temperature_2m_max[index]; var min = LocationService.data.weather.daily.temperature_2m_min[index]; if (Settings.data.location.useFahrenheit) { max = LocationService.celsiusToFahrenheit(max); min = LocationService.celsiusToFahrenheit(min); } max = Math.round(max); min = Math.round(min); return max + "°/" + min + "°"; } pointSize: Style.fontSizeM font.weight: Style.fontWeightMedium color: Color.mOnSurfaceVariant horizontalAlignment: Text.AlignHCenter Layout.fillWidth: true } } } } Item { Layout.fillWidth: batteryIndicator.isReady } // Battery and Keyboard Layout (full mode only) ColumnLayout { Layout.alignment: (batteryIndicator.isReady) ? (Qt.AlignRight | Qt.AlignVCenter) : Qt.AlignVCenter spacing: Style.marginM visible: batteryIndicator.isReady || keyboardLayout.currentLayout !== "Unknown" || LockKeysService.capsLockOn // Battery RowLayout { spacing: Style.marginXS visible: batteryIndicator.isReady NIcon { icon: batteryIndicator.icon pointSize: Style.fontSizeM color: batteryIndicator.charging ? Color.mPrimary : Color.mOnSurfaceVariant } NText { text: Math.round(batteryIndicator.percent) + "%" color: Color.mOnSurfaceVariant pointSize: Style.fontSizeM } } // Keyboard Layout RowLayout { spacing: Style.marginXS visible: keyboardLayout.currentLayout !== "Unknown" NIcon { icon: "keyboard" pointSize: Style.fontSizeM color: Color.mOnSurfaceVariant } NText { text: keyboardLayout.currentLayout color: Color.mOnSurfaceVariant pointSize: Style.fontSizeM elide: Text.ElideRight } } // Caps Lock RowLayout { spacing: Style.marginXS visible: batteryIndicator.isReady || keyboardLayout.currentLayout !== "Unknown" || LockKeysService.capsLockOn NIcon { icon: "lock" pointSize: Style.fontSizeM color: LockKeysService.capsLockOn ? Color.mPrimary : Qt.alpha(Color.mOnSurfaceVariant, 0.5) } NText { text: I18n.tr("bar.lock-keys.show-caps-lock-label") color: LockKeysService.capsLockOn ? Color.mOnSurfaceVariant : Qt.alpha(Color.mOnSurfaceVariant, 0.65) pointSize: Style.fontSizeM elide: Text.ElideRight } } } Item { Layout.preferredWidth: Style.marginM } } // Password input RowLayout { Layout.fillWidth: true spacing: 0 Item { Layout.preferredWidth: Style.marginM } Rectangle { id: passwordInputContainer Layout.fillWidth: true Layout.preferredHeight: 48 radius: Style.iRadiusL color: Color.mSurface border.color: passwordInput.activeFocus ? Color.mPrimary : Qt.alpha(Color.mOutline, 0.3) border.width: passwordInput.activeFocus ? 2 : 1 property bool passwordVisible: false // Ctrl + A to highlight the portion Shortcut { sequence: StandardKey.SelectAll enabled: passwordInput.activeFocus onActivated: passwordInput.selectAll() } // Esc to clear selection Shortcut { sequences: [StandardKey.Cancel] enabled: passwordInput.activeFocus && passwordInput.selectionStart !== passwordInput.selectionEnd onActivated: passwordInput.deselect() } Row { anchors.left: parent.left anchors.leftMargin: 18 anchors.verticalCenter: parent.verticalCenter spacing: Style.marginL NIcon { icon: "login-2" pointSize: Style.fontSizeL color: passwordInput.activeFocus ? Color.mPrimary : Color.mOnSurfaceVariant anchors.verticalCenter: parent.verticalCenter } Row { spacing: 0 Rectangle { width: 2 height: 20 color: Color.mPrimary visible: passwordInput.activeFocus && passwordInput.text.length === 0 anchors.verticalCenter: parent.verticalCenter // Smooth fade animation (when animations enabled) SequentialAnimation on opacity { loops: Animation.Infinite running: root.animationsEnabled && passwordInput.activeFocus && passwordInput.text.length === 0 NumberAnimation { to: 0 duration: 530 } NumberAnimation { to: 1 duration: 530 } } // Simple toggle (when animations disabled) — no per-frame repaints Timer { interval: 530 running: !root.animationsEnabled && passwordInput.activeFocus && passwordInput.text.length === 0 repeat: true onTriggered: parent.opacity = parent.opacity > 0.5 ? 0 : 1 } } // Host for dots / plain text and the caret (caret x follows passwordInput.cursorPosition) Item { id: passwordVisualHost height: 20 width: passwordInputContainer.passwordVisible ? Math.min(visiblePasswordPlainText.implicitWidth, 550) : Math.min(passwordDisplayContent.width, 550) anchors.verticalCenter: parent.verticalCenter readonly property real caretVisualX: { const len = passwordInput.text.length; if (len <= 0) return 0; if (passwordInputContainer.passwordVisible) { const adv = passwordCaretFontMetrics.advanceWidth(passwordInput.text.substring(0, passwordInput.cursorPosition)); return Math.max(0, Math.min(adv, width)); } const w = passwordDisplayContent.width; if (w <= 0) return 0; return Math.max(0, Math.min((passwordInput.cursorPosition / len) * w, width)); } // Password dots display with selection support Item { width: Math.min(passwordDisplayContent.width, 550) height: 20 visible: passwordInput.text.length > 0 && !passwordInputContainer.passwordVisible anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter clip: true // Proportional selection highlight behind the dots Rectangle { id: selectionHighlight visible: passwordInput.selectionStart !== passwordInput.selectionEnd && passwordInput.text.length > 0 color: Qt.alpha(Color.mPrimary, 0.8) height: parent.height + Style.marginS anchors.verticalCenter: parent.verticalCenter x: (passwordInput.selectionStart / passwordInput.text.length) * passwordDisplayContent.width width: ((passwordInput.selectionEnd - passwordInput.selectionStart) / passwordInput.text.length) * passwordDisplayContent.width } Row { id: passwordDisplayContent spacing: Style.marginXXXS anchors.verticalCenter: parent.verticalCenter Repeater { id: iconRepeater model: ScriptModel { values: Array(passwordInput.text.length) } property list passwordChars: ["circle-filled", "pentagon-filled", "michelin-star-filled", "square-rounded-filled", "guitar-pick-filled", "blob-filled", "triangle-filled"] NIcon { id: icon required property int index // This will be called with index = -1 when the TextInput is deleted // So we make sur index is positive to avoid warning on array accesses property bool drawCustomChar: index >= 0 && Settings.data.general.passwordChars // Flip color when this dot falls inside the active selection range property bool isSelected: index >= 0 && passwordInput.selectionStart !== passwordInput.selectionEnd && index >= passwordInput.selectionStart && index < passwordInput.selectionEnd icon: drawCustomChar ? iconRepeater.passwordChars[index % iconRepeater.passwordChars.length] : "circle-filled" pointSize: Style.fontSizeL color: isSelected ? Color.mOnPrimary : Color.mPrimary opacity: 1.0 scale: animationsEnabled ? 0.5 : 1 ParallelAnimation { id: iconAnim NumberAnimation { target: icon properties: "scale" to: 1 duration: Style.animationFast easing.type: Easing.BezierSpline easing.bezierCurve: Easing.OutInBounce } } Component.onCompleted: { if (animationsEnabled) { iconAnim.start(); } } } } } // Mouse area for click-to-position and drag-to-select MouseArea { anchors.fill: parent cursorShape: Qt.IBeamCursor property int dragStartPos: 0 property bool pendingSelectAll: false // Resets double-click state if user pauses too long between clicks Timer { id: doubleClickResetTimer interval: 600 onTriggered: parent.pendingSelectAll = false } function charIndexFromX(mouseX) { if (passwordInput.text.length === 0) return 0; var charWidth = passwordDisplayContent.width / passwordInput.text.length; // floor so clicking anywhere on a dot selects that dot, not the next return Math.max(0, Math.min(passwordInput.text.length - 1, Math.floor(mouseX / charWidth))); } onPressed: function (mouse) { doubleClickResetTimer.stop(); passwordInput.forceActiveFocus(); dragStartPos = charIndexFromX(mouse.x); passwordInput.cursorPosition = dragStartPos; } onPositionChanged: function (mouse) { pendingSelectAll = false; var curPos = charIndexFromX(mouse.x); if (curPos <= dragStartPos) { passwordInput.select(curPos, dragStartPos + 1); } else { passwordInput.select(dragStartPos, curPos + 1); } } onDoubleClicked: function (mouse) { passwordInput.forceActiveFocus(); if (pendingSelectAll) { passwordInput.selectAll(); pendingSelectAll = false; } else { var pos = charIndexFromX(mouse.x); passwordInput.select(pos, Math.min(pos + 1, passwordInput.text.length)); pendingSelectAll = true; doubleClickResetTimer.restart(); } } } } NText { id: visiblePasswordPlainText text: passwordInput.text color: Color.mPrimary pointSize: Style.fontSizeM visible: passwordInput.text.length > 0 && passwordInputContainer.passwordVisible anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter elide: Text.ElideRight width: Math.min(implicitWidth, 550) } FontMetrics { id: passwordCaretFontMetrics font: visiblePasswordPlainText.font } Rectangle { width: 2 height: 20 x: passwordVisualHost.caretVisualX color: Color.mPrimary // Hide the cursor when text is selected visible: passwordInput.activeFocus && passwordInput.text.length > 0 && passwordInput.selectionStart === passwordInput.selectionEnd anchors.verticalCenter: parent.verticalCenter // Smooth fade animation (when animations enabled) SequentialAnimation on opacity { loops: Animation.Infinite running: root.animationsEnabled && passwordInput.activeFocus && passwordInput.text.length > 0 && passwordInput.selectionStart === passwordInput.selectionEnd NumberAnimation { to: 0 duration: 530 } NumberAnimation { to: 1 duration: 530 } } // Simple toggle (when animations disabled) — no per-frame repaints Timer { interval: 530 running: !root.animationsEnabled && passwordInput.activeFocus && passwordInput.text.length > 0 && passwordInput.selectionStart === passwordInput.selectionEnd repeat: true onTriggered: parent.opacity = parent.opacity > 0.5 ? 0 : 1 } } } } } // Eye button to toggle password visibility Rectangle { anchors.right: submitButton.left anchors.rightMargin: 4 anchors.verticalCenter: parent.verticalCenter width: 36 height: 36 radius: Math.min(Style.iRadiusL, width / 2) color: eyeButtonArea.containsMouse ? Color.mPrimary : "transparent" visible: passwordInput.text.length > 0 enabled: !lockContext || !lockContext.unlockInProgress NIcon { anchors.centerIn: parent icon: parent.parent.passwordVisible ? "eye-off" : "eye" pointSize: Style.fontSizeM color: eyeButtonArea.containsMouse ? Color.mOnPrimary : Color.mOnSurfaceVariant Behavior on color { ColorAnimation { duration: Style.animationFast easing.type: Easing.OutCubic } } } MouseArea { id: eyeButtonArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: parent.parent.passwordVisible = !parent.parent.passwordVisible } Behavior on color { ColorAnimation { duration: Style.animationFast easing.type: Easing.OutCubic } } } // Submit button Rectangle { id: submitButton anchors.right: parent.right anchors.rightMargin: 8 anchors.verticalCenter: parent.verticalCenter width: 36 height: 36 radius: Math.min(Style.iRadiusL, width / 2) color: submitButtonArea.containsMouse ? Color.mPrimary : "transparent" border.color: Color.mPrimary border.width: Style.borderS enabled: !lockContext || !lockContext.unlockInProgress NIcon { anchors.centerIn: parent icon: "arrow-forward" pointSize: Style.fontSizeM color: submitButtonArea.containsMouse ? Color.mOnPrimary : Color.mPrimary Behavior on color { ColorAnimation { duration: Style.animationFast easing.type: Easing.OutCubic } } } MouseArea { id: submitButtonArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: root.doUnlock() } Behavior on color { ColorAnimation { duration: Style.animationFast easing.type: Easing.OutCubic } } } Behavior on border.color { ColorAnimation { duration: Style.animationFast easing.type: Easing.OutCubic } } } Item { Layout.preferredWidth: Style.marginM } } // Session control buttons RowLayout { id: sessionButtonRow Layout.fillWidth: true Layout.preferredHeight: Settings.data.general.compactLockScreen ? 36 : 48 Layout.alignment: Qt.AlignHCenter spacing: Style.marginM visible: Settings.data.general.showSessionButtonsOnLockScreen readonly property int buttonCount: Settings.data.general.showHibernateOnLockScreen ? 5 : 4 readonly property real availableWidth: bottomContainer.width - 48 readonly property real buttonWidth: (availableWidth - (buttonCount - 1) * spacing) / buttonCount readonly property real buttonHeight: sessionButtonRow.height Item { Layout.preferredWidth: sessionButtonRow.buttonWidth Layout.preferredHeight: sessionButtonRow.buttonHeight NButton { anchors.fill: parent icon: "logout" text: I18n.tr("common.logout") outlined: true backgroundColor: Color.mOnSurfaceVariant textColor: Color.mOnPrimary fontSize: Settings.data.general.compactLockScreen ? Style.fontSizeS : Style.fontSizeM iconSize: Settings.data.general.compactLockScreen ? Style.fontSizeM : Style.fontSizeL horizontalAlignment: Qt.AlignHCenter buttonRadius: Style.radiusL onClicked: startTimer("logout") } } Item { Layout.preferredWidth: sessionButtonRow.buttonWidth Layout.preferredHeight: sessionButtonRow.buttonHeight NButton { anchors.fill: parent icon: "suspend" text: I18n.tr("common.suspend") outlined: true backgroundColor: Color.mOnSurfaceVariant textColor: Color.mOnPrimary fontSize: Settings.data.general.compactLockScreen ? Style.fontSizeS : Style.fontSizeM iconSize: Settings.data.general.compactLockScreen ? Style.fontSizeM : Style.fontSizeL horizontalAlignment: Qt.AlignHCenter buttonRadius: Style.radiusL onClicked: startTimer("suspend") } } Item { Layout.preferredWidth: sessionButtonRow.buttonWidth Layout.preferredHeight: sessionButtonRow.buttonHeight visible: Settings.data.general.showHibernateOnLockScreen NButton { anchors.fill: parent icon: "hibernate" text: I18n.tr("common.hibernate") outlined: true backgroundColor: Color.mOnSurfaceVariant textColor: Color.mOnPrimary fontSize: Settings.data.general.compactLockScreen ? Style.fontSizeS : Style.fontSizeM iconSize: Settings.data.general.compactLockScreen ? Style.fontSizeM : Style.fontSizeL horizontalAlignment: Qt.AlignHCenter buttonRadius: Style.radiusL onClicked: startTimer("hibernate") } } Item { Layout.preferredWidth: sessionButtonRow.buttonWidth Layout.preferredHeight: sessionButtonRow.buttonHeight NButton { anchors.fill: parent icon: "reboot" text: I18n.tr("common.reboot") outlined: true backgroundColor: Color.mOnSurfaceVariant textColor: Color.mOnPrimary fontSize: Settings.data.general.compactLockScreen ? Style.fontSizeS : Style.fontSizeM iconSize: Settings.data.general.compactLockScreen ? Style.fontSizeM : Style.fontSizeL horizontalAlignment: Qt.AlignHCenter buttonRadius: Style.radiusL onClicked: startTimer("reboot") } } Item { Layout.preferredWidth: sessionButtonRow.buttonWidth Layout.preferredHeight: sessionButtonRow.buttonHeight NButton { anchors.fill: parent icon: "shutdown" text: I18n.tr("common.shutdown") outlined: true backgroundColor: Color.mError textColor: Color.mOnError fontSize: Settings.data.general.compactLockScreen ? Style.fontSizeS : Style.fontSizeM iconSize: Settings.data.general.compactLockScreen ? Style.fontSizeM : Style.fontSizeL horizontalAlignment: Qt.AlignHCenter buttonRadius: Style.radiusL onClicked: startTimer("shutdown") } } } } } }