| // 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 <algorithm> |
| #include <iostream> |
| #include <string> |
| #include <vector> |
| |
| #include <base/callback.h> |
| #include <base/command_line.h> |
| #include <base/files/file.h> |
| #include <base/files/file_descriptor_watcher_posix.h> |
| #include <base/memory/ref_counted.h> |
| #include <base/memory/weak_ptr.h> |
| #include <base/optional.h> |
| #include <base/run_loop.h> |
| #include <base/strings/stringprintf.h> |
| #include <base/strings/string_util.h> |
| #include <base/strings/string_split.h> |
| #include <base/synchronization/condition_variable.h> |
| #include <base/synchronization/lock.h> |
| #include <base/task/single_thread_task_executor.h> |
| #include <brillo/errors/error.h> |
| #include <brillo/flag_helper.h> |
| #include <brillo/process/process.h> |
| #include <brillo/syslog_logging.h> |
| #include <chromeos/dbus/service_constants.h> |
| #include <dbus/bus.h> |
| #include <lorgnette/proto_bindings/lorgnette_service.pb.h> |
| #include <re2/re2.h> |
| |
| #include "lorgnette/dbus-proxies.h" |
| #include "lorgnette/guess_source.h" |
| |
| using org::chromium::lorgnette::ManagerProxy; |
| |
| namespace { |
| |
| base::Optional<std::vector<std::string>> ReadLines(base::File* file) { |
| std::string buf(1 << 20, '\0'); |
| int read = file->ReadAtCurrentPos(&buf[0], buf.size()); |
| if (read < 0) { |
| PLOG(ERROR) << "Reading from file failed"; |
| return base::nullopt; |
| } |
| |
| buf.resize(read); |
| return base::SplitString(buf, "\n", base::KEEP_WHITESPACE, |
| base::SPLIT_WANT_ALL); |
| } |
| |
| std::string EscapeScannerName(const std::string& scanner_name) { |
| std::string escaped; |
| for (char c : scanner_name) { |
| if (isalnum(c)) { |
| escaped += c; |
| } else { |
| escaped += '_'; |
| } |
| } |
| return escaped; |
| } |
| |
| base::Optional<lorgnette::CancelScanResponse> CancelScan( |
| ManagerProxy* manager, const std::string& uuid) { |
| lorgnette::CancelScanRequest request; |
| request.set_scan_uuid(uuid); |
| std::vector<uint8_t> request_in(request.ByteSizeLong()); |
| request.SerializeToArray(request_in.data(), request_in.size()); |
| |
| brillo::ErrorPtr error; |
| std::vector<uint8_t> response_out; |
| if (!manager->CancelScan(request_in, &response_out, &error)) { |
| LOG(ERROR) << "Cancelling scan failed: " << error->GetMessage(); |
| return base::nullopt; |
| } |
| |
| lorgnette::CancelScanResponse response; |
| if (!response.ParseFromArray(response_out.data(), response_out.size())) { |
| LOG(ERROR) << "Failed to parse CancelScanResponse"; |
| return base::nullopt; |
| } |
| |
| return response; |
| } |
| |
| class ScanHandler { |
| public: |
| ScanHandler(base::RepeatingClosure quit_closure, |
| ManagerProxy* manager, |
| std::string scanner_name) |
| : cvar_(&lock_), |
| quit_closure_(quit_closure), |
| manager_(manager), |
| scanner_name_(scanner_name), |
| base_output_path_("/tmp/scan-" + EscapeScannerName(scanner_name) + |
| ".png"), |
| current_page_(1), |
| connected_callback_called_(false), |
| connection_status_(false) { |
| manager_->RegisterScanStatusChangedSignalHandler( |
| base::BindRepeating(&ScanHandler::HandleScanStatusChangedSignal, |
| weak_factory_.GetWeakPtr()), |
| base::BindOnce(&ScanHandler::OnConnectedCallback, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| bool WaitUntilConnected(); |
| |
| bool StartScan(uint32_t resolution, |
| const lorgnette::DocumentSource& scan_source, |
| const base::Optional<lorgnette::ScanRegion>& scan_region, |
| lorgnette::ColorMode color_mode); |
| |
| private: |
| void HandleScanStatusChangedSignal( |
| const std::vector<uint8_t>& signal_serialized); |
| |
| void OnConnectedCallback(const std::string& interface_name, |
| const std::string& signal_name, |
| bool signal_connected); |
| |
| void RequestNextPage(); |
| base::Optional<lorgnette::GetNextImageResponse> GetNextImage( |
| const base::FilePath& output_path); |
| |
| base::Lock lock_; |
| base::ConditionVariable cvar_; |
| base::RepeatingClosure quit_closure_; |
| |
| ManagerProxy* manager_; // Not owned. |
| std::string scanner_name_; |
| base::FilePath base_output_path_; |
| base::Optional<std::string> scan_uuid_; |
| int current_page_; |
| |
| bool connected_callback_called_; |
| bool connection_status_; |
| |
| base::WeakPtrFactory<ScanHandler> weak_factory_{this}; |
| }; |
| |
| bool ScanHandler::WaitUntilConnected() { |
| base::AutoLock auto_lock(lock_); |
| while (!connected_callback_called_) { |
| cvar_.Wait(); |
| } |
| return connection_status_; |
| } |
| |
| bool ScanHandler::StartScan( |
| uint32_t resolution, |
| const lorgnette::DocumentSource& scan_source, |
| const base::Optional<lorgnette::ScanRegion>& scan_region, |
| lorgnette::ColorMode color_mode) { |
| lorgnette::StartScanRequest request; |
| request.set_device_name(scanner_name_); |
| request.mutable_settings()->set_resolution(resolution); |
| request.mutable_settings()->set_source_name(scan_source.name()); |
| request.mutable_settings()->set_color_mode(color_mode); |
| if (scan_region.has_value()) |
| *request.mutable_settings()->mutable_scan_region() = scan_region.value(); |
| |
| std::vector<uint8_t> request_in(request.ByteSizeLong()); |
| request.SerializeToArray(request_in.data(), request_in.size()); |
| |
| brillo::ErrorPtr error; |
| std::vector<uint8_t> response_out; |
| if (!manager_->StartScan(request_in, &response_out, &error)) { |
| LOG(ERROR) << "StartScan failed: " << error->GetMessage(); |
| return false; |
| } |
| |
| lorgnette::StartScanResponse response; |
| if (!response.ParseFromArray(response_out.data(), response_out.size())) { |
| LOG(ERROR) << "Failed to parse StartScanResponse"; |
| return false; |
| } |
| |
| if (response.state() == lorgnette::SCAN_STATE_FAILED) { |
| LOG(ERROR) << "StartScan failed: " << response.failure_reason(); |
| return false; |
| } |
| |
| std::cout << "Scan " << response.scan_uuid() << " started successfully" |
| << std::endl; |
| scan_uuid_ = response.scan_uuid(); |
| |
| RequestNextPage(); |
| return true; |
| } |
| |
| void ScanHandler::HandleScanStatusChangedSignal( |
| const std::vector<uint8_t>& signal_serialized) { |
| if (!scan_uuid_.has_value()) { |
| return; |
| } |
| |
| lorgnette::ScanStatusChangedSignal signal; |
| if (!signal.ParseFromArray(signal_serialized.data(), |
| signal_serialized.size())) { |
| LOG(ERROR) << "Failed to parse ScanStatusSignal"; |
| return; |
| } |
| |
| if (signal.state() == lorgnette::SCAN_STATE_IN_PROGRESS) { |
| std::cout << "Page " << signal.page() << " is " << signal.progress() |
| << "% finished" << std::endl; |
| } else if (signal.state() == lorgnette::SCAN_STATE_FAILED) { |
| LOG(ERROR) << "Scan failed: " << signal.failure_reason(); |
| quit_closure_.Run(); |
| } else if (signal.state() == lorgnette::SCAN_STATE_PAGE_COMPLETED) { |
| std::cout << "Page " << signal.page() << " completed." << std::endl; |
| current_page_ += 1; |
| if (signal.more_pages()) |
| RequestNextPage(); |
| } else if (signal.state() == lorgnette::SCAN_STATE_COMPLETED) { |
| std::cout << "Scan completed successfully." << std::endl; |
| quit_closure_.Run(); |
| } else if (signal.state() == lorgnette::SCAN_STATE_CANCELLED) { |
| std::cout << "Scan cancelled." << std::endl; |
| quit_closure_.Run(); |
| } |
| } |
| |
| void ScanHandler::OnConnectedCallback(const std::string& interface_name, |
| const std::string& signal_name, |
| bool signal_connected) { |
| base::AutoLock auto_lock(lock_); |
| connected_callback_called_ = true; |
| connection_status_ = signal_connected; |
| if (!signal_connected) { |
| LOG(ERROR) << "Failed to connect to ScanStatusChanged signal"; |
| } |
| cvar_.Signal(); |
| } |
| |
| base::Optional<lorgnette::GetNextImageResponse> ScanHandler::GetNextImage( |
| const base::FilePath& output_path) { |
| lorgnette::GetNextImageRequest request; |
| request.set_scan_uuid(scan_uuid_.value()); |
| std::vector<uint8_t> request_in(request.ByteSizeLong()); |
| request.SerializeToArray(request_in.data(), request_in.size()); |
| |
| base::File output_file( |
| output_path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); |
| |
| if (!output_file.IsValid()) { |
| PLOG(ERROR) << "Failed to open output file " << output_path; |
| return base::nullopt; |
| } |
| |
| brillo::ErrorPtr error; |
| std::vector<uint8_t> response_out; |
| if (!manager_->GetNextImage(request_in, output_file.GetPlatformFile(), |
| &response_out, &error)) { |
| LOG(ERROR) << "GetNextImage failed: " << error->GetMessage(); |
| return base::nullopt; |
| } |
| |
| lorgnette::GetNextImageResponse response; |
| if (!response.ParseFromArray(response_out.data(), response_out.size())) { |
| LOG(ERROR) << "Failed to parse StartScanResponse"; |
| return base::nullopt; |
| } |
| |
| return response; |
| } |
| |
| void ScanHandler::RequestNextPage() { |
| base::FilePath output_path = base_output_path_.InsertBeforeExtension( |
| base::StringPrintf("_page%d", current_page_)); |
| |
| base::Optional<lorgnette::GetNextImageResponse> response = |
| GetNextImage(output_path); |
| if (!response.has_value()) { |
| quit_closure_.Run(); |
| } |
| |
| if (!response.value().success()) { |
| LOG(ERROR) << "Requesting next page failed: " |
| << response.value().failure_reason(); |
| quit_closure_.Run(); |
| } else { |
| std::cout << "Reading page " << current_page_ << " to " |
| << output_path.value() << std::endl; |
| } |
| } |
| |
| base::Optional<std::vector<std::string>> ListScanners(ManagerProxy* manager) { |
| brillo::ErrorPtr error; |
| std::vector<uint8_t> out_scanner_list; |
| if (!manager->ListScanners(&out_scanner_list, &error)) { |
| LOG(ERROR) << "ListScanners failed: " << error->GetMessage(); |
| return base::nullopt; |
| } |
| |
| lorgnette::ListScannersResponse scanner_list; |
| if (!scanner_list.ParseFromArray(out_scanner_list.data(), |
| out_scanner_list.size())) { |
| LOG(ERROR) << "Failed to parse ListScanners response"; |
| return base::nullopt; |
| } |
| |
| std::vector<std::string> scanner_names; |
| std::cout << "SANE scanners: " << std::endl; |
| for (const lorgnette::ScannerInfo& scanner : scanner_list.scanners()) { |
| std::cout << scanner.name() << ": " << scanner.manufacturer() << " " |
| << scanner.model() << "(" << scanner.type() << ")" << std::endl; |
| scanner_names.push_back(scanner.name()); |
| } |
| std::cout << scanner_list.scanners_size() << " SANE scanners found." |
| << std::endl; |
| return scanner_names; |
| } |
| |
| base::Optional<lorgnette::ScannerCapabilities> GetScannerCapabilities( |
| ManagerProxy* manager, const std::string& scanner_name) { |
| brillo::ErrorPtr error; |
| std::vector<uint8_t> serialized; |
| if (!manager->GetScannerCapabilities(scanner_name, &serialized, &error)) { |
| LOG(ERROR) << "GetScannerCapabilities failed: " << error->GetMessage(); |
| return base::nullopt; |
| } |
| |
| lorgnette::ScannerCapabilities capabilities; |
| if (!capabilities.ParseFromArray(serialized.data(), serialized.size())) { |
| LOG(ERROR) << "Failed to parse ScannerCapabilities response"; |
| return base::nullopt; |
| } |
| return capabilities; |
| } |
| |
| void PrintScannerCapabilities( |
| const lorgnette::ScannerCapabilities& capabilities) { |
| std::cout << "--- Capabilities ---" << std::endl; |
| std::cout << "Resolutions:" << std::endl; |
| for (uint32_t resolution : capabilities.resolutions()) { |
| std::cout << "\t" << resolution << std::endl; |
| } |
| |
| std::cout << "Sources:" << std::endl; |
| for (const lorgnette::DocumentSource& source : capabilities.sources()) { |
| std::cout << "\t" << source.name() << " (" |
| << lorgnette::SourceType_Name(source.type()) << ")" << std::endl; |
| if (source.has_area()) { |
| std::cout << "\t\t" << source.area().width() << "mm wide by " |
| << source.area().height() << "mm tall" << std::endl; |
| } |
| } |
| |
| std::cout << "Color Modes:" << std::endl; |
| for (int color_mode : capabilities.color_modes()) { |
| std::cout << "\t" << lorgnette::ColorMode_Name(color_mode) << std::endl; |
| } |
| } |
| |
| base::Optional<std::vector<std::string>> ReadAirscanOutput( |
| brillo::ProcessImpl* discover) { |
| base::File discover_output(discover->GetPipe(STDOUT_FILENO)); |
| if (!discover_output.IsValid()) { |
| LOG(ERROR) << "Failed to open airscan-discover output pipe"; |
| return base::nullopt; |
| } |
| |
| int ret = discover->Wait(); |
| if (ret != 0) { |
| LOG(ERROR) << "airscan-discover exited with error " << ret; |
| return base::nullopt; |
| } |
| |
| base::Optional<std::vector<std::string>> lines = ReadLines(&discover_output); |
| if (!lines.has_value()) { |
| LOG(ERROR) << "Failed to read output from airscan-discover"; |
| return base::nullopt; |
| } |
| |
| std::vector<std::string> scanner_names; |
| for (const std::string& line : lines.value()) { |
| // Line format is something like: |
| // " Lexmark MB2236adwe = https://192.168.0.15:443/eSCL/, eSCL" |
| // We use '.*\S' to match the device name instead of '\S+' so that we can |
| // properly match internal spaces. Since the regex is greedy by default, |
| // we need to end the match group with '\S' so that it doesn't capture any |
| // trailing white-space. |
| std::string name, url; |
| if (RE2::FullMatch(line, R"(\s*(.*\S)\s+=\s+(.+), eSCL)", &name, &url)) { |
| // Replace ':' with '_' because sane-airscan uses ':' to delimit the |
| // fields of the device_string (i.e."airscan:escl:MyPrinter:[url]) passed |
| // to it. |
| base::ReplaceChars(name, ":", "_", &name); |
| scanner_names.push_back("airscan:escl:" + name + ":" + url); |
| } |
| } |
| |
| return scanner_names; |
| } |
| |
| class ScanRunner { |
| public: |
| explicit ScanRunner(ManagerProxy* manager) : manager_(manager) {} |
| |
| void SetResolution(uint32_t resolution) { resolution_ = resolution; } |
| void SetSource(lorgnette::SourceType source) { source_ = source; } |
| void SetScanRegion(const lorgnette::ScanRegion& region) { region_ = region; } |
| void SetColorMode(lorgnette::ColorMode color_mode) { |
| color_mode_ = color_mode; |
| } |
| |
| bool RunScanner(const std::string& scanner); |
| |
| private: |
| ManagerProxy* manager_; // Not owned. |
| uint32_t resolution_; |
| lorgnette::SourceType source_; |
| base::Optional<lorgnette::ScanRegion> region_; |
| lorgnette::ColorMode color_mode_; |
| }; |
| |
| bool ScanRunner::RunScanner(const std::string& scanner) { |
| std::cout << "Getting device capabilities for " << scanner << std::endl; |
| base::Optional<lorgnette::ScannerCapabilities> capabilities = |
| GetScannerCapabilities(manager_, scanner); |
| if (!capabilities.has_value()) |
| return false; |
| PrintScannerCapabilities(capabilities.value()); |
| |
| if (!base::Contains(capabilities->resolutions(), resolution_)) { |
| // Many scanners will round the requested resolution to the nearest |
| // supported resolution. We will attempt to scan with the given resolution |
| // since it may still work. |
| LOG(WARNING) << "Requested scan resolution " << resolution_ |
| << " is not supported by the selected scanner. " |
| "Attempting to request it anyways."; |
| } |
| |
| base::Optional<lorgnette::DocumentSource> scan_source; |
| for (const lorgnette::DocumentSource& source : capabilities->sources()) { |
| if (source.type() == source_) { |
| scan_source = source; |
| break; |
| } |
| } |
| |
| if (!scan_source.has_value()) { |
| LOG(ERROR) << "Requested scan source " |
| << lorgnette::SourceType_Name(source_) |
| << " is not supported by the selected scanner"; |
| return false; |
| } |
| |
| if (region_.has_value()) { |
| if (!scan_source->has_area()) { |
| LOG(ERROR) |
| << "Requested scan source does not support specifying a scan region."; |
| return false; |
| } |
| |
| if (region_->top_left_x() == -1.0) |
| region_->set_top_left_x(0.0); |
| if (region_->top_left_y() == -1.0) |
| region_->set_top_left_y(0.0); |
| if (region_->bottom_right_x() == -1.0) |
| region_->set_bottom_right_x(scan_source->area().width()); |
| if (region_->bottom_right_y() == -1.0) |
| region_->set_bottom_right_y(scan_source->area().height()); |
| } |
| |
| if (!base::Contains(capabilities->color_modes(), color_mode_)) { |
| LOG(ERROR) << "Requested scan source does not support color mode " |
| << ColorMode_Name(color_mode_); |
| return false; |
| } |
| |
| // Implicitly uses this thread's executor as defined in main. |
| base::RunLoop run_loop; |
| ScanHandler handler(run_loop.QuitClosure(), manager_, scanner); |
| |
| if (!handler.WaitUntilConnected()) { |
| return false; |
| } |
| |
| std::cout << "Scanning from " << scanner << std::endl; |
| |
| if (!handler.StartScan(resolution_, scan_source.value(), region_, |
| color_mode_)) { |
| return false; |
| } |
| |
| // Will run until the ScanHandler runs this RunLoop's quit_closure. |
| run_loop.Run(); |
| return true; |
| } |
| |
| bool DoScan(std::unique_ptr<ManagerProxy> manager, |
| uint32_t scan_resolution, |
| lorgnette::SourceType source_type, |
| const lorgnette::ScanRegion& region, |
| lorgnette::ColorMode color_mode, |
| bool scan_from_all_scanners) { |
| // Start the airscan-discover process immediately since it can be slightly |
| // long-running. We read the output later after we've gotten a scanner list |
| // from lorgnette. |
| brillo::ProcessImpl discover; |
| discover.AddArg("/usr/bin/airscan-discover"); |
| discover.RedirectUsingPipe(STDOUT_FILENO, false); |
| if (!discover.Start()) { |
| LOG(ERROR) << "Failed to start airscan-discover process"; |
| return false; |
| } |
| |
| std::cout << "Getting scanner list." << std::endl; |
| base::Optional<std::vector<std::string>> sane_scanners = |
| ListScanners(manager.get()); |
| if (!sane_scanners.has_value()) |
| return false; |
| |
| base::Optional<std::vector<std::string>> airscan_scanners = |
| ReadAirscanOutput(&discover); |
| if (!airscan_scanners.has_value()) |
| return false; |
| |
| std::vector<std::string> scanners = std::move(sane_scanners.value()); |
| scanners.insert(scanners.end(), airscan_scanners.value().begin(), |
| airscan_scanners.value().end()); |
| |
| ScanRunner runner(manager.get()); |
| runner.SetResolution(scan_resolution); |
| runner.SetSource(source_type); |
| runner.SetColorMode(color_mode); |
| |
| if (region.top_left_x() != -1.0 || region.top_left_y() != -1.0 || |
| region.bottom_right_x() != -1.0 || region.bottom_right_y() != -1.0) { |
| runner.SetScanRegion(region); |
| } |
| |
| std::cout << "Choose a scanner (blank to quit):" << std::endl; |
| for (int i = 0; i < scanners.size(); i++) { |
| std::cout << i << ". " << scanners[i] << std::endl; |
| } |
| |
| if (!scan_from_all_scanners) { |
| int index = -1; |
| std::cout << "> "; |
| std::cin >> index; |
| if (std::cin.fail()) { |
| return 0; |
| } |
| |
| std::string scanner = scanners[index]; |
| return !runner.RunScanner(scanner); |
| } |
| |
| std::cout << "Scanning from all scanners." << std::endl; |
| std::vector<std::string> successes; |
| std::vector<std::string> failures; |
| for (const std::string& scanner : scanners) { |
| if (runner.RunScanner(scanner)) { |
| successes.push_back(scanner); |
| } else { |
| failures.push_back(scanner); |
| } |
| } |
| std::cout << "Successful scans:" << std::endl; |
| for (const std::string& scanner : successes) { |
| std::cout << " " << scanner << std::endl; |
| } |
| std::cout << "Failed scans:" << std::endl; |
| for (const std::string& scanner : failures) { |
| std::cout << " " << scanner << std::endl; |
| } |
| |
| return true; |
| } |
| |
| } // namespace |
| |
| int main(int argc, char** argv) { |
| brillo::InitLog(brillo::kLogToSyslog | brillo::kLogToStderrIfTty | |
| brillo::kLogHeader); |
| |
| // Scan options. |
| DEFINE_uint32(scan_resolution, 100, |
| "The scan resolution to request from the scanner"); |
| DEFINE_string(scan_source, "Platen", |
| "The scan source to use for the scanner, (e.g. Platen, ADF " |
| "Simplex, ADF Duplex)"); |
| DEFINE_string(color_mode, "Color", |
| "The color mode to use for the scanner, (e.g. Color, Grayscale," |
| "Lineart)"); |
| DEFINE_bool(all, false, |
| "Loop through all detected scanners instead of prompting."); |
| DEFINE_double(top_left_x, -1.0, |
| "Top-left X position of the scan region (mm)"); |
| DEFINE_double(top_left_y, -1.0, |
| "Top-left Y position of the scan region (mm)"); |
| DEFINE_double(bottom_right_x, -1.0, |
| "Bottom-right X position of the scan region (mm)"); |
| DEFINE_double(bottom_right_y, -1.0, |
| "Bottom-right Y position of the scan region (mm)"); |
| |
| // Cancel Scan options |
| DEFINE_string(uuid, "", "UUID of the scan job to cancel."); |
| |
| brillo::FlagHelper::Init(argc, argv, |
| "lorgnette_cli, command-line interface to " |
| "Chromium OS Scanning Daemon"); |
| |
| const std::vector<std::string>& args = |
| base::CommandLine::ForCurrentProcess()->GetArgs(); |
| if (args.size() != 1 || (args[0] != "scan" && args[0] != "cancel_scan")) { |
| std::cerr << "usage: lorgnette_cli [scan|cancel_scan] [FLAGS...]" |
| << std::endl; |
| return 1; |
| } |
| const std::string& command = args[0]; |
| |
| // Create a task executor for this thread. This will automatically be bound |
| // to the current thread so that it is usable by other code for posting tasks. |
| base::SingleThreadTaskExecutor executor(base::MessagePumpType::IO); |
| |
| // Create a FileDescriptorWatcher instance for this thread. The libbase D-Bus |
| // bindings use this internally via thread-local storage, but do not properly |
| // instantiate it. |
| base::FileDescriptorWatcher watcher(executor.task_runner()); |
| |
| dbus::Bus::Options options; |
| options.bus_type = dbus::Bus::SYSTEM; |
| scoped_refptr<dbus::Bus> bus(new dbus::Bus(options)); |
| auto manager = |
| std::make_unique<ManagerProxy>(bus, lorgnette::kManagerServiceName); |
| |
| if (command == "scan") { |
| if (!FLAGS_uuid.empty()) { |
| LOG(ERROR) << "--uuid flag is not supported in scan mode."; |
| return 1; |
| } |
| |
| base::Optional<lorgnette::SourceType> source_type = |
| GuessSourceType(FLAGS_scan_source); |
| |
| if (!source_type.has_value()) { |
| LOG(ERROR) |
| << "Unknown source type: \"" << FLAGS_scan_source |
| << "\". Supported values are \"Platen\",\"ADF\", \"ADF Simplex\"" |
| ", and \"ADF Duplex\""; |
| return 1; |
| } |
| |
| lorgnette::ScanRegion region; |
| region.set_top_left_x(FLAGS_top_left_x); |
| region.set_top_left_y(FLAGS_top_left_y); |
| region.set_bottom_right_x(FLAGS_bottom_right_x); |
| region.set_bottom_right_y(FLAGS_bottom_right_y); |
| |
| std::string color_mode_string = base::ToLowerASCII(FLAGS_color_mode); |
| lorgnette::ColorMode color_mode; |
| if (color_mode_string == "color") { |
| color_mode = lorgnette::MODE_COLOR; |
| } else if (color_mode_string == "grayscale" || |
| color_mode_string == "gray") { |
| color_mode = lorgnette::MODE_GRAYSCALE; |
| } else if (color_mode_string == "lineart" || color_mode_string == "bw") { |
| color_mode = lorgnette::MODE_LINEART; |
| } else { |
| LOG(ERROR) << "Unknown color mode: \"" << color_mode_string |
| << "\". Supported values are \"Color\", \"Grayscale\", and " |
| "\"Lineart\""; |
| return 1; |
| } |
| |
| bool success = DoScan(std::move(manager), FLAGS_scan_resolution, |
| source_type.value(), region, color_mode, FLAGS_all); |
| return success ? 0 : 1; |
| } else if (command == "cancel_scan") { |
| if (FLAGS_uuid.empty()) { |
| LOG(ERROR) << "Must specify scan uuid to cancel using --uuid=[...]"; |
| return 1; |
| } |
| |
| base::Optional<lorgnette::CancelScanResponse> response = |
| CancelScan(manager.get(), FLAGS_uuid); |
| if (!response.has_value()) |
| return 1; |
| |
| if (!response->success()) { |
| LOG(ERROR) << "Failed to cancel scan: " << response->failure_reason(); |
| return 1; |
| } |
| return 0; |
| } |
| } |