PlasmaZones v3.0.6
Fixed
- Settings window (and any 5th+ window) left tiled on autotile→snap (#504):
calculateResnapFromAutotileOrdercapped both passes atmin(windowCount, zoneCount), silently dropping windows past the new layout's zone count even when their pre-autotile zone was unique and still available. With 5 autotiled windows resnapping into a 4-zone layout, the 5th was always left tiled — in practice usually the settings window (last in focus order). The first pass (restore-original-zone) now iterates ALL windows; the positional fallback iterates all windows but breaks once every zone is claimed.claimedZoneIndicesstill prevents double-booking. - 250+ ms stall before windows start moving on autotile→snap (#504):
slotScreensChangedran a synchronoussetNoBorder(false)loop on autotile disable. Each call is a Wayland decoration round-trip (30–120 ms per window) and the loop blocked kwin's main thread long enough that the queuedapplyGeometriesBatch("resnap")D-Bus signal couldn't dispatch. The affected window IDs are now stashed and drained fromslotApplyGeometriesBatch'sonCompleteonce the resnap has been dispatched — windows start animating to their snap positions within ~2 ms of the daemon emit instead of 250+ ms. The drain processes one window per event-loop tick so frames render between calls, keeping the concurrent OSD show animation smooth. A chunk-time check againstm_autotileScreensskips the restore if the user rapid-cycles back into autotile mid-drain (the new toggle'ssetWindowBorderless(true)is now authoritative). - Focus follows mouse not re-focusing after focus steal (#461, #503):
AutotileHandler::handleCursorMovedshort-circuited againstm_lastFocusFollowsMouseWindowId, a local cache of the window it had most recently auto-focused. The cache was only invalidated bysetFocusFollowsMouse(false),onWindowClosedfor the cached ID, andslotScreensChanged— not by Alt-Tab, a click, a daemon-driven activate, or a new window stealing focus. After any of those, the cache pointed at a window the compositor was no longer focusing on, and the next cursor pass-over matched the stale cache and skippedactivateWindow— focus stayed wherever it had drifted to. The cache is replaced with a live read againstKWin::effects->activeWindow(), so the no-op gate fires only when the cursor window actually still holds focus. - Tiling assignments saved on the settings page silently dropped (#497, #499):
LayoutAdaptor::m_algorithmRegistrywas never wired up after the DI refactor — the daemon's composition root set it onOverlayService,ZoneSelectorController, etc., but missedLayoutAdaptor. Every per-monitor / per-activity tiling-mode assignment hitsetAssignmentEntry's validation branch (!m_algorithmRegistry), loggedunknown tiling algorithm: "<id>", and was dropped before being stored. The cascade fell back to the global default, which read like "my changes reverted." Snapping assignments persisted; only tiling assignments vanished. The registry is now injected. - Daemon toggle re-enabling itself after disable (#497, #499):
setEnabled(false)ransystemctl --user disable, which only blocks boot-time autostart. The installed/usr/share/dbus-1/services/org.plasmazones.servicefile pinsSystemdService=plasmazones.service, so any subsequent D-Bus call from the KWin effect (window-lifecycle callbacks likesetWindowMetadata,windowClosed) routed through systemd and started the unit, and the toggle flipped back on within seconds. Switched tosystemctl --user mask, which blocks both autostart and D-Bus-routed activation. Re-enable chainsunmask→enable. - Snapping Assignments Monitor row reverted to old value after Save (#497, #501):
SettingsController::getLayoutForScreen(snapping side) called the daemon's contextualgetLayoutForScreenD-Bus method, which walks the current-desktop / current-activity cascade. The tiling counterpart already queried the explicit screen-level slot. The mismatch let a stored per-desktop or per-activity entry shadow the screen-level slot the user just wrote — the Monitor row's own re-read pulled the higher-priority entry's old value and the combo snapped back. The snapping path now mirrorsgetTilingLayoutForScreen's slot-level shape. - OSD announced the wrong layout name after editing a non-current-context slot (#497, #501): the OSD callback on
applyAssignmentChangesresolved the layout vialayoutForScreen(screen, currentDesktop, currentActivity), the same cascade as the Monitor-row bug. Editing a non-current-context slot (e.g. a screen-level row while aDesktop:1:Activity:Xentry held the visible context) produced an OSD that announced the cascade winner — the unchanged OLD layout — as if it were the change. TheassignmentChangesAppliedsignal now carries the full(screenId, virtualDesktop, activity)of every modified slot, and the OSD resolves the layout at that exact slot. - Windows on disabled monitors / desktops still tracked and resurrected after restart (#461, #498): a window closing on a monitor or desktop the user had disabled snap/autotile on still got a
PendingRestorerecorded, then resurfaced when the same app reopened or KWin rehomed it after the disabled screen slept. The persistedWindowZoneAssignmentsFull,PendingRestoreQueues, andAutotilePendingRestoresare now filtered on both load and save against the current disable lists, andWindowTrackingServicetakes an injectedShouldTrackPredicateso live closes on disabled contexts never write the entry in the first place. The autotile engine matches via an injectedShouldPersistRestorePredicateat three filter sites (live, save, load), so future engine changes can't drift from the snap side (#502). - Re-enabling a disabled virtual desktop sometimes silently failing (#461, #498):
setDesktopDisabled/setActivityDisableddid an exact-string remove using the screen-name form QML supplied, but the read-side check resolved both connector-name and screen-ID forms. After a screen redetect, the re-enable removed zero entries and the desktop stayed disabled. Both setters now try every variant on remove. - Popup menus and Steam image previews snapped to weird zones (#461, #498):
isTileableWindowrejected PopupMenu / DropdownMenu / Menu / Tooltip / keep-above windows;shouldHandleWindowdid not. The asymmetry let a window KWin classifies as e.g. PopupMenu (Steam's chat image-preview popup, on a steamwebhelper class with notransientFor) pass the snap-side filter while autotile rejected it — surfacing as "snapped to a zone in snap mode" depending on the screen's current assignment. Both filters now reject the same window types. - Stale snap assignments from deleted layouts lingering across first-launch (#500):
SnapEngine::onLayoutChanged()'sif (!prevLayout) return;early-out skipped the stale-assignment cleanup on first launch, so session-restored windows whose zone IDs referenced a deleted layout stayed inm_snapStateforever. The cleanup also kept multi-zone windows when any zone survived, leaving dangling zone IDs in the assignment list that downstreammultiZoneGeometry/zonesForWindowwould iterate. The cleanup now runs unconditionally on layout change and rebuilds multi-zone assignments to the surviving subset. - Critical-notification windows triggering focus tracking and appearing in the app picker (#500):
notifyWindowActivatedand the app-picker enumeration rejectedisNotificationbut notisCriticalNotification. KWin treats them as distinct window types, so a critical-notification could fire the focus shader or show up in the KCM exclusion-list picker. Both sites now reject both types. m_shuttingDownflag not reset on daemon restart (#500):stop()set the flag and nothing else cleared it, so anystop()→start()cycle (programmatic restart, tests) left every shutdown-guarded code path silenced on the second run.start()now resets the flag.- Per-screen autotile algorithm change overwrote the global default (#500): changing the autotile algorithm on a single monitor (e.g. spiral on screen B while screen A stays on default) wrote the new algorithm into
Tiling.Behavior.DefaultAlgorithm, so the next per-screen apply on screen A picked up screen B's choice as if it were the user's global default.
Changed
- Nix release-pipeline transform aligned with the new flake shape (#496):
packaging/nix/generate-release-nix.shfailed on v3.0.5's tag push because PR #489's flake rewrite introduced afetchFromGitHub,argument and a multi-lineversion = let ... in ...block that the awk transform never knew about. The script now drops or injectsfetchFromGitHub,to match either pre- or post-rewrite shape, and rewrites the multi-line version block to the hardcodedversion = "<VERSION>";form. The legacy single-line rewrite stays as a fallback.
Installation
Arch Linux (AUR):
yay -S plasmazones # or plasmazones-binArch Linux (manual):
sudo pacman -U plasmazones-3.0.6-*-x86_64.pkg.tar.zstKDE Neon / Debian-based:
sudo dpkg -i plasmazones_3.0.6-*_amd64.deb
sudo apt-get install -f # Install dependencies if neededFedora (COPR):
sudo dnf copr enable fuddlesworth/PlasmaZones
sudo dnf install plasmazonesFedora (manual RPM):
# Fedora 43
sudo dnf install plasmazones-3.0.6-*.fc43.x86_64.rpm
# Fedora 44
sudo dnf install plasmazones-3.0.6-*.fc44.x86_64.rpmopenSUSE Tumbleweed (manual RPM):
sudo zypper install plasmazones-3.0.6-*.x86_64.rpmopenSUSE Tumbleweed (OBS):
sudo zypper addrepo https://download.opensuse.org/repositories/home:fuddlesworth/openSUSE_Tumbleweed/home:fuddlesworth.repo
sudo zypper refresh
sudo zypper install plasmazonesUniversal Linux (AppDir):
For Fedora Atomic, Steam Deck, or non-root user installation:
tar xzf plasmazones-3.0.6-linux-x86_64.tar.gz
cd plasmazones-linux-x86_64
./install.shNixOS (flake):
# flake.nix inputs
plasmazones.url = "github:fuddlesworth/PlasmaZones";
# configuration.nix
programs.plasmazones.enable = true;NixOS (standalone):
Download plasmazones.nix from the release assets, then:
# configuration.nix
environment.systemPackages = [
(pkgs.callPackage ./plasmazones.nix {})
];Post-Installation
systemctl --user enable --now plasmazones.service
systemsettings kcm_plasmazones