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