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;
34 BrailleController::BrailleController() {
37 BrailleController::~BrailleController() {
41 BrailleController* BrailleController::GetInstance() {
42 return BrailleControllerImpl::GetInstance();
46 BrailleControllerImpl* BrailleControllerImpl::GetInstance() {
47 return Singleton<BrailleControllerImpl,
48 LeakySingletonTraits<BrailleControllerImpl> >::get();
51 BrailleControllerImpl::BrailleControllerImpl()
52 : started_connecting_(false),
53 connect_scheduled_(false) {
54 create_brlapi_connection_function_ = base::Bind(
55 &BrailleControllerImpl::CreateBrlapiConnection,
56 base::Unretained(this));
59 BrailleControllerImpl::~BrailleControllerImpl() {
62 void BrailleControllerImpl::TryLoadLibBrlApi() {
63 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
64 if (libbrlapi_loader_.loaded())
66 // These versions of libbrlapi work the same for the functions we
67 // are using. (0.6.0 adds brlapi_writeWText).
68 static const char* kSupportedVersions[] = {
72 for (size_t i = 0; i < arraysize(kSupportedVersions); ++i) {
73 if (libbrlapi_loader_.Load(kSupportedVersions[i]))
76 LOG(WARNING) << "Couldn't load libbrlapi: " << strerror(errno);
79 scoped_ptr<DisplayState> BrailleControllerImpl::GetDisplayState() {
80 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
82 scoped_ptr<DisplayState> display_state(new DisplayState);
83 if (connection_.get() && connection_->Connected()) {
85 if (!connection_->GetDisplaySize(&size)) {
87 } else if (size > 0) { // size == 0 means no display present.
88 display_state->available = true;
89 display_state->text_cell_count.reset(new int(size));
92 return display_state.Pass();
95 void BrailleControllerImpl::WriteDots(const std::string& cells) {
96 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
97 if (connection_ && connection_->Connected()) {
99 if (!connection_->GetDisplaySize(&size)) {
102 std::vector<unsigned char> sizedCells(size);
103 std::memcpy(&sizedCells[0], cells.data(), std::min(cells.size(), size));
104 if (size > cells.size())
105 std::fill(sizedCells.begin() + cells.size(), sizedCells.end(), 0);
106 if (!connection_->WriteDots(&sizedCells[0]))
111 void BrailleControllerImpl::AddObserver(BrailleObserver* observer) {
112 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
113 if (!BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
115 &BrailleControllerImpl::StartConnecting,
116 base::Unretained(this)))) {
119 observers_.AddObserver(observer);
122 void BrailleControllerImpl::RemoveObserver(BrailleObserver* observer) {
123 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
124 observers_.RemoveObserver(observer);
127 void BrailleControllerImpl::SetCreateBrlapiConnectionForTesting(
128 const CreateBrlapiConnectionFunction& function) {
129 if (function.is_null()) {
130 create_brlapi_connection_function_ = base::Bind(
131 &BrailleControllerImpl::CreateBrlapiConnection,
132 base::Unretained(this));
134 create_brlapi_connection_function_ = function;
138 void BrailleControllerImpl::PokeSocketDirForTesting() {
139 OnSocketDirChangedOnIOThread();
142 void BrailleControllerImpl::StartConnecting() {
143 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
144 if (started_connecting_)
146 started_connecting_ = true;
148 if (!libbrlapi_loader_.loaded()) {
151 // Only try to connect after we've started to watch the
152 // socket directory. This is necessary to avoid a race condition
153 // and because we don't retry to connect after errors that will
154 // persist until there's a change to the socket directory (i.e.
156 BrowserThread::PostTaskAndReply(
157 BrowserThread::FILE, FROM_HERE,
159 &BrailleControllerImpl::StartWatchingSocketDirOnFileThread,
160 base::Unretained(this)),
162 &BrailleControllerImpl::TryToConnect,
163 base::Unretained(this)));
164 ResetRetryConnectHorizon();
167 void BrailleControllerImpl::StartWatchingSocketDirOnFileThread() {
168 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
169 base::FilePath brlapi_dir(BRLAPI_SOCKETPATH);
170 if (!file_path_watcher_.Watch(
171 brlapi_dir, false, base::Bind(
172 &BrailleControllerImpl::OnSocketDirChangedOnFileThread,
173 base::Unretained(this)))) {
174 LOG(WARNING) << "Couldn't watch brlapi directory " << BRLAPI_SOCKETPATH;
178 void BrailleControllerImpl::OnSocketDirChangedOnFileThread(
179 const base::FilePath& path, bool error) {
180 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
182 LOG(ERROR) << "Error watching brlapi directory: " << path.value();
185 BrowserThread::PostTask(
186 BrowserThread::IO, FROM_HERE, base::Bind(
187 &BrailleControllerImpl::OnSocketDirChangedOnIOThread,
188 base::Unretained(this)));
191 void BrailleControllerImpl::OnSocketDirChangedOnIOThread() {
192 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
193 VLOG(1) << "BrlAPI directory changed";
194 // Every directory change resets the max retry time to the appropriate delay
196 ResetRetryConnectHorizon();
197 // Try after an initial delay to give the driver a chance to connect.
198 ScheduleTryToConnect();
201 void BrailleControllerImpl::TryToConnect() {
202 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
203 DCHECK(libbrlapi_loader_.loaded());
204 connect_scheduled_ = false;
205 if (!connection_.get())
206 connection_ = create_brlapi_connection_function_.Run();
207 if (connection_.get() && !connection_->Connected()) {
208 VLOG(1) << "Trying to connect to brlapi";
209 BrlapiConnection::ConnectResult result = connection_->Connect(base::Bind(
210 &BrailleControllerImpl::DispatchKeys,
211 base::Unretained(this)));
213 case BrlapiConnection::CONNECT_SUCCESS:
214 DispatchOnDisplayStateChanged(GetDisplayState());
216 case BrlapiConnection::CONNECT_ERROR_NO_RETRY:
218 case BrlapiConnection::CONNECT_ERROR_RETRY:
219 ScheduleTryToConnect();
227 void BrailleControllerImpl::ResetRetryConnectHorizon() {
228 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
229 retry_connect_horizon_ = Time::Now() + TimeDelta::FromMilliseconds(
230 kConnectRetryTimeout);
233 void BrailleControllerImpl::ScheduleTryToConnect() {
234 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
235 TimeDelta delay(TimeDelta::FromMilliseconds(kConnectionDelayMs));
236 // Don't reschedule if there's already a connect scheduled or
237 // the next attempt would fall outside of the retry limit.
238 if (connect_scheduled_)
240 if (Time::Now() + delay > retry_connect_horizon_) {
241 VLOG(1) << "Stopping to retry to connect to brlapi";
244 VLOG(1) << "Scheduling connection retry to brlapi";
245 connect_scheduled_ = true;
246 BrowserThread::PostDelayedTask(BrowserThread::IO, FROM_HERE,
248 &BrailleControllerImpl::TryToConnect,
249 base::Unretained(this)),
253 void BrailleControllerImpl::Disconnect() {
254 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
255 if (!connection_ || !connection_->Connected())
257 connection_->Disconnect();
258 DispatchOnDisplayStateChanged(scoped_ptr<DisplayState>(new DisplayState()));
261 scoped_ptr<BrlapiConnection> BrailleControllerImpl::CreateBrlapiConnection() {
262 DCHECK(libbrlapi_loader_.loaded());
263 return BrlapiConnection::Create(&libbrlapi_loader_);
266 scoped_ptr<KeyEvent> BrailleControllerImpl::MapKeyCode(brlapi_keyCode_t code) {
267 brlapi_expandedKeyCode_t expanded;
268 if (libbrlapi_loader_.brlapi_expandKeyCode(code, &expanded) != 0) {
269 LOG(ERROR) << "Couldn't expand key code " << code;
270 return scoped_ptr<KeyEvent>();
272 scoped_ptr<KeyEvent> result(new KeyEvent);
273 result->command = KEY_COMMAND_NONE;
274 switch (expanded.type) {
275 case BRLAPI_KEY_TYPE_CMD:
276 switch (expanded.command) {
277 case BRLAPI_KEY_CMD_LNUP:
278 result->command = KEY_COMMAND_LINE_UP;
280 case BRLAPI_KEY_CMD_LNDN:
281 result->command = KEY_COMMAND_LINE_DOWN;
283 case BRLAPI_KEY_CMD_FWINLT:
284 result->command = KEY_COMMAND_PAN_LEFT;
286 case BRLAPI_KEY_CMD_FWINRT:
287 result->command = KEY_COMMAND_PAN_RIGHT;
289 case BRLAPI_KEY_CMD_TOP:
290 result->command = KEY_COMMAND_TOP;
292 case BRLAPI_KEY_CMD_BOT:
293 result->command = KEY_COMMAND_BOTTOM;
295 case BRLAPI_KEY_CMD_ROUTE:
296 result->command = KEY_COMMAND_ROUTING;
297 result->display_position.reset(new int(expanded.argument));
299 case BRLAPI_KEY_CMD_PASSDOTS:
300 result->command = KEY_COMMAND_DOTS;
301 // The 8 low-order bits in the argument contains the dots.
302 result->braille_dots.reset(new int(expanded.argument & 0xf));
303 if ((expanded.argument & BRLAPI_DOTC) != 0)
304 result->space_key.reset(new bool(true));
309 if (result->command == KEY_COMMAND_NONE)
311 return result.Pass();
314 void BrailleControllerImpl::DispatchKeys() {
315 DCHECK(connection_.get());
316 brlapi_keyCode_t code;
318 int result = connection_->ReadKey(&code);
319 if (result < 0) { // Error.
320 brlapi_error_t* err = connection_->BrlapiError();
321 if (err->brlerrno == BRLAPI_ERROR_LIBCERR && err->libcerrno == EINTR)
323 // Disconnect on other errors.
324 VLOG(1) << "BrlAPI error: " << connection_->BrlapiStrError();
327 } else if (result == 0) { // No more data.
330 scoped_ptr<KeyEvent> event = MapKeyCode(code);
332 DispatchKeyEvent(event.Pass());
336 void BrailleControllerImpl::DispatchKeyEvent(scoped_ptr<KeyEvent> event) {
337 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
338 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
340 &BrailleControllerImpl::DispatchKeyEvent,
341 base::Unretained(this),
342 base::Passed(&event)));
345 FOR_EACH_OBSERVER(BrailleObserver, observers_, OnKeyEvent(*event));
348 void BrailleControllerImpl::DispatchOnDisplayStateChanged(
349 scoped_ptr<DisplayState> new_state) {
350 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
351 if (!BrowserThread::PostTask(
352 BrowserThread::UI, FROM_HERE,
353 base::Bind(&BrailleControllerImpl::DispatchOnDisplayStateChanged,
354 base::Unretained(this),
355 base::Passed(&new_state)))) {
360 FOR_EACH_OBSERVER(BrailleObserver, observers_,
361 OnDisplayStateChanged(*new_state));
364 } // namespace braille_display_private
366 } // namespace extensions