/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include using namespace psp; using namespace osl; #if ENABLE_DBUS && ENABLE_GIO // Function to execute when name is acquired on the bus void CPDManager::onNameAcquired (GDBusConnection *connection, const gchar *, gpointer user_data) { gchar* contents; GDBusNodeInfo *introspection_data; // Get Interface for introspection g_file_get_contents (FRONTEND_INTERFACE, &contents, nullptr, nullptr); introspection_data = g_dbus_node_info_new_for_xml (contents, nullptr); g_dbus_connection_register_object (connection, "/org/libreoffice/PrintDialog", introspection_data->interfaces[0], nullptr, nullptr, /* user_data */ nullptr, /* user_data_free_func */ nullptr); /* GError** */ g_free(contents); g_dbus_node_info_unref(introspection_data); CPDManager* current = static_cast(user_data); std::vector> backends = current->getTempBackends(); for (auto const& backend : backends) { GDBusProxy *proxy; // Get Interface for introspection g_file_get_contents (BACKEND_INTERFACE, &contents, nullptr, nullptr); introspection_data = g_dbus_node_info_new_for_xml (contents, nullptr); proxy = g_dbus_proxy_new_sync (connection, G_DBUS_PROXY_FLAGS_NONE, introspection_data->interfaces[0], backend.first.c_str(), backend.second, "org.openprinting.PrintBackend", nullptr, nullptr); g_free(backend.second); g_assert (proxy != nullptr); g_dbus_proxy_call(proxy, "ActivateBackend", nullptr, G_DBUS_CALL_FLAGS_NONE, -1, nullptr, nullptr, nullptr); g_free(contents); g_object_unref(proxy); g_dbus_node_info_unref(introspection_data); } } void CPDManager::onNameLost (GDBusConnection *, const gchar *name, gpointer) { g_message("Name Lost: %s", name); } void CPDManager::printerAdded (GDBusConnection *connection, const gchar *sender_name, const gchar *object_path, const gchar *interface_name, const gchar *, GVariant *parameters, gpointer user_data) { CPDManager* current = static_cast(user_data); GDBusProxy *proxy; proxy = current->getProxy(sender_name); if (proxy == nullptr) { gchar* contents; GDBusNodeInfo *introspection_data; // Get Interface for introspection g_file_get_contents ("/usr/share/dbus-1/interfaces/org.openprinting.Backend.xml", &contents, nullptr, nullptr); introspection_data = g_dbus_node_info_new_for_xml (contents, nullptr); proxy = g_dbus_proxy_new_sync (connection, G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, introspection_data->interfaces[0], sender_name, object_path, interface_name, nullptr, nullptr); g_free(contents); g_dbus_node_info_unref(introspection_data); std::pair new_backend (sender_name, proxy); current->addBackend(new_backend); } CPDPrinter *pDest = static_cast(malloc(sizeof(CPDPrinter))); pDest->backend = proxy; 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)); std::stringstream printerName; printerName << pDest->name << ", " << pDest->backend_name; std::stringstream uniqueName; uniqueName << pDest->id << ", " << pDest->backend_name; rtl_TextEncoding aEncoding = osl_getThreadTextEncoding(); OUString aPrinterName = OStringToOUString( printerName.str().c_str(), aEncoding ); OUString aUniqueName = OStringToOUString( uniqueName.str().c_str(), aEncoding ); current->addNewPrinter(aPrinterName, aUniqueName, pDest); } void CPDManager::printerRemoved (GDBusConnection *, const gchar *, const gchar *, const gchar *, const gchar *, GVariant *parameters, gpointer user_data) { // TODO: Remove every data linked to this particular printer. CPDManager* pManager = static_cast(user_data); char* id; char* backend_name; g_variant_get (parameters, "(ss)", &id, &backend_name); std::stringstream uniqueName; uniqueName << id << ", " << backend_name; rtl_TextEncoding aEncoding = osl_getThreadTextEncoding(); OUString aUniqueName = OStringToOUString( uniqueName.str().c_str(), aEncoding ); std::unordered_map::iterator it = pManager->m_aCPDDestMap.find( aUniqueName ); if (it == pManager->m_aCPDDestMap.end()) { SAL_WARN("vcl.unx.print", "CPD trying to remove non-existent printer from list"); return; } pManager->m_aCPDDestMap.erase(it); std::unordered_map::iterator printersIt = pManager->m_aPrinters.find( aUniqueName ); if (printersIt == pManager->m_aPrinters.end()) { SAL_WARN("vcl.unx.print", "CPD trying to remove non-existent printer from m_aPrinters"); return; } pManager->m_aPrinters.erase(printersIt); } GDBusProxy* CPDManager::getProxy(const std::string& target) { std::unordered_map::const_iterator it = m_pBackends.find(target); if (it == m_pBackends.end()) { return nullptr; } return it->second; } void CPDManager::addBackend(std::pair pair) { m_pBackends.insert(pair); } void CPDManager::addTempBackend(const std::pair& pair) { m_tBackends.push_back(pair); } std::vector> const & CPDManager::getTempBackends() const { return m_tBackends; } void CPDManager::addNewPrinter(const OUString& aPrinterName, const OUString& aUniqueName, CPDPrinter *pDest) { m_aCPDDestMap[aUniqueName] = pDest; bool bSetToGlobalDefaults = m_aPrinters.find( aUniqueName ) == m_aPrinters.end(); Printer aPrinter = m_aPrinters[ aUniqueName ]; if( bSetToGlobalDefaults ) aPrinter.m_aInfo = m_aGlobalDefaults; aPrinter.m_aInfo.m_aPrinterName = aPrinterName; // TODO: I don't know how this should work when we have multiple // sources with multiple possible defaults for each // if( pDest->is_default ) // m_aDefaultPrinter = aPrinterName; rtl_TextEncoding aEncoding = osl_getThreadTextEncoding(); aPrinter.m_aInfo.m_aComment = OStringToOUString(pDest->info, aEncoding); aPrinter.m_aInfo.m_aLocation = OStringToOUString(pDest->location, aEncoding); // note: the parser that goes with the PrinterInfo // is created implicitly by the JobData::operator=() // when it detects the NULL ptr m_pParser. // if we wanted to fill in the parser here this // would mean we'd have to send a dbus message for each and // every printer - which would be really bad runtime // behaviour aPrinter.m_aInfo.m_pParser = nullptr; aPrinter.m_aInfo.m_aContext.setParser( nullptr ); std::unordered_map< OUString, PPDContext >::const_iterator c_it = m_aDefaultContexts.find( aUniqueName ); if( c_it != m_aDefaultContexts.end() ) { aPrinter.m_aInfo.m_pParser = c_it->second.getParser(); aPrinter.m_aInfo.m_aContext = c_it->second; } aPrinter.m_aInfo.setDefaultBackend(true); aPrinter.m_aInfo.m_aDriverName = "CPD:" + aUniqueName; m_aPrinters[ aUniqueName ] = aPrinter; } #endif /* * CPDManager class */ CPDManager* CPDManager::tryLoadCPD() { CPDManager* pManager = nullptr; #if ENABLE_DBUS && ENABLE_GIO static const char* pEnv = getenv("SAL_DISABLE_CPD"); if (!pEnv || !*pEnv) { // interface description XML files are needed in 'onNameAcquired()' if (!g_file_test(FRONTEND_INTERFACE, G_FILE_TEST_IS_REGULAR) || !g_file_test(BACKEND_INTERFACE, G_FILE_TEST_IS_REGULAR)) { return nullptr; } GDir *dir; const gchar *filename; dir = g_dir_open(BACKEND_DIR, 0, nullptr); if (dir != nullptr) { while ((filename = g_dir_read_name(dir))) { if (pManager == nullptr) { pManager = new CPDManager(); } gchar* contents; std::stringstream filepath; filepath << BACKEND_DIR << '/' << filename; g_file_get_contents(filepath.str().c_str(), &contents, nullptr, nullptr); std::pair new_tbackend (filename, contents); pManager->addTempBackend(new_tbackend); } g_dir_close(dir); } } #endif return pManager; } CPDManager::CPDManager() : PrinterInfoManager( PrinterInfoManager::Type::CPD ) { #if ENABLE_DBUS && ENABLE_GIO // Get Destinations number and pointers GError *error = nullptr; m_pConnection = g_bus_get_sync (G_BUS_TYPE_SESSION, nullptr, &error); g_assert_no_error (error); #endif } CPDManager::~CPDManager() { #if ENABLE_DBUS && ENABLE_GIO g_dbus_connection_emit_signal (m_pConnection, nullptr, "/org/libreoffice/PrintDialog", "org.openprinting.PrintFrontend", "StopListing", nullptr, nullptr); g_dbus_connection_flush_sync (m_pConnection, nullptr, nullptr); g_dbus_connection_close_sync (m_pConnection, nullptr, nullptr); for (auto const& backend : m_pBackends) { g_object_unref(backend.second); } for (auto const& backend : m_aCPDDestMap) { free(backend.second); } #endif } const PPDParser* CPDManager::createCPDParser( const OUString& rPrinter ) { const PPDParser* pNewParser = nullptr; #if ENABLE_DBUS && ENABLE_GIO OUString aPrinter; if( rPrinter.startsWith("CPD:") ) aPrinter = rPrinter.copy( 4 ); else aPrinter = rPrinter; std::unordered_map< OUString, CPDPrinter * >::iterator dest_it = m_aCPDDestMap.find( aPrinter ); if( dest_it != m_aCPDDestMap.end() ) { CPDPrinter* pDest = dest_it->second; GVariant* ret = nullptr; GError* error = nullptr; ret = g_dbus_proxy_call_sync (pDest->backend, "GetAllOptions", g_variant_new("(s)", (pDest->id)), G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error); if (ret != nullptr && error == nullptr) { // TODO: These keys need to be redefined to preserve usage across libreoffice // InputSlot - media-col.media-source? // Font - not needed now as it is required only for ps and we are using pdf // Dial? - for FAX (need to look up PWG spec) int num_attribute; GVariantIter *iter_attr, *iter_supported_values; g_variant_get (ret, "(ia(ssia(s)))", &num_attribute, &iter_attr); rtl_TextEncoding aEncoding = osl_getThreadTextEncoding(); PPDKey *pKey = nullptr; OUString aValueName; PPDValue* pValue; std::vector keys; std::vector default_values; for (int i = 0; i < num_attribute; i++) { char *name, *default_value; int num_supported_values; g_variant_iter_loop(iter_attr, "(ssia(s))", &name, &default_value, &num_supported_values, &iter_supported_values); OUString aOptionName = OStringToOUString( name, aEncoding ); OUString aDefaultValue = OStringToOUString( default_value, aEncoding ); if (aOptionName == "sides") { // Duplex key is used throughout for checking Duplex Support aOptionName = OUString("Duplex"); } else if (aOptionName == "printer-resolution") { // Resolution key is used in places aOptionName = OUString("Resolution"); } else if (aOptionName == "media") { // PageSize key is used in many places aOptionName = OUString("PageSize"); } default_values.push_back(aDefaultValue); pKey = new PPDKey( aOptionName ); // If number of values are 0, this is not settable via UI if (num_supported_values > 0 && aDefaultValue != "NA") pKey->m_bUIOption = true; bool bDefaultFound = false; for (int j = 0; j < num_supported_values; j++) { char* value; g_variant_iter_loop(iter_supported_values, "(s)", &value); aValueName = OStringToOUString( value, aEncoding ); if (aOptionName == "Duplex") { // Duplex key matches against very specific Values if (aValueName == "one-sided") { aValueName = OUString("None"); } else if (aValueName == "two-sided-long-edge") { aValueName = OUString("DuplexNoTumble"); } else if (aValueName == "two-sided-short-edge") { aValueName = OUString("DuplexTumble"); } } pValue = pKey->insertValue( aValueName, eQuoted ); if( ! pValue ) continue; pValue->m_aValue = aValueName; if (aValueName.equals(aDefaultValue)) { pKey->m_pDefaultValue = pValue; bDefaultFound = true; } } // This could be done to ensure default values also appear as options: if (!bDefaultFound && pKey->m_bUIOption) { // pValue = pKey->insertValue( aDefaultValue, eQuoted ); // if( pValue ) // pValue->m_aValue = aDefaultValue; } keys.emplace_back(pKey); } pKey = new PPDKey("ModelName"); aValueName = OStringToOUString( "", aEncoding ); pValue = pKey->insertValue( aValueName, eQuoted ); if( pValue ) pValue->m_aValue = aValueName; pKey->m_pDefaultValue = pValue; keys.emplace_back(pKey); pKey = new PPDKey("NickName"); aValueName = OStringToOUString( pDest->name, aEncoding ); pValue = pKey->insertValue( aValueName, eQuoted ); if( pValue ) pValue->m_aValue = aValueName; pKey->m_pDefaultValue = pValue; keys.emplace_back(pKey); pNewParser = new PPDParser(aPrinter, keys); PrinterInfo& rInfo = m_aPrinters[ aPrinter ].m_aInfo; PPDContext& rContext = m_aDefaultContexts[ aPrinter ]; rContext.setParser( pNewParser ); setDefaultPaper( rContext ); std::vector::iterator defit = default_values.begin(); for (auto const& key : keys) { const PPDValue* p1Value = key->getValue( *defit ); if( p1Value ) { if( p1Value != key->getDefaultValue() ) { rContext.setValue( key, p1Value, true ); SAL_INFO("vcl.unx.print", "key " << pKey->getKey() << " is set to " << *defit); } else SAL_INFO("vcl.unx.print", "key " << pKey->getKey() << " is defaulted to " << *defit); } ++defit; } rInfo.m_pParser = pNewParser; rInfo.m_aContext = rContext; g_variant_unref(ret); } else { g_clear_error(&error); SAL_INFO("vcl.unx.print", "CPD GetAllOptions failed, falling back to generic driver"); } } else SAL_INFO("vcl.unx.print", "no dest found for printer " << aPrinter); if( ! pNewParser ) { // get the default PPD pNewParser = PPDParser::getParser( "SGENPRT" ); SAL_WARN("vcl.unx.print", "Parsing default SGENPRT PPD" ); PrinterInfo& rInfo = m_aPrinters[ aPrinter ].m_aInfo; rInfo.m_pParser = pNewParser; rInfo.m_aContext.setParser( pNewParser ); } #else (void)rPrinter; #endif return pNewParser; } void CPDManager::initialize() { // get normal printers, clear printer list PrinterInfoManager::initialize(); #if ENABLE_DBUS && ENABLE_GIO g_bus_own_name_on_connection (m_pConnection, "org.libreoffice.print-dialog", G_BUS_NAME_OWNER_FLAGS_NONE, onNameAcquired, onNameLost, this, nullptr); g_dbus_connection_signal_subscribe (m_pConnection, // DBus Connection nullptr, // Sender Name "org.openprinting.PrintBackend", // Sender Interface "PrinterAdded", // Signal Name nullptr, // Object Path nullptr, // arg0 behaviour G_DBUS_SIGNAL_FLAGS_NONE, // Signal Flags printerAdded, // Callback Function this, nullptr); g_dbus_connection_signal_subscribe (m_pConnection, // DBus Connection nullptr, // Sender Name "org.openprinting.PrintBackend", // Sender Interface "PrinterRemoved", // Signal Name nullptr, // Object Path nullptr, // arg0 behaviour G_DBUS_SIGNAL_FLAGS_NONE, // Signal Flags printerRemoved, // Callback Function this, nullptr); // remove everything that is not a CUPS printer and not // a special purpose printer (PDF, Fax) std::unordered_map< OUString, Printer >::iterator it = m_aPrinters.begin(); while (it != m_aPrinters.end()) { if( m_aCPDDestMap.find( it->first ) != m_aCPDDestMap.end() ) { ++it; continue; } if( !it->second.m_aInfo.m_aFeatures.isEmpty() ) { ++it; continue; } it = m_aPrinters.erase(it); } #endif } void CPDManager::setupJobContextData( JobData& rData ) { #if ENABLE_DBUS && ENABLE_GIO std::unordered_map::iterator dest_it = m_aCPDDestMap.find( rData.m_aPrinterName ); if( dest_it == m_aCPDDestMap.end() ) return PrinterInfoManager::setupJobContextData( rData ); std::unordered_map< OUString, Printer >::iterator p_it = m_aPrinters.find( rData.m_aPrinterName ); if( p_it == m_aPrinters.end() ) // huh ? { SAL_WARN("vcl.unx.print", "CPD printer list in disorder, " "no dest for printer " << rData.m_aPrinterName); return; } if( p_it->second.m_aInfo.m_pParser == nullptr ) { // in turn calls createCPDParser // which updates the printer info p_it->second.m_aInfo.m_pParser = PPDParser::getParser( p_it->second.m_aInfo.m_aDriverName ); } if( p_it->second.m_aInfo.m_aContext.getParser() == nullptr ) { OUString aPrinter; if( p_it->second.m_aInfo.m_aDriverName.startsWith("CPD:") ) aPrinter = p_it->second.m_aInfo.m_aDriverName.copy( 4 ); else aPrinter = p_it->second.m_aInfo.m_aDriverName; p_it->second.m_aInfo.m_aContext = m_aDefaultContexts[ aPrinter ]; } rData.m_pParser = p_it->second.m_aInfo.m_pParser; rData.m_aContext = p_it->second.m_aInfo.m_aContext; #else (void)rData; #endif } FILE* CPDManager::startSpool( const OUString& rPrintername, bool bQuickCommand ) { #if ENABLE_DBUS && ENABLE_GIO SAL_INFO( "vcl.unx.print", "startSpool: " << rPrintername << " " << (bQuickCommand ? "true" : "false") ); if( m_aCPDDestMap.find( rPrintername ) == m_aCPDDestMap.end() ) { SAL_INFO( "vcl.unx.print", "defer to PrinterInfoManager::startSpool" ); return PrinterInfoManager::startSpool( rPrintername, bQuickCommand ); } OUString aTmpURL, aTmpFile; osl_createTempFile( nullptr, nullptr, &aTmpURL.pData ); osl_getSystemPathFromFileURL( aTmpURL.pData, &aTmpFile.pData ); OString aSysFile = OUStringToOString( aTmpFile, osl_getThreadTextEncoding() ); FILE* fp = fopen( aSysFile.getStr(), "w" ); if( fp ) m_aSpoolFiles[fp] = aSysFile; return fp; #else (void)rPrintername; (void)bQuickCommand; return nullptr; #endif } #if ENABLE_DBUS && ENABLE_GIO void CPDManager::getOptionsFromDocumentSetup( const JobData& rJob, bool bBanner, const OString& rJobName, int& rNumOptions, GVariant **arr ) { GVariantBuilder *builder; builder = g_variant_builder_new(G_VARIANT_TYPE("a(ss)")); g_variant_builder_add(builder, "(ss)", "job-name", rJobName.getStr()); if( rJob.m_pParser == rJob.m_aContext.getParser() && rJob.m_pParser ) { std::size_t i; std::size_t nKeys = rJob.m_aContext.countValuesModified(); ::std::vector< const PPDKey* > aKeys( nKeys ); for( i = 0; i < nKeys; i++ ) aKeys[i] = rJob.m_aContext.getModifiedKey( i ); for( i = 0; i < nKeys; i++ ) { const PPDKey* pKey = aKeys[i]; const PPDValue* pValue = rJob.m_aContext.getValue( pKey ); OUString sPayLoad; if (pValue) { sPayLoad = pValue->m_bCustomOption ? pValue->m_aCustomOption : pValue->m_aOption; } if (!sPayLoad.isEmpty()) { OString aKey = OUStringToOString( pKey->getKey(), RTL_TEXTENCODING_ASCII_US ); OString aValue = OUStringToOString( sPayLoad, RTL_TEXTENCODING_ASCII_US ); if (aKey.equals("Duplex")) { aKey = OString("sides"); } else if (aKey.equals("Resolution")) { aKey = OString("printer-resolution"); } else if (aKey.equals("PageSize")) { aKey = OString("media"); } if (aKey.equals("sides")) { if (aValue.equals("None")) { aValue = OString("one-sided"); } else if (aValue.equals("DuplexNoTumble")) { aValue = OString("two-sided-long-edge"); } else if (aValue.equals("DuplexTumble")) { aValue = OString("two-sided-short-edge"); } } g_variant_builder_add(builder, "(ss)", aKey.getStr(), aValue.getStr()); } } } if( rJob.m_nPDFDevice > 0 && rJob.m_nCopies > 1 ) { OString aVal( OString::number( rJob.m_nCopies ) ); g_variant_builder_add(builder, "(ss)", "copies", aVal.getStr()); rNumOptions++; // TODO: something for collate // Maybe this is the equivalent ipp attribute: if (rJob.m_bCollate) { g_variant_builder_add(builder, "(ss)", "multiple-document-handling", "separate-documents-collated-copies"); } else { g_variant_builder_add(builder, "(ss)", "multiple-document-handling", "separate-documents-uncollated-copies"); } rNumOptions++; } if( ! bBanner ) { g_variant_builder_add(builder, "(ss)", "job-sheets", "none"); rNumOptions++; } if (rJob.m_eOrientation == orientation::Portrait) { g_variant_builder_add(builder, "(ss)", "orientation-requested", "portrait"); rNumOptions++; } else if (rJob.m_eOrientation == orientation::Landscape) { g_variant_builder_add(builder, "(ss)", "orientation-requested", "landscape"); rNumOptions++; } (*arr) = g_variant_new("a(ss)", builder); g_variant_builder_unref(builder); } #endif bool CPDManager::endSpool( const OUString& rPrintername, const OUString& rJobTitle, FILE* pFile, const JobData& rDocumentJobData, bool bBanner, const OUString& rFaxNumber ) { bool success = false; #if ENABLE_DBUS && ENABLE_GIO SAL_INFO( "vcl.unx.print", "endSpool: " << rPrintername << "," << rJobTitle << " copy count = " << rDocumentJobData.m_nCopies ); std::unordered_map< OUString, CPDPrinter * >::iterator dest_it = m_aCPDDestMap.find( rPrintername ); if( dest_it == m_aCPDDestMap.end() ) { SAL_INFO( "vcl.unx.print", "defer to PrinterInfoManager::endSpool" ); return PrinterInfoManager::endSpool( rPrintername, rJobTitle, pFile, rDocumentJobData, bBanner, rFaxNumber ); } std::unordered_map< FILE*, OString, FPtrHash >::const_iterator it = m_aSpoolFiles.find( pFile ); if( it != m_aSpoolFiles.end() ) { fclose( pFile ); rtl_TextEncoding aEnc = osl_getThreadTextEncoding(); OString sJobName(OUStringToOString(rJobTitle, aEnc)); if (!rFaxNumber.isEmpty()) { sJobName = OUStringToOString(rFaxNumber, aEnc); } OString aSysFile = it->second; CPDPrinter* pDest = dest_it->second; GVariant* ret; gint job_id; int nNumOptions = 0; GVariant *pArr = nullptr; getOptionsFromDocumentSetup( rDocumentJobData, bBanner, sJobName, nNumOptions, &pArr ); ret = g_dbus_proxy_call_sync (pDest->backend, "printFile", g_variant_new( "(ssi@a(ss))", (pDest->id), aSysFile.getStr(), nNumOptions, pArr ), G_DBUS_CALL_FLAGS_NONE, -1, nullptr, nullptr); g_variant_get (ret, "(i)", &job_id); if (job_id != -1) { success = true; } g_variant_unref(ret); unlink( it->second.getStr() ); m_aSpoolFiles.erase( pFile ); } #else (void)rPrintername; (void)rJobTitle; (void)pFile; (void)rDocumentJobData; (void)bBanner; (void)rFaxNumber; #endif return success; } bool CPDManager::checkPrintersChanged( bool ) { #if ENABLE_DBUS && ENABLE_GIO bool bChanged = m_aPrintersChanged; m_aPrintersChanged = false; g_dbus_connection_emit_signal (m_pConnection, nullptr, "/org/libreoffice/PrintDialog", "org.openprinting.PrintFrontend", "RefreshBackend", nullptr, nullptr); return bChanged; #else return false; #endif } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */