Files
community-plugins/gamer-mode/widget.luau
T
RAMAandGitHub 12e0501c44 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
2026-08-10 20:26:19 -04:00

146 lines
4.8 KiB
Luau

--!nonstrict
--
-- Bar widget: a glyph plus a live tooltip. It owns no state -- it renders whatever the
-- service publishes and sends commands back through `noctalia.state`.
local PANEL_ID = "nomadcxx/gamer-mode:main"
local MIB_PER_GIB = 1024
local M = {}
local metrics = noctalia.state.get("metrics") or { available = false }
local gameMode = noctalia.state.get("game_mode") or { enabled = false, suspended = {} }
local nonceCounter = 0
local function gibibytes(mib)
return string.format("%.1fG", (tonumber(mib) or 0) / MIB_PER_GIB)
end
local function percent(fraction)
return string.format("%d%%", math.floor((tonumber(fraction) or 0) * 100 + 0.5))
end
-- 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
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
-- 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 function iconFile()
local resting = trimmed(noctalia.getConfig("icon_file"))
if resting == "" then
return nil
end
if gameMode.enabled then
local active = trimmed(noctalia.getConfig("icon_file_active"))
if active ~= "" then
return active
end
end
return resting
end
local function render()
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)
nonceCounter = nonceCounter + 1
noctalia.state.set("command", {
nonce = noctalia.nowMs() * 1000 + nonceCounter,
action = action,
})
end
-- Left-click opens the panel by default. A first click should show what the machine is
-- doing, not suspend a list of programs the user has not read yet.
function M.onClick()
if (noctalia.getConfig("click_action") or "open_panel") == "toggle" then
sendCommand("toggle")
else
noctalia.togglePanel(PANEL_ID)
end
end
-- Right-click always toggles, whatever click_action says, so the one-click path stays
-- available without opening the panel first.
function M.onRightClick()
sendCommand("toggle")
end
noctalia.state.watch("metrics", function(value)
metrics = type(value) == "table" and value or { available = false }
render()
end)
noctalia.state.watch("game_mode", function(value)
gameMode = type(value) == "table" and value or { enabled = false, suspended = {} }
render()
end)
-- ── shell entry points (must be globals) ──
function onClick()
M.onClick()
end
function onRightClick()
M.onRightClick()
end
function onConfigChanged()
render()
end
render()
return M