| // 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_, ¶ms); |
| 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 |