0

customization: Add support for KeyEvent observing

Adds support for observing key events which does the following:
- Blocks key events from the given device
- Notifies observers when the event is blocked and tells the observers
  what key was pressed.

These key events can come from either mice or graphics tablets.

Bug: b/241965717
Change-Id: Icabd9799d280fdc7ce8f9e57ec56f107b2cb55ca
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4679518
Reviewed-by: Zentaro Kavanagh <zentaro@chromium.org>
Commit-Queue: David Padlipsky <dpad@google.com>
Cr-Commit-Position: refs/heads/main@{#1181234}
This commit is contained in:
David Padlipsky
2023-08-08 23:58:36 +00:00
committed by Chromium LUCI CQ
parent 5e1348a298
commit eedcbfea8b
4 changed files with 197 additions and 34 deletions

@@ -3896,6 +3896,7 @@ test("ash_unittests") {
"//ui/events/ozone:ozone",
"//ui/events/ozone/evdev:event_device_info",
"//ui/events/ozone/evdev:event_device_info_test_utils",
"//ui/events/ozone/layout",
"//ui/gfx",
"//ui/gfx:test_support",
"//ui/gfx/geometry",

@@ -78,6 +78,17 @@ PeripheralCustomizationEventRewriter::PeripheralCustomizationEventRewriter() =
PeripheralCustomizationEventRewriter::~PeripheralCustomizationEventRewriter() =
default;
absl::optional<PeripheralCustomizationEventRewriter::DeviceType>
PeripheralCustomizationEventRewriter::GetDeviceTypeToObserve(int device_id) {
if (mice_to_observe_.contains(device_id)) {
return DeviceType::kMouse;
}
if (graphics_tablets_to_observe_.contains(device_id)) {
return DeviceType::kGraphicsTablet;
}
return absl::nullopt;
}
void PeripheralCustomizationEventRewriter::StartObservingMouse(int device_id) {
mice_to_observe_.insert(device_id);
}
@@ -135,25 +146,60 @@ bool PeripheralCustomizationEventRewriter::NotifyMouseEventObserving(
return true;
}
bool PeripheralCustomizationEventRewriter::NotifyKeyEventObserving(
const ui::KeyEvent& key_event,
DeviceType device_type) {
// Observers should only be notified on key presses.
if (key_event.type() != ui::ET_KEY_PRESSED) {
return true;
}
const auto button = mojom::Button::NewVkey(key_event.key_code());
for (auto& observer : observers_) {
switch (device_type) {
case DeviceType::kMouse:
observer.OnMouseButtonPressed(key_event.source_device_id(), *button);
break;
case DeviceType::kGraphicsTablet:
observer.OnGraphicsTabletButtonPressed(key_event.source_device_id(),
*button);
break;
}
}
return true;
}
ui::EventDispatchDetails PeripheralCustomizationEventRewriter::RewriteKeyEvent(
const ui::KeyEvent& key_event,
const Continuation continuation) {
auto device_type_to_observe =
GetDeviceTypeToObserve(key_event.source_device_id());
if (device_type_to_observe) {
if (NotifyKeyEventObserving(key_event, *device_type_to_observe)) {
return DiscardEvent(continuation);
}
}
return SendEvent(continuation, &key_event);
}
ui::EventDispatchDetails
PeripheralCustomizationEventRewriter::RewriteMouseEvent(
const ui::MouseEvent& mouse_event,
const Continuation continuation) {
const bool is_mouse_to_observe =
mice_to_observe_.contains(mouse_event.source_device_id());
const bool is_graphics_tablet_to_observe =
graphics_tablets_to_observe_.contains(mouse_event.source_device_id());
if (is_mouse_to_observe || is_graphics_tablet_to_observe) {
const DeviceType device_type =
is_mouse_to_observe ? DeviceType::kMouse : DeviceType::kGraphicsTablet;
if (NotifyMouseEventObserving(mouse_event, device_type)) {
auto device_type_to_observe =
GetDeviceTypeToObserve(mouse_event.source_device_id());
if (device_type_to_observe) {
if (NotifyMouseEventObserving(mouse_event, *device_type_to_observe)) {
return DiscardEvent(continuation);
}
// Otherwise, the flags must be cleared for the remappable buttons so they
// do not affect the application while the mouse is meant to be observed.
ui::MouseEvent rewritten_event = mouse_event;
const int remappable_flags = GetRemappableMouseEventFlags(device_type);
const int remappable_flags =
GetRemappableMouseEventFlags(*device_type_to_observe);
rewritten_event.set_flags(rewritten_event.flags() & ~remappable_flags);
rewritten_event.set_changed_button_flags(
rewritten_event.changed_button_flags() & ~remappable_flags);
@@ -172,6 +218,10 @@ ui::EventDispatchDetails PeripheralCustomizationEventRewriter::RewriteEvent(
return RewriteMouseEvent(*event.AsMouseEvent(), continuation);
}
if (event.IsKeyEvent()) {
return RewriteKeyEvent(*event.AsKeyEvent(), continuation);
}
return SendEvent(continuation, &event);
}

@@ -69,9 +69,17 @@ class ASH_EXPORT PeripheralCustomizationEventRewriter
// the given `device_type`. Returns true if the event should be discarded.
bool NotifyMouseEventObserving(const ui::MouseEvent& mouse_event,
DeviceType device_type);
// Notifies observers if the given `key_event` is a remappable button for
// the given `device_type`. Returns true if the event should be discarded.
bool NotifyKeyEventObserving(const ui::KeyEvent& key_event,
DeviceType device_type);
ui::EventDispatchDetails RewriteMouseEvent(const ui::MouseEvent& mouse_event,
const Continuation continuation);
ui::EventDispatchDetails RewriteKeyEvent(const ui::KeyEvent& key_event,
const Continuation continuation);
absl::optional<DeviceType> GetDeviceTypeToObserve(int device_id);
base::flat_set<int> mice_to_observe_;
base::flat_set<int> graphics_tablets_to_observe_;

@@ -15,8 +15,15 @@
#include "base/notreached.h"
#include "base/strings/stringprintf.h"
#include "base/test/scoped_feature_list.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/variant.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/dom/dom_key.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#include "ui/events/ozone/layout/scoped_keyboard_layout_engine.h"
#include "ui/events/ozone/layout/stub/stub_keyboard_layout_engine.h"
#include "ui/events/test/test_event_rewriter_continuation.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/point_f.h"
@@ -93,27 +100,28 @@ class TestObserver : public PeripheralCustomizationEventRewriter::Observer {
pressed_graphics_tablet_buttons_;
};
using EventTypeVariant = absl::variant<ui::MouseEvent, ui::KeyEvent>;
struct EventRewriterTestData {
ui::MouseEvent incoming_event;
absl::optional<ui::MouseEvent> rewritten_event;
EventTypeVariant incoming_event;
absl::optional<EventTypeVariant> rewritten_event;
absl::optional<mojom::Button> pressed_button;
EventRewriterTestData(ui::MouseEvent incoming_event,
absl::optional<ui::MouseEvent> rewritten_event)
EventRewriterTestData(EventTypeVariant incoming_event,
absl::optional<EventTypeVariant> rewritten_event)
: incoming_event(incoming_event),
rewritten_event(rewritten_event),
pressed_button(absl::nullopt) {}
EventRewriterTestData(ui::MouseEvent incoming_event,
absl::optional<ui::MouseEvent> rewritten_event,
EventRewriterTestData(EventTypeVariant incoming_event,
absl::optional<EventTypeVariant> rewritten_event,
mojom::CustomizableButton button)
: incoming_event(incoming_event), rewritten_event(rewritten_event) {
pressed_button = mojom::Button();
pressed_button->set_customizable_button(button);
}
EventRewriterTestData(ui::MouseEvent incoming_event,
absl::optional<ui::MouseEvent> rewritten_event,
EventRewriterTestData(EventTypeVariant incoming_event,
absl::optional<EventTypeVariant> rewritten_event,
ui::KeyboardCode key_code)
: incoming_event(incoming_event), rewritten_event(rewritten_event) {
pressed_button = mojom::Button();
@@ -123,6 +131,36 @@ struct EventRewriterTestData {
EventRewriterTestData(const EventRewriterTestData& data) = default;
};
// Before test suites are initialized, paraterized data gets generated.
// `ui::KeyEvent` structs rely on the keyboard layout engine being setup.
// Therefore, before any suites are initialized, the keyboard layout engine must
// be configured before using/creating any `ui::KeyEvent` structs. Once a suite
// is setup, this function will be disabled which will stop any further layout
// engines from being created.
std::unique_ptr<ui::ScopedKeyboardLayoutEngine> CreateLayoutEngine(
bool disable_permanently = false) {
static bool disabled = false;
if (disable_permanently || disabled) {
disabled = true;
return nullptr;
}
return std::make_unique<ui::ScopedKeyboardLayoutEngine>(
std::make_unique<ui::StubKeyboardLayoutEngine>());
}
ui::KeyEvent CreateKeyButtonEvent(ui::EventType type,
ui::KeyboardCode key_code,
int flags = ui::EF_NONE,
ui::DomCode code = ui::DomCode::NONE,
ui::DomKey key = ui::DomKey::NONE,
int device_id = kDeviceId) {
auto engine = CreateLayoutEngine();
ui::KeyEvent key_event(type, key_code, code, flags, key, /*time_stamp=*/{});
key_event.set_source_device_id(device_id);
return key_event;
}
ui::MouseEvent CreateMouseButtonEvent(ui::EventType type,
int flags,
int changed_button_flags,
@@ -134,22 +172,53 @@ ui::MouseEvent CreateMouseButtonEvent(ui::EventType type,
return mouse_event;
}
std::string ConvertToString(const ui::MouseEvent& mouse_event) {
return base::StringPrintf(
"MouseEvent type=%d flags=0x%X changed_button_flags=0x%X",
mouse_event.type(), mouse_event.flags(),
mouse_event.changed_button_flags());
}
std::string ConvertToString(const ui::KeyEvent& key_event) {
auto engine = CreateLayoutEngine();
return base::StringPrintf(
"KeyboardEvent type=%d code=0x%06X flags=0x%X vk=0x%02X key=0x%08X "
"scan=0x%08X",
key_event.type(), key_event.key_code(),
static_cast<uint32_t>(key_event.code()), key_event.flags(),
static_cast<uint32_t>(key_event.GetDomKey()), key_event.scan_code());
}
std::string ConvertToString(const EventTypeVariant& event) {
return absl::visit([](auto&& event) { return ConvertToString(event); },
event);
}
std::string ConvertToString(const ui::Event& event) {
if (event.IsMouseEvent()) {
const auto& mouse_event = *event.AsMouseEvent();
return base::StringPrintf(
"MouseEvent type=%d flags=0x%X changed_button_flags=0x%X",
mouse_event.type(), mouse_event.flags(),
mouse_event.changed_button_flags());
return ConvertToString(*event.AsMouseEvent());
}
if (event.IsKeyEvent()) {
return ConvertToString(*event.AsKeyEvent());
}
NOTREACHED_NORETURN();
}
const ui::Event& GetEventFromVariant(const EventTypeVariant& event) {
if (absl::holds_alternative<ui::MouseEvent>(event)) {
return absl::get<ui::MouseEvent>(event);
} else {
return absl::get<ui::KeyEvent>(event);
}
}
} // namespace
class PeripheralCustomizationEventRewriterTest : public AshTestBase {
class PeripheralCustomizationEventRewriterTest : public testing::Test {
public:
PeripheralCustomizationEventRewriterTest() = default;
PeripheralCustomizationEventRewriterTest() {
CreateLayoutEngine(/*disable_permanently=*/true);
}
PeripheralCustomizationEventRewriterTest(
const PeripheralCustomizationEventRewriterTest&) = delete;
PeripheralCustomizationEventRewriterTest& operator=(
@@ -162,7 +231,6 @@ class PeripheralCustomizationEventRewriterTest : public AshTestBase {
features::kInputDeviceSettingsSplit},
{});
AshTestBase::SetUp();
rewriter_ = std::make_unique<PeripheralCustomizationEventRewriter>();
observer_ = std::make_unique<TestObserver>();
rewriter_->AddObserver(observer_.get());
@@ -172,7 +240,6 @@ class PeripheralCustomizationEventRewriterTest : public AshTestBase {
rewriter_->RemoveObserver(observer_.get());
observer_.reset();
rewriter_.reset();
AshTestBase::TearDown();
scoped_feature_list_.Reset();
}
@@ -220,6 +287,7 @@ INSTANTIATE_TEST_SUITE_P(
All,
MouseButtonObserverTest,
testing::ValuesIn(std::vector<EventRewriterTestData>{
// MouseEvent tests:
{
CreateMouseButtonEvent(ui::ET_MOUSE_PRESSED,
ui::EF_BACK_MOUSE_BUTTON,
@@ -298,6 +366,26 @@ INSTANTIATE_TEST_SUITE_P(
ui::EF_RIGHT_MOUSE_BUTTON,
ui::EF_NONE),
},
// KeyEvent tests:
{
CreateKeyButtonEvent(ui::ET_KEY_PRESSED,
ui::VKEY_A,
ui::EF_COMMAND_DOWN),
absl::nullopt,
ui::VKEY_A,
},
{
CreateKeyButtonEvent(ui::ET_KEY_PRESSED, ui::VKEY_B, ui::EF_NONE),
absl::nullopt,
ui::VKEY_B,
},
// Test that key releases are consumed, but not sent to observers.
{
CreateKeyButtonEvent(ui::ET_KEY_RELEASED, ui::VKEY_A),
absl::nullopt,
},
}),
[](const testing::TestParamInfo<EventRewriterTestData>& info) {
std::string name = ConvertToString(info.param.incoming_event);
@@ -312,7 +400,7 @@ TEST_P(MouseButtonObserverTest, EventRewriting) {
rewriter_->StartObservingMouse(kDeviceId);
TestEventRewriterContinuation continuation;
rewriter_->RewriteEvent(data.incoming_event,
rewriter_->RewriteEvent(GetEventFromVariant(data.incoming_event),
continuation.weak_ptr_factory_.GetWeakPtr());
if (!data.rewritten_event) {
ASSERT_TRUE(continuation.discarded());
@@ -324,7 +412,6 @@ TEST_P(MouseButtonObserverTest, EventRewriting) {
}
} else {
ASSERT_TRUE(continuation.passthrough_event);
ASSERT_TRUE(continuation.passthrough_event->IsMouseEvent());
EXPECT_EQ(ConvertToString(*data.rewritten_event),
ConvertToString(*continuation.passthrough_event));
}
@@ -334,10 +421,9 @@ TEST_P(MouseButtonObserverTest, EventRewriting) {
// After we stop observing, the passthrough event should be an identity of the
// original.
rewriter_->RewriteEvent(data.incoming_event,
rewriter_->RewriteEvent(GetEventFromVariant(data.incoming_event),
continuation.weak_ptr_factory_.GetWeakPtr());
ASSERT_TRUE(continuation.passthrough_event);
ASSERT_TRUE(continuation.passthrough_event->IsMouseEvent());
EXPECT_EQ(ConvertToString(data.incoming_event),
ConvertToString(*continuation.passthrough_event));
}
@@ -425,6 +511,26 @@ INSTANTIATE_TEST_SUITE_P(
ui::EF_LEFT_MOUSE_BUTTON,
ui::EF_NONE),
},
// KeyEvent tests:
{
CreateKeyButtonEvent(ui::ET_KEY_PRESSED,
ui::VKEY_A,
ui::EF_COMMAND_DOWN),
absl::nullopt,
ui::VKEY_A,
},
{
CreateKeyButtonEvent(ui::ET_KEY_PRESSED, ui::VKEY_B, ui::EF_NONE),
absl::nullopt,
ui::VKEY_B,
},
// Test that key releases are consumed, but not sent to observers.
{
CreateKeyButtonEvent(ui::ET_KEY_RELEASED, ui::VKEY_A),
absl::nullopt,
},
}),
[](const testing::TestParamInfo<EventRewriterTestData>& info) {
std::string name = ConvertToString(info.param.incoming_event);
@@ -439,7 +545,7 @@ TEST_P(GraphicsTabletButtonObserverTest, RewriteEvent) {
rewriter_->StartObservingGraphicsTablet(kDeviceId);
TestEventRewriterContinuation continuation;
rewriter_->RewriteEvent(data.incoming_event,
rewriter_->RewriteEvent(GetEventFromVariant(data.incoming_event),
continuation.weak_ptr_factory_.GetWeakPtr());
if (!data.rewritten_event) {
ASSERT_TRUE(continuation.discarded());
@@ -451,7 +557,6 @@ TEST_P(GraphicsTabletButtonObserverTest, RewriteEvent) {
}
} else {
ASSERT_TRUE(continuation.passthrough_event);
ASSERT_TRUE(continuation.passthrough_event->IsMouseEvent());
EXPECT_EQ(ConvertToString(*data.rewritten_event),
ConvertToString(*continuation.passthrough_event));
}
@@ -461,10 +566,9 @@ TEST_P(GraphicsTabletButtonObserverTest, RewriteEvent) {
// After we stop observing, the passthrough event should be an identity of the
// original.
rewriter_->RewriteEvent(data.incoming_event,
rewriter_->RewriteEvent(GetEventFromVariant(data.incoming_event),
continuation.weak_ptr_factory_.GetWeakPtr());
ASSERT_TRUE(continuation.passthrough_event);
ASSERT_TRUE(continuation.passthrough_event->IsMouseEvent());
EXPECT_EQ(ConvertToString(data.incoming_event),
ConvertToString(*continuation.passthrough_event));
}