Bug 1700051: part 28) Refactor `WordSplitState<T>::GetDOMWordSeparatorOffset` to...
[gecko.git] / widget / nsPrinterCUPS.cpp
blobaea739d73feebc3d71b4af7c923f4c1892885bdc
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 if (!mShim.cupsLocalizeDestMedia) {
119 return aMedia.media;
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()) {
133 return true;
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);
154 if (!value) {
155 return false;
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,
172 aOption, aValue);
175 bool nsPrinterCUPS::IsCUPSVersionAtLeast(uint64_t aCUPSMajor,
176 uint64_t aCUPSMinor,
177 uint64_t aCUPSPatch) const {
178 auto printerInfoLock = TryEnsurePrinterInfo();
179 // Compare major version.
180 if (printerInfoLock->mCUPSMajor > aCUPSMajor) {
181 return true;
183 if (printerInfoLock->mCUPSMajor < aCUPSMajor) {
184 return false;
187 // Compare minor version.
188 if (printerInfoLock->mCUPSMinor > aCUPSMinor) {
189 return true;
191 if (printerInfoLock->mCUPSMinor < aCUPSMinor) {
192 return false;
195 // Compare patch.
196 return aCUPSPatch <= printerInfoLock->mCUPSPatch;
199 http_t* nsPrinterCUPS::Connection::GetConnection(cups_dest_t* aDest) {
200 if (mWasInited) {
201 return mConnection;
203 mWasInited = true;
205 // blocking call
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);
213 if (connection) {
214 mConnection = connection;
216 return mConnection;
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;
232 cups_size_t media;
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.
237 #ifdef XP_MACOSX
238 hasDefaultMedia =
239 mShim.cupsGetDestMediaDefault(CUPS_HTTP_DEFAULT, mPrinter, printerInfo,
240 CUPS_MEDIA_FLAGS_DEFAULT, &media);
241 #else
243 ipp_attribute_t* defaultMediaIPP =
244 mShim.cupsFindDestDefault
245 ? mShim.cupsFindDestDefault(CUPS_HTTP_DEFAULT, mPrinter,
246 printerInfo, "media")
247 : nullptr;
249 const char* defaultMediaName =
250 defaultMediaIPP ? mShim.ippGetString(defaultMediaIPP, 0, nullptr)
251 : nullptr;
253 hasDefaultMedia = defaultMediaName &&
254 mShim.cupsGetDestMediaByName(
255 CUPS_HTTP_DEFAULT, mPrinter, printerInfo,
256 defaultMediaName, CUPS_MEDIA_FLAGS_DEFAULT, &media);
258 #endif
260 if (!hasDefaultMedia) {
261 return PrintSettingsInitializer{
262 std::move(printerName),
263 PaperInfo(),
264 SupportsColor(),
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),
277 SupportsColor(),
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),
289 SupportsColor(),
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;
299 if (!printerInfo) {
300 return {};
303 const int paperCount =
304 mShim.cupsGetDestMediaCount
305 ? mShim.cupsGetDestMediaCount(connection, mPrinter, printerInfo,
306 CUPS_MEDIA_FLAGS_DEFAULT)
307 : 0;
308 nsTArray<PaperInfo> paperList;
309 nsTHashtable<nsCharPtrHashKey> paperSet(std::max(paperCount, 0));
311 paperList.SetCapacity(paperCount);
312 for (int i = 0; i < paperCount; ++i) {
313 cups_size_t media;
314 const int getInfoSucceeded = mShim.cupsGetDestMediaByIndex(
315 connection, mPrinter, printerInfo, i, CUPS_MEDIA_FLAGS_DEFAULT, &media);
317 if (!getInfoSucceeded || !paperSet.EnsureInserted(media.media)) {
318 continue;
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));
327 } else {
328 const char* const mediaName =
329 connection ? LocalizeMediaName(*connection, media) : media.media;
330 paperList.AppendElement(
331 MakePaperInfo(NS_ConvertUTF8toUTF16(mediaName), media));
335 return paperList;
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)) {
344 return lock;
347 if (aConnection == CUPS_HTTP_DEFAULT) {
348 lock->mTriedInitWithDefault = true;
349 } else {
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
355 // functions.
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);
362 return lock;
365 void nsPrinterCUPS::ForEachExtraMonochromeSetting(
366 FunctionRef<void(const nsACString&, const nsACString&)> aCallback) {
367 nsAutoCString pref;
368 Preferences::GetCString("print.cups.monochrome.extra_settings", pref);
369 if (pref.IsEmpty()) {
370 return;
373 for (const auto& pair : pref.Split(',')) {
374 auto splitter = pair.Split(':');
375 auto end = splitter.end();
377 auto key = splitter.begin();
378 if (key == end) {
379 continue;
382 auto value = ++splitter.begin();
383 if (value == end) {
384 continue;
387 aCallback(*key, *value);