diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-17 13:26:07 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-17 13:26:07 +0200 |
| commit | 96841b1cef108130bcf1ab21c34cf89b92f1b35c (patch) | |
| tree | 29ecfe725930f6e27e72574d7a8d43172370da7b | |
| parent | 8f749d8b1fe5c2740d8829a78fbfbb3c739dade9 (diff) | |
| download | sddm-theme-udt-96841b1cef108130bcf1ab21c34cf89b92f1b35c.tar.gz sddm-theme-udt-96841b1cef108130bcf1ab21c34cf89b92f1b35c.zip | |
fix: pin the login card per SDDM screen window
| -rw-r--r-- | README.md | 4 | ||||
| -rw-r--r-- | theme/Main.qml | 118 | ||||
| -rw-r--r-- | theme/components/LoginCard.qml | 9 | ||||
| -rw-r--r-- | theme/theme.conf | 4 |
4 files changed, 71 insertions, 64 deletions
@@ -12,6 +12,10 @@ 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 +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. + ## Install The theme code is copied by root. UDT's `install.sh` prints the block; run it in diff --git a/theme/Main.qml b/theme/Main.qml index e5c529f..a813d6c 100644 --- a/theme/Main.qml +++ b/theme/Main.qml @@ -1,9 +1,11 @@ // SPDX-License-Identifier: GPL-2.0-only // Copyright (C) 2026 Danilo M. <danix@danix.xyz> // -// UDT SDDM greeter. The root item spans every screen. The wallpaper is drawn -// once per screen; the login card is pinned to one screen (config uiScreen, -// else the primary screen); input wakes the card after it fades. +// 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 +// after it fades. import QtQuick import "components" @@ -11,81 +13,68 @@ import "components" Item { id: root - // SDDM sizes the greeter to the union of every screen. - readonly property var union: screenModel.geometry(-1) - width: union.width - height: union.height + // SDDM creates one window per screen and sizes it to that screen. The + // model holds exactly one row and geometry(index) ignores index, returning + // this window's own screen at origin (0,0). + readonly property var windowGeometry: screenModel.geometry(0) + width: windowGeometry.width + height: windowGeometry.height // Palette. Values come from theme.conf, overridden live by theme.conf.user. readonly property string baseColor: config.stringValue("base") - readonly property int cardRadius: 10 readonly property string wallpaperUrl: config.stringValue("background") === "" ? "" : "file://" + config.stringValue("background") + // The screen the card 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") - property var primaryGeometry: screenModel.geometry(screenModel.primary >= 0 ? screenModel.primary : 0) - property var namedGeometry: primaryGeometry - readonly property var cardGeometry: wantedScreen === "" ? primaryGeometry : namedGeometry + property string thisScreenName: "" + readonly property bool cardScreen: wantedScreen === "" + ? primaryScreen + : (thisScreenName === wantedScreen) + + // The wallpaper fills this window's single screen. The frosted card + // samples this image. + Image { + id: wallpaperImage + anchors.fill: parent + source: root.wallpaperUrl + fillMode: Image.PreserveAspectCrop + sourceSize.width: width + sourceSize.height: height + smooth: true + asynchronous: true + } - // The Image delegate of the screen the card sits on. The frosted card - // samples it. Null until the model has populated. - property Item cardScreenImage: null + // Scrim so the card and text read on any wallpaper. + Rectangle { + anchors.fill: parent + color: root.baseColor + opacity: 0.45 + } + // Non-visual: reads the model's single row to learn this screen's name. Repeater { - id: screens model: screenModel delegate: Item { - x: geometry.x - y: geometry.y - width: geometry.width - height: geometry.height - - Image { - id: screenImage - anchors.fill: parent - source: root.wallpaperUrl - fillMode: Image.PreserveAspectCrop - sourceSize.width: parent.width - sourceSize.height: parent.height - smooth: true - asynchronous: true - } - - // Scrim so the card and text read on any wallpaper. - Rectangle { - anchors.fill: parent - color: root.baseColor - opacity: 0.45 - } - - readonly property string screenName: name - - Component.onCompleted: root.registerScreen(index, screenName, screenImage, geometry) + visible: false + Component.onCompleted: root.thisScreenName = name } } - // Filled as the model populates: the primary screen's name, and the - // wallpaper image plus geometry of whichever screen owns the card. - property string primaryName: "" - - function registerScreen(index, screenName, image, geom) { - if (index === screenModel.primary) - root.primaryName = screenName - if (screenName === root.cardScreenName()) { - root.cardScreenImage = image - root.namedGeometry = geom - } - } - - function cardScreenName() { - return root.wantedScreen !== "" ? root.wantedScreen : root.primaryName - } - // Idle: fade the card 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. + onUiVisibleChanged: { + if (!uiVisible) + root.forceActiveFocus() + } + Timer { id: idle interval: config.intValue("fadeoutMs") > 0 ? config.intValue("fadeoutMs") : 60000 @@ -95,7 +84,10 @@ Item { } function wake() { - root.uiVisible = true + if (!root.uiVisible) { + root.uiVisible = true + card.restoreFocus() + } idle.restart() } @@ -113,12 +105,12 @@ Item { LoginCard { id: card - x: root.cardGeometry.x + (root.cardGeometry.width - width) / 2 + card.shakeOffset - y: root.cardGeometry.y + (root.cardGeometry.height - height) / 2 - backdropSource: root.cardScreenImage - visible: opacity > 0 + anchors.centerIn: parent + visible: root.cardScreen + enabled: root.uiVisible opacity: root.uiVisible ? 1 : 0 Behavior on opacity { NumberAnimation { duration: 260; easing.type: Easing.OutCubic } } + backdropSource: wallpaperImage onWake: root.wake() } } diff --git a/theme/components/LoginCard.qml b/theme/components/LoginCard.qml index 5f336d0..d76269f 100644 --- a/theme/components/LoginCard.qml +++ b/theme/components/LoginCard.qml @@ -239,6 +239,15 @@ Item { sddm.login(usernameField.text, passwordField.text, sessionBox.currentIndex) } + // Return keyboard focus after the idle fade disabled the card: password + // when a username is already filled, else the username field. + function restoreFocus() { + if (usernameField.text.length > 0) + passwordField.forceActiveFocus() + else + usernameField.forceActiveFocus() + } + // Login failure wins the notice line; otherwise Caps Lock, if on. property string loginError: "" readonly property string message: { diff --git a/theme/theme.conf b/theme/theme.conf index c2b8269..b5af812 100644 --- a/theme/theme.conf +++ b/theme/theme.conf @@ -26,7 +26,9 @@ blue=#8aadf4 lavender=#b7bdf8 accent=#b7bdf8 -# Empty uiScreen pins the card to the primary screen. +# Empty uiScreen pins the card 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= # 60000 matches the stock theme's idle timeout. fadeoutMs=60000 |
