diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d482a1..c90d5b5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED gtk4) pkg_check_modules(CAIRO REQUIRED cairo) +find_package(X11) # Find glib-compile-resources tool find_program(GLIB_COMPILE_RESOURCES glib-compile-resources REQUIRED) @@ -52,6 +53,10 @@ target_link_libraries(temp-monitor m ) +if(X11_FOUND) + target_link_libraries(temp-monitor ${X11_LIBRARIES}) +endif() + target_compile_options(temp-monitor PRIVATE ${GTK_CFLAGS_OTHER}) target_compile_options(temp-monitor PRIVATE ${CAIRO_CFLAGS_OTHER}) diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..9f68da6 Binary files /dev/null and b/icon.png differ diff --git a/include/config_manager.h b/include/config_manager.h index 113631e..1237567 100644 --- a/include/config_manager.h +++ b/include/config_manager.h @@ -17,11 +17,17 @@ public: // Getters and setters int getWindowWidth() const { return windowWidth; } int getWindowHeight() const { return windowHeight; } + int getWindowX() const { return windowX; } + int getWindowY() const { return windowY; } + bool hasWindowPosition() const { return hasSavedWindowPosition; } int getPollingTime() const { return pollingTime; } int getHistoryLength() const { return historyLength; } void setWindowWidth(int w) { windowWidth = w; } void setWindowHeight(int h) { windowHeight = h; } + void setWindowX(int x) { windowX = x; hasSavedWindowPosition = true; } + void setWindowY(int y) { windowY = y; hasSavedWindowPosition = true; } + void clearWindowPosition() { hasSavedWindowPosition = false; } void setPollingTime(int t) { pollingTime = t; } void setHistoryLength(int m) { historyLength = m; } @@ -39,6 +45,9 @@ private: mutable std::string cachedConfigPath; int windowWidth; int windowHeight; + int windowX; + int windowY; + bool hasSavedWindowPosition; int pollingTime; int historyLength; diff --git a/include/mainwindow.h b/include/mainwindow.h index 8cc3905..023c213 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -27,9 +27,13 @@ private: static void onHistoryLengthChanged(GtkSpinButton *spinButton, gpointer userData); static void onClearButtonClicked(GtkButton *button, gpointer userData); static void onQuitButtonClicked(GtkButton *button, gpointer userData); + static void onWindowMap(GtkWidget *widget, gpointer userData); static void onColorSet(GObject *object, GParamSpec *pspec, gpointer userData); static void onNameChanged(GtkEditable *editable, gpointer userData); static void onVisibilityToggled(GtkCheckButton *checkButton, gpointer userData); + + bool getWindowPosition(int &x, int &y) const; + void applySavedWindowPosition(); GtkWidget *window; GtkWidget *statusLabel; @@ -42,6 +46,7 @@ private: int refreshRateSec; GtkWidget *legendBox; std::map tempLabels; + bool restoreWindowPositionPending; }; // Global main loop reference for proper shutdown diff --git a/src/config_manager.cpp b/src/config_manager.cpp index 216401b..9a3faca 100644 --- a/src/config_manager.cpp +++ b/src/config_manager.cpp @@ -7,7 +7,9 @@ #include ConfigManager::ConfigManager() - : windowWidth(1200), windowHeight(700), pollingTime(1), historyLength(10) + : windowWidth(1200), windowHeight(700), + windowX(0), windowY(0), hasSavedWindowPosition(false), + pollingTime(1), historyLength(10) { } @@ -73,6 +75,12 @@ void ConfigManager::load() windowWidth = std::stoi(value); } else if (key == "window_height") { windowHeight = std::stoi(value); + } else if (key == "window_x") { + windowX = std::stoi(value); + hasSavedWindowPosition = true; + } else if (key == "window_y") { + windowY = std::stoi(value); + hasSavedWindowPosition = true; } else if (key == "polling_time") { pollingTime = std::stoi(value); } else if (key == "history_length") { @@ -107,6 +115,10 @@ void ConfigManager::save() file << "# Auto-generated, do not edit manually\n\n"; file << "window_width = " << windowWidth << "\n"; file << "window_height = " << windowHeight << "\n"; + if (hasSavedWindowPosition) { + file << "window_x = " << windowX << "\n"; + file << "window_y = " << windowY << "\n"; + } file << "polling_time = " << pollingTime << "\n"; file << "history_length = " << historyLength << "\n"; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index b8d9f7a..45385a1 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -7,10 +7,18 @@ #include #include +#if defined(__has_include) +#if __has_include() +#include +#include +#define TEMP_MONITOR_HAS_GDK_X11 1 +#endif +#endif + MainWindow::MainWindow() : window(nullptr), statusLabel(nullptr), refreshRateSpinBox(nullptr), historyLengthSpinBox(nullptr), - timerID(0), refreshRateSec(3) + timerID(0), refreshRateSec(3), restoreWindowPositionPending(false) { monitor = std::make_unique(); config = std::make_unique(); @@ -28,6 +36,7 @@ MainWindow::MainWindow() // Set window size from config gtk_window_set_default_size(GTK_WINDOW(window), config->getWindowWidth(), config->getWindowHeight()); + restoreWindowPositionPending = config->hasWindowPosition(); // Start update timer timerID = g_timeout_add(refreshRateSec * 1000, onUpdateTimer, this); @@ -76,6 +85,7 @@ void MainWindow::setupUI() gtk_window_set_default_size(GTK_WINDOW(window), 1200, 700); g_signal_connect(window, "close-request", G_CALLBACK(onDeleteWindow), this); + g_signal_connect(window, "map", G_CALLBACK(onWindowMap), this); // Main box GtkWidget *mainBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); @@ -208,11 +218,22 @@ void MainWindow::onQuitButtonClicked(GtkButton *button, gpointer userData) void MainWindow::saveWindowState() { - int width, height; - gtk_window_get_default_size(GTK_WINDOW(window), &width, &height); + int width = gtk_widget_get_width(window); + int height = gtk_widget_get_height(window); + if (width <= 0 || height <= 0) { + gtk_window_get_default_size(GTK_WINDOW(window), &width, &height); + } config->setWindowWidth(width > 0 ? width : 1200); config->setWindowHeight(height > 0 ? height : 700); + + int posX = 0; + int posY = 0; + if (getWindowPosition(posX, posY)) { + config->setWindowX(posX); + config->setWindowY(posY); + } + config->setPollingTime(refreshRateSec); config->save(); } @@ -362,6 +383,81 @@ void MainWindow::show() gtk_widget_set_visible(window, TRUE); } +void MainWindow::onWindowMap(GtkWidget *widget, gpointer userData) +{ + (void)widget; + MainWindow *self = static_cast(userData); + self->applySavedWindowPosition(); +} + +void MainWindow::applySavedWindowPosition() +{ + if (!restoreWindowPositionPending || !config->hasWindowPosition()) { + return; + } + + restoreWindowPositionPending = false; + +#ifdef TEMP_MONITOR_HAS_GDK_X11 + GdkDisplay *display = gtk_widget_get_display(window); + if (!display || !GDK_IS_X11_DISPLAY(display)) { + return; + } + + GdkSurface *surface = gtk_native_get_surface(GTK_NATIVE(window)); + if (!surface || !GDK_IS_X11_SURFACE(surface)) { + return; + } + + Display *xDisplay = gdk_x11_display_get_xdisplay(GDK_X11_DISPLAY(display)); + if (!xDisplay) { + return; + } + + Window xid = gdk_x11_surface_get_xid(surface); + XMoveWindow(xDisplay, xid, config->getWindowX(), config->getWindowY()); + XFlush(xDisplay); +#endif +} + +bool MainWindow::getWindowPosition(int &x, int &y) const +{ +#ifdef TEMP_MONITOR_HAS_GDK_X11 + GdkDisplay *display = gtk_widget_get_display(window); + if (!display || !GDK_IS_X11_DISPLAY(display)) { + return false; + } + + GdkSurface *surface = gtk_native_get_surface(GTK_NATIVE(window)); + if (!surface || !GDK_IS_X11_SURFACE(surface)) { + return false; + } + + Display *xDisplay = gdk_x11_display_get_xdisplay(GDK_X11_DISPLAY(display)); + if (!xDisplay) { + return false; + } + + Window xid = gdk_x11_surface_get_xid(surface); + Window root = DefaultRootWindow(xDisplay); + Window child = 0; + int absX = 0; + int absY = 0; + + if (!XTranslateCoordinates(xDisplay, xid, root, 0, 0, &absX, &absY, &child)) { + return false; + } + + x = absX; + y = absY; + return true; +#else + (void)x; + (void)y; + return false; +#endif +} + void MainWindow::onColorSet(GObject *object, GParamSpec *pspec, gpointer userData) { MainWindow *self = static_cast(userData); diff --git a/src/temp_monitor.cpp b/src/temp_monitor.cpp index 88b1369..1a7590e 100644 --- a/src/temp_monitor.cpp +++ b/src/temp_monitor.cpp @@ -6,17 +6,36 @@ #include #include #include +#include + +namespace { +bool isDebugLoggingEnabled() +{ + static const bool enabled = [] { + const char* envValue = std::getenv("TEMP_MONITOR_DEBUG"); + return envValue && std::strcmp(envValue, "1") == 0; + }(); + return enabled; +} +} TempMonitor::TempMonitor() { - std::cerr << "[DEBUG] TempMonitor constructor - starting sensor discovery" << std::endl; + if (isDebugLoggingEnabled()) { + std::cerr << "[DEBUG] TempMonitor constructor - starting sensor discovery" << std::endl; + } discoverSensors(); - std::cerr << "[DEBUG] Sensor discovery complete - found " << discoveredSensors.size() << " sensors" << std::endl; + if (isDebugLoggingEnabled()) { + std::cerr << "[DEBUG] Sensor discovery complete - found " << discoveredSensors.size() << " sensors" << std::endl; + } } void TempMonitor::discoverSensors() { - std::cerr << "[DEBUG] discoverSensors() started" << std::endl; + const bool debug = isDebugLoggingEnabled(); + if (debug) { + std::cerr << "[DEBUG] discoverSensors() started" << std::endl; + } discoveredSensors.clear(); DIR* hwmonDir = opendir("/sys/class/hwmon"); if (!hwmonDir) { @@ -24,13 +43,17 @@ void TempMonitor::discoverSensors() return; } - std::cerr << "[DEBUG] Opened /sys/class/hwmon successfully" << std::endl; + if (debug) { + std::cerr << "[DEBUG] Opened /sys/class/hwmon successfully" << std::endl; + } struct dirent* entry; while ((entry = readdir(hwmonDir)) != nullptr) { std::string hwmonName = entry->d_name; if (hwmonName.find("hwmon") != 0) continue; - std::cerr << "[DEBUG] Processing hwmon device: " << hwmonName << std::endl; + if (debug) { + std::cerr << "[DEBUG] Processing hwmon device: " << hwmonName << std::endl; + } std::string path = "/sys/class/hwmon/" + hwmonName; // Read "name" from sysfs @@ -42,24 +65,32 @@ void TempMonitor::discoverSensors() nameFile.close(); } - std::cerr << "[DEBUG] Device name: " << nameFromSysfs << std::endl; + if (debug) { + std::cerr << "[DEBUG] Device name: " << nameFromSysfs << std::endl; + } // Filter: Only interested in nvme and coretemp (CPU) if (nameFromSysfs != "nvme" && nameFromSysfs != "coretemp") { - std::cerr << "[DEBUG] Skipped - not nvme or coretemp" << std::endl; + if (debug) { + std::cerr << "[DEBUG] Skipped - not nvme or coretemp" << std::endl; + } continue; } DIR* dDir = opendir(path.c_str()); if (dDir) { - std::cerr << "[DEBUG] Opened directory: " << path << std::endl; + if (debug) { + std::cerr << "[DEBUG] Opened directory: " << path << std::endl; + } 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); - std::cerr << "[DEBUG] Found temperature file: " << fname << " (id: " << id << ")" << std::endl; + if (debug) { + std::cerr << "[DEBUG] Found temperature file: " << fname << " (id: " << id << ")" << std::endl; + } // Use "label" from sysfs for sensor name if available std::string labelFromSysfs = ""; @@ -70,12 +101,16 @@ void TempMonitor::discoverSensors() labelFile.close(); } - std::cerr << "[DEBUG] Label: '" << labelFromSysfs << "'" << std::endl; + if (debug) { + std::cerr << "[DEBUG] Label: '" << labelFromSysfs << "'" << std::endl; + } // For CPU (coretemp), filter only Package id 0 (if label exists) if (nameFromSysfs == "coretemp") { if (!labelFromSysfs.empty() && labelFromSysfs != "Package id 0") { - std::cerr << "[DEBUG] Filtered (coretemp, not Package id 0)" << std::endl; + if (debug) { + std::cerr << "[DEBUG] Filtered (coretemp, not Package id 0)" << std::endl; + } continue; } } @@ -83,8 +118,10 @@ void TempMonitor::discoverSensors() std::string finalSensorName = labelFromSysfs.empty() ? "temp" + id : labelFromSysfs; std::string deviceDisplayName = nameFromSysfs + " (" + hwmonName + ")"; - std::cerr << "[DEBUG] Added sensor: " << finalSensorName << " from " << deviceDisplayName << std::endl; - std::cerr << "[DEBUG] Path: " << path + "/" + fname << std::endl; + if (debug) { + std::cerr << "[DEBUG] Added sensor: " << finalSensorName << " from " << deviceDisplayName << std::endl; + std::cerr << "[DEBUG] Path: " << path + "/" + fname << std::endl; + } discoveredSensors.push_back({deviceDisplayName, finalSensorName, path + "/" + fname}); } @@ -95,17 +132,24 @@ void TempMonitor::discoverSensors() } } closedir(hwmonDir); - std::cerr << "[DEBUG] discoverSensors() finished - total sensors: " << discoveredSensors.size() << std::endl; + if (debug) { + std::cerr << "[DEBUG] discoverSensors() finished - total sensors: " << discoveredSensors.size() << std::endl; + } } std::vector TempMonitor::getAvailableDevices() { - std::cerr << "[DEBUG] getAvailableDevices() called" << std::endl; + const bool debug = isDebugLoggingEnabled(); + if (debug) { + std::cerr << "[DEBUG] getAvailableDevices() called" << std::endl; + } std::vector devices; auto all = getAllTemperatures(); for (auto const& [name, sensors] : all) { devices.push_back(name); - std::cerr << "[DEBUG] Device: " << name << " with " << sensors.size() << " sensors" << std::endl; + if (debug) { + std::cerr << "[DEBUG] Device: " << name << " with " << sensors.size() << " sensors" << std::endl; + } } return devices; } @@ -123,7 +167,9 @@ double TempMonitor::readTemperatureFromFile(const std::string &filePath) try { long tempMilliC = std::stol(line); double tempC = tempMilliC / 1000.0; - std::cerr << "[DEBUG] Read temperature from " << filePath << ": " << tempC << "°C" << std::endl; + if (isDebugLoggingEnabled()) { + std::cerr << "[DEBUG] Read temperature from " << filePath << ": " << tempC << "°C" << std::endl; + } return tempC; } catch (...) { std::cerr << "[ERROR] Failed to parse temperature value: " << line << std::endl; @@ -134,11 +180,16 @@ double TempMonitor::readTemperatureFromFile(const std::string &filePath) std::map TempMonitor::readTemperatures(const std::string &device) { - std::cerr << "[DEBUG] readTemperatures() called for device: " << device << std::endl; + const bool debug = isDebugLoggingEnabled(); + if (debug) { + std::cerr << "[DEBUG] readTemperatures() called for device: " << device << std::endl; + } auto all = getAllTemperatures(); if (all.count(device)) { auto sensors = all.at(device); - std::cerr << "[DEBUG] Found " << sensors.size() << " sensors for device" << std::endl; + if (debug) { + std::cerr << "[DEBUG] Found " << sensors.size() << " sensors for device" << std::endl; + } return sensors; } std::cerr << "[ERROR] Device not found: " << device << std::endl; @@ -147,7 +198,10 @@ std::map TempMonitor::readTemperatures(const std::string &d std::map> TempMonitor::getAllTemperatures() { - std::cerr << "[DEBUG] getAllTemperatures() called" << std::endl; + const bool debug = isDebugLoggingEnabled(); + if (debug) { + std::cerr << "[DEBUG] getAllTemperatures() called" << std::endl; + } std::map> allTemperatures; for (const auto& sensor : discoveredSensors) { @@ -157,6 +211,8 @@ std::map> TempMonitor::getAllTemperat } } - std::cerr << "[DEBUG] getAllTemperatures() finished - total devices: " << allTemperatures.size() << std::endl; + if (debug) { + std::cerr << "[DEBUG] getAllTemperatures() finished - total devices: " << allTemperatures.size() << std::endl; + } return allTemperatures; }