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/window_capturer.h"
12
13 #include <assert.h>
14 #include <string.h>
15 #include <X11/Xatom.h>
16 #include <X11/extensions/Xcomposite.h>
17 #include <X11/extensions/Xrender.h>
18 #include <X11/Xutil.h>
19
20 #include <algorithm>
21
22 #include "webrtc/base/scoped_ptr.h"
23 #include "webrtc/base/scoped_ref_ptr.h"
24 #include "webrtc/modules/desktop_capture/desktop_capture_options.h"
25 #include "webrtc/modules/desktop_capture/desktop_frame.h"
26 #include "webrtc/modules/desktop_capture/x11/shared_x_display.h"
27 #include "webrtc/modules/desktop_capture/x11/x_error_trap.h"
28 #include "webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h"
29 #include "webrtc/system_wrappers/include/logging.h"
30
31 namespace webrtc {
32
33 namespace {
34
35 // Convenience wrapper for XGetWindowProperty() results.
36 template <class PropertyType>
37 class XWindowProperty {
38 public:
XWindowProperty(Display * display,Window window,Atom property)39 XWindowProperty(Display* display, Window window, Atom property)
40 : is_valid_(false),
41 size_(0),
42 data_(NULL) {
43 const int kBitsPerByte = 8;
44 Atom actual_type;
45 int actual_format;
46 unsigned long bytes_after; // NOLINT: type required by XGetWindowProperty
47 int status = XGetWindowProperty(display, window, property, 0L, ~0L, False,
48 AnyPropertyType, &actual_type,
49 &actual_format, &size_,
50 &bytes_after, &data_);
51 if (status != Success) {
52 data_ = NULL;
53 return;
54 }
55 if (sizeof(PropertyType) * kBitsPerByte != actual_format) {
56 size_ = 0;
57 return;
58 }
59
60 is_valid_ = true;
61 }
62
~XWindowProperty()63 ~XWindowProperty() {
64 if (data_)
65 XFree(data_);
66 }
67
68 // True if we got properly value successfully.
is_valid() const69 bool is_valid() const { return is_valid_; }
70
71 // Size and value of the property.
size() const72 size_t size() const { return size_; }
data() const73 const PropertyType* data() const {
74 return reinterpret_cast<PropertyType*>(data_);
75 }
data()76 PropertyType* data() {
77 return reinterpret_cast<PropertyType*>(data_);
78 }
79
80 private:
81 bool is_valid_;
82 unsigned long size_; // NOLINT: type required by XGetWindowProperty
83 unsigned char* data_;
84
85 RTC_DISALLOW_COPY_AND_ASSIGN(XWindowProperty);
86 };
87
88 class WindowCapturerLinux : public WindowCapturer,
89 public SharedXDisplay::XEventHandler {
90 public:
91 WindowCapturerLinux(const DesktopCaptureOptions& options);
92 virtual ~WindowCapturerLinux();
93
94 // WindowCapturer interface.
95 bool GetWindowList(WindowList* windows) override;
96 bool SelectWindow(WindowId id) override;
97 bool BringSelectedWindowToFront() override;
98
99 // DesktopCapturer interface.
100 void Start(Callback* callback) override;
101 void Capture(const DesktopRegion& region) override;
102
103 // SharedXDisplay::XEventHandler interface.
104 bool HandleXEvent(const XEvent& event) override;
105
106 private:
display()107 Display* display() { return x_display_->display(); }
108
109 // Iterates through |window| hierarchy to find first visible window, i.e. one
110 // that has WM_STATE property set to NormalState.
111 // See http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.3.1 .
112 ::Window GetApplicationWindow(::Window window);
113
114 // Returns true if the |window| is a desktop element.
115 bool IsDesktopElement(::Window window);
116
117 // Returns window title for the specified X |window|.
118 bool GetWindowTitle(::Window window, std::string* title);
119
120 Callback* callback_;
121
122 rtc::scoped_refptr<SharedXDisplay> x_display_;
123
124 Atom wm_state_atom_;
125 Atom window_type_atom_;
126 Atom normal_window_type_atom_;
127 bool has_composite_extension_;
128
129 ::Window selected_window_;
130 XServerPixelBuffer x_server_pixel_buffer_;
131
132 RTC_DISALLOW_COPY_AND_ASSIGN(WindowCapturerLinux);
133 };
134
WindowCapturerLinux(const DesktopCaptureOptions & options)135 WindowCapturerLinux::WindowCapturerLinux(const DesktopCaptureOptions& options)
136 : callback_(NULL),
137 x_display_(options.x_display()),
138 has_composite_extension_(false),
139 selected_window_(0) {
140 // Create Atoms so we don't need to do it every time they are used.
141 wm_state_atom_ = XInternAtom(display(), "WM_STATE", True);
142 window_type_atom_ = XInternAtom(display(), "_NET_WM_WINDOW_TYPE", True);
143 normal_window_type_atom_ = XInternAtom(
144 display(), "_NET_WM_WINDOW_TYPE_NORMAL", True);
145
146 int event_base, error_base, major_version, minor_version;
147 if (XCompositeQueryExtension(display(), &event_base, &error_base) &&
148 XCompositeQueryVersion(display(), &major_version, &minor_version) &&
149 // XCompositeNameWindowPixmap() requires version 0.2
150 (major_version > 0 || minor_version >= 2)) {
151 has_composite_extension_ = true;
152 } else {
153 LOG(LS_INFO) << "Xcomposite extension not available or too old.";
154 }
155
156 x_display_->AddEventHandler(ConfigureNotify, this);
157 }
158
~WindowCapturerLinux()159 WindowCapturerLinux::~WindowCapturerLinux() {
160 x_display_->RemoveEventHandler(ConfigureNotify, this);
161 }
162
GetWindowList(WindowList * windows)163 bool WindowCapturerLinux::GetWindowList(WindowList* windows) {
164 WindowList result;
165
166 XErrorTrap error_trap(display());
167
168 int num_screens = XScreenCount(display());
169 for (int screen = 0; screen < num_screens; ++screen) {
170 ::Window root_window = XRootWindow(display(), screen);
171 ::Window parent;
172 ::Window *children;
173 unsigned int num_children;
174 int status = XQueryTree(display(), root_window, &root_window, &parent,
175 &children, &num_children);
176 if (status == 0) {
177 LOG(LS_ERROR) << "Failed to query for child windows for screen "
178 << screen;
179 continue;
180 }
181
182 for (unsigned int i = 0; i < num_children; ++i) {
183 // Iterate in reverse order to return windows from front to back.
184 ::Window app_window =
185 GetApplicationWindow(children[num_children - 1 - i]);
186 if (app_window && !IsDesktopElement(app_window)) {
187 Window w;
188 w.id = app_window;
189 if (GetWindowTitle(app_window, &w.title))
190 result.push_back(w);
191 }
192 }
193
194 if (children)
195 XFree(children);
196 }
197
198 windows->swap(result);
199
200 return true;
201 }
202
SelectWindow(WindowId id)203 bool WindowCapturerLinux::SelectWindow(WindowId id) {
204 if (!x_server_pixel_buffer_.Init(display(), id))
205 return false;
206
207 // Tell the X server to send us window resizing events.
208 XSelectInput(display(), id, StructureNotifyMask);
209
210 selected_window_ = id;
211
212 // In addition to needing X11 server-side support for Xcomposite, it actually
213 // needs to be turned on for the window. If the user has modern
214 // hardware/drivers but isn't using a compositing window manager, that won't
215 // be the case. Here we automatically turn it on.
216
217 // Redirect drawing to an offscreen buffer (ie, turn on compositing). X11
218 // remembers who has requested this and will turn it off for us when we exit.
219 XCompositeRedirectWindow(display(), id, CompositeRedirectAutomatic);
220
221 return true;
222 }
223
BringSelectedWindowToFront()224 bool WindowCapturerLinux::BringSelectedWindowToFront() {
225 if (!selected_window_)
226 return false;
227
228 unsigned int num_children;
229 ::Window* children;
230 ::Window parent;
231 ::Window root;
232 // Find the root window to pass event to.
233 int status = XQueryTree(
234 display(), selected_window_, &root, &parent, &children, &num_children);
235 if (status == 0) {
236 LOG(LS_ERROR) << "Failed to query for the root window.";
237 return false;
238 }
239
240 if (children)
241 XFree(children);
242
243 XRaiseWindow(display(), selected_window_);
244
245 // Some window managers (e.g., metacity in GNOME) consider it illegal to
246 // raise a window without also giving it input focus with
247 // _NET_ACTIVE_WINDOW, so XRaiseWindow() on its own isn't enough.
248 Atom atom = XInternAtom(display(), "_NET_ACTIVE_WINDOW", True);
249 if (atom != None) {
250 XEvent xev;
251 xev.xclient.type = ClientMessage;
252 xev.xclient.serial = 0;
253 xev.xclient.send_event = True;
254 xev.xclient.window = selected_window_;
255 xev.xclient.message_type = atom;
256
257 // The format member is set to 8, 16, or 32 and specifies whether the
258 // data should be viewed as a list of bytes, shorts, or longs.
259 xev.xclient.format = 32;
260
261 memset(xev.xclient.data.l, 0, sizeof(xev.xclient.data.l));
262
263 XSendEvent(display(),
264 root,
265 False,
266 SubstructureRedirectMask | SubstructureNotifyMask,
267 &xev);
268 }
269 XFlush(display());
270 return true;
271 }
272
Start(Callback * callback)273 void WindowCapturerLinux::Start(Callback* callback) {
274 assert(!callback_);
275 assert(callback);
276
277 callback_ = callback;
278 }
279
Capture(const DesktopRegion & region)280 void WindowCapturerLinux::Capture(const DesktopRegion& region) {
281 if (!x_server_pixel_buffer_.IsWindowValid()) {
282 LOG(LS_INFO) << "The window is no longer valid.";
283 callback_->OnCaptureCompleted(NULL);
284 return;
285 }
286
287 x_display_->ProcessPendingXEvents();
288
289 if (!has_composite_extension_) {
290 // Without the Xcomposite extension we capture when the whole window is
291 // visible on screen and not covered by any other window. This is not
292 // something we want so instead, just bail out.
293 LOG(LS_INFO) << "No Xcomposite extension detected.";
294 callback_->OnCaptureCompleted(NULL);
295 return;
296 }
297
298 DesktopFrame* frame =
299 new BasicDesktopFrame(x_server_pixel_buffer_.window_size());
300
301 x_server_pixel_buffer_.Synchronize();
302 x_server_pixel_buffer_.CaptureRect(DesktopRect::MakeSize(frame->size()),
303 frame);
304
305 frame->mutable_updated_region()->SetRect(
306 DesktopRect::MakeSize(frame->size()));
307
308 callback_->OnCaptureCompleted(frame);
309 }
310
HandleXEvent(const XEvent & event)311 bool WindowCapturerLinux::HandleXEvent(const XEvent& event) {
312 if (event.type == ConfigureNotify) {
313 XConfigureEvent xce = event.xconfigure;
314 if (!DesktopSize(xce.width, xce.height).equals(
315 x_server_pixel_buffer_.window_size())) {
316 if (!x_server_pixel_buffer_.Init(display(), selected_window_)) {
317 LOG(LS_ERROR) << "Failed to initialize pixel buffer after resizing.";
318 }
319 return true;
320 }
321 }
322 return false;
323 }
324
GetApplicationWindow(::Window window)325 ::Window WindowCapturerLinux::GetApplicationWindow(::Window window) {
326 // Get WM_STATE property of the window.
327 XWindowProperty<uint32_t> window_state(display(), window, wm_state_atom_);
328
329 // WM_STATE is considered to be set to WithdrawnState when it missing.
330 int32_t state = window_state.is_valid() ?
331 *window_state.data() : WithdrawnState;
332
333 if (state == NormalState) {
334 // Window has WM_STATE==NormalState. Return it.
335 return window;
336 } else if (state == IconicState) {
337 // Window is in minimized. Skip it.
338 return 0;
339 }
340
341 // If the window is in WithdrawnState then look at all of its children.
342 ::Window root, parent;
343 ::Window *children;
344 unsigned int num_children;
345 if (!XQueryTree(display(), window, &root, &parent, &children,
346 &num_children)) {
347 LOG(LS_ERROR) << "Failed to query for child windows although window"
348 << "does not have a valid WM_STATE.";
349 return 0;
350 }
351 ::Window app_window = 0;
352 for (unsigned int i = 0; i < num_children; ++i) {
353 app_window = GetApplicationWindow(children[i]);
354 if (app_window)
355 break;
356 }
357
358 if (children)
359 XFree(children);
360 return app_window;
361 }
362
IsDesktopElement(::Window window)363 bool WindowCapturerLinux::IsDesktopElement(::Window window) {
364 if (window == 0)
365 return false;
366
367 // First look for _NET_WM_WINDOW_TYPE. The standard
368 // (http://standards.freedesktop.org/wm-spec/latest/ar01s05.html#id2760306)
369 // says this hint *should* be present on all windows, and we use the existence
370 // of _NET_WM_WINDOW_TYPE_NORMAL in the property to indicate a window is not
371 // a desktop element (that is, only "normal" windows should be shareable).
372 XWindowProperty<uint32_t> window_type(display(), window, window_type_atom_);
373 if (window_type.is_valid() && window_type.size() > 0) {
374 uint32_t* end = window_type.data() + window_type.size();
375 bool is_normal = (end != std::find(
376 window_type.data(), end, normal_window_type_atom_));
377 return !is_normal;
378 }
379
380 // Fall back on using the hint.
381 XClassHint class_hint;
382 Status status = XGetClassHint(display(), window, &class_hint);
383 bool result = false;
384 if (status == 0) {
385 // No hints, assume this is a normal application window.
386 return result;
387 }
388
389 if (strcmp("gnome-panel", class_hint.res_name) == 0 ||
390 strcmp("desktop_window", class_hint.res_name) == 0) {
391 result = true;
392 }
393 XFree(class_hint.res_name);
394 XFree(class_hint.res_class);
395 return result;
396 }
397
GetWindowTitle(::Window window,std::string * title)398 bool WindowCapturerLinux::GetWindowTitle(::Window window, std::string* title) {
399 int status;
400 bool result = false;
401 XTextProperty window_name;
402 window_name.value = NULL;
403 if (window) {
404 status = XGetWMName(display(), window, &window_name);
405 if (status && window_name.value && window_name.nitems) {
406 int cnt;
407 char **list = NULL;
408 status = Xutf8TextPropertyToTextList(display(), &window_name, &list,
409 &cnt);
410 if (status >= Success && cnt && *list) {
411 if (cnt > 1) {
412 LOG(LS_INFO) << "Window has " << cnt
413 << " text properties, only using the first one.";
414 }
415 *title = *list;
416 result = true;
417 }
418 if (list)
419 XFreeStringList(list);
420 }
421 if (window_name.value)
422 XFree(window_name.value);
423 }
424 return result;
425 }
426
427 } // namespace
428
429 // static
Create(const DesktopCaptureOptions & options)430 WindowCapturer* WindowCapturer::Create(const DesktopCaptureOptions& options) {
431 if (!options.x_display())
432 return NULL;
433 return new WindowCapturerLinux(options);
434 }
435
436 } // namespace webrtc
437