diff --git a/printing/BUILD.gn b/printing/BUILD.gn index fbbf79cde97262..26524b9cc2dc14 100644 --- a/printing/BUILD.gn +++ b/printing/BUILD.gn @@ -169,12 +169,27 @@ component("printing") { # of the print backend and enables a custom implementation instead. defines += [ "PRINT_BACKEND_AVAILABLE" ] - sources += [ - "backend/cups_helper.cc", - "backend/cups_helper.h", - "backend/print_backend_cups.cc", - "backend/print_backend_cups.h", - ] + if (is_chromeos) { + sources += [ + "backend/cups_connection.cc", + "backend/cups_connection.h", + "backend/cups_deleters.cc", + "backend/cups_deleters.h", + "backend/cups_ipp_util.cc", + "backend/cups_ipp_util.h", + "backend/cups_printer.cc", + "backend/cups_printer.h", + "backend/print_backend_cups_ipp.cc", + "backend/print_backend_cups_ipp.h", + ] + } else { + sources += [ + "backend/cups_helper.cc", + "backend/cups_helper.h", + "backend/print_backend_cups.cc", + "backend/print_backend_cups.h", + ] + } } if (is_chromeos) { @@ -240,7 +255,12 @@ test("printing_unittests") { if (use_cups) { configs += [ ":cups" ] - sources += [ "backend/cups_helper_unittest.cc" ] + + if (is_chromeos) { + sources += [ "backend/cups_ipp_util_unittest.cc" ] + } else { + sources += [ "backend/cups_helper_unittest.cc" ] + } } } diff --git a/printing/backend/cups_connection.cc b/printing/backend/cups_connection.cc new file mode 100644 index 00000000000000..efb5216b68e28f --- /dev/null +++ b/printing/backend/cups_connection.cc @@ -0,0 +1,137 @@ +// Copyright 2016 The Chromium 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 "printing/backend/cups_connection.h" + +#include +#include + +#include "base/logging.h" +#include "base/memory/ptr_util.h" +#include "base/strings/stringprintf.h" + +namespace printing { + +namespace { + +const int kTimeoutMs = 3000; + +class DestinationEnumerator { + public: + DestinationEnumerator() {} + + static int cups_callback(void* user_data, unsigned flags, cups_dest_t* dest) { + cups_dest_t* copied_dest; + cupsCopyDest(dest, 0, &copied_dest); + reinterpret_cast(user_data)->store_dest( + copied_dest); + + // keep going + return 1; + } + + void store_dest(cups_dest_t* dest) { dests_.emplace_back(dest); } + + // Returns the collected destinations. + std::vector>& get_dests() { + return dests_; + } + + private: + std::vector> dests_; + + DISALLOW_COPY_AND_ASSIGN(DestinationEnumerator); +}; + +} // namespace + +CupsConnection::CupsConnection(const GURL& print_server_url, + http_encryption_t encryption, + bool blocking) + : print_server_url_(print_server_url), + cups_encryption_(encryption), + blocking_(blocking), + cups_http_(nullptr) {} + +CupsConnection::CupsConnection(CupsConnection&& connection) + : print_server_url_(connection.print_server_url_), + cups_encryption_(connection.cups_encryption_), + blocking_(connection.blocking_), + cups_http_(std::move(connection.cups_http_)) {} + +CupsConnection::~CupsConnection() {} + +bool CupsConnection::Connect() { + if (cups_http_) + return true; // we're already connected + + std::string host; + int port; + + if (!print_server_url_.is_empty()) { + host = print_server_url_.host(); + port = print_server_url_.IntPort(); + } else { + host = cupsServer(); + port = ippPort(); + } + + cups_http_.reset(httpConnect2(host.c_str(), port, nullptr, AF_UNSPEC, + cups_encryption_, blocking_ ? 1 : 0, kTimeoutMs, + nullptr)); + return !!cups_http_; +} + +std::vector CupsConnection::GetDests() { + if (!Connect()) { + LOG(WARNING) << "CUPS connection failed"; + return std::vector(); + } + + DestinationEnumerator enumerator; + int success = + cupsEnumDests(CUPS_DEST_FLAGS_NONE, kTimeoutMs, + nullptr, // no cancel signal + 0, // all the printers + CUPS_PRINTER_SCANNER, // except the scanners + &DestinationEnumerator::cups_callback, &enumerator); + + if (!success) { + LOG(WARNING) << "Enumerating printers failed"; + return std::vector(); + } + + auto dests = std::move(enumerator.get_dests()); + std::vector printers; + for (auto& dest : dests) { + printers.emplace_back(cups_http_.get(), std::move(dest), nullptr); + } + + return printers; +} + +std::unique_ptr CupsConnection::GetPrinter( + const std::string& name) { + if (!Connect()) + return nullptr; + + cups_dest_t* dest = cupsGetNamedDest(cups_http_.get(), name.c_str(), nullptr); + if (!dest) + return nullptr; + + cups_dinfo_t* info = cupsCopyDestInfo(cups_http_.get(), dest); + return base::MakeUnique( + cups_http_.get(), std::unique_ptr(dest), + std::unique_ptr(info)); +} + +std::string CupsConnection::server_name() const { + return print_server_url_.host(); +} + +int CupsConnection::last_error() const { + return cupsLastError(); +} + +} // namespace printing diff --git a/printing/backend/cups_connection.h b/printing/backend/cups_connection.h new file mode 100644 index 00000000000000..5429edb32ea550 --- /dev/null +++ b/printing/backend/cups_connection.h @@ -0,0 +1,59 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_BACKEND_CUPS_CONNECTION_H_ +#define PRINTING_BACKEND_CUPS_CONNECTION_H_ + +#include + +#include +#include +#include + +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "printing/backend/cups_deleters.h" +#include "printing/backend/cups_printer.h" +#include "printing/printing_export.h" +#include "url/gurl.h" + +namespace printing { + +// Represents a connection to a CUPS server. +class PRINTING_EXPORT CupsConnection { + public: + CupsConnection(const GURL& print_server_url, + http_encryption_t encryption, + bool blocking); + + CupsConnection(CupsConnection&& connection); + + ~CupsConnection(); + + // Returns a vector of all the printers configure on the CUPS server. + std::vector GetDests(); + + // Returns a printer for |printer_name| from the connected server. + std::unique_ptr GetPrinter(const std::string& printer_name); + + std::string server_name() const; + + int last_error() const; + + private: + // lazily initialize http connection + bool Connect(); + + GURL print_server_url_; + http_encryption_t cups_encryption_; + bool blocking_; + + std::unique_ptr cups_http_; + + DISALLOW_COPY_AND_ASSIGN(CupsConnection); +}; + +} // namespace printing + +#endif // PRINTING_BACKEND_CUPS_CONNECTION_H_ diff --git a/printing/backend/cups_deleters.cc b/printing/backend/cups_deleters.cc new file mode 100644 index 00000000000000..c37366cf093d9c --- /dev/null +++ b/printing/backend/cups_deleters.cc @@ -0,0 +1,21 @@ +// Copyright 2016 The Chromium 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 "printing/backend/cups_deleters.h" + +namespace printing { + +void HttpDeleter::operator()(http_t* http) const { + httpClose(http); +} + +void DestinationDeleter::operator()(cups_dest_t* dest) const { + cupsFreeDests(1, dest); +} + +void DestInfoDeleter::operator()(cups_dinfo_t* info) const { + cupsFreeDestInfo(info); +} + +} // namespace printing diff --git a/printing/backend/cups_deleters.h b/printing/backend/cups_deleters.h new file mode 100644 index 00000000000000..ac4ef4df162658 --- /dev/null +++ b/printing/backend/cups_deleters.h @@ -0,0 +1,29 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_BACKEND_CUPS_DELETERS_H_ +#define PRINTING_BACKEND_CUPS_DELETERS_H_ + +#include + +namespace printing { + +struct HttpDeleter { + public: + void operator()(http_t* http) const; +}; + +struct DestinationDeleter { + public: + void operator()(cups_dest_t* dest) const; +}; + +struct DestInfoDeleter { + public: + void operator()(cups_dinfo_t* info) const; +}; + +} // namespace printing + +#endif // PRINTING_BACKEND_CUPS_DELETERS_H_ diff --git a/printing/backend/cups_ipp_util.cc b/printing/backend/cups_ipp_util.cc new file mode 100644 index 00000000000000..49f02d08a7a41b --- /dev/null +++ b/printing/backend/cups_ipp_util.cc @@ -0,0 +1,298 @@ +// Copyright 2016 The Chromium 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 "printing/backend/cups_ipp_util.h" + +#include + +#include +#include +#include + +#include "base/logging.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "printing/backend/cups_printer.h" +#include "printing/backend/print_backend_consts.h" +#include "printing/units.h" + +namespace printing { + +namespace { + +const char kIppCollate[] = "sheet-collate"; // RFC 3381 +const char kIppCopies[] = CUPS_COPIES; +const char kIppColor[] = CUPS_PRINT_COLOR_MODE; +const char kIppMedia[] = CUPS_MEDIA; +const char kIppDuplex[] = CUPS_SIDES; + +const char kCollated[] = "collated"; + +const int kMicronsPerMM = 1000; +const double kMMPerInch = 25.4; +const double kMicronsPerInch = kMMPerInch * kMicronsPerMM; + +enum Unit { + INCHES, + MILLIMETERS, +}; + +struct ColorMap { + const char* color; + ColorModel model; +}; + +const ColorMap kColorList[]{ + {CUPS_PRINT_COLOR_MODE_COLOR, COLORMODE_COLOR}, + {CUPS_PRINT_COLOR_MODE_MONOCHROME, COLORMODE_MONOCHROME}, +}; + +ColorModel ColorModelFromIppColor(base::StringPiece ippColor) { + for (const ColorMap& color : kColorList) { + if (ippColor.compare(color.color) == 0) { + return color.model; + } + } + + return UNKNOWN_COLOR_MODEL; +} + +bool PrinterSupportsValue(const CupsOptionProvider& printer, + base::StringPiece name, + base::StringPiece value) { + std::vector values = + printer.GetSupportedOptionValueStrings(name); + return ContainsValue(values, value); +} + +DuplexMode PrinterDefaultDuplex(const CupsOptionProvider& printer) { + ipp_attribute_t* attr = printer.GetDefaultOptionValue(kIppDuplex); + if (!attr) + return UNKNOWN_DUPLEX_MODE; + + const char* value = ippGetString(attr, 0, nullptr); + if (base::EqualsCaseInsensitiveASCII(value, CUPS_SIDES_ONE_SIDED)) + return SIMPLEX; + + if (base::EqualsCaseInsensitiveASCII(value, CUPS_SIDES_TWO_SIDED_PORTRAIT)) + return LONG_EDGE; + + if (base::EqualsCaseInsensitiveASCII(value, CUPS_SIDES_TWO_SIDED_LANDSCAPE)) + return SHORT_EDGE; + + return UNKNOWN_DUPLEX_MODE; +} + +gfx::Size DimensionsToMicrons(base::StringPiece value) { + Unit unit; + base::StringPiece dims; + size_t unit_position; + if ((unit_position = value.find("mm")) != base::StringPiece::npos) { + unit = MILLIMETERS; + dims = value.substr(0, unit_position); + } else if ((unit_position = value.find("in")) != base::StringPiece::npos) { + unit = INCHES; + dims = value.substr(0, unit_position); + } else { + LOG(WARNING) << "Could not parse paper dimensions"; + return {0, 0}; + } + + std::vector pieces = base::SplitString( + dims, "x", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + double width; + double height; + + if (pieces.size() != 2 || !base::StringToDouble(pieces[0], &width) || + !base::StringToDouble(pieces[1], &height)) { + return {0, 0}; + } + + int width_microns; + int height_microns; + + switch (unit) { + case MILLIMETERS: + width_microns = width * kMicronsPerMM; + height_microns = height * kMicronsPerMM; + break; + case INCHES: + width_microns = width * kMicronsPerInch; + height_microns = height * kMicronsPerInch; + break; + default: + NOTREACHED(); + break; + } + + return gfx::Size{width_microns, height_microns}; +} + +PrinterSemanticCapsAndDefaults::Paper ParsePaper(base::StringPiece value) { + // _x{in,mm} + // e.g. na_letter_8.5x11in, iso_a4_210x297mm + + std::vector pieces = base::SplitStringPiece( + value, "_", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + // we expect at least a display string and a dimension string + if (pieces.size() < 2) + return PrinterSemanticCapsAndDefaults::Paper(); + + base::StringPiece dimensions = pieces.back(); + + std::string display = pieces[0].as_string(); + for (size_t i = 1; i <= pieces.size() - 2; ++i) { + display.append(" "); + pieces[i].AppendToString(&display); + } + + PrinterSemanticCapsAndDefaults::Paper paper; + paper.display_name = display; + paper.vendor_id = value.as_string(); + paper.size_um = DimensionsToMicrons(dimensions); + + return paper; +} + +void ExtractColor(const CupsOptionProvider& printer, + PrinterSemanticCapsAndDefaults* printer_info) { + printer_info->bw_model = UNKNOWN_COLOR_MODEL; + printer_info->color_model = UNKNOWN_COLOR_MODEL; + + // color and b&w + std::vector color_models = SupportedColorModels(printer); + for (ColorModel color : color_models) { + switch (color) { + case COLORMODE_COLOR: + printer_info->color_model = COLORMODE_COLOR; + break; + case COLORMODE_MONOCHROME: + printer_info->bw_model = COLORMODE_MONOCHROME; + break; + default: + // value not needed + break; + } + } + + // changeable + printer_info->color_changeable = + (printer_info->color_model != UNKNOWN_COLOR_MODEL && + printer_info->bw_model != UNKNOWN_COLOR_MODEL); + + // default color + printer_info->color_default = DefaultColorModel(printer) == COLORMODE_COLOR; +} + +void ExtractCopies(const CupsOptionProvider& printer, + PrinterSemanticCapsAndDefaults* printer_info) { + // copies + int upper_bound; + int lower_bound; + CopiesRange(printer, &lower_bound, &upper_bound); + printer_info->copies_capable = (lower_bound != -1) && (upper_bound >= 2); +} + +} // namespace + +ColorModel DefaultColorModel(const CupsOptionProvider& printer) { + // default color + ipp_attribute_t* attr = printer.GetDefaultOptionValue(kIppColor); + if (!attr) + return UNKNOWN_COLOR_MODEL; + + return ColorModelFromIppColor(ippGetString(attr, 0, nullptr)); +} + +std::vector SupportedColorModels( + const CupsOptionProvider& printer) { + std::vector colors; + + std::vector color_modes = + printer.GetSupportedOptionValueStrings(kIppColor); + + for (base::StringPiece color : color_modes) { + ColorModel color_model = ColorModelFromIppColor(color); + if (color_model != UNKNOWN_COLOR_MODEL) { + colors.push_back(color_model); + } + } + + return colors; +} + +PrinterSemanticCapsAndDefaults::Paper DefaultPaper( + const CupsOptionProvider& printer) { + ipp_attribute_t* attr = printer.GetDefaultOptionValue(kIppMedia); + if (!attr) + return PrinterSemanticCapsAndDefaults::Paper(); + + return ParsePaper(ippGetString(attr, 0, nullptr)); +} + +std::vector SupportedPapers( + const CupsOptionProvider& printer) { + std::vector papers = + printer.GetSupportedOptionValueStrings(kIppMedia); + std::vector parsed_papers; + for (base::StringPiece paper : papers) { + parsed_papers.push_back(ParsePaper(paper)); + } + + return parsed_papers; +} + +void CopiesRange(const CupsOptionProvider& printer, + int* lower_bound, + int* upper_bound) { + ipp_attribute_t* attr = printer.GetSupportedOptionValues(kIppCopies); + if (!attr) { + *lower_bound = -1; + *upper_bound = -1; + } + + *lower_bound = ippGetRange(attr, 0, upper_bound); +} + +bool CollateCapable(const CupsOptionProvider& printer) { + std::vector values = + printer.GetSupportedOptionValueStrings(kIppCollate); + auto iter = std::find(values.begin(), values.end(), kCollated); + return iter != values.end(); +} + +bool CollateDefault(const CupsOptionProvider& printer) { + ipp_attribute_t* attr = printer.GetDefaultOptionValue(kIppCollate); + if (!attr) + return false; + + base::StringPiece name = ippGetString(attr, 0, nullptr); + return name.compare(kCollated) == 0; +} + +void CapsAndDefaultsFromPrinter(const CupsOptionProvider& printer, + PrinterSemanticCapsAndDefaults* printer_info) { + // duplex + printer_info->duplex_default = PrinterDefaultDuplex(printer); + printer_info->duplex_capable = + PrinterSupportsValue(printer, kIppDuplex, CUPS_SIDES_TWO_SIDED_PORTRAIT); + + // collate + printer_info->collate_default = CollateDefault(printer); + printer_info->collate_capable = CollateCapable(printer); + + // paper + printer_info->default_paper = DefaultPaper(printer); + printer_info->papers = SupportedPapers(printer); + + ExtractCopies(printer, printer_info); + ExtractColor(printer, printer_info); + + // TODO(skau): Add dpi and default_dpi +} + +} // namespace printing diff --git a/printing/backend/cups_ipp_util.h b/printing/backend/cups_ipp_util.h new file mode 100644 index 00000000000000..9887ca5664c8aa --- /dev/null +++ b/printing/backend/cups_ipp_util.h @@ -0,0 +1,50 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_BACKEND_CUPS_IPP_UTIL_H_ +#define PRINTING_BACKEND_CUPS_IPP_UTIL_H_ + +#include + +#include "base/strings/string_piece.h" +#include "printing/backend/cups_printer.h" +#include "printing/backend/print_backend.h" + +namespace printing { + +// Returns the default ColorModel for |printer|. +ColorModel DefaultColorModel(const CupsOptionProvider& printer); + +// Returns the set of supported ColorModels for |printer|. +std::vector SupportedColorModels(const CupsOptionProvider& printer); + +// Returns the default paper setting for |printer|. +PrinterSemanticCapsAndDefaults::Paper DefaultPaper( + const CupsOptionProvider& printer); + +// Returns the list of papers supported by the |printer|. +std::vector SupportedPapers( + const CupsOptionProvider& printer); + +// Retrieves the supported number of copies from |printer| and writes the +// extremities of the range into |lower_bound| and |upper_bound|. Values are +// set to -1 if there is an error. +void CopiesRange(const CupsOptionProvider& printer, + int* lower_bound, + int* upper_bound); + +// Returns true if |printer| can do collation. +bool CollateCapable(const CupsOptionProvider& printer); + +// Returns true if |printer| has collation enabled by default. +bool CollateDefault(const CupsOptionProvider& printer); + +// Populates the |printer_info| object with attributes retrived using IPP from +// |printer|. +void CapsAndDefaultsFromPrinter(const CupsOptionProvider& printer, + PrinterSemanticCapsAndDefaults* printer_info); + +} // namespace printing + +#endif // PRINTING_BACKEND_CUPS_IPP_UTIL_H_ diff --git a/printing/backend/cups_ipp_util_unittest.cc b/printing/backend/cups_ipp_util_unittest.cc new file mode 100644 index 00000000000000..8b44d4a1a15cbd --- /dev/null +++ b/printing/backend/cups_ipp_util_unittest.cc @@ -0,0 +1,193 @@ +// Copyright 2016 The Chromium 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 + +#include +#include + +#include "base/logging.h" +#include "base/memory/ptr_util.h" +#include "printing/backend/cups_ipp_util.h" +#include "printing/backend/cups_printer.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace printing { + +class MockCupsOptionProvider : public CupsOptionProvider { + public: + ~MockCupsOptionProvider() override {} + + ipp_attribute_t* GetSupportedOptionValues( + base::StringPiece option_name) const override { + const auto attr = supported_attributes_.find(option_name); + return attr != supported_attributes_.end() ? attr->second : nullptr; + } + + std::vector GetSupportedOptionValueStrings( + base::StringPiece option_name) const override { + ipp_attribute_t* attr = GetSupportedOptionValues(option_name); + if (!attr) + return std::vector(); + + std::vector strings; + int size = ippGetCount(attr); + for (int i = 0; i < size; ++i) { + strings.emplace_back(ippGetString(attr, i, nullptr)); + } + + return strings; + } + + ipp_attribute_t* GetDefaultOptionValue( + base::StringPiece option_name) const override { + const auto attr = default_attributes_.find(option_name); + return attr != default_attributes_.end() ? attr->second : nullptr; + } + + bool CheckOptionSupported(base::StringPiece name, + base::StringPiece value) const override { + NOTREACHED(); + return false; + } + + void SetSupportedOptions(base::StringPiece name, ipp_attribute_t* attribute) { + supported_attributes_[name] = attribute; + } + + void SetOptionDefault(base::StringPiece name, ipp_attribute_t* attribute) { + default_attributes_[name] = attribute; + } + + private: + std::map supported_attributes_; + std::map default_attributes_; +}; + +class PrintBackendCupsIppUtilTest : public ::testing::Test { + protected: + void SetUp() override { + ipp_ = ippNew(); + printer_ = base::MakeUnique(); + } + + void TearDown() override { + ippDelete(ipp_); + printer_.reset(); + } + + ipp_t* ipp_; + std::unique_ptr printer_; +}; + +ipp_attribute_t* MakeRange(ipp_t* ipp, int lower_bound, int upper_bound) { + return ippAddRange(ipp, IPP_TAG_PRINTER, "TEST_DATA", lower_bound, + upper_bound); +} + +ipp_attribute_t* MakeString(ipp_t* ipp, const char* value) { + return ippAddString(ipp, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "TEST_DATA", + nullptr, value); +} + +ipp_attribute_t* MakeStringCollection(ipp_t* ipp, + std::vector strings) { + return ippAddStrings(ipp, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "TEST_DATA", + strings.size(), nullptr, strings.data()); +} + +TEST_F(PrintBackendCupsIppUtilTest, CopiesCapable) { + printer_->SetSupportedOptions("copies", MakeRange(ipp_, 1, 2)); + + PrinterSemanticCapsAndDefaults caps; + CapsAndDefaultsFromPrinter(*printer_, &caps); + + EXPECT_TRUE(caps.copies_capable); +} + +TEST_F(PrintBackendCupsIppUtilTest, CopiesNotCapable) { + // copies missing, no setup + PrinterSemanticCapsAndDefaults caps; + CapsAndDefaultsFromPrinter(*printer_, &caps); + + EXPECT_FALSE(caps.copies_capable); +} + +TEST_F(PrintBackendCupsIppUtilTest, ColorPrinter) { + printer_->SetSupportedOptions( + "print-color-mode", MakeStringCollection(ipp_, {"color", "monochrome"})); + printer_->SetOptionDefault("print-color-mode", MakeString(ipp_, "color")); + + PrinterSemanticCapsAndDefaults caps; + CapsAndDefaultsFromPrinter(*printer_, &caps); + + EXPECT_TRUE(caps.color_changeable); + EXPECT_TRUE(caps.color_default); +} + +TEST_F(PrintBackendCupsIppUtilTest, BWPrinter) { + printer_->SetSupportedOptions("print-color-mode", + MakeStringCollection(ipp_, {"monochrome"})); + printer_->SetOptionDefault("print-color-mode", + MakeString(ipp_, "monochrome")); + + PrinterSemanticCapsAndDefaults caps; + CapsAndDefaultsFromPrinter(*printer_, &caps); + + EXPECT_FALSE(caps.color_changeable); + EXPECT_FALSE(caps.color_default); +} + +TEST_F(PrintBackendCupsIppUtilTest, DuplexSupported) { + printer_->SetSupportedOptions( + "sides", + MakeStringCollection(ipp_, {"two-sided-long-edge", "one-sided"})); + printer_->SetOptionDefault("sides", MakeString(ipp_, "one-sided")); + + PrinterSemanticCapsAndDefaults caps; + CapsAndDefaultsFromPrinter(*printer_, &caps); + + EXPECT_TRUE(caps.duplex_capable); + EXPECT_FALSE(caps.duplex_default); +} + +TEST_F(PrintBackendCupsIppUtilTest, DuplexNotSupported) { + printer_->SetSupportedOptions("sides", + MakeStringCollection(ipp_, {"one-sided"})); + printer_->SetOptionDefault("sides", MakeString(ipp_, "one-sided")); + + PrinterSemanticCapsAndDefaults caps; + CapsAndDefaultsFromPrinter(*printer_, &caps); + + EXPECT_FALSE(caps.duplex_capable); + EXPECT_FALSE(caps.duplex_default); +} + +TEST_F(PrintBackendCupsIppUtilTest, A4PaperSupported) { + printer_->SetSupportedOptions( + "media", MakeStringCollection(ipp_, {"iso_a4_210x297mm"})); + + PrinterSemanticCapsAndDefaults caps; + CapsAndDefaultsFromPrinter(*printer_, &caps); + + PrinterSemanticCapsAndDefaults::Paper paper = caps.papers[0]; + EXPECT_EQ("iso a4", paper.display_name); + EXPECT_EQ("iso_a4_210x297mm", paper.vendor_id); + EXPECT_EQ(210000, paper.size_um.width()); + EXPECT_EQ(297000, paper.size_um.height()); +} + +TEST_F(PrintBackendCupsIppUtilTest, LegalPaperDefault) { + printer_->SetOptionDefault("media", MakeString(ipp_, "na_legal_8.5x14in")); + + PrinterSemanticCapsAndDefaults caps; + CapsAndDefaultsFromPrinter(*printer_, &caps); + + EXPECT_EQ("na legal", caps.default_paper.display_name); + EXPECT_EQ("na_legal_8.5x14in", caps.default_paper.vendor_id); + EXPECT_EQ(215900, caps.default_paper.size_um.width()); + EXPECT_EQ(355600, caps.default_paper.size_um.height()); +} + +} // namespace printing diff --git a/printing/backend/cups_printer.cc b/printing/backend/cups_printer.cc new file mode 100644 index 00000000000000..5738ed20170422 --- /dev/null +++ b/printing/backend/cups_printer.cc @@ -0,0 +1,240 @@ +// Copyright 2016 The Chromium 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 "printing/backend/cups_printer.h" + +#include + +#include +#include + +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/strings/string_number_conversions.h" +#include "printing/backend/cups_connection.h" +#include "printing/backend/print_backend.h" + +namespace { + +const char kDriverInfoTagName[] = "system_driverinfo"; + +const char kCUPSPrinterInfoOpt[] = "printer-info"; +const char kCUPSPrinterStateOpt[] = "printer-state"; +const char kCUPSPrinterMakeModelOpt[] = "printer-make-and-model"; + +} // namespace + +namespace printing { + +CupsPrinter::CupsPrinter(http_t* http, + std::unique_ptr dest, + std::unique_ptr info) + : cups_http_(http), + destination_(std::move(dest)), + dest_info_(std::move(info)) { + DCHECK(cups_http_); + DCHECK(destination_); +} + +CupsPrinter::CupsPrinter(CupsPrinter&& printer) + : cups_http_(printer.cups_http_), + destination_(std::move(printer.destination_)), + dest_info_(std::move(printer.dest_info_)) { + DCHECK(cups_http_); + DCHECK(destination_); +} + +CupsPrinter::~CupsPrinter() {} + +bool CupsPrinter::is_default() const { + return destination_->is_default; +} + +ipp_attribute_t* CupsPrinter::GetSupportedOptionValues( + base::StringPiece option_name) const { + if (!InitializeDestInfo()) + return nullptr; + + return cupsFindDestSupported(cups_http_, destination_.get(), dest_info_.get(), + option_name.as_string().c_str()); +} + +std::vector CupsPrinter::GetSupportedOptionValueStrings( + base::StringPiece option_name) const { + ipp_attribute_t* attr = GetSupportedOptionValues(option_name); + std::vector values; + if (!attr) { + return values; + } + + base::StringPiece value; + int num_options = ippGetCount(attr); + for (int i = 0; i < num_options; ++i) { + value.set(ippGetString(attr, i, nullptr)); + values.push_back(value); + } + + return values; +} + +ipp_attribute_t* CupsPrinter::GetDefaultOptionValue( + base::StringPiece option_name) const { + if (!InitializeDestInfo()) + return nullptr; + + return cupsFindDestDefault(cups_http_, destination_.get(), dest_info_.get(), + option_name.as_string().c_str()); +} + +bool CupsPrinter::CheckOptionSupported(base::StringPiece name, + base::StringPiece value) const { + if (!InitializeDestInfo()) + return false; + + int supported = cupsCheckDestSupported( + cups_http_, destination_.get(), dest_info_.get(), + name.as_string().c_str(), value.as_string().c_str()); + return supported == 1; +} + +bool CupsPrinter::ToPrinterInfo(PrinterBasicInfo* printer_info) const { + const cups_dest_t* printer = destination_.get(); + + printer_info->printer_name = printer->name; + printer_info->is_default = printer->is_default; + + const char* info = cupsGetOption(kCUPSPrinterInfoOpt, printer->num_options, + printer->options); + if (info) + printer_info->printer_description = info; + + const char* state = cupsGetOption(kCUPSPrinterStateOpt, printer->num_options, + printer->options); + if (state) + base::StringToInt(state, &printer_info->printer_status); + + const char* drv_info = cupsGetOption(kCUPSPrinterMakeModelOpt, + printer->num_options, printer->options); + if (drv_info) + printer_info->options[kDriverInfoTagName] = *drv_info; + + // Store printer options. + for (int opt_index = 0; opt_index < printer->num_options; ++opt_index) { + printer_info->options[printer->options[opt_index].name] = + printer->options[opt_index].value; + } + + return true; +} + +base::FilePath CupsPrinter::GetPPD() const { + base::StringPiece printer_name = destination_->name; + const char* ppd_path = + cupsGetPPD2(cups_http_, printer_name.as_string().c_str()); + base::FilePath path(ppd_path); + + if (ppd_path) { + // There is no reliable way right now to detect full and complete PPD + // get downloaded. If we reach http timeout, it may simply return + // downloaded part as a full response. It might be good enough to check + // http->data_remaining or http->_data_remaining, unfortunately http_t + // is an internal structure and fields are not exposed in CUPS headers. + // httpGetLength or httpGetLength2 returning the full content size. + // Comparing file size against that content length might be unreliable + // since some http reponses are encoded and content_length > file size. + // Let's just check for the obvious CUPS and http errors here. + ipp_status_t error_code = cupsLastError(); + int http_error = httpError(cups_http_); + if (error_code > IPP_OK_EVENTS_COMPLETE || http_error != 0) { + LOG(ERROR) << "Error downloading PPD file, name: " << destination_->name + << ", CUPS error: " << static_cast(error_code) + << ", HTTP error: " << http_error; + base::DeleteFile(path, false); + path.clear(); + } + } + + return path; +} + +std::string CupsPrinter::GetName() const { + return std::string(destination_->name); +} + +std::string CupsPrinter::GetMakeAndModel() const { + const char* make_and_model = + cupsGetOption(kCUPSPrinterMakeModelOpt, destination_->num_options, + destination_->options); + + return make_and_model ? std::string(make_and_model) : std::string(); +} + +bool CupsPrinter::IsAvailable() const { + return InitializeDestInfo(); +} + +bool CupsPrinter::InitializeDestInfo() const { + if (dest_info_) + return true; + + dest_info_.reset(cupsCopyDestInfo(cups_http_, destination_.get())); + return !!dest_info_; +} + +ipp_status_t CupsPrinter::CreateJob(int* job_id, + base::StringPiece title, + const std::vector& options) { + DCHECK(dest_info_) << "Verify availability before starting a print job"; + + cups_option_t* data = const_cast( + options.data()); // createDestJob will not modify the data + ipp_status_t create_status = cupsCreateDestJob( + cups_http_, destination_.get(), dest_info_.get(), job_id, + title.as_string().c_str(), options.size(), data); + + return create_status; +} + +bool CupsPrinter::StartDocument(int job_id, + base::StringPiece document_name, + bool last_document, + const std::vector& options) { + DCHECK(dest_info_); + DCHECK(job_id); + + cups_option_t* data = const_cast( + options.data()); // createStartDestDocument will not modify the data + http_status_t start_doc_status = cupsStartDestDocument( + cups_http_, destination_.get(), dest_info_.get(), job_id, + document_name.as_string().c_str(), CUPS_FORMAT_PDF, options.size(), data, + last_document ? 0 : 1); + + return start_doc_status == HTTP_CONTINUE; +} + +bool CupsPrinter::StreamData(const std::vector& buffer) { + http_status_t status = + cupsWriteRequestData(cups_http_, buffer.data(), buffer.size()); + return status == HTTP_STATUS_CONTINUE; +} + +bool CupsPrinter::FinishDocument() { + DCHECK(dest_info_); + + ipp_status_t status = + cupsFinishDestDocument(cups_http_, destination_.get(), dest_info_.get()); + + return status == IPP_STATUS_OK; +} + +ipp_status_t CupsPrinter::CloseJob(int job_id) { + DCHECK(dest_info_); + DCHECK(job_id); + + return cupsCloseDestJob(cups_http_, destination_.get(), dest_info_.get(), + job_id); +} + +} // namespace printing diff --git a/printing/backend/cups_printer.h b/printing/backend/cups_printer.h new file mode 100644 index 00000000000000..65b1732d1c390a --- /dev/null +++ b/printing/backend/cups_printer.h @@ -0,0 +1,141 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_BACKEND_CUPS_PRINTER_H_ +#define PRINTING_BACKEND_CUPS_PRINTER_H_ + +#include + +#include +#include +#include + +#include "printing/backend/cups_deleters.h" +#include "printing/printing_export.h" +#include "url/gurl.h" + +namespace base { +class FilePath; +} + +namespace printing { + +struct PrinterBasicInfo; + +// Provides information regarding cups options. +class PRINTING_EXPORT CupsOptionProvider { + public: + virtual ~CupsOptionProvider() = default; + + // Returns the supported ipp attributes for the given |option_name|. + // ipp_attribute_t* is owned by CupsOptionProvider. + virtual ipp_attribute_t* GetSupportedOptionValues( + base::StringPiece option_name) const = 0; + + // Returns supported attribute values for |option_name| where the value can be + // convered to a string. + virtual std::vector GetSupportedOptionValueStrings( + base::StringPiece option_name) const = 0; + + // Returns the default ipp attributes for the given |option_name|. + // ipp_attribute_t* is owned by CupsOptionProvider. + virtual ipp_attribute_t* GetDefaultOptionValue( + base::StringPiece option_name) const = 0; + + // Returns true if the |value| is supported by option |name|. + virtual bool CheckOptionSupported(base::StringPiece name, + base::StringPiece value) const = 0; +}; + +// Represents a CUPS printer. +// Retrieves information from CUPS printer objects as requested. This class +// is only valid as long as the CupsConnection which created it exists as they +// share an http connection which the CupsConnection closes on destruction. +class PRINTING_EXPORT CupsPrinter : public CupsOptionProvider { + public: + // Create a printer with a connection defined by |http| and |dest|. |info| + // can be null and will be lazily initialized when needed. + CupsPrinter(http_t* http, + std::unique_ptr dest, + std::unique_ptr info); + + CupsPrinter(CupsPrinter&& printer); + + ~CupsPrinter() override; + + // Returns true if this is the default printer + bool is_default() const; + + // CupsOptionProvider + ipp_attribute_t* GetSupportedOptionValues( + base::StringPiece option_name) const override; + std::vector GetSupportedOptionValueStrings( + base::StringPiece option_name) const override; + ipp_attribute_t* GetDefaultOptionValue( + base::StringPiece option_name) const override; + bool CheckOptionSupported(base::StringPiece name, + base::StringPiece value) const override; + + // Returns the file name for the PPD retrieved from the print server. + base::FilePath GetPPD() const; + + // Returns the name of the printer as configured in CUPS + std::string GetName() const; + + std::string GetMakeAndModel() const; + + // Returns true if the printer is currently reachable and working. + bool IsAvailable() const; + + // Populates |basic_info| with the relevant information about the printer + bool ToPrinterInfo(PrinterBasicInfo* basic_info) const; + + // Start a print job. Writes the id of the started job to |job_id|. |job_id| + // is 0 if there is an error. Check availability before using this operation. + // Usage on an unavailable printer is undefined. + ipp_status_t CreateJob(int* job_id, + base::StringPiece job_title, + const std::vector& options); + + // Add a document to a print job. |job_id| must be non-zero and refer to a + // job started with CreateJob. |document_name| will be displayed in print + // status. |last_doc| should be true if this is the last document for this + // print job. |options| should be IPP key value pairs for the Send-Document + // operation. + bool StartDocument(int job_id, + base::StringPiece document_name, + bool last_doc, + const std::vector& options); + + // Add data to the current document started by StartDocument. Calling this + // without a started document will fail. + bool StreamData(const std::vector& buffer); + + // Finish the current document. Another document can be added or the job can + // be closed to complete printing. + bool FinishDocument(); + + // Close the job. If the job is not closed, the documents will not be + // printed. |job_id| should match the id from CreateJob. + ipp_status_t CloseJob(int job_id); + + private: + // Lazily initialize dest info as it can require a network call + bool InitializeDestInfo() const; + + // http connection owned by the CupsConnection which created this object + http_t* const cups_http_; + + // information to identify a printer + std::unique_ptr destination_; + + // opaque object containing printer attributes and options + mutable std::unique_ptr dest_info_; + + DISALLOW_COPY_AND_ASSIGN(CupsPrinter); +}; + +} // namespace printing + +#endif // PRINTING_BACKEND_CUPS_PRINTER_H_ diff --git a/printing/backend/print_backend_chromeos.cc b/printing/backend/print_backend_chromeos.cc index 35f51b7ec6085e..e27bc4e658ace8 100644 --- a/printing/backend/print_backend_chromeos.cc +++ b/printing/backend/print_backend_chromeos.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -10,12 +10,14 @@ #include "url/gurl.h" #if defined(USE_CUPS) -#include "printing/backend/print_backend_cups.h" +#include "base/memory/ptr_util.h" +#include "printing/backend/print_backend_cups_ipp.h" #endif // defined(USE_CUPS) namespace printing { -// Provides a stubbed out PrintBackend implementation for use on ChromeOS. +// Provides either a stubbed out PrintBackend implementation or a CUPS IPP +// implementation for use on ChromeOS. class PrintBackendChromeOS : public PrintBackend { public: PrintBackendChromeOS(); @@ -93,9 +95,13 @@ scoped_refptr PrintBackend::CreateInstance( print_backend_settings->GetInteger(kCUPSEncryption, &encryption); } GURL print_server_url(print_server_url_str.c_str()); - return new PrintBackendCUPS(print_server_url, - static_cast(encryption), - cups_blocking == kValueTrue); + + std::unique_ptr connection = + base::MakeUnique( + print_server_url, static_cast(encryption), + cups_blocking == kValueTrue); + + return new PrintBackendCupsIpp(std::move(connection)); #endif // defined(USE_CUPS) } diff --git a/printing/backend/print_backend_cups_ipp.cc b/printing/backend/print_backend_cups_ipp.cc new file mode 100644 index 00000000000000..34139e482b3a77 --- /dev/null +++ b/printing/backend/print_backend_cups_ipp.cc @@ -0,0 +1,153 @@ +// Copyright 2016 The Chromium 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 "printing/backend/print_backend_cups_ipp.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include "base/debug/leak_annotations.h" +#include "base/files/file_util.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/synchronization/lock.h" +#include "base/values.h" +#include "printing/backend/cups_connection.h" +#include "printing/backend/cups_ipp_util.h" +#include "printing/backend/print_backend_consts.h" +#include "printing/units.h" +#include "url/gurl.h" + +namespace printing { + +PrintBackendCupsIpp::PrintBackendCupsIpp( + std::unique_ptr cups_connection) + : cups_connection_(std::move(cups_connection)) {} + +PrintBackendCupsIpp::~PrintBackendCupsIpp() {} + +bool PrintBackendCupsIpp::EnumeratePrinters(PrinterList* printer_list) { + DCHECK(printer_list); + printer_list->clear(); + + std::vector printers = cups_connection_->GetDests(); + if (printers.empty()) { + LOG(WARNING) << "CUPS: Error getting printers from CUPS server" + << ", server: " << cups_connection_->server_name() + << ", error: " + << static_cast(cups_connection_->last_error()); + + return false; + } + + for (const auto& printer : printers) { + PrinterBasicInfo basic_info; + if (printer.ToPrinterInfo(&basic_info)) { + printer_list->push_back(basic_info); + } + } + + return true; +} + +std::string PrintBackendCupsIpp::GetDefaultPrinterName() { + std::vector printers = cups_connection_->GetDests(); + for (const auto& printer : printers) { + if (printer.is_default()) { + return printer.GetName(); + } + } + + return std::string(); +} + +bool PrintBackendCupsIpp::GetPrinterBasicInfo(const std::string& printer_name, + PrinterBasicInfo* printer_info) { + std::unique_ptr printer( + cups_connection_->GetPrinter(printer_name)); + if (!printer || !printer->IsAvailable()) + return false; + + DCHECK_EQ(printer_name, printer->GetName()); + + return printer->ToPrinterInfo(printer_info); +} + +bool PrintBackendCupsIpp::GetPrinterSemanticCapsAndDefaults( + const std::string& printer_name, + PrinterSemanticCapsAndDefaults* printer_info) { + std::unique_ptr printer( + cups_connection_->GetPrinter(printer_name)); + if (!printer) + return false; + + CapsAndDefaultsFromPrinter(*printer, printer_info); + + return true; +} + +bool PrintBackendCupsIpp::GetPrinterCapsAndDefaults( + const std::string& printer_name, + PrinterCapsAndDefaults* printer_info) { + DCHECK(printer_info); + + // Read the ppd file for Cloud Print. We don't use PPD anymore otherwise. + std::unique_ptr printer( + cups_connection_->GetPrinter(printer_name)); + if (!printer) + return false; + + base::FilePath ppd_path(printer->GetPPD()); + // In some cases CUPS failed to get ppd file. + if (ppd_path.empty()) { + LOG(ERROR) << "CUPS: Failed to get PPD, printer name: " << printer_name; + return false; + } + + std::string content; + bool res = base::ReadFileToString(ppd_path, &content); + + base::DeleteFile(ppd_path, false); + + if (res) { + printer_info->printer_capabilities.swap(content); + printer_info->caps_mime_type = "application/pagemaker"; + // In CUPS, printer defaults is a part of PPD file. Nothing to upload here. + printer_info->printer_defaults.clear(); + printer_info->defaults_mime_type.clear(); + } + + return res; +} + +std::string PrintBackendCupsIpp::GetPrinterDriverInfo( + const std::string& printer_name) { + std::unique_ptr printer( + cups_connection_->GetPrinter(printer_name)); + if (!printer || !printer->IsAvailable()) + return std::string(); + + DCHECK_EQ(printer_name, printer->GetName()); + return printer->GetMakeAndModel(); +} + +bool PrintBackendCupsIpp::IsValidPrinter(const std::string& printer_name) { + std::unique_ptr printer( + cups_connection_->GetPrinter(printer_name)); + if (!printer) + return false; + + return printer->IsAvailable(); +} + +} // namespace printing diff --git a/printing/backend/print_backend_cups_ipp.h b/printing/backend/print_backend_cups_ipp.h new file mode 100644 index 00000000000000..06cc73dcac6d4f --- /dev/null +++ b/printing/backend/print_backend_cups_ipp.h @@ -0,0 +1,42 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_BACKEND_PRINT_BACKEND_CUPS_IPP_H_ +#define PRINTING_BACKEND_PRINT_BACKEND_CUPS_IPP_H_ + +#include +#include + +#include "printing/backend/cups_connection.h" +#include "printing/backend/print_backend.h" +#include "url/gurl.h" + +namespace printing { + +class PrintBackendCupsIpp : public PrintBackend { + public: + explicit PrintBackendCupsIpp(std::unique_ptr connection); + + private: + ~PrintBackendCupsIpp() override; + + // PrintBackend implementation. + bool EnumeratePrinters(PrinterList* printer_list) override; + std::string GetDefaultPrinterName() override; + bool GetPrinterBasicInfo(const std::string& printer_name, + PrinterBasicInfo* printer_info) override; + bool GetPrinterSemanticCapsAndDefaults( + const std::string& printer_name, + PrinterSemanticCapsAndDefaults* printer_info) override; + bool GetPrinterCapsAndDefaults(const std::string& printer_name, + PrinterCapsAndDefaults* printer_info) override; + std::string GetPrinterDriverInfo(const std::string& printer_name) override; + bool IsValidPrinter(const std::string& printer_name) override; + + std::unique_ptr cups_connection_; +}; + +} // namespace printing + +#endif // PRINTING_BACKEND_PRINT_BACKEND_CUPS_IPP_H_ diff --git a/printing/printing.gyp b/printing/printing.gyp index be202cbfdb838d..695870cac0611e 100644 --- a/printing/printing.gyp +++ b/printing/printing.gyp @@ -72,7 +72,6 @@ 'print_settings_initializer_win.h', 'printed_document.cc', 'printed_document.h', - 'printed_document_linux.cc', 'printed_document_mac.cc', 'printed_document_win.cc', 'printed_page.cc', @@ -174,10 +173,28 @@ # of the print backend and enables a custom implementation instead. 'PRINT_BACKEND_AVAILABLE', ], - 'sources': [ - 'backend/cups_helper.cc', - 'backend/cups_helper.h', - 'backend/print_backend_cups.cc', + + 'conditions': [ + ['chromeos==1', { + 'sources': [ + 'backend/cups_connection.cc', + 'backend/cups_connection.h', + 'backend/cups_deleter.cc', + 'backend/cups_deleter.h', + 'backend/cups_ipp_util.cc', + 'backend/cups_ipp_util.h', + 'backend/cups_printer.cc', + 'backend/cups_printer.h', + 'backend/print_backend_cups_ipp.cc', + 'backend/print_backend_cups_ipp.h', + ], + }, { # chromeos==0 + 'sources': [ + 'backend/cups_helper.cc', + 'backend/cups_helper.h', + 'backend/print_backend_cups.cc', + ], + }], ], }], ['OS=="linux" and chromeos==1', { @@ -192,6 +209,7 @@ }], ['OS=="linux" and chromeos==0', { 'sources': [ + 'printed_document_linux.cc', 'printing_context_linux.cc', 'printing_context_linux.h', ], @@ -247,8 +265,12 @@ 'defines': [ 'USE_CUPS', ], - 'sources': [ - 'backend/cups_helper_unittest.cc', + 'conditions': [ + ['chromeos==1', { + 'sources': ['backend/cups_ipp_util_unittest.cc'], + }, { + 'sources': ['backend/cups_helper_unittest.cc'], + }], ], }], ],