File indexing completed on 2023-05-30 11:40:28
0001 /* 0002 Copyright (C) 2011 Martin Klapetek <martin.klapetek@gmail.com> 0003 Copyright (C) 2011 Dario Freddi <dario.freddi@collabora.com> 0004 0005 This library is free software; you can redistribute it and/or 0006 modify it under the terms of the GNU Lesser General Public 0007 License as published by the Free Software Foundation; either 0008 version 2.1 of the License, or (at your option) any later version. 0009 0010 This library is distributed in the hope that it will be useful, 0011 but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0013 Lesser General Public License for more details. 0014 0015 You should have received a copy of the GNU Lesser General Public 0016 License along with this library; if not, write to the Free Software 0017 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 0018 */ 0019 0020 0021 #include "contact-request-handler.h" 0022 #include "ktp_kded_debug.h" 0023 0024 #include <KTp/error-dictionary.h> 0025 #include <KTp/core.h> 0026 #include <KTp/Widgets/contact-info-dialog.h> 0027 0028 #include <TelepathyQt/Account> 0029 #include <TelepathyQt/AccountManager> 0030 #include <TelepathyQt/Connection> 0031 #include <TelepathyQt/Contact> 0032 #include <TelepathyQt/ContactManager> 0033 #include <TelepathyQt/PendingComposite> 0034 #include <TelepathyQt/PendingOperation> 0035 0036 #include <KStatusNotifierItem> 0037 #include <KLocalizedString> 0038 0039 #include <QAction> 0040 #include <QIcon> 0041 #include <QFutureWatcher> 0042 #include <QtConcurrentFilter> 0043 #include <QMenu> 0044 0045 Q_DECLARE_METATYPE(Tp::ContactPtr) 0046 0047 static bool kde_tp_filter_contacts_by_publication_status(const Tp::ContactPtr &contact) 0048 { 0049 return contact->publishState() == Tp::Contact::PresenceStateAsk && !contact->isBlocked(); 0050 } 0051 0052 ContactRequestHandler::ContactRequestHandler(QObject *parent) 0053 : QObject(parent) 0054 { 0055 connect(KTp::accountManager().data(), SIGNAL(newAccount(Tp::AccountPtr)), 0056 this, SLOT(onNewAccountAdded(Tp::AccountPtr))); 0057 0058 QList<Tp::AccountPtr> accounts = KTp::accountManager()->allAccounts(); 0059 0060 Q_FOREACH(const Tp::AccountPtr &account, accounts) { 0061 onNewAccountAdded(account); 0062 } 0063 0064 } 0065 0066 ContactRequestHandler::~ContactRequestHandler() 0067 { 0068 0069 } 0070 0071 void ContactRequestHandler::onNewAccountAdded(const Tp::AccountPtr &account) 0072 { 0073 qCWarning(KTP_KDED_MODULE); 0074 Q_ASSERT(account->isReady(Tp::Account::FeatureCore)); 0075 0076 if (account->connection()) { 0077 handleNewConnection(account->connection()); 0078 } 0079 0080 connect(account.data(), 0081 SIGNAL(connectionChanged(Tp::ConnectionPtr)), 0082 this, SLOT(onConnectionChanged(Tp::ConnectionPtr))); 0083 } 0084 0085 void ContactRequestHandler::onConnectionChanged(const Tp::ConnectionPtr &connection) 0086 { 0087 if (!connection.isNull()) { 0088 handleNewConnection(connection); 0089 } 0090 } 0091 0092 void ContactRequestHandler::handleNewConnection(const Tp::ConnectionPtr &connection) 0093 { 0094 qCDebug(KTP_KDED_MODULE); 0095 connect(connection->contactManager().data(), SIGNAL(presencePublicationRequested(Tp::Contacts)), 0096 this, SLOT(onPresencePublicationRequested(Tp::Contacts))); 0097 0098 connect(connection->contactManager().data(), 0099 SIGNAL(stateChanged(Tp::ContactListState)), 0100 this, SLOT(onContactManagerStateChanged(Tp::ContactListState))); 0101 0102 onContactManagerStateChanged(connection->contactManager(), 0103 connection->contactManager()->state()); 0104 } 0105 0106 void ContactRequestHandler::onContactManagerStateChanged(Tp::ContactListState state) 0107 { 0108 onContactManagerStateChanged(Tp::ContactManagerPtr(qobject_cast< Tp::ContactManager* >(sender())), state); 0109 } 0110 0111 void ContactRequestHandler::onContactManagerStateChanged(const Tp::ContactManagerPtr &contactManager, 0112 Tp::ContactListState state) 0113 { 0114 if (state == Tp::ContactListStateSuccess) { 0115 QFutureWatcher< Tp::ContactPtr > *watcher = new QFutureWatcher< Tp::ContactPtr >(this); 0116 connect(watcher, SIGNAL(finished()), this, SLOT(onAccountsPresenceStatusFiltered())); 0117 watcher->setFuture(QtConcurrent::filtered(contactManager->allKnownContacts(), 0118 kde_tp_filter_contacts_by_publication_status)); 0119 0120 qCDebug(KTP_KDED_MODULE) << "Watcher is on"; 0121 } else { 0122 qCDebug(KTP_KDED_MODULE) << "Watcher still off, state is" << state << "contactManager is" << contactManager.isNull(); 0123 } 0124 } 0125 0126 void ContactRequestHandler::onAccountsPresenceStatusFiltered() 0127 { 0128 qCDebug(KTP_KDED_MODULE) << "Watcher is here"; 0129 QFutureWatcher< Tp::ContactPtr > *watcher = dynamic_cast< QFutureWatcher< Tp::ContactPtr > * >(sender()); 0130 qCDebug(KTP_KDED_MODULE) << "Watcher is casted"; 0131 Tp::Contacts contacts = watcher->future().results().toSet(); 0132 qCDebug(KTP_KDED_MODULE) << "Watcher is used"; 0133 if (!contacts.isEmpty()) { 0134 onPresencePublicationRequested(contacts); 0135 } 0136 watcher->deleteLater(); 0137 } 0138 0139 void ContactRequestHandler::onPresencePublicationRequested(const Tp::Contacts &contacts) 0140 { 0141 qCDebug(KTP_KDED_MODULE) << "New contact requested"; 0142 0143 Q_FOREACH (const Tp::ContactPtr &contact, contacts) { 0144 Tp::ContactManagerPtr manager = contact->manager(); 0145 0146 if (contact->subscriptionState() == Tp::Contact::PresenceStateYes) { 0147 Tp::PendingOperation *op = manager->authorizePresencePublication(QList< Tp::ContactPtr >() << contact); 0148 op->setProperty("__contact", QVariant::fromValue(contact)); 0149 0150 connect(op, SIGNAL(finished(Tp::PendingOperation*)), 0151 this, SLOT(onFinalizeSubscriptionFinished(Tp::PendingOperation*))); 0152 } else { 0153 // Handle multiaccount requests properly 0154 if (m_pendingContacts.contains(contact->id())) { 0155 // It's likely we have a simultaneous request 0156 bool newReq = true; 0157 QHash<QString, Tp::ContactPtr>::const_iterator i = m_pendingContacts.constFind(contact->id()); 0158 while (i != m_pendingContacts.constEnd() && i.key() == contact->id()) { 0159 if (i.value().data() == contact.data()) { 0160 newReq = false; 0161 break; 0162 } 0163 ++i; 0164 } 0165 0166 if (newReq) { 0167 // Insert multi 0168 m_pendingContacts.insertMulti(contact->id(), contact); 0169 } 0170 } else { 0171 // Simple insertion 0172 m_pendingContacts.insert(contact->id(), contact); 0173 } 0174 0175 connect(contact.data(), SIGNAL(invalidated()), this, SLOT(onContactInvalidated())); 0176 0177 updateMenus(); 0178 0179 if (!m_notifierItem.isNull()) { 0180 m_notifierItem.data()->showMessage(i18n("New contact request"), //krazy:exclude=qmethods 0181 i18n("The contact %1 wants to be able to chat with you.", 0182 contact->id()), 0183 QLatin1String("list-add-user")); 0184 } 0185 } 0186 } 0187 } 0188 0189 void ContactRequestHandler::onFinalizeSubscriptionFinished(Tp::PendingOperation *op) 0190 { 0191 Tp::ContactPtr contact = op->property("__contact").value< Tp::ContactPtr >(); 0192 0193 Q_ASSERT(!contact.isNull()); 0194 0195 if (op->isError()) { 0196 // ARGH 0197 if (!m_notifierItem.isNull()) { 0198 m_notifierItem.data()->showMessage(i18n("Error adding contact"), 0199 i18n("%1 has been added successfully to your contact list, " 0200 "but might be unable to see when you are online. Error details: %2", 0201 contact->alias(), KTp::ErrorDictionary::displayVerboseErrorMessage(op->errorName())), 0202 QLatin1String("dialog-error")); 0203 } 0204 } else { 0205 // Update the menu 0206 m_pendingContacts.remove(contact->id()); 0207 updateMenus(); 0208 } 0209 } 0210 0211 void ContactRequestHandler::onContactInvalidated() 0212 { 0213 Tp::ContactPtr contact = Tp::ContactPtr(qobject_cast<Tp::Contact*>(sender())); 0214 0215 m_pendingContacts.remove(contact->id()); 0216 updateMenus(); 0217 } 0218 0219 void ContactRequestHandler::onNotifierActivated(bool active, const QPoint &pos) 0220 { 0221 if (active) { 0222 if (m_notifierItem) { 0223 m_notifierItem.data()->contextMenu()->popup(pos); 0224 } 0225 } 0226 } 0227 0228 void ContactRequestHandler::onContactRequestApproved() 0229 { 0230 QString contactId = qobject_cast<QAction*>(sender())->data().toString(); 0231 0232 // Disable the action in the meanwhile 0233 m_menuItems.value(contactId)->setEnabled(false); 0234 0235 if (!contactId.isEmpty()) { 0236 QList<Tp::PendingOperation*> operations; 0237 QHash<QString, Tp::ContactPtr>::const_iterator i = m_pendingContacts.constFind(contactId); 0238 while (i != m_pendingContacts.constEnd() && i.key() == contactId) { 0239 if (!i.value()->manager().isNull()) { 0240 Tp::PendingOperation *op = i.value()->manager()->authorizePresencePublication(QList< Tp::ContactPtr >() << i.value()); 0241 op->setProperty("__contact", QVariant::fromValue(i.value())); 0242 operations.append(op); 0243 } 0244 ++i; 0245 } 0246 0247 // Take the first value, if any 0248 if (!operations.isEmpty()) { 0249 Tp::ContactPtr contact = m_pendingContacts.find(contactId).value(); 0250 0251 Tp::PendingComposite *op = new Tp::PendingComposite(operations, true, contact); 0252 op->setProperty("__contact", QVariant::fromValue(contact)); 0253 0254 connect(op, SIGNAL(finished(Tp::PendingOperation*)), 0255 this, SLOT(onAuthorizePresencePublicationFinished(Tp::PendingOperation*))); 0256 } 0257 } 0258 0259 } 0260 0261 void ContactRequestHandler::onShowContactDetails() 0262 { 0263 QString contactId = qobject_cast<QAction*>(sender())->data().toString(); 0264 0265 if (!contactId.isEmpty()) { 0266 const Tp::ContactPtr contact = m_pendingContacts.find(contactId).value(); 0267 const Tp::ContactManagerPtr manager = contact->manager(); 0268 Q_FOREACH (const Tp::AccountPtr &account, KTp::accountManager()->allAccounts()) { 0269 if (account->connection() == manager->connection()) { 0270 KTp::ContactInfoDialog *dialog = new KTp::ContactInfoDialog(account, contact); 0271 connect(dialog, SIGNAL(closeClicked()), dialog, SLOT(deleteLater())); 0272 dialog->show(); 0273 break; 0274 } 0275 } 0276 } 0277 } 0278 0279 void ContactRequestHandler::onAuthorizePresencePublicationFinished(Tp::PendingOperation *op) 0280 { 0281 Tp::ContactPtr contact = op->property("__contact").value< Tp::ContactPtr >(); 0282 0283 if (op->isError()) { 0284 if (!m_notifierItem.isNull()) { 0285 m_notifierItem.data()->showMessage(i18n("Error granting contact authorization"), 0286 i18n("There was an error while accepting the request: %1", 0287 KTp::ErrorDictionary::displayVerboseErrorMessage(op->errorName())), 0288 QLatin1String("dialog-error")); 0289 } 0290 0291 // Re-enable the action 0292 m_menuItems.value(contact->id())->setEnabled(true); 0293 } else { 0294 // op succeeded 0295 if (!m_notifierItem.isNull()) { 0296 m_notifierItem.data()->showMessage(i18n("Contact request accepted"), 0297 i18n("%1 will now be able to see when you are online", 0298 contact->alias()), QLatin1String("dialog-ok-apply")); 0299 } 0300 0301 // If needed, reiterate the request on the other end 0302 if (contact->manager()->canRequestPresenceSubscription() && 0303 contact->subscriptionState() == Tp::Contact::PresenceStateNo) { 0304 0305 Tp::PendingOperation *op = contact->manager()->requestPresenceSubscription(QList< Tp::ContactPtr >() << contact); 0306 op->setProperty("__contact", QVariant::fromValue(contact)); 0307 0308 connect(op, 0309 SIGNAL(finished(Tp::PendingOperation*)), 0310 this, SLOT(onFinalizeSubscriptionFinished(Tp::PendingOperation*))); 0311 } else { 0312 // Update the menu 0313 m_pendingContacts.remove(contact->id()); 0314 updateMenus(); 0315 } 0316 } 0317 } 0318 0319 void ContactRequestHandler::onContactRequestDenied() 0320 { 0321 QString contactId = qobject_cast<QAction*>(sender())->data().toString(); 0322 0323 // Disable the action in the meanwhile 0324 m_menuItems.value(contactId)->setEnabled(false); 0325 0326 if (!contactId.isEmpty()) { 0327 QList<Tp::PendingOperation*> operations; 0328 QHash<QString, Tp::ContactPtr>::const_iterator i = m_pendingContacts.constFind(contactId); 0329 while (i != m_pendingContacts.constEnd() && i.key() == contactId) { 0330 if (!i.value()->manager().isNull()) { 0331 //don't publish our presence to that user 0332 Tp::PendingOperation *op = i.value()->manager()->removePresencePublication(QList< Tp::ContactPtr >() << i.value()); 0333 op->setProperty("__contact", QVariant::fromValue(i.value())); 0334 operations.append(op); 0335 0336 //and block that contact 0337 if (i.value()->manager()->canBlockContacts()) { 0338 Tp::PendingOperation *blockOp = i.value()->manager()->blockContacts(QList<Tp::ContactPtr>() << i.value()); 0339 operations.append(blockOp); 0340 } 0341 } 0342 ++i; 0343 } 0344 0345 // Wait until all operations complete 0346 if (!operations.isEmpty()) { 0347 Tp::ContactPtr contact = m_pendingContacts.find(contactId).value(); 0348 0349 Tp::PendingComposite *op = new Tp::PendingComposite(operations, true, contact); 0350 op->setProperty("__contact", QVariant::fromValue(contact)); 0351 0352 connect(op, SIGNAL(finished(Tp::PendingOperation*)), 0353 this, SLOT(onRemovePresencePublicationFinished(Tp::PendingOperation*))); 0354 } 0355 } 0356 } 0357 0358 void ContactRequestHandler::onRemovePresencePublicationFinished(Tp::PendingOperation *op) 0359 { 0360 Tp::ContactPtr contact = op->property("__contact").value< Tp::ContactPtr >(); 0361 0362 if (op->isError()) { 0363 // ARGH 0364 m_notifierItem.data()->showMessage(i18n("Error denying contact request"), 0365 i18n("There was an error while denying the request: %1", 0366 KTp::ErrorDictionary::displayVerboseErrorMessage(op->errorName())), 0367 QLatin1String("dialog-error")); 0368 0369 // Re-enable the action 0370 m_menuItems.value(contact->id())->setEnabled(true); 0371 } else { 0372 // Yeah 0373 if (!m_notifierItem.isNull()) { 0374 m_notifierItem.data()->showMessage(i18n("Contact request denied"), 0375 i18n("%1 will not be able to see when you are online", 0376 contact->alias()), QLatin1String("dialog-information")); 0377 } 0378 // Update the menu 0379 m_pendingContacts.remove(contact->id()); 0380 updateMenus(); 0381 } 0382 } 0383 0384 void ContactRequestHandler::updateMenus() 0385 { 0386 if (m_notifierItem.isNull()) { 0387 m_notifierItem = new KStatusNotifierItem(QLatin1String("telepathy_kde_contact_requests"), this); 0388 m_notifierItem.data()->setCategory(KStatusNotifierItem::Communications); 0389 m_notifierItem.data()->setIconByName(QLatin1String("user-identity")); 0390 m_notifierItem.data()->setAttentionIconByName(QLatin1String("list-add-user")); 0391 m_notifierItem.data()->setStandardActionsEnabled(false); 0392 m_notifierItem.data()->setTitle(i18nc("Menu title", "Pending contact requests")); 0393 m_notifierItem.data()->setStatus(KStatusNotifierItem::Active); 0394 0395 QMenu *notifierMenu = new QMenu(0); 0396 notifierMenu->setTitle(i18nc("Context menu title", "Received contact requests")); 0397 0398 connect(m_notifierItem.data(), SIGNAL(activateRequested(bool,QPoint)), SLOT(onNotifierActivated(bool,QPoint))); 0399 0400 m_notifierItem.data()->setContextMenu(notifierMenu); 0401 } 0402 0403 qCDebug(KTP_KDED_MODULE) << m_pendingContacts.keys(); 0404 0405 //add members in pending contacts not in the menu to the menu. 0406 QHash<QString, Tp::ContactPtr>::const_iterator i; 0407 for (i = m_pendingContacts.constBegin(); i != m_pendingContacts.constEnd(); ++i) { 0408 if (m_menuItems.contains(i.key())) { 0409 // Skip 0410 continue; 0411 } 0412 0413 qCDebug(KTP_KDED_MODULE); 0414 Tp::ContactPtr contact = i.value(); 0415 0416 QMenu *contactMenu = new QMenu(m_notifierItem.data()->contextMenu()); 0417 contactMenu->setTitle(i18n("Request from %1", contact->alias())); 0418 contactMenu->setObjectName(contact->id()); 0419 0420 QAction *menuAction; 0421 0422 menuAction = new QAction(QIcon(QLatin1String("user-identity")), i18n("Contact Details"), contactMenu); 0423 menuAction->setData(i.key()); 0424 connect(menuAction, SIGNAL(triggered()), 0425 this, SLOT(onShowContactDetails())); 0426 contactMenu->addAction(menuAction); 0427 0428 if (!contact->publishStateMessage().isEmpty()) { 0429 contactMenu->insertSection(menuAction, contact->publishStateMessage()); 0430 } else { 0431 contactMenu->insertSection(menuAction, contact->alias()); 0432 } 0433 0434 contactMenu->addSeparator(); 0435 0436 menuAction = new QAction(QIcon::fromTheme(QLatin1String("dialog-ok-apply")), i18n("Approve"), contactMenu); 0437 menuAction->setData(i.key()); 0438 connect(menuAction, SIGNAL(triggered()), 0439 this, SLOT(onContactRequestApproved())); 0440 contactMenu->addAction(menuAction); 0441 0442 menuAction = new QAction(QIcon::fromTheme(QLatin1String("dialog-close")), i18n("Deny"), contactMenu); 0443 menuAction->setData(i.key()); 0444 connect(menuAction, SIGNAL(triggered()), 0445 this, SLOT(onContactRequestDenied())); 0446 contactMenu->addAction(menuAction); 0447 0448 m_notifierItem.data()->contextMenu()->addMenu(contactMenu); 0449 m_menuItems.insert(i.key(), contactMenu); 0450 } 0451 0452 //remove items that are still in the menu, but not in pending contacts 0453 QHash<QString, QMenu*>::iterator j = m_menuItems.begin(); 0454 while (j != m_menuItems.end()) { 0455 if (m_pendingContacts.contains(j.key())) { 0456 // Skip 0457 ++j; 0458 continue; 0459 } 0460 0461 // Remove 0462 m_notifierItem.data()->contextMenu()->removeAction(j.value()->menuAction()); 0463 j = m_menuItems.erase(j); 0464 } 0465 0466 if (m_menuItems.size() > 0) { 0467 //if menu still contains items, update the tooltip to have the correct number 0468 m_notifierItem.data()->setToolTip(QLatin1String("list-add-user"), 0469 i18np("You have 1 contact wanting to chat with you", 0470 "You have %1 contacts wanting to chat with you", 0471 m_menuItems.size()), 0472 QString()); 0473 } else { 0474 //if empty delete the status notifier item 0475 m_notifierItem.data()->deleteLater(); 0476 } 0477 0478 } 0479 0480 #include "contact-request-handler.moc"