added cpu, renaming

This commit is contained in:
rdavidek 2026-01-11 12:51:35 +01:00
parent 1519a1bee4
commit c8668e5f00
10 changed files with 264 additions and 88 deletions

View File

@ -2,6 +2,7 @@
#define CONFIG_MANAGER_H
#include <string>
#include <map>
class ConfigManager {
public:
@ -21,13 +22,22 @@ public:
void setWindowWidth(int w) { windowWidth = w; }
void setWindowHeight(int h) { windowHeight = h; }
void setPollingTime(int t) { pollingTime = t; }
std::map<std::string, std::string> getSensorColors() const { return sensorColors; }
std::map<std::string, std::string> getSensorNames() const { return sensorNames; }
void setSensorColor(const std::string &id, const std::string &color) { sensorColors[id] = color; }
void setSensorName(const std::string &id, const std::string &name) { sensorNames[id] = name; }
private:
std::string getConfigFilePath() const;
int windowWidth;
int windowHeight;
int pollingTime;
std::map<std::string, std::string> sensorColors;
std::map<std::string, std::string> sensorNames;
};
#endif // CONFIG_MANAGER_H

View File

@ -25,6 +25,7 @@ private:
static void onClearButtonClicked(GtkButton *button, gpointer userData);
static void onQuitButtonClicked(GtkButton *button, gpointer userData);
static void onColorSet(GObject *object, GParamSpec *pspec, gpointer userData);
static void onNameChanged(GtkEditable *editable, gpointer userData);
GtkWidget *window;
GtkWidget *statusLabel;
@ -35,6 +36,7 @@ private:
guint timerID;
int refreshRateSec;
GtkWidget *legendBox;
std::map<std::string, GtkWidget*> tempLabels;
};
// Global main loop reference for proper shutdown

View File

@ -26,6 +26,15 @@ public:
std::map<std::string, std::map<std::string, double>> getAllTemperatures();
private:
struct SensorInfo {
std::string deviceName;
std::string sensorName;
std::string path;
};
std::vector<SensorInfo> discoveredSensors;
void discoverSensors();
double readTemperatureFromFile(const std::string &filePath);
};

View File

@ -16,7 +16,8 @@ struct DataPoint {
struct SeriesData {
std::vector<DataPoint> points;
GdkRGBA color;
std::string name;
std::string id; // Unique internal ID (device + sensor)
std::string name; // User-friendly display name
};
class TemperatureChart {
@ -34,6 +35,11 @@ public:
// Get list of series with their colors
std::vector<std::pair<std::string, GdkRGBA>> getSeriesColors() const;
void setSeriesColor(const std::string &seriesName, const GdkRGBA &color);
// Name management
std::string getSeriesName(const std::string &seriesId) const;
void setSeriesName(const std::string &seriesId, const std::string &name);
void drawChart(GtkDrawingArea *area, cairo_t *cr, int width, int height);
private:

View File

@ -2,3 +2,30 @@
window {
/* Icon can be set via CSS if needed */
}
.small-entry {
font-size: 9px;
min-height: 0;
min-width: 0;
padding: 0px 1px;
margin: 0;
border: none;
background: transparent;
}
.small-entry:focus {
background: rgba(255,255,255,0.1);
}
.temp-label-small {
font-size: 9px;
font-weight: bold;
margin-left: 1px;
color: #ccc;
}
/* Compact box items */
.legend-item {
margin: 0 4px;
padding: 0;
}

View File

@ -7,7 +7,7 @@
#include <unistd.h>
ConfigManager::ConfigManager()
: windowWidth(1200), windowHeight(700), pollingTime(3)
: windowWidth(1200), windowHeight(700), pollingTime(1)
{
}
@ -67,6 +67,10 @@ void ConfigManager::load()
windowHeight = std::stoi(value);
} else if (key == "polling_time") {
pollingTime = std::stoi(value);
} else if (key.find("color_") == 0) {
sensorColors[key.substr(6)] = value;
} else if (key.find("name_") == 0) {
sensorNames[key.substr(5)] = value;
}
}
@ -90,5 +94,13 @@ void ConfigManager::save()
file << "window_height = " << windowHeight << "\n";
file << "polling_time = " << pollingTime << "\n";
for (auto const& [id, color] : sensorColors) {
file << "color_" << id << " = " << color << "\n";
}
for (auto const& [id, name] : sensorNames) {
file << "name_" << id << " = " << name << "\n";
}
file.close();
}

