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-5014GitHub issuePublicReceived

KeyboardPanel: a throwing plugin close() locks the whole shell's mouse input

Reported by aligu7 · filed Sep 10, 2026Closed upstream on 2026-09-10 by its own reporter aligu7. GitHub close reason: completed. Closed by its own reporter — not by itself evidence of a fix.

Received and recorded verbatim. Nothing has been checked 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?
Not yet mapped to any requirement or component.
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. No decision has been recorded yet.

Original report

KeyboardPanel: a throwing plugin close() locks the whole shell's mouse inputrevision 1 · captured 2026-10-04 12:10 UTC via poll

Summary

If any plugin's Panel.close() throws, the shell's keyboard panel overlay stays mapped forever and swallows every pointer click on the entire desktop until quickshell is restarted. Keyboard input (and Hyprland binds) keep working, which matches the "only my mouse died" report.

Evidence (real incident, today)

The third-party idr4n/omarchy-prayer-times panel did:

function close() {
  root.showSettings = false
  root.setCenterHoverRevealSuppressed(false)   // threw TypeError
  root.controller.hide()                        // never reached
}

after the plugin-facing bar API made centerHoverRevealSuppressed readonly (see https://github.com/idr4n/omarchy-prayer-times/pull/1). The log repeated every click:

WARN scene: .../prayer-times/Panel.qml[65:-1]: TypeError: Cannot assign to read-only property "centerHoverRevealSuppressed"

Why it wedges input

Ui/KeyboardPanel.qml close() (~line 69):

function close() {
  if (owner && "close" in owner) owner.close()
  else root.open = false
}

When owner.close() throws, the assignment of root.open = false never runs and the Panel controller is never hidden, so:
- the full-screen PanelWindow stays mapped,
- the dismiss MouseArea keeps catching clicks and calls close() again → throws again → the popup can never close,
- the overlay blocks pointer input compositor-wide.

Suggested hardening

Let the keyboard panel always tear itself down even if a plugin-authored close() throws:

function close() {
  try {
    if (owner && "close" in owner) owner.close()
  } catch (e) { /* fall through and force-unmap */ }
  if (owner && owner.controller) owner.controller.hide()   // drives root.open via binding
  if (!owner || !("close" in owner)) root.open = false
}

Because a plugin's popup open is bound to owner.controller's state, forcing the controller hide() is what reliably unmaps the overlay and triggers the onOpenChanged release path. Consider also running releasePopout regardless.

This turns a single bad third-party plugin into a closed popup instead of a reboot-requiring UI lockout.

View raw text
## Summary
If any plugin's `Panel.close()` throws, the shell's keyboard panel overlay stays mapped forever and **swallows every pointer click on the entire desktop** until `quickshell` is restarted. Keyboard input (and Hyprland binds) keep working, which matches the "only my mouse died" report.

## Evidence (real incident, today)
The third-party `idr4n/omarchy-prayer-times` panel did:

```qml
function close() {
  root.showSettings = false
  root.setCenterHoverRevealSuppressed(false)   // threw TypeError
  root.controller.hide()                        // never reached
}
```

after the plugin-facing bar API made `centerHoverRevealSuppressed` readonly (see https://github.com/idr4n/omarchy-prayer-times/pull/1). The log repeated every click:

```
WARN scene: .../prayer-times/Panel.qml[65:-1]: TypeError: Cannot assign to read-only property "centerHoverRevealSuppressed"
```

## Why it wedges input
`Ui/KeyboardPanel.qml` `close()` (~line 69):

```qml
function close() {
  if (owner && "close" in owner) owner.close()
  else root.open = false
}
```

When `owner.close()` throws, the assignment of `root.open = false` never runs and the Panel controller is never hidden, so:
- the full-screen `PanelWindow` stays mapped,
- the dismiss `MouseArea` keeps catching clicks and calls `close()` again → throws again → the popup can never close,
- the overlay blocks pointer input compositor-wide.

## Suggested hardening
Let the keyboard panel always tear itself down even if a plugin-authored `close()` throws:

```qml
function close() {
  try {
    if (owner && "close" in owner) owner.close()
  } catch (e) { /* fall through and force-unmap */ }
  if (owner && owner.controller) owner.controller.hide()   // drives root.open via binding
  if (!owner || !("close" in owner)) root.open = false
}
```

Because a plugin's popup `open` is bound to `owner.controller`'s state, forcing the controller `hide()` is what reliably unmaps the overlay and triggers the `onOpenChanged` release path. Consider also running `releasePopout` regardless.

This turns a single bad third-party plugin into a closed popup instead of a reboot-requiring UI lockout.

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/11080· 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.

No decision has been recorded yet.