// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "chrome/browser/extensions/api/braille_display_private/brlapi_connection.h"
#include <errno.h>
#include <string>
#include "base/files/file_descriptor_watcher_posix.h"
#include "base/logging.h"
#include "base/memory/free_deleter.h"
#include "base/memory/raw_ptr.h"
#include "base/system/sys_info.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
namespace extensions {
namespace api {
namespace braille_display_private {
namespace {
// Default virtual terminal. This can be overriden by setting the
// WINDOWPATH environment variable. This is only used when not running
// under Chrome OS (that is in aura for a Linux desktop).
// TODO(plundblad): Find a way to detect the controlling terminal of the
// X server.
static const int kDefaultTtyLinux = 7;
#if BUILDFLAG(IS_CHROMEOS_ASH)
// The GUI is always running on vt1 in Chrome OS.
static const int kDefaultTtyChromeOS = 1;
#endif
} // namespace
class BrlapiConnectionImpl : public BrlapiConnection {
public:
explicit BrlapiConnectionImpl(LibBrlapiLoader* loader) :
libbrlapi_loader_(loader) {}
BrlapiConnectionImpl(const BrlapiConnectionImpl&) = delete;
BrlapiConnectionImpl& operator=(const BrlapiConnectionImpl&) = delete;
~BrlapiConnectionImpl() override { Disconnect(); }
ConnectResult Connect(OnDataReadyCallback on_data_ready) override;
void Disconnect() override;
bool Connected() override { return handle_ != nullptr; }
brlapi_error_t* BrlapiError() override;
std::string BrlapiStrError() override;
bool GetDisplaySize(unsigned int* rows, unsigned int* columns) override;
bool WriteDots(const std::vector<unsigned char>& cells) override;
int ReadKey(brlapi_keyCode_t* keyCode) override;
bool GetCellSize(unsigned int* cell_size) override;
private:
bool CheckConnected();
ConnectResult ConnectResultForError();
raw_ptr<LibBrlapiLoader> libbrlapi_loader_;
std::unique_ptr<brlapi_handle_t, base::FreeDeleter> handle_;
std::unique_ptr<base::FileDescriptorWatcher::Controller> fd_controller_;
};
std::unique_ptr<BrlapiConnection> BrlapiConnection::Create(
LibBrlapiLoader* loader) {
DCHECK(loader->loaded());
return std::unique_ptr<BrlapiConnection>(new BrlapiConnectionImpl(loader));
}
BrlapiConnection::ConnectResult BrlapiConnectionImpl::Connect(
OnDataReadyCallback on_data_ready) {
DCHECK(!handle_);
handle_.reset(reinterpret_cast<brlapi_handle_t*>(
malloc(libbrlapi_loader_->brlapi_getHandleSize())));
int fd = libbrlapi_loader_->brlapi__openConnection(handle_.get(), nullptr,
nullptr);
if (fd < 0) {
handle_.reset();
VLOG(1) << "Error connecting to brlapi: " << BrlapiStrError();
return ConnectResultForError();
}
int path[2] = {0, 0};
int pathElements = 0;
#if BUILDFLAG(IS_CHROMEOS_ASH)
if (base::SysInfo::IsRunningOnChromeOS())
path[pathElements++] = kDefaultTtyChromeOS;
#endif
if (pathElements == 0 && getenv("WINDOWPATH") == nullptr)
path[pathElements++] = kDefaultTtyLinux;
if (libbrlapi_loader_->brlapi__enterTtyModeWithPath(
handle_.get(), path, pathElements, nullptr) < 0) {
LOG(ERROR) << "brlapi: couldn't enter tty mode: " << BrlapiStrError();
Disconnect();
return CONNECT_ERROR_RETRY;
}
unsigned int rows = 0;
unsigned int columns = 0;
if (!GetDisplaySize(&rows, &columns)) {
// Error already logged.
Disconnect();
return CONNECT_ERROR_RETRY;
}
// A display size of 0 means no display connected. We can't reliably
// detect when a display gets connected, so fail and let the caller
// retry connecting.
if (rows * columns == 0) {
VLOG(1) << "No braille display connected";
Disconnect();
return CONNECT_ERROR_RETRY;
}
const brlapi_keyCode_t extraKeys[] = {
BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_OFFLINE,
// brltty 5.1 converts dot input to Unicode characters unless we
// explicitly accept this command.
BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_PASSDOTS,
};
if (libbrlapi_loader_->brlapi__acceptKeys(handle_.get(),
brlapi_rangeType_command, extraKeys,
std::size(extraKeys)) < 0) {
LOG(ERROR) << "Couldn't acceptKeys: " << BrlapiStrError();
Disconnect();
return CONNECT_ERROR_RETRY;
}
fd_controller_ =
base::FileDescriptorWatcher::WatchReadable(fd, std::move(on_data_ready));
return CONNECT_SUCCESS;
}
void BrlapiConnectionImpl::Disconnect() {
if (!handle_) {
return;
}
fd_controller_.reset();
libbrlapi_loader_->brlapi__closeConnection(
handle_.get());
handle_.reset();
}
brlapi_error_t* BrlapiConnectionImpl::BrlapiError() {
return libbrlapi_loader_->brlapi_error_location();
}
std::string BrlapiConnectionImpl::BrlapiStrError() {
return libbrlapi_loader_->brlapi_strerror(BrlapiError());
}
bool BrlapiConnectionImpl::GetDisplaySize(unsigned int* columns,
unsigned int* rows) {
if (!CheckConnected()) {
return false;
}
if (libbrlapi_loader_->brlapi__getDisplaySize(handle_.get(), columns, rows) <
0) {
LOG(ERROR) << "Couldn't get braille display size " << BrlapiStrError();
return false;
}
return true;
}
bool BrlapiConnectionImpl::WriteDots(const std::vector<unsigned char>& cells) {
// Cells is a 2D vector, compressed into 1D.
if (!CheckConnected())
return false;
if (libbrlapi_loader_->brlapi__writeDots(handle_.get(), cells.data()) < 0) {
VLOG(1) << "Couldn't write to brlapi: " << BrlapiStrError();
return false;
}
return true;
}
int BrlapiConnectionImpl::ReadKey(brlapi_keyCode_t* key_code) {
if (!CheckConnected())
return -1;
return libbrlapi_loader_->brlapi__readKey(
handle_.get(), 0 /*wait*/, key_code);
}
bool BrlapiConnectionImpl::GetCellSize(unsigned int* cell_size) {
if (!CheckConnected()) {
return false;
}
brlapi_param_deviceCellSize_t device_cell_size;
ssize_t result = libbrlapi_loader_->brlapi__getParameter(
handle_.get(), BRLAPI_PARAM_DEVICE_CELL_SIZE, 0, BRLAPI_PARAMF_GLOBAL,
&device_cell_size, sizeof(device_cell_size));
if (result == -1 || result != sizeof(device_cell_size))
return false;
*cell_size = device_cell_size;
return true;
}
bool BrlapiConnectionImpl::CheckConnected() {
if (!handle_) {
BrlapiError()->brlerrno = BRLAPI_ERROR_ILLEGAL_INSTRUCTION;
return false;
}
return true;
}
BrlapiConnection::ConnectResult BrlapiConnectionImpl::ConnectResultForError() {
const brlapi_error_t* error = BrlapiError();
// For the majority of users, the socket file will never exist because
// the daemon is never run. Avoid retrying in this case, relying on
// the socket directory to change and trigger further tries if the
// daemon comes up later on.
if (error->brlerrno == BRLAPI_ERROR_LIBCERR
&& error->libcerrno == ENOENT) {
return CONNECT_ERROR_NO_RETRY;
}
return CONNECT_ERROR_RETRY;
}
} // namespace braille_display_private
} // namespace api
} // namespace extensions