Home | History | Annotate | Download | only in desktop_capture
      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:
     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   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:
    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 
    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   frame->mutable_updated_region()->SetRect(
    306       DesktopRect::MakeSize(frame->size()));
    307 
    308   callback_->OnCaptureCompleted(frame);
    309 }
    310 
    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 
    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 
    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 
    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
    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