Line data Source code
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 <assert.h>
12 : #include <string.h>
13 : #include <X11/Xatom.h>
14 : #include <X11/extensions/Xcomposite.h>
15 : #include <X11/extensions/Xrender.h>
16 : #include <X11/Xutil.h>
17 :
18 : #include <algorithm>
19 :
20 : #include "webrtc/base/constructormagic.h"
21 : #include "webrtc/base/scoped_ref_ptr.h"
22 : #include "webrtc/modules/desktop_capture/desktop_capturer.h"
23 : #include "webrtc/modules/desktop_capture/desktop_capture_options.h"
24 : #include "webrtc/modules/desktop_capture/desktop_frame.h"
25 : #include "webrtc/modules/desktop_capture/x11/shared_x_display.h"
26 : #include "webrtc/modules/desktop_capture/x11/x_error_trap.h"
27 : #include "webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h"
28 : #include "webrtc/system_wrappers/include/logging.h"
29 : #include "webrtc/modules/desktop_capture/x11/shared_x_util.h"
30 :
31 : namespace webrtc {
32 :
33 : namespace {
34 :
35 : class WindowCapturerLinux : public DesktopCapturer,
36 : public SharedXDisplay::XEventHandler {
37 : public:
38 : WindowCapturerLinux(const DesktopCaptureOptions& options);
39 : ~WindowCapturerLinux() override;
40 :
41 : // DesktopCapturer interface.
42 : void Start(Callback* callback) override;
43 : void Stop() override;
44 : void CaptureFrame() override;
45 : bool GetSourceList(SourceList* sources) override;
46 : bool SelectSource(SourceId id) override;
47 : bool FocusOnSelectedSource() override;
48 :
49 : // SharedXDisplay::XEventHandler interface.
50 : bool HandleXEvent(const XEvent& event) override;
51 :
52 : private:
53 0 : Display* display() { return x_display_->display(); }
54 :
55 : // Iterates through |window| hierarchy to find first visible window, i.e. one
56 : // that has WM_STATE property set to NormalState.
57 : // See http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.3.1 .
58 : ::Window GetApplicationWindow(::Window window);
59 :
60 : // Returns true if the |window| is a desktop element.
61 : bool IsDesktopElement(::Window window);
62 :
63 : // Returns window title for the specified X |window|.
64 : bool GetWindowTitle(::Window window, std::string* title);
65 :
66 : // Returns the id of the owning process.
67 : int GetWindowProcessID(::Window window);
68 :
69 : Callback* callback_ = nullptr;
70 :
71 : rtc::scoped_refptr<SharedXDisplay> x_display_;
72 :
73 : Atom wm_state_atom_;
74 : Atom window_type_atom_;
75 : Atom normal_window_type_atom_;
76 : bool has_composite_extension_ = false;
77 :
78 : ::Window selected_window_ = 0;
79 : XServerPixelBuffer x_server_pixel_buffer_;
80 :
81 : RTC_DISALLOW_COPY_AND_ASSIGN(WindowCapturerLinux);
82 : };
83 :
84 0 : WindowCapturerLinux::WindowCapturerLinux(const DesktopCaptureOptions& options)
85 0 : : x_display_(options.x_display()) {
86 : // Create Atoms so we don't need to do it every time they are used.
87 0 : wm_state_atom_ = XInternAtom(display(), "WM_STATE", True);
88 0 : window_type_atom_ = XInternAtom(display(), "_NET_WM_WINDOW_TYPE", True);
89 0 : normal_window_type_atom_ = XInternAtom(
90 : display(), "_NET_WM_WINDOW_TYPE_NORMAL", True);
91 :
92 : int event_base, error_base, major_version, minor_version;
93 0 : if (XCompositeQueryExtension(display(), &event_base, &error_base) &&
94 0 : XCompositeQueryVersion(display(), &major_version, &minor_version) &&
95 : // XCompositeNameWindowPixmap() requires version 0.2
96 0 : (major_version > 0 || minor_version >= 2)) {
97 0 : has_composite_extension_ = true;
98 : } else {
99 0 : LOG(LS_INFO) << "Xcomposite extension not available or too old.";
100 : }
101 :
102 0 : x_display_->AddEventHandler(ConfigureNotify, this);
103 0 : }
104 :
105 0 : WindowCapturerLinux::~WindowCapturerLinux() {
106 0 : x_display_->RemoveEventHandler(ConfigureNotify, this);
107 0 : }
108 :
109 0 : bool WindowCapturerLinux::GetSourceList(SourceList* sources) {
110 0 : SourceList result;
111 :
112 0 : XErrorTrap error_trap(display());
113 :
114 0 : int num_screens = XScreenCount(display());
115 0 : for (int screen = 0; screen < num_screens; ++screen) {
116 0 : ::Window root_window = XRootWindow(display(), screen);
117 : ::Window parent;
118 : ::Window *children;
119 : unsigned int num_children;
120 0 : int status = XQueryTree(display(), root_window, &root_window, &parent,
121 0 : &children, &num_children);
122 0 : if (status == 0) {
123 0 : LOG(LS_ERROR) << "Failed to query for child windows for screen "
124 0 : << screen;
125 0 : continue;
126 : }
127 :
128 0 : for (unsigned int i = 0; i < num_children; ++i) {
129 : // Iterate in reverse order to return windows from front to back.
130 : ::Window app_window =
131 0 : GetApplicationWindow(children[num_children - 1 - i]);
132 0 : if (app_window && !IsDesktopElement(app_window)) {
133 0 : Source w;
134 0 : w.id = app_window;
135 :
136 0 : unsigned int processId = GetWindowProcessID(app_window);
137 0 : w.pid = (pid_t)processId;
138 :
139 : XWindowAttributes window_attr;
140 0 : if(!XGetWindowAttributes(display(),w.id,&window_attr)){
141 0 : LOG(LS_ERROR)<<"Bad request for attributes for window ID:"<<w.id;
142 0 : continue;
143 : }
144 0 : if((window_attr.width <= 0) || (window_attr.height <=0)){
145 0 : continue;
146 : }
147 :
148 0 : if (GetWindowTitle(app_window, &w.title))
149 0 : result.push_back(w);
150 : }
151 : }
152 :
153 0 : if (children)
154 0 : XFree(children);
155 : }
156 :
157 0 : sources->swap(result);
158 :
159 0 : return true;
160 : }
161 :
162 0 : bool WindowCapturerLinux::SelectSource(SourceId id) {
163 0 : if (!x_server_pixel_buffer_.Init(display(), id))
164 0 : return false;
165 :
166 : // Tell the X server to send us window resizing events.
167 0 : XSelectInput(display(), id, StructureNotifyMask);
168 :
169 0 : selected_window_ = id;
170 :
171 : // In addition to needing X11 server-side support for Xcomposite, it actually
172 : // needs to be turned on for the window. If the user has modern
173 : // hardware/drivers but isn't using a compositing window manager, that won't
174 : // be the case. Here we automatically turn it on.
175 :
176 : // Redirect drawing to an offscreen buffer (ie, turn on compositing). X11
177 : // remembers who has requested this and will turn it off for us when we exit.
178 0 : XCompositeRedirectWindow(display(), id, CompositeRedirectAutomatic);
179 :
180 0 : return true;
181 : }
182 :
183 0 : bool WindowCapturerLinux::FocusOnSelectedSource() {
184 0 : if (!selected_window_)
185 0 : return false;
186 :
187 : unsigned int num_children;
188 : ::Window* children;
189 : ::Window parent;
190 : ::Window root;
191 : // Find the root window to pass event to.
192 0 : int status = XQueryTree(
193 0 : display(), selected_window_, &root, &parent, &children, &num_children);
194 0 : if (status == 0) {
195 0 : LOG(LS_ERROR) << "Failed to query for the root window.";
196 0 : return false;
197 : }
198 :
199 0 : if (children)
200 0 : XFree(children);
201 :
202 0 : XRaiseWindow(display(), selected_window_);
203 :
204 : // Some window managers (e.g., metacity in GNOME) consider it illegal to
205 : // raise a window without also giving it input focus with
206 : // _NET_ACTIVE_WINDOW, so XRaiseWindow() on its own isn't enough.
207 0 : Atom atom = XInternAtom(display(), "_NET_ACTIVE_WINDOW", True);
208 0 : if (atom != None) {
209 : XEvent xev;
210 0 : xev.xclient.type = ClientMessage;
211 0 : xev.xclient.serial = 0;
212 0 : xev.xclient.send_event = True;
213 0 : xev.xclient.window = selected_window_;
214 0 : xev.xclient.message_type = atom;
215 :
216 : // The format member is set to 8, 16, or 32 and specifies whether the
217 : // data should be viewed as a list of bytes, shorts, or longs.
218 0 : xev.xclient.format = 32;
219 :
220 0 : memset(xev.xclient.data.l, 0, sizeof(xev.xclient.data.l));
221 :
222 0 : XSendEvent(display(),
223 : root,
224 : False,
225 : SubstructureRedirectMask | SubstructureNotifyMask,
226 0 : &xev);
227 : }
228 0 : XFlush(display());
229 0 : return true;
230 : }
231 :
232 0 : void WindowCapturerLinux::Start(Callback* callback) {
233 0 : assert(!callback_);
234 0 : assert(callback);
235 :
236 0 : callback_ = callback;
237 0 : }
238 :
239 0 : void WindowCapturerLinux::Stop() {
240 0 : callback_ = NULL;
241 0 : }
242 :
243 0 : void WindowCapturerLinux::CaptureFrame() {
244 0 : x_display_->ProcessPendingXEvents();
245 :
246 0 : if (!x_server_pixel_buffer_.IsWindowValid()) {
247 0 : LOG(LS_INFO) << "The window is no longer valid.";
248 0 : callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
249 0 : return;
250 : }
251 :
252 0 : if (!has_composite_extension_) {
253 : // Without the Xcomposite extension we capture when the whole window is
254 : // visible on screen and not covered by any other window. This is not
255 : // something we want so instead, just bail out.
256 0 : LOG(LS_INFO) << "No Xcomposite extension detected.";
257 0 : callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
258 0 : return;
259 : }
260 :
261 : std::unique_ptr<DesktopFrame> frame(
262 0 : new BasicDesktopFrame(x_server_pixel_buffer_.window_size()));
263 :
264 0 : x_server_pixel_buffer_.Synchronize();
265 0 : if (!x_server_pixel_buffer_.CaptureRect(DesktopRect::MakeSize(frame->size()),
266 : frame.get())) {
267 0 : callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
268 0 : return;
269 : }
270 :
271 0 : frame->mutable_updated_region()->SetRect(
272 0 : DesktopRect::MakeSize(frame->size()));
273 :
274 0 : callback_->OnCaptureResult(Result::SUCCESS, std::move(frame));
275 : }
276 :
277 0 : bool WindowCapturerLinux::HandleXEvent(const XEvent& event) {
278 0 : if (event.type == ConfigureNotify) {
279 0 : XConfigureEvent xce = event.xconfigure;
280 0 : if (!DesktopSize(xce.width, xce.height).equals(
281 : x_server_pixel_buffer_.window_size())) {
282 0 : if (!x_server_pixel_buffer_.Init(display(), selected_window_)) {
283 0 : LOG(LS_ERROR) << "Failed to initialize pixel buffer after resizing.";
284 : }
285 0 : return true;
286 : }
287 : }
288 0 : return false;
289 : }
290 :
291 0 : ::Window WindowCapturerLinux::GetApplicationWindow(::Window window) {
292 : // Get WM_STATE property of the window.
293 0 : XWindowProperty<uint32_t> window_state(display(), window, wm_state_atom_);
294 :
295 : // WM_STATE is considered to be set to WithdrawnState when it missing.
296 0 : int32_t state = window_state.is_valid() ?
297 0 : *window_state.data() : WithdrawnState;
298 :
299 0 : if (state == NormalState) {
300 : // Window has WM_STATE==NormalState. Return it.
301 0 : return window;
302 0 : } else if (state == IconicState) {
303 : // Window is in minimized. Skip it.
304 0 : return 0;
305 : }
306 :
307 : // If the window is in WithdrawnState then look at all of its children.
308 : ::Window root, parent;
309 : ::Window *children;
310 : unsigned int num_children;
311 0 : if (!XQueryTree(display(), window, &root, &parent, &children,
312 : &num_children)) {
313 0 : LOG(LS_ERROR) << "Failed to query for child windows although window"
314 0 : << "does not have a valid WM_STATE.";
315 0 : return 0;
316 : }
317 0 : ::Window app_window = 0;
318 0 : for (unsigned int i = 0; i < num_children; ++i) {
319 0 : app_window = GetApplicationWindow(children[i]);
320 0 : if (app_window)
321 0 : break;
322 : }
323 :
324 0 : if (children)
325 0 : XFree(children);
326 0 : return app_window;
327 : }
328 :
329 0 : bool WindowCapturerLinux::IsDesktopElement(::Window window) {
330 0 : if (window == 0)
331 0 : return false;
332 :
333 : // First look for _NET_WM_WINDOW_TYPE. The standard
334 : // (http://standards.freedesktop.org/wm-spec/latest/ar01s05.html#id2760306)
335 : // says this hint *should* be present on all windows, and we use the existence
336 : // of _NET_WM_WINDOW_TYPE_NORMAL in the property to indicate a window is not
337 : // a desktop element (that is, only "normal" windows should be shareable).
338 0 : XWindowProperty<uint32_t> window_type(display(), window, window_type_atom_);
339 0 : if (window_type.is_valid() && window_type.size() > 0) {
340 0 : uint32_t* end = window_type.data() + window_type.size();
341 0 : bool is_normal = (end != std::find(
342 0 : window_type.data(), end, normal_window_type_atom_));
343 0 : return !is_normal;
344 : }
345 :
346 : // Fall back on using the hint.
347 : XClassHint class_hint;
348 0 : Status status = XGetClassHint(display(), window, &class_hint);
349 0 : bool result = false;
350 0 : if (status == 0) {
351 : // No hints, assume this is a normal application window.
352 0 : return result;
353 : }
354 :
355 0 : if (strcmp("gnome-panel", class_hint.res_name) == 0 ||
356 0 : strcmp("desktop_window", class_hint.res_name) == 0) {
357 0 : result = true;
358 : }
359 0 : XFree(class_hint.res_name);
360 0 : XFree(class_hint.res_class);
361 0 : return result;
362 : }
363 :
364 0 : bool WindowCapturerLinux::GetWindowTitle(::Window window, std::string* title) {
365 : int status;
366 0 : bool result = false;
367 : XTextProperty window_name;
368 0 : window_name.value = nullptr;
369 0 : if (window) {
370 0 : status = XGetWMName(display(), window, &window_name);
371 0 : if (status && window_name.value && window_name.nitems) {
372 : int cnt;
373 0 : char** list = nullptr;
374 0 : status = Xutf8TextPropertyToTextList(display(), &window_name, &list,
375 0 : &cnt);
376 0 : if (status >= Success && cnt && *list) {
377 0 : if (cnt > 1) {
378 0 : LOG(LS_INFO) << "Window has " << cnt
379 0 : << " text properties, only using the first one.";
380 : }
381 0 : *title = *list;
382 0 : result = true;
383 : }
384 0 : if (list)
385 0 : XFreeStringList(list);
386 : }
387 0 : if (window_name.value)
388 0 : XFree(window_name.value);
389 : }
390 0 : return result;
391 : }
392 :
393 : } // namespace
394 :
395 0 : int WindowCapturerLinux::GetWindowProcessID(::Window window) {
396 : // Get _NET_WM_PID property of the window.
397 0 : Atom process_atom = XInternAtom(display(), "_NET_WM_PID", True);
398 0 : XWindowProperty<uint32_t> process_id(display(), window, process_atom);
399 :
400 0 : return process_id.is_valid() ? *process_id.data() : 0;
401 : }
402 :
403 : // static
404 0 : std::unique_ptr<DesktopCapturer> DesktopCapturer::CreateRawWindowCapturer(
405 : const DesktopCaptureOptions& options) {
406 0 : if (!options.x_display())
407 0 : return nullptr;
408 0 : return std::unique_ptr<DesktopCapturer>(new WindowCapturerLinux(options));
409 : }
410 :
411 : } // namespace webrtc
|