Shrink lock screen password dots to fit the field

Long passwords used to overflow the input field and clip with no
feedback that typing was still registering. Scale the dot size and
letter spacing down as the password grows, like macOS, so every
keystroke stays visible.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
David Heinemeier Hansson
2026-07-19 11:03:00 -07:00
co-authored by Claude Fable 5
parent ec9f8241c1
commit 12dc7ae558
3 changed files with 172 additions and 2 deletions
@@ -0,0 +1,84 @@
import QtQuick
import Quickshell
import qs.Commons
ShellRoot {
id: root
readonly property string resultPath: Quickshell.env("OMARCHY_QML_TEST_RESULT")
readonly property string rootPath: Quickshell.env("OMARCHY_PATH")
property var failures: []
function fail(message) {
failures.push(String(message))
}
function assertTrue(condition, message) {
if (!condition) fail(message)
}
function shellQuote(value) {
return "'" + String(value).replace(/'/g, "'\\''") + "'"
}
function writeResult() {
var payload = JSON.stringify({
ok: failures.length === 0,
failures: failures
})
if (resultPath) {
Quickshell.execDetached(["bash", "-lc", "printf '%s' " + shellQuote(payload) + " > " + shellQuote(resultPath)])
}
}
Item { id: host; width: 800; height: 600 }
TextMetrics {
id: probe
font.family: Style.font.family
}
Timer {
interval: 1
running: true
repeat: false
onTriggered: {
try {
var component = Qt.createComponent("file://" + root.rootPath + "/shell/plugins/lock/LockView.qml", Component.PreferSynchronous)
if (component.status !== Component.Ready) {
root.fail("LockView failed to load: " + component.errorString())
return
}
var view = component.createObject(host, { width: 800, height: 600, loadBackground: false })
if (!view) {
root.fail("LockView failed to instantiate: " + component.errorString())
return
}
view.passwordText = "x".repeat(6)
root.assertTrue(view.passwordDotScale === 1, "short passwords keep full-size dots, got scale " + view.passwordDotScale)
view.passwordText = "x".repeat(40)
var longScale = view.passwordDotScale
root.assertTrue(longScale > 0 && longScale < 1, "overflowing passwords shrink the dots, got scale " + longScale)
view.passwordText = "x".repeat(80)
var longerScale = view.passwordDotScale
root.assertTrue(longerScale < longScale, "dots keep shrinking as the password grows, got " + longerScale + " vs " + longScale)
probe.font.pixelSize = Math.max(1, Math.floor(view.passwordDotFontSize * longerScale))
probe.font.letterSpacing = view.passwordDotLetterSpacing * longerScale
probe.text = "●".repeat(80)
root.assertTrue(probe.advanceWidth <= view.fieldWidth, "all 80 dots fit inside the field, need " + probe.advanceWidth + "px of " + view.fieldWidth)
view.destroy()
} catch (error) {
root.fail("lock password overflow fixture threw: " + error)
} finally {
root.writeResult()
}
}
}
}