aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-17 14:37:07 +0200
committerDanilo M. <danix@danix.xyz>2026-09-17 14:37:07 +0200
commit27ef16187a672acb96e8d930e0ab4bf893ad8f6a (patch)
tree9ce5a7b3c697bf267db0569101b22dca2283ffe9
parent8ead58fb5baa8d781159cf443c0f7ffd68cb6775 (diff)
downloadsddm-theme-udt-27ef16187a672acb96e8d930e0ab4bf893ad8f6a.tar.gz
sddm-theme-udt-27ef16187a672acb96e8d930e0ab4bf893ad8f6a.zip
fix: quote dateFormat and gate hidden credential field focus
Quote the comma-bearing dateFormat default; QSettings reads an unquoted comma as a QStringList and SDDM's stringValue() then returns the empty string, leaving the value inert. Note the caveat in the README Panel section. Set activeFocusOnTab per credential stage so Tab cannot reach the offscreen, transparent field and silently append to the submitted value. forceActiveFocus() ignores activeFocusOnTab, so focusStage() still lands focus in each stage. Apply the remaining review fixes: compare the PowerBar fallback as a string, animate the back chevron width, give both opacity behaviours an OutCubic easing, gate the clock timer on panel visibility, clamp panelWidth to 1, and reword stale "card" prose to "panel".
-rw-r--r--README.md6
-rw-r--r--theme/Main.qml18
-rw-r--r--theme/components/Backdrop.qml8
-rw-r--r--theme/components/Credentials.qml7
-rw-r--r--theme/components/Panel.qml2
-rw-r--r--theme/components/PowerBar.qml2
-rw-r--r--theme/theme.conf6
7 files changed, 29 insertions, 20 deletions
diff --git a/README.md b/README.md
index 8316215..24c52c9 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ its colours from the UDT palette, including the wallpaper-snapped accent.
SDDM merges into the theme config at every greeter start. Wallpaper and accent
need no root re-run; only theme code changes do.
-Set `uiScreen` in the root-owned `theme/theme.conf` to pin the login card to a
+Set `uiScreen` in the root-owned `theme/theme.conf` to pin the login panel to a
named screen; leave it empty for the primary screen. It does not belong in the
generated `theme.conf.user`, which is overwritten on every wallpaper change.
@@ -24,6 +24,10 @@ From top to bottom it shows the clock (`timeFormat`) and date (`dateFormat`),
the `greeting`, the avatar, one credential slot, the power row, and the session
selector at the bottom.
+If you override `dateFormat`, quote any value containing a comma (for example
+`dateFormat="dddd, d MMMM"`): SDDM reads the config through QSettings, which
+treats an unquoted comma as a list, and the date then comes out empty.
+
The credential slot is staged: type the username, confirm, and it slides out to
reveal the password; confirm again to log in. Esc or the back chevron returns
to the username.
diff --git a/theme/Main.qml b/theme/Main.qml
index a5acac9..0a7ce3d 100644
--- a/theme/Main.qml
+++ b/theme/Main.qml
@@ -3,8 +3,8 @@
//
// UDT SDDM greeter. SDDM creates one window per screen, each sized to its own
// screen and carrying a one-row ScreenModel, so this root item is exactly one
-// screen. The wallpaper fills it; the login card draws only on the screen
-// named by config uiScreen, else on the primary screen. Input wakes the card
+// screen. The wallpaper fills it; the login panel draws only on the screen
+// named by config uiScreen, else on the primary screen. Input wakes the panel
// after it fades.
import QtQuick
@@ -27,7 +27,7 @@ Item {
? ""
: "file://" + config.stringValue("background")
- // The screen the card belongs on: config uiScreen if non-empty, else the
+ // The screen the panel belongs on: config uiScreen if non-empty, else the
// primary screen. `primaryScreen` is a per-window context property SDDM
// sets true only on the window showing the primary screen.
readonly property string wantedScreen: config.stringValue("uiScreen")
@@ -36,7 +36,7 @@ Item {
? primaryScreen
: (thisScreenName === wantedScreen)
- // The wallpaper fills this window's single screen. The frosted card
+ // The wallpaper fills this window's single screen. The frosted panel
// samples this image.
Image {
id: wallpaperImage
@@ -49,7 +49,7 @@ Item {
asynchronous: true
}
- // Scrim so the card and text read on any wallpaper.
+ // Scrim so the panel and text read on any wallpaper.
Rectangle {
anchors.fill: parent
color: root.baseColor
@@ -65,11 +65,11 @@ Item {
}
}
- // Idle: fade the card after fadeoutMs, restore on any input.
+ // Idle: fade the panel after fadeoutMs, restore on any input.
property bool uiVisible: true
- // While faded the card is disabled, which clears its focus; take focus on
- // the root so the first key press wakes the card instead of vanishing.
+ // While faded the panel is disabled, which clears its focus; take focus on
+ // the root so the first key press wakes the panel instead of vanishing.
onUiVisibleChanged: {
if (!uiVisible)
root.forceActiveFocus()
@@ -107,7 +107,7 @@ Item {
// key is missing or malformed.
readonly property real panelWidth: {
var v = parseFloat(config.stringValue("panelWidth"))
- return v > 0 ? v : 0.30
+ return v > 0 ? Math.min(v, 1) : 0.30
}
Panel {
diff --git a/theme/components/Backdrop.qml b/theme/components/Backdrop.qml
index c13b766..de39bc2 100644
--- a/theme/components/Backdrop.qml
+++ b/theme/components/Backdrop.qml
@@ -1,9 +1,9 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
//
-// A frosted panel. It snapshots the wallpaper image the card sits on, blurs it
-// and masks it to the card's rounded rect, then tints it for text contrast.
-// When there is no source, only the tint draws, so the card still reads.
+// A frosted panel. It snapshots the wallpaper image the panel sits on, blurs it
+// and masks it to the panel's rounded rect, then tints it for text contrast.
+// When there is no source, only the tint draws, so the panel still reads.
import QtQuick
import QtQuick.Effects
@@ -25,7 +25,7 @@ Item {
if (backdrop.sourceItem === null)
return Qt.rect(0, 0, 0, 0)
// Guard reads: mapToItem() hides its reads in C++, so the binding
- // would not otherwise re-run when the card or image moves (e.g.
+ // would not otherwise re-run when the panel or image moves (e.g.
// the failed-login shake). Reading the coordinates registers them.
var _guard = backdrop.x + backdrop.y + backdrop.parent.x + backdrop.parent.y
+ backdrop.sourceItem.x + backdrop.sourceItem.y
diff --git a/theme/components/Credentials.qml b/theme/components/Credentials.qml
index c2aba29..2bfbaf5 100644
--- a/theme/components/Credentials.qml
+++ b/theme/components/Credentials.qml
@@ -56,6 +56,7 @@ Item {
height: 44
clip: true
visible: width > 0
+ Behavior on width { NumberAnimation { duration: 220; easing.type: Easing.OutCubic } }
Rectangle {
anchors.fill: parent
@@ -122,6 +123,7 @@ Item {
height: slot.height
text: userModel.lastUser !== undefined ? userModel.lastUser : ""
placeholderText: "Username"
+ activeFocusOnTab: !creds.passwordStage
font.family: "Noto Sans"
color: config.stringValue("text")
palette.base: config.stringValue("surface0")
@@ -135,7 +137,7 @@ Item {
x: creds.passwordStage ? -slot.width : 0
opacity: creds.passwordStage ? 0 : 1
Behavior on x { NumberAnimation { duration: 220; easing.type: Easing.OutCubic } }
- Behavior on opacity { NumberAnimation { duration: 220 } }
+ Behavior on opacity { NumberAnimation { duration: 220; easing.type: Easing.OutCubic } }
onAccepted: creds.confirm()
onTextChanged: creds.wake()
Keys.onPressed: event => {
@@ -150,6 +152,7 @@ Item {
height: slot.height
echoMode: TextInput.Password
placeholderText: "Password"
+ activeFocusOnTab: creds.passwordStage
font.family: "Noto Sans"
color: config.stringValue("text")
palette.base: config.stringValue("surface0")
@@ -163,7 +166,7 @@ Item {
x: creds.passwordStage ? 0 : slot.width
opacity: creds.passwordStage ? 1 : 0
Behavior on x { NumberAnimation { duration: 220; easing.type: Easing.OutCubic } }
- Behavior on opacity { NumberAnimation { duration: 220 } }
+ Behavior on opacity { NumberAnimation { duration: 220; easing.type: Easing.OutCubic } }
onAccepted: creds.confirm()
Keys.onPressed: event => {
creds.wake()
diff --git a/theme/components/Panel.qml b/theme/components/Panel.qml
index 4df989b..eac2c58 100644
--- a/theme/components/Panel.qml
+++ b/theme/components/Panel.qml
@@ -36,7 +36,7 @@ Item {
property date now: new Date()
Timer {
interval: 1000
- running: true
+ running: panel.visible
repeat: true
onTriggered: panel.now = new Date()
}
diff --git a/theme/components/PowerBar.qml b/theme/components/PowerBar.qml
index 1ce3e76..3d6b2c5 100644
--- a/theme/components/PowerBar.qml
+++ b/theme/components/PowerBar.qml
@@ -49,7 +49,7 @@ Row {
source: modelData.icon !== "" ? modelData.icon : modelData.fallback
visible: false
onStatusChanged: {
- if (status === Image.Error && source !== modelData.fallback)
+ if (status === Image.Error && String(source) !== modelData.fallback)
source = modelData.fallback
}
}
diff --git a/theme/theme.conf b/theme/theme.conf
index 2db45a1..33e6bd3 100644
--- a/theme/theme.conf
+++ b/theme/theme.conf
@@ -26,7 +26,7 @@ blue=#8aadf4
lavender=#b7bdf8
accent=#b7bdf8
-# Empty uiScreen pins the card to the primary screen. Set a screen name here,
+# Empty uiScreen pins the panel to the primary screen. Set a screen name here,
# in this root-owned file: theme.conf.user is regenerated on every wallpaper
# change and is not the place for uiScreen.
uiScreen=
@@ -41,7 +41,9 @@ panelWidth=0.30
# Clock and greeting. Qt date/time format strings. The date follows the
# greeter's locale, which may be C.
timeFormat=HH:mm
-dateFormat=dddd, d MMMM
+# Quote any format containing a comma, here and in theme.conf.user: QSettings
+# reads an unquoted comma as a list, and SDDM's stringValue() then returns "".
+dateFormat="dddd, d MMMM"
greeting=hello there
# Power button icons. udt-accent writes absolute paths here from the icon theme