Repository navigation
Expand file tree
/
Copy pathhook.cpp
More file actions
97 lines (85 loc) · 3.77 KB
/
Copy pathhook.cpp
File metadata and controls
97 lines (85 loc) · 3.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
// hook.cpp
//
// Builds to vghook32.dll / vghook64.dll - the DLL that gets injected
// into a target process (e.g. the Citrix Workspace app's session-rendering
// process, or the Windows App's RDP-rendering process) by vgagent32.exe /
// vgagent64.exe (see agent.cpp). Once loaded, it spawns a background
// thread that repeatedly walks every top-level window owned by the host
// process and marks it WDA_EXCLUDEFROMCAPTURE via SetWindowDisplayAffinity -
// unless live protection has been switched off from the vgtray.exe tray
// icon, in which case it sets WDA_NONE instead, reverting the window back to
// normal. See kProtectionEventName in common.h for how that toggle reaches
// in here.
//
// That flag is enforced by the Desktop Window Manager (DWM) at the
// compositor level: any consumer of the composited desktop surface -
// Print Screen, Snipping Tool, screen recorders, most screen-share tools,
// and most remote-control/remote-support tools - will see a blank/omitted
// region wherever this window is, while the window still displays normally
// on the machine's own physical screen.
//
// Nothing here reads, modifies, or transmits any window content. It only
// ever calls a single documented Win32 API (SetWindowDisplayAffinity) on
// windows that belong to the process this DLL is loaded into.
#include <windows.h>
#include "common.h"
static const DWORD kPollIntervalMs = 750;
struct MarkContext {
DWORD targetPid;
DWORD desiredAffinity;
};
static BOOL CALLBACK MarkWindowIfOwned(HWND hwnd, LPARAM lparam) {
auto* ctx = reinterpret_cast<MarkContext*>(lparam);
DWORD ownerPid = 0;
GetWindowThreadProcessId(hwnd, &ownerPid);
if (ownerPid == ctx->targetPid) {
DWORD currentAffinity = WDA_NONE;
if (GetWindowDisplayAffinity(hwnd, ¤tAffinity)) {
if (currentAffinity != ctx->desiredAffinity) {
SetWindowDisplayAffinity(hwnd, ctx->desiredAffinity);
}
}
}
return TRUE; // keep enumerating
}
static DWORD WINAPI ProtectionThreadProc(LPVOID) {
const DWORD myPid = GetCurrentProcessId();
// Opened once and held for the process's lifetime. If vgtray.exe
// isn't running yet (or ever), this simply fails and every poll below
// falls back to "protect" - the safe default is hiding content, not
// exposing it, if the on/off signal is unavailable for any reason.
HANDLE enabledEvent = OpenEventW(SYNCHRONIZE, FALSE,
vg_common::kProtectionEventName);
// Loop forever: new windows (dialogs, re-opened session windows, etc.)
// can appear at any point after injection, so a single pass at
// load time isn't enough - we keep re-applying the flag. Re-checking
// the enabled state every cycle (rather than once) is what lets the
// tray's Enable/Disable toggle affect a window that's already hidden.
for (;;) {
DWORD desiredAffinity = WDA_EXCLUDEFROMCAPTURE;
if (enabledEvent && WaitForSingleObject(enabledEvent, 0) != WAIT_OBJECT_0) {
desiredAffinity = WDA_NONE; // tray says protection is off - reveal
}
MarkContext ctx{myPid, desiredAffinity};
EnumWindows(MarkWindowIfOwned, reinterpret_cast<LPARAM>(&ctx));
Sleep(kPollIntervalMs);
}
return 0;
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD reasonForCall, LPVOID) {
switch (reasonForCall) {
case DLL_PROCESS_ATTACH: {
// Avoid unnecessary DLL_THREAD_ATTACH/DETACH notifications.
DisableThreadLibraryCalls(hModule);
HANDLE hThread = CreateThread(
nullptr, 0, ProtectionThreadProc, nullptr, 0, nullptr);
if (hThread) {
CloseHandle(hThread);
}
break;
}
default:
break;
}
return TRUE;
}