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/x_server_pixel_buffer.h"
12 
13 #include <X11/Xutil.h>
14 #include <stdint.h>
15 #include <string.h>
16 #include <sys/ipc.h>
17 #include <sys/shm.h>
18 
19 #include "modules/desktop_capture/desktop_frame.h"
20 #include "modules/desktop_capture/linux/window_list_utils.h"
21 #include "modules/desktop_capture/linux/x_error_trap.h"
22 #include "modules/desktop_capture/linux/x_window_property.h"
23 #include "rtc_base/checks.h"
24 #include "rtc_base/logging.h"
25 
26 namespace webrtc {
27 
28 namespace {
29 
30 // Returns the number of bits |mask| has to be shifted left so its last
31 // (most-significant) bit set becomes the most-significant bit of the word.
32 // When |mask| is 0 the function returns 31.
MaskToShift(uint32_t mask)33 uint32_t MaskToShift(uint32_t mask) {
34   int shift = 0;
35   if ((mask & 0xffff0000u) == 0) {
36     mask <<= 16;
37     shift += 16;
38   }
39   if ((mask & 0xff000000u) == 0) {
40     mask <<= 8;
41     shift += 8;
42   }
43   if ((mask & 0xf0000000u) == 0) {
44     mask <<= 4;
45     shift += 4;
46   }
47   if ((mask & 0xc0000000u) == 0) {
48     mask <<= 2;
49     shift += 2;
50   }
51   if ((mask & 0x80000000u) == 0)
52     shift += 1;
53 
54   return shift;
55 }
56 
57 // Returns true if |image| is in RGB format.
IsXImageRGBFormat(XImage * image)58 bool IsXImageRGBFormat(XImage* image) {
59   return image->bits_per_pixel == 32 && image->red_mask == 0xff0000 &&
60          image->green_mask == 0xff00 && image->blue_mask == 0xff;
61 }
62 
63 // We expose two forms of blitting to handle variations in the pixel format.
64 // In FastBlit(), the operation is effectively a memcpy.
FastBlit(XImage * x_image,uint8_t * src_pos,const DesktopRect & rect,DesktopFrame * frame)65 void FastBlit(XImage* x_image,
66               uint8_t* src_pos,
67               const DesktopRect& rect,
68               DesktopFrame* frame) {
69   RTC_DCHECK_LE(frame->top_left().x(), rect.left());
70   RTC_DCHECK_LE(frame->top_left().y(), rect.top());
71 
72   int src_stride = x_image->bytes_per_line;
73   int dst_x = rect.left() - frame->top_left().x();
74   int dst_y = rect.top() - frame->top_left().y();
75 
76   uint8_t* dst_pos = frame->data() + frame->stride() * dst_y;
77   dst_pos += dst_x * DesktopFrame::kBytesPerPixel;
78 
79   int height = rect.height();
80   int row_bytes = rect.width() * DesktopFrame::kBytesPerPixel;
81   for (int y = 0; y < height; ++y) {
82     memcpy(dst_pos, src_pos, row_bytes);
83     src_pos += src_stride;
84     dst_pos += frame->stride();
85   }
86 }
87 
SlowBlit(XImage * x_image,uint8_t * src_pos,const DesktopRect & rect,DesktopFrame * frame)88 void SlowBlit(XImage* x_image,
89               uint8_t* src_pos,
90               const DesktopRect& rect,
91               DesktopFrame* frame) {
92   RTC_DCHECK_LE(frame->top_left().x(), rect.left());
93   RTC_DCHECK_LE(frame->top_left().y(), rect.top());
94 
95   int src_stride = x_image->bytes_per_line;
96   int dst_x = rect.left() - frame->top_left().x();
97   int dst_y = rect.top() - frame->top_left().y();
98   int width = rect.width(), height = rect.height();
99 
100   uint32_t red_mask = x_image->red_mask;
101   uint32_t green_mask = x_image->red_mask;
102   uint32_t blue_mask = x_image->blue_mask;
103 
104   uint32_t red_shift = MaskToShift(red_mask);
105   uint32_t green_shift = MaskToShift(green_mask);
106   uint32_t blue_shift = MaskToShift(blue_mask);
107 
108   int bits_per_pixel = x_image->bits_per_pixel;
109 
110   uint8_t* dst_pos = frame->data() + frame->stride() * dst_y;
111   dst_pos += dst_x * DesktopFrame::kBytesPerPixel;
112   // TODO(hclam): Optimize, perhaps using MMX code or by converting to
113   // YUV directly.
114   // TODO(sergeyu): This code doesn't handle XImage byte order properly and
115   // won't work with 24bpp images. Fix it.
116   for (int y = 0; y < height; y++) {
117     uint32_t* dst_pos_32 = reinterpret_cast<uint32_t*>(dst_pos);
118     uint32_t* src_pos_32 = reinterpret_cast<uint32_t*>(src_pos);
119     uint16_t* src_pos_16 = reinterpret_cast<uint16_t*>(src_pos);
120     for (int x = 0; x < width; x++) {
121       // Dereference through an appropriately-aligned pointer.
122       uint32_t pixel;
123       if (bits_per_pixel == 32) {
124         pixel = src_pos_32[x];
125       } else if (bits_per_pixel == 16) {
126         pixel = src_pos_16[x];
127       } else {
128         pixel = src_pos[x];
129       }
130       uint32_t r = (pixel & red_mask) << red_shift;
131       uint32_t g = (pixel & green_mask) << green_shift;
132       uint32_t b = (pixel & blue_mask) << blue_shift;
133       // Write as 32-bit RGB.
134       dst_pos_32[x] =
135           ((r >> 8) & 0xff0000) | ((g >> 16) & 0xff00) | ((b >> 24) & 0xff);
136     }
137     dst_pos += frame->stride();
138     src_pos += src_stride;
139   }
140 }
141 
142 }  // namespace
143 
XServerPixelBuffer()144 XServerPixelBuffer::XServerPixelBuffer() {}
145 
~XServerPixelBuffer()146 XServerPixelBuffer::~XServerPixelBuffer() {
147   Release();
148 }
149 
Release()150 void XServerPixelBuffer::Release() {
151   if (x_image_) {
152     XDestroyImage(x_image_);
153     x_image_ = nullptr;
154   }
155   if (x_shm_image_) {
156     XDestroyImage(x_shm_image_);
157     x_shm_image_ = nullptr;
158   }
159   if (shm_pixmap_) {
160     XFreePixmap(display_, shm_pixmap_);
161     shm_pixmap_ = 0;
162   }
163   if (shm_gc_) {
164     XFreeGC(display_, shm_gc_);
165     shm_gc_ = nullptr;
166   }
167 
168   ReleaseSharedMemorySegment();
169 
170   window_ = 0;
171 }
172 
ReleaseSharedMemorySegment()173 void XServerPixelBuffer::ReleaseSharedMemorySegment() {
174   if (!shm_segment_info_)
175     return;
176   if (shm_segment_info_->shmaddr != nullptr)
177     shmdt(shm_segment_info_->shmaddr);
178   if (shm_segment_info_->shmid != -1)
179     shmctl(shm_segment_info_->shmid, IPC_RMID, 0);
180   delete shm_segment_info_;
181   shm_segment_info_ = nullptr;
182 }
183 
Init(XAtomCache * cache,Window window)184 bool XServerPixelBuffer::Init(XAtomCache* cache, Window window) {
185   Release();
186   display_ = cache->display();
187 
188   XWindowAttributes attributes;
189   if (!GetWindowRect(display_, window, &window_rect_, &attributes)) {
190     return false;
191   }
192 
193   if (cache->IccProfile() != None) {
194     // |window| is the root window when doing screen capture.
195     XWindowProperty<uint8_t> icc_profile_property(cache->display(), window,
196                                                   cache->IccProfile());
197     if (icc_profile_property.is_valid() && icc_profile_property.size() > 0) {
198       icc_profile_ = std::vector<uint8_t>(
199           icc_profile_property.data(),
200           icc_profile_property.data() + icc_profile_property.size());
201     } else {
202       RTC_LOG(LS_WARNING) << "Failed to get icc profile";
203     }
204   }
205 
206   window_ = window;
207   InitShm(attributes);
208 
209   return true;
210 }
211 
InitShm(const XWindowAttributes & attributes)212 void XServerPixelBuffer::InitShm(const XWindowAttributes& attributes) {
213   Visual* default_visual = attributes.visual;
214   int default_depth = attributes.depth;
215 
216   int major, minor;
217   Bool have_pixmaps;
218   if (!XShmQueryVersion(display_, &major, &minor, &have_pixmaps)) {
219     // Shared memory not supported. CaptureRect will use the XImage API instead.
220     return;
221   }
222 
223   bool using_shm = false;
224   shm_segment_info_ = new XShmSegmentInfo;
225   shm_segment_info_->shmid = -1;
226   shm_segment_info_->shmaddr = nullptr;
227   shm_segment_info_->readOnly = False;
228   x_shm_image_ = XShmCreateImage(display_, default_visual, default_depth,
229                                  ZPixmap, 0, shm_segment_info_,
230                                  window_rect_.width(), window_rect_.height());
231   if (x_shm_image_) {
232     shm_segment_info_->shmid =
233         shmget(IPC_PRIVATE, x_shm_image_->bytes_per_line * x_shm_image_->height,
234                IPC_CREAT | 0600);
235     if (shm_segment_info_->shmid != -1) {
236       void* shmat_result = shmat(shm_segment_info_->shmid, 0, 0);
237       if (shmat_result != reinterpret_cast<void*>(-1)) {
238         shm_segment_info_->shmaddr = reinterpret_cast<char*>(shmat_result);
239         x_shm_image_->data = shm_segment_info_->shmaddr;
240 
241         XErrorTrap error_trap(display_);
242         using_shm = XShmAttach(display_, shm_segment_info_);
243         XSync(display_, False);
244         if (error_trap.GetLastErrorAndDisable() != 0)
245           using_shm = false;
246         if (using_shm) {
247           RTC_LOG(LS_VERBOSE)
248               << "Using X shared memory segment " << shm_segment_info_->shmid;
249         }
250       }
251     } else {
252       RTC_LOG(LS_WARNING) << "Failed to get shared memory segment. "
253                              "Performance may be degraded.";
254     }
255   }
256 
257   if (!using_shm) {
258     RTC_LOG(LS_WARNING)
259         << "Not using shared memory. Performance may be degraded.";
260     ReleaseSharedMemorySegment();
261     return;
262   }
263 
264   if (have_pixmaps)
265     have_pixmaps = InitPixmaps(default_depth);
266 
267   shmctl(shm_segment_info_->shmid, IPC_RMID, 0);
268   shm_segment_info_->shmid = -1;
269 
270   RTC_LOG(LS_VERBOSE) << "Using X shared memory extension v" << major << "."
271                       << minor << " with" << (have_pixmaps ? "" : "out")
272                       << " pixmaps.";
273 }
274 
InitPixmaps(int depth)275 bool XServerPixelBuffer::InitPixmaps(int depth) {
276   if (XShmPixmapFormat(display_) != ZPixmap)
277     return false;
278 
279   {
280     XErrorTrap error_trap(display_);
281     shm_pixmap_ = XShmCreatePixmap(
282         display_, window_, shm_segment_info_->shmaddr, shm_segment_info_,
283         window_rect_.width(), window_rect_.height(), depth);
284     XSync(display_, False);
285     if (error_trap.GetLastErrorAndDisable() != 0) {
286       // |shm_pixmap_| is not not valid because the request was not processed
287       // by the X Server, so zero it.
288       shm_pixmap_ = 0;
289       return false;
290     }
291   }
292 
293   {
294     XErrorTrap error_trap(display_);
295     XGCValues shm_gc_values;
296     shm_gc_values.subwindow_mode = IncludeInferiors;
297     shm_gc_values.graphics_exposures = False;
298     shm_gc_ = XCreateGC(display_, window_,
299                         GCSubwindowMode | GCGraphicsExposures, &shm_gc_values);
300     XSync(display_, False);
301     if (error_trap.GetLastErrorAndDisable() != 0) {
302       XFreePixmap(display_, shm_pixmap_);
303       shm_pixmap_ = 0;
304       shm_gc_ = 0;  // See shm_pixmap_ comment above.
305       return false;
306     }
307   }
308 
309   return true;
310 }
311 
IsWindowValid() const312 bool XServerPixelBuffer::IsWindowValid() const {
313   XWindowAttributes attributes;
314   {
315     XErrorTrap error_trap(display_);
316     if (!XGetWindowAttributes(display_, window_, &attributes) ||
317         error_trap.GetLastErrorAndDisable() != 0) {
318       return false;
319     }
320   }
321   return true;
322 }
323 
Synchronize()324 void XServerPixelBuffer::Synchronize() {
325   if (shm_segment_info_ && !shm_pixmap_) {
326     // XShmGetImage can fail if the display is being reconfigured.
327     XErrorTrap error_trap(display_);
328     // XShmGetImage fails if the window is partially out of screen.
329     xshm_get_image_succeeded_ =
330         XShmGetImage(display_, window_, x_shm_image_, 0, 0, AllPlanes);
331   }
332 }
333 
CaptureRect(const DesktopRect & rect,DesktopFrame * frame)334 bool XServerPixelBuffer::CaptureRect(const DesktopRect& rect,
335                                      DesktopFrame* frame) {
336   RTC_DCHECK_LE(rect.right(), window_rect_.width());
337   RTC_DCHECK_LE(rect.bottom(), window_rect_.height());
338 
339   XImage* image;
340   uint8_t* data;
341 
342   if (shm_segment_info_ && (shm_pixmap_ || xshm_get_image_succeeded_)) {
343     if (shm_pixmap_) {
344       XCopyArea(display_, window_, shm_pixmap_, shm_gc_, rect.left(),
345                 rect.top(), rect.width(), rect.height(), rect.left(),
346                 rect.top());
347       XSync(display_, False);
348     }
349 
350     image = x_shm_image_;
351     data = reinterpret_cast<uint8_t*>(image->data) +
352            rect.top() * image->bytes_per_line +
353            rect.left() * image->bits_per_pixel / 8;
354 
355   } else {
356     if (x_image_)
357       XDestroyImage(x_image_);
358     x_image_ = XGetImage(display_, window_, rect.left(), rect.top(),
359                          rect.width(), rect.height(), AllPlanes, ZPixmap);
360     if (!x_image_)
361       return false;
362 
363     image = x_image_;
364     data = reinterpret_cast<uint8_t*>(image->data);
365   }
366 
367   if (IsXImageRGBFormat(image)) {
368     FastBlit(image, data, rect, frame);
369   } else {
370     SlowBlit(image, data, rect, frame);
371   }
372 
373   if (!icc_profile_.empty())
374     frame->set_icc_profile(icc_profile_);
375 
376   return true;
377 }
378 
379 }  // namespace webrtc
380