1 /* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 2; -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsPrinterCUPS.h"
8 #include "mozilla/GkRustUtils.h"
9 #include "mozilla/StaticPrefs_print.h"
10 #include "nsTHashtable.h"
12 #include "nsPrinterBase.h"
13 #include "nsPrintSettingsImpl.h"
16 using namespace mozilla
;
18 // Requested attributes for IPP requests, just the CUPS version now.
19 static constexpr Array
<const char* const, 1> requestedAttributes
{
22 static constexpr double kPointsPerHundredthMillimeter
= 72.0 / 2540.0;
24 static PaperInfo
MakePaperInfo(const nsAString
& aName
,
25 const cups_size_t
& aMedia
) {
26 // XXX Do we actually have the guarantee that this is utf-8?
27 NS_ConvertUTF8toUTF16
paperId(aMedia
.media
); // internal paper name/ID
30 {aMedia
.width
* kPointsPerHundredthMillimeter
,
31 aMedia
.length
* kPointsPerHundredthMillimeter
},
32 Some(gfx::MarginDouble
{aMedia
.top
* kPointsPerHundredthMillimeter
,
33 aMedia
.right
* kPointsPerHundredthMillimeter
,
34 aMedia
.bottom
* kPointsPerHundredthMillimeter
,
35 aMedia
.left
* kPointsPerHundredthMillimeter
}));
38 // Fetches the CUPS version for the print server controlling the printer. This
39 // will only modify the output arguments if the fetch succeeds.
40 static void FetchCUPSVersionForPrinter(const nsCUPSShim
& aShim
,
41 const cups_dest_t
* const aDest
,
42 uint64_t& aOutMajor
, uint64_t& aOutMinor
,
43 uint64_t& aOutPatch
) {
44 // Make an IPP request to the server for the printer.
45 const char* const uri
= aShim
.cupsGetOption(
46 "printer-uri-supported", aDest
->num_options
, aDest
->options
);
51 ipp_t
* const ippRequest
= aShim
.ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES
);
53 // Set the URI we want to use.
54 aShim
.ippAddString(ippRequest
, IPP_TAG_OPERATION
, IPP_TAG_URI
, "printer-uri",
57 // Set the attributes to request.
58 aShim
.ippAddStrings(ippRequest
, IPP_TAG_OPERATION
, IPP_TAG_KEYWORD
,
59 "requested-attributes", requestedAttributes
.Length
,
60 nullptr, &(requestedAttributes
[0]));
62 // Use the default HTTP connection to query the CUPS server itself to get
64 // Note that cupsDoRequest will delete the request whether it succeeds or
65 // fails, so we should not use ippDelete on it.
66 if (ipp_t
* const ippResponse
=
67 aShim
.cupsDoRequest(CUPS_HTTP_DEFAULT
, ippRequest
, "/")) {
68 ipp_attribute_t
* const versionAttrib
=
69 aShim
.ippFindAttribute(ippResponse
, "cups-version", IPP_TAG_TEXT
);
70 if (versionAttrib
&& aShim
.ippGetCount(versionAttrib
) == 1) {
71 const char* versionString
= aShim
.ippGetString(versionAttrib
, 0, nullptr);
72 MOZ_ASSERT(versionString
);
73 // On error, GkRustUtils::ParseSemVer will not modify its arguments.
74 GkRustUtils::ParseSemVer(
75 nsDependentCSubstring
{MakeStringSpan(versionString
)}, aOutMajor
,
76 aOutMinor
, aOutPatch
);
78 aShim
.ippDelete(ippResponse
);
82 nsPrinterCUPS::~nsPrinterCUPS() {
83 auto printerInfoLock
= mPrinterInfoMutex
.Lock();
84 if (printerInfoLock
->mPrinterInfo
) {
85 mShim
.cupsFreeDestInfo(printerInfoLock
->mPrinterInfo
);
88 mShim
.cupsFreeDests(1, mPrinter
);
94 nsPrinterCUPS::GetName(nsAString
& aName
) {
95 GetPrinterName(aName
);
100 nsPrinterCUPS::GetSystemName(nsAString
& aName
) {
101 CopyUTF8toUTF16(MakeStringSpan(mPrinter
->name
), aName
);
105 void nsPrinterCUPS::GetPrinterName(nsAString
& aName
) const {
106 if (mDisplayName
.IsEmpty()) {
108 CopyUTF8toUTF16(MakeStringSpan(mPrinter
->name
), aName
);
110 aName
= mDisplayName
;
114 const char* nsPrinterCUPS::LocalizeMediaName(http_t
& aConnection
,
115 cups_size_t
& aMedia
) const {
116 // The returned string is owned by mPrinterInfo.
117 // https://www.cups.org/doc/cupspm.html#cupsLocalizeDestMedia
118 auto printerInfoLock
= TryEnsurePrinterInfo();
119 cups_dinfo_t
* const printerInfo
= printerInfoLock
->mPrinterInfo
;
120 return mShim
.cupsLocalizeDestMedia(&aConnection
, mPrinter
, printerInfo
,
121 CUPS_MEDIA_FLAGS_DEFAULT
, &aMedia
);
124 bool nsPrinterCUPS::SupportsDuplex() const {
125 return Supports(CUPS_SIDES
, CUPS_SIDES_TWO_SIDED_PORTRAIT
);
128 bool nsPrinterCUPS::SupportsMonochrome() const {
129 if (!SupportsColor()) {
132 return StaticPrefs::print_cups_monochrome_enabled();
135 bool nsPrinterCUPS::SupportsColor() const {
136 // CUPS 2.1 (particularly as used in Ubuntu 16) is known to have inaccurate
137 // results for CUPS_PRINT_COLOR_MODE.
138 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1660658#c15
139 if (!IsCUPSVersionAtLeast(2, 2, 0)) {
140 return true; // See comment for PrintSettingsInitializer.mPrintInColor
142 return Supports(CUPS_PRINT_COLOR_MODE
, CUPS_PRINT_COLOR_MODE_AUTO
) ||
143 Supports(CUPS_PRINT_COLOR_MODE
, CUPS_PRINT_COLOR_MODE_COLOR
) ||
144 !Supports(CUPS_PRINT_COLOR_MODE
, CUPS_PRINT_COLOR_MODE_MONOCHROME
);
147 bool nsPrinterCUPS::SupportsCollation() const {
148 // We can't depend on cupsGetIntegerOption existing.
149 const char* const value
= mShim
.cupsGetOption(
150 "printer-type", mPrinter
->num_options
, mPrinter
->options
);
154 // If the value is non-numeric, then atoi will return 0, which will still
155 // cause this function to return false.
156 const int type
= atoi(value
);
157 return type
& CUPS_PRINTER_COLLATE
;
160 nsPrinterBase::PrinterInfo
nsPrinterCUPS::CreatePrinterInfo() const {
161 Connection connection
{mShim
};
162 return PrinterInfo
{PaperList(connection
), DefaultSettings(connection
)};
165 bool nsPrinterCUPS::Supports(const char* aOption
, const char* aValue
) const {
166 auto printerInfoLock
= TryEnsurePrinterInfo();
167 cups_dinfo_t
* const printerInfo
= printerInfoLock
->mPrinterInfo
;
168 return mShim
.cupsCheckDestSupported(CUPS_HTTP_DEFAULT
, mPrinter
, printerInfo
,
172 bool nsPrinterCUPS::IsCUPSVersionAtLeast(uint64_t aCUPSMajor
,
174 uint64_t aCUPSPatch
) const {
175 auto printerInfoLock
= TryEnsurePrinterInfo();
176 // Compare major version.
177 if (printerInfoLock
->mCUPSMajor
> aCUPSMajor
) {
180 if (printerInfoLock
->mCUPSMajor
< aCUPSMajor
) {
184 // Compare minor version.
185 if (printerInfoLock
->mCUPSMinor
> aCUPSMinor
) {
188 if (printerInfoLock
->mCUPSMinor
< aCUPSMinor
) {
193 return aCUPSPatch
<= printerInfoLock
->mCUPSPatch
;
196 http_t
* nsPrinterCUPS::Connection::GetConnection(cups_dest_t
* aDest
) {
203 http_t
* const connection
= mShim
.cupsConnectDest(aDest
, CUPS_DEST_FLAGS_NONE
,
204 /* timeout(ms) */ 5000,
205 /* cancel */ nullptr,
206 /* resource */ nullptr,
207 /* resourcesize */ 0,
208 /* callback */ nullptr,
209 /* user_data */ nullptr);
211 mConnection
= connection
;
216 nsPrinterCUPS::Connection::~Connection() {
217 if (mWasInited
&& mConnection
) {
218 mShim
.httpClose(mConnection
);
222 PrintSettingsInitializer
nsPrinterCUPS::DefaultSettings(
223 Connection
& aConnection
) const {
224 nsString printerName
;
225 GetPrinterName(printerName
);
226 auto printerInfoLock
= TryEnsurePrinterInfo();
227 cups_dinfo_t
* const printerInfo
= printerInfoLock
->mPrinterInfo
;
231 // cupsGetDestMediaDefault appears to return more accurate defaults on macOS,
232 // and the IPP attribute appears to return more accurate defaults on Linux.
234 bool hasDefaultMedia
=
235 mShim
.cupsGetDestMediaDefault(CUPS_HTTP_DEFAULT
, mPrinter
, printerInfo
,
236 CUPS_MEDIA_FLAGS_DEFAULT
, &media
);
238 ipp_attribute_t
* defaultMediaIPP
= mShim
.cupsFindDestDefault(
239 CUPS_HTTP_DEFAULT
, mPrinter
, printerInfo
, "media");
241 const char* defaultMediaName
=
242 mShim
.ippGetString(defaultMediaIPP
, 0, nullptr);
244 bool hasDefaultMedia
= mShim
.cupsGetDestMediaByName(
245 CUPS_HTTP_DEFAULT
, mPrinter
, printerInfo
, defaultMediaName
,
246 CUPS_MEDIA_FLAGS_DEFAULT
, &media
);
249 if (!hasDefaultMedia
) {
250 return PrintSettingsInitializer
{
251 std::move(printerName
),
257 // Check if this is a localized fallback paper size, in which case we can
258 // avoid using the CUPS localization methods.
259 const gfx::SizeDouble sizeDouble
{
260 media
.width
* kPointsPerHundredthMillimeter
,
261 media
.length
* kPointsPerHundredthMillimeter
};
262 if (const PaperInfo
* const paperInfo
= FindCommonPaperSize(sizeDouble
)) {
263 return PrintSettingsInitializer
{
264 std::move(printerName
),
265 MakePaperInfo(paperInfo
->mName
, media
),
270 http_t
* const connection
= aConnection
.GetConnection(mPrinter
);
271 // XXX Do we actually have the guarantee that this is utf-8?
272 NS_ConvertUTF8toUTF16 localizedName
{
273 connection
? LocalizeMediaName(*connection
, media
) : ""};
275 return PrintSettingsInitializer
{
276 std::move(printerName
),
277 MakePaperInfo(localizedName
, media
),
282 nsTArray
<mozilla::PaperInfo
> nsPrinterCUPS::PaperList(
283 Connection
& aConnection
) const {
284 http_t
* const connection
= aConnection
.GetConnection(mPrinter
);
285 auto printerInfoLock
= TryEnsurePrinterInfo(connection
);
286 cups_dinfo_t
* const printerInfo
= printerInfoLock
->mPrinterInfo
;
292 const int paperCount
= mShim
.cupsGetDestMediaCount(
293 connection
, mPrinter
, printerInfo
, CUPS_MEDIA_FLAGS_DEFAULT
);
294 nsTArray
<PaperInfo
> paperList
;
295 nsTHashtable
<nsCharPtrHashKey
> paperSet(std::max(paperCount
, 0));
297 paperList
.SetCapacity(paperCount
);
298 for (int i
= 0; i
< paperCount
; ++i
) {
300 const int getInfoSucceeded
= mShim
.cupsGetDestMediaByIndex(
301 connection
, mPrinter
, printerInfo
, i
, CUPS_MEDIA_FLAGS_DEFAULT
, &media
);
303 if (!getInfoSucceeded
|| !paperSet
.EnsureInserted(media
.media
)) {
306 // Check if this is a PWG paper size, in which case we can avoid using the
307 // CUPS localization methods.
308 const gfx::SizeDouble sizeDouble
{
309 media
.width
* kPointsPerHundredthMillimeter
,
310 media
.length
* kPointsPerHundredthMillimeter
};
311 if (const PaperInfo
* const paperInfo
= FindCommonPaperSize(sizeDouble
)) {
312 paperList
.AppendElement(MakePaperInfo(paperInfo
->mName
, media
));
314 const char* const mediaName
=
315 connection
? LocalizeMediaName(*connection
, media
) : media
.media
;
316 paperList
.AppendElement(
317 MakePaperInfo(NS_ConvertUTF8toUTF16(mediaName
), media
));
324 nsPrinterCUPS::PrinterInfoLock
nsPrinterCUPS::TryEnsurePrinterInfo(
325 http_t
* const aConnection
) const {
326 PrinterInfoLock lock
= mPrinterInfoMutex
.Lock();
327 if (lock
->mPrinterInfo
||
328 (aConnection
== CUPS_HTTP_DEFAULT
? lock
->mTriedInitWithDefault
329 : lock
->mTriedInitWithConnection
)) {
333 if (aConnection
== CUPS_HTTP_DEFAULT
) {
334 lock
->mTriedInitWithDefault
= true;
336 lock
->mTriedInitWithConnection
= true;
339 // All CUPS calls that take the printer info do null-checks internally, so we
340 // can fetch this info and only worry about the result of the later CUPS
342 lock
->mPrinterInfo
= mShim
.cupsCopyDestInfo(aConnection
, mPrinter
);
344 // Even if we failed to fetch printer info, it is still possible we can talk
345 // to the print server and get its CUPS version.
346 FetchCUPSVersionForPrinter(mShim
, mPrinter
, lock
->mCUPSMajor
,
347 lock
->mCUPSMinor
, lock
->mCUPSPatch
);
351 void nsPrinterCUPS::ForEachExtraMonochromeSetting(
352 FunctionRef
<void(const nsACString
&, const nsACString
&)> aCallback
) {
354 Preferences::GetCString("print.cups.monochrome.extra_settings", pref
);
355 if (pref
.IsEmpty()) {
359 for (const auto& pair
: pref
.Split(',')) {
360 auto splitter
= pair
.Split(':');
361 auto end
= splitter
.end();
363 auto key
= splitter
.begin();
368 auto value
= ++splitter
.begin();
373 aCallback(*key
, *value
);