diff --git a/configure-printer/ConfigureDialog.cpp b/configure-printer/ConfigureDialog.cpp index 9f2a098..02c67e4 100644 --- a/configure-printer/ConfigureDialog.cpp +++ b/configure-printer/ConfigureDialog.cpp @@ -1,209 +1,209 @@ /*************************************************************************** * Copyright (C) 2010-2018 by Daniel Nicoletti * * dantti12@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; see the file COPYING. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "ConfigureDialog.h" #include "PrinterPage.h" #include "ModifyPrinter.h" #include "PrinterBehavior.h" #include "PrinterOptions.h" #include "Debug.h" #include "KCupsRequest.h" #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(QList) ConfigureDialog::ConfigureDialog(const QString &destName, bool isClass, QWidget *parent) : KPageDialog(parent) { setFaceType(List); setModal(false); setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Apply); setWindowTitle(destName); setWindowIcon(QIcon::fromTheme(QLatin1String("configure"))); enableButtonApply(false); // Needed so we have our dialog size saved setAttribute(Qt::WA_DeleteOnClose); QStringList attr; KPageWidgetItem *page; modifyPrinter = new ModifyPrinter(destName, isClass, this); auto printerBehavior = new PrinterBehavior(destName, isClass, this); attr << modifyPrinter->neededValues(); attr << printerBehavior->neededValues(); attr << KCUPS_PRINTER_TYPE; // needed to know if it's a remote printer attr << KCUPS_PRINTER_MAKE_AND_MODEL; KCupsPrinter printer; QPointer request = new KCupsRequest; request->getPrinterAttributes(destName, isClass, attr); request->waitTillFinished(); if (!request) { return; } if (!request->hasError() && !request->printers().isEmpty()){ printer = request->printers().first(); } -// qCDebug(PM_CONFIGURE_PRINTER) << "VALUES" << printer.a rgument(); +// qCDebug(PM_CONFIGURE_PRINTER) << "VALUES" << printer.argument(); // qCDebug(PM_CONFIGURE_PRINTER) << "marker" << values["marker-levels"].value >(); request->deleteLater(); // qCDebug(PM_CONFIGURE_PRINTER) << values; if (printer.type() & CUPS_PRINTER_LOCAL) { qCDebug(PM_CONFIGURE_PRINTER) << "CUPS_PRINTER_LOCAL"; } isClass = printer.isClass(); bool isRemote = false; if (printer.type() & CUPS_PRINTER_REMOTE) { qCDebug(PM_CONFIGURE_PRINTER) << "CUPS_PRINTER_REMOTE"; isRemote = true; } if (printer.type() & CUPS_PRINTER_BW) { qCDebug(PM_CONFIGURE_PRINTER) << "CUPS_PRINTER_BW"; } if (printer.type() & CUPS_PRINTER_COLOR) { qCDebug(PM_CONFIGURE_PRINTER) << "CUPS_PRINTER_COLOR"; } if (printer.type() & CUPS_PRINTER_MFP) { qCDebug(PM_CONFIGURE_PRINTER) << "CUPS_PRINTER_MFP"; } modifyPrinter->setRemote(isRemote); modifyPrinter->setValues(printer); page = new KPageWidgetItem(modifyPrinter, i18n("Modify Printer")); page->setHeader(i18n("Configure")); page->setIcon(QIcon::fromTheme(QLatin1String("dialog-information"))); // CONNECT this signal ONLY to the first Page connect(modifyPrinter, &ModifyPrinter::changed, this, &ConfigureDialog::enableButtonApply); addPage(page); if (!isClass) { // At least on localhost:631 modify printer does not show printer options // for classes printerOptions = new PrinterOptions(destName, isClass, isRemote, this); page = new KPageWidgetItem(printerOptions, i18n("Printer Options")); page->setHeader(i18n("Set the Default Printer Options")); page->setIcon(QIcon::fromTheme(QLatin1String("view-pim-tasks"))); addPage(page); connect(modifyPrinter, &ModifyPrinter::ppdChanged, this, &ConfigureDialog::ppdChanged); modifyPrinter->setCurrentMake(printerOptions->currentMake()); modifyPrinter->setCurrentMakeAndModel(printerOptions->currentMakeAndModel()); } printerBehavior->setRemote(isRemote); printerBehavior->setValues(printer); page = new KPageWidgetItem(printerBehavior, i18n("Banners, Policies and Allowed Users")); page->setHeader(i18n("Banners, Policies and Allowed Users")); page->setIcon(QIcon::fromTheme(QLatin1String("feed-subscribe"))); addPage(page); // connect this after ALL pages were added, otherwise the slot will be called connect(this, &ConfigureDialog::currentPageChanged, this, &ConfigureDialog::currentPageChangedSlot); KConfigGroup group(KSharedConfig::openConfig(QLatin1String("print-manager")), "ConfigureDialog"); KWindowConfig::restoreWindowSize(windowHandle(), group); connect(buttonBox(), &QDialogButtonBox::clicked, this, &ConfigureDialog::slotButtonClicked); } void ConfigureDialog::ppdChanged() { printerOptions->reloadPPD(); modifyPrinter->setCurrentMake(printerOptions->currentMake()); modifyPrinter->setCurrentMakeAndModel(printerOptions->currentMakeAndModel()); } ConfigureDialog::~ConfigureDialog() { KConfigGroup group(KSharedConfig::openConfig(QLatin1String("print-manager")), "ConfigureDialog"); KWindowConfig::saveWindowSize(windowHandle(), group); } void ConfigureDialog::currentPageChangedSlot(KPageWidgetItem *current, KPageWidgetItem *before) { auto currentPage = qobject_cast(current->widget()); auto beforePage = qobject_cast(before->widget()); qCDebug(PM_CONFIGURE_PRINTER) << "currentPageChanged" << beforePage << currentPage; // Check if the before page has changes savePage(beforePage); if (beforePage) { disconnect(beforePage, &PrinterPage::changed, this, &ConfigureDialog::enableButtonApply); } // connect the changed signal to the new page and check if it has changes connect(currentPage, &PrinterPage::changed, this, &ConfigureDialog::enableButtonApply); enableButtonApply(currentPage->hasChanges()); } void ConfigureDialog::enableButtonApply(bool enable) { qDebug() << Q_FUNC_INFO << enable << sender(); button(QDialogButtonBox::QDialogButtonBox::Apply)->setEnabled(enable); } void ConfigureDialog::slotButtonClicked(QAbstractButton * pressedButton) { auto page = qobject_cast(currentPage()->widget()); if (pressedButton == button(QDialogButtonBox::Ok) || pressedButton == button(QDialogButtonBox::Apply)) { page->save(); } } void ConfigureDialog::closeEvent(QCloseEvent *event) { auto page = qobject_cast(currentPage()->widget()); if (savePage(page)) { event->accept(); } else { event->ignore(); } } bool ConfigureDialog::savePage(PrinterPage *page) { if (page->hasChanges()) { int ret; ret = KMessageBox::warningYesNoCancel(this, i18n("The current page has changes.\n" "Do you want to save them?")); if (ret == KMessageBox::Yes) { page->save(); } else if (ret == KMessageBox::Cancel) { return false; } } return true; } #include "moc_ConfigureDialog.cpp" diff --git a/configure-printer/ConfigurePrinterInterface.cpp b/configure-printer/ConfigurePrinterInterface.cpp index 12a2109..0016eb0 100644 --- a/configure-printer/ConfigurePrinterInterface.cpp +++ b/configure-printer/ConfigurePrinterInterface.cpp @@ -1,124 +1,124 @@ /*************************************************************************** * Copyright (C) 2010-2018 Daniel Nicoletti * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ #include "ConfigurePrinterInterface.h" #include "configureprinteradaptor.h" #include "ConfigureDialog.h" #include "Debug.h" #include #include #include #include #include #include ConfigurePrinterInterface::ConfigurePrinterInterface(QObject *parent) : QObject(parent) { qCDebug(PM_CONFIGURE_PRINTER) << "Creating Helper"; (void) new ConfigurePrinterAdaptor(this); if (!QDBusConnection::sessionBus().registerService(QLatin1String("org.kde.ConfigurePrinter"))) { qCDebug(PM_CONFIGURE_PRINTER) << "another helper is already running"; return; } if (!QDBusConnection::sessionBus().registerObject(QLatin1String("/"), this)) { qCDebug(PM_CONFIGURE_PRINTER) << "unable to register service interface to dbus"; return; } // setup the timer that updates the UIs m_updateUi = new QTimer(this); m_updateUi->setInterval(1000); m_updateUi->start(); } ConfigurePrinterInterface::~ConfigurePrinterInterface() { } void ConfigurePrinterInterface::ConfigurePrinter(const QString &destName) { if (!m_uis.contains(destName)) { // Reserve this since the CUPS call might take a long time m_uis[destName] = nullptr; // Get destinations with these attributes QPointer request = new KCupsRequest; request->getPrinters({ KCUPS_PRINTER_NAME, KCUPS_PRINTER_TYPE }); request->waitTillFinished(); if (!request) { return; } bool found = false; KCupsPrinter printer; const KCupsPrinters printers = request->printers(); for (const KCupsPrinter &p : printers) { if (p.name() == destName) { printer = p; found = true; break; } } request->deleteLater(); if (found) { auto ui = new ConfigureDialog(printer.name(), printer.isClass()); connect(m_updateUi, &QTimer::timeout, ui, static_cast(&ConfigureDialog::update)); connect(ui, &ConfigureDialog::finished, this, &ConfigurePrinterInterface::RemovePrinter); ui->show(); m_uis[printer.name()] = ui; } else { // Remove the reservation m_uis.remove(destName); // if no destination was found and we aren't showing // a queue quit the app if (m_uis.isEmpty()) { emit quit(); } return; } } - // Check it it's not reserved + // Check if it's not reserved if (m_uis.value(destName)) { KWindowSystem::forceActiveWindow(m_uis.value(destName)->winId()); } } void ConfigurePrinterInterface::RemovePrinter() { auto ui = qobject_cast(sender()); if (ui) { m_uis.remove(m_uis.key(ui)); } // if no destination was found and we aren't showing // a queue quit the app if (m_uis.isEmpty()) { emit quit(); } } #include "moc_ConfigurePrinterInterface.cpp" diff --git a/configure-printer/PrinterBehavior.cpp b/configure-printer/PrinterBehavior.cpp index b4807f9..7933ce0 100644 --- a/configure-printer/PrinterBehavior.cpp +++ b/configure-printer/PrinterBehavior.cpp @@ -1,329 +1,329 @@ /*************************************************************************** * Copyright (C) 2010-2018 by Daniel Nicoletti * * dantti12@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; see the file COPYING. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "PrinterBehavior.h" #include "ui_PrinterBehavior.h" #include "Debug.h" #include PrinterBehavior::PrinterBehavior(const QString &destName, bool isClass, QWidget *parent) : PrinterPage(parent), ui(new Ui::PrinterBehavior), m_destName(destName), m_isClass(isClass) { ui->setupUi(this); connect(ui->errorPolicyCB, static_cast(&QComboBox::currentIndexChanged), this, &PrinterBehavior::currentIndexChangedCB); connect(ui->operationPolicyCB, static_cast(&QComboBox::currentIndexChanged), this, &PrinterBehavior::currentIndexChangedCB); connect(ui->startingBannerCB, static_cast(&QComboBox::currentIndexChanged), this, &PrinterBehavior::currentIndexChangedCB); connect(ui->endingBannerCB, static_cast(&QComboBox::currentIndexChanged), this, &PrinterBehavior::currentIndexChangedCB); connect(ui->usersELB, &KEditListWidget::changed, this, &PrinterBehavior::userListChanged); connect(ui->allowRB, &QRadioButton::toggled, this, &PrinterBehavior::userListChanged); } PrinterBehavior::~PrinterBehavior() { delete ui; } void PrinterBehavior::setValues(const KCupsPrinter &printer) { int defaultChoice; ui->errorPolicyCB->clear(); const QStringList errorPolicySupported = printer.errorPolicySupported(); for (const QString &value : errorPolicySupported) { ui->errorPolicyCB->addItem(errorPolicyString(value), value); } const QStringList errorPolicy = printer.errorPolicy(); if (!errorPolicy.isEmpty()) { defaultChoice = ui->errorPolicyCB->findData(errorPolicy.first()); ui->errorPolicyCB->setCurrentIndex(defaultChoice); ui->errorPolicyCB->setProperty("defaultChoice", defaultChoice); } ui->operationPolicyCB->clear(); const QStringList opPolicySupported = printer.opPolicySupported(); for (const QString &value : opPolicySupported) { ui->operationPolicyCB->addItem(operationPolicyString(value), value); } const QStringList operationPolicy = printer.opPolicy(); if (!errorPolicy.isEmpty()) { defaultChoice = ui->operationPolicyCB->findData(operationPolicy.first()); ui->operationPolicyCB->setCurrentIndex(defaultChoice); ui->operationPolicyCB->setProperty("defaultChoice", defaultChoice); } ui->startingBannerCB->clear(); ui->endingBannerCB->clear(); const QStringList jobSheetsSupported = printer.jobSheetsSupported(); for (const QString &value : jobSheetsSupported) { ui->startingBannerCB->addItem(jobSheetsString(value), value); ui->endingBannerCB->addItem(jobSheetsString(value), value); } const QStringList bannerPolicy = printer.jobSheetsDefault(); if (bannerPolicy.size() == 2) { defaultChoice = ui->startingBannerCB->findData(bannerPolicy.at(0)); ui->startingBannerCB->setCurrentIndex(defaultChoice); ui->startingBannerCB->setProperty("defaultChoice", defaultChoice); defaultChoice = ui->endingBannerCB->findData(bannerPolicy.at(1)); ui->endingBannerCB->setCurrentIndex(defaultChoice); ui->endingBannerCB->setProperty("defaultChoice", defaultChoice); } if (!printer.requestingUserNameAllowed().isEmpty()) { QStringList list = printer.requestingUserNameAllowed(); - list.sort(); // sort the list here to be able to comapare it later + list.sort(); // sort the list here to be able to compare it later ui->usersELB->setEnabled(true); if (list != ui->usersELB->items()) { ui->usersELB->clear(); ui->usersELB->insertStringList(list); } ui->usersELB->setProperty("defaultList", list); ui->allowRB->setProperty("defaultChoice", true); // Set checked AFTER the default choice was set - // otherwise the signal will be emmited + // otherwise the signal will be emitted // which sets that we have a change ui->allowRB->setChecked(true); } else if (!printer.requestingUserNameDenied().isEmpty()) { QStringList list = printer.requestingUserNameDenied(); - list.sort(); // sort the list here to be able to comapare it later + list.sort(); // sort the list here to be able to compare it later ui->usersELB->setEnabled(true); if (list != ui->usersELB->items()) { ui->usersELB->clear(); ui->usersELB->insertStringList(list); } ui->usersELB->setProperty("defaultList", list); ui->allowRB->setProperty("defaultChoice", false); // Set checked AFTER the default choice was set - // otherwise the signal will be emmited + // otherwise the signal will be emitted // which sets that we have a change ui->preventRB->setChecked(true); } // Clear previous changes m_changes = 0; emit changed(false); m_changedValues.clear(); ui->errorPolicyCB->setProperty("different", false); ui->operationPolicyCB->setProperty("different", false); ui->startingBannerCB->setProperty("different", false); ui->endingBannerCB->setProperty("different", false); ui->usersELB->setProperty("different", false); } void PrinterBehavior::userListChanged() { if (ui->usersELB->isEnabled() == false && (ui->allowRB->isChecked() || ui->preventRB->isChecked())) { // this only happen when the list was empty ui-> usersELB->setEnabled(true); } QStringList currentList = ui->usersELB->items(); // sort the list so we can be sure it's different currentList.sort(); const QStringList defaultList = ui->usersELB->property("defaultList").toStringList(); bool isDifferent = currentList != defaultList; if (isDifferent == false && currentList.isEmpty() == false) { // if the lists are equal and not empty the user might have // changed the Radio Button... if (ui->allowRB->isChecked() != ui->allowRB->property("defaultChoice").toBool()) { isDifferent = true; } } if (isDifferent != ui->usersELB->property("different").toBool()) { // it's different from the last time so add or remove changes isDifferent ? m_changes++ : m_changes--; ui->usersELB->setProperty("different", isDifferent); emit changed(m_changes); } } void PrinterBehavior::currentIndexChangedCB(int index) { auto comboBox = qobject_cast(sender()); bool isDifferent = comboBox->property("defaultChoice").toInt() != index; qCDebug(PM_CONFIGURE_PRINTER) << Q_FUNC_INFO << "isDifferent" << isDifferent << this; if (isDifferent != comboBox->property("different").toBool()) { // it's different from the last time so add or remove changes isDifferent ? m_changes++ : m_changes--; comboBox->setProperty("different", isDifferent); qCDebug(PM_CONFIGURE_PRINTER) << Q_FUNC_INFO << m_changes << this; emit changed(m_changes); } const QString attribute = comboBox->property("AttributeName").toString(); QVariant value; // job-sheets-default has always two values if (attribute == QLatin1String("job-sheets-default")) { value = QStringList({ ui->startingBannerCB->itemData(ui->startingBannerCB->currentIndex()).toString(), ui->endingBannerCB->itemData(ui->endingBannerCB->currentIndex()).toString() }); } else { value = comboBox->itemData(index).toString(); } // store the new values if (isDifferent) { m_changedValues[attribute] = value; } else { m_changedValues.remove(attribute); } } QString PrinterBehavior::errorPolicyString(const QString &policy) const { // TODO search for others policies of printer-error-policy-supported if (policy == QLatin1String("abort-job")) { return i18n("Abort job"); } else if (policy == QLatin1String("retry-current-job")) { return i18n("Retry current job"); } else if (policy == QLatin1String("retry-job")) { return i18n("Retry job"); } else if (policy == QLatin1String("stop-printer")) { return i18n("Stop printer"); } return policy; } QString PrinterBehavior::operationPolicyString(const QString &policy) const { // TODO search for others policies of printer-error-policy-supported if (policy == QLatin1String("authenticated")) { return i18n("Authenticated"); } else if (policy == QLatin1String("default")) { return i18n("Default"); } return policy; } QString PrinterBehavior::jobSheetsString(const QString &policy) const { // TODO search for others policies of printer-error-policy-supported if (policy == QLatin1String("none")) { return i18n("None"); } else if (policy == QLatin1String("classified")) { return i18n("Classified"); } else if (policy == QLatin1String("confidential")) { return i18n("Confidential"); } else if (policy == QLatin1String("secret")) { return i18n("Secret"); } else if (policy == QLatin1String("standard")) { return i18n("Standard"); } else if (policy == QLatin1String("topsecret")) { return i18n("Topsecret"); } else if (policy == QLatin1String("unclassified")) { return i18n("Unclassified"); } return policy; } void PrinterBehavior::save() { if (m_changes) { QVariantHash changedValues = m_changedValues; // since a QStringList might be big we get it here instead // of adding it at edit time. if (ui->usersELB->property("different").toBool()) { QStringList list = ui->usersELB->items(); if (list.isEmpty()) { list << QLatin1String("all"); changedValues[KCUPS_REQUESTING_USER_NAME_ALLOWED] = list; } else { if (ui->allowRB->isChecked()) { changedValues[KCUPS_REQUESTING_USER_NAME_ALLOWED] = list; } else { changedValues[KCUPS_REQUESTING_USER_NAME_DENIED] = list; } } } QPointer request = new KCupsRequest; if (m_isClass) { request->addOrModifyClass(m_destName, changedValues); } else { request->addOrModifyPrinter(m_destName, changedValues); } request->waitTillFinished(); if (request) { if (!request->hasError()) { request->getPrinterAttributes(m_destName, m_isClass, neededValues()); request->waitTillFinished(); if (request && !request->hasError() && !request->printers().isEmpty()){ KCupsPrinter printer = request->printers().first(); setValues(printer); } } request->deleteLater(); } } } void PrinterBehavior::setRemote(bool remote) { ui->errorPolicyCB->setEnabled(!remote); ui->operationPolicyCB->setEnabled(!remote); ui->startingBannerCB->setEnabled(!remote); ui->endingBannerCB->setEnabled(!remote); ui->allowRB->setEnabled(!remote); ui->preventRB->setEnabled(!remote); ui->usersELB->setEnabled(!remote); } bool PrinterBehavior::hasChanges() { return m_changes; } QStringList PrinterBehavior::neededValues() const { return QStringList({ KCUPS_JOB_SHEETS_DEFAULT, KCUPS_JOB_SHEETS_SUPPORTED, KCUPS_PRINTER_ERROR_POLICY, KCUPS_PRINTER_ERROR_POLICY_SUPPORTED, KCUPS_PRINTER_OP_POLICY, KCUPS_PRINTER_OP_POLICY_SUPPORTED, KCUPS_REQUESTING_USER_NAME_ALLOWED, KCUPS_REQUESTING_USER_NAME_DENIED }); } #include "moc_PrinterBehavior.cpp" diff --git a/configure-printer/PrinterOptions.cpp b/configure-printer/PrinterOptions.cpp index a8176b5..cbd4ef0 100644 --- a/configure-printer/PrinterOptions.cpp +++ b/configure-printer/PrinterOptions.cpp @@ -1,828 +1,828 @@ /*************************************************************************** * Copyright (C) 2010-2018 by Daniel Nicoletti * * dantti12@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; see the file COPYING. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * * * - * The save PPD snipet is from CUPS * + * The save PPD snippet is from CUPS * * Copyright 2007-2009 by Apple Inc. * * Copyright 1997-2007 by Easy Software Products. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * This is converted from LGPL 2 in accordance with section 3 * * See http://www.cups.org/documentation.php/license.html * ***************************************************************************/ #include "PrinterOptions.h" #include "ui_PrinterOptions.h" #include "Debug.h" #include #include #include #include #include #include #include #include #include #include #include #define DEFAULT_CHOICE "defaultChoice" PrinterOptions::PrinterOptions(const QString &destName, bool isClass, bool isRemote, QWidget *parent) : PrinterPage(parent), ui(new Ui::PrinterOptions), m_destName(destName), m_isClass(isClass), m_isRemote(isRemote) { ui->setupUi(this); reloadPPD(); } void PrinterOptions::on_autoConfigurePB_clicked() { QPointer request = new KCupsRequest; request->printCommand(m_destName, QLatin1String("AutoConfigure"), i18n("Set Default Options")); request->waitTillFinished(); if (request) { request->deleteLater(); } } void PrinterOptions::reloadPPD() { // The caller "owns" the file that is created and must unlink the returned filename. if (!m_filename.isEmpty()) { unlink(qUtf8Printable(m_filename)); } // remove all the options while (ui->verticalLayout->count()) { qCDebug(PM_CONFIGURE_PRINTER) << "removing" << ui->verticalLayout->count(); QLayoutItem *item = ui->verticalLayout->itemAt(0); ui->verticalLayout->removeItem(item); if (item->widget()) { item->widget()->deleteLater(); delete item; } else if (item->layout()) { qCDebug(PM_CONFIGURE_PRINTER) << "removing layout" << ui->verticalLayout->count(); // item->layout()->deleteLater(); } else if (item->spacerItem()) { delete item->spacerItem(); } } m_changes = 0; m_customValues.clear(); emit changed(false); QPointer request = new KCupsRequest; request->getPrinterPPD(m_destName); request->waitTillFinished(); if (!request) { return; } m_filename = request->printerPPD(); m_ppd = ppdOpenFile(qUtf8Printable(m_filename)); request->deleteLater(); if (m_ppd == nullptr) { qCWarning(PM_CONFIGURE_PRINTER) << "Could not open ppd file:" << m_filename << request->errorMsg(); m_filename.clear(); return; } ppdLocalize(m_ppd); // select the default options on the ppd file ppdMarkDefaults(m_ppd); // TODO try to use QTextCodec aliases const char *lang_encoding; lang_encoding = m_ppd->lang_encoding; if (lang_encoding && !strcasecmp (lang_encoding, "ISOLatin1")) { m_codec = QTextCodec::codecForName("ISO-8859-1"); } else if (lang_encoding && !strcasecmp (lang_encoding, "ISOLatin2")) { m_codec = QTextCodec::codecForName("ISO-8859-2"); } else if (lang_encoding && !strcasecmp (lang_encoding, "ISOLatin5")) { m_codec = QTextCodec::codecForName("ISO-8859-5"); } else if (lang_encoding && !strcasecmp (lang_encoding, "JIS83-RKSJ")) { m_codec = QTextCodec::codecForName("SHIFT-JIS"); } else if (lang_encoding && !strcasecmp (lang_encoding, "MacStandard")) { m_codec = QTextCodec::codecForName("MACINTOSH"); } else if (lang_encoding && !strcasecmp (lang_encoding, "WindowsANSI")) { m_codec = QTextCodec::codecForName("WINDOWS-1252"); } else { // Guess m_codec = QTextCodec::codecForName(lang_encoding); } if (m_codec == nullptr) { m_codec = QTextCodec::codecForName("UTF-8"); } if (m_ppd->manufacturer) { m_make = m_codec->toUnicode(m_ppd->manufacturer); } if (m_ppd->nickname) { m_makeAndModel = m_codec->toUnicode(m_ppd->nickname); } ui->autoConfigurePB->hide(); ppd_attr_t *ppdattr; if (m_ppd->num_filters == 0 || ((ppdattr = ppdFindAttr(m_ppd, "cupsCommands", nullptr)) != nullptr && ppdattr->value && strstr(ppdattr->value, "AutoConfigure"))) { ui->autoConfigurePB->show(); } else { for (int i = 0; i < m_ppd->num_filters; i ++) { if (!strncmp(m_ppd->filters[i], "application/vnd.cups-postscript", 31)) { ui->autoConfigurePB->show(); break; } } } createGroups(); } void PrinterOptions::createGroups() { int i; ppd_group_t *group; // Iterate over the groups for (i = 0, group = m_ppd->groups; i < m_ppd->num_groups; i++, group++) { // The name of the group QString name = m_codec->toUnicode(group->name); - // The humman name of the group + // The human name of the group QString text = m_codec->toUnicode(group->text); // The group box were the options will be laid out auto groupBox = new QGroupBox(text, ui->scrollArea); // Create the form layout to put options in auto gFormLayout = new QFormLayout(groupBox); gFormLayout->setFormAlignment(Qt::AlignCenter); groupBox->setLayout(gFormLayout); ui->verticalLayout->addWidget(groupBox); int j; ppd_option_t *option; // Iterate over the options in the group for (j = 0, option = group->options; j < group->num_options; j++, option++) { QString oKeyword = m_codec->toUnicode(option->keyword); QString oText = m_codec->toUnicode(option->text); QString oDefChoice = m_codec->toUnicode(option->defchoice); // The python system-config-printer skips this one // which has the same data as "PageSize", let's hope // they did the right thing if (oKeyword == QLatin1String("PageRegion")) { continue; } QWidget *optionW = nullptr; switch (option->ui) { case PPD_UI_BOOLEAN: optionW = pickBoolean(option, oKeyword, ui->scrollAreaWidgetContents); break; case PPD_UI_PICKMANY: optionW = pickMany(option, oKeyword, ui->scrollAreaWidgetContents); break; case PPD_UI_PICKONE: optionW = pickOne(option, oKeyword, ui->scrollAreaWidgetContents); break; default: qCWarning(PM_CONFIGURE_PRINTER) << "Option type not recognized: " << option->ui; // let's use the most common optionW = pickOne(option, oKeyword, ui->scrollAreaWidgetContents); break; } if (optionW) { // insert the option widget gFormLayout->addRow(oText, optionW); } } } ui->verticalLayout->addStretch(); } QWidget* PrinterOptions::pickBoolean(ppd_option_t *option, const QString &keyword, QWidget *parent) const { Q_UNUSED(keyword) auto widget = new QWidget(parent); auto layout = new QHBoxLayout(widget); auto radioGroup = new QButtonGroup(widget); widget->setLayout(layout); int i; ppd_choice_t *choice; QString defChoice = m_codec->toUnicode(option->defchoice); // Iterate over the choices in the option for (i = 0, choice = option->choices; i < option->num_choices; ++i, ++choice) { const QString choiceName = m_codec->toUnicode(choice->choice); const QString cText = m_codec->toUnicode(choice->text); auto button = new QRadioButton(cText, widget); button->setChecked(defChoice == choiceName); button->setProperty("choice", choiceName); // if we are in looking at a remote printer we can't save it button->setEnabled(!m_isRemote); layout->addWidget(button); radioGroup->addButton(button); } // store the default choice radioGroup->setProperty(DEFAULT_CHOICE, defChoice); radioGroup->setProperty("Keyword", keyword); connect(radioGroup, static_cast(&QButtonGroup::buttonClicked), this, &PrinterOptions::radioBtClicked); return widget; } void PrinterOptions::radioBtClicked(QAbstractButton *button) { QObject *radioGroup = sender(); bool isDifferent = radioGroup->property(DEFAULT_CHOICE).toString() != button->property("choice"); if (isDifferent != radioGroup->property("different").toBool()) { // it's different from the last time so add or remove changes isDifferent ? m_changes++ : m_changes--; radioGroup->setProperty("different", isDifferent); emit changed(m_changes); } QString keyword = radioGroup->property("Keyword").toString(); QString choice = button->property("choice").toString(); radioGroup->setProperty("currentChoice", choice); // TODO warning about conflicts // ppdMarkOption(m_ppd, // m_codec->fromUnicode(keyword), // m_codec->fromUnicode(choice)); // store the new value if (isDifferent) { m_customValues[keyword] = radioGroup; } else { m_customValues.remove(keyword); } } QWidget* PrinterOptions::pickMany(ppd_option_t *option, const QString &keyword, QWidget *parent) const { Q_UNUSED(keyword) auto listView = new QListView(parent); auto model = new QStandardItemModel(listView); listView->setModel(model); listView->setItemDelegate(new NoSelectionRectDelegate(listView)); int i; ppd_choice_t *choice; const QString oDefChoice = m_codec->toUnicode(option->defchoice); // Iterate over the choices in the option for (i = 0, choice = option->choices; i < option->num_choices; ++i, ++choice) { const QString cName = m_codec->toUnicode(choice->choice); const QString cText = m_codec->toUnicode(choice->text); auto item = new QStandardItem(cText); item->setData(cName); item->setCheckable(true); item->setEditable(false); // TODO there is only ONE default choice, what about the other // Items selected?! item->setCheckState(oDefChoice == cName ? Qt::Checked : Qt::Unchecked); model->appendRow(item); } // if we are in looking at a remote printer we can't save it listView->setEnabled(!m_isRemote); return qobject_cast(listView); } QWidget* PrinterOptions::pickOne(ppd_option_t *option, const QString &keyword, QWidget *parent) const { int i; ppd_choice_t *choice; const QString defChoice = m_codec->toUnicode(option->defchoice); auto comboBox = new QComboBox(parent); // Iterate over the choices in the option for (i = 0, choice = option->choices; i < option->num_choices; ++i, ++choice) { const QString cName = m_codec->toUnicode(choice->choice); const QString cText = m_codec->toUnicode(choice->text); comboBox->addItem(cText, cName); } // store the default choice comboBox->setProperty(DEFAULT_CHOICE, defChoice); comboBox->setProperty("Keyword", keyword); comboBox->setCurrentIndex(comboBox->findData(defChoice)); // connect the signal AFTER setCurrentIndex is called connect(comboBox, static_cast(&QComboBox::currentIndexChanged), this, &PrinterOptions::currentIndexChangedCB); // if we are in looking at a remote printer we can't save it comboBox->setEnabled(!m_isRemote); return qobject_cast(comboBox); } void PrinterOptions::currentIndexChangedCB(int index) { auto comboBox = qobject_cast(sender()); bool isDifferent = comboBox->property(DEFAULT_CHOICE).toString() != comboBox->itemData(index); if (isDifferent != comboBox->property("different").toBool()) { // it's different from the last time so add or remove changes isDifferent ? m_changes++ : m_changes--; comboBox->setProperty("different", isDifferent); emit changed(m_changes); } QString keyword = comboBox->property("Keyword").toString(); QString value = comboBox->itemData(index).toString(); comboBox->setProperty("currentChoice", value); // TODO warning about conflicts // ppdMarkOption(m_ppd, // m_codec->fromUnicode(keyword), // m_codec->fromUnicode(value)); // store the new value if (isDifferent) { m_customValues[keyword] = qobject_cast(comboBox); } else { m_customValues.remove(keyword); } } PrinterOptions::~PrinterOptions() { if (m_ppd != nullptr) { ppdClose(m_ppd); } if (!m_filename.isEmpty()) { unlink(qUtf8Printable(m_filename)); } delete ui; } const char * /* O - Value of variable */ PrinterOptions::getVariable(const char *name) const /* I - Name of variable */ { const QString keyword = m_codec->toUnicode(name); auto it = m_customValues.constFind(keyword); if (it != m_customValues.constEnd()) { const QString value = it.value()->property("currentChoice").toString(); return m_codec->fromUnicode(value).constData(); } else { return nullptr; } } /* * 'get_points()' - Get a value in points. */ double /* O - Number in points */ PrinterOptions::get_points(double number, /* I - Original number */ const char *uval) /* I - Units */ { if (!strcmp(uval, "mm")) /* Millimeters */ return (number * 72.0 / 25.4); else if (!strcmp(uval, "cm")) /* Centimeters */ return (number * 72.0 / 2.54); else if (!strcmp(uval, "in")) /* Inches */ return (number * 72.0); else if (!strcmp(uval, "ft")) /* Feet */ return (number * 72.0 * 12.0); else if (!strcmp(uval, "m")) /* Meters */ return (number * 72.0 / 0.0254); else /* Points */ return (number); } /* * 'get_option_value()' - Return the value of an option. * * This function also handles generation of custom option values. */ char * /* O - Value string or nullptr on error */ PrinterOptions::get_option_value( ppd_file_t *ppd, /* I - PPD file */ const char *name, /* I - Option name */ char *buffer, /* I - String buffer */ size_t bufsize) const /* I - Size of buffer */ { char *bufptr, /* Pointer into buffer */ *bufend; /* End of buffer */ ppd_coption_t *coption; /* Custom option */ ppd_cparam_t *cparam; /* Current custom parameter */ char keyword[256]; /* Parameter name */ const char *val, /* Parameter value */ *uval; /* Units value */ long integer; /* Integer value */ double number, /* Number value */ number_points; /* Number in points */ /* * See if we have a custom option choice... */ if ((val = getVariable(name)) == nullptr) { /* * Option not found! */ return (nullptr); } else if (strcasecmp(val, "Custom") || (coption = ppdFindCustomOption(ppd, name)) == nullptr) { /* * Not a custom choice... */ qstrncpy(buffer, val, bufsize); return (buffer); } /* * OK, we have a custom option choice, format it... */ *buffer = '\0'; if (!strcmp(coption->keyword, "PageSize")) { const char *lval; /* Length string value */ double width, /* Width value */ width_points, /* Width in points */ length, /* Length value */ length_points; /* Length in points */ val = getVariable("PageSize.Width"); lval = getVariable("PageSize.Height"); uval = getVariable("PageSize.Units"); if (!val || !lval || !uval || (width = strtod(val, nullptr)) == 0.0 || (length = strtod(lval, nullptr)) == 0.0 || (strcmp(uval, "pt") && strcmp(uval, "in") && strcmp(uval, "ft") && strcmp(uval, "cm") && strcmp(uval, "mm") && strcmp(uval, "m"))) { return (nullptr); } width_points = get_points(width, uval); length_points = get_points(length, uval); if (width_points < ppd->custom_min[0] || width_points > ppd->custom_max[0] || length_points < ppd->custom_min[1] || length_points > ppd->custom_max[1]) { return (nullptr); } snprintf(buffer, bufsize, "Custom.%gx%g%s", width, length, uval); } else if (cupsArrayCount(coption->params) == 1) { cparam = ppdFirstCustomParam(coption); snprintf(keyword, sizeof(keyword), "%s.%s", coption->keyword, cparam->name); if ((val = getVariable(keyword)) == nullptr) return (nullptr); switch (cparam->type) { case PPD_CUSTOM_CURVE : case PPD_CUSTOM_INVCURVE : case PPD_CUSTOM_REAL : if ((number = strtod(val, nullptr)) == 0.0 || number < cparam->minimum.custom_real || number > cparam->maximum.custom_real) return (nullptr); snprintf(buffer, bufsize, "Custom.%g", number); break; case PPD_CUSTOM_INT : if (!*val || (integer = strtol(val, nullptr, 10)) == LONG_MIN || integer == LONG_MAX || integer < cparam->minimum.custom_int || integer > cparam->maximum.custom_int) return (nullptr); snprintf(buffer, bufsize, "Custom.%ld", integer); break; case PPD_CUSTOM_POINTS : snprintf(keyword, sizeof(keyword), "%s.Units", coption->keyword); if ((number = strtod(val, nullptr)) == 0.0 || (uval = getVariable(keyword)) == nullptr || (strcmp(uval, "pt") && strcmp(uval, "in") && strcmp(uval, "ft") && strcmp(uval, "cm") && strcmp(uval, "mm") && strcmp(uval, "m"))) return (nullptr); number_points = get_points(number, uval); if (number_points < cparam->minimum.custom_points || number_points > cparam->maximum.custom_points) return (nullptr); snprintf(buffer, bufsize, "Custom.%g%s", number, uval); break; case PPD_CUSTOM_PASSCODE : for (uval = val; *uval; ++uval) { if (!isdigit(*uval & 255)) { return (nullptr); } } case PPD_CUSTOM_PASSWORD : case PPD_CUSTOM_STRING : integer = (long)strlen(val); if (integer < cparam->minimum.custom_string || integer > cparam->maximum.custom_string) { return (nullptr); } snprintf(buffer, bufsize, "Custom.%s", val); break; } } else { const char *prefix = "{"; /* Prefix string */ bufptr = buffer; bufend = buffer + bufsize; for (cparam = ppdFirstCustomParam(coption); cparam; cparam = ppdNextCustomParam(coption)) { snprintf(keyword, sizeof(keyword), "%s.%s", coption->keyword, cparam->name); if ((val = getVariable(keyword)) == nullptr) { return (nullptr); } snprintf(bufptr, bufend - bufptr, "%s%s=", prefix, cparam->name); bufptr += strlen(bufptr); prefix = " "; switch (cparam->type) { case PPD_CUSTOM_CURVE : case PPD_CUSTOM_INVCURVE : case PPD_CUSTOM_REAL : if ((number = strtod(val, nullptr)) == 0.0 || number < cparam->minimum.custom_real || number > cparam->maximum.custom_real) return (nullptr); snprintf(bufptr, bufend - bufptr, "%g", number); break; case PPD_CUSTOM_INT : if (!*val || (integer = strtol(val, nullptr, 10)) == LONG_MIN || integer == LONG_MAX || integer < cparam->minimum.custom_int || integer > cparam->maximum.custom_int) { return (nullptr); } snprintf(bufptr, bufend - bufptr, "%ld", integer); break; case PPD_CUSTOM_POINTS : snprintf(keyword, sizeof(keyword), "%s.Units", coption->keyword); if ((number = strtod(val, nullptr)) == 0.0 || (uval = getVariable(keyword)) == nullptr || (strcmp(uval, "pt") && strcmp(uval, "in") && strcmp(uval, "ft") && strcmp(uval, "cm") && strcmp(uval, "mm") && strcmp(uval, "m"))) { return (nullptr); } number_points = get_points(number, uval); if (number_points < cparam->minimum.custom_points || number_points > cparam->maximum.custom_points) { return (nullptr); } snprintf(bufptr, bufend - bufptr, "%g%s", number, uval); break; case PPD_CUSTOM_PASSCODE : for (uval = val; *uval; uval ++) { if (!isdigit(*uval & 255)) { return (nullptr); } } case PPD_CUSTOM_PASSWORD : case PPD_CUSTOM_STRING : integer = (long)strlen(val); if (integer < cparam->minimum.custom_string || integer > cparam->maximum.custom_string) { return (nullptr); } if ((bufptr + 2) > bufend) { return (nullptr); } bufend --; *bufptr++ = '\"'; while (*val && bufptr < bufend) { if (*val == '\\' || *val == '\"') { if ((bufptr + 1) >= bufend) { return (nullptr); } *bufptr++ = '\\'; } *bufptr++ = *val++; } if (bufptr >= bufend) { return (nullptr); } *bufptr++ = '\"'; *bufptr = '\0'; bufend ++; break; } bufptr += strlen(bufptr); } if (bufptr == buffer || (bufend - bufptr) < 2) { return (nullptr); } strcpy(bufptr, "}"); } return (buffer); } void PrinterOptions::save() { char tempfile[1024]; const char *var; cups_file_t *in, /* Input file */ *out; /* Output file */ char line[1024], /* Line from PPD file */ value[1024], /* Option value */ keyword[1024], /* Keyword from Default line */ *keyptr; /* Pointer into keyword... */ // copy cups-1.4.2/cgi-bin line 3779 if (!m_filename.isEmpty()) { out = cupsTempFile2(tempfile, sizeof(tempfile)); in = cupsFileOpen(qUtf8Printable(m_filename), "r"); if (!in || !out) { if (in) { cupsFileClose(in); } if (out) { cupsFileClose(out); unlink(tempfile); } // TODO add a KMessageBox::error return; } while (cupsFileGets(in, line, sizeof(line))) { if (!strncmp(line, "*cupsProtocol:", 14)) { continue; } else if (strncmp(line, "*Default", 8)) { cupsFilePrintf(out, "%s\n", line); } else { /* * Get default option name... */ qstrncpy(keyword, line + 8, sizeof(keyword)); for (keyptr = keyword; *keyptr; keyptr ++) { if (*keyptr == ':' || isspace(*keyptr & 255)) { break; } } *keyptr = '\0'; if (!strcmp(keyword, "PageRegion") || !strcmp(keyword, "PaperDimension") || !strcmp(keyword, "ImageableArea")) { var = get_option_value(m_ppd, "PageSize", value, sizeof(value)); } else { var = get_option_value(m_ppd, keyword, value, sizeof(value)); } if (!var) { cupsFilePrintf(out, "%s\n", line); } else { cupsFilePrintf(out, "*Default%s: %s\n", keyword, var); } } } cupsFileClose(in); cupsFileClose(out); } else { // TODO add a KMessageBox::error qCWarning(PM_CONFIGURE_PRINTER) << "No printer PPD file set, can't save options."; return; } QVariantHash values; // we need null values QPointer request = new KCupsRequest; if (m_isClass) { request->addOrModifyClass(m_destName, values); } else { request->addOrModifyPrinter(m_destName, values, QString::fromUtf8(tempfile)); } // Disable the widget till the request is processed // Otherwise the user might change something in the ui // which won't be saved but the apply but when the request // finishes we will set the current options as default setEnabled(false); request->waitTillFinished(); // unlink the file unlink(tempfile); if (request) { setEnabled(true); if (!request->hasError()) { // if we succefully save the new ppd we need now to // clear our changes auto i = m_customValues.constBegin(); while (i != m_customValues.constEnd()) { QObject *obj = i.value(); const QString currentChoice = obj->property("currentChoice").toString(); // Store the current choice as the default one obj->setProperty(DEFAULT_CHOICE, currentChoice); obj->setProperty("currentChoice", QVariant()); obj->setProperty("different", false); ++i; } m_changes = 0; m_customValues.clear(); emit changed(false); } request->deleteLater(); } } bool PrinterOptions::hasChanges() { return m_changes; } QString PrinterOptions::currentMake() const { return m_make; } QString PrinterOptions::currentMakeAndModel() const { return m_makeAndModel; } #include "moc_PrinterOptions.cpp" diff --git a/libkcups/ClassListWidget.cpp b/libkcups/ClassListWidget.cpp index a65405b..54b0864 100644 --- a/libkcups/ClassListWidget.cpp +++ b/libkcups/ClassListWidget.cpp @@ -1,184 +1,184 @@ /*************************************************************************** * Copyright (C) 2010-2018 by Daniel Nicoletti * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; see the file COPYING. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "ClassListWidget.h" #include "SelectMakeModel.h" #include "KCupsRequest.h" #include "NoSelectionRectDelegate.h" #include #include #include #include #include ClassListWidget::ClassListWidget(QWidget *parent) : QListView(parent) { KConfigDialogManager::changedMap()->insert(QLatin1String("ClassListWidget"), SIGNAL(changed(QString))); m_model = new QStandardItemModel(this); setModel(m_model); setItemDelegate(new NoSelectionRectDelegate(this)); // Setup the busy cursor m_busySeq = new KPixmapSequenceOverlayPainter(this); m_busySeq->setSequence(KPixmapSequence(QLatin1String("process-working"), KIconLoader::SizeSmallMedium)); m_busySeq->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); m_busySeq->setWidget(viewport()); connect(m_model, &QStandardItemModel::dataChanged, this, &ClassListWidget::modelChanged); m_delayedInit.setInterval(0); m_delayedInit.setSingleShot(true); connect(&m_delayedInit, &QTimer::timeout, this, &ClassListWidget::init); m_delayedInit.start(); } ClassListWidget::~ClassListWidget() { } void ClassListWidget::init() { - m_busySeq->start(); // Start spining + m_busySeq->start(); // Start spinning m_model->clear(); QStringList att; att << KCUPS_PRINTER_NAME; att << KCUPS_PRINTER_URI_SUPPORTED; // Get destinations with these masks m_request = new KCupsRequest; connect(m_request, &KCupsRequest::finished, this, &ClassListWidget::loadFinished); if (m_showClasses) { m_request->getPrinters(att); } else { m_request->getPrinters(att, CUPS_PRINTER_CLASS | CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT); } } void ClassListWidget::loadFinished(KCupsRequest *request) { // If we have an old request running discard it's result and get a new one if (m_request != request) { request->deleteLater(); return; } - m_busySeq->stop(); // Stop spining + m_busySeq->stop(); // Stop spinning const KCupsPrinters printers = request->printers(); request->deleteLater(); m_request = nullptr; for (const KCupsPrinter &printer : printers) { QString destName = printer.name(); if (destName != m_printerName) { auto item = new QStandardItem; item->setText(destName); item->setCheckable(true); item->setEditable(false); item->setData(printer.uriSupported()); updateItemState(item); m_model->appendRow(item); } } modelChanged(); } void ClassListWidget::modelChanged() { QStringList currentMembers = currentSelected(false); m_changed = m_selectedPrinters != currentMembers; emit changed(selectedPrinters()); emit changed(m_changed); } QStringList ClassListWidget::currentSelected(bool uri) const { QStringList currentMembers; for (int i = 0; i < m_model->rowCount(); i++) { QStandardItem *item = m_model->item(i); if (item && item->checkState() == Qt::Checked) { if (uri) { currentMembers << item->data().toString(); } else { currentMembers << item->text(); } } } currentMembers.sort(); return currentMembers; } void ClassListWidget::updateItemState(QStandardItem *item) const { if (m_selectedPrinters.contains(item->text())) { item->setCheckState(Qt::Checked); } else { item->setCheckState(Qt::Unchecked); } } bool ClassListWidget::hasChanges() { return m_changed; } void ClassListWidget::setPrinter(const QString &printer) { if (m_printerName != printer) { m_printerName = printer; m_delayedInit.start(); } } QString ClassListWidget::selectedPrinters() const { return currentSelected(false).join(QLatin1String("|")); } void ClassListWidget::setSelectedPrinters(const QString &selected) { m_selectedPrinters = selected.split(QLatin1Char('|')); m_selectedPrinters.sort(); m_delayedInit.start(); } bool ClassListWidget::showClasses() const { return m_showClasses; } void ClassListWidget::setShowClasses(bool enable) { if (m_showClasses != enable) { m_showClasses = enable; m_delayedInit.start(); } } #include "moc_ClassListWidget.cpp" diff --git a/libkcups/JobModel.cpp b/libkcups/JobModel.cpp index 580ab04..4c77673 100644 --- a/libkcups/JobModel.cpp +++ b/libkcups/JobModel.cpp @@ -1,637 +1,637 @@ /*************************************************************************** * Copyright (C) 2010-2018 by Daniel Nicoletti * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; see the file COPYING. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "JobModel.h" #include #include #include #include #include #include #include #include #include #include #include JobModel::JobModel(QObject *parent) : QStandardItemModel(parent) { setHorizontalHeaderItem(ColStatus, new QStandardItem(i18n("Status"))); setHorizontalHeaderItem(ColName, new QStandardItem(i18n("Name"))); setHorizontalHeaderItem(ColUser, new QStandardItem(i18n("User"))); setHorizontalHeaderItem(ColCreated, new QStandardItem(i18n("Created"))); setHorizontalHeaderItem(ColCompleted, new QStandardItem(i18n("Completed"))); setHorizontalHeaderItem(ColPages, new QStandardItem(i18n("Pages"))); setHorizontalHeaderItem(ColProcessed, new QStandardItem(i18n("Processed"))); setHorizontalHeaderItem(ColSize, new QStandardItem(i18n("Size"))); setHorizontalHeaderItem(ColStatusMessage, new QStandardItem(i18n("Status Message"))); setHorizontalHeaderItem(ColPrinter, new QStandardItem(i18n("Printer"))); setHorizontalHeaderItem(ColFromHost, new QStandardItem(i18n("From Hostname"))); m_roles = QStandardItemModel::roleNames(); m_roles[RoleJobId] = "jobId"; m_roles[RoleJobState] = "jobState"; m_roles[RoleJobName] = "jobName"; m_roles[RoleJobPages] = "jobPages"; m_roles[RoleJobSize] = "jobSize"; m_roles[RoleJobOwner] = "jobOwner"; m_roles[RoleJobCreatedAt] = "jobCreatedAt"; m_roles[RoleJobIconName] = "jobIconName"; m_roles[RoleJobCancelEnabled] = "jobCancelEnabled"; m_roles[RoleJobHoldEnabled] = "jobHoldEnabled"; m_roles[RoleJobReleaseEnabled] = "jobReleaseEnabled"; m_roles[RoleJobRestartEnabled] = "jobRestartEnabled"; m_roles[RoleJobPrinter] = "jobPrinter"; m_roles[RoleJobOriginatingHostName] = "jobFrom"; // This is emitted when a job change it's state connect(KCupsConnection::global(), &KCupsConnection::jobState, this, &JobModel::insertUpdateJob); // This is emitted when a job is created connect(KCupsConnection::global(), &KCupsConnection::jobCreated, this, &JobModel::insertUpdateJob); // This is emitted when a job is stopped connect(KCupsConnection::global(), &KCupsConnection::jobStopped, this, &JobModel::insertUpdateJob); // This is emitted when a job has it's config changed connect(KCupsConnection::global(), &KCupsConnection::jobConfigChanged, this, &JobModel::insertUpdateJob); // This is emitted when a job change it's progress connect(KCupsConnection::global(), &KCupsConnection::jobProgress, this, &JobModel::insertUpdateJob); // This is emitted when a printer is removed connect(KCupsConnection::global(), &KCupsConnection::jobCompleted, this, &JobModel::jobCompleted); connect(KCupsConnection::global(), &KCupsConnection::serverAudit, this, &JobModel::getJobs); connect(KCupsConnection::global(), &KCupsConnection::serverStarted, this, &JobModel::getJobs); connect(KCupsConnection::global(), &KCupsConnection::serverStopped, this, &JobModel::getJobs); connect(KCupsConnection::global(), &KCupsConnection::serverRestarted, this, &JobModel::getJobs); } void JobModel::setParentWId(WId parentId) { m_parentId = parentId; } void JobModel::init(const QString &destName) { m_destName = destName; // Get all jobs getJobs(); } void JobModel::hold(const QString &printerName, int jobId) { QPointer request = new KCupsRequest; request->holdJob(printerName, jobId); request->waitTillFinished(); if (request) { request->deleteLater(); } } void JobModel::release(const QString &printerName, int jobId) { QPointer request = new KCupsRequest; request->releaseJob(printerName, jobId); request->waitTillFinished(); if (request) { request->deleteLater(); } } void JobModel::cancel(const QString &printerName, int jobId) { QPointer request = new KCupsRequest; request->cancelJob(printerName, jobId); request->waitTillFinished(); if (request) { request->deleteLater(); } } void JobModel::move(const QString &printerName, int jobId, const QString &toPrinterName) { QPointer request = new KCupsRequest; request->moveJob(printerName, jobId, toPrinterName); request->waitTillFinished(); if (request) { request->deleteLater(); } } void JobModel::getJobs() { if (m_jobRequest) { return; } m_jobRequest = new KCupsRequest; connect(m_jobRequest, &KCupsRequest::finished, this, &JobModel::getJobFinished); const static QStringList attrs({ KCUPS_JOB_ID, KCUPS_JOB_NAME, KCUPS_JOB_K_OCTETS, KCUPS_JOB_K_OCTETS_PROCESSED, KCUPS_JOB_STATE, KCUPS_TIME_AT_COMPLETED, KCUPS_TIME_AT_CREATION, KCUPS_TIME_AT_PROCESSING, KCUPS_JOB_PRINTER_URI, KCUPS_JOB_ORIGINATING_USER_NAME, KCUPS_JOB_ORIGINATING_HOST_NAME, KCUPS_JOB_MEDIA_PROGRESS, KCUPS_JOB_MEDIA_SHEETS, KCUPS_JOB_MEDIA_SHEETS_COMPLETED, KCUPS_JOB_PRINTER_STATE_MESSAGE, KCUPS_JOB_PRESERVED }); m_jobRequest->getJobs(m_destName, false, m_whichjobs, attrs); m_processingJob.clear(); } void JobModel::getJobFinished(KCupsRequest *request) { if (request) { if (request->hasError()) { // clear the model after so that the proper widget can be shown clear(); } else { const KCupsJobs jobs = request->jobs(); qCDebug(LIBKCUPS) << jobs.size(); for (int i = 0; i < jobs.size(); ++i) { const KCupsJob job = jobs.at(i); if (job.state() == IPP_JOB_PROCESSING) { m_processingJob = job.name(); } // try to find the job row const int job_row = jobRow(job.id()); if (job_row == -1) { // not found, insert new one insertJob(i, job); } else { // update the job updateJob(job_row, job); if (job_row != i) { // found at wrong position // take it and insert on the right position const QList row = takeRow(job_row); insertRow(i, row); } } } // remove old printers // The above code starts from 0 and make sure // dest == modelIndex(x) and if it's not the // case it either inserts or moves it. // so any item > num_jobs can be safely deleted while (rowCount() > jobs.size()) { removeRow(rowCount() - 1); } } request->deleteLater(); } else { qCWarning(LIBKCUPS) << "Should not be called from a non KCupsRequest class" << sender(); } m_jobRequest = nullptr; } void JobModel::jobCompleted(const QString &text, const QString &printerUri, const QString &printerName, uint printerState, const QString &printerStateReasons, bool printerIsAcceptingJobs, uint jobId, uint jobState, const QString &jobStateReasons, const QString &jobName, uint jobImpressionsCompleted) { // REALLY? all these parameters just to say foo was deleted?? Q_UNUSED(text) Q_UNUSED(printerUri) Q_UNUSED(printerName) Q_UNUSED(printerState) Q_UNUSED(printerStateReasons) Q_UNUSED(printerIsAcceptingJobs) Q_UNUSED(jobId) Q_UNUSED(jobState) Q_UNUSED(jobStateReasons) Q_UNUSED(jobName) Q_UNUSED(jobImpressionsCompleted) // We grab all jobs again getJobs(); } void JobModel::insertUpdateJob(const QString &text, const QString &printerUri, const QString &printerName, uint printerState, const QString &printerStateReasons, bool printerIsAcceptingJobs, uint jobId, uint jobState, const QString &jobStateReasons, const QString &jobName, uint jobImpressionsCompleted) { // REALLY? all these parameters just to say foo was created?? Q_UNUSED(text) Q_UNUSED(printerUri) Q_UNUSED(printerName) Q_UNUSED(printerState) Q_UNUSED(printerStateReasons) Q_UNUSED(printerIsAcceptingJobs) Q_UNUSED(jobId) Q_UNUSED(jobState) Q_UNUSED(jobStateReasons) Q_UNUSED(jobName) Q_UNUSED(jobImpressionsCompleted) // We grab all jobs again getJobs(); } void JobModel::insertJob(int pos, const KCupsJob &job) { // insert the first column which has the job state and id QList row; ipp_jstate_e jobState = job.state(); auto statusItem = new QStandardItem(jobStatus(jobState)); statusItem->setData(jobState, RoleJobState); statusItem->setData(job.id(), RoleJobId); statusItem->setData(job.name(), RoleJobName); statusItem->setData(job.originatingUserName(), RoleJobOwner); statusItem->setData(job.originatingHostName(), RoleJobOriginatingHostName); QString size = KFormat().formatByteSize(job.size()); statusItem->setData(size, RoleJobSize); QString createdAt = QLocale().toString(job.createdAt()); statusItem->setData(createdAt, RoleJobCreatedAt); // TODO move the update code before the insert and reuse some code... statusItem->setData(KCupsJob::iconName(jobState), RoleJobIconName); statusItem->setData(KCupsJob::cancelEnabled(jobState), RoleJobCancelEnabled); statusItem->setData(KCupsJob::holdEnabled(jobState), RoleJobHoldEnabled); statusItem->setData(KCupsJob::releaseEnabled(jobState), RoleJobReleaseEnabled); statusItem->setData(job.reprintEnabled(), RoleJobRestartEnabled); QString pages = QString::number(job.pages()); if (job.processedPages()) { pages = QString::number(job.processedPages()) + QLatin1Char('/') + QString::number(job.processedPages()); } if (statusItem->data(RoleJobPages) != pages) { statusItem->setData(pages, RoleJobPages); } row << statusItem; for (int i = ColName; i < LastColumn; i++) { // adds all Items to the model row << new QStandardItem; } // insert the whole row insertRow(pos, row); // update the items updateJob(pos, job); } void JobModel::updateJob(int pos, const KCupsJob &job) { // Job Status & internal dataipp_jstate_e ipp_jstate_e jobState = job.state(); QStandardItem *colStatus = item(pos, ColStatus); if (colStatus->data(RoleJobState).toInt() != jobState) { colStatus->setText(jobStatus(jobState)); colStatus->setData(static_cast(jobState), RoleJobState); colStatus->setData(KCupsJob::iconName(jobState), RoleJobIconName); colStatus->setData(KCupsJob::cancelEnabled(jobState), RoleJobCancelEnabled); colStatus->setData(KCupsJob::holdEnabled(jobState), RoleJobHoldEnabled); colStatus->setData(KCupsJob::releaseEnabled(jobState), RoleJobReleaseEnabled); colStatus->setData(job.reprintEnabled(), RoleJobRestartEnabled); } const QString pages = job.processedPages() ? QString::number(job.processedPages()) + QLatin1Char('/') + QString::number(job.processedPages()) : QString::number(job.pages()); if (colStatus->data(RoleJobPages) != pages) { colStatus->setData(pages, RoleJobPages); } // internal dest name & column const QString destName = job.printer(); if (colStatus->data(RoleJobPrinter).toString() != destName) { colStatus->setData(destName, RoleJobPrinter); // Column job printer Name item(pos, ColPrinter)->setText(destName); } // job name const QString jobName = job.name(); if (item(pos, ColName)->text() != jobName) { colStatus->setData(jobName, RoleJobName); item(pos, ColName)->setText(jobName); } // owner of the job // try to get the full user name QString userString = job.originatingUserName(); const KUser user(userString); if (user.isValid() && !user.property(KUser::FullName).toString().isEmpty()) { userString = user.property(KUser::FullName).toString(); } // user name QStandardItem *colUser = item(pos, ColUser); if (colUser->text() != userString) { colUser->setText(userString); } // when it was created const QDateTime timeAtCreation = job.createdAt(); QStandardItem *colCreated = item(pos, ColCreated); if (colCreated->data(Qt::DisplayRole).toDateTime() != timeAtCreation) { colCreated->setData(timeAtCreation, Qt::DisplayRole); } // when it was completed const QDateTime completedAt = job.completedAt(); QStandardItem *colCompleted = item(pos, ColCompleted); if (colCompleted->data(Qt::DisplayRole).toDateTime() != completedAt) { if (!completedAt.isNull()) { colCompleted->setData(completedAt, Qt::DisplayRole); } else { // Clean the data might happen when the job is restarted colCompleted->setText(QString()); } } // job pages const int completedPages = job.processedPages(); QStandardItem *colPages = item(pos, ColPages); if (colPages->data(Qt::UserRole) != completedPages) { colPages->setData(completedPages, Qt::UserRole); colPages->setText(QString::number(completedPages)); } - // when it was precessed + // when it was processed const QDateTime timeAtProcessing = job.processedAt(); QStandardItem *colProcessed = item(pos, ColProcessed); if (colProcessed->data(Qt::DisplayRole).toDateTime() != timeAtProcessing) { if (!timeAtProcessing.isNull()) { colProcessed->setData(timeAtProcessing, Qt::DisplayRole); } else { // Clean the data might happen when the job is restarted colCompleted->setText(QString()); } } int jobSize = job.size(); QStandardItem *colSize = item(pos, ColSize); if (colSize->data(Qt::UserRole) != jobSize) { colSize->setData(jobSize, Qt::UserRole); colSize->setText(KFormat().formatByteSize(jobSize)); } // job printer state message const QString stateMessage = job.stateMsg(); QStandardItem *colStatusMessage = item(pos, ColStatusMessage); if (colStatusMessage->text() != stateMessage) { colStatusMessage->setText(stateMessage); } // owner of the job // try to get the full user name const QString originatingHostName = job.originatingHostName(); QStandardItem *colFromHost = item(pos, ColFromHost); if (colFromHost->text() != originatingHostName) { colFromHost->setText(originatingHostName); } } QStringList JobModel::mimeTypes() const { return { QStringLiteral("application/x-cupsjobs") }; } Qt::DropActions JobModel::supportedDropActions() const { return Qt::MoveAction; } QMimeData* JobModel::mimeData(const QModelIndexList &indexes) const { auto mimeData = new QMimeData(); QByteArray encodedData; QDataStream stream(&encodedData, QIODevice::WriteOnly); for (const QModelIndex &index : indexes) { if (index.isValid() && index.column() == 0) { // serialize the jobId and fromDestName stream << data(index, RoleJobId).toInt() << data(index, RoleJobPrinter).toString() << item(index.row(), ColName)->text(); } } mimeData->setData(QLatin1String("application/x-cupsjobs"), encodedData); return mimeData; } bool JobModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_UNUSED(row) Q_UNUSED(column) Q_UNUSED(parent) if (action == Qt::IgnoreAction) { return true; } if (!data->hasFormat(QLatin1String("application/x-cupsjobs"))) { return false; } QByteArray encodedData = data->data(QLatin1String("application/x-cupsjobs")); QDataStream stream(&encodedData, QIODevice::ReadOnly); bool ret = false; while (!stream.atEnd()) { QString fromDestName, displayName; int jobId; // get the jobid and the from dest name stream >> jobId >> fromDestName >> displayName; if (fromDestName == m_destName) { continue; } QPointer request = new KCupsRequest; request->moveJob(fromDestName, jobId, m_destName); request->waitTillFinished(); if (request) { if (request->hasError()) { // failed to move one job // we return here to avoid more password tries KMessageBox::detailedSorryWId(m_parentId, i18n("Failed to move '%1' to '%2'", displayName, m_destName), request->errorMsg(), i18n("Failed")); } request->deleteLater(); ret = !request->hasError(); } } return ret; } QHash JobModel::roleNames() const { return m_roles; } KCupsRequest* JobModel::modifyJob(int row, JobAction action, const QString &newDestName, const QModelIndex &parent) { Q_UNUSED(parent) if (row < 0 || row >= rowCount()) { qCWarning(LIBKCUPS) << "Row number is invalid:" << row; return 0; } QStandardItem *job = item(row, ColStatus); int jobId = job->data(RoleJobId).toInt(); QString destName = job->data(RoleJobPrinter).toString(); // ignore some jobs ipp_jstate_t state = static_cast(job->data(RoleJobState).toInt()); if ((state == IPP_JOB_HELD && action == Hold) || (state == IPP_JOB_CANCELED && action == Cancel) || (state != IPP_JOB_HELD && action == Release)) { return 0; } auto request = new KCupsRequest; switch (action) { case Cancel: request->cancelJob(destName, jobId); break; case Hold: request->holdJob(destName, jobId); break; case Release: request->releaseJob(destName, jobId); break; case Reprint: request->restartJob(destName, jobId); break; case Move: request->moveJob(destName, jobId, newDestName); break; default: qCWarning(LIBKCUPS) << "Unknown ACTION called!!!" << action; return 0; } return request; } int JobModel::jobRow(int jobId) { // find the position of the jobId inside the model for (int i = 0; i < rowCount(); i++) { if (jobId == item(i)->data(RoleJobId).toInt()) { return i; } } // -1 if not found return -1; } QString JobModel::jobStatus(ipp_jstate_e job_state) { switch (job_state) { case IPP_JOB_PENDING : return i18n("Pending"); case IPP_JOB_HELD : return i18n("On hold"); case IPP_JOB_PROCESSING : return QLatin1String("-"); case IPP_JOB_STOPPED : return i18n("Stopped"); case IPP_JOB_CANCELED : return i18n("Canceled"); case IPP_JOB_ABORTED : return i18n("Aborted"); case IPP_JOB_COMPLETED : return i18n("Completed"); } return QLatin1String("-"); } void JobModel::clear() { removeRows(0, rowCount()); } void JobModel::setWhichJobs(WhichJobs whichjobs) { switch (whichjobs) { case WhichActive: m_whichjobs = CUPS_WHICHJOBS_ACTIVE; break; case WhichCompleted: m_whichjobs = CUPS_WHICHJOBS_COMPLETED; break; case WhichAll: m_whichjobs = CUPS_WHICHJOBS_ALL; break; } getJobs(); } Qt::ItemFlags JobModel::flags(const QModelIndex &index) const { if (index.isValid()) { ipp_jstate_t state = static_cast(item(index.row(), ColStatus)->data(RoleJobState).toInt()); if (state == IPP_JOB_PENDING || state == IPP_JOB_PROCESSING) { return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; } } return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDropEnabled; } QString JobModel::processingJob() const { return m_processingJob; } #include "moc_JobModel.cpp" diff --git a/libkcups/KCupsConnection.cpp b/libkcups/KCupsConnection.cpp index 6054990..2646bbb 100644 --- a/libkcups/KCupsConnection.cpp +++ b/libkcups/KCupsConnection.cpp @@ -1,899 +1,899 @@ /*************************************************************************** * Copyright (C) 2010-2018 by Daniel Nicoletti * * dantti12@gmail.com * * Copyright (C) 2012 Harald Sitter * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; see the file COPYING. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "KCupsConnection.h" #include "Debug.h" #include "KCupsPasswordDialog.h" #include "KIppRequest.h" #include #include #include #include #include #include #include #define RENEW_INTERVAL 3500 #define SUBSCRIPTION_DURATION 3600 #define DBUS_SERVER_RESTARTED "server-restarted" // ServerRestarted #define DBUS_SERVER_STARTED "server-started" // ServerStarted #define DBUS_SERVER_STOPPED "server-stopped" // ServerStopped #define DBUS_SERVER_AUDIT "server-audit" // ServerAudit #define DBUS_PRINTER_RESTARTED "printer-restarted" // PrinterRestarted #define DBUS_PRINTER_SHUTDOWN "printer-shutdown" // PrinterShutdown #define DBUS_PRINTER_STOPPED "printer-stopped" // PrinterStopped #define DBUS_PRINTER_STATE_CHANGED "printer-state-changed" // PrinterStateChanged #define DBUS_PRINTER_FINISHINGS_CHANGED "printer-finishings-changed" // PrinterFinishingsChanged #define DBUS_PRINTER_MEDIA_CHANGED "printer-media-changed" // PrinterMediaChanged #define DBUS_PRINTER_ADDED "printer-added" // PrinterAdded #define DBUS_PRINTER_DELETED "printer-deleted" // PrinterDeleted #define DBUS_PRINTER_MODIFIED "printer-modified" // PrinterModified #define DBUS_JOB_STATE_CHANGED "job-state-changed" // JobState #define DBUS_JOB_CREATED "job-created" // JobCreated #define DBUS_JOB_COMPLETED "job-completed" // JobCompleted #define DBUS_JOB_STOPPED "job-stopped" // JobStopped #define DBUS_JOB_CONFIG_CHANGED "job-config-changed" // JobConfigChanged #define DBUS_JOB_PROGRESS "job-progress" // JobProgress Q_DECLARE_METATYPE(QList) Q_DECLARE_METATYPE(QList) KCupsConnection* KCupsConnection::m_instance = nullptr; static int password_retries = 0; static int total_retries = 0; static int internalErrorCount = 0; const char * password_cb(const char *prompt, http_t *http, const char *method, const char *resource, void *user_data); KCupsConnection* KCupsConnection::global() { if (!m_instance) { m_instance = new KCupsConnection(qApp); } return m_instance; } KCupsConnection::KCupsConnection(QObject *parent) : QThread(parent) { init(); } KCupsConnection::KCupsConnection(const QUrl &server, QObject *parent) : QThread(parent), m_serverUrl(server) { qRegisterMetaType("KIppRequest"); init(); } KCupsConnection::~KCupsConnection() { if (m_instance == this) { m_instance = nullptr; } m_passwordDialog->deleteLater(); quit(); wait(); delete m_renewTimer; delete m_subscriptionTimer; } void KCupsConnection::setPasswordMainWindow(WId mainwindow) { m_passwordDialog->setMainWindow(mainwindow); } void KCupsConnection::init() { // Creating the dialog before start() will make it run on the gui thread m_passwordDialog = new KCupsPasswordDialog; // setup the DBus subscriptions // Server related signals // ServerStarted notifierConnect(QLatin1String("ServerStarted"), this, SIGNAL(serverStarted(QString))); // ServerStopped notifierConnect(QLatin1String("ServerStopped"), this, SIGNAL(serverStopped(QString))); // ServerRestarted notifierConnect(QLatin1String("ServerRestarted"), this, SIGNAL(serverRestarted(QString))); // ServerAudit notifierConnect(QLatin1String("ServerAudit"), this, SIGNAL(serverAudit(QString))); // Printer related signals // PrinterAdded notifierConnect(QLatin1String("PrinterAdded"), this, SIGNAL(printerAdded(QString,QString,QString,uint,QString,bool))); // PrinterModified notifierConnect(QLatin1String("PrinterModified"), this, SIGNAL(printerModified(QString,QString,QString,uint,QString,bool))); // PrinterDeleted notifierConnect(QLatin1String("PrinterDeleted"), this, SIGNAL(printerDeleted(QString,QString,QString,uint,QString,bool))); // PrinterStateChanged notifierConnect(QLatin1String("PrinterStateChanged"), this, SIGNAL(printerStateChanged(QString,QString,QString,uint,QString,bool))); // PrinterStopped notifierConnect(QLatin1String("PrinterStopped"), this, SIGNAL(printerStopped(QString,QString,QString,uint,QString,bool))); // PrinterShutdown notifierConnect(QLatin1String("PrinterShutdown"), this, SIGNAL(printerShutdown(QString,QString,QString,uint,QString,bool))); // PrinterRestarted notifierConnect(QLatin1String("PrinterRestarted"), this, SIGNAL(printerRestarted(QString,QString,QString,uint,QString,bool))); // PrinterMediaChanged notifierConnect(QLatin1String("PrinterMediaChanged"), this, SIGNAL(printerMediaChanged(QString,QString,QString,uint,QString,bool))); // PrinterFinishingsChanged notifierConnect(QLatin1String("PrinterFinishingsChanged"), this, SIGNAL(PrinterFinishingsChanged(QString,QString,QString,uint,QString,bool))); // Job related signals // JobState notifierConnect(QLatin1String("JobState"), this, SIGNAL(jobState(QString,QString,QString,uint,QString,bool,uint,uint,QString,QString,uint))); // JobCreated notifierConnect(QLatin1String("JobCreated"), this, SIGNAL(jobCreated(QString,QString,QString,uint,QString,bool,uint,uint,QString,QString,uint))); // JobStopped notifierConnect(QLatin1String("JobStopped"), this, SIGNAL(jobStopped(QString,QString,QString,uint,QString,bool,uint,uint,QString,QString,uint))); // JobConfigChanged notifierConnect(QLatin1String("JobConfigChanged"), this, SIGNAL(jobConfigChanged(QString,QString,QString,uint,QString,bool,uint,uint,QString,QString,uint))); // JobProgress notifierConnect(QLatin1String("JobProgress"), this, SIGNAL(jobProgress(QString,QString,QString,uint,QString,bool,uint,uint,QString,QString,uint))); // JobCompleted notifierConnect(QLatin1String("JobCompleted"), this, SIGNAL(jobCompleted(QString,QString,QString,uint,QString,bool,uint,uint,QString,QString,uint))); // This signal is needed since the cups registration thing // doesn't emit printerAdded when we add a printer class // This is emitted when a printer/queue is changed QDBusConnection::systemBus().connect(QLatin1String(""), QLatin1String("/com/redhat/PrinterSpooler"), QLatin1String("com.redhat.PrinterSpooler"), QLatin1String("PrinterAdded"), this, SIGNAL(rhPrinterAdded(QString))); // This signal is needed since the cups registration thing // sometimes simple stops working... don't ask me why // This is emitted when a printer/queue is changed QDBusConnection::systemBus().connect(QLatin1String(""), QLatin1String("/com/redhat/PrinterSpooler"), QLatin1String("com.redhat.PrinterSpooler"), QLatin1String("QueueChanged"), this, SIGNAL(rhQueueChanged(QString))); // This signal is needed since the cups registration thing // doesn't emit printerRemoved when we add a printer class // This is emitted when a printer/queue is changed QDBusConnection::systemBus().connect(QLatin1String(""), QLatin1String("/com/redhat/PrinterSpooler"), QLatin1String("com.redhat.PrinterSpooler"), QLatin1String("PrinterRemoved"), this, SIGNAL(rhPrinterRemoved(QString))); QDBusConnection::systemBus().connect(QLatin1String(""), QLatin1String("/com/redhat/PrinterSpooler"), QLatin1String("com.redhat.PrinterSpooler"), QLatin1String("JobQueuedLocal"), this, SIGNAL(rhJobQueuedLocal(QString,uint,QString))); QDBusConnection::systemBus().connect(QLatin1String(""), QLatin1String("/com/redhat/PrinterSpooler"), QLatin1String("com.redhat.PrinterSpooler"), QLatin1String("JobStartedLocal"), this, SIGNAL(rhJobStartedLocal(QString,uint,QString))); // Creates the timer that will renew the DBus subscription m_renewTimer = new QTimer; m_renewTimer->setInterval(RENEW_INTERVAL*1000); m_renewTimer->moveToThread(this); connect(m_renewTimer, &QTimer::timeout, this, static_cast(&KCupsConnection::renewDBusSubscription), Qt::DirectConnection); // Creates the timer to merge updates on the DBus subscription m_subscriptionTimer = new QTimer; m_subscriptionTimer->setInterval(0); m_subscriptionTimer->setSingleShot(true); m_subscriptionTimer->moveToThread(this); connect(m_subscriptionTimer, &QTimer::timeout, this, &KCupsConnection::updateSubscription, Qt::DirectConnection); // Starts this thread start(); } void KCupsConnection::run() { // Check if we need an special connection if (!m_serverUrl.isEmpty()) { if (m_serverUrl.port() < 0) { // TODO find out if there's a better way of hardcoding // the CUPS port m_serverUrl.setPort(631); } cupsSetServer(qUtf8Printable(m_serverUrl.authority())); } // This is dead cool, cups will call the thread_password_cb() // function when a password set is needed, as we passed the // password dialog pointer the functions just need to call // it on a blocking mode. cupsSetPasswordCB2(password_cb, m_passwordDialog); m_inited = true; exec(); // Event loop quit so cancelDBusSubscription() if (m_subscriptionId != -1) { cancelDBusSubscription(); } } bool KCupsConnection::readyToStart() { if (QThread::currentThread() == this) { password_retries = 0; total_retries = 0; internalErrorCount = 0; return true; } return false; } ReturnArguments KCupsConnection::request(const KIppRequest &request, ipp_tag_t groupTag) const { ReturnArguments ret; ipp_t *response = nullptr; do { ippDelete(response); response = nullptr; response = request.sendIppRequest(); } while (retry(qUtf8Printable(request.resource()), request.operation())); if (response && groupTag != IPP_TAG_ZERO) { ret = parseIPPVars(response, groupTag); } ippDelete(response); return ret; } int KCupsConnection::renewDBusSubscription(int subscriptionId, int leaseDuration, const QStringList &events) { int ret = -1; ipp_op_t operation; // check if we have a valid subscription ID if (subscriptionId >= 0) { // Add the "notify-events" values to the request operation = IPP_RENEW_SUBSCRIPTION; } else { operation = IPP_CREATE_PRINTER_SUBSCRIPTION; } KIppRequest request(operation, QLatin1String("/")); request.addString(IPP_TAG_OPERATION, IPP_TAG_URI, KCUPS_PRINTER_URI, QLatin1String("/")); request.addInteger(IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER, KCUPS_NOTIFY_LEASE_DURATION, leaseDuration); if (operation == IPP_CREATE_PRINTER_SUBSCRIPTION) { // Add the "notify-events" values to the request request.addStringList(IPP_TAG_SUBSCRIPTION, IPP_TAG_KEYWORD, KCUPS_NOTIFY_EVENTS, events); request.addString(IPP_TAG_SUBSCRIPTION, IPP_TAG_KEYWORD, KCUPS_NOTIFY_PULL_METHOD, QLatin1String("ippget")); request.addString(IPP_TAG_SUBSCRIPTION, IPP_TAG_URI, KCUPS_NOTIFY_RECIPIENT_URI, QLatin1String("dbus://")); } else { request.addInteger(IPP_TAG_OPERATION, IPP_TAG_INTEGER, KCUPS_NOTIFY_SUBSCRIPTION_ID, subscriptionId); } ipp_t *response = nullptr; do { // Do the request response = request.sendIppRequest(); } while (retry("/", operation)); #if !(CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6) if (response && ippGetStatusCode(response) == IPP_OK) { #else if (response && response->request.status.status_code == IPP_OK) { #endif // !(CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6) ipp_attribute_t *attr; if (subscriptionId >= 0) { // Request was ok, just return the current subscription ret = subscriptionId; } else if ((attr = ippFindAttribute(response, "notify-subscription-id", IPP_TAG_INTEGER)) == nullptr) { qCWarning(LIBKCUPS) << "No notify-subscription-id in response!"; ret = -1; } else { #if !(CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6) ret = ippGetInteger(attr, 0); } } else if (subscriptionId >= 0 && response && ippGetStatusCode(response) == IPP_NOT_FOUND) { qCDebug(LIBKCUPS) << "Subscription not found"; // When the subscription is not found try to get a new one return renewDBusSubscription(-1, leaseDuration, events); #else ret = attr->values[0].integer; } } else if (subscriptionId >= 0 && response && response->request.status.status_code == IPP_NOT_FOUND) { qCDebug(LIBKCUPS) << "Subscription not found"; // When the subscription is not found try to get a new one return renewDBusSubscription(-1, leaseDuration, events); #endif // !(CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6) } else { qCDebug(LIBKCUPS) << "Request failed" << cupsLastError() << httpGetStatus(CUPS_HTTP_DEFAULT); // When the server stops/restarts we will have some error so ignore it ret = subscriptionId; } ippDelete(response); return ret; } void KCupsConnection::notifierConnect(const QString &signal, QObject *receiver, const char *slot) { QDBusConnection systemBus = QDBusConnection::systemBus(); systemBus.connect(QString(), QStringLiteral("/org/cups/cupsd/Notifier"), QStringLiteral("org.cups.cupsd.Notifier"), signal, receiver, slot); } void KCupsConnection::connectNotify(const QMetaMethod & signal) { QMutexLocker locker(&m_mutex); QString event = eventForSignal(signal); if (!event.isNull()) { m_connectedEvents << event; QMetaObject::invokeMethod(m_subscriptionTimer, "start", Qt::QueuedConnection); } } void KCupsConnection::disconnectNotify(const QMetaMethod & signal) { QMutexLocker locker(&m_mutex); QString event = eventForSignal(signal); if (!event.isNull()) { m_connectedEvents.removeOne(event); QMetaObject::invokeMethod(m_subscriptionTimer, "start", Qt::QueuedConnection); } } QString KCupsConnection::eventForSignal(const QMetaMethod & signal) const { // Server signals if (signal == QMetaMethod::fromSignal(&KCupsConnection::serverAudit)) { return QStringLiteral(DBUS_SERVER_AUDIT); } if (signal == QMetaMethod::fromSignal(&KCupsConnection::serverStarted)) { return QStringLiteral(DBUS_SERVER_STARTED); } if (signal == QMetaMethod::fromSignal(&KCupsConnection::serverStopped)) { return QStringLiteral(DBUS_SERVER_STOPPED); } if (signal == QMetaMethod::fromSignal(&KCupsConnection::serverRestarted)) { return QStringLiteral(DBUS_SERVER_RESTARTED); } // Printer signals if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerAdded)) { return QStringLiteral(DBUS_PRINTER_ADDED); } if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerDeleted)) { return QStringLiteral(DBUS_PRINTER_DELETED); } if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerFinishingsChanged)) { return QStringLiteral(DBUS_PRINTER_FINISHINGS_CHANGED); } if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerMediaChanged)) { return QStringLiteral(DBUS_PRINTER_MEDIA_CHANGED); } if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerModified)) { return QStringLiteral(DBUS_PRINTER_MODIFIED); } if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerRestarted)) { return QStringLiteral(DBUS_PRINTER_RESTARTED); } if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerShutdown)) { return QStringLiteral(DBUS_PRINTER_SHUTDOWN); } if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerStateChanged)) { return QStringLiteral(DBUS_PRINTER_STATE_CHANGED); } if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerStopped)) { return QStringLiteral(DBUS_PRINTER_STOPPED); } // job signals if (signal == QMetaMethod::fromSignal(&KCupsConnection::jobCompleted)) { return QStringLiteral(DBUS_JOB_COMPLETED); } if (signal == QMetaMethod::fromSignal(&KCupsConnection::jobConfigChanged)) { return QStringLiteral(DBUS_JOB_CONFIG_CHANGED); } if (signal == QMetaMethod::fromSignal(&KCupsConnection::jobCreated)) { return QStringLiteral(DBUS_JOB_CREATED); } if (signal == QMetaMethod::fromSignal(&KCupsConnection::jobProgress)) { return QStringLiteral(DBUS_JOB_PROGRESS); } if (signal == QMetaMethod::fromSignal(&KCupsConnection::jobState)) { return QStringLiteral(DBUS_JOB_STATE_CHANGED); } if (signal == QMetaMethod::fromSignal(&KCupsConnection::jobStopped)) { return QStringLiteral(DBUS_JOB_STOPPED); } // No registered event signal matched return QString(); } void KCupsConnection::updateSubscription() { QMutexLocker locker(&m_mutex); // Build the current list QStringList currentEvents = m_connectedEvents; currentEvents.sort(); currentEvents.removeDuplicates(); // Check if the requested events are already being asked if (m_requestedDBusEvents != currentEvents) { m_requestedDBusEvents = currentEvents; // If we already have a subscription lets cancel // and create a new one if (m_subscriptionId >= 0) { cancelDBusSubscription(); } - // Canculates the new events + // Calculates the new events renewDBusSubscription(); } } void KCupsConnection::renewDBusSubscription() { // check if we have a valid subscription ID if (m_subscriptionId >= 0) { m_subscriptionId = renewDBusSubscription(m_subscriptionId, SUBSCRIPTION_DURATION); } // The above request might fail if the subscription was cancelled if (m_subscriptionId < 0) { if (m_requestedDBusEvents.isEmpty()) { m_renewTimer->stop(); } else { m_subscriptionId = renewDBusSubscription(m_subscriptionId, SUBSCRIPTION_DURATION, m_requestedDBusEvents); m_renewTimer->start(); } } } void KCupsConnection::cancelDBusSubscription() { KIppRequest request(IPP_CANCEL_SUBSCRIPTION, QLatin1String("/")); request.addString(IPP_TAG_OPERATION, IPP_TAG_URI, KCUPS_PRINTER_URI, QLatin1String("/")); request.addInteger(IPP_TAG_OPERATION, IPP_TAG_INTEGER, KCUPS_NOTIFY_SUBSCRIPTION_ID, m_subscriptionId); do { // Do the request ippDelete(request.sendIppRequest()); } while (retry(qUtf8Printable(request.resource()), request.operation())); // Reset the subscription id m_subscriptionId = -1; } ReturnArguments KCupsConnection::parseIPPVars(ipp_t *response, ipp_tag_t group_tag) { ipp_attribute_t *attr; ReturnArguments ret; #if !(CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6) QVariantHash destAttributes; for (attr = ippFirstAttribute(response); attr != nullptr; attr = ippNextAttribute(response)) { - // We hit an attribute sepparator + // We hit an attribute separator if (ippGetName(attr) == nullptr) { ret << destAttributes; destAttributes.clear(); continue; } - // Skip leading attributes until we hit a a group which can be a printer, job... + // Skip leading attributes until we hit a group which can be a printer, job... if (ippGetGroupTag(attr) != group_tag || (ippGetValueTag(attr) != IPP_TAG_INTEGER && ippGetValueTag(attr) != IPP_TAG_ENUM && ippGetValueTag(attr) != IPP_TAG_BOOLEAN && ippGetValueTag(attr) != IPP_TAG_TEXT && ippGetValueTag(attr) != IPP_TAG_TEXTLANG && ippGetValueTag(attr) != IPP_TAG_LANGUAGE && ippGetValueTag(attr) != IPP_TAG_NAME && ippGetValueTag(attr) != IPP_TAG_NAMELANG && ippGetValueTag(attr) != IPP_TAG_KEYWORD && ippGetValueTag(attr) != IPP_TAG_RANGE && ippGetValueTag(attr) != IPP_TAG_URI)) { continue; } // Add a printer description attribute... destAttributes[QString::fromUtf8(ippGetName(attr))] = ippAttrToVariant(attr); } if (!destAttributes.isEmpty()) { ret << destAttributes; } #else for (attr = response->attrs; attr != nullptr; attr = attr->next) { /* - * Skip leading attributes until we hit a a group which can be a printer, job... + * Skip leading attributes until we hit a group which can be a printer, job... */ while (attr && attr->group_tag != group_tag) { attr = attr->next; } if (attr == nullptr) { break; } /* * Pull the needed attributes from this printer... */ QVariantHash destAttributes; for (; attr && attr->group_tag == group_tag; attr = attr->next) { if (attr->value_tag != IPP_TAG_INTEGER && attr->value_tag != IPP_TAG_ENUM && attr->value_tag != IPP_TAG_BOOLEAN && attr->value_tag != IPP_TAG_TEXT && attr->value_tag != IPP_TAG_TEXTLANG && attr->value_tag != IPP_TAG_LANGUAGE && attr->value_tag != IPP_TAG_NAME && attr->value_tag != IPP_TAG_NAMELANG && attr->value_tag != IPP_TAG_KEYWORD && attr->value_tag != IPP_TAG_RANGE && attr->value_tag != IPP_TAG_URI) { continue; } /* * Add a printer description attribute... */ destAttributes[QString::fromUtf8(attr->name)] = ippAttrToVariant(attr); } ret << destAttributes; if (attr == nullptr) { break; } } #endif // !(CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6) return ret; } QVariant KCupsConnection::ippAttrToVariant(ipp_attribute_t *attr) { QVariant ret; #if !(CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6) switch (ippGetValueTag(attr)) { case IPP_TAG_INTEGER: case IPP_TAG_ENUM: if (ippGetCount(attr) == 1) { ret = ippGetInteger(attr, 0); } else { QList values; for (int i = 0; i < ippGetCount(attr); ++i) { values << ippGetInteger(attr, i); } ret = qVariantFromValue(values); } break; case IPP_TAG_BOOLEAN: if (ippGetCount(attr)== 1) { ret = ippGetBoolean(attr, 0); } else { QList values; for (int i = 0; i < ippGetCount(attr); ++i) { values << ippGetBoolean(attr, i); } ret = qVariantFromValue(values); } break; case IPP_TAG_RANGE: { QVariantList values; for (int i = 0; i < ippGetCount(attr); ++i) { int rangeUpper; values << ippGetRange(attr, i, &rangeUpper); values << rangeUpper; } ret = values; } break; default: if (ippGetCount(attr)== 1) { ret = QString::fromUtf8(ippGetString(attr, 0, nullptr)); } else { QStringList values; for (int i = 0; i < ippGetCount(attr); ++i) { values << QString::fromUtf8(ippGetString(attr, i, nullptr)); } ret = values; } } #else switch (attr->value_tag) { case IPP_TAG_INTEGER: case IPP_TAG_ENUM: if (attr->num_values == 1) { ret = attr->values[0].integer; } else { QList values; for (int i = 0; i < attr->num_values; ++i) { values << attr->values[i].integer; } ret = qVariantFromValue(values); } break; case IPP_TAG_BOOLEAN: if (attr->num_values == 1) { ret = static_cast(attr->values[0].integer); } else { QList values; for (int i = 0; i < attr->num_values; ++i) { values << static_cast(attr->values[i].integer); } ret = qVariantFromValue(values); } break; case IPP_TAG_RANGE: { QVariantList values; for (int i = 0; i < attr->num_values; ++i) { values << attr->values[i].range.lower; values << attr->values[i].range.upper; } ret = values; } break; default: if (attr->num_values == 1) { ret = QString::fromUtf8(attr->values[0].string.text); } else { QStringList values; for (int i = 0; i < attr->num_values; ++i) { values << QString::fromUtf8(attr->values[i].string.text); } ret = values; } } #endif // !(CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6) return ret; } bool KCupsConnection::retry(const char *resource, int operation) const { ipp_status_t status = cupsLastError(); if (operation != -1) { qCDebug(LIBKCUPS) << ippOpString(static_cast(operation)) << "last error:" << status << cupsLastErrorString(); } else { qCDebug(LIBKCUPS) << operation << "last error:" << status << cupsLastErrorString(); } // When CUPS process stops our connection // with it fails and has to be re-established if (status == IPP_INTERNAL_ERROR) { // Deleting this connection thread forces it // to create a new CUPS connection qCWarning(LIBKCUPS) << "IPP_INTERNAL_ERROR: clearing cookies and reconnecting"; // TODO maybe reconnect is enough // httpClearCookie(CUPS_HTTP_DEFAULT); // Reconnect to CUPS if (httpReconnect(CUPS_HTTP_DEFAULT)) { qCWarning(LIBKCUPS) << "Failed to reconnect" << cupsLastErrorString(); // Server might be restarting sleep for a few ms msleep(500); } // Try the request again return ++internalErrorCount < 3; } total_retries++; if (total_retries > (password_retries + 3)) { // Something is wrong. // This will happen if the password_cb function is not called, // which will for example be the case if the server has // an IP blacklist and thus always return 403. // In this case, there is nothing we can do. return false; } bool forceAuth = false; // If our user is forbidden to perform the // task we try again using the root user // ONLY if it was the first time if (status == IPP_FORBIDDEN && password_retries == 0) { // Pretend to be the root user // Sometimes setting this just works cupsSetUser("root"); // force authentication forceAuth = true; } if (status == IPP_NOT_AUTHORIZED || status == IPP_NOT_AUTHENTICATED) { if (password_retries > 3 || password_retries == -1) { // the authentication failed 3 times - // OR the dialog was canceld (-1) + // OR the dialog was canceled (-1) // reset to 0 and quit the do-while loop password_retries = 0; total_retries = 0; return false; } // force authentication forceAuth = true; } if (forceAuth) { // force authentication qCDebug(LIBKCUPS) << "Calling cupsDoAuthentication() password_retries:" << password_retries; int ret = cupsDoAuthentication(CUPS_HTTP_DEFAULT, "POST", resource); qCDebug(LIBKCUPS) << "Called cupsDoAuthentication(), success:" << (ret == -1 ? true : false); // If the authentication was successful // sometimes just trying to be root works return ret == -1 ? true : false; } // the action was not forbidden return false; } const char * password_cb(const char *prompt, http_t *http, const char *method, const char *resource, void *user_data) { Q_UNUSED(http) Q_UNUSED(method) Q_UNUSED(resource) if (++password_retries > 3) { // cancel the authentication cupsSetUser(nullptr); return nullptr; } auto passwordDialog = static_cast(user_data); bool wrongPassword = password_retries > 1; // use prompt text from CUPS callback for dialog passwordDialog->setPromptText(i18n("A CUPS connection requires authentication: \"%1\"", QString::fromUtf8(prompt))); // This will block this thread until exec is not finished qCDebug(LIBKCUPS) << password_retries; QMetaObject::invokeMethod(passwordDialog, "exec", Qt::BlockingQueuedConnection, Q_ARG(QString, QString::fromUtf8(cupsUser())), Q_ARG(bool, wrongPassword)); qCDebug(LIBKCUPS) << passwordDialog->accepted(); // The password dialog has just returned check the result // method that returns QDialog enums if (passwordDialog->accepted()) { cupsSetUser(qUtf8Printable(passwordDialog->username())); return qUtf8Printable(passwordDialog->password()); } else { // the dialog was canceled password_retries = -1; cupsSetUser(nullptr); return nullptr; } } #include "moc_KCupsConnection.cpp" diff --git a/libkcups/KCupsConnection.h b/libkcups/KCupsConnection.h index 502ec46..a15d97b 100644 --- a/libkcups/KCupsConnection.h +++ b/libkcups/KCupsConnection.h @@ -1,405 +1,405 @@ /*************************************************************************** * Copyright (C) 2010-2018 by Daniel Nicoletti * * dantti12@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; see the file COPYING. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef KCUPSCONNECTION_H #define KCUPSCONNECTION_H #include #include #include #include #include #include #include #include #include #define KCUPS_DEVICE_CLASS QLatin1String("device-class") #define KCUPS_DEVICE_ID QLatin1String("device-id") #define KCUPS_DEVICE_INFO QLatin1String("device-info") #define KCUPS_DEVICE_MAKE_AND_MODEL QLatin1String("device-make-and-model") #define KCUPS_DEVICE_LOCATION QLatin1String("device-location") #define KCUPS_DEVICE_URI QLatin1String("device-uri") #define KCUPS_PRINTER_NAME QLatin1String("printer-name") #define KCUPS_PRINTER_LOCATION QLatin1String("printer-location") #define KCUPS_PRINTER_INFO QLatin1String("printer-info") #define KCUPS_PRINTER_URI QLatin1String("printer-uri") #define KCUPS_PRINTER_MAKE_AND_MODEL QLatin1String("printer-make-and-model") #define KCUPS_PRINTER_STATE QLatin1String("printer-state") #define KCUPS_PRINTER_STATE_MESSAGE QLatin1String("printer-state-message") #define KCUPS_PRINTER_IS_SHARED QLatin1String("printer-is-shared") #define KCUPS_PRINTER_IS_ACCEPTING_JOBS QLatin1String("printer-is-accepting-jobs") #define KCUPS_PRINTER_TYPE QLatin1String("printer-type") #define KCUPS_PRINTER_TYPE_MASK QLatin1String("printer-type-mask") #define KCUPS_PRINTER_COMMANDS QLatin1String("printer-commands") #define KCUPS_PRINTER_URI_SUPPORTED QLatin1String("printer-uri-supported") #define KCUPS_PRINTER_ERROR_POLICY QLatin1String("printer-error-policy") #define KCUPS_PRINTER_ERROR_POLICY_SUPPORTED QLatin1String("printer-error-policy-supported") #define KCUPS_PRINTER_OP_POLICY QLatin1String("printer-op-policy") #define KCUPS_PRINTER_OP_POLICY_SUPPORTED QLatin1String("printer-op-policy-supported") #define KCUPS_MEMBER_URIS QLatin1String("member-uris") #define KCUPS_MEMBER_NAMES QLatin1String("member-names") #define KCUPS_MARKER_CHANGE_TIME QLatin1String("marker-change-time") #define KCUPS_MARKER_COLORS QLatin1String("marker-colors") #define KCUPS_MARKER_LEVELS QLatin1String("marker-levels") #define KCUPS_MARKER_HIGH_LEVELS "marker-high-levels" #define KCUPS_MARKER_LOW_LEVELS "marker-low-levels" #define KCUPS_MARKER_NAMES QLatin1String("marker-names") #define KCUPS_MARKER_TYPES QLatin1String("marker-types") #define KCUPS_MARKER_MESSAGE "marker-message" #define KCUPS_JOB_ID QLatin1String("job-id") #define KCUPS_JOB_NAME QLatin1String("job-name") #define KCUPS_JOB_K_OCTETS QLatin1String("job-k-octets") #define KCUPS_JOB_K_OCTETS_PROCESSED QLatin1String("job-k-octets-processed") #define KCUPS_JOB_PRINTER_URI QLatin1String("job-printer-uri") #define KCUPS_JOB_PRINTER_STATE_MESSAGE QLatin1String("job-printer-state-message") #define KCUPS_JOB_ORIGINATING_USER_NAME QLatin1String("job-originating-user-name") #define KCUPS_JOB_ORIGINATING_HOST_NAME QLatin1String("job-originating-host-name") #define KCUPS_JOB_MEDIA_PROGRESS QLatin1String("job-media-progress") #define KCUPS_JOB_MEDIA_SHEETS QLatin1String("job-media-sheets") #define KCUPS_JOB_MEDIA_SHEETS_COMPLETED QLatin1String("job-media-sheets-completed") #define KCUPS_JOB_PRESERVED QLatin1String("job-preserved") #define KCUPS_JOB_STATE QLatin1String("job-state") #define KCUPS_JOB_SHEETS_DEFAULT QLatin1String("job-sheets-default") #define KCUPS_JOB_SHEETS_SUPPORTED QLatin1String("job-sheets-supported") #define KCUPS_JOB_SHEETS_DEFAULT QLatin1String("job-sheets-default") #define KCUPS_JOB_SHEETS_SUPPORTED QLatin1String("job-sheets-supported") #define KCUPS_MY_JOBS QLatin1String("my-jobs") #define KCUPS_WHICH_JOBS QLatin1String("which-jobs") #define KCUPS_TIME_AT_COMPLETED QLatin1String("time-at-completed") #define KCUPS_TIME_AT_CREATION QLatin1String("time-at-creation") #define KCUPS_TIME_AT_PROCESSING QLatin1String("time-at-processing") #define KCUPS_REQUESTED_ATTRIBUTES QLatin1String("requested-attributes") #define KCUPS_REQUESTING_USER_NAME QLatin1String("requesting-user-name") #define KCUPS_REQUESTING_USER_NAME_ALLOWED QLatin1String("requesting-user-name-allowed") #define KCUPS_REQUESTING_USER_NAME_DENIED QLatin1String("requesting-user-name-denied") #define KCUPS_PPD_MAKE_AND_MODEL QLatin1String("ppd-make-and-model") #define KCUPS_NOTIFY_EVENTS QLatin1String("notify-events") #define KCUPS_NOTIFY_PULL_METHOD QLatin1String("notify-pull-method") #define KCUPS_NOTIFY_RECIPIENT_URI QLatin1String("notify-recipient-uri") #define KCUPS_NOTIFY_LEASE_DURATION QLatin1String("notify-lease-duration") #define KCUPS_NOTIFY_SUBSCRIPTION_ID QLatin1String("notify-subscription-id") typedef QList ReturnArguments; class KIppRequest; class KCupsPasswordDialog; class Q_DECL_EXPORT KCupsConnection : public QThread { Q_OBJECT public: /** * This is the main Cups class @author Daniel Nicoletti * * By calling KCupsConnection::global() you have access to it. - * Due to cups archtecture, this class has to live on a + * Due to cups architecture, this class has to live on a * separate thread so we avoid blocking the user interface when * the cups call blocks. * * It is IMPORTANT that we do not create several thread * for each cups request, doing so is a valid but breaks our * authentication. We could tho store the user information an * set the user/password every time it was needed. But I am not * sure this is safe. * * Extending this means either adding methods to the KCupsRequest * class which will move to this thread and then run. */ static KCupsConnection* global(); /** * @brief KCupsConnection * @param parent * * This is the default constructor that connects to the default server * If you don't have any special reason for creating a connection * on your own consider calling global() */ explicit KCupsConnection(QObject *parent = nullptr); explicit KCupsConnection(const QUrl &server, QObject *parent = nullptr); ~KCupsConnection() override; void setPasswordMainWindow(WId mainwindow); Q_SIGNALS: /** * emitted when "server-started" is registered */ void serverStarted(const QString &text); /** * emitted when "server-stopped" is registered */ void serverStopped(const QString &text); /** * emitted when "server-restarted" is registered */ void serverRestarted(const QString &text); /** * emitted when "server-audit" is registered */ void serverAudit(const QString &text); /** * emitted when "printer-added" is registered */ void printerAdded(const QString &text, const QString &printerUri, const QString &printerName, uint printerState, const QString &printerStateReasons, bool printerIsAcceptingJobs); /** * emitted when "printer-modified" is registered */ void printerModified(const QString &text, const QString &printerUri, const QString &printerName, uint printerState, const QString &printerStateReasons, bool printerIsAcceptingJobs); /** * emitted when "printer-deleted" is registered */ void printerDeleted(const QString &text, const QString &printerUri, const QString &printerName, uint printerState, const QString &printerStateReasons, bool printerIsAcceptingJobs); /** * emitted when "printer-state-changed" is registered */ void printerStateChanged(const QString &text, const QString &printerUri, const QString &printerName, uint printerState, const QString &printerStateReasons, bool printerIsAcceptingJobs); /** * emitted when "printer-stopped" is registered */ void printerStopped(const QString &text, const QString &printerUri, const QString &printerName, uint printerState, const QString &printerStateReasons, bool printerIsAcceptingJobs); /** * emitted when "printer-restarted" is registered */ void printerRestarted(const QString &text, const QString &printerUri, const QString &printerName, uint printerState, const QString &printerStateReasons, bool printerIsAcceptingJobs); /** * emitted when "printer-shutdown" is registered */ void printerShutdown(const QString &text, const QString &printerUri, const QString &printerName, uint printerState, const QString &printerStateReasons, bool printerIsAcceptingJobs); /** * emitted when "printer-media-changed" is registered */ void printerMediaChanged(const QString &text, const QString &printerUri, const QString &printerName, uint printerState, const QString &printerStateReasons, bool printerIsAcceptingJobs); /** * emitted when "printer-finishings-changed" is registered */ void printerFinishingsChanged(const QString &text, const QString &printerUri, const QString &printerName, uint printerState, const QString &printerStateReasons, bool printerIsAcceptingJobs); /** * emitted when "job-state-changed" is registered */ void jobState(const QString &text, const QString &printerUri, const QString &printerName, uint printerState, const QString &printerStateReasons, bool printerIsAcceptingJobs, uint jobId, uint jobState, const QString &jobStateReasons, const QString &jobName, uint jobImpressionsCompleted); /** * emitted when "job-created" is registered */ void jobCreated(const QString &text, const QString &printerUri, const QString &printerName, uint printerState, const QString &printerStateReasons, bool printerIsAcceptingJobs, uint jobId, uint jobState, const QString &jobStateReasons, const QString &jobName, uint jobImpressionsCompleted); /** * emitted when "job-stopped" is registered */ void jobStopped(const QString &text, const QString &printerUri, const QString &printerName, uint printerState, const QString &printerStateReasons, bool printerIsAcceptingJobs, uint jobId, uint jobState, const QString &jobStateReasons, const QString &jobName, uint jobImpressionsCompleted); /** * emitted when "job-config-changed" is registered */ void jobConfigChanged(const QString &text, const QString &printerUri, const QString &printerName, uint printerState, const QString &printerStateReasons, bool printerIsAcceptingJobs, uint jobId, uint jobState, const QString &jobStateReasons, const QString &jobName, uint jobImpressionsCompleted); /** * emitted when "job-progress" is registered */ void jobProgress(const QString &text, const QString &printerUri, const QString &printerName, uint printerState, const QString &printerStateReasons, bool printerIsAcceptingJobs, uint jobId, uint jobState, const QString &jobStateReasons, const QString &jobName, uint jobImpressionsCompleted); /** * emitted when "job-completed" is registered */ void jobCompleted(const QString &text, const QString &printerUri, const QString &printerName, uint printerState, const QString &printerStateReasons, bool printerIsAcceptingJobs, uint jobId, uint jobState, const QString &jobStateReasons, const QString &jobName, uint jobImpressionsCompleted); void rhPrinterAdded(const QString &queueName); void rhPrinterRemoved(const QString &queueName); void rhQueueChanged(const QString &queueName); void rhJobQueuedLocal(const QString &queueName, uint jobId, const QString &jobOwner); void rhJobStartedLocal(const QString &queueName, uint jobId, const QString &jobOwner); protected: friend class KCupsRequest; virtual void run() override; bool readyToStart(); bool retry(const char *resource, int operation) const; ReturnArguments request(const KIppRequest &request, ipp_tag_t groupTag = IPP_TAG_ZERO) const; private slots: void updateSubscription(); void renewDBusSubscription(); void cancelDBusSubscription(); protected: virtual void connectNotify(const QMetaMethod & signal) override; virtual void disconnectNotify(const QMetaMethod & signal) override; QString eventForSignal(const QMetaMethod & signal) const; private: void init(); int renewDBusSubscription(int subscriptionId, int leaseDuration, const QStringList &events = QStringList()); void notifierConnect(const QString &signal, QObject *receiver, const char *slot); static ReturnArguments parseIPPVars(ipp_t *response, ipp_tag_t group_tag); static QVariant ippAttrToVariant(ipp_attribute_t *attr); static KCupsConnection* m_instance; bool m_inited = false; KCupsPasswordDialog *m_passwordDialog; QUrl m_serverUrl; QTimer *m_subscriptionTimer; QTimer *m_renewTimer; QStringList m_connectedEvents; //note this updated in another thread. Always guard with m_mutex QStringList m_requestedDBusEvents; int m_subscriptionId = -1; QMutex m_mutex; }; #endif // KCUPSCONNECTION_H diff --git a/libkcups/KCupsPasswordDialog.cpp b/libkcups/KCupsPasswordDialog.cpp index 05ea31c..987aa4e 100644 --- a/libkcups/KCupsPasswordDialog.cpp +++ b/libkcups/KCupsPasswordDialog.cpp @@ -1,93 +1,93 @@ /*************************************************************************** * Copyright (C) 2010-2012 by Daniel Nicoletti * * dantti12@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; see the file COPYING. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "KCupsPasswordDialog.h" #include "Debug.h" #include #include #include #include KCupsPasswordDialog::KCupsPasswordDialog(QObject *parent) : QObject(parent), m_accepted(false), m_mainwindow(0), - // default text, can be overriden using setPromptText() + // default text, can be overridden using setPromptText() m_promptText(i18n("Enter an username and a password to complete the task")) { } void KCupsPasswordDialog::setMainWindow(WId mainwindow) { m_mainwindow = mainwindow; } void KCupsPasswordDialog::setPromptText(const QString &text) { m_promptText = text; } void KCupsPasswordDialog::exec(const QString &username, bool wrongPassword) { QPointer dialog = new KPasswordDialog(nullptr, KPasswordDialog::ShowUsernameLine); dialog->setPrompt(m_promptText); dialog->setModal(false); dialog->setUsername(username); if (wrongPassword) { dialog->showErrorMessage(QString(), KPasswordDialog::UsernameError); dialog->showErrorMessage(i18n("Wrong username or password"), KPasswordDialog::PasswordError); } dialog->show(); if (m_mainwindow) { KWindowSystem::setMainWindow(dialog, m_mainwindow); } KWindowSystem::forceActiveWindow(dialog->winId()); // Do not return from this method now dialog->exec(); if (dialog) { m_accepted = dialog->result() == QDialog::Accepted; m_username = dialog->username(); m_password = dialog->password(); dialog->deleteLater(); } } bool KCupsPasswordDialog::accepted() const { return m_accepted; } QString KCupsPasswordDialog::username() const { return m_username; } QString KCupsPasswordDialog::password() const { return m_password; } #include "moc_KCupsPasswordDialog.cpp" diff --git a/libkcups/KCupsRequest.h b/libkcups/KCupsRequest.h index 117583f..ec99dd3 100644 --- a/libkcups/KCupsRequest.h +++ b/libkcups/KCupsRequest.h @@ -1,323 +1,323 @@ /*************************************************************************** * Copyright (C) 2010-2012 by Daniel Nicoletti * * dantti12@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; see the file COPYING. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef KCUPS_REQUEST_H #define KCUPS_REQUEST_H #include #include #include "KCupsConnection.h" #include "KCupsJob.h" #include "KCupsPrinter.h" #include "KCupsServer.h" #include "KIppRequest.h" class Q_DECL_EXPORT KCupsRequest : public QObject { Q_OBJECT public: /** * Default constructor, it takes no parent * because it will move to KCupsConnection thread * * Before calling any method connect to finished() signal or * use waitTillFinished(). * You must delete the object manually after finished * using deleteLater(). */ explicit KCupsRequest(KCupsConnection *connection = nullptr); /** * This method creates an event loop * and quits after the request is finished */ void waitTillFinished(); /** * This method returns true if there was an error with the request */ bool hasError() const; ipp_status_t error() const; http_status_t httpStatus() const; QString serverError() const; QString errorMsg() const; KCupsConnection* connection() const; /** * Non empty when getPrinters is called and finish is emitted */ KCupsPrinters printers() const; /** * Non empty when getPPDs is called and finish is emitted */ ReturnArguments ppds() const; /** * Non empty when getServerSettings() is called and finish is emitted */ KCupsServer serverSettings() const; /** * Non empty when \sa getPrinterPPD() is called and finish is emitted * \warning You must unlik the given file name */ QString printerPPD() const; /** * Non empty when getJobs is called and finish is emitted */ KCupsJobs jobs() const; /** * Get all available PPDs from the givem make * @param make the maker of the printer */ Q_INVOKABLE void getPPDS(const QString &make = QString()); /** * Get all devices that could be added as a printer * This method emits device() */ Q_INVOKABLE void getDevices(int timeout = CUPS_TIMEOUT_DEFAULT); /** * Get all devices that could be added as a printer * This method emits device() */ Q_INVOKABLE void getDevices(int timeout, QStringList includeSchemes, QStringList excludeSchemes); /** * Get all available printers * @param mask filter the kind of printer that will be emitted (-1 to no filter) - * @param requestedAttr the attibutes to retrieve from cups + * @param requestedAttr the attributes to retrieve from cups * This method emits printer() * * THIS function can get the default server dest through the * "printer-is-default" attribute BUT it does not get user * defined default printer, see cupsGetDefault() on www.cups.org for details */ Q_INVOKABLE void getPrinters(QStringList attributes, int mask = -1); /** * Get attributes from a given printer * @param printer The printer to apply the change * @param isClass True it is a printer class * @param attributes The attributes you are requesting * * @return The return will be stored in \sa printers() */ Q_INVOKABLE void getPrinterAttributes(const QString &printerName, bool isClass, QStringList attributes); /** * Get all jobs * This method emits job() * TODO we need to see if we authenticate as root to do some taks * the myJobs will return the user's jobs or the root's jobs * @param printer which printer you are requiring jobs for (empty = all printers) * @param myJobs true if you only want your jobs * @param whichJobs which kind jobs should be sent */ Q_INVOKABLE void getJobs(const QString &printerName, bool myJobs, int whichJobs, QStringList attributes); /** * Get attributes from a given printer * @param printer The printer to apply the change * @param isClass True it is a printer class * @param attributes The attributes you are requesting * * @return The return will be stored in \sa printers() */ Q_INVOKABLE void getJobAttributes(int jobId, const QString &printerUri, QStringList attributes); /** * Get the CUPS server settings * This method emits server() */ Q_INVOKABLE void getServerSettings(); /** * Get the PPD associated with @arg printerName * the result is stored at \sa printerPPD() */ Q_INVOKABLE void getPrinterPPD(const QString &printerName); /** * Get the CUPS server settings * @param userValues the new server settings */ Q_INVOKABLE void setServerSettings(const KCupsServer &server); // ---- Printer Methods /** * Add or Modify a Printer * @param printerName The printer to apply the change * @param attributes The new attributes of the printer * @param filename The file name in case of changing the PPD */ void addOrModifyPrinter(const QString &printerName, const QVariantHash &attributes, const QString &filename = QString()); /** * Add or Modify a Class * @param className The class to apply the change * @param attributes The new attributes of the printer */ void addOrModifyClass(const QString &className, const QVariantHash &attributes); /** * Set if a given printer should be shared among other cups * @param printer The printer to apply the change * @param isClass True it is a printer class * @param shared True if it should be shared */ void setShared(const QString &printerName, bool isClass, bool shared); /** * Set if a given printer should be the default one among others * @param printer The printer to apply the change */ void setDefaultPrinter(const QString &printerName); /** * Pause the given printer from receiving jobs * @param printer The printer to apply the change */ void pausePrinter(const QString &printerName); /** * Resume the given printer from receiving jobs * @param printer The printer to apply the change */ void resumePrinter(const QString &printerName); /** * Allows the given printer from receiving jobs * @param printer The printer to apply the change */ void acceptJobs(const QString &printerName); /** * Prevents the given printer from receiving jobs * @param printer The printer to apply the change */ void rejectJobs(const QString &printerName); /** * Delete the given printer, if it's not local it's not * possible to delete it * @param printer The printer to apply the change */ void deletePrinter(const QString &printerName); /** * Print a test page * @param printerName The printer where the test should be done * @param isClass True it is a printer class */ void printTestPage(const QString &printerName, bool isClass); /** * Print a command test * @param printerName The printer where the test should be done * @param command The command to print * @param title The title of the command */ Q_INVOKABLE void printCommand(const QString &printerName, const QString &command, const QString &title); // Jobs methods /** * Cancels tries to cancel a given job * @param printerName the destination name (printer) * @param jobId the job identification */ void cancelJob(const QString &printerName, int jobId); /** * Holds the printing of a given job * @param printerName the destination name (printer) * @param jobId the job identification */ void holdJob(const QString &printerName, int jobId); /** * Holds the printing of a given job * @param printerName the destination name (printer) * @param jobId the job identification */ void releaseJob(const QString &printerName, int jobId); /** * Restart the printing of a given job * @param printerName the destination name (printer) * @param jobId the job identification */ void restartJob(const QString &printerName, int jobId); /** * Holds the printing of a given job * @param fromDestName the destination name which holds the job * @param jobId the job identification * @param toDestName the destination to hold the job */ void moveJob(const QString &fromPrinterName, int jobId, const QString &toPrinterName); signals: void device(const QString &device_class, const QString &device_id, const QString &device_info, const QString &device_make_and_model, const QString &device_uri, const QString &device_location); void finished(KCupsRequest *); private: void invokeMethod(const char *method, const QVariant &arg1 = QVariant(), const QVariant &arg2 = QVariant(), const QVariant &arg3 = QVariant(), const QVariant &arg4 = QVariant(), const QVariant &arg5 = QVariant(), const QVariant &arg6 = QVariant(), const QVariant &arg7 = QVariant(), const QVariant &arg8 = QVariant()); Q_INVOKABLE void process(const KIppRequest &request); void setError(http_status_t httpStatus, ipp_status_t error, const QString &errorMsg); void setFinished(bool delayed = false); KCupsConnection *m_connection; QEventLoop m_loop; bool m_finished = true; ipp_status_t m_error = IPP_OK; http_status_t m_httpStatus; QString m_errorMsg; ReturnArguments m_ppds; KCupsServer m_server; QString m_ppdFile; KCupsPrinters m_printers; KCupsJobs m_jobs; }; #endif // KCUPS_REQUEST_H diff --git a/printqueue/PrintQueue.cpp b/printqueue/PrintQueue.cpp index 7c71602..2fc62d3 100644 --- a/printqueue/PrintQueue.cpp +++ b/printqueue/PrintQueue.cpp @@ -1,121 +1,121 @@ /*************************************************************************** * Copyright (C) 2010-2018 by Daniel Nicoletti * * dantti12@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; see the file COPYING. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "PrintQueue.h" #include "PrintQueueUi.h" #include #include #include #include #include PrintQueue::PrintQueue(int &argc, char **argv) : QApplication(argc, argv) { } PrintQueue::~PrintQueue() { } void PrintQueue::showQueues(const QStringList &queues, const QString &cwd) { Q_UNUSED(cwd) if (!queues.isEmpty()) { for (const QString &queue : queues) { showQueue(queue); } } else { qDebug() << "called with no args"; // If DBus called the ui list won't be empty QTimer::singleShot(500, this, &PrintQueue::removeQueue); } } void PrintQueue::showQueue(const QString &destName) { qDebug() << Q_FUNC_INFO << destName; if (!m_uis.contains(destName)) { // Reserve this since the CUPS call might take a long time m_uis[destName] = nullptr; // Get destinations with these attributes QPointer request = new KCupsRequest; request->getPrinters({ KCUPS_PRINTER_NAME, KCUPS_PRINTER_TYPE }); request->waitTillFinished(); if (!request) { return; } bool found = false; KCupsPrinter printer; const KCupsPrinters printers = request->printers(); for (const KCupsPrinter &printerItem : printers) { if (printerItem.name() == destName) { printer = printerItem; found = true; break; } } request->deleteLater(); if (found) { auto ui = new PrintQueueUi(printer); connect(ui, &PrintQueueUi::finished, this, &PrintQueue::removeQueue); ui->show(); m_uis[printer.name()] = ui; } else { // Remove the reservation m_uis.remove(destName); // if no destination was found and we aren't showing // a queue quit the app if (m_uis.isEmpty()) { emit quit(); } return; } } - // Check it it's not reserved + // Check if it's not reserved if (m_uis.value(destName)) { KWindowSystem::forceActiveWindow(m_uis.value(destName)->winId()); } } void PrintQueue::removeQueue() { auto ui = qobject_cast(sender()); if (ui) { m_uis.remove(m_uis.key(ui)); } // if no destination was found and we aren't showing // a queue quit the app if (m_uis.isEmpty()) { quit(); } } #include "moc_PrintQueue.cpp" diff --git a/printqueue/PrintQueueUi.cpp b/printqueue/PrintQueueUi.cpp index 0f88d2f..aad092d 100644 --- a/printqueue/PrintQueueUi.cpp +++ b/printqueue/PrintQueueUi.cpp @@ -1,613 +1,613 @@ /*************************************************************************** * Copyright (C) 2010-2018 by Daniel Nicoletti * * dantti12@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; see the file COPYING. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "PrintQueueUi.h" #include "ui_PrintQueueUi.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define PRINTER_ICON_SIZE 92 PrintQueueUi::PrintQueueUi(const KCupsPrinter &printer, QWidget *parent) : QDialog(parent), ui(new Ui::PrintQueueUi), m_destName(printer.name()) { ui->setupUi(this); // since setupUi needs to setup on the mainWidget() // we need to manually connect the buttons connect(ui->cancelJobPB, &QPushButton::clicked, this, &PrintQueueUi::cancelJob); connect(ui->holdJobPB, &QPushButton::clicked, this, &PrintQueueUi::holdJob); connect(ui->resumeJobPB, &QPushButton::clicked, this, &PrintQueueUi::resumeJob); connect(ui->reprintPB, &QPushButton::clicked, this, &PrintQueueUi::reprintJob); connect(ui->pausePrinterPB, &QPushButton::clicked, this, &PrintQueueUi::pausePrinter); connect(ui->configurePrinterPB, &QPushButton::clicked, this, &PrintQueueUi::configurePrinter); connect(ui->whichJobsCB, static_cast(&QComboBox::currentIndexChanged), this, &PrintQueueUi::whichJobsIndexChanged); // Needed so we have our dialog size saved setAttribute(Qt::WA_DeleteOnClose); setWindowIcon(printer.icon()); if (printer.info().isEmpty()) { m_title = printer.name(); } else { m_title = printer.name() % QLatin1String(" - ") % printer.info(); } setWindowTitle(m_title); setSizeGripEnabled(true); (void) minimumSizeHint(); //Force the dialog to be laid out now layout()->setContentsMargins(0,0,0,0); m_isClass = printer.isClass(); // setup default options ui->jobsView->setCornerWidget(new QWidget); setupButtons(); // loads the standard key icon m_printerIcon = printer.icon().pixmap(PRINTER_ICON_SIZE, PRINTER_ICON_SIZE); ui->iconL->setPixmap(m_printerIcon); m_pauseIcon = KIconLoader::global()->loadIcon(QLatin1String("media-playback-pause"), KIconLoader::NoGroup, KIconLoader::SizeMedium, KIconLoader::DefaultState, QStringList(), nullptr, true); ui->printerStatusMsgL->setText(QString()); // setup the jobs model m_model = new JobModel(this); m_model->setParentWId(winId()); m_model->init(printer.name()); connect(m_model, &JobModel::dataChanged, this, &PrintQueueUi::updateButtons); connect(m_model, &JobModel::dataChanged, this, &PrintQueueUi::update); m_proxyModel = new JobSortFilterModel(this); m_proxyModel->setSourceModel(m_model); m_proxyModel->setDynamicSortFilter(true); ui->jobsView->setModel(m_proxyModel); ui->jobsView->setItemDelegate(new NoSelectionRectDelegate(this)); // sort by status column means the jobs will be sorted by the queue order ui->jobsView->sortByColumn(JobModel::ColStatus, Qt::AscendingOrder); connect(ui->jobsView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &PrintQueueUi::updateButtons); connect(ui->jobsView, &QTreeView::customContextMenuRequested, this, &PrintQueueUi::showContextMenu); ui->jobsView->header()->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->jobsView->header(), &QHeaderView::customContextMenuRequested, this, &PrintQueueUi::showHeaderContextMenu); QHeaderView *header = ui->jobsView->header(); header->setSectionResizeMode(QHeaderView::Interactive); header->setStretchLastSection(false); header->setSectionResizeMode(JobModel::ColStatus, QHeaderView::ResizeToContents); header->setSectionResizeMode(JobModel::ColName, QHeaderView::Stretch); header->setSectionResizeMode(JobModel::ColUser, QHeaderView::ResizeToContents); header->setSectionResizeMode(JobModel::ColCreated, QHeaderView::ResizeToContents); header->setSectionResizeMode(JobModel::ColCompleted, QHeaderView::ResizeToContents); header->setSectionResizeMode(JobModel::ColPages, QHeaderView::ResizeToContents); header->setSectionResizeMode(JobModel::ColProcessed, QHeaderView::ResizeToContents); header->setSectionResizeMode(JobModel::ColSize, QHeaderView::ResizeToContents); header->setSectionResizeMode(JobModel::ColStatusMessage, QHeaderView::ResizeToContents); header->setSectionResizeMode(JobModel::ColPrinter, QHeaderView::ResizeToContents); KConfigGroup printQueue(KSharedConfig::openConfig(QLatin1String("print-manager")), "PrintQueue"); if (printQueue.hasKey("ColumnState")) { // restore the header state order header->restoreState(printQueue.readEntry("ColumnState", QByteArray())); } else { // Hide some columns ColPrinter header->hideSection(JobModel::ColPrinter); header->hideSection(JobModel::ColUser); header->hideSection(JobModel::ColCompleted); header->hideSection(JobModel::ColSize); header->hideSection(JobModel::ColFromHost); } // This is emitted when a printer is modified connect(KCupsConnection::global(), &KCupsConnection::printerModified, this, &PrintQueueUi::updatePrinter); // This is emitted when a printer has it's state changed connect(KCupsConnection::global(), &KCupsConnection::printerStateChanged, this, &PrintQueueUi::updatePrinter); // This is emitted when a printer is stopped connect(KCupsConnection::global(), &KCupsConnection::printerStopped, this, &PrintQueueUi::updatePrinter); // This is emitted when a printer is restarted connect(KCupsConnection::global(), &KCupsConnection::printerRestarted, this, &PrintQueueUi::updatePrinter); // This is emitted when a printer is shutdown connect(KCupsConnection::global(), &KCupsConnection::printerShutdown, this, &PrintQueueUi::updatePrinter); // This is emitted when a printer is removed connect(KCupsConnection::global(), &KCupsConnection::printerDeleted, this, &PrintQueueUi::updatePrinter); // This is emitted when a printer/queue is changed // Deprecated stuff that works better than the above connect(KCupsConnection::global(), &KCupsConnection::rhPrinterAdded, this, &PrintQueueUi::updatePrinterByName); connect(KCupsConnection::global(), &KCupsConnection::rhPrinterRemoved, this, &PrintQueueUi::updatePrinterByName); connect(KCupsConnection::global(), &KCupsConnection::rhQueueChanged, this, &PrintQueueUi::updatePrinterByName); updatePrinterByName(m_destName); // Restore the dialog size KConfigGroup configGroup(KSharedConfig::openConfig(QLatin1String("print-manager")), "PrintQueue"); KWindowConfig::restoreWindowSize(windowHandle(), configGroup); auto delJobShortcut = new QShortcut(QKeySequence::Delete, ui->jobsView); delJobShortcut->setContext(Qt::WidgetShortcut); connect(delJobShortcut, &QShortcut::activated, this, &PrintQueueUi::cancelJob); } PrintQueueUi::~PrintQueueUi() { KConfigGroup configGroup(KSharedConfig::openConfig(QLatin1String("print-manager")), "PrintQueue"); // save the header state order configGroup.writeEntry("ColumnState", ui->jobsView->header()->saveState()); // Save the dialog size KWindowConfig::saveWindowSize(windowHandle(), configGroup); delete ui; } int PrintQueueUi::columnCount(const QModelIndex &parent) const { if (!parent.isValid()) { return JobModel::LastColumn; } return 0; } void PrintQueueUi::setState(int state, const QString &message) { qDebug() << state << message; if (state != m_lastState || ui->printerStatusMsgL->text() != message) { // save the last state so the ui doesn't need to keep updating if (ui->printerStatusMsgL->text() != message) { ui->printerStatusMsgL->setText(message); } m_lastState = state; QPixmap icon(m_printerIcon); m_printerPaused = false; switch (state) { case KCupsPrinter::Idle: ui->statusL->setText(i18n("Printer ready")); ui->pausePrinterPB->setText(i18n("Pause Printer")); ui->pausePrinterPB->setIcon(QIcon::fromTheme(QLatin1String("media-playback-pause"))); break; case KCupsPrinter::Printing: if (!m_title.isNull()) { QString jobTitle = m_model->processingJob(); if (jobTitle.isEmpty()) { ui->statusL->setText(i18n("Printing...")); } else { ui->statusL->setText(i18n("Printing '%1'", jobTitle)); } ui->pausePrinterPB->setText(i18n("Pause Printer")); ui->pausePrinterPB->setIcon(QIcon::fromTheme(QLatin1String("media-playback-pause"))); } break; case KCupsPrinter::Stopped: m_printerPaused = true; ui->statusL->setText(i18n("Printer paused")); ui->pausePrinterPB->setText(i18n("Resume Printer")); ui->pausePrinterPB->setIcon(QIcon::fromTheme(QLatin1String("media-playback-start"))); - // create a paiter to paint the action icon over the key icon + // create a painter to paint the action icon over the key icon { QPainter painter(&icon); // the emblem icon to size 32 int overlaySize = KIconLoader::SizeMedium; QPoint startPoint; // bottom right corner startPoint = QPoint(PRINTER_ICON_SIZE - overlaySize - 2, PRINTER_ICON_SIZE - overlaySize - 2); painter.drawPixmap(startPoint, m_pauseIcon); } break; default : ui->statusL->setText(i18n("Printer state unknown")); break; } // set the printer icon setWindowIcon(icon); } } void PrintQueueUi::showContextMenu(const QPoint &point) { // check if the click was actually over a job if (!ui->jobsView->indexAt(point).isValid() || m_preparingMenu) { return; } m_preparingMenu = true; bool moveTo = false; QItemSelection selection; // we need to map the selection to source to get the real indexes selection = m_proxyModel->mapSelectionToSource(ui->jobsView->selectionModel()->selection()); // if the selection is empty the user clicked on an empty space if (!selection.indexes().isEmpty()) { const QModelIndexList indexes = selection.indexes(); for (const QModelIndex &index : indexes) { if (index.column() == 0 && index.flags() & Qt::ItemIsDragEnabled) { // Found a move to item moveTo = true; break; } } // if we can move a job create the menu if (moveTo) { // context menu auto menu = new QMenu(this); // move to menu auto moveToMenu = new QMenu(i18n("Move to"), this); // get printers we can move to QPointer request = new KCupsRequest; request->getPrinters({ KCUPS_PRINTER_NAME, KCUPS_PRINTER_INFO }); request->waitTillFinished(); if (!request) { return; } const KCupsPrinters printers = request->printers(); request->deleteLater(); for (const KCupsPrinter &printer : printers) { // If there is a printer and it's not the current one add it // as a new destination if (printer.name() != m_destName) { QAction *action = moveToMenu->addAction(printer.info()); action->setData(printer.name()); } } if (!moveToMenu->isEmpty()) { menu->addMenu(moveToMenu); // show the menu on the right point QAction *action = menu->exec(ui->jobsView->mapToGlobal(point)); if (action) { // move the job modifyJob(JobModel::Move, action->data().toString()); } } } } m_preparingMenu = false; } void PrintQueueUi::showHeaderContextMenu(const QPoint &point) { // Displays a menu containing the header name, and // a check box to indicate if it's being shown auto menu = new QMenu(this); for (int i = 0; i < m_proxyModel->columnCount(); i++) { auto name = m_proxyModel->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString(); QAction *action = menu->addAction(name); action->setCheckable(true); action->setChecked(!ui->jobsView->header()->isSectionHidden(i)); action->setData(i); } QAction *action = menu->exec(ui->jobsView->header()->mapToGlobal(point)); if (action) { int section = action->data().toInt(); if (action->isChecked()) { ui->jobsView->header()->showSection(section); } else { ui->jobsView->header()->hideSection(section); } } } void PrintQueueUi::updatePrinterByName(const QString &printer) { qDebug() << printer << m_destName; if (printer != m_destName) { // It was another printer that changed return; } const QStringList attr({ KCUPS_PRINTER_INFO, KCUPS_PRINTER_TYPE, KCUPS_PRINTER_STATE, KCUPS_PRINTER_STATE_MESSAGE, }); auto request = new KCupsRequest; connect(request, &KCupsRequest::finished, this, &PrintQueueUi::getAttributesFinished); request->getPrinterAttributes(printer, m_isClass, attr); } void PrintQueueUi::updatePrinter(const QString &text, const QString &printerUri, const QString &printerName, uint printerState, const QString &printerStateReasons, bool printerIsAcceptingJobs) { // REALLY? all these parameters just to say foo was added?? Q_UNUSED(text) Q_UNUSED(printerUri) Q_UNUSED(printerState) Q_UNUSED(printerStateReasons) Q_UNUSED(printerIsAcceptingJobs) qDebug() << printerName << printerStateReasons; updatePrinterByName(printerName); } void PrintQueueUi::getAttributesFinished(KCupsRequest *request) { qDebug() << request->hasError() << request->printers().isEmpty(); if (request->hasError() || request->printers().isEmpty()) { // if cups stops we disable our queue setEnabled(false); request->deleteLater(); // DO not delete before using as the request is in another thread return; } else if (isEnabled() == false) { // if cups starts again we enable our queue setEnabled(true); } KCupsPrinter printer = request->printers().first(); // get printer-info if (printer.info().isEmpty()) { m_title = printer.name(); } else { m_title = printer.name() + QLatin1String(" - ") + printer.info(); } // get printer-state setState(printer.state(), printer.stateMsg()); // store if the printer is a class m_isClass = printer.isClass(); request->deleteLater(); update(); } void PrintQueueUi::update() { // Set window title if (m_model->rowCount()) { if (m_destName.isNull()) { setWindowTitle(i18np("All Printers (%1 Job)", "All Printers (%1 Jobs)", m_model->rowCount())); } else { setWindowTitle(i18np("%2 (%1 Job)", "%2 (%1 Jobs)", m_model->rowCount(), m_title)); } } else { setWindowTitle(m_destName.isNull() ? i18n("All Printers") : m_title); } } void PrintQueueUi::updateButtons() { bool cancel, hold, release, reprint; // Set all options to false cancel = hold = release = reprint = false; QItemSelection selection; // we need to map the selection to source to get the real indexes selection = m_proxyModel->mapSelectionToSource(ui->jobsView->selectionModel()->selection()); // enable or disable the job action buttons if something is selected if (!selection.indexes().isEmpty()) { const QModelIndexList indexes = selection.indexes(); for (const QModelIndex &index : indexes) { if (index.column() == 0) { switch (static_cast(index.data(JobModel::RoleJobState).toInt())) { case IPP_JOB_CANCELED : case IPP_JOB_COMPLETED : case IPP_JOB_ABORTED : break; case IPP_JOB_HELD : case IPP_JOB_STOPPED : release = true; cancel = true; break; default: cancel = hold = true; break; } if (index.data(JobModel::RoleJobRestartEnabled).toBool()) { reprint = true; } } } } ui->cancelJobPB->setEnabled(cancel); ui->holdJobPB->setEnabled(hold); ui->resumeJobPB->setEnabled(release); ui->reprintPB->setEnabled(reprint); } void PrintQueueUi::modifyJob(int action, const QString &destName) { // get all selected indexes QItemSelection selection; // we need to map the selection to source to get the real indexes selection = m_proxyModel->mapSelectionToSource(ui->jobsView->selectionModel()->selection()); const QModelIndexList indexes = selection.indexes(); for (const QModelIndex &index : indexes) { if (index.column() == 0) { KCupsRequest *request; request = m_model->modifyJob(index.row(), static_cast(action), destName); if (!request) { // probably the job already has this state // or this is an unknown action continue; } request->waitTillFinished(); if (request->hasError()) { QString msg, jobName; jobName = m_model->item(index.row(), static_cast(JobModel::ColName))->text(); switch (action) { case JobModel::Cancel: msg = i18n("Failed to cancel '%1'", jobName); break; case JobModel::Hold: msg = i18n("Failed to hold '%1'", jobName); break; case JobModel::Release: msg = i18n("Failed to release '%1'", jobName); break; case JobModel::Reprint: msg = i18n("Failed to reprint '%1'", jobName); break; case JobModel::Move: msg = i18n("Failed to move '%1' to '%2'", jobName, destName); break; } KMessageBox::detailedSorry(this, msg, request->errorMsg(), i18n("Failed")); } request->deleteLater(); } } } void PrintQueueUi::pausePrinter() { // STOP and RESUME printer QPointer request = new KCupsRequest; if (m_printerPaused) { qDebug() << m_destName << "m_printerPaused"; request->resumePrinter(m_destName); } else { qDebug() << m_destName << "NOT m_printerPaused"; request->pausePrinter(m_destName); } request->waitTillFinished(); if (request) { request->deleteLater(); } } void PrintQueueUi::configurePrinter() { QProcess::startDetached(QLatin1String("configure-printer"), {m_destName}); } void PrintQueueUi::cancelJob() { // CANCEL a job modifyJob(JobModel::Cancel); } void PrintQueueUi::holdJob() { // HOLD a job modifyJob(JobModel::Hold); } void PrintQueueUi::resumeJob() { // RESUME a job modifyJob(JobModel::Release); } void PrintQueueUi::reprintJob() { modifyJob(JobModel::Reprint); } void PrintQueueUi::whichJobsIndexChanged(int index) { switch (index) { case 1: m_model->setWhichJobs(JobModel::WhichCompleted); break; case 2: m_model->setWhichJobs(JobModel::WhichAll); break; default: m_model->setWhichJobs(JobModel::WhichActive); break; } } void PrintQueueUi::setupButtons() { // setup jobs buttons // cancel action ui->cancelJobPB->setIcon(QIcon::fromTheme(QLatin1String("dialog-cancel"))); // hold job action ui->holdJobPB->setIcon(QIcon::fromTheme(QLatin1String("document-open-recent"))); // resume job action // TODO we need a new icon ui->resumeJobPB->setIcon(QIcon::fromTheme(QLatin1String("media-playback-start"))); ui->reprintPB->setIcon(QIcon::fromTheme(QLatin1String("view-refresh"))); const QIcon viewFilterIcon = QIcon::fromTheme(QLatin1String("view-filter")); ui->whichJobsCB->setItemIcon(0, viewFilterIcon); ui->whichJobsCB->setItemIcon(1, viewFilterIcon); ui->whichJobsCB->setItemIcon(2, viewFilterIcon); // stop start printer ui->pausePrinterPB->setIcon(QIcon::fromTheme(QLatin1String("media-playback-pause"))); // configure printer ui->configurePrinterPB->setIcon(QIcon::fromTheme(QLatin1String("configure"))); } void PrintQueueUi::closeEvent(QCloseEvent *event) { // emits finished signal to be removed the cache emit finished(); QWidget::closeEvent(event); } #include "moc_PrintQueueUi.cpp"