From f9fe12a0ea811c7a99abb9c66f750547d8037273 Mon Sep 17 00:00:00 2001 From: collecting Date: Tue, 7 Oct 2025 12:43:47 +0000 Subject: [PATCH 1/7] feat: Add CPU & GPU Temperatures --- src/citron/util/performance_overlay.cpp | 108 +++++++++++++++++++++++- 1 file changed, 106 insertions(+), 2 deletions(-) diff --git a/src/citron/util/performance_overlay.cpp b/src/citron/util/performance_overlay.cpp index 4c8b8db72..d11f258a4 100644 --- a/src/citron/util/performance_overlay.cpp +++ b/src/citron/util/performance_overlay.cpp @@ -14,6 +14,16 @@ #include #include +#include +#include +#include + +#ifdef Q_OS_WIN +#include +#include +#include +#pragma comment(lib, "wbemuuid.lib") // For MSVC, helps the linker find the library +#endif #include "citron/main.h" #include "citron/util/performance_overlay.h" @@ -199,6 +209,11 @@ void PerformanceOverlay::UpdatePerformanceStats() { } } + // Update hardware temperatures every 4th update (every 2 seconds) + if (update_counter % 4 == 0) { + UpdateHardwareTemperatures(); + } + // If we don't have valid data yet, use defaults if (std::isnan(current_fps) || current_fps <= 0.0) { current_fps = 60.0; @@ -222,6 +237,83 @@ void PerformanceOverlay::UpdatePerformanceStats() { update(); } +void PerformanceOverlay::UpdateHardwareTemperatures() { + // Reset data + cpu_temperature = 0.0f; + gpu_temperature = 0.0f; + cpu_sensor_type.clear(); + gpu_sensor_type.clear(); + +#if defined(Q_OS_LINUX) || defined(Q_OS_ANDROID) + QDir thermal_dir(QString::fromUtf8("/sys/class/thermal/")); + QStringList filters{QString::fromUtf8("thermal_zone*")}; + QStringList thermal_zones = thermal_dir.entryList(filters, QDir::Dirs); + + for (const QString& zone_name : thermal_zones) { + QFile type_file(thermal_dir.filePath(zone_name + QString::fromUtf8("/type"))); + if (!type_file.open(QIODevice::ReadOnly | QIODevice::Text)) continue; + QString type = QString::fromUtf8(type_file.readAll()).trimmed(); + type_file.close(); + + QFile temp_file(thermal_dir.filePath(zone_name + QString::fromUtf8("/temp"))); + if (!temp_file.open(QIODevice::ReadOnly | QIODevice::Text)) continue; + float temp = temp_file.readAll().trimmed().toFloat() / 1000.0f; + temp_file.close(); + + if (type.contains(QString::fromUtf8("x86_pkg_temp")) || type.contains(QString::fromUtf8("cpu"))) { + if (temp > cpu_temperature) { + cpu_temperature = temp; + cpu_sensor_type = QString::fromUtf8("CPU Temp"); + } + } else if (type.contains(QString::fromUtf8("radeon")) || type.contains(QString::fromUtf8("amdgpu")) || type.contains(QString::fromUtf8("nvidia")) || type.contains(QString::fromUtf8("nouveau"))) { + if (temp > gpu_temperature) { + gpu_temperature = temp; + gpu_sensor_type = QString::fromUtf8("GPU Temp"); + } + } + } + +#elif defined(Q_OS_WIN) + HRESULT hres; + IWbemLocator* pLoc = nullptr; + IWbemServices* pSvc = nullptr; + + hres = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)&pLoc); + + if (SUCCEEDED(hres)) { + hres = pLoc->ConnectServer(_bstr_t(L"ROOT\\WMI"), NULL, NULL, 0, NULL, 0, 0, &pSvc); + if (SUCCEEDED(hres)) { + hres = CoSetProxyBlanket(pSvc, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, + RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE); + if (SUCCEEDED(hres)) { + IEnumWbemClassObject* pEnumerator = nullptr; + hres = pSvc->ExecQuery(bstr_t("WQL"), bstr_t("SELECT * FROM MSAcpi_ThermalZoneTemperature"), + WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumerator); + if (SUCCEEDED(hres)) { + IWbemClassObject* pclsObj = nullptr; + ULONG uReturn = 0; + while (pEnumerator) { + pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn); + if (uReturn == 0) break; + + VARIANT vtProp; + pclsObj->Get(L"CurrentTemperature", 0, &vtProp, 0, 0); + float temp_kelvin = vtProp.uintVal / 10.0f; + cpu_temperature = temp_kelvin - 273.15f; + cpu_sensor_type = QString::fromUtf8("CPU"); + VariantClear(&vtProp); + pclsObj->Release(); + } + pEnumerator->Release(); + } + } + } + } + if(pSvc) pSvc->Release(); + if(pLoc) pLoc->Release(); +#endif +} + void PerformanceOverlay::UpdatePosition() { if (!main_window) { return; @@ -266,6 +358,18 @@ void PerformanceOverlay::DrawPerformanceInfo(QPainter& painter) { painter.drawText(padding, y_offset, speed_text); y_offset += line_height - 2; + // Draw CPU and GPU Temperatures + if (cpu_temperature > 0.0f && !cpu_sensor_type.isEmpty()) { + QString temp_text = QString::fromUtf8("%1: %2 °C").arg(cpu_sensor_type).arg(cpu_temperature, 0, 'f', 0); + painter.drawText(padding, y_offset, temp_text); + y_offset += line_height - 2; + } + if (gpu_temperature > 0.0f && !gpu_sensor_type.isEmpty()) { + QString temp_text = QString::fromUtf8("%1: %2 °C").arg(gpu_sensor_type).arg(gpu_temperature, 0, 'f', 0); + painter.drawText(padding, y_offset, temp_text); + y_offset += line_height - 2; + } + // Draw shader building info with accent color if (shaders_building > 0) { painter.setPen(QColor(255, 152, 0, 255)); // Material Design orange @@ -314,7 +418,7 @@ void PerformanceOverlay::DrawFrameGraph(QPainter& painter) { QPainterPath graph_path; const int point_count = static_cast(frame_times.size()); - const double x_step = static_cast(graph_width) / (point_count - 1); + const double x_step = static_cast(graph_width) / (std::max(1, point_count - 1)); for (int i = 0; i < point_count; ++i) { const double frame_time = frame_times[i]; @@ -391,4 +495,4 @@ QString PerformanceOverlay::FormatFrameTime(double frame_time_ms) const { return QString::fromUtf8("0.00"); } return QString::number(frame_time_ms, 'f', 2); -} \ No newline at end of file +} From e9feac8b8fe5b09e9047247b6f7451a12d4ec316 Mon Sep 17 00:00:00 2001 From: collecting Date: Tue, 7 Oct 2025 12:44:18 +0000 Subject: [PATCH 2/7] feat: Add CPU & GPU Temperature --- src/citron/util/performance_overlay.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/citron/util/performance_overlay.h b/src/citron/util/performance_overlay.h index 90cafb7e6..73dbf99f2 100644 --- a/src/citron/util/performance_overlay.h +++ b/src/citron/util/performance_overlay.h @@ -37,6 +37,7 @@ private slots: private: void UpdatePosition(); + void UpdateHardwareTemperatures(); void DrawPerformanceInfo(QPainter& painter); void DrawFrameGraph(QPainter& painter); QColor GetFpsColor(double fps) const; @@ -52,6 +53,10 @@ private: double current_frame_time = 0.0; int shaders_building = 0; double emulation_speed = 0.0; + float cpu_temperature = 0.0f; + float gpu_temperature = 0.0f; + QString cpu_sensor_type; + QString gpu_sensor_type; // Frame graph data static constexpr size_t MAX_FRAME_HISTORY = 120; // 2 seconds at 60 FPS @@ -87,4 +92,4 @@ private: bool has_been_moved = false; QPoint drag_start_pos; QPoint widget_start_pos; -}; \ No newline at end of file +}; From 0386af7a1777c01acd82b9cfb058345e2b4b8faa Mon Sep 17 00:00:00 2001 From: collecting Date: Tue, 7 Oct 2025 12:44:46 +0000 Subject: [PATCH 3/7] feat: Add GPU & CPU Temperatures --- src/citron/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/citron/CMakeLists.txt b/src/citron/CMakeLists.txt index 1b18cd17d..c78d36b09 100644 --- a/src/citron/CMakeLists.txt +++ b/src/citron/CMakeLists.txt @@ -391,6 +391,10 @@ if (APPLE) elseif(WIN32) # compile as a win32 gui application instead of a console application target_link_libraries(citron PRIVATE Qt6::EntryPointPrivate) + + # Add Windows-specific libraries for WMI/COM to read hardware sensors + target_link_libraries(citron PRIVATE ole32 oleaut32 wbemuuid) + if(MSVC) target_link_libraries(citron PRIVATE version.lib) set_target_properties(citron PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS") From a9b48aaa396f828508e3a93352f1113a56de621d Mon Sep 17 00:00:00 2001 From: collecting Date: Tue, 7 Oct 2025 13:06:56 +0000 Subject: [PATCH 4/7] feat: Add Color & Fix UI For Temp --- src/citron/util/performance_overlay.cpp | 57 ++++++++++++++++++------- 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/src/citron/util/performance_overlay.cpp b/src/citron/util/performance_overlay.cpp index d11f258a4..9f927eeff 100644 --- a/src/citron/util/performance_overlay.cpp +++ b/src/citron/util/performance_overlay.cpp @@ -49,6 +49,7 @@ PerformanceOverlay::PerformanceOverlay(GMainWindow* parent) border_color = QColor(60, 60, 60, 120); // Subtle border text_color = QColor(220, 220, 220, 255); // Light gray text fps_color = QColor(76, 175, 80, 255); // Material Design green + temperature_color = QColor(76, 175, 80, 255); // Default to green // Graph colors graph_background_color = QColor(40, 40, 40, 100); @@ -230,8 +231,10 @@ void PerformanceOverlay::UpdatePerformanceStats() { AddFrameTime(current_frame_time); } - // Update FPS color based on performance + // Update FPS and Temperature colors based on performance fps_color = GetFpsColor(current_fps); + temperature_color = GetTemperatureColor(std::max(cpu_temperature, gpu_temperature)); + // Trigger a repaint update(); @@ -263,12 +266,12 @@ void PerformanceOverlay::UpdateHardwareTemperatures() { if (type.contains(QString::fromUtf8("x86_pkg_temp")) || type.contains(QString::fromUtf8("cpu"))) { if (temp > cpu_temperature) { cpu_temperature = temp; - cpu_sensor_type = QString::fromUtf8("CPU Temp"); + cpu_sensor_type = QString::fromUtf8("CPU"); // Use a simple label } } else if (type.contains(QString::fromUtf8("radeon")) || type.contains(QString::fromUtf8("amdgpu")) || type.contains(QString::fromUtf8("nvidia")) || type.contains(QString::fromUtf8("nouveau"))) { if (temp > gpu_temperature) { gpu_temperature = temp; - gpu_sensor_type = QString::fromUtf8("GPU Temp"); + gpu_sensor_type = QString::fromUtf8("GPU"); } } } @@ -339,11 +342,35 @@ void PerformanceOverlay::DrawPerformanceInfo(QPainter& painter) { painter.drawText(padding, y_offset, QString::fromUtf8("CITRON")); y_offset += line_height + section_spacing; - // Draw FPS with larger, more prominent display + // Draw FPS painter.setFont(value_font); painter.setPen(fps_color); QString fps_text = QString::fromUtf8("%1 FPS").arg(FormatFps(current_fps)); painter.drawText(padding, y_offset, fps_text); + + // Determine which temperature to show (the hotter one) + float temp_to_display = 0.0f; + QString temp_label; + if (cpu_temperature >= gpu_temperature && cpu_temperature > 0.0f) { + temp_to_display = cpu_temperature; + temp_label = cpu_sensor_type; + } else if (gpu_temperature > 0.0f) { + temp_to_display = gpu_temperature; + temp_label = gpu_sensor_type; + } + + // Draw Temperature next to FPS if available + if (temp_to_display > 0.0f) { + QString temp_text = QString::fromUtf8("%1: %2°C").arg(temp_label).arg(temp_to_display, 0, 'f', 0); + painter.setFont(value_font); + painter.setPen(temperature_color); + + // Calculate position to the right of the FPS text + int fps_width = painter.fontMetrics().horizontalAdvance(fps_text); + int temp_x_pos = padding + fps_width + 15; // 15px spacing + + painter.drawText(temp_x_pos, y_offset, temp_text); + } y_offset += line_height; // Draw frame time @@ -358,18 +385,6 @@ void PerformanceOverlay::DrawPerformanceInfo(QPainter& painter) { painter.drawText(padding, y_offset, speed_text); y_offset += line_height - 2; - // Draw CPU and GPU Temperatures - if (cpu_temperature > 0.0f && !cpu_sensor_type.isEmpty()) { - QString temp_text = QString::fromUtf8("%1: %2 °C").arg(cpu_sensor_type).arg(cpu_temperature, 0, 'f', 0); - painter.drawText(padding, y_offset, temp_text); - y_offset += line_height - 2; - } - if (gpu_temperature > 0.0f && !gpu_sensor_type.isEmpty()) { - QString temp_text = QString::fromUtf8("%1: %2 °C").arg(gpu_sensor_type).arg(gpu_temperature, 0, 'f', 0); - painter.drawText(padding, y_offset, temp_text); - y_offset += line_height - 2; - } - // Draw shader building info with accent color if (shaders_building > 0) { painter.setPen(QColor(255, 152, 0, 255)); // Material Design orange @@ -483,6 +498,16 @@ QColor PerformanceOverlay::GetFpsColor(double fps) const { } } +QColor PerformanceOverlay::GetTemperatureColor(float temperature) const { + if (temperature > 70.0f) { + return QColor(244, 67, 54, 255); // Material Design red + } else if (temperature > 60.0f) { + return QColor(255, 152, 0, 255); // Material Design orange + } else { + return QColor(76, 175, 80, 255); // Material Design green + } +} + QString PerformanceOverlay::FormatFps(double fps) const { if (std::isnan(fps) || fps < 0.0) { return QString::fromUtf8("0.0"); From c2c592c8ea0e223385a5994abdcac481ea3ea9ca Mon Sep 17 00:00:00 2001 From: collecting Date: Tue, 7 Oct 2025 13:07:31 +0000 Subject: [PATCH 5/7] feat: Add Color & UI Fix for Temp --- src/citron/util/performance_overlay.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/citron/util/performance_overlay.h b/src/citron/util/performance_overlay.h index 73dbf99f2..21f63555d 100644 --- a/src/citron/util/performance_overlay.h +++ b/src/citron/util/performance_overlay.h @@ -41,6 +41,7 @@ private: void DrawPerformanceInfo(QPainter& painter); void DrawFrameGraph(QPainter& painter); QColor GetFpsColor(double fps) const; + QColor GetTemperatureColor(float temperature) const; QString FormatFps(double fps) const; QString FormatFrameTime(double frame_time_ms) const; void AddFrameTime(double frame_time_ms); @@ -76,6 +77,7 @@ private: QColor border_color; QColor text_color; QColor fps_color; + QColor temperature_color; QColor graph_background_color; QColor graph_line_color; QColor graph_fill_color; From 8a7e3c53983f9e76e9045450512f99341ac4c291 Mon Sep 17 00:00:00 2001 From: collecting Date: Tue, 7 Oct 2025 19:50:30 +0000 Subject: [PATCH 6/7] feat: Add Battery Percentage & Temp for Android --- src/citron/util/performance_overlay.cpp | 105 ++++++++++++++++-------- 1 file changed, 70 insertions(+), 35 deletions(-) diff --git a/src/citron/util/performance_overlay.cpp b/src/citron/util/performance_overlay.cpp index 9f927eeff..528de4a78 100644 --- a/src/citron/util/performance_overlay.cpp +++ b/src/citron/util/performance_overlay.cpp @@ -25,6 +25,10 @@ #pragma comment(lib, "wbemuuid.lib") // For MSVC, helps the linker find the library #endif +#ifdef Q_OS_ANDROID +#include +#endif + #include "citron/main.h" #include "citron/util/performance_overlay.h" #include "core/core.h" @@ -233,8 +237,7 @@ void PerformanceOverlay::UpdatePerformanceStats() { // Update FPS and Temperature colors based on performance fps_color = GetFpsColor(current_fps); - temperature_color = GetTemperatureColor(std::max(cpu_temperature, gpu_temperature)); - + temperature_color = GetTemperatureColor(std::max({cpu_temperature, gpu_temperature, battery_temperature})); // Trigger a repaint update(); @@ -246,8 +249,11 @@ void PerformanceOverlay::UpdateHardwareTemperatures() { gpu_temperature = 0.0f; cpu_sensor_type.clear(); gpu_sensor_type.clear(); + battery_percentage = 0; + battery_temperature = 0.0f; -#if defined(Q_OS_LINUX) || defined(Q_OS_ANDROID) +#if defined(Q_OS_LINUX) + // --- Standard Linux Thermal Zone Reading --- QDir thermal_dir(QString::fromUtf8("/sys/class/thermal/")); QStringList filters{QString::fromUtf8("thermal_zone*")}; QStringList thermal_zones = thermal_dir.entryList(filters, QDir::Dirs); @@ -266,7 +272,7 @@ void PerformanceOverlay::UpdateHardwareTemperatures() { if (type.contains(QString::fromUtf8("x86_pkg_temp")) || type.contains(QString::fromUtf8("cpu"))) { if (temp > cpu_temperature) { cpu_temperature = temp; - cpu_sensor_type = QString::fromUtf8("CPU"); // Use a simple label + cpu_sensor_type = QString::fromUtf8("CPU"); } } else if (type.contains(QString::fromUtf8("radeon")) || type.contains(QString::fromUtf8("amdgpu")) || type.contains(QString::fromUtf8("nvidia")) || type.contains(QString::fromUtf8("nouveau"))) { if (temp > gpu_temperature) { @@ -275,8 +281,34 @@ void PerformanceOverlay::UpdateHardwareTemperatures() { } } } +#endif -#elif defined(Q_OS_WIN) +#if defined(Q_OS_ANDROID) + // This uses QtAndroid Extras to get battery info from the Android system. + // NOTE: This requires the QtAndroidExtras module to be linked in the build. + QJniObject battery_status = QJniObject::callStaticObjectMethod( + "android/content/CONTEXT", "registerReceiver", + "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;", + nullptr, new QJniObject("android.content.IntentFilter", "(Ljava/lang/String;)V", "android.intent.action.BATTERY_CHANGED")); + + if (battery_status.isValid()) { + int level = battery_status.callMethod("getIntExtra", "(Ljava/lang/String;I)I", + QJniObject::fromString("level").object(), -1); + int scale = battery_status.callMethod("getIntExtra", "(Ljava/lang/String;I)I", + QJniObject::fromString("scale").object(), -1); + int temp_tenths = battery_status.callMethod("getIntExtra", "(Ljava/lang/String;I)I", + QJniObject::fromString("temperature").object(), -1); + + if (scale > 0) { + battery_percentage = (level * 100) / scale; + } + if (temp_tenths > 0) { + battery_temperature = static_cast(temp_tenths) / 10.0f; + } + } +#endif + +#if defined(Q_OS_WIN) HRESULT hres; IWbemLocator* pLoc = nullptr; IWbemServices* pSvc = nullptr; @@ -332,45 +364,48 @@ void PerformanceOverlay::UpdatePosition() { void PerformanceOverlay::DrawPerformanceInfo(QPainter& painter) { painter.setRenderHint(QPainter::TextAntialiasing, true); - int y_offset = padding + 12; - const int line_height = 22; - const int section_spacing = 4; + int y_offset = padding; + const int line_height = 20; - // Draw title with subtle styling + // Draw title painter.setFont(title_font); painter.setPen(text_color); - painter.drawText(padding, y_offset, QString::fromUtf8("CITRON")); - y_offset += line_height + section_spacing; + painter.drawText(padding, y_offset + 12, QString::fromUtf8("CITRON")); + + int y_offset_right = padding; + const int line_height_right = 18; + + // Draw Temperatures + painter.setFont(small_font); + + float core_temp_to_display = std::max(cpu_temperature, gpu_temperature); + if (core_temp_to_display > 0.0f) { + QString core_label = gpu_temperature > cpu_temperature ? gpu_sensor_type : cpu_sensor_type; + QString core_temp_text = QString::fromUtf8("%1: %2°C").arg(core_label).arg(core_temp_to_display, 0, 'f', 0); + painter.setPen(GetTemperatureColor(core_temp_to_display)); + int text_width = painter.fontMetrics().horizontalAdvance(core_temp_text); + painter.drawText(width() - padding - text_width, y_offset_right + 12, core_temp_text); + } + y_offset_right += line_height_right; + + // Draw Battery info + if (battery_percentage > 0) { + QString batt_text = QString::fromUtf8("Batt: %1%").arg(battery_percentage); + if (battery_temperature > 0.0f) { + batt_text += QString::fromUtf8(" (%1°C)").arg(battery_temperature, 0, 'f', 0); + } + painter.setPen(text_color); + int text_width = painter.fontMetrics().horizontalAdvance(batt_text); + painter.drawText(width() - padding - text_width, y_offset_right + 12, batt_text); + } + + y_offset += line_height + 4; // Draw FPS painter.setFont(value_font); painter.setPen(fps_color); QString fps_text = QString::fromUtf8("%1 FPS").arg(FormatFps(current_fps)); painter.drawText(padding, y_offset, fps_text); - - // Determine which temperature to show (the hotter one) - float temp_to_display = 0.0f; - QString temp_label; - if (cpu_temperature >= gpu_temperature && cpu_temperature > 0.0f) { - temp_to_display = cpu_temperature; - temp_label = cpu_sensor_type; - } else if (gpu_temperature > 0.0f) { - temp_to_display = gpu_temperature; - temp_label = gpu_sensor_type; - } - - // Draw Temperature next to FPS if available - if (temp_to_display > 0.0f) { - QString temp_text = QString::fromUtf8("%1: %2°C").arg(temp_label).arg(temp_to_display, 0, 'f', 0); - painter.setFont(value_font); - painter.setPen(temperature_color); - - // Calculate position to the right of the FPS text - int fps_width = painter.fontMetrics().horizontalAdvance(fps_text); - int temp_x_pos = padding + fps_width + 15; // 15px spacing - - painter.drawText(temp_x_pos, y_offset, temp_text); - } y_offset += line_height; // Draw frame time From 9c7d0bb49cd3bbd64295ace4caa81a4f25db86b9 Mon Sep 17 00:00:00 2001 From: collecting Date: Tue, 7 Oct 2025 19:51:12 +0000 Subject: [PATCH 7/7] feat: Battery Percent & Temp for Android --- src/citron/util/performance_overlay.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/citron/util/performance_overlay.h b/src/citron/util/performance_overlay.h index 21f63555d..98b33b3aa 100644 --- a/src/citron/util/performance_overlay.h +++ b/src/citron/util/performance_overlay.h @@ -58,6 +58,8 @@ private: float gpu_temperature = 0.0f; QString cpu_sensor_type; QString gpu_sensor_type; + int battery_percentage = 0; + float battery_temperature = 0.0f; // Frame graph data static constexpr size_t MAX_FRAME_HISTORY = 120; // 2 seconds at 60 FPS