tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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