Focus windows through Hyprland so scrolling layouts follow, plus workspace/urgent/launch feedback - #1
Open
assada wants to merge 1 commit into
Conversation
Clicking a dock icon went through the Wayland activate request, which only hands over keyboard focus. On Hyprland's scrolling layout that switches to the window's workspace and leaves the window itself parked off-screen, so the click reads as a no-op: with three columns on one workspace, the target sat at x=-1459 on a 1536px viewport and stayed there. Focus now goes through the compositor's own dispatcher, which moves the viewport with it (x=-1459 -> 12). The same path fixes wheel cycling, the right-click window list, and windows on special workspaces, and it degrades to the Wayland request when no Hyprland address is available. Around that: - Workspace hints. A tooltip names the workspace when the app's windows are all elsewhere (Slack [3]), and window rows in the context menu carry the same label ([3] Slack - DM). Read live off the Hyprland handles, so a window moving between workspaces needs no model rebuild. - Urgent highlights. A window asking for attention pulses its indicator and icon ring in the theme's urgent colour until it is focused. Toggle: showUrgentHint. - Launch feedback. A cold app pulses its icon until its window shows up, so one click does not become three. - Click active to minimize (opt-in, clickToMinimize). Clicking the app you are already in parks its single window on a hidden special:minimized workspace and clicking again returns it to where it came from. Multi-window apps keep cycling. The workspace name is the state, so a shell restart cannot strand a window: without a remembered origin it comes back to the current workspace. - Middle click opens a new window. - Reveal and tooltip dwell delays, so a pointer travelling to the bottom of a window no longer summons the dock and sweeping across icons no longer flashes every label. Tunable via revealDelay and tooltipDelay; set either to 0 for the old behaviour. Two rendering fixes: - Menu rows are drawn at one width. Each row used to size itself from its own label, so hover and checked fills stepped in and out down the menu (measured 232 / 225 / 273px in one menu), and a row changed width when it got checked. Rows now report an implicit width, the menu takes the widest, and every row draws at that. The mark column is always reserved, so labels share one left edge. - A custom dock colour no longer hides the dock's own chrome. bgColor is measured against the theme's bar foreground, and the launcher glyph, indicators, separator, and card outline flip to the readable side when the two land on the same side of the scale. A light theme with a dark card was drawing dark on dark at roughly 1.3:1. Internals: DockModel.cycleAppWindow and activateApp are replaced by a pure pickAppWindow that only chooses a window, leaving the acting to the host. That also fixes backwards wheel cycling on an unfocused app, which used to land on the second-to-last window instead of the last. The two duplicated tooltip blocks became one HoverTooltip component. Docs: new config keys, middle click, and the screen default, which the table described as the focused monitor while the code picks the first.
NothingManTR
added a commit
to NothingManTR/omadock
that referenced
this pull request
Aug 19, 2026
… auto-contrast) into PR thepathless#2
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.
Why
Clicking an icon in the dock went through the Wayland
activaterequest (Toplevel.activate()), which only hands over keyboard focus. On Hyprland's scrolling layout that is not enough: the workspace switches, the window takes focus, and the window itself stays parked off-screen. The click reads as "nothing happened".Measured on a workspace with
tiledLayout: "scrolling"and three columns on a 1536px logical viewport:Routing focus through the compositor's own dispatcher moves the viewport with the focus:
The same path fixes wheel cycling, the right-click window list, and windows sitting on special workspaces. When no Hyprland address is available it falls back to the Wayland request, so nothing regresses on a compositor without
hyprland-toplevel-mapping-v1.Two compatibility details worth knowing, both verified against Hyprland 0.56.2:
hyprctl dispatch focuswindow address:0x...now answerserror: ')' expected near 'address'. Quickshell exposesHyprland.usingLua, so both syntaxes are sent from one helper rather than sniffing versions.HyprlandToplevel.addressreturns the address without the0xprefix (557f72140cb0) while Hyprland's selectors expectaddress:0x…, so the address is normalised in one place.What else is in here
Everything below is built on the same live Hyprland handles that the focus fix needs, which is why it travels with it. Urgency and workspace are read straight off those handles, so a window moving between workspaces or raising its urgent hint needs no model rebuild.
Workspace hints. When an app's windows all live somewhere else, its tooltip names the workspace (
Slack [3]), and every row in the right-click window list carries the same label ([3] Slack — DM). Parked and starting windows read the same way:Files [minimized],Slack [starting…]. An earlier draft put a numbered badge in the corner of each icon; on a normal desktop that fires on almost every icon at once and reads like an unread count, so it was dropped in favour of the tooltip.Urgent highlights (
showUrgentHint, default on). A window asking for attention pulses its indicator dot and icon ring in the theme's urgent colour until you focus it. Hyprland clears the hint itself on focus.Launch feedback. Clicking a cold app pulses its icon until the window shows up, or until waiting stops being informative (12s). Pending launches are tracked by window-count growth, so "new window" on an already-running app pulses too. Practical effect: one click on a slow Electron app stops becoming three.
Click active to minimize (
clickToMinimize, default off). Clicking the app you are already in is otherwise a dead click. With the option on, a single-window app is parked on a hiddenspecial:minimizedworkspace and clicking the icon again brings it back to the workspace it came from. Multi-window apps keep cycling, which stays the more useful answer.Hyprland has no real minimize, so this is a special workspace by necessity — but the workspace name is the state, so a shell restart cannot strand a window: with no remembered origin it simply returns to the workspace you are on. It uses its own
special:minimizedrather thanspecial:scratchpad, so it never disturbs windows a user has deliberately parked in their scratchpad. Left off by default because on a tiling WM it reflows the layout, which should be a deliberate choice.Middle click opens a new window — the usual dock convention, and the button was free.
Dwell delays. The reveal strip fired the instant the pointer crossed the bottom 6px, so travelling to the bottom of a window summoned the dock on the way past; there is now a short dwell (
revealDelay, 160ms). Tooltips appeared with no delay, so sweeping across the dock flashed a label for every icon (tooltipDelay, 450ms). Set either to0for the previous behaviour.Two rendering fixes
Menu rows are drawn at one width. Every
ContextRowsized itself from its own label, and theColumntook the maximum, so hover and checked fills stepped in and out down the menu. Measured from a screenshot of the settings menu: 232 / 225 / 225 / 273px. The+26pxallowance also applied only when a row waschecked, so a row changed width when you toggled it. Rows now report an implicit width, the menu takes the widest piece of content, and every row draws at that width — identical fills by construction. Only implicit widths are read, so feeding the result back into the rows cannot loop. The mark column is always reserved as well, so labels share one left edge and toggling a row shifts nothing.A custom dock colour no longer hides the dock's own chrome.
Color.bar.textis tuned for the bar's background, not forbgColor. With a light theme and a dark card the launcher glyph drew dark-on-dark: glyph strokes at 0.06–0.20 luminance on a 0.28 background, about 1.3:1. The dock colour is now measured against the theme foreground and the glyph, indicators, separator, and card outline flip to the readable side only when the two land on the same side of the scale:bgColorthemetheme"theme"and"none"keep usingColor.bar.textexactly as before, so default installs render identically.Internals
DockModel.cycleAppWindow/activateAppare replaced by a purepickAppWindowthat only chooses a window and leaves the acting to the host — which is what let onefocusToplevelhandle the scrolling layout, minimized windows, and the Wayland fallback in a single place. It also fixes backwards wheel cycling on an unfocused app, which used to land on the second-to-last window instead of the last.HoverTooltipinline component (~50 lines to ~30, and the item tooltip now hides behind an open context menu, which only the button tooltip did before).buildEntriestakes a resolver and carries the Hyprland handle per window. The model is rebuilt on bothToplevelManager.toplevelsandHyprland.toplevelschanges, debounced at 40ms, because Hyprland resolves its handle slightly apart from the Wayland announcement.Compatibility
0.Hyprland.dispatchis used in-process — nohyprctlsubprocess per click.1.5.0inmanifest.json; happy to drop that commit if you version releases yourself.Docs
README gains the new config keys, the middle-click row, the automatic-contrast note, and one factual fix: the
screenrow described the default as the focused monitor while the code picksQuickshell.screens[0].Testing
Hyprland 0.56.2, Quickshell 0.3.0, Omarchy shell, single 1920x1080 monitor at 1.25 scale, light theme with a custom dark
bgColor.Hyprland.usingLuaconfirmedtruein steady state with all toplevels mapped to their Wayland handles.hl.dsp.window.movewithfollow = false/follow = trueverified on a throwaway window, including that the special workspace disappears cleanly once the last window leaves it.rowWidthresolves to 0 and rows fall back to their implicit width, i.e. today's behaviour.