Compare commits

..

No commits in common. "88a0886681321a22dc6e4c568a219c6d5e7b3242" and "cf5581657ba2a9eb8fedb2f6f3468730a4b18f13" have entirely different histories.

7 changed files with 17 additions and 146 deletions

View file

@ -91,7 +91,6 @@ programs.nova-shell.modules = {
weather.interval = 3600000; # refresh interval in ms (default 1h) weather.interval = 3600000; # refresh interval in ms (default 1h)
temperature.warm = 55; # °C threshold for warm color temperature.warm = 55; # °C threshold for warm color
temperature.hot = 75; # °C threshold for hot color temperature.hot = 75; # °C threshold for hot color
temperature.device = "x86_pkg_temp"; # pin to a specific thermal zone (default: system max)
battery.warning = 30; # % for warning notification battery.warning = 30; # % for warning notification
battery.critical = 10; # % for critical blink + notification battery.critical = 10; # % for critical blink + notification
cpu.interval = 2000; # polling interval in ms cpu.interval = 2000; # polling interval in ms

View file

@ -56,8 +56,7 @@ QtObject {
property var temperature: ({ property var temperature: ({
enable: true, enable: true,
warm: 80, warm: 80,
hot: 90, hot: 90
device: ""
}) })
property var gpu: ({ property var gpu: ({
enable: true enable: true

View file

@ -46,7 +46,6 @@ QtObject {
readonly property Connections _notifConn: Connections { readonly property Connections _notifConn: Connections {
target: root.notification target: root.notification
function onClosed() { function onClosed() {
if (root.state !== "dismissed")
M.NotifService.dismiss(root.id); M.NotifService.dismiss(root.id);
} }
} }

View file

@ -33,7 +33,6 @@ QtObject {
// Temperature // Temperature
property int tempCelsius: 0 property int tempCelsius: 0
property var tempHistory: [] // 150 samples @ 4s each 10 min property var tempHistory: [] // 150 samples @ 4s each 10 min
property var tempDevices: [] // [{name, celsius}] sorted hottest-first
// GPU // GPU
property bool gpuAvailable: false property bool gpuAvailable: false
@ -96,8 +95,6 @@ QtObject {
root.tempCelsius = ev.celsius; root.tempCelsius = ev.celsius;
const th = root.tempHistory.concat([ev.celsius]); const th = root.tempHistory.concat([ev.celsius]);
root.tempHistory = th.length > 150 ? th.slice(th.length - 150) : th; root.tempHistory = th.length > 150 ? th.slice(th.length - 150) : th;
if (ev.devices)
root.tempDevices = ev.devices;
} else if (ev.type === "gpu") { } else if (ev.type === "gpu") {
root.gpuAvailable = true; root.gpuAvailable = true;
root.gpuVendor = ev.vendor; root.gpuVendor = ev.vendor;

View file

@ -9,17 +9,7 @@ M.BarSection {
readonly property int _warm: M.Modules.temperature.warm || 80 readonly property int _warm: M.Modules.temperature.warm || 80
readonly property int _hot: M.Modules.temperature.hot || 90 readonly property int _hot: M.Modules.temperature.hot || 90
readonly property string _deviceFilter: M.Modules.temperature.device || "" readonly property int _temp: M.SystemStats.tempCelsius
// If a device filter is set, use that device's temp; otherwise fall back to system max
readonly property int _temp: {
if (_deviceFilter !== "") {
const dev = M.SystemStats.tempDevices.find(d => d.name === _deviceFilter);
if (dev)
return dev.celsius;
}
return M.SystemStats.tempCelsius;
}
property color _stateColor: _temp > _hot ? M.Theme.base08 : _temp > _warm ? M.Theme.base0A : root.accentColor property color _stateColor: _temp > _hot ? M.Theme.base08 : _temp > _warm ? M.Theme.base0A : root.accentColor
Behavior on _stateColor { Behavior on _stateColor {
@ -267,57 +257,6 @@ M.BarSection {
} }
} }
// Per-device breakdown
Rectangle {
width: parent.width - 16
height: 1
anchors.horizontalCenter: parent.horizontalCenter
color: M.Theme.base03
visible: M.SystemStats.tempDevices.length > 0
}
Repeater {
model: M.SystemStats.tempDevices
delegate: Item {
required property var modelData
width: hoverPanel.contentWidth
height: 22
readonly property bool _isActive: root._deviceFilter === modelData.name
Rectangle {
anchors.fill: parent
anchors.leftMargin: 8
anchors.rightMargin: 8
color: _isActive ? Qt.rgba(root.accentColor.r, root.accentColor.g, root.accentColor.b, 0.12) : "transparent"
radius: 3
}
Text {
anchors.left: parent.left
anchors.leftMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: modelData.name
color: _isActive ? root.accentColor : M.Theme.base04
font.pixelSize: M.Theme.fontSize - 2
font.family: M.Theme.fontFamily
elide: Text.ElideRight
width: parent.width - 80
}
Text {
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: modelData.celsius + "\u00B0C"
color: root._tempColor(modelData.celsius)
font.pixelSize: M.Theme.fontSize - 2
font.family: M.Theme.fontFamily
font.bold: _isActive
}
}
}
Item { Item {
width: 1 width: 1
height: 4 height: 4

View file

@ -141,14 +141,6 @@ in
default = 90; default = 90;
description = "Temperature threshold for hot state (°C)."; description = "Temperature threshold for hot state (°C).";
}; };
device = lib.mkOption {
type = lib.types.str;
default = "";
description = ''
Thermal zone device name to use for the primary temperature reading
(e.g. "x86_pkg_temp", "acpitz"). Leave empty to use the system maximum.
'';
};
}; };
battery = moduleOpt "battery" { battery = moduleOpt "battery" {
warning = lib.mkOption { warning = lib.mkOption {

View file

@ -1,79 +1,25 @@
use std::collections::HashMap;
use std::fs; use std::fs;
use std::io::Write; use std::io::Write;
#[derive(Debug)] pub fn read_temp_celsius() -> Option<i32> {
pub struct ThermalDevice { let mut max: Option<i32> = None;
pub name: String,
pub celsius: i32,
}
pub fn read_thermal_devices() -> Vec<ThermalDevice> {
let mut by_name: HashMap<String, i32> = HashMap::new();
for i in 0.. { for i in 0.. {
let temp_path = format!("/sys/class/thermal/thermal_zone{i}/temp"); let path = format!("/sys/class/thermal/thermal_zone{i}/temp");
let type_path = format!("/sys/class/thermal/thermal_zone{i}/type"); match fs::read_to_string(&path) {
Ok(s) => {
let temp_str = match fs::read_to_string(&temp_path) { if let Ok(millic) = s.trim().parse::<i32>() {
Ok(s) => s, let c = millic / 1000;
max = Some(max.map_or(c, |m: i32| m.max(c)));
}
}
Err(_) => break, Err(_) => break,
};
let millic: i32 = match temp_str.trim().parse() {
Ok(v) => v,
Err(_) => continue,
};
let celsius = millic / 1000;
let name = fs::read_to_string(&type_path)
.map(|s| s.trim().to_string())
.unwrap_or_else(|_| format!("zone{i}"));
// Keep the highest temp seen for each device type
let entry = by_name.entry(name).or_insert(celsius);
if celsius > *entry {
*entry = celsius;
} }
} }
max
let mut devices: Vec<ThermalDevice> = by_name
.into_iter()
.map(|(name, celsius)| ThermalDevice { name, celsius })
.collect();
// Sort descending by temp so the hottest shows first
devices.sort_by(|a, b| b.celsius.cmp(&a.celsius));
devices
} }
pub fn emit_temp(out: &mut impl Write) { pub fn emit_temp(out: &mut impl Write) {
let devices = read_thermal_devices(); if let Some(c) = read_temp_celsius() {
if devices.is_empty() { let _ = writeln!(out, "{{\"type\":\"temp\",\"celsius\":{c}}}");
return;
} }
let max = devices.iter().map(|d| d.celsius).max().unwrap_or(0);
let devices_json: Vec<String> = devices
.iter()
.map(|d| {
format!(
"{{\"name\":{},\"celsius\":{}}}",
json_str(&d.name),
d.celsius
)
})
.collect();
let _ = writeln!(
out,
"{{\"type\":\"temp\",\"celsius\":{max},\"devices\":[{}]}}",
devices_json.join(",")
);
}
fn json_str(s: &str) -> String {
let escaped = s.replace('\\', "\\\\").replace('"', "\\\"");
format!("\"{escaped}\"")
} }