feat(sysmon): added classic (no nvidia) gpu_temp in bar and desktop widgets + some settings polishing with "visibleWhen"

This commit is contained in:
Lemmy
2026-05-10 16:33:38 -04:00
parent b62f6fead8
commit db08c80201
16 changed files with 447 additions and 132 deletions
+1
View File
@@ -917,6 +917,7 @@
"always": "Always",
"cpu-temp": "CPU Temperature",
"cpu-usage": "CPU Usage",
"gpu-temp": "GPU Temperature",
"disk-percent": "Disk Percent",
"full": "Full",
"gauge": "Gauge",
+2
View File
@@ -304,6 +304,8 @@ std::unique_ptr<Widget> WidgetFactory::create(const std::string& name, wl_output
SysmonStat stat = SysmonStat::CpuUsage;
if (statStr == "cpu_temp") {
stat = SysmonStat::CpuTemp;
} else if (statStr == "gpu_temp") {
stat = SysmonStat::GpuTemp;
} else if (statStr == "ram_used") {
stat = SysmonStat::RamUsed;
} else if (statStr == "ram_pct") {
+32
View File
@@ -33,6 +33,7 @@ namespace {
constexpr auto kInitialSampleRetryDelay = std::chrono::milliseconds(250);
bool needsCpuTemp(SysmonStat stat) { return stat == SysmonStat::CpuTemp; }
bool needsGpuTemp(SysmonStat stat) { return stat == SysmonStat::GpuTemp; }
} // namespace
@@ -44,6 +45,9 @@ SysmonWidget::SysmonWidget(SystemMonitorService* monitor, wl_output* output, Sys
if (needsCpuTemp(m_stat)) {
m_monitor->retainCpuTemp();
}
if (needsGpuTemp(m_stat)) {
m_monitor->retainGpuTemp();
}
if (m_stat == SysmonStat::DiskPct && !m_diskPath.empty()) {
m_monitor->retainDiskPath(m_diskPath);
}
@@ -55,6 +59,9 @@ SysmonWidget::~SysmonWidget() {
if (needsCpuTemp(m_stat)) {
m_monitor->releaseCpuTemp();
}
if (needsGpuTemp(m_stat)) {
m_monitor->releaseGpuTemp();
}
if (m_stat == SysmonStat::DiskPct && !m_diskPath.empty()) {
m_monitor->releaseDiskPath(m_diskPath);
}
@@ -412,6 +419,23 @@ double SysmonWidget::normalizedFromStats(SysmonStat stat, const SystemStats& sta
}
return 0.0;
case SysmonStat::GpuTemp:
if (stats.gpuTempC.has_value()) {
const double temp = *stats.gpuTempC;
if (temp < tempMin) {
tempMin = temp;
}
if (temp > tempMax) {
tempMax = temp;
}
const double range = tempMax - tempMin;
if (range <= 0.0) {
return 0.5;
}
return std::clamp((temp - tempMin) / range, 0.0, 1.0);
}
return 0.0;
case SysmonStat::RamUsed:
if (stats.ramTotalMb > 0) {
return static_cast<double>(stats.ramUsedMb) / static_cast<double>(stats.ramTotalMb);
@@ -466,6 +490,12 @@ std::string SysmonWidget::formatValue() const {
}
return "--";
case SysmonStat::GpuTemp:
if (stats.gpuTempC.has_value()) {
return std::format("{:.0f}°C", *stats.gpuTempC);
}
return "--";
case SysmonStat::RamUsed:
if (stats.ramUsedMb >= 1024) {
return std::format("{:.1f}G", static_cast<double>(stats.ramUsedMb) / 1024.0);
@@ -495,6 +525,8 @@ const char* SysmonWidget::glyphName(SysmonStat stat) {
return "cpu-usage";
case SysmonStat::CpuTemp:
return "cpu-temperature";
case SysmonStat::GpuTemp:
return "temperature";
case SysmonStat::RamUsed:
case SysmonStat::RamPct:
return "memory";
+1 -1
View File
@@ -15,7 +15,7 @@ class SystemMonitorService;
struct SystemStats;
struct wl_output;
enum class SysmonStat { CpuUsage, CpuTemp, RamUsed, RamPct, SwapPct, DiskPct };
enum class SysmonStat { CpuUsage, CpuTemp, GpuTemp, RamUsed, RamPct, SwapPct, DiskPct };
enum class SysmonDisplayMode { Text, Graph, Gauge };
class SysmonWidget : public Widget {
@@ -175,6 +175,8 @@ DesktopWidgetFactory::create(const std::string& type,
auto parseStat = [](const std::string& s) -> DesktopSysmonStat {
if (s == "cpu_temp")
return DesktopSysmonStat::CpuTemp;
if (s == "gpu_temp")
return DesktopSysmonStat::GpuTemp;
if (s == "ram_pct")
return DesktopSysmonStat::RamPct;
if (s == "swap_pct")
@@ -20,6 +20,7 @@ namespace {
const auto kSampleInterval = std::chrono::duration_cast<std::chrono::steady_clock::duration>(std::chrono::seconds(1));
bool needsCpuTemp(DesktopSysmonStat stat) { return stat == DesktopSysmonStat::CpuTemp; }
bool needsGpuTemp(DesktopSysmonStat stat) { return stat == DesktopSysmonStat::GpuTemp; }
} // namespace
@@ -31,8 +32,12 @@ DesktopSysmonWidget::DesktopSysmonWidget(SystemMonitorService* monitor, DesktopS
if (m_monitor != nullptr) {
if (needsCpuTemp(m_stat))
m_monitor->retainCpuTemp();
if (needsGpuTemp(m_stat))
m_monitor->retainGpuTemp();
if (m_stat2.has_value() && needsCpuTemp(*m_stat2))
m_monitor->retainCpuTemp();
if (m_stat2.has_value() && needsGpuTemp(*m_stat2))
m_monitor->retainGpuTemp();
}
}
@@ -40,8 +45,12 @@ DesktopSysmonWidget::~DesktopSysmonWidget() {
if (m_monitor != nullptr) {
if (needsCpuTemp(m_stat))
m_monitor->releaseCpuTemp();
if (needsGpuTemp(m_stat))
m_monitor->releaseGpuTemp();
if (m_stat2.has_value() && needsCpuTemp(*m_stat2))
m_monitor->releaseCpuTemp();
if (m_stat2.has_value() && needsGpuTemp(*m_stat2))
m_monitor->releaseGpuTemp();
}
}
@@ -195,6 +204,20 @@ double DesktopSysmonWidget::normalizedFromStats(DesktopSysmonStat stat, const Sy
}
return 0.0;
case DesktopSysmonStat::GpuTemp:
if (stats.gpuTempC.has_value()) {
const double temp = *stats.gpuTempC;
if (temp < tempMin)
tempMin = temp;
if (temp > tempMax)
tempMax = temp;
const double range = tempMax - tempMin;
if (range <= 0.0)
return 0.5;
return std::clamp((temp - tempMin) / range, 0.0, 1.0);
}
return 0.0;
case DesktopSysmonStat::RamPct:
return stats.ramUsagePercent / 100.0;
@@ -225,6 +248,12 @@ std::string DesktopSysmonWidget::formatValueFor(DesktopSysmonStat stat) const {
}
return "--";
case DesktopSysmonStat::GpuTemp:
if (stats.gpuTempC.has_value()) {
return std::format("{:.0f}°C", *stats.gpuTempC);
}
return "--";
case DesktopSysmonStat::RamPct:
return std::format("{:.0f}%", stats.ramUsagePercent);
@@ -323,6 +352,8 @@ const char* DesktopSysmonWidget::glyphName(DesktopSysmonStat stat) {
return "cpu-usage";
case DesktopSysmonStat::CpuTemp:
return "cpu-temperature";
case DesktopSysmonStat::GpuTemp:
return "temperature";
case DesktopSysmonStat::RamPct:
return "memory";
case DesktopSysmonStat::SwapPct:
@@ -15,7 +15,7 @@ class GraphNode;
class Label;
class SystemMonitorService;
enum class DesktopSysmonStat : std::uint8_t { CpuUsage, CpuTemp, RamPct, SwapPct };
enum class DesktopSysmonStat : std::uint8_t { CpuUsage, CpuTemp, GpuTemp, RamPct, SwapPct };
class DesktopSysmonWidget : public DesktopWidget {
public:
+92 -64
View File
@@ -566,6 +566,47 @@ namespace settings {
return spec.defaultValue;
}
std::string settingCurrentString(const Config& cfg, std::string_view widgetName, const std::string& key,
const std::vector<WidgetSettingSpec>& allSpecs) {
if (const auto it = cfg.widgets.find(std::string(widgetName)); it != cfg.widgets.end()) {
if (const auto settingIt = it->second.settings.find(key); settingIt != it->second.settings.end()) {
if (const auto* s = std::get_if<std::string>(&settingIt->second)) {
return *s;
}
if (const auto* b = std::get_if<bool>(&settingIt->second)) {
return *b ? "true" : "false";
}
}
}
for (const auto& s : allSpecs) {
if (s.key == key) {
if (const auto* str = std::get_if<std::string>(&s.defaultValue)) {
return *str;
}
if (const auto* b = std::get_if<bool>(&s.defaultValue)) {
return *b ? "true" : "false";
}
break;
}
}
return {};
}
bool isSettingVisible(const Config& cfg, std::string_view widgetName, const WidgetSettingSpec& spec,
const std::vector<WidgetSettingSpec>& allSpecs) {
if (!spec.visibleWhen.has_value()) {
return true;
}
const auto& cond = *spec.visibleWhen;
const auto currentValue = settingCurrentString(cfg, widgetName, cond.key, allSpecs);
for (const auto& v : cond.values) {
if (v == currentValue) {
return true;
}
}
return false;
}
bool settingValueAsBool(const WidgetSettingValue& value) {
if (const auto* v = std::get_if<bool>(&value)) {
return *v;
@@ -871,6 +912,9 @@ namespace settings {
if (spec.key == "capsule_group" && managedCapsuleGroups.empty()) {
continue;
}
if (!isSettingVisible(ctx.config, widgetName, spec, specs)) {
continue;
}
if (spec.advanced && !ctx.showAdvanced) {
continue;
}
@@ -895,6 +939,7 @@ namespace settings {
.control = TextSetting{},
.advanced = spec.advanced,
.searchText = {},
.visibleWhen = std::nullopt,
};
switch (spec.valueType) {
@@ -1164,15 +1209,18 @@ namespace settings {
inspector->addChild(std::move(groupRow));
}
if (!currentLaneInherited && !currentLaneKey.empty()) {
auto moveRow = std::make_unique<Flex>();
moveRow->setDirection(FlexDirection::Horizontal);
moveRow->setAlign(FlexAlign::Center);
moveRow->setGap(Style::spaceXs * ctx.scale);
const bool pendingDelete = guiManaged && ctx.pendingDeleteWidgetName == widgetName;
const bool renaming = guiManaged && ctx.renamingWidgetName == widgetName;
auto moveSpacer = std::make_unique<Flex>();
moveSpacer->setFlexGrow(1.0f);
moveRow->addChild(std::move(moveSpacer));
if (!pendingDelete && !renaming && !currentLaneInherited && !currentLaneKey.empty()) {
auto actionRow = std::make_unique<Flex>();
actionRow->setDirection(FlexDirection::Horizontal);
actionRow->setAlign(FlexAlign::Center);
actionRow->setGap(Style::spaceXs * ctx.scale);
auto actionSpacer = std::make_unique<Flex>();
actionSpacer->setFlexGrow(1.0f);
actionRow->addChild(std::move(actionSpacer));
for (const auto targetLane : kLaneKeys) {
if (targetLane == currentLaneKey) {
@@ -1200,17 +1248,48 @@ namespace settings {
targetItems.push_back(widgetName);
setOverrides({{sourcePath, sourceItems}, {targetPath, targetItems}});
});
moveRow->addChild(std::move(moveBtn));
actionRow->addChild(std::move(moveBtn));
}
inspector->addChild(std::move(moveRow));
if (guiManaged) {
auto renameBtn = std::make_unique<Button>();
renameBtn->setText(i18n::tr("settings.entities.widget.instance.rename"));
renameBtn->setVariant(ButtonVariant::Ghost);
renameBtn->setFontSize(Style::fontSizeCaption * ctx.scale);
renameBtn->setMinHeight(Style::controlHeightSm * ctx.scale);
renameBtn->setPadding(Style::spaceXs * ctx.scale, Style::spaceSm * ctx.scale);
renameBtn->setRadius(Style::radiusSm * ctx.scale);
renameBtn->setOnClick(
[&renamingWidgetName = ctx.renamingWidgetName, widgetName, requestRebuild = ctx.requestRebuild]() {
renamingWidgetName = widgetName;
requestRebuild();
});
actionRow->addChild(std::move(renameBtn));
auto deleteBtn = std::make_unique<Button>();
deleteBtn->setGlyph("trash");
deleteBtn->setText(i18n::tr("settings.entities.widget.instance.delete"));
deleteBtn->setVariant(ButtonVariant::Ghost);
deleteBtn->setFontSize(Style::fontSizeCaption * ctx.scale);
deleteBtn->setGlyphSize(Style::fontSizeCaption * ctx.scale);
deleteBtn->setMinHeight(Style::controlHeightSm * ctx.scale);
deleteBtn->setPadding(Style::spaceXs * ctx.scale, Style::spaceSm * ctx.scale);
deleteBtn->setRadius(Style::radiusSm * ctx.scale);
deleteBtn->setOnClick([&pendingDeleteWidgetName = ctx.pendingDeleteWidgetName,
&renamingWidgetName = ctx.renamingWidgetName, widgetName,
requestRebuild = ctx.requestRebuild]() {
pendingDeleteWidgetName = widgetName;
renamingWidgetName.clear();
requestRebuild();
});
actionRow->addChild(std::move(deleteBtn));
}
inspector->addChild(std::move(actionRow));
}
addWidgetSettingsPanel(*inspector, widgetName, managedCapsuleGroupOptions(ctx.config, currentLanePath), ctx);
const bool pendingDelete = guiManaged && ctx.pendingDeleteWidgetName == widgetName;
const bool renaming = guiManaged && ctx.renamingWidgetName == widgetName;
if (renaming) {
auto renameRow = std::make_unique<Flex>();
renameRow->setDirection(FlexDirection::Horizontal);
@@ -1339,57 +1418,6 @@ namespace settings {
confirmPanel->addChild(std::move(confirmRow));
inspector->addChild(std::move(confirmPanel));
} else if (guiManaged && !currentLaneInherited && !currentLaneKey.empty()) {
auto actionRow = std::make_unique<Flex>();
actionRow->setDirection(FlexDirection::Horizontal);
actionRow->setAlign(FlexAlign::Center);
actionRow->setGap(Style::spaceXs * ctx.scale);
auto actionSpacer = std::make_unique<Flex>();
actionSpacer->setFlexGrow(1.0f);
actionRow->addChild(std::move(actionSpacer));
if (guiManaged && !renaming) {
auto renameBtn = std::make_unique<Button>();
renameBtn->setText(i18n::tr("settings.entities.widget.instance.rename"));
renameBtn->setVariant(ButtonVariant::Ghost);
renameBtn->setFontSize(Style::fontSizeCaption * ctx.scale);
renameBtn->setMinHeight(Style::controlHeightSm * ctx.scale);
renameBtn->setPadding(Style::spaceXs * ctx.scale, Style::spaceSm * ctx.scale);
renameBtn->setRadius(Style::radiusSm * ctx.scale);
renameBtn->setOnClick(
[&renamingWidgetName = ctx.renamingWidgetName, widgetName, requestRebuild = ctx.requestRebuild]() {
renamingWidgetName = widgetName;
requestRebuild();
});
actionRow->addChild(std::move(renameBtn));
}
if (guiManaged) {
auto deleteBtn = std::make_unique<Button>();
deleteBtn->setGlyph("trash");
deleteBtn->setText(i18n::tr("settings.entities.widget.instance.delete"));
deleteBtn->setVariant(ButtonVariant::Ghost);
deleteBtn->setFontSize(Style::fontSizeCaption * ctx.scale);
deleteBtn->setGlyphSize(Style::fontSizeCaption * ctx.scale);
deleteBtn->setMinHeight(Style::controlHeightSm * ctx.scale);
deleteBtn->setPadding(Style::spaceXs * ctx.scale, Style::spaceSm * ctx.scale);
deleteBtn->setRadius(Style::radiusSm * ctx.scale);
deleteBtn->setOnClick([&pendingDeleteWidgetName = ctx.pendingDeleteWidgetName,
&renamingWidgetName = ctx.renamingWidgetName, widgetName,
requestRebuild = ctx.requestRebuild]() {
pendingDeleteWidgetName = widgetName;
renamingWidgetName.clear();
requestRebuild();
});
actionRow->addChild(std::move(deleteBtn));
}
if (actionRow->children().empty()) {
// nothing to add — leave inspector without action row
} else {
inspector->addChild(std::move(actionRow));
}
}
} else {
std::string targetLaneKey;
+27
View File
@@ -1532,6 +1532,30 @@ namespace settings {
const ListSetting& list) { makeListBlock(section, entry, list); },
};
auto isEntryVisible = [&](const SettingEntry& e) -> bool {
if (!e.visibleWhen.has_value()) {
return true;
}
const auto& cond = *e.visibleWhen;
for (const auto& other : registry) {
if (other.path == cond.path) {
std::string currentValue;
if (const auto* toggle = std::get_if<ToggleSetting>(&other.control)) {
currentValue = toggle->checked ? "true" : "false";
} else if (const auto* select = std::get_if<SelectSetting>(&other.control)) {
currentValue = select->selectedValue;
}
for (const auto& v : cond.values) {
if (v == currentValue) {
return true;
}
}
return false;
}
}
return true;
};
for (const auto& entry : registry) {
if (ctx.searchQuery.empty() && !ctx.selectedSection.empty() && entry.section != ctx.selectedSection) {
continue;
@@ -1539,6 +1563,9 @@ namespace settings {
if (!ctx.showAdvanced && entry.advanced) {
continue;
}
if (!isEntryVisible(entry)) {
continue;
}
if (ctx.showOverriddenOnly && ctx.configService != nullptr &&
!ctx.configService->hasEffectiveOverride(entry.path)) {
continue;
+103 -53
View File
@@ -158,6 +158,7 @@ namespace settings {
.control = std::move(control),
.advanced = advanced,
.searchText = lower(searchText),
.visibleWhen = std::nullopt,
};
}
@@ -942,28 +943,52 @@ namespace settings {
entries.push_back(makeEntry(section, "capsules", tr("settings.schema.bar.widget-capsules.label"),
tr("settings.schema.bar.widget-capsules.description"), path("capsule"),
ToggleSetting{selectedBar->widgetCapsuleDefault}, "pill"));
entries.push_back(makeEntry(section, "capsules", tr("settings.schema.bar.capsule-groups.label"),
tr("settings.schema.bar.capsule-groups.description"), path("capsule_groups"),
ListSetting{.items = selectedBar->widgetCapsuleGroups}, "grouped capsules"));
entries.push_back(makeEntry(section, "capsules", tr("settings.schema.bar.capsule-fill.label"),
tr("settings.schema.bar.capsule-fill.description"), path("capsule_fill"),
colorRolePicker(selectedBar->widgetCapsuleFill), "color role pill", true));
entries.push_back(makeEntry(section, "capsules", tr("settings.schema.bar.capsule-foreground.label"),
tr("settings.schema.bar.capsule-foreground.description"), path("capsule_foreground"),
optionalColorRolePicker(selectedBar->widgetCapsuleForeground),
"color role foreground pill", true));
entries.push_back(makeEntry(section, "capsules", tr("settings.schema.bar.capsule-border.label"),
tr("settings.schema.bar.capsule-border.description"), path("capsule_border"),
capsuleBorderRolePicker(selectedBar->widgetCapsuleBorder), "color role pill outline",
true));
entries.push_back(makeEntry(section, "capsules", tr("settings.schema.bar.capsule-padding.label"),
tr("settings.schema.bar.capsule-padding.description"), path("capsule_padding"),
SliderSetting{selectedBar->widgetCapsulePadding, 0.0f, 48.0f, 1.0f, false},
"pill inset", true));
entries.push_back(makeEntry(section, "capsules", tr("settings.schema.bar.capsule-opacity.label"),
tr("settings.schema.bar.capsule-opacity.description"), path("capsule_opacity"),
SliderSetting{selectedBar->widgetCapsuleOpacity, 0.0f, 1.0f, 0.01f, false},
"pill alpha", true));
const SettingVisibility capsuleOn{path("capsule"), {"true"}};
{
auto e = makeEntry(section, "capsules", tr("settings.schema.bar.capsule-groups.label"),
tr("settings.schema.bar.capsule-groups.description"), path("capsule_groups"),
ListSetting{.items = selectedBar->widgetCapsuleGroups}, "grouped capsules");
e.visibleWhen = capsuleOn;
entries.push_back(std::move(e));
}
{
auto e = makeEntry(section, "capsules", tr("settings.schema.bar.capsule-fill.label"),
tr("settings.schema.bar.capsule-fill.description"), path("capsule_fill"),
colorRolePicker(selectedBar->widgetCapsuleFill), "color role pill", true);
e.visibleWhen = capsuleOn;
entries.push_back(std::move(e));
}
{
auto e = makeEntry(section, "capsules", tr("settings.schema.bar.capsule-foreground.label"),
tr("settings.schema.bar.capsule-foreground.description"), path("capsule_foreground"),
optionalColorRolePicker(selectedBar->widgetCapsuleForeground), "color role foreground pill",
true);
e.visibleWhen = capsuleOn;
entries.push_back(std::move(e));
}
{
auto e = makeEntry(section, "capsules", tr("settings.schema.bar.capsule-border.label"),
tr("settings.schema.bar.capsule-border.description"), path("capsule_border"),
capsuleBorderRolePicker(selectedBar->widgetCapsuleBorder), "color role pill outline", true);
e.visibleWhen = capsuleOn;
entries.push_back(std::move(e));
}
{
auto e =
makeEntry(section, "capsules", tr("settings.schema.bar.capsule-padding.label"),
tr("settings.schema.bar.capsule-padding.description"), path("capsule_padding"),
SliderSetting{selectedBar->widgetCapsulePadding, 0.0f, 48.0f, 1.0f, false}, "pill inset", true);
e.visibleWhen = capsuleOn;
entries.push_back(std::move(e));
}
{
auto e =
makeEntry(section, "capsules", tr("settings.schema.bar.capsule-opacity.label"),
tr("settings.schema.bar.capsule-opacity.description"), path("capsule_opacity"),
SliderSetting{selectedBar->widgetCapsuleOpacity, 0.0f, 1.0f, 0.01f, false}, "pill alpha", true);
e.visibleWhen = capsuleOn;
entries.push_back(std::move(e));
}
entries.push_back(makeEntry(section, "widget-list", tr("settings.schema.bar.start-widgets.label"),
tr("settings.schema.bar.start-widgets.description"), path("start"),
ListSetting{.items = selectedBar->startWidgets}, "left"));
@@ -1071,37 +1096,62 @@ namespace settings {
entries.push_back(makeEntry(section, "capsules", tr("settings.schema.bar.widget-capsules.label"),
tr("settings.schema.bar.widget-capsules.description"), mpath("capsule"),
ToggleSetting{ovr.widgetCapsuleDefault.value_or(bar.widgetCapsuleDefault)}, "pill"));
entries.push_back(makeEntry(section, "capsules", tr("settings.schema.bar.capsule-groups.label"),
tr("settings.schema.bar.capsule-groups.description"), mpath("capsule_groups"),
ListSetting{.items = ovr.widgetCapsuleGroups.value_or(bar.widgetCapsuleGroups)},
"grouped capsules"));
entries.push_back(makeEntry(section, "capsules", tr("settings.schema.bar.capsule-fill.label"),
tr("settings.schema.bar.capsule-fill.description"), mpath("capsule_fill"),
colorRolePicker(ovr.widgetCapsuleFill.value_or(bar.widgetCapsuleFill)),
"color role pill", true));
entries.push_back(
makeEntry(section, "capsules", tr("settings.schema.bar.capsule-foreground.label"),
tr("settings.schema.bar.capsule-foreground.description"), mpath("capsule_foreground"),
optionalColorRolePicker(ovr.widgetCapsuleForeground.has_value() ? ovr.widgetCapsuleForeground
: bar.widgetCapsuleForeground),
"color role foreground pill", true));
entries.push_back(makeEntry(
section, "capsules", tr("settings.schema.bar.capsule-border.label"),
tr("settings.schema.bar.capsule-border.description"), mpath("capsule_border"),
capsuleBorderRolePicker(ovr.widgetCapsuleBorderSpecified ? ovr.widgetCapsuleBorder : bar.widgetCapsuleBorder),
"color role pill outline", true));
entries.push_back(
makeEntry(section, "capsules", tr("settings.schema.bar.capsule-padding.label"),
tr("settings.schema.bar.capsule-padding.description"), mpath("capsule_padding"),
SliderSetting{static_cast<float>(ovr.widgetCapsulePadding.value_or(bar.widgetCapsulePadding)), 0.0f,
48.0f, 1.0f, false},
"pill inset", true));
entries.push_back(
makeEntry(section, "capsules", tr("settings.schema.bar.capsule-opacity.label"),
tr("settings.schema.bar.capsule-opacity.description"), mpath("capsule_opacity"),
SliderSetting{static_cast<float>(ovr.widgetCapsuleOpacity.value_or(bar.widgetCapsuleOpacity)), 0.0f,
1.0f, 0.01f, false},
"pill alpha", true));
const SettingVisibility mCapsuleOn{mpath("capsule"), {"true"}};
{
auto e = makeEntry(section, "capsules", tr("settings.schema.bar.capsule-groups.label"),
tr("settings.schema.bar.capsule-groups.description"), mpath("capsule_groups"),
ListSetting{.items = ovr.widgetCapsuleGroups.value_or(bar.widgetCapsuleGroups)},
"grouped capsules");
e.visibleWhen = mCapsuleOn;
entries.push_back(std::move(e));
}
{
auto e =
makeEntry(section, "capsules", tr("settings.schema.bar.capsule-fill.label"),
tr("settings.schema.bar.capsule-fill.description"), mpath("capsule_fill"),
colorRolePicker(ovr.widgetCapsuleFill.value_or(bar.widgetCapsuleFill)), "color role pill", true);
e.visibleWhen = mCapsuleOn;
entries.push_back(std::move(e));
}
{
auto e =
makeEntry(section, "capsules", tr("settings.schema.bar.capsule-foreground.label"),
tr("settings.schema.bar.capsule-foreground.description"), mpath("capsule_foreground"),
optionalColorRolePicker(ovr.widgetCapsuleForeground.has_value() ? ovr.widgetCapsuleForeground
: bar.widgetCapsuleForeground),
"color role foreground pill", true);
e.visibleWhen = mCapsuleOn;
entries.push_back(std::move(e));
}
{
auto e = makeEntry(section, "capsules", tr("settings.schema.bar.capsule-border.label"),
tr("settings.schema.bar.capsule-border.description"), mpath("capsule_border"),
capsuleBorderRolePicker(ovr.widgetCapsuleBorderSpecified ? ovr.widgetCapsuleBorder
: bar.widgetCapsuleBorder),
"color role pill outline", true);
e.visibleWhen = mCapsuleOn;
entries.push_back(std::move(e));
}
{
auto e =
makeEntry(section, "capsules", tr("settings.schema.bar.capsule-padding.label"),
tr("settings.schema.bar.capsule-padding.description"), mpath("capsule_padding"),
SliderSetting{static_cast<float>(ovr.widgetCapsulePadding.value_or(bar.widgetCapsulePadding)),
0.0f, 48.0f, 1.0f, false},
"pill inset", true);
e.visibleWhen = mCapsuleOn;
entries.push_back(std::move(e));
}
{
auto e =
makeEntry(section, "capsules", tr("settings.schema.bar.capsule-opacity.label"),
tr("settings.schema.bar.capsule-opacity.description"), mpath("capsule_opacity"),
SliderSetting{static_cast<float>(ovr.widgetCapsuleOpacity.value_or(bar.widgetCapsuleOpacity)),
0.0f, 1.0f, 0.01f, false},
"pill alpha", true);
e.visibleWhen = mCapsuleOn;
entries.push_back(std::move(e));
}
entries.push_back(makeEntry(section, "widget-list", tr("settings.schema.bar.start-widgets.label"),
tr("settings.schema.bar.start-widgets.description"), mpath("start"),
ListSetting{.items = ovr.startWidgets.value_or(bar.startWidgets)}, "left"));
+6
View File
@@ -112,6 +112,11 @@ namespace settings {
ListSetting, ShortcutListSetting, SessionPanelActionsSetting, ColorSetting,
MultiSelectSetting, ButtonSetting, ColorRolePickerSetting, SearchPickerSetting>;
struct SettingVisibility {
std::vector<std::string> path;
std::vector<std::string> values;
};
struct SettingEntry {
std::string section;
std::string group;
@@ -121,6 +126,7 @@ namespace settings {
SettingControl control;
bool advanced = false;
std::string searchText;
std::optional<SettingVisibility> visibleWhen;
};
// Runtime conditions that gate optional sections (e.g. compositor-specific features).
+1
View File
@@ -1470,6 +1470,7 @@ void SettingsWindow::buildScene(std::uint32_t width, std::uint32_t height) {
.control = settings::ButtonSetting{i18n::tr("settings.schema.desktop.widgets-editor.button"),
m_openDesktopWidgetEditor},
.searchText = "desktop widgets editor edit",
.visibleWhen = std::nullopt,
};
m_settingsRegistry.insert(it, std::move(btn));
}
+29 -13
View File
@@ -276,16 +276,23 @@ namespace settings {
}
std::vector<WidgetSettingSpec> commonWidgetSettingSpecs() {
const WidgetSettingVisibility capsuleOn{"capsule", {"true"}};
auto capsuleGroup = stringSpec("capsule_group");
capsuleGroup.visibleWhen = capsuleOn;
auto capsuleFill = colorRoleSpec("capsule_fill", "surface_variant");
capsuleFill.visibleWhen = capsuleOn;
auto capsuleBorder = colorRoleSpec("capsule_border", {}, true);
capsuleBorder.visibleWhen = capsuleOn;
auto capsuleForeground = colorRoleSpec("capsule_foreground", {}, true);
capsuleForeground.visibleWhen = capsuleOn;
auto capsulePadding = doubleSpec("capsule_padding", static_cast<double>(Style::barCapsulePadding), 0.0, 48.0, 1.0);
capsulePadding.visibleWhen = capsuleOn;
auto capsuleOpacity = doubleSpec("capsule_opacity", 1.0, 0.0, 1.0, 0.01);
capsuleOpacity.visibleWhen = capsuleOn;
return {
boolSpec("anchor", false, true),
colorRoleSpec("color", {}, true),
boolSpec("capsule", false),
stringSpec("capsule_group"),
colorRoleSpec("capsule_fill", "surface_variant"),
colorRoleSpec("capsule_border", {}, true),
colorRoleSpec("capsule_foreground", {}, true),
doubleSpec("capsule_padding", static_cast<double>(Style::barCapsulePadding), 0.0, 48.0, 1.0),
doubleSpec("capsule_opacity", 1.0, 0.0, 1.0, 0.01),
boolSpec("anchor", false, true), colorRoleSpec("color", {}, true), boolSpec("capsule", false),
std::move(capsuleGroup), std::move(capsuleFill), std::move(capsuleBorder),
std::move(capsuleForeground), std::move(capsulePadding), std::move(capsuleOpacity),
};
}
@@ -299,8 +306,9 @@ namespace settings {
};
const std::vector<WidgetSettingSelectOption> sysmonStats = {
{"cpu_usage", "settings.widgets.options.cpu-usage"}, {"cpu_temp", "settings.widgets.options.cpu-temp"},
{"ram_used", "settings.widgets.options.ram-used"}, {"ram_pct", "settings.widgets.options.ram-percent"},
{"swap_pct", "settings.widgets.options.swap-percent"}, {"disk_pct", "settings.widgets.options.disk-percent"},
{"gpu_temp", "settings.widgets.options.gpu-temp"}, {"ram_used", "settings.widgets.options.ram-used"},
{"ram_pct", "settings.widgets.options.ram-percent"}, {"swap_pct", "settings.widgets.options.swap-percent"},
{"disk_pct", "settings.widgets.options.disk-percent"},
};
const std::vector<WidgetSettingSelectOption> sysmonDisplay = {
{"gauge", "settings.widgets.options.gauge"},
@@ -383,7 +391,11 @@ namespace settings {
add(doubleSpec("length", 8.0, 0.0, 400.0, 1.0));
} else if (type == "sysmon") {
add(selectSpec("stat", "cpu_usage", sysmonStats));
add(stringSpec("path", "/"));
{
auto path = stringSpec("path", "/");
path.visibleWhen = WidgetSettingVisibility{"stat", {"disk_pct"}};
add(std::move(path));
}
add(segmentedSpec("display", "gauge", sysmonDisplay));
add(boolSpec("show_label", true));
} else if (type == "taskbar") {
@@ -393,7 +405,11 @@ namespace settings {
add(stringListSpec("hidden"));
add(stringListSpec("pinned"));
add(boolSpec("drawer", false));
add(intSpec("drawer_columns", 3, 1.0, 5.0, 1.0));
{
auto cols = intSpec("drawer_columns", 3, 1.0, 5.0, 1.0);
cols.visibleWhen = WidgetSettingVisibility{"drawer", {"true"}};
add(std::move(cols));
}
} else if (type == "volume") {
add(segmentedSpec("device", "output", volumeDeviceOptions));
add(boolSpec("show_label", true));
@@ -52,6 +52,11 @@ namespace settings {
std::string_view labelKey;
};
struct WidgetSettingVisibility {
std::string key;
std::vector<std::string> values;
};
struct WidgetSettingSpec {
std::string key;
std::string labelKey;
@@ -64,6 +69,7 @@ namespace settings {
std::vector<WidgetSettingSelectOption> options;
bool advanced = false;
bool segmented = false; // applies when valueType == Select
std::optional<WidgetSettingVisibility> visibleWhen;
};
[[nodiscard]] const std::vector<WidgetTypeSpec>& widgetTypeSpecs();
+108
View File
@@ -105,6 +105,47 @@ namespace {
t.find("soc") != std::string::npos || t.find("package") != std::string::npos;
}
int scoreGpuHwmonSensor(const std::string& hwmon_name, const std::string& label) {
const std::string name = toLower(hwmon_name);
const std::string lbl = toLower(label);
if (name.find("nvidia") != std::string::npos) {
return -1;
}
int score = 0;
if (name == "amdgpu") {
score += 20;
} else if (name == "i915" || name == "xe") {
score += 20;
} else if (name == "nouveau") {
score += 10;
} else {
return -1;
}
if (lbl.find("junction") != std::string::npos || lbl.find("edge") != std::string::npos) {
score += 30;
} else if (lbl.find("gpu") != std::string::npos || lbl.find("mem") != std::string::npos) {
score += 25;
}
return score;
}
bool isGpuHwmonAwake(const std::filesystem::path& hwmonPath) {
namespace fs = std::filesystem;
const auto deviceLink = hwmonPath / "device";
if (!fs::exists(deviceLink)) {
return true;
}
const auto status = readSmallTextFile(deviceLink / "power" / "runtime_status");
if (!status.has_value()) {
return true;
}
return *status == "active";
}
constexpr Logger kLog("sysmon");
} // namespace
@@ -143,6 +184,10 @@ void SystemMonitorService::retainCpuTemp() { m_cpuTempRefs.fetch_add(1, std::mem
void SystemMonitorService::releaseCpuTemp() { m_cpuTempRefs.fetch_sub(1, std::memory_order_relaxed); }
void SystemMonitorService::retainGpuTemp() { m_gpuTempRefs.fetch_add(1, std::memory_order_relaxed); }
void SystemMonitorService::releaseGpuTemp() { m_gpuTempRefs.fetch_sub(1, std::memory_order_relaxed); }
void SystemMonitorService::retainDiskPath(const std::string& path) {
const float initialPercent = isRunning() ? readDiskUsagePercent(path) : 0.0f;
std::lock_guard lock{m_statsMutex};
@@ -212,6 +257,7 @@ void SystemMonitorService::samplingLoop() {
next.sampledAt = std::chrono::steady_clock::now();
std::vector<std::string> diskPaths;
std::optional<double> previousCpuTemp;
std::optional<double> previousGpuTemp;
const auto currentCpu = readCpuTotals();
if (prevCpu.has_value() && currentCpu.has_value()) {
@@ -266,6 +312,7 @@ void SystemMonitorService::samplingLoop() {
{
std::lock_guard lock{m_statsMutex};
previousCpuTemp = m_latest.cpuTempC;
previousGpuTemp = m_latest.gpuTempC;
diskPaths.reserve(m_diskHistories.size());
for (const auto& [path, disk] : m_diskHistories) {
if (disk.refs > 0) {
@@ -281,6 +328,13 @@ void SystemMonitorService::samplingLoop() {
next.cpuTempC = previousCpuTemp.value_or(40.0);
}
if (m_gpuTempRefs.load(std::memory_order_relaxed) > 0) {
next.gpuTempC = readGpuTempCelsius();
}
if (!next.gpuTempC.has_value()) {
next.gpuTempC = previousGpuTemp;
}
std::vector<std::pair<std::string, float>> diskPercents;
diskPercents.reserve(diskPaths.size());
for (const auto& path : diskPaths) {
@@ -473,6 +527,60 @@ std::optional<double> SystemMonitorService::readCpuTempCelsius() {
return fallback_temp;
}
std::optional<double> SystemMonitorService::readGpuTempCelsius() {
namespace fs = std::filesystem;
const fs::path hwmon_root{"/sys/class/hwmon"};
if (!fs::exists(hwmon_root) || !fs::is_directory(hwmon_root)) {
return std::nullopt;
}
int bestScore = -1;
std::optional<double> best_temp;
for (const auto& hwmon_entry : fs::directory_iterator{hwmon_root}) {
if (!hwmon_entry.is_directory()) {
continue;
}
const std::string hwmonName = readSmallTextFile(hwmon_entry.path() / "name").value_or("");
const int nameScore = scoreGpuHwmonSensor(hwmonName, "");
if (nameScore < 0) {
continue;
}
if (!isGpuHwmonAwake(hwmon_entry.path())) {
continue;
}
for (const auto& file_entry : fs::directory_iterator{hwmon_entry.path()}) {
if (!file_entry.is_regular_file()) {
continue;
}
const std::string fileName = file_entry.path().filename().string();
if (!fileName.starts_with("temp") || !fileName.ends_with("_input")) {
continue;
}
const std::string base = fileName.substr(0, fileName.size() - 6);
const std::string label = readSmallTextFile(hwmon_entry.path() / (base + "_label")).value_or("");
const auto tempC = readTempInputCelsius(file_entry.path());
if (!tempC.has_value()) {
continue;
}
const int score = scoreGpuHwmonSensor(hwmonName, label);
if (score > bestScore) {
bestScore = score;
best_temp = *tempC;
}
}
}
return best_temp;
}
float SystemMonitorService::readDiskUsagePercent(const std::string& path) {
struct statvfs sv{};
if (::statvfs(path.c_str(), &sv) == 0 && sv.f_blocks > 0) {
+5
View File
@@ -21,6 +21,7 @@ struct SystemStats {
std::uint64_t swapUsedMb{0};
std::uint64_t swapTotalMb{0};
std::optional<double> cpuTempC;
std::optional<double> gpuTempC;
double netRxBytesPerSec{0.0};
double netTxBytesPerSec{0.0};
double loadAvg1{0.0};
@@ -45,6 +46,8 @@ public:
void retainCpuTemp();
void releaseCpuTemp();
void retainGpuTemp();
void releaseGpuTemp();
void retainDiskPath(const std::string& path);
void releaseDiskPath(const std::string& path);
[[nodiscard]] float diskUsagePercent(const std::string& path) const;
@@ -75,6 +78,7 @@ private:
};
[[nodiscard]] static std::optional<MemData> readMemoryKb();
[[nodiscard]] static std::optional<double> readCpuTempCelsius();
[[nodiscard]] static std::optional<double> readGpuTempCelsius();
[[nodiscard]] static float readDiskUsagePercent(const std::string& path);
struct NetIfaceBytes {
@@ -86,6 +90,7 @@ private:
std::atomic<bool> m_running{false};
std::atomic<int> m_cpuTempRefs{0};
std::atomic<int> m_gpuTempRefs{0};
std::thread m_thread;
std::mutex m_wakeMutex;
std::condition_variable m_wakeCv;