blob: b0f715d6805d4348437f396974b93b69c956a892 [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 "ash/autoclick/autoclick_controller.h"
#include "ash/aura/wm_window_aura.h"
#include "ash/common/wm/root_window_finder.h"
#include "ash/shell.h"
#include "base/timer/timer.h"
#include "ui/aura/env.h"
#include "ui/aura/window_tree_host.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/event_handler.h"
#include "ui/events/event_processor.h"
#include "ui/events/event_utils.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/wm/core/coordinate_conversion.h"
namespace ash {
namespace {
// The threshold of mouse movement measured in DIP that will
// initiate a new autoclick.
const int kMovementThreshold = 20;
bool IsModifierKey(ui::KeyboardCode key_code) {
return key_code == ui::VKEY_SHIFT || key_code == ui::VKEY_LSHIFT ||
key_code == ui::VKEY_CONTROL || key_code == ui::VKEY_LCONTROL ||
key_code == ui::VKEY_RCONTROL || key_code == ui::VKEY_MENU ||
key_code == ui::VKEY_LMENU || key_code == ui::VKEY_RMENU;
}
} // namespace
// static.
base::TimeDelta AutoclickController::GetDefaultAutoclickDelay() {
return base::TimeDelta::FromMilliseconds(int64_t{kDefaultAutoclickDelayMs});
}
const int AutoclickController::kDefaultAutoclickDelayMs = 1000;
class AutoclickControllerImpl : public AutoclickController,
public ui::EventHandler {
public:
AutoclickControllerImpl();
~AutoclickControllerImpl() override;
private:
// AutoclickController overrides:
void SetDelegate(std::unique_ptr<Delegate> delegate) override;
void SetEnabled(bool enabled) override;
bool IsEnabled() const override;
void SetAutoclickDelay(base::TimeDelta delay) override;
base::TimeDelta GetAutoclickDelay() const override;
// ui::EventHandler overrides:
void OnMouseEvent(ui::MouseEvent* event) override;
void OnKeyEvent(ui::KeyEvent* event) override;
void OnTouchEvent(ui::TouchEvent* event) override;
void OnGestureEvent(ui::GestureEvent* event) override;
void OnScrollEvent(ui::ScrollEvent* event) override;
void StartRingDisplay();
void StopRingDisplay();
void ChangeRingDisplayCenter();
void InitClickTimer();
void DoAutoclick();
bool enabled_;
base::TimeDelta delay_;
int mouse_event_flags_;
std::unique_ptr<base::Timer> autoclick_timer_;
std::unique_ptr<Delegate> delegate_;
// The position in screen coordinates used to determine
// the distance the mouse has moved.
gfx::Point anchor_location_;
gfx::Point current_mouse_location_;
DISALLOW_COPY_AND_ASSIGN(AutoclickControllerImpl);
};
AutoclickControllerImpl::AutoclickControllerImpl()
: enabled_(false),
delay_(GetDefaultAutoclickDelay()),
mouse_event_flags_(ui::EF_NONE),
delegate_(nullptr),
anchor_location_(-kMovementThreshold, -kMovementThreshold) {
InitClickTimer();
}
AutoclickControllerImpl::~AutoclickControllerImpl() {}
void AutoclickControllerImpl::SetDelegate(std::unique_ptr<Delegate> delegate) {
delegate_ = std::move(delegate);
}
void AutoclickControllerImpl::SetEnabled(bool enabled) {
if (enabled_ == enabled)
return;
enabled_ = enabled;
if (enabled_) {
Shell::GetInstance()->AddPreTargetHandler(this);
autoclick_timer_->Stop();
} else {
Shell::GetInstance()->RemovePreTargetHandler(this);
}
}
bool AutoclickControllerImpl::IsEnabled() const {
return enabled_;
}
void AutoclickControllerImpl::SetAutoclickDelay(base::TimeDelta delay) {
delay_ = delay;
InitClickTimer();
}
base::TimeDelta AutoclickControllerImpl::GetAutoclickDelay() const {
return delay_;
}
void AutoclickControllerImpl::StartRingDisplay() {
if (delegate_)
delegate_->StartGesture(delay_, anchor_location_);
}
void AutoclickControllerImpl::StopRingDisplay() {
if (delegate_)
delegate_->StopGesture();
}
void AutoclickControllerImpl::ChangeRingDisplayCenter() {
if (delegate_)
delegate_->SetGestureCenter(current_mouse_location_);
}
void AutoclickControllerImpl::InitClickTimer() {
autoclick_timer_.reset(new base::Timer(
FROM_HERE, delay_,
base::Bind(&AutoclickControllerImpl::DoAutoclick, base::Unretained(this)),
false));
}
void AutoclickControllerImpl::OnMouseEvent(ui::MouseEvent* event) {
if (event->type() == ui::ET_MOUSE_MOVED &&
!(event->flags() & ui::EF_IS_SYNTHESIZED)) {
mouse_event_flags_ = event->flags();
gfx::Point mouse_location = event->location();
::wm::ConvertPointToScreen(static_cast<aura::Window*>(event->target()),
&mouse_location);
// The distance between the mouse location and the anchor location
// must exceed a certain threshold to initiate a new autoclick countdown.
// This ensures that mouse jitter caused by poor motor control does not
// 1. initiate an unwanted autoclick from rest
// 2. prevent the autoclick from ever occuring when the mouse
// arrives at the target.
gfx::Vector2d delta = mouse_location - anchor_location_;
if (delta.LengthSquared() >= kMovementThreshold * kMovementThreshold) {
anchor_location_ = mouse_location;
autoclick_timer_->Reset();
StartRingDisplay();
} else if (autoclick_timer_->IsRunning()) {
current_mouse_location_ = mouse_location;
ChangeRingDisplayCenter();
}
} else if (event->type() == ui::ET_MOUSE_PRESSED) {
autoclick_timer_->Stop();
StopRingDisplay();
} else if (event->type() == ui::ET_MOUSEWHEEL &&
autoclick_timer_->IsRunning()) {
autoclick_timer_->Reset();
StartRingDisplay();
}
}
void AutoclickControllerImpl::OnKeyEvent(ui::KeyEvent* event) {
int modifier_mask = ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN |
ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN |
ui::EF_IS_EXTENDED_KEY;
int new_modifiers = event->flags() & modifier_mask;
mouse_event_flags_ = (mouse_event_flags_ & ~modifier_mask) | new_modifiers;
if (!IsModifierKey(event->key_code())) {
autoclick_timer_->Stop();
StopRingDisplay();
}
}
void AutoclickControllerImpl::OnTouchEvent(ui::TouchEvent* event) {
autoclick_timer_->Stop();
StopRingDisplay();
}
void AutoclickControllerImpl::OnGestureEvent(ui::GestureEvent* event) {
autoclick_timer_->Stop();
StopRingDisplay();
}
void AutoclickControllerImpl::OnScrollEvent(ui::ScrollEvent* event) {
autoclick_timer_->Stop();
StopRingDisplay();
}
void AutoclickControllerImpl::DoAutoclick() {
gfx::Point screen_location = aura::Env::GetInstance()->last_mouse_location();
aura::Window* root_window =
WmWindowAura::GetAuraWindow(wm::GetRootWindowAt(screen_location));
DCHECK(root_window) << "Root window not found while attempting autoclick.";
gfx::Point click_location(screen_location);
anchor_location_ = click_location;
::wm::ConvertPointFromScreen(root_window, &click_location);
aura::WindowTreeHost* host = root_window->GetHost();
host->ConvertPointToHost(&click_location);
ui::MouseEvent press_event(ui::ET_MOUSE_PRESSED, click_location,
click_location, ui::EventTimeForNow(),
mouse_event_flags_ | ui::EF_LEFT_MOUSE_BUTTON,
ui::EF_LEFT_MOUSE_BUTTON);
ui::MouseEvent release_event(ui::ET_MOUSE_RELEASED, click_location,
click_location, ui::EventTimeForNow(),
mouse_event_flags_ | ui::EF_LEFT_MOUSE_BUTTON,
ui::EF_LEFT_MOUSE_BUTTON);
ui::EventDispatchDetails details =
host->event_processor()->OnEventFromSource(&press_event);
if (!details.dispatcher_destroyed)
details = host->event_processor()->OnEventFromSource(&release_event);
if (details.dispatcher_destroyed)
return;
}
// static.
AutoclickController* AutoclickController::CreateInstance() {
return new AutoclickControllerImpl();
}
} // namespace ash