Proof Portal

Project overview

Omarchy

ProbeLabs65 findings · 95 requirements

A proof layer — requirements, tests and verified fixes — for two of Omarchy's subsystems: the application menu (launcher scripts, QML model, JSONC config, search and selection) and the lock screen (lock scripts, QML, PAM authentication). Scope is deliberately limited to those components of omacom/omarchy; the rest of the distribution is not covered.

Inbox
ReportR-1447GitHub issuePublicFixed

[Quattro] Screen lock moves the default audio sink to the internal speakers: DPMS-off drops HDMI port availability and flips the ALSA card profile

Reported by manozzo · filed Aug 25, 2026 · open at source

In the audited area (lock) — decided by agents · verified. Not validated yet.

At a glance

1 · What is claimed?
Only the report text below — no validator has restated it as checkable claims yet.
2 · What does it affect?
AreaIn the audited arealock· Decided by agents · verified
3 · Is it reproducible?
No validation has been run yet.
4 · What remains unclear?
  • Not validated yet: no claims, no reproducer.
5 · What happens next?
Our staff decide whether the report is valid. Where this project allows it, agents decide whether it duplicates another report or a known issue. So far: in the audited area (lock) — decided by agents · verified. The outcome (valid, duplicate, not a bug, …) is not decided yet. See the decision.

Original report

[Quattro] Screen lock moves the default audio sink to the internal speakers: DPMS-off drops HDMI port availability and flips the ALSA card profilerevision 1 · captured 2026-09-28 21:24 UTC via poll

Summary

On Quattro, locking the screen silently moves the default audio sink from my USB headset to the internal laptop speakers. Notification sounds (Discord and WhatsApp as Chromium web apps, Telegram native) then play out loud on the speakers for as long as the machine sits locked. Unlocking moves the sink back to the headset on its own, with no user action.

This did not happen on 3.8.x. It is a side effect of Quattro's lock path, not of any audio code — no Omarchy script touches pactl/wpctl during lock or idle.

System details

  • Omarchy 4.0.0-1
  • Kernel 7.1.8-arch1-3
  • CPU: Intel Core Ultra 7 258V (Lunar Lake)
  • GPU: Intel Arc 130V/140V
  • Audio: sof_sdw / cs42l43 (SoundWire)
  • PipeWire 1.6.8, WirePlumber 1.6.8, Hyprland v0.56.2
  • 3 displays: eDP-1 (internal), DP-1 and HDMI-A-1 (external)
  • Output device: Logitech G435 Wireless (USB dongle, 046d:0acb)

Steps to reproduce

  1. A laptop whose ALSA UCM exposes two mutually exclusive HiFi profiles — one carrying Headphones, the other carrying Speaker.
  2. At least one external monitor over HDMI/DP, so the card's HDMI ports report available.
  3. A USB headset selected as the default output, with nothing plugged into the 3.5 mm jack.
  4. default.configured.audio.sink in ~/.local/state/wireplumber/default-nodes pointing at the internal Speaker sink. This is easy to end up in — any past selection of "Speakers" in the audio panel puts it there permanently.
  5. Lock the screen and leave it locked.
  6. Have something play a notification sound.

Expected: audio keeps coming out of the headset.
Actual: audio comes out of the internal speakers until the screen is unlocked.

Root cause

  1. omarchy-system-lock locks and turns the display off (its own summary: "Lock the computer and turn off the display"). On 3.8.x these were separate: hypridle ran loginctl lock-session at 151 s — which does not blank the display — and hyprctl dispatch dpms off only at 330 s. A manual lock never blanked the display at all. Quattro merged the two, so every lock now blanks immediately.
  1. DPMS-off drops the HDMI/DP hot-plug detect, so the card's [Out] HDMI1 / [Out] HDMI2 ports flip from available to not available.
  1. The active profile HiFi (HDMI1, HDMI2, HDMI3, Headphones, Headset, Mic) (priority 10300) is now left with no available output port — [Out] Headphones is already not available with an empty jack. WirePlumber re-selects the sibling profile HiFi (HDMI1, HDMI2, HDMI3, Headset, Mic, Speaker) (priority 10200).
  1. That sibling is the only profile carrying [Out] Speaker, so the node alsa_output.pci-...sof_sdw.HiFi__Speaker__sink comes into existence.
  1. WirePlumber's default-nodes policy walks the MRU stack in default-nodes and picks the first sink that currently exists. With the Speaker sink at the top, it instantly wins over the headset, which is only ...audio.sink.0.
  1. On unlock the HDMI ports return, the profile flips back, the Speaker node disappears, and the default falls through to the headset — which is why it "fixes itself".

