• 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 "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