| 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..6b24f244e61b194a3b6d2e652dec58d185a9ea1c
|
| --- /dev/null
|
| +++ b/chrome/browser/extensions/api/braille_display_private/braille_controller_brlapi.cc
|
| @@ -0,0 +1,309 @@
|
| +// 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/files/file_path_watcher.h"
|
| +#include "base/memory/scoped_ptr.h"
|
| +#include "base/memory/singleton.h"
|
| +#include "base/observer_list.h"
|
| +#include "base/time/time.h"
|
| +#include "chrome/browser/extensions/api/braille_display_private/brlapi_connection.h"
|
| +#include "content/public/browser/browser_thread.h"
|
| +#include "library_loaders/libbrlapi.h"
|
| +
|
| +namespace extensions {
|
| +using content::BrowserThread;
|
| +using base::TimeDelta;
|
| +namespace api {
|
| +namespace braille_display_private {
|
| +
|
| +namespace {
|
| +// Delay between detecting a directory update and trying to connect
|
| +// to the brlapi.
|
| +const int64 kConnectionDelayMs = 1000;
|
| +}
|
| +
|
| +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(BrailleObserver* observer) OVERRIDE;
|
| + virtual void RemoveObserver(BrailleObserver* observer) OVERRIDE;
|
| + virtual void SetCreateBrlapiConnectionForTesting(
|
| + const CreateBrlapiConnectionFunction& callback) OVERRIDE;
|
| +
|
| + private:
|
| + BrailleControllerImpl();
|
| + virtual ~BrailleControllerImpl();
|
| + void TryLoadLibBrlApi();
|
| + void StartConnecting();
|
| + void OnSocketDirChanged(const base::FilePath& path, bool error);
|
| + void TryToConnect();
|
| + scoped_ptr<BrlapiConnection> CreateBrlapiConnection();
|
| + void DispatchKeys();
|
| + scoped_ptr<KeyEvent> MapKeyCode(brlapi_keyCode_t code);
|
| + void DispatchKeyEvent(scoped_ptr<KeyEvent> event);
|
| +
|
| + LibBrlapiLoader libbrlapi_loader_;
|
| + CreateBrlapiConnectionFunction create_brlapi_connection_function_;
|
| +
|
| + // Manipulated on the IO thread.
|
| + scoped_ptr<BrlapiConnection> connection_;
|
| + base::FilePathWatcher file_path_watcher_;
|
| +
|
| + // Manipulated on the UI thread.
|
| + ObserverList<BrailleObserver> 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
|
| +BrailleControllerImpl* BrailleControllerImpl::GetInstance() {
|
| + return Singleton<BrailleControllerImpl,
|
| + LeakySingletonTraits<BrailleControllerImpl> >::get();
|
| +}
|
| +
|
| +BrailleControllerImpl::BrailleControllerImpl()
|
| + : watching_dir_(false) {
|
| + create_brlapi_connection_function_ = base::Bind(
|
| + &BrailleControllerImpl::CreateBrlapiConnection,
|
| + base::Unretained(this));
|
| +}
|
| +
|
| +BrailleControllerImpl::~BrailleControllerImpl() {
|
| +}
|
| +
|
| +void BrailleControllerImpl::TryLoadLibBrlApi() {
|
| + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
|
| + if (libbrlapi_loader_.loaded())
|
| + return;
|
| + // These versions of libbrlapi work the same for the functions we
|
| + // are using. (0.6.0 adds brlapi_writeWText).
|
| + static const char* kSupportedVersions[] = {
|
| + "libbrlapi.so.0.5",
|
| + "libbrlapi.so.0.6"
|
| + };
|
| + for (size_t i = 0; i < arraysize(kSupportedVersions); ++i) {
|
| + if (libbrlapi_loader_.Load(kSupportedVersions[i]))
|
| + return;
|
| + }
|
| + LOG(WARNING) << "Couldn't load libbrlapi: " << strerror(errno);
|
| +}
|
| +
|
| +scoped_ptr<base::DictionaryValue> BrailleControllerImpl::GetDisplayState() {
|
| + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
|
| + if (!watching_dir_) {
|
| + watching_dir_ = true;
|
| + StartConnecting();
|
| + }
|
| + DisplayState displayState;
|
| + if (connection_.get() && connection_->Connected()) {
|
| + size_t size;
|
| + if (!connection_->GetDisplaySize(&size)) {
|
| + connection_->Disconnect();
|
| + } else if (size > 0) { // size == 0 means no display present.
|
| + 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 (connection_ && connection_->Connected()) {
|
| + size_t size;
|
| + if (!connection_->GetDisplaySize(&size)) {
|
| + connection_->Disconnect();
|
| + }
|
| + std::vector<unsigned char> sizedCells(size);
|
| + std::memcpy(&sizedCells[0], cells.data(), std::min(cells.size(), size));
|
| + if (size > cells.size())
|
| + std::fill(sizedCells.begin() + cells.size(), sizedCells.end(), 0);
|
| + if (!connection_->WriteDots(&sizedCells[0]))
|
| + connection_->Disconnect();
|
| + }
|
| +}
|
| +
|
| +void BrailleControllerImpl::AddObserver(BrailleObserver* observer) {
|
| + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
|
| + observers_.AddObserver(observer);
|
| +}
|
| +
|
| +void BrailleControllerImpl::RemoveObserver(BrailleObserver* observer) {
|
| + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
|
| + observers_.RemoveObserver(observer);
|
| +}
|
| +
|
| +void BrailleControllerImpl::SetCreateBrlapiConnectionForTesting(
|
| + const CreateBrlapiConnectionFunction& function) {
|
| + if (function.is_null()) {
|
| + create_brlapi_connection_function_ = base::Bind(
|
| + &BrailleControllerImpl::CreateBrlapiConnection,
|
| + base::Unretained(this));
|
| + } else {
|
| + create_brlapi_connection_function_ = function;
|
| + }
|
| +}
|
| +
|
| +void BrailleControllerImpl::StartConnecting() {
|
| + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
|
| + TryLoadLibBrlApi();
|
| + if (!libbrlapi_loader_.loaded()) {
|
| + return;
|
| + }
|
| + 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;
|
| + }
|
| + TryToConnect();
|
| +}
|
| +
|
| +void BrailleControllerImpl::OnSocketDirChanged(const base::FilePath& path,
|
| + bool error) {
|
| + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
|
| + DCHECK(libbrlapi_loader_.loaded());
|
| + if (error) {
|
| + LOG(ERROR) << "Error watching brlapi directory: " << path.value();
|
| + return;
|
| + }
|
| + LOG(INFO) << "BrlAPI directory changed";
|
| + BrowserThread::PostDelayedTask(BrowserThread::IO, FROM_HERE,
|
| + base::Bind(
|
| + &BrailleControllerImpl::TryToConnect,
|
| + base::Unretained(this)),
|
| + TimeDelta::FromMilliseconds(
|
| + kConnectionDelayMs));
|
| +}
|
| +
|
| +void BrailleControllerImpl::TryToConnect() {
|
| + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
|
| + DCHECK(libbrlapi_loader_.loaded());
|
| + if (!connection_.get())
|
| + connection_ = create_brlapi_connection_function_.Run();
|
| + if (connection_.get() && !connection_->Connected()) {
|
| + if (!connection_->Connect(base::Bind(
|
| + &BrailleControllerImpl::DispatchKeys,
|
| + base::Unretained(this))))
|
| + LOG(WARNING) << "Couldn't connect to brlapi";
|
| + }
|
| +}
|
| +
|
| +scoped_ptr<BrlapiConnection> BrailleControllerImpl::CreateBrlapiConnection() {
|
| + DCHECK(libbrlapi_loader_.loaded());
|
| + return BrlapiConnection::Create(&libbrlapi_loader_);
|
| +}
|
| +
|
| +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 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() {
|
| + DCHECK(connection_.get());
|
| + brlapi_keyCode_t code;
|
| + while (true) {
|
| + int result = connection_->ReadKey(&code);
|
| + if (result < 0) { // Error.
|
| + brlapi_error_t* err = connection_->BrlapiError();
|
| + if (err->brlerrno == BRLAPI_ERROR_LIBCERR && err->libcerrno == EINTR)
|
| + continue;
|
| + // Disconnect on other errors.
|
| + LOG(ERROR) << "BrlAPI error: " << connection_->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(BrailleObserver, observers_, OnKeyEvent(*event));
|
| +}
|
| +
|
| +} // namespace braille_display_private
|
| +} // namespace api
|
| +} // namespace extensions
|
|
|