| 1 | #define WIN32_LEAN_AND_MEAN |
| 2 | #define COBJMACROS |
| 3 | #define UNICODE |
| 4 | #define _UNICODE |
| 5 | #include <windows.h> |
| 6 | #include <d3d11.h> |
| 7 | #include "capture.h" |
| 8 | |
| 9 | struct size { INT32 width, height; }; |
| 10 | struct object { void **vtable; }; |
| 11 | /* WinRT ABI calls stay here so the Win7 executable imports no newer runtime. */ |
| 12 | #define METHOD(o, slot, signature) ((signature)((struct object *)(o))->vtable[slot]) |
| 13 | typedef HRESULT (WINAPI *query_fn)(void *, const GUID *, void **); |
| 14 | typedef ULONG (WINAPI *release_fn)(void *); |
| 15 | typedef HRESULT (WINAPI *close_fn)(void *); |
| 16 | typedef HRESULT (WINAPI *object_fn)(void *, void **); |
| 17 | typedef HRESULT (WINAPI *size_fn)(void *, struct size *); |
| 18 | typedef HRESULT (WINAPI *window_fn)(void *, HWND, const GUID *, void **); |
| 19 | typedef HRESULT (WINAPI *pool_fn)(void *, void *, int, int, struct size, void **); |
| 20 | typedef HRESULT (WINAPI *recreate_fn)(void *, void *, int, int, struct size); |
| 21 | typedef HRESULT (WINAPI *session_fn)(void *, void *, void **); |
| 22 | typedef HRESULT (WINAPI *bool_fn)(void *, BOOLEAN); |
| 23 | typedef HRESULT (WINAPI *initialize_fn)(int); |
| 24 | typedef void (WINAPI *uninitialize_fn)(void); |
| 25 | typedef HRESULT (WINAPI *string_fn)(PCWSTR, UINT32, void **); |
| 26 | typedef HRESULT (WINAPI *delete_string_fn)(void *); |
| 27 | typedef HRESULT (WINAPI *factory_fn)(void *, const GUID *, void **); |
| 28 | typedef HRESULT (WINAPI *device_fn)(IDXGIDevice *, void **); |
| 29 | typedef HRESULT (WINAPI *attribute_fn)(HWND, DWORD, PVOID, DWORD); |
| 30 | |
| 31 | static const GUID item_iid = {0x79c3f95b,0x31f7,0x4ec2,{0xa4,0x64,0x63,0x2e,0xf5,0xd3,0x07,0x60}}; |
| 32 | static const GUID interop_iid = {0x3628e81b,0x3cac,0x4c60,{0xb7,0xf4,0x23,0xce,0x0e,0x0c,0x33,0x56}}; |
| 33 | static const GUID pool_iid = {0x589b103f,0x6bbc,0x5df5,{0xa9,0x91,0x02,0xe2,0x8b,0x3b,0x66,0xd5}}; |
| 34 | static const GUID cursor_iid = {0x2c39ae40,0x7d2e,0x5044,{0x80,0x4e,0x8b,0x67,0x99,0xd4,0xcf,0x9e}}; |
| 35 | static const GUID close_iid = {0x30d5a829,0x7fa4,0x4026,{0x83,0xbb,0xd7,0x5b,0xae,0x4e,0xa9,0x9e}}; |
| 36 | static const GUID dxgi_iid = {0x54ec77fa,0x1377,0x44e6,{0x8c,0x32,0x88,0xfd,0x5f,0x44,0xc8,0x4c}}; |
| 37 | static const GUID access_iid = {0xa9b3d012,0x3df2,0x4ee3,{0xb8,0xd1,0x86,0x95,0xf4,0x57,0xd3,0xc1}}; |
| 38 | static const GUID texture_iid = {0x6f15aaf2,0xd208,0x4e89,{0x9a,0xb4,0x48,0x95,0x35,0xd3,0x4f,0x9c}}; |
| 39 | |
| 40 | struct shell_paint { |
| 41 | HWND hwnd; |
| 42 | HANDLE thread; |
| 43 | struct shell_paint *next; |
| 44 | HDC dc; |
| 45 | HBITMAP dib, selected; |
| 46 | uint8_t *bits; |
| 47 | size_t pixels; |
| 48 | DWORD started; |
| 49 | }; |
| 50 | |
| 51 | struct window { |
| 52 | HWND hwnd; |
| 53 | uint32_t id, width, height; |
| 54 | struct size pool_size; |
| 55 | void *pool, *session; |
| 56 | ID3D11Texture2D *staging; |
| 57 | uint8_t *before; |
| 58 | struct shell_paint *paint; |
| 59 | int initial, seen, shell; |
| 60 | DWORD waiting; |
| 61 | }; |
| 62 | static struct window windows[SG_MAX_WINDOWS], desktop; |
| 63 | static RECT bounds[SG_MAX_WINDOWS], capture_bounds[SG_MAX_WINDOWS]; |
| 64 | static uint32_t layout[SG_WINDOWS_WORDS + SG_MAX_WINDOWS * SG_WINDOW_WORDS]; |
| 65 | static uint32_t prior[SG_WINDOWS_WORDS + SG_MAX_WINDOWS * SG_WINDOW_WORDS]; |
| 66 | static uint32_t count, next_id; |
| 67 | static size_t allocated, painted; |
| 68 | static struct shell_paint *retired_paints; |
| 69 | static int invalid; |
| 70 | static HANDLE heap; |
| 71 | /* Capture workers outlive session disposal; retain their DLLs until process exit. */ |
| 72 | static HMODULE runtime, d3d, dwm, composition; |
| 73 | static ID3D11Device *device; |
| 74 | static ID3D11DeviceContext *context; |
| 75 | static void *capture_device, *interop, *pool_factory; |
| 76 | static string_fn create_string; |
| 77 | static delete_string_fn delete_string; |
| 78 | static factory_fn get_factory; |
| 79 | static uninitialize_fn uninitialize; |
| 80 | static attribute_fn attribute; |
| 81 | static DWORD retry, desktop_retry; |
| 82 | |
| 83 | static void release(void *object) { |
| 84 | if (object) METHOD(object, 2, release_fn)(object); |
| 85 | } |
| 86 | static void close_object(void *object) { |
| 87 | void *closable = NULL; |
| 88 | if (object && SUCCEEDED(METHOD(object, 0, query_fn)(object, &close_iid, &closable))) { |
| 89 | METHOD(closable, 6, close_fn)(closable); |
| 90 | release(closable); |
| 91 | } |
| 92 | release(object); |
| 93 | } |
| 94 | static void free_paint(struct shell_paint *paint) { |
| 95 | if (paint->thread) CloseHandle(paint->thread); |
| 96 | if (paint->selected) SelectObject(paint->dc, paint->selected); |
| 97 | if (paint->dib) DeleteObject(paint->dib); |
| 98 | if (paint->dc) DeleteDC(paint->dc); |
| 99 | painted -= paint->pixels; |
| 100 | HeapFree(heap, 0, paint); |
| 101 | } |
| 102 | static DWORD WINAPI paint_shell(void *data) { |
| 103 | struct shell_paint *paint = data; |
| 104 | BOOL success = PrintWindow(paint->hwnd, paint->dc, 2); |
| 105 | GdiFlush(); |
| 106 | return success; |
| 107 | } |
| 108 | static void reap_paints(void) { |
| 109 | struct shell_paint **link = &retired_paints; |
| 110 | while (*link) { |
| 111 | struct shell_paint *paint = *link; |
| 112 | if (WaitForSingleObject(paint->thread, 0) == WAIT_OBJECT_0) { |
| 113 | *link = paint->next; free_paint(paint); |
| 114 | } else link = &paint->next; |
| 115 | } |
| 116 | } |
| 117 | static void detach_paint(struct window *window) { |
| 118 | struct shell_paint *paint = window->paint; |
| 119 | if (!paint) return; |
| 120 | window->paint = NULL; |
| 121 | if (!paint->thread || WaitForSingleObject(paint->thread, 0) == WAIT_OBJECT_0) free_paint(paint); |
| 122 | else { paint->next = retired_paints; retired_paints = paint; } |
| 123 | } |
| 124 | |
| 125 | static void drop(struct window *window) { |
| 126 | close_object(window->session); close_object(window->pool); |
| 127 | if (window->staging) ID3D11Texture2D_Release(window->staging); |
| 128 | detach_paint(window); |
| 129 | if (window->before) { allocated -= (size_t)window->width * window->height; HeapFree(heap, 0, window->before); } |
| 130 | window->hwnd = NULL; window->session = window->pool = NULL; |
| 131 | window->staging = NULL; window->before = NULL; |
| 132 | window->width = window->height = 0; |
| 133 | } |
| 134 | static int factory(PCWSTR name, const GUID *iid, void **result) { |
| 135 | void *string = NULL; |
| 136 | UINT32 length = 0; |
| 137 | while (name[length]) ++length; |
| 138 | if (FAILED(create_string(name, length, &string))) return 0; |
| 139 | HRESULT status = get_factory(string, iid, result); |
| 140 | delete_string(string); |
| 141 | HMODULE module; |
| 142 | return SUCCEEDED(status) && GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_PIN, |
| 143 | (LPCWSTR)((struct object *)*result)->vtable[0], &module); |
| 144 | } |
| 145 | |
| 146 | void sg_windows_stop(void) { |
| 147 | reap_paints(); |
| 148 | uint32_t i; |
| 149 | for (i = 0; i < SG_MAX_WINDOWS; ++i) drop(windows + i); |
| 150 | count = 0; |
| 151 | retry = 0; |
| 152 | for (i = 0; i < sizeof(prior) / sizeof(prior[0]); ++i) prior[i] = 0; |
| 153 | } |
| 154 | |
| 155 | void sg_capture_stop(void) { |
| 156 | sg_windows_stop(); |
| 157 | drop(&desktop); |
| 158 | desktop_retry = 0; |
| 159 | release(pool_factory); release(interop); release(capture_device); |
| 160 | if (context) ID3D11DeviceContext_Release(context); |
| 161 | if (device) ID3D11Device_Release(device); |
| 162 | pool_factory = interop = capture_device = NULL; context = NULL; device = NULL; |
| 163 | if (uninitialize) uninitialize(); |
| 164 | uninitialize = NULL; |
| 165 | } |
| 166 | |
| 167 | int sg_capture_start(void) { |
| 168 | initialize_fn initialize; |
| 169 | typedef HRESULT (WINAPI *create_fn)(IDXGIAdapter *, D3D_DRIVER_TYPE, HMODULE, UINT, const D3D_FEATURE_LEVEL *, UINT, UINT, ID3D11Device **, D3D_FEATURE_LEVEL *, ID3D11DeviceContext **); |
| 170 | create_fn create; |
| 171 | device_fn wrap; |
| 172 | IDXGIDevice *dxgi = NULL; |
| 173 | if (device) return 1; |
| 174 | heap = GetProcessHeap(); |
| 175 | if (!runtime) runtime = LoadLibraryW(L"combase.dll"); |
| 176 | if (!d3d) d3d = LoadLibraryW(L"d3d11.dll"); |
| 177 | if (!dwm) dwm = LoadLibraryW(L"dwmapi.dll"); |
| 178 | if (!composition) composition = LoadLibraryW(L"dcomp.dll"); |
| 179 | if (!runtime || !d3d || !dwm || !composition) goto failed; |
| 180 | initialize = (initialize_fn)GetProcAddress(runtime, "RoInitialize"); |
| 181 | create_string = (string_fn)GetProcAddress(runtime, "WindowsCreateString"); |
| 182 | delete_string = (delete_string_fn)GetProcAddress(runtime, "WindowsDeleteString"); |
| 183 | get_factory = (factory_fn)GetProcAddress(runtime, "RoGetActivationFactory"); |
| 184 | create = (create_fn)GetProcAddress(d3d, "D3D11CreateDevice"); |
| 185 | wrap = (device_fn)GetProcAddress(d3d, "CreateDirect3D11DeviceFromDXGIDevice"); |
| 186 | attribute = (attribute_fn)GetProcAddress(dwm, "DwmGetWindowAttribute"); |
| 187 | if (!initialize || !create_string || !delete_string || !get_factory || !create || !wrap || !attribute || FAILED(initialize(1))) goto failed; |
| 188 | uninitialize = (uninitialize_fn)GetProcAddress(runtime, "RoUninitialize"); |
| 189 | if (!factory(L"Windows.Graphics.Capture.GraphicsCaptureItem", &interop_iid, &interop) || |
| 190 | !factory(L"Windows.Graphics.Capture.Direct3D11CaptureFramePool", &pool_iid, &pool_factory)) goto failed; |
| 191 | if (FAILED(create(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, D3D11_CREATE_DEVICE_BGRA_SUPPORT, NULL, 0, D3D11_SDK_VERSION, &device, NULL, &context)) && |
| 192 | FAILED(create(NULL, D3D_DRIVER_TYPE_WARP, NULL, D3D11_CREATE_DEVICE_BGRA_SUPPORT, NULL, 0, D3D11_SDK_VERSION, &device, NULL, &context))) goto failed; |
| 193 | if (FAILED(ID3D11Device_QueryInterface(device, &dxgi_iid, (void **)&dxgi))) goto failed; |
| 194 | HRESULT status = wrap(dxgi, &capture_device); |
| 195 | IDXGIDevice_Release(dxgi); |
| 196 | if (FAILED(status)) goto failed; |
| 197 | return 1; |
| 198 | failed: |
| 199 | sg_capture_stop(); |
| 200 | return 0; |
| 201 | } |
| 202 | |
| 203 | static BOOL CALLBACK enumerate(HWND hwnd, LPARAM unused) { |
| 204 | RECT rect; |
| 205 | DWORD cloaked = 0; |
| 206 | uint32_t i; |
| 207 | (void)unused; |
| 208 | if (!IsWindowVisible(hwnd) || IsIconic(hwnd)) return TRUE; |
| 209 | attribute(hwnd, 14, &cloaked, sizeof(cloaked)); |
| 210 | if (cloaked || !GetWindowRect(hwnd, &rect) || rect.right <= rect.left || rect.bottom <= rect.top) return TRUE; |
| 211 | if (rect.left >= (LONG)layout[SG_WINDOWS_WIDTH] || rect.top >= (LONG)layout[SG_WINDOWS_HEIGHT] || rect.right <= 0 || rect.bottom <= 0) return TRUE; |
| 212 | if (count == SG_MAX_WINDOWS || (GetWindowLongW(hwnd, GWL_EXSTYLE) & WS_EX_LAYERED)) { invalid = 1; return FALSE; } |
| 213 | for (i = 0; i < SG_MAX_WINDOWS; ++i) if (windows[i].hwnd == hwnd) break; |
| 214 | if (i == SG_MAX_WINDOWS) { |
| 215 | for (i = 0; i < SG_MAX_WINDOWS; ++i) if (!windows[i].hwnd) break; |
| 216 | if (i == SG_MAX_WINDOWS) { invalid = 1; return FALSE; } |
| 217 | windows[i].hwnd = hwnd; windows[i].id = ++next_id; windows[i].initial = 1; |
| 218 | } |
| 219 | windows[i].seen = 1; |
| 220 | WCHAR class_name[32] = {0}; |
| 221 | GetClassNameW(hwnd, class_name, 32); |
| 222 | windows[i].shell = hwnd == GetShellWindow() || |
| 223 | !lstrcmpW(class_name, L"Shell_TrayWnd") || !lstrcmpW(class_name, L"Shell_SecondaryTrayWnd") || !lstrcmpW(class_name, L"WorkerW"); |
| 224 | if ((!lstrcmpW(class_name, L"ApplicationFrameWindow") && !GetWindowTextLengthW(hwnd)) || !lstrcmpW(class_name, L"Windows.UI.Core.CoreWindow") || !lstrcmpW(class_name, L"Xaml_WindowedPopupClass") || !lstrcmpW(class_name, L"XamlExplorerHostIslandWindow")) { invalid = 1; return FALSE; } |
| 225 | if (!windows[i].shell && (GetWindowLongW(hwnd, GWL_STYLE) & WS_CAPTION) != WS_CAPTION) { invalid = 1; return FALSE; } |
| 226 | capture_bounds[count] = rect; |
| 227 | if (!windows[i].shell) attribute(hwnd, 9, &rect, sizeof(rect)); |
| 228 | if (windows[i].before && !windows[i].initial && |
| 229 | (windows[i].width != (uint32_t)(rect.right - rect.left) || windows[i].height != (uint32_t)(rect.bottom - rect.top))) { |
| 230 | windows[i].initial = 1; windows[i].waiting = GetTickCount(); |
| 231 | } |
| 232 | bounds[count] = rect; |
| 233 | uint32_t *record = layout + SG_WINDOWS_WORDS + count++ * SG_WINDOW_WORDS; |
| 234 | record[SG_WINDOW_ID] = windows[i].id; |
| 235 | record[SG_WINDOW_X] = (uint32_t)rect.left; record[SG_WINDOW_Y] = (uint32_t)rect.top; |
| 236 | record[SG_WINDOW_WIDTH] = windows[i].before && !windows[i].shell ? windows[i].width : (uint32_t)(rect.right - rect.left); |
| 237 | record[SG_WINDOW_HEIGHT] = windows[i].before && !windows[i].shell ? windows[i].height : (uint32_t)(rect.bottom - rect.top); |
| 238 | for (i = SG_WINDOW_CAPTION_LEFT; i <= SG_WINDOW_CAPTION_BOTTOM; ++i) record[i] = 0; |
| 239 | if ((GetWindowLongW(hwnd, GWL_STYLE) & WS_CAPTION) == WS_CAPTION) { |
| 240 | TITLEBARINFO title = {0}; |
| 241 | title.cbSize = sizeof(title); |
| 242 | if (GetTitleBarInfo(hwnd, &title) && title.rcTitleBar.right > title.rcTitleBar.left && !(title.rgstate[0] & STATE_SYSTEM_INVISIBLE)) { |
| 243 | record[SG_WINDOW_CAPTION_LEFT] = (uint32_t)(title.rcTitleBar.left + GetSystemMetrics(SM_CXSIZE)); |
| 244 | record[SG_WINDOW_CAPTION_TOP] = (uint32_t)title.rcTitleBar.top; |
| 245 | record[SG_WINDOW_CAPTION_RIGHT] = (uint32_t)(title.rcTitleBar.right - GetSystemMetrics(SM_CXSIZE) * 3); |
| 246 | record[SG_WINDOW_CAPTION_BOTTOM] = (uint32_t)title.rcTitleBar.bottom; |
| 247 | LONG left = (LONG)record[SG_WINDOW_CAPTION_LEFT], right = (LONG)record[SG_WINDOW_CAPTION_RIGHT]; |
| 248 | LONG y = (title.rcTitleBar.top + title.rcTitleBar.bottom) / 2; |
| 249 | for (int sample = 0; sample < 3; ++sample) { |
| 250 | LONG x = left + 1 + sample * (right - left - 2) / 2; |
| 251 | DWORD_PTR hit; |
| 252 | if (right <= left || !SendMessageTimeoutW(hwnd, WM_NCHITTEST, 0, MAKELPARAM(x, y), SMTO_ABORTIFHUNG | SMTO_BLOCK, 1, &hit) || hit != HTCAPTION) { |
| 253 | for (i = SG_WINDOW_CAPTION_LEFT; i <= SG_WINDOW_CAPTION_BOTTOM; ++i) record[i] = 0; |
| 254 | break; |
| 255 | } |
| 256 | } |
| 257 | } |
| 258 | } |
| 259 | return TRUE; |
| 260 | } |
| 261 | |
| 262 | static int start_window(struct window *window) { |
| 263 | void *item = NULL, *cursor = NULL; |
| 264 | struct size size; |
| 265 | int success = 0; |
| 266 | HWND target = window->hwnd ? window->hwnd : (HWND)MonitorFromPoint((POINT){0, 0}, MONITOR_DEFAULTTOPRIMARY); |
| 267 | if (FAILED(METHOD(interop, window->hwnd ? 3 : 4, window_fn)(interop, target, &item_iid, &item))) goto done; |
| 268 | if (FAILED(METHOD(item, 7, size_fn)(item, &size)) || size.width <= 0 || size.height <= 0 || size.width > (INT32)SG_MAX_DIMENSION || size.height > (INT32)SG_MAX_DIMENSION) goto done; |
| 269 | if (FAILED(METHOD(pool_factory, 6, pool_fn)(pool_factory, capture_device, DXGI_FORMAT_B8G8R8A8_UNORM, 2, size, &window->pool))) goto done; |
| 270 | if (FAILED(METHOD(window->pool, 10, session_fn)(window->pool, item, &window->session))) goto done; |
| 271 | if (FAILED(METHOD(window->session, 0, query_fn)(window->session, &cursor_iid, &cursor))) goto done; |
| 272 | if (FAILED(METHOD(cursor, 7, bool_fn)(cursor, FALSE)) || FAILED(METHOD(window->session, 6, close_fn)(window->session))) goto done; |
| 273 | window->pool_size = size; |
| 274 | window->waiting = GetTickCount(); |
| 275 | success = 1; |
| 276 | done: |
| 277 | release(cursor); release(item); |
| 278 | return success; |
| 279 | } |
| 280 | |
| 281 | static int latest_frame(struct window *window, void **frame) { |
| 282 | void *next = NULL; |
| 283 | for (int i = 0; i < 2; ++i) { |
| 284 | if (FAILED(METHOD(window->pool, 7, object_fn)(window->pool, &next))) return 0; |
| 285 | if (!next) return 1; |
| 286 | close_object(*frame); |
| 287 | *frame = next; |
| 288 | } |
| 289 | return 1; |
| 290 | } |
| 291 | |
| 292 | int sg_desktop_capture(uint8_t *pixels, uint32_t width, uint32_t height, int refresh) { |
| 293 | reap_paints(); |
| 294 | void *frame = NULL, *surface = NULL, *access = NULL; |
| 295 | ID3D11Texture2D *texture = NULL; |
| 296 | D3D11_TEXTURE2D_DESC desc; |
| 297 | D3D11_MAPPED_SUBRESOURCE mapped; |
| 298 | struct size size; |
| 299 | int result = -1; |
| 300 | if (desktop_retry && (DWORD)(GetTickCount() - desktop_retry) < 1000) return -1; |
| 301 | if (!device && !sg_capture_start()) { desktop_retry = GetTickCount(); return -1; } |
| 302 | if (!desktop.pool && !start_window(&desktop)) goto done; |
| 303 | if (!latest_frame(&desktop, &frame)) goto done; |
| 304 | result = 0; |
| 305 | if (!frame) { |
| 306 | if (refresh && desktop.staging && desktop.width == width && desktop.height == height) goto readback; |
| 307 | if ((DWORD)(GetTickCount() - desktop.waiting) > 500 && (!desktop.staging || desktop.width != width || desktop.height != height)) result = -1; |
| 308 | goto done; |
| 309 | } |
| 310 | if (FAILED(METHOD(frame, 8, size_fn)(frame, &size))) { result = -1; goto done; } |
| 311 | if (size.width != (INT32)width || size.height != (INT32)height) { result = -1; goto done; } |
| 312 | if (desktop.pool_size.width != size.width || desktop.pool_size.height != size.height) { |
| 313 | if (FAILED(METHOD(desktop.pool, 6, recreate_fn)(desktop.pool, capture_device, DXGI_FORMAT_B8G8R8A8_UNORM, 2, size))) { result = -1; goto done; } |
| 314 | desktop.pool_size = size; |
| 315 | goto done; |
| 316 | } |
| 317 | result = -1; |
| 318 | if (FAILED(METHOD(frame, 6, object_fn)(frame, &surface)) || |
| 319 | FAILED(METHOD(surface, 0, query_fn)(surface, &access_iid, &access)) || |
| 320 | FAILED(METHOD(access, 3, query_fn)(access, &texture_iid, (void **)&texture))) goto done; |
| 321 | ID3D11Texture2D_GetDesc(texture, &desc); |
| 322 | if (desc.Width < width || desc.Height < height) goto done; |
| 323 | if (desktop.width != width || desktop.height != height) { |
| 324 | if (desktop.staging) ID3D11Texture2D_Release(desktop.staging); |
| 325 | desktop.staging = NULL; |
| 326 | desc.Width = width; desc.Height = height; |
| 327 | desc.BindFlags = 0; desc.MiscFlags = 0; desc.Usage = D3D11_USAGE_STAGING; desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; |
| 328 | if (FAILED(ID3D11Device_CreateTexture2D(device, &desc, NULL, &desktop.staging))) goto done; |
| 329 | desktop.width = width; desktop.height = height; |
| 330 | } |
| 331 | D3D11_BOX box = {0, 0, 0, width, height, 1}; |
| 332 | ID3D11DeviceContext_CopySubresourceRegion(context, (ID3D11Resource *)desktop.staging, 0, 0, 0, 0, (ID3D11Resource *)texture, 0, &box); |
| 333 | readback: |
| 334 | result = -1; |
| 335 | if (FAILED(ID3D11DeviceContext_Map(context, (ID3D11Resource *)desktop.staging, 0, D3D11_MAP_READ, 0, &mapped))) goto done; |
| 336 | for (uint32_t y = 0; y < height; ++y) { |
| 337 | uint8_t *src = (uint8_t *)mapped.pData + (size_t)y * mapped.RowPitch; |
| 338 | uint8_t *dst = pixels + (size_t)y * width * 4; |
| 339 | for (uint32_t x = 0; x < width * 4; ++x) dst[x] = src[x]; |
| 340 | } |
| 341 | ID3D11DeviceContext_Unmap(context, (ID3D11Resource *)desktop.staging, 0); |
| 342 | result = 1; |
| 343 | done: |
| 344 | if (texture) ID3D11Texture2D_Release(texture); |
| 345 | release(access); release(surface); close_object(frame); |
| 346 | if (result < 0) { drop(&desktop); desktop_retry = GetTickCount(); } |
| 347 | else desktop_retry = 0; |
| 348 | return result; |
| 349 | } |
| 350 | |
| 351 | static int atlas_fits(void) { |
| 352 | uint32_t width = layout[SG_WINDOWS_WIDTH], height = 0, x = 0, row_height = 0, last_id = 0; |
| 353 | for (uint32_t i = 0; i < count; ++i) { |
| 354 | uint32_t w = layout[SG_WINDOWS_WORDS + i * SG_WINDOW_WORDS + SG_WINDOW_WIDTH]; |
| 355 | if (w > width) width = w; |
| 356 | } |
| 357 | for (uint32_t i = 0; i < count; ++i) { |
| 358 | uint32_t *next = NULL; |
| 359 | for (uint32_t n = 0; n < count; ++n) { |
| 360 | uint32_t *record = layout + SG_WINDOWS_WORDS + n * SG_WINDOW_WORDS; |
| 361 | if (record[SG_WINDOW_ID] > last_id && (!next || record[SG_WINDOW_ID] < next[SG_WINDOW_ID])) next = record; |
| 362 | } |
| 363 | uint32_t w = next[SG_WINDOW_WIDTH], h = next[SG_WINDOW_HEIGHT]; |
| 364 | if (!w || !h || w > SG_MAX_DIMENSION || h > SG_MAX_DIMENSION) return 0; |
| 365 | if (x + w > width) { x = 0; height += row_height; row_height = 0; } |
| 366 | x += w; |
| 367 | if (h > row_height) row_height = h; |
| 368 | last_id = next[SG_WINDOW_ID]; |
| 369 | } |
| 370 | height += row_height; |
| 371 | return width <= SG_MAX_DIMENSION && height <= SG_MAX_DIMENSION && (size_t)width * height <= SG_MAX_PIXELS; |
| 372 | } |
| 373 | |
| 374 | int sg_windows_poll(sg_emit emit, int refresh, const uint8_t *pixels, uint32_t width, uint32_t height) { |
| 375 | uint32_t i, n, words[SG_SURFACE_WORDS] = {0}; |
| 376 | if (retry && (DWORD)(GetTickCount() - retry) < 1000 && !refresh) return 1; |
| 377 | retry = 0; |
| 378 | count = 0; invalid = 0; |
| 379 | layout[SG_WINDOWS_WIDTH] = width; |
| 380 | layout[SG_WINDOWS_HEIGHT] = height; |
| 381 | for (i = 0; i < SG_MAX_WINDOWS; ++i) windows[i].seen = 0; |
| 382 | HWND foreground = GetForegroundWindow(); |
| 383 | if (foreground && foreground != GetShellWindow() && foreground != FindWindowW(L"Shell_TrayWnd", NULL) && |
| 384 | (GetWindowLongW(foreground, GWL_STYLE) & WS_CAPTION) != WS_CAPTION) invalid = 1; |
| 385 | if (!invalid) EnumWindows(enumerate, 0); |
| 386 | for (i = 0; i < SG_MAX_WINDOWS; ++i) if (windows[i].hwnd && !windows[i].seen) drop(windows + i); |
| 387 | if (!invalid && !atlas_fits()) invalid = 1; |
| 388 | layout[SG_WINDOWS_COUNT] = invalid ? 0 : count; |
| 389 | n = SG_WINDOWS_WORDS + layout[SG_WINDOWS_COUNT] * SG_WINDOW_WORDS; |
| 390 | int changed = refresh; |
| 391 | for (i = 0; i < n; ++i) if (layout[i] != prior[i]) changed = 1; |
| 392 | if (changed && !emit(SG_WINDOWS, layout, n, NULL, 0)) return 0; |
| 393 | for (i = 0; i < n; ++i) prior[i] = layout[i]; |
| 394 | if (invalid) { sg_windows_stop(); retry = GetTickCount(); return 1; } |
| 395 | for (i = 0; i < SG_MAX_WINDOWS * 2; ++i) { |
| 396 | struct window *window = windows + i % SG_MAX_WINDOWS; |
| 397 | if (window->shell != (i < SG_MAX_WINDOWS)) continue; |
| 398 | void *frame = NULL, *surface = NULL, *access = NULL; |
| 399 | ID3D11Texture2D *texture = NULL; |
| 400 | D3D11_TEXTURE2D_DESC desc; |
| 401 | D3D11_BOX box; |
| 402 | D3D11_MAPPED_SUBRESOURCE mapped; |
| 403 | struct size size; |
| 404 | struct sg_rect rect; |
| 405 | uint32_t y; |
| 406 | int success = 1, mapped_texture = 0; |
| 407 | if (!window->hwnd) continue; |
| 408 | uint32_t *record = NULL; |
| 409 | for (n = 0; n < count; ++n) { |
| 410 | uint32_t *candidate = layout + SG_WINDOWS_WORDS + n * SG_WINDOW_WORDS; |
| 411 | if (candidate[SG_WINDOW_ID] == window->id) { record = candidate; break; } |
| 412 | } |
| 413 | if (!record) continue; |
| 414 | if (window->shell) { |
| 415 | size.width = capture_bounds[n].right - capture_bounds[n].left; |
| 416 | size.height = capture_bounds[n].bottom - capture_bounds[n].top; |
| 417 | mapped.pData = NULL; |
| 418 | } else { |
| 419 | if (!window->pool && !start_window(window)) goto frame_failed; |
| 420 | if (!latest_frame(window, &frame)) goto frame_failed; |
| 421 | if (!frame) { |
| 422 | if (window->initial && (DWORD)(GetTickCount() - window->waiting) > 500) goto frame_failed; |
| 423 | continue; |
| 424 | } |
| 425 | if (FAILED(METHOD(frame, 8, size_fn)(frame, &size))) goto frame_failed; |
| 426 | if (window->pool_size.width != size.width || window->pool_size.height != size.height) { |
| 427 | if (size.width <= 0 || size.height <= 0 || size.width > (INT32)SG_MAX_DIMENSION || size.height > (INT32)SG_MAX_DIMENSION) goto frame_failed; |
| 428 | if (FAILED(METHOD(window->pool, 6, recreate_fn)(window->pool, capture_device, DXGI_FORMAT_B8G8R8A8_UNORM, 2, size))) goto frame_failed; |
| 429 | window->pool_size = size; |
| 430 | window->initial = 1; window->waiting = GetTickCount(); |
| 431 | goto frame_done; |
| 432 | } |
| 433 | if (FAILED(METHOD(frame, 6, object_fn)(frame, &surface)) || |
| 434 | FAILED(METHOD(surface, 0, query_fn)(surface, &access_iid, &access)) || |
| 435 | FAILED(METHOD(access, 3, query_fn)(access, &texture_iid, (void **)&texture))) goto frame_failed; |
| 436 | } |
| 437 | if (size.width <= 0 || size.height <= 0 || size.width > (INT32)SG_MAX_DIMENSION || size.height > (INT32)SG_MAX_DIMENSION) goto frame_failed; |
| 438 | if (texture) { |
| 439 | ID3D11Texture2D_GetDesc(texture, &desc); |
| 440 | if (desc.Width < (UINT)size.width || desc.Height < (UINT)size.height) goto frame_failed; |
| 441 | LONG left = 0, top = 0; |
| 442 | LONG right = bounds[n].right - bounds[n].left, bottom = bounds[n].bottom - bounds[n].top; |
| 443 | if (size.width == capture_bounds[n].right - capture_bounds[n].left && size.height == capture_bounds[n].bottom - capture_bounds[n].top) { |
| 444 | left = bounds[n].left - capture_bounds[n].left; top = bounds[n].top - capture_bounds[n].top; |
| 445 | right += left; bottom += top; |
| 446 | } else if (size.width != right || size.height != bottom) goto frame_failed; |
| 447 | if (left < 0 || top < 0 || right > size.width || bottom > size.height || right <= left || bottom <= top) goto frame_failed; |
| 448 | box = (D3D11_BOX){(UINT)left, (UINT)top, 0, (UINT)right, (UINT)bottom, 1}; |
| 449 | size.width = right - left; size.height = bottom - top; |
| 450 | } |
| 451 | if (record[SG_WINDOW_WIDTH] != (uint32_t)size.width || record[SG_WINDOW_HEIGHT] != (uint32_t)size.height) { |
| 452 | record[SG_WINDOW_WIDTH] = (uint32_t)size.width; record[SG_WINDOW_HEIGHT] = (uint32_t)size.height; |
| 453 | if (!atlas_fits()) goto frame_failed; |
| 454 | if (!emit(SG_WINDOWS, layout, SG_WINDOWS_WORDS + count * SG_WINDOW_WORDS, NULL, 0)) { success = 0; goto frame_done; } |
| 455 | for (n = 0; n < SG_WINDOWS_WORDS + count * SG_WINDOW_WORDS; ++n) prior[n] = layout[n]; |
| 456 | } |
| 457 | if (window->width != (uint32_t)size.width || window->height != (uint32_t)size.height) { |
| 458 | if (window->staging) ID3D11Texture2D_Release(window->staging); |
| 459 | window->staging = NULL; |
| 460 | detach_paint(window); |
| 461 | if (window->before) { allocated -= (size_t)window->width * window->height; HeapFree(heap, 0, window->before); window->before = NULL; } |
| 462 | if (allocated + (size_t)size.width * size.height > SG_MAX_PIXELS) goto frame_failed; |
| 463 | window->width = (uint32_t)size.width; window->height = (uint32_t)size.height; |
| 464 | window->before = HeapAlloc(heap, 0, (size_t)window->width * window->height * 4); |
| 465 | if (!window->before) goto frame_failed; |
| 466 | allocated += (size_t)window->width * window->height; |
| 467 | if (window->shell) { |
| 468 | BITMAPINFO info = {0}; |
| 469 | size_t pixels = (size_t)window->width * window->height; |
| 470 | if (painted + pixels > SG_MAX_PIXELS) goto frame_failed; |
| 471 | window->paint = HeapAlloc(heap, HEAP_ZERO_MEMORY, sizeof(struct shell_paint)); |
| 472 | if (!window->paint) goto frame_failed; |
| 473 | struct shell_paint *paint = window->paint; |
| 474 | paint->pixels = pixels; painted += pixels; paint->hwnd = window->hwnd; |
| 475 | paint->dc = CreateCompatibleDC(NULL); |
| 476 | info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); info.bmiHeader.biWidth = size.width; |
| 477 | info.bmiHeader.biHeight = -size.height; info.bmiHeader.biPlanes = 1; info.bmiHeader.biBitCount = 32; |
| 478 | paint->dib = CreateDIBSection(paint->dc, &info, DIB_RGB_COLORS, (void **)&paint->bits, NULL, 0); |
| 479 | if (!paint->dc || !paint->dib) goto frame_failed; |
| 480 | paint->selected = SelectObject(paint->dc, paint->dib); |
| 481 | paint->started = GetTickCount(); |
| 482 | paint->thread = CreateThread(NULL, 0, paint_shell, paint, 0, NULL); |
| 483 | if (!paint->thread) goto frame_failed; |
| 484 | } else { |
| 485 | desc.Width = window->width; desc.Height = window->height; |
| 486 | desc.BindFlags = 0; desc.MiscFlags = 0; desc.Usage = D3D11_USAGE_STAGING; desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; |
| 487 | if (FAILED(ID3D11Device_CreateTexture2D(device, &desc, NULL, &window->staging))) goto frame_failed; |
| 488 | } |
| 489 | window->initial = 1; |
| 490 | } |
| 491 | if (window->shell) { |
| 492 | if (WaitForSingleObject(window->paint->thread, 0) != WAIT_OBJECT_0) { |
| 493 | if ((DWORD)(GetTickCount() - window->paint->started) >= 500) goto frame_failed; |
| 494 | return 1; |
| 495 | } |
| 496 | DWORD painted_ok; |
| 497 | if (!GetExitCodeThread(window->paint->thread, &painted_ok) || !painted_ok) goto frame_failed; |
| 498 | for (y = 0; y < window->height; ++y) { |
| 499 | LONG screen_y = (LONG)record[SG_WINDOW_Y] + (LONG)y; |
| 500 | if (screen_y < 0 || screen_y >= (LONG)layout[SG_WINDOWS_HEIGHT]) continue; |
| 501 | LONG x = (LONG)record[SG_WINDOW_X], right = x + (LONG)window->width; |
| 502 | if (x < 0) x = 0; |
| 503 | if (right > (LONG)layout[SG_WINDOWS_WIDTH]) right = (LONG)layout[SG_WINDOWS_WIDTH]; |
| 504 | while (x < right) { |
| 505 | LONG stop = right; |
| 506 | for (n = 0; n < count; ++n) { |
| 507 | uint32_t *other = layout + SG_WINDOWS_WORDS + n * SG_WINDOW_WORDS; |
| 508 | if (other[SG_WINDOW_ID] == window->id) break; |
| 509 | LONG left = capture_bounds[n].left - 2, top = capture_bounds[n].top - 2; |
| 510 | LONG edge = capture_bounds[n].right + 2; |
| 511 | if (screen_y < top || screen_y >= capture_bounds[n].bottom + 2) continue; |
| 512 | if (left <= x && edge > x) { x = edge; stop = x; break; } |
| 513 | if (left > x && left < stop) stop = left; |
| 514 | } |
| 515 | if (x >= stop) continue; |
| 516 | uint8_t *dst = window->paint->bits + ((size_t)y * window->width + (x - (LONG)record[SG_WINDOW_X])) * 4; |
| 517 | const uint8_t *src = pixels + ((size_t)screen_y * layout[SG_WINDOWS_WIDTH] + x) * 4; |
| 518 | for (LONG byte = 0; byte < (stop - x) * 4; ++byte) dst[byte] = src[byte]; |
| 519 | x = stop; |
| 520 | } |
| 521 | } |
| 522 | mapped.pData = window->paint->bits; mapped.RowPitch = window->width * 4; |
| 523 | } else { |
| 524 | ID3D11DeviceContext_CopySubresourceRegion(context, (ID3D11Resource *)window->staging, 0, 0, 0, 0, (ID3D11Resource *)texture, 0, &box); |
| 525 | if (FAILED(ID3D11DeviceContext_Map(context, (ID3D11Resource *)window->staging, 0, D3D11_MAP_READ, 0, &mapped))) goto frame_failed; |
| 526 | mapped_texture = 1; |
| 527 | for (y = 8; y + 8 < window->height; ++y) { |
| 528 | const uint8_t *row = (const uint8_t *)mapped.pData + (size_t)y * mapped.RowPitch; |
| 529 | for (uint32_t x = 8; x + 8 < window->width; ++x) if (row[x * 4 + 3] != 255) goto frame_failed; |
| 530 | } |
| 531 | } |
| 532 | rect.x = window->width; rect.y = window->height; rect.width = rect.height = 0; |
| 533 | if (refresh || window->initial) { rect.x = rect.y = 0; rect.width = window->width; rect.height = window->height; } |
| 534 | else for (y = 0; y < window->height; ++y) { |
| 535 | struct sg_rect row; |
| 536 | if (sg_difference(window->before + (size_t)y * window->width * 4, (uint8_t *)mapped.pData + (size_t)y * mapped.RowPitch, window->width, 1, &row)) { |
| 537 | if (row.x < rect.x) rect.x = row.x; |
| 538 | if (row.x + row.width > rect.width) rect.width = row.x + row.width; |
| 539 | if (y < rect.y) rect.y = y; |
| 540 | rect.height = y + 1; |
| 541 | } |
| 542 | } |
| 543 | if (rect.width && rect.height) { |
| 544 | if (!refresh && !window->initial) { rect.width -= rect.x; rect.height -= rect.y; } |
| 545 | words[SG_SURFACE_ID] = window->id; words[SG_SURFACE_WIDTH] = window->width; words[SG_SURFACE_HEIGHT] = window->height; |
| 546 | words[SG_SURFACE_X] = rect.x; words[SG_SURFACE_Y] = rect.y; words[SG_SURFACE_RECT_WIDTH] = rect.width; words[SG_SURFACE_RECT_HEIGHT] = rect.height; |
| 547 | success = emit(SG_SURFACE, words, SG_SURFACE_WORDS, (uint8_t *)mapped.pData + (size_t)rect.y * mapped.RowPitch + rect.x * 4, mapped.RowPitch); |
| 548 | for (y = rect.y; y < rect.y + rect.height; ++y) { |
| 549 | uint32_t x; |
| 550 | uint8_t *src = (uint8_t *)mapped.pData + (size_t)y * mapped.RowPitch + rect.x * 4; |
| 551 | uint8_t *dst = window->before + ((size_t)y * window->width + rect.x) * 4; |
| 552 | for (x = 0; x < rect.width * 4; ++x) dst[x] = src[x]; |
| 553 | } |
| 554 | window->initial = 0; |
| 555 | } |
| 556 | goto frame_done; |
| 557 | frame_failed: |
| 558 | invalid = 1; |
| 559 | frame_done: |
| 560 | if (mapped_texture) ID3D11DeviceContext_Unmap(context, (ID3D11Resource *)window->staging, 0); |
| 561 | if (texture) ID3D11Texture2D_Release(texture); |
| 562 | release(access); release(surface); close_object(frame); |
| 563 | if (!success) return 0; |
| 564 | if (invalid) { drop(window); break; } |
| 565 | } |
| 566 | if (invalid) { |
| 567 | sg_windows_stop(); |
| 568 | retry = GetTickCount(); |
| 569 | uint32_t disabled[SG_WINDOWS_WORDS] = {layout[SG_WINDOWS_WIDTH], layout[SG_WINDOWS_HEIGHT], 0}; |
| 570 | if (!emit(SG_WINDOWS, disabled, SG_WINDOWS_WORDS, NULL, 0)) return 0; |
| 571 | prior[SG_WINDOWS_COUNT] = 0; |
| 572 | } |
| 573 | return 1; |
| 574 | } |