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_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 |
OLD | NEW |