blob: d8c6e57a36a39de4ec4084a2571cfb3ace348378 [file] [log] [blame]
/*
* 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