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 21 namespace { 22 23 const uint32 kUsbLeftControl = 0x0700e0; 24 const uint32 kUsbRightControl = 0x0700e4; 25 const uint32 kUsbLeftAlt = 0x0700e2; 26 const uint32 kUsbRightAlt = 0x0700e6; 27 const uint32 kUsbDelete = 0x07004c; 28 29 bool 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 40 namespace remoting { 41 42 using protocol::ClipboardEvent; 43 using protocol::MouseEvent; 44 using protocol::KeyEvent; 45 46 class SessionInputInjectorWin::Core 47 : public base::RefCountedThreadSafe<SessionInputInjectorWin::Core>, 48 public InputInjector { 49 public: 50 Core( 51 scoped_refptr<base::SingleThreadTaskRunner> input_task_runner, 52 scoped_ptr<InputInjector> nested_executor, 53 scoped_refptr<base::SingleThreadTaskRunner> inject_sas_task_runner, 54 const base::Closure& inject_sas); 55 56 // InputInjector implementation. 57 virtual void Start( 58 scoped_ptr<protocol::ClipboardStub> client_clipboard) OVERRIDE; 59 60 // protocol::ClipboardStub implementation. 61 virtual void InjectClipboardEvent( 62 const protocol::ClipboardEvent& event) OVERRIDE; 63 64 // protocol::InputStub implementation. 65 virtual void InjectKeyEvent(const protocol::KeyEvent& event) OVERRIDE; 66 virtual void InjectMouseEvent(const protocol::MouseEvent& event) OVERRIDE; 67 68 private: 69 friend class base::RefCountedThreadSafe<Core>; 70 virtual ~Core(); 71 72 // Switches to the desktop receiving a user input if different from 73 // the current one. 74 void SwitchToInputDesktop(); 75 76 scoped_refptr<base::SingleThreadTaskRunner> input_task_runner_; 77 78 // Pointer to the next event executor. 79 scoped_ptr<InputInjector> nested_executor_; 80 81 scoped_refptr<base::SingleThreadTaskRunner> inject_sas_task_runner_; 82 83 webrtc::ScopedThreadDesktop desktop_; 84 85 // Used to inject Secure Attention Sequence on Vista+. 86 base::Closure inject_sas_; 87 88 // Used to inject Secure Attention Sequence on XP. 89 scoped_ptr<SasInjector> sas_injector_; 90 91 // Keys currently pressed by the client, used to detect Ctrl-Alt-Del. 92 std::set<uint32> pressed_keys_; 93 94 DISALLOW_COPY_AND_ASSIGN(Core); 95 }; 96 97 SessionInputInjectorWin::Core::Core( 98 scoped_refptr<base::SingleThreadTaskRunner> input_task_runner, 99 scoped_ptr<InputInjector> nested_executor, 100 scoped_refptr<base::SingleThreadTaskRunner> inject_sas_task_runner, 101 const base::Closure& inject_sas) 102 : input_task_runner_(input_task_runner), 103 nested_executor_(nested_executor.Pass()), 104 inject_sas_task_runner_(inject_sas_task_runner), 105 inject_sas_(inject_sas) { 106 } 107 108 void SessionInputInjectorWin::Core::Start( 109 scoped_ptr<protocol::ClipboardStub> client_clipboard) { 110 if (!input_task_runner_->BelongsToCurrentThread()) { 111 input_task_runner_->PostTask( 112 FROM_HERE, 113 base::Bind(&Core::Start, this, base::Passed(&client_clipboard))); 114 return; 115 } 116 117 nested_executor_->Start(client_clipboard.Pass()); 118 } 119 120 void SessionInputInjectorWin::Core::InjectClipboardEvent( 121 const ClipboardEvent& event) { 122 if (!input_task_runner_->BelongsToCurrentThread()) { 123 input_task_runner_->PostTask( 124 FROM_HERE, base::Bind(&Core::InjectClipboardEvent, this, event)); 125 return; 126 } 127 128 nested_executor_->InjectClipboardEvent(event); 129 } 130 131 void SessionInputInjectorWin::Core::InjectKeyEvent(const KeyEvent& event) { 132 if (!input_task_runner_->BelongsToCurrentThread()) { 133 input_task_runner_->PostTask( 134 FROM_HERE, base::Bind(&Core::InjectKeyEvent, this, event)); 135 return; 136 } 137 138 // HostEventDispatcher should drop events lacking the pressed field. 139 DCHECK(event.has_pressed()); 140 141 if (event.has_usb_keycode()) { 142 if (event.pressed()) { 143 // Simulate secure attention sequence if Ctrl-Alt-Del was just pressed. 144 if (event.usb_keycode() == kUsbDelete && 145 CheckCtrlAndAltArePressed(pressed_keys_)) { 146 VLOG(3) << "Sending Secure Attention Sequence to the session"; 147 148 if (base::win::GetVersion() < base::win::VERSION_VISTA) { 149 if (!sas_injector_) 150 sas_injector_ = SasInjector::Create(); 151 if (!sas_injector_->InjectSas()) 152 LOG(ERROR) << "Failed to inject Secure Attention Sequence."; 153 } else { 154 inject_sas_task_runner_->PostTask(FROM_HERE, inject_sas_); 155 } 156 } 157 158 pressed_keys_.insert(event.usb_keycode()); 159 } else { 160 pressed_keys_.erase(event.usb_keycode()); 161 } 162 } 163 164 SwitchToInputDesktop(); 165 nested_executor_->InjectKeyEvent(event); 166 } 167 168 void SessionInputInjectorWin::Core::InjectMouseEvent(const MouseEvent& event) { 169 if (!input_task_runner_->BelongsToCurrentThread()) { 170 input_task_runner_->PostTask( 171 FROM_HERE, base::Bind(&Core::InjectMouseEvent, this, event)); 172 return; 173 } 174 175 SwitchToInputDesktop(); 176 nested_executor_->InjectMouseEvent(event); 177 } 178 179 SessionInputInjectorWin::Core::~Core() { 180 } 181 182 void SessionInputInjectorWin::Core::SwitchToInputDesktop() { 183 // Switch to the desktop receiving user input if different from the current 184 // one. 185 scoped_ptr<webrtc::Desktop> input_desktop( 186 webrtc::Desktop::GetInputDesktop()); 187 if (input_desktop.get() != NULL && !desktop_.IsSame(*input_desktop)) { 188 // If SetThreadDesktop() fails, the thread is still assigned a desktop. 189 // So we can continue capture screen bits, just from a diffected desktop. 190 desktop_.SetThreadDesktop(input_desktop.release()); 191 } 192 } 193 194 SessionInputInjectorWin::SessionInputInjectorWin( 195 scoped_refptr<base::SingleThreadTaskRunner> input_task_runner, 196 scoped_ptr<InputInjector> nested_executor, 197 scoped_refptr<base::SingleThreadTaskRunner> inject_sas_task_runner, 198 const base::Closure& inject_sas) { 199 core_ = new Core(input_task_runner, nested_executor.Pass(), 200 inject_sas_task_runner, inject_sas); 201 } 202 203 SessionInputInjectorWin::~SessionInputInjectorWin() { 204 } 205 206 void SessionInputInjectorWin::Start( 207 scoped_ptr<protocol::ClipboardStub> client_clipboard) { 208 core_->Start(client_clipboard.Pass()); 209 } 210 211 void SessionInputInjectorWin::InjectClipboardEvent( 212 const protocol::ClipboardEvent& event) { 213 core_->InjectClipboardEvent(event); 214 } 215 216 void SessionInputInjectorWin::InjectKeyEvent(const protocol::KeyEvent& event) { 217 core_->InjectKeyEvent(event); 218 } 219 220 void SessionInputInjectorWin::InjectMouseEvent( 221 const protocol::MouseEvent& event) { 222 core_->InjectMouseEvent(event); 223 } 224 225 } // namespace remoting 226