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 #include "webrtc/modules/desktop_capture/window_capturer.h"
11 #include "webrtc/modules/desktop_capture/app_capturer.h"
12 #include "webrtc/modules/desktop_capture/screen_capturer.h"
13 #include "webrtc/modules/desktop_capture/shared_desktop_frame.h"
14 #include "webrtc/modules/desktop_capture/x11/shared_x_util.h"
15 
16 #include <assert.h>
17 #include <string.h>
18 #include <X11/Xatom.h>
19 #include <X11/extensions/Xcomposite.h>
20 #include <X11/extensions/Xrender.h>
21 #include <X11/Xutil.h>
22 #include <X11/Xregion.h>
23 
24 #include <algorithm>
25 
26 #include "webrtc/modules/desktop_capture/desktop_capture_options.h"
27 #include "webrtc/modules/desktop_capture/desktop_frame.h"
28 #include "webrtc/modules/desktop_capture/x11/shared_x_display.h"
29 #include "webrtc/modules/desktop_capture/x11/x_error_trap.h"
30 #include "webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h"
31 #include "webrtc/system_wrappers/interface/logging.h"
32 #include "webrtc/base/scoped_ptr.h"
33 #include "webrtc/system_wrappers/interface/scoped_refptr.h"
34 
35 namespace webrtc {
36 
37 namespace {
38 
39 class WindowsCapturerProxy : DesktopCapturer::Callback {
40 public:
WindowsCapturerProxy()41   WindowsCapturerProxy() : window_capturer_(WindowCapturer::Create()) {
42     window_capturer_->Start(this);
43   }
~WindowsCapturerProxy()44   ~WindowsCapturerProxy() {}
SelectWindow(WindowId windowId)45   void SelectWindow(WindowId windowId) { window_capturer_->SelectWindow(windowId); }
GetFrame()46   rtc::scoped_ptr<DesktopFrame>& GetFrame() { return frame_; }
Capture(const DesktopRegion & region)47   void Capture(const DesktopRegion& region) { window_capturer_->Capture(region); }
48 
49   // Callback interface
CreateSharedMemory(size_t)50   virtual SharedMemory *CreateSharedMemory(size_t) override { return NULL; }
OnCaptureCompleted(DesktopFrame * frame)51   virtual void OnCaptureCompleted(DesktopFrame *frame) override { frame_.reset(frame); }
52 
53 private:
54   rtc::scoped_ptr<WindowCapturer> window_capturer_;
55   rtc::scoped_ptr<DesktopFrame> frame_;
56 };
57 
58 class ScreenCapturerProxy : DesktopCapturer::Callback {
59 public:
ScreenCapturerProxy()60   ScreenCapturerProxy() : screen_capturer_(ScreenCapturer::Create()) {
61     screen_capturer_->SelectScreen(kFullDesktopScreenId);
62     screen_capturer_->Start(this);
63   }
Capture(const DesktopRegion & region)64   void Capture(const DesktopRegion& region) { screen_capturer_->Capture(region); }
GetFrame()65   rtc::scoped_ptr<DesktopFrame>& GetFrame() { return frame_; }
66 
67   // Callback interface
CreateSharedMemory(size_t)68   virtual SharedMemory *CreateSharedMemory(size_t) override { return NULL; }
OnCaptureCompleted(DesktopFrame * frame)69   virtual void OnCaptureCompleted(DesktopFrame *frame) override { frame_.reset(frame); }
70 protected:
71   rtc::scoped_ptr<ScreenCapturer> screen_capturer_;
72   rtc::scoped_ptr<DesktopFrame> frame_;
73 };
74 
75 class AppCapturerLinux : public AppCapturer {
76 public:
77   AppCapturerLinux(const DesktopCaptureOptions& options);
78   virtual ~AppCapturerLinux();
79 
80   // AppCapturer interface.
81   virtual bool GetAppList(AppList* apps) override;
82   virtual bool SelectApp(ProcessId processId) override;
83   virtual bool BringAppToFront() override;
84 
85   // DesktopCapturer interface.
86   virtual void Start(Callback* callback) override;
87   virtual void Stop() override;
88   virtual void Capture(const DesktopRegion& region) override;
89 
90 protected:
GetDisplay()91   Display* GetDisplay() { return x_display_->display(); }
92   bool UpdateRegions();
93 
94   void CaptureWebRTC(const DesktopRegion& region);
95   void CaptureSample(const DesktopRegion& region);
96 
97   void FillDesktopFrameRegionWithColor(DesktopFrame* pDesktopFrame,Region rgn, uint32_t color);
98 private:
99   Callback* callback_;
100   ProcessId selected_process_;
101 
102   // Sample Mode
103   ScreenCapturerProxy screen_capturer_proxy_;
104   // Mask of foreground (non-app windows in front of selected)
105   Region rgn_mask_;
106   // Region of selected windows
107   Region rgn_visual_;
108   // Mask of background (desktop, non-app windows behind selected)
109   Region rgn_background_;
110 
111   // WebRtc Window mode
112   WindowsCapturerProxy window_capturer_proxy_;
113 
114   scoped_refptr<SharedXDisplay> x_display_;
115   DISALLOW_COPY_AND_ASSIGN(AppCapturerLinux);
116 };
117 
AppCapturerLinux(const DesktopCaptureOptions & options)118 AppCapturerLinux::AppCapturerLinux(const DesktopCaptureOptions& options)
119     : callback_(NULL),
120       selected_process_(0),
121       x_display_(options.x_display()) {
122   rgn_mask_ = XCreateRegion();
123   rgn_visual_ = XCreateRegion();
124   rgn_background_ = XCreateRegion();
125 }
126 
~AppCapturerLinux()127 AppCapturerLinux::~AppCapturerLinux() {
128   if (rgn_mask_) {
129     XDestroyRegion(rgn_mask_);
130   }
131   if (rgn_visual_) {
132     XDestroyRegion(rgn_visual_);
133   }
134   if (rgn_background_) {
135     XDestroyRegion(rgn_background_);
136   }
137 }
138 
139 // AppCapturer interface.
GetAppList(AppList * apps)140 bool AppCapturerLinux::GetAppList(AppList* apps) {
141   // Implemented in DesktopDeviceInfo
142   return true;
143 }
SelectApp(ProcessId processId)144 bool AppCapturerLinux::SelectApp(ProcessId processId) {
145   selected_process_ = processId;
146   return true;
147 }
BringAppToFront()148 bool AppCapturerLinux::BringAppToFront() {
149   // Not implemented yet: See Bug 1036653
150   return true;
151 }
152 
153 // DesktopCapturer interface.
Start(Callback * callback)154 void AppCapturerLinux::Start(Callback* callback) {
155   assert(!callback_);
156   assert(callback);
157 
158   callback_ = callback;
159 }
160 
Stop()161 void AppCapturerLinux::Stop() {
162   callback_ = NULL;
163 }
164 
Capture(const DesktopRegion & region)165 void AppCapturerLinux::Capture(const DesktopRegion& region) {
166   CaptureSample(region);
167 }
168 
CaptureWebRTC(const DesktopRegion & region)169 void AppCapturerLinux::CaptureWebRTC(const DesktopRegion& region) {
170   XErrorTrap error_trap(GetDisplay());
171 
172   int nScreenWidth = DisplayWidth(GetDisplay(), DefaultScreen(GetDisplay()));
173   int nScreenHeight = DisplayHeight(GetDisplay(), DefaultScreen(GetDisplay()));
174   rtc::scoped_ptr<DesktopFrame> frame(new BasicDesktopFrame(DesktopSize(nScreenWidth, nScreenHeight)));
175 
176   WindowUtilX11 window_util_x11(x_display_);
177 
178   ::Window root_window = XRootWindow(GetDisplay(), DefaultScreen(GetDisplay()));
179   ::Window parent;
180   ::Window root_return;
181   ::Window *children;
182   unsigned int num_children;
183   int status = XQueryTree(GetDisplay(), root_window, &root_return, &parent,
184       &children, &num_children);
185   if (status == 0) {
186     LOG(LS_ERROR) << "Failed to query for child windows for screen "
187         << DefaultScreen(GetDisplay());
188     return;
189   }
190 
191   for (unsigned int i = 0; i < num_children; ++i) {
192     ::Window app_window = window_util_x11.GetApplicationWindow(children[i]);
193     if (!app_window) {
194       continue;
195     }
196 
197     unsigned int processId = window_util_x11.GetWindowProcessID(app_window);
198     if(processId != 0 && processId == selected_process_) {
199       // capture
200       window_capturer_proxy_.SelectWindow(app_window);
201       window_capturer_proxy_.Capture(region);
202       DesktopFrame* frameWin = window_capturer_proxy_.GetFrame().get();
203       if (frameWin == NULL) {
204         continue;
205       }
206 
207       XRectangle  win_rect;
208       window_util_x11.GetWindowRect(app_window,win_rect, false);
209       if (win_rect.width <= 0 || win_rect.height <= 0) {
210         continue;
211       }
212 
213       DesktopSize winFrameSize = frameWin->size();
214       DesktopRect target_rect = DesktopRect::MakeXYWH(win_rect.x,
215                                                       win_rect.y,
216                                                       winFrameSize.width(),
217                                                       winFrameSize.height());
218 
219       // bitblt into background frame
220       frame->CopyPixelsFrom(*frameWin, DesktopVector(0, 0), target_rect);
221     }
222   }
223 
224   if (children) {
225     XFree(children);
226   }
227 
228   // trigger event
229   if (callback_) {
230     callback_->OnCaptureCompleted(error_trap.GetLastErrorAndDisable() != 0 ?
231                                   NULL :
232                                   frame.release());
233   }
234 }
235 
CaptureSample(const DesktopRegion & region)236 void AppCapturerLinux::CaptureSample(const DesktopRegion& region) {
237   XErrorTrap error_trap(GetDisplay());
238 
239   //Capture screen >> set root window as capture window
240   screen_capturer_proxy_.Capture(region);
241   DesktopFrame* frame = screen_capturer_proxy_.GetFrame().get();
242   if (frame) {
243     // calculate app visual/foreground region
244     UpdateRegions();
245 
246     // TODO: background/foreground mask colors should be configurable; see Bug 1054503
247     // fill background with black
248     FillDesktopFrameRegionWithColor(frame, rgn_background_, 0xFF000000);
249 
250     // fill foreground with yellow
251     FillDesktopFrameRegionWithColor(frame, rgn_mask_, 0xFFFFFF00);
252  }
253 
254   // trigger event
255   if (callback_) {
256     callback_->OnCaptureCompleted(error_trap.GetLastErrorAndDisable() != 0 ?
257                                   NULL :
258                                   screen_capturer_proxy_.GetFrame().release());
259   }
260 }
261 
FillDesktopFrameRegionWithColor(DesktopFrame * pDesktopFrame,Region rgn,uint32_t color)262 void AppCapturerLinux::FillDesktopFrameRegionWithColor(DesktopFrame* pDesktopFrame, Region rgn, uint32_t color) {
263   XErrorTrap error_trap(GetDisplay());
264 
265   if (!pDesktopFrame) {
266     return;
267   }
268   if (XEmptyRegion(rgn)) {
269     return;
270   }
271 
272   REGION * st_rgn = (REGION *)rgn;
273   if(st_rgn && st_rgn->numRects > 0) {
274     for (short i = 0; i < st_rgn->numRects; i++) {
275       for (short j = st_rgn->rects[i].y1; j < st_rgn->rects[i].y2; j++) {
276         uint32_t* dst_pos = reinterpret_cast<uint32_t*>(pDesktopFrame->data() + pDesktopFrame->stride() * j);
277         for (short k = st_rgn->rects[i].x1; k < st_rgn->rects[i].x2; k++) {
278           dst_pos[k] = color;
279         }
280       }
281     }
282   }
283 }
284 
UpdateRegions()285 bool AppCapturerLinux::UpdateRegions() {
286   XErrorTrap error_trap(GetDisplay());
287 
288   XSubtractRegion(rgn_visual_, rgn_visual_, rgn_visual_);
289   XSubtractRegion(rgn_mask_, rgn_mask_, rgn_mask_);
290   WindowUtilX11 window_util_x11(x_display_);
291   int num_screens = XScreenCount(GetDisplay());
292   for (int screen = 0; screen < num_screens; ++screen) {
293     int nScreenCX = DisplayWidth(GetDisplay(), screen);
294     int nScreenCY = DisplayHeight(GetDisplay(), screen);
295 
296     XRectangle  screen_rect;
297     screen_rect.x = 0;
298     screen_rect.y = 0;
299     screen_rect.width = nScreenCX;
300     screen_rect.height = nScreenCY;
301 
302     XUnionRectWithRegion(&screen_rect, rgn_background_, rgn_background_);
303     XXorRegion(rgn_mask_, rgn_mask_, rgn_mask_);
304     XXorRegion(rgn_visual_, rgn_visual_, rgn_visual_);
305 
306     ::Window root_window = XRootWindow(GetDisplay(), screen);
307     ::Window parent;
308     ::Window root_return;
309     ::Window *children;
310     unsigned int num_children;
311     int status = XQueryTree(GetDisplay(), root_window, &root_return, &parent, &children, &num_children);
312     if (status == 0) {
313       LOG(LS_ERROR) << "Failed to query for child windows for screen " << screen;
314       continue;
315     }
316     for (unsigned int i = 0; i < num_children; ++i) {
317       ::Window app_window = window_util_x11.GetApplicationWindow(children[i]);
318       if (!app_window) {
319         continue;
320       }
321 
322       // Get window region
323       XRectangle  win_rect;
324       window_util_x11.GetWindowRect(app_window, win_rect, true);
325       if (win_rect.width <= 0 || win_rect.height <= 0) {
326         continue;
327       }
328 
329       Region win_rgn = XCreateRegion();
330       XUnionRectWithRegion(&win_rect, win_rgn, win_rgn);
331       // update rgn_visual_ , rgn_mask_,
332       unsigned int processId = window_util_x11.GetWindowProcessID(app_window);
333       if (processId != 0 && processId == selected_process_) {
334         XUnionRegion(rgn_visual_, win_rgn, rgn_visual_);
335         XSubtractRegion(rgn_mask_, win_rgn, rgn_mask_);
336       } else {
337         Region win_rgn_intersect = XCreateRegion();
338         XIntersectRegion(rgn_visual_, win_rgn, win_rgn_intersect);
339 
340         XSubtractRegion(rgn_visual_, win_rgn_intersect, rgn_visual_);
341         XUnionRegion(win_rgn_intersect, rgn_mask_, rgn_mask_);
342 
343         if (win_rgn_intersect) {
344           XDestroyRegion(win_rgn_intersect);
345         }
346       }
347       if (win_rgn) {
348         XDestroyRegion(win_rgn);
349       }
350     }
351 
352     if (children) {
353       XFree(children);
354     }
355   }
356 
357   XSubtractRegion(rgn_background_, rgn_visual_, rgn_background_);
358 
359   return true;
360 }
361 
362 }  // namespace
363 
364 // static
Create(const DesktopCaptureOptions & options)365 AppCapturer* AppCapturer::Create(const DesktopCaptureOptions& options) {
366   return new AppCapturerLinux(options);
367 }
368 
369 }  // namespace webrtc
370