Files
community-plugins/gamer-mode/widget.luau
T
RAMAandGitHub 0f64148bea Add gamer-mode plugin (#168)
* feat: add gamer-mode plugin

Live CPU, RAM, swap, GPU, VRAM, load and network readings in the bar and a
panel, plus a one-click mode that suspends background resource hogs and
restores exactly what it suspended.

* chore: rebuild the thumbnail with the upstream generator

Produced by assets.noctalia.dev/plugins/thumbnail-generator.html with the
title, the Gaming tag, the panel screenshot and the Red accent, rather than
composed by hand at the same dimensions.

* fix(gamer-mode): write the session before suspending anything

Enable suspended targets first and dropped the writeSnapshot return, so
a failed write left processes frozen and units stopped with no record to
restore them from, while the service published gamer mode as off and
switched the power profile.

The snapshot now lands on disk first, and a failed write aborts the
enable with the machine untouched and an error notification.

Writing first can record a target whose suspend command then failed.
That direction is safe: thawing is unconditional and SIGCONT to a
running process is a no-op, and a stop target restarts only after a live
probe says it is still down.
2026-07-30 22:32:21 -04:00

117 lines
3.4 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
-- 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))
end
return label
end
function M.formatTooltip(m, showTemps, gm)
m = m or {}
if not m.available then
return noctalia.tr("widget.tooltip_loading")
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)
end
if m.vramUsedMb then
parts[#parts + 1] = "VRAM " .. gibibytes(m.vramUsedMb)
end
local tooltip = table.concat(parts, " | ")
if gm and gm.enabled then
tooltip = noctalia.tr("notify.enabled_title") .. " | " .. tooltip
end
return tooltip
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")
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