View File

@ -4,6 +4,7 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <iostream>
#include "mainwindow.h"
// Declaration from resources.c
@ -15,6 +16,7 @@ GMainLoop *gMainLoop = nullptr;
int main(int argc, char *argv[])
{
/*
// Daemonize the process
pid_t pid = fork();
@ -45,16 +47,23 @@ int main(int argc, char *argv[])
dup2(fd, STDERR_FILENO);
close(fd);
}
*/
gtk_init();
std::cerr << "GTK Initialized" << std::endl;
// Register resources containing the application icon
GResource *resource = resources_get_resource();
g_resources_register(resource);
std::cerr << "Resources registered" << std::endl;
gMainLoop = g_main_loop_new(nullptr, FALSE);
std::cerr << "Creating MainWindow" << std::endl;
MainWindow *window = new MainWindow();
std::cerr << "MainWindow created" << std::endl;
window->show();
// Run the main event loop

View File

@ -9,7 +9,8 @@
MainWindow::MainWindow()
: window(nullptr), statusLabel(nullptr), refreshRateSpinBox(nullptr),
chart(nullptr), monitor(nullptr), config(nullptr), timerID(0), refreshRateSec(3)
chart(nullptr), monitor(nullptr), config(nullptr), timerID(0),
refreshRateSec(3)
{
monitor = new TempMonitor();
config = new ConfigManager();
@ -48,6 +49,14 @@ MainWindow::~MainWindow()
void MainWindow::setupUI()
{
// Load CSS
GtkCssProvider *provider = gtk_css_provider_new();
gtk_css_provider_load_from_resource(provider, "/org/kamma/nvme-monitor/style.css");
gtk_style_context_add_provider_for_display(gdk_display_get_default(),
GTK_STYLE_PROVIDER(provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
g_object_unref(provider);
window = gtk_window_new();
gtk_window_set_title(GTK_WINDOW(window), "NVMe Temperature Monitor");
@ -94,7 +103,7 @@ void MainWindow::setupUI()
GtkWidget *clearButton = gtk_button_new_with_label("Clear Data");
g_signal_connect(clearButton, "clicked", G_CALLBACK(onClearButtonClicked), this);
gtk_box_append(GTK_BOX(controlBox), clearButton);
// Status label
statusLabel = gtk_label_new("Initializing...");
gtk_label_set_xalign(GTK_LABEL(statusLabel), 0);
@ -113,10 +122,15 @@ void MainWindow::setupUI()
gtk_box_append(GTK_BOX(mainBox), chart->getWidget());
gtk_widget_set_vexpand(chart->getWidget(), TRUE);
// Legend box
legendBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10);
gtk_widget_set_margin_top(legendBox, 10);
gtk_box_append(GTK_BOX(mainBox), legendBox);
// Legend box with horizontal scrolling only
legendBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
GtkWidget *legendScroll = gtk_scrolled_window_new();
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(legendScroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER);
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(legendScroll), legendBox);
gtk_widget_set_margin_top(legendScroll, 2);
gtk_box_append(GTK_BOX(mainBox), legendScroll);
}
gboolean MainWindow::onDeleteWindow(GtkWidget *widget, gpointer userData)
@ -185,6 +199,7 @@ void MainWindow::saveWindowState()
void MainWindow::updateTemperatures()
{
std::cerr << "updateTemperatures starting" << std::endl;
auto allTemps = monitor->getAllTemperatures();
// Get current real time in milliseconds since epoch
@ -198,13 +213,41 @@ void MainWindow::updateTemperatures()
const std::string &device = deviceEntry.first;
const auto &temps = deviceEntry.second;
std::cerr << "Processing device: " << device << " with " << temps.size() << " sensors" << std::endl;
for (const auto &tempEntry : temps) {
const std::string &sensorName = tempEntry.first;
double temperature = tempEntry.second;
std::cerr << " Sensor: " << sensorName << " Temp: " << temperature << std::endl;
if (chart->addTemperatureData(device, sensorName, temperature, currentTime)) {
needsLegendUpdate = true;
// Apply saved settings for new series
std::string seriesId = device + " - " + sensorName;
auto savedNames = config->getSensorNames();
if (savedNames.count(seriesId)) {
chart->setSeriesName(seriesId, savedNames[seriesId]);
}
auto savedColors = config->getSensorColors();
if (savedColors.count(seriesId)) {
GdkRGBA color;
if (gdk_rgba_parse(&color, savedColors[seriesId].c_str())) {
chart->setSeriesColor(seriesId, color);
}
}
}
// Update individual temperature label in legend
std::string seriesId = device + " - " + sensorName;
if (tempLabels.count(seriesId)) {
char buf[16];
snprintf(buf, sizeof(buf), "%.1f°C", temperature);
gtk_label_set_text(GTK_LABEL(tempLabels[seriesId]), buf);
}
totalReadings++;
}
}
@ -235,33 +278,48 @@ void MainWindow::updateLegend()
child = next;
}
tempLabels.clear();
// Get series colors from chart
auto seriesColors = chart->getSeriesColors();
// Add legend items
for (const auto &pair : seriesColors) {
const std::string &seriesName = pair.first;
const std::string &seriesId = pair.first;
const GdkRGBA &color = pair.second;
std::string displayName = chart->getSeriesName(seriesId);
// Create container for legend item
GtkWidget *itemBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
GtkWidget *itemBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2);
gtk_widget_add_css_class(itemBox, "legend-item");
// Create color dialog and button (modern GTK4 way)
// Create color dialog and button
GtkColorDialog *dialog = gtk_color_dialog_new();
GtkWidget *colorButton = gtk_color_dialog_button_new(dialog);
gtk_color_dialog_button_set_rgba(GTK_COLOR_DIALOG_BUTTON(colorButton), &color);
gtk_widget_set_size_request(colorButton, 24, 24);
// Store series name in user data to know which series to update
g_object_set_data_full(G_OBJECT(colorButton), "series-name", g_strdup(seriesName.c_str()), g_free);
gtk_widget_set_size_request(colorButton, 12, 12);
// Store series id in user data
g_object_set_data_full(G_OBJECT(colorButton), "series-name", g_strdup(seriesId.c_str()), g_free);
g_signal_connect(colorButton, "notify::rgba", G_CALLBACK(onColorSet), this);
gtk_box_append(GTK_BOX(itemBox), colorButton);
// Create label with series name
GtkWidget *label = gtk_label_new(seriesName.c_str());
gtk_box_append(GTK_BOX(itemBox), label);
// Create editable label for series name
GtkWidget *entry = gtk_entry_new();
gtk_editable_set_text(GTK_EDITABLE(entry), displayName.c_str());
gtk_widget_set_size_request(entry, 60, -1);
gtk_widget_add_css_class(entry, "small-entry");
// Store series id in user data
g_object_set_data_full(G_OBJECT(entry), "series-name", g_strdup(seriesId.c_str()), g_free);
g_signal_connect(entry, "changed", G_CALLBACK(onNameChanged), this);
gtk_box_append(GTK_BOX(itemBox), entry);
// Temperature label
GtkWidget *tempLabel = gtk_label_new("---°C");
gtk_widget_add_css_class(tempLabel, "temp-label-small");
gtk_box_append(GTK_BOX(itemBox), tempLabel);
tempLabels[seriesId] = tempLabel;
gtk_box_append(GTK_BOX(legendBox), itemBox);
}
@ -283,6 +341,27 @@ void MainWindow::onColorSet(GObject *object, GParamSpec *pspec, gpointer userDat
const GdkRGBA *color = gtk_color_dialog_button_get_rgba(GTK_COLOR_DIALOG_BUTTON(object));
if (color) {
self->chart->setSeriesColor(seriesName, *color);
// Save to config
char *colorStr = gdk_rgba_to_string(color);
self->config->setSensorColor(seriesName, colorStr);
g_free(colorStr);
self->config->save();
}
}
}
void MainWindow::onNameChanged(GtkEditable *editable, gpointer userData)
{
MainWindow *self = static_cast<MainWindow*>(userData);
const char *seriesId = static_cast<const char*>(g_object_get_data(G_OBJECT(editable), "series-name"));
if (seriesId) {
const char *newName = gtk_editable_get_text(editable);
if (newName) {
self->chart->setSeriesName(seriesId, newName);
self->config->setSensorName(seriesId, newName);
self->config->save();
}
}
}

