blob: 8d6d39535f7ff9b79ed4c8c68e3e97a21725e35c [file] [log] [blame]
// Copyright 2013 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/views/test/ui_controls_factory_desktop_aurax11.h"
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/check_op.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "ui/aura/client/screen_position_client.h"
#include "ui/aura/env.h"
#include "ui/aura/test/aura_test_utils.h"
#include "ui/aura/test/ui_controls_factory_aura.h"
#include "ui/aura/test/x11_event_sender.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/base/test/ui_controls_aura.h"
#include "ui/base/x/x11_util.h"
#include "ui/compositor/dip_util.h"
#include "ui/events/keycodes/keyboard_code_conversion_x.h"
#include "ui/events/test/x11_event_waiter.h"
#include "ui/gfx/x/x11.h"
#include "ui/gfx/x/x11_types.h"
#include "ui/views/test/test_desktop_screen_x11.h"
#include "ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h"
namespace views {
namespace test {
namespace {
using ui_controls::DOWN;
using ui_controls::LEFT;
using ui_controls::MIDDLE;
using ui_controls::MouseButton;
using ui_controls::RIGHT;
using ui_controls::UIControlsAura;
using ui_controls::UP;
// Mask of the buttons currently down.
unsigned button_down_mask = 0;
// Restore Xlib constants that were #undef'ed by gen/ui/gfx/x/xproto.h.
constexpr int CopyFromParent = 0;
constexpr int InputOnly = 2;
constexpr int KeyPress = 2;
constexpr int KeyRelease = 3;
constexpr int ButtonPress = 4;
constexpr int ButtonRelease = 5;
constexpr int Button1 = 1;
constexpr int Button2 = 2;
constexpr int Button3 = 3;
class UIControlsDesktopX11 : public UIControlsAura {
public:
UIControlsDesktopX11()
: x_display_(gfx::GetXDisplay()),
x_root_window_(DefaultRootWindow(x_display_)),
x_window_(XCreateWindow(x_display_,
x_root_window_,
-100, // x
-100, // y
10, // width
10, // height
0, // border width
CopyFromParent, // depth
InputOnly,
reinterpret_cast<Visual*>(CopyFromParent),
0,
nullptr)) {
XStoreName(x_display_, x_window_, "Chromium UIControlsDesktopX11 Window");
}
~UIControlsDesktopX11() override { XDestroyWindow(x_display_, x_window_); }
bool SendKeyPress(gfx::NativeWindow window,
ui::KeyboardCode key,
bool control,
bool shift,
bool alt,
bool command) override {
DCHECK(!command); // No command key on Aura
return SendKeyPressNotifyWhenDone(window, key, control, shift, alt, command,
base::OnceClosure());
}
bool SendKeyPressNotifyWhenDone(gfx::NativeWindow window,
ui::KeyboardCode key,
bool control,
bool shift,
bool alt,
bool command,
base::OnceClosure closure) override {
DCHECK(!command); // No command key on Aura
aura::WindowTreeHost* host = window->GetHost();
XEvent xevent;
xevent.xkey = {};
xevent.xkey.type = KeyPress;
if (control) {
SetKeycodeAndSendThenMask(host, &xevent, XK_Control_L, ControlMask);
}
if (shift)
SetKeycodeAndSendThenMask(host, &xevent, XK_Shift_L, ShiftMask);
if (alt)
SetKeycodeAndSendThenMask(host, &xevent, XK_Alt_L, Mod1Mask);
xevent.xkey.keycode =
XKeysymToKeycode(x_display_, ui::XKeysymForWindowsKeyCode(key, shift));
aura::test::PostEventToWindowTreeHost(xevent, host);
// Send key release events.
xevent.xkey.type = KeyRelease;
aura::test::PostEventToWindowTreeHost(xevent, host);
if (alt)
UnmaskAndSetKeycodeThenSend(host, &xevent, Mod1Mask, XK_Alt_L);
if (shift)
UnmaskAndSetKeycodeThenSend(host, &xevent, ShiftMask, XK_Shift_L);
if (control) {
UnmaskAndSetKeycodeThenSend(host, &xevent, ControlMask, XK_Control_L);
}
DCHECK(!xevent.xkey.state);
RunClosureAfterAllPendingUIEvents(std::move(closure));
return true;
}
bool SendMouseMove(int screen_x, int screen_y) override {
return SendMouseMoveNotifyWhenDone(screen_x, screen_y, base::OnceClosure());
}
bool SendMouseMoveNotifyWhenDone(int screen_x,
int screen_y,
base::OnceClosure closure) override {
gfx::Point screen_location(screen_x, screen_y);
gfx::Point root_location = screen_location;
aura::Window* root_window = RootWindowForPoint(screen_location);
aura::client::ScreenPositionClient* screen_position_client =
aura::client::GetScreenPositionClient(root_window);
if (screen_position_client) {
screen_position_client->ConvertPointFromScreen(root_window,
&root_location);
}
aura::WindowTreeHost* host = root_window->GetHost();
gfx::Point root_current_location =
aura::test::QueryLatestMousePositionRequestInHost(host);
host->ConvertPixelsToDIP(&root_current_location);
auto* screen = views::test::TestDesktopScreenX11::GetInstance();
DCHECK_EQ(screen, display::Screen::GetScreen());
screen->set_cursor_screen_point(gfx::Point(screen_x, screen_y));
if (root_location != root_current_location && button_down_mask == 0) {
// Move the cursor because EnterNotify/LeaveNotify are generated with the
// current mouse position as a result of XGrabPointer()
root_window->MoveCursorTo(root_location);
} else {
XEvent xevent;
xevent.xmotion = {};
XMotionEvent* xmotion = &xevent.xmotion;
xmotion->type = MotionNotify;
xmotion->x = root_location.x();
xmotion->y = root_location.y();
xmotion->state = button_down_mask;
xmotion->same_screen = x11::True;
// RootWindow will take care of other necessary fields.
aura::test::PostEventToWindowTreeHost(xevent, host);
}
RunClosureAfterAllPendingUIEvents(std::move(closure));
return true;
}
bool SendMouseEvents(MouseButton type,
int button_state,
int accelerator_state) override {
return SendMouseEventsNotifyWhenDone(
type, button_state, base::OnceClosure(), accelerator_state);
}
bool SendMouseEventsNotifyWhenDone(MouseButton type,
int button_state,
base::OnceClosure closure,
int accelerator_state) override {
XEvent xevent;
xevent.xbutton = {};
XButtonEvent* xbutton = &xevent.xbutton;
gfx::Point mouse_loc = aura::Env::GetInstance()->last_mouse_location();
aura::Window* root_window = RootWindowForPoint(mouse_loc);
aura::client::ScreenPositionClient* screen_position_client =
aura::client::GetScreenPositionClient(root_window);
if (screen_position_client)
screen_position_client->ConvertPointFromScreen(root_window, &mouse_loc);
xbutton->x = mouse_loc.x();
xbutton->y = mouse_loc.y();
xbutton->same_screen = x11::True;
switch (type) {
case LEFT:
xbutton->button = Button1;
xbutton->state = Button1Mask;
break;
case MIDDLE:
xbutton->button = Button2;
xbutton->state = Button2Mask;
break;
case RIGHT:
xbutton->button = Button3;
xbutton->state = Button3Mask;
break;
}
// Process the accelerator key state.
if (accelerator_state & ui_controls::kShift)
xbutton->state |= ShiftMask;
if (accelerator_state & ui_controls::kControl)
xbutton->state |= ControlMask;
if (accelerator_state & ui_controls::kAlt)
xbutton->state |= Mod1Mask;
if (accelerator_state & ui_controls::kCommand)
xbutton->state |= Mod4Mask;
// RootWindow will take care of other necessary fields.
if (button_state & DOWN) {
xevent.xbutton.type = ButtonPress;
aura::test::PostEventToWindowTreeHost(xevent, root_window->GetHost());
button_down_mask |= xbutton->state;
}
if (button_state & UP) {
xevent.xbutton.type = ButtonRelease;
aura::test::PostEventToWindowTreeHost(xevent, root_window->GetHost());
button_down_mask = (button_down_mask | xbutton->state) ^ xbutton->state;
}
RunClosureAfterAllPendingUIEvents(std::move(closure));
return true;
}
bool SendMouseClick(MouseButton type) override {
return SendMouseEvents(type, UP | DOWN, ui_controls::kNoAccelerator);
}
void RunClosureAfterAllPendingUIEvents(base::OnceClosure closure) {
if (closure.is_null())
return;
ui::XEventWaiter::Create(x_window_, std::move(closure));
}
private:
aura::Window* RootWindowForPoint(const gfx::Point& point) {
// Most interactive_ui_tests run inside of the aura_test_helper
// environment. This means that we can't rely on display::Screen and several
// other things to work properly. Therefore we hack around this by
// iterating across the windows owned DesktopWindowTreeHostLinux since this
// doesn't rely on having a DesktopScreenX11.
std::vector<aura::Window*> windows =
DesktopWindowTreeHostLinux::GetAllOpenWindows();
const auto i =
std::find_if(windows.cbegin(), windows.cend(), [point](auto* window) {
return window->GetBoundsInScreen().Contains(point) ||
window->HasCapture();
});
DCHECK(i != windows.cend()) << "Couldn't find RW for " << point.ToString()
<< " among " << windows.size() << " RWs.";
return (*i)->GetRootWindow();
}
void SetKeycodeAndSendThenMask(aura::WindowTreeHost* host,
XEvent* xevent,
KeySym keysym,
unsigned int mask) {
xevent->xkey.keycode = XKeysymToKeycode(x_display_, keysym);
aura::test::PostEventToWindowTreeHost(*xevent, host);
xevent->xkey.state |= mask;
}
void UnmaskAndSetKeycodeThenSend(aura::WindowTreeHost* host,
XEvent* xevent,
unsigned int mask,
KeySym keysym) {
xevent->xkey.state ^= mask;
xevent->xkey.keycode = XKeysymToKeycode(x_display_, keysym);
aura::test::PostEventToWindowTreeHost(*xevent, host);
}
// Our X11 state.
Display* x_display_;
::Window x_root_window_;
// Input-only window used for events.
::Window x_window_;
DISALLOW_COPY_AND_ASSIGN(UIControlsDesktopX11);
};
} // namespace
UIControlsAura* CreateUIControlsDesktopAura() {
return new UIControlsDesktopX11();
}
} // namespace test
} // namespace views