Chromium Code Reviews| Index: chrome/browser/extensions/api/braille_display_private/braille_controller_brlapi.cc |
| diff --git a/chrome/browser/extensions/api/braille_display_private/braille_controller_brlapi.cc b/chrome/browser/extensions/api/braille_display_private/braille_controller_brlapi.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..dac89ffa10f920244297dcd59f635ab76f55b278 |
| --- /dev/null |
| +++ b/chrome/browser/extensions/api/braille_display_private/braille_controller_brlapi.cc |
| @@ -0,0 +1,403 @@ |
| +// Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "chrome/browser/extensions/api/braille_display_private/braille_controller.h" |
| + |
| +#include <algorithm> |
| +#include <cerrno> |
| +#include <cstring> |
| +#include <vector> |
| + |
| +#include "base/bind.h" |
| +#include "base/bind_helpers.h" |
| +#include "base/chromeos/chromeos_version.h" |
| +#include "base/files/file_path_watcher.h" |
| +#include "base/memory/scoped_ptr.h" |
| +#include "base/memory/singleton.h" |
| +#include "base/message_loop/message_loop.h" |
| +#include "base/observer_list.h" |
| +#include "base/time/time.h" |
| +#include "chrome/common/extensions/api/braille_display_private.h" |
|
David Tseng
2013/09/05 19:21:23
Already included in header.
|
| +#include "content/public/browser/browser_thread.h" |
| +#include "library_loaders/libbrlapi.h" |
| + |
| +namespace extensions { |
| +using content::BrowserThread; |
| +using base::MessageLoopForIO; |
| +using base::TimeDelta; |
| +namespace api { |
| +namespace braille_display_private { |
| +namespace { |
| +// Default virtual terminal. This can be overriden by setting the |
| +// WINDOWPATH environment variable. This is only used when not running |
| +// under Crhome OS (that is in aura for a Linux desktop). |
|
David Tseng
2013/09/05 19:21:23
ChromeOS
|
| +// TODO(plundblad): Find a way to detect the controlling terminal of the |
| +// X server. |
| +static const int kDefaultTtyLinux = 7; |
| +#if defined(OS_CHROMEOS) |
| +// The GUI is always running on vt1 in ChromeOS. |
| +static const int kDefaultTtyChromeOS = 1; |
|
David Tseng
2013/09/05 19:21:23
You could eliminate one of the constants by doing
|
| +#endif |
| +} // namespace |
| + |
| +class BrailleControllerImpl : public BrailleController { |
| + public: |
| + static BrailleControllerImpl* GetInstance(); |
| + virtual scoped_ptr<base::DictionaryValue> GetDisplayState() OVERRIDE; |
| + virtual void WriteDots(const std::string& cells) OVERRIDE; |
| + virtual void AddObserver(Observer* observer) OVERRIDE; |
| + virtual void RemoveObserver(Observer* observer) OVERRIDE; |
| + |
| + private: |
| + class Connection : public MessageLoopForIO::Watcher { |
| + public: |
| + explicit Connection(BrailleControllerImpl* braille_controller) : |
| + braille_controller_(braille_controller) { |
| + } |
| + |
| + virtual ~Connection() { |
| + Disconnect(); |
| + } |
| + |
| + bool Connect() { |
| + DCHECK(!handle_); |
| + if (!braille_controller_->libbrlapi_loader_.loaded()) { |
| + return false; |
| + } |
| + handle_.reset((brlapi_handle_t*) malloc( |
| + braille_controller_->libbrlapi_loader_.brlapi_getHandleSize())); |
| + int fd = braille_controller_->libbrlapi_loader_.brlapi__openConnection( |
| + handle_.get(), NULL, NULL); |
| + if (fd < 0) { |
| + handle_.reset(); |
| + LOG(ERROR) << "Error connecting to brlapi: " |
| + << braille_controller_->BrlApiStrError(); |
| + return false; |
| + } |
| + if (!MessageLoopForIO::current()->WatchFileDescriptor( |
| + fd, true, MessageLoopForIO::WATCH_READ, &fd_controller_, this)) { |
| + LOG(ERROR) << "Couldn't watch file descriptor " << fd; |
| + Disconnect(); |
| + return false; |
| + } |
| + return true; |
| + } |
| + |
| + void Disconnect() { |
| + if (handle_ == NULL) { |
| + return; |
| + } |
| + fd_controller_.StopWatchingFileDescriptor(); |
| + braille_controller_->libbrlapi_loader_.brlapi__closeConnection( |
| + handle_.get()); |
| + handle_.reset(); |
| + } |
| + |
| + bool Connected() { return handle_; } |
| + |
| + brlapi_handle_t* GetHandle() { return handle_.get(); } |
| + |
| + // Gets the total size of the display, which may be 0 if no display is |
| + // present, returning true on success. Note that this is cached in the |
| + // brlapi client so it is cheap. |
| + bool GetDisplaySize(size_t* size) { |
| + if (!handle_) { |
| + return false; |
| + } |
| + unsigned int columns, rows; |
| + if (braille_controller_->libbrlapi_loader_.brlapi__getDisplaySize( |
| + handle_.get(), &columns, &rows) < 0) { |
| + return false; |
| + } |
| + *size = columns * rows; |
| + return true; |
| + } |
| + |
| + // MessageLoopForIO::Watcher |
| + virtual void OnFileCanReadWithoutBlocking(int fd) { |
| + braille_controller_->DispatchKeys(); |
| + } |
| + |
| + virtual void OnFileCanWriteWithoutBlocking(int fd) {} |
| + |
| + private: |
| + scoped_ptr<brlapi_handle_t, base::FreeDeleter> handle_; |
| + MessageLoopForIO::FileDescriptorWatcher fd_controller_; |
| + BrailleControllerImpl* braille_controller_; |
| + }; |
| + BrailleControllerImpl(); |
| + virtual ~BrailleControllerImpl(); |
| + void StartConnections(); |
| + void StopConnections(); |
| + void OnSocketDirChanged(const base::FilePath& path, bool error); |
| + void ReconnectAll(); |
| + void UpdateConnections(); |
| + void TryConnection(); |
| + void DispatchKeys(); |
| + scoped_ptr<KeyEvent> MapKeyCode(brlapi_keyCode_t code); |
| + void DispatchKeyEvent(scoped_ptr<KeyEvent> event); |
| + brlapi_error_t* BrlApiError(); |
| + std::string BrlApiStrError(); |
| + LibBrlApiLoader libbrlapi_loader_; |
| + // Manipulated on the IO thread. |
| + Connection connection_; |
| + base::FilePathWatcher file_path_watcher_; |
| + // Manipulated on the UI thread. |
| + ObserverList<Observer> observers_; |
| + bool watching_dir_; |
| + friend struct DefaultSingletonTraits<BrailleControllerImpl>; |
| + DISALLOW_COPY_AND_ASSIGN(BrailleControllerImpl); |
| +}; |
| + |
| +BrailleController::BrailleController() { |
| +} |
| + |
| +BrailleController::~BrailleController() { |
| +} |
| + |
| +// static |
| +BrailleController* BrailleController::GetInstance() { |
| + return BrailleControllerImpl::GetInstance(); |
| +} |
| + |
| +BrailleControllerImpl::BrailleControllerImpl() |
| + : connection_(this), watching_dir_(false) { |
| + if (!libbrlapi_loader_.Load("libbrlapi.so.0.5")) { |
|
David Tseng
2013/09/05 19:21:23
Define this as a static constant.
|
| + LOG(ERROR) << "Couldn't load libbrlapi: " << strerror(errno); |
| + } |
|
David Tseng
2013/09/05 19:21:23
No need for braces in if clauses of one line (Chro
|
| +} |
| + |
| +BrailleControllerImpl::~BrailleControllerImpl() { |
| +} |
| + |
| +// static |
| +BrailleControllerImpl* BrailleControllerImpl::GetInstance() { |
| + return Singleton<BrailleControllerImpl, |
| + LeakySingletonTraits<BrailleControllerImpl> >::get(); |
| +} |
| + |
| +scoped_ptr<base::DictionaryValue> BrailleControllerImpl::GetDisplayState() { |
| + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
| + DisplayState displayState; |
| + if (connection_.Connected()) { |
| + size_t size; |
| + if (!connection_.GetDisplaySize(&size)) { |
| + LOG(ERROR) << "Couldn't get braille display size " << BrlApiStrError(); |
| + connection_.Disconnect(); |
| + } else if (size > 0) { |
|
David Tseng
2013/09/05 19:21:23
When does a display get size of zero? Is that also
|
| + displayState.available = true; |
| + displayState.text_cell_count.reset(new int(size)); |
| + } |
| + } |
| + return displayState.ToValue().Pass(); |
| +} |
| + |
| +void BrailleControllerImpl::WriteDots(const std::string& cells) { |
| + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
| + if (!libbrlapi_loader_.loaded()) { |
|
David Tseng
2013/09/05 19:21:23
Ditto; remove braces.
|
| + return; |
| + } |
| + if (connection_.Connected()) { |
| + brlapi_handle_t* handle = connection_.GetHandle(); |
| + size_t size; |
| + if (!connection_.GetDisplaySize(&size)) { |
| + LOG(ERROR) << "Couldn't get display size " << BrlApiStrError(); |
| + connection_.Disconnect(); |
| + } |
| + std::vector<unsigned char> sizedCells(size); |
| + std::memcpy(&sizedCells[0], cells.data(), std::min(cells.size(), size)); |
| + if (size > cells.size()) { |
|
David Tseng
2013/09/05 19:21:23
Ditto.
|
| + std::fill(sizedCells.begin() + cells.size(), sizedCells.end(), 0); |
| + } |
| + if (libbrlapi_loader_.brlapi__writeDots(handle, &sizedCells[0]) < 0) { |
| + LOG(ERROR) << "Couldn't write to brlapi: " << BrlApiStrError(); |
| + connection_.Disconnect(); |
|
David Tseng
2013/09/05 19:21:23
Are there instances where we don't want to disconn
|
| + } |
| + } |
| +} |
| + |
| +void BrailleControllerImpl::AddObserver(Observer* observer) { |
| + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| + observers_.AddObserver(observer); |
| + if (libbrlapi_loader_.loaded() && !watching_dir_) { |
| + watching_dir_ = true; |
| + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, |
| + base::Bind(&BrailleControllerImpl::StartConnections, |
| + base::Unretained(this))); |
| + } |
| +} |
| + |
| +void BrailleControllerImpl::RemoveObserver(Observer* observer) { |
| + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| + observers_.RemoveObserver(observer); |
| +} |
| + |
| +void BrailleControllerImpl::StartConnections() { |
| + base::FilePath brlapi_dir(BRLAPI_SOCKETPATH); |
| + LOG(ERROR) << "Watching brlapi dir " << BRLAPI_SOCKETPATH; |
|
David Tseng
2013/09/05 19:21:23
INFO?
|
| + if (!file_path_watcher_.Watch( |
| + brlapi_dir, false, base::Bind( |
| + &BrailleControllerImpl::OnSocketDirChanged, |
| + base::Unretained(this)))) { |
| + LOG(WARNING) << "Couldn't watch brlapi directory " << BRLAPI_SOCKETPATH; |
| + return; |
| + } |
| + UpdateConnections(); |
| +} |
| + |
| +void BrailleControllerImpl::OnSocketDirChanged(const base::FilePath& path, |
| + bool error) { |
| + if (error) { |
| + LOG(ERROR) << "Error watching brlapi path: " << path.value(); |
| + return; |
| + } |
| + LOG(ERROR) << "BrlAPI directory changed"; |
| + BrowserThread::PostDelayedTask(BrowserThread::IO, FROM_HERE, |
| + base::Bind( |
| + &BrailleControllerImpl::UpdateConnections, |
| + base::Unretained(this)), |
| + TimeDelta::FromSeconds(1)); |
| + |
| +} |
| + |
| +void BrailleControllerImpl::UpdateConnections() { |
| + if (!connection_.Connected()) { |
| + TryConnection(); |
| + } |
| +} |
| + |
| +void BrailleControllerImpl::TryConnection() { |
| + if (!connection_.Connect()) { |
| + LOG(ERROR) << "Couldn't connect to brlapi\n"; |
| + return; |
| + } |
| + int path[2] = {0, 0}; |
| + int pathElements = 0; |
| +#if defined(OS_CHROMEOS) |
| + if (base::chromeos::IsRunningOnChromeOS()) { |
| + path[pathElements++] = kDefaultTtyChromeOS; |
| + } |
| +#endif |
| + if (pathElements == 0 && getenv("WINDOWPATH") == NULL) { |
| + path[pathElements++] = kDefaultTtyLinux; |
| + } |
| + if (libbrlapi_loader_.brlapi__enterTtyModeWithPath( |
| + connection_.GetHandle(), path, pathElements, NULL) < 0) { |
| + LOG(ERROR) << "brlapi: couldn't enter tty mode: " << BrlApiStrError(); |
| + connection_.Disconnect(); |
| + return; |
| + } |
| + const brlapi_keyCode_t extraKeys[] = { |
| + BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_OFFLINE, |
| + }; |
| + if (libbrlapi_loader_.brlapi__acceptKeys( |
| + connection_.GetHandle(), |
| + brlapi_rangeType_command, |
| + extraKeys, |
| + arraysize(extraKeys)) < 0) { |
| + LOG(ERROR) << "Couldn't acceptKeys: " << BrlApiStrError(); |
| + connection_.Disconnect(); |
| + return; |
| + } |
| +} |
| + |
| +scoped_ptr<KeyEvent> BrailleControllerImpl::MapKeyCode(brlapi_keyCode_t code) { |
| + brlapi_expandedKeyCode_t expanded; |
| + if (libbrlapi_loader_.brlapi_expandKeyCode(code, &expanded) != 0) { |
| + LOG(ERROR) << "Couldn't expand key key code " << code; |
| + return scoped_ptr<KeyEvent>(); |
| + } |
| + scoped_ptr<KeyEvent> result(new KeyEvent); |
| + result->command = KEY_COMMAND_NONE; |
| + switch (expanded.type) { |
| + case BRLAPI_KEY_TYPE_CMD: |
| + switch (expanded.command) { |
| + case BRLAPI_KEY_CMD_LNUP: |
| + result->command = KEY_COMMAND_LINE_UP; |
| + break; |
| + case BRLAPI_KEY_CMD_LNDN: |
| + result->command = KEY_COMMAND_LINE_DOWN; |
| + break; |
| + case BRLAPI_KEY_CMD_FWINLT: |
| + result->command = KEY_COMMAND_PAN_LEFT; |
| + break; |
| + case BRLAPI_KEY_CMD_FWINRT: |
| + result->command = KEY_COMMAND_PAN_RIGHT; |
| + break; |
| + case BRLAPI_KEY_CMD_TOP: |
| + result->command = KEY_COMMAND_TOP; |
| + break; |
| + case BRLAPI_KEY_CMD_BOT: |
| + result->command = KEY_COMMAND_BOTTOM; |
| + break; |
| + case BRLAPI_KEY_CMD_ROUTE: |
| + result->command = KEY_COMMAND_ROUTING; |
| + result->display_position.reset(new int(expanded.argument)); |
| + break; |
| + case BRLAPI_KEY_CMD_PASSDOTS: |
| + result->command = KEY_COMMAND_DOTS; |
| + // The 8 low-order bits in the argument contains the dots. |
| + result->braille_dots.reset(new int(expanded.argument & 0xf)); |
| + if ((expanded.argument & BRLAPI_DOTC) != 0) { |
| + result->space_key.reset(new bool(true)); |
| + } |
| + break; |
| + } |
| + break; |
| + } |
| + if (result->command == KEY_COMMAND_NONE) { |
| + result.reset(); |
| + } |
| + return result.Pass(); |
| +} |
| + |
| +void BrailleControllerImpl::DispatchKeys() { |
| + brlapi_handle_t* handle = connection_.GetHandle(); |
| + DCHECK(handle != NULL); |
| + brlapi_keyCode_t code; |
| + while (true) { |
| + int result = libbrlapi_loader_.brlapi__readKey(handle, 0 /*wait*/, &code); |
| + if (result < 0) { // Error. |
| + brlapi_error_t* err = BrlApiError(); |
| + if (err->brlerrno == BRLAPI_ERROR_LIBCERR && err->libcerrno == EINTR) { |
|
David Tseng
2013/09/05 19:21:23
Ditto.
|
| + continue; |
| + } |
| + // Disconnect on other errors. |
| + LOG(ERROR) << "BrlAPI error: " << BrlApiStrError(); |
| + connection_.Disconnect(); |
| + return; |
| + } else if (result == 0) { // No more data. |
| + return; |
| + } |
| + scoped_ptr<KeyEvent> event = MapKeyCode(code); |
| + if (event) { |
| + DispatchKeyEvent(event.Pass()); |
| + } |
| + } |
| +} |
| + |
| +void BrailleControllerImpl::DispatchKeyEvent(scoped_ptr<KeyEvent> event) { |
| + if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { |
| + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| + base::Bind( |
| + &BrailleControllerImpl::DispatchKeyEvent, |
| + base::Unretained(this), |
| + base::Passed(&event))); |
| + return; |
| + } |
| + FOR_EACH_OBSERVER(Observer, observers_, OnKeyEvent(*event)); |
| +} |
| + |
| +brlapi_error_t* BrailleControllerImpl::BrlApiError() { |
| + DCHECK(libbrlapi_loader_.loaded()); |
| + return libbrlapi_loader_.brlapi_error_location(); |
| +} |
| + |
| +std::string BrailleControllerImpl::BrlApiStrError() { |
| + return libbrlapi_loader_.brlapi_strerror(BrlApiError()); |
| +} |
| + |
| +} // namespace braille_display_private |
| +} // namespace api |
| +} // namespace extensions |