Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / third_party / webrtc / modules / desktop_capture / window_capturer_x11.cc
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/modules/desktop_capture/desktop_capture_options.h"
23 #include "webrtc/modules/desktop_capture/desktop_frame.h"
24 #include "webrtc/modules/desktop_capture/x11/shared_x_display.h"
25 #include "webrtc/modules/desktop_capture/x11/x_error_trap.h"
26 #include "webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h"
27 #include "webrtc/system_wrappers/interface/logging.h"
28 #include "webrtc/system_wrappers/interface/scoped_ptr.h"
29 #include "webrtc/system_wrappers/interface/scoped_refptr.h"
30
31 namespace webrtc {
32
33 namespace {
34
35 // Convenience wrapper for XGetWindowProperty() results.
36 template <class PropertyType>
37 class XWindowProperty {
38  public:
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
63   ~XWindowProperty() {
64     if (data_)
65       XFree(data_);
66   }
67
68   // True if we got properly value successfully.
69   bool is_valid() const { return is_valid_; }
70
71   // Size and value of the property.
72   size_t size() const { return size_; }
73   const PropertyType* data() const {
74     return reinterpret_cast<PropertyType*>(data_);
75   }
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   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   virtual bool GetWindowList(WindowList* windows) OVERRIDE;
96   virtual bool SelectWindow(WindowId id) OVERRIDE;
97   virtual bool BringSelectedWindowToFront() OVERRIDE;
98
99   // DesktopCapturer interface.
100   virtual void Start(Callback* callback) OVERRIDE;
101   virtual void Capture(const DesktopRegion& region) OVERRIDE;
102
103   // SharedXDisplay::XEventHandler interface.
104   virtual bool HandleXEvent(const XEvent& event) OVERRIDE;
105
106  private:
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   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   DISALLOW_COPY_AND_ASSIGN(WindowCapturerLinux);
133 };
134
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
159 WindowCapturerLinux::~WindowCapturerLinux() {
160   x_display_->RemoveEventHandler(ConfigureNotify, this);
161 }
162
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
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
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
273 void WindowCapturerLinux::Start(Callback* callback) {
274   assert(!callback_);
275   assert(callback);
276
277   callback_ = callback;
278 }
279
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   callback_->OnCaptureCompleted(frame);
306 }
307
308 bool WindowCapturerLinux::HandleXEvent(const XEvent& event) {
309   if (event.type == ConfigureNotify) {
310     XConfigureEvent xce = event.xconfigure;
311     if (!DesktopSize(xce.width, xce.height).equals(
312             x_server_pixel_buffer_.window_size())) {
313       if (!x_server_pixel_buffer_.Init(display(), selected_window_)) {
314         LOG(LS_ERROR) << "Failed to initialize pixel buffer after resizing.";
315       }
316       return true;
317     }
318   }
319   return false;
320 }
321
322 ::Window WindowCapturerLinux::GetApplicationWindow(::Window window) {
323   // Get WM_STATE property of the window.
324   XWindowProperty<uint32_t> window_state(display(), window, wm_state_atom_);
325
326   // WM_STATE is considered to be set to WithdrawnState when it missing.
327   int32_t state = window_state.is_valid() ?
328       *window_state.data() : WithdrawnState;
329
330   if (state == NormalState) {
331     // Window has WM_STATE==NormalState. Return it.
332     return window;
333   } else if (state == IconicState) {
334     // Window is in minimized. Skip it.
335     return 0;
336   }
337
338   // If the window is in WithdrawnState then look at all of its children.
339   ::Window root, parent;
340   ::Window *children;
341   unsigned int num_children;
342   if (!XQueryTree(display(), window, &root, &parent, &children,
343                   &num_children)) {
344     LOG(LS_ERROR) << "Failed to query for child windows although window"
345                   << "does not have a valid WM_STATE.";
346     return 0;
347   }
348   ::Window app_window = 0;
349   for (unsigned int i = 0; i < num_children; ++i) {
350     app_window = GetApplicationWindow(children[i]);
351     if (app_window)
352       break;
353   }
354
355   if (children)
356     XFree(children);
357   return app_window;
358 }
359
360 bool WindowCapturerLinux::IsDesktopElement(::Window window) {
361   if (window == 0)
362     return false;
363
364   // First look for _NET_WM_WINDOW_TYPE. The standard
365   // (http://standards.freedesktop.org/wm-spec/latest/ar01s05.html#id2760306)
366   // says this hint *should* be present on all windows, and we use the existence
367   // of _NET_WM_WINDOW_TYPE_NORMAL in the property to indicate a window is not
368   // a desktop element (that is, only "normal" windows should be shareable).
369   XWindowProperty<uint32_t> window_type(display(), window, window_type_atom_);
370   if (window_type.is_valid() && window_type.size() > 0) {
371     uint32_t* end = window_type.data() + window_type.size();
372     bool is_normal = (end != std::find(
373         window_type.data(), end, normal_window_type_atom_));
374     return !is_normal;
375   }
376
377   // Fall back on using the hint.
378   XClassHint class_hint;
379   Status status = XGetClassHint(display(), window, &class_hint);
380   bool result = false;
381   if (status == 0) {
382     // No hints, assume this is a normal application window.
383     return result;
384   }
385
386   if (strcmp("gnome-panel", class_hint.res_name) == 0 ||
387       strcmp("desktop_window", class_hint.res_name) == 0) {
388     result = true;
389   }
390   XFree(class_hint.res_name);
391   XFree(class_hint.res_class);
392   return result;
393 }
394
395 bool WindowCapturerLinux::GetWindowTitle(::Window window, std::string* title) {
396   int status;
397   bool result = false;
398   XTextProperty window_name;
399   window_name.value = NULL;
400   if (window) {
401     status = XGetWMName(display(), window, &window_name);
402     if (status && window_name.value && window_name.nitems) {
403       int cnt;
404       char **list = NULL;
405       status = Xutf8TextPropertyToTextList(display(), &window_name, &list,
406                                            &cnt);
407       if (status >= Success && cnt && *list) {
408         if (cnt > 1) {
409           LOG(LS_INFO) << "Window has " << cnt
410                        << " text properties, only using the first one.";
411         }
412         *title = *list;
413         result = true;
414       }
415       if (list)
416         XFreeStringList(list);
417     }
418     if (window_name.value)
419       XFree(window_name.value);
420   }
421   return result;
422 }
423
424 }  // namespace
425
426 // static
427 WindowCapturer* WindowCapturer::Create(const DesktopCaptureOptions& options) {
428   if (!options.x_display())
429     return NULL;
430   return new WindowCapturerLinux(options);
431 }
432
433 }  // namespace webrtc