OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "chrome/browser/extensions/api/braille_display_private/braille_controll er.h" | |
6 | |
7 #include <algorithm> | |
8 #include <cerrno> | |
9 #include <cstring> | |
10 #include <vector> | |
11 | |
12 #include "base/bind.h" | |
13 #include "base/bind_helpers.h" | |
14 #include "base/chromeos/chromeos_version.h" | |
15 #include "base/files/file_path_watcher.h" | |
16 #include "base/memory/scoped_ptr.h" | |
17 #include "base/memory/singleton.h" | |
18 #include "base/message_loop/message_loop.h" | |
19 #include "base/observer_list.h" | |
20 #include "base/time/time.h" | |
21 #include "content/public/browser/browser_thread.h" | |
22 #include "library_loaders/libbrlapi.h" | |
23 | |
24 namespace extensions { | |
25 using content::BrowserThread; | |
26 using base::MessageLoopForIO; | |
27 using base::TimeDelta; | |
28 namespace api { | |
29 namespace braille_display_private { | |
30 namespace { | |
31 // Default virtual terminal. This can be overriden by setting the | |
32 // WINDOWPATH environment variable. This is only used when not running | |
33 // under CrhomeOS (that is in aura for a Linux desktop). | |
34 // TODO(plundblad): Find a way to detect the controlling terminal of the | |
35 // X server. | |
36 static const int kDefaultTtyLinux = 7; | |
37 #if defined(OS_CHROMEOS) | |
38 // The GUI is always running on vt1 in ChromeOS. | |
39 static const int kDefaultTtyChromeOS = 1; | |
40 #endif | |
41 } // namespace | |
42 | |
43 class BrailleControllerImpl : public BrailleController { | |
44 public: | |
45 static BrailleControllerImpl* GetInstance(); | |
46 virtual scoped_ptr<base::DictionaryValue> GetDisplayState() OVERRIDE; | |
47 virtual void WriteDots(const std::string& cells) OVERRIDE; | |
48 virtual void AddObserver(BrailleObserver* observer) OVERRIDE; | |
49 virtual void RemoveObserver(BrailleObserver* observer) OVERRIDE; | |
50 | |
51 private: | |
52 class Connection : public MessageLoopForIO::Watcher { | |
dmazzoni
2013/09/06 16:49:53
Since there's only one connection at a time, why i
| |
53 public: | |
54 explicit Connection(BrailleControllerImpl* braille_controller) : | |
55 braille_controller_(braille_controller) { | |
56 } | |
57 | |
58 virtual ~Connection() { | |
59 Disconnect(); | |
60 } | |
61 | |
62 bool Connect() { | |
dmazzoni
2013/09/06 16:49:53
Don't inline nontrivial functions in the class def
| |
63 DCHECK(!handle_); | |
64 if (!braille_controller_->libbrlapi_loader_.loaded()) { | |
65 return false; | |
66 } | |
67 handle_.reset((brlapi_handle_t*) malloc( | |
68 braille_controller_->libbrlapi_loader_.brlapi_getHandleSize())); | |
69 int fd = braille_controller_->libbrlapi_loader_.brlapi__openConnection( | |
70 handle_.get(), NULL, NULL); | |
71 if (fd < 0) { | |
72 handle_.reset(); | |
73 LOG(ERROR) << "Error connecting to brlapi: " | |
74 << braille_controller_->BrlApiStrError(); | |
75 return false; | |
76 } | |
77 if (!MessageLoopForIO::current()->WatchFileDescriptor( | |
78 fd, true, MessageLoopForIO::WATCH_READ, &fd_controller_, this)) { | |
79 LOG(ERROR) << "Couldn't watch file descriptor " << fd; | |
80 Disconnect(); | |
81 return false; | |
82 } | |
83 return true; | |
84 } | |
85 | |
86 void Disconnect() { | |
87 if (handle_ == NULL) { | |
88 return; | |
89 } | |
90 fd_controller_.StopWatchingFileDescriptor(); | |
91 braille_controller_->libbrlapi_loader_.brlapi__closeConnection( | |
92 handle_.get()); | |
93 handle_.reset(); | |
94 } | |
95 | |
96 bool Connected() { return handle_; } | |
97 | |
98 brlapi_handle_t* GetHandle() { return handle_.get(); } | |
99 | |
100 // Gets the total size of the display, which may be 0 if no display is | |
101 // present, returning true on success. Note that this is cached in the | |
102 // brlapi client so it is cheap. | |
103 bool GetDisplaySize(size_t* size) { | |
104 if (!handle_) { | |
105 return false; | |
106 } | |
107 unsigned int columns, rows; | |
108 if (braille_controller_->libbrlapi_loader_.brlapi__getDisplaySize( | |
109 handle_.get(), &columns, &rows) < 0) { | |
110 return false; | |
111 } | |
112 *size = columns * rows; | |
113 return true; | |
114 } | |
115 | |
116 // MessageLoopForIO::Watcher | |
117 virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE { | |
118 braille_controller_->DispatchKeys(); | |
119 } | |
120 | |
121 virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE {} | |
122 | |
123 private: | |
124 scoped_ptr<brlapi_handle_t, base::FreeDeleter> handle_; | |
125 MessageLoopForIO::FileDescriptorWatcher fd_controller_; | |
126 BrailleControllerImpl* braille_controller_; | |
127 }; | |
128 BrailleControllerImpl(); | |
dmazzoni
2013/09/06 16:49:53
Nit: blank line before
| |
129 virtual ~BrailleControllerImpl(); | |
130 void TryLoadLibBrlApi(); | |
131 void StartConnections(); | |
132 void StopConnections(); | |
133 void OnSocketDirChanged(const base::FilePath& path, bool error); | |
134 void ReconnectAll(); | |
135 void UpdateConnections(); | |
136 void TryConnection(); | |
137 void DispatchKeys(); | |
138 scoped_ptr<KeyEvent> MapKeyCode(brlapi_keyCode_t code); | |
dmazzoni
2013/09/06 16:49:53
Nit: blank line between functions and data members
| |
139 void DispatchKeyEvent(scoped_ptr<KeyEvent> event); | |
dmazzoni
2013/09/06 16:49:53
Nit: put all functions, then all data members rath
| |
140 brlapi_error_t* BrlApiError(); | |
141 std::string BrlApiStrError(); | |
142 LibBrlApiLoader libbrlapi_loader_; | |
143 // Manipulated on the IO thread. | |
144 Connection connection_; | |
145 base::FilePathWatcher file_path_watcher_; | |
146 // Manipulated on the UI thread. | |
147 ObserverList<BrailleObserver> observers_; | |
148 bool watching_dir_; | |
149 friend struct DefaultSingletonTraits<BrailleControllerImpl>; | |
150 DISALLOW_COPY_AND_ASSIGN(BrailleControllerImpl); | |
151 }; | |
152 | |
153 BrailleController::BrailleController() { | |
154 } | |
155 | |
156 BrailleController::~BrailleController() { | |
157 } | |
158 | |
159 // static | |
160 BrailleController* BrailleController::GetInstance() { | |
161 return BrailleControllerImpl::GetInstance(); | |
162 } | |
163 | |
164 BrailleControllerImpl::BrailleControllerImpl() | |
165 : connection_(this), watching_dir_(false) { | |
166 TryLoadLibBrlApi(); | |
167 } | |
168 | |
169 BrailleControllerImpl::~BrailleControllerImpl() { | |
170 } | |
171 | |
172 void BrailleControllerImpl::TryLoadLibBrlApi() { | |
173 if (libbrlapi_loader_.loaded()) | |
174 return; | |
175 // These versions of libbrlapi work the same for the functions we | |
176 // are using. (0.6.0 adds brlapi_writeWText). | |
177 static const char* kSupportedVersions[] = { | |
178 "libbrlapi.so.0.5", | |
179 "libbrlapi.so.0.6" | |
180 }; | |
181 for (size_t i = 0; i < arraysize(kSupportedVersions); ++i) { | |
182 if (libbrlapi_loader_.Load(kSupportedVersions[i])) | |
183 return; | |
184 } | |
185 LOG(ERROR) << "Couldn't load libbrlapi: " << strerror(errno); | |
186 } | |
187 | |
188 // static | |
189 BrailleControllerImpl* BrailleControllerImpl::GetInstance() { | |
190 return Singleton<BrailleControllerImpl, | |
191 LeakySingletonTraits<BrailleControllerImpl> >::get(); | |
192 } | |
193 | |
194 scoped_ptr<base::DictionaryValue> BrailleControllerImpl::GetDisplayState() { | |
195 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); | |
196 DisplayState displayState; | |
197 if (connection_.Connected()) { | |
198 size_t size; | |
199 if (!connection_.GetDisplaySize(&size)) { | |
200 LOG(ERROR) << "Couldn't get braille display size " << BrlApiStrError(); | |
201 connection_.Disconnect(); | |
202 } else if (size > 0) { // size == 0 means no display present. | |
203 displayState.available = true; | |
204 displayState.text_cell_count.reset(new int(size)); | |
205 } | |
206 } | |
207 return displayState.ToValue().Pass(); | |
208 } | |
209 | |
210 void BrailleControllerImpl::WriteDots(const std::string& cells) { | |
211 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); | |
212 if (!libbrlapi_loader_.loaded()) | |
213 return; | |
214 if (connection_.Connected()) { | |
215 brlapi_handle_t* handle = connection_.GetHandle(); | |
216 size_t size; | |
217 if (!connection_.GetDisplaySize(&size)) { | |
218 LOG(ERROR) << "Couldn't get display size " << BrlApiStrError(); | |
219 connection_.Disconnect(); | |
220 } | |
221 std::vector<unsigned char> sizedCells(size); | |
222 std::memcpy(&sizedCells[0], cells.data(), std::min(cells.size(), size)); | |
223 if (size > cells.size()) | |
224 std::fill(sizedCells.begin() + cells.size(), sizedCells.end(), 0); | |
225 if (libbrlapi_loader_.brlapi__writeDots(handle, &sizedCells[0]) < 0) { | |
226 LOG(ERROR) << "Couldn't write to brlapi: " << BrlApiStrError(); | |
227 connection_.Disconnect(); | |
228 } | |
229 } | |
230 } | |
231 | |
232 void BrailleControllerImpl::AddObserver(BrailleObserver* observer) { | |
233 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
234 observers_.AddObserver(observer); | |
235 if (libbrlapi_loader_.loaded() && !watching_dir_) { | |
236 watching_dir_ = true; | |
237 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, | |
238 base::Bind(&BrailleControllerImpl::StartConnections, | |
239 base::Unretained(this))); | |
240 } | |
241 } | |
242 | |
243 void BrailleControllerImpl::RemoveObserver(BrailleObserver* observer) { | |
244 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
245 observers_.RemoveObserver(observer); | |
246 } | |
247 | |
248 void BrailleControllerImpl::StartConnections() { | |
249 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); | |
250 base::FilePath brlapi_dir(BRLAPI_SOCKETPATH); | |
251 LOG(INFO) << "Watching brlapi directory: " << BRLAPI_SOCKETPATH; | |
252 if (!file_path_watcher_.Watch( | |
253 brlapi_dir, false, base::Bind( | |
254 &BrailleControllerImpl::OnSocketDirChanged, | |
255 base::Unretained(this)))) { | |
256 LOG(WARNING) << "Couldn't watch brlapi directory " << BRLAPI_SOCKETPATH; | |
257 return; | |
258 } | |
259 UpdateConnections(); | |
260 } | |
261 | |
262 void BrailleControllerImpl::OnSocketDirChanged(const base::FilePath& path, | |
263 bool error) { | |
264 if (error) { | |
265 LOG(ERROR) << "Error watching brlapi directory: " << path.value(); | |
266 return; | |
267 } | |
268 LOG(INFO) << "BrlAPI directory changed"; | |
269 BrowserThread::PostDelayedTask(BrowserThread::IO, FROM_HERE, | |
270 base::Bind( | |
271 &BrailleControllerImpl::UpdateConnections, | |
272 base::Unretained(this)), | |
273 TimeDelta::FromSeconds(1)); | |
274 | |
275 } | |
276 | |
277 void BrailleControllerImpl::UpdateConnections() { | |
278 if (!connection_.Connected()) | |
279 TryConnection(); | |
280 } | |
281 | |
282 void BrailleControllerImpl::TryConnection() { | |
283 if (!connection_.Connect()) { | |
284 LOG(ERROR) << "Couldn't connect to brlapi\n"; | |
285 return; | |
286 } | |
287 int path[2] = {0, 0}; | |
288 int pathElements = 0; | |
289 #if defined(OS_CHROMEOS) | |
290 if (base::chromeos::IsRunningOnChromeOS()) | |
291 path[pathElements++] = kDefaultTtyChromeOS; | |
292 #endif | |
293 if (pathElements == 0 && getenv("WINDOWPATH") == NULL) | |
294 path[pathElements++] = kDefaultTtyLinux; | |
295 if (libbrlapi_loader_.brlapi__enterTtyModeWithPath( | |
296 connection_.GetHandle(), path, pathElements, NULL) < 0) { | |
297 LOG(ERROR) << "brlapi: couldn't enter tty mode: " << BrlApiStrError(); | |
298 connection_.Disconnect(); | |
299 return; | |
300 } | |
301 const brlapi_keyCode_t extraKeys[] = { | |
302 BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_OFFLINE, | |
303 }; | |
304 if (libbrlapi_loader_.brlapi__acceptKeys( | |
305 connection_.GetHandle(), | |
306 brlapi_rangeType_command, | |
307 extraKeys, | |
308 arraysize(extraKeys)) < 0) { | |
309 LOG(ERROR) << "Couldn't acceptKeys: " << BrlApiStrError(); | |
310 connection_.Disconnect(); | |
311 return; | |
312 } | |
313 } | |
314 | |
315 scoped_ptr<KeyEvent> BrailleControllerImpl::MapKeyCode(brlapi_keyCode_t code) { | |
316 brlapi_expandedKeyCode_t expanded; | |
317 if (libbrlapi_loader_.brlapi_expandKeyCode(code, &expanded) != 0) { | |
318 LOG(ERROR) << "Couldn't expand key key code " << code; | |
319 return scoped_ptr<KeyEvent>(); | |
320 } | |
321 scoped_ptr<KeyEvent> result(new KeyEvent); | |
322 result->command = KEY_COMMAND_NONE; | |
323 switch (expanded.type) { | |
324 case BRLAPI_KEY_TYPE_CMD: | |
325 switch (expanded.command) { | |
326 case BRLAPI_KEY_CMD_LNUP: | |
327 result->command = KEY_COMMAND_LINE_UP; | |
328 break; | |
329 case BRLAPI_KEY_CMD_LNDN: | |
330 result->command = KEY_COMMAND_LINE_DOWN; | |
331 break; | |
332 case BRLAPI_KEY_CMD_FWINLT: | |
333 result->command = KEY_COMMAND_PAN_LEFT; | |
334 break; | |
335 case BRLAPI_KEY_CMD_FWINRT: | |
336 result->command = KEY_COMMAND_PAN_RIGHT; | |
337 break; | |
338 case BRLAPI_KEY_CMD_TOP: | |
339 result->command = KEY_COMMAND_TOP; | |
340 break; | |
341 case BRLAPI_KEY_CMD_BOT: | |
342 result->command = KEY_COMMAND_BOTTOM; | |
343 break; | |
344 case BRLAPI_KEY_CMD_ROUTE: | |
345 result->command = KEY_COMMAND_ROUTING; | |
346 result->display_position.reset(new int(expanded.argument)); | |
347 break; | |
348 case BRLAPI_KEY_CMD_PASSDOTS: | |
349 result->command = KEY_COMMAND_DOTS; | |
350 // The 8 low-order bits in the argument contains the dots. | |
351 result->braille_dots.reset(new int(expanded.argument & 0xf)); | |
352 if ((expanded.argument & BRLAPI_DOTC) != 0) | |
353 result->space_key.reset(new bool(true)); | |
354 break; | |
355 } | |
356 break; | |
357 } | |
358 if (result->command == KEY_COMMAND_NONE) | |
359 result.reset(); | |
360 return result.Pass(); | |
361 } | |
362 | |
363 void BrailleControllerImpl::DispatchKeys() { | |
364 brlapi_handle_t* handle = connection_.GetHandle(); | |
365 DCHECK(handle != NULL); | |
366 brlapi_keyCode_t code; | |
367 while (true) { | |
368 int result = libbrlapi_loader_.brlapi__readKey(handle, 0 /*wait*/, &code); | |
369 if (result < 0) { // Error. | |
370 brlapi_error_t* err = BrlApiError(); | |
371 if (err->brlerrno == BRLAPI_ERROR_LIBCERR && err->libcerrno == EINTR) | |
372 continue; | |
373 // Disconnect on other errors. | |
374 LOG(ERROR) << "BrlAPI error: " << BrlApiStrError(); | |
375 connection_.Disconnect(); | |
376 return; | |
377 } else if (result == 0) { // No more data. | |
378 return; | |
379 } | |
380 scoped_ptr<KeyEvent> event = MapKeyCode(code); | |
381 if (event) | |
382 DispatchKeyEvent(event.Pass()); | |
383 } | |
384 } | |
385 | |
386 void BrailleControllerImpl::DispatchKeyEvent(scoped_ptr<KeyEvent> event) { | |
387 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { | |
388 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | |
389 base::Bind( | |
390 &BrailleControllerImpl::DispatchKeyEvent, | |
391 base::Unretained(this), | |
392 base::Passed(&event))); | |
393 return; | |
394 } | |
395 FOR_EACH_OBSERVER(BrailleObserver, observers_, OnKeyEvent(*event)); | |
396 } | |
397 | |
398 brlapi_error_t* BrailleControllerImpl::BrlApiError() { | |
399 DCHECK(libbrlapi_loader_.loaded()); | |
400 return libbrlapi_loader_.brlapi_error_location(); | |
401 } | |
402 | |
403 std::string BrailleControllerImpl::BrlApiStrError() { | |
404 return libbrlapi_loader_.brlapi_strerror(BrlApiError()); | |
405 } | |
406 | |
407 } // namespace braille_display_private | |
408 } // namespace api | |
409 } // namespace extensions | |
OLD | NEW |