Telepathy integration

This sends recieved text messages to any Telepathy client and allows the
user to respond from there.
This should work with both our clients and Empathy.

An account on telepathy is created on activation.

As Telepathy clients expect backends to be always running, this is
started by the daemon to
suppress client errors. The plugin system then talks to the same CM via
use of a singleton accessor.

Based on work by Alexandr Akulich then tidied up and rebased.
This commit is contained in:
David Edmundson 2014-11-30 02:52:43 +01:00
parent 501e5431ec
commit e4cbf22519
18 changed files with 924 additions and 8 deletions

View file

@ -13,6 +13,11 @@ set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} ${CMAKE_SOURCE_DI
find_package(Qt5 5.2 REQUIRED COMPONENTS Quick) find_package(Qt5 5.2 REQUIRED COMPONENTS Quick)
find_package(KF5 REQUIRED COMPONENTS I18n ConfigWidgets DBusAddons IconThemes) find_package(KF5 REQUIRED COMPONENTS I18n ConfigWidgets DBusAddons IconThemes)
find_package(Qca-qt5 2.1.0 REQUIRED) find_package(Qca-qt5 2.1.0 REQUIRED)
find_package(TelepathyQt5 0.9.5)
find_package(TelepathyQt5Service 0.9.5)
if (TelepathyQt5_FOUND)
add_definitions(-DHAVE_TELEPATHY)
endif()
include_directories(${CMAKE_SOURCE_DIR}) include_directories(${CMAKE_SOURCE_DIR})
@ -49,6 +54,11 @@ add_subdirectory(plugins)
add_subdirectory(plasmoid) add_subdirectory(plasmoid)
add_subdirectory(cli) add_subdirectory(cli)
add_subdirectory(fileitemactionplugin) add_subdirectory(fileitemactionplugin)
if (TelepathyQt5_FOUND)
add_subdirectory(telepathy-cm)
endif()
if(BUILD_TESTING) if(BUILD_TESTING)
add_subdirectory(tests) add_subdirectory(tests)
endif() endif()

View file

@ -8,6 +8,9 @@ add_executable(kdeconnectd kdeconnectd.cpp)
target_link_libraries(kdeconnectd kdeconnectcore KF5::KIOWidgets KF5::DBusAddons KF5::Notifications KF5::I18n Qt5::Widgets) target_link_libraries(kdeconnectd kdeconnectcore KF5::KIOWidgets KF5::DBusAddons KF5::Notifications KF5::I18n Qt5::Widgets)
ecm_mark_nongui_executable(kdeconnectd) ecm_mark_nongui_executable(kdeconnectd)
if(TelepathyQt5_FOUND)
target_link_libraries(kdeconnectd connectcm)
endif()
configure_file(kdeconnectd.desktop.cmake ${CMAKE_CURRENT_BINARY_DIR}/kdeconnectd.desktop) configure_file(kdeconnectd.desktop.cmake ${CMAKE_CURRENT_BINARY_DIR}/kdeconnectd.desktop)
configure_file(org.kde.kdeconnect.service.in ${CMAKE_CURRENT_BINARY_DIR}/org.kde.kdeconnect.service) configure_file(org.kde.kdeconnect.service.in ${CMAKE_CURRENT_BINARY_DIR}/org.kde.kdeconnect.service)
@ -15,3 +18,4 @@ configure_file(org.kde.kdeconnect.service.in ${CMAKE_CURRENT_BINARY_DIR}/org.kde
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kdeconnectd.desktop DESTINATION ${AUTOSTART_INSTALL_DIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kdeconnectd.desktop DESTINATION ${AUTOSTART_INSTALL_DIR})
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.kdeconnect.service DESTINATION ${DBUS_SERVICES_INSTALL_DIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.kdeconnect.service DESTINATION ${DBUS_SERVICES_INSTALL_DIR})
install(TARGETS kdeconnectd DESTINATION ${LIBEXEC_INSTALL_DIR}) install(TARGETS kdeconnectd DESTINATION ${LIBEXEC_INSTALL_DIR})

View file

@ -34,6 +34,10 @@
#include "core/device.h" #include "core/device.h"
#include "kdeconnect-version.h" #include "kdeconnect-version.h"
#ifdef HAVE_TELEPATHY
#include "kdeconnecttelepathyprotocolfactory.h"
#endif
#ifndef Q_OS_WIN #ifndef Q_OS_WIN
#include <sys/socket.h> #include <sys/socket.h>
@ -128,6 +132,11 @@ int main(int argc, char* argv[])
QObject::connect(daemon, SIGNAL(destroyed(QObject*)), &app, SLOT(quit())); QObject::connect(daemon, SIGNAL(destroyed(QObject*)), &app, SLOT(quit()));
initializeTermHandlers(&app, daemon); initializeTermHandlers(&app, daemon);
#ifdef HAVE_TELEPATHY
//keep a reference to the KTP CM so that we can register on DBus
auto telepathyPlugin = KDEConnectTelepathyProtocolFactory::interface();
#endif
return app.exec(); return app.exec();
} }

View file

