Compare commits

...

7 Commits

Author SHA1 Message Date
3a98bc08e9 shell: document why UseQApplication is required
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 21:42:03 +02:00
f6796f3726 sysstats: refresh cpuFreq every 3rd tick instead of every tick
/proc/cpuinfo is regenerated in full by the kernel on every read and
its cost grows with core count, making it the priciest per-tick read.
Bar frequency display doesn't need 1 Hz precision.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 21:42:02 +02:00
6655c9c500 bar: wrap onto two rows when one row doesn't fit
On narrow outputs (portrait rotation) the single-row layout overflowed.
Compare the needed width (implicit sizes of all three clusters) against
the window width; when it doesn't fit, double the bar height and move
the metric pills to a second row, keeping workspaces + clock/tray on
the first.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 21:41:13 +02:00
b7ea18c751 volume: reduce scroll sensitivity
Use 2% steps and accumulate wheel deltas so high-res/inertial scrolling
steps once per notch instead of firing per micro-event.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 21:41:02 +02:00
796be77341 fix wifi disconnected icon 2026-06-06 23:37:44 +02:00
7a95288f81 theme: increase contrast 2026-06-04 21:39:52 +02:00
fa3e41ecc2 clock: move near end of bar 2026-06-04 21:39:16 +02:00
7 changed files with 85 additions and 39 deletions

View File

