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 if (!mShim
.cupsLocalizeDestMedia
) {
121 auto printerInfoLock
= TryEnsurePrinterInfo();
122 cups_dinfo_t
* const printerInfo
= printerInfoLock
->mPrinterInfo
;
123 return mShim
.cupsLocalizeDestMedia(&aConnection
, mPrinter
, printerInfo
,
124 CUPS_MEDIA_FLAGS_DEFAULT
, &aMedia
);
127 bool nsPrinterCUPS::SupportsDuplex() const {
128 return Supports(CUPS_SIDES
, CUPS_SIDES_TWO_SIDED_PORTRAIT
);
131 bool nsPrinterCUPS::SupportsMonochrome() const {
132 if (!SupportsColor()) {
135 return StaticPrefs::print_cups_monochrome_enabled();
138 bool nsPrinterCUPS::SupportsColor() const {
139 // CUPS 2.1 (particularly as used in Ubuntu 16) is known to have inaccurate
140 // results for CUPS_PRINT_COLOR_MODE.
141 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1660658#c15
142 if (!IsCUPSVersionAtLeast(2, 2, 0)) {
143 return true; // See comment for PrintSettingsInitializer.mPrintInColor
145 return Supports(CUPS_PRINT_COLOR_MODE
, CUPS_PRINT_COLOR_MODE_AUTO
) ||
146 Supports(CUPS_PRINT_COLOR_MODE
, CUPS_PRINT_COLOR_MODE_COLOR
) ||
147 !Supports(CUPS_PRINT_COLOR_MODE
, CUPS_PRINT_COLOR_MODE_MONOCHROME
);
150 bool nsPrinterCUPS::SupportsCollation() const {
151 // We can't depend on cupsGetIntegerOption existing.
152 const char* const value
= mShim
.cupsGetOption(
153 "printer-type", mPrinter
->num_options
, mPrinter
->options
);
157 // If the value is non-numeric, then atoi will return 0, which will still
158 // cause this function to return false.
159 const int type
= atoi(value
);
160 return type
& CUPS_PRINTER_COLLATE
;
163 nsPrinterBase::PrinterInfo
nsPrinterCUPS::CreatePrinterInfo() const {
164 Connection connection
{mShim
};
165 return PrinterInfo
{PaperList(connection
), DefaultSettings(connection
)};
168 bool nsPrinterCUPS::Supports(const char* aOption
, const char* aValue
) const {
169 auto printerInfoLock
= TryEnsurePrinterInfo();
170 cups_dinfo_t
* const printerInfo
= printerInfoLock
->mPrinterInfo
;
171 return mShim
.cupsCheckDestSupported(CUPS_HTTP_DEFAULT
, mPrinter
, printerInfo
,
175 bool nsPrinterCUPS::IsCUPSVersionAtLeast(uint64_t aCUPSMajor
,
177 uint64_t aCUPSPatch
) const {
178 auto printerInfoLock
= TryEnsurePrinterInfo();
179 // Compare major version.
180 if (printerInfoLock
->mCUPSMajor
> aCUPSMajor
) {
183 if (printerInfoLock
->mCUPSMajor
< aCUPSMajor
) {
187 // Compare minor version.
188 if (printerInfoLock
->mCUPSMinor
> aCUPSMinor
) {
191 if (printerInfoLock
->mCUPSMinor
< aCUPSMinor
) {
196 return aCUPSPatch
<= printerInfoLock
->mCUPSPatch
;
199 http_t
* nsPrinterCUPS::Connection::GetConnection(cups_dest_t
* aDest
) {
206 http_t
* const connection
= mShim
.cupsConnectDest(aDest
, CUPS_DEST_FLAGS_NONE
,
207 /* timeout(ms) */ 5000,
208 /* cancel */ nullptr,
209 /* resource */ nullptr,
210 /* resourcesize */ 0,
211 /* callback */ nullptr,
212 /* user_data */ nullptr);
214 mConnection
= connection
;
219 nsPrinterCUPS::Connection::~Connection() {
220 if (mWasInited
&& mConnection
) {
221 mShim
.httpClose(mConnection
);
225 PrintSettingsInitializer
nsPrinterCUPS::DefaultSettings(
226 Connection
& aConnection
) const {
227 nsString printerName
;
228 GetPrinterName(printerName
);
229 auto printerInfoLock
= TryEnsurePrinterInfo();
230 cups_dinfo_t
* const printerInfo
= printerInfoLock
->mPrinterInfo
;
234 bool hasDefaultMedia
= false;
235 // cupsGetDestMediaDefault appears to return more accurate defaults on macOS,
236 // and the IPP attribute appears to return more accurate defaults on Linux.
239 mShim
.cupsGetDestMediaDefault(CUPS_HTTP_DEFAULT
, mPrinter
, printerInfo
,
240 CUPS_MEDIA_FLAGS_DEFAULT
, &media
);
243 ipp_attribute_t
* defaultMediaIPP
=
244 mShim
.cupsFindDestDefault
245 ? mShim
.cupsFindDestDefault(CUPS_HTTP_DEFAULT
, mPrinter
,
246 printerInfo
, "media")
249 const char* defaultMediaName
=
250 defaultMediaIPP
? mShim
.ippGetString(defaultMediaIPP
, 0, nullptr)
253 hasDefaultMedia
= defaultMediaName
&&
254 mShim
.cupsGetDestMediaByName(
255 CUPS_HTTP_DEFAULT
, mPrinter
, printerInfo
,
256 defaultMediaName
, CUPS_MEDIA_FLAGS_DEFAULT
, &media
);
260 if (!hasDefaultMedia
) {
261 return PrintSettingsInitializer
{
262 std::move(printerName
),
268 // Check if this is a localized fallback paper size, in which case we can
269 // avoid using the CUPS localization methods.
270 const gfx::SizeDouble sizeDouble
{
271 media
.width
* kPointsPerHundredthMillimeter
,
272 media
.length
* kPointsPerHundredthMillimeter
};
273 if (const PaperInfo
* const paperInfo
= FindCommonPaperSize(sizeDouble
)) {
274 return PrintSettingsInitializer
{
275 std::move(printerName
),
276 MakePaperInfo(paperInfo
->mName
, media
),
281 http_t
* const connection
= aConnection
.GetConnection(mPrinter
);
282 // XXX Do we actually have the guarantee that this is utf-8?
283 NS_ConvertUTF8toUTF16 localizedName
{
284 connection
? LocalizeMediaName(*connection
, media
) : ""};
286 return PrintSettingsInitializer
{
287 std::move(printerName
),
288 MakePaperInfo(localizedName
, media
),
293 nsTArray
<mozilla::PaperInfo
> nsPrinterCUPS::PaperList(
294 Connection
& aConnection
) const {
295 http_t
* const connection
= aConnection
.GetConnection(mPrinter
);
296 auto printerInfoLock
= TryEnsurePrinterInfo(connection
);
297 cups_dinfo_t
* const printerInfo
= printerInfoLock
->mPrinterInfo
;
303 const int paperCount
=
304 mShim
.cupsGetDestMediaCount
305 ? mShim
.cupsGetDestMediaCount(connection
, mPrinter
, printerInfo
,
306 CUPS_MEDIA_FLAGS_DEFAULT
)
308 nsTArray
<PaperInfo
> paperList
;
309 nsTHashtable
<nsCharPtrHashKey
> paperSet(std::max(paperCount
, 0));
311 paperList
.SetCapacity(paperCount
);
312 for (int i
= 0; i
< paperCount
; ++i
) {
314 const int getInfoSucceeded
= mShim
.cupsGetDestMediaByIndex(
315 connection
, mPrinter
, printerInfo
, i
, CUPS_MEDIA_FLAGS_DEFAULT
, &media
);
317 if (!getInfoSucceeded
|| !paperSet
.EnsureInserted(media
.media
)) {
320 // Check if this is a PWG paper size, in which case we can avoid using the
321 // CUPS localization methods.
322 const gfx::SizeDouble sizeDouble
{
323 media
.width
* kPointsPerHundredthMillimeter
,
324 media
.length
* kPointsPerHundredthMillimeter
};
325 if (const PaperInfo
* const paperInfo
= FindCommonPaperSize(sizeDouble
)) {
326 paperList
.AppendElement(MakePaperInfo(paperInfo
->mName
, media
));
328 const char* const mediaName
=
329 connection
? LocalizeMediaName(*connection
, media
) : media
.media
;
330 paperList
.AppendElement(
331 MakePaperInfo(NS_ConvertUTF8toUTF16(mediaName
), media
));
338 nsPrinterCUPS::PrinterInfoLock
nsPrinterCUPS::TryEnsurePrinterInfo(
339 http_t
* const aConnection
) const {
340 PrinterInfoLock lock
= mPrinterInfoMutex
.Lock();
341 if (lock
->mPrinterInfo
||
342 (aConnection
== CUPS_HTTP_DEFAULT
? lock
->mTriedInitWithDefault
343 : lock
->mTriedInitWithConnection
)) {
347 if (aConnection
== CUPS_HTTP_DEFAULT
) {
348 lock
->mTriedInitWithDefault
= true;
350 lock
->mTriedInitWithConnection
= true;
353 // All CUPS calls that take the printer info do null-checks internally, so we
354 // can fetch this info and only worry about the result of the later CUPS
356 lock
->mPrinterInfo
= mShim
.cupsCopyDestInfo(aConnection
, mPrinter
);
358 // Even if we failed to fetch printer info, it is still possible we can talk
359 // to the print server and get its CUPS version.
360 FetchCUPSVersionForPrinter(mShim
, mPrinter
, lock
->mCUPSMajor
,
361 lock
->mCUPSMinor
, lock
->mCUPSPatch
);
365 void nsPrinterCUPS::ForEachExtraMonochromeSetting(
366 FunctionRef
<void(const nsACString
&, const nsACString
&)> aCallback
) {
368 Preferences::GetCString("print.cups.monochrome.extra_settings", pref
);
369 if (pref
.IsEmpty()) {
373 for (const auto& pair
: pref
.Split(',')) {
374 auto splitter
= pair
.Split(':');
375 auto end
= splitter
.end();
377 auto key
= splitter
.begin();
382 auto value
= ++splitter
.begin();
387 aCallback(*key
, *value
);