1 /*
2 * GDI video grab interface
3 *
4 * This file is part of FFmpeg.
5 *
6 * Copyright (C) 2013 Calvin Walton <calvin.walton@kepstin.ca>
7 * Copyright (C) 2007-2010 Christophe Gisquet <word1.word2@gmail.com>
8 *
9 * FFmpeg is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public License
11 * as published by the Free Software Foundation; either version 2.1
12 * of the License, or (at your option) any later version.
13 *
14 * FFmpeg is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with FFmpeg; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 */
23
24 /**
25 * @file
26 * GDI frame device demuxer
27 * @author Calvin Walton <calvin.walton@kepstin.ca>
28 * @author Christophe Gisquet <word1.word2@gmail.com>
29 */
30
31 #include "config.h"
38 #include <windows.h>
39
40 /**
41 * GDI Device Demuxer context
42 */
44 const AVClass *
class;
/**< Class for private options */
45
46 int frame_size;
/**< Size in bytes of the frame pixel data */
50
51 int draw_mouse;
/**< Draw mouse cursor (private option) */
54 int width;
/**< Width of the grab frame (private option) */
55 int height;
/**< Height of the grab frame (private option) */
56 int offset_x;
/**< Capture x offset (private option) */
57 int offset_y;
/**< Capture y offset (private option) */
58
59 HWND
hwnd;
/**< Handle of the window for the grab */
61 HDC
dest_hdc;
/**< Destination, source-compatible DC */
62 BITMAPINFO
bmi;
/**< Information describing DIB format */
63 HBITMAP
hbmp;
/**< Information on the bitmap captured */
64 void *
buffer;
/**< The buffer containing the bitmap image data */
65 RECT
clip_rect;
/**< The subarea of the screen or window to clip */
66
68
70 };
71
72 #define WIN32_API_ERROR(str) \
73 av_log(s1, AV_LOG_ERROR, str " (error %li)\n", GetLastError())
74
75 #define REGION_WND_BORDER 3
76
77 /**
78 * Callback to handle Windows messages for the region outline window.
79 *
80 * In particular, this handles painting the frame rectangle.
81 *
82 * @param hwnd The region outline window handle.
83 * @param msg The Windows message.
84 * @param wparam First Windows message parameter.
85 * @param lparam Second Windows message parameter.
86 * @return 0 success, !0 failure
87 */
88 static LRESULT CALLBACK
90 {
91 PAINTSTRUCT ps;
92 HDC hdc;
94
95 switch (msg) {
96 case WM_PAINT:
97 hdc = BeginPaint(hwnd, &ps);
98
99 GetClientRect(hwnd, &
rect);
100 FrameRect(hdc, &
rect, GetStockObject(BLACK_BRUSH));
101
103 FrameRect(hdc, &
rect, GetStockObject(WHITE_BRUSH));
104
106 FrameRect(hdc, &
rect, GetStockObject(BLACK_BRUSH));
107
108 EndPaint(hwnd, &ps);
109 break;
110 default:
111 return DefWindowProc(hwnd, msg, wparam, lparam);
112 }
113 return 0;
114 }
115
116 /**
117 * Initialize the region outline window.
118 *
119 * @param s1 The format context.
120 * @param gdigrab gdigrab context.
121 * @return 0 success, !0 failure
122 */
123 static int
125 {
126 HWND hwnd;
129 HRGN region_interior =
NULL;
130
131 DWORD style = WS_POPUP | WS_VISIBLE;
132 DWORD ex = WS_EX_TOOLWINDOW | WS_EX_TOPMOST | WS_EX_TRANSPARENT;
133
136
137 AdjustWindowRectEx(&
rect, style, FALSE, ex);
138
139 // Create a window with no owner; use WC_DIALOG instead of writing a custom
140 // window class
141 hwnd = CreateWindowEx(ex, WC_DIALOG,
NULL, style,
rect.left,
rect.top,
144 if (!hwnd) {
147 }
148
149 // Set the window shape to only include the border area
150 GetClientRect(hwnd, &
rect);
151 region = CreateRectRgn(0, 0,
156 CombineRgn(region, region, region_interior, RGN_DIFF);
157 if (!SetWindowRgn(hwnd, region, FALSE)) {
160 }
161 // The "region" memory is now owned by the window
163 DeleteObject(region_interior);
164
166
167 ShowWindow(hwnd, SW_SHOW);
168
170
171 return 0;
172
174 if (region)
175 DeleteObject(region);
176 if (region_interior)
177 DeleteObject(region_interior);
178 if (hwnd)
179 DestroyWindow(hwnd);
180 return 1;
181 }
182
183 /**
184 * Cleanup/free the region outline window.
185 *
186 * @param s1 The format context.
187 * @param gdigrab gdigrab context.
188 */
189 static void
191 {
195 }
196
197 /**
198 * Process the Windows message queue.
199 *
200 * This is important to prevent Windows from thinking the window has become
201 * unresponsive. As well, things like WM_PAINT (to actually draw the window
202 * contents) are handled from the message queue context.
203 *
204 * @param s1 The format context.
205 * @param gdigrab gdigrab context.
206 */
207 static void
209 {
211 MSG msg;
212
213 while (PeekMessage(&msg, hwnd, 0, 0, PM_REMOVE)) {
214 DispatchMessage(&msg);
215 }
216 }
217
218 /**
219 * Initializes the gdi grab device demuxer (public device demuxer API).
220 *
221 * @param s1 Context from avformat core
222 * @return AVERROR_IO error, 0 success
223 */
224 static int
226 {
228
235
236 const char *filename = s1->
url;
239
240 int bpp;
241 int horzres;
242 int vertres;
243 int desktophorzres;
244 int desktopvertres;
245 RECT virtual_rect;
247 BITMAP bmp;
249
250 if (!strncmp(filename, "title=", 6)) {
251 wchar_t *name_w =
NULL;
253
254 if(utf8towchar(
name, &name_w)) {
257 }
258 if(!name_w) {
261 }
262
267 "Can't find window '%s', aborting.\n",
name);
270 }
273 "Can't show region when grabbing a window.\n");
275 }
276 } else if (!strcmp(filename, "desktop")) {
278 } else if (!strncmp(filename, "hwnd=", 5)) {
279 char *p;
281
282 hwnd = (HWND) strtoull(
name, &p, 0);
283
284 if (p ==
NULL || p ==
name || p[0] !=
'0円')
285 {
287 "Invalid window handle '%s', must be a valid integer.\n",
name);
290 }
291 } else {
293 "Please use \"desktop\", \"title=<windowname>\" or \"hwnd=<hwnd>\" to specify your target.\n");
296 }
297
298 /* This will get the device context for the selected window, or if
299 * none, the primary screen */
305 }
307
310 desktophorzres = GetDeviceCaps(
source_hdc, DESKTOPHORZRES);
311 desktopvertres = GetDeviceCaps(
source_hdc, DESKTOPVERTRES);
312
314 GetClientRect(
hwnd, &virtual_rect);
315 /* window -- get the right height and width for scaling DPI */
316 virtual_rect.left = virtual_rect.left * desktophorzres / horzres;
317 virtual_rect.right = virtual_rect.right * desktophorzres / horzres;
318 virtual_rect.top = virtual_rect.top * desktopvertres / vertres;
319 virtual_rect.bottom = virtual_rect.bottom * desktopvertres / vertres;
320 } else {
321 /* desktop -- get the right height and width for scaling DPI */
322 virtual_rect.left = GetSystemMetrics(SM_XVIRTUALSCREEN);
323 virtual_rect.top = GetSystemMetrics(SM_YVIRTUALSCREEN);
324 virtual_rect.right = (virtual_rect.left + GetSystemMetrics(SM_CXVIRTUALSCREEN)) * desktophorzres / horzres;
325 virtual_rect.bottom = (virtual_rect.top + GetSystemMetrics(SM_CYVIRTUALSCREEN)) * desktopvertres / vertres;
326 }
327
328 /* If no width or height set, use full screen/window area */
334 } else {
339 }
340
341 if (
clip_rect.left < virtual_rect.left ||
344 clip_rect.bottom > virtual_rect.bottom) {
346 "Capture area (%li,%li),(%li,%li) extends outside window area (%li,%li),(%li,%li)",
349 virtual_rect.left, virtual_rect.top,
350 virtual_rect.right, virtual_rect.bottom);
353 }
354
355
358 "Found window %s, capturing %lix%lix%i at (%li,%li)\n",
363 } else {
365 "Capturing whole desktop as %lix%lix%i at (%li,%li)\n",
369 }
370
376 }
377
383 }
384
385 /* Create a DIB and select it into the dest_hdc */
386 bmi.bmiHeader.biSize =
sizeof(BITMAPINFOHEADER);
389 bmi.bmiHeader.biPlanes = 1;
390 bmi.bmiHeader.biBitCount = bpp;
391 bmi.bmiHeader.biCompression = BI_RGB;
392 bmi.bmiHeader.biSizeImage = 0;
393 bmi.bmiHeader.biXPelsPerMeter = 0;
394 bmi.bmiHeader.biYPelsPerMeter = 0;
395 bmi.bmiHeader.biClrUsed = 0;
396 bmi.bmiHeader.biClrImportant = 0;
403 }
404
409 }
410
411 /* Get info from the bitmap */
412 GetObject(
hbmp,
sizeof(BITMAP), &bmp);
413
415 if (!st) {
418 }
420
423 (bpp <= 8 ? (1 << bpp) : 0) * sizeof(RGBQUAD) /* palette size */;
426
434
436
441 }
442 }
443
445
449
450 return 0;
451
462 }
463
464 /**
465 * Paints a mouse pointer in a Win32 image.
466 *
467 * @param s1 Context of the log information
468 * @param s Current grad structure
469 */
471 {
472 CURSORINFO ci = {0};
473
474 #define CURSOR_ERROR(str) \
475 if (!gdigrab->cursor_error_printed) { \
476 WIN32_API_ERROR(str); \
477 gdigrab->cursor_error_printed = 1; \
478 }
479
480 ci.cbSize = sizeof(ci);
481
482 if (GetCursorInfo(&ci)) {
483 HCURSOR icon = CopyCursor(ci.hCursor);
494
495 if (ci.flags != CURSOR_SHOWING)
496 return;
497
498 if (!icon) {
499 /* Use the standard arrow cursor as a fallback.
500 * You'll probably only hit this in Wine, which can't fetch
501 * the current system cursor. */
502 icon = CopyCursor(LoadCursor(
NULL, IDC_ARROW));
503 }
504
505 if (!GetIconInfo(icon, &
info)) {
507 goto icon_error;
508 }
509
512
516
517 //that would keep the correct location of mouse with hidpi screens
518 pos.x =
pos.x * desktophorzres / horzres;
519 pos.y =
pos.y * desktopvertres / vertres;
520 } else {
522 goto icon_error;
523 }
524 } else {
525 //that would keep the correct location of mouse with hidpi screens
526 pos.x = ci.ptScreenPos.x * desktophorzres / horzres -
clip_rect.left -
info.xHotspot;
527 pos.y = ci.ptScreenPos.y * desktopvertres / vertres -
clip_rect.top -
info.yHotspot;
528 }
529
531 ci.ptScreenPos.x, ci.ptScreenPos.y,
pos.x,
pos.y);
532
537 }
538
539 icon_error:
541 DeleteObject(
info.hbmMask);
543 DeleteObject(
info.hbmColor);
544 if (icon)
545 DestroyCursor(icon);
546 } else {
548 }
549 }
550
551 /**
552 * Grabs a frame from gdi (public device demuxer API).
553 *
554 * @param s1 Context from avformat core
555 * @param pkt Packet holding the grabbed frame
556 * @return frame size in bytes
557 */
559 {
561
567
568 BITMAPFILEHEADER bfh;
570
572
573 /* Calculate the time of the next frame */
575
576 /* Run Window message processing queue */
579
580 /* wait based on the frame rate */
581 for (;;) {
584 if (delay <= 0) {
587 }
588 break;
589 }
592 } else {
594 }
595 }
596
600
601 /* Blit screen grab */
609 }
612
613 /* Copy bits to packet data */
614
615 bfh.bfType = 0x4d42; /* "BM" in little-endian */
616 bfh.bfSize = file_size;
617 bfh.bfReserved1 = 0;
618 bfh.bfReserved2 = 0;
620
621 memcpy(
pkt->
data, &bfh,
sizeof(bfh));
622
624
628
630
632
634 }
635
636 /**
637 * Closes gdi frame grabber (public device demuxer API).
638 *
639 * @param s1 Context from avformat core
640 * @return 0 success, !0 failure
641 */
643 {
645
648
650 ReleaseDC(
s->hwnd,
s->source_hdc);
652 DeleteDC(
s->dest_hdc);
654 DeleteObject(
s->hbmp);
656 DeleteDC(
s->source_hdc);
657
658 return 0;
659 }
660
661 #define OFFSET(x) offsetof(struct gdigrab, x)
662 #define DEC AV_OPT_FLAG_DECODING_PARAM
671 };
672
679 };
680
681 /** gdi grabber device demuxer declaration */
687 .priv_data_size =
sizeof(
struct gdigrab),
691 };