1#define WIN32_LEAN_AND_MEAN
2#define UNICODE
3#define _UNICODE
4#include <windows.h>
5#include "cursor.h"
6
7typedef HCURSOR (WINAPI *cursor_frame_fn)(HCURSOR, DWORD, DWORD, DWORD *, DWORD *);
8static cursor_frame_fn cursor_frame;
9static HANDLE output;
10static HANDLE heap;
11static OVERLAPPED sending;
12
13static int write_bytes(const void *data, DWORD length) {
14 const uint8_t *bytes = data;
15 DWORD written;
16 while (length) {
17 ResetEvent(sending.hEvent);
18 if (!WriteFile(output, bytes, length, &written, &sending)) {
19 if (GetLastError() != ERROR_IO_PENDING) return 0;
20 if (WaitForSingleObject(sending.hEvent, 3000) != WAIT_OBJECT_0) {
21 CancelIo(output); WaitForSingleObject(sending.hEvent, INFINITE); return 0;
22 }
23 }
24 if (!GetOverlappedResult(output, &sending, &written, FALSE)) return 0;
25 if (!written) return 0;
26 bytes += written; length -= written;
27 }
28 return 1;
29}
30
31static int packet(uint32_t type, uint32_t length, const uint32_t *words, uint32_t count) {
32 uint8_t header[12], number[4];
33 uint32_t i;
34 header[0] = SG_MAGIC[0]; header[1] = SG_MAGIC[1]; header[2] = SG_MAGIC[2]; header[3] = SG_MAGIC[3];
35 sg_word(header + 4, type); sg_word(header + 8, length);
36 if (!write_bytes(header, sizeof(header))) return 0;
37 for (i = 0; i < count; ++i) {
38 sg_word(number, words[i]);
39 if (!write_bytes(number, sizeof(number))) return 0;
40 }
41 return 1;
42}
43
44static int capture_cursor(HCURSOR cursor, uint32_t id) {
45 ICONINFO icon = {0};
46 BITMAP bitmap;
47 BITMAPINFO info = {0};
48 HDC dc = NULL;
49 HBITMAP dib = NULL, previous = NULL;
50 uint8_t *bits = NULL, *black = NULL, *frames = NULL;
51 uint32_t width, height, count = 1, step, words[SG_CURSOR_WORDS] = {0};
52 DWORD rate = 6, total = 1;
53 size_t pixels, frame_bytes;
54 int success = -1;
55 if (!GetIconInfo(cursor, &icon)) return -1;
56 if (!GetObjectW(icon.hbmColor ? icon.hbmColor : icon.hbmMask, sizeof(bitmap), &bitmap)) goto done;
57 width = (uint32_t)bitmap.bmWidth;
58 height = (uint32_t)(icon.hbmColor ? bitmap.bmHeight : bitmap.bmHeight / 2);
59 if (!width || !height || width > SG_MAX_CURSOR_SIZE || height > SG_MAX_CURSOR_SIZE || icon.xHotspot >= width || icon.yHotspot >= height) goto done;
60 if (cursor_frame && cursor_frame(cursor, 0, 0, &rate, &total) && total > 1 && total <= SG_MAX_CURSOR_FRAMES) count = total;
61 pixels = (size_t)width * height;
62 frame_bytes = 4 + pixels * 5;
63 frames = HeapAlloc(heap, 0, frame_bytes * count);
64 black = HeapAlloc(heap, 0, pixels * 4);
65 dc = CreateCompatibleDC(NULL);
66 info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); info.bmiHeader.biWidth = (LONG)width;
67 info.bmiHeader.biHeight = -(LONG)height; info.bmiHeader.biPlanes = 1; info.bmiHeader.biBitCount = 32;
68 dib = CreateDIBSection(dc, &info, DIB_RGB_COLORS, (void **)&bits, NULL, 0);
69 if (!frames || !black || !dc || !dib) goto done;
70 previous = SelectObject(dc, dib);
71 for (step = 0; step < count; ++step) {
72 uint8_t *frame = frames + frame_bytes * step;
73 size_t i;
74 HCURSOR image = cursor;
75 rate = 6;
76 if (count > 1) {
77 image = cursor_frame(cursor, 0, step, &rate, &total);
78 if (!image || total != count || !rate || rate > 3600) { image = cursor; count = 1; rate = 6; }
79 }
80 sg_word(frame, (rate * 1000u + 30u) / 60u);
81 for (i = 0; i < pixels * 4; ++i) bits[i] = 0;
82 if (!DrawIconEx(dc, 0, 0, image, (int)width, (int)height, 0, NULL, DI_NORMAL)) goto done;
83 GdiFlush();
84 for (i = 0; i < pixels * 4; ++i) black[i] = bits[i];
85 for (i = 0; i < pixels * 4; ++i) bits[i] = 255;
86 if (!DrawIconEx(dc, 0, 0, image, (int)width, (int)height, 0, NULL, DI_NORMAL)) goto done;
87 GdiFlush();
88 if (!sg_rgba(black, bits, frame + 4, frame + 4 + pixels * 4, pixels)) goto done;
89 }
90 words[SG_CURSOR_ID] = id; words[SG_CURSOR_WIDTH] = width; words[SG_CURSOR_HEIGHT] = height;
91 words[SG_CURSOR_HOT_X] = icon.xHotspot; words[SG_CURSOR_HOT_Y] = icon.yHotspot; words[SG_CURSOR_FRAMES] = count;
92 success = packet(SG_CURSOR, SG_CURSOR_WORDS * 4 + (uint32_t)(frame_bytes * count), words, SG_CURSOR_WORDS) && write_bytes(frames, (DWORD)(frame_bytes * count));
93done:
94 if (previous) SelectObject(dc, previous);
95 if (dib) DeleteObject(dib);
96 if (dc) DeleteDC(dc);
97 if (icon.hbmColor) DeleteObject(icon.hbmColor);
98 if (icon.hbmMask) DeleteObject(icon.hbmMask);
99 if (black) HeapFree(heap, 0, black);
100 if (frames) HeapFree(heap, 0, frames);
101 return success;
102}
103
104static void stream(DWORD session) {
105 HDC screen = GetDC(NULL), dc = CreateCompatibleDC(screen);
106 HBITMAP dib = NULL, previous = NULL;
107 uint8_t *pixels = NULL, *before = NULL;
108 uint32_t width = 0, height = 0, id = 0;
109 HCURSOR last_cursor = NULL;
110 POINT last_point = {-1, -1};
111 DWORD last_flags = ~0u, captured = GetTickCount() - 34;
112 DWORD heartbeat = GetTickCount(), received = 0;
113 OVERLAPPED receiving = {0};
114 uint8_t request[12];
115 int pending = 0;
116 int suspended = 0, initial = 1;
117 receiving.hEvent = CreateEventW(NULL, TRUE, FALSE, NULL);
118 if (!screen || !dc || !receiving.hEvent) goto done;
119 for (;;) {
120 if (session != WTSGetActiveConsoleSessionId()) goto done;
121 CURSORINFO cursor = {sizeof(CURSORINFO), 0, NULL, {0, 0}};
122 HDESK desktop;
123 WCHAR name[32];
124 DWORD length;
125 DWORD amount = 0;
126 if (!pending) {
127 ResetEvent(receiving.hEvent);
128 if (!ReadFile(output, request + received, (DWORD)sizeof(request) - received, &amount, &receiving)) {
129 if (GetLastError() != ERROR_IO_PENDING) goto done;
130 pending = 1;
131 } else if (!GetOverlappedResult(output, &receiving, &amount, FALSE)) goto done;
132 } else if (GetOverlappedResult(output, &receiving, &amount, FALSE)) pending = 0;
133 else if (GetLastError() != ERROR_IO_INCOMPLETE) goto done;
134 if (!pending) {
135 if (!amount) goto done;
136 received += amount;
137 if (received == sizeof(request)) {
138 uint8_t expected[12] = {SG_MAGIC[0], SG_MAGIC[1], SG_MAGIC[2], SG_MAGIC[3], 0,0,0,0, 0,0,0,0};
139 uint32_t i;
140 sg_word(expected + 4, SG_REFRESH);
141 for (i = 0; i < sizeof(request); ++i) if (request[i] != expected[i]) goto done;
142 received = 0; initial = 1; last_cursor = NULL; last_flags = ~0u;
143 }
144 }
145 if ((DWORD)(GetTickCount() - heartbeat) >= 1000) {
146 if (!packet(SG_HEARTBEAT, 0, NULL, 0)) goto done;
147 heartbeat = GetTickCount();
148 }
149 desktop = OpenInputDesktop(0, FALSE, DESKTOP_READOBJECTS);
150 int active = desktop && GetUserObjectInformationW(desktop, UOI_NAME, name, sizeof(name), &length) && name[0] == L'D' && name[1] == L'e' && name[2] == L'f' && name[3] == L'a' && name[4] == L'u' && name[5] == L'l' && name[6] == L't' && !name[7];
151 if (desktop) CloseDesktop(desktop);
152 if (!active || !GetCursorInfo(&cursor)) {
153 if (!suspended && !packet(SG_SUSPEND, 0, NULL, 0)) goto done;
154 suspended = 1; initial = 1; last_cursor = NULL; last_flags = ~0u;
155 Sleep(100); continue;
156 }
157 int changed = cursor.hCursor && cursor.hCursor != last_cursor;
158 if (changed) ++id;
159 if (changed || cursor.flags != last_flags || cursor.ptScreenPos.x != last_point.x || cursor.ptScreenPos.y != last_point.y) {
160 uint32_t words[SG_POINTER_WORDS] = {0};
161 words[SG_POINTER_ID] = id; words[SG_POINTER_X] = (uint32_t)cursor.ptScreenPos.x;
162 words[SG_POINTER_Y] = (uint32_t)cursor.ptScreenPos.y; words[SG_POINTER_VISIBLE] = (cursor.flags & CURSOR_SHOWING) ? 1 : 0;
163 if (!packet(SG_POINTER, sizeof(words), words, SG_POINTER_WORDS)) goto done;
164 last_flags = cursor.flags; last_point = cursor.ptScreenPos;
165 }
166 if (changed) {
167 int result = capture_cursor(cursor.hCursor, id);
168 if (!result) goto done;
169 if (result < 0) {
170 uint32_t words[SG_CURSOR_WORDS] = {0};
171 words[SG_CURSOR_ID] = id;
172 if (!packet(SG_CURSOR, sizeof(words), words, SG_CURSOR_WORDS)) goto done;
173 }
174 last_cursor = cursor.hCursor;
175 }
176 if ((DWORD)(GetTickCount() - captured) >= 33) {
177 uint32_t w = (uint32_t)GetSystemMetrics(SM_CXSCREEN), h = (uint32_t)GetSystemMetrics(SM_CYSCREEN), y;
178 struct sg_rect rect;
179 uint32_t words[SG_SCREEN_WORDS] = {0};
180 if (!w || !h || w > SG_MAX_DIMENSION || h > SG_MAX_DIMENSION || w * h > SG_MAX_PIXELS) goto done;
181 if (w != width || h != height) {
182 BITMAPINFO info = {0};
183 if (previous) { SelectObject(dc, previous); previous = NULL; }
184 if (dib) DeleteObject(dib);
185 if (before) HeapFree(heap, 0, before);
186 before = HeapAlloc(heap, 0, (size_t)w * h * 4);
187 info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); info.bmiHeader.biWidth = (LONG)w;
188 info.bmiHeader.biHeight = -(LONG)h; info.bmiHeader.biPlanes = 1; info.bmiHeader.biBitCount = 32;
189 dib = CreateDIBSection(dc, &info, DIB_RGB_COLORS, (void **)&pixels, NULL, 0);
190 if (!before || !dib) goto done;
191 previous = SelectObject(dc, dib); width = w; height = h; initial = 1;
192 }
193 if (!BitBlt(dc, 0, 0, (int)width, (int)height, screen, 0, 0, SRCCOPY | CAPTUREBLT)) {
194 if (!suspended && !packet(SG_SUSPEND, 0, NULL, 0)) goto done;
195 suspended = 1; initial = 1; Sleep(100); continue;
196 }
197 GdiFlush();
198 if (initial) { rect.x = 0; rect.y = 0; rect.width = width; rect.height = height; }
199 else if (!sg_difference(before, pixels, width, height, &rect)) { captured = GetTickCount(); Sleep(8); continue; }
200 words[SG_SCREEN_WIDTH] = width; words[SG_SCREEN_HEIGHT] = height;
201 words[SG_SCREEN_X] = rect.x; words[SG_SCREEN_Y] = rect.y;
202 words[SG_SCREEN_RECT_WIDTH] = rect.width; words[SG_SCREEN_RECT_HEIGHT] = rect.height;
203 if (!packet(SG_SCREEN, sizeof(words) + rect.width * rect.height * 4, words, SG_SCREEN_WORDS)) goto done;
204 for (y = rect.y; y < rect.y + rect.height; ++y) {
205 size_t offset = ((size_t)y * width + rect.x) * 4, i;
206 if (!write_bytes(pixels + offset, rect.width * 4)) goto done;
207 for (i = 0; i < rect.width * 4; ++i) before[offset + i] = pixels[offset + i];
208 }
209 initial = 0; suspended = 0; captured = GetTickCount();
210 }
211 Sleep(8);
212 }
213done:
214 CancelIo(output);
215 if (pending) WaitForSingleObject(receiving.hEvent, INFINITE);
216 if (receiving.hEvent) CloseHandle(receiving.hEvent);
217 if (previous) SelectObject(dc, previous);
218 if (dib) DeleteObject(dib);
219 if (before) HeapFree(heap, 0, before);
220 if (dc) DeleteDC(dc);
221 if (screen) ReleaseDC(NULL, screen);
222}
223
224void mainCRTStartup(void) {
225 FARPROC symbol;
226 DWORD session;
227 if (!ProcessIdToSessionId(GetCurrentProcessId(), &session)) ExitProcess(1);
228 heap = GetProcessHeap();
229 sending.hEvent = CreateEventW(NULL, TRUE, FALSE, NULL);
230 if (!sending.hEvent) ExitProcess(1);
231 SetProcessDPIAware();
232 symbol = GetProcAddress(GetModuleHandleW(L"user32.dll"), "GetCursorFrameInfo");
233 /* This optional user32 export supplies ANI step timing; static capture uses only documented APIs. */
234 cursor_frame = (cursor_frame_fn)symbol;
235 for (;;) {
236 if (session != WTSGetActiveConsoleSessionId()) { Sleep(1000); continue; }
237 output = CreateFileW(L"\\\\.\\Global\\" SG_CHANNEL, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
238 if (output != INVALID_HANDLE_VALUE) { stream(session); CloseHandle(output); }
239 Sleep(1000);
240 }
241}