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