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