pulsar-mouse: make the per-state widget colors configurable (#336)

* pulsar-mouse: make the per-state widget colors configurable

Both widgets hardcoded their state colors: error for a fault, secondary
while charging, error at the mouse's low-power threshold. The desktop
widget's one `color` setting only covered the normal state, and the bar
widget had no color setting at all.

Expose all four states (normal/charging/low/error) as `type = "color"`
settings on each widget, in the same shape battery-widget uses. Defaults
reproduce exactly what was hardcoded, so an existing install renders
identically until someone changes one.

The new settings are `advanced`; the desktop widget's pre-existing
`color` stays where it was so it does not disappear behind the toggle
for anyone already using it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* pulsar-mouse: rename the bar's normal-state key to normal_color

A bar widget's plugin settings share one TOML table with Noctalia's own
per-widget presentation settings, where `color` already exists ("Color
role for this widget's icon and label"). A plugin declaring `color`
there does not shadow it, it aliases it - one `color = "primary"` under
[widget.<id>] drove both pickers at once, so setting either silently
moved the other.

Renamed to `normal_color`, which also makes the retest unambiguous: the
glyph follows the plugin setting while Presentation's own Color picker
stays on Default.

The desktop widget keeps plain `color` - no clash there, and renaming it
would break existing configs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
harveywuk
2026-08-11 11:28:30 -04:00
committed by GitHub
co-authored by Claude Opus 5
parent a7581e8a1c
commit 13bde174ad
5 changed files with 132 additions and 16 deletions
+13 -2
View File
@@ -30,7 +30,7 @@ noctalia msg plugins enable harveywuk/pulsar-mouse
### Bar Widget ### Bar Widget
Add the `bar` bar widget to your bar. Shows a battery glyph, icon-only, with a tooltip for the full status (percentage, charging, signal strength when available) - the glyph is the only thing rendered regardless of bar orientation; the glyph turns a secondary accent color while charging, or red once battery drops to the mouse's configured Low Power Mode threshold (see Controls Panel; falls back to 15% on a driver that doesn't expose that threshold, e.g. nordic.py). On a wired mouse (no battery to show) it stays visible as a plain neutral glyph rather than hiding, since it's the only way to open the controls panel - DPI/lighting controls still work on a wired mouse, only the Power tab doesn't apply. If `pulsar-mouse-gui` isn't installed or running at all, this widget still works, it just always reads a fresh value directly from the mouse instead of the GUI's cached one (see Data source). Add the `bar` bar widget to your bar. Shows a battery glyph, icon-only, with a tooltip for the full status (percentage, charging, signal strength when available) - the glyph is the only thing rendered regardless of bar orientation; by default the glyph turns a secondary accent color while charging, or red once battery drops to the mouse's configured Low Power Mode threshold (see Controls Panel; falls back to 15% on a driver that doesn't expose that threshold, e.g. nordic.py) - each of those state colors is configurable, see Settings. On a wired mouse (no battery to show) it stays visible as a plain neutral glyph rather than hiding, since it's the only way to open the controls panel - DPI/lighting controls still work on a wired mouse, only the Power tab doesn't apply. If `pulsar-mouse-gui` isn't installed or running at all, this widget still works, it just always reads a fresh value directly from the mouse instead of the GUI's cached one (see Data source).
### Controls Panel ### Controls Panel
@@ -77,16 +77,27 @@ The `bar` and `battery` entries both read battery percentage/charging/low-power-
| Setting | Type | Default | Description | | Setting | Type | Default | Description |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `glyph_size` | `int` | `16` | Glyph size, 10-32. | | `glyph_size` | `int` | `16` | Glyph size, 10-32. |
| `normal_color` | `color` | `on_surface` | Glyph color at a normal battery level, and on a wired mouse. |
| `charging_color` | `color` | `secondary` | Glyph color while charging. |
| `warning_color` | `color` | `error` | Glyph color at or below the Low Power Mode threshold. |
| `error_color` | `color` | `error` | Glyph color when the mouse or the CLI can't be found. |
### Desktop Widget ### Desktop Widget
| Setting | Type | Default | Description | | Setting | Type | Default | Description |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `color` | `color` | `primary` | Accent color for the percentage text and progress bar. | | `color` | `color` | `primary` | Accent color for the percentage text and progress bar at a normal battery level. |
| `charging_color` | `color` | `secondary` | Used while charging. |
| `warning_color` | `color` | `error` | Used at or below the Low Power Mode threshold. |
| `error_color` | `color` | `error` | Used when the mouse or the CLI can't be found. |
| `show_progress` | `bool` | `true` | Shows or hides the battery-level progress bar. | | `show_progress` | `bool` | `true` | Shows or hides the battery-level progress bar. |
| `show_percent` | `bool` | `true` | Turn off to rely on just the glyph and progress bar. | | `show_percent` | `bool` | `true` | Turn off to rely on just the glyph and progress bar. |
| `glyph_size` | `int` | `28` | Glyph size, 16-64. | | `glyph_size` | `int` | `28` | Glyph size, 16-64. |
The four state colors are per-widget, so the bar and desktop widgets can differ. All of them (bar's `normal_color` included) sit behind the settings UI's **advanced** toggle, except the desktop widget's `color`. The defaults reproduce exactly what these were before they were configurable, so an existing setup looks unchanged until you touch one.
The normal-state setting is called `normal_color` on the bar widget but `color` on the desktop widget. That asymmetry is deliberate: a bar widget's plugin settings share a TOML table with Noctalia's own per-widget presentation settings, where `color` is already taken ("Color role for this widget's icon and label"). A plugin declaring `color` there does not shadow it, it aliases it - one key backs both pickers, so setting either silently moves the other. Desktop widgets have no such clash, and renaming that one would break existing configs.
## Notes ## Notes
- **No network access.** Every entry talks only to the local `pulsar-mouse` CLI (a spawned process, never a daemon) and, for the bar/desktop widgets, reads a local cache file. - **No network access.** Every entry talks only to the local `pulsar-mouse` CLI (a spawned process, never a daemon) and, for the bar/desktop widgets, reads a local cache file.
+23 -9
View File
@@ -17,9 +17,10 @@
-- --
-- Icon-only (no percentage label): battery level and charging state are -- Icon-only (no percentage label): battery level and charging state are
-- already in the hover tooltip, so a bar-row number would just be the same -- already in the hover tooltip, so a bar-row number would just be the same
-- fact twice. Just the Tabler "mouse-filled" glyph, tinted red by -- fact twice. Just the Tabler "mouse-filled" glyph, tinted per state by
-- statusColor() same as before, swapping to "mouse-off" for a genuine -- statusColor(), swapping to "mouse-off" for a genuine error/no-mouse state.
-- error/no-mouse state. -- Each of those state colors is a setting (normal/charging/low/error),
-- defaulting to what they were previously hardcoded to.
-- --
-- Signal quality has no synchronous getter (see gui.py's own comment on -- Signal quality has no synchronous getter (see gui.py's own comment on
-- this) - it only arrives via an async hidraw event the GUI listens for, so -- this) - it only arrives via an async hidraw event the GUI listens for, so
@@ -57,6 +58,17 @@ local checkingCli = false
local isVertical = false local isVertical = false
local glyphSize = noctalia.getConfig("glyph_size") local glyphSize = noctalia.getConfig("glyph_size")
-- Per-state colors, defaulted in plugin.toml to the values these used to be
-- hardcoded to. Read once here rather than per-render, same as glyph_size -
-- a settings change reloads the widget anyway.
--
-- `normal_color`, not `color` - see plugin.toml for why that name is
-- unusable on a bar widget.
local normalColor = noctalia.getConfig("normal_color")
local chargingColor = noctalia.getConfig("charging_color")
local warningColor = noctalia.getConfig("warning_color")
local errorColor = noctalia.getConfig("error_color")
local function statusGlyph(color) local function statusGlyph(color)
local name = (errorText ~= nil or percent == nil) and "mouse-off" or "mouse-filled" local name = (errorText ~= nil or percent == nil) and "mouse-off" or "mouse-filled"
return ui.glyph({ name = name, size = glyphSize, color = color }) return ui.glyph({ name = name, size = glyphSize, color = color })
@@ -64,15 +76,15 @@ end
local function statusColor() local function statusColor()
if errorText ~= nil then if errorText ~= nil then
return "error" return errorColor
end end
if charging then if charging then
return "secondary" return chargingColor
end end
if percent ~= nil and percent <= (lowPowerThreshold or LOW_POWER_DEFAULT) then if percent ~= nil and percent <= (lowPowerThreshold or LOW_POWER_DEFAULT) then
return "error" return warningColor
end end
return "on_surface" return normalColor
end end
local function tooltipText() local function tooltipText()
@@ -98,8 +110,10 @@ local function render()
local glyph local glyph
if not wireless then if not wireless then
-- No battery to show, but still clickable - a wired mouse still has -- No battery to show, but still clickable - a wired mouse still has
-- DPI/lighting controls in the panel, just not the Power tab. -- DPI/lighting controls in the panel, just not the Power tab. Takes the
glyph = ui.glyph({ name = "mouse-filled", size = glyphSize, color = "on_surface" }) -- normal-state color: there's nothing wrong here, it's just permanently
-- the only state this mouse has.
glyph = ui.glyph({ name = "mouse-filled", size = glyphSize, color = normalColor })
barWidget.setTooltip(noctalia.tr("ui.wired")) barWidget.setTooltip(noctalia.tr("ui.wired"))
else else
glyph = statusGlyph(statusColor()) glyph = statusGlyph(statusColor())
+9 -4
View File
@@ -25,6 +25,11 @@ local LOW_POWER_DEFAULT = 15 -- used when the driver has no low-power-threshold
-- getter (e.g. nordic.py) or it hasn't been read yet -- getter (e.g. nordic.py) or it hasn't been read yet
local color = noctalia.getConfig("color") local color = noctalia.getConfig("color")
-- The other three states. Defaulted in plugin.toml to the values these were
-- previously hardcoded to, so an existing install renders identically.
local chargingColor = noctalia.getConfig("charging_color")
local warningColor = noctalia.getConfig("warning_color")
local errorColor = noctalia.getConfig("error_color")
local showProgress = noctalia.getConfig("show_progress") local showProgress = noctalia.getConfig("show_progress")
local showPercent = noctalia.getConfig("show_percent") local showPercent = noctalia.getConfig("show_percent")
local glyphSize = noctalia.getConfig("glyph_size") local glyphSize = noctalia.getConfig("glyph_size")
@@ -38,13 +43,13 @@ local checkingCli = false
local function statusColor() local function statusColor()
if errorText ~= nil then if errorText ~= nil then
return "error" return errorColor
end end
if charging then if charging then
return "secondary" return chargingColor
end end
if percent ~= nil and percent <= (lowPowerThreshold or LOW_POWER_DEFAULT) then if percent ~= nil and percent <= (lowPowerThreshold or LOW_POWER_DEFAULT) then
return "error" return warningColor
end end
return color return color
end end
@@ -77,7 +82,7 @@ local function render()
if errorText ~= nil then if errorText ~= nil then
-- Diagnostic, not decorative - stays visible even with show_percent off, -- Diagnostic, not decorative - stays visible even with show_percent off,
-- since otherwise a broken setup would just silently show a bare glyph. -- since otherwise a broken setup would just silently show a bare glyph.
table.insert(rows, ui.label({ key = "status", text = errorText, fontSize = 12, color = "error" })) table.insert(rows, ui.label({ key = "status", text = errorText, fontSize = 12, color = errorColor }))
elseif showPercent and percent == nil then elseif showPercent and percent == nil then
table.insert(rows, ui.label({ key = "status", text = "--", fontSize = 20, color = "outline" })) table.insert(rows, ui.label({ key = "status", text = "--", fontSize = 20, color = "outline" }))
elseif showPercent then elseif showPercent then
+71 -1
View File
@@ -19,7 +19,7 @@
id = "harveywuk/pulsar-mouse" id = "harveywuk/pulsar-mouse"
name = "Pulsar Mouse" name = "Pulsar Mouse"
version = "1.6.1" version = "1.7.0"
plugin_api = 9 plugin_api = 9
author = "harveywuk" author = "harveywuk"
license = "MIT" license = "MIT"
@@ -41,6 +41,50 @@ entry = "bar.luau"
max = 32 max = 32
step = 1 step = 1
# Per-state glyph colors. The defaults reproduce what used to be hardcoded,
# so an existing install looks identical until someone changes one. Marked
# advanced since the interesting knob for most people is glyph_size - these
# are for matching a specific colorscheme.
#
# Deliberately NOT called `color`: a bar widget's settings share one TOML
# table with Noctalia's own per-widget presentation settings, and `color`
# is already taken there (the "Color role for this widget's icon and
# label" one). Declaring it here doesn't shadow that setting, it aliases
# it - one `color = ...` key ends up backing both pickers, so setting
# either silently moves the other. The desktop widget has no such clash,
# which is why its equivalent is still plain `color`.
[[widget.setting]]
key = "normal_color"
type = "color"
label_key = "settings.normal_color.label"
description_key = "settings.normal_color.description"
default = "on_surface"
advanced = true
[[widget.setting]]
key = "charging_color"
type = "color"
label_key = "settings.charging_color.label"
description_key = "settings.charging_color.description"
default = "secondary"
advanced = true
[[widget.setting]]
key = "warning_color"
type = "color"
label_key = "settings.warning_color.label"
description_key = "settings.warning_color.description"
default = "error"
advanced = true
[[widget.setting]]
key = "error_color"
type = "color"
label_key = "settings.error_color.label"
description_key = "settings.error_color.description"
default = "error"
advanced = true
[[panel]] [[panel]]
id = "controls" id = "controls"
entry = "panel.luau" entry = "panel.luau"
@@ -61,6 +105,32 @@ entry = "desktop.luau"
description_key = "settings.color.description" description_key = "settings.color.description"
default = "primary" default = "primary"
# The other three states (see bar's copy of these) - `color` above stays
# non-advanced since it's the one that was always here.
[[desktop_widget.setting]]
key = "charging_color"
type = "color"
label_key = "settings.charging_color.label"
description_key = "settings.charging_color.description"
default = "secondary"
advanced = true
[[desktop_widget.setting]]
key = "warning_color"
type = "color"
label_key = "settings.warning_color.label"
description_key = "settings.warning_color.description"
default = "error"
advanced = true
[[desktop_widget.setting]]
key = "error_color"
type = "color"
label_key = "settings.error_color.label"
description_key = "settings.error_color.description"
default = "error"
advanced = true
[[desktop_widget.setting]] [[desktop_widget.setting]]
key = "show_progress" key = "show_progress"
type = "bool" type = "bool"
+16
View File
@@ -1,18 +1,34 @@
{ {
"settings": { "settings": {
"charging_color": {
"description": "Used while the mouse is charging",
"label": "Charging Color"
},
"color": { "color": {
"description": "Accent color for the percentage text and progress bar", "description": "Accent color for the percentage text and progress bar",
"label": "Color" "label": "Color"
}, },
"error_color": {
"description": "Used when the mouse or the pulsar-mouse CLI can't be found",
"label": "Error Color"
},
"glyph_size": { "glyph_size": {
"label": "Glyph Size" "label": "Glyph Size"
}, },
"normal_color": {
"description": "Glyph color at a normal battery level, and on a wired mouse",
"label": "Normal Color"
},
"show_percent": { "show_percent": {
"description": "Turn off to rely on just the glyph (and progress bar, if shown)", "description": "Turn off to rely on just the glyph (and progress bar, if shown)",
"label": "Show Percentage Text" "label": "Show Percentage Text"
}, },
"show_progress": { "show_progress": {
"label": "Show Progress Bar" "label": "Show Progress Bar"
},
"warning_color": {
"description": "Used at or below the mouse's Low Power Mode threshold",
"label": "Low Battery Color"
} }
}, },
"ui": { "ui": {