| 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
 | 
| 
 |