IME / WGC reproduction materials Six original source-text and supporting files combined for one Microsoft Q&A attachment. The original file bytes, line endings and SHA-256 values are preserved in each section. To reconstruct exact files, begin immediately after the BEGIN CONTENT marker's newline and read the stated Original bytes count. The separator following those bytes is not part of the file. WGC-REPRO.txt contains the original build/run instructions and filename mapping. Packaging only: no new build, native experiment, executable, image or run log is included. ========== BEGIN FILE: WindowSnapshot.cpp.txt ========== Original bytes: 32654 Original SHA-256: 0F174E67295D7A4975C9465AAECEAAFA7F208301A4E4B97EF4743BD052FA0B17 BEGIN CONTENT #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using Microsoft::WRL::ComPtr; using namespace winrt::Windows::Graphics::Capture; using namespace winrt::Windows::Graphics::DirectX; using namespace winrt::Windows::Graphics::DirectX::Direct3D11; namespace { class Handle final { public: explicit Handle(HANDLE h = nullptr) noexcept : value_(h) {} ~Handle() { if (value_ && value_ != INVALID_HANDLE_VALUE) CloseHandle(value_); } Handle(const Handle&) = delete; Handle& operator=(const Handle&) = delete; HANDLE Get() const noexcept { return value_; } private: HANDLE value_{}; }; struct Problem final : std::runtime_error { Problem(std::string message, int code) : runtime_error(std::move(message)), exitCode(code) {} int exitCode; }; [[noreturn]] void Win32Failure(const char* operation) { throw Problem(std::string(operation) + ": win32=" + std::to_string(GetLastError()), 2); } std::uint64_t Ticks(FILETIME time) noexcept { ULARGE_INTEGER value{}; value.LowPart = time.dwLowDateTime; value.HighPart = time.dwHighDateTime; return value.QuadPart; } std::uint64_t UtcNow() noexcept { FILETIME time{}; GetSystemTimeAsFileTime(&time); return Ticks(time); } std::uint64_t QpcNow() { LARGE_INTEGER value{}, frequency{}; if (!QueryPerformanceCounter(&value) || !QueryPerformanceFrequency(&frequency) || value.QuadPart <= 0 || frequency.QuadPart <= 0) Win32Failure("query-performance-counter"); const auto v = static_cast(value.QuadPart), f = static_cast(frequency.QuadPart); if (v / f > UINT64_MAX / 10'000'000ULL || v % f > UINT64_MAX / 10'000'000ULL) throw Problem("query-performance-counter: 100ns conversion overflow", 2); return (v / f) * 10'000'000ULL + ((v % f) * 10'000'000ULL) / f; } std::string Utf8(const std::wstring& value) { return winrt::to_string(value); } std::string Quote(std::string_view value) { std::string result = "\""; constexpr char hex[] = "0123456789abcdef"; for (const unsigned char c : value) { if (c == '"' || c == '\\') { result += '\\'; result += static_cast(c); } else if (c < 32) { result += "\\u00"; result += hex[c >> 4]; result += hex[c & 15]; } else result += static_cast(c); } return result + '"'; } std::uint64_t Number(const std::wstring& value) { const auto text = Utf8(value); std::uint64_t number{}; const auto parsed = std::from_chars(text.data(), text.data() + text.size(), number); if (parsed.ec != std::errc{} || parsed.ptr != text.data() + text.size() || number == 0) throw Problem("arguments: expected a positive decimal integer", 1); return number; } struct Options { DWORD pid{}; HWND hwnd{}; std::filesystem::path output; bool secondary{}; DWORD timeoutMs{5000}; unsigned minFrames{1}; DWORD observeMs{}; std::array observeRoi{}; // left, top, width, height in captured pixels std::array saveRoi{}; // optional: persist only this rectangle, never the full frame }; struct Result { std::string status{"failed"}, stage{"arguments"}, error, imageSha256; std::wstring imagePath; int exitCode{1}, width{}, height{}; std::uint64_t startedUtc{UtcNow()}, endedUtc{}, processCreatedUtc{}, captureStartedQpc{}, frameQpc{}, readbackUtc{}; unsigned frames{}, discardedFrames{}; bool secondaryReadback{}, cursorDisabled{}, saved{}, cleanupSuccess{true}, closedDuringCapture{}; bool destroyObserved{}, captureModulePinned{}; }; std::string HashFile(const std::wstring& path) { Handle file(CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)); if (file.Get() == INVALID_HANDLE_VALUE) Win32Failure("open-process-image"); struct HashOwner { BCRYPT_ALG_HANDLE algorithm{}; BCRYPT_HASH_HANDLE hash{}; ~HashOwner() { if (hash) BCryptDestroyHash(hash); if (algorithm) BCryptCloseAlgorithmProvider(algorithm, 0); } } owner; const auto check = [](NTSTATUS status) { if (status < 0) throw Problem("process-image-sha256: bcrypt=" + std::to_string(status), 2); }; check(BCryptOpenAlgorithmProvider(&owner.algorithm, BCRYPT_SHA256_ALGORITHM, nullptr, 0)); check(BCryptCreateHash(owner.algorithm, &owner.hash, nullptr, 0, nullptr, 0, 0)); std::vector buffer(65536); DWORD read{}; for (;;) { if (!ReadFile(file.Get(), buffer.data(), static_cast(buffer.size()), &read, nullptr)) Win32Failure("read-process-image"); if (!read) break; check(BCryptHashData(owner.hash, buffer.data(), read, 0)); } unsigned char digest[32]{}; check(BCryptFinishHash(owner.hash, digest, sizeof(digest), 0)); constexpr char hex[] = "0123456789abcdef"; std::string output; for (auto byte : digest) { output += hex[byte >> 4]; output += hex[byte & 15]; } return output; } void ExactWindow(const Options& options, HANDLE process) { DWORD pid{}; if (WaitForSingleObject(process, 0) != WAIT_TIMEOUT || !IsWindow(options.hwnd) || !IsWindowVisible(options.hwnd) || !GetWindowThreadProcessId(options.hwnd, &pid) || pid != options.pid) throw Problem("target-identity: exact visible window/process no longer alive", 2); } // HWND values have no public generation handle. Observe destruction from before // the first identity check, and reject reuse even when PID/thread are unchanged. // The caller must still own and identify the window before invoking this helper. class WindowLifetime final { public: WindowLifetime(const Options& options, HANDLE process, Result& result) : options_(options), process_(process), result_(result) { observer_ = this; hook_ = SetWinEventHook(EVENT_OBJECT_DESTROY, EVENT_OBJECT_DESTROY, nullptr, &OnEvent, options.pid, 0, WINEVENT_OUTOFCONTEXT); if (!hook_) { observer_ = nullptr; Win32Failure("observe-window-lifetime"); } } ~WindowLifetime() { if (hook_ && !UnhookWinEvent(hook_)) result_.cleanupSuccess = false; observer_ = nullptr; } WindowLifetime(const WindowLifetime&) = delete; WindowLifetime& operator=(const WindowLifetime&) = delete; void Check() { // Out-of-context WinEvents are delivered on the registering thread. MSG message{}; while (PeekMessageW(&message, nullptr, 0, 0, PM_REMOVE)) { TranslateMessage(&message); DispatchMessageW(&message); } if (result_.destroyObserved) throw Problem("target-identity: original window destroyed; HWND reuse rejected", 2); ExactWindow(options_, process_); } private: static void CALLBACK OnEvent(HWINEVENTHOOK, DWORD event, HWND hwnd, LONG object, LONG child, DWORD, DWORD) noexcept { if (observer_ && event == EVENT_OBJECT_DESTROY && hwnd == observer_->options_.hwnd && object == OBJID_WINDOW && child == CHILDID_SELF) observer_->result_.destroyObserved = true; } inline static thread_local WindowLifetime* observer_{}; const Options& options_; HANDLE process_{}; Result& result_; HWINEVENTHOOK hook_{}; }; void CheckDeadline(ULONGLONG deadline) { if (GetTickCount64() >= deadline) throw Problem("timeout: fresh frame/readback not completed; outcome unknown", 2); } struct Image { int width{}, height{}; std::vector pixels; }; Image CropForSave(const Image& image, const std::array& roi) { const auto [left, top, width, height] = roi; if (left < 0 || top < 0 || width <= 0 || height <= 0 || left > image.width - width || top > image.height - height) throw Problem("save ROI outside captured image", 1); Image crop{width, height, std::vector(static_cast(width) * height * 4)}; for (int y = 0; y < height; ++y) { const auto start = (static_cast(top + y) * image.width + left) * 4; std::copy_n(image.pixels.data() + start, static_cast(width) * 4, crop.pixels.data() + static_cast(y) * width * 4); } return crop; } struct Apartment final { Apartment() { winrt::init_apartment(winrt::apartment_type::multi_threaded); } ~Apartment() { winrt::uninit_apartment(); } }; void PinCaptureModule(Result& result) { // A real run faulted in GraphicsCapture.dll_unloaded after Capture returned. // WGC's asynchronous shutdown may outlive its last COM object. This one-shot // helper pins only the activated system DLL until process termination; its // sessions, frames, pools and GPU objects are still explicitly released. wchar_t systemDirectory[MAX_PATH]{}; const auto length = GetSystemDirectoryW(systemDirectory, MAX_PATH); if (!length || length >= MAX_PATH) Win32Failure("capture-module-system-directory"); const auto path = std::wstring(systemDirectory, length) + L"\\GraphicsCapture.dll"; HMODULE module{}; if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, path.c_str(), &module)) Win32Failure("pin-activated-capture-module"); result.captureModulePinned = true; } class CaptureResources final { public: explicit CaptureResources(Result& result) : result_(result) {} ~CaptureResources() { if (session) { try { session.Close(); } catch (...) { result_.cleanupSuccess = false; } session = nullptr; } if (pool) { try { pool.Close(); } catch (...) { result_.cleanupSuccess = false; } pool = nullptr; } if (item && subscribed) { try { item.Closed(closedToken); } catch (...) { result_.cleanupSuccess = false; } } item = nullptr; device = nullptr; context.Reset(); d3d.Reset(); } ComPtr d3d; ComPtr context; IDirect3DDevice device{nullptr}; GraphicsCaptureItem item{nullptr}; Direct3D11CaptureFramePool pool{nullptr}; GraphicsCaptureSession session{nullptr}; std::shared_ptr closed = std::make_shared(false); winrt::event_token closedToken{}; bool subscribed{}; private: Result& result_; }; Image ReadFrame(CaptureResources& resources, const Direct3D11CaptureFrame& frame, Result& result, ULONGLONG deadline) { auto surface = frame.Surface(); auto access = surface.as<::Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>(); ComPtr texture; winrt::check_hresult(access->GetInterface(IID_PPV_ARGS(&texture))); D3D11_TEXTURE2D_DESC description{}; texture->GetDesc(&description); const auto size = frame.ContentSize(); if (size.Width <= 0 || size.Height <= 0 || static_cast(size.Width) > description.Width || static_cast(size.Height) > description.Height || description.Format != DXGI_FORMAT_B8G8R8A8_UNORM) throw Problem("frame-content: unsupported size/format or source resize", 2); const auto bytes = static_cast(size.Width) * static_cast(size.Height) * 4; if (bytes > MAXDWORD - sizeof(BITMAPFILEHEADER) - sizeof(BITMAPINFOHEADER)) throw Problem("frame-content: BMP exceeds supported size", 2); Image image{size.Width, size.Height, std::vector(static_cast(bytes))}; description.Usage = D3D11_USAGE_STAGING; description.BindFlags = 0; description.CPUAccessFlags = D3D11_CPU_ACCESS_READ; description.MiscFlags = 0; ComPtr staging; winrt::check_hresult(resources.d3d->CreateTexture2D(&description, nullptr, &staging)); resources.context->CopyResource(staging.Get(), texture.Get()); resources.context->Flush(); D3D11_MAPPED_SUBRESOURCE mapped{}; for (;;) { CheckDeadline(deadline); const auto status = resources.context->Map(staging.Get(), 0, D3D11_MAP_READ, D3D11_MAP_FLAG_DO_NOT_WAIT, &mapped); if (status != DXGI_ERROR_WAS_STILL_DRAWING) { winrt::check_hresult(status); break; } Sleep(1); } for (int row = 0; row < size.Height; ++row) { const auto* source = static_cast(mapped.pData) + static_cast(row) * mapped.RowPitch; auto* target = image.pixels.data() + static_cast(row) * static_cast(size.Width) * 4; std::copy_n(source, static_cast(size.Width) * 4, target); } resources.context->Unmap(staging.Get(), 0); result.width = size.Width; result.height = size.Height; result.readbackUtc = UtcNow(); return image; } void WriteBytes(HANDLE file, const void* data, DWORD size); void ObserveRoi(HANDLE file, const Options& options, const Image& image, const Result& result) { const auto [left, top, width, height] = options.observeRoi; if (left < 0 || top < 0 || width <= 0 || height <= 0 || left > image.width - width || top > image.height - height) throw Problem("observation ROI outside captured image", 1); unsigned black{}, gray{}, white{}, background{}, transparent{}; for (int y = top; y < top + height; ++y) for (int x = left; x < left + width; ++x) { const auto offset = (static_cast(y) * static_cast(image.width) + static_cast(x)) * 4; const auto* pixel = image.pixels.data() + offset; if (pixel[3] == 0) ++transparent; if (pixel[0] != pixel[1] || pixel[1] != pixel[2]) continue; if (pixel[0] == 0) ++black; if (pixel[0] == 32) ++background; if (pixel[0] == 192) ++gray; if (pixel[0] == 255) ++white; } const auto record = "{\"event\":\"roi_frame\",\"frame_qpc_100ns\":" + std::to_string(result.frameQpc) + ",\"readback_utc_100ns\":" + std::to_string(result.readbackUtc) + ",\"capture_started_qpc_100ns\":" + std::to_string(result.captureStartedQpc) + ",\"pixels\":" + std::to_string(static_cast(width) * static_cast(height)) + ",\"black\":" + std::to_string(black) + ",\"gray\":" + std::to_string(gray) + ",\"white\":" + std::to_string(white) + ",\"background\":" + std::to_string(background) + ",\"transparent\":" + std::to_string(transparent) + "}\n"; WriteBytes(file, record.data(), static_cast(record.size())); } Image Capture(const Options& options, WindowLifetime& lifetime, Result& result) { CaptureResources resources(result); result.stage = "capture-initialization"; if (!GraphicsCaptureSession::IsSupported()) throw Problem("WGC unavailable", 2); D3D_FEATURE_LEVEL level{}; winrt::check_hresult(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT, nullptr, 0, D3D11_SDK_VERSION, &resources.d3d, &level, &resources.context)); ComPtr dxgi; winrt::check_hresult(resources.d3d.As(&dxgi)); winrt::com_ptr inspectable; winrt::check_hresult(CreateDirect3D11DeviceFromDXGIDevice(dxgi.Get(), inspectable.put())); resources.device = inspectable.as(); auto interop = winrt::get_activation_factory(); PinCaptureModule(result); lifetime.Check(); winrt::check_hresult(interop->CreateForWindow(options.hwnd, winrt::guid_of(), winrt::put_abi(resources.item))); resources.closedToken = resources.item.Closed([closed = resources.closed](auto&&, auto&&) { closed->store(true); }); resources.subscribed = true; const auto size = resources.item.Size(); if (size.Width <= 0 || size.Height <= 0) throw Problem("capture-item: empty size", 2); resources.pool = Direct3D11CaptureFramePool::CreateFreeThreaded(resources.device, DirectXPixelFormat::B8G8R8A8UIntNormalized, 2, size); resources.session = resources.pool.CreateCaptureSession(resources.item); resources.session.IncludeSecondaryWindows(options.secondary); result.secondaryReadback = resources.session.IncludeSecondaryWindows(); if (result.secondaryReadback != options.secondary) throw Problem("secondary mode readback differs", 2); resources.session.IsCursorCaptureEnabled(false); result.cursorDisabled = !resources.session.IsCursorCaptureEnabled(); if (!result.cursorDisabled) throw Problem("cursor exclusion readback differs", 2); lifetime.Check(); const auto firstFrameDeadline = GetTickCount64() + options.timeoutMs; const auto deadline = firstFrameDeadline + options.observeMs; auto statisticsPath = options.output; statisticsPath.replace_extension(L".frames.jsonl"); Handle statistics(options.observeMs ? CreateFileW(statisticsPath.c_str(), GENERIC_WRITE, FILE_SHARE_READ, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr) : nullptr); if (options.observeMs && statistics.Get() == INVALID_HANDLE_VALUE) Win32Failure("create-observation-statistics"); ULONGLONG observeUntil{}; Image lastImage; result.captureStartedQpc = QpcNow(); resources.session.StartCapture(); result.stage = "awaiting-fresh-frame"; for (;;) { if (observeUntil && GetTickCount64() >= observeUntil) return lastImage; if (!observeUntil) CheckDeadline(firstFrameDeadline); CheckDeadline(deadline); lifetime.Check(); result.closedDuringCapture = resources.closed->load(); if (result.closedDuringCapture) throw Problem("capture item closed", 2); auto frame = resources.pool.TryGetNextFrame(); if (frame) { struct FrameClose final { Direct3D11CaptureFrame& frame; Result& result; ~FrameClose() { try { frame.Close(); } catch (...) { result.cleanupSuccess = false; } } } close{frame, result}; CheckDeadline(deadline); const auto timestamp = frame.SystemRelativeTime().count(); if (timestamp > 0 && static_cast(timestamp) >= result.captureStartedQpc && static_cast(timestamp) > result.frameQpc) { result.frameQpc = static_cast(timestamp); ++result.frames; if (result.frames >= options.minFrames) { result.stage = "frame-readback"; auto image = ReadFrame(resources, frame, result, deadline); CheckDeadline(deadline); lifetime.Check(); if (resources.closed->load()) { result.closedDuringCapture = true; throw Problem("capture item closed during readback", 2); } if (!options.observeMs) return image; ObserveRoi(statistics.Get(), options, image, result); lastImage = std::move(image); if (!observeUntil) observeUntil = GetTickCount64() + options.observeMs; } } else ++result.discardedFrames; } CheckDeadline(deadline); Sleep(5); } } void WriteBytes(HANDLE file, const void* data, DWORD size) { DWORD written{}; if (!WriteFile(file, data, size, &written, nullptr) || written != size) Win32Failure("write-output"); } void SaveBmp(const std::filesystem::path& path, const Image& image, Result& result) { bool created = false; try { Handle file(CreateFileW(path.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr)); if (file.Get() == INVALID_HANDLE_VALUE) Win32Failure("create-bmp"); created = true; BITMAPFILEHEADER header{}; header.bfType = 0x4d42; header.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); header.bfSize = header.bfOffBits + static_cast(image.pixels.size()); BITMAPINFOHEADER info{}; info.biSize = sizeof(info); info.biWidth = image.width; info.biHeight = -image.height; info.biPlanes = 1; info.biBitCount = 32; info.biCompression = BI_RGB; info.biSizeImage = static_cast(image.pixels.size()); WriteBytes(file.Get(), &header, sizeof(header)); WriteBytes(file.Get(), &info, sizeof(info)); WriteBytes(file.Get(), image.pixels.data(), static_cast(image.pixels.size())); if (!FlushFileBuffers(file.Get())) Win32Failure("flush-bmp"); } catch (...) { if (created && !DeleteFileW(path.c_str())) result.cleanupSuccess = false; throw; } } void SaveJson(const std::filesystem::path& path, const std::string& json, Result& result) { // The final name is the commit marker; readers never observe a partial JSON. auto temporary = path; temporary += L".tmp-" + std::to_wstring(GetCurrentProcessId()) + L"-" + std::to_wstring(UtcNow()); bool created = false; try { { Handle file(CreateFileW(temporary.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr)); if (file.Get() == INVALID_HANDLE_VALUE) Win32Failure("create-result-json-temporary"); created = true; WriteBytes(file.Get(), json.data(), static_cast(json.size())); if (!FlushFileBuffers(file.Get())) Win32Failure("flush-result-json"); } // No REPLACE_EXISTING: another run's receipt is never overwritten. if (!MoveFileExW(temporary.c_str(), path.c_str(), MOVEFILE_WRITE_THROUGH)) Win32Failure("publish-result-json"); created = false; } catch (...) { if (created && !DeleteFileW(temporary.c_str())) result.cleanupSuccess = false; throw; } } Options Parse(int argc, wchar_t** argv) { Options options; unsigned seen{}; for (int index = 1; index < argc; index += 2) { if (index + 1 >= argc) throw Problem("arguments: each option needs a value", 1); const std::wstring flag = argv[index], value = argv[index + 1]; unsigned bit{}; if (flag == L"--pid") { bit = 1; const auto n = Number(value); if (n > MAXDWORD) throw Problem("pid out of range", 1); options.pid = static_cast(n); } else if (flag == L"--hwnd") { bit = 2; const auto n = Number(value); if (n > UINTPTR_MAX) throw Problem("hwnd out of range", 1); options.hwnd = reinterpret_cast(static_cast(n)); } else if (flag == L"--output") { bit = 4; options.output = value; } else if (flag == L"--secondary") { bit = 8; if (value != L"on" && value != L"off") throw Problem("secondary must be on or off", 1); options.secondary = value == L"on"; } else if (flag == L"--timeout-ms") { bit = 16; const auto n = Number(value); if (n > MAXDWORD) throw Problem("timeout out of range", 1); options.timeoutMs = static_cast(n); } else if (flag == L"--min-frames") { bit = 32; const auto n = Number(value); if (n > UINT_MAX) throw Problem("min-frames out of range", 1); options.minFrames = static_cast(n); } else if (flag == L"--observe-ms") { bit = 64; const auto n = Number(value); if (!n || n > 300000) throw Problem("observe-ms out of range", 1); options.observeMs = static_cast(n); } else if (flag == L"--observe-roi" || flag == L"--save-roi") { bit = flag == L"--save-roi" ? 256 : 128; std::size_t start{}; auto& roi = bit == 256 ? options.saveRoi : options.observeRoi; for (std::size_t part = 0; part < roi.size(); ++part) { const auto end = value.find(L',', start); if ((part == 3) != (end == std::wstring::npos)) throw Problem("ROI requires left,top,width,height", 1); const auto component = value.substr(start, end == std::wstring::npos ? end : end - start); const auto n = part < 2 && component == L"0" ? 0 : Number(component); if (n > INT_MAX) throw Problem("ROI out of range", 1); roi[part] = static_cast(n); start = end + 1; } } else throw Problem("arguments: unknown option " + Utf8(flag), 1); if ((seen & bit) != 0) throw Problem("arguments: duplicate option", 1); seen |= bit; } if ((seen & 15) != 15) throw Problem("required: --pid --hwnd --output --secondary; optional: --timeout-ms (5000), --min-frames (1)", 1); if ((seen & 192) != 0 && (seen & 192) != 192) throw Problem("observe-ms and observe-roi are required together", 1); if ((seen & 256) && (!options.saveRoi[2] || !options.saveRoi[3])) throw Problem("save ROI must have positive size", 1); if (!options.output.is_absolute() || _wcsicmp(options.output.extension().c_str(), L".bmp") != 0) throw Problem("output must be an absolute new .bmp path", 1); if (!std::filesystem::is_directory(options.output.parent_path())) throw Problem("output parent directory does not exist", 1); return options; } std::string Json(const Options& options, const Result& result) { const auto boolean = [](bool value) { return value ? "true" : "false"; }; return "{\n \"schemaVersion\": 1,\n \"kind\": \"showthese-owned-window-snapshot\",\n \"status\": " + Quote(result.status) + ",\n \"exitCode\": " + std::to_string(result.exitCode) + ",\n \"stage\": " + Quote(result.stage) + ",\n \"error\": " + Quote(result.error) + ",\n \"targetPid\": " + std::to_string(options.pid) + ",\n \"targetHwnd\": " + std::to_string(reinterpret_cast(options.hwnd)) + ",\n \"processImagePath\": " + Quote(Utf8(result.imagePath)) + ",\n \"processImageSha256\": " + Quote(result.imageSha256) + ",\n \"processCreatedUtcFileTime100ns\": " + std::to_string(result.processCreatedUtc) + ",\n \"secondaryRequested\": " + boolean(options.secondary) + ",\n \"secondaryReadback\": " + boolean(result.secondaryReadback) + ",\n \"cursorCaptureDisabled\": " + boolean(result.cursorDisabled) + ",\n \"timeoutMs\": " + std::to_string(options.timeoutMs) + ",\n \"observeDurationMs\": " + std::to_string(options.observeMs) + ",\n \"observeRoi\": [" + std::to_string(options.observeRoi[0]) + "," + std::to_string(options.observeRoi[1]) + "," + std::to_string(options.observeRoi[2]) + "," + std::to_string(options.observeRoi[3]) + "]" + ",\n \"timeoutScope\": \"Capture start through GPU readback; initialization and file IO require a caller process watchdog\"" + ",\n \"minimumFreshFrames\": " + std::to_string(options.minFrames) + ",\n \"freshFramesObserved\": " + std::to_string(result.frames) + ",\n \"discardedFrames\": " + std::to_string(result.discardedFrames) + ",\n \"startedUtcFileTime100ns\": " + std::to_string(result.startedUtc) + ",\n \"captureStartedSystemRelativeTime100ns\": " + std::to_string(result.captureStartedQpc) + ",\n \"frameSystemRelativeTime100ns\": " + std::to_string(result.frameQpc) + ",\n \"readbackUtcFileTime100ns\": " + std::to_string(result.readbackUtc) + ",\n \"endedUtcFileTime100ns\": " + std::to_string(result.endedUtc) + ",\n \"contentWidth\": " + std::to_string(result.width) + ",\n \"contentHeight\": " + std::to_string(result.height) + ",\n \"savedRoi\": [" + std::to_string(options.saveRoi[0]) + "," + std::to_string(options.saveRoi[1]) + "," + std::to_string(options.saveRoi[2]) + "," + std::to_string(options.saveRoi[3]) + "]" + ",\n \"bmpPath\": " + Quote(Utf8(options.output.wstring())) + ",\n \"bmpSaved\": " + boolean(result.saved) + ",\n \"captureItemClosed\": " + boolean(result.closedDuringCapture) + ",\n \"captureModulePinnedUntilProcessExit\": " + boolean(result.captureModulePinned) + ",\n \"targetWindowDestroyObserved\": " + boolean(result.destroyObserved) + ",\n \"cleanupSuccess\": " + boolean(result.cleanupSuccess) + ",\n \"scope\": \"Fresh exact-window pixels only; no product operation or publication acknowledgement asserted\"\n}\n"; } } // namespace int wmain(int argc, wchar_t** argv) { Options options; Result result; // Keep the MTA alive through identity checks, receipt publication and all // native callback cleanup, rather than uninitializing it inside Capture. std::unique_ptr apartment; // Preserve a usable receipt destination even for an invalid non-output argument. for (int i = 1; i + 1 < argc; ++i) if (std::wstring_view(argv[i]) == L"--output") { options.output = argv[i + 1]; break; } bool preserveExisting = false; try { options = Parse(argc, argv); auto jsonPath = options.output; jsonPath.replace_extension(L".json"); auto statisticsPath = options.output; statisticsPath.replace_extension(L".frames.jsonl"); if (std::filesystem::exists(options.output) || std::filesystem::exists(jsonPath) || (options.observeMs && std::filesystem::exists(statisticsPath))) { preserveExisting = true; throw Problem("output or adjacent JSON already exists; refusing overwrite", 1); } result.stage = "target-identity"; Handle process(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE, options.pid)); if (!process.Get()) Win32Failure("open-target-process"); WindowLifetime lifetime(options, process.Get(), result); lifetime.Check(); FILETIME created{}, exited{}, kernel{}, user{}; if (!GetProcessTimes(process.Get(), &created, &exited, &kernel, &user)) Win32Failure("target-process-times"); result.processCreatedUtc = Ticks(created); std::wstring imagePath(32768, L'\0'); DWORD size = static_cast(imagePath.size()); if (!QueryFullProcessImageNameW(process.Get(), 0, imagePath.data(), &size)) Win32Failure("target-process-image"); imagePath.resize(size); result.imagePath = imagePath; result.imageSha256 = HashFile(imagePath); lifetime.Check(); apartment = std::make_unique(); const auto image = Capture(options, lifetime, result); if (!result.cleanupSuccess) throw Problem("capture resource cleanup failed", 1); result.stage = "before-save-identity"; lifetime.Check(); result.stage = "save-bmp"; if (options.saveRoi[2]) SaveBmp(options.output, CropForSave(image, options.saveRoi), result); else SaveBmp(options.output, image, result); result.saved = true; result.stage = "after-save-identity"; lifetime.Check(); result.status = options.observeMs ? "observed_sequence" : "captured"; result.stage = "complete"; result.exitCode = 0; } catch (const Problem& problem) { result.exitCode = problem.exitCode; result.status = result.exitCode == 2 ? "inconclusive" : "failed"; result.error = problem.what(); } catch (const winrt::hresult_error& error) { result.exitCode = 2; result.status = "inconclusive"; result.error = "hresult=" + std::to_string(error.code().value) + ": " + winrt::to_string(error.message()); } catch (const std::exception& error) { result.exitCode = 1; result.status = "failed"; result.error = error.what(); } catch (...) { result.exitCode = 1; result.status = "failed"; result.error = "unknown helper failure"; } if (!result.cleanupSuccess) { result.exitCode = 1; result.status = "failed"; if (result.error.empty()) result.error = "cleanup failed"; } result.endedUtc = UtcNow(); try { if (!result.saved && !options.output.empty() && std::filesystem::exists(options.output)) preserveExisting = true; const auto json = Json(options, result); if (!preserveExisting && options.output.is_absolute() && _wcsicmp(options.output.extension().c_str(), L".bmp") == 0) { auto jsonPath = options.output; jsonPath.replace_extension(L".json"); SaveJson(jsonPath, json, result); } else if (result.exitCode == 0) throw Problem("no valid result destination", 1); std::fwrite(json.data(), 1, json.size(), stdout); } catch (const std::exception& error) { std::fprintf(stderr, "Result receipt unavailable: %s\n", error.what()); return 1; } return result.exitCode; } ========== END FILE: WindowSnapshot.cpp.txt ========== ========== BEGIN FILE: CMakeLists.txt ========== Original bytes: 453 Original SHA-256: A0D892E00237AC4D209F590F1E6C0C8368FC4A82BD4AF4647A46A2358BDE6266 BEGIN CONTENT cmake_minimum_required(VERSION 3.25) project(ShowTheseWindowSnapshot LANGUAGES CXX) add_executable(WindowSnapshot WindowSnapshot.cpp) target_compile_features(WindowSnapshot PRIVATE cxx_std_20) target_compile_definitions(WindowSnapshot PRIVATE UNICODE _UNICODE WIN32_LEAN_AND_MEAN NOMINMAX) target_compile_options(WindowSnapshot PRIVATE /W4 /WX /permissive- /utf-8 /EHsc) target_link_libraries(WindowSnapshot PRIVATE d3d11 dxgi windowsapp ole32 bcrypt) ========== END FILE: CMakeLists.txt ========== ========== BEGIN FILE: SecondaryTopologyFixture.cs.txt ========== Original bytes: 18845 Original SHA-256: E1CDABF3D0B4F0383386AA21597C99A692636CB7482D2B0499583A084A9CC735 BEGIN CONTENT using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.IO; using System.Runtime.InteropServices; using System.Web.Script.Serialization; using System.Windows.Forms; // A local synthetic capture fixture. No input, hooks, target-app mutations, // process-memory access, IME class names or security-setting APIs. static class SecondaryTopologyFixture { [StructLayout(LayoutKind.Sequential)] struct Rect { public int l, t, r, b; } [DllImport("user32.dll")] static extern bool SetProcessDpiAwarenessContext(IntPtr value); [DllImport("user32.dll")] static extern bool IsWindow(IntPtr window); [DllImport("user32.dll")] static extern bool IsWindowVisible(IntPtr window); [DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr window, out uint pid); [DllImport("user32.dll")] static extern IntPtr GetWindow(IntPtr window, uint relation); [DllImport("user32.dll")] static extern IntPtr GetAncestor(IntPtr window, uint relation); [DllImport("user32.dll")] static extern IntPtr GetWindowLongPtrW(IntPtr window, int index); [DllImport("user32.dll", SetLastError = true)] static extern IntPtr SetWindowLongPtrW(IntPtr window, int index, IntPtr value); [DllImport("kernel32.dll")] static extern void SetLastError(uint value); [DllImport("kernel32.dll")] static extern uint GetCurrentThreadId(); [DllImport("user32.dll")] static extern bool GetWindowRect(IntPtr window, out Rect rect); [DllImport("user32.dll")] static extern bool GetWindowDisplayAffinity(IntPtr window, out uint affinity); [DllImport("user32.dll")] static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll")] static extern IntPtr GetTopWindow(IntPtr window); [DllImport("user32.dll")] static extern bool IsIconic(IntPtr window); [DllImport("dwmapi.dll")] static extern int DwmGetWindowAttribute(IntPtr window, uint attribute, out uint value, int bytes); static readonly JavaScriptSerializer Json = new JavaScriptSerializer(); static string directory; sealed class OwnedForm : Form { readonly IntPtr nativeOwner; public OwnedForm(IntPtr owner) { nativeOwner = owner; } protected override bool ShowWithoutActivation { get { return true; } } protected override CreateParams CreateParams { get { var p = base.CreateParams; p.Style = unchecked((int)0x80000000); // same WS_POPUP in all roles/cases p.ExStyle |= 0x80; // WS_EX_TOOLWINDOW; never TOPMOST or LAYERED p.Parent = nativeOwner; return p; } } } sealed class NativeOwner : IWin32Window { public NativeOwner(IntPtr handle) { Handle = handle; } public IntPtr Handle { get; private set; } } sealed class PopupOwner : IDisposable { readonly List forms = new List(); public IntPtr Popup { get; private set; } public PopupOwner(IntPtr primary, int hiddenCount) { try { IntPtr owner = primary; for (int n = 0; n != hiddenCount; ++n) { var hidden = NewForm(owner, new Rectangle(80, 80, 20, 20), Color.Navy); forms.Add(hidden); owner = hidden.Handle; SetOwnOwner(owner, forms.Count == 1 ? primary : forms[forms.Count - 2].Handle); if (IsWindowVisible(owner)) throw new Exception("hidden owner became visible"); } var popup = NewForm(owner, new Rectangle(200, 180, 180, 90), Color.Red); forms.Add(popup); popup.Show(new NativeOwner(owner)); Popup = popup.Handle; if (!IsWindowVisible(Popup)) throw new Exception("popup not visible"); WriteNew("popup-ready.json", new { popup = Read(Popup), owners = Chain(Popup) }); } catch { Dispose(); throw; } } public void Dispose() { for (int n = forms.Count - 1; n >= 0; --n) forms[n].Dispose(); forms.Clear(); } } static OwnedForm NewForm(IntPtr owner, Rectangle bounds, Color color) { var f = new OwnedForm(owner); f.Text = "Synthetic WGC topology"; f.FormBorderStyle = FormBorderStyle.None; f.ShowInTaskbar = false; f.StartPosition = FormStartPosition.Manual; f.AutoScaleMode = AutoScaleMode.None; f.Bounds = bounds; f.BackColor = color; return f; } static void SetOwnOwner(IntPtr window, IntPtr owner) { // WinForms adds an invisible parking owner to taskbar-less forms. // Set only this UI thread's own synthetic handle to the intended case topology. uint pid; uint tid = GetWindowThreadProcessId(window, out pid); using (var self = Process.GetCurrentProcess()) if (pid != self.Id || tid != GetCurrentThreadId() || !IsWindow(window)) throw new Exception("owner mutation target is not this fixture UI thread"); SetLastError(0); IntPtr previous = SetWindowLongPtrW(window, -8, owner); int error = Marshal.GetLastWin32Error(); if ((previous == IntPtr.Zero && error != 0) || GetWindow(window, 4) != owner) throw new Exception("synthetic owner assignment failed: " + error); } static long Created(int pid) { using (var process = Process.GetProcessById(pid)) return process.StartTime.ToFileTimeUtc(); } static object Read(IntPtr window) { uint pid; uint tid = GetWindowThreadProcessId(window, out pid); Rect rect; if (!GetWindowRect(window, out rect)) throw new Exception("window rect unavailable"); uint cloak, affinity; int cloakResult = DwmGetWindowAttribute(window, 14, out cloak, 4); bool affinityKnown = GetWindowDisplayAffinity(window, out affinity); return new { hwnd = window.ToInt64(), pid = pid, tid = tid, created = Created((int)pid), owner = GetWindow(window, 4).ToInt64(), rootOwner = GetAncestor(window, 3).ToInt64(), alive = IsWindow(window), visible = IsWindowVisible(window), style = unchecked((uint)GetWindowLongPtrW(window, -16).ToInt64()), exStyle = unchecked((uint)GetWindowLongPtrW(window, -20).ToInt64()), rect = new int[] { rect.l, rect.t, rect.r, rect.b }, cloakResult = cloakResult, cloaked = cloak, affinityKnown = affinityKnown, affinity = affinity }; } static object[] Chain(IntPtr window) { var result = new List(); var seen = new HashSet(); while (window != IntPtr.Zero) { if (!seen.Add(window.ToInt64()) || result.Count >= 5) throw new Exception("owner chain invalid"); result.Add(Read(window)); window = GetWindow(window, 4); } return result.ToArray(); } static void WriteNew(string name, object value) { string target = Path.Combine(directory, name), pending = target + ".pending"; using (var writer = new StreamWriter(new FileStream(pending, FileMode.CreateNew))) writer.Write(Json.Serialize(value)); File.Move(pending, target); // publish complete records only; never overwrite } static object Order(IntPtr primary, IntPtr popup) { bool foundPopup = false, foundPrimary = false; int scanned = 0; var between = new List(); var seen = new HashSet(); Rect primaryRect; if (!GetWindowRect(primary, out primaryRect)) throw new Exception("primary rect unavailable"); for (var w = GetTopWindow(IntPtr.Zero); w != IntPtr.Zero; w = GetWindow(w, 2)) { if (++scanned > 4096 || !seen.Add(w.ToInt64())) throw new Exception("z-order enumeration invalid"); if (w == popup) { foundPopup = true; continue; } if (w == primary) { foundPrimary = true; break; } if (!foundPopup || !IsWindowVisible(w) || IsIconic(w)) continue; uint cloak; int result = DwmGetWindowAttribute(w, 14, out cloak, 4); if (result == 0 && cloak != 0) continue; Rect rect; if (!GetWindowRect(w, out rect)) throw new Exception("intervening rect unavailable"); uint style = unchecked((uint)GetWindowLongPtrW(w, -16).ToInt64()); uint exStyle = unchecked((uint)GetWindowLongPtrW(w, -20).ToInt64()); bool intersects = rect.l < primaryRect.r && rect.r > primaryRect.l && rect.t < primaryRect.b && rect.b > primaryRect.t; // Anonymous geometry/style only: no foreign title, pixels or identity. between.Add(new { style = style, exStyle = exStyle, intersectsPrimary = intersects, rect = new int[] { rect.l, rect.t, rect.r, rect.b }, cloakKnown = result == 0, qualifyingStyle = (style & 0x80000000U) != 0 || (exStyle & 0x80U) != 0 }); if (between.Count > 64) throw new Exception("intervening window limit"); } return new { popupFound = foundPopup, primaryFound = foundPrimary, popupAbovePrimary = foundPopup && foundPrimary, visibleIntervening = between }; } static string Quote(string value) { if (value.IndexOf('"') >= 0 || value.EndsWith("\\")) throw new Exception("unsupported argument path"); return "\"" + value + "\""; } static void ValidateRoot(IntPtr window, int pid, long created, string token) { var record = Json.Deserialize>(File.ReadAllText(Path.Combine(directory, "primary.json"))); uint actual; GetWindowThreadProcessId(window, out actual); if (!IsWindow(window) || actual != pid || Created(pid) != created || Convert.ToInt64(record["primary"]) != window.ToInt64() || Convert.ToInt32(record["pid"]) != pid || Convert.ToInt64(record["created"]) != created || Convert.ToString(record["token"]) != token) throw new Exception("fixture primary identity expired or does not match handshake"); using (var root = Process.GetProcessById(pid)) using (var self = Process.GetCurrentProcess()) if (!String.Equals(root.MainModule.FileName, self.MainModule.FileName, StringComparison.OrdinalIgnoreCase)) throw new Exception("owner is not this fixture executable"); } static int Child(string[] args) { if (args.Length != 7) return 64; IntPtr primary = new IntPtr(long.Parse(args[2])); int pid = int.Parse(args[3]); long created = long.Parse(args[4]); string token = args[5]; int hiddenCount = int.Parse(args[6]); if (hiddenCount != 0 && hiddenCount != 2) return 64; ValidateRoot(primary, pid, created, token); IntPtr popupHandle; using (var popup = new PopupOwner(primary, hiddenCount)) using (var timer = new Timer()) { popupHandle = popup.Popup; int ticks = 0; Exception error = null; timer.Interval = 200; timer.Tick += delegate { try { ValidateRoot(primary, pid, created, token); if (++ticks >= 300 || File.Exists(Path.Combine(directory, "child-stop"))) Application.ExitThread(); } catch (Exception e) { error = e; Application.ExitThread(); } }; timer.Start(); Application.Run(); timer.Stop(); if (error != null) throw error; } WriteNew("child-ended.json", new { normalExit = true, popupDestroyed = !IsWindow(popupHandle) }); return 0; } static int Root(string[] args) { if (args.Length != 4 || (args[2] != "same" && args[2] != "cross") || (args[3] != "direct" && args[3] != "hidden")) return 64; bool cross = args[2] == "cross"; int hiddenCount = args[3] == "hidden" ? 2 : 0; string token = Guid.NewGuid().ToString("N"); Process child = null; PopupOwner popup = null; Exception error = null; IntPtr mainHandle = IntPtr.Zero, popupHandle = IntPtr.Zero; IntPtr foregroundBefore = GetForegroundWindow(); int ticks = 0, stopTicks = 0; bool stopping = false; using (var main = NewForm(IntPtr.Zero, new Rectangle(80, 80, 640, 400), Color.Navy)) using (var timer = new Timer()) { timer.Interval = 200; main.Shown += delegate { try { mainHandle = main.Handle; SetOwnOwner(mainHandle, IntPtr.Zero); using (var self = Process.GetCurrentProcess()) { long created = self.StartTime.ToFileTimeUtc(); WriteNew("primary.json", new { pid = self.Id, primary = mainHandle.ToInt64(), created = created, token = token, crossProcess = cross, hiddenOwners = hiddenCount, primaryFacts = Read(mainHandle), foregroundBefore = foregroundBefore.ToInt64(), foregroundAfterShow = GetForegroundWindow().ToInt64() }); if (cross) { var start = new ProcessStartInfo(self.MainModule.FileName, "child " + Quote(directory) + " " + mainHandle.ToInt64() + " " + self.Id + " " + created + " " + token + " " + hiddenCount); start.UseShellExecute = false; start.CreateNoWindow = true; start.WindowStyle = ProcessWindowStyle.Hidden; child = Process.Start(start); } else { popup = new PopupOwner(mainHandle, hiddenCount); popupHandle = popup.Popup; } } timer.Start(); } catch (Exception e) { error = e; main.Close(); } }; timer.Tick += delegate { try { main.BackColor = (++ticks % 2 == 0) ? Color.Navy : Color.DarkBlue; if (File.Exists(Path.Combine(directory, "popup-ready.json")) && !File.Exists(Path.Combine(directory, "ready.json"))) WriteNew("ready.json", new { utc = DateTime.UtcNow.ToString("o"), ticks = ticks }); foreach (var phase in new string[] { "before-off", "after-off", "before-on", "after-on" }) { if (!File.Exists(Path.Combine(directory, phase + ".request")) || File.Exists(Path.Combine(directory, phase + ".json"))) continue; var ready = Json.Deserialize>( File.ReadAllText(Path.Combine(directory, "popup-ready.json"))); var facts = (Dictionary)ready["popup"]; IntPtr currentPopup = new IntPtr(Convert.ToInt64(facts["hwnd"])); WriteNew(phase + ".json", new { utc = DateTime.UtcNow.ToString("o"), ticks = ticks, primary = Read(mainHandle), popup = Read(currentPopup), owners = Chain(currentPopup), order = Order(mainHandle, currentPopup) }); } if (child != null && child.HasExited && !stopping && !File.Exists(Path.Combine(directory, "stop"))) { error = new Exception("fixture child exited before stop: " + child.ExitCode); main.Close(); } if (ticks >= 300 || File.Exists(Path.Combine(directory, "stop"))) stopping = true; if (stopping) { // Keep the primary alive and pumping while its own child retires. // Destroying the owner first would invalidate the child's identity guard. if (child != null && !child.HasExited) { if (!File.Exists(Path.Combine(directory, "child-stop"))) File.WriteAllText(Path.Combine(directory, "child-stop"), "stop"); if (++stopTicks < 50) return; child.Kill(); bool exited = child.WaitForExit(2000); error = new Exception(exited ? "owned child stop watchdog expired; forced exit confirmed" : "owned child stop watchdog expired; forced exit unconfirmed"); } main.Close(); } } catch (Exception e) { error = e; main.Close(); } }; try { Application.Run(main); } finally { timer.Stop(); if (popup != null) popup.Dispose(); if (child != null) { using (child) { if (!File.Exists(Path.Combine(directory, "child-stop"))) File.WriteAllText(Path.Combine(directory, "child-stop"), "stop"); if (!child.WaitForExit(10000)) { child.Kill(); bool exited = child.WaitForExit(2000); error = new Exception(exited ? "owned child watchdog expired; forced exit confirmed" : "owned child watchdog expired; forced exit unconfirmed"); } else if (child.ExitCode != 0) error = new Exception("fixture child exit: " + child.ExitCode); } } } } WriteNew("root-ended.json", new { normalExit = error == null, ticks = ticks, primaryDestroyed = !IsWindow(mainHandle), sameProcessPopupDestroyed = popupHandle == IntPtr.Zero || !IsWindow(popupHandle), error = error == null ? null : error.Message }); if (error != null) throw error; return 0; } [STAThread] static int Main(string[] args) { if (args.Length < 2 || !Directory.Exists(args[1])) return 64; directory = Path.GetFullPath(args[1]); try { if (!SetProcessDpiAwarenessContext(new IntPtr(-4))) throw new Exception("physical DPI context unavailable"); if (args[0] == "root") return Root(args); if (args[0] == "child") return Child(args); return 64; } catch (Exception e) { WriteNew(args[0] + "-failure.json", new { type = e.GetType().Name, error = e.Message }); return 1; } } } ========== END FILE: SecondaryTopologyFixture.cs.txt ========== ========== BEGIN FILE: Run-Topology.ps1.txt ========== Original bytes: 6666 Original SHA-256: 362F246D7E7CDCA0A40252B9AFCE3A9E443A59FE3B5A5DCA912D81ACA13666D5 BEGIN CONTENT param( [Parameter(Mandatory=$true)][string]$FixturePath, [Parameter(Mandatory=$true)][string]$CapturePath, [Parameter(Mandatory=$true)][string]$OutputDirectory ) $ErrorActionPreference='Stop' $fixture=(Resolve-Path -LiteralPath $FixturePath).Path $capture=(Resolve-Path -LiteralPath $CapturePath).Path $r=[IO.Path]::GetFullPath($OutputDirectory) # The retained runner uses ArgumentList strings: reject whitespace before launch. if($fixture -match '\s' -or $capture -match '\s' -or $r -match '\s'){ throw 'This diagnostic runner requires paths without whitespace.' } if(Test-Path -LiteralPath $r){throw 'Output directory must be new; no overwrite or automatic retry'} New-Item -ItemType Directory -Path $r|Out-Null $revision='review source bundle; see SOURCE-IDENTITY.json' $fixtureHash=(Get-FileHash -LiteralPath $fixture).Hash $captureHash=(Get-FileHash -LiteralPath $capture).Hash function Read-Ready([string]$path,[Diagnostics.Process]$process){ $watch=[Diagnostics.Stopwatch]::StartNew() while(!(Test-Path -LiteralPath $path)){ if($process.HasExited){throw ('Fixture exited before ready: '+$process.ExitCode)} if($watch.Elapsed.TotalSeconds -ge 10){throw ('Fixture record deadline: '+(Split-Path $path -Leaf))} Start-Sleep -Milliseconds 100 } Get-Content -LiteralPath $path -Raw|ConvertFrom-Json } function Snapshot([string]$directory,[string]$phase,[Diagnostics.Process]$process,[int]$hidden,[bool]$cross,[int]$rootPid){ $request=Join-Path $directory ($phase+'.request') $stream=[IO.File]::Open($request,[IO.FileMode]::CreateNew);$stream.Dispose() $data=Read-Ready (Join-Path $directory ($phase+'.json')) $process if(!$data.primary.alive -or !$data.primary.visible -or !$data.popup.alive -or !$data.popup.visible -or $data.primary.pid -ne $rootPid -or $data.primary.cloaked -ne 0 -or $data.popup.cloaked -ne 0 -or $data.primary.cloakResult -ne 0 -or $data.popup.cloakResult -ne 0 -or !$data.primary.affinityKnown -or !$data.popup.affinityKnown -or $data.primary.affinity -ne 0 -or $data.popup.affinity -ne 0 -or !$data.order.popupAbovePrimary -or @($data.order.visibleIntervening).Count -ne 0){throw 'Window exposure/identity qualification failed'} if(@($data.owners).Count -ne ($hidden+2) -or $data.popup.rootOwner -ne $data.primary.hwnd){throw 'Owner chain qualification failed'} for($i=0;$i -lt ($data.owners.Count-1);$i++){ $w=$data.owners[$i];$same=$w.pid -eq $rootPid if($same -eq $cross -or !$w.alive -or ($i -gt 0 -and $w.visible)){throw 'Process/hidden-owner qualification failed'} if($w.owner -ne $data.owners[$i+1].hwnd){throw 'Owner chain identity mismatch'} } $data } $cases=@(@('same','direct'),@('same','hidden'),@('cross','direct'),@('cross','hidden')) $finished=@();$errorText=$null foreach($case in $cases){ $kind=$case[0]+'-'+$case[1];$dir=Join-Path $r $kind if(Test-Path $dir){throw 'Case directory exists; no blind retry or overwrite'} New-Item -ItemType Directory -Path $dir|Out-Null $rootProcess=$null;$caseError=$null try{ $rootProcess=Start-Process -FilePath $fixture -ArgumentList @('root',$dir,$case[0],$case[1]) -WindowStyle Hidden -PassThru -RedirectStandardOutput ($dir+'/root.stdout') -RedirectStandardError ($dir+'/root.stderr') $null=Read-Ready ($dir+'/ready.json') $rootProcess $primary=Get-Content ($dir+'/primary.json') -Raw|ConvertFrom-Json if($primary.pid -ne $rootProcess.Id -or $primary.foregroundBefore -ne $primary.foregroundAfterShow){throw 'Fixture identity or no-activation qualification failed'} $hidden=if($case[1] -eq 'hidden'){2}else{0};$cross=$case[0] -eq 'cross' foreach($mode in @('off','on')){ $before=Snapshot $dir ('before-'+$mode) $rootProcess $hidden $cross $rootProcess.Id $output=Join-Path $dir ($mode+'.bmp') $cp=Start-Process -FilePath $capture -ArgumentList @('--pid',[string]$primary.pid,'--hwnd',[string]$primary.primary,'--output',$output,'--secondary',$mode,'--min-frames','5') -WindowStyle Hidden -PassThru -RedirectStandardOutput ($dir+'/'+$mode+'.stdout') -RedirectStandardError ($dir+'/'+$mode+'.stderr') try{if(!$cp.WaitForExit(15000)){$cp.Kill();$exited=$cp.WaitForExit(2000);throw ('Owned capture watchdog expired; exit confirmed='+$exited)};if($cp.ExitCode -ne 0){throw ('Capture exit '+$cp.ExitCode)}}finally{$cp.Dispose()} $after=Snapshot $dir ('after-'+$mode) $rootProcess $hidden $cross $rootProcess.Id $frame=Get-Content ($dir+'/'+$mode+'.json') -Raw|ConvertFrom-Json if($frame.status -ne 'captured' -or $frame.exitCode -ne 0 -or !$frame.cleanupSuccess -or $frame.freshFramesObserved -lt 5 -or $frame.timeoutMs -ne 5000 -or $frame.secondaryReadback -ne ($mode -eq 'on') -or $frame.targetPid -ne $primary.pid -or $frame.processCreatedUtcFileTime100ns -ne $primary.created){throw 'Capture receipt qualification failed'} if($before.primary.created -ne $after.primary.created -or $before.popup.hwnd -ne $after.popup.hwnd -or $before.popup.created -ne $after.popup.created){throw 'Window expired during capture'} } }catch{$caseError=$_.Exception.Message} finally{ if($null -ne $rootProcess){ if(!$rootProcess.HasExited){[IO.File]::WriteAllText(($dir+'/stop'),'stop')} if(!$rootProcess.WaitForExit(15000)){$rootProcess.Kill();$exited=$rootProcess.WaitForExit(2000);$caseError='Owned fixture watchdog expired; exit confirmed='+$exited} if($rootProcess.ExitCode -ne 0){$caseError='Fixture exit '+$rootProcess.ExitCode+'; '+$caseError} $rootProcess.Dispose() } } if($null -eq $caseError){ $ended=Get-Content ($dir+'/root-ended.json') -Raw|ConvertFrom-Json if(!$ended.normalExit -or !$ended.primaryDestroyed -or !$ended.sameProcessPopupDestroyed){$caseError='Fixture cleanup failed'} if($cross){$endedChild=Get-Content ($dir+'/child-ended.json') -Raw|ConvertFrom-Json;if(!$endedChild.normalExit -or !$endedChild.popupDestroyed){$caseError='Child cleanup failed'}} } $finished+=[ordered]@{case=$kind;qualified=($null -eq $caseError);error=$caseError} $finished|ConvertTo-Json -Depth 5|Set-Content ($r+'/case-results.json') -Encoding utf8 if($null -ne $caseError){$errorText=$caseError;break} Write-Output ('Qualified off/on acquisition: '+$kind) } [ordered]@{sourceRevision=$revision;fixtureSha256=$fixtureHash;captureSha256=$captureHash;fixtureUnchanged=((Get-FileHash $fixture).Hash -eq $fixtureHash);captureUnchanged=((Get-FileHash $capture).Hash -eq $captureHash);finishedUtc=[DateTime]::UtcNow.ToString('o');casesCompleted=$finished.Count;error=$errorText;imageInterpretation='pending; qualification is not proof popup was included'}|ConvertTo-Json -Depth 5|Set-Content ($r+'/run-result.json') -Encoding utf8 if($null -ne $errorText){throw $errorText} ========== END FILE: Run-Topology.ps1.txt ========== ========== BEGIN FILE: SOURCE-IDENTITY.txt ========== Original bytes: 2252 Original SHA-256: 4F4C33600974C0FEFEFCFFE2D8FCAFBCE28ABA7CA352FC7E3BF689EC57E2C967 BEGIN CONTENT { "sourceRevision": "842d80bbf0504670c67218168f0c219f381c1e0c", "observedTopologyRevision": "1d08588b67dce226abb78134eb95cf6d712efbf3", "sourcesUnchangedSinceObservedRevision": true, "sources": [ { "file": "capture/WindowSnapshot.cpp", "sha256": "0F174E67295D7A4975C9465AAECEAAFA7F208301A4E4B97EF4743BD052FA0B17" }, { "file": "capture/CMakeLists.txt", "sha256": "A0D892E00237AC4D209F590F1E6C0C8368FC4A82BD4AF4647A46A2358BDE6266" }, { "file": "SecondaryTopologyFixture.cs", "sha256": "E1CDABF3D0B4F0383386AA21597C99A692636CB7482D2B0499583A084A9CC735" } ], "runnerChanges": "Only the original six-line launch configuration is replaced by explicit local paths and new-output/whitespace guards. Relative provenance paths remain. Acquisition, assertions, deadlines, ownership and cleanup body unchanged.", "runnerSyntaxValidated": true, "nativeRunOfPackagedRunner": false, "content": "Self-owned solid-color window diagnostics only; no real IME document, screenshot, user title, raw process dump, product source or executable is bundled.", "buildCheck": { "hostOS": "Windows build 26200.0 at source-package build check; not the earlier native observation", "windowsSdk": "10.0.26100.0", "msvc": "19.42.34435.0", "captureArchitecture": "ARM64", "fixtureArchitecture": "AnyCPU", "buildExitCode": 0, "diagnosticExecutablesLaunched": false, "binariesIncluded": false }, "originalRunner": { "path": "out/receipts/IME-WGC-TOPOLOGY-20261031-01/adopted/run-topology.ps1", "sha256": "F363A3BA8A8268577068A8BE313A887FC80B7BEB0FCFA219570135C715F9568E", "comparisonStart": "function Read-Ready(", "preservedBodySha256Utf8": "C5BAB1D98B167C122C17D4014BCC78BC3F0A8F1BD3349AB2DC7916E8B7ECDC8A", "characterExactMatch": true }, "revision02": { "changes": "Explanatory text and metadata only; source and runner byte hashes match revision01", "priorBuildCheck": "../IME-OFFICIAL-BINDING-20261031-01/result.json", "newBuild": false, "newNativeTest": false, "identifiersRetained": [ "ShowTheseWindowSnapshot", "showthese-owned-window-snapshot" ] } } ========== END FILE: SOURCE-IDENTITY.txt ========== ========== BEGIN FILE: WGC-REPRO.txt ========== Original bytes: 7170 Original SHA-256: D8FBDA90C53FD896D473B72FCFDEBAC0466D74C0C7948175A3AC9CE19E65D291 BEGIN CONTENT Color-only WGC secondary-window reproduction, supplied as supporting material. No user document, real IME image, product implementation or executable is included. Topology: primary: self-created 640 x 400 window alternating Navy/DarkBlue on a timer configured to 200 ms to produce frame updates popup: self-created red 180 x 90 WS_POPUP at primary offset (120, 100) owner chain: popup -> [two hidden owner windows, when used] -> primary cases: same process/UI thread or separate process/UI thread; direct owner or two hidden owners popup stays inside the primary; no visible intervening window is accepted The documented secondary-window test concerns WS_POPUP or WS_EX_TOOLWINDOW style, intersection with the primary, and eligibility of intervening windows. The API requirements list Windows 11 24H2/build 26100. The page also displays a generic prerelease-information notice; this does not by itself establish that this property is preview-only. Its description does not specify a process or hidden-owner rule. Our before/after records check exact identities, styles, intersection, owner chains, visibility, cloak/affinity and no visible intervening window. The Z-order check skips invisible/iconic/cloaked windows. These finite, non-atomic observations are not a proof of every compositor qualification condition or a complete per-frame desktop reconstruction. The observed contrast is suitable for asking about the documented behavior, not for asserting a Windows specification violation. Main four-case comparison: one off/on pair per configuration, with the following final saved BMP red-pixel counts. These are prior observations, not a new run of this package or an estimate of failure frequency: same/direct: secondary off 0, on 16200 same/hidden: secondary off 0, on 16200 cross/direct: secondary off 0, on 16200 cross/hidden: secondary off 0, on 0 Every primary capture retained the original 5 fresh-frame minimum and initial 5000 ms deadline, with secondary readback and exact topology/visibility/geometry/lifetime checks before and after capture. Each row's pixel result concerns its final saved BMP, not all intermediate frames. An additional cross/hidden same-popup control recorded primary-on/5 fresh frames/red 0, direct-popup-off/1 fresh frame/red 16200, then primary-on/5 fresh frames/red 0. Thus there are additional primary omissions in the same controlled configuration, not only the matrix row. These separate sessions/times are not atomic surface comparisons or independent environmental replications. A later cross/direct off/on repeat returned red 0/16200. These observations do not isolate process from UI-thread effects, prove a minimum hidden-owner depth, establish an internal Windows eligibility rule, or show the real IME has the same cause. Real IME preedit omissions were separately observed in one application path; this four-topology experiment used synthetic color windows and did not establish the same mechanism. Other OS builds, GPUs and architectures have not been compared. The exact OS build/update revision for the prior four-case run was not recorded in that receipt; the build-check host context below must not be substituted for it. SOURCE-IDENTITY.json identifies the exact sources and original observation revision. The runner's repository-specific launch configuration was replaced by explicit local paths and new-output/whitespace guards. Relative provenance paths remain in the identity record; no particular workstation's absolute path is required to run it. Existing diagnostic identifiers ShowTheseWindowSnapshot and showthese-owned-window-snapshot remain in the unchanged sources. The acquisition, qualification, deadlines and cleanup body is unchanged. The original first attempt was rejected before capture because WinForms inserted an unintended owner. The adopted fixture explicitly sets its own owner chain and still requires the exact observed chain; this is not assertion relaxation. It never changes another application's ownership. Build prerequisites: Windows, Visual Studio 2022 C++ tools, C++/WinRT headers from Windows SDK 10.0.26100 or later, CMake 3.25+, and .NET Framework's C# compiler. Choose ARM64 or x64 to match the evaluation host. These unchanged sources were build-checked in the previous package revision for ARM64 with SDK 10.0.26100.0 and MSVC 19.42.34435.0 on a host reporting Windows build 26200.0. That build-check context is not a measurement of the earlier native run. This revision changes explanatory text only: no rebuild or new native experiment was performed, and the portable runner remains unexecuted. If received as Microsoft Q&A text attachments, save WindowSnapshot.cpp.txt as capture/WindowSnapshot.cpp, CMakeLists.txt as capture/CMakeLists.txt, SecondaryTopologyFixture.cs.txt as SecondaryTopologyFixture.cs, and Run-Topology.ps1.txt as Run-Topology.ps1. SOURCE-IDENTITY.txt is informational JSON. These are openly labelled source-text exports. Example commands from an extracted directory with no whitespace in its path: cmake -S .\capture -B .\build\capture -G "Visual Studio 17 2022" -A ARM64 cmake --build .\build\capture --config Release & "$env:WINDIR\Microsoft.NET\Framework64\v4.0.30319\csc.exe" /nologo /target:winexe /optimize+ /platform:anycpu /r:System.Windows.Forms.dll /r:System.Drawing.dll /r:System.Web.Extensions.dll /out:.\build\SecondaryTopologyFixture.exe .\SecondaryTopologyFixture.cs powershell.exe -NoProfile -File .\Run-Topology.ps1 -FixturePath .\build\SecondaryTopologyFixture.exe -CapturePath .\build\capture\Release\WindowSnapshot.exe -OutputDirectory C:\WgcRepro\run-01 For x64, replace the CMake architecture ARM64 with x64. The output directory must not already exist. This diagnostic runner rejects whitespace in input/output paths because its retained argument transport uses strings. This is a runner limitation, not a product requirement. Running the last command displays only the fixture's color windows; it sends no keyboard input. Use an idle interactive desktop to avoid occlusion affecting qualification. It runs the four off/on pairs, records exact fixture identities, and requests child exit before destroying the primary. Existing deadlines, forced-exit failures and no-overwrite behavior remain in force. A failed qualification is not a product/capture pass. Stop on a failure rather than changing deadlines or assertions. The runner reports acquisition qualification only, not pixel inclusion. Inspect the eight saved BMPs for the red rectangle and compare the corresponding JSON before interpreting the observation. No outcome is pre-labelled a Windows defect. Generated logs contain the local fixture executable path, PID, HWND and process-creation identity; failed topology qualification may record limited intervening-window metadata. These local run outputs are not included in this source package and are not an automatically approved upload. No submitted attachment should be inferred from building or running the sources. SDK API reference: https://learn.microsoft.com/en-us/uwp/api/windows.graphics.capture.graphicscapturesession.includesecondarywindows?view=winrt-26100 ========== END FILE: WGC-REPRO.txt ==========