Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(116)

Side by Side Diff: chrome/browser/extensions/api/braille_display_private/braille_controller_brlapi.cc

Issue 13355002: Implement chrome.brailleDisplayPrivate API (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Temporarily default brlapi to be enabled on chromeos to run through trybots, this is now rebased on… Created 7 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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/chromeos/chromeos_version.h"
15 #include "base/files/file_path_watcher.h"
16 #include "base/memory/scoped_ptr.h"
17 #include "base/memory/singleton.h"
18 #include "base/message_loop/message_loop.h"
19 #include "base/observer_list.h"
20 #include "base/time/time.h"
21 #include "chrome/common/extensions/api/braille_display_private.h"
David Tseng 2013/09/05 19:21:23 Already included in header.
22 #include "content/public/browser/browser_thread.h"
23 #include "library_loaders/libbrlapi.h"
24
25 namespace extensions {
26 using content::BrowserThread;
27 using base::MessageLoopForIO;
28 using base::TimeDelta;
29 namespace api {
30 namespace braille_display_private {
31 namespace {
32 // Default virtual terminal. This can be overriden by setting the
33 // WINDOWPATH environment variable. This is only used when not running
34 // under Crhome OS (that is in aura for a Linux desktop).
David Tseng 2013/09/05 19:21:23 ChromeOS
35 // TODO(plundblad): Find a way to detect the controlling terminal of the
36 // X server.
37 static const int kDefaultTtyLinux = 7;
38 #if defined(OS_CHROMEOS)
39 // The GUI is always running on vt1 in ChromeOS.
40 static const int kDefaultTtyChromeOS = 1;
David Tseng 2013/09/05 19:21:23 You could eliminate one of the constants by doing
41 #endif
42 } // namespace
43
44 class BrailleControllerImpl : public BrailleController {
45 public:
46 static BrailleControllerImpl* GetInstance();
47 virtual scoped_ptr<base::DictionaryValue> GetDisplayState() OVERRIDE;
48 virtual void WriteDots(const std::string& cells) OVERRIDE;
49 virtual void AddObserver(Observer* observer) OVERRIDE;
50 virtual void RemoveObserver(Observer* observer) OVERRIDE;
51
52 private:
53 class Connection : public MessageLoopForIO::Watcher {
54 public:
55 explicit Connection(BrailleControllerImpl* braille_controller) :
56 braille_controller_(braille_controller) {
57 }
58
59 virtual ~Connection() {
60 Disconnect();
61 }
62
63 bool Connect() {
64 DCHECK(!handle_);
65 if (!braille_controller_->libbrlapi_loader_.loaded()) {
66 return false;
67 }
68 handle_.reset((brlapi_handle_t*) malloc(
69 braille_controller_->libbrlapi_loader_.brlapi_getHandleSize()));
70 int fd = braille_controller_->libbrlapi_loader_.brlapi__openConnection(
71 handle_.get(), NULL, NULL);
72 if (fd < 0) {
73 handle_.reset();
74 LOG(ERROR) << "Error connecting to brlapi: "
75 << braille_controller_->BrlApiStrError();
76 return false;
77 }
78 if (!MessageLoopForIO::current()->WatchFileDescriptor(
79 fd, true, MessageLoopForIO::WATCH_READ, &fd_controller_, this)) {
80 LOG(ERROR) << "Couldn't watch file descriptor " << fd;
81 Disconnect();
82 return false;
83 }
84 return true;
85 }
86
87 void Disconnect() {
88 if (handle_ == NULL) {
89 return;
90 }
91 fd_controller_.StopWatchingFileDescriptor();
92 braille_controller_->libbrlapi_loader_.brlapi__closeConnection(
93 handle_.get());
94 handle_.reset();
95 }
96
97 bool Connected() { return handle_; }
98
99 brlapi_handle_t* GetHandle() { return handle_.get(); }
100
101 // Gets the total size of the display, which may be 0 if no display is
102 // present, returning true on success. Note that this is cached in the
103 // brlapi client so it is cheap.
104 bool GetDisplaySize(size_t* size) {
105 if (!handle_) {
106 return false;
107 }
108 unsigned int columns, rows;
109 if (braille_controller_->libbrlapi_loader_.brlapi__getDisplaySize(
110 handle_.get(), &columns, &rows) < 0) {
111 return false;
112 }
113 *size = columns * rows;
114 return true;
115 }
116
117 // MessageLoopForIO::Watcher
118 virtual void OnFileCanReadWithoutBlocking(int fd) {
119 braille_controller_->DispatchKeys();
120 }
121
122 virtual void OnFileCanWriteWithoutBlocking(int fd) {}
123
124 private:
125 scoped_ptr<brlapi_handle_t, base::FreeDeleter> handle_;
126 MessageLoopForIO::FileDescriptorWatcher fd_controller_;
127 BrailleControllerImpl* braille_controller_;
128 };
129 BrailleControllerImpl();
130 virtual ~BrailleControllerImpl();
131 void StartConnections();
132 void StopConnections();
133 void OnSocketDirChanged(const base::FilePath& path, bool error);
134 void ReconnectAll();
135 void UpdateConnections();
136 void TryConnection();
137 void DispatchKeys();
138 scoped_ptr<KeyEvent> MapKeyCode(brlapi_keyCode_t code);
139 void DispatchKeyEvent(scoped_ptr<KeyEvent> event);
140 brlapi_error_t* BrlApiError();
141 std::string BrlApiStrError();
142 LibBrlApiLoader libbrlapi_loader_;
143 // Manipulated on the IO thread.
144 Connection connection_;
145 base::FilePathWatcher file_path_watcher_;
146 // Manipulated on the UI thread.
147 ObserverList<Observer> observers_;
148 bool watching_dir_;
149 friend struct DefaultSingletonTraits<BrailleControllerImpl>;
150 DISALLOW_COPY_AND_ASSIGN(BrailleControllerImpl);
151 };
152
153 BrailleController::BrailleController() {
154 }
155
156 BrailleController::~BrailleController() {
157 }
158
159 // static
160 BrailleController* BrailleController::GetInstance() {
161 return BrailleControllerImpl::GetInstance();
162 }
163
164 BrailleControllerImpl::BrailleControllerImpl()
165 : connection_(this), watching_dir_(false) {
166 if (!libbrlapi_loader_.Load("libbrlapi.so.0.5")) {
David Tseng 2013/09/05 19:21:23 Define this as a static constant.
167 LOG(ERROR) << "Couldn't load libbrlapi: " << strerror(errno);
168 }
David Tseng 2013/09/05 19:21:23 No need for braces in if clauses of one line (Chro
169 }
170
171 BrailleControllerImpl::~BrailleControllerImpl() {
172 }
173
174 // static
175 BrailleControllerImpl* BrailleControllerImpl::GetInstance() {
176 return Singleton<BrailleControllerImpl,
177 LeakySingletonTraits<BrailleControllerImpl> >::get();
178 }
179
180 scoped_ptr<base::DictionaryValue> BrailleControllerImpl::GetDisplayState() {
181 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
182 DisplayState displayState;
183 if (connection_.Connected()) {
184 size_t size;
185 if (!connection_.GetDisplaySize(&size)) {
186 LOG(ERROR) << "Couldn't get braille display size " << BrlApiStrError();
187 connection_.Disconnect();
188 } else if (size > 0) {
David Tseng 2013/09/05 19:21:23 When does a display get size of zero? Is that also
189 displayState.available = true;
190 displayState.text_cell_count.reset(new int(size));
191 }
192 }
193 return displayState.ToValue().Pass();
194 }
195
196 void BrailleControllerImpl::WriteDots(const std::string& cells) {
197 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
198 if (!libbrlapi_loader_.loaded()) {
David Tseng 2013/09/05 19:21:23 Ditto; remove braces.
199 return;
200 }
201 if (connection_.Connected()) {
202 brlapi_handle_t* handle = connection_.GetHandle();
203 size_t size;
204 if (!connection_.GetDisplaySize(&size)) {
205 LOG(ERROR) << "Couldn't get display size " << BrlApiStrError();
206 connection_.Disconnect();
207 }
208 std::vector<unsigned char> sizedCells(size);
209 std::memcpy(&sizedCells[0], cells.data(), std::min(cells.size(), size));
210 if (size > cells.size()) {
David Tseng 2013/09/05 19:21:23 Ditto.
211 std::fill(sizedCells.begin() + cells.size(), sizedCells.end(), 0);
212 }
213 if (libbrlapi_loader_.brlapi__writeDots(handle, &sizedCells[0]) < 0) {
214 LOG(ERROR) << "Couldn't write to brlapi: " << BrlApiStrError();
215 connection_.Disconnect();
David Tseng 2013/09/05 19:21:23 Are there instances where we don't want to disconn
216 }
217 }
218 }
219
220 void BrailleControllerImpl::AddObserver(Observer* observer) {
221 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
222 observers_.AddObserver(observer);
223 if (libbrlapi_loader_.loaded() && !watching_dir_) {
224 watching_dir_ = true;
225 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
226 base::Bind(&BrailleControllerImpl::StartConnections,
227 base::Unretained(this)));
228 }
229 }
230
231 void BrailleControllerImpl::RemoveObserver(Observer* observer) {
232 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
233 observers_.RemoveObserver(observer);
234 }
235
236 void BrailleControllerImpl::StartConnections() {
237 base::FilePath brlapi_dir(BRLAPI_SOCKETPATH);
238 LOG(ERROR) << "Watching brlapi dir " << BRLAPI_SOCKETPATH;
David Tseng 2013/09/05 19:21:23 INFO?
239 if (!file_path_watcher_.Watch(
240 brlapi_dir, false, base::Bind(
241 &BrailleControllerImpl::OnSocketDirChanged,
242 base::Unretained(this)))) {
243 LOG(WARNING) << "Couldn't watch brlapi directory " << BRLAPI_SOCKETPATH;
244 return;
245 }
246 UpdateConnections();
247 }
248
249 void BrailleControllerImpl::OnSocketDirChanged(const base::FilePath& path,
250 bool error) {
251 if (error) {
252 LOG(ERROR) << "Error watching brlapi path: " << path.value();
253 return;
254 }
255 LOG(ERROR) << "BrlAPI directory changed";
256 BrowserThread::PostDelayedTask(BrowserThread::IO, FROM_HERE,
257 base::Bind(
258 &BrailleControllerImpl::UpdateConnections,
259 base::Unretained(this)),
260 TimeDelta::FromSeconds(1));
261
262 }
263
264 void BrailleControllerImpl::UpdateConnections() {
265 if (!connection_.Connected()) {
266 TryConnection();
267 }
268 }
269
270 void BrailleControllerImpl::TryConnection() {
271 if (!connection_.Connect()) {
272 LOG(ERROR) << "Couldn't connect to brlapi\n";
273 return;
274 }
275 int path[2] = {0, 0};
276 int pathElements = 0;
277 #if defined(OS_CHROMEOS)
278 if (base::chromeos::IsRunningOnChromeOS()) {
279 path[pathElements++] = kDefaultTtyChromeOS;
280 }
281 #endif
282 if (pathElements == 0 && getenv("WINDOWPATH") == NULL) {
283 path[pathElements++] = kDefaultTtyLinux;
284 }
285 if (libbrlapi_loader_.brlapi__enterTtyModeWithPath(
286 connection_.GetHandle(), path, pathElements, NULL) < 0) {
287 LOG(ERROR) << "brlapi: couldn't enter tty mode: " << BrlApiStrError();
288 connection_.Disconnect();
289 return;
290 }
291 const brlapi_keyCode_t extraKeys[] = {
292 BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_OFFLINE,
293 };
294 if (libbrlapi_loader_.brlapi__acceptKeys(
295 connection_.GetHandle(),
296 brlapi_rangeType_command,
297 extraKeys,
298 arraysize(extraKeys)) < 0) {
299 LOG(ERROR) << "Couldn't acceptKeys: " << BrlApiStrError();
300 connection_.Disconnect();
301 return;
302 }
303 }
304
305 scoped_ptr<KeyEvent> BrailleControllerImpl::MapKeyCode(brlapi_keyCode_t code) {
306 brlapi_expandedKeyCode_t expanded;
307 if (libbrlapi_loader_.brlapi_expandKeyCode(code, &expanded) != 0) {
308 LOG(ERROR) << "Couldn't expand key key code " << code;
309 return scoped_ptr<KeyEvent>();
310 }
311 scoped_ptr<KeyEvent> result(new KeyEvent);
312 result->command = KEY_COMMAND_NONE;
313 switch (expanded.type) {
314 case BRLAPI_KEY_TYPE_CMD:
315 switch (expanded.command) {
316 case BRLAPI_KEY_CMD_LNUP:
317 result->command = KEY_COMMAND_LINE_UP;
318 break;
319 case BRLAPI_KEY_CMD_LNDN:
320 result->command = KEY_COMMAND_LINE_DOWN;
321 break;
322 case BRLAPI_KEY_CMD_FWINLT:
323 result->command = KEY_COMMAND_PAN_LEFT;
324 break;
325 case BRLAPI_KEY_CMD_FWINRT:
326 result->command = KEY_COMMAND_PAN_RIGHT;
327 break;
328 case BRLAPI_KEY_CMD_TOP:
329 result->command = KEY_COMMAND_TOP;
330 break;
331 case BRLAPI_KEY_CMD_BOT:
332 result->command = KEY_COMMAND_BOTTOM;
333 break;
334 case BRLAPI_KEY_CMD_ROUTE:
335 result->command = KEY_COMMAND_ROUTING;
336 result->display_position.reset(new int(expanded.argument));
337 break;
338 case BRLAPI_KEY_CMD_PASSDOTS:
339 result->command = KEY_COMMAND_DOTS;
340 // The 8 low-order bits in the argument contains the dots.
341 result->braille_dots.reset(new int(expanded.argument & 0xf));
342 if ((expanded.argument & BRLAPI_DOTC) != 0) {
343 result->space_key.reset(new bool(true));
344 }
345 break;
346 }
347 break;
348 }
349 if (result->command == KEY_COMMAND_NONE) {
350 result.reset();
351 }
352 return result.Pass();
353 }
354
355 void BrailleControllerImpl::DispatchKeys() {
356 brlapi_handle_t* handle = connection_.GetHandle();
357 DCHECK(handle != NULL);
358 brlapi_keyCode_t code;
359 while (true) {
360 int result = libbrlapi_loader_.brlapi__readKey(handle, 0 /*wait*/, &code);
361 if (result < 0) { // Error.
362 brlapi_error_t* err = BrlApiError();
363 if (err->brlerrno == BRLAPI_ERROR_LIBCERR && err->libcerrno == EINTR) {
David Tseng 2013/09/05 19:21:23 Ditto.
364 continue;
365 }
366 // Disconnect on other errors.
367 LOG(ERROR) << "BrlAPI error: " << BrlApiStrError();
368 connection_.Disconnect();
369 return;
370 } else if (result == 0) { // No more data.
371 return;
372 }
373 scoped_ptr<KeyEvent> event = MapKeyCode(code);
374 if (event) {
375 DispatchKeyEvent(event.Pass());
376 }
377 }
378 }
379
380 void BrailleControllerImpl::DispatchKeyEvent(scoped_ptr<KeyEvent> event) {
381 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
382 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
383 base::Bind(
384 &BrailleControllerImpl::DispatchKeyEvent,
385 base::Unretained(this),
386 base::Passed(&event)));
387 return;
388 }
389 FOR_EACH_OBSERVER(Observer, observers_, OnKeyEvent(*event));
390 }
391
392 brlapi_error_t* BrailleControllerImpl::BrlApiError() {
393 DCHECK(libbrlapi_loader_.loaded());
394 return libbrlapi_loader_.brlapi_error_location();
395 }
396
397 std::string BrailleControllerImpl::BrlApiStrError() {
398 return libbrlapi_loader_.brlapi_strerror(BrlApiError());
399 }
400
401 } // namespace braille_display_private
402 } // namespace api
403 } // namespace extensions
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698