Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4256c3b86f | ||
|
|
42d11e7a14 | ||
|
|
b5be146619 |
12 changed files with 112 additions and 89 deletions
20
README.md
20
README.md
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
20
shell/modules/PulseAnimation.qml
Normal file
20
shell/modules/PulseAnimation.qml
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,9 @@ QtObject {
|
|||
device: ""
|
||||
})
|
||||
property var gpu: ({
|
||||
enable: true
|
||||
enable: true,
|
||||
warm: 70,
|
||||
hot: 85
|
||||
})
|
||||
property var cpu: ({
|
||||
enable: true
|
||||
|
|
|
|||
Reference in a new issue