diff --git a/INSTALL.md b/INSTALL.md
index 9007c53..31f449d 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -259,6 +259,45 @@ The recommended option above — leave UAC on, let the helper do the privileged
work — avoids all of this: the elevated half is a small binary with no config
and no scripting, and `init.lua` stays unprivileged.
+### uiAccess (advanced — removes the last UIPI gap without elevating)
+
+There is a third route that needs neither an elevated mshell nor the helper:
+build mshell with the **uiAccess** manifest. A uiAccess process may bypass the
+UIPI restrictions that stop an unelevated shell from touching elevated windows,
+so a single unelevated mshell can tile, restore, re-style and close them **and**
+its keyboard hook keeps working while an elevated window has focus — the one
+thing the helper cannot do. It stays unelevated, so `init.lua` stays
+unprivileged and auto-reload keeps working.
+
+Windows guards the flag strictly, and these are hard requirements, not
+suggestions:
+
+- **The binary must be Authenticode-signed by a certificate the machine
+ trusts.** The PKI signature check is always enforced for a uiAccess process.
+- **It must live under a protected path** — `C:\Program Files\...` or
+ `C:\Windows\System32\...`. `C:\mshell` does **not** qualify, so the usual
+ install location has to change for this build.
+
+If either is not met, Windows refuses the flag — the process may launch without
+it, or fail to launch at all. Because being started as the Winlogon shell is an
+unusual launch path for a uiAccess binary, **verify it actually worked**: the
+startup log says `uiAccess GRANTED` when the flag is live. If it doesn't, you
+are running an ordinary build and should keep the helper.
+
+Building it:
+
+```
+make UIACCESS=1 # embeds src/mshell.uiaccess.manifest instead of the default
+```
+
+then sign `mshell.exe` and install it under `C:\Program Files\mshell\`. For a
+personal machine you can create your own certificate, trust it machine-wide,
+sign with it, and delete the private key afterwards so nothing else can ever be
+signed as mshell; a public release needs a real code-signing certificate. The
+default `make` build is unchanged and still boots unsigned from `C:\mshell`, so
+nothing here is required — it is the maximal-control option for those who want
+it.
+
## 5. Turn off the rest of Windows (optional, recommended)
mshell itself already eats every `Win`+* combo, Alt+Tab / Alt+Esc / Ctrl+Esc,
diff --git a/Makefile b/Makefile
index d5b2dbb..41a5d89 100644
--- a/Makefile
+++ b/Makefile
@@ -22,6 +22,19 @@ STRICT_WARNINGS = -Wshadow -Wformat=2 -Wvla
RCFLAGS = -DVER_MAJOR=$(VER_MAJOR) \
-DVER_MINOR=$(VER_MINOR) \
-DVER_PATCH=$(VER_PATCH)
+
+# Opt-in uiAccess build: `make UIACCESS=1` embeds the uiAccess="true" manifest
+# instead of the default asInvoker one. The resulting binary must be
+# Authenticode-signed and installed under Program Files or Windows will not
+# honour the flag (it may even refuse to launch) — see src/mshell.uiaccess.manifest
+# and INSTALL.md. The default build is unchanged, so an unsigned shell still boots.
+UIACCESS ?=
+ifneq ($(UIACCESS),)
+RCFLAGS += -DMSHELL_MANIFEST='\"mshell.uiaccess.manifest\"'
+MANIFEST_FILE = $(SRC_DIR)/mshell.uiaccess.manifest
+else
+MANIFEST_FILE = $(SRC_DIR)/mshell.exe.manifest
+endif
LDFLAGS = -luser32 -lgdi32 -lshell32 -lole32 -luuid -ldwmapi -lwtsapi32 \
-ladvapi32 -lpowrprof -lwinhttp -lbcrypt \
-lruntimeobject -lm
@@ -223,8 +236,8 @@ $(SRC_DIR)/%.o: $(SRC_DIR)/%.c $(SRC_DIR)/mshell.h
@echo " CC $<"
$(CC) $(CFLAGS) $(STRICT_WARNINGS) -c -o $@ $<
-$(RES_OBJ): $(SRC_DIR)/mshell.rc $(SRC_DIR)/mshell.exe.manifest
- @echo " RC $<"
+$(RES_OBJ): $(SRC_DIR)/mshell.rc $(MANIFEST_FILE)
+ @echo " RC $< ($(notdir $(MANIFEST_FILE)))"
$(WINDRES) $(RCFLAGS) -I$(SRC_DIR) -O coff -i $< -o $@
$(LUA_DIR)/%.o: $(LUA_DIR)/%.c
diff --git a/README.md b/README.md
index 7a4883a..b6d5db7 100644
--- a/README.md
+++ b/README.md
@@ -151,6 +151,16 @@ tiled, driven entirely from the keyboard and configured in Lua.
with `mshell.exec("mcapture.exe", "--select")` like any other program.
- Single global instance, low-level keyboard hook, out-of-context WinEvent
hooks — no DLL injection.
+- **Anti-cheat safe by construction.** mshell never injects a DLL, creates a
+ remote thread, or writes into another process's memory, and it never touches
+ the boot chain (no kernel driver, no test-signing, so Secure Boot and TPM
+ stay intact for Vanguard, Javelin, Ricochet, EAC, BattlEye and FACEIT). It
+ only ever calls public window APIs from its own process, reads a game
+ process's name and path to match a rule, and — where a window is owned by a
+ higher-integrity process — asks the elevated helper (`mshelld.exe`) to move,
+ restore, re-style or close it. This is a deliberate non-goal: mshell will not
+ gain a feature that requires reaching into another process, because doing so
+ is exactly what kernel anti-cheat is built to flag.
## Build
diff --git a/src/helper.c b/src/helper.c
index adec9f3..b70d0b9 100644
--- a/src/helper.c
+++ b/src/helper.c
@@ -236,6 +236,32 @@ bool helper_close_window(HWND hwnd) {
return helper_request(&req, L"a window could not be closed", &warned);
}
+bool helper_show_window(HWND hwnd, int cmd) {
+ static bool warned;
+
+ ProtoMsg req = {
+ .type = PROTO_SHOW,
+ .version = MSHELLD_PROTO_VERSION,
+ .hwnd = (uint64_t)(uintptr_t)hwnd,
+ .flags = (uint32_t)cmd,
+ };
+ return helper_request(&req, L"a window could not be restored", &warned);
+}
+
+bool helper_set_style(HWND hwnd, LONG_PTR style, LONG_PTR exstyle) {
+ static bool warned;
+
+ ProtoMsg req = {
+ .type = PROTO_SETSTYLE,
+ .version = MSHELLD_PROTO_VERSION,
+ .hwnd = (uint64_t)(uintptr_t)hwnd,
+ .style = (int64_t)style,
+ .exstyle = (int64_t)exstyle,
+ };
+ return helper_request(&req, L"a window's frame could not be stripped",
+ &warned);
+}
+
#define HELPER_TASK_NAME L"mshelld"
#define HELPER_SETTLE_MS 1500
#define HELPER_SCHTASKS_TIMEOUT_MS 15000
diff --git a/src/main.c b/src/main.c
index 7fd1663..13aae84 100644
--- a/src/main.c
+++ b/src/main.c
@@ -18,8 +18,35 @@ static bool is_elevated(void) {
return ok && elev.TokenIsElevated != 0;
}
+/* Whether this process actually holds the uiAccess token flag. A build made
+ with `make UIACCESS=1` only gets it when Windows accepts the manifest, which
+ needs the binary signed and under Program Files — so this is the authoritative
+ check, not the manifest. */
+static bool has_uiaccess(void) {
+ HANDLE token = NULL;
+ if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token))
+ return false;
+
+ DWORD ui = 0, size = sizeof(ui);
+ BOOL ok = GetTokenInformation(token, TokenUIAccess, &ui, size, &size);
+ CloseHandle(token);
+ return ok && ui != 0;
+}
+
static UINT g_prev_fg_lock_timeout = 0;
+/* Programs mshell launches inherit its environment. SDL's default is to
+ minimize a fullscreen game the moment it loses focus — which fights the
+ "nothing minimizes" rule, since there is no taskbar to bring it back from,
+ and mshell would then have to restore it after the fact. Turning the hint
+ off here stops the minimize happening in the first place, for every SDL1/2/3
+ game started from the shell. The variable is a no-op for non-SDL programs. */
+static void set_child_env_defaults(void) {
+ if (!SetEnvironmentVariableW(L"SDL_VIDEO_MINIMIZE_ON_FOCUS_LOSS", L"0"))
+ log_w(L"could not set SDL_VIDEO_MINIMIZE_ON_FOCUS_LOSS for launched "
+ L"games: %lu", GetLastError());
+}
+
static LONG WINAPI mshell_crash_handler(EXCEPTION_POINTERS *ep) {
static LONG entered = 0;
if (InterlockedExchange(&entered, 1)) return EXCEPTION_CONTINUE_SEARCH;
@@ -376,14 +403,24 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
update_clear_staged_image();
g.elevated = is_elevated();
-
- if (!g.elevated && shell_mode)
+ g.uiaccess = has_uiaccess();
+
+ if (g.uiaccess)
+ log_err(L"uiAccess GRANTED — mshell can manage windows owned by "
+ L"higher-integrity processes directly, and its keyboard hook "
+ L"keeps working while one has focus. The mshelld.exe helper is "
+ L"not needed in this mode.");
+ else if (!g.elevated && shell_mode)
log_w(L"Shell mode at user level (UAC on) — windows owned by elevated "
- L"processes can't be managed (UIPI).");
+ L"processes can't be managed directly (UIPI); the mshelld.exe "
+ L"helper covers moving/restoring/closing them. (This is not a "
+ L"uiAccess build, or Windows declined the flag — see INSTALL.md.)");
if (!SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS))
log_w(L"SetPriorityClass(ABOVE_NORMAL) failed: %lu", GetLastError());
+ set_child_env_defaults();
+
config_reset();
if (!SystemParametersInfoW(SPI_GETFOREGROUNDLOCKTIMEOUT, 0,
diff --git a/src/mshell.h b/src/mshell.h
index 832c1e3..6e44018 100644
--- a/src/mshell.h
+++ b/src/mshell.h
@@ -272,6 +272,7 @@ typedef struct {
bool fs_has_prev;
bool needs_helper;
bool place_refused;
+ bool unminimize_refused;
bool always_on_top;
bool layout_hidden;
bool urgent;
@@ -584,6 +585,7 @@ typedef struct {
bool panicked;
bool safe_mode;
bool elevated;
+ bool uiaccess;
} MShell;
@@ -834,6 +836,8 @@ bool helper_available(void);
bool helper_set_window_pos(HWND hwnd, int x, int y, int w, int h, UINT flags);
bool helper_set_topmost(HWND hwnd, bool on);
bool helper_close_window(HWND hwnd);
+bool helper_show_window(HWND hwnd, int cmd);
+bool helper_set_style(HWND hwnd, LONG_PTR style, LONG_PTR exstyle);
typedef enum {
PLACE_OK = 0,
diff --git a/src/mshell.rc b/src/mshell.rc
index 0455b25..3349fb0 100644
--- a/src/mshell.rc
+++ b/src/mshell.rc
@@ -12,7 +12,14 @@
#define CREATEPROCESS_MANIFEST_RESOURCE_ID 1
#endif
-CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "mshell.exe.manifest"
+/* The manifest file is chosen at build time: the default asInvoker one, or the
+ uiAccess="true" variant when the Makefile passes MSHELL_MANIFEST (make
+ UIACCESS=1). windres expands the macro to the quoted file name. */
+#ifndef MSHELL_MANIFEST
+#define MSHELL_MANIFEST "mshell.exe.manifest"
+#endif
+
+CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST MSHELL_MANIFEST
VS_VERSION_INFO VERSIONINFO
FILEVERSION VER_MAJOR,VER_MINOR,VER_PATCH,0
diff --git a/src/mshell.uiaccess.manifest b/src/mshell.uiaccess.manifest
new file mode 100644
index 0000000..952b702
--- /dev/null
+++ b/src/mshell.uiaccess.manifest
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PerMonitorV2, PerMonitor, system
+ true/pm
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/mshelld.c b/src/mshelld.c
index 4c25e18..539dee9 100644
--- a/src/mshelld.c
+++ b/src/mshelld.c
@@ -273,6 +273,38 @@ static bool do_close(const ProtoMsg *m) {
return PostMessageW(h, WM_CLOSE, 0, 0) != 0;
}
+/* ShowWindow returns the window's PREVIOUS visibility, not success, so we
+ confirm the state change ourselves: a restore must clear the iconic bit. */
+static bool do_show(const ProtoMsg *m) {
+ HWND h = (HWND)(uintptr_t)m->hwnd;
+ int cmd = (int)m->flags;
+ if (!h || !IsWindow(h)) return false;
+
+ ShowWindow(h, cmd);
+
+ if (cmd == SW_RESTORE || cmd == SW_SHOWNOACTIVATE ||
+ cmd == SW_SHOW || cmd == SW_SHOWNA)
+ return !IsIconic(h);
+ return true;
+}
+
+static bool do_setstyle(const ProtoMsg *m) {
+ HWND h = (HWND)(uintptr_t)m->hwnd;
+ if (!h || !IsWindow(h)) return false;
+
+ SetLastError(0);
+ SetWindowLongPtrW(h, GWL_STYLE, (LONG_PTR)m->style);
+ SetWindowLongPtrW(h, GWL_EXSTYLE, (LONG_PTR)m->exstyle);
+
+ /* A style change only takes visual effect once the frame is recomputed. */
+ SetWindowPos(h, NULL, 0, 0, 0, 0,
+ SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
+ SWP_NOACTIVATE | SWP_FRAMECHANGED);
+
+ return (GetWindowLongPtrW(h, GWL_STYLE) & 0xFFFFFFFFu) ==
+ ((LONG_PTR)m->style & 0xFFFFFFFFu);
+}
+
static void serve(const Client *c) {
bool greeted = false;
@@ -319,6 +351,16 @@ static void serve(const Client *c) {
else if (!do_close(&in)) out.type = PROTO_FAIL;
break;
+ case PROTO_SHOW:
+ if (!greeted) out.type = PROTO_FAIL;
+ else if (!do_show(&in)) out.type = PROTO_FAIL;
+ break;
+
+ case PROTO_SETSTYLE:
+ if (!greeted) out.type = PROTO_FAIL;
+ else if (!do_setstyle(&in)) out.type = PROTO_FAIL;
+ break;
+
default:
out.type = PROTO_FAIL;
break;
diff --git a/src/proto.h b/src/proto.h
index 485e37f..d56f582 100644
--- a/src/proto.h
+++ b/src/proto.h
@@ -2,16 +2,18 @@
#include
-#define MSHELLD_PROTO_VERSION 4u
+#define MSHELLD_PROTO_VERSION 5u
#define MSHELLD_PIPE_PREFIX L"\\\\.\\pipe\\mshelld-"
typedef enum {
- PROTO_HELLO = 1,
- PROTO_SETPOS = 2,
- PROTO_ZORDER = 3,
- PROTO_CLOSE = 5,
- PROTO_OK = 6,
- PROTO_FAIL = 7,
+ PROTO_HELLO = 1,
+ PROTO_SETPOS = 2,
+ PROTO_ZORDER = 3,
+ PROTO_CLOSE = 5,
+ PROTO_OK = 6,
+ PROTO_FAIL = 7,
+ PROTO_SHOW = 8, /* ShowWindow(hwnd, (int)flags) on a higher-integrity window */
+ PROTO_SETSTYLE = 9, /* set GWL_STYLE/GWL_EXSTYLE, then frame-change, cross-integrity */
} ProtoType;
typedef struct {
@@ -21,4 +23,9 @@ typedef struct {
uint64_t hwnd;
int32_t x, y, w, h;
uint32_t flags;
+
+ /* PROTO_SETSTYLE: the whole GWL_STYLE / GWL_EXSTYLE words to assert.
+ 64-bit because LONG_PTR is 64-bit on the only architecture we build. */
+ int64_t style;
+ int64_t exstyle;
} ProtoMsg;
diff --git a/src/window_decor.c b/src/window_decor.c
index 8fd5a7e..8ad5464 100644
--- a/src/window_decor.c
+++ b/src/window_decor.c
@@ -73,11 +73,21 @@ void window_strip_decorations(HWND hwnd) {
SWP_NOACTIVATE | SWP_FRAMECHANGED);
bool stripped = !(GetWindowLongPtrW(hwnd, GWL_STYLE) & WS_CAPTION);
+
+ /* A window owned by a higher-integrity process ignores SetWindowLongPtr
+ from here (UIPI), so it keeps its title bar and min/max buttons. The
+ elevated helper can set the style for us — it did the frame-change too. */
+ if (!stripped) {
+ stripped = helper_set_style(hwnd, style, exstyle) &&
+ !(GetWindowLongPtrW(hwnd, GWL_STYLE) & WS_CAPTION);
+ }
+
if (!stripped && !mw->decor_strip_refused) {
mw->decor_strip_refused = true;
log_msg(LOG_WARN, L"could not strip decorations from %p — the window "
- L"belongs to a higher-integrity process and keeps its "
- L"title bar", (void *)hwnd);
+ L"belongs to a higher-integrity process and no "
+ L"mshelld.exe is running to do it, so it keeps its "
+ L"title bar (see INSTALL.md)", (void *)hwnd);
}
mw->decorations_stripped = stripped;
@@ -109,6 +119,12 @@ void window_restore_decorations(HWND hwnd) {
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
SWP_NOACTIVATE | SWP_FRAMECHANGED);
+ /* If the strip went through the helper, the restore must too: a higher-
+ integrity window would otherwise keep the stripped frame after mshell
+ exits. */
+ if (GetWindowLongPtrW(hwnd, GWL_STYLE) != mw->orig_style)
+ helper_set_style(hwnd, mw->orig_style, mw->orig_exstyle);
+
mw->decorations_stripped = false;
frame_props_init();
RemovePropW(hwnd, PROP_ORIG_STYLE);
diff --git a/src/window_visibility.c b/src/window_visibility.c
index 7b56ec8..935a43f 100644
--- a/src/window_visibility.c
+++ b/src/window_visibility.c
@@ -270,6 +270,21 @@ void window_unminimize(ManagedWindow *mw) {
events_suppress_begin();
ShowWindow(mw->hwnd, SW_SHOWNOACTIVATE);
+
+ /* ShowWindow can't report a UIPI refusal: on a window owned by a higher-
+ integrity process it silently does nothing and the window stays
+ minimized with no taskbar to bring it back from. Confirm it actually
+ un-minimized, and hand it to the elevated helper if it didn't. */
+ if (IsIconic(mw->hwnd)) {
+ if (!helper_show_window(mw->hwnd, SW_SHOWNOACTIVATE) &&
+ !mw->unminimize_refused) {
+ mw->unminimize_refused = true;
+ log_msg(LOG_WARN, L"could not un-minimize %p — it belongs to a "
+ L"higher-integrity process and no mshelld.exe is "
+ L"running to restore it (see INSTALL.md).",
+ (void *)mw->hwnd);
+ }
+ }
events_suppress_end();
mw->has_applied = false;
}
diff --git a/src/window_zorder.c b/src/window_zorder.c
index 5922e0f..1f4296b 100644
--- a/src/window_zorder.c
+++ b/src/window_zorder.c
@@ -207,7 +207,12 @@ static void claim_foreground_rights(void) {
void window_focus(HWND hwnd) {
if (!hwnd || !IsWindow(hwnd)) return;
- if (IsIconic(hwnd)) ShowWindow(hwnd, SW_RESTORE);
+ if (IsIconic(hwnd)) {
+ ShowWindow(hwnd, SW_RESTORE);
+ /* A higher-integrity window ignores the call above; let the helper
+ restore it so focusing a minimized admin window still works. */
+ if (IsIconic(hwnd)) helper_show_window(hwnd, SW_RESTORE);
+ }
SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
diff --git a/tools/probe_uipi.c b/tools/probe_uipi.c
index b54f109..7be7a61 100644
--- a/tools/probe_uipi.c
+++ b/tools/probe_uipi.c
@@ -370,6 +370,92 @@ static int cmd_syscommand(int argc, wchar_t **argv) {
return pass ? 0 : 1;
}
+/* Replay the three things mshell does to a managed window — strip the frame,
+ un-minimize it, move it — directly from this (unelevated) process, so we can
+ see which ones a higher-integrity window silently refuses. This is the
+ on-machine confirmation of why elevated windows need the helper or uiAccess. */
+static int cmd_manage(int argc, wchar_t **argv) {
+ HWND target = resolve_target(argc, argv, 2);
+ if (!target) {
+ wprintf(L"FAIL no target: open an elevated window (e.g. Task Manager) "
+ L"or pass an HWND\n");
+ return 2;
+ }
+
+ wchar_t desc[128];
+ describe_window(target, desc, 128);
+ wprintf(L"target %p %ls\n\n", (void *)target, desc);
+
+ DWORD protect = try_setpos_noop(target);
+ wprintf(L"guard no-op SetWindowPos error = %ld %ls\n\n", (long)protect,
+ protect == ERROR_ACCESS_DENIED
+ ? L"(UIPI-protected: this is the interesting case)"
+ : L"(NOT protected from here — try an elevated target unelevated)");
+
+ /* [1] Strip WS_CAPTION, as window_strip_decorations does. */
+ wprintf(L"[1] SetWindowLongPtr(GWL_STYLE) to drop WS_CAPTION/SYSMENU/"
+ L"MIN/MAXBOX\n");
+ LONG_PTR style0 = GetWindowLongPtrW(target, GWL_STYLE);
+ LONG_PTR want = style0 & ~(LONG_PTR)(WS_CAPTION | WS_SYSMENU |
+ WS_MINIMIZEBOX | WS_MAXIMIZEBOX);
+ SetLastError(0);
+ SetWindowLongPtrW(target, GWL_STYLE, want);
+ DWORD e_style = GetLastError();
+ SetWindowPos(target, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE |
+ SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
+ LONG_PTR style1 = GetWindowLongPtrW(target, GWL_STYLE);
+ bool stripped = !(style1 & WS_CAPTION);
+ wprintf(L" set error = %lu caption now %ls -> %ls\n",
+ e_style, stripped ? L"GONE" : L"STILL PRESENT",
+ stripped ? L"strip SUCCEEDED" : L"strip REFUSED (UIPI)");
+ if (stripped) { /* put it back so the probe is non-destructive */
+ SetWindowLongPtrW(target, GWL_STYLE, style0);
+ SetWindowPos(target, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE |
+ SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
+ }
+ wprintf(L"\n");
+
+ /* [2] Minimize via the window's own system menu (always allowed), then try
+ to un-minimize it the way mshell does — ShowWindow(SW_SHOWNOACTIVATE). */
+ wprintf(L"[2] minimize, then ShowWindow(SW_SHOWNOACTIVATE) to bring it back\n");
+ SendMessageTimeoutW(target, WM_SYSCOMMAND, SC_MINIMIZE, 0,
+ SMTO_ABORTIFHUNG, 1000, NULL);
+ bool went_down = wait_for(target, is_iconic, 1500);
+ wprintf(L" minimized = %d\n", went_down);
+ if (went_down) {
+ ShowWindow(target, SW_SHOWNOACTIVATE);
+ bool back = wait_for(target, is_not_iconic, 1500);
+ wprintf(L" ShowWindow restored = %ls\n",
+ back ? L"YES — restore works from here"
+ : L"NO — restore SILENTLY REFUSED (needs helper/uiAccess)");
+ if (!back) { /* don't leave the user's window stuck minimized */
+ DWORD e_sc = try_post(target, WM_SYSCOMMAND, SC_RESTORE, 0);
+ wprintf(L" (left minimized; SC_RESTORE post error = %ld)\n",
+ (long)e_sc);
+ }
+ }
+ wprintf(L"\n");
+
+ /* [3] Move it 1px and back, as the tiler's SetWindowPos does. */
+ wprintf(L"[3] SetWindowPos move (what tiling does)\n");
+ RECT r = {0};
+ GetWindowRect(target, &r);
+ SetLastError(0);
+ BOOL moved = SetWindowPos(target, NULL, r.left + 1, r.top, 0, 0,
+ SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
+ DWORD e_move = moved ? 0 : GetLastError();
+ if (moved) SetWindowPos(target, NULL, r.left, r.top, 0, 0,
+ SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
+ wprintf(L" moved = %d error = %ld -> %ls\n\n", moved != 0, (long)e_move,
+ moved ? L"move allowed" : L"move REFUSED (UIPI)");
+
+ wprintf(L"VERDICT on a UIPI-protected target, expect strip/restore/move to "
+ L"be refused from here;\n"
+ L" that is exactly the gap mshelld.exe (or a uiAccess build) "
+ L"closes.\n");
+ return 0;
+}
+
static const wchar_t *mods_name(UINT mods, wchar_t *buf, size_t cap) {
_snwprintf(buf, cap, L"%ls%ls%ls%ls",
(mods & MOD_WIN) ? L"Win+" : L"",
@@ -743,6 +829,7 @@ static int cmd_hotkey(int argc, wchar_t **argv) {
static void usage(void) {
wprintf(L"usage: probe_uipi integrity\n"
L" probe_uipi syscommand [hwnd] [--close]\n"
+ L" probe_uipi manage [hwnd]\n"
L" probe_uipi hotkey [--mode mshell|pass] [--seconds N] [--stall]\n"
L" probe_uipi register-all\n");
}
@@ -761,6 +848,7 @@ int wmain(int argc, wchar_t **argv) {
if (argc < 2) { usage(); return 2; }
if (_wcsicmp(argv[1], L"integrity") == 0) return cmd_integrity();
if (_wcsicmp(argv[1], L"syscommand") == 0) return cmd_syscommand(argc, argv);
+ if (_wcsicmp(argv[1], L"manage") == 0) return cmd_manage(argc, argv);
if (_wcsicmp(argv[1], L"hotkey") == 0) return cmd_hotkey(argc, argv);
if (_wcsicmp(argv[1], L"register-all") == 0) return cmd_register_all();
usage();