Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(120)

Side by Side Diff: remoting/host/capturer_win.cc

Issue 10696134: remoting/host: Rename Capturer to VideoFrameCapturer. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 8 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "remoting/host/capturer.h"
6
7 #include <windows.h>
8
9 #include "base/file_path.h"
10 #include "base/logging.h"
11 #include "base/memory/scoped_ptr.h"
12 #include "base/scoped_native_library.h"
13 #include "base/utf_string_conversions.h"
14 #include "base/win/scoped_gdi_object.h"
15 #include "remoting/base/capture_data.h"
16 #include "remoting/host/capturer_helper.h"
17 #include "remoting/host/desktop_win.h"
18 #include "remoting/host/differ.h"
19 #include "remoting/host/scoped_thread_desktop_win.h"
20 #include "remoting/proto/control.pb.h"
21
22 namespace remoting {
23
24 namespace {
25
26 // Constants from dwmapi.h.
27 const UINT DWM_EC_DISABLECOMPOSITION = 0;
28 const UINT DWM_EC_ENABLECOMPOSITION = 1;
29
30 typedef HRESULT (WINAPI * DwmEnableCompositionFunc)(UINT);
31
32 const char kDwmapiLibraryName[] = "dwmapi";
33
34 // Pixel colors used when generating cursor outlines.
35 const uint32 kPixelBgraBlack = 0xff000000;
36 const uint32 kPixelBgraWhite = 0xffffffff;
37 const uint32 kPixelBgraTransparent = 0x00000000;
38
39 // CapturerGdi captures 32bit RGB using GDI.
40 //
41 // CapturerGdi is double-buffered as required by Capturer. See
42 // remoting/host/capturer.h.
43 class CapturerGdi : public Capturer {
44 public:
45 CapturerGdi();
46 virtual ~CapturerGdi();
47
48 // Capturer interface.
49 virtual void Start(const CursorShapeChangedCallback& callback) OVERRIDE;
50 virtual void Stop() OVERRIDE;
51 virtual void ScreenConfigurationChanged() OVERRIDE;
52 virtual media::VideoFrame::Format pixel_format() const OVERRIDE;
53 virtual void ClearInvalidRegion() OVERRIDE;
54 virtual void InvalidateRegion(const SkRegion& invalid_region) OVERRIDE;
55 virtual void InvalidateScreen(const SkISize& size) OVERRIDE;
56 virtual void InvalidateFullScreen() OVERRIDE;
57 virtual void CaptureInvalidRegion(
58 const CaptureCompletedCallback& callback) OVERRIDE;
59 virtual const SkISize& size_most_recent() const OVERRIDE;
60
61 private:
62 struct VideoFrameBuffer {
63 VideoFrameBuffer(void* data, const SkISize& size, int bytes_per_pixel,
64 int bytes_per_row)
65 : data(data), size(size), bytes_per_pixel(bytes_per_pixel),
66 bytes_per_row(bytes_per_row) {
67 }
68 VideoFrameBuffer() {
69 data = 0;
70 size = SkISize::Make(0, 0);
71 bytes_per_pixel = 0;
72 bytes_per_row = 0;
73 }
74 void* data;
75 SkISize size;
76 int bytes_per_pixel;
77 int bytes_per_row;
78 };
79
80 // Make sure that the current buffer has the same size as the screen.
81 void UpdateBufferCapture(const SkISize& size);
82
83 // Allocate memory for a buffer of a given size, freeing any memory previously
84 // allocated for that buffer.
85 void ReallocateBuffer(int buffer_index, const SkISize& size);
86
87 void CalculateInvalidRegion();
88 void CaptureRegion(const SkRegion& region,
89 const CaptureCompletedCallback& callback);
90
91 void ReleaseBuffers();
92 // Generates an image in the current buffer.
93 void CaptureImage();
94
95 // Expand the cursor shape to add a white outline for visibility against
96 // dark backgrounds.
97 void AddCursorOutline(int width, int height, uint32* dst);
98
99 // Capture the current cursor shape.
100 void CaptureCursor();
101
102 // Gets the screen size.
103 SkISize GetScreenSize();
104
105 // A thread-safe list of invalid rectangles, and the size of the most
106 // recently captured screen.
107 CapturerHelper helper_;
108
109 // Callback notified whenever the cursor shape is changed.
110 CursorShapeChangedCallback cursor_shape_changed_callback_;
111
112 // Snapshot of the last cursor bitmap we sent to the client. This is used
113 // to diff against the current cursor so we only send a cursor-change
114 // message when the shape has changed.
115 scoped_array<uint8> last_cursor_;
116 SkISize last_cursor_size_;
117
118 // There are two buffers for the screen images, as required by Capturer.
119 static const int kNumBuffers = 2;
120 VideoFrameBuffer buffers_[kNumBuffers];
121
122 ScopedThreadDesktopWin desktop_;
123
124 // Gdi specific information about screen.
125 HWND desktop_window_;
126 HDC desktop_dc_;
127 HDC memory_dc_;
128 HBITMAP target_bitmap_[kNumBuffers];
129
130 // The screen size attached to the device contexts through which the screen
131 // is captured.
132 SkISize dc_size_;
133
134 // The current buffer with valid data for reading.
135 int current_buffer_;
136
137 // Format of pixels returned in buffer.
138 media::VideoFrame::Format pixel_format_;
139
140 // Class to calculate the difference between two screen bitmaps.
141 scoped_ptr<Differ> differ_;
142
143 base::ScopedNativeLibrary dwmapi_library_;
144 DwmEnableCompositionFunc composition_func_;
145
146 DISALLOW_COPY_AND_ASSIGN(CapturerGdi);
147 };
148
149 // 3780 pixels per meter is equivalent to 96 DPI, typical on desktop monitors.
150 static const int kPixelsPerMeter = 3780;
151 // 32 bit RGBA is 4 bytes per pixel.
152 static const int kBytesPerPixel = 4;
153
154 CapturerGdi::CapturerGdi()
155 : last_cursor_size_(SkISize::Make(0, 0)),
156 desktop_window_(NULL),
157 desktop_dc_(NULL),
158 memory_dc_(NULL),
159 dc_size_(SkISize::Make(0, 0)),
160 current_buffer_(0),
161 pixel_format_(media::VideoFrame::RGB32),
162 composition_func_(NULL) {
163 memset(target_bitmap_, 0, sizeof(target_bitmap_));
164 memset(buffers_, 0, sizeof(buffers_));
165 ScreenConfigurationChanged();
166 }
167
168 CapturerGdi::~CapturerGdi() {
169 ReleaseBuffers();
170 }
171
172 media::VideoFrame::Format CapturerGdi::pixel_format() const {
173 return pixel_format_;
174 }
175
176 void CapturerGdi::ClearInvalidRegion() {
177 helper_.ClearInvalidRegion();
178 }
179
180 void CapturerGdi::InvalidateRegion(const SkRegion& invalid_region) {
181 helper_.InvalidateRegion(invalid_region);
182 }
183
184 void CapturerGdi::InvalidateScreen(const SkISize& size) {
185 helper_.InvalidateScreen(size);
186 }
187
188 void CapturerGdi::InvalidateFullScreen() {
189 helper_.InvalidateFullScreen();
190 }
191
192 void CapturerGdi::CaptureInvalidRegion(
193 const CaptureCompletedCallback& callback) {
194 // Force the system to power-up display hardware, if it has been suspended.
195 SetThreadExecutionState(ES_DISPLAY_REQUIRED);
196
197 // Perform the capture.
198 CalculateInvalidRegion();
199 SkRegion invalid_region;
200 helper_.SwapInvalidRegion(&invalid_region);
201 CaptureRegion(invalid_region, callback);
202
203 // Check for cursor shape update.
204 CaptureCursor();
205 }
206
207 const SkISize& CapturerGdi::size_most_recent() const {
208 return helper_.size_most_recent();
209 }
210
211 void CapturerGdi::ReleaseBuffers() {
212 for (int i = kNumBuffers - 1; i >= 0; i--) {
213 if (target_bitmap_[i]) {
214 DeleteObject(target_bitmap_[i]);
215 target_bitmap_[i] = NULL;
216 }
217 if (buffers_[i].data) {
218 DeleteObject(buffers_[i].data);
219 buffers_[i].data = NULL;
220 }
221 }
222
223 if (desktop_dc_) {
224 ReleaseDC(desktop_window_, desktop_dc_);
225 desktop_window_ = NULL;
226 desktop_dc_ = NULL;
227 }
228
229 if (memory_dc_) {
230 DeleteDC(memory_dc_);
231 memory_dc_ = NULL;
232 }
233 }
234
235 void CapturerGdi::Start(
236 const CursorShapeChangedCallback& callback) {
237 cursor_shape_changed_callback_ = callback;
238
239 // Load dwmapi.dll dynamically since it is not available on XP.
240 if (!dwmapi_library_.is_valid()) {
241 FilePath path(base::GetNativeLibraryName(UTF8ToUTF16(kDwmapiLibraryName)));
242 dwmapi_library_.Reset(base::LoadNativeLibrary(path, NULL));
243 }
244
245 if (dwmapi_library_.is_valid() && composition_func_ == NULL) {
246 composition_func_ = static_cast<DwmEnableCompositionFunc>(
247 dwmapi_library_.GetFunctionPointer("DwmEnableComposition"));
248 }
249
250 // Vote to disable Aero composited desktop effects while capturing. Windows
251 // will restore Aero automatically if the process exits. This has no effect
252 // under Windows 8 or higher. See crbug.com/124018.
253 if (composition_func_ != NULL) {
254 (*composition_func_)(DWM_EC_DISABLECOMPOSITION);
255 }
256 }
257
258 void CapturerGdi::Stop() {
259 // Restore Aero.
260 if (composition_func_ != NULL) {
261 (*composition_func_)(DWM_EC_ENABLECOMPOSITION);
262 }
263 }
264
265 void CapturerGdi::ScreenConfigurationChanged() {
266 // We poll for screen configuration changes, so ignore notifications.
267 }
268
269 void CapturerGdi::UpdateBufferCapture(const SkISize& size) {
270 // Switch to the desktop receiving user input if different from the current
271 // one.
272 scoped_ptr<DesktopWin> input_desktop = DesktopWin::GetInputDesktop();
273 if (input_desktop.get() != NULL && !desktop_.IsSame(*input_desktop)) {
274 // Release GDI resources otherwise SetThreadDesktop will fail.
275 if (desktop_dc_) {
276 ReleaseDC(desktop_window_, desktop_dc_);
277 desktop_window_ = NULL;
278 desktop_dc_ = NULL;
279 }
280
281 if (memory_dc_) {
282 DeleteDC(memory_dc_);
283 memory_dc_ = NULL;
284 }
285
286 ReleaseBuffers();
287
288 // If SetThreadDesktop() fails, the thread is still assigned a desktop.
289 // So we can continue capture screen bits, just from a diffented desktop.
290 desktop_.SetThreadDesktop(input_desktop.Pass());
291 }
292
293 // Make sure the DCs have the correct dimensions.
294 if (size != dc_size_) {
295 // TODO(simonmorris): screen dimensions changing isn't equivalent to needing
296 // a new DC, but it's good enough for now.
297 if (desktop_dc_) {
298 ReleaseDC(desktop_window_, desktop_dc_);
299 desktop_window_ = NULL;
300 desktop_dc_ = NULL;
301 }
302
303 if (memory_dc_) {
304 DeleteDC(memory_dc_);
305 memory_dc_ = NULL;
306 }
307 }
308
309 if (desktop_dc_ == NULL) {
310 DCHECK(desktop_window_ == NULL);
311 DCHECK(memory_dc_ == NULL);
312
313 desktop_window_ = GetDesktopWindow();
314 desktop_dc_ = GetDC(desktop_window_);
315 memory_dc_ = CreateCompatibleDC(desktop_dc_);
316 dc_size_ = size;
317 }
318
319 // Make sure the current bitmap has the correct dimensions.
320 if (buffers_[current_buffer_].data == NULL ||
321 size != buffers_[current_buffer_].size) {
322 ReallocateBuffer(current_buffer_, size);
323 InvalidateFullScreen();
324 }
325 }
326
327 void CapturerGdi::ReallocateBuffer(int buffer_index, const SkISize& size) {
328 // Delete any previously constructed bitmap.
329 if (target_bitmap_[buffer_index]) {
330 DeleteObject(target_bitmap_[buffer_index]);
331 target_bitmap_[buffer_index] = NULL;
332 }
333 if (buffers_[buffer_index].data) {
334 DeleteObject(buffers_[buffer_index].data);
335 buffers_[buffer_index].data = NULL;
336 }
337
338 // Create a bitmap to keep the desktop image.
339 int rounded_width = (size.width() + 3) & (~3);
340
341 // Dimensions of screen.
342 pixel_format_ = media::VideoFrame::RGB32;
343 int bytes_per_row = rounded_width * kBytesPerPixel;
344
345 // Create a device independent bitmap (DIB) that is the same size.
346 BITMAPINFO bmi;
347 memset(&bmi, 0, sizeof(bmi));
348 bmi.bmiHeader.biHeight = -size.height();
349 bmi.bmiHeader.biWidth = rounded_width;
350 bmi.bmiHeader.biPlanes = 1;
351 bmi.bmiHeader.biBitCount = kBytesPerPixel * 8;
352 bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
353 bmi.bmiHeader.biSizeImage = bytes_per_row * size.height();
354 bmi.bmiHeader.biXPelsPerMeter = kPixelsPerMeter;
355 bmi.bmiHeader.biYPelsPerMeter = kPixelsPerMeter;
356
357 // Create memory for the buffers.
358 target_bitmap_[buffer_index] =
359 CreateDIBSection(desktop_dc_, &bmi, DIB_RGB_COLORS,
360 static_cast<void**>(&buffers_[buffer_index].data),
361 NULL, 0);
362 buffers_[buffer_index].size = SkISize::Make(bmi.bmiHeader.biWidth,
363 std::abs(bmi.bmiHeader.biHeight));
364 buffers_[buffer_index].bytes_per_pixel = bmi.bmiHeader.biBitCount / 8;
365 buffers_[buffer_index].bytes_per_row =
366 bmi.bmiHeader.biSizeImage / std::abs(bmi.bmiHeader.biHeight);
367 }
368
369 void CapturerGdi::CalculateInvalidRegion() {
370 CaptureImage();
371
372 const VideoFrameBuffer& current = buffers_[current_buffer_];
373
374 // Find the previous and current screens.
375 int prev_buffer_id = current_buffer_ - 1;
376 if (prev_buffer_id < 0) {
377 prev_buffer_id = kNumBuffers - 1;
378 }
379 const VideoFrameBuffer& prev = buffers_[prev_buffer_id];
380
381 // Maybe the previous and current screens can't be differenced.
382 if ((current.size != prev.size) ||
383 (current.bytes_per_pixel != prev.bytes_per_pixel) ||
384 (current.bytes_per_row != prev.bytes_per_row)) {
385 InvalidateScreen(current.size);
386 return;
387 }
388
389 // Make sure the differencer is set up correctly for these previous and
390 // current screens.
391 if (!differ_.get() ||
392 (differ_->width() != current.size.width()) ||
393 (differ_->height() != current.size.height()) ||
394 (differ_->bytes_per_pixel() != current.bytes_per_pixel) ||
395 (differ_->bytes_per_row() != current.bytes_per_row)) {
396 differ_.reset(new Differ(current.size.width(), current.size.height(),
397 current.bytes_per_pixel, current.bytes_per_row));
398 }
399
400 SkRegion region;
401 differ_->CalcDirtyRegion(prev.data, current.data, &region);
402
403 InvalidateRegion(region);
404 }
405
406 void CapturerGdi::CaptureRegion(const SkRegion& region,
407 const CaptureCompletedCallback& callback) {
408 const VideoFrameBuffer& buffer = buffers_[current_buffer_];
409 current_buffer_ = (current_buffer_ + 1) % kNumBuffers;
410
411 DataPlanes planes;
412 planes.data[0] = static_cast<uint8*>(buffer.data);
413 planes.strides[0] = buffer.bytes_per_row;
414
415 scoped_refptr<CaptureData> data(new CaptureData(planes,
416 buffer.size,
417 pixel_format_));
418 data->mutable_dirty_region() = region;
419
420 helper_.set_size_most_recent(data->size());
421
422 callback.Run(data);
423 }
424
425 void CapturerGdi::CaptureImage() {
426 // Make sure the structures we use to capture the image have the correct size.
427 UpdateBufferCapture(GetScreenSize());
428
429 // Select the target bitmap into the memory dc.
430 SelectObject(memory_dc_, target_bitmap_[current_buffer_]);
431
432 // And then copy the rect from desktop to memory.
433 BitBlt(memory_dc_, 0, 0, buffers_[current_buffer_].size.width(),
434 buffers_[current_buffer_].size.height(), desktop_dc_, 0, 0,
435 SRCCOPY | CAPTUREBLT);
436 }
437
438 void CapturerGdi::AddCursorOutline(int width, int height, uint32* dst) {
439 for (int y = 0; y < height; y++) {
440 for (int x = 0; x < width; x++) {
441 // If this is a transparent pixel (bgr == 0 and alpha = 0), check the
442 // neighbor pixels to see if this should be changed to an outline pixel.
443 if (*dst == kPixelBgraTransparent) {
444 // Change to white pixel if any neighbors (top, bottom, left, right)
445 // are black.
446 if ((y > 0 && dst[-width] == kPixelBgraBlack) ||
447 (y < height - 1 && dst[width] == kPixelBgraBlack) ||
448 (x > 0 && dst[-1] == kPixelBgraBlack) ||
449 (x < width - 1 && dst[1] == kPixelBgraBlack)) {
450 *dst = kPixelBgraWhite;
451 }
452 }
453 dst++;
454 }
455 }
456 }
457
458 void CapturerGdi::CaptureCursor() {
459 CURSORINFO cursor_info;
460 cursor_info.cbSize = sizeof(CURSORINFO);
461 if (!GetCursorInfo(&cursor_info)) {
462 VLOG(3) << "Unable to get cursor info. Error = " << GetLastError();
463 return;
464 }
465
466 // Note that this does not need to be freed.
467 HCURSOR hcursor = cursor_info.hCursor;
468 ICONINFO iinfo;
469 if (!GetIconInfo(hcursor, &iinfo)) {
470 VLOG(3) << "Unable to get cursor icon info. Error = " << GetLastError();
471 return;
472 }
473 int hotspot_x = iinfo.xHotspot;
474 int hotspot_y = iinfo.yHotspot;
475
476 // Get the cursor bitmap.
477 base::win::ScopedBitmap hbitmap;
478 BITMAP bitmap;
479 bool color_bitmap;
480 if (iinfo.hbmColor) {
481 // Color cursor bitmap.
482 color_bitmap = true;
483 hbitmap.Set((HBITMAP)CopyImage(iinfo.hbmColor, IMAGE_BITMAP, 0, 0,
484 LR_CREATEDIBSECTION));
485 if (!hbitmap.Get()) {
486 VLOG(3) << "Unable to copy color cursor image. Error = "
487 << GetLastError();
488 return;
489 }
490
491 // Free the color and mask bitmaps since we only need our copy.
492 DeleteObject(iinfo.hbmColor);
493 DeleteObject(iinfo.hbmMask);
494 } else {
495 // Black and white (xor) cursor.
496 color_bitmap = false;
497 hbitmap.Set(iinfo.hbmMask);
498 }
499
500 if (!GetObject(hbitmap.Get(), sizeof(BITMAP), &bitmap)) {
501 VLOG(3) << "Unable to get cursor bitmap. Error = " << GetLastError();
502 return;
503 }
504
505 int width = bitmap.bmWidth;
506 int height = bitmap.bmHeight;
507 // For non-color cursors, the mask contains both an AND and an XOR mask and
508 // the height includes both. Thus, the width is correct, but we need to
509 // divide by 2 to get the correct mask height.
510 if (!color_bitmap) {
511 height /= 2;
512 }
513 int data_size = height * width * kBytesPerPixel;
514
515 scoped_ptr<protocol::CursorShapeInfo> cursor_proto(
516 new protocol::CursorShapeInfo());
517 cursor_proto->mutable_data()->resize(data_size);
518 uint8* cursor_dst_data = const_cast<uint8*>(reinterpret_cast<const uint8*>(
519 cursor_proto->mutable_data()->data()));
520
521 // Copy/convert cursor bitmap into format needed by chromotocol.
522 int row_bytes = bitmap.bmWidthBytes;
523 if (color_bitmap) {
524 if (bitmap.bmPlanes != 1 || bitmap.bmBitsPixel != 32) {
525 VLOG(3) << "Unsupported color cursor format. Error = " << GetLastError();
526 return;
527 }
528
529 // Cursor bitmap is stored upside-down on Windows. Flip the rows and store
530 // it in the proto.
531 uint8* cursor_src_data = reinterpret_cast<uint8*>(bitmap.bmBits);
532 uint8* src = cursor_src_data + ((height - 1) * row_bytes);
533 uint8* dst = cursor_dst_data;
534 for (int row = 0; row < height; row++) {
535 memcpy(dst, src, row_bytes);
536 dst += width * kBytesPerPixel;
537 src -= row_bytes;
538 }
539 } else {
540 if (bitmap.bmPlanes != 1 || bitmap.bmBitsPixel != 1) {
541 VLOG(3) << "Unsupported cursor mask format. Error = " << GetLastError();
542 return;
543 }
544
545 // x2 because there are 2 masks in the bitmap: AND and XOR.
546 int mask_bytes = height * row_bytes * 2;
547 scoped_array<uint8> mask(new uint8[mask_bytes]);
548 if (!GetBitmapBits(hbitmap.Get(), mask_bytes, mask.get())) {
549 VLOG(3) << "Unable to get cursor mask bits. Error = " << GetLastError();
550 return;
551 }
552 uint8* and_mask = mask.get();
553 uint8* xor_mask = mask.get() + height * row_bytes;
554 uint8* dst = cursor_dst_data;
555 bool add_outline = false;
556 for (int y = 0; y < height; y++) {
557 for (int x = 0; x < width; x++) {
558 int byte = y * row_bytes + x / 8;
559 int bit = 7 - x % 8;
560 int and = and_mask[byte] & (1 << bit);
561 int xor = xor_mask[byte] & (1 << bit);
562
563 // The two cursor masks combine as follows:
564 // AND XOR Windows Result Our result RGB Alpha
565 // 0 0 Black Black 00 ff
566 // 0 1 White White ff ff
567 // 1 0 Screen Transparent 00 00
568 // 1 1 Reverse-screen Black 00 ff
569 // Since we don't support XOR cursors, we replace the "Reverse Screen"
570 // with black. In this case, we also add an outline around the cursor
571 // so that it is visible against a dark background.
572 int rgb = (!and && xor) ? 0xff : 0x00;
573 int alpha = (and && !xor) ? 0x00 : 0xff;
574 *dst++ = rgb;
575 *dst++ = rgb;
576 *dst++ = rgb;
577 *dst++ = alpha;
578 if (and && xor) {
579 add_outline = true;
580 }
581 }
582 }
583 if (add_outline) {
584 AddCursorOutline(width, height,
585 reinterpret_cast<uint32*>(cursor_dst_data));
586 }
587 }
588
589 // Compare the current cursor with the last one we sent to the client. If
590 // they're the same, then don't bother sending the cursor again.
591 if (last_cursor_size_.equals(width, height) &&
592 memcmp(last_cursor_.get(), cursor_dst_data, data_size) == 0) {
593 return;
594 }
595
596 VLOG(3) << "Sending updated cursor: " << width << "x" << height;
597
598 cursor_proto->set_width(width);
599 cursor_proto->set_height(height);
600 cursor_proto->set_hotspot_x(hotspot_x);
601 cursor_proto->set_hotspot_y(hotspot_y);
602
603 // Record the last cursor image that we sent to the client.
604 last_cursor_.reset(new uint8[data_size]);
605 memcpy(last_cursor_.get(), cursor_dst_data, data_size);
606 last_cursor_size_ = SkISize::Make(width, height);
607
608 cursor_shape_changed_callback_.Run(cursor_proto.Pass());
609 }
610
611 SkISize CapturerGdi::GetScreenSize() {
612 return SkISize::Make(GetSystemMetrics(SM_CXSCREEN),
613 GetSystemMetrics(SM_CYSCREEN));
614 }
615
616 } // namespace
617
618 // static
619 Capturer* Capturer::Create() {
620 return new CapturerGdi();
621 }
622
623 } // namespace remoting
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698