blob: d4b587a3686fb70238f60a2ef30f4e26dddb785f [file] [log] [blame] [edit]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "lorgnette/sane_device_impl.h"
#include <optional>
#include <base/check.h>
#include <base/containers/contains.h>
#include <base/containers/flat_map.h>
#include <base/logging.h>
#include <chromeos/dbus/service_constants.h>
#include <re2/re2.h>
#include <sane/saneopts.h>
#include <sane-airscan/airscan.h>
#include "lorgnette/constants.h"
#include "lorgnette/dbus_adaptors/org.chromium.lorgnette.Manager.h"
#include "lorgnette/guess_source.h"
static const char* kRightJustification = "right";
static const char* kCenterJustification = "center";
namespace lorgnette {
namespace {
DocumentSource CreateDocumentSource(const std::string& name) {
DocumentSource source;
source.set_name(name);
std::optional<SourceType> type = GuessSourceType(name);
if (type.has_value()) {
source.set_type(type.value());
}
return source;
}
ColorMode ColorModeFromSaneString(const std::string& mode) {
if (mode == kScanPropertyModeLineart)
return MODE_LINEART;
else if (mode == kScanPropertyModeGray)
return MODE_GRAYSCALE;
else if (mode == kScanPropertyModeColor)
return MODE_COLOR;
return MODE_UNSPECIFIED;
}
} // namespace
SaneDeviceImpl::~SaneDeviceImpl() {
if (handle_) {
// If a scan is running, this will call sane_cancel() first.
libsane_->sane_close(handle_);
}
base::AutoLock auto_lock(open_devices_->first);
open_devices_->second.erase(name_);
}
std::optional<ValidOptionValues> SaneDeviceImpl::GetValidOptionValues(
brillo::ErrorPtr* error) {
if (!handle_) {
brillo::Error::AddTo(error, FROM_HERE, kDbusDomain, kManagerServiceError,
"No scanner connected");
return std::nullopt;
}
ValidOptionValues values;
// TODO(b/179492658): Once the scan app is using the resolutions from
// DocumentSource instead of ScannerCapabilities, remove this logic.
std::optional<std::vector<uint32_t>> resolutions = GetResolutions(error);
if (!resolutions.has_value()) {
return std::nullopt; // brillo::Error::AddTo already called.
}
values.resolutions = std::move(resolutions.value());
if (known_options_.count(kSource) != 0) {
const SaneOption& option = known_options_.at(kSource);
std::optional<std::vector<std::string>> source_names =
option.GetValidStringValues();
if (!source_names.has_value()) {
brillo::Error::AddToPrintf(
error, FROM_HERE, kDbusDomain, kManagerServiceError,
"Failed to get valid values for sources setting from option %s",
option.GetName().c_str());
return std::nullopt;
}
for (const std::string& source_name : source_names.value()) {
values.sources.push_back(CreateDocumentSource(source_name));
}
} else {
// The backend doesn't expose any source options; add a special default
// source using our special source name. We'll calculate the scannable area
// for this default source later.
values.sources.push_back(
CreateDocumentSource(kUnspecifiedDefaultSourceName));
}
if (known_options_.count(kTopLeftX) != 0 &&
known_options_.count(kTopLeftY) != 0 &&
known_options_.count(kBottomRightX) != 0 &&
known_options_.count(kBottomRightY) != 0) {
DCHECK(!values.sources.empty())
<< "Sources is missing default source value.";
// We can get the capabilities for each scan source by setting the
// document source to each possible value, and then calculating the area
// for that source and retrieving the source's supported resolutions and
// color modes.
std::optional<std::string> initial_source = GetDocumentSource(error);
if (!initial_source.has_value()) {
return std::nullopt; // brillo::Error::AddTo already called.
}
for (DocumentSource& source : values.sources) {
LOG(INFO) << __func__
<< ": Loading options for source: " << source.name();
if (!SetDocumentSource(error, source.name())) {
return std::nullopt; // brillo::Error::AddTo already called.
}
std::optional<ScannableArea> area = CalculateScannableArea(error);
if (!area.has_value()) {
return std::nullopt; // brillo::Error::AddTo already called.
}
*source.mutable_area() = std::move(area.value());
std::optional<std::vector<uint32_t>> resolutions = GetResolutions(error);
if (!resolutions.has_value()) {
return std::nullopt; // brillo::Error::AddTo already called.
}
// These values correspond to the values of Chromium's
// ScanJobSettingsResolution enum in
// src/ash/webui/scanning/scanning_uma.h. Before adding values
// here, add them to the ScanJobSettingsResolution enum.
const std::vector<uint32_t> supported_resolutions = {75, 100, 150,
200, 300, 600};
for (const uint32_t resolution : resolutions.value()) {
if (base::Contains(supported_resolutions, resolution)) {
source.add_resolutions(resolution);
}
}
std::optional<std::vector<std::string>> color_modes =
GetColorModes(error);
if (!color_modes.has_value()) {
return std::nullopt; // brillo::Error::AddTo already called.
}
for (const std::string& mode : color_modes.value()) {
const ColorMode color_mode = ColorModeFromSaneString(mode);
if (color_mode != MODE_UNSPECIFIED) {
source.add_color_modes(color_mode);
}
}
}
// Restore DocumentSource to its initial value.
LOG(INFO) << __func__
<< ": Restoring original source: " << initial_source.value();
if (!SetDocumentSource(error, initial_source.value())) {
return std::nullopt; // brillo::Error::AddTo already called.
}
}
// TODO(b/179492658): Once the scan app is using the color modes from
// DocumentSource instead of ScannerCapabilities, remove this logic.
std::optional<std::vector<std::string>> color_modes = GetColorModes(error);
if (!color_modes.has_value()) {
return std::nullopt; // brillo::Error::AddTo already called.
}
values.color_modes = std::move(color_modes.value());
return values;
}
std::optional<ScannerConfig> SaneDeviceImpl::GetCurrentConfig(
brillo::ErrorPtr* error) {
if (!handle_) {
brillo::Error::AddTo(error, FROM_HERE, kDbusDomain, kManagerServiceError,
"No scanner connected");
return std::nullopt;
}
ScannerConfig config;
for (const auto& kv : all_options_) {
std::optional<ScannerOption> option = kv.second.ToScannerOption();
if (!option.has_value()) {
LOG(ERROR) << "Unable to convert option " << kv.second.GetName()
<< " to ScannerOption proto";
brillo::Error::AddToPrintf(
error, FROM_HERE, kDbusDomain, kManagerServiceError,
"Unable to convert option %s to ScannerOption proto",
kv.second.GetName().c_str());
continue;
}
(*config.mutable_options())[kv.first] = std::move(*option);
}
for (const auto& group : option_groups_) {
*config.add_option_groups() = group;
}
return config;
}
std::optional<int> SaneDeviceImpl::GetScanResolution(brillo::ErrorPtr* error) {
return GetOption<int>(error, kResolution);
}
bool SaneDeviceImpl::SetScanResolution(brillo::ErrorPtr* error,
int resolution) {
return SetOption(error, kResolution, resolution);
}
std::optional<std::string> SaneDeviceImpl::GetDocumentSource(
brillo::ErrorPtr* error) {
return GetOption<std::string>(error, kSource);
}
bool SaneDeviceImpl::SetDocumentSource(brillo::ErrorPtr* error,
const std::string& source_name) {
return SetOption(error, kSource, source_name);
}
std::optional<ColorMode> SaneDeviceImpl::GetColorMode(brillo::ErrorPtr* error) {
std::optional<std::string> sane_color_mode =
GetOption<std::string>(error, kScanMode);
if (!sane_color_mode.has_value())
return std::nullopt; // brillo::Error::AddTo already called.
return ColorModeFromSaneString(sane_color_mode.value());
}
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, kDbusDomain,
kManagerServiceError, "Invalid color mode: %s",
ColorMode_Name(color_mode).c_str());
return false;
}
return SetOption(error, kScanMode, mode_string);
}
bool SaneDeviceImpl::SetScanRegion(brillo::ErrorPtr* error,
const ScanRegion& region) {
// If the scanner exposes page-width and page-height options, these need to be
// set before the main scan region coordinates will be accepted.
if (base::Contains(known_options_, kPageWidth)) {
double page_width = region.bottom_right_x() - region.top_left_x();
if (!SetOption(error, kPageWidth, page_width)) {
return false; // brillo::Error::AddTo already called.
}
}
if (base::Contains(known_options_, kPageHeight)) {
double page_height = region.bottom_right_y() - region.top_left_y();
if (!SetOption(error, kPageHeight, page_height)) {
return false; // brillo::Error::AddTo already called.
}
}
// Get the offsets for X and Y so that if the device's coordinate system
// doesn't start at (0, 0), we can translate the requested region into the
// device's coordinates. We provide the appearance to the user that all
// region options start at (0, 0).
std::optional<double> x_offset = GetOptionOffset(error, kTopLeftX);
if (!x_offset.has_value())
return false; // brillo::Error::AddTo already called.
// Get ADF justification offset modification if justification is specified.
std::optional<uint32_t> justification_x_offset =
GetJustificationXOffset(region, error);
if (!justification_x_offset.has_value()) {
return false; // brillo::Error::AddTo already called.
}
x_offset.value() += justification_x_offset.value();
std::optional<double> y_offset = GetOptionOffset(error, kTopLeftY);
if (!y_offset.has_value())
return false; // brillo::Error::AddTo already called.
const base::flat_map<ScanOption, double> values{
{kTopLeftX, region.top_left_x() + x_offset.value()},
{kTopLeftY, region.top_left_y() + y_offset.value()},
{kBottomRightX, region.bottom_right_x() + x_offset.value()},
{kBottomRightY, region.bottom_right_y() + y_offset.value()},
};
for (const auto& kv : values) {
ScanOption option_name = kv.first;
double value = kv.second;
if (!SetOption(error, option_name, value)) {
return false; // brillo::Error::AddTo already called.
}
}
return true;
}
SANE_Status SaneDeviceImpl::StartScan(brillo::ErrorPtr* error) {
if (scan_running_) {
// If we haven't already reached EOF for the current image frame and we
// try to start acquiring a new frame, SANE will fail with an unhelpful
// error. This error message makes it a little clearer what's happening.
brillo::Error::AddTo(error, FROM_HERE, kDbusDomain, kManagerServiceError,
"Scan is already in progress");
return SANE_STATUS_DEVICE_BUSY;
}
SANE_Status status = libsane_->sane_start(handle_);
if (status != SANE_STATUS_GOOD) {
return status;
}
scan_running_ = true;
StartJob();
// Attempt to set non-blocking I/O on the handle. Don't return an error if
// this fails because both cases have to be handled when reading scan data
// anyway.
if (libsane_->sane_set_io_mode(handle_, SANE_TRUE) == SANE_STATUS_GOOD) {
LOG(INFO) << __func__ << ": Set handle to non-blocking I/O";
} else {
LOG(INFO) << __func__ << ": Device does not support non-blocking I/O";
}
return SANE_STATUS_GOOD;
}
SANE_Status SaneDeviceImpl::GetScanParameters(brillo::ErrorPtr* error,
ScanParameters* parameters) {
if (!handle_) {
brillo::Error::AddTo(error, FROM_HERE, kDbusDomain, kManagerServiceError,
"No scanner connected");
return SANE_STATUS_IO_ERROR;
}
if (!parameters) {
brillo::Error::AddTo(error, FROM_HERE, kDbusDomain, kManagerServiceError,
"Non-NULL ScanParameters required");
return SANE_STATUS_INVAL;
}
SANE_Parameters params;
SANE_Status status = libsane_->sane_get_parameters(handle_, &params);
if (status != SANE_STATUS_GOOD) {
brillo::Error::AddToPrintf(
error, FROM_HERE, kDbusDomain, kManagerServiceError,
"Failed to read scan parameters: %s", sane_strstatus(status));
return status;
}
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, kDbusDomain, kManagerServiceError,
"Unsupported scan frame format");
return SANE_STATUS_INVAL;
}
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 SANE_STATUS_GOOD;
}
SANE_Status SaneDeviceImpl::ReadScanData(brillo::ErrorPtr* error,
uint8_t* buf,
size_t count,
size_t* read_out) {
if (!handle_) {
brillo::Error::AddTo(error, FROM_HERE, kDbusDomain, kManagerServiceError,
"No scanner connected");
return SANE_STATUS_INVAL;
}
if (!buf || !read_out) {
brillo::Error::AddTo(error, FROM_HERE, kDbusDomain, kManagerServiceError,
"'buf' and 'read' pointers cannot be null");
return SANE_STATUS_INVAL;
}
SANE_Int read = 0;
SANE_Status status = libsane_->sane_read(handle_, buf, count, &read);
// The SANE API requires that a non GOOD status will return 0 bytes read.
*read_out = read;
if (status != SANE_STATUS_GOOD) {
scan_running_ = false;
if (status == SANE_STATUS_EOF || status == SANE_STATUS_CANCELLED) {
// Stop tracking after a terminal status so the next page can be started
// without calling cancel. Other statuses keep the job open to ensure
// that subsequent scans on the same handle trigger a cleanup.
EndJob();
}
}
return status;
}
bool SaneDeviceImpl::CancelScan(brillo::ErrorPtr* error) {
if (!handle_) {
brillo::Error::AddTo(error, FROM_HERE, kDbusDomain, kManagerServiceError,
"No scanner connected");
return false;
}
scan_running_ = false;
libsane_->sane_cancel(handle_);
EndJob();
return true;
}
SANE_Status SaneDeviceImpl::SetOption(brillo::ErrorPtr* error,
const ScannerOption& option) {
auto kv = all_options_.find(option.name());
if (kv == all_options_.end()) {
LOG(ERROR) << __func__ << ": Didn't find index for option "
<< option.name();
brillo::Error::AddToPrintf(error, FROM_HERE, kDbusDomain,
kManagerServiceError, "Option %s not found",
option.name().c_str());
return SANE_STATUS_UNSUPPORTED;
}
SaneOption& sane_option = kv->second;
if (!sane_option.Set(option)) {
brillo::Error::AddToPrintf(error, FROM_HERE, kDbusDomain,
kManagerServiceError, "Unable to set option %s",
option.name().c_str());
return SANE_STATUS_INVAL;
}
return UpdateDeviceOption(error, &sane_option);
}
SaneDeviceImpl::SaneDeviceImpl(LibsaneWrapper* libsane,
SANE_Handle handle,
const std::string& name,
std::shared_ptr<DeviceSet> open_devices)
: libsane_(libsane),
handle_(handle),
name_(name),
open_devices_(open_devices),
scan_running_(false) {
CHECK(libsane);
CHECK(open_devices);
}
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 =
libsane_->sane_get_option_descriptor(handle_, 0);
if (!desc) {
LOG(ERROR) << __func__ << ": Unable to retrieve option descriptor 0";
brillo::Error::AddTo(error, FROM_HERE, kDbusDomain, kManagerServiceError,
"Unable to get option count descriptor for device");
return false;
}
SANE_Int num_options = 0;
SANE_Status status = libsane_->sane_control_option(
handle_, 0, SANE_ACTION_GET_VALUE, &num_options, nullptr);
if (status != SANE_STATUS_GOOD) {
LOG(ERROR) << __func__ << ": Unable to retrieve value from option 0: "
<< sane_strstatus(status);
brillo::Error::AddToPrintf(
error, FROM_HERE, kDbusDomain, kManagerServiceError,
"Unable to get option count for device: %s", sane_strstatus(status));
return false;
}
LOG(INFO) << __func__ << ": Expected option count: "
<< num_options - 1; // -1 to ignore option 0.
base::flat_map<std::string, ScanOption> region_options = {
{SANE_NAME_SCAN_TL_X, kTopLeftX},
{SANE_NAME_SCAN_TL_Y, kTopLeftY},
{SANE_NAME_SCAN_BR_X, kBottomRightX},
{SANE_NAME_SCAN_BR_Y, kBottomRightY},
{SANE_NAME_PAGE_WIDTH, kPageWidth},
{SANE_NAME_PAGE_HEIGHT, kPageHeight},
};
known_options_.clear();
all_options_.clear();
all_options_.reserve(num_options);
option_groups_.clear();
lorgnette::OptionGroup* current_group = nullptr;
size_t active_options = 0;
size_t inactive_options = 0;
// Start at 1, since we've already checked option 0 above.
for (int i = 1; i < num_options; i++) {
const SANE_Option_Descriptor* opt =
libsane_->sane_get_option_descriptor(handle_, i);
if (!opt) {
LOG(ERROR) << __func__ << ": Unable to get option descriptor " << i;
brillo::Error::AddToPrintf(
error, FROM_HERE, kDbusDomain, kManagerServiceError,
"Unable to get option descriptor %d for device", i);
return false;
}
// Don't track group options in the main option list.
if (opt->type == SANE_TYPE_GROUP) {
lorgnette::OptionGroup& group = option_groups_.emplace_back();
group.set_title(opt->title ? opt->title : "Untitled");
current_group = &group;
continue;
}
// Check for known options used by the simplified API.
std::optional<ScanOption> known_option_name;
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) {
known_option_name = kResolution;
} else if ((opt->type == SANE_TYPE_STRING) &&
strcmp(opt->name, SANE_NAME_SCAN_MODE) == 0) {
known_option_name = kScanMode;
} else if ((opt->type == SANE_TYPE_STRING) &&
strcmp(opt->name, SANE_NAME_SCAN_SOURCE) == 0) {
known_option_name = kSource;
} else if ((opt->type == SANE_TYPE_STRING) &&
strcmp(opt->name, SANE_NAME_ADF_JUSTIFICATION_X) == 0) {
known_option_name = kJustificationX;
} else if ((opt->type == SANE_TYPE_INT || opt->type == SANE_TYPE_FIXED) &&
opt->size == sizeof(SANE_Int)) {
auto enum_value = region_options.find(opt->name);
if (enum_value != region_options.end()) {
// Do not support the case where scan dimensions are specified in
// pixels. Don't stop parsing the option because the advanced API
// still can make use of this case.
if (opt->unit == SANE_UNIT_MM) {
known_option_name = enum_value->second;
} else {
LOG(WARNING) << __func__ << ": Found dimension option " << opt->name
<< " with incompatible unit: " << opt->unit;
}
}
}
// Before retrieving the value, disable options that are known to cause
// problems.
SaneOption sane_option(*opt, i);
if (sane_option.IsIncompatibleWithDevice(name_)) {
sane_option.Disable();
}
// For options that are supposed to have a value, retrieve the value.
if (sane_option.IsActive() && sane_option.GetSize() > 0) {
SANE_Status status = libsane_->sane_control_option(
handle_, i, SANE_ACTION_GET_VALUE, sane_option.GetPointer(), NULL);
if (status != SANE_STATUS_GOOD) {
std::string name = known_option_name.has_value()
? OptionDisplayName(known_option_name.value())
: sane_option.GetName();
LOG(ERROR) << __func__ << ": Unable to read value of option "
<< sane_option.GetName() << " at index " << i << ": "
<< sane_strstatus(status);
brillo::Error::AddToPrintf(
error, FROM_HERE, kDbusDomain, kManagerServiceError,
"Unable to read value of %s option for device", name.c_str());
return false;
}
}
if (sane_option.IsActive()) {
active_options++;
} else {
inactive_options++;
}
// known_options gets a copy of the option, not a pointer to the same
// object. There are fewer than a dozen known options and they don't
// interact directly with all_options, so this duplication shouldn't be a
// problem.
if (known_option_name.has_value()) {
known_options_.insert({known_option_name.value(), sane_option});
}
if (current_group) {
current_group->add_members(sane_option.GetName());
} else {
LOG(WARNING) << __func__ << ": Option " << sane_option.GetName()
<< " is not part of any group";
}
all_options_.emplace(sane_option.GetName(), std::move(sane_option));
}
std::string current_source = GetDocumentSource(nullptr).value_or("Unknown");
LOG(INFO) << __func__ << ": Successfully loaded " << active_options
<< " active and " << inactive_options
<< " inactive device options in " << option_groups_.size()
<< " groups with active source: " << current_source;
return true;
}
SANE_Status SaneDeviceImpl::UpdateDeviceOption(brillo::ErrorPtr* error,
SaneOption* option) {
SANE_Int result_flags;
SANE_Action action = option->GetAction();
SANE_Status status = libsane_->sane_control_option(
handle_, option->GetIndex(), action, option->GetPointer(), &result_flags);
if (status != SANE_STATUS_GOOD) {
brillo::Error::AddToPrintf(
error, FROM_HERE, kDbusDomain, kManagerServiceError,
"Failed to set %s to %s: %s", option->GetName().c_str(),
option->DisplayValue().c_str(), sane_strstatus(status));
// Reload options, to bring local value and device value back in sync.
LoadOptions(error);
return status;
}
// Reload options if they're out of date:
// 1. The backend tells us to reload with SANE_INFO_RELOAD_OPTIONS.
// 2. The backend changed the value and returned SANE_INFO_INEXACT.
// 3. The new value is unknown because automatic setting was requested.
// For cases 2 and 3, we could reload just this option as an optimization,
// but this is not currently implemented.
if ((result_flags & (SANE_INFO_RELOAD_OPTIONS | SANE_INFO_INEXACT)) ||
action == SANE_ACTION_SET_AUTO) {
LoadOptions(error);
}
return SANE_STATUS_GOOD;
}
std::optional<ScannableArea> SaneDeviceImpl::CalculateScannableArea(
brillo::ErrorPtr* error) {
// What we know from the SANE API docs (verbatim):
// * The unit of all four scan region options must be identical
// * A frontend can determine the size of the scan surface by first checking
// that the options have range constraints associated. If a range or
// word-list constraints exist, the frontend can take the minimum and
// maximum values of one of the x and y option range-constraints to
// determine the scan surface size.
//
// Based on my examination of sane-backends, every backend that declares this
// set of options uses a range constraint.
//
// Several backends also have --page-width and --page-height options that
// define the real maximum values. If these are present, they are handled
// automatically in the GetXRange and GetYRange functions.
ScannableArea area;
std::optional<OptionRange> x_range = GetXRange(error);
if (!x_range.has_value()) {
return std::nullopt; // brillo::Error::AddTo already called.
}
area.set_width(x_range.value().size);
std::optional<OptionRange> y_range = GetYRange(error);
if (!y_range.has_value()) {
return std::nullopt; // brillo::Error::AddTo already called.
}
area.set_height(y_range.value().size);
return area;
}
// Calculates the starting value of the range for the given ScanOption.
// Requires that |known_options_| contains |option|, and that the corresponding
// option descriptor for |option| has a range constraint.
std::optional<double> SaneDeviceImpl::GetOptionOffset(
brillo::ErrorPtr* error, SaneDeviceImpl::ScanOption option) {
if (known_options_.count(option) == 0) {
brillo::Error::AddToPrintf(
error, FROM_HERE, kDbusDomain, kManagerServiceError,
"Device is missing option %s", OptionDisplayName(option));
return std::nullopt;
}
const SaneOption& sane_opt = known_options_.at(option);
std::optional<OptionRange> range = sane_opt.GetValidRange();
if (!range.has_value()) {
brillo::Error::AddToPrintf(
error, FROM_HERE, kDbusDomain, kManagerServiceError,
"Failed to get range for option: %s", sane_opt.GetName().c_str());
return std::nullopt;
}
return range->start;
}
const char* SaneDeviceImpl::OptionDisplayName(ScanOption option) {
switch (option) {
case kResolution:
return SANE_NAME_SCAN_RESOLUTION;
case kScanMode:
return SANE_NAME_SCAN_MODE;
case kSource:
return SANE_NAME_SCAN_SOURCE;
case kTopLeftX:
return SANE_NAME_SCAN_TL_X;
case kTopLeftY:
return SANE_NAME_SCAN_TL_Y;
case kBottomRightX:
return SANE_NAME_SCAN_BR_X;
case kBottomRightY:
return SANE_NAME_SCAN_BR_Y;
case kJustificationX:
return SANE_NAME_ADF_JUSTIFICATION_X;
case kPageWidth:
return SANE_NAME_PAGE_WIDTH;
case kPageHeight:
return SANE_NAME_PAGE_HEIGHT;
}
}
template <typename T>
bool SaneDeviceImpl::SetOption(brillo::ErrorPtr* error,
ScanOption option_type,
T value) {
if (known_options_.count(option_type) == 0) {
brillo::Error::AddToPrintf(error, FROM_HERE, kDbusDomain,
kManagerServiceError, "No %s option found.",
OptionDisplayName(option_type));
return false;
}
SaneOption& option = known_options_.at(option_type);
if (!option.Set(value)) {
brillo::Error::AddToPrintf(
error, FROM_HERE, kDbusDomain, kManagerServiceError,
"Failed to set SaneOption %s", OptionDisplayName(option_type));
return false;
}
return UpdateDeviceOption(error, &option) == SANE_STATUS_GOOD;
}
template <typename T>
std::optional<T> SaneDeviceImpl::GetOption(brillo::ErrorPtr* error,
ScanOption option_type) {
if (known_options_.count(option_type) == 0) {
brillo::Error::AddToPrintf(error, FROM_HERE, kDbusDomain,
kManagerServiceError, "No %s option found.",
OptionDisplayName(option_type));
return std::nullopt;
}
const SaneOption& option = known_options_.at(option_type);
std::optional<T> value = option.Get<T>();
if (!value.has_value()) {
brillo::Error::AddTo(error, FROM_HERE, brillo::errors::dbus::kDomain,
kManagerServiceError,
option.GetName() + " is the wrong type");
}
return value;
}
std::optional<std::vector<uint32_t>> SaneDeviceImpl::GetResolutions(
brillo::ErrorPtr* error) {
if (known_options_.count(kResolution) == 0) {
brillo::Error::AddTo(error, FROM_HERE, kDbusDomain, kManagerServiceError,
"No resolutions available");
return std::nullopt;
}
const SaneOption& option = known_options_.at(kResolution);
std::optional<std::vector<uint32_t>> resolutions = option.GetValidIntValues();
if (!resolutions.has_value()) {
brillo::Error::AddToPrintf(
error, FROM_HERE, kDbusDomain, kManagerServiceError,
"Failed to get valid values for resolution setting from %s",
option.GetName().c_str());
return std::nullopt;
}
return resolutions.value();
}
std::optional<std::vector<std::string>> SaneDeviceImpl::GetColorModes(
brillo::ErrorPtr* error) {
if (known_options_.count(kScanMode) == 0) {
brillo::Error::AddTo(error, FROM_HERE, kDbusDomain, kManagerServiceError,
"No color modes available");
return std::nullopt;
}
const SaneOption& option = known_options_.at(kScanMode);
std::optional<std::vector<std::string>> color_modes =
option.GetValidStringValues();
if (!color_modes.has_value()) {
brillo::Error::AddToPrintf(
error, FROM_HERE, kDbusDomain, kManagerServiceError,
"Failed to get valid values for scan modes setting from %s",
option.GetName().c_str());
return std::nullopt;
}
return color_modes.value();
}
std::optional<uint32_t> SaneDeviceImpl::GetJustificationXOffset(
const ScanRegion& region, brillo::ErrorPtr* error) {
// Offset modification only necessary for ADF source at the moment.
std::optional<std::string> current_source = GetDocumentSource(error);
if (!current_source.has_value()) {
return std::nullopt; // brillo::Error::AddTo already called.
}
DocumentSource src = CreateDocumentSource(current_source.value());
if (src.type() != SOURCE_ADF_SIMPLEX && src.type() != SOURCE_ADF_DUPLEX) {
return 0;
}
std::optional<OptionRange> x_range = GetXRange(error);
if (!x_range.has_value()) {
return std::nullopt; // brillo::Error::AddTo already called.
}
std::optional<std::string> x_justification =
GetOption<std::string>(error, kJustificationX);
if (!x_justification.has_value()) {
return 0;
}
int max_width = (x_range.value().size);
int width = region.bottom_right_x() - region.top_left_x();
// Calculate offset based off of Epson-provided math.
uint32_t x_offset = 0;
if (x_justification.value() == kRightJustification) {
x_offset = max_width - width;
} else if (x_justification.value() == kCenterJustification) {
x_offset = (max_width - width) / 2;
}
return x_offset;
}
std::optional<OptionRange> SaneDeviceImpl::GetXRange(brillo::ErrorPtr* error) {
ScanOption which_option;
if (base::Contains(known_options_, kPageWidth)) {
which_option = kPageWidth;
} else {
which_option = kTopLeftX;
}
const SaneOption& option = known_options_.at(which_option);
std::optional<OptionRange> x_range = option.GetValidRange();
if (!x_range.has_value()) {
brillo::Error::AddToPrintf(
error, FROM_HERE, kDbusDomain, kManagerServiceError,
"Invalid top-left X constraint in option %s", option.GetName().c_str());
return std::nullopt;
}
return x_range;
}
std::optional<OptionRange> SaneDeviceImpl::GetYRange(brillo::ErrorPtr* error) {
ScanOption which_option;
if (base::Contains(known_options_, kPageHeight)) {
which_option = kPageHeight;
} else {
which_option = kBottomRightY;
}
const SaneOption& option = known_options_.at(which_option);
std::optional<OptionRange> y_range = option.GetValidRange();
if (!y_range.has_value()) {
brillo::Error::AddToPrintf(error, FROM_HERE, kDbusDomain,
kManagerServiceError,
"Invalid bottom-right Y constraint in option %s",
option.GetName().c_str());
return std::nullopt;
}
return y_range;
}
} // namespace lorgnette