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
12 namespace extensions {
13 using base::MessageLoopForIO;
14 namespace api {
15 namespace braille_display_private {
16
17 namespace {
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.
23 static const int kDefaultTtyLinux = 7;
24 #if defined(OS_CHROMEOS)
25 // The GUI is always running on vt1 in Chrome OS.
26 static const int kDefaultTtyChromeOS = 1;
27 #endif
28 } // namespace
29
30 class BrlapiConnectionImpl : public BrlapiConnection,
31 MessageLoopForIO::Watcher {
32 public:
BrlapiConnectionImpl(LibBrlapiLoader * loader)33 explicit BrlapiConnectionImpl(LibBrlapiLoader* loader) :
34 libbrlapi_loader_(loader) {}
35
~BrlapiConnectionImpl()36 virtual ~BrlapiConnectionImpl() {
37 Disconnect();
38 }
39
40 virtual ConnectResult Connect(const OnDataReadyCallback& on_data_ready)
41 OVERRIDE;
42 virtual void Disconnect() OVERRIDE;
Connected()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
OnFileCanReadWithoutBlocking(int fd)51 virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE {
52 on_data_ready_.Run();
53 }
54
OnFileCanWriteWithoutBlocking(int fd)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
BrlapiConnection()69 BrlapiConnection::BrlapiConnection() {
70 }
71
~BrlapiConnection()72 BrlapiConnection::~BrlapiConnection() {
73 }
74
Create(LibBrlapiLoader * loader)75 scoped_ptr<BrlapiConnection> BrlapiConnection::Create(
76 LibBrlapiLoader* loader) {
77 DCHECK(loader->loaded());
78 return scoped_ptr<BrlapiConnection>(new BrlapiConnectionImpl(loader));
79 }
80
Connect(const OnDataReadyCallback & on_data_ready)81 BrlapiConnection::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
Disconnect()149 void 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
BrlapiError()159 brlapi_error_t* BrlapiConnectionImpl::BrlapiError() {
160 return libbrlapi_loader_->brlapi_error_location();
161 }
162
BrlapiStrError()163 std::string BrlapiConnectionImpl::BrlapiStrError() {
164 return libbrlapi_loader_->brlapi_strerror(BrlapiError());
165 }
166
GetDisplaySize(size_t * size)167 bool 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
WriteDots(const unsigned char * cells)181 bool 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
ReadKey(brlapi_keyCode_t * key_code)191 int 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
CheckConnected()198 bool BrlapiConnectionImpl::CheckConnected() {
199 if (!handle_) {
200 BrlapiError()->brlerrno = BRLAPI_ERROR_ILLEGAL_INSTRUCTION;
201 return false;
202 }
203 return true;
204 }
205
ConnectResultForError()206 BrlapiConnection::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