summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/chrome_device_client.cc4
-rw-r--r--device/hid/hid_connection_linux.cc274
-rw-r--r--device/hid/hid_connection_linux.h62
-rw-r--r--device/hid/hid_connection_unittest.cc4
-rw-r--r--device/hid/hid_device_info.h4
-rw-r--r--device/hid/hid_service.cc9
-rw-r--r--device/hid/hid_service.h3
-rw-r--r--device/hid/hid_service_linux.cc379
-rw-r--r--device/hid/hid_service_linux.h38
-rw-r--r--device/hid/hid_service_unittest.cc1
-rw-r--r--extensions/browser/api/hid/hid_api.cc9
-rw-r--r--extensions/browser/api/hid/hid_apitest.cc8
-rw-r--r--extensions/browser/api/hid/hid_connection_resource.h9
-rw-r--r--extensions/browser/api/hid/hid_device_manager.cc9
-rw-r--r--extensions/shell/browser/shell_device_client.cc4
15 files changed, 510 insertions, 307 deletions
diff --git a/chrome/browser/chrome_device_client.cc b/chrome/browser/chrome_device_client.cc
index 2e23634..f93fb90 100644
--- a/chrome/browser/chrome_device_client.cc
+++ b/chrome/browser/chrome_device_client.cc
@@ -22,7 +22,5 @@ device::UsbService* ChromeDeviceClient::GetUsbService() {
device::HidService* ChromeDeviceClient::GetHidService() {
return device::HidService::GetInstance(
content::BrowserThread::GetMessageLoopProxyForThread(
- content::BrowserThread::FILE),
- content::BrowserThread::GetMessageLoopProxyForThread(
- content::BrowserThread::UI));
+ content::BrowserThread::FILE));
}
diff --git a/device/hid/hid_connection_linux.cc b/device/hid/hid_connection_linux.cc
index bec4de7..29e5cce 100644
--- a/device/hid/hid_connection_linux.cc
+++ b/device/hid/hid_connection_linux.cc
@@ -5,15 +5,17 @@
#include "device/hid/hid_connection_linux.h"
#include <errno.h>
-#include <fcntl.h>
#include <linux/hidraw.h>
#include <sys/ioctl.h>
#include <string>
+#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_pump_libevent.h"
#include "base/posix/eintr_wrapper.h"
+#include "base/thread_task_runner_handle.h"
#include "base/threading/thread_restrictions.h"
#include "base/tuple.h"
#include "device/hid/hid_service.h"
@@ -28,54 +30,110 @@
namespace device {
-HidConnectionLinux::HidConnectionLinux(HidDeviceInfo device_info,
- std::string dev_node)
- : HidConnection(device_info) {
- int flags = base::File::FLAG_OPEN |
- base::File::FLAG_READ |
- base::File::FLAG_WRITE;
-
- base::File device_file(base::FilePath(dev_node), flags);
- if (!device_file.IsValid()) {
- base::File::Error file_error = device_file.error_details();
+class HidConnectionLinux::Helper : public base::MessagePumpLibevent::Watcher {
+ public:
+ Helper(base::PlatformFile platform_file,
+ const HidDeviceInfo& device_info,
+ base::WeakPtr<HidConnectionLinux> connection,
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner)
+ : platform_file_(platform_file),
+ connection_(connection),
+ task_runner_(task_runner) {
+ if (!base::MessageLoopForIO::current()->WatchFileDescriptor(
+ platform_file_, true, base::MessageLoopForIO::WATCH_READ,
+ &file_watcher_, this)) {
+ LOG(ERROR) << "Failed to start watching device file.";
+ }
- if (file_error == base::File::FILE_ERROR_ACCESS_DENIED) {
- VLOG(1) << "Access denied opening device read-write, trying read-only.";
+ // Report buffers must always have room for the report ID.
+ report_buffer_size_ = device_info.max_input_report_size + 1;
+ has_report_id_ = device_info.has_report_id;
+ }
- flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
+ virtual ~Helper() { DCHECK(thread_checker_.CalledOnValidThread()); }
+
+ private:
+ // base::MessagePumpLibevent::Watcher implementation.
+ void OnFileCanReadWithoutBlocking(int fd) override {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_EQ(fd, platform_file_);
+
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(report_buffer_size_));
+ char* data = buffer->data();
+ size_t length = report_buffer_size_;
+ if (!has_report_id_) {
+ // Linux will not prefix the buffer with a report ID if report IDs are not
+ // used by the device. Prefix the buffer with 0.
+ *data++ = 0;
+ length--;
+ }
- device_file = base::File(base::FilePath(dev_node), flags);
+ ssize_t bytes_read = HANDLE_EINTR(read(platform_file_, data, length));
+ if (bytes_read < 0) {
+ if (errno == EAGAIN) {
+ return;
+ }
+ VPLOG(1) << "Read failed";
+ return;
}
- }
- if (!device_file.IsValid()) {
- LOG(ERROR) << "Failed to open '" << dev_node << "': "
- << base::File::ErrorToString(device_file.error_details());
- return;
+ if (!has_report_id_) {
+ // Behave as if the byte prefixed above as the the report ID was read.
+ bytes_read++;
+ }
+
+ task_runner_->PostTask(FROM_HERE,
+ base::Bind(&HidConnectionLinux::ProcessInputReport,
+ connection_, buffer, bytes_read));
}
- if (fcntl(device_file.GetPlatformFile(), F_SETFL,
- fcntl(device_file.GetPlatformFile(), F_GETFL) | O_NONBLOCK)) {
- PLOG(ERROR) << "Failed to set non-blocking flag to device file";
- return;
+ void OnFileCanWriteWithoutBlocking(int fd) override {
+ NOTREACHED(); // Only listening for reads.
}
+
+ base::ThreadChecker thread_checker_;
+ base::PlatformFile platform_file_;
+ size_t report_buffer_size_;
+ bool has_report_id_;
+ base::WeakPtr<HidConnectionLinux> connection_;
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+ base::MessagePumpLibevent::FileDescriptorWatcher file_watcher_;
+};
+
+HidConnectionLinux::HidConnectionLinux(
+ HidDeviceInfo device_info,
+ base::File device_file,
+ scoped_refptr<base::SingleThreadTaskRunner> file_task_runner)
+ : HidConnection(device_info),
+ file_task_runner_(file_task_runner),
+ weak_factory_(this) {
+ task_runner_ = base::ThreadTaskRunnerHandle::Get();
device_file_ = device_file.Pass();
- if (!base::MessageLoopForIO::current()->WatchFileDescriptor(
- device_file_.GetPlatformFile(),
- true,
- base::MessageLoopForIO::WATCH_READ_WRITE,
- &device_file_watcher_,
- this)) {
- LOG(ERROR) << "Failed to start watching device file.";
- }
+ // The helper is passed a weak pointer to this connection so that it can be
+ // cleaned up after the connection is closed. The weak pointer must be
+ // constructed on this thread.
+ file_task_runner_->PostTask(FROM_HERE,
+ base::Bind(&HidConnectionLinux::StartHelper, this,
+ weak_factory_.GetWeakPtr()));
}
HidConnectionLinux::~HidConnectionLinux() {
}
void HidConnectionLinux::PlatformClose() {
- Disconnect();
- Flush();
+ // By closing the device file on the FILE thread (1) the requirement that
+ // base::File::Close is called on a thread where I/O is allowed is satisfied
+ // and (2) any tasks posted to this task runner that refer to this file will
+ // complete before it is closed.
+ file_task_runner_->DeleteSoon(FROM_HERE, helper_.release());
+ file_task_runner_->PostTask(FROM_HERE,
+ base::Bind(&HidConnectionLinux::CloseDevice,
+ base::Passed(&device_file_)));
+
+ while (!pending_reads_.empty()) {
+ pending_reads_.front().callback.Run(false, NULL, 0);
+ pending_reads_.pop();
+ }
}
void HidConnectionLinux::PlatformRead(const ReadCallback& callback) {
@@ -90,19 +148,13 @@ void HidConnectionLinux::PlatformWrite(scoped_refptr<net::IOBuffer> buffer,
const WriteCallback& callback) {
// Linux expects the first byte of the buffer to always be a report ID so the
// buffer can be used directly.
- const ssize_t bytes_written =
- HANDLE_EINTR(write(device_file_.GetPlatformFile(), buffer->data(), size));
- if (bytes_written < 0) {
- VPLOG(1) << "Write failed";
- Disconnect();
- callback.Run(false);
- } else {
- if (static_cast<size_t>(bytes_written) != size) {
- LOG(WARNING) << "Incomplete HID write: " << bytes_written
- << " != " << size;
- }
- callback.Run(true);
- }
+ file_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&HidConnectionLinux::BlockingWrite,
+ device_file_.GetPlatformFile(), buffer, size,
+ base::Bind(&HidConnectionLinux::FinishWrite,
+ weak_factory_.GetWeakPtr(), size, callback),
+ task_runner_));
}
void HidConnectionLinux::PlatformGetFeatureReport(
@@ -115,9 +167,51 @@ void HidConnectionLinux::PlatformGetFeatureReport(
new net::IOBufferWithSize(device_info().max_feature_report_size + 1));
buffer->data()[0] = report_id;
- int result = ioctl(device_file_.GetPlatformFile(),
- HIDIOCGFEATURE(buffer->size()),
- buffer->data());
+ file_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &HidConnectionLinux::BlockingIoctl, device_file_.GetPlatformFile(),
+ HIDIOCGFEATURE(buffer->size()), buffer,
+ base::Bind(&HidConnectionLinux::FinishGetFeatureReport,
+ weak_factory_.GetWeakPtr(), report_id, buffer, callback),
+ task_runner_));
+}
+
+void HidConnectionLinux::PlatformSendFeatureReport(
+ scoped_refptr<net::IOBuffer> buffer,
+ size_t size,
+ const WriteCallback& callback) {
+ // Linux expects the first byte of the buffer to always be a report ID so the
+ // buffer can be used directly.
+ file_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&HidConnectionLinux::BlockingIoctl,
+ device_file_.GetPlatformFile(), HIDIOCSFEATURE(size), buffer,
+ base::Bind(&HidConnectionLinux::FinishSendFeatureReport,
+ weak_factory_.GetWeakPtr(), callback),
+ task_runner_));
+}
+
+void HidConnectionLinux::FinishWrite(size_t expected_size,
+ const WriteCallback& callback,
+ ssize_t result) {
+ if (result < 0) {
+ VPLOG(1) << "Write failed";
+ callback.Run(false);
+ } else {
+ if (static_cast<size_t>(result) != expected_size) {
+ LOG(WARNING) << "Incomplete HID write: " << result
+ << " != " << expected_size;
+ }
+ callback.Run(true);
+ }
+}
+
+void HidConnectionLinux::FinishGetFeatureReport(
+ uint8_t report_id,
+ scoped_refptr<net::IOBuffer> buffer,
+ const ReadCallback& callback,
+ int result) {
if (result < 0) {
VPLOG(1) << "Failed to get feature report";
callback.Run(false, NULL, 0);
@@ -134,14 +228,8 @@ void HidConnectionLinux::PlatformGetFeatureReport(
}
}
-void HidConnectionLinux::PlatformSendFeatureReport(
- scoped_refptr<net::IOBuffer> buffer,
- size_t size,
- const WriteCallback& callback) {
- // Linux expects the first byte of the buffer to always be a report ID so the
- // buffer can be used directly.
- int result = ioctl(
- device_file_.GetPlatformFile(), HIDIOCSFEATURE(size), buffer->data());
+void HidConnectionLinux::FinishSendFeatureReport(const WriteCallback& callback,
+ int result) {
if (result < 0) {
VPLOG(1) << "Failed to send feature report";
callback.Run(false);
@@ -150,55 +238,41 @@ void HidConnectionLinux::PlatformSendFeatureReport(
}
}
-void HidConnectionLinux::OnFileCanReadWithoutBlocking(int fd) {
- DCHECK(thread_checker().CalledOnValidThread());
- DCHECK_EQ(fd, device_file_.GetPlatformFile());
-
- size_t expected_report_size = device_info().max_input_report_size + 1;
- scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(expected_report_size));
- char* data = buffer->data();
- if (!device_info().has_report_id) {
- // Linux will not prefix the buffer with a report ID if they are not used
- // by the device.
- data[0] = 0;
- data++;
- expected_report_size--;
- }
-
- ssize_t bytes_read = HANDLE_EINTR(
- read(device_file_.GetPlatformFile(), data, expected_report_size));
- if (bytes_read < 0) {
- if (errno == EAGAIN) {
- return;
- }
- VPLOG(1) << "Read failed";
- Disconnect();
- return;
- }
- if (!device_info().has_report_id) {
- // Include the byte prepended earlier.
- bytes_read++;
- }
+void HidConnectionLinux::StartHelper(
+ base::WeakPtr<HidConnectionLinux> weak_ptr) {
+ base::ThreadRestrictions::AssertIOAllowed();
- ProcessInputReport(buffer, bytes_read);
+ helper_.reset(new Helper(device_file_.GetPlatformFile(), device_info(),
+ weak_ptr, task_runner_));
}
-void HidConnectionLinux::OnFileCanWriteWithoutBlocking(int fd) {
+// static
+void HidConnectionLinux::BlockingWrite(
+ base::PlatformFile platform_file,
+ scoped_refptr<net::IOBuffer> buffer,
+ size_t size,
+ const InternalWriteCallback& callback,
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
+ base::ThreadRestrictions::AssertIOAllowed();
+ ssize_t result = HANDLE_EINTR(write(platform_file, buffer->data(), size));
+ task_runner->PostTask(FROM_HERE, base::Bind(callback, result));
}
-void HidConnectionLinux::Disconnect() {
- DCHECK(thread_checker().CalledOnValidThread());
- device_file_watcher_.StopWatchingFileDescriptor();
- device_file_.Close();
-
- Flush();
+// static
+void HidConnectionLinux::BlockingIoctl(
+ base::PlatformFile platform_file,
+ int request,
+ scoped_refptr<net::IOBuffer> buffer,
+ const IoctlCallback& callback,
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
+ base::ThreadRestrictions::AssertIOAllowed();
+ int result = ioctl(platform_file, request, buffer->data());
+ task_runner->PostTask(FROM_HERE, base::Bind(callback, result));
}
-void HidConnectionLinux::Flush() {
- while (!pending_reads_.empty()) {
- pending_reads_.front().callback.Run(false, NULL, 0);
- pending_reads_.pop();
- }
+// static
+void HidConnectionLinux::CloseDevice(base::File device_file) {
+ device_file.Close();
}
void HidConnectionLinux::ProcessInputReport(scoped_refptr<net::IOBuffer> buffer,
diff --git a/device/hid/hid_connection_linux.h b/device/hid/hid_connection_linux.h
index 4850d6a..d6befe4 100644
--- a/device/hid/hid_connection_linux.h
+++ b/device/hid/hid_connection_linux.h
@@ -8,18 +8,30 @@
#include <queue>
#include "base/files/file.h"
-#include "base/message_loop/message_pump_libevent.h"
+#include "base/memory/weak_ptr.h"
#include "device/hid/hid_connection.h"
+namespace base {
+class SingleThreadTaskRunner;
+}
+
namespace device {
-class HidConnectionLinux : public HidConnection,
- public base::MessagePumpLibevent::Watcher {
+class HidConnectionLinux : public HidConnection {
public:
- HidConnectionLinux(HidDeviceInfo device_info, std::string dev_node);
+ HidConnectionLinux(
+ HidDeviceInfo device_info,
+ base::File device_file,
+ scoped_refptr<base::SingleThreadTaskRunner> file_thread_runner);
private:
+ class Helper;
+ friend class Helper;
friend class base::RefCountedThreadSafe<HidConnectionLinux>;
+
+ typedef base::Callback<void(ssize_t)> InternalWriteCallback;
+ typedef base::Callback<void(int)> IoctlCallback;
+
~HidConnectionLinux() override;
// HidConnection implementation.
@@ -34,22 +46,52 @@ class HidConnectionLinux : public HidConnection,
size_t size,
const WriteCallback& callback) override;
- // base::MessagePumpLibevent::Watcher implementation.
- void OnFileCanReadWithoutBlocking(int fd) override;
- void OnFileCanWriteWithoutBlocking(int fd) override;
+ // Callbacks for blocking operations run on the FILE thread.
+ void FinishWrite(size_t expected_size,
+ const WriteCallback& callback,
+ ssize_t result);
+ void FinishGetFeatureReport(uint8_t report_id,
+ scoped_refptr<net::IOBuffer> buffer,
+ const ReadCallback& callback,
+ int result);
+ void FinishSendFeatureReport(const WriteCallback& callback, int result);
+
+ // Starts the FileDescriptorWatcher that reads input events from the device.
+ // Must be called on a thread that has a base::MessageLoopForIO.
+ void StartHelper(base::WeakPtr<HidConnectionLinux> weak_ptr);
- void Disconnect();
+ // Writes to the device. This operation may block.
+ static void BlockingWrite(
+ base::PlatformFile platform_file,
+ scoped_refptr<net::IOBuffer> buffer,
+ size_t size,
+ const InternalWriteCallback& callback,
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner);
+ // Performs an ioctl on the device. This operation may block.
+ static void BlockingIoctl(
+ base::PlatformFile platform_file,
+ int request,
+ scoped_refptr<net::IOBuffer> buffer,
+ const IoctlCallback& callback,
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner);
+
+ // Closes the device file descriptor. Must be called on the FILE thread.
+ static void CloseDevice(base::File device_file);
- void Flush();
void ProcessInputReport(scoped_refptr<net::IOBuffer> buffer, size_t size);
void ProcessReadQueue();
base::File device_file_;
- base::MessagePumpLibevent::FileDescriptorWatcher device_file_watcher_;
+ scoped_ptr<Helper> helper_;
+
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+ scoped_refptr<base::SingleThreadTaskRunner> file_task_runner_;
std::queue<PendingHidReport> pending_reports_;
std::queue<PendingHidRead> pending_reads_;
+ base::WeakPtrFactory<HidConnectionLinux> weak_factory_;
+
DISALLOW_COPY_AND_ASSIGN(HidConnectionLinux);
};
diff --git a/device/hid/hid_connection_unittest.cc b/device/hid/hid_connection_unittest.cc
index a8530ee..fbacb52 100644
--- a/device/hid/hid_connection_unittest.cc
+++ b/device/hid/hid_connection_unittest.cc
@@ -98,9 +98,7 @@ class HidConnectionTest : public testing::Test {
if (!UsbTestGadget::IsTestEnabled()) return;
message_loop_.reset(new base::MessageLoopForIO());
- service_ = HidService::GetInstance(
- message_loop_->message_loop_proxy(),
- message_loop_->message_loop_proxy());
+ service_ = HidService::GetInstance(message_loop_->message_loop_proxy());
ASSERT_TRUE(service_);
test_gadget_ = UsbTestGadget::Claim();
diff --git a/device/hid/hid_device_info.h b/device/hid/hid_device_info.h
index bb1cc04..917a129 100644
--- a/device/hid/hid_device_info.h
+++ b/device/hid/hid_device_info.h
@@ -39,6 +39,10 @@ struct HidDeviceInfo {
size_t max_input_report_size;
size_t max_output_report_size;
size_t max_feature_report_size;
+
+#if defined(OS_LINUX)
+ std::string device_node;
+#endif
};
} // namespace device
diff --git a/device/hid/hid_service.cc b/device/hid/hid_service.cc
index 071b396..78273ae6 100644
--- a/device/hid/hid_service.cc
+++ b/device/hid/hid_service.cc
@@ -4,9 +4,7 @@
#include "device/hid/hid_service.h"
-#include "base/lazy_instance.h"
#include "base/logging.h"
-#include "base/memory/scoped_vector.h"
#include "base/message_loop/message_loop.h"
#include "base/stl_util.h"
@@ -45,11 +43,10 @@ class HidService::Destroyer : public base::MessageLoop::DestructionObserver {
};
HidService* HidService::GetInstance(
- scoped_refptr<base::SingleThreadTaskRunner> file_task_runner,
- scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) {
+ scoped_refptr<base::SingleThreadTaskRunner> file_task_runner) {
if (g_service == NULL) {
#if defined(OS_LINUX) && defined(USE_UDEV)
- g_service = new HidServiceLinux(ui_task_runner);
+ g_service = new HidServiceLinux(file_task_runner);
#elif defined(OS_MACOSX)
g_service = new HidServiceMac(file_task_runner);
#elif defined(OS_WIN)
@@ -87,6 +84,7 @@ void HidService::GetDevices(std::vector<HidDeviceInfo>* devices) {
// Fills in the device info struct of the given device_id.
bool HidService::GetDeviceInfo(const HidDeviceId& device_id,
HidDeviceInfo* info) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
DeviceMap::const_iterator it = devices_.find(device_id);
if (it == devices_.end())
return false;
@@ -95,7 +93,6 @@ bool HidService::GetDeviceInfo(const HidDeviceId& device_id,
}
HidService::HidService() {
- DCHECK(thread_checker_.CalledOnValidThread());
}
void HidService::AddDevice(const HidDeviceInfo& info) {
diff --git a/device/hid/hid_service.h b/device/hid/hid_service.h
index c01fcbc..2df2864 100644
--- a/device/hid/hid_service.h
+++ b/device/hid/hid_service.h
@@ -24,8 +24,7 @@ class HidService {
ConnectCallback;
static HidService* GetInstance(
- scoped_refptr<base::SingleThreadTaskRunner> file_task_runner,
- scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner);
+ scoped_refptr<base::SingleThreadTaskRunner> file_task_runner);
static void SetInstanceForTest(HidService* instance);
diff --git a/device/hid/hid_service_linux.cc b/device/hid/hid_service_linux.cc
index 45688f5..1319331 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,275 @@ 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:
+ 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() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ }
-} // namespace
+ private:
+ // DeviceMonitorLinux::Observer:
+ void OnDeviceAdded(udev_device* device) override {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ 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) {
+ 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 {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ 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 {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ base::MessageLoop::current()->RemoveDestructionObserver(this);
+ delete this;
}
- if (HexStringToUInt(base::StringPiece(parts[1]), &int_property)) {
- device_info.vendor_id = int_property;
- }
+ base::ThreadChecker thread_checker_;
+ 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(&params)));
+ } 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(&params)));
+}
- 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(&params)));
+ } 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(&params)));
+}
+
+// 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
diff --git a/device/hid/hid_service_linux.h b/device/hid/hid_service_linux.h
index 80239c4..59b6036 100644
--- a/device/hid/hid_service_linux.h
+++ b/device/hid/hid_service_linux.h
@@ -8,40 +8,50 @@
#include "base/compiler_specific.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
-#include "device/hid/device_monitor_linux.h"
#include "device/hid/hid_device_info.h"
#include "device/hid/hid_service.h"
-struct udev_device;
-
namespace device {
class HidConnection;
-class HidServiceLinux : public HidService,
- public DeviceMonitorLinux::Observer {
+class HidServiceLinux : public HidService {
public:
HidServiceLinux(scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner);
void Connect(const HidDeviceId& device_id,
const ConnectCallback& callback) override;
- // Implements DeviceMonitorLinux::Observer:
- void OnDeviceAdded(udev_device* device) override;
- void OnDeviceRemoved(udev_device* device) override;
-
private:
+ struct ConnectParams;
+ class Helper;
+ friend class Helper;
+
~HidServiceLinux() override;
- void FinishConnect(const HidDeviceId& device_id,
- const std::string device_node,
- const ConnectCallback& callback,
- bool success);
+ // Constructs this services helper object that lives on the FILE thread.
+ static void StartHelper(
+ base::WeakPtr<HidServiceLinux> weak_ptr,
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner);
+
+ // These functions implement the process of locating, requesting access to and
+ // opening a device. Because this operation crosses multiple threads these
+ // functions are static and the necessary parameters are passed as a single
+ // struct.
+#if defined(OS_CHROMEOS)
+ static void OnRequestPathAccessComplete(scoped_ptr<ConnectParams> params,
+ bool success);
+#endif // defined(OS_CHROMEOS)
+ static void OpenDevice(scoped_ptr<ConnectParams> params);
+ static void ConnectImpl(scoped_ptr<ConnectParams> params);
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
- scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_;
+ scoped_refptr<base::SingleThreadTaskRunner> file_task_runner_;
+ // The helper lives on the FILE thread and holds a weak reference back to the
+ // service that owns it.
base::WeakPtrFactory<HidServiceLinux> weak_factory_;
+ scoped_ptr<Helper> helper_;
DISALLOW_COPY_AND_ASSIGN(HidServiceLinux);
};
diff --git a/device/hid/hid_service_unittest.cc b/device/hid/hid_service_unittest.cc
index 6e82ef1e..2291300 100644
--- a/device/hid/hid_service_unittest.cc
+++ b/device/hid/hid_service_unittest.cc
@@ -14,7 +14,6 @@ namespace device {
TEST(HidServiceTest, Create) {
base::MessageLoopForIO message_loop;
HidService* service = HidService::GetInstance(
- message_loop.message_loop_proxy(),
message_loop.message_loop_proxy());
ASSERT_TRUE(service);
diff --git a/extensions/browser/api/hid/hid_api.cc b/extensions/browser/api/hid/hid_api.cc
index fcb8abf..27d495d 100644
--- a/extensions/browser/api/hid/hid_api.cc
+++ b/extensions/browser/api/hid/hid_api.cc
@@ -64,12 +64,11 @@ HidAsyncApiFunction::HidAsyncApiFunction()
HidAsyncApiFunction::~HidAsyncApiFunction() {}
bool HidAsyncApiFunction::PrePrepare() {
-#if defined(OS_MACOSX)
- // Migration from FILE thread to UI thread. OS X gets it first.
- set_work_thread_id(content::BrowserThread::UI);
-#else
- // TODO(reillyg): Migrate Linux/CrOS and Windows as well.
+#if defined(OS_WIN)
+ // TODO(reillyg): Migrate Windows backend from FILE thread to UI thread.
set_work_thread_id(content::BrowserThread::FILE);
+#else
+ set_work_thread_id(content::BrowserThread::UI);
#endif
device_manager_ = HidDeviceManager::Get(browser_context());
if (!device_manager_) {
diff --git a/extensions/browser/api/hid/hid_apitest.cc b/extensions/browser/api/hid/hid_apitest.cc
index ad91648..0f70ff7 100644
--- a/extensions/browser/api/hid/hid_apitest.cc
+++ b/extensions/browser/api/hid/hid_apitest.cc
@@ -169,12 +169,16 @@ class HidApiTest : public ShellApiTest {
public:
void SetUpOnMainThread() override {
ShellApiTest::SetUpOnMainThread();
+#if defined(OS_WIN)
+ // TODO(reillyg): Migrate Windows backend from FILE thread to UI thread.
base::RunLoop run_loop;
- BrowserThread::PostTaskAndReply(BrowserThread::FILE,
- FROM_HERE,
+ BrowserThread::PostTaskAndReply(BrowserThread::FILE, FROM_HERE,
base::Bind(&HidApiTest::SetUpService, this),
run_loop.QuitClosure());
run_loop.Run();
+#else
+ SetUpService();
+#endif
}
void SetUpService() { HidService::SetInstanceForTest(new MockHidService()); }
diff --git a/extensions/browser/api/hid/hid_connection_resource.h b/extensions/browser/api/hid/hid_connection_resource.h
index d2248c4..b3d8b66 100644
--- a/extensions/browser/api/hid/hid_connection_resource.h
+++ b/extensions/browser/api/hid/hid_connection_resource.h
@@ -21,14 +21,13 @@ namespace extensions {
class HidConnectionResource : public ApiResource {
public:
-#if defined(OS_MACOSX)
- // Migration from FILE thread to UI thread. OS X gets it first.
+#if defined(OS_WIN)
+ // TODO(reillyg): Migrate Windows backend from FILE thread to UI thread.
static const content::BrowserThread::ID kThreadId =
- content::BrowserThread::UI;
+ content::BrowserThread::FILE;
#else
- // TODO(reillyg): Migrate Linux/CrOS and Windows as well.
static const content::BrowserThread::ID kThreadId =
- content::BrowserThread::FILE;
+ content::BrowserThread::UI;
#endif
HidConnectionResource(const std::string& owner_extension_id,
diff --git a/extensions/browser/api/hid/hid_device_manager.cc b/extensions/browser/api/hid/hid_device_manager.cc
index 838099e..1490e87 100644
--- a/extensions/browser/api/hid/hid_device_manager.cc
+++ b/extensions/browser/api/hid/hid_device_manager.cc
@@ -148,12 +148,11 @@ bool HidDeviceManager::HasPermission(const Extension* extension,
// static
bool HidDeviceManager::IsCalledOnValidThread() {
-#if defined(OS_MACOSX)
- // Migration from FILE thread to UI thread. OS X gets it first.
- return content::BrowserThread::CurrentlyOn(content::BrowserThread::UI);
-#else
- // TODO(reillyg): Migrate Linux/CrOS and Windows as well.
+#if defined(OS_WIN)
+ // TODO(reillyg): Migrate Windows backend from FILE thread to UI thread.
return content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE);
+#else
+ return content::BrowserThread::CurrentlyOn(content::BrowserThread::UI);
#endif
}
diff --git a/extensions/shell/browser/shell_device_client.cc b/extensions/shell/browser/shell_device_client.cc
index 8055881..90e974b 100644
--- a/extensions/shell/browser/shell_device_client.cc
+++ b/extensions/shell/browser/shell_device_client.cc
@@ -24,9 +24,7 @@ device::UsbService* ShellDeviceClient::GetUsbService() {
device::HidService* ShellDeviceClient::GetHidService() {
return device::HidService::GetInstance(
content::BrowserThread::GetMessageLoopProxyForThread(
- content::BrowserThread::FILE),
- content::BrowserThread::GetMessageLoopProxyForThread(
- content::BrowserThread::UI));
+ content::BrowserThread::FILE));
}
}