1 /* 2 * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11 #include "webrtc/modules/desktop_capture/screen_capturer.h" 12 13 #include <string.h> 14 #include <set> 15 16 #include <X11/extensions/Xdamage.h> 17 #include <X11/extensions/Xfixes.h> 18 #include <X11/Xlib.h> 19 #include <X11/Xutil.h> 20 21 #include "webrtc/modules/desktop_capture/desktop_capture_options.h" 22 #include "webrtc/modules/desktop_capture/desktop_frame.h" 23 #include "webrtc/modules/desktop_capture/differ.h" 24 #include "webrtc/modules/desktop_capture/mouse_cursor_shape.h" 25 #include "webrtc/modules/desktop_capture/screen_capture_frame_queue.h" 26 #include "webrtc/modules/desktop_capture/screen_capturer_helper.h" 27 #include "webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h" 28 #include "webrtc/system_wrappers/interface/logging.h" 29 #include "webrtc/system_wrappers/interface/scoped_ptr.h" 30 #include "webrtc/system_wrappers/interface/tick_util.h" 31 32 // TODO(sergeyu): Move this to a header where it can be shared. 33 #if defined(NDEBUG) 34 #define DCHECK(condition) (void)(condition) 35 #else // NDEBUG 36 #define DCHECK(condition) if (!(condition)) {abort();} 37 #endif 38 39 namespace webrtc { 40 41 namespace { 42 43 // A class to perform video frame capturing for Linux. 44 class ScreenCapturerLinux : public ScreenCapturer, 45 public SharedXDisplay::XEventHandler { 46 public: 47 ScreenCapturerLinux(); 48 virtual ~ScreenCapturerLinux(); 49 50 // TODO(ajwong): Do we really want this to be synchronous? 51 bool Init(const DesktopCaptureOptions& options); 52 53 // DesktopCapturer interface. 54 virtual void Start(Callback* delegate) OVERRIDE; 55 virtual void Capture(const DesktopRegion& region) OVERRIDE; 56 57 // ScreenCapturer interface. 58 virtual void SetMouseShapeObserver( 59 MouseShapeObserver* mouse_shape_observer) OVERRIDE; 60 virtual bool GetScreenList(ScreenList* screens) OVERRIDE; 61 virtual bool SelectScreen(ScreenId id) OVERRIDE; 62 63 private: 64 Display* display() { return options_.x_display()->display(); } 65 66 // SharedXDisplay::XEventHandler interface. 67 virtual bool HandleXEvent(const XEvent& event) OVERRIDE; 68 69 void InitXDamage(); 70 71 // Capture the cursor image and notify the delegate if it was captured. 72 void CaptureCursor(); 73 74 // Capture screen pixels to the current buffer in the queue. In the DAMAGE 75 // case, the ScreenCapturerHelper already holds the list of invalid rectangles 76 // from HandleXEvent(). In the non-DAMAGE case, this captures the 77 // whole screen, then calculates some invalid rectangles that include any 78 // differences between this and the previous capture. 79 DesktopFrame* CaptureScreen(); 80 81 // Called when the screen configuration is changed. 82 void ScreenConfigurationChanged(); 83 84 // Synchronize the current buffer with |last_buffer_|, by copying pixels from 85 // the area of |last_invalid_rects|. 86 // Note this only works on the assumption that kNumBuffers == 2, as 87 // |last_invalid_rects| holds the differences from the previous buffer and 88 // the one prior to that (which will then be the current buffer). 89 void SynchronizeFrame(); 90 91 void DeinitXlib(); 92 93 DesktopCaptureOptions options_; 94 95 Callback* callback_; 96 MouseShapeObserver* mouse_shape_observer_; 97 98 // X11 graphics context. 99 GC gc_; 100 Window root_window_; 101 102 // XFixes. 103 bool has_xfixes_; 104 int xfixes_event_base_; 105 int xfixes_error_base_; 106 107 // XDamage information. 108 bool use_damage_; 109 Damage damage_handle_; 110 int damage_event_base_; 111 int damage_error_base_; 112 XserverRegion damage_region_; 113 114 // Access to the X Server's pixel buffer. 115 XServerPixelBuffer x_server_pixel_buffer_; 116 117 // A thread-safe list of invalid rectangles, and the size of the most 118 // recently captured screen. 119 ScreenCapturerHelper helper_; 120 121 // Queue of the frames buffers. 122 ScreenCaptureFrameQueue queue_; 123 124 // Invalid region from the previous capture. This is used to synchronize the 125 // current with the last buffer used. 126 DesktopRegion last_invalid_region_; 127 128 // |Differ| for use when polling for changes. 129 scoped_ptr<Differ> differ_; 130 131 DISALLOW_COPY_AND_ASSIGN(ScreenCapturerLinux); 132 }; 133 134 ScreenCapturerLinux::ScreenCapturerLinux() 135 : callback_(NULL), 136 mouse_shape_observer_(NULL), 137 gc_(NULL), 138 root_window_(BadValue), 139 has_xfixes_(false), 140 xfixes_event_base_(-1), 141 xfixes_error_base_(-1), 142 use_damage_(false), 143 damage_handle_(0), 144 damage_event_base_(-1), 145 damage_error_base_(-1), 146 damage_region_(0) { 147 helper_.SetLogGridSize(4); 148 } 149 150 ScreenCapturerLinux::~ScreenCapturerLinux() { 151 options_.x_display()->RemoveEventHandler(ConfigureNotify, this); 152 if (use_damage_) { 153 options_.x_display()->RemoveEventHandler( 154 damage_event_base_ + XDamageNotify, this); 155 } 156 if (has_xfixes_) { 157 options_.x_display()->RemoveEventHandler( 158 xfixes_event_base_ + XFixesCursorNotify, this); 159 } 160 DeinitXlib(); 161 } 162 163 bool ScreenCapturerLinux::Init(const DesktopCaptureOptions& options) { 164 options_ = options; 165 166 root_window_ = RootWindow(display(), DefaultScreen(display())); 167 if (root_window_ == BadValue) { 168 LOG(LS_ERROR) << "Unable to get the root window"; 169 DeinitXlib(); 170 return false; 171 } 172 173 gc_ = XCreateGC(display(), root_window_, 0, NULL); 174 if (gc_ == NULL) { 175 LOG(LS_ERROR) << "Unable to get graphics context"; 176 DeinitXlib(); 177 return false; 178 } 179 180 options_.x_display()->AddEventHandler(ConfigureNotify, this); 181 182 // Check for XFixes extension. This is required for cursor shape 183 // notifications, and for our use of XDamage. 184 if (XFixesQueryExtension(display(), &xfixes_event_base_, 185 &xfixes_error_base_)) { 186 has_xfixes_ = true; 187 } else { 188 LOG(LS_INFO) << "X server does not support XFixes."; 189 } 190 191 // Register for changes to the dimensions of the root window. 192 XSelectInput(display(), root_window_, StructureNotifyMask); 193 194 if (!x_server_pixel_buffer_.Init(display(), DefaultRootWindow(display()))) { 195 LOG(LS_ERROR) << "Failed to initialize pixel buffer."; 196 return false; 197 } 198 199 if (has_xfixes_) { 200 // Register for changes to the cursor shape. 201 XFixesSelectCursorInput(display(), root_window_, 202 XFixesDisplayCursorNotifyMask); 203 options_.x_display()->AddEventHandler( 204 xfixes_event_base_ + XFixesCursorNotify, this); 205 } 206 207 if (options_.use_update_notifications()) { 208 InitXDamage(); 209 } 210 211 return true; 212 } 213 214 void ScreenCapturerLinux::InitXDamage() { 215 // Our use of XDamage requires XFixes. 216 if (!has_xfixes_) { 217 return; 218 } 219 220 // Check for XDamage extension. 221 if (!XDamageQueryExtension(display(), &damage_event_base_, 222 &damage_error_base_)) { 223 LOG(LS_INFO) << "X server does not support XDamage."; 224 return; 225 } 226 227 // TODO(lambroslambrou): Disable DAMAGE in situations where it is known 228 // to fail, such as when Desktop Effects are enabled, with graphics 229 // drivers (nVidia, ATI) that fail to report DAMAGE notifications 230 // properly. 231 232 // Request notifications every time the screen becomes damaged. 233 damage_handle_ = XDamageCreate(display(), root_window_, 234 XDamageReportNonEmpty); 235 if (!damage_handle_) { 236 LOG(LS_ERROR) << "Unable to initialize XDamage."; 237 return; 238 } 239 240 // Create an XFixes server-side region to collate damage into. 241 damage_region_ = XFixesCreateRegion(display(), 0, 0); 242 if (!damage_region_) { 243 XDamageDestroy(display(), damage_handle_); 244 LOG(LS_ERROR) << "Unable to create XFixes region."; 245 return; 246 } 247 248 options_.x_display()->AddEventHandler( 249 damage_event_base_ + XDamageNotify, this); 250 251 use_damage_ = true; 252 LOG(LS_INFO) << "Using XDamage extension."; 253 } 254 255 void ScreenCapturerLinux::Start(Callback* callback) { 256 DCHECK(!callback_); 257 DCHECK(callback); 258 259 callback_ = callback; 260 } 261 262 void ScreenCapturerLinux::Capture(const DesktopRegion& region) { 263 TickTime capture_start_time = TickTime::Now(); 264 265 queue_.MoveToNextFrame(); 266 267 // Process XEvents for XDamage and cursor shape tracking. 268 options_.x_display()->ProcessPendingXEvents(); 269 270 // ProcessPendingXEvents() may call ScreenConfigurationChanged() which 271 // reinitializes |x_server_pixel_buffer_|. Check if the pixel buffer is still 272 // in a good shape. 273 if (!x_server_pixel_buffer_.is_initialized()) { 274 // We failed to initialize pixel buffer. 275 callback_->OnCaptureCompleted(NULL); 276 return; 277 } 278 279 // If the current frame is from an older generation then allocate a new one. 280 // Note that we can't reallocate other buffers at this point, since the caller 281 // may still be reading from them. 282 if (!queue_.current_frame()) { 283 scoped_ptr<DesktopFrame> frame( 284 new BasicDesktopFrame(x_server_pixel_buffer_.window_size())); 285 queue_.ReplaceCurrentFrame(frame.release()); 286 } 287 288 // Refresh the Differ helper used by CaptureFrame(), if needed. 289 DesktopFrame* frame = queue_.current_frame(); 290 if (!use_damage_ && ( 291 !differ_.get() || 292 (differ_->width() != frame->size().width()) || 293 (differ_->height() != frame->size().height()) || 294 (differ_->bytes_per_row() != frame->stride()))) { 295 differ_.reset(new Differ(frame->size().width(), frame->size().height(), 296 DesktopFrame::kBytesPerPixel, 297 frame->stride())); 298 } 299 300 DesktopFrame* result = CaptureScreen(); 301 last_invalid_region_ = result->updated_region(); 302 result->set_capture_time_ms( 303 (TickTime::Now() - capture_start_time).Milliseconds()); 304 callback_->OnCaptureCompleted(result); 305 } 306 307 void ScreenCapturerLinux::SetMouseShapeObserver( 308 MouseShapeObserver* mouse_shape_observer) { 309 DCHECK(!mouse_shape_observer_); 310 DCHECK(mouse_shape_observer); 311 312 mouse_shape_observer_ = mouse_shape_observer; 313 } 314 315 bool ScreenCapturerLinux::GetScreenList(ScreenList* screens) { 316 DCHECK(screens->size() == 0); 317 // TODO(jiayl): implement screen enumeration. 318 Screen default_screen; 319 default_screen.id = 0; 320 screens->push_back(default_screen); 321 return true; 322 } 323 324 bool ScreenCapturerLinux::SelectScreen(ScreenId id) { 325 // TODO(jiayl): implement screen selection. 326 return true; 327 } 328 329 bool ScreenCapturerLinux::HandleXEvent(const XEvent& event) { 330 if (use_damage_ && (event.type == damage_event_base_ + XDamageNotify)) { 331 const XDamageNotifyEvent* damage_event = 332 reinterpret_cast<const XDamageNotifyEvent*>(&event); 333 if (damage_event->damage != damage_handle_) 334 return false; 335 DCHECK(damage_event->level == XDamageReportNonEmpty); 336 return true; 337 } else if (event.type == ConfigureNotify) { 338 ScreenConfigurationChanged(); 339 return true; 340 } else if (has_xfixes_ && 341 event.type == xfixes_event_base_ + XFixesCursorNotify) { 342 const XFixesCursorNotifyEvent* cursor_event = 343 reinterpret_cast<const XFixesCursorNotifyEvent*>(&event); 344 if (cursor_event->window == root_window_ && 345 cursor_event->subtype == XFixesDisplayCursorNotify) { 346 CaptureCursor(); 347 } 348 // Always return false for cursor notifications, because there might be 349 // other listeners for these for the same window. 350 return false; 351 } 352 return false; 353 } 354 355 void ScreenCapturerLinux::CaptureCursor() { 356 DCHECK(has_xfixes_); 357 358 XFixesCursorImage* img = XFixesGetCursorImage(display()); 359 if (!img) { 360 return; 361 } 362 363 scoped_ptr<MouseCursorShape> cursor(new MouseCursorShape()); 364 cursor->size = DesktopSize(img->width, img->height); 365 cursor->hotspot = DesktopVector(img->xhot, img->yhot); 366 367 int total_bytes = cursor->size.width ()* cursor->size.height() * 368 DesktopFrame::kBytesPerPixel; 369 cursor->data.resize(total_bytes); 370 371 // Xlib stores 32-bit data in longs, even if longs are 64-bits long. 372 unsigned long* src = img->pixels; 373 uint32_t* dst = reinterpret_cast<uint32_t*>(&*(cursor->data.begin())); 374 uint32_t* dst_end = dst + (img->width * img->height); 375 while (dst < dst_end) { 376 *dst++ = static_cast<uint32_t>(*src++); 377 } 378 XFree(img); 379 380 if (mouse_shape_observer_) 381 mouse_shape_observer_->OnCursorShapeChanged(cursor.release()); 382 } 383 384 DesktopFrame* ScreenCapturerLinux::CaptureScreen() { 385 DesktopFrame* frame = queue_.current_frame()->Share(); 386 assert(x_server_pixel_buffer_.window_size().equals(frame->size())); 387 388 // Pass the screen size to the helper, so it can clip the invalid region if it 389 // expands that region to a grid. 390 helper_.set_size_most_recent(frame->size()); 391 392 // In the DAMAGE case, ensure the frame is up-to-date with the previous frame 393 // if any. If there isn't a previous frame, that means a screen-resolution 394 // change occurred, and |invalid_rects| will be updated to include the whole 395 // screen. 396 if (use_damage_ && queue_.previous_frame()) 397 SynchronizeFrame(); 398 399 DesktopRegion* updated_region = frame->mutable_updated_region(); 400 401 x_server_pixel_buffer_.Synchronize(); 402 if (use_damage_ && queue_.previous_frame()) { 403 // Atomically fetch and clear the damage region. 404 XDamageSubtract(display(), damage_handle_, None, damage_region_); 405 int rects_num = 0; 406 XRectangle bounds; 407 XRectangle* rects = XFixesFetchRegionAndBounds(display(), damage_region_, 408 &rects_num, &bounds); 409 for (int i = 0; i < rects_num; ++i) { 410 updated_region->AddRect(DesktopRect::MakeXYWH( 411 rects[i].x, rects[i].y, rects[i].width, rects[i].height)); 412 } 413 XFree(rects); 414 helper_.InvalidateRegion(*updated_region); 415 416 // Capture the damaged portions of the desktop. 417 helper_.TakeInvalidRegion(updated_region); 418 419 // Clip the damaged portions to the current screen size, just in case some 420 // spurious XDamage notifications were received for a previous (larger) 421 // screen size. 422 updated_region->IntersectWith( 423 DesktopRect::MakeSize(x_server_pixel_buffer_.window_size())); 424 425 for (DesktopRegion::Iterator it(*updated_region); 426 !it.IsAtEnd(); it.Advance()) { 427 x_server_pixel_buffer_.CaptureRect(it.rect(), frame); 428 } 429 } else { 430 // Doing full-screen polling, or this is the first capture after a 431 // screen-resolution change. In either case, need a full-screen capture. 432 DesktopRect screen_rect = DesktopRect::MakeSize(frame->size()); 433 x_server_pixel_buffer_.CaptureRect(screen_rect, frame); 434 435 if (queue_.previous_frame()) { 436 // Full-screen polling, so calculate the invalid rects here, based on the 437 // changed pixels between current and previous buffers. 438 DCHECK(differ_.get() != NULL); 439 DCHECK(queue_.previous_frame()->data()); 440 differ_->CalcDirtyRegion(queue_.previous_frame()->data(), 441 frame->data(), updated_region); 442 } else { 443 // No previous buffer, so always invalidate the whole screen, whether 444 // or not DAMAGE is being used. DAMAGE doesn't necessarily send a 445 // full-screen notification after a screen-resolution change, so 446 // this is done here. 447 updated_region->SetRect(screen_rect); 448 } 449 } 450 451 return frame; 452 } 453 454 void ScreenCapturerLinux::ScreenConfigurationChanged() { 455 // Make sure the frame buffers will be reallocated. 456 queue_.Reset(); 457 458 helper_.ClearInvalidRegion(); 459 if (!x_server_pixel_buffer_.Init(display(), DefaultRootWindow(display()))) { 460 LOG(LS_ERROR) << "Failed to initialize pixel buffer after screen " 461 "configuration change."; 462 } 463 } 464 465 void ScreenCapturerLinux::SynchronizeFrame() { 466 // Synchronize the current buffer with the previous one since we do not 467 // capture the entire desktop. Note that encoder may be reading from the 468 // previous buffer at this time so thread access complaints are false 469 // positives. 470 471 // TODO(hclam): We can reduce the amount of copying here by subtracting 472 // |capturer_helper_|s region from |last_invalid_region_|. 473 // http://crbug.com/92354 474 DCHECK(queue_.previous_frame()); 475 476 DesktopFrame* current = queue_.current_frame(); 477 DesktopFrame* last = queue_.previous_frame(); 478 DCHECK(current != last); 479 for (DesktopRegion::Iterator it(last_invalid_region_); 480 !it.IsAtEnd(); it.Advance()) { 481 current->CopyPixelsFrom(*last, it.rect().top_left(), it.rect()); 482 } 483 } 484 485 void ScreenCapturerLinux::DeinitXlib() { 486 if (gc_) { 487 XFreeGC(display(), gc_); 488 gc_ = NULL; 489 } 490 491 x_server_pixel_buffer_.Release(); 492 493 if (display()) { 494 if (damage_handle_) { 495 XDamageDestroy(display(), damage_handle_); 496 damage_handle_ = 0; 497 } 498 499 if (damage_region_) { 500 XFixesDestroyRegion(display(), damage_region_); 501 damage_region_ = 0; 502 } 503 } 504 } 505 506 } // namespace 507 508 // static 509 ScreenCapturer* ScreenCapturer::Create(const DesktopCaptureOptions& options) { 510 if (!options.x_display()) 511 return NULL; 512 513 scoped_ptr<ScreenCapturerLinux> capturer(new ScreenCapturerLinux()); 514 if (!capturer->Init(options)) 515 capturer.reset(); 516 return capturer.release(); 517 } 518 519 } // namespace webrtc 520