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