OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 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_controll
er.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/files/file_path_watcher.h" |
| 15 #include "base/memory/scoped_ptr.h" |
| 16 #include "base/memory/singleton.h" |
| 17 #include "base/observer_list.h" |
| 18 #include "base/time/time.h" |
| 19 #include "chrome/browser/extensions/api/braille_display_private/brlapi_connectio
n.h" |
| 20 #include "content/public/browser/browser_thread.h" |
| 21 #include "library_loaders/libbrlapi.h" |
| 22 |
| 23 namespace extensions { |
| 24 using content::BrowserThread; |
| 25 using base::TimeDelta; |
| 26 namespace api { |
| 27 namespace braille_display_private { |
| 28 |
| 29 namespace { |
| 30 // Delay between detecting a directory update and trying to connect |
| 31 // to the brlapi. |
| 32 const int64 kConnectionDelayMs = 1000; |
| 33 } |
| 34 |
| 35 class BrailleControllerImpl : public BrailleController { |
| 36 public: |
| 37 static BrailleControllerImpl* GetInstance(); |
| 38 virtual scoped_ptr<base::DictionaryValue> GetDisplayState() OVERRIDE; |
| 39 virtual void WriteDots(const std::string& cells) OVERRIDE; |
| 40 virtual void AddObserver(BrailleObserver* observer) OVERRIDE; |
| 41 virtual void RemoveObserver(BrailleObserver* observer) OVERRIDE; |
| 42 virtual void SetCreateBrlapiConnectionForTesting( |
| 43 const CreateBrlapiConnectionFunction& callback) OVERRIDE; |
| 44 |
| 45 private: |
| 46 BrailleControllerImpl(); |
| 47 virtual ~BrailleControllerImpl(); |
| 48 void TryLoadLibBrlApi(); |
| 49 void StartConnecting(); |
| 50 void OnSocketDirChanged(const base::FilePath& path, bool error); |
| 51 void TryToConnect(); |
| 52 scoped_ptr<BrlapiConnection> CreateBrlapiConnection(); |
| 53 void DispatchKeys(); |
| 54 scoped_ptr<KeyEvent> MapKeyCode(brlapi_keyCode_t code); |
| 55 void DispatchKeyEvent(scoped_ptr<KeyEvent> event); |
| 56 |
| 57 LibBrlapiLoader libbrlapi_loader_; |
| 58 CreateBrlapiConnectionFunction create_brlapi_connection_function_; |
| 59 |
| 60 // Manipulated on the IO thread. |
| 61 scoped_ptr<BrlapiConnection> connection_; |
| 62 base::FilePathWatcher file_path_watcher_; |
| 63 |
| 64 // Manipulated on the UI thread. |
| 65 ObserverList<BrailleObserver> observers_; |
| 66 bool watching_dir_; |
| 67 |
| 68 friend struct DefaultSingletonTraits<BrailleControllerImpl>; |
| 69 |
| 70 DISALLOW_COPY_AND_ASSIGN(BrailleControllerImpl); |
| 71 }; |
| 72 |
| 73 BrailleController::BrailleController() { |
| 74 } |
| 75 |
| 76 BrailleController::~BrailleController() { |
| 77 } |
| 78 |
| 79 // static |
| 80 BrailleController* BrailleController::GetInstance() { |
| 81 return BrailleControllerImpl::GetInstance(); |
| 82 } |
| 83 |
| 84 // static |
| 85 BrailleControllerImpl* BrailleControllerImpl::GetInstance() { |
| 86 return Singleton<BrailleControllerImpl, |
| 87 LeakySingletonTraits<BrailleControllerImpl> >::get(); |
| 88 } |
| 89 |
| 90 BrailleControllerImpl::BrailleControllerImpl() |
| 91 : watching_dir_(false) { |
| 92 create_brlapi_connection_function_ = base::Bind( |
| 93 &BrailleControllerImpl::CreateBrlapiConnection, |
| 94 base::Unretained(this)); |
| 95 } |
| 96 |
| 97 BrailleControllerImpl::~BrailleControllerImpl() { |
| 98 } |
| 99 |
| 100 void BrailleControllerImpl::TryLoadLibBrlApi() { |
| 101 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
| 102 if (libbrlapi_loader_.loaded()) |
| 103 return; |
| 104 // These versions of libbrlapi work the same for the functions we |
| 105 // are using. (0.6.0 adds brlapi_writeWText). |
| 106 static const char* kSupportedVersions[] = { |
| 107 "libbrlapi.so.0.5", |
| 108 "libbrlapi.so.0.6" |
| 109 }; |
| 110 for (size_t i = 0; i < arraysize(kSupportedVersions); ++i) { |
| 111 if (libbrlapi_loader_.Load(kSupportedVersions[i])) |
| 112 return; |
| 113 } |
| 114 LOG(WARNING) << "Couldn't load libbrlapi: " << strerror(errno); |
| 115 } |
| 116 |
| 117 scoped_ptr<base::DictionaryValue> BrailleControllerImpl::GetDisplayState() { |
| 118 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
| 119 if (!watching_dir_) { |
| 120 watching_dir_ = true; |
| 121 StartConnecting(); |
| 122 } |
| 123 DisplayState displayState; |
| 124 if (connection_.get() && connection_->Connected()) { |
| 125 size_t size; |
| 126 if (!connection_->GetDisplaySize(&size)) { |
| 127 connection_->Disconnect(); |
| 128 } else if (size > 0) { // size == 0 means no display present. |
| 129 displayState.available = true; |
| 130 displayState.text_cell_count.reset(new int(size)); |
| 131 } |
| 132 } |
| 133 return displayState.ToValue().Pass(); |
| 134 } |
| 135 |
| 136 void BrailleControllerImpl::WriteDots(const std::string& cells) { |
| 137 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
| 138 if (connection_ && connection_->Connected()) { |
| 139 size_t size; |
| 140 if (!connection_->GetDisplaySize(&size)) { |
| 141 connection_->Disconnect(); |
| 142 } |
| 143 std::vector<unsigned char> sizedCells(size); |
| 144 std::memcpy(&sizedCells[0], cells.data(), std::min(cells.size(), size)); |
| 145 if (size > cells.size()) |
| 146 std::fill(sizedCells.begin() + cells.size(), sizedCells.end(), 0); |
| 147 if (!connection_->WriteDots(&sizedCells[0])) |
| 148 connection_->Disconnect(); |
| 149 } |
| 150 } |
| 151 |
| 152 void BrailleControllerImpl::AddObserver(BrailleObserver* observer) { |
| 153 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| 154 observers_.AddObserver(observer); |
| 155 } |
| 156 |
| 157 void BrailleControllerImpl::RemoveObserver(BrailleObserver* observer) { |
| 158 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| 159 observers_.RemoveObserver(observer); |
| 160 } |
| 161 |
| 162 void BrailleControllerImpl::SetCreateBrlapiConnectionForTesting( |
| 163 const CreateBrlapiConnectionFunction& function) { |
| 164 if (function.is_null()) { |
| 165 create_brlapi_connection_function_ = base::Bind( |
| 166 &BrailleControllerImpl::CreateBrlapiConnection, |
| 167 base::Unretained(this)); |
| 168 } else { |
| 169 create_brlapi_connection_function_ = function; |
| 170 } |
| 171 } |
| 172 |
| 173 void BrailleControllerImpl::StartConnecting() { |
| 174 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
| 175 TryLoadLibBrlApi(); |
| 176 if (!libbrlapi_loader_.loaded()) { |
| 177 return; |
| 178 } |
| 179 base::FilePath brlapi_dir(BRLAPI_SOCKETPATH); |
| 180 if (!file_path_watcher_.Watch( |
| 181 brlapi_dir, false, base::Bind( |
| 182 &BrailleControllerImpl::OnSocketDirChanged, |
| 183 base::Unretained(this)))) { |
| 184 LOG(WARNING) << "Couldn't watch brlapi directory " << BRLAPI_SOCKETPATH; |
| 185 return; |
| 186 } |
| 187 TryToConnect(); |
| 188 } |
| 189 |
| 190 void BrailleControllerImpl::OnSocketDirChanged(const base::FilePath& path, |
| 191 bool error) { |
| 192 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
| 193 DCHECK(libbrlapi_loader_.loaded()); |
| 194 if (error) { |
| 195 LOG(ERROR) << "Error watching brlapi directory: " << path.value(); |
| 196 return; |
| 197 } |
| 198 LOG(INFO) << "BrlAPI directory changed"; |
| 199 BrowserThread::PostDelayedTask(BrowserThread::IO, FROM_HERE, |
| 200 base::Bind( |
| 201 &BrailleControllerImpl::TryToConnect, |
| 202 base::Unretained(this)), |
| 203 TimeDelta::FromMilliseconds( |
| 204 kConnectionDelayMs)); |
| 205 } |
| 206 |
| 207 void BrailleControllerImpl::TryToConnect() { |
| 208 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
| 209 DCHECK(libbrlapi_loader_.loaded()); |
| 210 if (!connection_.get()) |
| 211 connection_ = create_brlapi_connection_function_.Run(); |
| 212 if (connection_.get() && !connection_->Connected()) { |
| 213 if (!connection_->Connect(base::Bind( |
| 214 &BrailleControllerImpl::DispatchKeys, |
| 215 base::Unretained(this)))) |
| 216 LOG(WARNING) << "Couldn't connect to brlapi"; |
| 217 } |
| 218 } |
| 219 |
| 220 scoped_ptr<BrlapiConnection> BrailleControllerImpl::CreateBrlapiConnection() { |
| 221 DCHECK(libbrlapi_loader_.loaded()); |
| 222 return BrlapiConnection::Create(&libbrlapi_loader_); |
| 223 } |
| 224 |
| 225 scoped_ptr<KeyEvent> BrailleControllerImpl::MapKeyCode(brlapi_keyCode_t code) { |
| 226 brlapi_expandedKeyCode_t expanded; |
| 227 if (libbrlapi_loader_.brlapi_expandKeyCode(code, &expanded) != 0) { |
| 228 LOG(ERROR) << "Couldn't expand key code " << code; |
| 229 return scoped_ptr<KeyEvent>(); |
| 230 } |
| 231 scoped_ptr<KeyEvent> result(new KeyEvent); |
| 232 result->command = KEY_COMMAND_NONE; |
| 233 switch (expanded.type) { |
| 234 case BRLAPI_KEY_TYPE_CMD: |
| 235 switch (expanded.command) { |
| 236 case BRLAPI_KEY_CMD_LNUP: |
| 237 result->command = KEY_COMMAND_LINE_UP; |
| 238 break; |
| 239 case BRLAPI_KEY_CMD_LNDN: |
| 240 result->command = KEY_COMMAND_LINE_DOWN; |
| 241 break; |
| 242 case BRLAPI_KEY_CMD_FWINLT: |
| 243 result->command = KEY_COMMAND_PAN_LEFT; |
| 244 break; |
| 245 case BRLAPI_KEY_CMD_FWINRT: |
| 246 result->command = KEY_COMMAND_PAN_RIGHT; |
| 247 break; |
| 248 case BRLAPI_KEY_CMD_TOP: |
| 249 result->command = KEY_COMMAND_TOP; |
| 250 break; |
| 251 case BRLAPI_KEY_CMD_BOT: |
| 252 result->command = KEY_COMMAND_BOTTOM; |
| 253 break; |
| 254 case BRLAPI_KEY_CMD_ROUTE: |
| 255 result->command = KEY_COMMAND_ROUTING; |
| 256 result->display_position.reset(new int(expanded.argument)); |
| 257 break; |
| 258 case BRLAPI_KEY_CMD_PASSDOTS: |
| 259 result->command = KEY_COMMAND_DOTS; |
| 260 // The 8 low-order bits in the argument contains the dots. |
| 261 result->braille_dots.reset(new int(expanded.argument & 0xf)); |
| 262 if ((expanded.argument & BRLAPI_DOTC) != 0) |
| 263 result->space_key.reset(new bool(true)); |
| 264 break; |
| 265 } |
| 266 break; |
| 267 } |
| 268 if (result->command == KEY_COMMAND_NONE) |
| 269 result.reset(); |
| 270 return result.Pass(); |
| 271 } |
| 272 |
| 273 void BrailleControllerImpl::DispatchKeys() { |
| 274 DCHECK(connection_.get()); |
| 275 brlapi_keyCode_t code; |
| 276 while (true) { |
| 277 int result = connection_->ReadKey(&code); |
| 278 if (result < 0) { // Error. |
| 279 brlapi_error_t* err = connection_->BrlapiError(); |
| 280 if (err->brlerrno == BRLAPI_ERROR_LIBCERR && err->libcerrno == EINTR) |
| 281 continue; |
| 282 // Disconnect on other errors. |
| 283 LOG(ERROR) << "BrlAPI error: " << connection_->BrlapiStrError(); |
| 284 connection_->Disconnect(); |
| 285 return; |
| 286 } else if (result == 0) { // No more data. |
| 287 return; |
| 288 } |
| 289 scoped_ptr<KeyEvent> event = MapKeyCode(code); |
| 290 if (event) |
| 291 DispatchKeyEvent(event.Pass()); |
| 292 } |
| 293 } |
| 294 |
| 295 void BrailleControllerImpl::DispatchKeyEvent(scoped_ptr<KeyEvent> event) { |
| 296 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { |
| 297 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| 298 base::Bind( |
| 299 &BrailleControllerImpl::DispatchKeyEvent, |
| 300 base::Unretained(this), |
| 301 base::Passed(&event))); |
| 302 return; |
| 303 } |
| 304 FOR_EACH_OBSERVER(BrailleObserver, observers_, OnKeyEvent(*event)); |
| 305 } |
| 306 |
| 307 } // namespace braille_display_private |
| 308 } // namespace api |
| 309 } // namespace extensions |
OLD | NEW |