1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <unx/cpdmgr.hxx>
21 #include <unx/cupsmgr.hxx>
22 #include <unx/helper.hxx>
23 
24 #include <saldatabasic.hxx>
25 
26 #include <tools/urlobj.hxx>
27 #include <tools/config.hxx>
28 
29 #include <i18nutil/paper.hxx>
30 #include <rtl/strbuf.hxx>
31 #include <sal/log.hxx>
32 
33 #include <osl/file.hxx>
34 #include <osl/thread.hxx>
35 #include <osl/mutex.hxx>
36 
37 // filename of configuration files
38 #define PRINT_FILENAME  "psprint.conf"
39 // the group of the global defaults
40 #define GLOBAL_DEFAULTS_GROUP "__Global_Printer_Defaults__"
41 
42 #include <unordered_set>
43 
44 using namespace psp;
45 using namespace osl;
46 
47 namespace psp
48 {
49     class SystemQueueInfo final : public Thread
50     {
51         mutable Mutex               m_aMutex;
52         bool                        m_bChanged;
53         std::vector< PrinterInfoManager::SystemPrintQueue >
54                                     m_aQueues;
55         OUString                    m_aCommand;
56 
57         virtual void SAL_CALL run() override;
58 
59         public:
60         SystemQueueInfo();
61         virtual ~SystemQueueInfo() override;
62 
63         bool hasChanged() const;
64         OUString getCommand() const;
65 
66         // sets changed status to false; therefore not const
67         void getSystemQueues( std::vector< PrinterInfoManager::SystemPrintQueue >& rQueues );
68     };
69 } // namespace
70 
71 /*
72 *  class PrinterInfoManager
73 */
74 
get()75 PrinterInfoManager& PrinterInfoManager::get()
76 {
77     SalData* pSalData = GetSalData();
78 
79     if( ! pSalData->m_pPIManager )
80     {
81         pSalData->m_pPIManager = CPDManager::tryLoadCPD();
82         if( ! pSalData->m_pPIManager )
83             pSalData->m_pPIManager = CUPSManager::tryLoadCUPS();
84         if( ! pSalData->m_pPIManager )
85             pSalData->m_pPIManager = new PrinterInfoManager();
86 
87         pSalData->m_pPIManager->initialize();
88 #if OSL_DEBUG_LEVEL > 1
89         fprintf( stderr, "PrinterInfoManager::get create Manager of type %d\n", pSalData->m_pPIManager->getType() );
90 #endif
91     }
92 
93     return *pSalData->m_pPIManager;
94 }
95 
release()96 void PrinterInfoManager::release()
97 {
98     SalData* pSalData = GetSalData();
99     delete pSalData->m_pPIManager;
100     pSalData->m_pPIManager = nullptr;
101 }
102 
PrinterInfoManager(Type eType)103 PrinterInfoManager::PrinterInfoManager( Type eType ) :
104     m_eType( eType ),
105     m_bUseIncludeFeature( false ),
106     m_bUseJobPatch( true ),
107     m_aSystemDefaultPaper( "A4" )
108 {
109     if( eType == Type::Default )
110         m_pQueueInfo.reset( new SystemQueueInfo );
111 
112     m_aSystemDefaultPaper = OStringToOUString(
113         PaperInfo::toPSName(PaperInfo::getSystemDefaultPaper().getPaper()),
114         RTL_TEXTENCODING_UTF8);
115 }
116 
~PrinterInfoManager()117 PrinterInfoManager::~PrinterInfoManager()
118 {
119     #if OSL_DEBUG_LEVEL > 1
120     fprintf( stderr, "PrinterInfoManager: destroyed Manager of type %d\n", getType() );
121     #endif
122 }
123 
checkPrintersChanged(bool bWait)124 bool PrinterInfoManager::checkPrintersChanged( bool bWait )
125 {
126     // check if files were created, deleted or modified since initialize()
127     bool bChanged = false;
128     for (auto const& watchFile : m_aWatchFiles)
129     {
130         DirectoryItem aItem;
131         if( DirectoryItem::get( watchFile.m_aFilePath, aItem ) )
132         {
133             if( watchFile.m_aModified.Seconds != 0 )
134             {
135                 bChanged = true; // file probably has vanished
136                 break;
137             }
138         }
139         else
140         {
141             FileStatus aStatus( osl_FileStatus_Mask_ModifyTime );
142             if( aItem.getFileStatus( aStatus ) )
143             {
144                 bChanged = true; // unlikely but not impossible
145                 break;
146             }
147             else
148             {
149                 TimeValue aModified = aStatus.getModifyTime();
150                 if( aModified.Seconds != watchFile.m_aModified.Seconds )
151                 {
152                     bChanged = true;
153                     break;
154                 }
155             }
156         }
157     }
158 
159     if( bWait && m_pQueueInfo )
160     {
161         #if OSL_DEBUG_LEVEL > 1
162         fprintf( stderr, "syncing printer discovery thread\n" );
163         #endif
164         m_pQueueInfo->join();
165         #if OSL_DEBUG_LEVEL > 1
166         fprintf( stderr, "done: syncing printer discovery thread\n" );
167         #endif
168     }
169 
170     if( ! bChanged && m_pQueueInfo )
171         bChanged = m_pQueueInfo->hasChanged();
172     if( bChanged )
173     {
174         initialize();
175     }
176 
177     return bChanged;
178 }
179 
initialize()180 void PrinterInfoManager::initialize()
181 {
182     m_bUseIncludeFeature = false;
183     m_aPrinters.clear();
184     m_aWatchFiles.clear();
185     OUString aDefaultPrinter;
186 
187     // first initialize the global defaults
188     // have to iterate over all possible files
189     // there should be only one global setup section in all
190     // available config files
191     m_aGlobalDefaults = PrinterInfo();
192 
193     // need a parser for the PPDContext. generic printer should do.
194     m_aGlobalDefaults.m_pParser = PPDParser::getParser( "SGENPRT" );
195     m_aGlobalDefaults.m_aContext.setParser( m_aGlobalDefaults.m_pParser );
196 
197     if( ! m_aGlobalDefaults.m_pParser )
198     {
199         #if OSL_DEBUG_LEVEL > 1
200         fprintf( stderr, "Error: no default PPD file SGENPRT available, shutting down psprint...\n" );
201         #endif
202         return;
203     }
204 
205     std::vector< OUString > aDirList;
206     psp::getPrinterPathList( aDirList, nullptr );
207     for (auto const& printDir : aDirList)
208     {
209         INetURLObject aFile( printDir, INetProtocol::File, INetURLObject::EncodeMechanism::All );
210         aFile.Append( PRINT_FILENAME );
211         Config aConfig( aFile.PathToFileName() );
212         if( aConfig.HasGroup( GLOBAL_DEFAULTS_GROUP ) )
213         {
214             #if OSL_DEBUG_LEVEL > 1
215             fprintf( stderr, "found global defaults in %s\n", OUStringToOString( aFile.PathToFileName(), RTL_TEXTENCODING_ISO_8859_1 ).getStr() );
216             #endif
217             aConfig.SetGroup( GLOBAL_DEFAULTS_GROUP );
218 
219             OString aValue( aConfig.ReadKey( "Copies" ) );
220             if (!aValue.isEmpty())
221                 m_aGlobalDefaults.m_nCopies = aValue.toInt32();
222 
223             aValue = aConfig.ReadKey( "Orientation" );
224             if (!aValue.isEmpty())
225                 m_aGlobalDefaults.m_eOrientation = aValue.equalsIgnoreAsciiCase("Landscape") ? orientation::Landscape : orientation::Portrait;
226 
227             aValue = aConfig.ReadKey( "MarginAdjust" );
228             if (!aValue.isEmpty())
229             {
230                 sal_Int32 nIdx {0};
231                 m_aGlobalDefaults.m_nLeftMarginAdjust = aValue.getToken(0, ',', nIdx).toInt32();
232                 m_aGlobalDefaults.m_nRightMarginAdjust  = aValue.getToken(0, ',', nIdx).toInt32();
233                 m_aGlobalDefaults.m_nTopMarginAdjust = aValue.getToken(0, ',', nIdx).toInt32();
234                 m_aGlobalDefaults.m_nBottomMarginAdjust = aValue.getToken(0, ',', nIdx).toInt32();
235             }
236 
237             aValue = aConfig.ReadKey( "ColorDepth", "24" );
238             if (!aValue.isEmpty())
239                 m_aGlobalDefaults.m_nColorDepth = aValue.toInt32();
240 
241             aValue = aConfig.ReadKey( "ColorDevice" );
242             if (!aValue.isEmpty())
243                 m_aGlobalDefaults.m_nColorDevice = aValue.toInt32();
244 
245             aValue = aConfig.ReadKey( "PSLevel" );
246             if (!aValue.isEmpty())
247                 m_aGlobalDefaults.m_nPSLevel = aValue.toInt32();
248 
249             aValue = aConfig.ReadKey( "PDFDevice" );
250             if (!aValue.isEmpty())
251                 m_aGlobalDefaults.m_nPDFDevice = aValue.toInt32();
252 
253             // get the PPDContext of global JobData
254             for( int nKey = 0; nKey < aConfig.GetKeyCount(); ++nKey )
255             {
256                 OString aKey( aConfig.GetKeyName( nKey ) );
257                 if (aKey.startsWith("PPD_"))
258                 {
259                     aValue = aConfig.ReadKey( aKey );
260                     const PPDKey* pKey = m_aGlobalDefaults.m_pParser->getKey(OStringToOUString(aKey.copy(4), RTL_TEXTENCODING_ISO_8859_1));
261                     if( pKey )
262                     {
263                         m_aGlobalDefaults.m_aContext.
264                         setValue( pKey,
265                                   aValue == "*nil" ? nullptr : pKey->getValue(OStringToOUString(aValue, RTL_TEXTENCODING_ISO_8859_1)),
266                                   true );
267                     }
268                 }
269             }
270         }
271     }
272     setDefaultPaper( m_aGlobalDefaults.m_aContext );
273 
274     // now collect all available printers
275     for (auto const& printDir : aDirList)
276     {
277         INetURLObject aDir( printDir, INetProtocol::File, INetURLObject::EncodeMechanism::All );
278         INetURLObject aFile( aDir );
279         aFile.Append( PRINT_FILENAME );
280 
281         // check directory validity
282         OUString aUniPath;
283         FileBase::getFileURLFromSystemPath( aDir.PathToFileName(), aUniPath );
284         Directory aDirectory( aUniPath );
285         if( aDirectory.open() )
286             continue;
287         aDirectory.close();
288 
289         FileBase::getFileURLFromSystemPath( aFile.PathToFileName(), aUniPath );
290         FileStatus aStatus( osl_FileStatus_Mask_ModifyTime );
291         DirectoryItem aItem;
292 
293         // setup WatchFile list
294         WatchFile aWatchFile;
295         aWatchFile.m_aFilePath = aUniPath;
296         if( ! DirectoryItem::get( aUniPath, aItem ) &&
297             ! aItem.getFileStatus( aStatus ) )
298         {
299             aWatchFile.m_aModified = aStatus.getModifyTime();
300         }
301         else
302         {
303             aWatchFile.m_aModified.Seconds = 0;
304             aWatchFile.m_aModified.Nanosec = 0;
305         }
306         m_aWatchFiles.push_back( aWatchFile );
307 
308         Config aConfig( aFile.PathToFileName() );
309         for( int nGroup = 0; nGroup < aConfig.GetGroupCount(); nGroup++ )
310         {
311             aConfig.SetGroup( aConfig.GetGroupName( nGroup ) );
312             OString aValue = aConfig.ReadKey( "Printer" );
313             if (!aValue.isEmpty())
314             {
315                 OUString aPrinterName;
316 
317                 sal_Int32 nNamePos = aValue.indexOf('/');
318                 // check for valid value of "Printer"
319                 if (nNamePos == -1)
320                     continue;
321 
322                 Printer aPrinter;
323                 // initialize to global defaults
324                 aPrinter.m_aInfo = m_aGlobalDefaults;
325 
326                 aPrinterName = OStringToOUString(aValue.copy(nNamePos+1),
327                     RTL_TEXTENCODING_UTF8);
328                 aPrinter.m_aInfo.m_aPrinterName = aPrinterName;
329                 aPrinter.m_aInfo.m_aDriverName = OStringToOUString(aValue.copy(0, nNamePos), RTL_TEXTENCODING_UTF8);
330 
331                 // set parser, merge settings
332                 // don't do this for CUPS printers as this is done
333                 // by the CUPS system itself
334                 if( !aPrinter.m_aInfo.m_aDriverName.startsWith( "CUPS:" ) )
335                 {
336                     aPrinter.m_aInfo.m_pParser          = PPDParser::getParser( aPrinter.m_aInfo.m_aDriverName );
337                     aPrinter.m_aInfo.m_aContext.setParser( aPrinter.m_aInfo.m_pParser );
338                     // note: setParser also purges the context
339 
340                     // ignore this printer if its driver is not found
341                     if( ! aPrinter.m_aInfo.m_pParser )
342                         continue;
343 
344                     // merge the ppd context keys if the printer has the same keys and values
345                     // this is a bit tricky, since it involves mixing two PPDs
346                     // without constraints which might end up badly
347                     // this feature should be use with caution
348                     // it is mainly to select default paper sizes for new printers
349                     for( int nPPDValueModified = 0; nPPDValueModified < m_aGlobalDefaults.m_aContext.countValuesModified(); nPPDValueModified++ )
350                     {
351                         const PPDKey* pDefKey = m_aGlobalDefaults.m_aContext.getModifiedKey( nPPDValueModified );
352                         const PPDValue* pDefValue = m_aGlobalDefaults.m_aContext.getValue( pDefKey );
353                         const PPDKey* pPrinterKey = pDefKey ? aPrinter.m_aInfo.m_pParser->getKey( pDefKey->getKey() ) : nullptr;
354                         if( pDefKey && pPrinterKey )
355                             // at least the options exist in both PPDs
356                         {
357                             if( pDefValue )
358                             {
359                                 const PPDValue* pPrinterValue = pPrinterKey->getValue( pDefValue->m_aOption );
360                                 if( pPrinterValue )
361                                     // the printer has a corresponding option for the key
362                                     aPrinter.m_aInfo.m_aContext.setValue( pPrinterKey, pPrinterValue );
363                             }
364                             else
365                                 aPrinter.m_aInfo.m_aContext.setValue( pPrinterKey, nullptr );
366                         }
367                     }
368 
369                     aValue = aConfig.ReadKey( "Command" );
370                     // no printer without a command
371                     if (aValue.isEmpty())
372                     {
373                         /*  TODO:
374                         *  porters: please append your platform to the Solaris
375                         *  case if your platform has SystemV printing per default.
376                         */
377                         #if defined __sun
378                         aValue = "lp";
379                         #else
380                         aValue = "lpr";
381                         #endif
382                     }
383                     aPrinter.m_aInfo.m_aCommand = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8);
384                 }
385 
386                 aValue = aConfig.ReadKey( "QuickCommand" );
387                 aPrinter.m_aInfo.m_aQuickCommand = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8);
388 
389                 aValue = aConfig.ReadKey( "Features" );
390                 aPrinter.m_aInfo.m_aFeatures = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8);
391 
392                 // override the settings in m_aGlobalDefaults if keys exist
393                 aValue = aConfig.ReadKey( "DefaultPrinter" );
394                 if (aValue != "0" && !aValue.equalsIgnoreAsciiCase("false"))
395                     aDefaultPrinter = aPrinterName;
396 
397                 aValue = aConfig.ReadKey( "Location" );
398                 aPrinter.m_aInfo.m_aLocation = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8);
399 
400                 aValue = aConfig.ReadKey( "Comment" );
401                 aPrinter.m_aInfo.m_aComment = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8);
402 
403                 aValue = aConfig.ReadKey( "Copies" );
404                 if (!aValue.isEmpty())
405                     aPrinter.m_aInfo.m_nCopies = aValue.toInt32();
406 
407                 aValue = aConfig.ReadKey( "Orientation" );
408                 if (!aValue.isEmpty())
409                     aPrinter.m_aInfo.m_eOrientation = aValue.equalsIgnoreAsciiCase("Landscape") ? orientation::Landscape : orientation::Portrait;
410 
411                 aValue = aConfig.ReadKey( "MarginAdjust" );
412                 if (!aValue.isEmpty())
413                 {
414                     sal_Int32 nIdx {0};
415                     aPrinter.m_aInfo.m_nLeftMarginAdjust = aValue.getToken(0, ',', nIdx).toInt32();
416                     aPrinter.m_aInfo.m_nRightMarginAdjust = aValue.getToken(0, ',', nIdx).toInt32();
417                     aPrinter.m_aInfo.m_nTopMarginAdjust = aValue.getToken(0, ',', nIdx).toInt32();
418                     aPrinter.m_aInfo.m_nBottomMarginAdjust = aValue.getToken(0, ',', nIdx).toInt32();
419                 }
420 
421                 aValue = aConfig.ReadKey( "ColorDepth" );
422                 if (!aValue.isEmpty())
423                     aPrinter.m_aInfo.m_nColorDepth = aValue.toInt32();
424 
425                 aValue = aConfig.ReadKey( "ColorDevice" );
426                 if (!aValue.isEmpty())
427                     aPrinter.m_aInfo.m_nColorDevice = aValue.toInt32();
428 
429                 aValue = aConfig.ReadKey( "PSLevel" );
430                 if (!aValue.isEmpty())
431                     aPrinter.m_aInfo.m_nPSLevel = aValue.toInt32();
432 
433                 aValue = aConfig.ReadKey( "PDFDevice" );
434                 if (!aValue.isEmpty())
435                     aPrinter.m_aInfo.m_nPDFDevice = aValue.toInt32();
436 
437                 // now iterate over all keys to extract multi key information:
438                 // 1. PPDContext information
439                 for( int nKey = 0; nKey < aConfig.GetKeyCount(); ++nKey )
440                 {
441                     OString aKey( aConfig.GetKeyName( nKey ) );
442                     if( aKey.startsWith("PPD_") && aPrinter.m_aInfo.m_pParser )
443                     {
444                         aValue = aConfig.ReadKey( aKey );
445                         const PPDKey* pKey = aPrinter.m_aInfo.m_pParser->getKey(OStringToOUString(aKey.copy(4), RTL_TEXTENCODING_ISO_8859_1));
446                         if( pKey )
447                         {
448                             aPrinter.m_aInfo.m_aContext.
449                             setValue( pKey,
450                                       aValue == "*nil" ? nullptr : pKey->getValue(OStringToOUString(aValue, RTL_TEXTENCODING_ISO_8859_1)),
451                                       true );
452                         }
453                     }
454                 }
455 
456                 setDefaultPaper( aPrinter.m_aInfo.m_aContext );
457 
458                 // if it's a "Generic Printer", apply defaults from config...
459                 aPrinter.m_aInfo.resolveDefaultBackend();
460 
461                 // finally insert printer
462                 FileBase::getFileURLFromSystemPath( aFile.PathToFileName(), aPrinter.m_aFile );
463                 std::unordered_map< OUString, Printer >::const_iterator find_it =
464                 m_aPrinters.find( aPrinterName );
465                 if( find_it != m_aPrinters.end() )
466                 {
467                     aPrinter.m_aAlternateFiles = find_it->second.m_aAlternateFiles;
468                     aPrinter.m_aAlternateFiles.insert( find_it->second.m_aFile );
469                 }
470                 m_aPrinters[ aPrinterName ] = aPrinter;
471             }
472         }
473     }
474 
475     // set default printer
476     if( !m_aPrinters.empty() )
477     {
478         if( m_aPrinters.find( aDefaultPrinter ) == m_aPrinters.end() )
479             aDefaultPrinter = m_aPrinters.begin()->first;
480     }
481     else
482         aDefaultPrinter.clear();
483     m_aDefaultPrinter = aDefaultPrinter;
484 
485     if( m_eType != Type::Default )
486         return;
487 
488     // add a default printer for every available print queue
489     // merge paper default printer, all else from global defaults
490     PrinterInfo aMergeInfo( m_aGlobalDefaults );
491     aMergeInfo.m_aDriverName    = "SGENPRT";
492     aMergeInfo.m_aFeatures      = "autoqueue";
493 
494     if( !m_aDefaultPrinter.isEmpty() )
495     {
496         PrinterInfo aDefaultInfo( getPrinterInfo( m_aDefaultPrinter ) );
497 
498         const PPDKey* pDefKey           = aDefaultInfo.m_pParser->getKey( "PageSize" );
499         const PPDKey* pMergeKey         = aMergeInfo.m_pParser->getKey( "PageSize" );
500         const PPDValue* pDefValue       = aDefaultInfo.m_aContext.getValue( pDefKey );
501         const PPDValue* pMergeValue     = pMergeKey ? pMergeKey->getValue( pDefValue->m_aOption ) : nullptr;
502         if( pMergeKey && pMergeValue )
503             aMergeInfo.m_aContext.setValue( pMergeKey, pMergeValue );
504     }
505 
506     if( m_pQueueInfo && m_pQueueInfo->hasChanged() )
507     {
508         m_aSystemPrintCommand = m_pQueueInfo->getCommand();
509         m_pQueueInfo->getSystemQueues( m_aSystemPrintQueues );
510         m_pQueueInfo.reset();
511     }
512     for (auto const& printQueue : m_aSystemPrintQueues)
513     {
514         OUString aPrinterName = "<" + printQueue.m_aQueue + ">";
515 
516         if( m_aPrinters.find( aPrinterName ) != m_aPrinters.end() )
517             // probably user made this one permanent
518             continue;
519 
520         OUString aCmd( m_aSystemPrintCommand );
521         aCmd = aCmd.replaceAll( "(PRINTER)", printQueue.m_aQueue );
522 
523         Printer aPrinter;
524 
525         // initialize to merged defaults
526         aPrinter.m_aInfo = aMergeInfo;
527         aPrinter.m_aInfo.m_aPrinterName     = aPrinterName;
528         aPrinter.m_aInfo.m_aCommand         = aCmd;
529         aPrinter.m_aInfo.m_aComment         = printQueue.m_aComment;
530         aPrinter.m_aInfo.m_aLocation        = printQueue.m_aLocation;
531 
532         m_aPrinters[ aPrinterName ] = aPrinter;
533     }
534 }
535 
listPrinters(::std::vector<OUString> & rVector) const536 void PrinterInfoManager::listPrinters( ::std::vector< OUString >& rVector ) const
537 {
538     rVector.clear();
539     for (auto const& printer : m_aPrinters)
540         rVector.push_back(printer.first);
541 }
542 
getPrinterInfo(const OUString & rPrinter) const543 const PrinterInfo& PrinterInfoManager::getPrinterInfo( const OUString& rPrinter ) const
544 {
545     static PrinterInfo aEmptyInfo;
546     std::unordered_map< OUString, Printer >::const_iterator it = m_aPrinters.find( rPrinter );
547 
548     SAL_WARN_IF( it == m_aPrinters.end(), "vcl", "Do not ask for info about nonexistent printers" );
549 
550     return it != m_aPrinters.end() ? it->second.m_aInfo : aEmptyInfo;
551 }
552 
checkFeatureToken(const OUString & rPrinterName,const char * pToken) const553 bool PrinterInfoManager::checkFeatureToken( const OUString& rPrinterName, const char* pToken ) const
554 {
555     const PrinterInfo& rPrinterInfo( getPrinterInfo( rPrinterName ) );
556     sal_Int32 nIndex = 0;
557     while( nIndex != -1 )
558     {
559         OUString aOuterToken = rPrinterInfo.m_aFeatures.getToken( 0, ',', nIndex );
560         if( aOuterToken.getToken( 0, '=' ).equalsIgnoreAsciiCaseAscii( pToken ) )
561             return true;
562     }
563     return false;
564 }
565 
startSpool(const OUString & rPrintername,bool bQuickCommand)566 FILE* PrinterInfoManager::startSpool( const OUString& rPrintername, bool bQuickCommand )
567 {
568     const PrinterInfo&   rPrinterInfo   = getPrinterInfo (rPrintername);
569     const OUString& rCommand       = (bQuickCommand && !rPrinterInfo.m_aQuickCommand.isEmpty() ) ?
570                                           rPrinterInfo.m_aQuickCommand : rPrinterInfo.m_aCommand;
571     OString aShellCommand  = OUStringToOString (rCommand, RTL_TEXTENCODING_ISO_8859_1) +
572         " 2>/dev/null";
573 
574     return popen (aShellCommand.getStr(), "w");
575 }
576 
endSpool(const OUString &,const OUString &,FILE * pFile,const JobData &,bool,const OUString &)577 bool PrinterInfoManager::endSpool( const OUString& /*rPrintername*/, const OUString& /*rJobTitle*/, FILE* pFile, const JobData& /*rDocumentJobData*/, bool /*bBanner*/, const OUString& /*rFaxNumber*/ )
578 {
579     return (0 == pclose( pFile ));
580 }
581 
setupJobContextData(JobData & rData)582 void PrinterInfoManager::setupJobContextData( JobData& rData )
583 {
584     std::unordered_map< OUString, Printer >::iterator it =
585     m_aPrinters.find( rData.m_aPrinterName );
586     if( it != m_aPrinters.end() )
587     {
588         rData.m_pParser     = it->second.m_aInfo.m_pParser;
589         rData.m_aContext    = it->second.m_aInfo.m_aContext;
590     }
591 }
592 
setDefaultPaper(PPDContext & rContext) const593 void PrinterInfoManager::setDefaultPaper( PPDContext& rContext ) const
594 {
595     if(  ! rContext.getParser() )
596         return;
597 
598     const PPDKey* pPageSizeKey = rContext.getParser()->getKey( "PageSize" );
599     if( ! pPageSizeKey )
600         return;
601 
602     int nModified = rContext.countValuesModified();
603     while( nModified-- &&
604         rContext.getModifiedKey( nModified ) != pPageSizeKey )
605         ;
606 
607     if( nModified >= 0 ) // paper was set already, do not modify
608     {
609         #if OSL_DEBUG_LEVEL > 1
610         fprintf( stderr, "not setting default paper, already set %s\n",
611                  OUStringToOString( rContext.getValue( pPageSizeKey )->m_aOption, RTL_TEXTENCODING_ISO_8859_1 ).getStr() );
612         #endif
613         return;
614     }
615 
616     // paper not set, fill in default value
617     const PPDValue* pPaperVal = nullptr;
618     int nValues = pPageSizeKey->countValues();
619     for( int i = 0; i < nValues && ! pPaperVal; i++ )
620     {
621         const PPDValue* pVal = pPageSizeKey->getValue( i );
622         if( pVal->m_aOption.equalsIgnoreAsciiCase( m_aSystemDefaultPaper ) )
623             pPaperVal = pVal;
624     }
625     if( pPaperVal )
626     {
627         #if OSL_DEBUG_LEVEL > 1
628         fprintf( stderr, "setting default paper %s\n", OUStringToOString( pPaperVal->m_aOption, RTL_TEXTENCODING_ISO_8859_1 ).getStr() );
629         #endif
630         rContext.setValue( pPageSizeKey, pPaperVal );
631         #if OSL_DEBUG_LEVEL > 1
632         pPaperVal = rContext.getValue( pPageSizeKey );
633         fprintf( stderr, "-> got paper %s\n", OUStringToOString( pPaperVal->m_aOption, RTL_TEXTENCODING_ISO_8859_1 ).getStr() );
634         #endif
635     }
636 }
637 
SystemQueueInfo()638 SystemQueueInfo::SystemQueueInfo() :
639     m_bChanged( false )
640 {
641     create();
642 }
643 
~SystemQueueInfo()644 SystemQueueInfo::~SystemQueueInfo()
645 {
646     static const char* pNoSyncDetection = getenv( "SAL_DISABLE_SYNCHRONOUS_PRINTER_DETECTION" );
647     if( ! pNoSyncDetection || !*pNoSyncDetection )
648         join();
649     else
650         terminate();
651 }
652 
hasChanged() const653 bool SystemQueueInfo::hasChanged() const
654 {
655     MutexGuard aGuard( m_aMutex );
656     bool bChanged = m_bChanged;
657     return bChanged;
658 }
659 
getSystemQueues(std::vector<PrinterInfoManager::SystemPrintQueue> & rQueues)660 void SystemQueueInfo::getSystemQueues( std::vector< PrinterInfoManager::SystemPrintQueue >& rQueues )
661 {
662     MutexGuard aGuard( m_aMutex );
663     rQueues = m_aQueues;
664     m_bChanged = false;
665 }
666 
getCommand() const667 OUString SystemQueueInfo::getCommand() const
668 {
669     MutexGuard aGuard( m_aMutex );
670     OUString aRet = m_aCommand;
671     return aRet;
672 }
673 
674 struct SystemCommandParameters;
675 typedef void(* tokenHandler)(const std::vector< OString >&,
676                 std::vector< PrinterInfoManager::SystemPrintQueue >&,
677                 const SystemCommandParameters*);
678 
679 struct SystemCommandParameters
680 {
681     const char*     pQueueCommand;
682     const char*     pPrintCommand;
683     const char*     pForeToken;
684     const char*     pAftToken;
685     unsigned int const    nForeTokenCount;
686     tokenHandler const    pHandler;
687 };
688 
689 #if ! (defined(LINUX) || defined(NETBSD) || defined(DRAGONFLY) || defined(OPENBSD))
lpgetSysQueueTokenHandler(const std::vector<OString> & i_rLines,std::vector<PrinterInfoManager::SystemPrintQueue> & o_rQueues,const SystemCommandParameters *)690 static void lpgetSysQueueTokenHandler(
691     const std::vector< OString >& i_rLines,
692     std::vector< PrinterInfoManager::SystemPrintQueue >& o_rQueues,
693     const SystemCommandParameters* )
694 {
695     rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
696     std::unordered_set< OUString > aUniqueSet;
697     std::unordered_set< OUString > aOnlySet;
698     aUniqueSet.insert( OUString( "_all" ) );
699     aUniqueSet.insert( OUString( "_default" ) );
700 
701     // the eventual "all" attribute of the "_all" queue tells us, which
702     // printers are to be used for this user at all
703 
704     // find _all: line
705     OString aAllLine( "_all:" );
706     OString aAllAttr( "all=" );
707     auto it = std::find_if(i_rLines.begin(), i_rLines.end(),
708         [&aAllLine](const OString& rLine) { return rLine.indexOf( aAllLine, 0 ) == 0; });
709     if( it != i_rLines.end() )
710     {
711         // now find the "all" attribute
712         ++it;
713         it = std::find_if(it, i_rLines.end(),
714             [&aAllAttr](const OString& rLine) { return WhitespaceToSpace( rLine ).startsWith( aAllAttr ); });
715         if( it != i_rLines.end() )
716         {
717             // insert the comma separated entries into the set of printers to use
718             OString aClean( WhitespaceToSpace( *it ) );
719             sal_Int32 nPos = aAllAttr.getLength();
720             while( nPos != -1 )
721             {
722                 OString aTok( aClean.getToken( 0, ',', nPos ) );
723                 if( !aTok.isEmpty() )
724                     aOnlySet.insert( OStringToOUString( aTok, aEncoding ) );
725             }
726         }
727     }
728 
729     bool bInsertAttribute = false;
730     OString aDescrStr( "description=" );
731     OString aLocStr( "location=" );
732     for (auto const& line : i_rLines)
733     {
734         sal_Int32 nPos = 0;
735         // find the begin of a new printer section
736         nPos = line.indexOf( ':', 0 );
737         if( nPos != -1 )
738         {
739             OUString aSysQueue( OStringToOUString( line.copy( 0, nPos ), aEncoding ) );
740             // do not insert duplicates (e.g. lpstat tends to produce such lines)
741             // in case there was a "_all" section, insert only those printer explicitly
742             // set in the "all" attribute
743             if( aUniqueSet.find( aSysQueue ) == aUniqueSet.end() &&
744                 ( aOnlySet.empty() || aOnlySet.find( aSysQueue ) != aOnlySet.end() )
745                 )
746             {
747                 o_rQueues.push_back( PrinterInfoManager::SystemPrintQueue() );
748                 o_rQueues.back().m_aQueue = aSysQueue;
749                 o_rQueues.back().m_aLocation = aSysQueue;
750                 aUniqueSet.insert( aSysQueue );
751                 bInsertAttribute = true;
752             }
753             else
754                 bInsertAttribute = false;
755             continue;
756         }
757         if( bInsertAttribute && ! o_rQueues.empty() )
758         {
759             // look for "description" attribute, insert as comment
760             nPos = line.indexOf( aDescrStr, 0 );
761             if( nPos != -1 )
762             {
763                 OString aComment( WhitespaceToSpace( line.copy(nPos+12) ) );
764                 if( !aComment.isEmpty() )
765                     o_rQueues.back().m_aComment = OStringToOUString(aComment, aEncoding);
766                 continue;
767             }
768             // look for "location" attribute, inser as location
769             nPos = line.indexOf( aLocStr, 0 );
770             if( nPos != -1 )
771             {
772                 OString aLoc( WhitespaceToSpace( line.copy(nPos+9) ) );
773                 if( !aLoc.isEmpty() )
774                     o_rQueues.back().m_aLocation = OStringToOUString(aLoc, aEncoding);
775                 continue;
776             }
777         }
778     }
779 }
780 #endif
standardSysQueueTokenHandler(const std::vector<OString> & i_rLines,std::vector<PrinterInfoManager::SystemPrintQueue> & o_rQueues,const SystemCommandParameters * i_pParms)781 static void standardSysQueueTokenHandler(
782     const std::vector< OString >& i_rLines,
783     std::vector< PrinterInfoManager::SystemPrintQueue >& o_rQueues,
784     const SystemCommandParameters* i_pParms)
785 {
786     rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
787     std::unordered_set< OUString > aUniqueSet;
788     OString aForeToken( i_pParms->pForeToken );
789     OString aAftToken( i_pParms->pAftToken );
790     /* Normal Unix print queue discovery, also used for Darwin 5 LPR printing
791     */
792     for (auto const& line : i_rLines)
793     {
794         sal_Int32 nPos = 0;
795 
796         // search for a line describing a printer:
797         // find if there are enough tokens before the name
798         for( unsigned int i = 0; i < i_pParms->nForeTokenCount && nPos != -1; i++ )
799         {
800             nPos = line.indexOf( aForeToken, nPos );
801             if( nPos != -1 && line.getLength() >= nPos+aForeToken.getLength() )
802                 nPos += aForeToken.getLength();
803         }
804         if( nPos != -1 )
805         {
806             // find if there is the token after the queue
807             sal_Int32 nAftPos = line.indexOf( aAftToken, nPos );
808             if( nAftPos != -1 )
809             {
810                 // get the queue name between fore and aft tokens
811                 OUString aSysQueue( OStringToOUString( line.copy( nPos, nAftPos - nPos ), aEncoding ) );
812                 // do not insert duplicates (e.g. lpstat tends to produce such lines)
813                 if( aUniqueSet.insert( aSysQueue ).second )
814                 {
815                     o_rQueues.emplace_back( );
816                     o_rQueues.back().m_aQueue = aSysQueue;
817                     o_rQueues.back().m_aLocation = aSysQueue;
818                 }
819             }
820         }
821     }
822 }
823 
824 static const struct SystemCommandParameters aParms[] =
825 {
826     #if defined(LINUX) || defined(NETBSD) || defined(DRAGONFLY) || defined(OPENBSD)
827     { "/usr/sbin/lpc status", "lpr -P \"(PRINTER)\"", "", ":", 0, standardSysQueueTokenHandler },
828     { "lpc status", "lpr -P \"(PRINTER)\"", "", ":", 0, standardSysQueueTokenHandler },
829     { "LANG=C;LC_ALL=C;export LANG LC_ALL;lpstat -s", "lp -d \"(PRINTER)\"", "system for ", ": ", 1, standardSysQueueTokenHandler }
830     #else
831     { "LANG=C;LC_ALL=C;export LANG LC_ALL;lpget list", "lp -d \"(PRINTER)\"", "", ":", 0, lpgetSysQueueTokenHandler },
832     { "LANG=C;LC_ALL=C;export LANG LC_ALL;lpstat -s", "lp -d \"(PRINTER)\"", "system for ", ": ", 1, standardSysQueueTokenHandler },
833     { "/usr/sbin/lpc status", "lpr -P \"(PRINTER)\"", "", ":", 0, standardSysQueueTokenHandler },
834     { "lpc status", "lpr -P \"(PRINTER)\"", "", ":", 0, standardSysQueueTokenHandler }
835     #endif
836 };
837 
run()838 void SystemQueueInfo::run()
839 {
840     osl_setThreadName("LPR psp::SystemQueueInfo");
841 
842     char pBuffer[1024];
843     std::vector< OString > aLines;
844 
845     /* Discover which command we can use to get a list of all printer queues */
846     for(const auto & rParm : aParms)
847     {
848         aLines.clear();
849         #if OSL_DEBUG_LEVEL > 1
850         fprintf( stderr, "trying print queue command \"%s\" ... ", rParm.pQueueCommand );
851         #endif
852         OString aCmdLine = rParm.pQueueCommand + OStringLiteral(" 2>/dev/null");
853         FILE *pPipe;
854         if( (pPipe = popen( aCmdLine.getStr(), "r" )) )
855         {
856             while( fgets( pBuffer, 1024, pPipe ) )
857                 aLines.emplace_back( pBuffer );
858             if( ! pclose( pPipe ) )
859             {
860                 std::vector< PrinterInfoManager::SystemPrintQueue > aSysPrintQueues;
861                 rParm.pHandler( aLines, aSysPrintQueues, &rParm );
862                 MutexGuard aGuard( m_aMutex );
863                 m_bChanged  = true;
864                 m_aQueues   = aSysPrintQueues;
865                 m_aCommand  = OUString::createFromAscii( rParm.pPrintCommand );
866                 #if OSL_DEBUG_LEVEL > 1
867                 fprintf( stderr, "success\n" );
868                 #endif
869                 break;
870             }
871         }
872         #if OSL_DEBUG_LEVEL > 1
873         fprintf( stderr, "failed\n" );
874         #endif
875     }
876 }
877 
878 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
879