Evidence

Instrumented state during a real lock/unlock cycle:

18:14:37  profile=[Headphones, Headset, Mic]  default=HEADSET  hdmi_avail=2  dpms=[true,true,true]     ← idle
18:18:31  profile=[Headset, Mic, Speaker]     default=HEADSET  hdmi_avail=0  dpms=[false,false,false]  ← LOCKED
          speaker_sink_exists=1
18:19:38  profile=[Headphones, Headset, Mic]  default=HEADSET  hdmi_avail=1  dpms=[true,true,true]     ← unlocked
          speaker_sink_exists=0

Steps 2→4 are visible directly: HDMI availability 2 → 0, profile flip, and the Speaker sink appearing and disappearing in lockstep with DPMS.

Step 5 reproduces in isolation, with no locking involved at all — just forcing the profile flip by hand:

BEFORE  -> default-sink: ...G435_Wireless_Gaming_Headset...analog-stereo
DURING  -> default-sink: ...sof_sdw.HiFi__Speaker__sink        ← stolen the moment the node appears
AFTER   -> default-sink: ...G435_Wireless_Gaming_Headset...analog-stereo

Collateral damage

This profile churn recreates the internal card's nodes on every lock/unlock. After ~20 h of uptime my internal sinks carry IDs 12436–12441 while the USB headset is still 57 — thousands of node create/destroy cycles. That is exactly the condition described in #6952 (Quickshell SIGSEGV in QQuickRepeater when PipeWire removes USB audio nodes), so this likely feeds crash reports that look unrelated.

Workaround

wpctl set-default <headset-sink-id>

This promotes the headset to the top of the MRU stack. Verified: the Speaker node still appears during lock (step 4 still happens), but the default now stays on the headset through the whole cycle.

Suggested fixes

  1. Stop DPMS from driving card-profile selection. A blanked display is not an unplugged monitor; HDMI audio endpoints should not lose availability just because the panel is off. A packaged monitor.alsa.rules entry pinning the profile for SOF/SoundWire cards would fit the precedent already set by #5830, which ships WirePlumber rules to fix profile selection.
  2. Guard the default sink across profile churn — if the sink currently in use still exists, a newly appearing node should not take the default from it. The audio panel already had to grow "guard seamless output switching" for a related class of problem in #6548.
  3. Decouple locking from display blanking, at least for manual locks, restoring the 3.8.x behavior where the display stayed on.

Side note

Throughout the locked period, loginctl show-session $XDG_SESSION_ID -p LockedHint reported no. The Quattro Quickshell lock screen does not appear to set logind's LockedHint, so anything relying on it cannot tell the session is locked. Unrelated to the audio bug, but noticed while instrumenting.

View raw text
## Summary

On Quattro, locking the screen silently moves the default audio sink from my USB headset to the **internal laptop speakers**. Notification sounds (Discord and WhatsApp as Chromium web apps, Telegram native) then play out loud on the speakers for as long as the machine sits locked. Unlocking moves the sink back to the headset on its own, with no user action.

This did not happen on 3.8.x. It is a side effect of Quattro's lock path, not of any audio code — no Omarchy script touches `pactl`/`wpctl` during lock or idle.

## System details

- Omarchy 4.0.0-1
- Kernel 7.1.8-arch1-3
- CPU: Intel Core Ultra 7 258V (Lunar Lake)
- GPU: Intel Arc 130V/140V
- Audio: `sof_sdw` / `cs42l43` (SoundWire)
- PipeWire 1.6.8, WirePlumber 1.6.8, Hyprland v0.56.2
- 3 displays: eDP-1 (internal), DP-1 and HDMI-A-1 (external)
- Output device: Logitech G435 Wireless (USB dongle, `046d:0acb`)

## Steps to reproduce

1. A laptop whose ALSA UCM exposes two **mutually exclusive** HiFi profiles — one carrying `Headphones`, the other carrying `Speaker`.
2. At least one external monitor over HDMI/DP, so the card's HDMI ports report `available`.
3. A USB headset selected as the default output, with nothing plugged into the 3.5 mm jack.
4. `default.configured.audio.sink` in `~/.local/state/wireplumber/default-nodes` pointing at the internal `Speaker` sink. This is easy to end up in — any past selection of "Speakers" in the audio panel puts it there permanently.
5. Lock the screen and leave it locked.
6. Have something play a notification sound.

**Expected:** audio keeps coming out of the headset.
**Actual:** audio comes out of the internal speakers until the screen is unlocked.

## Root cause

