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.
5 #include "chrome/browser/extensions/api/braille_display_private/braille_controller_brlapi.h"
12 #include "base/bind.h"
13 #include "base/bind_helpers.h"
14 #include "base/time/time.h"
15 #include "chrome/browser/extensions/api/braille_display_private/brlapi_connection.h"
16 #include "content/public/browser/browser_thread.h"
18 namespace extensions {
19 using content::BrowserThread;
21 using base::TimeDelta;
23 namespace braille_display_private {
26 // Delay between detecting a directory update and trying to connect
28 const int64 kConnectionDelayMs = 500;
29 // How long to periodically retry connecting after a brltty restart.
30 // Some displays are slow to connect.
31 const int64 kConnectRetryTimeout = 20000;
32 // Bitmask for all braille dots in a key command argument, which coincides
33 // with the representation in the braille_dots member of the KeyEvent
35 const int kAllDots = BRLAPI_DOT1 | BRLAPI_DOT2 | BRLAPI_DOT3 | BRLAPI_DOT4 |
36 BRLAPI_DOT5 | BRLAPI_DOT6 | BRLAPI_DOT7 | BRLAPI_DOT8;
39 BrailleController::BrailleController() {
42 BrailleController::~BrailleController() {
46 BrailleController* BrailleController::GetInstance() {
47 return BrailleControllerImpl::GetInstance();
51 BrailleControllerImpl* BrailleControllerImpl::GetInstance() {
52 return Singleton<BrailleControllerImpl,
53 LeakySingletonTraits<BrailleControllerImpl> >::get();
56 BrailleControllerImpl::BrailleControllerImpl()
57 : started_connecting_(false),
58 connect_scheduled_(false) {
59 create_brlapi_connection_function_ = base::Bind(
60 &BrailleControllerImpl::CreateBrlapiConnection,
61 base::Unretained(this));
64 BrailleControllerImpl::~BrailleControllerImpl() {
67 void BrailleControllerImpl::TryLoadLibBrlApi() {
68 DCHECK_CURRENTLY_ON(BrowserThread::IO);
69 if (libbrlapi_loader_.loaded())
71 // These versions of libbrlapi work the same for the functions we
72 // are using. (0.6.0 adds brlapi_writeWText).
73 static const char* kSupportedVersions[] = {
77 for (size_t i = 0; i < arraysize(kSupportedVersions); ++i) {
78 if (libbrlapi_loader_.Load(kSupportedVersions[i]))
81 LOG(WARNING) << "Couldn't load libbrlapi: " << strerror(errno);
84 scoped_ptr<DisplayState> BrailleControllerImpl::GetDisplayState() {
85 DCHECK_CURRENTLY_ON(BrowserThread::IO);
87 scoped_ptr<DisplayState> display_state(new DisplayState);
88 if (connection_.get() && connection_->Connected()) {
90 if (!connection_->GetDisplaySize(&size)) {
92 } else if (size > 0) { // size == 0 means no display present.
93 display_state->available = true;
94 display_state->text_cell_count.reset(new int(size));
97 return display_state.Pass();
100 void BrailleControllerImpl::WriteDots(const std::string& cells) {
101 DCHECK_CURRENTLY_ON(BrowserThread::IO);
102 if (connection_ && connection_->Connected()) {
104 if (!connection_->GetDisplaySize(&size)) {
107 std::vector<unsigned char> sizedCells(size);
108 std::memcpy(&sizedCells[0], cells.data(), std::min(cells.size(), size));
109 if (size > cells.size())
110 std::fill(sizedCells.begin() + cells.size(), sizedCells.end(), 0);
111 if (!connection_->WriteDots(&sizedCells[0]))
116 void BrailleControllerImpl::AddObserver(BrailleObserver* observer) {
117 DCHECK_CURRENTLY_ON(BrowserThread::UI);
118 if (!BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
120 &BrailleControllerImpl::StartConnecting,
121 base::Unretained(this)))) {
124 observers_.AddObserver(observer);
127 void BrailleControllerImpl::RemoveObserver(BrailleObserver* observer) {
128 DCHECK_CURRENTLY_ON(BrowserThread::UI);
129 observers_.RemoveObserver(observer);
132 void BrailleControllerImpl::SetCreateBrlapiConnectionForTesting(
133 const CreateBrlapiConnectionFunction& function) {
134 if (function.is_null()) {
135 create_brlapi_connection_function_ = base::Bind(
136 &BrailleControllerImpl::CreateBrlapiConnection,
137 base::Unretained(this));
139 create_brlapi_connection_function_ = function;
143 void BrailleControllerImpl::PokeSocketDirForTesting() {
144 OnSocketDirChangedOnIOThread();
147 void BrailleControllerImpl::StartConnecting() {
148 DCHECK_CURRENTLY_ON(BrowserThread::IO);
149 if (started_connecting_)
151 started_connecting_ = true;
153 if (!libbrlapi_loader_.loaded()) {
156 // Only try to connect after we've started to watch the
157 // socket directory. This is necessary to avoid a race condition
158 // and because we don't retry to connect after errors that will
159 // persist until there's a change to the socket directory (i.e.
161 BrowserThread::PostTaskAndReply(
162 BrowserThread::FILE, FROM_HERE,
164 &BrailleControllerImpl::StartWatchingSocketDirOnFileThread,
165 base::Unretained(this)),
167 &BrailleControllerImpl::TryToConnect,
168 base::Unretained(this)));
169 ResetRetryConnectHorizon();
172 void BrailleControllerImpl::StartWatchingSocketDirOnFileThread() {
173 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
174 base::FilePath brlapi_dir(BRLAPI_SOCKETPATH);
175 if (!file_path_watcher_.Watch(
176 brlapi_dir, false, base::Bind(
177 &BrailleControllerImpl::OnSocketDirChangedOnFileThread,
178 base::Unretained(this)))) {
179 LOG(WARNING) << "Couldn't watch brlapi directory " << BRLAPI_SOCKETPATH;
183 void BrailleControllerImpl::OnSocketDirChangedOnFileThread(
184 const base::FilePath& path, bool error) {
185 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
187 LOG(ERROR) << "Error watching brlapi directory: " << path.value();
190 BrowserThread::PostTask(
191 BrowserThread::IO, FROM_HERE, base::Bind(
192 &BrailleControllerImpl::OnSocketDirChangedOnIOThread,
193 base::Unretained(this)));
196 void BrailleControllerImpl::OnSocketDirChangedOnIOThread() {
197 DCHECK_CURRENTLY_ON(BrowserThread::IO);
198 VLOG(1) << "BrlAPI directory changed";
199 // Every directory change resets the max retry time to the appropriate delay
201 ResetRetryConnectHorizon();
202 // Try after an initial delay to give the driver a chance to connect.
203 ScheduleTryToConnect();
206 void BrailleControllerImpl::TryToConnect() {
207 DCHECK_CURRENTLY_ON(BrowserThread::IO);
208 DCHECK(libbrlapi_loader_.loaded());
209 connect_scheduled_ = false;
210 if (!connection_.get())
211 connection_ = create_brlapi_connection_function_.Run();
212 if (connection_.get() && !connection_->Connected()) {
213 VLOG(1) << "Trying to connect to brlapi";
214 BrlapiConnection::ConnectResult result = connection_->Connect(base::Bind(
215 &BrailleControllerImpl::DispatchKeys,
216 base::Unretained(this)));
218 case BrlapiConnection::CONNECT_SUCCESS:
219 DispatchOnDisplayStateChanged(GetDisplayState());
221 case BrlapiConnection::CONNECT_ERROR_NO_RETRY:
223 case BrlapiConnection::CONNECT_ERROR_RETRY:
224 ScheduleTryToConnect();
232 void BrailleControllerImpl::ResetRetryConnectHorizon() {
233 DCHECK_CURRENTLY_ON(BrowserThread::IO);
234 retry_connect_horizon_ = Time::Now() + TimeDelta::FromMilliseconds(
235 kConnectRetryTimeout);
238 void BrailleControllerImpl::ScheduleTryToConnect() {
239 DCHECK_CURRENTLY_ON(BrowserThread::IO);
240 TimeDelta delay(TimeDelta::FromMilliseconds(kConnectionDelayMs));
241 // Don't reschedule if there's already a connect scheduled or
242 // the next attempt would fall outside of the retry limit.
243 if (connect_scheduled_)
245 if (Time::Now() + delay > retry_connect_horizon_) {
246 VLOG(1) << "Stopping to retry to connect to brlapi";
249 VLOG(1) << "Scheduling connection retry to brlapi";
250 connect_scheduled_ = true;
251 BrowserThread::PostDelayedTask(BrowserThread::IO, FROM_HERE,
253 &BrailleControllerImpl::TryToConnect,
254 base::Unretained(this)),
258 void BrailleControllerImpl::Disconnect() {
259 DCHECK_CURRENTLY_ON(BrowserThread::IO);
260 if (!connection_ || !connection_->Connected())
262 connection_->Disconnect();
263 DispatchOnDisplayStateChanged(scoped_ptr<DisplayState>(new DisplayState()));
266 scoped_ptr<BrlapiConnection> BrailleControllerImpl::CreateBrlapiConnection() {
267 DCHECK(libbrlapi_loader_.loaded());
268 return BrlapiConnection::Create(&libbrlapi_loader_);
271 scoped_ptr<KeyEvent> BrailleControllerImpl::MapKeyCode(brlapi_keyCode_t code) {
272 brlapi_expandedKeyCode_t expanded;
273 if (libbrlapi_loader_.brlapi_expandKeyCode(code, &expanded) != 0) {
274 LOG(ERROR) << "Couldn't expand key code " << code;
275 return scoped_ptr<KeyEvent>();
277 scoped_ptr<KeyEvent> result(new KeyEvent);
278 result->command = KEY_COMMAND_NONE;
279 switch (expanded.type) {
280 case BRLAPI_KEY_TYPE_CMD:
281 switch (expanded.command) {
282 case BRLAPI_KEY_CMD_LNUP:
283 result->command = KEY_COMMAND_LINE_UP;
285 case BRLAPI_KEY_CMD_LNDN:
286 result->command = KEY_COMMAND_LINE_DOWN;
288 case BRLAPI_KEY_CMD_FWINLT:
289 result->command = KEY_COMMAND_PAN_LEFT;
291 case BRLAPI_KEY_CMD_FWINRT:
292 result->command = KEY_COMMAND_PAN_RIGHT;
294 case BRLAPI_KEY_CMD_TOP:
295 result->command = KEY_COMMAND_TOP;
297 case BRLAPI_KEY_CMD_BOT:
298 result->command = KEY_COMMAND_BOTTOM;
300 case BRLAPI_KEY_CMD_ROUTE:
301 result->command = KEY_COMMAND_ROUTING;
302 result->display_position.reset(new int(expanded.argument));
304 case BRLAPI_KEY_CMD_PASSDOTS:
305 result->command = KEY_COMMAND_DOTS;
306 result->braille_dots.reset(new int(expanded.argument & kAllDots));
307 if ((expanded.argument & BRLAPI_DOTC) != 0)
308 result->space_key.reset(new bool(true));
313 if (result->command == KEY_COMMAND_NONE)
315 return result.Pass();
318 void BrailleControllerImpl::DispatchKeys() {
319 DCHECK(connection_.get());
320 brlapi_keyCode_t code;
322 int result = connection_->ReadKey(&code);
323 if (result < 0) { // Error.
324 brlapi_error_t* err = connection_->BrlapiError();
325 if (err->brlerrno == BRLAPI_ERROR_LIBCERR && err->libcerrno == EINTR)
327 // Disconnect on other errors.
328 VLOG(1) << "BrlAPI error: " << connection_->BrlapiStrError();
331 } else if (result == 0) { // No more data.
334 scoped_ptr<KeyEvent> event = MapKeyCode(code);
336 DispatchKeyEvent(event.Pass());
340 void BrailleControllerImpl::DispatchKeyEvent(scoped_ptr<KeyEvent> event) {
341 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
342 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
344 &BrailleControllerImpl::DispatchKeyEvent,
345 base::Unretained(this),
346 base::Passed(&event)));
349 VLOG(1) << "Dispatching key event: " << *event->ToValue();
350 FOR_EACH_OBSERVER(BrailleObserver, observers_, OnKeyEvent(*event));
353 void BrailleControllerImpl::DispatchOnDisplayStateChanged(
354 scoped_ptr<DisplayState> new_state) {
355 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
356 if (!BrowserThread::PostTask(
357 BrowserThread::UI, FROM_HERE,
358 base::Bind(&BrailleControllerImpl::DispatchOnDisplayStateChanged,
359 base::Unretained(this),
360 base::Passed(&new_state)))) {
365 FOR_EACH_OBSERVER(BrailleObserver, observers_,
366 OnDisplayStateChanged(*new_state));
369 } // namespace braille_display_private
371 } // namespace extensions