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