| Index: chrome/browser/extensions/api/braille_private/braille_controller.cc
|
| diff --git a/chrome/browser/extensions/api/braille_private/braille_controller.cc b/chrome/browser/extensions/api/braille_private/braille_controller.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..3f7a50c618f85d6a9b2deefe536794a1a2d82ef5
|
| --- /dev/null
|
| +++ b/chrome/browser/extensions/api/braille_private/braille_controller.cc
|
| @@ -0,0 +1,469 @@
|
| +// 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_private/braille_controller.h"
|
| +
|
| +#include <errno.h>
|
| +#include <map>
|
| +
|
| +#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_private.h"
|
| +#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_private {
|
| +namespace {
|
| +// Default virtual terminal. This can be overriden by setting the
|
| +// WINDOWPATH environment variable.
|
| +// TODO(plundblad): Find a way to detect the controlling terminal of the
|
| +// X server.
|
| +static const int kDefaultTtyLinux = 7;
|
| +#if defined(OS_CHROMEOS)
|
| +static const int kDefaultTtyChromeOS = 1;
|
| +#endif
|
| +} // namespace
|
| +
|
| +class BrailleControllerImpl : public BrailleController {
|
| + public:
|
| + static BrailleControllerImpl* GetInstance();
|
| + virtual scoped_ptr<base::DictionaryValue> GetDisplayState() OVERRIDE;
|
| + virtual void WriteText(const std::string& text, int cursor) OVERRIDE;
|
| + virtual void AddObserver(Observer* observer) OVERRIDE;
|
| + virtual void RemoveObserver(Observer* observer) OVERRIDE;
|
| + private:
|
| + class Connection : public MessageLoopForIO::Watcher {
|
| + public:
|
| + Connection(BrailleControllerImpl* braille_controller) :
|
| + braille_controller_(braille_controller) {
|
| + DCHECK(braille_controller_->libbrlapi_loader_.loaded());
|
| + }
|
| +
|
| + virtual ~Connection() {
|
| + Disconnect();
|
| + }
|
| +
|
| + bool Connect() {
|
| + DCHECK(!handle_);
|
| + 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)) {
|
| + Disconnect();
|
| + return false;
|
| + }
|
| + return true;
|
| + }
|
| +
|
| + void Disconnect() {
|
| + if (handle_ == NULL) {
|
| + return;
|
| + }
|
| + braille_controller_->libbrlapi_loader_.brlapi__closeConnection(
|
| + handle_.get());
|
| + handle_.reset();
|
| + }
|
| +
|
| + bool Connected() { return handle_; }
|
| +
|
| + brlapi_handle_t* GetHandle() { return handle_.get(); }
|
| +
|
| + MessageLoopForIO::FileDescriptorWatcher* GetFDController() {
|
| + return &fd_controller_;
|
| + }
|
| +
|
| + // MessageLoopForIO::Watcher
|
| + virtual void OnFileCanReadWithoutBlocking(int fd) {
|
| + LOG(ERROR) << "Braille fd " << fd << " ready";
|
| + braille_controller_->DispatchKeys(this);
|
| + }
|
| +
|
| + virtual void OnFileCanWriteWithoutBlocking(int fd) {}
|
| +
|
| + private:
|
| + scoped_ptr<brlapi_handle_t, base::FreeDeleter> handle_;
|
| + MessageLoopForIO::FileDescriptorWatcher fd_controller_;
|
| + BrailleControllerImpl* braille_controller_;
|
| + };
|
| + typedef std::map<unsigned long, Connection*> WindowIdToConnectionMap;
|
| + BrailleControllerImpl();
|
| + virtual ~BrailleControllerImpl();
|
| + virtual void AddWindowOnIOThread(unsigned long windowId) OVERRIDE;
|
| + virtual void RemoveWindowOnIOThread(unsigned long windowId) OVERRIDE;
|
| + void StartConnections();
|
| + void StopConnections();
|
| + void OnSocketDirChanged(const base::FilePath& path, bool error);
|
| + void DisconnectAll();
|
| + void ReconnectAll();
|
| + void UpdateConnections();
|
| + void TryConnection(unsigned long windowId, Connection* connection);
|
| + void DispatchKeys(Connection* connection);
|
| + 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.
|
| + WindowIdToConnectionMap connections_;
|
| + 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();
|
| +}
|
| +
|
| +// static
|
| +void BrailleController::AddWindow(unsigned long windowId) {
|
| + if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
|
| + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
|
| + base::Bind(&BrailleController::AddWindow,
|
| + windowId));
|
| + } else {
|
| + GetInstance()->AddWindowOnIOThread(windowId);
|
| + }
|
| +}
|
| +
|
| +// static
|
| +void BrailleController::RemoveWindow(unsigned long windowId) {
|
| + if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
|
| + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
|
| + base::Bind(&BrailleController::RemoveWindow,
|
| + windowId));
|
| + } else {
|
| + GetInstance()->RemoveWindowOnIOThread(windowId);
|
| + }
|
| +}
|
| +
|
| +BrailleControllerImpl::BrailleControllerImpl() {
|
| + if (!libbrlapi_loader_.Load("libbrlapi.so.0.5")) {
|
| + LOG(ERROR) << "Couldn't load libbrlapi: " << strerror(errno);
|
| + return;
|
| + }
|
| +}
|
| +
|
| +BrailleControllerImpl::~BrailleControllerImpl() {
|
| +}
|
| +
|
| +// static
|
| +BrailleControllerImpl* BrailleControllerImpl::GetInstance() {
|
| + return Singleton<BrailleControllerImpl,
|
| + LeakySingletonTraits<BrailleControllerImpl> >::get();
|
| +}
|
| +
|
| +scoped_ptr<base::DictionaryValue> BrailleControllerImpl::GetDisplayState()
|
| + OVERRIDE {
|
| + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
|
| + DisplayState displayState;
|
| + for (WindowIdToConnectionMap::const_iterator i = connections_.begin();
|
| + i != connections_.end(); ++i) {
|
| + if (!i->second) {
|
| + continue;
|
| + }
|
| + brlapi_handle_t* handle = i->second->GetHandle();
|
| + unsigned int columns, lines;
|
| + if (libbrlapi_loader_.brlapi__getDisplaySize(handle, &columns, &lines)
|
| + < 0) {
|
| + // TODO: handle error.
|
| + continue;
|
| + }
|
| + if (columns > 0) {
|
| + displayState.available = true;
|
| + displayState.text_cells.reset(new int(columns * lines));
|
| + break;
|
| + }
|
| + }
|
| + return displayState.ToValue().Pass();
|
| +}
|
| +
|
| +void BrailleControllerImpl::WriteText(
|
| + const std::string& text, int cursor) {
|
| + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
|
| + if (!libbrlapi_loader_.loaded()) {
|
| + return;
|
| + }
|
| + // Only ascii for testing.
|
| + std::string asciiText;
|
| + asciiText.reserve(text.length());
|
| + for (std::string::const_iterator i = text.begin(); i != text.end(); ++i) {
|
| + if (static_cast<unsigned char>(*i) < 0x80) {
|
| + asciiText += *i;
|
| + } else {
|
| + asciiText.append("?");
|
| + }
|
| + }
|
| + if (cursor < 0) {
|
| + cursor = BRLAPI_CURSOR_OFF;
|
| + } else {
|
| + cursor += 1; // ONe-based in brlapi.
|
| + }
|
| + for (WindowIdToConnectionMap::const_iterator i = connections_.begin();
|
| + i != connections_.end(); ++i) {
|
| + if (!i->second) {
|
| + continue;
|
| + }
|
| + brlapi_handle_t* handle = i->second->GetHandle();
|
| + if (libbrlapi_loader_.brlapi__writeText(
|
| + handle, cursor, asciiText.c_str()) < 0) {
|
| + LOG(ERROR) << "Couldn't write to window %lu" << i->first;
|
| + }
|
| + }
|
| +}
|
| +
|
| +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::AddWindowOnIOThread(unsigned long windowId) {
|
| + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
|
| + LOG(ERROR) << "Setting up braille for window " << windowId;
|
| + if (!libbrlapi_loader_.loaded()) {
|
| + LOG(INFO) << "libbrlapi not loaded";
|
| + return;
|
| + }
|
| + WindowIdToConnectionMap::iterator it = connections_.find(windowId);
|
| + if (it != connections_.end()) {
|
| + LOG(WARNING) << "BrlAPI connection already exists for window " << windowId;
|
| + } else {
|
| + it = connections_.insert(
|
| + std::make_pair(windowId, new Connection(this))).first;
|
| + }
|
| + TryConnection(it->first, it->second);
|
| +}
|
| +
|
| +void BrailleControllerImpl::RemoveWindowOnIOThread(unsigned long windowId) {
|
| + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
|
| + LOG(ERROR) << "Tearing down braille for window " << windowId;
|
| + if (!libbrlapi_loader_.loaded()) {
|
| + return;
|
| + }
|
| + WindowIdToConnectionMap::iterator it = connections_.find(windowId);
|
| + if (it == connections_.end()) {
|
| + return;
|
| + }
|
| + delete it->second;
|
| + connections_.erase(it);
|
| +}
|
| +
|
| +void BrailleControllerImpl::StartConnections() {
|
| + base::FilePath brlapi_dir(BRLAPI_SOCKETPATH);
|
| + 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(WARNING) << "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::DisconnectAll() {
|
| + for (WindowIdToConnectionMap::iterator i = connections_.begin();
|
| + i != connections_.end(); ++i) {
|
| + delete i->second;
|
| + }
|
| + connections_.clear();
|
| +}
|
| +
|
| +void BrailleControllerImpl::ReconnectAll() {
|
| + DisconnectAll();
|
| + UpdateConnections();
|
| +}
|
| +
|
| +void BrailleControllerImpl::UpdateConnections() {
|
| + for (WindowIdToConnectionMap::const_iterator i = connections_.begin();
|
| + i != connections_.end(); ++i) {
|
| + if (i->second->Connected()) {
|
| + continue;
|
| + }
|
| + TryConnection(i->first, i->second);
|
| + }
|
| +}
|
| +
|
| +void BrailleControllerImpl::TryConnection(unsigned long windowId,
|
| + Connection* connection) {
|
| + 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;
|
| + path[pathElements++] = windowId;
|
| + }
|
| + if (libbrlapi_loader_.brlapi__enterTtyModeWithPath(
|
| + connection->GetHandle(), path, pathElements, NULL) < 0) {
|
| + LOG(ERROR) << "brlapi: couldn't enter tty mode: " << BrlApiStrError();
|
| + connection->Disconnect();
|
| + return;
|
| + }
|
| + if (libbrlapi_loader_.brlapi__writeText(
|
| + connection->GetHandle(), 0, "Chrome") < 0) {
|
| + LOG(ERROR) << "Couldn't write window title to brlapi: "
|
| + << 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) {
|
| + // TODO: Make this a table.
|
| + brlapi_expandedKeyCode_t expanded;
|
| + if (libbrlapi_loader_.brlapi_expandKeyCode(code, &expanded) != 0) {
|
| + // TODO: Log error.
|
| + return scoped_ptr<KeyEvent>();
|
| + }
|
| + KeyCommand keyCommand = KEY_COMMAND_NONE;
|
| + switch (expanded.type) {
|
| + case BRLAPI_KEY_TYPE_CMD:
|
| + switch (expanded.command) {
|
| + case BRLAPI_KEY_CMD_LNUP:
|
| + keyCommand = KEY_COMMAND_LINE_UP;
|
| + break;
|
| + case BRLAPI_KEY_CMD_LNDN:
|
| + keyCommand = KEY_COMMAND_LINE_DOWN;
|
| + break;
|
| + case BRLAPI_KEY_CMD_FWINLT:
|
| + keyCommand = KEY_COMMAND_PAN_LEFT;
|
| + break;
|
| + case BRLAPI_KEY_CMD_FWINRT:
|
| + keyCommand = KEY_COMMAND_PAN_RIGHT;
|
| + break;
|
| + }
|
| + break;
|
| + }
|
| + scoped_ptr<KeyEvent> result;
|
| + if (keyCommand != KEY_COMMAND_NONE) {
|
| + result.reset(new KeyEvent());
|
| + result->command = keyCommand;
|
| + }
|
| + return result.Pass();
|
| +}
|
| +
|
| +void BrailleControllerImpl::DispatchKeys(Connection* connection) {
|
| + brlapi_handle_t* handle = connection->GetHandle();
|
| + brlapi_keyCode_t code;
|
| + while (true) {
|
| + int result = libbrlapi_loader_.brlapi__readKey(handle, 0 /*wait*/, &code);
|
| + if (result < 0) {
|
| + brlapi_error_t* err = BrlApiError();
|
| + if (err->brlerrno == BRLAPI_ERROR_LIBCERR && err->libcerrno == EINTR) {
|
| + continue;
|
| + }
|
| + break;
|
| + } else if (result == 0) {
|
| + return;
|
| + }
|
| + LOG(ERROR) << "Got key: %ld" << code;
|
| + scoped_ptr<KeyEvent> event = MapKeyCode(code);
|
| + if (event) {
|
| + LOG(ERROR) << "Dispatching key";
|
| + DispatchKeyEvent(event.Pass());
|
| + }
|
| + }
|
| +
|
| + // Error, disconnect everything.
|
| + LOG(ERROR) << "BrlAPI error: " << BrlApiStrError();
|
| + DisconnectAll();
|
| +}
|
| +
|
| +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->ToValue()));
|
| +}
|
| +
|
| +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_private
|
| +} // namespace api
|
| +} // namespace extensions
|
|
|