Bug 1692971 [wpt PR 27638] - WebKit export of https://bugs.webkit.org/show_bug.cgi...
[gecko.git] / widget / nsPrinterCUPS.cpp
blob7adbaf0d986b65addbc21c7c479b24fa298ccfea
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"
11 #include "nsPaper.h"
12 #include "nsPrinterBase.h"
13 #include "nsPrintSettingsImpl.h"
14 #include "plstr.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{
20 "cups-version"};
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
28 return PaperInfo(
29 paperId, aName,
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);
47 if (!uri) {
48 return;
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",
55 nullptr, 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
63 // the CUPS version.
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);
87 if (mPrinter) {
88 mShim.cupsFreeDests(1, mPrinter);
89 mPrinter = nullptr;
93 NS_IMETHODIMP
94 nsPrinterCUPS::GetName(nsAString& aName) {
95 GetPrinterName(aName);
96 return NS_OK;
99 NS_IMETHODIMP
100 nsPrinterCUPS::GetSystemName(nsAString& aName) {
101 CopyUTF8toUTF16(MakeStringSpan(mPrinter->name), aName);
102 return NS_OK;
105 void nsPrinterCUPS::GetPrinterName(nsAString& aName) const {
106 if (mDisplayName.IsEmpty()) {
107 aName.Truncate();
108 CopyUTF8toUTF16(MakeStringSpan(mPrinter->name), aName);
109 } else {
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()) {
130 return true;
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);
151 if (!value) {
152 return false;
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,
169 aOption, aValue);
172 bool nsPrinterCUPS::IsCUPSVersionAtLeast(uint64_t aCUPSMajor,
173 uint64_t aCUPSMinor,
174 uint64_t aCUPSPatch) const {
175 auto printerInfoLock = TryEnsurePrinterInfo();
176 // Compare major version.
177 if (printerInfoLock->mCUPSMajor > aCUPSMajor) {
178 return true;
180 if (printerInfoLock->mCUPSMajor < aCUPSMajor) {
181 return false;
184 // Compare minor version.
185 if (printerInfoLock->mCUPSMinor > aCUPSMinor) {
186 return true;
188 if (printerInfoLock->mCUPSMinor < aCUPSMinor) {
189 return false;
192 // Compare patch.
193 return aCUPSPatch <= printerInfoLock->mCUPSPatch;
196 http_t* nsPrinterCUPS::Connection::GetConnection(cups_dest_t* aDest) {
197 if (mWasInited) {
198 return mConnection;
200 mWasInited = true;
202 // blocking call
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);
210 if (connection) {
211 mConnection = connection;
213 return mConnection;
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;
229 cups_size_t media;
231 // cupsGetDestMediaDefault appears to return more accurate defaults on macOS,
232 // and the IPP attribute appears to return more accurate defaults on Linux.
233 #ifdef XP_MACOSX
234 bool hasDefaultMedia =
235 mShim.cupsGetDestMediaDefault(CUPS_HTTP_DEFAULT, mPrinter, printerInfo,
236 CUPS_MEDIA_FLAGS_DEFAULT, &media);
237 #else
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);
247 #endif
249 if (!hasDefaultMedia) {
250 return PrintSettingsInitializer{
251 std::move(printerName),
252 PaperInfo(),
253 SupportsColor(),
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),
266 SupportsColor(),
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),
278 SupportsColor(),
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;
288 if (!printerInfo) {
289 return {};
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) {
299 cups_size_t media;
300 const int getInfoSucceeded = mShim.cupsGetDestMediaByIndex(
301 connection, mPrinter, printerInfo, i, CUPS_MEDIA_FLAGS_DEFAULT, &media);
303 if (!getInfoSucceeded || !paperSet.EnsureInserted(media.media)) {
304 continue;
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));
313 } else {
314 const char* const mediaName =
315 connection ? LocalizeMediaName(*connection, media) : media.media;
316 paperList.AppendElement(
317 MakePaperInfo(NS_ConvertUTF8toUTF16(mediaName), media));
321 return paperList;
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)) {
330 return lock;
333 if (aConnection == CUPS_HTTP_DEFAULT) {
334 lock->mTriedInitWithDefault = true;
335 } else {
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
341 // functions.
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);
348 return lock;
351 void nsPrinterCUPS::ForEachExtraMonochromeSetting(
352 FunctionRef<void(const nsACString&, const nsACString&)> aCallback) {
353 nsAutoCString pref;
354 Preferences::GetCString("print.cups.monochrome.extra_settings", pref);
355 if (pref.IsEmpty()) {
356 return;
359 for (const auto& pair : pref.Split(',')) {
360 auto splitter = pair.Split(':');
361 auto end = splitter.end();
363 auto key = splitter.begin();
364 if (key == end) {
365 continue;
368 auto value = ++splitter.begin();
369 if (value == end) {
370 continue;
373 aCallback(*key, *value);