7#include "kwalletfreedesktopservice.h"
10#include "kwalletd_debug.h"
11#include "kwalletfreedesktopcollection.h"
12#include "kwalletfreedesktopitem.h"
13#include "kwalletfreedesktopprompt.h"
14#include "kwalletfreedesktopserviceadaptor.h"
15#include "kwalletfreedesktopsession.h"
16#include <KConfigGroup>
24[[maybe_unused]]
int DBUS_SECRET_SERVICE_META_TYPE_REGISTER = []() {
25 qDBusRegisterMetaType<StrStrMap>();
26 qDBusRegisterMetaType<QMap<QString, QString>>();
27 qDBusRegisterMetaType<FreedesktopSecret>();
28 qDBusRegisterMetaType<FreedesktopSecretMap>();
29 qDBusRegisterMetaType<PropertiesMap>();
30 qDBusRegisterMetaType<QCA::SecureArray>();
39 const auto utf8Str = str.
toUtf8();
40 static constexpr char hex[] =
"0123456789abcdef";
41 static_assert(
sizeof(
hex) == 17);
44 mangled.
reserve(utf8Str.size());
46 for (
const auto &c : utf8Str) {
47 if ((c <
'A' || c >
'Z') && (c <
'a' || c >
'z') && (c <
'0' || c >
'9') && c !=
'_') {
48 const auto cp =
static_cast<quint8
>(c);
61#define LABEL_NUMBER_PREFIX "__"
62#define LABEL_NUMBER_POSTFIX "_"
63#define LABEL_NUMBER_REGEX "(^.*)" LABEL_NUMBER_PREFIX "(\\d+)" LABEL_NUMBER_POSTFIX "$"
65EntryLocation EntryLocation::fromUniqueLabel(
const FdoUniqueLabel &uniqLabel)
71 if (slashPos == -1 || slashPos == uniqLabel.label.
size() - 1) {
72 dir = QStringLiteral(FDO_SECRETS_DEFAULT_DIR);
74 dir = uniqLabel.label.
left(slashPos);
78 return EntryLocation{
dir, FdoUniqueLabel::makeName(name, uniqLabel.copyId)};
81FdoUniqueLabel EntryLocation::toUniqueLabel()
const
83 return FdoUniqueLabel::fromEntryLocation(*
this);
86FdoUniqueLabel FdoUniqueLabel::fromEntryLocation(
const EntryLocation &entryLocation)
88 const auto uniqLabel = FdoUniqueLabel::fromName(entryLocation.key);
90 if (entryLocation.folder == QStringLiteral(FDO_SECRETS_DEFAULT_DIR)) {
93 return {entryLocation.folder +
QChar::fromLatin1(
'/') + uniqLabel.label, uniqLabel.copyId};
97FdoUniqueLabel FdoUniqueLabel::fromName(
const QString &name)
101 const auto match = regexp.match(name);
102 if (
match.hasMatch()) {
105 const int n = strNum.
toInt(&ok);
107 return FdoUniqueLabel{
match.captured(1), n};
110 return FdoUniqueLabel{
name};
118 return label + QStringLiteral(LABEL_NUMBER_PREFIX) +
QString::number(n) + QStringLiteral(LABEL_NUMBER_POSTFIX);
122QString FdoUniqueLabel::toName()
const
124 return makeName(label, copyId);
127EntryLocation FdoUniqueLabel::toEntryLocation()
const
129 return EntryLocation::fromUniqueLabel(*
this);
132QString KWalletFreedesktopService::wrapToCollectionPath(
const QString &itemPath)
138KWalletFreedesktopService::KWalletFreedesktopService(KWalletD *parent)
141 , m_kwalletrc(QStringLiteral(
"kwalletrc"))
143 (void)
new KWalletFreedesktopServiceAdaptor(
this);
150 if (!parent || !walletGroup.readEntry(
"Enabled",
true)) {
154 connect(m_parent,
static_cast<void (KWalletD::*)(
const QString &)
>(&KWalletD::walletClosed),
this, &KWalletFreedesktopService::lockCollection);
155 connect(m_parent, &KWalletD::entryUpdated,
this, &KWalletFreedesktopService::entryUpdated);
156 connect(m_parent, &KWalletD::entryDeleted,
this, &KWalletFreedesktopService::entryDeleted);
157 connect(m_parent, &KWalletD::entryRenamed,
this, &KWalletFreedesktopService::entryRenamed);
158 connect(m_parent, &KWalletD::walletDeleted,
this, &KWalletFreedesktopService::walletDeleted);
159 connect(m_parent, &KWalletD::walletCreated,
this, &KWalletFreedesktopService::walletCreated);
161 const auto walletNames = backend()->wallets();
164 for (
const QString &walletName : walletNames) {
165 const auto objectPath = makeUniqueObjectPath(walletName);
166 auto collection = std::make_unique<KWalletFreedesktopCollection>(
this, -1, walletName, objectPath);
168 m_collections.emplace(objectPath.path(), std::move(collection));
172KWalletFreedesktopService::~KWalletFreedesktopService() =
default;
177 result.
reserve(m_collections.size());
179 for (
const auto &collectionPair : m_collections) {
188 prompt.
setPath(QStringLiteral(
"/"));
190 const auto labelIter =
properties.find(QStringLiteral(
"org.freedesktop.Secret.Collection.Label"));
192 sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral(
"Collection.Label property is missing"));
195 if (!labelIter->canConvert<
QString>()) {
196 sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral(
"Type of Collection.Label property is invalid"));
200 prompt = nextPromptPath();
201 auto fdoPromptPtr = std::make_unique<KWalletFreedesktopPrompt>(
this, prompt, PromptType::Create, message().service());
202 auto &fdoPrompt = *m_prompts.emplace(prompt.
path(), std::move(fdoPromptPtr)).first->second;
204 fdoPrompt.appendProperties(labelIter->toString(),
QDBusObjectPath(
"/"), alias);
205 fdoPrompt.subscribeForWalletAsyncOpened();
215 const auto item = getItemByObjectPath(itemPath);
218 result.
insert(itemPath, item->getSecret(connection(), message(), session));
220 sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral(
"Can't find item at path ") + itemPath.path());
235 const QString collectionPath = wrapToCollectionPath(resolveIfAlias(
object.
path()));
237 const auto foundCollection = m_collections.find(collectionPath);
238 if (foundCollection != m_collections.end()) {
239 const int walletHandle = foundCollection->second->walletHandle();
240 const int rc = m_parent->close(walletHandle,
true, FDO_APPID, message());
245 sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral(
"Can't lock object at path ") + collectionPath);
248 sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral(
"Collection at path ") + collectionPath + QStringLiteral(
" does not exist"));
257 std::unique_ptr<KWalletFreedesktopSessionAlgorithm> sessionAlgorithm;
258 if (algorithm == QStringLiteral(
"plain")) {
259 sessionAlgorithm = createSessionAlgorithmPlain();
260 }
else if (algorithm == QStringLiteral(
"dh-ietf1024-sha256-aes128-cbc-pkcs7")) {
262 sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral(
"Second input argument must be a byte array."));
268 sendErrorReply(QDBusError::ErrorType::NotSupported,
269 QStringLiteral(
"Algorithm ") + algorithm
270 + QStringLiteral(
" is not supported. (only plain and dh-ietf1024-sha256-aes128-cbc-pkcs7 are supported)"));
274 if (!sessionAlgorithm) {
279 const QString sessionPath = createSession(std::move(sessionAlgorithm));
289 if (name == QStringLiteral(
"default")) {
291 walletName = defaultWalletName(cfg);
294 KConfigGroup cfg(&m_kwalletrc,
"org.freedesktop.secrets.aliases");
295 walletName = cfg.readEntry(name,
QString());
299 const auto *collection = getCollectionByWalletName(walletName);
301 return collection->fdoObjectPath();
312 for (
const auto &collectionPair : m_collections) {
313 auto &collection = *collectionPair.second;
315 if (collection.locked()) {
316 locked += collection.SearchItems(attributes);
318 unlocked += collection.SearchItems(attributes);
327 const auto foundCollection = m_collections.find(collectionPath.
path());
328 if (foundCollection == m_collections.end()) {
332 auto *collection = foundCollection->second.get();
333 createCollectionAlias(name, collection);
338 if (alias.
startsWith(QStringLiteral(FDO_ALIAS_PATH))) {
339 const auto path = ReadAlias(alias.
remove(0, QStringLiteral(FDO_ALIAS_PATH).size())).
path();
340 if (path != QStringLiteral(
"/")) {
343 sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral(
"Alias ") + alias + QStringLiteral(
" does not exist"));
348 if (!alias.
startsWith(QStringLiteral(FDO_SECRETS_COLLECTION_PATH))) {
349 sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral(
"Collection object path is invalid"));
356struct UnlockedObject {
370 const QString strPath =
object.path();
371 const QString collectionPath = wrapToCollectionPath(resolveIfAlias(strPath));
373 const auto foundCollection = m_collections.find(collectionPath);
374 if (foundCollection != m_collections.end()) {
375 if (foundCollection->second->locked()) {
381 sendErrorReply(QDBusError::ErrorType::InvalidObjectPath, QStringLiteral(
"Object ") + strPath + QStringLiteral(
" does not exist"));
386 if (!needUnlock.
empty()) {
387 const auto promptPath = nextPromptPath();
388 auto fdoPromptPtr = std::make_unique<KWalletFreedesktopPrompt>(
this, promptPath, PromptType::Open, message().service());
389 auto &fdoPrompt = *m_prompts.emplace(promptPath.path(), std::move(fdoPromptPtr)).first->second;
393 for (
const auto &[walletName, objectPath] : std::as_const(needUnlock)) {
394 fdoPrompt.appendProperties(walletName, objectPath);
397 fdoPrompt.subscribeForWalletAsyncOpened();
402std::unique_ptr<KWalletFreedesktopSessionAlgorithm> KWalletFreedesktopService::createSessionAlgorithmPlain()
const
404 return std::make_unique<KWalletFreedesktopSessionAlgorithmPlain>();
407std::unique_ptr<KWalletFreedesktopSessionAlgorithm> KWalletFreedesktopService::createSessionAlgorithmDhAes(
const QByteArray &clientKey)
const
409 if (clientKey.
size() < FDO_DH_PUBLIC_KEY_SIZE) {
410 sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral(
"Client public key size is invalid"));
416 if (dlGroup.isNull()) {
417 sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral(
"createDLGroup failed: maybe libqca-ossl is missing"));
424 const auto commonSecret = privateKey.deriveKey(clientPublicKey);
425 const auto symmetricKey =
QCA::HKDF().
makeKey(commonSecret, {}, {}, FDO_SECRETS_CIPHER_KEY_SIZE);
427 return std::make_unique<KWalletFreedesktopSessionAlgorithmDhAes>(publicKey, symmetricKey);
430QString KWalletFreedesktopService::createSession(std::unique_ptr<KWalletFreedesktopSessionAlgorithm> algorithm)
432 const QString sessionPath = QStringLiteral(FDO_SECRETS_SESSION_PATH) +
QString::number(++m_session_counter);
433 auto session = std::make_unique<KWalletFreedesktopSession>(
this, std::move(algorithm), sessionPath, connection(), message());
434 m_sessions[sessionPath] = std::move(session);
440 auto walletName = cfg.
readEntry(
"Default Wallet",
"kdewallet");
442 walletName = QStringLiteral(
"kdewallet");
449 auto *collection = getCollectionByWalletName(walletName);
453 collection->onWalletChangeState(handle);
454 onCollectionChanged(collection->fdoObjectPath());
455 objectPath = collection->fdoObjectPath().path();
457 const auto path = makeUniqueObjectPath(walletName);
458 objectPath =
path.path();
459 auto newCollection = std::make_unique<KWalletFreedesktopCollection>(
this, handle, walletName, path);
460 m_collections[objectPath] = std::move(newCollection);
461 onCollectionCreated(path);
468void KWalletFreedesktopService::lockCollection(
const QString &name)
470 auto *collection = getCollectionByWalletName(name);
472 collection->onWalletChangeState(-1);
473 onCollectionChanged(collection->fdoObjectPath());
478void KWalletFreedesktopService::entryUpdated(
const QString &walletName,
const QString &folder,
const QString &entryName)
480 auto *collection = getCollectionByWalletName(walletName);
485 const EntryLocation entryLocation{folder, entryName};
486 const auto *item = collection->findItemByEntryLocation(entryLocation);
488 collection->onItemChanged(item->fdoObjectPath());
490 auto objectPath = collection->nextItemPath();
491 collection->pushNewItem(entryLocation.toUniqueLabel(), objectPath);
492 collection->onItemCreated(objectPath);
497void KWalletFreedesktopService::entryDeleted(
const QString &walletName,
const QString &folder,
const QString &entryName)
499 auto *collection = getCollectionByWalletName(walletName);
504 const auto *item = collection->findItemByEntryLocation({folder, entryName});
506 collection->onItemDeleted(item->fdoObjectPath());
511void KWalletFreedesktopService::entryRenamed(
const QString &walletName,
const QString &folder,
const QString &oldName,
const QString &newName)
513 auto *collection = getCollectionByWalletName(walletName);
518 const EntryLocation oldLocation{folder, oldName};
519 const EntryLocation newLocation{folder, newName};
521 auto *item = collection->findItemByEntryLocation(oldLocation);
524 if (!collection->findItemByEntryLocation(newLocation)) {
525 qCWarning(KWALLETD_LOG) <<
"Cannot rename secret service label:" << FdoUniqueLabel::fromEntryLocation(oldLocation).label;
531 collection->itemAttributes().renameLabel(oldLocation, newLocation);
532 item->uniqueLabel(newLocation.toUniqueLabel());
533 collection->onItemChanged(item->fdoObjectPath());
538void KWalletFreedesktopService::walletDeleted(
const QString &walletName)
540 auto *collection = getCollectionByWalletName(walletName);
542 collection->Delete();
547void KWalletFreedesktopService::walletCreated(
const QString &walletName)
549 const auto objectPath = makeUniqueObjectPath(walletName);
550 auto collection = std::make_unique<KWalletFreedesktopCollection>(
this, -1, walletName, objectPath);
551 m_collections.emplace(objectPath.path(), std::move(collection));
552 onCollectionCreated(objectPath);
555bool KWalletFreedesktopService::desecret(
const QDBusMessage &message, FreedesktopSecret &secret)
557 const auto foundSession = m_sessions.find(secret.session.
path());
559 if (foundSession != m_sessions.end()) {
560 const KWalletFreedesktopSession &session = *foundSession->second;
561 return session.decrypt(message, secret);
567bool KWalletFreedesktopService::ensecret(
const QDBusMessage &message, FreedesktopSecret &secret)
569 const auto foundSession = m_sessions.find(secret.session.
path());
571 if (foundSession != m_sessions.end()) {
572 const KWalletFreedesktopSession &session = *foundSession->second;
573 return session.encrypt(message, secret);
581 static uint64_t
id = 0;
588 arg << secret.session;
589 arg << secret.parameters;
591 arg << secret.mimeType;
599 arg >> secret.session;
600 arg >> secret.parameters;
602 arg >> secret.mimeType;
611 explicit_zero_mem(bytes.data(), bytes.size());
620 explicit_zero_mem(bytes.
data(), bytes.
size());
628 explicit_zero_mem(bytes.data(), bytes.size());
637 explicit_zero_mem(byteArray.
data(), byteArray.
size());
641KWalletD *KWalletFreedesktopService::backend()
const
651KWalletFreedesktopItem *KWalletFreedesktopService::getItemByObjectPath(
const QDBusObjectPath &path)
const
653 const auto str =
path.path();
654 if (!str.
startsWith(QStringLiteral(FDO_SECRETS_COLLECTION_PATH))) {
658 const QString collectionPath = wrapToCollectionPath(str);
659 const auto collectionPos = m_collections.find(collectionPath);
660 if (collectionPos == m_collections.end()) {
664 const auto &collection = collectionPos->second;
665 return collection->getItemByObjectPath(str);
668KWalletFreedesktopPrompt *KWalletFreedesktopService::getPromptByObjectPath(
const QDBusObjectPath &path)
const
670 const auto foundPrompt = m_prompts.find(
path.path());
671 if (foundPrompt != m_prompts.end()) {
672 return foundPrompt->second.get();
678FdoUniqueLabel KWalletFreedesktopService::makeUniqueCollectionLabel(
const QString &label)
681 auto walletName =
label;
684 while (wallets.
contains(walletName)) {
685 walletName = FdoUniqueLabel::makeName(label, ++n);
691QString KWalletFreedesktopService::makeUniqueWalletName(
const QString &labelPrefix)
693 return makeUniqueCollectionLabel(labelPrefix).toName();
698 auto mangled = mangleInvalidObjectPathChars(walletName);
699 mangled.
insert(0, QStringLiteral(FDO_SECRETS_COLLECTION_PATH));
703 while (m_collections.count(result)) {
713 KConfigGroup cfg(&m_kwalletrc,
"org.freedesktop.secrets.aliases");
717 for (
auto i =
map.begin(); i !=
map.end(); ++i) {
718 if (i.value() == walletName) {
724 if (defaultWalletName(cfgWallet) == walletName) {
725 aliases.
push_back(QStringLiteral(
"default"));
731void KWalletFreedesktopService::updateCollectionAlias(
const QString &alias,
const QString &walletName)
733 QString sectName = QStringLiteral(
"org.freedesktop.secrets.aliases");
736 if (alias == QStringLiteral(
"default")) {
737 sectName = QStringLiteral(
"Wallet");
738 sectKey = QStringLiteral(
"Default Wallet");
746void KWalletFreedesktopService::createCollectionAlias(
const QString &alias,
const QString &walletName)
748 QString sectName = QStringLiteral(
"org.freedesktop.secrets.aliases");
751 if (alias == QStringLiteral(
"default")) {
752 sectName = QStringLiteral(
"Wallet");
753 sectKey = QStringLiteral(
"Default Wallet");
760 if (!prevWalletName.
isEmpty()) {
761 const auto *prevCollection = getCollectionByWalletName(prevWalletName);
762 if (prevCollection) {
770 auto *collection = getCollectionByWalletName(walletName);
776void KWalletFreedesktopService::createCollectionAlias(
const QString &alias, KWalletFreedesktopCollection *collection)
778 QString sectName = QStringLiteral(
"org.freedesktop.secrets.aliases");
781 if (alias == QStringLiteral(
"default")) {
782 sectName = QStringLiteral(
"Wallet");
783 sectKey = QStringLiteral(
"Default Wallet");
790 if (!prevWalletName.
isEmpty()) {
791 const auto *prevCollection = getCollectionByWalletName(prevWalletName);
792 if (prevCollection) {
797 cfg.
writeEntry(sectKey, collection->walletName());
802void KWalletFreedesktopService::removeAlias(
const QString &alias)
804 if (alias == QStringLiteral(
"default")) {
808 KConfigGroup cfg(&m_kwalletrc,
"org.freedesktop.secrets.aliases");
814KWalletFreedesktopCollection *KWalletFreedesktopService::getCollectionByWalletName(
const QString &walletName)
const
816 for (
const auto &collectionKeyValue : m_collections) {
817 const auto collection = collectionKeyValue.second.get();
818 if (collection->walletName() == walletName) {
826void KWalletFreedesktopService::deletePrompt(
const QString &objectPath)
828 const auto foundPrompt = m_prompts.find(objectPath);
829 if (foundPrompt == m_prompts.end()) {
836 foundPrompt->second->deleteLater();
837 foundPrompt->second.release();
838 m_prompts.erase(foundPrompt);
841void KWalletFreedesktopService::deleteSession(
const QString &objectPath)
843 const auto foundSession = m_sessions.find(objectPath);
844 if (foundSession == m_sessions.end()) {
851 foundSession->second->deleteLater();
852 foundSession->second.release();
853 m_sessions.erase(foundSession);
856void KWalletFreedesktopService::onCollectionCreated(
const QDBusObjectPath &path)
858 Q_EMIT CollectionCreated(path);
862 onPropertiesChanged(props);
865void KWalletFreedesktopService::onCollectionChanged(
const QDBusObjectPath &path)
867 Q_EMIT CollectionChanged(path);
870void KWalletFreedesktopService::onCollectionDeleted(
const QDBusObjectPath &path)
872 const auto collectionMapPos = m_collections.find(
path.path());
873 if (collectionMapPos == m_collections.end()) {
876 auto &collectionPair = *collectionMapPos;
877 collectionPair.second->itemAttributes().deleteFile();
882 collectionPair.second->deleteLater();
883 collectionPair.second.release();
884 m_collections.erase(collectionMapPos);
886 Q_EMIT CollectionDeleted(path);
890 onPropertiesChanged(props);
893void KWalletFreedesktopService::onPropertiesChanged(
const QVariantMap &properties)
895 auto msg =
QDBusMessage::createSignal(fdoObjectPath().
path(), QStringLiteral(
"org.freedesktop.DBus.Properties"), QStringLiteral(
"PropertiesChanged"));
896 auto args = QVariantList();
898 msg.setArguments(args);
904 return stream << value.
path();
920 while (!arg.
atEnd()) {
933 value.map.insert(key, val);
948void explicit_zero_mem(
void *data,
size_t size)
950#if defined(KWALLETD_HAVE_EXPLICIT_BZERO)
951 explicit_bzero(data, size);
952#elif defined(KWALLETD_HAVE_RTLSECUREZEROMEMORY)
953 RtlSecureZeroMemory(data, size);
955 auto p =
reinterpret_cast<volatile char *
>(data);
956 for (
size_t i = 0; i < size; ++i) {
962#include "moc_kwalletfreedesktopservice.cpp"
void deleteEntry(const char *key, WriteConfigFlags pFlags=Normal)
void writeEntry(const char *key, const char *value, WriteConfigFlags pFlags=Normal)
QString readEntry(const char *key, const char *aDefault=nullptr) const
QMap< QString, QString > entryMap() const
void reparseConfiguration()
SymmetricKey makeKey(const SecureArray &secret, const InitializationVector &salt, const InitializationVector &info, unsigned int keyLength)
PrivateKey createDH(const DLGroup &domain, const QString &provider=QString())
DLGroup createDLGroup(QCA::DLGroupSet set, const QString &provider=QString())
QByteArray toByteArray() const
KCALENDARCORE_EXPORT QDataStream & operator>>(QDataStream &in, const KCalendarCore::Alarm::Ptr &)
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
QString path(const QString &relativePath)
KIOCORE_EXPORT QString dir(const QString &fileClass)
QString name(StandardAction id)
QString label(StandardShortcut id)
QDebug operator<<(QDebug dbg, const PerceptualColor::MultiSpinBoxSection &value)
qsizetype size() const const
void beginMap(QMetaType keyMetaType, QMetaType valueMetaType)
bool registerObject(const QString &path, QObject *object, RegisterOptions options)
bool registerService(const QString &serviceName)
bool send(const QDBusMessage &message) const const
QDBusConnection sessionBus()
void unregisterObject(const QString &path, UnregisterMode mode)
QDBusMessage createSignal(const QString &path, const QString &interface, const QString &name)
QString path() const const
void setPath(const QString &path)
QVariant variant() const const
void push_back(parameter_type value)
void reserve(qsizetype size)
iterator insert(const Key &key, const T &value)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
QString & insert(qsizetype position, QChar ch)
bool isEmpty() const const
QString left(qsizetype n) const const
QString number(double n, char format, int precision)
QString & remove(QChar ch, Qt::CaseSensitivity cs)
void reserve(qsizetype size)
QString right(qsizetype n) const const
QString section(QChar sep, qsizetype start, qsizetype end, SectionFlags flags) const const
qsizetype size() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
int toInt(bool *ok, int base) const const
QByteArray toUtf8() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QTextStream & hex(QTextStream &stream)
QFuture< void > map(Iterator begin, Iterator end, MapFunctor &&function)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
bool canConvert() const const
QVariant fromValue(T &&value)
QByteArray toByteArray() const const