Index: device/hid/hid_service_linux.cc |
diff --git a/device/hid/hid_service_linux.cc b/device/hid/hid_service_linux.cc |
index 45688f5b8b7f6a35a34d49277d67a49c3c4d1992..ab80f29924f7b5442d237b57e07b89181dfb1e68 100644 |
--- a/device/hid/hid_service_linux.cc |
+++ b/device/hid/hid_service_linux.cc |
@@ -4,19 +4,21 @@ |
#include "device/hid/hid_service_linux.h" |
+#include <fcntl.h> |
#include <string> |
#include "base/bind.h" |
#include "base/files/file.h" |
#include "base/files/file_path.h" |
#include "base/files/file_util.h" |
+#include "base/location.h" |
#include "base/logging.h" |
-#include "base/stl_util.h" |
+#include "base/scoped_observer.h" |
#include "base/strings/string_number_conversions.h" |
-#include "base/strings/string_piece.h" |
#include "base/strings/string_split.h" |
#include "base/thread_task_runner_handle.h" |
#include "base/threading/thread_restrictions.h" |
+#include "device/hid/device_monitor_linux.h" |
#include "device/hid/hid_connection_linux.h" |
#include "device/hid/hid_device_info.h" |
#include "device/hid/hid_report_descriptor.h" |
@@ -38,194 +40,270 @@ const char kHIDName[] = "HID_NAME"; |
const char kHIDUnique[] = "HID_UNIQ"; |
const char kSysfsReportDescriptorKey[] = "report_descriptor"; |
-#if defined(OS_CHROMEOS) |
-void OnRequestAccessComplete( |
- scoped_refptr<base::SingleThreadTaskRunner> reply_task_runner, |
- const base::Callback<void(bool success)>& callback, |
- bool success) { |
- reply_task_runner->PostTask(FROM_HERE, base::Bind(callback, success)); |
-} |
+} // namespace |
-void RequestAccess( |
- const std::string& device_node, |
- scoped_refptr<base::SingleThreadTaskRunner> reply_task_runner, |
- const base::Callback<void(bool success)>& callback) { |
- bool success = false; |
+struct HidServiceLinux::ConnectParams { |
+ ConnectParams(const HidDeviceInfo& device_info, |
+ const ConnectCallback& callback, |
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner, |
+ scoped_refptr<base::SingleThreadTaskRunner> file_task_runner) |
+ : device_info(device_info), |
+ callback(callback), |
+ task_runner(task_runner), |
+ file_task_runner(file_task_runner) {} |
+ ~ConnectParams() {} |
- if (base::SysInfo::IsRunningOnChromeOS()) { |
- chromeos::PermissionBrokerClient* client = |
- chromeos::DBusThreadManager::Get()->GetPermissionBrokerClient(); |
- DCHECK(client) << "Could not get permission broker client."; |
- if (client) { |
- client->RequestPathAccess( |
- device_node, |
- -1, |
- base::Bind(OnRequestAccessComplete, reply_task_runner, callback)); |
- return; |
- } |
- } else { |
- // Not really running on Chrome OS, declare success. |
- success = true; |
+ HidDeviceInfo device_info; |
+ ConnectCallback callback; |
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner; |
+ scoped_refptr<base::SingleThreadTaskRunner> file_task_runner; |
+ base::File device_file; |
+}; |
+ |
+class HidServiceLinux::Helper : public DeviceMonitorLinux::Observer, |
+ public base::MessageLoop::DestructionObserver, |
+ public base::NonThreadSafe { |
+ public: |
+ Helper(base::WeakPtr<HidServiceLinux> service, |
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner) |
+ : observer_(this), service_(service), task_runner_(task_runner) { |
+ DeviceMonitorLinux* monitor = DeviceMonitorLinux::GetInstance(); |
+ observer_.Add(monitor); |
+ monitor->Enumerate( |
+ base::Bind(&Helper::OnDeviceAdded, base::Unretained(this))); |
} |
- reply_task_runner->PostTask(FROM_HERE, base::Bind(callback, success)); |
-} |
-#endif |
+ virtual ~Helper() {} |
-} // namespace |
+ private: |
+ // DeviceMonitorLinux::Observer: |
+ void OnDeviceAdded(udev_device* device) override { |
+ const char* device_path = udev_device_get_syspath(device); |
+ if (!device_path) { |
+ return; |
+ } |
+ const char* subsystem = udev_device_get_subsystem(device); |
+ if (!subsystem || strcmp(subsystem, kHidrawSubsystem) != 0) { |
+ return; |
+ } |
-HidServiceLinux::HidServiceLinux( |
- scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) |
- : ui_task_runner_(ui_task_runner), |
- weak_factory_(this) { |
- base::ThreadRestrictions::AssertIOAllowed(); |
- task_runner_ = base::ThreadTaskRunnerHandle::Get(); |
- DeviceMonitorLinux* monitor = DeviceMonitorLinux::GetInstance(); |
- monitor->AddObserver(this); |
- monitor->Enumerate( |
- base::Bind(&HidServiceLinux::OnDeviceAdded, weak_factory_.GetWeakPtr())); |
-} |
+ HidDeviceInfo device_info; |
+ device_info.device_id = device_path; |
-void HidServiceLinux::Connect(const HidDeviceId& device_id, |
- const ConnectCallback& callback) { |
- DCHECK(thread_checker_.CalledOnValidThread()); |
+ const char* str_property = udev_device_get_devnode(device); |
+ if (!str_property) { |
+ return; |
+ } |
+ device_info.device_node = str_property; |
- ScopedUdevDevicePtr device = |
- DeviceMonitorLinux::GetInstance()->GetDeviceFromPath( |
- device_id); |
- if (!device) { |
- task_runner_->PostTask(FROM_HERE, base::Bind(callback, nullptr)); |
- return; |
- } |
+ udev_device* parent = udev_device_get_parent(device); |
+ if (!parent) { |
+ return; |
+ } |
- const char* device_node = udev_device_get_devnode(device.get()); |
- if (!device_node) { |
- task_runner_->PostTask(FROM_HERE, base::Bind(callback, nullptr)); |
- return; |
- } |
+ const char* hid_id = udev_device_get_property_value(parent, kHIDID); |
+ if (!hid_id) { |
+ return; |
+ } |
- base::Callback<void(bool success)> finish_connect = |
- base::Bind(&HidServiceLinux::FinishConnect, |
- weak_factory_.GetWeakPtr(), |
- device_id, |
- std::string(device_node), |
- callback); |
+ std::vector<std::string> parts; |
+ base::SplitString(hid_id, ':', &parts); |
+ if (parts.size() != 3) { |
+ return; |
+ } |
-#if defined(OS_CHROMEOS) |
- ui_task_runner_->PostTask(FROM_HERE, |
- base::Bind(RequestAccess, |
- std::string(device_node), |
- task_runner_, |
- finish_connect)); |
-#else |
- // Use the task runner to preserve the asynchronous behavior of this call on |
- // non-Chrome OS platforms. |
- task_runner_->PostTask(FROM_HERE, base::Bind(finish_connect, true)); |
-#endif |
-} |
+ uint32_t int_property = 0; |
+ if (HexStringToUInt(base::StringPiece(parts[1]), &int_property)) { |
+ device_info.vendor_id = int_property; |
+ } |
-HidServiceLinux::~HidServiceLinux() { |
- if (DeviceMonitorLinux::HasInstance()) |
- DeviceMonitorLinux::GetInstance()->RemoveObserver(this); |
-} |
+ if (HexStringToUInt(base::StringPiece(parts[2]), &int_property)) { |
+ device_info.product_id = int_property; |
+ } |
-void HidServiceLinux::OnDeviceAdded(udev_device* device) { |
- if (!device) |
- return; |
+ str_property = udev_device_get_property_value(parent, kHIDUnique); |
+ if (str_property != NULL) { |
Ken Rockot(use gerrit already)
2014/12/03 22:35:10
nit: could you nullptr these while you're here?
Reilly Grant (use Gerrit)
2014/12/03 23:10:28
Technically this is a C API and so NULL is correct
|
+ device_info.serial_number = str_property; |
+ } |
- const char* device_path = udev_device_get_syspath(device); |
- if (!device_path) |
- return; |
- const char* subsystem = udev_device_get_subsystem(device); |
- if (!subsystem || strcmp(subsystem, kHidrawSubsystem) != 0) |
- return; |
+ str_property = udev_device_get_property_value(parent, kHIDName); |
+ if (str_property != NULL) { |
+ device_info.product_name = str_property; |
+ } |
- HidDeviceInfo device_info; |
- device_info.device_id = device_path; |
+ const char* parent_sysfs_path = udev_device_get_syspath(parent); |
+ if (!parent_sysfs_path) { |
+ return; |
+ } |
+ base::FilePath report_descriptor_path = |
+ base::FilePath(parent_sysfs_path).Append(kSysfsReportDescriptorKey); |
+ std::string report_descriptor_str; |
+ if (!base::ReadFileToString(report_descriptor_path, |
+ &report_descriptor_str)) { |
+ return; |
+ } |
- uint32_t int_property = 0; |
- const char* str_property = NULL; |
+ HidReportDescriptor report_descriptor( |
+ reinterpret_cast<uint8_t*>(&report_descriptor_str[0]), |
+ report_descriptor_str.length()); |
+ report_descriptor.GetDetails( |
+ &device_info.collections, &device_info.has_report_id, |
+ &device_info.max_input_report_size, &device_info.max_output_report_size, |
+ &device_info.max_feature_report_size); |
- udev_device* parent = udev_device_get_parent(device); |
- if (!parent) { |
- return; |
+ task_runner_->PostTask(FROM_HERE, base::Bind(&HidServiceLinux::AddDevice, |
+ service_, device_info)); |
} |
- const char* hid_id = udev_device_get_property_value(parent, kHIDID); |
- if (!hid_id) { |
- return; |
+ void OnDeviceRemoved(udev_device* device) override { |
+ const char* device_path = udev_device_get_syspath(device); |
+ if (device_path) { |
+ task_runner_->PostTask( |
+ FROM_HERE, |
+ base::Bind(&HidServiceLinux::RemoveDevice, service_, device_path)); |
+ } |
} |
- std::vector<std::string> parts; |
- base::SplitString(hid_id, ':', &parts); |
- if (parts.size() != 3) { |
- return; |
+ // base::MessageLoop::DestructionObserver: |
+ void WillDestroyCurrentMessageLoop() override { |
+ base::MessageLoop::current()->RemoveDestructionObserver(this); |
+ delete this; |
} |
- if (HexStringToUInt(base::StringPiece(parts[1]), &int_property)) { |
- device_info.vendor_id = int_property; |
- } |
+ ScopedObserver<DeviceMonitorLinux, DeviceMonitorLinux::Observer> observer_; |
- if (HexStringToUInt(base::StringPiece(parts[2]), &int_property)) { |
- device_info.product_id = int_property; |
- } |
+ // This weak pointer is only valid when checked on this task runner. |
+ base::WeakPtr<HidServiceLinux> service_; |
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_; |
+}; |
- str_property = udev_device_get_property_value(parent, kHIDUnique); |
- if (str_property != NULL) { |
- device_info.serial_number = str_property; |
- } |
+HidServiceLinux::HidServiceLinux( |
+ scoped_refptr<base::SingleThreadTaskRunner> file_task_runner) |
+ : file_task_runner_(file_task_runner), weak_factory_(this) { |
+ task_runner_ = base::ThreadTaskRunnerHandle::Get(); |
+ // The device watcher is passed a weak pointer back to this service so that it |
+ // can be cleaned up after the service is destroyed however this weak pointer |
+ // must be constructed on the this thread where it will be checked. |
+ file_task_runner_->PostTask( |
+ FROM_HERE, base::Bind(&HidServiceLinux::StartHelper, |
+ weak_factory_.GetWeakPtr(), task_runner_)); |
+} |
- str_property = udev_device_get_property_value(parent, kHIDName); |
- if (str_property != NULL) { |
- device_info.product_name = str_property; |
- } |
+// static |
+void HidServiceLinux::StartHelper( |
+ base::WeakPtr<HidServiceLinux> weak_ptr, |
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner) { |
+ // Helper is a message loop destruction observer and will delete itself when |
+ // this thread's message loop is destroyed. |
+ new Helper(weak_ptr, task_runner); |
+} |
- const char* parent_sysfs_path = udev_device_get_syspath(parent); |
- if (!parent_sysfs_path) { |
+void HidServiceLinux::Connect(const HidDeviceId& device_id, |
+ const ConnectCallback& callback) { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ |
+ const auto& map_entry = devices().find(device_id); |
+ if (map_entry == devices().end()) { |
+ task_runner_->PostTask(FROM_HERE, base::Bind(callback, nullptr)); |
return; |
} |
- base::FilePath report_descriptor_path = |
- base::FilePath(parent_sysfs_path).Append(kSysfsReportDescriptorKey); |
- std::string report_descriptor_str; |
- if (!base::ReadFileToString(report_descriptor_path, &report_descriptor_str)) { |
+ const HidDeviceInfo& device_info = map_entry->second; |
+ |
+ scoped_ptr<ConnectParams> params(new ConnectParams( |
+ device_info, callback, task_runner_, file_task_runner_)); |
+ |
+#if defined(OS_CHROMEOS) |
+ if (base::SysInfo::IsRunningOnChromeOS()) { |
+ chromeos::PermissionBrokerClient* client = |
+ chromeos::DBusThreadManager::Get()->GetPermissionBrokerClient(); |
+ DCHECK(client) << "Could not get permission broker client."; |
+ if (client) { |
+ client->RequestPathAccess( |
+ device_info.device_node, -1, |
+ base::Bind(&HidServiceLinux::OnRequestPathAccessComplete, |
+ base::Passed(¶ms))); |
+ } else { |
+ task_runner_->PostTask(FROM_HERE, base::Bind(callback, nullptr)); |
+ } |
return; |
} |
+#endif // defined(OS_CHROMEOS) |
- HidReportDescriptor report_descriptor( |
- reinterpret_cast<uint8_t*>(&report_descriptor_str[0]), |
- report_descriptor_str.length()); |
- report_descriptor.GetDetails(&device_info.collections, |
- &device_info.has_report_id, |
- &device_info.max_input_report_size, |
- &device_info.max_output_report_size, |
- &device_info.max_feature_report_size); |
+ file_task_runner_->PostTask( |
+ FROM_HERE, |
+ base::Bind(&HidServiceLinux::OpenDevice, base::Passed(¶ms))); |
+} |
- AddDevice(device_info); |
+HidServiceLinux::~HidServiceLinux() { |
+ file_task_runner_->DeleteSoon(FROM_HERE, helper_.release()); |
} |
-void HidServiceLinux::OnDeviceRemoved(udev_device* device) { |
- const char* device_path = udev_device_get_syspath(device);; |
- if (device_path) { |
- RemoveDevice(device_path); |
+#if defined(OS_CHROMEOS) |
+// static |
+void HidServiceLinux::OnRequestPathAccessComplete( |
+ scoped_ptr<ConnectParams> params, |
+ bool success) { |
+ if (success) { |
+ scoped_refptr<base::SingleThreadTaskRunner> file_task_runner = |
+ params->file_task_runner; |
+ file_task_runner->PostTask( |
+ FROM_HERE, |
+ base::Bind(&HidServiceLinux::OpenDevice, base::Passed(¶ms))); |
+ } else { |
+ params->callback.Run(nullptr); |
} |
} |
+#endif // defined(OS_CHROMEOS) |
-void HidServiceLinux::FinishConnect( |
- const HidDeviceId& device_id, |
- const std::string device_node, |
- const base::Callback<void(scoped_refptr<HidConnection>)>& callback, |
- bool success) { |
- DCHECK(thread_checker_.CalledOnValidThread()); |
- if (!success) { |
- callback.Run(nullptr); |
+// static |
+void HidServiceLinux::OpenDevice(scoped_ptr<ConnectParams> params) { |
+ base::ThreadRestrictions::AssertIOAllowed(); |
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner = params->task_runner; |
+ base::FilePath device_path(params->device_info.device_node); |
+ base::File& device_file = params->device_file; |
+ int flags = |
+ base::File::FLAG_OPEN | base::File::FLAG_READ | base::File::FLAG_WRITE; |
+ device_file.Initialize(device_path, flags); |
+ if (!device_file.IsValid()) { |
+ base::File::Error file_error = device_file.error_details(); |
+ |
+ if (file_error == base::File::FILE_ERROR_ACCESS_DENIED) { |
+ VLOG(1) << "Access denied opening device read-write, trying read-only."; |
+ flags = base::File::FLAG_OPEN | base::File::FLAG_READ; |
+ device_file.Initialize(device_path, flags); |
+ } |
+ } |
+ if (!device_file.IsValid()) { |
+ LOG(ERROR) << "Failed to open '" << params->device_info.device_node << "': " |
+ << base::File::ErrorToString(device_file.error_details()); |
+ task_runner->PostTask(FROM_HERE, base::Bind(params->callback, nullptr)); |
+ return; |
} |
- const auto& map_entry = devices().find(device_id); |
- if (map_entry == devices().end()) { |
- callback.Run(nullptr); |
+ int result = fcntl(device_file.GetPlatformFile(), F_GETFL); |
+ if (result == -1) { |
+ PLOG(ERROR) << "Failed to get flags from the device file descriptor"; |
+ task_runner->PostTask(FROM_HERE, base::Bind(params->callback, nullptr)); |
+ return; |
+ } |
+ |
+ result = fcntl(device_file.GetPlatformFile(), F_SETFL, result | O_NONBLOCK); |
+ if (result == -1) { |
+ PLOG(ERROR) << "Failed to set the non-blocking flag on the device fd"; |
+ task_runner->PostTask(FROM_HERE, base::Bind(params->callback, nullptr)); |
+ return; |
} |
- callback.Run(new HidConnectionLinux(map_entry->second, device_node)); |
+ task_runner->PostTask(FROM_HERE, base::Bind(&HidServiceLinux::ConnectImpl, |
+ base::Passed(¶ms))); |
+} |
+ |
+// static |
+void HidServiceLinux::ConnectImpl(scoped_ptr<ConnectParams> params) { |
+ DCHECK(params->device_file.IsValid()); |
+ params->callback.Run(make_scoped_refptr( |
+ new HidConnectionLinux(params->device_info, params->device_file.Pass(), |
+ params->file_task_runner))); |
} |
} // namespace device |