Commit c2928858 authored by Alexander Stippich's avatar Alexander Stippich
Browse files

add new KSaneCore component

parent 9a3ab2a3
......@@ -28,6 +28,8 @@ target_sources(KF5Sane PRIVATE
ksaneimagebuilder.cpp
ksanewidget_p.cpp
splittercollapser.cpp
ksanecore.cpp
ksanecore_p.cpp
ksaneauth.cpp
ksaneoption.cpp
ksaneinternaloption.cpp
......@@ -80,6 +82,7 @@ set_target_properties(KF5Sane
ecm_generate_headers(KSane_HEADERS
HEADER_NAMES
KSaneWidget
KSaneCore
KSaneOption
REQUIRED_HEADERS KSane_HEADERS
RELATIVE "../src/"
......
/* ============================================================
*
* SPDX-FileCopyrightText: 2007-2010 Kare Sars <kare dot sars at iki dot fi>
* SPDX-FileCopyrightText: 2009 Matthias Nagl <matthias at nagl dot info>
* SPDX-FileCopyrightText: 2009 Grzegorz Kurtyka <grzegorz dot kurtyka at gmail dot com>
* SPDX-FileCopyrightText: 2007-2008 Gilles Caulier <caulier dot gilles at gmail dot com>
* SPDX-FileCopyrightText: 2014 Gregor Mitsch : port to KDE5 frameworks
* SPDX-FileCopyrightText: 2021 Alexander Stippich <a.stippich@gmx.net>
*
* SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*
* ============================================================ */
//Qt includes
#include <QMutex>
// Sane includes
extern "C"
{
#include <sane/saneopts.h>
#include <sane/sane.h>
}
#include "ksanecore.h"
#include "ksanecore_p.h"
#include <ksane_debug.h>
namespace KSaneIface
{
static int s_objectCount = 0;
Q_GLOBAL_STATIC(QMutex, s_objectMutex)
KSaneCore::KSaneCore(QObject *parent)
: QObject(parent), d(std::unique_ptr<KSaneCorePrivate>(new KSaneCorePrivate(this)))
{
SANE_Int version;
SANE_Status status;
s_objectMutex->lock();
s_objectCount++;
if (s_objectCount == 1) {
// only call sane init for the first instance
status = sane_init(&version, &KSaneAuth::authorization);
if (status != SANE_STATUS_GOOD) {
qCDebug(KSANE_LOG) << "libksane: sane_init() failed("
<< sane_strstatus(status) << ")";
}
}
s_objectMutex->unlock();
// read the device list to get a list of vendor and model info
d->m_findDevThread->start();
d->m_readValuesTimer.setSingleShot(true);
connect(&d->m_readValuesTimer, &QTimer::timeout, d.get(), &KSaneCorePrivate::reloadValues);
}
KSaneCore::~KSaneCore()
{
closeDevice();
s_objectMutex->lock();
s_objectCount--;
if (s_objectCount <= 0) {
// only delete the find-devices and authorization singletons and call sane_exit
// if this is the last instance
delete d->m_findDevThread;
delete d->m_auth;
sane_exit();
}
s_objectMutex->unlock();
}
QString KSaneCore::deviceName() const
{
return d->m_devName;
}
QString KSaneCore::deviceVendor() const
{
return d->m_vendor;
}
QString KSaneCore::deviceModel() const
{
return d->m_model;
}
bool KSaneCore::reloadDevicesList()
{
/* On some SANE backends, the handle becomes invalid when
* querying for new devices. Hence, this is only allowed when
* no device is currently opened. */
if (d->m_saneHandle == nullptr) {
d->m_findDevThread->start();
return true;
}
return false;
}
KSaneCore::KSaneOpenStatus KSaneCore::openDevice(const QString &deviceName)
{
SANE_Status status;
if (d->m_saneHandle != nullptr) {
// this KSaneCore already has an open device
return KSaneOpenStatus::OpeningFailed;
}
// don't bother trying to open if the device string is empty
if (deviceName.isEmpty()) {
return KSaneOpenStatus::OpeningFailed;
}
// save the device name
d->m_devName = deviceName;
// Try to open the device
status = sane_open(deviceName.toLatin1().constData(), &d->m_saneHandle);
if (status == SANE_STATUS_ACCESS_DENIED) {
return KSaneOpenStatus::OpeningDenied;
}
if (status != SANE_STATUS_GOOD) {
qCDebug(KSANE_LOG) << "sane_open(\"" << deviceName << "\", &handle) failed! status = " << sane_strstatus(status);
d->m_devName.clear();
return KSaneOpenStatus::OpeningFailed;
}
return d->loadDeviceOptions();
}
KSaneCore::KSaneOpenStatus KSaneCore::openRestrictedDevice(const QString &deviceName, QString userName, QString password)
{
SANE_Status status;
if (d->m_saneHandle != nullptr) {
// this KSaneCore already has an open device
return KSaneOpenStatus::OpeningFailed;
}
// don't bother trying to open if the device string is empty
if (deviceName.isEmpty()) {
return KSaneOpenStatus::OpeningFailed;
}
// save the device name
d->m_devName = deviceName;
// add/update the device user-name and password for authentication
d->m_auth->setDeviceAuth(d->m_devName, userName, password);
// Try to open the device
status = sane_open(deviceName.toLatin1().constData(), &d->m_saneHandle);
if (status == SANE_STATUS_ACCESS_DENIED) {
return KSaneOpenStatus::OpeningDenied;
}
if (status != SANE_STATUS_GOOD) {
qCDebug(KSANE_LOG) << "sane_open(\"" << deviceName << "\", &handle) failed! status = " << sane_strstatus(status);
d->m_auth->clearDeviceAuth(d->m_devName);
d->m_devName.clear();
return KSaneOpenStatus::OpeningFailed;
}
return d->loadDeviceOptions();
}
bool KSaneCore::closeDevice()
{
if (!d->m_saneHandle) {
return false;
}
stopScan();
disconnect(d->m_scanThread);
if (d->m_scanThread->isRunning()) {
connect(d->m_scanThread, &QThread::finished, d->m_scanThread, &QThread::deleteLater);
}
if (d->m_scanThread->isFinished()) {
d->m_scanThread->deleteLater();
}
d->m_scanThread = nullptr;
d->m_auth->clearDeviceAuth(d->m_devName);
sane_close(d->m_saneHandle);
d->m_saneHandle = nullptr;
d->clearDeviceOptions();
return true;
}
void KSaneCore::startScan()
{
d->m_cancelMultiPageScan = false;
// execute a pending value reload
while (d->m_readValuesTimer.isActive()) {
d->m_readValuesTimer.stop();
d->reloadValues();
}
d->m_optionPollTimer.stop();
d->m_scanThread->start();
}
void KSaneCore::stopScan()
{
d->m_cancelMultiPageScan = true;
if (d->m_scanThread->isRunning()) {
d->m_scanThread->cancelScan();
}
}
QImage *KSaneCore::scanImage() const
{
return d->m_scanThread->scanImage();
}
void KSaneCore::lockScanImage()
{
d->m_scanThread->lockScanImage();
}
void KSaneCore::unlockScanImage()
{
d->m_scanThread->unlockScanImage();
}
QList<KSaneOption *> KSaneCore::getOptionsList()
{
return d->m_externalOptionsList;
}
KSaneOption *KSaneCore::getOption(KSaneCore::KSaneOptionName optionEnum)
{
auto it = d->m_optionsLocation.find(optionEnum);
if (it != d->m_optionsLocation.end()) {
return d->m_externalOptionsList.at(it.value());
}
return nullptr;
}
KSaneOption *KSaneCore::getOption(QString optionName)
{
for (const auto &option : qAsConst(d->m_externalOptionsList)) {
if (option->name() == optionName) {
return option;
}
}
return nullptr;
}
QMap <QString, QString> KSaneCore::getOptionsMap()
{
KSaneBaseOption *option;
QMap <QString, QString> options;
QString tmp;
for (int i = 0; i < d->m_optionsList.size(); i++) {
option = d->m_optionsList.at(i);
tmp = option->valueAsString();
if (!tmp.isEmpty()) {
options[option->name()] = tmp;
}
}
return options;
}
int KSaneCore::setOptionsMap(const QMap <QString, QString> &opts)
{
if (d->m_scanThread->isRunning()) {
return -1;
}
QMap <QString, QString> optionMapCopy = opts;
QString tmp;
int i;
int ret = 0;
KSaneOption *sourceOption = getOption(SourceOption);
KSaneOption *modeOption = getOption(ScanModeOption);
// Priorize source option
if (sourceOption != nullptr && optionMapCopy.contains(sourceOption->name())) {
if (sourceOption->setValue(optionMapCopy[sourceOption->name()]) ) {
ret++;
}
optionMapCopy.remove(sourceOption->name());
}
// Priorize mode option
if (modeOption != nullptr && optionMapCopy.contains(modeOption->name())) {
if (modeOption->setValue(optionMapCopy[modeOption->name()])) {
ret++;
}
optionMapCopy.remove(modeOption->name());
}
// Update remaining options
for (i = 0; i < d->m_optionsList.size(); i++) {
const auto it = optionMapCopy.find(d->m_optionsList.at(i)->name());
if (it != optionMapCopy.end() && d->m_optionsList.at(i)->setValue(it.value())) {
ret++;
}
}
return ret;
}
} // NameSpace KSaneIface
/* ============================================================
*
* SPDX-FileCopyrightText: 2007-2010 Kare Sars <kare dot sars at iki dot fi>
* SPDX-FileCopyrightText: 2007 Gilles Caulier <caulier dot gilles at gmail dot com>
* SPDX-FileCopyrightText: 2014 Gregor Mitsch : port to KDE5 frameworks
* SPDX-FileCopyrightText: 2021 Alexander Stippich <a.stippich@gmx.net>
*
* SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*
* ============================================================ */
#ifndef KSANE_CORE_H
#define KSANE_CORE_H
#include "ksane_export.h"
#include <memory>
#include <QObject>
#include <QList>
#include <QImage>
/** This namespace collects all methods and classes in LibKSane. */
namespace KSaneIface
{
class KSaneCorePrivate;
class KSaneOption;
/**
* This class provides the core interface for accessing the scan controls and options.
*/
class KSANE_EXPORT KSaneCore : public QObject
{
Q_OBJECT
friend class KSaneCorePrivate;
public:
/** @note There might come more enumerations in the future. */
enum KSaneScanStatus {
NoError, // The scanning was finished successfully
ErrorGeneral, // The error string should contain an error message.
Information // There is some information to the user.
};
/** Enum determining whether the scanner opened correctly. */
enum KSaneOpenStatus {
OpeningSucceeded, // scanner opened successfully
OpeningDenied, // access was denied,
OpeningFailed, // opening the scanner failed for unknown reasons
};
/**
* This enumeration is used to obtain a specific option with getOption(KSaneOptionName).
* Depending on the backend, not all options are available, nor this list is complete.
* For the remaining options, getOptionsList() must be used.
*/
enum KSaneOptionName {
SourceOption,
ScanModeOption,
BitDepthOption,
ResolutionOption,
TopLeftXOption,
TopLeftYOption,
BottomRightXOption,
BottomRightYOption,
FilmTypeOption,
NegativeOption,
InvertColorOption,
PageSizeOption,
ThresholdOption,
XResolutionOption,
YResolutionOption,
PreviewOption,
WaitForButtonOption,
BrightnessOption,
ContrastOption,
GammaOption,
GammaRedOption,
GammaGreenOption,
GammaBlueOption,
BlackLevelOption,
WhiteLevelOption,
};
struct DeviceInfo {
QString name; /* unique device name */
QString vendor; /* device vendor string */
QString model; /* device model name */
QString type; /* device type (e.g., "flatbed scanner") */
};
/**
* This constructor initializes the private class variables, but the widget is left empty.
* The options and the preview are added with the call to openDevice().
*/
KSaneCore(QObject *parent = nullptr);
/** Standard destructor */
~KSaneCore();
/**
* Get the list of available scanning devices. Connect to availableDevices()
* which is fired once these devices are known.
* @return whether the devices list are reloaded or not.
*/
bool reloadDevicesList();
/**
* This method opens the specified scanner device and adds the scan options to the
* options list.
* @param deviceName is the libsane device name for the scanner to open.
* @return the status of the opening action.
*/
KSaneOpenStatus openDevice(const QString &deviceName);
/**
* This method opens the specified scanner device with a specified username and password.
* Adds the scan options to the options list.
* @param deviceName is the libsane device name for the scanner to open.
* @param userName the username required to open for the scanner.
* @param password the password required to open for the scanner.
* @return the status of the opening action.
*/
KSaneOpenStatus openRestrictedDevice(const QString &deviceName, QString userName, QString password);
/**
* This method closes the currently open scanner device.
* @return 'true' if all goes well and 'false' if no device is open.
*/
bool closeDevice();
/**
* This method returns the internal device name of the currently opened scanner.
*/
QString deviceName() const;
/**
* This method returns the vendor name of the currently opened scanner.
*/
QString deviceVendor() const;
/**
* This method returns the model of the currently opened scanner.
*/
QString deviceModel() const;
/**
* This function returns all available options when a device is opened.
* @return list containing pointers to all KSaneOptions provided by the backend.
* Becomes invalid when closing a device.
* The pointers must not be deleted by the client.
*/
QList<KSaneOption *> getOptionsList();
/**
* This function returns a specific option.
* @param optionEnum the enum specifying the option.
* @return pointer to the KSaneOption. Returns a nullptr in case the options
* is not available for the currently opened device.
*/
KSaneOption *getOption(KSaneOptionName optionEnum);
/**
* This function returns a specific option.
* @param optionName the internal name of the option defined by SANE.
* @return pointer to the KSaneOption. Returns a nullptr in case the options
* is not available for the currently opened device.
*/
KSaneOption *getOption(QString optionName);
/**
* This method reads the available parameters and their values and
* returns them in a QMap (Name, value)
* @param opts is a QMap with the parameter names and values.
*/
QMap <QString, QString> getOptionsMap();
/**
* This method can be used to write many parameter values at once.
* @param opts is a QMap with the parameter names and values.
* @return This function returns the number of successful writes
* or -1 if scanning is in progress.
*/
int setOptionsMap(const QMap <QString, QString> &opts);
/**
* Gives direct access to the QImage that is used to store the image
* data retrieved from the scanner.
* Useful to display the an in-progress image while scanning.
* When accessing the direct image pointer during a scan, the image
* must be locked before accessing the image and unlocked afterwards
* using the lockScanImage() and unlockScanImage() functions.
* @return pointer for direct access of the QImage data.
*/
QImage *scanImage() const;
/**
* Locks the mutex protecting the QImage pointer of scanImage() from
* concurrent access during scanning.
*/
void lockScanImage();
/**
* Unlocks the mutex protecting the QImage pointer of scanImage() from
* concurrent access during scanning. The scanning progress will blocked
* when lockScanImage() is called until unlockScanImage() is called.
*/
void unlockScanImage();
public Q_SLOTS:
/**
* This method can be used to cancel a scan or prevent an automatic new scan.
*/
void stopScan();
/**
* This method can be used to start a scan (if no GUI is needed).
* @note libksane may return one or more images as a result of one invocation of this slot.
* If no more images are wanted scanCancel should be called in the slot handling the
* imageReady signal.
*/
void startScan();
Q_SIGNALS:
/**
* This signal is emitted when a final scan is ready.
* @param scannedImage is the QImage containing the scanned image data.
*/
void scannedImageReady(const QImage &scannedImage);
/**
* This signal is emitted when the scanning has ended.
* @param status contains a ScanStatus status code.
* @param strStatus If an error has occurred this string will contain an error message.
* otherwise the string is empty.
*/
void scanFinished(KSaneScanStatus status, const QString &strStatus);
/**
* This signal is emitted when the user is to be notified about something.
* @note If no slot is connected to this signal the message will be displayed in a KMessageBox.
* @param type contains a ScanStatus code to identify the type of message (error/info/...).
* @param strStatus If an error has occurred this string will contain an error message.
* otherwise the string is empty.
*/
void userMessage(KSaneScanStatus status, const QString &strStatus);
/**
* This signal is emitted for progress information during a scan.
* The GUI already has a progress bar, but if the GUI is hidden,
* this can be used to display a progress bar.
* @param percent is the percentage of the scan progress (0-100).
*/
void scanProgress(int percent);
/**
* This signal is emitted every time the device list is updated or
* after initGetDeviceList() is called.
* @param deviceList is a QList of KSaneCore::DeviceInfo that contain the
* device name, model, vendor and type of the attached scanners.
* @note The list is only a snapshot of the current available devices. Devices
* might be added or removed/opened after the signal is emitted.
*/
void availableDevices(const QList<KSaneCore::DeviceInfo> &deviceList);
/**
* This signal is emitted when a hardware button is pressed.
* @param optionName is the untranslated technical name of the sane-option.
* @param optionLabel is the translated user visible label of the sane-option.
* @param pressed indicates if the value is true or false.
* @note The SANE standard does not specify hardware buttons and their behaviors,
* so this signal is emitted for sane-options that behave like hardware buttons.
* That is the sane-options are read-only and type boolean. The naming of hardware
* buttons also differ from backend to backend.
*/
void buttonPressed(const QString &optionName, const QString &optionLabel, bool pressed);
/**
* This signal is emitted when the device info of the already opened scanner device
* is updated and vendor() and model() return the corresponding names.
* @param deviceName is the technical device name of the currently opened scanner.
* @param deivceVendor is the vendor of the currently opened scanner.
* @param deviceModel is the model name of the currently opened scanner.
*/
void openedDeviceInfoUpdated(const QString &deviceName, const QString &deivceVendor, const QString &deviceModel);
private:
std::unique_ptr<KSaneCorePrivate> d;
};
} // NameSpace KSaneIface
#endif // KSANE_CORE_H
/* ============================================================
*
* SPDX-FileCopyrightText: 2007-2008 Kare Sars <kare dot sars at iki dot fi>
* SPDX-FileCopyrightText: 2007-2008 Gilles Caulier <caulier dot gilles at gmail dot com>
* SPDX-FileCopyrightText: 2014 Gregor Mitsch : port to KDE5 frameworks
* SPDX-FileCopyrightText: 2021 Alexander Stippich <a.stippich@gmx.net>