KSaneCore

interface.cpp
1/*
2 * SPDX-FileCopyrightText: 2007-2010 Kare Sars <kare dot sars at iki dot fi>
3 * SPDX-FileCopyrightText: 2009 Matthias Nagl <matthias at nagl dot info>
4 * SPDX-FileCopyrightText: 2009 Grzegorz Kurtyka <grzegorz dot kurtyka at gmail dot com>
5 * SPDX-FileCopyrightText: 2007-2008 Gilles Caulier <caulier dot gilles at gmail dot com>
6 * SPDX-FileCopyrightText: 2014 Gregor Mitsch : port to KDE5 frameworks
7 * SPDX-FileCopyrightText: 2021 Alexander Stippich <a.stippich@gmx.net>
8 *
9 * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
10 */
11
12// Qt includes
13#include <QJsonArray>
14#include <QJsonObject>
15#include <QJsonValue>
16#include <QMetaEnum>
17#include <QMutex>
18#include <QUrl>
19
20// Sane includes
21extern "C" {
22#include <sane/sane.h>
23#include <sane/saneopts.h>
24}
25
26#include "interface.h"
27#include "interface_p.h"
28
29#include <ksanecore_debug.h>
30
31namespace KSaneCore
32{
33static int s_objectCount = 0;
34
35Q_GLOBAL_STATIC(QMutex, s_objectMutex)
36
38 : QObject(parent)
39 , d(std::make_unique<InterfacePrivate>(this))
40{
41 SANE_Int version;
42 SANE_Status status;
43
44 s_objectMutex->lock();
45 s_objectCount++;
46
47 if (s_objectCount == 1) {
48 // only call sane init for the first instance
49 status = sane_init(&version, &Authentication::authorization);
50 if (status != SANE_STATUS_GOOD) {
51 qCDebug(KSANECORE_LOG) << "libksane: sane_init() failed(" << sane_strstatus(status) << ")";
52 }
53 }
54 s_objectMutex->unlock();
55
56 d->m_readValuesTimer.setSingleShot(true);
57 connect(&d->m_readValuesTimer, &QTimer::timeout, d.get(), &InterfacePrivate::reloadValues);
58}
59
61{
63
64 s_objectMutex->lock();
65 s_objectCount--;
66 if (s_objectCount <= 0) {
67 // only delete the find-devices and authorization singletons and call sane_exit
68 // if this is the last instance
69 delete d->m_findDevThread;
70 delete d->m_auth;
71 sane_exit();
72 }
73 s_objectMutex->unlock();
74}
75
77{
78 return d->m_devName;
79}
80
82{
83 return d->m_vendor;
84}
85
87{
88 return d->m_model;
89}
90
92{
93 d->m_previewDPI = dpi;
94}
95
97{
98 /* On some SANE backends, the handle becomes invalid when
99 * querying for new devices. Hence, this is only allowed when
100 * no device is currently opened. */
101 if (d->m_saneHandle == nullptr) {
102 d->m_findDevThread->setDeviceType(type);
103 d->m_findDevThread->start();
104 return true;
105 }
106 return false;
107}
108
110{
111 SANE_Status status;
112
113 if (d->m_saneHandle != nullptr) {
114 // this CoreInterface already has an open device
115 return OpenStatus::OpeningFailed;
116 }
117
118 // don't bother trying to open if the device string is empty
119 if (deviceName.isEmpty()) {
120 return OpenStatus::OpeningFailed;
121 }
122 // save the device name
123 d->m_devName = deviceName;
124
125 // Try to open the device
126 status = sane_open(deviceName.toLatin1().constData(), &d->m_saneHandle);
127
128 if (status == SANE_STATUS_ACCESS_DENIED) {
129 return OpenStatus::OpeningDenied;
130 }
131
132 if (status != SANE_STATUS_GOOD) {
133 qCDebug(KSANECORE_LOG) << "sane_open(\"" << deviceName << "\", &handle) failed! status = " << sane_strstatus(status);
134 d->m_devName.clear();
135 return OpenStatus::OpeningFailed;
136 }
137
138 return d->loadDeviceOptions();
139}
140
141Interface::OpenStatus Interface::openRestrictedDevice(const QString &deviceName, const QString &userName, const QString &password)
142{
143 SANE_Status status;
144
145 if (d->m_saneHandle != nullptr) {
146 // this CoreInterface already has an open device
147 return OpenStatus::OpeningFailed;
148 }
149
150 // don't bother trying to open if the device string is empty
151 if (deviceName.isEmpty()) {
152 return OpenStatus::OpeningFailed;
153 }
154 // save the device name
155 d->m_devName = deviceName;
156
157 // add/update the device user-name and password for authentication
158 d->m_auth->setDeviceAuth(d->m_devName, userName, password);
159
160 // Try to open the device
161 status = sane_open(deviceName.toLatin1().constData(), &d->m_saneHandle);
162
163 if (status == SANE_STATUS_ACCESS_DENIED) {
164 return OpenStatus::OpeningDenied;
165 }
166
167 if (status != SANE_STATUS_GOOD) {
168 qCDebug(KSANECORE_LOG) << "sane_open(\"" << deviceName << "\", &handle) failed! status = " << sane_strstatus(status);
169 d->m_auth->clearDeviceAuth(d->m_devName);
170 d->m_devName.clear();
171 return OpenStatus::OpeningFailed;
172 }
173
174 return d->loadDeviceOptions();
175}
176
178{
179 if (!d->m_saneHandle) {
180 return false;
181 }
182 stopScan();
183
184 disconnect(d->m_scanThread);
185 if (d->m_scanThread->isRunning()) {
186 connect(d->m_scanThread, &QThread::finished, d->m_scanThread, &QThread::deleteLater);
187 }
188 if (d->m_scanThread->isFinished()) {
189 d->m_scanThread->deleteLater();
190 }
191 d->m_scanThread = nullptr;
192
193 d->m_auth->clearDeviceAuth(d->m_devName);
194 sane_close(d->m_saneHandle);
195 d->m_saneHandle = nullptr;
196 d->clearDeviceOptions();
197
198 return true;
199}
200
202{
203 if (!d->m_saneHandle) {
204 return;
205 }
206 d->m_cancelMultiPageScan = false;
207 // execute a pending value reload
208 while (d->m_readValuesTimer.isActive()) {
209 d->m_readValuesTimer.stop();
210 d->reloadValues();
211 }
212 d->m_optionPollTimer.stop();
213 d->emitProgress(-1);
214 d->m_scanThread->start();
215}
216
218{
219 d->m_previewScan = true;
220 Option *topLeftXOption = getOption(Interface::TopLeftXOption);
221 Option *topLeftYOption = getOption(Interface::TopLeftYOption);
222 Option *bottomRightXOption = getOption(Interface::BottomRightXOption);
223 Option *bottomRightYOption = getOption(Interface::BottomRightYOption);
224 Option *previewOption = getOption(Interface::PreviewOption);
225 Option *resolutionOption = getOption(Interface::ResolutionOption);
226 Option *bitDepthOption = getOption(Interface::BitDepthOption);
227 Option *yResolutionOption = getOption(Interface::YResolutionOption);
228 Option *xResolutionOption = getOption(Interface::XResolutionOption);
229
230 int targetPreviewDPI;
231 if (topLeftXOption != nullptr) {
232 topLeftXOption->storeCurrentData();
233 topLeftXOption->setValue(topLeftXOption->minimumValue());
234 }
235 if (topLeftYOption != nullptr) {
236 topLeftYOption->storeCurrentData();
237 topLeftYOption->setValue(topLeftYOption->minimumValue());
238 }
239 if (bottomRightXOption != nullptr) {
240 bottomRightXOption->storeCurrentData();
241 bottomRightXOption->setValue(bottomRightXOption->maximumValue());
242 }
243 if (bottomRightYOption != nullptr) {
244 bottomRightYOption->storeCurrentData();
245 bottomRightYOption->setValue(bottomRightYOption->maximumValue());
246 }
247
248 if (resolutionOption != nullptr) {
249 resolutionOption->storeCurrentData();
250 if (d->m_previewDPI < resolutionOption->minimumValue().toFloat()) {
251 targetPreviewDPI = qMax(resolutionOption->minimumValue().toFloat(), 25.0f);
252 if ((bottomRightXOption != nullptr) && (bottomRightYOption != nullptr)) {
253 if (bottomRightXOption->valueUnit() == KSaneCore::Option::UnitMilliMeter) {
254 targetPreviewDPI = 300 * 25.4 / (bottomRightXOption->value().toFloat());
255 // always round to a multiple of 25
256 int remainder = targetPreviewDPI % 25;
257 targetPreviewDPI = targetPreviewDPI + 25 - remainder;
258 }
259 }
260 } else {
261 targetPreviewDPI = d->m_previewDPI;
262 }
263 if (resolutionOption->type() == KSaneCore::Option::TypeValueList) {
264 const auto &values = resolutionOption->valueList();
265 if (values.count() <= 0) {
266 qCWarning(KSANECORE_LOG) << "Resolution option is broken and has no entries";
267 return;
268 }
269 /* if there are discrete values, try to find the one which fits best. */
270 int minIndex = 0;
271 int minDistance = abs(values.at(0).toInt() - d->m_previewDPI);
272 for (int i = 1; i < values.count(); ++i) {
273 int distance = abs(values.at(i).toInt() - d->m_previewDPI);
274 if (distance < minDistance) {
275 minIndex = i;
276 minDistance = distance;
277 }
278 }
279 targetPreviewDPI = values.at(minIndex).toInt();
280 }
281
282 resolutionOption->setValue(targetPreviewDPI);
283 if ((yResolutionOption != nullptr) && (resolutionOption == xResolutionOption)) {
284 yResolutionOption->storeCurrentData();
285 yResolutionOption->setValue(targetPreviewDPI);
286 }
287 }
288 if (bitDepthOption != nullptr) {
289 bitDepthOption->storeCurrentData();
290 if (bitDepthOption->value() == 16) {
291 bitDepthOption->setValue(8);
292 }
293 }
294 if (previewOption != nullptr) {
295 previewOption->setValue(true);
296 }
297 startScan();
298}
299
301{
302 if (!d->m_saneHandle) {
303 return;
304 }
305
306 d->m_cancelMultiPageScan = true;
307 if (d->m_scanThread->isRunning()) {
308 d->m_scanThread->cancelScan();
309 }
310 if (d->m_batchModeTimer.isActive()) {
311 d->m_batchModeTimer.stop();
313 Q_EMIT scanFinished(ScanStatus::NoError, i18n("Scanning stopped by user."));
314 }
315}
316
318{
319 if (d->m_saneHandle != nullptr) {
320 return d->m_scanThread->scanImage();
321 }
322 return nullptr;
323}
324
326{
327 if (d->m_saneHandle != nullptr) {
328 d->m_scanThread->lockScanImage();
329 }
330}
331
333{
334 if (d->m_saneHandle != nullptr) {
335 d->m_scanThread->unlockScanImage();
336 }
337}
338
340{
341 if (d->m_saneHandle == nullptr) {
342 return QJsonObject();
343 }
344 QJsonObject scannerData;
345 scannerData[QLatin1String("deviceName")] = d->m_devName;
346 scannerData[QLatin1String("deviceModel")] = d->m_model;
347 scannerData[QLatin1String("deviceVendor")] = d->m_vendor;
348
349 return scannerData;
350}
351
353{
354 if (d->m_saneHandle == nullptr) {
355 return QJsonObject();
356 }
357
358 QJsonObject optionData;
359 for (const auto &option : std::as_const(d->m_optionsList)) {
360 QJsonObject JsonOption;
361 JsonOption[QLatin1String("Title")] = option->title();
362 JsonOption[QLatin1String("Description")] = option->description();
363 JsonOption[QLatin1String("Type")] = QLatin1String(QMetaEnum::fromType<KSaneCore::Option::OptionType>().valueToKey(option->type()));
364 JsonOption[QLatin1String("State")] = QLatin1String(QMetaEnum::fromType<KSaneCore::Option::OptionState>().valueToKey(option->state()));
365 JsonOption[QLatin1String("Unit")] = QLatin1String(QMetaEnum::fromType<KSaneCore::Option::OptionUnit>().valueToKey(option->valueUnit()));
366 JsonOption[QLatin1String("Value size")] = option->valueSize();
367 JsonOption[QLatin1String("Step value")] = option->stepValue().toString();
368 JsonOption[QLatin1String("Current value")] = option->value().toString();
369 JsonOption[QLatin1String("Max value")] = option->maximumValue().toString();
370 JsonOption[QLatin1String("Min value")] = option->minimumValue().toString();
371 JsonOption[QLatin1String("Value list")] = QJsonArray::fromVariantList(option->valueList());
372 JsonOption[QLatin1String("Internal value list")] = QJsonArray::fromVariantList(option->internalValueList());
373 optionData[option->name()] = JsonOption;
374 }
375 return optionData;
376}
377
379{
380 return d->m_externalOptionsList;
381}
382
384{
385 auto it = d->m_optionsLocation.find(optionEnum);
386 if (it != d->m_optionsLocation.end()) {
387 return d->m_externalOptionsList.at(it.value());
388 }
389 return nullptr;
390}
391
393{
394 for (const auto &option : std::as_const(d->m_externalOptionsList)) {
395 if (option->name() == optionName) {
396 return option;
397 }
398 }
399 return nullptr;
400}
401
403{
405 QString tmp;
406
407 for (const auto option : std::as_const(d->m_optionsList)) {
408 tmp = option->valueAsString();
409 if (!tmp.isEmpty()) {
410 options[option->name()] = tmp;
411 }
412 }
413 return options;
414}
415
417{
418 if (!d->m_saneHandle || d->m_scanThread->isRunning()) {
419 return -1;
420 }
421
422 QMap<QString, QString> optionMapCopy = options;
423
424 int i;
425 int ret = 0;
426
427 Option *sourceOption = getOption(SourceOption);
428 Option *modeOption = getOption(ScanModeOption);
429
430 // Priorize source option
431 if (sourceOption != nullptr && optionMapCopy.contains(sourceOption->name())) {
432 if (sourceOption->setValue(optionMapCopy[sourceOption->name()])) {
433 ret++;
434 }
435 optionMapCopy.remove(sourceOption->name());
436 }
437
438 // Priorize mode option
439 if (modeOption != nullptr && optionMapCopy.contains(modeOption->name())) {
440 if (modeOption->setValue(optionMapCopy[modeOption->name()])) {
441 ret++;
442 }
443 optionMapCopy.remove(modeOption->name());
444 }
445
446 // Update remaining options
447 for (i = 0; i < d->m_optionsList.size(); i++) {
448 const auto it = optionMapCopy.find(d->m_optionsList.at(i)->name());
449 if (it != optionMapCopy.end() && d->m_optionsList.at(i)->setValue(it.value())) {
450 ret++;
451 }
452 }
453 return ret;
454}
455
456} // NameSpace KSaneCore
457
458#include "moc_interface.cpp"
static void authorization(SANE_String_Const resource, SANE_Char *username, SANE_Char *password)
static function called by sane_open to get authorization from user
This class provides the core interface for accessing the scan controls and options.
Definition interface.h:34
void startPreviewScan()
This method is used to start a preview scan.
QList< Option * > getOptionsList()
This function returns all available options when a device is opened.
int setOptionsMap(const QMap< QString, QString > &options)
This method can be used to write many parameter values at once.
void stopScan()
This method is used to cancel a scan or prevent an automatic new scan.
OptionName
This enumeration is used to obtain a specific option with getOption(KSaneOptionName).
Definition interface.h:63
OpenStatus openRestrictedDevice(const QString &deviceName, const QString &userName, const QString &password)
This method opens the specified scanner device with a specified username and password.
bool reloadDevicesList(DeviceType type=AllDevices)
Get the list of available scanning devices.
Definition interface.cpp:96
QString deviceModel() const
This method returns the model of the currently opened scanner.
Definition interface.cpp:86
Option * getOption(OptionName optionEnum)
This function returns a specific option.
QString deviceVendor() const
This method returns the vendor name of the currently opened scanner.
Definition interface.cpp:81
OpenStatus openDevice(const QString &deviceName)
This method opens the specified scanner device and adds the scan options to the options list.
~Interface() override
Standard destructor.
Definition interface.cpp:60
void lockScanImage()
Locks the mutex protecting the QImage pointer of scanImage() from concurrent access during scanning.
bool closeDevice()
This method closes the currently open scanner device.
void unlockScanImage()
Unlocks the mutex protecting the QImage pointer of scanImage() from concurrent access during scanning...
QMap< QString, QString > getOptionsMap()
This method reads the available parameters and their values and returns them in a QMap (Name,...
QJsonObject scannerDeviceToJson()
Returns a JSON object containing the device name, model and vendor.
void scanFinished(KSaneCore::Interface::ScanStatus status, const QString &strStatus)
This signal is emitted when the scanning has ended.
OpenStatus
Enum determining whether the scanner opened correctly.
Definition interface.h:52
void batchModeCountDown(int remainingSeconds)
This signal is emitted for the count down when in batch mode.
QJsonObject scannerOptionsToJson()
Returns a JSON Object with all available data for all scanner options.
QImage * scanImage() const
Gives direct access to the QImage that is used to store the image data retrieved from the scanner.
void setPreviewResolution(float dpi)
This function is used to set the preferred resolution for scanning the preview.
Definition interface.cpp:91
DeviceType
This enumeration is used to filter the devices found by SANE.
Definition interface.h:98
QString deviceName() const
This method returns the internal device name of the currently opened scanner.
Definition interface.cpp:76
void startScan()
This method is used to start a scan.
A wrapper class providing access to the internal KSaneBaseOption to access all options provided by KS...
Definition option.h:29
OptionType type() const
This function the type of the option as determined by KSANECore.
Definition option.cpp:60
OptionUnit valueUnit() const
This function returns an enum specifying whether the values of the option have a unit,...
Definition option.cpp:123
QVariant minimumValue() const
This function returns the minimum value for the option.
Definition option.cpp:69
bool setValue(const QVariant &value)
This slot allows to change the current value of the option.
Definition option.cpp:141
bool storeCurrentData()
This function temporarily stores the current value in a member variable.
Definition option.cpp:150
QVariantList valueList() const
This function returns the list of possible values if the option is of type OptionType::TypeValueList.
Definition option.cpp:96
QString name() const
This function returns the internal name of the option.
Definition option.cpp:33
QVariant value() const
This function returns the currently active value for the option.
Definition option.cpp:114
QVariant maximumValue() const
This function returns the maximum value for the option.
Definition option.cpp:78
Q_SCRIPTABLE CaptureState status()
QString i18n(const char *text, const TYPE &arg...)
const char * constData() const const
QJsonArray fromVariantList(const QVariantList &list)
QJsonValue value(QLatin1StringView key) const const
QString toString() const const
bool contains(const Key &key) const const
iterator end()
iterator find(const Key &key)
size_type remove(const Key &key)
QMetaEnum fromType()
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
bool disconnect(const QMetaObject::Connection &connection)
bool isEmpty() const const
QByteArray toLatin1() const const
void finished()
void timeout()
float toFloat(bool *ok) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 16:56:33 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.