0

[MouseKeys] Show bubble view below cursor

This change ensures the bubble view is displayed below the mouse
cursor and that the correct messages are displayed depending on
the mouse key configuration.

screen-recording: http://screencast/cast/NDYyNzAzODI5MDgzNzUwNHxkOTBhYTczNC01ZA

Bug: 380053616
Change-Id: I5dcbf7264884ca3e58392e1fbe8c641c02fd1d97
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6139523
Reviewed-by: Akihiro Ota <akihiroota@chromium.org>
Commit-Queue: Jesulayomi Kupoluyi <lkupo@google.com>
Cr-Commit-Position: refs/heads/main@{#1405135}
This commit is contained in:
LK Kupoluyi
2025-01-10 20:28:48 -08:00
committed by Chromium LUCI CQ
parent 220c747973
commit ec27c41005
14 changed files with 200 additions and 18 deletions

@ -9,9 +9,11 @@
#include "ash/accessibility/mouse_keys/mouse_keys_controller.h"
#include "ash/accessibility/accessibility_controller.h"
#include "ash/display/window_tree_host_manager.h"
#include "ash/public/cpp/window_tree_host_lookup.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/accessibility/mouse_keys/mouse_keys_bubble_controller.h"
#include "ash/wm/window_util.h"
#include "base/containers/flat_map.h"
@ -20,6 +22,7 @@
#include "ui/aura/client/cursor_client.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/events/event_sink.h"
#include "ui/events/event_utils.h"
#include "ui/wm/core/coordinate_conversion.h"
@ -99,9 +102,14 @@ MouseKeysController::~MouseKeysController() {
void MouseKeysController::Toggle() {
paused_ = !paused_;
if (paused_) {
UpdateMouseKeysBubble(true, MouseKeysBubbleIconType::kHidden,
IDS_ASH_MOUSE_KEYS_PAUSED);
// Reset everything when pausing.
ResetMovement();
dragging_ = false;
} else {
UpdateMouseKeysBubble(true, MouseKeysBubbleIconType::kHidden,
IDS_ASH_MOUSE_KEYS_RESUMED);
}
}
@ -265,10 +273,15 @@ void MouseKeysController::PressKey(MouseKey key) {
break;
case kKeyClick:
case kKeyDragStart:
if (!dragging_) {
SendMouseEventToLocation(ui::EventType::kMousePressed,
last_mouse_position_dips_);
dragging_ = true;
if (dragging_) {
break;
}
SendMouseEventToLocation(ui::EventType::kMousePressed,
last_mouse_position_dips_);
dragging_ = true;
if (key == kKeyDragStart) {
UpdateMouseKeysBubble(true, MouseKeysBubbleIconType::kMouseDrag,
IDS_ASH_MOUSE_KEYS_PERIOD_RELEASE);
}
break;
case kKeyDragStop:
@ -276,6 +289,8 @@ void MouseKeysController::PressKey(MouseKey key) {
SendMouseEventToLocation(ui::EventType::kMouseReleased,
last_mouse_position_dips_);
dragging_ = false;
UpdateMouseKeysBubble(false, MouseKeysBubbleIconType::kMouseDrag,
IDS_ASH_MOUSE_KEYS_PERIOD_RELEASE);
}
break;
case kKeyDoubleClick:
@ -293,13 +308,13 @@ void MouseKeysController::PressKey(MouseKey key) {
}
break;
case kKeySelectLeftButton:
current_mouse_button_ = kLeft;
UpdateCurrentMouseButton(kLeft);
break;
case kKeySelectRightButton:
current_mouse_button_ = kRight;
UpdateCurrentMouseButton(kRight);
break;
case kKeySelectBothButtons:
current_mouse_button_ = kBoth;
UpdateCurrentMouseButton(kBoth);
break;
case kKeySelectNextButton:
SelectNextButton();
@ -345,13 +360,33 @@ void MouseKeysController::ReleaseKey(MouseKey key) {
void MouseKeysController::SelectNextButton() {
switch (current_mouse_button_) {
case kLeft:
current_mouse_button_ = kRight;
UpdateCurrentMouseButton(kRight);
break;
case kRight:
current_mouse_button_ = kBoth;
UpdateCurrentMouseButton(kBoth);
break;
case kBoth:
UpdateCurrentMouseButton(kLeft);
break;
}
}
void MouseKeysController::UpdateCurrentMouseButton(MouseButton mouse_button) {
switch (mouse_button) {
case kLeft:
current_mouse_button_ = kLeft;
UpdateMouseKeysBubble(true, MouseKeysBubbleIconType::kButtonChanged,
IDS_ASH_MOUSE_KEYS_LEFT_MOUSE_BUTTON);
break;
case kRight:
current_mouse_button_ = kRight;
UpdateMouseKeysBubble(true, MouseKeysBubbleIconType::kButtonChanged,
IDS_ASH_MOUSE_KEYS_RIGHT_MOUSE_BUTTON);
break;
case kBoth:
current_mouse_button_ = kBoth;
UpdateMouseKeysBubble(true, MouseKeysBubbleIconType::kButtonChanged,
IDS_ASH_MOUSE_KEYS_BOTH_MOUSE_BUTTONS);
break;
}
}
@ -410,6 +445,13 @@ void MouseKeysController::UpdateState() {
speed_ = std::clamp(speed_ + acceleration, 0.0, max_speed_);
}
void MouseKeysController::UpdateMouseKeysBubble(bool visible,
MouseKeysBubbleIconType icon,
const int name_resource_id) {
mouse_keys_bubble_controller_->UpdateBubble(
visible, icon, l10n_util::GetStringUTF16(name_resource_id));
}
void MouseKeysController::ResetMovement() {
speed_ = 0;
if (update_timer_.IsRunning()) {

@ -8,6 +8,7 @@
#include <memory>
#include "ash/ash_export.h"
#include "ash/public/cpp/accessibility_controller_enums.h"
#include "base/timer/timer.h"
#include "ui/events/event.h"
#include "ui/events/event_handler.h"
@ -71,6 +72,10 @@ class ASH_EXPORT MouseKeysController : public ui::EventHandler {
max_speed_ = factor * kBaseSpeedDIPPerSecond * kUpdateFrequencyInSeconds;
}
gfx::Point GetLastMousePositionDips() const {
return last_mouse_position_dips_;
}
enum MouseKey {
kKeyUpLeft = 0,
kKeyUp,
@ -114,11 +119,15 @@ class ASH_EXPORT MouseKeysController : public ui::EventHandler {
ui::DomCode input,
MouseKey output);
void PressKey(MouseKey key);
void ReleaseKey(MouseKey key);
void SelectNextButton();
void RefreshVelocity();
void UpdateState();
void ReleaseKey(MouseKey key);
void ResetMovement();
void SelectNextButton();
void UpdateCurrentMouseButton(MouseButton mouse_button);
void UpdateMouseKeysBubble(bool visible,
MouseKeysBubbleIconType icon,
const int name_resource_id);
void UpdateState();
bool enabled_ = false;
bool paused_ = false;

@ -9,6 +9,8 @@
#include "ash/events/test_event_capturer.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/system/accessibility/mouse_keys/mouse_keys_bubble_controller.h"
#include "ash/system/accessibility/mouse_keys/mouse_keys_bubble_view.h"
#include "ash/test/ash_test_base.h"
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
@ -83,6 +85,8 @@ class EventRewriterWrapper : public ui::EventRewriter {
}
};
} // namespace
class MouseKeysTest : public AshTestBase {
protected:
MouseKeysTest()
@ -118,6 +122,36 @@ class MouseKeysTest : public AshTestBase {
Shell::Get()->accessibility_controller()->mouse_keys().SetEnabled(enabled);
}
MouseKeysBubbleController* GetBubbleController() const {
return Shell::Get()
->mouse_keys_controller()
->GetMouseKeysBubbleControllerForTest();
}
MouseKeysBubbleView* GetBubbleView() const {
return GetBubbleController()->mouse_keys_bubble_view_;
}
bool IsBubbleVisible() {
// Add a null check for widget_.
if (GetBubbleController()->widget_ == nullptr) {
return false;
}
return GetBubbleController()->widget_->IsVisible();
}
const std::u16string GetBubbleText() const {
return GetBubbleView()->GetTextForTesting();
}
bool IsButtonChangeIconVisible() const {
return GetBubbleView()->GetMouseButtonChangeIconForTesting()->GetVisible();
}
bool IsMouseDraggedIconVisible() const {
return GetBubbleView()->GetMouseDragIconForTesting()->GetVisible();
}
const std::vector<ui::KeyEvent>& CheckForKeyEvents() {
base::RunLoop().RunUntilIdle();
return event_capturer_.key_events();
@ -305,8 +339,6 @@ class MouseKeysTest : public AshTestBase {
EventRewriterWrapper rewriter_;
};
} // namespace
TEST_F(MouseKeysTest, ToggleEnabled) {
std::vector<ui::MouseEvent> events;
@ -434,7 +466,16 @@ TEST_F(MouseKeysTest, SelectButtonRightHand) {
// Press , and the mouse action should be the right button.
ClearEvents();
EXPECT_FALSE(IsBubbleVisible());
PressAndReleaseKey(ui::VKEY_OEM_COMMA);
// Bubble view with right button change message and button change icon
// should be displayed.
EXPECT_TRUE(IsBubbleVisible());
EXPECT_EQ(GetBubbleText(), u"Right mouse button");
EXPECT_TRUE(IsButtonChangeIconVisible());
EXPECT_FALSE(IsMouseDraggedIconVisible());
PressAndReleaseKey(ui::VKEY_I);
EXPECT_EQ(0u, CheckForKeyEvents().size());
ExpectClick(CheckForMouseEvents(), ui::EF_RIGHT_MOUSE_BUTTON,
@ -443,6 +484,14 @@ TEST_F(MouseKeysTest, SelectButtonRightHand) {
// Press , and the mouse action should be both buttons.
ClearEvents();
PressAndReleaseKey(ui::VKEY_OEM_COMMA);
// Bubble view with both mouse buttons change message
// and button change icon should be displayed.
EXPECT_TRUE(IsBubbleVisible());
EXPECT_EQ(GetBubbleText(), u"Both mouse buttons");
EXPECT_TRUE(IsButtonChangeIconVisible());
EXPECT_FALSE(IsMouseDraggedIconVisible());
PressAndReleaseKey(ui::VKEY_I);
EXPECT_EQ(0u, CheckForKeyEvents().size());
ExpectClick(CheckForMouseEvents(),
@ -452,6 +501,14 @@ TEST_F(MouseKeysTest, SelectButtonRightHand) {
// Press , and the mouse action should be the left button.
ClearEvents();
PressAndReleaseKey(ui::VKEY_OEM_COMMA);
// Bubble view with left mouse buttons change message
// and button change icon should be displayed.
EXPECT_TRUE(IsBubbleVisible());
EXPECT_EQ(GetBubbleText(), u"Left mouse button");
EXPECT_TRUE(IsButtonChangeIconVisible());
EXPECT_FALSE(IsMouseDraggedIconVisible());
PressAndReleaseKey(ui::VKEY_I);
EXPECT_EQ(0u, CheckForKeyEvents().size());
ExpectClick(CheckForMouseEvents(), ui::EF_LEFT_MOUSE_BUTTON,
@ -955,6 +1012,13 @@ TEST_F(MouseKeysTest, Dragging) {
// Start Drag.
ClearEvents();
PressAndReleaseKey(ui::VKEY_M);
// Bubble view with the correct message and icon should be displayed.
EXPECT_TRUE(IsBubbleVisible());
EXPECT_EQ(GetBubbleText(), u"Press \".\" to release");
EXPECT_TRUE(IsMouseDraggedIconVisible());
EXPECT_FALSE(IsButtonChangeIconVisible());
auto mouse_events = CheckForMouseEvents();
EXPECT_EQ(0u, CheckForKeyEvents().size());
ASSERT_EQ(1u, mouse_events.size());
@ -965,6 +1029,9 @@ TEST_F(MouseKeysTest, Dragging) {
// Move right.
ClearEvents();
PressKey(ui::VKEY_O);
EXPECT_TRUE(IsBubbleVisible());
EXPECT_TRUE(IsMouseDraggedIconVisible());
EXPECT_FALSE(IsButtonChangeIconVisible());
task_environment()->FastForwardBy(base::Seconds(kTenEventsInSeconds));
ReleaseKey(ui::VKEY_O);
mouse_events = CheckForMouseEvents();
@ -981,6 +1048,7 @@ TEST_F(MouseKeysTest, Dragging) {
// Stop Drag.
ClearEvents();
PressAndReleaseKey(ui::VKEY_OEM_PERIOD);
EXPECT_FALSE(IsBubbleVisible());
mouse_events = CheckForMouseEvents();
EXPECT_EQ(0u, CheckForKeyEvents().size());
ASSERT_EQ(1u, mouse_events.size());
@ -1004,6 +1072,7 @@ TEST_F(MouseKeysTest, DragWithClick) {
// Start Drag.
ClearEvents();
PressKey(ui::VKEY_I);
EXPECT_FALSE(IsBubbleVisible());
auto mouse_events = CheckForMouseEvents();
EXPECT_EQ(0u, CheckForKeyEvents().size());
ASSERT_EQ(1u, mouse_events.size());
@ -1030,6 +1099,7 @@ TEST_F(MouseKeysTest, DragWithClick) {
// Stop Drag.
ClearEvents();
ReleaseKey(ui::VKEY_I);
EXPECT_FALSE(IsBubbleVisible());
mouse_events = CheckForMouseEvents();
EXPECT_EQ(0u, CheckForKeyEvents().size());
ASSERT_EQ(1u, mouse_events.size());
@ -1104,6 +1174,12 @@ TEST_F(MouseKeysTest, Accelerator) {
accelerator_controller->PerformActionIfEnabled(
AcceleratorAction::kToggleMouseKeys, {});
// Bubble view with the paused message and no icon should be displayed.
EXPECT_TRUE(IsBubbleVisible());
EXPECT_EQ(GetBubbleText(), u"Mouse keys paused");
EXPECT_FALSE(IsMouseDraggedIconVisible());
EXPECT_FALSE(IsButtonChangeIconVisible());
ClearEvents();
PressAndReleaseKey(ui::VKEY_I);
EXPECT_EQ(0u, CheckForMouseEvents().size());
@ -1112,6 +1188,12 @@ TEST_F(MouseKeysTest, Accelerator) {
accelerator_controller->PerformActionIfEnabled(
AcceleratorAction::kToggleMouseKeys, {});
// Bubble view with the resumed message and no icon should be displayed.
EXPECT_TRUE(IsBubbleVisible());
EXPECT_EQ(GetBubbleText(), u"Mouse keys resumed");
EXPECT_FALSE(IsMouseDraggedIconVisible());
EXPECT_FALSE(IsButtonChangeIconVisible());
ClearEvents();
PressAndReleaseKey(ui::VKEY_I);
EXPECT_EQ(0u, CheckForKeyEvents().size());

@ -8058,6 +8058,26 @@ Otherwise the built-in touchpad will be re-enabled in <ph name="TIMEOUT_SECONDS"
Otherwise the built-in touchpad will be re-enabled in <ph name="TIMEOUT_SECONDS">$1<ex>30 seconds</ex></ph>.
</message>
<!-- Mouse Keys touchpad feature -->
<message name="IDS_ASH_MOUSE_KEYS_LEFT_MOUSE_BUTTON" desc="Label to show left mouse button click.">
Left mouse button
</message>
<message name="IDS_ASH_MOUSE_KEYS_RIGHT_MOUSE_BUTTON" desc="Label to show right mouse button click.">
Right mouse button
</message>
<message name="IDS_ASH_MOUSE_KEYS_BOTH_MOUSE_BUTTONS" desc="Label to show both mouse buttons click.">
Both mouse buttons
</message>
<message name="IDS_ASH_MOUSE_KEYS_PERIOD_RELEASE" desc="Label to show how to release mouse button after a drag.">
Press "." to release
</message>
<message name="IDS_ASH_MOUSE_KEYS_PAUSED" desc="Label to show the mouse keys paused state.">
Mouse keys paused
</message>
<message name="IDS_ASH_MOUSE_KEYS_RESUMED" desc="Label to show the mouse keys resumed state.">
Mouse keys resumed
</message>
<!-- Guest session confirmation dialog -->
<message name="IDS_GUEST_SESSION_CONFIRMATION_DIALOG_TITLE" desc="Title of the dialog for switching to device guest mode.">
Sign out now?

@ -0,0 +1 @@
395b40b66a6eb418c215ec27d51849e9bf49858e

@ -0,0 +1 @@
e664d024675310f47e00408d69ab48a15ef93f93

@ -0,0 +1 @@
fc923e4c9c7fd91f0e7a3c5f68847e6440050d45

@ -0,0 +1 @@
74dd4761eb3f1ff31db38edd03ac6ce1b45e8c9f

@ -0,0 +1 @@
a8db772c7246545f7fee691afaaaf35f2c22c163

@ -0,0 +1 @@
45ec4a19469a26b671929ae36eeaeb5d585558c6

@ -24,8 +24,17 @@ void MouseKeysBubbleController::UpdateBubble(
MouseKeysBubbleIconType icon,
const std::optional<std::u16string>& text) {
EnsureInitialize();
gfx::Point cursor_position =
Shell::Get()->mouse_keys_controller()->GetLastMousePositionDips();
cursor_position.Offset(16, 16);
if (mouse_keys_bubble_view_) {
mouse_keys_bubble_view_->SetAnchorRect(
gfx::Rect(cursor_position, gfx::Size()));
}
Update(icon, text);
// TODO(crbug.com/380053616) implement logic to make bubble follow cursor.
widget_->SetVisible(visible);
Update(icon, text);
}

@ -42,6 +42,7 @@ class ASH_EXPORT MouseKeysBubbleController : public views::ViewObserver {
private:
friend class MouseKeysBubbleControllerTest;
friend class MouseKeysTest;
// Performs initialization if necessary.
void EnsureInitialize();

@ -71,14 +71,16 @@ MouseKeysBubbleView::MouseKeysBubbleView() {
SetLayoutManager(std::move(layout));
UseCompactMargins();
AddChildView(
CreateLabelView(std::u16string(), kColorAshTextColorPrimary, &label_));
// Add icons.
AddChildView(
CreateImageView(kSystemMenuMouseIcon, &mouse_button_change_icon_));
// TODO(crbug.com/380053616): Change to the correct drag icon.
AddChildView(CreateImageView(kWmModeGestureResizeIcon, &mouse_drag_icon_));
// Add label.
AddChildView(
CreateLabelView(std::u16string(), kColorAshTextColorPrimary, &label_));
GetViewAccessibility().SetRole(ax::mojom::Role::kGenericContainer);
// Note: this static variable is used so that this view can be identified
// from tests. Do not change this, as it will cause test failures.
@ -114,6 +116,15 @@ std::u16string MouseKeysBubbleView::GetTextForTesting() const {
return label_->GetText();
}
views::ImageView* MouseKeysBubbleView::GetMouseButtonChangeIconForTesting()
const {
return mouse_button_change_icon_;
}
views::ImageView* MouseKeysBubbleView::GetMouseDragIconForTesting() const {
return mouse_drag_icon_;
}
BEGIN_METADATA(MouseKeysBubbleView)
END_METADATA
} // namespace ash

@ -41,6 +41,8 @@ class ASH_EXPORT MouseKeysBubbleView : public views::BubbleDialogDelegateView {
views::Widget* widget) const override;
std::u16string GetTextForTesting() const;
views::ImageView* GetMouseButtonChangeIconForTesting() const;
views::ImageView* GetMouseDragIconForTesting() const;
private:
raw_ptr<views::Label> label_ = nullptr;