File indexing completed on 2024-10-27 07:30:05

0001 /*
0002  * SPDX-FileCopyrightText: 2007-2008 Kare Sars <kare dot sars at iki dot fi>
0003  * SPDX-FileCopyrightText: 2007-2008 Gilles Caulier <caulier dot gilles at gmail dot com>
0004  * SPDX-FileCopyrightText: 2014 Gregor Mitsch : port to KDE5 frameworks
0005  * SPDX-FileCopyrightText: 2021 Alexander Stippich <a.stippich@gmx.net>
0006  *
0007  * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0008  */
0009 
0010 #include "interface_p.h"
0011 
0012 #include <QImage>
0013 #include <QRegularExpression>
0014 
0015 #include <ksanecore_debug.h>
0016 
0017 #include "actionoption.h"
0018 #include "batchdelayoption.h"
0019 #include "batchmodeoption.h"
0020 #include "booloption.h"
0021 #include "doubleoption.h"
0022 #include "gammaoption.h"
0023 #include "integeroption.h"
0024 #include "internaloption.h"
0025 #include "invertoption.h"
0026 #include "listoption.h"
0027 #include "pagesizeoption.h"
0028 #include "stringoption.h"
0029 
0030 namespace KSaneCore
0031 {
0032 
0033 InterfacePrivate::InterfacePrivate(Interface *parent)
0034     : q(parent)
0035 {
0036     clearDeviceOptions();
0037 
0038     m_findDevThread = FindSaneDevicesThread::getInstance();
0039     connect(m_findDevThread, &FindSaneDevicesThread::finished, this, &InterfacePrivate::devicesListUpdated);
0040     connect(m_findDevThread, &FindSaneDevicesThread::finished, this, &InterfacePrivate::signalDevicesListUpdate);
0041 
0042     m_auth = Authentication::getInstance();
0043     m_optionPollTimer.setInterval(100);
0044     connect(&m_optionPollTimer, &QTimer::timeout, this, &InterfacePrivate::pollPollOptions);
0045 
0046     m_batchModeTimer.setInterval(1000);
0047     connect(&m_batchModeTimer, &QTimer::timeout, this, &InterfacePrivate::batchModeTimerUpdate);
0048 }
0049 
0050 Interface::OpenStatus InterfacePrivate::loadDeviceOptions()
0051 {
0052     static const QHash<QString, Interface::OptionName> stringEnumTranslation = {
0053         {QStringLiteral(SANE_NAME_SCAN_SOURCE), Interface::SourceOption},
0054         {QStringLiteral(SANE_NAME_SCAN_MODE), Interface::ScanModeOption},
0055         {QStringLiteral(SANE_NAME_BIT_DEPTH), Interface::BitDepthOption},
0056         {QStringLiteral(SANE_NAME_SCAN_RESOLUTION), Interface::ResolutionOption},
0057         {QStringLiteral(SANE_NAME_SCAN_TL_X), Interface::TopLeftXOption},
0058         {QStringLiteral(SANE_NAME_SCAN_TL_Y), Interface::TopLeftYOption},
0059         {QStringLiteral(SANE_NAME_SCAN_BR_X), Interface::BottomRightXOption},
0060         {QStringLiteral(SANE_NAME_SCAN_BR_Y), Interface::BottomRightYOption},
0061         {QStringLiteral("film-type"), Interface::FilmTypeOption},
0062         {QStringLiteral(SANE_NAME_NEGATIVE), Interface::NegativeOption},
0063         {InvertColorsOptionName, Interface::InvertColorOption},
0064         {PageSizeOptionName, Interface::PageSizeOption},
0065         {QStringLiteral(SANE_NAME_THRESHOLD), Interface::ThresholdOption},
0066         {QStringLiteral(SANE_NAME_SCAN_X_RESOLUTION), Interface::XResolutionOption},
0067         {QStringLiteral(SANE_NAME_SCAN_Y_RESOLUTION), Interface::YResolutionOption},
0068         {QStringLiteral(SANE_NAME_PREVIEW), Interface::PreviewOption},
0069         {QStringLiteral("wait-for-button"), Interface::WaitForButtonOption},
0070         {QStringLiteral(SANE_NAME_BRIGHTNESS), Interface::BrightnessOption},
0071         {QStringLiteral(SANE_NAME_CONTRAST), Interface::ContrastOption},
0072         {QStringLiteral(SANE_NAME_GAMMA_VECTOR), Interface::GammaOption},
0073         {QStringLiteral(SANE_NAME_GAMMA_VECTOR_R), Interface::GammaRedOption},
0074         {QStringLiteral(SANE_NAME_GAMMA_VECTOR_G), Interface::GammaGreenOption},
0075         {QStringLiteral(SANE_NAME_GAMMA_VECTOR_B), Interface::GammaBlueOption},
0076         {QStringLiteral(SANE_NAME_BLACK_LEVEL), Interface::BlackLevelOption},
0077         {QStringLiteral(SANE_NAME_WHITE_LEVEL), Interface::WhiteLevelOption},
0078         {BatchModeOptionName, Interface::BatchModeOption},
0079         {BatchDelayOptionName, Interface::BatchDelayOption},
0080     };
0081 
0082     const SANE_Option_Descriptor *optDesc;
0083     SANE_Status status;
0084     SANE_Word numSaneOptions;
0085     SANE_Int res;
0086     // update the device list if needed to get the vendor and model info
0087     if (m_findDevThread->devicesList().size() == 0) {
0088         m_findDevThread->start();
0089     } else {
0090         // use the "old" existing list
0091         devicesListUpdated();
0092         // if m_vendor is not updated it means that the list needs to be updated.
0093         if (m_vendor.isEmpty()) {
0094             m_findDevThread->start();
0095         }
0096     }
0097 
0098     // Read the options (start with option 0 the number of parameters)
0099     optDesc = sane_get_option_descriptor(m_saneHandle, 0);
0100     if (optDesc == nullptr) {
0101         m_auth->clearDeviceAuth(m_devName);
0102         m_devName.clear();
0103         return Interface::OpeningFailed;
0104     }
0105     QVarLengthArray<char> data(optDesc->size);
0106     status = sane_control_option(m_saneHandle, 0, SANE_ACTION_GET_VALUE, data.data(), &res);
0107     if (status != SANE_STATUS_GOOD) {
0108         m_auth->clearDeviceAuth(m_devName);
0109         m_devName.clear();
0110         return Interface::OpeningFailed;
0111     }
0112     numSaneOptions = *reinterpret_cast<SANE_Word *>(data.data());
0113 
0114     // read the rest of the options
0115     BaseOption *option = nullptr;
0116     BaseOption *optionTopLeftX = nullptr;
0117     BaseOption *optionTopLeftY = nullptr;
0118     BaseOption *optionBottomRightX = nullptr;
0119     BaseOption *optionBottomRightY = nullptr;
0120     BaseOption *optionResolution = nullptr;
0121     m_optionsList.reserve(numSaneOptions);
0122     m_externalOptionsList.reserve(numSaneOptions);
0123     for (int i = 1; i < numSaneOptions; ++i) {
0124         switch (BaseOption::optionType(sane_get_option_descriptor(m_saneHandle, i))) {
0125         case Option::TypeDetectFail:
0126             option = new BaseOption(m_saneHandle, i);
0127             break;
0128         case Option::TypeBool:
0129             option = new BoolOption(m_saneHandle, i);
0130             break;
0131         case Option::TypeInteger:
0132             option = new IntegerOption(m_saneHandle, i);
0133             break;
0134         case Option::TypeDouble:
0135             option = new DoubleOption(m_saneHandle, i);
0136             break;
0137         case Option::TypeValueList:
0138             option = new ListOption(m_saneHandle, i);
0139             break;
0140         case Option::TypeString:
0141             option = new StringOption(m_saneHandle, i);
0142             break;
0143         case Option::TypeGamma:
0144             option = new GammaOption(m_saneHandle, i);
0145             break;
0146         case Option::TypeAction:
0147             option = new ActionOption(m_saneHandle, i);
0148             break;
0149         }
0150         option->readOption();
0151         option->readValue();
0152 
0153         if (option->name() == QStringLiteral(SANE_NAME_SCAN_TL_X)) {
0154             optionTopLeftX = option;
0155         }
0156         if (option->name() == QStringLiteral(SANE_NAME_SCAN_TL_Y)) {
0157             optionTopLeftY = option;
0158         }
0159         if (option->name() == QStringLiteral(SANE_NAME_SCAN_BR_X)) {
0160             optionBottomRightX = option;
0161         }
0162         if (option->name() == QStringLiteral(SANE_NAME_SCAN_BR_Y)) {
0163             optionBottomRightY = option;
0164         }
0165         if (option->name() == QStringLiteral(SANE_NAME_SCAN_RESOLUTION)) {
0166             optionResolution = option;
0167         }
0168         if (option->name() == QStringLiteral(SANE_NAME_SCAN_SOURCE)) {
0169             // some scanners only have ADF and never update the source name
0170             determineMultiPageScanning(option->value());
0171             connect(option, &BaseOption::valueChanged, this, &InterfacePrivate::determineMultiPageScanning);
0172         }
0173         if (option->name() == QStringLiteral("wait-for-button")) {
0174             connect(option, &BaseOption::valueChanged, this, &InterfacePrivate::setWaitForExternalButton);
0175         }
0176 
0177         m_optionsList.append(option);
0178         m_externalOptionsList.append(new InternalOption(option));
0179         connect(option, &BaseOption::optionsNeedReload, this, &InterfacePrivate::reloadOptions);
0180         connect(option, &BaseOption::valuesNeedReload, this, &InterfacePrivate::scheduleValuesReload);
0181 
0182         if (option->needsPolling()) {
0183             m_optionsPollList.append(option);
0184             if (option->type() == Option::TypeBool) {
0185                 connect(option, &BaseOption::valueChanged, this, [=](const QVariant &newValue) {
0186                     Q_EMIT q->buttonPressed(option->name(), option->title(), newValue.toBool());
0187                 });
0188             }
0189         }
0190         const auto it = stringEnumTranslation.find(option->name());
0191         if (it != stringEnumTranslation.constEnd()) {
0192             m_optionsLocation.insert(it.value(), i - 1);
0193         }
0194     }
0195 
0196     // add extra option for selecting specific page sizes
0197     BaseOption *pageSizeOption = new PageSizeOption(optionTopLeftX, optionTopLeftY, optionBottomRightX, optionBottomRightY, optionResolution);
0198     m_optionsList.append(pageSizeOption);
0199     m_externalOptionsList.append(new InternalOption(pageSizeOption));
0200     m_optionsLocation.insert(Interface::PageSizeOption, m_optionsList.size() - 1);
0201 
0202     // add extra option for batch mode scanning with a delay
0203     m_batchMode = new BatchModeOption();
0204     m_optionsList.append(m_batchMode);
0205     m_externalOptionsList.append(new InternalOption(m_batchMode));
0206     m_optionsLocation.insert(Interface::BatchModeOption, m_optionsList.size() - 1);
0207     m_batchModeDelay = new BatchDelayOption();
0208     m_optionsList.append(m_batchModeDelay);
0209     m_externalOptionsList.append(new InternalOption(m_batchModeDelay));
0210     m_optionsLocation.insert(Interface::BatchDelayOption, m_optionsList.size() - 1);
0211 
0212     // add extra option for inverting image colors
0213     BaseOption *invertOption = new InvertOption();
0214     m_optionsList.append(invertOption);
0215     m_externalOptionsList.append(new InternalOption(invertOption));
0216     m_optionsLocation.insert(Interface::InvertColorOption, m_optionsList.size() - 1);
0217 
0218     // NOTICE The Pixma network backend behaves badly. polling a value will result in 1 second
0219     // sleeps for every poll. The problem has been reported, but no easy/quick fix was available and
0220     // the bug has been there for multiple years. Since this destroys the usability of the backend totally,
0221     // we simply put the backend on the naughty list and disable the option polling.
0222     static QRegularExpression pixmaNetworkBackend(QStringLiteral("pixma.*\\d+\\.\\d+\\.\\d+\\.\\d+"));
0223     m_optionPollingNaughtylisted = false;
0224     if (pixmaNetworkBackend.match(m_devName).hasMatch()) {
0225         m_optionPollingNaughtylisted = true;
0226     }
0227 
0228     // start polling the poll options
0229     if (m_optionsPollList.size() > 0 && !m_optionPollingNaughtylisted) {
0230         m_optionPollTimer.start();
0231     }
0232 
0233     // Create the scan thread
0234     m_scanThread = new ScanThread(m_saneHandle);
0235 
0236     m_scanThread->setImageInverted(invertOption->value());
0237     connect(invertOption, &InvertOption::valueChanged, m_scanThread, &ScanThread::setImageInverted);
0238 
0239     if (optionResolution != nullptr) {
0240         m_scanThread->setImageResolution(optionResolution->value());
0241         connect(optionResolution, &BaseOption::valueChanged, m_scanThread, &ScanThread::setImageResolution);
0242     }
0243 
0244     connect(m_scanThread, &ScanThread::scanProgressUpdated, q, &Interface::scanProgress);
0245     connect(m_scanThread, &ScanThread::finished, this, &InterfacePrivate::imageScanFinished);
0246 
0247     // try to set to default values
0248     setDefaultValues();
0249     return Interface::OpeningSucceeded;
0250 }
0251 
0252 void InterfacePrivate::clearDeviceOptions()
0253 {
0254     // delete all the options in the list.
0255     while (!m_optionsList.isEmpty()) {
0256         delete m_optionsList.takeFirst();
0257         delete m_externalOptionsList.takeFirst();
0258     }
0259 
0260     m_optionsLocation.clear();
0261     m_optionsPollList.clear();
0262     m_optionPollTimer.stop();
0263 
0264     m_devName.clear();
0265     m_model.clear();
0266     m_vendor.clear();
0267     m_batchMode = nullptr;
0268     m_batchModeDelay = nullptr;
0269 }
0270 
0271 void InterfacePrivate::devicesListUpdated()
0272 {
0273     if (m_vendor.isEmpty()) {
0274         const QList<DeviceInformation *> deviceList = m_findDevThread->devicesList();
0275         for (const auto &device : deviceList) {
0276             if (device->name() == m_devName) {
0277                 m_vendor = device->vendor();
0278                 m_model = device->model();
0279                 break;
0280             }
0281         }
0282     }
0283 }
0284 
0285 void InterfacePrivate::signalDevicesListUpdate()
0286 {
0287     Q_EMIT q->availableDevices(m_findDevThread->devicesList());
0288 }
0289 
0290 void InterfacePrivate::setDefaultValues()
0291 {
0292     Option *option;
0293 
0294     // Try to get Color mode by default
0295     if ((option = q->getOption(Interface::ScanModeOption)) != nullptr) {
0296         option->setValue(sane_i18n(SANE_VALUE_SCAN_MODE_COLOR));
0297     }
0298 
0299     // Try to set 8 bit color
0300     if ((option = q->getOption(Interface::BitDepthOption)) != nullptr) {
0301         option->setValue(8);
0302     }
0303 
0304     // Try to set Scan resolution to 300 DPI
0305     if ((option = q->getOption(Interface::ResolutionOption)) != nullptr) {
0306         option->setValue(300);
0307     }
0308 }
0309 
0310 void InterfacePrivate::scheduleValuesReload()
0311 {
0312     m_readValuesTimer.start(5);
0313 }
0314 
0315 void InterfacePrivate::reloadOptions()
0316 {
0317     for (const auto option : qAsConst(m_optionsList)) {
0318         option->readOption();
0319         // Also read the values
0320         option->readValue();
0321     }
0322 }
0323 
0324 void InterfacePrivate::reloadValues()
0325 {
0326     for (const auto option : qAsConst(m_optionsList)) {
0327         option->readValue();
0328     }
0329 }
0330 
0331 void InterfacePrivate::pollPollOptions()
0332 {
0333     for (int i = 1; i < m_optionsPollList.size(); ++i) {
0334         m_optionsPollList.at(i)->readValue();
0335     }
0336 }
0337 
0338 void InterfacePrivate::imageScanFinished()
0339 {
0340     Q_EMIT q->scanProgress(100);
0341     if (m_scanThread->frameStatus() == ScanThread::ReadReady) {
0342         Q_EMIT q->scannedImageReady(*m_scanThread->scanImage());
0343         // now check if we should have automatic ADF batch scanning
0344         if (m_executeMultiPageScanning && !m_cancelMultiPageScan) {
0345             // in batch mode only one area can be scanned per page
0346             Q_EMIT q->scanProgress(-1);
0347             m_scanThread->start();
0348             return;
0349         }
0350         // check if we should have timed batch scanning
0351         if (m_batchMode->value().toBool() && !m_cancelMultiPageScan) {
0352             // in batch mode only one area can be scanned per page
0353             m_batchModeCounter = 0;
0354             batchModeTimerUpdate();
0355             m_batchModeTimer.start();
0356             return;
0357         }
0358         // Check if we have a "wait for button" batch scanning
0359         if (m_waitForExternalButton) {
0360             qCDebug(KSANECORE_LOG) << "waiting for external button press to start next scan";
0361             Q_EMIT q->scanProgress(-1);
0362             m_scanThread->start();
0363             return;
0364         }
0365         scanIsFinished(Interface::NoError, QString());
0366     } else {
0367         switch (m_scanThread->saneStatus()) {
0368         case SANE_STATUS_GOOD:
0369         case SANE_STATUS_CANCELLED:
0370         case SANE_STATUS_EOF:
0371             scanIsFinished(Interface::NoError, sane_i18n(sane_strstatus(m_scanThread->saneStatus())));
0372             break;
0373         case SANE_STATUS_NO_DOCS:
0374             Q_EMIT q->userMessage(Interface::Information, sane_i18n(sane_strstatus(m_scanThread->saneStatus())));
0375             scanIsFinished(Interface::Information, sane_i18n(sane_strstatus(m_scanThread->saneStatus())));
0376             break;
0377         case SANE_STATUS_UNSUPPORTED:
0378         case SANE_STATUS_IO_ERROR:
0379         case SANE_STATUS_NO_MEM:
0380         case SANE_STATUS_INVAL:
0381         case SANE_STATUS_JAMMED:
0382         case SANE_STATUS_COVER_OPEN:
0383         case SANE_STATUS_DEVICE_BUSY:
0384         case SANE_STATUS_ACCESS_DENIED:
0385             Q_EMIT q->userMessage(Interface::ErrorGeneral, sane_i18n(sane_strstatus(m_scanThread->saneStatus())));
0386             scanIsFinished(Interface::ErrorGeneral, sane_i18n(sane_strstatus(m_scanThread->saneStatus())));
0387             break;
0388         }
0389     }
0390 }
0391 
0392 void InterfacePrivate::scanIsFinished(Interface::ScanStatus status, const QString &message)
0393 {
0394     sane_cancel(m_saneHandle);
0395     if (m_optionsPollList.size() > 0 && !m_optionPollingNaughtylisted) {
0396         m_optionPollTimer.start();
0397     }
0398 
0399     Q_EMIT q->scanFinished(status, message);
0400 }
0401 
0402 void InterfacePrivate::determineMultiPageScanning(const QVariant &value)
0403 {
0404     const QString sourceString = value.toString();
0405 
0406     m_executeMultiPageScanning = sourceString.contains(QStringLiteral("Automatic Document Feeder"))
0407         || sourceString.contains(sane_i18n("Automatic Document Feeder")) || sourceString.contains(QStringLiteral("ADF"))
0408         || sourceString.contains(QStringLiteral("Duplex"));
0409 }
0410 
0411 void InterfacePrivate::setWaitForExternalButton(const QVariant &value)
0412 {
0413     m_waitForExternalButton = value.toBool();
0414 }
0415 
0416 void InterfacePrivate::batchModeTimerUpdate()
0417 {
0418     const int delay = m_batchModeDelay->value().toInt();
0419     Q_EMIT q->batchModeCountDown(delay - m_batchModeCounter);
0420     if (m_batchModeCounter >= delay) {
0421         m_batchModeCounter = 0;
0422         if (m_scanThread != nullptr) {
0423             Q_EMIT q->scanProgress(-1);
0424             m_scanThread->start();
0425         }
0426         m_batchModeTimer.stop();
0427     }
0428     m_batchModeCounter++;
0429 }
0430 
0431 } // NameSpace KSaneCore
0432 
0433 #include "moc_interface_p.cpp"