// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "remoting/host/win/session_input_injector.h"
#include <stddef.h>
#include <set>
#include <string>
#include <utility>
#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/task/single_thread_task_runner.h"
#include "remoting/host/sas_injector.h"
#include "remoting/proto/event.pb.h"
#include "third_party/webrtc/modules/desktop_capture/win/desktop.h"
#include "third_party/webrtc/modules/desktop_capture/win/scoped_thread_desktop.h"
#include "ui/events/keycodes/dom/dom_code.h"
namespace {
bool CheckCtrlAndAltArePressed(const std::set<ui::DomCode>& pressed_keys) {
size_t ctrl_keys = pressed_keys.count(ui::DomCode::CONTROL_LEFT) +
pressed_keys.count(ui::DomCode::CONTROL_RIGHT);
size_t alt_keys = pressed_keys.count(ui::DomCode::ALT_LEFT) +
pressed_keys.count(ui::DomCode::ALT_RIGHT);
return ctrl_keys != 0 && alt_keys != 0 &&
(ctrl_keys + alt_keys == pressed_keys.size());
}
bool IsWinKeyPressed(const std::set<ui::DomCode>& pressed_keys) {
size_t win_keys = pressed_keys.count(ui::DomCode::META_LEFT) +
pressed_keys.count(ui::DomCode::META_RIGHT);
return win_keys != 0 && win_keys == pressed_keys.size();
}
} // namespace
namespace remoting {
using protocol::ClipboardEvent;
using protocol::KeyEvent;
using protocol::MouseEvent;
using protocol::TextEvent;
using protocol::TouchEvent;
class SessionInputInjectorWin::Core
: public base::RefCountedThreadSafe<SessionInputInjectorWin::Core>,
public InputInjector {
public:
Core(scoped_refptr<base::SingleThreadTaskRunner> input_task_runner,
std::unique_ptr<InputInjector> nested_executor,
scoped_refptr<base::SingleThreadTaskRunner> inject_sas_task_runner,
const base::RepeatingClosure& inject_sas,
const base::RepeatingClosure& lock_workstation);
Core(const Core&) = delete;
Core& operator=(const Core&) = delete;
// InputInjector implementation.
void Start(std::unique_ptr<ClipboardStub> client_clipboard) override;
// protocol::ClipboardStub implementation.
void InjectClipboardEvent(const ClipboardEvent& event) override;
// protocol::InputStub implementation.
void InjectKeyEvent(const KeyEvent& event) override;
void InjectTextEvent(const TextEvent& event) override;
void InjectMouseEvent(const MouseEvent& event) override;
void InjectTouchEvent(const TouchEvent& event) override;
private:
friend class base::RefCountedThreadSafe<Core>;
~Core() override;
// Switches to the desktop receiving a user input if different from
// the current one.
void SwitchToInputDesktop();
scoped_refptr<base::SingleThreadTaskRunner> input_task_runner_;
// Pointer to the next event executor.
std::unique_ptr<InputInjector> nested_executor_;
scoped_refptr<base::SingleThreadTaskRunner> execute_action_task_runner_;
webrtc::ScopedThreadDesktop desktop_;
// Used to inject Secure Attention Sequence.
base::RepeatingClosure inject_sas_;
// Used to lock the current session on non-home SKUs of Windows.
base::RepeatingClosure lock_workstation_;
// Keys currently pressed by the client, used to detect key sequences.
std::set<ui::DomCode> pressed_keys_;
};
SessionInputInjectorWin::Core::Core(
scoped_refptr<base::SingleThreadTaskRunner> input_task_runner,
std::unique_ptr<InputInjector> nested_executor,
scoped_refptr<base::SingleThreadTaskRunner> execute_action_task_runner,
const base::RepeatingClosure& inject_sas,
const base::RepeatingClosure& lock_workstation)
: input_task_runner_(input_task_runner),
nested_executor_(std::move(nested_executor)),
execute_action_task_runner_(execute_action_task_runner),
inject_sas_(inject_sas),
lock_workstation_(lock_workstation) {}
void SessionInputInjectorWin::Core::Start(
std::unique_ptr<protocol::ClipboardStub> client_clipboard) {
if (!input_task_runner_->BelongsToCurrentThread()) {
input_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&Core::Start, this, std::move(client_clipboard)));
return;
}
nested_executor_->Start(std::move(client_clipboard));
}
void SessionInputInjectorWin::Core::InjectClipboardEvent(
const ClipboardEvent& event) {
if (!input_task_runner_->BelongsToCurrentThread()) {
input_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&Core::InjectClipboardEvent, this, event));
return;
}
nested_executor_->InjectClipboardEvent(event);
}
void SessionInputInjectorWin::Core::InjectKeyEvent(const KeyEvent& event) {
if (!input_task_runner_->BelongsToCurrentThread()) {
input_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&Core::InjectKeyEvent, this, event));
return;
}
// HostEventDispatcher should drop events lacking the pressed field.
DCHECK(event.has_pressed());
if (event.has_usb_keycode()) {
ui::DomCode dom_code = static_cast<ui::DomCode>(event.usb_keycode());
if (event.pressed()) {
// Simulate secure attention sequence if Ctrl-Alt-Del was just pressed.
if (dom_code == ui::DomCode::DEL &&
CheckCtrlAndAltArePressed(pressed_keys_)) {
VLOG(3) << "Sending Secure Attention Sequence to the session";
execute_action_task_runner_->PostTask(FROM_HERE, inject_sas_);
} else if (dom_code == ui::DomCode::US_L &&
IsWinKeyPressed(pressed_keys_)) {
execute_action_task_runner_->PostTask(FROM_HERE, lock_workstation_);
}
pressed_keys_.insert(dom_code);
} else {
pressed_keys_.erase(dom_code);
}
}
SwitchToInputDesktop();
nested_executor_->InjectKeyEvent(event);
}
void SessionInputInjectorWin::Core::InjectTextEvent(const TextEvent& event) {
if (!input_task_runner_->BelongsToCurrentThread()) {
input_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&Core::InjectTextEvent, this, event));
return;
}
SwitchToInputDesktop();
nested_executor_->InjectTextEvent(event);
}
void SessionInputInjectorWin::Core::InjectMouseEvent(const MouseEvent& event) {
if (!input_task_runner_->BelongsToCurrentThread()) {
input_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&Core::InjectMouseEvent, this, event));
return;
}
SwitchToInputDesktop();
nested_executor_->InjectMouseEvent(event);
}
void SessionInputInjectorWin::Core::InjectTouchEvent(const TouchEvent& event) {
if (!input_task_runner_->BelongsToCurrentThread()) {
input_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&Core::InjectTouchEvent, this, event));
return;
}
SwitchToInputDesktop();
nested_executor_->InjectTouchEvent(event);
}
SessionInputInjectorWin::Core::~Core() {}
void SessionInputInjectorWin::Core::SwitchToInputDesktop() {
// Switch to the desktop receiving user input if different from the current
// one.
std::unique_ptr<webrtc::Desktop> input_desktop(
webrtc::Desktop::GetInputDesktop());
if (input_desktop.get() != nullptr && !desktop_.IsSame(*input_desktop)) {
// If SetThreadDesktop() fails, the thread is still assigned a desktop.
// So we can continue capture screen bits, just from a diffected desktop.
desktop_.SetThreadDesktop(input_desktop.release());
}
}
SessionInputInjectorWin::SessionInputInjectorWin(
scoped_refptr<base::SingleThreadTaskRunner> input_task_runner,
std::unique_ptr<InputInjector> nested_executor,
scoped_refptr<base::SingleThreadTaskRunner> inject_sas_task_runner,
const base::RepeatingClosure& inject_sas,
const base::RepeatingClosure& lock_workstation) {
core_ = new Core(input_task_runner, std::move(nested_executor),
inject_sas_task_runner, inject_sas, lock_workstation);
}
SessionInputInjectorWin::~SessionInputInjectorWin() {}
void SessionInputInjectorWin::Start(
std::unique_ptr<protocol::ClipboardStub> client_clipboard) {
core_->Start(std::move(client_clipboard));
}
void SessionInputInjectorWin::InjectClipboardEvent(
const protocol::ClipboardEvent& event) {
core_->InjectClipboardEvent(event);
}
void SessionInputInjectorWin::InjectKeyEvent(const protocol::KeyEvent& event) {
core_->InjectKeyEvent(event);
}
void SessionInputInjectorWin::InjectTextEvent(
const protocol::TextEvent& event) {
core_->InjectTextEvent(event);
}
void SessionInputInjectorWin::InjectMouseEvent(
const protocol::MouseEvent& event) {
core_->InjectMouseEvent(event);
}
void SessionInputInjectorWin::InjectTouchEvent(
const protocol::TouchEvent& event) {
core_->InjectTouchEvent(event);
}
} // namespace remoting