| /* |
| * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. |
| * |
| * Use of this source code is governed by a BSD-style license |
| * that can be found in the LICENSE file in the root of the source |
| * tree. An additional intellectual property rights grant can be found |
| * in the file PATENTS. All contributing project authors may |
| * be found in the AUTHORS file in the root of the source tree. |
| */ |
| |
| #include "webrtc/modules/desktop_capture/x11/window_list_utils.h" |
| |
| #include <string.h> |
| #include <X11/Xlib.h> |
| #include <X11/Xatom.h> |
| #include <X11/Xutil.h> |
| |
| #include <algorithm> |
| |
| #include "webrtc/modules/desktop_capture/x11/x_error_trap.h" |
| #include "webrtc/rtc_base/checks.h" |
| #include "webrtc/rtc_base/constructormagic.h" |
| #include "webrtc/rtc_base/logging.h" |
| |
| namespace webrtc { |
| |
| namespace { |
| |
| class DeferXFree { |
| public: |
| explicit DeferXFree(void* data) : data_(data) {} |
| ~DeferXFree(); |
| |
| private: |
| void* const data_; |
| }; |
| |
| DeferXFree::~DeferXFree() { |
| if (data_) |
| XFree(data_); |
| } |
| |
| // Convenience wrapper for XGetWindowProperty() results. |
| template <class PropertyType> |
| class XWindowProperty { |
| public: |
| XWindowProperty(Display* display, Window window, Atom property) { |
| const int kBitsPerByte = 8; |
| Atom actual_type; |
| int actual_format; |
| unsigned long bytes_after; // NOLINT: type required by XGetWindowProperty |
| int status = XGetWindowProperty(display, window, property, 0L, ~0L, False, |
| AnyPropertyType, &actual_type, |
| &actual_format, &size_, |
| &bytes_after, &data_); |
| if (status != Success) { |
| data_ = nullptr; |
| return; |
| } |
| if (sizeof(PropertyType) * kBitsPerByte != actual_format) { |
| size_ = 0; |
| return; |
| } |
| |
| is_valid_ = true; |
| } |
| |
| ~XWindowProperty() { |
| if (data_) |
| XFree(data_); |
| } |
| |
| // True if we got properly value successfully. |
| bool is_valid() const { return is_valid_; } |
| |
| // Size and value of the property. |
| size_t size() const { return size_; } |
| const PropertyType* data() const { |
| return reinterpret_cast<PropertyType*>(data_); |
| } |
| PropertyType* data() { |
| return reinterpret_cast<PropertyType*>(data_); |
| } |
| |
| private: |
| bool is_valid_ = false; |
| unsigned long size_ = 0; // NOLINT: type required by XGetWindowProperty |
| unsigned char* data_ = nullptr; |
| |
| RTC_DISALLOW_COPY_AND_ASSIGN(XWindowProperty); |
| }; |
| |
| // Iterates through |window| hierarchy to find first visible window, i.e. one |
| // that has WM_STATE property set to NormalState. |
| // See http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.3.1 . |
| ::Window GetApplicationWindow(XAtomCache* cache, ::Window window) { |
| int32_t state = GetWindowState(cache, window); |
| if (state == NormalState) { |
| // Window has WM_STATE==NormalState. Return it. |
| return window; |
| } else if (state == IconicState) { |
| // Window is in minimized. Skip it. |
| return 0; |
| } |
| |
| RTC_DCHECK_EQ(state, WithdrawnState); |
| // If the window is in WithdrawnState then look at all of its children. |
| ::Window root, parent; |
| ::Window *children; |
| unsigned int num_children; |
| if (!XQueryTree(cache->display(), window, &root, &parent, &children, |
| &num_children)) { |
| LOG(LS_ERROR) << "Failed to query for child windows although window" |
| << "does not have a valid WM_STATE."; |
| return 0; |
| } |
| ::Window app_window = 0; |
| for (unsigned int i = 0; i < num_children; ++i) { |
| app_window = GetApplicationWindow(cache, children[i]); |
| if (app_window) |
| break; |
| } |
| |
| if (children) |
| XFree(children); |
| return app_window; |
| } |
| |
| // Returns true if the |window| is a desktop element. |
| bool IsDesktopElement(XAtomCache* cache, ::Window window) { |
| RTC_DCHECK(cache); |
| if (window == 0) |
| return false; |
| |
| // First look for _NET_WM_WINDOW_TYPE. The standard |
| // (http://standards.freedesktop.org/wm-spec/latest/ar01s05.html#id2760306) |
| // says this hint *should* be present on all windows, and we use the existence |
| // of _NET_WM_WINDOW_TYPE_NORMAL in the property to indicate a window is not |
| // a desktop element (that is, only "normal" windows should be shareable). |
| XWindowProperty<uint32_t> window_type( |
| cache->display(), window, cache->WindowType()); |
| if (window_type.is_valid() && window_type.size() > 0) { |
| uint32_t* end = window_type.data() + window_type.size(); |
| bool is_normal = (end != std::find( |
| window_type.data(), |
| end, |
| cache->WindowTypeNormal())); |
| return !is_normal; |
| } |
| |
| // Fall back on using the hint. |
| XClassHint class_hint; |
| Status status = XGetClassHint(cache->display(), window, &class_hint); |
| if (status == 0) { |
| // No hints, assume this is a normal application window. |
| return false; |
| } |
| |
| DeferXFree free_res_name(class_hint.res_name); |
| DeferXFree free_res_class(class_hint.res_class); |
| return strcmp("gnome-panel", class_hint.res_name) == 0 || |
| strcmp("desktop_window", class_hint.res_name) == 0; |
| } |
| |
| } // namespace |
| |
| int32_t GetWindowState(XAtomCache* cache, ::Window window) { |
| // Get WM_STATE property of the window. |
| XWindowProperty<uint32_t> window_state( |
| cache->display(), window, cache->WmState()); |
| |
| // WM_STATE is considered to be set to WithdrawnState when it missing. |
| return window_state.is_valid() ? *window_state.data() : WithdrawnState; |
| } |
| |
| bool GetWindowList(XAtomCache* cache, |
| rtc::FunctionView<bool(::Window)> on_window) { |
| RTC_DCHECK(cache); |
| RTC_DCHECK(on_window); |
| ::Display* const display = cache->display(); |
| |
| int failed_screens = 0; |
| const int num_screens = XScreenCount(display); |
| for (int screen = 0; screen < num_screens; screen++) { |
| ::Window root_window = XRootWindow(display, screen); |
| ::Window parent; |
| ::Window* children; |
| unsigned int num_children; |
| { |
| XErrorTrap error_trap(display); |
| if (XQueryTree(display, |
| root_window, |
| &root_window, |
| &parent, |
| &children, |
| &num_children) == 0 || |
| error_trap.GetLastErrorAndDisable() != 0) { |
| failed_screens++; |
| LOG(LS_ERROR) << "Failed to query for child windows for screen " |
| << screen; |
| continue; |
| } |
| } |
| |
| DeferXFree free_children(children); |
| |
| for (unsigned int i = 0; i < num_children; i++) { |
| // Iterates in reverse order to return windows from front to back. |
| ::Window app_window = |
| GetApplicationWindow(cache, children[num_children - 1 - i]); |
| if (app_window && !IsDesktopElement(cache, app_window)) { |
| if (!on_window(app_window)) { |
| return true; |
| } |
| } |
| } |
| } |
| |
| return failed_screens < num_screens; |
| } |
| |
| bool GetWindowRect(::Display* display, |
| ::Window window, |
| DesktopRect* rect, |
| XWindowAttributes* attributes /* = nullptr */) { |
| XWindowAttributes local_attributes; |
| int offset_x; |
| int offset_y; |
| if (attributes == nullptr) { |
| attributes = &local_attributes; |
| } |
| |
| { |
| XErrorTrap error_trap(display); |
| if (!XGetWindowAttributes(display, window, attributes) || |
| error_trap.GetLastErrorAndDisable() != 0) { |
| return false; |
| } |
| } |
| { |
| XErrorTrap error_trap(display); |
| ::Window child; |
| if (!XTranslateCoordinates(display, |
| window, |
| attributes->root, |
| 0, |
| 0, |
| &offset_x, |
| &offset_y, |
| &child) || |
| error_trap.GetLastErrorAndDisable() != 0) { |
| return false; |
| } |
| } |
| |
| *rect = DesktopRectFromXAttributes(*attributes); |
| rect->Translate(offset_x, offset_y); |
| return true; |
| } |
| |
| } // namespace webrtc |