microsoft / microsoft/PowerToys
[FancyZones] Switching virtual desktops applies the previous/primary desktop's layout (race: current-desktop ID read from registry before Windows updates it)
- Dominant language
- C
- Stars
- 139k
- Forks
- 8.6k
- PR merge metrics
- PR metrics pending
Description
### Microsoft PowerToys version
0.100.0
### Installation method
WinGet
### Area(s) with issue?
FancyZones
### Steps to reproduce
**Setup:** multi-monitor + multiple named Windows virtual desktops, each with its own
FancyZones layout per monitor.
1. On desktop A (e.g. the first/leftmost desktop in the Task View order), have layout set X.
2. Switch to desktop B, which has a different layout set Y.
3. Intermittently, desktop B is shown with **desktop A's** layouts instead of its own.
4. Switch away and back, and it often **corrects itself** — the result is timing-dependent
(nondeterministic), which is the tell-tale sign of a race.
In my case, switching from "Main" (the first desktop) to "Communication" repeatedly left
Communication showing **Main's** layout on a monitor; re-switching fixed it; switching again
sometimes re-broke it — with no other change than the desktop switch itself.
### Root cause (traced in the 0.100.0 source)
This is a **race between the virtual-desktop switch and how FancyZones reads which desktop is
now current** — distinct from the stored-layout matching bug in #49016 (the two compound).
- A desktop switch fires a WinHook callback `VirtualDesktopChanged()`, which posts
`WM_PRIV_VD_SWITCH`; the handler runs `OnDisplayChange(DisplayChangeType::VirtualDesktop)`,
re-reading the current desktop via
`VirtualDesktop::GetCurrentVirtualDesktopIdFromRegistry()` to decide which layouts to apply.
- That function reads the current-desktop GUID from the **registry**
(`HKCU\…\VirtualDesktops`, value `CurrentVirtualDesktop`). **Windows updates that value
asynchronously** after a switch, so at the instant FancyZones reads it, it frequently still
holds the desktop you just **left** → FancyZones applies the *previous* desktop's layouts.
- When the read yields nothing, the function **explicitly falls back to the first element of
the desktop array** — the code comment says *"taking first element from virtual desktop
array, which is primary desktop."* So failures are **biased toward the first/primary
desktop's** layout, which is exactly what "another desktop's layout bleeds in" looks like.
```cpp
// VirtualDesktop.cpp — GetCurrentVirtualDesktopIdFromRegistry()
std::optional desktopId = NewGetCurrentDesktopId(); // newer reg key (async)
if (desktopId) return *desktopId;
desktopId = GetDesktopIdFromCurrentSession(); // per-session reg key (async)
if (desktopId) return *desktopId;
auto ids = GetVirtualDesktopIdsFromRegistry(); // fallback:
if (ids && ids->size() > 0) return ids->at(0); // the FIRST/"primary" desktop
return GUID_NULL;
```
- Because the desktop-change lifecycle also reconciles and can **persist** the layout file
(`AppliedLayouts::SyncVirtualDesktops()` calls `SaveData()` when it thinks the map changed,
and the AppliedLayouts `FileWatcher` then reloads + rebroadcasts), a transient wrong read can
get **written to `applied-layouts.json`**, turning a flicker into durable corruption.
### ✔️ Expected Behavior
After switching to a desktop, FancyZones applies the layout saved for **that** desktop. The
current-desktop identity used to pick layouts should be the settled value for the desktop
actually switched to, not a value read before the OS has updated it.
### ❌ Actual Behavior
Right after a switch, FancyZones reads a stale/not-yet-updated current-desktop GUID from the
registry (or falls back to the first/primary desktop), so it applies the **previous** (or
primary) desktop's layouts to the desktop you just switched to. It is timing-dependent and
often self-corrects on the next switch — and can be persisted to `applied-layouts.json`.
### Additional Information
- OS: Windows 11 Pro, build 26200 · 3 monitors · 4 named virtual desktops · per-user install.
- **Distinct from but compounding #49016** (which is about *which stored entry wins* among
duplicate per-monitor entries; the constant-`0` `WorkAreaId` hash makes that a first-match
linear scan). This issue is about *which virtual desktop FancyZones thinks is current* at
switch time.
- Related (all closed): #16370, #13733, #28371, #36897, tracker #12985.
#### Proposed fix (direction)
1. Don't trust the registry value immediately on a switch. Confirm the current desktop via the
`IVirtualDesktopManager` COM API (e.g. `GetWindowDesktopId` for a known on-screen window),
or poll/debounce the registry value until it stabilizes, **before** applying layouts.
2. Drop the "fall back to `ids->at(0)` (primary)" behavior when the current desktop is unknown
— it biases corruption toward the primary desktop. Defer applying until the ID is known.
3. Don't `SaveData()` from `SyncVirtualDesktops()` when the current desktop is `GUID_NULL` /
uncertain, so a transient bad read can't be persisted.
Relevant files: `src/modules/fancyzones/FancyZonesLib/VirtualDesktop.cpp`,
`src/modules/fancyzones/FancyZonesLib/FancyZones.cpp` (`OnDisplayChange` / `WM_PRIV_VD_SWITCH`),
`src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppliedLayouts.cpp` (`SyncVirtualDesktops`).
### Other Software
Connected through a docking station.
Contributor guide
Research direction
Start in src/modules/fancyzones/FancyZonesLib/VirtualDesktop.cpp and trace GetCurrentVirtualDesktopIdFromRegistry() through FancyZones.cpp's OnDisplayChange and WM_PRIV_VD_SWITCH handling. Then inspect AppliedLayouts.cpp, especially SyncVirtualDesktops and its SaveData path, while reproducing a multi-desktop switch. Done means the switched desktop consistently receives its own layout and an uncertain desktop ID cannot overwrite applied-layouts.json.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- desktop, operating-systems
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 45/100