Files
homelab/dotfiles/quickshell/HyprChrome/Widgets/Greeter/LoginContent.qml
T
darmanandClaude Opus 5 f3dddc9150 feat(terra): replace tuigreet with a quickshell greeter
greetd now runs a throwaway Hyprland hosting dotfiles/quickshell/greeter.qml,
configured per host via homelab.greeter.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 23:34:10 +02:00

305 lines
9.5 KiB
QML

pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.HyprChrome.Theme
// Headless visual core of the login greeter; Greeter.qml owns greetd.
Item {
id: root
property string hostName: ""
property string sessionName: ""
property date now: new Date()
// Label over the secret field. Empty means the plain password step; set
// when PAM asks for something else (OTP, PIN).
property string inputPrompt: ""
property bool responseVisible: false
property string message: ""
property bool messageIsError: false
property bool failed: false
property bool busy: false
property alias user: userInput.text
property alias response: secretInput.text
signal submitted(string user, string response)
signal userEdited
signal powerRequested(string action)
function focusSecret() { secretInput.forceActiveFocus(); }
function focusUser() { userInput.forceActiveFocus(); }
function clearSecret() { secretInput.text = ""; }
implicitWidth: 640
implicitHeight: panel.implicitHeight
function submit() {
if (root.busy)
return;
if (userInput.text.trim() === "") {
root.focusUser();
return;
}
root.submitted(userInput.text.trim(), secretInput.text);
}
component MicroText: Text {
color: Theme.muted
font.family: Theme.microFont
font.pixelSize: 8
font.letterSpacing: 0.9
elide: Text.ElideRight
}
component Field: Rectangle {
id: field
property bool active: false
property bool alert: false
property string glyph: ""
default property alias input: slot.data
Layout.fillWidth: true
implicitHeight: 36
color: Theme.selection
border.width: 1
border.color: field.alert ? Theme.hot : field.active ? Theme.accentAlpha(0.7) : Theme.hair
Behavior on border.color { ColorAnimation { duration: 120 } }
// Focus tick on the left edge.
Rectangle {
width: 2
height: parent.height
color: Theme.accent
visible: field.active
}
Text {
id: glyphText
x: 12
anchors.verticalCenter: parent.verticalCenter
text: field.glyph
color: field.active ? Theme.accent : Theme.disabled
font.family: Theme.displayFont
font.pixelSize: 14
font.bold: true
}
Item {
id: slot
anchors.left: glyphText.right
anchors.leftMargin: 10
anchors.right: parent.right
anchors.rightMargin: 12
anchors.top: parent.top
anchors.bottom: parent.bottom
}
}
component PowerChip: Rectangle {
id: chip
property string label: ""
property string action: ""
width: chipLabel.implicitWidth + 16
height: chipLabel.implicitHeight + 8
color: chipMouse.containsMouse ? Theme.accent : "transparent"
border.width: 1
border.color: chipMouse.containsMouse ? Theme.accent : Theme.disabled
Text {
id: chipLabel
anchors.centerIn: parent
text: chip.label
color: chipMouse.containsMouse ? Theme.surface : Theme.muted
font.family: Theme.microFont
font.pixelSize: 9
font.letterSpacing: 0.8
}
MouseArea {
id: chipMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.powerRequested(chip.action)
}
}
LoginPanel {
id: panel
width: root.width
panelId: "LGN"
title: "SESSION // " + root.hostName.toUpperCase()
meta: "GREETD"
busy: root.busy
ColumnLayout {
width: parent.width
spacing: 16
RowLayout {
Layout.fillWidth: true
spacing: 20
// ---- clock column ----
ColumnLayout {
Layout.preferredWidth: 190
Layout.alignment: Qt.AlignTop
spacing: 4
Text {
text: Qt.formatTime(root.now, "HH:mm")
color: Theme.text
font.family: Theme.displayFont
font.pixelSize: 54
font.letterSpacing: 2
}
MicroText {
text: Qt.formatDate(root.now, "ddd dd.MM.yyyy").toUpperCase()
color: Theme.accent
font.pixelSize: 10
}
// Seconds as a segment meter: one cell per five seconds.
Row {
Layout.topMargin: 10
spacing: 3
Repeater {
model: 12
Rectangle {
required property int index
width: 11
height: 5
color: index < Math.floor(root.now.getSeconds() / 5) + 1
? Theme.accent : Theme.raised
}
}
}
MicroText {
Layout.topMargin: 10
text: "SESSION " + root.sessionName.toUpperCase()
}
}
Rectangle {
Layout.fillHeight: true
implicitWidth: 1
color: Theme.textAlpha(0.12)
}
// ---- credentials ----
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
spacing: 6
MicroText { text: "OPERATOR" }
Field {
glyph: "@"
active: userInput.activeFocus
TextInput {
id: userInput
anchors.fill: parent
verticalAlignment: TextInput.AlignVCenter
enabled: !root.busy && root.inputPrompt === ""
color: Theme.text
selectionColor: Theme.accent
selectedTextColor: Theme.surface
font.family: Theme.displayFont
font.pixelSize: 14
font.letterSpacing: 1
clip: true
onTextEdited: root.userEdited()
onAccepted: root.focusSecret()
KeyNavigation.tab: secretInput
}
}
MicroText {
Layout.topMargin: 6
text: root.inputPrompt !== "" ? root.inputPrompt.toUpperCase() : "PASSPHRASE"
color: root.inputPrompt !== "" ? Theme.accent : Theme.muted
}
Field {
glyph: ">_"
active: secretInput.activeFocus
alert: root.failed
TextInput {
id: secretInput
anchors.fill: parent
verticalAlignment: TextInput.AlignVCenter
enabled: !root.busy
color: Theme.text
selectionColor: Theme.accent
selectedTextColor: Theme.surface
font.family: Theme.displayFont
font.pixelSize: 14
font.letterSpacing: 1
clip: true
echoMode: root.responseVisible ? TextInput.Normal : TextInput.Password
passwordCharacter: "▪"
passwordMaskDelay: 0
onAccepted: root.submit()
KeyNavigation.backtab: userInput
}
}
// Status line; keeps its height so the panel does not jump.
MicroText {
Layout.fillWidth: true
Layout.topMargin: 4
Layout.preferredHeight: 12
text: root.busy && root.message === "" ? "AUTHENTICATING…" : root.message.toUpperCase()
color: root.messageIsError ? Theme.hot : root.busy ? Theme.accent : Theme.muted
font.pixelSize: 9
}
}
}
// ---- footer ----
Rectangle {
Layout.fillWidth: true
implicitHeight: 1
color: Theme.textAlpha(0.12)
}
RowLayout {
Layout.fillWidth: true
spacing: 14
MicroText { text: "ENTER LOG IN" }
MicroText { text: "TAB SWITCH FIELD" }
Item { Layout.fillWidth: true }
PowerChip { label: "REBOOT"; action: "reboot" }
PowerChip { label: "POWER OFF"; action: "poweroff" }
}
}
}
}