blob: 1705bde7d598efff4d58d417ae3aef9763b1dd26 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/platform_window/x11/x11_topmost_window_finder.h"
#include <stddef.h>
#include <algorithm>
#include <memory>
#include <vector>
#include "base/containers/contains.h"
#include "build/build_config.h"
#include "third_party/skia/include/core/SkRect.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/x/test/x11_property_change_waiter.h"
#include "ui/base/x/x11_util.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/gfx/x/connection.h"
#include "ui/gfx/x/event.h"
#include "ui/gfx/x/shape.h"
#include "ui/gfx/x/x11_atom_cache.h"
#include "ui/gfx/x/x11_path.h"
#include "ui/gfx/x/xproto.h"
#include "ui/views/test/widget_test.h"
#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
#include "ui/views/widget/desktop_aura/desktop_window_tree_host_linux.h"
#include "ui/views/widget/widget.h"
#if defined(USE_OZONE)
#include "ui/base/ui_base_features.h"
#include "ui/ozone/public/ozone_platform.h"
#endif
namespace views {
namespace {
// Waits till |window| is minimized.
class MinimizeWaiter : public ui::X11PropertyChangeWaiter {
public:
explicit MinimizeWaiter(x11::Window window)
: ui::X11PropertyChangeWaiter(window, "_NET_WM_STATE") {}
~MinimizeWaiter() override = default;
private:
// ui::X11PropertyChangeWaiter:
bool ShouldKeepOnWaiting() override {
std::vector<x11::Atom> wm_states;
if (ui::GetAtomArrayProperty(xwindow(), "_NET_WM_STATE", &wm_states)) {
return !base::Contains(wm_states, x11::GetAtom("_NET_WM_STATE_HIDDEN"));
}
return true;
}
DISALLOW_COPY_AND_ASSIGN(MinimizeWaiter);
};
// Waits till |_NET_CLIENT_LIST_STACKING| is updated to include
// |expected_windows|.
class StackingClientListWaiter : public ui::X11PropertyChangeWaiter {
public:
StackingClientListWaiter(x11::Window* expected_windows, size_t count)
: ui::X11PropertyChangeWaiter(ui::GetX11RootWindow(),
"_NET_CLIENT_LIST_STACKING"),
expected_windows_(expected_windows, expected_windows + count) {}
~StackingClientListWaiter() override = default;
// X11PropertyChangeWaiter:
void Wait() override {
// StackingClientListWaiter may be created after
// _NET_CLIENT_LIST_STACKING already contains |expected_windows|.
if (!ShouldKeepOnWaiting())
return;
ui::X11PropertyChangeWaiter::Wait();
}
private:
// ui::X11PropertyChangeWaiter:
bool ShouldKeepOnWaiting() override {
std::vector<x11::Window> stack;
ui::GetXWindowStack(ui::GetX11RootWindow(), &stack);
return !std::all_of(
expected_windows_.cbegin(), expected_windows_.cend(),
[&stack](x11::Window window) { return base::Contains(stack, window); });
}
std::vector<x11::Window> expected_windows_;
DISALLOW_COPY_AND_ASSIGN(StackingClientListWaiter);
};
void IconifyWindow(x11::Connection* connection, x11::Window window) {
ui::SendClientMessage(window, ui::GetX11RootWindow(),
x11::GetAtom("WM_CHANGE_STATE"),
{ui::WM_STATE_ICONIC, 0, 0, 0, 0});
}
} // namespace
class X11TopmostWindowFinderTest : public test::DesktopWidgetTestInteractive {
public:
X11TopmostWindowFinderTest() = default;
~X11TopmostWindowFinderTest() override = default;
// DesktopWidgetTestInteractive
void SetUp() override {
#if defined(USE_OZONE)
// Run tests only for X11 (ozone or not Ozone).
if (features::IsUsingOzonePlatform() &&
ui::OzonePlatform::GetPlatformNameForTest() != "x11") {
// SetUp still is required to be run. Otherwise, ViewsTestBase CHECKs in
// the dtor.
DesktopWidgetTestInteractive::SetUp();
GTEST_SKIP();
}
#endif
// Make X11 synchronous for our display connection. This does not force the
// window manager to behave synchronously.
connection()->SynchronizeForTest(true);
DesktopWidgetTestInteractive::SetUp();
}
void TearDown() override {
if (!IsSkipped())
connection()->SynchronizeForTest(false);
DesktopWidgetTestInteractive::TearDown();
}
// Creates and shows a Widget with |bounds|. The caller takes ownership of
// the returned widget.
std::unique_ptr<Widget> CreateAndShowWidget(const gfx::Rect& bounds) {
std::unique_ptr<Widget> toplevel(new Widget);
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.native_widget = new DesktopNativeWidgetAura(toplevel.get());
params.bounds = bounds;
params.remove_standard_frame = true;
toplevel->Init(std::move(params));
toplevel->Show();
return toplevel;
}
// Creates and shows an X window with |bounds|.
x11::Window CreateAndShowXWindow(const gfx::Rect& bounds) {
x11::Window root = ui::GetX11RootWindow();
auto window = connection()->GenerateId<x11::Window>();
connection()->CreateWindow({
.wid = window,
.parent = root,
.width = 1,
.height = 1,
});
ui::SetUseOSWindowFrame(window, false);
ShowAndSetXWindowBounds(window, bounds);
return window;
}
// Shows |window| and sets its bounds.
void ShowAndSetXWindowBounds(x11::Window window, const gfx::Rect& bounds) {
connection()->MapWindow({window});
connection()->ConfigureWindow({
.window = window,
.x = bounds.x(),
.y = bounds.y(),
.width = bounds.width(),
.height = bounds.height(),
});
}
x11::Connection* connection() { return x11::Connection::Get(); }
// Returns the topmost X window at the passed in screen position.
x11::Window FindTopmostXWindowAt(int screen_x, int screen_y) {
ui::X11TopmostWindowFinder finder;
return finder.FindWindowAt(gfx::Point(screen_x, screen_y));
}
// Returns the topmost aura::Window at the passed in screen position. Returns
// NULL if the topmost window does not have an associated aura::Window.
aura::Window* FindTopmostLocalProcessWindowAt(int screen_x, int screen_y) {
ui::X11TopmostWindowFinder finder;
auto widget = static_cast<gfx::AcceleratedWidget>(
finder.FindLocalProcessWindowAt(gfx::Point(screen_x, screen_y), {}));
return widget != gfx::kNullAcceleratedWidget
? DesktopWindowTreeHostPlatform::GetContentWindowForWidget(
widget)
: nullptr;
}
// Returns the topmost aura::Window at the passed in screen position ignoring
// |ignore_window|. Returns NULL if the topmost window does not have an
// associated aura::Window.
aura::Window* FindTopmostLocalProcessWindowWithIgnore(
int screen_x,
int screen_y,
aura::Window* ignore_window) {
std::set<gfx::AcceleratedWidget> ignore;
ignore.insert(ignore_window->GetHost()->GetAcceleratedWidget());
ui::X11TopmostWindowFinder finder;
auto widget =
static_cast<gfx::AcceleratedWidget>(finder.FindLocalProcessWindowAt(
gfx::Point(screen_x, screen_y), ignore));
return widget != gfx::kNullAcceleratedWidget
? DesktopWindowTreeHostPlatform::GetContentWindowForWidget(
widget)
: nullptr;
}
private:
DISALLOW_COPY_AND_ASSIGN(X11TopmostWindowFinderTest);
};
TEST_F(X11TopmostWindowFinderTest, Basic) {
// Avoid positioning test windows at 0x0 because window managers often have a
// panel/launcher along one of the screen edges and do not allow windows to
// position themselves to overlap the panel/launcher.
std::unique_ptr<Widget> widget1(
CreateAndShowWidget(gfx::Rect(100, 100, 200, 100)));
aura::Window* window1 = widget1->GetNativeWindow();
x11::Window x11_window1 =
static_cast<x11::Window>(window1->GetHost()->GetAcceleratedWidget());
x11::Window x11_window2 = CreateAndShowXWindow(gfx::Rect(200, 100, 100, 200));
std::unique_ptr<Widget> widget3(
CreateAndShowWidget(gfx::Rect(100, 190, 200, 110)));
aura::Window* window3 = widget3->GetNativeWindow();
x11::Window x11_window3 =
static_cast<x11::Window>(window3->GetHost()->GetAcceleratedWidget());
x11::Window windows[] = {x11_window1, x11_window2, x11_window3};
StackingClientListWaiter waiter(windows, base::size(windows));
waiter.Wait();
connection()->DispatchAll();
EXPECT_EQ(x11_window1, FindTopmostXWindowAt(150, 150));
EXPECT_EQ(window1, FindTopmostLocalProcessWindowAt(150, 150));
EXPECT_EQ(x11_window2, FindTopmostXWindowAt(250, 150));
EXPECT_FALSE(FindTopmostLocalProcessWindowAt(250, 150));
EXPECT_EQ(x11_window3, FindTopmostXWindowAt(250, 250));
EXPECT_EQ(window3, FindTopmostLocalProcessWindowAt(250, 250));
EXPECT_EQ(x11_window3, FindTopmostXWindowAt(150, 250));
EXPECT_EQ(window3, FindTopmostLocalProcessWindowAt(150, 250));
EXPECT_EQ(x11_window3, FindTopmostXWindowAt(150, 195));
EXPECT_EQ(window3, FindTopmostLocalProcessWindowAt(150, 195));
EXPECT_NE(x11_window1, FindTopmostXWindowAt(1000, 1000));
EXPECT_NE(x11_window2, FindTopmostXWindowAt(1000, 1000));
EXPECT_NE(x11_window3, FindTopmostXWindowAt(1000, 1000));
EXPECT_FALSE(FindTopmostLocalProcessWindowAt(1000, 1000));
EXPECT_EQ(window1,
FindTopmostLocalProcessWindowWithIgnore(150, 150, window3));
EXPECT_FALSE(FindTopmostLocalProcessWindowWithIgnore(250, 250, window3));
EXPECT_FALSE(FindTopmostLocalProcessWindowWithIgnore(150, 250, window3));
EXPECT_EQ(window1,
FindTopmostLocalProcessWindowWithIgnore(150, 195, window3));
connection()->DestroyWindow({x11_window2});
}
// Test that the minimized state is properly handled.
TEST_F(X11TopmostWindowFinderTest, Minimized) {
std::unique_ptr<Widget> widget1(
CreateAndShowWidget(gfx::Rect(100, 100, 100, 100)));
aura::Window* window1 = widget1->GetNativeWindow();
x11::Window x11_window1 =
static_cast<x11::Window>(window1->GetHost()->GetAcceleratedWidget());
x11::Window x11_window2 = CreateAndShowXWindow(gfx::Rect(300, 100, 100, 100));
x11::Window windows[] = {x11_window1, x11_window2};
StackingClientListWaiter stack_waiter(windows, base::size(windows));
stack_waiter.Wait();
connection()->DispatchAll();
EXPECT_EQ(x11_window1, FindTopmostXWindowAt(150, 150));
{
MinimizeWaiter minimize_waiter(x11_window1);
IconifyWindow(connection(), x11_window1);
minimize_waiter.Wait();
}
EXPECT_NE(x11_window1, FindTopmostXWindowAt(150, 150));
EXPECT_NE(x11_window2, FindTopmostXWindowAt(150, 150));
// Repeat test for an X window which does not belong to a views::Widget
// because the code path is different.
EXPECT_EQ(x11_window2, FindTopmostXWindowAt(350, 150));
{
MinimizeWaiter minimize_waiter(x11_window2);
IconifyWindow(connection(), x11_window2);
minimize_waiter.Wait();
}
EXPECT_NE(x11_window1, FindTopmostXWindowAt(350, 150));
EXPECT_NE(x11_window2, FindTopmostXWindowAt(350, 150));
connection()->DestroyWindow({x11_window2});
}
// Test that non-rectangular windows are properly handled.
TEST_F(X11TopmostWindowFinderTest, NonRectangular) {
if (!ui::IsShapeExtensionAvailable())
return;
std::unique_ptr<Widget> widget1(
CreateAndShowWidget(gfx::Rect(100, 100, 100, 100)));
x11::Window window1 = static_cast<x11::Window>(
widget1->GetNativeWindow()->GetHost()->GetAcceleratedWidget());
auto shape1 = std::make_unique<Widget::ShapeRects>();
shape1->emplace_back(0, 10, 10, 90);
shape1->emplace_back(10, 0, 90, 100);
widget1->SetShape(std::move(shape1));
SkRegion skregion2;
skregion2.op(SkIRect::MakeXYWH(0, 10, 10, 90), SkRegion::kUnion_Op);
skregion2.op(SkIRect::MakeXYWH(10, 0, 90, 100), SkRegion::kUnion_Op);
x11::Window window2 = CreateAndShowXWindow(gfx::Rect(300, 100, 100, 100));
auto region2 = x11::CreateRegionFromSkRegion(skregion2);
x11::Connection::Get()->shape().Rectangles({
.operation = x11::Shape::So::Set,
.destination_kind = x11::Shape::Sk::Bounding,
.ordering = x11::ClipOrdering::YXBanded,
.destination_window = window2,
.rectangles = *region2,
});
x11::Window windows[] = {window1, window2};
StackingClientListWaiter stack_waiter(windows, base::size(windows));
stack_waiter.Wait();
connection()->DispatchAll();
EXPECT_EQ(window1, FindTopmostXWindowAt(105, 120));
EXPECT_NE(window1, FindTopmostXWindowAt(105, 105));
EXPECT_NE(window2, FindTopmostXWindowAt(105, 105));
// Repeat test for an X window which does not belong to a views::Widget
// because the code path is different.
EXPECT_EQ(window2, FindTopmostXWindowAt(305, 120));
EXPECT_NE(window1, FindTopmostXWindowAt(305, 105));
EXPECT_NE(window2, FindTopmostXWindowAt(305, 105));
connection()->DestroyWindow({window2});
}
// Test that a window with an empty shape are properly handled.
TEST_F(X11TopmostWindowFinderTest, NonRectangularEmptyShape) {
if (!ui::IsShapeExtensionAvailable())
return;
std::unique_ptr<Widget> widget1(
CreateAndShowWidget(gfx::Rect(100, 100, 100, 100)));
x11::Window window1 = static_cast<x11::Window>(
widget1->GetNativeWindow()->GetHost()->GetAcceleratedWidget());
auto shape1 = std::make_unique<Widget::ShapeRects>();
shape1->emplace_back();
// Widget takes ownership of |shape1|.
widget1->SetShape(std::move(shape1));
x11::Window windows[] = {window1};
StackingClientListWaiter stack_waiter(windows, base::size(windows));
stack_waiter.Wait();
connection()->DispatchAll();
EXPECT_NE(window1, FindTopmostXWindowAt(105, 105));
}
// Test that setting a Null shape removes the shape.
// crbug.com/955316: flaky on Linux
#if defined(OS_LINUX) || defined(OS_CHROMEOS)
#define MAYBE_NonRectangularNullShape DISABLED_NonRectangularNullShape
#else
#define MAYBE_NonRectangularNullShape NonRectangularNullShape
#endif
TEST_F(X11TopmostWindowFinderTest, MAYBE_NonRectangularNullShape) {
if (!ui::IsShapeExtensionAvailable())
return;
std::unique_ptr<Widget> widget1(
CreateAndShowWidget(gfx::Rect(100, 100, 100, 100)));
x11::Window window1 = static_cast<x11::Window>(
widget1->GetNativeWindow()->GetHost()->GetAcceleratedWidget());
auto shape1 = std::make_unique<Widget::ShapeRects>();
shape1->emplace_back();
widget1->SetShape(std::move(shape1));
// Remove the shape - this is now just a normal window.
widget1->SetShape(nullptr);
x11::Window windows[] = {window1};
StackingClientListWaiter stack_waiter(windows, base::size(windows));
stack_waiter.Wait();
connection()->DispatchAll();
EXPECT_EQ(window1, FindTopmostXWindowAt(105, 105));
}
// Test that the TopmostWindowFinder finds windows which belong to menus
// (which may or may not belong to Chrome).
//
// Flakes (https://crbug.com/955316)
TEST_F(X11TopmostWindowFinderTest, DISABLED_Menu) {
x11::Window window = CreateAndShowXWindow(gfx::Rect(100, 100, 100, 100));
x11::Window root = ui::GetX11RootWindow();
auto menu_window = connection()->GenerateId<x11::Window>();
connection()->CreateWindow({
.wid = menu_window,
.parent = root,
.width = 1,
.height = 1,
.c_class = x11::WindowClass::CopyFromParent,
.override_redirect = x11::Bool32(true),
});
ui::SetAtomProperty(menu_window, "_NET_WM_WINDOW_TYPE", "ATOM",
x11::GetAtom("_NET_WM_WINDOW_TYPE_MENU"));
ui::SetUseOSWindowFrame(menu_window, false);
ShowAndSetXWindowBounds(menu_window, gfx::Rect(140, 110, 100, 100));
connection()->DispatchAll();
// |menu_window| is never added to _NET_CLIENT_LIST_STACKING.
x11::Window windows[] = {window};
StackingClientListWaiter stack_waiter(windows, base::size(windows));
stack_waiter.Wait();
EXPECT_EQ(window, FindTopmostXWindowAt(110, 110));
EXPECT_EQ(menu_window, FindTopmostXWindowAt(150, 120));
EXPECT_EQ(menu_window, FindTopmostXWindowAt(210, 120));
connection()->DestroyWindow({window});
connection()->DestroyWindow({menu_window});
}
} // namespace views