1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "remoting/host/win/session_input_injector.h"
6
7#include <set>
8#include <string>
9
10#include "base/bind.h"
11#include "base/callback.h"
12#include "base/compiler_specific.h"
13#include "base/location.h"
14#include "base/single_thread_task_runner.h"
15#include "base/win/windows_version.h"
16#include "remoting/host/sas_injector.h"
17#include "remoting/proto/event.pb.h"
18#include "third_party/webrtc/modules/desktop_capture/win/desktop.h"
19#include "third_party/webrtc/modules/desktop_capture/win/scoped_thread_desktop.h"
20
21namespace {
22
23const uint32 kUsbLeftControl =  0x0700e0;
24const uint32 kUsbRightControl = 0x0700e4;
25const uint32 kUsbLeftAlt =  0x0700e2;
26const uint32 kUsbRightAlt = 0x0700e6;
27const uint32 kUsbDelete = 0x07004c;
28
29bool CheckCtrlAndAltArePressed(const std::set<uint32>& pressed_keys) {
30  size_t ctrl_keys = pressed_keys.count(kUsbLeftControl) +
31    pressed_keys.count(kUsbRightControl);
32  size_t alt_keys = pressed_keys.count(kUsbLeftAlt) +
33    pressed_keys.count(kUsbRightAlt);
34  return ctrl_keys != 0 && alt_keys != 0 &&
35    (ctrl_keys + alt_keys == pressed_keys.size());
36}
37
38} // namespace
39
40namespace remoting {
41
42using protocol::ClipboardEvent;
43using protocol::KeyEvent;
44using protocol::MouseEvent;
45using protocol::TextEvent;
46
47class SessionInputInjectorWin::Core
48    : public base::RefCountedThreadSafe<SessionInputInjectorWin::Core>,
49      public InputInjector {
50 public:
51  Core(
52      scoped_refptr<base::SingleThreadTaskRunner> input_task_runner,
53      scoped_ptr<InputInjector> nested_executor,
54      scoped_refptr<base::SingleThreadTaskRunner> inject_sas_task_runner,
55      const base::Closure& inject_sas);
56
57  // InputInjector implementation.
58  virtual void Start(
59      scoped_ptr<protocol::ClipboardStub> client_clipboard) OVERRIDE;
60
61  // protocol::ClipboardStub implementation.
62  virtual void InjectClipboardEvent(
63      const protocol::ClipboardEvent& event) OVERRIDE;
64
65  // protocol::InputStub implementation.
66  virtual void InjectKeyEvent(const protocol::KeyEvent& event) OVERRIDE;
67  virtual void InjectTextEvent(const protocol::TextEvent& event) OVERRIDE;
68  virtual void InjectMouseEvent(const protocol::MouseEvent& event) OVERRIDE;
69
70 private:
71  friend class base::RefCountedThreadSafe<Core>;
72  virtual ~Core();
73
74  // Switches to the desktop receiving a user input if different from
75  // the current one.
76  void SwitchToInputDesktop();
77
78  scoped_refptr<base::SingleThreadTaskRunner> input_task_runner_;
79
80  // Pointer to the next event executor.
81  scoped_ptr<InputInjector> nested_executor_;
82
83  scoped_refptr<base::SingleThreadTaskRunner> inject_sas_task_runner_;
84
85  webrtc::ScopedThreadDesktop desktop_;
86
87  // Used to inject Secure Attention Sequence on Vista+.
88  base::Closure inject_sas_;
89
90  // Used to inject Secure Attention Sequence on XP.
91  scoped_ptr<SasInjector> sas_injector_;
92
93  // Keys currently pressed by the client, used to detect Ctrl-Alt-Del.
94  std::set<uint32> pressed_keys_;
95
96  DISALLOW_COPY_AND_ASSIGN(Core);
97};
98
99SessionInputInjectorWin::Core::Core(
100    scoped_refptr<base::SingleThreadTaskRunner> input_task_runner,
101    scoped_ptr<InputInjector> nested_executor,
102    scoped_refptr<base::SingleThreadTaskRunner> inject_sas_task_runner,
103    const base::Closure& inject_sas)
104    : input_task_runner_(input_task_runner),
105      nested_executor_(nested_executor.Pass()),
106      inject_sas_task_runner_(inject_sas_task_runner),
107      inject_sas_(inject_sas) {
108}
109
110void SessionInputInjectorWin::Core::Start(
111    scoped_ptr<protocol::ClipboardStub> client_clipboard) {
112  if (!input_task_runner_->BelongsToCurrentThread()) {
113    input_task_runner_->PostTask(
114        FROM_HERE,
115        base::Bind(&Core::Start, this, base::Passed(&client_clipboard)));
116    return;
117  }
118
119  nested_executor_->Start(client_clipboard.Pass());
120}
121
122void SessionInputInjectorWin::Core::InjectClipboardEvent(
123    const ClipboardEvent& event) {
124  if (!input_task_runner_->BelongsToCurrentThread()) {
125    input_task_runner_->PostTask(
126        FROM_HERE, base::Bind(&Core::InjectClipboardEvent, this, event));
127    return;
128  }
129
130  nested_executor_->InjectClipboardEvent(event);
131}
132
133void SessionInputInjectorWin::Core::InjectKeyEvent(const KeyEvent& event) {
134  if (!input_task_runner_->BelongsToCurrentThread()) {
135    input_task_runner_->PostTask(
136        FROM_HERE, base::Bind(&Core::InjectKeyEvent, this, event));
137    return;
138  }
139
140  // HostEventDispatcher should drop events lacking the pressed field.
141  DCHECK(event.has_pressed());
142
143  if (event.has_usb_keycode()) {
144    if (event.pressed()) {
145      // Simulate secure attention sequence if Ctrl-Alt-Del was just pressed.
146      if (event.usb_keycode() == kUsbDelete &&
147          CheckCtrlAndAltArePressed(pressed_keys_)) {
148        VLOG(3) << "Sending Secure Attention Sequence to the session";
149
150        if (base::win::GetVersion() < base::win::VERSION_VISTA) {
151          if (!sas_injector_)
152            sas_injector_ = SasInjector::Create();
153          if (!sas_injector_->InjectSas())
154            LOG(ERROR) << "Failed to inject Secure Attention Sequence.";
155        } else {
156          inject_sas_task_runner_->PostTask(FROM_HERE, inject_sas_);
157        }
158      }
159
160      pressed_keys_.insert(event.usb_keycode());
161    } else {
162      pressed_keys_.erase(event.usb_keycode());
163    }
164  }
165
166  SwitchToInputDesktop();
167  nested_executor_->InjectKeyEvent(event);
168}
169
170void SessionInputInjectorWin::Core::InjectTextEvent(const TextEvent& event) {
171  if (!input_task_runner_->BelongsToCurrentThread()) {
172    input_task_runner_->PostTask(
173        FROM_HERE, base::Bind(&Core::InjectTextEvent, this, event));
174    return;
175  }
176
177  SwitchToInputDesktop();
178  nested_executor_->InjectTextEvent(event);
179}
180
181void SessionInputInjectorWin::Core::InjectMouseEvent(const MouseEvent& event) {
182  if (!input_task_runner_->BelongsToCurrentThread()) {
183    input_task_runner_->PostTask(
184        FROM_HERE, base::Bind(&Core::InjectMouseEvent, this, event));
185    return;
186  }
187
188  SwitchToInputDesktop();
189  nested_executor_->InjectMouseEvent(event);
190}
191
192SessionInputInjectorWin::Core::~Core() {
193}
194
195void SessionInputInjectorWin::Core::SwitchToInputDesktop() {
196  // Switch to the desktop receiving user input if different from the current
197  // one.
198  scoped_ptr<webrtc::Desktop> input_desktop(
199      webrtc::Desktop::GetInputDesktop());
200  if (input_desktop.get() != NULL && !desktop_.IsSame(*input_desktop)) {
201    // If SetThreadDesktop() fails, the thread is still assigned a desktop.
202    // So we can continue capture screen bits, just from a diffected desktop.
203    desktop_.SetThreadDesktop(input_desktop.release());
204  }
205}
206
207SessionInputInjectorWin::SessionInputInjectorWin(
208    scoped_refptr<base::SingleThreadTaskRunner> input_task_runner,
209    scoped_ptr<InputInjector> nested_executor,
210    scoped_refptr<base::SingleThreadTaskRunner> inject_sas_task_runner,
211    const base::Closure& inject_sas) {
212  core_ = new Core(input_task_runner, nested_executor.Pass(),
213                   inject_sas_task_runner, inject_sas);
214}
215
216SessionInputInjectorWin::~SessionInputInjectorWin() {
217}
218
219void SessionInputInjectorWin::Start(
220    scoped_ptr<protocol::ClipboardStub> client_clipboard) {
221  core_->Start(client_clipboard.Pass());
222}
223
224void SessionInputInjectorWin::InjectClipboardEvent(
225    const protocol::ClipboardEvent& event) {
226  core_->InjectClipboardEvent(event);
227}
228
229void SessionInputInjectorWin::InjectKeyEvent(const protocol::KeyEvent& event) {
230  core_->InjectKeyEvent(event);
231}
232
233void SessionInputInjectorWin::InjectTextEvent(
234    const protocol::TextEvent& event) {
235  core_->InjectTextEvent(event);
236}
237
238void SessionInputInjectorWin::InjectMouseEvent(
239    const protocol::MouseEvent& event) {
240  core_->InjectMouseEvent(event);
241}
242
243}  // namespace remoting
244