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
39namespace webrtc {
40
41namespace {
42
43// A class to perform video frame capturing for Linux.
44class 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
134ScreenCapturerLinux::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
150ScreenCapturerLinux::~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
163bool 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
214void 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
255void ScreenCapturerLinux::Start(Callback* callback) {
256  DCHECK(!callback_);
257  DCHECK(callback);
258
259  callback_ = callback;
260}
261
262void 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
307void 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
315bool 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
324bool ScreenCapturerLinux::SelectScreen(ScreenId id) {
325  // TODO(jiayl): implement screen selection.
326  return true;
327}
328
329bool 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
355void 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
384DesktopFrame* 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
454void 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
465void 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
485void 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
509ScreenCapturer* 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