blob: f64fd2cf98186920dfbcbadc7b3ee4b8074c92c6 [file] [log] [blame]
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "lorgnette/sane_client_impl.h"
#include <base/logging.h>
#include <chromeos/dbus/service_constants.h>
#include <sane/saneopts.h>
#include "lorgnette/dbus_adaptors/org.chromium.lorgnette.Manager.h"
namespace lorgnette {
// static
std::unique_ptr<SaneClientImpl> SaneClientImpl::Create() {
SANE_Status status = sane_init(nullptr, nullptr);
if (status != SANE_STATUS_GOOD) {
LOG(ERROR) << "Unable to initialize SANE";
return nullptr;
}
// Cannot use make_unique() with a private constructor.
return std::unique_ptr<SaneClientImpl>(new SaneClientImpl());
}
SaneClientImpl::~SaneClientImpl() {
sane_exit();
}
bool SaneClientImpl::ListDevices(brillo::ErrorPtr* error,
std::vector<ScannerInfo>* scanners_out) {
base::AutoLock auto_lock(lock_);
const SANE_Device** device_list;
SANE_Status status = sane_get_devices(&device_list, SANE_FALSE);
if (status != SANE_STATUS_GOOD) {
brillo::Error::AddTo(error, FROM_HERE, brillo::errors::dbus::kDomain,
kManagerServiceError,
"Unable to get device list from SANE");
return false;
}
return DeviceListToScannerInfo(device_list, scanners_out);
}
// static
bool SaneClientImpl::DeviceListToScannerInfo(
const SANE_Device** device_list, std::vector<ScannerInfo>* scanners_out) {
if (!device_list || !scanners_out) {
LOG(ERROR) << "'device_list' and 'scanners_out' cannot be NULL";
return false;
}
std::unordered_set<std::string> names;
std::vector<ScannerInfo> scanners;
for (int i = 0; device_list[i]; i++) {
const SANE_Device* dev = device_list[i];
if (!dev->name || strcmp(dev->name, "") == 0)
continue;
if (names.count(dev->name) != 0) {
LOG(ERROR) << "Duplicate device name: " << dev->name;
return false;
}
names.insert(dev->name);
ScannerInfo info;
info.set_name(dev->name);
info.set_manufacturer(dev->vendor ? dev->vendor : "");
info.set_model(dev->model ? dev->model : "");
info.set_type(dev->type ? dev->type : "");
scanners.push_back(info);
}
*scanners_out = scanners;
return true;
}
SaneClientImpl::SaneClientImpl()
: open_devices_(std::make_shared<DeviceSet>()) {}
std::unique_ptr<SaneDevice> SaneClientImpl::ConnectToDevice(
brillo::ErrorPtr* error, const std::string& device_name) {
base::AutoLock auto_lock(lock_);
SANE_Handle handle;
SANE_Status status = sane_open(device_name.c_str(), &handle);
if (status != SANE_STATUS_GOOD) {
brillo::Error::AddToPrintf(error, FROM_HERE, brillo::errors::dbus::kDomain,
kManagerServiceError,
"Unable to open device '%s': %s",
device_name.c_str(), sane_strstatus(status));
return nullptr;
}
{
base::AutoLock auto_lock(open_devices_->first);
if (open_devices_->second.count(device_name) != 0) {
brillo::Error::AddToPrintf(
error, FROM_HERE, brillo::errors::dbus::kDomain, kManagerServiceError,
"Device '%s' is currently in-use", device_name.c_str());
return nullptr;
}
open_devices_->second.insert(device_name);
}
// Cannot use make_unique() with a private constructor.
auto device = std::unique_ptr<SaneDeviceImpl>(
new SaneDeviceImpl(handle, device_name, open_devices_));
device->LoadOptions(error);
return device;
}
SaneDeviceImpl::~SaneDeviceImpl() {
if (handle_)
sane_close(handle_);
base::AutoLock auto_lock(open_devices_->first);
open_devices_->second.erase(name_);
}
bool SaneDeviceImpl::GetValidOptionValues(brillo::ErrorPtr* error,
ValidOptionValues* values_out) {
if (!handle_) {
brillo::Error::AddTo(error, FROM_HERE, brillo::errors::dbus::kDomain,
kManagerServiceError, "No scanner connected");
return false;
}
if (!values_out) {
brillo::Error::AddTo(error, FROM_HERE, brillo::errors::dbus::kDomain,
kManagerServiceError,
"'values_out' pointer cannot be null");
return false;
}
ValidOptionValues values;
if (!GetValidIntOptionValues(error, kResolution, &values.resolutions)) {
brillo::Error::AddTo(error, FROM_HERE, brillo::errors::dbus::kDomain,
kManagerServiceError,
"Failed to get valid values for resolution setting");
return false;
}
if (!GetValidStringOptionValues(error, kSource, &values.sources)) {
brillo::Error::AddTo(error, FROM_HERE, brillo::errors::dbus::kDomain,
kManagerServiceError,
"Failed to get valid values for sources setting");
return false;
}
if (!GetValidStringOptionValues(error, kScanMode, &values.color_modes)) {
brillo::Error::AddTo(error, FROM_HERE, brillo::errors::dbus::kDomain,
kManagerServiceError,
"Failed to get valid values for scan modes setting");
return false;
}
*values_out = values;
return true;
}
bool SaneDeviceImpl::SetScanResolution(brillo::ErrorPtr* error,
int resolution) {
if (options_.count(kResolution) == 0) {
brillo::Error::AddTo(error, FROM_HERE, brillo::errors::dbus::kDomain,
kManagerServiceError, "No resolution option found.");
return false;
}
SaneOption option = options_[kResolution];
option.SetInt(resolution);
bool should_reload = false;
SANE_Status status = SetOption(&option, &should_reload);
if (status != SANE_STATUS_GOOD) {
brillo::Error::AddToPrintf(error, FROM_HERE, brillo::errors::dbus::kDomain,
kManagerServiceError,
"Unable to set resolution to %d: %s", resolution,
sane_strstatus(status));
return false;
}
if (should_reload)
LoadOptions(error);
return true;
}
bool SaneDeviceImpl::SetDocumentSource(brillo::ErrorPtr* error,
const DocumentSource& source) {
if (options_.count(kSource) == 0) {
brillo::Error::AddTo(error, FROM_HERE, brillo::errors::dbus::kDomain,
kManagerServiceError, "No source option found.");
return false;
}
SaneOption option = options_[kSource];
if (!option.SetString(source.name())) {
brillo::Error::AddTo(error, FROM_HERE, brillo::errors::dbus::kDomain,
kManagerServiceError, "Failed to set SaneOption");
}
bool should_reload = false;
SANE_Status status = SetOption(&option, &should_reload);
if (should_reload)
LoadOptions(error);
if (status != SANE_STATUS_GOOD) {
brillo::Error::AddToPrintf(error, FROM_HERE, brillo::errors::dbus::kDomain,
kManagerServiceError,
"Unable to set document source to %s: %s",
source.name().c_str(), sane_strstatus(status));
return false;
}
return true;
}
bool SaneDeviceImpl::SetColorMode(brillo::ErrorPtr* error,
ColorMode color_mode) {
std::string mode_string = "";
switch (color_mode) {
case MODE_LINEART:
mode_string = kScanPropertyModeLineart;
break;
case MODE_GRAYSCALE:
mode_string = kScanPropertyModeGray;
break;
case MODE_COLOR:
mode_string = kScanPropertyModeColor;
break;
default:
brillo::Error::AddToPrintf(
error, FROM_HERE, brillo::errors::dbus::kDomain, kManagerServiceError,
"Invalid color mode: %s", ColorMode_Name(color_mode).c_str());
return false;
}
if (options_.count(kScanMode) == 0) {
brillo::Error::AddTo(error, FROM_HERE, brillo::errors::dbus::kDomain,
kManagerServiceError, "No scan mode option found.");
return false;
}
SaneOption option = options_[kScanMode];
option.SetString(mode_string);
bool should_reload = false;
SANE_Status status = SetOption(&option, &should_reload);
if (should_reload)
LoadOptions(error);
if (status != SANE_STATUS_GOOD) {
brillo::Error::AddToPrintf(error, FROM_HERE, brillo::errors::dbus::kDomain,
kManagerServiceError,
"Unable to set scan mode to %s: %s",
mode_string.c_str(), sane_strstatus(status));
return false;
}
return true;
}
bool SaneDeviceImpl::StartScan(brillo::ErrorPtr* error) {
if (scan_running_) {
brillo::Error::AddTo(error, FROM_HERE, brillo::errors::dbus::kDomain,
kManagerServiceError, "Scan is already in progress");
return false;
}
SANE_Status status = sane_start(handle_);
if (status != SANE_STATUS_GOOD) {
brillo::Error::AddToPrintf(error, FROM_HERE, brillo::errors::dbus::kDomain,
kManagerServiceError, "Failed to start scan: %s",
sane_strstatus(status));
return false;
}
scan_running_ = true;
return true;
}
bool SaneDeviceImpl::GetScanParameters(brillo::ErrorPtr* error,
ScanParameters* parameters) {
if (!handle_) {
brillo::Error::AddTo(error, FROM_HERE, brillo::errors::dbus::kDomain,
kManagerServiceError, "No scanner connected");
return false;
}
if (!parameters) {
brillo::Error::AddTo(error, FROM_HERE, brillo::errors::dbus::kDomain,
kManagerServiceError,
"'parameters' pointer cannot be null");
return false;
}
SANE_Parameters params;
SANE_Status status = sane_get_parameters(handle_, &params);
if (status != SANE_STATUS_GOOD) {
brillo::Error::AddToPrintf(
error, FROM_HERE, brillo::errors::dbus::kDomain, kManagerServiceError,
"Failed to read scan parameters: %s", sane_strstatus(status));
return false;
}
switch (params.format) {
case SANE_FRAME_GRAY:
parameters->format = kGrayscale;
break;
case SANE_FRAME_RGB:
parameters->format = kRGB;
break;
default:
brillo::Error::AddTo(error, FROM_HERE, brillo::errors::dbus::kDomain,
kManagerServiceError,
"Unsupported scan frame format");
return false;
}
parameters->bytes_per_line = params.bytes_per_line;
parameters->pixels_per_line = params.pixels_per_line;
parameters->lines = params.lines;
parameters->depth = params.depth;
return true;
}
bool SaneDeviceImpl::ReadScanData(brillo::ErrorPtr* error,
uint8_t* buf,
size_t count,
size_t* read_out) {
if (!handle_) {
brillo::Error::AddTo(error, FROM_HERE, brillo::errors::dbus::kDomain,
kManagerServiceError, "No scanner connected");
return false;
}
if (!scan_running_) {
brillo::Error::AddTo(error, FROM_HERE, brillo::errors::dbus::kDomain,
kManagerServiceError, "No scan in progress");
return false;
}
if (!buf || !read_out) {
brillo::Error::AddTo(error, FROM_HERE, brillo::errors::dbus::kDomain,
kManagerServiceError,
"'buf' and 'read' pointers cannot be null");
return false;
}
SANE_Int read = 0;
SANE_Status status = sane_read(handle_, buf, count, &read);
switch (status) {
case SANE_STATUS_GOOD:
*read_out = read;
return true;
case SANE_STATUS_EOF:
*read_out = 0;
scan_running_ = false;
// sane_cancel() must always be called once a scan has completed.
sane_cancel(handle_);
return true;
default:
brillo::Error::AddToPrintf(
error, FROM_HERE, brillo::errors::dbus::kDomain, kManagerServiceError,
"sane_read() failed: %s", sane_strstatus(status));
return false;
}
}
bool SaneDeviceImpl::SaneOption::SetInt(int i) {
switch (type) {
case SANE_TYPE_INT:
value.i = i;
return true;
case SANE_TYPE_FIXED:
value.f = SANE_FIX(static_cast<double>(i));
return true;
default:
return false;
}
}
bool SaneDeviceImpl::SaneOption::SetString(const std::string& s) {
if (type != SANE_TYPE_STRING) {
return false;
}
if (value.s)
free(value.s);
value.s = strdup(s.c_str());
if (!value.s)
return false;
return true;
}
SaneDeviceImpl::SaneOption::~SaneOption() {
if (type == SANE_TYPE_STRING)
free(value.s);
}
SaneDeviceImpl::SaneDeviceImpl(SANE_Handle handle,
const std::string& name,
std::shared_ptr<DeviceSet> open_devices)
: handle_(handle),
name_(name),
open_devices_(open_devices),
scan_running_(false) {}
bool SaneDeviceImpl::LoadOptions(brillo::ErrorPtr* error) {
// First we get option descriptor 0, which contains the total count of
// options. We don't strictly need the descriptor, but it's "Good form" to
// do so according to 'scanimage'.
const SANE_Option_Descriptor* desc = sane_get_option_descriptor(handle_, 0);
if (!desc) {
brillo::Error::AddTo(error, FROM_HERE, brillo::errors::dbus::kDomain,
kManagerServiceError,
"Unable to get option count for device");
return false;
}
SANE_Int num_options = 0;
SANE_Status status = sane_control_option(handle_, 0, SANE_ACTION_GET_VALUE,
&num_options, nullptr);
if (status != SANE_STATUS_GOOD) {
brillo::Error::AddTo(error, FROM_HERE, brillo::errors::dbus::kDomain,
kManagerServiceError,
"Unable to get option count for device");
return false;
}
options_.clear();
// Start at 1, since we've already checked option 0 above.
for (int i = 1; i < num_options; i++) {
const SANE_Option_Descriptor* opt = sane_get_option_descriptor(handle_, i);
if (!opt) {
brillo::Error::AddToPrintf(
error, FROM_HERE, brillo::errors::dbus::kDomain, kManagerServiceError,
"Unable to get option %d for device", i);
return false;
}
if ((opt->type == SANE_TYPE_INT || opt->type == SANE_TYPE_FIXED) &&
opt->size == sizeof(SANE_Int) && opt->unit == SANE_UNIT_DPI &&
strcmp(opt->name, SANE_NAME_SCAN_RESOLUTION) == 0) {
options_[kResolution].index = i;
options_[kResolution].type = opt->type;
} else if ((opt->type == SANE_TYPE_STRING) &&
strcmp(opt->name, SANE_NAME_SCAN_MODE) == 0) {
options_[kScanMode].index = i;
options_[kScanMode].type = opt->type;
options_[kScanMode].value.s = NULL;
} else if ((opt->type == SANE_TYPE_STRING) &&
strcmp(opt->name, SANE_NAME_SCAN_SOURCE) == 0) {
options_[kSource].index = i;
options_[kSource].type = opt->type;
options_[kSource].value.s = NULL;
}
}
return true;
}
SANE_Status SaneDeviceImpl::SetOption(SaneOption* option, bool* should_reload) {
void* value;
switch (option->type) {
case SANE_TYPE_INT:
value = &option->value.i;
break;
case SANE_TYPE_FIXED:
value = &option->value.f;
break;
case SANE_TYPE_STRING:
// Do not use '&' here, since SANE_String is already a pointer type.
value = option->value.s;
break;
default:
return SANE_STATUS_UNSUPPORTED;
}
SANE_Int result_flags;
SANE_Status status = sane_control_option(
handle_, option->index, SANE_ACTION_SET_VALUE, value, &result_flags);
if (status != SANE_STATUS_GOOD) {
return status;
}
if (result_flags & SANE_INFO_RELOAD_OPTIONS) {
*should_reload = true;
}
return status;
}
bool SaneDeviceImpl::GetValidStringOptionValues(
brillo::ErrorPtr* error,
ScanOption option,
std::vector<std::string>* values_out) {
if (!values_out)
return false;
if (options_.count(option) == 0) {
return false;
}
int index = options_[option].index;
const SANE_Option_Descriptor* opt =
sane_get_option_descriptor(handle_, index);
if (!opt) {
brillo::Error::AddToPrintf(
error, FROM_HERE, brillo::errors::dbus::kDomain, kManagerServiceError,
"Unable to get option descriptor (%d) for device", index);
return false;
}
if (opt->constraint_type != SANE_CONSTRAINT_STRING_LIST) {
brillo::Error::AddToPrintf(
error, FROM_HERE, brillo::errors::dbus::kDomain, kManagerServiceError,
"Invalid option constraint type %d", opt->constraint_type);
return false;
}
std::vector<std::string> values;
for (int i = 0; opt->constraint.string_list[i]; i++) {
values.push_back(opt->constraint.string_list[i]);
}
*values_out = values;
return true;
}
bool SaneDeviceImpl::GetValidIntOptionValues(
brillo::ErrorPtr* error,
ScanOption option,
std::vector<uint32_t>* values_out) {
if (!values_out)
return false;
if (options_.count(option) == 0) {
return false;
}
int index = options_[option].index;
const SANE_Option_Descriptor* opt =
sane_get_option_descriptor(handle_, index);
if (!opt) {
brillo::Error::AddToPrintf(
error, FROM_HERE, brillo::errors::dbus::kDomain, kManagerServiceError,
"Unable to get option descriptor (%d) for device", index);
return false;
}
if (opt->constraint_type != SANE_CONSTRAINT_WORD_LIST) {
brillo::Error::AddToPrintf(
error, FROM_HERE, brillo::errors::dbus::kDomain, kManagerServiceError,
"Invalid option constraint type %d", opt->constraint_type);
return false;
}
std::vector<uint32_t> values;
int num_values = opt->constraint.word_list[0];
for (int i = 1; i <= num_values; i++) {
SANE_Word w = opt->constraint.word_list[i];
int value = opt->type == SANE_TYPE_FIXED ? SANE_UNFIX(w) : w;
values.push_back(value);
}
*values_out = values;
return true;
}
} // namespace lorgnette