diff --git a/INSTALL.md b/INSTALL.md index 2040eab..9f318c5 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -142,26 +142,22 @@ elevated. In order of preference: `mshelld.exe` ships alongside mshell and exists to make elevating mshell unnecessary. It is elevated; mshell is not. It has **no config file, no Lua, no -scripting, no window rules and no keyboard hook** — it accepts four requests: +scripting, no window rules and no keyboard hook** — it accepts three requests: "put this window at this rectangle", "put this window in or out of the -always-on-top band", "cloak or uncloak this window" and "post this window a -WM_CLOSE". Every decision stays in the unelevated shell. +always-on-top band" and "post this window a WM_CLOSE". Every decision stays in +the unelevated shell. With it running, an unelevated mshell tiles windows owned by elevated processes (Task Manager, regedit, an admin terminal) instead of leaving them floating — -and, just as importantly, *hides* them when you switch desktops: cloaking a -window is blocked by the same integrity check as moving it, so without the +and, just as importantly, *hides* them when you switch desktops: an elevated +window can't be sunk under the backdrop, so it is parked off every display +instead, and that move meets the same integrity check as tiling it. Without the helper an elevated window is visible on every desktop at once. -**And cloaking is not only about elevated windows.** DWM refuses -`DWMWA_CLOAK` on *any* window an unelevated process does not own, ordinary -same-user windows included, so with no helper running the default `"cloak"` -hide policy is not what actually happens: every desktop switch falls back to -`ShowWindow(SW_HIDE)`. Chromium-based apps (Chrome, Edge, Electron) rebuild -their compositor in the wrong place after that — the page walks further into -the window on every switch, with the app's own frame colour filling the gap, -until the app is restarted. If you switch desktops with a browser open, install -the helper. +The helper does not cloak anything. DWM only lets a window's owner cloak it, so +no other process can — elevated or not. Ordinary windows don't need the helper +to be hidden: they are sunk under the desktop backdrop (see "How a desktop is +taken off the screen" in the README). It is also what lets `float_on_top` hold for such a window: floats are kept above the grid by @@ -211,7 +207,7 @@ all. The log says which identity it granted on the line above `listening on`, so if mshell is not connecting, start there. **What running it trusts.** The helper is elevated and does what it is asked — -place a window, band it topmost, cloak it, close it — for any window, including +place a window, band it topmost, close it — for any window, including ones owned by elevated processes. That is the whole point of it: tiling an administrator's window means being able to move an administrator's window. mshelld only accepts requests from the `mshell.exe` sitting in its own diff --git a/MANUAL-TESTS.md b/MANUAL-TESTS.md index 15edd2d..a374164 100644 --- a/MANUAL-TESTS.md +++ b/MANUAL-TESTS.md @@ -188,7 +188,8 @@ something off-screen being handed the keyboard. `\\.\pipe\mshelld-`. The helper's log names the SID it granted the pipe to, which should be yours. - Mismatched builds (an old `mshelld.exe` against a new `mshell.exe`) refuse - each other with a logged protocol-version message. + each other with a logged protocol-version message. Protocol 3 → 4 in + particular: no window is closed or moved while they disagree. ### Hiding and closing elevated windows (protocol v2) @@ -198,7 +199,7 @@ With `mshelld.exe` running: is back, drawn correctly (not black). Before v2 it stayed on every desktop. - With Task Manager hidden on a background desktop, quit mshell (`Win+Shift+Q`): it is visible afterwards — an elevated window must not be - stranded cloaked on exit. + stranded off-screen on exit. - Focus Task Manager and press the close binding (`Win+Shift+c`): it closes. - Same three with an *admin* terminal or regedit, and with an app run explicitly as administrator (right-click → Run as administrator). @@ -913,8 +914,8 @@ Chrome or Edge, VS Code, Discord or Spotify (Electron), and something WPF. and usable afterwards — a cloaked window that outlives mshell keeps a taskbar button that does nothing, so this is the check that matters most. - Kill `mshell.exe` from Task Manager with windows on three desktops (shell mode - — Winlogon restarts it). The restarted mshell uncloaks what the dead one left - behind; the log says `uncloaked N window(s)`. + — Winlogon restarts it). The restarted mshell recovers what the dead one left + behind; the log says `recovered N window(s)`. - `mshell.window.policy.hide("hide")`, save, then switch desktops. Desktops still work. Windows may flicker and a GPU-heavy app may briefly blank — that is the mechanism, and it is why `"cloak"` is the default. diff --git a/Makefile b/Makefile index 8244d7c..483f9de 100644 --- a/Makefile +++ b/Makefile @@ -131,7 +131,7 @@ HELPER = mshelld.exe HELPER_FULL = mshelld.unstripped.exe HELPER_SRCS = $(SRC_DIR)/mshelld.c $(SRC_DIR)/log.c $(SRC_DIR)/pipe_sd.c HELPER_OBJS = $(HELPER_SRCS:.c=.o) -HELPER_LDLIBS = -luser32 -ladvapi32 -ldwmapi -lwintrust -lcrypt32 +HELPER_LDLIBS = -luser32 -ladvapi32 -lwintrust -lcrypt32 SYMBOLS = $(TARGET).debug $(HELPER).debug diff --git a/README.md b/README.md index 21e6094..c5e2357 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,7 @@ tiled, driven entirely from the keyboard and configured in Lua. - **An optional privileged helper** (`mshelld.exe`) so an *unelevated* mshell can still tile, hide and close windows owned by elevated processes — without your `init.lua` ever becoming administrator-level code. No config, no Lua, no - scripting: it moves, cloaks and closes windows, and nothing else. Installed by + scripting: it moves, restacks and closes windows, and nothing else. Installed by `install.bat`; started by `install.bat /helper` from an administrator prompt. - **A built-in launcher.** `launcher` types a name and runs a program. If you want modules, Lua configuration and a clipboard/emoji story, @@ -619,9 +619,15 @@ made on the fly and never bound a key to. **How a desktop is taken off the screen (`set_hide_policy`).** A desktop you are not on is a set of windows that have been removed from view, and *how* they are -removed decides whether they come back looking right. mshell **cloaks** them -through DWM — the window keeps rendering and DWM simply stops compositing it, -which is exactly the mechanism Windows' own virtual desktops use. +removed decides whether they come back looking right. By default mshell +**sinks** them: each window drops below the desktop backdrop in the z-order, so +Windows still considers it visible and it keeps rendering, but nothing on +screen shows it. A window that can't be sunk — always-on-top, or owned by an +elevated process — is parked off every display instead (through `mshelld.exe` +for an elevated one), and `ShowWindow(SW_HIDE)` is the last resort. The policy +is still called `"cloak"` from when it meant DWM cloaking, which mshell can't +use: DWM only lets a window's owner cloak it, and the shell cloak Windows' own +virtual desktops use lives inside explorer.exe. The obvious alternative, `ShowWindow(SW_HIDE)`, is what mshell did before 0.13.0 and it has a visible failure mode: clearing a window's visible bit makes DWM @@ -638,9 +644,9 @@ mshell.window.policy.hide("cloak") -- default mshell.window.policy.hide("hide") -- pre-0.13.0 ShowWindow(SW_HIDE) ``` -Cloaking alone is **not** what fixes the blackness, though — an app stops -presenting whether it learns it is invisible from the hide or from its own -occlusion tracking noticing the cloak. What fixes it is that a window coming +Keeping the window visible is **not** what fixes the blackness on its own, +though — an app can still stop presenting when its own occlusion tracking +decides nobody can see it. What fixes it is that a window coming back is always *asked to draw*: `RedrawWindow` over it and its children, plus a forced re-placement carrying `SWP_FRAMECHANGED` and `SWP_NOCOPYBITS` so the tiler cannot skip it for already being in the right place. That applies to both diff --git a/src/helper.c b/src/helper.c index cb8d5f3..7c6fb1f 100644 --- a/src/helper.c +++ b/src/helper.c @@ -225,18 +225,6 @@ bool helper_set_topmost(HWND hwnd, bool on) { &warned); } -bool helper_set_cloak(HWND hwnd, bool on) { - static bool warned; - - ProtoMsg req = { - .type = PROTO_CLOAK, - .version = MSHELLD_PROTO_VERSION, - .hwnd = (uint64_t)(uintptr_t)hwnd, - .flags = on ? 1u : 0u, - }; - return helper_request(&req, L"a window could not be hidden", &warned); -} - bool helper_close_window(HWND hwnd) { static bool warned; diff --git a/src/mshell.h b/src/mshell.h index 1203bd0..4db962e 100644 --- a/src/mshell.h +++ b/src/mshell.h @@ -829,7 +829,6 @@ void helper_restart_async(void); 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_set_cloak(HWND hwnd, bool on); bool helper_close_window(HWND hwnd); typedef enum { diff --git a/src/mshelld.c b/src/mshelld.c index af3620e..4c25e18 100644 --- a/src/mshelld.c +++ b/src/mshelld.c @@ -7,7 +7,6 @@ #endif #include -#include #include #include #include @@ -20,10 +19,6 @@ #include "log.h" #include "pipe_sd.h" -#ifndef DWMWA_CLOAK -#define DWMWA_CLOAK 13 -#endif - #define MSHELLD_CLIENT_EXE L"mshell.exe" #define MSHELLD_HANDSHAKE_MS 5000 #define MSHELLD_WRITE_MS 5000 @@ -271,14 +266,6 @@ static bool do_zorder(const ProtoMsg *m) { SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE) != 0; } -static bool do_cloak(const ProtoMsg *m) { - HWND h = (HWND)(uintptr_t)m->hwnd; - if (!h || !IsWindow(h)) return false; - - BOOL v = (m->flags & 1u) ? TRUE : FALSE; - return SUCCEEDED(DwmSetWindowAttribute(h, DWMWA_CLOAK, &v, sizeof(v))); -} - static bool do_close(const ProtoMsg *m) { HWND h = (HWND)(uintptr_t)m->hwnd; if (!h || !IsWindow(h)) return false; @@ -327,11 +314,6 @@ static void serve(const Client *c) { else if (!do_zorder(&in)) out.type = PROTO_FAIL; break; - case PROTO_CLOAK: - if (!greeted) out.type = PROTO_FAIL; - else if (!do_cloak(&in)) out.type = PROTO_FAIL; - break; - case PROTO_CLOSE: if (!greeted) out.type = PROTO_FAIL; else if (!do_close(&in)) out.type = PROTO_FAIL; diff --git a/src/proto.h b/src/proto.h index e0b52f5..485e37f 100644 --- a/src/proto.h +++ b/src/proto.h @@ -2,17 +2,16 @@ #include -#define MSHELLD_PROTO_VERSION 3u +#define MSHELLD_PROTO_VERSION 4u #define MSHELLD_PIPE_PREFIX L"\\\\.\\pipe\\mshelld-" typedef enum { - PROTO_HELLO = 1, - PROTO_SETPOS, - PROTO_ZORDER, - PROTO_CLOAK, - PROTO_CLOSE, - PROTO_OK, - PROTO_FAIL, + PROTO_HELLO = 1, + PROTO_SETPOS = 2, + PROTO_ZORDER = 3, + PROTO_CLOSE = 5, + PROTO_OK = 6, + PROTO_FAIL = 7, } ProtoType; typedef struct { diff --git a/src/window_visibility.c b/src/window_visibility.c index dadd574..feee019 100644 --- a/src/window_visibility.c +++ b/src/window_visibility.c @@ -17,7 +17,6 @@ static HRESULT dwm_set_cloaked(HWND hwnd, bool on) { static bool window_set_cloaked(HWND hwnd, bool on) { HRESULT hr = dwm_set_cloaked(hwnd, on); if (SUCCEEDED(hr)) return true; - if (helper_set_cloak(hwnd, on)) return true; static bool warned; if (!warned) {