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 "chrome/browser/chromeos/system/input_device_settings.h" 6 7#include "base/bind.h" 8#include "base/command_line.h" 9#include "base/files/file_path.h" 10#include "base/files/file_util.h" 11#include "base/memory/ref_counted.h" 12#include "base/message_loop/message_loop.h" 13#include "base/process/kill.h" 14#include "base/process/launch.h" 15#include "base/process/process_handle.h" 16#include "base/strings/string_util.h" 17#include "base/strings/stringprintf.h" 18#include "base/sys_info.h" 19#include "base/task_runner.h" 20#include "base/threading/sequenced_worker_pool.h" 21#include "chrome/browser/browser_process.h" 22#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h" 23#include "chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.h" 24#include "chrome/common/pref_names.h" 25#include "chromeos/system/statistics_provider.h" 26#include "content/public/browser/browser_thread.h" 27 28namespace chromeos { 29namespace system { 30 31namespace { 32 33InputDeviceSettings* g_instance_; 34InputDeviceSettings* g_test_instance_; 35 36const char kDeviceTypeTouchpad[] = "touchpad"; 37const char kDeviceTypeMouse[] = "mouse"; 38const char kInputControl[] = "/opt/google/input/inputcontrol"; 39 40typedef base::RefCountedData<bool> RefCountedBool; 41 42bool ScriptExists(const std::string& script) { 43 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); 44 return base::PathExists(base::FilePath(script)); 45} 46 47// Executes the input control script asynchronously, if it exists. 48void ExecuteScriptOnFileThread(const std::vector<std::string>& argv) { 49 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); 50 DCHECK(!argv.empty()); 51 const std::string& script(argv[0]); 52 53 // Script must exist on device. 54 DCHECK(!base::SysInfo::IsRunningOnChromeOS() || ScriptExists(script)); 55 56 if (!ScriptExists(script)) 57 return; 58 59 base::ProcessHandle handle; 60 base::LaunchProcess(CommandLine(argv), base::LaunchOptions(), &handle); 61 base::EnsureProcessGetsReaped(handle); 62} 63 64void ExecuteScript(const std::vector<std::string>& argv) { 65 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 66 67 if (argv.size() == 1) 68 return; 69 70 VLOG(1) << "About to launch: \"" 71 << CommandLine(argv).GetCommandLineString() << "\""; 72 73 // Control scripts can take long enough to cause SIGART during shutdown 74 // (http://crbug.com/261426). Run the blocking pool task with 75 // CONTINUE_ON_SHUTDOWN so it won't be joined when Chrome shuts down. 76 base::SequencedWorkerPool* pool = content::BrowserThread::GetBlockingPool(); 77 scoped_refptr<base::TaskRunner> runner = 78 pool->GetTaskRunnerWithShutdownBehavior( 79 base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN); 80 runner->PostTask(FROM_HERE, base::Bind(&ExecuteScriptOnFileThread, argv)); 81} 82 83void AddSensitivityArguments(const char* device_type, int value, 84 std::vector<std::string>* argv) { 85 DCHECK(value >= kMinPointerSensitivity && value <= kMaxPointerSensitivity); 86 argv->push_back(base::StringPrintf("--%s_sensitivity=%d", 87 device_type, value)); 88} 89 90void AddTPControlArguments(const char* control, 91 bool enabled, 92 std::vector<std::string>* argv) { 93 argv->push_back(base::StringPrintf("--%s=%d", control, enabled ? 1 : 0)); 94} 95 96void DeviceExistsBlockingPool(const char* device_type, 97 scoped_refptr<RefCountedBool> exists) { 98 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); 99 exists->data = false; 100 if (!ScriptExists(kInputControl)) 101 return; 102 103 std::vector<std::string> argv; 104 argv.push_back(kInputControl); 105 argv.push_back(base::StringPrintf("--type=%s", device_type)); 106 argv.push_back("--list"); 107 std::string output; 108 // Output is empty if the device is not found. 109 exists->data = base::GetAppOutput(CommandLine(argv), &output) && 110 !output.empty(); 111 DVLOG(1) << "DeviceExistsBlockingPool:" << device_type << "=" << exists->data; 112} 113 114void RunCallbackUIThread( 115 scoped_refptr<RefCountedBool> exists, 116 const InputDeviceSettings::DeviceExistsCallback& callback) { 117 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 118 DVLOG(1) << "RunCallbackUIThread " << exists->data; 119 callback.Run(exists->data); 120} 121 122void DeviceExists(const char* script, 123 const InputDeviceSettings::DeviceExistsCallback& callback) { 124 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 125 126 // One or both of the control scripts can apparently hang during shutdown 127 // (http://crbug.com/255546). Run the blocking pool task with 128 // CONTINUE_ON_SHUTDOWN so it won't be joined when Chrome shuts down. 129 scoped_refptr<RefCountedBool> exists(new RefCountedBool(false)); 130 base::SequencedWorkerPool* pool = content::BrowserThread::GetBlockingPool(); 131 scoped_refptr<base::TaskRunner> runner = 132 pool->GetTaskRunnerWithShutdownBehavior( 133 base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN); 134 runner->PostTaskAndReply(FROM_HERE, 135 base::Bind(&DeviceExistsBlockingPool, script, exists), 136 base::Bind(&RunCallbackUIThread, exists, callback)); 137} 138 139class InputDeviceSettingsImpl : public InputDeviceSettings { 140 public: 141 InputDeviceSettingsImpl(); 142 143 private: 144 // Overridden from InputDeviceSettings. 145 virtual void TouchpadExists(const DeviceExistsCallback& callback) OVERRIDE; 146 virtual void UpdateTouchpadSettings(const TouchpadSettings& settings) 147 OVERRIDE; 148 virtual void SetTouchpadSensitivity(int value) OVERRIDE; 149 virtual void SetTapToClick(bool enabled) OVERRIDE; 150 virtual void SetThreeFingerClick(bool enabled) OVERRIDE; 151 virtual void SetTapDragging(bool enabled) OVERRIDE; 152 virtual void SetNaturalScroll(bool enabled) OVERRIDE; 153 virtual void MouseExists(const DeviceExistsCallback& callback) OVERRIDE; 154 virtual void UpdateMouseSettings(const MouseSettings& update) OVERRIDE; 155 virtual void SetMouseSensitivity(int value) OVERRIDE; 156 virtual void SetPrimaryButtonRight(bool right) OVERRIDE; 157 virtual bool ForceKeyboardDrivenUINavigation() OVERRIDE; 158 virtual void ReapplyTouchpadSettings() OVERRIDE; 159 virtual void ReapplyMouseSettings() OVERRIDE; 160 161 private: 162 TouchpadSettings current_touchpad_settings_; 163 MouseSettings current_mouse_settings_; 164 165 DISALLOW_COPY_AND_ASSIGN(InputDeviceSettingsImpl); 166}; 167 168InputDeviceSettingsImpl::InputDeviceSettingsImpl() {} 169 170void InputDeviceSettingsImpl::TouchpadExists( 171 const DeviceExistsCallback& callback) { 172 DeviceExists(kDeviceTypeTouchpad, callback); 173} 174 175void InputDeviceSettingsImpl::UpdateTouchpadSettings( 176 const TouchpadSettings& settings) { 177 std::vector<std::string> argv; 178 if (current_touchpad_settings_.Update(settings, &argv)) 179 ExecuteScript(argv); 180} 181 182void InputDeviceSettingsImpl::SetTouchpadSensitivity(int value) { 183 TouchpadSettings settings; 184 settings.SetSensitivity(value); 185 UpdateTouchpadSettings(settings); 186} 187 188void InputDeviceSettingsImpl::SetNaturalScroll(bool enabled) { 189 TouchpadSettings settings; 190 settings.SetNaturalScroll(enabled); 191 UpdateTouchpadSettings(settings); 192} 193 194void InputDeviceSettingsImpl::SetTapToClick(bool enabled) { 195 TouchpadSettings settings; 196 settings.SetTapToClick(enabled); 197 UpdateTouchpadSettings(settings); 198} 199 200void InputDeviceSettingsImpl::SetThreeFingerClick(bool enabled) { 201 // For Alex/ZGB. 202 TouchpadSettings settings; 203 settings.SetThreeFingerClick(enabled); 204 UpdateTouchpadSettings(settings); 205} 206 207void InputDeviceSettingsImpl::SetTapDragging(bool enabled) { 208 TouchpadSettings settings; 209 settings.SetTapDragging(enabled); 210 UpdateTouchpadSettings(settings); 211} 212 213void InputDeviceSettingsImpl::MouseExists( 214 const DeviceExistsCallback& callback) { 215 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 216 DeviceExists(kDeviceTypeMouse, callback); 217} 218 219void InputDeviceSettingsImpl::UpdateMouseSettings(const MouseSettings& update) { 220 std::vector<std::string> argv; 221 if (current_mouse_settings_.Update(update, &argv)) 222 ExecuteScript(argv); 223} 224 225void InputDeviceSettingsImpl::SetMouseSensitivity(int value) { 226 MouseSettings settings; 227 settings.SetSensitivity(value); 228 UpdateMouseSettings(settings); 229} 230 231void InputDeviceSettingsImpl::SetPrimaryButtonRight(bool right) { 232 MouseSettings settings; 233 settings.SetPrimaryButtonRight(right); 234 UpdateMouseSettings(settings); 235} 236 237bool InputDeviceSettingsImpl::ForceKeyboardDrivenUINavigation() { 238 policy::BrowserPolicyConnectorChromeOS* connector = 239 g_browser_process->platform_part()->browser_policy_connector_chromeos(); 240 if (!connector) 241 return false; 242 243 policy::DeviceCloudPolicyManagerChromeOS* policy_manager = 244 connector->GetDeviceCloudPolicyManager(); 245 if (!policy_manager) 246 return false; 247 248 if (policy_manager->IsRemoraRequisition() || 249 policy_manager->IsSharkRequisition()) { 250 return true; 251 } 252 253 bool keyboard_driven = false; 254 if (chromeos::system::StatisticsProvider::GetInstance()->GetMachineFlag( 255 kOemKeyboardDrivenOobeKey, &keyboard_driven)) { 256 return keyboard_driven; 257 } 258 259 return false; 260} 261 262void InputDeviceSettingsImpl::ReapplyTouchpadSettings() { 263 TouchpadSettings settings = current_touchpad_settings_; 264 current_touchpad_settings_ = TouchpadSettings(); 265 UpdateTouchpadSettings(settings); 266} 267 268void InputDeviceSettingsImpl::ReapplyMouseSettings() { 269 MouseSettings settings = current_mouse_settings_; 270 current_mouse_settings_ = MouseSettings(); 271 UpdateMouseSettings(settings); 272} 273 274} // namespace 275 276TouchpadSettings::TouchpadSettings() {} 277 278TouchpadSettings& TouchpadSettings::operator=(const TouchpadSettings& other) { 279 if (&other != this) { 280 sensitivity_ = other.sensitivity_; 281 tap_to_click_ = other.tap_to_click_; 282 three_finger_click_ = other.three_finger_click_; 283 tap_dragging_ = other.tap_dragging_; 284 natural_scroll_ = other.natural_scroll_; 285 } 286 return *this; 287} 288 289void TouchpadSettings::SetSensitivity(int value) { 290 sensitivity_.Set(value); 291} 292 293int TouchpadSettings::GetSensitivity() const { 294 return sensitivity_.value(); 295} 296 297void TouchpadSettings::SetTapToClick(bool enabled) { 298 tap_to_click_.Set(enabled); 299} 300 301bool TouchpadSettings::GetTapToClick() const { 302 return tap_to_click_.value(); 303} 304 305void TouchpadSettings::SetNaturalScroll(bool enabled) { 306 natural_scroll_.Set(enabled); 307} 308 309bool TouchpadSettings::GetNaturalScroll() const { 310 return natural_scroll_.value(); 311} 312 313void TouchpadSettings::SetThreeFingerClick(bool enabled) { 314 three_finger_click_.Set(enabled); 315} 316 317bool TouchpadSettings::GetThreeFingerClick() const { 318 return three_finger_click_.value(); 319} 320 321void TouchpadSettings::SetTapDragging(bool enabled) { 322 tap_dragging_.Set(enabled); 323} 324 325bool TouchpadSettings::GetTapDragging() const { 326 return tap_dragging_.value(); 327} 328 329bool TouchpadSettings::Update(const TouchpadSettings& settings, 330 std::vector<std::string>* argv) { 331 if (argv) 332 argv->push_back(kInputControl); 333 bool updated = false; 334 if (sensitivity_.Update(settings.sensitivity_)) { 335 updated = true; 336 if (argv) 337 AddSensitivityArguments(kDeviceTypeTouchpad, sensitivity_.value(), argv); 338 } 339 if (tap_to_click_.Update(settings.tap_to_click_)) { 340 updated = true; 341 if (argv) 342 AddTPControlArguments("tapclick", tap_to_click_.value(), argv); 343 } 344 if (three_finger_click_.Update(settings.three_finger_click_)) { 345 updated = true; 346 if (argv) 347 AddTPControlArguments("t5r2_three_finger_click", 348 three_finger_click_.value(), 349 argv); 350 } 351 if (tap_dragging_.Update(settings.tap_dragging_)) { 352 updated = true; 353 if (argv) 354 AddTPControlArguments("tapdrag", tap_dragging_.value(), argv); 355 } 356 natural_scroll_.Update(settings.natural_scroll_); 357 // Always send natural scrolling to the shell command, as a workaround. 358 // See crbug.com/406480 359 if (natural_scroll_.is_set()) { 360 updated = true; 361 if (argv) 362 AddTPControlArguments("australian_scrolling", natural_scroll_.value(), 363 argv); 364 } 365 return updated; 366} 367 368MouseSettings::MouseSettings() {} 369 370MouseSettings& MouseSettings::operator=(const MouseSettings& other) { 371 if (&other != this) { 372 sensitivity_ = other.sensitivity_; 373 primary_button_right_ = other.primary_button_right_; 374 } 375 return *this; 376} 377 378void MouseSettings::SetSensitivity(int value) { 379 sensitivity_.Set(value); 380} 381 382int MouseSettings::GetSensitivity() const { 383 return sensitivity_.value(); 384} 385 386void MouseSettings::SetPrimaryButtonRight(bool right) { 387 primary_button_right_.Set(right); 388} 389 390bool MouseSettings::GetPrimaryButtonRight() const { 391 return primary_button_right_.value(); 392} 393 394bool MouseSettings::Update(const MouseSettings& settings, 395 std::vector<std::string>* argv) { 396 if (argv) 397 argv->push_back(kInputControl); 398 bool updated = false; 399 if (sensitivity_.Update(settings.sensitivity_)) { 400 updated = true; 401 if (argv) 402 AddSensitivityArguments(kDeviceTypeMouse, sensitivity_.value(), argv); 403 } 404 if (primary_button_right_.Update(settings.primary_button_right_)) { 405 updated = true; 406 if (argv) { 407 AddTPControlArguments("mouse_swap_lr", primary_button_right_.value(), 408 argv); 409 } 410 } 411 return updated; 412} 413 414// static 415InputDeviceSettings* InputDeviceSettings::Get() { 416 if (g_test_instance_) 417 return g_test_instance_; 418 if (!g_instance_) 419 g_instance_ = new InputDeviceSettingsImpl; 420 return g_instance_; 421} 422 423// static 424void InputDeviceSettings::SetSettingsForTesting( 425 InputDeviceSettings* test_settings) { 426 if (g_test_instance_ == test_settings) 427 return; 428 delete g_test_instance_; 429 g_test_instance_ = test_settings; 430} 431 432} // namespace system 433} // namespace chromeos 434