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 <cups/cups.h>
21 #include <cups/http.h>
22 #include <cups/ipp.h>
23 #include <cups/ppd.h>
24 
25 #include <unistd.h>
26 
27 #include <unx/cupsmgr.hxx>
28 
29 #include <osl/thread.h>
30 #include <osl/diagnose.h>
31 #include <osl/conditn.hxx>
32 
33 #include <rtl/ustrbuf.hxx>
34 
35 #include <officecfg/Office/Common.hxx>
36 
37 #include <vcl/button.hxx>
38 #include <vcl/dialog.hxx>
39 #include <vcl/fixed.hxx>
40 
41 #include <algorithm>
42 
43 using namespace psp;
44 using namespace osl;
45 
46 struct GetPPDAttribs
47 {
48     osl::Condition      m_aCondition;
49     OString             m_aParameter;
50     OString             m_aResult;
51     int                 m_nRefs;
52     bool*               m_pResetRunning;
53     osl::Mutex*         m_pSyncMutex;
54 
GetPPDAttribsGetPPDAttribs55     GetPPDAttribs( const char * m_pParameter,
56                    bool* pResetRunning, osl::Mutex* pSyncMutex )
57             : m_aParameter( m_pParameter ),
58               m_pResetRunning( pResetRunning ),
59               m_pSyncMutex( pSyncMutex )
60     {
61         m_nRefs = 2;
62         m_aCondition.reset();
63     }
64 
~GetPPDAttribsGetPPDAttribs65     ~GetPPDAttribs()
66     {
67         if( !m_aResult.isEmpty() )
68             unlink( m_aResult.getStr() );
69     }
70 
unrefGetPPDAttribs71     void unref()
72     {
73         if( --m_nRefs == 0 )
74         {
75             *m_pResetRunning = false;
76             delete this;
77         }
78     }
79 
executeCallGetPPDAttribs80     void executeCall()
81     {
82         // This CUPS method is not at all thread-safe we need
83         // to dup the pointer to a static buffer it returns ASAP
84         OString aResult = cupsGetPPD(m_aParameter.getStr());
85         MutexGuard aGuard( *m_pSyncMutex );
86         m_aResult = aResult;
87         m_aCondition.set();
88         unref();
89     }
90 
waitResultGetPPDAttribs91     OString waitResult( TimeValue const *pDelay )
92     {
93         m_pSyncMutex->release();
94 
95         if (m_aCondition.wait( pDelay ) != Condition::result_ok
96             )
97         {
98             SAL_WARN("vcl.unx.print",
99                     "cupsGetPPD " << m_aParameter << " timed out");
100         }
101         m_pSyncMutex->acquire();
102 
103         OString aRetval = m_aResult;
104         m_aResult.clear();
105         unref();
106 
107         return aRetval;
108     }
109 };
110 
111 extern "C" {
getPPDWorker(void* pData)112     static void getPPDWorker(void* pData)
113     {
114         osl_setThreadName("CUPSManager getPPDWorker");
115         GetPPDAttribs* pAttribs = static_cast<GetPPDAttribs*>(pData);
116         pAttribs->executeCall();
117     }
118 }
119 
threadedCupsGetPPD( const char* pPrinter )120 OString CUPSManager::threadedCupsGetPPD( const char* pPrinter )
121 {
122     OString aResult;
123 
124     m_aGetPPDMutex.acquire();
125     // if one thread hangs in cupsGetPPD already, don't start another
126     if( ! m_bPPDThreadRunning )
127     {
128         m_bPPDThreadRunning = true;
129         GetPPDAttribs* pAttribs = new GetPPDAttribs( pPrinter,
130                                                      &m_bPPDThreadRunning,
131                                                      &m_aGetPPDMutex );
132 
133         oslThread aThread = osl_createThread( getPPDWorker, pAttribs );
134 
135         TimeValue aValue;
136         aValue.Seconds = 5;
137         aValue.Nanosec = 0;
138 
139         // NOTE: waitResult release and acquires the GetPPD mutex
140         aResult = pAttribs->waitResult( &aValue );
141         osl_destroyThread( aThread );
142     }
143     m_aGetPPDMutex.release();
144 
145     return aResult;
146 }
147 
setPasswordCallback( const char* )148 static const char* setPasswordCallback( const char* /*pIn*/ )
149 {
150     const char* pRet = nullptr;
151 
152     PrinterInfoManager& rMgr = PrinterInfoManager::get();
153     if( rMgr.getType() == PrinterInfoManager::Type::CUPS ) // sanity check
154         pRet = static_cast<CUPSManager&>(rMgr).authenticateUser();
155     return pRet;
156 }
157 
158 /*
159  *  CUPSManager class
160  */
161 
tryLoadCUPS()162 CUPSManager* CUPSManager::tryLoadCUPS()
163 {
164     CUPSManager* pManager = nullptr;
165     static const char* pEnv = getenv("SAL_DISABLE_CUPS");
166 
167     if (!pEnv || !*pEnv)
168         pManager = new CUPSManager();
169     return pManager;
170 }
171 
172 extern "C"
173 {
run_dest_thread_stub( void* pThis )174 static void run_dest_thread_stub( void* pThis )
175 {
176     osl_setThreadName("CUPSManager cupsGetDests");
177     CUPSManager::runDestThread( pThis );
178 }
179 }
180 
CUPSManager()181 CUPSManager::CUPSManager() :
182         PrinterInfoManager( PrinterInfoManager::Type::CUPS ),
183         m_nDests( 0 ),
184         m_pDests( nullptr ),
185         m_bNewDests( false ),
186         m_bPPDThreadRunning( false )
187 {
188     m_aDestThread = osl_createThread( run_dest_thread_stub, this );
189 }
190 
~CUPSManager()191 CUPSManager::~CUPSManager()
192 {
193     if( m_aDestThread )
194     {
195         // if the thread is still running here, then
196         // cupsGetDests is hung; terminate the thread instead of joining
197         osl_terminateThread( m_aDestThread );
198         osl_destroyThread( m_aDestThread );
199     }
200 
201     if (m_nDests && m_pDests)
202         cupsFreeDests( m_nDests, static_cast<cups_dest_t*>(m_pDests) );
203 }
204 
runDestThread( void* pThis )205 void CUPSManager::runDestThread( void* pThis )
206 {
207     static_cast<CUPSManager*>(pThis)->runDests();
208 }
209 
runDests()210 void CUPSManager::runDests()
211 {
212     SAL_INFO("vcl.unx.print", "starting cupsGetDests");
213     cups_dest_t* pDests = nullptr;
214 
215     // n#722902 - do a fast-failing check for cups working *at all* first
216     http_t* p_http;
217     if( (p_http=httpConnectEncrypt(
218              cupsServer(),
219              ippPort(),
220              cupsEncryption())) != nullptr )
221     {
222         int nDests = cupsGetDests2(p_http,  &pDests);
223         SAL_INFO("vcl.unx.print", "came out of cupsGetDests");
224 
225         osl::MutexGuard aGuard( m_aCUPSMutex );
226         m_nDests = nDests;
227         m_pDests = pDests;
228         m_bNewDests = true;
229         SAL_INFO("vcl.unx.print", "finished cupsGetDests");
230 
231         httpClose(p_http);
232     }
233 }
234 
initialize()235 void CUPSManager::initialize()
236 {
237     // get normal printers, clear printer list
238     PrinterInfoManager::initialize();
239 
240     // check whether thread has completed
241     // if not behave like old printing system
242     osl::MutexGuard aGuard( m_aCUPSMutex );
243 
244     if( ! m_bNewDests )
245         return;
246 
247     // dest thread has run, clean up
248     if( m_aDestThread )
249     {
250         osl_joinWithThread( m_aDestThread );
251         osl_destroyThread( m_aDestThread );
252         m_aDestThread = nullptr;
253     }
254     m_bNewDests = false;
255 
256     // clear old stuff
257     m_aCUPSDestMap.clear();
258 
259     if( ! (m_nDests && m_pDests ) )
260         return;
261 
262     // check for CUPS server(?) > 1.2
263     // since there is no API to query, check for options that were
264     // introduced in dests with 1.2
265     // this is needed to check for %%IncludeFeature support
266     // (#i65684#, #i65491#)
267     bool bUsePDF = false;
268     cups_dest_t* pDest = static_cast<cups_dest_t*>(m_pDests);
269     const char* pOpt = cupsGetOption( "printer-info",
270                                                       pDest->num_options,
271                                                       pDest->options );
272     if( pOpt )
273     {
274         m_bUseIncludeFeature = true;
275         bUsePDF = officecfg::Office::Common::Print::Option::Printer::PDFAsStandardPrintJobFormat::get();
276     }
277 
278     m_aGlobalDefaults.setDefaultBackend(bUsePDF);
279 
280     // do not send include JobPatch; CUPS will insert that itself
281     // TODO: currently unknown which versions of CUPS insert JobPatches
282     // so currently it is assumed CUPS = don't insert JobPatch files
283     m_bUseJobPatch = false;
284 
285     rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
286     int nPrinter = m_nDests;
287 
288     // reset global default PPD options; these are queried on demand from CUPS
289     m_aGlobalDefaults.m_pParser = nullptr;
290     m_aGlobalDefaults.m_aContext = PPDContext();
291 
292     // add CUPS printers, should there be a printer
293     // with the same name as a CUPS printer, overwrite it
294     while( nPrinter-- )
295     {
296         pDest = static_cast<cups_dest_t*>(m_pDests)+nPrinter;
297         OUString aPrinterName = OStringToOUString( pDest->name, aEncoding );
298         if( pDest->instance && *pDest->instance )
299         {
300             OUStringBuffer aBuf( 256 );
301             aBuf.append( aPrinterName );
302             aBuf.append( '/' );
303             aBuf.append( OStringToOUString( pDest->instance, aEncoding ) );
304             aPrinterName = aBuf.makeStringAndClear();
305         }
306 
307         // initialize printer with possible configuration from psprint.conf
308         bool bSetToGlobalDefaults = m_aPrinters.find( aPrinterName ) == m_aPrinters.end();
309         Printer aPrinter = m_aPrinters[ aPrinterName ];
310         if( bSetToGlobalDefaults )
311             aPrinter.m_aInfo = m_aGlobalDefaults;
312         aPrinter.m_aInfo.m_aPrinterName = aPrinterName;
313         if( pDest->is_default )
314             m_aDefaultPrinter = aPrinterName;
315 
316         for( int k = 0; k < pDest->num_options; k++ )
317         {
318             if(!strcmp(pDest->options[k].name, "printer-info"))
319                 aPrinter.m_aInfo.m_aComment=OStringToOUString(pDest->options[k].value, aEncoding);
320             if(!strcmp(pDest->options[k].name, "printer-location"))
321                 aPrinter.m_aInfo.m_aLocation=OStringToOUString(pDest->options[k].value, aEncoding);
322         }
323 
324         OUStringBuffer aBuf( 256 );
325         aBuf.append( "CUPS:" );
326         aBuf.append( aPrinterName );
327         // note: the parser that goes with the PrinterInfo
328         // is created implicitly by the JobData::operator=()
329         // when it detects the NULL ptr m_pParser.
330         // if we wanted to fill in the parser here this
331         // would mean we'd have to download PPDs for each and
332         // every printer - which would be really bad runtime
333         // behaviour
334         aPrinter.m_aInfo.m_pParser = nullptr;
335         aPrinter.m_aInfo.m_aContext.setParser( nullptr );
336         std::unordered_map< OUString, PPDContext >::const_iterator c_it = m_aDefaultContexts.find( aPrinterName );
337         if( c_it != m_aDefaultContexts.end() )
338         {
339             aPrinter.m_aInfo.m_pParser = c_it->second.getParser();
340             aPrinter.m_aInfo.m_aContext = c_it->second;
341         }
342         aPrinter.m_aInfo.setDefaultBackend(bUsePDF);
343         aPrinter.m_aInfo.m_aDriverName = aBuf.makeStringAndClear();
344         aPrinter.m_bModified = false;
345 
346         m_aPrinters[ aPrinter.m_aInfo.m_aPrinterName ] = aPrinter;
347         m_aCUPSDestMap[ aPrinter.m_aInfo.m_aPrinterName ] = nPrinter;
348     }
349 
350     // remove everything that is not a CUPS printer and not
351     // a special purpose printer (PDF, Fax)
352     std::unordered_map< OUString, Printer >::iterator it = m_aPrinters.begin();
353     while(it != m_aPrinters.end())
354     {
355         if( m_aCUPSDestMap.find( it->first ) != m_aCUPSDestMap.end() )
356         {
357             ++it;
358             continue;
359         }
360 
361         if( !it->second.m_aInfo.m_aFeatures.isEmpty() )
362         {
363             ++it;
364             continue;
365         }
366         it = m_aPrinters.erase(it);
367     }
368 
369     cupsSetPasswordCB( setPasswordCallback );
370 }
371 
updatePrinterContextInfo( ppd_group_t* pPPDGroup, PPDContext& rContext )372 static void updatePrinterContextInfo( ppd_group_t* pPPDGroup, PPDContext& rContext )
373 {
374     rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
375     for( int i = 0; i < pPPDGroup->num_options; i++ )
376     {
377         ppd_option_t* pOption = pPPDGroup->options + i;
378         for( int n = 0; n < pOption->num_choices; n++ )
379         {
380             ppd_choice_t* pChoice = pOption->choices + n;
381             if( pChoice->marked )
382             {
383                 const PPDKey* pKey = rContext.getParser()->getKey( OStringToOUString( pOption->keyword, aEncoding ) );
384                 if( pKey )
385                 {
386                     const PPDValue* pValue = pKey->getValue( OStringToOUString( pChoice->choice, aEncoding ) );
387                     if( pValue )
388                     {
389                         if( pValue != pKey->getDefaultValue() )
390                         {
391                             rContext.setValue( pKey, pValue, true );
392                             SAL_INFO("vcl.unx.print", "key " << pOption->keyword << " is set to " << pChoice->choice);
393 
394                         }
395                         else
396                             SAL_INFO("vcl.unx.print", "key " << pOption->keyword << " is defaulted to " << pChoice->choice);
397                     }
398                     else
399                         SAL_INFO("vcl.unx.print", "caution: value " << pChoice->choice << " not found in key " << pOption->keyword);
400                 }
401                 else
402                     SAL_INFO("vcl.unx.print", "caution: key " << pOption->keyword << " not found in parser");
403             }
404         }
405     }
406 
407     // recurse through subgroups
408     for( int g = 0; g < pPPDGroup->num_subgroups; g++ )
409     {
410         updatePrinterContextInfo( pPPDGroup->subgroups + g, rContext );
411     }
412 }
413 
createCUPSParser( const OUString& rPrinter )414 const PPDParser* CUPSManager::createCUPSParser( const OUString& rPrinter )
415 {
416     const PPDParser* pNewParser = nullptr;
417     OUString aPrinter;
418 
419     if( rPrinter.startsWith("CUPS:") )
420         aPrinter = rPrinter.copy( 5 );
421     else
422         aPrinter = rPrinter;
423 
424     if( m_aCUPSMutex.tryToAcquire() )
425     {
426         if (m_nDests && m_pDests)
427         {
428             std::unordered_map< OUString, int >::iterator dest_it =
429             m_aCUPSDestMap.find( aPrinter );
430             if( dest_it != m_aCUPSDestMap.end() )
431             {
432                 cups_dest_t* pDest = static_cast<cups_dest_t*>(m_pDests) + dest_it->second;
433                 OString aPPDFile = threadedCupsGetPPD( pDest->name );
434                 SAL_INFO("vcl.unx.print",
435                         "PPD for " << aPrinter << " is " << aPPDFile);
436                 if( !aPPDFile.isEmpty() )
437                 {
438                     rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
439                     OUString aFileName( OStringToOUString( aPPDFile, aEncoding ) );
440                     // update the printer info with context information
441                     ppd_file_t* pPPD = ppdOpenFile( aPPDFile.getStr() );
442                     if( pPPD )
443                     {
444                         // create the new parser
445                         PPDParser* pCUPSParser = new PPDParser( aFileName );
446                         pCUPSParser->m_aFile = rPrinter;
447                         pNewParser = pCUPSParser;
448 
449                         /*int nConflicts =*/ cupsMarkOptions( pPPD, pDest->num_options, pDest->options );
450                         SAL_INFO("vcl.unx.print", "processing the following options for printer " << pDest->name << " (instance " << (pDest->instance == nullptr ? "null" : pDest->instance) << "):");
451                         for( int k = 0; k < pDest->num_options; k++ )
452                             SAL_INFO("vcl.unx.print",
453                                 "   \"" << pDest->options[k].name <<
454                                 "\" = \"" << pDest->options[k].value << "\"");
455                         PrinterInfo& rInfo = m_aPrinters[ aPrinter ].m_aInfo;
456 
457                         // remember the default context for later use
458                         PPDContext& rContext = m_aDefaultContexts[ aPrinter ];
459                         rContext.setParser( pNewParser );
460                         // set system default paper; printer CUPS PPD options
461                         // may overwrite it
462                         setDefaultPaper( rContext );
463                         for( int i = 0; i < pPPD->num_groups; i++ )
464                             updatePrinterContextInfo( pPPD->groups + i, rContext );
465 
466                         rInfo.m_pParser = pNewParser;
467                         rInfo.m_aContext = rContext;
468 
469                         // clean up the mess
470                         ppdClose( pPPD );
471                     }
472                     else
473                         SAL_INFO("vcl.unx.print", "ppdOpenFile failed, falling back to generic driver");
474 
475                     // remove temporary PPD file
476                     if (!getenv("SAL_CUPS_PPD_RETAIN_TMP"))
477                         unlink( aPPDFile.getStr() );
478                 }
479                 else
480                     SAL_INFO("vcl.unx.print", "cupsGetPPD failed, falling back to generic driver");
481             }
482             else
483                 SAL_INFO("vcl.unx.print", "no dest found for printer " << aPrinter);
484         }
485         m_aCUPSMutex.release();
486     }
487     else
488         SAL_WARN("vcl.unx.print", "could not acquire CUPS mutex !!!" );
489 
490     if( ! pNewParser )
491     {
492         // get the default PPD
493         pNewParser = PPDParser::getParser( "SGENPRT" );
494         SAL_WARN("vcl.unx.print", "Parsing default SGENPRT PPD" );
495 
496         PrinterInfo& rInfo = m_aPrinters[ aPrinter ].m_aInfo;
497 
498         rInfo.m_pParser = pNewParser;
499         rInfo.m_aContext.setParser( pNewParser );
500     }
501 
502     return pNewParser;
503 }
504 
setupJobContextData( JobData& rData )505 void CUPSManager::setupJobContextData( JobData& rData )
506 {
507     std::unordered_map< OUString, int >::iterator dest_it =
508         m_aCUPSDestMap.find( rData.m_aPrinterName );
509 
510     if( dest_it == m_aCUPSDestMap.end() )
511         return PrinterInfoManager::setupJobContextData( rData );
512 
513     std::unordered_map< OUString, Printer >::iterator p_it =
514         m_aPrinters.find( rData.m_aPrinterName );
515     if( p_it == m_aPrinters.end() ) // huh ?
516     {
517         SAL_WARN("vcl.unx.print", "CUPS printer list in disorder, "
518                 "no dest for printer " << rData.m_aPrinterName);
519         return;
520     }
521 
522     if( p_it->second.m_aInfo.m_pParser == nullptr )
523     {
524         // in turn calls createCUPSParser
525         // which updates the printer info
526         p_it->second.m_aInfo.m_pParser = PPDParser::getParser( p_it->second.m_aInfo.m_aDriverName );
527     }
528     if( p_it->second.m_aInfo.m_aContext.getParser() == nullptr )
529     {
530         OUString aPrinter;
531         if( p_it->second.m_aInfo.m_aDriverName.startsWith("CUPS:") )
532             aPrinter = p_it->second.m_aInfo.m_aDriverName.copy( 5 );
533         else
534             aPrinter = p_it->second.m_aInfo.m_aDriverName;
535 
536         p_it->second.m_aInfo.m_aContext = m_aDefaultContexts[ aPrinter ];
537     }
538 
539     rData.m_pParser     = p_it->second.m_aInfo.m_pParser;
540     rData.m_aContext    = p_it->second.m_aInfo.m_aContext;
541 }
542 
startSpool( const OUString& rPrintername, bool bQuickCommand )543 FILE* CUPSManager::startSpool( const OUString& rPrintername, bool bQuickCommand )
544 {
545     SAL_INFO( "vcl.unx.print", "startSpool: " << rPrintername << " " << (bQuickCommand ? "true" : "false") );
546 
547     if( m_aCUPSDestMap.find( rPrintername ) == m_aCUPSDestMap.end() )
548     {
549         SAL_INFO( "vcl.unx.print", "defer to PrinterInfoManager::startSpool" );
550         return PrinterInfoManager::startSpool( rPrintername, bQuickCommand );
551     }
552 
553     OUString aTmpURL, aTmpFile;
554     osl_createTempFile( nullptr, nullptr, &aTmpURL.pData );
555     osl_getSystemPathFromFileURL( aTmpURL.pData, &aTmpFile.pData );
556     OString aSysFile = OUStringToOString( aTmpFile, osl_getThreadTextEncoding() );
557     FILE* fp = fopen( aSysFile.getStr(), "w" );
558     if( fp )
559         m_aSpoolFiles[fp] = aSysFile;
560 
561     return fp;
562 }
563 
564 struct less_ppd_key
565 {
operator ()less_ppd_key566     bool operator()(const PPDKey* left, const PPDKey* right)
567     { return left->getOrderDependency() < right->getOrderDependency(); }
568 };
569 
getOptionsFromDocumentSetup( const JobData& rJob, bool bBanner, int& rNumOptions, void** rOptions )570 void CUPSManager::getOptionsFromDocumentSetup( const JobData& rJob, bool bBanner, int& rNumOptions, void** rOptions )
571 {
572     rNumOptions = 0;
573     *rOptions = nullptr;
574 
575     // emit features ordered to OrderDependency
576     // ignore features that are set to default
577 
578     // sanity check
579     if( rJob.m_pParser == rJob.m_aContext.getParser() && rJob.m_pParser )
580     {
581         int i;
582         int nKeys = rJob.m_aContext.countValuesModified();
583         ::std::vector< const PPDKey* > aKeys( nKeys );
584         for(  i = 0; i < nKeys; i++ )
585             aKeys[i] = rJob.m_aContext.getModifiedKey( i );
586         ::std::sort( aKeys.begin(), aKeys.end(), less_ppd_key() );
587 
588         for( i = 0; i < nKeys; i++ )
589         {
590             const PPDKey* pKey = aKeys[i];
591             const PPDValue* pValue = rJob.m_aContext.getValue( pKey );
592             OUString sPayLoad;
593             if (pValue && pValue->m_eType == eInvocation)
594             {
595                 sPayLoad = pValue->m_bCustomOption ? pValue->m_aCustomOption : pValue->m_aOption;
596             }
597 
598             if (!sPayLoad.isEmpty())
599             {
600                 OString aKey = OUStringToOString( pKey->getKey(), RTL_TEXTENCODING_ASCII_US );
601                 OString aValue = OUStringToOString( sPayLoad, RTL_TEXTENCODING_ASCII_US );
602                 rNumOptions = cupsAddOption( aKey.getStr(), aValue.getStr(), rNumOptions, reinterpret_cast<cups_option_t**>(rOptions) );
603             }
604         }
605     }
606 
607     if( rJob.m_nPDFDevice > 0 && rJob.m_nCopies > 1 )
608     {
609         OString aVal( OString::number( rJob.m_nCopies ) );
610         rNumOptions = cupsAddOption( "copies", aVal.getStr(), rNumOptions, reinterpret_cast<cups_option_t**>(rOptions) );
611         aVal = OString::boolean(rJob.m_bCollate);
612         rNumOptions = cupsAddOption( "collate", aVal.getStr(), rNumOptions, reinterpret_cast<cups_option_t**>(rOptions) );
613     }
614     if( ! bBanner )
615     {
616         rNumOptions = cupsAddOption( "job-sheets", "none", rNumOptions, reinterpret_cast<cups_option_t**>(rOptions) );
617     }
618 }
619 
endSpool( const OUString& rPrintername, const OUString& rJobTitle, FILE* pFile, const JobData& rDocumentJobData, bool bBanner, const OUString& rFaxNumber )620 bool CUPSManager::endSpool( const OUString& rPrintername, const OUString& rJobTitle, FILE* pFile, const JobData& rDocumentJobData, bool bBanner, const OUString& rFaxNumber )
621 {
622     SAL_INFO( "vcl.unx.print", "endSpool: " << rPrintername << "," << rJobTitle << " copy count = " << rDocumentJobData.m_nCopies );
623 
624     int nJobID = 0;
625 
626     osl::MutexGuard aGuard( m_aCUPSMutex );
627 
628     std::unordered_map< OUString, int >::iterator dest_it =
629         m_aCUPSDestMap.find( rPrintername );
630     if( dest_it == m_aCUPSDestMap.end() )
631     {
632         SAL_INFO( "vcl.unx.print", "defer to PrinterInfoManager::endSpool" );
633         return PrinterInfoManager::endSpool( rPrintername, rJobTitle, pFile, rDocumentJobData, bBanner, rFaxNumber );
634     }
635 
636     std::unordered_map< FILE*, OString, FPtrHash >::const_iterator it = m_aSpoolFiles.find( pFile );
637     if( it != m_aSpoolFiles.end() )
638     {
639         fclose( pFile );
640         rtl_TextEncoding aEnc = osl_getThreadTextEncoding();
641 
642         // setup cups options
643         int nNumOptions = 0;
644         cups_option_t* pOptions = nullptr;
645         getOptionsFromDocumentSetup( rDocumentJobData, bBanner, nNumOptions, reinterpret_cast<void**>(&pOptions) );
646 
647         OString sJobName(OUStringToOString(rJobTitle, aEnc));
648 
649         //fax4CUPS, "the job name will be dialled for you"
650         //so override the jobname with the desired number
651         if (!rFaxNumber.isEmpty())
652         {
653             sJobName = OUStringToOString(rFaxNumber, aEnc);
654         }
655 
656         cups_dest_t* pDest = static_cast<cups_dest_t*>(m_pDests) + dest_it->second;
657         nJobID = cupsPrintFile(pDest->name,
658             it->second.getStr(),
659             sJobName.getStr(),
660             nNumOptions, pOptions);
661         SAL_INFO("vcl.unx.print", "cupsPrintFile( " << pDest->name << ", "
662                 << it->second << ", " << rJobTitle << ", " << nNumOptions
663                 << ", " << pOptions << " ) returns " << nJobID);
664         for( int n = 0; n < nNumOptions; n++ )
665             SAL_INFO("vcl.unx.print",
666                 "    option " << pOptions[n].name << "=" << pOptions[n].value);
667 #if OSL_DEBUG_LEVEL > 1
668         OString aCmd( "cp " );
669         aCmd = aCmd + it->second.getStr();
670         aCmd = aCmd + OString( " $HOME/cupsprint.ps" );
671         system( aCmd.getStr() );
672 #endif
673 
674         unlink( it->second.getStr() );
675         m_aSpoolFiles.erase( pFile );
676         if( pOptions )
677             cupsFreeOptions( nNumOptions, pOptions );
678     }
679 
680     return nJobID != 0;
681 }
682 
checkPrintersChanged( bool bWait )683 bool CUPSManager::checkPrintersChanged( bool bWait )
684 {
685     bool bChanged = false;
686     if( bWait )
687     {
688         if(  m_aDestThread )
689         {
690             // initial asynchronous detection still running
691             SAL_INFO("vcl.unx.print", "syncing cups discovery thread");
692             osl_joinWithThread( m_aDestThread );
693             osl_destroyThread( m_aDestThread );
694             m_aDestThread = nullptr;
695             SAL_INFO("vcl.unx.print", "done: syncing cups discovery thread");
696         }
697         else
698         {
699             // #i82321# check for cups printer updates
700             // with this change the whole asynchronous detection in a thread is
701             // almost useless. The only relevance left is for some stalled systems
702             // where the user can set SAL_DISABLE_SYNCHRONOUS_PRINTER_DETECTION
703             // (see vcl/unx/source/gdi/salprnpsp.cxx)
704             // so that checkPrintersChanged( true ) will never be called
705 
706             // there is no way to query CUPS whether the printer list has changed
707             // so get the dest list anew
708             if( m_nDests && m_pDests )
709                 cupsFreeDests( m_nDests, static_cast<cups_dest_t*>(m_pDests) );
710             m_nDests = 0;
711             m_pDests = nullptr;
712             runDests();
713         }
714     }
715     if( m_aCUPSMutex.tryToAcquire() )
716     {
717         bChanged = m_bNewDests;
718         m_aCUPSMutex.release();
719     }
720 
721     if( ! bChanged )
722     {
723         bChanged = PrinterInfoManager::checkPrintersChanged( bWait );
724         // #i54375# ensure new merging with CUPS list in :initialize
725         if( bChanged )
726             m_bNewDests = true;
727     }
728 
729     if( bChanged )
730         initialize();
731 
732     return bChanged;
733 }
734 
addPrinter( const OUString& rName, const OUString& rDriver )735 bool CUPSManager::addPrinter( const OUString& rName, const OUString& rDriver )
736 {
737     // don't touch the CUPS printers
738     if( m_aCUPSDestMap.find( rName ) != m_aCUPSDestMap.end() ||
739         rDriver.startsWith("CUPS:")
740         )
741         return false;
742     return PrinterInfoManager::addPrinter( rName, rDriver );
743 }
744 
removePrinter( const OUString& rName, bool bCheck )745 bool CUPSManager::removePrinter( const OUString& rName, bool bCheck )
746 {
747     // don't touch the CUPS printers
748     if( m_aCUPSDestMap.find( rName ) != m_aCUPSDestMap.end() )
749         return false;
750     return PrinterInfoManager::removePrinter( rName, bCheck );
751 }
752 
setDefaultPrinter( const OUString& rName )753 bool CUPSManager::setDefaultPrinter( const OUString& rName )
754 {
755     bool bSuccess = false;
756     std::unordered_map< OUString, int >::iterator nit =
757         m_aCUPSDestMap.find( rName );
758     if( nit != m_aCUPSDestMap.end() && m_aCUPSMutex.tryToAcquire() )
759     {
760         cups_dest_t* pDests = static_cast<cups_dest_t*>(m_pDests);
761         for( int i = 0; i < m_nDests; i++ )
762             pDests[i].is_default = 0;
763         pDests[ nit->second ].is_default = 1;
764         cupsSetDests( m_nDests, static_cast<cups_dest_t*>(m_pDests) );
765         m_aDefaultPrinter = rName;
766         m_aCUPSMutex.release();
767         bSuccess = true;
768     }
769     else
770         bSuccess = PrinterInfoManager::setDefaultPrinter( rName );
771 
772     return bSuccess;
773 }
774 
writePrinterConfig()775 bool CUPSManager::writePrinterConfig()
776 {
777     bool bDestModified = false;
778     rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
779 
780     for( std::unordered_map< OUString, Printer >::iterator prt =
781              m_aPrinters.begin(); prt != m_aPrinters.end(); ++prt )
782     {
783         std::unordered_map< OUString, int >::iterator nit =
784             m_aCUPSDestMap.find( prt->first );
785         if( nit == m_aCUPSDestMap.end() )
786             continue;
787 
788         if( ! prt->second.m_bModified )
789             continue;
790 
791         if( m_aCUPSMutex.tryToAcquire() )
792         {
793             bDestModified = true;
794             cups_dest_t* pDest = static_cast<cups_dest_t*>(m_pDests) + nit->second;
795             PrinterInfo& rInfo = prt->second.m_aInfo;
796 
797             // create new option list
798             int nNewOptions = 0;
799             cups_option_t* pNewOptions = nullptr;
800             int nValues = rInfo.m_aContext.countValuesModified();
801             for( int i = 0; i < nValues; i++ )
802             {
803                 const PPDKey* pKey = rInfo.m_aContext.getModifiedKey( i );
804                 const PPDValue* pValue = rInfo.m_aContext.getValue( pKey );
805                 if( pKey && pValue ) // sanity check
806                 {
807                     OString aName = OUStringToOString( pKey->getKey(), aEncoding );
808                     OString aValue = OUStringToOString( pValue->m_aOption, aEncoding );
809                     nNewOptions = cupsAddOption( aName.getStr(), aValue.getStr(), nNewOptions, &pNewOptions );
810                 }
811             }
812             // set PPD options on CUPS dest
813             cupsFreeOptions( pDest->num_options, pDest->options );
814             pDest->num_options = nNewOptions;
815             pDest->options = pNewOptions;
816             m_aCUPSMutex.release();
817         }
818     }
819     if( bDestModified && m_aCUPSMutex.tryToAcquire() )
820     {
821         cupsSetDests( m_nDests, static_cast<cups_dest_t*>(m_pDests) );
822         m_aCUPSMutex.release();
823     }
824 
825     return PrinterInfoManager::writePrinterConfig();
826 }
827 
828 namespace
829 {
830     class RTSPWDialog : public ModalDialog
831     {
832         VclPtr<FixedText> m_pText;
833         VclPtr<Edit>      m_pUserEdit;
834         VclPtr<Edit>      m_pPassEdit;
835 
836     public:
837         RTSPWDialog(const OString& rServer, const OString& rUserName, vcl::Window* pParent);
838         virtual ~RTSPWDialog() override;
839         virtual void dispose() override;
840         OString getUserName() const;
841         OString getPassword() const;
842     };
843 
RTSPWDialog( const OString& rServer, const OString& rUserName, vcl::Window* pParent )844     RTSPWDialog::RTSPWDialog( const OString& rServer, const OString& rUserName, vcl::Window* pParent )
845         : ModalDialog(pParent, "CUPSPasswordDialog",
846             "vcl/ui/cupspassworddialog.ui")
847     {
848         get(m_pText, "text");
849         get(m_pUserEdit, "user");
850         get(m_pPassEdit, "pass");
851 
852         OUString aText(m_pText->GetText());
853         aText = aText.replaceFirst("%s", OStringToOUString(rServer, osl_getThreadTextEncoding()));
854         m_pText->SetText(aText);
855         m_pUserEdit->SetText( OStringToOUString(rUserName, osl_getThreadTextEncoding()));
856     }
857 
~RTSPWDialog()858     RTSPWDialog::~RTSPWDialog()
859     {
860         disposeOnce();
861     }
862 
dispose()863     void RTSPWDialog::dispose()
864     {
865         m_pText.clear();
866         m_pUserEdit.clear();
867         m_pPassEdit.clear();
868         ModalDialog::dispose();
869     }
870 
getUserName() const871     OString RTSPWDialog::getUserName() const
872     {
873         return OUStringToOString( m_pUserEdit->GetText(), osl_getThreadTextEncoding() );
874     }
875 
getPassword() const876     OString RTSPWDialog::getPassword() const
877     {
878         return OUStringToOString( m_pPassEdit->GetText(), osl_getThreadTextEncoding() );
879     }
880 
AuthenticateQuery(const OString& rServer, OString& rUserName, OString& rPassword)881     bool AuthenticateQuery(const OString& rServer, OString& rUserName, OString& rPassword)
882     {
883         bool bRet = false;
884 
885         ScopedVclPtrInstance<RTSPWDialog> aDialog(rServer, rUserName, nullptr);
886         if (aDialog->Execute())
887         {
888             rUserName = aDialog->getUserName();
889             rPassword = aDialog->getPassword();
890             bRet = true;
891         }
892 
893         return bRet;
894     }
895 }
896 
authenticateUser()897 const char* CUPSManager::authenticateUser()
898 {
899     const char* pRet = nullptr;
900 
901     osl::MutexGuard aGuard( m_aCUPSMutex );
902 
903     OString aUser = cupsUser();
904     OString aServer = cupsServer();
905     OString aPassword;
906     if (AuthenticateQuery(aServer, aUser, aPassword))
907     {
908         m_aPassword = aPassword;
909         m_aUser = aUser;
910         cupsSetUser( m_aUser.getStr() );
911         pRet = m_aPassword.getStr();
912     }
913 
914     return pRet;
915 }
916 
917 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
918