import QtQuick 2.15 import QtQuick.Window 2.15 Rectangle { id: root width: Screen.width height: Screen.height color: colors.background QtObject { id: colors // Keep these in sync with the Mizuki Dark palette used by hyprlock. readonly property color background: "#191017" readonly property color black: "#000000" readonly property color text: "#fafafa" readonly property color pink: "#f5c2e7" readonly property color secondary: "#bac2de" readonly property color error: "#f38ba8" readonly property color input: "#1affffff" readonly property color inputActive: "#2affffff" } property int sessionIndex: Math.max(0, sessionModel.lastIndex) property bool sessionMenuOpen: false property bool loginInProgress: false property string errorText: "" property date now: new Date() // Keep the interactive layout in the same coordinate system as hyprlock. // Fixed logical margins prevent fractional scaling from pulling elements // apart on the greeter screen. property real edgeMargin: 32 property real loginBottomMargin: 120 function sessionCount() { return sessionModel.rowCount(); } function sessionName(index) { if (index === undefined) index = sessionIndex; var count = sessionCount(); if (count === 0) return "No session"; index = Math.max(0, Math.min(index, count - 1)); var label = sessionModel.data(sessionModel.index(index, 0), Qt.UserRole + 4); return label ? label.toString() : "Desktop"; } function selectSession(index) { sessionIndex = index; sessionMenuOpen = false; } function login() { if (loginInProgress || username.text.trim().length === 0) return; errorText = ""; loginInProgress = true; sddm.login(username.text.trim(), password.text, sessionIndex); } Timer { interval: 1000 repeat: true running: true onTriggered: root.now = new Date() } // Hyprlock uses the current wallpaper with a dark, low-brightness treatment. // SDDM cannot read the logged-in user's current-wallpaper symlink, so it uses // the same default wallpaper shipped with this host. Image { anchors.fill: parent source: Qt.resolvedUrl("wallpaper.png") fillMode: Image.PreserveAspectCrop asynchronous: true cache: true smooth: true } Rectangle { anchors.fill: parent color: colors.black opacity: 0.40 } Rectangle { anchors.fill: parent color: colors.background opacity: 0.08 } // One clock container owns all four labels. Keeping the labels in one // column avoids independent anchor calculations drifting at fractional // display scales, while the whole cluster stays above the login form. Item { id: clockCluster width: 420 height: 360 anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter anchors.verticalCenterOffset: -80 Column { anchors.centerIn: parent spacing: -18 Text { width: clockCluster.width text: Qt.formatTime(root.now, "HH") color: colors.text horizontalAlignment: Text.AlignHCenter font.family: "Adwaita Sans" font.pixelSize: 112 font.weight: Font.Bold } Text { width: clockCluster.width text: Qt.formatTime(root.now, "mm") color: colors.text horizontalAlignment: Text.AlignHCenter font.family: "Adwaita Sans" font.pixelSize: 112 font.weight: Font.Bold } Item { width: 1; height: 8 } Text { width: clockCluster.width text: Qt.formatDate(root.now, "dddd") color: colors.pink horizontalAlignment: Text.AlignHCenter font.family: "JetBrainsMono NFM" font.pixelSize: 18 font.bold: true } Text { width: clockCluster.width text: Qt.formatDate(root.now, "dd MMM") color: colors.pink horizontalAlignment: Text.AlignHCenter font.family: "JetBrainsMono NFM" font.pixelSize: 14 font.bold: true } } } // User selection stays immediately above the password field, as requested. Column { id: loginArea width: 250 anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom anchors.bottomMargin: root.loginBottomMargin spacing: 10 Item { width: parent.width height: 60 Text { anchors.left: parent.left anchors.top: parent.top text: "USER" color: colors.pink font.family: "JetBrainsMono NFM" font.pixelSize: 10 font.letterSpacing: 1.2 } Rectangle { id: userField anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom height: 42 radius: 20 color: username.activeFocus ? colors.inputActive : colors.input border.color: colors.pink border.width: username.activeFocus ? 3 : 2 TextInput { id: username anchors.fill: parent anchors.leftMargin: 16 anchors.rightMargin: 16 color: colors.text selectionColor: colors.pink selectedTextColor: colors.background verticalAlignment: TextInput.AlignVCenter font.family: "Adwaita Sans" font.pixelSize: 16 text: userModel.lastUser selectByMouse: true clip: true Keys.onPressed: function(event) { if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { password.forceActiveFocus(); event.accepted = true; } } onTextChanged: root.errorText = "" } Text { anchors.left: parent.left anchors.leftMargin: 16 anchors.verticalCenter: parent.verticalCenter text: "Username..." color: colors.secondary font.family: "Adwaita Sans" font.pixelSize: 16 font.italic: true visible: username.text.length === 0 && !username.activeFocus } } } Rectangle { id: passwordField width: parent.width height: 50 radius: 22 color: password.activeFocus ? colors.inputActive : colors.input border.color: colors.pink border.width: 3 TextInput { id: password anchors.fill: parent anchors.leftMargin: 16 anchors.rightMargin: 16 color: colors.pink selectionColor: colors.pink selectedTextColor: colors.background verticalAlignment: TextInput.AlignVCenter horizontalAlignment: TextInput.AlignHCenter font.family: "Adwaita Sans" font.pixelSize: 17 echoMode: TextInput.Password passwordCharacter: "•" selectByMouse: true clip: true Keys.onPressed: function(event) { if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { root.login(); event.accepted = true; } } onTextChanged: root.errorText = "" } Text { anchors.fill: parent anchors.leftMargin: 16 anchors.rightMargin: 16 verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter text: "Password..." color: colors.secondary font.family: "Adwaita Sans" font.pixelSize: 16 font.italic: true visible: password.text.length === 0 && !password.activeFocus } } Text { width: parent.width height: visible ? implicitHeight : 0 text: root.errorText color: colors.error horizontalAlignment: Text.AlignHCenter wrapMode: Text.WordWrap font.family: "Adwaita Sans" font.pixelSize: 12 visible: root.errorText.length > 0 } } // A quiet, centered failure message does not change the lockscreen-like layout. Connections { target: sddm function onLoginFailed() { root.loginInProgress = false; root.errorText = "That password did not work. Try again."; password.text = ""; password.forceActiveFocus(); } function onLoginSucceeded() { root.loginInProgress = false; } function onInformationMessage(message) { root.loginInProgress = false; root.errorText = message; } } // Bottom-left desktop/session selector. Item { id: sessionSelector z: 100 width: 250 height: 58 anchors.left: parent.left anchors.leftMargin: root.edgeMargin anchors.bottom: parent.bottom anchors.bottomMargin: root.edgeMargin Text { anchors.left: parent.left anchors.top: parent.top text: "SESSION" color: colors.pink font.family: "JetBrainsMono NFM" font.pixelSize: 10 font.letterSpacing: 1.2 } Rectangle { id: sessionButton anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom height: 38 radius: 19 color: sessionMouse.containsMouse ? "#32ffffff" : "#1affffff" border.color: colors.pink border.width: 2 opacity: root.sessionMenuOpen ? 1 : 0.9 Text { anchors.left: parent.left anchors.leftMargin: 16 anchors.right: arrow.left anchors.verticalCenter: parent.verticalCenter text: root.sessionName() color: colors.text elide: Text.ElideRight font.family: "Adwaita Sans" font.pixelSize: 14 } Text { id: arrow anchors.right: parent.right anchors.rightMargin: 15 anchors.verticalCenter: parent.verticalCenter text: root.sessionMenuOpen ? "⌃" : "⌄" color: colors.pink font.family: "Adwaita Sans" font.pixelSize: 18 } MouseArea { id: sessionMouse anchors.fill: parent hoverEnabled: true enabled: root.sessionCount() > 0 onClicked: root.sessionMenuOpen = !root.sessionMenuOpen } } } MouseArea { id: sessionMenuDismiss anchors.fill: parent z: 110 visible: root.sessionMenuOpen onClicked: root.sessionMenuOpen = false } Rectangle { id: sessionMenu z: 111 width: sessionSelector.width height: Math.min(Math.max(46, root.sessionCount() * 46), root.height * 0.38) anchors.left: sessionSelector.left // sessionButton is inside sessionSelector, so anchor to the sibling // container instead of crossing item hierarchies. anchors.bottom: sessionSelector.top anchors.bottomMargin: 8 radius: 18 color: "#f2191017" border.color: colors.pink border.width: 2 visible: root.sessionMenuOpen && root.sessionCount() > 0 clip: true ListView { id: sessionList anchors.fill: parent anchors.margins: 4 clip: true model: root.sessionCount() currentIndex: root.sessionIndex delegate: Rectangle { width: sessionList.width height: 42 radius: 14 color: index === root.sessionIndex ? "#32f5c2e7" : (sessionDelegateMouse.containsMouse ? "#22f5c2e7" : "transparent") Text { anchors.left: parent.left anchors.leftMargin: 14 anchors.right: parent.right anchors.rightMargin: 12 anchors.verticalCenter: parent.verticalCenter text: root.sessionName(index) color: colors.text elide: Text.ElideRight font.family: "Adwaita Sans" font.pixelSize: 14 } MouseArea { id: sessionDelegateMouse anchors.fill: parent hoverEnabled: true onClicked: root.selectSession(index) } } } } // SDDM-only controls; the lock screen itself has no power controls. Row { z: 100 anchors.right: parent.right anchors.rightMargin: root.edgeMargin anchors.bottom: parent.bottom anchors.bottomMargin: root.edgeMargin + 6 spacing: 22 Text { text: "POWER OFF" color: powerMouse.containsMouse ? colors.text : colors.secondary font.family: "JetBrainsMono NFM" font.pixelSize: 10 font.letterSpacing: 1.1 MouseArea { id: powerMouse anchors.fill: parent anchors.margins: -10 hoverEnabled: true onClicked: sddm.powerOff() } } Text { text: "REBOOT" color: rebootMouse.containsMouse ? colors.text : colors.secondary font.family: "JetBrainsMono NFM" font.pixelSize: 10 font.letterSpacing: 1.1 MouseArea { id: rebootMouse anchors.fill: parent anchors.margins: -10 hoverEnabled: true onClicked: sddm.reboot() } } } Component.onCompleted: { if (username.text.length > 0) password.forceActiveFocus(); else username.forceActiveFocus(); } }