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