Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(160)

Side by Side Diff: device/hid/hid_connection_linux.cc

Issue 2799743006: Remove MessageLoop destruction observer from hid_connection_linux.cc (Closed)
Patch Set: Last review comments Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « device/hid/hid_connection_linux.h ('k') | device/hid/hid_service_linux.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2014 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "device/hid/hid_connection_linux.h" 5 #include "device/hid/hid_connection_linux.h"
6 6
7 #include <errno.h> 7 #include <errno.h>
8 #include <linux/hidraw.h> 8 #include <linux/hidraw.h>
9 #include <sys/ioctl.h> 9 #include <sys/ioctl.h>
10 10
11 #include <memory> 11 #include <memory>
12 #include <string> 12 #include <string>
13 #include <utility> 13 #include <utility>
14 14
15 #include "base/bind.h" 15 #include "base/bind.h"
16 #include "base/files/file_descriptor_watcher_posix.h" 16 #include "base/files/file_descriptor_watcher_posix.h"
17 #include "base/files/file_path.h"
18 #include "base/macros.h" 17 #include "base/macros.h"
19 #include "base/message_loop/message_loop.h" 18 #include "base/memory/ptr_util.h"
20 #include "base/posix/eintr_wrapper.h" 19 #include "base/posix/eintr_wrapper.h"
21 #include "base/threading/thread_restrictions.h" 20 #include "base/threading/thread_restrictions.h"
22 #include "base/threading/thread_task_runner_handle.h" 21 #include "base/threading/thread_task_runner_handle.h"
23 #include "components/device_event_log/device_event_log.h" 22 #include "components/device_event_log/device_event_log.h"
24 #include "device/hid/hid_service.h" 23 #include "device/hid/hid_service.h"
25 24
26 // These are already defined in newer versions of linux/hidraw.h. 25 // These are already defined in newer versions of linux/hidraw.h.
27 #ifndef HIDIOCSFEATURE 26 #ifndef HIDIOCSFEATURE
28 #define HIDIOCSFEATURE(len) _IOC(_IOC_WRITE | _IOC_READ, 'H', 0x06, len) 27 #define HIDIOCSFEATURE(len) _IOC(_IOC_WRITE | _IOC_READ, 'H', 0x06, len)
29 #endif 28 #endif
30 #ifndef HIDIOCGFEATURE 29 #ifndef HIDIOCGFEATURE
31 #define HIDIOCGFEATURE(len) _IOC(_IOC_WRITE | _IOC_READ, 'H', 0x07, len) 30 #define HIDIOCGFEATURE(len) _IOC(_IOC_WRITE | _IOC_READ, 'H', 0x07, len)
32 #endif 31 #endif
33 32
34 namespace device { 33 namespace device {
35 34
36 class HidConnectionLinux::FileThreadHelper 35 class HidConnectionLinux::FileThreadHelper {
37 : public base::MessageLoop::DestructionObserver {
38 public: 36 public:
39 FileThreadHelper(base::PlatformFile platform_file, 37 FileThreadHelper(base::ScopedFD fd,
40 scoped_refptr<HidDeviceInfo> device_info, 38 scoped_refptr<HidDeviceInfo> device_info,
41 base::WeakPtr<HidConnectionLinux> connection, 39 base::WeakPtr<HidConnectionLinux> connection)
42 scoped_refptr<base::SingleThreadTaskRunner> task_runner) 40 : fd_(std::move(fd)),
43 : platform_file_(platform_file),
44 connection_(connection), 41 connection_(connection),
45 task_runner_(task_runner) { 42 origin_task_runner_(base::ThreadTaskRunnerHandle::Get()) {
43 sequence_checker_.DetachFromSequence();
46 // Report buffers must always have room for the report ID. 44 // Report buffers must always have room for the report ID.
47 report_buffer_size_ = device_info->max_input_report_size() + 1; 45 report_buffer_size_ = device_info->max_input_report_size() + 1;
48 has_report_id_ = device_info->has_report_id(); 46 has_report_id_ = device_info->has_report_id();
49 } 47 }
50 48
51 ~FileThreadHelper() override { 49 ~FileThreadHelper() { DCHECK(sequence_checker_.CalledOnValidSequence()); }
52 DCHECK(thread_checker_.CalledOnValidThread());
53 base::MessageLoop::current()->RemoveDestructionObserver(this);
54 }
55 50
56 // Starts the FileDescriptorWatcher that reads input events from the device. 51 // Starts the FileDescriptorWatcher that reads input events from the device.
57 // Must be called on a thread that has a base::MessageLoopForIO. 52 // Must be called on a thread that has a base::MessageLoopForIO.
58 static void Start(std::unique_ptr<FileThreadHelper> self) { 53 void Start() {
54 DCHECK(sequence_checker_.CalledOnValidSequence());
59 base::ThreadRestrictions::AssertIOAllowed(); 55 base::ThreadRestrictions::AssertIOAllowed();
60 self->thread_checker_.DetachFromThread();
61 56
62 self->file_watcher_ = base::FileDescriptorWatcher::WatchReadable( 57 file_watcher_ = base::FileDescriptorWatcher::WatchReadable(
63 self->platform_file_, 58 fd_.get(), base::Bind(&FileThreadHelper::OnFileCanReadWithoutBlocking,
64 base::Bind(&FileThreadHelper::OnFileCanReadWithoutBlocking, 59 base::Unretained(this)));
65 base::Unretained(self.get()))); 60 }
66 61
67 // |self| is now owned by the current message loop. 62 void Write(scoped_refptr<net::IOBuffer> buffer,
68 base::MessageLoop::current()->AddDestructionObserver(self.release()); 63 size_t size,
64 const WriteCallback& callback) {
65 DCHECK(sequence_checker_.CalledOnValidSequence());
66 ssize_t result = HANDLE_EINTR(write(fd_.get(), buffer->data(), size));
67 if (result < 0) {
68 HID_PLOG(EVENT) << "Write failed";
69 origin_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false));
70 } else {
71 if (static_cast<size_t>(result) != size)
72 HID_LOG(EVENT) << "Incomplete HID write: " << result << " != " << size;
73 origin_task_runner_->PostTask(FROM_HERE, base::Bind(callback, true));
74 }
75 }
76
77 void GetFeatureReport(uint8_t report_id,
78 scoped_refptr<net::IOBufferWithSize> buffer,
79 const ReadCallback& callback) {
80 DCHECK(sequence_checker_.CalledOnValidSequence());
81 int result = HANDLE_EINTR(
82 ioctl(fd_.get(), HIDIOCGFEATURE(buffer->size()), buffer->data()));
83 if (result < 0) {
84 HID_PLOG(EVENT) << "Failed to get feature report";
85 origin_task_runner_->PostTask(FROM_HERE,
86 base::Bind(callback, false, nullptr, 0));
87 } else if (result == 0) {
88 HID_LOG(EVENT) << "Get feature result too short.";
89 origin_task_runner_->PostTask(FROM_HERE,
90 base::Bind(callback, false, nullptr, 0));
91 } else if (report_id == 0) {
92 // Linux adds a 0 to the beginning of the data received from the device.
93 scoped_refptr<net::IOBuffer> copied_buffer(new net::IOBuffer(result - 1));
94 memcpy(copied_buffer->data(), buffer->data() + 1, result - 1);
95 origin_task_runner_->PostTask(
96 FROM_HERE, base::Bind(callback, true, copied_buffer, result - 1));
97 } else {
98 origin_task_runner_->PostTask(FROM_HERE,
99 base::Bind(callback, true, buffer, result));
100 }
101 }
102
103 void SendFeatureReport(scoped_refptr<net::IOBuffer> buffer,
104 size_t size,
105 const WriteCallback& callback) {
106 DCHECK(sequence_checker_.CalledOnValidSequence());
107 int result =
108 HANDLE_EINTR(ioctl(fd_.get(), HIDIOCSFEATURE(size), buffer->data()));
109 if (result < 0) {
110 HID_PLOG(EVENT) << "Failed to send feature report";
111 origin_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false));
112 } else {
113 origin_task_runner_->PostTask(FROM_HERE, base::Bind(callback, true));
114 }
69 } 115 }
70 116
71 private: 117 private:
72 void OnFileCanReadWithoutBlocking() { 118 void OnFileCanReadWithoutBlocking() {
73 DCHECK(thread_checker_.CalledOnValidThread()); 119 DCHECK(sequence_checker_.CalledOnValidSequence());
74 120
75 scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(report_buffer_size_)); 121 scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(report_buffer_size_));
76 char* data = buffer->data(); 122 char* data = buffer->data();
77 size_t length = report_buffer_size_; 123 size_t length = report_buffer_size_;
78 if (!has_report_id_) { 124 if (!has_report_id_) {
79 // Linux will not prefix the buffer with a report ID if report IDs are not 125 // Linux will not prefix the buffer with a report ID if report IDs are not
80 // used by the device. Prefix the buffer with 0. 126 // used by the device. Prefix the buffer with 0.
81 *data++ = 0; 127 *data++ = 0;
82 length--; 128 length--;
83 } 129 }
84 130
85 ssize_t bytes_read = HANDLE_EINTR(read(platform_file_, data, length)); 131 ssize_t bytes_read = HANDLE_EINTR(read(fd_.get(), data, length));
86 if (bytes_read < 0) { 132 if (bytes_read < 0) {
87 if (errno != EAGAIN) { 133 if (errno != EAGAIN) {
88 HID_PLOG(EVENT) << "Read failed"; 134 HID_PLOG(EVENT) << "Read failed";
89 // This assumes that the error is unrecoverable and disables reading 135 // This assumes that the error is unrecoverable and disables reading
90 // from the device until it has been re-opened. 136 // from the device until it has been re-opened.
91 // TODO(reillyg): Investigate starting and stopping the file descriptor 137 // TODO(reillyg): Investigate starting and stopping the file descriptor
92 // watcher in response to pending read requests so that per-request 138 // watcher in response to pending read requests so that per-request
93 // errors can be returned to the client. 139 // errors can be returned to the client.
94 file_watcher_.reset(); 140 file_watcher_.reset();
95 } 141 }
96 return; 142 return;
97 } 143 }
98 if (!has_report_id_) { 144 if (!has_report_id_) {
99 // Behave as if the byte prefixed above as the the report ID was read. 145 // Behave as if the byte prefixed above as the the report ID was read.
100 bytes_read++; 146 bytes_read++;
101 } 147 }
102 148
103 task_runner_->PostTask(FROM_HERE, 149 origin_task_runner_->PostTask(
104 base::Bind(&HidConnectionLinux::ProcessInputReport, 150 FROM_HERE, base::Bind(&HidConnectionLinux::ProcessInputReport,
105 connection_, buffer, bytes_read)); 151 connection_, buffer, bytes_read));
106 } 152 }
107 153
108 // base::MessageLoop::DestructionObserver: 154 base::SequenceChecker sequence_checker_;
109 void WillDestroyCurrentMessageLoop() override { 155 base::ScopedFD fd_;
110 DCHECK(thread_checker_.CalledOnValidThread());
111 delete this;
112 }
113
114 base::ThreadChecker thread_checker_;
115 base::PlatformFile platform_file_;
116 size_t report_buffer_size_; 156 size_t report_buffer_size_;
117 bool has_report_id_; 157 bool has_report_id_;
118 base::WeakPtr<HidConnectionLinux> connection_; 158 base::WeakPtr<HidConnectionLinux> connection_;
119 scoped_refptr<base::SingleThreadTaskRunner> task_runner_; 159 const scoped_refptr<base::SequencedTaskRunner> origin_task_runner_;
120 std::unique_ptr<base::FileDescriptorWatcher::Controller> file_watcher_; 160 std::unique_ptr<base::FileDescriptorWatcher::Controller> file_watcher_;
121 161
122 DISALLOW_COPY_AND_ASSIGN(FileThreadHelper); 162 DISALLOW_COPY_AND_ASSIGN(FileThreadHelper);
123 }; 163 };
124 164
125 HidConnectionLinux::HidConnectionLinux( 165 HidConnectionLinux::HidConnectionLinux(
126 scoped_refptr<HidDeviceInfo> device_info, 166 scoped_refptr<HidDeviceInfo> device_info,
127 base::File device_file, 167 base::ScopedFD fd,
128 scoped_refptr<base::SingleThreadTaskRunner> file_task_runner) 168 scoped_refptr<base::SequencedTaskRunner> blocking_task_runner)
129 : HidConnection(device_info), 169 : HidConnection(device_info),
130 file_task_runner_(file_task_runner), 170 blocking_task_runner_(std::move(blocking_task_runner)),
131 weak_factory_(this) { 171 weak_factory_(this) {
132 task_runner_ = base::ThreadTaskRunnerHandle::Get(); 172 helper_ = base::MakeUnique<FileThreadHelper>(std::move(fd), device_info,
133 device_file_ = std::move(device_file); 173 weak_factory_.GetWeakPtr());
134 174 blocking_task_runner_->PostTask(
135 // The helper is passed a weak pointer to this connection so that it can be 175 FROM_HERE,
136 // cleaned up after the connection is closed. 176 base::Bind(&FileThreadHelper::Start, base::Unretained(helper_.get())));
137 std::unique_ptr<FileThreadHelper> helper(
138 new FileThreadHelper(device_file_.GetPlatformFile(), device_info,
139 weak_factory_.GetWeakPtr(), task_runner_));
140 helper_ = helper.get();
141 file_task_runner_->PostTask(
142 FROM_HERE, base::Bind(&FileThreadHelper::Start, base::Passed(&helper)));
143 } 177 }
144 178
145 HidConnectionLinux::~HidConnectionLinux() { 179 HidConnectionLinux::~HidConnectionLinux() {
146 DCHECK(helper_ == nullptr); 180 DCHECK(sequence_checker_.CalledOnValidSequence());
147 } 181 }
148 182
149 void HidConnectionLinux::PlatformClose() { 183 void HidConnectionLinux::PlatformClose() {
150 // By closing the device file on the FILE thread (1) the requirement that 184 // By closing the device on the blocking task runner 1) the requirement that
151 // base::File::Close is called on a thread where I/O is allowed is satisfied 185 // base::ScopedFD is destroyed on a thread where I/O is allowed is satisfied
152 // and (2) any tasks posted to this task runner that refer to this file will 186 // and 2) any tasks posted to this task runner that refer to this file will
153 // complete before it is closed. 187 // complete before it is closed.
154 file_task_runner_->DeleteSoon(FROM_HERE, helper_); 188 blocking_task_runner_->DeleteSoon(FROM_HERE, helper_.release());
155 helper_ = nullptr;
156 file_task_runner_->PostTask(FROM_HERE,
157 base::Bind(&HidConnectionLinux::CloseDevice,
158 base::Passed(&device_file_)));
159 189
160 while (!pending_reads_.empty()) { 190 while (!pending_reads_.empty()) {
161 pending_reads_.front().callback.Run(false, NULL, 0); 191 pending_reads_.front().callback.Run(false, NULL, 0);
162 pending_reads_.pop(); 192 pending_reads_.pop();
163 } 193 }
164 } 194 }
165 195
166 void HidConnectionLinux::PlatformRead(const ReadCallback& callback) { 196 void HidConnectionLinux::PlatformRead(const ReadCallback& callback) {
167 PendingHidRead pending_read; 197 PendingHidRead pending_read;
168 pending_read.callback = callback; 198 pending_read.callback = callback;
169 pending_reads_.push(pending_read); 199 pending_reads_.push(pending_read);
170 ProcessReadQueue(); 200 ProcessReadQueue();
171 } 201 }
172 202
173 void HidConnectionLinux::PlatformWrite(scoped_refptr<net::IOBuffer> buffer, 203 void HidConnectionLinux::PlatformWrite(scoped_refptr<net::IOBuffer> buffer,
174 size_t size, 204 size_t size,
175 const WriteCallback& callback) { 205 const WriteCallback& callback) {
176 // Linux expects the first byte of the buffer to always be a report ID so the 206 // Linux expects the first byte of the buffer to always be a report ID so the
177 // buffer can be used directly. 207 // buffer can be used directly.
178 file_task_runner_->PostTask( 208 blocking_task_runner_->PostTask(
179 FROM_HERE, 209 FROM_HERE,
180 base::Bind(&HidConnectionLinux::BlockingWrite, 210 base::Bind(&FileThreadHelper::Write, base::Unretained(helper_.get()),
181 device_file_.GetPlatformFile(), buffer, size, 211 buffer, size, callback));
182 base::Bind(&HidConnectionLinux::FinishWrite,
183 weak_factory_.GetWeakPtr(), size, callback),
184 task_runner_));
185 } 212 }
186 213
187 void HidConnectionLinux::PlatformGetFeatureReport( 214 void HidConnectionLinux::PlatformGetFeatureReport(
188 uint8_t report_id, 215 uint8_t report_id,
189 const ReadCallback& callback) { 216 const ReadCallback& callback) {
190 // The first byte of the destination buffer is the report ID being requested 217 // The first byte of the destination buffer is the report ID being requested
191 // and is overwritten by the feature report. 218 // and is overwritten by the feature report.
192 DCHECK_GT(device_info()->max_feature_report_size(), 0u); 219 DCHECK_GT(device_info()->max_feature_report_size(), 0u);
193 scoped_refptr<net::IOBufferWithSize> buffer( 220 scoped_refptr<net::IOBufferWithSize> buffer(
194 new net::IOBufferWithSize(device_info()->max_feature_report_size() + 1)); 221 new net::IOBufferWithSize(device_info()->max_feature_report_size() + 1));
195 buffer->data()[0] = report_id; 222 buffer->data()[0] = report_id;
196 223
197 file_task_runner_->PostTask( 224 blocking_task_runner_->PostTask(
198 FROM_HERE, 225 FROM_HERE,
199 base::Bind( 226 base::Bind(&FileThreadHelper::GetFeatureReport,
200 &HidConnectionLinux::BlockingIoctl, device_file_.GetPlatformFile(), 227 base::Unretained(helper_.get()), report_id, buffer, callback));
201 HIDIOCGFEATURE(buffer->size()), buffer,
202 base::Bind(&HidConnectionLinux::FinishGetFeatureReport,
203 weak_factory_.GetWeakPtr(), report_id, buffer, callback),
204 task_runner_));
205 } 228 }
206 229
207 void HidConnectionLinux::PlatformSendFeatureReport( 230 void HidConnectionLinux::PlatformSendFeatureReport(
208 scoped_refptr<net::IOBuffer> buffer, 231 scoped_refptr<net::IOBuffer> buffer,
209 size_t size, 232 size_t size,
210 const WriteCallback& callback) { 233 const WriteCallback& callback) {
211 // Linux expects the first byte of the buffer to always be a report ID so the 234 // Linux expects the first byte of the buffer to always be a report ID so the
212 // buffer can be used directly. 235 // buffer can be used directly.
213 file_task_runner_->PostTask( 236 blocking_task_runner_->PostTask(
214 FROM_HERE, 237 FROM_HERE,
215 base::Bind(&HidConnectionLinux::BlockingIoctl, 238 base::Bind(&FileThreadHelper::SendFeatureReport,
216 device_file_.GetPlatformFile(), HIDIOCSFEATURE(size), buffer, 239 base::Unretained(helper_.get()), buffer, size, callback));
217 base::Bind(&HidConnectionLinux::FinishSendFeatureReport,
218 weak_factory_.GetWeakPtr(), callback),
219 task_runner_));
220 }
221
222 void HidConnectionLinux::FinishWrite(size_t expected_size,
223 const WriteCallback& callback,
224 ssize_t result) {
225 if (result < 0) {
226 HID_PLOG(EVENT) << "Write failed";
227 callback.Run(false);
228 } else {
229 if (static_cast<size_t>(result) != expected_size) {
230 HID_LOG(EVENT) << "Incomplete HID write: " << result
231 << " != " << expected_size;
232 }
233 callback.Run(true);
234 }
235 }
236
237 void HidConnectionLinux::FinishGetFeatureReport(
238 uint8_t report_id,
239 scoped_refptr<net::IOBuffer> buffer,
240 const ReadCallback& callback,
241 int result) {
242 if (result < 0) {
243 HID_PLOG(EVENT) << "Failed to get feature report";
244 callback.Run(false, NULL, 0);
245 } else if (result == 0) {
246 HID_LOG(EVENT) << "Get feature result too short.";
247 callback.Run(false, NULL, 0);
248 } else if (report_id == 0) {
249 // Linux adds a 0 to the beginning of the data received from the device.
250 scoped_refptr<net::IOBuffer> copied_buffer(new net::IOBuffer(result - 1));
251 memcpy(copied_buffer->data(), buffer->data() + 1, result - 1);
252 callback.Run(true, copied_buffer, result - 1);
253 } else {
254 callback.Run(true, buffer, result);
255 }
256 }
257
258 void HidConnectionLinux::FinishSendFeatureReport(const WriteCallback& callback,
259 int result) {
260 if (result < 0) {
261 HID_PLOG(EVENT) << "Failed to send feature report";
262 callback.Run(false);
263 } else {
264 callback.Run(true);
265 }
266 }
267
268 // static
269 void HidConnectionLinux::BlockingWrite(
270 base::PlatformFile platform_file,
271 scoped_refptr<net::IOBuffer> buffer,
272 size_t size,
273 const InternalWriteCallback& callback,
274 scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
275 base::ThreadRestrictions::AssertIOAllowed();
276 ssize_t result = HANDLE_EINTR(write(platform_file, buffer->data(), size));
277 task_runner->PostTask(FROM_HERE, base::Bind(callback, result));
278 }
279
280 // static
281 void HidConnectionLinux::BlockingIoctl(
282 base::PlatformFile platform_file,
283 int request,
284 scoped_refptr<net::IOBuffer> buffer,
285 const IoctlCallback& callback,
286 scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
287 base::ThreadRestrictions::AssertIOAllowed();
288 int result = ioctl(platform_file, request, buffer->data());
289 task_runner->PostTask(FROM_HERE, base::Bind(callback, result));
290 }
291
292 // static
293 void HidConnectionLinux::CloseDevice(base::File device_file) {
294 device_file.Close();
295 } 240 }
296 241
297 void HidConnectionLinux::ProcessInputReport(scoped_refptr<net::IOBuffer> buffer, 242 void HidConnectionLinux::ProcessInputReport(scoped_refptr<net::IOBuffer> buffer,
298 size_t size) { 243 size_t size) {
299 DCHECK(thread_checker().CalledOnValidThread()); 244 DCHECK(thread_checker().CalledOnValidThread());
300 PendingHidReport report; 245 PendingHidReport report;
301 report.buffer = buffer; 246 report.buffer = buffer;
302 report.size = size; 247 report.size = size;
303 pending_reports_.push(report); 248 pending_reports_.push(report);
304 ProcessReadQueue(); 249 ProcessReadQueue();
305 } 250 }
306 251
307 void HidConnectionLinux::ProcessReadQueue() { 252 void HidConnectionLinux::ProcessReadQueue() {
308 DCHECK(thread_checker().CalledOnValidThread()); 253 DCHECK(thread_checker().CalledOnValidThread());
309 while (pending_reads_.size() && pending_reports_.size()) { 254 while (pending_reads_.size() && pending_reports_.size()) {
310 PendingHidRead read = pending_reads_.front(); 255 PendingHidRead read = pending_reads_.front();
311 PendingHidReport report = pending_reports_.front(); 256 PendingHidReport report = pending_reports_.front();
312 257
313 pending_reports_.pop(); 258 pending_reports_.pop();
314 if (CompleteRead(report.buffer, report.size, read.callback)) { 259 if (CompleteRead(report.buffer, report.size, read.callback))
315 pending_reads_.pop(); 260 pending_reads_.pop();
316 }
317 } 261 }
318 } 262 }
319 263
320 } // namespace device 264 } // namespace device
OLDNEW
« no previous file with comments | « device/hid/hid_connection_linux.h ('k') | device/hid/hid_service_linux.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698