@ -12,3 +12,9 @@ target_link_libraries(kdeconnect_telephony
KF5::I18n KF5::I18n
KF5::Notifications KF5::Notifications
) )
if (TelepathyQt5_FOUND)
target_link_libraries(kdeconnect_telephony
connectcm
)
endif()

View file

@ -35,7 +35,11 @@ Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_TELEPHONY, "kdeconnect.plugin.telephony")
TelephonyPlugin::TelephonyPlugin(QObject *parent, const QVariantList &args) TelephonyPlugin::TelephonyPlugin(QObject *parent, const QVariantList &args)
: KdeConnectPlugin(parent, args) : KdeConnectPlugin(parent, args)
{ {
#ifdef HAVE_TELEPATHY
//keep a reference to the KTP CM so that we can register on DBus
m_telepathyInterface = KDEConnectTelepathyProtocolFactory::interface();
connect(m_telepathyInterface.constData(), SIGNAL(messageReceived(QString,QString)), SLOT(sendSms(QString,QString)));
#endif
} }
KNotification* TelephonyPlugin::createNotification(const NetworkPackage& np) KNotification* TelephonyPlugin::createNotification(const NetworkPackage& np)
@ -88,10 +92,11 @@ KNotification* TelephonyPlugin::createNotification(const NetworkPackage& np)
notification->setActions( QStringList(i18n("Mute Call")) ); notification->setActions( QStringList(i18n("Mute Call")) );
connect(notification, &KNotification::action1Activated, this, &TelephonyPlugin::sendMutePackage); connect(notification, &KNotification::action1Activated, this, &TelephonyPlugin::sendMutePackage);
} else if (event == QLatin1String("sms")) { } else if (event == QLatin1String("sms")) {
const QString messageBody = np.get<QString>("messageBody","");
notification->setActions( QStringList(i18n("Reply")) ); notification->setActions( QStringList(i18n("Reply")) );
notification->setProperty("phoneNumber", phoneNumber); notification->setProperty("phoneNumber", phoneNumber);
notification->setProperty("contactName", contactName); notification->setProperty("contactName", contactName);
notification->setProperty("originalMessage", np.get<QString>("messageBody","")); notification->setProperty("originalMessage", messageBody);
connect(notification, &KNotification::action1Activated, this, &TelephonyPlugin::showSendSmsDialog); connect(notification, &KNotification::action1Activated, this, &TelephonyPlugin::showSendSmsDialog);
} }
@ -104,15 +109,23 @@ bool TelephonyPlugin::receivePackage(const NetworkPackage& np)
if (np.get<bool>("isCancel")) { if (np.get<bool>("isCancel")) {
//TODO: Clear the old notification //TODO: Clear the old notification
return true;
}
#ifdef HAVE_TELEPATHY
if (np.get<QString>("event") == QLatin1String("sms")) {
const QString messageBody = np.get<QString>("messageBody","");
const QString phoneNumber = np.get<QString>("phoneNumber", i18n("unknown number"));
const QString contactName = np.get<QString>("contactName", phoneNumber);
if (m_telepathyInterface->sendMessage(contactName, messageBody)) {
return true;
}
}
#endif
} else {
KNotification* n = createNotification(np); KNotification* n = createNotification(np);
if (n != nullptr) n->sendEvent(); if (n != nullptr) n->sendEvent();
}
return true; return true;
} }
void TelephonyPlugin::sendMutePackage() void TelephonyPlugin::sendMutePackage()

View file

@ -27,6 +27,10 @@
#include <core/kdeconnectplugin.h> #include <core/kdeconnectplugin.h>
#ifdef HAVE_TELEPATHY
#include "kdeconnecttelepathyprotocolfactory.h"
#endif
#define PACKAGE_TYPE_TELEPHONY QLatin1String("kdeconnect.telephony") #define PACKAGE_TYPE_TELEPHONY QLatin1String("kdeconnect.telephony")
Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_PLUGIN_TELEPHONY) Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_PLUGIN_TELEPHONY)
@ -51,6 +55,11 @@ private Q_SLOTS:
private: private:
KNotification* createNotification(const NetworkPackage& np); KNotification* createNotification(const NetworkPackage& np);
#ifdef HAVE_TELEPATHY
ConnectProtocolPtr m_telepathyInterface;
#endif
}; };
#endif #endif

View file

@ -0,0 +1,24 @@
project(connectcm)
add_definitions(-DCONNECTCM_LIBRARY)
set(connectcm_SOURCES
connection.cpp
protocol.cpp
textchannel.cpp
kdeconnecttelepathyprotocolfactory.cpp
)
add_library(connectcm SHARED ${connectcm_SOURCES})
target_link_libraries(connectcm
PUBLIC
Qt5::Core
${TELEPATHY_QT5_LIBRARIES}
${TELEPATHY_QT5_SERVICE_LIBRARIES}
)
install (TARGETS connectcm
${INSTALL_TARGETS_DEFAULT_ARGS}
)

1
telepathy-cm/README Normal file
View file

@ -0,0 +1 @@
Connect TelepathyQt-based Connection Manager.

View file

