overview: fix chip positioning, stale geometry, and broken bindings#188
Open
kanikamaxxing wants to merge 3 commits into
Open
overview: fix chip positioning, stale geometry, and broken bindings#188kanikamaxxing wants to merge 3 commits into
kanikamaxxing wants to merge 3 commits into
Conversation
The mapped monitor object in AxctlService discards mon.metadata.x/y/transform so monitorData.x is undefined for every monitor. OverviewWindow's position math falls back to 0, which works for the monitor at the origin but renders windows on any other monitor outside the thumbnail bounds (the global window x is used without the monitor offset subtracted). Also drops the per-monitor window filter in Overview so each overview shows all windows across monitors, and computes per-window scale from the window's own monitor width so cross-monitor windows fit the cell properly instead of being sized for the overview's monitor. OverviewWindow now also pins the target workspace to the drop monitor after movetoworkspacesilent, so drops on cells in monitor A's overview actually land on monitor A regardless of where the workspace currently lives. And imports qs.modules.bar.workspaces so the existing CompositorData reference on drag finalize stops throwing ReferenceError. Repro for the primary bug: dual-monitor setup with monitors at non-zero offsets. The overview on the second monitor renders all window thumbnails outside their cell because monitorData.x is undefined and global x positions are used as-is.
CompositorData.updateWindowList() was a no-op, relying on the inline state piggybacked onto every axctl subscribe event. That state is stale for window geometry: when a dispatch like movetoworkspacesilent causes Hyprland to re-tile, the resulting per-window resizes do not get reflected in the axctl daemon's cached state until something else (e.g. Event.WorkspaceChanged) triggers a refresh. The visible symptom in the overview: drag a chip from cell A to cell B and the dispatch happens correctly — the real windows re-tile on the desktop — but every chip keeps showing the pre-move size, leaving white space around each window thumbnail. Closing and reopening the overview fixes it; so does switching monitors (the focus change emits a workspace event which finally flushes the daemon's state). hyprctl clients -j returns the correct sizes the whole time, axctl window list does not. Wire updateWindowList() to actually fetch hyprctl clients -j, mapped to the same record shape AxctlService.applyState produces so other consumers are unaffected. Axctl events are still used as the "something changed" trigger (they fire reliably even when their inline state is stale), but the authoritative geometry comes from hyprctl now. Debounced at 60ms so a burst of events doesn't fork many subprocesses. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two related chip-update papercuts on top of the hyprctl refresh fix: 1. After a drag/click on a chip, the onReleased handler did root.x = root.initX and root.y = root.initY to snap the chip back into place. In QML, a plain assignment destroys the property binding — so x and y stopped tracking initX/initY forever after, and subsequent windowData updates couldn't move the chip. The MouseArea's drag.target already breaks the binding during drag, so a clean release path has to restore it. Re-bind via Qt.binding(() => root.initX) in all three onReleased branches (cross-workspace move, floating reposition, plain reset) and the same in ScrollingWorkspace's drop handler (bound to baseX/baseY, since initX there is a press-time snapshot rather than a reactive binding). 2. Every chip rebuild (which happens on each axctl event because filteredWindowData returns a fresh array of fresh objects, so the Repeater destroys + recreates delegates) starts with Item-default x/y/width/height = 0, then the bindings kick in and Behavior on each animates 0 -> target over animDuration. With axctl-driven state churn, chips never finished settling — they perpetually looked half-sized with white borders around them. Gate the Behaviors behind a `ready` flag flipped true in Component.onCompleted so the initial assignment skips the animation; subsequent in-place updates still animate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three related fixes for the overview's window-thumbnail rendering. Each one is independent of the others but they all surface as "chips look wrong until you close + reopen the overview", so grouping them.
1. Render windows correctly on non-origin monitors (original commit)
On a multi-monitor setup where any monitor is positioned at a non-zero virtual-desktop offset, opening the overview on that monitor renders every window thumbnail outside its workspace cell — windows render at their global
xposition scaled down instead of at their monitor-local position.Root cause:
AxctlService.qml's monitor mapping discardsmon.metadata.x,mon.metadata.y, andmon.metadata.transform, somonitorData.xis undefined for every monitor.OverviewWindow.qml'slet base = windowData.at[0] - monitorData.xfalls back to0, which only works for a monitor at the origin.Also drops the per-monitor window filter in
Overview.qmlso each overview shows windows from every monitor, looks up each window's own monitor for position math, and computes per-window scale ascell_width / window_monitor_widthso cross-monitor windows fit their cell properly. Aftermovetoworkspacesilenton drop, also dispatchesmoveworkspacetomonitor <ws> <drop_monitor>so windows dropped on monitor A's overview land on monitor A regardless of which monitor the workspace was previously bound to.2. Refresh
windowListfromhyprctlon every axctl eventCompositorData.updateWindowList()was a no-op, relying on the inline state piggybacked onto every axctl subscribe event. That state is stale for window geometry: when a dispatch likemovetoworkspacesilentcauses Hyprland to re-tile, the resulting per-window resizes do not get reflected in the axctl daemon's cached state until something else (e.g.Event.WorkspaceChanged) triggers a refresh.Visible symptom: drag a chip from cell A to cell B — the dispatch happens correctly, the real windows re-tile on the desktop, but every chip keeps showing the pre-move size, leaving white space around each window thumbnail. Closing and reopening the overview fixes it; so does switching monitors (the focus change emits a workspace event which finally flushes the daemon's state).
hyprctl clients -jreturns the correct sizes the whole time,axctl window listdoes not.Wire
updateWindowList()to actually fetchhyprctl clients -j, mapped to the same record shapeAxctlService.applyStateproduces so other consumers are unaffected. Axctl events are still used as the "something changed" trigger (they fire reliably even when their inline state is stale), but the authoritative geometry comes from hyprctl now. Debounced at 60ms so a burst of events doesn't fork many subprocesses.3. Restore
Qt.bindingafter drag, gate animations untilreadyTwo cleanup follow-ups exposed by #2:
After a drag/click on a chip,
onReleaseddidroot.x = root.initX(and the same fory) to snap the chip back into place. In QML, a plain assignment destroys the property binding — soxandystopped trackinginitX/initYforever after, and subsequentwindowDataupdates couldn't move the chip. TheMouseArea'sdrag.targetalready breaks the binding during drag, so a clean release path has to restore it. Re-bind viaQt.binding(() => root.initX)in all threeonReleasedbranches and the same inScrollingWorkspace's drop handler.Every chip rebuild (which happens on each axctl event because
filteredWindowDatareturns a fresh array of fresh objects, so theRepeaterdestroys + recreates delegates) starts with Item-defaultx/y/width/height = 0, then the bindings kick in andBehavioron each animates0 -> targetoveranimDuration. With axctl-driven state churn, chips never finish settling — they perpetually look half-sized with white borders around them. Gate the Behaviors behind areadyflag flipped true inComponent.onCompletedso the initial assignment skips the animation; subsequent in-place updates still animate.Test plan
Happy to iterate if anything here should land differently. Thanks!
— robin