gamer-mode: 0.7.0 (#331)

* gamer-mode: 0.7.0

Adds a monitor bar widget that renders live readings the way the shell's own
sysmon widgets do, with a flame band beneath them while gamer mode runs.

- Readings warm toward the highlight colour over the shell's own activity and
  critical thresholds, so a monitor sitting beside a sysmon capsule group warms
  in step with it.
- The flame is advected along a wandering wind and sharpened so it reads as
  tongues rather than a smudge; the flare ignites and decays on an ease-out.
- The band's slot is reserved on each side of the readings, so toggling gamer
  mode never moves a digit and the readings stay centred in the pill.
- Panel readings outrank their captions, progress fills warm at the same
  thresholds, and the mark carries a load-reactive halo with a soft ember under
  the header while the mode runs.
- The mark is redrawn and ships a dark and a light ramp.

* gamer-mode: tighten the README prose
This commit is contained in:
RAMA
2026-08-10 20:26:19 -04:00
committed by GitHub
parent ced017593e
commit 12e0501c44
11 changed files with 1410 additions and 262 deletions
+61 -32
View File
@@ -20,47 +20,76 @@ local function percent(fraction)
return string.format("%d%%", math.floor((tonumber(fraction) or 0) * 100 + 0.5))
end
-- withTemp appends a temperature only when there is one to show, so a machine without
-- sensors reads as "GPU 18%" rather than "GPU 18% 0°C".
local function withTemp(label, showTemps, temp)
if showTemps and tonumber(temp) then
return label .. string.format(" %d°C", math.floor(tonumber(temp) + 0.5))
-- Rows rather than a joined string: setTooltip renders a list of {key, value} tables as
-- an aligned two-column block.
function M.tooltipRows(m, showTemps, gm)
m = type(m) == "table" and m or {}
local rows = {}
if not m.available then
rows[#rows + 1] = { key = noctalia.tr("widget.tooltip_loading"), value = "—" }
else
rows[#rows + 1] = { key = noctalia.tr("widget.cpu"), value = percent(m.cpuPerc) }
if showTemps and tonumber(m.cpuTemp) then
rows[#rows + 1] = { key = noctalia.tr("widget.cpu_temp"), value = string.format("%d°C", math.floor(tonumber(m.cpuTemp) + 0.5)) }
end
rows[#rows + 1] = { key = noctalia.tr("widget.ram"), value = gibibytes(m.memUsedMb) }
-- GPU and VRAM rows are omitted entirely when unsupported: an empty reading is
-- more honest than a zero that looks like an idle GPU.
if m.gpuAvailable and m.gpuPerc then
rows[#rows + 1] = { key = noctalia.tr("widget.gpu"), value = percent(m.gpuPerc) }
end
if showTemps and tonumber(m.gpuTemp) then
rows[#rows + 1] = { key = noctalia.tr("widget.gpu_temp"), value = string.format("%d°C", math.floor(tonumber(m.gpuTemp) + 0.5)) }
end
if m.vramUsedMb then
rows[#rows + 1] = { key = noctalia.tr("widget.vram"), value = gibibytes(m.vramUsedMb) }
end
end
return label
local validGm = type(gm) == "table" and gm or {}
local suspended = type(validGm.suspended) == "table" and validGm.suspended or {}
rows[#rows + 1] = {
key = noctalia.tr("widget.gamer_mode"),
value = validGm.enabled == true
and noctalia.trp("widget.gamer_mode_on", #suspended)
or noctalia.tr("widget.gamer_mode_off"),
}
return rows
end
function M.formatTooltip(m, showTemps, gm)
m = m or {}
if not m.available then
return noctalia.tr("widget.tooltip_loading")
end
-- Trimmed locally on purpose. The real binding is `noctalia.string.trim`, but the test
-- harness exposes a top-level `noctalia.trim`, so code written against the mock would
-- pass the suite and fail in the shell. A Lua pattern depends on neither.
local function trimmed(value)
return (tostring(value or ""):gsub("^%s+", ""):gsub("%s+$", ""))
end
local parts = {
withTemp("CPU " .. percent(m.cpuPerc), showTemps, m.cpuTemp),
"RAM " .. gibibytes(m.memUsedMb),
}
-- GPU and VRAM segments are omitted entirely when unsupported: an empty reading is
-- more honest than a zero that looks like an idle GPU.
if m.gpuAvailable and m.gpuPerc then
parts[#parts + 1] = withTemp("GPU " .. percent(m.gpuPerc), showTemps, m.gpuTemp)
local function iconFile()
local resting = trimmed(noctalia.getConfig("icon_file"))
if resting == "" then
return nil
end
if m.vramUsedMb then
parts[#parts + 1] = "VRAM " .. gibibytes(m.vramUsedMb)
if gameMode.enabled then
local active = trimmed(noctalia.getConfig("icon_file_active"))
if active ~= "" then
return active
end
end
local tooltip = table.concat(parts, " | ")
if gm and gm.enabled then
tooltip = noctalia.tr("notify.enabled_title") .. " | " .. tooltip
end
return tooltip
return resting
end
local function render()
barWidget.setGlyph(noctalia.getConfig("glyph") or "device-gamepad-2")
barWidget.setTooltip(M.formatTooltip(metrics, noctalia.getConfig("show_temps") ~= false, gameMode))
-- Accent the glyph while gamer mode is on, so the bar shows the state at a glance
-- without needing the tooltip.
barWidget.setGlyphColor(gameMode.enabled and "primary" or "on_surface")
local icon = iconFile()
if icon then
-- watch = true, so editing the file updates the bar without a reload. An image
-- cannot be tinted, which is what icon_file_active exists to work around.
barWidget.setImage(icon, true, 18)
else
barWidget.setGlyph(noctalia.getConfig("glyph") or "device-gamepad-2")
barWidget.setGlyphColor(gameMode.enabled and "primary" or "on_surface")
end
barWidget.setTooltip(M.tooltipRows(metrics, noctalia.getConfig("show_temps") ~= false, gameMode))
end
local function sendCommand(action)