Skip to content

Add shortcut to focus the window occupying a zone - #219

Open
dssouza-ti wants to merge 1 commit into
gerritdevriese:mainfrom
dssouza-ti:feature/hotkeys-active-window-per-zone
Open

dssouza-ti wants to merge 1 commit into
gerritdevriese:mainfrom
dssouza-ti:feature/hotkeys-active-window-per-zone

Conversation

@dssouza-ti

@dssouza-ti dssouza-ti commented Aug 30, 2026

Copy link
Copy Markdown

Closes #111

Summary

Adds a shortcut to focus the window occupying a given zone: Meta + Ctrl + Num 1-9 activates the window in the matching zone of the active layout, and cycles through them when a zone holds more than one window.

KZones can already move windows into zones, but there is no way to jump focus to a zone. The existing "Switch to next/previous window in current zone" only cycles inside the zone the active window already occupies, so it cannot be used to reach a different zone. On a wide monitor with a multi column layout, this fills that gap: the zone number becomes a direct focus target.

Motivation

I had been using a small standalone KWin script for this, but it hardcoded its zone list as fixed screen percentages, so it silently disagreed with KZones whenever I switched or edited a layout. This PR merges that behaviour into KZones proper and resolves the zones from config.layouts[currentLayout] instead, so the shortcuts always follow the active layout.

What changed

File Change
src/contents/ui/components/Shortcuts.qml New focusZone(int zone) signal and a Repeater of nine ShortcutHandlers bound to Meta+Ctrl+Num+1..9
src/contents/ui/main.qml New focusWindowInZone(), getWindowsOverlappingZone() and getZoneGeometry(), plus the onFocusZone handler
src/contents/code/utils.mjs New pure helpers overlapArea(), windowKey() and pickWindowInZone()
README.md Row added to the shortcut table

No new configuration settings, so config/main.xml, ui/config.ui and loadConfig() are untouched.

Design notes

Zone geometry comes from the rendered items. getZoneGeometry(layout, zone) reads the zone rect back via mapToGlobal() on the Zones.qml item, which is what moveClientToZone() already did. That block was duplicated in moveClientToZone() and in onFullScreenChanged(), so I extracted it and both now call the helper. This keeps a single source of truth for zone geometry, so focusing and snapping can never disagree about where a zone is.

Matching is by geometric overlap, not by the stored client.zone. Windows that KZones never snapped, or that were snapped while a different layout was active, still count as occupying a zone. This complements the existing getWindowsInZone() / switchWindowInZone() pair, which remain untouched and keep using the stored zone.

Every window covering nearly as much of the zone as the best match counts as being in it (area >= bestArea * 0.9). Without this, only the single best overlapping window is reachable and a stack of windows sharing a zone is not.

The cycle order comes from a stable per window key, not the stacking order. Activating a window raises it, so a stacking ordered ring would put the window you just left permanently back in "next" position, ping-ponging between two windows and never reaching the rest. The key is internalId (a stable per window UUID). Stacking order is still used, but only to decide where the cycle starts, so entering a zone from outside lands on the window last used there.

Window filtering reuses the existing checkFilter(), so the user's Include/Exclude filter list applies to focusing as well as to snapping, plus checks for minimized, output, desktops and activities (an empty desktop or activity list means "on all", matching KWin's semantics). If you would rather the filter list govern snapping only, that is a one line change.

Enumeration uses Workspace.stackingOrder. The declarative scripting API does not expose windowList(); that exists only on the imperative API. I hit this during testing and it is noted in a comment so it does not get reintroduced.

Choice of key binding

Ctrl+Alt+Num and Meta+Num are already taken by "Move active window to zone" and "Activate layout", so this uses Meta+Ctrl+Num. Shift deliberately is not part of the sequence: XKB's KEYPAD key type maps Shift+NumLock back to the base level (KP_End, KP_Down, and so on), which makes any Shift+Num+N binding physically unreachable while NumLock is on. There is a comment in Shortcuts.qml recording this.

As with the other numeric shortcuts, these may not bind by default if the sequence is already claimed on a given system.

Testing

Environment: Fedora Linux 44 (KDE Plasma Desktop Edition), Plasma 6.7.4, KWin 6.7.4, Qt 6.11.1, Wayland.

  • Ran in a nested Plasma session via make test, and confirmed all nine actions register with kglobalaccel.
  • Verified against a 9 zone layout (3 columns, full and half height) that a zone holding two windows cycles between them, and that focusing a zone from outside lands on the topmost window there.
  • Confirmed no QML or JS exceptions in the journal while exercising the shortcuts.
  • Installed and in daily use on my main machine.

I also checked the pure selection logic (pickWindowInZone) with a standalone Node harness covering the overlap maths, the area threshold, the entry point, and specifically the raise-reshuffle case described above. I have not included it in this PR, since the repository has no test setup and I did not want to impose one, but I am happy to add it if you would like.

Formatting note: CONTRIBUTING.md asks for qmlformat, and my distribution ships qmlformat-qt6 (Qt 6.11), whose output style differs from the committed formatting. Running it reformatted both files wholesale (stripping () from parameterless signals, collapsing blank lines, rewriting return ;), so I reverted that and matched the surrounding style by hand to keep the diff reviewable. Say the word if you would prefer the files run through your version of the tool.

Compatibility and risk

  • Plasma 6 only, consistent with the existing package.
  • No configuration schema change, so no migration and no effect on existing settings.
  • Existing behaviour is unchanged apart from the extracted getZoneGeometry() helper, which is a straight refactor of two duplicated blocks.
  • KPlugin.Version in metadata.json is deliberately not bumped, since version bumps appear to be separate release commits in this repository.

Reviewer notes, full disclosure

  1. I am not a developer.
  2. This was fully vibecoded using Claude Opus 5, while following your contributing guidelines. I did not find any rule forbidding AI usage.
  3. The functionality was tested live and I am currently using this feature on my own Fedora KDE 44 system.
  4. If vibecoding is not welcome in this repository, feel free to reject this PR without reviewing it. I will respect your rules.

Meta+Ctrl+Num+1..9 activates the window in the matching zone of the
active layout, cycling through them when a zone holds more than one.

Zones are resolved from the current layout instead of fixed geometry, so
the shortcuts follow layout switches and layout edits. Candidates are
matched by how much they overlap the zone rectangle rather than by the
zone stored on the client, so windows that were never snapped by KZones
still count as occupying a zone.

Every window covering nearly as much of the zone as the best match
counts as being in it, which keeps a stack of windows sharing a zone
reachable. The cycle order comes from a stable per-window key rather
than the stacking order: activating a window raises it, which would
otherwise leave the window just left permanently next in line and
ping-pong between two windows. Stacking order still decides where the
cycle starts, so entering a zone lands on the window last used there.

Also extract getZoneGeometry() from the two places that read a zone rect
back from the rendered items, and keep the selection logic pure in
pickWindowInZone().
@dssouza-ti
dssouza-ti force-pushed the feature/hotkeys-active-window-per-zone branch from bdfd261 to f5326aa Compare August 30, 2026 00:47
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.

[Feature request] Shortcuts to switch to window in previous/next zone

1 participant