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"
32 #include "nsThreadUtils.h"
34 #include "mozilla/Preferences.h"
35 #include "mozilla/StaticPrefs_print.h"
39 #include <sys/types.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() {
58 g_object_unref(mGtkPageSetup
);
61 if (mGtkPrintSettings
) {
62 g_object_unref(mGtkPrintSettings
);
66 mSpoolFile
->Remove(false);
70 NS_IMPL_ISUPPORTS(nsDeviceContextSpecGTK
, nsIDeviceContextSpec
)
72 already_AddRefed
<PrintTarget
> nsDeviceContextSpecGTK::MakePrintTarget() {
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
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
));
91 // Spool file. Use Glib's temporary file function since we're
92 // already dependent on the gtk software stack.
94 gint fd
= g_file_open_tmp("XXXXXX.tmp", &buf
, nullptr);
99 if (NS_FAILED(NS_NewNativeLocalFile(nsDependentCString(buf
),
100 getter_AddRefs(mSpoolFile
)))) {
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))) {
116 return PrintTargetPDF::CreateOrNull(stream
, IntSize::Ceil(width
, height
));
119 #define DECLARE_KNOWN_MONOCHROME_SETTING(key_, value_) {"cups-" key_, value_},
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
,
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)) {
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
) {
149 if (fabs(gtk_paper_size_get_width(aSize
, GTK_UNIT_MM
) -
150 gtk_paper_size_get_width(aOtherSize
, GTK_UNIT_MM
)) > kEpsilon
) {
156 // Prefer the ppd name because some printers don't deal well even with standard
158 static GUniquePtr
<GtkPaperSize
> PpdSizeFromIppName(const gchar
* aIppName
) {
159 static constexpr struct {
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
));
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
)) {
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()))) {
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())) {
219 // Not the same after all, so use our custom paper sizes instead.
223 /** -------------------------------------------------------
224 * Initialize the nsDeviceContextSpecGTK
228 NS_IMETHODIMP
nsDeviceContextSpecGTK::Init(nsIPrintSettings
* aPS
,
229 bool aIsPrintPreview
) {
230 RefPtr
<nsPrintSettingsGTK
> settings
= do_QueryObject(aPS
);
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
) {
252 extra
.AppendASCII("cups-");
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
,
268 static void print_callback(GtkPrintJob
* aJob
, gpointer aData
,
269 const GError
* aError
) {
270 g_object_unref(aJob
);
271 ((nsIFile
*)aData
)->Remove(false);
275 gboolean
nsDeviceContextSpecGTK::PrinterEnumerator(GtkPrinter
* aPrinter
,
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.
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;
313 // We haven't found it yet - keep searching...
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(),
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,
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
);
347 CopyUTF16toUTF8(aTitle
, mTitle
);
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
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.
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
));
377 case nsIPrintSettings::kOutputDestinationFile
: {
378 // Handle print-to-file ourselves for the benefit of embedders
380 nsCOMPtr
<nsIFile
> destFile
;
381 mPrintSettings
->GetToFileName(targetPath
);
383 nsresult rv
= NS_NewLocalFile(targetPath
, getter_AddRefs(destFile
));
385 return PrintEndDocumentPromise::CreateAndReject(rv
, __func__
);
388 return nsIDeviceContextSpec::EndDocumentAsync(
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);
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
));
415 case nsIPrintSettings::kOutputDestinationStream
:
416 // Nothing to do, handled in MakePrintTarget.
417 MOZ_ASSERT(!mSpoolFile
);
420 return PrintEndDocumentPromise::CreateAndResolve(true, __func__
);