1 /* -*- mode: c++; c-basic-offset:4 -*-
2 smartcard/readerstatus.cpp
4 This file is part of Kleopatra, the KDE keymanager
5 Copyright (c) 2009 Klarälvdalens Datakonsult AB
7 Kleopatra is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 Kleopatra is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 In addition, as a special exception, the copyright holders give
22 permission to link the code of this program with any edition of
23 the Qt library by Trolltech AS, Norway (or with modified versions
24 of Qt that use the same license as Qt), and distribute linked
25 combinations including the two. You must obey the GNU General
26 Public License in all respects for all of the code used other than
27 Qt. If you modify this file, you may extend this exception to
28 your version of the file, but you are not obligated to do so. If
29 you do not wish to do so, delete this exception statement from
33 #include <config-kleopatra.h>
35 #include "readerstatus.h"
37 #include <utils/gnupg-helper.h>
38 #include <utils/kdsignalblocker.h>
39 #include <utils/filesystemwatcher.h>
41 #include <Libkleo/Stl_Util>
43 #include <gpgme++/context.h>
44 #include <gpgme++/assuanresult.h>
45 #include <gpgme++/defaultassuantransaction.h>
46 #include <gpgme++/key.h>
47 #include <gpgme++/keylistresult.h>
49 #include <gpg-error.h>
51 #include "kleopatra_debug.h"
53 #include <QStringList>
57 #include <QWaitCondition>
61 #include <boost/algorithm/string/split.hpp>
62 #include <boost/algorithm/string/classification.hpp>
63 #include <boost/algorithm/string/case_conv.hpp>
64 #include <boost/static_assert.hpp>
65 #include <boost/range.hpp>
66 #include <boost/bind.hpp>
77 using namespace Kleo::SmartCard
;
78 using namespace GpgME
;
79 using namespace boost
;
81 static const unsigned int CHECK_INTERVAL
= 2000; // msecs
83 static ReaderStatus
*self
= 0;
88 status(ReaderStatus::NoCard
),
89 appType(ReaderStatus::UnknownApplication
),
94 CardInfo(const QString
&fn
, ReaderStatus::Status s
)
97 appType(ReaderStatus::UnknownApplication
),
104 ReaderStatus::Status status
;
105 std::string serialNumber
;
106 ReaderStatus::AppType appType
;
108 std::vector
<ReaderStatus::PinState
> pinStates
;
111 static const char *flags
[] = {
117 BOOST_STATIC_ASSERT((sizeof flags
/ sizeof * flags
== ReaderStatus::_NumScdStates
));
119 static const char *prettyFlags
[] = {
128 BOOST_STATIC_ASSERT((sizeof prettyFlags
/ sizeof * prettyFlags
== ReaderStatus::NumStates
));
130 static QByteArray
read_file(const QString
&fileName
)
132 QFile
file(fileName
);
133 if (!file
.exists()) {
134 qCDebug(KLEOPATRA_LOG
) << "read_file: file" << fileName
<< "does not exist";
137 if (!file
.open(QIODevice::ReadOnly
)) {
138 qCDebug(KLEOPATRA_LOG
) << "read_file: failed to open" << fileName
<< ':' << file
.errorString();
141 return file
.readAll().trimmed();
144 static unsigned int parseFileName(const QString
&fileName
, bool *ok
)
146 QRegExp
rx(QStringLiteral("reader_(\\d+)\\.status"));
150 if (rx
.exactMatch(QFileInfo(fileName
).fileName())) {
151 return rx
.cap(1).toUInt(ok
, 10);
158 template <typename T_Target
, typename T_Source
>
159 std::auto_ptr
<T_Target
> dynamic_pointer_cast(std::auto_ptr
<T_Source
> &in
)
161 if (T_Target
*const target
= dynamic_cast<T_Target
*>(in
.get())) {
163 return std::auto_ptr
<T_Target
>(target
);
165 return std::auto_ptr
<T_Target
>();
169 template <typename T
>
170 const T
&_trace__impl(const T
&t
, const char *msg
)
172 qCDebug(KLEOPATRA_LOG
) << msg
<< t
;
175 #define TRACE( x ) _trace__impl( x, #x )
178 static QDebug
operator<<(QDebug s
, const std::vector
< std::pair
<std::string
, std::string
> > &v
)
180 typedef std::pair
<std::string
, std::string
> pair
;
182 Q_FOREACH (const pair
&p
, v
) {
183 s
<< "status(" << QString::fromStdString(p
.first
) << ") =" << QString::fromStdString(p
.second
) << endl
;
188 static const char *app_types
[] = {
189 "_", // will hopefully never be used as an app-type :)
196 BOOST_STATIC_ASSERT((sizeof app_types
/ sizeof * app_types
== ReaderStatus::NumAppTypes
));
198 static ReaderStatus::AppType
parse_app_type(const std::string
&s
)
200 qCDebug(KLEOPATRA_LOG
) << "parse_app_type(" << s
.c_str() << ")";
201 const char **it
= std::find(begin(app_types
), end(app_types
), to_lower_copy(s
));
202 if (it
== end(app_types
)) {
203 return TRACE(ReaderStatus::UnknownApplication
);
205 return TRACE(static_cast<ReaderStatus::AppType
>(it
- begin(app_types
)));
209 static int parse_app_version(const std::string
&s
)
211 return std::atoi(s
.c_str());
214 static ReaderStatus::PinState
parse_pin_state(const std::string
&s
)
216 switch (int i
= std::atoi(s
.c_str())) {
217 case -4: return ReaderStatus::NullPin
;
218 case -3: return ReaderStatus::PinBlocked
;
219 case -2: return ReaderStatus::NoPin
;
220 case -1: return ReaderStatus::UnknownPinState
;
223 return ReaderStatus::UnknownPinState
;
225 return ReaderStatus::PinOk
;
230 static std::auto_ptr
<DefaultAssuanTransaction
> gpgagent_transact(shared_ptr
<Context
> &gpgAgent
, const char *command
, Error
&err
)
232 #ifdef DEBUG_SCREADER
233 qCDebug(KLEOPATRA_LOG
) << "gpgagent_transact(" << command
<< ")";
235 const AssuanResult res
= gpgAgent
->assuanTransact(command
);
238 err
= res
.assuanError();
241 #ifdef DEBUG_SCREADER
242 qCDebug(KLEOPATRA_LOG
) << "gpgagent_transact(" << command
<< "):" << QString::fromLocal8Bit(err
.asString());
244 if (err
.code() >= GPG_ERR_ASS_GENERAL
&& err
.code() <= GPG_ERR_ASS_UNKNOWN_INQUIRE
) {
245 qCDebug(KLEOPATRA_LOG
) << "Assuan problem, killing context";
248 return std::auto_ptr
<DefaultAssuanTransaction
>();
250 std::auto_ptr
<AssuanTransaction
> t
= gpgAgent
->takeLastAssuanTransaction();
251 return dynamic_pointer_cast
<DefaultAssuanTransaction
>(t
);
254 // returns const std::string so template deduction in boost::split works, and we don't need a temporary
255 static const std::string
scd_getattr_status(shared_ptr
<Context
> &gpgAgent
, const char *what
, Error
&err
)
257 std::string cmd
= "SCD GETATTR ";
259 const std::auto_ptr
<DefaultAssuanTransaction
> t
= gpgagent_transact(gpgAgent
, cmd
.c_str(), err
);
261 qCDebug(KLEOPATRA_LOG
) << "scd_getattr_status(" << what
<< "): got" << t
->statusLines();
262 return t
->firstStatusLine(what
);
264 qCDebug(KLEOPATRA_LOG
) << "scd_getattr_status(" << what
<< "): t == NULL";
265 return std::string();
269 static unsigned int parse_event_counter(const std::string
&str
)
272 if (sscanf(str
.c_str(), "%*u %*u %u ", &result
) == 1) {
278 static unsigned int get_event_counter(shared_ptr
<Context
> &gpgAgent
)
281 const std::auto_ptr
<DefaultAssuanTransaction
> t
= gpgagent_transact(gpgAgent
, "GETEVENTCOUNTER", err
);
283 qCDebug(KLEOPATRA_LOG
) << "get_event_counter(): got error" << err
.asString();
286 #ifdef DEBUG_SCREADER
287 qCDebug(KLEOPATRA_LOG
) << "get_event_counter(): got" << t
->statusLines();
289 return parse_event_counter(t
->firstStatusLine("EVENTCOUNTER"));
291 qCDebug(KLEOPATRA_LOG
) << "scd_getattr_status(): t == NULL";
296 // returns const std::string so template deduction in boost::split works, and we don't need a temporary
297 static const std::string
gpgagent_data(shared_ptr
<Context
> &gpgAgent
, const char *what
, Error
&err
)
299 const std::auto_ptr
<DefaultAssuanTransaction
> t
= gpgagent_transact(gpgAgent
, what
, err
);
303 return std::string();
307 static std::string
parse_keypairinfo(const std::string
&kpi
)
309 static const char hexchars
[] = "0123456789abcdefABCDEF";
310 return '&' + kpi
.substr(0, kpi
.find_first_not_of(hexchars
));
313 static bool parse_keypairinfo_and_lookup_key(Context
*ctx
, const std::string
&kpi
)
318 const std::string pattern
= parse_keypairinfo(kpi
);
319 qCDebug(KLEOPATRA_LOG
) << "parse_keypairinfo_and_lookup_key: pattern=" << pattern
.c_str();
320 if (const Error err
= ctx
->startKeyListing(pattern
.c_str())) {
321 qCDebug(KLEOPATRA_LOG
) << "parse_keypairinfo_and_lookup_key: startKeyListing failed:" << err
.asString();
325 const Key key
= ctx
->nextKey(e
);
326 ctx
->endKeyListing();
327 qCDebug(KLEOPATRA_LOG
) << "parse_keypairinfo_and_lookup_key: e=" << e
.code() << "; key.isNull()" << key
.isNull();
328 return !e
&& !key
.isNull();
331 static CardInfo
get_card_status(const QString
&fileName
, unsigned int idx
, shared_ptr
<Context
> &gpg_agent
)
333 #ifdef DEBUG_SCREADER
334 qCDebug(KLEOPATRA_LOG
) << "get_card_status(" << fileName
<< ',' << idx
<< ',' << gpg_agent
.get() << ')';
336 CardInfo
ci(fileName
, ReaderStatus::CardUsable
);
337 if (idx
!= 0 || !gpg_agent
) {
341 ci
.serialNumber
= gpgagent_data(gpg_agent
, "SCD SERIALNO", err
);
342 if (err
.code() == GPG_ERR_CARD_NOT_PRESENT
|| err
.code() == GPG_ERR_CARD_REMOVED
) {
343 ci
.status
= ReaderStatus::NoCard
;
347 ci
.status
= ReaderStatus::CardError
;
350 ci
.appType
= parse_app_type(scd_getattr_status(gpg_agent
, "APPTYPE", err
));
354 if (ci
.appType
!= ReaderStatus::NksApplication
) {
355 qCDebug(KLEOPATRA_LOG
) << "get_card_status: not a NetKey card, giving up";
358 ci
.appVersion
= parse_app_version(scd_getattr_status(gpg_agent
, "NKS-VERSION", err
));
362 if (ci
.appVersion
!= 3) {
363 qCDebug(KLEOPATRA_LOG
) << "get_card_status: not a NetKey v3 card, giving up";
367 // the following only works for NKS v3...
368 std::vector
<std::string
> chvStatus
;
369 chvStatus
.reserve(4); // expected number of fields
370 split(chvStatus
, scd_getattr_status(gpg_agent
, "CHV-STATUS", err
), is_any_of(" \t"), token_compress_on
);
374 std::transform(chvStatus
.begin(), chvStatus
.end(),
375 std::back_inserter(ci
.pinStates
),
378 if (kdtools::contains(ci
.pinStates
, ReaderStatus::NullPin
)) {
379 ci
.status
= ReaderStatus::CardHasNullPin
;
383 // check for keys to learn:
384 const std::auto_ptr
<DefaultAssuanTransaction
> result
= gpgagent_transact(gpg_agent
, "SCD LEARN --keypairinfo", err
);
385 if (err
.code() || !result
.get()) {
388 const std::vector
<std::string
> keyPairInfos
= result
->statusLine("KEYPAIRINFO");
389 if (keyPairInfos
.empty()) {
393 // check that any of the
394 const std::auto_ptr
<Context
> klc(Context::createForProtocol(CMS
)); // what about OpenPGP?
398 klc
->setKeyListMode(Ephemeral
);
400 if (kdtools::any(keyPairInfos
, !boost::bind(&parse_keypairinfo_and_lookup_key
, klc
.get(), _1
))) {
401 ci
.status
= ReaderStatus::CardCanLearnKeys
;
404 #ifdef DEBUG_SCREADER
405 qCDebug(KLEOPATRA_LOG
) << "get_card_status: ci.status " << prettyFlags
[ci
.status
];
411 static std::vector
<CardInfo
> update_cardinfo(const QString
&gnupgHomePath
, shared_ptr
<Context
> &gpgAgent
)
413 #ifdef DEBUG_SCREADER
414 qCDebug(KLEOPATRA_LOG
) << "<update_cardinfo>";
416 const QDir
gnupgHome(gnupgHomePath
);
417 if (!gnupgHome
.exists()) {
418 qCWarning(KLEOPATRA_LOG
) << "gnupg home" << gnupgHomePath
<< "does not exist!";
421 const CardInfo ci
= get_card_status(gnupgHome
.absoluteFilePath(QStringLiteral("reader_0.status")), 0, gpgAgent
);
422 #ifdef DEBUG_SCREADER
423 qCDebug(KLEOPATRA_LOG
) << "</update_cardinfo>";
425 return std::vector
<CardInfo
>(1, ci
);
428 static bool check_event_counter_changed(shared_ptr
<Context
> &gpg_agent
, unsigned int &counter
)
430 const unsigned int oldCounter
= counter
;
431 counter
= get_event_counter(gpg_agent
);
432 if (oldCounter
!= counter
) {
433 #ifdef DEBUG_SCREADER
434 qCDebug(KLEOPATRA_LOG
) << "ReaderStatusThread[2nd]: events:" << oldCounter
<< "->" << counter
;
444 QPointer
<QObject
> receiver
;
449 static const Transaction checkTransaction
= { "__check__", 0, 0, Error() };
450 static const Transaction updateTransaction
= { "__update__", 0, 0, Error() };
451 static const Transaction quitTransaction
= { "__quit__", 0, 0, Error() };
455 class ReaderStatusThread
: public QThread
459 explicit ReaderStatusThread(QObject
*parent
= Q_NULLPTR
)
461 m_gnupgHomePath(Kleo::gnupgHomeDirectory()),
462 m_transactions(1, updateTransaction
) // force initial scan
464 connect(this, &ReaderStatusThread::oneTransactionFinished
,
465 this, &ReaderStatusThread::slotOneTransactionFinished
);
468 std::vector
<CardInfo
> cardInfos() const
470 const QMutexLocker
locker(&m_mutex
);
474 ReaderStatus::Status
cardStatus(unsigned int slot
) const
476 const QMutexLocker
locker(&m_mutex
);
477 if (slot
< m_cardInfos
.size()) {
478 return m_cardInfos
[slot
].status
;
480 return ReaderStatus::NoCard
;
484 void addTransaction(const Transaction
&t
)
486 const QMutexLocker
locker(&m_mutex
);
487 m_transactions
.push_back(t
);
488 m_waitForTransactions
.wakeOne();
491 // make QThread::sleep public
492 using QThread::sleep
;
495 void anyCardHasNullPinChanged(bool);
496 void anyCardCanLearnKeysChanged(bool);
497 void cardStatusChanged(unsigned int, Kleo::SmartCard::ReaderStatus::Status
);
498 void oneTransactionFinished();
503 qCDebug(KLEOPATRA_LOG
) << "ReaderStatusThread[GUI]::ping()";
504 addTransaction(updateTransaction
);
509 const QMutexLocker
locker(&m_mutex
);
510 m_transactions
.push_front(quitTransaction
);
511 m_waitForTransactions
.wakeOne();
514 void slotReaderStatusFileChanged()
516 const QDir
gnupgHome(m_gnupgHomePath
);
517 if (!gnupgHome
.exists()) {
518 qCWarning(KLEOPATRA_LOG
) << "gnupg home" << m_gnupgHomePath
<< "does not exist!";
522 QStringList files
= gnupgHome
.entryList(QStringList(QStringLiteral("reader_*.status")), QDir::Files
, QDir::Name
);
524 kdtools::sort(files
, boost::bind(parseFileName
, _1
, dummy
) < boost::bind(parseFileName
, _2
, dummy
));
526 std::vector
<QByteArray
> contents
;
528 Q_FOREACH (const QString
&file
, files
) {
530 const unsigned int idx
= parseFileName(file
, &ok
);
532 qCDebug(KLEOPATRA_LOG
) << "filename" << file
<< ": cannot parse reader slot number";
535 assert(idx
>= contents
.size());
536 contents
.resize(idx
);
537 contents
.push_back(read_file(gnupgHome
.absoluteFilePath(file
)));
540 // canonicalise by removing empty stuff from the end
541 while (!contents
.empty() && contents
.back().isEmpty()) {
545 if (contents
!= readerStatusFileContents
) {
549 readerStatusFileContents
.swap(contents
);
553 void slotOneTransactionFinished()
555 std::list
<Transaction
> ft
;
556 KDAB_SYNCHRONIZED(m_mutex
)
557 ft
.splice(ft
.begin(), m_finishedTransactions
);
558 Q_FOREACH (const Transaction
&t
, ft
)
559 if (t
.receiver
&& t
.slot
&& *t
.slot
) {
560 QMetaObject::invokeMethod(t
.receiver
, t
.slot
, Qt::DirectConnection
, Q_ARG(GpgME::Error
, t
.error
));
565 void run() Q_DECL_OVERRIDE
{
567 shared_ptr
<Context
> gpgAgent
;
568 unsigned int eventCounter
= -1;
575 std::list
<Transaction
> item
;
576 std::vector
<CardInfo
> oldCardInfos
;
580 std::auto_ptr
<Context
> c
= Context::createForEngine(AssuanEngine
, &err
);
581 if (err
.code() == GPG_ERR_NOT_SUPPORTED
) {
587 KDAB_SYNCHRONIZED(m_mutex
) {
589 while (m_transactions
.empty()) {
590 // go to sleep waiting for more work:
591 #ifdef DEBUG_SCREADER
592 qCDebug(KLEOPATRA_LOG
) << "ReaderStatusThread[2nd]: .zZZ";
594 if (!m_waitForTransactions
.wait(&m_mutex
, CHECK_INTERVAL
)) {
595 m_transactions
.push_front(checkTransaction
);
597 #ifdef DEBUG_SCREADER
598 qCDebug(KLEOPATRA_LOG
) << "ReaderStatusThread[2nd]: .oOO";
602 // splice off the first transaction without
603 // copying, so we own it without really importing
604 // it into this thread (the QPointer isn't
606 item
.splice(item
.end(),
607 m_transactions
, m_transactions
.begin());
609 // make local copies of the interesting stuff so
610 // we can release the mutex again:
611 command
= item
.front().command
;
612 nullSlot
= !item
.front().slot
;
613 oldCardInfos
= m_cardInfos
;
616 #ifdef DEBUG_SCREADER
617 qCDebug(KLEOPATRA_LOG
) << "ReaderStatusThread[2nd]: new iteration command=" << command
<< " ; nullSlot=" << nullSlot
;
619 // now, let's see what we got:
621 if (nullSlot
&& command
== quitTransaction
.command
) {
625 if ((nullSlot
&& command
== updateTransaction
.command
) ||
626 (nullSlot
&& command
== checkTransaction
.command
)) {
628 if (nullSlot
&& command
== checkTransaction
.command
&& !check_event_counter_changed(gpgAgent
, eventCounter
)) {
629 continue; // early out
632 std::vector
<CardInfo
> newCardInfos
633 = update_cardinfo(m_gnupgHomePath
, gpgAgent
);
635 newCardInfos
.resize(std::max(newCardInfos
.size(), oldCardInfos
.size()));
636 oldCardInfos
.resize(std::max(newCardInfos
.size(), oldCardInfos
.size()));
638 KDAB_SYNCHRONIZED(m_mutex
)
639 m_cardInfos
= newCardInfos
;
641 std::vector
<CardInfo
>::const_iterator
642 nit
= newCardInfos
.begin(), nend
= newCardInfos
.end(),
643 oit
= oldCardInfos
.begin(), oend
= oldCardInfos
.end();
645 unsigned int idx
= 0;
648 bool anyError
= false;
649 while (nit
!= nend
&& oit
!= oend
) {
650 if (nit
->status
!= oit
->status
) {
651 #ifdef DEBUG_SCREADER
652 qCDebug(KLEOPATRA_LOG
) << "ReaderStatusThread[2nd]: slot" << idx
<< ":" << prettyFlags
[oit
->status
] << "->" << prettyFlags
[nit
->status
];
654 Q_EMIT
cardStatusChanged(idx
, nit
->status
);
656 if (nit
->status
== ReaderStatus::CardCanLearnKeys
) {
659 if (nit
->status
== ReaderStatus::CardHasNullPin
) {
662 if (nit
->status
== ReaderStatus::CardError
) {
670 Q_EMIT
anyCardHasNullPinChanged(anyNP
);
671 Q_EMIT
anyCardCanLearnKeysChanged(anyLC
);
679 (void)gpgagent_transact(gpgAgent
, command
.constData(), item
.front().error
);
681 KDAB_SYNCHRONIZED(m_mutex
)
682 // splice 'item' into m_finishedTransactions:
683 m_finishedTransactions
.splice(m_finishedTransactions
.end(), item
);
685 Q_EMIT
oneTransactionFinished();
689 // update event counter in case anything above changed
692 eventCounter
= get_event_counter(gpgAgent
);
696 #ifdef DEBUG_SCREADER
697 qCDebug(KLEOPATRA_LOG
) << "eventCounter:" << eventCounter
;
704 mutable QMutex m_mutex
;
705 QWaitCondition m_waitForTransactions
;
706 const QString m_gnupgHomePath
;
707 std::vector
<QByteArray
> readerStatusFileContents
;
708 // protected by m_mutex:
709 std::vector
<CardInfo
> m_cardInfos
;
710 std::list
<Transaction
> m_transactions
, m_finishedTransactions
;
715 class ReaderStatus::Private
: ReaderStatusThread
717 friend class Kleo::SmartCard::ReaderStatus
;
718 ReaderStatus
*const q
;
720 explicit Private(ReaderStatus
*qq
)
721 : ReaderStatusThread(qq
),
725 KDAB_SET_OBJECT_NAME(watcher
);
727 qRegisterMetaType
<Status
>("Kleo::SmartCard::ReaderStatus::Status");
729 watcher
.whitelistFiles(QStringList(QStringLiteral("reader_*.status")));
730 watcher
.addPath(Kleo::gnupgHomeDirectory());
731 watcher
.setDelay(100);
733 connect(this, &::ReaderStatusThread::cardStatusChanged
,
734 q
, &ReaderStatus::cardStatusChanged
);
735 connect(this, &::ReaderStatusThread::anyCardHasNullPinChanged
,
736 q
, &ReaderStatus::anyCardHasNullPinChanged
);
737 connect(this, &::ReaderStatusThread::anyCardCanLearnKeysChanged
,
738 q
, &ReaderStatus::anyCardCanLearnKeysChanged
);
740 connect(&watcher
, &FileSystemWatcher::triggered
, this, &::ReaderStatusThread::slotReaderStatusFileChanged
);
753 bool anyCardHasNullPinImpl() const
755 return kdtools::any(cardInfos(), boost::bind(&CardInfo::status
, _1
) == CardHasNullPin
);
758 bool anyCardCanLearnKeysImpl() const
760 return kdtools::any(cardInfos(), boost::bind(&CardInfo::status
, _1
) == CardCanLearnKeys
);
764 FileSystemWatcher watcher
;
767 ReaderStatus::ReaderStatus(QObject
*parent
)
768 : QObject(parent
), d(new Private(this))
773 ReaderStatus::~ReaderStatus()
779 void ReaderStatus::startMonitoring()
785 ReaderStatus
*ReaderStatus::mutableInstance()
791 const ReaderStatus
*ReaderStatus::instance()
796 ReaderStatus::Status
ReaderStatus::cardStatus(unsigned int slot
) const
798 return d
->cardStatus(slot
);
801 bool ReaderStatus::anyCardHasNullPin() const
803 return d
->anyCardHasNullPinImpl();
806 bool ReaderStatus::anyCardCanLearnKeys() const
808 return d
->anyCardCanLearnKeysImpl();
811 std::vector
<ReaderStatus::PinState
> ReaderStatus::pinStates(unsigned int slot
) const
813 const std::vector
<CardInfo
> ci
= d
->cardInfos();
814 if (slot
< ci
.size()) {
815 return ci
[slot
].pinStates
;
817 return std::vector
<PinState
>();
821 void ReaderStatus::startSimpleTransaction(const QByteArray
&command
, QObject
*receiver
, const char *slot
)
823 const Transaction t
= { command
, receiver
, slot
, Error() };
824 d
->addTransaction(t
);
827 void ReaderStatus::updateStatus()
832 #include "readerstatus.moc"