summaryrefslogtreecommitdiffstats
path: root/vcl/unx/generic/printer
diff options
context:
space:
mode:
Diffstat (limited to 'vcl/unx/generic/printer')
-rw-r--r--vcl/unx/generic/printer/configuration/README5
-rw-r--r--vcl/unx/generic/printer/configuration/ppds/SGENPRT.PS582
-rw-r--r--vcl/unx/generic/printer/configuration/psprint.conf99
-rw-r--r--vcl/unx/generic/printer/cpdmgr.cxx757
-rw-r--r--vcl/unx/generic/printer/cupsmgr.cxx964
-rw-r--r--vcl/unx/generic/printer/jobdata.cxx316
-rw-r--r--vcl/unx/generic/printer/ppdparser.cxx1991
-rw-r--r--vcl/unx/generic/printer/printerinfomanager.cxx892
8 files changed, 5606 insertions, 0 deletions
diff --git a/vcl/unx/generic/printer/configuration/README b/vcl/unx/generic/printer/configuration/README
new file mode 100644
index 000000000..c39237a53
--- /dev/null
+++ b/vcl/unx/generic/printer/configuration/README
@@ -0,0 +1,5 @@
+Contains ppds for use by vcl when not using CUPS
+
+This is used for the print-to-file functionality. These two PPDs
+describe the range of paper sizes and postscript options necessary for
+printing to postscript without a configured printer.
diff --git a/vcl/unx/generic/printer/configuration/ppds/SGENPRT.PS b/vcl/unx/generic/printer/configuration/ppds/SGENPRT.PS
new file mode 100644
index 000000000..177e2c4e0
--- /dev/null
+++ b/vcl/unx/generic/printer/configuration/ppds/SGENPRT.PS
@@ -0,0 +1,582 @@
+*PPD-Adobe: "4.0"
+*%
+*% This file is part of the LibreOffice project.
+*%
+*% This Source Code Form is subject to the terms of the Mozilla Public
+*% License, v. 2.0. If a copy of the MPL was not distributed with this
+*% file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*%
+*% This file incorporates work covered by the following license notice:
+*%
+*% Licensed to the Apache Software Foundation (ASF) under one or more
+*% contributor license agreements. See the NOTICE file distributed
+*% with this work for additional information regarding copyright
+*% ownership. The ASF licenses this file to you under the Apache
+*% License, Version 2.0 (the "License")*% you may not use this file
+*% except in compliance with the License. You may obtain a copy of
+*% the License at http://www.apache.org/licenses/LICENSE-2.0 .
+*%
+
+*% The user must print with a PostScript(R) emulator to non PostScript(R)
+*% printers if the system has no specific printer support. This file
+*% allows the user to print to most printers without any modification.
+*% Standard paper sizes and resolutions are defined. There are some
+*% additional definitions for screen or online documents in this file.
+*% To print to a PostScript(R) printer, use the specific PPD file.
+
+*% ===== General =====
+
+*FormatVersion: "4.0"
+*FileVersion: "1.0"
+*LanguageEncoding: ISOLatin1
+*LanguageVersion: English
+*PSVersion: "(1) 1"
+*Product: "(Generic Printer)"
+*ModelName: "Generic Printer"
+*NickName: "Generic Printer"
+*PCFileName: "SGENPRT.PPD"
+
+
+*% ===== Basic Capabilities and Defaults =====
+
+*ColorDevice: True
+*DefaultColorSpace: RGB
+*LanguageLevel: "2"
+*TTRasterizer: Type42
+
+*% --- For None Color or old PostScript(R) printers use following lines ---
+*% *ColorDevice: False
+*% *DefaultColorSpace: Gray
+*% *LanguageLevel: "1"
+
+*FreeVM: "8388608"
+*VariablePaperSize: True
+*FileSystem: False
+*Throughput: "8"
+*Password: "0"
+*ExitServer: "
+ count 0 eq % is the password on the stack?
+ { true }
+ { dup % potential password
+ statusdict /checkpassword get exec not
+ } ifelse
+ { % if no password or not valid
+ (WARNING : Cannot perform the exitserver command.) =
+ (Password supplied is not valid.) =
+ (Please contact the author of this software.) = flush
+ quit
+ } if
+ serverdict /exitserver get exec
+"
+*End
+*Reset: "
+ count 0 eq % is the password on the stack?
+ { true }
+ { dup % potential password
+ statusdict /checkpassword get exec not
+ } ifelse
+ { % if no password or not valid
+ (WARNING : Cannot reset printer.) =
+ (Password supplied is not valid.) =
+ (Please contact the author of this software.) = flush
+ quit
+ } if
+ serverdict /exitserver get exec
+ systemdict /quit get exec
+ (WARNING : Printer Reset Failed.) = flush
+"
+*End
+
+
+*DefaultResolution: 300dpi
+
+*ResScreenFreq 72dpi: "60.0"
+*ResScreenFreq 144dpi: "60.0"
+*ResScreenFreq 300dpi: "60.0"
+*ResScreenFreq 360dpi: "60.0"
+*ResScreenFreq 600dpi: "60.0"
+*ResScreenFreq 720dpi: "60.0"
+*ResScreenFreq 1200dpi: "60.0"
+*ResScreenFreq 1440dpi: "60.0"
+*ResScreenFreq 2400dpi: "60.0"
+*ResScreenAngle 72dpi: "45.0"
+*ResScreenAngle 144dpi: "45.0"
+*ResScreenAngle 300dpi: "45.0"
+*ResScreenAngle 360dpi: "45.0"
+*ResScreenAngle 600dpi: "45.0"
+*ResScreenAngle 720dpi: "45.0"
+*ResScreenAngle 1200dpi: "45.0"
+*ResScreenAngle 1440dpi: "45.0"
+*ResScreenAngle 2400dpi: "45.0"
+
+
+*% ===== Halftone =====
+
+*ContoneOnly: False
+*DefaultHalftoneType: 1
+*ScreenFreq: "60.0"
+*ScreenAngle: "45.0"
+*DefaultScreenProc: Dot
+*ScreenProc Dot: "
+ { abs exch abs 2 copy add 1 gt {1 sub dup mul exch 1 sub
+ dup mul add 1 sub } { dup mul exch dup mul add 1 exch sub }
+ ifelse } bind
+"
+*End
+*ScreenProc Line: "{ exch pop abs neg } bind"
+*ScreenProc Ellipse: "
+ { abs exch abs 2 copy mul exch 4 mul add 3 sub dup 0
+ lt { pop dup mul exch .75 div dup mul add 4 div 1 exch sub }
+ { dup 1 gt { pop 1 exch sub dup mul exch 1 exch sub .75 div
+ dup mul add 4 div 1 sub }
+ { .5 exch sub exch pop exch pop } ifelse } ifelse } bind
+"
+*End
+*ScreenProc Cross: "{ abs exch abs 2 copy gt { exch } if pop neg } bind"
+
+*DefaultTransfer: Null
+*Transfer Null: "{ } bind"
+*Transfer Null.Inverse: "{ 1 exch sub } bind"
+
+
+*% ===== Paper =====
+
+*OpenUI *PageSize: PickOne
+*OrderDependency: 30 AnySetup *PageSize
+*DefaultPageSize: Letter
+*PageSize A0: "<</PageSize [2384 3370] /ImagingBBox null>> setpagedevice"
+*PageSize A1: "<</PageSize [1684 2384] /ImagingBBox null>> setpagedevice"
+*PageSize A2: "<</PageSize [1191 1684] /ImagingBBox null>> setpagedevice"
+*PageSize A3: "<</PageSize [842 1191] /ImagingBBox null>> setpagedevice"
+*PageSize A4: "<</PageSize [595 842] /ImagingBBox null>> setpagedevice"
+*PageSize A5: "<</PageSize [420 595] /ImagingBBox null>> setpagedevice"
+*PageSize A6: "<</PageSize [297 420] /ImagingBBox null>> setpagedevice"
+*PageSize B4: "<</PageSize [728 1032] /ImagingBBox null>> setpagedevice"
+*PageSize B5: "<</PageSize [516 729] /ImagingBBox null>> setpagedevice"
+*PageSize B6: "<</PageSize [363 516] /ImagingBBox null>> setpagedevice"
+*PageSize Legal/US Legal: "<</PageSize [612 1008] /ImagingBBox null>> setpagedevice"
+*PageSize Letter/US Letter: "<</PageSize [612 792] /ImagingBBox null>> setpagedevice"
+*PageSize Executive: "<</PageSize [522 756] /ImagingBBox null>> setpagedevice"
+*PageSize Statement: "<</PageSize [396 612] /ImagingBBox null>> setpagedevice"
+*PageSize Tabloid/US Tabloid: "<</PageSize [792 1224] /ImagingBBox null>> setpagedevice"
+*PageSize Ledger/Ledger Landscape: "<</PageSize [1224 792] /ImagingBBox null>> setpagedevice"
+*PageSize AnsiC/US C: "<</PageSize [1224 1584] /ImagingBBox null>> setpagedevice"
+*PageSize AnsiD/US D: "<</PageSize [1584 2448] /ImagingBBox null>> setpagedevice"
+*PageSize AnsiE/US E: "<</PageSize [2448 3168] /ImagingBBox null>> setpagedevice"
+*PageSize ARCHA/ARCH A: "<</PageSize [648 864] /ImagingBBox null>> setpagedevice"
+*PageSize ARCHB/ARCH B: "<</PageSize [864 1296] /ImagingBBox null>> setpagedevice"
+*PageSize ARCHC/ARCH C: "<</PageSize [1296 1728] /ImagingBBox null>> setpagedevice"
+*PageSize ARCHD/ARCH D: "<</PageSize [1728 2592] /ImagingBBox null>> setpagedevice"
+*PageSize ARCHE/ARCH E: "<</PageSize [2592 3456] /ImagingBBox null>> setpagedevice"
+*PageSize EnvMonarch/Monarch Envelope: "<</PageSize [279 540] /ImagingBBox null>> setpagedevice"
+*PageSize EnvDL/DL Envelope: "<</PageSize [312 624] /ImagingBBox null>> setpagedevice"
+*PageSize EnvC4/C4 Envelope: "<</PageSize [649 918] /ImagingBBox null>> setpagedevice"
+*PageSize EnvC5/C5 Envelope: "<</PageSize [459 649] /ImagingBBox null>> setpagedevice"
+*PageSize EnvC6/C6 Envelope: "<</PageSize [323 459] /ImagingBBox null>> setpagedevice"
+*PageSize Env10/C10 Envelope: "<</PageSize [297 684] /ImagingBBox null>> setpagedevice"
+*PageSize EnvC65/C65 Envelope: "<</PageSize [324 648] /ImagingBBox null>> setpagedevice"
+*PageSize Folio: "<</PageSize [595 935] /ImagingBBox null>> setpagedevice"
+*?PageSize: "
+ save
+ currentpagedevice /PageSize get aload pop
+ 2 copy gt {exch} if
+ (Unknown)
+ 32 dict
+ dup [2384 3370] (A0) put
+ dup [1684 2384] (A1) put
+ dup [1191 1684] (A2) put
+ dup [842 1191] (A3) put
+ dup [595 842] (A4) put
+ dup [420 595] (A5) put
+ dup [297 420] (A6) put
+ dup [728 1032] (B4) put
+ dup [516 729] (B5) put
+ dup [363 516] (B6) put
+ dup [612 1008] (Legal) put
+ dup [612 792] (Letter) put
+ dup [522 756] (Executive) put
+ dup [396 612] (Statement) put
+ dup [792 1224] (Tabloid) put
+ dup [1224 792] (Ledger) put
+ dup [1224 1584] (AnsiC) put
+ dup [1584 2448] (AnsiD) put
+ dup [2448 3168] (AnsiE) put
+ dup [648 864] (ARCHA) put
+ dup [864 1296] (ARCHB) put
+ dup [1296 1728] (ARCHC) put
+ dup [1728 2592] (ARCHD) put
+ dup [2592 3456] (ARCHE) put
+ dup [279 540] (EnvMonarch) put
+ dup [312 624] (EnvDL) put
+ dup [649 918] (EnvC4) put
+ dup [459 649] (EnvC5) put
+ dup [323 459] (EnvC6) put
+ dup [297 684] (Env10) put
+ dup [324 648] (EnvC65) put
+ dup [595 935] (Folio) put
+ { exch aload pop 4 index sub abs 5 le exch
+ 5 index sub abs 5 le and
+ { exch pop exit } { pop } ifelse
+ } bind forall
+ = flush pop pop
+ restore
+"
+*End
+*CloseUI: *PageSize
+
+*OpenUI *PageRegion: PickOne
+*OrderDependency: 40 AnySetup *PageRegion
+*DefaultPageRegion: Letter
+*PageRegion A0: "<</PageSize [2384 3370] /ImagingBBox null>> setpagedevice"
+*PageRegion A1: "<</PageSize [1684 2384] /ImagingBBox null>> setpagedevice"
+*PageRegion A2: "<</PageSize [1191 1684] /ImagingBBox null>> setpagedevice"
+*PageRegion A3: "<</PageSize [842 1191] /ImagingBBox null>> setpagedevice"
+*PageRegion A4: "<</PageSize [595 842] /ImagingBBox null>> setpagedevice"
+*PageRegion A5: "<</PageSize [420 595] /ImagingBBox null>> setpagedevice"
+*PageRegion A6: "<</PageSize [297 420] /ImagingBBox null>> setpagedevice"
+*PageRegion B4: "<</PageSize [728 1032] /ImagingBBox null>> setpagedevice"
+*PageRegion B5: "<</PageSize [516 729] /ImagingBBox null>> setpagedevice"
+*PageRegion B6: "<</PageSize [363 516] /ImagingBBox null>> setpagedevice"
+*PageRegion Legal/US Legal: "<</PageSize [612 1008] /ImagingBBox null>> setpagedevice"
+*PageRegion Letter/US Letter: "<</PageSize [612 792] /ImagingBBox null>> setpagedevice"
+*PageRegion Executive: "<</PageSize [522 756] /ImagingBBox null>> setpagedevice"
+*PageRegion Statement: "<</PageSize [396 612] /ImagingBBox null>> setpagedevice"
+*PageRegion Tabloid/US Tabloid: "<</PageSize [792 1224] /ImagingBBox null>> setpagedevice"
+*PageRegion Ledger/Ledger Landscape: "<</PageSize [1224 792] /ImagingBBox null>> setpagedevice"
+*PageRegion AnsiC/US C: "<</PageSize [1224 1584] /ImagingBBox null>> setpagedevice"
+*PageRegion AnsiD/US D: "<</PageSize [1584 2448] /ImagingBBox null>> setpagedevice"
+*PageRegion AnsiE/US E: "<</PageSize [2448 3168] /ImagingBBox null>> setpagedevice"
+*PageRegion ARCHA/ARCH A: "<</PageSize [648 864] /ImagingBBox null>> setpagedevice"
+*PageRegion ARCHB/ARCH B: "<</PageSize [864 1296] /ImagingBBox null>> setpagedevice"
+*PageRegion ARCHC/ARCH C: "<</PageSize [1296 1728] /ImagingBBox null>> setpagedevice"
+*PageRegion ARCHD/ARCH D: "<</PageSize [1728 2592] /ImagingBBox null>> setpagedevice"
+*PageRegion ARCHE/ARCH E: "<</PageSize [2592 3456] /ImagingBBox null>> setpagedevice"
+*PageRegion EnvMonarch/Monarch Envelope: "<</PageSize [279 540] /ImagingBBox null>> setpagedevice"
+*PageRegion EnvDL/DL Envelope: "<</PageSize [312 624] /ImagingBBox null>> setpagedevice"
+*PageRegion EnvC4/C4 Envelope: "<</PageSize [649 918] /ImagingBBox null>> setpagedevice"
+*PageRegion EnvC5/C5 Envelope: "<</PageSize [459 649] /ImagingBBox null>> setpagedevice"
+*PageRegion EnvC6/C6 Envelope: "<</PageSize [323 459] /ImagingBBox null>> setpagedevice"
+*PageRegion Env10/C10 Envelope: "<</PageSize [297 684] /ImagingBBox null>> setpagedevice"
+*PageRegion EnvC65/C65 Envelope: "<</PageSize [324 648] /ImagingBBox null>> setpagedevice"
+*PageRegion Folio: "<</PageSize [595 935] /ImagingBBox null>> setpagedevice"
+*CloseUI: *PageRegion
+
+*DefaultImageableArea: Letter
+*ImageableArea A0: "0 0 2384 3370"
+*ImageableArea A1: "0 0 1684 2384"
+*ImageableArea A2: "0 0 1191 1684"
+*ImageableArea A3: "18 18 824 1173"
+*ImageableArea A4: "18 18 577 824"
+*ImageableArea A5: "18 18 402 577"
+*ImageableArea A6: "18 18 279 402"
+*ImageableArea B4: "18 18 710 1014"
+*ImageableArea B5: "18 18 498 711"
+*ImageableArea B6: "18 18 345 498"
+*ImageableArea Legal: "18 18 594 990"
+*ImageableArea Letter: "18 18 594 774"
+*ImageableArea Executive: "18 18 504 738"
+*ImageableArea Statement: "18 18 378 594"
+*ImageableArea Tabloid: "18 18 774 1206"
+*ImageableArea Ledger: "18 18 1206 774"
+*ImageableArea AnsiC: "0 0 1224 1584"
+*ImageableArea AnsiD: "0 0 1584 2448"
+*ImageableArea AnsiE: "0 0 2448 3168"
+*ImageableArea ARCHA: "0 0 648 864"
+*ImageableArea ARCHB: "0 0 864 1296"
+*ImageableArea ARCHC: "0 0 1296 1728"
+*ImageableArea ARCHD: "0 0 1728 2592"
+*ImageableArea ARCHE: "0 0 2592 3456"
+*ImageableArea EnvMonarch: "0 0 279 540"
+*ImageableArea EnvDL: "0 0 312 624"
+*ImageableArea EnvC4: "0 0 649 918"
+*ImageableArea EnvC5: "0 0 459 649"
+*ImageableArea EnvC6: "0 0 323 459"
+*ImageableArea Env10: "0 0 297 684"
+*ImageableArea EnvC65: "0 0 324 648"
+*ImageableArea Folio: "0 0 595 935"
+
+*DefaultPaperDimension: Letter
+*PaperDimension A0: "2384 3370"
+*PaperDimension A1: "1684 2384"
+*PaperDimension A2: "1191 1684"
+*PaperDimension A3: "842 1191"
+*PaperDimension A4: "595 842"
+*PaperDimension A5: "420 595"
+*PaperDimension A6: "297 420"
+*PaperDimension B4: "728 1032"
+*PaperDimension B5: "516 729"
+*PaperDimension B6: "363 516"
+*PaperDimension Legal: "612 1008"
+*PaperDimension Letter: "612 792"
+*PaperDimension Executive: "522 756"
+*PaperDimension Statement: "396 612"
+*PaperDimension Tabloid: "792 1224"
+*PaperDimension Ledger: "1224 792"
+*PaperDimension AnsiC: "1224 1584"
+*PaperDimension AnsiD: "1584 2448"
+*PaperDimension AnsiE: "2448 3168"
+*PaperDimension ARCHA: "648 864"
+*PaperDimension ARCHB: "864 1296"
+*PaperDimension ARCHC: "1296 1728"
+*PaperDimension ARCHD: "1728 2592"
+*PaperDimension ARCHE: "2592 3456"
+*PaperDimension EnvMonarch: "279 540"
+*PaperDimension EnvDL: "312 624"
+*PaperDimension EnvC4: "649 918"
+*PaperDimension EnvC5: "459 649"
+*PaperDimension EnvC6: "323 459"
+*PaperDimension Env10: "297 684"
+*PaperDimension EnvC65: "324 648"
+*PaperDimension Folio: "595 935"
+
+*% ===== Duplex =====
+*OpenUI *Duplex/Duplex: PickOne
+*OrderDependency: 30 AnySetup *Duplex
+*DefaultDuplex: Simplex
+*Duplex Simplex: ""
+*Duplex None/Off: "
+<</Duplex false /Tumble false
+ /Policies << /Duplex 1 /Tumble 1 >>
+>> setpagedevice"
+*Duplex DuplexNoTumble/Long edge:"
+<</Duplex true /Tumble false
+ /Policies << /Duplex 1 /Tumble 1 >>
+>> setpagedevice"
+*Duplex DuplexTumble/Short edge:"
+<</Duplex true /Tumble true
+ /Policies << /Duplex 1 /Tumble 1 >>
+>> setpagedevice"
+*End
+*CloseUI: *Duplex
+
+*% ===== ManualFeed ===
+*OpenUI *ManualFeed/Manual Feed: Boolean
+*OrderDependency: 15 AnySetup *ManualFeed
+*DefaultManualFeed: False
+*ManualFeed False: "
+<< /ManualFeed false /Policies << /ManualFeed 1 >> >> setpagedevice"
+*ManualFeed True: "
+<< /ManualFeed true /Policies << /ManualFeed 1 >> >> setpagedevice"
+*End
+*CloseUI: *ManualFeed
+
+*% ===== Fonts =====
+
+*DefaultFont: Courier
+*Font AvantGarde-Book: Standard "(001.002)" Standard ROM
+*Font AvantGarde-BookOblique: Standard "(001.000)" Standard ROM
+*Font AvantGarde-Demi: Standard "(001.000)" Standard ROM
+*Font AvantGarde-DemiOblique: Standard "(001.000)" Standard ROM
+*Font Bookman-Demi: Standard "(001.000)" Standard ROM
+*Font Bookman-DemiItalic: Standard "(001.000)" Standard ROM
+*Font Bookman-Light: Standard "(001.000)" Standard ROM
+*Font Bookman-LightItalic: Standard "(001.000)" Standard ROM
+*Font Courier: Standard "(001.000)" Standard ROM
+*Font Courier-Bold: Standard "(001.000)" Standard ROM
+*Font Courier-BoldOblique: Standard "(001.000)" Standard ROM
+*Font Courier-Oblique: Standard "(001.000)" Standard ROM
+*Font Helvetica: Standard "(001.000)" Standard ROM
+*Font Helvetica-Bold: Standard "(001.000)" Standard ROM
+*Font Helvetica-BoldOblique: Standard "(001.000)" Standard ROM
+*Font Helvetica-Narrow: Standard "(001.000)" Standard ROM
+*Font Helvetica-Narrow-Bold: Standard "(001.000)" Standard ROM
+*Font Helvetica-Narrow-BoldOblique: Standard "(001.000)" Standard ROM
+*Font Helvetica-Narrow-Oblique: Standard "(001.000)" Standard ROM
+*Font Helvetica-Oblique: Standard "(001.000)" Standard ROM
+*Font NewCenturySchlbk-Bold: Standard "(001.000)" Standard ROM
+*Font NewCenturySchlbk-BoldItalic: Standard "(001.000)" Standard ROM
+*Font NewCenturySchlbk-Italic: Standard "(001.000)" Standard ROM
+*Font NewCenturySchlbk-Roman: Standard "(001.000)" Standard ROM
+*Font Palatino-Bold: Standard "(001.000)" Standard ROM
+*Font Palatino-BoldItalic: Standard "(001.000)" Standard ROM
+*Font Palatino-Italic: Standard "(001.000)" Standard ROM
+*Font Palatino-Roman: Standard "(001.000)" Standard ROM
+*Font Symbol: Special "(001.001)" Special ROM
+*Font Times-Bold: Standard "(001.000)" Standard ROM
+*Font Times-BoldItalic: Standard "(001.000)" Standard ROM
+*Font Times-Italic: Standard "(001.000)" Standard ROM
+*Font Times-Roman: Standard "(001.000)" Standard ROM
+*Font ZapfChancery-MediumItalic: Standard "(001.000)" Standard ROM
+*Font ZapfDingbats: Special "(001.000)" Special ROM
+*?FontQuery: "
+ save
+ {
+ count 1 gt
+ {
+ exch dup 127 string cvs (/) print print (:) print
+ /Font resourcestatus {pop pop (Yes)} {(No)} ifelse =
+ }
+ { exit } ifelse
+ } bind loop
+ (*) = flush
+ restore
+"
+*End
+
+*?FontList: "
+ save
+ (*) {cvn ==} 128 string /Font resourceforall
+ (*) = flush
+ restore
+"
+*End
+
+
+*% === Printer Messages ===
+
+*Message: "%%[ exitserver: permanent state may be changed ]%%"
+*Message: "%%[ Flushing: rest of job (to end-of-file) will be ignored ]%%"
+*Message: "\FontName\ not found, using Courier"
+
+*% Status (format: %%[ status: <one of these> %%] )
+*Status: "idle"
+*Status: "busy"
+*Status: "waiting"
+*Status: "printing"
+*Status: "PrinterError: timeout, clearing printer"
+*Status: "PrinterError: paper entry misfeed"
+*Status: "PrinterError: warming up"
+*Status: "PrinterError: service call"
+*Status: "PrinterError: no toner cartridge"
+*Status: "PrinterError: no paper tray"
+*Status: "PrinterError: cover open"
+*Status: "PrinterError: resetting printer"
+*Status: "PrinterError: out of paper"
+*Status: "PrinterError: timeout"
+*Status: "PrinterError: manual feed timeout"
+
+*% Input Sources (format: %%[ status: <stat>; source: <one of these>]%% )
+
+*% Printer Error (format: %%[ PrinterError: <one of these>]%%)
+*PrinterError: "timeout, clearing printer"
+*PrinterError: "paper entry misfeed"
+*PrinterError: "warming up"
+*PrinterError: "service call"
+*PrinterError: "no toner cartridge"
+*PrinterError: "no paper tray"
+*PrinterError: "cover open"
+*PrinterError: "resetting printer"
+*PrinterError: "out of paper"
+*PrinterError: "timeout"
+*PrinterError: "manual feed timeout"
+
+
+*% ===== Color Separation =====
+
+*DefaultColorSep: ProcessBlack.60lpi.300dpi/60 lpi / 300 dpi
+*InkName: ProcessBlack/Process Black
+*InkName: CustomColor/Custom Color
+*InkName: ProcessCyan/Process Cyan
+*InkName: ProcessMagenta/Process Magenta
+*InkName: ProcessYellow/Process Yellow
+
+*% --- For 60 lpi / 72 dpi ---
+*ColorSepScreenAngle ProcessBlack.60lpi.72dpi/60 lpi / 72 dpi: "45"
+*ColorSepScreenAngle CustomColor.60lpi.72dpi/60 lpi / 72 dpi: "45"
+*ColorSepScreenAngle ProcessCyan.60lpi.72dpi/60 lpi / 72 dpi: "15"
+*ColorSepScreenAngle ProcessMagenta.60lpi.72dpi/60 lpi / 72 dpi: "75"
+*ColorSepScreenAngle ProcessYellow.60lpi.72dpi/60 lpi / 72 dpi: "0"
+*ColorSepScreenFreq ProcessBlack.60lpi.72dpi/60 lpi / 72 dpi: "60"
+*ColorSepScreenFreq CustomColor.60lpi.72dpi/60 lpi / 72 dpi: "60"
+*ColorSepScreenFreq ProcessCyan.60lpi.72dpi/60 lpi / 72 dpi: "60"
+*ColorSepScreenFreq ProcessMagenta.60lpi.72dpi/60 lpi / 72 dpi: "60"
+*ColorSepScreenFreq ProcessYellow.60lpi.72dpi/60 lpi / 72 dpi: "60"
+
+*% --- For 60 lpi / 144 dpi ---
+*ColorSepScreenAngle ProcessBlack.60lpi.144dpi/60 lpi / 144 dpi: "45"
+*ColorSepScreenAngle CustomColor.60lpi.144dpi/60 lpi / 144 dpi: "45"
+*ColorSepScreenAngle ProcessCyan.60lpi.144dpi/60 lpi / 144 dpi: "15"
+*ColorSepScreenAngle ProcessMagenta.60lpi.144dpi/60 lpi / 144 dpi: "75"
+*ColorSepScreenAngle ProcessYellow.60lpi.144dpi/60 lpi / 144 dpi: "0"
+*ColorSepScreenFreq ProcessBlack.60lpi.144dpi/60 lpi / 144 dpi: "60"
+*ColorSepScreenFreq CustomColor.60lpi.144dpi/60 lpi / 144 dpi: "60"
+*ColorSepScreenFreq ProcessCyan.60lpi.144dpi/60 lpi / 144 dpi: "60"
+*ColorSepScreenFreq ProcessMagenta.60lpi.144dpi/60 lpi / 144 dpi: "60"
+*ColorSepScreenFreq ProcessYellow.60lpi.144dpi/60 lpi / 144 dpi: "60"
+
+*% --- For 60 lpi / 300 dpi ---
+*ColorSepScreenAngle ProcessBlack.60lpi.300dpi/60 lpi / 300 dpi: "45"
+*ColorSepScreenAngle CustomColor.60lpi.300dpi/60 lpi / 300 dpi: "45"
+*ColorSepScreenAngle ProcessCyan.60lpi.300dpi/60 lpi / 300 dpi: "15"
+*ColorSepScreenAngle ProcessMagenta.60lpi.300dpi/60 lpi / 300 dpi: "75"
+*ColorSepScreenAngle ProcessYellow.60lpi.300dpi/60 lpi / 300 dpi: "0"
+*ColorSepScreenFreq ProcessBlack.60lpi.300dpi/60 lpi / 300 dpi: "60"
+*ColorSepScreenFreq CustomColor.60lpi.300dpi/60 lpi / 300 dpi: "60"
+*ColorSepScreenFreq ProcessCyan.60lpi.300dpi/60 lpi / 300 dpi: "60"
+*ColorSepScreenFreq ProcessMagenta.60lpi.300dpi/60 lpi / 300 dpi: "60"
+*ColorSepScreenFreq ProcessYellow.60lpi.300dpi/60 lpi / 300 dpi: "60"
+
+*% --- For 60 lpi / 360 dpi ---
+*ColorSepScreenAngle ProcessBlack.60lpi.360dpi/60 lpi / 360 dpi: "45"
+*ColorSepScreenAngle CustomColor.60lpi.360dpi/60 lpi / 360 dpi: "45"
+*ColorSepScreenAngle ProcessCyan.60lpi.360dpi/60 lpi / 360 dpi: "15"
+*ColorSepScreenAngle ProcessMagenta.60lpi.360dpi/60 lpi / 360 dpi: "75"
+*ColorSepScreenAngle ProcessYellow.60lpi.360dpi/60 lpi / 360 dpi: "0"
+*ColorSepScreenFreq ProcessBlack.60lpi.360dpi/60 lpi / 360 dpi: "60"
+*ColorSepScreenFreq CustomColor.60lpi.360dpi/60 lpi / 360 dpi: "60"
+*ColorSepScreenFreq ProcessCyan.60lpi.360dpi/60 lpi / 360 dpi: "60"
+*ColorSepScreenFreq ProcessMagenta.60lpi.360dpi/60 lpi / 360 dpi: "60"
+*ColorSepScreenFreq ProcessYellow.60lpi.360dpi/60 lpi / 360 dpi: "60"
+
+*% --- For 71 lpi / 600 dpi ---
+*ColorSepScreenAngle ProcessBlack.71lpi.600dpi/71 lpi / 600 dpi: "45.0"
+*ColorSepScreenAngle CustomColor.71lpi.600dpi/71 lpi / 600 dpi: "45.0"
+*ColorSepScreenAngle ProcessCyan.71lpi.600dpi/71 lpi / 600 dpi: "71.5651"
+*ColorSepScreenAngle ProcessMagenta.71lpi.600dpi/71 lpi / 600 dpi: "18.4349"
+*ColorSepScreenAngle ProcessYellow.71lpi.600dpi/71 lpi / 600 dpi: "0.0"
+*ColorSepScreenFreq ProcessBlack.71lpi.600dpi/71 lpi / 600 dpi: "70.7107"
+*ColorSepScreenFreq CustomColor.71lpi.600dpi/71 lpi / 600 dpi: "70.7107"
+*ColorSepScreenFreq ProcessCyan.71lpi.600dpi/71 lpi / 600 dpi: "63.2456"
+*ColorSepScreenFreq ProcessMagenta.71lpi.600dpi/71 lpi / 600 dpi: "63.2456"
+*ColorSepScreenFreq ProcessYellow.71lpi.600dpi/71 lpi / 600 dpi: "66.6667"
+
+*% --- For 71 lpi / 720 dpi ---
+*ColorSepScreenAngle ProcessBlack.71lpi.720dpi/71 lpi / 720 dpi: "45.0"
+*ColorSepScreenAngle CustomColor.71lpi.720dpi/71 lpi / 720 dpi: "45.0"
+*ColorSepScreenAngle ProcessCyan.71lpi.720dpi/71 lpi / 720 dpi: "71.5651"
+*ColorSepScreenAngle ProcessMagenta.71lpi.720dpi/71 lpi / 720 dpi: "18.4349"
+*ColorSepScreenAngle ProcessYellow.71lpi.720dpi/71 lpi / 720 dpi: "0.0"
+*ColorSepScreenFreq ProcessBlack.71lpi.720dpi/71 lpi / 720 dpi: "70.7107"
+*ColorSepScreenFreq CustomColor.71lpi.720dpi/71 lpi / 720 dpi: "70.7107"
+*ColorSepScreenFreq ProcessCyan.71lpi.720dpi/71 lpi / 720 dpi: "63.2456"
+*ColorSepScreenFreq ProcessMagenta.71lpi.720dpi/71 lpi / 720 dpi: "63.2456"
+*ColorSepScreenFreq ProcessYellow.71lpi.720dpi/71 lpi / 720 dpi: "66.6667"
+
+*% --- For 100 lpi / 1200 dpi ---
+*ColorSepScreenAngle ProcessBlack.100lpi.1200dpi/100 lpi / 1200 dpi: "45.0"
+*ColorSepScreenAngle CustomColor.100lpi.1200dpi/100 lpi / 1200 dpi: "45.0"
+*ColorSepScreenAngle ProcessCyan.100lpi.1200dpi/100 lpi / 1200 dpi: "15.0"
+*ColorSepScreenAngle ProcessMagenta.100lpi.1200dpi/100 lpi / 1200 dpi: "75.0"
+*ColorSepScreenAngle ProcessYellow.100lpi.1200dpi/100 lpi / 1200 dpi: "0.0"
+*ColorSepScreenFreq ProcessBlack.100lpi.1200dpi/100 lpi / 1200 dpi: "100.0"
+*ColorSepScreenFreq CustomColor.100lpi.1200dpi/100 lpi / 1200 dpi: "100.0"
+*ColorSepScreenFreq ProcessCyan.100lpi.1200dpi/100 lpi / 1200 dpi: "100.0"
+*ColorSepScreenFreq ProcessMagenta.100lpi.1200dpi/100 lpi / 1200 dpi: "100.0"
+*ColorSepScreenFreq ProcessYellow.100lpi.1200dpi/100 lpi / 1200 dpi: "100.0"
+
+*% --- For 100 lpi / 1440 dpi ---
+*ColorSepScreenAngle ProcessBlack.100lpi.1440dpi/100 lpi / 1440 dpi: "45.0"
+*ColorSepScreenAngle CustomColor.100lpi.1440dpi/100 lpi / 1440 dpi: "45.0"
+*ColorSepScreenAngle ProcessCyan.100lpi.1440dpi/100 lpi / 1440 dpi: "15.0"
+*ColorSepScreenAngle ProcessMagenta.100lpi.1440dpi/100 lpi / 1440 dpi: "75.0"
+*ColorSepScreenAngle ProcessYellow.100lpi.1440dpi/100 lpi / 1440 dpi: "0.0"
+*ColorSepScreenFreq ProcessBlack.100lpi.1440dpi/100 lpi / 1440 dpi: "100.0"
+*ColorSepScreenFreq CustomColor.100lpi.1440dpi/100 lpi / 1440 dpi: "100.0"
+*ColorSepScreenFreq ProcessCyan.100lpi.1440dpi/100 lpi / 1440 dpi: "100.0"
+*ColorSepScreenFreq ProcessMagenta.100lpi.1440dpi/100 lpi / 1440 dpi: "100.0"
+*ColorSepScreenFreq ProcessYellow.100lpi.1440dpi/100 lpi / 1440 dpi: "100.0"
+
+*% --- For 175 lpi / 2400 dpi ---
+*ColorSepScreenAngle ProcessBlack.175lpi.2400dpi/175 lpi / 2400 dpi: "45.0"
+*ColorSepScreenAngle CustomColor.175lpi.2400dpi/175 lpi / 2400 dpi: "45.0"
+*ColorSepScreenAngle ProcessCyan.175lpi.2400dpi/175 lpi / 2400 dpi: "15.0"
+*ColorSepScreenAngle ProcessMagenta.175lpi.2400dpi/175 lpi / 2400 dpi: "75.0"
+*ColorSepScreenAngle ProcessYellow.175lpi.2400dpi/175 lpi / 2400 dpi: "0.0"
+*ColorSepScreenFreq ProcessBlack.175lpi.2400dpi/175 lpi / 2400 dpi: "175.0"
+*ColorSepScreenFreq CustomColor.175lpi.2400dpi/175 lpi / 2400 dpi: "175.0"
+*ColorSepScreenFreq ProcessCyan.175lpi.2400dpi/175 lpi / 2400 dpi: "175.0"
+*ColorSepScreenFreq ProcessMagenta.175lpi.2400dpi/175 lpi / 2400 dpi: "175.0"
+*ColorSepScreenFreq ProcessYellow.175lpi.2400dpi/175 lpi / 2400 dpi: "175.0"
+
+*% Last Edit Date: March 24 2000
+*% end of PPD file
diff --git a/vcl/unx/generic/printer/configuration/psprint.conf b/vcl/unx/generic/printer/configuration/psprint.conf
new file mode 100644
index 000000000..1b56e084e
--- /dev/null
+++ b/vcl/unx/generic/printer/configuration/psprint.conf
@@ -0,0 +1,99 @@
+;
+; This file is part of the LibreOffice project.
+;
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;
+; This file incorporates work covered by the following license notice:
+;
+; Licensed to the Apache Software Foundation (ASF) under one or more
+; contributor license agreements. See the NOTICE file distributed
+; with this work for additional information regarding copyright
+; ownership. The ASF licenses this file to you under the Apache
+; License, Version 2.0 (the "License"); you may not use this file
+; except in compliance with the License. You may obtain a copy of
+; the License at http://www.apache.org/licenses/LICENSE-2.0 .
+;
+[__Global_Printer_Defaults__]
+; Copies: the default number of copies produced
+; if key is absent the default is 1
+; Copies=1
+
+; Orientation: the default orientation of pages
+; possible Values: Portrait, Landscape
+; if key is absent the default is Portrait
+; Orientation=Portrait
+
+; Scale: the default scaling of output in percent
+; if key is absent the default is 100
+; Scale=100
+
+; MarginAdjust: the default adjustment to driver margins in 1/100 mm
+; MarginAdjust contains corrections for the driver defined margins
+; the values are comma separated
+; the order is: left,right,top,bottom
+; if key is absent the default is 0,0,0,0
+; MarginAdjust=0,0,0,0
+
+; ColorDepth: the default colordepth of the device in bits
+; possible values: 1, 8, 24
+; if key is absent the default is 24
+; ColorDepth=24
+
+; ColorDevice: the default setting whether the device is color capable
+; possible values: 0: driver setting, -1: grey scale, 1: color
+; if key is absent the default is 0
+; ColorDepth=0
+
+; PSLevel: the default setting of the PostScript level of the output
+; possible values: 0: driver setting, 1: level 1, 2: level2
+; if key is absent the default is 0
+; PSLevel=0
+
+; PPD_PageSize: the default page size to use. If a specific printer does
+; not support this page size its default is used instead.
+; possible values: A0, A1, A2, A3, A4, A5, A6, B4, B5, B6,
+; Legal, Letter, Executive, Statement, Tabloid,
+; Ledger, AnsiC, AnsiD, ARCHA, ARCHB, ARCHC,
+; ARCHD, ARCHE, EnvMonarch, EnvC4, EnvC5, EnvC6,
+; Env10, EnvC65, Folio
+; if key is absent the default value is driver specific
+; PPD_PageSize=A4
+
+
+[Generic Printer]
+; for every printer a group with at least the keys
+; "Printer" and "Command" is required
+
+; Printer: contains the base name of the PPD and the Printer name separated by /
+Printer=SGENPRT/Generic Printer
+
+; DefaultPrinter: marks the default printer
+DefaultPrinter=1
+
+; Location: a user readable string that will be shown in the print dialog
+Location=
+
+; Comment: a user readable string that will be shown in the print dialog
+Comment=
+
+; Command: a command line that accepts PostScript as standard input (pipe)
+; note: a shell will be started for the command
+Command=
+
+; QuickCommand: a command line that accepts PostScript as standard input (pipe)
+; this command line will be used instead of the command line given in the
+; "Command" key, if the user presses the direct print button. In this case
+; no print dialog should be shown, neither from the printing application nor
+; from the command line (example "kprinter --nodialog --stdin")
+; note: a shell will be started for the command
+;QuickCommand=
+
+; Features: a string containing additional comma separated properties of a printer
+; currently valid properties:
+; fax for a Fax printer queue
+; pdf=<dir> for a PDF printer where <dir> is the base directory for output files
+; external_dialog to notify that the print command of a printer will show a dialog
+; and therefore the application should not show its own dialog.
+;Features=
diff --git a/vcl/unx/generic/printer/cpdmgr.cxx b/vcl/unx/generic/printer/cpdmgr.cxx
new file mode 100644
index 000000000..e8b22111d
--- /dev/null
+++ b/vcl/unx/generic/printer/cpdmgr.cxx
@@ -0,0 +1,757 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <cstddef>
+#include <unistd.h>
+
+#include <unx/cpdmgr.hxx>
+
+#include <osl/file.h>
+#include <osl/thread.h>
+
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+
+#include <config_dbus.h>
+#include <config_gio.h>
+
+using namespace psp;
+using namespace osl;
+
+#if ENABLE_DBUS && ENABLE_GIO
+// Function to execute when name is acquired on the bus
+void CPDManager::onNameAcquired (GDBusConnection *connection,
+ const gchar *,
+ gpointer user_data)
+{
+ gchar* contents;
+ // Get Interface for introspection
+ if (!g_file_get_contents (FRONTEND_INTERFACE, &contents, nullptr, nullptr))
+ return;
+
+ GDBusNodeInfo *introspection_data = g_dbus_node_info_new_for_xml (contents, nullptr);
+
+ g_dbus_connection_register_object (connection,
+ "/org/libreoffice/PrintDialog",
+ introspection_data->interfaces[0],
+ nullptr,
+ nullptr, /* user_data */
+ nullptr, /* user_data_free_func */
+ nullptr); /* GError** */
+ g_free(contents);
+ g_dbus_node_info_unref(introspection_data);
+
+ CPDManager* current = static_cast<CPDManager*>(user_data);
+ std::vector<std::pair<std::string, gchar*>> backends = current->getTempBackends();
+ for (auto const& backend : backends)
+ {
+ // Get Interface for introspection
+ if (g_file_get_contents(BACKEND_INTERFACE, &contents, nullptr, nullptr))
+ {
+ introspection_data = g_dbus_node_info_new_for_xml (contents, nullptr);
+ GDBusProxy *proxy = g_dbus_proxy_new_sync (connection,
+ G_DBUS_PROXY_FLAGS_NONE,
+ introspection_data->interfaces[0],
+ backend.first.c_str(),
+ backend.second,
+ "org.openprinting.PrintBackend",
+ nullptr,
+ nullptr);
+ g_assert (proxy != nullptr);
+ g_dbus_proxy_call(proxy, "ActivateBackend",
+ nullptr,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, nullptr, nullptr, nullptr);
+
+ g_free(contents);
+ g_object_unref(proxy);
+ g_dbus_node_info_unref(introspection_data);
+ }
+ g_free(backend.second);
+ }
+}
+
+void CPDManager::onNameLost (GDBusConnection *,
+ const gchar *name,
+ gpointer)
+{
+ g_message("Name Lost: %s", name);
+}
+
+void CPDManager::printerAdded (GDBusConnection *connection,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ CPDManager* current = static_cast<CPDManager*>(user_data);
+ GDBusProxy *proxy;
+ proxy = current->getProxy(sender_name);
+ if (proxy == nullptr) {
+ gchar* contents;
+
+ // Get Interface for introspection
+ if (g_file_get_contents ("/usr/share/dbus-1/interfaces/org.openprinting.Backend.xml", &contents, nullptr, nullptr)) {
+ GDBusNodeInfo *introspection_data = g_dbus_node_info_new_for_xml (contents, nullptr);
+ proxy = g_dbus_proxy_new_sync (connection,
+ G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
+ introspection_data->interfaces[0],
+ sender_name,
+ object_path,
+ interface_name,
+ nullptr,
+ nullptr);
+
+ g_free(contents);
+ g_dbus_node_info_unref(introspection_data);
+ std::pair<std::string, GDBusProxy *> new_backend (sender_name, proxy);
+ current->addBackend(std::move(new_backend));
+ }
+ }
+ CPDPrinter *pDest = static_cast<CPDPrinter *>(malloc(sizeof(CPDPrinter)));
+ pDest->backend = proxy;
+ g_variant_get (parameters, "(sssssbss)", &(pDest->id), &(pDest->name), &(pDest->info), &(pDest->location), &(pDest->make_and_model), &(pDest->is_accepting_jobs), &(pDest->printer_state), &(pDest->backend_name));
+ std::stringstream printerName;
+ printerName << pDest->name << ", " << pDest->backend_name;
+ std::stringstream uniqueName;
+ uniqueName << pDest->id << ", " << pDest->backend_name;
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ OUString aPrinterName = OStringToOUString( printerName.str().c_str(), aEncoding );
+ OUString aUniqueName = OStringToOUString( uniqueName.str().c_str(), aEncoding );
+ current->addNewPrinter(aPrinterName, aUniqueName, pDest);
+}
+
+void CPDManager::printerRemoved (GDBusConnection *,
+ const gchar *,
+ const gchar *,
+ const gchar *,
+ const gchar *,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ // TODO: Remove every data linked to this particular printer.
+ CPDManager* pManager = static_cast<CPDManager*>(user_data);
+ char* id;
+ char* backend_name;
+ g_variant_get (parameters, "(ss)", &id, &backend_name);
+ std::stringstream uniqueName;
+ uniqueName << id << ", " << backend_name;
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ OUString aUniqueName = OStringToOUString( uniqueName.str().c_str(), aEncoding );
+ std::unordered_map<OUString, CPDPrinter *>::iterator it = pManager->m_aCPDDestMap.find( aUniqueName );
+ if (it == pManager->m_aCPDDestMap.end()) {
+ SAL_WARN("vcl.unx.print", "CPD trying to remove non-existent printer from list");
+ return;
+ }
+ pManager->m_aCPDDestMap.erase(it);
+ std::unordered_map<OUString, Printer>::iterator printersIt = pManager->m_aPrinters.find( aUniqueName );
+ if (printersIt == pManager->m_aPrinters.end()) {
+ SAL_WARN("vcl.unx.print", "CPD trying to remove non-existent printer from m_aPrinters");
+ return;
+ }
+ pManager->m_aPrinters.erase(printersIt);
+}
+
+GDBusProxy* CPDManager::getProxy(const std::string& target)
+{
+ std::unordered_map<std::string, GDBusProxy *>::const_iterator it = m_pBackends.find(target);
+ if (it == m_pBackends.end()) {
+ return nullptr;
+ }
+ return it->second;
+}
+
+void CPDManager::addBackend(std::pair<std::string, GDBusProxy *> pair) {
+ m_pBackends.insert(pair);
+}
+
+void CPDManager::addTempBackend(const std::pair<std::string, gchar*>& pair)
+{
+ m_tBackends.push_back(pair);
+}
+
+std::vector<std::pair<std::string, gchar*>> const & CPDManager::getTempBackends() const {
+ return m_tBackends;
+}
+
+void CPDManager::addNewPrinter(const OUString& aPrinterName, const OUString& aUniqueName, CPDPrinter *pDest) {
+ m_aCPDDestMap[aUniqueName] = pDest;
+ bool bSetToGlobalDefaults = m_aPrinters.find( aUniqueName ) == m_aPrinters.end();
+ Printer aPrinter = m_aPrinters[ aUniqueName ];
+ if( bSetToGlobalDefaults )
+ aPrinter.m_aInfo = m_aGlobalDefaults;
+ aPrinter.m_aInfo.m_aPrinterName = aPrinterName;
+
+ // TODO: I don't know how this should work when we have multiple
+ // sources with multiple possible defaults for each
+ // if( pDest->is_default )
+ // m_aDefaultPrinter = aPrinterName;
+
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ aPrinter.m_aInfo.m_aComment = OStringToOUString(pDest->info, aEncoding);
+ aPrinter.m_aInfo.m_aLocation = OStringToOUString(pDest->location, aEncoding);
+ // note: the parser that goes with the PrinterInfo
+ // is created implicitly by the JobData::operator=()
+ // when it detects the NULL ptr m_pParser.
+ // if we wanted to fill in the parser here this
+ // would mean we'd have to send a dbus message for each and
+ // every printer - which would be really bad runtime
+ // behaviour
+ aPrinter.m_aInfo.m_pParser = nullptr;
+ aPrinter.m_aInfo.m_aContext.setParser( nullptr );
+ std::unordered_map< OUString, PPDContext >::const_iterator c_it = m_aDefaultContexts.find( aUniqueName );
+ if( c_it != m_aDefaultContexts.end() )
+ {
+ aPrinter.m_aInfo.m_pParser = c_it->second.getParser();
+ aPrinter.m_aInfo.m_aContext = c_it->second;
+ }
+ aPrinter.m_aInfo.setDefaultBackend(true);
+ aPrinter.m_aInfo.m_aDriverName = "CPD:" + aUniqueName;
+ m_aPrinters[ aUniqueName ] = aPrinter;
+}
+#endif
+
+/*
+ * CPDManager class
+ */
+
+CPDManager* CPDManager::tryLoadCPD()
+{
+ CPDManager* pManager = nullptr;
+#if ENABLE_DBUS && ENABLE_GIO
+ static const char* pEnv = getenv("SAL_DISABLE_CPD");
+
+ if (!pEnv || !*pEnv) {
+ // interface description XML files are needed in 'onNameAcquired()'
+ if (!g_file_test(FRONTEND_INTERFACE, G_FILE_TEST_IS_REGULAR) ||
+ !g_file_test(BACKEND_INTERFACE, G_FILE_TEST_IS_REGULAR)) {
+ return nullptr;
+ }
+
+ GDir *dir;
+ const gchar *filename;
+ dir = g_dir_open(BACKEND_DIR, 0, nullptr);
+ if (dir != nullptr) {
+ while ((filename = g_dir_read_name(dir))) {
+ if (pManager == nullptr) {
+ pManager = new CPDManager();
+ }
+ gchar* contents;
+ std::stringstream filepath;
+ filepath << BACKEND_DIR << '/' << filename;
+ if (g_file_get_contents(filepath.str().c_str(), &contents, nullptr, nullptr))
+ {
+ std::pair<std::string, gchar*> new_tbackend (filename, contents);
+ pManager->addTempBackend(new_tbackend);
+ }
+ }
+ g_dir_close(dir);
+ }
+ }
+#endif
+ return pManager;
+}
+
+CPDManager::CPDManager() :
+ PrinterInfoManager( PrinterInfoManager::Type::CPD )
+{
+#if ENABLE_DBUS && ENABLE_GIO
+ // Get Destinations number and pointers
+ GError *error = nullptr;
+ m_pConnection = g_bus_get_sync (G_BUS_TYPE_SESSION, nullptr, &error);
+ g_assert_no_error (error);
+#endif
+}
+
+CPDManager::~CPDManager()
+{
+#if ENABLE_DBUS && ENABLE_GIO
+ g_dbus_connection_emit_signal (m_pConnection,
+ nullptr,
+ "/org/libreoffice/PrintDialog",
+ "org.openprinting.PrintFrontend",
+ "StopListing",
+ nullptr,
+ nullptr);
+ g_dbus_connection_flush_sync (m_pConnection,
+ nullptr,
+ nullptr);
+ g_dbus_connection_close_sync (m_pConnection,
+ nullptr,
+ nullptr);
+ for (auto const& backend : m_pBackends)
+ {
+ g_object_unref(backend.second);
+ }
+ for (auto const& backend : m_aCPDDestMap)
+ {
+ free(backend.second);
+ }
+#endif
+}
+
+
+const PPDParser* CPDManager::createCPDParser( const OUString& rPrinter )
+{
+ const PPDParser* pNewParser = nullptr;
+#if ENABLE_DBUS && ENABLE_GIO
+ OUString aPrinter;
+
+ if( rPrinter.startsWith("CPD:") )
+ aPrinter = rPrinter.copy( 4 );
+ else
+ aPrinter = rPrinter;
+
+ std::unordered_map< OUString, CPDPrinter * >::iterator dest_it =
+ m_aCPDDestMap.find( aPrinter );
+
+ if( dest_it != m_aCPDDestMap.end() )
+ {
+ CPDPrinter* pDest = dest_it->second;
+ GVariant* ret = nullptr;
+ GError* error = nullptr;
+ ret = g_dbus_proxy_call_sync (pDest->backend, "GetAllOptions",
+ g_variant_new("(s)", (pDest->id)),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, nullptr, &error);
+ if (ret != nullptr && error == nullptr)
+ {
+ // TODO: These keys need to be redefined to preserve usage across libreoffice
+ // InputSlot - media-col.media-source?
+ // Font - not needed now as it is required only for ps and we are using pdf
+ // Dial? - for FAX (need to look up PWG spec)
+
+ int num_attribute;
+ GVariantIter *iter_attr, *iter_supported_values;
+ g_variant_get (ret, "(ia(ssia(s)))", &num_attribute, &iter_attr);
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ PPDKey *pKey = nullptr;
+ OUString aValueName;
+ PPDValue* pValue;
+ std::vector<PPDKey*> keys;
+ std::vector<OUString> default_values;
+ for (int i = 0; i < num_attribute; i++) {
+ char *name, *default_value;
+ int num_supported_values;
+ g_variant_iter_loop(iter_attr, "(ssia(s))",
+ &name, &default_value,
+ &num_supported_values, &iter_supported_values);
+ OUString aOptionName = OStringToOUString( name, aEncoding );
+ OUString aDefaultValue = OStringToOUString( default_value, aEncoding );
+ if (aOptionName == "sides") {
+ // Duplex key is used throughout for checking Duplex Support
+ aOptionName = OUString("Duplex");
+ } else if (aOptionName == "printer-resolution") {
+ // Resolution key is used in places
+ aOptionName = OUString("Resolution");
+ } else if (aOptionName == "media") {
+ // PageSize key is used in many places
+ aOptionName = OUString("PageSize");
+ }
+ default_values.push_back(aDefaultValue);
+ pKey = new PPDKey( aOptionName );
+
+ // If number of values are 0, this is not settable via UI
+ if (num_supported_values > 0 && aDefaultValue != "NA")
+ pKey->m_bUIOption = true;
+
+ bool bDefaultFound = false;
+
+ for (int j = 0; j < num_supported_values; j++) {
+ char* value;
+ g_variant_iter_loop(iter_supported_values, "(s)", &value);
+ aValueName = OStringToOUString( value, aEncoding );
+ if (aOptionName == "Duplex") {
+ // Duplex key matches against very specific Values
+ if (aValueName == "one-sided") {
+ aValueName = OUString("None");
+ } else if (aValueName == "two-sided-long-edge") {
+ aValueName = OUString("DuplexNoTumble");
+ } else if (aValueName == "two-sided-short-edge") {
+ aValueName = OUString("DuplexTumble");
+ }
+ }
+
+ pValue = pKey->insertValue( aValueName, eQuoted );
+ if( ! pValue )
+ continue;
+ pValue->m_aValue = aValueName;
+
+ if (aValueName.equals(aDefaultValue)) {
+ pKey->m_pDefaultValue = pValue;
+ bDefaultFound = true;
+ }
+
+ }
+ // This could be done to ensure default values also appear as options:
+ if (!bDefaultFound && pKey->m_bUIOption) {
+ // pValue = pKey->insertValue( aDefaultValue, eQuoted );
+ // if( pValue )
+ // pValue->m_aValue = aDefaultValue;
+ }
+ keys.emplace_back(pKey);
+ }
+
+ pKey = new PPDKey("ModelName");
+ aValueName = OStringToOUString( "", aEncoding );
+ pValue = pKey->insertValue( aValueName, eQuoted );
+ if( pValue )
+ pValue->m_aValue = aValueName;
+ pKey->m_pDefaultValue = pValue;
+ keys.emplace_back(pKey);
+
+ pKey = new PPDKey("NickName");
+ aValueName = OStringToOUString( pDest->name, aEncoding );
+ pValue = pKey->insertValue( aValueName, eQuoted );
+ if( pValue )
+ pValue->m_aValue = aValueName;
+ pKey->m_pDefaultValue = pValue;
+ keys.emplace_back(pKey);
+
+ pNewParser = new PPDParser(aPrinter, keys);
+ PrinterInfo& rInfo = m_aPrinters[ aPrinter ].m_aInfo;
+ PPDContext& rContext = m_aDefaultContexts[ aPrinter ];
+ rContext.setParser( pNewParser );
+ setDefaultPaper( rContext );
+ std::vector<OUString>::iterator defit = default_values.begin();
+ for (auto const& key : keys)
+ {
+ const PPDValue* p1Value = key->getValue( *defit );
+ if( p1Value )
+ {
+ if( p1Value != key->getDefaultValue() )
+ {
+ rContext.setValue( key, p1Value, true );
+ SAL_INFO("vcl.unx.print", "key " << pKey->getKey() << " is set to " << *defit);
+ }
+ else
+ SAL_INFO("vcl.unx.print", "key " << pKey->getKey() << " is defaulted to " << *defit);
+ }
+ ++defit;
+ }
+
+ rInfo.m_pParser = pNewParser;
+ rInfo.m_aContext = rContext;
+ g_variant_unref(ret);
+ }
+ else
+ {
+ g_clear_error(&error);
+ SAL_INFO("vcl.unx.print", "CPD GetAllOptions failed, falling back to generic driver");
+ }
+ }
+ else
+ SAL_INFO("vcl.unx.print", "no dest found for printer " << aPrinter);
+
+ if( ! pNewParser )
+ {
+ // get the default PPD
+ pNewParser = PPDParser::getParser( "SGENPRT" );
+ SAL_WARN("vcl.unx.print", "Parsing default SGENPRT PPD" );
+
+ PrinterInfo& rInfo = m_aPrinters[ aPrinter ].m_aInfo;
+
+ rInfo.m_pParser = pNewParser;
+ rInfo.m_aContext.setParser( pNewParser );
+ }
+#else
+ (void)rPrinter;
+#endif
+ return pNewParser;
+}
+
+
+void CPDManager::initialize()
+{
+ // get normal printers, clear printer list
+ PrinterInfoManager::initialize();
+#if ENABLE_DBUS && ENABLE_GIO
+ g_bus_own_name_on_connection (m_pConnection,
+ "org.libreoffice.print-dialog",
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ onNameAcquired,
+ onNameLost,
+ this,
+ nullptr);
+
+ g_dbus_connection_signal_subscribe (m_pConnection, // DBus Connection
+ nullptr, // Sender Name
+ "org.openprinting.PrintBackend", // Sender Interface
+ "PrinterAdded", // Signal Name
+ nullptr, // Object Path
+ nullptr, // arg0 behaviour
+ G_DBUS_SIGNAL_FLAGS_NONE, // Signal Flags
+ printerAdded, // Callback Function
+ this,
+ nullptr);
+ g_dbus_connection_signal_subscribe (m_pConnection, // DBus Connection
+ nullptr, // Sender Name
+ "org.openprinting.PrintBackend", // Sender Interface
+ "PrinterRemoved", // Signal Name
+ nullptr, // Object Path
+ nullptr, // arg0 behaviour
+ G_DBUS_SIGNAL_FLAGS_NONE, // Signal Flags
+ printerRemoved, // Callback Function
+ this,
+ nullptr);
+
+ // remove everything that is not a CUPS printer and not
+ // a special purpose printer (PDF, Fax)
+ std::unordered_map< OUString, Printer >::iterator it = m_aPrinters.begin();
+ while (it != m_aPrinters.end())
+ {
+ if( m_aCPDDestMap.find( it->first ) != m_aCPDDestMap.end() )
+ {
+ ++it;
+ continue;
+ }
+
+ if( !it->second.m_aInfo.m_aFeatures.isEmpty() )
+ {
+ ++it;
+ continue;
+ }
+ it = m_aPrinters.erase(it);
+ }
+#endif
+}
+
+void CPDManager::setupJobContextData( JobData& rData )
+{
+#if ENABLE_DBUS && ENABLE_GIO
+ std::unordered_map<OUString, CPDPrinter *>::iterator dest_it =
+ m_aCPDDestMap.find( rData.m_aPrinterName );
+
+ if( dest_it == m_aCPDDestMap.end() )
+ return PrinterInfoManager::setupJobContextData( rData );
+
+ std::unordered_map< OUString, Printer >::iterator p_it =
+ m_aPrinters.find( rData.m_aPrinterName );
+ if( p_it == m_aPrinters.end() ) // huh ?
+ {
+ SAL_WARN("vcl.unx.print", "CPD printer list in disorder, "
+ "no dest for printer " << rData.m_aPrinterName);
+ return;
+ }
+
+ if( p_it->second.m_aInfo.m_pParser == nullptr )
+ {
+ // in turn calls createCPDParser
+ // which updates the printer info
+ p_it->second.m_aInfo.m_pParser = PPDParser::getParser( p_it->second.m_aInfo.m_aDriverName );
+ }
+ if( p_it->second.m_aInfo.m_aContext.getParser() == nullptr )
+ {
+ OUString aPrinter;
+ if( p_it->second.m_aInfo.m_aDriverName.startsWith("CPD:") )
+ aPrinter = p_it->second.m_aInfo.m_aDriverName.copy( 4 );
+ else
+ aPrinter = p_it->second.m_aInfo.m_aDriverName;
+
+ p_it->second.m_aInfo.m_aContext = m_aDefaultContexts[ aPrinter ];
+ }
+
+ rData.m_pParser = p_it->second.m_aInfo.m_pParser;
+ rData.m_aContext = p_it->second.m_aInfo.m_aContext;
+#else
+ (void)rData;
+#endif
+}
+
+FILE* CPDManager::startSpool( const OUString& rPrintername, bool bQuickCommand )
+{
+#if ENABLE_DBUS && ENABLE_GIO
+ SAL_INFO( "vcl.unx.print", "startSpool: " << rPrintername << " " << (bQuickCommand ? "true" : "false") );
+ if( m_aCPDDestMap.find( rPrintername ) == m_aCPDDestMap.end() )
+ {
+ SAL_INFO( "vcl.unx.print", "defer to PrinterInfoManager::startSpool" );
+ return PrinterInfoManager::startSpool( rPrintername, bQuickCommand );
+ }
+ OUString aTmpURL, aTmpFile;
+ osl_createTempFile( nullptr, nullptr, &aTmpURL.pData );
+ osl_getSystemPathFromFileURL( aTmpURL.pData, &aTmpFile.pData );
+ OString aSysFile = OUStringToOString( aTmpFile, osl_getThreadTextEncoding() );
+ FILE* fp = fopen( aSysFile.getStr(), "w" );
+ if( fp )
+ m_aSpoolFiles[fp] = aSysFile;
+
+ return fp;
+#else
+ (void)rPrintername;
+ (void)bQuickCommand;
+ return nullptr;
+#endif
+}
+
+#if ENABLE_DBUS && ENABLE_GIO
+void CPDManager::getOptionsFromDocumentSetup( const JobData& rJob, bool bBanner, const OString& rJobName, int& rNumOptions, GVariant **arr )
+{
+ GVariantBuilder *builder;
+ builder = g_variant_builder_new(G_VARIANT_TYPE("a(ss)"));
+ g_variant_builder_add(builder, "(ss)", "job-name", rJobName.getStr());
+ if( rJob.m_pParser == rJob.m_aContext.getParser() && rJob.m_pParser ) {
+ std::size_t i;
+ std::size_t nKeys = rJob.m_aContext.countValuesModified();
+ ::std::vector< const PPDKey* > aKeys( nKeys );
+ for( i = 0; i < nKeys; i++ )
+ aKeys[i] = rJob.m_aContext.getModifiedKey( i );
+ for( i = 0; i < nKeys; i++ ) {
+ const PPDKey* pKey = aKeys[i];
+ const PPDValue* pValue = rJob.m_aContext.getValue( pKey );
+ OUString sPayLoad;
+ if (pValue) {
+ sPayLoad = pValue->m_bCustomOption ? pValue->m_aCustomOption : pValue->m_aOption;
+ }
+ if (!sPayLoad.isEmpty()) {
+ OString aKey = OUStringToOString( pKey->getKey(), RTL_TEXTENCODING_ASCII_US );
+ OString aValue = OUStringToOString( sPayLoad, RTL_TEXTENCODING_ASCII_US );
+ if (aKey.equals("Duplex")) {
+ aKey = OString("sides");
+ } else if (aKey.equals("Resolution")) {
+ aKey = OString("printer-resolution");
+ } else if (aKey.equals("PageSize")) {
+ aKey = OString("media");
+ }
+ if (aKey.equals("sides")) {
+ if (aValue.equals("None")) {
+ aValue = OString("one-sided");
+ } else if (aValue.equals("DuplexNoTumble")) {
+ aValue = OString("two-sided-long-edge");
+ } else if (aValue.equals("DuplexTumble")) {
+ aValue = OString("two-sided-short-edge");
+ }
+ }
+ g_variant_builder_add(builder, "(ss)", aKey.getStr(), aValue.getStr());
+ }
+ }
+ }
+ if( rJob.m_nPDFDevice > 0 && rJob.m_nCopies > 1 )
+ {
+ OString aVal( OString::number( rJob.m_nCopies ) );
+ g_variant_builder_add(builder, "(ss)", "copies", aVal.getStr());
+ rNumOptions++;
+ // TODO: something for collate
+ // Maybe this is the equivalent ipp attribute:
+ if (rJob.m_bCollate) {
+ g_variant_builder_add(builder, "(ss)", "multiple-document-handling", "separate-documents-collated-copies");
+ } else {
+ g_variant_builder_add(builder, "(ss)", "multiple-document-handling", "separate-documents-uncollated-copies");
+ }
+ rNumOptions++;
+ }
+ if( ! bBanner )
+ {
+ g_variant_builder_add(builder, "(ss)", "job-sheets", "none");
+ rNumOptions++;
+ }
+ if (rJob.m_eOrientation == orientation::Portrait) {
+ g_variant_builder_add(builder, "(ss)", "orientation-requested", "portrait");
+ rNumOptions++;
+ } else if (rJob.m_eOrientation == orientation::Landscape) {
+ g_variant_builder_add(builder, "(ss)", "orientation-requested", "landscape");
+ rNumOptions++;
+ }
+ (*arr) = g_variant_new("a(ss)", builder);
+ g_variant_builder_unref(builder);
+}
+#endif
+
+bool CPDManager::endSpool( const OUString& rPrintername, const OUString& rJobTitle, FILE* pFile, const JobData& rDocumentJobData, bool bBanner, const OUString& rFaxNumber )
+{
+ bool success = false;
+#if ENABLE_DBUS && ENABLE_GIO
+ SAL_INFO( "vcl.unx.print", "endSpool: " << rPrintername << "," << rJobTitle << " copy count = " << rDocumentJobData.m_nCopies );
+ std::unordered_map< OUString, CPDPrinter * >::iterator dest_it =
+ m_aCPDDestMap.find( rPrintername );
+ if( dest_it == m_aCPDDestMap.end() )
+ {
+ SAL_INFO( "vcl.unx.print", "defer to PrinterInfoManager::endSpool" );
+ return PrinterInfoManager::endSpool( rPrintername, rJobTitle, pFile, rDocumentJobData, bBanner, rFaxNumber );
+ }
+
+ std::unordered_map< FILE*, OString, FPtrHash >::const_iterator it = m_aSpoolFiles.find( pFile );
+ if( it != m_aSpoolFiles.end() )
+ {
+ fclose( pFile );
+ rtl_TextEncoding aEnc = osl_getThreadTextEncoding();
+ OString sJobName(OUStringToOString(rJobTitle, aEnc));
+ if (!rFaxNumber.isEmpty())
+ {
+ sJobName = OUStringToOString(rFaxNumber, aEnc);
+ }
+ OString aSysFile = it->second;
+ CPDPrinter* pDest = dest_it->second;
+ GVariant* ret;
+ gint job_id;
+ int nNumOptions = 0;
+ GVariant *pArr = nullptr;
+ getOptionsFromDocumentSetup( rDocumentJobData, bBanner, sJobName, nNumOptions, &pArr );
+ ret = g_dbus_proxy_call_sync (pDest->backend, "printFile",
+ g_variant_new(
+ "(ssi@a(ss))",
+ (pDest->id),
+ aSysFile.getStr(),
+ nNumOptions,
+ pArr
+ ),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, nullptr, nullptr);
+ g_variant_get (ret, "(i)", &job_id);
+ if (job_id != -1) {
+ success = true;
+ }
+ g_variant_unref(ret);
+ unlink( it->second.getStr() );
+ m_aSpoolFiles.erase(it);
+ }
+#else
+ (void)rPrintername;
+ (void)rJobTitle;
+ (void)pFile;
+ (void)rDocumentJobData;
+ (void)bBanner;
+ (void)rFaxNumber;
+#endif
+ return success;
+}
+
+bool CPDManager::checkPrintersChanged( bool )
+{
+#if ENABLE_DBUS && ENABLE_GIO
+ bool bChanged = m_aPrintersChanged;
+ m_aPrintersChanged = false;
+ g_dbus_connection_emit_signal (m_pConnection,
+ nullptr,
+ "/org/libreoffice/PrintDialog",
+ "org.openprinting.PrintFrontend",
+ "RefreshBackend",
+ nullptr,
+ nullptr);
+ return bChanged;
+#else
+ return false;
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
+
diff --git a/vcl/unx/generic/printer/cupsmgr.cxx b/vcl/unx/generic/printer/cupsmgr.cxx
new file mode 100644
index 000000000..6acfe1db6
--- /dev/null
+++ b/vcl/unx/generic/printer/cupsmgr.cxx
@@ -0,0 +1,964 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <cups/cups.h>
+#include <cups/http.h>
+#include <cups/ipp.h>
+#include <cups/ppd.h>
+
+#include <unistd.h>
+
+#include <unx/cupsmgr.hxx>
+
+#include <o3tl/string_view.hxx>
+#include <osl/thread.h>
+#include <osl/file.h>
+#include <osl/conditn.hxx>
+
+#include <rtl/strbuf.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+
+#include <officecfg/Office/Common.hxx>
+
+#include <vcl/svapp.hxx>
+#include <vcl/weld.hxx>
+#include <vcl/window.hxx>
+
+#include <algorithm>
+#include <cstddef>
+#include <string_view>
+
+using namespace psp;
+using namespace osl;
+
+namespace {
+
+struct GetPPDAttribs
+{
+ osl::Condition m_aCondition;
+ OString m_aParameter;
+ OString m_aResult;
+ int m_nRefs;
+ bool* m_pResetRunning;
+ osl::Mutex* m_pSyncMutex;
+
+ GetPPDAttribs( const char * m_pParameter,
+ bool* pResetRunning, osl::Mutex* pSyncMutex )
+ : m_aParameter( m_pParameter ),
+ m_pResetRunning( pResetRunning ),
+ m_pSyncMutex( pSyncMutex )
+ {
+ m_nRefs = 2;
+ m_aCondition.reset();
+ }
+
+ ~GetPPDAttribs()
+ {
+ if( !m_aResult.isEmpty() )
+ unlink( m_aResult.getStr() );
+ }
+
+ void unref()
+ {
+ if( --m_nRefs == 0 )
+ {
+ *m_pResetRunning = false;
+ delete this;
+ }
+ }
+
+ void executeCall()
+ {
+ // This CUPS method is not at all thread-safe we need
+ // to dup the pointer to a static buffer it returns ASAP
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+ const char* pResult = cupsGetPPD(m_aParameter.getStr());
+ OString aResult = pResult ? OString(pResult) : OString();
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+ MutexGuard aGuard( *m_pSyncMutex );
+ m_aResult = aResult;
+ m_aCondition.set();
+ unref();
+ }
+
+ OString waitResult( TimeValue const *pDelay )
+ {
+ m_pSyncMutex->release();
+
+ if (m_aCondition.wait( pDelay ) != Condition::result_ok
+ )
+ {
+ SAL_WARN("vcl.unx.print",
+ "cupsGetPPD " << m_aParameter << " timed out");
+ }
+ m_pSyncMutex->acquire();
+
+ OString aRetval = m_aResult;
+ m_aResult.clear();
+ unref();
+
+ return aRetval;
+ }
+};
+
+}
+
+extern "C" {
+ static void getPPDWorker(void* pData)
+ {
+ osl_setThreadName("CUPSManager getPPDWorker");
+ GetPPDAttribs* pAttribs = static_cast<GetPPDAttribs*>(pData);
+ pAttribs->executeCall();
+ }
+}
+
+OString CUPSManager::threadedCupsGetPPD( const char* pPrinter )
+{
+ OString aResult;
+
+ m_aGetPPDMutex.acquire();
+ // if one thread hangs in cupsGetPPD already, don't start another
+ if( ! m_bPPDThreadRunning )
+ {
+ m_bPPDThreadRunning = true;
+ GetPPDAttribs* pAttribs = new GetPPDAttribs( pPrinter,
+ &m_bPPDThreadRunning,
+ &m_aGetPPDMutex );
+
+ oslThread aThread = osl_createThread( getPPDWorker, pAttribs );
+
+ TimeValue aValue;
+ aValue.Seconds = 5;
+ aValue.Nanosec = 0;
+
+ // NOTE: waitResult release and acquires the GetPPD mutex
+ aResult = pAttribs->waitResult( &aValue );
+ osl_destroyThread( aThread );
+ }
+ m_aGetPPDMutex.release();
+
+ return aResult;
+}
+
+static const char* setPasswordCallback( const char* /*pIn*/ )
+{
+ const char* pRet = nullptr;
+
+ PrinterInfoManager& rMgr = PrinterInfoManager::get();
+ if( rMgr.getType() == PrinterInfoManager::Type::CUPS ) // sanity check
+ pRet = static_cast<CUPSManager&>(rMgr).authenticateUser();
+ return pRet;
+}
+
+/*
+ * CUPSManager class
+ */
+
+CUPSManager* CUPSManager::tryLoadCUPS()
+{
+ CUPSManager* pManager = nullptr;
+ static const char* pEnv = getenv("SAL_DISABLE_CUPS");
+
+ if (!pEnv || !*pEnv)
+ pManager = new CUPSManager();
+ return pManager;
+}
+
+extern "C"
+{
+static void run_dest_thread_stub( void* pThis )
+{
+ osl_setThreadName("CUPSManager cupsGetDests");
+ CUPSManager::runDestThread( pThis );
+}
+}
+
+CUPSManager::CUPSManager() :
+ PrinterInfoManager( PrinterInfoManager::Type::CUPS ),
+ m_nDests( 0 ),
+ m_pDests( nullptr ),
+ m_bNewDests( false ),
+ m_bPPDThreadRunning( false )
+{
+ m_aDestThread = osl_createThread( run_dest_thread_stub, this );
+}
+
+CUPSManager::~CUPSManager()
+{
+ if( m_aDestThread )
+ {
+ osl_joinWithThread( m_aDestThread );
+ osl_destroyThread( m_aDestThread );
+ }
+
+ if (m_nDests && m_pDests)
+ cupsFreeDests( m_nDests, static_cast<cups_dest_t*>(m_pDests) );
+}
+
+void CUPSManager::runDestThread( void* pThis )
+{
+ static_cast<CUPSManager*>(pThis)->runDests();
+}
+
+void CUPSManager::runDests()
+{
+ SAL_INFO("vcl.unx.print", "starting cupsGetDests");
+ cups_dest_t* pDests = nullptr;
+
+ // n#722902 - do a fast-failing check for cups working *at all* first
+ http_t* p_http;
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+ if( (p_http=httpConnectEncrypt(
+ cupsServer(),
+ ippPort(),
+ cupsEncryption())) == nullptr )
+ return;
+
+ int nDests = cupsGetDests2(p_http, &pDests);
+ SAL_INFO("vcl.unx.print", "came out of cupsGetDests");
+
+ osl::MutexGuard aGuard( m_aCUPSMutex );
+ m_nDests = nDests;
+ m_pDests = pDests;
+ m_bNewDests = true;
+ SAL_INFO("vcl.unx.print", "finished cupsGetDests");
+
+ httpClose(p_http);
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+}
+
+void CUPSManager::initialize()
+{
+ // get normal printers, clear printer list
+ PrinterInfoManager::initialize();
+
+ // check whether thread has completed
+ // if not behave like old printing system
+ osl::MutexGuard aGuard( m_aCUPSMutex );
+
+ if( ! m_bNewDests )
+ return;
+
+ // dest thread has run, clean up
+ if( m_aDestThread )
+ {
+ osl_joinWithThread( m_aDestThread );
+ osl_destroyThread( m_aDestThread );
+ m_aDestThread = nullptr;
+ }
+ m_bNewDests = false;
+
+ // clear old stuff
+ m_aCUPSDestMap.clear();
+
+ if( ! (m_nDests && m_pDests ) )
+ return;
+
+ // check for CUPS server(?) > 1.2
+ // since there is no API to query, check for options that were
+ // introduced in dests with 1.2
+ // this is needed to check for %%IncludeFeature support
+ // (#i65684#, #i65491#)
+ bool bUsePDF = false;
+ cups_dest_t* pDest = static_cast<cups_dest_t*>(m_pDests);
+ const char* pOpt = cupsGetOption( "printer-info",
+ pDest->num_options,
+ pDest->options );
+ if( pOpt )
+ {
+ m_bUseIncludeFeature = true;
+ bUsePDF = officecfg::Office::Common::Print::Option::Printer::PDFAsStandardPrintJobFormat::get();
+ }
+
+ m_aGlobalDefaults.setDefaultBackend(bUsePDF);
+
+ // do not send include JobPatch; CUPS will insert that itself
+ // TODO: currently unknown which versions of CUPS insert JobPatches
+ // so currently it is assumed CUPS = don't insert JobPatch files
+ m_bUseJobPatch = false;
+
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ int nPrinter = m_nDests;
+
+ // reset global default PPD options; these are queried on demand from CUPS
+ m_aGlobalDefaults.m_pParser = nullptr;
+ m_aGlobalDefaults.m_aContext = PPDContext();
+
+ // add CUPS printers, should there be a printer
+ // with the same name as a CUPS printer, overwrite it
+ while( nPrinter-- )
+ {
+ pDest = static_cast<cups_dest_t*>(m_pDests)+nPrinter;
+ OUString aPrinterName = OStringToOUString( pDest->name, aEncoding );
+ if( pDest->instance && *pDest->instance )
+ {
+ aPrinterName += "/" +
+ OStringToOUString( pDest->instance, aEncoding );
+ }
+
+ // initialize printer with possible configuration from psprint.conf
+ bool bSetToGlobalDefaults = m_aPrinters.find( aPrinterName ) == m_aPrinters.end();
+ Printer aPrinter = m_aPrinters[ aPrinterName ];
+ if( bSetToGlobalDefaults )
+ aPrinter.m_aInfo = m_aGlobalDefaults;
+ aPrinter.m_aInfo.m_aPrinterName = aPrinterName;
+ if( pDest->is_default )
+ m_aDefaultPrinter = aPrinterName;
+
+ for( int k = 0; k < pDest->num_options; k++ )
+ {
+ if(!strcmp(pDest->options[k].name, "printer-info"))
+ aPrinter.m_aInfo.m_aComment=OStringToOUString(pDest->options[k].value, aEncoding);
+ if(!strcmp(pDest->options[k].name, "printer-location"))
+ aPrinter.m_aInfo.m_aLocation=OStringToOUString(pDest->options[k].value, aEncoding);
+ if(!strcmp(pDest->options[k].name, "auth-info-required"))
+ aPrinter.m_aInfo.m_aAuthInfoRequired=OStringToOUString(pDest->options[k].value, aEncoding);
+ }
+
+ // note: the parser that goes with the PrinterInfo
+ // is created implicitly by the JobData::operator=()
+ // when it detects the NULL ptr m_pParser.
+ // if we wanted to fill in the parser here this
+ // would mean we'd have to download PPDs for each and
+ // every printer - which would be really bad runtime
+ // behaviour
+ aPrinter.m_aInfo.m_pParser = nullptr;
+ aPrinter.m_aInfo.m_aContext.setParser( nullptr );
+ std::unordered_map< OUString, PPDContext >::const_iterator c_it = m_aDefaultContexts.find( aPrinterName );
+ if( c_it != m_aDefaultContexts.end() )
+ {
+ aPrinter.m_aInfo.m_pParser = c_it->second.getParser();
+ aPrinter.m_aInfo.m_aContext = c_it->second;
+ }
+ aPrinter.m_aInfo.setDefaultBackend(bUsePDF);
+ aPrinter.m_aInfo.m_aDriverName = "CUPS:" + aPrinterName;
+
+ m_aPrinters[ aPrinter.m_aInfo.m_aPrinterName ] = aPrinter;
+ m_aCUPSDestMap[ aPrinter.m_aInfo.m_aPrinterName ] = nPrinter;
+ }
+
+ // remove everything that is not a CUPS printer and not
+ // a special purpose printer (PDF, Fax)
+ std::unordered_map< OUString, Printer >::iterator it = m_aPrinters.begin();
+ while(it != m_aPrinters.end())
+ {
+ if( m_aCUPSDestMap.find( it->first ) != m_aCUPSDestMap.end() )
+ {
+ ++it;
+ continue;
+ }
+
+ if( !it->second.m_aInfo.m_aFeatures.isEmpty() )
+ {
+ ++it;
+ continue;
+ }
+ it = m_aPrinters.erase(it);
+ }
+
+ cupsSetPasswordCB( setPasswordCallback );
+}
+
+static void updatePrinterContextInfo( ppd_group_t* pPPDGroup, PPDContext& rContext )
+{
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ for( int i = 0; i < pPPDGroup->num_options; i++ )
+ {
+ ppd_option_t* pOption = pPPDGroup->options + i;
+ for( int n = 0; n < pOption->num_choices; n++ )
+ {
+ ppd_choice_t* pChoice = pOption->choices + n;
+ if( pChoice->marked )
+ {
+ const PPDKey* pKey = rContext.getParser()->getKey( OStringToOUString( pOption->keyword, aEncoding ) );
+ if( pKey )
+ {
+ const PPDValue* pValue = pKey->getValue( OStringToOUString( pChoice->choice, aEncoding ) );
+ if( pValue )
+ {
+ if( pValue != pKey->getDefaultValue() )
+ {
+ rContext.setValue( pKey, pValue, true );
+ SAL_INFO("vcl.unx.print", "key " << pOption->keyword << " is set to " << pChoice->choice);
+
+ }
+ else
+ SAL_INFO("vcl.unx.print", "key " << pOption->keyword << " is defaulted to " << pChoice->choice);
+ }
+ else
+ SAL_INFO("vcl.unx.print", "caution: value " << pChoice->choice << " not found in key " << pOption->keyword);
+ }
+ else
+ SAL_INFO("vcl.unx.print", "caution: key " << pOption->keyword << " not found in parser");
+ }
+ }
+ }
+
+ // recurse through subgroups
+ for( int g = 0; g < pPPDGroup->num_subgroups; g++ )
+ {
+ updatePrinterContextInfo( pPPDGroup->subgroups + g, rContext );
+ }
+}
+
+const PPDParser* CUPSManager::createCUPSParser( const OUString& rPrinter )
+{
+ const PPDParser* pNewParser = nullptr;
+ OUString aPrinter;
+
+ if( rPrinter.startsWith("CUPS:") )
+ aPrinter = rPrinter.copy( 5 );
+ else
+ aPrinter = rPrinter;
+
+ if( m_aCUPSMutex.tryToAcquire() )
+ {
+ if (m_nDests && m_pDests)
+ {
+ std::unordered_map< OUString, int >::iterator dest_it =
+ m_aCUPSDestMap.find( aPrinter );
+ if( dest_it != m_aCUPSDestMap.end() )
+ {
+ cups_dest_t* pDest = static_cast<cups_dest_t*>(m_pDests) + dest_it->second;
+ OString aPPDFile = threadedCupsGetPPD( pDest->name );
+ SAL_INFO("vcl.unx.print",
+ "PPD for " << aPrinter << " is " << aPPDFile);
+ if( !aPPDFile.isEmpty() )
+ {
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ OUString aFileName( OStringToOUString( aPPDFile, aEncoding ) );
+ // update the printer info with context information
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+ ppd_file_t* pPPD = ppdOpenFile( aPPDFile.getStr() );
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+ if( pPPD )
+ {
+ // create the new parser
+ PPDParser* pCUPSParser = new PPDParser( aFileName );
+ pCUPSParser->m_aFile = rPrinter;
+ pNewParser = pCUPSParser;
+
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+ /*int nConflicts =*/ cupsMarkOptions( pPPD, pDest->num_options, pDest->options );
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+ SAL_INFO("vcl.unx.print", "processing the following options for printer " << pDest->name << " (instance " << (pDest->instance == nullptr ? "null" : pDest->instance) << "):");
+ for( int k = 0; k < pDest->num_options; k++ )
+ SAL_INFO("vcl.unx.print",
+ " \"" << pDest->options[k].name <<
+ "\" = \"" << pDest->options[k].value << "\"");
+ PrinterInfo& rInfo = m_aPrinters[ aPrinter ].m_aInfo;
+
+ // remember the default context for later use
+ PPDContext& rContext = m_aDefaultContexts[ aPrinter ];
+ rContext.setParser( pNewParser );
+ // set system default paper; printer CUPS PPD options
+ // may overwrite it
+ setDefaultPaper( rContext );
+ for( int i = 0; i < pPPD->num_groups; i++ )
+ updatePrinterContextInfo( pPPD->groups + i, rContext );
+
+ rInfo.m_pParser = pNewParser;
+ rInfo.m_aContext = rContext;
+
+ // clean up the mess
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+ ppdClose( pPPD );
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+
+ }
+ else
+ SAL_INFO("vcl.unx.print", "ppdOpenFile failed, falling back to generic driver");
+
+ // remove temporary PPD file
+ if (!getenv("SAL_CUPS_PPD_RETAIN_TMP"))
+ unlink( aPPDFile.getStr() );
+ }
+ else
+ SAL_INFO("vcl.unx.print", "cupsGetPPD failed, falling back to generic driver");
+ }
+ else
+ SAL_INFO("vcl.unx.print", "no dest found for printer " << aPrinter);
+ }
+ m_aCUPSMutex.release();
+ }
+ else
+ SAL_WARN("vcl.unx.print", "could not acquire CUPS mutex !!!" );
+
+ if( ! pNewParser )
+ {
+ // get the default PPD
+ pNewParser = PPDParser::getParser( "SGENPRT" );
+ SAL_INFO("vcl.unx.print", "Parsing default SGENPRT PPD" );
+
+ PrinterInfo& rInfo = m_aPrinters[ aPrinter ].m_aInfo;
+
+ rInfo.m_pParser = pNewParser;
+ rInfo.m_aContext.setParser( pNewParser );
+ }
+
+ return pNewParser;
+}
+
+void CUPSManager::setupJobContextData( JobData& rData )
+{
+ std::unordered_map< OUString, int >::iterator dest_it =
+ m_aCUPSDestMap.find( rData.m_aPrinterName );
+
+ if( dest_it == m_aCUPSDestMap.end() )
+ return PrinterInfoManager::setupJobContextData( rData );
+
+ std::unordered_map< OUString, Printer >::iterator p_it =
+ m_aPrinters.find( rData.m_aPrinterName );
+ if( p_it == m_aPrinters.end() ) // huh ?
+ {
+ SAL_WARN("vcl.unx.print", "CUPS printer list in disorder, "
+ "no dest for printer " << rData.m_aPrinterName);
+ return;
+ }
+
+ if( p_it->second.m_aInfo.m_pParser == nullptr )
+ {
+ // in turn calls createCUPSParser
+ // which updates the printer info
+ p_it->second.m_aInfo.m_pParser = PPDParser::getParser( p_it->second.m_aInfo.m_aDriverName );
+ }
+ if( p_it->second.m_aInfo.m_aContext.getParser() == nullptr )
+ {
+ OUString aPrinter;
+ if( p_it->second.m_aInfo.m_aDriverName.startsWith("CUPS:") )
+ aPrinter = p_it->second.m_aInfo.m_aDriverName.copy( 5 );
+ else
+ aPrinter = p_it->second.m_aInfo.m_aDriverName;
+
+ p_it->second.m_aInfo.m_aContext = m_aDefaultContexts[ aPrinter ];
+ }
+
+ rData.m_pParser = p_it->second.m_aInfo.m_pParser;
+ rData.m_aContext = p_it->second.m_aInfo.m_aContext;
+}
+
+FILE* CUPSManager::startSpool( const OUString& rPrintername, bool bQuickCommand )
+{
+ SAL_INFO( "vcl.unx.print", "startSpool: " << rPrintername << " " << (bQuickCommand ? "true" : "false") );
+
+ if( m_aCUPSDestMap.find( rPrintername ) == m_aCUPSDestMap.end() )
+ {
+ SAL_INFO( "vcl.unx.print", "defer to PrinterInfoManager::startSpool" );
+ return PrinterInfoManager::startSpool( rPrintername, bQuickCommand );
+ }
+
+ OUString aTmpURL, aTmpFile;
+ osl_createTempFile( nullptr, nullptr, &aTmpURL.pData );
+ osl_getSystemPathFromFileURL( aTmpURL.pData, &aTmpFile.pData );
+ OString aSysFile = OUStringToOString( aTmpFile, osl_getThreadTextEncoding() );
+ FILE* fp = fopen( aSysFile.getStr(), "w" );
+ if( fp )
+ m_aSpoolFiles[fp] = aSysFile;
+
+ return fp;
+}
+
+namespace {
+
+struct less_ppd_key
+{
+ bool operator()(const PPDKey* left, const PPDKey* right)
+ { return left->getOrderDependency() < right->getOrderDependency(); }
+};
+
+}
+
+void CUPSManager::getOptionsFromDocumentSetup( const JobData& rJob, bool bBanner, int& rNumOptions, void** rOptions )
+{
+ rNumOptions = 0;
+ *rOptions = nullptr;
+
+ // emit features ordered to OrderDependency
+ // ignore features that are set to default
+
+ // sanity check
+ if( rJob.m_pParser == rJob.m_aContext.getParser() && rJob.m_pParser )
+ {
+ std::size_t i;
+ std::size_t nKeys = rJob.m_aContext.countValuesModified();
+ ::std::vector< const PPDKey* > aKeys( nKeys );
+ for( i = 0; i < nKeys; i++ )
+ aKeys[i] = rJob.m_aContext.getModifiedKey( i );
+ ::std::sort( aKeys.begin(), aKeys.end(), less_ppd_key() );
+
+ for( i = 0; i < nKeys; i++ )
+ {
+ const PPDKey* pKey = aKeys[i];
+ const PPDValue* pValue = rJob.m_aContext.getValue( pKey );
+ OUString sPayLoad;
+ if (pValue && pValue->m_eType == eInvocation)
+ {
+ sPayLoad = pValue->m_bCustomOption ? pValue->m_aCustomOption : pValue->m_aOption;
+ }
+
+ if (!sPayLoad.isEmpty())
+ {
+ OString aKey = OUStringToOString( pKey->getKey(), RTL_TEXTENCODING_ASCII_US );
+ OString aValue = OUStringToOString( sPayLoad, RTL_TEXTENCODING_ASCII_US );
+ rNumOptions = cupsAddOption( aKey.getStr(), aValue.getStr(), rNumOptions, reinterpret_cast<cups_option_t**>(rOptions) );
+ }
+ }
+ }
+
+ if( rJob.m_nPDFDevice > 0 && rJob.m_nCopies > 1 )
+ {
+ OString aVal( OString::number( rJob.m_nCopies ) );
+ rNumOptions = cupsAddOption( "copies", aVal.getStr(), rNumOptions, reinterpret_cast<cups_option_t**>(rOptions) );
+ aVal = OString::boolean(rJob.m_bCollate);
+ rNumOptions = cupsAddOption( "collate", aVal.getStr(), rNumOptions, reinterpret_cast<cups_option_t**>(rOptions) );
+ }
+ if( ! bBanner )
+ {
+ rNumOptions = cupsAddOption( "job-sheets", "none", rNumOptions, reinterpret_cast<cups_option_t**>(rOptions) );
+ }
+}
+
+namespace
+{
+ class RTSPWDialog : public weld::GenericDialogController
+ {
+ std::unique_ptr<weld::Label> m_xText;
+ std::unique_ptr<weld::Label> m_xDomainLabel;
+ std::unique_ptr<weld::Entry> m_xDomainEdit;
+ std::unique_ptr<weld::Label> m_xUserLabel;
+ std::unique_ptr<weld::Entry> m_xUserEdit;
+ std::unique_ptr<weld::Label> m_xPassLabel;
+ std::unique_ptr<weld::Entry> m_xPassEdit;
+
+ public:
+ RTSPWDialog(weld::Window* pParent, std::string_view rServer, std::string_view rUserName);
+
+ OString getDomain() const
+ {
+ return OUStringToOString( m_xDomainEdit->get_text(), osl_getThreadTextEncoding() );
+ }
+
+ OString getUserName() const
+ {
+ return OUStringToOString( m_xUserEdit->get_text(), osl_getThreadTextEncoding() );
+ }
+
+ OString getPassword() const
+ {
+ return OUStringToOString( m_xPassEdit->get_text(), osl_getThreadTextEncoding() );
+ }
+
+ void SetDomainVisible(bool bShow)
+ {
+ m_xDomainLabel->set_visible(bShow);
+ m_xDomainEdit->set_visible(bShow);
+ }
+
+ void SetUserVisible(bool bShow)
+ {
+ m_xUserLabel->set_visible(bShow);
+ m_xUserEdit->set_visible(bShow);
+ }
+
+ void SetPassVisible(bool bShow)
+ {
+ m_xPassLabel->set_visible(bShow);
+ m_xPassEdit->set_visible(bShow);
+ }
+ };
+
+ RTSPWDialog::RTSPWDialog(weld::Window* pParent, std::string_view rServer, std::string_view rUserName)
+ : GenericDialogController(pParent, "vcl/ui/cupspassworddialog.ui", "CUPSPasswordDialog")
+ , m_xText(m_xBuilder->weld_label("text"))
+ , m_xDomainLabel(m_xBuilder->weld_label("label3"))
+ , m_xDomainEdit(m_xBuilder->weld_entry("domain"))
+ , m_xUserLabel(m_xBuilder->weld_label("label1"))
+ , m_xUserEdit(m_xBuilder->weld_entry("user"))
+ , m_xPassLabel(m_xBuilder->weld_label("label2"))
+ , m_xPassEdit(m_xBuilder->weld_entry("pass"))
+ {
+ OUString aText(m_xText->get_label());
+ aText = aText.replaceFirst("%s", OStringToOUString(rServer, osl_getThreadTextEncoding()));
+ m_xText->set_label(aText);
+ m_xDomainEdit->set_text("WORKGROUP");
+ if (rUserName.empty())
+ m_xUserEdit->grab_focus();
+ else
+ {
+ m_xUserEdit->set_text(OStringToOUString(rUserName, osl_getThreadTextEncoding()));
+ m_xPassEdit->grab_focus();
+ }
+ }
+
+ bool AuthenticateQuery(std::string_view rServer, OString& rUserName, OString& rPassword)
+ {
+ bool bRet = false;
+
+ RTSPWDialog aDialog(Application::GetDefDialogParent(), rServer, rUserName);
+ if (aDialog.run() == RET_OK)
+ {
+ rUserName = aDialog.getUserName();
+ rPassword = aDialog.getPassword();
+ bRet = true;
+ }
+
+ return bRet;
+ }
+}
+
+namespace
+{
+ OString EscapeCupsOption(const OString& rIn)
+ {
+ OStringBuffer sRet;
+ sal_Int32 nLen = rIn.getLength();
+ for (sal_Int32 i = 0; i < nLen; ++i)
+ {
+ switch(rIn[i])
+ {
+ case '\\':
+ case '\'':
+ case '\"':
+ case ',':
+ case ' ':
+ case '\f':
+ case '\n':
+ case '\r':
+ case '\t':
+ case '\v':
+ sRet.append('\\');
+ break;
+ }
+ sRet.append(rIn[i]);
+ }
+ return sRet.makeStringAndClear();
+ }
+}
+
+bool CUPSManager::endSpool( const OUString& rPrintername, const OUString& rJobTitle, FILE* pFile, const JobData& rDocumentJobData, bool bBanner, const OUString& rFaxNumber )
+{
+ SAL_INFO( "vcl.unx.print", "endSpool: " << rPrintername << "," << rJobTitle << " copy count = " << rDocumentJobData.m_nCopies );
+
+ int nJobID = 0;
+
+ osl::MutexGuard aGuard( m_aCUPSMutex );
+
+ std::unordered_map< OUString, int >::iterator dest_it =
+ m_aCUPSDestMap.find( rPrintername );
+ if( dest_it == m_aCUPSDestMap.end() )
+ {
+ SAL_INFO( "vcl.unx.print", "defer to PrinterInfoManager::endSpool" );
+ return PrinterInfoManager::endSpool( rPrintername, rJobTitle, pFile, rDocumentJobData, bBanner, rFaxNumber );
+ }
+
+ std::unordered_map< FILE*, OString, FPtrHash >::const_iterator it = m_aSpoolFiles.find( pFile );
+ if( it != m_aSpoolFiles.end() )
+ {
+ fclose( pFile );
+ rtl_TextEncoding aEnc = osl_getThreadTextEncoding();
+
+ // setup cups options
+ int nNumOptions = 0;
+ cups_option_t* pOptions = nullptr;
+ auto ppOptions = reinterpret_cast<void**>(&pOptions);
+ getOptionsFromDocumentSetup( rDocumentJobData, bBanner, nNumOptions, ppOptions );
+
+ PrinterInfo aInfo(getPrinterInfo(rPrintername));
+ if (!aInfo.m_aAuthInfoRequired.isEmpty())
+ {
+ bool bDomain(false), bUser(false), bPass(false);
+ sal_Int32 nIndex = 0;
+ do
+ {
+ std::u16string_view aToken = o3tl::getToken(aInfo.m_aAuthInfoRequired, 0, ',', nIndex);
+ if (aToken == u"domain")
+ bDomain = true;
+ else if (aToken == u"username")
+ bUser = true;
+ else if (aToken == u"password")
+ bPass = true;
+ }
+ while (nIndex >= 0);
+
+ if (bDomain || bUser || bPass)
+ {
+ OString sPrinterName(OUStringToOString(rPrintername, RTL_TEXTENCODING_UTF8));
+ OString sUser = cupsUser();
+ RTSPWDialog aDialog(Application::GetDefDialogParent(), sPrinterName, sUser);
+ aDialog.SetDomainVisible(bDomain);
+ aDialog.SetUserVisible(bUser);
+ aDialog.SetPassVisible(bPass);
+
+ if (aDialog.run() == RET_OK)
+ {
+ OString sAuth;
+ if (bDomain)
+ sAuth = EscapeCupsOption(aDialog.getDomain());
+ if (bUser)
+ {
+ if (bDomain)
+ sAuth += ",";
+ sAuth += EscapeCupsOption(aDialog.getUserName());
+ }
+ if (bPass)
+ {
+ if (bUser || bDomain)
+ sAuth += ",";
+ sAuth += EscapeCupsOption(aDialog.getPassword());
+ }
+ nNumOptions = cupsAddOption("auth-info", sAuth.getStr(), nNumOptions, &pOptions);
+ }
+ }
+ }
+
+ OString sJobName(OUStringToOString(rJobTitle, aEnc));
+
+ //fax4CUPS, "the job name will be dialled for you"
+ //so override the jobname with the desired number
+ if (!rFaxNumber.isEmpty())
+ {
+ sJobName = OUStringToOString(rFaxNumber, aEnc);
+ }
+
+ cups_dest_t* pDest = static_cast<cups_dest_t*>(m_pDests) + dest_it->second;
+ nJobID = cupsPrintFile(pDest->name,
+ it->second.getStr(),
+ sJobName.getStr(),
+ nNumOptions, pOptions);
+ SAL_INFO("vcl.unx.print", "cupsPrintFile( " << pDest->name << ", "
+ << it->second << ", " << rJobTitle << ", " << nNumOptions
+ << ", " << pOptions << " ) returns " << nJobID);
+ for( int n = 0; n < nNumOptions; n++ )
+ SAL_INFO("vcl.unx.print",
+ " option " << pOptions[n].name << "=" << pOptions[n].value);
+#if OSL_DEBUG_LEVEL > 1
+ OString aCmd( "cp " );
+ aCmd += it->second.getStr();
+ aCmd += OString( " $HOME/cupsprint.ps" );
+ system( aCmd.getStr() );
+#endif
+
+ unlink( it->second.getStr() );
+ m_aSpoolFiles.erase(it);
+ if( pOptions )
+ cupsFreeOptions( nNumOptions, pOptions );
+ }
+
+ return nJobID != 0;
+}
+
+bool CUPSManager::checkPrintersChanged( bool bWait )
+{
+ bool bChanged = false;
+ if( bWait )
+ {
+ if( m_aDestThread )
+ {
+ // initial asynchronous detection still running
+ SAL_INFO("vcl.unx.print", "syncing cups discovery thread");
+ osl_joinWithThread( m_aDestThread );
+ osl_destroyThread( m_aDestThread );
+ m_aDestThread = nullptr;
+ SAL_INFO("vcl.unx.print", "done: syncing cups discovery thread");
+ }
+ else
+ {
+ // #i82321# check for cups printer updates
+ // with this change the whole asynchronous detection in a thread is
+ // almost useless. The only relevance left is for some stalled systems
+ // where the user can set SAL_DISABLE_SYNCHRONOUS_PRINTER_DETECTION
+ // (see vcl/unx/source/gdi/salprnpsp.cxx)
+ // so that checkPrintersChanged( true ) will never be called
+
+ // there is no way to query CUPS whether the printer list has changed
+ // so get the dest list anew
+ if( m_nDests && m_pDests )
+ cupsFreeDests( m_nDests, static_cast<cups_dest_t*>(m_pDests) );
+ m_nDests = 0;
+ m_pDests = nullptr;
+ runDests();
+ }
+ }
+ if( m_aCUPSMutex.tryToAcquire() )
+ {
+ bChanged = m_bNewDests;
+ m_aCUPSMutex.release();
+ }
+
+ if( ! bChanged )
+ {
+ bChanged = PrinterInfoManager::checkPrintersChanged( bWait );
+ // #i54375# ensure new merging with CUPS list in :initialize
+ if( bChanged )
+ m_bNewDests = true;
+ }
+
+ if( bChanged )
+ initialize();
+
+ return bChanged;
+}
+
+const char* CUPSManager::authenticateUser()
+{
+ const char* pRet = nullptr;
+
+ osl::MutexGuard aGuard( m_aCUPSMutex );
+
+ OString aUser = cupsUser();
+ OString aServer = cupsServer();
+ OString aPassword;
+ if (AuthenticateQuery(aServer, aUser, aPassword))
+ {
+ m_aPassword = aPassword;
+ m_aUser = aUser;
+ cupsSetUser( m_aUser.getStr() );
+ pRet = m_aPassword.getStr();
+ }
+
+ return pRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/printer/jobdata.cxx b/vcl/unx/generic/printer/jobdata.cxx
new file mode 100644
index 000000000..9707a8109
--- /dev/null
+++ b/vcl/unx/generic/printer/jobdata.cxx
@@ -0,0 +1,316 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <officecfg/Office/Common.hxx>
+#include <jobdata.hxx>
+#include <printerinfomanager.hxx>
+#include <tools/stream.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <rtl/strbuf.hxx>
+#include <memory>
+
+using namespace psp;
+
+JobData& JobData::operator=(const JobData& rRight)
+{
+ if(this == &rRight)
+ return *this;
+
+ m_nCopies = rRight.m_nCopies;
+ m_bCollate = rRight.m_bCollate;
+ m_nLeftMarginAdjust = rRight.m_nLeftMarginAdjust;
+ m_nRightMarginAdjust = rRight.m_nRightMarginAdjust;
+ m_nTopMarginAdjust = rRight.m_nTopMarginAdjust;
+ m_nBottomMarginAdjust = rRight.m_nBottomMarginAdjust;
+ m_nColorDepth = rRight.m_nColorDepth;
+ m_eOrientation = rRight.m_eOrientation;
+ m_aPrinterName = rRight.m_aPrinterName;
+ m_bPapersizeFromSetup = rRight.m_bPapersizeFromSetup;
+ m_pParser = rRight.m_pParser;
+ m_aContext = rRight.m_aContext;
+ m_nPSLevel = rRight.m_nPSLevel;
+ m_nPDFDevice = rRight.m_nPDFDevice;
+ m_nColorDevice = rRight.m_nColorDevice;
+
+ if( !m_pParser && !m_aPrinterName.isEmpty() )
+ {
+ PrinterInfoManager& rMgr = PrinterInfoManager::get();
+ rMgr.setupJobContextData( *this );
+ }
+ return *this;
+}
+
+void JobData::setCollate( bool bCollate )
+{
+ if (m_nPDFDevice > 0)
+ {
+ m_bCollate = bCollate;
+ return;
+ }
+ const PPDParser* pParser = m_aContext.getParser();
+ if( !pParser )
+ return;
+
+ const PPDKey* pKey = pParser->getKey( "Collate" );
+ if( !pKey )
+ return;
+
+ const PPDValue* pVal = nullptr;
+ if( bCollate )
+ pVal = pKey->getValue( "True" );
+ else
+ {
+ pVal = pKey->getValue( "False" );
+ if( ! pVal )
+ pVal = pKey->getValue( "None" );
+ }
+ m_aContext.setValue( pKey, pVal );
+}
+
+void JobData::setPaper( int i_nWidth, int i_nHeight )
+{
+ if( m_pParser )
+ {
+ OUString aPaper( m_pParser->matchPaper( i_nWidth, i_nHeight ) );
+
+ const PPDKey* pKey = m_pParser->getKey( "PageSize" );
+ const PPDValue* pValue = pKey ? pKey->getValueCaseInsensitive( aPaper ) : nullptr;
+
+ if (pKey && pValue)
+ m_aContext.setValue( pKey, pValue );
+ }
+}
+
+void JobData::setPaperBin( int i_nPaperBin )
+{
+ if( m_pParser )
+ {
+ const PPDKey* pKey = m_pParser->getKey( "InputSlot" );
+ const PPDValue* pValue = pKey ? pKey->getValue( i_nPaperBin ) : nullptr;
+
+ if (pKey && pValue)
+ m_aContext.setValue( pKey, pValue );
+ }
+}
+
+bool JobData::getStreamBuffer( void*& pData, sal_uInt32& bytes )
+{
+ // consistency checks
+ if( ! m_pParser )
+ m_pParser = m_aContext.getParser();
+ if( m_pParser != m_aContext.getParser() ||
+ ! m_pParser )
+ return false;
+
+ SvMemoryStream aStream;
+
+ // write header job data
+ aStream.WriteLine("JobData 1");
+
+ OStringBuffer aLine;
+
+ aLine.append("printer=");
+ aLine.append(OUStringToOString(m_aPrinterName, RTL_TEXTENCODING_UTF8));
+ aStream.WriteLine(aLine);
+ aLine.setLength(0);
+
+ aLine.append("orientation=");
+ if (m_eOrientation == orientation::Landscape)
+ aLine.append("Landscape");
+ else
+ aLine.append("Portrait");
+ aStream.WriteLine(aLine);
+ aLine.setLength(0);
+
+ aLine.append("copies=");
+ aLine.append(static_cast<sal_Int32>(m_nCopies));
+ aStream.WriteLine(aLine);
+ aLine.setLength(0);
+
+ if (m_nPDFDevice > 0)
+ {
+ aLine.append("collate=");
+ aLine.append(OString::boolean(m_bCollate));
+ aStream.WriteLine(aLine);
+ aLine.setLength(0);
+ }
+
+ aLine.append("marginadjustment=");
+ aLine.append(static_cast<sal_Int32>(m_nLeftMarginAdjust));
+ aLine.append(',');
+ aLine.append(static_cast<sal_Int32>(m_nRightMarginAdjust));
+ aLine.append(',');
+ aLine.append(static_cast<sal_Int32>(m_nTopMarginAdjust));
+ aLine.append(',');
+ aLine.append(static_cast<sal_Int32>(m_nBottomMarginAdjust));
+ aStream.WriteLine(aLine);
+ aLine.setLength(0);
+
+ aLine.append("colordepth=");
+ aLine.append(static_cast<sal_Int32>(m_nColorDepth));
+ aStream.WriteLine(aLine);
+ aLine.setLength(0);
+
+ aLine.append("pslevel=");
+ aLine.append(static_cast<sal_Int32>(m_nPSLevel));
+ aStream.WriteLine(aLine);
+ aLine.setLength(0);
+
+ aLine.append("pdfdevice=");
+ aLine.append(static_cast<sal_Int32>(m_nPDFDevice));
+ aStream.WriteLine(aLine);
+ aLine.setLength(0);
+
+ aLine.append("colordevice=");
+ aLine.append(static_cast<sal_Int32>(m_nColorDevice));
+ aStream.WriteLine(aLine);
+ aLine.setLength(0);
+
+ // now append the PPDContext stream buffer
+ aStream.WriteLine( "PPDContextData" );
+ sal_uLong nBytes;
+ std::unique_ptr<char[]> pContextBuffer(m_aContext.getStreamableBuffer( nBytes ));
+ if( nBytes )
+ aStream.WriteBytes( pContextBuffer.get(), nBytes );
+ pContextBuffer.reset();
+
+ // success
+ bytes = static_cast<sal_uInt32>(aStream.Tell());
+ pData = std::malloc( bytes );
+ memcpy( pData, aStream.GetData(), bytes );
+ return true;
+}
+
+bool JobData::constructFromStreamBuffer( const void* pData, sal_uInt32 bytes, JobData& rJobData )
+{
+ SvMemoryStream aStream( const_cast<void*>(pData), bytes, StreamMode::READ );
+ OString aLine;
+ bool bVersion = false;
+ bool bPrinter = false;
+ bool bOrientation = false;
+ bool bCopies = false;
+ bool bContext = false;
+ bool bMargin = false;
+ bool bColorDepth = false;
+ bool bColorDevice = false;
+ bool bPSLevel = false;
+ bool bPDFDevice = false;
+
+ const char printerEquals[] = "printer=";
+ const char orientatationEquals[] = "orientation=";
+ const char copiesEquals[] = "copies=";
+ const char collateEquals[] = "collate=";
+ const char marginadjustmentEquals[] = "marginadjustment=";
+ const char colordepthEquals[] = "colordepth=";
+ const char colordeviceEquals[] = "colordevice=";
+ const char pslevelEquals[] = "pslevel=";
+ const char pdfdeviceEquals[] = "pdfdevice=";
+
+ while( ! aStream.eof() )
+ {
+ aStream.ReadLine( aLine );
+ if (aLine.startsWith("JobData"))
+ bVersion = true;
+ else if (aLine.startsWith(printerEquals))
+ {
+ bPrinter = true;
+ rJobData.m_aPrinterName = OStringToOUString(aLine.subView(RTL_CONSTASCII_LENGTH(printerEquals)), RTL_TEXTENCODING_UTF8);
+ }
+ else if (aLine.startsWith(orientatationEquals))
+ {
+ bOrientation = true;
+ rJobData.m_eOrientation = o3tl::equalsIgnoreAsciiCase(aLine.subView(RTL_CONSTASCII_LENGTH(orientatationEquals)), "landscape") ? orientation::Landscape : orientation::Portrait;
+ }
+ else if (aLine.startsWith(copiesEquals))
+ {
+ bCopies = true;
+ rJobData.m_nCopies = o3tl::toInt32(aLine.subView(RTL_CONSTASCII_LENGTH(copiesEquals)));
+ }
+ else if (aLine.startsWith(collateEquals))
+ {
+ rJobData.m_bCollate = aLine.copy(RTL_CONSTASCII_LENGTH(collateEquals)).toBoolean();
+ }
+ else if (aLine.startsWith(marginadjustmentEquals))
+ {
+ bMargin = true;
+ sal_Int32 nIdx {RTL_CONSTASCII_LENGTH(marginadjustmentEquals)};
+ rJobData.m_nLeftMarginAdjust = o3tl::toInt32(o3tl::getToken(aLine, 0, ',', nIdx));
+ rJobData.m_nRightMarginAdjust = o3tl::toInt32(o3tl::getToken(aLine, 0, ',', nIdx));
+ rJobData.m_nTopMarginAdjust = o3tl::toInt32(o3tl::getToken(aLine, 0, ',', nIdx));
+ rJobData.m_nBottomMarginAdjust = o3tl::toInt32(o3tl::getToken(aLine, 0, ',', nIdx));
+ }
+ else if (aLine.startsWith(colordepthEquals))
+ {
+ bColorDepth = true;
+ rJobData.m_nColorDepth = o3tl::toInt32(aLine.subView(RTL_CONSTASCII_LENGTH(colordepthEquals)));
+ }
+ else if (aLine.startsWith(colordeviceEquals))
+ {
+ bColorDevice = true;
+ rJobData.m_nColorDevice = o3tl::toInt32(aLine.subView(RTL_CONSTASCII_LENGTH(colordeviceEquals)));
+ }
+ else if (aLine.startsWith(pslevelEquals))
+ {
+ bPSLevel = true;
+ rJobData.m_nPSLevel = o3tl::toInt32(aLine.subView(RTL_CONSTASCII_LENGTH(pslevelEquals)));
+ }
+ else if (aLine.startsWith(pdfdeviceEquals))
+ {
+ bPDFDevice = true;
+ rJobData.m_nPDFDevice = o3tl::toInt32(aLine.subView(RTL_CONSTASCII_LENGTH(pdfdeviceEquals)));
+ }
+ else if (aLine == "PPDContextData" && bPrinter)
+ {
+ PrinterInfoManager& rManager = PrinterInfoManager::get();
+ const PrinterInfo& rInfo = rManager.getPrinterInfo( rJobData.m_aPrinterName );
+ rJobData.m_pParser = PPDParser::getParser( rInfo.m_aDriverName );
+ if( rJobData.m_pParser )
+ {
+ rJobData.m_aContext.setParser( rJobData.m_pParser );
+ sal_uInt64 nBytes = bytes - aStream.Tell();
+ std::vector<char> aRemain(nBytes+1);
+ nBytes = aStream.ReadBytes(aRemain.data(), nBytes);
+ if (nBytes)
+ {
+ aRemain.resize(nBytes+1);
+ aRemain[nBytes] = 0;
+ rJobData.m_aContext.rebuildFromStreamBuffer(aRemain);
+ bContext = true;
+ }
+ }
+ }
+ }
+
+ return bVersion && bPrinter && bOrientation && bCopies && bContext && bMargin && bPSLevel && bPDFDevice && bColorDevice && bColorDepth;
+}
+
+void JobData::resolveDefaultBackend()
+{
+ if (m_nPSLevel == 0 && m_nPDFDevice == 0)
+ setDefaultBackend(officecfg::Office::Common::Print::Option::Printer::PDFAsStandardPrintJobFormat::get());
+}
+
+void JobData::setDefaultBackend(bool bUsePDF)
+{
+ if (bUsePDF && m_nPSLevel == 0 && m_nPDFDevice == 0)
+ m_nPDFDevice = 1;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/printer/ppdparser.cxx b/vcl/unx/generic/printer/ppdparser.cxx
new file mode 100644
index 000000000..c16d257e2
--- /dev/null
+++ b/vcl/unx/generic/printer/ppdparser.cxx
@@ -0,0 +1,1991 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <stdlib.h>
+
+#include <comphelper/string.hxx>
+#include <o3tl/string_view.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <ppdparser.hxx>
+#include <strhelper.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+
+#include <unx/helper.hxx>
+#include <unx/cupsmgr.hxx>
+#include <unx/cpdmgr.hxx>
+
+#include <tools/urlobj.hxx>
+#include <tools/stream.hxx>
+#include <tools/zcodec.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/file.hxx>
+#include <osl/process.h>
+#include <osl/thread.h>
+#include <rtl/strbuf.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <salhelper/linkhelper.hxx>
+
+#include <com/sun/star/lang/Locale.hpp>
+
+#include <mutex>
+#include <unordered_map>
+
+#ifdef ENABLE_CUPS
+#include <cups/cups.h>
+#endif
+
+#include <config_dbus.h>
+#include <config_gio.h>
+#include <o3tl/hash_combine.hxx>
+
+namespace psp
+{
+ class PPDTranslator
+ {
+ struct LocaleEqual
+ {
+ bool operator()(const css::lang::Locale& i_rLeft,
+ const css::lang::Locale& i_rRight) const
+ {
+ return i_rLeft.Language == i_rRight.Language &&
+ i_rLeft.Country == i_rRight.Country &&
+ i_rLeft.Variant == i_rRight.Variant;
+ }
+ };
+
+ struct LocaleHash
+ {
+ size_t operator()(const css::lang::Locale& rLocale) const
+ {
+ std::size_t seed = 0;
+ o3tl::hash_combine(seed, rLocale.Language.hashCode());
+ o3tl::hash_combine(seed, rLocale.Country.hashCode());
+ o3tl::hash_combine(seed, rLocale.Variant.hashCode());
+ return seed;
+ }
+ };
+
+ typedef std::unordered_map< css::lang::Locale, OUString, LocaleHash, LocaleEqual > translation_map;
+ typedef std::unordered_map< OUString, translation_map > key_translation_map;
+
+ key_translation_map m_aTranslations;
+ public:
+ PPDTranslator() {}
+
+ void insertValue(
+ const OUString& i_rKey,
+ const OUString& i_rOption,
+ const OUString& i_rValue,
+ const OUString& i_rTranslation,
+ const css::lang::Locale& i_rLocale
+ );
+
+ void insertOption( const OUString& i_rKey,
+ const OUString& i_rOption,
+ const OUString& i_rTranslation,
+ const css::lang::Locale& i_rLocale )
+ {
+ insertValue( i_rKey, i_rOption, OUString(), i_rTranslation, i_rLocale );
+ }
+
+ void insertKey( const OUString& i_rKey,
+ const OUString& i_rTranslation,
+ const css::lang::Locale& i_rLocale = css::lang::Locale() )
+ {
+ insertValue( i_rKey, OUString(), OUString(), i_rTranslation, i_rLocale );
+ }
+
+ OUString translateValue(
+ const OUString& i_rKey,
+ const OUString& i_rOption
+ ) const;
+
+ OUString translateOption( const OUString& i_rKey,
+ const OUString& i_rOption ) const
+ {
+ return translateValue( i_rKey, i_rOption );
+ }
+
+ OUString translateKey( const OUString& i_rKey ) const
+ {
+ return translateValue( i_rKey, OUString() );
+ }
+ };
+
+ static css::lang::Locale normalizeInputLocale(
+ const css::lang::Locale& i_rLocale
+ )
+ {
+ css::lang::Locale aLoc( i_rLocale );
+ if( aLoc.Language.isEmpty() )
+ {
+ // empty locale requested, fill in application UI locale
+ aLoc = Application::GetSettings().GetUILanguageTag().getLocale();
+
+ #if OSL_DEBUG_LEVEL > 1
+ static const char* pEnvLocale = getenv( "SAL_PPDPARSER_LOCALE" );
+ if( pEnvLocale && *pEnvLocale )
+ {
+ OString aStr( pEnvLocale );
+ sal_Int32 nLen = aStr.getLength();
+ aLoc.Language = OStringToOUString( aStr.copy( 0, std::min(nLen, 2) ), RTL_TEXTENCODING_MS_1252 );
+ if( nLen >=5 && aStr[2] == '_' )
+ aLoc.Country = OStringToOUString( aStr.copy( 3, 2 ), RTL_TEXTENCODING_MS_1252 );
+ else
+ aLoc.Country.clear();
+ aLoc.Variant.clear();
+ }
+ #endif
+ }
+ /* FIXME-BCP47: using Variant, uppercase? */
+ aLoc.Language = aLoc.Language.toAsciiLowerCase();
+ aLoc.Country = aLoc.Country.toAsciiUpperCase();
+ aLoc.Variant = aLoc.Variant.toAsciiUpperCase();
+
+ return aLoc;
+ }
+
+ void PPDTranslator::insertValue(
+ const OUString& i_rKey,
+ const OUString& i_rOption,
+ const OUString& i_rValue,
+ const OUString& i_rTranslation,
+ const css::lang::Locale& i_rLocale
+ )
+ {
+ OUStringBuffer aKey( i_rKey.getLength() + i_rOption.getLength() + i_rValue.getLength() + 2 );
+ aKey.append( i_rKey );
+ if( !i_rOption.isEmpty() || !i_rValue.isEmpty() )
+ {
+ aKey.append( ':' );
+ aKey.append( i_rOption );
+ }
+ if( !i_rValue.isEmpty() )
+ {
+ aKey.append( ':' );
+ aKey.append( i_rValue );
+ }
+ if( !aKey.isEmpty() && !i_rTranslation.isEmpty() )
+ {
+ OUString aK( aKey.makeStringAndClear() );
+ css::lang::Locale aLoc;
+ /* FIXME-BCP47: using Variant, uppercase? */
+ aLoc.Language = i_rLocale.Language.toAsciiLowerCase();
+ aLoc.Country = i_rLocale.Country.toAsciiUpperCase();
+ aLoc.Variant = i_rLocale.Variant.toAsciiUpperCase();
+ m_aTranslations[ aK ][ aLoc ] = i_rTranslation;
+ }
+ }
+
+ OUString PPDTranslator::translateValue(
+ const OUString& i_rKey,
+ const OUString& i_rOption
+ ) const
+ {
+ OUString aResult;
+
+ OUStringBuffer aKey( i_rKey.getLength() + i_rOption.getLength() + 2 );
+ aKey.append( i_rKey );
+ if( !i_rOption.isEmpty() )
+ {
+ aKey.append( ':' );
+ aKey.append( i_rOption );
+ }
+ if( !aKey.isEmpty() )
+ {
+ OUString aK( aKey.makeStringAndClear() );
+ key_translation_map::const_iterator it = m_aTranslations.find( aK );
+ if( it != m_aTranslations.end() )
+ {
+ const translation_map& rMap( it->second );
+
+ css::lang::Locale aLoc( normalizeInputLocale( css::lang::Locale() ) );
+ /* FIXME-BCP47: use LanguageTag::getFallbackStrings()? */
+ for( int nTry = 0; nTry < 4; nTry++ )
+ {
+ translation_map::const_iterator tr = rMap.find( aLoc );
+ if( tr != rMap.end() )
+ {
+ aResult = tr->second;
+ break;
+ }
+ switch( nTry )
+ {
+ case 0: aLoc.Variant.clear();break;
+ case 1: aLoc.Country.clear();break;
+ case 2: aLoc.Language.clear();break;
+ }
+ }
+ }
+ }
+ return aResult;
+ }
+
+ class PPDCache
+ {
+ public:
+ std::vector< std::unique_ptr<PPDParser> > aAllParsers;
+ std::optional<std::unordered_map< OUString, OUString >> xAllPPDFiles;
+ };
+}
+
+using namespace psp;
+
+namespace
+{
+ PPDCache& getPPDCache()
+ {
+ static PPDCache thePPDCache;
+ return thePPDCache;
+ }
+
+class PPDDecompressStream
+{
+private:
+ PPDDecompressStream(const PPDDecompressStream&) = delete;
+ PPDDecompressStream& operator=(const PPDDecompressStream&) = delete;
+
+ std::unique_ptr<SvFileStream> mpFileStream;
+ std::unique_ptr<SvMemoryStream> mpMemStream;
+ OUString maFileName;
+
+public:
+ explicit PPDDecompressStream( const OUString& rFile );
+ ~PPDDecompressStream();
+
+ bool IsOpen() const;
+ bool eof() const;
+ OString ReadLine();
+ void Open( const OUString& i_rFile );
+ void Close();
+ const OUString& GetFileName() const { return maFileName; }
+};
+
+}
+
+PPDDecompressStream::PPDDecompressStream( const OUString& i_rFile )
+{
+ Open( i_rFile );
+}
+
+PPDDecompressStream::~PPDDecompressStream()
+{
+ Close();
+}
+
+void PPDDecompressStream::Open( const OUString& i_rFile )
+{
+ Close();
+
+ mpFileStream.reset( new SvFileStream( i_rFile, StreamMode::READ ) );
+ maFileName = mpFileStream->GetFileName();
+
+ if( ! mpFileStream->IsOpen() )
+ {
+ Close();
+ return;
+ }
+
+ OString aLine;
+ mpFileStream->ReadLine( aLine );
+ mpFileStream->Seek( 0 );
+
+ // check for compress'ed or gzip'ed file
+ if( aLine.getLength() <= 1 ||
+ static_cast<unsigned char>(aLine[0]) != 0x1f ||
+ static_cast<unsigned char>(aLine[1]) != 0x8b /* check for gzip */ )
+ return;
+
+ // so let's try to decompress the stream
+ mpMemStream.reset( new SvMemoryStream( 4096, 4096 ) );
+ ZCodec aCodec;
+ aCodec.BeginCompression( ZCODEC_DEFAULT_COMPRESSION, /*gzLib*/true );
+ tools::Long nComp = aCodec.Decompress( *mpFileStream, *mpMemStream );
+ aCodec.EndCompression();
+ if( nComp < 0 )
+ {
+ // decompression failed, must be an uncompressed stream after all
+ mpMemStream.reset();
+ mpFileStream->Seek( 0 );
+ }
+ else
+ {
+ // compression successful, can get rid of file stream
+ mpFileStream.reset();
+ mpMemStream->Seek( 0 );
+ }
+}
+
+void PPDDecompressStream::Close()
+{
+ mpMemStream.reset();
+ mpFileStream.reset();
+}
+
+bool PPDDecompressStream::IsOpen() const
+{
+ return (mpMemStream || (mpFileStream && mpFileStream->IsOpen()));
+}
+
+bool PPDDecompressStream::eof() const
+{
+ return ( mpMemStream ? mpMemStream->eof() : ( mpFileStream == nullptr || mpFileStream->eof() ) );
+}
+
+OString PPDDecompressStream::ReadLine()
+{
+ OString o_rLine;
+ if( mpMemStream )
+ mpMemStream->ReadLine( o_rLine );
+ else if( mpFileStream )
+ mpFileStream->ReadLine( o_rLine );
+ return o_rLine;
+}
+
+static osl::FileBase::RC resolveLink( const OUString& i_rURL, OUString& o_rResolvedURL, OUString& o_rBaseName, osl::FileStatus::Type& o_rType)
+{
+ salhelper::LinkResolver aResolver(osl_FileStatus_Mask_FileName |
+ osl_FileStatus_Mask_Type |
+ osl_FileStatus_Mask_FileURL);
+
+ osl::FileBase::RC aRet = aResolver.fetchFileStatus(i_rURL, 10/*nLinkLevel*/);
+
+ if (aRet == osl::FileBase::E_None)
+ {
+ o_rResolvedURL = aResolver.m_aStatus.getFileURL();
+ o_rBaseName = aResolver.m_aStatus.getFileName();
+ o_rType = aResolver.m_aStatus.getFileType();
+ }
+
+ return aRet;
+}
+
+void PPDParser::scanPPDDir( const OUString& rDir )
+{
+ static struct suffix_t
+ {
+ const char* pSuffix;
+ const sal_Int32 nSuffixLen;
+ } const pSuffixes[] =
+ { { ".PS", 3 }, { ".PPD", 4 }, { ".PS.GZ", 6 }, { ".PPD.GZ", 7 } };
+
+ PPDCache &rPPDCache = getPPDCache();
+
+ osl::Directory aDir( rDir );
+ if ( aDir.open() != osl::FileBase::E_None )
+ return;
+
+ osl::DirectoryItem aItem;
+
+ INetURLObject aPPDDir(rDir);
+ while( aDir.getNextItem( aItem ) == osl::FileBase::E_None )
+ {
+ osl::FileStatus aStatus( osl_FileStatus_Mask_FileName );
+ if( aItem.getFileStatus( aStatus ) == osl::FileBase::E_None )
+ {
+ OUString aFileURL, aFileName;
+ osl::FileStatus::Type eType = osl::FileStatus::Unknown;
+ OUString aURL = rDir + "/" + aStatus.getFileName();
+
+ if(resolveLink( aURL, aFileURL, aFileName, eType ) == osl::FileBase::E_None)
+ {
+ if( eType == osl::FileStatus::Regular )
+ {
+ INetURLObject aPPDFile = aPPDDir;
+ aPPDFile.Append( aFileName );
+
+ // match extension
+ for(const suffix_t & rSuffix : pSuffixes)
+ {
+ if( aFileName.getLength() > rSuffix.nSuffixLen )
+ {
+ if( aFileName.endsWithIgnoreAsciiCaseAsciiL( rSuffix.pSuffix, rSuffix.nSuffixLen ) )
+ {
+ (*rPPDCache.xAllPPDFiles)[ aFileName.copy( 0, aFileName.getLength() - rSuffix.nSuffixLen ) ] = aPPDFile.PathToFileName();
+ break;
+ }
+ }
+ }
+ }
+ else if( eType == osl::FileStatus::Directory )
+ {
+ scanPPDDir( aFileURL );
+ }
+ }
+ }
+ }
+ aDir.close();
+}
+
+void PPDParser::initPPDFiles(PPDCache &rPPDCache)
+{
+ if( rPPDCache.xAllPPDFiles )
+ return;
+
+ rPPDCache.xAllPPDFiles.emplace();
+
+ // check installation directories
+ std::vector< OUString > aPathList;
+ psp::getPrinterPathList( aPathList, PRINTER_PPDDIR );
+ for (auto const& path : aPathList)
+ {
+ INetURLObject aPPDDir( path, INetProtocol::File, INetURLObject::EncodeMechanism::All );
+ scanPPDDir( aPPDDir.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
+ }
+ if( rPPDCache.xAllPPDFiles->find( OUString( "SGENPRT" ) ) != rPPDCache.xAllPPDFiles->end() )
+ return;
+
+ // last try: search in directory of executable (mainly for setup)
+ OUString aExe;
+ if( osl_getExecutableFile( &aExe.pData ) == osl_Process_E_None )
+ {
+ INetURLObject aDir( aExe );
+ aDir.removeSegment();
+ SAL_INFO("vcl.unx.print", "scanning last chance dir: "
+ << aDir.GetMainURL(INetURLObject::DecodeMechanism::NONE));
+ scanPPDDir( aDir.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
+ SAL_INFO("vcl.unx.print", "SGENPRT "
+ << (rPPDCache.xAllPPDFiles->find("SGENPRT") ==
+ rPPDCache.xAllPPDFiles->end() ? "not found" : "found"));
+ }
+}
+
+OUString PPDParser::getPPDFile( const OUString& rFile )
+{
+ INetURLObject aPPD( rFile, INetProtocol::File, INetURLObject::EncodeMechanism::All );
+ // someone might enter a full qualified name here
+ PPDDecompressStream aStream( aPPD.PathToFileName() );
+ if( ! aStream.IsOpen() )
+ {
+ std::unordered_map< OUString, OUString >::const_iterator it;
+ PPDCache &rPPDCache = getPPDCache();
+
+ bool bRetry = true;
+ do
+ {
+ initPPDFiles(rPPDCache);
+ // some PPD files contain dots beside the extension, so try name first
+ // and cut of points after that
+ OUString aBase( rFile );
+ sal_Int32 nLastIndex = aBase.lastIndexOf( '/' );
+ if( nLastIndex >= 0 )
+ aBase = aBase.copy( nLastIndex+1 );
+ do
+ {
+ it = rPPDCache.xAllPPDFiles->find( aBase );
+ nLastIndex = aBase.lastIndexOf( '.' );
+ if( nLastIndex > 0 )
+ aBase = aBase.copy( 0, nLastIndex );
+ } while( it == rPPDCache.xAllPPDFiles->end() && nLastIndex > 0 );
+
+ if( it == rPPDCache.xAllPPDFiles->end() && bRetry )
+ {
+ // a new file ? rehash
+ rPPDCache.xAllPPDFiles.reset();
+ bRetry = false;
+ // note this is optimized for office start where
+ // no new files occur and initPPDFiles is called only once
+ }
+ } while( ! rPPDCache.xAllPPDFiles );
+
+ if( it != rPPDCache.xAllPPDFiles->end() )
+ aStream.Open( it->second );
+ }
+
+ OUString aRet;
+ if( aStream.IsOpen() )
+ {
+ OString aLine = aStream.ReadLine();
+ if (aLine.startsWith("*PPD-Adobe"))
+ aRet = aStream.GetFileName();
+ else
+ {
+ // our *Include hack does usually not begin
+ // with *PPD-Adobe, so try some lines for *Include
+ int nLines = 10;
+ while (aLine.indexOf("*Include") != 0 && --nLines)
+ aLine = aStream.ReadLine();
+ if( nLines )
+ aRet = aStream.GetFileName();
+ }
+ }
+
+ return aRet;
+}
+
+const PPDParser* PPDParser::getParser( const OUString& rFile )
+{
+ // Recursive because we can get re-entered via CUPSManager::createCUPSParser
+ static std::recursive_mutex aMutex;
+ std::scoped_lock aGuard( aMutex );
+
+ OUString aFile = rFile;
+ if( !rFile.startsWith( "CUPS:" ) && !rFile.startsWith( "CPD:" ) )
+ aFile = getPPDFile( rFile );
+ if( aFile.isEmpty() )
+ {
+ SAL_INFO("vcl.unx.print", "Could not get printer PPD file \""
+ << rFile << "\" !");
+ return nullptr;
+ }
+ else
+ SAL_INFO("vcl.unx.print", "Parsing printer info from \""
+ << rFile << "\" !");
+
+
+ PPDCache &rPPDCache = getPPDCache();
+ for( auto const & i : rPPDCache.aAllParsers )
+ if( i->m_aFile == aFile )
+ return i.get();
+
+ PPDParser* pNewParser = nullptr;
+ if( !aFile.startsWith( "CUPS:" ) && !aFile.startsWith( "CPD:" ) )
+ pNewParser = new PPDParser( aFile );
+ else
+ {
+ PrinterInfoManager& rMgr = PrinterInfoManager::get();
+ if( rMgr.getType() == PrinterInfoManager::Type::CUPS )
+ {
+#ifdef ENABLE_CUPS
+ pNewParser = const_cast<PPDParser*>(static_cast<CUPSManager&>(rMgr).createCUPSParser( aFile ));
+#endif
+ } else if ( rMgr.getType() == PrinterInfoManager::Type::CPD )
+ {
+#if ENABLE_DBUS && ENABLE_GIO
+ pNewParser = const_cast<PPDParser*>(static_cast<CPDManager&>(rMgr).createCPDParser( aFile ));
+#endif
+ }
+ }
+ if( pNewParser )
+ {
+ // this may actually be the SGENPRT parser,
+ // so ensure uniqueness here (but don't remove last we delete us!)
+ if (std::none_of(
+ rPPDCache.aAllParsers.begin(),
+ rPPDCache.aAllParsers.end(),
+ [pNewParser] (std::unique_ptr<PPDParser> const & x) { return x.get() == pNewParser; } ))
+ {
+ // insert new parser to vector
+ rPPDCache.aAllParsers.emplace_back(pNewParser);
+ }
+ }
+ return pNewParser;
+}
+
+PPDParser::PPDParser(const OUString& rFile, const std::vector<PPDKey*>& keys)
+ : m_aFile(rFile)
+ , m_bColorDevice(false)
+ , m_bType42Capable(false)
+ , m_nLanguageLevel(0)
+ , m_aFileEncoding(RTL_TEXTENCODING_MS_1252)
+ , m_pImageableAreas(nullptr)
+ , m_pDefaultPaperDimension(nullptr)
+ , m_pPaperDimensions(nullptr)
+ , m_pDefaultInputSlot(nullptr)
+ , m_pDefaultResolution(nullptr)
+ , m_pTranslator(new PPDTranslator())
+{
+ for (auto & key: keys)
+ {
+ insertKey( std::unique_ptr<PPDKey>(key) );
+ }
+
+ // fill in shortcuts
+ const PPDKey* pKey;
+
+ pKey = getKey( "PageSize" );
+
+ if ( pKey ) {
+ std::unique_ptr<PPDKey> pImageableAreas(new PPDKey("ImageableArea"));
+ std::unique_ptr<PPDKey> pPaperDimensions(new PPDKey("PaperDimension"));
+#if defined(CUPS_VERSION_MAJOR)
+#if (CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR >= 7) || CUPS_VERSION_MAJOR > 1
+ for (int i = 0; i < pKey->countValues(); i++) {
+ const PPDValue* pValue = pKey -> getValue(i);
+ OUString aValueName = pValue -> m_aOption;
+ PPDValue* pImageableAreaValue = pImageableAreas -> insertValue( aValueName, eQuoted );
+ PPDValue* pPaperDimensionValue = pPaperDimensions -> insertValue( aValueName, eQuoted );
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ OString o = OUStringToOString( aValueName, aEncoding );
+ pwg_media_t *pPWGMedia = pwgMediaForPWG(o.pData->buffer);
+ if (pPWGMedia != nullptr) {
+ OUStringBuffer aBuf( 256 );
+ aBuf = "0 0 " +
+ OUString::number(PWG_TO_POINTS(pPWGMedia -> width)) +
+ " " +
+ OUString::number(PWG_TO_POINTS(pPWGMedia -> length));
+ if ( pImageableAreaValue )
+ pImageableAreaValue->m_aValue = aBuf.makeStringAndClear();
+ aBuf.append( PWG_TO_POINTS(pPWGMedia -> width) );
+ aBuf.append( " " );
+ aBuf.append( PWG_TO_POINTS(pPWGMedia -> length) );
+ if ( pPaperDimensionValue )
+ pPaperDimensionValue->m_aValue = aBuf.makeStringAndClear();
+ if (aValueName.equals(pKey -> getDefaultValue() -> m_aOption)) {
+ pImageableAreas -> m_pDefaultValue = pImageableAreaValue;
+ pPaperDimensions -> m_pDefaultValue = pPaperDimensionValue;
+ }
+ }
+ }
+#endif // HAVE_CUPS_API_1_7
+#endif
+ insertKey(std::move(pImageableAreas));
+ insertKey(std::move(pPaperDimensions));
+ }
+
+ m_pImageableAreas = getKey( "ImageableArea" );
+ const PPDValue* pDefaultImageableArea = nullptr;
+ if( m_pImageableAreas )
+ pDefaultImageableArea = m_pImageableAreas->getDefaultValue();
+ if (m_pImageableAreas == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no ImageableArea in " << m_aFile);
+ }
+ if (pDefaultImageableArea == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no DefaultImageableArea in " << m_aFile);
+ }
+
+ m_pPaperDimensions = getKey( "PaperDimension" );
+ if( m_pPaperDimensions )
+ m_pDefaultPaperDimension = m_pPaperDimensions->getDefaultValue();
+ if (m_pPaperDimensions == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no PaperDimensions in " << m_aFile);
+ }
+ if (m_pDefaultPaperDimension == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no DefaultPaperDimensions in " << m_aFile);
+ }
+
+ auto pResolutions = getKey( "Resolution" );
+ if( pResolutions )
+ m_pDefaultResolution = pResolutions->getDefaultValue();
+ if (pResolutions == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no Resolution in " << m_aFile);
+ }
+ SAL_INFO_IF(!m_pDefaultResolution, "vcl.unx.print", "no DefaultResolution in " + m_aFile);
+
+ auto pInputSlots = getKey( "InputSlot" );
+ if( pInputSlots )
+ m_pDefaultInputSlot = pInputSlots->getDefaultValue();
+ SAL_INFO_IF(!pInputSlots, "vcl.unx.print", "no InputSlot in " << m_aFile);
+ SAL_INFO_IF(!m_pDefaultInputSlot, "vcl.unx.print", "no DefaultInputSlot in " << m_aFile);
+
+ auto pFontList = getKey( "Font" );
+ if (pFontList == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no Font in " << m_aFile);
+ }
+
+ // fill in direct values
+ if( (pKey = getKey( "print-color-mode" )) )
+ m_bColorDevice = pKey->countValues() > 1;
+}
+
+PPDParser::PPDParser( const OUString& rFile ) :
+ m_aFile( rFile ),
+ m_bColorDevice( false ),
+ m_bType42Capable( false ),
+ m_nLanguageLevel( 0 ),
+ m_aFileEncoding( RTL_TEXTENCODING_MS_1252 ),
+ m_pImageableAreas( nullptr ),
+ m_pDefaultPaperDimension( nullptr ),
+ m_pPaperDimensions( nullptr ),
+ m_pDefaultInputSlot( nullptr ),
+ m_pDefaultResolution( nullptr ),
+ m_pTranslator( new PPDTranslator() )
+{
+ // read in the file
+ std::vector< OString > aLines;
+ PPDDecompressStream aStream( m_aFile );
+ if( aStream.IsOpen() )
+ {
+ bool bLanguageEncoding = false;
+ while( ! aStream.eof() )
+ {
+ OString aCurLine = aStream.ReadLine();
+ if( aCurLine.startsWith("*") )
+ {
+ if (aCurLine.matchIgnoreAsciiCase("*include:"))
+ {
+ aCurLine = aCurLine.copy(9);
+ aCurLine = comphelper::string::strip(aCurLine, ' ');
+ aCurLine = comphelper::string::strip(aCurLine, '\t');
+ aCurLine = comphelper::string::stripEnd(aCurLine, '\r');
+ aCurLine = comphelper::string::stripEnd(aCurLine, '\n');
+ aCurLine = comphelper::string::strip(aCurLine, '"');
+ aStream.Close();
+ aStream.Open(getPPDFile(OStringToOUString(aCurLine, m_aFileEncoding)));
+ continue;
+ }
+ else if( ! bLanguageEncoding &&
+ aCurLine.matchIgnoreAsciiCase("*languageencoding") )
+ {
+ bLanguageEncoding = true; // generally only the first one counts
+ OString aLower = aCurLine.toAsciiLowerCase();
+ if( aLower.indexOf("isolatin1", 17 ) != -1 ||
+ aLower.indexOf("windowsansi", 17 ) != -1 )
+ m_aFileEncoding = RTL_TEXTENCODING_MS_1252;
+ else if( aLower.indexOf("isolatin2", 17 ) != -1 )
+ m_aFileEncoding = RTL_TEXTENCODING_ISO_8859_2;
+ else if( aLower.indexOf("isolatin5", 17 ) != -1 )
+ m_aFileEncoding = RTL_TEXTENCODING_ISO_8859_5;
+ else if( aLower.indexOf("jis83-rksj", 17 ) != -1 )
+ m_aFileEncoding = RTL_TEXTENCODING_SHIFT_JIS;
+ else if( aLower.indexOf("macstandard", 17 ) != -1 )
+ m_aFileEncoding = RTL_TEXTENCODING_APPLE_ROMAN;
+ else if( aLower.indexOf("utf-8", 17 ) != -1 )
+ m_aFileEncoding = RTL_TEXTENCODING_UTF8;
+ }
+ }
+ aLines.push_back( aCurLine );
+ }
+ }
+ aStream.Close();
+
+ // now get the Values
+ parse( aLines );
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "acquired " << m_aKeys.size()
+ << " Keys from PPD " << m_aFile << ":");
+ for (auto const& key : m_aKeys)
+ {
+ const PPDKey* pKey = key.second.get();
+ char const* pSetupType = "<unknown>";
+ switch( pKey->m_eSetupType )
+ {
+ case PPDKey::SetupType::ExitServer: pSetupType = "ExitServer";break;
+ case PPDKey::SetupType::Prolog: pSetupType = "Prolog";break;
+ case PPDKey::SetupType::DocumentSetup: pSetupType = "DocumentSetup";break;
+ case PPDKey::SetupType::PageSetup: pSetupType = "PageSetup";break;
+ case PPDKey::SetupType::JCLSetup: pSetupType = "JCLSetup";break;
+ case PPDKey::SetupType::AnySetup: pSetupType = "AnySetup";break;
+ default: break;
+ }
+ SAL_INFO("vcl.unx.print", "\t\"" << pKey->getKey() << "\" ("
+ << pKey->countValues() << "values) OrderDependency: "
+ << pKey->m_nOrderDependency << pSetupType );
+ for( int j = 0; j < pKey->countValues(); j++ )
+ {
+ const PPDValue* pValue = pKey->getValue( j );
+ char const* pVType = "<unknown>";
+ switch( pValue->m_eType )
+ {
+ case eInvocation: pVType = "invocation";break;
+ case eQuoted: pVType = "quoted";break;
+ case eString: pVType = "string";break;
+ case eSymbol: pVType = "symbol";break;
+ case eNo: pVType = "no";break;
+ default: break;
+ }
+ SAL_INFO("vcl.unx.print", "\t\t"
+ << (pValue == pKey->m_pDefaultValue ? "(Default:) " : "")
+ << "option: \"" << pValue->m_aOption
+ << "\", value: type " << pVType << " \""
+ << pValue->m_aValue << "\"");
+ }
+ }
+ SAL_INFO("vcl.unx.print",
+ "constraints: (" << m_aConstraints.size() << " found)");
+ for (auto const& constraint : m_aConstraints)
+ {
+ SAL_INFO("vcl.unx.print", "*\"" << constraint.m_pKey1->getKey() << "\" \""
+ << (constraint.m_pOption1 ? constraint.m_pOption1->m_aOption : "<nil>")
+ << "\" *\"" << constraint.m_pKey2->getKey() << "\" \""
+ << (constraint.m_pOption2 ? constraint.m_pOption2->m_aOption : "<nil>")
+ << "\"");
+ }
+#endif
+
+ // fill in shortcuts
+ const PPDKey* pKey;
+
+ m_pImageableAreas = getKey( "ImageableArea" );
+ const PPDValue * pDefaultImageableArea = nullptr;
+ if( m_pImageableAreas )
+ pDefaultImageableArea = m_pImageableAreas->getDefaultValue();
+ if (m_pImageableAreas == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no ImageableArea in " << m_aFile);
+ }
+ if (pDefaultImageableArea == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no DefaultImageableArea in " << m_aFile);
+ }
+
+ m_pPaperDimensions = getKey( "PaperDimension" );
+ if( m_pPaperDimensions )
+ m_pDefaultPaperDimension = m_pPaperDimensions->getDefaultValue();
+ if (m_pPaperDimensions == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no PaperDimensions in " << m_aFile);
+ }
+ if (m_pDefaultPaperDimension == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no DefaultPaperDimensions in " << m_aFile);
+ }
+
+ auto pResolutions = getKey( "Resolution" );
+ if( pResolutions )
+ m_pDefaultResolution = pResolutions->getDefaultValue();
+ if (pResolutions == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no Resolution in " << m_aFile);
+ }
+ SAL_INFO_IF(!m_pDefaultResolution, "vcl.unx.print", "no DefaultResolution in " + m_aFile);
+
+ auto pInputSlots = getKey( "InputSlot" );
+ if( pInputSlots )
+ m_pDefaultInputSlot = pInputSlots->getDefaultValue();
+ SAL_INFO_IF(!pInputSlots, "vcl.unx.print", "no InputSlot in " << m_aFile);
+ SAL_INFO_IF(!m_pDefaultInputSlot, "vcl.unx.print", "no DefaultInputSlot in " << m_aFile);
+
+ auto pFontList = getKey( "Font" );
+ if (pFontList == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no Font in " << m_aFile);
+ }
+
+ // fill in direct values
+ if ((pKey = getKey("ColorDevice")))
+ {
+ if (const PPDValue* pValue = pKey->getValue(0))
+ m_bColorDevice = pValue->m_aValue.startsWithIgnoreAsciiCase("true");
+ }
+
+ if ((pKey = getKey("LanguageLevel")))
+ {
+ if (const PPDValue* pValue = pKey->getValue(0))
+ m_nLanguageLevel = pValue->m_aValue.toInt32();
+ }
+ if ((pKey = getKey("TTRasterizer")))
+ {
+ if (const PPDValue* pValue = pKey->getValue(0))
+ m_bType42Capable = pValue->m_aValue.equalsIgnoreAsciiCase( "Type42" );
+ }
+}
+
+PPDParser::~PPDParser()
+{
+ m_pTranslator.reset();
+}
+
+void PPDParser::insertKey( std::unique_ptr<PPDKey> pKey )
+{
+ m_aOrderedKeys.push_back( pKey.get() );
+ m_aKeys[ pKey->getKey() ] = std::move(pKey);
+}
+
+const PPDKey* PPDParser::getKey( int n ) const
+{
+ return (n >= 0 && o3tl::make_unsigned(n) < m_aOrderedKeys.size()) ? m_aOrderedKeys[n] : nullptr;
+}
+
+const PPDKey* PPDParser::getKey( const OUString& rKey ) const
+{
+ PPDParser::hash_type::const_iterator it = m_aKeys.find( rKey );
+ return it != m_aKeys.end() ? it->second.get() : nullptr;
+}
+
+bool PPDParser::hasKey( const PPDKey* pKey ) const
+{
+ return pKey && ( m_aKeys.find( pKey->getKey() ) != m_aKeys.end() );
+}
+
+static sal_uInt8 getNibble( char cChar )
+{
+ sal_uInt8 nRet = 0;
+ if( cChar >= '0' && cChar <= '9' )
+ nRet = sal_uInt8( cChar - '0' );
+ else if( cChar >= 'A' && cChar <= 'F' )
+ nRet = 10 + sal_uInt8( cChar - 'A' );
+ else if( cChar >= 'a' && cChar <= 'f' )
+ nRet = 10 + sal_uInt8( cChar - 'a' );
+ return nRet;
+}
+
+OUString PPDParser::handleTranslation(const OString& i_rString, bool bIsGlobalized)
+{
+ sal_Int32 nOrigLen = i_rString.getLength();
+ OStringBuffer aTrans( nOrigLen );
+ const char* pStr = i_rString.getStr();
+ const char* pEnd = pStr + nOrigLen;
+ while( pStr < pEnd )
+ {
+ if( *pStr == '<' )
+ {
+ pStr++;
+ char cChar;
+ while( *pStr != '>' && pStr < pEnd-1 )
+ {
+ cChar = getNibble( *pStr++ ) << 4;
+ cChar |= getNibble( *pStr++ );
+ aTrans.append( cChar );
+ }
+ pStr++;
+ }
+ else
+ aTrans.append( *pStr++ );
+ }
+ return OStringToOUString( aTrans, bIsGlobalized ? RTL_TEXTENCODING_UTF8 : m_aFileEncoding );
+}
+
+namespace
+{
+ bool oddDoubleQuoteCount(OStringBuffer &rBuffer)
+ {
+ bool bHasOddCount = false;
+ for (sal_Int32 i = 0; i < rBuffer.getLength(); ++i)
+ {
+ if (rBuffer[i] == '"')
+ bHasOddCount = !bHasOddCount;
+ }
+ return bHasOddCount;
+ }
+}
+
+void PPDParser::parse( ::std::vector< OString >& rLines )
+{
+ // Name for PPD group into which all options are put for which the PPD
+ // does not explicitly define a group.
+ // This is similar to how CUPS handles it,
+ // s. Sweet, Michael R. (2001): Common UNIX Printing System, p. 251:
+ // "Each option in turn is associated with a group stored in the
+ // ppd_group_t structure. Groups can be specified in the PPD file; if an
+ // option is not associated with a group, it is put in a "General" or
+ // "Extra" group depending on the option.
+ static constexpr OStringLiteral aDefaultPPDGroupName("General");
+
+ std::vector< OString >::iterator line = rLines.begin();
+ PPDParser::hash_type::const_iterator keyit;
+
+ // name of the PPD group that is currently being processed
+ OString aCurrentGroup = aDefaultPPDGroupName;
+
+ while( line != rLines.end() )
+ {
+ OString aCurrentLine( *line );
+ ++line;
+
+ SAL_INFO("vcl.unx.print", "Parse line '" << aCurrentLine << "'");
+
+ if (aCurrentLine.getLength() < 2 || aCurrentLine[0] != '*')
+ continue;
+ if( aCurrentLine[1] == '%' )
+ continue;
+
+ OString aKey = GetCommandLineToken( 0, aCurrentLine.getToken(0, ':') );
+ sal_Int32 nPos = aKey.indexOf('/');
+ if (nPos != -1)
+ aKey = aKey.copy(0, nPos);
+ if(!aKey.isEmpty())
+ {
+ aKey = aKey.copy(1); // remove the '*'
+ }
+ if(aKey.isEmpty())
+ {
+ continue;
+ }
+
+ if (aKey == "CloseGroup")
+ {
+ aCurrentGroup = aDefaultPPDGroupName;
+ continue;
+ }
+ if (aKey == "OpenGroup")
+ {
+ OString aGroupName = aCurrentLine;
+ sal_Int32 nPosition = aGroupName.indexOf('/');
+ if (nPosition != -1)
+ {
+ aGroupName = aGroupName.copy(0, nPosition);
+ }
+
+ aCurrentGroup = GetCommandLineToken(1, aGroupName);
+ continue;
+ }
+ if ((aKey == "CloseUI") ||
+ (aKey == "JCLCloseUI") ||
+ (aKey == "End") ||
+ (aKey == "JCLEnd") ||
+ (aKey == "OpenSubGroup") ||
+ (aKey == "CloseSubGroup"))
+ {
+ continue;
+ }
+
+ if ((aKey == "OpenUI") || (aKey == "JCLOpenUI"))
+ {
+ parseOpenUI( aCurrentLine, aCurrentGroup);
+ continue;
+ }
+ else if (aKey == "OrderDependency")
+ {
+ parseOrderDependency( aCurrentLine );
+ continue;
+ }
+ else if (aKey == "UIConstraints" ||
+ aKey == "NonUIConstraints")
+ {
+ continue; // parsed in pass 2
+ }
+ else if( aKey == "CustomPageSize" ) // currently not handled
+ continue;
+ else if (aKey.startsWith("Custom", &aKey) )
+ {
+ //fdo#43049 very basic support for Custom entries, we ignore the
+ //validation params and types
+ OUString aUniKey(OStringToOUString(aKey, RTL_TEXTENCODING_MS_1252));
+ keyit = m_aKeys.find( aUniKey );
+ if(keyit != m_aKeys.end())
+ {
+ PPDKey* pKey = keyit->second.get();
+ pKey->insertValue("Custom", eInvocation, true);
+ }
+ continue;
+ }
+
+ // default values are parsed in pass 2
+ if (aKey.startsWith("Default"))
+ continue;
+
+ bool bQuery = false;
+ if (aKey[0] == '?')
+ {
+ aKey = aKey.copy(1);
+ bQuery = true;
+ }
+
+ OUString aUniKey(OStringToOUString(aKey, RTL_TEXTENCODING_MS_1252));
+ // handle CUPS extension for globalized PPDs
+ /* FIXME-BCP47: really only ISO 639-1 two character language codes?
+ * goodnight... */
+ bool bIsGlobalizedLine = false;
+ css::lang::Locale aTransLocale;
+ if( ( aUniKey.getLength() > 3 && aUniKey[ 2 ] == '.' ) ||
+ ( aUniKey.getLength() > 5 && aUniKey[ 2 ] == '_' && aUniKey[ 5 ] == '.' ) )
+ {
+ if( aUniKey[ 2 ] == '.' )
+ {
+ aTransLocale.Language = aUniKey.copy( 0, 2 );
+ aUniKey = aUniKey.copy( 3 );
+ }
+ else
+ {
+ aTransLocale.Language = aUniKey.copy( 0, 2 );
+ aTransLocale.Country = aUniKey.copy( 3, 2 );
+ aUniKey = aUniKey.copy( 6 );
+ }
+ bIsGlobalizedLine = true;
+ }
+
+ OUString aOption;
+ nPos = aCurrentLine.indexOf(':');
+ if( nPos != -1 )
+ {
+ aOption = OStringToOUString(
+ aCurrentLine.subView( 1, nPos-1 ), RTL_TEXTENCODING_MS_1252 );
+ aOption = GetCommandLineToken( 1, aOption );
+ sal_Int32 nTransPos = aOption.indexOf( '/' );
+ if( nTransPos != -1 )
+ aOption = aOption.copy(0, nTransPos);
+ }
+
+ PPDValueType eType = eNo;
+ OUString aValue;
+ OUString aOptionTranslation;
+ OUString aValueTranslation;
+ if( nPos != -1 )
+ {
+ // found a colon, there may be an option
+ OString aLine = aCurrentLine.copy( 1, nPos-1 );
+ aLine = WhitespaceToSpace( aLine );
+ sal_Int32 nTransPos = aLine.indexOf('/');
+ if (nTransPos != -1)
+ aOptionTranslation = handleTranslation( aLine.copy(nTransPos+1), bIsGlobalizedLine );
+
+ // read in more lines if necessary for multiline values
+ aLine = aCurrentLine.copy( nPos+1 );
+ if (!aLine.isEmpty())
+ {
+ OStringBuffer aBuffer(aLine);
+ while (line != rLines.end() && oddDoubleQuoteCount(aBuffer))
+ {
+ // copy the newlines also
+ aBuffer.append('\n');
+ aBuffer.append(*line);
+ ++line;
+ }
+ aLine = aBuffer.makeStringAndClear();
+ }
+ aLine = WhitespaceToSpace( aLine );
+
+ // #i100644# handle a missing value (actually a broken PPD)
+ if( aLine.isEmpty() )
+ {
+ if( !aOption.isEmpty() &&
+ !aUniKey.startsWith( "JCL" ) )
+ eType = eInvocation;
+ else
+ eType = eQuoted;
+ }
+ // check for invocation or quoted value
+ else if(aLine[0] == '"')
+ {
+ aLine = aLine.copy(1);
+ nTransPos = aLine.indexOf('"');
+ if (nTransPos == -1)
+ nTransPos = aLine.getLength();
+ aValue = OStringToOUString(aLine.subView(0, nTransPos), RTL_TEXTENCODING_MS_1252);
+ // after the second doublequote can follow a / and a translation
+ if (nTransPos < aLine.getLength() - 2)
+ {
+ aValueTranslation = handleTranslation( aLine.copy( nTransPos+2 ), bIsGlobalizedLine );
+ }
+ // check for quoted value
+ if( !aOption.isEmpty() &&
+ !aUniKey.startsWith( "JCL" ) )
+ eType = eInvocation;
+ else
+ eType = eQuoted;
+ }
+ // check for symbol value
+ else if(aLine[0] == '^')
+ {
+ aLine = aLine.copy(1);
+ aValue = OStringToOUString(aLine, RTL_TEXTENCODING_MS_1252);
+ eType = eSymbol;
+ }
+ else
+ {
+ // must be a string value then
+ // strictly this is false because string values
+ // can contain any whitespace which is reduced
+ // to one space by now
+ // who cares ...
+ nTransPos = aLine.indexOf('/');
+ if (nTransPos == -1)
+ nTransPos = aLine.getLength();
+ aValue = OStringToOUString(aLine.subView(0, nTransPos), RTL_TEXTENCODING_MS_1252);
+ if (nTransPos+1 < aLine.getLength())
+ aValueTranslation = handleTranslation( aLine.copy( nTransPos+1 ), bIsGlobalizedLine );
+ eType = eString;
+ }
+ }
+
+ // handle globalized PPD entries
+ if( bIsGlobalizedLine )
+ {
+ // handle main key translations of form:
+ // *ll_CC.Translation MainKeyword/translated text: ""
+ if( aUniKey == "Translation" )
+ {
+ m_pTranslator->insertKey( aOption, aOptionTranslation, aTransLocale );
+ }
+ // handle options translations of for:
+ // *ll_CC.MainKeyword OptionKeyword/translated text: ""
+ else
+ {
+ m_pTranslator->insertOption( aUniKey, aOption, aOptionTranslation, aTransLocale );
+ }
+ continue;
+ }
+
+ PPDKey* pKey = nullptr;
+ keyit = m_aKeys.find( aUniKey );
+ if( keyit == m_aKeys.end() )
+ {
+ pKey = new PPDKey( aUniKey );
+ insertKey( std::unique_ptr<PPDKey>(pKey) );
+ }
+ else
+ pKey = keyit->second.get();
+
+ if( eType == eNo && bQuery )
+ continue;
+
+ PPDValue* pValue = pKey->insertValue( aOption, eType );
+ if( ! pValue )
+ continue;
+ pValue->m_aValue = aValue;
+
+ if( !aOptionTranslation.isEmpty() )
+ m_pTranslator->insertOption( aUniKey, aOption, aOptionTranslation, aTransLocale );
+ if( !aValueTranslation.isEmpty() )
+ m_pTranslator->insertValue( aUniKey, aOption, aValue, aValueTranslation, aTransLocale );
+
+ // eventually update query and remove from option list
+ if( bQuery && !pKey->m_bQueryValue )
+ {
+ pKey->m_bQueryValue = true;
+ pKey->eraseValue( pValue->m_aOption );
+ }
+ }
+
+ // second pass: fill in defaults
+ for( const auto& aLine : rLines )
+ {
+ if (aLine.startsWith("*Default"))
+ {
+ SAL_INFO("vcl.unx.print", "Found a default: '" << aLine << "'");
+ OUString aKey(OStringToOUString(aLine.subView(8), RTL_TEXTENCODING_MS_1252));
+ sal_Int32 nPos = aKey.indexOf( ':' );
+ if( nPos != -1 )
+ {
+ aKey = aKey.copy(0, nPos);
+ OUString aOption(OStringToOUString(
+ WhitespaceToSpace(aLine.subView(nPos+9)),
+ RTL_TEXTENCODING_MS_1252));
+ keyit = m_aKeys.find( aKey );
+ if( keyit != m_aKeys.end() )
+ {
+ PPDKey* pKey = keyit->second.get();
+ const PPDValue* pDefValue = pKey->getValue( aOption );
+ if( pKey->m_pDefaultValue == nullptr )
+ pKey->m_pDefaultValue = pDefValue;
+ }
+ else
+ {
+ // some PPDs contain defaults for keys that
+ // do not exist otherwise
+ // (example: DefaultResolution)
+ // so invent that key here and have a default value
+ std::unique_ptr<PPDKey> pKey(new PPDKey( aKey ));
+ pKey->insertValue( aOption, eInvocation /*or what ?*/ );
+ pKey->m_pDefaultValue = pKey->getValue( aOption );
+ insertKey( std::move(pKey) );
+ }
+ }
+ }
+ else if (aLine.startsWith("*UIConstraints") ||
+ aLine.startsWith("*NonUIConstraints"))
+ {
+ parseConstraint( aLine );
+ }
+ }
+}
+
+void PPDParser::parseOpenUI(const OString& rLine, std::string_view rPPDGroup)
+{
+ OUString aTranslation;
+ OString aKey = rLine;
+
+ sal_Int32 nPos = aKey.indexOf(':');
+ if( nPos != -1 )
+ aKey = aKey.copy(0, nPos);
+ nPos = aKey.indexOf('/');
+ if( nPos != -1 )
+ {
+ aTranslation = handleTranslation( aKey.copy( nPos + 1 ), false );
+ aKey = aKey.copy(0, nPos);
+ }
+ aKey = GetCommandLineToken( 1, aKey );
+ aKey = aKey.copy(1);
+
+ OUString aUniKey(OStringToOUString(aKey, RTL_TEXTENCODING_MS_1252));
+ PPDParser::hash_type::const_iterator keyit = m_aKeys.find( aUniKey );
+ PPDKey* pKey;
+ if( keyit == m_aKeys.end() )
+ {
+ pKey = new PPDKey( aUniKey );
+ insertKey( std::unique_ptr<PPDKey>(pKey) );
+ }
+ else
+ pKey = keyit->second.get();
+
+ pKey->m_bUIOption = true;
+ m_pTranslator->insertKey( pKey->getKey(), aTranslation );
+
+ pKey->m_aGroup = OStringToOUString(rPPDGroup, RTL_TEXTENCODING_MS_1252);
+}
+
+void PPDParser::parseOrderDependency(const OString& rLine)
+{
+ OString aLine(rLine);
+ sal_Int32 nPos = aLine.indexOf(':');
+ if( nPos != -1 )
+ aLine = aLine.copy( nPos+1 );
+
+ sal_Int32 nOrder = GetCommandLineToken( 0, aLine ).toInt32();
+ OString aSetup = GetCommandLineToken( 1, aLine );
+ OUString aKey(OStringToOUString(GetCommandLineToken(2, aLine), RTL_TEXTENCODING_MS_1252));
+ if( aKey[ 0 ] != '*' )
+ return; // invalid order dependency
+ aKey = aKey.replaceAt( 0, 1, u"" );
+
+ PPDKey* pKey;
+ PPDParser::hash_type::const_iterator keyit = m_aKeys.find( aKey );
+ if( keyit == m_aKeys.end() )
+ {
+ pKey = new PPDKey( aKey );
+ insertKey( std::unique_ptr<PPDKey>(pKey) );
+ }
+ else
+ pKey = keyit->second.get();
+
+ pKey->m_nOrderDependency = nOrder;
+ if( aSetup == "ExitServer" )
+ pKey->m_eSetupType = PPDKey::SetupType::ExitServer;
+ else if( aSetup == "Prolog" )
+ pKey->m_eSetupType = PPDKey::SetupType::Prolog;
+ else if( aSetup == "DocumentSetup" )
+ pKey->m_eSetupType = PPDKey::SetupType::DocumentSetup;
+ else if( aSetup == "PageSetup" )
+ pKey->m_eSetupType = PPDKey::SetupType::PageSetup;
+ else if( aSetup == "JCLSetup" )
+ pKey->m_eSetupType = PPDKey::SetupType::JCLSetup;
+ else
+ pKey->m_eSetupType = PPDKey::SetupType::AnySetup;
+}
+
+void PPDParser::parseConstraint( const OString& rLine )
+{
+ bool bFailed = false;
+
+ OUString aLine(OStringToOUString(rLine, RTL_TEXTENCODING_MS_1252));
+ sal_Int32 nIdx = rLine.indexOf(':');
+ if (nIdx != -1)
+ aLine = aLine.replaceAt(0, nIdx + 1, u"");
+ PPDConstraint aConstraint;
+ int nTokens = GetCommandLineTokenCount( aLine );
+ for( int i = 0; i < nTokens; i++ )
+ {
+ OUString aToken = GetCommandLineToken( i, aLine );
+ if( !aToken.isEmpty() && aToken[ 0 ] == '*' )
+ {
+ aToken = aToken.replaceAt( 0, 1, u"" );
+ if( aConstraint.m_pKey1 )
+ aConstraint.m_pKey2 = getKey( aToken );
+ else
+ aConstraint.m_pKey1 = getKey( aToken );
+ }
+ else
+ {
+ if( aConstraint.m_pKey2 )
+ {
+ if( ! ( aConstraint.m_pOption2 = aConstraint.m_pKey2->getValue( aToken ) ) )
+ bFailed = true;
+ }
+ else if( aConstraint.m_pKey1 )
+ {
+ if( ! ( aConstraint.m_pOption1 = aConstraint.m_pKey1->getValue( aToken ) ) )
+ bFailed = true;
+ }
+ else
+ // constraint for nonexistent keys; this happens
+ // e.g. in HP4PLUS3
+ bFailed = true;
+ }
+ }
+ // there must be two keywords
+ if( ! aConstraint.m_pKey1 || ! aConstraint.m_pKey2 || bFailed )
+ {
+ SAL_INFO("vcl.unx.print",
+ "Warning: constraint \"" << rLine << "\" is invalid");
+ }
+ else
+ m_aConstraints.push_back( aConstraint );
+}
+
+OUString PPDParser::getDefaultPaperDimension() const
+{
+ if( m_pDefaultPaperDimension )
+ return m_pDefaultPaperDimension->m_aOption;
+
+ return OUString();
+}
+
+bool PPDParser::getMargins(
+ std::u16string_view rPaperName,
+ int& rLeft, int& rRight,
+ int& rUpper, int& rLower ) const
+{
+ if( ! m_pImageableAreas || ! m_pPaperDimensions )
+ return false;
+
+ int nPDim=-1, nImArea=-1, i;
+ for( i = 0; i < m_pImageableAreas->countValues(); i++ )
+ if( rPaperName == m_pImageableAreas->getValue( i )->m_aOption )
+ nImArea = i;
+ for( i = 0; i < m_pPaperDimensions->countValues(); i++ )
+ if( rPaperName == m_pPaperDimensions->getValue( i )->m_aOption )
+ nPDim = i;
+ if( nPDim == -1 || nImArea == -1 )
+ return false;
+
+ double ImLLx, ImLLy, ImURx, ImURy;
+ double PDWidth, PDHeight;
+ OUString aArea = m_pImageableAreas->getValue( nImArea )->m_aValue;
+ ImLLx = StringToDouble( GetCommandLineToken( 0, aArea ) );
+ ImLLy = StringToDouble( GetCommandLineToken( 1, aArea ) );
+ ImURx = StringToDouble( GetCommandLineToken( 2, aArea ) );
+ ImURy = StringToDouble( GetCommandLineToken( 3, aArea ) );
+ aArea = m_pPaperDimensions->getValue( nPDim )->m_aValue;
+ PDWidth = StringToDouble( GetCommandLineToken( 0, aArea ) );
+ PDHeight = StringToDouble( GetCommandLineToken( 1, aArea ) );
+ rLeft = static_cast<int>(ImLLx + 0.5);
+ rLower = static_cast<int>(ImLLy + 0.5);
+ rUpper = static_cast<int>(PDHeight - ImURy + 0.5);
+ rRight = static_cast<int>(PDWidth - ImURx + 0.5);
+
+ return true;
+}
+
+bool PPDParser::getPaperDimension(
+ std::u16string_view rPaperName,
+ int& rWidth, int& rHeight ) const
+{
+ if( ! m_pPaperDimensions )
+ return false;
+
+ int nPDim=-1;
+ for( int i = 0; i < m_pPaperDimensions->countValues(); i++ )
+ if( rPaperName == m_pPaperDimensions->getValue( i )->m_aOption )
+ nPDim = i;
+ if( nPDim == -1 )
+ return false;
+
+ double PDWidth, PDHeight;
+ OUString aArea = m_pPaperDimensions->getValue( nPDim )->m_aValue;
+ PDWidth = StringToDouble( GetCommandLineToken( 0, aArea ) );
+ PDHeight = StringToDouble( GetCommandLineToken( 1, aArea ) );
+ rHeight = static_cast<int>(PDHeight + 0.5);
+ rWidth = static_cast<int>(PDWidth + 0.5);
+
+ return true;
+}
+
+OUString PPDParser::matchPaper( int nWidth, int nHeight ) const
+{
+ if( ! m_pPaperDimensions )
+ return OUString();
+
+ int nPDim = -1;
+ double fSort = 2e36, fNewSort;
+
+ for( int i = 0; i < m_pPaperDimensions->countValues(); i++ )
+ {
+ OUString aArea = m_pPaperDimensions->getValue( i )->m_aValue;
+ double PDWidth = StringToDouble( GetCommandLineToken( 0, aArea ) );
+ double PDHeight = StringToDouble( GetCommandLineToken( 1, aArea ) );
+ PDWidth /= static_cast<double>(nWidth);
+ PDHeight /= static_cast<double>(nHeight);
+ if( PDWidth >= 0.9 && PDWidth <= 1.1 &&
+ PDHeight >= 0.9 && PDHeight <= 1.1 )
+ {
+ fNewSort =
+ (1.0-PDWidth)*(1.0-PDWidth) + (1.0-PDHeight)*(1.0-PDHeight);
+ if( fNewSort == 0.0 ) // perfect match
+ return m_pPaperDimensions->getValue( i )->m_aOption;
+
+ if( fNewSort < fSort )
+ {
+ fSort = fNewSort;
+ nPDim = i;
+ }
+ }
+ }
+
+ static bool bDontSwap = false;
+ if( nPDim == -1 && ! bDontSwap )
+ {
+ // swap portrait/landscape and try again
+ bDontSwap = true;
+ OUString rRet = matchPaper( nHeight, nWidth );
+ bDontSwap = false;
+ return rRet;
+ }
+
+ return nPDim != -1 ? m_pPaperDimensions->getValue( nPDim )->m_aOption : OUString();
+}
+
+OUString PPDParser::getDefaultInputSlot() const
+{
+ if( m_pDefaultInputSlot )
+ return m_pDefaultInputSlot->m_aValue;
+ return OUString();
+}
+
+void PPDParser::getResolutionFromString(std::u16string_view rString,
+ int& rXRes, int& rYRes )
+{
+ rXRes = rYRes = 300;
+
+ const size_t nDPIPos {rString.find( u"dpi" )};
+ if( nDPIPos != std::u16string_view::npos )
+ {
+ const size_t nPos {rString.find( 'x' )};
+ if( nPos != std::u16string_view::npos )
+ {
+ rXRes = o3tl::toInt32(rString.substr( 0, nPos ));
+ rYRes = o3tl::toInt32(rString.substr(nPos+1, nDPIPos - nPos - 1));
+ }
+ else
+ rXRes = rYRes = o3tl::toInt32(rString.substr( 0, nDPIPos ));
+ }
+}
+
+void PPDParser::getDefaultResolution( int& rXRes, int& rYRes ) const
+{
+ if( m_pDefaultResolution )
+ {
+ getResolutionFromString( m_pDefaultResolution->m_aValue, rXRes, rYRes );
+ return;
+ }
+
+ rXRes = 300;
+ rYRes = 300;
+}
+
+OUString PPDParser::translateKey( const OUString& i_rKey ) const
+{
+ OUString aResult( m_pTranslator->translateKey( i_rKey ) );
+ if( aResult.isEmpty() )
+ aResult = i_rKey;
+ return aResult;
+}
+
+OUString PPDParser::translateOption( const OUString& i_rKey,
+ const OUString& i_rOption ) const
+{
+ OUString aResult( m_pTranslator->translateOption( i_rKey, i_rOption ) );
+ if( aResult.isEmpty() )
+ aResult = i_rOption;
+ return aResult;
+}
+
+/*
+ * PPDKey
+ */
+
+PPDKey::PPDKey( const OUString& rKey ) :
+ m_aKey( rKey ),
+ m_pDefaultValue( nullptr ),
+ m_bQueryValue( false ),
+ m_bUIOption( false ),
+ m_nOrderDependency( 100 ),
+ m_eSetupType( SetupType::AnySetup )
+{
+}
+
+PPDKey::~PPDKey()
+{
+}
+
+const PPDValue* PPDKey::getValue( int n ) const
+{
+ return (n >= 0 && o3tl::make_unsigned(n) < m_aOrderedValues.size()) ? m_aOrderedValues[n] : nullptr;
+}
+
+const PPDValue* PPDKey::getValue( const OUString& rOption ) const
+{
+ PPDKey::hash_type::const_iterator it = m_aValues.find( rOption );
+ return it != m_aValues.end() ? &it->second : nullptr;
+}
+
+const PPDValue* PPDKey::getValueCaseInsensitive( const OUString& rOption ) const
+{
+ const PPDValue* pValue = getValue( rOption );
+ if( ! pValue )
+ {
+ for( size_t n = 0; n < m_aOrderedValues.size() && ! pValue; n++ )
+ if( m_aOrderedValues[n]->m_aOption.equalsIgnoreAsciiCase( rOption ) )
+ pValue = m_aOrderedValues[n];
+ }
+
+ return pValue;
+}
+
+void PPDKey::eraseValue( const OUString& rOption )
+{
+ PPDKey::hash_type::iterator it = m_aValues.find( rOption );
+ if( it == m_aValues.end() )
+ return;
+
+ auto vit = std::find(m_aOrderedValues.begin(), m_aOrderedValues.end(), &(it->second ));
+ if( vit != m_aOrderedValues.end() )
+ m_aOrderedValues.erase( vit );
+
+ m_aValues.erase( it );
+}
+
+PPDValue* PPDKey::insertValue(const OUString& rOption, PPDValueType eType, bool bCustomOption)
+{
+ if( m_aValues.find( rOption ) != m_aValues.end() )
+ return nullptr;
+
+ PPDValue aValue;
+ aValue.m_aOption = rOption;
+ aValue.m_bCustomOption = bCustomOption;
+ aValue.m_eType = eType;
+ m_aValues[ rOption ] = aValue;
+ PPDValue* pValue = &m_aValues[rOption];
+ m_aOrderedValues.push_back( pValue );
+ return pValue;
+}
+
+/*
+ * PPDContext
+ */
+
+PPDContext::PPDContext() :
+ m_pParser( nullptr )
+{
+}
+
+PPDContext& PPDContext::operator=( PPDContext&& rCopy )
+{
+ std::swap(m_pParser, rCopy.m_pParser);
+ std::swap(m_aCurrentValues, rCopy.m_aCurrentValues);
+ return *this;
+}
+
+const PPDKey* PPDContext::getModifiedKey( std::size_t n ) const
+{
+ if( m_aCurrentValues.size() <= n )
+ return nullptr;
+
+ hash_type::const_iterator it = m_aCurrentValues.begin();
+ std::advance(it, n);
+ return it->first;
+}
+
+void PPDContext::setParser( const PPDParser* pParser )
+{
+ if( pParser != m_pParser )
+ {
+ m_aCurrentValues.clear();
+ m_pParser = pParser;
+ }
+}
+
+const PPDValue* PPDContext::getValue( const PPDKey* pKey ) const
+{
+ if( ! m_pParser )
+ return nullptr;
+
+ hash_type::const_iterator it = m_aCurrentValues.find( pKey );
+ if( it != m_aCurrentValues.end() )
+ return it->second;
+
+ if( ! m_pParser->hasKey( pKey ) )
+ return nullptr;
+
+ const PPDValue* pValue = pKey->getDefaultValue();
+ if( ! pValue )
+ pValue = pKey->getValue( 0 );
+
+ return pValue;
+}
+
+const PPDValue* PPDContext::setValue( const PPDKey* pKey, const PPDValue* pValue, bool bDontCareForConstraints )
+{
+ if( ! m_pParser || ! pKey )
+ return nullptr;
+
+ // pValue can be NULL - it means ignore this option
+
+ if( ! m_pParser->hasKey( pKey ) )
+ return nullptr;
+
+ // check constraints
+ if( pValue )
+ {
+ if( bDontCareForConstraints )
+ {
+ m_aCurrentValues[ pKey ] = pValue;
+ }
+ else if( checkConstraints( pKey, pValue, true ) )
+ {
+ m_aCurrentValues[ pKey ] = pValue;
+
+ // after setting this value, check all constraints !
+ hash_type::iterator it = m_aCurrentValues.begin();
+ while( it != m_aCurrentValues.end() )
+ {
+ if( it->first != pKey &&
+ ! checkConstraints( it->first, it->second, false ) )
+ {
+ SAL_INFO("vcl.unx.print", "PPDContext::setValue: option "
+ << it->first->getKey()
+ << " (" << it->second->m_aOption
+ << ") is constrained after setting "
+ << pKey->getKey()
+ << " to " << pValue->m_aOption);
+ resetValue( it->first, true );
+ it = m_aCurrentValues.begin();
+ }
+ else
+ ++it;
+ }
+ }
+ }
+ else
+ m_aCurrentValues[ pKey ] = nullptr;
+
+ return pValue;
+}
+
+bool PPDContext::checkConstraints( const PPDKey* pKey, const PPDValue* pValue )
+{
+ if( ! m_pParser || ! pKey || ! pValue )
+ return false;
+
+ // ensure that this key is already in the list if it exists at all
+ if( m_aCurrentValues.find( pKey ) != m_aCurrentValues.end() )
+ return checkConstraints( pKey, pValue, false );
+
+ // it is not in the list, insert it temporarily
+ bool bRet = false;
+ if( m_pParser->hasKey( pKey ) )
+ {
+ const PPDValue* pDefValue = pKey->getDefaultValue();
+ m_aCurrentValues[ pKey ] = pDefValue;
+ bRet = checkConstraints( pKey, pValue, false );
+ m_aCurrentValues.erase( pKey );
+ }
+
+ return bRet;
+}
+
+bool PPDContext::resetValue( const PPDKey* pKey, bool bDefaultable )
+{
+ if( ! pKey || ! m_pParser || ! m_pParser->hasKey( pKey ) )
+ return false;
+
+ const PPDValue* pResetValue = pKey->getValue( "None" );
+ if( ! pResetValue )
+ pResetValue = pKey->getValue( "False" );
+ if( ! pResetValue && bDefaultable )
+ pResetValue = pKey->getDefaultValue();
+
+ bool bRet = pResetValue && ( setValue( pKey, pResetValue ) == pResetValue );
+
+ return bRet;
+}
+
+bool PPDContext::checkConstraints( const PPDKey* pKey, const PPDValue* pNewValue, bool bDoReset )
+{
+ if( ! pNewValue )
+ return true;
+
+ // sanity checks
+ if( ! m_pParser )
+ return false;
+
+ if( pKey->getValue( pNewValue->m_aOption ) != pNewValue )
+ return false;
+
+ // None / False and the default can always be set, but be careful !
+ // setting them might influence constrained values
+ if( pNewValue->m_aOption == "None" || pNewValue->m_aOption == "False" ||
+ pNewValue == pKey->getDefaultValue() )
+ return true;
+
+ const ::std::vector< PPDParser::PPDConstraint >& rConstraints( m_pParser->getConstraints() );
+ for (auto const& constraint : rConstraints)
+ {
+ const PPDKey* pLeft = constraint.m_pKey1;
+ const PPDKey* pRight = constraint.m_pKey2;
+ if( ! pLeft || ! pRight || ( pKey != pLeft && pKey != pRight ) )
+ continue;
+
+ const PPDKey* pOtherKey = pKey == pLeft ? pRight : pLeft;
+ const PPDValue* pOtherKeyOption = pKey == pLeft ? constraint.m_pOption2 : constraint.m_pOption1;
+ const PPDValue* pKeyOption = pKey == pLeft ? constraint.m_pOption1 : constraint.m_pOption2;
+
+ // syntax *Key1 option1 *Key2 option2
+ if( pKeyOption && pOtherKeyOption )
+ {
+ if( pNewValue != pKeyOption )
+ continue;
+ if( pOtherKeyOption == getValue( pOtherKey ) )
+ {
+ return false;
+ }
+ }
+ // syntax *Key1 option *Key2 or *Key1 *Key2 option
+ else if( pOtherKeyOption || pKeyOption )
+ {
+ if( pKeyOption )
+ {
+ if( ! ( pOtherKeyOption = getValue( pOtherKey ) ) )
+ continue; // this should not happen, PPD broken
+
+ if( pKeyOption == pNewValue &&
+ pOtherKeyOption->m_aOption != "None" &&
+ pOtherKeyOption->m_aOption != "False" )
+ {
+ // check if the other value can be reset and
+ // do so if possible
+ if( bDoReset && resetValue( pOtherKey ) )
+ continue;
+
+ return false;
+ }
+ }
+ else if( pOtherKeyOption )
+ {
+ if( getValue( pOtherKey ) == pOtherKeyOption &&
+ pNewValue->m_aOption != "None" &&
+ pNewValue->m_aOption != "False" )
+ return false;
+ }
+ else
+ {
+ // this should not happen, PPD is broken
+ }
+ }
+ // syntax *Key1 *Key2
+ else
+ {
+ const PPDValue* pOtherValue = getValue( pOtherKey );
+ if( pOtherValue->m_aOption != "None" &&
+ pOtherValue->m_aOption != "False" &&
+ pNewValue->m_aOption != "None" &&
+ pNewValue->m_aOption != "False" )
+ return false;
+ }
+ }
+ return true;
+}
+
+char* PPDContext::getStreamableBuffer( sal_uLong& rBytes ) const
+{
+ rBytes = 0;
+ if( m_aCurrentValues.empty() )
+ return nullptr;
+ for (auto const& elem : m_aCurrentValues)
+ {
+ OString aCopy(OUStringToOString(elem.first->getKey(), RTL_TEXTENCODING_MS_1252));
+ rBytes += aCopy.getLength();
+ rBytes += 1; // for ':'
+ if( elem.second )
+ {
+ aCopy = OUStringToOString(elem.second->m_aOption, RTL_TEXTENCODING_MS_1252);
+ rBytes += aCopy.getLength();
+ }
+ else
+ rBytes += 4;
+ rBytes += 1; // for '\0'
+ }
+ rBytes += 1;
+ char* pBuffer = new char[ rBytes ];
+ memset( pBuffer, 0, rBytes );
+ char* pRun = pBuffer;
+ for (auto const& elem : m_aCurrentValues)
+ {
+ OString aCopy(OUStringToOString(elem.first->getKey(), RTL_TEXTENCODING_MS_1252));
+ int nBytes = aCopy.getLength();
+ memcpy( pRun, aCopy.getStr(), nBytes );
+ pRun += nBytes;
+ *pRun++ = ':';
+ if( elem.second )
+ aCopy = OUStringToOString(elem.second->m_aOption, RTL_TEXTENCODING_MS_1252);
+ else
+ aCopy = "*nil";
+ nBytes = aCopy.getLength();
+ memcpy( pRun, aCopy.getStr(), nBytes );
+ pRun += nBytes;
+
+ *pRun++ = 0;
+ }
+ return pBuffer;
+}
+
+void PPDContext::rebuildFromStreamBuffer(const std::vector<char> &rBuffer)
+{
+ if( ! m_pParser )
+ return;
+
+ m_aCurrentValues.clear();
+
+ const size_t nBytes = rBuffer.size() - 1;
+ size_t nRun = 0;
+ while (nRun < nBytes && rBuffer[nRun])
+ {
+ OString aLine(rBuffer.data() + nRun);
+ sal_Int32 nPos = aLine.indexOf(':');
+ if( nPos != -1 )
+ {
+ const PPDKey* pKey = m_pParser->getKey( OStringToOUString( aLine.subView( 0, nPos ), RTL_TEXTENCODING_MS_1252 ) );
+ if( pKey )
+ {
+ const PPDValue* pValue = nullptr;
+ OUString aOption(
+ OStringToOUString(aLine.subView(nPos+1), RTL_TEXTENCODING_MS_1252));
+ if (aOption != "*nil")
+ pValue = pKey->getValue( aOption );
+ m_aCurrentValues[ pKey ] = pValue;
+ SAL_INFO("vcl.unx.print",
+ "PPDContext::rebuildFromStreamBuffer: read PPDKeyValue { "
+ << pKey->getKey() << " , "
+ << (pValue ? aOption : "<nil>")
+ << " }");
+ }
+ }
+ nRun += aLine.getLength()+1;
+ }
+}
+
+int PPDContext::getRenderResolution() const
+{
+ // initialize to reasonable default, if parser is not set
+ int nDPI = 300;
+ if( m_pParser )
+ {
+ int nDPIx = 300, nDPIy = 300;
+ const PPDKey* pKey = m_pParser->getKey( "Resolution" );
+ if( pKey )
+ {
+ const PPDValue* pValue = getValue( pKey );
+ if( pValue )
+ PPDParser::getResolutionFromString( pValue->m_aOption, nDPIx, nDPIy );
+ else
+ m_pParser->getDefaultResolution( nDPIx, nDPIy );
+ }
+ else
+ m_pParser->getDefaultResolution( nDPIx, nDPIy );
+
+ nDPI = std::max(nDPIx, nDPIy);
+ }
+ return nDPI;
+}
+
+void PPDContext::getPageSize( OUString& rPaper, int& rWidth, int& rHeight ) const
+{
+ // initialize to reasonable default, if parser is not set
+ rPaper = "A4";
+ rWidth = 595;
+ rHeight = 842;
+ if( !m_pParser )
+ return;
+
+ const PPDKey* pKey = m_pParser->getKey( "PageSize" );
+ if( !pKey )
+ return;
+
+ const PPDValue* pValue = getValue( pKey );
+ if( pValue )
+ {
+ rPaper = pValue->m_aOption;
+ m_pParser->getPaperDimension( rPaper, rWidth, rHeight );
+ }
+ else
+ {
+ rPaper = m_pParser->getDefaultPaperDimension();
+ m_pParser->getDefaultPaperDimension( rWidth, rHeight );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/printer/printerinfomanager.cxx b/vcl/unx/generic/printer/printerinfomanager.cxx
new file mode 100644
index 000000000..ae87f6adf
--- /dev/null
+++ b/vcl/unx/generic/printer/printerinfomanager.cxx
@@ -0,0 +1,892 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <unx/cpdmgr.hxx>
+#include <unx/cupsmgr.hxx>
+#include <unx/gendata.hxx>
+#include <unx/helper.hxx>
+
+#include <tools/urlobj.hxx>
+#include <tools/config.hxx>
+
+#include <i18nutil/paper.hxx>
+#include <rtl/strbuf.hxx>
+#include <sal/log.hxx>
+
+#include <osl/file.hxx>
+#include <osl/thread.hxx>
+#include <osl/mutex.hxx>
+#include <o3tl/string_view.hxx>
+
+// filename of configuration files
+constexpr OUStringLiteral PRINT_FILENAME = u"psprint.conf";
+// the group of the global defaults
+constexpr OStringLiteral GLOBAL_DEFAULTS_GROUP = "__Global_Printer_Defaults__";
+
+#include <cstddef>
+#include <unordered_set>
+
+using namespace psp;
+using namespace osl;
+
+namespace psp
+{
+ class SystemQueueInfo final : public Thread
+ {
+ mutable Mutex m_aMutex;
+ bool m_bChanged;
+ std::vector< PrinterInfoManager::SystemPrintQueue >
+ m_aQueues;
+ OUString m_aCommand;
+
+ virtual void SAL_CALL run() override;
+
+ public:
+ SystemQueueInfo();
+ virtual ~SystemQueueInfo() override;
+
+ bool hasChanged() const;
+ OUString getCommand() const;
+
+ // sets changed status to false; therefore not const
+ void getSystemQueues( std::vector< PrinterInfoManager::SystemPrintQueue >& rQueues );
+ };
+} // namespace
+
+/*
+* class PrinterInfoManager
+*/
+
+PrinterInfoManager& PrinterInfoManager::get()
+{
+ // can't move to GenericUnixSalData, because of vcl/null/printerinfomanager.cxx
+ GenericUnixSalData* pSalData = GetGenericUnixSalData();
+ PrinterInfoManager* pPIM = pSalData->m_pPrinterInfoManager.get();
+ if (pPIM)
+ return *pPIM;
+
+ pPIM = CPDManager::tryLoadCPD();
+ if (!pPIM)
+ pPIM = CUPSManager::tryLoadCUPS();
+ if (!pPIM)
+ pPIM = new PrinterInfoManager();
+ pSalData->m_pPrinterInfoManager.reset(pPIM);
+ pPIM->initialize();
+
+ SAL_INFO("vcl.unx.print", "created PrinterInfoManager of type "
+ << static_cast<int>(pPIM->getType()));
+ return *pPIM;
+}
+
+PrinterInfoManager::PrinterInfoManager( Type eType ) :
+ m_eType( eType ),
+ m_bUseIncludeFeature( false ),
+ m_bUseJobPatch( true ),
+ m_aSystemDefaultPaper( "A4" )
+{
+ if( eType == Type::Default )
+ m_pQueueInfo.reset( new SystemQueueInfo );
+
+ m_aSystemDefaultPaper = OStringToOUString(
+ PaperInfo::toPSName(PaperInfo::getSystemDefaultPaper().getPaper()),
+ RTL_TEXTENCODING_UTF8);
+}
+
+PrinterInfoManager::~PrinterInfoManager()
+{
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "PrinterInfoManager: "
+ << "destroyed Manager of type "
+ << ((int) getType()));
+#endif
+}
+
+bool PrinterInfoManager::checkPrintersChanged( bool bWait )
+{
+ // check if files were created, deleted or modified since initialize()
+ bool bChanged = false;
+ for (auto const& watchFile : m_aWatchFiles)
+ {
+ DirectoryItem aItem;
+ if( DirectoryItem::get( watchFile.m_aFilePath, aItem ) )
+ {
+ if( watchFile.m_aModified.Seconds != 0 )
+ {
+ bChanged = true; // file probably has vanished
+ break;
+ }
+ }
+ else
+ {
+ FileStatus aStatus( osl_FileStatus_Mask_ModifyTime );
+ if( aItem.getFileStatus( aStatus ) )
+ {
+ bChanged = true; // unlikely but not impossible
+ break;
+ }
+ else
+ {
+ TimeValue aModified = aStatus.getModifyTime();
+ if( aModified.Seconds != watchFile.m_aModified.Seconds )
+ {
+ bChanged = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if( bWait && m_pQueueInfo )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "syncing printer discovery thread.");
+#endif
+ m_pQueueInfo->join();
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "done: syncing printer discovery thread.");
+#endif
+ }
+
+ if( ! bChanged && m_pQueueInfo )
+ bChanged = m_pQueueInfo->hasChanged();
+ if( bChanged )
+ {
+ initialize();
+ }
+
+ return bChanged;
+}
+
+void PrinterInfoManager::initialize()
+{
+ m_bUseIncludeFeature = false;
+ m_aPrinters.clear();
+ m_aWatchFiles.clear();
+ OUString aDefaultPrinter;
+
+ // first initialize the global defaults
+ // have to iterate over all possible files
+ // there should be only one global setup section in all
+ // available config files
+ m_aGlobalDefaults = PrinterInfo();
+
+ // need a parser for the PPDContext. generic printer should do.
+ m_aGlobalDefaults.m_pParser = PPDParser::getParser( "SGENPRT" );
+ m_aGlobalDefaults.m_aContext.setParser( m_aGlobalDefaults.m_pParser );
+
+ if( ! m_aGlobalDefaults.m_pParser )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "Error: no default PPD file "
+ << "SGENPRT available, shutting down psprint...");
+#endif
+ return;
+ }
+
+ std::vector< OUString > aDirList;
+ psp::getPrinterPathList( aDirList, nullptr );
+ for (auto const& printDir : aDirList)
+ {
+ INetURLObject aFile( printDir, INetProtocol::File, INetURLObject::EncodeMechanism::All );
+ aFile.Append( PRINT_FILENAME );
+ Config aConfig( aFile.PathToFileName() );
+ if( aConfig.HasGroup( GLOBAL_DEFAULTS_GROUP ) )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "found global defaults in "
+ << aFile.PathToFileName());
+#endif
+ aConfig.SetGroup( GLOBAL_DEFAULTS_GROUP );
+
+ OString aValue( aConfig.ReadKey( "Copies" ) );
+ if (!aValue.isEmpty())
+ m_aGlobalDefaults.m_nCopies = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "Orientation" );
+ if (!aValue.isEmpty())
+ m_aGlobalDefaults.m_eOrientation = aValue.equalsIgnoreAsciiCase("Landscape") ? orientation::Landscape : orientation::Portrait;
+
+ aValue = aConfig.ReadKey( "MarginAdjust" );
+ if (!aValue.isEmpty())
+ {
+ sal_Int32 nIdx {0};
+ m_aGlobalDefaults.m_nLeftMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx));
+ m_aGlobalDefaults.m_nRightMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx));
+ m_aGlobalDefaults.m_nTopMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx));
+ m_aGlobalDefaults.m_nBottomMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx));
+ }
+
+ aValue = aConfig.ReadKey( "ColorDepth", "24" );
+ if (!aValue.isEmpty())
+ m_aGlobalDefaults.m_nColorDepth = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "ColorDevice" );
+ if (!aValue.isEmpty())
+ m_aGlobalDefaults.m_nColorDevice = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "PSLevel" );
+ if (!aValue.isEmpty())
+ m_aGlobalDefaults.m_nPSLevel = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "PDFDevice" );
+ if (!aValue.isEmpty())
+ m_aGlobalDefaults.m_nPDFDevice = aValue.toInt32();
+
+ // get the PPDContext of global JobData
+ for( int nKey = 0; nKey < aConfig.GetKeyCount(); ++nKey )
+ {
+ OString aKey( aConfig.GetKeyName( nKey ) );
+ if (aKey.startsWith("PPD_"))
+ {
+ aValue = aConfig.ReadKey( aKey );
+ const PPDKey* pKey = m_aGlobalDefaults.m_pParser->getKey(OStringToOUString(aKey.subView(4), RTL_TEXTENCODING_ISO_8859_1));
+ if( pKey )
+ {
+ m_aGlobalDefaults.m_aContext.
+ setValue( pKey,
+ aValue == "*nil" ? nullptr : pKey->getValue(OStringToOUString(aValue, RTL_TEXTENCODING_ISO_8859_1)),
+ true );
+ }
+ }
+ }
+ }
+ }
+ setDefaultPaper( m_aGlobalDefaults.m_aContext );
+
+ // now collect all available printers
+ for (auto const& printDir : aDirList)
+ {
+ INetURLObject aDir( printDir, INetProtocol::File, INetURLObject::EncodeMechanism::All );
+ INetURLObject aFile( aDir );
+ aFile.Append( PRINT_FILENAME );
+
+ // check directory validity
+ OUString aUniPath;
+ FileBase::getFileURLFromSystemPath( aDir.PathToFileName(), aUniPath );
+ Directory aDirectory( aUniPath );
+ if( aDirectory.open() )
+ continue;
+ aDirectory.close();
+
+ FileBase::getFileURLFromSystemPath( aFile.PathToFileName(), aUniPath );
+ FileStatus aStatus( osl_FileStatus_Mask_ModifyTime );
+ DirectoryItem aItem;
+
+ // setup WatchFile list
+ WatchFile aWatchFile;
+ aWatchFile.m_aFilePath = aUniPath;
+ if( ! DirectoryItem::get( aUniPath, aItem ) &&
+ ! aItem.getFileStatus( aStatus ) )
+ {
+ aWatchFile.m_aModified = aStatus.getModifyTime();
+ }
+ else
+ {
+ aWatchFile.m_aModified.Seconds = 0;
+ aWatchFile.m_aModified.Nanosec = 0;
+ }
+ m_aWatchFiles.push_back( aWatchFile );
+
+ Config aConfig( aFile.PathToFileName() );
+ for( int nGroup = 0; nGroup < aConfig.GetGroupCount(); nGroup++ )
+ {
+ aConfig.SetGroup( aConfig.GetGroupName( nGroup ) );
+ OString aValue = aConfig.ReadKey( "Printer" );
+ if (!aValue.isEmpty())
+ {
+ OUString aPrinterName;
+
+ sal_Int32 nNamePos = aValue.indexOf('/');
+ // check for valid value of "Printer"
+ if (nNamePos == -1)
+ continue;
+
+ Printer aPrinter;
+ // initialize to global defaults
+ aPrinter.m_aInfo = m_aGlobalDefaults;
+
+ aPrinterName = OStringToOUString(aValue.subView(nNamePos+1),
+ RTL_TEXTENCODING_UTF8);
+ aPrinter.m_aInfo.m_aPrinterName = aPrinterName;
+ aPrinter.m_aInfo.m_aDriverName = OStringToOUString(aValue.subView(0, nNamePos), RTL_TEXTENCODING_UTF8);
+
+ // set parser, merge settings
+ // don't do this for CUPS printers as this is done
+ // by the CUPS system itself
+ if( !aPrinter.m_aInfo.m_aDriverName.startsWith( "CUPS:" ) )
+ {
+ aPrinter.m_aInfo.m_pParser = PPDParser::getParser( aPrinter.m_aInfo.m_aDriverName );
+ aPrinter.m_aInfo.m_aContext.setParser( aPrinter.m_aInfo.m_pParser );
+ // note: setParser also purges the context
+
+ // ignore this printer if its driver is not found
+ if( ! aPrinter.m_aInfo.m_pParser )
+ continue;
+
+ // merge the ppd context keys if the printer has the same keys and values
+ // this is a bit tricky, since it involves mixing two PPDs
+ // without constraints which might end up badly
+ // this feature should be use with caution
+ // it is mainly to select default paper sizes for new printers
+ for( std::size_t nPPDValueModified = 0; nPPDValueModified < m_aGlobalDefaults.m_aContext.countValuesModified(); nPPDValueModified++ )
+ {
+ const PPDKey* pDefKey = m_aGlobalDefaults.m_aContext.getModifiedKey( nPPDValueModified );
+ const PPDValue* pDefValue = m_aGlobalDefaults.m_aContext.getValue( pDefKey );
+ const PPDKey* pPrinterKey = pDefKey ? aPrinter.m_aInfo.m_pParser->getKey( pDefKey->getKey() ) : nullptr;
+ if( pDefKey && pPrinterKey )
+ // at least the options exist in both PPDs
+ {
+ if( pDefValue )
+ {
+ const PPDValue* pPrinterValue = pPrinterKey->getValue( pDefValue->m_aOption );
+ if( pPrinterValue )
+ // the printer has a corresponding option for the key
+ aPrinter.m_aInfo.m_aContext.setValue( pPrinterKey, pPrinterValue );
+ }
+ else
+ aPrinter.m_aInfo.m_aContext.setValue( pPrinterKey, nullptr );
+ }
+ }
+
+ aValue = aConfig.ReadKey( "Command" );
+ // no printer without a command
+ if (aValue.isEmpty())
+ {
+ /* TODO:
+ * porters: please append your platform to the Solaris
+ * case if your platform has SystemV printing per default.
+ */
+ #if defined __sun
+ aValue = "lp";
+ #else
+ aValue = "lpr";
+ #endif
+ }
+ aPrinter.m_aInfo.m_aCommand = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8);
+ }
+
+ aValue = aConfig.ReadKey( "QuickCommand" );
+ aPrinter.m_aInfo.m_aQuickCommand = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8);
+
+ aValue = aConfig.ReadKey( "Features" );
+ aPrinter.m_aInfo.m_aFeatures = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8);
+
+ // override the settings in m_aGlobalDefaults if keys exist
+ aValue = aConfig.ReadKey( "DefaultPrinter" );
+ if (aValue != "0" && !aValue.equalsIgnoreAsciiCase("false"))
+ aDefaultPrinter = aPrinterName;
+
+ aValue = aConfig.ReadKey( "Location" );
+ aPrinter.m_aInfo.m_aLocation = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8);
+
+ aValue = aConfig.ReadKey( "Comment" );
+ aPrinter.m_aInfo.m_aComment = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8);
+
+ aValue = aConfig.ReadKey( "Copies" );
+ if (!aValue.isEmpty())
+ aPrinter.m_aInfo.m_nCopies = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "Orientation" );
+ if (!aValue.isEmpty())
+ aPrinter.m_aInfo.m_eOrientation = aValue.equalsIgnoreAsciiCase("Landscape") ? orientation::Landscape : orientation::Portrait;
+
+ aValue = aConfig.ReadKey( "MarginAdjust" );
+ if (!aValue.isEmpty())
+ {
+ sal_Int32 nIdx {0};
+ aPrinter.m_aInfo.m_nLeftMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx));
+ aPrinter.m_aInfo.m_nRightMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx));
+ aPrinter.m_aInfo.m_nTopMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx));
+ aPrinter.m_aInfo.m_nBottomMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx));
+ }
+
+ aValue = aConfig.ReadKey( "ColorDepth" );
+ if (!aValue.isEmpty())
+ aPrinter.m_aInfo.m_nColorDepth = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "ColorDevice" );
+ if (!aValue.isEmpty())
+ aPrinter.m_aInfo.m_nColorDevice = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "PSLevel" );
+ if (!aValue.isEmpty())
+ aPrinter.m_aInfo.m_nPSLevel = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "PDFDevice" );
+ if (!aValue.isEmpty())
+ aPrinter.m_aInfo.m_nPDFDevice = aValue.toInt32();
+
+ // now iterate over all keys to extract multi key information:
+ // 1. PPDContext information
+ for( int nKey = 0; nKey < aConfig.GetKeyCount(); ++nKey )
+ {
+ OString aKey( aConfig.GetKeyName( nKey ) );
+ if( aKey.startsWith("PPD_") && aPrinter.m_aInfo.m_pParser )
+ {
+ aValue = aConfig.ReadKey( aKey );
+ const PPDKey* pKey = aPrinter.m_aInfo.m_pParser->getKey(OStringToOUString(aKey.subView(4), RTL_TEXTENCODING_ISO_8859_1));
+ if( pKey )
+ {
+ aPrinter.m_aInfo.m_aContext.
+ setValue( pKey,
+ aValue == "*nil" ? nullptr : pKey->getValue(OStringToOUString(aValue, RTL_TEXTENCODING_ISO_8859_1)),
+ true );
+ }
+ }
+ }
+
+ setDefaultPaper( aPrinter.m_aInfo.m_aContext );
+
+ // if it's a "Generic Printer", apply defaults from config...
+ aPrinter.m_aInfo.resolveDefaultBackend();
+
+ // finally insert printer
+ FileBase::getFileURLFromSystemPath( aFile.PathToFileName(), aPrinter.m_aFile );
+ std::unordered_map< OUString, Printer >::const_iterator find_it =
+ m_aPrinters.find( aPrinterName );
+ if( find_it != m_aPrinters.end() )
+ {
+ aPrinter.m_aAlternateFiles = find_it->second.m_aAlternateFiles;
+ aPrinter.m_aAlternateFiles.insert( find_it->second.m_aFile );
+ }
+ m_aPrinters[ aPrinterName ] = aPrinter;
+ }
+ }
+ }
+
+ // set default printer
+ if( !m_aPrinters.empty() )
+ {
+ if( m_aPrinters.find( aDefaultPrinter ) == m_aPrinters.end() )
+ aDefaultPrinter = m_aPrinters.begin()->first;
+ }
+ else
+ aDefaultPrinter.clear();
+ m_aDefaultPrinter = aDefaultPrinter;
+
+ if( m_eType != Type::Default )
+ return;
+
+ // add a default printer for every available print queue
+ // merge paper default printer, all else from global defaults
+ PrinterInfo aMergeInfo( m_aGlobalDefaults );
+ aMergeInfo.m_aDriverName = "SGENPRT";
+ aMergeInfo.m_aFeatures = "autoqueue";
+
+ if( !m_aDefaultPrinter.isEmpty() )
+ {
+ PrinterInfo aDefaultInfo( getPrinterInfo( m_aDefaultPrinter ) );
+
+ const PPDKey* pDefKey = aDefaultInfo.m_pParser->getKey( "PageSize" );
+ const PPDKey* pMergeKey = aMergeInfo.m_pParser->getKey( "PageSize" );
+ const PPDValue* pDefValue = aDefaultInfo.m_aContext.getValue( pDefKey );
+ const PPDValue* pMergeValue = pMergeKey ? pMergeKey->getValue( pDefValue->m_aOption ) : nullptr;
+ if( pMergeKey && pMergeValue )
+ aMergeInfo.m_aContext.setValue( pMergeKey, pMergeValue );
+ }
+
+ if( m_pQueueInfo && m_pQueueInfo->hasChanged() )
+ {
+ m_aSystemPrintCommand = m_pQueueInfo->getCommand();
+ m_pQueueInfo->getSystemQueues( m_aSystemPrintQueues );
+ m_pQueueInfo.reset();
+ }
+ for (auto const& printQueue : m_aSystemPrintQueues)
+ {
+ OUString aPrinterName = "<" + printQueue.m_aQueue + ">";
+
+ if( m_aPrinters.find( aPrinterName ) != m_aPrinters.end() )
+ // probably user made this one permanent
+ continue;
+
+ OUString aCmd( m_aSystemPrintCommand );
+ aCmd = aCmd.replaceAll( "(PRINTER)", printQueue.m_aQueue );
+
+ Printer aPrinter;
+
+ // initialize to merged defaults
+ aPrinter.m_aInfo = aMergeInfo;
+ aPrinter.m_aInfo.m_aPrinterName = aPrinterName;
+ aPrinter.m_aInfo.m_aCommand = aCmd;
+ aPrinter.m_aInfo.m_aComment = printQueue.m_aComment;
+ aPrinter.m_aInfo.m_aLocation = printQueue.m_aLocation;
+
+ m_aPrinters[ aPrinterName ] = aPrinter;
+ }
+}
+
+void PrinterInfoManager::listPrinters( ::std::vector< OUString >& rVector ) const
+{
+ rVector.clear();
+ for (auto const& printer : m_aPrinters)
+ rVector.push_back(printer.first);
+}
+
+const PrinterInfo& PrinterInfoManager::getPrinterInfo( const OUString& rPrinter ) const
+{
+ static PrinterInfo aEmptyInfo;
+ std::unordered_map< OUString, Printer >::const_iterator it = m_aPrinters.find( rPrinter );
+
+ SAL_WARN_IF( it == m_aPrinters.end(), "vcl", "Do not ask for info about nonexistent printers" );
+
+ return it != m_aPrinters.end() ? it->second.m_aInfo : aEmptyInfo;
+}
+
+bool PrinterInfoManager::checkFeatureToken( const OUString& rPrinterName, const char* pToken ) const
+{
+ const PrinterInfo& rPrinterInfo( getPrinterInfo( rPrinterName ) );
+ sal_Int32 nIndex = 0;
+ while( nIndex != -1 )
+ {
+ OUString aOuterToken = rPrinterInfo.m_aFeatures.getToken( 0, ',', nIndex );
+ if( aOuterToken.getToken( 0, '=' ).equalsIgnoreAsciiCaseAscii( pToken ) )
+ return true;
+ }
+ return false;
+}
+
+FILE* PrinterInfoManager::startSpool( const OUString& rPrintername, bool bQuickCommand )
+{
+ const PrinterInfo& rPrinterInfo = getPrinterInfo (rPrintername);
+ const OUString& rCommand = (bQuickCommand && !rPrinterInfo.m_aQuickCommand.isEmpty() ) ?
+ rPrinterInfo.m_aQuickCommand : rPrinterInfo.m_aCommand;
+ OString aShellCommand = OUStringToOString (rCommand, RTL_TEXTENCODING_ISO_8859_1) +
+ " 2>/dev/null";
+
+ return popen (aShellCommand.getStr(), "w");
+}
+
+bool PrinterInfoManager::endSpool( const OUString& /*rPrintername*/, const OUString& /*rJobTitle*/, FILE* pFile, const JobData& /*rDocumentJobData*/, bool /*bBanner*/, const OUString& /*rFaxNumber*/ )
+{
+ return (0 == pclose( pFile ));
+}
+
+void PrinterInfoManager::setupJobContextData( JobData& rData )
+{
+ std::unordered_map< OUString, Printer >::iterator it =
+ m_aPrinters.find( rData.m_aPrinterName );
+ if( it != m_aPrinters.end() )
+ {
+ rData.m_pParser = it->second.m_aInfo.m_pParser;
+ rData.m_aContext = it->second.m_aInfo.m_aContext;
+ }
+}
+
+void PrinterInfoManager::setDefaultPaper( PPDContext& rContext ) const
+{
+ if( ! rContext.getParser() )
+ return;
+
+ const PPDKey* pPageSizeKey = rContext.getParser()->getKey( "PageSize" );
+ if( ! pPageSizeKey )
+ return;
+
+ std::size_t nModified = rContext.countValuesModified();
+ auto set = false;
+ for (std::size_t i = 0; i != nModified; ++i) {
+ if (rContext.getModifiedKey(i) == pPageSizeKey) {
+ set = true;
+ break;
+ }
+ }
+
+ if( set ) // paper was set already, do not modify
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN("vcl.unx.print", "not setting default paper, already set "
+ << rContext.getValue( pPageSizeKey )->m_aOption);
+#endif
+ return;
+ }
+
+ // paper not set, fill in default value
+ const PPDValue* pPaperVal = nullptr;
+ int nValues = pPageSizeKey->countValues();
+ for( int i = 0; i < nValues && ! pPaperVal; i++ )
+ {
+ const PPDValue* pVal = pPageSizeKey->getValue( i );
+ if( pVal->m_aOption.equalsIgnoreAsciiCase( m_aSystemDefaultPaper ) )
+ pPaperVal = pVal;
+ }
+ if( pPaperVal )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "setting default paper "
+ << pPaperVal->m_aOption);
+#endif
+ rContext.setValue( pPageSizeKey, pPaperVal );
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "-> got paper "
+ << rContext.getValue( pPageSizeKey )->m_aOption);
+#endif
+ }
+}
+
+SystemQueueInfo::SystemQueueInfo() :
+ m_bChanged( false )
+{
+ create();
+}
+
+SystemQueueInfo::~SystemQueueInfo()
+{
+ static const char* pNoSyncDetection = getenv( "SAL_DISABLE_SYNCHRONOUS_PRINTER_DETECTION" );
+ if( ! pNoSyncDetection || !*pNoSyncDetection )
+ join();
+ else
+ terminate();
+}
+
+bool SystemQueueInfo::hasChanged() const
+{
+ MutexGuard aGuard( m_aMutex );
+ bool bChanged = m_bChanged;
+ return bChanged;
+}
+
+void SystemQueueInfo::getSystemQueues( std::vector< PrinterInfoManager::SystemPrintQueue >& rQueues )
+{
+ MutexGuard aGuard( m_aMutex );
+ rQueues = m_aQueues;
+ m_bChanged = false;
+}
+
+OUString SystemQueueInfo::getCommand() const
+{
+ MutexGuard aGuard( m_aMutex );
+ OUString aRet = m_aCommand;
+ return aRet;
+}
+
+namespace {
+
+struct SystemCommandParameters;
+
+}
+
+typedef void(* tokenHandler)(const std::vector< OString >&,
+ std::vector< PrinterInfoManager::SystemPrintQueue >&,
+ const SystemCommandParameters*);
+
+namespace {
+
+struct SystemCommandParameters
+{
+ const char* pQueueCommand;
+ const char* pPrintCommand;
+ const char* pForeToken;
+ const char* pAftToken;
+ unsigned int nForeTokenCount;
+ tokenHandler pHandler;
+};
+
+}
+
+#if ! (defined(LINUX) || defined(NETBSD) || defined(FREEBSD) || defined(OPENBSD))
+static void lpgetSysQueueTokenHandler(
+ const std::vector< OString >& i_rLines,
+ std::vector< PrinterInfoManager::SystemPrintQueue >& o_rQueues,
+ const SystemCommandParameters* )
+{
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ std::unordered_set< OUString > aUniqueSet;
+ std::unordered_set< OUString > aOnlySet;
+ aUniqueSet.insert( OUString( "_all" ) );
+ aUniqueSet.insert( OUString( "_default" ) );
+
+ // the eventual "all" attribute of the "_all" queue tells us, which
+ // printers are to be used for this user at all
+
+ // find _all: line
+ OString aAllLine( "_all:" );
+ OString aAllAttr( "all=" );
+ auto it = std::find_if(i_rLines.begin(), i_rLines.end(),
+ [&aAllLine](const OString& rLine) { return rLine.indexOf( aAllLine, 0 ) == 0; });
+ if( it != i_rLines.end() )
+ {
+ // now find the "all" attribute
+ ++it;
+ it = std::find_if(it, i_rLines.end(),
+ [&aAllAttr](const OString& rLine) { return WhitespaceToSpace( rLine ).startsWith( aAllAttr ); });
+ if( it != i_rLines.end() )
+ {
+ // insert the comma separated entries into the set of printers to use
+ OString aClean( WhitespaceToSpace( *it ) );
+ sal_Int32 nPos = aAllAttr.getLength();
+ while( nPos != -1 )
+ {
+ OString aTok( aClean.getToken( 0, ',', nPos ) );
+ if( !aTok.isEmpty() )
+ aOnlySet.insert( OStringToOUString( aTok, aEncoding ) );
+ }
+ }
+ }
+
+ bool bInsertAttribute = false;
+ OString aDescrStr( "description=" );
+ OString aLocStr( "location=" );
+ for (auto const& line : i_rLines)
+ {
+ sal_Int32 nPos = 0;
+ // find the begin of a new printer section
+ nPos = line.indexOf( ':', 0 );
+ if( nPos != -1 )
+ {
+ OUString aSysQueue( OStringToOUString( line.copy( 0, nPos ), aEncoding ) );
+ // do not insert duplicates (e.g. lpstat tends to produce such lines)
+ // in case there was a "_all" section, insert only those printer explicitly
+ // set in the "all" attribute
+ if( aUniqueSet.find( aSysQueue ) == aUniqueSet.end() &&
+ ( aOnlySet.empty() || aOnlySet.find( aSysQueue ) != aOnlySet.end() )
+ )
+ {
+ o_rQueues.push_back( PrinterInfoManager::SystemPrintQueue() );
+ o_rQueues.back().m_aQueue = aSysQueue;
+ o_rQueues.back().m_aLocation = aSysQueue;
+ aUniqueSet.insert( aSysQueue );
+ bInsertAttribute = true;
+ }
+ else
+ bInsertAttribute = false;
+ continue;
+ }
+ if( bInsertAttribute && ! o_rQueues.empty() )
+ {
+ // look for "description" attribute, insert as comment
+ nPos = line.indexOf( aDescrStr, 0 );
+ if( nPos != -1 )
+ {
+ OString aComment( WhitespaceToSpace( line.copy(nPos+12) ) );
+ if( !aComment.isEmpty() )
+ o_rQueues.back().m_aComment = OStringToOUString(aComment, aEncoding);
+ continue;
+ }
+ // look for "location" attribute, inser as location
+ nPos = line.indexOf( aLocStr, 0 );
+ if( nPos != -1 )
+ {
+ OString aLoc( WhitespaceToSpace( line.copy(nPos+9) ) );
+ if( !aLoc.isEmpty() )
+ o_rQueues.back().m_aLocation = OStringToOUString(aLoc, aEncoding);
+ continue;
+ }
+ }
+ }
+}
+#endif
+static void standardSysQueueTokenHandler(
+ const std::vector< OString >& i_rLines,
+ std::vector< PrinterInfoManager::SystemPrintQueue >& o_rQueues,
+ const SystemCommandParameters* i_pParms)
+{
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ std::unordered_set< OUString > aUniqueSet;
+ OString aForeToken( i_pParms->pForeToken );
+ OString aAftToken( i_pParms->pAftToken );
+ /* Normal Unix print queue discovery, also used for Darwin 5 LPR printing
+ */
+ for (auto const& line : i_rLines)
+ {
+ sal_Int32 nPos = 0;
+
+ // search for a line describing a printer:
+ // find if there are enough tokens before the name
+ for( unsigned int i = 0; i < i_pParms->nForeTokenCount && nPos != -1; i++ )
+ {
+ nPos = line.indexOf( aForeToken, nPos );
+ if( nPos != -1 && line.getLength() >= nPos+aForeToken.getLength() )
+ nPos += aForeToken.getLength();
+ }
+ if( nPos != -1 )
+ {
+ // find if there is the token after the queue
+ sal_Int32 nAftPos = line.indexOf( aAftToken, nPos );
+ if( nAftPos != -1 )
+ {
+ // get the queue name between fore and aft tokens
+ OUString aSysQueue( OStringToOUString( line.subView( nPos, nAftPos - nPos ), aEncoding ) );
+ // do not insert duplicates (e.g. lpstat tends to produce such lines)
+ if( aUniqueSet.insert( aSysQueue ).second )
+ {
+ o_rQueues.emplace_back( );
+ o_rQueues.back().m_aQueue = aSysQueue;
+ o_rQueues.back().m_aLocation = aSysQueue;
+ }
+ }
+ }
+ }
+}
+
+const struct SystemCommandParameters aParms[] =
+{
+ #if defined(LINUX) || defined(NETBSD) || defined(FREEBSD) || defined(OPENBSD)
+ { "/usr/sbin/lpc status", "lpr -P \"(PRINTER)\"", "", ":", 0, standardSysQueueTokenHandler },
+ { "lpc status", "lpr -P \"(PRINTER)\"", "", ":", 0, standardSysQueueTokenHandler },
+ { "LANG=C;LC_ALL=C;export LANG LC_ALL;lpstat -s", "lp -d \"(PRINTER)\"", "system for ", ": ", 1, standardSysQueueTokenHandler }
+ #else
+ { "LANG=C;LC_ALL=C;export LANG LC_ALL;lpget list", "lp -d \"(PRINTER)\"", "", ":", 0, lpgetSysQueueTokenHandler },
+ { "LANG=C;LC_ALL=C;export LANG LC_ALL;lpstat -s", "lp -d \"(PRINTER)\"", "system for ", ": ", 1, standardSysQueueTokenHandler },
+ { "/usr/sbin/lpc status", "lpr -P \"(PRINTER)\"", "", ":", 0, standardSysQueueTokenHandler },
+ { "lpc status", "lpr -P \"(PRINTER)\"", "", ":", 0, standardSysQueueTokenHandler }
+ #endif
+};
+
+void SystemQueueInfo::run()
+{
+ osl_setThreadName("LPR psp::SystemQueueInfo");
+
+ char pBuffer[1024];
+ std::vector< OString > aLines;
+
+ /* Discover which command we can use to get a list of all printer queues */
+ for(const auto & rParm : aParms)
+ {
+ aLines.clear();
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "trying print queue command \""
+ << rParm.pQueueCommand
+ << "\" ...");
+#endif
+ OString aCmdLine = rParm.pQueueCommand + OString::Concat(" 2>/dev/null");
+ FILE *pPipe;
+ if( (pPipe = popen( aCmdLine.getStr(), "r" )) )
+ {
+ while( fgets( pBuffer, 1024, pPipe ) )
+ aLines.emplace_back( pBuffer );
+ if( ! pclose( pPipe ) )
+ {
+ std::vector< PrinterInfoManager::SystemPrintQueue > aSysPrintQueues;
+ rParm.pHandler( aLines, aSysPrintQueues, &rParm );
+ MutexGuard aGuard( m_aMutex );
+ m_bChanged = true;
+ m_aQueues = aSysPrintQueues;
+ m_aCommand = OUString::createFromAscii( rParm.pPrintCommand );
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "printing queue command: success.");
+#endif
+ break;
+ }
+ }
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "printing queue command: failed.");
+#endif
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */