window_capturer_x11.cc (8949B)
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 "modules/desktop_capture/linux/x11/window_capturer_x11.h" 12 13 #include <X11/X.h> 14 #include <X11/Xlib.h> 15 #include <X11/Xutil.h> 16 #include <X11/extensions/Xcomposite.h> 17 #include <X11/extensions/composite.h> 18 19 #include <cstring> 20 #include <memory> 21 #include <string> 22 #include <utility> 23 24 #include "api/scoped_refptr.h" 25 #include "modules/desktop_capture/desktop_capture_options.h" 26 #include "modules/desktop_capture/desktop_capture_types.h" 27 #include "modules/desktop_capture/desktop_capturer.h" 28 #include "modules/desktop_capture/desktop_frame.h" 29 #include "modules/desktop_capture/desktop_geometry.h" 30 #include "modules/desktop_capture/desktop_region.h" 31 #include "modules/desktop_capture/linux/x11/shared_x_display.h" 32 #include "modules/desktop_capture/linux/x11/window_finder_x11.h" 33 #include "modules/desktop_capture/linux/x11/window_list_utils.h" 34 #include "rtc_base/checks.h" 35 #include "rtc_base/logging.h" 36 #include "rtc_base/trace_event.h" 37 38 namespace webrtc { 39 40 WindowCapturerX11::WindowCapturerX11(const DesktopCaptureOptions& options) 41 : x_display_(options.x_display()), 42 atom_cache_(display()), 43 window_finder_(&atom_cache_) { 44 int event_base, error_base, major_version, minor_version; 45 if (XCompositeQueryExtension(display(), &event_base, &error_base) && 46 XCompositeQueryVersion(display(), &major_version, &minor_version) && 47 // XCompositeNameWindowPixmap() requires version 0.2 48 (major_version > 0 || minor_version >= 2)) { 49 has_composite_extension_ = true; 50 } else { 51 RTC_LOG(LS_INFO) << "Xcomposite extension not available or too old."; 52 } 53 54 x_display_->AddEventHandler(ConfigureNotify, this); 55 } 56 57 WindowCapturerX11::~WindowCapturerX11() { 58 x_display_->RemoveEventHandler(ConfigureNotify, this); 59 } 60 61 bool WindowCapturerX11::GetSourceList(SourceList* sources) { 62 return GetWindowList(&atom_cache_, [this, sources](::Window window) { 63 Source w; 64 w.id = window; 65 w.pid = (pid_t)GetWindowProcessID(window); 66 if (this->GetWindowTitle(window, &w.title)) { 67 sources->push_back(w); 68 } 69 return true; 70 }); 71 } 72 73 bool WindowCapturerX11::SelectSource(SourceId id) { 74 if (!x_server_pixel_buffer_.Init(&atom_cache_, id)) 75 return false; 76 77 // Tell the X server to send us window resizing events. 78 XSelectInput(display(), id, StructureNotifyMask); 79 80 selected_window_ = id; 81 82 // In addition to needing X11 server-side support for Xcomposite, it actually 83 // needs to be turned on for the window. If the user has modern 84 // hardware/drivers but isn't using a compositing window manager, that won't 85 // be the case. Here we automatically turn it on. 86 87 // Redirect drawing to an offscreen buffer (ie, turn on compositing). X11 88 // remembers who has requested this and will turn it off for us when we exit. 89 XCompositeRedirectWindow(display(), id, CompositeRedirectAutomatic); 90 91 return true; 92 } 93 94 bool WindowCapturerX11::FocusOnSelectedSource() { 95 if (!selected_window_) 96 return false; 97 98 unsigned int num_children; 99 ::Window* children; 100 ::Window parent; 101 ::Window root; 102 // Find the root window to pass event to. 103 int status = XQueryTree(display(), selected_window_, &root, &parent, 104 &children, &num_children); 105 if (status == 0) { 106 RTC_LOG(LS_ERROR) << "Failed to query for the root window."; 107 return false; 108 } 109 110 if (children) 111 XFree(children); 112 113 XRaiseWindow(display(), selected_window_); 114 115 // Some window managers (e.g., metacity in GNOME) consider it illegal to 116 // raise a window without also giving it input focus with 117 // _NET_ACTIVE_WINDOW, so XRaiseWindow() on its own isn't enough. 118 Atom atom = XInternAtom(display(), "_NET_ACTIVE_WINDOW", True); 119 if (atom != None) { 120 XEvent xev; 121 xev.xclient.type = ClientMessage; 122 xev.xclient.serial = 0; 123 xev.xclient.send_event = True; 124 xev.xclient.window = selected_window_; 125 xev.xclient.message_type = atom; 126 127 // The format member is set to 8, 16, or 32 and specifies whether the 128 // data should be viewed as a list of bytes, shorts, or longs. 129 xev.xclient.format = 32; 130 131 memset(xev.xclient.data.l, 0, sizeof(xev.xclient.data.l)); 132 133 XSendEvent(display(), root, False, 134 SubstructureRedirectMask | SubstructureNotifyMask, &xev); 135 } 136 XFlush(display()); 137 return true; 138 } 139 140 void WindowCapturerX11::Start(Callback* callback) { 141 RTC_DCHECK(!callback_); 142 RTC_DCHECK(callback); 143 144 callback_ = callback; 145 } 146 147 void WindowCapturerX11::CaptureFrame() { 148 TRACE_EVENT0("webrtc", "WindowCapturerX11::CaptureFrame"); 149 150 if (!x_server_pixel_buffer_.IsWindowValid()) { 151 RTC_LOG(LS_ERROR) << "The window is no longer valid."; 152 callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); 153 return; 154 } 155 156 x_display_->ProcessPendingXEvents(); 157 158 if (!has_composite_extension_) { 159 // Without the Xcomposite extension we capture when the whole window is 160 // visible on screen and not covered by any other window. This is not 161 // something we want so instead, just bail out. 162 RTC_LOG(LS_ERROR) << "No Xcomposite extension detected."; 163 callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); 164 return; 165 } 166 167 if (GetWindowState(&atom_cache_, selected_window_) == IconicState) { 168 // Window is in minimized. Return a 1x1 frame as same as OSX/Win does. 169 std::unique_ptr<DesktopFrame> frame( 170 new BasicDesktopFrame(DesktopSize(1, 1), FOURCC_ARGB)); 171 callback_->OnCaptureResult(Result::SUCCESS, std::move(frame)); 172 return; 173 } 174 175 std::unique_ptr<DesktopFrame> frame( 176 new BasicDesktopFrame(x_server_pixel_buffer_.window_size(), FOURCC_ARGB)); 177 178 x_server_pixel_buffer_.Synchronize(); 179 if (!x_server_pixel_buffer_.CaptureRect(DesktopRect::MakeSize(frame->size()), 180 frame.get())) { 181 RTC_LOG(LS_WARNING) << "Temporarily failed to capture winodw."; 182 callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); 183 return; 184 } 185 186 frame->mutable_updated_region()->SetRect( 187 DesktopRect::MakeSize(frame->size())); 188 frame->set_top_left(x_server_pixel_buffer_.window_rect().top_left()); 189 frame->set_capturer_id(DesktopCapturerId::kX11CapturerLinux); 190 191 callback_->OnCaptureResult(Result::SUCCESS, std::move(frame)); 192 } 193 194 bool WindowCapturerX11::IsOccluded(const DesktopVector& pos) { 195 return window_finder_.GetWindowUnderPoint(pos) != 196 static_cast<WindowId>(selected_window_); 197 } 198 199 bool WindowCapturerX11::HandleXEvent(const XEvent& event) { 200 if (event.type == ConfigureNotify) { 201 XConfigureEvent xce = event.xconfigure; 202 if (xce.window == selected_window_) { 203 if (!DesktopRectFromXAttributes(xce).equals( 204 x_server_pixel_buffer_.window_rect())) { 205 if (!x_server_pixel_buffer_.Init(&atom_cache_, selected_window_)) { 206 RTC_LOG(LS_ERROR) 207 << "Failed to initialize pixel buffer after resizing."; 208 } 209 } 210 } 211 } 212 213 // Always returns false, so other observers can still receive the events. 214 return false; 215 } 216 217 bool WindowCapturerX11::GetWindowTitle(::Window window, std::string* title) { 218 int status; 219 bool result = false; 220 XTextProperty window_name; 221 window_name.value = nullptr; 222 if (window) { 223 status = XGetWMName(display(), window, &window_name); 224 if (status && window_name.value && window_name.nitems) { 225 int cnt; 226 char** list = nullptr; 227 status = 228 Xutf8TextPropertyToTextList(display(), &window_name, &list, &cnt); 229 if (status >= Success && cnt && *list) { 230 if (cnt > 1) { 231 RTC_LOG(LS_INFO) << "Window has " << cnt 232 << " text properties, only using the first one."; 233 } 234 *title = *list; 235 result = true; 236 } 237 if (list) 238 XFreeStringList(list); 239 } 240 if (window_name.value) 241 XFree(window_name.value); 242 } 243 return result; 244 } 245 246 int WindowCapturerX11::GetWindowProcessID(::Window window) { 247 // Get _NET_WM_PID property of the window. 248 Atom process_atom = XInternAtom(display(), "_NET_WM_PID", True); 249 XWindowProperty<uint32_t> process_id(display(), window, process_atom); 250 251 return process_id.is_valid() ? *process_id.data() : 0; 252 } 253 254 // static 255 std::unique_ptr<DesktopCapturer> WindowCapturerX11::CreateRawWindowCapturer( 256 const DesktopCaptureOptions& options) { 257 if (!options.x_display()) 258 return nullptr; 259 260 RTC_LOG(LS_INFO) 261 << "video capture: WindowCapturerX11::CreateRawWindowCapturer creates " 262 "DesktopCapturer of type WindowCapturerX11"; 263 return std::unique_ptr<DesktopCapturer>(new WindowCapturerX11(options)); 264 } 265 266 } // namespace webrtc