blob: a41589a092c79af598a9c10a435a23016cb11665 [file] [log] [blame]
// Copyright 2014 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 "remoting/host/input_injector_chromeos.h"
#include <set>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "remoting/host/chromeos/point_transformer.h"
#include "remoting/host/clipboard.h"
#include "remoting/proto/internal.pb.h"
#include "ui/base/ime/chromeos/ime_keyboard.h"
#include "ui/base/ime/chromeos/input_method_manager.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
#include "ui/events/system_input_injector.h"
namespace remoting {
using protocol::ClipboardEvent;
using protocol::KeyEvent;
using protocol::MouseEvent;
using protocol::TextEvent;
using protocol::TouchEvent;
namespace {
ui::EventFlags MouseButtonToUIFlags(MouseEvent::MouseButton button) {
switch (button) {
case MouseEvent::BUTTON_LEFT:
return ui::EF_LEFT_MOUSE_BUTTON;
case MouseEvent::BUTTON_RIGHT:
return ui::EF_RIGHT_MOUSE_BUTTON;
case MouseEvent::BUTTON_MIDDLE:
return ui::EF_MIDDLE_MOUSE_BUTTON;
default:
NOTREACHED();
return ui::EF_NONE;
}
}
// Check if the given key could be mapped to caps lock
bool IsLockKey(ui::DomCode dom_code) {
switch (dom_code) {
// Ignores all the keys that could possibly be mapped to Caps Lock in event
// rewriter. Please refer to ui::EventRewriterChromeOS::RewriteModifierKeys.
case ui::DomCode::F16:
case ui::DomCode::CAPS_LOCK:
case ui::DomCode::META_LEFT:
case ui::DomCode::META_RIGHT:
case ui::DomCode::CONTROL_LEFT:
case ui::DomCode::CONTROL_RIGHT:
case ui::DomCode::ALT_LEFT:
case ui::DomCode::ALT_RIGHT:
case ui::DomCode::ESCAPE:
case ui::DomCode::BACKSPACE:
return true;
default:
return false;
}
}
// If caps_lock is specified, sets local keyboard state to match.
void SetCapsLockState(bool caps_lock) {
chromeos::input_method::InputMethodManager* ime =
chromeos::input_method::InputMethodManager::Get();
ime->GetImeKeyboard()->SetCapsLockEnabled(caps_lock);
}
} // namespace
// This class is run exclusively on the UI thread of the browser process.
class InputInjectorChromeos::Core {
public:
Core(ui::SystemInputInjectorFactory* system_input_injector_factory);
// Mirrors the public InputInjectorChromeos interface.
void InjectClipboardEvent(const ClipboardEvent& event);
void InjectKeyEvent(const KeyEvent& event);
void InjectTextEvent(const TextEvent& event);
void InjectMouseEvent(const MouseEvent& event);
void Start(std::unique_ptr<protocol::ClipboardStub> client_clipboard);
private:
void SetLockStates(uint32_t states);
std::unique_ptr<ui::SystemInputInjector> delegate_;
std::unique_ptr<Clipboard> clipboard_;
// Used to rotate the input coordinates appropriately based on the current
// display rotation settings.
std::unique_ptr<PointTransformer> point_transformer_;
// Creates |delegate_|. We store this since Core is created on one thread,
// but then Start() is run on a different one.
ui::SystemInputInjectorFactory* system_input_injector_factory_;
DISALLOW_COPY_AND_ASSIGN(Core);
};
InputInjectorChromeos::Core::Core(
ui::SystemInputInjectorFactory* system_input_injector_factory)
: system_input_injector_factory_(system_input_injector_factory) {}
void InputInjectorChromeos::Core::InjectClipboardEvent(
const ClipboardEvent& event) {
clipboard_->InjectClipboardEvent(event);
}
void InputInjectorChromeos::Core::InjectKeyEvent(const KeyEvent& event) {
DCHECK(event.has_pressed());
DCHECK(event.has_usb_keycode());
ui::DomCode dom_code =
ui::KeycodeConverter::UsbKeycodeToDomCode(event.usb_keycode());
if (event.pressed() && !IsLockKey(dom_code)) {
if (event.has_caps_lock_state()) {
SetCapsLockState(event.caps_lock_state());
} else if (event.has_lock_states()) {
SetCapsLockState((event.lock_states() &
protocol::KeyEvent::LOCK_STATES_CAPSLOCK) != 0);
}
}
// Ignore events which can't be mapped.
if (dom_code != ui::DomCode::NONE) {
delegate_->InjectKeyEvent(dom_code, event.pressed(),
true /* suppress_auto_repeat */);
}
}
void InputInjectorChromeos::Core::InjectTextEvent(const TextEvent& event) {
// Chrome OS only supports It2Me, which is not supported on mobile clients, so
// we don't need to implement text events.
NOTIMPLEMENTED();
}
void InputInjectorChromeos::Core::InjectMouseEvent(const MouseEvent& event) {
if (event.has_button() && event.has_button_down()) {
delegate_->InjectMouseButton(MouseButtonToUIFlags(event.button()),
event.button_down());
} else if (event.has_wheel_delta_y() || event.has_wheel_delta_x()) {
delegate_->InjectMouseWheel(event.wheel_delta_x(), event.wheel_delta_y());
} else {
DCHECK(event.has_x() && event.has_y());
delegate_->MoveCursorTo(point_transformer_->ToScreenCoordinates(
gfx::PointF(event.x(), event.y())));
}
}
void InputInjectorChromeos::Core::Start(
std::unique_ptr<protocol::ClipboardStub> client_clipboard) {
// OK, so we now need to plumb from ChromotingHostContext to here.
delegate_ = system_input_injector_factory_->CreateSystemInputInjector();
DCHECK(delegate_);
// Implemented by remoting::ClipboardAura.
clipboard_ = Clipboard::Create();
clipboard_->Start(std::move(client_clipboard));
point_transformer_.reset(new PointTransformer());
}
InputInjectorChromeos::InputInjectorChromeos(
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
ui::SystemInputInjectorFactory* system_input_injector_factory)
: input_task_runner_(task_runner) {
core_.reset(new Core(system_input_injector_factory));
}
InputInjectorChromeos::~InputInjectorChromeos() {
input_task_runner_->DeleteSoon(FROM_HERE, core_.release());
}
void InputInjectorChromeos::InjectClipboardEvent(const ClipboardEvent& event) {
input_task_runner_->PostTask(
FROM_HERE, base::Bind(&Core::InjectClipboardEvent,
base::Unretained(core_.get()), event));
}
void InputInjectorChromeos::InjectKeyEvent(const KeyEvent& event) {
input_task_runner_->PostTask(
FROM_HERE,
base::Bind(&Core::InjectKeyEvent, base::Unretained(core_.get()), event));
}
void InputInjectorChromeos::InjectTextEvent(const TextEvent& event) {
input_task_runner_->PostTask(
FROM_HERE,
base::Bind(&Core::InjectTextEvent, base::Unretained(core_.get()), event));
}
void InputInjectorChromeos::InjectMouseEvent(const MouseEvent& event) {
input_task_runner_->PostTask(
FROM_HERE, base::Bind(&Core::InjectMouseEvent,
base::Unretained(core_.get()), event));
}
void InputInjectorChromeos::InjectTouchEvent(const TouchEvent& event) {
NOTIMPLEMENTED() << "Raw touch event injection not implemented for ChromeOS.";
}
void InputInjectorChromeos::Start(
std::unique_ptr<protocol::ClipboardStub> client_clipboard) {
input_task_runner_->PostTask(
FROM_HERE, base::Bind(&Core::Start, base::Unretained(core_.get()),
base::Passed(&client_clipboard)));
}
// static
std::unique_ptr<InputInjector> InputInjector::Create(
scoped_refptr<base::SingleThreadTaskRunner> input_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
ui::SystemInputInjectorFactory* system_input_injector_factory) {
// The Ozone input injector must be called on the UI task runner of the
// browser process.
return base::WrapUnique(
new InputInjectorChromeos(ui_task_runner, system_input_injector_factory));
}
// static
bool InputInjector::SupportsTouchEvents() {
return false;
}
} // namespace remoting