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

Side by Side Diff: chrome/browser/extensions/api/braille_private/braille_controller.cc

Issue 13355002: Implement chrome.brailleDisplayPrivate API (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 7 years, 4 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
OLDNEW
(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_private/braille_controller.h"
6
7 #include <errno.h>
8 #include <map>
9
10 #include "base/bind.h"
11 #include "base/bind_helpers.h"
12 #include "base/chromeos/chromeos_version.h"
13 #include "base/files/file_path_watcher.h"
14 #include "base/memory/scoped_ptr.h"
15 #include "base/memory/singleton.h"
16 #include "base/message_loop/message_loop.h"
17 #include "base/observer_list.h"
18 #include "base/time/time.h"
19 #include "chrome/common/extensions/api/braille_private.h"
20 #include "content/public/browser/browser_thread.h"
21 #include "library_loaders/libbrlapi.h"
22
23 namespace extensions {
24 using content::BrowserThread;
25 using base::MessageLoopForIO;
26 using base::TimeDelta;
27 namespace api {
28 namespace braille_private {
29 namespace {
30 // Default virtual terminal. This can be overriden by setting the
31 // WINDOWPATH environment variable.
32 // TODO(plundblad): Find a way to detect the controlling terminal of the
33 // X server.
34 static const int kDefaultTtyLinux = 7;
35 #if defined(OS_CHROMEOS)
36 static const int kDefaultTtyChromeOS = 1;
37 #endif
38 } // namespace
39
40 class BrailleControllerImpl : public BrailleController {
41 public:
42 static BrailleControllerImpl* GetInstance();
43 virtual scoped_ptr<base::DictionaryValue> GetDisplayState() OVERRIDE;
44 virtual void WriteText(const std::string& text, int cursor) OVERRIDE;
45 virtual void AddObserver(Observer* observer) OVERRIDE;
46 virtual void RemoveObserver(Observer* observer) OVERRIDE;
47 private:
48 class Connection : public MessageLoopForIO::Watcher {
49 public:
50 Connection(BrailleControllerImpl* braille_controller) :
51 braille_controller_(braille_controller) {
52 DCHECK(braille_controller_->libbrlapi_loader_.loaded());
53 }
54
55 virtual ~Connection() {
56 Disconnect();
57 }
58
59 bool Connect() {
60 DCHECK(!handle_);
61 handle_.reset((brlapi_handle_t*) malloc(
62 braille_controller_->libbrlapi_loader_.brlapi_getHandleSize()));
63 int fd = braille_controller_->libbrlapi_loader_.brlapi__openConnection(
64 handle_.get(), NULL, NULL);
65 if (fd < 0) {
66 handle_.reset();
67 LOG(ERROR) << "Error connecting to brlapi: "
68 << braille_controller_->BrlApiStrError();
69 return false;
70 }
71 if (!MessageLoopForIO::current()->WatchFileDescriptor(
72 fd, true, MessageLoopForIO::WATCH_READ, &fd_controller_, this)) {
73 Disconnect();
74 return false;
75 }
76 return true;
77 }
78
79 void Disconnect() {
80 if (handle_ == NULL) {
81 return;
82 }
83 braille_controller_->libbrlapi_loader_.brlapi__closeConnection(
84 handle_.get());
85 handle_.reset();
86 }
87
88 bool Connected() { return handle_; }
89
90 brlapi_handle_t* GetHandle() { return handle_.get(); }
91
92 MessageLoopForIO::FileDescriptorWatcher* GetFDController() {
93 return &fd_controller_;
94 }
95
96 // MessageLoopForIO::Watcher
97 virtual void OnFileCanReadWithoutBlocking(int fd) {
98 LOG(ERROR) << "Braille fd " << fd << " ready";
99 braille_controller_->DispatchKeys(this);
100 }
101
102 virtual void OnFileCanWriteWithoutBlocking(int fd) {}
103
104 private:
105 scoped_ptr<brlapi_handle_t, base::FreeDeleter> handle_;
106 MessageLoopForIO::FileDescriptorWatcher fd_controller_;
107 BrailleControllerImpl* braille_controller_;
108 };
109 typedef std::map<unsigned long, Connection*> WindowIdToConnectionMap;
110 BrailleControllerImpl();
111 virtual ~BrailleControllerImpl();
112 virtual void AddWindowOnIOThread(unsigned long windowId) OVERRIDE;
113 virtual void RemoveWindowOnIOThread(unsigned long windowId) OVERRIDE;
114 void StartConnections();
115 void StopConnections();
116 void OnSocketDirChanged(const base::FilePath& path, bool error);
117 void DisconnectAll();
118 void ReconnectAll();
119 void UpdateConnections();
120 void TryConnection(unsigned long windowId, Connection* connection);
121 void DispatchKeys(Connection* connection);
122 scoped_ptr<KeyEvent> MapKeyCode(brlapi_keyCode_t code);
123 void DispatchKeyEvent(scoped_ptr<KeyEvent> event);
124 brlapi_error_t* BrlApiError();
125 std::string BrlApiStrError();
126 LibBrlApiLoader libbrlapi_loader_;
127 // Manipulated on the IO thread.
128 WindowIdToConnectionMap connections_;
129 base::FilePathWatcher file_path_watcher_;
130 // Manipulated on the UI thread.
131 ObserverList<Observer> observers_;
132 bool watching_dir_;
133 friend struct DefaultSingletonTraits<BrailleControllerImpl>;
134 DISALLOW_COPY_AND_ASSIGN(BrailleControllerImpl);
135 };
136
137 BrailleController::BrailleController() {
138 }
139
140 BrailleController::~BrailleController() {
141 }
142
143 // static
144 BrailleController* BrailleController::GetInstance() {
145 return BrailleControllerImpl::GetInstance();
146 }
147
148 // static
149 void BrailleController::AddWindow(unsigned long windowId) {
150 if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
151 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
152 base::Bind(&BrailleController::AddWindow,
153 windowId));
154 } else {
155 GetInstance()->AddWindowOnIOThread(windowId);
156 }
157 }
158
159 // static
160 void BrailleController::RemoveWindow(unsigned long windowId) {
161 if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
162 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
163 base::Bind(&BrailleController::RemoveWindow,
164 windowId));
165 } else {
166 GetInstance()->RemoveWindowOnIOThread(windowId);
167 }
168 }
169
170 BrailleControllerImpl::BrailleControllerImpl() {
171 if (!libbrlapi_loader_.Load("libbrlapi.so.0.5")) {
172 LOG(ERROR) << "Couldn't load libbrlapi: " << strerror(errno);
173 return;
174 }
175 }
176
177 BrailleControllerImpl::~BrailleControllerImpl() {
178 }
179
180 // static
181 BrailleControllerImpl* BrailleControllerImpl::GetInstance() {
182 return Singleton<BrailleControllerImpl,
183 LeakySingletonTraits<BrailleControllerImpl> >::get();
184 }
185
186 scoped_ptr<base::DictionaryValue> BrailleControllerImpl::GetDisplayState()
187 OVERRIDE {
188 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
189 DisplayState displayState;
190 for (WindowIdToConnectionMap::const_iterator i = connections_.begin();
191 i != connections_.end(); ++i) {
192 if (!i->second) {
193 continue;
194 }
195 brlapi_handle_t* handle = i->second->GetHandle();
196 unsigned int columns, lines;
197 if (libbrlapi_loader_.brlapi__getDisplaySize(handle, &columns, &lines)
198 < 0) {
199 // TODO: handle error.
200 continue;
201 }
202 if (columns > 0) {
203 displayState.available = true;
204 displayState.text_cells.reset(new int(columns * lines));
205 break;
206 }
207 }
208 return displayState.ToValue().Pass();
209 }
210
211 void BrailleControllerImpl::WriteText(
212 const std::string& text, int cursor) {
213 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
214 if (!libbrlapi_loader_.loaded()) {
215 return;
216 }
217 // Only ascii for testing.
218 std::string asciiText;
219 asciiText.reserve(text.length());
220 for (std::string::const_iterator i = text.begin(); i != text.end(); ++i) {
221 if (static_cast<unsigned char>(*i) < 0x80) {
222 asciiText += *i;
223 } else {
224 asciiText.append("?");
225 }
226 }
227 if (cursor < 0) {
228 cursor = BRLAPI_CURSOR_OFF;
229 } else {
230 cursor += 1; // ONe-based in brlapi.
231 }
232 for (WindowIdToConnectionMap::const_iterator i = connections_.begin();
233 i != connections_.end(); ++i) {
234 if (!i->second) {
235 continue;
236 }
237 brlapi_handle_t* handle = i->second->GetHandle();
238 if (libbrlapi_loader_.brlapi__writeText(
239 handle, cursor, asciiText.c_str()) < 0) {
240 LOG(ERROR) << "Couldn't write to window %lu" << i->first;
241 }
242 }
243 }
244
245 void BrailleControllerImpl::AddObserver(Observer* observer) {
246 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
247 observers_.AddObserver(observer);
248 if (libbrlapi_loader_.loaded() && !watching_dir_) {
249 watching_dir_ = true;
250 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
251 base::Bind(&BrailleControllerImpl::StartConnections,
252 base::Unretained(this)));
253 }
254 }
255
256 void BrailleControllerImpl::RemoveObserver(Observer* observer) {
257 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
258 observers_.RemoveObserver(observer);
259 }
260
261 void BrailleControllerImpl::AddWindowOnIOThread(unsigned long windowId) {
262 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
263 LOG(ERROR) << "Setting up braille for window " << windowId;
264 if (!libbrlapi_loader_.loaded()) {
265 LOG(INFO) << "libbrlapi not loaded";
266 return;
267 }
268 WindowIdToConnectionMap::iterator it = connections_.find(windowId);
269 if (it != connections_.end()) {
270 LOG(WARNING) << "BrlAPI connection already exists for window " << windowId;
271 } else {
272 it = connections_.insert(
273 std::make_pair(windowId, new Connection(this))).first;
274 }
275 TryConnection(it->first, it->second);
276 }
277
278 void BrailleControllerImpl::RemoveWindowOnIOThread(unsigned long windowId) {
279 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
280 LOG(ERROR) << "Tearing down braille for window " << windowId;
281 if (!libbrlapi_loader_.loaded()) {
282 return;
283 }
284 WindowIdToConnectionMap::iterator it = connections_.find(windowId);
285 if (it == connections_.end()) {
286 return;
287 }
288 delete it->second;
289 connections_.erase(it);
290 }
291
292 void BrailleControllerImpl::StartConnections() {
293 base::FilePath brlapi_dir(BRLAPI_SOCKETPATH);
294 if (!file_path_watcher_.Watch(
295 brlapi_dir, false, base::Bind(
296 &BrailleControllerImpl::OnSocketDirChanged,
297 base::Unretained(this)))) {
298 LOG(WARNING) << "Couldn't watch brlapi directory " << BRLAPI_SOCKETPATH;
299 return;
300 }
301 UpdateConnections();
302 }
303
304 void BrailleControllerImpl::OnSocketDirChanged(const base::FilePath& path,
305 bool error) {
306 if (error) {
307 LOG(WARNING) << "Error watching brlapi path: " << path.value();
308 return;
309 }
310 LOG(ERROR) << "BrlAPI directory changed";
311 BrowserThread::PostDelayedTask(BrowserThread::IO, FROM_HERE,
312 base::Bind(
313 &BrailleControllerImpl::UpdateConnections,
314 base::Unretained(this)),
315 TimeDelta::FromSeconds(1));
316
317 }
318
319 void BrailleControllerImpl::DisconnectAll() {
320 for (WindowIdToConnectionMap::iterator i = connections_.begin();
321 i != connections_.end(); ++i) {
322 delete i->second;
323 }
324 connections_.clear();
325 }
326
327 void BrailleControllerImpl::ReconnectAll() {
328 DisconnectAll();
329 UpdateConnections();
330 }
331
332 void BrailleControllerImpl::UpdateConnections() {
333 for (WindowIdToConnectionMap::const_iterator i = connections_.begin();
334 i != connections_.end(); ++i) {
335 if (i->second->Connected()) {
336 continue;
337 }
338 TryConnection(i->first, i->second);
339 }
340 }
341
342 void BrailleControllerImpl::TryConnection(unsigned long windowId,
343 Connection* connection) {
344 if (!connection->Connect()) {
345 LOG(ERROR) << "Couldn't connect to brlapi\n";
346 return;
347 }
348 int path[2] = {0, 0};
349 int pathElements = 0;
350 #if defined(OS_CHROMEOS)
351 if (base::chromeos::IsRunningOnChromeOS()) {
352 path[pathElements++] = kDefaultTtyChromeOS;
353 }
354 #endif
355 if (pathElements == 0 && getenv("WINDOWPATH") == NULL) {
356 path[pathElements++] = kDefaultTtyLinux;
357 path[pathElements++] = windowId;
358 }
359 if (libbrlapi_loader_.brlapi__enterTtyModeWithPath(
360 connection->GetHandle(), path, pathElements, NULL) < 0) {
361 LOG(ERROR) << "brlapi: couldn't enter tty mode: " << BrlApiStrError();
362 connection->Disconnect();
363 return;
364 }
365 if (libbrlapi_loader_.brlapi__writeText(
366 connection->GetHandle(), 0, "Chrome") < 0) {
367 LOG(ERROR) << "Couldn't write window title to brlapi: "
368 << BrlApiStrError();
369 connection->Disconnect();
370 return;
371 }
372 const brlapi_keyCode_t extraKeys[] = {
373 BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_OFFLINE,
374 };
375 if (libbrlapi_loader_.brlapi__acceptKeys(
376 connection->GetHandle(),
377 brlapi_rangeType_command,
378 extraKeys,
379 arraysize(extraKeys)) < 0) {
380 LOG(ERROR) << "Couldn't acceptKeys: " << BrlApiStrError();
381 connection->Disconnect();
382 return;
383 }
384 }
385
386 scoped_ptr<KeyEvent> BrailleControllerImpl::MapKeyCode(brlapi_keyCode_t code) {
387 // TODO: Make this a table.
388 brlapi_expandedKeyCode_t expanded;
389 if (libbrlapi_loader_.brlapi_expandKeyCode(code, &expanded) != 0) {
390 // TODO: Log error.
391 return scoped_ptr<KeyEvent>();
392 }
393 KeyCommand keyCommand = KEY_COMMAND_NONE;
394 switch (expanded.type) {
395 case BRLAPI_KEY_TYPE_CMD:
396 switch (expanded.command) {
397 case BRLAPI_KEY_CMD_LNUP:
398 keyCommand = KEY_COMMAND_LINE_UP;
399 break;
400 case BRLAPI_KEY_CMD_LNDN:
401 keyCommand = KEY_COMMAND_LINE_DOWN;
402 break;
403 case BRLAPI_KEY_CMD_FWINLT:
404 keyCommand = KEY_COMMAND_PAN_LEFT;
405 break;
406 case BRLAPI_KEY_CMD_FWINRT:
407 keyCommand = KEY_COMMAND_PAN_RIGHT;
408 break;
409 }
410 break;
411 }
412 scoped_ptr<KeyEvent> result;
413 if (keyCommand != KEY_COMMAND_NONE) {
414 result.reset(new KeyEvent());
415 result->command = keyCommand;
416 }
417 return result.Pass();
418 }
419
420 void BrailleControllerImpl::DispatchKeys(Connection* connection) {
421 brlapi_handle_t* handle = connection->GetHandle();
422 brlapi_keyCode_t code;
423 while (true) {
424 int result = libbrlapi_loader_.brlapi__readKey(handle, 0 /*wait*/, &code);
425 if (result < 0) {
426 brlapi_error_t* err = BrlApiError();
427 if (err->brlerrno == BRLAPI_ERROR_LIBCERR && err->libcerrno == EINTR) {
428 continue;
429 }
430 break;
431 } else if (result == 0) {
432 return;
433 }
434 LOG(ERROR) << "Got key: %ld" << code;
435 scoped_ptr<KeyEvent> event = MapKeyCode(code);
436 if (event) {
437 LOG(ERROR) << "Dispatching key";
438 DispatchKeyEvent(event.Pass());
439 }
440 }
441
442 // Error, disconnect everything.
443 LOG(ERROR) << "BrlAPI error: " << BrlApiStrError();
444 DisconnectAll();
445 }
446
447 void BrailleControllerImpl::DispatchKeyEvent(scoped_ptr<KeyEvent> event) {
448 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
449 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
450 base::Bind(
451 &BrailleControllerImpl::DispatchKeyEvent,
452 base::Unretained(this),
453 base::Passed(&event)));
454 return;
455 }
456 FOR_EACH_OBSERVER(Observer, observers_, OnKeyEvent(event->ToValue()));
457 }
458
459 brlapi_error_t* BrailleControllerImpl::BrlApiError() {
460 DCHECK(libbrlapi_loader_.loaded());
461 return libbrlapi_loader_.brlapi_error_location();
462 }
463
464 std::string BrailleControllerImpl::BrlApiStrError() {
465 return libbrlapi_loader_.brlapi_strerror(BrlApiError());
466 }
467 } // namespace braille_private
468 } // namespace api
469 } // namespace extensions
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698