@@ -11,7 +11,8 @@ Singleton {
readonly property string thermo: String.fromCharCode(0xf2c9) // thermometer-half readonly property string thermo: String.fromCharCode(0xf2c9) // thermometer-half
readonly property string disk: String.fromCharCode(0xf0a0) // hdd-o readonly property string disk: String.fromCharCode(0xf0a0) // hdd-o
readonly property string wifi: String.fromCharCode(0xf1eb) // wifi readonly property string wifi: String.fromCharCode(0xf1eb) // wifi
readonly property string ethernet: String.fromCharCode(0xf6ff) // network-wired readonly property string ethernet: String.fromCodePoint(0xf0200) // md-ethernet (FA5 network-wired 0xf6ff is absent from the bundled Nerd Fonts)
readonly property string netOff: String.fromCodePoint(0xf05aa) // md-wifi-off (shown when there is no active interface)
readonly property string down: String.fromCharCode(0xf063) // arrow-down readonly property string down: String.fromCharCode(0xf063) // arrow-down
readonly property string up: String.fromCharCode(0xf062) // arrow-up readonly property string up: String.fromCharCode(0xf062) // arrow-up
readonly property string clock: String.fromCharCode(0xf017) // clock-o readonly property string clock: String.fromCharCode(0xf017) // clock-o

View File

@@ -11,28 +11,28 @@ Singleton {
readonly property int padding: 10 readonly property int padding: 10
readonly property int gap: 4 readonly property int gap: 4
// --- palette (Catppuccin Mocha, darkened toward OLED black) --- // --- palette (deep near-black base, high-contrast foreground) ---
readonly property color base: "#0d0d12" readonly property color base: "#05060a"
readonly property color mantle: "#08080b" readonly property color mantle: "#000000"
readonly property color surface0: "#1c1c28" readonly property color surface0: "#15161f"
readonly property color surface1: "#2a2a3a" readonly property color surface1: "#232532"
readonly property color overlay: "#52526a" readonly property color overlay: "#4a4d63"
readonly property color text: "#cdd6f4" readonly property color text: "#eef1f8"
readonly property color subtext: "#a6adc8" readonly property color subtext: "#b4bbd0"
readonly property color rosewater: "#f5e0dc" readonly property color rosewater: "#ffdfd6"
readonly property color red: "#f38ba8" readonly property color red: "#ff7a93"
readonly property color peach: "#fab387" readonly property color peach: "#ffb074"
readonly property color yellow: "#f9e2af" readonly property color yellow: "#ffe07a"
readonly property color green: "#a6e3a1" readonly property color green: "#8ff09a"
readonly property color teal: "#94e2d5" readonly property color teal: "#7ff0df"
readonly property color sky: "#89dceb" readonly property color sky: "#74d9ff"
readonly property color blue: "#89b4fa" readonly property color blue: "#7aa9ff"
readonly property color mauve: "#cba6f7" readonly property color mauve: "#c799ff"
readonly property color lavender: "#b4befe" readonly property color lavender: "#aebcff"
// bar background, slightly translucent // bar background, near-opaque true black
readonly property color barColor: Qt.rgba(0.051, 0.051, 0.071, 0.94) readonly property color barColor: Qt.rgba(0.02, 0.024, 0.039, 0.96)
// --- typography --- // --- typography ---
readonly property string font: "Inter, sans-serif" readonly property string font: "Inter, sans-serif"

View File

@@ -52,6 +52,12 @@ Singleton {
// poll interval in seconds // poll interval in seconds
readonly property int interval: 1 readonly property int interval: 1
// cpuFreq comes from /proc/cpuinfo, which the kernel regenerates in full on
// every read (cost grows with core count) — the priciest per-tick read here.
// Frequency on a status bar doesn't need 1 Hz precision, so only refresh it
// every Nth tick instead of on the CPU%/mem hot path.
readonly property int freqEveryTicks: 3
// discovered sysfs paths (filled once by discover.sh) // discovered sysfs paths (filled once by discover.sh)
property string tempPath: "" property string tempPath: ""
property string batPath: "" property string batPath: ""
@@ -271,15 +277,17 @@ Singleton {
} }
function _tickOnce() { function _tickOnce() {
const doFreq = root._tick % root.freqEveryTicks === 0;
statView.reload(); statView.reload();
cpuInfoView.reload(); if (doFreq) cpuInfoView.reload();
memView.reload(); memView.reload();
routeView.reload(); routeView.reload();
if (root.tempPath) tempView.reload(); if (root.tempPath) tempView.reload();
if (root.batPath) { batCapView.reload(); batStatusView.reload(); } if (root.batPath) { batCapView.reload(); batStatusView.reload(); }
_parseCpu(); _parseCpu();
_parseFreq(); if (doFreq) _parseFreq();
_parseMem(); _parseMem();
_parseTemp(); _parseTemp();
_parseBattery(); _parseBattery();

View File

@@ -1,4 +1,7 @@
//@ pragma UseQApplication //@ pragma UseQApplication
// Required: Quickshell renders the system-tray context menu (right-click in
// Tray.qml -> item.display()) via QtWidgets' QMenu. Dropping this loads the
// lighter QGuiApplication but breaks tray menus. Verified 2026-06-08.
import Quickshell import Quickshell
import "widgets" import "widgets"

View File

@@ -29,7 +29,20 @@ PanelWindow {
left: true left: true
right: true right: true
} }
implicitHeight: Theme.barHeight
// Wrap onto two rows when a single row can't hold everything (portrait
// outputs). Needed width is computed from implicit sizes, which don't
// depend on which row anything sits in, so this can't bind-loop.
readonly property real neededWidth:
(Theme.gap + 2) * 2 // outer margins
+ workspaces.implicitWidth
+ Theme.gap * 4 // breathing room between clusters
+ metricsRow.implicitWidth
+ Theme.gap
+ endRow.implicitWidth
readonly property bool twoRows: width > 0 && neededWidth > width
implicitHeight: twoRows ? Theme.barHeight * 2 : Theme.barHeight
// background strip // background strip
Rectangle { Rectangle {
@@ -37,24 +50,35 @@ PanelWindow {
color: Theme.barColor color: Theme.barColor
} }
// left: workspaces // row 1 left: workspaces
Workspaces { Workspaces {
id: workspaces
anchors.left: parent.left anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.gap + 2 anchors.leftMargin: Theme.gap + 2
y: (Theme.barHeight - height) / 2
screen: panel.modelData screen: panel.modelData
} }
// center: clock // row 1 right: clock, tray
Clock { RowLayout {
anchors.centerIn: parent id: endRow
anchors.right: parent.right
anchors.rightMargin: Theme.gap + 2
y: (Theme.barHeight - height) / 2
spacing: Theme.gap
Clock {}
Tray { panelWindow: panel }
} }
// right: battery, system metrics, volume, tray // metrics: left of the clock in one-row mode, own second row when narrow
RowLayout { RowLayout {
anchors.right: parent.right id: metricsRow
anchors.verticalCenter: parent.verticalCenter anchors.right: panel.twoRows ? parent.right : endRow.left
anchors.rightMargin: Theme.gap + 2 anchors.rightMargin: panel.twoRows ? Theme.gap + 2 : Theme.gap
y: panel.twoRows
? Theme.barHeight + (Theme.barHeight - height) / 2
: (Theme.barHeight - height) / 2
spacing: Theme.gap spacing: Theme.gap
Battery {} Battery {}
@@ -64,6 +88,5 @@ PanelWindow {
Disk {} Disk {}
Network {} Network {}
Volume {} Volume {}
Tray { panelWindow: panel }
} }
} }

View File

@@ -20,10 +20,11 @@ Item {
spacing: Theme.spacing spacing: Theme.spacing
Text { Text {
text: SysStats.iface.startsWith("wl") ? Icons.wifi : Icons.ethernet text: SysStats.iface === "" ? Icons.netOff
: SysStats.iface.startsWith("wl") ? Icons.wifi : Icons.ethernet
font.family: Theme.monoFont font.family: Theme.monoFont
font.pixelSize: Theme.fontSize + 1 font.pixelSize: Theme.fontSize + 1
color: Theme.sky color: SysStats.iface === "" ? Theme.overlay : Theme.sky
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
} }
@@ -127,10 +128,11 @@ Item {
spacing: Theme.spacing spacing: Theme.spacing
Text { Text {
text: SysStats.iface.startsWith("wl") ? Icons.wifi : Icons.ethernet text: SysStats.iface === "" ? Icons.netOff
: SysStats.iface.startsWith("wl") ? Icons.wifi : Icons.ethernet
font.family: Theme.monoFont font.family: Theme.monoFont
font.pixelSize: Theme.fontSize + 1 font.pixelSize: Theme.fontSize + 1
color: Theme.sky color: SysStats.iface === "" ? Theme.overlay : Theme.sky
} }
Text { Text {
text: "Network by process" text: "Network by process"

View File

@@ -17,11 +17,20 @@ MouseArea {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
// accumulate raw wheel deltas so high-res / inertial scrolling doesn't
// fire a full step per micro-event; one notch (120 units) == one step
property real _wheelAccum: 0
readonly property real _wheelStep: 0.02
onClicked: { if (audio) audio.muted = !audio.muted; } onClicked: { if (audio) audio.muted = !audio.muted; }
onWheel: wheel => { onWheel: wheel => {
if (!audio) return; if (!audio) return;
_wheelAccum += wheel.angleDelta.y;
const notches = Math.trunc(_wheelAccum / 120);
if (notches === 0) return;
_wheelAccum -= notches * 120;
audio.volume = Math.max(0, Math.min(1, audio.volume = Math.max(0, Math.min(1,
audio.volume + (wheel.angleDelta.y > 0 ? 0.05 : -0.05))); audio.volume + notches * _wheelStep));
} }
// keep the sink's audio properties live // keep the sink's audio properties live