| // 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/containers/flat_map.h> |
| #include <base/logging.h> |
| #include <chromeos/dbus/service_constants.h> |
| #include <sane/saneopts.h> |
| |
| #include "lorgnette/dbus_adaptors/org.chromium.lorgnette.Manager.h" |
| #include "lorgnette/guess_source.h" |
| |
| static const char* kDbusDomain = brillo::errors::dbus::kDomain; |
| |
| namespace lorgnette { |
| |
| namespace { |
| |
| DocumentSource CreateDocumentSource(const std::string& name) { |
| DocumentSource source; |
| source.set_name(name); |
| base::Optional<SourceType> type = GuessSourceType(name); |
| if (type.has_value()) { |
| source.set_type(type.value()); |
| } |
| return source; |
| } |
| |
| } // namespace |
| |
| // 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(); |
| } |
| |
| base::Optional<std::vector<ScannerInfo>> SaneClientImpl::ListDevices( |
| brillo::ErrorPtr* error) { |
| base::AutoLock auto_lock(lock_); |
| const SANE_Device** device_list; |
| LOG(INFO) << "Calling sane_get_devices"; |
| SANE_Status status = sane_get_devices(&device_list, SANE_FALSE); |
| if (status != SANE_STATUS_GOOD) { |
| brillo::Error::AddTo(error, FROM_HERE, kDbusDomain, kManagerServiceError, |
| "Unable to get device list from SANE"); |
| return base::nullopt; |
| } |
| |
| return DeviceListToScannerInfo(device_list); |
| } |
| |
| // static |
| base::Optional<std::vector<ScannerInfo>> |
| SaneClientImpl::DeviceListToScannerInfo(const SANE_Device** device_list) { |
| if (!device_list) { |
| LOG(ERROR) << "'device_list' cannot be NULL"; |
| return base::nullopt; |
| } |
| |
| 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 base::nullopt; |
| } |
| 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); |
| } |
| return scanners; |
| } |
| |
| SaneClientImpl::SaneClientImpl() |
| : open_devices_(std::make_shared<DeviceSet>()) {} |
| |
| std::unique_ptr<SaneDevice> SaneClientImpl::ConnectToDeviceInternal( |
| brillo::ErrorPtr* error, const std::string& device_name) { |
| LOG(INFO) << "Creating connection to device: " << device_name; |
| base::AutoLock auto_lock(lock_); |
| SANE_Handle handle; |
| { |
| LOG(INFO) << "Locking open_devices"; |
| base::AutoLock auto_lock(open_devices_->first); |
| if (open_devices_->second.count(device_name) != 0) { |
| brillo::Error::AddToPrintf( |
| error, FROM_HERE, kDbusDomain, kManagerServiceError, |
| "Device '%s' is currently in-use", device_name.c_str()); |
| return nullptr; |
| } |
| |
| LOG(INFO) << "Calling sane_open"; |
| SANE_Status status = sane_open(device_name.c_str(), &handle); |
| if (status != SANE_STATUS_GOOD) { |
| brillo::Error::AddToPrintf(error, FROM_HERE, kDbusDomain, |
| kManagerServiceError, |
| "Unable to open device '%s': %s", |
| device_name.c_str(), sane_strstatus(status)); |
| return nullptr; |
| } |
| |
| open_devices_->second.insert(device_name); |
| } |
| LOG(INFO) << "SANE handle opened successfully"; |
| |
| // 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_) { |
| // If a scan is running, this will call sane_cancel() first. |
| sane_close(handle_); |
| } |
| base::AutoLock auto_lock(open_devices_->first); |
| open_devices_->second.erase(name_); |
| } |
| |
| base::Optional<ValidOptionValues> SaneDeviceImpl::GetValidOptionValues( |
| brillo::ErrorPtr* error) { |
| if (!handle_) { |
| brillo::Error::AddTo(error, FROM_HERE, kDbusDomain, kManagerServiceError, |
| "No scanner connected"); |
| return base::nullopt; |
| } |
| |
| ValidOptionValues values; |
| if (options_.count(kResolution) != 0) { |
| int index = options_.at(kResolution).GetIndex(); |
| const SANE_Option_Descriptor* descriptor = |
| sane_get_option_descriptor(handle_, index); |
| if (!descriptor) { |
| brillo::Error::AddToPrintf( |
| error, FROM_HERE, kDbusDomain, kManagerServiceError, |
| "Unable to get resolution option at index %d", index); |
| return base::nullopt; |
| } |
| |
| base::Optional<std::vector<uint32_t>> resolutions = |
| GetValidIntOptionValues(error, *descriptor); |
| if (!resolutions.has_value()) { |
| brillo::Error::AddTo(error, FROM_HERE, kDbusDomain, kManagerServiceError, |
| "Failed to get valid values for resolution setting"); |
| return base::nullopt; |
| } |
| values.resolutions = std::move(resolutions.value()); |
| } |
| |
| if (options_.count(kSource) != 0) { |
| int index = options_.at(kSource).GetIndex(); |
| const SANE_Option_Descriptor* descriptor = |
| sane_get_option_descriptor(handle_, index); |
| if (!descriptor) { |
| brillo::Error::AddToPrintf( |
| error, FROM_HERE, kDbusDomain, kManagerServiceError, |
| "Unable to get source option at index %d", index); |
| return base::nullopt; |
| } |
| |
| base::Optional<std::vector<std::string>> source_names = |
| GetValidStringOptionValues(error, *descriptor); |
| if (!source_names.has_value()) { |
| brillo::Error::AddTo(error, FROM_HERE, kDbusDomain, kManagerServiceError, |
| "Failed to get valid values for sources setting"); |
| return base::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 (options_.count(kTopLeftX) != 0 && options_.count(kTopLeftY) != 0 && |
| options_.count(kBottomRightX) != 0 && |
| options_.count(kBottomRightY) != 0) { |
| DCHECK(!values.sources.empty()) |
| << "Sources is missing default source value."; |
| // We can get the scan dimensions for each scan source by setting the |
| // document source to each possible value, and then calculating the area |
| // for that source. |
| base::Optional<std::string> initial_source = GetDocumentSource(error); |
| if (!initial_source.has_value()) { |
| return base::nullopt; |
| } |
| |
| for (DocumentSource& source : values.sources) { |
| if (!SetDocumentSource(error, source.name())) { |
| return base::nullopt; |
| } |
| |
| base::Optional<ScannableArea> area = CalculateScannableArea(error); |
| if (!area.has_value()) { |
| return base::nullopt; |
| } |
| |
| *source.mutable_area() = std::move(area.value()); |
| } |
| |
| // Restore DocumentSource to its initial value. |
| if (!SetDocumentSource(error, initial_source.value())) { |
| return base::nullopt; |
| } |
| } |
| |
| if (options_.count(kScanMode) != 0) { |
| int index = options_.at(kScanMode).GetIndex(); |
| const SANE_Option_Descriptor* descriptor = |
| sane_get_option_descriptor(handle_, index); |
| if (!descriptor) { |
| brillo::Error::AddToPrintf( |
| error, FROM_HERE, kDbusDomain, kManagerServiceError, |
| "Unable to get scan mode option at index %d", index); |
| return base::nullopt; |
| } |
| |
| base::Optional<std::vector<std::string>> color_modes = |
| GetValidStringOptionValues(error, *descriptor); |
| |
| if (!color_modes.has_value()) { |
| brillo::Error::AddTo(error, FROM_HERE, kDbusDomain, kManagerServiceError, |
| "Failed to get valid values for scan modes setting"); |
| return base::nullopt; |
| } |
| values.color_modes = std::move(color_modes.value()); |
| } |
| |
| return values; |
| } |
| |
| base::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); |
| } |
| |
| base::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); |
| } |
| |
| 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) { |
| // 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). |
| base::Optional<double> x_offset = GetOptionOffset(error, kTopLeftX); |
| if (!x_offset.has_value()) |
| return false; |
| |
| base::Optional<double> y_offset = GetOptionOffset(error, kTopLeftY); |
| if (!y_offset.has_value()) |
| return false; |
| |
| 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; |
| } |
| } |
| 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 = sane_start(handle_); |
| if (status == SANE_STATUS_GOOD) { |
| scan_running_ = true; |
| } |
| |
| return status; |
| } |
| |
| base::Optional<ScanParameters> SaneDeviceImpl::GetScanParameters( |
| brillo::ErrorPtr* error) { |
| if (!handle_) { |
| brillo::Error::AddTo(error, FROM_HERE, kDbusDomain, kManagerServiceError, |
| "No scanner connected"); |
| return base::nullopt; |
| } |
| |
| SANE_Parameters params; |
| SANE_Status status = sane_get_parameters(handle_, ¶ms); |
| if (status != SANE_STATUS_GOOD) { |
| brillo::Error::AddToPrintf( |
| error, FROM_HERE, kDbusDomain, kManagerServiceError, |
| "Failed to read scan parameters: %s", sane_strstatus(status)); |
| return base::nullopt; |
| } |
| |
| ScanParameters parameters; |
| 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 base::nullopt; |
| } |
| |
| 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 parameters; |
| } |
| |
| 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 (!scan_running_) { |
| brillo::Error::AddTo(error, FROM_HERE, kDbusDomain, kManagerServiceError, |
| "No scan in progress"); |
| 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 = 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; |
| } |
| 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; |
| sane_cancel(handle_); |
| return true; |
| } |
| |
| // static |
| base::Optional<std::vector<std::string>> |
| SaneDeviceImpl::GetValidStringOptionValues(brillo::ErrorPtr* error, |
| const SANE_Option_Descriptor& opt) { |
| if (opt.constraint_type != SANE_CONSTRAINT_STRING_LIST) { |
| brillo::Error::AddToPrintf( |
| error, FROM_HERE, kDbusDomain, kManagerServiceError, |
| "Invalid option constraint type %d", opt.constraint_type); |
| return base::nullopt; |
| } |
| |
| std::vector<std::string> values; |
| for (int i = 0; opt.constraint.string_list[i]; i++) { |
| values.push_back(opt.constraint.string_list[i]); |
| } |
| |
| return values; |
| } |
| |
| // static |
| base::Optional<std::vector<uint32_t>> SaneDeviceImpl::GetValidIntOptionValues( |
| brillo::ErrorPtr* error, const SANE_Option_Descriptor& opt) { |
| std::vector<uint32_t> values; |
| if (opt.constraint_type == SANE_CONSTRAINT_WORD_LIST) { |
| 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); |
| } |
| } else if (opt.constraint_type == SANE_CONSTRAINT_RANGE) { |
| const SANE_Range* range = opt.constraint.range; |
| for (int i = range->min; i <= range->max; i += range->quant) { |
| const int value = opt.type == SANE_TYPE_FIXED ? SANE_UNFIX(i) : i; |
| values.push_back(value); |
| } |
| } else { |
| brillo::Error::AddToPrintf( |
| error, FROM_HERE, kDbusDomain, kManagerServiceError, |
| "Invalid option constraint type %d", opt.constraint_type); |
| return base::nullopt; |
| } |
| |
| return values; |
| } |
| |
| // static |
| base::Optional<OptionRange> SaneDeviceImpl::GetOptionRange( |
| brillo::ErrorPtr* error, const SANE_Option_Descriptor& opt) { |
| if (opt.constraint_type != SANE_CONSTRAINT_RANGE) { |
| brillo::Error::AddToPrintf( |
| error, FROM_HERE, kDbusDomain, kManagerServiceError, |
| "Expected range constraint for option %s", opt.name); |
| return base::nullopt; |
| } |
| |
| OptionRange option_range; |
| const SANE_Range* range = opt.constraint.range; |
| switch (opt.type) { |
| case SANE_TYPE_INT: |
| option_range.start = range->min; |
| option_range.size = range->max - range->min; |
| return option_range; |
| case SANE_TYPE_FIXED: |
| option_range.start = SANE_UNFIX(range->min); |
| option_range.size = SANE_UNFIX(range->max - range->min); |
| return option_range; |
| default: |
| brillo::Error::AddToPrintf( |
| error, FROM_HERE, kDbusDomain, kManagerServiceError, |
| "Unexpected option type %d for option %s", opt.type, opt.name); |
| return base::nullopt; |
| } |
| } |
| |
| SaneOption::SaneOption(const SANE_Option_Descriptor& opt, int index) { |
| name_ = opt.name; |
| index_ = index; |
| type_ = opt.type; |
| if (type_ == SANE_TYPE_STRING) { |
| // opt.size is the maximum size of the string option, including the null |
| // terminator (which is mandatory). |
| string_data_.resize(opt.size); |
| } |
| } |
| |
| bool SaneOption::Set(int i) { |
| switch (type_) { |
| case SANE_TYPE_INT: |
| int_data_.i = i; |
| return true; |
| case SANE_TYPE_FIXED: |
| int_data_.f = SANE_FIX(static_cast<double>(i)); |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool SaneOption::Set(double d) { |
| switch (type_) { |
| case SANE_TYPE_INT: |
| int_data_.i = static_cast<int>(d); |
| return true; |
| case SANE_TYPE_FIXED: |
| int_data_.f = SANE_FIX(d); |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool SaneOption::Set(const std::string& s) { |
| if (type_ != SANE_TYPE_STRING) { |
| return false; |
| } |
| |
| size_t size_with_null = s.size() + 1; |
| if (size_with_null > string_data_.size()) { |
| LOG(ERROR) << "String size " << size_with_null |
| << " exceeds maximum option size " << string_data_.size(); |
| return false; |
| } |
| |
| memcpy(string_data_.data(), s.c_str(), size_with_null); |
| return true; |
| } |
| |
| template <> |
| base::Optional<int> SaneOption::Get() const { |
| switch (type_) { |
| case SANE_TYPE_INT: |
| return int_data_.i; |
| case SANE_TYPE_FIXED: |
| return static_cast<int>(SANE_UNFIX(int_data_.f)); |
| default: |
| return base::nullopt; |
| } |
| } |
| |
| template <> |
| base::Optional<std::string> SaneOption::Get() const { |
| if (type_ != SANE_TYPE_STRING) |
| return base::nullopt; |
| |
| return std::string(string_data_.data()); |
| } |
| |
| void* SaneOption::GetPointer() { |
| if (type_ == SANE_TYPE_STRING) |
| return string_data_.data(); |
| else if (type_ == SANE_TYPE_INT) |
| return &int_data_.i; |
| else if (type_ == SANE_TYPE_FIXED) |
| return &int_data_.f; |
| else |
| return nullptr; |
| } |
| |
| int SaneOption::GetIndex() const { |
| return index_; |
| } |
| |
| std::string SaneOption::GetName() const { |
| return name_; |
| } |
| |
| std::string SaneOption::DisplayValue() const { |
| switch (type_) { |
| case SANE_TYPE_INT: |
| return std::to_string(int_data_.i); |
| case SANE_TYPE_FIXED: |
| return std::to_string(static_cast<int>(SANE_UNFIX(int_data_.f))); |
| case SANE_TYPE_STRING: |
| return Get<std::string>().value(); |
| default: |
| return "[invalid]"; |
| } |
| } |
| |
| 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) { |
| LOG(INFO) << "Loading device options"; |
| // 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, kDbusDomain, 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, kDbusDomain, kManagerServiceError, |
| "Unable to get option count for device"); |
| return false; |
| } |
| |
| 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}, |
| }; |
| |
| 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, kDbusDomain, |
| kManagerServiceError, |
| "Unable to get option %d for device", i); |
| return false; |
| } |
| |
| base::Optional<ScanOption> 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) { |
| option_name = kResolution; |
| } else if ((opt->type == SANE_TYPE_STRING) && |
| strcmp(opt->name, SANE_NAME_SCAN_MODE) == 0) { |
| option_name = kScanMode; |
| } else if ((opt->type == SANE_TYPE_STRING) && |
| strcmp(opt->name, SANE_NAME_SCAN_SOURCE) == 0) { |
| option_name = kSource; |
| } 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. |
| if (opt->unit != SANE_UNIT_MM) { |
| LOG(WARNING) << "Found dimension option " << opt->name |
| << " with incompatible unit: " << opt->unit; |
| continue; |
| } |
| option_name = enum_value->second; |
| } |
| } |
| |
| if (option_name.has_value()) { |
| SaneOption sane_option(*opt, i); |
| SANE_Status status = sane_control_option( |
| handle_, i, SANE_ACTION_GET_VALUE, sane_option.GetPointer(), NULL); |
| if (status != SANE_STATUS_GOOD) { |
| brillo::Error::AddToPrintf( |
| error, FROM_HERE, kDbusDomain, kManagerServiceError, |
| "Unable to read value of %s option for device", |
| OptionDisplayName(option_name.value())); |
| return false; |
| } |
| options_.insert({option_name.value(), std::move(sane_option)}); |
| } |
| } |
| |
| LOG(INFO) << "Device options loaded successfully"; |
| return true; |
| } |
| |
| bool SaneDeviceImpl::UpdateDeviceOption(brillo::ErrorPtr* error, |
| SaneOption* option) { |
| SANE_Int result_flags; |
| SANE_Status status = |
| sane_control_option(handle_, option->GetIndex(), SANE_ACTION_SET_VALUE, |
| option->GetPointer(), &result_flags); |
| if (status != SANE_STATUS_GOOD) { |
| brillo::Error::AddTo(error, FROM_HERE, kDbusDomain, kManagerServiceError, |
| "Unable to set " + option->GetName() + " to " + |
| option->DisplayValue() + " : " + |
| sane_strstatus(status)); |
| // Reload options, to bring local value and device value back in sync. |
| LoadOptions(error); |
| return false; |
| } |
| |
| // We also reload if we get SANE_INFO_INEXACT because we want to know |
| // what value the printer changed our requested value to. |
| // As an optimization, we could only reload this particular option. |
| if (result_flags & (SANE_INFO_RELOAD_OPTIONS | SANE_INFO_INEXACT)) { |
| return LoadOptions(error); |
| } |
| |
| return true; |
| } |
| |
| base::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. |
| ScannableArea area; |
| int index = options_.at(kTopLeftX).GetIndex(); |
| const SANE_Option_Descriptor* descriptor = |
| sane_get_option_descriptor(handle_, index); |
| if (!descriptor) { |
| brillo::Error::AddToPrintf( |
| error, FROM_HERE, kDbusDomain, kManagerServiceError, |
| "Unable to get top-left X option at index %d", index); |
| return base::nullopt; |
| } |
| |
| base::Optional<OptionRange> x_range = GetOptionRange(error, *descriptor); |
| if (!x_range.has_value()) { |
| return base::nullopt; |
| } |
| area.set_width(x_range.value().size); |
| |
| index = options_.at(kBottomRightY).GetIndex(); |
| descriptor = sane_get_option_descriptor(handle_, index); |
| if (!descriptor) { |
| brillo::Error::AddToPrintf( |
| error, FROM_HERE, kDbusDomain, kManagerServiceError, |
| "Unable to get bottom-right Y option at index %d", index); |
| return base::nullopt; |
| } |
| |
| base::Optional<OptionRange> y_range = GetOptionRange(error, *descriptor); |
| if (!y_range.has_value()) { |
| return base::nullopt; |
| } |
| area.set_height(y_range.value().size); |
| return area; |
| } |
| |
| // Calculates the starting value of the range for the given ScanOption. |
| // Requires that |options_| contains |option|, and that the corresponding |
| // option descriptor for |option| has a range constraint. |
| base::Optional<double> SaneDeviceImpl::GetOptionOffset( |
| brillo::ErrorPtr* error, SaneDeviceImpl::ScanOption option) { |
| if (options_.count(option) == 0) { |
| brillo::Error::AddToPrintf( |
| error, FROM_HERE, kDbusDomain, kManagerServiceError, |
| "Device is missing option %s", OptionDisplayName(option)); |
| return base::nullopt; |
| } |
| |
| int index = options_.at(option).GetIndex(); |
| const SANE_Option_Descriptor* descriptor = |
| sane_get_option_descriptor(handle_, index); |
| if (!descriptor) { |
| brillo::Error::AddToPrintf(error, FROM_HERE, kDbusDomain, |
| kManagerServiceError, |
| "Unable to get option %s at index %d", |
| OptionDisplayName(option), index); |
| return base::nullopt; |
| } |
| |
| base::Optional<OptionRange> range = GetOptionRange(error, *descriptor); |
| if (!range.has_value()) { |
| brillo::Error::AddToPrintf( |
| error, FROM_HERE, kDbusDomain, kManagerServiceError, |
| "Failed to get range for %s option.", descriptor->name); |
| return base::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; |
| } |
| } |
| |
| template <typename T> |
| bool SaneDeviceImpl::SetOption(brillo::ErrorPtr* error, |
| ScanOption option_type, |
| T value) { |
| if (options_.count(option_type) == 0) { |
| brillo::Error::AddToPrintf(error, FROM_HERE, kDbusDomain, |
| kManagerServiceError, "No %s option found.", |
| OptionDisplayName(option_type)); |
| return false; |
| } |
| |
| SaneOption& option = 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); |
| } |
| |
| template <typename T> |
| base::Optional<T> SaneDeviceImpl::GetOption(brillo::ErrorPtr* error, |
| ScanOption option_type) { |
| if (options_.count(option_type) == 0) { |
| brillo::Error::AddToPrintf(error, FROM_HERE, kDbusDomain, |
| kManagerServiceError, "No %s option found.", |
| OptionDisplayName(option_type)); |
| return base::nullopt; |
| } |
| |
| const SaneOption& option = options_.at(option_type); |
| base::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; |
| } |
| |
| } // namespace lorgnette |