Skip to content

Remember the internal monitor's position across clamshell recoveryFix clamshell remember internal position - #10664

Open
Roriz wants to merge 2 commits into
omacom:quattrofrom
Roriz:fix-clamshell-remember-internal-position
Open

Remember the internal monitor's position across clamshell recoveryFix clamshell remember internal position#10664
Roriz wants to merge 2 commits into
omacom:quattrofrom
Roriz:fix-clamshell-remember-internal-position

Conversation

@Roriz

@Roriz Roriz commented Sep 7, 2026

Copy link
Copy Markdown

What's wrong?

Undock a laptop and the internal panel can come back on the wrong side of the external monitor.

The panel was at 0x0, to the left of the external. Close the lid, work docked, open it again, and it now sits on the right, with every window that was on it.

Why

On clamshell entry the script disables the panel. On exit it re-enables the panel with position = "auto", because the shipped monitors.lua has no rule naming the internal output, so read_monitor_position finds nothing and falls back to auto.

auto places a panel after whatever is already positioned, rather than putting it back where it was. The external monitor never went down, so it keeps 0x0 and the returning panel goes after it. Hyprland recomputes the layout instead of restoring it, and with the external already placed, the result differs from before.

@mokkabonna measured the same ordering on #7326, where two externals "effectively switched sides" after a scale change.

The fix: save and restore

Read the panel's real position from the compositor just before it goes down, and put it back when it comes up.

disable_internal()        -> remember_internal_position()   # hyprctl monitors all -j
enable_internal_output()  -> read_monitor_position()        # returns the saved value

read_monitor_position now tries a rule for the internal output, then the saved position, then auto. The new step sits between the two that were already there.

Nothing here reconstructs the layout. It reads no more config than before and adds no fallback chain. It records the position the panel actually had, from hyprctl monitors, while that value still exists, and hands the same value back on recovery.

The source is the compositor, not monitors.lua, so there is no config evaluation to replicate. Whatever hyprctl reported for the panel is what goes back. A machine that has never docked has no saved value and behaves as it does today.

Scope

The production diff is 38 added lines with no changed lines: one constant, three functions, one if block, and one call. Every existing code path is byte-identical.

It does not touch:

References #7326 rather than closing it. That issue also covers the scaling path, which this PR leaves alone.

Roriz and others added 2 commits September 7, 2026 12:46
Hyprland's position="auto" is order-dependent, not identity-based: it
appends a newly re-enabled output after whatever else is already
positioned. Docking (lid closed + external active) disables the
internal panel via a reload, leaving the external monitor as the sole
active output at 0x0. Undocking re-enables the internal panel with
"auto" again, but by then the external monitor already occupies the
first slot, so the internal panel gets appended after it -- silently
swapping their left/right arrangement on every dock/undock cycle.

Extend the existing scale-remembering pattern (remember_internal_scale
/ SCALE_STATE) to position: capture the internal panel's live x/y right
before disabling it, and prefer that remembered value over "auto" on
recovery, behind the existing configured-rule check so an explicit
per-output rule still wins. A machine that has never docked still
falls back to "auto" exactly as before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Extend the coverage added in the previous commit with the edge cases
worth nailing down before this touches a live desktop's monitor state:

- Negative coordinates round-trip (a monitor above/left of the origin).
- A corrupted or injection-crafted position state file is validated on
  read and never forwarded into the generated hyprctl eval string,
  mirroring the omacom#8129 hardening for the internal connector name.
- A non-integer reported position is never remembered in the first
  place.
- A manual internal-monitor disable keeps the clamshell layer from
  touching scale or position at all.
- A poll tick re-running disable_internal while already docked does
  not clobber the remembered value with a disabled panel's empty read.
- Two successive dock/undock cycles track a rearranged position
  without leaking a stale one from the first cycle.
- A live scale-drift correction (panel never disabled) carries the
  remembered position instead of slipping back to "auto".
- A configured position variable reference, and the catch-all rule's
  own position, are both confirmed against precedence: a rule specific
  to the internal output wins, a remembered value beats the catch-all,
  and the catch-all's position is never read for the internal panel.

Also made the hyprctl stub list an external monitor alongside the
internal one, like real `hyprctl monitors all -j` output, so every
case in the file exercises the by-name jq selection instead of a
single-monitor array that could never expose a selection bug.

Verified these fail without the previous commit's fix (reverted the
script only, kept the new tests: fails immediately on "clamshell
disable remembers internal position").

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant