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"