diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
commit | 940b4d1848e8c70ab7642901a68594e8016caffc (patch) | |
tree | eb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /vcl/unx/generic/printer | |
parent | Initial commit. (diff) | |
download | libreoffice-940b4d1848e8c70ab7642901a68594e8016caffc.tar.xz libreoffice-940b4d1848e8c70ab7642901a68594e8016caffc.zip |
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vcl/unx/generic/printer')
-rw-r--r-- | vcl/unx/generic/printer/configuration/README | 5 | ||||
-rw-r--r-- | vcl/unx/generic/printer/configuration/ppds/SGENPRT.PS | 582 | ||||
-rw-r--r-- | vcl/unx/generic/printer/configuration/psprint.conf | 99 | ||||
-rw-r--r-- | vcl/unx/generic/printer/cpdmgr.cxx | 754 | ||||
-rw-r--r-- | vcl/unx/generic/printer/cupsmgr.cxx | 965 | ||||
-rw-r--r-- | vcl/unx/generic/printer/jobdata.cxx | 306 | ||||
-rw-r--r-- | vcl/unx/generic/printer/ppdparser.cxx | 1987 | ||||
-rw-r--r-- | vcl/unx/generic/printer/printerinfomanager.cxx | 900 |
8 files changed, 5598 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..bebd56833 --- /dev/null +++ b/vcl/unx/generic/printer/cpdmgr.cxx @@ -0,0 +1,754 @@ +/* -*- 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; + GDBusNodeInfo *introspection_data; + + // Get Interface for introspection + g_file_get_contents (FRONTEND_INTERFACE, &contents, nullptr, nullptr); + 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) + { + GDBusProxy *proxy; + // Get Interface for introspection + g_file_get_contents (BACKEND_INTERFACE, &contents, nullptr, nullptr); + introspection_data = g_dbus_node_info_new_for_xml (contents, nullptr); + 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_free(backend.second); + 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); + } +} + +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; + GDBusNodeInfo *introspection_data; + + // Get Interface for introspection + g_file_get_contents ("/usr/share/dbus-1/interfaces/org.openprinting.Backend.xml", &contents, nullptr, nullptr); + 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(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; + 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( pFile ); + } +#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..f5a50345a --- /dev/null +++ b/vcl/unx/generic/printer/cupsmgr.cxx @@ -0,0 +1,965 @@ +/* -*- 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 <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> + +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 + OString aResult = cupsGetPPD(m_aParameter.getStr()); +#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 ) + { + // if the thread is still running here, then + // cupsGetDests is hung; terminate the thread instead of joining + osl_terminateThread( 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 ) + { + 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, const OString& rServer, const OString& 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, const OString& rServer, const OString& 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.isEmpty()) + m_xUserEdit->grab_focus(); + else + { + m_xUserEdit->set_text(OStringToOUString(rUserName, osl_getThreadTextEncoding())); + m_xPassEdit->grab_focus(); + } + } + + bool AuthenticateQuery(const OString& rServer, OString& rUserName, OString& rPassword) + { + bool bRet = false; + + vcl::Window* pWin = Application::GetDefDialogParent(); + RTSPWDialog aDialog(pWin ? pWin->GetFrameWeld() : nullptr, 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 + { + OUString aToken = aInfo.m_aAuthInfoRequired.getToken(0, ',', nIndex); + if (aToken == "domain") + bDomain = true; + else if (aToken == "username") + bUser = true; + else if (aToken == "password") + bPass = true; + } + while (nIndex >= 0); + + if (bDomain || bUser || bPass) + { + OString sPrinterName(OUStringToOString(rPrintername, RTL_TEXTENCODING_UTF8)); + OString sUser = cupsUser(); + vcl::Window* pWin = Application::GetDefDialogParent(); + RTSPWDialog aDialog(pWin ? pWin->GetFrameWeld() : nullptr, 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( pFile ); + 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..362305bea --- /dev/null +++ b/vcl/unx/generic/printer/jobdata.cxx @@ -0,0 +1,306 @@ +/* -*- 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 <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 ) + { + const PPDKey* pKey = pParser->getKey( "Collate" ); + if( pKey ) + { + 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.makeStringAndClear()); + + aLine.append("orientation="); + if (m_eOrientation == orientation::Landscape) + aLine.append("Landscape"); + else + aLine.append("Portrait"); + aStream.WriteLine(aLine.makeStringAndClear()); + + aLine.append("copies="); + aLine.append(static_cast<sal_Int32>(m_nCopies)); + aStream.WriteLine(aLine.makeStringAndClear()); + + if (m_nPDFDevice > 0) + { + aLine.append("collate="); + aLine.append(OString::boolean(m_bCollate)); + aStream.WriteLine(aLine.makeStringAndClear()); + } + + aLine.append("margindajustment="); + 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.makeStringAndClear()); + + aLine.append("colordepth="); + aLine.append(static_cast<sal_Int32>(m_nColorDepth)); + aStream.WriteLine(aLine.makeStringAndClear()); + + aLine.append("pslevel="); + aLine.append(static_cast<sal_Int32>(m_nPSLevel)); + aStream.WriteLine(aLine.makeStringAndClear()); + + aLine.append("pdfdevice="); + aLine.append(static_cast<sal_Int32>(m_nPDFDevice)); + aStream.WriteLine(aLine.makeStringAndClear()); + + aLine.append("colordevice="); + aLine.append(static_cast<sal_Int32>(m_nColorDevice)); + aStream.WriteLine(aLine.makeStringAndClear()); + + // now append the PPDContext stream buffer + aStream.WriteLine( "PPDContexData" ); + 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 margindajustmentEquals[] = "margindajustment="; + 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.copy(RTL_CONSTASCII_LENGTH(printerEquals)), RTL_TEXTENCODING_UTF8); + } + else if (aLine.startsWith(orientatationEquals)) + { + bOrientation = true; + rJobData.m_eOrientation = aLine.copy(RTL_CONSTASCII_LENGTH(orientatationEquals)).equalsIgnoreAsciiCase("landscape") ? orientation::Landscape : orientation::Portrait; + } + else if (aLine.startsWith(copiesEquals)) + { + bCopies = true; + rJobData.m_nCopies = aLine.copy(RTL_CONSTASCII_LENGTH(copiesEquals)).toInt32(); + } + else if (aLine.startsWith(collateEquals)) + { + rJobData.m_bCollate = aLine.copy(RTL_CONSTASCII_LENGTH(collateEquals)).toBoolean(); + } + else if (aLine.startsWith(margindajustmentEquals)) + { + bMargin = true; + sal_Int32 nIdx {RTL_CONSTASCII_LENGTH(margindajustmentEquals)}; + rJobData.m_nLeftMarginAdjust = aLine.getToken(0, ',', nIdx).toInt32(); + rJobData.m_nRightMarginAdjust = aLine.getToken(0, ',', nIdx).toInt32(); + rJobData.m_nTopMarginAdjust = aLine.getToken(0, ',', nIdx).toInt32(); + rJobData.m_nBottomMarginAdjust = aLine.getToken(0, ',', nIdx).toInt32(); + } + else if (aLine.startsWith(colordepthEquals)) + { + bColorDepth = true; + rJobData.m_nColorDepth = aLine.copy(RTL_CONSTASCII_LENGTH(colordepthEquals)).toInt32(); + } + else if (aLine.startsWith(colordeviceEquals)) + { + bColorDevice = true; + rJobData.m_nColorDevice = aLine.copy(RTL_CONSTASCII_LENGTH(colordeviceEquals)).toInt32(); + } + else if (aLine.startsWith(pslevelEquals)) + { + bPSLevel = true; + rJobData.m_nPSLevel = aLine.copy(RTL_CONSTASCII_LENGTH(pslevelEquals)).toInt32(); + } + else if (aLine.startsWith(pdfdeviceEquals)) + { + bPDFDevice = true; + rJobData.m_nPDFDevice = aLine.copy(RTL_CONSTASCII_LENGTH(pdfdeviceEquals)).toInt32(); + } + else if (aLine == "PPDContexData" && 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..d949441db --- /dev/null +++ b/vcl/unx/generic/printer/ppdparser.cxx @@ -0,0 +1,1987 @@ +/* -*- 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 <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/mutex.hxx> +#include <osl/file.hxx> +#include <osl/process.h> +#include <osl/thread.h> +#include <rtl/strbuf.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/instance.hxx> +#include <sal/log.hxx> +#include <salhelper/linkhelper.hxx> + +#include <com/sun/star/lang/Locale.hpp> + +#include <unordered_map> + +#ifdef ENABLE_CUPS +#include <cups/cups.h> +#endif + +#include <config_dbus.h> +#include <config_gio.h> +#include <boost/functional/hash.hpp> + +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; + boost::hash_combine(seed, rLocale.Language.hashCode()); + boost::hash_combine(seed, rLocale.Country.hashCode()); + boost::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::unique_ptr<std::unordered_map< OUString, OUString >> pAllPPDFiles; + }; +} + +using namespace psp; + +namespace +{ + struct thePPDCache : public rtl::Static<PPDCache, 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 */ ) + { + // so let's try to decompress the stream + mpMemStream.reset( new SvMemoryStream( 4096, 4096 ) ); + ZCodec aCodec; + aCodec.BeginCompression( ZCODEC_DEFAULT_COMPRESSION, /*gzLib*/true ); + 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 = thePPDCache::get(); + + osl::Directory aDir( rDir ); + if ( aDir.open() == osl::FileBase::E_None ) + { + 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.pAllPPDFiles)[ 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.pAllPPDFiles ) + return; + + rPPDCache.pAllPPDFiles.reset(new std::unordered_map< OUString, OUString >); + + // 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.pAllPPDFiles->find( OUString( "SGENPRT" ) ) == rPPDCache.pAllPPDFiles->end() ) + { + // 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.pAllPPDFiles->find("SGENPRT") == + rPPDCache.pAllPPDFiles->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 = thePPDCache::get(); + + 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.pAllPPDFiles->find( aBase ); + nLastIndex = aBase.lastIndexOf( '.' ); + if( nLastIndex > 0 ) + aBase = aBase.copy( 0, nLastIndex ); + } while( it == rPPDCache.pAllPPDFiles->end() && nLastIndex > 0 ); + + if( it == rPPDCache.pAllPPDFiles->end() && bRetry ) + { + // a new file ? rehash + rPPDCache.pAllPPDFiles.reset(); + bRetry = false; + // note this is optimized for office start where + // no new files occur and initPPDFiles is called only once + } + } while( ! rPPDCache.pAllPPDFiles ); + + if( it != rPPDCache.pAllPPDFiles->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 ) +{ + static ::osl::Mutex aMutex; + ::osl::Guard< ::osl::Mutex > 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 = thePPDCache::get(); + 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::stripStart(aCurLine, ' '); + aCurLine = comphelper::string::stripEnd(aCurLine, ' '); + aCurLine = comphelper::string::stripStart(aCurLine, '\t'); + aCurLine = comphelper::string::stripEnd(aCurLine, '\t'); + aCurLine = comphelper::string::stripEnd(aCurLine, '\r'); + aCurLine = comphelper::string::stripEnd(aCurLine, '\n'); + aCurLine = comphelper::string::stripStart(aCurLine, '"'); + aCurLine = comphelper::string::stripEnd(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.makeStringAndClear(), 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 const OString 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.copy( 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.copy(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.copy(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.copy(8), RTL_TEXTENCODING_MS_1252)); + sal_Int32 nPos = aKey.indexOf( ':' ); + if( nPos != -1 ) + { + aKey = aKey.copy(0, nPos); + OUString aOption(OStringToOUString( + WhitespaceToSpace(aLine.copy(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, const OString& 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, "" ); + + 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, ""); + 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, "" ); + 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( + const OUString& 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( + const OUString& 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( + const OUString& rString, + int& rXRes, int& rYRes ) +{ + rXRes = rYRes = 300; + + const sal_Int32 nDPIPos {rString.indexOf( "dpi" )}; + if( nDPIPos != -1 ) + { + const sal_Int32 nPos {rString.indexOf( 'x' )}; + if( nPos >=0 ) + { + rXRes = rString.copy( 0, nPos ).toInt32(); + rYRes = rString.copy(nPos+1, nDPIPos - nPos - 1).toInt32(); + } + else + rXRes = rYRes = rString.copy( 0, nDPIPos ).toInt32(); + } +} + +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.copy( 0, nPos ), RTL_TEXTENCODING_MS_1252 ) ); + if( pKey ) + { + const PPDValue* pValue = nullptr; + OUString aOption(OStringToOUString(aLine.copy(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 ) + { + const PPDKey* pKey = m_pParser->getKey( "PageSize" ); + if( pKey ) + { + 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..ff4b5e691 --- /dev/null +++ b/vcl/unx/generic/printer/printerinfomanager.cxx @@ -0,0 +1,900 @@ +/* -*- 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/helper.hxx> + +#include <saldatabasic.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> + +// filename of configuration files +#define PRINT_FILENAME "psprint.conf" +// the group of the global defaults +#define 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() +{ + SalData* pSalData = GetSalData(); + + if( ! pSalData->m_pPIManager ) + { + pSalData->m_pPIManager = CPDManager::tryLoadCPD(); + if( ! pSalData->m_pPIManager ) + pSalData->m_pPIManager = CUPSManager::tryLoadCUPS(); + if( ! pSalData->m_pPIManager ) + pSalData->m_pPIManager = new PrinterInfoManager(); + pSalData->m_pPIManager->initialize(); +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.print", "PrinterInfoManager::get " + << "create Manager of type " + << ((int) pSalData->m_pPIManager->getType())); +#endif + } + + return *pSalData->m_pPIManager; +} + +void PrinterInfoManager::release() +{ + SalData* pSalData = GetSalData(); + delete pSalData->m_pPIManager; + pSalData->m_pPIManager = nullptr; +} + +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 = aValue.getToken(0, ',', nIdx).toInt32(); + m_aGlobalDefaults.m_nRightMarginAdjust = aValue.getToken(0, ',', nIdx).toInt32(); + m_aGlobalDefaults.m_nTopMarginAdjust = aValue.getToken(0, ',', nIdx).toInt32(); + m_aGlobalDefaults.m_nBottomMarginAdjust = aValue.getToken(0, ',', nIdx).toInt32(); + } + + 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.copy(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.copy(nNamePos+1), + RTL_TEXTENCODING_UTF8); + aPrinter.m_aInfo.m_aPrinterName = aPrinterName; + aPrinter.m_aInfo.m_aDriverName = OStringToOUString(aValue.copy(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 = aValue.getToken(0, ',', nIdx).toInt32(); + aPrinter.m_aInfo.m_nRightMarginAdjust = aValue.getToken(0, ',', nIdx).toInt32(); + aPrinter.m_aInfo.m_nTopMarginAdjust = aValue.getToken(0, ',', nIdx).toInt32(); + aPrinter.m_aInfo.m_nBottomMarginAdjust = aValue.getToken(0, ',', nIdx).toInt32(); + } + + 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.copy(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.copy( 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; + } + } + } + } +} + +static 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 + OStringLiteral(" 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: */ |