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