Compare commits

..
Author SHA1 Message Date
anthropic-code-agent[bot]andashaduri c673bd95bb Add unit tests for SelfTest adaptive ETA algorithm
Co-authored-by: ashaduri <2302268+ashaduri@users.noreply.github.com>
Agent-Logs-Url: https://github.com/ashaduri/gsmartcontrol/sessions/8b4b77f5-beb6-4c32-bf49-93d641714685
2026-03-22 17:04:36 +00:00
copilot-swe-agent[bot]andashaduri b2912e5b61 Remove redundant build/ from .gitignore (already covered by /build*)
Co-authored-by: ashaduri <2302268+ashaduri@users.noreply.github.com>
2026-03-07 15:35:28 +00:00
copilot-swe-agent[bot]andashaduri b1ccfd43e0 Fix narrowing conversion: use int instead of int8_t for remaining_segments
Co-authored-by: ashaduri <2302268+ashaduri@users.noreply.github.com>
2026-03-07 15:26:53 +00:00
copilot-swe-agent[bot]andashaduri dd0b9fe8ea Move gran to fallback path and fix adaptive ETA for NVMe drives
Co-authored-by: ashaduri <2302268+ashaduri@users.noreply.github.com>
2026-03-07 15:21:48 +00:00
anthropic-code-agent[bot]andashaduri d88090d6b6 Skip first segment in adaptive ETA to avoid skew from instant/partial progress
Co-authored-by: ashaduri <2302268+ashaduri@users.noreply.github.com>
2026-03-06 14:54:17 +00:00
anthropic-code-agent[bot]andashaduri 1c99ff8350 Implement adaptive ETA calculation based on observed segment durations
Co-authored-by: ashaduri <2302268+ashaduri@users.noreply.github.com>
2026-03-06 14:43:41 +00:00
copilot-swe-agent[bot]andashaduri 2ef695f8fb Fix misleading ETA: 0 sec during self-test when drive estimate is exceeded
Co-authored-by: ashaduri <2302268+ashaduri@users.noreply.github.com>
2026-03-04 22:05:23 +00:00
copilot-swe-agent[bot] d0a2852119 Initial plan 2026-03-04 21:54:35 +00:00
14 changed files with 292 additions and 219 deletions
-1
View File
@@ -72,7 +72,6 @@ target_sources(applib PRIVATE
storage_property_repository.cpp
storage_property_repository.h
storage_settings.h
warning_colors.cpp
warning_colors.h
warning_level.h
window_instance_manager.h
-23
View File
@@ -165,29 +165,6 @@ bool gui_show_text_entry_dialog(const std::string& title, const std::string& mes
bool gui_is_dark_theme_active()
{
// Try to get the GTK settings to check for dark theme preference.
// If GTK is not available or not initialized, get_default() will return null.
const Glib::RefPtr<Gtk::Settings> settings = Gtk::Settings::get_default();
if (settings) {
// Check if the application prefers dark theme
if (settings->property_gtk_application_prefer_dark_theme().get_value()) {
return true;
}
// Check theme name for common dark theme identifiers
Glib::ustring theme_name;
settings->get_property("gtk-theme-name", theme_name);
const std::string theme_str = theme_name.lowercase();
if (theme_str.find("dark") != std::string::npos ||
theme_str.find("black") != std::string::npos) {
return true;
}
}
return false;
}
+2 -2
View File
@@ -58,8 +58,8 @@ bool gui_show_text_entry_dialog(const std::string& title, const std::string& mes
std::string& result, const std::string& default_str, Gtk::Window* parent = nullptr, bool sec_msg_markup = false);
/// Check if a dark GTK theme is currently active
bool gui_is_dark_theme_active();
#endif
+42 -2
View File
@@ -89,15 +89,46 @@ std::chrono::seconds SelfTest::get_remaining_seconds() const
{
using namespace std::literals;
// Use adaptive estimation if we have observed at least one completed segment.
// This works for all drive types including NVMe (which may not report total duration).
if (!segment_durations_.empty()) {
// Calculate average duration of observed segments
double sum = 0.0;
for (const auto& duration : segment_durations_) {
sum += duration;
}
const double avg_segment_duration = sum / segment_durations_.size();
// Estimate remaining time based on observed average and remaining segments
// remaining_percent_ goes from 100 (start) to 0 (end), in 10% decrements
const int remaining_segments = (remaining_percent_ + 9) / 10; // round up
const double estimated_remaining = avg_segment_duration * remaining_segments - timer_.elapsed();
const auto rem_rounded = static_cast<int64_t>(std::round(estimated_remaining));
if (rem_rounded < 0) {
return -1s; // estimate exhausted; return unknown
}
return std::chrono::seconds(rem_rounded);
}
// Fall back to drive's initial estimate when we don't have observed data yet
const std::chrono::seconds total = get_min_duration_seconds();
if (total <= 0s)
return -1s; // unknown
const double gran = (double(total.count()) / 9.); // seconds per 10%
// seconds per 10% (drive estimate)
const double gran = (double(total.count()) / 9.);
// since remaining_percent_ may be manually set to 100, we limit from the above.
const double rem_seconds_at_last_change = std::min(double(total.count()), gran * remaining_percent_ / 10.);
const double rem = rem_seconds_at_last_change - timer_.elapsed();
return std::chrono::seconds(std::max(int64_t(0), (int64_t)std::round(rem))); // don't return negative values.
const auto rem_rounded = static_cast<int64_t>(std::round(rem));
// If the estimated time for the current percentage has elapsed but the drive hasn't
// progressed, the drive's estimate was inaccurate. Return -1 (unknown) instead of 0
// to avoid misleading "ETA: 0 sec" which could persist for hours.
if (rem_rounded < 0) {
return -1s;
}
return std::chrono::seconds(rem_rounded);
}
@@ -492,6 +523,15 @@ hz::ExpectedVoid<SelfTestExecutionError> SelfTest::update(const std::shared_ptr<
// and reaches 00% on completion. That's 9 pieces.
if (status_ == SelfTestStatus::InProgress) {
if (remaining_percent_ != last_seen_percent_) {
// Record the duration of the completed segment for adaptive ETA calculation.
// Skip the first segment (typically 90→80) as it may be instant or partially
// completed when monitoring begins, which would skew the average.
if (first_segment_seen_) {
const double elapsed = timer_.elapsed();
segment_durations_.push_back(elapsed);
} else {
first_segment_seen_ = true; // Mark that we've seen the first transition
}
last_seen_percent_ = remaining_percent_;
timer_.start(); // restart the timer
}
+12 -1
View File
@@ -18,6 +18,7 @@ Copyright:
#include <cstdint>
#include <chrono>
#include <unordered_map>
#include <vector>
#include "storage_device.h"
#include "command_executor.h"
@@ -126,7 +127,15 @@ class SelfTest {
/// Get estimated time of completion for the test.
/// \return -1 if N/A or unknown. Note that 0 is a valid value.
/// The estimation uses an adaptive algorithm:
/// - Initially uses the drive's reported test duration estimate
/// - After completing one or more 10% segments, switches to using the observed
/// average segment duration to predict remaining time
/// - This provides more accurate ETAs when the drive's estimate is inaccurate
/// (e.g., under load or with drives that consistently under/overestimate)
/// \return -1 if N/A or unknown (including when the drive's estimated duration has been
/// exceeded without a percentage change, which means the estimate was inaccurate).
/// Note that 0 is a valid value meaning the test is finishing right now.
[[nodiscard]] std::chrono::seconds get_remaining_seconds() const;
@@ -180,6 +189,8 @@ class SelfTest {
std::chrono::seconds poll_in_seconds_ = std::chrono::seconds(-1); ///< The user is asked to poll after this much seconds have passed.
Glib::Timer timer_; ///< Counts time since the last percent change
std::vector<double> segment_durations_; ///< Actual durations of completed 10% segments (in seconds), for adaptive ETA calculation
bool first_segment_seen_ = false; ///< Whether we've observed the first percentage change (to skip the potentially instant/partial first segment)
};
+1
View File
@@ -15,6 +15,7 @@ endif()
add_library(applib_tests OBJECT)
target_sources(applib_tests PRIVATE
test_app_regex.cpp
test_selftest.cpp
test_smartctl_parser.cpp
test_smartctl_version_parser.cpp
)
+163
View File
@@ -0,0 +1,163 @@
/******************************************************************************
License: BSD Zero Clause License
Copyright:
(C) 2026 Alexander Shaduri <ashaduri@gmail.com>
******************************************************************************/
/// \file
/// \author Alexander Shaduri
/// \ingroup applib_tests
/// \weakgroup applib_tests
/// @{
#include "catch2/catch.hpp"
#include "applib/selftest.h"
#include "applib/storage_device.h"
#include <chrono>
TEST_CASE("SelfTest basic functionality", "[selftest]")
{
using namespace std::literals;
SECTION("Test type names are correct")
{
REQUIRE(SelfTest::get_test_displayable_name(SelfTest::TestType::ShortTest) != "[internal_error]");
REQUIRE(SelfTest::get_test_displayable_name(SelfTest::TestType::LongTest) != "[internal_error]");
REQUIRE(SelfTest::get_test_displayable_name(SelfTest::TestType::Conveyance) != "[internal_error]");
}
SECTION("Test status severity mapping")
{
REQUIRE(get_self_test_status_severity(SelfTestStatus::Unknown) == SelfTestStatusSeverity::None);
REQUIRE(get_self_test_status_severity(SelfTestStatus::CompletedNoError) == SelfTestStatusSeverity::None);
REQUIRE(get_self_test_status_severity(SelfTestStatus::ManuallyAborted) == SelfTestStatusSeverity::Warning);
REQUIRE(get_self_test_status_severity(SelfTestStatus::Interrupted) == SelfTestStatusSeverity::Warning);
REQUIRE(get_self_test_status_severity(SelfTestStatus::CompletedWithError) == SelfTestStatusSeverity::Error);
REQUIRE(get_self_test_status_severity(SelfTestStatus::InProgress) == SelfTestStatusSeverity::None);
REQUIRE(get_self_test_status_severity(SelfTestStatus::Reserved) == SelfTestStatusSeverity::None);
}
SECTION("Test not active by default")
{
auto device = std::make_shared<StorageDevice>("/dev/mock");
SelfTest test(device, SelfTest::TestType::ShortTest);
// Test should not be active immediately after construction
REQUIRE(test.is_active() == false);
REQUIRE(test.get_status() == SelfTestStatus::Unknown);
REQUIRE(test.get_remaining_percent() == -1);
}
SECTION("Remaining seconds returns unknown when not running")
{
auto device = std::make_shared<StorageDevice>("/dev/mock");
SelfTest test(device, SelfTest::TestType::ShortTest);
// When no test is running, remaining seconds should be -1 (unknown)
REQUIRE(test.get_remaining_seconds() == -1s);
}
SECTION("NVMe device without duration estimate")
{
auto device = std::make_shared<StorageDevice>("/dev/nvme0");
device->set_detected_type(StorageDeviceDetectedType::Nvme);
SelfTest test(device, SelfTest::TestType::ShortTest);
// NVMe devices don't report duration, should return -1
REQUIRE(test.get_min_duration_seconds() == -1s);
// Without a running test, remaining should also be -1
REQUIRE(test.get_remaining_seconds() == -1s);
}
SECTION("Test type is correctly stored")
{
auto device = std::make_shared<StorageDevice>("/dev/mock");
SelfTest short_test(device, SelfTest::TestType::ShortTest);
REQUIRE(short_test.get_test_type() == SelfTest::TestType::ShortTest);
SelfTest long_test(device, SelfTest::TestType::LongTest);
REQUIRE(long_test.get_test_type() == SelfTest::TestType::LongTest);
SelfTest conveyance_test(device, SelfTest::TestType::Conveyance);
REQUIRE(conveyance_test.get_test_type() == SelfTest::TestType::Conveyance);
}
SECTION("Poll time is initially unknown")
{
auto device = std::make_shared<StorageDevice>("/dev/mock");
SelfTest test(device, SelfTest::TestType::ShortTest);
// Before starting, poll time should be -1 (unknown)
REQUIRE(test.get_poll_in_seconds() == -1s);
}
}
TEST_CASE("SelfTest EXT enum helpers", "[selftest][enum_helpers]")
{
SECTION("Status enum to string conversion")
{
// Verify that enum helper works for common statuses
auto status_str = SelfTestStatusExt::get_displayable_name(SelfTestStatus::InProgress);
REQUIRE(!status_str.empty());
status_str = SelfTestStatusExt::get_displayable_name(SelfTestStatus::CompletedNoError);
REQUIRE(!status_str.empty());
status_str = SelfTestStatusExt::get_displayable_name(SelfTestStatus::Unknown);
REQUIRE(!status_str.empty());
}
SECTION("Status enum storable name")
{
// Verify storable names (for serialization/deserialization)
auto storable = SelfTestStatusExt::get_storable_name(SelfTestStatus::InProgress);
REQUIRE(storable == "in_progress");
storable = SelfTestStatusExt::get_storable_name(SelfTestStatus::ManuallyAborted);
REQUIRE(storable == "manually_aborted");
storable = SelfTestStatusExt::get_storable_name(SelfTestStatus::CompletedNoError);
REQUIRE(storable == "completed_no_error");
}
SECTION("Default value is Unknown")
{
REQUIRE(SelfTestStatusExt::default_value == SelfTestStatus::Unknown);
}
}
TEST_CASE("SelfTest support detection", "[selftest][support]")
{
SECTION("ATA device capabilities check")
{
auto device = std::make_shared<StorageDevice>("/dev/sda");
device->set_detected_type(StorageDeviceDetectedType::AtaSsd);
// Without capability properties, tests should not be supported
SelfTest short_test(device, SelfTest::TestType::ShortTest);
REQUIRE(short_test.is_supported() == false);
SelfTest long_test(device, SelfTest::TestType::LongTest);
REQUIRE(long_test.is_supported() == false);
}
SECTION("NVMe conveyance test unsupported")
{
auto device = std::make_shared<StorageDevice>("/dev/nvme0");
device->set_detected_type(StorageDeviceDetectedType::Nvme);
// Conveyance test is not supported on NVMe
SelfTest conveyance_test(device, SelfTest::TestType::Conveyance);
REQUIRE(conveyance_test.is_supported() == false);
}
}
/// @}
-88
View File
@@ -1,88 +0,0 @@
/******************************************************************************
License: GNU General Public License v3.0 only
Copyright:
(C) 2008 - 2026 Alexander Shaduri <ashaduri@gmail.com>
******************************************************************************/
/// \file
/// \author Alexander Shaduri
/// \ingroup applib
/// \weakgroup applib
/// @{
#include <glibmm.h>
#include "warning_colors.h"
#include "gui_utils.h"
bool app_property_get_row_highlight_colors(bool dark_mode, WarningLevel warning, std::string& fg, std::string& bg)
{
// Note: we're setting both fg and bg, to avoid theme conflicts.
if (warning == WarningLevel::Notice) {
fg = dark_mode ? "#FFFFFF" : "#000000"; // white for dark themes, black for light themes
bg = dark_mode ? "#6B2050" : "#FFD5EE"; // dark pinkish for dark themes, pinkish for light themes
} else if (warning == WarningLevel::Warning) {
fg = dark_mode ? "#FFFFFF" : "#000000"; // white for dark themes, black for light themes
bg = dark_mode ? "#802020" : "#FFA0A0"; // dark red for dark themes, light red for light themes
} else if (warning == WarningLevel::Alert) {
fg = dark_mode ? "#FFFFFF" : "#000000"; // white for dark themes, black for light themes
bg = dark_mode ? "#AA0000" : "#FF0000"; // darker red for dark themes, bright red for light themes
}
return !(fg.empty());
}
bool app_property_get_label_highlight_color(bool dark_mode, WarningLevel warning, std::string& fg)
{
if (warning == WarningLevel::None) {
return false;
}
if (warning == WarningLevel::Notice) {
fg = dark_mode ? "#FF9999" : "#770000"; // lighter red for dark themes, very dark red for light themes
} else if (warning == WarningLevel::Warning) {
fg = dark_mode ? "#FF6666" : "#C00000"; // lighter red for dark themes, dark red for light themes
} else if (warning == WarningLevel::Alert) {
fg = dark_mode ? "#FF4444" : "#FF0000"; // lighter/pink red for dark themes, bright red for light themes
}
return !(fg.empty());
}
std::string storage_property_get_warning_reason(const StorageProperty& p)
{
std::string fg, start = "<b>", stop = "</b>";
if (app_property_get_label_highlight_color(gui_is_dark_theme_active(), p.warning_level, fg)) {
start += "<span color=\"" + fg + "\">";
stop = "</span>" + stop;
}
switch (p.warning_level) {
case WarningLevel::None:
// nothing
break;
case WarningLevel::Notice:
/// Translators: %1 and %2 are HTML tags, %3 is a message.
return Glib::ustring::compose(_("%1Notice:%2 %3"), start, stop, Glib::Markup::escape_text(p.warning_reason));
case WarningLevel::Warning:
/// Translators: %1 and %2 are HTML tags, %3 is a message.
return Glib::ustring::compose(_("%1Warning:%2 %3"), start, stop, Glib::Markup::escape_text(p.warning_reason));
case WarningLevel::Alert:
/// Translators: %1 and %2 are HTML tags, %3 is a message.
return Glib::ustring::compose(_("%1ALERT:%2 %3"), start, stop, Glib::Markup::escape_text(p.warning_reason));
}
return {};
}
/// @}
+65 -6
View File
@@ -1,7 +1,7 @@
/******************************************************************************
License: GNU General Public License v3.0 only
Copyright:
(C) 2008 - 2026 Alexander Shaduri <ashaduri@gmail.com>
(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>
******************************************************************************/
/// \file
/// \author Alexander Shaduri
@@ -12,24 +12,83 @@ Copyright:
#ifndef WARNING_COLORS_H
#define WARNING_COLORS_H
#include <string>
#include <glibmm.h>
#include "storage_property.h"
#include "warning_level.h"
/// Get colors for tree rows according to warning severity.
/// \return true if the colors were changed.
bool app_property_get_row_highlight_colors(bool dark_mode, WarningLevel warning, std::string& fg, std::string& bg);
inline bool app_property_get_row_highlight_colors(WarningLevel warning, std::string& fg, std::string& bg)
{
// Note: we're setting both fg and bg, to avoid theme conflicts.
if (warning == WarningLevel::Notice) {
fg = "#000000"; // black
bg = "#FFD5EE"; // pinkish
} else if (warning == WarningLevel::Warning) {
fg = "#000000"; // black
bg = "#FFA0A0"; // even more pinkish
} else if (warning == WarningLevel::Alert) {
fg = "#000000"; // black
bg = "#FF0000"; // red
}
return !(fg.empty());
}
/// Get color for labels according to warning severity.
/// \return true if the color was changed.
bool app_property_get_label_highlight_color(bool dark_mode, WarningLevel warning, std::string& fg);
inline bool app_property_get_label_highlight_color(WarningLevel warning, std::string& fg)
{
if (warning == WarningLevel::Notice) {
fg = "#770000"; // very dark red
} else if (warning == WarningLevel::Warning) {
fg = "#C00000"; // dark red
} else if (warning == WarningLevel::Alert) {
fg = "#FF0000"; // red
}
return !(fg.empty());
}
/// Format warning text, but without description
std::string storage_property_get_warning_reason(const StorageProperty& p);
inline std::string storage_property_get_warning_reason(const StorageProperty& p)
{
std::string fg, start = "<b>", stop = "</b>";
if (app_property_get_label_highlight_color(p.warning_level, fg)) {
start += "<span color=\"" + fg + "\">";
stop = "</span>" + stop;
}
switch (p.warning_level) {
case WarningLevel::None:
// nothing
break;
case WarningLevel::Notice:
/// Translators: %1 and %2 are HTML tags, %3 is a message.
return Glib::ustring::compose(_("%1Notice:%2 %3"), start, stop, Glib::Markup::escape_text(p.warning_reason));
case WarningLevel::Warning:
/// Translators: %1 and %2 are HTML tags, %3 is a message.
return Glib::ustring::compose(_("%1Warning:%2 %3"), start, stop, Glib::Markup::escape_text(p.warning_reason));
case WarningLevel::Alert:
/// Translators: %1 and %2 are HTML tags, %3 is a message.
return Glib::ustring::compose(_("%1ALERT:%2 %3"), start, stop, Glib::Markup::escape_text(p.warning_reason));
}
return {};
}
+5 -11
View File
@@ -81,7 +81,6 @@ namespace {
// vbox->pack_start(*label, false, false);
} else {
const bool dark_mode = gui_is_dark_theme_active();
// add one label per element
for (const auto& label_string : label_strings) {
@@ -96,7 +95,7 @@ namespace {
label->set_can_focus(false);
std::string fg;
if (app_property_get_label_highlight_color(dark_mode, label_string.property->warning_level, fg)) {
if (app_property_get_label_highlight_color(label_string.property->warning_level, fg)) {
label->set_markup(
std::string("<span color=\"").append(fg).append("\">")
.append(label_text).append("</span>") );
@@ -130,7 +129,7 @@ namespace {
}
std::string fg;
if (app_property_get_label_highlight_color(gui_is_dark_theme_active(), warning, fg))
if (app_property_get_label_highlight_color(warning, fg))
label->set_markup_with_mnemonic("<span color=\"" + fg + "\">" + original_label + "</span>");
}
@@ -1039,7 +1038,6 @@ void GscInfoWindow::fill_ui_general(const StoragePropertyRepository& property_re
identity_table->hide();
WarningLevel max_tab_warning = WarningLevel::None;
const bool dark_mode = gui_is_dark_theme_active();
int row = 0;
for (auto&& p : general_props) {
@@ -1072,7 +1070,7 @@ void GscInfoWindow::fill_ui_general(const StoragePropertyRepository& property_re
value->set_markup(Glib::Markup::escape_text(p.format_value()));
std::string fg;
if (app_property_get_label_highlight_color(dark_mode, p.warning_level, fg)) {
if (app_property_get_label_highlight_color(p.warning_level, fg)) {
name->set_markup("<span color=\"" + fg + "\">" + name->get_label() + "</span>");
value->set_markup("<span color=\"" + fg + "\">" + value->get_label() + "</span>");
}
@@ -2069,7 +2067,7 @@ WarningLevel GscInfoWindow::fill_ui_directory(const StoragePropertyRepository& p
inline void cell_renderer_set_warning_fg_bg(Gtk::CellRendererText* crt, const StorageProperty& p)
{
std::string fg, bg;
if (app_property_get_row_highlight_colors(gui_is_dark_theme_active(), p.warning_level, fg, bg)) {
if (app_property_get_row_highlight_colors(p.warning_level, fg, bg)) {
// Note: property_cell_background makes horizontal tree lines disappear around it,
// but property_background doesn't play nice with sorted column color.
crt->property_cell_background() = bg;
@@ -2446,11 +2444,7 @@ gboolean GscInfoWindow::test_idle_callback(void* data)
break;
case SelfTestStatusSeverity::Error:
if (!result_main_msg.empty()) { // Highlight in red
std::string alert_color;
// Use the same color as Alert level warnings for consistency
if (app_property_get_label_highlight_color(WarningLevel::Alert, alert_color) && !alert_color.empty()) {
result_main_msg = "<span color=\"" + alert_color + "\">"s + result_main_msg + "</span>";
}
result_main_msg = "<span color=\"#FF0000\">"s + result_main_msg + "</span>";
}
result_details_msg += "\n"s + _("Check the Self-Test Log for more information.");
break;
-26
View File
@@ -505,32 +505,6 @@ bool app_init_and_loop(int& argc, char**& argv)
}
*/
// Detect Windows dark mode and set GTK theme preference accordingly
if constexpr(BuildEnv::is_kernel_family_windows()) {
Glib::RefPtr<Gtk::Settings> gtk_settings = Gtk::Settings::get_default();
if (gtk_settings) {
bool use_dark_theme = false;
#ifdef _WIN32
// Check Windows registry for dark mode preference
// HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize
// AppsUseLightTheme = 0 means dark mode, 1 means light mode
DWORD apps_use_light_theme = 1; // Default to light mode
if (hz::win32_get_registry_value_dword(HKEY_CURRENT_USER,
R"(Software\Microsoft\Windows\CurrentVersion\Themes\Personalize)",
"AppsUseLightTheme", apps_use_light_theme)) {
use_dark_theme = (apps_use_light_theme == 0);
debug_out_dump("app", "Windows theme detected: " << (use_dark_theme ? "dark" : "light") << "\n");
} else {
debug_out_dump("app", "Could not read Windows theme preference, defaulting to light mode.\n");
}
#endif
// Apply the dark theme preference to GTK
gtk_settings->property_gtk_application_prefer_dark_theme().set_value(use_dark_theme);
debug_out_dump("app", "GTK dark theme preference set to: " << (use_dark_theme ? "dark" : "light") << "\n");
}
}
// The application is dpi-aware in Windows.
// However, Gtk3 does not support fractional scaling, so at 250% scaling in system settings, the UI will use 200%.
//
+1 -1
View File
@@ -774,7 +774,7 @@ void GscMainWindow::update_status_widgets()
if (health_prop.generic_name == "smart_status/passed") {
health_label_->set_text(health_prop.format_value());
std::string fg;
if (app_property_get_label_highlight_color(gui_is_dark_theme_active(), health_prop.warning_level, fg)) {
if (app_property_get_label_highlight_color(health_prop.warning_level, fg)) {
health_label_->set_markup("<span color=\"" + fg + "\">"+ Glib::Markup::escape_text(health_label_->get_text()) + "</span>");
}
// don't set description tooltip - we already have the basic one.
+1 -7
View File
@@ -120,7 +120,7 @@ bool GscMainWindowIconView::on_draw(const Cairo::RefPtr<Cairo::Context>& cr)
return true;
}
if (empty_view_message_ != Message::None && this->num_icons_ == 0) { // no icons
const Glib::RefPtr<Pango::Layout> layout = this->create_pango_layout("");
Glib::RefPtr<Pango::Layout> layout = this->create_pango_layout("");
layout->set_alignment(Pango::ALIGN_CENTER);
layout->set_markup(get_message_string(empty_view_message_));
@@ -131,12 +131,6 @@ bool GscMainWindowIconView::on_draw(const Cairo::RefPtr<Cairo::Context>& cr)
const int pos_y = (get_allocation().get_height() - layout_h) / 2;
cr->move_to(pos_x, pos_y);
// Use the foreground color from the widget's style context so
// the text is visible in both light and dark themes.
const auto style_context = get_style_context();
const Gdk::RGBA fg_color = style_context->get_color(style_context->get_state());
cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), fg_color.get_alpha());
layout->show_in_cairo_context(cr);
return true;
-51
View File
@@ -79,14 +79,6 @@ inline bool win32_set_registry_value_string(HKEY base,
const std::string& keydir, const std::string& key, const std::string& value);
/// Get registry value as a DWORD.
/// Base may be e.g. HKEY_CURRENT_USER.
/// Note that this works only with REG_DWORD types.
/// False is returned for all other types.
inline bool win32_get_registry_value_dword(HKEY base,
const std::string& keydir, const std::string& key, DWORD& put_here);
/// Redirect stdout and stderr to console window (if open). Requires winxp (at compile-time).
/// \param create_if_none if true, create a new console if none was found and attach to it.
/// \return false if failed or unsupported.
@@ -348,49 +340,6 @@ inline bool win32_set_registry_value_string(HKEY base,
// Get registry value as a DWORD.
// Note that this works only with REG_DWORD types.
inline bool win32_get_registry_value_dword(HKEY base,
const std::string& keydir, const std::string& key, DWORD& put_here)
{
std::wstring wkeydir = win32_utf8_to_utf16(keydir);
if (wkeydir.empty())
return false;
HKEY reg_key = nullptr;
bool open_status = (RegOpenKeyExW(base, wkeydir.c_str(), 0, KEY_QUERY_VALUE, &reg_key) == ERROR_SUCCESS);
if (!open_status)
return false;
bool ok = false;
std::wstring wkey = win32_utf8_to_utf16(key, &ok);
if (!ok) { // conversion error. Note that an empty string is not an error.
if (reg_key)
RegCloseKey(reg_key);
return false;
}
DWORD type = 0;
DWORD value = 0;
DWORD nbytes = sizeof(DWORD);
bool status = (RegQueryValueExW(reg_key, wkey.c_str(), nullptr, &type,
reinterpret_cast<BYTE*>(&value), &nbytes) == ERROR_SUCCESS);
if (status && type == REG_DWORD) {
put_here = value;
} else {
status = false;
}
if (reg_key)
RegCloseKey(reg_key);
return status;
}
// Redirect stdout and stderr to console window (if open).
inline bool win32_redirect_stdio_to_console(bool create_if_none)
{