blob: 5a14356fd16fe9744f6c27a1b3a744e75ea43b53 [file] [log] [blame]
sergeyu@chromium.orge562e022013-08-23 18:22:12 +00001/*
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 <string.h>
14#include <X11/Xatom.h>
15#include <X11/extensions/Xcomposite.h>
16#include <X11/extensions/Xrender.h>
17#include <X11/Xutil.h>
18#include <algorithm>
19#include <cassert>
20
sergeyu@chromium.org91685dc2013-10-12 22:40:05 +000021#include "webrtc/modules/desktop_capture/desktop_capture_options.h"
sergeyu@chromium.orge562e022013-08-23 18:22:12 +000022#include "webrtc/modules/desktop_capture/desktop_frame.h"
sergeyu@chromium.org91685dc2013-10-12 22:40:05 +000023#include "webrtc/modules/desktop_capture/x11/shared_x_display.h"
sergeyu@chromium.orge562e022013-08-23 18:22:12 +000024#include "webrtc/modules/desktop_capture/x11/x_error_trap.h"
25#include "webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h"
26#include "webrtc/system_wrappers/interface/logging.h"
27#include "webrtc/system_wrappers/interface/scoped_ptr.h"
sergeyu@chromium.org91685dc2013-10-12 22:40:05 +000028#include "webrtc/system_wrappers/interface/scoped_refptr.h"
sergeyu@chromium.orge562e022013-08-23 18:22:12 +000029
30namespace webrtc {
31
32namespace {
33
34// Convenience wrapper for XGetWindowProperty() results.
35template <class PropertyType>
36class XWindowProperty {
37 public:
38 XWindowProperty(Display* display, Window window, Atom property)
39 : is_valid_(false),
40 size_(0),
41 data_(NULL) {
42 const int kBitsPerByte = 8;
43 Atom actual_type;
44 int actual_format;
45 unsigned long bytes_after; // NOLINT: type required by XGetWindowProperty
46 int status = XGetWindowProperty(display, window, property, 0L, ~0L, False,
47 AnyPropertyType, &actual_type,
48 &actual_format, &size_,
49 &bytes_after, &data_);
50 if (status != Success) {
51 data_ = NULL;
52 return;
53 }
54 if (sizeof(PropertyType) * kBitsPerByte != actual_format) {
55 size_ = 0;
56 return;
57 }
58
59 is_valid_ = true;
60 }
61
62 ~XWindowProperty() {
63 if (data_)
64 XFree(data_);
65 }
66
67 // True if we got properly value successfully.
68 bool is_valid() const { return is_valid_; }
69
70 // Size and value of the property.
71 size_t size() const { return size_; }
72 const PropertyType* data() const {
73 return reinterpret_cast<PropertyType*>(data_);
74 }
75 PropertyType* data() {
76 return reinterpret_cast<PropertyType*>(data_);
77 }
78
79 private:
80 bool is_valid_;
81 unsigned long size_; // NOLINT: type required by XGetWindowProperty
82 unsigned char* data_;
83
84 DISALLOW_COPY_AND_ASSIGN(XWindowProperty);
85};
86
87class WindowCapturerLinux : public WindowCapturer {
88 public:
sergeyu@chromium.org91685dc2013-10-12 22:40:05 +000089 WindowCapturerLinux(const DesktopCaptureOptions& options);
sergeyu@chromium.orge562e022013-08-23 18:22:12 +000090 virtual ~WindowCapturerLinux();
91
92 // WindowCapturer interface.
93 virtual bool GetWindowList(WindowList* windows) OVERRIDE;
94 virtual bool SelectWindow(WindowId id) OVERRIDE;
95
96 // DesktopCapturer interface.
97 virtual void Start(Callback* callback) OVERRIDE;
98 virtual void Capture(const DesktopRegion& region) OVERRIDE;
99
100 private:
sergeyu@chromium.org91685dc2013-10-12 22:40:05 +0000101 Display* display() { return x_display_->display(); }
102
sergeyu@chromium.orge562e022013-08-23 18:22:12 +0000103 // Iterates through |window| hierarchy to find first visible window, i.e. one
104 // that has WM_STATE property set to NormalState.
105 // See http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.3.1 .
106 ::Window GetApplicationWindow(::Window window);
107
108 // Returns true if the |window| is a desktop element.
109 bool IsDesktopElement(::Window window);
110
111 // Returns window title for the specified X |window|.
112 bool GetWindowTitle(::Window window, std::string* title);
113
114 Callback* callback_;
115
sergeyu@chromium.org91685dc2013-10-12 22:40:05 +0000116 scoped_refptr<SharedXDisplay> x_display_;
sergeyu@chromium.orge562e022013-08-23 18:22:12 +0000117
118 Atom wm_state_atom_;
119 Atom window_type_atom_;
120 Atom normal_window_type_atom_;
121 bool has_composite_extension_;
122
123 ::Window selected_window_;
124 XServerPixelBuffer x_server_pixel_buffer_;
125
126 DISALLOW_COPY_AND_ASSIGN(WindowCapturerLinux);
127};
128
sergeyu@chromium.org91685dc2013-10-12 22:40:05 +0000129WindowCapturerLinux::WindowCapturerLinux(const DesktopCaptureOptions& options)
sergeyu@chromium.orge562e022013-08-23 18:22:12 +0000130 : callback_(NULL),
sergeyu@chromium.org91685dc2013-10-12 22:40:05 +0000131 x_display_(options.x_display()),
sergeyu@chromium.orge562e022013-08-23 18:22:12 +0000132 has_composite_extension_(false),
133 selected_window_(0) {
sergeyu@chromium.orge562e022013-08-23 18:22:12 +0000134 // Create Atoms so we don't need to do it every time they are used.
sergeyu@chromium.org91685dc2013-10-12 22:40:05 +0000135 wm_state_atom_ = XInternAtom(display(), "WM_STATE", True);
136 window_type_atom_ = XInternAtom(display(), "_NET_WM_WINDOW_TYPE", True);
sergeyu@chromium.orge562e022013-08-23 18:22:12 +0000137 normal_window_type_atom_ = XInternAtom(
sergeyu@chromium.org91685dc2013-10-12 22:40:05 +0000138 display(), "_NET_WM_WINDOW_TYPE_NORMAL", True);
sergeyu@chromium.orge562e022013-08-23 18:22:12 +0000139
140 int event_base, error_base, major_version, minor_version;
sergeyu@chromium.org91685dc2013-10-12 22:40:05 +0000141 if (XCompositeQueryExtension(display(), &event_base, &error_base) &&
142 XCompositeQueryVersion(display(), &major_version, &minor_version) &&
sergeyu@chromium.orge562e022013-08-23 18:22:12 +0000143 // XCompositeNameWindowPixmap() requires version 0.2
144 (major_version > 0 || minor_version >= 2)) {
145 has_composite_extension_ = true;
146 } else {
147 LOG(LS_INFO) << "Xcomposite extension not available or too old.";
148 }
149}
150
sergeyu@chromium.org91685dc2013-10-12 22:40:05 +0000151WindowCapturerLinux::~WindowCapturerLinux() {}
sergeyu@chromium.orge562e022013-08-23 18:22:12 +0000152
153bool WindowCapturerLinux::GetWindowList(WindowList* windows) {
sergeyu@chromium.orge562e022013-08-23 18:22:12 +0000154 WindowList result;
155
sergeyu@chromium.org91685dc2013-10-12 22:40:05 +0000156 XErrorTrap error_trap(display());
sergeyu@chromium.orge562e022013-08-23 18:22:12 +0000157
sergeyu@chromium.org91685dc2013-10-12 22:40:05 +0000158 int num_screens = XScreenCount(display());
sergeyu@chromium.orge562e022013-08-23 18:22:12 +0000159 for (int screen = 0; screen < num_screens; ++screen) {
sergeyu@chromium.org91685dc2013-10-12 22:40:05 +0000160 ::Window root_window = XRootWindow(display(), screen);
sergeyu@chromium.orge562e022013-08-23 18:22:12 +0000161 ::Window parent;
162 ::Window *children;
163 unsigned int num_children;
sergeyu@chromium.org91685dc2013-10-12 22:40:05 +0000164 int status = XQueryTree(display(), root_window, &root_window, &parent,
sergeyu@chromium.orge562e022013-08-23 18:22:12 +0000165 &children, &num_children);
166 if (status == 0) {
167 LOG(LS_ERROR) << "Failed to query for child windows for screen "
168 << screen;
169 continue;
170 }
171
172 for (unsigned int i = 0; i < num_children; ++i) {
173 // Iterate in reverse order to return windows from front to back.
174 ::Window app_window =
175 GetApplicationWindow(children[num_children - 1 - i]);
176 if (app_window && !IsDesktopElement(app_window)) {
177 Window w;
178 w.id = app_window;
179 if (GetWindowTitle(app_window, &w.title))
180 result.push_back(w);
181 }
182 }
183
184 if (children)
185 XFree(children);
186 }
187
188 windows->swap(result);
189
190 return true;
191}
192
193bool WindowCapturerLinux::SelectWindow(WindowId id) {
sergeyu@chromium.org91685dc2013-10-12 22:40:05 +0000194 if (!x_server_pixel_buffer_.Init(display(), id))
sergeyu@chromium.orge562e022013-08-23 18:22:12 +0000195 return false;
196
197 selected_window_ = id;
198
199 // In addition to needing X11 server-side support for Xcomposite, it actually
200 // needs to be turned on for the window. If the user has modern
201 // hardware/drivers but isn't using a compositing window manager, that won't
202 // be the case. Here we automatically turn it on.
203
204 // Redirect drawing to an offscreen buffer (ie, turn on compositing). X11
205 // remembers who has requested this and will turn it off for us when we exit.
sergeyu@chromium.org91685dc2013-10-12 22:40:05 +0000206 XCompositeRedirectWindow(display(), id, CompositeRedirectAutomatic);
sergeyu@chromium.orge562e022013-08-23 18:22:12 +0000207
208 return true;
209}
210
211void WindowCapturerLinux::Start(Callback* callback) {
212 assert(!callback_);
213 assert(callback);
214
215 callback_ = callback;
216}
217
218void WindowCapturerLinux::Capture(const DesktopRegion& region) {
219 if (!has_composite_extension_) {
220 // Without the Xcomposite extension we capture when the whole window is
221 // visible on screen and not covered by any other window. This is not
222 // something we want so instead, just bail out.
223 LOG(LS_INFO) << "No Xcomposite extension detected.";
224 callback_->OnCaptureCompleted(NULL);
225 return;
226 }
227
228 DesktopFrame* frame =
229 new BasicDesktopFrame(x_server_pixel_buffer_.window_size());
230
231 x_server_pixel_buffer_.Synchronize();
232 x_server_pixel_buffer_.CaptureRect(DesktopRect::MakeSize(frame->size()),
233 frame);
234
235 callback_->OnCaptureCompleted(frame);
236}
237
238::Window WindowCapturerLinux::GetApplicationWindow(::Window window) {
239 // Get WM_STATE property of the window.
sergeyu@chromium.org91685dc2013-10-12 22:40:05 +0000240 XWindowProperty<uint32_t> window_state(display(), window, wm_state_atom_);
sergeyu@chromium.orge562e022013-08-23 18:22:12 +0000241
242 // WM_STATE is considered to be set to WithdrawnState when it missing.
243 int32_t state = window_state.is_valid() ?
244 *window_state.data() : WithdrawnState;
245
246 if (state == NormalState) {
247 // Window has WM_STATE==NormalState. Return it.
248 return window;
249 } else if (state == IconicState) {
250 // Window is in minimized. Skip it.
251 return 0;
252 }
253
254 // If the window is in WithdrawnState then look at all of its children.
255 ::Window root, parent;
256 ::Window *children;
257 unsigned int num_children;
sergeyu@chromium.org91685dc2013-10-12 22:40:05 +0000258 if (!XQueryTree(display(), window, &root, &parent, &children,
sergeyu@chromium.orge562e022013-08-23 18:22:12 +0000259 &num_children)) {
260 LOG(LS_ERROR) << "Failed to query for child windows although window"
261 << "does not have a valid WM_STATE.";
262 return 0;
263 }
264 ::Window app_window = 0;
265 for (unsigned int i = 0; i < num_children; ++i) {
266 app_window = GetApplicationWindow(children[i]);
267 if (app_window)
268 break;
269 }
270
271 if (children)
272 XFree(children);
273 return app_window;
274}
275
276bool WindowCapturerLinux::IsDesktopElement(::Window window) {
277 if (window == 0)
278 return false;
279
280 // First look for _NET_WM_WINDOW_TYPE. The standard
281 // (http://standards.freedesktop.org/wm-spec/latest/ar01s05.html#id2760306)
282 // says this hint *should* be present on all windows, and we use the existence
283 // of _NET_WM_WINDOW_TYPE_NORMAL in the property to indicate a window is not
284 // a desktop element (that is, only "normal" windows should be shareable).
sergeyu@chromium.org91685dc2013-10-12 22:40:05 +0000285 XWindowProperty<uint32_t> window_type(display(), window, window_type_atom_);
sergeyu@chromium.orge562e022013-08-23 18:22:12 +0000286 if (window_type.is_valid() && window_type.size() > 0) {
287 uint32_t* end = window_type.data() + window_type.size();
288 bool is_normal = (end != std::find(
289 window_type.data(), end, normal_window_type_atom_));
290 return !is_normal;
291 }
292
293 // Fall back on using the hint.
294 XClassHint class_hint;
sergeyu@chromium.org91685dc2013-10-12 22:40:05 +0000295 Status status = XGetClassHint(display(), window, &class_hint);
sergeyu@chromium.orge562e022013-08-23 18:22:12 +0000296 bool result = false;
297 if (status == 0) {
298 // No hints, assume this is a normal application window.
299 return result;
300 }
301
302 if (strcmp("gnome-panel", class_hint.res_name) == 0 ||
303 strcmp("desktop_window", class_hint.res_name) == 0) {
304 result = true;
305 }
306 XFree(class_hint.res_name);
307 XFree(class_hint.res_class);
308 return result;
309}
310
311bool WindowCapturerLinux::GetWindowTitle(::Window window, std::string* title) {
312 int status;
313 bool result = false;
314 XTextProperty window_name;
315 window_name.value = NULL;
316 if (window) {
sergeyu@chromium.org91685dc2013-10-12 22:40:05 +0000317 status = XGetWMName(display(), window, &window_name);
sergeyu@chromium.orge562e022013-08-23 18:22:12 +0000318 if (status && window_name.value && window_name.nitems) {
319 int cnt;
320 char **list = NULL;
sergeyu@chromium.org91685dc2013-10-12 22:40:05 +0000321 status = Xutf8TextPropertyToTextList(display(), &window_name, &list,
sergeyu@chromium.orge562e022013-08-23 18:22:12 +0000322 &cnt);
323 if (status >= Success && cnt && *list) {
324 if (cnt > 1) {
325 LOG(LS_INFO) << "Window has " << cnt
326 << " text properties, only using the first one.";
327 }
328 *title = *list;
329 result = true;
330 }
331 if (list)
332 XFreeStringList(list);
333 }
334 if (window_name.value)
335 XFree(window_name.value);
336 }
337 return result;
338}
339
340} // namespace
341
342// static
sergeyu@chromium.org91685dc2013-10-12 22:40:05 +0000343WindowCapturer* WindowCapturer::Create(const DesktopCaptureOptions& options) {
344 if (!options.x_display())
345 return NULL;
346 return new WindowCapturerLinux(options);
sergeyu@chromium.orge562e022013-08-23 18:22:12 +0000347}
348
349} // namespace webrtc