Improved MPRIS controls.

MPRIS now uses xml dbus interfaces-
MPRIS now detects when properties like volume or playbas status change.
Link providers now emit connectionLost and connectionReceived, like in Android.
Disabled connection notifications.
Added some missing const modifiers.
This commit is contained in:
Albert Vaca 2013-08-10 05:21:55 +02:00
parent af02ca7c74
commit 9022823aef
18 changed files with 231 additions and 76 deletions

View file

@ -9,7 +9,10 @@ find_package(KDE4 REQUIRED)
include(KDE4Defaults)
include_directories(${KDE4_INCLUDES})
include_directories(${CMAKE_CURRENT_BINARY_DIR})
add_subdirectory(daemon)
add_subdirectory(kcm)
#add_subdirectory(kioslave)
add_subdirectory(test)

View file

@ -23,6 +23,18 @@ set(kded_kdeconnect_SRCS
device.cpp
)
qt4_add_dbus_interface(
kded_kdeconnect_SRCS
packageinterfaces/mprisdbusinterface.xml
mprisdbusinterface
)
qt4_add_dbus_interface(
kded_kdeconnect_SRCS
packageinterfaces/propertiesInterface.xml
propertiesdbusinterface
)
kde4_add_plugin(kded_kdeconnect ${kded_kdeconnect_SRCS})
target_link_libraries(kded_kdeconnect

View file

@ -91,14 +91,14 @@ Daemon::Daemon(QObject *parent, const QList<QVariant>&)
device,SLOT(sendPackage(const NetworkPackage&)));
}
}
QNetworkSession* network = new QNetworkSession(QNetworkConfigurationManager().defaultConfiguration());
//Listen to incomming connections
Q_FOREACH (LinkProvider* a, mLinkProviders) {
connect(network, SIGNAL(stateChanged(QNetworkSession::State)),
a, SLOT(onNetworkChange(QNetworkSession::State)));
connect(a,SIGNAL(onNewDeviceLink(NetworkPackage,DeviceLink*)),
connect(a,SIGNAL(onConnectionReceived(NetworkPackage,DeviceLink*)),
this,SLOT(onNewDeviceLink(NetworkPackage,DeviceLink*)));
}
@ -130,7 +130,6 @@ QStringList Daemon::devices()
return mDevices.keys();
}
void Daemon::onNewDeviceLink(const NetworkPackage& identityPackage, DeviceLink* dl)
{
const QString& id = identityPackage.get<QString>("deviceId");
@ -143,16 +142,16 @@ void Daemon::onNewDeviceLink(const NetworkPackage& identityPackage, DeviceLink*
Device* device = mDevices[id];
device->addLink(dl);
if (device->paired()) {
/*if (device->paired()) {
KNotification* notification = new KNotification("pingReceived"); //KNotification::Persistent
notification->setPixmap(KIcon("dialog-ok").pixmap(48, 48));
notification->setComponentData(KComponentData("kdeconnect", "kdeconnect"));
notification->setTitle(device->name());
notification->setText("Succesfully connected");
notification->sendEvent();
}
}*/
emit deviceStatusChanged(id);
Q_EMIT deviceStatusChanged(id);
} else {
qDebug() << "It is a new device";
@ -164,10 +163,8 @@ void Daemon::onNewDeviceLink(const NetworkPackage& identityPackage, DeviceLink*
Q_FOREACH (PackageInterface* pr, mPackageInterfaces) {
connect(device,SIGNAL(receivedPackage(const Device&, const NetworkPackage&)),
pr,SLOT(receivePackage(const Device&, const NetworkPackage&)));
connect(pr,SIGNAL(sendPackage(const NetworkPackage&)),
device,SLOT(sendPackage(const NetworkPackage&)));
}
emit newDeviceAdded(id);
Q_EMIT newDeviceAdded(id);
}
}

View file

@ -74,6 +74,10 @@ void Device::addLink(DeviceLink* link)
connect(link,SIGNAL(destroyed(QObject*)),this,SLOT(linkDestroyed(QObject*)));
m_deviceLinks.append(link);
//TODO: Somehow destroy previous device links from the same provider,
//but if we do it here, the provider will keep a broken ref!
connect(link, SIGNAL(receivedPackage(NetworkPackage)), this, SLOT(privateReceivedPackage(NetworkPackage)));
qSort(m_deviceLinks.begin(),m_deviceLinks.end(),lessThan);
@ -100,7 +104,7 @@ void Device::removeLink(DeviceLink* link)
}
}
bool Device::sendPackage(const NetworkPackage& np)
bool Device::sendPackage(const NetworkPackage& np) const
{
Q_FOREACH(DeviceLink* dl, m_deviceLinks) {
if (dl->sendPackage(np)) return true;
@ -136,5 +140,3 @@ void Device::sendPing()
qDebug() << "sendPing:" << success;
}

View file

@ -61,7 +61,7 @@ public:
Q_SIGNALS:
void receivedPackage(const Device& device, const NetworkPackage& np);
public Q_SLOTS:
bool sendPackage(const NetworkPackage& np);
bool sendPackage(const NetworkPackage& np) const;
//Public dbus operations
public Q_SLOTS:

View file

@ -39,10 +39,10 @@ public:
const QString& deviceId() { return mDeviceId; }
LinkProvider* provider() { return mLinkProvider; }
virtual bool sendPackage(const NetworkPackage& np) = 0;
virtual bool sendPackage(const NetworkPackage& np) const = 0;
signals:
void receivedPackage(const NetworkPackage& np);
void receivedPackage(const NetworkPackage& np) const;
private:
QString mDeviceId;

View file

@ -31,8 +31,8 @@ class EchoDeviceLink
public:
EchoDeviceLink(const QString& d, LoopbackLinkProvider* a);
bool sendPackage(const NetworkPackage& np) {
emit receivedPackage(np);
bool sendPackage(const NetworkPackage& np) const {
Q_EMIT receivedPackage(np);
return true;
}

View file

@ -29,7 +29,7 @@ TcpDeviceLink::TcpDeviceLink(const QString& d, LinkProvider* a, QTcpSocket* sock
connect(mSocket, SIGNAL(readyRead()), this, SLOT(dataReceived()));
}
bool TcpDeviceLink::sendPackage(const NetworkPackage& np)
bool TcpDeviceLink::sendPackage(const NetworkPackage& np) const
{
int written = mSocket->write(np.serialize());
return written != -1;

View file

@ -38,7 +38,7 @@ class TcpDeviceLink
public:
TcpDeviceLink(const QString& d, LinkProvider* a, QTcpSocket* socket);
bool sendPackage(const NetworkPackage& np);
bool sendPackage(const NetworkPackage& np) const;
private Q_SLOTS:
void dataReceived();

View file

@ -101,7 +101,7 @@ void AvahiTcpLinkProvider::dataReceived()
qDebug() << "AvahiTcpLinkProvider creating link to device" << id << "(" << socket->peerAddress() << ")";
emit onNewDeviceLink(np, dl);
Q_EMIT onConnectionReceived(np, dl);
disconnect(socket,SIGNAL(readyRead()),this,SLOT(dataReceived()));
@ -111,9 +111,11 @@ void AvahiTcpLinkProvider::dataReceived()
}
void AvahiTcpLinkProvider::deviceLinkDestroyed(QObject* deviceLink)
void AvahiTcpLinkProvider::deviceLinkDestroyed(QObject* uncastedDeviceLink)
{
const QString& id = ((DeviceLink*)deviceLink)->deviceId();
DeviceLink* deviceLink = (DeviceLink*)uncastedDeviceLink;
Q_EMIT onConnectionLost(deviceLink);
const QString& id = deviceLink->deviceId();
if (links.contains(id)) links.remove(id);
}

View file

@ -156,7 +156,7 @@ void BroadcastTcpLinkProvider::connected()
//TODO: Use reverse connection too to preffer connecting a unstable device (a phone) to a stable device (a computer)
if (success) {
qDebug() << "Handshaking done (i'm the existing device)";
emit onNewDeviceLink(*np, dl);
Q_EMIT onConnectionReceived(*np, dl);
} else {
//I think this will never happen
qDebug() << "Fallback (2), try reverse connection";
@ -218,7 +218,7 @@ void BroadcastTcpLinkProvider::dataReceived()
qDebug() << "Handshaking done (i'm the new device)";
emit onNewDeviceLink(np, dl);
Q_EMIT onConnectionReceived(np, dl);
disconnect(socket,SIGNAL(readyRead()),this,SLOT(dataReceived()));
@ -228,14 +228,12 @@ void BroadcastTcpLinkProvider::dataReceived()
}
void BroadcastTcpLinkProvider::deviceLinkDestroyed(QObject* deviceLink)
void BroadcastTcpLinkProvider::deviceLinkDestroyed(QObject* uncastedDeviceLink)
{
const QString& id = ((DeviceLink*)deviceLink)->deviceId();
qDebug() << "deviceLinkDestroyed";
if (links.contains(id)) {
qDebug() << "removing link from link list";
links.remove(id);
}
DeviceLink* deviceLink = (DeviceLink*)uncastedDeviceLink;
Q_EMIT onConnectionLost(deviceLink);
const QString& id = deviceLink->deviceId();
if (links.contains(id)) links.remove(id);
}
BroadcastTcpLinkProvider::~BroadcastTcpLinkProvider()

View file

@ -55,7 +55,8 @@ public Q_SLOTS:
Q_SIGNALS:
//NOTE: The provider will to destroy the DeviceLink when it's no longer accessible,
// and every user should listen to the destroyed signal to remove its references.
void onNewDeviceLink(const NetworkPackage& identityPackage, DeviceLink*);
void onConnectionReceived(const NetworkPackage& identityPackage, DeviceLink*);
void onConnectionLost(DeviceLink*);
};

View file

@ -38,6 +38,6 @@ LoopbackLinkProvider::~LoopbackLinkProvider()
void LoopbackLinkProvider::setDiscoverable(bool b)
{
qDebug() << "Echo Device discovery emitted";
if (b) emit onNewDeviceLink(identityPackage, echoDeviceLink);
if (b) Q_EMIT onConnectionReceived(identityPackage, echoDeviceLink);
}

View file

@ -57,7 +57,7 @@ public:
}
template<typename T> void set(const QString& key, const T& value) { mBody[key] = QVariant(value); }
bool has(const QString& key) { return mBody.contains(key); }
bool has(const QString& key) const { return mBody.contains(key); }
private:
void setId(long id) { mId = id; }

