Backed out changeset 5c7de47bcacb (bug 1927094) for causing Bug 1928689. a=backout
[gecko.git] / widget / gtk / nsDeviceContextSpecG.cpp
blob18ab3f4e3d8173fce518c1d8e3bca4f392612d81
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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 "nsDeviceContextSpecG.h"
8 #include "mozilla/gfx/PrintPromise.h"
9 #include "mozilla/gfx/PrintTargetPDF.h"
10 #include "mozilla/Logging.h"
11 #include "mozilla/Services.h"
12 #include "mozilla/GUniquePtr.h"
13 #include "mozilla/WidgetUtilsGtk.h"
15 #include "prenv.h" /* for PR_GetEnv */
17 #include "nsComponentManagerUtils.h"
18 #include "nsIObserverService.h"
19 #include "nsPrintfCString.h"
20 #include "nsQueryObject.h"
21 #include "nsReadableUtils.h"
22 #include "nsThreadUtils.h"
24 #include "nsCUPSShim.h"
25 #include "nsPrinterCUPS.h"
27 #include "nsPrintSettingsGTK.h"
29 #include "nsIFileStreams.h"
30 #include "nsIFile.h"
31 #include "nsTArray.h"
32 #include "nsThreadUtils.h"
34 #include "mozilla/Preferences.h"
35 #include "mozilla/StaticPrefs_print.h"
37 #include <dlfcn.h>
38 #include <unistd.h>
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #include <fcntl.h>
43 // To check if we need to use flatpak portal for printing
44 #include "nsIGIOService.h"
46 using namespace mozilla;
48 using mozilla::gfx::IntSize;
49 using mozilla::gfx::PrintEndDocumentPromise;
50 using mozilla::gfx::PrintTarget;
51 using mozilla::gfx::PrintTargetPDF;
53 nsDeviceContextSpecGTK::nsDeviceContextSpecGTK()
54 : mGtkPrintSettings(nullptr), mGtkPageSetup(nullptr) {}
56 nsDeviceContextSpecGTK::~nsDeviceContextSpecGTK() {
57 if (mGtkPageSetup) {
58 g_object_unref(mGtkPageSetup);
61 if (mGtkPrintSettings) {
62 g_object_unref(mGtkPrintSettings);
65 if (mSpoolFile) {
66 mSpoolFile->Remove(false);
70 NS_IMPL_ISUPPORTS(nsDeviceContextSpecGTK, nsIDeviceContextSpec)
72 already_AddRefed<PrintTarget> nsDeviceContextSpecGTK::MakePrintTarget() {
73 double width, height;
74 mPrintSettings->GetEffectiveSheetSize(&width, &height);
76 // convert twips to points
77 width /= TWIPS_PER_POINT_FLOAT;
78 height /= TWIPS_PER_POINT_FLOAT;
80 // We shouldn't be attempting to get a surface if we've already got a spool
81 // file.
82 MOZ_ASSERT(!mSpoolFile);
84 auto stream = [&]() -> nsCOMPtr<nsIOutputStream> {
85 if (mPrintSettings->GetOutputDestination() ==
86 nsIPrintSettings::kOutputDestinationStream) {
87 nsCOMPtr<nsIOutputStream> out;
88 mPrintSettings->GetOutputStream(getter_AddRefs(out));
89 return out;
91 // Spool file. Use Glib's temporary file function since we're
92 // already dependent on the gtk software stack.
93 gchar* buf;
94 gint fd = g_file_open_tmp("XXXXXX.tmp", &buf, nullptr);
95 if (-1 == fd) {
96 return nullptr;
98 close(fd);
99 if (NS_FAILED(NS_NewNativeLocalFile(nsDependentCString(buf),
100 getter_AddRefs(mSpoolFile)))) {
101 unlink(buf);
102 g_free(buf);
103 return nullptr;
105 mSpoolName = buf;
106 g_free(buf);
107 mSpoolFile->SetPermissions(0600);
108 nsCOMPtr<nsIFileOutputStream> stream =
109 do_CreateInstance("@mozilla.org/network/file-output-stream;1");
110 if (NS_FAILED(stream->Init(mSpoolFile, -1, -1, 0))) {
111 return nullptr;
113 return stream;
114 }();
116 return PrintTargetPDF::CreateOrNull(stream, IntSize::Ceil(width, height));
119 #define DECLARE_KNOWN_MONOCHROME_SETTING(key_, value_) {"cups-" key_, value_},
121 struct {
122 const char* mKey;
123 const char* mValue;
124 } kKnownMonochromeSettings[] = {
125 CUPS_EACH_MONOCHROME_PRINTER_SETTING(DECLARE_KNOWN_MONOCHROME_SETTING)};
127 #undef DECLARE_KNOWN_MONOCHROME_SETTING
129 // https://developer.gnome.org/gtk3/stable/GtkPaperSize.html#gtk-paper-size-new-from-ipp
130 static GUniquePtr<GtkPaperSize> GtkPaperSizeFromIpp(const gchar* aIppName,
131 gdouble aWidth,
132 gdouble aHeight) {
133 static auto sPtr = (GtkPaperSize * (*)(const gchar*, gdouble, gdouble))
134 dlsym(RTLD_DEFAULT, "gtk_paper_size_new_from_ipp");
135 if (gtk_check_version(3, 16, 0)) {
136 return nullptr;
138 return GUniquePtr<GtkPaperSize>(sPtr(aIppName, aWidth, aHeight));
141 static bool PaperSizeAlmostEquals(GtkPaperSize* aSize,
142 GtkPaperSize* aOtherSize) {
143 const double kEpsilon = 1.0; // millimetres
144 // GTK stores sizes internally in millimetres so just use that.
145 if (fabs(gtk_paper_size_get_height(aSize, GTK_UNIT_MM) -
146 gtk_paper_size_get_height(aOtherSize, GTK_UNIT_MM)) > kEpsilon) {
147 return false;
149 if (fabs(gtk_paper_size_get_width(aSize, GTK_UNIT_MM) -
150 gtk_paper_size_get_width(aOtherSize, GTK_UNIT_MM)) > kEpsilon) {
151 return false;
153 return true;
156 // Prefer the ppd name because some printers don't deal well even with standard
157 // ipp names.
158 static GUniquePtr<GtkPaperSize> PpdSizeFromIppName(const gchar* aIppName) {
159 static constexpr struct {
160 const char* mCups;
161 const char* mGtk;
162 } kMap[] = {
163 {CUPS_MEDIA_A3, GTK_PAPER_NAME_A3},
164 {CUPS_MEDIA_A4, GTK_PAPER_NAME_A4},
165 {CUPS_MEDIA_A5, GTK_PAPER_NAME_A5},
166 {CUPS_MEDIA_LETTER, GTK_PAPER_NAME_LETTER},
167 {CUPS_MEDIA_LEGAL, GTK_PAPER_NAME_LEGAL},
168 // Other gtk sizes with no standard CUPS constant: _EXECUTIVE and _B5
171 for (const auto& entry : kMap) {
172 if (!strcmp(entry.mCups, aIppName)) {
173 return GUniquePtr<GtkPaperSize>(gtk_paper_size_new(entry.mGtk));
177 return nullptr;
180 // This is a horrible workaround for some printer driver bugs that treat custom
181 // page sizes different to standard ones. If our paper object matches one of a
182 // standard one, use a standard paper size object instead.
184 // We prefer ppd to ipp to custom sizes.
186 // See bug 414314, bug 1691798, and bug 1717292 for more info.
187 static GUniquePtr<GtkPaperSize> GetStandardGtkPaperSize(
188 GtkPaperSize* aGeckoPaperSize) {
189 // We should get an ipp name from cups, try to get a ppd from that first.
190 const gchar* geckoName = gtk_paper_size_get_name(aGeckoPaperSize);
191 if (auto ppd = PpdSizeFromIppName(geckoName)) {
192 return ppd;
195 // We try gtk_paper_size_new_from_ipp next, because even though
196 // gtk_paper_size_new tries to deal with ipp, it has some rounding issues that
197 // the ipp equivalent doesn't have, see
198 // https://gitlab.gnome.org/GNOME/gtk/-/issues/3685.
199 if (auto ipp = GtkPaperSizeFromIpp(
200 geckoName, gtk_paper_size_get_width(aGeckoPaperSize, GTK_UNIT_POINTS),
201 gtk_paper_size_get_height(aGeckoPaperSize, GTK_UNIT_POINTS))) {
202 if (!gtk_paper_size_is_custom(ipp.get())) {
203 if (auto ppd = PpdSizeFromIppName(gtk_paper_size_get_name(ipp.get()))) {
204 return ppd;
206 return ipp;
210 GUniquePtr<GtkPaperSize> size(gtk_paper_size_new(geckoName));
211 // gtk_paper_size_is_equal compares just paper names. The name in Gecko
212 // might come from CUPS, which is an ipp size, and gets normalized by gtk.
213 // So check also for the same actual paper size.
214 if (gtk_paper_size_is_equal(size.get(), aGeckoPaperSize) ||
215 PaperSizeAlmostEquals(aGeckoPaperSize, size.get())) {
216 return size;
219 // Not the same after all, so use our custom paper sizes instead.
220 return nullptr;
223 /** -------------------------------------------------------
224 * Initialize the nsDeviceContextSpecGTK
225 * @update dc 2/15/98
226 * @update syd 3/2/99
228 NS_IMETHODIMP nsDeviceContextSpecGTK::Init(nsIPrintSettings* aPS,
229 bool aIsPrintPreview) {
230 RefPtr<nsPrintSettingsGTK> settings = do_QueryObject(aPS);
231 if (!settings) {
232 return NS_ERROR_NO_INTERFACE;
234 mPrintSettings = aPS;
236 mGtkPrintSettings = settings->GetGtkPrintSettings();
237 mGtkPageSetup = settings->GetGtkPageSetup();
239 GtkPaperSize* geckoPaperSize = gtk_page_setup_get_paper_size(mGtkPageSetup);
240 GUniquePtr<GtkPaperSize> gtkPaperSize =
241 GetStandardGtkPaperSize(geckoPaperSize);
243 mGtkPageSetup = gtk_page_setup_copy(mGtkPageSetup);
244 mGtkPrintSettings = gtk_print_settings_copy(mGtkPrintSettings);
246 if (!aPS->GetPrintInColor() && StaticPrefs::print_cups_monochrome_enabled()) {
247 for (const auto& setting : kKnownMonochromeSettings) {
248 gtk_print_settings_set(mGtkPrintSettings, setting.mKey, setting.mValue);
250 auto applySetting = [&](const nsACString& aKey, const nsACString& aVal) {
251 nsAutoCString extra;
252 extra.AppendASCII("cups-");
253 extra.Append(aKey);
254 gtk_print_settings_set(mGtkPrintSettings, extra.get(),
255 nsAutoCString(aVal).get());
257 nsPrinterCUPS::ForEachExtraMonochromeSetting(applySetting);
260 GtkPaperSize* properPaperSize =
261 gtkPaperSize ? gtkPaperSize.get() : geckoPaperSize;
262 gtk_print_settings_set_paper_size(mGtkPrintSettings, properPaperSize);
263 gtk_page_setup_set_paper_size_and_default_margins(mGtkPageSetup,
264 properPaperSize);
265 return NS_OK;
268 static void print_callback(GtkPrintJob* aJob, gpointer aData,
269 const GError* aError) {
270 g_object_unref(aJob);
271 ((nsIFile*)aData)->Remove(false);
274 /* static */
275 gboolean nsDeviceContextSpecGTK::PrinterEnumerator(GtkPrinter* aPrinter,
276 gpointer aData) {
277 nsDeviceContextSpecGTK* spec = (nsDeviceContextSpecGTK*)aData;
279 if (spec->mHasEnumerationFoundAMatch) {
280 // We're already done, but we're letting the enumeration run its course,
281 // to avoid a GTK bug.
282 return FALSE;
285 // Find the printer whose name matches the one inside the settings.
286 nsString printerName;
287 nsresult rv = spec->mPrintSettings->GetPrinterName(printerName);
288 if (NS_SUCCEEDED(rv) && !printerName.IsVoid()) {
289 NS_ConvertUTF16toUTF8 requestedName(printerName);
290 const char* currentName = gtk_printer_get_name(aPrinter);
291 if (requestedName.Equals(currentName)) {
292 nsPrintSettingsGTK::From(spec->mPrintSettings)->SetGtkPrinter(aPrinter);
294 // Bug 1145916 - attempting to kick off a print job for this printer
295 // during this tick of the event loop will result in the printer backend
296 // misunderstanding what the capabilities of the printer are due to a
297 // GTK bug (https://bugzilla.gnome.org/show_bug.cgi?id=753041). We
298 // sidestep this by deferring the print to the next tick.
299 NS_DispatchToCurrentThread(
300 NewRunnableMethod("nsDeviceContextSpecGTK::StartPrintJob", spec,
301 &nsDeviceContextSpecGTK::StartPrintJob));
303 // We're already done, but we need to let the enumeration run its course,
304 // to avoid a GTK bug. So we record that we've found a match and
305 // then return FALSE.
306 // TODO: If/when we can be sure that GTK handles this OK, we could
307 // return TRUE to avoid some needless enumeration.
308 spec->mHasEnumerationFoundAMatch = true;
309 return FALSE;
313 // We haven't found it yet - keep searching...
314 return FALSE;
317 void nsDeviceContextSpecGTK::StartPrintJob() {
318 GtkPrintJob* job = gtk_print_job_new(
319 mTitle.get(), nsPrintSettingsGTK::From(mPrintSettings)->GetGtkPrinter(),
320 mGtkPrintSettings, mGtkPageSetup);
322 if (!gtk_print_job_set_source_file(job, mSpoolName.get(), nullptr)) return;
324 // Now gtk owns the print job, and will be released via our callback.
325 gtk_print_job_send(job, print_callback, mSpoolFile.forget().take(),
326 [](gpointer aData) {
327 auto* spoolFile = static_cast<nsIFile*>(aData);
328 NS_RELEASE(spoolFile);
332 void nsDeviceContextSpecGTK::EnumeratePrinters() {
333 mHasEnumerationFoundAMatch = false;
334 gtk_enumerate_printers(&nsDeviceContextSpecGTK::PrinterEnumerator, this,
335 nullptr, TRUE);
338 NS_IMETHODIMP
339 nsDeviceContextSpecGTK::BeginDocument(const nsAString& aTitle,
340 const nsAString& aPrintToFileName,
341 int32_t aStartPage, int32_t aEndPage) {
342 // Print job names exceeding 255 bytes are safe with GTK version 3.18.2 or
343 // newer. This is a workaround for old GTK.
344 if (gtk_check_version(3, 18, 2) != nullptr) {
345 PrintTarget::AdjustPrintJobNameForIPP(aTitle, mTitle);
346 } else {
347 CopyUTF16toUTF8(aTitle, mTitle);
350 return NS_OK;
353 RefPtr<PrintEndDocumentPromise> nsDeviceContextSpecGTK::EndDocument() {
354 switch (mPrintSettings->GetOutputDestination()) {
355 case nsIPrintSettings::kOutputDestinationPrinter: {
356 // At this point, we might have a GtkPrinter set up in nsPrintSettingsGTK,
357 // or we might not. In the single-process case, we probably will, as this
358 // is populated by the print settings dialog, or set to the default
359 // printer.
360 // In the multi-process case, we proxy the print settings dialog over to
361 // the parent process, and only get the name of the printer back on the
362 // content process side. In that case, we need to enumerate the printers
363 // on the content side, and find a printer with a matching name.
365 if (nsPrintSettingsGTK::From(mPrintSettings)->GetGtkPrinter()) {
366 // We have a printer, so we can print right away.
367 StartPrintJob();
368 } else {
369 // We don't have a printer. We have to enumerate the printers and find
370 // one with a matching name.
371 NS_DispatchToCurrentThread(
372 NewRunnableMethod("nsDeviceContextSpecGTK::EnumeratePrinters", this,
373 &nsDeviceContextSpecGTK::EnumeratePrinters));
375 break;
377 case nsIPrintSettings::kOutputDestinationFile: {
378 // Handle print-to-file ourselves for the benefit of embedders
379 nsString targetPath;
380 nsCOMPtr<nsIFile> destFile;
381 mPrintSettings->GetToFileName(targetPath);
383 nsresult rv = NS_NewLocalFile(targetPath, getter_AddRefs(destFile));
384 if (NS_FAILED(rv)) {
385 return PrintEndDocumentPromise::CreateAndReject(rv, __func__);
388 return nsIDeviceContextSpec::EndDocumentAsync(
389 __func__,
390 [destFile = std::move(destFile),
391 spoolFile = std::move(mSpoolFile)]() -> nsresult {
392 nsAutoString destLeafName;
393 auto rv = destFile->GetLeafName(destLeafName);
394 NS_ENSURE_SUCCESS(rv, rv);
396 nsCOMPtr<nsIFile> destDir;
397 rv = destFile->GetParent(getter_AddRefs(destDir));
398 NS_ENSURE_SUCCESS(rv, rv);
400 rv = spoolFile->MoveTo(destDir, destLeafName);
401 NS_ENSURE_SUCCESS(rv, rv);
403 // This is the standard way to get the UNIX umask. Ugh.
404 mode_t mask = umask(0);
405 umask(mask);
406 // If you're not familiar with umasks, they contain the bits of what
407 // NOT to set in the permissions (thats because files and
408 // directories have different numbers of bits for their permissions)
409 destFile->SetPermissions(0666 & ~(mask));
410 return NS_OK;
413 break;
415 case nsIPrintSettings::kOutputDestinationStream:
416 // Nothing to do, handled in MakePrintTarget.
417 MOZ_ASSERT(!mSpoolFile);
418 break;
420 return PrintEndDocumentPromise::CreateAndResolve(true, __func__);