Implementing FancyZones Window Layout Zones: PowerToys Development Guide
FancyZones splits monitors into configurable zones using a three-layer architecture where WorkArea objects manage zone snapping, layouts persist as JSON files, and hot-key events route through FancyZones.cpp to snap windows programmatically.
FancyZones is the window management module in Microsoft PowerToys that enables users to organize desktop space through customizable snap zones. When implementing FancyZones window layout zones, developers interact with a C++ core engine, a WPF editor, and JSON-based persistence layers. This guide examines the source code architecture in microsoft/PowerToys to explain how zones are created, applied, and extended.
Architecture Overview
The FancyZones implementation follows a strict three-layer separation to isolate window management logic from UI and persistence concerns.
Layer 1: Module Entry Point – src/modules/fancyzones/FancyZones/FancyZones.cpp implements the IFancyZones interface and hosts the main window-message loop. This layer receives global hook events, hot-key input, and orchestrates updates across all monitors.
Layer 2: Work-Area Model – src/modules/fancyzones/FancyZonesLib/WorkArea.h and WorkArea.cpp represent a unique monitor + virtual-desktop pair. Each WorkArea owns a ZoneSet and exposes methods to create, snap, cycle, and update windows for that specific display context.
Layer 3: Layout Data – FancyZonesDataTypes.h, FancyZonesData.h, and CustomLayouts.h define the serializable structures (Zone, ZoneSet, CustomLayoutData) that describe grid, canvas, and custom layouts. The module communicates with the FancyZones editor (a separate WPF app) via JSON files read/written by FancyZonesEditorIO.cs.
Work-Area Lifecycle
Understanding the lifecycle of a WorkArea is essential for implementing FancyZones window layout zones correctly.
First, the system detects monitors and virtual desktops using MonitorUtils::IdentifyMonitors() and VirtualDesktop::instance().GetCurrentVirtualDesktopIdFromRegistry(). For each unique pair, WorkArea::Create instantiates a new work-area via AddWorkArea in FancyZones.cpp.
Next, the engine assigns a ZoneSet loaded from AppliedLayouts.json (or defaults) using AppliedLayouts::instance().ApplyLayout. When users trigger a hot-key or a new window appears, WorkArea::Snap calculates the target position and resizes the window accordingly.
Finally, whenever a display change, virtual-desktop switch, or layout reload occurs, FancyZones::OnDisplayChange evaluates ShouldWorkAreasBeRecreated to determine whether existing objects can be reused or must be rebuilt from scratch.
Layout Types and Data Models
FancyZones supports four distinct layout types defined in FancyZonesDataTypes.h.
Grid layouts use GridLayoutInfo to define uniform rows and columns with optional per-cell sizing percentages. Canvas layouts store arbitrary rectangles via CanvasLayoutInfo, allowing free-form zone placement. PriorityGrid extends the grid concept with directional ordering for cursor-based zone selection. Custom layouts wrap any of the above under a UUID stored in CustomLayoutData.
All layout data serialize to JSON using the helper methods in FancyZonesEditorCommon/Data/FancyZonesJsonContext.cs. The editor writes to AppliedLayouts.json, CustomLayouts.json, and LayoutHotkeys.json, while the main module watches these files via FileWatcher (common/SettingsAPI/FileWatcher.h) to reload configurations dynamically.
Hot-Key Processing Pipeline
The low-level keyboard hook resides in KeyboardInput.cpp. When a snap hot-key is pressed, FancyZones::ShouldProcessSnapHotkey validates the foreground window and current layout before delegating to WindowKeyboardSnap or WindowMouseSnap.
WindowKeyboardSnap calculates the target ZoneIndexSet based on arrow-key direction and invokes WorkArea::Snap to execute the move. This pipeline ensures that window positioning logic remains decoupled from raw input handling, allowing developers to inject custom snapping behaviors at the WorkArea layer.
Inter-Process Communication
The FancyZones module launches the editor using ShellExecuteEx (see ToggleEditor in FancyZones.cpp). Before launch, EditorParameters::Save writes monitor rectangles and the "span-across-monitors" flag to editor-parameters.json.
Both processes remain synchronized through shared JSON files monitored by FileWatcher. When the editor closes, the main module receives a WM_PRIV_EDITOR message, reloads the configuration, and rebuilds its WorkArea objects to reflect any layout changes made by the user.
Extending FancyZones: Step-by-Step Implementation
When adding a new zone type or custom behavior to FancyZones, follow this precise development flow:
- Define the data model – Extend
FancyZonesDataTypes.hwith a new struct (e.g.,RadialLayoutInfo). - Add JSON serialization – Update
FancyZonesJsonContext.csto read and write the new fields. - Create a layout class – Derive from
Layout(found inFancyZonesLib/WorkArea.h) and implementCalculateZonesandSnap. - Update the layout registry – Modify
CustomLayouts::GetLayoutto recognize the newCustomLayoutData::type. - Expose UI controls – Add XAML elements in
FancyZonesEditor.xamland bind them viaMainWindowSettingsModel.
Practical Implementation Examples
Programmatically Add a Custom Grid Layout
The following C# example demonstrates creating a 2x3 grid layout and persisting it for the primary monitor:
using Microsoft.PowerToys.FancyZonesEditor.Common;
using FancyZonesEditor.Models;
// Build a GridLayoutInfo (rows = 2, columns = 3)
var grid = new GridLayoutModel("MyCustomGrid", LayoutType.Custom)
{
Rows = 2,
Columns = 3,
RowPercents = new List<int> { 50, 50 },
ColumnPercents = new List<int> { 33, 34, 33 },
Zones = new List<Rectangle>()
};
// Convert to JSON-serializable structure
var layoutData = new CustomLayoutData
{
name = grid.Name,
type = CustomLayoutType.Grid,
info = grid.ToGridLayoutInfo()
};
// Persist to CustomLayouts.json
FancyZonesEditorIO.SerializeCustomLayout(layoutData);
Key files: FancyZonesEditor.Common/FancyZonesDataIO.cs, FancyZonesEditor.Models/GridLayoutModel.cs
Apply a Layout from Native C++ Code
To apply a layout programmatically after a display change:
#include <FancyZonesLib/FancyZonesData.h>
#include <FancyZonesLib/FancyZonesLib.h>
void ApplyMyLayout(const FancyZonesDataTypes::WorkAreaId& waId)
{
// Retrieve layout by UUID
auto layoutOpt = CustomLayouts::instance().GetLayout(myLayoutId);
if (!layoutOpt) return;
// Apply to work-area
if (AppliedLayouts::instance().ApplyLayout(waId, layoutOpt.value()))
{
// Refresh visual zones
FancyZones::RefreshLayouts();
}
}
Key files: FancyZonesLib/CustomLayouts.h, FancyZonesLib/AppliedLayouts.h, FancyZones.cpp
Hook a New Hot-Key for Multi-Monitor Spanning
Register and handle a custom hot-key in the main module:
// Registration in FancyZones::Run()
constexpr int HOTKEY_SPAN = 4;
auto hk = FancyZonesSettings::settings().spanZonesAcrossMonitorsHotkey;
RegisterHotKey(m_window, HOTKEY_SPAN, hk.get_modifiers(), hk.get_code());
// Handling in WndProc
case WM_HOTKEY:
if (wparam == HOTKEY_SPAN)
{
FancyZonesSettings::instance().UpdateSetting(
SettingId::SpanZonesAcrossMonitors,
!FancyZonesSettings::settings().spanZonesAcrossMonitors);
}
break;
Key files: FancyZones.cpp, FancyZonesSettings.h
Key Source Files
| Path | Role |
|---|---|
src/modules/fancyzones/FancyZones/FancyZones.cpp |
Core module implementation, hook handling, and work-area orchestration. |
src/modules/fancyzones/FancyZonesLib/WorkArea.h / WorkArea.cpp |
Per-monitor/desktop zone management, snap logic, and cycling. |
src/modules/fancyzones/FancyZonesLib/FancyZonesDataTypes.h |
Data models for zones, layouts, and virtual-desktop IDs. |
src/modules/fancyzones/FancyZonesLib/CustomLayouts.h |
Registry for user-defined layouts (load, query, apply). |
src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs |
WPF editor UI and JSON persistence logic. |
src/modules/fancyzones/editor/FancyZonesEditor/Common/FancyZonesDataIO.cs |
Serialization helpers for layout data. |
src/settings-ui/Settings.UI/ViewModels/FancyZonesViewModel.cs |
Settings UI glue for hot-keys and toggles. |
Summary
- FancyZones uses a three-tier architecture separating module logic (
FancyZones.cpp), per-display state (WorkArea), and data models (FancyZonesDataTypes.h). - WorkArea objects manage the lifecycle of zones for each monitor/virtual-desktop pair, reloading when
OnDisplayChangedetects configuration changes. - JSON files (
AppliedLayouts.json,CustomLayouts.json) serve as the communication bridge between the C++ engine and the WPF editor, watched byFileWatcher. - Hot-key processing flows from
KeyboardInput.cppthrough validation routines toWorkArea::Snapfor execution. - Extensions require updating the data model, JSON context, layout class, and editor UI to maintain consistency across the codebase.
Frequently Asked Questions
How does FancyZones detect monitor layout changes?
FancyZones monitors system events through MonitorUtils::IdentifyMonitors() and VirtualDesktop::instance().GetCurrentVirtualDesktopIdFromRegistry(). When a change occurs, FancyZones::OnDisplayChange evaluates ShouldWorkAreasBeRecreated to decide whether to reuse existing WorkArea objects or reconstruct them with updated zone configurations.
What is the difference between Grid and Canvas layouts?
Grid layouts (GridLayoutInfo) divide screens into uniform rows and columns with percentage-based sizing, ideal for structured workflows. Canvas layouts (CanvasLayoutInfo) store arbitrary pixel rectangles, allowing free-form zone placement anywhere on the display. Both types serialize to JSON and instantiate ZoneSet objects through CustomLayouts::GetLayout.
Can I trigger zone snapping programmatically without user input?
Yes. Native code can invoke WorkArea::Snap directly after retrieving a layout via CustomLayouts::instance().GetLayout and applying it through AppliedLayouts::instance().ApplyLayout. For C# scenarios, use FancyZonesEditorIO.SerializeCustomLayout to persist layouts that the engine will load automatically.
Where are custom layouts stored on disk?
Custom layouts persist in %LocalAppData%\Microsoft\PowerToys\FancyZones\CustomLayouts.json, while active monitor assignments reside in AppliedLayouts.json. Hot-key mappings use LayoutHotkeys.json. Both the C++ module and the WPF editor watch these files via FileWatcher to synchronize state changes immediately.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →