Introduction
I came to discover that, you cannot simply overlay Metro/UWP/Windows Store Apps (or whatever they are called these days) using always on top windows.
I learned while it is possible, for that to work the program has to have the uiAccess=True
-flag set in the manifest, and it must be signed.
That, of course, isn't good news for any hobby or small open-source project, as code-signing certificates cost money.
There is another way, however...
But first, let's see what happens if you try to focus any old “always on top”-window while running a fullscreen store app.
If that happens, it causes the Windows Store app in “fullscreen”-mode to instantly minimize.
Even though store apps never really run in exclusive fullscreen!
Some programs however are perfectly capable of doing this, like the Windows Taskbar itself or DisplayFusions intermediate loading window...
I asked the developers of DisplayFusion how they are doing it, and they shared the secret with me: The aforementioned uiAccess=True
-flag along with a signed executable.
For my endless quest in getting Steam Input to work with stuff it normally doesn't, an overlay for Windows Store apps would be a real boon.
But spending ~100 bucks per year for a free project, that I have way too little time to properly maintain and support, doesn't seem all too appealing.
Luckily, with a bit of hacking, using undocumented Windows APIs, I managed to get it to work.
I think the whole process is rather interesting, and so I wanted to share it with you.
Window z-order
Z-ordering in Windows 10 (up from Win8) works in so-called “WindowBands”
The order of (some) bands, from lowest to highest, is as follows:
- ZBID_DESKTOP
- ZBID_IMMERSIVE_BACKGROUND
- ZBID_IMMERSIVE_APPCHROME
- ZBID_IMMERSIVE_MOGO
- ZBID_IMMERSIVE_INACTIVEMOBODY
- ZBID_IMMERSIVE_NOTIFICATION
- ZBID_IMMERSIVE_EDGY
- ZBID_SYSTEM_TOOLS
- ZBID_LOCK (Win10)
- ZBID_ABOVELOCK_UX (Win10)
- ZBID_IMMERSIVE_IHM
- ZBID_GENUINE_WINDOWS
- ZBID_UIACCESS
If you're wondering why you never heard of “WindowBands” before, it is because you normally won't get to use them (explicitly).
All the APIs are undocumented and not made publicly available by Microsoft.
But we will get to that.
Any regular old window gets created with ZBID_DESKTOP
and will remain there.
Setting a window to be always-on-top using SetWindowPos(...)
will just change the ordering inside the ZBID_DESKTOP
-band.
An exception to that is, for example, the aforementioneduiAccess=True
-flag with a signed executable. Then the window will get created in the ZBID_UIACCESS
-band.
If any ZBID_DESKTOP
-window gets “touched” while running a store app in “fullscreen”-mode, the store app gets minimized.
Other bands don't necessarily cause that behavior.
Using other WindowBands
Code
Enum
enum ZBID
{
ZBID_DEFAULT = 0,
ZBID_DESKTOP = 1,
ZBID_UIACCESS = 2,
ZBID_IMMERSIVE_IHM = 3,
ZBID_IMMERSIVE_NOTIFICATION = 4,
ZBID_IMMERSIVE_APPCHROME = 5,
ZBID_IMMERSIVE_MOGO = 6,
ZBID_IMMERSIVE_EDGY = 7,
ZBID_IMMERSIVE_INACTIVEMOBODY = 8,
ZBID_IMMERSIVE_INACTIVEDOCK = 9,
ZBID_IMMERSIVE_ACTIVEMOBODY = 10,
ZBID_IMMERSIVE_ACTIVEDOCK = 11,
ZBID_IMMERSIVE_BACKGROUND = 12,
ZBID_IMMERSIVE_SEARCH = 13,
ZBID_GENUINE_WINDOWS = 14,
ZBID_IMMERSIVE_RESTRICTED = 15,
ZBID_SYSTEM_TOOLS = 16,
// Win10
ZBID_LOCK = 17,
ZBID_ABOVELOCK_UX = 18,
};
CreateWindowInBand
Same as CreateWindowEx
except with 1 more parameter dwBand
where you specify the band the window should stay.
HWND WINAPI CreateWindowInBand(
DWORD dwExStyle,
LPCWSTR lpClassName,
LPCWSTR lpWindowName,
DWORD dwStyle,
int x,
int y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU hMenu,
HINSTANCE hInstance,
LPVOID lpParam,
DWORD dwBand);
SetWindowBand
Same as SetWindowPos
except with 1 more parameter dwBand
BOOL WINAPI SetWindowBand(
HWND hWnd,
HWND hwndInsertAfter,
DWORD dwBand);
GetWindowBand
BOOL WINAPI GetWindowBand(
HWND hWnd,
PDWORD pdwBand);
Calling the APIs?
All those functions are private APIs found in user32.dll
, so you have to use GetProcAdress
to get to them.
But Microsoft implemented some checks to prevent their usage.
GetWindowBand
always works.CreateWindowInBand/Ex
only works if you passZBID_DEFAULT
orZBID_DESKTOP
ZBID_UIACCESS
works only with theuiAccess=True
-flag set and code-signing.SetWindowBand
will never work.
Calling them anyway...
CreateWindowInBand
To use CreateWindowInBand
with any ZBID, you need to inject a .dll into an immersive process (bright blue in ProcessExplorer) and call it from there.
SetWindowBand
SetWindowBand
gets a little more interesting.
In order to use that, you have to inject a .dll into explorer.exe
and hook SetWindowBand
with a trampoline.
As soon as the StartMenu opens, or a new window gets created, the function will be called.
Now you can call the original function twice.
Once with a different HWND and ZBID and then with the original parameters.
A full example of this can be found here
Conclusion
With the introduction of Windows Store apps, Microsoft also introduced the concept of WindowBands, to more offer more granular control over window z-ordering.
Many Bands are available, but they don't allow you to use them explicitly.
With a bit of hacking, however, it's completely possible to freely take advantage of any WindowBand.
And so GloSC/GlosSI (My cobbled together tool for SteamInput) users can enjoy this:
Credits/Sources: adeltax