PowerToys/doc/specs/PowerToys-fancyzones.md
Bartosz Sosnowski 8431b80e48 FancyZones and Shortcut Guide initial commit
Co-authored-by: Alexis Campailla <alexis@janeasystems.com>
Co-authored-by: Bret Anderson <bretan@microsoft.com>
Co-authored-by: Enrico Giordani <enrico.giordani@gmail.com>
Co-authored-by: Jaime Bernardo <jaime@janeasystems.com>
Co-authored-by: Jeff Bogdan <jeffbog@microsoft.com>
Co-authored-by: March Rogers <marchr@microsoft.com>
Co-authored-by: Mike Harsh <mharsh@microsoft.com>
Co-authored-by: Nachum Bundak <Nachum.Bundak@microsoft.com>
Co-authored-by: Oliver Jones <ojones@microsoft.com>
Co-authored-by: Patrick Little <plittle@microsoft.com>
2019-09-05 18:12:40 +02:00

9.7 KiB

FancyZones

FancyZones

FancyZones is the base class that runs the show. It uses hooks to monitor for windows entering and exiting the move/size loop and to listen for key presses for hotkey interception. For every connected display, it creates a ZoneWindow which is used to display the active ZoneSet per monitor for use when editing the layout or displaying the drop targets. A ZoneSet is composed of one or more Zones which are the locations where windows can be easily positioned.

SetWinEventHook

The main driving force behind FancyZones is the accessibility hook used to know when a window enters the move/size loop. It listens for EVENT_SYSTEM_MOVESIZESTART, EVENT_SYSTEM_MOVESIZEEND, and EVENT_OBJECT_LOCATIONCHANGE. For each of these three events, it forwards on to the ZoneWindow associated with the monitor that the window being dragged is currently on.

Keyboard Hook

A low-level keyboard hook is installed in order to, optionally, intercept Window+Arrow hotkeys. Traditionally, Win+Left/Right arrow will move a window between Windows Snap regions. This hook allows FancyZones to use Win+Left/Right arrow to move windows between Zones. The hook also allows using 0-9 to change the active ZoneSet during a drag operation.

Display Changes

During initial standup, FancyZones creates a ZoneWindow for each connected monitor. When it receives a WM_DISPLAYCHANGE, it updates the available ZoneWindows to reflect the state of the system (eg add a new ZoneWindow for newly connected monitor, delete ZoneWindow for disconnected monitor, etc)

Interface

interface IFancyZones : public IUnknown
{
    // Returns the main application window
    IFACEMETHOD_(HWND, GetWindow)() = 0;

    // Returns the global HINSTANCE for the process
    IFACEMETHOD_(HINSTANCE, GetHInstance)() = 0;

    // Returns the global Settings object used to look up individual settings throughout the product
    IFACEMETHOD_(Settings, GetSettings)() = 0;

    // Used in WinMain to initialize FancyZones and enter the message loop
    IFACEMETHOD_(void, Run)() = 0;

    // Toggles the visibility of all ZoneWindows
    IFACEMETHOD_(void, ToggleZoneViewers)() = 0;

    // Shows a single ZoneWindow in editor mode on the provided monitor
    IFACEMETHOD_(void, ShowZoneEditorForMonitor)(_In_ HMONITOR monitor) = 0;

    // Returns true if we are currently detecting a movesize loop
    IFACEMETHOD_(bool, InMoveSize)() = 0;

    // Called by the event hook in response to EVENT_SYSTEM_MOVESIZESTART
    IFACEMETHOD(MoveSizeEnter)(_In_ HWND window, _In_ HMONITOR monitor, POINT ptScreen) = 0;

    // Called by the event hook in response to EVENT_SYSTEM_MOVESIZEEND
    IFACEMETHOD(MoveSizeExit)(_In_ HWND window, POINT ptScreen) = 0;

    // Called by the event hook in response to EVENT_OBJECT_LOCATIONCHANGE
    IFACEMETHOD(MoveSizeUpdate)(_In_ HMONITOR monitor, POINT ptScreen) = 0;