@ -0,0 +1,12 @@
#ifndef CONNECTCM_EXPORT_H
#define CONNECTCM_EXPORT_H
#include <QtCore/QtGlobal>
#if defined(CONNECTCM_LIBRARY)
#define CONNECTCM_EXPORT Q_DECL_EXPORT
#else
#define CONNECTCM_EXPORT Q_DECL_IMPORT
#endif
#endif

362
telepathy-cm/connection.cpp Normal file
View file

@ -0,0 +1,362 @@
/*
Copyright (C) 2014 Alexandr Akulich <akulichalexander@gmail.com>
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "connection.h"
#include "textchannel.h"
#include <TelepathyQt/Constants>
#include <TelepathyQt/BaseChannel>
#include <TelepathyQt/Presence>
#include <QDateTime>
#include <QDebug>
Tp::SimpleStatusSpecMap ConnectConnection::getSimpleStatusSpecMap()
{
//Presence
Tp::SimpleStatusSpec spAvailable;
spAvailable.type = Tp::ConnectionPresenceTypeAvailable;
spAvailable.maySetOnSelf = false;
spAvailable.canHaveMessage = true;
Tp::SimpleStatusSpec spOffline;
spOffline.type = Tp::ConnectionPresenceTypeOffline;
spOffline.maySetOnSelf = false;
spOffline.canHaveMessage = false;
Tp::SimpleStatusSpecMap specs;
specs.insert(QLatin1String("available"), spAvailable);
specs.insert(QLatin1String("offline"), spOffline);
return specs;
}
ConnectConnection::ConnectConnection(const QDBusConnection &dbusConnection, const QString &cmName, const QString &protocolName, const QVariantMap &parameters) :
Tp::BaseConnection(dbusConnection, cmName, protocolName, parameters)
{
qDebug() << "making new connection";
/* Connection.Interface.Contacts */
contactsIface = Tp::BaseConnectionContactsInterface::create();
contactsIface->setGetContactAttributesCallback(Tp::memFun(this, &ConnectConnection::getContactAttributes));
contactsIface->setContactAttributeInterfaces(QStringList()
<< TP_QT_IFACE_CONNECTION
<< TP_QT_IFACE_CONNECTION_INTERFACE_CONTACT_LIST
<< TP_QT_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE);
plugInterface(Tp::AbstractConnectionInterfacePtr::dynamicCast(contactsIface));
/* Connection.Interface.SimplePresence */
simplePresenceIface = Tp::BaseConnectionSimplePresenceInterface::create();
simplePresenceIface->setSetPresenceCallback(Tp::memFun(this,&ConnectConnection::setPresence));
plugInterface(Tp::AbstractConnectionInterfacePtr::dynamicCast(simplePresenceIface));
/* Connection.Interface.ContactList */
contactListIface = Tp::BaseConnectionContactListInterface::create();
contactListIface->setGetContactListAttributesCallback(Tp::memFun(this, &ConnectConnection::getContactListAttributes));
// contactListIface->setRequestSubscriptionCallback(Tp::memFun(this, &ConnectConnection::requestSubscription));
plugInterface(Tp::AbstractConnectionInterfacePtr::dynamicCast(contactListIface));
/* Connection.Interface.Requests */
requestsIface = Tp::BaseConnectionRequestsInterface::create(this);
/* Fill requestableChannelClasses */
Tp::RequestableChannelClass text;
text.fixedProperties[TP_QT_IFACE_CHANNEL+".ChannelType"] = TP_QT_IFACE_CHANNEL_TYPE_TEXT;
text.fixedProperties[TP_QT_IFACE_CHANNEL+".TargetHandleType"] = Tp::HandleTypeContact;
text.allowedProperties.append(TP_QT_IFACE_CHANNEL+".TargetHandle");
text.allowedProperties.append(TP_QT_IFACE_CHANNEL+".TargetID");
requestsIface->requestableChannelClasses << text;
plugInterface(Tp::AbstractConnectionInterfacePtr::dynamicCast(requestsIface));
QString selfName = QLatin1String("SelfContact");
if (parameters.contains("self_name")) {
selfName = parameters.value("self_name").toString();
}
if (parameters.contains("device_id")) {
m_deviceId = parameters.value("device_id").toString();
}
setSelfHandle(addContact(selfName + "@kdeconnect_" + m_deviceId));
setConnectCallback(Tp::memFun(this, &ConnectConnection::connect));
setInspectHandlesCallback(Tp::memFun(this, &ConnectConnection::inspectHandles));
setCreateChannelCallback(Tp::memFun(this, &ConnectConnection::createChannelCB));
setRequestHandlesCallback(Tp::memFun(this, &ConnectConnection::requestHandles));
}
ConnectConnection::~ConnectConnection()
{
qDebug() << "goodbye connection";
}
void ConnectConnection::connect(Tp::DBusError *error)
{
setStatus(Tp::ConnectionStatusConnecting, Tp::ConnectionStatusReasonRequested);
simplePresenceIface->setStatuses(getSimpleStatusSpecMap());
Tp::SimpleContactPresences presences;
Tp::SimplePresence presence;
presence.status = "available";
presence.statusMessage = "";
presence.type = Tp::ConnectionPresenceTypeAvailable;
presences[selfHandle()] = presence;
simplePresenceIface->setPresences(presences);
setStatus(Tp::ConnectionStatusConnected, Tp::ConnectionStatusReasonRequested);
/* Set ContactList status */
contactListIface->setContactListState(Tp::ContactListStateSuccess);
}
QStringList ConnectConnection::inspectHandles(uint handleType, const Tp::UIntList &handles, Tp::DBusError *error)
{
qDebug() << Q_FUNC_INFO;
if (handleType != Tp::HandleTypeContact) {
error->set(TP_QT_ERROR_INVALID_ARGUMENT, "Unsupported handle type");
return QStringList();
}
QStringList result;
foreach (uint handle, handles) {
if (!m_handles.contains(handle)) {
return QStringList();
}
result.append(m_handles.value(handle));
}
return result;
}
Tp::BaseChannelPtr ConnectConnection::createChannelCB(const QVariantMap &request, Tp::DBusError *error)
{
const uint targetHandleType = request.value(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandleType")).toUInt();
const QString channelType = request.value(TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType")).toString();
//note if we ever have this invoked from external clients we need to look for TargetID too and look it up
const uint targetHandle = request.value(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandle")).toUInt();
qDebug() << "ConnectConnection::createChannel " << channelType
<< " " << targetHandleType
<< " " << targetHandle;
if ((targetHandleType != Tp::HandleTypeContact) || (targetHandle == 0)) {
error->set(TP_QT_ERROR_INVALID_HANDLE, "createChannel error");
return Tp::BaseChannelPtr();
}
Tp::BaseChannelPtr baseChannel = Tp::BaseChannel::create(this, channelType, Tp::HandleType(targetHandleType), targetHandle);
QString identifier = m_handles.value(targetHandle);
if (channelType == TP_QT_IFACE_CHANNEL_TYPE_TEXT) {
ConnectTextChannelPtr textType = ConnectTextChannel::create(this, baseChannel.data(), targetHandle, identifier);
qDebug() << "Text interface is called " << textType->interfaceName();
baseChannel->plugInterface(Tp::AbstractChannelInterfacePtr::dynamicCast(textType));
}
return baseChannel;
}
Tp::UIntList ConnectConnection::requestHandles(uint handleType, const QStringList &identifiers, Tp::DBusError *error)
{
qDebug() << Q_FUNC_INFO << identifiers;
Tp::UIntList result;
if (handleType != Tp::HandleTypeContact) {
error->set(TP_QT_ERROR_INVALID_ARGUMENT, "ConnectConnection::requestHandles - Handle Type unknown");
return result;
}
foreach(const QString &identify, identifiers) {
uint handle = m_handles.key(identify, 0);
if (handle) {
result.append(handle);
} else {
result.append(addContact(identify));
}
}
return result;
}
Tp::ContactAttributesMap ConnectConnection::getContactListAttributes(const QStringList &interfaces, bool hold, Tp::DBusError *error)
{
qDebug() << Q_FUNC_INFO;
Tp::ContactAttributesMap contactAttributes;
foreach (const uint handle, m_handles.keys()) {
if (handle == selfHandle()) {
continue;
}
QVariantMap attributes;
attributes["org.freedesktop.Telepathy.Connection/contact-id"] = m_handles.value(handle);
attributes["org.freedesktop.Telepathy.Connection.Interface.ContactList/subscribe"] = Tp::SubscriptionStateYes;
attributes["org.freedesktop.Telepathy.Connection.Interface.ContactList/publish"] = Tp::SubscriptionStateYes;
attributes["org.freedesktop.Telepathy.Connection.Interface.ConnectPresence/presence"] = QVariant::fromValue(getPresence(handle));
contactAttributes[handle] = attributes;
}
return contactAttributes;
}
Tp::ContactAttributesMap ConnectConnection::getContactAttributes(const Tp::UIntList &handles, const QStringList &interfaces, Tp::DBusError *error)
{
// Connection.Interface.Contacts
// http://telepathy.freedesktop.org/spec/Connection_Interface_Contacts.html#Method:GetContactAttributes
qDebug() << Q_FUNC_INFO << handles;
Tp::ContactAttributesMap contactAttributes;
foreach (const uint handle, handles) {
if (m_handles.contains(handle)){
QVariantMap attributes;
attributes["org.freedesktop.Telepathy.Connection/contact-id"] = m_handles.value(handle);
if (handle != selfHandle() && interfaces.contains("org.freedesktop.Telepathy.Connection.Interface.ContactList")) {
attributes["org.freedesktop.Telepathy.Connection.Interface.ContactList/subscribe"] = Tp::SubscriptionStateYes;
attributes["org.freedesktop.Telepathy.Connection.Interface.ContactList/publish"] = Tp::SubscriptionStateYes;
attributes["org.freedesktop.Telepathy.Connection.Interface.SimplePresence/presence"] = QVariant::fromValue(getPresence(handle));
}
contactAttributes[handle] = attributes;
}
}
return contactAttributes;
}
Tp::SimplePresence ConnectConnection::getPresence(uint handle)
{
return Tp::Presence::offline().barePresence();
}
uint ConnectConnection::setPresence(const QString &status, const QString &message, Tp::DBusError *error)
{
qDebug() << Q_FUNC_INFO << "not implemented";
return 0;
}
uint ConnectConnection::ensureContact(const QString &identifier)
{
uint handle = getHandle(identifier);
if (!handle) {
handle = addContact(identifier);
}
return handle;
}
uint ConnectConnection::addContacts(const QStringList &identifiers)
{
qDebug() << Q_FUNC_INFO;
uint handle = 0;
if (!m_handles.isEmpty()) {
handle = m_handles.keys().last();
}
QList<uint> newHandles;
foreach(const QString &identifier, identifiers) {
++handle;
m_handles.insert(handle, identifier);
newHandles << handle;
}
return handle;
}
uint ConnectConnection::addContact(const QString &identifier)
{
qDebug() << Q_FUNC_INFO;
return addContacts(QStringList() << identifier);
}
/* Receive message from someone to ourself */
bool ConnectConnection::receiveMessage(const QString &sender, const QString &message)
{
uint senderHandle, targetHandle;
Tp::HandleType handleType = Tp::HandleTypeContact;
senderHandle = targetHandle = ensureContact(sender);
Tp::DBusError error;
bool yours;
QVariantMap request;
request[TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType")] = TP_QT_IFACE_CHANNEL_TYPE_TEXT;
request[TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandle")] = targetHandle;
request[TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandleType")] = Tp::HandleTypeContact;
request[TP_QT_IFACE_CHANNEL + QLatin1String(".InitiatorHandle")] = targetHandle; //they texted you, so they started it
Tp::BaseChannelPtr channel = ensureChannel(request, yours, false, &error);
if (error.isValid()) {
qWarning() << "ensureChannel failed:" << error.name() << " " << error.message();
return false;
}
Tp::BaseChannelTextTypePtr textChannel = Tp::BaseChannelTextTypePtr::dynamicCast(channel->interface(TP_QT_IFACE_CHANNEL_TYPE_TEXT));
if (!textChannel) {
qDebug() << "Error, channel is not a textChannel??";
return false;
}
uint timestamp = QDateTime::currentMSecsSinceEpoch() / 1000;
Tp::MessagePartList body;
Tp::MessagePart text;
text["content-type"] = QDBusVariant("text/plain");
text["content"] = QDBusVariant(message);
body << text;
Tp::MessagePartList partList;
Tp::MessagePart header;
header["message-received"] = QDBusVariant(timestamp);
header["message-sender"] = QDBusVariant(senderHandle);
header["message-sender-id"] = QDBusVariant(sender);
//header["sender-nickname"] = QDBusVariant(pushName);
header["message-type"] = QDBusVariant(Tp::ChannelTextMessageTypeNormal);
partList << header << body;
textChannel->addReceivedMessage(partList);
return true;
}
void ConnectConnection::setContactList(const QStringList &identifiers)
{
// Actually it don't clear previous list (not implemented yet)
addContacts(identifiers);
// Tp::ContactSubscriptionMap changes;
// Tp::HandleIdentifierMap identifiers;
// Tp::HandleIdentifierMap removals;
QList<uint> handles;
for (int i = 0; i < identifiers.count(); ++i) {
handles.append(ensureContact(identifiers.at(i)));
}
}
uint ConnectConnection::getHandle(const QString &identifier) const
{
foreach (uint key, m_handles.keys()) {
if (m_handles.value(key) == identifier) {
return key;
}
}
return 0;
}

71
telepathy-cm/connection.h Normal file
View file

@ -0,0 +1,71 @@
/*
Copyright (C) 2014 Alexandr Akulich <akulichalexander@gmail.com>
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef CONNECTCM_CONNECTION_H
#define CONNECTCM_CONNECTION_H
#include "connectcm_export.h"
#include <TelepathyQt/BaseConnection>
#include <TelepathyQt/BaseChannel>
class CONNECTCM_EXPORT ConnectConnection : public Tp::BaseConnection
{
Q_OBJECT
public:
ConnectConnection(const QDBusConnection &dbusConnection,
const QString &cmName, const QString &protocolName,
const QVariantMap &parameters);
~ConnectConnection();
static Tp::SimpleStatusSpecMap getSimpleStatusSpecMap();
void connect(Tp::DBusError *error);
QStringList inspectHandles(uint handleType, const Tp::UIntList &handles, Tp::DBusError *error);
Tp::BaseChannelPtr createChannelCB(const QVariantMap &request, Tp::DBusError *error);
Tp::UIntList requestHandles(uint handleType, const QStringList &identifiers, Tp::DBusError *error);
Tp::ContactAttributesMap getContactListAttributes(const QStringList &interfaces, bool hold, Tp::DBusError *error);
Tp::ContactAttributesMap getContactAttributes(const Tp::UIntList &handles, const QStringList &interfaces, Tp::DBusError *error);
Tp::SimplePresence getPresence(uint handle);
uint setPresence(const QString &status, const QString &message, Tp::DBusError *error);
uint ensureContact(const QString &identifier);
public slots:
bool receiveMessage(const QString &sender, const QString &message);
void setContactList(const QStringList &identifiers);
signals:
void messageReceived(const QString &sender, const QString &message);
private:
uint getHandle(const QString &identifier) const;
uint addContact(const QString &identifier);
uint addContacts(const QStringList &identifiers);
Tp::BaseConnectionContactsInterfacePtr contactsIface;
Tp::BaseConnectionSimplePresenceInterfacePtr simplePresenceIface;
Tp::BaseConnectionContactListInterfacePtr contactListIface;
Tp::BaseConnectionAddressingInterfacePtr addressingIface;
Tp::BaseConnectionRequestsInterfacePtr requestsIface;
QMap<uint, QString> m_handles;
/* Maps a contact handle to its subscription state */
QString m_deviceId;
};
#endif // CONNECTCM_CONNECTION_H

View file

@ -0,0 +1,6 @@
[ConnectionManager]
BusName=org.freedesktop.Telepathy.ConnectionManager.kdeconnect
ObjectPath=/org/freedesktop/Telepathy/ConnectionManager/kdeconnect
Interfaces=
[Protocol kdeconnect]

View file

@ -0,0 +1,68 @@
#include <TelepathyQt/Types>
#include <TelepathyQt/BaseConnectionManager>
#include <TelepathyQt/Constants>
#include <TelepathyQt/Debug>
#include <TelepathyQt/Types>
#include <TelepathyQt/ConnectionManager>
#include <TelepathyQt/AccountManager>
#include <TelepathyQt/Account>
#include <TelepathyQt/PendingReady>
#include <TelepathyQt/PendingAccount>
#include <TelepathyQt/AccountSet>
#include <QtDBus/QDBusConnection>
#include "protocol.h"
#include "kdeconnecttelepathyprotocolfactory.h"
Tp::WeakPtr<KDEConnectTelepathyProtocol> KDEConnectTelepathyProtocolFactory::s_interface;
ConnectProtocolPtr KDEConnectTelepathyProtocolFactory::interface() {
if (s_interface.isNull()) {
Tp::registerTypes();
Tp::enableDebug(true);
Tp::enableWarnings(true);
ConnectProtocolPtr protocol = Tp::BaseProtocol::create<KDEConnectTelepathyProtocol>(
QDBusConnection::sessionBus(),
QLatin1String("kdeconnect"));
s_interface = protocol;
static Tp::BaseConnectionManagerPtr cm = Tp::BaseConnectionManager::create(
QDBusConnection::sessionBus(), QLatin1String("kdeconnect"));
protocol->setConnectionManagerName(cm->name());
protocol->setEnglishName(QLatin1String("KDE Connect"));
protocol->setIconName(QLatin1String("kdeconnect"));
protocol->setVCardField(QLatin1String("phone_number"));
cm->addProtocol(protocol);
cm->registerObject();
//fake being a client and create an account to use this connection
//maybe this should be per device.. with a device ID as a parameter, but lets keep it connect for now
Tp::AccountManagerPtr am = Tp::AccountManager::create(QDBusConnection::sessionBus());
QObject::connect(am->becomeReady(), &Tp::PendingOperation::finished, [am]() {
Tp::AccountSetPtr accounts = am->accountsByProtocol("kdeconnect");
if (!accounts) {
return;
}
if (accounts->accounts().isEmpty()) {
Tp::PendingAccount* pa = am->createAccount("kdeconnect", "kdeconnect", "kdeconnect", QVariantMap(), QVariantMap());
QObject::connect(pa, &Tp::PendingOperation::finished, pa, [pa](){
if (!pa->account()) {
return;
}
pa->account()->setEnabled(true);
pa->account()->setRequestedPresence(Tp::Presence::available());
});
} else {
Tp::AccountPtr account = accounts->accounts().first();
account->setRequestedPresence(Tp::Presence::available());
}
});
}
return s_interface.toStrongRef();
}

View file

@ -0,0 +1,18 @@
#include "protocol.h"
#include "connectcm_export.h"
typedef Tp::SharedPtr<KDEConnectTelepathyProtocol> ConnectProtocolPtr;
/*
* SingletonFactory for plugins to get access to the Telepathy connection manager
* Whilst the main process also holds a reference.
*
*/
class CONNECTCM_EXPORT KDEConnectTelepathyProtocolFactory
{
public:
static ConnectProtocolPtr interface();
private:
static Tp::WeakPtr<KDEConnectTelepathyProtocol> s_interface;
};

116
telepathy-cm/protocol.cpp Normal file
View file

@ -0,0 +1,116 @@
/*
Copyright (C) 2014 Alexandr Akulich <akulichalexander@gmail.com>
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "protocol.h"
#include "connection.h"
#include <TelepathyQt/BaseConnection>
#include <TelepathyQt/Constants>
#include <TelepathyQt/RequestableChannelClassSpec>
#include <TelepathyQt/RequestableChannelClassSpecList>
#include <TelepathyQt/Types>
#include <QLatin1String>
#include <QVariantMap>
KDEConnectTelepathyProtocol::KDEConnectTelepathyProtocol(const QDBusConnection &dbusConnection, const QString &name)
: BaseProtocol(dbusConnection, name)
{
// setParameters(Tp::ProtocolParameterList()
// << Tp::ProtocolParameter(QLatin1String("device_id"), QLatin1String("s"), Tp::ConnMgrParamFlagRequired)
// << Tp::ProtocolParameter(QLatin1String("self_name"), QLatin1String("s"), 0));
setRequestableChannelClasses(Tp::RequestableChannelClassSpecList() << Tp::RequestableChannelClassSpec::textChat());
// callbacks
setCreateConnectionCallback(memFun(this, &KDEConnectTelepathyProtocol::createConnection));
setIdentifyAccountCallback(memFun(this, &KDEConnectTelepathyProtocol::identifyAccount));
setNormalizeContactCallback(memFun(this, &KDEConnectTelepathyProtocol::normalizeContact));
addrIface = Tp::BaseProtocolAddressingInterface::create();
addrIface->setAddressableVCardFields(QStringList() << QLatin1String("x-example-vcard-field"));
addrIface->setAddressableUriSchemes(QStringList() << QLatin1String("example-uri-scheme"));
addrIface->setNormalizeVCardAddressCallback(memFun(this, &KDEConnectTelepathyProtocol::normalizeVCardAddress));
addrIface->setNormalizeContactUriCallback(memFun(this, &KDEConnectTelepathyProtocol::normalizeContactUri));
plugInterface(Tp::AbstractProtocolInterfacePtr::dynamicCast(addrIface));
/*
presenceIface = Tp::BaseProtocolPresenceInterface::create();
presenceIface->setStatuses(Tp::PresenceSpecList(ConnectConnection::getConnectStatusSpecMap()));
plugInterface(Tp::AbstractProtocolInterfacePtr::dynamicCast(presenceIface));*/
Tp::DBusError err;
}
KDEConnectTelepathyProtocol::~KDEConnectTelepathyProtocol()
{
}
void KDEConnectTelepathyProtocol::setConnectionManagerName(const QString &newName)
{
m_connectionManagerName = newName;
}
bool KDEConnectTelepathyProtocol::sendMessage(QString sender, QString message)
{
if (m_connection) {
return m_connection->receiveMessage(sender, message);
}
return false;
}
void KDEConnectTelepathyProtocol::setContactList(QStringList list)
{
if (m_connection) {
m_connection->setContactList(list);
}
}
Tp::BaseConnectionPtr KDEConnectTelepathyProtocol::createConnection(const QVariantMap &parameters, Tp::DBusError *error)
{
Q_UNUSED(error)
auto newConnection = Tp::BaseConnection::create<ConnectConnection>(m_connectionManagerName, this->name(), parameters);
connect(newConnection.constData(), SIGNAL(messageReceived(QString,QString)), SIGNAL(messageReceived(QString,QString)));
m_connection = newConnection;
return newConnection;
}
QString KDEConnectTelepathyProtocol::identifyAccount(const QVariantMap &parameters, Tp::DBusError *error)
{
qDebug() << Q_FUNC_INFO << parameters;
error->set(QLatin1String("IdentifyAccount.Error.NotImplemented"), QLatin1String(""));
return QString();
}
QString KDEConnectTelepathyProtocol::normalizeContact(const QString &contactId, Tp::DBusError *error)
{
qDebug() << Q_FUNC_INFO << contactId;
error->set(QLatin1String("NormalizeContact.Error.NotImplemented"), QLatin1String(""));
return QString();
}
QString KDEConnectTelepathyProtocol::normalizeVCardAddress(const QString &vcardField, const QString vcardAddress,
Tp::DBusError *error)
{
qDebug() << Q_FUNC_INFO << vcardField << vcardAddress;
error->set(QLatin1String("NormalizeVCardAddress.Error.NotImplemented"), QLatin1String(""));
return QString();
}
QString KDEConnectTelepathyProtocol::normalizeContactUri(const QString &uri, Tp::DBusError *error)
{
qDebug() << Q_FUNC_INFO << uri;
error->set(QLatin1String("NormalizeContactUri.Error.NotImplemented"), QLatin1String(""));
return QString();
}

68
telepathy-cm/protocol.h Normal file
View file

@ -0,0 +1,68 @@
/*
Copyright (C) 2014 Alexandr Akulich <akulichalexander@gmail.com>
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef CONNECTCM_PROTOCOL_H
#define CONNECTCM_PROTOCOL_H
#include "connectcm_export.h"
#include <TelepathyQt/BaseProtocol>
class ConnectConnection;
class CONNECTCM_EXPORT KDEConnectTelepathyProtocol : public Tp::BaseProtocol
{
Q_OBJECT
Q_DISABLE_COPY(KDEConnectTelepathyProtocol)
public:
KDEConnectTelepathyProtocol(const QDBusConnection &dbusConnection, const QString &name);
virtual ~KDEConnectTelepathyProtocol();
QString connectionManagerName() const;
void setConnectionManagerName(const QString &newName);
public slots:
bool sendMessage(QString sender, QString message);
void setContactList(QStringList list);
signals:
void contactsListChanged(QStringList);
void messageReceived(QString sender, QString message);
private:
Tp::BaseConnectionPtr createConnection(const QVariantMap &parameters, Tp::DBusError *error);
QString identifyAccount(const QVariantMap &parameters, Tp::DBusError *error);
QString normalizeContact(const QString &contactId, Tp::DBusError *error);
// Proto.I.Addressing
QString normalizeVCardAddress(const QString &vCardField, const QString vCardAddress,
Tp::DBusError *error);
QString normalizeContactUri(const QString &uri, Tp::DBusError *error);
Tp::BaseProtocolAddressingInterfacePtr addrIface;
Tp::BaseProtocolAvatarsInterfacePtr avatarsIface;
QString m_connectionManagerName;
//normally keeping the connection in the protocol would be really weird
//however we want to proxy the messages to the active connection and want a single entry point
Tp::SharedPtr<ConnectConnection> m_connection;
};
inline QString KDEConnectTelepathyProtocol::connectionManagerName() const
{
return m_connectionManagerName;
}
#endif // CONNECTCM_PROTOCOL_H

View file

@ -0,0 +1,73 @@
/*
Copyright (C) 2014 Alexandr Akulich <akulichalexander@gmail.com>
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "textchannel.h"
#include <TelepathyQt/Constants>
#include <TelepathyQt/RequestableChannelClassSpec>
#include <TelepathyQt/RequestableChannelClassSpecList>
#include <TelepathyQt/Types>
#include <QLatin1String>
#include <QVariantMap>
#include <QDebug>
ConnectTextChannel::ConnectTextChannel(QObject *connection, Tp::BaseChannel *baseChannel, uint targetHandle, const QString &identifier)
: Tp::BaseChannelTextType(baseChannel),
m_connection(connection),
m_identifier(identifier)
{
QStringList supportedContentTypes = QStringList() << "text/plain";
Tp::UIntList messageTypes = Tp::UIntList() << Tp::ChannelTextMessageTypeNormal;
uint messagePartSupportFlags = 0;
uint deliveryReportingSupport = 0;
m_messagesIface = Tp::BaseChannelMessagesInterface::create(this,
supportedContentTypes,
messageTypes,
messagePartSupportFlags,
deliveryReportingSupport);
baseChannel->plugInterface(Tp::AbstractChannelInterfacePtr::dynamicCast(m_messagesIface));
m_messagesIface->setSendMessageCallback(Tp::memFun(this, &ConnectTextChannel::sendMessageCallback));
}
ConnectTextChannelPtr ConnectTextChannel::create(QObject *connection, Tp::BaseChannel *baseChannel, uint targetHandle, const QString &identifier)
{
return ConnectTextChannelPtr(new ConnectTextChannel(connection, baseChannel, targetHandle, identifier));
}
ConnectTextChannel::~ConnectTextChannel()
{
}
QString ConnectTextChannel::sendMessageCallback(const Tp::MessagePartList &messageParts, uint flags, Tp::DBusError *error)
{
QString content;
for (Tp::MessagePartList::const_iterator i = messageParts.begin()+1; i != messageParts.end(); ++i) {
if(i->count(QLatin1String("content-type"))
&& i->value(QLatin1String("content-type")).variant().toString() == QLatin1String("text/plain")
&& i->count(QLatin1String("content")))
{
content = i->value(QLatin1String("content")).variant().toString();
break;
}
}
QMetaObject::invokeMethod(m_connection, "messageReceived", Q_ARG(QString, m_identifier), Q_ARG(QString, content));
return QString();
}

View file

@ -0,0 +1,46 @@
/*
Copyright (C) 2014 Alexandr Akulich <akulichalexander@gmail.com>
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef CONNECTCM_TEXTCHANNEL_H
#define CONNECTCM_TEXTCHANNEL_H
#include "connectcm_export.h"
#include <TelepathyQt/BaseChannel>
class ConnectTextChannel;
typedef Tp::SharedPtr<ConnectTextChannel> ConnectTextChannelPtr;
class CONNECTCM_EXPORT ConnectTextChannel : public Tp::BaseChannelTextType
{
Q_OBJECT
public:
static ConnectTextChannelPtr create(QObject *connection, Tp::BaseChannel *baseChannel, uint targetHandle, const QString &identifier);
virtual ~ConnectTextChannel();
QString sendMessageCallback(const Tp::MessagePartList &messageParts, uint flags, Tp::DBusError *error);
private:
ConnectTextChannel(QObject *connection, Tp::BaseChannel *baseChannel, uint targetHandle, const QString &identifier);
QObject *m_connection;
QString m_identifier;
Tp::BaseChannelTextTypePtr m_channelTextType;
Tp::BaseChannelMessagesInterfacePtr m_messagesIface;
};
#endif // CONNECTCM_TEXTCHANNEL_H