The floating dictation indicator β proof that the key was heard.
A menu-bar glyph changing from π to π΄ is technically feedback, but it is
20 pixels away from where you are looking and it does not move. When you hold
a key and speak into a machine that gives no answer, the question is never
"which icon is showing" β it is "is this thing listening to me right now?".
So the indicator sits above the Dock, in the middle of the screen, and its
bars move with your voice: it answers the real question, because a bar that
responds to sound proves the microphone is genuinely open, not merely that a
code path was entered.
Two constraints shape everything here:
- It must never steal focus. Dictation ends by sending Cmd+V to the
frontmost app; a panel that activates would make itself frontmost and the
text would land in the void. Hence a non-activating
NSPanel that ignores
mouse events entirely and is ordered front "regardless".
- It must never touch AppKit off the main thread. Levels and state
changes arrive on the audio and key-tap threads. Rather than marshal each
one, those threads only assign to plain attributes; a main-thread timer
reads them and does all the drawing. One direction, no locks, no dispatch.
The geometry β how tall each bar is for a given level β is pure and tested.
Only :class:DictationOverlay touches a window server.
Classes
DictationOverlay
The live panel. Import-safe: AppKit is only touched in start().
Call :meth:set_state and :meth:set_level from any thread.
Source code in src/diapason/desktop/overlay.py
| def __init__(self) -> None:
# Written from the tap/audio threads, read from the main thread. Plain
# attribute assignment is atomic under the GIL, and a frame drawn from
# a half-updated pair is indistinguishable from the frame before it.
self.state = "idle"
self.level = 0.0
# Spectral shape, written from the audio thread like level. A tuple so
# a frame can never read a half-rebuilt list.
self.bands: tuple = ()
self._phase = 0.0
self._alpha = 0.0
self._hide_at = 0.0
self._window = None
self._view = None
self._timer = None
# True once the WebGL entity is up; False means the drawn-bar fallback.
self._web = False
self._bridge_at = 0.0
self._pushed_state = ""
|
Methods:
set_bands
Per-band energies, 0β1, low frequencies first.
Source code in src/diapason/desktop/overlay.py
| def set_bands(self, bands) -> None:
"""Per-band energies, 0β1, low frequencies first."""
self.bands = tuple(bands)
|
on_status
on_status(message: str) -> None
Adapter for DictationService(on_status=β¦).
Source code in src/diapason/desktop/overlay.py
| def on_status(self, message: str) -> None:
"""Adapter for ``DictationService(on_status=β¦)``."""
self.set_state(state_for_status(message))
|
start
Create the panel and begin the animation timer. False if no GUI.
Source code in src/diapason/desktop/overlay.py
| def start(self) -> bool: # pragma: no cover - needs a window server
"""Create the panel and begin the animation timer. False if no GUI."""
try:
self._build()
self._start_timer()
return True
except Exception: # noqa: BLE001 - headless or no window server
logger.debug("overlay unavailable", exc_info=True)
self._window = None
return False
|
Functions:
overlay_page
Where the bundled entity page lives inside the package.
Source code in src/diapason/desktop/overlay.py
| def overlay_page() -> Path:
"""Where the bundled entity page lives inside the package."""
return Path(__file__).with_name("overlay_page.html")
|
normalized_level
normalized_level(level: float) -> float
Map an RMS level (0β100 scale) onto 0β1 with a usable curve.
Source code in src/diapason/desktop/overlay.py
| def normalized_level(level: float) -> float:
"""Map an RMS level (0β100 scale) onto 0β1 with a usable curve."""
if level <= 0.0:
return 0.0
return min(1.0, math.sqrt(level / FULL_SCALE))
|
bar_heights
bar_heights(
level: float,
*,
state: str = "recording",
phase: float = 0.0,
bars: int = BARS,
) -> List[float]
Height of each bar, 0β1, for a level and animation phase.
phase advances one full cycle per second; it is what keeps the bars
alive while the level is steady. Unknown states render as idle rather
than raising β an indicator is never worth crashing dictation over.
Source code in src/diapason/desktop/overlay.py
| def bar_heights(
level: float, *, state: str = "recording", phase: float = 0.0, bars: int = BARS
) -> List[float]:
"""Height of each bar, 0β1, for a level and animation phase.
``phase`` advances one full cycle per second; it is what keeps the bars
alive while the level is steady. Unknown states render as idle rather
than raising β an indicator is never worth crashing dictation over.
"""
if state == "transcribing":
# No microphone is open, so there is no level to show. A travelling
# wave says "working" without pretending to be a meter.
return [
0.22 + 0.5 * (0.5 + 0.5 * math.sin(phase * 2 * math.pi * 1.4 - i * 0.9))
for i in range(bars)
]
if state != "recording":
return [MIN_HEIGHT] * bars
norm = normalized_level(level)
out: List[float] = []
for i in range(bars):
shape = SHAPE[i % len(SHAPE)]
# Per-bar phase offset so they ripple instead of pumping in unison.
wobble = 0.78 + 0.22 * math.sin(phase * 2 * math.pi + i * 2.1)
height = MIN_HEIGHT + (1.0 - MIN_HEIGHT) * norm * shape * wobble
out.append(max(MIN_HEIGHT, min(1.0, height)))
return out
|
state_for_status
state_for_status(message: str) -> str
Map a DictationService status line onto an overlay state.
The service speaks in human sentences ("captured 2.3s (level 1.4)"); the
overlay has four faces. Keeping the mapping here means the status strings
stay free to change wording without breaking the UI.
Source code in src/diapason/desktop/overlay.py
| def state_for_status(message: str) -> str:
"""Map a DictationService status line onto an overlay state.
The service speaks in human sentences ("captured 2.3s (level 1.4)"); the
overlay has four faces. Keeping the mapping here means the status strings
stay free to change wording without breaking the UI.
"""
if message.startswith("recording"):
return "recording"
if message.startswith(("transcribing", "captured", "pasting")):
return "transcribing"
if message.startswith("pasted"):
return "done"
if message.startswith(("cancelled", "ERROR", "no audio", "empty", "skipped")):
return "hide"
return "idle"
|