Chromium Code Reviews| 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 |