View file

@ -20,6 +20,8 @@
#include "mpriscontrolpackageinterface.h"
#include "propertiesdbusinterface.h"
#include <QDebug>
#include <QDBusConnection>
#include <QDBusInterface>
@ -52,7 +54,7 @@ void MprisControlPackageInterface::serviceOwnerChanged(const QString &name,
if (name.startsWith("org.mpris.MediaPlayer2")) {
qDebug() << "Something registered in bus" << name << oldOwner << newOwner;
qDebug() << "Something (un)registered in bus" << name << oldOwner << newOwner;
if (oldOwner.isEmpty()) {
addPlayer(name);
@ -62,14 +64,59 @@ void MprisControlPackageInterface::serviceOwnerChanged(const QString &name,
}
}
void MprisControlPackageInterface::addPlayer(const QString& ifaceName)
void MprisControlPackageInterface::addPlayer(const QString& service)
{
QDBusInterface interface(ifaceName, "/org/mpris/MediaPlayer2", "org.mpris.MediaPlayer2");
//TODO: Make this async
QString identity = interface.property("Identity").toString();
playerList[identity] = ifaceName;
qDebug() << "addPlayer" << ifaceName << identity;
QDBusInterface mprisInterface(service, "/org/mpris/MediaPlayer2", "org.mpris.MediaPlayer2");
const QString& identity = mprisInterface.property("Identity").toString();
playerList[identity] = service;
qDebug() << "addPlayer" << service << identity;
sendPlayerList();
OrgFreedesktopDBusPropertiesInterface* freedesktopInterface = new OrgFreedesktopDBusPropertiesInterface(service, "/org/mpris/MediaPlayer2", QDBusConnection::sessionBus(), this);
connect(freedesktopInterface, SIGNAL(PropertiesChanged(QString, QVariantMap, QStringList)), this, SLOT(propertiesChanged(QString, QVariantMap)));
}
void MprisControlPackageInterface::propertiesChanged(const QString& propertyInterface, const QVariantMap& properties)
{
NetworkPackage np(PACKAGE_TYPE_MPRIS);
bool somethingToSend = false;
if (properties.contains("Volume")) {
int volume = (int) (properties["Volume"].toDouble()*100);
if (volume != prevVolume) {
np.set("volume",volume);
prevVolume = volume;
somethingToSend = true;
}
}
if (properties.contains("Metadata")) {
QDBusArgument bullshit = qvariant_cast<QDBusArgument>(properties["Metadata"]);
QVariantMap nowPlayingMap;
bullshit >> nowPlayingMap;
if (nowPlayingMap.contains("xesam:title")) {
QString nowPlaying = nowPlayingMap["xesam:title"].toString();
if (nowPlayingMap.contains("xesam:artist")) {
nowPlaying = nowPlayingMap["xesam:artist"].toString() + " - " + nowPlaying;
}
np.set("nowPlaying",nowPlaying);
somethingToSend = true;
}
}
if (properties.contains("PlaybackStatus")) {
bool playing = (properties["PlaybackStatus"].toString() == "Playing");
np.set("isPlaying", playing);
somethingToSend = true;
}
if (somethingToSend) {
OrgFreedesktopDBusPropertiesInterface* interface = (OrgFreedesktopDBusPropertiesInterface*)sender();
const QString& service = interface->service();
const QString& player = playerList.key(service);
np.set("player", player);
sendPackage(np);
}
}
void MprisControlPackageInterface::removePlayer(const QString& ifaceName)
@ -78,53 +125,77 @@ void MprisControlPackageInterface::removePlayer(const QString& ifaceName)
sendPlayerList();
}
void MprisControlPackageInterface::sendPlayerList()
{
NetworkPackage np(PACKAGE_TYPE_MPRIS);
np.set("playerList",playerList.keys());
sendPackage(np);
}
bool MprisControlPackageInterface::receivePackage (const Device& device, const NetworkPackage& np)
{
Q_UNUSED(device);
if (np.type() != PACKAGE_TYPE_MPRIS) return false;
QString player = np.get<QString>("player");
if (!playerList.contains(player)) {
sendPlayerList();
return true;
//Send the player list
const QString& player = np.get<QString>("player");
bool valid_player = playerList.contains(player);
if (!valid_player || np.get<bool>("requestPlayerList")) {
sendPlayerList(&device);
if (!valid_player) {
return true;
}
}
QDBusInterface mprisInterface(playerList[player], "/org/mpris/MediaPlayer2", "org.mpris.MediaPlayer2.Player");
if (np.get<bool>("requestPlayerList")) {
sendPlayerList();
}
QString action = np.get<QString>("action");
if (!action.isEmpty()) {
//Do something to the mpris interface
OrgMprisMediaPlayer2PlayerInterface mprisInterface(playerList[player], "/org/mpris/MediaPlayer2", QDBusConnection::sessionBus());
if (np.has("action")) {
const QString& action = np.get<QString>("action");
qDebug() << "Calling action" << action << "in" << playerList[player];
//TODO: Check for valid actions
mprisInterface.call(action);
sendNowPlaying(mprisInterface);
} else if (np.get<bool>("requestNowPlaying")) {
sendNowPlaying(mprisInterface);
}
if (np.has("setVolume")) {
double volume = np.get<int>("setVolume")/100.f;
qDebug() << "Setting volume" << volume << "to" << playerList[player];
mprisInterface.setVolume(volume);
}
//Send something read from the mpris interface
NetworkPackage answer(PACKAGE_TYPE_MPRIS);
bool somethingToSend = false;
if (np.get<bool>("requestNowPlaying")) {
QVariantMap nowPlayingMap = mprisInterface.metadata();
QString nowPlaying = nowPlayingMap["xesam:title"].toString();
if (nowPlayingMap.contains("xesam:artist")) {
nowPlaying = nowPlayingMap["xesam:artist"].toString() + " - " + nowPlaying;
}
answer.set("nowPlaying",nowPlaying);
bool playing = (mprisInterface.playbackStatus() == QLatin1String("Playing"));
answer.set("isPlaying", playing);
somethingToSend = true;
}
if (np.get<bool>("requestVolume")) {
int volume = (int)(mprisInterface.volume() * 100);
answer.set("volume",volume);
somethingToSend = true;
}
if (somethingToSend) {
answer.set("player", player);
device.sendPackage(answer);
}
return true;
}
void MprisControlPackageInterface::sendNowPlaying(const QDBusInterface& mprisInterface)
void MprisControlPackageInterface::sendPlayerList(const Device* device)
{
QVariantMap nowPlayingMap = mprisInterface.property("Metadata").toMap();
QString nowPlaying = nowPlayingMap["xesam:title"].toString();
if (nowPlayingMap.contains("xesam:artist")) {
nowPlaying = nowPlayingMap["xesam:artist"].toString() + " - " + nowPlaying;
}
NetworkPackage np(PACKAGE_TYPE_MPRIS);
np.set("nowPlaying",nowPlaying);
sendPackage(np);
np.set("playerList",playerList.keys());
if (device == NULL) Q_EMIT sendPackage(np);
else device->sendPackage(np);
}

View file

@ -22,6 +22,7 @@
#define MPRISCONTROLPACKAGEINTERFACE_H
#include "packageinterface.h"
#include "mprisdbusinterface.h"
#include <QSet>
#include <QString>
@ -37,16 +38,16 @@ public:
MprisControlPackageInterface();
virtual bool receivePackage(const Device& device, const NetworkPackage& np);
public Q_SLOTS:
private Q_SLOTS:
void serviceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner);
void propertiesChanged(const QString& interface, const QVariantMap& properties);
private:
QHash<QString, QString> playerList;
void addPlayer(const QString& ifaceName);
void removePlayer(const QString& ifaceName);
void sendPlayerList();
void sendNowPlaying(const QDBusInterface& interface);
void sendPlayerList(const Device* device = 0);
int prevVolume;
};

View file

@ -0,0 +1,45 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.mpris.MediaPlayer2.Player">
<method name="Next"/>
<method name="Previous"/>
<method name="Pause"/>
<method name="PlayPause"/>
<method name="Stop"/>
<method name="Play"/>
<method name="Seek">
<arg direction="in" type="x" name="Offset"/>
</method>
<method name="SetPosition">
<arg direction="in" type="o" name="TrackId"/>
<arg direction="in" type="x" name="Position"/>
</method>
<method name="OpenUri">
<arg direction="in" type="s"/>
</method>
<!-- Signals -->
<signal name="Seeked">
<arg type="x" name="Position"/>
</signal>
<!-- Properties -->
<property access="read" type="s" name="PlaybackStatus"/>
<property access="readwrite" type="s" name="LoopStatus"/>
<property access="readwrite" type="d" name="Rate"/>
<property access="readwrite" type="b" name="Shuffle"/>
<property access="read" type="a{sv}" name="Metadata">
<annotation value="QVariantMap" name="com.trolltech.QtDBus.QtTypeName"/>
</property>
<property access="readwrite" type="d" name="Volume"/>
<property access="read" type="x" name="Position"/>
<property access="read" type="d" name="MinimumRate"/>
<property access="read" type="d" name="MaximumRate"/>
<property access="read" type="b" name="CanGoNext"/>
<property access="read" type="b" name="CanGoPrevious"/>
<property access="read" type="b" name="CanPlay"/>
<property access="read" type="b" name="CanPause"/>
<property access="read" type="b" name="CanSeek"/>
<property access="read" type="b" name="CanControl"/>
</interface>
</node>

View file

@ -0,0 +1,23 @@
<?xml version="1.0"?>
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<!-- GDBus 2.32.4 -->
<node>
<interface name="org.freedesktop.DBus.Properties">
<method name="Get">
<arg type="s" name="interface_name" direction="in"/>
<arg type="s" name="property_name" direction="in"/>
<arg type="v" name="value" direction="out"/>
</method>
<method name="Set">
<arg type="s" name="interface_name" direction="in"/>
<arg type="s" name="property_name" direction="in"/>
<arg type="v" name="value" direction="in"/>
</method>
<signal name="PropertiesChanged">
<arg type="s" name="interface_name"/>
<arg type="a{sv}" name="changed_properties"/>
<arg type="as" name="invalidated_properties"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap" />
</signal>
</interface>
</node>