File indexing completed on 2023-12-10 04:56:57
0001 /* 0002 * Copyright (C) 2012 David Edmundson <kde@davidedmundson.co.uk> 0003 * 0004 * This library is free software; you can redistribute it and/or 0005 * modify it under the terms of the GNU Lesser General Public 0006 * License as published by the Free Software Foundation; either 0007 * version 2.1 of the License, or (at your option) any later version. 0008 * 0009 * This library is distributed in the hope that it will be useful, 0010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0012 * Lesser General Public License for more details. 0013 * 0014 * You should have received a copy of the GNU Lesser General Public 0015 * License along with this library; if not, write to the Free Software 0016 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 0017 */ 0018 0019 #include "contact.h" 0020 0021 #include <TelepathyQt/ContactManager> 0022 #include <TelepathyQt/Connection> 0023 #include <TelepathyQt/ContactCapabilities> 0024 #include <TelepathyQt/AvatarData> 0025 #include <TelepathyQt/Utils> 0026 0027 #include <QBitmap> 0028 #include <QPixmap> 0029 #include <QPixmapCache> 0030 0031 #include <KIconLoader> 0032 #include <KConfigGroup> 0033 #include <KConfig> 0034 0035 #include "capabilities-hack-private.h" 0036 0037 KTp::Contact::Contact(Tp::ContactManager *manager, const Tp::ReferencedHandles &handle, const Tp::Features &requestedFeatures, const QVariantMap &attributes) 0038 : Tp::Contact(manager, handle, requestedFeatures, attributes) 0039 { 0040 connect(manager->connection().data(), SIGNAL(destroyed()), SIGNAL(invalidated())); 0041 connect(manager->connection().data(), SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)), SIGNAL(invalidated())); 0042 connect(this, SIGNAL(avatarTokenChanged(QString)), SLOT(invalidateAvatarCache())); 0043 connect(this, SIGNAL(avatarDataChanged(Tp::AvatarData)), SLOT(invalidateAvatarCache())); 0044 connect(this, SIGNAL(presenceChanged(Tp::Presence)), SLOT(onPresenceChanged(Tp::Presence))); 0045 } 0046 0047 void KTp::Contact::onPresenceChanged(const Tp::Presence &presence) 0048 { 0049 Q_UNUSED(presence) 0050 /* Temporary workaround for upstream bug https://bugs.freedesktop.org/show_bug.cgi?id=55883) 0051 * Close https://bugs.kde.org/show_bug.cgi?id=308217 when fixed upstream */ 0052 Q_EMIT clientTypesChanged(clientTypes()); 0053 } 0054 0055 QString KTp::Contact::accountUniqueIdentifier() const 0056 { 0057 if (m_accountUniqueIdentifier.isEmpty() && manager()->connection()) { 0058 const_cast<KTp::Contact*>(this)->m_accountUniqueIdentifier = manager()->connection()->property("accountUID").toString(); 0059 } 0060 return m_accountUniqueIdentifier; 0061 } 0062 0063 QString KTp::Contact::uri() const 0064 { 0065 // so real ID will look like 0066 // ktp://gabble/jabber/blah/asdfjwer?foo@bar.com 0067 // ? is used as it is not a valid character in the dbus path that makes up the account UID 0068 return QStringLiteral("ktp://") + accountUniqueIdentifier() + QLatin1Char('?') + id(); 0069 } 0070 0071 KTp::Presence KTp::Contact::presence() const 0072 { 0073 if (!manager() || !manager()->connection()) { 0074 return Tp::Presence::offline(); 0075 } 0076 0077 return KTp::Presence(Tp::Contact::presence()); 0078 } 0079 0080 bool KTp::Contact::textChatCapability() const 0081 { 0082 if (!manager() || !manager()->connection()) { 0083 return false; 0084 } 0085 0086 return capabilities().textChats(); 0087 } 0088 0089 bool KTp::Contact::audioCallCapability() const 0090 { 0091 if (!manager() || !manager()->connection()) { 0092 return false; 0093 } 0094 0095 Tp::ConnectionPtr connection = manager()->connection(); 0096 bool contactCanStreamAudio = CapabilitiesHackPrivate::audioCalls( 0097 capabilities(), connection->cmName()); 0098 bool selfCanStreamAudio = CapabilitiesHackPrivate::audioCalls( 0099 connection->selfContact()->capabilities(), connection->cmName()); 0100 return contactCanStreamAudio && selfCanStreamAudio; 0101 } 0102 0103 bool KTp::Contact::videoCallCapability() const 0104 { 0105 if (!manager() || !manager()->connection()) { 0106 return false; 0107 } 0108 0109 Tp::ConnectionPtr connection = manager()->connection(); 0110 bool contactCanStreamVideo = CapabilitiesHackPrivate::videoCalls( 0111 capabilities(), connection->cmName()); 0112 bool selfCanStreamVideo = CapabilitiesHackPrivate::videoCalls( 0113 connection->selfContact()->capabilities(), connection->cmName()); 0114 return contactCanStreamVideo && selfCanStreamVideo; 0115 } 0116 0117 bool KTp::Contact::fileTransferCapability() const 0118 { 0119 if (!manager() || !manager()->connection()) { 0120 return false; 0121 } 0122 0123 bool contactCanHandleFiles = capabilities().fileTransfers(); 0124 bool selfCanHandleFiles = manager()->connection()->selfContact()->capabilities().fileTransfers(); 0125 return contactCanHandleFiles && selfCanHandleFiles; 0126 } 0127 0128 bool KTp::Contact::collaborativeEditingCapability() const 0129 { 0130 if (!manager() || !manager()->connection()) { 0131 return false; 0132 } 0133 0134 static const QString collab(QLatin1String("infinote")); 0135 bool selfCanShare = manager()->connection()->selfContact()->capabilities().streamTubes(collab); 0136 bool otherCanShare = capabilities().streamTubes(collab); 0137 return selfCanShare && otherCanShare; 0138 } 0139 0140 QStringList KTp::Contact::dbusTubeServicesCapability() const 0141 { 0142 if (!manager() || !manager()->connection()) { 0143 return QStringList(); 0144 } 0145 0146 return getCommonElements(capabilities().dbusTubeServices(), 0147 manager()->connection()->selfContact()->capabilities().dbusTubeServices()); 0148 } 0149 0150 QStringList KTp::Contact::streamTubeServicesCapability() const 0151 { 0152 if (!manager() || !manager()->connection()) { 0153 return QStringList(); 0154 } 0155 0156 return getCommonElements(capabilities().streamTubeServices(), 0157 manager()->connection()->selfContact()->capabilities().streamTubeServices()); 0158 } 0159 0160 QStringList KTp::Contact::clientTypes() const 0161 { 0162 /* Temporary workaround for upstream bug https://bugs.freedesktop.org/show_bug.cgi?id=55883) 0163 * Close https://bugs.kde.org/show_bug.cgi?id=308217 when fixed upstream */ 0164 if (Tp::Contact::presence().type() == Tp::ConnectionPresenceTypeOffline) { 0165 return QStringList(); 0166 } 0167 0168 //supress any errors trying to access ClientTypes when we don't have them 0169 if (! actualFeatures().contains(Tp::Contact::FeatureClientTypes)) { 0170 return QStringList(); 0171 } 0172 0173 return Tp::Contact::clientTypes(); 0174 } 0175 0176 QPixmap KTp::Contact::avatarPixmap() 0177 { 0178 QPixmap avatar; 0179 0180 //check pixmap cache for the avatar, if not present, load the avatar 0181 if (!QPixmapCache::find(keyCache(), avatar)){ 0182 QString file = avatarData().fileName; 0183 0184 //if contact does not provide path, let's see if we have avatar for the stored token 0185 if (file.isEmpty()) { 0186 KConfig config(QLatin1String("ktelepathy-avatarsrc")); 0187 KConfigGroup avatarTokenGroup = config.group(id()); 0188 QString avatarToken = avatarTokenGroup.readEntry(QLatin1String("avatarToken")); 0189 //only bother loading the pixmap if the token is not empty 0190 if (!avatarToken.isEmpty()) { 0191 avatar.load(buildAvatarPath(avatarToken)); 0192 } 0193 } else { 0194 avatar.load(file); 0195 } 0196 0197 //if neither above succeeded, return empty QPixmap, 0198 //PersonsModel will return the default icon instead 0199 if (avatar.isNull()) { 0200 return QPixmap(); 0201 } 0202 0203 //insert the contact into pixmap cache for faster lookup 0204 QPixmapCache::insert(keyCache(), avatar); 0205 } 0206 0207 return avatar; 0208 } 0209 0210 void KTp::Contact::avatarToGray(QPixmap &avatar) 0211 { 0212 QImage image = avatar.toImage(); 0213 QImage alpha= image.alphaChannel(); 0214 for (int i = 0; i < image.width(); ++i) { 0215 for (int j = 0; j < image.height(); ++j) { 0216 int colour = qGray(image.pixel(i, j)); 0217 image.setPixel(i, j, qRgb(colour, colour, colour)); 0218 } 0219 } 0220 image.setAlphaChannel(alpha); 0221 avatar = QPixmap::fromImage(image); 0222 } 0223 0224 QString KTp::Contact::keyCache() const 0225 { 0226 return id() + (presence().type() == Tp::ConnectionPresenceTypeOffline ? QLatin1String("-offline") : QLatin1String("-online")); 0227 } 0228 0229 QString KTp::Contact::buildAvatarPath(const QString &avatarToken) 0230 { 0231 QString cacheDir = QString::fromLatin1(qgetenv("XDG_CACHE_HOME")); 0232 if (cacheDir.isEmpty()) { 0233 cacheDir = QStringLiteral("%1/.cache").arg(QLatin1String(qgetenv("HOME"))); 0234 } 0235 0236 if (manager().isNull()) { 0237 return QString(); 0238 } 0239 0240 if (manager()->connection().isNull()) { 0241 return QString(); 0242 } 0243 0244 Tp::ConnectionPtr conn = manager()->connection(); 0245 QString path = QStringLiteral("%1/telepathy/avatars/%2/%3"). 0246 arg(cacheDir).arg(conn->cmName()).arg(conn->protocolName()); 0247 0248 QString avatarFileName = QStringLiteral("%1/%2").arg(path).arg(Tp::escapeAsIdentifier(avatarToken)); 0249 0250 return avatarFileName; 0251 } 0252 0253 void KTp::Contact::invalidateAvatarCache() 0254 { 0255 QPixmapCache::remove(id() + QLatin1String("-offline")); 0256 QPixmapCache::remove(id() + QLatin1String("-online")); 0257 } 0258 0259 QStringList KTp::Contact::getCommonElements(const QStringList &list1, const QStringList &list2) 0260 { 0261 /* QStringList::contains(QString) perform iterative comparsion, so there is no reason 0262 * to select smaller list as base for this cycle. */ 0263 QStringList commonElements; 0264 Q_FOREACH(const QString &i, list1) { 0265 if (list2.contains(i)) { 0266 commonElements << i; 0267 } 0268 } 0269 return commonElements; 0270 }