This source file includes following definitions.
- GetInstance
- GetInstance
- connect_scheduled_
- TryLoadLibBrlApi
- GetDisplayState
- WriteDots
- AddObserver
- RemoveObserver
- SetCreateBrlapiConnectionForTesting
- PokeSocketDirForTesting
- StartConnecting
- StartWatchingSocketDirOnFileThread
- OnSocketDirChangedOnFileThread
- OnSocketDirChangedOnIOThread
- TryToConnect
- ResetRetryConnectHorizon
- ScheduleTryToConnect
- Disconnect
- CreateBrlapiConnection
- MapKeyCode
- DispatchKeys
- DispatchKeyEvent
- DispatchOnDisplayStateChanged
#include "chrome/browser/extensions/api/braille_display_private/braille_controller_brlapi.h"
#include <algorithm>
#include <cerrno>
#include <cstring>
#include <vector>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/time/time.h"
#include "chrome/browser/extensions/api/braille_display_private/brlapi_connection.h"
#include "content/public/browser/browser_thread.h"
namespace extensions {
using content::BrowserThread;
using base::Time;
using base::TimeDelta;
namespace api {
namespace braille_display_private {
namespace {
const int64 kConnectionDelayMs = 500;
const int64 kConnectRetryTimeout = 20000;
const int kAllDots = BRLAPI_DOT1 | BRLAPI_DOT2 | BRLAPI_DOT3 | BRLAPI_DOT4 |
BRLAPI_DOT5 | BRLAPI_DOT6 | BRLAPI_DOT7 | BRLAPI_DOT8;
}
BrailleController::BrailleController() {
}
BrailleController::~BrailleController() {
}
BrailleController* BrailleController::GetInstance() {
return BrailleControllerImpl::GetInstance();
}
BrailleControllerImpl* BrailleControllerImpl::GetInstance() {
return Singleton<BrailleControllerImpl,
LeakySingletonTraits<BrailleControllerImpl> >::get();
}
BrailleControllerImpl::BrailleControllerImpl()
: started_connecting_(false),
connect_scheduled_(false) {
create_brlapi_connection_function_ = base::Bind(
&BrailleControllerImpl::CreateBrlapiConnection,
base::Unretained(this));
}
BrailleControllerImpl::~BrailleControllerImpl() {
}
void BrailleControllerImpl::TryLoadLibBrlApi() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (libbrlapi_loader_.loaded())
return;
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<DisplayState> BrailleControllerImpl::GetDisplayState() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
StartConnecting();
scoped_ptr<DisplayState> display_state(new DisplayState);
if (connection_.get() && connection_->Connected()) {
size_t size;
if (!connection_->GetDisplaySize(&size)) {
Disconnect();
} else if (size > 0) {
display_state->available = true;
display_state->text_cell_count.reset(new int(size));
}
}
return display_state.Pass();
}
void BrailleControllerImpl::WriteDots(const std::string& cells) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (connection_ && connection_->Connected()) {
size_t size;
if (!connection_->GetDisplaySize(&size)) {
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]))
Disconnect();
}
}
void BrailleControllerImpl::AddObserver(BrailleObserver* observer) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
base::Bind(
&BrailleControllerImpl::StartConnecting,
base::Unretained(this)))) {
NOTREACHED();
}
observers_.AddObserver(observer);
}
void BrailleControllerImpl::RemoveObserver(BrailleObserver* observer) {
DCHECK_CURRENTLY_ON(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::PokeSocketDirForTesting() {
OnSocketDirChangedOnIOThread();
}
void BrailleControllerImpl::StartConnecting() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (started_connecting_)
return;
started_connecting_ = true;
TryLoadLibBrlApi();
if (!libbrlapi_loader_.loaded()) {
return;
}
BrowserThread::PostTaskAndReply(
BrowserThread::FILE, FROM_HERE,
base::Bind(
&BrailleControllerImpl::StartWatchingSocketDirOnFileThread,
base::Unretained(this)),
base::Bind(
&BrailleControllerImpl::TryToConnect,
base::Unretained(this)));
ResetRetryConnectHorizon();
}
void BrailleControllerImpl::StartWatchingSocketDirOnFileThread() {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
base::FilePath brlapi_dir(BRLAPI_SOCKETPATH);
if (!file_path_watcher_.Watch(
brlapi_dir, false, base::Bind(
&BrailleControllerImpl::OnSocketDirChangedOnFileThread,
base::Unretained(this)))) {
LOG(WARNING) << "Couldn't watch brlapi directory " << BRLAPI_SOCKETPATH;
}
}
void BrailleControllerImpl::OnSocketDirChangedOnFileThread(
const base::FilePath& path, bool error) {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
if (error) {
LOG(ERROR) << "Error watching brlapi directory: " << path.value();
return;
}
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE, base::Bind(
&BrailleControllerImpl::OnSocketDirChangedOnIOThread,
base::Unretained(this)));
}
void BrailleControllerImpl::OnSocketDirChangedOnIOThread() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
VLOG(1) << "BrlAPI directory changed";
ResetRetryConnectHorizon();
ScheduleTryToConnect();
}
void BrailleControllerImpl::TryToConnect() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(libbrlapi_loader_.loaded());
connect_scheduled_ = false;
if (!connection_.get())
connection_ = create_brlapi_connection_function_.Run();
if (connection_.get() && !connection_->Connected()) {
VLOG(1) << "Trying to connect to brlapi";
BrlapiConnection::ConnectResult result = connection_->Connect(base::Bind(
&BrailleControllerImpl::DispatchKeys,
base::Unretained(this)));
switch (result) {
case BrlapiConnection::CONNECT_SUCCESS:
DispatchOnDisplayStateChanged(GetDisplayState());
break;
case BrlapiConnection::CONNECT_ERROR_NO_RETRY:
break;
case BrlapiConnection::CONNECT_ERROR_RETRY:
ScheduleTryToConnect();
break;
default:
NOTREACHED();
}
}
}
void BrailleControllerImpl::ResetRetryConnectHorizon() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
retry_connect_horizon_ = Time::Now() + TimeDelta::FromMilliseconds(
kConnectRetryTimeout);
}
void BrailleControllerImpl::ScheduleTryToConnect() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
TimeDelta delay(TimeDelta::FromMilliseconds(kConnectionDelayMs));
if (connect_scheduled_)
return;
if (Time::Now() + delay > retry_connect_horizon_) {
VLOG(1) << "Stopping to retry to connect to brlapi";
return;
}
VLOG(1) << "Scheduling connection retry to brlapi";
connect_scheduled_ = true;
BrowserThread::PostDelayedTask(BrowserThread::IO, FROM_HERE,
base::Bind(
&BrailleControllerImpl::TryToConnect,
base::Unretained(this)),
delay);
}
void BrailleControllerImpl::Disconnect() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!connection_ || !connection_->Connected())
return;
connection_->Disconnect();
DispatchOnDisplayStateChanged(scoped_ptr<DisplayState>(new DisplayState()));
}
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;
result->braille_dots.reset(new int(expanded.argument & kAllDots));
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) {
brlapi_error_t* err = connection_->BrlapiError();
if (err->brlerrno == BRLAPI_ERROR_LIBCERR && err->libcerrno == EINTR)
continue;
VLOG(1) << "BrlAPI error: " << connection_->BrlapiStrError();
Disconnect();
return;
} else if (result == 0) {
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;
}
VLOG(1) << "Dispatching key event: " << *event->ToValue();
FOR_EACH_OBSERVER(BrailleObserver, observers_, OnKeyEvent(*event));
}
void BrailleControllerImpl::DispatchOnDisplayStateChanged(
scoped_ptr<DisplayState> new_state) {
if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
if (!BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&BrailleControllerImpl::DispatchOnDisplayStateChanged,
base::Unretained(this),
base::Passed(&new_state)))) {
NOTREACHED();
}
return;
}
FOR_EACH_OBSERVER(BrailleObserver, observers_,
OnDisplayStateChanged(*new_state));
}
}
}
}