The whole point of Thermal Canary Pro’s Smart Alert Overlay is that it’s silent until it isn’t: no gauge to babysit, just a toast that appears over your fullscreen game only when a sensor actually crosses a danger threshold, with a sound so you notice it even with a headset on.

That second part broke in a way that took real debugging to catch, because every signal said it was working.

The toast itself: bypassing the window manager on purpose

Getting a notification to draw on top of a fullscreen game on Linux isn’t free. Most windows are managed: the window manager decides stacking order, and a fullscreen game normally grabs the display and refuses to let anything else draw over it.

The overlay uses Qt.WindowType.BypassWindowManagerHint, which creates an X11 override-redirect window, one the window manager has no say over at all. Combined with WA_ShowWithoutActivating (so the toast never steals keyboard focus mid-game) and WA_TranslucentBackground (for real rounded corners instead of a rectangular box), that’s what makes a toast physically capable of appearing over a fullscreen Vulkan surface.

The bug: Qt swore the sound played, and nothing came out

For the actual alert sound, the natural choice was Qt’s own audio stack, QSoundEffect or QMediaPlayer, both ship with PyQt6. Both reported a completely successful playback lifecycle: QSoundEffect.isPlaying flipped from True to False exactly like a normal finished playback, QMediaPlayer.mediaStatusChanged reached EndOfMedia, zero errors surfaced anywhere.

And on a PipeWire desktop, nothing came out of the speakers. Not quiet, not distorted, just silent, while every API signal insisted the exact opposite.

The root cause, confirmed live and advisor-reviewed: PyQt6 bundles its own libffmpegmediaplugin.so, which links its own copy of libpulsecommon. That bundled copy appears to silently fail the shared-memory ring-buffer handshake against the system’s running pipewire-pulse daemon.

The client believes it opened a stream and happily writes audio samples into it, nobody on the other end is actually reading them. Same WAV file, played via the system’s own paplay on the same machine at the same moment, works instantly.

The fix: shell out, don’t fight Qt’s audio stack

Rather than trying to patch or replace Qt’s bundled audio plugin per user’s Pulse/PipeWire/ALSA setup, the fix is to skip it entirely: fire-and-forget subprocess calls to whichever of pw-play, paplay, or aplay actually exists on the system, in that order. paplay gets volume control (--volume=<0-65536>), aplay is the last resort with no volume control at all, for a pure-ALSA system with neither of the other two installed.

All three use the system’s real audio libraries, not a bundled copy, so the same handshake that silently failed inside the app works normally outside it.

Why the alarm doesn’t stop when you dismiss the toast

One more deliberate, slightly aggressive design choice: closing the toast (clicking it, or its auto-dismiss timer for a WARN-level alert) does not stop the alarm sound for a CRIT-level one. CRIT loops a beep every 2.5 seconds for as long as the sensor genuinely stays in danger, and it keeps looping even if you’ve already closed the visual toast.

Dismissing a notification isn’t the same as the hardware actually cooling down, so the notification can go away without the alarm going quiet.

The loop only stops on its own once the sensor cools 2 degrees below the CRIT threshold, a small hysteresis margin so a sensor oscillating right at the line doesn’t restart the sound every single tick.

Why this matters beyond one app

If you’re building anything on Linux that needs to make noise reliably, audio players are one of the rare cases where shelling out to a real CLI tool beats a language’s own “native” audio bindings, precisely because the bindings bundle their own copy of a library the system already has a working one of.

Two copies of the same audio stack talking past each other is a specific, checkable failure mode, and Qt’s own success signals will not tell you it happened.