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