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/braille_controller_brlapi.h" 6 7 #include <algorithm> 8 #include <cerrno> 9 #include <cstring> 10 #include <vector> 11 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 "chrome/browser/extensions/api/braille_display_private/brlapi_keycode_map.h" 17 #include "content/public/browser/browser_thread.h" 18 19 namespace extensions { 20 using content::BrowserThread; 21 using base::Time; 22 using base::TimeDelta; 23 namespace api { 24 namespace braille_display_private { 25 26 namespace { 27 // Delay between detecting a directory update and trying to connect 28 // to the brlapi. 29 const int64 kConnectionDelayMs = 500; 30 // How long to periodically retry connecting after a brltty restart. 31 // Some displays are slow to connect. 32 const int64 kConnectRetryTimeout = 20000; 33 } // namespace 34 35 BrailleController::BrailleController() { 36 } 37 38 BrailleController::~BrailleController() { 39 } 40 41 // static 42 BrailleController* BrailleController::GetInstance() { 43 return BrailleControllerImpl::GetInstance(); 44 } 45 46 // static 47 BrailleControllerImpl* BrailleControllerImpl::GetInstance() { 48 return Singleton<BrailleControllerImpl, 49 LeakySingletonTraits<BrailleControllerImpl> >::get(); 50 } 51 52 BrailleControllerImpl::BrailleControllerImpl() 53 : started_connecting_(false), 54 connect_scheduled_(false) { 55 create_brlapi_connection_function_ = base::Bind( 56 &BrailleControllerImpl::CreateBrlapiConnection, 57 base::Unretained(this)); 58 } 59 60 BrailleControllerImpl::~BrailleControllerImpl() { 61 } 62 63 void BrailleControllerImpl::TryLoadLibBrlApi() { 64 DCHECK_CURRENTLY_ON(BrowserThread::IO); 65 if (libbrlapi_loader_.loaded()) 66 return; 67 // These versions of libbrlapi work the same for the functions we 68 // are using. (0.6.0 adds brlapi_writeWText). 69 static const char* kSupportedVersions[] = { 70 "libbrlapi.so.0.5", 71 "libbrlapi.so.0.6" 72 }; 73 for (size_t i = 0; i < arraysize(kSupportedVersions); ++i) { 74 if (libbrlapi_loader_.Load(kSupportedVersions[i])) 75 return; 76 } 77 LOG(WARNING) << "Couldn't load libbrlapi: " << strerror(errno); 78 } 79 80 scoped_ptr<DisplayState> BrailleControllerImpl::GetDisplayState() { 81 DCHECK_CURRENTLY_ON(BrowserThread::IO); 82 StartConnecting(); 83 scoped_ptr<DisplayState> display_state(new DisplayState); 84 if (connection_.get() && connection_->Connected()) { 85 size_t size; 86 if (!connection_->GetDisplaySize(&size)) { 87 Disconnect(); 88 } else if (size > 0) { // size == 0 means no display present. 89 display_state->available = true; 90 display_state->text_cell_count.reset(new int(size)); 91 } 92 } 93 return display_state.Pass(); 94 } 95 96 void BrailleControllerImpl::WriteDots(const std::string& cells) { 97 DCHECK_CURRENTLY_ON(BrowserThread::IO); 98 if (connection_ && connection_->Connected()) { 99 size_t size; 100 if (!connection_->GetDisplaySize(&size)) { 101 Disconnect(); 102 } 103 std::vector<unsigned char> sizedCells(size); 104 std::memcpy(&sizedCells[0], cells.data(), std::min(cells.size(), size)); 105 if (size > cells.size()) 106 std::fill(sizedCells.begin() + cells.size(), sizedCells.end(), 0); 107 if (!connection_->WriteDots(&sizedCells[0])) 108 Disconnect(); 109 } 110 } 111 112 void BrailleControllerImpl::AddObserver(BrailleObserver* observer) { 113 DCHECK_CURRENTLY_ON(BrowserThread::UI); 114 if (!BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, 115 base::Bind( 116 &BrailleControllerImpl::StartConnecting, 117 base::Unretained(this)))) { 118 NOTREACHED(); 119 } 120 observers_.AddObserver(observer); 121 } 122 123 void BrailleControllerImpl::RemoveObserver(BrailleObserver* observer) { 124 DCHECK_CURRENTLY_ON(BrowserThread::UI); 125 observers_.RemoveObserver(observer); 126 } 127 128 void BrailleControllerImpl::SetCreateBrlapiConnectionForTesting( 129 const CreateBrlapiConnectionFunction& function) { 130 if (function.is_null()) { 131 create_brlapi_connection_function_ = base::Bind( 132 &BrailleControllerImpl::CreateBrlapiConnection, 133 base::Unretained(this)); 134 } else { 135 create_brlapi_connection_function_ = function; 136 } 137 } 138 139 void BrailleControllerImpl::PokeSocketDirForTesting() { 140 OnSocketDirChangedOnIOThread(); 141 } 142 143 void BrailleControllerImpl::StartConnecting() { 144 DCHECK_CURRENTLY_ON(BrowserThread::IO); 145 if (started_connecting_) 146 return; 147 started_connecting_ = true; 148 TryLoadLibBrlApi(); 149 if (!libbrlapi_loader_.loaded()) { 150 return; 151 } 152 // Only try to connect after we've started to watch the 153 // socket directory. This is necessary to avoid a race condition 154 // and because we don't retry to connect after errors that will 155 // persist until there's a change to the socket directory (i.e. 156 // ENOENT). 157 BrowserThread::PostTaskAndReply( 158 BrowserThread::FILE, FROM_HERE, 159 base::Bind( 160 &BrailleControllerImpl::StartWatchingSocketDirOnFileThread, 161 base::Unretained(this)), 162 base::Bind( 163 &BrailleControllerImpl::TryToConnect, 164 base::Unretained(this))); 165 ResetRetryConnectHorizon(); 166 } 167 168 void BrailleControllerImpl::StartWatchingSocketDirOnFileThread() { 169 DCHECK_CURRENTLY_ON(BrowserThread::FILE); 170 base::FilePath brlapi_dir(BRLAPI_SOCKETPATH); 171 if (!file_path_watcher_.Watch( 172 brlapi_dir, false, base::Bind( 173 &BrailleControllerImpl::OnSocketDirChangedOnFileThread, 174 base::Unretained(this)))) { 175 LOG(WARNING) << "Couldn't watch brlapi directory " << BRLAPI_SOCKETPATH; 176 } 177 } 178 179 void BrailleControllerImpl::OnSocketDirChangedOnFileThread( 180 const base::FilePath& path, bool error) { 181 DCHECK_CURRENTLY_ON(BrowserThread::FILE); 182 if (error) { 183 LOG(ERROR) << "Error watching brlapi directory: " << path.value(); 184 return; 185 } 186 BrowserThread::PostTask( 187 BrowserThread::IO, FROM_HERE, base::Bind( 188 &BrailleControllerImpl::OnSocketDirChangedOnIOThread, 189 base::Unretained(this))); 190 } 191 192 void BrailleControllerImpl::OnSocketDirChangedOnIOThread() { 193 DCHECK_CURRENTLY_ON(BrowserThread::IO); 194 VLOG(1) << "BrlAPI directory changed"; 195 // Every directory change resets the max retry time to the appropriate delay 196 // into the future. 197 ResetRetryConnectHorizon(); 198 // Try after an initial delay to give the driver a chance to connect. 199 ScheduleTryToConnect(); 200 } 201 202 void BrailleControllerImpl::TryToConnect() { 203 DCHECK_CURRENTLY_ON(BrowserThread::IO); 204 DCHECK(libbrlapi_loader_.loaded()); 205 connect_scheduled_ = false; 206 if (!connection_.get()) 207 connection_ = create_brlapi_connection_function_.Run(); 208 if (connection_.get() && !connection_->Connected()) { 209 VLOG(1) << "Trying to connect to brlapi"; 210 BrlapiConnection::ConnectResult result = connection_->Connect(base::Bind( 211 &BrailleControllerImpl::DispatchKeys, 212 base::Unretained(this))); 213 switch (result) { 214 case BrlapiConnection::CONNECT_SUCCESS: 215 DispatchOnDisplayStateChanged(GetDisplayState()); 216 break; 217 case BrlapiConnection::CONNECT_ERROR_NO_RETRY: 218 break; 219 case BrlapiConnection::CONNECT_ERROR_RETRY: 220 ScheduleTryToConnect(); 221 break; 222 default: 223 NOTREACHED(); 224 } 225 } 226 } 227 228 void BrailleControllerImpl::ResetRetryConnectHorizon() { 229 DCHECK_CURRENTLY_ON(BrowserThread::IO); 230 retry_connect_horizon_ = Time::Now() + TimeDelta::FromMilliseconds( 231 kConnectRetryTimeout); 232 } 233 234 void BrailleControllerImpl::ScheduleTryToConnect() { 235 DCHECK_CURRENTLY_ON(BrowserThread::IO); 236 TimeDelta delay(TimeDelta::FromMilliseconds(kConnectionDelayMs)); 237 // Don't reschedule if there's already a connect scheduled or 238 // the next attempt would fall outside of the retry limit. 239 if (connect_scheduled_) 240 return; 241 if (Time::Now() + delay > retry_connect_horizon_) { 242 VLOG(1) << "Stopping to retry to connect to brlapi"; 243 return; 244 } 245 VLOG(1) << "Scheduling connection retry to brlapi"; 246 connect_scheduled_ = true; 247 BrowserThread::PostDelayedTask(BrowserThread::IO, FROM_HERE, 248 base::Bind( 249 &BrailleControllerImpl::TryToConnect, 250 base::Unretained(this)), 251 delay); 252 } 253 254 void BrailleControllerImpl::Disconnect() { 255 DCHECK_CURRENTLY_ON(BrowserThread::IO); 256 if (!connection_ || !connection_->Connected()) 257 return; 258 connection_->Disconnect(); 259 DispatchOnDisplayStateChanged(scoped_ptr<DisplayState>(new DisplayState())); 260 } 261 262 scoped_ptr<BrlapiConnection> BrailleControllerImpl::CreateBrlapiConnection() { 263 DCHECK(libbrlapi_loader_.loaded()); 264 return BrlapiConnection::Create(&libbrlapi_loader_); 265 } 266 267 void BrailleControllerImpl::DispatchKeys() { 268 DCHECK(connection_.get()); 269 brlapi_keyCode_t code; 270 while (true) { 271 int result = connection_->ReadKey(&code); 272 if (result < 0) { // Error. 273 brlapi_error_t* err = connection_->BrlapiError(); 274 if (err->brlerrno == BRLAPI_ERROR_LIBCERR && err->libcerrno == EINTR) 275 continue; 276 // Disconnect on other errors. 277 VLOG(1) << "BrlAPI error: " << connection_->BrlapiStrError(); 278 Disconnect(); 279 return; 280 } else if (result == 0) { // No more data. 281 return; 282 } 283 scoped_ptr<KeyEvent> event = BrlapiKeyCodeToEvent(code); 284 if (event) 285 DispatchKeyEvent(event.Pass()); 286 } 287 } 288 289 void BrailleControllerImpl::DispatchKeyEvent(scoped_ptr<KeyEvent> event) { 290 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { 291 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 292 base::Bind( 293 &BrailleControllerImpl::DispatchKeyEvent, 294 base::Unretained(this), 295 base::Passed(&event))); 296 return; 297 } 298 VLOG(1) << "Dispatching key event: " << *event->ToValue(); 299 FOR_EACH_OBSERVER(BrailleObserver, observers_, OnBrailleKeyEvent(*event)); 300 } 301 302 void BrailleControllerImpl::DispatchOnDisplayStateChanged( 303 scoped_ptr<DisplayState> new_state) { 304 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { 305 if (!BrowserThread::PostTask( 306 BrowserThread::UI, FROM_HERE, 307 base::Bind(&BrailleControllerImpl::DispatchOnDisplayStateChanged, 308 base::Unretained(this), 309 base::Passed(&new_state)))) { 310 NOTREACHED(); 311 } 312 return; 313 } 314 FOR_EACH_OBSERVER(BrailleObserver, observers_, 315 OnBrailleDisplayStateChanged(*new_state)); 316 } 317 318 } // namespace braille_display_private 319 } // namespace api 320 } // namespace extensions 321