From 51ee964ecfd243de71905c3f9f3c796a4f107666 Mon Sep 17 00:00:00 2001 From: Evan Koschik Date: Thu, 11 Dec 2025 15:33:39 -0500 Subject: [PATCH 01/11] initial copy... --- Samples/WindowPlacement/README.md | 858 +++++++++ .../cpp/FullScreenSample/FullScreenSample.cpp | 325 ++++ .../FullScreenSample/FullScreenSample.vcxproj | 130 ++ .../cpp/FullScreenSample/full-screen.ico | Bin 0 -> 67646 bytes .../cpp/FullScreenSample/resource.h | 2 + .../cpp/FullScreenSample/resources.rc | 4 + .../SaveRestoreSample/SaveRestoreSample.cpp | 420 +++++ .../SaveRestoreSample.vcxproj | 130 ++ .../cpp/SaveRestoreSample/resource.h | 2 + .../cpp/SaveRestoreSample/resources.rc | 4 + .../cpp/SaveRestoreSample/save.ico | Bin 0 -> 164551 bytes .../cpp/WindowPlacementSamples.sln | 40 + .../cpp/inc/CurrentMonitorTopology.h | 726 ++++++++ Samples/WindowPlacement/cpp/inc/MiscUser32.h | 209 +++ Samples/WindowPlacement/cpp/inc/MonitorData.h | 224 +++ Samples/WindowPlacement/cpp/inc/PlacementEx.h | 1596 +++++++++++++++++ .../WindowPlacement/cpp/inc/RegistryHelpers.h | 85 + Samples/WindowPlacement/cpp/inc/User32Utils.h | 44 + .../cpp/inc/VirtualDesktopIds.h | 118 ++ .../WindowPlacement/cpp/inc/WindowActions.h | 386 ++++ 20 files changed, 5303 insertions(+) create mode 100644 Samples/WindowPlacement/README.md create mode 100644 Samples/WindowPlacement/cpp/FullScreenSample/FullScreenSample.cpp create mode 100644 Samples/WindowPlacement/cpp/FullScreenSample/FullScreenSample.vcxproj create mode 100644 Samples/WindowPlacement/cpp/FullScreenSample/full-screen.ico create mode 100644 Samples/WindowPlacement/cpp/FullScreenSample/resource.h create mode 100644 Samples/WindowPlacement/cpp/FullScreenSample/resources.rc create mode 100644 Samples/WindowPlacement/cpp/SaveRestoreSample/SaveRestoreSample.cpp create mode 100644 Samples/WindowPlacement/cpp/SaveRestoreSample/SaveRestoreSample.vcxproj create mode 100644 Samples/WindowPlacement/cpp/SaveRestoreSample/resource.h create mode 100644 Samples/WindowPlacement/cpp/SaveRestoreSample/resources.rc create mode 100644 Samples/WindowPlacement/cpp/SaveRestoreSample/save.ico create mode 100644 Samples/WindowPlacement/cpp/WindowPlacementSamples.sln create mode 100644 Samples/WindowPlacement/cpp/inc/CurrentMonitorTopology.h create mode 100644 Samples/WindowPlacement/cpp/inc/MiscUser32.h create mode 100644 Samples/WindowPlacement/cpp/inc/MonitorData.h create mode 100644 Samples/WindowPlacement/cpp/inc/PlacementEx.h create mode 100644 Samples/WindowPlacement/cpp/inc/RegistryHelpers.h create mode 100644 Samples/WindowPlacement/cpp/inc/User32Utils.h create mode 100644 Samples/WindowPlacement/cpp/inc/VirtualDesktopIds.h create mode 100644 Samples/WindowPlacement/cpp/inc/WindowActions.h diff --git a/Samples/WindowPlacement/README.md b/Samples/WindowPlacement/README.md new file mode 100644 index 00000000..0d15b8fc --- /dev/null +++ b/Samples/WindowPlacement/README.md @@ -0,0 +1,858 @@ + +# Remembering Window Positions with PlacementEx + +This README is paired with the header files in this directory: + +- [PlacementEx.h](PlacementEx.h) +- [MonitorData.h](MonitorData.h) +- [MiscUser32.h](MiscUser32.h) +- [CurrentMonitorTopology.h](CurrentMonitorTopology.h) +- [VirtualDesktopIds.h](VirtualDesktopIds.h) + +These header files simplify storing & restoring window positions for apps. For +example, storing the position when a window closes and using it to pick an +initial position when the app launches. In this way, they are an extension of +[`SetWindowPlacement`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowplacement) +and +[`GetWindowPlacement`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowplacement). + +The intended audience of this file is developers who are using or modifying +these headers in apps or frameworks. + +These header files use only public APIs that have been stable since before +Windows 10 (and in most cases long before). For example: + + - [CreateWindow](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexa) + - [SetWindowPos](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowpos) + - [GetMonitorInfo](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmonitorinfoa) + - [GetWindowPlacement](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowplacement) + - [SetWindowPlacement](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowplacement) + - [GetDpiForWindow](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdpiforwindow) + - [IsWindowArranged](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-iswindowarranged) + - [GetDpiForMonitor](https://learn.microsoft.com/en-us/windows/win32/api/shellscalingapi/nf-shellscalingapi-getdpiformonitor) + - [DwmGetWindowAttribute](https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmgetwindowattribute) + - [IVirtualDesktopManager](https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-ivirtualdesktopmanager) + +## Overview + +Most users expect apps to launch where they last closed. For example, apps +closed on secondary monitors should relaunch on that monitor (and not on the +primary). Or if maximized, apps should relaunch maximized (not at their restore +position). + +Most apps today attempt to do this. The +[`SetWindowPlacement`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowplacement) +and +[`GetWindowPlacement`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowplacement) +APIs assist with this. But even when using these APIs correctly, sometimes apps +launch in unexpected places, especially for users with monitors that have +different DPI scales, or whose machines reboot or dock/undock frequently. + +This happened because restore was initially (~Win3.1) fairly simple and grew in +complexity over many decades. **Multiple monitors**, **DPI**, **Arrangement**, +and **Virtual Desktops** in particular make positioning top-level windows more +complicated, which in turn makes storing window positions (and using them later) +more complicated. This README, and [PlacementEx.h](PlacementEx.h), introduce +fresh guidance for storing window positions. + +## Background + +### Screen Coordinates + +Windows (HWNDs) parented to the Desktop window are called [**top-level windows**](https://learn.microsoft.com/en-us/windows/win32/winmsg/about-windows#parent-or-owner-window-handle). +These windows can move freely between all monitors. (As opposed to **child windows**, +which move relative to their parent). + +The coordinate space of the Desktop window, the one top-level windows move within, +is called **Screen Coordinates**. + +There is always a monitor connected to the system whose top-left corner is 0,0 +in screen coordinates. This is the primary monitor. + +Monitors to the left of the primary have negative X values, and ones above the +primary have negative Y values. + +### Monitors (`HMONITOR`s) + +APIs like [`MonitorFromPoint`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfrompoint) and +[`MonitorFromRect`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfromrect) +return a handle to a **monitor**, an `HMONITOR`. This handle can be used with APIs +like [`GetMonitorInfo`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmonitorinfoa) +and [`GetDpiForMonitor`](https://learn.microsoft.com/en-us/windows/win32/api/shellscalingapi/nf-shellscalingapi-getdpiformonitor) +to read critical information about a monitor: + +- **Monitor Rect**. This is the position and size (resolution) of the monitor. +- **Work Area**. This is a subset of the monitor rect that isn't covered by the + taskbar (or other docked toolbars, like Voice Access). +- **DPI**. This is the scale for content on the monitor. See **DPI**, below. +- **Display Name**. This is a string uniquely identifying the monitor. This is + available in `MONITORINFOEX` as `szDevice`. + +See [MonitorData.h](MonitorData.h), which defines helpers to query the monitor +data. + +### DPI + +Since Vista, Windows supports custom **DPIs** (Dots Per Inch) and scale factors, +so that apps scale properly on high-resolution displays. In 8.1, this was +extended to allow each monitor to have its own DPI, and this has improved a few +times since. Windows scales 'DPI-unaware' apps by stretching them, while +'DPI-aware' apps/windows must scale themselves. This is typically done via a +**scale factor**, which is `(DPI) / 96`; a logical DPI of 144 would have a scale +factor of 144 / 96 = 150%. + +**Logical** units refer to units that have been scaled by this scale factor, +whereas **physical** units refer to units that have not. For example, 12 logical +pixels on a 150% scale monitor correspond to 18 physical pixels. Screen +coordinates are in physical pixels. + +Today, there are 3 types of [DPI awareness](https://learn.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows). A window can be any one of these: + + - **DPI unaware**. The window renders itself at the Windows default of 96 DPI (scale factor 100%). Windows stretches this window bitmap to the actual DPI of the monitor. + + - **"System DPI" aware**. Previously called 'Aware'. The window receives the + primary monitor's DPI at the time the process was launched, and must scale + its UI appropriately. Windows stretches this window bitmap if the window + moves to a monitor with a different DPI or if the primary monitor's DPI + changes. + + - **"Per-Monitor DPI" aware**. The window is expected to handle + [`WM_DPICHANGED`](https://learn.microsoft.com/en-us/windows/win32/hidpi/wm-dpichanged), + which tells the window when its DPI scale should change (e.g. when moved + between monitors). This provides the window a `RECT` that must be used to + resize the window to the new DPI. + +Consider a console window with some number of lines of text, or a browser window +with some number of paragraphs or pictures. If the window is Per-Monitor DPI +aware, and changes from 200% to 100% scaling without changing its window size +(but scales its *content* correctly), the content visible in the window would +double/quadruple. For example, a window that is 640 logical pixels wide would be +1280 physical pixels at 200% scaling, and should shrink down to 640 physical +pixels when moved to an 100% scale monitor. + +It is possible for a thread to change its awareness, using [`SetThreadDpiAwarenessContext`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setthreaddpiawarenesscontext). +When a window is created, it is 'stamped' with the thread awareness at the +time, and the thread will automatically switch back to that awareness when +dispatching messages to the window. This allows a thread to create two windows +with different awarenesses, which means the coordinates seen by the two windows +will be different. + +To know the DPI a window is currently scaling to: [`GetDpiForWindow`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdpiforwindow). + +>![WARNING] +> +>Do not use `MonitorFromWindow` followed by `GetDpiForMonitor` to determine a +window's current DPI. This can be wrong while a window is being dragged between +monitors of different DPIs, or briefly while monitors are changing (for example +after the primary monitor changes but before the window is moved to stay on the +same monitor it was on, which is now in a different location). + +## Window positioning concepts + +### Maximize, Minimize, and Restore + +The API `ShowWindow` can **Maximize**, **Minimize**, and **Restore** a window. + +**Maximized** windows fit the monitor. By default, this will moves the window's +resize borders *outside* the work area. Indeed, moving the cursor to the top of +the monitor and dragging will begin *moving* a Maximized window, not resizing it +(although this will typically *cause* a resize as the window unmaximizes and +begins moving). And moving the cursor to the top-right corner of the monitor +should put it over the Maximized window's close button. + +**Minimized** windows are normally off-screen and can be restored by clicking on +the Taskbar, or pressing Alt-Tab. On SKUs without a Shell (or a Taskbar), the +default Minimize position is the bottom-right of the monitor. + +If the window is not in a special state like Maximized/Minimized, the window is +'normal', or **Restored**. When a normal window becomes Maximized/Minimized, its +previous position is saved (as the normal position). Restoring a window in a +state like Maximized/Minimized moves it back to this normal position. + +The window styles `WS_MAXIMIZED` and `WS_MINIMIZED` are set when a window is in +these states. But using `SetWindowLongPtr` to set these styles directly will not +function correctly: it will not move the window to be maximized or cache its +normal/restore position. Use [`ShowWindow`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow) with something like `SW_MAXIMIZE`, +`SW_MINIMIZE`, or `SW_SHOWMINNOACTIVE` instead. + +To check if a window is Maximized: [`IsZoomed`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-iszoomed) + +To check if a window is Minimized: [`IsIconic`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-isiconic) + +### Arranged (Snapped) + +**Arranged** (or **Snapped**) windows are similar to Maximized windows. They have a normal +position that is different from their 'real' position (`GetWindowRect`), and the +real position is 'fit' to the monitor's work area. + +While Maximized windows fill the entire work area, Arranged windows are aligned +with some number of edges of the work area. For example, left half, corners, +columns, etc. Dragging the Arranged window or double-clicking the title bar will +(like with Maximized windows) cause the window to restore to its normal size. + +To check if a window is Arranged: [`IsWindowArranged`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-iswindowarranged) + +Users can Arrange windows in many ways: + + - Drag a window and hit the edges of the monitor with the cursor + - Drag a window and drop it onto the Snap Flyout that appears at the top of the screen + - Hotkeys like Win+Left/Right + - Snap another window and choose this window from Snap Assist + - See [this MSDN page](https://support.microsoft.com/en-us/windows/snap-your-windows-885a9b1e-a983-a3b1-16cd-c531795e6241) for more info. + +However, `GetWindowPlacement` (detailed below) does not handle Arranged windows. +Also, unlike Maximized/Minimized, there is no `ShowWindow` command (or other +API) to Arrange a window. There is a workaround for this, but it is not trivial. +It's based on 2 factors: + + - Double-clicking on a window's top (or bottom) resize area will Arrange + the window. This aligns the top and bottom borders with the top/bottom of the + work area. + + - Apps can move themselves while Arranged. + + This is true of Maximized as well, though it is not generally advised. + + You can call `SetWindowPos` to move a Maximized, Minimized, or Arranged + window, but this will not change the window's state (styles) or normal + position. Doing this without consulting the monitor information can leave + the window in an unexpected position, possibly off-screen! + +This means that you can do something like this to Arrange a window +and set its position: + +```cpp +DefWindowProc(hwnd, WM_NCLBUTTONDBLCLK, HTTOP, 0); +SetWindowPos(hwnd, nullptr, x, y, cx, cy, SWP_NOZORDER | SWP_NOACTIVATE); +``` + +Be careful when moving an Arranged or Maximized window: + + - Arranged positions should fit a work area, aligning visibly with the monitor + edges. + + While you can move an Arranged or Maximized window into the center of the + screen, this will look and behave strangely! The user will see unexpected + window borders (ones for Max/Arranged windows), and moving the window will + 'restore' the window to a previous size, which could look unexpected. + + Normally, Arranged windows are perfectly aligned with 2 or 3 edges of the + work area. For example, left half, top-left corner, or top/bottom. + + - When moving an Arranged rect between monitors, the relative distance to + each edge of the monitor should be maintained. This is unlike the normal + rect, which generally should retain its logical size. + + - Some windows have invisible resize areas. + + Simply positioning the window (`SetWindowPos`) to align with the edges of + the monitor will leave visible gaps between the window and the monitor, + because part of the window is invisible/transparent. + + To query the visible bounds of a window (the extended frame bounds): + + ```cpp + RECT rcFrame; + HRESULT hr = DwmGetWindowAttribute(hwnd, + DWMWA_EXTENDED_FRAME_BOUNDS, &rcFrame, sizeof(rcFrame)); + ``` + + This value can be compared with `GetWindowRect` to know the size of the + window's invisible resize areas. + + Caveats: + - The window should not be Maximized/Minimized when getting the frame + bounds (the values are different in those states). + - Changing a window's DPI (moving it between monitors) can change the + size of the invisible area! Apps moving windows between monitors + should move the window to the target monitor before querying the + window's frame bounds. + - This API does not handle DPI virtualization. If an app or window is + virtualized for DPI (for example, a DPI unaware window), these values + may not match those from GetWindowRect. + +### Cascading + +If an app launches multiple windows, or if the user launches an app multiple +times, it is common to **cascade** the windows. This means moving each window +down and right a bit, leaving the previous window's title bar visible. If the +new position is now outside the work area, the window is moved to the top or +left side of the same monitor. + +The size of the 'nudge' is ideally the height of the window's caption bar. +This can be queried using system metrics: + +```cpp +UINT captionheight = + GetSystemMetricsForDpi(SM_CYCAPTION, dpi) + + (2 * GetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi)); +``` + +Adding `2 * SM_CXSIZEFRAME` ensures the entire title bar from the previous +window is visible, since it includes the invisible resize area (which is part of +the title bar when not Maximized). + +> ![NOTE] +> +> The similar function [`GetSystemMetrics`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsystemmetrics) +returns values for the *process* DPI. If an app is Per-Monitor DPI aware, it +should use [`GetSystemMetricsForDpi`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsystemmetricsfordpi) +and the DPI of the window, [`GetDpiForWindow`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdpiforwindow). + +## Common factors when relaunching windows + +### `Get*` and `SetWindowPlacement` + +[GetWindowPlacement](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowplacement) and [SetWindowPlacement](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowplacement) are the main APIs intended to remember window placement between launches. They handle Maximized, Minimized, and Restored windows to an extent. However, they do not handle Arranged windows. + +`GetWindowPlacement` returns a [`WINDOWPLACEMENT`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-windowplacement), which contains: + + - `rcNormalPosition` is the window position (the normal, restore position if + Maximized, Minimized, or Arranged.) + - `showCmd` is a `ShowWindow` command, `SW_MAXIMIZE`, `SW_MINIMIZE`, or `SW_NORMAL`. + - `flags` can be `WPF_RESTORETOMAXIMIZED` (and others). + - **Note:** It is not recommended to use the other fields, like ptMinPosition. + +[`SetWindowPlacement`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowplacement) accepts this `WINDOWPLACEMENT` struct and +sets a window's normal position and Maximize state. Internally, this calls [`SetWindowPos`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowpos) +and [`ShowWindow`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow). + +When these APIs were created, prior to multiple monitor support, apps could +`GetWindowPlacement` on exit, store the `WINDOWPLACEMENT` to the registry, and +`SetWindowPlacement` when launching, and the app successfully launched where it +was when last closed in all cases. Today, doing this (and nothing else to +adjust the position) would lead to the app sometimes being the wrong size or +partially off-screen. + +> ![NOTE] +> The normal rect is in 'workspace coordinates', which is offset by the space +between the window's monitor's work area and monitor rect. These coordinates +match screen coordinates, even on secondary monitors, unless the taskbar is on +the top or left. + +### The `STARTUPINFO` + +There is a struct called [**`STARTUPINFO`**](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa), +which contains flags set by whoever launched the process, for example by the +taskbar or start menu. Apps can query these flags using [`GetStartupInfo`](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getstartupinfow). A few `STARTUPINFO` flags relate to the position of the app's first window. + + - `STARTF_USESHOWWINDOW` is set if the caller requested that the window launch + `SW_MAXIMIZE` or `SW_MINIMIZE`. Note that apps can 'overrule' this if the app last + closed Maximized and is being launched with `SW_NORMAL`. + + For example, from cmd: + ```cmd + > start /min notepad + ``` + + From PowerShell: + ```powershell + > start -WindowStyle Minimized notepad + ``` + + - The `hStdOutput` can, for non-console apps, be an `HMONITOR`. This is the + 'Monitor Hint.' If launched from the Start Menu or Taskbar on a secondary + monitor, this `HMONITOR` will be the monitor the user clicked on to launch the + app. + + - Less used/useful are `STARTF_USEPOSITION` and `STARTF_USESIZE`. These specify the + position and size of the first window. + +### `CreateWindow` parameters + +[**`CreateWindow`**](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowa) +takes an: `x`, `y`, `nWidth`, `nHeight`, and styles. (And other parameters not +discussed here.) + +If the position and size parameters are ALL `CW_USEDEFAULT` (a special value), +the window will be in the default position: + +- On the primary, unless a monitor hint is set in `STARTUPINFO`. +- Cascading: at a position a bit down/right from the last window to get the default + position on that monitor, see above. +- A size picked from the size of the monitor (likely too large on very large + monitors). + +Note that if the size parameters are set, the window will *always* launch on the +primary and at the requested size (which is assumed scaled to the DPI of the +primary monitor). + +If a process is launched with the `STARTUPINFO` show command set, that is applied +to the first window the app makes visible (matching some criteria, like `WS_CAPTION`). + +Since these parameters must all be `CW_USEDEFAULT` for the magic behavior to +occur, when restoring window positions (including Maximize state, size, and +monitor), it is recommended to create the window hidden (without `WS_VISIBLE`) +and in the default position (`CW_USEDEFAULT`). Then, after the window is +created, the app should move the window to its correct position (which in some +cases requires moving multiple times), and only show the window when it is in +the final position. + +Providing a size or position to `CreateWindow` requires querying the monitor +information and pre-scaling the size to the DPI of the monitor (and adjusting +it to ensure the position is on screen). If Maximizing/Minimizing, this would +be translated to the styles field (`WS_MAXIMIZE`/`WS_MINIMIZE`). + +Apps can also position themselves within `WM_CREATE`. Apps can pick an initial +size here (and call `SetWindowPos`), but calling `ShowWindow` or +`SetWindowPlacement` to Maximize the window can cause problems! After +`WM_CREATE` returns, if the window has `WS_MAXIMIZE`/`WS_MINIMIZE` styles, it is +assumed that the window was created with these styles (and its current position +is the normal position). If the window has been Maximized already at this point, +restoring it will not move the window (its normal position was set to the +Maximize position). This is (much) worse if done for Minimize, since it could +cause the window to be stuck off screen. + +## Restarting windows after updates/crashes + +There are also several other factors to consider when apps *restart themselves* +(e.g. after updating themselves) or are restarted automatically (e.g. after a +system update causes a reboot). + +### Relaunching Minimized + +If an app is closed while the window is Maximized, it is ideal to launch +Maximized. But, if closed while Minimized, for example via Taskbar/Alt-Tab, the +window should typically launch to its restore position. (As a bonus, you can use +`WPF_RESTORETOMAXIMIZED` to launch Maximized if the window was Maximized prior +to Minimizing and closing.) + +BUT, what if the app restarts itself? If a window is Minimized while the app +restarts, the app should relaunch the window Minimized, 'staying Minimized' +over the app restart. + +Similarly, if the system reboots while the app is open and minimized, the window +should be Minimized after reboot. (In other words, if a machine reboots +overnight, all Minimized windows should not be restored from Minimize). + +As a general rule: when storing positions, remember the Minimize state. When +launching normally, this state should be ignored (the window should launch to +the normal position, or Maximized if WPF_RESTORETOMAXIMIZED). But when +*relaunching* after a reboot or crash, relaunch to Minimized if it was +previously Minimized. + +>![NOTE] +> +> A previous section described `STARTUPINFO`, which allows users to launch an +appwith an explicit show command, e.g. `SW_MINIMIZE`. If launched in this way, +like `start /min`, the app should launch Minimized regardless of the last close +position. + +### Virtual Desktops + +Win+Tab and Taskview button on the taskbar allow the user to create +multiple [**Virtual Desktops**](https://support.microsoft.com/en-us/windows/configure-multiple-desktops-in-windows-36f52e38-5b4a-557b-2ff9-e1a60c976434) (sometimes called **Desktops**). These are groups +of windows that the user can switch between. When a window is on a background +virtual desktop, it is cloaked (hidden). + +Apps generally do not need to worry about virtual desktops, including for most +scenarios related to restoring window positions. In a normal app launch, it is +always the case that the window should launch on the current virtual desktop +(even if closed while on a background desktop). + +But, when restarted (automatic app/sytstem update, etc), apps should ideally +'keep' their windows on the same virtual desktops. Consider an app like a +browser that creates many windows, and a user has organized them onto different +virtual desktops. If the system reboots and the app is restarted, it should +restore all window positions, *and* it should move each window to its previous +virtual desktop. + +This is done using the [`IVirtualDesktopManager`](https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-ivirtualdesktopmanager) APIs, which has these two functions: + - [`GetWindowDesktopId`](https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ivirtualdesktopmanager-getwindowdesktopid) + - [`MoveWindowToDesktop`](https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ivirtualdesktopmanager-movewindowtodesktop) + +When storing previous window positions, apps should remember the GUID for the +window's virtual desktop. This is used ONLY when the app is restarted, not in a +'normal launch'. This is similar to minimized; if closed while minimized and +launched normally, the app should start restored (or maximized). + +### Restarting after Windows Update with `RegisterApplicationRestart` + +If you search for 'Restart apps after signing in' from the start menu, you'll +find a user setting (which is off by default). If enabled, apps should [relaunch +themselves if opened while the system +reboots](https://blogs.windows.com/windowsdeveloper/2023/07/20/help-users-resume-your-app-seamlessly-after-a-windows-update/). +Your app should call [`RegisterApplicationRestart`](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-registerapplicationrestart), which accepts a parameter +that is passed as a command line argument when the system relaunches your app +after a reboot. + +For example: + +```cpp +// Register your app for restart; you can control which types of restart with flags. +PCWSTR restartCmdLine = L"restart"; +DWORD flags = 0; +RegisterApplicationRestart(restartCmdLine, flags); + +// Detect a restart by checking for the argument +const bool isRestart = (wcsstr(cmdLine, restartCmdLine) != nullptr); +``` + +If your app receives this command line argument, you know your app is restarting. You should use this as a signal to reuse previous minimized state and virtual desktops. + +Note that when closing, apps should handle `WM_ENDSESSION` to know when they are +being closed in all cases. This message is sent prior to destroying the window +when the system is shutting down. + +## Example: putting it all together + +We now have the background to correctly relaunch windows in their previous +locations. + +We'll need this stored information to do so correctly: + +* the window's last **normal position** (a `RECT` in screen coordinates) +* potentially its last **Arranged position** (also in screen coordinates) + * If a window is Arranged, it has both a normal rect and an Arranged rect. (If + Max/Min/Normal, the Arranged rect is not set.) +* some information about the window's last monitor + * its **work area** + * its **DPI** + * its **device name** (`szDevice` from `MONITORINFOEX`) + +### Moving stored positions between monitors + +First, we need an algorithm for translating stored positions between monitors. +This is necessary if the stored position is from a monitor that no longer exists +or changed in some way. Given the stored information above and a new monitor +with its own work area & DPI, we can adjust our `RECT`s: + +* **The normal rect** + + 1. Adjust the rect to stay the same logical offset from its work area. + (Calculate the logical offset by calculating the offset in physical pixels + and then scaling by the DPI). + 2. Scale the rect's size from the previous DPI to the new DPI, keeping the same + logical size. + 3. If the rect is now larger than the work area of its new monitor, pick a + different size. Preferably, scale the original size based on the ratio + between the old work area to the new one. + 4. Crop the rect as needed to remain entirely within the work area. + +* **The Arrange rect** + - The Arranged rect should be stored in 'frame bounds' (without invisible + resize borders). The size of the invisible resize borders change when the + DPI changes, and this size does not scale linearly. To ensure Arranged + positions keep windows visibly aligned when their DPI changes while + Arranged, it is necessary to remove the invisible resize borders prior to + storing the rect, and add them back in when Arranging the window, after + the window is already on the monitor it is being Arranged on. + - The Arranged rect always is sized to stay the same relative distance from + each edge of the work area. + +Note that it is not necessary to handle the Maximize/Minimize positions when +moving the other RECTs to a different monitor. These positions are chosen +depending on the monitor a window is on when it is Maximized/Minimized, and apps +can handle messages like `WM_GETMINMAXINFO` to override these default positions. + +### Moving a window to a stored position + +Next, we need an algorithm for moving a window to a desired position on any +monitor. + +Let's go step by step. Given the same stored information plus a **show window +command** (`SW_*`): + +1. **Pick a monitor.** + + To pick the best monitor in all cases, it is recommended to store the device + name (`MONITORINFOEX`'s `szDevice`), in addition to the work area. In cases + like changing the primary monitor, this results in better behavior than + reusing the work area (since a user changing the primary monitor likely does + not think of it as a change in coordinate space). + + So: + + - If a monitor exists with the same name, use that monitor. + + - If that's not available, fall back to `MonitorFromRect` with + `MONITOR_DEFAULTTONEAREST`. + +2. **Adjust the normal rect** (and Arrange rect if Arranged) for this desired + monitor. + + - Each rect (normal/arranged) is transformed in a different way. + + - See previous section. + +3. **Disable painting & animations**, if needed. + + - In some cases, it will be necessary to move the window multiple times. To + avoid flickering, temporarily disable painting and animations. + + - Calling `WM_SETREDRAW` with `false` for `wParam` disables painting for the + window. While disabled, the window's contents on screen will not be + changed (though the window will not be hidden, if it is visible). When + re-enabling painting (`WM_SETREDRAW` `true`) at the end, the window will need + to be explicitly invalidated (repainted) and activated, since both are + skipped while `WM_SETREDRAW` false. + + - `SystemParametersInfo`'s `SPI_SETANIMATION` sets a global user setting for + animating window transitions (Max/Min/Arrange/Restore). If moving the + window multiple times, it is best to temporarily disable animations. This + ensures the window isn't shown animating from a position the user didn't + see the window at. + + ```c++ + DefWindowProc(hwnd, WM_SETREDRAW, 1, 0); + + ANIMATIONINFO animationInfo = { sizeof(ANIMATIONINFO) }; + SystemParametersInfo(SPI_GETANIMATION, 0, &animationInfo, 0); + bool needsAnimationReset = !!animationInfo.iMinAnimate; + if (needsAnimationReset) + { + animationInfo.iMinAnimate = false; + SystemParametersInfo(SPI_SETANIMATION, 0, &animationInfo, 0); + } + + // ... move window multiple times + + DefWindowProc(hwnd, WM_SETREDRAW, 1, 0); + SetActiveWindow(hwnd); + InvalidateRect(hwnd, nullptr, true); + + if (needsAnimationReset) + { + animationInfo.iMinAnimate = true; + SystemParametersInfo(SPI_SETANIMATION, 0, &animationInfo, 0); + } + ``` + +4. **Restore the window**. If a window is Maximized, Minimized, or Arranged, the + window needs to be restored (SW_RESTORE) before it is moved to the desired + normal position. + + - The normal position is defined as the last position the window had before + Maximizing/Minimizing/Arranging. + + - If Maximized and moving to another monitor (but staying Maximized), the + window must be restored first, moved to the other monitor, and then + Maximized. Moving only once would leave the normal position on the + previous monitor (restoring the window would move it to another monitor). + +5. **Move the window to the correct monitor**. If a window is changing monitors, it must be moved to that monitor prior to + maximizing/minimizing. + + - This is especially important when changing DPI, since moving a window + between monitors of different DPIs causes the window to change size + (via WM_DPICHANGED). + + - It is fine to simply SetWindowPos with the desired normal RECT, expecting + that the window may receive a WM_DPICHANGED with a size that is scaled + by some amount (from the window's current DPI to the new monitor's DPI). + The next time the window is moved it will already be at the right DPI and + not have the size scaled. + + - One 'gotcha' with the above is that if moving to a higher DPI, this + 'unwanted WM_DPICHANGED' RECT could potentially move the window onto some + other monitor! (If moving to the bottom right of a high DPI monitor and + we find that another monitor is to the right/below.) To account for this, + scale the temp RECT from the monitor DPI to the window DPI, ensuring that + when the system scales the RECT in the opposite direction, the final size + and position match the stored (and now modified) normal RECT. + +6. Once on the desired monitor, call `SetWindowPlacement` to set the desired + normal position and show state. + +7. If Arranging, the show command with SetWindowPlacement is SW_NORMAL. We need + to make the window arranged and move it to the Arranged position. + (SetWindowPlacement doesn't support Arranged.) + + ```c++ + DefWindowProc(hwnd, WM_NCLBUTTONDBLCLK, HTTOP, 0); + + // The Arranged RECT is stored in frame bounds (no invisible resize + // borders). It has already been 'moved' (fit) to the monitor we're moving + // to, staying aligned with the edges of the work area. + // Now that the window is on the monitor it is moving to, and Normal (not + // Maximized/Minimized), query the size of the invisible resize borders + // and expand the Arranged RECT by that size. + RECT rcWindow, rcFrame; + GetWindowRect(hwnd, &rcWindow); + DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &rcFrame, sizeof(rcFrame)); + arrangeRect->left -= rcFrame.left - rcWindow.left; + arrangeRect->top -= rcFrame.top - rcWindow.top; + arrangeRect->right += rcWindow.right - rcFrame.right; + arrangeRect->bottom += rcWindow.bottom - rcFrame.botto; + + SetWindowPos(hwnd, + nullptr, + arrangeRect.left, + arrangeRect.top, + arrangeRect.right - arrangeRect.left, + arrangeRect.bottom - arrangeRect.top, + SWP_NOZORDER | SWP_NOACTIVATE); + ``` + +### Remembering Window Positions + +Now we can stitch this all together so that your app will relaunch wherever it +was when last closed: + +1. The app defines some persisted data store, like a registry path. + + ```cpp + PCWSTR appRegKeyName = L"SOFTWARE\\UniqueNameForThisApp"; + PCWSTR lastCloseRegKeyName = L"LastClosePosition"; + ``` + +2. Before creating the window, check if another instance of the app is already + running. + + - Note: [`FindWindow`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-findwindowa) +returns the top window in z-order if multiple other instances are running. + + ```c++ + HWND hwndPrev = FindWindow(wndClassName, nullptr); + ``` + +3. Create the window without `VS_VISIBLE` and with `CW_USEDEFAULT` position and size. + + ```c++ + HWND hwnd = CreateWindowEx( + ... + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + ...); + + if (!hwnd) + { + // Handle failure. + } + ``` + +4. Set the initial position. + + - If there was a previous window position, start with that window's position. + - If that window is Minimized, use the restored position. + - Cascade the position, moving it down/right to keep both windows title bars visible. + - If no previous window, check the last close position. + - If no last close position, set some hard coded logical size reasonable for + the app to use by default. + - If the STARTUPINFO has relevant flags set, adjust the position picked above + as needed. + - Set the window position and show the window. + +3. Call `RegisterApplicationRestart`. + + - Do this after a successful launch. If the app is running while the system + shuts down for updates, it will then be relaunched after the system reboots. + + ```c++ + RegisterApplicationRestart(L"", 0); + ``` + +4. When the window gets `WM_CLOSE`, store the window position in the registry. + + ```c++ + // ...wndproc... + case WM_CLOSE: + PlacementEx::StorePlacementInRegistry( + hwnd, + appRegKeyName, + lastCloseRegKeyName); + break; + ``` + +## More scenarios + +### FullScreen windows + +All previous sections describe 4 possible window states, Max/Min/Arrange/Normal. +In these four states, all required information to capture a window's position is +known to the system (and exposed via APIs like `GetWindowPlacement` and `GetDpiForWindow`). + +FullScreen is another (separate) state: + + - A FullScreen window does not have the `WS_CAPTION` or `WS_THICKFRAME` styles. + (It has no title bar or resize borders). + - A FullScreen window fits the monitor rect, covering the taskbar (if one is + present on the monitor). + +Some uncommon knowledge about FullScreen windows: + + - They are NOT always above other apps (z-order), or covering all monitors. + - They can be Maximized or Minimized. (ex, maximize a browser window then F11 + twice. This enters FullScreen and exits back to Maximized). + - They can change monitors. (ex, unplug a FullScreen window's monitor, or + change the resolution of the monitor a FullScreen window is on). + +The main difference between FullScreen and Maximize is that the system does +not know all of the state for the window (only the app knows the window's restore +from FullScreen position). + +If an app becomes FullScreen, and is FullScreen when it is closed, it should NOT +store the FullScreen position (the one sized to the monitor). Launching to this +size, without being FullScreen (and without knowing the right restore position +upon exiting FullScreen) would lead to unexpected behavior. Instead, apps that +enter FullScreen must remember their position prior to becoming FullScreen. They +can use this as their "restore position" when exiting full screen, and they +should then store this as their "current position" when storing placement . + + ```c++ + case WM_DESTROY: + if (WI_IsFlagSet(placement.flags, PlacementFlags::FullScreen)) + { + placement.MoveToMonitor(hwnd); + } + else + { + PlacementEx::GetPlacement(hwnd, &placement); + } + + placement.StoreInRegistry(registryPath, lastCloseRegKeyName); + break; + ``` + + +### Handling monitor changes + +After an app creates a window, the monitors can change at any time! This means +that screen coordinates that are 'on-screen' (within the bounds of some monitor) +could become off-screen if that monitor is removed, or changes size. + +Most apps care about 2 "monitor" events: + +* `WM_DISPLAYCHANGE`: sent to all top-level windows when monitors change. +* `WM_SETTINGCHANGE` (for `SPI_SETWORKAREA`): sent when work area changes. This is important: if a new docked toolbar appears (e.g. Voice Access), only this event is fired, not `WM_DISPLAYCHANGE`. + +DANGER: If an app moves itself in response to WM_DISPLAYCHANGE, it may end up +off screen or in unexpected places! + + - A user has two monitors connected to a laptop, using two ports on the + laptop. The user unplugs one cable, then immediately unplugs the other. + + - It is possible that the first display change moves the window to the monitor + that is about to be removed. If this happens, the window may be moved twice. + + - It is possible that the window receives the first WM_DISPLAYCHANGE after + being moved once, but after that monitor it was moved to was removed. During + this time, the window moving itself would cause the system to NOT move the + window the second time. + + - The final state of the window becomes hard to define. If the user expects + the system's behavior (like moving the window back to a monitor it was on when + the monitor was unplugged), the window might end up on the wrong monitor. And + if the app only sizes itself, but does not check that it's position is on + screen, this could cause the window to end up off screen. + +It is important to handle failures when using HMONITOR APIs, even when using +MONITOR_DEFAULTTONEAREST. (The handle this call returns will never be null, but +it could become invalid before it is used.) + +```cpp +MONITORINFOEX mi { sizeof(mi) }; +if (!GetMonitorInfo(MonitorFromRect(&rc, MONITOR_DEFAULTTONEAREST), &mi)) +{ + // Return without moving the window. The monitors are changing. + return; +} +``` + +Note: Apps sometimes query the monitors very frequently, which can make these +'very rare' error cases difficult to handle, or expensive to compute. See +[CurrentMonitorTopology.h](CurrentMonitorTopology.h), which defines a cache +for the monitor data that addresses these issues. \ No newline at end of file diff --git a/Samples/WindowPlacement/cpp/FullScreenSample/FullScreenSample.cpp b/Samples/WindowPlacement/cpp/FullScreenSample/FullScreenSample.cpp new file mode 100644 index 00000000..ff29c084 --- /dev/null +++ b/Samples/WindowPlacement/cpp/FullScreenSample/FullScreenSample.cpp @@ -0,0 +1,325 @@ + +#define USE_VIRTUAL_DESKTOP_APIS +#include "User32Utils.h" +#include "resource.h" + +// A FullScreen window is sized to the monitor and doesn't have a caption +// bar or resize borders (in other words, no WS_CAPTION or WS_THICKFRAME). +// +// When exiting FullScreen, users expect the window to move back to its previous +// position, stored when entering FullScreen. This is similar to Maximize/Minimize, +// except apps must track this restore position manually; the system does not. +// +// This sample demonstrates entering/exiting FullScreen, using PlacementEx to +// implement this memory. PlacementEx remembers the window position (when +// entering) and uses that position to move the window at a later time (exiting). +PlacementEx fsPlacement; + +// The last close position is stored in the registry, using a string that is +// unique to this app. +PCWSTR registryPath = L"SOFTWARE\\Microsoft\\Win32Samples\\FullScreenSample"; +PCWSTR lastCloseRegKeyName = L"LastClosePosition"; + +// Called after creating the window (hidden). This picks a good starting +// position for the window and shows it. +// +// hwndPrev Another instance of this app, opened prior to this +// instance opening. If this is not null, this window will +// launch over the other instance, in the same position but +// cascaded (moved down/right a bit to keep both windows visible). +// +// isRestart If true, this is a restart (not a normal launch). The +// system restarted while this app was running, and we're +// being relaunched. +// This allows the window to launch minimized, or cloaked (on +// a background virtual desktop). +// +void SetInitialPosition(HWND hwnd, HWND hwndPrev, bool isRestart) +{ + PlacementParams pp({ 600, 400 }, registryPath, lastCloseRegKeyName); + + if (isRestart) + { + pp.SetIsRestart(); + } + else if (hwndPrev) + { + pp.SetPrevWindow(hwndPrev); + } + + fsPlacement = pp.PositionAndShow(hwnd); + + // Repaint now that fsPlacement is set (checked for when painting to pick + // background color). + InvalidateRect(hwnd, nullptr, true); +} + +// Called before destroying the window. This stores the current window placement +// in the registry, as the last close position. +// +// If closed while FullScreen, make sure our stored position is on the window's +// current monitor (but do not refresh it). We want to launch next time as +// FullScreen, with the restore position that we stored when entering FullScreen. +void SaveLastClosePosition(HWND hwnd) +{ + if (fsPlacement.IsFullScreen()) + { + fsPlacement.MoveToWindowMonitor(hwnd); + } + else if (!PlacementEx::GetPlacement(hwnd, &fsPlacement)) + { + return; + } + + fsPlacement.StoreInRegistry( + registryPath, + lastCloseRegKeyName); +} + +// Handle keyboard input. +void OnWmChar(HWND hwnd, WPARAM wParam) +{ + switch (wParam) + { + // Enter key toggles FullScreen. + case VK_RETURN: + fsPlacement.ToggleFullScreen(hwnd); + return; + + // Space toggles Maximize. + // This is not done while FullScreen. + case VK_SPACE: + if (!fsPlacement.IsFullScreen()) + { + ShowWindow(hwnd, IsZoomed(hwnd) ? SW_RESTORE : SW_MAXIMIZE); + } + return; + + // M key minimizes the window. + case 0x4D: // M key + case 0x6D: // m key + ShowWindow(hwnd, SW_MINIMIZE); + return; + + // C key moves the cursor to the center of the window. + case 0x43: // C key + case 0x63: // c key + { + RECT rc; + GetWindowRect(hwnd, &rc); + SetCursorPos(rc.left + ((RECTWIDTH(rc)) / 2), rc.top + ((RECTHEIGHT(rc)) / 2)); + return; + } + + // Escape key exits. + case VK_ESCAPE: + DestroyWindow(hwnd); + return; + } +} + +void OnWmPaint(HWND hwnd, HDC hdc) +{ + const COLORREF rgbMaize = RGB(255, 203, 5); + const COLORREF rgbBlue = RGB(0, 39, 76); + static HBRUSH hbrMaize = CreateSolidBrush(rgbMaize); + static HBRUSH hbrBlue = CreateSolidBrush(rgbBlue); + const UINT dpi = GetDpiForWindow(hwnd); + + // Create a font of size 30 (* DPI scale) and store it until DPI changes. + static HFONT hfont = nullptr; + static UINT dpiLast = 0; + if (dpiLast != dpi) + { + if (hfont) + { + DeleteObject(hfont); + } + + hfont = CreateFont(MulDiv(30, dpi, 96), + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + L"Courier New"); + + dpiLast = dpi; + } + SelectObject(hdc, hfont); + + // Background in one color, text in the other. + const bool fFullScreen = fsPlacement.IsFullScreen(); + HBRUSH hbrBackground = fFullScreen ? hbrMaize : hbrBlue; + COLORREF rgbText = fFullScreen ? rgbBlue : rgbMaize; + + SetBkMode(hdc, TRANSPARENT); + SetTextColor(hdc, rgbText); + + RECT rc; + GetClientRect(hwnd, &rc); + + // Draw background + FillRect(hdc, &rc, hbrBackground); + + const UINT nudge = MulDiv(30, dpi, 96); + + // Draw text + + rc.top += (3 * nudge); + rc.left += nudge; + + PCWSTR toggleFullTxt = fFullScreen ? + L"ENTER to exit Fullscreen" : L"ENTER to enter Fullscreen"; + DrawText(hdc, toggleFullTxt, (int)wcslen(toggleFullTxt), &rc, DT_LEFT); + + if (!fFullScreen) + { + rc.top += nudge; + PCWSTR toggleMaxTxt = IsZoomed(hwnd) ? + L"SPACE to restore from Maximize" : L"SPACE to Maximize"; + DrawText(hdc, toggleMaxTxt, (int)wcslen(toggleMaxTxt), &rc, DT_LEFT); + } + + rc.top += nudge; + PCWSTR minTxt = L"M to minimize"; + DrawText(hdc, minTxt, (int)wcslen(minTxt), &rc, DT_LEFT); + + rc.top += nudge; + PCWSTR escTxt = L"ESC to close"; + DrawText(hdc, escTxt, (int)wcslen(escTxt), &rc, DT_LEFT); + + rc.top += nudge; + PCWSTR cTxt = L"C to move cursor to center"; + DrawText(hdc, cTxt, (int)wcslen(cTxt), &rc, DT_LEFT); +} + +LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) + { + case WM_CHAR: + OnWmChar(hwnd, wParam); + break; + + case WM_SYSCOMMAND: + + // If the window is maximized, FullScreen, and someone is trying to + // restore the window (for example, Win+Down hotkey), exit FullScreen. + // (We do NOT want to remain FullScreen but restore from Maximize and + // move to the restore position...) + if ((wParam == SC_RESTORE) && + IsZoomed(hwnd) && + fsPlacement.IsFullScreen()) + { + fsPlacement.ExitFullScreen(hwnd); + return 0; + } + break; + + case WM_PAINT: + { + PAINTSTRUCT ps; + OnWmPaint(hwnd, BeginPaint(hwnd, &ps)); + EndPaint(hwnd, &ps); + break; + } + + case WM_DPICHANGED: + { + RECT* prc = (RECT*)lParam; + + SetWindowPos(hwnd, + nullptr, + prc->left, + prc->top, + prc->right - prc->left, + prc->bottom - prc->top, + SWP_NOZORDER | SWP_NOACTIVATE); + break; + } + + case WM_ENDSESSION: + case WM_DESTROY: + SaveLastClosePosition(hwnd); + PostQuitMessage(0); + break; + } + + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +bool InitWindow(HINSTANCE hInst, bool isRestart) +{ + PCWSTR windowTitle = L"FullScreen Sample"; + PCWSTR wndClassName = L"FullScreenSampleWindow"; + + WNDCLASSEX wc = { sizeof(wc) }; + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc = WndProc; + wc.hInstance = hInst; + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.lpszClassName = wndClassName; + wc.hIcon = LoadIcon(hInst, MAKEINTRESOURCE(IDC_FULLSCREEN)); + + if (!RegisterClassEx(&wc)) + { + return false; + } + + HWND hwndPrev = FindWindow(wndClassName, nullptr); + + // Create the window with the default position and not visible. + HWND hwnd = CreateWindowEx( + 0, + wndClassName, + windowTitle, + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + nullptr, + nullptr, + hInst, + nullptr); + + if (!hwnd) + { + return false; + } + + SetInitialPosition(hwnd, hwndPrev, isRestart); + + return true; +} + +int wWinMain(HINSTANCE hInst, HINSTANCE, LPWSTR cmdLine, int) +{ + // If 'u' in the command line, run as DPI Unaware. Otherwise run as + // Per-Monitor DPI Aware. + SetThreadDpiAwarenessContext((wcsstr(cmdLine, L"u") != nullptr) ? + DPI_AWARENESS_CONTEXT_UNAWARE : + DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + + // Initialize COM, needed for virtual desktop APIs (USE_VIRTUAL_DESKTOP_APIS). + CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + + PCWSTR restartCmdLine = L"restart"; + const bool isRestart = (wcsstr(cmdLine, restartCmdLine) != nullptr); + RegisterApplicationRestart(restartCmdLine, 0); + + // Create the main window. + if (!InitWindow(hInst, isRestart)) + { + MessageBox(NULL, + L"Failed to create Main Window.", + L"ERROR", MB_ICONEXCLAMATION | MB_OK); + return 1; + } + + MSG msg; + while (GetMessage(&msg, nullptr, 0, 0)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + return 0; +} diff --git a/Samples/WindowPlacement/cpp/FullScreenSample/FullScreenSample.vcxproj b/Samples/WindowPlacement/cpp/FullScreenSample/FullScreenSample.vcxproj new file mode 100644 index 00000000..c4b45c4b --- /dev/null +++ b/Samples/WindowPlacement/cpp/FullScreenSample/FullScreenSample.vcxproj @@ -0,0 +1,130 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + $(VCTargetsPath11) + + + {C1D15996-DAB8-4608-A6ED-CE0AE53EEBA8} + FullScreenSample + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + Level3 + Disabled + ../inc;%(AdditionalIncludeDirectories) + + + true + Windows + + + + + Level3 + MaxSpeed + true + true + ../inc;%(AdditionalIncludeDirectories) + + + true + true + true + Windows + + + + + Level3 + Disabled + ../inc;%(AdditionalIncludeDirectories) + + + true + Windows + + + + + Level3 + MaxSpeed + true + true + ../inc;%(AdditionalIncludeDirectories) + + + true + true + true + Windows + + + + + + + + + \ No newline at end of file diff --git a/Samples/WindowPlacement/cpp/FullScreenSample/full-screen.ico b/Samples/WindowPlacement/cpp/FullScreenSample/full-screen.ico new file mode 100644 index 0000000000000000000000000000000000000000..efff82cd2e0a44432151c3bd729691fb17ee0233 GIT binary patch literal 67646 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 + UINT dpi = GetDpiForWindow(hwnd); + pmmi->ptMinTrackSize.x = MulDiv(minSize.cx, dpi, 96); + pmmi->ptMinTrackSize.y = MulDiv(minSize.cy, dpi, 96); +} + +// Handle WM_GETMINMAXINFO: +// - escape closes the window +// - 1 key sizes window to 500x500 +void OnWmChar(HWND hwnd, WPARAM wParam) +{ + switch(wParam) + { + case VK_ESCAPE: + SendMessage(hwnd, WM_CLOSE, 0, 0); + return; + + case 0x31: /* VK_1 */ + { + PlacementEx pex; + if (PlacementEx::GetPlacement(hwnd, &pex)) + { + pex.showCmd = SW_SHOWNORMAL; + WI_ClearFlag(pex.flags, PlacementFlags::Arranged); + pex.SetLogicalSize(defaultSize); + PlacementEx::SetPlacement(hwnd, &pex); + } + return; + } + } +} + +void OnWmPaint(HWND hwnd, HDC hdc) +{ + const COLORREF rgbMaize = RGB(255, 203, 5); + const COLORREF rgbBlue = RGB(0, 39, 76); + const COLORREF rgbRed = RGB(122, 18, 28); + static HBRUSH hbrMaize = CreateSolidBrush(rgbMaize); + static HBRUSH hbrBlue = CreateSolidBrush(rgbBlue); + static HBRUSH hbrRed = CreateSolidBrush(rgbRed); + const UINT dpi = GetDpiForWindow(hwnd); + const UINT textSize = 25; + static HFONT hfont = nullptr; + static UINT dpiLast = 0; + + // Create a font and store it until the window changes DPI (scale). + if (dpiLast != dpi) + { + if (hfont) + { + DeleteObject(hfont); + } + + hfont = CreateFont(MulDiv(textSize, dpi, 96), + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + L"Courier New"); + + dpiLast = dpi; + } + + SetBkMode(hdc, TRANSPARENT); + SelectObject(hdc, hfont); + + // If maximized - Blue (yellow text) + // If arranged - Red (yellow text) + // Restored - Yellow (blue text) + COLORREF rgbText; + HBRUSH hbrBackground; + if (IsZoomed(hwnd)) + { + rgbText = rgbMaize; + hbrBackground = hbrBlue; + } + else if (IsWindowArranged(hwnd)) + { + rgbText = rgbMaize; + hbrBackground = hbrRed; + } + else + { + rgbText = rgbBlue; + hbrBackground = hbrMaize; + } + SetTextColor(hdc, rgbText); + + RECT rc; + GetClientRect(hwnd, &rc); + FillRect(hdc, &rc, hbrBackground); + UINT nudge = MulDiv(10, dpi, 96); + InflateRect(&rc, -1 * nudge, -1 * nudge); + RECT rcTxt = rc; + + const UINT lineHeight = nudge + MulDiv(textSize, dpi, 96); + + // If Maximized or Arranged + if (IsZoomed(hwnd) || IsWindowArranged(hwnd)) + { + PCWSTR maxTxt = IsZoomed(hwnd) ? L"Maximized" : L"Arranged"; + DrawText(hdc, maxTxt, (int)wcslen(maxTxt), &rc, DT_LEFT); + rc.top += lineHeight; + } + + RECT rcWindow; + GetWindowRect(hwnd, &rcWindow); + + // Current window size, then rect. + std::wstring sizeStr = wil::str_printf(L"(%d x %d)", + RECTWIDTH(rcWindow), RECTHEIGHT(rcWindow)); + DrawText(hdc, sizeStr.c_str(), (int)wcslen(sizeStr.c_str()), &rc, DT_LEFT); + rc.top += lineHeight; + + std::wstring rectStr = wil::str_printf(L"[%d, %d, %d, %d]", + rcWindow.left, rcWindow.top, rcWindow.right, rcWindow.bottom); + DrawText(hdc, rectStr.c_str(), (int)wcslen(rectStr.c_str()), &rc, DT_LEFT); + + // Reset the rect to the full window (with nudge) and move to the bottom + // row (the user settings). + rc = rcTxt; + rc.top = rc.bottom - lineHeight; + + // UseMonitorHint -> Launch Monitor: Last/Best (where 'use hint' == best) + std::wstring lastMonSettingTxt = wil::str_printf( + L"Launch Monitor: %ws", UseMonitorHint ? L"Best" : L"Last"); + DrawText(hdc, lastMonSettingTxt.c_str(), (int)wcslen(lastMonSettingTxt.c_str()), + &rc, DT_SINGLELINE | DT_LEFT | DT_BOTTOM); + + // Remember the rect for the monitor hint rect; clicking toggles the setting. + rcUseMonitorHintTxt = rc; + + // AllowOffScreen. If no, this sets PlacementFlags::AllowPartiallyOffScreen. + rc.top -= lineHeight; + rc.bottom -= lineHeight; + std::wstring offscreenSettingTxt = wil::str_printf( + L"Allow OffScreen: %ws", AllowOffScreen ? L"Yes" : L"No"); + DrawText(hdc, offscreenSettingTxt.c_str(), (int)wcslen(offscreenSettingTxt.c_str()), + &rc, DT_SINGLELINE | DT_LEFT | DT_BOTTOM); + + // Remember the rect for the offscreen text; clicking toggles the setting. + rcAllowOffScreenTxt = rc; +} + +LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) + { + case WM_PAINT: + { + PAINTSTRUCT ps; + OnWmPaint(hwnd, BeginPaint(hwnd, &ps)); + EndPaint(hwnd, &ps); + break; + } + + case WM_WINDOWPOSCHANGED: + { + // Repaint when the window rect changes + auto pwp = reinterpret_cast(lParam); + RECT rc = { pwp->x, pwp->y, pwp->x + pwp->cx, pwp->y + pwp->cy }; + static RECT rcWindowLast = {}; + if (!EqualRect(&rc, &rcWindowLast)) + { + rcWindowLast = rc; + InvalidateRect(hwnd, nullptr, true); + } + break; + } + + case WM_LBUTTONDOWN: + OnWmLButtonDown(hwnd, lParam); + break; + + case WM_GETMINMAXINFO: + OnWmGetMinMaxInfo(hwnd, lParam); + break; + + case WM_DPICHANGED: + { + RECT* prc = reinterpret_cast(lParam); + SetWindowPos(hwnd, + nullptr, + prc->left, + prc->top, + RECTWIDTH(*prc), + RECTHEIGHT(*prc), + SWP_NOZORDER | SWP_NOACTIVATE); + break; + } + + case WM_CHAR: + OnWmChar(hwnd, wParam); + break; + + case WM_DESTROY: + SaveLastClosePosition(hwnd); + PostQuitMessage(0); + break; + } + + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +int wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR cmdLine, int) +{ + // Run as Per-Monitor DPI Aware, or Unaware if 'u' in the command line. + SetThreadDpiAwarenessContext( + (wcsstr(cmdLine, L"u") != nullptr) ? + DPI_AWARENESS_CONTEXT_UNAWARE : + DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + + // Initialize COM, needed for virtual desktop APIs (USE_VIRTUAL_DESKTOP_APIS). + CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + + // Read settings stored in registry. + ReadUserSettings(); + + WNDCLASSEX wc = { sizeof(WNDCLASSEX) }; + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.hInstance = hInstance; + wc.lpfnWndProc = WndProc; + wc.lpszClassName = wndClassName; + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDC_SAVE)); + + // Create the window and show it. + if (!RegisterClassEx(&wc) || !CreateMainWindow(hInstance, cmdLine)) + { + return 1; + } + + // Pump messages until exit. + MSG msg; + while (GetMessage(&msg, nullptr, 0, 0)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + return 0; +} diff --git a/Samples/WindowPlacement/cpp/SaveRestoreSample/SaveRestoreSample.vcxproj b/Samples/WindowPlacement/cpp/SaveRestoreSample/SaveRestoreSample.vcxproj new file mode 100644 index 00000000..b701e6eb --- /dev/null +++ b/Samples/WindowPlacement/cpp/SaveRestoreSample/SaveRestoreSample.vcxproj @@ -0,0 +1,130 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + $(VCTargetsPath11) + + + {92E6DB91-D852-412E-BD45-2EB168A1D090} + SaveRestoreSample + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + Level3 + Disabled + ../inc;%(AdditionalIncludeDirectories) + + + true + Windows + + + + + Level3 + MaxSpeed + true + true + ../inc;%(AdditionalIncludeDirectories) + + + true + true + true + Windows + + + + + Level3 + Disabled + ../inc;%(AdditionalIncludeDirectories) + + + true + Windows + + + + + Level3 + MaxSpeed + true + true + ../inc;%(AdditionalIncludeDirectories) + + + true + true + true + Windows + + + + + + + + + \ No newline at end of file diff --git a/Samples/WindowPlacement/cpp/SaveRestoreSample/resource.h b/Samples/WindowPlacement/cpp/SaveRestoreSample/resource.h new file mode 100644 index 00000000..7361a484 --- /dev/null +++ b/Samples/WindowPlacement/cpp/SaveRestoreSample/resource.h @@ -0,0 +1,2 @@ + +#define IDC_SAVE 10101 diff --git a/Samples/WindowPlacement/cpp/SaveRestoreSample/resources.rc b/Samples/WindowPlacement/cpp/SaveRestoreSample/resources.rc new file mode 100644 index 00000000..08f5aacf --- /dev/null +++ b/Samples/WindowPlacement/cpp/SaveRestoreSample/resources.rc @@ -0,0 +1,4 @@ + +#include "resource.h" + +IDC_SAVE ICON "save.ico" diff --git a/Samples/WindowPlacement/cpp/SaveRestoreSample/save.ico b/Samples/WindowPlacement/cpp/SaveRestoreSample/save.ico new file mode 100644 index 0000000000000000000000000000000000000000..e538112df63436ca2535f78b44ce062cf8d2d2ef GIT binary patch literal 164551 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 SharedMonitorPtr; + +// TODO: Split out 'slim' version, which doesn't create a window and requires +// the user to call Update() on WM_DISPLAYCHANGE/WM_SETTINGCHANGE. + +class CurrentMonitorTopology +{ +public: + // Returns the number of monitors. + UINT NumMonitors() const + { + return m_numMonitors; + } + + // Returns the primary monitor (the one whose origin is 0, 0). + SharedMonitorPtr + GetPrimaryMonitor() + { + return MonitorFromPoint( {0, 0} ); + } + + // The topology ID is a unique ID for the monitor topology. + // + // This can be used to see if any changes have been made since some point + // in the past. + // + // Note: In GE_RELEASE+, this uses a new system API, GetCurrentMonitorTopologyId. + // On releases that have the API, this topology ID is global (shared by all + // apps in the user session). On earlier releases, this class implements a + // local counter whenever the monitor topology is refreshed. + UINT GetTopologyId() const + { + return m_lastTopologyId; + } + + // + // ForEachMonitor + // Allows the caller to do something for each monitor. + // The provided function takes a MonitorData and returns true if the walk + // should continue. + // + typedef std::function ForEachMonitorFn; + void ForEachMonitor(ForEachMonitorFn fn); + + // + // MonitorFromX functions. + // + // MonitorFromPoint + // MonitorFromRect + // MonitorFromWindow + // MonitorFromPastMonitor + // + // These functions default to 'nearest'. This means that if the point/rect + // is not on any monitor, the function falls back to the nearest monitor. + // Each function also accepts 'null' and 'primary' as fallback options. + // + + enum class Fallback + { + Null = 0, // MONITOR_DEFAULTTONULL + Primary = 1, // MONITOR_DEFAULTTOPRIMARY + Nearest = 2, // MONITOR_DEFAULTTONEAREST + }; + + SharedMonitorPtr + MonitorFromPoint( + POINT pt, + Fallback fallback = Fallback::Nearest); + + SharedMonitorPtr + MonitorFromRect( + const RECT& rc, + Fallback fallback = Fallback::Nearest); + + // A window's monitor is defined as the monitor it's RECT is mostly on. + // (If a window is not overlapping any monitor, the fallback determines if + // this returns null/primary/nearest.) + // + // IMPORTANT: This could be different from the monitor that the window is + // scaling to (for DPI). For all scaling purposes, use GetDpiForWindow, + // NOT the DPI of the MonitorFromWindow. + SharedMonitorPtr + MonitorFromWindow( + HWND hwnd, + Fallback fallback = Fallback::Nearest) + { + RECT rc; + GetWindowRect(hwnd, &rc); + return MonitorFromRect(rc, fallback); + } + + SharedMonitorPtr + MonitorFromName(PCWSTR deviceName); + + // Given a monitor from the past (copied from the current monitor topology + // at some point in the past), this finds a monitor in the current topology + // that best matches the past monitor. + SharedMonitorPtr + MonitorFromPastMonitor( + const MonitorData& previousMonitor, + Fallback fallback = Fallback::Nearest); + + // + // Topology change notification. + // + // A window can register to be notified when the topology changes. It + // provides its window handle and a message ID, and when the topology + // data is changed the window is sent this message, allowing it to respond + // to the updated topology data. + // + + void RegisterTopologyChangeMessage(HWND hwndNotify, UINT msgId); + + // + // Constructor and destructor. + // + + CurrentMonitorTopology() + { + m_validDpiAwarenesses[GetThreadDpiAwareness()] = true; + + RefreshMonitorData(false /* force */); + + m_hwndListener = CreateListenerWindow(); + } + + ~CurrentMonitorTopology() + { + DestroyWindow(m_hwndListener); + } + +private: + // Note: This function attempts to dynamically load (GetProcAddress) a + // recently-added API: + // + // user32!GetCurrentMonitorTopologyId + // + // This API returns a non-zero unique ID for the monitor topology. If any + // of the monitors change, this ID is updated. If the API is not available, + // this returns 0. + // + // Note: this API was added to user32 in GE_RELEASE but is not present in + // header files, so it must be dynamically loaded. + // https://learn.microsoft.com/en-us/windows/win32/winmsg/winuser/nf-winuser-getcurrentmonitortopologyid + static UINT GetCurrentMonitorTopologyId(); + + // Called for each monitor when syncing the monitor data with the current + // monitor topology. This queries for some additional data, stores the data + // in a MonitorData, and adds it to the list. + static BOOL + MonitorEnumProc(HMONITOR handle, HDC, PRECT prcMonitor, LPARAM pThisPtr); + + // Called when the object is created and whenever the listener window gets + // a WM_DISPLAYCHANGE or WM_SETTINGCHANGE/SPI_SETWORKAREA. + // This refreshes all monitor data to reflect the current monitor topology. + void RefreshMonitorData(bool force = false); + + // Called after refreshing the monitor data. + // Notifies all windows that have registered for topology change notifications. + void NotifyWindowsForTopologyChange(); + + // + // The listener window is a top level (but never visible) window used to + // listen for WM_DISPLAYCHANGE and WM_SETTINGCHANGE/SPI_SETWORKAREA. + // These messages are broadcast to all top level windows when the monitors + // change. + // + + static LRESULT CALLBACK + ListenerWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + + HWND CreateListenerWindow(); + + // + // The monitor topology data + // + + UINT m_lastTopologyId = 0; + UINT m_numMonitors = 0; + + // There are three monitor lists, one for each DPI awareness. If the + // monitors have >100% DPI, the values (coordinates) for each monitor are + // different depending on the thread's awareness. + // This class uses the data queried at the same awareness as the thread. + // This allows a thread to switch between awarenesses, + // SetThreadDpiAwarenessContext, and still see correct information (the + // same data that would be returned by MonitorFromRect and GetMonitorInfo). + bool m_validDpiAwarenesses[NUM_DPI_AWARENESS] = {}; + std::vector m_monitorLists[NUM_DPI_AWARENESS]; + + // The listener window. + HWND m_hwndListener = nullptr; + + // Windows that have requested topology change notifications. + // Used by RegisterTopologyChangeMessage. + struct NotifyChangeRegistration + { + HWND hwndNotify; + UINT msgId; + }; + std::vector m_notifyChangeRegistrations; +}; + +inline void +CurrentMonitorTopology::RegisterTopologyChangeMessage( + HWND hwndNotify, + UINT msgId) +{ + NotifyChangeRegistration info{}; + info.hwndNotify = hwndNotify; + info.msgId = msgId; + m_notifyChangeRegistrations.push_back(info); +} + +inline void +CurrentMonitorTopology::NotifyWindowsForTopologyChange() +{ + for (const auto& registration : m_notifyChangeRegistrations) + { + SendMessage(registration.hwndNotify, registration.msgId, 0, 0); + } +} + +inline void +CurrentMonitorTopology::ForEachMonitor(ForEachMonitorFn fn) +{ + DPI_AWARENESS awareness = GetThreadDpiAwareness(); + + // If the current thread is running at an awareness that we haven't + // seen before, make this awareness valid and refresh the monitor data + // with 'force' parameter. This forces us to reload the monitor data + // at this awareness. + if (!m_validDpiAwarenesses[awareness]) + { + m_validDpiAwarenesses[awareness] = true; + + RefreshMonitorData(true /* force */); + } + + // Call the function for each monitor in the list. + for (const auto& monitor : m_monitorLists[awareness]) + { + if (!fn(monitor)) + { + break; + } + } +} + +/* static */ +inline BOOL +CurrentMonitorTopology::MonitorEnumProc( + HMONITOR handle, + HDC, + PRECT, + LPARAM pMonListPtr) +{ + // Get the data for this monitor. + // This can fail (HMONITORs can become invalid at any time). + MonitorData monData; + if (!MonitorData::FromHandle(handle, &monData)) + { + return false; + } + + // Allocate a SharedMonitorPtr (std::shared_ptr). + SharedMonitorPtr monitorData = SharedMonitorPtr(new MonitorData(monData)); + if (!monitorData) + { + return false; + } + + // Add the shared monitor pointer to the list provided by the caller. + auto pMonitorList = reinterpret_cast*>(pMonListPtr); + pMonitorList->push_back(monitorData); + + return true; +} + +inline void +CurrentMonitorTopology::RefreshMonitorData(bool force) +{ + // There is a new API GetCurrentMonitorTopologyId, added in GE_RELEASE. + // + // We use this API if it is available to know the current topology ID. + // This makes this ID global (shared with all apps). + // + // Using the system's ID also allows us to skip unneeded work when we get + // several WM_DISPLAYCHANGE in a row. It's likely we receive the first one + // after all changes to the monitors have been made, so we can throw out + // subsequent messages by comparing the topology ID and finding it hasn't + // changed. + const UINT topologyId = GetCurrentMonitorTopologyId(); + if (topologyId) + { + // If called with force, we're being called because the thread changed + // its DPI awareness, requiring that we rebuild the monitor lists + // because the number of awarenesses we're maintaining has changed, not + // because we think the monitors have changed. + if (!force && (m_lastTopologyId == topologyId)) + { + return; + } + m_lastTopologyId = topologyId; + } + else + { + // Fallback (topology ID API not available): Increment the ID every + // time we receive a display change or setting change. + // This ID could be used by someone using this class in the same way + // that we use the API when it is available (to know if anything about + // the monitors may have changed between two points in time). + m_lastTopologyId++; + } + + bool succeeded = true; + + m_numMonitors = GetSystemMetrics(SM_CMONITORS); + + DPI_AWARENESS_CONTEXT dpiPrev = GetThreadDpiAwarenessContext(); + for (UINT i = 0; i < NUM_DPI_AWARENESS; i++) + { + // We only maintain the list for this awareness if the awareness is + // valid (if we've seen the thread running at this awareness before). + if (!m_validDpiAwarenesses[i]) + { + continue; + } + + // Switch the thread to this awareness. + SetThreadDpiAwarenessContext( + (i == 0) ? DPI_AWARENESS_CONTEXT_UNAWARE : + (i == 1) ? DPI_AWARENESS_CONTEXT_SYSTEM_AWARE : + DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + + // TODO: Build new data in a temp list and swap it with the real list + // iff successful? (avoiding ever leaving the state invalid, without + // any monitors) + + // Delete the previous monitors in the list. + m_monitorLists[i].clear(); + + // Build the monitor list. + if (!EnumDisplayMonitors(nullptr, nullptr, + MonitorEnumProc, reinterpret_cast(&m_monitorLists[i]))) + { + succeeded = false; + } + } + SetThreadDpiAwarenessContext(dpiPrev); + + // If we succeeded but the topology ID has changed since we started, the + // data we've gathered is invalid. Don't treat this as a success. We know + // in this case (and other failures) that it was caused by a mode change + // that we haven't yet been notified for (and hopefully the next time we + // try will be very soon and will succeed). + if (succeeded && + topologyId && + (topologyId != GetCurrentMonitorTopologyId())) + { + succeeded = false; + } + + // Iff the operation succeeded, notify anyone that has registered for + // topology change messages. + if (succeeded) + { + NotifyWindowsForTopologyChange(); + } +} + +inline SharedMonitorPtr +CurrentMonitorTopology::MonitorFromPoint( + POINT pt, + Fallback fallback) +{ + SharedMonitorPtr candidate = nullptr; + double nearestDistance = 0; + + ForEachMonitor([&](SharedMonitorPtr monitor) + { + RECT rc = monitor->monitorRect; + + // If the point is on this monitor, pick it. + if (PtInRect(&rc, pt)) + { + candidate = monitor; + + // Don't continue walking the monitor list. + return false; + } + + // If fallback is nearest, keep track of the nearest monitor. + if (fallback == Fallback::Nearest) + { + // Measure the distance between the center of each monitor and the + // point. + // + // Note: This is an approximation. A very wide monitor and very + // small monitor that are both close to a point would prefer the + // smaller monitor (because its center is closer to the point, + // even if the wider monitor's edge is really closer). + // + // See the MonitorFromPoint implementation (internal): + // MONITORFROMPOINTALGORITHM, onecoreuap/windows/core/ntuser/rtl/mmrtl.cxx + // https://microsoft.visualstudio.com/DefaultCollection/OS/_git/0d54b6ef-7283-444f-847a-343728d58a4d?path=%2fonecoreuap%2fwindows%2fcore%2fntuser%2frtl%2fmmrtl.cxx&version=GBofficial/ge_current_directadept_hip1&line=126&lineEnd=126&lineStartColumn=8&lineEndColumn=34&lineStyle=plain + + POINT ptCenter = { + rc.left + (RECTWIDTH(rc) / 2), + rc.top + (RECTHEIGHT(rc) / 2) }; + + double distanceSquared = + std::pow(pt.x - ptCenter.x, 2) + std::pow(pt.y - ptCenter.y, 2); + + if (!candidate || (nearestDistance > distanceSquared)) + { + candidate = monitor; + nearestDistance = distanceSquared; + } + } + + // Keep walking the monitor list. + return true; + }); + + if (candidate) + { + return candidate; + } + + switch (fallback) + { + case Fallback::Nearest: + // Assert(false) + // We're guarenteed >1 monitors and we should have picked some + // monitor as the nearest (candidate). + + case Fallback::Primary: + return GetPrimaryMonitor(); + + default: /* Fallback::Null */ + return nullptr; + } +} + +inline SharedMonitorPtr +CurrentMonitorTopology::MonitorFromRect( + const RECT& rc, + Fallback fallback) +{ + SharedMonitorPtr candidate = nullptr; + UINT candidateArea = 0; + const UINT rectArea = RECTWIDTH(rc) * RECTHEIGHT(rc); + + // Intersect the rect with each monitor rect, looking for the monitor + // that intersects with the rect the most. + ForEachMonitor([&](SharedMonitorPtr monitor) + { + RECT rcI; + if (IntersectRect(&rcI, &monitor->monitorRect, &rc)) + { + UINT area = RECTWIDTH(rcI) * RECTHEIGHT(rcI); + + // If the rect is entirely on this monitor, set the result and + // stop the search. + if (area == rectArea) + { + candidate = monitor; + return false; + } + + // If this monitor intersects with the rect more than the current + // candidate, set this monitor as the candidate (but continue + // walking the monitor list). + if (area > candidateArea) + { + candidate = monitor; + candidateArea = area; + } + } + + // Keep walking the monitor list. + return true; + }); + + if (candidate) + { + return candidate; + } + + switch (fallback) + { + case Fallback::Nearest: + // Return the monitor that is closest to the center of the rect. + // + // TODO: This is an approximation. It may not be the best (closest) + // monitor to the point. We could consider improving this. + // + // See MonitorFromRect implementation (internal): + // MONITORFROMRECTALGORITHM, onecoreuap/windows/core/ntuser/rtl/mmrtl.cxx + // https://microsoft.visualstudio.com/DefaultCollection/OS/_git/0d54b6ef-7283-444f-847a-343728d58a4d?path=%2fonecoreuap%2fwindows%2fcore%2fntuser%2frtl%2fmmrtl.cxx&version=GBofficial/ge_current_directadept_hip1&line=371&lineEnd=371&lineStartColumn=8&lineEndColumn=33&lineStyle=plain + return MonitorFromPoint({ + rc.left + (RECTWIDTH(rc) / 2), + rc.top + (RECTHEIGHT(rc) / 2) + }); + + case Fallback::Primary: + return GetPrimaryMonitor(); + + default: /* Fallback::Null */ + return nullptr; + } +} + +inline SharedMonitorPtr +CurrentMonitorTopology::MonitorFromName(PCWSTR deviceName) +{ + SharedMonitorPtr result = nullptr; + + ForEachMonitor([&](SharedMonitorPtr monitor) + { + if (monitor->MatchesDeviceName(deviceName)) + { + result = monitor; + return false; + } + + // Keep walking the monitor list. + return true; + }); + + return result; +} + +inline SharedMonitorPtr +CurrentMonitorTopology::MonitorFromPastMonitor( + const MonitorData& previousMonitor, + Fallback fallback) +{ + // If any monitor has a matching display name, use that monitor. + // Consider the case where the primary monitor changes. One monitor's + // handle, rect, etc, may have changed, but still be around. The most + // stable thing we know about the monitor is the device name string. + + SharedMonitorPtr result = MonitorFromName(previousMonitor.deviceName); + + if (result) + { + return result; + } + + switch (fallback) + { + case Fallback::Nearest: + // Return the monitor nearest to the previous monitor rect. + return MonitorFromRect(previousMonitor.monitorRect, fallback); + + case Fallback::Primary: + return GetPrimaryMonitor(); + + default: /* Fallback::Null */ + return nullptr; + } +} + +/* static */ +inline UINT +CurrentMonitorTopology::GetCurrentMonitorTopologyId() +{ + using pfnType = UINT(*)(); + static pfnType pfnGetTopologyId = nullptr; + static bool loadedApi = false; + + // This public API is not in header files; load it dynamically. + // https://learn.microsoft.com/en-us/windows/win32/winmsg/winuser/nf-winuser-getcurrentmonitortopologyid + if (!loadedApi) + { + loadedApi = true; + HMODULE hmod = LoadLibraryA("user32.dll"); + if (hmod) + { + pfnGetTopologyId = reinterpret_cast( + GetProcAddress(hmod, "GetCurrentMonitorTopologyId")); + } + } + + if (pfnGetTopologyId) + { + return pfnGetTopologyId(); + } + + return 0; +} + +/* static */ +inline LRESULT CALLBACK +CurrentMonitorTopology::ListenerWndProc( + HWND hwnd, + UINT msg, + WPARAM wParam, + LPARAM lParam) +{ + switch (msg) + { + case WM_CREATE: + { + // Store the instance pointer (CreateWindow params) in the window. + SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast( + reinterpret_cast(lParam)->lpCreateParams)); + break; + } + + // Most changes to the monitors will broadcast WM_DISPLAYCHANGE. + // But, apps (or the taskbar) can change the work area directly, which + // isn't considered a display change. To respond to these changes as + // well, also listen for WM_SETTINGCHANGE/SPI_SETWORKAREA. + case WM_SETTINGCHANGE: + if (wParam != SPI_SETWORKAREA) + { + break; + } + case WM_DISPLAYCHANGE: + { + // Get the instance pointer, stored on the window. + CurrentMonitorTopology* instance = + reinterpret_cast( + GetWindowLongPtrW(hwnd, GWLP_USERDATA)); + + // Refresh the monitor data. + if (instance) + { + instance->RefreshMonitorData(); + } + + break; + } + } + + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +inline HWND +CurrentMonitorTopology::CreateListenerWindow() +{ + PCWSTR windowTitle = L"TrackMonitorsListenerWindow"; + PCWSTR wndClassName = L"TrackMonitorsListenerWindowClass"; + HINSTANCE hInst = GetModuleHandle(NULL); + + static bool registered = false; + if (!registered) + { + WNDCLASSEX wc = { sizeof(WNDCLASSEX) }; + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc = ListenerWndProc; + wc.hInstance = hInst; + wc.lpszClassName = wndClassName; + + if (!RegisterClassEx(&wc)) + { + return nullptr; + } + + registered = true; + } + + // The listener window is mostly blank (no styles, position, etc), + // but it has the topmost flag, WS_EX_TOPMOST. This moves the window + // to the top, in Z-order (it is above other non-topmost windows). + // + // Broadcasted messages are received in Z-order, windows on top first. + // If another window on this thread is listening for the same messages + // and queries the monitor data from those messages, we want this + // window to receive these messages first. Otherwise, the other window + // could be left with stale monitor topology info. + // + // Note: RegisterTopologyChangeMessage allows a window to register for + // updates from this class. Using that is strictly better than listening + // to the broadcasted messages (but we make ourselves topmost anyway to + // hopefully account for that usage). + + return CreateWindowEx(WS_EX_TOPMOST, + wndClassName, + windowTitle, + 0, + 0, 0, 0, 0, + nullptr, + nullptr, + hInst, + reinterpret_cast(this)); +} diff --git a/Samples/WindowPlacement/cpp/inc/MiscUser32.h b/Samples/WindowPlacement/cpp/inc/MiscUser32.h new file mode 100644 index 00000000..6b01febc --- /dev/null +++ b/Samples/WindowPlacement/cpp/inc/MiscUser32.h @@ -0,0 +1,209 @@ +#pragma once +// Note: Intended to be included by User32Utils.h. + +inline LONG RECTWIDTH(const RECT& rc) +{ + return (rc.right - rc.left); +} + +inline LONG RECTHEIGHT(const RECT& rc) +{ + return (rc.bottom - rc.top); +} + +constexpr UINT NUM_DPI_AWARENESS = 3; + +// Returns true if the window is a top-level window (parented to the desktop window). +inline bool IsTopLevel(HWND hwnd) +{ + return (GetAncestor(hwnd, GA_PARENT) == GetDesktopWindow()); +} + +// Returns the DPI the thread is virtualized to, or 0 if the thread is not +// virtualized (Per-Monitor DPI Aware). +inline UINT GetThreadVirtualizedDpi() +{ + return GetDpiFromDpiAwarenessContext(GetThreadDpiAwarenessContext()); +} + +// Returns one of: +// - DPI_AWARENESS_UNAWARE +// Virtualized to 100% DPI +// - DPI_AWARENESS_SYSTEM_AWARE +// Virtualized to an arbitrary DPI, the DPI of the primary monitor +// when the process started. +// - DPI_AWARENESS_PER_MONITOR_AWARE +// Not virtualized. Expected to scale to the current DPI of the monitor. +inline DPI_AWARENESS GetThreadDpiAwareness() +{ + return GetAwarenessFromDpiAwarenessContext(GetThreadDpiAwarenessContext()); +} + +// +// Cloaking +// + +inline bool IsCloaked(HWND hwnd) +{ + DWORD dwCloak = 0; + DwmGetWindowAttribute(hwnd, DWMWA_CLOAKED, &dwCloak, sizeof(dwCloak)); + return dwCloak != 0; +} + +inline bool IsShellCloaked(HWND hwnd) +{ + DWORD dwCloak = 0; + DwmGetWindowAttribute(hwnd, DWMWA_CLOAKED, &dwCloak, sizeof(dwCloak)); + return WI_IsFlagSet(dwCloak, DWM_CLOAKED_SHELL); +} + +inline void CloakWindow(HWND hwnd, BOOL cloak = TRUE) +{ + DwmSetWindowAttribute(hwnd, DWMWA_CLOAK, &cloak, sizeof(cloak)); +} + +inline void UnCloakWindow(HWND hwnd) +{ + CloakWindow(hwnd, false /* cloak */); +} + +// Cloaking a window temporarily allows it to be moved multiple times, +// Maximized, etc without flashing or animating from unexpected locations. +class TempCloakWindowIf +{ + HWND _hwnd = nullptr; +public: + TempCloakWindowIf(HWND hwnd, bool shouldHide = true) + { + if (shouldHide && !IsCloaked(hwnd)) + { + CloakWindow(hwnd); + _hwnd = hwnd; + } + } + ~TempCloakWindowIf() + { + if (_hwnd) + { + UnCloakWindow(_hwnd); + } + } +}; + +// Note: 'Margins' below refers to invisible resize borders. +// +// This uses DWMWA_EXTENDED_FRAME_BOUNDS (the visible bounds) and GetWindowRect +// (the real/input bounds). The margins is the difference between these rects. +// +// DWMWA_EXTENDED_FRAME_BOUNDS returns PHYSICAL values, even if the caller is +// virtualized for DPI. GetWindowRect (and most other APIs) return logical +// coordinates. To allow using the margins from DPI virtualized apps, this +// switches explicitly to Per-Monitor DPI Aware (Physical) to call GetWindowRect +// and scales the values from the monitor DPI to the logical DPI, if needed. +inline RECT GetWindowMargins(HWND hwnd) +{ + DPI_AWARENESS_CONTEXT prev = SetThreadDpiAwarenessContext( + DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + + RECT margins{}; + + RECT rcWindow, rcFrame; + if (GetWindowRect(hwnd, &rcWindow) && + SUCCEEDED(DwmGetWindowAttribute( + hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &rcFrame, sizeof(rcFrame)))) + { + margins.left = rcFrame.left - rcWindow.left; + margins.top = rcFrame.top - rcWindow.top; + margins.right = rcWindow.right - rcFrame.right; + margins.bottom = rcWindow.bottom - rcFrame.bottom; + } + + MonitorData mon; + if ((GetAwarenessFromDpiAwarenessContext(prev) != DPI_AWARENESS_PER_MONITOR_AWARE) && + (MonitorData::FromRect(rcWindow, &mon))) + { + UINT monitorDpi = mon.dpi; + UINT threadDpi = GetDpiFromDpiAwarenessContext(prev); + + margins.left = MulDiv(margins.left, threadDpi, monitorDpi); + margins.top = MulDiv(margins.top, threadDpi, monitorDpi); + margins.right = MulDiv(margins.right, threadDpi, monitorDpi); + margins.bottom = MulDiv(margins.bottom, threadDpi, monitorDpi); + } + + SetThreadDpiAwarenessContext(prev); + + return margins; +} + +inline void ExtendByMargins(RECT* rc, const RECT& margins) +{ + rc->left -= margins.left; + rc->top -= margins.top; + rc->right += margins.right; + rc->bottom += margins.bottom; +} + +inline void ReduceByMargins(RECT* rc, const RECT& margins) +{ + rc->left += margins.left; + rc->top += margins.top; + rc->right -= margins.right; + rc->bottom -= margins.bottom; +} + +// Gets a windows 'extended frame bounds'. +// +// This is the visible bounds of the window, not including the invisible resize +// area. +// +// Note: This doesn't call DWMWA_EXTENDED_FRAME_BOUNDS directly, because it +// does not work in DPI virtualized apps. (The result canot always be compared +// to GetWindowRect.) GetWindowMargins determines logical margins and this +// reduces the result of GetWindowRect by that amount, 'logical extended frame +// bounds'. +inline bool DwmGetExtendedFrameBounds( + HWND hwnd, + _Out_ PRECT prcFrame) +{ + if (!GetWindowRect(hwnd, prcFrame)) + { + return false; + } + + ReduceByMargins(prcFrame, GetWindowMargins(hwnd)); + + return true; +} + +// Returns true if the user setting for snapping is enabled. +// Settings, system, multitasking, 'snap windows'. +inline bool IsSnappingEnabled() +{ + BOOL snapEnabled = FALSE; + return SystemParametersInfo(SPI_GETWINARRANGING, 0, &snapEnabled, 0) && snapEnabled; +} + +// Wrapper that dynamically loads the API user32!IsWindowArranged. +// +// There is currently no header file for this function, so load it +// dynamically per the documentation. +// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-iswindowarranged +inline bool IsWindowArrangedWrapper(HWND hwnd) +{ + using PFNISWINDOWARRANGED = BOOL(*)(HWND hwnd); + static PFNISWINDOWARRANGED fnIsWindowArranged = nullptr; + static bool doOnce = false; + if (!doOnce) + { + doOnce = true; + HMODULE hmodUser = LoadLibraryW(L"user32.dll"); + fnIsWindowArranged = reinterpret_cast( + GetProcAddress(hmodUser, "IsWindowArranged")); + } + if (fnIsWindowArranged) + { + return !!fnIsWindowArranged(hwnd); + } + return false; +} diff --git a/Samples/WindowPlacement/cpp/inc/MonitorData.h b/Samples/WindowPlacement/cpp/inc/MonitorData.h new file mode 100644 index 00000000..15ecf91e --- /dev/null +++ b/Samples/WindowPlacement/cpp/inc/MonitorData.h @@ -0,0 +1,224 @@ +#pragma once +// Note: Intended to be included by User32Utils.h. + +// +// MonitorData +// +// A MonitorData has information about a monitor (HMONITOR). This is similar +// to the MONITORINFOEX struct, but it also includes the DPI. +// + +class MonitorData +{ +public: + // These helpers use MonitorFrom(Point/Rect/Window) to pick the nearest + // monitor, and returns a MonitorData with info about that monitor. + // Note: HMONITORs can become invalid at any time. Callers are expected + // to check the return value and handle failure. + static bool FromHandle(HMONITOR monitorHandle, _Out_ MonitorData* monitorData) noexcept; + static bool FromPoint(POINT pt, _Out_ MonitorData* monitorData) noexcept; + static bool FromRect(RECT rc, _Out_ MonitorData* monitorData) noexcept; + static bool FromWindow(HWND hwnd, _Out_ MonitorData* monitorData) noexcept; + static bool FromDeviceName(PCWSTR deviceName, _Out_ MonitorData* monitorData) noexcept; + + // Compare two monitors to see if they are different in ANY way. + // The MonitorData is information about a monitor at a point in time. If + // the monitor changes (for example resolution or DPI changes, but handle + // and name do not), this will detect that the monitors are NOT equal. + bool operator==(const MonitorData& otherMonitor) const noexcept + { + return Equals(otherMonitor); + } + bool Equals(const MonitorData& otherMonitor) const noexcept; + + // Compares only the device name (the string) for two monitors. This is + // used when finding a monitor that is 'best' to use, given a monitor from + // the past. (If the monitor from the past changed, it will likely have the + // same device name, but other fields have changed.) + bool MatchesDeviceName(PCWSTR otherDeviceName) const noexcept; + +private: + static BOOL MonitorEnumProc(HMONITOR, HDC, PRECT, LPARAM); + +public: + // The HMONITOR is the system's handle for this monitor, which can be + // used by APIs that take a monitor, like GetMonitorInfo. + // WARNING: This handle can become invalid at any time! It is best to read + // all needed data about a monitor before making any changes, and not + // assuming that the handle is still valid later. + HMONITOR handle = nullptr; + + // The monitor rect is the position/size of the monitor. These values are + // in screen coordinates, where the primary monitor origin is 0, 0. + RECT monitorRect = {}; + + // The work area is a subset of the monitor rect. This is the part of the + // monitor that is not covered by the taskbar (or taskbar-like apps). + // Non-topmost windows should generally stay within the work area of their + // monitor. + RECT workArea = {}; + + // The DPI is used for calculating the monitor's scale factor (the density + // of the pixels that the window's output is being displayed on). All UI + // drawn by an app should be scaled by the DPI scale factor: + // + // If the logical height of a button is 25, and the DPI is 192 (200%), + // the physical height of the button is 50: MulDiv(25, 192, 96). + // + // WARNING: While it is often the case that a window's DPI matches the DPI + // of the monitor it is mostly on, this is NOT always true! Apps should use + // GetDpiForWindow in most cases to know what scale factor a window should + // be using, or listen for WM_DPICHANGED to know when the DPI changes after + // the window is created. Apps should NOT assume that finding the DPI of the + // monitor that the window position is currently on will be the same. (In + // cases where the monitors change, or when a window is dragged between + // monitors, there are times when these two DPIs disagree, and using the + // monitor DPI can cause the window to get stuck at the wrong DPI!) + UINT dpi = 0; + + // The device name is a string representing the monitor. In cases where a + // monitor's position, handle, and other fields change (like changing + // primary monitor), the device id remains stable. + WCHAR deviceName[CCHDEVICENAME] = {}; +}; + +/* static */ +inline bool +MonitorData::FromHandle(HMONITOR monitorHandle, _Out_ MonitorData* monitorData) noexcept +{ + MONITORINFOEX mi = { sizeof(mi) }; + // Note: GetDpiForMonitor returns x/y DPI but these values are always equal. + UINT _dpi, unused; + + if (!GetMonitorInfo(monitorHandle, &mi) || + (GetDpiForMonitor(monitorHandle, MDT_EFFECTIVE_DPI, &_dpi, &unused) != S_OK)) + { + return false; + } + + monitorData->handle = monitorHandle; + monitorData->monitorRect = mi.rcMonitor; + monitorData->workArea = mi.rcWork; + monitorData->dpi = _dpi; + + if (FAILED(StringCchCopy( + monitorData->deviceName, ARRAYSIZE(monitorData->deviceName), mi.szDevice))) + { + return false; + } + + return true; +} + +/* static */ +inline bool +MonitorData::FromPoint(POINT pt, _Out_ MonitorData* monitorData) noexcept +{ + return FromHandle(MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST), monitorData); +} + +/* static */ +inline bool +MonitorData::FromRect(RECT rc, _Out_ MonitorData* monitorData) noexcept +{ + return FromHandle(MonitorFromRect(&rc, MONITOR_DEFAULTTONEAREST), monitorData); +} + +/* static */ +inline bool +MonitorData::FromWindow(HWND hwnd, _Out_ MonitorData* monitorData) noexcept +{ + return FromHandle(MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST), monitorData); +} + +struct DeviceNameSearchData +{ + PCWSTR deviceName; + MonitorData result; + bool resultSet = false; +}; + +/* static */ +inline BOOL +MonitorData::MonitorEnumProc( + HMONITOR handle, + HDC, + PRECT, + LPARAM lParam) +{ + DeviceNameSearchData* data = reinterpret_cast(lParam); + + MonitorData monitor; + if (FromHandle(handle, &monitor)) + { + if (monitor.MatchesDeviceName(data->deviceName)) + { + data->result = monitor; + data->resultSet = true; + return FALSE; + } + } + + return TRUE; +} + +/* static */ +inline bool +MonitorData::FromDeviceName( + PCWSTR deviceName, + _Out_ MonitorData* monitorData) noexcept +{ + DeviceNameSearchData data; + data.deviceName = deviceName; + + EnumDisplayMonitors(nullptr, nullptr, + MonitorEnumProc, reinterpret_cast(&data)); + + if (data.resultSet) + { + *monitorData = data.result; + return true; + } + + return false; +} + +inline bool +MonitorData::MatchesDeviceName(PCWSTR otherDeviceName) const noexcept +{ + return (wcscmp(deviceName, otherDeviceName) == 0); +} + +inline bool +MonitorData::Equals(const MonitorData& otherMonitor) const noexcept +{ + // If provided a reference to the same address as this monitor, we + // know all of the fields are the same (we don't need to check). + if (this == &otherMonitor) + { + return true; + } + + if (dpi != otherMonitor.dpi) + { + return false; + } + + if (!EqualRect(&monitorRect, &otherMonitor.monitorRect)) + { + return false; + } + + if (!EqualRect(&workArea, &otherMonitor.workArea)) + { + return false; + } + + if (!MatchesDeviceName(otherMonitor.deviceName)) + { + return false; + } + + // All fields match; the provided monitor data is equivalent to this one. + return true; +} diff --git a/Samples/WindowPlacement/cpp/inc/PlacementEx.h b/Samples/WindowPlacement/cpp/inc/PlacementEx.h new file mode 100644 index 00000000..3b212ee0 --- /dev/null +++ b/Samples/WindowPlacement/cpp/inc/PlacementEx.h @@ -0,0 +1,1596 @@ +#pragma once +// Note: Intended to be included by User32Utils.h. + +// +// PlacementEx +// +// This stores a top-level window's position and monitor info. +// +// GetPlacement stores a window's position into the provided PlacementEx, and +// SetPlacement moves a window to this stored position, adjusting it as needed +// to fit the best monitor. +// +// This also has helpers that do other operations related to storing window +// positions, such as FullScreen, Cascading, StartupInfo, etc. +// +// See the readme.md in the same directory as this file more information. +// + +// Internal flags used to track window state between launches +enum class PlacementFlags +{ + None = 0x0000, + + RestoreToMaximized = 0x0001, // WPF_RESTORETOMAXIMIZED + // Valid only if minimized (SW_MINIMIZE). + // When restored from minimized, the + // window will be maximized (restoring + // it again will move it to the normal + // position). + + Arranged = 0x0002, // Window is arranged (snapped). + // Set by GetPlacement if IsWindowArranged. + // + // This is used by SetPlacement if not + // maximizing or minimizing, and if the + // user setting for snapping is enabled. + // + // Like maximized/minimized windows, + // arranged windows have separate normal + // positions and 'real' position. Calling + // ShowWindow SW_RESTORE will move the + // window back to its normal position. + // + // The arrange position is stored in + // the arrangeRect field. This is the + // visible bounds of the window, + // GetWindowRect, reduced by the size of + // the window's invisible resize borders. + + AllowPartiallyOffScreen = 0x0004, // Skips forcing the normal position + // within the bounds of the work area, + // which SetPlacement does by default. + + AllowSizing = 0x0008, // Set (by GetPlacement) if window has + // WS_THICKFRAME. + // In SetPlacement, if the normal rect + // becomes larger than the work area, for + // example if a wide window is moved to a + // small monitor. + // When this happens, the size of the + // window is picked using the difference + // in monitor sizes, instead of respecting + // the logical size of the window. + + KeepHidden = 0x0010, // Any show command other than SW_HIDE + // will show the window. The KeepHidden + // flag will cause the window to stay + // hidden if it is not already visible. + + RestoreToArranged = 0x0020, // Like RestoreToMaximized, but restores + // arranged. + // Like Arranged flag, this flag indicates + // the arrangeRect field is set to the + // visible bounds (the arrange rect, + // aligned with the work area). + // Valid only if minimized (SW_MINIMIZE). + + FullScreen = 0x0040, // A FullScreen window fills the monitor + // rect, and has no caption (WS_CAPTION) + // or resize borders (WS_THICKFRAME). + // To Enter/Exit FullScreen, the app must + // remember the previous position (unlike + // Maximize/Minimize, where the system + // remembers this position). PlacementEx + // handles this scenario. + + VirtualDesktopId = 0x0080, // The virtualDesktopId field is set. + // This is a GUID used with the virtual + // desktop APIs. + // Apps should only restore windows to a + // past virtual desktop if restarting, + // either because of a session reboot or + // app re-launch. + // (Default app launch should not launch + // to a background desktop; similar to + // launching minimized.) + // Note: Virtual desktops require defining + // USE_VIRTUAL_DESKTOP_APIS prior to including + // User32Utils.h. See VirtualDesktopIds.h. + + NoActivate = 0x0100, // Don't activate the window. + // This is implied if VirtualDesktopId. +}; +DEFINE_ENUM_FLAG_OPERATORS(PlacementFlags); + +// Flags to adjust how PlacementEx uses STARTUPINFO +enum class StartupInfoFlags +{ + ShowCommand = 0x0001, // Heed the show command set by the command + // line: + // cmd: 'start /max ' + // pwsh: 'start -WindowStyle Maximized' + // This allows the user to launch an app + // Maximized or Minimized. + + MonitorHint = 0x0002, // Heed the monitor hint set by the taskbar, + // start menu, desktop icons, file explorer, + // etc. It indicates which monitor the app + // should launch on. + + None = 0, + + // [Default] Heed both ShowCommand and MonitorHint. This matches the + // default behavior of CreateWindow. + All = ShowCommand | MonitorHint, +}; +DEFINE_ENUM_FLAG_OPERATORS(StartupInfoFlags); + +class PlacementEx +{ +public: + // Stores a window's position/monitor data in a PlacementEx. + static bool GetPlacement( + HWND hwnd, + _Out_ PlacementEx* placement); + + // Moves a window to a position stored in a PlacementEx. + static bool SetPlacement(HWND hwnd, PlacementEx* placement); + + // Stores a placement in the registry. + void StoreInRegistry( + PCWSTR registryPath, + PCWSTR registryKeyName); + + static void StorePlacementInRegistry( + HWND hwnd, + PCWSTR registryPath, + PCWSTR registryKeyName); + + // FullScreen + // + // A FullScreen window is one that fills its monitor, and has some styles + // removed (no WS_CAPTION or WS_THICKFRAME). When entering FullScreen, the + // previous window position is stored. When exiting FullScreen, this position + // is moved to the window's monitor (which may have changed) and used to + // move the window to it's previous position. + bool EnterFullScreen( + HWND hwnd, + LONG_PTR styles = WS_CAPTION | WS_THICKFRAME); + + bool ExitFullScreen( + HWND hwnd, + LONG_PTR styles = WS_CAPTION | WS_THICKFRAME); + + bool IsFullScreen() const + { + return HasFlag(PlacementFlags::FullScreen); + } + + bool ToggleFullScreen( + HWND hwnd, + LONG_PTR styles = WS_CAPTION | WS_THICKFRAME) + { + if (IsFullScreen()) + { + return ExitFullScreen(hwnd, styles); + } + + return EnterFullScreen(hwnd, styles); + } + + // Sets the size. + // The provided size is logical, and scaled up by the DPI stored in the + // placement. The final position is adjusted to stay entirely within the + // work area. + void SetLogicalSize(SIZE size); + + // Fits the placement to a different monitor. + // This changes the normal rect, work area, and dpi fields. + // By default (unless AllowPartiallyOffScreen), the final normal rect will + // be entirely within the bounds of the provided monitor's work area. + void MoveToMonitor(const MonitorData& targetMonitor); + + void MoveToWindowMonitor(HWND hwnd) + { + MonitorData monitor; + if (MonitorData::FromWindow(hwnd, &monitor)) + { + MoveToMonitor(monitor); + } + } + + // Finds the monitor that best matches the position stored in the placement. + // This uses the device name (a string) to match monitors, and falls back to + // closest monitor (MonitorFromRect). + bool FindClosestMonitor(_Out_ MonitorData* monitor) const; + + // Moves the position down/right by roughly the height of the title bar. + // This is done when launching two windows to the same place. Rather than + // have both windows in the exact same place (one covering the other), + // an app can choose to cascade the windows, ensuring the user can see both + // window's title bars. Note that this function doesn't check that scenario: + // it always moves the window. + void Cascade(); + + // Updates a placement to account for flags set in the STARTUPINFO. + // If no startup info is provided, this uses GetStartupInfo. + // These are flags set when launching an application. For example, they + // can request the app launch Maximized/Minimized, or on a certain monitor. + void AdjustForStartupInfo( + _In_opt_ STARTUPINFO* psi = nullptr, + StartupInfoFlags siFlags = StartupInfoFlags::All); + + // AdjustForStartupInfo and also modify the placement for use as a 'main + // window' (normal launch). When launching the main window normally, it + // should not be minimized or on a background virtual desktop. + void AdjustForMainWindow( + _In_opt_ STARTUPINFO* psi = nullptr, + StartupInfoFlags siFlags = StartupInfoFlags::All); + + // Sets the show command (SW_ value) for this placement. + // There are a few special cases, including 'restore to maximize' (if + // changing the placement from maximize to minimize, this sets the + // RestoreToMaximized placement flag). + void SetShowCommand(UINT cmdFromStartupIfo); + + void RestoreIfMinimized() + { + if (IsMinimizeShowCmd(showCmd)) + { + SetShowCommand(SW_NORMAL); + } + } + + // Returns true if the provided show command is minimize/restore. + // (There are several SW_ values for each of these, for example + // SW_SHOWMINNOACTIVE, SW_SHOWMINIMIZED, SW_MINIMIZE.) + static bool IsMinimizeShowCmd(UINT cmd); + static bool IsRestoreShowCmd(UINT cmd); + + // Helper to move a RECT as needed to be fully within a work area. + static RECT KeepRectOnMonitor( + const RECT& rcPrev, + const RECT & workArea); + + // Helper to move a normal position from one monitor to another. + void AdjustNormalRect(const RECT& rcWorkNew, UINT dpiNew); + + // Given an arrangement rect stored from one monitor (work area), adjusts + // the rect to stay the same relative position (like top-left corner) on + // a different monitor's work area. + void AdjustArrangeRect(const RECT rcWorkNew); + + // Adds (or Removes) some window styles. + static void AddRemoveWindowStyles( + HWND hwnd, + bool add, + LONG_PTR stylesToChange); + +#ifdef USE_WINDOW_ACTION_APIS + // If available, SetPlacement is implemented mostly using the new API + // ApplyWindowAction (in WindowActions.h). + // https://learn.microsoft.com/en-us/windows/win32/winmsg/winuser/nf-winuser-applywindowaction + static bool ApplyPlacementExAsAction( + HWND hwnd, + PlacementEx* placement); +#endif + + // ToString() and FromString() allow you to store a placement in a string, + // for example to write it to the registry. + std::wstring ToString() const; + + static std::optional FromString( + const std::wstring& placementString); + + static bool FromString( + const std::wstring& placementString, + _Out_ PlacementEx* placement) + { + std::optional plex = FromString(placementString); + if (plex) + { + *placement = plex.value(); + return true; + } + return false; + } + + static bool FromRegistryKey( + PCWSTR registryPath, + PCWSTR registryKeyName, + _Out_ PlacementEx* placement); + + // Checks if a PlacementEx is valid (filled in by GetPlacement). + bool IsValid() const; + + // Zeros out a PlacementEx, making it invalid. + void Clear(); + + bool HasFlag(PlacementFlags pf) const + { + return (flags & pf) == pf; + } + + // Position (in screen coordinates) of the window. + // For Maximized/Minimized windows this rect is the restore position. + RECT normalRect = {}; + + // The work area of the monitor (in screen coordinates). + RECT workArea = {}; + + // The (potentially virtualized) DPI of the window. If the window is DPI- + // aware, returns the actual DPI of the window. Otherwise returns the + // virtualized DPI that the app sees before the system stretches it to the + // monitor's actual DPI. + // + // For DPI-aware windows, scale factor can be calculated by dividing by 96 + // (e.g. 120 DPI would be a scale factor of 120/96 = 125%). These windows + // must manually scale their content by this scale factor, or content will + // be rendered too small on high-DPI screens. + UINT dpi = 0; + + // The 'Show Command' is a value accepted by ShowWindow(). + // This defines the window's state (SW_MAXIMIZE, SW_MINIMIZE, SW_NORMAL). + UINT showCmd = 0; + + // The arrange rect is used only when the flag Arranged is set. + // + // This rect is the 'frame bounds' (visible bounds) of the window when it + // is snapped (arranged). This rect does not include the invisible resize + // borders (it is the rect that is expected to be aligned with the monitor + // edges). + // + // Like maximized/minimized windows, arranged windows have two positions, + // their normal rect (GetWindowPlacement) and their current position. For + // maximized/minimized, this positions are always re-evaluated for the + // monitor automatically, so we don't need to store the min/max positions. + // But we DO need to store the arranged positions, for example to remember + // that a window was snapped to the left half of its monitor (or top-left + // corner, etc). + RECT arrangeRect = {}; + + // Additional flags. + PlacementFlags flags = PlacementFlags::None; + + // The device name is a string representing the monitor (also 'device ID'). + // In cases like changing the primary monitor, the handle, rect, etc, all + // may change but the device name would remain stable. + WCHAR deviceName[CCHDEVICENAME] = {}; + +#ifdef USE_VIRTUAL_DESKTOP_APIS + GUID virtualDesktopId = {}; +#endif +}; + +// A PlacementEx is created zero initialized, which is invalid. +// +// A valid placement has: +// - a non-zero size normal position +// - a non-zero size work area +// - a normal position that intersects with the work area +// - a DPI that is >=96 (100%, the minimum supported DPI) +inline bool +PlacementEx::IsValid() const +{ + RECT rcI; + return (dpi >= 96) && + !IsRectEmpty(&normalRect) && + !IsRectEmpty(&workArea) && + IntersectRect(&rcI, &normalRect, &workArea); +} + +inline void +PlacementEx::Clear() +{ + dpi = 0; + showCmd = 0; + flags = PlacementFlags::None; + normalRect = {}; + arrangeRect = {}; + workArea = {}; + deviceName[0] = 0; +} + +/* static */ +inline bool +PlacementEx::GetPlacement( + HWND hwnd, + _Out_ PlacementEx* placement) +{ + placement->Clear(); + + // PlacementEx is for top-level windows only (parented to the Desktop Window). + // For other windows, child or message windows, you should call SetWindowPos + // directly. + if (!IsTopLevel(hwnd)) + { + return false; + } + + // Call GetWindowPlacement to get the window's show state (max/min/normal) + // and the normal position. + // Note: If the window is arranged (snapped) this returns 'normal' as the + // state but the normal position is the restore position (not where the + // window is, but where it will go if restored from arrange). + WINDOWPLACEMENT wp = { sizeof(wp) }; + if (!GetWindowPlacement(hwnd, &wp)) + { + return false; + } + + // Get the monitor info for the window's current monitor. + MonitorData monitorData; + if (!MonitorData::FromWindow(hwnd, &monitorData)) + { + return false; + } + + PlacementFlags flags = PlacementFlags::None; + RECT rcWork = monitorData.workArea; + RECT rcMonitor = monitorData.monitorRect; + RECT rcNormal = wp.rcNormalPosition; + RECT rcArrange{}; + + // Offset the normal rect by the work area offset from the monitor rect, + // aka 'Workspace Coordinates'. (PlacementEx doesn't use workspace + // coordinates, but Get/SetWindowPlacement do). + OffsetRect(&rcNormal, + rcWork.left - rcMonitor.left, rcWork.top - rcMonitor.top); + + // Set RestoreToMaximize flag if window is minimized and GetWindowPlacement + // set the WPF_RESTORETOMAXIMIZED flag. + // Note: ASYNC flag is never expected here, and the min position is ignored. + if (IsMinimizeShowCmd(wp.showCmd) && + (WI_IsFlagSet(wp.flags, WPF_RESTORETOMAXIMIZED))) + { + WI_SetFlag(flags, PlacementFlags::RestoreToMaximized); + } + + // The DPI in the PlacementEx is the window's DPI, GetDpiForWindow. + // But, if the current thread is virtualized for DPI, we instead use the + // thread's DPI. This could be different from the DPI that the window + // is running at (which is what GetDpiForwindow returns). The other APIs + // we're calling, querying the window and monitor info, are using the + // thread DPI. + const UINT threadDpi = GetThreadVirtualizedDpi(); + const UINT dpi = (threadDpi == 0) ? GetDpiForWindow(hwnd) : threadDpi; + + // Set the arranged flag and arranged ret if the window is arranged. + if (IsWindowArrangedWrapper(hwnd)) + { + // The arranged rect is the visible bounds of the window. + // This does not include the invisible resize borders (if the window + // has any). + // Note: This helper is in MiscUser32.h, and calls DwmGetWindowAttribute + // (DWMWA_EXTENDED_FRAME_BOUNDS) with some adjustments (to make it safe + // to call from DPI virtualized apps). + if (!DwmGetExtendedFrameBounds(hwnd, &rcArrange)) + { + return false; + } + + WI_SetFlag(flags, PlacementFlags::Arranged); + } + + LONG_PTR styles = GetWindowLongPtr(hwnd, GWL_STYLE); + + // Set the AllowSizing flag if the window has the WS_THICKFRAME style. + if (WI_IsFlagSet(styles, WS_THICKFRAME)) + { + WI_SetFlag(flags, PlacementFlags::AllowSizing); + } + + // If window has no caption/resize border styles and is positioned + // perfectly fitting the monitor rect, set FullScreen flag. + RECT rcWindow; + if (WI_AreAllFlagsClear(styles, WS_CAPTION | WS_THICKFRAME) && + GetWindowRect(hwnd, &rcWindow) && + EqualRect(&rcWindow, &rcMonitor)) + { + WI_SetFlag(flags, PlacementFlags::FullScreen); + } + +#ifdef USE_VIRTUAL_DESKTOP_APIS + if (GetVirtualDesktopId(hwnd, &placement->virtualDesktopId)) + { + WI_SetFlag(flags, PlacementFlags::VirtualDesktopId); + } +#endif + + // Fill in the provided PlacementEx. + placement->normalRect = rcNormal; + placement->arrangeRect = rcArrange; + placement->dpi = dpi; + placement->showCmd = wp.showCmd; + placement->workArea = rcWork; + placement->flags = flags; + + if (FAILED(StringCchCopy( + placement->deviceName, ARRAYSIZE(placement->deviceName), monitorData.deviceName))) + { + return false; + } + + return true; +} + +inline bool +PlacementEx::FindClosestMonitor(_Out_ MonitorData* monitor) const +{ + // Use the device name first to try to match the monitor. If two monitors + // change places (user switches primary monitor), the position and other + // monitor fields will change, but the device names should be most stable. + // + // Fall back to finding the monitor closest to the normal rect. + + return MonitorData::FromDeviceName(deviceName, monitor) || + MonitorData::FromRect(normalRect, monitor); +} + +/* static */ +inline bool +PlacementEx::SetPlacement(HWND hwnd, PlacementEx* placement) +{ +#ifdef USE_WINDOW_ACTION_APIS + // If using the Window Action APIs (ApplyWindowAction), SetPlacement is + // handled entirely in ApplyPlacementExAsAction (in WindowActions.h). + if (IsApplyWindowActionSupported()) + { + return ApplyPlacementExAsAction(hwnd, placement); + } +#endif + + // Determine which monitor is best/closest. + MonitorData targetMonitor; + if (!placement->FindClosestMonitor(&targetMonitor)) + { + return false; + } + + // Adjust the position to fit the monitor picked above. + placement->MoveToMonitor(targetMonitor); + + // The RestoreToArranged flag is read only if Minimizing the window and + // if snapping (the global user setting) is enabled. + // If this is set, we'll arrange the window first, then minimize it. + const bool isMinFromArranged = + IsMinimizeShowCmd(placement->showCmd) && + placement->HasFlag(PlacementFlags::RestoreToArranged) && + IsSnappingEnabled(); + + // The Arranged flag is read only when not Maximizing/Minimizing, and + // if snapping (the global user setting) is enabled. + // If Arranged (or RestoreToArranged), the arrangeRect field is set to + // the visible bounds of the arranged window, fit to the work area. + // If arranging the window, after moving it to the normal position, we'll + // arrange (snap) the window and move it to the arrange rect, updated as + // needed to fit the new monitor's work area. + const bool isArranged = + isMinFromArranged || + (placement->HasFlag(PlacementFlags::Arranged) && + !IsMinimizeShowCmd(placement->showCmd) && + (placement->showCmd != SW_MAXIMIZE) && + IsSnappingEnabled()); + + // Determine if the window is changing monitors. + // Note: This is the monitor the window is on now (not the monitor this + // placement is from). + // When moving a window between monitors, we always need to move it twice + // (or else moving between monitors with different DPIs could result in + // the wrong final window position). + MonitorData previousMonitor; + const bool isChangingMonitor = + MonitorData::FromWindow(hwnd, &previousMonitor) && + !previousMonitor.Equals(targetMonitor); + + // Determine if the window was previously Maximized/Minimized/Arranged. + const bool wasMinMaxArranged = + IsZoomed(hwnd) || IsIconic(hwnd) || IsWindowArrangedWrapper(hwnd); + + // The KeepHidden flag is only read if the window is currently hidden. + // Most show commands (other than SW_HIDE) will show the window. If this + // flag is set (and window is not already visible) we'll hide the window + // at the end. + const bool keepHidden = + !IsWindowVisible(hwnd) && + placement->HasFlag(PlacementFlags::KeepHidden); + + // The FullScreen flag causes the window to be sized to the monitor. + const bool fullScreen = placement->HasFlag(PlacementFlags::FullScreen); + + // The VirtualDesktopId flag will set the window's virtual desktop ID, + // potentially moving it to a background virtual desktop. + const bool setVirtualDesktop = + placement->HasFlag(PlacementFlags::VirtualDesktopId); + +#ifdef USE_VIRTUAL_DESKTOP_APIS + // If we're going to set the virtual desktop ID, remember the last + // foreground window. This is used before moving the window to a background + // virtual desktop, to avoid changing the active virtual desktop. + const HWND prevFg = setVirtualDesktop ? GetForegroundWindow() : nullptr; +#endif + + // If moving the window multiple times, cloak the window during the operation. + // This avoids the window flashing or animating from an unexpected location. + TempCloakWindowIf hideIf(hwnd, isChangingMonitor || + isArranged || + wasMinMaxArranged || + keepHidden || + fullScreen || + setVirtualDesktop); + + // Restore the window if it is maximized, minimized, or arranged. + // The normal position is the last non-Max/Min/Arrange position the window + // had. This means that we need the window to be 'normal' temporarily. + if (wasMinMaxArranged) + { + // SW_SHOWNOACTIVATE is like SW_RESTORE, but doesn't activate. + // Note: SW_SHOWNA shows but doesn't restore from Min/Max/Arrange. + ShowWindow(hwnd, SW_SHOWNOACTIVATE); + } + + // If the window is changing monitors, we need to move it onto the monitor + // before moving it into the final position. + // + // This is needed to handle DPI changes. When moving a window over a DPI + // change, it's final size can be unpredictable. To ensure the window ends + // up at the position we picked above (MoveToMonitor) we need to move it + // to that position after it is already on the monitor it is moving to. + if (isChangingMonitor) + { + UINT toDpi = GetDpiForWindow(hwnd); + UINT fromDpi = targetMonitor.dpi; + int cx = MulDiv(RECTWIDTH(placement->normalRect), toDpi, fromDpi); + int cy = MulDiv(RECTHEIGHT(placement->normalRect), toDpi, fromDpi); + + SetWindowPos(hwnd, + nullptr, + placement->normalRect.left, + placement->normalRect.top, + cx, + cy, + SWP_NOACTIVATE | SWP_NOZORDER); + } + + // Transform coordinates to Workspace Coordinates. + // PlacementEx stores coordinates in Screen Coordinates, but + // Get/SetWindowPlacement use workspace: offset by difference in + // work/monitor rect origin. + OffsetRect(&placement->normalRect, + targetMonitor.monitorRect.left - targetMonitor.workArea.left, + targetMonitor.monitorRect.top - targetMonitor.workArea.top); + + WINDOWPLACEMENT wp = { sizeof(wp) }; + wp.rcNormalPosition = placement->normalRect; + + // Use SW_NORMAL if minimizing from arrange. + // After setting the normal position, we'll arrange the window, and then + // we'll minimize the window (setting the caller's minimize show command). + wp.showCmd = isMinFromArranged ? SW_NORMAL : placement->showCmd; + + // If RestoreToMaximized, set WPF_RESTORETOMAXIMIZED. + if (placement->HasFlag(PlacementFlags::RestoreToMaximized)) + { + WI_SetFlag(wp.flags, WPF_RESTORETOMAXIMIZED); + } + + // Call SetWindowPlacement. + // This sets the normal position set above, and Min/Maximize state + // depending on the caller's show command (and decisions above). + if (!SetWindowPlacement(hwnd, &wp)) + { + return false; + } + + // If Arranging the window (and no failure from call above), make the + // window arranged and move it into the adjusted arrange rect (fit to + // the new monitor and expanded by the invisible resize area for this + // window on its current monitor). + if (isArranged) + { + // It is possible that we ended up Minimized/Maximized in the call + // above, even if we requested SW_NORMAL. This can happen if this is + // the first window shown by this process and the process was launched + // Minimized (and the caller set the RestoreToArranged flag, indicating + // the window should be Arranged and then Minimized). + if (IsIconic(hwnd) || IsZoomed(hwnd)) + { + ShowWindow(hwnd, SW_RESTORE); + } + + // 'Double click' on the top resize border. + // This causes the window to become arranged, snapping the top and + // bottom edges of the window to it's monitor's work area. + DefWindowProc(hwnd, WM_NCLBUTTONDBLCLK, HTTOP, 0); + + // The arrange rect is stored as frame bounds (without the invisible + // resize borders, aka window margins). We need to add these invisible + // resize borders back in, at the window's current DPI (now that it is + // on the final monitor). + RECT rcArrange = placement->arrangeRect; + ExtendByMargins(&rcArrange, GetWindowMargins(hwnd)); + + // Move the window to the adjusted arranged window position. + SetWindowPos( + hwnd, + nullptr, + rcArrange.left, + rcArrange.top, + RECTWIDTH(rcArrange), + RECTHEIGHT(rcArrange), + SWP_NOACTIVATE | SWP_NOZORDER); + } + + // If minimizing but RestoreToArrange flag was set, we still need to + // minimize the window (above we arranged it). + if (isMinFromArranged) + { + ShowWindow(hwnd, placement->showCmd); + } + + // FullScreen causes the window to be sized to the monitor, and have some + // window styles removed. + if (fullScreen) + { + placement->EnterFullScreen(hwnd); + } + +#ifdef USE_VIRTUAL_DESKTOP_APIS + // If provided a virtual desktop ID, move the window to that virtual desktop. + // + // Note: This is done after showing/activating the window (on the current + // virtual desktop). If we were to move the window to a background virtual + // desktop prior to activating it, that would switch the current virtual + // desktop. + // + // This assumes that the caller is restarting immediately after a reboot. + // For normal app launch, where app does not want to ever launch in a + // background virtual desktop, the VirtualDesktopId flag should not be set. + if (setVirtualDesktop) + { + // TODO: Activation/Virtual desktop switch problems... + // + // SetWindowPlacement above has activated the window. In most cases + // (if window ends up visible and on the current virtual desktop) this + // is what we want. The window should activate and come to foreground + // if possible. + // + // Note: We could tweak the SW_ commands, but there is no way to + // Maximize without activating :(. + // + // If an app launches several windows, and some are on a background + // virtual desktop, we MUST make sure to not cause a change in the + // current/active virtual desktop. This is destructive, but unfortunately + // happens if we attempt to move the foreground window to a background + // virtual desktop. + // + // Another problem: IVirtualDesktopManager can check if a window is on + // a background virtual desktop, (IsWindowOnCurrentVirtualDesktop), but + // we cannot tell from the GUID alone if it will move the window to a + // background virtual desktop. (So, by the time we can check if we're + // moving to a background desktop it is too late to avoid doing so with + // the foreground window.) + // + // Ideally, if an app launches several windows and ANY are on the active + // virtual desktop, those windows would be activated (and foreground if + // the app has foreground rights). But if the first window created ends + // up on a background virtual desktop, the app has no other eligible + // windows to make foreground when it must give it up on the one that + // was just created (to move it to a background desktop). + // + // So, we call GetForegroundWindow prior to activating this window, + // and here (before setting the virtual desktop ID), SetForegroundWindow + // on that window, 'giving up' foreground rights (this app will launch + // all windows without activating them). This NORMALLY works, but can + // still show flashing or cause a virtual desktop switch (but it avoids + // it in most cases). + // + // Ideas: + // - Should the VD APIs handle this (enforcing that calling the APIs + // never changes the active virtual desktop?) + // + // - Should/can we ask if a GUID is a background desktop, and use it to + // avoid giving up foreground here if launching first window on the + // current desktop? + // + // - Should there be an Activate/NoActivate placement flag, which handles + // showing without activating separately from virtual desktops? + if (prevFg) + { + SetForegroundWindow(prevFg); + } + + MoveToVirtualDesktop(hwnd, placement->virtualDesktopId); + // Note: Return value is true if window moved to a background desktop. + } +#endif + + // Hide the window if KeepHidden flag and window started invisible. + if (keepHidden) + { + ShowWindow(hwnd, SW_HIDE); + } + + return true; +} + +// +// PlacementParams +// +// This is used when creating a window, to pick the initial position of the +// window. +// +// Most apps will want to do a bit better than the default. This helper allows +// apps to opt into customized behavior: +// +// - Setting a custom fallback size (the size of the window the first time the +// user ever launches the app). +// +// - Setting a registry key to store the last close position. +// +// - Adjusting behavior for 'restart' scenarios, where it is ok to launch +// minimized or on a background virtual desktop. +// +// - Using another window as the position, cascading to keep the new position +// from covering the previous window. This avoids launching two instances +// of the app and ending up with two windows in the exact same place (one +// covering the other). +// +class PlacementParams +{ + const SIZE _defaultSize; + PlacementEx _placement = {}; + bool _isRestart = false; + bool _allowOffscreen = false; + StartupInfoFlags _startupInfoFlags = StartupInfoFlags::All; + +public: + // Apps should specify the default size, which is used only if no other + // size is available (the default is 600 x 400). + // + // If the app saves its last close position in the registry using + // PlacementEx::StorePlacementInRegistry, that registry key can be provided + // here, to launch the window where it closed by default. + PlacementParams( + SIZE defSize = { 600, 400 }, + PCWSTR keyPath = nullptr, + PCWSTR keyName = nullptr) : _defaultSize(defSize) + { + if (keyPath && keyName) + { + PlacementEx::FromRegistryKey(keyPath, keyName, &_placement); + } + } + + // Called if relaunching the app (as opposed to a 'normal' launch). This + // allows the window to launch Minimized or on a background desktop (if + // closed in that state). + void SetIsRestart() + { + _isRestart = true; + } + + // The Previous Window + // + // Apps that can launch many instances of itself should ideally not blindly + // use the position in the registry. Else, launching many instances of the + // app would 'pile up' (many windows would be in exactly the same place). + // + // Instead, if another instance is running, the new instance should 'cascade' + // over the other window (down/right a bit, keeping both title bars visible). + void FindPrevWindow(PCWSTR className) + { + HWND hwndPrev = FindWindow(className, nullptr); + + if (hwndPrev) + { + SetPrevWindow(hwndPrev); + } + } + + void SetPrevWindow(HWND hwndPrev) + { + PlacementEx placementT; + if (PlacementEx::GetPlacement(hwndPrev, &placementT)) + { + // If the other window is FullScreen, reject (ignore) it. + // We don't want to interpret the FullScreen position as a normal RECT, + // but we don't know the other window's normal RECT... + if (!placementT.HasFlag(PlacementFlags::FullScreen)) + { + placementT.Cascade(); + _placement = placementT; + } + } + } + + // By default, the position is always entirely within the bounds of the + // work area. Allowing offscreen skips this adjustment, but only if the new + // position is at least 50% within the work area. (SetPlacement does not + // allow moving a window more than 50% off screen.) + void SetAllowPartiallyOffscreen() + { + _allowOffscreen = true; + } + + // StartupInfo flags are set by default, and can be cleared. + void ClearStartupInfoFlag(StartupInfoFlags flag) + { + WI_ClearAllFlags(_startupInfoFlags, flag); + } + + // Positions the window and show it (make it visible). This should be done + // after all setup is complete, and when the thread is ready to start + // receiving messages. + PlacementEx PositionAndShow(HWND hwnd) + { + // If no position set, start with the window's current position and set + // the size using the default size. + if (!_placement.IsValid() && + PlacementEx::GetPlacement(hwnd, &_placement)) + { + _placement.SetLogicalSize(_defaultSize); + } + + if (_placement.IsValid()) + { + // If not a restart, make sure the window is not minimized (or cloaked). + // This also modifies the placement as needed if any of the StartupInfo + // flags are set (for example, a request to launch this app Maximized/ + // Minimized or on a particular monitor). + if (!_isRestart) + { + _placement.AdjustForMainWindow(nullptr, _startupInfoFlags); + } + + if (_allowOffscreen) + { + WI_SetFlag(_placement.flags, PlacementFlags::AllowPartiallyOffScreen); + } + + // Set the initial window position and show the window. + if (PlacementEx::SetPlacement(hwnd, &_placement)) + { + return _placement; + } + } + + // Something failed above. Fall back to showing the window at it's current + // position. + ShowWindow(hwnd, SW_SHOW); + PlacementEx::GetPlacement(hwnd, &_placement); + return _placement; + } +}; + +/* static */ +inline void +PlacementEx::StorePlacementInRegistry( + HWND hwnd, + PCWSTR registryPath, + PCWSTR registryKeyName) +{ + PlacementEx placement; + if (GetPlacement(hwnd, &placement)) + { + placement.StoreInRegistry(registryPath, registryKeyName); + } +} + +inline void +PlacementEx::StoreInRegistry( + PCWSTR registryPath, + PCWSTR registryKeyName) +{ + WriteStringRegKey(registryPath, registryKeyName, ToString()); +} + +inline void +PlacementEx::SetLogicalSize(SIZE size) +{ + normalRect.right = normalRect.left + MulDiv(size.cx, dpi, 96); + normalRect.bottom = normalRect.top + MulDiv(size.cy, dpi, 96); + normalRect = KeepRectOnMonitor(normalRect, workArea); +} + +/* static */ +inline void +PlacementEx::AddRemoveWindowStyles( + HWND hwnd, + bool add, + LONG_PTR stylesToChange) +{ + LONG_PTR style = GetWindowLongPtr(hwnd, GWL_STYLE); + if (add) + { + style = style | stylesToChange; + } + else + { + style = style & ~stylesToChange; + } + + SetWindowLongPtr(hwnd, GWL_STYLE, style); + + // Recompute the client rect (which may change depending on styles). + SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, + SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER); +} + +inline bool +PlacementEx::EnterFullScreen( + HWND hwnd, + LONG_PTR styles) +{ + // If the FullScreen flag is not already set, update this placement to + // the window's current position. + if (!IsFullScreen() && !GetPlacement(hwnd, this)) + { + return false; + } + + // Get the monitor info. + MonitorData monitor; + if (!MonitorData::FromRect(normalRect, &monitor)) + { + return false; + } + RECT rcMonitor = monitor.monitorRect; + + // Set the FullScreen flag. + WI_SetFlag(flags, PlacementFlags::FullScreen); + + // Remove styles for window border and caption. + AddRemoveWindowStyles(hwnd, false /* remove styles */, styles); + + // Move the window to fit the monitor rect. + // Also show the window (if it is hidden) and activate it. + SetWindowPos(hwnd, + nullptr, + rcMonitor.left, + rcMonitor.top, + rcMonitor.right - rcMonitor.left, + rcMonitor.bottom - rcMonitor.top, + SWP_SHOWWINDOW); + + return true; +} + +inline bool +PlacementEx::ExitFullScreen( + HWND hwnd, + LONG_PTR styles) +{ + if (!IsFullScreen()) + { + return false; + } + + // Make sure the position is on the monitor the window is currently on. + MoveToWindowMonitor(hwnd); + + // Clear the FullScreen flag. + WI_ClearFlag(flags, PlacementFlags::FullScreen); + + // Add styles for window border and caption. + AddRemoveWindowStyles(hwnd, true /* add styles */, styles); + + // Move the window to the restore position (where it was before entering + // FullScreen). + SetPlacement(hwnd, this); + + return true; +} + +/* static */ +inline RECT +PlacementEx::KeepRectOnMonitor( + const RECT& rcPrev, + const RECT & workArea) +{ + RECT rc = rcPrev; + + // Check right/bottom before left/top. + // This keeps the title bar (top-left of the window) on-screen in cases + // where the new size is larger than the new monitor's work area. + + if (rc.right > workArea.right) + { + OffsetRect(&rc, workArea.right - rc.right, 0); + } + if (rc.left < workArea.left) + { + OffsetRect(&rc, workArea.left - rc.left, 0); + } + if (rc.bottom > workArea.bottom) + { + OffsetRect(&rc, 0, workArea.bottom - rc.bottom); + } + if (rc.top < workArea.top) + { + OffsetRect(&rc, 0, workArea.top - rc.top); + } + + return rc; +} + +inline void +PlacementEx::AdjustNormalRect( + const RECT& rcWorkNew, + UINT dpiNew) +{ + const RECT rcNormalPrev = normalRect; + const RECT rcWorkPrev = workArea; + const UINT dpiPrev = dpi; + const int cxWorkPrev = RECTWIDTH(rcWorkPrev); + const int cxWorkNew = RECTWIDTH(rcWorkNew); + const int cyWorkPrev = RECTHEIGHT(rcWorkPrev); + const int cyWorkNew = RECTHEIGHT(rcWorkNew); + + // Scale the offset from the monitor's work area by the difference in work + // area size. This ensures that the position is roughly in the same place + // if moved to a monitor with a very different size work area and back + // (after forcing the position onto the work area if necessary). + normalRect.left = rcWorkNew.left + + MulDiv(rcNormalPrev.left - rcWorkPrev.left, cxWorkPrev, cxWorkNew); + normalRect.top = rcWorkNew.top + + MulDiv(rcNormalPrev.top - rcWorkPrev.top, cyWorkPrev, cyWorkNew); + + // Scale the size (width/height) from the old monitor's DPI to the new + // monitor's DPI. This retains the logical window size. + normalRect.right = normalRect.left + + MulDiv(RECTWIDTH(rcNormalPrev), dpiNew, dpiPrev); + normalRect.bottom = normalRect.top + + MulDiv(RECTHEIGHT(rcNormalPrev), dpiNew, dpiPrev); + + // The AllowPartiallyOffScreen skips remaining adjustments, but only if the + // new position is at least 50% within the new monitor's work area. + if (HasFlag(PlacementFlags::AllowPartiallyOffScreen)) + { + RECT rcI{}; + IntersectRect(&rcI, &normalRect, &rcWorkNew); + UINT onscreenArea = RECTWIDTH(rcI) * RECTHEIGHT(rcI); + UINT totalArea = RECTWIDTH(normalRect) * RECTHEIGHT(normalRect); + + if (onscreenArea > (totalArea / 2)) + { + return; + } + } + + // Nudge the window to stay within the bounds of the work area. + normalRect = KeepRectOnMonitor(normalRect, rcWorkNew); + + // If the window is now the same size as the work area or larger, pick a + // new size using the previous size and work area. (If previously half the + // monitor width, in the center, choose something similar fit to the new + // monitor's work area. + if (HasFlag(PlacementFlags::AllowSizing)) + { + if (RECTWIDTH(normalRect) > cxWorkNew) + { + normalRect.left = rcWorkNew.left + + MulDiv(rcNormalPrev.left - rcWorkPrev.left, cxWorkNew, cxWorkPrev); + + normalRect.right = rcWorkNew.right - + MulDiv(rcWorkPrev.right - rcNormalPrev.right, cxWorkNew, cxWorkPrev); + } + + if (RECTHEIGHT(normalRect) > cyWorkNew) + { + normalRect.top = rcWorkNew.top + + MulDiv(rcNormalPrev.top - rcWorkPrev.top, cyWorkNew, cyWorkPrev); + + normalRect.bottom = rcWorkNew.bottom - + MulDiv(rcWorkPrev.bottom - rcNormalPrev.bottom, cyWorkNew, cyWorkPrev); + } + } +} + +inline void +PlacementEx::AdjustArrangeRect( + const RECT rcWorkNew) +{ + const RECT rcWorkPrev = workArea; + const LONG cxPrev = RECTWIDTH(rcWorkPrev); + const LONG cyPrev = RECTHEIGHT(rcWorkPrev); + const LONG cxNew = RECTWIDTH(rcWorkNew); + const LONG cyNew = RECTHEIGHT(rcWorkNew); + + // Adjust each side of the window, keeping the window at the same relative + // position wrt each monitor edge. Don't allow the window to be outside the + // new work area (even if it was previously partially outside the monitor). + arrangeRect.left = rcWorkNew.left + + max(0, MulDiv((arrangeRect.left - rcWorkPrev.left), cxNew, cxPrev)); + + arrangeRect.right = rcWorkNew.right - + max(0, MulDiv((rcWorkPrev.right - arrangeRect.right), cxNew, cxPrev)); + + arrangeRect.top = rcWorkNew.top + + max(0, MulDiv((arrangeRect.top - rcWorkPrev.top), cyNew, cyPrev)); + + arrangeRect.bottom = rcWorkNew.bottom - + max(0, MulDiv((rcWorkPrev.bottom - arrangeRect.bottom), cyNew, cyPrev)); +} + +inline void +PlacementEx::MoveToMonitor(const MonitorData& targetMonitor) +{ + // Move the normal rect to the target monitor. + AdjustNormalRect(targetMonitor.workArea, targetMonitor.dpi); + + // If the Arranged or RestoreToArranged flag are set, transform the arrange + // rect from the previous work area to the new work area. This retains the + // alignment with the work area edges (for example, left snapped). + if (HasFlag(PlacementFlags::Arranged) || + HasFlag(PlacementFlags::RestoreToArranged)) + { + AdjustArrangeRect(targetMonitor.workArea); + } + + // This changes the DPI and monitor info this placement stores. + workArea = targetMonitor.workArea; + dpi = targetMonitor.dpi; + StringCchCopy(deviceName, ARRAYSIZE(deviceName), targetMonitor.deviceName); +} + +inline void +PlacementEx::Cascade() +{ + // Compute the height of the caption bar, using the DPI in the PlacementEx. + // Note: This adds 2x the size of the border because the caption bar's true + // height doesn't include the top resize area. (When we cascade a window, + // we want the full caption bar on the previous window to be visible, and + // using only the reported height of the caption bar from system metrics + // would cause the window to visibly overlap the other window's title bar.) + const UINT captionHeight = + GetSystemMetricsForDpi(SM_CYCAPTION, dpi) + + (2 * GetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi)); + + // Move to the right/down by the height of the caption bar. + OffsetRect(&normalRect, captionHeight, captionHeight); + + // If new position is now past the right side of the work area, move it + // back to the left side of the work area. + if (normalRect.right > workArea.right) + { + OffsetRect(&normalRect, workArea.left - normalRect.left, 0); + } + + // If new position is now past the bottom of the work area, move it back + // to the left side of the work area. + if (normalRect.bottom > workArea.bottom) + { + OffsetRect(&normalRect, 0, workArea.top - normalRect.top); + } +} + +inline void +PlacementEx::AdjustForMainWindow( + _In_opt_ STARTUPINFO* psi, + StartupInfoFlags siFlags) +{ + // If Minimized, switch to restored (or Maximized). + RestoreIfMinimized(); + + // Do not launch on a background virtual desktop (cloaked). + WI_ClearFlag(flags, PlacementFlags::VirtualDesktopId); + + // Apply startup info parameters (start Max/Min or Monitor Hint). + AdjustForStartupInfo(psi, siFlags); +} + +inline void +PlacementEx::AdjustForStartupInfo( + _In_opt_ STARTUPINFO* psi, + StartupInfoFlags siFlags) +{ + STARTUPINFO si{}; + + // Read the startup info if one was not provided explicitly. + if (!psi) + { + si.cb = sizeof(si); + GetStartupInfo(&si); + psi = &si; + } + + // If the monitor hint is set, move the stored position to the hint monitor. + MonitorData monitor; + if (WI_IsFlagSet(siFlags, StartupInfoFlags::MonitorHint) && + MonitorData::FromHandle((HMONITOR)psi->hStdOutput, &monitor)) + { + MoveToMonitor(monitor); + } + + // Set the show command if the flag is set (both by the caller and the + // startup info). + // This notably handles cases where the startup info requests Maximized or + // Minimized. The other show commands, SW_NORMAL, SW_SHOWDEFAULT, are NOT + // used. This ensures a Maximized window that is closed and launched in a + // default manner relaunches Maximized (not at the normal position). + // Also check for show commands like SW_HIDE and SW_SHOWNA, though these + // flags are not generally used with the startup info. + if (WI_IsFlagSet(siFlags, StartupInfoFlags::ShowCommand) && + WI_IsFlagSet(psi->dwFlags, STARTF_USESHOWWINDOW)) + { + switch (psi->wShowWindow) + { + case SW_HIDE: + WI_SetFlag(flags, PlacementFlags::KeepHidden); + break; + + case SW_SHOWNOACTIVATE: + case SW_SHOWNA: + WI_SetFlag(flags, PlacementFlags::NoActivate); + break; + + case SW_NORMAL: + case SW_SHOW: + case SW_RESTORE: + case SW_SHOWDEFAULT: + // Keep the existing show command. + break; + + default: + SetShowCommand(psi->wShowWindow); + } + } +} + +/* static */ +inline bool +PlacementEx::IsMinimizeShowCmd(UINT cmd) +{ + switch (cmd) + { + case SW_SHOWMINNOACTIVE: + case SW_SHOWMINIMIZED: + case SW_MINIMIZE: + return true; + } + return false; +} + +/* static */ +inline bool +PlacementEx::IsRestoreShowCmd(UINT cmd) +{ + switch (cmd) + { + case SW_NORMAL: + case SW_RESTORE: + case SW_SHOWDEFAULT: + return true; + } + return false; +} + +inline void +PlacementEx::SetShowCommand(UINT newShowCmd) +{ + // If Restoring a Minimized placement with 'restore to maximize' flag, swap + // the new show command to SW_MAXIMIZE and clear the restore to maximize flag. + if (IsRestoreShowCmd(newShowCmd) && + IsMinimizeShowCmd(showCmd) && + WI_IsFlagSet(flags, PlacementFlags::RestoreToMaximized)) + { + newShowCmd = SW_MAXIMIZE; + WI_ClearFlag(flags, PlacementFlags::RestoreToMaximized); + } + + // If Maximized and now Minimizing, set the 'restore to maximize' flag. + if (IsMinimizeShowCmd(newShowCmd) && + (showCmd == SW_MAXIMIZE)) + { + WI_SetFlag(flags, PlacementFlags::RestoreToMaximized); + } + + // If arranged and now Minimizing, set 'restore to arranged' flag instead + // of 'arranged'. + if (IsMinimizeShowCmd(newShowCmd) && + WI_IsFlagSet(flags, PlacementFlags::Arranged)) + { + WI_ClearFlag(flags, PlacementFlags::Arranged); + WI_SetFlag(flags, PlacementFlags::RestoreToArranged); + } + + showCmd = newShowCmd; +} + +/* static */ +inline bool +PlacementEx::FromRegistryKey( + PCWSTR registryPath, + PCWSTR registryKeyName, + _Out_ PlacementEx* placement) +{ + std::wstring placementStr = ReadStringRegKey(registryPath, registryKeyName); + + return !placementStr.empty() && FromString(placementStr, placement); +} + +// TODO: Something better for To/FromString()... +// - maybe switch to PCWSTR? +// StringCchPrintf +// see %SDXROOT%\clientcore\windows\tools\uiext\util.cpp +// _ParseDecimalUINT +// limit numbers to 16 bit + + +constexpr PCWSTR GUID_FORMAT_STR = + L"%08X-%04hX-%04hX-%02hhX%02hhX-%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX"; + +inline void StringToGuid(const std::wstring& guidStr, _Out_ GUID* guid) +{ + swscanf(guidStr.c_str(), GUID_FORMAT_STR, + &guid->Data1, &guid->Data2, &guid->Data3, + &guid->Data4[0], &guid->Data4[1], &guid->Data4[2], &guid->Data4[3], + &guid->Data4[4], &guid->Data4[5], &guid->Data4[6], &guid->Data4[7]); +} + +inline std::wstring GuidToString(const GUID& guid) +{ + return wil::str_printf( + GUID_FORMAT_STR, + guid.Data1, + guid.Data2, + guid.Data3, + guid.Data4[0], + guid.Data4[1], + guid.Data4[2], + guid.Data4[3], + guid.Data4[4], + guid.Data4[5], + guid.Data4[6], + guid.Data4[7] + ); +} + +// Serializes the placement information into a string. +inline std::wstring +PlacementEx::ToString() const +{ + // Serialize the data to a string. + // 15 integers then a string, separated by commas: + // - [left,top,right,bottom] normal rect + // - [left,top,right,bottom] work area + // - dpi + // - show command + // - placement flags + // - [left,top,right,bottom] arrange rect + // - virtual desktop GUID + // - device name + +#ifdef USE_VIRTUAL_DESKTOP_APIS + std::wstring guidString = GuidToString(virtualDesktopId); + + return wil::str_printf( + L"%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%ws,%ws\n", + normalRect.left, + normalRect.top, + normalRect.right, + normalRect.bottom, + workArea.left, + workArea.top, + workArea.right, + workArea.bottom, + dpi, + showCmd, + flags, + arrangeRect.left, + arrangeRect.top, + arrangeRect.right, + arrangeRect.bottom, + guidString.c_str(), + deviceName); +#else + return wil::str_printf( + L"%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%ws\n", + normalRect.left, + normalRect.top, + normalRect.right, + normalRect.bottom, + workArea.left, + workArea.top, + workArea.right, + workArea.bottom, + dpi, + showCmd, + flags, + arrangeRect.left, + arrangeRect.top, + arrangeRect.right, + arrangeRect.bottom, + deviceName); +#endif + +} + +// Converts a string (produced by PlacementEx::ToString()) into a PlacementEx. +/* static */ +inline std::optional +PlacementEx::FromString(const std::wstring& placementString) +{ + if (placementString.empty()) + { + return std::nullopt; + } + + std::wstring delim = L","; + + #define NUM_INTEGERS_IN_PLACEMENT_STRING 15 + int parsedInts[NUM_INTEGERS_IN_PLACEMENT_STRING]; + + PlacementEx pex; + try + { + auto start = 0U; + auto end = placementString.find(delim); + int index = 0; +#ifdef USE_VIRTUAL_DESKTOP_APIS + GUID virtualDesktopId = {}; +#endif + + while (end != std::wstring::npos) + { + std::wstring token = placementString.substr(start, end - start); + + start = (UINT)(end + delim.length()); + end = placementString.find(delim, start); + + if (index == NUM_INTEGERS_IN_PLACEMENT_STRING) + { +#ifdef USE_VIRTUAL_DESKTOP_APIS + StringToGuid(token, &virtualDesktopId); +#endif + break; + } + + parsedInts[index] = stoi(token); + + index++; + } + + if (index != NUM_INTEGERS_IN_PLACEMENT_STRING) + { + return std::nullopt; + } + + // The remainder of the string is the deviceName. + std::wstring deviceName = placementString.substr( + start, placementString.length() - start - 1); + // TODO: registry string has a newline, or a '.'? + // its important this string matches what we read live: + // - close, change primary monitor, relaunch to same position + + pex.normalRect.left = parsedInts[0]; + pex.normalRect.top = parsedInts[1]; + pex.normalRect.right = parsedInts[2]; + pex.normalRect.bottom = parsedInts[3]; + pex.workArea.left = parsedInts[4]; + pex.workArea.top = parsedInts[5]; + pex.workArea.right = parsedInts[6]; + pex.workArea.bottom = parsedInts[7]; + pex.dpi = parsedInts[8]; + pex.showCmd = parsedInts[9]; + pex.flags = static_cast(parsedInts[10]); + pex.arrangeRect.left = parsedInts[11]; + pex.arrangeRect.top = parsedInts[12]; + pex.arrangeRect.right = parsedInts[13]; + pex.arrangeRect.bottom = parsedInts[14]; +#ifdef USE_VIRTUAL_DESKTOP_APIS + pex.virtualDesktopId = virtualDesktopId; +#endif + + StringCchCopy(pex.deviceName, + ARRAYSIZE(pex.deviceName), deviceName.c_str()); + } + catch(std::invalid_argument const& /* ex */) + { + return std::nullopt; + } + + return pex; +} diff --git a/Samples/WindowPlacement/cpp/inc/RegistryHelpers.h b/Samples/WindowPlacement/cpp/inc/RegistryHelpers.h new file mode 100644 index 00000000..407e4226 --- /dev/null +++ b/Samples/WindowPlacement/cpp/inc/RegistryHelpers.h @@ -0,0 +1,85 @@ +#pragma once +// Note: Intended to be included by User32Utils.h. + +// +// Helpers for reading and writing values (strings and DWORDs) to app-specific +// registry keys, used to persist data like window positions. +// + +inline HKEY GetAppRegKey(PCWSTR appName) +{ + HKEY hKey = nullptr; + + LONG openRes = RegOpenKeyEx(HKEY_CURRENT_USER, appName, + 0, KEY_ALL_ACCESS , &hKey); + + if (openRes != ERROR_SUCCESS) + { + RegCreateKeyEx(HKEY_CURRENT_USER, appName, + 0, NULL, 0, KEY_WRITE, NULL, &hKey, NULL); + } + + return hKey; +} + +inline std::wstring ReadStringRegKey(PCWSTR appName, PCWSTR keyName) +{ + HKEY hKey = GetAppRegKey(appName); + + WCHAR textBuffer[500]; + DWORD dwBufferSize = sizeof(textBuffer); + + // Read the registry key. + ULONG nError = RegQueryValueExW(hKey, keyName, + 0, NULL, (LPBYTE)textBuffer, &dwBufferSize); + + // Return empty string if registry read failed. + if (nError != ERROR_SUCCESS) + { + return L""; + } + + RegCloseKey(hKey); + + return (PWSTR)&textBuffer; +} + +inline void WriteStringRegKey(PCWSTR appName, PCWSTR keyName, std::wstring keyValue) +{ + HKEY hKey = GetAppRegKey(appName); + + RegSetValueEx(hKey, keyName, 0, REG_SZ, + (LPBYTE)keyValue.c_str(), (DWORD)((wcslen(keyValue.c_str()) * 2) + 1)); + + RegCloseKey(hKey); +} + +inline void DeleteRegValue(PCWSTR appName, PCWSTR keyName) +{ + HKEY hKey = GetAppRegKey(appName); + + RegDeleteValue(hKey, keyName); + + RegCloseKey(hKey); +} + +inline DWORD ReadDwordRegKey(PCWSTR appName, PCWSTR keyName, DWORD dwDefault) +{ + HKEY hKey = GetAppRegKey(appName); + + unsigned long type = REG_DWORD, size = 1024; + RegQueryValueEx(hKey, keyName, nullptr, &type, (PBYTE)&dwDefault, &size); + + RegCloseKey(hKey); + + return dwDefault; +} + +inline void WriteDwordRegKey(PCWSTR appName, PCWSTR keyName, DWORD dwValue) +{ + HKEY hKey = GetAppRegKey(appName); + + RegSetValueEx(hKey, keyName, 0, REG_DWORD, (PBYTE)&dwValue, sizeof(dwValue)); + + RegCloseKey(hKey); +} diff --git a/Samples/WindowPlacement/cpp/inc/User32Utils.h b/Samples/WindowPlacement/cpp/inc/User32Utils.h new file mode 100644 index 00000000..44c16b01 --- /dev/null +++ b/Samples/WindowPlacement/cpp/inc/User32Utils.h @@ -0,0 +1,44 @@ +#pragma once + +#ifdef USE_WINDOW_ACTION_APIS +#include +#include +#include +#endif + +#include "windows.h" +#include "shellscalingapi.h" +#include "dwmapi.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "MonitorData.h" +#include "MiscUser32.h" +#include "RegistryHelpers.h" +#include "CurrentMonitorTopology.h" + +#ifdef USE_VIRTUAL_DESKTOP_APIS +#include "shobjidl.h" +#include "shobjidl_core.h" +#include +#include "VirtualDesktopIds.h" +#endif + +#ifdef USE_WINDOW_ACTION_APIS +#include +bool IsApplyWindowActionSupported(); +bool ApplyWindowActionWrapper(HWND hwnd, WINDOW_ACTION* action); +#endif + +#include "PlacementEx.h" + +#ifdef USE_WINDOW_ACTION_APIS +#include "WindowActions.h" +#endif diff --git a/Samples/WindowPlacement/cpp/inc/VirtualDesktopIds.h b/Samples/WindowPlacement/cpp/inc/VirtualDesktopIds.h new file mode 100644 index 00000000..3e1380c0 --- /dev/null +++ b/Samples/WindowPlacement/cpp/inc/VirtualDesktopIds.h @@ -0,0 +1,118 @@ +#pragma once +// Note: Included by User32Utils.h, if USE_VIRTUAL_DESKTOP_APIS is defined. + +// +// This file has wrapper functions used by PlacementEx.h to use virtual desktops. +// https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-ivirtualdesktopmanager +// +// This requires: +// - initializing COM, by running this on each thread: +// CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); +// - linking against: +// $(ONECOREUAP_INTERNAL_SDK_LIB_PATH)\onecoreuapuuid.lib +// $(ONECORE_INTERNAL_PRIV_SDK_LIB_PATH_L)\OneCore_Forwarder_ole32.lib +// - Not querying window state from a SendMessage call +// (the virtual desktop APIs fail in this case...) +// +// These APIs require linking to additional binaries, and can also (because +// of COM usage) lead to deadlocks depending on how the APIs are called. See +// VirtualDesktopHelper in chromium, which moves these calls to a background +// thread for this reason: +// https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/views/frame/browser_desktop_window_tree_host_win.cc +// +// TODO: Maybe add a helper like VirtualDesktopHelper from chromium? +// - manage background thread that initializes COM, and allows calls from +// within a SendMessage call. +// - dynamically link com dlls? +// - RegisterCloakedNotification/WM_CLOAKED_STATE_CHANGED? +// + +// Uncomment the below if debugging virtual desktops issues: +// #define BREAK_ON_VIRTUAL_DESKTOP_FAILURE + +// Returns the GUID for the virtual desktop that a window belongs to. +inline bool +GetVirtualDesktopId( + HWND hwnd, + _Out_opt_ GUID* desktopId) +{ + Microsoft::WRL::ComPtr spVirtualDesktopManager; + + HRESULT hr = CoCreateInstance(__uuidof(VirtualDesktopManager), + nullptr, + CLSCTX_INPROC_SERVER, + IID_IVirtualDesktopManager, + (void**)&spVirtualDesktopManager); + + if (hr != S_OK) + { + // Note: The API above fails if the current thread needs to call: + // CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); +#ifdef BREAK_ON_VIRTUAL_DESKTOP_FAILURE + __debugbreak(); +#endif + return false; + } + + // Get the ID (GUID) of the window's virtual desktop. + hr = spVirtualDesktopManager->GetWindowDesktopId(hwnd, desktopId); + if (hr != S_OK) + { +#ifdef BREAK_ON_VIRTUAL_DESKTOP_FAILURE + // ELEMENTNOTFOUND is expected if newly created window queries its + // desktop ID (if taskbar isn't aware of the window yet). + if (hr != TYPE_E_ELEMENTNOTFOUND) + { + // Note: The call above fails if called while handling a SendMessage, + // error code: RPC_E_CANTCALLOUT_ININPUTSYNCCALL + __debugbreak(); + } +#endif + return false; + } + + return true; +} + +// Moves a window to a Virtual Desktop, specified by the virtual desktop GUID. +// This returns true if the window was successfully moved AND if the window is +// now on a background (not active) virtual desktop. +inline bool +MoveToVirtualDesktop( + HWND hwnd, + const GUID& desktopId) +{ + Microsoft::WRL::ComPtr spVirtualDesktopManager; + + HRESULT hr = CoCreateInstance(__uuidof(VirtualDesktopManager), + nullptr, + CLSCTX_INPROC_SERVER, + IID_IVirtualDesktopManager, + (void**)&spVirtualDesktopManager); + + // Note: Virtual desktop IDs are best effort. + // They may fail for legit reasons, like user deleted the virtual desktop. + + if (hr != S_OK) + { + return false; + } + + hr = spVirtualDesktopManager->MoveWindowToDesktop(hwnd, desktopId); + + if (hr != S_OK) + { + return false; + } + + BOOL isCurrentDesktop = TRUE; + hr = spVirtualDesktopManager->IsWindowOnCurrentVirtualDesktop(hwnd, &isCurrentDesktop); + + if (hr != S_OK) + { + return false; + } + + // Return true if the window is now on a background virtual desktop. + return !isCurrentDesktop; +} diff --git a/Samples/WindowPlacement/cpp/inc/WindowActions.h b/Samples/WindowPlacement/cpp/inc/WindowActions.h new file mode 100644 index 00000000..c9432055 --- /dev/null +++ b/Samples/WindowPlacement/cpp/inc/WindowActions.h @@ -0,0 +1,386 @@ +#pragma once +// Note: Included by User32Utils.h, if USE_WINDOW_ACTION_APIS is defined. + +// +// Note: These APIs are only available on newer OS builds. Apps using these +// APIs that need to run on older releases should handle a fallback when the +// APIs fail. +// + +// ======================================================================== + +// +// CWindowAction +// +// Helper class to set fields in a WINDOW_ACTION and call ApplyWindowAction. +// +// A Window Action describes changes to make to a top level window. Moving, +// sizing, activating, maximizing, etc. +// +// The 'kinds' field of the action is flags that describe the changes to make. +// Some have no additional payload, like WAK_ACTIVATE, and others like +// WAK_POSITION have a corresponding field containing the value. +// +// The 'modifiers' field is also flags, some of which have their own field. The +// modifiers change the behavior of one or more of the kinds in some way. For +// example, the modifier WAM_FRAME_BOUNDS changes how the provided rect +// (position/size) are interpretted. +// +class CWindowAction : public WINDOW_ACTION +{ +public: + CWindowAction() + { + RtlZeroMemory(this, sizeof(this)); + } + + // Calls ApplyWindowAction to apply the changes in the action to the window. + bool Apply(HWND hwnd) + { + return ApplyWindowActionWrapper(hwnd, this); + } + + // + // The functions below set fields in the action. + // + + // Activates the window. + // This makes the window the active window (GetActiveWindow), and the + // foreground window (GetForegroundWindow) if the app is in foreground. + void SetActivate() + { + WI_SetFlag(kinds, WAK_ACTIVATE); + } + + // Shows (or hides) a window. This sets/clears WS_VISIBLE. + void SetVisible(bool val = true) + { + WI_SetFlag(kinds, WAK_VISIBILITY); + visible = val; + } + + // The insert after window is the window that this window is behind, or + // below (in z-order). This window can be special sentinel values like + // HWND_TOP or HWND_TOPMOST, which have special meaning. + void SetInsertAfter(HWND val) + { + WI_SetFlag(kinds, WAK_INSERT_AFTER); + insertAfter = val; + } + + // Set the position and size (the rect). + // By default, this rect includes parts of the window that are invisible + // resize borders. (As opposed to 'frame bounds', the visible bounds of + // the window, with invisible resize borders removed.) + void SetRect(RECT rc) + { + WI_SetAllFlags(kinds, WAK_POSITION | WAK_SIZE); + position.x = rc.left; + position.y = rc.top; + size.cx = RECTWIDTH(rc); + size.cy = RECTHEIGHT(rc); + } + + // Changes the behavior of the provided rect, indicating the rect does not + // include invisible resize borders (it is the desired visible bounds of + // the window). This is most useful with Arranged positions, which normally + // align the visible bounds of the window with the edges of the monitor. + void SetFrameBounds() + { + WI_SetFlag(modifiers, WAM_FRAME_BOUNDS); + } + + // Sets the state, Minimize, Maximize, Arrange, Restore. + // When Min/Max/Arranged, the window has a 'normal' position that is + // separate from it's current position. (Normal is the last non-special + // position.) Restoring a window from Max/Min/Arrange moves it back to the + // normal position. + void SetState(WINDOW_PLACEMENT_STATE state) + { + WI_SetFlag(kinds, WAK_PLACEMENT_STATE); + placementState = state; + } + + // Sets the normal rect. This requires also setting the state. + // + // If Max/Min/Arranged, the normal rect overrides the default normal + // position (which is the previous non-special position the window had). + // If the state is Restored, the normal position has the same meaning as + // the position and size (the rect). + void SetNormalRect(RECT rc) + { + WI_SetFlag(kinds, WAK_NORMAL_RECT); + normalRect = rc; + } + + void SetMaximized() + { + SetState(WPS_MAXIMIZED); + } + + void SetRestored() + { + SetState(WPS_NORMAL); + } + + // Setting Arranged requires an arranged rect, which is in frame bounds + // (visible bounds, no invisible resize borders). This is expected to be + // aligned with edges of the work area (like left half, or corner, etc). + void SetArranged(RECT arrangeRect) + { + SetState(WPS_ARRANGED); + SetRect(arrangeRect); + SetFrameBounds(); + } + + // Minimize normally remembers the previous state of the window (if + // Maximized or Arranged). Restoring a Minimized windows returns the window + // to that previous state. Optionally, an action can specify the window be + // Minimized but restore to Maximized or Arranged. + void SetMinimized() + { + SetState(WPS_MINIMIZED); + } + + void SetMinRestoreToMaximized() + { + SetState(WPS_MINIMIZED); + WI_SetFlag(modifiers, WAM_RESTORE_TO_MAXIMIZED); + } + + void SetMinRestoreToArranged(RECT arrangeRect) + { + SetState(WPS_MINIMIZED); + WI_SetFlag(modifiers, WAM_RESTORE_TO_ARRANGED); + SetRect(arrangeRect); + SetFrameBounds(); + } + + // The fit to monitor flag causes the window's normal position to be moved + // as needed to stay entirely within the bounds of the work area. + void SetFitToMonitor() + { + WI_SetFlag(kinds, WAK_FIT_TO_MONITOR); + } + + // The Move to Monitor flag specifies the monitor the window should be on, + // using a point (this picks the nearest monitor to this point, in screen + // coordinates). + void SetMoveToMonitorPoint(POINT point) + { + WI_SetFlag(kinds, WAK_MOVE_TO_MONITOR); + pointOnMonitor = point; + } + + void SetMoveToMonitor(MonitorData monitor) + { + SetMoveToMonitorPoint({ monitor.workArea.left, monitor.workArea.top }); + } + + // The provided position and size are assumed to be picked for the current + // monitors. If a position is from the past, the action can specify the + // work area from the past. The provided position is adjusted if the work + // area has changed, to ensure the window's position relative to the monitor + // stays the same. + void SetPreviousWorkArea(RECT prevWorkArea) + { + WI_SetFlag(modifiers, WAM_WORK_AREA); + workArea = prevWorkArea; + } + + // The provided size is assumed to be picked for the window's current DPI. + // If the size is picked from a past DPI, or for the DPI of the monitor, + // the DPI field in the action should be set to that DPI. This ensures that + // if the window changes DPI, the final size matches the one provided. + void SetPreviousDpi(UINT prevDpi) + { + WI_SetFlag(modifiers, WAM_DPI); + dpi = prevDpi; + } +}; + +// Called by PlacementEx::SetPlacement, if IsApplyWindowActionSupported. +// This translates the PlacementEx into a Window Action and calls ApplyWindowAction. +// If this succeeds, the caller will return without making additional changes +// (all the changes SetPlacement makes are made here if ApplyWindowAction is supported). +/* static */ +inline bool +PlacementEx::ApplyPlacementExAsAction(HWND hwnd, PlacementEx* placement) +{ + const bool fVirtDesktop = placement->HasFlag(PlacementFlags::VirtualDesktopId); + const bool fFullScreen = placement->HasFlag(PlacementFlags::FullScreen); + + // Hide window temporarily if moving multiple times. + TempCloakWindowIf hideIf(hwnd, fVirtDesktop || fFullScreen); + + CWindowAction action; + + // Activate by default, unless NoActivate flag. + // If setting a virtual desktop this implies NoActivate. + if (!placement->HasFlag(PlacementFlags::NoActivate) && !fVirtDesktop) + { + action.SetActivate(); + } + + // Show window by default, unless KeepHidden and previously invisible. + if (!IsWindowVisible(hwnd) && + !placement->HasFlag(PlacementFlags::KeepHidden)) + { + action.SetVisible(); + } + + // Set Fit to Monitor by default, unless AllowPartiallyOffScreen. + if (!placement->HasFlag(PlacementFlags::AllowPartiallyOffScreen)) + { + action.SetFitToMonitor(); + } + + // Set the state (Min/Max/Arrange/Restore). + // + // This is determined by the show command (SW_MAXIMIZE, SW_MINIMIZE, etc), + // and by the Arranged PlacementEx flag. + // + // If Minimize, the two PlacementEx flags RestoreToMaximized/Arranged set + // the restore state (when window restores from Min). + // + // If Arrange, or Min restore to Arrange, the PlacementEx has an arrange + // position. This is a rect within the work area in the placement, normally + // aligned with 2 or 3 edges of the work area. (Position does not include + // invisible frame bounds, WAM_FRAME_BOUNDS.) + if (placement->showCmd == SW_MAXIMIZE) + { + action.SetMaximized(); + } + else if (IsMinimizeShowCmd(placement->showCmd)) + { + if (placement->HasFlag(PlacementFlags::RestoreToMaximized)) + { + action.SetMinRestoreToMaximized(); + } + else if (placement->HasFlag(PlacementFlags::RestoreToArranged)) + { + action.SetMinRestoreToArranged(placement->arrangeRect); + } + else + { + action.SetMinimized(); + } + } + else if (placement->HasFlag(PlacementFlags::Arranged)) + { + action.SetArranged(placement->arrangeRect); + } + else + { + action.SetRestored(); + } + + // Set the normal rect. This is the restore position if Min/Max/Arrange, + // or the window position if restored. + action.SetNormalRect(placement->normalRect); + + // Set the work area and DPI. These are from the window/monitor when the + // placement was created, and are used to update the position as needed if + // the monitors have changed. + action.SetPreviousWorkArea(placement->workArea); + action.SetPreviousDpi(placement->dpi); + + // Call ApplyWindowAction to apply the changes. + if (!action.Apply(hwnd)) + { + return false; + } + + // Enter FullScreen, if needed. + if (fFullScreen) + { + placement->EnterFullScreen(hwnd); + } + +#ifdef USE_VIRTUAL_DESKTOP_APIS + // Set virtual desktop ID, if needed. + if (fVirtDesktop) + { + MoveToVirtualDesktop(hwnd, placement->virtualDesktopId); + } +#endif + + return true; +} + +// ApplyWindowAction is dynamically loaded the first time it is called. +using fnApplyWindowAction = BOOL(*)(HWND hwnd, WINDOW_ACTION* action); +fnApplyWindowAction pfnApplyWindowAction = nullptr; + +// Called once per process to load ApplyWindowAction (and check if supported). +inline void LoadApplyWindowActionApi() +{ + // Dynamically load the user32!ApplyWindowAction API. + pfnApplyWindowAction = reinterpret_cast( + GetProcAddress(LoadLibrary(L"user32.dll"), "ApplyWindowAction")); + + // There are some OS builds that have the API export prior to the API being + // supported. To know if the API is supported (prior to attempting to use + // it to move the window), we create a dummy window on the first call and + // attempt to call ApplyWindowAction on it, to see if it returns false. + if (pfnApplyWindowAction) + { + HINSTANCE hInstance = GetModuleHandle(NULL); + PCWSTR className = L"ProbeApplyApiWindowClassName"; + + WNDCLASSEX wc = { sizeof(WNDCLASSEX) }; + wc.hInstance = hInstance; + wc.lpfnWndProc = DefWindowProc; + wc.lpszClassName = className; + RegisterClassEx(&wc); + + HWND hwnd = CreateWindowEx( + 0, className, nullptr, + 0, 0, 0, 0, 0, + nullptr, nullptr, hInstance, nullptr); + + WINDOW_ACTION action{}; + action.kinds = WAK_POSITION; + + if (!pfnApplyWindowAction(hwnd, &action)) + { + // The API is present but disabled. Clear the pointer so that we + // consider it not available. + pfnApplyWindowAction = nullptr; + } + + DestroyWindow(hwnd); + UnregisterClass(className, hInstance); + } +} + +// Returns true if the ApplyWindowAction API is supported on the current OS. +bool IsApplyWindowActionSupported() +{ + static bool initOnce = false; + if (!initOnce) + { + initOnce = true; + LoadApplyWindowActionApi(); + } + + return (pfnApplyWindowAction != nullptr); +} + +// Calls ApplyWindowAction, if it is available and supported on the current OS. +inline bool ApplyWindowActionWrapper(HWND hwnd, WINDOW_ACTION* action) +{ + if (IsApplyWindowActionSupported()) + { + if (pfnApplyWindowAction(hwnd, action)) + { + return true; + } + +#ifdef BREAK_ON_WINDOW_ACTIONS_FAILURE + __debugbreak(); +#endif + } + + return false; +} From 6351006e503b7025258608026e6415f91eff1b0f Mon Sep 17 00:00:00 2001 From: Ben Stolovitz Date: Thu, 11 Dec 2025 16:00:20 -0500 Subject: [PATCH 02/11] (AI) attempting to fix packages --- .../FullScreenSample/FullScreenSample.vcxproj | 20 +++++++++++++++---- .../cpp/FullScreenSample/packages.config | 4 ++++ .../SaveRestoreSample.vcxproj | 20 +++++++++++++++---- .../cpp/SaveRestoreSample/packages.config | 4 ++++ Samples/WindowPlacement/cpp/packages.config | 4 ++++ 5 files changed, 44 insertions(+), 8 deletions(-) create mode 100644 Samples/WindowPlacement/cpp/FullScreenSample/packages.config create mode 100644 Samples/WindowPlacement/cpp/SaveRestoreSample/packages.config create mode 100644 Samples/WindowPlacement/cpp/packages.config diff --git a/Samples/WindowPlacement/cpp/FullScreenSample/FullScreenSample.vcxproj b/Samples/WindowPlacement/cpp/FullScreenSample/FullScreenSample.vcxproj index c4b45c4b..3221cd05 100644 --- a/Samples/WindowPlacement/cpp/FullScreenSample/FullScreenSample.vcxproj +++ b/Samples/WindowPlacement/cpp/FullScreenSample/FullScreenSample.vcxproj @@ -1,5 +1,5 @@ - + Debug @@ -19,11 +19,11 @@ - $(VCTargetsPath11) - - + 16.0 + Win32Proj {C1D15996-DAB8-4608-A6ED-CE0AE53EEBA8} FullScreenSample + 10.0 @@ -55,6 +55,8 @@ + + @@ -124,7 +126,17 @@ + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + \ No newline at end of file diff --git a/Samples/WindowPlacement/cpp/FullScreenSample/packages.config b/Samples/WindowPlacement/cpp/FullScreenSample/packages.config new file mode 100644 index 00000000..26065b14 --- /dev/null +++ b/Samples/WindowPlacement/cpp/FullScreenSample/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Samples/WindowPlacement/cpp/SaveRestoreSample/SaveRestoreSample.vcxproj b/Samples/WindowPlacement/cpp/SaveRestoreSample/SaveRestoreSample.vcxproj index b701e6eb..0c873691 100644 --- a/Samples/WindowPlacement/cpp/SaveRestoreSample/SaveRestoreSample.vcxproj +++ b/Samples/WindowPlacement/cpp/SaveRestoreSample/SaveRestoreSample.vcxproj @@ -1,5 +1,5 @@ - + Debug @@ -19,11 +19,11 @@ - $(VCTargetsPath11) - - + 16.0 + Win32Proj {92E6DB91-D852-412E-BD45-2EB168A1D090} SaveRestoreSample + 10.0 @@ -55,6 +55,8 @@ + + @@ -124,7 +126,17 @@ + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + \ No newline at end of file diff --git a/Samples/WindowPlacement/cpp/SaveRestoreSample/packages.config b/Samples/WindowPlacement/cpp/SaveRestoreSample/packages.config new file mode 100644 index 00000000..26065b14 --- /dev/null +++ b/Samples/WindowPlacement/cpp/SaveRestoreSample/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Samples/WindowPlacement/cpp/packages.config b/Samples/WindowPlacement/cpp/packages.config new file mode 100644 index 00000000..130712fe --- /dev/null +++ b/Samples/WindowPlacement/cpp/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file From 3d51ac5f96fcc6092e371cbe6f8603d76da66d74 Mon Sep 17 00:00:00 2001 From: Ben Stolovitz Date: Thu, 11 Dec 2025 16:06:22 -0500 Subject: [PATCH 03/11] (AI) still broken --- .../FullScreenSample/FullScreenSample.vcxproj | 56 ++++++++++++------- .../SaveRestoreSample.vcxproj | 56 ++++++++++++------- 2 files changed, 74 insertions(+), 38 deletions(-) diff --git a/Samples/WindowPlacement/cpp/FullScreenSample/FullScreenSample.vcxproj b/Samples/WindowPlacement/cpp/FullScreenSample/FullScreenSample.vcxproj index 3221cd05..b3eac3ca 100644 --- a/Samples/WindowPlacement/cpp/FullScreenSample/FullScreenSample.vcxproj +++ b/Samples/WindowPlacement/cpp/FullScreenSample/FullScreenSample.vcxproj @@ -29,26 +29,27 @@ Application true - v143 + v142 Unicode Application false - v143 + v142 true Unicode + false Application true - v143 + v142 Unicode Application false - v143 + v142 true Unicode @@ -70,57 +71,74 @@ - + + true + + + false + false + + + true + + + false + Level3 - Disabled - ../inc;%(AdditionalIncludeDirectories) + true + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true - true Windows + true Level3 - MaxSpeed true true - ../inc;%(AdditionalIncludeDirectories) + true + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + Disabled - true + Windows true true - Windows + true Level3 - Disabled - ../inc;%(AdditionalIncludeDirectories) + true + _DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true - true Windows + true Level3 - MaxSpeed true true - ../inc;%(AdditionalIncludeDirectories) + true + NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true - true + Windows true true - Windows + true diff --git a/Samples/WindowPlacement/cpp/SaveRestoreSample/SaveRestoreSample.vcxproj b/Samples/WindowPlacement/cpp/SaveRestoreSample/SaveRestoreSample.vcxproj index 0c873691..1436c81c 100644 --- a/Samples/WindowPlacement/cpp/SaveRestoreSample/SaveRestoreSample.vcxproj +++ b/Samples/WindowPlacement/cpp/SaveRestoreSample/SaveRestoreSample.vcxproj @@ -29,26 +29,27 @@ Application true - v143 + v142 Unicode Application false - v143 + v142 true Unicode + false Application true - v143 + v142 Unicode Application false - v143 + v142 true Unicode @@ -70,57 +71,74 @@ - + + true + + + false + false + + + true + + + false + Level3 - Disabled - ../inc;%(AdditionalIncludeDirectories) + true + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true - true Windows + true Level3 - MaxSpeed true true - ../inc;%(AdditionalIncludeDirectories) + true + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + Disabled - true + Windows true true - Windows + true Level3 - Disabled - ../inc;%(AdditionalIncludeDirectories) + true + _DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true - true Windows + true Level3 - MaxSpeed true true - ../inc;%(AdditionalIncludeDirectories) + true + NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true - true + Windows true true - Windows + true From 0345988426a3584a49d6521b503425c61aee66ed Mon Sep 17 00:00:00 2001 From: Ben Stolovitz Date: Thu, 11 Dec 2025 16:06:32 -0500 Subject: [PATCH 04/11] VS-created project --- .../ConsoleApplication1.sln | 31 +++++ .../ConsoleApplication1.vcxproj | 130 ++++++++++++++++++ .../ConsoleApplication1.vcxproj.filters | 37 +++++ .../ConsoleApplication1/PropertySheet.props | 16 +++ .../ConsoleApplication1/main.cpp | 11 ++ .../ConsoleApplication1/packages.config | 5 + .../ConsoleApplication1/pch.cpp | 1 + .../ConsoleApplication1/pch.h | 3 + .../ConsoleApplication1/readme.txt | 30 ++++ .../FullScreenSample/FullScreenSample.vcxproj | 31 +++-- .../SaveRestoreSample.vcxproj | 29 ++-- .../cpp/WindowPlacementSamples.sln | 20 +-- 12 files changed, 313 insertions(+), 31 deletions(-) create mode 100644 Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1.sln create mode 100644 Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/ConsoleApplication1.vcxproj create mode 100644 Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/ConsoleApplication1.vcxproj.filters create mode 100644 Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/PropertySheet.props create mode 100644 Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/main.cpp create mode 100644 Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/packages.config create mode 100644 Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/pch.cpp create mode 100644 Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/pch.h create mode 100644 Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/readme.txt diff --git a/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1.sln b/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1.sln new file mode 100644 index 00000000..ae42bfae --- /dev/null +++ b/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36408.4 d17.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ConsoleApplication1", "ConsoleApplication1\ConsoleApplication1.vcxproj", "{04A94FC2-E929-4327-9E0A-F70EF4059773}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {04A94FC2-E929-4327-9E0A-F70EF4059773}.Debug|x64.ActiveCfg = Debug|x64 + {04A94FC2-E929-4327-9E0A-F70EF4059773}.Debug|x64.Build.0 = Debug|x64 + {04A94FC2-E929-4327-9E0A-F70EF4059773}.Debug|x86.ActiveCfg = Debug|Win32 + {04A94FC2-E929-4327-9E0A-F70EF4059773}.Debug|x86.Build.0 = Debug|Win32 + {04A94FC2-E929-4327-9E0A-F70EF4059773}.Release|x64.ActiveCfg = Release|x64 + {04A94FC2-E929-4327-9E0A-F70EF4059773}.Release|x64.Build.0 = Release|x64 + {04A94FC2-E929-4327-9E0A-F70EF4059773}.Release|x86.ActiveCfg = Release|Win32 + {04A94FC2-E929-4327-9E0A-F70EF4059773}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {886355CC-4702-425E-958D-3163266C6EA0} + EndGlobalSection +EndGlobal diff --git a/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/ConsoleApplication1.vcxproj b/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/ConsoleApplication1.vcxproj new file mode 100644 index 00000000..4951e9fe --- /dev/null +++ b/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/ConsoleApplication1.vcxproj @@ -0,0 +1,130 @@ + + + + + true + true + true + true + 15.0 + {04a94fc2-e929-4327-9e0a-f70ef4059773} + Win32Proj + ConsoleApplication1 + 10.0.26100.0 + 10.0.17134.0 + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + Application + v143 + v142 + v141 + v140 + Unicode + + + true + true + + + false + true + false + + + + + + + + + + + + + + + + Use + pch.h + $(IntDir)pch.pch + _CONSOLE;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) + Level4 + %(AdditionalOptions) /permissive- /bigobj + + + + + Disabled + _DEBUG;%(PreprocessorDefinitions) + + + Console + false + + + + + WIN32;%(PreprocessorDefinitions) + + + + + MaxSpeed + true + true + NDEBUG;%(PreprocessorDefinitions) + + + Console + true + true + false + + + + + + + + + Create + + + + + + + false + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/ConsoleApplication1.vcxproj.filters b/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/ConsoleApplication1.vcxproj.filters new file mode 100644 index 00000000..388ab2e2 --- /dev/null +++ b/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/ConsoleApplication1.vcxproj.filters @@ -0,0 +1,37 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + + + Source Files + + + Source Files + + + + + + + + + + \ No newline at end of file diff --git a/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/PropertySheet.props b/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/PropertySheet.props new file mode 100644 index 00000000..b0c62269 --- /dev/null +++ b/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/PropertySheet.props @@ -0,0 +1,16 @@ + + + + + + + + \ No newline at end of file diff --git a/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/main.cpp b/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/main.cpp new file mode 100644 index 00000000..d5b80654 --- /dev/null +++ b/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/main.cpp @@ -0,0 +1,11 @@ +#include "pch.h" + +using namespace winrt; +using namespace Windows::Foundation; + +int main() +{ + init_apartment(); + Uri uri(L"http://aka.ms/cppwinrt"); + printf("Hello, %ls!\n", uri.AbsoluteUri().c_str()); +} diff --git a/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/packages.config b/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/packages.config new file mode 100644 index 00000000..9d957b68 --- /dev/null +++ b/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/packages.config @@ -0,0 +1,5 @@ + + + + + diff --git a/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/pch.cpp b/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/pch.cpp new file mode 100644 index 00000000..bcb5590b --- /dev/null +++ b/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/pch.h b/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/pch.h new file mode 100644 index 00000000..2eec5b92 --- /dev/null +++ b/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/pch.h @@ -0,0 +1,3 @@ +#pragma once +#include +#include diff --git a/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/readme.txt b/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/readme.txt new file mode 100644 index 00000000..403138c7 --- /dev/null +++ b/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/readme.txt @@ -0,0 +1,30 @@ +======================================================================== + C++/WinRT ConsoleApplication1 Project Overview +======================================================================== + +This project demonstrates how to get started consuming Windows Runtime +classes directly from standard C++, using platform projection headers +generated from Windows SDK metadata files. + +Steps to generate and consume SDK platform projection: +1. Build project initially to generate platform projection headers into + your Generated Files folder. +2. Include a projection namespace header in your pch.h, such as + . +3. Consume winrt namespace and any Windows Runtime namespaces, such as + winrt::Windows::Foundation, from source code. +4. Initialize apartment via init_apartment() and consume winrt classes. + +Steps to generate and consume a projection from third party metadata: +1. Add a WinMD reference by right-clicking the References project node + and selecting "Add Reference...". In the Add References dialog, + browse to the component WinMD you want to consume and add it. +2. Build the project once to generate projection headers for the + referenced WinMD file under the "Generated Files" subfolder. +3. As above, include projection headers in pch or source code + to consume projected Windows Runtime classes. + +======================================================================== +Learn more about C++/WinRT here: +http://aka.ms/cppwinrt/ +======================================================================== diff --git a/Samples/WindowPlacement/cpp/FullScreenSample/FullScreenSample.vcxproj b/Samples/WindowPlacement/cpp/FullScreenSample/FullScreenSample.vcxproj index b3eac3ca..43dfed82 100644 --- a/Samples/WindowPlacement/cpp/FullScreenSample/FullScreenSample.vcxproj +++ b/Samples/WindowPlacement/cpp/FullScreenSample/FullScreenSample.vcxproj @@ -21,8 +21,8 @@ 16.0 Win32Proj - {C1D15996-DAB8-4608-A6ED-CE0AE53EEBA8} - FullScreenSample + {6e745655-513e-4713-b3ab-d6d3f62d7734} + AcousticEchoCancellation 10.0 @@ -88,12 +88,13 @@ Level3 true - WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) true - Windows + Console true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;mfplat.lib;mmdevapi.lib;mfuuid.lib;mfreadwrite.lib;windowsapp.lib;userenv.lib;Avrt.lib;%(AdditionalDependencies) @@ -102,27 +103,29 @@ true true true - WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true Disabled - Windows + Console true true true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;mfplat.lib;mmdevapi.lib;mfuuid.lib;mfreadwrite.lib;windowsapp.lib;userenv.lib;Avrt.lib;%(AdditionalDependencies) Level3 true - _DEBUG;_WINDOWS;%(PreprocessorDefinitions) + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) true - Windows + Console true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;mfplat.lib;mmdevapi.lib;mfuuid.lib;mfreadwrite.lib;windowsapp.lib;userenv.lib;Avrt.lib;%(AdditionalDependencies) @@ -131,18 +134,24 @@ true true true - NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true + F:\os2\os2\public\amd64fre - Windows + Console true true true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;mfplat.lib;mmdevapi.lib;mfuuid.lib;mfreadwrite.lib;windowsapp.lib;userenv.lib;Avrt.lib;%(AdditionalDependencies) - + + + + + diff --git a/Samples/WindowPlacement/cpp/SaveRestoreSample/SaveRestoreSample.vcxproj b/Samples/WindowPlacement/cpp/SaveRestoreSample/SaveRestoreSample.vcxproj index 1436c81c..e6d8fdfd 100644 --- a/Samples/WindowPlacement/cpp/SaveRestoreSample/SaveRestoreSample.vcxproj +++ b/Samples/WindowPlacement/cpp/SaveRestoreSample/SaveRestoreSample.vcxproj @@ -22,7 +22,7 @@ 16.0 Win32Proj {92E6DB91-D852-412E-BD45-2EB168A1D090} - SaveRestoreSample + AcousticEchoCancellation 10.0 @@ -88,12 +88,13 @@ Level3 true - WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) true - Windows + Console true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;mfplat.lib;mmdevapi.lib;mfuuid.lib;mfreadwrite.lib;windowsapp.lib;userenv.lib;Avrt.lib;%(AdditionalDependencies) @@ -102,27 +103,29 @@ true true true - WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true Disabled - Windows + Console true true true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;mfplat.lib;mmdevapi.lib;mfuuid.lib;mfreadwrite.lib;windowsapp.lib;userenv.lib;Avrt.lib;%(AdditionalDependencies) Level3 true - _DEBUG;_WINDOWS;%(PreprocessorDefinitions) + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) true - Windows + Console true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;mfplat.lib;mmdevapi.lib;mfuuid.lib;mfreadwrite.lib;windowsapp.lib;userenv.lib;Avrt.lib;%(AdditionalDependencies) @@ -131,18 +134,24 @@ true true true - NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true + F:\os2\os2\public\amd64fre - Windows + Console true true true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;mfplat.lib;mmdevapi.lib;mfuuid.lib;mfreadwrite.lib;windowsapp.lib;userenv.lib;Avrt.lib;%(AdditionalDependencies) - + + + + + diff --git a/Samples/WindowPlacement/cpp/WindowPlacementSamples.sln b/Samples/WindowPlacement/cpp/WindowPlacementSamples.sln index db554598..56a1b03b 100644 --- a/Samples/WindowPlacement/cpp/WindowPlacementSamples.sln +++ b/Samples/WindowPlacement/cpp/WindowPlacementSamples.sln @@ -2,7 +2,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FullScreenSample", "FullScreenSample\FullScreenSample.vcxproj", "{C1D15996-DAB8-4608-A6ED-CE0AE53EEBA8}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FullScreenSample", "FullScreenSample\FullScreenSample.vcxproj", "{6E745655-513E-4713-B3AB-D6D3F62D7734}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SaveRestoreSample", "SaveRestoreSample\SaveRestoreSample.vcxproj", "{92E6DB91-D852-412E-BD45-2EB168A1D090}" EndProject @@ -14,14 +14,14 @@ Global Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {C1D15996-DAB8-4608-A6ED-CE0AE53EEBA8}.Debug|x64.ActiveCfg = Debug|x64 - {C1D15996-DAB8-4608-A6ED-CE0AE53EEBA8}.Debug|x64.Build.0 = Debug|x64 - {C1D15996-DAB8-4608-A6ED-CE0AE53EEBA8}.Debug|x86.ActiveCfg = Debug|Win32 - {C1D15996-DAB8-4608-A6ED-CE0AE53EEBA8}.Debug|x86.Build.0 = Debug|Win32 - {C1D15996-DAB8-4608-A6ED-CE0AE53EEBA8}.Release|x64.ActiveCfg = Release|x64 - {C1D15996-DAB8-4608-A6ED-CE0AE53EEBA8}.Release|x64.Build.0 = Release|x64 - {C1D15996-DAB8-4608-A6ED-CE0AE53EEBA8}.Release|x86.ActiveCfg = Release|Win32 - {C1D15996-DAB8-4608-A6ED-CE0AE53EEBA8}.Release|x86.Build.0 = Release|Win32 + {6E745655-513E-4713-B3AB-D6D3F62D7734}.Debug|x64.ActiveCfg = Debug|x64 + {6E745655-513E-4713-B3AB-D6D3F62D7734}.Debug|x64.Build.0 = Debug|x64 + {6E745655-513E-4713-B3AB-D6D3F62D7734}.Debug|x86.ActiveCfg = Debug|Win32 + {6E745655-513E-4713-B3AB-D6D3F62D7734}.Debug|x86.Build.0 = Debug|Win32 + {6E745655-513E-4713-B3AB-D6D3F62D7734}.Release|x64.ActiveCfg = Release|x64 + {6E745655-513E-4713-B3AB-D6D3F62D7734}.Release|x64.Build.0 = Release|x64 + {6E745655-513E-4713-B3AB-D6D3F62D7734}.Release|x86.ActiveCfg = Release|Win32 + {6E745655-513E-4713-B3AB-D6D3F62D7734}.Release|x86.Build.0 = Release|Win32 {92E6DB91-D852-412E-BD45-2EB168A1D090}.Debug|x64.ActiveCfg = Debug|x64 {92E6DB91-D852-412E-BD45-2EB168A1D090}.Debug|x64.Build.0 = Debug|x64 {92E6DB91-D852-412E-BD45-2EB168A1D090}.Debug|x86.ActiveCfg = Debug|Win32 @@ -37,4 +37,4 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F71C6299-3377-464E-A9B6-60CE7E909160} EndGlobalSection -EndGlobal \ No newline at end of file +EndGlobal From ee3ed1ac0b9697fdf43e9adf56e18b88c9baf261 Mon Sep 17 00:00:00 2001 From: Ben Stolovitz Date: Thu, 11 Dec 2025 16:09:48 -0500 Subject: [PATCH 05/11] rename --- .../FullScreenSample.filters} | 0 .../FullScreenSample.vcxproj} | 2 +- .../PropertySheet.props | 0 .../{ConsoleApplication1 => FullScreenSample}/main.cpp | 0 .../{ConsoleApplication1 => FullScreenSample}/packages.config | 0 .../{ConsoleApplication1 => FullScreenSample}/pch.cpp | 0 .../{ConsoleApplication1 => FullScreenSample}/pch.h | 0 .../{ConsoleApplication1 => FullScreenSample}/readme.txt | 0 .../{ConsoleApplication1.sln => WindowPlacement.sln} | 2 +- 9 files changed, 2 insertions(+), 2 deletions(-) rename Samples/WindowPlacement/ConsoleApplication1/{ConsoleApplication1/ConsoleApplication1.vcxproj.filters => FullScreenSample/FullScreenSample.filters} (100%) rename Samples/WindowPlacement/ConsoleApplication1/{ConsoleApplication1/ConsoleApplication1.vcxproj => FullScreenSample/FullScreenSample.vcxproj} (99%) rename Samples/WindowPlacement/ConsoleApplication1/{ConsoleApplication1 => FullScreenSample}/PropertySheet.props (100%) rename Samples/WindowPlacement/ConsoleApplication1/{ConsoleApplication1 => FullScreenSample}/main.cpp (100%) rename Samples/WindowPlacement/ConsoleApplication1/{ConsoleApplication1 => FullScreenSample}/packages.config (100%) rename Samples/WindowPlacement/ConsoleApplication1/{ConsoleApplication1 => FullScreenSample}/pch.cpp (100%) rename Samples/WindowPlacement/ConsoleApplication1/{ConsoleApplication1 => FullScreenSample}/pch.h (100%) rename Samples/WindowPlacement/ConsoleApplication1/{ConsoleApplication1 => FullScreenSample}/readme.txt (100%) rename Samples/WindowPlacement/ConsoleApplication1/{ConsoleApplication1.sln => WindowPlacement.sln} (88%) diff --git a/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/ConsoleApplication1.vcxproj.filters b/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/FullScreenSample.filters similarity index 100% rename from Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/ConsoleApplication1.vcxproj.filters rename to Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/FullScreenSample.filters diff --git a/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/ConsoleApplication1.vcxproj b/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/FullScreenSample.vcxproj similarity index 99% rename from Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/ConsoleApplication1.vcxproj rename to Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/FullScreenSample.vcxproj index 4951e9fe..a8634398 100644 --- a/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/ConsoleApplication1.vcxproj +++ b/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/FullScreenSample.vcxproj @@ -9,7 +9,7 @@ 15.0 {04a94fc2-e929-4327-9e0a-f70ef4059773} Win32Proj - ConsoleApplication1 + FullScreenSample 10.0.26100.0 10.0.17134.0 diff --git a/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/PropertySheet.props b/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/PropertySheet.props similarity index 100% rename from Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/PropertySheet.props rename to Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/PropertySheet.props diff --git a/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/main.cpp b/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/main.cpp similarity index 100% rename from Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/main.cpp rename to Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/main.cpp diff --git a/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/packages.config b/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/packages.config similarity index 100% rename from Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/packages.config rename to Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/packages.config diff --git a/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/pch.cpp b/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/pch.cpp similarity index 100% rename from Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/pch.cpp rename to Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/pch.cpp diff --git a/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/pch.h b/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/pch.h similarity index 100% rename from Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/pch.h rename to Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/pch.h diff --git a/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/readme.txt b/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/readme.txt similarity index 100% rename from Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1/readme.txt rename to Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/readme.txt diff --git a/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1.sln b/Samples/WindowPlacement/ConsoleApplication1/WindowPlacement.sln similarity index 88% rename from Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1.sln rename to Samples/WindowPlacement/ConsoleApplication1/WindowPlacement.sln index ae42bfae..c2775eb5 100644 --- a/Samples/WindowPlacement/ConsoleApplication1/ConsoleApplication1.sln +++ b/Samples/WindowPlacement/ConsoleApplication1/WindowPlacement.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.14.36408.4 d17.14 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ConsoleApplication1", "ConsoleApplication1\ConsoleApplication1.vcxproj", "{04A94FC2-E929-4327-9E0A-F70EF4059773}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FullScreenSample", "FullScreenSample\FullScreenSample.vcxproj", "{04A94FC2-E929-4327-9E0A-F70EF4059773}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution From c35fbe66a50e62f2538fa30a809860678a80edfb Mon Sep 17 00:00:00 2001 From: Ben Stolovitz Date: Thu, 11 Dec 2025 16:27:15 -0500 Subject: [PATCH 06/11] ok, wil? --- .../FullScreenSample/FullScreenSample.cpp | 325 ++++++++++++++++++ .../FullScreenSample/FullScreenSample.vcxproj | 34 +- .../FullScreenSample/full-screen.ico | Bin 0 -> 67646 bytes .../FullScreenSample/main.cpp | 11 - .../FullScreenSample/pch.cpp | 1 - .../FullScreenSample/pch.h | 3 - .../FullScreenSample/readme.txt | 30 -- .../FullScreenSample/resource.h | 2 + .../FullScreenSample/resources.rc | 4 + .../inc/CurrentMonitorTopology.h | 0 .../inc/MiscUser32.h | 0 .../inc/MonitorData.h | 0 .../inc/PlacementEx.h | 0 .../inc/RegistryHelpers.h | 0 .../inc/User32Utils.h | 0 .../inc/VirtualDesktopIds.h | 0 .../inc/WindowActions.h | 0 17 files changed, 352 insertions(+), 58 deletions(-) create mode 100644 Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/FullScreenSample.cpp create mode 100644 Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/full-screen.ico delete mode 100644 Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/main.cpp delete mode 100644 Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/pch.cpp delete mode 100644 Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/pch.h delete mode 100644 Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/readme.txt create mode 100644 Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/resource.h create mode 100644 Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/resources.rc rename Samples/WindowPlacement/{cpp => ConsoleApplication1}/inc/CurrentMonitorTopology.h (100%) rename Samples/WindowPlacement/{cpp => ConsoleApplication1}/inc/MiscUser32.h (100%) rename Samples/WindowPlacement/{cpp => ConsoleApplication1}/inc/MonitorData.h (100%) rename Samples/WindowPlacement/{cpp => ConsoleApplication1}/inc/PlacementEx.h (100%) rename Samples/WindowPlacement/{cpp => ConsoleApplication1}/inc/RegistryHelpers.h (100%) rename Samples/WindowPlacement/{cpp => ConsoleApplication1}/inc/User32Utils.h (100%) rename Samples/WindowPlacement/{cpp => ConsoleApplication1}/inc/VirtualDesktopIds.h (100%) rename Samples/WindowPlacement/{cpp => ConsoleApplication1}/inc/WindowActions.h (100%) diff --git a/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/FullScreenSample.cpp b/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/FullScreenSample.cpp new file mode 100644 index 00000000..9aa99b26 --- /dev/null +++ b/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/FullScreenSample.cpp @@ -0,0 +1,325 @@ + +#define USE_VIRTUAL_DESKTOP_APIS +#include "../inc/User32Utils.h" +#include "resource.h" + +// A FullScreen window is sized to the monitor and doesn't have a caption +// bar or resize borders (in other words, no WS_CAPTION or WS_THICKFRAME). +// +// When exiting FullScreen, users expect the window to move back to its previous +// position, stored when entering FullScreen. This is similar to Maximize/Minimize, +// except apps must track this restore position manually; the system does not. +// +// This sample demonstrates entering/exiting FullScreen, using PlacementEx to +// implement this memory. PlacementEx remembers the window position (when +// entering) and uses that position to move the window at a later time (exiting). +PlacementEx fsPlacement; + +// The last close position is stored in the registry, using a string that is +// unique to this app. +PCWSTR registryPath = L"SOFTWARE\\Microsoft\\Win32Samples\\FullScreenSample"; +PCWSTR lastCloseRegKeyName = L"LastClosePosition"; + +// Called after creating the window (hidden). This picks a good starting +// position for the window and shows it. +// +// hwndPrev Another instance of this app, opened prior to this +// instance opening. If this is not null, this window will +// launch over the other instance, in the same position but +// cascaded (moved down/right a bit to keep both windows visible). +// +// isRestart If true, this is a restart (not a normal launch). The +// system restarted while this app was running, and we're +// being relaunched. +// This allows the window to launch minimized, or cloaked (on +// a background virtual desktop). +// +void SetInitialPosition(HWND hwnd, HWND hwndPrev, bool isRestart) +{ + PlacementParams pp({ 600, 400 }, registryPath, lastCloseRegKeyName); + + if (isRestart) + { + pp.SetIsRestart(); + } + else if (hwndPrev) + { + pp.SetPrevWindow(hwndPrev); + } + + fsPlacement = pp.PositionAndShow(hwnd); + + // Repaint now that fsPlacement is set (checked for when painting to pick + // background color). + InvalidateRect(hwnd, nullptr, true); +} + +// Called before destroying the window. This stores the current window placement +// in the registry, as the last close position. +// +// If closed while FullScreen, make sure our stored position is on the window's +// current monitor (but do not refresh it). We want to launch next time as +// FullScreen, with the restore position that we stored when entering FullScreen. +void SaveLastClosePosition(HWND hwnd) +{ + if (fsPlacement.IsFullScreen()) + { + fsPlacement.MoveToWindowMonitor(hwnd); + } + else if (!PlacementEx::GetPlacement(hwnd, &fsPlacement)) + { + return; + } + + fsPlacement.StoreInRegistry( + registryPath, + lastCloseRegKeyName); +} + +// Handle keyboard input. +void OnWmChar(HWND hwnd, WPARAM wParam) +{ + switch (wParam) + { + // Enter key toggles FullScreen. + case VK_RETURN: + fsPlacement.ToggleFullScreen(hwnd); + return; + + // Space toggles Maximize. + // This is not done while FullScreen. + case VK_SPACE: + if (!fsPlacement.IsFullScreen()) + { + ShowWindow(hwnd, IsZoomed(hwnd) ? SW_RESTORE : SW_MAXIMIZE); + } + return; + + // M key minimizes the window. + case 0x4D: // M key + case 0x6D: // m key + ShowWindow(hwnd, SW_MINIMIZE); + return; + + // C key moves the cursor to the center of the window. + case 0x43: // C key + case 0x63: // c key + { + RECT rc; + GetWindowRect(hwnd, &rc); + SetCursorPos(rc.left + ((RECTWIDTH(rc)) / 2), rc.top + ((RECTHEIGHT(rc)) / 2)); + return; + } + + // Escape key exits. + case VK_ESCAPE: + DestroyWindow(hwnd); + return; + } +} + +void OnWmPaint(HWND hwnd, HDC hdc) +{ + const COLORREF rgbMaize = RGB(255, 203, 5); + const COLORREF rgbBlue = RGB(0, 39, 76); + static HBRUSH hbrMaize = CreateSolidBrush(rgbMaize); + static HBRUSH hbrBlue = CreateSolidBrush(rgbBlue); + const UINT dpi = GetDpiForWindow(hwnd); + + // Create a font of size 30 (* DPI scale) and store it until DPI changes. + static HFONT hfont = nullptr; + static UINT dpiLast = 0; + if (dpiLast != dpi) + { + if (hfont) + { + DeleteObject(hfont); + } + + hfont = CreateFont(MulDiv(30, dpi, 96), + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + L"Courier New"); + + dpiLast = dpi; + } + SelectObject(hdc, hfont); + + // Background in one color, text in the other. + const bool fFullScreen = fsPlacement.IsFullScreen(); + HBRUSH hbrBackground = fFullScreen ? hbrMaize : hbrBlue; + COLORREF rgbText = fFullScreen ? rgbBlue : rgbMaize; + + SetBkMode(hdc, TRANSPARENT); + SetTextColor(hdc, rgbText); + + RECT rc; + GetClientRect(hwnd, &rc); + + // Draw background + FillRect(hdc, &rc, hbrBackground); + + const UINT nudge = MulDiv(30, dpi, 96); + + // Draw text + + rc.top += (3 * nudge); + rc.left += nudge; + + PCWSTR toggleFullTxt = fFullScreen ? + L"ENTER to exit Fullscreen" : L"ENTER to enter Fullscreen"; + DrawText(hdc, toggleFullTxt, (int)wcslen(toggleFullTxt), &rc, DT_LEFT); + + if (!fFullScreen) + { + rc.top += nudge; + PCWSTR toggleMaxTxt = IsZoomed(hwnd) ? + L"SPACE to restore from Maximize" : L"SPACE to Maximize"; + DrawText(hdc, toggleMaxTxt, (int)wcslen(toggleMaxTxt), &rc, DT_LEFT); + } + + rc.top += nudge; + PCWSTR minTxt = L"M to minimize"; + DrawText(hdc, minTxt, (int)wcslen(minTxt), &rc, DT_LEFT); + + rc.top += nudge; + PCWSTR escTxt = L"ESC to close"; + DrawText(hdc, escTxt, (int)wcslen(escTxt), &rc, DT_LEFT); + + rc.top += nudge; + PCWSTR cTxt = L"C to move cursor to center"; + DrawText(hdc, cTxt, (int)wcslen(cTxt), &rc, DT_LEFT); +} + +LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) + { + case WM_CHAR: + OnWmChar(hwnd, wParam); + break; + + case WM_SYSCOMMAND: + + // If the window is maximized, FullScreen, and someone is trying to + // restore the window (for example, Win+Down hotkey), exit FullScreen. + // (We do NOT want to remain FullScreen but restore from Maximize and + // move to the restore position...) + if ((wParam == SC_RESTORE) && + IsZoomed(hwnd) && + fsPlacement.IsFullScreen()) + { + fsPlacement.ExitFullScreen(hwnd); + return 0; + } + break; + + case WM_PAINT: + { + PAINTSTRUCT ps; + OnWmPaint(hwnd, BeginPaint(hwnd, &ps)); + EndPaint(hwnd, &ps); + break; + } + + case WM_DPICHANGED: + { + RECT* prc = (RECT*)lParam; + + SetWindowPos(hwnd, + nullptr, + prc->left, + prc->top, + prc->right - prc->left, + prc->bottom - prc->top, + SWP_NOZORDER | SWP_NOACTIVATE); + break; + } + + case WM_ENDSESSION: + case WM_DESTROY: + SaveLastClosePosition(hwnd); + PostQuitMessage(0); + break; + } + + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +bool InitWindow(HINSTANCE hInst, bool isRestart) +{ + PCWSTR windowTitle = L"FullScreen Sample"; + PCWSTR wndClassName = L"FullScreenSampleWindow"; + + WNDCLASSEX wc = { sizeof(wc) }; + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc = WndProc; + wc.hInstance = hInst; + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.lpszClassName = wndClassName; + wc.hIcon = LoadIcon(hInst, MAKEINTRESOURCE(IDC_FULLSCREEN)); + + if (!RegisterClassEx(&wc)) + { + return false; + } + + HWND hwndPrev = FindWindow(wndClassName, nullptr); + + // Create the window with the default position and not visible. + HWND hwnd = CreateWindowEx( + 0, + wndClassName, + windowTitle, + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + nullptr, + nullptr, + hInst, + nullptr); + + if (!hwnd) + { + return false; + } + + SetInitialPosition(hwnd, hwndPrev, isRestart); + + return true; +} + +int wWinMain(HINSTANCE hInst, HINSTANCE, LPWSTR cmdLine, int) +{ + // If 'u' in the command line, run as DPI Unaware. Otherwise run as + // Per-Monitor DPI Aware. + SetThreadDpiAwarenessContext((wcsstr(cmdLine, L"u") != nullptr) ? + DPI_AWARENESS_CONTEXT_UNAWARE : + DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + + // Initialize COM, needed for virtual desktop APIs (USE_VIRTUAL_DESKTOP_APIS). + CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + + PCWSTR restartCmdLine = L"restart"; + const bool isRestart = (wcsstr(cmdLine, restartCmdLine) != nullptr); + RegisterApplicationRestart(restartCmdLine, 0); + + // Create the main window. + if (!InitWindow(hInst, isRestart)) + { + MessageBox(NULL, + L"Failed to create Main Window.", + L"ERROR", MB_ICONEXCLAMATION | MB_OK); + return 1; + } + + MSG msg; + while (GetMessage(&msg, nullptr, 0, 0)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + return 0; +} diff --git a/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/FullScreenSample.vcxproj b/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/FullScreenSample.vcxproj index a8634398..58f17d26 100644 --- a/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/FullScreenSample.vcxproj +++ b/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/FullScreenSample.vcxproj @@ -63,12 +63,10 @@ - Use - pch.h - $(IntDir)pch.pch _CONSOLE;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) Level4 %(AdditionalOptions) /permissive- /bigobj + ..\inc;%(AdditionalIncludeDirectories) @@ -101,24 +99,33 @@ - + + - - - Create - + - - - - false - + + + + + + + + + + + + + + + + @@ -126,5 +133,6 @@ + \ No newline at end of file diff --git a/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/full-screen.ico b/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/full-screen.ico new file mode 100644 index 0000000000000000000000000000000000000000..efff82cd2e0a44432151c3bd729691fb17ee0233 GIT binary patch literal 67646 zcmeI52V4|a7sXdWKoon!-g`sry(IQ-V%ONCCecI_HKy1#cB97Fd%=p>yVyYN4ZC7P zss&L|EcZM2?Y?Dp*j;83BFX3E_sRXLGW<7kqz?bjt1If1OqWHc z(-j4Hi{BLQ^HYA7F_liBfBotIpZ36~J@9D{#JL9$MRdsx*eRa#`=6Ho_8wr}9ROMm zU0RSHq%%Bs!DDK`|He3jl)y=WBcAj3nH0d@fYL|&H$E*%a}TgyX+Ta;7!(J^0H+v* zKmm{+qvIAF;4P*tJnq&qUK?Web#J{if68lK)C8dw7dFfM3!p=*;#c4s~ z{H=elo}>4GirIeX=bm*agWqom>VwY=)Wu_MP!m)KRY4U{5tIk+pd2U*N`VqUdZ`GW z7X)e_<-&VPFDZS*UdjZNKH~VqUNRSxlyOPvC5}llE^$m!#-&eQLR|OjS(c9XC_}#8 zOACCul_KWYS0(9a*AjyL9gDu$)TWUC#?}S=H@3=uiJ8|R*VY+zbFKv*)9d6|!}KVX zVR+Ay>4)_ym40Xs(5hgX{@CXYz~_LyR1dJ1YJnO+dZ{v=tG!eP?N1mzLMhc4lF@^~^ED$X zq+i&nNb2ET3a1{{wQ%a8T?;!8&6nD)4c3=qlG00c@LI;CYIx3ZiM><-a9rY;~4Jt?e6o>A&2*CCB!qJ<-ce=>Jc8fPJvDRc^tKmbqwqi=2XO&2!M!X4wT>nz|C> z)g&8jZk$!%*(eKbYM5EDu|X!9Un{-;yqf9!=haBdT$)wY#c!lr>a`=vIeUyK>+JD+ zS?4taiaY(;HlM@K?eaSeYnRu4D1+^et9@fHar;&Yu$Q#9Z`{V^1$lssNitVb#w2s? z8|U4!eUoimT)C36ePcasS(2}5Q?%*H-sR}u?12sSGZM4@^9+J@pQWd@_0kDE>ZYYN zbapDu-@(>m0gacpS>q%dE51zSV{h@oC>U2e+0hnQPy;ZR6aCb0E%d zv@UiYt&22c{nZ{gJERd^81bdWd?2CP0vQK527E{yNC*$G50+JP5-hFiNK2|X2o_gP zL5nKc3l>(iBW6K`59Y_hx;5Vw z&@F7?svF+Y6<%_Mmw;8>(VgjV4|h%Zz<<#LN(9NTH68pV}Wc}412s^gLoc!^AFDmo_J@!8?2tBcf zZmpX^H`mUftBXg|iQn1@jty!kV!(eT4t$92wtlxwcHNKAo%=_eLul1M#lEk~Og{Z} zd7;131JACWinrDY@x_-R_vz6^9|{Ne_phUCtEYk~bYg6GIyS5|9R-$h!lcv|*s5cE zKhNz4bmw{t*W9$`01kV*mYH&FBKYvPL z=Z^{Q>{&yXS4;vE$!BC2^8T?Ic@J(z@yQ7jDj$firnY=9p87jV{nZ|br9W%``U!Xp z9ud7_!VQEGMTLS81Ht-7+%<4V|0q8n1rG_Y!Efvv8F${lQ?6B_ zK7S3b1fMw!4$;jW%jw*rF?4p}7&?ui(8*&r==hOqv})N|n)}xYnmhXh4gTQ(_3F8Y zzV5S^>eSgxZspcfg^C*}i|a~qOtqAB_KRu$Up~;EJrFDtz%bZXv6 zfzOohX=|U_Mzm^b%je>$e@fpz6?|ELwFhGDwl}PQ1WliIipp2?B=-uNs8pE^lq&5q zp&x4)f0od^*;0RK85yA60})pU9a3-^_#3!H^n|$xE)YFFALAqTR+J$war^Zq5+J_3 ze;aF)t-bN`EZIOM3eBwb2jOzAyjn*~0+%<)D!ZtEU@% z!~WSlxTVoI?4b=E%9_*~q(|bZzccH9{+r4|f29ZH`WYV$`giQK*MiQA@IH>^)Eh~c z#tUpET^4WBW%B{YNSFH}>GE9zS4davA?ZpzC0(Ha{pXV}FdYBf7H|yE-~kZ>3~@lK z2iOOX&w;ag9-Se2blL#&-SCs(1Y!&PBtX0oZ5G-qZkITBVUW8#di09E8Q@C}saI0& z{A=m6&$m*q-rh8B{8?JR0deqD7~Q)4jKaf_=OpkVHCH~yQ~y+F`&W)&{gocLF}-i> zH5U3mBcZ?UFTI|uVadm&bALm+3S=9V@%hZ~2ehwScQakT!0}v}3s~|1w*!W@ATAFW zV}K$KSo8qvukZl-Al#2Aj5!W`^@Ji$g&w1a7rm)usa2$N1Zj7YU0P4FOSyuaQZJ?4 zxtCFms%xl2`yDiN$YENt=sbCQKOq0hPw4UE56^d%y36PB)Iasb@2b*;Z>tFXr3Y?K z?`KAT#7X2~9XstKoo+Vi3fv%FB^x!D9%1eI*mqt_I=cn5YAKGp7cwWb5AMVUEa&2b3IPUGVG#cE*nVaw2JJVmQapt^Qe58 zh1B%(Wi+7oI+{7bi*~F#Ko^c2p@;zZi`yyTX~T9s4?r$s=wF%1VWj5D$9U+^HDVXm zU+n?&`g2YdNndr_uh&06VtK`Q=+EC_GN2tvKA&2*-cI+fBgYf^CzJ;`7hnweKwNP^ zv;(p&uoMS41{mT1Jn%NihmP&oM;SAt&2w0;Z`;_{?$P?Tt;l`S@jl9al9FXPKsuMr z@WX1c&6zUHp<;#RQQhjxsC!2bn)Lfd+O&8xT|TsxqHgaX3Iq?f>){+lwJ3F$&*Py# z*9g;I8cuk=9RhUu!mh?j<(x>xUmdj0dF9j}P(mB+N!$13j#1IBhB#slv|d}zY&$aC!GBL;Yq z&h0trc>5~8$9`e(oZH6Ik;r+1NmuYT>2jSzUUV4w(H68FD@d1YE~QN~i}L20LzUc@ z(3dS%)0koF$!n=6-9NcS9|u%wNX?ax@zLL_OSz{01FMtT1Hs!Dn*K%Zb94I-4}9}2 z)}7n`yq9d(p8ewfmUQWNiQ}Mmue}s@AMHHrul7K!+JH3=sQQI*dH{U_*)I_K$KL~? zSB_9QH$JWxkS^;HX#Y-sT=2Gd~w z%c)1lH53sRVyXpRDRr06<|S7u>KFtc)(nJ=tDiA>KAJA0QU(D{Q}Mj&BOuE<+~}F{X*md zi%C})nsPg!-Y04=aPGwLK6H!Hw;9C|Bl06Q^6`P#Sg+P9de3=)YLS{NALF6F{rZk& zTU`FWhR|Q>fyc+UoBl;U{tflFAwzs9dGa~P|Igd5KktLGFUc;KA0;?-DvEQ#BIm_)oFFM}_ZLf3*jm-#{*;T(~x3 zIQR4O&%R`D|2OG!p4P{5UU#+Et-Tf=K>W*fmUJoC2%pQkQPpajXw%v=6!i$2%YL`H zI&ow`!q$gO;()cyUkZl`2+^Jv#E!^~1dR*U*HqN9e%~ zRX| zcUVke7k4D8{zt#AJmu=3TBP}z(Oz2xW z;21)?zGu`9I#Xy;PVCWin=X`t3LEp)fR}_kj5IJnflPHKp$${05ySRI6g8R zKzy`u|8UIU96&dViWXT;^l*1#>hE~u>nc-Of3*jc(u?P;KcA;X-E-QsbCf0xJf4vH zD}BIyrJSckU%62u#6P3{G9S?5fsa!kdVKRJ?f2YA`ST->=XN0f4clTsRp_4+?IQXB z-}GDu{r5os$0l-wUpt#n?Z4Bx0X2NC4X!QpS9-wY5Aq8h`@#AHz5d8`xu);-%JT8g zUM?0yx-!sHYmDH}ar`s%4;wYws>lf~)drMxV#EMr97sS9Sf~wz-|!`7^U8g+Xy$I} z-f1J%tFxAJX2j)m&$UF;LB?F8q*oEM@EaCTWrd$yx4 zYOXdQazaaSK&uBXYp4&!v;|LYpQMm$Cn)r~AFW-ypXN^6P2D+Eq6M+Vl879tW;v z{Gao=fi-=v|5%4uf9ZjHe+@LFKgWO8KbnO8T=UF)Nc81mZJX4dYvNoJ(V?%XrLExa zm4Igq-~0X7!xZ(H>q6#g1LoR-0MmY<#W--)<{DvC&>31We?PTqzL6?b^q_os79j^l z%@2KkJ3DM&N)P>dXF+WwuRn6yTl%)2w@J2baXpIthPK~vB^4~NjDjwp?GM_gS%1U$ z&qC0i$Ny>>|DEc}pxXCu{8X2e9%;2)|HhFm{I9ouga=X#Pm z!TZkW>$~7J`sup-*KwTR)$a>Feko^>xag1NluR#5ZNz&f8l$0)1E1!*Se~ zEAL4@NAFP6W;@XqqF#{osJ_mn^ou+uxZP8n6Iizz^;j{MFgOkmaBYZDwguMWz;*Ny zfU#f5vEYiK9k^_2?V13l>>Ne!kEJ=L;7uxIme+a;*<-LE(F`j|<*z_EB0K zPly?P`|hXkK-B+~V@cIxcqu&$E`U4(ssaO(7f4dz$D>u76>~o>N(gV*< z?o#|U`Bduvj&|+3M+FL^O;3-w&c`ipBj>s}Uy(NB3fi+fK!2`4=>I~TGxYMjCbZQh zL(asxk@P?Bzd~p`oe^8%oq2Oma~EUE!#>ah@LwX-2zPBfLInySx5u`MK57}XchxlL zFXH2KN?zcK-a7@*&R0g;A@>76a=j3FQ<;)W>5vyR2tKIyK;r0cFZEY@;N>l}y=E7^ z2lnrOK!u9nIN(@c3b`Ji%f|jl>9UNxHX~;f=Lw=>c|eRC;`1Zv;G0s|Z>&A%ONPFF z%T_z-?oGsWIqrpHfJq!cF2G~L<@j)maX^_1zz1=~0UK(BZ^O^ix~1M!sGz|EoCB3L zY>V<(S3YNL%i`G1a!5bsfop^8cg}Z=Z2{r|9~Wr*t5#k`q5f$1gT3`0h^4=`+5W$c z@!x)1w~8$Sey&ffztRJi^=DJOr=v%Ms8|WCKOgh#ffV{$_1d+_s|+<4DdNWocFb} zpGOTp_aF*6WY8ZTFrz(@KO8<5%qZyw_qv$FV|2&EFG(EmvZ z55TXy50)&sVyF+iw&H>3)VCjw0pxh1F2>`4c?=Bt3ggE5QS@`1=a$!d2oGS~lWYqT z)C0FZ*aK0)=V;~pgE)5${kc!TwUF}2-_#!9wt>fY@Oe&b_slWT7O($Ag?`_+Vi80`a7H+SbH+-ul9fy>BT>t zIvq~#m9~kouAB?a{>vY!;Twbg(R%$Y!~y>c56R8lQ$J3E$IRi_uUvIA`JYE$;q_C! z*M$e*1!FtFK8Pz0m>EA5t40{0XbZG@;IhRU;j57IG-2d!N}CS)V>`q+_wvw@;~K{R zKDSvCV|=ikb~u;UyvcfcdfP{wFT8p3FvbgPM7xP&fyWPX%?RV=li~IDHE8>V{_sFB z;(!JH)gG`>f5+1U>rA=(OGBZ*(gRxb-|TgVytdq>Lx&zx#M3Ba-%!>F<+%d=HGcSa zdF>W*!XZPBkxPbEk@(8LH21Q2(r0!Gj+9Lwm*eq4S5lsdKvxIOY)txPFLz!RLH= zTsegtiHQMxTqqX4zDUkLq4a=_`a7K)RB!&hUmKCq1A#O9DgIJiPmvEQ`i0{98AFXw z?*ncNUO{^%#sg1)&P`z4 z$g}%rY3L6-(5Ax!Jf4$lIOSgH^F8Fn#fz^ZuT{wBRAb2Ag&e1=#}8AN_8TcxYHmlC zP_Ep|ac#j-gZ>5&@HKaeB?$WSTqJ4_*sA~aq4j-Of29Y47yP97OIn^<^MD~2Fyn!k z+Q4&BBZ|>K0zR-72h@E6_JQmZaIM%-8!(9jXj>B81FV0H2ZFAhrh)ypiCjkK01xmv zFMfpn>z5v&Hz6kt`t#!nlQt=Of~F zz()O@uMKM;^jCV|>Hd{k4tg`6fd@o=@SP$Th{FSl`jGw_O!&aq4tz));22e_&@UtnkljBSAy z51=-b2sJ`+tue-tJ-!XitzKs;*BEjvIIeF8WLv=fBE=&0S9`z){gW?f?^Zf!RC7{$AYt_9m5r|l zo{RHEA|DXBfE5oUczp=%fiWM@S|8$^fNMmCoY2@Oun-4?{$~{P4X_VnJHW9()fR9r zpwge`qTx9zmGcpC9Dx4yKt2%H`F}g_el?r1{%Q|I2VuJtY>^(|aU=S%BbMt!%Cxa-9qTE^gx11FMbI6 z8{>c$54_aZ2}Mq5%>!H?{P)xdV?1y!e%BgX&|f_-83eBd_}(0G>c6W`t(L66+5-uz zKmVE*55%q!#^eI*1DOwSpD?a|q4Hb-_X(J|>O+ZNBh=;rZU+=?fxNazt^e7>BM+^mmoObM}y~?=lP$lEe`&}8cqov+lrJPc(iPI zBI(aRtM|c)d;oJ29#}qmiiFa2$X$-+lqc6*NV;6iMcJzB1F|_qsW0n3P&|lua zKzhJN{hh83Z#+M2Tx(+er3WIs{!9$fRllj#1Kb}__6c6Z^b6JbK%&%#xIHlFuf+p4 zj3JAO0q0}#L5uYvJZ$nBCJU`^2(kgy0c|J_*iv|yT zsu3a=G}H!S>3_s{4-@HuS(Q@SqJPSJqgwj1{z?zLIQ-%IzkdCWmMyBJI%!L|X4_K=WsOrT3g&Ls+{gob=RoU5=<3Cl{_%;!&ztRKIchO%_ zEj-3+%hr2hu2a=N#d>+k{|V*HqaVklONX%#xZcAa*UGJ19}w%aF>go@XsHh=^8u|M z_&3!EE!2m2Zeqp4bJwfyX=S7Ssh>=2OOOBfQs^%|U`~Ix@?K(GQu@p*Y5Jd+=sn~z zw_LdRnA)`6FUGivG2z8=jSFAvgR#Lb=~sxcg66&#yI;sYkmCl-oiF-7tPyg)t5`w} z`YS!~R~602KcD+^Vg1z}cprshO0n=fwa#fUMi=AKc#L+&tgGnnc~|JwE7Sar=RR-O z@gSvez*ttE&$aL!jAzE!*Rp&~AFh?d7^u7j*3sU57^7=;VN=Bw2R?R<(0YAn(;7b# ztUgXVJ#ij~$Bmeb9l2`eI#Zl8GM+EGfagHWKck8RX7uNK+p+%A19PgT)*AoCoS#o8 zwG;X)J)oAx#~1VQ@b&wtFxH>vHPrnrj^`OOvEGJ-wVyEYJUQ_k{yeU~z)k)99ThSD zwG!a#!wlE+7c8=Y_&3cj);y53YlM-(Hz`N1RpgR!4W-Gnn##JZqqk2C`p4-3w7KxW z*&}|mZQT()YY!Xxh3o^28;GUi|ZMK1)xYyr$iI9^m-K*i|0OpV?bK?zsYVm#`0b{uA~P{E-9s!R-Lk_AKV_ zsy*=Us1bhKe~;KFV!UUHCF1$2#W-(kwmzhc12~Rw-uUXNQ@B2QIi<<4lAP17q=mB& zn8pDM`pf$nu?POH>Z0}dxAPg?uo>&G^uV(vKgGykc-HER{bZj4W7B!ge!f0QdI0e} zO@3*YL4+ zJa;c&?^EWv{#qcsH))(V^uHP70acAK^wtF`SP0iq^Y=M#;`?Oa`HYGC6#An^XhDCz z*DdSM*r0!kGeetnW&M>Nc)n(oiTv(SUG+N_- zihz-=Cr3{0DD;;eh&r%L@fV0zth|ZqTXC#rMBB#gHy^)p`}mRPd*EwV*$aGqtMcEh zJ?Gyk9H93A@`k1@_R^a-7&C5mG3Np96EcRJFfI>p?L;8Q4XElv>hZ)H#}VGWc7syO z`yR30%5COrfB2d{XgTah9Lq-i^=-k+kSkQNBCh{r?KvKn#5MH%{T#S<4DXHlWw$|p zg9lXlhe3b7@43brl&P2Kukq%2Zj{@{&soPR zK>fa^*YEY-5%5*~*N@}2I0jVvg6GDD zmJZHK=)v{NMi0CWzm9v|Ag;3Z90z&6Gr7rCn9yJ80lx3O z#`C|9w@0-R`YS!~+9yW;h=^$VZNzD;A98y3qWF zZcwY1Tu;OI_Rk=n$axXhgLrOy)}8ZY{(ZEaZQ6NL zVBibm-;ezS`o~!xLVI9r3$%K`_Hl$ygYQ$0+*ohcF!Mq4+RJ^wwGe!c`v+%z&I=#t zwO@icZ|=nYZXR4WiqH4!z1?&?v0DEN$H=o$7E*e^pnny_fE>Wu_#db6iS2z@f29ZB z-oI?z?R-zeGH!@b+%{R8vrlfP`jNHge1`80#`_>Eazviflu2q;A~febu>4#7ec^a+DBf0)>(9r$CUfzd%Zbk;^#1xN z_33+z_UsKLjy2bBM9{l;tZ!V484uWWohi2m$TN)jU|b$B9Y=WNz?GQ3K5J!df5?C1 z_JQ|o&iYFa{8>Hyeh6LxWCISC#K-&2VUs%e zM*Z29lpc5&ig8X@I2Y%0xH>zmGtX_LHTJXqrE$Ewz~{K`IdkSkmt$Mq}h7oPu*k8QPnmR=XWtN)%g z?fJVr|C7|;I47k)*7t?U?g~fzSKjxYJutdrS`TRJ4zdEP`ahl8(Vz8KdO-OHxUMJW z>vTmQzbYTsHo$!$#P$-UJ?Wr#kT|yaey9n%zyq4Bf?CWdYH|&A9K5NjwNn=mLxuPrjIABJ9uO`_{JTSImx-|yn>8=dRz4ROCNBZk8316_i=o6m*3<2 zL*jR1^uGolzyohznCzeMi$1iqS#}Wvqz5$WFV6k5{z?x-Z=7KKeXje7`RrYN_1_=ks7vPg@@i`j>TEi+bU8Lkxfi%;>+Zc@7f~Xx0DXuPvLt`KyP} zUwR;V(;q?>9^2Kn1LibzKHRqzU7EFyFPl*3VwB&%7)C9^kwZzfr0*>#sjoAld;wzo}UG_-Fl<9+*%$ zgO>cy?%ePe&EC%GNlFjAG3d`Vm{MiYuJL#;o}-q1&GU2eJr1~D#q}Jnb-N=k5cA%{ z8)|<@&$CbXJP6+t7r8+DP6riRkQh&)|3|73zI`4-9xKnr^bdJHW4Rw}dE}UheO|Ee z8iW1@52*Bi1pT+S$Z6sM&HDe=TIjFzz}q9Mje=af^q5ACIZIV*Y}23H=P_M;uU8)9 z!RG_gVoU&^r{o$I->0lB>be{!xXowHm44vYoGY`Bz^>g7jDII_o@ns^_X(LOeGD*; z9Z<&s+iHZXmYvmar}|v5E&6lK6#I$Cj>McZL^}{me_x@$(gPE!X4G>0+n*fXvc4?sdl{`PgmUM@{J!jSKCfYm=E~3VITHLv z=4>kr`rp*Y0W?mnKOSVN!tUcF;SVN_zwmj!Jir-0#I(f`J?BD^l{+E1dXRF+#^nh0V zLnn8c%=#-m@Lr?->K%Xc)-!Q!$i&GPs9TRCxYyHGDpc5mGH1bgZQdVz{*&v$JWo82 z4|G^5@{8bL(|kt#y9x6e9{2}pgpY!PFwO_(f_QuR9%r`bt^O>Jslo50OuZEP-;VKs z1^tyC(5Sxy>reCg2>qo8-k-$1W&*F$`!I}g|A2+hqEna8peQe9f0#)wWeBLbGy&? z{$Nb>H+aB|{<~V|5ivk|U{bZrTH1eyph=ylu>NWfDDweXAK-pL^ri{K^?~R;3+T=9 zO@e6OP4o`ui}{=(^Zpg~nQD3Q;*B`hbM#mUty+DHejj-TeE@HoIr}nkjqx8{xMQn~ z1?qeto*sA*@GzzZz+*S0h6S$c$2al$JkFIl|6%xE89Yvu;kki1W|%>L{0A6f06f6C zs$zMJ_`kbNUQ&8MqyCP86FW|o`fKrktP!fu7pTV%$gyORvwG4CkJ0qpV-)e2(x{{B z!8!`tyOwV4-AiY7pCG=@>`gGXFG(!$24Vc6(I1>E80v?jk09d$;(_tJu|5v4{ycuc zoQAf(R}k%{d-?T(FWPJ~op-{5{z?x_s*y#@@$VElqx(*!{)yoMIi{4aFOk=n@U@2O z>kWDC0XhG`;{`v^ljXk>&q2cTP(*sK0jmYCF()O@OUS%aU8DAKEX48T4Lx`eA&!5( z?-5fL_qA}Zpr>2+{nV$=0qWCxAI+G09QU<4A@JVgPp5pYf-4ls?X8%f81qjV?{Uv_ zPB1th&qM~8IsT8+o-gu=7$7~MRsR<=yZfm1PbwbZxrmhW5y^S!+u-+@BK2r zhyl_A8ud?UP5=KV56Jr+^F8n7eebXQP?IiyUxWMyRu^0vP>n8rS5@HuQ+>MiXMegi zqd(o8(T{?+FQA}p%zS#}vy~#Rof165V`49?|Gu^bOgx}bf9K~jyZf4p|H}8&^#NHY zl>GuNIpMz{4%q5}3*S~D=KMF6>0JLxg0uZ9(wV*$1gF0)Pt2)4?sT$uIl-k-?di&- zUV>}0zNh<}|28lOw;etSPGGvQ6S%#VrEBA3yGiP=^uUyw*|g+;&QY^__$##*&rj)y zm=oY&Mxwp0M7zOGS9-q#Y%D?V*1FL<53t&e-mWT3Zx+|3==luG@z?OH-`q)Tz~`MMIFANLiVAKR7A zft$<6Q^1B6X+XQ8+Az~5j_7JBt(VTzhwf}*B@sU_&; z%XE+jQ%H~nIG^4$3Oz;Ps{K-OTZ$q0L%k_gTKHm@F$oKrh>^} zBA5WifiYkd7zus{zv}4qFPZ4|@Qn0&7#IqE%1FUwUAAop>JqtDAj1{bxhdnP3K(2Bv^X;14h!j0K~C zLVp^L*TcXN@FN%mz6S%qx1c}h3;KXwpa=>d)U zr;Zi>6I1_{*w4$sQm_~-1oOdMFb8~O{pky=Lu=3yv;fUO6VM1W0H1?;pbn@7YJh5> z3aA7s0C(U9%IN5=n;k_~OGA-WKqVK7tms1FH8Rn|&$3fsT~`XM18Td{?S^^iO3Q+D zxdmumfc%>kpfl}?TFeLHY75u{)9d8m_P-q9@!u(cxy2kQz7xCmp>?pMsudPeo6&fvnDg;QVRnY86*f=K}`)-5`2qU`hY9n)R1C;9;zb zH#h+HfjwXs*a4D){)4d|KY)SYJMazY2fhZqK~m8_FV-bD$O*E8Y#iS{8=x@YUo}XWCHde)^d47o2FLvKZU<>0vrcPL4RcqI0EbR8~6qM z42FWAKvL1aEY_toC<%&#qM$G+2=aqO&|m3+->YVu4#7);48Sgy_;}+Y#{MNK{r|## znFam?(}A@#yz`_9>HbC?qZc(gW@Kdsd0b z{}cN7Uxod$0xSbdz#^~!%maUeq@e#7SPz*4Hpla(pfP9&>Vu@Be=@9#p$&MNH5I9I zLWBOrZPtIQyY2e##=7hT+rd`g1w6qkOJ5Nh5muXG8*-laloK|G3cKT@O3`17PIkRt^Y~(BHplldnIUIe5C+Vx*s-}LxT3i?mNewqNrgRx*V7zus{zk=al7)T2GSHOC>12<3x zlmaC{F;D~~g8mm9p$H7{}cMp$KRO?=78B? zCYS-HfhmCX{{wt%{TpB%J_q$c9Z(C@0M$ShPzihl{rx%?C-pfah+P}x1s3o9cXedD zF0u0eB^?s}_+N+p;sI8Jm0&qo3KoNfAgSoz4(swIXaib-7N8ku0vds&puYpw!yY6D z$p2nsNExe6c(7Bk=g_?>usZis_|&cwW9iT5|NIU4M+`~ffG>aA5de-ESpR)_WAx`X-~>1he85p~7)#aX!0mrSP#=5->Vn#!Ca4aof`3r|w@!9+zI_RkaYU^D{!S%AAgzr3*5>_U3pl)) z)9apD4=C#ZAEN(8?3eXmEm#9qffZmGSOSuQ{++QN9YK507JLC(gO;EdBk`^_4t7$O$#oCu7yAvU@`XN-*~Z$n9*&HS^YT=IE=A>mfHVASPyRh z_k+D)H`ocbgRQ^|c!H#$|F>9={-7`D1A2iTpd084l8XMWSeL9IGsp3jwzKMINGh$^EO4(HHEG`=c6OA82j;Wyd*pEYx5!&_2<5zkIO$(1Bh?@{~i0~ zS1=q514F=%U=a8o3;-Wn|58|o;-DBP0t$fwARovBa)BH`gZ}qxx@BY3ORZk|txzS%2w)16@nssFE`c&-cpbdo7Lk&?|2G|708c zexBvs-+i81^Z>U7%P}r=GtLd|$&OeLc7h#X8`uiGz-F)sYy=z7c36x5%dtPW{a*|g zg85)Bm;+{mnP3J;3i>z0dNcuzKm+hOs0ZqRS|E}1f1T5j!YaGc^$w*hUSGiaAMfcF zIkZ~NR*;p)`f%JgTkkiO-Q-pBdt*CPjMD?`gG55kJz$)N1ap!p=cH0!W5RP4E9Wbg z^VZ92&E);8BF8nL=cDUT?`=`FIFj0(*mA zheAKgO@Yk|Q9%2Wbh&ROah;JI zPipBJ6Te>NB7d%)vnAAI?RlPeTeX))_|JO4(sjqm`(4ZHj+NIN^7W?Ht~upv3*_|$ zcZN40MtQxVE!P+Db!L{XHM8Ua-(KzyN7TyA`5wpo57FM}0d5Pd<^zdz{L8qajsqM6 z5+V*H1rOZ#sV*^mZGpM#3vBVguC8U4epw_VpWo$v9{2O(d)!Oy{j=zn>BcH zm%cA&_1OMmR*z#+pMT= z(_|)SVu#6r6C~{?-yhdD7P$6*e)yyg$HH(g6DIVJc0M5!+6scked%LlY#W~-@Ni6P zpTN)Q%eM#Q$THg0QDS6=ZH&y%(_pGqcvtyZV*zX+Y`FuX-c!{Yyem+4K_9c6W z;q}!Ddye}s40}&2ykGd3um$OBt=^XYPkpq1oyLdy)o8M}Z}q18`c!MSThbHHdsY_g zz@w3Fcn(f|S7YMIZ>mr7>tB7cZ~v;3ef#R!->brRn>vmwT*&&`gq)XAodIbs&_`JUgIZg zDc=%*!ZGF@`6uN&^0nb>?_*x0<-$}+%GbzaRIeRXukHSAuiu((pWVOwwWDhN9bcJl z-+%7)Q`PqU*IwUIZQuXfYj4%|e|l}QLmdC*`y;Jt{Xg_|9@X!~^}5y_oyjsn{aO`I l$TXfNBhz?mr)n?JfO;!`KM%LMig#oqY -#include diff --git a/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/readme.txt b/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/readme.txt deleted file mode 100644 index 403138c7..00000000 --- a/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/readme.txt +++ /dev/null @@ -1,30 +0,0 @@ -======================================================================== - C++/WinRT ConsoleApplication1 Project Overview -======================================================================== - -This project demonstrates how to get started consuming Windows Runtime -classes directly from standard C++, using platform projection headers -generated from Windows SDK metadata files. - -Steps to generate and consume SDK platform projection: -1. Build project initially to generate platform projection headers into - your Generated Files folder. -2. Include a projection namespace header in your pch.h, such as - . -3. Consume winrt namespace and any Windows Runtime namespaces, such as - winrt::Windows::Foundation, from source code. -4. Initialize apartment via init_apartment() and consume winrt classes. - -Steps to generate and consume a projection from third party metadata: -1. Add a WinMD reference by right-clicking the References project node - and selecting "Add Reference...". In the Add References dialog, - browse to the component WinMD you want to consume and add it. -2. Build the project once to generate projection headers for the - referenced WinMD file under the "Generated Files" subfolder. -3. As above, include projection headers in pch or source code - to consume projected Windows Runtime classes. - -======================================================================== -Learn more about C++/WinRT here: -http://aka.ms/cppwinrt/ -======================================================================== diff --git a/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/resource.h b/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/resource.h new file mode 100644 index 00000000..b66e3f1d --- /dev/null +++ b/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/resource.h @@ -0,0 +1,2 @@ + +#define IDC_FULLSCREEN 10102 diff --git a/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/resources.rc b/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/resources.rc new file mode 100644 index 00000000..aeb00af9 --- /dev/null +++ b/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/resources.rc @@ -0,0 +1,4 @@ + +#include "resource.h" + +IDC_FULLSCREEN ICON "full-screen.ico" diff --git a/Samples/WindowPlacement/cpp/inc/CurrentMonitorTopology.h b/Samples/WindowPlacement/ConsoleApplication1/inc/CurrentMonitorTopology.h similarity index 100% rename from Samples/WindowPlacement/cpp/inc/CurrentMonitorTopology.h rename to Samples/WindowPlacement/ConsoleApplication1/inc/CurrentMonitorTopology.h diff --git a/Samples/WindowPlacement/cpp/inc/MiscUser32.h b/Samples/WindowPlacement/ConsoleApplication1/inc/MiscUser32.h similarity index 100% rename from Samples/WindowPlacement/cpp/inc/MiscUser32.h rename to Samples/WindowPlacement/ConsoleApplication1/inc/MiscUser32.h diff --git a/Samples/WindowPlacement/cpp/inc/MonitorData.h b/Samples/WindowPlacement/ConsoleApplication1/inc/MonitorData.h similarity index 100% rename from Samples/WindowPlacement/cpp/inc/MonitorData.h rename to Samples/WindowPlacement/ConsoleApplication1/inc/MonitorData.h diff --git a/Samples/WindowPlacement/cpp/inc/PlacementEx.h b/Samples/WindowPlacement/ConsoleApplication1/inc/PlacementEx.h similarity index 100% rename from Samples/WindowPlacement/cpp/inc/PlacementEx.h rename to Samples/WindowPlacement/ConsoleApplication1/inc/PlacementEx.h diff --git a/Samples/WindowPlacement/cpp/inc/RegistryHelpers.h b/Samples/WindowPlacement/ConsoleApplication1/inc/RegistryHelpers.h similarity index 100% rename from Samples/WindowPlacement/cpp/inc/RegistryHelpers.h rename to Samples/WindowPlacement/ConsoleApplication1/inc/RegistryHelpers.h diff --git a/Samples/WindowPlacement/cpp/inc/User32Utils.h b/Samples/WindowPlacement/ConsoleApplication1/inc/User32Utils.h similarity index 100% rename from Samples/WindowPlacement/cpp/inc/User32Utils.h rename to Samples/WindowPlacement/ConsoleApplication1/inc/User32Utils.h diff --git a/Samples/WindowPlacement/cpp/inc/VirtualDesktopIds.h b/Samples/WindowPlacement/ConsoleApplication1/inc/VirtualDesktopIds.h similarity index 100% rename from Samples/WindowPlacement/cpp/inc/VirtualDesktopIds.h rename to Samples/WindowPlacement/ConsoleApplication1/inc/VirtualDesktopIds.h diff --git a/Samples/WindowPlacement/cpp/inc/WindowActions.h b/Samples/WindowPlacement/ConsoleApplication1/inc/WindowActions.h similarity index 100% rename from Samples/WindowPlacement/cpp/inc/WindowActions.h rename to Samples/WindowPlacement/ConsoleApplication1/inc/WindowActions.h From 810424839c2069ea75624f0583703caaf1e8a842 Mon Sep 17 00:00:00 2001 From: Ben Stolovitz Date: Thu, 11 Dec 2025 16:28:15 -0500 Subject: [PATCH 07/11] (AI) fix compile --- .../FullScreenSample/FullScreenSample.vcxproj | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/FullScreenSample.vcxproj b/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/FullScreenSample.vcxproj index 58f17d26..57ee3de3 100644 --- a/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/FullScreenSample.vcxproj +++ b/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/FullScreenSample.vcxproj @@ -75,8 +75,9 @@ _DEBUG;%(PreprocessorDefinitions) - Console + Windows false + gdi32.lib;shcore.lib;dwmapi.lib;%(AdditionalDependencies) @@ -92,10 +93,11 @@ NDEBUG;%(PreprocessorDefinitions) - Console + Windows true true false + gdi32.lib;shcore.lib;dwmapi.lib;%(AdditionalDependencies) From d8211af15003cc150b70c7156e6e00557f26cdcf Mon Sep 17 00:00:00 2001 From: Ben Stolovitz Date: Thu, 11 Dec 2025 16:31:46 -0500 Subject: [PATCH 08/11] (AI-ish) remove Cppwinrt references --- .../FullScreenSample/FullScreenSample.vcxproj | 12 +++--------- .../FullScreenSample/packages.config | 1 - .../ConsoleApplication1/inc/MonitorData.h | 3 ++- .../ConsoleApplication1/inc/User32Utils.h | 1 + 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/FullScreenSample.vcxproj b/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/FullScreenSample.vcxproj index 57ee3de3..a534d9bc 100644 --- a/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/FullScreenSample.vcxproj +++ b/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/FullScreenSample.vcxproj @@ -1,10 +1,6 @@ - - true - true - true true 15.0 {04a94fc2-e929-4327-9e0a-f70ef4059773} @@ -67,6 +63,7 @@ Level4 %(AdditionalOptions) /permissive- /bigobj ..\inc;%(AdditionalIncludeDirectories) + stdcpp17 @@ -77,7 +74,7 @@ Windows false - gdi32.lib;shcore.lib;dwmapi.lib;%(AdditionalDependencies) + gdi32.lib;shcore.lib;dwmapi.lib;advapi32.lib;%(AdditionalDependencies) @@ -97,7 +94,7 @@ true true false - gdi32.lib;shcore.lib;dwmapi.lib;%(AdditionalDependencies) + gdi32.lib;shcore.lib;dwmapi.lib;advapi32.lib;%(AdditionalDependencies) @@ -126,15 +123,12 @@ - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - \ No newline at end of file diff --git a/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/packages.config b/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/packages.config index 9d957b68..bc4698db 100644 --- a/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/packages.config +++ b/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/packages.config @@ -1,5 +1,4 @@ - diff --git a/Samples/WindowPlacement/ConsoleApplication1/inc/MonitorData.h b/Samples/WindowPlacement/ConsoleApplication1/inc/MonitorData.h index 15ecf91e..c22fd8f7 100644 --- a/Samples/WindowPlacement/ConsoleApplication1/inc/MonitorData.h +++ b/Samples/WindowPlacement/ConsoleApplication1/inc/MonitorData.h @@ -86,7 +86,8 @@ class MonitorData inline bool MonitorData::FromHandle(HMONITOR monitorHandle, _Out_ MonitorData* monitorData) noexcept { - MONITORINFOEX mi = { sizeof(mi) }; + MONITORINFOEX mi; + mi.cbSize = sizeof(mi); // Note: GetDpiForMonitor returns x/y DPI but these values are always equal. UINT _dpi, unused; diff --git a/Samples/WindowPlacement/ConsoleApplication1/inc/User32Utils.h b/Samples/WindowPlacement/ConsoleApplication1/inc/User32Utils.h index 44c16b01..75a2db7c 100644 --- a/Samples/WindowPlacement/ConsoleApplication1/inc/User32Utils.h +++ b/Samples/WindowPlacement/ConsoleApplication1/inc/User32Utils.h @@ -18,6 +18,7 @@ #include #include #include +#include #include "MonitorData.h" #include "MiscUser32.h" From b6a5845d899a7fd16d25f4d3ed5aa0cd0c9c0036 Mon Sep 17 00:00:00 2001 From: Ben Stolovitz Date: Thu, 11 Dec 2025 16:31:54 -0500 Subject: [PATCH 09/11] revert --- Samples/WindowPlacement/ConsoleApplication1/inc/MonitorData.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Samples/WindowPlacement/ConsoleApplication1/inc/MonitorData.h b/Samples/WindowPlacement/ConsoleApplication1/inc/MonitorData.h index c22fd8f7..15ecf91e 100644 --- a/Samples/WindowPlacement/ConsoleApplication1/inc/MonitorData.h +++ b/Samples/WindowPlacement/ConsoleApplication1/inc/MonitorData.h @@ -86,8 +86,7 @@ class MonitorData inline bool MonitorData::FromHandle(HMONITOR monitorHandle, _Out_ MonitorData* monitorData) noexcept { - MONITORINFOEX mi; - mi.cbSize = sizeof(mi); + MONITORINFOEX mi = { sizeof(mi) }; // Note: GetDpiForMonitor returns x/y DPI but these values are always equal. UINT _dpi, unused; From a403e8237c2cbb4ef7527d7a11f0d583f34179ef Mon Sep 17 00:00:00 2001 From: Ben Stolovitz Date: Thu, 11 Dec 2025 16:39:47 -0500 Subject: [PATCH 10/11] Port SaveRestoreSample --- .../SaveRestoreSample/PropertySheet.props | 16 + .../SaveRestoreSample/SaveRestoreSample.cpp | 420 ++++++++++++++++++ .../SaveRestoreSample.filters | 37 ++ .../SaveRestoreSample.vcxproj | 134 ++++++ .../SaveRestoreSample/packages.config | 4 + .../SaveRestoreSample/resource.h | 2 + .../SaveRestoreSample/resources.rc | 4 + .../SaveRestoreSample/save.ico | Bin 0 -> 164551 bytes .../ConsoleApplication1/WindowPlacement.sln | 12 +- .../ConsoleApplication1/inc/User32Utils.h | 7 - 10 files changed, 628 insertions(+), 8 deletions(-) create mode 100644 Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/PropertySheet.props create mode 100644 Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/SaveRestoreSample.cpp create mode 100644 Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/SaveRestoreSample.filters create mode 100644 Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/SaveRestoreSample.vcxproj create mode 100644 Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/packages.config create mode 100644 Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/resource.h create mode 100644 Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/resources.rc create mode 100644 Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/save.ico diff --git a/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/PropertySheet.props b/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/PropertySheet.props new file mode 100644 index 00000000..b0c62269 --- /dev/null +++ b/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/PropertySheet.props @@ -0,0 +1,16 @@ + + + + + + + + \ No newline at end of file diff --git a/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/SaveRestoreSample.cpp b/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/SaveRestoreSample.cpp new file mode 100644 index 00000000..6a641bf5 --- /dev/null +++ b/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/SaveRestoreSample.cpp @@ -0,0 +1,420 @@ + +#define USE_VIRTUAL_DESKTOP_APIS +#define USE_WINDOW_ACTION_APIS +#include "User32Utils.h" +#include "resource.h" +#include "windowsx.h" + +// +// This sample app uses PlacementEx.h to implement 'remembering positions'. When +// the window closes, its position and other information (a PlacementEx) is +// stored in the registry. When launching, this stored position is used to pick +// an initial position. +// +// This information includes Minimized state and virtual desktop information. +// Typically, this information is unused (the app launches restored/Maximized +// and on the active desktop). But, if the app is restarted, like after a system +// reboot, we use that info to correctly relaunch the window Minimized and on +// its original desktop (if closed in that state). +// + +PCWSTR appRegKeyName = L"SOFTWARE\\Microsoft\\Win32Samples\\SaveRestoreSample"; +PCWSTR lastCloseRegKeyName = L"LastClosePosition"; +PCWSTR wndClassName = L"SaveRestoreSampleWindow"; +constexpr SIZE defaultSize = { 600, 400 }; +constexpr SIZE minSize = { 300, 200 }; + +// Monitor Hint +// +// When launched from UI (like the Taskbar, desktop icons, file explorer, etc), +// apps receive a 'monitor hint', the monitor of the UI the user interacted +// with, in their `STARTUPINFO`. By default, CreateWindow uses this hint to +// place the window, so by default PlacementEx also uses this hint to override +// any stored position (where the window was last closed). This is generally the +// correct behavior: users will see apps on the monitor they interacted with. +// However, some users prefer to hide taskbars on their non-primary displays and +// may be frustrated that apps always relaunch on their primary display (instead +// of where they last closed). +// +// If `true`: consumes the monitor hint & launches on that monitor. +// If `false`: ignores the monitor hint & launches on monitor it was last-closed on. +bool UseMonitorHint = true; +PCWSTR UseMonitorHintKeyName = L"UseMonitorHint"; +RECT rcUseMonitorHintTxt = {}; + +// Allow Partially Off-Screen +// +// Users can move and resize apps to any positions and dimensions, including +// hard-to-recover ones (e.g. extremely large and in the bottom-right corner). +// In most cases, users don't want apps to restart in those positions (e.g. the +// move was accidental or they forgot they did such an absurd move or they +// relaunch to recover a more sensible state). However, rare apps may +// deliberately want to be saved & relaunched partially off-screen. +// +// If `true`: sets the flag `PlacementFlags::AllowPartiallyOffScreen`, which +// allows a window to relaunch partially off-screen (outside the work area). +// If `false`: the relaunch position is moved as needed to be 100% inside the +// work area. +// +// If the new position is more than 50% outside the work area, PlacementEx +// assumes this was accidental and repositions the window anyway. +bool AllowOffScreen = true; +PCWSTR AllowOffScreenKeyName = L"AllowPartiallyOffscreen"; +RECT rcAllowOffScreenTxt = {}; + +// Creates the window, picks an initial position, and shows/activates it. +bool CreateMainWindow(HINSTANCE hInstance, PWSTR cmdLine) +{ + // PlacementParams allows us to pick the initial window position. + // Here we pick the default size (to use the very first time launching on + // a machine), and a registry key to use to store the last close position, + // which is used by default when launching. (The app normally launches + // where it was when it closed.) + PlacementParams pp(defaultSize, appRegKeyName, lastCloseRegKeyName); + + // Look for existing windows with the same class name. + // If one exists (the top/last activated one) the new window will be + // placed above the previous window, shifted down/right a bit to keep the + // previous window's title bar visible (aka 'cascading'). + pp.FindPrevWindow(wndClassName); + + // Create the new window using default (CW_USEDEFAULT) position and size. + // Do not set WS_VISIBLE (keep the window hidden). + HWND hwnd = CreateWindowEx( + 0, + wndClassName, + L"SaveRestore Sample", + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + nullptr, + nullptr, + hInstance, + nullptr); + + if (!hwnd) + { + return false; + } + + // Register to be restarted if the system reboots while this app is running. + // Also check if this is a restart (using a command line string). This + // modifies how we initially position the window (for example, if closed + // while Minimized or on a background virtual desktop). + PCWSTR restartCmdLine = L"/restart"; + RegisterApplicationRestart(restartCmdLine, 0); + if (wcsstr(cmdLine, restartCmdLine) != nullptr) + { + pp.SetIsRestart(); + } + + // Turning off UseMonitorHint disables the default StartupInfo::MonitorHint flag. + if (!UseMonitorHint) + { + pp.ClearStartupInfoFlag(StartupInfoFlags::MonitorHint); + } + + // AllowPartiallyOffScreen is enabled by default, but the user setting can + // disable it. + if (AllowOffScreen) + { + pp.SetAllowPartiallyOffscreen(); + } + + // Move the window to the initial position and show it. + pp.PositionAndShow(hwnd); + + return true; +} + +// Called when the window is closing. +// This updates the last close position, stored in the registry. We'll use +// this position by default when launching a new instance. +void SaveLastClosePosition(HWND hwnd) +{ + PlacementEx::StorePlacementInRegistry( + hwnd, + appRegKeyName, + lastCloseRegKeyName); +} + +// Read registry key for MonitorHint user setting. This is called on first +// launch and after changing the settings (clicking text toggles the setting). +void ReadUserSettings() +{ + // UseMonitorHint default is 1 (enabled) + DWORD dw = ReadDwordRegKey(appRegKeyName, UseMonitorHintKeyName, 1); + UseMonitorHint = (dw == 1); + + // AllowOffScreen default is 0 (disabled) + dw = ReadDwordRegKey(appRegKeyName, AllowOffScreenKeyName, 0); + AllowOffScreen = (dw == 1); +} + +// Handle WM_LBUTTONDOWN: Clicking on settings text toggles setting +void OnWmLButtonDown(HWND hwnd, LPARAM lParam) +{ + POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + + if (PtInRect(&rcUseMonitorHintTxt, pt)) + { + // Toggle monitor hint setting + WriteDwordRegKey(appRegKeyName, UseMonitorHintKeyName, !UseMonitorHint); + ReadUserSettings(); + InvalidateRect(hwnd, nullptr, true); + } + + if (PtInRect(&rcAllowOffScreenTxt, pt)) + { + // Toggle allow offscreen setting + WriteDwordRegKey(appRegKeyName, AllowOffScreenKeyName, !AllowOffScreen); + ReadUserSettings(); + InvalidateRect(hwnd, nullptr, true); + } + +} + +// Handle WM_GETMINMAXINFO: Enforce a minimum logical size +void OnWmGetMinMaxInfo(HWND hwnd, LPARAM lParam) +{ + MINMAXINFO* pmmi = reinterpret_cast(lParam); + UINT dpi = GetDpiForWindow(hwnd); + pmmi->ptMinTrackSize.x = MulDiv(minSize.cx, dpi, 96); + pmmi->ptMinTrackSize.y = MulDiv(minSize.cy, dpi, 96); +} + +// Handle WM_GETMINMAXINFO: +// - escape closes the window +// - 1 key sizes window to 500x500 +void OnWmChar(HWND hwnd, WPARAM wParam) +{ + switch(wParam) + { + case VK_ESCAPE: + SendMessage(hwnd, WM_CLOSE, 0, 0); + return; + + case 0x31: /* VK_1 */ + { + PlacementEx pex; + if (PlacementEx::GetPlacement(hwnd, &pex)) + { + pex.showCmd = SW_SHOWNORMAL; + WI_ClearFlag(pex.flags, PlacementFlags::Arranged); + pex.SetLogicalSize(defaultSize); + PlacementEx::SetPlacement(hwnd, &pex); + } + return; + } + } +} + +void OnWmPaint(HWND hwnd, HDC hdc) +{ + const COLORREF rgbMaize = RGB(255, 203, 5); + const COLORREF rgbBlue = RGB(0, 39, 76); + const COLORREF rgbRed = RGB(122, 18, 28); + static HBRUSH hbrMaize = CreateSolidBrush(rgbMaize); + static HBRUSH hbrBlue = CreateSolidBrush(rgbBlue); + static HBRUSH hbrRed = CreateSolidBrush(rgbRed); + const UINT dpi = GetDpiForWindow(hwnd); + const UINT textSize = 25; + static HFONT hfont = nullptr; + static UINT dpiLast = 0; + + // Create a font and store it until the window changes DPI (scale). + if (dpiLast != dpi) + { + if (hfont) + { + DeleteObject(hfont); + } + + hfont = CreateFont(MulDiv(textSize, dpi, 96), + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + L"Courier New"); + + dpiLast = dpi; + } + + SetBkMode(hdc, TRANSPARENT); + SelectObject(hdc, hfont); + + // If maximized - Blue (yellow text) + // If arranged - Red (yellow text) + // Restored - Yellow (blue text) + COLORREF rgbText; + HBRUSH hbrBackground; + if (IsZoomed(hwnd)) + { + rgbText = rgbMaize; + hbrBackground = hbrBlue; + } + else if (IsWindowArranged(hwnd)) + { + rgbText = rgbMaize; + hbrBackground = hbrRed; + } + else + { + rgbText = rgbBlue; + hbrBackground = hbrMaize; + } + SetTextColor(hdc, rgbText); + + RECT rc; + GetClientRect(hwnd, &rc); + FillRect(hdc, &rc, hbrBackground); + UINT nudge = MulDiv(10, dpi, 96); + InflateRect(&rc, -1 * nudge, -1 * nudge); + RECT rcTxt = rc; + + const UINT lineHeight = nudge + MulDiv(textSize, dpi, 96); + + // If Maximized or Arranged + if (IsZoomed(hwnd) || IsWindowArranged(hwnd)) + { + PCWSTR maxTxt = IsZoomed(hwnd) ? L"Maximized" : L"Arranged"; + DrawText(hdc, maxTxt, (int)wcslen(maxTxt), &rc, DT_LEFT); + rc.top += lineHeight; + } + + RECT rcWindow; + GetWindowRect(hwnd, &rcWindow); + + // Current window size, then rect. + std::wstring sizeStr = wil::str_printf(L"(%d x %d)", + RECTWIDTH(rcWindow), RECTHEIGHT(rcWindow)); + DrawText(hdc, sizeStr.c_str(), (int)wcslen(sizeStr.c_str()), &rc, DT_LEFT); + rc.top += lineHeight; + + std::wstring rectStr = wil::str_printf(L"[%d, %d, %d, %d]", + rcWindow.left, rcWindow.top, rcWindow.right, rcWindow.bottom); + DrawText(hdc, rectStr.c_str(), (int)wcslen(rectStr.c_str()), &rc, DT_LEFT); + + // Reset the rect to the full window (with nudge) and move to the bottom + // row (the user settings). + rc = rcTxt; + rc.top = rc.bottom - lineHeight; + + // UseMonitorHint -> Launch Monitor: Last/Best (where 'use hint' == best) + std::wstring lastMonSettingTxt = wil::str_printf( + L"Launch Monitor: %ws", UseMonitorHint ? L"Best" : L"Last"); + DrawText(hdc, lastMonSettingTxt.c_str(), (int)wcslen(lastMonSettingTxt.c_str()), + &rc, DT_SINGLELINE | DT_LEFT | DT_BOTTOM); + + // Remember the rect for the monitor hint rect; clicking toggles the setting. + rcUseMonitorHintTxt = rc; + + // AllowOffScreen. If no, this sets PlacementFlags::AllowPartiallyOffScreen. + rc.top -= lineHeight; + rc.bottom -= lineHeight; + std::wstring offscreenSettingTxt = wil::str_printf( + L"Allow OffScreen: %ws", AllowOffScreen ? L"Yes" : L"No"); + DrawText(hdc, offscreenSettingTxt.c_str(), (int)wcslen(offscreenSettingTxt.c_str()), + &rc, DT_SINGLELINE | DT_LEFT | DT_BOTTOM); + + // Remember the rect for the offscreen text; clicking toggles the setting. + rcAllowOffScreenTxt = rc; +} + +LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) + { + case WM_PAINT: + { + PAINTSTRUCT ps; + OnWmPaint(hwnd, BeginPaint(hwnd, &ps)); + EndPaint(hwnd, &ps); + break; + } + + case WM_WINDOWPOSCHANGED: + { + // Repaint when the window rect changes + auto pwp = reinterpret_cast(lParam); + RECT rc = { pwp->x, pwp->y, pwp->x + pwp->cx, pwp->y + pwp->cy }; + static RECT rcWindowLast = {}; + if (!EqualRect(&rc, &rcWindowLast)) + { + rcWindowLast = rc; + InvalidateRect(hwnd, nullptr, true); + } + break; + } + + case WM_LBUTTONDOWN: + OnWmLButtonDown(hwnd, lParam); + break; + + case WM_GETMINMAXINFO: + OnWmGetMinMaxInfo(hwnd, lParam); + break; + + case WM_DPICHANGED: + { + RECT* prc = reinterpret_cast(lParam); + SetWindowPos(hwnd, + nullptr, + prc->left, + prc->top, + RECTWIDTH(*prc), + RECTHEIGHT(*prc), + SWP_NOZORDER | SWP_NOACTIVATE); + break; + } + + case WM_CHAR: + OnWmChar(hwnd, wParam); + break; + + case WM_DESTROY: + SaveLastClosePosition(hwnd); + PostQuitMessage(0); + break; + } + + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +int wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR cmdLine, int) +{ + // Run as Per-Monitor DPI Aware, or Unaware if 'u' in the command line. + SetThreadDpiAwarenessContext( + (wcsstr(cmdLine, L"u") != nullptr) ? + DPI_AWARENESS_CONTEXT_UNAWARE : + DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + + // Initialize COM, needed for virtual desktop APIs (USE_VIRTUAL_DESKTOP_APIS). + CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + + // Read settings stored in registry. + ReadUserSettings(); + + WNDCLASSEX wc = { sizeof(WNDCLASSEX) }; + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.hInstance = hInstance; + wc.lpfnWndProc = WndProc; + wc.lpszClassName = wndClassName; + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDC_SAVE)); + + // Create the window and show it. + if (!RegisterClassEx(&wc) || !CreateMainWindow(hInstance, cmdLine)) + { + return 1; + } + + // Pump messages until exit. + MSG msg; + while (GetMessage(&msg, nullptr, 0, 0)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + return 0; +} diff --git a/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/SaveRestoreSample.filters b/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/SaveRestoreSample.filters new file mode 100644 index 00000000..388ab2e2 --- /dev/null +++ b/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/SaveRestoreSample.filters @@ -0,0 +1,37 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + + + Source Files + + + Source Files + + + + + + + + + + \ No newline at end of file diff --git a/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/SaveRestoreSample.vcxproj b/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/SaveRestoreSample.vcxproj new file mode 100644 index 00000000..4c45a120 --- /dev/null +++ b/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/SaveRestoreSample.vcxproj @@ -0,0 +1,134 @@ + + + + true + 15.0 + {4654392F-E2E8-4B21-960B-1CD9E88E77E3} + Win32Proj + SaveRestoreSample + 10.0.26100.0 + 10.0.17134.0 + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + Application + v143 + v142 + v141 + v140 + Unicode + + + true + true + + + false + true + false + + + + + + + + + + + + + + + + _CONSOLE;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) + Level4 + %(AdditionalOptions) /permissive- /bigobj + ..\inc;%(AdditionalIncludeDirectories) + stdcpp17 + + + + + Disabled + _DEBUG;%(PreprocessorDefinitions) + + + Windows + false + gdi32.lib;shcore.lib;dwmapi.lib;advapi32.lib;%(AdditionalDependencies) + + + + + WIN32;%(PreprocessorDefinitions) + + + + + MaxSpeed + true + true + NDEBUG;%(PreprocessorDefinitions) + + + Windows + true + true + false + gdi32.lib;shcore.lib;dwmapi.lib;advapi32.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/packages.config b/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/packages.config new file mode 100644 index 00000000..bc4698db --- /dev/null +++ b/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/packages.config @@ -0,0 +1,4 @@ + + + + diff --git a/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/resource.h b/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/resource.h new file mode 100644 index 00000000..7361a484 --- /dev/null +++ b/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/resource.h @@ -0,0 +1,2 @@ + +#define IDC_SAVE 10101 diff --git a/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/resources.rc b/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/resources.rc new file mode 100644 index 00000000..08f5aacf --- /dev/null +++ b/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/resources.rc @@ -0,0 +1,4 @@ + +#include "resource.h" + +IDC_SAVE ICON "save.ico" diff --git a/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/save.ico b/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/save.ico new file mode 100644 index 0000000000000000000000000000000000000000..e538112df63436ca2535f78b44ce062cf8d2d2ef GIT binary patch literal 164551 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 -#include -#include -#endif - #include "windows.h" #include "shellscalingapi.h" #include "dwmapi.h" @@ -33,7 +27,6 @@ #endif #ifdef USE_WINDOW_ACTION_APIS -#include bool IsApplyWindowActionSupported(); bool ApplyWindowActionWrapper(HWND hwnd, WINDOW_ACTION* action); #endif From 93c2244a070b34ffc5c71408f80a7116695464b0 Mon Sep 17 00:00:00 2001 From: Ben Stolovitz Date: Thu, 11 Dec 2025 16:40:54 -0500 Subject: [PATCH 11/11] move to cpp --- .../FullScreenSample/FullScreenSample.cpp | 325 -------------- .../FullScreenSample/FullScreenSample.vcxproj | 134 ------ .../FullScreenSample/full-screen.ico | Bin 67646 -> 0 bytes .../FullScreenSample/packages.config | 4 - .../FullScreenSample/resource.h | 2 - .../FullScreenSample/resources.rc | 4 - .../SaveRestoreSample/SaveRestoreSample.cpp | 420 ------------------ .../SaveRestoreSample.vcxproj | 134 ------ .../SaveRestoreSample/packages.config | 4 - .../SaveRestoreSample/resource.h | 2 - .../SaveRestoreSample/resources.rc | 4 - .../SaveRestoreSample/save.ico | Bin 164551 -> 0 bytes .../cpp/FullScreenSample/FullScreenSample.cpp | 2 +- .../FullScreenSample/FullScreenSample.filters | 0 .../FullScreenSample/FullScreenSample.vcxproj | 159 +++---- .../FullScreenSample/PropertySheet.props | 0 .../cpp/FullScreenSample/packages.config | 4 +- .../SaveRestoreSample/PropertySheet.props | 0 .../SaveRestoreSample.filters | 0 .../SaveRestoreSample.vcxproj | 159 +++---- .../cpp/SaveRestoreSample/packages.config | 4 +- .../WindowPlacement.sln | 0 .../cpp/WindowPlacementSamples.sln | 40 -- .../inc/CurrentMonitorTopology.h | 0 .../inc/MiscUser32.h | 0 .../inc/MonitorData.h | 0 .../inc/PlacementEx.h | 0 .../inc/RegistryHelpers.h | 0 .../inc/User32Utils.h | 0 .../inc/VirtualDesktopIds.h | 0 .../inc/WindowActions.h | 0 Samples/WindowPlacement/cpp/packages.config | 4 - 32 files changed, 129 insertions(+), 1276 deletions(-) delete mode 100644 Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/FullScreenSample.cpp delete mode 100644 Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/FullScreenSample.vcxproj delete mode 100644 Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/full-screen.ico delete mode 100644 Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/packages.config delete mode 100644 Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/resource.h delete mode 100644 Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/resources.rc delete mode 100644 Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/SaveRestoreSample.cpp delete mode 100644 Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/SaveRestoreSample.vcxproj delete mode 100644 Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/packages.config delete mode 100644 Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/resource.h delete mode 100644 Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/resources.rc delete mode 100644 Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/save.ico rename Samples/WindowPlacement/{ConsoleApplication1 => cpp}/FullScreenSample/FullScreenSample.filters (100%) rename Samples/WindowPlacement/{ConsoleApplication1 => cpp}/FullScreenSample/PropertySheet.props (100%) rename Samples/WindowPlacement/{ConsoleApplication1 => cpp}/SaveRestoreSample/PropertySheet.props (100%) rename Samples/WindowPlacement/{ConsoleApplication1 => cpp}/SaveRestoreSample/SaveRestoreSample.filters (100%) rename Samples/WindowPlacement/{ConsoleApplication1 => cpp}/WindowPlacement.sln (100%) delete mode 100644 Samples/WindowPlacement/cpp/WindowPlacementSamples.sln rename Samples/WindowPlacement/{ConsoleApplication1 => cpp}/inc/CurrentMonitorTopology.h (100%) rename Samples/WindowPlacement/{ConsoleApplication1 => cpp}/inc/MiscUser32.h (100%) rename Samples/WindowPlacement/{ConsoleApplication1 => cpp}/inc/MonitorData.h (100%) rename Samples/WindowPlacement/{ConsoleApplication1 => cpp}/inc/PlacementEx.h (100%) rename Samples/WindowPlacement/{ConsoleApplication1 => cpp}/inc/RegistryHelpers.h (100%) rename Samples/WindowPlacement/{ConsoleApplication1 => cpp}/inc/User32Utils.h (100%) rename Samples/WindowPlacement/{ConsoleApplication1 => cpp}/inc/VirtualDesktopIds.h (100%) rename Samples/WindowPlacement/{ConsoleApplication1 => cpp}/inc/WindowActions.h (100%) delete mode 100644 Samples/WindowPlacement/cpp/packages.config diff --git a/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/FullScreenSample.cpp b/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/FullScreenSample.cpp deleted file mode 100644 index 9aa99b26..00000000 --- a/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/FullScreenSample.cpp +++ /dev/null @@ -1,325 +0,0 @@ - -#define USE_VIRTUAL_DESKTOP_APIS -#include "../inc/User32Utils.h" -#include "resource.h" - -// A FullScreen window is sized to the monitor and doesn't have a caption -// bar or resize borders (in other words, no WS_CAPTION or WS_THICKFRAME). -// -// When exiting FullScreen, users expect the window to move back to its previous -// position, stored when entering FullScreen. This is similar to Maximize/Minimize, -// except apps must track this restore position manually; the system does not. -// -// This sample demonstrates entering/exiting FullScreen, using PlacementEx to -// implement this memory. PlacementEx remembers the window position (when -// entering) and uses that position to move the window at a later time (exiting). -PlacementEx fsPlacement; - -// The last close position is stored in the registry, using a string that is -// unique to this app. -PCWSTR registryPath = L"SOFTWARE\\Microsoft\\Win32Samples\\FullScreenSample"; -PCWSTR lastCloseRegKeyName = L"LastClosePosition"; - -// Called after creating the window (hidden). This picks a good starting -// position for the window and shows it. -// -// hwndPrev Another instance of this app, opened prior to this -// instance opening. If this is not null, this window will -// launch over the other instance, in the same position but -// cascaded (moved down/right a bit to keep both windows visible). -// -// isRestart If true, this is a restart (not a normal launch). The -// system restarted while this app was running, and we're -// being relaunched. -// This allows the window to launch minimized, or cloaked (on -// a background virtual desktop). -// -void SetInitialPosition(HWND hwnd, HWND hwndPrev, bool isRestart) -{ - PlacementParams pp({ 600, 400 }, registryPath, lastCloseRegKeyName); - - if (isRestart) - { - pp.SetIsRestart(); - } - else if (hwndPrev) - { - pp.SetPrevWindow(hwndPrev); - } - - fsPlacement = pp.PositionAndShow(hwnd); - - // Repaint now that fsPlacement is set (checked for when painting to pick - // background color). - InvalidateRect(hwnd, nullptr, true); -} - -// Called before destroying the window. This stores the current window placement -// in the registry, as the last close position. -// -// If closed while FullScreen, make sure our stored position is on the window's -// current monitor (but do not refresh it). We want to launch next time as -// FullScreen, with the restore position that we stored when entering FullScreen. -void SaveLastClosePosition(HWND hwnd) -{ - if (fsPlacement.IsFullScreen()) - { - fsPlacement.MoveToWindowMonitor(hwnd); - } - else if (!PlacementEx::GetPlacement(hwnd, &fsPlacement)) - { - return; - } - - fsPlacement.StoreInRegistry( - registryPath, - lastCloseRegKeyName); -} - -// Handle keyboard input. -void OnWmChar(HWND hwnd, WPARAM wParam) -{ - switch (wParam) - { - // Enter key toggles FullScreen. - case VK_RETURN: - fsPlacement.ToggleFullScreen(hwnd); - return; - - // Space toggles Maximize. - // This is not done while FullScreen. - case VK_SPACE: - if (!fsPlacement.IsFullScreen()) - { - ShowWindow(hwnd, IsZoomed(hwnd) ? SW_RESTORE : SW_MAXIMIZE); - } - return; - - // M key minimizes the window. - case 0x4D: // M key - case 0x6D: // m key - ShowWindow(hwnd, SW_MINIMIZE); - return; - - // C key moves the cursor to the center of the window. - case 0x43: // C key - case 0x63: // c key - { - RECT rc; - GetWindowRect(hwnd, &rc); - SetCursorPos(rc.left + ((RECTWIDTH(rc)) / 2), rc.top + ((RECTHEIGHT(rc)) / 2)); - return; - } - - // Escape key exits. - case VK_ESCAPE: - DestroyWindow(hwnd); - return; - } -} - -void OnWmPaint(HWND hwnd, HDC hdc) -{ - const COLORREF rgbMaize = RGB(255, 203, 5); - const COLORREF rgbBlue = RGB(0, 39, 76); - static HBRUSH hbrMaize = CreateSolidBrush(rgbMaize); - static HBRUSH hbrBlue = CreateSolidBrush(rgbBlue); - const UINT dpi = GetDpiForWindow(hwnd); - - // Create a font of size 30 (* DPI scale) and store it until DPI changes. - static HFONT hfont = nullptr; - static UINT dpiLast = 0; - if (dpiLast != dpi) - { - if (hfont) - { - DeleteObject(hfont); - } - - hfont = CreateFont(MulDiv(30, dpi, 96), - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - L"Courier New"); - - dpiLast = dpi; - } - SelectObject(hdc, hfont); - - // Background in one color, text in the other. - const bool fFullScreen = fsPlacement.IsFullScreen(); - HBRUSH hbrBackground = fFullScreen ? hbrMaize : hbrBlue; - COLORREF rgbText = fFullScreen ? rgbBlue : rgbMaize; - - SetBkMode(hdc, TRANSPARENT); - SetTextColor(hdc, rgbText); - - RECT rc; - GetClientRect(hwnd, &rc); - - // Draw background - FillRect(hdc, &rc, hbrBackground); - - const UINT nudge = MulDiv(30, dpi, 96); - - // Draw text - - rc.top += (3 * nudge); - rc.left += nudge; - - PCWSTR toggleFullTxt = fFullScreen ? - L"ENTER to exit Fullscreen" : L"ENTER to enter Fullscreen"; - DrawText(hdc, toggleFullTxt, (int)wcslen(toggleFullTxt), &rc, DT_LEFT); - - if (!fFullScreen) - { - rc.top += nudge; - PCWSTR toggleMaxTxt = IsZoomed(hwnd) ? - L"SPACE to restore from Maximize" : L"SPACE to Maximize"; - DrawText(hdc, toggleMaxTxt, (int)wcslen(toggleMaxTxt), &rc, DT_LEFT); - } - - rc.top += nudge; - PCWSTR minTxt = L"M to minimize"; - DrawText(hdc, minTxt, (int)wcslen(minTxt), &rc, DT_LEFT); - - rc.top += nudge; - PCWSTR escTxt = L"ESC to close"; - DrawText(hdc, escTxt, (int)wcslen(escTxt), &rc, DT_LEFT); - - rc.top += nudge; - PCWSTR cTxt = L"C to move cursor to center"; - DrawText(hdc, cTxt, (int)wcslen(cTxt), &rc, DT_LEFT); -} - -LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) -{ - switch (msg) - { - case WM_CHAR: - OnWmChar(hwnd, wParam); - break; - - case WM_SYSCOMMAND: - - // If the window is maximized, FullScreen, and someone is trying to - // restore the window (for example, Win+Down hotkey), exit FullScreen. - // (We do NOT want to remain FullScreen but restore from Maximize and - // move to the restore position...) - if ((wParam == SC_RESTORE) && - IsZoomed(hwnd) && - fsPlacement.IsFullScreen()) - { - fsPlacement.ExitFullScreen(hwnd); - return 0; - } - break; - - case WM_PAINT: - { - PAINTSTRUCT ps; - OnWmPaint(hwnd, BeginPaint(hwnd, &ps)); - EndPaint(hwnd, &ps); - break; - } - - case WM_DPICHANGED: - { - RECT* prc = (RECT*)lParam; - - SetWindowPos(hwnd, - nullptr, - prc->left, - prc->top, - prc->right - prc->left, - prc->bottom - prc->top, - SWP_NOZORDER | SWP_NOACTIVATE); - break; - } - - case WM_ENDSESSION: - case WM_DESTROY: - SaveLastClosePosition(hwnd); - PostQuitMessage(0); - break; - } - - return DefWindowProc(hwnd, msg, wParam, lParam); -} - -bool InitWindow(HINSTANCE hInst, bool isRestart) -{ - PCWSTR windowTitle = L"FullScreen Sample"; - PCWSTR wndClassName = L"FullScreenSampleWindow"; - - WNDCLASSEX wc = { sizeof(wc) }; - wc.style = CS_HREDRAW | CS_VREDRAW; - wc.lpfnWndProc = WndProc; - wc.hInstance = hInst; - wc.hCursor = LoadCursor(NULL, IDC_ARROW); - wc.lpszClassName = wndClassName; - wc.hIcon = LoadIcon(hInst, MAKEINTRESOURCE(IDC_FULLSCREEN)); - - if (!RegisterClassEx(&wc)) - { - return false; - } - - HWND hwndPrev = FindWindow(wndClassName, nullptr); - - // Create the window with the default position and not visible. - HWND hwnd = CreateWindowEx( - 0, - wndClassName, - windowTitle, - WS_OVERLAPPEDWINDOW, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - nullptr, - nullptr, - hInst, - nullptr); - - if (!hwnd) - { - return false; - } - - SetInitialPosition(hwnd, hwndPrev, isRestart); - - return true; -} - -int wWinMain(HINSTANCE hInst, HINSTANCE, LPWSTR cmdLine, int) -{ - // If 'u' in the command line, run as DPI Unaware. Otherwise run as - // Per-Monitor DPI Aware. - SetThreadDpiAwarenessContext((wcsstr(cmdLine, L"u") != nullptr) ? - DPI_AWARENESS_CONTEXT_UNAWARE : - DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); - - // Initialize COM, needed for virtual desktop APIs (USE_VIRTUAL_DESKTOP_APIS). - CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); - - PCWSTR restartCmdLine = L"restart"; - const bool isRestart = (wcsstr(cmdLine, restartCmdLine) != nullptr); - RegisterApplicationRestart(restartCmdLine, 0); - - // Create the main window. - if (!InitWindow(hInst, isRestart)) - { - MessageBox(NULL, - L"Failed to create Main Window.", - L"ERROR", MB_ICONEXCLAMATION | MB_OK); - return 1; - } - - MSG msg; - while (GetMessage(&msg, nullptr, 0, 0)) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - - return 0; -} diff --git a/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/FullScreenSample.vcxproj b/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/FullScreenSample.vcxproj deleted file mode 100644 index a534d9bc..00000000 --- a/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/FullScreenSample.vcxproj +++ /dev/null @@ -1,134 +0,0 @@ - - - - true - 15.0 - {04a94fc2-e929-4327-9e0a-f70ef4059773} - Win32Proj - FullScreenSample - 10.0.26100.0 - 10.0.17134.0 - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - Application - v143 - v142 - v141 - v140 - Unicode - - - true - true - - - false - true - false - - - - - - - - - - - - - - - - _CONSOLE;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) - Level4 - %(AdditionalOptions) /permissive- /bigobj - ..\inc;%(AdditionalIncludeDirectories) - stdcpp17 - - - - - Disabled - _DEBUG;%(PreprocessorDefinitions) - - - Windows - false - gdi32.lib;shcore.lib;dwmapi.lib;advapi32.lib;%(AdditionalDependencies) - - - - - WIN32;%(PreprocessorDefinitions) - - - - - MaxSpeed - true - true - NDEBUG;%(PreprocessorDefinitions) - - - Windows - true - true - false - gdi32.lib;shcore.lib;dwmapi.lib;advapi32.lib;%(AdditionalDependencies) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - \ No newline at end of file diff --git a/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/full-screen.ico b/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/full-screen.ico deleted file mode 100644 index efff82cd2e0a44432151c3bd729691fb17ee0233..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 67646 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 l$TXfNBhz?mr)n?JfO;!`KM%LMig#oqY - - - diff --git a/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/resource.h b/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/resource.h deleted file mode 100644 index b66e3f1d..00000000 --- a/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/resource.h +++ /dev/null @@ -1,2 +0,0 @@ - -#define IDC_FULLSCREEN 10102 diff --git a/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/resources.rc b/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/resources.rc deleted file mode 100644 index aeb00af9..00000000 --- a/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/resources.rc +++ /dev/null @@ -1,4 +0,0 @@ - -#include "resource.h" - -IDC_FULLSCREEN ICON "full-screen.ico" diff --git a/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/SaveRestoreSample.cpp b/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/SaveRestoreSample.cpp deleted file mode 100644 index 6a641bf5..00000000 --- a/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/SaveRestoreSample.cpp +++ /dev/null @@ -1,420 +0,0 @@ - -#define USE_VIRTUAL_DESKTOP_APIS -#define USE_WINDOW_ACTION_APIS -#include "User32Utils.h" -#include "resource.h" -#include "windowsx.h" - -// -// This sample app uses PlacementEx.h to implement 'remembering positions'. When -// the window closes, its position and other information (a PlacementEx) is -// stored in the registry. When launching, this stored position is used to pick -// an initial position. -// -// This information includes Minimized state and virtual desktop information. -// Typically, this information is unused (the app launches restored/Maximized -// and on the active desktop). But, if the app is restarted, like after a system -// reboot, we use that info to correctly relaunch the window Minimized and on -// its original desktop (if closed in that state). -// - -PCWSTR appRegKeyName = L"SOFTWARE\\Microsoft\\Win32Samples\\SaveRestoreSample"; -PCWSTR lastCloseRegKeyName = L"LastClosePosition"; -PCWSTR wndClassName = L"SaveRestoreSampleWindow"; -constexpr SIZE defaultSize = { 600, 400 }; -constexpr SIZE minSize = { 300, 200 }; - -// Monitor Hint -// -// When launched from UI (like the Taskbar, desktop icons, file explorer, etc), -// apps receive a 'monitor hint', the monitor of the UI the user interacted -// with, in their `STARTUPINFO`. By default, CreateWindow uses this hint to -// place the window, so by default PlacementEx also uses this hint to override -// any stored position (where the window was last closed). This is generally the -// correct behavior: users will see apps on the monitor they interacted with. -// However, some users prefer to hide taskbars on their non-primary displays and -// may be frustrated that apps always relaunch on their primary display (instead -// of where they last closed). -// -// If `true`: consumes the monitor hint & launches on that monitor. -// If `false`: ignores the monitor hint & launches on monitor it was last-closed on. -bool UseMonitorHint = true; -PCWSTR UseMonitorHintKeyName = L"UseMonitorHint"; -RECT rcUseMonitorHintTxt = {}; - -// Allow Partially Off-Screen -// -// Users can move and resize apps to any positions and dimensions, including -// hard-to-recover ones (e.g. extremely large and in the bottom-right corner). -// In most cases, users don't want apps to restart in those positions (e.g. the -// move was accidental or they forgot they did such an absurd move or they -// relaunch to recover a more sensible state). However, rare apps may -// deliberately want to be saved & relaunched partially off-screen. -// -// If `true`: sets the flag `PlacementFlags::AllowPartiallyOffScreen`, which -// allows a window to relaunch partially off-screen (outside the work area). -// If `false`: the relaunch position is moved as needed to be 100% inside the -// work area. -// -// If the new position is more than 50% outside the work area, PlacementEx -// assumes this was accidental and repositions the window anyway. -bool AllowOffScreen = true; -PCWSTR AllowOffScreenKeyName = L"AllowPartiallyOffscreen"; -RECT rcAllowOffScreenTxt = {}; - -// Creates the window, picks an initial position, and shows/activates it. -bool CreateMainWindow(HINSTANCE hInstance, PWSTR cmdLine) -{ - // PlacementParams allows us to pick the initial window position. - // Here we pick the default size (to use the very first time launching on - // a machine), and a registry key to use to store the last close position, - // which is used by default when launching. (The app normally launches - // where it was when it closed.) - PlacementParams pp(defaultSize, appRegKeyName, lastCloseRegKeyName); - - // Look for existing windows with the same class name. - // If one exists (the top/last activated one) the new window will be - // placed above the previous window, shifted down/right a bit to keep the - // previous window's title bar visible (aka 'cascading'). - pp.FindPrevWindow(wndClassName); - - // Create the new window using default (CW_USEDEFAULT) position and size. - // Do not set WS_VISIBLE (keep the window hidden). - HWND hwnd = CreateWindowEx( - 0, - wndClassName, - L"SaveRestore Sample", - WS_OVERLAPPEDWINDOW, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - nullptr, - nullptr, - hInstance, - nullptr); - - if (!hwnd) - { - return false; - } - - // Register to be restarted if the system reboots while this app is running. - // Also check if this is a restart (using a command line string). This - // modifies how we initially position the window (for example, if closed - // while Minimized or on a background virtual desktop). - PCWSTR restartCmdLine = L"/restart"; - RegisterApplicationRestart(restartCmdLine, 0); - if (wcsstr(cmdLine, restartCmdLine) != nullptr) - { - pp.SetIsRestart(); - } - - // Turning off UseMonitorHint disables the default StartupInfo::MonitorHint flag. - if (!UseMonitorHint) - { - pp.ClearStartupInfoFlag(StartupInfoFlags::MonitorHint); - } - - // AllowPartiallyOffScreen is enabled by default, but the user setting can - // disable it. - if (AllowOffScreen) - { - pp.SetAllowPartiallyOffscreen(); - } - - // Move the window to the initial position and show it. - pp.PositionAndShow(hwnd); - - return true; -} - -// Called when the window is closing. -// This updates the last close position, stored in the registry. We'll use -// this position by default when launching a new instance. -void SaveLastClosePosition(HWND hwnd) -{ - PlacementEx::StorePlacementInRegistry( - hwnd, - appRegKeyName, - lastCloseRegKeyName); -} - -// Read registry key for MonitorHint user setting. This is called on first -// launch and after changing the settings (clicking text toggles the setting). -void ReadUserSettings() -{ - // UseMonitorHint default is 1 (enabled) - DWORD dw = ReadDwordRegKey(appRegKeyName, UseMonitorHintKeyName, 1); - UseMonitorHint = (dw == 1); - - // AllowOffScreen default is 0 (disabled) - dw = ReadDwordRegKey(appRegKeyName, AllowOffScreenKeyName, 0); - AllowOffScreen = (dw == 1); -} - -// Handle WM_LBUTTONDOWN: Clicking on settings text toggles setting -void OnWmLButtonDown(HWND hwnd, LPARAM lParam) -{ - POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; - - if (PtInRect(&rcUseMonitorHintTxt, pt)) - { - // Toggle monitor hint setting - WriteDwordRegKey(appRegKeyName, UseMonitorHintKeyName, !UseMonitorHint); - ReadUserSettings(); - InvalidateRect(hwnd, nullptr, true); - } - - if (PtInRect(&rcAllowOffScreenTxt, pt)) - { - // Toggle allow offscreen setting - WriteDwordRegKey(appRegKeyName, AllowOffScreenKeyName, !AllowOffScreen); - ReadUserSettings(); - InvalidateRect(hwnd, nullptr, true); - } - -} - -// Handle WM_GETMINMAXINFO: Enforce a minimum logical size -void OnWmGetMinMaxInfo(HWND hwnd, LPARAM lParam) -{ - MINMAXINFO* pmmi = reinterpret_cast(lParam); - UINT dpi = GetDpiForWindow(hwnd); - pmmi->ptMinTrackSize.x = MulDiv(minSize.cx, dpi, 96); - pmmi->ptMinTrackSize.y = MulDiv(minSize.cy, dpi, 96); -} - -// Handle WM_GETMINMAXINFO: -// - escape closes the window -// - 1 key sizes window to 500x500 -void OnWmChar(HWND hwnd, WPARAM wParam) -{ - switch(wParam) - { - case VK_ESCAPE: - SendMessage(hwnd, WM_CLOSE, 0, 0); - return; - - case 0x31: /* VK_1 */ - { - PlacementEx pex; - if (PlacementEx::GetPlacement(hwnd, &pex)) - { - pex.showCmd = SW_SHOWNORMAL; - WI_ClearFlag(pex.flags, PlacementFlags::Arranged); - pex.SetLogicalSize(defaultSize); - PlacementEx::SetPlacement(hwnd, &pex); - } - return; - } - } -} - -void OnWmPaint(HWND hwnd, HDC hdc) -{ - const COLORREF rgbMaize = RGB(255, 203, 5); - const COLORREF rgbBlue = RGB(0, 39, 76); - const COLORREF rgbRed = RGB(122, 18, 28); - static HBRUSH hbrMaize = CreateSolidBrush(rgbMaize); - static HBRUSH hbrBlue = CreateSolidBrush(rgbBlue); - static HBRUSH hbrRed = CreateSolidBrush(rgbRed); - const UINT dpi = GetDpiForWindow(hwnd); - const UINT textSize = 25; - static HFONT hfont = nullptr; - static UINT dpiLast = 0; - - // Create a font and store it until the window changes DPI (scale). - if (dpiLast != dpi) - { - if (hfont) - { - DeleteObject(hfont); - } - - hfont = CreateFont(MulDiv(textSize, dpi, 96), - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - L"Courier New"); - - dpiLast = dpi; - } - - SetBkMode(hdc, TRANSPARENT); - SelectObject(hdc, hfont); - - // If maximized - Blue (yellow text) - // If arranged - Red (yellow text) - // Restored - Yellow (blue text) - COLORREF rgbText; - HBRUSH hbrBackground; - if (IsZoomed(hwnd)) - { - rgbText = rgbMaize; - hbrBackground = hbrBlue; - } - else if (IsWindowArranged(hwnd)) - { - rgbText = rgbMaize; - hbrBackground = hbrRed; - } - else - { - rgbText = rgbBlue; - hbrBackground = hbrMaize; - } - SetTextColor(hdc, rgbText); - - RECT rc; - GetClientRect(hwnd, &rc); - FillRect(hdc, &rc, hbrBackground); - UINT nudge = MulDiv(10, dpi, 96); - InflateRect(&rc, -1 * nudge, -1 * nudge); - RECT rcTxt = rc; - - const UINT lineHeight = nudge + MulDiv(textSize, dpi, 96); - - // If Maximized or Arranged - if (IsZoomed(hwnd) || IsWindowArranged(hwnd)) - { - PCWSTR maxTxt = IsZoomed(hwnd) ? L"Maximized" : L"Arranged"; - DrawText(hdc, maxTxt, (int)wcslen(maxTxt), &rc, DT_LEFT); - rc.top += lineHeight; - } - - RECT rcWindow; - GetWindowRect(hwnd, &rcWindow); - - // Current window size, then rect. - std::wstring sizeStr = wil::str_printf(L"(%d x %d)", - RECTWIDTH(rcWindow), RECTHEIGHT(rcWindow)); - DrawText(hdc, sizeStr.c_str(), (int)wcslen(sizeStr.c_str()), &rc, DT_LEFT); - rc.top += lineHeight; - - std::wstring rectStr = wil::str_printf(L"[%d, %d, %d, %d]", - rcWindow.left, rcWindow.top, rcWindow.right, rcWindow.bottom); - DrawText(hdc, rectStr.c_str(), (int)wcslen(rectStr.c_str()), &rc, DT_LEFT); - - // Reset the rect to the full window (with nudge) and move to the bottom - // row (the user settings). - rc = rcTxt; - rc.top = rc.bottom - lineHeight; - - // UseMonitorHint -> Launch Monitor: Last/Best (where 'use hint' == best) - std::wstring lastMonSettingTxt = wil::str_printf( - L"Launch Monitor: %ws", UseMonitorHint ? L"Best" : L"Last"); - DrawText(hdc, lastMonSettingTxt.c_str(), (int)wcslen(lastMonSettingTxt.c_str()), - &rc, DT_SINGLELINE | DT_LEFT | DT_BOTTOM); - - // Remember the rect for the monitor hint rect; clicking toggles the setting. - rcUseMonitorHintTxt = rc; - - // AllowOffScreen. If no, this sets PlacementFlags::AllowPartiallyOffScreen. - rc.top -= lineHeight; - rc.bottom -= lineHeight; - std::wstring offscreenSettingTxt = wil::str_printf( - L"Allow OffScreen: %ws", AllowOffScreen ? L"Yes" : L"No"); - DrawText(hdc, offscreenSettingTxt.c_str(), (int)wcslen(offscreenSettingTxt.c_str()), - &rc, DT_SINGLELINE | DT_LEFT | DT_BOTTOM); - - // Remember the rect for the offscreen text; clicking toggles the setting. - rcAllowOffScreenTxt = rc; -} - -LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) -{ - switch (msg) - { - case WM_PAINT: - { - PAINTSTRUCT ps; - OnWmPaint(hwnd, BeginPaint(hwnd, &ps)); - EndPaint(hwnd, &ps); - break; - } - - case WM_WINDOWPOSCHANGED: - { - // Repaint when the window rect changes - auto pwp = reinterpret_cast(lParam); - RECT rc = { pwp->x, pwp->y, pwp->x + pwp->cx, pwp->y + pwp->cy }; - static RECT rcWindowLast = {}; - if (!EqualRect(&rc, &rcWindowLast)) - { - rcWindowLast = rc; - InvalidateRect(hwnd, nullptr, true); - } - break; - } - - case WM_LBUTTONDOWN: - OnWmLButtonDown(hwnd, lParam); - break; - - case WM_GETMINMAXINFO: - OnWmGetMinMaxInfo(hwnd, lParam); - break; - - case WM_DPICHANGED: - { - RECT* prc = reinterpret_cast(lParam); - SetWindowPos(hwnd, - nullptr, - prc->left, - prc->top, - RECTWIDTH(*prc), - RECTHEIGHT(*prc), - SWP_NOZORDER | SWP_NOACTIVATE); - break; - } - - case WM_CHAR: - OnWmChar(hwnd, wParam); - break; - - case WM_DESTROY: - SaveLastClosePosition(hwnd); - PostQuitMessage(0); - break; - } - - return DefWindowProc(hwnd, msg, wParam, lParam); -} - -int wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR cmdLine, int) -{ - // Run as Per-Monitor DPI Aware, or Unaware if 'u' in the command line. - SetThreadDpiAwarenessContext( - (wcsstr(cmdLine, L"u") != nullptr) ? - DPI_AWARENESS_CONTEXT_UNAWARE : - DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); - - // Initialize COM, needed for virtual desktop APIs (USE_VIRTUAL_DESKTOP_APIS). - CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); - - // Read settings stored in registry. - ReadUserSettings(); - - WNDCLASSEX wc = { sizeof(WNDCLASSEX) }; - wc.style = CS_HREDRAW | CS_VREDRAW; - wc.hInstance = hInstance; - wc.lpfnWndProc = WndProc; - wc.lpszClassName = wndClassName; - wc.hCursor = LoadCursor(NULL, IDC_ARROW); - wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDC_SAVE)); - - // Create the window and show it. - if (!RegisterClassEx(&wc) || !CreateMainWindow(hInstance, cmdLine)) - { - return 1; - } - - // Pump messages until exit. - MSG msg; - while (GetMessage(&msg, nullptr, 0, 0)) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - - return 0; -} diff --git a/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/SaveRestoreSample.vcxproj b/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/SaveRestoreSample.vcxproj deleted file mode 100644 index 4c45a120..00000000 --- a/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/SaveRestoreSample.vcxproj +++ /dev/null @@ -1,134 +0,0 @@ - - - - true - 15.0 - {4654392F-E2E8-4B21-960B-1CD9E88E77E3} - Win32Proj - SaveRestoreSample - 10.0.26100.0 - 10.0.17134.0 - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - Application - v143 - v142 - v141 - v140 - Unicode - - - true - true - - - false - true - false - - - - - - - - - - - - - - - - _CONSOLE;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) - Level4 - %(AdditionalOptions) /permissive- /bigobj - ..\inc;%(AdditionalIncludeDirectories) - stdcpp17 - - - - - Disabled - _DEBUG;%(PreprocessorDefinitions) - - - Windows - false - gdi32.lib;shcore.lib;dwmapi.lib;advapi32.lib;%(AdditionalDependencies) - - - - - WIN32;%(PreprocessorDefinitions) - - - - - MaxSpeed - true - true - NDEBUG;%(PreprocessorDefinitions) - - - Windows - true - true - false - gdi32.lib;shcore.lib;dwmapi.lib;advapi32.lib;%(AdditionalDependencies) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - \ No newline at end of file diff --git a/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/packages.config b/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/packages.config deleted file mode 100644 index bc4698db..00000000 --- a/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/resource.h b/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/resource.h deleted file mode 100644 index 7361a484..00000000 --- a/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/resource.h +++ /dev/null @@ -1,2 +0,0 @@ - -#define IDC_SAVE 10101 diff --git a/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/resources.rc b/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/resources.rc deleted file mode 100644 index 08f5aacf..00000000 --- a/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/resources.rc +++ /dev/null @@ -1,4 +0,0 @@ - -#include "resource.h" - -IDC_SAVE ICON "save.ico" diff --git a/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/save.ico b/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/save.ico deleted file mode 100644 index e538112df63436ca2535f78b44ce062cf8d2d2ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 164551 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 - + + + true + 15.0 + {04a94fc2-e929-4327-9e0a-f70ef4059773} + Win32Proj + FullScreenSample + 10.0.26100.0 + 10.0.17134.0 + + Debug @@ -18,152 +28,107 @@ x64 - - 16.0 - Win32Proj - {6e745655-513e-4713-b3ab-d6d3f62d7734} - AcousticEchoCancellation - 10.0 - - - - Application - true - v142 - Unicode - - + Application - false - v142 - true + v143 + v142 + v141 + v140 Unicode - false - - Application + true - v142 - Unicode + true - - Application + false - v142 true - Unicode + false - - - - + - - - - - + + - - true - - - false - false - - - true - - - false - - + - Level3 - true - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true + _CONSOLE;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) + Level4 + %(AdditionalOptions) /permissive- /bigobj + ..\inc;%(AdditionalIncludeDirectories) + stdcpp17 - - Console - true - kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;mfplat.lib;mmdevapi.lib;mfuuid.lib;mfreadwrite.lib;windowsapp.lib;userenv.lib;Avrt.lib;%(AdditionalDependencies) - - + - Level3 - true - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true Disabled + _DEBUG;%(PreprocessorDefinitions) - Console - true - true - true - kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;mfplat.lib;mmdevapi.lib;mfuuid.lib;mfreadwrite.lib;windowsapp.lib;userenv.lib;Avrt.lib;%(AdditionalDependencies) + Windows + false + gdi32.lib;shcore.lib;dwmapi.lib;advapi32.lib;%(AdditionalDependencies) - + - Level3 - true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true + WIN32;%(PreprocessorDefinitions) - - Console - true - kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;mfplat.lib;mmdevapi.lib;mfuuid.lib;mfreadwrite.lib;windowsapp.lib;userenv.lib;Avrt.lib;%(AdditionalDependencies) - - + - Level3 + MaxSpeed true true - true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - F:\os2\os2\public\amd64fre + NDEBUG;%(PreprocessorDefinitions) - Console + Windows true true - true - kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;mfplat.lib;mmdevapi.lib;mfuuid.lib;mfreadwrite.lib;windowsapp.lib;userenv.lib;Avrt.lib;%(AdditionalDependencies) + false + gdi32.lib;shcore.lib;dwmapi.lib;advapi32.lib;%(AdditionalDependencies) - - + + - + - + + + + + + + + + + + + + + + - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + \ No newline at end of file diff --git a/Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/PropertySheet.props b/Samples/WindowPlacement/cpp/FullScreenSample/PropertySheet.props similarity index 100% rename from Samples/WindowPlacement/ConsoleApplication1/FullScreenSample/PropertySheet.props rename to Samples/WindowPlacement/cpp/FullScreenSample/PropertySheet.props diff --git a/Samples/WindowPlacement/cpp/FullScreenSample/packages.config b/Samples/WindowPlacement/cpp/FullScreenSample/packages.config index 26065b14..bc4698db 100644 --- a/Samples/WindowPlacement/cpp/FullScreenSample/packages.config +++ b/Samples/WindowPlacement/cpp/FullScreenSample/packages.config @@ -1,4 +1,4 @@ - + - \ No newline at end of file + diff --git a/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/PropertySheet.props b/Samples/WindowPlacement/cpp/SaveRestoreSample/PropertySheet.props similarity index 100% rename from Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/PropertySheet.props rename to Samples/WindowPlacement/cpp/SaveRestoreSample/PropertySheet.props diff --git a/Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/SaveRestoreSample.filters b/Samples/WindowPlacement/cpp/SaveRestoreSample/SaveRestoreSample.filters similarity index 100% rename from Samples/WindowPlacement/ConsoleApplication1/SaveRestoreSample/SaveRestoreSample.filters rename to Samples/WindowPlacement/cpp/SaveRestoreSample/SaveRestoreSample.filters diff --git a/Samples/WindowPlacement/cpp/SaveRestoreSample/SaveRestoreSample.vcxproj b/Samples/WindowPlacement/cpp/SaveRestoreSample/SaveRestoreSample.vcxproj index e6d8fdfd..4c45a120 100644 --- a/Samples/WindowPlacement/cpp/SaveRestoreSample/SaveRestoreSample.vcxproj +++ b/Samples/WindowPlacement/cpp/SaveRestoreSample/SaveRestoreSample.vcxproj @@ -1,5 +1,15 @@ - + + + true + 15.0 + {4654392F-E2E8-4B21-960B-1CD9E88E77E3} + Win32Proj + SaveRestoreSample + 10.0.26100.0 + 10.0.17134.0 + + Debug @@ -18,152 +28,107 @@ x64 - - 16.0 - Win32Proj - {92E6DB91-D852-412E-BD45-2EB168A1D090} - AcousticEchoCancellation - 10.0 - - - - Application - true - v142 - Unicode - - + Application - false - v142 - true + v143 + v142 + v141 + v140 Unicode - false - - Application + true - v142 - Unicode + true - - Application + false - v142 true - Unicode + false - - - - + - - - - - + + - - true - - - false - false - - - true - - - false - - + - Level3 - true - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true + _CONSOLE;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) + Level4 + %(AdditionalOptions) /permissive- /bigobj + ..\inc;%(AdditionalIncludeDirectories) + stdcpp17 - - Console - true - kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;mfplat.lib;mmdevapi.lib;mfuuid.lib;mfreadwrite.lib;windowsapp.lib;userenv.lib;Avrt.lib;%(AdditionalDependencies) - - + - Level3 - true - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true Disabled + _DEBUG;%(PreprocessorDefinitions) - Console - true - true - true - kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;mfplat.lib;mmdevapi.lib;mfuuid.lib;mfreadwrite.lib;windowsapp.lib;userenv.lib;Avrt.lib;%(AdditionalDependencies) + Windows + false + gdi32.lib;shcore.lib;dwmapi.lib;advapi32.lib;%(AdditionalDependencies) - + - Level3 - true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true + WIN32;%(PreprocessorDefinitions) - - Console - true - kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;mfplat.lib;mmdevapi.lib;mfuuid.lib;mfreadwrite.lib;windowsapp.lib;userenv.lib;Avrt.lib;%(AdditionalDependencies) - - + - Level3 + MaxSpeed true true - true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - F:\os2\os2\public\amd64fre + NDEBUG;%(PreprocessorDefinitions) - Console + Windows true true - true - kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;mfplat.lib;mmdevapi.lib;mfuuid.lib;mfreadwrite.lib;windowsapp.lib;userenv.lib;Avrt.lib;%(AdditionalDependencies) + false + gdi32.lib;shcore.lib;dwmapi.lib;advapi32.lib;%(AdditionalDependencies) - - + + - + - + + + + + + + + + + + + + + + - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + \ No newline at end of file diff --git a/Samples/WindowPlacement/cpp/SaveRestoreSample/packages.config b/Samples/WindowPlacement/cpp/SaveRestoreSample/packages.config index 26065b14..bc4698db 100644 --- a/Samples/WindowPlacement/cpp/SaveRestoreSample/packages.config +++ b/Samples/WindowPlacement/cpp/SaveRestoreSample/packages.config @@ -1,4 +1,4 @@ - + - \ No newline at end of file + diff --git a/Samples/WindowPlacement/ConsoleApplication1/WindowPlacement.sln b/Samples/WindowPlacement/cpp/WindowPlacement.sln similarity index 100% rename from Samples/WindowPlacement/ConsoleApplication1/WindowPlacement.sln rename to Samples/WindowPlacement/cpp/WindowPlacement.sln diff --git a/Samples/WindowPlacement/cpp/WindowPlacementSamples.sln b/Samples/WindowPlacement/cpp/WindowPlacementSamples.sln deleted file mode 100644 index 56a1b03b..00000000 --- a/Samples/WindowPlacement/cpp/WindowPlacementSamples.sln +++ /dev/null @@ -1,40 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FullScreenSample", "FullScreenSample\FullScreenSample.vcxproj", "{6E745655-513E-4713-B3AB-D6D3F62D7734}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SaveRestoreSample", "SaveRestoreSample\SaveRestoreSample.vcxproj", "{92E6DB91-D852-412E-BD45-2EB168A1D090}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {6E745655-513E-4713-B3AB-D6D3F62D7734}.Debug|x64.ActiveCfg = Debug|x64 - {6E745655-513E-4713-B3AB-D6D3F62D7734}.Debug|x64.Build.0 = Debug|x64 - {6E745655-513E-4713-B3AB-D6D3F62D7734}.Debug|x86.ActiveCfg = Debug|Win32 - {6E745655-513E-4713-B3AB-D6D3F62D7734}.Debug|x86.Build.0 = Debug|Win32 - {6E745655-513E-4713-B3AB-D6D3F62D7734}.Release|x64.ActiveCfg = Release|x64 - {6E745655-513E-4713-B3AB-D6D3F62D7734}.Release|x64.Build.0 = Release|x64 - {6E745655-513E-4713-B3AB-D6D3F62D7734}.Release|x86.ActiveCfg = Release|Win32 - {6E745655-513E-4713-B3AB-D6D3F62D7734}.Release|x86.Build.0 = Release|Win32 - {92E6DB91-D852-412E-BD45-2EB168A1D090}.Debug|x64.ActiveCfg = Debug|x64 - {92E6DB91-D852-412E-BD45-2EB168A1D090}.Debug|x64.Build.0 = Debug|x64 - {92E6DB91-D852-412E-BD45-2EB168A1D090}.Debug|x86.ActiveCfg = Debug|Win32 - {92E6DB91-D852-412E-BD45-2EB168A1D090}.Debug|x86.Build.0 = Debug|Win32 - {92E6DB91-D852-412E-BD45-2EB168A1D090}.Release|x64.ActiveCfg = Release|x64 - {92E6DB91-D852-412E-BD45-2EB168A1D090}.Release|x64.Build.0 = Release|x64 - {92E6DB91-D852-412E-BD45-2EB168A1D090}.Release|x86.ActiveCfg = Release|Win32 - {92E6DB91-D852-412E-BD45-2EB168A1D090}.Release|x86.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {F71C6299-3377-464E-A9B6-60CE7E909160} - EndGlobalSection -EndGlobal diff --git a/Samples/WindowPlacement/ConsoleApplication1/inc/CurrentMonitorTopology.h b/Samples/WindowPlacement/cpp/inc/CurrentMonitorTopology.h similarity index 100% rename from Samples/WindowPlacement/ConsoleApplication1/inc/CurrentMonitorTopology.h rename to Samples/WindowPlacement/cpp/inc/CurrentMonitorTopology.h diff --git a/Samples/WindowPlacement/ConsoleApplication1/inc/MiscUser32.h b/Samples/WindowPlacement/cpp/inc/MiscUser32.h similarity index 100% rename from Samples/WindowPlacement/ConsoleApplication1/inc/MiscUser32.h rename to Samples/WindowPlacement/cpp/inc/MiscUser32.h diff --git a/Samples/WindowPlacement/ConsoleApplication1/inc/MonitorData.h b/Samples/WindowPlacement/cpp/inc/MonitorData.h similarity index 100% rename from Samples/WindowPlacement/ConsoleApplication1/inc/MonitorData.h rename to Samples/WindowPlacement/cpp/inc/MonitorData.h diff --git a/Samples/WindowPlacement/ConsoleApplication1/inc/PlacementEx.h b/Samples/WindowPlacement/cpp/inc/PlacementEx.h similarity index 100% rename from Samples/WindowPlacement/ConsoleApplication1/inc/PlacementEx.h rename to Samples/WindowPlacement/cpp/inc/PlacementEx.h diff --git a/Samples/WindowPlacement/ConsoleApplication1/inc/RegistryHelpers.h b/Samples/WindowPlacement/cpp/inc/RegistryHelpers.h similarity index 100% rename from Samples/WindowPlacement/ConsoleApplication1/inc/RegistryHelpers.h rename to Samples/WindowPlacement/cpp/inc/RegistryHelpers.h diff --git a/Samples/WindowPlacement/ConsoleApplication1/inc/User32Utils.h b/Samples/WindowPlacement/cpp/inc/User32Utils.h similarity index 100% rename from Samples/WindowPlacement/ConsoleApplication1/inc/User32Utils.h rename to Samples/WindowPlacement/cpp/inc/User32Utils.h diff --git a/Samples/WindowPlacement/ConsoleApplication1/inc/VirtualDesktopIds.h b/Samples/WindowPlacement/cpp/inc/VirtualDesktopIds.h similarity index 100% rename from Samples/WindowPlacement/ConsoleApplication1/inc/VirtualDesktopIds.h rename to Samples/WindowPlacement/cpp/inc/VirtualDesktopIds.h diff --git a/Samples/WindowPlacement/ConsoleApplication1/inc/WindowActions.h b/Samples/WindowPlacement/cpp/inc/WindowActions.h similarity index 100% rename from Samples/WindowPlacement/ConsoleApplication1/inc/WindowActions.h rename to Samples/WindowPlacement/cpp/inc/WindowActions.h diff --git a/Samples/WindowPlacement/cpp/packages.config b/Samples/WindowPlacement/cpp/packages.config deleted file mode 100644 index 130712fe..00000000 --- a/Samples/WindowPlacement/cpp/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file