blob: 05d31542ea3a8cad07a511440e66bb0db79bcd82 [file]
// Copyright (c) 2019 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/events/x/x11_event_translation.h"
#include <xcb/xcb.h>
#include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/event_utils.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/dom/dom_key.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#include "ui/events/test/events_test_utils.h"
#include "ui/events/test/events_test_utils_x11.h"
#include "ui/events/test/keyboard_layout.h"
#include "ui/events/test/scoped_event_test_tick_clock.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/x/connection.h"
#include "ui/gfx/x/event.h"
#include "ui/gfx/x/keysyms/keysyms.h"
#include "ui/gfx/x/xproto.h"
#if defined(USE_OZONE)
#include "ui/base/ui_base_features.h"
#endif
namespace ui {
namespace {
int XkbBuildCoreState(int key_button_mask, int group) {
return ((group & 0x3) << 13) | (key_button_mask & 0xff);
}
} // namespace
// Ensure DomKey extraction happens lazily in Ozone X11, while in non-Ozone
// path it is set right away in XEvent => ui::Event translation. This prevents
// regressions such as crbug.com/1007389.
TEST(XEventTranslationTest, KeyEventDomKeyExtraction) {
ui::ScopedKeyboardLayout keyboard_layout(ui::KEYBOARD_LAYOUT_ENGLISH_US);
ScopedXI2Event xev;
xev.InitKeyEvent(ET_KEY_PRESSED, VKEY_RETURN, EF_NONE);
auto keyev = ui::BuildKeyEventFromXEvent(*xev);
EXPECT_TRUE(keyev);
KeyEventTestApi test(keyev.get());
#if defined(USE_OZONE)
if (features::IsUsingOzonePlatform()) {
EXPECT_EQ(ui::DomKey::NONE, test.dom_key());
} else
#endif
{
EXPECT_EQ(ui::DomKey::ENTER, test.dom_key());
}
EXPECT_EQ(13, keyev->GetCharacter());
EXPECT_EQ("Enter", keyev->GetCodeString());
}
// Ensure KeyEvent::Properties is properly set regardless X11 build config is
// in place. This prevents regressions such as crbug.com/1047999.
TEST(XEventTranslationTest, KeyEventXEventPropertiesSet) {
ui::ScopedKeyboardLayout keyboard_layout(ui::KEYBOARD_LAYOUT_ENGLISH_US);
ScopedXI2Event scoped_xev;
scoped_xev.InitKeyEvent(ET_KEY_PRESSED, VKEY_A, EF_NONE);
x11::Event* xev = scoped_xev;
auto* connection = x11::Connection::Get();
// Set keyboard group in XKeyEvent
uint32_t state = XkbBuildCoreState(
static_cast<uint32_t>(xev->As<x11::KeyEvent>()->state), 2u);
// Set IBus-specific flags
state |= 0x3 << ui::kPropertyKeyboardIBusFlagOffset;
xev->As<x11::KeyEvent>()->state = static_cast<x11::KeyButMask>(state);
auto keyev = ui::BuildKeyEventFromXEvent(*xev);
EXPECT_TRUE(keyev);
auto* properties = keyev->properties();
EXPECT_TRUE(properties);
EXPECT_EQ(3u, properties->size());
// Ensure hardware keycode, keyboard group and ibus flag properties are
// properly set.
auto hw_keycode_it = properties->find(ui::kPropertyKeyboardHwKeyCode);
EXPECT_NE(hw_keycode_it, properties->end());
EXPECT_EQ(1u, hw_keycode_it->second.size());
EXPECT_EQ(static_cast<uint8_t>(connection->KeysymToKeycode(XK_a)),
hw_keycode_it->second[0]);
auto kbd_group_it = properties->find(ui::kPropertyKeyboardGroup);
EXPECT_NE(kbd_group_it, properties->end());
EXPECT_EQ(1u, kbd_group_it->second.size());
EXPECT_EQ(2u, kbd_group_it->second[0]);
auto ibus_flag_it = properties->find(ui::kPropertyKeyboardIBusFlag);
EXPECT_NE(ibus_flag_it, properties->end());
EXPECT_EQ(1u, ibus_flag_it->second.size());
EXPECT_EQ(0x3, ibus_flag_it->second[0]);
}
// Ensure XEvents with bogus timestamps are properly handled when translated
// into ui::*Events.
TEST(XEventTranslationTest, BogusTimestampCorrection) {
using base::TimeDelta;
using base::TimeTicks;
ui::ScopedKeyboardLayout keyboard_layout(ui::KEYBOARD_LAYOUT_ENGLISH_US);
ScopedXI2Event scoped_xev;
scoped_xev.InitKeyEvent(ET_KEY_PRESSED, VKEY_RETURN, EF_NONE);
x11::Event* xev = scoped_xev;
test::ScopedEventTestTickClock test_clock;
test_clock.Advance(TimeDelta::FromSeconds(1));
// Set initial time as 1000ms
TimeTicks now_ticks = EventTimeForNow();
int64_t now_ms = (now_ticks - TimeTicks()).InMilliseconds();
EXPECT_EQ(1000, now_ms);
// Emulate XEvent generated 500ms before current time (non-bogus) and verify
// the translated Event uses native event's timestamp.
xev->As<x11::KeyEvent>()->time = static_cast<x11::Time>(500);
auto keyev = ui::BuildKeyEventFromXEvent(*xev);
EXPECT_TRUE(keyev);
EXPECT_EQ(now_ticks - TimeDelta::FromMilliseconds(500), keyev->time_stamp());
// Emulate XEvent generated 1000ms ahead in time (bogus timestamp) and verify
// the translated Event's timestamp is fixed using (i.e: EventTimeForNow()
// instead of the original XEvent's time)
xev->As<x11::KeyEvent>()->time = static_cast<x11::Time>(2000);
auto keyev2 = ui::BuildKeyEventFromXEvent(*xev);
EXPECT_TRUE(keyev2);
EXPECT_EQ(EventTimeForNow(), keyev2->time_stamp());
// Emulate XEvent >= 60sec old (bogus timestamp) and ensure translated
// ui::Event's timestamp has been corrected (i.e: use ui::EventTimeForNow()
// instead of the original XEvent's time). To emulate such scenario, we
// advance the clock by 5 minutes and set the XEvent's time to 1min, so delta
// is 4min 1sec.
test_clock.Advance(TimeDelta::FromMinutes(5));
xev->As<x11::KeyEvent>()->time = static_cast<x11::Time>(1000 * 60);
auto keyev3 = ui::BuildKeyEventFromXEvent(*xev);
EXPECT_TRUE(keyev3);
EXPECT_EQ(EventTimeForNow(), keyev3->time_stamp());
}
// Ensure MouseEvent::changed_button_flags is correctly translated from
// X{Button,Crossing}Events.
TEST(XEventTranslationTest, ChangedMouseButtonFlags) {
ui::ScopedXI2Event event;
// Taking in a ButtonPress XEvent, with left button pressed.
event.InitButtonEvent(ui::ET_MOUSE_PRESSED, gfx::Point(500, 500),
ui::EF_LEFT_MOUSE_BUTTON);
auto mouseev = ui::BuildMouseEventFromXEvent(*event);
EXPECT_TRUE(mouseev);
EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, mouseev->changed_button_flags());
// Taking in a ButtonPress XEvent, with no button pressed.
x11::Event& x11_event = *event;
x11_event.As<x11::ButtonEvent>()->detail = static_cast<x11::Button>(0);
auto mouseev2 = ui::BuildMouseEventFromXEvent(*event);
EXPECT_TRUE(mouseev2);
EXPECT_EQ(0, mouseev2->changed_button_flags());
// Taking in a EnterNotify XEvent
x11::Event enter_event(x11::CrossingEvent{
.opcode = x11::CrossingEvent::EnterNotify,
.detail = x11::NotifyDetail::Virtual,
});
auto mouseev3 = ui::BuildMouseEventFromXEvent(enter_event);
EXPECT_TRUE(mouseev3);
EXPECT_EQ(0, mouseev3->changed_button_flags());
}
// Verifies 'repeat' flag is properly set when key events for modifiers and
// their counterparts are mixed. Ensures regressions like crbug.com/1069690
// are not reintroduced in the future.
TEST(XEventTranslationTest, KeyModifiersCounterpartRepeat) {
using base::TimeDelta;
// Use a TestTickClock so we have the power to control the time :)
test::ScopedEventTestTickClock test_clock;
// Create and init a XEvent for ShiftLeft key.
ui::ScopedXI2Event shift_l_pressed;
shift_l_pressed.InitKeyEvent(ET_KEY_PRESSED, VKEY_LSHIFT, EF_NONE);
// Press ShiftLeft a first time and hold it.
auto keyev_shift_l_pressed = BuildKeyEventFromXEvent(*shift_l_pressed);
EXPECT_FALSE(keyev_shift_l_pressed->is_repeat());
// Create a few more ShiftLeft key events and ensure 'repeat' flag is set.
test_clock.Advance(TimeDelta::FromMilliseconds(100));
keyev_shift_l_pressed = BuildKeyEventFromXEvent(*shift_l_pressed);
EXPECT_TRUE(keyev_shift_l_pressed->is_repeat());
test_clock.Advance(TimeDelta::FromMilliseconds(200));
keyev_shift_l_pressed = BuildKeyEventFromXEvent(*shift_l_pressed);
EXPECT_TRUE(keyev_shift_l_pressed->is_repeat());
test_clock.Advance(TimeDelta::FromMilliseconds(500));
keyev_shift_l_pressed = BuildKeyEventFromXEvent(*shift_l_pressed);
EXPECT_TRUE(keyev_shift_l_pressed->is_repeat());
// Press and release ShiftRight and verify 'repeat' flag is not set.
// Create and init XEvent for emulating a ShiftRight key press.
ui::ScopedXI2Event shift_r_pressed;
shift_r_pressed.InitKeyEvent(ET_KEY_PRESSED, VKEY_RSHIFT, EF_SHIFT_DOWN);
test_clock.Advance(TimeDelta::FromSeconds(1));
auto keyev_shift_r_pressed = BuildKeyEventFromXEvent(*shift_r_pressed);
EXPECT_FALSE(keyev_shift_r_pressed->is_repeat());
EXPECT_EQ(ET_KEY_PRESSED, keyev_shift_r_pressed->type());
// Create and init XEvent for emulating a ShiftRight key release.
ui::ScopedXI2Event shift_r_released;
shift_r_released.InitKeyEvent(ET_KEY_RELEASED, VKEY_RSHIFT, EF_SHIFT_DOWN);
test_clock.Advance(TimeDelta::FromMilliseconds(300));
auto keyev_shift_r_released = BuildKeyEventFromXEvent(*shift_r_released);
EXPECT_FALSE(keyev_shift_r_released->is_repeat());
EXPECT_EQ(ET_KEY_RELEASED, keyev_shift_r_released->type());
}
} // namespace ui