Bug 1747171 don't hold mutex while spinning nested event loop r=jesup
[gecko.git] / widget / nsPrinterListCUPS.cpp
blob4ebf1390b006923bea91c574f7c90e4db708e84d
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "nsPrinterListCUPS.h"
7 #include "mozilla/IntegerRange.h"
8 #include "mozilla/Maybe.h"
9 #include "mozilla/StaticPrefs_print.h"
10 #include "nsCUPSShim.h"
11 #include "nsPrinterCUPS.h"
12 #include "nsString.h"
13 #include "prenv.h"
15 // Use a local static to initialize the CUPS shim lazily, when it's needed.
16 // This is used in order to avoid a global constructor.
17 static nsCUPSShim& CupsShim() {
18 static nsCUPSShim sCupsShim;
19 return sCupsShim;
22 using PrinterInfo = nsPrinterListBase::PrinterInfo;
24 /**
25 * Retrieves a human-readable name for the printer from CUPS.
26 * https://www.cups.org/doc/cupspm.html#basic-destination-information
28 static void GetDisplayNameForPrinter(const cups_dest_t& aDest,
29 nsAString& aName) {
30 // macOS clients expect prettified printer names
31 // while GTK clients expect non-prettified names.
32 // If you change this, please change NamedPrinter accordingly.
33 #ifdef XP_MACOSX
34 const char* displayName = CupsShim().cupsGetOption(
35 "printer-info", aDest.num_options, aDest.options);
36 if (displayName) {
37 CopyUTF8toUTF16(MakeStringSpan(displayName), aName);
39 #endif
42 NS_IMETHODIMP
43 nsPrinterListCUPS::InitPrintSettingsFromPrinter(
44 const nsAString& aPrinterName, nsIPrintSettings* aPrintSettings) {
45 MOZ_ASSERT(aPrintSettings);
47 // Set a default file name.
48 nsAutoString filename;
49 nsresult rv = aPrintSettings->GetToFileName(filename);
50 if (NS_FAILED(rv) || filename.IsEmpty()) {
51 const char* path = PR_GetEnv("PWD");
52 if (!path) {
53 path = PR_GetEnv("HOME");
56 if (path) {
57 CopyUTF8toUTF16(mozilla::MakeStringSpan(path), filename);
58 filename.AppendLiteral("/mozilla.pdf");
59 } else {
60 filename.AssignLiteral("mozilla.pdf");
63 aPrintSettings->SetToFileName(filename);
66 aPrintSettings->SetIsInitializedFromPrinter(true);
67 return NS_OK;
70 static int CupsDestCallback(void* user_data, unsigned aFlags,
71 cups_dest_t* aDest) {
72 MOZ_ASSERT(user_data);
73 auto* printerInfoList = static_cast<nsTArray<PrinterInfo>*>(user_data);
75 cups_dest_t* ownedDest = nullptr;
76 mozilla::DebugOnly<const int> numCopied =
77 CupsShim().cupsCopyDest(aDest, 0, &ownedDest);
78 MOZ_ASSERT(numCopied == 1);
80 nsString name;
81 GetDisplayNameForPrinter(*aDest, name);
83 printerInfoList->AppendElement(PrinterInfo{std::move(name), ownedDest});
85 return aFlags == CUPS_DEST_FLAGS_MORE ? 1 : 0;
88 nsTArray<PrinterInfo> nsPrinterListCUPS::Printers() const {
89 if (!CupsShim().InitOkay()) {
90 return {};
93 auto FreeDestsAndClear = [](nsTArray<PrinterInfo>& aArray) {
94 for (auto& info : aArray) {
95 CupsShim().cupsFreeDests(1, static_cast<cups_dest_t*>(info.mCupsHandle));
97 aArray.Clear();
100 nsTArray<PrinterInfo> printerInfoList;
101 if (CupsShim().cupsEnumDests(
102 CUPS_DEST_FLAGS_NONE,
103 int(StaticPrefs::print_cups_enum_dests_timeout_ms()),
104 nullptr /* cancel* */, CUPS_PRINTER_LOCAL,
105 CUPS_PRINTER_FAX | CUPS_PRINTER_SCANNER, &CupsDestCallback,
106 &printerInfoList)) {
107 return printerInfoList;
110 // An error occurred - retry with CUPS_PRINTER_DISCOVERED masked out (since
111 // it looks like there are a lot of error cases for that in cupsEnumDests):
112 FreeDestsAndClear(printerInfoList);
113 if (CupsShim().cupsEnumDests(
114 CUPS_DEST_FLAGS_NONE,
115 0 /* 0 timeout should be okay when masking CUPS_PRINTER_DISCOVERED */,
116 nullptr /* cancel* */, CUPS_PRINTER_LOCAL,
117 CUPS_PRINTER_FAX | CUPS_PRINTER_SCANNER | CUPS_PRINTER_DISCOVERED,
118 &CupsDestCallback, &printerInfoList)) {
119 return printerInfoList;
122 // Another error occurred. Maybe printerInfoList could be partially
123 // populated, so perhaps we could return it without clearing it in the hope
124 // that there are some usable dests. However, presuambly CUPS doesn't
125 // guarantee that any dests that it added are complete and safe to use when
126 // an error occurs?
127 FreeDestsAndClear(printerInfoList);
128 return {};
131 RefPtr<nsIPrinter> nsPrinterListCUPS::CreatePrinter(PrinterInfo aInfo) const {
132 return mozilla::MakeRefPtr<nsPrinterCUPS>(
133 mCommonPaperInfo, CupsShim(), std::move(aInfo.mName),
134 static_cast<cups_dest_t*>(aInfo.mCupsHandle));
137 mozilla::Maybe<PrinterInfo> nsPrinterListCUPS::PrinterByName(
138 nsString aPrinterName) const {
139 mozilla::Maybe<PrinterInfo> rv;
140 if (!CupsShim().InitOkay()) {
141 return rv;
144 // Will contain the printer, if found. This must be fully owned, and not a
145 // member of another array of printers.
146 cups_dest_t* printer = nullptr;
148 #ifdef XP_MACOSX
149 // On OS X the printer name given to this function is the readable/display
150 // name and not the CUPS name, so we iterate over all the printers for now.
151 // See bug 1659807 for one approach to improve perf here.
153 nsAutoCString printerName;
154 CopyUTF16toUTF8(aPrinterName, printerName);
155 cups_dest_t* printers = nullptr;
156 const auto numPrinters = CupsShim().cupsGetDests(&printers);
157 for (auto i : mozilla::IntegerRange(0, numPrinters)) {
158 const char* const displayName = CupsShim().cupsGetOption(
159 "printer-info", printers[i].num_options, printers[i].options);
160 if (printerName == displayName) {
161 // The second arg to CupsShim().cupsCopyDest is called num_dests, but
162 // it actually copies num_dests + 1 elements.
163 CupsShim().cupsCopyDest(printers + i, 0, &printer);
164 break;
167 CupsShim().cupsFreeDests(numPrinters, printers);
169 #else
170 // On GTK, we only ever show the CUPS name of printers, so we can use
171 // cupsGetNamedDest directly.
173 const auto printerName = NS_ConvertUTF16toUTF8(aPrinterName);
174 printer = CupsShim().cupsGetNamedDest(CUPS_HTTP_DEFAULT, printerName.get(),
175 nullptr);
177 #endif
179 if (printer) {
180 // Since the printer name had to be passed by-value, we can move the
181 // name from that.
182 rv.emplace(PrinterInfo{std::move(aPrinterName), printer});
184 return rv;
187 mozilla::Maybe<PrinterInfo> nsPrinterListCUPS::PrinterBySystemName(
188 nsString aPrinterName) const {
189 mozilla::Maybe<PrinterInfo> rv;
190 if (!CupsShim().InitOkay()) {
191 return rv;
194 const auto printerName = NS_ConvertUTF16toUTF8(aPrinterName);
195 if (cups_dest_t* const printer = CupsShim().cupsGetNamedDest(
196 CUPS_HTTP_DEFAULT, printerName.get(), nullptr)) {
197 rv.emplace(PrinterInfo{std::move(aPrinterName), printer});
199 return rv;
202 nsresult nsPrinterListCUPS::SystemDefaultPrinterName(nsAString& aName) const {
203 aName.Truncate();
205 if (!CupsShim().InitOkay()) {
206 return NS_OK;
209 // Passing in nullptr for the name will return the default, if any.
210 cups_dest_t* dest =
211 CupsShim().cupsGetNamedDest(CUPS_HTTP_DEFAULT, /* name */ nullptr,
212 /* instance */ nullptr);
213 if (!dest) {
214 return NS_OK;
217 GetDisplayNameForPrinter(*dest, aName);
218 if (aName.IsEmpty()) {
219 CopyUTF8toUTF16(mozilla::MakeStringSpan(dest->name), aName);
222 CupsShim().cupsFreeDests(1, dest);
223 return NS_OK;