1 /*
2 * Copyright (c) 2017 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/x11/window_list_utils.h"
12
13 #include <X11/Xlib.h>
14 #include <X11/Xutil.h>
15 #include <string.h>
16
17 #include <algorithm>
18
19 #include "modules/desktop_capture/linux/x11/x_error_trap.h"
20 #include "modules/desktop_capture/linux/x11/x_window_property.h"
21 #include "rtc_base/checks.h"
22 #include "rtc_base/logging.h"
23
24 namespace webrtc {
25
26 namespace {
27
28 class DeferXFree {
29 public:
DeferXFree(void * data)30 explicit DeferXFree(void* data) : data_(data) {}
31 ~DeferXFree();
32
33 private:
34 void* const data_;
35 };
36
~DeferXFree()37 DeferXFree::~DeferXFree() {
38 if (data_)
39 XFree(data_);
40 }
41
42 // Iterates through `window` hierarchy to find first visible window, i.e. one
43 // that has WM_STATE property set to NormalState.
44 // See http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.3.1 .
GetApplicationWindow(XAtomCache * cache,::Window window)45 ::Window GetApplicationWindow(XAtomCache* cache, ::Window window) {
46 int32_t state = GetWindowState(cache, window);
47 if (state == NormalState) {
48 // Window has WM_STATE==NormalState. Return it.
49 return window;
50 } else if (state == IconicState) {
51 // Window is in minimized. Skip it.
52 return 0;
53 }
54
55 RTC_DCHECK_EQ(state, WithdrawnState);
56 // If the window is in WithdrawnState then look at all of its children.
57 ::Window root, parent;
58 ::Window* children;
59 unsigned int num_children;
60 if (!XQueryTree(cache->display(), window, &root, &parent, &children,
61 &num_children)) {
62 RTC_LOG(LS_ERROR) << "Failed to query for child windows although window"
63 "does not have a valid WM_STATE.";
64 return 0;
65 }
66 ::Window app_window = 0;
67 for (unsigned int i = 0; i < num_children; ++i) {
68 app_window = GetApplicationWindow(cache, children[i]);
69 if (app_window)
70 break;
71 }
72
73 if (children)
74 XFree(children);
75 return app_window;
76 }
77
78 // Returns true if the `window` is a desktop element.
IsDesktopElement(XAtomCache * cache,::Window window)79 bool IsDesktopElement(XAtomCache* cache, ::Window window) {
80 RTC_DCHECK(cache);
81 if (window == 0)
82 return false;
83
84 // First look for _NET_WM_WINDOW_TYPE. The standard
85 // (http://standards.freedesktop.org/wm-spec/latest/ar01s05.html#id2760306)
86 // says this hint *should* be present on all windows, and we use the existence
87 // of _NET_WM_WINDOW_TYPE_NORMAL in the property to indicate a window is not
88 // a desktop element (that is, only "normal" windows should be shareable).
89 XWindowProperty<uint32_t> window_type(cache->display(), window,
90 cache->WindowType());
91 if (window_type.is_valid() && window_type.size() > 0) {
92 uint32_t* end = window_type.data() + window_type.size();
93 bool is_normal =
94 (end != std::find(window_type.data(), end, cache->WindowTypeNormal()));
95 return !is_normal;
96 }
97
98 // Fall back on using the hint.
99 XClassHint class_hint;
100 Status status = XGetClassHint(cache->display(), window, &class_hint);
101 if (status == 0) {
102 // No hints, assume this is a normal application window.
103 return false;
104 }
105
106 DeferXFree free_res_name(class_hint.res_name);
107 DeferXFree free_res_class(class_hint.res_class);
108 return strcmp("gnome-panel", class_hint.res_name) == 0 ||
109 strcmp("desktop_window", class_hint.res_name) == 0;
110 }
111
112 } // namespace
113
GetWindowState(XAtomCache * cache,::Window window)114 int32_t GetWindowState(XAtomCache* cache, ::Window window) {
115 // Get WM_STATE property of the window.
116 XWindowProperty<uint32_t> window_state(cache->display(), window,
117 cache->WmState());
118
119 // WM_STATE is considered to be set to WithdrawnState when it missing.
120 return window_state.is_valid() ? *window_state.data() : WithdrawnState;
121 }
122
GetWindowList(XAtomCache * cache,rtc::FunctionView<bool (::Window)> on_window)123 bool GetWindowList(XAtomCache* cache,
124 rtc::FunctionView<bool(::Window)> on_window) {
125 RTC_DCHECK(cache);
126 RTC_DCHECK(on_window);
127 ::Display* const display = cache->display();
128
129 int failed_screens = 0;
130 const int num_screens = XScreenCount(display);
131 for (int screen = 0; screen < num_screens; screen++) {
132 ::Window root_window = XRootWindow(display, screen);
133 ::Window parent;
134 ::Window* children;
135 unsigned int num_children;
136 {
137 XErrorTrap error_trap(display);
138 if (XQueryTree(display, root_window, &root_window, &parent, &children,
139 &num_children) == 0 ||
140 error_trap.GetLastErrorAndDisable() != 0) {
141 failed_screens++;
142 RTC_LOG(LS_ERROR) << "Failed to query for child windows for screen "
143 << screen;
144 continue;
145 }
146 }
147
148 DeferXFree free_children(children);
149
150 for (unsigned int i = 0; i < num_children; i++) {
151 // Iterates in reverse order to return windows from front to back.
152 ::Window app_window =
153 GetApplicationWindow(cache, children[num_children - 1 - i]);
154 if (app_window && !IsDesktopElement(cache, app_window)) {
155 if (!on_window(app_window)) {
156 return true;
157 }
158 }
159 }
160 }
161
162 return failed_screens < num_screens;
163 }
164
GetWindowRect(::Display * display,::Window window,DesktopRect * rect,XWindowAttributes * attributes)165 bool GetWindowRect(::Display* display,
166 ::Window window,
167 DesktopRect* rect,
168 XWindowAttributes* attributes /* = nullptr */) {
169 XWindowAttributes local_attributes;
170 int offset_x;
171 int offset_y;
172 if (attributes == nullptr) {
173 attributes = &local_attributes;
174 }
175
176 {
177 XErrorTrap error_trap(display);
178 if (!XGetWindowAttributes(display, window, attributes) ||
179 error_trap.GetLastErrorAndDisable() != 0) {
180 return false;
181 }
182 }
183 *rect = DesktopRectFromXAttributes(*attributes);
184
185 {
186 XErrorTrap error_trap(display);
187 ::Window child;
188 if (!XTranslateCoordinates(display, window, attributes->root, -rect->left(),
189 -rect->top(), &offset_x, &offset_y, &child) ||
190 error_trap.GetLastErrorAndDisable() != 0) {
191 return false;
192 }
193 }
194 rect->Translate(offset_x, offset_y);
195 return true;
196 }
197
198 } // namespace webrtc
199