github fuddlesworth/PlasmaZones v3.0.6
PlasmaZones v3.0.6

4 hours ago

PlasmaZones v3.0.6

Fixed

  • Settings window (and any 5th+ window) left tiled on autotile→snap (#504): calculateResnapFromAutotileOrder capped both passes at min(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. claimedZoneIndices still prevents double-booking.
  • 250+ ms stall before windows start moving on autotile→snap (#504): slotScreensChanged ran a synchronous setNoBorder(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 queued applyGeometriesBatch("resnap") D-Bus signal couldn't dispatch. The affected window IDs are now stashed and drained from slotApplyGeometriesBatch's onComplete once 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 against m_autotileScreens skips the restore if the user rapid-cycles back into autotile mid-drain (the new toggle's setWindowBorderless(true) is now authoritative).
  • Focus follows mouse not re-focusing after focus steal (#461, #503): AutotileHandler::handleCursorMoved short-circuited against m_lastFocusFollowsMouseWindowId, a local cache of the window it had most recently auto-focused. The cache was only invalidated by setFocusFollowsMouse(false), onWindowClosed for the cached ID, and slotScreensChanged — 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 skipped activateWindow — focus stayed wherever it had drifted to. The cache is replaced with a live read against KWin::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_algorithmRegistry was never wired up after the DI refactor — the daemon's composition root set it on OverlayService, ZoneSelectorController, etc., but missed LayoutAdaptor. Every per-monitor / per-activity tiling-mode assignment hit setAssignmentEntry's validation branch (!m_algorithmRegistry), logged unknown 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) ran systemctl --user disable, which only blocks boot-time autostart. The installed /usr/share/dbus-1/services/org.plasmazones.service file pins SystemdService=plasmazones.service, so any subsequent D-Bus call from the KWin effect (window-lifecycle callbacks like setWindowMetadata, windowClosed) routed through systemd and started the unit, and the toggle flipped back on within seconds. Switched to systemctl --user mask, which blocks both autostart and D-Bus-routed activation. Re-enable chains unmaskenable.
  • Snapping Assignments Monitor row reverted to old value after Save (#497, #501): SettingsController::getLayoutForScreen (snapping side) called the daemon's contextual getLayoutForScreen D-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 mirrors getTilingLayoutForScreen's slot-level shape.
  • OSD announced the wrong layout name after editing a non-current-context slot (#497, #501): the OSD callback on applyAssignmentChanges resolved the layout via layoutForScreen(screen, currentDesktop, currentActivity), the same cascade as the Monitor-row bug. Editing a non-current-context slot (e.g. a screen-level row while a Desktop:1:Activity:X entry held the visible context) produced an OSD that announced the cascade winner — the unchanged OLD layout — as if it were the change. The assignmentChangesApplied signal 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 PendingRestore recorded, then resurfaced when the same app reopened or KWin rehomed it after the disabled screen slept. The persisted WindowZoneAssignmentsFull, PendingRestoreQueues, and AutotilePendingRestores are now filtered on both load and save against the current disable lists, and WindowTrackingService takes an injected ShouldTrackPredicate so live closes on disabled contexts never write the entry in the first place. The autotile engine matches via an injected ShouldPersistRestorePredicate at 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/setActivityDisabled did 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): isTileableWindow rejected PopupMenu / DropdownMenu / Menu / Tooltip / keep-above windows; shouldHandleWindow did not. The asymmetry let a window KWin classifies as e.g. PopupMenu (Steam's chat image-preview popup, on a steamwebhelper class with no transientFor) 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()'s if (!prevLayout) return; early-out skipped the stale-assignment cleanup on first launch, so session-restored windows whose zone IDs referenced a deleted layout stayed in m_snapState forever. The cleanup also kept multi-zone windows when any zone survived, leaving dangling zone IDs in the assignment list that downstream multiZoneGeometry/zonesForWindow would 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): notifyWindowActivated and the app-picker enumeration rejected isNotification but not isCriticalNotification. 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_shuttingDown flag not reset on daemon restart (#500): stop() set the flag and nothing else cleared it, so any stop()→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.sh failed on v3.0.5's tag push because PR #489's flake rewrite introduced a fetchFromGitHub, argument and a multi-line version = let ... in ... block that the awk transform never knew about. The script now drops or injects fetchFromGitHub, to match either pre- or post-rewrite shape, and rewrites the multi-line version block to the hardcoded version = "<VERSION>"; form. The legacy single-line rewrite stays as a fallback.

Installation

Arch Linux (AUR):

yay -S plasmazones  # or plasmazones-bin

Arch Linux (manual):

sudo pacman -U plasmazones-3.0.6-*-x86_64.pkg.tar.zst

KDE Neon / Debian-based:

sudo dpkg -i plasmazones_3.0.6-*_amd64.deb
sudo apt-get install -f  # Install dependencies if needed

Fedora (COPR):

sudo dnf copr enable fuddlesworth/PlasmaZones
sudo dnf install plasmazones

Fedora (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.rpm

openSUSE Tumbleweed (manual RPM):

sudo zypper install plasmazones-3.0.6-*.x86_64.rpm

openSUSE Tumbleweed (OBS):

sudo zypper addrepo https://download.opensuse.org/repositories/home:fuddlesworth/openSUSE_Tumbleweed/home:fuddlesworth.repo
sudo zypper refresh
sudo zypper install plasmazones

Universal 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.sh

NixOS (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

Don't miss a new PlasmaZones release

NewReleases is sending notifications on new releases.