1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "printing/backend/cups_helper.h"
9 #include "base/base_paths.h"
10 #include "base/files/file_util.h"
11 #include "base/logging.h"
12 #include "base/path_service.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/string_split.h"
15 #include "base/strings/string_util.h"
16 #include "base/values.h"
17 #include "printing/backend/print_backend.h"
18 #include "printing/backend/print_backend_consts.h"
19 #include "printing/units.h"
24 // This section contains helper code for PPD parsing for semantic capabilities.
27 const char kColorDevice
[] = "ColorDevice";
28 const char kColorModel
[] = "ColorModel";
29 const char kColorMode
[] = "ColorMode";
30 const char kProcessColorModel
[] = "ProcessColorModel";
31 const char kPrintoutMode
[] = "PrintoutMode";
32 const char kDraftGray
[] = "Draft.Gray";
33 const char kHighGray
[] = "High.Gray";
35 const char kDuplex
[] = "Duplex";
36 const char kDuplexNone
[] = "None";
37 const char kPageSize
[] = "PageSize";
39 const double kMicronsPerPoint
= 10.0f
* kHundrethsMMPerInch
/ kPointsPerInch
;
41 void ParseLpOptions(const base::FilePath
& filepath
,
42 const std::string
& printer_name
,
43 int* num_options
, cups_option_t
** options
) {
45 if (!base::ReadFileToString(filepath
, &content
))
48 const char kDest
[] = "dest";
49 const char kDefault
[] = "default";
50 const size_t kDestLen
= sizeof(kDest
) - 1;
51 const size_t kDefaultLen
= sizeof(kDefault
) - 1;
53 for (base::StringPiece line
:
54 base::SplitStringPiece(content
, "\n", base::KEEP_WHITESPACE
,
55 base::SPLIT_WANT_NONEMPTY
)) {
56 if (base::StartsWith(line
, base::StringPiece(kDefault
, kDefaultLen
),
57 base::CompareCase::INSENSITIVE_ASCII
) &&
58 isspace(line
[kDefaultLen
])) {
59 line
= line
.substr(kDefaultLen
);
60 } else if (base::StartsWith(line
, base::StringPiece(kDest
, kDestLen
),
61 base::CompareCase::INSENSITIVE_ASCII
) &&
62 isspace(line
[kDestLen
])) {
63 line
= line
.substr(kDestLen
);
68 line
= base::TrimWhitespaceASCII(line
, base::TRIM_ALL
);
72 size_t space_found
= line
.find(' ');
73 if (space_found
== base::StringPiece::npos
)
76 base::StringPiece name
= line
.substr(0, space_found
);
80 if (!base::EqualsCaseInsensitiveASCII(printer_name
, name
))
81 continue; // This is not the required printer.
83 line
= line
.substr(space_found
+ 1);
84 // Remove extra spaces.
85 line
= base::TrimWhitespaceASCII(line
, base::TRIM_ALL
);
88 // Parse the selected printer custom options. Need to pass a
89 // null-terminated string.
90 *num_options
= cupsParseOptions(line
.as_string().c_str(), 0, options
);
94 void MarkLpOptions(const std::string
& printer_name
, ppd_file_t
** ppd
) {
95 cups_option_t
* options
= NULL
;
98 const char kSystemLpOptionPath
[] = "/etc/cups/lpoptions";
99 const char kUserLpOptionPath
[] = ".cups/lpoptions";
101 std::vector
<base::FilePath
> file_locations
;
102 file_locations
.push_back(base::FilePath(kSystemLpOptionPath
));
103 base::FilePath homedir
;
104 PathService::Get(base::DIR_HOME
, &homedir
);
105 file_locations
.push_back(base::FilePath(homedir
.Append(kUserLpOptionPath
)));
107 for (std::vector
<base::FilePath
>::const_iterator it
= file_locations
.begin();
108 it
!= file_locations
.end(); ++it
) {
111 ParseLpOptions(*it
, printer_name
, &num_options
, &options
);
112 if (num_options
> 0 && options
) {
113 cupsMarkOptions(*ppd
, num_options
, options
);
114 cupsFreeOptions(num_options
, options
);
119 bool GetBasicColorModelSettings(ppd_file_t
* ppd
,
120 ColorModel
* color_model_for_black
,
121 ColorModel
* color_model_for_color
,
122 bool* color_is_default
) {
123 ppd_option_t
* color_model
= ppdFindOption(ppd
, kColorModel
);
127 if (ppdFindChoice(color_model
, printing::kBlack
))
128 *color_model_for_black
= printing::BLACK
;
129 else if (ppdFindChoice(color_model
, printing::kGray
))
130 *color_model_for_black
= printing::GRAY
;
131 else if (ppdFindChoice(color_model
, printing::kGrayscale
))
132 *color_model_for_black
= printing::GRAYSCALE
;
134 if (ppdFindChoice(color_model
, printing::kColor
))
135 *color_model_for_color
= printing::COLOR
;
136 else if (ppdFindChoice(color_model
, printing::kCMYK
))
137 *color_model_for_color
= printing::CMYK
;
138 else if (ppdFindChoice(color_model
, printing::kRGB
))
139 *color_model_for_color
= printing::RGB
;
140 else if (ppdFindChoice(color_model
, printing::kRGBA
))
141 *color_model_for_color
= printing::RGBA
;
142 else if (ppdFindChoice(color_model
, printing::kRGB16
))
143 *color_model_for_color
= printing::RGB16
;
144 else if (ppdFindChoice(color_model
, printing::kCMY
))
145 *color_model_for_color
= printing::CMY
;
146 else if (ppdFindChoice(color_model
, printing::kKCMY
))
147 *color_model_for_color
= printing::KCMY
;
148 else if (ppdFindChoice(color_model
, printing::kCMY_K
))
149 *color_model_for_color
= printing::CMY_K
;
151 ppd_choice_t
* marked_choice
= ppdFindMarkedChoice(ppd
, kColorModel
);
153 marked_choice
= ppdFindChoice(color_model
, color_model
->defchoice
);
157 !base::EqualsCaseInsensitiveASCII(marked_choice
->choice
,
159 !base::EqualsCaseInsensitiveASCII(marked_choice
->choice
,
161 !base::EqualsCaseInsensitiveASCII(marked_choice
->choice
,
162 printing::kGrayscale
);
167 bool GetPrintOutModeColorSettings(ppd_file_t
* ppd
,
168 ColorModel
* color_model_for_black
,
169 ColorModel
* color_model_for_color
,
170 bool* color_is_default
) {
171 ppd_option_t
* printout_mode
= ppdFindOption(ppd
, kPrintoutMode
);
175 *color_model_for_color
= printing::PRINTOUTMODE_NORMAL
;
176 *color_model_for_black
= printing::PRINTOUTMODE_NORMAL
;
178 // Check to see if NORMAL_GRAY value is supported by PrintoutMode.
179 // If NORMAL_GRAY is not supported, NORMAL value is used to
180 // represent grayscale. If NORMAL_GRAY is supported, NORMAL is used to
182 if (ppdFindChoice(printout_mode
, printing::kNormalGray
))
183 *color_model_for_black
= printing::PRINTOUTMODE_NORMAL_GRAY
;
185 // Get the default marked choice to identify the default color setting
187 ppd_choice_t
* printout_mode_choice
= ppdFindMarkedChoice(ppd
, kPrintoutMode
);
188 if (!printout_mode_choice
) {
189 printout_mode_choice
= ppdFindChoice(printout_mode
,
190 printout_mode
->defchoice
);
192 if (printout_mode_choice
) {
193 if (base::EqualsCaseInsensitiveASCII(printout_mode_choice
->choice
,
194 printing::kNormalGray
) ||
195 base::EqualsCaseInsensitiveASCII(printout_mode_choice
->choice
,
197 base::EqualsCaseInsensitiveASCII(printout_mode_choice
->choice
,
199 *color_model_for_black
= printing::PRINTOUTMODE_NORMAL_GRAY
;
200 *color_is_default
= false;
206 bool GetColorModeSettings(ppd_file_t
* ppd
,
207 ColorModel
* color_model_for_black
,
208 ColorModel
* color_model_for_color
,
209 bool* color_is_default
) {
210 // Samsung printers use "ColorMode" attribute in their ppds.
211 ppd_option_t
* color_mode_option
= ppdFindOption(ppd
, kColorMode
);
212 if (!color_mode_option
)
215 if (ppdFindChoice(color_mode_option
, printing::kColor
))
216 *color_model_for_color
= printing::COLORMODE_COLOR
;
218 if (ppdFindChoice(color_mode_option
, printing::kMonochrome
))
219 *color_model_for_black
= printing::COLORMODE_MONOCHROME
;
221 ppd_choice_t
* mode_choice
= ppdFindMarkedChoice(ppd
, kColorMode
);
223 mode_choice
= ppdFindChoice(color_mode_option
,
224 color_mode_option
->defchoice
);
228 *color_is_default
= base::EqualsCaseInsensitiveASCII(
229 mode_choice
->choice
, printing::kColor
);
234 bool GetHPColorSettings(ppd_file_t
* ppd
,
235 ColorModel
* color_model_for_black
,
236 ColorModel
* color_model_for_color
,
237 bool* color_is_default
) {
238 // HP printers use "Color/Color Model" attribute in their ppds.
239 ppd_option_t
* color_mode_option
= ppdFindOption(ppd
, printing::kColor
);
240 if (!color_mode_option
)
243 if (ppdFindChoice(color_mode_option
, printing::kColor
))
244 *color_model_for_color
= printing::HP_COLOR_COLOR
;
245 if (ppdFindChoice(color_mode_option
, printing::kBlack
))
246 *color_model_for_black
= printing::HP_COLOR_BLACK
;
248 ppd_choice_t
* mode_choice
= ppdFindMarkedChoice(ppd
, kColorMode
);
250 mode_choice
= ppdFindChoice(color_mode_option
,
251 color_mode_option
->defchoice
);
254 *color_is_default
= base::EqualsCaseInsensitiveASCII(
255 mode_choice
->choice
, printing::kColor
);
260 bool GetProcessColorModelSettings(ppd_file_t
* ppd
,
261 ColorModel
* color_model_for_black
,
262 ColorModel
* color_model_for_color
,
263 bool* color_is_default
) {
264 // Canon printers use "ProcessColorModel" attribute in their ppds.
265 ppd_option_t
* color_mode_option
= ppdFindOption(ppd
, kProcessColorModel
);
266 if (!color_mode_option
)
269 if (ppdFindChoice(color_mode_option
, printing::kRGB
))
270 *color_model_for_color
= printing::PROCESSCOLORMODEL_RGB
;
271 else if (ppdFindChoice(color_mode_option
, printing::kCMYK
))
272 *color_model_for_color
= printing::PROCESSCOLORMODEL_CMYK
;
274 if (ppdFindChoice(color_mode_option
, printing::kGreyscale
))
275 *color_model_for_black
= printing::PROCESSCOLORMODEL_GREYSCALE
;
277 ppd_choice_t
* mode_choice
= ppdFindMarkedChoice(ppd
, kProcessColorModel
);
279 mode_choice
= ppdFindChoice(color_mode_option
,
280 color_mode_option
->defchoice
);
284 *color_is_default
= !base::EqualsCaseInsensitiveASCII(
285 mode_choice
->choice
, printing::kGreyscale
);
290 bool GetColorModelSettings(ppd_file_t
* ppd
,
291 ColorModel
* cm_black
,
292 ColorModel
* cm_color
,
294 bool is_color_device
= false;
295 ppd_attr_t
* attr
= ppdFindAttr(ppd
, kColorDevice
, NULL
);
296 if (attr
&& attr
->value
)
297 is_color_device
= ppd
->color_device
;
299 *is_color
= is_color_device
;
300 return (is_color_device
&&
301 GetBasicColorModelSettings(ppd
, cm_black
, cm_color
, is_color
)) ||
302 GetPrintOutModeColorSettings(ppd
, cm_black
, cm_color
, is_color
) ||
303 GetColorModeSettings(ppd
, cm_black
, cm_color
, is_color
) ||
304 GetHPColorSettings(ppd
, cm_black
, cm_color
, is_color
) ||
305 GetProcessColorModelSettings(ppd
, cm_black
, cm_color
, is_color
);
308 // Default port for IPP print servers.
309 const int kDefaultIPPServerPort
= 631;
313 // Helper wrapper around http_t structure, with connection and cleanup
315 HttpConnectionCUPS::HttpConnectionCUPS(const GURL
& print_server_url
,
316 http_encryption_t encryption
)
318 // If we have an empty url, use default print server.
319 if (print_server_url
.is_empty())
322 int port
= print_server_url
.IntPort();
323 if (port
== url::PORT_UNSPECIFIED
)
324 port
= kDefaultIPPServerPort
;
326 http_
= httpConnectEncrypt(print_server_url
.host().c_str(), port
, encryption
);
328 LOG(ERROR
) << "CP_CUPS: Failed connecting to print server: "
333 HttpConnectionCUPS::~HttpConnectionCUPS() {
338 void HttpConnectionCUPS::SetBlocking(bool blocking
) {
339 httpBlocking(http_
, blocking
? 1 : 0);
342 http_t
* HttpConnectionCUPS::http() {
346 bool ParsePpdCapabilities(
347 const std::string
& printer_name
,
348 const std::string
& printer_capabilities
,
349 PrinterSemanticCapsAndDefaults
* printer_info
) {
350 base::FilePath ppd_file_path
;
351 if (!base::CreateTemporaryFile(&ppd_file_path
))
354 int data_size
= printer_capabilities
.length();
355 if (data_size
!= base::WriteFile(
357 printer_capabilities
.data(),
359 base::DeleteFile(ppd_file_path
, false);
363 ppd_file_t
* ppd
= ppdOpenFile(ppd_file_path
.value().c_str());
366 ppd_status_t ppd_status
= ppdLastError(&line
);
367 LOG(ERROR
) << "Failed to open PDD file: error " << ppd_status
<< " at line "
368 << line
<< ", " << ppdErrorString(ppd_status
);
371 ppdMarkDefaults(ppd
);
372 MarkLpOptions(printer_name
, &ppd
);
374 printing::PrinterSemanticCapsAndDefaults caps
;
375 caps
.collate_capable
= true;
376 caps
.collate_default
= true;
377 caps
.copies_capable
= true;
379 ppd_choice_t
* duplex_choice
= ppdFindMarkedChoice(ppd
, kDuplex
);
380 if (!duplex_choice
) {
381 ppd_option_t
* option
= ppdFindOption(ppd
, kDuplex
);
383 duplex_choice
= ppdFindChoice(option
, option
->defchoice
);
387 caps
.duplex_capable
= true;
388 if (!base::EqualsCaseInsensitiveASCII(duplex_choice
->choice
, kDuplexNone
))
389 caps
.duplex_default
= printing::LONG_EDGE
;
391 caps
.duplex_default
= printing::SIMPLEX
;
394 bool is_color
= false;
395 ColorModel cm_color
= UNKNOWN_COLOR_MODEL
, cm_black
= UNKNOWN_COLOR_MODEL
;
396 if (!GetColorModelSettings(ppd
, &cm_black
, &cm_color
, &is_color
)) {
397 VLOG(1) << "Unknown printer color model";
400 caps
.color_changeable
= ((cm_color
!= UNKNOWN_COLOR_MODEL
) &&
401 (cm_black
!= UNKNOWN_COLOR_MODEL
) &&
402 (cm_color
!= cm_black
));
403 caps
.color_default
= is_color
;
404 caps
.color_model
= cm_color
;
405 caps
.bw_model
= cm_black
;
407 if (ppd
->num_sizes
> 0 && ppd
->sizes
) {
408 VLOG(1) << "Paper list size - " << ppd
->num_sizes
;
409 ppd_option_t
* paper_option
= ppdFindOption(ppd
, kPageSize
);
410 for (int i
= 0; i
< ppd
->num_sizes
; ++i
) {
411 gfx::Size
paper_size_microns(
412 static_cast<int>(ppd
->sizes
[i
].width
* kMicronsPerPoint
+ 0.5),
413 static_cast<int>(ppd
->sizes
[i
].length
* kMicronsPerPoint
+ 0.5));
414 if (paper_size_microns
.width() > 0 && paper_size_microns
.height() > 0) {
415 PrinterSemanticCapsAndDefaults::Paper paper
;
416 paper
.size_um
= paper_size_microns
;
417 paper
.vendor_id
= ppd
->sizes
[i
].name
;
419 ppd_choice_t
* paper_choice
=
420 ppdFindChoice(paper_option
, ppd
->sizes
[i
].name
);
421 // Human readable paper name should be UTF-8 encoded, but some PPDs
422 // do not follow this standard.
423 if (paper_choice
&& base::IsStringUTF8(paper_choice
->text
)) {
424 paper
.display_name
= paper_choice
->text
;
427 caps
.papers
.push_back(paper
);
428 if (i
== 0 || ppd
->sizes
[i
].marked
) {
429 caps
.default_paper
= paper
;
436 base::DeleteFile(ppd_file_path
, false);
438 *printer_info
= caps
;
442 } // namespace printing