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