Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3226)

Unified Diff: chrome/browser/extensions/api/braille_display_private/braille_controller_brlapi.cc

Issue 13355002: Implement chrome.brailleDisplayPrivate API (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Style fixes and add support for two versions of libbrlapi when dlopening it. Created 7 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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..ccca763bebfe65be8d1fc3379b23b1f43fbec818
--- /dev/null
+++ b/chrome/browser/extensions/api/braille_display_private/braille_controller_brlapi.cc
@@ -0,0 +1,409 @@
+// 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/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 "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_display_private {
+namespace {
+// Default virtual terminal. This can be overriden by setting the
+// WINDOWPATH environment variable. This is only used when not running
+// under CrhomeOS (that is in aura for a Linux desktop).
+// TODO(plundblad): Find a way to detect the controlling terminal of the
+// X server.
+static const int kDefaultTtyLinux = 7;
+#if defined(OS_CHROMEOS)
+// The GUI is always running on vt1 in 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 WriteDots(const std::string& cells) OVERRIDE;
+ virtual void AddObserver(BrailleObserver* observer) OVERRIDE;
+ virtual void RemoveObserver(BrailleObserver* observer) OVERRIDE;
+
+ private:
+ class Connection : public MessageLoopForIO::Watcher {
dmazzoni 2013/09/06 16:49:53 Since there's only one connection at a time, why i
+ public:
+ explicit Connection(BrailleControllerImpl* braille_controller) :
+ braille_controller_(braille_controller) {
+ }
+
+ virtual ~Connection() {
+ Disconnect();
+ }
+
+ bool Connect() {
dmazzoni 2013/09/06 16:49:53 Don't inline nontrivial functions in the class def
+ DCHECK(!handle_);
+ if (!braille_controller_->libbrlapi_loader_.loaded()) {
+ return false;
+ }
+ 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)) {
+ LOG(ERROR) << "Couldn't watch file descriptor " << fd;
+ Disconnect();
+ return false;
+ }
+ return true;
+ }
+
+ void Disconnect() {
+ if (handle_ == NULL) {
+ return;
+ }
+ fd_controller_.StopWatchingFileDescriptor();
+ braille_controller_->libbrlapi_loader_.brlapi__closeConnection(
+ handle_.get());
+ handle_.reset();
+ }
+
+ bool Connected() { return handle_; }
+
+ brlapi_handle_t* GetHandle() { return handle_.get(); }
+
+ // Gets the total size of the display, which may be 0 if no display is
+ // present, returning true on success. Note that this is cached in the
+ // brlapi client so it is cheap.
+ bool GetDisplaySize(size_t* size) {
+ if (!handle_) {
+ return false;
+ }
+ unsigned int columns, rows;
+ if (braille_controller_->libbrlapi_loader_.brlapi__getDisplaySize(
+ handle_.get(), &columns, &rows) < 0) {
+ return false;
+ }
+ *size = columns * rows;
+ return true;
+ }
+
+ // MessageLoopForIO::Watcher
+ virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE {
+ braille_controller_->DispatchKeys();
+ }
+
+ virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE {}
+
+ private:
+ scoped_ptr<brlapi_handle_t, base::FreeDeleter> handle_;
+ MessageLoopForIO::FileDescriptorWatcher fd_controller_;
+ BrailleControllerImpl* braille_controller_;
+ };
+ BrailleControllerImpl();
dmazzoni 2013/09/06 16:49:53 Nit: blank line before
+ virtual ~BrailleControllerImpl();
+ void TryLoadLibBrlApi();
+ void StartConnections();
+ void StopConnections();
+ void OnSocketDirChanged(const base::FilePath& path, bool error);
+ void ReconnectAll();
+ void UpdateConnections();
+ void TryConnection();
+ void DispatchKeys();
+ scoped_ptr<KeyEvent> MapKeyCode(brlapi_keyCode_t code);
dmazzoni 2013/09/06 16:49:53 Nit: blank line between functions and data members
+ void DispatchKeyEvent(scoped_ptr<KeyEvent> event);
dmazzoni 2013/09/06 16:49:53 Nit: put all functions, then all data members rath
+ brlapi_error_t* BrlApiError();
+ std::string BrlApiStrError();
+ LibBrlApiLoader libbrlapi_loader_;
+ // Manipulated on the IO thread.
+ Connection 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();
+}
+
+BrailleControllerImpl::BrailleControllerImpl()
+ : connection_(this), watching_dir_(false) {
+ TryLoadLibBrlApi();
+}
+
+BrailleControllerImpl::~BrailleControllerImpl() {
+}
+
+void BrailleControllerImpl::TryLoadLibBrlApi() {
+ 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(ERROR) << "Couldn't load libbrlapi: " << strerror(errno);
+}
+
+// static
+BrailleControllerImpl* BrailleControllerImpl::GetInstance() {
+ return Singleton<BrailleControllerImpl,
+ LeakySingletonTraits<BrailleControllerImpl> >::get();
+}
+
+scoped_ptr<base::DictionaryValue> BrailleControllerImpl::GetDisplayState() {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
+ DisplayState displayState;
+ if (connection_.Connected()) {
+ size_t size;
+ if (!connection_.GetDisplaySize(&size)) {
+ LOG(ERROR) << "Couldn't get braille display size " << BrlApiStrError();
+ 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 (!libbrlapi_loader_.loaded())
+ return;
+ if (connection_.Connected()) {
+ brlapi_handle_t* handle = connection_.GetHandle();
+ size_t size;
+ if (!connection_.GetDisplaySize(&size)) {
+ LOG(ERROR) << "Couldn't get display size " << BrlApiStrError();
+ 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 (libbrlapi_loader_.brlapi__writeDots(handle, &sizedCells[0]) < 0) {
+ LOG(ERROR) << "Couldn't write to brlapi: " << BrlApiStrError();
+ connection_.Disconnect();
+ }
+ }
+}
+
+void BrailleControllerImpl::AddObserver(BrailleObserver* 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(BrailleObserver* observer) {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+ observers_.RemoveObserver(observer);
+}
+
+void BrailleControllerImpl::StartConnections() {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
+ base::FilePath brlapi_dir(BRLAPI_SOCKETPATH);
+ LOG(INFO) << "Watching brlapi directory: " << 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(ERROR) << "Error watching brlapi directory: " << path.value();
+ return;
+ }
+ LOG(INFO) << "BrlAPI directory changed";
+ BrowserThread::PostDelayedTask(BrowserThread::IO, FROM_HERE,
+ base::Bind(
+ &BrailleControllerImpl::UpdateConnections,
+ base::Unretained(this)),
+ TimeDelta::FromSeconds(1));
+
+}
+
+void BrailleControllerImpl::UpdateConnections() {
+ if (!connection_.Connected())
+ TryConnection();
+}
+
+void BrailleControllerImpl::TryConnection() {
+ 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;
+ if (libbrlapi_loader_.brlapi__enterTtyModeWithPath(
+ connection_.GetHandle(), path, pathElements, NULL) < 0) {
+ LOG(ERROR) << "brlapi: couldn't enter tty mode: " << 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) {
+ brlapi_expandedKeyCode_t expanded;
+ if (libbrlapi_loader_.brlapi_expandKeyCode(code, &expanded) != 0) {
+ LOG(ERROR) << "Couldn't expand key 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() {
+ brlapi_handle_t* handle = connection_.GetHandle();
+ DCHECK(handle != NULL);
+ brlapi_keyCode_t code;
+ while (true) {
+ int result = libbrlapi_loader_.brlapi__readKey(handle, 0 /*wait*/, &code);
+ if (result < 0) { // Error.
+ brlapi_error_t* err = BrlApiError();
+ if (err->brlerrno == BRLAPI_ERROR_LIBCERR && err->libcerrno == EINTR)
+ continue;
+ // Disconnect on other errors.
+ LOG(ERROR) << "BrlAPI error: " << 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));
+}
+
+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_display_private
+} // namespace api
+} // namespace extensions

Powered by Google App Engine
This is Rietveld 408576698