blob: 2e6fbf47200a47dc3c1f42b61d799d0b9d98c790 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/system/input_device_settings.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop/message_loop.h"
#include "base/process/kill.h"
#include "base/process/launch.h"
#include "base/process/process_handle.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/sys_info.h"
#include "base/task_runner.h"
#include "base/threading/sequenced_worker_pool.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
#include "chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.h"
#include "chrome/common/pref_names.h"
#include "chromeos/system/statistics_provider.h"
#include "content/public/browser/browser_thread.h"
namespace chromeos {
namespace system {
namespace {
InputDeviceSettings* g_instance_;
InputDeviceSettings* g_test_instance_;
const char kDeviceTypeTouchpad[] = "touchpad";
const char kDeviceTypeMouse[] = "mouse";
const char kInputControl[] = "/opt/google/input/inputcontrol";
typedef base::RefCountedData<bool> RefCountedBool;
bool ScriptExists(const std::string& script) {
DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
return base::PathExists(base::FilePath(script));
}
// Executes the input control script asynchronously, if it exists.
void ExecuteScriptOnFileThread(const std::vector<std::string>& argv) {
DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
DCHECK(!argv.empty());
const std::string& script(argv[0]);
// Script must exist on device.
DCHECK(!base::SysInfo::IsRunningOnChromeOS() || ScriptExists(script));
if (!ScriptExists(script))
return;
base::ProcessHandle handle;
base::LaunchProcess(CommandLine(argv), base::LaunchOptions(), &handle);
base::EnsureProcessGetsReaped(handle);
}
void ExecuteScript(const std::vector<std::string>& argv) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
if (argv.size() == 1)
return;
VLOG(1) << "About to launch: \""
<< CommandLine(argv).GetCommandLineString() << "\"";
// Control scripts can take long enough to cause SIGART during shutdown
// (http://crbug.com/261426). Run the blocking pool task with
// CONTINUE_ON_SHUTDOWN so it won't be joined when Chrome shuts down.
base::SequencedWorkerPool* pool = content::BrowserThread::GetBlockingPool();
scoped_refptr<base::TaskRunner> runner =
pool->GetTaskRunnerWithShutdownBehavior(
base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
runner->PostTask(FROM_HERE, base::Bind(&ExecuteScriptOnFileThread, argv));
}
void AddSensitivityArguments(const char* device_type, int value,
std::vector<std::string>* argv) {
DCHECK(value >= kMinPointerSensitivity && value <= kMaxPointerSensitivity);
argv->push_back(base::StringPrintf("--%s_sensitivity=%d",
device_type, value));
}
void AddTPControlArguments(const char* control,
bool enabled,
std::vector<std::string>* argv) {
argv->push_back(base::StringPrintf("--%s=%d", control, enabled ? 1 : 0));
}
void DeviceExistsBlockingPool(const char* device_type,
scoped_refptr<RefCountedBool> exists) {
DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
exists->data = false;
if (!ScriptExists(kInputControl))
return;
std::vector<std::string> argv;
argv.push_back(kInputControl);
argv.push_back(base::StringPrintf("--type=%s", device_type));
argv.push_back("--list");
std::string output;
// Output is empty if the device is not found.
exists->data = base::GetAppOutput(CommandLine(argv), &output) &&
!output.empty();
DVLOG(1) << "DeviceExistsBlockingPool:" << device_type << "=" << exists->data;
}
void RunCallbackUIThread(
scoped_refptr<RefCountedBool> exists,
const InputDeviceSettings::DeviceExistsCallback& callback) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
DVLOG(1) << "RunCallbackUIThread " << exists->data;
callback.Run(exists->data);
}
void DeviceExists(const char* script,
const InputDeviceSettings::DeviceExistsCallback& callback) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
// One or both of the control scripts can apparently hang during shutdown
// (http://crbug.com/255546). Run the blocking pool task with
// CONTINUE_ON_SHUTDOWN so it won't be joined when Chrome shuts down.
scoped_refptr<RefCountedBool> exists(new RefCountedBool(false));
base::SequencedWorkerPool* pool = content::BrowserThread::GetBlockingPool();
scoped_refptr<base::TaskRunner> runner =
pool->GetTaskRunnerWithShutdownBehavior(
base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
runner->PostTaskAndReply(FROM_HERE,
base::Bind(&DeviceExistsBlockingPool, script, exists),
base::Bind(&RunCallbackUIThread, exists, callback));
}
class InputDeviceSettingsImpl : public InputDeviceSettings {
public:
InputDeviceSettingsImpl();
private:
// Overridden from InputDeviceSettings.
virtual void TouchpadExists(const DeviceExistsCallback& callback) override;
virtual void UpdateTouchpadSettings(const TouchpadSettings& settings)
override;
virtual void SetTouchpadSensitivity(int value) override;
virtual void SetTapToClick(bool enabled) override;
virtual void SetThreeFingerClick(bool enabled) override;
virtual void SetTapDragging(bool enabled) override;
virtual void SetNaturalScroll(bool enabled) override;
virtual void MouseExists(const DeviceExistsCallback& callback) override;
virtual void UpdateMouseSettings(const MouseSettings& update) override;
virtual void SetMouseSensitivity(int value) override;
virtual void SetPrimaryButtonRight(bool right) override;
virtual bool ForceKeyboardDrivenUINavigation() override;
virtual void ReapplyTouchpadSettings() override;
virtual void ReapplyMouseSettings() override;
private:
TouchpadSettings current_touchpad_settings_;
MouseSettings current_mouse_settings_;
DISALLOW_COPY_AND_ASSIGN(InputDeviceSettingsImpl);
};
InputDeviceSettingsImpl::InputDeviceSettingsImpl() {}
void InputDeviceSettingsImpl::TouchpadExists(
const DeviceExistsCallback& callback) {
DeviceExists(kDeviceTypeTouchpad, callback);
}
void InputDeviceSettingsImpl::UpdateTouchpadSettings(
const TouchpadSettings& settings) {
std::vector<std::string> argv;
if (current_touchpad_settings_.Update(settings, &argv))
ExecuteScript(argv);
}
void InputDeviceSettingsImpl::SetTouchpadSensitivity(int value) {
TouchpadSettings settings;
settings.SetSensitivity(value);
UpdateTouchpadSettings(settings);
}
void InputDeviceSettingsImpl::SetNaturalScroll(bool enabled) {
TouchpadSettings settings;
settings.SetNaturalScroll(enabled);
UpdateTouchpadSettings(settings);
}
void InputDeviceSettingsImpl::SetTapToClick(bool enabled) {
TouchpadSettings settings;
settings.SetTapToClick(enabled);
UpdateTouchpadSettings(settings);
}
void InputDeviceSettingsImpl::SetThreeFingerClick(bool enabled) {
// For Alex/ZGB.
TouchpadSettings settings;
settings.SetThreeFingerClick(enabled);
UpdateTouchpadSettings(settings);
}
void InputDeviceSettingsImpl::SetTapDragging(bool enabled) {
TouchpadSettings settings;
settings.SetTapDragging(enabled);
UpdateTouchpadSettings(settings);
}
void InputDeviceSettingsImpl::MouseExists(
const DeviceExistsCallback& callback) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
DeviceExists(kDeviceTypeMouse, callback);
}
void InputDeviceSettingsImpl::UpdateMouseSettings(const MouseSettings& update) {
std::vector<std::string> argv;
if (current_mouse_settings_.Update(update, &argv))
ExecuteScript(argv);
}
void InputDeviceSettingsImpl::SetMouseSensitivity(int value) {
MouseSettings settings;
settings.SetSensitivity(value);
UpdateMouseSettings(settings);
}
void InputDeviceSettingsImpl::SetPrimaryButtonRight(bool right) {
MouseSettings settings;
settings.SetPrimaryButtonRight(right);
UpdateMouseSettings(settings);
}
bool InputDeviceSettingsImpl::ForceKeyboardDrivenUINavigation() {
policy::BrowserPolicyConnectorChromeOS* connector =
g_browser_process->platform_part()->browser_policy_connector_chromeos();
if (!connector)
return false;
policy::DeviceCloudPolicyManagerChromeOS* policy_manager =
connector->GetDeviceCloudPolicyManager();
if (!policy_manager)
return false;
if (policy_manager->IsRemoraRequisition() ||
policy_manager->IsSharkRequisition()) {
return true;
}
bool keyboard_driven = false;
if (chromeos::system::StatisticsProvider::GetInstance()->GetMachineFlag(
kOemKeyboardDrivenOobeKey, &keyboard_driven)) {
return keyboard_driven;
}
return false;
}
void InputDeviceSettingsImpl::ReapplyTouchpadSettings() {
TouchpadSettings settings = current_touchpad_settings_;
current_touchpad_settings_ = TouchpadSettings();
UpdateTouchpadSettings(settings);
}
void InputDeviceSettingsImpl::ReapplyMouseSettings() {
MouseSettings settings = current_mouse_settings_;
current_mouse_settings_ = MouseSettings();
UpdateMouseSettings(settings);
}
} // namespace
TouchpadSettings::TouchpadSettings() {}
TouchpadSettings& TouchpadSettings::operator=(const TouchpadSettings& other) {
if (&other != this) {
sensitivity_ = other.sensitivity_;
tap_to_click_ = other.tap_to_click_;
three_finger_click_ = other.three_finger_click_;
tap_dragging_ = other.tap_dragging_;
natural_scroll_ = other.natural_scroll_;
}
return *this;
}
void TouchpadSettings::SetSensitivity(int value) {
sensitivity_.Set(value);
}
int TouchpadSettings::GetSensitivity() const {
return sensitivity_.value();
}
void TouchpadSettings::SetTapToClick(bool enabled) {
tap_to_click_.Set(enabled);
}
bool TouchpadSettings::GetTapToClick() const {
return tap_to_click_.value();
}
void TouchpadSettings::SetNaturalScroll(bool enabled) {
natural_scroll_.Set(enabled);
}
bool TouchpadSettings::GetNaturalScroll() const {
return natural_scroll_.value();
}
void TouchpadSettings::SetThreeFingerClick(bool enabled) {
three_finger_click_.Set(enabled);
}
bool TouchpadSettings::GetThreeFingerClick() const {
return three_finger_click_.value();
}
void TouchpadSettings::SetTapDragging(bool enabled) {
tap_dragging_.Set(enabled);
}
bool TouchpadSettings::GetTapDragging() const {
return tap_dragging_.value();
}
bool TouchpadSettings::Update(const TouchpadSettings& settings,
std::vector<std::string>* argv) {
if (argv)
argv->push_back(kInputControl);
bool updated = false;
if (sensitivity_.Update(settings.sensitivity_)) {
updated = true;
if (argv)
AddSensitivityArguments(kDeviceTypeTouchpad, sensitivity_.value(), argv);
}
if (tap_to_click_.Update(settings.tap_to_click_)) {
updated = true;
if (argv)
AddTPControlArguments("tapclick", tap_to_click_.value(), argv);
}
if (three_finger_click_.Update(settings.three_finger_click_)) {
updated = true;
if (argv)
AddTPControlArguments("t5r2_three_finger_click",
three_finger_click_.value(),
argv);
}
if (tap_dragging_.Update(settings.tap_dragging_)) {
updated = true;
if (argv)
AddTPControlArguments("tapdrag", tap_dragging_.value(), argv);
}
natural_scroll_.Update(settings.natural_scroll_);
// Always send natural scrolling to the shell command, as a workaround.
// See crbug.com/406480
if (natural_scroll_.is_set()) {
updated = true;
if (argv)
AddTPControlArguments("australian_scrolling", natural_scroll_.value(),
argv);
}
return updated;
}
MouseSettings::MouseSettings() {}
MouseSettings& MouseSettings::operator=(const MouseSettings& other) {
if (&other != this) {
sensitivity_ = other.sensitivity_;
primary_button_right_ = other.primary_button_right_;
}
return *this;
}
void MouseSettings::SetSensitivity(int value) {
sensitivity_.Set(value);
}
int MouseSettings::GetSensitivity() const {
return sensitivity_.value();
}
void MouseSettings::SetPrimaryButtonRight(bool right) {
primary_button_right_.Set(right);
}
bool MouseSettings::GetPrimaryButtonRight() const {
return primary_button_right_.value();
}
bool MouseSettings::Update(const MouseSettings& settings,
std::vector<std::string>* argv) {
if (argv)
argv->push_back(kInputControl);
bool updated = false;
if (sensitivity_.Update(settings.sensitivity_)) {
updated = true;
if (argv)
AddSensitivityArguments(kDeviceTypeMouse, sensitivity_.value(), argv);
}
if (primary_button_right_.Update(settings.primary_button_right_)) {
updated = true;
if (argv) {
AddTPControlArguments("mouse_swap_lr", primary_button_right_.value(),
argv);
}
}
return updated;
}
// static
InputDeviceSettings* InputDeviceSettings::Get() {
if (g_test_instance_)
return g_test_instance_;
if (!g_instance_)
g_instance_ = new InputDeviceSettingsImpl;
return g_instance_;
}
// static
void InputDeviceSettings::SetSettingsForTesting(
InputDeviceSettings* test_settings) {
if (g_test_instance_ == test_settings)
return;
delete g_test_instance_;
g_test_instance_ = test_settings;
}
} // namespace system
} // namespace chromeos