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

Side by Side Diff: remoting/host/video_frame_capturer_mac.mm

Issue 10833060: Update for OS X 10.6 SDK. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Updated comments. Created 8 years, 4 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
« no previous file with comments | « remoting/host/event_executor_mac.cc ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "remoting/host/video_frame_capturer.h" 5 #include "remoting/host/video_frame_capturer.h"
6 6
7 #include <ApplicationServices/ApplicationServices.h> 7 #include <ApplicationServices/ApplicationServices.h>
8 #include <Cocoa/Cocoa.h> 8 #include <Cocoa/Cocoa.h>
9 #include <dlfcn.h> 9 #include <dlfcn.h>
10 #include <IOKit/pwr_mgt/IOPMLib.h> 10 #include <IOKit/pwr_mgt/IOPMLib.h>
(...skipping 19 matching lines...) Expand all
30 SkIRect CGRectToSkIRect(const CGRect& rect) { 30 SkIRect CGRectToSkIRect(const CGRect& rect) {
31 SkIRect sk_rect = { 31 SkIRect sk_rect = {
32 SkScalarRound(rect.origin.x), 32 SkScalarRound(rect.origin.x),
33 SkScalarRound(rect.origin.y), 33 SkScalarRound(rect.origin.y),
34 SkScalarRound(rect.origin.x + rect.size.width), 34 SkScalarRound(rect.origin.x + rect.size.width),
35 SkScalarRound(rect.origin.y + rect.size.height) 35 SkScalarRound(rect.origin.y + rect.size.height)
36 }; 36 };
37 return sk_rect; 37 return sk_rect;
38 } 38 }
39 39
40 #if (MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_5)
41 // Possibly remove VideoFrameCapturerMac::CgBlitPreLion as well depending on
42 // performance of VideoFrameCapturerMac::CgBlitPostLion on 10.6.
43 #error No longer need to import CGDisplayCreateImage.
44 #else
45 // Declared here because CGDisplayCreateImage does not exist in the 10.5 SDK,
46 // which we are currently compiling against, and it is required on 10.7 to do
47 // screen capture.
48 typedef CGImageRef (*CGDisplayCreateImageFunc)(CGDirectDisplayID displayID);
49 #endif
50
51 // The amount of time allowed for displays to reconfigure. 40 // The amount of time allowed for displays to reconfigure.
52 const int64 kDisplayReconfigurationTimeoutInSeconds = 10; 41 const int64 kDisplayReconfigurationTimeoutInSeconds = 10;
53 42
54 class scoped_pixel_buffer_object { 43 class scoped_pixel_buffer_object {
55 public: 44 public:
56 scoped_pixel_buffer_object(); 45 scoped_pixel_buffer_object();
57 ~scoped_pixel_buffer_object(); 46 ~scoped_pixel_buffer_object();
58 47
59 bool Init(CGLContextObj cgl_context, int size_in_bytes); 48 bool Init(CGLContextObj cgl_context, int size_in_bytes);
60 void Release(); 49 void Release();
(...skipping 175 matching lines...) Expand 10 before | Expand all | Expand 10 after
236 // Contains an invalid region from the previous capture. 225 // Contains an invalid region from the previous capture.
237 SkRegion last_invalid_region_; 226 SkRegion last_invalid_region_;
238 227
239 // Format of pixels returned in buffer. 228 // Format of pixels returned in buffer.
240 media::VideoFrame::Format pixel_format_; 229 media::VideoFrame::Format pixel_format_;
241 230
242 // Acts as a critical section around our display configuration data 231 // Acts as a critical section around our display configuration data
243 // structures. Specifically cgl_context_ and pixel_buffer_object_. 232 // structures. Specifically cgl_context_ and pixel_buffer_object_.
244 base::WaitableEvent display_configuration_capture_event_; 233 base::WaitableEvent display_configuration_capture_event_;
245 234
246 // Will be non-null on lion.
247 CGDisplayCreateImageFunc display_create_image_func_;
248
249 // Power management assertion to prevent the screen from sleeping. 235 // Power management assertion to prevent the screen from sleeping.
250 IOPMAssertionID power_assertion_id_display_; 236 IOPMAssertionID power_assertion_id_display_;
251 237
252 // Power management assertion to indicate that the user is active. 238 // Power management assertion to indicate that the user is active.
253 IOPMAssertionID power_assertion_id_user_; 239 IOPMAssertionID power_assertion_id_user_;
254 240
255 DISALLOW_COPY_AND_ASSIGN(VideoFrameCapturerMac); 241 DISALLOW_COPY_AND_ASSIGN(VideoFrameCapturerMac);
256 }; 242 };
257 243
258 VideoFrameCapturerMac::VideoFrameCapturerMac() 244 VideoFrameCapturerMac::VideoFrameCapturerMac()
259 : cgl_context_(NULL), 245 : cgl_context_(NULL),
260 current_buffer_(0), 246 current_buffer_(0),
261 last_buffer_(NULL), 247 last_buffer_(NULL),
262 pixel_format_(media::VideoFrame::RGB32), 248 pixel_format_(media::VideoFrame::RGB32),
263 display_configuration_capture_event_(false, true), 249 display_configuration_capture_event_(false, true),
264 display_create_image_func_(NULL),
265 power_assertion_id_display_(kIOPMNullAssertionID), 250 power_assertion_id_display_(kIOPMNullAssertionID),
266 power_assertion_id_user_(kIOPMNullAssertionID) { 251 power_assertion_id_user_(kIOPMNullAssertionID) {
267 } 252 }
268 253
269 VideoFrameCapturerMac::~VideoFrameCapturerMac() { 254 VideoFrameCapturerMac::~VideoFrameCapturerMac() {
270 ReleaseBuffers(); 255 ReleaseBuffers();
271 CGUnregisterScreenRefreshCallback( 256 CGUnregisterScreenRefreshCallback(
272 VideoFrameCapturerMac::ScreenRefreshCallback, this); 257 VideoFrameCapturerMac::ScreenRefreshCallback, this);
273 CGScreenUnregisterMoveCallback( 258 CGScreenUnregisterMoveCallback(
274 VideoFrameCapturerMac::ScreenUpdateMoveCallback, this); 259 VideoFrameCapturerMac::ScreenUpdateMoveCallback, this);
(...skipping 18 matching lines...) Expand all
293 LOG(ERROR) << "CGScreenRegisterMoveCallback " << err; 278 LOG(ERROR) << "CGScreenRegisterMoveCallback " << err;
294 return false; 279 return false;
295 } 280 }
296 err = CGDisplayRegisterReconfigurationCallback( 281 err = CGDisplayRegisterReconfigurationCallback(
297 VideoFrameCapturerMac::DisplaysReconfiguredCallback, this); 282 VideoFrameCapturerMac::DisplaysReconfiguredCallback, this);
298 if (err != kCGErrorSuccess) { 283 if (err != kCGErrorSuccess) {
299 LOG(ERROR) << "CGDisplayRegisterReconfigurationCallback " << err; 284 LOG(ERROR) << "CGDisplayRegisterReconfigurationCallback " << err;
300 return false; 285 return false;
301 } 286 }
302 287
303 if (base::mac::IsOSLionOrLater()) {
304 display_create_image_func_ =
305 reinterpret_cast<CGDisplayCreateImageFunc>(
306 dlsym(RTLD_NEXT, "CGDisplayCreateImage"));
307 if (!display_create_image_func_) {
308 LOG(ERROR) << "Unable to load CGDisplayCreateImage on Lion";
309 return false;
310 }
311 }
312 ScreenConfigurationChanged(); 288 ScreenConfigurationChanged();
313 return true; 289 return true;
314 } 290 }
315 291
316 void VideoFrameCapturerMac::ReleaseBuffers() { 292 void VideoFrameCapturerMac::ReleaseBuffers() {
317 if (cgl_context_) { 293 if (cgl_context_) {
318 pixel_buffer_object_.Release(); 294 pixel_buffer_object_.Release();
319 CGLDestroyContext(cgl_context_); 295 CGLDestroyContext(cgl_context_);
320 cgl_context_ = NULL; 296 cgl_context_ = NULL;
321 } 297 }
322 // The buffers might be in use by the encoder, so don't delete them here. 298 // The buffers might be in use by the encoder, so don't delete them here.
323 // Instead, mark them as "needs update"; next time the buffers are used by 299 // Instead, mark them as "needs update"; next time the buffers are used by
324 // the capturer, they will be recreated if necessary. 300 // the capturer, they will be recreated if necessary.
325 for (int i = 0; i < kNumBuffers; ++i) { 301 for (int i = 0; i < kNumBuffers; ++i) {
326 buffers_[i].set_needs_update(); 302 buffers_[i].set_needs_update();
327 } 303 }
328 } 304 }
329 305
330 void VideoFrameCapturerMac::Start(const CursorShapeChangedCallback& callback) { 306 void VideoFrameCapturerMac::Start(const CursorShapeChangedCallback& callback) {
331 cursor_shape_changed_callback_ = callback; 307 cursor_shape_changed_callback_ = callback;
332 308
333 // Create power management assertions to wake the display and prevent it from 309 // Create power management assertions to wake the display and prevent it from
334 // going to sleep on user idle. 310 // going to sleep on user idle.
335 IOPMAssertionCreate(kIOPMAssertionTypeNoDisplaySleep, 311 // TODO(jamiewalch): Use IOPMAssertionDeclareUserActivity on 10.7.3 and above
336 kIOPMAssertionLevelOn, 312 // instead of the following two assertions.
337 &power_assertion_id_display_); 313 IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep,
338 IOPMAssertionCreate(CFSTR("UserIsActive"), 314 kIOPMAssertionLevelOn,
339 kIOPMAssertionLevelOn, 315 CFSTR("Chrome Remote Desktop connection active"),
340 &power_assertion_id_user_); 316 &power_assertion_id_display_);
317 // This assertion ensures that the display is woken up if it already asleep
318 // (as used by Apple Remote Desktop).
319 IOPMAssertionCreateWithName(CFSTR("UserIsActive"),
320 kIOPMAssertionLevelOn,
321 CFSTR("Chrome Remote Desktop connection active"),
322 &power_assertion_id_user_);
341 } 323 }
342 324
343 void VideoFrameCapturerMac::Stop() { 325 void VideoFrameCapturerMac::Stop() {
344 if (power_assertion_id_display_ != kIOPMNullAssertionID) { 326 if (power_assertion_id_display_ != kIOPMNullAssertionID) {
345 IOPMAssertionRelease(power_assertion_id_display_); 327 IOPMAssertionRelease(power_assertion_id_display_);
346 power_assertion_id_display_ = kIOPMNullAssertionID; 328 power_assertion_id_display_ = kIOPMNullAssertionID;
347 } 329 }
348 if (power_assertion_id_user_ != kIOPMNullAssertionID) { 330 if (power_assertion_id_user_ != kIOPMNullAssertionID) {
349 IOPMAssertionRelease(power_assertion_id_user_); 331 IOPMAssertionRelease(power_assertion_id_user_);
350 power_assertion_id_user_ = kIOPMNullAssertionID; 332 power_assertion_id_user_ = kIOPMNullAssertionID;
(...skipping 15 matching lines...) Expand all
366 348
367 // Critical section shared with DisplaysReconfigured(...). 349 // Critical section shared with DisplaysReconfigured(...).
368 CHECK(display_configuration_capture_event_.TimedWait( 350 CHECK(display_configuration_capture_event_.TimedWait(
369 base::TimeDelta::FromSeconds(kDisplayReconfigurationTimeoutInSeconds))); 351 base::TimeDelta::FromSeconds(kDisplayReconfigurationTimeoutInSeconds)));
370 SkRegion region; 352 SkRegion region;
371 helper_.SwapInvalidRegion(&region); 353 helper_.SwapInvalidRegion(&region);
372 VideoFrameBuffer& current_buffer = buffers_[current_buffer_]; 354 VideoFrameBuffer& current_buffer = buffers_[current_buffer_];
373 current_buffer.Update(); 355 current_buffer.Update();
374 356
375 bool flip = false; // GL capturers need flipping. 357 bool flip = false; // GL capturers need flipping.
376 if (display_create_image_func_ != NULL) { 358 if (base::mac::IsOSLionOrLater()) {
377 // Lion requires us to use their new APIs for doing screen capture. 359 // Lion requires us to use their new APIs for doing screen capture. These
360 // APIS currently crash on 10.6.8 if there is no monitor attached.
378 CgBlitPostLion(current_buffer, region); 361 CgBlitPostLion(current_buffer, region);
379 } else if (cgl_context_) { 362 } else if (cgl_context_) {
380 flip = true; 363 flip = true;
381 if (pixel_buffer_object_.get() != 0) { 364 if (pixel_buffer_object_.get() != 0) {
382 GlBlitFast(current_buffer, region); 365 GlBlitFast(current_buffer, region);
383 } else { 366 } else {
384 // See comment in scoped_pixel_buffer_object::Init about why the slow 367 // See comment in scoped_pixel_buffer_object::Init about why the slow
385 // path is always used on 10.5. 368 // path is always used on 10.5.
386 GlBlitSlow(current_buffer); 369 GlBlitSlow(current_buffer);
387 } 370 }
(...skipping 196 matching lines...) Expand 10 before | Expand all | Expand 10 after
584 const int buffer_height = buffer.size().height(); 567 const int buffer_height = buffer.size().height();
585 const int buffer_width = buffer.size().width(); 568 const int buffer_width = buffer.size().width();
586 569
587 // Clip to the size of our current screen. 570 // Clip to the size of our current screen.
588 SkIRect clip_rect = SkIRect::MakeWH(buffer_width, buffer_height); 571 SkIRect clip_rect = SkIRect::MakeWH(buffer_width, buffer_height);
589 572
590 if (last_buffer_) 573 if (last_buffer_)
591 memcpy(buffer.ptr(), last_buffer_, buffer.bytes_per_row() * buffer_height); 574 memcpy(buffer.ptr(), last_buffer_, buffer.bytes_per_row() * buffer_height);
592 last_buffer_ = buffer.ptr(); 575 last_buffer_ = buffer.ptr();
593 CGDirectDisplayID main_display = CGMainDisplayID(); 576 CGDirectDisplayID main_display = CGMainDisplayID();
577 #pragma clang diagnostic push
578 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
594 uint8* display_base_address = 579 uint8* display_base_address =
595 reinterpret_cast<uint8*>(CGDisplayBaseAddress(main_display)); 580 reinterpret_cast<uint8*>(CGDisplayBaseAddress(main_display));
596 CHECK(display_base_address); 581 CHECK(display_base_address);
597 int src_bytes_per_row = CGDisplayBytesPerRow(main_display); 582 int src_bytes_per_row = CGDisplayBytesPerRow(main_display);
598 int src_bytes_per_pixel = CGDisplayBitsPerPixel(main_display) / 8; 583 int src_bytes_per_pixel = CGDisplayBitsPerPixel(main_display) / 8;
584 #pragma clang diagnostic pop
599 // TODO(hclam): We can reduce the amount of copying here by subtracting 585 // TODO(hclam): We can reduce the amount of copying here by subtracting
600 // |capturer_helper_|s region from |last_invalid_region_|. 586 // |capturer_helper_|s region from |last_invalid_region_|.
601 // http://crbug.com/92354 587 // http://crbug.com/92354
602 for(SkRegion::Iterator i(region); !i.done(); i.next()) { 588 for(SkRegion::Iterator i(region); !i.done(); i.next()) {
603 SkIRect copy_rect = i.rect(); 589 SkIRect copy_rect = i.rect();
604 if (copy_rect.intersect(clip_rect)) { 590 if (copy_rect.intersect(clip_rect)) {
605 CopyRect(display_base_address, 591 CopyRect(display_base_address,
606 src_bytes_per_row, 592 src_bytes_per_row,
607 buffer.ptr(), 593 buffer.ptr(),
608 buffer.bytes_per_row(), 594 buffer.bytes_per_row(),
609 src_bytes_per_pixel, 595 src_bytes_per_pixel,
610 copy_rect); 596 copy_rect);
611 } 597 }
612 } 598 }
613 } 599 }
614 600
615 void VideoFrameCapturerMac::CgBlitPostLion(const VideoFrameBuffer& buffer, 601 void VideoFrameCapturerMac::CgBlitPostLion(const VideoFrameBuffer& buffer,
616 const SkRegion& region) { 602 const SkRegion& region) {
617 const int buffer_height = buffer.size().height(); 603 const int buffer_height = buffer.size().height();
618 const int buffer_width = buffer.size().width(); 604 const int buffer_width = buffer.size().width();
619 605
620 // Clip to the size of our current screen. 606 // Clip to the size of our current screen.
621 SkIRect clip_rect = SkIRect::MakeWH(buffer_width, buffer_height); 607 SkIRect clip_rect = SkIRect::MakeWH(buffer_width, buffer_height);
622 608
623 if (last_buffer_) 609 if (last_buffer_)
624 memcpy(buffer.ptr(), last_buffer_, buffer.bytes_per_row() * buffer_height); 610 memcpy(buffer.ptr(), last_buffer_, buffer.bytes_per_row() * buffer_height);
625 last_buffer_ = buffer.ptr(); 611 last_buffer_ = buffer.ptr();
626 CGDirectDisplayID display = CGMainDisplayID(); 612 CGDirectDisplayID display = CGMainDisplayID();
627 base::mac::ScopedCFTypeRef<CGImageRef> image( 613 base::mac::ScopedCFTypeRef<CGImageRef> image(
628 display_create_image_func_(display)); 614 CGDisplayCreateImage(display));
629 if (image.get() == NULL) 615 if (image.get() == NULL)
630 return; 616 return;
631 CGDataProviderRef provider = CGImageGetDataProvider(image); 617 CGDataProviderRef provider = CGImageGetDataProvider(image);
632 base::mac::ScopedCFTypeRef<CFDataRef> data(CGDataProviderCopyData(provider)); 618 base::mac::ScopedCFTypeRef<CFDataRef> data(CGDataProviderCopyData(provider));
633 if (data.get() == NULL) 619 if (data.get() == NULL)
634 return; 620 return;
635 const uint8* display_base_address = CFDataGetBytePtr(data); 621 const uint8* display_base_address = CFDataGetBytePtr(data);
636 int src_bytes_per_row = CGImageGetBytesPerRow(image); 622 int src_bytes_per_row = CGImageGetBytesPerRow(image);
637 int src_bytes_per_pixel = CGImageGetBitsPerPixel(image) / 8; 623 int src_bytes_per_pixel = CGImageGetBitsPerPixel(image) / 8;
638 // TODO(hclam): We can reduce the amount of copying here by subtracting 624 // TODO(hclam): We can reduce the amount of copying here by subtracting
(...skipping 19 matching lines...) Expand all
658 void VideoFrameCapturerMac::ScreenConfigurationChanged() { 644 void VideoFrameCapturerMac::ScreenConfigurationChanged() {
659 ReleaseBuffers(); 645 ReleaseBuffers();
660 helper_.ClearInvalidRegion(); 646 helper_.ClearInvalidRegion();
661 last_buffer_ = NULL; 647 last_buffer_ = NULL;
662 648
663 CGDirectDisplayID mainDevice = CGMainDisplayID(); 649 CGDirectDisplayID mainDevice = CGMainDisplayID();
664 int width = CGDisplayPixelsWide(mainDevice); 650 int width = CGDisplayPixelsWide(mainDevice);
665 int height = CGDisplayPixelsHigh(mainDevice); 651 int height = CGDisplayPixelsHigh(mainDevice);
666 helper_.InvalidateScreen(SkISize::Make(width, height)); 652 helper_.InvalidateScreen(SkISize::Make(width, height));
667 653
668 if (!CGDisplayUsesOpenGLAcceleration(mainDevice)) { 654 if (base::mac::IsOSLionOrLater()) {
669 VLOG(3) << "OpenGL support not available."; 655 LOG(INFO) << "Using CgBlitPostLion.";
670 return;
671 }
672
673 if (display_create_image_func_ != NULL) {
674 // No need for any OpenGL support on Lion 656 // No need for any OpenGL support on Lion
675 return; 657 return;
676 } 658 }
677 659
660 if (!CGDisplayUsesOpenGLAcceleration(mainDevice)) {
661 LOG(INFO) << "Using CgBlitPreLion.";
662 return;
663 }
664
665 LOG(INFO) << "Using GlBlit";
666
678 CGLPixelFormatAttribute attributes[] = { 667 CGLPixelFormatAttribute attributes[] = {
679 kCGLPFAFullScreen, 668 kCGLPFAFullScreen,
680 kCGLPFADisplayMask, 669 kCGLPFADisplayMask,
681 (CGLPixelFormatAttribute)CGDisplayIDToOpenGLDisplayMask(mainDevice), 670 (CGLPixelFormatAttribute)CGDisplayIDToOpenGLDisplayMask(mainDevice),
682 (CGLPixelFormatAttribute)0 671 (CGLPixelFormatAttribute)0
683 }; 672 };
684 CGLPixelFormatObj pixel_format = NULL; 673 CGLPixelFormatObj pixel_format = NULL;
685 GLint matching_pixel_format_count = 0; 674 GLint matching_pixel_format_count = 0;
686 CGLError err = CGLChoosePixelFormat(attributes, 675 CGLError err = CGLChoosePixelFormat(attributes,
687 &pixel_format, 676 &pixel_format,
688 &matching_pixel_format_count); 677 &matching_pixel_format_count);
689 DCHECK_EQ(err, kCGLNoError); 678 DCHECK_EQ(err, kCGLNoError);
690 err = CGLCreateContext(pixel_format, NULL, &cgl_context_); 679 err = CGLCreateContext(pixel_format, NULL, &cgl_context_);
691 DCHECK_EQ(err, kCGLNoError); 680 DCHECK_EQ(err, kCGLNoError);
692 CGLDestroyPixelFormat(pixel_format); 681 CGLDestroyPixelFormat(pixel_format);
693 CGLSetFullScreen(cgl_context_); 682 CGLSetFullScreenOnDisplay(cgl_context_,
683 CGDisplayIDToOpenGLDisplayMask(mainDevice));
694 CGLSetCurrentContext(cgl_context_); 684 CGLSetCurrentContext(cgl_context_);
695 685
696 size_t buffer_size = width * height * sizeof(uint32_t); 686 size_t buffer_size = width * height * sizeof(uint32_t);
697 pixel_buffer_object_.Init(cgl_context_, buffer_size); 687 pixel_buffer_object_.Init(cgl_context_, buffer_size);
698 } 688 }
699 689
700 void VideoFrameCapturerMac::ScreenRefresh(CGRectCount count, 690 void VideoFrameCapturerMac::ScreenRefresh(CGRectCount count,
701 const CGRect* rect_array) { 691 const CGRect* rect_array) {
702 SkIRect skirect_array[count]; 692 SkIRect skirect_array[count];
703 for (CGRectCount i = 0; i < count; ++i) { 693 for (CGRectCount i = 0; i < count; ++i) {
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after
774 VideoFrameCapturer* VideoFrameCapturer::Create() { 764 VideoFrameCapturer* VideoFrameCapturer::Create() {
775 VideoFrameCapturerMac* capturer = new VideoFrameCapturerMac(); 765 VideoFrameCapturerMac* capturer = new VideoFrameCapturerMac();
776 if (!capturer->Init()) { 766 if (!capturer->Init()) {
777 delete capturer; 767 delete capturer;
778 capturer = NULL; 768 capturer = NULL;
779 } 769 }
780 return capturer; 770 return capturer;
781 } 771 }
782 772
783 } // namespace remoting 773 } // namespace remoting
OLDNEW
« no previous file with comments | « remoting/host/event_executor_mac.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698