View File

@ -9,6 +9,72 @@
TempMonitor::TempMonitor()
{
discoverSensors();
}
void TempMonitor::discoverSensors()
{
discoveredSensors.clear();
DIR* hwmonDir = opendir("/sys/class/hwmon");
if (!hwmonDir) return;
struct dirent* entry;
while ((entry = readdir(hwmonDir)) != nullptr) {
std::string hwmonName = entry->d_name;
if (hwmonName.find("hwmon") != 0) continue;
std::string path = "/sys/class/hwmon/" + hwmonName;
// Read "name" from sysfs
std::string nameFromSysfs = "Unknown";
std::ifstream nameFile(path + "/name");
if (nameFile.is_open()) {
std::getline(nameFile, nameFromSysfs);
nameFromSysfs.erase(nameFromSysfs.find_last_not_of(" \n\r\t") + 1);
nameFile.close();
}
// Filter: Only interested in nvme and coretemp (CPU)
if (nameFromSysfs != "nvme" && nameFromSysfs != "coretemp") continue;
DIR* dDir = opendir(path.c_str());
if (dDir) {
struct dirent* sEntry;
while ((sEntry = readdir(dDir)) != nullptr) {
std::string fname = sEntry->d_name;
// Only temperature sensors (temp*_input)
if (fname.find("temp") == 0 && fname.find("_input") != std::string::npos) {
std::string id = fname.substr(4, fname.find("_input") - 4);
// Use "label" from sysfs for sensor name if available
std::string labelFromSysfs = "";
std::ifstream labelFile(path + "/temp" + id + "_label");
if (labelFile.is_open()) {
std::getline(labelFile, labelFromSysfs);
labelFromSysfs.erase(labelFromSysfs.find_last_not_of(" \n\r\t") + 1);
labelFile.close();
}
// For CPU (coretemp), filter only Package id 0
if (nameFromSysfs == "coretemp") {
if (labelFromSysfs != "Package id 0") continue;
}
// For NVMe, filter out Composite sensor
if (nameFromSysfs == "nvme") {
if (labelFromSysfs == "Composite") continue;
}
std::string finalSensorName = labelFromSysfs.empty() ? "temp" + id : labelFromSysfs;
std::string deviceDisplayName = nameFromSysfs + " (" + hwmonName + ")";
discoveredSensors.push_back({deviceDisplayName, finalSensorName, path + "/" + fname});
}
}
closedir(dDir);
}
}
closedir(hwmonDir);
}
std::vector<std::string> TempMonitor::getAvailableDevices()
@ -52,73 +118,12 @@ std::map<std::string, std::map<std::string, double>> TempMonitor::getAllTemperat
{
std::map<std::string, std::map<std::string, double>> allTemperatures;
DIR* hwmonDir = opendir("/sys/class/hwmon");
if (!hwmonDir) return allTemperatures;
struct dirent* entry;
while ((entry = readdir(hwmonDir)) != nullptr) {
std::string hwmonName = entry->d_name;
if (hwmonName.find("hwmon") != 0) continue;
std::string path = "/sys/class/hwmon/" + hwmonName;
// Read "name" from sysfs as requested
std::string nameFromSysfs = "Unknown";
std::ifstream nameFile(path + "/name");
if (nameFile.is_open()) {
std::getline(nameFile, nameFromSysfs);
nameFromSysfs.erase(nameFromSysfs.find_last_not_of(" \n\r\t") + 1);
nameFile.close();
}
// Filter: Only interested in nvme and coretemp (CPU)
if (nameFromSysfs != "nvme" && nameFromSysfs != "coretemp") continue;
std::map<std::string, double> sensors;
DIR* dDir = opendir(path.c_str());
if (dDir) {
struct dirent* sEntry;
while ((sEntry = readdir(dDir)) != nullptr) {
std::string fname = sEntry->d_name;
// Only temperature sensors (temp*_input)
if (fname.find("temp") == 0 && fname.find("_input") != std::string::npos) {
std::string id = fname.substr(4, fname.find("_input") - 4);
double temp = readTemperatureFromFile(path + "/" + fname);
if (temp > -200.0) {
// Use "label" from sysfs for sensor name if available
std::string labelFromSysfs = "";
std::ifstream labelFile(path + "/temp" + id + "_label");
if (labelFile.is_open()) {
std::getline(labelFile, labelFromSysfs);
labelFromSysfs.erase(labelFromSysfs.find_last_not_of(" \n\r\t") + 1);
labelFile.close();
}
// For CPU (coretemp), filter only Package id 0
if (nameFromSysfs == "coretemp") {
if (labelFromSysfs != "Package id 0") continue;
}
// For NVMe, filter out Composite sensor
if (nameFromSysfs == "nvme") {
if (labelFromSysfs == "Composite") continue;
}
std::string finalSensorName = labelFromSysfs.empty() ? "temp" + id : labelFromSysfs;
sensors[finalSensorName] = temp;
}
}
}
closedir(dDir);
}
if (!sensors.empty()) {
// Label device with its sysfs name and instance ID
allTemperatures[nameFromSysfs + " (" + hwmonName + ")"] = sensors;
for (const auto& sensor : discoveredSensors) {
double temp = readTemperatureFromFile(sensor.path);
if (temp > -200.0) {
allTemperatures[sensor.deviceName][sensor.sensorName] = temp;
}
}
closedir(hwmonDir);
return allTemperatures;
}

View File

@ -180,6 +180,7 @@ bool TemperatureChart::addTemperatureData(const std::string &device, const std::
if (seriesMap.find(seriesKey) == seriesMap.end()) {
SeriesData series;
series.color = getColorForSeries(seriesKey);
series.id = seriesKey;
series.name = seriesKey;
seriesMap[seriesKey] = series;
isNew = true;
@ -230,8 +231,8 @@ void TemperatureChart::clear()
std::vector<std::pair<std::string, GdkRGBA>> TemperatureChart::getSeriesColors() const
{
std::vector<std::pair<std::string, GdkRGBA>> result;
for (const auto &entry : colorMap) {
result.push_back({entry.first, entry.second});
for (const auto &pair : seriesMap) {
result.push_back({pair.first, pair.second.color});
}
return result;
}
@ -249,6 +250,22 @@ void TemperatureChart::setSeriesColor(const std::string &seriesName, const GdkRG
gtk_widget_queue_draw(drawingArea);
}
std::string TemperatureChart::getSeriesName(const std::string &seriesId) const
{
auto it = seriesMap.find(seriesId);
if (it != seriesMap.end()) {
return it->second.name;
}
return seriesId;
}
void TemperatureChart::setSeriesName(const std::string &seriesId, const std::string &name)
{
if (seriesMap.find(seriesId) != seriesMap.end()) {
seriesMap[seriesId].name = name;
}
}
gboolean TemperatureChart::onTick(gpointer userData)
{
TemperatureChart *self = static_cast<TemperatureChart*>(userData);
@ -414,7 +431,7 @@ TemperatureChart::NearestPoint TemperatureChart::findNearestDataPoint(double mou
result.found = true;
result.temperature = point.temperature;
result.timestamp = point.timestamp;
result.seriesName = seriesEntry.first;
result.seriesName = series.name;
}
}
}