You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -9,20 +9,25 @@ A development environment for building and customizing SDDM login themes for KDE
9
9
## Commands
10
10
11
11
### Live preview (primary development loop)
12
+
12
13
```sh
13
14
./scripts/preview.sh # preview the default theme
14
15
./scripts/preview.sh -theme <name># preview a specific theme
15
-
qml6 preview/Preview.qml # one-shot preview (no file watching)
16
16
```
17
17
18
+
The preview requires the Python host (`preview-host.py`) — there is no standalone `qml6` launch path. The host injects mock SDDM context properties and provides hot-reload via file watching.
19
+
18
20
### Lint (static analysis + runtime type check)
21
+
19
22
```sh
20
23
./scripts/lint-qml.sh # lint all themes
21
24
./scripts/lint-qml.sh -theme <name># lint a specific theme
22
25
```
23
-
CI runs this on every push/PR. It runs `qmllint` for static analysis, then launches the preview under `xvfb` for 3 seconds to catch runtime type errors (`Unable to assign`, `ReferenceError`, `TypeError`).
26
+
27
+
CI runs this on every push/PR. It runs `qmllint` for static analysis, then launches the preview host under `xvfb` for 3 seconds to catch runtime type errors (`Unable to assign`, `ReferenceError`, `TypeError`).
24
28
25
29
### Full-fidelity SDDM test
30
+
26
31
```sh
27
32
./scripts/test-sddm.sh # test default theme with real SDDM greeter
28
33
./scripts/test-sddm.sh -theme <name>
@@ -31,29 +36,294 @@ CI runs this on every push/PR. It runs `qmllint` for static analysis, then launc
31
36
## Architecture
32
37
33
38
### Theme structure
39
+
34
40
Themes live under `themes/<name>/`. The default theme is at `themes/default/`.
assets/ # background image and other static assets
55
+
background.jpg
56
+
faces/ # user avatar images (optional)
57
+
```
58
+
59
+
Any number of themes can coexist in `themes/` and be previewed interchangeably.
42
60
43
61
### Preview harness
44
-
`preview/Preview.qml` mirrors `Main.qml` layout but replaces SDDM context properties with mock objects (`mockSddm`, `mockConfig`, `mockUserModel`, `mockSessionModel`, `mockKeyboard`). The preview script symlinks the selected theme's `components/` and `assets/` into `preview/` so relative QML imports resolve correctly.
62
+
63
+
The preview system is designed so themes need zero awareness of the preview environment. The real SDDM greeter and the preview host both inject the same five context properties, so `Main.qml` works identically in both.
64
+
65
+
**How it works:**
66
+
67
+
1.`preview.sh` symlinks the selected theme's `Main.qml`, `components/`, and `assets/` into `preview/` so QML relative imports resolve correctly.
68
+
2.`preview-host.py` (PyQt6) creates mock objects for all five SDDM context properties and registers them via `engine.rootContext().setContextProperty()` — the same mechanism SDDM uses.
69
+
3.`Preview.qml` is a thin window shell with a `Loader` that loads `Main.qml`. It also binds `config.screenWidth`/`config.screenHeight` to the window dimensions and provides the hot-reload overlay badge.
70
+
4. The `HotReloader` watches theme files and triggers a 3-phase reload: unload Loader -> clear component cache -> reload Loader.
71
+
72
+
**Key design principle:** No theme-specific layout logic exists outside `themes/`. The `preview/` directory contains only the generic harness. Adding a new theme requires no changes to preview infrastructure.
45
73
46
74
In preview mode, use password `test` for successful login; any other password triggers "Login Failed".
47
75
48
76
### SDDM context properties
49
-
SDDM injects five globals that themes depend on: `sddm`, `config`, `userModel`, `sessionModel`, `keyboard`. Components receive these as explicit properties rather than accessing globals directly, making them portable between Main.qml (real SDDM) and Preview.qml (mocks).
77
+
78
+
SDDM injects five globals that themes depend on:
79
+
80
+
| Property | Type | Description |
81
+
|----------|------|-------------|
82
+
|`sddm`| QObject | Proxy with `login()`, `powerOff()`, `reboot()`, `suspend()` and capability booleans (`canPowerOff`, `canReboot`, `canSuspend`) |
All scripts set `QT_QUICK_CONTROLS_STYLE=Basic` to avoid Breeze/Plasma-specific errors outside a full Plasma session.
106
+
107
+
All scripts set `QT_QUICK_CONTROLS_STYLE=Basic` to avoid Breeze/Plasma-specific errors outside a full Plasma session. Without this, ComboBox and other controls fail with `TypeError: Cannot read property 'Overlay' of undefined`.
108
+
109
+
## Key Files
110
+
111
+
### Theme files (per theme)
112
+
113
+
-`themes/<name>/Main.qml` — Root layout: background, clock, login form, power bar, footer. Wires SDDM context properties to component props.
-`themes/<name>/components/LoginForm.qml` — Password field + login icon button in a horizontal Row. No username field; uses `defaultUsername` from `userModel.lastUser`.
116
+
-`themes/<name>/components/PowerBar.qml` — Suspend/Reboot/Shut Down buttons using unicode icons. Inline `component PowerButton` definition.
117
+
-`themes/<name>/components/Clock.qml` — Time + date display with configurable formats via Qt `timeFormat`/`dateFormat` strings.
118
+
-`themes/<name>/components/SessionSelector.qml` — ComboBox for desktop session selection, bound to `sessionModel`.
119
+
120
+
### Preview infrastructure
121
+
122
+
-`preview/Preview.qml` — Thin window shell. Loader + hot-reload wiring + overlay badge. No mock objects or theme layout logic.
123
+
-`scripts/preview-host.py` — PyQt6 host. Defines `MockSddm`, `MockConfig`, `MockUserModel`, `MockSessionModel`, `MockKeyboard`. Reads `theme.conf` for config values. Provides `HotReloader` for live preview.
124
+
-`scripts/preview.sh` — Sets up symlinks (`Main.qml`, `components/`, `assets/` into `preview/`), launches `preview-host.py` with the theme directory as argument.
125
+
-`scripts/lint-qml.sh` — Static + runtime lint. Uses `qmllint` then launches `preview-host.py` under `xvfb` to catch runtime type errors.
126
+
127
+
## QML / Qt Quick Guidelines
128
+
129
+
### Imports available
130
+
131
+
```qml
132
+
import QtQuick // core types: Item, Rectangle, Text, Image, etc.
import Qt5Compat.GraphicalEffects // DropShadow, InnerShadow, etc. (Qt 6 compat module)
136
+
```
137
+
138
+
**Never import:**`org.kde.plasma.*`, `org.kde.breeze.components`, `Kirigami`, `PlasmaExtras`, or any KDE-specific modules. These break outside a full Plasma session.
139
+
140
+
### Applying graphical effects (DropShadow, etc.)
141
+
142
+
Use `layer.enabled` + `layer.effect` to apply effects inline without restructuring the component tree or hiding the source:
143
+
144
+
```qml
145
+
TextField {
146
+
layer.enabled: true
147
+
layer.effect: DropShadow {
148
+
horizontalOffset: 0
149
+
verticalOffset: 2
150
+
radius: 12.0
151
+
samples: 25 // recommended: 1 + radius * 2
152
+
color: "#40000000"
153
+
transparentBorder: true
154
+
}
155
+
}
156
+
```
157
+
158
+
The alternative (separate `DropShadow` item with `visible: false` on the source) works but requires restructuring sibling elements.
159
+
160
+
### Inset / recessed field effect
161
+
162
+
Simulate an inset border using two nested Rectangles inside a background `Item`:
163
+
164
+
```qml
165
+
background: Item {
166
+
Rectangle {
167
+
anchors.fill: parent
168
+
radius: 8
169
+
color: "transparent"
170
+
border.color: Qt.rgba(0, 0, 0, 0.5) // dark outer ring = shadow edge
171
+
border.width: 1
172
+
}
173
+
Rectangle {
174
+
anchors.fill: parent
175
+
anchors.margins: 1
176
+
radius: 7
177
+
color: "#1e293b" // dark slate fill
178
+
border.color: Qt.rgba(255, 255, 255, 0.06) // subtle light rim
179
+
border.width: 1
180
+
}
181
+
}
182
+
```
183
+
184
+
### Component.onCompleted
185
+
186
+
**Critical:** A QML component can only have ONE `Component.onCompleted` handler. If you define two, QML reports "Property value set multiple times" and the component silently fails to load. Merge all initialization into a single block:
Keys.onEnterPressed: doLogin() // numpad Enter — always handle both
218
+
```
219
+
220
+
### Common QML patterns in this codebase
221
+
222
+
-`renderType: Text.NativeRendering` on all Text elements for crisp rendering at login screen resolution
223
+
-`Behavior on color { ColorAnimation { duration: 120 } }` for smooth hover transitions
224
+
- Components expose signals (e.g., `signal loginRequest(string username, string password)`) rather than calling SDDM directly
225
+
- Inline component definitions via `component Name: Item { ... }` (used in PowerBar.qml)
226
+
227
+
## PyQt6 / Preview Host Guidelines
228
+
229
+
### Object lifetime (garbage collection)
230
+
231
+
When registering Python QObjects as QML context properties, keep Python references alive for the process lifetime. Without this, Python's GC frees the objects while QML still holds pointers, causing `null` access errors:
232
+
233
+
```python
234
+
# WRONG — objects are GC'd immediately after setContextProperty returns
235
+
ctx.setContextProperty("sddm", MockSddm())
236
+
237
+
# CORRECT — dict keeps references alive
238
+
mocks = {
239
+
"sddm": MockSddm(),
240
+
"config": MockConfig(theme_dir),
241
+
}
242
+
for name, obj in mocks.items():
243
+
ctx.setContextProperty(name, obj)
244
+
```
245
+
246
+
### configparser key casing
247
+
248
+
Python's `configparser` lowercases all keys by default. `primaryColor` in theme.conf becomes `primarycolor`. Map explicitly:
returnself._get("primarycolor") # configparser stored it lowercase
254
+
```
255
+
256
+
### File paths for QML
257
+
258
+
QML `Image.source` expects either a relative path (resolved from the QML file's location) or a `file:///` URL. When providing paths from Python, always use absolute file URLs:
SDDM models expose data via named roles. Python mocks must define `roleNames()` returning a dict of `{role_int: b"name"}` and handle those roles in `data()`:
- SDDM theme API: the SDDM wiki on GitHub documents what context properties are injected and what roles the models expose. Search for `sddm theme api` or `sddm theme development`.
328
+
- Qt Quick Controls styling: the `Basic` style is Qt's unstyled fallback. Search `Qt Quick Controls styles Basic`.
329
+
-`eos-breeze-sddm` (GitHub: `endeavouros-team/eos-breeze-sddm`): reference SDDM theme this project draws design inspiration from.
0 commit comments