This commit is contained in:
Damocles 2026-04-12 18:44:27 +02:00
parent 21f96dc68e
commit b06e3582ff
23 changed files with 597 additions and 197 deletions

View file

@ -16,7 +16,8 @@ M.BarSection {
property bool _osdActive: false property bool _osdActive: false
readonly property bool _showPanel: root._hovered || _panelHovered || _osdActive readonly property bool _showPanel: root._hovered || _panelHovered || _osdActive
onPercentChanged: if (percent > 0) _flashPanel() onPercentChanged: if (percent > 0)
_flashPanel()
function _flashPanel() { function _flashPanel() {
_osdActive = true; _osdActive = true;
@ -56,7 +57,8 @@ M.BarSection {
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: { onStreamFinished: {
const dev = text.trim(); const dev = text.trim();
if (dev) root._blDev = "/sys/class/backlight/" + dev; if (dev)
root._blDev = "/sys/class/backlight/" + dev;
} }
} }
} }
@ -114,10 +116,7 @@ M.BarSection {
anchors.left: true anchors.left: true
margins.top: 0 margins.top: 0
margins.left: Math.max(0, Math.min( margins.left: Math.max(0, Math.min(Math.round(root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0) - implicitWidth / 2), (panel.screen?.width ?? 1920) - implicitWidth))
Math.round(root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0) - implicitWidth / 2),
(panel.screen?.width ?? 1920) - implicitWidth
))
implicitWidth: panelContent.width implicitWidth: panelContent.width
implicitHeight: panelContent.height implicitHeight: panelContent.height
@ -138,14 +137,38 @@ M.BarSection {
ParallelAnimation { ParallelAnimation {
id: showAnim id: showAnim
NumberAnimation { target: panelContent; property: "opacity"; to: 1; duration: 120; easing.type: Easing.OutCubic } NumberAnimation {
NumberAnimation { target: panelContent; property: "y"; to: 0; duration: 150; easing.type: Easing.OutCubic } target: panelContent
property: "opacity"
to: 1
duration: 120
easing.type: Easing.OutCubic
}
NumberAnimation {
target: panelContent
property: "y"
to: 0
duration: 150
easing.type: Easing.OutCubic
}
} }
ParallelAnimation { ParallelAnimation {
id: hideAnim id: hideAnim
NumberAnimation { target: panelContent; property: "opacity"; to: 0; duration: 150; easing.type: Easing.InCubic } NumberAnimation {
NumberAnimation { target: panelContent; property: "y"; to: -panelContent.height; duration: 150; easing.type: Easing.InCubic } target: panelContent
property: "opacity"
to: 0
duration: 150
easing.type: Easing.InCubic
}
NumberAnimation {
target: panelContent
property: "y"
to: -panelContent.height
duration: 150
easing.type: Easing.InCubic
}
onFinished: panel._winVisible = false onFinished: panel._winVisible = false
} }
@ -210,7 +233,11 @@ M.BarSection {
color: M.Theme.base0A color: M.Theme.base0A
radius: 3 radius: 3
Behavior on width { NumberAnimation { duration: 80 } } Behavior on width {
NumberAnimation {
duration: 80
}
}
} }
MouseArea { MouseArea {
@ -218,7 +245,10 @@ M.BarSection {
anchors.margins: -6 anchors.margins: -6
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => _set(mouse) onPressed: mouse => _set(mouse)
onPositionChanged: mouse => { if (pressed) _set(mouse); } onPositionChanged: mouse => {
if (pressed)
_set(mouse);
}
function _set(mouse) { function _set(mouse) {
root.setPercent(mouse.x / slider.width * 100); root.setPercent(mouse.x / slider.width * 100);
} }

View file

@ -34,8 +34,14 @@ PanelWindow {
height: 3 height: 3
gradient: Gradient { gradient: Gradient {
orientation: Gradient.Horizontal orientation: Gradient.Horizontal
GradientStop { position: 0; color: M.Theme.base0C } GradientStop {
GradientStop { position: 1; color: M.Theme.base09 } position: 0
color: M.Theme.base0C
}
GradientStop {
position: 1
color: M.Theme.base09
}
} }
} }
@ -53,8 +59,12 @@ PanelWindow {
M.BarGroup { M.BarGroup {
borderColor: M.Theme.base0D borderColor: M.Theme.base0D
M.Clock { visible: M.Modules.clock.enable } M.Clock {
M.Notifications { visible: M.Modules.notifications.enable } visible: M.Modules.clock.enable
}
M.Notifications {
visible: M.Modules.notifications.enable
}
} }
} }
@ -67,11 +77,17 @@ PanelWindow {
M.BarGroup { M.BarGroup {
borderColor: M.Theme.base0D borderColor: M.Theme.base0D
M.Workspaces { bar: bar; visible: M.Modules.workspaces.enable } M.Workspaces {
bar: bar
visible: M.Modules.workspaces.enable
}
} }
M.BarGroup { M.BarGroup {
borderColor: M.Theme.base0D borderColor: M.Theme.base0D
M.Tray { bar: bar; visible: M.Modules.tray.enable } M.Tray {
bar: bar
visible: M.Modules.tray.enable
}
} }
M.BarGroup { M.BarGroup {
borderColor: M.Theme.base0D borderColor: M.Theme.base0D
@ -80,7 +96,9 @@ PanelWindow {
visible: M.Modules.windowTitle.enable visible: M.Modules.windowTitle.enable
} }
} }
Item { Layout.fillWidth: true } Item {
Layout.fillWidth: true
}
} }
// ---- right ---- // ---- right ----
@ -90,45 +108,73 @@ PanelWindow {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: M.Theme.barSpacing spacing: M.Theme.barSpacing
Item { Layout.fillWidth: true } Item {
Layout.fillWidth: true
}
// Media // Media
M.BarGroup { M.BarGroup {
borderColor: M.Theme.base0E borderColor: M.Theme.base0E
M.Mpris { bar: bar } M.Mpris {
M.Volume { visible: M.Modules.volume.enable } bar: bar
}
M.Volume {
visible: M.Modules.volume.enable
}
} }
// Connectivity // Connectivity
M.BarGroup { M.BarGroup {
borderColor: M.Theme.base0D borderColor: M.Theme.base0D
M.Network { bar: bar; visible: M.Modules.network.enable } M.Network {
M.Bluetooth { bar: bar } bar: bar
visible: M.Modules.network.enable
}
M.Bluetooth {
bar: bar
}
} }
// Controls // Controls
M.BarGroup { M.BarGroup {
borderColor: M.Theme.base0A borderColor: M.Theme.base0A
M.Backlight {} M.Backlight {}
M.PowerProfile { visible: M.Modules.powerProfile.enable } M.PowerProfile {
M.IdleInhibitor { visible: M.Modules.idleInhibitor.enable } visible: M.Modules.powerProfile.enable
}
M.IdleInhibitor {
visible: M.Modules.idleInhibitor.enable
}
} }
// Stats // Stats
M.BarGroup { M.BarGroup {
borderColor: M.Theme.base08 borderColor: M.Theme.base08
M.Cpu { visible: M.Modules.cpu.enable } M.Cpu {
M.Memory { visible: M.Modules.memory.enable } visible: M.Modules.cpu.enable
M.Temperature { visible: M.Modules.temperature.enable } }
M.Weather { visible: M.Modules.weather.enable } M.Memory {
M.Disk { visible: M.Modules.disk.enable } visible: M.Modules.memory.enable
}
M.Temperature {
visible: M.Modules.temperature.enable
}
M.Weather {
visible: M.Modules.weather.enable
}
M.Disk {
visible: M.Modules.disk.enable
}
} }
// Power // Power
M.BarGroup { M.BarGroup {
borderColor: M.Theme.base08 borderColor: M.Theme.base08
M.Battery {} M.Battery {}
M.Power { bar: bar; visible: M.Modules.power.enable } M.Power {
bar: bar
visible: M.Modules.power.enable
}
} }
} }
} }

