blob: 3908d067369acf457660d8f025cb7932da39fc8d [file] [log] [blame]
// Copyright 2014 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 <string>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/macros.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/task_scheduler/post_task.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/browser/chromeos/system/fake_input_device_settings.h"
#include "content/public/browser/browser_thread.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/devices/x11/device_data_manager_x11.h"
#include "ui/events/devices/x11/device_list_cache_x11.h"
#include "ui/events/devices/x11/touch_factory_x11.h"
#include "ui/gfx/x/x11_types.h"
namespace chromeos {
namespace system {
namespace {
InputDeviceSettings* g_instance = nullptr;
const char kDeviceTypeTouchpad[] = "touchpad";
const char kDeviceTypeMouse[] = "mouse";
const char kInputControl[] = "/opt/google/input/inputcontrol";
// The name of the xinput device corresponding to the internal touchpad.
const char kInternalTouchpadName[] = "Elan Touchpad";
typedef base::RefCountedData<bool> RefCountedBool;
bool ScriptExists(const std::string& script) {
base::ThreadRestrictions::AssertIOAllowed();
return base::PathExists(base::FilePath(script));
}
// Executes the input control script asynchronously, if it exists.
void ExecuteScriptOnFileThread(const std::vector<std::string>& argv) {
base::ThreadRestrictions::AssertIOAllowed();
DCHECK(!argv.empty());
const std::string& script(argv[0]);
// Script must exist on device and is of correct format.
DCHECK(script.compare(kInputControl) == 0);
DCHECK(!base::SysInfo::IsRunningOnChromeOS() || ScriptExists(script));
if (!ScriptExists(script))
return;
base::Process process =
base::LaunchProcess(base::CommandLine(argv), base::LaunchOptions());
if (process.IsValid())
base::EnsureProcessGetsReaped(process.Pid());
}
void ExecuteScript(const std::vector<std::string>& argv) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (argv.size() == 1)
return;
VLOG(1) << "About to launch: \""
<< base::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::PostTaskWithTraits(
FROM_HERE,
{base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&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) {
base::ThreadRestrictions::AssertIOAllowed();
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(base::CommandLine(argv), &output) && !output.empty();
DVLOG(1) << "DeviceExistsBlockingPool:" << device_type << "=" << exists->data;
}
void RunCallbackUIThread(scoped_refptr<RefCountedBool> exists,
InputDeviceSettings::DeviceExistsCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DVLOG(1) << "RunCallbackUIThread " << exists->data;
std::move(callback).Run(exists->data);
}
void DeviceExists(const char* script,
InputDeviceSettings::DeviceExistsCallback callback) {
DCHECK_CURRENTLY_ON(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::PostTaskWithTraitsAndReply(
FROM_HERE,
{base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&DeviceExistsBlockingPool, script, exists),
base::BindOnce(&RunCallbackUIThread, exists, std::move(callback)));
}
// InputDeviceSettings for Linux with X11.
class InputDeviceSettingsImplX11 : public InputDeviceSettings {
public:
InputDeviceSettingsImplX11();
protected:
~InputDeviceSettingsImplX11() override {}
private:
// Overridden from InputDeviceSettings.
void TouchpadExists(DeviceExistsCallback callback) override;
void UpdateTouchpadSettings(const TouchpadSettings& settings) override;
void SetTouchpadSensitivity(int value) override;
void SetTapToClick(bool enabled) override;
void SetThreeFingerClick(bool enabled) override;
void SetTapDragging(bool enabled) override;
void SetNaturalScroll(bool enabled) override;
void MouseExists(DeviceExistsCallback callback) override;
void UpdateMouseSettings(const MouseSettings& settings) override;
void SetMouseSensitivity(int value) override;
void SetPrimaryButtonRight(bool right) override;
void ReapplyTouchpadSettings() override;
void ReapplyMouseSettings() override;
InputDeviceSettings::FakeInterface* GetFakeInterface() override;
void SetInternalTouchpadEnabled(bool enabled) override;
void SetTouchscreensEnabled(bool enabled) override;
// Generate arguments for the inputcontrol script.
//
// |argv| is filled with arguments of script, that should be launched in order
// to apply update.
void GenerateTouchpadArguments(std::vector<std::string>* argv);
void GenerateMouseArguments(std::vector<std::string>* argv);
TouchpadSettings current_touchpad_settings_;
MouseSettings current_mouse_settings_;
DISALLOW_COPY_AND_ASSIGN(InputDeviceSettingsImplX11);
};
InputDeviceSettingsImplX11::InputDeviceSettingsImplX11() {
}
void InputDeviceSettingsImplX11::TouchpadExists(DeviceExistsCallback callback) {
DeviceExists(kDeviceTypeTouchpad, std::move(callback));
}
void InputDeviceSettingsImplX11::UpdateTouchpadSettings(
const TouchpadSettings& settings) {
std::vector<std::string> argv;
if (current_touchpad_settings_.Update(settings)) {
GenerateTouchpadArguments(&argv);
ExecuteScript(argv);
}
}
void InputDeviceSettingsImplX11::SetTouchpadSensitivity(int value) {
TouchpadSettings settings;
settings.SetSensitivity(value);
UpdateTouchpadSettings(settings);
}
void InputDeviceSettingsImplX11::SetNaturalScroll(bool enabled) {
TouchpadSettings settings;
settings.SetNaturalScroll(enabled);
UpdateTouchpadSettings(settings);
}
void InputDeviceSettingsImplX11::SetTapToClick(bool enabled) {
TouchpadSettings settings;
settings.SetTapToClick(enabled);
UpdateTouchpadSettings(settings);
}
void InputDeviceSettingsImplX11::SetThreeFingerClick(bool enabled) {
// For Alex/ZGB.
TouchpadSettings settings;
settings.SetThreeFingerClick(enabled);
UpdateTouchpadSettings(settings);
}
void InputDeviceSettingsImplX11::SetTapDragging(bool enabled) {
TouchpadSettings settings;
settings.SetTapDragging(enabled);
UpdateTouchpadSettings(settings);
}
void InputDeviceSettingsImplX11::MouseExists(DeviceExistsCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DeviceExists(kDeviceTypeMouse, std::move(callback));
}
void InputDeviceSettingsImplX11::UpdateMouseSettings(
const MouseSettings& update) {
std::vector<std::string> argv;
if (current_mouse_settings_.Update(update)) {
GenerateMouseArguments(&argv);
ExecuteScript(argv);
}
}
void InputDeviceSettingsImplX11::SetMouseSensitivity(int value) {
MouseSettings settings;
settings.SetSensitivity(value);
UpdateMouseSettings(settings);
}
void InputDeviceSettingsImplX11::SetPrimaryButtonRight(bool right) {
MouseSettings settings;
settings.SetPrimaryButtonRight(right);
UpdateMouseSettings(settings);
}
void InputDeviceSettingsImplX11::ReapplyTouchpadSettings() {
TouchpadSettings settings = current_touchpad_settings_;
current_touchpad_settings_ = TouchpadSettings();
UpdateTouchpadSettings(settings);
}
void InputDeviceSettingsImplX11::ReapplyMouseSettings() {
MouseSettings settings = current_mouse_settings_;
current_mouse_settings_ = MouseSettings();
UpdateMouseSettings(settings);
}
void InputDeviceSettingsImplX11::SetInternalTouchpadEnabled(bool enabled) {
ui::DeviceDataManagerX11* device_data_manager =
ui::DeviceDataManagerX11::GetInstance();
if (!device_data_manager->IsXInput2Available())
return;
const XIDeviceList& xi_dev_list =
ui::DeviceListCacheX11::GetInstance()->GetXI2DeviceList(
gfx::GetXDisplay());
for (int i = 0; i < xi_dev_list.count; ++i) {
std::string device_name(xi_dev_list[i].name);
base::TrimWhitespaceASCII(device_name, base::TRIM_TRAILING, &device_name);
if (device_name == kInternalTouchpadName) {
if (enabled)
device_data_manager->EnableDevice(xi_dev_list[i].deviceid);
else
device_data_manager->DisableDevice(xi_dev_list[i].deviceid);
return;
}
}
}
InputDeviceSettings::FakeInterface*
InputDeviceSettingsImplX11::GetFakeInterface() {
return nullptr;
}
void InputDeviceSettingsImplX11::SetTouchscreensEnabled(bool enabled) {
ui::TouchFactory::GetInstance()->SetTouchscreensEnabled(enabled);
}
void InputDeviceSettingsImplX11::GenerateTouchpadArguments(
std::vector<std::string>* argv) {
argv->push_back(kInputControl);
if (current_touchpad_settings_.IsSensitivitySet()) {
AddSensitivityArguments(kDeviceTypeTouchpad,
current_touchpad_settings_.GetSensitivity(), argv);
}
if (current_touchpad_settings_.IsTapToClickSet()) {
AddTPControlArguments("tapclick",
current_touchpad_settings_.GetTapToClick(), argv);
}
if (current_touchpad_settings_.IsThreeFingerClickSet()) {
AddTPControlArguments("t5r2_three_finger_click",
current_touchpad_settings_.GetThreeFingerClick(),
argv);
}
if (current_touchpad_settings_.IsTapDraggingSet()) {
AddTPControlArguments("tapdrag",
current_touchpad_settings_.GetTapDragging(), argv);
}
if (current_touchpad_settings_.IsNaturalScrollSet()) {
AddTPControlArguments("australian_scrolling",
current_touchpad_settings_.GetNaturalScroll(), argv);
}
}
void InputDeviceSettingsImplX11::GenerateMouseArguments(
std::vector<std::string>* argv) {
argv->push_back(kInputControl);
if (current_mouse_settings_.IsSensitivitySet()) {
AddSensitivityArguments(kDeviceTypeMouse,
current_mouse_settings_.GetSensitivity(), argv);
}
if (current_mouse_settings_.IsPrimaryButtonRightSet()) {
AddTPControlArguments(
"mouse_swap_lr", current_mouse_settings_.GetPrimaryButtonRight(), argv);
}
}
} // namespace
// static
InputDeviceSettings* InputDeviceSettings::Get() {
if (!g_instance) {
if (base::SysInfo::IsRunningOnChromeOS())
g_instance = new InputDeviceSettingsImplX11;
else
g_instance = new FakeInputDeviceSettings;
}
return g_instance;
}
} // namespace system
} // namespace chromeos