screen_capturer_x11.cc (18878B)
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 "modules/desktop_capture/linux/x11/screen_capturer_x11.h" 12 13 #include <X11/X.h> 14 #include <X11/Xlib.h> 15 #include <X11/extensions/Xdamage.h> 16 #include <X11/extensions/Xfixes.h> 17 #include <X11/extensions/Xrandr.h> 18 #include <X11/extensions/damagewire.h> 19 #include <X11/extensions/randr.h> 20 #include <dlfcn.h> 21 22 #include <cstdint> 23 #include <cstring> 24 #include <memory> 25 #include <utility> 26 27 #include "modules/desktop_capture/desktop_capture_options.h" 28 #include "modules/desktop_capture/desktop_capture_types.h" 29 #include "modules/desktop_capture/desktop_capturer.h" 30 #include "modules/desktop_capture/desktop_frame.h" 31 #include "modules/desktop_capture/desktop_geometry.h" 32 #include "modules/desktop_capture/desktop_region.h" 33 #include "modules/desktop_capture/linux/x11/x_server_pixel_buffer.h" 34 #include "modules/desktop_capture/screen_capture_frame_queue.h" 35 #include "modules/desktop_capture/screen_capturer_helper.h" 36 #include "modules/desktop_capture/shared_desktop_frame.h" 37 #include "rtc_base/checks.h" 38 #include "rtc_base/logging.h" 39 #include "rtc_base/sanitizer.h" 40 #include "rtc_base/time_utils.h" 41 #include "rtc_base/trace_event.h" 42 43 namespace webrtc { 44 45 ScreenCapturerX11::ScreenCapturerX11() { 46 helper_.SetLogGridSize(4); 47 } 48 49 ScreenCapturerX11::~ScreenCapturerX11() { 50 options_.x_display()->RemoveEventHandler(ConfigureNotify, this); 51 if (use_damage_) { 52 options_.x_display()->RemoveEventHandler(damage_event_base_ + XDamageNotify, 53 this); 54 } 55 if (use_randr_) { 56 options_.x_display()->RemoveEventHandler( 57 randr_event_base_ + RRScreenChangeNotify, this); 58 } 59 DeinitXlib(); 60 } 61 62 bool ScreenCapturerX11::Init(const DesktopCaptureOptions& options) { 63 TRACE_EVENT0("webrtc", "ScreenCapturerX11::Init"); 64 options_ = options; 65 66 atom_cache_ = std::make_unique<XAtomCache>(display()); 67 68 root_window_ = RootWindow(display(), DefaultScreen(display())); 69 if (root_window_ == BadValue) { 70 RTC_LOG(LS_ERROR) << "Unable to get the root window"; 71 DeinitXlib(); 72 return false; 73 } 74 75 gc_ = XCreateGC(display(), root_window_, 0, nullptr); 76 if (gc_ == nullptr) { 77 RTC_LOG(LS_ERROR) << "Unable to get graphics context"; 78 DeinitXlib(); 79 return false; 80 } 81 82 options_.x_display()->AddEventHandler(ConfigureNotify, this); 83 84 // Check for XFixes extension. This is required for cursor shape 85 // notifications, and for our use of XDamage. 86 if (XFixesQueryExtension(display(), &xfixes_event_base_, 87 &xfixes_error_base_)) { 88 has_xfixes_ = true; 89 } else { 90 RTC_LOG(LS_INFO) << "X server does not support XFixes."; 91 } 92 93 // Register for changes to the dimensions of the root window. 94 XSelectInput(display(), root_window_, StructureNotifyMask); 95 96 if (!x_server_pixel_buffer_.Init(atom_cache_.get(), 97 DefaultRootWindow(display()))) { 98 RTC_LOG(LS_ERROR) << "Failed to initialize pixel buffer."; 99 return false; 100 } 101 102 if (options_.use_update_notifications()) { 103 InitXDamage(); 104 } 105 106 InitXrandr(); 107 108 // Default source set here so that selected_monitor_rect_ is sized correctly. 109 SelectSource(kFullDesktopScreenId); 110 111 return true; 112 } 113 114 void ScreenCapturerX11::InitXDamage() { 115 // Our use of XDamage requires XFixes. 116 if (!has_xfixes_) { 117 return; 118 } 119 120 // Check for XDamage extension. 121 if (!XDamageQueryExtension(display(), &damage_event_base_, 122 &damage_error_base_)) { 123 RTC_LOG(LS_INFO) << "X server does not support XDamage."; 124 return; 125 } 126 127 // TODO(lambroslambrou): Disable DAMAGE in situations where it is known 128 // to fail, such as when Desktop Effects are enabled, with graphics 129 // drivers (nVidia, ATI) that fail to report DAMAGE notifications 130 // properly. 131 132 // Request notifications every time the screen becomes damaged. 133 damage_handle_ = 134 XDamageCreate(display(), root_window_, XDamageReportNonEmpty); 135 if (!damage_handle_) { 136 RTC_LOG(LS_ERROR) << "Unable to initialize XDamage."; 137 return; 138 } 139 140 // Create an XFixes server-side region to collate damage into. 141 damage_region_ = XFixesCreateRegion(display(), nullptr, 0); 142 if (!damage_region_) { 143 XDamageDestroy(display(), damage_handle_); 144 RTC_LOG(LS_ERROR) << "Unable to create XFixes region."; 145 return; 146 } 147 148 options_.x_display()->AddEventHandler(damage_event_base_ + XDamageNotify, 149 this); 150 151 use_damage_ = true; 152 RTC_LOG(LS_INFO) << "Using XDamage extension."; 153 } 154 155 RTC_NO_SANITIZE("cfi-icall") 156 void ScreenCapturerX11::InitXrandr() { 157 int major_version = 0; 158 int minor_version = 0; 159 int error_base_ignored = 0; 160 if (XRRQueryExtension(display(), &randr_event_base_, &error_base_ignored) && 161 XRRQueryVersion(display(), &major_version, &minor_version)) { 162 if (major_version > 1 || (major_version == 1 && minor_version >= 5)) { 163 // Dynamically link XRRGetMonitors and XRRFreeMonitors as a workaround 164 // to avoid a dependency issue with Debian 8. 165 get_monitors_ = reinterpret_cast<get_monitors_func>( 166 dlsym(RTLD_DEFAULT, "XRRGetMonitors")); 167 free_monitors_ = reinterpret_cast<free_monitors_func>( 168 dlsym(RTLD_DEFAULT, "XRRFreeMonitors")); 169 if (get_monitors_ && free_monitors_) { 170 use_randr_ = true; 171 RTC_LOG(LS_INFO) << "Using XRandR extension v" << major_version << '.' 172 << minor_version << '.'; 173 monitors_ = 174 get_monitors_(display(), root_window_, true, &num_monitors_); 175 176 // Register for screen change notifications 177 XRRSelectInput(display(), root_window_, RRScreenChangeNotifyMask); 178 options_.x_display()->AddEventHandler( 179 randr_event_base_ + RRScreenChangeNotify, this); 180 } else { 181 RTC_LOG(LS_ERROR) << "Unable to link XRandR monitor functions."; 182 } 183 } else { 184 RTC_LOG(LS_ERROR) << "XRandR entension is older than v1.5."; 185 } 186 } else { 187 RTC_LOG(LS_ERROR) << "X server does not support XRandR."; 188 } 189 } 190 191 RTC_NO_SANITIZE("cfi-icall") 192 void ScreenCapturerX11::UpdateMonitors() { 193 // The queue should be reset whenever |selected_monitor_rect_| changes, so 194 // that the DCHECKs in CaptureScreen() are satisfied. 195 queue_.Reset(); 196 197 if (monitors_) { 198 free_monitors_(monitors_); 199 monitors_ = nullptr; 200 } 201 202 monitors_ = get_monitors_(display(), root_window_, true, &num_monitors_); 203 204 if (selected_monitor_name_) { 205 if (selected_monitor_name_ == static_cast<Atom>(kFullDesktopScreenId)) { 206 selected_monitor_rect_ = 207 DesktopRect::MakeSize(x_server_pixel_buffer_.window_size()); 208 return; 209 } 210 211 for (int i = 0; i < num_monitors_; ++i) { 212 XRRMonitorInfo& m = monitors_[i]; 213 if (selected_monitor_name_ == m.name) { 214 RTC_LOG(LS_INFO) << "XRandR monitor " << m.name << " rect updated."; 215 selected_monitor_rect_ = 216 DesktopRect::MakeXYWH(m.x, m.y, m.width, m.height); 217 const auto& pixel_buffer_rect = x_server_pixel_buffer_.window_rect(); 218 if (!pixel_buffer_rect.ContainsRect(selected_monitor_rect_)) { 219 // This is never expected to happen, but crop the rectangle anyway 220 // just in case the server returns inconsistent information. 221 // CaptureScreen() expects `selected_monitor_rect_` to lie within 222 // the pixel-buffer's rectangle. 223 RTC_LOG(LS_WARNING) 224 << "Cropping selected monitor rect to fit the pixel-buffer."; 225 selected_monitor_rect_.IntersectWith(pixel_buffer_rect); 226 } 227 return; 228 } 229 } 230 231 // The selected monitor is not connected anymore 232 RTC_LOG(LS_INFO) << "XRandR selected monitor " << selected_monitor_name_ 233 << " lost."; 234 selected_monitor_rect_ = DesktopRect::MakeWH(0, 0); 235 } 236 } 237 238 void ScreenCapturerX11::Start(Callback* callback) { 239 RTC_DCHECK(!callback_); 240 RTC_DCHECK(callback); 241 242 callback_ = callback; 243 } 244 245 void ScreenCapturerX11::CaptureFrame() { 246 TRACE_EVENT0("webrtc", "ScreenCapturerX11::CaptureFrame"); 247 int64_t capture_start_time_nanos = TimeNanos(); 248 249 queue_.MoveToNextFrame(); 250 if (queue_.current_frame() && queue_.current_frame()->IsShared()) { 251 RTC_DLOG(LS_WARNING) << "Overwriting frame that is still shared."; 252 } 253 254 // Process XEvents for XDamage and cursor shape tracking. 255 options_.x_display()->ProcessPendingXEvents(); 256 257 // ProcessPendingXEvents() may call ScreenConfigurationChanged() which 258 // reinitializes `x_server_pixel_buffer_`. Check if the pixel buffer is still 259 // in a good shape. 260 if (!x_server_pixel_buffer_.is_initialized()) { 261 // We failed to initialize pixel buffer. 262 RTC_LOG(LS_ERROR) << "Pixel buffer is not initialized."; 263 callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); 264 return; 265 } 266 267 // Allocate the current frame buffer only if it is not already allocated. 268 // Note that we can't reallocate other buffers at this point, since the caller 269 // may still be reading from them. 270 if (!queue_.current_frame()) { 271 std::unique_ptr<DesktopFrame> frame( 272 new BasicDesktopFrame(selected_monitor_rect_.size(), FOURCC_ARGB)); 273 274 // We set the top-left of the frame so the mouse cursor will be composited 275 // properly, and our frame buffer will not be overrun while blitting. 276 frame->set_top_left(selected_monitor_rect_.top_left()); 277 queue_.ReplaceCurrentFrame(SharedDesktopFrame::Wrap(std::move(frame))); 278 } 279 280 std::unique_ptr<DesktopFrame> result = CaptureScreen(); 281 if (!result) { 282 RTC_LOG(LS_WARNING) << "Temporarily failed to capture screen."; 283 callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); 284 return; 285 } 286 287 last_invalid_region_ = result->updated_region(); 288 result->set_capture_time_ms((TimeNanos() - capture_start_time_nanos) / 289 kNumNanosecsPerMillisec); 290 result->set_capturer_id(DesktopCapturerId::kX11CapturerLinux); 291 callback_->OnCaptureResult(Result::SUCCESS, std::move(result)); 292 } 293 294 bool ScreenCapturerX11::GetSourceList(SourceList* sources) { 295 RTC_DCHECK(sources->empty()); 296 if (!use_randr_) { 297 sources->push_back({}); 298 return true; 299 } 300 301 // Ensure that `monitors_` is updated with changes that may have happened 302 // between calls to GetSourceList(). 303 options_.x_display()->ProcessPendingXEvents(); 304 305 for (int i = 0; i < num_monitors_; ++i) { 306 XRRMonitorInfo& m = monitors_[i]; 307 char* monitor_title = XGetAtomName(display(), m.name); 308 309 // Note name is an X11 Atom used to id the monitor. 310 sources->push_back({static_cast<SourceId>(m.name), 0, monitor_title}); 311 XFree(monitor_title); 312 } 313 314 return true; 315 } 316 317 bool ScreenCapturerX11::SelectSource(SourceId id) { 318 // Prevent the reuse of any frame buffers allocated for a previously selected 319 // source. This is required to stop crashes, or old data from appearing in 320 // a captured frame, when the new source is sized differently then the source 321 // that was selected at the time a reused frame buffer was created. 322 queue_.Reset(); 323 324 if (!use_randr_ || id == kFullDesktopScreenId) { 325 selected_monitor_name_ = kFullDesktopScreenId; 326 selected_monitor_rect_ = 327 DesktopRect::MakeSize(x_server_pixel_buffer_.window_size()); 328 return true; 329 } 330 331 for (int i = 0; i < num_monitors_; ++i) { 332 if (id == static_cast<SourceId>(monitors_[i].name)) { 333 RTC_LOG(LS_INFO) << "XRandR selected source: " << id; 334 XRRMonitorInfo& m = monitors_[i]; 335 selected_monitor_name_ = m.name; 336 selected_monitor_rect_ = 337 DesktopRect::MakeXYWH(m.x, m.y, m.width, m.height); 338 const auto& pixel_buffer_rect = x_server_pixel_buffer_.window_rect(); 339 if (!pixel_buffer_rect.ContainsRect(selected_monitor_rect_)) { 340 RTC_LOG(LS_WARNING) 341 << "Cropping selected monitor rect to fit the pixel-buffer."; 342 selected_monitor_rect_.IntersectWith(pixel_buffer_rect); 343 } 344 return true; 345 } 346 } 347 return false; 348 } 349 350 bool ScreenCapturerX11::HandleXEvent(const XEvent& event) { 351 if (use_damage_ && (event.type == damage_event_base_ + XDamageNotify)) { 352 const XDamageNotifyEvent* damage_event = 353 reinterpret_cast<const XDamageNotifyEvent*>(&event); 354 if (damage_event->damage != damage_handle_) 355 return false; 356 RTC_DCHECK(damage_event->level == XDamageReportNonEmpty); 357 return true; 358 } else if (use_randr_ && 359 event.type == randr_event_base_ + RRScreenChangeNotify) { 360 XRRUpdateConfiguration(const_cast<XEvent*>(&event)); 361 UpdateMonitors(); 362 RTC_LOG(LS_INFO) << "XRandR screen change event received."; 363 return false; 364 } else if (event.type == ConfigureNotify) { 365 ScreenConfigurationChanged(); 366 return false; 367 } 368 return false; 369 } 370 371 std::unique_ptr<DesktopFrame> ScreenCapturerX11::CaptureScreen() { 372 std::unique_ptr<SharedDesktopFrame> frame = queue_.current_frame()->Share(); 373 RTC_DCHECK(selected_monitor_rect_.size().equals(frame->size())); 374 RTC_DCHECK(selected_monitor_rect_.top_left().equals(frame->top_left())); 375 376 // Pass the screen size to the helper, so it can clip the invalid region if it 377 // expands that region to a grid. Note that the helper operates in the 378 // DesktopFrame coordinate system where the top-left pixel is (0, 0), even for 379 // a monitor with non-zero offset relative to `x_server_pixel_buffer_`. 380 helper_.set_size_most_recent(frame->size()); 381 382 // In the DAMAGE case, ensure the frame is up-to-date with the previous frame 383 // if any. If there isn't a previous frame, that means a screen-resolution 384 // change occurred, and `invalid_rects` will be updated to include the whole 385 // screen. 386 if (use_damage_ && queue_.previous_frame()) 387 SynchronizeFrame(); 388 389 DesktopRegion* updated_region = frame->mutable_updated_region(); 390 391 x_server_pixel_buffer_.Synchronize(); 392 if (use_damage_ && queue_.previous_frame()) { 393 // Atomically fetch and clear the damage region. 394 XDamageSubtract(display(), damage_handle_, None, damage_region_); 395 int rects_num = 0; 396 XRectangle bounds; 397 XRectangle* rects = XFixesFetchRegionAndBounds(display(), damage_region_, 398 &rects_num, &bounds); 399 for (int i = 0; i < rects_num; ++i) { 400 auto damage_rect = DesktopRect::MakeXYWH(rects[i].x, rects[i].y, 401 rects[i].width, rects[i].height); 402 403 // Damage-regions are relative to `x_server_pixel_buffer`, so convert the 404 // region to DesktopFrame coordinates where the top-left is always (0, 0), 405 // before adding to the frame's updated_region. `helper_` also operates in 406 // DesktopFrame coordinates, and it will take care of cropping away any 407 // damage-regions that lie outside the selected monitor. 408 damage_rect.Translate(-frame->top_left()); 409 updated_region->AddRect(damage_rect); 410 } 411 XFree(rects); 412 helper_.InvalidateRegion(*updated_region); 413 414 // Capture the damaged portions of the desktop. 415 helper_.TakeInvalidRegion(updated_region); 416 417 for (DesktopRegion::Iterator it(*updated_region); !it.IsAtEnd(); 418 it.Advance()) { 419 auto rect = it.rect(); 420 rect.Translate(frame->top_left()); 421 if (!x_server_pixel_buffer_.CaptureRect(rect, frame.get())) 422 return nullptr; 423 } 424 } else { 425 // Doing full-screen polling, or this is the first capture after a 426 // screen-resolution change. In either case, need a full-screen capture. 427 if (!x_server_pixel_buffer_.CaptureRect(selected_monitor_rect_, 428 frame.get())) { 429 return nullptr; 430 } 431 updated_region->SetRect(DesktopRect::MakeSize(frame->size())); 432 } 433 434 return std::move(frame); 435 } 436 437 void ScreenCapturerX11::ScreenConfigurationChanged() { 438 TRACE_EVENT0("webrtc", "ScreenCapturerX11::ScreenConfigurationChanged"); 439 // Make sure the frame buffers will be reallocated. 440 queue_.Reset(); 441 442 helper_.ClearInvalidRegion(); 443 if (!x_server_pixel_buffer_.Init(atom_cache_.get(), 444 DefaultRootWindow(display()))) { 445 RTC_LOG(LS_ERROR) << "Failed to initialize pixel buffer after screen " 446 "configuration change."; 447 } 448 449 if (use_randr_) { 450 // Adding/removing RANDR monitors can generate a ConfigureNotify event 451 // without generating any RRScreenChangeNotify event. So it is important to 452 // update the monitors here even if the screen resolution hasn't changed. 453 UpdateMonitors(); 454 } else { 455 selected_monitor_rect_ = 456 DesktopRect::MakeSize(x_server_pixel_buffer_.window_size()); 457 } 458 } 459 460 void ScreenCapturerX11::SynchronizeFrame() { 461 // Synchronize the current buffer with the previous one since we do not 462 // capture the entire desktop. Note that encoder may be reading from the 463 // previous buffer at this time so thread access complaints are false 464 // positives. 465 466 // TODO(hclam): We can reduce the amount of copying here by subtracting 467 // `capturer_helper_`s region from `last_invalid_region_`. 468 // http://crbug.com/92354 469 RTC_DCHECK(queue_.previous_frame()); 470 471 DesktopFrame* current = queue_.current_frame(); 472 DesktopFrame* last = queue_.previous_frame(); 473 RTC_DCHECK(current != last); 474 for (DesktopRegion::Iterator it(last_invalid_region_); !it.IsAtEnd(); 475 it.Advance()) { 476 const DesktopRect& r = it.rect(); 477 current->CopyPixelsFrom(*last, r.top_left(), r); 478 } 479 } 480 481 RTC_NO_SANITIZE("cfi-icall") 482 void ScreenCapturerX11::DeinitXlib() { 483 if (monitors_) { 484 free_monitors_(monitors_); 485 monitors_ = nullptr; 486 } 487 488 if (gc_) { 489 XFreeGC(display(), gc_); 490 gc_ = nullptr; 491 } 492 493 x_server_pixel_buffer_.Release(); 494 495 if (display()) { 496 if (damage_handle_) { 497 XDamageDestroy(display(), damage_handle_); 498 damage_handle_ = 0; 499 } 500 501 if (damage_region_) { 502 XFixesDestroyRegion(display(), damage_region_); 503 damage_region_ = 0; 504 } 505 } 506 } 507 508 // static 509 std::unique_ptr<DesktopCapturer> ScreenCapturerX11::CreateRawScreenCapturer( 510 const DesktopCaptureOptions& options) { 511 if (!options.x_display()) 512 return nullptr; 513 514 RTC_LOG(LS_INFO) 515 << "video capture: ScreenCapturerX11::CreateRawScreenCapturer creates " 516 "DesktopCapturer of type ScreenCapturerX11"; 517 std::unique_ptr<ScreenCapturerX11> capturer(new ScreenCapturerX11()); 518 if (!capturer->Init(options)) { 519 RTC_LOG(LS_INFO) 520 << "video capture: ScreenCapturerX11::CreateRawScreenCapturer " 521 "DesktopCapturer is null because it can not be initiated"; 522 return nullptr; 523 } 524 525 return std::move(capturer); 526 } 527 528 } // namespace webrtc