View file

@ -41,8 +41,14 @@ Item {
anchors.fill: parent anchors.fill: parent
radius: M.Theme.radius radius: M.Theme.radius
gradient: Gradient { gradient: Gradient {
GradientStop { position: 0; color: Qt.rgba(root.borderColor.r, root.borderColor.g, root.borderColor.b, 0.15) } GradientStop {
GradientStop { position: 1; color: "transparent" } position: 0
color: Qt.rgba(root.borderColor.r, root.borderColor.g, root.borderColor.b, 0.15)
}
GradientStop {
position: 1
color: "transparent"
}
} }
} }

View file

@ -21,9 +21,23 @@ Text {
SequentialAnimation { SequentialAnimation {
id: _crossfade id: _crossfade
NumberAnimation { target: root; property: "opacity"; to: 0; duration: 60; easing.type: Easing.InQuad } NumberAnimation {
ScriptAction { script: root._displayIcon = root._pendingIcon } target: root
NumberAnimation { target: root; property: "opacity"; to: 1; duration: 100; easing.type: Easing.OutQuad } property: "opacity"
to: 0
duration: 60
easing.type: Easing.InQuad
}
ScriptAction {
script: root._displayIcon = root._pendingIcon
}
NumberAnimation {
target: root
property: "opacity"
to: 1
duration: 100
easing.type: Easing.OutQuad
}
} }
color: M.Theme.base05 color: M.Theme.base05
font.pixelSize: M.Theme.fontSize + 1 font.pixelSize: M.Theme.fontSize + 1

View file

@ -8,7 +8,11 @@ Row {
property string tooltip: "" property string tooltip: ""
property bool _hovered: false property bool _hovered: false
Behavior on opacity { NumberAnimation { duration: 150 } } Behavior on opacity {
NumberAnimation {
duration: 150
}
}
layer.enabled: _hovered layer.enabled: _hovered
layer.effect: MultiEffect { layer.effect: MultiEffect {

View file

@ -28,19 +28,37 @@ M.BarSection {
SequentialAnimation { SequentialAnimation {
running: root._critical running: root._critical
loops: Animation.Infinite loops: Animation.Infinite
NumberAnimation { target: root; property: "_blinkOpacity"; to: 0.2; duration: 400; easing.type: Easing.InOutQuad } NumberAnimation {
NumberAnimation { target: root; property: "_blinkOpacity"; to: 1; duration: 400; easing.type: Easing.InOutQuad } target: root
onRunningChanged: if (!running) root._blinkOpacity = 1 property: "_blinkOpacity"
to: 0.2
duration: 400
easing.type: Easing.InOutQuad
}
NumberAnimation {
target: root
property: "_blinkOpacity"
to: 1
duration: 400
easing.type: Easing.InOutQuad
}
onRunningChanged: if (!running)
root._blinkOpacity = 1
} }
property bool _warnSent: false property bool _warnSent: false
property bool _critSent: false property bool _critSent: false
onChargingChanged: { _warnSent = false; _critSent = false; } onChargingChanged: {
_warnSent = false;
_critSent = false;
}
onPctChanged: { onPctChanged: {
if (charging) return; if (charging)
return;
if (pct < _critThresh && !_critSent) { if (pct < _critThresh && !_critSent) {
_critSent = true; _warnSent = true; _critSent = true;
_warnSent = true;
_notif.command = ["notify-send", "--urgency=critical", "--icon=battery-low", "--category=device", "Very Low Battery", "Connect to power now!"]; _notif.command = ["notify-send", "--urgency=critical", "--icon=battery-low", "--category=device", "Very Low Battery", "Connect to power now!"];
_notif.running = true; _notif.running = true;
} else if (pct < _warnThresh && !_warnSent) { } else if (pct < _warnThresh && !_warnSent) {
@ -50,7 +68,9 @@ M.BarSection {
} }
} }
Process { id: _notif } Process {
id: _notif
}
M.BarIcon { M.BarIcon {
icon: { icon: {

View file

@ -72,7 +72,10 @@ M.BarSection {
TapHandler { TapHandler {
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onTapped: { menuLoader.active = !menuLoader.active; M.FlyoutState.visible = false; } onTapped: {
menuLoader.active = !menuLoader.active;
M.FlyoutState.visible = false;
}
} }
TapHandler { TapHandler {

View file

@ -13,23 +13,18 @@ M.PopupPanel {
property Process _scanner: Process { property Process _scanner: Process {
id: scanner id: scanner
running: true running: true
command: ["sh", "-c", command: ["sh", "-c", "bluetoothctl devices Paired 2>/dev/null | while read -r _ mac name; do " + "info=$(bluetoothctl info \"$mac\" 2>/dev/null); " + "conn=$(echo \"$info\" | grep -c 'Connected: yes'); " + "bat=$(echo \"$info\" | awk -F'[(): ]' '/Battery Percentage/{for(i=1;i<=NF;i++) if($i+0==$i && $i!=\"\") print $i}'); " + "echo \"$mac:$conn:${bat:-}:$name\"; " + "done"]
"bluetoothctl devices Paired 2>/dev/null | while read -r _ mac name; do " +
"info=$(bluetoothctl info \"$mac\" 2>/dev/null); " +
"conn=$(echo \"$info\" | grep -c 'Connected: yes'); " +
"bat=$(echo \"$info\" | awk -F'[(): ]' '/Battery Percentage/{for(i=1;i<=NF;i++) if($i+0==$i && $i!=\"\") print $i}'); " +
"echo \"$mac:$conn:${bat:-}:$name\"; " +
"done"
]
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: { onStreamFinished: {
const devs = []; const devs = [];
for (const line of text.trim().split("\n")) { for (const line of text.trim().split("\n")) {
if (!line) continue; if (!line)
continue;
const i1 = line.indexOf(":"); const i1 = line.indexOf(":");
const i2 = line.indexOf(":", i1 + 1); const i2 = line.indexOf(":", i1 + 1);
const i3 = line.indexOf(":", i2 + 1); const i3 = line.indexOf(":", i2 + 1);
if (i3 < 0) continue; if (i3 < 0)
continue;
devs.push({ devs.push({
mac: line.slice(0, i1), mac: line.slice(0, i1),
connected: line.slice(i1 + 1, i2) === "1", connected: line.slice(i1 + 1, i2) === "1",
@ -38,7 +33,8 @@ M.PopupPanel {
}); });
} }
devs.sort((a, b) => { devs.sort((a, b) => {
if (a.connected !== b.connected) return a.connected ? -1 : 1; if (a.connected !== b.connected)
return a.connected ? -1 : 1;
return a.name.localeCompare(b.name); return a.name.localeCompare(b.name);
}); });
menuWindow._devices = devs; menuWindow._devices = devs;
@ -51,7 +47,8 @@ M.PopupPanel {
property string action: "" property string action: ""
property string mac: "" property string mac: ""
command: ["bluetoothctl", action, mac] command: ["bluetoothctl", action, mac]
onRunningChanged: if (!running) scanner.running = true onRunningChanged: if (!running)
scanner.running = true
} }
Repeater { Repeater {

View file

@ -33,24 +33,45 @@ PanelWindow {
anchors.left: true anchors.left: true
margins.top: 0 margins.top: 0
margins.left: Math.max(0, Math.min( margins.left: Math.max(0, Math.min(Math.round(M.FlyoutState.itemX - implicitWidth / 2), screen.width - implicitWidth))
Math.round(M.FlyoutState.itemX - implicitWidth / 2),
screen.width - implicitWidth
))
implicitWidth: label.implicitWidth + M.Theme.barPadding * 2 implicitWidth: label.implicitWidth + M.Theme.barPadding * 2
implicitHeight: label.implicitHeight + M.Theme.barPadding * 2 implicitHeight: label.implicitHeight + M.Theme.barPadding * 2
ParallelAnimation { ParallelAnimation {
id: showAnim id: showAnim
NumberAnimation { target: content; property: "opacity"; to: 1; duration: 120; easing.type: Easing.OutCubic } NumberAnimation {
NumberAnimation { target: content; property: "y"; to: 0; duration: 150; easing.type: Easing.OutCubic } target: content
property: "opacity"
to: 1
duration: 120
easing.type: Easing.OutCubic
}
NumberAnimation {
target: content
property: "y"
to: 0
duration: 150
easing.type: Easing.OutCubic
}
} }
ParallelAnimation { ParallelAnimation {
id: hideAnim id: hideAnim
NumberAnimation { target: content; property: "opacity"; to: 0; duration: 150; easing.type: Easing.InCubic } NumberAnimation {
NumberAnimation { target: content; property: "y"; to: -content.height; duration: 150; easing.type: Easing.InCubic } target: content
property: "opacity"
to: 0
duration: 150
easing.type: Easing.InCubic
}
NumberAnimation {
target: content
property: "y"
to: -content.height
duration: 150
easing.type: Easing.InCubic
}
onFinished: root._winVisible = false onFinished: root._winVisible = false
} }

View file

@ -7,25 +7,77 @@ import Quickshell.Io
QtObject { QtObject {
id: root id: root
property var workspaces: ({ enable: true }) property var workspaces: ({
property var tray: ({ enable: true }) enable: true
property var windowTitle: ({ enable: true }) })
property var clock: ({ enable: true }) property var tray: ({
property var notifications: ({ enable: true }) enable: true
property var mpris: ({ enable: true }) })
property var volume: ({ enable: true }) property var windowTitle: ({
property var bluetooth: ({ enable: true, interval: 5000 }) enable: true
property var backlight: ({ enable: true, step: 5 }) })
property var network: ({ enable: true, interval: 5000 }) property var clock: ({
property var powerProfile: ({ enable: true, interval: 5000 }) enable: true
property var idleInhibitor: ({ enable: true }) })
property var weather: ({ enable: true, args: ["--nerd"], interval: 3600000 }) property var notifications: ({
property var temperature: ({ enable: true, interval: 2000, warm: 60, hot: 80 }) enable: true
property var cpu: ({ enable: true, interval: 1000 }) })
property var memory: ({ enable: true, interval: 2000 }) property var mpris: ({
property var disk: ({ enable: true, interval: 30000 }) enable: true
property var battery: ({ enable: true, warning: 25, critical: 15 }) })
property var power: ({ enable: true }) property var volume: ({
enable: true
})
property var bluetooth: ({
enable: true,
interval: 5000
})
property var backlight: ({
enable: true,
step: 5
})
property var network: ({
enable: true,
interval: 5000
})
property var powerProfile: ({
enable: true,
interval: 5000
})
property var idleInhibitor: ({
enable: true
})
property var weather: ({
enable: true,
args: ["--nerd"],
interval: 3600000
})
property var temperature: ({
enable: true,
interval: 2000,
warm: 60,
hot: 80
})
property var cpu: ({
enable: true,
interval: 1000
})
property var memory: ({
enable: true,
interval: 2000
})
property var disk: ({
enable: true,
interval: 30000
})
property var battery: ({
enable: true,
warning: 25,
critical: 15
})
property var power: ({
enable: true
})
property FileView _file: FileView { property FileView _file: FileView {
path: (Quickshell.env("XDG_CONFIG_HOME") || (Quickshell.env("HOME") + "/.config")) + "/nova-shell/modules.json" path: (Quickshell.env("XDG_CONFIG_HOME") || (Quickshell.env("HOME") + "/.config")) + "/nova-shell/modules.json"
@ -42,12 +94,15 @@ QtObject {
return; return;
} }
for (const k of Object.keys(data)) { for (const k of Object.keys(data)) {
if (!(k in root)) continue; if (!(k in root))
continue;
const v = data[k]; const v = data[k];
if (typeof v === "object" && v !== null) if (typeof v === "object" && v !== null)
root[k] = Object.assign({}, root[k], v); root[k] = Object.assign({}, root[k], v);
else if (typeof v === "boolean") else if (typeof v === "boolean")
root[k] = Object.assign({}, root[k], { enable: v }); root[k] = Object.assign({}, root[k], {
enable: v
});
} }
} }
} }

View file

@ -19,8 +19,12 @@ M.BarSection {
// Cache art URL at root level so it's captured even when panel is hidden // Cache art URL at root level so it's captured even when panel is hidden
readonly property string _artUrl: player?.trackArtUrl ?? "" readonly property string _artUrl: player?.trackArtUrl ?? ""
readonly property string _currentTrack: player?.trackTitle ?? "" readonly property string _currentTrack: player?.trackTitle ?? ""
on_ArtUrlChanged: if (_artUrl) _cachedArt = _artUrl on_ArtUrlChanged: if (_artUrl)
on_CurrentTrackChanged: if (_currentTrack !== _artTrack) { _artTrack = _currentTrack; _cachedArt = _artUrl || "" } _cachedArt = _artUrl
on_CurrentTrackChanged: if (_currentTrack !== _artTrack) {
_artTrack = _currentTrack;
_cachedArt = _artUrl || "";
}
// Preload art while panel is hidden ensures QML image cache has the pixels // Preload art while panel is hidden ensures QML image cache has the pixels
Image { Image {
@ -89,10 +93,7 @@ M.BarSection {
anchors.left: true anchors.left: true
margins.top: 0 margins.top: 0
margins.left: Math.max(0, Math.min( margins.left: Math.max(0, Math.min(Math.round(root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0) - implicitWidth / 2), (panel.screen?.width ?? 1920) - implicitWidth))
Math.round(root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0) - implicitWidth / 2),
(panel.screen?.width ?? 1920) - implicitWidth
))
implicitWidth: panelContent.width implicitWidth: panelContent.width
implicitHeight: panelContent.height implicitHeight: panelContent.height
@ -113,14 +114,38 @@ M.BarSection {
ParallelAnimation { ParallelAnimation {
id: showAnim id: showAnim
NumberAnimation { target: panelContent; property: "opacity"; to: 1; duration: 120; easing.type: Easing.OutCubic } NumberAnimation {
NumberAnimation { target: panelContent; property: "y"; to: 0; duration: 150; easing.type: Easing.OutCubic } target: panelContent
property: "opacity"
to: 1
duration: 120
easing.type: Easing.OutCubic
}
NumberAnimation {
target: panelContent
property: "y"
to: 0
duration: 150
easing.type: Easing.OutCubic
}
} }
ParallelAnimation { ParallelAnimation {
id: hideAnim id: hideAnim
NumberAnimation { target: panelContent; property: "opacity"; to: 0; duration: 150; easing.type: Easing.InCubic } NumberAnimation {
NumberAnimation { target: panelContent; property: "y"; to: -panelContent.height; duration: 150; easing.type: Easing.InCubic } target: panelContent
property: "opacity"
to: 0
duration: 150
easing.type: Easing.InCubic
}
NumberAnimation {
target: panelContent
property: "y"
to: -panelContent.height
duration: 150
easing.type: Easing.InCubic
}
onFinished: panel._winVisible = false onFinished: panel._winVisible = false
} }
@ -172,10 +197,14 @@ M.BarSection {
source: root._cachedArt source: root._cachedArt
property bool _hasArt: false property bool _hasArt: false
onStatusChanged: if (status === Image.Ready) _hasArt = true onStatusChanged: if (status === Image.Ready)
_hasArt = true
Connections { Connections {
target: root target: root
function on_CachedArtChanged() { if (!root._cachedArt) _artImg._hasArt = false } function on_CachedArtChanged() {
if (!root._cachedArt)
_artImg._hasArt = false;
}
} }
} }
@ -186,8 +215,14 @@ M.BarSection {
height: 40 height: 40
visible: _artImg.visible visible: _artImg.visible
gradient: Gradient { gradient: Gradient {
GradientStop { position: 0; color: "transparent" } GradientStop {
GradientStop { position: 1; color: M.Theme.base01 } position: 0
color: "transparent"
}
GradientStop {
position: 1
color: M.Theme.base01
}
} }
} }
@ -229,7 +264,8 @@ M.BarSection {
width: parent.width width: parent.width
text: { text: {
const p = root.player; const p = root.player;
if (!p) return ""; if (!p)
return "";
const artist = Array.isArray(p.trackArtists) ? p.trackArtists.join(", ") : (p.trackArtists || ""); const artist = Array.isArray(p.trackArtists) ? p.trackArtists.join(", ") : (p.trackArtists || "");
return [artist, p.trackAlbum].filter(s => s).join(" \u2014 "); return [artist, p.trackAlbum].filter(s => s).join(" \u2014 ");
} }

View file

@ -10,7 +10,8 @@ M.BarSection {
const parts = []; const parts = [];
if (root.state === "wifi") { if (root.state === "wifi") {
parts.push("WiFi: " + root.essid); parts.push("WiFi: " + root.essid);
if (root.signal) parts.push("Signal: " + root.signal + "%"); if (root.signal)
parts.push("Signal: " + root.signal + "%");
} else if (root.state === "eth") { } else if (root.state === "eth") {
parts.push("Ethernet"); parts.push("Ethernet");
} else if (root.state === "linked") { } else if (root.state === "linked") {
@ -18,8 +19,10 @@ M.BarSection {
} else { } else {
return "Disconnected"; return "Disconnected";
} }
if (root.ipAddr) parts.push("IP: " + root.ipAddr); if (root.ipAddr)
if (root.ifname) parts.push("Interface: " + root.ifname); parts.push("IP: " + root.ipAddr);
if (root.ifname)
parts.push("Interface: " + root.ifname);
return parts.join("\n"); return parts.join("\n");
} }
@ -96,7 +99,10 @@ M.BarSection {
TapHandler { TapHandler {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onTapped: { menuLoader.active = !menuLoader.active; M.FlyoutState.visible = false; } onTapped: {
menuLoader.active = !menuLoader.active;
M.FlyoutState.visible = false;
}
} }
Loader { Loader {

View file

@ -13,12 +13,7 @@ M.PopupPanel {
property Process _scanner: Process { property Process _scanner: Process {
id: scanner id: scanner
running: true running: true
command: ["sh", "-c", command: ["sh", "-c", "echo '---CONNS---';" + "nmcli -t -f NAME,UUID,TYPE,ACTIVE connection show 2>/dev/null;" + "echo '---WIFI---';" + "nmcli -t -f SSID,SIGNAL device wifi list --rescan no 2>/dev/null"]
"echo '---CONNS---';" +
"nmcli -t -f NAME,UUID,TYPE,ACTIVE connection show 2>/dev/null;" +
"echo '---WIFI---';" +
"nmcli -t -f SSID,SIGNAL device wifi list --rescan no 2>/dev/null"
]
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: { onStreamFinished: {
const sections = text.split("---WIFI---"); const sections = text.split("---WIFI---");
@ -28,16 +23,19 @@ M.PopupPanel {
// Visible SSIDs with signal // Visible SSIDs with signal
const visible = {}; const visible = {};
for (const l of wifiLines.trim().split("\n")) { for (const l of wifiLines.trim().split("\n")) {
if (!l) continue; if (!l)
continue;
const parts = l.split(":"); const parts = l.split(":");
const ssid = parts[0]; const ssid = parts[0];
if (ssid) visible[ssid] = parseInt(parts[1]) || 0; if (ssid)
visible[ssid] = parseInt(parts[1]) || 0;
} }
// Saved connections filter: show wired always, wifi only if visible // Saved connections filter: show wired always, wifi only if visible
const nets = []; const nets = [];
for (const l of connLines.trim().split("\n")) { for (const l of connLines.trim().split("\n")) {
if (!l) continue; if (!l)
continue;
const parts = l.split(":"); const parts = l.split(":");
const name = parts[0]; const name = parts[0];
const uuid = parts[1]; const uuid = parts[1];
@ -45,7 +43,8 @@ M.PopupPanel {
const active = parts[3] === "yes"; const active = parts[3] === "yes";
const isWifi = type.includes("wireless"); const isWifi = type.includes("wireless");
if (isWifi && !(name in visible)) continue; if (isWifi && !(name in visible))
continue;
nets.push({ nets.push({
name: name, name: name,
@ -58,8 +57,10 @@ M.PopupPanel {
// Active first, then by signal (wifi) or name // Active first, then by signal (wifi) or name
nets.sort((a, b) => { nets.sort((a, b) => {
if (a.active !== b.active) return a.active ? -1 : 1; if (a.active !== b.active)
if (a.signal >= 0 && b.signal >= 0) return b.signal - a.signal; return a.active ? -1 : 1;
if (a.signal >= 0 && b.signal >= 0)
return b.signal - a.signal;
return a.name.localeCompare(b.name); return a.name.localeCompare(b.name);
}); });
@ -72,14 +73,16 @@ M.PopupPanel {
id: connectProc id: connectProc
property string uuid: "" property string uuid: ""
command: ["nmcli", "connection", "up", uuid] command: ["nmcli", "connection", "up", uuid]
onRunningChanged: if (!running) scanner.running = true onRunningChanged: if (!running)
scanner.running = true
} }
property Process _disconnectProc: Process { property Process _disconnectProc: Process {
id: disconnectProc id: disconnectProc
property string uuid: "" property string uuid: ""
command: ["nmcli", "connection", "down", uuid] command: ["nmcli", "connection", "down", uuid]
onRunningChanged: if (!running) scanner.running = true onRunningChanged: if (!running)
scanner.running = true
} }
Repeater { Repeater {

View file

@ -57,17 +57,31 @@ M.BarSection {
id: countScale id: countScale
origin.x: countLabel.width / 2 origin.x: countLabel.width / 2
origin.y: countLabel.height / 2 origin.y: countLabel.height / 2
xScale: 1; yScale: 1 xScale: 1
yScale: 1
} }
SequentialAnimation { SequentialAnimation {
id: popAnim id: popAnim
NumberAnimation { target: countScale; properties: "xScale,yScale"; to: 1.4; duration: 100; easing.type: Easing.OutQuad } NumberAnimation {
NumberAnimation { target: countScale; properties: "xScale,yScale"; to: 1.0; duration: 200; easing.type: Easing.OutElastic } target: countScale
properties: "xScale,yScale"
to: 1.4
duration: 100
easing.type: Easing.OutQuad
}
NumberAnimation {
target: countScale
properties: "xScale,yScale"
to: 1.0
duration: 200
easing.type: Easing.OutElastic
}
} }
} }
onCountChanged: if (count > 0) popAnim.start() onCountChanged: if (count > 0)
popAnim.start()
TapHandler { TapHandler {
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton

View file

@ -35,24 +35,45 @@ PanelWindow {
anchors.left: true anchors.left: true
margins.top: 0 margins.top: 0
margins.left: Math.max(0, Math.min( margins.left: Math.max(0, Math.min(Math.round(M.OsdState.itemX - implicitWidth / 2), screen.width - implicitWidth))
Math.round(M.OsdState.itemX - implicitWidth / 2),
screen.width - implicitWidth
))
implicitWidth: 200 implicitWidth: 200
implicitHeight: 48 implicitHeight: 48
ParallelAnimation { ParallelAnimation {
id: showAnim id: showAnim
NumberAnimation { target: content; property: "opacity"; to: 1; duration: 150; easing.type: Easing.OutCubic } NumberAnimation {
NumberAnimation { target: content; property: "y"; to: 0; duration: 200; easing.type: Easing.OutCubic } target: content
property: "opacity"
to: 1
duration: 150
easing.type: Easing.OutCubic
}
NumberAnimation {
target: content
property: "y"
to: 0
duration: 200
easing.type: Easing.OutCubic
}
} }
ParallelAnimation { ParallelAnimation {
id: hideAnim id: hideAnim
NumberAnimation { target: content; property: "opacity"; to: 0; duration: 250; easing.type: Easing.InCubic } NumberAnimation {
NumberAnimation { target: content; property: "y"; to: -content.height; duration: 250; easing.type: Easing.InCubic } target: content
property: "opacity"
to: 0
duration: 250
easing.type: Easing.InCubic
}
NumberAnimation {
target: content
property: "y"
to: -content.height
duration: 250
easing.type: Easing.InCubic
}
onFinished: root._winVisible = false onFinished: root._winVisible = false
} }
@ -104,7 +125,10 @@ PanelWindow {
radius: 3 radius: 3
Behavior on width { Behavior on width {
NumberAnimation { duration: 120; easing.type: Easing.OutCubic } NumberAnimation {
duration: 120
easing.type: Easing.OutCubic
}
} }
} }
} }

View file

@ -14,7 +14,7 @@ PanelWindow {
required property real anchorX required property real anchorX
property real panelWidth: 220 property real panelWidth: 220
signal dismissed() signal dismissed
visible: true visible: true
color: "transparent" color: "transparent"
@ -44,10 +44,7 @@ PanelWindow {
Item { Item {
id: panel id: panel
x: Math.max(0, Math.min( x: Math.max(0, Math.min(Math.round(root.anchorX - contentCol.width / 2), root.width - contentCol.width))
Math.round(root.anchorX - contentCol.width / 2),
root.width - contentCol.width
))
y: 0 y: 0
width: contentCol.width width: contentCol.width
height: contentCol.height height: contentCol.height
@ -79,14 +76,40 @@ PanelWindow {
ParallelAnimation { ParallelAnimation {
id: showAnim id: showAnim
NumberAnimation { target: panel; property: "opacity"; from: 0; to: 1; duration: 150; easing.type: Easing.OutCubic } NumberAnimation {
NumberAnimation { target: panel; property: "y"; from: -panel.height; to: 0; duration: 200; easing.type: Easing.OutCubic } target: panel
property: "opacity"
from: 0
to: 1
duration: 150
easing.type: Easing.OutCubic
}
NumberAnimation {
target: panel
property: "y"
from: -panel.height
to: 0
duration: 200
easing.type: Easing.OutCubic
}
} }
ParallelAnimation { ParallelAnimation {
id: hideAnim id: hideAnim
NumberAnimation { target: panel; property: "opacity"; to: 0; duration: 150; easing.type: Easing.InCubic } NumberAnimation {
NumberAnimation { target: panel; property: "y"; to: -panel.height; duration: 150; easing.type: Easing.InCubic } target: panel
property: "opacity"
to: 0
duration: 150
easing.type: Easing.InCubic
}
NumberAnimation {
target: panel
property: "y"
to: -panel.height
duration: 150
easing.type: Easing.InCubic
}
onFinished: root.dismissed() onFinished: root.dismissed()
} }
} }

View file

@ -18,7 +18,10 @@ M.BarIcon {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { menuLoader.active = !menuLoader.active; M.FlyoutState.visible = false; } onClicked: {
menuLoader.active = !menuLoader.active;
M.FlyoutState.visible = false;
}
} }
Loader { Loader {

View file

@ -18,11 +18,36 @@ M.PopupPanel {
Repeater { Repeater {
model: [ model: [
{ label: "Lock", icon: "\uF023", cmd: ["loginctl", "lock-session"], color: M.Theme.base0D }, {
{ label: "Suspend", icon: "\uF186", cmd: ["systemctl", "suspend"], color: M.Theme.base0E }, label: "Lock",
{ label: "Logout", icon: "\uF2F5", cmd: menuWindow._isNiri ? ["niri", "msg", "action", "quit"] : ["loginctl", "terminate-user", ""], color: M.Theme.base0A }, icon: "\uF023",
{ label: "Reboot", icon: "\uF021", cmd: ["systemctl", "reboot"], color: M.Theme.base09 }, cmd: ["loginctl", "lock-session"],
{ label: "Shutdown", icon: "\uF011", cmd: ["systemctl", "poweroff"], color: M.Theme.base08 } color: M.Theme.base0D
},
{
label: "Suspend",
icon: "\uF186",
cmd: ["systemctl", "suspend"],
color: M.Theme.base0E
},
{
label: "Logout",
icon: "\uF2F5",
cmd: menuWindow._isNiri ? ["niri", "msg", "action", "quit"] : ["loginctl", "terminate-user", ""],
color: M.Theme.base0A
},
{
label: "Reboot",
icon: "\uF021",
cmd: ["systemctl", "reboot"],
color: M.Theme.base09
},
{
label: "Shutdown",
icon: "\uF011",
cmd: ["systemctl", "poweroff"],
color: M.Theme.base08
}
] ]
delegate: Item { delegate: Item {

View file

@ -8,9 +8,12 @@ M.BarSection {
tooltip: "Temperature: " + root.celsius + "\u00B0C" tooltip: "Temperature: " + root.celsius + "\u00B0C"
property int celsius: 0 property int celsius: 0
property color _stateColor: celsius > (M.Modules.temperature.hot || 80) ? M.Theme.base09 property color _stateColor: celsius > (M.Modules.temperature.hot || 80) ? M.Theme.base09 : celsius > (M.Modules.temperature.warm || 60) ? M.Theme.base0A : M.Theme.base08
: celsius > (M.Modules.temperature.warm || 60) ? M.Theme.base0A : M.Theme.base08 Behavior on _stateColor {
Behavior on _stateColor { ColorAnimation { duration: 300 } } ColorAnimation {
duration: 300
}
}
FileView { FileView {
id: thermal id: thermal

View file

@ -30,9 +30,22 @@ RowLayout {
SequentialAnimation { SequentialAnimation {
running: iconItem._needsAttention running: iconItem._needsAttention
loops: Animation.Infinite loops: Animation.Infinite
NumberAnimation { target: iconItem; property: "_pulseOpacity"; to: 0.3; duration: 400; easing.type: Easing.InOutQuad } NumberAnimation {
NumberAnimation { target: iconItem; property: "_pulseOpacity"; to: 1; duration: 400; easing.type: Easing.InOutQuad } target: iconItem
onRunningChanged: if (!running) iconItem._pulseOpacity = 1 property: "_pulseOpacity"
to: 0.3
duration: 400
easing.type: Easing.InOutQuad
}
NumberAnimation {
target: iconItem
property: "_pulseOpacity"
to: 1
duration: 400
easing.type: Easing.InOutQuad
}
onRunningChanged: if (!running)
iconItem._pulseOpacity = 1
} }
Item { Item {

View file

@ -120,10 +120,7 @@ M.BarSection {
anchors.left: true anchors.left: true
margins.top: 0 margins.top: 0
margins.left: Math.max(0, Math.min( margins.left: Math.max(0, Math.min(Math.round(root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0) - implicitWidth / 2), (panel.screen?.width ?? 1920) - implicitWidth))
Math.round(root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0) - implicitWidth / 2),
(panel.screen?.width ?? 1920) - implicitWidth
))
implicitWidth: panelContent.width implicitWidth: panelContent.width
implicitHeight: panelContent.height implicitHeight: panelContent.height
@ -146,14 +143,38 @@ M.BarSection {
ParallelAnimation { ParallelAnimation {
id: showAnim id: showAnim
NumberAnimation { target: panelContent; property: "opacity"; to: 1; duration: 120; easing.type: Easing.OutCubic } NumberAnimation {
NumberAnimation { target: panelContent; property: "y"; to: 0; duration: 150; easing.type: Easing.OutCubic } target: panelContent
property: "opacity"
to: 1
duration: 120
easing.type: Easing.OutCubic
}
NumberAnimation {
target: panelContent
property: "y"
to: 0
duration: 150
easing.type: Easing.OutCubic
}
} }
ParallelAnimation { ParallelAnimation {
id: hideAnim id: hideAnim
NumberAnimation { target: panelContent; property: "opacity"; to: 0; duration: 150; easing.type: Easing.InCubic } NumberAnimation {
NumberAnimation { target: panelContent; property: "y"; to: -panelContent.height; duration: 150; easing.type: Easing.InCubic } target: panelContent
property: "opacity"
to: 0
duration: 150
easing.type: Easing.InCubic
}
NumberAnimation {
target: panelContent
property: "y"
to: -panelContent.height
duration: 150
easing.type: Easing.InCubic
}
onFinished: panel._winVisible = false onFinished: panel._winVisible = false
} }
@ -203,7 +224,8 @@ M.BarSection {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: if (root.sink?.audio) root.sink.audio.muted = !root.sink.audio.muted onClicked: if (root.sink?.audio)
root.sink.audio.muted = !root.sink.audio.muted
} }
} }
@ -229,7 +251,11 @@ M.BarSection {
color: root._volumeColor color: root._volumeColor
radius: 3 radius: 3
Behavior on width { NumberAnimation { duration: 80 } } Behavior on width {
NumberAnimation {
duration: 80
}
}
} }
MouseArea { MouseArea {
@ -237,9 +263,13 @@ M.BarSection {
anchors.margins: -6 anchors.margins: -6
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => _setVol(mouse) onPressed: mouse => _setVol(mouse)
onPositionChanged: mouse => { if (pressed) _setVol(mouse); } onPositionChanged: mouse => {
if (pressed)
_setVol(mouse);
}
function _setVol(mouse) { function _setVol(mouse) {
if (!root.sink?.audio) return; if (!root.sink?.audio)
return;
root.sink.audio.volume = Math.max(0, Math.min(1, mouse.x / slider.width)); root.sink.audio.volume = Math.max(0, Math.min(1, mouse.x / slider.width));
} }
} }
@ -281,7 +311,12 @@ M.BarSection {
property real _targetHeight: root._expanded ? implicitHeight : 0 property real _targetHeight: root._expanded ? implicitHeight : 0
height: _targetHeight height: _targetHeight
Behavior on height { NumberAnimation { duration: 200; easing.type: Easing.OutCubic } } Behavior on height {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
// Separator // Separator
Rectangle { Rectangle {
@ -445,9 +480,13 @@ M.BarSection {
anchors.margins: -6 anchors.margins: -6
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => _set(mouse) onPressed: mouse => _set(mouse)
onPositionChanged: mouse => { if (pressed) _set(mouse); } onPositionChanged: mouse => {
if (pressed)
_set(mouse);
}
function _set(mouse) { function _set(mouse) {
if (!streamEntry.modelData.audio) return; if (!streamEntry.modelData.audio)
return;
streamEntry.modelData.audio.volume = Math.max(0, Math.min(1, mouse.x / streamSlider.width)); streamEntry.modelData.audio.volume = Math.max(0, Math.min(1, mouse.x / streamSlider.width));
} }
} }
@ -468,7 +507,10 @@ M.BarSection {
} }
// Bottom padding // Bottom padding
Item { width: 1; height: 4 } Item {
width: 1
height: 4
}
} }
} }
} }

View file

@ -90,7 +90,11 @@ Row {
height: 20 height: 20
radius: M.Theme.radius radius: M.Theme.radius
color: pill.active ? M.Theme.base0D : (pill._hovered ? M.Theme.base03 : M.Theme.base02) color: pill.active ? M.Theme.base0D : (pill._hovered ? M.Theme.base03 : M.Theme.base02)
Behavior on color { ColorAnimation { duration: 150 } } Behavior on color {
ColorAnimation {
duration: 150
}
}
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent

View file

@ -66,7 +66,8 @@ in
default = true; default = true;
description = "Enable the ${name} module."; description = "Enable the ${name} module.";
}; };
} // extra; }
// extra;
}; };
}; };
intervalOpt = default: { intervalOpt = default: {
@ -103,7 +104,9 @@ in
description = "Brightness adjustment step (%)."; description = "Brightness adjustment step (%).";
}; };
}; };
temperature = moduleOpt "temperature" ((intervalOpt 2000) // { temperature = moduleOpt "temperature" (
(intervalOpt 2000)
// {
warm = lib.mkOption { warm = lib.mkOption {
type = lib.types.int; type = lib.types.int;
default = 60; default = 60;
@ -114,7 +117,8 @@ in
default = 80; default = 80;
description = "Temperature threshold for hot state (°C)."; description = "Temperature threshold for hot state (°C).";
}; };
}); }
);
battery = moduleOpt "battery" { battery = moduleOpt "battery" {
warning = lib.mkOption { warning = lib.mkOption {
type = lib.types.int; type = lib.types.int;
@ -127,7 +131,9 @@ in
description = "Battery percentage for critical notification and blink."; description = "Battery percentage for critical notification and blink.";
}; };
}; };
weather = moduleOpt "weather" ((intervalOpt 3600000) // { weather = moduleOpt "weather" (
(intervalOpt 3600000)
// {
args = lib.mkOption { args = lib.mkOption {
type = lib.types.listOf lib.types.str; type = lib.types.listOf lib.types.str;
default = [ "--nerd" ]; default = [ "--nerd" ];
@ -138,7 +144,8 @@ in
"Berlin" "Berlin"
]; ];
}; };
}); }
);
}; };
theme = lib.mkOption { theme = lib.mkOption {
@ -174,7 +181,8 @@ in
++ lib.optional cfg.modules.weather.enable pkgs.wttrbar; ++ lib.optional cfg.modules.weather.enable pkgs.wttrbar;
xdg.configFile."nova-shell/modules.json".source = xdg.configFile."nova-shell/modules.json".source =
(pkgs.formats.json { }).generate "nova-shell-modules.json" cfg.modules; (pkgs.formats.json { }).generate "nova-shell-modules.json"
cfg.modules;
xdg.configFile."nova-shell/theme.json".source = xdg.configFile."nova-shell/theme.json".source =
let let