add noctalia and fuzzel
This commit is contained in:
@@ -0,0 +1,387 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Services.Pam
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Services.Compositor
|
||||
import qs.Services.Hardware
|
||||
import qs.Services.Keyboard
|
||||
import qs.Services.Media
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
Loader {
|
||||
id: root
|
||||
active: false
|
||||
|
||||
// Track if the visualizer should be shown (lockscreen active + media playing + non-compact mode)
|
||||
readonly property bool needsSpectrum: root.active && !Settings.data.general.compactLockScreen && Settings.data.audio.visualizerType !== "" && Settings.data.audio.visualizerType !== "none"
|
||||
|
||||
onActiveChanged: {
|
||||
if (root.active && root.needsSpectrum) {
|
||||
SpectrumService.registerComponent("lockscreen");
|
||||
} else {
|
||||
SpectrumService.unregisterComponent("lockscreen");
|
||||
}
|
||||
|
||||
if (root.active) {
|
||||
LockKeysService.registerComponent("lockscreen");
|
||||
} else {
|
||||
LockKeysService.unregisterComponent("lockscreen");
|
||||
}
|
||||
}
|
||||
|
||||
onNeedsSpectrumChanged: {
|
||||
if (root.needsSpectrum) {
|
||||
SpectrumService.registerComponent("lockscreen");
|
||||
} else {
|
||||
SpectrumService.unregisterComponent("lockscreen");
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
// Register with panel service
|
||||
PanelService.lockScreen = this;
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
SpectrumService.unregisterComponent("lockscreen");
|
||||
LockKeysService.unregisterComponent("lockscreen");
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: unloadAfterUnlockTimer
|
||||
interval: 250
|
||||
repeat: false
|
||||
onTriggered: root.active = false
|
||||
}
|
||||
|
||||
function scheduleUnloadAfterUnlock() {
|
||||
unloadAfterUnlockTimer.start();
|
||||
}
|
||||
|
||||
sourceComponent: Component {
|
||||
Item {
|
||||
id: lockContainer
|
||||
|
||||
LockContext {
|
||||
id: lockContext
|
||||
onUnlocked: {
|
||||
lockSession.locked = false;
|
||||
root.scheduleUnloadAfterUnlock();
|
||||
lockContext.currentText = "";
|
||||
}
|
||||
onFailed: {
|
||||
lockContext.currentText = "";
|
||||
}
|
||||
}
|
||||
|
||||
// Whether any monitor from the user's lockScreenMonitors list is currently connected.
|
||||
readonly property bool anyConfiguredMonitorConnected: {
|
||||
const configured = Settings.data.general.lockScreenMonitors;
|
||||
if (!configured || configured.length === 0)
|
||||
return false;
|
||||
return (Quickshell.screens || []).some(s => configured.includes(s.name));
|
||||
}
|
||||
|
||||
WlSessionLock {
|
||||
id: lockSession
|
||||
locked: root.active
|
||||
|
||||
WlSessionLockSurface {
|
||||
id: lockSurface
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
active: true
|
||||
sourceComponent: (!lockContainer.anyConfiguredMonitorConnected || Settings.data.general.lockScreenMonitors.includes(lockSurface.screen?.name)) ? fullLockScreenComponent : blackScreenComponent
|
||||
}
|
||||
|
||||
Component {
|
||||
id: fullLockScreenComponent
|
||||
|
||||
Item {
|
||||
Item {
|
||||
id: batteryIndicator
|
||||
|
||||
property bool isReady: BatteryService.batteryReady
|
||||
property real percent: BatteryService.batteryPercentage
|
||||
property bool charging: BatteryService.batteryCharging
|
||||
property bool pluggedIn: BatteryService.batteryPluggedIn
|
||||
property bool batteryVisible: isReady
|
||||
property string icon: BatteryService.batteryIcon
|
||||
}
|
||||
|
||||
Item {
|
||||
id: keyboardLayout
|
||||
property string currentLayout: KeyboardLayoutService.currentLayout
|
||||
}
|
||||
|
||||
// Background with wallpaper, gradient, and screen corners
|
||||
LockScreenBackground {
|
||||
id: backgroundComponent
|
||||
screen: lockSurface.screen
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
// Mouse area to trigger focus on cursor movement (workaround for Hyprland focus issues)
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
onEntered: {
|
||||
// Avoid repeatedly forcing focus on every mouse move.
|
||||
// This can churn text-input surface state during monitor/suspend transitions.
|
||||
if (passwordInput && !passwordInput.activeFocus) {
|
||||
passwordInput.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Header with avatar, welcome, time, date
|
||||
LockScreenHeader {
|
||||
id: headerComponent
|
||||
}
|
||||
|
||||
// Info notification
|
||||
Rectangle {
|
||||
width: infoRowLayout.implicitWidth + Style.marginXL * 1.5
|
||||
height: 50
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: (Settings.data.general.compactLockScreen ? 280 : 360) * Style.uiScaleRatio
|
||||
radius: Style.radiusL
|
||||
color: Color.mTertiary
|
||||
visible: lockContext.showInfo && lockContext.infoMessage && !panelComponent.timerActive
|
||||
opacity: visible ? 1.0 : 0.0
|
||||
|
||||
RowLayout {
|
||||
id: infoRowLayout
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginM
|
||||
|
||||
NIcon {
|
||||
icon: "circle-key"
|
||||
pointSize: Style.fontSizeXL
|
||||
color: Color.mOnTertiary
|
||||
}
|
||||
|
||||
NText {
|
||||
text: lockContext.infoMessage
|
||||
color: Color.mOnTertiary
|
||||
pointSize: Style.fontSizeL
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Error notification
|
||||
Rectangle {
|
||||
width: errorRowLayout.implicitWidth + Style.marginXL * 1.5
|
||||
height: 50
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: (Settings.data.general.compactLockScreen ? 280 : 360) * Style.uiScaleRatio
|
||||
radius: Style.radiusL
|
||||
color: Color.mError
|
||||
visible: lockContext.showFailure && lockContext.errorMessage && !panelComponent.timerActive
|
||||
opacity: visible ? 1.0 : 0.0
|
||||
|
||||
RowLayout {
|
||||
id: errorRowLayout
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginM
|
||||
|
||||
NIcon {
|
||||
icon: "alert-circle"
|
||||
pointSize: Style.fontSizeXL
|
||||
color: Color.mOnError
|
||||
}
|
||||
|
||||
NText {
|
||||
text: lockContext.errorMessage || "Authentication failed"
|
||||
color: Color.mOnError
|
||||
pointSize: Style.fontSizeL
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Countdown notification
|
||||
Rectangle {
|
||||
width: countdownRowLayout.implicitWidth + Style.marginXL * 1.5
|
||||
height: 50
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: (Settings.data.general.compactLockScreen ? 280 : 360) * Style.uiScaleRatio
|
||||
radius: Style.radiusL
|
||||
color: Color.mSurface
|
||||
visible: panelComponent.timerActive
|
||||
opacity: visible ? 1.0 : 0.0
|
||||
|
||||
RowLayout {
|
||||
id: countdownRowLayout
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM
|
||||
spacing: Style.marginM
|
||||
|
||||
NIcon {
|
||||
icon: "clock"
|
||||
pointSize: Style.fontSizeXL
|
||||
color: Color.mPrimary
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("session-menu.action-in-seconds", {
|
||||
"action": I18n.tr("common." + panelComponent.pendingAction),
|
||||
"seconds": Math.ceil(panelComponent.timeRemaining / 1000)
|
||||
})
|
||||
color: Color.mOnSurface
|
||||
pointSize: Style.fontSizeL
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.weight: Style.fontWeightBold
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "x"
|
||||
tooltipText: I18n.tr("session-menu.cancel-timer")
|
||||
baseSize: 32
|
||||
colorBg: Qt.alpha(Color.mPrimary, 0.1)
|
||||
colorFg: Color.mPrimary
|
||||
colorBgHover: Color.mPrimary
|
||||
onClicked: panelComponent.cancelTimer()
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hidden input that receives actual text
|
||||
TextInput {
|
||||
id: passwordInput
|
||||
width: 0
|
||||
height: 0
|
||||
visible: false
|
||||
enabled: !lockContext.unlockInProgress
|
||||
echoMode: TextInput.Password
|
||||
passwordMaskDelay: 0
|
||||
|
||||
// Bidirectional sync — avoids a declarative binding which breaks on input
|
||||
onTextChanged: {
|
||||
if (lockContext.currentText !== text)
|
||||
lockContext.currentText = text;
|
||||
}
|
||||
Connections {
|
||||
target: lockContext
|
||||
function onCurrentTextChanged() {
|
||||
if (passwordInput.text !== lockContext.currentText)
|
||||
passwordInput.text = lockContext.currentText;
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onPressed: function (event) {
|
||||
if (Keybinds.checkKey(event, 'enter', Settings)) {
|
||||
lockContext.tryUnlock();
|
||||
event.accepted = true;
|
||||
}
|
||||
if (Keybinds.checkKey(event, 'escape', Settings) && panelComponent.timerActive) {
|
||||
panelComponent.cancelTimer();
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: forceActiveFocus()
|
||||
}
|
||||
|
||||
// Main panel with password, weather, media, session controls
|
||||
LockScreenPanel {
|
||||
id: panelComponent
|
||||
lockControl: lockContext
|
||||
batteryIndicator: batteryIndicator
|
||||
keyboardLayout: keyboardLayout
|
||||
passwordInput: passwordInput
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: blackScreenComponent
|
||||
|
||||
// Black surface for disabled monitors — still captures keyboard for password entry
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "black"
|
||||
|
||||
TextInput {
|
||||
id: blackScreenPasswordInput
|
||||
width: 0
|
||||
height: 0
|
||||
visible: false
|
||||
enabled: !lockContext.unlockInProgress
|
||||
echoMode: TextInput.Password
|
||||
passwordMaskDelay: 0
|
||||
|
||||
// Bidirectional sync — avoids a declarative binding which breaks on input
|
||||
onTextChanged: {
|
||||
if (lockContext.currentText !== text)
|
||||
lockContext.currentText = text;
|
||||
}
|
||||
Connections {
|
||||
target: lockContext
|
||||
function onCurrentTextChanged() {
|
||||
if (blackScreenPasswordInput.text !== lockContext.currentText)
|
||||
blackScreenPasswordInput.text = lockContext.currentText;
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onPressed: function (event) {
|
||||
if (Keybinds.checkKey(event, 'enter', Settings)) {
|
||||
lockContext.tryUnlock();
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: forceActiveFocus()
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
onPositionChanged: blackScreenPasswordInput.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user