auth: use the expressive text field for polkit, network prompts, greeter and settings password fields

This commit is contained in:
bbedward
2026-10-05 22:09:37 -04:00
parent 87a4da9e13
commit b81373aae8
8 changed files with 125 additions and 287 deletions
Generated
+3 -3
View File
@@ -3,11 +3,11 @@
"dank-qml-common": {
"flake": false,
"locked": {
"lastModified": 1791237894,
"narHash": "sha256-O2Gsf++QDSD7GHXGitFHvHKW+5jaYkTwKZb40U7EB2U=",
"lastModified": 1791252506,
"narHash": "sha256-AWmajKfU9u791pk4z3qr9WJT2Q11AJWlbdvNGzu1TL4=",
"owner": "AvengeMedia",
"repo": "dank-qml-common",
"rev": "b5b7639693b2c83f0f97ddd2af3d60d559174846",
"rev": "b690ebcf7eddffae846fb1aa1a9acaf89a384777",
"type": "github"
},
"original": {
+4 -6
View File
@@ -95,6 +95,7 @@ DDialog {
function onAuthenticationFailed() {
root.isLoading = false;
passwordField.shake();
}
function onAuthenticationRequestCancelled() {
@@ -150,18 +151,16 @@ DDialog {
id: passwordField
width: parent.width
outlined: true
controlHeight: Theme.fieldHeightLarge
expressive: true
morph: true
busy: root.isLoading
labelText: root.inputPromptLabel || I18n.tr("Password")
isError: PolkitService.authFailed
supportingText: isError ? I18n.tr("Authentication failed - try again") : ""
leftIconName: root.polkitPamHasFprint ? "fingerprint" : "lock"
font.pixelSize: Theme.fontSizeMedium
textColor: Theme.surfaceText
text: root.passwordInput
showPasswordToggle: !(root.currentFlow?.responseVisible ?? false)
echoMode: (root.currentFlow?.responseVisible ?? false) || passwordVisible ? TextInput.Normal : TextInput.Password
placeholderText: ""
enabled: !root.isLoading
onTextEdited: root.passwordInput = text
onAccepted: root.submitAuth()
@@ -181,7 +180,6 @@ DDialog {
wrapText: true
text: I18n.tr("Authenticate", "verb, polkit password dialog submit button")
enabled: !root.isLoading
busy: root.isLoading
onClicked: root.submitAuth()
}
]
+23 -27
View File
@@ -165,9 +165,8 @@ DModal {
open();
Qt.callLater(() => {
if (reason === "wrong-password" && fieldsInfo.length === 0) {
passwordInput.text = "";
}
if (reason === "wrong-password")
shakeSecretFields();
focusFirstField();
});
}
@@ -176,6 +175,19 @@ DModal {
close();
}
function shakeSecretFields() {
if (fieldsInfo.length === 0) {
passwordInput.text = "";
passwordInput.shake();
return;
}
for (var i = 0; i < dynamicFieldsRepeater.count; i++) {
const item = dynamicFieldsRepeater.itemAt(i);
if (item?.morph)
item.shake();
}
}
function getFieldLabel(fieldName) {
switch (fieldName) {
case "username":
@@ -369,13 +381,10 @@ DModal {
DTextField {
id: ssidInput
visible: isHiddenNetwork
outlined: true
controlHeight: Theme.fieldHeightLarge
expressive: true
leftIconName: "wifi"
width: parent.width
font.pixelSize: Theme.fontSizeMedium
textColor: Theme.surfaceText
labelText: I18n.tr("Network Name (SSID)")
enabled: root.shouldBeVisible
@@ -390,12 +399,10 @@ DModal {
id: fieldInput
required property var modelData
required property int index
outlined: true
controlHeight: Theme.fieldHeightLarge
expressive: true
morph: modelData.isSecret
leftIconName: modelData.isSecret ? "lock" : "person"
width: contentFocusScope.contentItem.width
font.pixelSize: Theme.fontSizeMedium
textColor: Theme.surfaceText
showPasswordToggle: modelData.isSecret
isError: modelData.isSecret && isPromptMode && promptReason === "wrong-password" && text.length === 0
supportingText: isError ? I18n.tr("Incorrect password") : ""
@@ -476,13 +483,10 @@ DModal {
DTextField {
id: usernameInput
visible: showUsernameField
outlined: true
controlHeight: Theme.fieldHeightLarge
expressive: true
leftIconName: "person"
width: parent.width
font.pixelSize: Theme.fontSizeMedium
textColor: Theme.surfaceText
text: wifiUsernameInput
labelText: I18n.tr("Username", "text field label for network, vpn and account forms")
enabled: root.shouldBeVisible
@@ -494,13 +498,11 @@ DModal {
DTextField {
id: passwordInput
visible: showPasswordField
outlined: true
controlHeight: Theme.fieldHeightLarge
expressive: true
morph: true
leftIconName: "lock"
width: parent.width
font.pixelSize: Theme.fontSizeMedium
textColor: Theme.surfaceText
text: wifiPasswordInput
showPasswordToggle: true
isError: isPromptMode && promptReason === "wrong-password" && text.length === 0
@@ -522,13 +524,10 @@ DModal {
DTextField {
id: anonInput
visible: showAnonField
outlined: true
controlHeight: Theme.fieldHeightLarge
expressive: true
leftIconName: "person_off"
width: parent.width
font.pixelSize: Theme.fontSizeMedium
textColor: Theme.surfaceText
text: wifiAnonymousIdentityInput
labelText: I18n.tr("Anonymous Identity (optional)")
enabled: root.shouldBeVisible
@@ -540,13 +539,10 @@ DModal {
DTextField {
id: domainMatchInput
visible: showDomainField
outlined: true
controlHeight: Theme.fieldHeightLarge
expressive: true
leftIconName: "domain"
width: parent.width
font.pixelSize: Theme.fontSizeMedium
textColor: Theme.surfaceText
text: wifiDomainInput
labelText: I18n.tr("Domain (optional)")
enabled: root.shouldBeVisible
@@ -174,6 +174,8 @@ CcListRow {
DTextField {
id: usernameField
width: parent.width
expressive: true
leftIconName: "person"
placeholderText: I18n.tr("Username")
text: (root.configData && (root.configData.username || (root.configData.data && root.configData.data.username))) || ""
}
@@ -181,10 +183,12 @@ CcListRow {
DTextField {
id: passwordField
width: parent.width
expressive: true
leftIconName: "lock"
placeholderText: I18n.tr("Password")
echoMode: TextInput.Password
showPasswordToggle: true
normalBorderColor: root.hasError ? Theme.error : Theme.outlineMedium
isError: root.hasError
}
DButton {
+84 -244
View File
@@ -353,7 +353,6 @@ Item {
GreeterState.username = "";
GreeterState.usernameInput = "";
GreeterState.selectedUserIndex = -1;
inputField.text = "";
root.applyPickerPreviewTheme();
Qt.callLater(() => inputField.forceActiveFocus());
}
@@ -365,7 +364,6 @@ Item {
root.userListOpen = true;
GreeterState.username = "";
GreeterState.usernameInput = "";
inputField.text = "";
root.applyPickerPreviewTheme();
}
@@ -387,7 +385,7 @@ Item {
Greetd.cancelSession();
const previousUser = GreeterState.username;
GreeterState.reset();
inputField.text = "";
inputField.passwordVisible = false;
PortalService.profileImage = "";
if (previousUser)
root.pickerThemeUsername = previousUser;
@@ -438,7 +436,6 @@ Item {
// Some PAM stacks expect an explicit empty response to advance U2F/fprint or fail normally.
Greetd.respond(GreeterState.passwordBuffer || "");
GreeterState.passwordBuffer = "";
inputField.text = "";
return true;
}
@@ -911,16 +908,14 @@ Item {
}
Rectangle {
property bool showPassword: false
Layout.fillWidth: true
Layout.preferredHeight: root.showUserPicker && root.userListOpen ? Math.max(60, userPicker.implicitHeight + Theme.spacingM * 2) : 60
Layout.preferredHeight: root.userListOpen ? Math.max(Theme.buttonHeightM, userPicker.implicitHeight + Theme.spacingM * 2) : Theme.buttonHeightM
visible: root.showUserPicker
clip: true
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.cardSurface, 0.9)
border.color: inputField.activeFocus ? Theme.primary : Theme.outlineMedium
border.width: inputField.activeFocus ? Theme.outlineWidthFocused : Theme.layerOutlineWidth
radius: Theme.cornerRadiusXL
color: Theme.surfaceContainerHigh
border.color: Theme.outlineMedium
border.width: Theme.layerOutlineWidth
GreeterUserPicker {
id: userPicker
@@ -931,7 +926,6 @@ Item {
anchors.top: root.userListOpen ? parent.top : undefined
anchors.margins: Theme.spacingM
maxExpandedHeight: root.userPickerMaxHeight
visible: root.showUserPicker && !GreeterState.showPasswordInput
expanded: root.userListOpen
autoLoginVisible: root.autoLoginAvailable
autoLoginChecked: root.autoLoginOnSuccess
@@ -942,248 +936,95 @@ Item {
onManualEntryRequested: root.enterManualUsernameEntry()
}
DIcon {
id: lockIcon
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
name: GreeterState.showPasswordInput ? "lock" : "person"
size: 20
color: inputField.activeFocus ? Theme.primary : Theme.surfaceVariantText
visible: !root.showUserPicker
Behavior on Layout.preferredHeight {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.standardEasing
}
}
}
TextInput {
id: inputField
DTextField {
id: inputField
property bool syncingFromState: false
readonly property bool inputIdle: (Greetd.state === GreetdState.Inactive || root.awaitingExternalAuth || root.pendingPasswordResponse) && !GreeterState.unlocking
anchors.fill: parent
anchors.leftMargin: lockIcon.width + Theme.spacingM * 2
anchors.rightMargin: {
let margin = Theme.spacingM;
if (GreeterState.showPasswordInput && revealButton.visible) {
margin += revealButton.width;
}
if (externalAuthButton.visible) {
margin += externalAuthButton.width;
}
if (virtualKeyboardButton.visible) {
margin += virtualKeyboardButton.width;
}
if (enterButton.visible) {
margin += enterButton.width + 2;
}
return margin;
}
enabled: !root.showUserPicker || GreeterState.showPasswordInput
opacity: 0
focus: !root.showUserPicker || GreeterState.showPasswordInput
echoMode: GreeterState.showPasswordInput ? (parent.showPassword ? TextInput.Normal : TextInput.Password) : TextInput.Normal
onTextChanged: {
if (syncingFromState)
return;
if (GreeterState.showPasswordInput) {
GreeterState.passwordBuffer = text;
if (!text || text.length === 0)
root.passwordSubmitRequested = false;
} else {
GreeterState.usernameInput = text;
}
}
onAccepted: {
if (GreeterState.showPasswordInput) {
root.startAuthSession(true);
} else {
if (text.trim()) {
root.submitUsername(text);
syncingFromState = true;
text = "";
syncingFromState = false;
}
}
Layout.fillWidth: true
visible: !root.showUserPicker
focus: visible
expressive: true
morph: GreeterState.showPasswordInput
busy: !inputIdle
isError: GreeterState.pamState !== ""
leftIconName: GreeterState.showPasswordInput ? "lock" : "person"
labelText: {
if (GreeterState.unlocking)
return I18n.tr("Logging in...");
if (!inputIdle)
return I18n.tr("Authenticating...");
if (GreeterState.showPasswordInput)
return I18n.tr("Password...");
return I18n.tr("Username...");
}
placeholderColor: inputIdle ? Theme.outline : Theme.primary
showPasswordToggle: GreeterState.showPasswordInput && inputIdle
echoMode: GreeterState.showPasswordInput ? TextInput.Password : TextInput.Normal
showAcceptButton: inputIdle
acceptButtonName: I18n.tr("Login")
text: GreeterState.showPasswordInput ? GreeterState.passwordBuffer : GreeterState.usernameInput
trailingContent: Row {
DActionButton {
iconName: root.greeterPamHasFprint ? "fingerprint" : "key"
tooltipText: root.greeterPamHasFprint ? I18n.tr("Fingerprint") : I18n.tr("Security key")
buttonSize: inputField.accessorySize
visible: GreeterState.showPasswordInput && root.greeterExternalAuthAvailable && GreeterState.passwordBuffer.length === 0 && inputField.inputIdle
onClicked: root.startAuthSession(false)
}
Component.onCompleted: {
syncingFromState = true;
text = GreeterState.showPasswordInput ? GreeterState.passwordBuffer : GreeterState.usernameInput;
syncingFromState = false;
if (isPrimaryScreen && !powerMenu.isVisible)
forceActiveFocus();
DActionButton {
iconName: "keyboard"
Accessible.name: I18n.tr("Keyboard")
buttonSize: inputField.accessorySize
visible: inputField.inputIdle
onClicked: {
if (keyboard_controller.isKeyboardActive)
keyboard_controller.hide();
else
keyboard_controller.show();
}
}
onVisibleChanged: {
if (visible && isPrimaryScreen && !powerMenu.isVisible)
forceActiveFocus();
}
onTextEdited: {
if (!GreeterState.showPasswordInput) {
GreeterState.usernameInput = text;
return;
}
GreeterState.passwordBuffer = text;
if (text.length === 0)
root.passwordSubmitRequested = false;
}
onAccepted: {
if (GreeterState.showPasswordInput) {
root.startAuthSession(true);
return;
}
if (text.trim())
root.submitUsername(text);
}
Component.onCompleted: {
if (isPrimaryScreen && !powerMenu.isVisible)
forceActiveFocus();
}
onVisibleChanged: {
if (visible && isPrimaryScreen && !powerMenu.isVisible)
forceActiveFocus();
}
KeyboardController {
id: keyboard_controller
target: inputField
rootObject: root
}
StyledText {
id: placeholder
anchors.left: lockIcon.right
anchors.leftMargin: Theme.spacingM
anchors.right: (GreeterState.showPasswordInput && revealButton.visible ? revealButton.left : (externalAuthButton.visible ? externalAuthButton.left : (virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : parent.right))))
anchors.rightMargin: 2
anchors.verticalCenter: parent.verticalCenter
text: {
if (GreeterState.unlocking) {
return I18n.tr("Logging in...");
}
if (Greetd.state !== GreetdState.Inactive && !awaitingExternalAuth && !pendingPasswordResponse) {
return I18n.tr("Authenticating...");
}
if (GreeterState.showPasswordInput) {
return I18n.tr("Password...");
}
if (root.showUserPicker) {
return "";
}
return I18n.tr("Username...");
}
color: (GreeterState.unlocking || (Greetd.state !== GreetdState.Inactive && !awaitingExternalAuth && !pendingPasswordResponse)) ? Theme.primary : Theme.outline
font.pixelSize: Theme.fontSizeMedium
opacity: (GreeterState.showPasswordInput ? GreeterState.passwordBuffer.length === 0 : (root.showUserPicker ? false : GreeterState.usernameInput.length === 0)) ? 1 : 0
Behavior on opacity {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.standardEasing
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
StyledText {
anchors.left: lockIcon.right
anchors.leftMargin: Theme.spacingM
anchors.right: (GreeterState.showPasswordInput && revealButton.visible ? revealButton.left : (externalAuthButton.visible ? externalAuthButton.left : (virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : parent.right))))
anchors.rightMargin: 2
anchors.verticalCenter: parent.verticalCenter
text: {
if (GreeterState.showPasswordInput) {
if (parent.showPassword) {
return GreeterState.passwordBuffer;
}
return "•".repeat(GreeterState.passwordBuffer.length);
}
return GreeterState.usernameInput;
}
color: Theme.surfaceText
font.pixelSize: (GreeterState.showPasswordInput && !parent.showPassword) ? Theme.fontSizeLarge : Theme.fontSizeMedium
opacity: (GreeterState.showPasswordInput ? GreeterState.passwordBuffer.length > 0 : (root.showUserPicker ? false : GreeterState.usernameInput.length > 0)) ? 1 : 0
clip: true
elide: Text.ElideNone
horizontalAlignment: implicitWidth > width ? Text.AlignRight : Text.AlignLeft
Behavior on opacity {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.standardEasing
}
}
}
DActionButton {
id: revealButton
anchors.right: externalAuthButton.visible ? externalAuthButton.left : (virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : parent.right))
anchors.rightMargin: 0
anchors.verticalCenter: parent.verticalCenter
iconName: parent.showPassword ? "visibility_off" : "visibility"
Accessible.name: parent.showPassword ? I18n.tr("Hide password") : I18n.tr("Show password")
buttonSize: 32
visible: GreeterState.showPasswordInput && GreeterState.passwordBuffer.length > 0 && (Greetd.state === GreetdState.Inactive || awaitingExternalAuth || pendingPasswordResponse) && !GreeterState.unlocking
enabled: visible
onClicked: parent.showPassword = !parent.showPassword
}
DActionButton {
id: externalAuthButton
anchors.right: virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : parent.right)
anchors.rightMargin: 0
anchors.verticalCenter: parent.verticalCenter
iconName: root.greeterPamHasFprint ? "fingerprint" : "key"
tooltipText: root.greeterPamHasFprint ? I18n.tr("Fingerprint") : I18n.tr("Security key")
buttonSize: 32
visible: GreeterState.showPasswordInput && root.greeterExternalAuthAvailable && GreeterState.passwordBuffer.length === 0 && (Greetd.state === GreetdState.Inactive || awaitingExternalAuth || pendingPasswordResponse) && !GreeterState.unlocking
enabled: visible
onClicked: root.startAuthSession(false)
}
DActionButton {
id: virtualKeyboardButton
anchors.right: enterButton.visible ? enterButton.left : parent.right
anchors.rightMargin: enterButton.visible ? 0 : Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
iconName: "keyboard"
Accessible.name: I18n.tr("Keyboard")
buttonSize: 32
visible: (Greetd.state === GreetdState.Inactive || awaitingExternalAuth || pendingPasswordResponse) && !GreeterState.unlocking && (!root.showUserPicker || GreeterState.showPasswordInput)
enabled: visible
onClicked: {
if (keyboard_controller.isKeyboardActive) {
keyboard_controller.hide();
} else {
keyboard_controller.show();
}
}
}
DActionButton {
id: enterButton
anchors.right: parent.right
anchors.rightMargin: 2
anchors.verticalCenter: parent.verticalCenter
iconName: "keyboard_return"
Accessible.name: I18n.tr("Login")
buttonSize: 36
visible: (Greetd.state === GreetdState.Inactive || awaitingExternalAuth || pendingPasswordResponse) && !GreeterState.unlocking && (!root.showUserPicker || GreeterState.showPasswordInput)
enabled: true
onClicked: {
if (GreeterState.showPasswordInput) {
root.startAuthSession(true);
} else {
if (inputField.text.trim()) {
root.submitUsername(inputField.text);
inputField.text = "";
}
}
}
Behavior on opacity {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
Behavior on border.color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Behavior on Layout.preferredHeight {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.standardEasing
}
expressive: true
}
}
}
@@ -1569,8 +1410,8 @@ Item {
passwordFailureCount = passwordFailureCount + 1;
}
authFeedbackMessage = currentAuthMessage();
inputField.shake();
GreeterState.passwordBuffer = "";
inputField.text = "";
placeholderDelay.restart();
Greetd.cancelSession();
}
@@ -1585,8 +1426,8 @@ Item {
GreeterState.unlocking = false;
GreeterState.pamState = "error";
authFeedbackMessage = currentAuthMessage();
inputField.shake();
GreeterState.passwordBuffer = "";
inputField.text = "";
placeholderDelay.restart();
Greetd.cancelSession();
}
@@ -1622,7 +1463,6 @@ Item {
GreeterState.pamState = "error";
authFeedbackMessage = currentAuthMessage();
GreeterState.passwordBuffer = "";
inputField.text = "";
placeholderDelay.restart();
Greetd.cancelSession();
}
@@ -72,7 +72,7 @@ Item {
DTextField {
id: usernameField
outlined: true
expressive: true
leftIconName: "person"
labelText: I18n.tr("Username")
width: parent.width
@@ -113,7 +113,7 @@ Item {
DTextField {
id: passwordField
outlined: true
expressive: true
leftIconName: "lock"
labelText: I18n.tr("Password")
width: parent.width
@@ -131,7 +131,7 @@ Item {
DTextField {
id: confirmField
outlined: true
expressive: true
leftIconName: "lock"
labelText: I18n.tr("Confirm password")
width: parent.width
@@ -852,7 +852,7 @@ Item {
SettingsRow {
visible: hotspotCard.showForm
body: DTextField {
outlined: true
expressive: true
width: parent.width
labelText: I18n.tr("Hotspot name", "hotspot SSID field label")
placeholderText: I18n.tr("SSID", "hotspot network name placeholder")
@@ -867,7 +867,7 @@ Item {
SettingsRow {
visible: hotspotCard.showForm
body: DTextField {
outlined: true
expressive: true
width: parent.width
labelText: I18n.tr("Password", "hotspot password field label")
placeholderText: I18n.tr("Optional; leave blank for open hotspot", "hotspot password field placeholder")