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