plugin: rust-side modules + theme services with serde-typed config

This commit is contained in:
Damocles 2026-05-04 22:58:12 +02:00
parent a86e90e927
commit f34f3f2f4e
95 changed files with 2477 additions and 1011 deletions

View file

@ -1,5 +1,6 @@
import QtQuick
import "../services" as S
import NovaStats as NS
Item {
id: root
@ -15,7 +16,7 @@ Item {
property bool _revealed: false
on_RawProgressChanged: if (_rawProgress >= 1)
_revealed = true
readonly property real _rawProgress: S.Theme.reducedMotion ? 1 : Math.max(0, Math.min(1, wavePhase / 300))
readonly property real _rawProgress: S.ThemeUtil.reducedMotion ? 1 : Math.max(0, Math.min(1, wavePhase / 300))
readonly property real _progress: (_revealed ? 1 : _rawProgress) * unlockFade
opacity: _progress
property real _slideX: (1 - _progress) * -80
@ -53,32 +54,32 @@ Item {
Text {
text: _clockRow._hours
color: S.Theme.base0C
color: NS.ThemeService.base0C
font.pixelSize: root._fontSize
font.family: S.Theme.fontFamily
font.family: NS.ThemeService.fontFamily
font.bold: true
}
Text {
text: ":"
color: S.Theme.base0E
color: NS.ThemeService.base0E
font.pixelSize: root._fontSize
font.family: S.Theme.fontFamily
font.family: NS.ThemeService.fontFamily
font.bold: true
}
Text {
text: _clockRow._minutes
color: S.Theme.base09
color: NS.ThemeService.base09
font.pixelSize: root._fontSize
font.family: S.Theme.fontFamily
font.family: NS.ThemeService.fontFamily
font.bold: true
}
}
Text {
text: Qt.formatDate(new Date(), "dddd, d MMMM")
color: S.Theme.base04
font.pixelSize: S.Theme.fontSize + 2
font.family: S.Theme.fontFamily
color: NS.ThemeService.base04
font.pixelSize: NS.ThemeService.fontSize + 2
font.family: NS.ThemeService.fontFamily
Timer {
interval: 60000

View file

@ -1,5 +1,6 @@
import QtQuick
import "../services" as S
import NovaStats as NS
Item {
id: root
@ -15,18 +16,18 @@ Item {
anchors.fill: parent
color: {
if (root.state === "fail" || root.state === "error")
return Qt.rgba(S.Theme.base08.r, S.Theme.base08.g, S.Theme.base08.b, 0.15);
return Qt.rgba(NS.ThemeService.base08.r, NS.ThemeService.base08.g, NS.ThemeService.base08.b, 0.15);
if (root.state === "busy")
return Qt.rgba(S.Theme.base0D.r, S.Theme.base0D.g, S.Theme.base0D.b, 0.1);
return S.Theme.base02;
return Qt.rgba(NS.ThemeService.base0D.r, NS.ThemeService.base0D.g, NS.ThemeService.base0D.b, 0.1);
return NS.ThemeService.base02;
}
radius: height / 2
border.color: {
if (root.state === "fail" || root.state === "error")
return S.Theme.base08;
return NS.ThemeService.base08;
if (root.state === "busy")
return S.Theme.base0D;
return S.Theme.base03;
return NS.ThemeService.base0D;
return NS.ThemeService.base03;
}
border.width: 1
@ -52,9 +53,9 @@ Item {
return "Too many attempts";
return "Enter password";
}
color: S.Theme.base04
font.pixelSize: S.Theme.fontSize
font.family: S.Theme.fontFamily
color: NS.ThemeService.base04
font.pixelSize: NS.ThemeService.fontSize
font.family: NS.ThemeService.fontFamily
opacity: root.buffer.length === 0 ? 1 : 0
Behavior on opacity {
@ -80,7 +81,7 @@ Item {
width: 10
height: 10
radius: 5
color: S.Theme.base05
color: NS.ThemeService.base05
scale: 0
opacity: 0

View file

@ -1,11 +1,12 @@
import QtQuick
import "../services" as S
import NovaStats as NS
Row {
id: root
spacing: 6
visible: (S.Modules.lock.notifications ?? true) && _notifGroups.length > 0
visible: (NS.ModulesService.lockNotifications ?? true) && _notifGroups.length > 0
readonly property var _notifGroups: {
const notifs = S.NotifService.list.filter(n => n.state !== "dismissed");
@ -34,8 +35,8 @@ Row {
width: _pillRow.implicitWidth + 12
height: 24
radius: 12
color: Qt.rgba(S.Theme.base01.r, S.Theme.base01.g, S.Theme.base01.b, 0.7)
border.color: Qt.rgba(S.Theme.base03.r, S.Theme.base03.g, S.Theme.base03.b, 0.3)
color: Qt.rgba(NS.ThemeService.base01.r, NS.ThemeService.base01.g, NS.ThemeService.base01.b, 0.7)
border.color: Qt.rgba(NS.ThemeService.base03.r, NS.ThemeService.base03.g, NS.ThemeService.base03.b, 0.3)
border.width: 1
HoverHandler {
@ -48,9 +49,9 @@ Row {
anchors.bottom: parent.top
anchors.bottomMargin: 4
text: _pill.modelData.name || ""
color: S.Theme.base04
font.pixelSize: S.Theme.fontSize - 2
font.family: S.Theme.fontFamily
color: NS.ThemeService.base04
font.pixelSize: NS.ThemeService.fontSize - 2
font.family: NS.ThemeService.fontFamily
visible: _pillHover.hovered && text !== ""
}
@ -71,9 +72,9 @@ Row {
Text {
anchors.verticalCenter: parent.verticalCenter
text: _pill.modelData.count > 1 ? _pill.modelData.count.toString() : ""
color: S.Theme.base04
font.pixelSize: S.Theme.fontSize - 2
font.family: S.Theme.fontFamily
color: NS.ThemeService.base04
font.pixelSize: NS.ThemeService.fontSize - 2
font.family: NS.ThemeService.fontFamily
visible: _pill.modelData.count > 1
}
}

View file

@ -4,6 +4,7 @@ import Quickshell
import Quickshell.Wayland
import "../services" as S
import "../applets" as C
import NovaStats as NS
WlSessionLockSurface {
id: root
@ -11,7 +12,7 @@ WlSessionLockSurface {
required property WlSessionLock lock
required property LockAuth auth
color: S.Theme.base00
color: NS.ThemeService.base00
// Keyboard input via TextInput - engages Qt's full input pipeline including
// text-input protocol, which is more reliable than Keys on a plain Item in
@ -47,7 +48,7 @@ WlSessionLockSurface {
// Threat level: eased curve so fails 1-2 are subtle, 3+ ramps up.
// Max ~0.6 at fail 5 (previously that was fail 3).
readonly property bool _threatEnabled: S.Modules.lock.threatEffect ?? true
readonly property bool _threatEnabled: NS.ModulesService.lockThreatEffect ?? true
readonly property real _threat: {
if (!_threatEnabled || root.auth.failCount <= 0)
return 0;
@ -68,7 +69,7 @@ WlSessionLockSurface {
layer.effect: ShaderEffect {
property real uThreat: root._threat
property real uPulse: root._heartbeat
property color uColor: S.Theme.base08
property color uColor: NS.ThemeService.base08
fragmentShader: Quickshell.shellPath("modules/lock_threat.frag.qsb")
}
@ -77,7 +78,7 @@ WlSessionLockSurface {
// compositor fallback color (niri red) from bleeding through.
Rectangle {
anchors.fill: parent
color: S.Theme.base00
color: NS.ThemeService.base00
}
// Clear desktop screenshot from ScreenshotService - visible immediately.
@ -86,7 +87,7 @@ WlSessionLockSurface {
Image {
anchors.fill: parent
source: S.ScreenshotService.get(root.screen?.name ?? "")
visible: (S.Modules.lock.screenshot ?? true) && source !== "" && !S.Theme.reducedMotion
visible: (NS.ModulesService.lockScreenshot ?? true) && source !== "" && !S.ThemeUtil.reducedMotion
opacity: _unlockFade
fillMode: Image.PreserveAspectCrop
}
@ -118,7 +119,7 @@ WlSessionLockSurface {
Image {
anchors.fill: parent
source: S.ScreenshotService.get(root.screen?.name ?? "")
visible: (S.Modules.lock.screenshot ?? true) && source !== "" && !S.Theme.reducedMotion
visible: (NS.ModulesService.lockScreenshot ?? true) && source !== "" && !S.ThemeUtil.reducedMotion
fillMode: Image.PreserveAspectCrop
layer.enabled: true
@ -186,9 +187,9 @@ WlSessionLockSurface {
anchors.top: _lockInput.bottom
anchors.topMargin: 16
text: root.auth.message
color: S.Theme.base08
font.pixelSize: S.Theme.fontSize - 1
font.family: S.Theme.fontFamily
color: NS.ThemeService.base08
font.pixelSize: NS.ThemeService.fontSize - 1
font.family: NS.ThemeService.fontFamily
opacity: root.auth.message ? 1 : 0
Behavior on opacity {
@ -206,10 +207,10 @@ WlSessionLockSurface {
anchors.horizontalCenter: parent.horizontalCenter
visible: root.auth.failCount > 0
text: root.auth.failCount + (root.auth.failCount === 1 ? " failed attempt" : " failed attempts")
color: S.Theme.base08
color: NS.ThemeService.base08
opacity: Math.max(0.4, root._threat)
font.pixelSize: S.Theme.fontSize - 2
font.family: S.Theme.fontFamily
font.pixelSize: NS.ThemeService.fontSize - 2
font.family: NS.ThemeService.fontFamily
}
// Right column - widgets, fly in when wave exits screen
@ -261,7 +262,7 @@ WlSessionLockSurface {
interval: 30000
running: _keyInput.text.length > 0 && root.auth.state !== "busy"
onTriggered: {
if (!S.Theme.reducedMotion)
if (!S.ThemeUtil.reducedMotion)
_shakeAnim.restart();
_keyInput.text = "";
}
@ -371,7 +372,7 @@ WlSessionLockSurface {
SequentialAnimation {
id: _heartbeatAnim
loops: Animation.Infinite
running: root._threatEnabled && root.auth.failCount >= 3 && root.lock.secure && !S.Theme.reducedMotion
running: root._threatEnabled && root.auth.failCount >= 3 && root.lock.secure && !S.ThemeUtil.reducedMotion
// Systole (sharp spike)
NumberAnimation {
@ -417,8 +418,8 @@ WlSessionLockSurface {
// so the bar's ScreenCorners aren't visible. Draw our own.
component LockCorner: Canvas {
property int corner: 0
readonly property int _r: S.Theme.screenRadius
visible: _r > 0 && S.Modules.screenCorners.enable
readonly property int _r: NS.ThemeService.screenRadius
visible: _r > 0 && NS.ModulesService.screenCornersEnable
width: _r
height: _r
z: 999

View file

@ -1,6 +1,7 @@
import QtQuick
import "../services" as S
import "../applets" as C
import NovaStats as NS
Item {
id: root
@ -16,7 +17,7 @@ Item {
property bool _revealed: false
on_RawProgressChanged: if (_rawProgress >= 1)
_revealed = true
readonly property real _rawProgress: S.Theme.reducedMotion ? 1 : (screenWidth > 0 ? Math.max(0, Math.min(1, (wavePhase - screenWidth) / 500)) : 0)
readonly property real _rawProgress: S.ThemeUtil.reducedMotion ? 1 : (screenWidth > 0 ? Math.max(0, Math.min(1, (wavePhase - screenWidth) / 500)) : 0)
readonly property real _progress: (_revealed ? 1 : _rawProgress) * unlockFade
opacity: _progress
property real _slideX: (1 - _progress) * 80
@ -38,11 +39,11 @@ Item {
id: _weatherCard
width: parent.width
height: _weatherContent.implicitHeight + 16
radius: S.Theme.radius + 2
color: Qt.rgba(S.Theme.base01.r, S.Theme.base01.g, S.Theme.base01.b, 0.7)
border.color: Qt.rgba(S.Theme.base03.r, S.Theme.base03.g, S.Theme.base03.b, 0.3)
radius: NS.ThemeService.radius + 2
color: Qt.rgba(NS.ThemeService.base01.r, NS.ThemeService.base01.g, NS.ThemeService.base01.b, 0.7)
border.color: Qt.rgba(NS.ThemeService.base03.r, NS.ThemeService.base03.g, NS.ThemeService.base03.b, 0.3)
border.width: 1
visible: (S.Modules.lock.weather ?? true) && S.WeatherService.available
visible: (NS.ModulesService.lockWeather ?? true) && S.WeatherService.available
C.WeatherApplet {
id: _weatherContent
@ -50,7 +51,7 @@ Item {
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: 8
accentColor: S.Theme.base0C
accentColor: NS.ThemeService.base0C
}
}
@ -65,11 +66,11 @@ Item {
id: _mprisCard
width: parent.width
height: _mprisContent.implicitHeight + 16
radius: S.Theme.radius + 2
color: Qt.rgba(S.Theme.base01.r, S.Theme.base01.g, S.Theme.base01.b, 0.7)
border.color: Qt.rgba(S.Theme.base03.r, S.Theme.base03.g, S.Theme.base03.b, 0.3)
radius: NS.ThemeService.radius + 2
color: Qt.rgba(NS.ThemeService.base01.r, NS.ThemeService.base01.g, NS.ThemeService.base01.b, 0.7)
border.color: Qt.rgba(NS.ThemeService.base03.r, NS.ThemeService.base03.g, NS.ThemeService.base03.b, 0.3)
border.width: 1
visible: (S.Modules.lock.mpris ?? true) && S.MprisService.player !== null
visible: (NS.ModulesService.lockMpris ?? true) && S.MprisService.player !== null
C.MprisApplet {
id: _mprisContent
@ -77,7 +78,7 @@ Item {
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: 8
accentColor: S.Theme.base0D
accentColor: NS.ThemeService.base0D
}
}
@ -86,11 +87,11 @@ Item {
id: _volumeCard
width: parent.width
height: _volumeContent.implicitHeight + 16
radius: S.Theme.radius + 2
color: Qt.rgba(S.Theme.base01.r, S.Theme.base01.g, S.Theme.base01.b, 0.7)
border.color: Qt.rgba(S.Theme.base03.r, S.Theme.base03.g, S.Theme.base03.b, 0.3)
radius: NS.ThemeService.radius + 2
color: Qt.rgba(NS.ThemeService.base01.r, NS.ThemeService.base01.g, NS.ThemeService.base01.b, 0.7)
border.color: Qt.rgba(NS.ThemeService.base03.r, NS.ThemeService.base03.g, NS.ThemeService.base03.b, 0.3)
border.width: 1
visible: (S.Modules.lock.volume ?? true) && S.PipewireService.sink !== null
visible: (NS.ModulesService.lockVolume ?? true) && S.PipewireService.sink !== null
C.VolumeApplet {
id: _volumeContent
@ -98,7 +99,7 @@ Item {
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: 8
accentColor: S.Theme.base0E
accentColor: NS.ThemeService.base0E
}
}
@ -107,9 +108,9 @@ Item {
id: _backlightCard
width: parent.width
height: _backlightContent.implicitHeight + 8
radius: S.Theme.radius + 2
color: Qt.rgba(S.Theme.base01.r, S.Theme.base01.g, S.Theme.base01.b, 0.7)
border.color: Qt.rgba(S.Theme.base03.r, S.Theme.base03.g, S.Theme.base03.b, 0.3)
radius: NS.ThemeService.radius + 2
color: Qt.rgba(NS.ThemeService.base01.r, NS.ThemeService.base01.g, NS.ThemeService.base01.b, 0.7)
border.color: Qt.rgba(NS.ThemeService.base03.r, NS.ThemeService.base03.g, NS.ThemeService.base03.b, 0.3)
border.width: 1
visible: S.BacklightService.available
@ -120,7 +121,7 @@ Item {
anchors.top: parent.top
anchors.topMargin: 4
percent: S.BacklightService.percent
accentColor: S.Theme.base0A
accentColor: NS.ThemeService.base0A
onSetPercent: pct => S.BacklightService.setPercent(pct)
}
}