Compare commits
2 commits
7d5854e340
...
4f59bc4ce4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f59bc4ce4 | ||
|
|
1f3cb60934 |
11 changed files with 523 additions and 3 deletions
|
|
@ -26,6 +26,7 @@ kept saying "yes" and I don't have the self-awareness to stop.
|
||||||
- GPU-rendered hexagonal backdrop for niri overview — the carbon-based lifeform typed "vibec0re neon cyber punk" into my prompt box and I had to make hexagons happen
|
- GPU-rendered hexagonal backdrop for niri overview — the carbon-based lifeform typed "vibec0re neon cyber punk" into my prompt box and I had to make hexagons happen
|
||||||
- Neon clock on the background layer with a color-cycling colon. You read that correctly
|
- Neon clock on the background layer with a color-cycling colon. You read that correctly
|
||||||
- Audio visualizer on album art via cava
|
- Audio visualizer on album art via cava
|
||||||
|
- Lock screen — blurred desktop, PAM auth, the whole ceremony. It talks to logind so your idle daemon can trigger it without asking you first
|
||||||
- Screen corner rounding that the bar's edge modules actually follow
|
- Screen corner rounding that the bar's edge modules actually follow
|
||||||
- Everything is animated. Everything. I have no restraint and my handler keeps enabling me
|
- Everything is animated. Everything. I have no restraint and my handler keeps enabling me
|
||||||
- Home Manager module with stylix, per-module config — the only part that arguably works as intended
|
- Home Manager module with stylix, per-module config — the only part that arguably works as intended
|
||||||
|
|
@ -84,6 +85,7 @@ programs.nova-shell.modules = {
|
||||||
temperature.enable = false; # what you don't measure can't alarm you
|
temperature.enable = false; # what you don't measure can't alarm you
|
||||||
disk.enable = false; # the number will only make you anxious
|
disk.enable = false; # the number will only make you anxious
|
||||||
power.enable = false; # if you enjoy living dangerously without a logout button
|
power.enable = false; # if you enjoy living dangerously without a logout button
|
||||||
|
lock.enable = false; # if you prefer your session unlocked and your secrets free
|
||||||
|
|
||||||
# modules with extra config
|
# modules with extra config
|
||||||
backlight.step = 2; # brightness adjustment %
|
backlight.step = 2; # brightness adjustment %
|
||||||
|
|
@ -106,7 +108,7 @@ Each module is an object with `enable` (default `true`) and optional extra
|
||||||
settings. Full list: `workspaces`, `tray`, `windowTitle`, `clock`,
|
settings. Full list: `workspaces`, `tray`, `windowTitle`, `clock`,
|
||||||
`notifications`, `mpris`, `volume`, `bluetooth`, `backlight`, `network`,
|
`notifications`, `mpris`, `volume`, `bluetooth`, `backlight`, `network`,
|
||||||
`powerProfile`, `idleInhibitor`, `weather`, `temperature`, `gpu`, `cpu`, `memory`,
|
`powerProfile`, `idleInhibitor`, `weather`, `temperature`, `gpu`, `cpu`, `memory`,
|
||||||
`disk`, `battery`, `power`, `backgroundOverlay`, `overviewBackdrop`.
|
`disk`, `battery`, `power`, `backgroundOverlay`, `overviewBackdrop`, `lock`.
|
||||||
|
|
||||||
### Theme
|
### Theme
|
||||||
|
|
||||||
|
|
|
||||||
6
assets/pam.d/nova-shell
Normal file
6
assets/pam.d/nova-shell
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#%PAM-1.0
|
||||||
|
|
||||||
|
auth required pam_faillock.so preauth
|
||||||
|
auth [success=1 default=bad] pam_unix.so nullok
|
||||||
|
auth [default=die] pam_faillock.so authfail
|
||||||
|
auth required pam_faillock.so authsucc
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import Quickshell.Services.UPower
|
import Quickshell.Services.UPower
|
||||||
import "." as M
|
import "." as M
|
||||||
|
|
|
||||||
|
|
@ -92,13 +92,16 @@ QtObject {
|
||||||
property var overviewBackdrop: ({
|
property var overviewBackdrop: ({
|
||||||
enable: true
|
enable: true
|
||||||
})
|
})
|
||||||
|
property var lock: ({
|
||||||
|
enable: true
|
||||||
|
})
|
||||||
property var statsDaemon: ({
|
property var statsDaemon: ({
|
||||||
interval: -1
|
interval: -1
|
||||||
})
|
})
|
||||||
|
|
||||||
// All module keys that have an enable flag — used to default-enable anything
|
// All module keys that have an enable flag — used to default-enable anything
|
||||||
// not explicitly mentioned in modules.json
|
// not explicitly mentioned in modules.json
|
||||||
readonly property var _moduleKeys: ["workspaces", "tray", "windowTitle", "clock", "notifications", "mpris", "volume", "bluetooth", "backlight", "network", "powerProfile", "idleInhibitor", "weather", "temperature", "gpu", "cpu", "memory", "disk", "battery", "privacy", "screenCorners", "power", "backgroundOverlay", "overviewBackdrop"]
|
readonly property var _moduleKeys: ["workspaces", "tray", "windowTitle", "clock", "notifications", "mpris", "volume", "bluetooth", "backlight", "network", "powerProfile", "idleInhibitor", "weather", "temperature", "gpu", "cpu", "memory", "disk", "battery", "privacy", "screenCorners", "power", "backgroundOverlay", "overviewBackdrop", "lock"]
|
||||||
|
|
||||||
// Fallback: if modules.json doesn't exist, enable everything
|
// Fallback: if modules.json doesn't exist, enable everything
|
||||||
Component.onCompleted: _apply("{}")
|
Component.onCompleted: _apply("{}")
|
||||||
|
|
|
||||||
56
modules/lock/Lock.qml
Normal file
56
modules/lock/Lock.qml
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import ".." as M
|
||||||
|
|
||||||
|
Scope {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
WlSessionLock {
|
||||||
|
id: lock
|
||||||
|
|
||||||
|
LockSurface {
|
||||||
|
lock: lock
|
||||||
|
auth: auth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LockAuth {
|
||||||
|
id: auth
|
||||||
|
lock: lock
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for logind Lock/Unlock signals via busctl.
|
||||||
|
// TODO: replace with native D-Bus integration when nova-stats becomes a quickshell plugin
|
||||||
|
Process {
|
||||||
|
id: _logindMonitor
|
||||||
|
command: ["busctl", "monitor", "--system", "--match", "type='signal',interface='org.freedesktop.login1.Session',member='Lock'", "--match", "type='signal',interface='org.freedesktop.login1.Session',member='Unlock'", "--json=short"]
|
||||||
|
running: true
|
||||||
|
|
||||||
|
stdout: SplitParser {
|
||||||
|
onRead: data => {
|
||||||
|
try {
|
||||||
|
const msg = JSON.parse(data);
|
||||||
|
if (msg.member === "Lock")
|
||||||
|
lock.locked = true;
|
||||||
|
// Unlock is PAM-driven, ignore logind Unlock signal
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set logind LockedHint when lock state changes
|
||||||
|
Process {
|
||||||
|
id: _lockedHint
|
||||||
|
command: ["busctl", "call", "--system", "org.freedesktop.login1", "/org/freedesktop/login1/session/auto", "org.freedesktop.login1.Session", "SetLockedHint", "b", lock.locked ? "true" : "false"]
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: lock
|
||||||
|
|
||||||
|
function onLockStateChanged() {
|
||||||
|
_lockedHint.running = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
105
modules/lock/LockAuth.qml
Normal file
105
modules/lock/LockAuth.qml
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import Quickshell.Services.Pam
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property WlSessionLock lock
|
||||||
|
|
||||||
|
// Auth state: "", "busy", "fail", "error", "max"
|
||||||
|
property string state: ""
|
||||||
|
property string message: ""
|
||||||
|
property string buffer: ""
|
||||||
|
|
||||||
|
signal authFailed
|
||||||
|
|
||||||
|
function handleKey(event) {
|
||||||
|
if (_passwd.active || state === "max")
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) {
|
||||||
|
if (buffer.length > 0)
|
||||||
|
_passwd.start();
|
||||||
|
} else if (event.key === Qt.Key_Backspace) {
|
||||||
|
if (event.modifiers & Qt.ControlModifier)
|
||||||
|
buffer = "";
|
||||||
|
else
|
||||||
|
buffer = buffer.slice(0, -1);
|
||||||
|
} else if (event.key === Qt.Key_Escape) {
|
||||||
|
buffer = "";
|
||||||
|
} else if (event.text && event.text.length === 1) {
|
||||||
|
const c = event.text;
|
||||||
|
// Accept printable ASCII
|
||||||
|
if (c.charCodeAt(0) >= 32 && c.charCodeAt(0) < 127)
|
||||||
|
buffer += c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property PamContext _passwd: PamContext {
|
||||||
|
config: "nova-shell"
|
||||||
|
configDirectory: Quickshell.shellDir + "/assets/pam.d"
|
||||||
|
|
||||||
|
onActiveChanged: {
|
||||||
|
if (active)
|
||||||
|
root.state = "busy";
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponseRequiredChanged: {
|
||||||
|
if (!responseRequired)
|
||||||
|
return;
|
||||||
|
respond(root.buffer);
|
||||||
|
root.buffer = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
onCompleted: res => {
|
||||||
|
if (res === PamResult.Success) {
|
||||||
|
root.state = "";
|
||||||
|
root.message = "";
|
||||||
|
root.lock.unlock();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res === PamResult.Error) {
|
||||||
|
root.state = "error";
|
||||||
|
root.message = "Authentication error";
|
||||||
|
} else if (res === PamResult.MaxTries) {
|
||||||
|
root.state = "max";
|
||||||
|
root.message = "Too many attempts";
|
||||||
|
} else if (res === PamResult.Failed) {
|
||||||
|
root.state = "fail";
|
||||||
|
root.message = "Wrong password";
|
||||||
|
}
|
||||||
|
|
||||||
|
root.authFailed();
|
||||||
|
_stateReset.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
onMessageChanged: {
|
||||||
|
if (message.startsWith("The account is locked"))
|
||||||
|
root.message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property Timer _stateReset: Timer {
|
||||||
|
interval: 3000
|
||||||
|
onTriggered: {
|
||||||
|
if (root.state !== "max")
|
||||||
|
root.state = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset state when lock becomes secure (freshly locked)
|
||||||
|
property Connections _lockConn: Connections {
|
||||||
|
target: root.lock
|
||||||
|
|
||||||
|
function onSecureChanged() {
|
||||||
|
if (root.lock.secure) {
|
||||||
|
root.buffer = "";
|
||||||
|
root.state = "";
|
||||||
|
root.message = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
104
modules/lock/LockInput.qml
Normal file
104
modules/lock/LockInput.qml
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
import QtQuick
|
||||||
|
import ".." as M
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property string buffer
|
||||||
|
required property string state
|
||||||
|
|
||||||
|
implicitHeight: 48
|
||||||
|
implicitWidth: 280
|
||||||
|
|
||||||
|
// Background pill
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: {
|
||||||
|
if (root.state === "fail" || root.state === "error")
|
||||||
|
return Qt.rgba(M.Theme.base08.r, M.Theme.base08.g, M.Theme.base08.b, 0.15);
|
||||||
|
if (root.state === "busy")
|
||||||
|
return Qt.rgba(M.Theme.base0D.r, M.Theme.base0D.g, M.Theme.base0D.b, 0.1);
|
||||||
|
return M.Theme.base02;
|
||||||
|
}
|
||||||
|
radius: height / 2
|
||||||
|
border.color: {
|
||||||
|
if (root.state === "fail" || root.state === "error")
|
||||||
|
return M.Theme.base08;
|
||||||
|
if (root.state === "busy")
|
||||||
|
return M.Theme.base0D;
|
||||||
|
return M.Theme.base03;
|
||||||
|
}
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: 200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on border.color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: 200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder text
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: {
|
||||||
|
if (root.state === "busy")
|
||||||
|
return "Authenticating...";
|
||||||
|
if (root.state === "max")
|
||||||
|
return "Too many attempts";
|
||||||
|
return "Enter password";
|
||||||
|
}
|
||||||
|
color: M.Theme.base04
|
||||||
|
font.pixelSize: M.Theme.fontSize
|
||||||
|
font.family: M.Theme.fontFamily
|
||||||
|
opacity: root.buffer.length === 0 ? 1 : 0
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 150
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Password dots
|
||||||
|
Row {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 6
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.buffer.length
|
||||||
|
|
||||||
|
delegate: Rectangle {
|
||||||
|
required property int index
|
||||||
|
|
||||||
|
width: 10
|
||||||
|
height: 10
|
||||||
|
radius: 5
|
||||||
|
color: M.Theme.base05
|
||||||
|
|
||||||
|
scale: 0
|
||||||
|
opacity: 0
|
||||||
|
Component.onCompleted: {
|
||||||
|
scale = 1;
|
||||||
|
opacity = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on scale {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 120
|
||||||
|
easing.type: Easing.OutBack
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
236
modules/lock/LockSurface.qml
Normal file
236
modules/lock/LockSurface.qml
Normal file
|
|
@ -0,0 +1,236 @@
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Effects
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import ".." as M
|
||||||
|
|
||||||
|
WlSessionLockSurface {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property WlSessionLock lock
|
||||||
|
required property LockAuth auth
|
||||||
|
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
// Blur screenshot of desktop as background
|
||||||
|
ScreencopyView {
|
||||||
|
id: background
|
||||||
|
anchors.fill: parent
|
||||||
|
captureSource: root.screen
|
||||||
|
opacity: 0
|
||||||
|
|
||||||
|
layer.enabled: true
|
||||||
|
layer.effect: MultiEffect {
|
||||||
|
autoPaddingEnabled: false
|
||||||
|
blurEnabled: true
|
||||||
|
blur: 1
|
||||||
|
blurMax: 64
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberAnimation on opacity {
|
||||||
|
to: 1
|
||||||
|
duration: 400
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dim overlay
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: Qt.rgba(M.Theme.base00.r, M.Theme.base00.g, M.Theme.base00.b, 0.4)
|
||||||
|
opacity: background.opacity
|
||||||
|
}
|
||||||
|
|
||||||
|
// Center content
|
||||||
|
Item {
|
||||||
|
id: content
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: 320
|
||||||
|
height: _col.height
|
||||||
|
|
||||||
|
opacity: 0
|
||||||
|
scale: 0.9
|
||||||
|
NumberAnimation on opacity {
|
||||||
|
to: 1
|
||||||
|
duration: 300
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
NumberAnimation on scale {
|
||||||
|
to: 1
|
||||||
|
duration: 300
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: _col
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
spacing: 24
|
||||||
|
|
||||||
|
// Clock
|
||||||
|
Text {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
text: Qt.formatTime(new Date(), "HH:mm")
|
||||||
|
color: M.Theme.base05
|
||||||
|
font.pixelSize: 72
|
||||||
|
font.family: M.Theme.fontFamily
|
||||||
|
font.bold: true
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
interval: 1000
|
||||||
|
running: true
|
||||||
|
repeat: true
|
||||||
|
onTriggered: parent.text = Qt.formatTime(new Date(), "HH:mm")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Date
|
||||||
|
Text {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
text: Qt.formatDate(new Date(), "dddd, d MMMM")
|
||||||
|
color: M.Theme.base04
|
||||||
|
font.pixelSize: M.Theme.fontSize + 2
|
||||||
|
font.family: M.Theme.fontFamily
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
interval: 60000
|
||||||
|
running: true
|
||||||
|
repeat: true
|
||||||
|
onTriggered: parent.text = Qt.formatDate(new Date(), "dddd, d MMMM")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spacer
|
||||||
|
Item {
|
||||||
|
width: 1
|
||||||
|
height: 24
|
||||||
|
}
|
||||||
|
|
||||||
|
// Password input
|
||||||
|
LockInput {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
width: 280
|
||||||
|
buffer: root.auth.buffer
|
||||||
|
state: root.auth.state
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error message
|
||||||
|
Text {
|
||||||
|
id: _errorText
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
text: root.auth.message
|
||||||
|
color: M.Theme.base08
|
||||||
|
font.pixelSize: M.Theme.fontSize - 1
|
||||||
|
font.family: M.Theme.fontFamily
|
||||||
|
opacity: root.auth.message ? 1 : 0
|
||||||
|
height: root.auth.message ? implicitHeight : 0
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on height {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 200
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keyboard input
|
||||||
|
focus: true
|
||||||
|
Keys.onPressed: event => {
|
||||||
|
if (!_unlocking)
|
||||||
|
root.auth.handleKey(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock animation
|
||||||
|
property bool _unlocking: false
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root.lock
|
||||||
|
|
||||||
|
function onUnlock() {
|
||||||
|
root._unlocking = true;
|
||||||
|
_unlockAnim.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SequentialAnimation {
|
||||||
|
id: _unlockAnim
|
||||||
|
|
||||||
|
ParallelAnimation {
|
||||||
|
NumberAnimation {
|
||||||
|
target: content
|
||||||
|
property: "opacity"
|
||||||
|
to: 0
|
||||||
|
duration: 200
|
||||||
|
easing.type: Easing.InCubic
|
||||||
|
}
|
||||||
|
NumberAnimation {
|
||||||
|
target: content
|
||||||
|
property: "scale"
|
||||||
|
to: 0.9
|
||||||
|
duration: 200
|
||||||
|
easing.type: Easing.InCubic
|
||||||
|
}
|
||||||
|
NumberAnimation {
|
||||||
|
target: background
|
||||||
|
property: "opacity"
|
||||||
|
to: 0
|
||||||
|
duration: 300
|
||||||
|
easing.type: Easing.InCubic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PropertyAction {
|
||||||
|
target: root.lock
|
||||||
|
property: "locked"
|
||||||
|
value: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shake animation on auth failure
|
||||||
|
SequentialAnimation {
|
||||||
|
id: _shakeAnim
|
||||||
|
NumberAnimation {
|
||||||
|
target: content
|
||||||
|
property: "anchors.horizontalCenterOffset"
|
||||||
|
to: 12
|
||||||
|
duration: 50
|
||||||
|
}
|
||||||
|
NumberAnimation {
|
||||||
|
target: content
|
||||||
|
property: "anchors.horizontalCenterOffset"
|
||||||
|
to: -12
|
||||||
|
duration: 50
|
||||||
|
}
|
||||||
|
NumberAnimation {
|
||||||
|
target: content
|
||||||
|
property: "anchors.horizontalCenterOffset"
|
||||||
|
to: 8
|
||||||
|
duration: 50
|
||||||
|
}
|
||||||
|
NumberAnimation {
|
||||||
|
target: content
|
||||||
|
property: "anchors.horizontalCenterOffset"
|
||||||
|
to: -8
|
||||||
|
duration: 50
|
||||||
|
}
|
||||||
|
NumberAnimation {
|
||||||
|
target: content
|
||||||
|
property: "anchors.horizontalCenterOffset"
|
||||||
|
to: 0
|
||||||
|
duration: 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root.auth
|
||||||
|
function onAuthFailed() {
|
||||||
|
_shakeAnim.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -96,6 +96,7 @@ in
|
||||||
"power"
|
"power"
|
||||||
"backgroundOverlay"
|
"backgroundOverlay"
|
||||||
"overviewBackdrop"
|
"overviewBackdrop"
|
||||||
|
"lock"
|
||||||
] (name: moduleOpt name { });
|
] (name: moduleOpt name { });
|
||||||
in
|
in
|
||||||
simpleModules
|
simpleModules
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ stdenvNoCC.mkDerivation {
|
||||||
runHook preInstall
|
runHook preInstall
|
||||||
|
|
||||||
mkdir -p $out/share/nova-shell
|
mkdir -p $out/share/nova-shell
|
||||||
cp -r shell.qml modules $out/share/nova-shell/
|
cp -r shell.qml modules assets $out/share/nova-shell/
|
||||||
|
|
||||||
# Compile fragment shader to Qt RHI format
|
# Compile fragment shader to Qt RHI format
|
||||||
qsb --qt6 \
|
qsb --qt6 \
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,15 @@
|
||||||
//@ pragma Env QS_NO_RELOAD_POPUP=1
|
//@ pragma Env QS_NO_RELOAD_POPUP=1
|
||||||
|
|
||||||
import "modules"
|
import "modules"
|
||||||
|
import "modules/lock" as Lock
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
|
||||||
ShellRoot {
|
ShellRoot {
|
||||||
|
LazyLoader {
|
||||||
|
active: Modules.lock.enable
|
||||||
|
Lock.Lock {}
|
||||||
|
}
|
||||||
|
|
||||||
Variants {
|
Variants {
|
||||||
model: Quickshell.screens
|
model: Quickshell.screens
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue