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 |