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 "chrome/common/extensions/api/braille_display_private.h" | |
David Tseng
2013/09/05 19:21:23
Already included in header.
| |
22 #include "content/public/browser/browser_thread.h" | |
23 #include "library_loaders/libbrlapi.h" | |
24 | |
25 namespace extensions { | |
26 using content::BrowserThread; | |
27 using base::MessageLoopForIO; | |
28 using base::TimeDelta; | |
29 namespace api { | |
30 namespace braille_display_private { | |
31 namespace { | |
32 // Default virtual terminal. This can be overriden by setting the | |
33 // WINDOWPATH environment variable. This is only used when not running | |
34 // under Crhome OS (that is in aura for a Linux desktop). | |
David Tseng
2013/09/05 19:21:23
ChromeOS
| |
35 // TODO(plundblad): Find a way to detect the controlling terminal of the | |
36 // X server. | |
37 static const int kDefaultTtyLinux = 7; | |
38 #if defined(OS_CHROMEOS) | |
39 // The GUI is always running on vt1 in ChromeOS. | |
40 static const int kDefaultTtyChromeOS = 1; | |
David Tseng
2013/09/05 19:21:23
You could eliminate one of the constants by doing
| |
41 #endif | |
42 } // namespace | |
43 | |
44 class BrailleControllerImpl : public BrailleController { | |
45 public: | |
46 static BrailleControllerImpl* GetInstance(); | |
47 virtual scoped_ptr<base::DictionaryValue> GetDisplayState() OVERRIDE; | |
48 virtual void WriteDots(const std::string& cells) OVERRIDE; | |
49 virtual void AddObserver(Observer* observer) OVERRIDE; | |
50 virtual void RemoveObserver(Observer* observer) OVERRIDE; | |
51 | |
52 private: | |
53 class Connection : public MessageLoopForIO::Watcher { | |
54 public: | |
55 explicit Connection(BrailleControllerImpl* braille_controller) : | |
56 braille_controller_(braille_controller) { | |
57 } | |
58 | |
59 virtual ~Connection() { | |
60 Disconnect(); | |
61 } | |
62 | |
63 bool Connect() { | |
64 DCHECK(!handle_); | |
65 if (!braille_controller_->libbrlapi_loader_.loaded()) { | |
66 return false; | |
67 } | |
68 handle_.reset((brlapi_handle_t*) malloc( | |
69 braille_controller_->libbrlapi_loader_.brlapi_getHandleSize())); | |
70 int fd = braille_controller_->libbrlapi_loader_.brlapi__openConnection( | |
71 handle_.get(), NULL, NULL); | |
72 if (fd < 0) { | |
73 handle_.reset(); | |
74 LOG(ERROR) << "Error connecting to brlapi: " | |
75 << braille_controller_->BrlApiStrError(); | |
76 return false; | |
77 } | |
78 if (!MessageLoopForIO::current()->WatchFileDescriptor( | |
79 fd, true, MessageLoopForIO::WATCH_READ, &fd_controller_, this)) { | |
80 LOG(ERROR) << "Couldn't watch file descriptor " << fd; | |
81 Disconnect(); | |
82 return false; | |
83 } | |
84 return true; | |
85 } | |
86 | |
87 void Disconnect() { | |
88 if (handle_ == NULL) { | |
89 return; | |
90 } | |
91 fd_controller_.StopWatchingFileDescriptor(); | |
92 braille_controller_->libbrlapi_loader_.brlapi__closeConnection( | |
93 handle_.get()); | |
94 handle_.reset(); | |
95 } | |
96 | |
97 bool Connected() { return handle_; } | |
98 | |
99 brlapi_handle_t* GetHandle() { return handle_.get(); } | |
100 | |
101 // Gets the total size of the display, which may be 0 if no display is | |
102 // present, returning true on success. Note that this is cached in the | |
103 // brlapi client so it is cheap. | |
104 bool GetDisplaySize(size_t* size) { | |
105 if (!handle_) { | |
106 return false; | |
107 } | |
108 unsigned int columns, rows; | |
109 if (braille_controller_->libbrlapi_loader_.brlapi__getDisplaySize( | |
110 handle_.get(), &columns, &rows) < 0) { | |
111 return false; | |
112 } | |
113 *size = columns * rows; | |
114 return true; | |
115 } | |
116 | |
117 // MessageLoopForIO::Watcher | |
118 virtual void OnFileCanReadWithoutBlocking(int fd) { | |
119 braille_controller_->DispatchKeys(); | |
120 } | |
121 | |
122 virtual void OnFileCanWriteWithoutBlocking(int fd) {} | |
123 | |
124 private: | |
125 scoped_ptr<brlapi_handle_t, base::FreeDeleter> handle_; | |
126 MessageLoopForIO::FileDescriptorWatcher fd_controller_; | |
127 BrailleControllerImpl* braille_controller_; | |
128 }; | |
129 BrailleControllerImpl(); | |
130 virtual ~BrailleControllerImpl(); | |
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); | |
139 void DispatchKeyEvent(scoped_ptr<KeyEvent> event); | |
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<Observer> 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 if (!libbrlapi_loader_.Load("libbrlapi.so.0.5")) { | |
David Tseng
2013/09/05 19:21:23
Define this as a static constant.
| |
167 LOG(ERROR) << "Couldn't load libbrlapi: " << strerror(errno); | |
168 } | |
David Tseng
2013/09/05 19:21:23
No need for braces in if clauses of one line (Chro
| |
169 } | |
170 | |
171 BrailleControllerImpl::~BrailleControllerImpl() { | |
172 } | |
173 | |
174 // static | |
175 BrailleControllerImpl* BrailleControllerImpl::GetInstance() { | |
176 return Singleton<BrailleControllerImpl, | |
177 LeakySingletonTraits<BrailleControllerImpl> >::get(); | |
178 } | |
179 | |
180 scoped_ptr<base::DictionaryValue> BrailleControllerImpl::GetDisplayState() { | |
181 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); | |
182 DisplayState displayState; | |
183 if (connection_.Connected()) { | |
184 size_t size; | |
185 if (!connection_.GetDisplaySize(&size)) { | |
186 LOG(ERROR) << "Couldn't get braille display size " << BrlApiStrError(); | |
187 connection_.Disconnect(); | |
188 } else if (size > 0) { | |
David Tseng
2013/09/05 19:21:23
When does a display get size of zero? Is that also
| |
189 displayState.available = true; | |
190 displayState.text_cell_count.reset(new int(size)); | |
191 } | |
192 } | |
193 return displayState.ToValue().Pass(); | |
194 } | |
195 | |
196 void BrailleControllerImpl::WriteDots(const std::string& cells) { | |
197 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); | |
198 if (!libbrlapi_loader_.loaded()) { | |
David Tseng
2013/09/05 19:21:23
Ditto; remove braces.
| |
199 return; | |
200 } | |
201 if (connection_.Connected()) { | |
202 brlapi_handle_t* handle = connection_.GetHandle(); | |
203 size_t size; | |
204 if (!connection_.GetDisplaySize(&size)) { | |
205 LOG(ERROR) << "Couldn't get display size " << BrlApiStrError(); | |
206 connection_.Disconnect(); | |
207 } | |
208 std::vector<unsigned char> sizedCells(size); | |
209 std::memcpy(&sizedCells[0], cells.data(), std::min(cells.size(), size)); | |
210 if (size > cells.size()) { | |
David Tseng
2013/09/05 19:21:23
Ditto.
| |
211 std::fill(sizedCells.begin() + cells.size(), sizedCells.end(), 0); | |
212 } | |
213 if (libbrlapi_loader_.brlapi__writeDots(handle, &sizedCells[0]) < 0) { | |
214 LOG(ERROR) << "Couldn't write to brlapi: " << BrlApiStrError(); | |
215 connection_.Disconnect(); | |
David Tseng
2013/09/05 19:21:23
Are there instances where we don't want to disconn
| |
216 } | |
217 } | |
218 } | |
219 | |
220 void BrailleControllerImpl::AddObserver(Observer* observer) { | |
221 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
222 observers_.AddObserver(observer); | |
223 if (libbrlapi_loader_.loaded() && !watching_dir_) { | |
224 watching_dir_ = true; | |
225 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, | |
226 base::Bind(&BrailleControllerImpl::StartConnections, | |
227 base::Unretained(this))); | |
228 } | |
229 } | |
230 | |
231 void BrailleControllerImpl::RemoveObserver(Observer* observer) { | |
232 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
233 observers_.RemoveObserver(observer); | |
234 } | |
235 | |
236 void BrailleControllerImpl::StartConnections() { | |
237 base::FilePath brlapi_dir(BRLAPI_SOCKETPATH); | |
238 LOG(ERROR) << "Watching brlapi dir " << BRLAPI_SOCKETPATH; | |
David Tseng
2013/09/05 19:21:23
INFO?
| |
239 if (!file_path_watcher_.Watch( | |
240 brlapi_dir, false, base::Bind( | |
241 &BrailleControllerImpl::OnSocketDirChanged, | |
242 base::Unretained(this)))) { | |
243 LOG(WARNING) << "Couldn't watch brlapi directory " << BRLAPI_SOCKETPATH; | |
244 return; | |
245 } | |
246 UpdateConnections(); | |
247 } | |
248 | |
249 void BrailleControllerImpl::OnSocketDirChanged(const base::FilePath& path, | |
250 bool error) { | |
251 if (error) { | |
252 LOG(ERROR) << "Error watching brlapi path: " << path.value(); | |
253 return; | |
254 } | |
255 LOG(ERROR) << "BrlAPI directory changed"; | |
256 BrowserThread::PostDelayedTask(BrowserThread::IO, FROM_HERE, | |
257 base::Bind( | |
258 &BrailleControllerImpl::UpdateConnections, | |
259 base::Unretained(this)), | |
260 TimeDelta::FromSeconds(1)); | |
261 | |
262 } | |
263 | |
264 void BrailleControllerImpl::UpdateConnections() { | |
265 if (!connection_.Connected()) { | |
266 TryConnection(); | |
267 } | |
268 } | |
269 | |
270 void BrailleControllerImpl::TryConnection() { | |
271 if (!connection_.Connect()) { | |
272 LOG(ERROR) << "Couldn't connect to brlapi\n"; | |
273 return; | |
274 } | |
275 int path[2] = {0, 0}; | |
276 int pathElements = 0; | |
277 #if defined(OS_CHROMEOS) | |
278 if (base::chromeos::IsRunningOnChromeOS()) { | |
279 path[pathElements++] = kDefaultTtyChromeOS; | |
280 } | |
281 #endif | |
282 if (pathElements == 0 && getenv("WINDOWPATH") == NULL) { | |
283 path[pathElements++] = kDefaultTtyLinux; | |
284 } | |
285 if (libbrlapi_loader_.brlapi__enterTtyModeWithPath( | |
286 connection_.GetHandle(), path, pathElements, NULL) < 0) { | |
287 LOG(ERROR) << "brlapi: couldn't enter tty mode: " << BrlApiStrError(); | |
288 connection_.Disconnect(); | |
289 return; | |
290 } | |
291 const brlapi_keyCode_t extraKeys[] = { | |
292 BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_OFFLINE, | |
293 }; | |
294 if (libbrlapi_loader_.brlapi__acceptKeys( | |
295 connection_.GetHandle(), | |
296 brlapi_rangeType_command, | |
297 extraKeys, | |
298 arraysize(extraKeys)) < 0) { | |
299 LOG(ERROR) << "Couldn't acceptKeys: " << BrlApiStrError(); | |
300 connection_.Disconnect(); | |
301 return; | |
302 } | |
303 } | |
304 | |
305 scoped_ptr<KeyEvent> BrailleControllerImpl::MapKeyCode(brlapi_keyCode_t code) { | |
306 brlapi_expandedKeyCode_t expanded; | |
307 if (libbrlapi_loader_.brlapi_expandKeyCode(code, &expanded) != 0) { | |
308 LOG(ERROR) << "Couldn't expand key key code " << code; | |
309 return scoped_ptr<KeyEvent>(); | |
310 } | |
311 scoped_ptr<KeyEvent> result(new KeyEvent); | |
312 result->command = KEY_COMMAND_NONE; | |
313 switch (expanded.type) { | |
314 case BRLAPI_KEY_TYPE_CMD: | |
315 switch (expanded.command) { | |
316 case BRLAPI_KEY_CMD_LNUP: | |
317 result->command = KEY_COMMAND_LINE_UP; | |
318 break; | |
319 case BRLAPI_KEY_CMD_LNDN: | |
320 result->command = KEY_COMMAND_LINE_DOWN; | |
321 break; | |
322 case BRLAPI_KEY_CMD_FWINLT: | |
323 result->command = KEY_COMMAND_PAN_LEFT; | |
324 break; | |
325 case BRLAPI_KEY_CMD_FWINRT: | |
326 result->command = KEY_COMMAND_PAN_RIGHT; | |
327 break; | |
328 case BRLAPI_KEY_CMD_TOP: | |
329 result->command = KEY_COMMAND_TOP; | |
330 break; | |
331 case BRLAPI_KEY_CMD_BOT: | |
332 result->command = KEY_COMMAND_BOTTOM; | |
333 break; | |
334 case BRLAPI_KEY_CMD_ROUTE: | |
335 result->command = KEY_COMMAND_ROUTING; | |
336 result->display_position.reset(new int(expanded.argument)); | |
337 break; | |
338 case BRLAPI_KEY_CMD_PASSDOTS: | |
339 result->command = KEY_COMMAND_DOTS; | |
340 // The 8 low-order bits in the argument contains the dots. | |
341 result->braille_dots.reset(new int(expanded.argument & 0xf)); | |
342 if ((expanded.argument & BRLAPI_DOTC) != 0) { | |
343 result->space_key.reset(new bool(true)); | |
344 } | |
345 break; | |
346 } | |
347 break; | |
348 } | |
349 if (result->command == KEY_COMMAND_NONE) { | |
350 result.reset(); | |
351 } | |
352 return result.Pass(); | |
353 } | |
354 | |
355 void BrailleControllerImpl::DispatchKeys() { | |
356 brlapi_handle_t* handle = connection_.GetHandle(); | |
357 DCHECK(handle != NULL); | |
358 brlapi_keyCode_t code; | |
359 while (true) { | |
360 int result = libbrlapi_loader_.brlapi__readKey(handle, 0 /*wait*/, &code); | |
361 if (result < 0) { // Error. | |
362 brlapi_error_t* err = BrlApiError(); | |
363 if (err->brlerrno == BRLAPI_ERROR_LIBCERR && err->libcerrno == EINTR) { | |
David Tseng
2013/09/05 19:21:23
Ditto.
| |
364 continue; | |
365 } | |
366 // Disconnect on other errors. | |
367 LOG(ERROR) << "BrlAPI error: " << BrlApiStrError(); | |
368 connection_.Disconnect(); | |
369 return; | |
370 } else if (result == 0) { // No more data. | |
371 return; | |
372 } | |
373 scoped_ptr<KeyEvent> event = MapKeyCode(code); | |
374 if (event) { | |
375 DispatchKeyEvent(event.Pass()); | |
376 } | |
377 } | |
378 } | |
379 | |
380 void BrailleControllerImpl::DispatchKeyEvent(scoped_ptr<KeyEvent> event) { | |
381 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { | |
382 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | |
383 base::Bind( | |
384 &BrailleControllerImpl::DispatchKeyEvent, | |
385 base::Unretained(this), | |
386 base::Passed(&event))); | |
387 return; | |
388 } | |
389 FOR_EACH_OBSERVER(Observer, observers_, OnKeyEvent(*event)); | |
390 } | |
391 | |
392 brlapi_error_t* BrailleControllerImpl::BrlApiError() { | |
393 DCHECK(libbrlapi_loader_.loaded()); | |
394 return libbrlapi_loader_.brlapi_error_location(); | |
395 } | |
396 | |
397 std::string BrailleControllerImpl::BrlApiStrError() { | |
398 return libbrlapi_loader_.brlapi_strerror(BrlApiError()); | |
399 } | |
400 | |
401 } // namespace braille_display_private | |
402 } // namespace api | |
403 } // namespace extensions | |
OLD | NEW |