OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright 2010 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 "webrtc/base/x11windowpicker.h" | |
12 | |
13 #include <math.h> | |
14 #include <string.h> | |
15 | |
16 #include <algorithm> | |
17 #include <string> | |
18 | |
19 #include <X11/Xatom.h> | |
20 #include <X11/extensions/Xcomposite.h> | |
21 #include <X11/extensions/Xrender.h> | |
22 #include <X11/Xutil.h> | |
23 | |
24 #include "webrtc/base/constructormagic.h" | |
25 #include "webrtc/base/logging.h" | |
26 | |
27 namespace rtc { | |
28 | |
29 // Convenience wrapper for XGetWindowProperty results. | |
30 template <class PropertyType> | |
31 class XWindowProperty { | |
32 public: | |
33 XWindowProperty(Display* display, Window window, Atom property) | |
34 : data_(NULL) { | |
35 const int kBitsPerByte = 8; | |
36 Atom actual_type; | |
37 int actual_format; | |
38 unsigned long bytes_after; // NOLINT: type required by XGetWindowProperty | |
39 int status = XGetWindowProperty(display, window, property, 0L, ~0L, False, | |
40 AnyPropertyType, &actual_type, | |
41 &actual_format, &size_, | |
42 &bytes_after, &data_); | |
43 succeeded_ = (status == Success); | |
44 if (!succeeded_) { | |
45 data_ = NULL; // Ensure nothing is freed. | |
46 } else if (sizeof(PropertyType) * kBitsPerByte != actual_format) { | |
47 LOG(LS_WARNING) << "Returned type size differs from " | |
48 "requested type size."; | |
49 succeeded_ = false; | |
50 // We still need to call XFree in this case, so leave data_ alone. | |
51 } | |
52 if (!succeeded_) { | |
53 size_ = 0; | |
54 } | |
55 } | |
56 | |
57 ~XWindowProperty() { | |
58 if (data_) { | |
59 XFree(data_); | |
60 } | |
61 } | |
62 | |
63 bool succeeded() const { return succeeded_; } | |
64 size_t size() const { return size_; } | |
65 const PropertyType* data() const { | |
66 return reinterpret_cast<PropertyType*>(data_); | |
67 } | |
68 PropertyType* data() { | |
69 return reinterpret_cast<PropertyType*>(data_); | |
70 } | |
71 | |
72 private: | |
73 bool succeeded_; | |
74 unsigned long size_; // NOLINT: type required by XGetWindowProperty | |
75 unsigned char* data_; | |
76 | |
77 RTC_DISALLOW_COPY_AND_ASSIGN(XWindowProperty); | |
78 }; | |
79 | |
80 // Stupid X11. It seems none of the synchronous returns codes from X11 calls | |
81 // are meaningful unless an asynchronous error handler is configured. This | |
82 // RAII class registers and unregisters an X11 error handler. | |
83 class XErrorSuppressor { | |
84 public: | |
85 explicit XErrorSuppressor(Display* display) | |
86 : display_(display), original_error_handler_(NULL) { | |
87 SuppressX11Errors(); | |
88 } | |
89 ~XErrorSuppressor() { | |
90 UnsuppressX11Errors(); | |
91 } | |
92 | |
93 private: | |
94 static int ErrorHandler(Display* display, XErrorEvent* e) { | |
95 char buf[256]; | |
96 XGetErrorText(display, e->error_code, buf, sizeof buf); | |
97 LOG(LS_WARNING) << "Received X11 error \"" << buf << "\" for request code " | |
98 << static_cast<unsigned int>(e->request_code); | |
99 return 0; | |
100 } | |
101 | |
102 void SuppressX11Errors() { | |
103 XFlush(display_); | |
104 XSync(display_, False); | |
105 original_error_handler_ = XSetErrorHandler(&ErrorHandler); | |
106 } | |
107 | |
108 void UnsuppressX11Errors() { | |
109 XFlush(display_); | |
110 XSync(display_, False); | |
111 XErrorHandler handler = XSetErrorHandler(original_error_handler_); | |
112 if (handler != &ErrorHandler) { | |
113 LOG(LS_WARNING) << "Unbalanced XSetErrorHandler() calls detected. " | |
114 << "Final error handler may not be what you expect!"; | |
115 } | |
116 original_error_handler_ = NULL; | |
117 } | |
118 | |
119 Display* display_; | |
120 XErrorHandler original_error_handler_; | |
121 | |
122 RTC_DISALLOW_COPY_AND_ASSIGN(XErrorSuppressor); | |
123 }; | |
124 | |
125 // Hiding all X11 specifics inside its own class. This to avoid | |
126 // conflicts between talk and X11 header declarations. | |
127 class XWindowEnumerator { | |
128 public: | |
129 XWindowEnumerator() | |
130 : display_(NULL), | |
131 has_composite_extension_(false), | |
132 has_render_extension_(false) { | |
133 } | |
134 | |
135 ~XWindowEnumerator() { | |
136 if (display_ != NULL) { | |
137 XCloseDisplay(display_); | |
138 } | |
139 } | |
140 | |
141 bool Init() { | |
142 if (display_ != NULL) { | |
143 // Already initialized. | |
144 return true; | |
145 } | |
146 display_ = XOpenDisplay(NULL); | |
147 if (display_ == NULL) { | |
148 LOG(LS_ERROR) << "Failed to open display."; | |
149 return false; | |
150 } | |
151 | |
152 XErrorSuppressor error_suppressor(display_); | |
153 | |
154 wm_state_ = XInternAtom(display_, "WM_STATE", True); | |
155 net_wm_icon_ = XInternAtom(display_, "_NET_WM_ICON", False); | |
156 | |
157 int event_base, error_base, major_version, minor_version; | |
158 if (XCompositeQueryExtension(display_, &event_base, &error_base) && | |
159 XCompositeQueryVersion(display_, &major_version, &minor_version) && | |
160 // XCompositeNameWindowPixmap() requires version 0.2 | |
161 (major_version > 0 || minor_version >= 2)) { | |
162 has_composite_extension_ = true; | |
163 } else { | |
164 LOG(LS_INFO) << "Xcomposite extension not available or too old."; | |
165 } | |
166 | |
167 if (XRenderQueryExtension(display_, &event_base, &error_base) && | |
168 XRenderQueryVersion(display_, &major_version, &minor_version) && | |
169 // XRenderSetPictureTransform() requires version 0.6 | |
170 (major_version > 0 || minor_version >= 6)) { | |
171 has_render_extension_ = true; | |
172 } else { | |
173 LOG(LS_INFO) << "Xrender extension not available or too old."; | |
174 } | |
175 return true; | |
176 } | |
177 | |
178 bool EnumerateWindows(WindowDescriptionList* descriptions) { | |
179 if (!Init()) { | |
180 return false; | |
181 } | |
182 XErrorSuppressor error_suppressor(display_); | |
183 int num_screens = XScreenCount(display_); | |
184 bool result = false; | |
185 for (int i = 0; i < num_screens; ++i) { | |
186 if (EnumerateScreenWindows(descriptions, i)) { | |
187 // We know we succeded on at least one screen. | |
188 result = true; | |
189 } | |
190 } | |
191 return result; | |
192 } | |
193 | |
194 bool EnumerateDesktops(DesktopDescriptionList* descriptions) { | |
195 if (!Init()) { | |
196 return false; | |
197 } | |
198 XErrorSuppressor error_suppressor(display_); | |
199 Window default_root_window = XDefaultRootWindow(display_); | |
200 int num_screens = XScreenCount(display_); | |
201 for (int i = 0; i < num_screens; ++i) { | |
202 Window root_window = XRootWindow(display_, i); | |
203 DesktopId id(DesktopId(root_window, i)); | |
204 // TODO: Figure out an appropriate desktop title. | |
205 DesktopDescription desc(id, ""); | |
206 desc.set_primary(root_window == default_root_window); | |
207 descriptions->push_back(desc); | |
208 } | |
209 return num_screens > 0; | |
210 } | |
211 | |
212 bool IsVisible(const WindowId& id) { | |
213 if (!Init()) { | |
214 return false; | |
215 } | |
216 XErrorSuppressor error_suppressor(display_); | |
217 XWindowAttributes attr; | |
218 if (!XGetWindowAttributes(display_, id.id(), &attr)) { | |
219 LOG(LS_ERROR) << "XGetWindowAttributes() failed"; | |
220 return false; | |
221 } | |
222 return attr.map_state == IsViewable; | |
223 } | |
224 | |
225 bool MoveToFront(const WindowId& id) { | |
226 if (!Init()) { | |
227 return false; | |
228 } | |
229 XErrorSuppressor error_suppressor(display_); | |
230 unsigned int num_children; | |
231 Window* children; | |
232 Window parent; | |
233 Window root; | |
234 | |
235 // Find root window to pass event to. | |
236 int status = XQueryTree(display_, id.id(), &root, &parent, &children, | |
237 &num_children); | |
238 if (status == 0) { | |
239 LOG(LS_WARNING) << "Failed to query for child windows."; | |
240 return false; | |
241 } | |
242 if (children != NULL) { | |
243 XFree(children); | |
244 } | |
245 | |
246 // Move the window to front. | |
247 XRaiseWindow(display_, id.id()); | |
248 | |
249 // Some window managers (e.g., metacity in GNOME) consider it illegal to | |
250 // raise a window without also giving it input focus with | |
251 // _NET_ACTIVE_WINDOW, so XRaiseWindow() on its own isn't enough. | |
252 Atom atom = XInternAtom(display_, "_NET_ACTIVE_WINDOW", True); | |
253 if (atom != None) { | |
254 XEvent xev; | |
255 long event_mask; | |
256 | |
257 xev.xclient.type = ClientMessage; | |
258 xev.xclient.serial = 0; | |
259 xev.xclient.send_event = True; | |
260 xev.xclient.window = id.id(); | |
261 xev.xclient.message_type = atom; | |
262 | |
263 // The format member is set to 8, 16, or 32 and specifies whether the | |
264 // data should be viewed as a list of bytes, shorts, or longs. | |
265 xev.xclient.format = 32; | |
266 | |
267 xev.xclient.data.l[0] = 0; | |
268 xev.xclient.data.l[1] = 0; | |
269 xev.xclient.data.l[2] = 0; | |
270 xev.xclient.data.l[3] = 0; | |
271 xev.xclient.data.l[4] = 0; | |
272 | |
273 event_mask = SubstructureRedirectMask | SubstructureNotifyMask; | |
274 | |
275 XSendEvent(display_, root, False, event_mask, &xev); | |
276 } | |
277 XFlush(display_); | |
278 return true; | |
279 } | |
280 | |
281 uint8_t* GetWindowIcon(const WindowId& id, int* width, int* height) { | |
282 if (!Init()) { | |
283 return NULL; | |
284 } | |
285 XErrorSuppressor error_suppressor(display_); | |
286 Atom ret_type; | |
287 int format; | |
288 unsigned long length, bytes_after, size; | |
289 unsigned char* data = NULL; | |
290 | |
291 // Find out the size of the icon data. | |
292 if (XGetWindowProperty( | |
293 display_, id.id(), net_wm_icon_, 0, 0, False, XA_CARDINAL, | |
294 &ret_type, &format, &length, &size, &data) == Success && | |
295 data) { | |
296 XFree(data); | |
297 } else { | |
298 LOG(LS_ERROR) << "Failed to get size of the icon."; | |
299 return NULL; | |
300 } | |
301 // Get the icon data, the format is one uint32_t each for width and height, | |
302 // followed by the actual pixel data. | |
303 if (size >= 2 && | |
304 XGetWindowProperty( | |
305 display_, id.id(), net_wm_icon_, 0, size, False, XA_CARDINAL, | |
306 &ret_type, &format, &length, &bytes_after, &data) == Success && | |
307 data) { | |
308 uint32_t* data_ptr = reinterpret_cast<uint32_t*>(data); | |
309 int w, h; | |
310 w = data_ptr[0]; | |
311 h = data_ptr[1]; | |
312 if (size < static_cast<unsigned long>(w * h + 2)) { | |
313 XFree(data); | |
314 LOG(LS_ERROR) << "Not a vaild icon."; | |
315 return NULL; | |
316 } | |
317 uint8_t* rgba = ArgbToRgba(&data_ptr[2], 0, 0, w, h, w, h, true); | |
318 XFree(data); | |
319 *width = w; | |
320 *height = h; | |
321 return rgba; | |
322 } else { | |
323 LOG(LS_ERROR) << "Failed to get window icon data."; | |
324 return NULL; | |
325 } | |
326 } | |
327 | |
328 uint8_t* GetWindowThumbnail(const WindowId& id, int width, int height) { | |
329 if (!Init()) { | |
330 return NULL; | |
331 } | |
332 | |
333 if (!has_composite_extension_) { | |
334 // Without the Xcomposite extension we would only get a good thumbnail if | |
335 // the whole window is visible on screen and not covered by any | |
336 // other window. This is not something we want so instead, just | |
337 // bail out. | |
338 LOG(LS_INFO) << "No Xcomposite extension detected."; | |
339 return NULL; | |
340 } | |
341 XErrorSuppressor error_suppressor(display_); | |
342 | |
343 Window root; | |
344 int x; | |
345 int y; | |
346 unsigned int src_width; | |
347 unsigned int src_height; | |
348 unsigned int border_width; | |
349 unsigned int depth; | |
350 | |
351 // In addition to needing X11 server-side support for Xcomposite, it | |
352 // actually needs to be turned on for this window in order to get a good | |
353 // thumbnail. If the user has modern hardware/drivers but isn't using a | |
354 // compositing window manager, that won't be the case. Here we | |
355 // automatically turn it on for shareable windows so that we can get | |
356 // thumbnails. We used to avoid it because the transition is visually ugly, | |
357 // but recent window managers don't always redirect windows which led to | |
358 // no thumbnails at all, which is a worse experience. | |
359 | |
360 // Redirect drawing to an offscreen buffer (ie, turn on compositing). | |
361 // X11 remembers what has requested this and will turn it off for us when | |
362 // we exit. | |
363 XCompositeRedirectWindow(display_, id.id(), CompositeRedirectAutomatic); | |
364 Pixmap src_pixmap = XCompositeNameWindowPixmap(display_, id.id()); | |
365 if (!src_pixmap) { | |
366 // Even if the backing pixmap doesn't exist, this still should have | |
367 // succeeded and returned a valid handle (it just wouldn't be a handle to | |
368 // anything). So this is a real error path. | |
369 LOG(LS_ERROR) << "XCompositeNameWindowPixmap() failed"; | |
370 return NULL; | |
371 } | |
372 if (!XGetGeometry(display_, src_pixmap, &root, &x, &y, | |
373 &src_width, &src_height, &border_width, | |
374 &depth)) { | |
375 // If the window does not actually have a backing pixmap, this is the path | |
376 // that will "fail", so it's a warning rather than an error. | |
377 LOG(LS_WARNING) << "XGetGeometry() failed (probably composite is not in " | |
378 << "use)"; | |
379 XFreePixmap(display_, src_pixmap); | |
380 return NULL; | |
381 } | |
382 | |
383 // If we get to here, then composite is in use for this window and it has a | |
384 // valid backing pixmap. | |
385 | |
386 XWindowAttributes attr; | |
387 if (!XGetWindowAttributes(display_, id.id(), &attr)) { | |
388 LOG(LS_ERROR) << "XGetWindowAttributes() failed"; | |
389 XFreePixmap(display_, src_pixmap); | |
390 return NULL; | |
391 } | |
392 | |
393 uint8_t* data = GetDrawableThumbnail(src_pixmap, attr.visual, src_width, | |
394 src_height, width, height); | |
395 XFreePixmap(display_, src_pixmap); | |
396 return data; | |
397 } | |
398 | |
399 int GetNumDesktops() { | |
400 if (!Init()) { | |
401 return -1; | |
402 } | |
403 | |
404 return XScreenCount(display_); | |
405 } | |
406 | |
407 uint8_t* GetDesktopThumbnail(const DesktopId& id, int width, int height) { | |
408 if (!Init()) { | |
409 return NULL; | |
410 } | |
411 XErrorSuppressor error_suppressor(display_); | |
412 | |
413 Window root_window = id.id(); | |
414 XWindowAttributes attr; | |
415 if (!XGetWindowAttributes(display_, root_window, &attr)) { | |
416 LOG(LS_ERROR) << "XGetWindowAttributes() failed"; | |
417 return NULL; | |
418 } | |
419 | |
420 return GetDrawableThumbnail(root_window, | |
421 attr.visual, | |
422 attr.width, | |
423 attr.height, | |
424 width, | |
425 height); | |
426 } | |
427 | |
428 bool GetDesktopDimensions(const DesktopId& id, int* width, int* height) { | |
429 if (!Init()) { | |
430 return false; | |
431 } | |
432 XErrorSuppressor error_suppressor(display_); | |
433 XWindowAttributes attr; | |
434 if (!XGetWindowAttributes(display_, id.id(), &attr)) { | |
435 LOG(LS_ERROR) << "XGetWindowAttributes() failed"; | |
436 return false; | |
437 } | |
438 *width = attr.width; | |
439 *height = attr.height; | |
440 return true; | |
441 } | |
442 | |
443 private: | |
444 uint8_t* GetDrawableThumbnail(Drawable src_drawable, | |
445 Visual* visual, | |
446 int src_width, | |
447 int src_height, | |
448 int dst_width, | |
449 int dst_height) { | |
450 if (!has_render_extension_) { | |
451 // Without the Xrender extension we would have to read the full window and | |
452 // scale it down in our process. Xrender is over a decade old so we aren't | |
453 // going to expend effort to support that situation. We still need to | |
454 // check though because probably some virtual VNC displays are in this | |
455 // category. | |
456 LOG(LS_INFO) << "No Xrender extension detected."; | |
457 return NULL; | |
458 } | |
459 | |
460 XRenderPictFormat* format = XRenderFindVisualFormat(display_, | |
461 visual); | |
462 if (!format) { | |
463 LOG(LS_ERROR) << "XRenderFindVisualFormat() failed"; | |
464 return NULL; | |
465 } | |
466 | |
467 // Create a picture to reference the window pixmap. | |
468 XRenderPictureAttributes pa; | |
469 pa.subwindow_mode = IncludeInferiors; // Don't clip child widgets | |
470 Picture src = XRenderCreatePicture(display_, | |
471 src_drawable, | |
472 format, | |
473 CPSubwindowMode, | |
474 &pa); | |
475 if (!src) { | |
476 LOG(LS_ERROR) << "XRenderCreatePicture() failed"; | |
477 return NULL; | |
478 } | |
479 | |
480 // Create a picture to reference the destination pixmap. | |
481 Pixmap dst_pixmap = XCreatePixmap(display_, | |
482 src_drawable, | |
483 dst_width, | |
484 dst_height, | |
485 format->depth); | |
486 if (!dst_pixmap) { | |
487 LOG(LS_ERROR) << "XCreatePixmap() failed"; | |
488 XRenderFreePicture(display_, src); | |
489 return NULL; | |
490 } | |
491 | |
492 Picture dst = XRenderCreatePicture(display_, dst_pixmap, format, 0, NULL); | |
493 if (!dst) { | |
494 LOG(LS_ERROR) << "XRenderCreatePicture() failed"; | |
495 XFreePixmap(display_, dst_pixmap); | |
496 XRenderFreePicture(display_, src); | |
497 return NULL; | |
498 } | |
499 | |
500 // Clear the background. | |
501 XRenderColor transparent = {0}; | |
502 XRenderFillRectangle(display_, | |
503 PictOpSrc, | |
504 dst, | |
505 &transparent, | |
506 0, | |
507 0, | |
508 dst_width, | |
509 dst_height); | |
510 | |
511 // Calculate how much we need to scale the image. | |
512 double scale_x = static_cast<double>(dst_width) / | |
513 static_cast<double>(src_width); | |
514 double scale_y = static_cast<double>(dst_height) / | |
515 static_cast<double>(src_height); | |
516 double scale = std::min(scale_y, scale_x); | |
517 | |
518 int scaled_width = round(src_width * scale); | |
519 int scaled_height = round(src_height * scale); | |
520 | |
521 // Render the thumbnail centered on both axis. | |
522 int centered_x = (dst_width - scaled_width) / 2; | |
523 int centered_y = (dst_height - scaled_height) / 2; | |
524 | |
525 // Scaling matrix | |
526 XTransform xform = { { | |
527 { XDoubleToFixed(1), XDoubleToFixed(0), XDoubleToFixed(0) }, | |
528 { XDoubleToFixed(0), XDoubleToFixed(1), XDoubleToFixed(0) }, | |
529 { XDoubleToFixed(0), XDoubleToFixed(0), XDoubleToFixed(scale) } | |
530 } }; | |
531 XRenderSetPictureTransform(display_, src, &xform); | |
532 | |
533 // Apply filter to smooth out the image. | |
534 XRenderSetPictureFilter(display_, src, FilterBest, NULL, 0); | |
535 | |
536 // Render the image to the destination picture. | |
537 XRenderComposite(display_, | |
538 PictOpSrc, | |
539 src, | |
540 None, | |
541 dst, | |
542 0, | |
543 0, | |
544 0, | |
545 0, | |
546 centered_x, | |
547 centered_y, | |
548 scaled_width, | |
549 scaled_height); | |
550 | |
551 // Get the pixel data from the X server. TODO: XGetImage | |
552 // might be slow here, compare with ShmGetImage. | |
553 XImage* image = XGetImage(display_, | |
554 dst_pixmap, | |
555 0, | |
556 0, | |
557 dst_width, | |
558 dst_height, | |
559 AllPlanes, ZPixmap); | |
560 uint8_t* data = ArgbToRgba(reinterpret_cast<uint32_t*>(image->data), | |
561 centered_x, centered_y, scaled_width, | |
562 scaled_height, dst_width, dst_height, false); | |
563 XDestroyImage(image); | |
564 XRenderFreePicture(display_, dst); | |
565 XFreePixmap(display_, dst_pixmap); | |
566 XRenderFreePicture(display_, src); | |
567 return data; | |
568 } | |
569 | |
570 uint8_t* ArgbToRgba(uint32_t* argb_data, | |
571 int x, | |
572 int y, | |
573 int w, | |
574 int h, | |
575 int stride_x, | |
576 int stride_y, | |
577 bool has_alpha) { | |
578 uint8_t* p; | |
579 int len = stride_x * stride_y * 4; | |
580 uint8_t* data = new uint8_t[len]; | |
581 memset(data, 0, len); | |
582 p = data + 4 * (y * stride_x + x); | |
583 for (int i = 0; i < h; ++i) { | |
584 for (int j = 0; j < w; ++j) { | |
585 uint32_t argb; | |
586 uint32_t rgba; | |
587 argb = argb_data[stride_x * (y + i) + x + j]; | |
588 rgba = (argb << 8) | (argb >> 24); | |
589 *p = rgba >> 24; | |
590 ++p; | |
591 *p = (rgba >> 16) & 0xff; | |
592 ++p; | |
593 *p = (rgba >> 8) & 0xff; | |
594 ++p; | |
595 *p = has_alpha ? rgba & 0xFF : 0xFF; | |
596 ++p; | |
597 } | |
598 p += (stride_x - w) * 4; | |
599 } | |
600 return data; | |
601 } | |
602 | |
603 bool EnumerateScreenWindows(WindowDescriptionList* descriptions, int screen) { | |
604 Window parent; | |
605 Window *children; | |
606 int status; | |
607 unsigned int num_children; | |
608 Window root_window = XRootWindow(display_, screen); | |
609 status = XQueryTree(display_, root_window, &root_window, &parent, &children, | |
610 &num_children); | |
611 if (status == 0) { | |
612 LOG(LS_ERROR) << "Failed to query for child windows."; | |
613 return false; | |
614 } | |
615 for (unsigned int i = 0; i < num_children; ++i) { | |
616 // Iterate in reverse order to display windows from front to back. | |
617 #ifdef CHROMEOS | |
618 // TODO(jhorwich): Short-term fix for crbug.com/120229: Don't need to | |
619 // filter, just return all windows and let the picker scan through them. | |
620 Window app_window = children[num_children - 1 - i]; | |
621 #else | |
622 Window app_window = GetApplicationWindow(children[num_children - 1 - i]); | |
623 #endif | |
624 if (app_window && | |
625 !X11WindowPicker::IsDesktopElement(display_, app_window)) { | |
626 std::string title; | |
627 if (GetWindowTitle(app_window, &title)) { | |
628 WindowId id(app_window); | |
629 WindowDescription desc(id, title); | |
630 descriptions->push_back(desc); | |
631 } | |
632 } | |
633 } | |
634 if (children != NULL) { | |
635 XFree(children); | |
636 } | |
637 return true; | |
638 } | |
639 | |
640 bool GetWindowTitle(Window window, std::string* title) { | |
641 int status; | |
642 bool result = false; | |
643 XTextProperty window_name; | |
644 window_name.value = NULL; | |
645 if (window) { | |
646 status = XGetWMName(display_, window, &window_name); | |
647 if (status && window_name.value && window_name.nitems) { | |
648 int cnt; | |
649 char **list = NULL; | |
650 status = Xutf8TextPropertyToTextList(display_, &window_name, &list, | |
651 &cnt); | |
652 if (status >= Success && cnt && *list) { | |
653 if (cnt > 1) { | |
654 LOG(LS_INFO) << "Window has " << cnt | |
655 << " text properties, only using the first one."; | |
656 } | |
657 *title = *list; | |
658 result = true; | |
659 } | |
660 if (list != NULL) { | |
661 XFreeStringList(list); | |
662 } | |
663 } | |
664 if (window_name.value != NULL) { | |
665 XFree(window_name.value); | |
666 } | |
667 } | |
668 return result; | |
669 } | |
670 | |
671 Window GetApplicationWindow(Window window) { | |
672 Window root, parent; | |
673 Window app_window = 0; | |
674 Window *children; | |
675 unsigned int num_children; | |
676 Atom type = None; | |
677 int format; | |
678 unsigned long nitems, after; | |
679 unsigned char *data; | |
680 | |
681 int ret = XGetWindowProperty(display_, window, | |
682 wm_state_, 0L, 2, | |
683 False, wm_state_, &type, &format, | |
684 &nitems, &after, &data); | |
685 if (ret != Success) { | |
686 LOG(LS_ERROR) << "XGetWindowProperty failed with return code " << ret | |
687 << " for window " << window << "."; | |
688 return 0; | |
689 } | |
690 if (type != None) { | |
691 int64_t state = static_cast<int64_t>(*data); | |
692 XFree(data); | |
693 return state == NormalState ? window : 0; | |
694 } | |
695 XFree(data); | |
696 if (!XQueryTree(display_, window, &root, &parent, &children, | |
697 &num_children)) { | |
698 LOG(LS_ERROR) << "Failed to query for child windows although window" | |
699 << "does not have a valid WM_STATE."; | |
700 return 0; | |
701 } | |
702 for (unsigned int i = 0; i < num_children; ++i) { | |
703 app_window = GetApplicationWindow(children[i]); | |
704 if (app_window) { | |
705 break; | |
706 } | |
707 } | |
708 if (children != NULL) { | |
709 XFree(children); | |
710 } | |
711 return app_window; | |
712 } | |
713 | |
714 Atom wm_state_; | |
715 Atom net_wm_icon_; | |
716 Display* display_; | |
717 bool has_composite_extension_; | |
718 bool has_render_extension_; | |
719 }; | |
720 | |
721 X11WindowPicker::X11WindowPicker() : enumerator_(new XWindowEnumerator()) { | |
722 } | |
723 | |
724 X11WindowPicker::~X11WindowPicker() { | |
725 } | |
726 | |
727 bool X11WindowPicker::IsDesktopElement(_XDisplay* display, Window window) { | |
728 if (window == 0) { | |
729 LOG(LS_WARNING) << "Zero is never a valid window."; | |
730 return false; | |
731 } | |
732 | |
733 // First look for _NET_WM_WINDOW_TYPE. The standard | |
734 // (http://standards.freedesktop.org/wm-spec/latest/ar01s05.html#id2760306) | |
735 // says this hint *should* be present on all windows, and we use the existence | |
736 // of _NET_WM_WINDOW_TYPE_NORMAL in the property to indicate a window is not | |
737 // a desktop element (that is, only "normal" windows should be shareable). | |
738 Atom window_type_atom = XInternAtom(display, "_NET_WM_WINDOW_TYPE", True); | |
739 XWindowProperty<uint32_t> window_type(display, window, window_type_atom); | |
740 if (window_type.succeeded() && window_type.size() > 0) { | |
741 Atom normal_window_type_atom = XInternAtom( | |
742 display, "_NET_WM_WINDOW_TYPE_NORMAL", True); | |
743 uint32_t* end = window_type.data() + window_type.size(); | |
744 bool is_normal = (end != std::find( | |
745 window_type.data(), end, normal_window_type_atom)); | |
746 return !is_normal; | |
747 } | |
748 | |
749 // Fall back on using the hint. | |
750 XClassHint class_hint; | |
751 Status s = XGetClassHint(display, window, &class_hint); | |
752 bool result = false; | |
753 if (s == 0) { | |
754 // No hints, assume this is a normal application window. | |
755 return result; | |
756 } | |
757 static const std::string gnome_panel("gnome-panel"); | |
758 static const std::string desktop_window("desktop_window"); | |
759 | |
760 if (gnome_panel.compare(class_hint.res_name) == 0 || | |
761 desktop_window.compare(class_hint.res_name) == 0) { | |
762 result = true; | |
763 } | |
764 XFree(class_hint.res_name); | |
765 XFree(class_hint.res_class); | |
766 return result; | |
767 } | |
768 | |
769 bool X11WindowPicker::Init() { | |
770 return enumerator_->Init(); | |
771 } | |
772 | |
773 bool X11WindowPicker::GetWindowList(WindowDescriptionList* descriptions) { | |
774 return enumerator_->EnumerateWindows(descriptions); | |
775 } | |
776 | |
777 bool X11WindowPicker::GetDesktopList(DesktopDescriptionList* descriptions) { | |
778 return enumerator_->EnumerateDesktops(descriptions); | |
779 } | |
780 | |
781 bool X11WindowPicker::IsVisible(const WindowId& id) { | |
782 return enumerator_->IsVisible(id); | |
783 } | |
784 | |
785 bool X11WindowPicker::MoveToFront(const WindowId& id) { | |
786 return enumerator_->MoveToFront(id); | |
787 } | |
788 | |
789 uint8_t* X11WindowPicker::GetWindowIcon(const WindowId& id, | |
790 int* width, | |
791 int* height) { | |
792 return enumerator_->GetWindowIcon(id, width, height); | |
793 } | |
794 | |
795 uint8_t* X11WindowPicker::GetWindowThumbnail(const WindowId& id, | |
796 int width, | |
797 int height) { | |
798 return enumerator_->GetWindowThumbnail(id, width, height); | |
799 } | |
800 | |
801 int X11WindowPicker::GetNumDesktops() { | |
802 return enumerator_->GetNumDesktops(); | |
803 } | |
804 | |
805 uint8_t* X11WindowPicker::GetDesktopThumbnail(const DesktopId& id, | |
806 int width, | |
807 int height) { | |
808 return enumerator_->GetDesktopThumbnail(id, width, height); | |
809 } | |
810 | |
811 bool X11WindowPicker::GetDesktopDimensions(const DesktopId& id, int* width, | |
812 int* height) { | |
813 return enumerator_->GetDesktopDimensions(id, width, height); | |
814 } | |
815 | |
816 } // namespace rtc | |
OLD | NEW |