added cpu, line graph, some fixes
This commit is contained in:
parent
44f968d9db
commit
1519a1bee4
@ -31,7 +31,7 @@ add_custom_command(
|
|||||||
set(SOURCES
|
set(SOURCES
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
src/mainwindow.cpp
|
src/mainwindow.cpp
|
||||||
src/nvme_monitor.cpp
|
src/temp_monitor.cpp
|
||||||
src/temperature_chart.cpp
|
src/temperature_chart.cpp
|
||||||
src/config_manager.cpp
|
src/config_manager.cpp
|
||||||
${CMAKE_BINARY_DIR}/resources.c
|
${CMAKE_BINARY_DIR}/resources.c
|
||||||
@ -39,7 +39,7 @@ set(SOURCES
|
|||||||
|
|
||||||
set(HEADERS
|
set(HEADERS
|
||||||
include/mainwindow.h
|
include/mainwindow.h
|
||||||
include/nvme_monitor.h
|
include/temp_monitor.h
|
||||||
include/temperature_chart.h
|
include/temperature_chart.h
|
||||||
include/config_manager.h
|
include/config_manager.h
|
||||||
)
|
)
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
#define MAINWINDOW_H
|
#define MAINWINDOW_H
|
||||||
|
|
||||||
#include <gtk/gtk.h>
|
#include <gtk/gtk.h>
|
||||||
#include "nvme_monitor.h"
|
#include "temp_monitor.h"
|
||||||
#include "temperature_chart.h"
|
#include "temperature_chart.h"
|
||||||
#include "config_manager.h"
|
#include "config_manager.h"
|
||||||
|
|
||||||
@ -24,12 +24,13 @@ private:
|
|||||||
static void onRefreshRateChanged(GtkSpinButton *spinButton, gpointer userData);
|
static void onRefreshRateChanged(GtkSpinButton *spinButton, gpointer userData);
|
||||||
static void onClearButtonClicked(GtkButton *button, gpointer userData);
|
static void onClearButtonClicked(GtkButton *button, gpointer userData);
|
||||||
static void onQuitButtonClicked(GtkButton *button, gpointer userData);
|
static void onQuitButtonClicked(GtkButton *button, gpointer userData);
|
||||||
|
static void onColorSet(GObject *object, GParamSpec *pspec, gpointer userData);
|
||||||
|
|
||||||
GtkWidget *window;
|
GtkWidget *window;
|
||||||
GtkWidget *statusLabel;
|
GtkWidget *statusLabel;
|
||||||
GtkSpinButton *refreshRateSpinBox;
|
GtkSpinButton *refreshRateSpinBox;
|
||||||
TemperatureChart *chart;
|
TemperatureChart *chart;
|
||||||
NvmeMonitor *monitor;
|
TempMonitor *monitor;
|
||||||
ConfigManager *config;
|
ConfigManager *config;
|
||||||
guint timerID;
|
guint timerID;
|
||||||
int refreshRateSec;
|
int refreshRateSec;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
#ifndef NVME_MONITOR_H
|
#ifndef TEMP_MONITOR_H
|
||||||
#define NVME_MONITOR_H
|
#define TEMP_MONITOR_H
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <map>
|
#include <map>
|
||||||
@ -12,11 +12,11 @@ struct TemperatureData {
|
|||||||
long timestamp;
|
long timestamp;
|
||||||
};
|
};
|
||||||
|
|
||||||
class NvmeMonitor {
|
class TempMonitor {
|
||||||
public:
|
public:
|
||||||
explicit NvmeMonitor();
|
explicit TempMonitor();
|
||||||
|
|
||||||
// Get list of available NVMe devices
|
// Get list of available devices
|
||||||
std::vector<std::string> getAvailableDevices();
|
std::vector<std::string> getAvailableDevices();
|
||||||
|
|
||||||
// Read temperatures from a specific device
|
// Read temperatures from a specific device
|
||||||
@ -26,9 +26,7 @@ public:
|
|||||||
std::map<std::string, std::map<std::string, double>> getAllTemperatures();
|
std::map<std::string, std::map<std::string, double>> getAllTemperatures();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<std::string> scanDevices();
|
|
||||||
std::map<std::string, std::string> findHwmonDevices();
|
|
||||||
double readTemperatureFromFile(const std::string &filePath);
|
double readTemperatureFromFile(const std::string &filePath);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // NVME_MONITOR_H
|
#endif // TEMP_MONITOR_H
|
||||||
@ -26,18 +26,19 @@ public:
|
|||||||
|
|
||||||
GtkWidget* getWidget() const { return drawingArea; }
|
GtkWidget* getWidget() const { return drawingArea; }
|
||||||
|
|
||||||
void addTemperatureData(const std::string &device, const std::string &sensor,
|
bool addTemperatureData(const std::string &device, const std::string &sensor,
|
||||||
double temperature, int64_t timestamp);
|
double temperature, int64_t timestamp);
|
||||||
void clear();
|
void clear();
|
||||||
void draw();
|
void draw();
|
||||||
|
|
||||||
// Get list of devices with their colors
|
// Get list of series with their colors
|
||||||
std::vector<std::pair<std::string, GdkRGBA>> getDeviceColors() const;
|
std::vector<std::pair<std::string, GdkRGBA>> getSeriesColors() const;
|
||||||
|
void setSeriesColor(const std::string &seriesName, const GdkRGBA &color);
|
||||||
void drawChart(GtkDrawingArea *area, cairo_t *cr, int width, int height);
|
void drawChart(GtkDrawingArea *area, cairo_t *cr, int width, int height);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setupColors();
|
void setupColors();
|
||||||
GdkRGBA getColorForDevice(const std::string &device);
|
GdkRGBA getColorForSeries(const std::string &seriesKey);
|
||||||
void redraw();
|
void redraw();
|
||||||
void updateThemeColors();
|
void updateThemeColors();
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,7 @@ MainWindow::MainWindow()
|
|||||||
: window(nullptr), statusLabel(nullptr), refreshRateSpinBox(nullptr),
|
: 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 NvmeMonitor();
|
monitor = new TempMonitor();
|
||||||
config = new ConfigManager();
|
config = new ConfigManager();
|
||||||
config->load();
|
config->load();
|
||||||
|
|
||||||
@ -192,6 +192,7 @@ void MainWindow::updateTemperatures()
|
|||||||
clock_gettime(CLOCK_REALTIME, &ts);
|
clock_gettime(CLOCK_REALTIME, &ts);
|
||||||
int64_t currentTime = (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
int64_t currentTime = (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||||||
int totalReadings = 0;
|
int totalReadings = 0;
|
||||||
|
bool needsLegendUpdate = false;
|
||||||
|
|
||||||
for (auto &deviceEntry : allTemps) {
|
for (auto &deviceEntry : allTemps) {
|
||||||
const std::string &device = deviceEntry.first;
|
const std::string &device = deviceEntry.first;
|
||||||
@ -201,7 +202,9 @@ void MainWindow::updateTemperatures()
|
|||||||
const std::string &sensorName = tempEntry.first;
|
const std::string &sensorName = tempEntry.first;
|
||||||
double temperature = tempEntry.second;
|
double temperature = tempEntry.second;
|
||||||
|
|
||||||
chart->addTemperatureData(device, sensorName, temperature, currentTime);
|
if (chart->addTemperatureData(device, sensorName, temperature, currentTime)) {
|
||||||
|
needsLegendUpdate = true;
|
||||||
|
}
|
||||||
totalReadings++;
|
totalReadings++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -216,9 +219,11 @@ void MainWindow::updateTemperatures()
|
|||||||
|
|
||||||
gtk_label_set_text(GTK_LABEL(statusLabel), oss.str().c_str());
|
gtk_label_set_text(GTK_LABEL(statusLabel), oss.str().c_str());
|
||||||
|
|
||||||
// Update legend
|
// Update legend ONLY if new sensors were found or legend is empty
|
||||||
|
if (needsLegendUpdate || gtk_widget_get_first_child(legendBox) == nullptr) {
|
||||||
updateLegend();
|
updateLegend();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::updateLegend()
|
void MainWindow::updateLegend()
|
||||||
{
|
{
|
||||||
@ -230,82 +235,32 @@ void MainWindow::updateLegend()
|
|||||||
child = next;
|
child = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get device colors from chart
|
// Get series colors from chart
|
||||||
auto deviceColors = chart->getDeviceColors();
|
auto seriesColors = chart->getSeriesColors();
|
||||||
|
|
||||||
// Add legend items
|
// Add legend items
|
||||||
for (const auto &pair : deviceColors) {
|
for (const auto &pair : seriesColors) {
|
||||||
const std::string &device = pair.first;
|
const std::string &seriesName = pair.first;
|
||||||
const GdkRGBA &color = pair.second;
|
const GdkRGBA &color = pair.second;
|
||||||
|
|
||||||
// Create container for legend item
|
// Create container for legend item
|
||||||
GtkWidget *itemBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
|
GtkWidget *itemBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
|
||||||
|
|
||||||
// Create color box (drawing area with fixed color)
|
// Create color dialog and button (modern GTK4 way)
|
||||||
GtkWidget *colorBox = gtk_drawing_area_new();
|
GtkColorDialog *dialog = gtk_color_dialog_new();
|
||||||
gtk_widget_set_size_request(colorBox, 20, 20);
|
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 color in user data
|
// Store series name in user data to know which series to update
|
||||||
GdkRGBA *colorPtr = new GdkRGBA(color);
|
g_object_set_data_full(G_OBJECT(colorButton), "series-name", g_strdup(seriesName.c_str()), g_free);
|
||||||
g_object_set_data_full(G_OBJECT(colorBox), "color", colorPtr,
|
|
||||||
[](gpointer data) { delete static_cast<GdkRGBA*>(data); });
|
|
||||||
|
|
||||||
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(colorBox),
|
g_signal_connect(colorButton, "notify::rgba", G_CALLBACK(onColorSet), this);
|
||||||
[](GtkDrawingArea *area, cairo_t *cr, int width, int height, gpointer data) {
|
|
||||||
GdkRGBA *color = static_cast<GdkRGBA*>(data);
|
|
||||||
cairo_set_source_rgba(cr, color->red, color->green, color->blue, color->alpha);
|
|
||||||
cairo_paint(cr);
|
|
||||||
|
|
||||||
// Get theme colors from GTK settings
|
gtk_box_append(GTK_BOX(itemBox), colorButton);
|
||||||
gboolean isDark = FALSE;
|
|
||||||
GtkSettings *settings = gtk_settings_get_default();
|
|
||||||
if (settings) {
|
|
||||||
gchar *themeName = nullptr;
|
|
||||||
g_object_get(settings, "gtk-theme-name", &themeName, nullptr);
|
|
||||||
if (themeName) {
|
|
||||||
std::string theme(themeName);
|
|
||||||
isDark = (theme.find("dark") != std::string::npos ||
|
|
||||||
theme.find("Dark") != std::string::npos);
|
|
||||||
g_free(themeName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try GSetting if not found in GTK settings
|
// Create label with series name
|
||||||
if (!isDark) {
|
GtkWidget *label = gtk_label_new(seriesName.c_str());
|
||||||
GSettingsSchemaSource *source = g_settings_schema_source_get_default();
|
|
||||||
if (source) {
|
|
||||||
GSettingsSchema *schema = g_settings_schema_source_lookup(source, "org.gnome.desktop.interface", FALSE);
|
|
||||||
if (schema) {
|
|
||||||
GSettings *gsettings = g_settings_new("org.gnome.desktop.interface");
|
|
||||||
if (gsettings) {
|
|
||||||
gchar *gtkTheme = g_settings_get_string(gsettings, "gtk-theme");
|
|
||||||
if (gtkTheme) {
|
|
||||||
std::string theme(gtkTheme);
|
|
||||||
isDark = (theme.find("dark") != std::string::npos ||
|
|
||||||
theme.find("Dark") != std::string::npos);
|
|
||||||
g_free(gtkTheme);
|
|
||||||
}
|
|
||||||
g_object_unref(gsettings);
|
|
||||||
}
|
|
||||||
g_settings_schema_unref(schema);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDark) {
|
|
||||||
cairo_set_source_rgb(cr, 0.6, 0.6, 0.6); // Lighter gray for dark theme
|
|
||||||
} else {
|
|
||||||
cairo_set_source_rgb(cr, 0.3, 0.3, 0.3); // Darker gray for light theme
|
|
||||||
}
|
|
||||||
cairo_set_line_width(cr, 1);
|
|
||||||
cairo_rectangle(cr, 0, 0, width, height);
|
|
||||||
cairo_stroke(cr);
|
|
||||||
}, colorPtr, nullptr);
|
|
||||||
|
|
||||||
gtk_box_append(GTK_BOX(itemBox), colorBox);
|
|
||||||
|
|
||||||
// Create label with device name
|
|
||||||
GtkWidget *label = gtk_label_new(device.c_str());
|
|
||||||
gtk_box_append(GTK_BOX(itemBox), label);
|
gtk_box_append(GTK_BOX(itemBox), label);
|
||||||
|
|
||||||
gtk_box_append(GTK_BOX(legendBox), itemBox);
|
gtk_box_append(GTK_BOX(legendBox), itemBox);
|
||||||
@ -318,3 +273,16 @@ void MainWindow::show()
|
|||||||
{
|
{
|
||||||
gtk_widget_set_visible(window, TRUE);
|
gtk_widget_set_visible(window, TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::onColorSet(GObject *object, GParamSpec *pspec, gpointer userData)
|
||||||
|
{
|
||||||
|
MainWindow *self = static_cast<MainWindow*>(userData);
|
||||||
|
const char *seriesName = static_cast<const char*>(g_object_get_data(object, "series-name"));
|
||||||
|
|
||||||
|
if (seriesName) {
|
||||||
|
const GdkRGBA *color = gtk_color_dialog_button_get_rgba(GTK_COLOR_DIALOG_BUTTON(object));
|
||||||
|
if (color) {
|
||||||
|
self->chart->setSeriesColor(seriesName, *color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,164 +0,0 @@
|
|||||||
#include "nvme_monitor.h"
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <fstream>
|
|
||||||
#include <sstream>
|
|
||||||
#include <regex>
|
|
||||||
#include <dirent.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <iostream>
|
|
||||||
#include <cctype>
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
NvmeMonitor::NvmeMonitor()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::string> NvmeMonitor::getAvailableDevices()
|
|
||||||
{
|
|
||||||
return scanDevices();
|
|
||||||
}
|
|
||||||
|
|
||||||
double NvmeMonitor::readTemperatureFromFile(const std::string &filePath)
|
|
||||||
{
|
|
||||||
std::ifstream file(filePath);
|
|
||||||
if (!file.is_open()) {
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string line;
|
|
||||||
if (std::getline(file, line)) {
|
|
||||||
try {
|
|
||||||
// Temperature is in millidegrees Celsius, convert to Celsius
|
|
||||||
long tempMilliC = std::stol(line);
|
|
||||||
return tempMilliC / 1000.0;
|
|
||||||
} catch (const std::exception &e) {
|
|
||||||
std::cerr << "Failed to parse temperature from " << filePath << ": " << e.what() << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
file.close();
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::map<std::string, std::string> NvmeMonitor::findHwmonDevices()
|
|
||||||
{
|
|
||||||
std::map<std::string, std::string> hwmonMap; // Maps device name to hwmon path
|
|
||||||
|
|
||||||
DIR* hwmonDir = opendir("/sys/class/hwmon");
|
|
||||||
if (!hwmonDir) return hwmonMap;
|
|
||||||
|
|
||||||
struct dirent* entry;
|
|
||||||
while ((entry = readdir(hwmonDir)) != nullptr) {
|
|
||||||
std::string hwmonName = entry->d_name;
|
|
||||||
|
|
||||||
// Look for hwmon* directories
|
|
||||||
if (hwmonName.find("hwmon") == 0) {
|
|
||||||
std::string namePath = "/sys/class/hwmon/" + hwmonName + "/name";
|
|
||||||
std::ifstream nameFile(namePath);
|
|
||||||
if (nameFile.is_open()) {
|
|
||||||
std::string deviceName;
|
|
||||||
if (std::getline(nameFile, deviceName)) {
|
|
||||||
// Remove trailing whitespace
|
|
||||||
deviceName.erase(deviceName.find_last_not_of(" \n\r\t") + 1);
|
|
||||||
|
|
||||||
if (deviceName == "nvme") {
|
|
||||||
hwmonMap[hwmonName] = "/sys/class/hwmon/" + hwmonName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nameFile.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
closedir(hwmonDir);
|
|
||||||
|
|
||||||
return hwmonMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::string> NvmeMonitor::scanDevices()
|
|
||||||
{
|
|
||||||
std::vector<std::string> devices;
|
|
||||||
|
|
||||||
DIR* devDir = opendir("/dev");
|
|
||||||
if (!devDir) return devices;
|
|
||||||
|
|
||||||
struct dirent* entry;
|
|
||||||
while ((entry = readdir(devDir)) != nullptr) {
|
|
||||||
std::string name = entry->d_name;
|
|
||||||
|
|
||||||
// Only include main device entries (nvme0, nvme1, etc.), not namespaces or fabrics
|
|
||||||
if (name.find("nvme") == 0 && name.length() > 4) {
|
|
||||||
// Check if the rest after "nvme" contains only digits
|
|
||||||
bool onlyDigits = true;
|
|
||||||
for (size_t i = 4; i < name.length(); i++) {
|
|
||||||
if (!std::isdigit(name[i])) {
|
|
||||||
onlyDigits = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (onlyDigits) {
|
|
||||||
devices.push_back("/dev/" + name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
closedir(devDir);
|
|
||||||
|
|
||||||
return devices;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::map<std::string, double> NvmeMonitor::readTemperatures(const std::string &device)
|
|
||||||
{
|
|
||||||
std::map<std::string, double> temperatures;
|
|
||||||
|
|
||||||
// Map hwmon devices to NVMe devices
|
|
||||||
auto hwmonMap = findHwmonDevices();
|
|
||||||
|
|
||||||
if (hwmonMap.empty()) {
|
|
||||||
std::cerr << "No NVMe hwmon devices found in /sys/class/hwmon" << std::endl;
|
|
||||||
return temperatures;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For now, read from the first NVMe device found
|
|
||||||
// In a more sophisticated implementation, we could map specific nvme devices to hwmon entries
|
|
||||||
auto firstHwmon = hwmonMap.begin();
|
|
||||||
std::string hwmonPath = firstHwmon->second;
|
|
||||||
|
|
||||||
// Read temperature sensors from hwmon
|
|
||||||
// temp1_input is typically the main sensor
|
|
||||||
double temp1 = readTemperatureFromFile(hwmonPath + "/temp1_input");
|
|
||||||
if (temp1 > 0) {
|
|
||||||
temperatures["Main"] = temp1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for additional temperature sensors (temp2, temp3, etc.)
|
|
||||||
for (int i = 2; i <= 10; i++) {
|
|
||||||
std::string tempPath = hwmonPath + "/temp" + std::to_string(i) + "_input";
|
|
||||||
struct stat buffer;
|
|
||||||
if (stat(tempPath.c_str(), &buffer) == 0) {
|
|
||||||
double temp = readTemperatureFromFile(tempPath);
|
|
||||||
if (temp > 0) {
|
|
||||||
std::string sensorName = "Sensor " + std::to_string(i);
|
|
||||||
temperatures[sensorName] = temp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (temperatures.empty()) {
|
|
||||||
std::cerr << "Failed to read temperatures from device: " << device << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
return temperatures;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::map<std::string, std::map<std::string, double>> NvmeMonitor::getAllTemperatures()
|
|
||||||
{
|
|
||||||
std::map<std::string, std::map<std::string, double>> allTemperatures;
|
|
||||||
|
|
||||||
std::vector<std::string> devices = scanDevices();
|
|
||||||
for (const auto &device : devices) {
|
|
||||||
auto temps = readTemperatures(device);
|
|
||||||
if (!temps.empty()) {
|
|
||||||
allTemperatures[device] = temps;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return allTemperatures;
|
|
||||||
}
|
|
||||||
124
src/temp_monitor.cpp
Normal file
124
src/temp_monitor.cpp
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
#include "temp_monitor.h"
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
TempMonitor::TempMonitor()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> TempMonitor::getAvailableDevices()
|
||||||
|
{
|
||||||
|
std::vector<std::string> devices;
|
||||||
|
auto all = getAllTemperatures();
|
||||||
|
for (auto const& [name, sensors] : all) {
|
||||||
|
devices.push_back(name);
|
||||||
|
}
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
double TempMonitor::readTemperatureFromFile(const std::string &filePath)
|
||||||
|
{
|
||||||
|
std::ifstream file(filePath);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
return -1000.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
if (std::getline(file, line)) {
|
||||||
|
try {
|
||||||
|
long tempMilliC = std::stol(line);
|
||||||
|
return tempMilliC / 1000.0;
|
||||||
|
} catch (...) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1000.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<std::string, double> TempMonitor::readTemperatures(const std::string &device)
|
||||||
|
{
|
||||||
|
auto all = getAllTemperatures();
|
||||||
|
if (all.count(device)) {
|
||||||
|
return all.at(device);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<std::string, std::map<std::string, double>> TempMonitor::getAllTemperatures()
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closedir(hwmonDir);
|
||||||
|
|
||||||
|
return allTemperatures;
|
||||||
|
}
|
||||||
@ -60,8 +60,14 @@ TemperatureChart::~TemperatureChart()
|
|||||||
{
|
{
|
||||||
if (tickHandler) {
|
if (tickHandler) {
|
||||||
g_source_remove(tickHandler);
|
g_source_remove(tickHandler);
|
||||||
|
tickHandler = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tooltipWindow) {
|
||||||
|
gtk_widget_unparent(tooltipWindow);
|
||||||
|
tooltipWindow = nullptr;
|
||||||
|
tooltipLabel = nullptr;
|
||||||
}
|
}
|
||||||
hideTooltip();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TemperatureChart::setupColors()
|
void TemperatureChart::setupColors()
|
||||||
@ -143,9 +149,9 @@ void TemperatureChart::updateThemeColors()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GdkRGBA TemperatureChart::getColorForDevice(const std::string &device)
|
GdkRGBA TemperatureChart::getColorForSeries(const std::string &seriesKey)
|
||||||
{
|
{
|
||||||
if (colorMap.find(device) == colorMap.end()) {
|
if (colorMap.find(seriesKey) == colorMap.end()) {
|
||||||
const GdkRGBA colors[] = {
|
const GdkRGBA colors[] = {
|
||||||
{1.0, 0.0, 0.0, 1.0},
|
{1.0, 0.0, 0.0, 1.0},
|
||||||
{0.0, 0.0, 1.0, 1.0},
|
{0.0, 0.0, 1.0, 1.0},
|
||||||
@ -158,23 +164,25 @@ GdkRGBA TemperatureChart::getColorForDevice(const std::string &device)
|
|||||||
};
|
};
|
||||||
|
|
||||||
int colorIndex = colorMap.size() % 8;
|
int colorIndex = colorMap.size() % 8;
|
||||||
colorMap[device] = colors[colorIndex];
|
colorMap[seriesKey] = colors[colorIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
return colorMap[device];
|
return colorMap[seriesKey];
|
||||||
}
|
}
|
||||||
|
|
||||||
void TemperatureChart::addTemperatureData(const std::string &device, const std::string &sensor,
|
bool TemperatureChart::addTemperatureData(const std::string &device, const std::string &sensor,
|
||||||
double temperature, int64_t timestamp)
|
double temperature, int64_t timestamp)
|
||||||
{
|
{
|
||||||
std::string seriesKey = device + " - " + sensor;
|
std::string seriesKey = device + " - " + sensor;
|
||||||
|
bool isNew = false;
|
||||||
|
|
||||||
// Create series if it doesn't exist
|
// Create series if it doesn't exist
|
||||||
if (seriesMap.find(seriesKey) == seriesMap.end()) {
|
if (seriesMap.find(seriesKey) == seriesMap.end()) {
|
||||||
SeriesData series;
|
SeriesData series;
|
||||||
series.color = getColorForDevice(device);
|
series.color = getColorForSeries(seriesKey);
|
||||||
series.name = seriesKey;
|
series.name = seriesKey;
|
||||||
seriesMap[seriesKey] = series;
|
seriesMap[seriesKey] = series;
|
||||||
|
isNew = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add data point
|
// Add data point
|
||||||
@ -204,6 +212,8 @@ void TemperatureChart::addTemperatureData(const std::string &device, const std::
|
|||||||
|
|
||||||
// Trigger redraw
|
// Trigger redraw
|
||||||
gtk_widget_queue_draw(drawingArea);
|
gtk_widget_queue_draw(drawingArea);
|
||||||
|
|
||||||
|
return isNew;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TemperatureChart::clear()
|
void TemperatureChart::clear()
|
||||||
@ -217,7 +227,7 @@ void TemperatureChart::clear()
|
|||||||
gtk_widget_queue_draw(drawingArea);
|
gtk_widget_queue_draw(drawingArea);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::pair<std::string, GdkRGBA>> TemperatureChart::getDeviceColors() const
|
std::vector<std::pair<std::string, GdkRGBA>> TemperatureChart::getSeriesColors() const
|
||||||
{
|
{
|
||||||
std::vector<std::pair<std::string, GdkRGBA>> result;
|
std::vector<std::pair<std::string, GdkRGBA>> result;
|
||||||
for (const auto &entry : colorMap) {
|
for (const auto &entry : colorMap) {
|
||||||
@ -226,6 +236,19 @@ std::vector<std::pair<std::string, GdkRGBA>> TemperatureChart::getDeviceColors()
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TemperatureChart::setSeriesColor(const std::string &seriesName, const GdkRGBA &color)
|
||||||
|
{
|
||||||
|
// Update color map
|
||||||
|
colorMap[seriesName] = color;
|
||||||
|
|
||||||
|
// Update series if it exists
|
||||||
|
if (seriesMap.find(seriesName) != seriesMap.end()) {
|
||||||
|
seriesMap[seriesName].color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
gtk_widget_queue_draw(drawingArea);
|
||||||
|
}
|
||||||
|
|
||||||
gboolean TemperatureChart::onTick(gpointer userData)
|
gboolean TemperatureChart::onTick(gpointer userData)
|
||||||
{
|
{
|
||||||
TemperatureChart *self = static_cast<TemperatureChart*>(userData);
|
TemperatureChart *self = static_cast<TemperatureChart*>(userData);
|
||||||
@ -253,7 +276,7 @@ void TemperatureChart::drawChart(GtkDrawingArea *area, cairo_t *cr, int width, i
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Margins
|
// Margins
|
||||||
double marginLeft = 70, marginRight = 20, marginTop = 40, marginBottom = 40;
|
double marginLeft = 20, marginRight = 70, marginTop = 40, marginBottom = 40;
|
||||||
double plotWidth = width - marginLeft - marginRight;
|
double plotWidth = width - marginLeft - marginRight;
|
||||||
double plotHeight = height - marginTop - marginBottom;
|
double plotHeight = height - marginTop - marginBottom;
|
||||||
|
|
||||||
@ -280,9 +303,9 @@ void TemperatureChart::drawChart(GtkDrawingArea *area, cairo_t *cr, int width, i
|
|||||||
// Draw axes
|
// Draw axes
|
||||||
cairo_set_source_rgb(cr, axisColor.red, axisColor.green, axisColor.blue);
|
cairo_set_source_rgb(cr, axisColor.red, axisColor.green, axisColor.blue);
|
||||||
cairo_set_line_width(cr, 2);
|
cairo_set_line_width(cr, 2);
|
||||||
cairo_move_to(cr, marginLeft, marginTop);
|
cairo_move_to(cr, marginLeft + plotWidth, marginTop);
|
||||||
cairo_line_to(cr, marginLeft, marginTop + plotHeight);
|
|
||||||
cairo_line_to(cr, marginLeft + plotWidth, marginTop + plotHeight);
|
cairo_line_to(cr, marginLeft + plotWidth, marginTop + plotHeight);
|
||||||
|
cairo_line_to(cr, marginLeft, marginTop + plotHeight);
|
||||||
cairo_stroke(cr);
|
cairo_stroke(cr);
|
||||||
|
|
||||||
// Draw axis labels
|
// Draw axis labels
|
||||||
@ -297,12 +320,12 @@ void TemperatureChart::drawChart(GtkDrawingArea *area, cairo_t *cr, int width, i
|
|||||||
char tempStr[16];
|
char tempStr[16];
|
||||||
snprintf(tempStr, sizeof(tempStr), "%.0f°C", temp);
|
snprintf(tempStr, sizeof(tempStr), "%.0f°C", temp);
|
||||||
|
|
||||||
// Measure text width for proper right alignment
|
// Measure text width for proper alignment
|
||||||
cairo_text_extents_t extents;
|
cairo_text_extents_t extents;
|
||||||
cairo_text_extents(cr, tempStr, &extents);
|
cairo_text_extents(cr, tempStr, &extents);
|
||||||
|
|
||||||
// Position text right-aligned, 8 pixels before the plot area
|
// Position text 8 pixels after the plot area on the right
|
||||||
double textX = marginLeft - 8 - extents.width;
|
double textX = marginLeft + plotWidth + 8;
|
||||||
cairo_move_to(cr, textX, y + 4);
|
cairo_move_to(cr, textX, y + 4);
|
||||||
cairo_show_text(cr, tempStr);
|
cairo_show_text(cr, tempStr);
|
||||||
}
|
}
|
||||||
@ -331,7 +354,7 @@ void TemperatureChart::drawChart(GtkDrawingArea *area, cairo_t *cr, int width, i
|
|||||||
// Set series color
|
// Set series color
|
||||||
cairo_set_source_rgba(cr, series.color.red, series.color.green,
|
cairo_set_source_rgba(cr, series.color.red, series.color.green,
|
||||||
series.color.blue, series.color.alpha);
|
series.color.blue, series.color.alpha);
|
||||||
cairo_set_line_width(cr, 2);
|
cairo_set_line_width(cr, 2.0);
|
||||||
|
|
||||||
bool firstPoint = true;
|
bool firstPoint = true;
|
||||||
for (const DataPoint &point : series.points) {
|
for (const DataPoint &point : series.points) {
|
||||||
@ -347,17 +370,6 @@ void TemperatureChart::drawChart(GtkDrawingArea *area, cairo_t *cr, int width, i
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
cairo_stroke(cr);
|
cairo_stroke(cr);
|
||||||
|
|
||||||
// Draw dots at data points
|
|
||||||
cairo_set_source_rgba(cr, series.color.red, series.color.green,
|
|
||||||
series.color.blue, series.color.alpha);
|
|
||||||
for (const DataPoint &point : series.points) {
|
|
||||||
double x = marginLeft + plotWidth * (1.0 - (double)(maxTime - point.timestamp) / MAX_TIME_MS);
|
|
||||||
double y = marginTop + plotHeight * (1.0 - (point.temperature - minTemp) / (maxTemp - minTemp));
|
|
||||||
|
|
||||||
cairo_arc(cr, x, y, 3, 0, 2 * M_PI);
|
|
||||||
cairo_fill(cr);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw title
|
// Draw title
|
||||||
@ -379,7 +391,7 @@ TemperatureChart::NearestPoint TemperatureChart::findNearestDataPoint(double mou
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Same margins as in drawChart
|
// Same margins as in drawChart
|
||||||
double marginLeft = 70, marginRight = 20, marginTop = 40, marginBottom = 40;
|
double marginLeft = 20, marginRight = 70, marginTop = 40, marginBottom = 40;
|
||||||
double plotWidth = width - marginLeft - marginRight;
|
double plotWidth = width - marginLeft - marginRight;
|
||||||
double plotHeight = height - marginTop - marginBottom;
|
double plotHeight = height - marginTop - marginBottom;
|
||||||
|
|
||||||
@ -421,6 +433,8 @@ void TemperatureChart::showTooltip(double x, double y, const NearestPoint &point
|
|||||||
gtk_widget_set_margin_bottom(tooltipLabel, 5);
|
gtk_widget_set_margin_bottom(tooltipLabel, 5);
|
||||||
gtk_popover_set_child(GTK_POPOVER(tooltipWindow), tooltipLabel);
|
gtk_popover_set_child(GTK_POPOVER(tooltipWindow), tooltipLabel);
|
||||||
gtk_popover_set_position(GTK_POPOVER(tooltipWindow), GTK_POS_TOP);
|
gtk_popover_set_position(GTK_POPOVER(tooltipWindow), GTK_POS_TOP);
|
||||||
|
gtk_popover_set_has_arrow(GTK_POPOVER(tooltipWindow), TRUE);
|
||||||
|
gtk_popover_set_autohide(GTK_POPOVER(tooltipWindow), FALSE);
|
||||||
gtk_widget_set_parent(tooltipWindow, drawingArea);
|
gtk_widget_set_parent(tooltipWindow, drawingArea);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -431,8 +445,10 @@ void TemperatureChart::showTooltip(double x, double y, const NearestPoint &point
|
|||||||
time_t timeSeconds = point.timestamp / 1000;
|
time_t timeSeconds = point.timestamp / 1000;
|
||||||
struct tm *timeinfo = localtime(&timeSeconds);
|
struct tm *timeinfo = localtime(&timeSeconds);
|
||||||
|
|
||||||
char timeStr[32];
|
char timeStr[32] = "??:??:??";
|
||||||
|
if (timeinfo) {
|
||||||
strftime(timeStr, sizeof(timeStr), "%H:%M:%S", timeinfo);
|
strftime(timeStr, sizeof(timeStr), "%H:%M:%S", timeinfo);
|
||||||
|
}
|
||||||
|
|
||||||
snprintf(tooltipText, sizeof(tooltipText),
|
snprintf(tooltipText, sizeof(tooltipText),
|
||||||
"%s\n%.1f°C\n%s",
|
"%s\n%.1f°C\n%s",
|
||||||
@ -450,15 +466,15 @@ void TemperatureChart::showTooltip(double x, double y, const NearestPoint &point
|
|||||||
rect.height = 1;
|
rect.height = 1;
|
||||||
gtk_popover_set_pointing_to(GTK_POPOVER(tooltipWindow), &rect);
|
gtk_popover_set_pointing_to(GTK_POPOVER(tooltipWindow), &rect);
|
||||||
|
|
||||||
|
if (!gtk_widget_get_visible(tooltipWindow)) {
|
||||||
gtk_widget_set_visible(tooltipWindow, TRUE);
|
gtk_widget_set_visible(tooltipWindow, TRUE);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void TemperatureChart::hideTooltip()
|
void TemperatureChart::hideTooltip()
|
||||||
{
|
{
|
||||||
if (tooltipWindow) {
|
if (tooltipWindow && gtk_widget_get_visible(tooltipWindow)) {
|
||||||
gtk_widget_unparent(tooltipWindow);
|
gtk_widget_set_visible(tooltipWindow, FALSE);
|
||||||
tooltipWindow = nullptr;
|
|
||||||
tooltipLabel = nullptr;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user