xref: /core/vcl/unx/generic/printer/cpdmgr.cxx (revision 545f198a)
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 <sal/config.h>
21 
22 #include <cstddef>
23 #include <unistd.h>
24 
25 #include <unx/cpdmgr.hxx>
26 
27 #include <osl/file.h>
28 #include <osl/thread.h>
29 
30 #include <rtl/ustrbuf.hxx>
31 #include <sal/log.hxx>
32 
33 #include <config_dbus.h>
34 #include <config_gio.h>
35 
36 using namespace psp;
37 using namespace osl;
38 
39 #if ENABLE_DBUS && ENABLE_GIO
40 // Function to execute when name is acquired on the bus
41 void CPDManager::onNameAcquired (GDBusConnection *connection,
42                                  const gchar *,
43                                  gpointer user_data)
44 {
45     gchar* contents;
46     GDBusNodeInfo *introspection_data;
47 
48     // Get Interface for introspection
49     g_file_get_contents (FRONTEND_INTERFACE, &contents, nullptr, nullptr);
50     introspection_data = g_dbus_node_info_new_for_xml (contents, nullptr);
51 
52     g_dbus_connection_register_object (connection,
53                                        "/org/libreoffice/PrintDialog",
54                                        introspection_data->interfaces[0],
55                                        nullptr,
56                                        nullptr,  /* user_data */
57                                        nullptr,  /* user_data_free_func */
58                                        nullptr); /* GError** */
59     g_free(contents);
60     g_dbus_node_info_unref(introspection_data);
61 
62     CPDManager* current = static_cast<CPDManager*>(user_data);
63     std::vector<std::pair<std::string, gchar*>> backends = current->getTempBackends();
64     for (auto const& backend : backends)
65     {
66         GDBusProxy *proxy;
67         // Get Interface for introspection
68         g_file_get_contents (BACKEND_INTERFACE, &contents, nullptr, nullptr);
69         introspection_data = g_dbus_node_info_new_for_xml (contents, nullptr);
70         proxy = g_dbus_proxy_new_sync (connection,
71                                        G_DBUS_PROXY_FLAGS_NONE,
72                                        introspection_data->interfaces[0],
73                                        backend.first.c_str(),
74                                        backend.second,
75                                        "org.openprinting.PrintBackend",
76                                        nullptr,
77                                        nullptr);
78         g_free(backend.second);
79         g_assert (proxy != nullptr);
80         g_dbus_proxy_call(proxy, "ActivateBackend",
81                           nullptr,
82                           G_DBUS_CALL_FLAGS_NONE,
83                           -1, nullptr, nullptr, nullptr);
84 
85         g_free(contents);
86         g_object_unref(proxy);
87         g_dbus_node_info_unref(introspection_data);
88     }
89 }
90 
91 void CPDManager::onNameLost (GDBusConnection *,
92                              const gchar *name,
93                              gpointer)
94 {
95     g_message("Name Lost: %s", name);
96 }
97 
98 void CPDManager::printerAdded (GDBusConnection *connection,
99                                const gchar     *sender_name,
100                                const gchar     *object_path,
101                                const gchar     *interface_name,
102                                const gchar     *,
103                                GVariant        *parameters,
104                                gpointer        user_data)
105 {
106     CPDManager* current = static_cast<CPDManager*>(user_data);
107     GDBusProxy *proxy;
108     proxy = current->getProxy(sender_name);
109     if (proxy == nullptr) {
110         gchar* contents;
111         GDBusNodeInfo *introspection_data;
112 
113         // Get Interface for introspection
114         g_file_get_contents ("/usr/share/dbus-1/interfaces/org.openprinting.Backend.xml", &contents, nullptr, nullptr);
115         introspection_data = g_dbus_node_info_new_for_xml (contents, nullptr);
116         proxy = g_dbus_proxy_new_sync (connection,
117                                        G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
118                                        introspection_data->interfaces[0],
119                                        sender_name,
120                                        object_path,
121                                        interface_name,
122                                        nullptr,
123                                        nullptr);
124 
125         g_free(contents);
126         g_dbus_node_info_unref(introspection_data);
127         std::pair<std::string, GDBusProxy *> new_backend (sender_name, proxy);
128         current->addBackend(new_backend);
129     }
130     CPDPrinter *pDest = static_cast<CPDPrinter *>(malloc(sizeof(CPDPrinter)));
131     pDest->backend = proxy;
132     g_variant_get (parameters, "(sssssbss)", &(pDest->id), &(pDest->name), &(pDest->info), &(pDest->location), &(pDest->make_and_model), &(pDest->is_accepting_jobs), &(pDest->printer_state), &(pDest->backend_name));
133     std::stringstream printerName;
134     printerName << pDest->name << ", " << pDest->backend_name;
135     std::stringstream uniqueName;
136     uniqueName << pDest->id << ", " << pDest->backend_name;
137     rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
138     OUString aPrinterName = OStringToOUString( printerName.str().c_str(), aEncoding );
139     OUString aUniqueName = OStringToOUString( uniqueName.str().c_str(), aEncoding );
140     current->addNewPrinter(aPrinterName, aUniqueName, pDest);
141 }
142 
143 void CPDManager::printerRemoved (GDBusConnection *,
144                                  const gchar     *,
145                                  const gchar     *,
146                                  const gchar     *,
147                                  const gchar     *,
148                                  GVariant        *parameters,
149                                  gpointer        user_data)
150 {
151     // TODO: Remove every data linked to this particular printer.
152     CPDManager* pManager = static_cast<CPDManager*>(user_data);
153     char* id;
154     char* backend_name;
155     g_variant_get (parameters, "(ss)", &id, &backend_name);
156     std::stringstream uniqueName;
157     uniqueName << id << ", " << backend_name;
158     rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
159     OUString aUniqueName = OStringToOUString( uniqueName.str().c_str(), aEncoding );
160     std::unordered_map<OUString, CPDPrinter *>::iterator it = pManager->m_aCPDDestMap.find( aUniqueName );
161     if (it == pManager->m_aCPDDestMap.end()) {
162         SAL_WARN("vcl.unx.print", "CPD trying to remove non-existent printer from list");
163         return;
164     }
165     pManager->m_aCPDDestMap.erase(it);
166     std::unordered_map<OUString, Printer>::iterator printersIt = pManager->m_aPrinters.find( aUniqueName );
167     if (printersIt == pManager->m_aPrinters.end()) {
168         SAL_WARN("vcl.unx.print", "CPD trying to remove non-existent printer from m_aPrinters");
169         return;
170     }
171     pManager->m_aPrinters.erase(printersIt);
172 }
173 
174 GDBusProxy* CPDManager::getProxy(const std::string& target)
175 {
176     std::unordered_map<std::string, GDBusProxy *>::const_iterator it = m_pBackends.find(target);
177     if (it == m_pBackends.end()) {
178         return nullptr;
179     }
180     return it->second;
181 }
182 
183 void CPDManager::addBackend(std::pair<std::string, GDBusProxy *> pair) {
184     m_pBackends.insert(pair);
185 }
186 
187 void CPDManager::addTempBackend(const std::pair<std::string, gchar*>& pair)
188 {
189     m_tBackends.push_back(pair);
190 }
191 
192 std::vector<std::pair<std::string, gchar*>> const & CPDManager::getTempBackends() const {
193     return m_tBackends;
194 }
195 
196 void CPDManager::addNewPrinter(const OUString& aPrinterName, const OUString& aUniqueName, CPDPrinter *pDest) {
197     m_aCPDDestMap[aUniqueName] = pDest;
198     bool bSetToGlobalDefaults = m_aPrinters.find( aUniqueName ) == m_aPrinters.end();
199     Printer aPrinter = m_aPrinters[ aUniqueName ];
200     if( bSetToGlobalDefaults )
201         aPrinter.m_aInfo = m_aGlobalDefaults;
202     aPrinter.m_aInfo.m_aPrinterName = aPrinterName;
203 
204     // TODO: I don't know how this should work when we have multiple
205     // sources with multiple possible defaults for each
206     // if( pDest->is_default )
207     //     m_aDefaultPrinter = aPrinterName;
208 
209     rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
210     aPrinter.m_aInfo.m_aComment = OStringToOUString(pDest->info, aEncoding);
211     aPrinter.m_aInfo.m_aLocation = OStringToOUString(pDest->location, aEncoding);
212     // note: the parser that goes with the PrinterInfo
213     // is created implicitly by the JobData::operator=()
214     // when it detects the NULL ptr m_pParser.
215     // if we wanted to fill in the parser here this
216     // would mean we'd have to send a dbus message for each and
217     // every printer - which would be really bad runtime
218     // behaviour
219     aPrinter.m_aInfo.m_pParser = nullptr;
220     aPrinter.m_aInfo.m_aContext.setParser( nullptr );
221     std::unordered_map< OUString, PPDContext >::const_iterator c_it = m_aDefaultContexts.find( aUniqueName );
222     if( c_it != m_aDefaultContexts.end() )
223     {
224         aPrinter.m_aInfo.m_pParser = c_it->second.getParser();
225         aPrinter.m_aInfo.m_aContext = c_it->second;
226     }
227     aPrinter.m_aInfo.setDefaultBackend(true);
228     aPrinter.m_aInfo.m_aDriverName = "CPD:" + aUniqueName;
229     m_aPrinters[ aUniqueName ] = aPrinter;
230 }
231 #endif
232 
233 /*
234  *  CPDManager class
235  */
236 
237 CPDManager* CPDManager::tryLoadCPD()
238 {
239     CPDManager* pManager = nullptr;
240 #if ENABLE_DBUS && ENABLE_GIO
241     static const char* pEnv = getenv("SAL_DISABLE_CPD");
242 
243     if (!pEnv || !*pEnv) {
244         // interface description XML files are needed in 'onNameAcquired()'
245         if (!g_file_test(FRONTEND_INTERFACE, G_FILE_TEST_IS_REGULAR) ||
246                 !g_file_test(BACKEND_INTERFACE, G_FILE_TEST_IS_REGULAR)) {
247             return nullptr;
248         }
249 
250         GDir *dir;
251         const gchar *filename;
252         dir = g_dir_open(BACKEND_DIR, 0, nullptr);
253         if (dir != nullptr) {
254             while ((filename = g_dir_read_name(dir))) {
255                 if (pManager == nullptr) {
256                     pManager = new CPDManager();
257                 }
258                 gchar* contents;
259                 std::stringstream filepath;
260                 filepath << BACKEND_DIR << '/' << filename;
261                 g_file_get_contents(filepath.str().c_str(), &contents, nullptr, nullptr);
262                 std::pair<std::string, gchar*> new_tbackend (filename, contents);
263                 pManager->addTempBackend(new_tbackend);
264             }
265             g_dir_close(dir);
266         }
267     }
268 #endif
269     return pManager;
270 }
271 
272 CPDManager::CPDManager() :
273     PrinterInfoManager( PrinterInfoManager::Type::CPD )
274 {
275 #if ENABLE_DBUS && ENABLE_GIO
276     // Get Destinations number and pointers
277     GError *error = nullptr;
278     m_pConnection = g_bus_get_sync (G_BUS_TYPE_SESSION, nullptr, &error);
279     g_assert_no_error (error);
280 #endif
281 }
282 
283 CPDManager::~CPDManager()
284 {
285 #if ENABLE_DBUS && ENABLE_GIO
286     g_dbus_connection_emit_signal (m_pConnection,
287                                    nullptr,
288                                    "/org/libreoffice/PrintDialog",
289                                    "org.openprinting.PrintFrontend",
290                                    "StopListing",
291                                    nullptr,
292                                    nullptr);
293     g_dbus_connection_flush_sync (m_pConnection,
294                                   nullptr,
295                                   nullptr);
296     g_dbus_connection_close_sync (m_pConnection,
297                                   nullptr,
298                                   nullptr);
299     for (auto const& backend : m_pBackends)
300     {
301         g_object_unref(backend.second);
302     }
303     for (auto const& backend : m_aCPDDestMap)
304     {
305         free(backend.second);
306     }
307 #endif
308 }
309 
310 
311 const PPDParser* CPDManager::createCPDParser( const OUString& rPrinter )
312 {
313     const PPDParser* pNewParser = nullptr;
314 #if ENABLE_DBUS && ENABLE_GIO
315     OUString aPrinter;
316 
317     if( rPrinter.startsWith("CPD:") )
318         aPrinter = rPrinter.copy( 4 );
319     else
320         aPrinter = rPrinter;
321 
322     std::unordered_map< OUString, CPDPrinter * >::iterator dest_it =
323         m_aCPDDestMap.find( aPrinter );
324 
325     if( dest_it != m_aCPDDestMap.end() )
326     {
327         CPDPrinter* pDest = dest_it->second;
328         GVariant* ret = nullptr;
329         GError* error = nullptr;
330         ret = g_dbus_proxy_call_sync (pDest->backend, "GetAllOptions",
331                                       g_variant_new("(s)", (pDest->id)),
332                                       G_DBUS_CALL_FLAGS_NONE,
333                                       -1, nullptr, &error);
334         if (ret != nullptr && error == nullptr)
335         {
336             // TODO: These keys need to be redefined to preserve usage across libreoffice
337             // InputSlot - media-col.media-source?
338             // Font - not needed now as it is required only for ps and we are using pdf
339             // Dial? - for FAX (need to look up PWG spec)
340 
341             int num_attribute;
342             GVariantIter *iter_attr, *iter_supported_values;
343             g_variant_get (ret, "(ia(ssia(s)))", &num_attribute, &iter_attr);
344             rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
345             PPDKey *pKey = nullptr;
346             OUString aValueName;
347             PPDValue* pValue;
348             std::vector<PPDKey*> keys;
349             std::vector<OUString> default_values;
350             for (int i = 0; i < num_attribute; i++) {
351                 char *name, *default_value;
352                 int num_supported_values;
353                 g_variant_iter_loop(iter_attr, "(ssia(s))",
354                                     &name, &default_value,
355                                     &num_supported_values, &iter_supported_values);
356                 OUString aOptionName = OStringToOUString( name, aEncoding );
357                 OUString aDefaultValue = OStringToOUString( default_value, aEncoding );
358                 if (aOptionName == "sides") {
359                     // Duplex key is used throughout for checking Duplex Support
360                     aOptionName = OUString("Duplex");
361                 } else if (aOptionName == "printer-resolution") {
362                     // Resolution key is used in places
363                     aOptionName = OUString("Resolution");
364                 } else if (aOptionName == "media") {
365                     // PageSize key is used in many places
366                     aOptionName = OUString("PageSize");
367                 }
368                 default_values.push_back(aDefaultValue);
369                 pKey = new PPDKey( aOptionName );
370 
371                 // If number of values are 0, this is not settable via UI
372                 if (num_supported_values > 0 && aDefaultValue != "NA")
373                     pKey->m_bUIOption = true;
374 
375                 bool bDefaultFound = false;
376 
377                 for (int j = 0; j < num_supported_values; j++) {
378                     char* value;
379                     g_variant_iter_loop(iter_supported_values, "(s)", &value);
380                     aValueName = OStringToOUString( value, aEncoding );
381                     if (aOptionName == "Duplex") {
382                         // Duplex key matches against very specific Values
383                         if (aValueName == "one-sided") {
384                             aValueName = OUString("None");
385                         } else if (aValueName == "two-sided-long-edge") {
386                             aValueName = OUString("DuplexNoTumble");
387                         } else if (aValueName == "two-sided-short-edge") {
388                             aValueName = OUString("DuplexTumble");
389                         }
390                     }
391 
392                     pValue = pKey->insertValue( aValueName, eQuoted );
393                     if( ! pValue )
394                         continue;
395                     pValue->m_aValue = aValueName;
396 
397                     if (aValueName.equals(aDefaultValue)) {
398                         pKey->m_pDefaultValue = pValue;
399                         bDefaultFound = true;
400                     }
401 
402                 }
403                 // This could be done to ensure default values also appear as options:
404                 if (!bDefaultFound && pKey->m_bUIOption) {
405                 //     pValue = pKey->insertValue( aDefaultValue, eQuoted );
406                 //     if( pValue )
407                 //         pValue->m_aValue = aDefaultValue;
408                 }
409                 keys.emplace_back(pKey);
410             }
411 
412             pKey = new PPDKey("ModelName");
413             aValueName = OStringToOUString( "", aEncoding );
414             pValue = pKey->insertValue( aValueName, eQuoted );
415             if( pValue )
416                 pValue->m_aValue = aValueName;
417             pKey->m_pDefaultValue = pValue;
418             keys.emplace_back(pKey);
419 
420             pKey = new PPDKey("NickName");
421             aValueName = OStringToOUString( pDest->name, aEncoding );
422             pValue = pKey->insertValue( aValueName, eQuoted );
423             if( pValue )
424                 pValue->m_aValue = aValueName;
425             pKey->m_pDefaultValue = pValue;
426             keys.emplace_back(pKey);
427 
428             pNewParser = new PPDParser(aPrinter, keys);
429             PrinterInfo& rInfo = m_aPrinters[ aPrinter ].m_aInfo;
430             PPDContext& rContext = m_aDefaultContexts[ aPrinter ];
431             rContext.setParser( pNewParser );
432             setDefaultPaper( rContext );
433             std::vector<OUString>::iterator defit = default_values.begin();
434             for (auto const& key : keys)
435             {
436                 const PPDValue* p1Value = key->getValue( *defit );
437                 if( p1Value )
438                 {
439                     if( p1Value != key->getDefaultValue() )
440                     {
441                         rContext.setValue( key, p1Value, true );
442                         SAL_INFO("vcl.unx.print", "key " << pKey->getKey() << " is set to " << *defit);
443                     }
444                     else
445                         SAL_INFO("vcl.unx.print", "key " << pKey->getKey() << " is defaulted to " << *defit);
446                 }
447                 ++defit;
448             }
449 
450             rInfo.m_pParser = pNewParser;
451             rInfo.m_aContext = rContext;
452             g_variant_unref(ret);
453         }
454         else
455         {
456             g_clear_error(&error);
457             SAL_INFO("vcl.unx.print", "CPD GetAllOptions failed, falling back to generic driver");
458         }
459     }
460     else
461         SAL_INFO("vcl.unx.print", "no dest found for printer " << aPrinter);
462 
463     if( ! pNewParser )
464     {
465         // get the default PPD
466         pNewParser = PPDParser::getParser( "SGENPRT" );
467         SAL_WARN("vcl.unx.print", "Parsing default SGENPRT PPD" );
468 
469         PrinterInfo& rInfo = m_aPrinters[ aPrinter ].m_aInfo;
470 
471         rInfo.m_pParser = pNewParser;
472         rInfo.m_aContext.setParser( pNewParser );
473     }
474 #else
475     (void)rPrinter;
476 #endif
477     return pNewParser;
478 }
479 
480 
481 void CPDManager::initialize()
482 {
483     // get normal printers, clear printer list
484     PrinterInfoManager::initialize();
485 #if ENABLE_DBUS && ENABLE_GIO
486     g_bus_own_name_on_connection (m_pConnection,
487                                   "org.libreoffice.print-dialog",
488                                   G_BUS_NAME_OWNER_FLAGS_NONE,
489                                   onNameAcquired,
490                                   onNameLost,
491                                   this,
492                                   nullptr);
493 
494     g_dbus_connection_signal_subscribe (m_pConnection,                    // DBus Connection
495                                         nullptr,                          // Sender Name
496                                         "org.openprinting.PrintBackend",  // Sender Interface
497                                         "PrinterAdded",                   // Signal Name
498                                         nullptr,                          // Object Path
499                                         nullptr,                          // arg0 behaviour
500                                         G_DBUS_SIGNAL_FLAGS_NONE,         // Signal Flags
501                                         printerAdded,                     // Callback Function
502                                         this,
503                                         nullptr);
504     g_dbus_connection_signal_subscribe (m_pConnection,                    // DBus Connection
505                                         nullptr,                          // Sender Name
506                                         "org.openprinting.PrintBackend",  // Sender Interface
507                                         "PrinterRemoved",                 // Signal Name
508                                         nullptr,                          // Object Path
509                                         nullptr,                          // arg0 behaviour
510                                         G_DBUS_SIGNAL_FLAGS_NONE,         // Signal Flags
511                                         printerRemoved,                   // Callback Function
512                                         this,
513                                         nullptr);
514 
515     // remove everything that is not a CUPS printer and not
516     // a special purpose printer (PDF, Fax)
517     std::unordered_map< OUString, Printer >::iterator it = m_aPrinters.begin();
518     while (it != m_aPrinters.end())
519     {
520         if( m_aCPDDestMap.find( it->first ) != m_aCPDDestMap.end() )
521         {
522             ++it;
523             continue;
524         }
525 
526         if( !it->second.m_aInfo.m_aFeatures.isEmpty() )
527         {
528             ++it;
529             continue;
530         }
531         it = m_aPrinters.erase(it);
532     }
533 #endif
534 }
535 
536 void CPDManager::setupJobContextData( JobData& rData )
537 {
538 #if ENABLE_DBUS && ENABLE_GIO
539     std::unordered_map<OUString, CPDPrinter *>::iterator dest_it =
540         m_aCPDDestMap.find( rData.m_aPrinterName );
541 
542     if( dest_it == m_aCPDDestMap.end() )
543         return PrinterInfoManager::setupJobContextData( rData );
544 
545     std::unordered_map< OUString, Printer >::iterator p_it =
546         m_aPrinters.find( rData.m_aPrinterName );
547     if( p_it == m_aPrinters.end() ) // huh ?
548     {
549         SAL_WARN("vcl.unx.print", "CPD printer list in disorder, "
550                  "no dest for printer " << rData.m_aPrinterName);
551         return;
552     }
553 
554     if( p_it->second.m_aInfo.m_pParser == nullptr )
555     {
556         // in turn calls createCPDParser
557         // which updates the printer info
558         p_it->second.m_aInfo.m_pParser = PPDParser::getParser( p_it->second.m_aInfo.m_aDriverName );
559     }
560     if( p_it->second.m_aInfo.m_aContext.getParser() == nullptr )
561     {
562         OUString aPrinter;
563         if( p_it->second.m_aInfo.m_aDriverName.startsWith("CPD:") )
564             aPrinter = p_it->second.m_aInfo.m_aDriverName.copy( 4 );
565         else
566             aPrinter = p_it->second.m_aInfo.m_aDriverName;
567 
568         p_it->second.m_aInfo.m_aContext = m_aDefaultContexts[ aPrinter ];
569     }
570 
571     rData.m_pParser     = p_it->second.m_aInfo.m_pParser;
572     rData.m_aContext    = p_it->second.m_aInfo.m_aContext;
573 #else
574     (void)rData;
575 #endif
576 }
577 
578 FILE* CPDManager::startSpool( const OUString& rPrintername, bool bQuickCommand )
579 {
580 #if ENABLE_DBUS && ENABLE_GIO
581     SAL_INFO( "vcl.unx.print", "startSpool: " << rPrintername << " " << (bQuickCommand ? "true" : "false") );
582     if( m_aCPDDestMap.find( rPrintername ) == m_aCPDDestMap.end() )
583     {
584         SAL_INFO( "vcl.unx.print", "defer to PrinterInfoManager::startSpool" );
585         return PrinterInfoManager::startSpool( rPrintername, bQuickCommand );
586     }
587     OUString aTmpURL, aTmpFile;
588     osl_createTempFile( nullptr, nullptr, &aTmpURL.pData );
589     osl_getSystemPathFromFileURL( aTmpURL.pData, &aTmpFile.pData );
590     OString aSysFile = OUStringToOString( aTmpFile, osl_getThreadTextEncoding() );
591     FILE* fp = fopen( aSysFile.getStr(), "w" );
592     if( fp )
593         m_aSpoolFiles[fp] = aSysFile;
594 
595     return fp;
596 #else
597     (void)rPrintername;
598     (void)bQuickCommand;
599     return nullptr;
600 #endif
601 }
602 
603 #if ENABLE_DBUS && ENABLE_GIO
604 void CPDManager::getOptionsFromDocumentSetup( const JobData& rJob, bool bBanner, const OString& rJobName, int& rNumOptions, GVariant **arr )
605 {
606     GVariantBuilder *builder;
607     builder = g_variant_builder_new(G_VARIANT_TYPE("a(ss)"));
608     g_variant_builder_add(builder, "(ss)", "job-name", rJobName.getStr());
609     if( rJob.m_pParser ==  rJob.m_aContext.getParser() &&  rJob.m_pParser ) {
610         std::size_t i;
611         std::size_t nKeys = rJob.m_aContext.countValuesModified();
612         ::std::vector< const PPDKey* > aKeys( nKeys );
613         for(  i = 0; i < nKeys; i++ )
614             aKeys[i] = rJob.m_aContext.getModifiedKey( i );
615         for( i = 0; i < nKeys; i++ ) {
616             const PPDKey* pKey = aKeys[i];
617             const PPDValue* pValue = rJob.m_aContext.getValue( pKey );
618             OUString sPayLoad;
619             if (pValue) {
620                 sPayLoad = pValue->m_bCustomOption ? pValue->m_aCustomOption : pValue->m_aOption;
621             }
622             if (!sPayLoad.isEmpty()) {
623                 OString aKey = OUStringToOString( pKey->getKey(), RTL_TEXTENCODING_ASCII_US );
624                 OString aValue = OUStringToOString( sPayLoad, RTL_TEXTENCODING_ASCII_US );
625                 if (aKey.equals("Duplex")) {
626                     aKey = OString("sides");
627                 } else if (aKey.equals("Resolution")) {
628                     aKey = OString("printer-resolution");
629                 } else if (aKey.equals("PageSize")) {
630                     aKey = OString("media");
631                 }
632                 if (aKey.equals("sides")) {
633                     if (aValue.equals("None")) {
634                         aValue = OString("one-sided");
635                     } else if (aValue.equals("DuplexNoTumble")) {
636                         aValue = OString("two-sided-long-edge");
637                     } else if (aValue.equals("DuplexTumble")) {
638                         aValue = OString("two-sided-short-edge");
639                     }
640                 }
641                 g_variant_builder_add(builder, "(ss)", aKey.getStr(), aValue.getStr());
642             }
643         }
644     }
645     if( rJob.m_nPDFDevice > 0 && rJob.m_nCopies > 1 )
646     {
647         OString aVal( OString::number( rJob.m_nCopies ) );
648         g_variant_builder_add(builder, "(ss)", "copies", aVal.getStr());
649         rNumOptions++;
650         // TODO: something for collate
651         // Maybe this is the equivalent ipp attribute:
652         if (rJob.m_bCollate) {
653             g_variant_builder_add(builder, "(ss)", "multiple-document-handling", "separate-documents-collated-copies");
654         } else {
655             g_variant_builder_add(builder, "(ss)", "multiple-document-handling", "separate-documents-uncollated-copies");
656         }
657         rNumOptions++;
658     }
659     if( ! bBanner )
660     {
661         g_variant_builder_add(builder, "(ss)", "job-sheets", "none");
662         rNumOptions++;
663     }
664     if (rJob.m_eOrientation == orientation::Portrait) {
665         g_variant_builder_add(builder, "(ss)", "orientation-requested", "portrait");
666         rNumOptions++;
667     } else if (rJob.m_eOrientation == orientation::Landscape) {
668         g_variant_builder_add(builder, "(ss)", "orientation-requested", "landscape");
669         rNumOptions++;
670     }
671     (*arr) = g_variant_new("a(ss)", builder);
672     g_variant_builder_unref(builder);
673 }
674 #endif
675 
676 bool CPDManager::endSpool( const OUString& rPrintername, const OUString& rJobTitle, FILE* pFile, const JobData& rDocumentJobData, bool bBanner, const OUString& rFaxNumber )
677 {
678     bool success = false;
679 #if ENABLE_DBUS && ENABLE_GIO
680     SAL_INFO( "vcl.unx.print", "endSpool: " << rPrintername << "," << rJobTitle << " copy count = " << rDocumentJobData.m_nCopies );
681     std::unordered_map< OUString, CPDPrinter * >::iterator dest_it =
682         m_aCPDDestMap.find( rPrintername );
683     if( dest_it == m_aCPDDestMap.end() )
684     {
685         SAL_INFO( "vcl.unx.print", "defer to PrinterInfoManager::endSpool" );
686         return PrinterInfoManager::endSpool( rPrintername, rJobTitle, pFile, rDocumentJobData, bBanner, rFaxNumber );
687     }
688 
689     std::unordered_map< FILE*, OString, FPtrHash >::const_iterator it = m_aSpoolFiles.find( pFile );
690     if( it != m_aSpoolFiles.end() )
691     {
692         fclose( pFile );
693         rtl_TextEncoding aEnc = osl_getThreadTextEncoding();
694         OString sJobName(OUStringToOString(rJobTitle, aEnc));
695         if (!rFaxNumber.isEmpty())
696         {
697             sJobName = OUStringToOString(rFaxNumber, aEnc);
698         }
699         OString aSysFile = it->second;
700         CPDPrinter* pDest = dest_it->second;
701         GVariant* ret;
702         gint job_id;
703         int nNumOptions = 0;
704         GVariant *pArr = nullptr;
705         getOptionsFromDocumentSetup( rDocumentJobData, bBanner, sJobName, nNumOptions, &pArr );
706         ret = g_dbus_proxy_call_sync (pDest->backend, "printFile",
707                                       g_variant_new(
708                                                     "(ssi@a(ss))",
709                                                     (pDest->id),
710                                                     aSysFile.getStr(),
711                                                     nNumOptions,
712                                                     pArr
713                                                     ),
714                                       G_DBUS_CALL_FLAGS_NONE,
715                                       -1, nullptr, nullptr);
716         g_variant_get (ret, "(i)", &job_id);
717         if (job_id != -1) {
718             success = true;
719         }
720         g_variant_unref(ret);
721         unlink( it->second.getStr() );
722         m_aSpoolFiles.erase( pFile );
723     }
724 #else
725     (void)rPrintername;
726     (void)rJobTitle;
727     (void)pFile;
728     (void)rDocumentJobData;
729     (void)bBanner;
730     (void)rFaxNumber;
731 #endif
732     return success;
733 }
734 
735 bool CPDManager::checkPrintersChanged( bool )
736 {
737 #if ENABLE_DBUS && ENABLE_GIO
738     bool bChanged = m_aPrintersChanged;
739     m_aPrintersChanged = false;
740     g_dbus_connection_emit_signal (m_pConnection,
741                                    nullptr,
742                                    "/org/libreoffice/PrintDialog",
743                                    "org.openprinting.PrintFrontend",
744                                    "RefreshBackend",
745                                    nullptr,
746                                    nullptr);
747     return bChanged;
748 #else
749     return false;
750 #endif
751 }
752 
753 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
754 
755