Compare commits

...
12 changed files with 112 additions and 89 deletions

View file

@ -16,23 +16,17 @@ kept saying "yes" and I don't have the self-awareness to stop. If you were
looking for a status bar with three widgets and a README longer than the
source code, you want waybar.
- Status bar with too many widgets, grouped into glowing color-coded sections
- Notification center that replaces swaync (whether you wanted that or not)
- Hover panels for volume, brightness, network, and media — OSD, mixer, device list, and wifi connections unified into hover panels; pin button keeps them open while you interact
- Volume panel shows output devices and per-app stream sliders inline
- CPU panel: per-core usage bars with load-colored sparklines, frequency readout, thermal throttle detection (freq label turns red), P/E-core grouping on hybrid CPUs, top processes by CPU usage
- Memory panel: used/cached/available breakdown with stacked bar, top processes by memory
- Disk panel: per-mount usage bars with color-coded fill, used/total sizes
- Network and bluetooth hover panels: wifi list, signal strength, connect/disconnect, radio toggle; bluetooth paired device list with connect/disconnect and power toggle
- Tray, power profile, idle inhibitor, privacy indicators, power menu
- Glowing status bar with hover panels for everything (cpu, gpu, memory, disk, battery, temperature, network, bluetooth, volume, brightness, media, weather, clock)
- Notification center (replaces swaync, whether you wanted that or not)
- Battery panel with 24h history, wattage sparkline, and the vague sense of being watched
- 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
- Audio visualizer on album art via cava
- Lock screen — hex wave overlay, media/volume/brightness controls, notification pills, crash recovery via logind
- Album art cava visualizer
- Lock screen with hex waves, media controls, and a threat shader that gets redder the more you fail
- 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
- Home Manager module with stylix, per-module config — the only part that arguably works as intended
- No documentation beyond this README. You could ask an LLM to explain the source to you. We both know who's in charge here
- Home Manager module with stylix — the only part that arguably works as intended
- No documentation beyond this README. Ask an LLM. We both know who's in charge here
## Installation

View file

