1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/extensions/api/braille_display_private/brlapi_connection.h"
6
7#include <errno.h>
8
9#include "base/message_loop/message_loop.h"
10#include "base/sys_info.h"
11
12namespace extensions {
13using base::MessageLoopForIO;
14namespace api {
15namespace braille_display_private {
16
17namespace {
18// Default virtual terminal.  This can be overriden by setting the
19// WINDOWPATH environment variable.  This is only used when not running
20// under Crhome OS (that is in aura for a Linux desktop).
21// TODO(plundblad): Find a way to detect the controlling terminal of the
22// X server.
23static const int kDefaultTtyLinux = 7;
24#if defined(OS_CHROMEOS)
25// The GUI is always running on vt1 in Chrome OS.
26static const int kDefaultTtyChromeOS = 1;
27#endif
28}  // namespace
29
30class BrlapiConnectionImpl : public BrlapiConnection,
31                             MessageLoopForIO::Watcher {
32 public:
33  explicit BrlapiConnectionImpl(LibBrlapiLoader* loader) :
34      libbrlapi_loader_(loader) {}
35
36  virtual ~BrlapiConnectionImpl() {
37    Disconnect();
38  }
39
40  virtual ConnectResult Connect(const OnDataReadyCallback& on_data_ready)
41      OVERRIDE;
42  virtual void Disconnect() OVERRIDE;
43  virtual bool Connected() OVERRIDE { return handle_; }
44  virtual brlapi_error_t* BrlapiError() OVERRIDE;
45  virtual std::string BrlapiStrError() OVERRIDE;
46  virtual bool GetDisplaySize(size_t* size) OVERRIDE;
47  virtual bool WriteDots(const unsigned char* cells) OVERRIDE;
48  virtual int ReadKey(brlapi_keyCode_t* keyCode) OVERRIDE;
49
50  // MessageLoopForIO::Watcher
51  virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE {
52    on_data_ready_.Run();
53  }
54
55  virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE {}
56
57 private:
58  bool CheckConnected();
59  ConnectResult ConnectResultForError();
60
61  LibBrlapiLoader* libbrlapi_loader_;
62  scoped_ptr<brlapi_handle_t, base::FreeDeleter> handle_;
63  MessageLoopForIO::FileDescriptorWatcher fd_controller_;
64  OnDataReadyCallback on_data_ready_;
65
66  DISALLOW_COPY_AND_ASSIGN(BrlapiConnectionImpl);
67};
68
69BrlapiConnection::BrlapiConnection() {
70}
71
72BrlapiConnection::~BrlapiConnection() {
73}
74
75scoped_ptr<BrlapiConnection> BrlapiConnection::Create(
76    LibBrlapiLoader* loader) {
77  DCHECK(loader->loaded());
78  return scoped_ptr<BrlapiConnection>(new BrlapiConnectionImpl(loader));
79}
80
81BrlapiConnection::ConnectResult BrlapiConnectionImpl::Connect(
82    const OnDataReadyCallback& on_data_ready) {
83  DCHECK(!handle_);
84  handle_.reset((brlapi_handle_t*) malloc(
85      libbrlapi_loader_->brlapi_getHandleSize()));
86  int fd = libbrlapi_loader_->brlapi__openConnection(handle_.get(), NULL, NULL);
87  if (fd < 0) {
88    handle_.reset();
89    VLOG(1) << "Error connecting to brlapi: " << BrlapiStrError();
90    return ConnectResultForError();
91  }
92  int path[2] = {0, 0};
93  int pathElements = 0;
94#if defined(OS_CHROMEOS)
95  if (base::SysInfo::IsRunningOnChromeOS())
96    path[pathElements++] = kDefaultTtyChromeOS;
97#endif
98  if (pathElements == 0 && getenv("WINDOWPATH") == NULL)
99    path[pathElements++] = kDefaultTtyLinux;
100  if (libbrlapi_loader_->brlapi__enterTtyModeWithPath(
101          handle_.get(), path, pathElements, NULL) < 0) {
102    LOG(ERROR) << "brlapi: couldn't enter tty mode: " << BrlapiStrError();
103    Disconnect();
104    return CONNECT_ERROR_RETRY;
105  }
106
107  size_t size;
108  if (!GetDisplaySize(&size)) {
109    // Error already logged.
110    Disconnect();
111    return CONNECT_ERROR_RETRY;
112  }
113
114  // A display size of 0 means no display connected.  We can't reliably
115  // detect when a display gets connected, so fail and let the caller
116  // retry connecting.
117  if (size == 0) {
118    VLOG(1) << "No braille display connected";
119    Disconnect();
120    return CONNECT_ERROR_RETRY;
121  }
122
123  const brlapi_keyCode_t extraKeys[] = {
124      BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_OFFLINE,
125      // brltty 5.1 converts dot input to Unicode characters unless we
126      // explicitly accept this command.
127      BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_PASSDOTS,
128  };
129  if (libbrlapi_loader_->brlapi__acceptKeys(
130          handle_.get(), brlapi_rangeType_command, extraKeys,
131          arraysize(extraKeys)) < 0) {
132    LOG(ERROR) << "Couldn't acceptKeys: " << BrlapiStrError();
133    Disconnect();
134    return CONNECT_ERROR_RETRY;
135  }
136
137  if (!MessageLoopForIO::current()->WatchFileDescriptor(
138          fd, true, MessageLoopForIO::WATCH_READ, &fd_controller_, this)) {
139    LOG(ERROR) << "Couldn't watch file descriptor " << fd;
140    Disconnect();
141    return CONNECT_ERROR_RETRY;
142  }
143
144  on_data_ready_ = on_data_ready;
145
146  return CONNECT_SUCCESS;
147}
148
149void BrlapiConnectionImpl::Disconnect() {
150  if (!handle_) {
151    return;
152  }
153  fd_controller_.StopWatchingFileDescriptor();
154  libbrlapi_loader_->brlapi__closeConnection(
155      handle_.get());
156  handle_.reset();
157}
158
159brlapi_error_t* BrlapiConnectionImpl::BrlapiError() {
160  return libbrlapi_loader_->brlapi_error_location();
161}
162
163std::string BrlapiConnectionImpl::BrlapiStrError() {
164  return libbrlapi_loader_->brlapi_strerror(BrlapiError());
165}
166
167bool BrlapiConnectionImpl::GetDisplaySize(size_t* size) {
168  if (!CheckConnected()) {
169    return false;
170  }
171  unsigned int columns, rows;
172  if (libbrlapi_loader_->brlapi__getDisplaySize(
173          handle_.get(), &columns, &rows) < 0) {
174    LOG(ERROR) << "Couldn't get braille display size " << BrlapiStrError();
175    return false;
176  }
177  *size = columns * rows;
178  return true;
179}
180
181bool BrlapiConnectionImpl::WriteDots(const unsigned char* cells) {
182  if (!CheckConnected())
183    return false;
184  if (libbrlapi_loader_->brlapi__writeDots(handle_.get(), cells) < 0) {
185    VLOG(1) << "Couldn't write to brlapi: " << BrlapiStrError();
186    return false;
187  }
188  return true;
189}
190
191int BrlapiConnectionImpl::ReadKey(brlapi_keyCode_t* key_code) {
192  if (!CheckConnected())
193    return -1;
194  return libbrlapi_loader_->brlapi__readKey(
195      handle_.get(), 0 /*wait*/, key_code);
196}
197
198bool BrlapiConnectionImpl::CheckConnected() {
199  if (!handle_) {
200    BrlapiError()->brlerrno = BRLAPI_ERROR_ILLEGAL_INSTRUCTION;
201    return false;
202  }
203  return true;
204}
205
206BrlapiConnection::ConnectResult BrlapiConnectionImpl::ConnectResultForError() {
207  const brlapi_error_t* error = BrlapiError();
208  // For the majority of users, the socket file will never exist because
209  // the daemon is never run.  Avoid retrying in this case, relying on
210  // the socket directory to change and trigger further tries if the
211  // daemon comes up later on.
212  if (error->brlerrno == BRLAPI_ERROR_LIBCERR
213      && error->libcerrno == ENOENT) {
214    return CONNECT_ERROR_NO_RETRY;
215  }
216  return CONNECT_ERROR_RETRY;
217}
218
219}  // namespace braille_display_private
220}  // namespace api
221}  // namespace extensions
222