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