mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-15 11:17:53 +01:00
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>
This commit is contained in:
committed by
Bartosz Sosnowski
parent
10c5396099
commit
8431b80e48
215
doc/specs/PowerToys-fancyzones.md
Normal file
215
doc/specs/PowerToys-fancyzones.md
Normal file
@@ -0,0 +1,215 @@
|
||||
# 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;
|
||||
};
|
||||
```
|
||||
|
||||
57
doc/specs/PowerToys-settings.md
Normal file
57
doc/specs/PowerToys-settings.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Power Toys Settings Framework and Core Infrastructure
|
||||
The Power Toys app will have a settings framework that each Power Toy can plug into. The settings framework has a UI frame that creates a page for each Power Toy. The UI frame should use the Navigation View “hamburger” UI. Each Power Toy will represent its settings as a json blob as described below.
|
||||
|
||||
Each Power Toy will line in a separate .dll and be run in a separate thread by the main Power Toys process. The main Power Toys .exe will expose key global Windows event handlers so that there is only one system-level hook for these critical events. The current set of Power Toys require these global events. This list will be amended as new Power Toys are authored that require additional global hooks.
|
||||
* SetWinEventHook - FancyZones requires knowledge of when a window enters the move/size loop. It listens for EVENT_SYSTEM_MOVESIZESTART, EVENT_SYSTEM_MOVESIZEEND, and EVENT_OBJECT_LOCATIONCHANGE messages from SetWinEventHook.
|
||||
* Low-level keyboard hook - The Windows key Shortcut Guide and FancyZones both require low-level keybord hooks to intercept keyboard input and get a first chance to process it. Other Power Toys will require this as well
|
||||
|
||||
* Each Power Toy must listen for 4 events:
|
||||
* Enable – When invoked, enables the Power Toys’ functionality and performs any necessary initialization. Invoked with a JSON string from the persisted settings store
|
||||
* Disable – When invoked, disables the Power Toys’ functionality and performs any clean-up to suspend all resource use
|
||||
* OutputSettings – Return a json serialized blob of the settings for the Power Toy
|
||||
* InputSettings – Invoked with a JSON string with updated settings from the UI which is then deserialized and the state is applied. If the settings cannot be applied by the Power Toy, the PT must return an error and an error string for the end user
|
||||
* Each Power Toy may optionally provide one or more custom configuration UIs that can be invoked from its settings page
|
||||
* Each custom UI is specified as a JSON string in the settings property bag
|
||||
* The Power Toy must provide a named method that returns a serialized JSON settings string for the settings framework to call
|
||||
* The method should launch UI to edit the settings but the UI shown must be asynchronous and not block the setting UI
|
||||
* The Power Toys main .exe will provide a method called InvokeSettingsUI that will show the settings dialog for the calling Power Toy.
|
||||
* Settings will be serialized by the settings framework and will be read at launch of the Power Toys framework and each Power Toy’s settings will be passed into the PT’s Enable method
|
||||
* Settings will be serialized on a per-user basis
|
||||
* The Settings JSON format will be versioned nad each payload must specify it's version attribute. The initial version is 1.0
|
||||
|
||||
## Power Toys Settings Object
|
||||
The settings JSON object for each Power Toy should provide:
|
||||
* Title string
|
||||
* Icon
|
||||
* Logo Image
|
||||
* Credits string
|
||||
* Credits link
|
||||
* Settings property bag. Each item in the property bag has two items:
|
||||
* String: display name
|
||||
* String: property / editor type
|
||||
* Version number: Currently only 1.0 is supported
|
||||
|
||||
Property Bag of settings in priority order (type->editor)
|
||||
* Bool->slide switch
|
||||
* Int->free text box
|
||||
* String->free text box
|
||||
* Int ->Up/Down spinner
|
||||
* Color-> Color picker
|
||||
* Image->File picker, preview area, drag and drop
|
||||
* Cursor->file picker and drop down, possibly an image
|
||||
* Property Bag JSON string->Button to launch a custom editor from the Power Toy
|
||||
* Method name to invoke. The method will return a serialized JSON string with the updated custom editor settings
|
||||
* String to display on the button
|
||||
* Percentage->Slider
|
||||
* Time->Time picker
|
||||
* Date->Date picker
|
||||
* IP address->masked text box
|
||||
|
||||
## PowerToys Main Settings Page
|
||||
* Need to get Nick to help with the settings UI design (see attached for a whiteboard sketch)
|
||||
* Need to have a settings page for overall PowerToys which will include the following
|
||||
* Check for updates
|
||||
* Startup at launch
|
||||
* Enable / disable for each utility.
|
||||
* This invokes the Enable and Disable events for the PowerToy and suspends all resource use including CPU, GPU, Networking, Disk I/O and memory commit
|
||||
* The settings UI should have an “Apply” button which will push the settings object to
|
||||
85
doc/specs/Shared-hooks.md
Normal file
85
doc/specs/Shared-hooks.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# Shared hooks
|
||||
|
||||
To minimize the performance impact on the machine only `runner` installs global hooks, passing the events to registered callbacks in each PowerToy module.
|
||||
|
||||
When a PowerToy module is loaded, the `runner` calls the [`get_events()`](/src/modules/interface/powertoy_module_interface.h#L40) method to get a NULL-terminated array of NULL-terminated strings with the names of the events that the PowerToy wants to subscribe to. A `const wchar_t*` string is provided for each of the event names.
|
||||
|
||||
Events are signalled by the `runner` calling the [`signal_event(name, data)`](/src/modules/interface/powertoy_module_interface.h#L53) method of the PowerToy module. The `name` parameter contains the NULL-terminated name of the event. The `data` parameter and the method return value are specific for each event.
|
||||
|
||||
Currently supported hooks:
|
||||
* `"ll_keyboard"` - [Low Level Keyboard Hook](#low-level-keyboard-hook)
|
||||
* `"win_hook_event"` - [Windows Event Hook](#windows-event-hook)
|
||||
|
||||
## Low Level Keyboard Hook
|
||||
|
||||
This event is signaled whenever the user presses or releases a key on the keyboard. To subscribe to this event, add `"ll_keyboard"` to the table returned by the `get_events()` method.
|
||||
|
||||
The PowerToys runner installs low-level keyboard hook using `SetWindowsHookEx(WH_KEYBOARD_LL, ...)`. See [this MSDN page](https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms644985(v%3Dvs.85)) for details.
|
||||
|
||||
When a keyboard event is signaled and `ncCode` equals `HC_ACTION`, the `wParam` and `lParam` event parameters are passed to all subscribed clients in the [`LowlevelKeyboardEvent`](/src/modules/interface/lowlevel_keyboard_event_data.h#L38-L41) struct.
|
||||
|
||||
The `intptr_t data` event argument is a pointer to the `LowlevelKeyboardEvent` struct.
|
||||
|
||||
A non-zero return value from any of the subscribed PowerToys will cause the runner hook proc to return 1, thus swallowing the keyboard event.
|
||||
|
||||
Example usage, that makes Windows ignore the L key:
|
||||
|
||||
```c++
|
||||
virtual const wchar_t** get_events() override {
|
||||
static const wchar_t* events[2] = { ll_keyboard,
|
||||
nullptr };
|
||||
return events;
|
||||
}
|
||||
|
||||
virtual intptr_t signal_event(const wchar_t* name, intptr_t data) override {
|
||||
if (wcscmp(name, ll_keyboard) == 0) {
|
||||
auto& event = *(reinterpret_cast<LowlevelKeyboardEvent*>(data));
|
||||
// The L key has vkCode of 0x4C, see:
|
||||
// https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
|
||||
if (event.wParam == WM_KEYDOWN && event.lParam->vkCode == 0x4C) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Windows Event Hook
|
||||
|
||||
This event is signaled for [a range of events](https://docs.microsoft.com/pl-pl/windows/win32/winauto/event-constants). To subscribe to this event, add `"win_hook_event"` to the table returned by the `get_events()` method. See [this MSDN doc](https://docs.microsoft.com/pl-pl/windows/win32/api/winuser/nf-winuser-setwineventhook) for details.
|
||||
|
||||
The `intptr_t data` event argument is a pointer to the [`WinHookEvent`](/src/modules/interface/win_hook_event_data.h#L43-L50) struct.
|
||||
|
||||
The return value of the event handler is ignored.
|
||||
|
||||
Example usage, that detects a window being resized:
|
||||
|
||||
```c++
|
||||
virtual const wchar_t** get_events() override {
|
||||
static const wchar_t* events[2] = { win_hook_event,
|
||||
nullptr };
|
||||
return events;
|
||||
}
|
||||
|
||||
virtual intptr_t signal_event(const wchar_t* name, intptr_t data) override {
|
||||
if (wcscmp(name, win_hook_event) == 0) {
|
||||
auto& event = *(reinterpret_cast<WinHookEvent*>(data));
|
||||
switch (event.event) {
|
||||
case EVENT_SYSTEM_MOVESIZESTART:
|
||||
size_start(event.hwnd);
|
||||
break;
|
||||
case EVENT_SYSTEM_MOVESIZEEND:
|
||||
size_end(event.hwnd);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
Taking too long to process the events has negative impact on the whole system performance. To address this, the events are signaled from a different thread, not from the event hook callback itself.
|
||||
Reference in New Issue
Block a user