    // Called during startup or on WM_DISPLAYCHANGE to add a ZoneWindow to the collection
    // There will be one ZoneWindow per connected monitor
    IFACEMETHOD(AddZoneWindow)(_In_ IZoneWindow* zoneWindow, _In_ HMONITOR monitor) = 0;

    // Called in response to WM_DISPLAYCHANGE from the main application window
    IFACEMETHOD_(void, OnDisplayChange)(DisplayChangeType changeType) = 0;

    // Used to move the specified HWND into Zone
    // The ZoneSet used is found by looking up the monitor that window is currently on
    // This gets called to keep windows in their current zones after a WM_DISPLAYCHANGE
    IFACEMETHOD_(void, MoveWindowIntoZoneByIndex)(_In_ HWND window, int index) = 0;

    // Used to filter out windows that the hook should not be processing
    // Currently checks if the window's GWL_STYLE has WS_MAXIMIZEBOX
    IFACEMETHOD_(bool, IsInterestingWindow)(_In_ HWND window) = 0;

    // Called byt the event hook in response to EVENT_OBJECT_NAMECHANGE on the Desktop window
    // The accessible name of the desktop window changes when the current virtual desktop changes
    IFACEMETHOD_(void, VirtualDesktopChanged)() = 0;

    // Returns the GUID of the current active virtual desktop
    IFACEMETHOD_(GUID, GetCurrentVirtualDesktopId)() = 0;

    // Called by the LL keyboard hook
    // Used to override snap hotkeys and to change the active ZoneSet during a drag
    IFACEMETHOD_(bool, OnKeyDown)(LPARAM lparam) = 0;

    // Keep windows positioned inside their zones when the active ZoneSet changes
    IFACEMETHOD_(void, MoveWindowsOnActiveZoneSetChange)() = 0;
};

ZoneWindow

ZoneWindow is used to display the Zones a user can drop a window in during a drag operation, flash the Zones when the ZoneSet changes, and draw the Zone Editor UI when in edit mode. Basically, when a ZoneSet needs to be visualized, ZoneWindow does it.

Interface

interface IZoneWindow : public IUnknown
{
    // Shows the ZoneWindow
    // If activate is true, set foreground to the window otherwise just show
    IFACEMETHOD(ShowZoneWindow)(bool activate) = 0;

    // Hide the ZoneWindow
    IFACEMETHOD(HideZoneWindow)() = 0;
    
    // Called when the drag enters the monitor this ZoneWindow is assigned to
    IFACEMETHOD(MoveSizeEnter)(_In_ HWND window, POINT ptScreen, DragMode dragMode) = 0;

    // Called when the drag exits the monitor
    IFACEMETHOD(MoveSizeExit)(_In_ HWND window, POINT ptScreen) = 0;

    // Called when the drag updates position on this monitor
    IFACEMETHOD(MoveSizeUpdate)(POINT ptScreen, DragMode dragMode) = 0;

    // Called when a drag ends and the window is not dropped in a Zone
    IFACEMETHOD(MoveSizeCancel)() = 0;

    // Returns the DragMode of the current drag operation
    // DragMode allows for overriding drag behavior via settings or via hotkey
    IFACEMETHOD_(DragMode, GetDragMode)() = 0;

    // Part of the chain to move a window into a specific Zone
    IFACEMETHOD_(void, MoveWindowIntoZoneByIndex)(_In_ HWND window, int index) = 0;
    
    // Used to cycle a window between zones via the hijacked snap hotkeys
    IFACEMETHOD_(void, MoveWindowIntoZoneByDirection)(_In_ HWND window, DWORD vkCode) = 0;

    // Called in response to WM_DISPLAYCHANGE
    // Allows cleanup, if necessary, since ZoneWindow will be destroyed shortly thereafter
    IFACEMETHOD_(void, OnDisplayChange)(DisplayChangeType type) = 0;

    // Allows changing the active ZoneSet via key press either during a drag or while the ZoneWindow is in foreground
    IFACEMETHOD_(void, CycleActiveZoneSet)(DWORD vkCode) = 0;
};

ZoneSet

Collection of one or more Zones. Only one ZoneSet is active at a time per monitor.

