| // Copyright (c) 2012 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_helper.h" |
| |
| #include <cups/ppd.h> |
| |
| #include "base/base_paths.h" |
| #include "base/files/file_util.h" |
| #include "base/logging.h" |
| #include "base/path_service.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "base/values.h" |
| #include "printing/backend/print_backend.h" |
| #include "printing/backend/print_backend_consts.h" |
| #include "printing/units.h" |
| #include "url/gurl.h" |
| |
| namespace printing { |
| |
| // This section contains helper code for PPD parsing for semantic capabilities. |
| namespace { |
| |
| const char kColorDevice[] = "ColorDevice"; |
| const char kColorModel[] = "ColorModel"; |
| const char kColorMode[] = "ColorMode"; |
| const char kProcessColorModel[] = "ProcessColorModel"; |
| const char kPrintoutMode[] = "PrintoutMode"; |
| const char kDraftGray[] = "Draft.Gray"; |
| const char kHighGray[] = "High.Gray"; |
| |
| const char kDuplex[] = "Duplex"; |
| const char kDuplexNone[] = "None"; |
| const char kPageSize[] = "PageSize"; |
| |
| const double kMicronsPerPoint = 10.0f * kHundrethsMMPerInch / kPointsPerInch; |
| |
| void ParseLpOptions(const base::FilePath& filepath, |
| const std::string& printer_name, |
| int* num_options, cups_option_t** options) { |
| std::string content; |
| if (!base::ReadFileToString(filepath, &content)) |
| return; |
| |
| const char kDest[] = "dest"; |
| const char kDefault[] = "default"; |
| const size_t kDestLen = sizeof(kDest) - 1; |
| const size_t kDefaultLen = sizeof(kDefault) - 1; |
| |
| for (base::StringPiece line : |
| base::SplitStringPiece(content, "\n", base::KEEP_WHITESPACE, |
| base::SPLIT_WANT_NONEMPTY)) { |
| if (base::StartsWith(line, base::StringPiece(kDefault, kDefaultLen), |
| base::CompareCase::INSENSITIVE_ASCII) && |
| isspace(line[kDefaultLen])) { |
| line = line.substr(kDefaultLen); |
| } else if (base::StartsWith(line, base::StringPiece(kDest, kDestLen), |
| base::CompareCase::INSENSITIVE_ASCII) && |
| isspace(line[kDestLen])) { |
| line = line.substr(kDestLen); |
| } else { |
| continue; |
| } |
| |
| line = base::TrimWhitespaceASCII(line, base::TRIM_ALL); |
| if (line.empty()) |
| continue; |
| |
| size_t space_found = line.find(' '); |
| if (space_found == base::StringPiece::npos) |
| continue; |
| |
| base::StringPiece name = line.substr(0, space_found); |
| if (name.empty()) |
| continue; |
| |
| if (!base::EqualsCaseInsensitiveASCII(printer_name, name)) |
| continue; // This is not the required printer. |
| |
| line = line.substr(space_found + 1); |
| // Remove extra spaces. |
| line = base::TrimWhitespaceASCII(line, base::TRIM_ALL); |
| if (line.empty()) |
| continue; |
| // Parse the selected printer custom options. Need to pass a |
| // null-terminated string. |
| *num_options = cupsParseOptions(line.as_string().c_str(), 0, options); |
| } |
| } |
| |
| void MarkLpOptions(const std::string& printer_name, ppd_file_t** ppd) { |
| cups_option_t* options = NULL; |
| int num_options = 0; |
| |
| const char kSystemLpOptionPath[] = "/etc/cups/lpoptions"; |
| const char kUserLpOptionPath[] = ".cups/lpoptions"; |
| |
| std::vector<base::FilePath> file_locations; |
| file_locations.push_back(base::FilePath(kSystemLpOptionPath)); |
| base::FilePath homedir; |
| PathService::Get(base::DIR_HOME, &homedir); |
| file_locations.push_back(base::FilePath(homedir.Append(kUserLpOptionPath))); |
| |
| for (std::vector<base::FilePath>::const_iterator it = file_locations.begin(); |
| it != file_locations.end(); ++it) { |
| num_options = 0; |
| options = NULL; |
| ParseLpOptions(*it, printer_name, &num_options, &options); |
| if (num_options > 0 && options) { |
| cupsMarkOptions(*ppd, num_options, options); |
| cupsFreeOptions(num_options, options); |
| } |
| } |
| } |
| |
| bool GetBasicColorModelSettings(ppd_file_t* ppd, |
| ColorModel* color_model_for_black, |
| ColorModel* color_model_for_color, |
| bool* color_is_default) { |
| ppd_option_t* color_model = ppdFindOption(ppd, kColorModel); |
| if (!color_model) |
| return false; |
| |
| if (ppdFindChoice(color_model, printing::kBlack)) |
| *color_model_for_black = printing::BLACK; |
| else if (ppdFindChoice(color_model, printing::kGray)) |
| *color_model_for_black = printing::GRAY; |
| else if (ppdFindChoice(color_model, printing::kGrayscale)) |
| *color_model_for_black = printing::GRAYSCALE; |
| |
| if (ppdFindChoice(color_model, printing::kColor)) |
| *color_model_for_color = printing::COLOR; |
| else if (ppdFindChoice(color_model, printing::kCMYK)) |
| *color_model_for_color = printing::CMYK; |
| else if (ppdFindChoice(color_model, printing::kRGB)) |
| *color_model_for_color = printing::RGB; |
| else if (ppdFindChoice(color_model, printing::kRGBA)) |
| *color_model_for_color = printing::RGBA; |
| else if (ppdFindChoice(color_model, printing::kRGB16)) |
| *color_model_for_color = printing::RGB16; |
| else if (ppdFindChoice(color_model, printing::kCMY)) |
| *color_model_for_color = printing::CMY; |
| else if (ppdFindChoice(color_model, printing::kKCMY)) |
| *color_model_for_color = printing::KCMY; |
| else if (ppdFindChoice(color_model, printing::kCMY_K)) |
| *color_model_for_color = printing::CMY_K; |
| |
| ppd_choice_t* marked_choice = ppdFindMarkedChoice(ppd, kColorModel); |
| if (!marked_choice) |
| marked_choice = ppdFindChoice(color_model, color_model->defchoice); |
| |
| if (marked_choice) { |
| *color_is_default = |
| !base::EqualsCaseInsensitiveASCII(marked_choice->choice, |
| printing::kBlack) && |
| !base::EqualsCaseInsensitiveASCII(marked_choice->choice, |
| printing::kGray) && |
| !base::EqualsCaseInsensitiveASCII(marked_choice->choice, |
| printing::kGrayscale); |
| } |
| return true; |
| } |
| |
| bool GetPrintOutModeColorSettings(ppd_file_t* ppd, |
| ColorModel* color_model_for_black, |
| ColorModel* color_model_for_color, |
| bool* color_is_default) { |
| ppd_option_t* printout_mode = ppdFindOption(ppd, kPrintoutMode); |
| if (!printout_mode) |
| return false; |
| |
| *color_model_for_color = printing::PRINTOUTMODE_NORMAL; |
| *color_model_for_black = printing::PRINTOUTMODE_NORMAL; |
| |
| // Check to see if NORMAL_GRAY value is supported by PrintoutMode. |
| // If NORMAL_GRAY is not supported, NORMAL value is used to |
| // represent grayscale. If NORMAL_GRAY is supported, NORMAL is used to |
| // represent color. |
| if (ppdFindChoice(printout_mode, printing::kNormalGray)) |
| *color_model_for_black = printing::PRINTOUTMODE_NORMAL_GRAY; |
| |
| // Get the default marked choice to identify the default color setting |
| // value. |
| ppd_choice_t* printout_mode_choice = ppdFindMarkedChoice(ppd, kPrintoutMode); |
| if (!printout_mode_choice) { |
| printout_mode_choice = ppdFindChoice(printout_mode, |
| printout_mode->defchoice); |
| } |
| if (printout_mode_choice) { |
| if (base::EqualsCaseInsensitiveASCII(printout_mode_choice->choice, |
| printing::kNormalGray) || |
| base::EqualsCaseInsensitiveASCII(printout_mode_choice->choice, |
| kHighGray) || |
| base::EqualsCaseInsensitiveASCII(printout_mode_choice->choice, |
| kDraftGray)) { |
| *color_model_for_black = printing::PRINTOUTMODE_NORMAL_GRAY; |
| *color_is_default = false; |
| } |
| } |
| return true; |
| } |
| |
| bool GetColorModeSettings(ppd_file_t* ppd, |
| ColorModel* color_model_for_black, |
| ColorModel* color_model_for_color, |
| bool* color_is_default) { |
| // Samsung printers use "ColorMode" attribute in their ppds. |
| ppd_option_t* color_mode_option = ppdFindOption(ppd, kColorMode); |
| if (!color_mode_option) |
| return false; |
| |
| if (ppdFindChoice(color_mode_option, printing::kColor)) |
| *color_model_for_color = printing::COLORMODE_COLOR; |
| |
| if (ppdFindChoice(color_mode_option, printing::kMonochrome)) |
| *color_model_for_black = printing::COLORMODE_MONOCHROME; |
| |
| ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kColorMode); |
| if (!mode_choice) { |
| mode_choice = ppdFindChoice(color_mode_option, |
| color_mode_option->defchoice); |
| } |
| |
| if (mode_choice) { |
| *color_is_default = base::EqualsCaseInsensitiveASCII( |
| mode_choice->choice, printing::kColor); |
| } |
| return true; |
| } |
| |
| bool GetHPColorSettings(ppd_file_t* ppd, |
| ColorModel* color_model_for_black, |
| ColorModel* color_model_for_color, |
| bool* color_is_default) { |
| // HP printers use "Color/Color Model" attribute in their ppds. |
| ppd_option_t* color_mode_option = ppdFindOption(ppd, printing::kColor); |
| if (!color_mode_option) |
| return false; |
| |
| if (ppdFindChoice(color_mode_option, printing::kColor)) |
| *color_model_for_color = printing::HP_COLOR_COLOR; |
| if (ppdFindChoice(color_mode_option, printing::kBlack)) |
| *color_model_for_black = printing::HP_COLOR_BLACK; |
| |
| ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kColorMode); |
| if (!mode_choice) { |
| mode_choice = ppdFindChoice(color_mode_option, |
| color_mode_option->defchoice); |
| } |
| if (mode_choice) { |
| *color_is_default = base::EqualsCaseInsensitiveASCII( |
| mode_choice->choice, printing::kColor); |
| } |
| return true; |
| } |
| |
| bool GetProcessColorModelSettings(ppd_file_t* ppd, |
| ColorModel* color_model_for_black, |
| ColorModel* color_model_for_color, |
| bool* color_is_default) { |
| // Canon printers use "ProcessColorModel" attribute in their ppds. |
| ppd_option_t* color_mode_option = ppdFindOption(ppd, kProcessColorModel); |
| if (!color_mode_option) |
| return false; |
| |
| if (ppdFindChoice(color_mode_option, printing::kRGB)) |
| *color_model_for_color = printing::PROCESSCOLORMODEL_RGB; |
| else if (ppdFindChoice(color_mode_option, printing::kCMYK)) |
| *color_model_for_color = printing::PROCESSCOLORMODEL_CMYK; |
| |
| if (ppdFindChoice(color_mode_option, printing::kGreyscale)) |
| *color_model_for_black = printing::PROCESSCOLORMODEL_GREYSCALE; |
| |
| ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kProcessColorModel); |
| if (!mode_choice) { |
| mode_choice = ppdFindChoice(color_mode_option, |
| color_mode_option->defchoice); |
| } |
| |
| if (mode_choice) { |
| *color_is_default = !base::EqualsCaseInsensitiveASCII( |
| mode_choice->choice, printing::kGreyscale); |
| } |
| return true; |
| } |
| |
| bool GetColorModelSettings(ppd_file_t* ppd, |
| ColorModel* cm_black, |
| ColorModel* cm_color, |
| bool* is_color) { |
| bool is_color_device = false; |
| ppd_attr_t* attr = ppdFindAttr(ppd, kColorDevice, NULL); |
| if (attr && attr->value) |
| is_color_device = ppd->color_device; |
| |
| *is_color = is_color_device; |
| return (is_color_device && |
| GetBasicColorModelSettings(ppd, cm_black, cm_color, is_color)) || |
| GetPrintOutModeColorSettings(ppd, cm_black, cm_color, is_color) || |
| GetColorModeSettings(ppd, cm_black, cm_color, is_color) || |
| GetHPColorSettings(ppd, cm_black, cm_color, is_color) || |
| GetProcessColorModelSettings(ppd, cm_black, cm_color, is_color); |
| } |
| |
| // Default port for IPP print servers. |
| const int kDefaultIPPServerPort = 631; |
| |
| } // namespace |
| |
| // Helper wrapper around http_t structure, with connection and cleanup |
| // functionality. |
| HttpConnectionCUPS::HttpConnectionCUPS(const GURL& print_server_url, |
| http_encryption_t encryption) |
| : http_(NULL) { |
| // If we have an empty url, use default print server. |
| if (print_server_url.is_empty()) |
| return; |
| |
| int port = print_server_url.IntPort(); |
| if (port == url::PORT_UNSPECIFIED) |
| port = kDefaultIPPServerPort; |
| |
| http_ = httpConnectEncrypt(print_server_url.host().c_str(), port, encryption); |
| if (http_ == NULL) { |
| LOG(ERROR) << "CP_CUPS: Failed connecting to print server: " |
| << print_server_url; |
| } |
| } |
| |
| HttpConnectionCUPS::~HttpConnectionCUPS() { |
| if (http_ != NULL) |
| httpClose(http_); |
| } |
| |
| void HttpConnectionCUPS::SetBlocking(bool blocking) { |
| httpBlocking(http_, blocking ? 1 : 0); |
| } |
| |
| http_t* HttpConnectionCUPS::http() { |
| return http_; |
| } |
| |
| bool ParsePpdCapabilities( |
| const std::string& printer_name, |
| const std::string& printer_capabilities, |
| PrinterSemanticCapsAndDefaults* printer_info) { |
| base::FilePath ppd_file_path; |
| if (!base::CreateTemporaryFile(&ppd_file_path)) |
| return false; |
| |
| int data_size = printer_capabilities.length(); |
| if (data_size != base::WriteFile( |
| ppd_file_path, |
| printer_capabilities.data(), |
| data_size)) { |
| base::DeleteFile(ppd_file_path, false); |
| return false; |
| } |
| |
| ppd_file_t* ppd = ppdOpenFile(ppd_file_path.value().c_str()); |
| if (!ppd) { |
| int line = 0; |
| ppd_status_t ppd_status = ppdLastError(&line); |
| LOG(ERROR) << "Failed to open PDD file: error " << ppd_status << " at line " |
| << line << ", " << ppdErrorString(ppd_status); |
| return false; |
| } |
| ppdMarkDefaults(ppd); |
| MarkLpOptions(printer_name, &ppd); |
| |
| printing::PrinterSemanticCapsAndDefaults caps; |
| caps.collate_capable = true; |
| caps.collate_default = true; |
| caps.copies_capable = true; |
| |
| ppd_choice_t* duplex_choice = ppdFindMarkedChoice(ppd, kDuplex); |
| if (!duplex_choice) { |
| ppd_option_t* option = ppdFindOption(ppd, kDuplex); |
| if (option) |
| duplex_choice = ppdFindChoice(option, option->defchoice); |
| } |
| |
| if (duplex_choice) { |
| caps.duplex_capable = true; |
| if (!base::EqualsCaseInsensitiveASCII(duplex_choice->choice, kDuplexNone)) |
| caps.duplex_default = printing::LONG_EDGE; |
| else |
| caps.duplex_default = printing::SIMPLEX; |
| } |
| |
| bool is_color = false; |
| ColorModel cm_color = UNKNOWN_COLOR_MODEL, cm_black = UNKNOWN_COLOR_MODEL; |
| if (!GetColorModelSettings(ppd, &cm_black, &cm_color, &is_color)) { |
| VLOG(1) << "Unknown printer color model"; |
| } |
| |
| caps.color_changeable = ((cm_color != UNKNOWN_COLOR_MODEL) && |
| (cm_black != UNKNOWN_COLOR_MODEL) && |
| (cm_color != cm_black)); |
| caps.color_default = is_color; |
| caps.color_model = cm_color; |
| caps.bw_model = cm_black; |
| |
| if (ppd->num_sizes > 0 && ppd->sizes) { |
| VLOG(1) << "Paper list size - " << ppd->num_sizes; |
| ppd_option_t* paper_option = ppdFindOption(ppd, kPageSize); |
| for (int i = 0; i < ppd->num_sizes; ++i) { |
| gfx::Size paper_size_microns( |
| static_cast<int>(ppd->sizes[i].width * kMicronsPerPoint + 0.5), |
| static_cast<int>(ppd->sizes[i].length * kMicronsPerPoint + 0.5)); |
| if (paper_size_microns.width() > 0 && paper_size_microns.height() > 0) { |
| PrinterSemanticCapsAndDefaults::Paper paper; |
| paper.size_um = paper_size_microns; |
| paper.vendor_id = ppd->sizes[i].name; |
| if (paper_option) { |
| ppd_choice_t* paper_choice = |
| ppdFindChoice(paper_option, ppd->sizes[i].name); |
| // Human readable paper name should be UTF-8 encoded, but some PPDs |
| // do not follow this standard. |
| if (paper_choice && base::IsStringUTF8(paper_choice->text)) { |
| paper.display_name = paper_choice->text; |
| } |
| } |
| caps.papers.push_back(paper); |
| if (i == 0 || ppd->sizes[i].marked) { |
| caps.default_paper = paper; |
| } |
| } |
| } |
| } |
| |
| ppdClose(ppd); |
| base::DeleteFile(ppd_file_path, false); |
| |
| *printer_info = caps; |
| return true; |
| } |
| |
| } // namespace printing |