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