Interface

interface IZoneSet : public IUnknown
{
    // Gets the unique ID used to identify this ZoneSet
    IFACEMETHOD_(GUID, GetId)() = 0;

    // Adds a Zone to the collection
    IFACEMETHOD(AddZone)(_In_ Microsoft::WRL::ComPtr<IZone> zone, bool front) = 0;

    // Removes a Zone from the collection
    IFACEMETHOD(RemoveZone)(_In_ Microsoft::WRL::ComPtr<IZone> zone) = 0;

    // Returns the topmost Zone at the given point
    IFACEMETHOD_(Microsoft::WRL::ComPtr<IZone>, ZoneFromPoint)(POINT pt) = 0;

    // Returns a Zone that the window is in
    // Will return nullptr if the window is not in a Zone
    IFACEMETHOD_(Microsoft::WRL::ComPtr<IZone>, ZoneFromWindow)(_In_ HWND window) = 0;

    // Gets all the Zones
    IFACEMETHOD_(std::vector<Microsoft::WRL::ComPtr<IZone>>, GetZones)() = 0;
    
    // ZoneSetLayout
    // * Grid - Pregenerated layout (2x2, 3x3, etc)
    // * Row - Pregenerated layout in a single row
    // * Focus - Pregenerated layout with a central focus Zone and fanned peripheral Zones
    // * Custom - User generated Zone
    IFACEMETHOD_(ZoneSetLayout, GetLayout)() = 0;

    // The amount of default padding between Zones in a generated layout
    IFACEMETHOD_(int, GetInnerPadding)() = 0;

    // Makes a copy of the IZoneSet and marks it as ZoneSetLayout::Custom
    IFACEMETHOD_(Microsoft::WRL::ComPtr<IZoneSet>, MakeCustomClone)() = 0;

    // Persists ZoneSet data to the registry
    IFACEMETHOD_(void, Save)() = 0;

    // Moves a Zone to the front of the collection
    IFACEMETHOD_(void, MoveZoneToFront)(_In_ Microsoft::WRL::ComPtr<IZone> zone) = 0;

    // Moves a Zone to the back of the collection
    IFACEMETHOD_(void, MoveZoneToBack)(_In_ Microsoft::WRL::ComPtr<IZone> zone) = 0;
    
    // Part of the chain to move a window into a specific Zone
    IFACEMETHOD_(void, MoveWindowIntoZoneByIndex)(_In_ HWND window, _In_ HWND zoneWindow, int index) = 0;

    // Part of the chain to move a window into a specific Zone
    IFACEMETHOD_(void, MoveWindowIntoZoneByDirection)(_In_ HWND window, _In_ HWND zoneWindow, DWORD vkCode) = 0;

    // Called when a drag ends or leaves the monitor this ZoneWindow is on
    // This will remove the window from its currently assigned Zone and assign it
    // to a different Zone based on the current cursor position
    IFACEMETHOD_(void, MoveSizeExit)(_In_ HWND window, _In_ HWND zoneWindow, _In_ POINT ptClient) = 0;
};

Zone

Basically a RECT and a map of HWND->RECT to keep track of where windows can be placed and which windows are currently in the Zone.

Interface

interface IZone : public IUnknown
{
    // Returns the RECT that this Zone represents
    IFACEMETHOD_(RECT, GetZoneRect)() = 0;

    // Returns true if the specified window is in this Zone's collection
    IFACEMETHOD_(bool, ContainsWindow)(_In_ HWND window) = 0;

    // Adds the window the collection
    IFACEMETHOD_(void, AddWindowToZone)(_In_ HWND window, _In_ HWND zoneWindow, bool stampZone) = 0;

    // Removes the window from the collection
    IFACEMETHOD_(void, RemoveWindowFromZone)(_In_ HWND window, bool restoreSize) = 0;

    // Sets an id for this Zone
    // The id will be unique per ZoneSet
    IFACEMETHOD_(void, SetId)(size_t id) = 0;

    // Returns the id given to this Zone
    IFACEMETHOD_(size_t, GetId)() = 0;
};