diff --git a/include/config_manager.h b/include/config_manager.h index 14a6c9e..432eead 100644 --- a/include/config_manager.h +++ b/include/config_manager.h @@ -2,6 +2,7 @@ #define CONFIG_MANAGER_H #include +#include 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 getSensorColors() const { return sensorColors; } + std::map 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 sensorColors; + std::map sensorNames; }; #endif // CONFIG_MANAGER_H diff --git a/include/mainwindow.h b/include/mainwindow.h index 63c9881..f73c739 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -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 tempLabels; }; // Global main loop reference for proper shutdown diff --git a/include/temp_monitor.h b/include/temp_monitor.h index ab022a9..a0e5c93 100644 --- a/include/temp_monitor.h +++ b/include/temp_monitor.h @@ -26,6 +26,15 @@ public: std::map> getAllTemperatures(); private: + struct SensorInfo { + std::string deviceName; + std::string sensorName; + std::string path; + }; + + std::vector discoveredSensors; + + void discoverSensors(); double readTemperatureFromFile(const std::string &filePath); }; diff --git a/include/temperature_chart.h b/include/temperature_chart.h index e24638e..a57fc9f 100644 --- a/include/temperature_chart.h +++ b/include/temperature_chart.h @@ -16,7 +16,8 @@ struct DataPoint { struct SeriesData { std::vector 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> 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: diff --git a/resources/style.css b/resources/style.css index dad86ce..285f960 100644 --- a/resources/style.css +++ b/resources/style.css @@ -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; +} diff --git a/src/config_manager.cpp b/src/config_manager.cpp index 64cc65a..d9e1543 100644 --- a/src/config_manager.cpp +++ b/src/config_manager.cpp @@ -7,7 +7,7 @@ #include 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(); } diff --git a/src/main.cpp b/src/main.cpp index 12f1d81..63fca48 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #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 diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 556023d..c77d62e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -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(userData); + const char *seriesId = static_cast(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(); } } } diff --git a/src/temp_monitor.cpp b/src/temp_monitor.cpp index 7d2b724..a2a92eb 100644 --- a/src/temp_monitor.cpp +++ b/src/temp_monitor.cpp @@ -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 TempMonitor::getAvailableDevices() @@ -52,73 +118,12 @@ std::map> TempMonitor::getAllTemperat { std::map> 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 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; } diff --git a/src/temperature_chart.cpp b/src/temperature_chart.cpp index 2c77233..71914e0 100644 --- a/src/temperature_chart.cpp +++ b/src/temperature_chart.cpp @@ -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> TemperatureChart::getSeriesColors() const { std::vector> 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(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; } } }