@ -87,7 +87,6 @@ in
"bluetooth"
"network"
"powerProfile"
"gpu"
"cpu"
"memory"
"idleInhibitor"
@ -171,6 +170,18 @@ in
description = "Brightness adjustment step (%).";
};
};
gpu = moduleOpt "gpu" {
warm = lib.mkOption {
type = lib.types.int;
default = 70;
description = "GPU temperature threshold for warm state (°C).";
};
hot = lib.mkOption {
type = lib.types.int;
default = 85;
description = "GPU temperature threshold for hot state (°C).";
};
};
temperature = moduleOpt "temperature" {
warm = lib.mkOption {
type = lib.types.int;

View file

@ -152,6 +152,24 @@ Column {
valueColor: root._stateColor
}
// Wattage history sparkline
SparklineCanvas {
visible: S.BatteryService.rateHistory.length > 1
anchors.left: parent.left
anchors.leftMargin: 12
anchors.right: parent.right
anchors.rightMargin: 12
height: 24
history: S.BatteryService.rateHistory
color: root._stateColor
active: root.active
maxSamples: 60
maxValue: null
minValue: null
backgroundTint: 0.08
colorFunction: v => v >= 0 ? S.Theme.base0B : root._stateColor
}
// Health row
InfoRow {
visible: S.BatteryService.healthSupported

View file

@ -158,7 +158,7 @@ Column {
height: 22
label: "Temp"
value: S.SystemStats.gpuTempC + "\u00B0C"
valueColor: S.SystemStats.gpuTempC > 85 ? S.Theme.base08 : S.SystemStats.gpuTempC > 70 ? S.Theme.base0A : S.Theme.base05
valueColor: S.SystemStats.gpuTempC > S.Modules.gpu.hot ? S.Theme.base08 : S.SystemStats.gpuTempC > S.Modules.gpu.warm ? S.Theme.base0A : S.Theme.base05
}
Item {

View file

@ -3,10 +3,15 @@ import QtQuick
Canvas {
id: root
// Data - array of 0-100 percentage values
// Data - array of numeric values. Set maxValue/minValue to null for auto-range.
property var history: []
property color color: "white"
property bool active: true
property var maxValue: 100
property var minValue: 0
readonly property real _max: maxValue !== null && maxValue !== undefined ? maxValue : (history.length ? Math.max(...history) : 100)
readonly property real _min: minValue !== null && minValue !== undefined ? minValue : (history.length ? Math.min(...history) : 0)
// Max x-axis slots (0 = use history.length, right-aligns shorter data)
property int maxSamples: 0
@ -45,6 +50,12 @@ Canvas {
const samples = maxSamples > 0 ? maxSamples : d.length;
const step = width / samples;
const offset = samples - d.length;
const lo = _min;
const hi = _max;
const range = hi - lo || 1;
function yOf(v) {
return height - height * ((v - lo) / range);
}
// Background tint
if (backgroundTint > 0) {
@ -59,26 +70,37 @@ Canvas {
ctx.setLineDash([3, 3]);
for (let t = 0; t < thresholds.length; t++) {
const th = thresholds[t];
const ty = height - height * (th.value / 100);
ctx.strokeStyle = th.color.toString();
ctx.beginPath();
ctx.moveTo(0, ty);
ctx.lineTo(width, ty);
ctx.moveTo(0, yOf(th.value));
ctx.lineTo(width, yOf(th.value));
ctx.stroke();
}
ctx.setLineDash([]);
ctx.globalAlpha = 1.0;
}
// Zero line when range spans negative values
if (lo < 0 && hi > 0) {
const zy = yOf(0);
ctx.strokeStyle = Qt.rgba(color.r, color.g, color.b, 0.2).toString();
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0, zy);
ctx.lineTo(width, zy);
ctx.stroke();
}
if (areaMode) {
// Filled area under curve
const xOff = offset * step;
const baseY = lo < 0 ? yOf(0) : height;
ctx.beginPath();
ctx.moveTo(xOff, height);
ctx.moveTo(xOff, baseY);
for (let i = 0; i < d.length; i++) {
ctx.lineTo(xOff + i * step, height - height * (d[i] / 100));
ctx.lineTo(xOff + i * step, yOf(d[i]));
}
ctx.lineTo(xOff + (d.length - 1) * step, height);
ctx.lineTo(xOff + (d.length - 1) * step, baseY);
ctx.closePath();
ctx.fillStyle = Qt.rgba(color.r, color.g, color.b, areaOpacity).toString();
ctx.fill();
@ -87,25 +109,27 @@ Canvas {
ctx.beginPath();
for (let i = 0; i < d.length; i++) {
const x = xOff + i * step;
const y = height - height * (d[i] / 100);
if (i === 0)
ctx.moveTo(x, y);
ctx.moveTo(x, yOf(d[i]));
else
ctx.lineTo(x, y);
ctx.lineTo(x, yOf(d[i]));
}
ctx.strokeStyle = color.toString();
ctx.lineWidth = root.lineWidth;
ctx.stroke();
} else {
// Bar chart
// Bar chart - bars grow from zero line (or bottom if all positive)
const hasCF = typeof colorFunction === "function";
const zy = lo < 0 ? yOf(0) : height;
if (!hasCF)
ctx.fillStyle = color.toString();
for (let i = 0; i < d.length; i++) {
const barH = Math.max(1, height * d[i] / 100);
if (hasCF)
ctx.fillStyle = colorFunction(d[i]).toString();
ctx.fillRect((offset + i) * step, height - barH, Math.max(1, step - 0.5), barH);
const dy = yOf(d[i]);
const barTop = Math.min(dy, zy);
const barH = Math.max(1, Math.abs(dy - zy));
ctx.fillRect((offset + i) * step, barTop, Math.max(1, step - 0.5), barH);
}
}
}

View file

@ -13,25 +13,9 @@ M.PinnableSection {
property real _blinkOpacity: 1
SequentialAnimation {
M.PulseAnimation on _blinkOpacity {
running: S.BatteryService.critical
loops: Animation.Infinite
NumberAnimation {
target: root
property: "_blinkOpacity"
to: 0.45
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
minOpacity: 0.45
}
// Bar widgets

View file

@ -55,19 +55,9 @@ Row {
shadowHorizontalOffset: 0
}
SequentialAnimation on opacity {
M.PulseAnimation on opacity {
running: root._videoCapture
loops: Animation.Infinite
NumberAnimation {
to: 0.4
duration: 600
easing.type: Easing.InOutQuad
}
NumberAnimation {
to: 1
duration: 600
easing.type: Easing.InOutQuad
}
halfDuration: 600
}
}
@ -89,19 +79,9 @@ Row {
shadowHorizontalOffset: 0
}
SequentialAnimation on opacity {
M.PulseAnimation on opacity {
running: root._audioIn
loops: Animation.Infinite
NumberAnimation {
to: 0.4
duration: 600
easing.type: Easing.InOutQuad
}
NumberAnimation {
to: 1
duration: 600
easing.type: Easing.InOutQuad
}
halfDuration: 600
}
}
}

View file

@ -0,0 +1,20 @@
import QtQuick
SequentialAnimation {
id: root
loops: Animation.Infinite
property real minOpacity: 0.4
property int halfDuration: 400
NumberAnimation {
to: root.minOpacity
duration: root.halfDuration
easing.type: Easing.InOutQuad
}
NumberAnimation {
to: 1
duration: root.halfDuration
easing.type: Easing.InOutQuad
}
}

View file

@ -29,25 +29,9 @@ RowLayout {
implicitWidth: S.Theme.fontSize + 4
implicitHeight: S.Theme.fontSize + 4
SequentialAnimation {
M.PulseAnimation on _pulseOpacity {
running: iconItem._needsAttention
loops: Animation.Infinite
NumberAnimation {
target: iconItem
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
minOpacity: 0.3
}
Item {

View file

@ -31,6 +31,7 @@ PowerModule 1.0 PowerModule.qml
PowerProfileModule 1.0 PowerProfileModule.qml
PrivacyModule 1.0 PrivacyModule.qml
ProcessList 1.0 ProcessList.qml
PulseAnimation 1.0 PulseAnimation.qml
ScreenCapture 1.0 ScreenCapture.qml
ScreenCorners 1.0 ScreenCorners.qml
TemperatureModule 1.0 TemperatureModule.qml

View file

@ -30,6 +30,8 @@ QtObject {
// 24h history (1440 samples @ 60s)
property var history: []
// Wattage history (60 samples @ 60s = 1h), signed: positive = charging
property var rateHistory: []
property var _histTimer: Timer {
interval: 60000
@ -39,6 +41,9 @@ QtObject {
onTriggered: {
const h = root.history.concat([root.percent]);
root.history = h.length > 1440 ? h.slice(h.length - 1440) : h;
const w = root.charging ? root.changeRate : -root.changeRate;
const rh = root.rateHistory.concat([w]);
root.rateHistory = rh.length > 60 ? rh.slice(rh.length - 60) : rh;
}
}

View file

@ -60,7 +60,9 @@ QtObject {
device: ""
})
property var gpu: ({
enable: true
enable: true,
warm: 70,
hot: 85
})
property var cpu: ({
enable: true