1. `omarchy-system-lock` locks **and turns the display off** (its own summary: *"Lock the computer and turn off the display"*). On 3.8.x these were separate: `hypridle` ran `loginctl lock-session` at 151 s — which does not blank the display — and `hyprctl dispatch dpms off` only at 330 s. A **manual** lock never blanked the display at all. Quattro merged the two, so every lock now blanks immediately.

2. DPMS-off drops the HDMI/DP hot-plug detect, so the card's `[Out] HDMI1` / `[Out] HDMI2` ports flip from `available` to `not available`.

3. The active profile `HiFi (HDMI1, HDMI2, HDMI3, Headphones, Headset, Mic)` (priority 10300) is now left with **no available output port** — `[Out] Headphones` is already `not available` with an empty jack. WirePlumber re-selects the sibling profile `HiFi (HDMI1, HDMI2, HDMI3, Headset, Mic, Speaker)` (priority 10200).

4. That sibling is the **only** profile carrying `[Out] Speaker`, so the node `alsa_output.pci-...sof_sdw.HiFi__Speaker__sink` **comes into existence**.

5. WirePlumber's default-nodes policy walks the MRU stack in `default-nodes` and picks the first sink that currently exists. With the Speaker sink at the top, it instantly wins over the headset, which is only `...audio.sink.0`.

6. On unlock the HDMI ports return, the profile flips back, the Speaker node disappears, and the default falls through to the headset — which is why it "fixes itself".

## Evidence

Instrumented state during a real lock/unlock cycle:

```
18:14:37  profile=[Headphones, Headset, Mic]  default=HEADSET  hdmi_avail=2  dpms=[true,true,true]     ← idle
18:18:31  profile=[Headset, Mic, Speaker]     default=HEADSET  hdmi_avail=0  dpms=[false,false,false]  ← LOCKED
          speaker_sink_exists=1
18:19:38  profile=[Headphones, Headset, Mic]  default=HEADSET  hdmi_avail=1  dpms=[true,true,true]     ← unlocked
          speaker_sink_exists=0
```

Steps 2→4 are visible directly: HDMI availability `2 → 0`, profile flip, and the Speaker sink appearing and disappearing in lockstep with DPMS.

Step 5 reproduces in isolation, with no locking involved at all — just forcing the profile flip by hand:

```
BEFORE  -> default-sink: ...G435_Wireless_Gaming_Headset...analog-stereo
DURING  -> default-sink: ...sof_sdw.HiFi__Speaker__sink        ← stolen the moment the node appears
AFTER   -> default-sink: ...G435_Wireless_Gaming_Headset...analog-stereo
```

## Collateral damage

This profile churn recreates the internal card's nodes on every lock/unlock. After ~20 h of uptime my internal sinks carry IDs `12436`–`12441` while the USB headset is still `57` — thousands of node create/destroy cycles. That is exactly the condition described in #6952 (Quickshell SIGSEGV in `QQuickRepeater` when PipeWire removes USB audio nodes), so this likely feeds crash reports that look unrelated.

## Workaround

```bash
wpctl set-default <headset-sink-id>
```

This promotes the headset to the top of the MRU stack. Verified: the Speaker node still appears during lock (step 4 still happens), but the default now stays on the headset through the whole cycle.

## Suggested fixes

1. **Stop DPMS from driving card-profile selection.** A blanked display is not an unplugged monitor; HDMI audio endpoints should not lose availability just because the panel is off. A packaged `monitor.alsa.rules` entry pinning the profile for SOF/SoundWire cards would fit the precedent already set by #5830, which ships WirePlumber rules to fix profile selection.
2. **Guard the default sink across profile churn** — if the sink currently in use still exists, a newly appearing node should not take the default from it. The audio panel already had to grow "guard seamless output switching" for a related class of problem in #6548.
3. **Decouple locking from display blanking**, at least for manual locks, restoring the 3.8.x behavior where the display stayed on.

## Side note

Throughout the locked period, `loginctl show-session $XDG_SESSION_ID -p LockedHint` reported `no`. The Quattro Quickshell lock screen does not appear to set logind's `LockedHint`, so anything relying on it cannot tell the session is locked. Unrelated to the audio bug, but noticed while instrumenting.

Validation

Not checked yet. A validator will restate this report as checkable claims, map it to the project's requirements and write a reproducer that the proof engine runs.

Linked

  • Related PRpr/14299· confirmed
  • Fixed bypr/6642· confirmed

Decision

Decisions are recorded by our staff — or, where this project allows it, by agents: checked by a second, independent agent, or by a single agent where the project's policy says so. Any decision reached so far is shown below.

  • Decided by agents · verified: in the audited area (lock)

The outcome (valid, duplicate, not a bug, …) is not decided yet.