| /* | 
 |  *  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. | 
 |  * | 
 |  *  Use of this source code is governed by a BSD-style license | 
 |  *  that can be found in the LICENSE file in the root of the source | 
 |  *  tree. An additional intellectual property rights grant can be found | 
 |  *  in the file PATENTS.  All contributing project authors may | 
 |  *  be found in the AUTHORS file in the root of the source tree. | 
 |  */ | 
 |  | 
 | #include <string.h> | 
 |  | 
 | #include <memory> | 
 | #include <set> | 
 | #include <utility> | 
 |  | 
 | #include <X11/Xlib.h> | 
 | #include <X11/Xutil.h> | 
 | #include <X11/extensions/Xdamage.h> | 
 | #include <X11/extensions/Xfixes.h> | 
 |  | 
 | #include "modules/desktop_capture/desktop_capture_options.h" | 
 | #include "modules/desktop_capture/desktop_capturer.h" | 
 | #include "modules/desktop_capture/desktop_frame.h" | 
 | #include "modules/desktop_capture/screen_capture_frame_queue.h" | 
 | #include "modules/desktop_capture/screen_capturer_helper.h" | 
 | #include "modules/desktop_capture/shared_desktop_frame.h" | 
 | #include "modules/desktop_capture/x11/x_server_pixel_buffer.h" | 
 | #include "rtc_base/checks.h" | 
 | #include "rtc_base/constructormagic.h" | 
 | #include "rtc_base/logging.h" | 
 | #include "rtc_base/timeutils.h" | 
 | #include "rtc_base/trace_event.h" | 
 |  | 
 | namespace webrtc { | 
 | namespace { | 
 |  | 
 | // A class to perform video frame capturing for Linux. | 
 | // | 
 | // If XDamage is used, this class sets DesktopFrame::updated_region() according | 
 | // to the areas reported by XDamage. Otherwise this class does not detect | 
 | // DesktopFrame::updated_region(), the field is always set to the entire frame | 
 | // rectangle. ScreenCapturerDifferWrapper should be used if that functionality | 
 | // is necessary. | 
 | class ScreenCapturerLinux : public DesktopCapturer, | 
 |                             public SharedXDisplay::XEventHandler { | 
 |  public: | 
 |   ScreenCapturerLinux(); | 
 |   ~ScreenCapturerLinux() override; | 
 |  | 
 |   // TODO(ajwong): Do we really want this to be synchronous? | 
 |   bool Init(const DesktopCaptureOptions& options); | 
 |  | 
 |   // DesktopCapturer interface. | 
 |   void Start(Callback* delegate) override; | 
 |   void CaptureFrame() override; | 
 |   bool GetSourceList(SourceList* sources) override; | 
 |   bool SelectSource(SourceId id) override; | 
 |  | 
 |  private: | 
 |   Display* display() { return options_.x_display()->display(); } | 
 |  | 
 |   // SharedXDisplay::XEventHandler interface. | 
 |   bool HandleXEvent(const XEvent& event) override; | 
 |  | 
 |   void InitXDamage(); | 
 |  | 
 |   // Capture screen pixels to the current buffer in the queue. In the DAMAGE | 
 |   // case, the ScreenCapturerHelper already holds the list of invalid rectangles | 
 |   // from HandleXEvent(). In the non-DAMAGE case, this captures the | 
 |   // whole screen, then calculates some invalid rectangles that include any | 
 |   // differences between this and the previous capture. | 
 |   std::unique_ptr<DesktopFrame> CaptureScreen(); | 
 |  | 
 |   // Called when the screen configuration is changed. | 
 |   void ScreenConfigurationChanged(); | 
 |  | 
 |   // Synchronize the current buffer with |last_buffer_|, by copying pixels from | 
 |   // the area of |last_invalid_rects|. | 
 |   // Note this only works on the assumption that kNumBuffers == 2, as | 
 |   // |last_invalid_rects| holds the differences from the previous buffer and | 
 |   // the one prior to that (which will then be the current buffer). | 
 |   void SynchronizeFrame(); | 
 |  | 
 |   void DeinitXlib(); | 
 |  | 
 |   DesktopCaptureOptions options_; | 
 |  | 
 |   Callback* callback_ = nullptr; | 
 |  | 
 |   // X11 graphics context. | 
 |   GC gc_ = nullptr; | 
 |   Window root_window_ = BadValue; | 
 |  | 
 |   // XFixes. | 
 |   bool has_xfixes_ = false; | 
 |   int xfixes_event_base_ = -1; | 
 |   int xfixes_error_base_ = -1; | 
 |  | 
 |   // XDamage information. | 
 |   bool use_damage_ = false; | 
 |   Damage damage_handle_ = 0; | 
 |   int damage_event_base_ = -1; | 
 |   int damage_error_base_ = -1; | 
 |   XserverRegion damage_region_ = 0; | 
 |  | 
 |   // Access to the X Server's pixel buffer. | 
 |   XServerPixelBuffer x_server_pixel_buffer_; | 
 |  | 
 |   // A thread-safe list of invalid rectangles, and the size of the most | 
 |   // recently captured screen. | 
 |   ScreenCapturerHelper helper_; | 
 |  | 
 |   // Queue of the frames buffers. | 
 |   ScreenCaptureFrameQueue<SharedDesktopFrame> queue_; | 
 |  | 
 |   // Invalid region from the previous capture. This is used to synchronize the | 
 |   // current with the last buffer used. | 
 |   DesktopRegion last_invalid_region_; | 
 |  | 
 |   RTC_DISALLOW_COPY_AND_ASSIGN(ScreenCapturerLinux); | 
 | }; | 
 |  | 
 | ScreenCapturerLinux::ScreenCapturerLinux() { | 
 |   helper_.SetLogGridSize(4); | 
 | } | 
 |  | 
 | ScreenCapturerLinux::~ScreenCapturerLinux() { | 
 |   options_.x_display()->RemoveEventHandler(ConfigureNotify, this); | 
 |   if (use_damage_) { | 
 |     options_.x_display()->RemoveEventHandler(damage_event_base_ + XDamageNotify, | 
 |                                              this); | 
 |   } | 
 |   DeinitXlib(); | 
 | } | 
 |  | 
 | bool ScreenCapturerLinux::Init(const DesktopCaptureOptions& options) { | 
 |   TRACE_EVENT0("webrtc", "ScreenCapturerLinux::Init"); | 
 |   options_ = options; | 
 |  | 
 |   root_window_ = RootWindow(display(), DefaultScreen(display())); | 
 |   if (root_window_ == BadValue) { | 
 |     RTC_LOG(LS_ERROR) << "Unable to get the root window"; | 
 |     DeinitXlib(); | 
 |     return false; | 
 |   } | 
 |  | 
 |   gc_ = XCreateGC(display(), root_window_, 0, NULL); | 
 |   if (gc_ == NULL) { | 
 |     RTC_LOG(LS_ERROR) << "Unable to get graphics context"; | 
 |     DeinitXlib(); | 
 |     return false; | 
 |   } | 
 |  | 
 |   options_.x_display()->AddEventHandler(ConfigureNotify, this); | 
 |  | 
 |   // Check for XFixes extension. This is required for cursor shape | 
 |   // notifications, and for our use of XDamage. | 
 |   if (XFixesQueryExtension(display(), &xfixes_event_base_, | 
 |                            &xfixes_error_base_)) { | 
 |     has_xfixes_ = true; | 
 |   } else { | 
 |     RTC_LOG(LS_INFO) << "X server does not support XFixes."; | 
 |   } | 
 |  | 
 |   // Register for changes to the dimensions of the root window. | 
 |   XSelectInput(display(), root_window_, StructureNotifyMask); | 
 |  | 
 |   if (!x_server_pixel_buffer_.Init(display(), DefaultRootWindow(display()))) { | 
 |     RTC_LOG(LS_ERROR) << "Failed to initialize pixel buffer."; | 
 |     return false; | 
 |   } | 
 |  | 
 |   if (options_.use_update_notifications()) { | 
 |     InitXDamage(); | 
 |   } | 
 |  | 
 |   return true; | 
 | } | 
 |  | 
 | void ScreenCapturerLinux::InitXDamage() { | 
 |   // Our use of XDamage requires XFixes. | 
 |   if (!has_xfixes_) { | 
 |     return; | 
 |   } | 
 |  | 
 |   // Check for XDamage extension. | 
 |   if (!XDamageQueryExtension(display(), &damage_event_base_, | 
 |                              &damage_error_base_)) { | 
 |     RTC_LOG(LS_INFO) << "X server does not support XDamage."; | 
 |     return; | 
 |   } | 
 |  | 
 |   // TODO(lambroslambrou): Disable DAMAGE in situations where it is known | 
 |   // to fail, such as when Desktop Effects are enabled, with graphics | 
 |   // drivers (nVidia, ATI) that fail to report DAMAGE notifications | 
 |   // properly. | 
 |  | 
 |   // Request notifications every time the screen becomes damaged. | 
 |   damage_handle_ = | 
 |       XDamageCreate(display(), root_window_, XDamageReportNonEmpty); | 
 |   if (!damage_handle_) { | 
 |     RTC_LOG(LS_ERROR) << "Unable to initialize XDamage."; | 
 |     return; | 
 |   } | 
 |  | 
 |   // Create an XFixes server-side region to collate damage into. | 
 |   damage_region_ = XFixesCreateRegion(display(), 0, 0); | 
 |   if (!damage_region_) { | 
 |     XDamageDestroy(display(), damage_handle_); | 
 |     RTC_LOG(LS_ERROR) << "Unable to create XFixes region."; | 
 |     return; | 
 |   } | 
 |  | 
 |   options_.x_display()->AddEventHandler(damage_event_base_ + XDamageNotify, | 
 |                                         this); | 
 |  | 
 |   use_damage_ = true; | 
 |   RTC_LOG(LS_INFO) << "Using XDamage extension."; | 
 | } | 
 |  | 
 | void ScreenCapturerLinux::Start(Callback* callback) { | 
 |   RTC_DCHECK(!callback_); | 
 |   RTC_DCHECK(callback); | 
 |  | 
 |   callback_ = callback; | 
 | } | 
 |  | 
 | void ScreenCapturerLinux::CaptureFrame() { | 
 |   TRACE_EVENT0("webrtc", "ScreenCapturerLinux::CaptureFrame"); | 
 |   int64_t capture_start_time_nanos = rtc::TimeNanos(); | 
 |  | 
 |   queue_.MoveToNextFrame(); | 
 |   RTC_DCHECK(!queue_.current_frame() || !queue_.current_frame()->IsShared()); | 
 |  | 
 |   // Process XEvents for XDamage and cursor shape tracking. | 
 |   options_.x_display()->ProcessPendingXEvents(); | 
 |  | 
 |   // ProcessPendingXEvents() may call ScreenConfigurationChanged() which | 
 |   // reinitializes |x_server_pixel_buffer_|. Check if the pixel buffer is still | 
 |   // in a good shape. | 
 |   if (!x_server_pixel_buffer_.is_initialized()) { | 
 |     // We failed to initialize pixel buffer. | 
 |     RTC_LOG(LS_ERROR) << "Pixel buffer is not initialized."; | 
 |     callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); | 
 |     return; | 
 |   } | 
 |  | 
 |   // If the current frame is from an older generation then allocate a new one. | 
 |   // Note that we can't reallocate other buffers at this point, since the caller | 
 |   // may still be reading from them. | 
 |   if (!queue_.current_frame()) { | 
 |     queue_.ReplaceCurrentFrame( | 
 |         SharedDesktopFrame::Wrap(std::unique_ptr<DesktopFrame>( | 
 |             new BasicDesktopFrame(x_server_pixel_buffer_.window_size())))); | 
 |   } | 
 |  | 
 |   std::unique_ptr<DesktopFrame> result = CaptureScreen(); | 
 |   if (!result) { | 
 |     RTC_LOG(LS_WARNING) << "Temporarily failed to capture screen."; | 
 |     callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); | 
 |     return; | 
 |   } | 
 |  | 
 |   last_invalid_region_ = result->updated_region(); | 
 |   result->set_capture_time_ms((rtc::TimeNanos() - capture_start_time_nanos) / | 
 |                               rtc::kNumNanosecsPerMillisec); | 
 |   callback_->OnCaptureResult(Result::SUCCESS, std::move(result)); | 
 | } | 
 |  | 
 | bool ScreenCapturerLinux::GetSourceList(SourceList* sources) { | 
 |   RTC_DCHECK(sources->size() == 0); | 
 |   // TODO(jiayl): implement screen enumeration. | 
 |   sources->push_back({0}); | 
 |   return true; | 
 | } | 
 |  | 
 | bool ScreenCapturerLinux::SelectSource(SourceId id) { | 
 |   // TODO(jiayl): implement screen selection. | 
 |   return true; | 
 | } | 
 |  | 
 | bool ScreenCapturerLinux::HandleXEvent(const XEvent& event) { | 
 |   if (use_damage_ && (event.type == damage_event_base_ + XDamageNotify)) { | 
 |     const XDamageNotifyEvent* damage_event = | 
 |         reinterpret_cast<const XDamageNotifyEvent*>(&event); | 
 |     if (damage_event->damage != damage_handle_) | 
 |       return false; | 
 |     RTC_DCHECK(damage_event->level == XDamageReportNonEmpty); | 
 |     return true; | 
 |   } else if (event.type == ConfigureNotify) { | 
 |     ScreenConfigurationChanged(); | 
 |     return true; | 
 |   } | 
 |   return false; | 
 | } | 
 |  | 
 | std::unique_ptr<DesktopFrame> ScreenCapturerLinux::CaptureScreen() { | 
 |   std::unique_ptr<SharedDesktopFrame> frame = queue_.current_frame()->Share(); | 
 |   RTC_DCHECK(x_server_pixel_buffer_.window_size().equals(frame->size())); | 
 |  | 
 |   // Pass the screen size to the helper, so it can clip the invalid region if it | 
 |   // expands that region to a grid. | 
 |   helper_.set_size_most_recent(frame->size()); | 
 |  | 
 |   // In the DAMAGE case, ensure the frame is up-to-date with the previous frame | 
 |   // if any.  If there isn't a previous frame, that means a screen-resolution | 
 |   // change occurred, and |invalid_rects| will be updated to include the whole | 
 |   // screen. | 
 |   if (use_damage_ && queue_.previous_frame()) | 
 |     SynchronizeFrame(); | 
 |  | 
 |   DesktopRegion* updated_region = frame->mutable_updated_region(); | 
 |  | 
 |   x_server_pixel_buffer_.Synchronize(); | 
 |   if (use_damage_ && queue_.previous_frame()) { | 
 |     // Atomically fetch and clear the damage region. | 
 |     XDamageSubtract(display(), damage_handle_, None, damage_region_); | 
 |     int rects_num = 0; | 
 |     XRectangle bounds; | 
 |     XRectangle* rects = XFixesFetchRegionAndBounds(display(), damage_region_, | 
 |                                                    &rects_num, &bounds); | 
 |     for (int i = 0; i < rects_num; ++i) { | 
 |       updated_region->AddRect(DesktopRect::MakeXYWH( | 
 |           rects[i].x, rects[i].y, rects[i].width, rects[i].height)); | 
 |     } | 
 |     XFree(rects); | 
 |     helper_.InvalidateRegion(*updated_region); | 
 |  | 
 |     // Capture the damaged portions of the desktop. | 
 |     helper_.TakeInvalidRegion(updated_region); | 
 |  | 
 |     // Clip the damaged portions to the current screen size, just in case some | 
 |     // spurious XDamage notifications were received for a previous (larger) | 
 |     // screen size. | 
 |     updated_region->IntersectWith( | 
 |         DesktopRect::MakeSize(x_server_pixel_buffer_.window_size())); | 
 |  | 
 |     for (DesktopRegion::Iterator it(*updated_region); !it.IsAtEnd(); | 
 |          it.Advance()) { | 
 |       if (!x_server_pixel_buffer_.CaptureRect(it.rect(), frame.get())) | 
 |         return nullptr; | 
 |     } | 
 |   } else { | 
 |     // Doing full-screen polling, or this is the first capture after a | 
 |     // screen-resolution change.  In either case, need a full-screen capture. | 
 |     DesktopRect screen_rect = DesktopRect::MakeSize(frame->size()); | 
 |     if (!x_server_pixel_buffer_.CaptureRect(screen_rect, frame.get())) | 
 |       return nullptr; | 
 |     updated_region->SetRect(screen_rect); | 
 |   } | 
 |  | 
 |   return std::move(frame); | 
 | } | 
 |  | 
 | void ScreenCapturerLinux::ScreenConfigurationChanged() { | 
 |   TRACE_EVENT0("webrtc", "ScreenCapturerLinux::ScreenConfigurationChanged"); | 
 |   // Make sure the frame buffers will be reallocated. | 
 |   queue_.Reset(); | 
 |  | 
 |   helper_.ClearInvalidRegion(); | 
 |   if (!x_server_pixel_buffer_.Init(display(), DefaultRootWindow(display()))) { | 
 |     RTC_LOG(LS_ERROR) << "Failed to initialize pixel buffer after screen " | 
 |                          "configuration change."; | 
 |   } | 
 | } | 
 |  | 
 | void ScreenCapturerLinux::SynchronizeFrame() { | 
 |   // Synchronize the current buffer with the previous one since we do not | 
 |   // capture the entire desktop. Note that encoder may be reading from the | 
 |   // previous buffer at this time so thread access complaints are false | 
 |   // positives. | 
 |  | 
 |   // TODO(hclam): We can reduce the amount of copying here by subtracting | 
 |   // |capturer_helper_|s region from |last_invalid_region_|. | 
 |   // http://crbug.com/92354 | 
 |   RTC_DCHECK(queue_.previous_frame()); | 
 |  | 
 |   DesktopFrame* current = queue_.current_frame(); | 
 |   DesktopFrame* last = queue_.previous_frame(); | 
 |   RTC_DCHECK(current != last); | 
 |   for (DesktopRegion::Iterator it(last_invalid_region_); !it.IsAtEnd(); | 
 |        it.Advance()) { | 
 |     current->CopyPixelsFrom(*last, it.rect().top_left(), it.rect()); | 
 |   } | 
 | } | 
 |  | 
 | void ScreenCapturerLinux::DeinitXlib() { | 
 |   if (gc_) { | 
 |     XFreeGC(display(), gc_); | 
 |     gc_ = nullptr; | 
 |   } | 
 |  | 
 |   x_server_pixel_buffer_.Release(); | 
 |  | 
 |   if (display()) { | 
 |     if (damage_handle_) { | 
 |       XDamageDestroy(display(), damage_handle_); | 
 |       damage_handle_ = 0; | 
 |     } | 
 |  | 
 |     if (damage_region_) { | 
 |       XFixesDestroyRegion(display(), damage_region_); | 
 |       damage_region_ = 0; | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | // static | 
 | std::unique_ptr<DesktopCapturer> DesktopCapturer::CreateRawScreenCapturer( | 
 |     const DesktopCaptureOptions& options) { | 
 |   if (!options.x_display()) | 
 |     return nullptr; | 
 |  | 
 |   std::unique_ptr<ScreenCapturerLinux> capturer(new ScreenCapturerLinux()); | 
 |   if (!capturer.get()->Init(options)) { | 
 |     return nullptr; | 
 |   } | 
 |  | 
 |   return std::move(capturer); | 
 | } | 
 |  | 
 | }  // namespace webrtc |